From 8d836581f9b1f57ceaa2b47654754ef1260b410b Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Wed, 16 Sep 2026 15:00:32 +0200 Subject: ata: libata-scsi: fix ata_dsm_trim_pages() kernel-doc make htmldocs fails with: Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301: ERROR: Unexpected indentation. [docutils] The Return: section of ata_dsm_trim_pages() ends its first sentence with a colon and continues with an indented bullet list. reStructuredText requires a blank line before an indented block, so docutils chokes on the list. Simply adding the missing blank line does not work either: Return: is a kernel-doc "special section", which is terminated by the first blank line, so the bullet list would end up in the Description section, detached from the sentence introducing it. Spell the two bounds out as prose instead, so that the Return: section stays self-contained. Documentation-only change. Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND") Reported-by: Thomas Huth Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/ Reviewed-by: Damien Le Moal Reviewed-by: Thomas Huth Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org Signed-off-by: Niklas Cassel --- drivers/usb/Kconfig | 162 + drivers/usb/Makefile | 67 + drivers/usb/atm/Kconfig | 68 + drivers/usb/atm/Makefile | 9 + drivers/usb/atm/cxacru.c | 1379 +++ drivers/usb/atm/speedtch.c | 949 +++ drivers/usb/atm/ueagle-atm.c | 2753 ++++++ drivers/usb/atm/usbatm.c | 1329 +++ drivers/usb/atm/usbatm.h | 185 + drivers/usb/atm/xusbatm.c | 218 + drivers/usb/c67x00/Makefile | 8 + drivers/usb/c67x00/c67x00-drv.c | 215 + drivers/usb/c67x00/c67x00-hcd.c | 399 + drivers/usb/c67x00/c67x00-hcd.h | 118 + drivers/usb/c67x00/c67x00-ll-hpi.c | 477 ++ drivers/usb/c67x00/c67x00-sched.c | 1148 +++ drivers/usb/c67x00/c67x00.h | 280 + drivers/usb/cdns3/Kconfig | 115 + drivers/usb/cdns3/Makefile | 30 + drivers/usb/cdns3/cdns3-debug.h | 157 + drivers/usb/cdns3/cdns3-ep0.c | 895 ++ drivers/usb/cdns3/cdns3-gadget.c | 3525 ++++++++ drivers/usb/cdns3/cdns3-gadget.h | 1377 +++ drivers/usb/cdns3/cdns3-imx.c | 437 + drivers/usb/cdns3/cdns3-pci-wrap.c | 203 + drivers/usb/cdns3/cdns3-plat.c | 372 + drivers/usb/cdns3/cdns3-starfive.c | 244 + drivers/usb/cdns3/cdns3-ti.c | 279 + drivers/usb/cdns3/cdns3-trace.c | 11 + drivers/usb/cdns3/cdns3-trace.h | 496 ++ drivers/usb/cdns3/cdnsp-debug.h | 584 ++ drivers/usb/cdns3/cdnsp-ep0.c | 483 ++ drivers/usb/cdns3/cdnsp-gadget.c | 2095 +++++ drivers/usb/cdns3/cdnsp-gadget.h | 1617 ++++ drivers/usb/cdns3/cdnsp-mem.c | 1365 +++ drivers/usb/cdns3/cdnsp-pci.c | 247 + drivers/usb/cdns3/cdnsp-ring.c | 2507 ++++++ drivers/usb/cdns3/cdnsp-trace.c | 12 + drivers/usb/cdns3/cdnsp-trace.h | 801 ++ drivers/usb/cdns3/core.c | 564 ++ drivers/usb/cdns3/core.h | 149 + drivers/usb/cdns3/drd.c | 551 ++ drivers/usb/cdns3/drd.h | 226 + drivers/usb/cdns3/gadget-export.h | 31 + drivers/usb/cdns3/host-export.h | 27 + drivers/usb/cdns3/host.c | 168 + drivers/usb/chipidea/Kconfig | 64 + drivers/usb/chipidea/Makefile | 19 + drivers/usb/chipidea/bits.h | 147 + drivers/usb/chipidea/ci.h | 495 ++ drivers/usb/chipidea/ci_hdrc_imx.c | 786 ++ drivers/usb/chipidea/ci_hdrc_imx.h | 42 + drivers/usb/chipidea/ci_hdrc_msm.c | 307 + drivers/usb/chipidea/ci_hdrc_npcm.c | 112 + drivers/usb/chipidea/ci_hdrc_pci.c | 170 + drivers/usb/chipidea/ci_hdrc_tegra.c | 415 + drivers/usb/chipidea/ci_hdrc_usb2.c | 129 + drivers/usb/chipidea/core.c | 1506 ++++ drivers/usb/chipidea/debug.c | 313 + drivers/usb/chipidea/host.c | 509 ++ drivers/usb/chipidea/host.h | 30 + drivers/usb/chipidea/otg.c | 275 + drivers/usb/chipidea/otg.h | 25 + drivers/usb/chipidea/otg_fsm.c | 851 ++ drivers/usb/chipidea/otg_fsm.h | 101 + drivers/usb/chipidea/trace.c | 23 + drivers/usb/chipidea/trace.h | 92 + drivers/usb/chipidea/udc.c | 2451 ++++++ drivers/usb/chipidea/udc.h | 100 + drivers/usb/chipidea/ulpi.c | 108 + drivers/usb/chipidea/usbmisc_imx.c | 1555 ++++ drivers/usb/class/Kconfig | 49 + drivers/usb/class/Makefile | 10 + drivers/usb/class/cdc-acm.c | 2189 +++++ drivers/usb/class/cdc-acm.h | 119 + drivers/usb/class/cdc-wdm.c | 1380 +++ drivers/usb/class/usblp.c | 1488 ++++ drivers/usb/class/usbtmc.c | 2623 ++++++ drivers/usb/common/Kconfig | 51 + drivers/usb/common/Makefile | 13 + drivers/usb/common/common.c | 452 + drivers/usb/common/common.h | 14 + drivers/usb/common/debug.c | 319 + drivers/usb/common/led.c | 48 + drivers/usb/common/ulpi.c | 379 + drivers/usb/common/usb-conn-gpio.c | 375 + drivers/usb/common/usb-otg-fsm.c | 453 + drivers/usb/core/Kconfig | 145 + drivers/usb/core/Makefile | 25 + drivers/usb/core/buffer.c | 215 + drivers/usb/core/config.c | 1163 +++ drivers/usb/core/devices.c | 546 ++ drivers/usb/core/devio.c | 2933 +++++++ drivers/usb/core/driver.c | 2085 +++++ drivers/usb/core/endpoint.c | 184 + drivers/usb/core/file.c | 193 + drivers/usb/core/generic.c | 336 + drivers/usb/core/hcd-pci.c | 652 ++ drivers/usb/core/hcd.c | 3194 +++++++ drivers/usb/core/hub.c | 6580 ++++++++++++++ drivers/usb/core/hub.h | 185 + drivers/usb/core/ledtrig-usbport.c | 358 + drivers/usb/core/message.c | 2521 ++++++ drivers/usb/core/notify.c | 68 + drivers/usb/core/of.c | 176 + drivers/usb/core/offload.c | 152 + drivers/usb/core/otg_productlist.h | 102 + drivers/usb/core/phy.c | 371 + drivers/usb/core/phy.h | 33 + drivers/usb/core/port.c | 866 ++ drivers/usb/core/quirks.c | 817 ++ drivers/usb/core/sysfs.c | 1373 +++ drivers/usb/core/trace.c | 6 + drivers/usb/core/trace.h | 61 + drivers/usb/core/urb.c | 1021 +++ drivers/usb/core/usb-acpi.c | 386 + drivers/usb/core/usb.c | 1294 +++ drivers/usb/core/usb.h | 217 + drivers/usb/dwc2/Kconfig | 97 + drivers/usb/dwc2/Makefile | 29 + drivers/usb/dwc2/core.c | 1218 +++ drivers/usb/dwc2/core.h | 1571 ++++ drivers/usb/dwc2/core_intr.c | 908 ++ drivers/usb/dwc2/debug.h | 19 + drivers/usb/dwc2/debugfs.c | 813 ++ drivers/usb/dwc2/drd.c | 259 + drivers/usb/dwc2/gadget.c | 5748 +++++++++++++ drivers/usb/dwc2/hcd.c | 5997 +++++++++++++ drivers/usb/dwc2/hcd.h | 787 ++ drivers/usb/dwc2/hcd_ddma.c | 1354 +++ drivers/usb/dwc2/hcd_intr.c | 2268 +++++ drivers/usb/dwc2/hcd_queue.c | 2032 +++++ drivers/usb/dwc2/hw.h | 889 ++ drivers/usb/dwc2/params.c | 1083 +++ drivers/usb/dwc2/pci.c | 136 + drivers/usb/dwc2/platform.c | 811 ++ drivers/usb/dwc3/Kconfig | 237 + drivers/usb/dwc3/Makefile | 63 + drivers/usb/dwc3/core.c | 2936 +++++++ drivers/usb/dwc3/core.h | 1752 ++++ drivers/usb/dwc3/debug.h | 450 + drivers/usb/dwc3/debugfs.c | 1039 +++ drivers/usb/dwc3/drd.c | 631 ++ drivers/usb/dwc3/dwc3-am62.c | 421 + drivers/usb/dwc3/dwc3-apple.c | 523 ++ drivers/usb/dwc3/dwc3-exynos.c | 285 + drivers/usb/dwc3/dwc3-generic-plat.c | 256 + drivers/usb/dwc3/dwc3-google.c | 626 ++ drivers/usb/dwc3/dwc3-haps.c | 148 + drivers/usb/dwc3/dwc3-imx.c | 448 + drivers/usb/dwc3/dwc3-imx8mp.c | 458 + drivers/usb/dwc3/dwc3-keystone.c | 223 + drivers/usb/dwc3/dwc3-meson-g12a.c | 989 +++ drivers/usb/dwc3/dwc3-octeon.c | 530 ++ drivers/usb/dwc3/dwc3-of-simple.c | 196 + drivers/usb/dwc3/dwc3-omap.c | 626 ++ drivers/usb/dwc3/dwc3-pci.c | 552 ++ drivers/usb/dwc3/dwc3-qcom-legacy.c | 935 ++ drivers/usb/dwc3/dwc3-qcom.c | 861 ++ drivers/usb/dwc3/dwc3-rtk.c | 459 + drivers/usb/dwc3/dwc3-st.c | 369 + drivers/usb/dwc3/dwc3-xilinx.c | 439 + drivers/usb/dwc3/ep0.c | 1233 +++ drivers/usb/dwc3/gadget.c | 4910 +++++++++++ drivers/usb/dwc3/gadget.h | 153 + drivers/usb/dwc3/glue.h | 196 + drivers/usb/dwc3/host.c | 235 + drivers/usb/dwc3/io.h | 60 + drivers/usb/dwc3/trace.c | 11 + drivers/usb/dwc3/trace.h | 387 + drivers/usb/dwc3/ulpi.c | 129 + drivers/usb/early/Makefile | 7 + drivers/usb/early/ehci-dbgp.c | 1092 +++ drivers/usb/early/xhci-dbc.c | 1011 +++ drivers/usb/early/xhci-dbc.h | 222 + drivers/usb/fotg210/Kconfig | 38 + drivers/usb/fotg210/Makefile | 6 + drivers/usb/fotg210/fotg210-core.c | 220 + drivers/usb/fotg210/fotg210-hcd.c | 5643 ++++++++++++ drivers/usb/fotg210/fotg210-hcd.h | 689 ++ drivers/usb/fotg210/fotg210-udc.c | 1306 +++ drivers/usb/fotg210/fotg210-udc.h | 254 + drivers/usb/fotg210/fotg210.h | 61 + drivers/usb/gadget/Kconfig | 511 ++ drivers/usb/gadget/Makefile | 12 + drivers/usb/gadget/composite.c | 2806 ++++++ drivers/usb/gadget/config.c | 213 + drivers/usb/gadget/configfs.c | 2108 +++++ drivers/usb/gadget/configfs.h | 21 + drivers/usb/gadget/epautoconf.c | 214 + drivers/usb/gadget/function/Makefile | 58 + drivers/usb/gadget/function/f_acm.c | 902 ++ drivers/usb/gadget/function/f_ecm.c | 981 +++ drivers/usb/gadget/function/f_eem.c | 687 ++ drivers/usb/gadget/function/f_fs.c | 4470 ++++++++++ drivers/usb/gadget/function/f_hid.c | 1700 ++++ drivers/usb/gadget/function/f_loopback.c | 597 ++ drivers/usb/gadget/function/f_mass_storage.c | 3623 ++++++++ drivers/usb/gadget/function/f_mass_storage.h | 144 + drivers/usb/gadget/function/f_midi.c | 1415 +++ drivers/usb/gadget/function/f_midi2.c | 2894 +++++++ drivers/usb/gadget/function/f_ncm.c | 1812 ++++ drivers/usb/gadget/function/f_obex.c | 491 ++ drivers/usb/gadget/function/f_phonet.c | 742 ++ drivers/usb/gadget/function/f_printer.c | 1575 ++++ drivers/usb/gadget/function/f_rndis.c | 1014 +++ drivers/usb/gadget/function/f_serial.c | 405 + drivers/usb/gadget/function/f_sourcesink.c | 1340 +++ drivers/usb/gadget/function/f_subset.c | 512 ++ drivers/usb/gadget/function/f_tcm.c | 2794 ++++++ drivers/usb/gadget/function/f_uac1.c | 1864 ++++ drivers/usb/gadget/function/f_uac1_legacy.c | 990 +++ drivers/usb/gadget/function/f_uac2.c | 2313 +++++ drivers/usb/gadget/function/f_uvc.c | 1181 +++ drivers/usb/gadget/function/f_uvc.h | 20 + drivers/usb/gadget/function/g_zero.h | 74 + drivers/usb/gadget/function/ndis.h | 47 + drivers/usb/gadget/function/rndis.c | 1201 +++ drivers/usb/gadget/function/rndis.h | 202 + drivers/usb/gadget/function/storage_common.c | 541 ++ drivers/usb/gadget/function/storage_common.h | 225 + drivers/usb/gadget/function/tcm.h | 164 + drivers/usb/gadget/function/u_audio.c | 1481 ++++ drivers/usb/gadget/function/u_audio.h | 135 + drivers/usb/gadget/function/u_ecm.h | 42 + drivers/usb/gadget/function/u_eem.h | 42 + drivers/usb/gadget/function/u_ether.c | 1275 +++ drivers/usb/gadget/function/u_ether.h | 321 + drivers/usb/gadget/function/u_ether_configfs.h | 200 + drivers/usb/gadget/function/u_fs.h | 307 + drivers/usb/gadget/function/u_gether.h | 41 + drivers/usb/gadget/function/u_hid.h | 42 + drivers/usb/gadget/function/u_midi.h | 37 + drivers/usb/gadget/function/u_midi2.h | 81 + drivers/usb/gadget/function/u_ncm.h | 47 + drivers/usb/gadget/function/u_phonet.h | 25 + drivers/usb/gadget/function/u_printer.h | 33 + drivers/usb/gadget/function/u_rndis.h | 61 + drivers/usb/gadget/function/u_serial.c | 1607 ++++ drivers/usb/gadget/function/u_serial.h | 78 + drivers/usb/gadget/function/u_tcm.h | 47 + drivers/usb/gadget/function/u_uac1.h | 71 + drivers/usb/gadget/function/u_uac1_legacy.c | 307 + drivers/usb/gadget/function/u_uac1_legacy.h | 76 + drivers/usb/gadget/function/u_uac2.h | 93 + drivers/usb/gadget/function/u_uvc.h | 109 + drivers/usb/gadget/function/uac_common.h | 9 + drivers/usb/gadget/function/uvc.h | 216 + drivers/usb/gadget/function/uvc_configfs.c | 3835 +++++++++ drivers/usb/gadget/function/uvc_configfs.h | 195 + drivers/usb/gadget/function/uvc_queue.c | 368 + drivers/usb/gadget/function/uvc_queue.h | 103 + drivers/usb/gadget/function/uvc_trace.c | 11 + drivers/usb/gadget/function/uvc_trace.h | 60 + drivers/usb/gadget/function/uvc_v4l2.c | 747 ++ drivers/usb/gadget/function/uvc_v4l2.h | 19 + drivers/usb/gadget/function/uvc_video.c | 843 ++ drivers/usb/gadget/function/uvc_video.h | 22 + drivers/usb/gadget/functions.c | 117 + drivers/usb/gadget/legacy/Kconfig | 532 ++ drivers/usb/gadget/legacy/Makefile | 46 + drivers/usb/gadget/legacy/acm_ms.c | 262 + drivers/usb/gadget/legacy/audio.c | 398 + drivers/usb/gadget/legacy/cdc2.c | 239 + drivers/usb/gadget/legacy/dbgp.c | 440 + drivers/usb/gadget/legacy/ether.c | 484 ++ drivers/usb/gadget/legacy/g_ffs.c | 574 ++ drivers/usb/gadget/legacy/gmidi.c | 186 + drivers/usb/gadget/legacy/hid.c | 297 + drivers/usb/gadget/legacy/inode.c | 2151 +++++ drivers/usb/gadget/legacy/mass_storage.c | 238 + drivers/usb/gadget/legacy/multi.c | 493 ++ drivers/usb/gadget/legacy/ncm.c | 211 + drivers/usb/gadget/legacy/nokia.c | 432 + drivers/usb/gadget/legacy/printer.c | 226 + drivers/usb/gadget/legacy/raw_gadget.c | 1376 +++ drivers/usb/gadget/legacy/serial.c | 325 + drivers/usb/gadget/legacy/tcm_usb_gadget.c | 173 + drivers/usb/gadget/legacy/webcam.c | 608 ++ drivers/usb/gadget/legacy/zero.c | 438 + drivers/usb/gadget/u_f.c | 30 + drivers/usb/gadget/u_os_desc.h | 120 + drivers/usb/gadget/udc/Kconfig | 453 + drivers/usb/gadget/udc/Makefile | 40 + drivers/usb/gadget/udc/amd5536udc.h | 661 ++ drivers/usb/gadget/udc/amd5536udc_pci.c | 216 + drivers/usb/gadget/udc/aspeed-vhub/Kconfig | 8 + drivers/usb/gadget/udc/aspeed-vhub/Makefile | 4 + drivers/usb/gadget/udc/aspeed-vhub/core.c | 471 + drivers/usb/gadget/udc/aspeed-vhub/dev.c | 612 ++ drivers/usb/gadget/udc/aspeed-vhub/ep0.c | 521 ++ drivers/usb/gadget/udc/aspeed-vhub/epn.c | 854 ++ drivers/usb/gadget/udc/aspeed-vhub/hub.c | 1082 +++ drivers/usb/gadget/udc/aspeed-vhub/vhub.h | 566 ++ drivers/usb/gadget/udc/aspeed_udc.c | 1615 ++++ drivers/usb/gadget/udc/at91_udc.c | 2039 +++++ drivers/usb/gadget/udc/at91_udc.h | 177 + drivers/usb/gadget/udc/atmel_usba_udc.c | 2459 ++++++ drivers/usb/gadget/udc/atmel_usba_udc.h | 381 + drivers/usb/gadget/udc/bcm63xx_udc.c | 2380 ++++++ drivers/usb/gadget/udc/bdc/Kconfig | 13 + drivers/usb/gadget/udc/bdc/Makefile | 7 + drivers/usb/gadget/udc/bdc/bdc.h | 489 ++ drivers/usb/gadget/udc/bdc/bdc_cmd.c | 367 + drivers/usb/gadget/udc/bdc/bdc_cmd.h | 23 + drivers/usb/gadget/udc/bdc/bdc_core.c | 676 ++ drivers/usb/gadget/udc/bdc/bdc_dbg.c | 118 + drivers/usb/gadget/udc/bdc/bdc_dbg.h | 32 + drivers/usb/gadget/udc/bdc/bdc_ep.c | 2038 +++++ drivers/usb/gadget/udc/bdc/bdc_ep.h | 17 + drivers/usb/gadget/udc/bdc/bdc_udc.c | 591 ++ drivers/usb/gadget/udc/cdns2/Kconfig | 11 + drivers/usb/gadget/udc/cdns2/Makefile | 7 + drivers/usb/gadget/udc/cdns2/cdns2-debug.h | 203 + drivers/usb/gadget/udc/cdns2/cdns2-ep0.c | 659 ++ drivers/usb/gadget/udc/cdns2/cdns2-gadget.c | 2468 ++++++ drivers/usb/gadget/udc/cdns2/cdns2-gadget.h | 716 ++ drivers/usb/gadget/udc/cdns2/cdns2-pci.c | 136 + drivers/usb/gadget/udc/cdns2/cdns2-trace.c | 11 + drivers/usb/gadget/udc/cdns2/cdns2-trace.h | 536 ++ drivers/usb/gadget/udc/core.c | 1982 +++++ drivers/usb/gadget/udc/dummy_hcd.c | 2953 +++++++ drivers/usb/gadget/udc/fsl_qe_udc.c | 2722 ++++++ drivers/usb/gadget/udc/fsl_qe_udc.h | 417 + drivers/usb/gadget/udc/fsl_udc_core.c | 2693 ++++++ drivers/usb/gadget/udc/fsl_usb2_udc.h | 545 ++ drivers/usb/gadget/udc/goku_udc.c | 1861 ++++ drivers/usb/gadget/udc/goku_udc.h | 289 + drivers/usb/gadget/udc/gr_udc.c | 2258 +++++ drivers/usb/gadget/udc/gr_udc.h | 220 + drivers/usb/gadget/udc/lpc32xx_udc.c | 3276 +++++++ drivers/usb/gadget/udc/m66592-udc.c | 1697 ++++ drivers/usb/gadget/udc/m66592-udc.h | 603 ++ drivers/usb/gadget/udc/max3420_udc.c | 1330 +++ drivers/usb/gadget/udc/net2280.c | 3882 +++++++++ drivers/usb/gadget/udc/net2280.h | 390 + drivers/usb/gadget/udc/omap_udc.c | 2992 +++++++ drivers/usb/gadget/udc/omap_udc.h | 207 + drivers/usb/gadget/udc/pch_udc.c | 3161 +++++++ drivers/usb/gadget/udc/pxa25x_udc.c | 2474 ++++++ drivers/usb/gadget/udc/pxa25x_udc.h | 243 + drivers/usb/gadget/udc/pxa27x_udc.c | 2566 ++++++ drivers/usb/gadget/udc/pxa27x_udc.h | 496 ++ drivers/usb/gadget/udc/r8a66597-udc.c | 1978 +++++ drivers/usb/gadget/udc/r8a66597-udc.h | 287 + drivers/usb/gadget/udc/renesas_usb3.c | 3029 +++++++ drivers/usb/gadget/udc/renesas_usbf.c | 3392 ++++++++ drivers/usb/gadget/udc/rzv2m_usb3drd.c | 137 + drivers/usb/gadget/udc/snps_udc_core.c | 3191 +++++++ drivers/usb/gadget/udc/snps_udc_plat.c | 324 + drivers/usb/gadget/udc/tegra-xudc.c | 4095 +++++++++ drivers/usb/gadget/udc/trace.c | 10 + drivers/usb/gadget/udc/trace.h | 299 + drivers/usb/gadget/udc/udc-xilinx.c | 2266 +++++ drivers/usb/gadget/usbstring.c | 91 + drivers/usb/host/Kconfig | 735 ++ drivers/usb/host/Makefile | 91 + drivers/usb/host/bcma-hcd.c | 499 ++ drivers/usb/host/ehci-atmel.c | 251 + drivers/usb/host/ehci-brcm.c | 281 + drivers/usb/host/ehci-dbg.c | 1081 +++ drivers/usb/host/ehci-exynos.c | 323 + drivers/usb/host/ehci-fsl.c | 732 ++ drivers/usb/host/ehci-fsl.h | 56 + drivers/usb/host/ehci-grlib.c | 177 + drivers/usb/host/ehci-hcd.c | 1401 +++ drivers/usb/host/ehci-hub.c | 1222 +++ drivers/usb/host/ehci-mem.c | 224 + drivers/usb/host/ehci-mv.c | 313 + drivers/usb/host/ehci-npcm7xx.c | 154 + drivers/usb/host/ehci-omap.c | 301 + drivers/usb/host/ehci-orion.c | 374 + drivers/usb/host/ehci-pci.c | 446 + drivers/usb/host/ehci-platform.c | 570 ++ drivers/usb/host/ehci-ppc-of.c | 239 + drivers/usb/host/ehci-ps3.c | 254 + drivers/usb/host/ehci-q.c | 1526 ++++ drivers/usb/host/ehci-sched.c | 2497 ++++++ drivers/usb/host/ehci-sh.c | 184 + drivers/usb/host/ehci-spear.c | 180 + drivers/usb/host/ehci-st.c | 352 + drivers/usb/host/ehci-sysfs.c | 168 + drivers/usb/host/ehci-timer.c | 426 + drivers/usb/host/ehci-xilinx-of.c | 229 + drivers/usb/host/ehci.h | 913 ++ drivers/usb/host/fhci-dbg.c | 96 + drivers/usb/host/fhci-hcd.c | 794 ++ drivers/usb/host/fhci-hub.c | 335 + drivers/usb/host/fhci-mem.c | 110 + drivers/usb/host/fhci-q.c | 281 + drivers/usb/host/fhci-sched.c | 891 ++ drivers/usb/host/fhci-tds.c | 619 ++ drivers/usb/host/fhci.h | 589 ++ drivers/usb/host/fsl-mph-dr-of.c | 376 + drivers/usb/host/isp116x-hcd.c | 1695 ++++ drivers/usb/host/isp116x.h | 595 ++ drivers/usb/host/max3421-hcd.c | 1983 +++++ drivers/usb/host/octeon-hcd.c | 3737 ++++++++ drivers/usb/host/octeon-hcd.h | 1847 ++++ drivers/usb/host/ohci-at91.c | 728 ++ drivers/usb/host/ohci-da8xx.c | 563 ++ drivers/usb/host/ohci-dbg.c | 782 ++ drivers/usb/host/ohci-exynos.c | 292 + drivers/usb/host/ohci-hcd.c | 1355 +++ drivers/usb/host/ohci-hub.c | 803 ++ drivers/usb/host/ohci-mem.c | 163 + drivers/usb/host/ohci-nxp.c | 279 + drivers/usb/host/ohci-omap.c | 428 + drivers/usb/host/ohci-pci.c | 358 + drivers/usb/host/ohci-platform.c | 395 + drivers/usb/host/ohci-ppc-of.c | 229 + drivers/usb/host/ohci-ps3.c | 239 + drivers/usb/host/ohci-pxa27x.c | 607 ++ drivers/usb/host/ohci-q.c | 1235 +++ drivers/usb/host/ohci-s3c2410.c | 492 ++ drivers/usb/host/ohci-sa1111.c | 291 + drivers/usb/host/ohci-sm501.c | 263 + drivers/usb/host/ohci-spear.c | 192 + drivers/usb/host/ohci-st.c | 330 + drivers/usb/host/ohci.h | 743 ++ drivers/usb/host/oxu210hp-hcd.c | 4307 ++++++++++ drivers/usb/host/pci-quirks.c | 1306 +++ drivers/usb/host/pci-quirks.h | 50 + drivers/usb/host/r8a66597-hcd.c | 2520 ++++++ drivers/usb/host/r8a66597.h | 339 + drivers/usb/host/sl811-hcd.c | 1800 ++++ drivers/usb/host/sl811.h | 249 + drivers/usb/host/sl811_cs.c | 203 + drivers/usb/host/ssb-hcd.c | 274 + drivers/usb/host/uhci-debug.c | 637 ++ drivers/usb/host/uhci-grlib.c | 193 + drivers/usb/host/uhci-hcd.c | 935 ++ drivers/usb/host/uhci-hcd.h | 722 ++ drivers/usb/host/uhci-hub.c | 421 + drivers/usb/host/uhci-pci.c | 318 + drivers/usb/host/uhci-platform.c | 214 + drivers/usb/host/uhci-q.c | 1793 ++++ drivers/usb/host/xen-hcd.c | 1611 ++++ drivers/usb/host/xhci-caps.h | 123 + drivers/usb/host/xhci-dbg.c | 35 + drivers/usb/host/xhci-dbgcap.c | 1664 ++++ drivers/usb/host/xhci-dbgcap.h | 278 + drivers/usb/host/xhci-dbgtty.c | 675 ++ drivers/usb/host/xhci-debugfs.c | 855 ++ drivers/usb/host/xhci-debugfs.h | 143 + drivers/usb/host/xhci-ext-caps.c | 110 + drivers/usb/host/xhci-ext-caps.h | 157 + drivers/usb/host/xhci-histb.c | 399 + drivers/usb/host/xhci-hub.c | 1967 +++++ drivers/usb/host/xhci-mem.c | 2519 ++++++ drivers/usb/host/xhci-mtk-sch.c | 1055 +++ drivers/usb/host/xhci-mtk.c | 878 ++ drivers/usb/host/xhci-mtk.h | 194 + drivers/usb/host/xhci-mvebu.c | 75 + drivers/usb/host/xhci-mvebu.h | 21 + drivers/usb/host/xhci-pci-prom21.c | 137 + drivers/usb/host/xhci-pci-renesas.c | 666 ++ drivers/usb/host/xhci-pci.c | 1000 +++ drivers/usb/host/xhci-pci.h | 13 + drivers/usb/host/xhci-plat.c | 658 ++ drivers/usb/host/xhci-plat.h | 36 + drivers/usb/host/xhci-port.h | 181 + drivers/usb/host/xhci-rcar-regs.h | 49 + drivers/usb/host/xhci-rcar.c | 296 + drivers/usb/host/xhci-ring.c | 4476 ++++++++++ drivers/usb/host/xhci-rzg3e-regs.h | 12 + drivers/usb/host/xhci-rzv2m.c | 39 + drivers/usb/host/xhci-rzv2m.h | 16 + drivers/usb/host/xhci-sideband.c | 496 ++ drivers/usb/host/xhci-tegra.c | 2877 +++++++ drivers/usb/host/xhci-trace.c | 15 + drivers/usb/host/xhci-trace.h | 652 ++ drivers/usb/host/xhci.c | 5738 +++++++++++++ drivers/usb/host/xhci.h | 2616 ++++++ drivers/usb/image/Kconfig | 29 + drivers/usb/image/Makefile | 7 + drivers/usb/image/mdc800.c | 1078 +++ drivers/usb/image/microtek.c | 769 ++ drivers/usb/image/microtek.h | 55 + drivers/usb/isp1760/Kconfig | 62 + drivers/usb/isp1760/Makefile | 6 + drivers/usb/isp1760/isp1760-core.c | 607 ++ drivers/usb/isp1760/isp1760-core.h | 97 + drivers/usb/isp1760/isp1760-hcd.c | 2624 ++++++ drivers/usb/isp1760/isp1760-hcd.h | 104 + drivers/usb/isp1760/isp1760-if.c | 302 + drivers/usb/isp1760/isp1760-regs.h | 308 + drivers/usb/isp1760/isp1760-udc.c | 1600 ++++ drivers/usb/isp1760/isp1760-udc.h | 108 + drivers/usb/misc/Kconfig | 361 + drivers/usb/misc/Makefile | 37 + drivers/usb/misc/adutux.c | 796 ++ drivers/usb/misc/apple-mfi-fastcharge.c | 257 + drivers/usb/misc/appledisplay.c | 354 + drivers/usb/misc/brcmstb-usb-pinmap.c | 356 + drivers/usb/misc/chaoskey.c | 605 ++ drivers/usb/misc/cypress_cy7c63.c | 265 + drivers/usb/misc/cytherm.c | 355 + drivers/usb/misc/ehset.c | 190 + drivers/usb/misc/emi26.c | 259 + drivers/usb/misc/emi62.c | 272 + drivers/usb/misc/ezusb.c | 152 + drivers/usb/misc/idmouse.c | 406 + drivers/usb/misc/iowarrior.c | 926 ++ drivers/usb/misc/isight_firmware.c | 132 + drivers/usb/misc/ldusb.c | 794 ++ drivers/usb/misc/legousbtower.c | 880 ++ drivers/usb/misc/lvstest.c | 476 ++ drivers/usb/misc/onboard_usb_dev.c | 732 ++ drivers/usb/misc/onboard_usb_dev.h | 176 + drivers/usb/misc/onboard_usb_dev_pdevs.c | 144 + drivers/usb/misc/qcom_eud.c | 266 + drivers/usb/misc/sisusbvga/Kconfig | 14 + drivers/usb/misc/sisusbvga/Makefile | 6 + drivers/usb/misc/sisusbvga/sisusb.h | 278 + drivers/usb/misc/sisusbvga/sisusb_struct.h | 162 + drivers/usb/misc/sisusbvga/sisusb_tables.h | 688 ++ drivers/usb/misc/sisusbvga/sisusbvga.c | 2969 +++++++ drivers/usb/misc/trancevibrator.c | 127 + drivers/usb/misc/usb-ljca.c | 906 ++ drivers/usb/misc/usb251xb.c | 834 ++ drivers/usb/misc/usb3503.c | 460 + drivers/usb/misc/usb4604.c | 163 + drivers/usb/misc/usbio.c | 756 ++ drivers/usb/misc/usblcd.c | 450 + drivers/usb/misc/usbsevseg.c | 395 + drivers/usb/misc/usbtest.c | 3080 +++++++ drivers/usb/misc/uss720.c | 839 ++ drivers/usb/misc/yurex.c | 537 ++ drivers/usb/mon/Kconfig | 13 + drivers/usb/mon/Makefile | 8 + drivers/usb/mon/mon_bin.c | 1420 ++++ drivers/usb/mon/mon_main.c | 423 + drivers/usb/mon/mon_stat.c | 70 + drivers/usb/mon/mon_text.c | 753 ++ drivers/usb/mon/usb_mon.h | 76 + drivers/usb/mtu3/Kconfig | 59 + drivers/usb/mtu3/Makefile | 30 + drivers/usb/mtu3/mtu3.h | 449 + drivers/usb/mtu3/mtu3_core.c | 1068 +++ drivers/usb/mtu3/mtu3_debug.h | 51 + drivers/usb/mtu3/mtu3_debugfs.c | 515 ++ drivers/usb/mtu3/mtu3_dr.c | 331 + drivers/usb/mtu3/mtu3_dr.h | 125 + drivers/usb/mtu3/mtu3_gadget.c | 773 ++ drivers/usb/mtu3/mtu3_gadget_ep0.c | 916 ++ drivers/usb/mtu3/mtu3_host.c | 393 + drivers/usb/mtu3/mtu3_hw_regs.h | 543 ++ drivers/usb/mtu3/mtu3_plat.c | 635 ++ drivers/usb/mtu3/mtu3_qmu.c | 686 ++ drivers/usb/mtu3/mtu3_qmu.h | 35 + drivers/usb/mtu3/mtu3_trace.c | 24 + drivers/usb/mtu3/mtu3_trace.h | 277 + drivers/usb/musb/Kconfig | 166 + drivers/usb/musb/Makefile | 36 + drivers/usb/musb/cppi_dma.h | 144 + drivers/usb/musb/da8xx.c | 649 ++ drivers/usb/musb/jz4740.c | 338 + drivers/usb/musb/mediatek.c | 537 ++ drivers/usb/musb/mpfs.c | 383 + drivers/usb/musb/musb_core.c | 2955 +++++++ drivers/usb/musb/musb_core.h | 623 ++ drivers/usb/musb/musb_cppi41.c | 811 ++ drivers/usb/musb/musb_debug.h | 34 + drivers/usb/musb/musb_debugfs.c | 336 + drivers/usb/musb/musb_dma.h | 209 + drivers/usb/musb/musb_dsps.c | 1048 +++ drivers/usb/musb/musb_gadget.c | 2095 +++++ drivers/usb/musb/musb_gadget.h | 116 + drivers/usb/musb/musb_gadget_ep0.c | 1058 +++ drivers/usb/musb/musb_host.c | 2777 ++++++ drivers/usb/musb/musb_host.h | 124 + drivers/usb/musb/musb_io.h | 49 + drivers/usb/musb/musb_regs.h | 362 + drivers/usb/musb/musb_trace.c | 25 + drivers/usb/musb/musb_trace.h | 389 + drivers/usb/musb/musb_virthub.c | 439 + drivers/usb/musb/musbhsdma.c | 455 + drivers/usb/musb/omap2430.c | 622 ++ drivers/usb/musb/omap2430.h | 48 + drivers/usb/musb/sunxi.c | 870 ++ drivers/usb/musb/tusb6010.c | 1302 +++ drivers/usb/musb/tusb6010.h | 215 + drivers/usb/musb/tusb6010_omap.c | 642 ++ drivers/usb/musb/ux500.c | 369 + drivers/usb/musb/ux500_dma.c | 396 + drivers/usb/phy/Kconfig | 163 + drivers/usb/phy/Makefile | 24 + drivers/usb/phy/of.c | 43 + drivers/usb/phy/phy-ab8500-usb.c | 1011 +++ drivers/usb/phy/phy-am335x-control.c | 193 + drivers/usb/phy/phy-am335x-control.h | 24 + drivers/usb/phy/phy-am335x.c | 146 + drivers/usb/phy/phy-fsl-usb.c | 1017 +++ drivers/usb/phy/phy-fsl-usb.h | 378 + drivers/usb/phy/phy-generic.c | 348 + drivers/usb/phy/phy-generic.h | 27 + drivers/usb/phy/phy-gpio-vbus-usb.c | 395 + drivers/usb/phy/phy-isp1301.c | 163 + drivers/usb/phy/phy-keystone.c | 118 + drivers/usb/phy/phy-mv-usb.h | 160 + drivers/usb/phy/phy-mxs-usb.c | 977 +++ drivers/usb/phy/phy-omap-otg.c | 150 + drivers/usb/phy/phy-tahvo.c | 439 + drivers/usb/phy/phy-tegra-usb.c | 1655 ++++ drivers/usb/phy/phy-twl6030-usb.c | 457 + drivers/usb/phy/phy-ulpi-viewport.c | 67 + drivers/usb/phy/phy-ulpi.c | 280 + drivers/usb/phy/phy.c | 748 ++ drivers/usb/renesas_usbhs/Kconfig | 17 + drivers/usb/renesas_usbhs/Makefile | 16 + drivers/usb/renesas_usbhs/common.c | 905 ++ drivers/usb/renesas_usbhs/common.h | 346 + drivers/usb/renesas_usbhs/fifo.c | 1486 ++++ drivers/usb/renesas_usbhs/fifo.h | 102 + drivers/usb/renesas_usbhs/mod.c | 375 + drivers/usb/renesas_usbhs/mod.h | 179 + drivers/usb/renesas_usbhs/mod_gadget.c | 1184 +++ drivers/usb/renesas_usbhs/mod_host.c | 1574 ++++ drivers/usb/renesas_usbhs/pipe.c | 852 ++ drivers/usb/renesas_usbhs/pipe.h | 117 + drivers/usb/renesas_usbhs/rcar2.c | 75 + drivers/usb/renesas_usbhs/rcar2.h | 4 + drivers/usb/renesas_usbhs/rcar3.c | 120 + drivers/usb/renesas_usbhs/rcar3.h | 6 + drivers/usb/renesas_usbhs/rza.c | 56 + drivers/usb/renesas_usbhs/rza.h | 6 + drivers/usb/renesas_usbhs/rza2.c | 86 + drivers/usb/roles/Kconfig | 29 + drivers/usb/roles/Makefile | 5 + drivers/usb/roles/class.c | 469 + drivers/usb/roles/intel-xhci-usb-role-switch.c | 227 + drivers/usb/serial/Kconfig | 656 ++ drivers/usb/serial/Makefile | 65 + drivers/usb/serial/Makefile-keyspan_pda_fw | 17 + drivers/usb/serial/aircable.c | 160 + drivers/usb/serial/ark3116.c | 733 ++ drivers/usb/serial/belkin_sa.c | 481 ++ drivers/usb/serial/belkin_sa.h | 120 + drivers/usb/serial/bus.c | 171 + drivers/usb/serial/ch341.c | 893 ++ drivers/usb/serial/console.c | 304 + drivers/usb/serial/cp210x.c | 2201 +++++ drivers/usb/serial/cyberjack.c | 420 + drivers/usb/serial/cypress_m8.c | 1222 +++ drivers/usb/serial/cypress_m8.h | 82 + drivers/usb/serial/digi_acceleport.c | 1513 ++++ drivers/usb/serial/empeg.c | 122 + drivers/usb/serial/ezusb_convert.pl | 51 + drivers/usb/serial/f81232.c | 1077 +++ drivers/usb/serial/f81534.c | 1574 ++++ drivers/usb/serial/ftdi_sio.c | 2878 +++++++ drivers/usb/serial/ftdi_sio.h | 579 ++ drivers/usb/serial/ftdi_sio_ids.h | 1643 ++++ drivers/usb/serial/garmin_gps.c | 1442 ++++ drivers/usb/serial/generic.c | 656 ++ drivers/usb/serial/io_16654.h | 192 + drivers/usb/serial/io_edgeport.c | 3132 +++++++ drivers/usb/serial/io_edgeport.h | 61 + drivers/usb/serial/io_ionsp.h | 451 + drivers/usb/serial/io_ti.c | 2775 ++++++ drivers/usb/serial/io_ti.h | 181 + drivers/usb/serial/io_usbvend.h | 682 ++ drivers/usb/serial/ipaq.c | 604 ++ drivers/usb/serial/ipw.c | 310 + drivers/usb/serial/ir-usb.c | 485 ++ drivers/usb/serial/iuu_phoenix.c | 1205 +++ drivers/usb/serial/iuu_phoenix.h | 117 + drivers/usb/serial/keyspan.c | 3105 +++++++ drivers/usb/serial/keyspan_pda.c | 745 ++ drivers/usb/serial/keyspan_usa26msg.h | 261 + drivers/usb/serial/keyspan_usa28msg.h | 202 + drivers/usb/serial/keyspan_usa49msg.h | 283 + drivers/usb/serial/keyspan_usa67msg.h | 255 + drivers/usb/serial/keyspan_usa90msg.h | 199 + drivers/usb/serial/kl5kusb105.c | 512 ++ drivers/usb/serial/kl5kusb105.h | 66 + drivers/usb/serial/kobil_sct.c | 511 ++ drivers/usb/serial/kobil_sct.h | 78 + drivers/usb/serial/mct_u232.c | 780 ++ drivers/usb/serial/mct_u232.h | 463 + drivers/usb/serial/metro-usb.c | 377 + drivers/usb/serial/mos7720.c | 1764 ++++ drivers/usb/serial/mos7840.c | 1805 ++++ drivers/usb/serial/mxuport.c | 1325 +++ drivers/usb/serial/navman.c | 115 + drivers/usb/serial/omninet.c | 177 + drivers/usb/serial/opticon.c | 403 + drivers/usb/serial/option.c | 2744 ++++++ drivers/usb/serial/oti6858.c | 839 ++ drivers/usb/serial/oti6858.h | 11 + drivers/usb/serial/pl2303.c | 1401 +++ drivers/usb/serial/pl2303.h | 177 + drivers/usb/serial/qcaux.c | 87 + drivers/usb/serial/qcserial.c | 490 ++ drivers/usb/serial/quatech2.c | 955 +++ drivers/usb/serial/safe_serial.c | 309 + drivers/usb/serial/sierra.c | 1058 +++ drivers/usb/serial/spcp8x5.c | 465 + drivers/usb/serial/ssu100.c | 526 ++ drivers/usb/serial/symbolserial.c | 191 + drivers/usb/serial/ti_usb_3410_5052.c | 1659 ++++ drivers/usb/serial/upd78f0730.c | 434 + drivers/usb/serial/usb-serial-simple.c | 173 + drivers/usb/serial/usb-serial.c | 1550 ++++ drivers/usb/serial/usb-wwan.h | 64 + drivers/usb/serial/usb_debug.c | 113 + drivers/usb/serial/usb_wwan.c | 657 ++ drivers/usb/serial/visor.c | 573 ++ drivers/usb/serial/visor.h | 157 + drivers/usb/serial/whiteheat.c | 802 ++ drivers/usb/serial/whiteheat.h | 298 + drivers/usb/serial/wishbone-serial.c | 88 + drivers/usb/serial/xr_serial.c | 1111 +++ drivers/usb/serial/xsens_mt.c | 67 + drivers/usb/storage/Kconfig | 199 + drivers/usb/storage/Makefile | 47 + drivers/usb/storage/alauda.c | 1274 +++ drivers/usb/storage/cypress_atacb.c | 286 + drivers/usb/storage/datafab.c | 757 ++ drivers/usb/storage/debug.c | 174 + drivers/usb/storage/debug.h | 51 + drivers/usb/storage/ene_ub6250.c | 2441 ++++++ drivers/usb/storage/freecom.c | 577 ++ drivers/usb/storage/initializers.c | 94 + drivers/usb/storage/initializers.h | 39 + drivers/usb/storage/isd200.c | 1586 ++++ drivers/usb/storage/jumpshot.c | 683 ++ drivers/usb/storage/karma.c | 234 + drivers/usb/storage/onetouch.c | 307 + drivers/usb/storage/option_ms.c | 160 + drivers/usb/storage/option_ms.h | 5 + drivers/usb/storage/protocol.c | 180 + drivers/usb/storage/protocol.h | 42 + drivers/usb/storage/realtek_cr.c | 1071 +++ drivers/usb/storage/scsiglue.c | 699 ++ drivers/usb/storage/scsiglue.h | 34 + drivers/usb/storage/sddr09.c | 1790 ++++ drivers/usb/storage/sddr55.c | 1020 +++ drivers/usb/storage/shuttle_usbat.c | 1872 ++++ drivers/usb/storage/sierra_ms.c | 192 + drivers/usb/storage/sierra_ms.h | 5 + drivers/usb/storage/transport.c | 1462 ++++ drivers/usb/storage/transport.h | 88 + drivers/usb/storage/uas-detect.h | 162 + drivers/usb/storage/uas.c | 1304 +++ drivers/usb/storage/unusual_alauda.h | 19 + drivers/usb/storage/unusual_cypress.h | 27 + drivers/usb/storage/unusual_datafab.h | 87 + drivers/usb/storage/unusual_devs.h | 2521 ++++++ drivers/usb/storage/unusual_ene_ub6250.h | 10 + drivers/usb/storage/unusual_freecom.h | 14 + drivers/usb/storage/unusual_isd200.h | 45 + drivers/usb/storage/unusual_jumpshot.h | 15 + drivers/usb/storage/unusual_karma.h | 14 + drivers/usb/storage/unusual_onetouch.h | 25 + drivers/usb/storage/unusual_realtek.h | 45 + drivers/usb/storage/unusual_sddr09.h | 44 + drivers/usb/storage/unusual_sddr55.h | 32 + drivers/usb/storage/unusual_uas.h | 208 + drivers/usb/storage/unusual_usbat.h | 31 + drivers/usb/storage/usb.c | 1258 +++ drivers/usb/storage/usb.h | 210 + drivers/usb/storage/usual-tables.c | 93 + drivers/usb/typec/Kconfig | 117 + drivers/usb/typec/Makefile | 14 + drivers/usb/typec/altmodes/Kconfig | 36 + drivers/usb/typec/altmodes/Makefile | 8 + drivers/usb/typec/altmodes/displayport.c | 874 ++ drivers/usb/typec/altmodes/displayport.h | 8 + drivers/usb/typec/altmodes/nvidia.c | 43 + drivers/usb/typec/altmodes/thunderbolt.c | 394 + drivers/usb/typec/anx7411.c | 1611 ++++ drivers/usb/typec/bus.c | 572 ++ drivers/usb/typec/bus.h | 30 + drivers/usb/typec/class.c | 2965 +++++++ drivers/usb/typec/class.h | 115 + drivers/usb/typec/hd3ss3220.c | 547 ++ drivers/usb/typec/mode_selection.c | 283 + drivers/usb/typec/mux.c | 484 ++ drivers/usb/typec/mux.h | 27 + drivers/usb/typec/mux/Kconfig | 98 + drivers/usb/typec/mux/Makefile | 12 + drivers/usb/typec/mux/fsa4480.c | 362 + drivers/usb/typec/mux/gpio-sbu-mux.c | 171 + drivers/usb/typec/mux/intel_pmc_mux.c | 850 ++ drivers/usb/typec/mux/it5205.c | 294 + drivers/usb/typec/mux/nb7vpq904m.c | 527 ++ drivers/usb/typec/mux/pi3usb30532.c | 190 + drivers/usb/typec/mux/ps883x.c | 532 ++ drivers/usb/typec/mux/ptn36502.c | 431 + drivers/usb/typec/mux/tusb1046.c | 196 + drivers/usb/typec/mux/wcd939x-usbss.c | 779 ++ drivers/usb/typec/pd.c | 797 ++ drivers/usb/typec/pd.h | 30 + drivers/usb/typec/port-mapper.c | 108 + drivers/usb/typec/retimer.c | 160 + drivers/usb/typec/retimer.h | 19 + drivers/usb/typec/rt1719.c | 954 +++ drivers/usb/typec/stusb160x.c | 879 ++ drivers/usb/typec/tcpm/Kconfig | 94 + drivers/usb/typec/tcpm/Makefile | 12 + drivers/usb/typec/tcpm/fusb302.c | 1868 ++++ drivers/usb/typec/tcpm/fusb302_reg.h | 177 + drivers/usb/typec/tcpm/maxim_contaminant.c | 450 + drivers/usb/typec/tcpm/qcom/Makefile | 7 + drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c | 166 + drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.h | 27 + .../usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c | 648 ++ .../usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h | 37 + .../typec/tcpm/qcom/qcom_pmic_typec_pdphy_stub.c | 80 + drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c | 818 ++ drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h | 35 + drivers/usb/typec/tcpm/tcpci.c | 1057 +++ drivers/usb/typec/tcpm/tcpci_maxim.h | 102 + drivers/usb/typec/tcpm/tcpci_maxim_core.c | 607 ++ drivers/usb/typec/tcpm/tcpci_mt6360.c | 230 + drivers/usb/typec/tcpm/tcpci_mt6370.c | 203 + drivers/usb/typec/tcpm/tcpci_rt1711h.c | 406 + drivers/usb/typec/tcpm/tcpm.c | 8985 ++++++++++++++++++++ drivers/usb/typec/tcpm/wcove.c | 701 ++ drivers/usb/typec/tipd/Kconfig | 12 + drivers/usb/typec/tipd/Makefile | 6 + drivers/usb/typec/tipd/core.c | 2117 +++++ drivers/usb/typec/tipd/tps6598x.h | 276 + drivers/usb/typec/tipd/trace.c | 9 + drivers/usb/typec/tipd/trace.h | 437 + drivers/usb/typec/ucsi/Kconfig | 107 + drivers/usb/typec/ucsi/Makefile | 30 + drivers/usb/typec/ucsi/cros_ec_ucsi.c | 387 + drivers/usb/typec/ucsi/debugfs.c | 176 + drivers/usb/typec/ucsi/displayport.c | 348 + drivers/usb/typec/ucsi/psy.c | 355 + drivers/usb/typec/ucsi/thunderbolt.c | 212 + drivers/usb/typec/ucsi/trace.c | 46 + drivers/usb/typec/ucsi/trace.h | 119 + drivers/usb/typec/ucsi/ucsi.c | 2403 ++++++ drivers/usb/typec/ucsi/ucsi.h | 664 ++ drivers/usb/typec/ucsi/ucsi_acpi.c | 303 + drivers/usb/typec/ucsi/ucsi_ccg.c | 1599 ++++ drivers/usb/typec/ucsi/ucsi_glink.c | 490 ++ drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 564 ++ drivers/usb/typec/ucsi/ucsi_stm32g0.c | 760 ++ drivers/usb/typec/ucsi/ucsi_yoga_c630.c | 312 + drivers/usb/typec/wusb3801.c | 435 + drivers/usb/usb-skeleton.c | 645 ++ drivers/usb/usbip/Kconfig | 77 + drivers/usb/usbip/Makefile | 14 + drivers/usb/usbip/stub.h | 105 + drivers/usb/usbip/stub_dev.c | 534 ++ drivers/usb/usbip/stub_main.c | 428 + drivers/usb/usbip/stub_rx.c | 711 ++ drivers/usb/usbip/stub_tx.c | 450 + drivers/usb/usbip/usbip_common.c | 920 ++ drivers/usb/usbip/usbip_common.h | 340 + drivers/usb/usbip/usbip_event.c | 196 + drivers/usb/usbip/vhci.h | 174 + drivers/usb/usbip/vhci_hcd.c | 1587 ++++ drivers/usb/usbip/vhci_rx.c | 270 + drivers/usb/usbip/vhci_sysfs.c | 532 ++ drivers/usb/usbip/vhci_tx.c | 256 + drivers/usb/usbip/vudc.h | 177 + drivers/usb/usbip/vudc_dev.c | 637 ++ drivers/usb/usbip/vudc_main.c | 110 + drivers/usb/usbip/vudc_rx.c | 243 + drivers/usb/usbip/vudc_sysfs.c | 268 + drivers/usb/usbip/vudc_transfer.c | 497 ++ drivers/usb/usbip/vudc_tx.c | 284 + 868 files changed, 590883 insertions(+) create mode 100644 drivers/usb/Kconfig create mode 100644 drivers/usb/Makefile 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/cdns3/Kconfig create mode 100644 drivers/usb/cdns3/Makefile create mode 100644 drivers/usb/cdns3/cdns3-debug.h create mode 100644 drivers/usb/cdns3/cdns3-ep0.c create mode 100644 drivers/usb/cdns3/cdns3-gadget.c create mode 100644 drivers/usb/cdns3/cdns3-gadget.h create mode 100644 drivers/usb/cdns3/cdns3-imx.c create mode 100644 drivers/usb/cdns3/cdns3-pci-wrap.c create mode 100644 drivers/usb/cdns3/cdns3-plat.c create mode 100644 drivers/usb/cdns3/cdns3-starfive.c create mode 100644 drivers/usb/cdns3/cdns3-ti.c create mode 100644 drivers/usb/cdns3/cdns3-trace.c create mode 100644 drivers/usb/cdns3/cdns3-trace.h create mode 100644 drivers/usb/cdns3/cdnsp-debug.h create mode 100644 drivers/usb/cdns3/cdnsp-ep0.c create mode 100644 drivers/usb/cdns3/cdnsp-gadget.c create mode 100644 drivers/usb/cdns3/cdnsp-gadget.h create mode 100644 drivers/usb/cdns3/cdnsp-mem.c create mode 100644 drivers/usb/cdns3/cdnsp-pci.c create mode 100644 drivers/usb/cdns3/cdnsp-ring.c create mode 100644 drivers/usb/cdns3/cdnsp-trace.c create mode 100644 drivers/usb/cdns3/cdnsp-trace.h create mode 100644 drivers/usb/cdns3/core.c create mode 100644 drivers/usb/cdns3/core.h create mode 100644 drivers/usb/cdns3/drd.c create mode 100644 drivers/usb/cdns3/drd.h create mode 100644 drivers/usb/cdns3/gadget-export.h create mode 100644 drivers/usb/cdns3/host-export.h create mode 100644 drivers/usb/cdns3/host.c 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/ci_hdrc_imx.c create mode 100644 drivers/usb/chipidea/ci_hdrc_imx.h create mode 100644 drivers/usb/chipidea/ci_hdrc_msm.c create mode 100644 drivers/usb/chipidea/ci_hdrc_npcm.c create mode 100644 drivers/usb/chipidea/ci_hdrc_pci.c create mode 100644 drivers/usb/chipidea/ci_hdrc_tegra.c create mode 100644 drivers/usb/chipidea/ci_hdrc_usb2.c create mode 100644 drivers/usb/chipidea/core.c create mode 100644 drivers/usb/chipidea/debug.c create mode 100644 drivers/usb/chipidea/host.c create mode 100644 drivers/usb/chipidea/host.h create mode 100644 drivers/usb/chipidea/otg.c create mode 100644 drivers/usb/chipidea/otg.h create mode 100644 drivers/usb/chipidea/otg_fsm.c create mode 100644 drivers/usb/chipidea/otg_fsm.h create mode 100644 drivers/usb/chipidea/trace.c create mode 100644 drivers/usb/chipidea/trace.h create mode 100644 drivers/usb/chipidea/udc.c create mode 100644 drivers/usb/chipidea/udc.h create mode 100644 drivers/usb/chipidea/ulpi.c 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/common/Kconfig create mode 100644 drivers/usb/common/Makefile create mode 100644 drivers/usb/common/common.c create mode 100644 drivers/usb/common/common.h create mode 100644 drivers/usb/common/debug.c create mode 100644 drivers/usb/common/led.c create mode 100644 drivers/usb/common/ulpi.c create mode 100644 drivers/usb/common/usb-conn-gpio.c create mode 100644 drivers/usb/common/usb-otg-fsm.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/ledtrig-usbport.c create mode 100644 drivers/usb/core/message.c create mode 100644 drivers/usb/core/notify.c create mode 100644 drivers/usb/core/of.c create mode 100644 drivers/usb/core/offload.c create mode 100644 drivers/usb/core/otg_productlist.h create mode 100644 drivers/usb/core/phy.c create mode 100644 drivers/usb/core/phy.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/trace.c create mode 100644 drivers/usb/core/trace.h 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/dwc2/Kconfig create mode 100644 drivers/usb/dwc2/Makefile create mode 100644 drivers/usb/dwc2/core.c create mode 100644 drivers/usb/dwc2/core.h create mode 100644 drivers/usb/dwc2/core_intr.c create mode 100644 drivers/usb/dwc2/debug.h create mode 100644 drivers/usb/dwc2/debugfs.c create mode 100644 drivers/usb/dwc2/drd.c create mode 100644 drivers/usb/dwc2/gadget.c create mode 100644 drivers/usb/dwc2/hcd.c create mode 100644 drivers/usb/dwc2/hcd.h create mode 100644 drivers/usb/dwc2/hcd_ddma.c create mode 100644 drivers/usb/dwc2/hcd_intr.c create mode 100644 drivers/usb/dwc2/hcd_queue.c create mode 100644 drivers/usb/dwc2/hw.h create mode 100644 drivers/usb/dwc2/params.c create mode 100644 drivers/usb/dwc2/pci.c create mode 100644 drivers/usb/dwc2/platform.c 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/drd.c create mode 100644 drivers/usb/dwc3/dwc3-am62.c create mode 100644 drivers/usb/dwc3/dwc3-apple.c create mode 100644 drivers/usb/dwc3/dwc3-exynos.c create mode 100644 drivers/usb/dwc3/dwc3-generic-plat.c create mode 100644 drivers/usb/dwc3/dwc3-google.c create mode 100644 drivers/usb/dwc3/dwc3-haps.c create mode 100644 drivers/usb/dwc3/dwc3-imx.c create mode 100644 drivers/usb/dwc3/dwc3-imx8mp.c create mode 100644 drivers/usb/dwc3/dwc3-keystone.c create mode 100644 drivers/usb/dwc3/dwc3-meson-g12a.c create mode 100644 drivers/usb/dwc3/dwc3-octeon.c create mode 100644 drivers/usb/dwc3/dwc3-of-simple.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/dwc3-qcom-legacy.c create mode 100644 drivers/usb/dwc3/dwc3-qcom.c create mode 100644 drivers/usb/dwc3/dwc3-rtk.c create mode 100644 drivers/usb/dwc3/dwc3-st.c create mode 100644 drivers/usb/dwc3/dwc3-xilinx.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/glue.h create mode 100644 drivers/usb/dwc3/host.c create mode 100644 drivers/usb/dwc3/io.h create mode 100644 drivers/usb/dwc3/trace.c create mode 100644 drivers/usb/dwc3/trace.h create mode 100644 drivers/usb/dwc3/ulpi.c create mode 100644 drivers/usb/early/Makefile create mode 100644 drivers/usb/early/ehci-dbgp.c create mode 100644 drivers/usb/early/xhci-dbc.c create mode 100644 drivers/usb/early/xhci-dbc.h create mode 100644 drivers/usb/fotg210/Kconfig create mode 100644 drivers/usb/fotg210/Makefile create mode 100644 drivers/usb/fotg210/fotg210-core.c create mode 100644 drivers/usb/fotg210/fotg210-hcd.c create mode 100644 drivers/usb/fotg210/fotg210-hcd.h create mode 100644 drivers/usb/fotg210/fotg210-udc.c create mode 100644 drivers/usb/fotg210/fotg210-udc.h create mode 100644 drivers/usb/fotg210/fotg210.h create mode 100644 drivers/usb/gadget/Kconfig create mode 100644 drivers/usb/gadget/Makefile 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/configfs.h create mode 100644 drivers/usb/gadget/epautoconf.c create mode 100644 drivers/usb/gadget/function/Makefile create mode 100644 drivers/usb/gadget/function/f_acm.c create mode 100644 drivers/usb/gadget/function/f_ecm.c create mode 100644 drivers/usb/gadget/function/f_eem.c create mode 100644 drivers/usb/gadget/function/f_fs.c create mode 100644 drivers/usb/gadget/function/f_hid.c create mode 100644 drivers/usb/gadget/function/f_loopback.c create mode 100644 drivers/usb/gadget/function/f_mass_storage.c create mode 100644 drivers/usb/gadget/function/f_mass_storage.h create mode 100644 drivers/usb/gadget/function/f_midi.c create mode 100644 drivers/usb/gadget/function/f_midi2.c create mode 100644 drivers/usb/gadget/function/f_ncm.c create mode 100644 drivers/usb/gadget/function/f_obex.c create mode 100644 drivers/usb/gadget/function/f_phonet.c create mode 100644 drivers/usb/gadget/function/f_printer.c create mode 100644 drivers/usb/gadget/function/f_rndis.c create mode 100644 drivers/usb/gadget/function/f_serial.c create mode 100644 drivers/usb/gadget/function/f_sourcesink.c create mode 100644 drivers/usb/gadget/function/f_subset.c create mode 100644 drivers/usb/gadget/function/f_tcm.c create mode 100644 drivers/usb/gadget/function/f_uac1.c create mode 100644 drivers/usb/gadget/function/f_uac1_legacy.c create mode 100644 drivers/usb/gadget/function/f_uac2.c create mode 100644 drivers/usb/gadget/function/f_uvc.c create mode 100644 drivers/usb/gadget/function/f_uvc.h create mode 100644 drivers/usb/gadget/function/g_zero.h create mode 100644 drivers/usb/gadget/function/ndis.h create mode 100644 drivers/usb/gadget/function/rndis.c create mode 100644 drivers/usb/gadget/function/rndis.h create mode 100644 drivers/usb/gadget/function/storage_common.c create mode 100644 drivers/usb/gadget/function/storage_common.h create mode 100644 drivers/usb/gadget/function/tcm.h create mode 100644 drivers/usb/gadget/function/u_audio.c create mode 100644 drivers/usb/gadget/function/u_audio.h create mode 100644 drivers/usb/gadget/function/u_ecm.h create mode 100644 drivers/usb/gadget/function/u_eem.h create mode 100644 drivers/usb/gadget/function/u_ether.c create mode 100644 drivers/usb/gadget/function/u_ether.h create mode 100644 drivers/usb/gadget/function/u_ether_configfs.h create mode 100644 drivers/usb/gadget/function/u_fs.h create mode 100644 drivers/usb/gadget/function/u_gether.h create mode 100644 drivers/usb/gadget/function/u_hid.h create mode 100644 drivers/usb/gadget/function/u_midi.h create mode 100644 drivers/usb/gadget/function/u_midi2.h create mode 100644 drivers/usb/gadget/function/u_ncm.h create mode 100644 drivers/usb/gadget/function/u_phonet.h create mode 100644 drivers/usb/gadget/function/u_printer.h create mode 100644 drivers/usb/gadget/function/u_rndis.h create mode 100644 drivers/usb/gadget/function/u_serial.c create mode 100644 drivers/usb/gadget/function/u_serial.h create mode 100644 drivers/usb/gadget/function/u_tcm.h create mode 100644 drivers/usb/gadget/function/u_uac1.h create mode 100644 drivers/usb/gadget/function/u_uac1_legacy.c create mode 100644 drivers/usb/gadget/function/u_uac1_legacy.h create mode 100644 drivers/usb/gadget/function/u_uac2.h create mode 100644 drivers/usb/gadget/function/u_uvc.h create mode 100644 drivers/usb/gadget/function/uac_common.h create mode 100644 drivers/usb/gadget/function/uvc.h create mode 100644 drivers/usb/gadget/function/uvc_configfs.c create mode 100644 drivers/usb/gadget/function/uvc_configfs.h create mode 100644 drivers/usb/gadget/function/uvc_queue.c create mode 100644 drivers/usb/gadget/function/uvc_queue.h create mode 100644 drivers/usb/gadget/function/uvc_trace.c create mode 100644 drivers/usb/gadget/function/uvc_trace.h create mode 100644 drivers/usb/gadget/function/uvc_v4l2.c create mode 100644 drivers/usb/gadget/function/uvc_v4l2.h create mode 100644 drivers/usb/gadget/function/uvc_video.c create mode 100644 drivers/usb/gadget/function/uvc_video.h create mode 100644 drivers/usb/gadget/functions.c create mode 100644 drivers/usb/gadget/legacy/Kconfig create mode 100644 drivers/usb/gadget/legacy/Makefile create mode 100644 drivers/usb/gadget/legacy/acm_ms.c create mode 100644 drivers/usb/gadget/legacy/audio.c create mode 100644 drivers/usb/gadget/legacy/cdc2.c create mode 100644 drivers/usb/gadget/legacy/dbgp.c create mode 100644 drivers/usb/gadget/legacy/ether.c create mode 100644 drivers/usb/gadget/legacy/g_ffs.c create mode 100644 drivers/usb/gadget/legacy/gmidi.c create mode 100644 drivers/usb/gadget/legacy/hid.c create mode 100644 drivers/usb/gadget/legacy/inode.c create mode 100644 drivers/usb/gadget/legacy/mass_storage.c create mode 100644 drivers/usb/gadget/legacy/multi.c create mode 100644 drivers/usb/gadget/legacy/ncm.c create mode 100644 drivers/usb/gadget/legacy/nokia.c create mode 100644 drivers/usb/gadget/legacy/printer.c create mode 100644 drivers/usb/gadget/legacy/raw_gadget.c create mode 100644 drivers/usb/gadget/legacy/serial.c create mode 100644 drivers/usb/gadget/legacy/tcm_usb_gadget.c create mode 100644 drivers/usb/gadget/legacy/webcam.c create mode 100644 drivers/usb/gadget/legacy/zero.c create mode 100644 drivers/usb/gadget/u_f.c create mode 100644 drivers/usb/gadget/u_os_desc.h create mode 100644 drivers/usb/gadget/udc/Kconfig create mode 100644 drivers/usb/gadget/udc/Makefile create mode 100644 drivers/usb/gadget/udc/amd5536udc.h create mode 100644 drivers/usb/gadget/udc/amd5536udc_pci.c create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/Kconfig create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/Makefile create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/core.c create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/dev.c create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/ep0.c create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/epn.c create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/hub.c create mode 100644 drivers/usb/gadget/udc/aspeed-vhub/vhub.h create mode 100644 drivers/usb/gadget/udc/aspeed_udc.c create mode 100644 drivers/usb/gadget/udc/at91_udc.c create mode 100644 drivers/usb/gadget/udc/at91_udc.h create mode 100644 drivers/usb/gadget/udc/atmel_usba_udc.c create mode 100644 drivers/usb/gadget/udc/atmel_usba_udc.h create mode 100644 drivers/usb/gadget/udc/bcm63xx_udc.c create mode 100644 drivers/usb/gadget/udc/bdc/Kconfig create mode 100644 drivers/usb/gadget/udc/bdc/Makefile create mode 100644 drivers/usb/gadget/udc/bdc/bdc.h create mode 100644 drivers/usb/gadget/udc/bdc/bdc_cmd.c create mode 100644 drivers/usb/gadget/udc/bdc/bdc_cmd.h create mode 100644 drivers/usb/gadget/udc/bdc/bdc_core.c create mode 100644 drivers/usb/gadget/udc/bdc/bdc_dbg.c create mode 100644 drivers/usb/gadget/udc/bdc/bdc_dbg.h create mode 100644 drivers/usb/gadget/udc/bdc/bdc_ep.c create mode 100644 drivers/usb/gadget/udc/bdc/bdc_ep.h create mode 100644 drivers/usb/gadget/udc/bdc/bdc_udc.c create mode 100644 drivers/usb/gadget/udc/cdns2/Kconfig create mode 100644 drivers/usb/gadget/udc/cdns2/Makefile create mode 100644 drivers/usb/gadget/udc/cdns2/cdns2-debug.h create mode 100644 drivers/usb/gadget/udc/cdns2/cdns2-ep0.c create mode 100644 drivers/usb/gadget/udc/cdns2/cdns2-gadget.c create mode 100644 drivers/usb/gadget/udc/cdns2/cdns2-gadget.h create mode 100644 drivers/usb/gadget/udc/cdns2/cdns2-pci.c create mode 100644 drivers/usb/gadget/udc/cdns2/cdns2-trace.c create mode 100644 drivers/usb/gadget/udc/cdns2/cdns2-trace.h create mode 100644 drivers/usb/gadget/udc/core.c create mode 100644 drivers/usb/gadget/udc/dummy_hcd.c create mode 100644 drivers/usb/gadget/udc/fsl_qe_udc.c create mode 100644 drivers/usb/gadget/udc/fsl_qe_udc.h create mode 100644 drivers/usb/gadget/udc/fsl_udc_core.c create mode 100644 drivers/usb/gadget/udc/fsl_usb2_udc.h create mode 100644 drivers/usb/gadget/udc/goku_udc.c create mode 100644 drivers/usb/gadget/udc/goku_udc.h create mode 100644 drivers/usb/gadget/udc/gr_udc.c create mode 100644 drivers/usb/gadget/udc/gr_udc.h create mode 100644 drivers/usb/gadget/udc/lpc32xx_udc.c create mode 100644 drivers/usb/gadget/udc/m66592-udc.c create mode 100644 drivers/usb/gadget/udc/m66592-udc.h create mode 100644 drivers/usb/gadget/udc/max3420_udc.c create mode 100644 drivers/usb/gadget/udc/net2280.c create mode 100644 drivers/usb/gadget/udc/net2280.h create mode 100644 drivers/usb/gadget/udc/omap_udc.c create mode 100644 drivers/usb/gadget/udc/omap_udc.h create mode 100644 drivers/usb/gadget/udc/pch_udc.c create mode 100644 drivers/usb/gadget/udc/pxa25x_udc.c create mode 100644 drivers/usb/gadget/udc/pxa25x_udc.h create mode 100644 drivers/usb/gadget/udc/pxa27x_udc.c create mode 100644 drivers/usb/gadget/udc/pxa27x_udc.h create mode 100644 drivers/usb/gadget/udc/r8a66597-udc.c create mode 100644 drivers/usb/gadget/udc/r8a66597-udc.h create mode 100644 drivers/usb/gadget/udc/renesas_usb3.c create mode 100644 drivers/usb/gadget/udc/renesas_usbf.c create mode 100644 drivers/usb/gadget/udc/rzv2m_usb3drd.c create mode 100644 drivers/usb/gadget/udc/snps_udc_core.c create mode 100644 drivers/usb/gadget/udc/snps_udc_plat.c create mode 100644 drivers/usb/gadget/udc/tegra-xudc.c create mode 100644 drivers/usb/gadget/udc/trace.c create mode 100644 drivers/usb/gadget/udc/trace.h create mode 100644 drivers/usb/gadget/udc/udc-xilinx.c create mode 100644 drivers/usb/gadget/usbstring.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-brcm.c create mode 100644 drivers/usb/host/ehci-dbg.c create mode 100644 drivers/usb/host/ehci-exynos.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-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-mv.c create mode 100644 drivers/usb/host/ehci-npcm7xx.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-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-sched.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-st.c create mode 100644 drivers/usb/host/ehci-sysfs.c create mode 100644 drivers/usb/host/ehci-timer.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/isp116x-hcd.c create mode 100644 drivers/usb/host/isp116x.h create mode 100644 drivers/usb/host/max3421-hcd.c create mode 100644 drivers/usb/host/octeon-hcd.c create mode 100644 drivers/usb/host/octeon-hcd.h 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-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-mem.c create mode 100644 drivers/usb/host/ohci-nxp.c create mode 100644 drivers/usb/host/ohci-omap.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-st.c create mode 100644 drivers/usb/host/ohci.h create mode 100644 drivers/usb/host/oxu210hp-hcd.c 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/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/xen-hcd.c create mode 100644 drivers/usb/host/xhci-caps.h create mode 100644 drivers/usb/host/xhci-dbg.c create mode 100644 drivers/usb/host/xhci-dbgcap.c create mode 100644 drivers/usb/host/xhci-dbgcap.h create mode 100644 drivers/usb/host/xhci-dbgtty.c create mode 100644 drivers/usb/host/xhci-debugfs.c create mode 100644 drivers/usb/host/xhci-debugfs.h create mode 100644 drivers/usb/host/xhci-ext-caps.c create mode 100644 drivers/usb/host/xhci-ext-caps.h create mode 100644 drivers/usb/host/xhci-histb.c 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-mtk-sch.c create mode 100644 drivers/usb/host/xhci-mtk.c create mode 100644 drivers/usb/host/xhci-mtk.h create mode 100644 drivers/usb/host/xhci-mvebu.c create mode 100644 drivers/usb/host/xhci-mvebu.h create mode 100644 drivers/usb/host/xhci-pci-prom21.c create mode 100644 drivers/usb/host/xhci-pci-renesas.c create mode 100644 drivers/usb/host/xhci-pci.c create mode 100644 drivers/usb/host/xhci-pci.h create mode 100644 drivers/usb/host/xhci-plat.c create mode 100644 drivers/usb/host/xhci-plat.h create mode 100644 drivers/usb/host/xhci-port.h create mode 100644 drivers/usb/host/xhci-rcar-regs.h create mode 100644 drivers/usb/host/xhci-rcar.c create mode 100644 drivers/usb/host/xhci-ring.c create mode 100644 drivers/usb/host/xhci-rzg3e-regs.h create mode 100644 drivers/usb/host/xhci-rzv2m.c create mode 100644 drivers/usb/host/xhci-rzv2m.h create mode 100644 drivers/usb/host/xhci-sideband.c create mode 100644 drivers/usb/host/xhci-tegra.c create mode 100644 drivers/usb/host/xhci-trace.c create mode 100644 drivers/usb/host/xhci-trace.h 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/isp1760/Kconfig create mode 100644 drivers/usb/isp1760/Makefile create mode 100644 drivers/usb/isp1760/isp1760-core.c create mode 100644 drivers/usb/isp1760/isp1760-core.h create mode 100644 drivers/usb/isp1760/isp1760-hcd.c create mode 100644 drivers/usb/isp1760/isp1760-hcd.h create mode 100644 drivers/usb/isp1760/isp1760-if.c create mode 100644 drivers/usb/isp1760/isp1760-regs.h create mode 100644 drivers/usb/isp1760/isp1760-udc.c create mode 100644 drivers/usb/isp1760/isp1760-udc.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/apple-mfi-fastcharge.c create mode 100644 drivers/usb/misc/appledisplay.c create mode 100644 drivers/usb/misc/brcmstb-usb-pinmap.c create mode 100644 drivers/usb/misc/chaoskey.c create mode 100644 drivers/usb/misc/cypress_cy7c63.c create mode 100644 drivers/usb/misc/cytherm.c create mode 100644 drivers/usb/misc/ehset.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/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/lvstest.c create mode 100644 drivers/usb/misc/onboard_usb_dev.c create mode 100644 drivers/usb/misc/onboard_usb_dev.h create mode 100644 drivers/usb/misc/onboard_usb_dev_pdevs.c create mode 100644 drivers/usb/misc/qcom_eud.c create mode 100644 drivers/usb/misc/sisusbvga/Kconfig create mode 100644 drivers/usb/misc/sisusbvga/Makefile create mode 100644 drivers/usb/misc/sisusbvga/sisusb.h create mode 100644 drivers/usb/misc/sisusbvga/sisusb_struct.h create mode 100644 drivers/usb/misc/sisusbvga/sisusb_tables.h create mode 100644 drivers/usb/misc/sisusbvga/sisusbvga.c create mode 100644 drivers/usb/misc/trancevibrator.c create mode 100644 drivers/usb/misc/usb-ljca.c create mode 100644 drivers/usb/misc/usb251xb.c create mode 100644 drivers/usb/misc/usb3503.c create mode 100644 drivers/usb/misc/usb4604.c create mode 100644 drivers/usb/misc/usbio.c create mode 100644 drivers/usb/misc/usblcd.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/mtu3/Kconfig create mode 100644 drivers/usb/mtu3/Makefile create mode 100644 drivers/usb/mtu3/mtu3.h create mode 100644 drivers/usb/mtu3/mtu3_core.c create mode 100644 drivers/usb/mtu3/mtu3_debug.h create mode 100644 drivers/usb/mtu3/mtu3_debugfs.c create mode 100644 drivers/usb/mtu3/mtu3_dr.c create mode 100644 drivers/usb/mtu3/mtu3_dr.h create mode 100644 drivers/usb/mtu3/mtu3_gadget.c create mode 100644 drivers/usb/mtu3/mtu3_gadget_ep0.c create mode 100644 drivers/usb/mtu3/mtu3_host.c create mode 100644 drivers/usb/mtu3/mtu3_hw_regs.h create mode 100644 drivers/usb/mtu3/mtu3_plat.c create mode 100644 drivers/usb/mtu3/mtu3_qmu.c create mode 100644 drivers/usb/mtu3/mtu3_qmu.h create mode 100644 drivers/usb/mtu3/mtu3_trace.c create mode 100644 drivers/usb/mtu3/mtu3_trace.h create mode 100644 drivers/usb/musb/Kconfig create mode 100644 drivers/usb/musb/Makefile create mode 100644 drivers/usb/musb/cppi_dma.h create mode 100644 drivers/usb/musb/da8xx.c create mode 100644 drivers/usb/musb/jz4740.c create mode 100644 drivers/usb/musb/mediatek.c create mode 100644 drivers/usb/musb/mpfs.c 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_cppi41.c 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_trace.c create mode 100644 drivers/usb/musb/musb_trace.h create mode 100644 drivers/usb/musb/musb_virthub.c create mode 100644 drivers/usb/musb/musbhsdma.c create mode 100644 drivers/usb/musb/omap2430.c create mode 100644 drivers/usb/musb/omap2430.h create mode 100644 drivers/usb/musb/sunxi.c 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/of.c create mode 100644 drivers/usb/phy/phy-ab8500-usb.c create mode 100644 drivers/usb/phy/phy-am335x-control.c create mode 100644 drivers/usb/phy/phy-am335x-control.h create mode 100644 drivers/usb/phy/phy-am335x.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-generic.c create mode 100644 drivers/usb/phy/phy-generic.h create mode 100644 drivers/usb/phy/phy-gpio-vbus-usb.c create mode 100644 drivers/usb/phy/phy-isp1301.c create mode 100644 drivers/usb/phy/phy-keystone.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-omap-otg.c create mode 100644 drivers/usb/phy/phy-tahvo.c create mode 100644 drivers/usb/phy/phy-tegra-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/renesas_usbhs/rcar2.c create mode 100644 drivers/usb/renesas_usbhs/rcar2.h create mode 100644 drivers/usb/renesas_usbhs/rcar3.c create mode 100644 drivers/usb/renesas_usbhs/rcar3.h create mode 100644 drivers/usb/renesas_usbhs/rza.c create mode 100644 drivers/usb/renesas_usbhs/rza.h create mode 100644 drivers/usb/renesas_usbhs/rza2.c create mode 100644 drivers/usb/roles/Kconfig create mode 100644 drivers/usb/roles/Makefile create mode 100644 drivers/usb/roles/class.c create mode 100644 drivers/usb/roles/intel-xhci-usb-role-switch.c 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/f81534.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/garmin_gps.c create mode 100644 drivers/usb/serial/generic.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_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_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/mxuport.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/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/upd78f0730.c create mode 100644 drivers/usb/serial/usb-serial-simple.c 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/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/xr_serial.c create mode 100644 drivers/usb/serial/xsens_mt.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-detect.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_uas.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/typec/Kconfig create mode 100644 drivers/usb/typec/Makefile create mode 100644 drivers/usb/typec/altmodes/Kconfig create mode 100644 drivers/usb/typec/altmodes/Makefile create mode 100644 drivers/usb/typec/altmodes/displayport.c create mode 100644 drivers/usb/typec/altmodes/displayport.h create mode 100644 drivers/usb/typec/altmodes/nvidia.c create mode 100644 drivers/usb/typec/altmodes/thunderbolt.c create mode 100644 drivers/usb/typec/anx7411.c create mode 100644 drivers/usb/typec/bus.c create mode 100644 drivers/usb/typec/bus.h create mode 100644 drivers/usb/typec/class.c create mode 100644 drivers/usb/typec/class.h create mode 100644 drivers/usb/typec/hd3ss3220.c create mode 100644 drivers/usb/typec/mode_selection.c create mode 100644 drivers/usb/typec/mux.c create mode 100644 drivers/usb/typec/mux.h create mode 100644 drivers/usb/typec/mux/Kconfig create mode 100644 drivers/usb/typec/mux/Makefile create mode 100644 drivers/usb/typec/mux/fsa4480.c create mode 100644 drivers/usb/typec/mux/gpio-sbu-mux.c create mode 100644 drivers/usb/typec/mux/intel_pmc_mux.c create mode 100644 drivers/usb/typec/mux/it5205.c create mode 100644 drivers/usb/typec/mux/nb7vpq904m.c create mode 100644 drivers/usb/typec/mux/pi3usb30532.c create mode 100644 drivers/usb/typec/mux/ps883x.c create mode 100644 drivers/usb/typec/mux/ptn36502.c create mode 100644 drivers/usb/typec/mux/tusb1046.c create mode 100644 drivers/usb/typec/mux/wcd939x-usbss.c create mode 100644 drivers/usb/typec/pd.c create mode 100644 drivers/usb/typec/pd.h create mode 100644 drivers/usb/typec/port-mapper.c create mode 100644 drivers/usb/typec/retimer.c create mode 100644 drivers/usb/typec/retimer.h create mode 100644 drivers/usb/typec/rt1719.c create mode 100644 drivers/usb/typec/stusb160x.c create mode 100644 drivers/usb/typec/tcpm/Kconfig create mode 100644 drivers/usb/typec/tcpm/Makefile create mode 100644 drivers/usb/typec/tcpm/fusb302.c create mode 100644 drivers/usb/typec/tcpm/fusb302_reg.h create mode 100644 drivers/usb/typec/tcpm/maxim_contaminant.c create mode 100644 drivers/usb/typec/tcpm/qcom/Makefile create mode 100644 drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c create mode 100644 drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.h create mode 100644 drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c create mode 100644 drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h create mode 100644 drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy_stub.c create mode 100644 drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c create mode 100644 drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h create mode 100644 drivers/usb/typec/tcpm/tcpci.c create mode 100644 drivers/usb/typec/tcpm/tcpci_maxim.h create mode 100644 drivers/usb/typec/tcpm/tcpci_maxim_core.c create mode 100644 drivers/usb/typec/tcpm/tcpci_mt6360.c create mode 100644 drivers/usb/typec/tcpm/tcpci_mt6370.c create mode 100644 drivers/usb/typec/tcpm/tcpci_rt1711h.c create mode 100644 drivers/usb/typec/tcpm/tcpm.c create mode 100644 drivers/usb/typec/tcpm/wcove.c create mode 100644 drivers/usb/typec/tipd/Kconfig create mode 100644 drivers/usb/typec/tipd/Makefile create mode 100644 drivers/usb/typec/tipd/core.c create mode 100644 drivers/usb/typec/tipd/tps6598x.h create mode 100644 drivers/usb/typec/tipd/trace.c create mode 100644 drivers/usb/typec/tipd/trace.h create mode 100644 drivers/usb/typec/ucsi/Kconfig create mode 100644 drivers/usb/typec/ucsi/Makefile create mode 100644 drivers/usb/typec/ucsi/cros_ec_ucsi.c create mode 100644 drivers/usb/typec/ucsi/debugfs.c create mode 100644 drivers/usb/typec/ucsi/displayport.c create mode 100644 drivers/usb/typec/ucsi/psy.c create mode 100644 drivers/usb/typec/ucsi/thunderbolt.c create mode 100644 drivers/usb/typec/ucsi/trace.c create mode 100644 drivers/usb/typec/ucsi/trace.h create mode 100644 drivers/usb/typec/ucsi/ucsi.c create mode 100644 drivers/usb/typec/ucsi/ucsi.h create mode 100644 drivers/usb/typec/ucsi/ucsi_acpi.c create mode 100644 drivers/usb/typec/ucsi/ucsi_ccg.c create mode 100644 drivers/usb/typec/ucsi/ucsi_glink.c create mode 100644 drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c create mode 100644 drivers/usb/typec/ucsi/ucsi_stm32g0.c create mode 100644 drivers/usb/typec/ucsi/ucsi_yoga_c630.c create mode 100644 drivers/usb/typec/wusb3801.c create mode 100644 drivers/usb/usb-skeleton.c create mode 100644 drivers/usb/usbip/Kconfig create mode 100644 drivers/usb/usbip/Makefile create mode 100644 drivers/usb/usbip/stub.h create mode 100644 drivers/usb/usbip/stub_dev.c create mode 100644 drivers/usb/usbip/stub_main.c create mode 100644 drivers/usb/usbip/stub_rx.c create mode 100644 drivers/usb/usbip/stub_tx.c create mode 100644 drivers/usb/usbip/usbip_common.c create mode 100644 drivers/usb/usbip/usbip_common.h create mode 100644 drivers/usb/usbip/usbip_event.c create mode 100644 drivers/usb/usbip/vhci.h create mode 100644 drivers/usb/usbip/vhci_hcd.c create mode 100644 drivers/usb/usbip/vhci_rx.c create mode 100644 drivers/usb/usbip/vhci_sysfs.c create mode 100644 drivers/usb/usbip/vhci_tx.c create mode 100644 drivers/usb/usbip/vudc.h create mode 100644 drivers/usb/usbip/vudc_dev.c create mode 100644 drivers/usb/usbip/vudc_main.c create mode 100644 drivers/usb/usbip/vudc_rx.c create mode 100644 drivers/usb/usbip/vudc_sysfs.c create mode 100644 drivers/usb/usbip/vudc_transfer.c create mode 100644 drivers/usb/usbip/vudc_tx.c (limited to 'drivers/usb') diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig new file mode 100644 index 000000000..abf8c6cde --- /dev/null +++ b/drivers/usb/Kconfig @@ -0,0 +1,162 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# USB device configuration +# + +config USB_OHCI_BIG_ENDIAN_DESC + bool + +config USB_OHCI_BIG_ENDIAN_MMIO + bool + +config USB_OHCI_LITTLE_ENDIAN + bool + default n if PPC_MPC52xx + default y + +config USB_EHCI_BIG_ENDIAN_MMIO + bool + +config USB_EHCI_BIG_ENDIAN_DESC + bool + +config USB_UHCI_BIG_ENDIAN_MMIO + bool + +config USB_UHCI_BIG_ENDIAN_DESC + 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 + +source "drivers/usb/common/Kconfig" + +config USB_ARCH_HAS_HCD + def_bool y + +config USB + tristate "Support for Host-side USB" + depends on USB_ARCH_HAS_HCD + select GENERIC_ALLOCATOR + select USB_COMMON + 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. + +config USB_PCI + bool "PCI based USB host interface" + depends on PCI + default y + help + Many embedded system SOCs (e.g. freescale T2080) have both + PCI and USB modules with the USB module directly controlled by + registers and having no relationship to the PCI module. + + If you have such a device you may say N here and PCI related code + will not be built in the USB driver. + +config USB_PCI_AMD + bool "AMD PCI USB host support" + depends on USB_PCI && HAS_IOPORT + default X86 || MACH_LOONGSON64 || PPC_PASEMI + help + Enable workarounds for USB implementation quirks in SB600/SB700/SB800 + and later south bridge implementations. These are common on x86 PCs + with AMD CPUs but rarely used elsewhere, with the exception of a few + powerpc and mips desktop machines. + +if USB + +source "drivers/usb/core/Kconfig" + +source "drivers/usb/mon/Kconfig" + +source "drivers/usb/host/Kconfig" + +source "drivers/usb/renesas_usbhs/Kconfig" + +source "drivers/usb/class/Kconfig" + +source "drivers/usb/storage/Kconfig" + +source "drivers/usb/image/Kconfig" + +source "drivers/usb/usbip/Kconfig" + +endif + +comment "USB dual-mode controller drivers" + +source "drivers/usb/cdns3/Kconfig" + +source "drivers/usb/fotg210/Kconfig" + +source "drivers/usb/mtu3/Kconfig" + +source "drivers/usb/musb/Kconfig" + +source "drivers/usb/dwc3/Kconfig" + +source "drivers/usb/dwc2/Kconfig" + +source "drivers/usb/chipidea/Kconfig" + +source "drivers/usb/isp1760/Kconfig" + +comment "USB port drivers" + +if USB + +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" + +source "drivers/usb/typec/Kconfig" + +source "drivers/usb/roles/Kconfig" + +endif # USB_SUPPORT diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile new file mode 100644 index 000000000..eecbd631f --- /dev/null +++ b/drivers/usb/Makefile @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the kernel USB device drivers. +# + +# Object files in subdirectories + +obj-$(CONFIG_USB_COMMON) += common/ +obj-$(CONFIG_USB) += core/ +obj-$(CONFIG_USB_SUPPORT) += phy/ + +obj-$(CONFIG_USB_DWC3) += dwc3/ +obj-$(CONFIG_USB_DWC2) += dwc2/ +obj-$(CONFIG_USB_ISP1760) += isp1760/ + +obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns3/ + +obj-$(CONFIG_USB_FOTG210) += fotg210/ + +obj-$(CONFIG_USB_MON) += mon/ +obj-$(CONFIG_USB_MTU3) += mtu3/ + +obj-$(CONFIG_USB_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_R8A66597_HCD) += host/ +obj-$(CONFIG_USB_FSL_USB2) += host/ +obj-$(CONFIG_USB_FOTG210_HCD) += host/ +obj-$(CONFIG_USB_MAX3421_HCD) += host/ +obj-$(CONFIG_USB_XEN_HCD) += host/ + +obj-$(CONFIG_USB_C67X00_HCD) += c67x00/ + +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_EARLY_PRINTK_USB) += 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_USBIP_CORE) += usbip/ + +obj-$(CONFIG_TYPEC) += typec/ + +obj-$(CONFIG_USB_ROLE_SWITCH) += roles/ diff --git a/drivers/usb/atm/Kconfig b/drivers/usb/atm/Kconfig new file mode 100644 index 000000000..3d958a1a1 --- /dev/null +++ b/drivers/usb/atm/Kconfig @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# USB/ATM DSL configuration +# + +menuconfig USB_ATM + tristate "USB DSL modem support" + depends on ATM + select CRC32 + 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 000000000..7ac65ce1a --- /dev/null +++ b/drivers/usb/atm/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for USB ATM/xDSL drivers +# +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 000000000..636f7886f --- /dev/null +++ b/drivers/usb/atm/cxacru.c @@ -0,0 +1,1379 @@ +// SPDX-License-Identifier: GPL-2.0+ +/****************************************************************************** + * 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 + ******************************************************************************/ + +/* + * 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 "usbatm.h" + +#define DRIVER_AUTHOR "Roman Kagan, David Woodhouse, Duncan Sands, Simon Arlott" +#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; + + /* control 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_RO(_name) + +#define CXACRU_CMD_INIT(_name) \ +static DEVICE_ATTR_RW(_name) + +#define CXACRU_SET_INIT(_name) \ +static DEVICE_ATTR_WO(_name) + +#define CXACRU_ATTR_INIT(_value, _type, _name) \ +static ssize_t _name##_show(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 sprintf(buf, "%u\n", value); +} + +static ssize_t cxacru_sysfs_showattr_s8(s8 value, char *buf) +{ + return sprintf(buf, "%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 sprintf(buf, "%u\n", value); + return sprintf(buf, "%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 sprintf(buf, "%u\n", value); + return sprintf(buf, "%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 sprintf(buf, "%u\n", value); + return sprintf(buf, "%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 sprintf(buf, "%u\n", value); + return sprintf(buf, "%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 mac_address_show(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 sprintf(buf, "%pM\n", instance->usbatm->atm_dev->esi); +} + +static ssize_t adsl_state_show(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 sprintf(buf, "%u\n", value); + return sprintf(buf, "%s\n", str[value]); +} + +static ssize_t adsl_state_store(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; + fallthrough; + 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 adsl_config_store(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 > 0x7f) + return -EINVAL; + if (tmp < 0 || tmp > len - pos) + 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); + +static struct attribute *cxacru_attrs[] = { + &dev_attr_adsl_config.attr, + &dev_attr_adsl_state.attr, + &dev_attr_adsl_controller_version.attr, + &dev_attr_adsl_headend_environment.attr, + &dev_attr_adsl_headend.attr, + &dev_attr_modulation.attr, + &dev_attr_line_startable.attr, + &dev_attr_downstream_hec_errors.attr, + &dev_attr_upstream_hec_errors.attr, + &dev_attr_downstream_fec_errors.attr, + &dev_attr_upstream_fec_errors.attr, + &dev_attr_downstream_crc_errors.attr, + &dev_attr_upstream_crc_errors.attr, + &dev_attr_startup_attempts.attr, + &dev_attr_downstream_bits_per_frame.attr, + &dev_attr_upstream_bits_per_frame.attr, + &dev_attr_transmitter_power.attr, + &dev_attr_downstream_attenuation.attr, + &dev_attr_upstream_attenuation.attr, + &dev_attr_downstream_snr_margin.attr, + &dev_attr_upstream_snr_margin.attr, + &dev_attr_mac_address.attr, + &dev_attr_line_status.attr, + &dev_attr_link_status.attr, + &dev_attr_upstream_rate.attr, + &dev_attr_downstream_rate.attr, + NULL, +}; +ATTRIBUTE_GROUPS(cxacru); + +/* the following three functions are stolen from drivers/usb/core/message.c */ +static void cxacru_blocking_completion(struct urb *urb) +{ + complete(urb->context); +} + +struct cxacru_timer { + struct timer_list timer; + struct urb *urb; +}; + +static void cxacru_timeout_kill(struct timer_list *t) +{ + struct cxacru_timer *timer = timer_container_of(timer, t, timer); + + usb_unlink_urb(timer->urb); +} + +static int cxacru_start_wait_urb(struct urb *urb, struct completion *done, + int *actual_length) +{ + struct cxacru_timer timer = { + .urb = urb, + }; + + timer_setup_on_stack(&timer.timer, cxacru_timeout_kill, 0); + mod_timer(&timer.timer, jiffies + msecs_to_jiffies(CMD_TIMEOUT)); + wait_for_completion(done); + timer_delete_sync(&timer.timer); + timer_destroy_on_stack(&timer.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: + if (ret < 0) + usb_kill_urb(instance->rcv_urb); + 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 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; + } + + /* 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; + fallthrough; + 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); + + if (start_polling) + cxacru_poll_status(&instance->poll_work.work); + return 0; +} + +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_info(usbatm, "ADSL state: running\n"); + break; + + case 1: + atm_info(usbatm, "ADSL state: stopped\n"); + break; + + default: + atm_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 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; + 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; + int ret; + + 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; + } + } + + /* 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); + goto done; + } + + /* 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); + goto done; + } + + /* 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); + goto done; + } + + /* 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); + goto done; + } + + /* 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); + goto done; + } + } + + /* 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); + goto done; + } + + 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); + goto done; + } + + /* 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); + goto done; + } + +done: + 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]; + static const u8 ep_addrs[] = { + CXACRU_EP_CMD + USB_DIR_IN, + CXACRU_EP_CMD + USB_DIR_OUT, + 0}; + int ret; + + /* instance init */ + instance = kzalloc_obj(*instance); + if (!instance) + 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) { + ret = -ENOMEM; + goto fail; + } + instance->snd_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!instance->snd_urb) { + ret = -ENOMEM; + goto fail; + } + + if (!cmd_ep) { + usb_dbg(usbatm_instance, "cxacru_bind: no command endpoint\n"); + ret = -ENODEV; + goto fail; + } + + if (usb_endpoint_xfer_int(&cmd_ep->desc)) + ret = usb_check_int_endpoints(intf, ep_addrs); + else + ret = usb_check_bulk_endpoints(intf, ep_addrs); + + if (!ret) { + usb_err(usbatm_instance, "cxacru_bind: interface has incorrect endpoints\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; + 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); + + /* stop polling from being stopped or started */ + instance->poll_state = CXPOLL_SHUTDOWN; + mutex_unlock(&instance->poll_state_serialize); + + 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, + .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, + .dev_groups = cxacru_groups, +}; + +module_usb_driver(cxacru_usb_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/atm/speedtch.c b/drivers/usb/atm/speedtch.c new file mode 100644 index 000000000..9d28c652b --- /dev/null +++ b/drivers/usb/atm/speedtch.c @@ -0,0 +1,949 @@ +// SPDX-License-Identifier: GPL-2.0+ +/****************************************************************************** + * 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 + ******************************************************************************/ + +#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_DESC "Alcatel SpeedTouch USB driver" + +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__); + + buffer = (unsigned char *)__get_free_page(GFP_KERNEL); + if (!buffer) { + 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(struct timer_list *t) +{ + struct speedtch_instance_data *instance = timer_container_of(instance, + t, + status_check_timer); + + 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(struct timer_list *t) +{ + struct speedtch_instance_data *instance = timer_container_of(instance, + t, + resubmit_timer); + 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)) { + timer_delete(&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; + } + + int_urb = instance->int_urb; + if (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: + int_urb = instance->int_urb; + if (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__); + + timer_delete_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); + timer_delete_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); + timer_delete_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 const 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++) { + cur_intf = usb_ifnum_to_if(usb_dev, i); + if (cur_intf) { + 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; + } + + data_intf = usb_ifnum_to_if(usb_dev, INTERFACE_DATA); + if (!data_intf) { + 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_obj(*instance); + + if (!instance) { + 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); + timer_setup(&instance->status_check_timer, speedtch_status_poll, 0); + instance->last_status = 0xff; + instance->poll_delay = MIN_POLL_DELAY; + + timer_setup(&instance->resubmit_timer, speedtch_resubmit_int, 0); + + 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, 16); + 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"); diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c new file mode 100644 index 000000000..61723e7ab --- /dev/null +++ b/drivers/usb/atm/ueagle-atm.c @@ -0,0 +1,2753 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-2-Clause) +/* + * Copyright (c) 2003, 2004 + * Damien Bergamini . All rights reserved. + * + * Copyright (c) 2005-2007 Matthieu Castet + * Copyright (c) 2005-2007 Stanislaw Gruszka + * + * 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 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_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[]; +} __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_freezable_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_interface *intf = context; + struct usb_device *usb = interface_to_usbdev(intf); + struct completion *fw_done = usb_get_intfdata(intf); + const u8 *pfw; + u8 value; + u32 crc = 0; + int ret, size; + + 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); + complete(fw_done); +} + +/* + * uea_load_firmware - Load usb firmware for pre-firmware devices. + */ +static int uea_load_firmware(struct usb_interface *intf, unsigned int ver) +{ + int ret; + char *fw_name = EAGLE_FIRMWARE; + struct usb_device *usb = interface_to_usbdev(intf); + + 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, intf, + 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); + + 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 endianness */ + } 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) { + 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) { + 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_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; + + return wait_cmv_ack(sc); +} + +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; + + 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; + + return wait_cmv_ack(sc); +} + +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 timing (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; + + 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; + + 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; + + kernel_param_lock(THIS_MODULE); + /* 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]; + + snprintf(cmv_name, UEA_FW_NAME_MAX, FW_DIR "%s%s", + file, ver == 2 ? ".v2" : ""); + kernel_param_unlock(THIS_MODULE); +} + +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_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; + 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(); + 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)); + } + + 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; + + 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: + 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; + + if (le16_to_cpu(cmv->wPreamble) != E1_PREAMBLE) + goto bad1; + + if (cmv->bDirection != E1_MODEMTOHOST) + goto bad1; + + /* FIXME : ADI930 reply wrong preamble (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); + 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); + 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)); + return; + +bad1: + uea_err(INS_TO_USBDEV(sc), "invalid cmv received, " + "wPreamble %d, bDirection %d\n", + le16_to_cpu(cmv->wPreamble), cmv->bDirection); +} + +/* 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_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); + 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); + 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)); + 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; + + 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, struct usb_interface *intf) +{ + struct intr_pkt *intr; + int ret = -ENOMEM; + int size; + + 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); + + if (intf->cur_altsetting->desc.bNumEndpoints < 1) { + ret = -ENODEV; + goto err0; + } + + intr = kmalloc(size, GFP_KERNEL); + if (!intr) + goto err0; + + sc->urb_int = usb_alloc_urb(0, GFP_KERNEL); + if (!sc->urb_int) + 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, + intf->cur_altsetting->endpoint[0].desc.bInterval); + + ret = usb_submit_urb(sc->urb_int, GFP_KERNEL); + if (ret < 0) { + uea_err(INS_TO_USBDEV(sc), + "urb submission 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"); + ret = PTR_ERR(sc->kthread); + goto err2; + } + + return 0; + +err2: + usb_kill_urb(sc->urb_int); +err1: + usb_free_urb(sc->urb_int); + sc->urb_int = NULL; + kfree(intr); +err0: + return ret; +} + +/* + * Stop the modem : kill kernel thread and free data + */ +static void uea_stop(struct uea_softc *sc) +{ + int ret; + + 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); +} + +/* 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 stat_status_show(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 = sysfs_emit(buf, "%08x\n", sc->stats.phy.state); +out: + mutex_unlock(&uea_mutex); + return ret; +} + +static ssize_t stat_status_store(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_RW(stat_status); + +static ssize_t stat_human_status_show(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 = sysfs_emit(buf, "Modem is booting\n"); + break; + case 1: + ret = sysfs_emit(buf, "Modem is initializing\n"); + break; + case 2: + ret = sysfs_emit(buf, "Modem is operational\n"); + break; + case 3: + ret = sysfs_emit(buf, "Modem synchronization failed\n"); + break; + default: + ret = sysfs_emit(buf, "Modem state is unknown\n"); + break; + } +out: + mutex_unlock(&uea_mutex); + return ret; +} + +static DEVICE_ATTR_RO(stat_human_status); + +static ssize_t stat_delin_show(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 = sysfs_emit(buf, "%s\n", delin); +out: + mutex_unlock(&uea_mutex); + return ret; +} + +static DEVICE_ATTR_RO(stat_delin); + +#define UEA_ATTR(name, reset) \ + \ +static ssize_t stat_##name##_show(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 = sysfs_emit(buf, "%08x\n", sc->stats.phy.name); \ + if (reset) \ + sc->stats.phy.name = 0; \ +out: \ + mutex_unlock(&uea_mutex); \ + return ret; \ +} \ + \ +static DEVICE_ATTR_RO(stat_##name) + +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 *uea_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, +}; +ATTRIBUTE_GROUPS(uea); + +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; + + /* interface 0 is for firmware/monitoring */ + if (ifnum != UEA_INTR_IFACE_NO) + return -ENODEV; + + usbatm->flags = (modem_index < NB_MODEM && 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_obj(struct uea_softc); + if (!sc) + 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 = uea_boot(sc, intf); + if (ret < 0) + goto error; + + return 0; + +error: + kfree(sc); + return ret; +} + +static void uea_unbind(struct usbatm_data *usbatm, struct usb_interface *intf) +{ + struct uea_softc *sc = usbatm->driver_data; + + 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); + bool single_iface = usb->config->desc.bNumInterfaces == 1; + int ret; + + uea_dbg(usb, "ADSL device found with 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)]); + + /* + * uea_probe() decides between the pre-firmware and post-firmware case + * from the USB id and stores a different object as interface data in + * each case: a struct completion for a pre-firmware device, a struct + * usbatm_data for a post-firmware one. uea_disconnect() instead tells + * the two apart by the number of interfaces (a pre-firmware device + * exposes a single interface, ADI930 has 2 and eagle has 3). A crafted + * device advertising a pre-firmware id together with a multi-interface + * descriptor (or the other way around) makes the two disagree, so that + * usbatm_usb_disconnect() treats the small completion object as a + * struct usbatm_data and reads out of bounds. Reject such inconsistent + * descriptors so both paths make the same decision. + */ + if (UEA_IS_PREFIRM(id) != single_iface) + return -ENODEV; + + usb_reset_device(usb); + + if (UEA_IS_PREFIRM(id)) { + struct completion *fw_done; + + /* Wait for the firmware load to be done, in .disconnect() */ + fw_done = kzalloc_obj(*fw_done); + if (!fw_done) + return -ENOMEM; + + init_completion(fw_done); + usb_set_intfdata(intf, fw_done); + + ret = uea_load_firmware(intf, UEA_CHIP_VERSION(id)); + if (ret) + kfree(fw_done); + + return ret; + } + + 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; + + /* 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"); + } else if (usb->config->desc.bNumInterfaces == 1) { + struct completion *fw_done = usb_get_intfdata(intf); + + uea_dbg(usb, "pre-firmware device, waiting firmware upload\n"); + wait_for_completion(fw_done); + uea_dbg(usb, "pre-firmware device, finished waiting\n"); + kfree(fw_done); + } +} + +/* + * 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, + .dev_groups = uea_groups, +}; + +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 000000000..7b0c79139 --- /dev/null +++ b/drivers/usb/atm/usbatm.c @@ -0,0 +1,1329 @@ +// SPDX-License-Identifier: GPL-2.0+ +/****************************************************************************** + * 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 + ******************************************************************************/ + +/* + * 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_DESC "Generic USB ATM/DSL I/O" + +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 const 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); */ + + /* Can be invoked from task context, protect against interrupts */ + 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); + + 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); + } + + 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); + + skb = dev_alloc_skb(length); + if (!skb) { + 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; + + 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; + + 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(struct tasklet_struct *t) +{ + struct usbatm_data *instance = from_tasklet(instance, t, + rx_channel.tasklet); + 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; + + 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(struct tasklet_struct *t) +{ + struct usbatm_data *instance = from_tasklet(instance, t, + tx_channel.tasklet); + 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; + + 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); +} + +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 VERBOSE_DEBUG + printk_ratelimited(KERN_DEBUG "%s: %s!\n", __func__, instance ? "disconnected" : "NULL instance"); +#endif + err = -ENODEV; + goto fail; + } + + 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); + + 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) +{ + kref_get(&instance->refcount); +} + +static void usbatm_put_instance(struct usbatm_data *instance) +{ + 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; + + 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; + + /* 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; + } + + new = kzalloc_obj(struct usbatm_vcc_data); + if (!new) { + 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; + + 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); +} + +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 = 8; + atm_dev->ci_range.vci_bits = 16; + 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); + + kthread_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, "%s", + 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(struct timer_list *t) +{ + struct usbatm_channel *channel = timer_container_of(channel, t, delay); + + tasklet_schedule(&channel->tasklet); +} + +static void usbatm_init_channel(struct usbatm_channel *channel) +{ + spin_lock_init(&channel->lock); + INIT_LIST_HEAD(&channel->list); + timer_setup(&channel->delay, usbatm_tasklet_schedule, 0); +} + +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; + size_t size; + + /* instance init */ + size = struct_size(instance, urbs, + size_add(num_rcv_urbs, num_snd_urbs)); + instance = kzalloc(size, GFP_KERNEL); + if (!instance) + return -ENOMEM; + + /* public fields */ + + instance->driver = driver; + strscpy(instance->driver_name, driver->driver_name, + sizeof(instance->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_setup(&instance->rx_channel.tasklet, usbatm_rx_process); + tasklet_setup(&instance->tx_channel.tasklet, usbatm_tx_process); + 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); + + 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; + + 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); + } + + /* 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; + + urb = usb_alloc_urb(iso_packets, GFP_KERNEL); + if (!urb) { + 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) { + 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: allocated 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) { + 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; + + 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]); + + timer_delete_sync(&instance->rx_channel.delay); + timer_delete_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) > sizeof_field(struct sk_buff, cb)) { + pr_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"); + +/************ +** 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 000000000..d96658e2e --- /dev/null +++ b/drivers/usb/atm/usbatm.h @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/****************************************************************************** + * usbatm.h - Generic USB xDSL driver core + * + * Copyright (C) 2001, Alcatel + * Copyright (C) 2003, Duncan Sands, SolNegro, Josep Comas + * Copyright (C) 2004, David Woodhouse + ******************************************************************************/ + +#ifndef _USBATM_H_ +#define _USBATM_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* +#define VERBOSE_DEBUG +*/ + +#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) +#define usb_dbg(instance, format, arg...) \ + dev_dbg(&(instance)->usb_intf->dev , format , ## arg) + +/* 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) +#define atm_dbg(instance, format, ...) \ + pr_debug("ATM dev %d: " format, \ + (instance)->atm_dev->number, ##__VA_ARGS__) +#define atm_rldbg(instance, format, ...) \ + pr_debug_ratelimited("ATM dev %d: " format, \ + (instance)->atm_dev->number, ##__VA_ARGS__) + +/* 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[]; +}; + +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 000000000..5c1e1f521 --- /dev/null +++ b/drivers/usb/atm/xusbatm.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0+ +/****************************************************************************** + * xusbatm.c - dumb usbatm-based driver for modems initialized in userspace + * + * Copyright (C) 2005 Duncan Sands, Roman Kagan (rkagan % mail ! ru) + ******************************************************************************/ + +#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; + } + ret = usb_set_interface(usb_dev, ifnum, altsetting); + if (ret) { + 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->driver_info; + 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); + + ret = xusbatm_capture_intf(usbatm, usb_dev, rx_intf, rx_alt, rx_intf != intf); + if (ret) + 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, + .no_dynamic_id = 1, +}; + +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) { + pr_warn("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_usb_ids[i].driver_info = 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"); diff --git a/drivers/usb/c67x00/Makefile b/drivers/usb/c67x00/Makefile new file mode 100644 index 000000000..0cde62d06 --- /dev/null +++ b/drivers/usb/c67x00/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for Cypress C67X00 USB Controller +# + +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 000000000..7ca6682c9 --- /dev/null +++ b/drivers/usb/c67x00/c67x00-drv.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 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 = dev_get_platdata(&pdev->dev); + if (!pdata) + return -ENODEV; + + c67x00 = kzalloc_obj(*c67x00); + 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 = dev_get_platdata(&pdev->dev); + 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 void 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); + free_irq(res->start, c67x00); + + iounmap(c67x00->hpi.base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + kfree(c67x00); +} + +static struct platform_driver c67x00_driver = { + .probe = c67x00_drv_probe, + .remove = c67x00_drv_remove, + .driver = { + .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 000000000..39f237666 --- /dev/null +++ b/drivers/usb/c67x00/c67x00-hcd.c @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#include +#include +#include + +#include "c67x00.h" +#include "c67x00-hcd.h" + +/* -------------------------------------------------------------------------- + * Root Hub Support + */ + +static __u8 c67x00_hub_des[] = { + 0x09, /* __u8 bLength; */ + USB_DT_HUB, /* __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 const 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; + } + + device_wakeup_enable(hcd->self.controller); + + 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 000000000..6ba5adced --- /dev/null +++ b/drivers/usb/c67x00/c67x00-hcd.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + */ + +#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 circumstances, 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 work_struct work; + + 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_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 000000000..7a214a3a6 --- /dev/null +++ b/drivers/usb/c67x00/c67x00-ll-hpi.c @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#include +#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 assumption 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 + +/* + * According to CY7C67300 specification (tables 140 and 141) HPI read and + * write cycle duration Tcyc must be at least 6T long, where T is 1/48MHz, + * which is 125ns. + */ +#define HPI_T_CYC_NS 125 + +static inline u16 hpi_read_reg(struct c67x00_device *dev, int reg) +{ + ndelay(HPI_T_CYC_NS); + return __raw_readw(dev->hpi.base + reg * dev->hpi.regstep); +} + +static inline void hpi_write_reg(struct c67x00_device *dev, int reg, u16 value) +{ + ndelay(HPI_T_CYC_NS); + __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 000000000..ae6c94f41 --- /dev/null +++ b/drivers/usb/c67x00/c67x00-sched.c @@ -0,0 +1,1148 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#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)) + +/* -------------------------------------------------------------------------- */ + +/* + * 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: %*ph\n", td_length(td), td->data); +} + +/* -------------------------------------------------------------------------- */ +/* 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_obj(*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 */ + reinit_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; + + /* Allocate and initialize urb private data */ + urbp = kzalloc_obj(*urbp, mem_flags); + if (!urbp) { + ret = -ENOMEM; + goto err_urbp; + } + + 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; + + 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: + usb_hcd_unlink_urb_from_ep(hcd, urb); +err_not_linked: + spin_unlock_irqrestore(&c67x00->lock, flags); + kfree(urbp); +err_urbp: + + 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, 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_obj(*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); + + 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; + } + 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) { + dev_dbg(c67x00_hcd_dev(c67x00), "create failed: %d\n", + ret); + urb->iso_frame_desc[urbp->cnt].actual_length = 0; + urb->iso_frame_desc[urbp->cnt].status = ret; + } + + urbp->ep_data->next_frame = + frame_add(urbp->ep_data->next_frame, urb->interval); + urbp->cnt++; + if (ret && urbp->cnt == urb->number_of_packets) + c67x00_giveback_urb(c67x00, urb, 0); + } + 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); + + 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; + int cnt; + + if (!urb) + return; + + 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_work(struct work_struct *work) +{ + struct c67x00_hcd *c67x00; + + c67x00 = container_of(work, struct c67x00_hcd, work); + c67x00_do_work(c67x00); +} + +void c67x00_sched_kick(struct c67x00_hcd *c67x00) +{ + queue_work(system_highpri_wq, &c67x00->work); +} + +int c67x00_sched_start_scheduler(struct c67x00_hcd *c67x00) +{ + INIT_WORK(&c67x00->work, c67x00_sched_work); + return 0; +} + +void c67x00_sched_stop_scheduler(struct c67x00_hcd *c67x00) +{ + cancel_work_sync(&c67x00->work); +} diff --git a/drivers/usb/c67x00/c67x00.h b/drivers/usb/c67x00/c67x00.h new file mode 100644 index 000000000..a4456d0ff --- /dev/null +++ b/drivers/usb/c67x00/c67x00.h @@ -0,0 +1,280 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + */ + +#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/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig new file mode 100644 index 000000000..39ad23d1a --- /dev/null +++ b/drivers/usb/cdns3/Kconfig @@ -0,0 +1,115 @@ +config USB_CDNS_SUPPORT + tristate "Cadence USB Support" + depends on USB_SUPPORT && HAS_DMA + depends on USB || USB_GADGET + depends on USB if !USB_GADGET + depends on USB_GADGET if !USB + select USB_XHCI_PLATFORM if USB_XHCI_HCD + select USB_ROLE_SWITCH + help + Say Y here if your system has a Cadence USBSS or USBSSP + dual-role controller. + It supports: dual-role switch, Host-only, and Peripheral-only. + +if USB_CDNS_SUPPORT + +config USB_CDNS3 + tristate "Cadence USB dual-role controller (USBSS and USBSSP)" + depends on USB_CDNS_SUPPORT + help + Say Y or M here if your system has an on-chip Cadence USB + dual-role controller. This covers both USBSS (USB 3.0) and + USBSSP (SuperSpeed Plus) IP; the driver detects the variant at + runtime. + + The core driver (core, DRD, generic platform binding for the + "cdns,usb3" device tree compatible, optional host and gadget) + builds as one module named cdns.ko when built as a loadable + module. + + It supports: dual-role switch, Host-only, and Peripheral-only. + +if USB_CDNS3 + +config USB_CDNS3_HOST + bool "Cadence USB host controller (xHCI)" + depends on USB=y || USB=USB_CDNS3 + help + Say Y here to enable host controller functionality for Cadence + USBSS and USBSSP dual-role controllers. + + The host controller is xHCI compliant and uses the standard + xHCI driver. + +config USB_CDNS3_GADGET + bool "Cadence USB device controller (USBSS and USBSSP)" + depends on USB_GADGET=y || USB_GADGET=USB_CDNS3 + help + Say Y here to include Cadence USB device (gadget) support for + both USBSS (USB 3.0) and USBSSP (SuperSpeed Plus) IP in the + cdns.ko module. The implementation is selected at runtime from + the detected controller version. + + USBSS gadget supports FF, HS and SS mode (not LS or SSP). + USBSSP gadget supports FF, HS, SS and SSP mode (not LS). + +comment "Platform glue driver support" + +config USB_CDNS3_PCI_WRAP + tristate "Cadence USB3 support on PCIe-based platforms" + depends on USB_PCI && ACPI + default USB_CDNS3 + help + If you're using the USBSS Core IP with a PCIe, please say + 'Y' or 'M' here. + + If you choose to build this driver as module it will + be dynamically linked and module will be called cdns3-pci.ko + +config USB_CDNSP_PCI + tristate "Cadence USBSSP support on PCIe-based platforms" + depends on USB_PCI && ACPI + default USB_CDNS3 + help + If you're using the USBSSP Core IP with a PCIe, please say + 'Y' or 'M' here. + + If you choose to build this driver as module it will + be dynamically linked and module will be called cdnsp-pci.ko + +config USB_CDNS3_TI + tristate "Cadence USB3 support on TI platforms" + depends on ARCH_K3 || COMPILE_TEST + default USB_CDNS3 + help + Say 'Y' or 'M' here if you are building for Texas Instruments + platforms that contain Cadence USB3 controller core. + + e.g. J721e. + +config USB_CDNS3_IMX + tristate "Cadence USB3 support on NXP i.MX platforms" + depends on ARCH_MXC || COMPILE_TEST + default USB_CDNS3 + help + Say 'Y' or 'M' here if you are building for NXP i.MX + platforms that contain Cadence USB3 controller core. + + For example, imx8qm and imx8qxp. + +config USB_CDNS3_STARFIVE + tristate "Cadence USB3 support on StarFive SoC platforms" + depends on ARCH_STARFIVE || COMPILE_TEST + default USB_CDNS3 + help + Say 'Y' or 'M' here if you are building for StarFive SoCs + platforms that contain Cadence USB3 controller core. + + e.g. JH7110. + + If you choose to build this driver as module it will + be dynamically linked and module will be called cdns3-starfive.ko + +endif # USB_CDNS3 + +endif # USB_CDNS_SUPPORT diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile new file mode 100644 index 000000000..b2e4ba6a4 --- /dev/null +++ b/drivers/usb/cdns3/Makefile @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0 +# define_trace.h needs to know how to find our header +CFLAGS_cdns3-trace.o := -I$(src) +CFLAGS_cdnsp-trace.o := -I$(src) + +obj-$(CONFIG_USB_CDNS3) += cdns.o + +cdns-y := core.o drd.o cdns3-plat.o +cdns-$(CONFIG_USB_CDNS3_HOST) += host.o + +ifneq ($(CONFIG_USB_CDNS3_GADGET),) +cdns-y += cdns3-gadget.o cdns3-ep0.o \ + cdnsp-ring.o cdnsp-gadget.o \ + cdnsp-mem.o cdnsp-ep0.o +endif + +ifneq ($(CONFIG_TRACING),) +ifneq ($(CONFIG_USB_CDNS3_GADGET),) +cdns-y += cdns3-trace.o cdnsp-trace.o +endif +endif + +## +# Platform-specific glue layers (PCI wrappers, SoC integration) +## +obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci-wrap.o +obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-pci.o +obj-$(CONFIG_USB_CDNS3_TI) += cdns3-ti.o +obj-$(CONFIG_USB_CDNS3_IMX) += cdns3-imx.o +obj-$(CONFIG_USB_CDNS3_STARFIVE) += cdns3-starfive.o diff --git a/drivers/usb/cdns3/cdns3-debug.h b/drivers/usb/cdns3/cdns3-debug.h new file mode 100644 index 000000000..4618cfe85 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-debug.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence USBSS DRD Driver. + * Debug header file. + * + * Copyright (C) 2018-2019 Cadence. + * + * Author: Pawel Laszczak + */ +#ifndef __LINUX_CDNS3_DEBUG +#define __LINUX_CDNS3_DEBUG + +#include "core.h" + +static inline char *cdns3_decode_usb_irq(char *str, + enum usb_device_speed speed, + u32 usb_ists) +{ + int ret; + + ret = sprintf(str, "IRQ %08x = ", usb_ists); + + if (usb_ists & (USB_ISTS_CON2I | USB_ISTS_CONI)) { + ret += sprintf(str + ret, "Connection %s\n", + usb_speed_string(speed)); + } + if (usb_ists & USB_ISTS_DIS2I || usb_ists & USB_ISTS_DISI) + ret += sprintf(str + ret, "Disconnection "); + if (usb_ists & USB_ISTS_L2ENTI) + ret += sprintf(str + ret, "suspended "); + if (usb_ists & USB_ISTS_L1ENTI) + ret += sprintf(str + ret, "L1 enter "); + if (usb_ists & USB_ISTS_L1EXTI) + ret += sprintf(str + ret, "L1 exit "); + if (usb_ists & USB_ISTS_L2ENTI) + ret += sprintf(str + ret, "L2 enter "); + if (usb_ists & USB_ISTS_L2EXTI) + ret += sprintf(str + ret, "L2 exit "); + if (usb_ists & USB_ISTS_U3EXTI) + ret += sprintf(str + ret, "U3 exit "); + if (usb_ists & USB_ISTS_UWRESI) + ret += sprintf(str + ret, "Warm Reset "); + if (usb_ists & USB_ISTS_UHRESI) + ret += sprintf(str + ret, "Hot Reset "); + if (usb_ists & USB_ISTS_U2RESI) + ret += sprintf(str + ret, "Reset"); + + return str; +} + +static inline char *cdns3_decode_ep_irq(char *str, + u32 ep_sts, + const char *ep_name) +{ + int ret; + + ret = sprintf(str, "IRQ for %s: %08x ", ep_name, ep_sts); + + if (ep_sts & EP_STS_SETUP) + ret += sprintf(str + ret, "SETUP "); + if (ep_sts & EP_STS_IOC) + ret += sprintf(str + ret, "IOC "); + if (ep_sts & EP_STS_ISP) + ret += sprintf(str + ret, "ISP "); + if (ep_sts & EP_STS_DESCMIS) + ret += sprintf(str + ret, "DESCMIS "); + if (ep_sts & EP_STS_STREAMR) + ret += sprintf(str + ret, "STREAMR "); + if (ep_sts & EP_STS_MD_EXIT) + ret += sprintf(str + ret, "MD_EXIT "); + if (ep_sts & EP_STS_TRBERR) + ret += sprintf(str + ret, "TRBERR "); + if (ep_sts & EP_STS_NRDY) + ret += sprintf(str + ret, "NRDY "); + if (ep_sts & EP_STS_PRIME) + ret += sprintf(str + ret, "PRIME "); + if (ep_sts & EP_STS_SIDERR) + ret += sprintf(str + ret, "SIDERRT "); + if (ep_sts & EP_STS_OUTSMM) + ret += sprintf(str + ret, "OUTSMM "); + if (ep_sts & EP_STS_ISOERR) + ret += sprintf(str + ret, "ISOERR "); + if (ep_sts & EP_STS_IOT) + ret += sprintf(str + ret, "IOT "); + + return str; +} + +static inline char *cdns3_decode_epx_irq(char *str, + char *ep_name, + u32 ep_sts) +{ + return cdns3_decode_ep_irq(str, ep_sts, ep_name); +} + +static inline char *cdns3_decode_ep0_irq(char *str, + int dir, + u32 ep_sts) +{ + return cdns3_decode_ep_irq(str, ep_sts, + dir ? "ep0IN" : "ep0OUT"); +} + +/** + * Debug a transfer ring. + * + * Prints out all TRBs in the endpoint ring, even those after the Link TRB. + *. + */ +static inline char *cdns3_dbg_ring(struct cdns3_endpoint *priv_ep, char *str) +{ + dma_addr_t addr = priv_ep->trb_pool_dma; + struct cdns3_trb *trb; + int trb_per_sector; + int ret = 0; + int i; + + trb_per_sector = GET_TRBS_PER_SEGMENT(priv_ep->type); + + trb = &priv_ep->trb_pool[priv_ep->dequeue]; + ret += sprintf(str + ret, "\n\t\tRing contents for %s:", priv_ep->name); + + ret += sprintf(str + ret, + "\n\t\tRing deq index: %d, trb: %p (virt), 0x%llx (dma)\n", + priv_ep->dequeue, trb, + (unsigned long long)cdns3_trb_virt_to_dma(priv_ep, trb)); + + trb = &priv_ep->trb_pool[priv_ep->enqueue]; + ret += sprintf(str + ret, + "\t\tRing enq index: %d, trb: %p (virt), 0x%llx (dma)\n", + priv_ep->enqueue, trb, + (unsigned long long)cdns3_trb_virt_to_dma(priv_ep, trb)); + + ret += sprintf(str + ret, + "\t\tfree trbs: %d, CCS=%d, PCS=%d\n", + priv_ep->free_trbs, priv_ep->ccs, priv_ep->pcs); + + if (trb_per_sector > TRBS_PER_SEGMENT) { + sprintf(str + ret, "\t\tTransfer ring %d too big\n", + trb_per_sector); + return str; + } + + for (i = 0; i < trb_per_sector; ++i) { + trb = &priv_ep->trb_pool[i]; + ret += sprintf(str + ret, + "\t\t@%pad %08x %08x %08x\n", &addr, + le32_to_cpu(trb->buffer), + le32_to_cpu(trb->length), + le32_to_cpu(trb->control)); + addr += sizeof(*trb); + } + + return str; +} + +#endif /*__LINUX_CDNS3_DEBUG*/ diff --git a/drivers/usb/cdns3/cdns3-ep0.c b/drivers/usb/cdns3/cdns3-ep0.c new file mode 100644 index 000000000..e29989d57 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-ep0.c @@ -0,0 +1,895 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBSS DRD Driver - gadget side. + * + * Copyright (C) 2018 Cadence Design Systems. + * Copyright (C) 2017-2018 NXP + * + * Authors: Pawel Jez , + * Pawel Laszczak + * Peter Chen + */ + +#include +#include + +#include "cdns3-gadget.h" +#include "cdns3-trace.h" + +static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, +}; + +/** + * cdns3_ep0_run_transfer - Do transfer on default endpoint hardware + * @priv_dev: extended gadget object + * @dma_addr: physical address where data is/will be stored + * @length: data length + * @erdy: set it to 1 when ERDY packet should be sent - + * exit from flow control state + * @zlp: add zero length packet + */ +static void cdns3_ep0_run_transfer(struct cdns3_device *priv_dev, + dma_addr_t dma_addr, + unsigned int length, int erdy, int zlp) +{ + struct cdns3_usb_regs __iomem *regs = priv_dev->regs; + struct cdns3_endpoint *priv_ep = priv_dev->eps[0]; + + priv_ep->trb_pool[0].buffer = cpu_to_le32(TRB_BUFFER(dma_addr)); + priv_ep->trb_pool[0].length = cpu_to_le32(TRB_LEN(length)); + + if (zlp) { + priv_ep->trb_pool[0].control = cpu_to_le32(TRB_CYCLE | TRB_TYPE(TRB_NORMAL)); + priv_ep->trb_pool[1].buffer = cpu_to_le32(TRB_BUFFER(dma_addr)); + priv_ep->trb_pool[1].length = cpu_to_le32(TRB_LEN(0)); + priv_ep->trb_pool[1].control = cpu_to_le32(TRB_CYCLE | TRB_IOC | + TRB_TYPE(TRB_NORMAL)); + } else { + priv_ep->trb_pool[0].control = cpu_to_le32(TRB_CYCLE | TRB_IOC | + TRB_TYPE(TRB_NORMAL)); + priv_ep->trb_pool[1].control = 0; + } + + trace_cdns3_prepare_trb(priv_ep, priv_ep->trb_pool); + + cdns3_select_ep(priv_dev, priv_dev->ep0_data_dir); + + writel(EP_STS_TRBERR, ®s->ep_sts); + writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma), ®s->ep_traddr); + trace_cdns3_doorbell_ep0(priv_dev->ep0_data_dir ? "ep0in" : "ep0out", + readl(®s->ep_traddr)); + + /* TRB should be prepared before starting transfer. */ + writel(EP_CMD_DRDY, ®s->ep_cmd); + + /* Resume controller before arming transfer. */ + __cdns3_gadget_wakeup(priv_dev); + + if (erdy) + writel(EP_CMD_ERDY, &priv_dev->regs->ep_cmd); +} + +/** + * cdns3_ep0_delegate_req - Returns status of handling setup packet + * Setup is handled by gadget driver + * @priv_dev: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns zero on success or negative value on failure + */ +static int cdns3_ep0_delegate_req(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl_req) +{ + int ret; + + spin_unlock(&priv_dev->lock); + priv_dev->setup_pending = 1; + ret = priv_dev->gadget_driver->setup(&priv_dev->gadget, ctrl_req); + priv_dev->setup_pending = 0; + spin_lock(&priv_dev->lock); + return ret; +} + +static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev) +{ + priv_dev->ep0_data_dir = 0; + priv_dev->ep0_stage = CDNS3_SETUP_STAGE; + cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, + sizeof(struct usb_ctrlrequest), 0, 0); +} + +static void cdns3_ep0_complete_setup(struct cdns3_device *priv_dev, + u8 send_stall, u8 send_erdy) +{ + struct cdns3_endpoint *priv_ep = priv_dev->eps[0]; + struct usb_request *request; + + request = cdns3_next_request(&priv_ep->pending_req_list); + if (request) + list_del_init(&request->list); + + if (send_stall) { + trace_cdns3_halt(priv_ep, send_stall, 0); + /* set_stall on ep0 */ + cdns3_select_ep(priv_dev, 0x00); + writel(EP_CMD_SSTALL, &priv_dev->regs->ep_cmd); + } else { + cdns3_prepare_setup_packet(priv_dev); + } + + priv_dev->ep0_stage = CDNS3_SETUP_STAGE; + writel((send_erdy ? EP_CMD_ERDY : 0) | EP_CMD_REQ_CMPL, + &priv_dev->regs->ep_cmd); +} + +/** + * cdns3_req_ep0_set_configuration - Handling of SET_CONFIG standard USB request + * @priv_dev: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, USB_GADGET_DELAYED_STATUS on deferred status stage, + * error code on error + */ +static int cdns3_req_ep0_set_configuration(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl_req) +{ + enum usb_device_state device_state = priv_dev->gadget.state; + u32 config = le16_to_cpu(ctrl_req->wValue); + int result = 0; + + switch (device_state) { + case USB_STATE_ADDRESS: + result = cdns3_ep0_delegate_req(priv_dev, ctrl_req); + + if (result || !config) + goto reset_config; + + break; + case USB_STATE_CONFIGURED: + result = cdns3_ep0_delegate_req(priv_dev, ctrl_req); + if (!config && !result) + goto reset_config; + + break; + default: + return -EINVAL; + } + + return 0; + +reset_config: + if (result != USB_GADGET_DELAYED_STATUS) + cdns3_hw_reset_eps_config(priv_dev); + + usb_gadget_set_state(&priv_dev->gadget, + USB_STATE_ADDRESS); + + return result; +} + +/** + * cdns3_req_ep0_set_address - Handling of SET_ADDRESS standard USB request + * @priv_dev: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns3_req_ep0_set_address(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl_req) +{ + enum usb_device_state device_state = priv_dev->gadget.state; + u32 reg; + u32 addr; + + addr = le16_to_cpu(ctrl_req->wValue); + + if (addr > USB_DEVICE_MAX_ADDRESS) { + dev_err(priv_dev->dev, + "Device address (%d) cannot be greater than %d\n", + addr, USB_DEVICE_MAX_ADDRESS); + return -EINVAL; + } + + if (device_state == USB_STATE_CONFIGURED) { + dev_err(priv_dev->dev, + "can't set_address from configured state\n"); + return -EINVAL; + } + + reg = readl(&priv_dev->regs->usb_cmd); + + writel(reg | USB_CMD_FADDR(addr) | USB_CMD_SET_ADDR, + &priv_dev->regs->usb_cmd); + + usb_gadget_set_state(&priv_dev->gadget, + (addr ? USB_STATE_ADDRESS : USB_STATE_DEFAULT)); + + return 0; +} + +/** + * cdns3_req_ep0_get_status - Handling of GET_STATUS standard USB request + * @priv_dev: extended gadget object + * @ctrl: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns3_req_ep0_get_status(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl) +{ + struct cdns3_endpoint *priv_ep; + __le16 *response_pkt; + u16 usb_status = 0; + u32 recip; + u8 index; + + recip = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recip) { + case USB_RECIP_DEVICE: + /* self powered */ + if (priv_dev->is_selfpowered) + usb_status = BIT(USB_DEVICE_SELF_POWERED); + + if (priv_dev->wake_up_flag) + usb_status |= BIT(USB_DEVICE_REMOTE_WAKEUP); + + if (priv_dev->gadget.speed != USB_SPEED_SUPER) + break; + + if (priv_dev->u1_allowed) + usb_status |= BIT(USB_DEV_STAT_U1_ENABLED); + + if (priv_dev->u2_allowed) + usb_status |= BIT(USB_DEV_STAT_U2_ENABLED); + + break; + case USB_RECIP_INTERFACE: + return cdns3_ep0_delegate_req(priv_dev, ctrl); + case USB_RECIP_ENDPOINT: + index = cdns3_ep_addr_to_index(le16_to_cpu(ctrl->wIndex)); + priv_ep = priv_dev->eps[index]; + + /* check if endpoint is stalled or stall is pending */ + cdns3_select_ep(priv_dev, le16_to_cpu(ctrl->wIndex)); + if (EP_STS_STALL(readl(&priv_dev->regs->ep_sts)) || + (priv_ep->flags & EP_STALL_PENDING)) + usb_status = BIT(USB_ENDPOINT_HALT); + break; + default: + return -EINVAL; + } + + response_pkt = (__le16 *)priv_dev->setup_buf; + *response_pkt = cpu_to_le16(usb_status); + + cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, + sizeof(*response_pkt), 1, 0); + return 0; +} + +static int cdns3_ep0_feature_handle_device(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl, + int set) +{ + enum usb_device_state state; + enum usb_device_speed speed; + int ret = 0; + u32 wValue; + u16 tmode; + + wValue = le16_to_cpu(ctrl->wValue); + state = priv_dev->gadget.state; + speed = priv_dev->gadget.speed; + + switch (wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + priv_dev->wake_up_flag = !!set; + break; + case USB_DEVICE_U1_ENABLE: + if (state != USB_STATE_CONFIGURED || speed != USB_SPEED_SUPER) + return -EINVAL; + + priv_dev->u1_allowed = !!set; + break; + case USB_DEVICE_U2_ENABLE: + if (state != USB_STATE_CONFIGURED || speed != USB_SPEED_SUPER) + return -EINVAL; + + priv_dev->u2_allowed = !!set; + break; + case USB_DEVICE_LTM_ENABLE: + ret = -EINVAL; + break; + case USB_DEVICE_TEST_MODE: + if (state != USB_STATE_CONFIGURED || speed > USB_SPEED_HIGH) + return -EINVAL; + + tmode = le16_to_cpu(ctrl->wIndex); + + if (!set || (tmode & 0xff) != 0) + return -EINVAL; + + tmode >>= 8; + switch (tmode) { + case USB_TEST_J: + case USB_TEST_K: + case USB_TEST_SE0_NAK: + case USB_TEST_PACKET: + cdns3_set_register_bit(&priv_dev->regs->usb_cmd, + USB_CMD_STMODE | + USB_STS_TMODE_SEL(tmode - 1)); + break; + default: + ret = -EINVAL; + } + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int cdns3_ep0_feature_handle_intf(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl, + int set) +{ + u32 wValue; + int ret = 0; + + wValue = le16_to_cpu(ctrl->wValue); + + switch (wValue) { + case USB_INTRF_FUNC_SUSPEND: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int cdns3_ep0_feature_handle_endpoint(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl, + int set) +{ + struct cdns3_endpoint *priv_ep; + int ret = 0; + u8 index; + + if (le16_to_cpu(ctrl->wValue) != USB_ENDPOINT_HALT) + return -EINVAL; + + if (!(le16_to_cpu(ctrl->wIndex) & ~USB_DIR_IN)) + return 0; + + index = cdns3_ep_addr_to_index(le16_to_cpu(ctrl->wIndex)); + priv_ep = priv_dev->eps[index]; + + cdns3_select_ep(priv_dev, le16_to_cpu(ctrl->wIndex)); + + if (set) + __cdns3_gadget_ep_set_halt(priv_ep); + else if (!(priv_ep->flags & EP_WEDGE)) + ret = __cdns3_gadget_ep_clear_halt(priv_ep); + + cdns3_select_ep(priv_dev, 0x00); + + return ret; +} + +/** + * cdns3_req_ep0_handle_feature - + * Handling of GET/SET_FEATURE standard USB request + * + * @priv_dev: extended gadget object + * @ctrl: pointer to received setup packet + * @set: must be set to 1 for SET_FEATURE request + * + * Returns 0 if success, error code on error + */ +static int cdns3_req_ep0_handle_feature(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl, + int set) +{ + int ret = 0; + u32 recip; + + recip = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recip) { + case USB_RECIP_DEVICE: + ret = cdns3_ep0_feature_handle_device(priv_dev, ctrl, set); + break; + case USB_RECIP_INTERFACE: + ret = cdns3_ep0_feature_handle_intf(priv_dev, ctrl, set); + break; + case USB_RECIP_ENDPOINT: + ret = cdns3_ep0_feature_handle_endpoint(priv_dev, ctrl, set); + break; + default: + return -EINVAL; + } + + return ret; +} + +/** + * cdns3_req_ep0_set_sel - Handling of SET_SEL standard USB request + * @priv_dev: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns3_req_ep0_set_sel(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl_req) +{ + if (priv_dev->gadget.state < USB_STATE_ADDRESS) + return -EINVAL; + + if (le16_to_cpu(ctrl_req->wLength) != 6) { + dev_err(priv_dev->dev, "Set SEL should be 6 bytes, got %d\n", + ctrl_req->wLength); + return -EINVAL; + } + + cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, 6, 1, 0); + return 0; +} + +/** + * cdns3_req_ep0_set_isoch_delay - + * Handling of GET_ISOCH_DELAY standard USB request + * @priv_dev: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns3_req_ep0_set_isoch_delay(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl_req) +{ + if (ctrl_req->wIndex || ctrl_req->wLength) + return -EINVAL; + + priv_dev->isoch_delay = le16_to_cpu(ctrl_req->wValue); + + return 0; +} + +/** + * cdns3_ep0_standard_request - Handling standard USB requests + * @priv_dev: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns3_ep0_standard_request(struct cdns3_device *priv_dev, + struct usb_ctrlrequest *ctrl_req) +{ + int ret; + + switch (ctrl_req->bRequest) { + case USB_REQ_SET_ADDRESS: + ret = cdns3_req_ep0_set_address(priv_dev, ctrl_req); + break; + case USB_REQ_SET_CONFIGURATION: + ret = cdns3_req_ep0_set_configuration(priv_dev, ctrl_req); + break; + case USB_REQ_GET_STATUS: + ret = cdns3_req_ep0_get_status(priv_dev, ctrl_req); + break; + case USB_REQ_CLEAR_FEATURE: + ret = cdns3_req_ep0_handle_feature(priv_dev, ctrl_req, 0); + break; + case USB_REQ_SET_FEATURE: + ret = cdns3_req_ep0_handle_feature(priv_dev, ctrl_req, 1); + break; + case USB_REQ_SET_SEL: + ret = cdns3_req_ep0_set_sel(priv_dev, ctrl_req); + break; + case USB_REQ_SET_ISOCH_DELAY: + ret = cdns3_req_ep0_set_isoch_delay(priv_dev, ctrl_req); + break; + default: + ret = cdns3_ep0_delegate_req(priv_dev, ctrl_req); + break; + } + + return ret; +} + +static void __pending_setup_status_handler(struct cdns3_device *priv_dev) +{ + struct usb_request *request = priv_dev->pending_status_request; + + if (priv_dev->status_completion_no_call && request && + request->complete) { + request->complete(&priv_dev->eps[0]->endpoint, request); + priv_dev->status_completion_no_call = 0; + } +} + +void cdns3_pending_setup_status_handler(struct work_struct *work) +{ + struct cdns3_device *priv_dev = container_of(work, struct cdns3_device, + pending_status_wq); + unsigned long flags; + + spin_lock_irqsave(&priv_dev->lock, flags); + __pending_setup_status_handler(priv_dev); + spin_unlock_irqrestore(&priv_dev->lock, flags); +} + +/** + * cdns3_ep0_setup_phase - Handling setup USB requests + * @priv_dev: extended gadget object + */ +static void cdns3_ep0_setup_phase(struct cdns3_device *priv_dev) +{ + struct usb_ctrlrequest *ctrl = priv_dev->setup_buf; + struct cdns3_endpoint *priv_ep = priv_dev->eps[0]; + int result; + + priv_dev->ep0_data_dir = ctrl->bRequestType & USB_DIR_IN; + + trace_cdns3_ctrl_req(ctrl); + + if (!list_empty(&priv_ep->pending_req_list)) { + struct usb_request *request; + + request = cdns3_next_request(&priv_ep->pending_req_list); + priv_ep->dir = priv_dev->ep0_data_dir; + cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), + -ECONNRESET); + } + + if (le16_to_cpu(ctrl->wLength)) + priv_dev->ep0_stage = CDNS3_DATA_STAGE; + else + priv_dev->ep0_stage = CDNS3_STATUS_STAGE; + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + result = cdns3_ep0_standard_request(priv_dev, ctrl); + else + result = cdns3_ep0_delegate_req(priv_dev, ctrl); + + if (result == USB_GADGET_DELAYED_STATUS) + return; + + if (result < 0) + cdns3_ep0_complete_setup(priv_dev, 1, 1); + else if (priv_dev->ep0_stage == CDNS3_STATUS_STAGE) + cdns3_ep0_complete_setup(priv_dev, 0, 1); +} + +static void cdns3_transfer_completed(struct cdns3_device *priv_dev) +{ + struct cdns3_endpoint *priv_ep = priv_dev->eps[0]; + + if (!list_empty(&priv_ep->pending_req_list)) { + struct usb_request *request; + + trace_cdns3_complete_trb(priv_ep, priv_ep->trb_pool); + request = cdns3_next_request(&priv_ep->pending_req_list); + + request->actual = + TRB_LEN(le32_to_cpu(priv_ep->trb_pool->length)); + + priv_ep->dir = priv_dev->ep0_data_dir; + cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), 0); + } + + cdns3_ep0_complete_setup(priv_dev, 0, 0); +} + +/** + * cdns3_check_new_setup - Check if controller receive new SETUP packet. + * @priv_dev: extended gadget object + * + * The SETUP packet can be kept in on-chip memory or in system memory. + */ +static bool cdns3_check_new_setup(struct cdns3_device *priv_dev) +{ + u32 ep_sts_reg; + + cdns3_select_ep(priv_dev, USB_DIR_OUT); + ep_sts_reg = readl(&priv_dev->regs->ep_sts); + + return !!(ep_sts_reg & (EP_STS_SETUP | EP_STS_STPWAIT)); +} + +/** + * cdns3_check_ep0_interrupt_proceed - Processes interrupt related to endpoint 0 + * @priv_dev: extended gadget object + * @dir: USB_DIR_IN for IN direction, USB_DIR_OUT for OUT direction + */ +void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir) +{ + u32 ep_sts_reg; + + cdns3_select_ep(priv_dev, dir); + + ep_sts_reg = readl(&priv_dev->regs->ep_sts); + writel(ep_sts_reg, &priv_dev->regs->ep_sts); + + trace_cdns3_ep0_irq(priv_dev, ep_sts_reg); + + __pending_setup_status_handler(priv_dev); + + if (ep_sts_reg & EP_STS_SETUP) + priv_dev->wait_for_setup = 1; + + if (priv_dev->wait_for_setup && ep_sts_reg & EP_STS_IOC) { + priv_dev->wait_for_setup = 0; + cdns3_ep0_setup_phase(priv_dev); + } else if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP)) { + priv_dev->ep0_data_dir = dir; + cdns3_transfer_completed(priv_dev); + } + + if (ep_sts_reg & EP_STS_DESCMIS) { + if (dir == 0 && !priv_dev->setup_pending) + cdns3_prepare_setup_packet(priv_dev); + } +} + +/** + * cdns3_gadget_ep0_enable + * @ep: pointer to endpoint zero object + * @desc: pointer to usb endpoint descriptor + * + * Function shouldn't be called by gadget driver, + * endpoint 0 is allways active + */ +static int cdns3_gadget_ep0_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + return -EINVAL; +} + +/** + * cdns3_gadget_ep0_disable + * @ep: pointer to endpoint zero object + * + * Function shouldn't be called by gadget driver, + * endpoint 0 is allways active + */ +static int cdns3_gadget_ep0_disable(struct usb_ep *ep) +{ + return -EINVAL; +} + +/** + * cdns3_gadget_ep0_set_halt + * @ep: pointer to endpoint zero object + * @value: 1 for set stall, 0 for clear stall + * + * Returns 0 + */ +static int cdns3_gadget_ep0_set_halt(struct usb_ep *ep, int value) +{ + /* TODO */ + return 0; +} + +/** + * cdns3_gadget_ep0_queue - Transfer data on endpoint zero + * @ep: pointer to endpoint zero object + * @request: pointer to request object + * @gfp_flags: gfp flags + * + * Returns 0 on success, error code elsewhere + */ +static int cdns3_gadget_ep0_queue(struct usb_ep *ep, + struct usb_request *request, + gfp_t gfp_flags) +{ + struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + unsigned long flags; + int ret = 0; + u8 zlp = 0; + int i; + + spin_lock_irqsave(&priv_dev->lock, flags); + trace_cdns3_ep0_queue(priv_dev, request); + + /* cancel the request if controller receive new SETUP packet. */ + if (cdns3_check_new_setup(priv_dev)) { + spin_unlock_irqrestore(&priv_dev->lock, flags); + return -ECONNRESET; + } + + /* send STATUS stage. Should be called only for SET_CONFIGURATION */ + if (priv_dev->ep0_stage == CDNS3_STATUS_STAGE) { + u32 val; + + cdns3_select_ep(priv_dev, 0x00); + + /* + * Configure all non-control EPs which are not enabled by class driver + */ + for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) { + priv_ep = priv_dev->eps[i]; + if (priv_ep && priv_ep->flags & EP_CLAIMED && + !(priv_ep->flags & EP_ENABLED)) + cdns3_ep_config(priv_ep, 0); + } + + cdns3_set_hw_configuration(priv_dev); + cdns3_ep0_complete_setup(priv_dev, 0, 1); + /* wait until configuration set */ + ret = readl_poll_timeout_atomic(&priv_dev->regs->usb_sts, val, + val & USB_STS_CFGSTS_MASK, 1, 100); + if (ret == -ETIMEDOUT) + dev_warn(priv_dev->dev, "timeout for waiting configuration set\n"); + + request->actual = 0; + priv_dev->status_completion_no_call = true; + priv_dev->pending_status_request = request; + usb_gadget_set_state(&priv_dev->gadget, USB_STATE_CONFIGURED); + spin_unlock_irqrestore(&priv_dev->lock, flags); + + /* + * Since there is no completion interrupt for status stage, + * it needs to call ->completion in software after + * ep0_queue is back. + */ + queue_work(system_freezable_wq, &priv_dev->pending_status_wq); + return ret; + } + + if (!list_empty(&priv_ep->pending_req_list)) { + dev_err(priv_dev->dev, + "can't handle multiple requests for ep0\n"); + spin_unlock_irqrestore(&priv_dev->lock, flags); + return -EBUSY; + } + + ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request, + priv_dev->ep0_data_dir); + if (ret) { + spin_unlock_irqrestore(&priv_dev->lock, flags); + dev_err(priv_dev->dev, "failed to map request\n"); + return -EINVAL; + } + + request->status = -EINPROGRESS; + list_add_tail(&request->list, &priv_ep->pending_req_list); + + if (request->zero && request->length && + (request->length % ep->maxpacket == 0)) + zlp = 1; + + cdns3_ep0_run_transfer(priv_dev, request->dma, request->length, 1, zlp); + + spin_unlock_irqrestore(&priv_dev->lock, flags); + + return ret; +} + +/** + * cdns3_gadget_ep_set_wedge - Set wedge on selected endpoint + * @ep: endpoint object + * + * Returns 0 + */ +int cdns3_gadget_ep_set_wedge(struct usb_ep *ep) +{ + struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + + dev_dbg(priv_dev->dev, "Wedge for %s\n", ep->name); + cdns3_gadget_ep_set_halt(ep, 1); + priv_ep->flags |= EP_WEDGE; + + return 0; +} + +static const struct usb_ep_ops cdns3_gadget_ep0_ops = { + .enable = cdns3_gadget_ep0_enable, + .disable = cdns3_gadget_ep0_disable, + .alloc_request = cdns3_gadget_ep_alloc_request, + .free_request = cdns3_gadget_ep_free_request, + .queue = cdns3_gadget_ep0_queue, + .dequeue = cdns3_gadget_ep_dequeue, + .set_halt = cdns3_gadget_ep0_set_halt, + .set_wedge = cdns3_gadget_ep_set_wedge, +}; + +/** + * cdns3_ep0_config - Configures default endpoint + * @priv_dev: extended gadget object + * + * Functions sets parameters: maximal packet size and enables interrupts + */ +void cdns3_ep0_config(struct cdns3_device *priv_dev) +{ + struct cdns3_usb_regs __iomem *regs; + struct cdns3_endpoint *priv_ep; + u32 max_packet_size = 64; + u32 ep_cfg; + + regs = priv_dev->regs; + + if (priv_dev->gadget.speed == USB_SPEED_SUPER) + max_packet_size = 512; + + priv_ep = priv_dev->eps[0]; + + if (!list_empty(&priv_ep->pending_req_list)) { + struct usb_request *request; + + request = cdns3_next_request(&priv_ep->pending_req_list); + list_del_init(&request->list); + } + + priv_dev->u1_allowed = 0; + priv_dev->u2_allowed = 0; + + priv_dev->gadget.ep0->maxpacket = max_packet_size; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(max_packet_size); + + /* init ep out */ + cdns3_select_ep(priv_dev, USB_DIR_OUT); + + if (priv_dev->dev_ver >= DEV_VER_V3) { + cdns3_set_register_bit(&priv_dev->regs->dtrans, + BIT(0) | BIT(16)); + cdns3_set_register_bit(&priv_dev->regs->tdl_from_trb, + BIT(0) | BIT(16)); + } + + ep_cfg = EP_CFG_ENABLE | EP_CFG_MAXPKTSIZE(max_packet_size); + + if (!(priv_ep->flags & EP_CONFIGURED)) + writel(ep_cfg, ®s->ep_cfg); + + writel(EP_STS_EN_SETUPEN | EP_STS_EN_DESCMISEN | EP_STS_EN_TRBERREN, + ®s->ep_sts_en); + + /* init ep in */ + cdns3_select_ep(priv_dev, USB_DIR_IN); + + if (!(priv_ep->flags & EP_CONFIGURED)) + writel(ep_cfg, ®s->ep_cfg); + + priv_ep->flags |= EP_CONFIGURED; + + writel(EP_STS_EN_SETUPEN | EP_STS_EN_TRBERREN, ®s->ep_sts_en); + + cdns3_set_register_bit(®s->usb_conf, USB_CONF_U1DS | USB_CONF_U2DS); +} + +/** + * cdns3_init_ep0 - Initializes software endpoint 0 of gadget + * @priv_dev: extended gadget object + * @priv_ep: extended endpoint object + * + * Returns 0 on success else error code. + */ +int cdns3_init_ep0(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep) +{ + sprintf(priv_ep->name, "ep0"); + + /* fill linux fields */ + priv_ep->endpoint.ops = &cdns3_gadget_ep0_ops; + priv_ep->endpoint.maxburst = 1; + usb_ep_set_maxpacket_limit(&priv_ep->endpoint, + CDNS3_EP0_MAX_PACKET_LIMIT); + priv_ep->endpoint.address = 0; + priv_ep->endpoint.caps.type_control = 1; + priv_ep->endpoint.caps.dir_in = 1; + priv_ep->endpoint.caps.dir_out = 1; + priv_ep->endpoint.name = priv_ep->name; + priv_ep->endpoint.desc = &cdns3_gadget_ep0_desc; + priv_dev->gadget.ep0 = &priv_ep->endpoint; + priv_ep->type = USB_ENDPOINT_XFER_CONTROL; + + return cdns3_allocate_trb_pool(priv_ep); +} diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c new file mode 100644 index 000000000..42311c1bf --- /dev/null +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -0,0 +1,3525 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBSS DRD Driver - gadget side. + * + * Copyright (C) 2018-2019 Cadence Design Systems. + * Copyright (C) 2017-2018 NXP + * + * Authors: Pawel Jez , + * Pawel Laszczak + * Peter Chen + */ + +/* + * Work around 1: + * At some situations, the controller may get stale data address in TRB + * at below sequences: + * 1. Controller read TRB includes data address + * 2. Software updates TRBs includes data address and Cycle bit + * 3. Controller read TRB which includes Cycle bit + * 4. DMA run with stale data address + * + * To fix this problem, driver needs to make the first TRB in TD as invalid. + * After preparing all TRBs driver needs to check the position of DMA and + * if the DMA point to the first just added TRB and doorbell is 1, + * then driver must defer making this TRB as valid. This TRB will be make + * as valid during adding next TRB only if DMA is stopped or at TRBERR + * interrupt. + * + * Issue has been fixed in DEV_VER_V3 version of controller. + * + * Work around 2: + * Controller for OUT endpoints has shared on-chip buffers for all incoming + * packets, including ep0out. It's FIFO buffer, so packets must be handle by DMA + * in correct order. If the first packet in the buffer will not be handled, + * then the following packets directed for other endpoints and functions + * will be blocked. + * Additionally the packets directed to one endpoint can block entire on-chip + * buffers. In this case transfer to other endpoints also will blocked. + * + * To resolve this issue after raising the descriptor missing interrupt + * driver prepares internal usb_request object and use it to arm DMA transfer. + * + * The problematic situation was observed in case when endpoint has been enabled + * but no usb_request were queued. Driver try detects such endpoints and will + * use this workaround only for these endpoint. + * + * Driver use limited number of buffer. This number can be set by macro + * CDNS3_WA2_NUM_BUFFERS. + * + * Such blocking situation was observed on ACM gadget. For this function + * host send OUT data packet but ACM function is not prepared for this packet. + * It's cause that buffer placed in on chip memory block transfer to other + * endpoints. + * + * Issue has been fixed in DEV_VER_V2 version of controller. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "gadget-export.h" +#include "cdns3-gadget.h" +#include "cdns3-trace.h" +#include "drd.h" + +static int __cdns3_gadget_ep_queue(struct usb_ep *ep, + struct usb_request *request, + gfp_t gfp_flags); + +static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, + struct usb_request *request); + +static int cdns3_ep_run_stream_transfer(struct cdns3_endpoint *priv_ep, + struct usb_request *request); + +/** + * cdns3_clear_register_bit - clear bit in given register. + * @ptr: address of device controller register to be read and changed + * @mask: bits requested to clar + */ +static void cdns3_clear_register_bit(void __iomem *ptr, u32 mask) +{ + mask = readl(ptr) & ~mask; + writel(mask, ptr); +} + +/** + * cdns3_set_register_bit - set bit in given register. + * @ptr: address of device controller register to be read and changed + * @mask: bits requested to set + */ +void cdns3_set_register_bit(void __iomem *ptr, u32 mask) +{ + mask = readl(ptr) | mask; + writel(mask, ptr); +} + +/** + * cdns3_ep_addr_to_index - Macro converts endpoint address to + * index of endpoint object in cdns3_device.eps[] container + * @ep_addr: endpoint address for which endpoint object is required + * + */ +u8 cdns3_ep_addr_to_index(u8 ep_addr) +{ + return (((ep_addr & 0x7F)) + ((ep_addr & USB_DIR_IN) ? 16 : 0)); +} + +static int cdns3_get_dma_pos(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep) +{ + int dma_index; + + dma_index = readl(&priv_dev->regs->ep_traddr) - priv_ep->trb_pool_dma; + + return dma_index / TRB_SIZE; +} + +/** + * cdns3_next_request - returns next request from list + * @list: list containing requests + * + * Returns request or NULL if no requests in list + */ +struct usb_request *cdns3_next_request(struct list_head *list) +{ + return list_first_entry_or_null(list, struct usb_request, list); +} + +/** + * cdns3_next_align_buf - returns next buffer from list + * @list: list containing buffers + * + * Returns buffer or NULL if no buffers in list + */ +static struct cdns3_aligned_buf *cdns3_next_align_buf(struct list_head *list) +{ + return list_first_entry_or_null(list, struct cdns3_aligned_buf, list); +} + +/** + * cdns3_next_priv_request - returns next request from list + * @list: list containing requests + * + * Returns request or NULL if no requests in list + */ +static struct cdns3_request *cdns3_next_priv_request(struct list_head *list) +{ + return list_first_entry_or_null(list, struct cdns3_request, list); +} + +/** + * cdns3_select_ep - selects endpoint + * @priv_dev: extended gadget object + * @ep: endpoint address + */ +void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep) +{ + if (priv_dev->selected_ep == ep) + return; + + priv_dev->selected_ep = ep; + writel(ep, &priv_dev->regs->ep_sel); +} + +/** + * cdns3_get_tdl - gets current tdl for selected endpoint. + * @priv_dev: extended gadget object + * + * Before calling this function the appropriate endpoint must + * be selected by means of cdns3_select_ep function. + */ +static int cdns3_get_tdl(struct cdns3_device *priv_dev) +{ + if (priv_dev->dev_ver < DEV_VER_V3) + return EP_CMD_TDL_GET(readl(&priv_dev->regs->ep_cmd)); + else + return readl(&priv_dev->regs->ep_tdl); +} + +dma_addr_t cdns3_trb_virt_to_dma(struct cdns3_endpoint *priv_ep, + struct cdns3_trb *trb) +{ + u32 offset = (char *)trb - (char *)priv_ep->trb_pool; + + return priv_ep->trb_pool_dma + offset; +} + +static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + + if (priv_ep->trb_pool) { + dma_pool_free(priv_dev->eps_dma_pool, + priv_ep->trb_pool, priv_ep->trb_pool_dma); + priv_ep->trb_pool = NULL; + } +} + +/** + * cdns3_allocate_trb_pool - Allocates TRB's pool for selected endpoint + * @priv_ep: endpoint object + * + * Function will return 0 on success or -ENOMEM on allocation error + */ +int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + int ring_size = TRB_RING_SIZE; + int num_trbs = ring_size / TRB_SIZE; + struct cdns3_trb *link_trb; + + if (priv_ep->trb_pool && priv_ep->alloc_ring_size < ring_size) + cdns3_free_trb_pool(priv_ep); + + if (!priv_ep->trb_pool) { + priv_ep->trb_pool = dma_pool_alloc(priv_dev->eps_dma_pool, + GFP_ATOMIC, + &priv_ep->trb_pool_dma); + + if (!priv_ep->trb_pool) + return -ENOMEM; + + priv_ep->alloc_ring_size = ring_size; + } + + memset(priv_ep->trb_pool, 0, ring_size); + + priv_ep->num_trbs = num_trbs; + + if (!priv_ep->num) + return 0; + + /* Initialize the last TRB as Link TRB */ + link_trb = (priv_ep->trb_pool + (priv_ep->num_trbs - 1)); + + if (priv_ep->use_streams) { + /* + * For stream capable endpoints driver use single correct TRB. + * The last trb has zeroed cycle bit + */ + link_trb->control = 0; + } else { + link_trb->buffer = cpu_to_le32(TRB_BUFFER(priv_ep->trb_pool_dma)); + link_trb->control = cpu_to_le32(TRB_CYCLE | TRB_TYPE(TRB_LINK) | TRB_TOGGLE); + } + return 0; +} + +/** + * cdns3_ep_stall_flush - Stalls and flushes selected endpoint + * @priv_ep: endpoint object + * + * Endpoint must be selected before call to this function + */ +static void cdns3_ep_stall_flush(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + int val; + + trace_cdns3_halt(priv_ep, 1, 1); + + writel(EP_CMD_DFLUSH | EP_CMD_ERDY | EP_CMD_SSTALL, + &priv_dev->regs->ep_cmd); + + /* wait for DFLUSH cleared */ + readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, + !(val & EP_CMD_DFLUSH), 1, 1000); + priv_ep->flags |= EP_STALLED; + priv_ep->flags &= ~EP_STALL_PENDING; +} + +/** + * cdns3_hw_reset_eps_config - reset endpoints configuration kept by controller. + * @priv_dev: extended gadget object + */ +void cdns3_hw_reset_eps_config(struct cdns3_device *priv_dev) +{ + int i; + + writel(USB_CONF_CFGRST, &priv_dev->regs->usb_conf); + + cdns3_allow_enable_l1(priv_dev, 0); + priv_dev->hw_configured_flag = 0; + priv_dev->onchip_used_size = 0; + priv_dev->out_mem_is_allocated = 0; + priv_dev->wait_for_setup = 0; + priv_dev->using_streams = 0; + + for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) + if (priv_dev->eps[i]) + priv_dev->eps[i]->flags &= ~EP_CONFIGURED; +} + +/** + * cdns3_ep_inc_trb - increment a trb index. + * @index: Pointer to the TRB index to increment. + * @cs: Cycle state + * @trb_in_seg: number of TRBs in segment + * + * The index should never point to the link TRB. After incrementing, + * if it is point to the link TRB, wrap around to the beginning and revert + * cycle state bit The + * link TRB is always at the last TRB entry. + */ +static void cdns3_ep_inc_trb(int *index, u8 *cs, int trb_in_seg) +{ + (*index)++; + if (*index == (trb_in_seg - 1)) { + *index = 0; + *cs ^= 1; + } +} + +/** + * cdns3_ep_inc_enq - increment endpoint's enqueue pointer + * @priv_ep: The endpoint whose enqueue pointer we're incrementing + */ +static void cdns3_ep_inc_enq(struct cdns3_endpoint *priv_ep) +{ + priv_ep->free_trbs--; + cdns3_ep_inc_trb(&priv_ep->enqueue, &priv_ep->pcs, priv_ep->num_trbs); +} + +/** + * cdns3_ep_inc_deq - increment endpoint's dequeue pointer + * @priv_ep: The endpoint whose dequeue pointer we're incrementing + */ +static void cdns3_ep_inc_deq(struct cdns3_endpoint *priv_ep) +{ + priv_ep->free_trbs++; + cdns3_ep_inc_trb(&priv_ep->dequeue, &priv_ep->ccs, priv_ep->num_trbs); +} + +/** + * cdns3_allow_enable_l1 - enable/disable permits to transition to L1. + * @priv_dev: Extended gadget object + * @enable: Enable/disable permit to transition to L1. + * + * If bit USB_CONF_L1EN is set and device receive Extended Token packet, + * then controller answer with ACK handshake. + * If bit USB_CONF_L1DS is set and device receive Extended Token packet, + * then controller answer with NYET handshake. + */ +void cdns3_allow_enable_l1(struct cdns3_device *priv_dev, int enable) +{ + if (enable) + writel(USB_CONF_L1EN, &priv_dev->regs->usb_conf); + else + writel(USB_CONF_L1DS, &priv_dev->regs->usb_conf); +} + +enum usb_device_speed cdns3_get_speed(struct cdns3_device *priv_dev) +{ + u32 reg; + + reg = readl(&priv_dev->regs->usb_sts); + + if (DEV_SUPERSPEED(reg)) + return USB_SPEED_SUPER; + else if (DEV_HIGHSPEED(reg)) + return USB_SPEED_HIGH; + else if (DEV_FULLSPEED(reg)) + return USB_SPEED_FULL; + else if (DEV_LOWSPEED(reg)) + return USB_SPEED_LOW; + return USB_SPEED_UNKNOWN; +} + +/** + * cdns3_start_all_request - add to ring all request not started + * @priv_dev: Extended gadget object + * @priv_ep: The endpoint for whom request will be started. + * + * Returns return ENOMEM if transfer ring i not enough TRBs to start + * all requests. + */ +static int cdns3_start_all_request(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep) +{ + struct usb_request *request; + int ret = 0; + u8 pending_empty = list_empty(&priv_ep->pending_req_list); + + /* + * If the last pending transfer is INTERNAL + * OR streams are enabled for this endpoint + * do NOT start new transfer till the last one is pending + */ + if (!pending_empty) { + struct cdns3_request *priv_req; + + request = cdns3_next_request(&priv_ep->pending_req_list); + priv_req = to_cdns3_request(request); + if ((priv_req->flags & REQUEST_INTERNAL) || + (priv_ep->flags & EP_TDLCHK_EN) || + priv_ep->use_streams) { + dev_dbg(priv_dev->dev, "Blocking external request\n"); + return ret; + } + } + + while (!list_empty(&priv_ep->deferred_req_list)) { + request = cdns3_next_request(&priv_ep->deferred_req_list); + + if (!priv_ep->use_streams) { + ret = cdns3_ep_run_transfer(priv_ep, request); + } else { + priv_ep->stream_sg_idx = 0; + ret = cdns3_ep_run_stream_transfer(priv_ep, request); + } + if (ret) + return ret; + + list_move_tail(&request->list, &priv_ep->pending_req_list); + if (request->stream_id != 0 || (priv_ep->flags & EP_TDLCHK_EN)) + break; + } + + priv_ep->flags &= ~EP_RING_FULL; + return ret; +} + +/* + * WA2: Set flag for all not ISOC OUT endpoints. If this flag is set + * driver try to detect whether endpoint need additional internal + * buffer for unblocking on-chip FIFO buffer. This flag will be cleared + * if before first DESCMISS interrupt the DMA will be armed. + */ +#define cdns3_wa2_enable_detection(priv_dev, priv_ep, reg) do { \ + if (!priv_ep->dir && priv_ep->type != USB_ENDPOINT_XFER_ISOC) { \ + priv_ep->flags |= EP_QUIRK_EXTRA_BUF_DET; \ + (reg) |= EP_STS_EN_DESCMISEN; \ + } } while (0) + +static void __cdns3_descmiss_copy_data(struct usb_request *request, + struct usb_request *descmiss_req) +{ + int length = request->actual + descmiss_req->actual; + struct scatterlist *s = request->sg; + + if (!s) { + if (length <= request->length) { + memcpy(&((u8 *)request->buf)[request->actual], + descmiss_req->buf, + descmiss_req->actual); + request->actual = length; + } else { + /* It should never occures */ + request->status = -ENOMEM; + } + } else { + if (length <= sg_dma_len(s)) { + void *p = phys_to_virt(sg_dma_address(s)); + + memcpy(&((u8 *)p)[request->actual], + descmiss_req->buf, + descmiss_req->actual); + request->actual = length; + } else { + request->status = -ENOMEM; + } + } +} + +/** + * cdns3_wa2_descmiss_copy_data - copy data from internal requests to + * request queued by class driver. + * @priv_ep: extended endpoint object + * @request: request object + */ +static void cdns3_wa2_descmiss_copy_data(struct cdns3_endpoint *priv_ep, + struct usb_request *request) +{ + struct usb_request *descmiss_req; + struct cdns3_request *descmiss_priv_req; + + while (!list_empty(&priv_ep->wa2_descmiss_req_list)) { + int chunk_end; + + descmiss_priv_req = + cdns3_next_priv_request(&priv_ep->wa2_descmiss_req_list); + descmiss_req = &descmiss_priv_req->request; + + /* driver can't touch pending request */ + if (descmiss_priv_req->flags & REQUEST_PENDING) + break; + + chunk_end = descmiss_priv_req->flags & REQUEST_INTERNAL_CH; + request->status = descmiss_req->status; + __cdns3_descmiss_copy_data(request, descmiss_req); + list_del_init(&descmiss_priv_req->list); + kfree(descmiss_req->buf); + cdns3_gadget_ep_free_request(&priv_ep->endpoint, descmiss_req); + --priv_ep->wa2_counter; + + if (!chunk_end) + break; + } +} + +static struct usb_request *cdns3_wa2_gadget_giveback(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep, + struct cdns3_request *priv_req) +{ + if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN && + priv_req->flags & REQUEST_INTERNAL) { + struct usb_request *req; + + req = cdns3_next_request(&priv_ep->deferred_req_list); + + priv_ep->descmis_req = NULL; + + if (!req) + return NULL; + + /* unmap the gadget request before copying data */ + usb_gadget_unmap_request_by_dev(priv_dev->sysdev, req, + priv_ep->dir); + + cdns3_wa2_descmiss_copy_data(priv_ep, req); + if (!(priv_ep->flags & EP_QUIRK_END_TRANSFER) && + req->length != req->actual) { + /* wait for next part of transfer */ + /* re-map the gadget request buffer*/ + usb_gadget_map_request_by_dev(priv_dev->sysdev, req, + usb_endpoint_dir_in(priv_ep->endpoint.desc)); + return NULL; + } + + if (req->status == -EINPROGRESS) + req->status = 0; + + list_del_init(&req->list); + cdns3_start_all_request(priv_dev, priv_ep); + return req; + } + + return &priv_req->request; +} + +static int cdns3_wa2_gadget_ep_queue(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep, + struct cdns3_request *priv_req) +{ + int deferred = 0; + + /* + * If transfer was queued before DESCMISS appear than we + * can disable handling of DESCMISS interrupt. Driver assumes that it + * can disable special treatment for this endpoint. + */ + if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_DET) { + u32 reg; + + cdns3_select_ep(priv_dev, priv_ep->num | priv_ep->dir); + priv_ep->flags &= ~EP_QUIRK_EXTRA_BUF_DET; + reg = readl(&priv_dev->regs->ep_sts_en); + reg &= ~EP_STS_EN_DESCMISEN; + trace_cdns3_wa2(priv_ep, "workaround disabled\n"); + writel(reg, &priv_dev->regs->ep_sts_en); + } + + if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN) { + u8 pending_empty = list_empty(&priv_ep->pending_req_list); + u8 descmiss_empty = list_empty(&priv_ep->wa2_descmiss_req_list); + + /* + * DESCMISS transfer has been finished, so data will be + * directly copied from internal allocated usb_request + * objects. + */ + if (pending_empty && !descmiss_empty && + !(priv_req->flags & REQUEST_INTERNAL)) { + cdns3_wa2_descmiss_copy_data(priv_ep, + &priv_req->request); + + trace_cdns3_wa2(priv_ep, "get internal stored data"); + + list_add_tail(&priv_req->request.list, + &priv_ep->pending_req_list); + cdns3_gadget_giveback(priv_ep, priv_req, + priv_req->request.status); + + /* + * Intentionally driver returns positive value as + * correct value. It informs that transfer has + * been finished. + */ + return EINPROGRESS; + } + + /* + * Driver will wait for completion DESCMISS transfer, + * before starts new, not DESCMISS transfer. + */ + if (!pending_empty && !descmiss_empty) { + trace_cdns3_wa2(priv_ep, "wait for pending transfer\n"); + deferred = 1; + } + + if (priv_req->flags & REQUEST_INTERNAL) + list_add_tail(&priv_req->list, + &priv_ep->wa2_descmiss_req_list); + } + + return deferred; +} + +static void cdns3_wa2_remove_old_request(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_request *priv_req; + + while (!list_empty(&priv_ep->wa2_descmiss_req_list)) { + u8 chain; + + priv_req = cdns3_next_priv_request(&priv_ep->wa2_descmiss_req_list); + chain = !!(priv_req->flags & REQUEST_INTERNAL_CH); + + trace_cdns3_wa2(priv_ep, "removes eldest request"); + + kfree(priv_req->request.buf); + list_del_init(&priv_req->list); + cdns3_gadget_ep_free_request(&priv_ep->endpoint, + &priv_req->request); + --priv_ep->wa2_counter; + + if (!chain) + break; + } +} + +/** + * cdns3_wa2_descmissing_packet - handles descriptor missing event. + * @priv_ep: extended gadget object + * + * This function is used only for WA2. For more information see Work around 2 + * description. + */ +static void cdns3_wa2_descmissing_packet(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_request *priv_req; + struct usb_request *request; + u8 pending_empty = list_empty(&priv_ep->pending_req_list); + + /* check for pending transfer */ + if (!pending_empty) { + trace_cdns3_wa2(priv_ep, "Ignoring Descriptor missing IRQ\n"); + return; + } + + if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_DET) { + priv_ep->flags &= ~EP_QUIRK_EXTRA_BUF_DET; + priv_ep->flags |= EP_QUIRK_EXTRA_BUF_EN; + } + + trace_cdns3_wa2(priv_ep, "Description Missing detected\n"); + + if (priv_ep->wa2_counter >= CDNS3_WA2_NUM_BUFFERS) { + trace_cdns3_wa2(priv_ep, "WA2 overflow\n"); + cdns3_wa2_remove_old_request(priv_ep); + } + + request = cdns3_gadget_ep_alloc_request(&priv_ep->endpoint, + GFP_ATOMIC); + if (!request) + goto err; + + priv_req = to_cdns3_request(request); + priv_req->flags |= REQUEST_INTERNAL; + + /* if this field is still assigned it indicate that transfer related + * with this request has not been finished yet. Driver in this + * case simply allocate next request and assign flag REQUEST_INTERNAL_CH + * flag to previous one. It will indicate that current request is + * part of the previous one. + */ + if (priv_ep->descmis_req) + priv_ep->descmis_req->flags |= REQUEST_INTERNAL_CH; + + priv_req->request.buf = kzalloc(CDNS3_DESCMIS_BUF_SIZE, + GFP_ATOMIC); + priv_ep->wa2_counter++; + + if (!priv_req->request.buf) { + cdns3_gadget_ep_free_request(&priv_ep->endpoint, request); + goto err; + } + + priv_req->request.length = CDNS3_DESCMIS_BUF_SIZE; + priv_ep->descmis_req = priv_req; + + __cdns3_gadget_ep_queue(&priv_ep->endpoint, + &priv_ep->descmis_req->request, + GFP_ATOMIC); + + return; + +err: + dev_err(priv_ep->cdns3_dev->dev, + "Failed: No sufficient memory for DESCMIS\n"); +} + +static void cdns3_wa2_reset_tdl(struct cdns3_device *priv_dev) +{ + u16 tdl = EP_CMD_TDL_GET(readl(&priv_dev->regs->ep_cmd)); + + if (tdl) { + u16 reset_val = EP_CMD_TDL_MAX + 1 - tdl; + + writel(EP_CMD_TDL_SET(reset_val) | EP_CMD_STDL, + &priv_dev->regs->ep_cmd); + } +} + +static void cdns3_wa2_check_outq_status(struct cdns3_device *priv_dev) +{ + u32 ep_sts_reg; + + /* select EP0-out */ + cdns3_select_ep(priv_dev, 0); + + ep_sts_reg = readl(&priv_dev->regs->ep_sts); + + if (EP_STS_OUTQ_VAL(ep_sts_reg)) { + u32 outq_ep_num = EP_STS_OUTQ_NO(ep_sts_reg); + struct cdns3_endpoint *outq_ep = priv_dev->eps[outq_ep_num]; + + if ((outq_ep->flags & EP_ENABLED) && !(outq_ep->use_streams) && + outq_ep->type != USB_ENDPOINT_XFER_ISOC && outq_ep_num) { + u8 pending_empty = list_empty(&outq_ep->pending_req_list); + + if ((outq_ep->flags & EP_QUIRK_EXTRA_BUF_DET) || + (outq_ep->flags & EP_QUIRK_EXTRA_BUF_EN) || + !pending_empty) { + } else { + u32 ep_sts_en_reg; + u32 ep_cmd_reg; + + cdns3_select_ep(priv_dev, outq_ep->num | + outq_ep->dir); + ep_sts_en_reg = readl(&priv_dev->regs->ep_sts_en); + ep_cmd_reg = readl(&priv_dev->regs->ep_cmd); + + outq_ep->flags |= EP_TDLCHK_EN; + cdns3_set_register_bit(&priv_dev->regs->ep_cfg, + EP_CFG_TDL_CHK); + + cdns3_wa2_enable_detection(priv_dev, outq_ep, + ep_sts_en_reg); + writel(ep_sts_en_reg, + &priv_dev->regs->ep_sts_en); + /* reset tdl value to zero */ + cdns3_wa2_reset_tdl(priv_dev); + /* + * Memory barrier - Reset tdl before ringing the + * doorbell. + */ + wmb(); + if (EP_CMD_DRDY & ep_cmd_reg) { + trace_cdns3_wa2(outq_ep, "Enabling WA2 skipping doorbell\n"); + + } else { + trace_cdns3_wa2(outq_ep, "Enabling WA2 ringing doorbell\n"); + /* + * ring doorbell to generate DESCMIS irq + */ + writel(EP_CMD_DRDY, + &priv_dev->regs->ep_cmd); + } + } + } + } +} + +/** + * cdns3_gadget_giveback - call struct usb_request's ->complete callback + * @priv_ep: The endpoint to whom the request belongs to + * @priv_req: The request we're giving back + * @status: completion code for the request + * + * Must be called with controller's lock held and interrupts disabled. This + * function will unmap @req and call its ->complete() callback to notify upper + * layers that it has completed. + */ +void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep, + struct cdns3_request *priv_req, + int status) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct usb_request *request = &priv_req->request; + + list_del_init(&request->list); + + if (request->status == -EINPROGRESS) + request->status = status; + + if (likely(!(priv_req->flags & REQUEST_UNALIGNED))) + usb_gadget_unmap_request_by_dev(priv_dev->sysdev, request, + priv_ep->dir); + + if ((priv_req->flags & REQUEST_UNALIGNED) && + priv_ep->dir == USB_DIR_OUT && !request->status) { + /* Make DMA buffer CPU accessible */ + dma_sync_single_for_cpu(priv_dev->sysdev, + priv_req->aligned_buf->dma, + request->actual, + priv_req->aligned_buf->dir); + memcpy(request->buf, priv_req->aligned_buf->buf, + request->actual); + } + + priv_req->flags &= ~(REQUEST_PENDING | REQUEST_UNALIGNED); + /* All TRBs have finished, clear the counter */ + priv_req->finished_trb = 0; + trace_cdns3_gadget_giveback(priv_req); + + if (priv_dev->dev_ver < DEV_VER_V2) { + request = cdns3_wa2_gadget_giveback(priv_dev, priv_ep, + priv_req); + if (!request) + return; + } + + /* + * zlp request is appended by driver, needn't call usb_gadget_giveback_request() to notify + * gadget composite driver. + */ + if (request->complete && request->buf != priv_dev->zlp_buf) { + spin_unlock(&priv_dev->lock); + usb_gadget_giveback_request(&priv_ep->endpoint, + request); + spin_lock(&priv_dev->lock); + } + + if (request->buf == priv_dev->zlp_buf) + cdns3_gadget_ep_free_request(&priv_ep->endpoint, request); +} + +static void cdns3_wa1_restore_cycle_bit(struct cdns3_endpoint *priv_ep) +{ + /* Work around for stale data address in TRB*/ + if (priv_ep->wa1_set) { + trace_cdns3_wa1(priv_ep, "restore cycle bit"); + + priv_ep->wa1_set = 0; + priv_ep->wa1_trb_index = 0xFFFF; + if (priv_ep->wa1_cycle_bit) { + priv_ep->wa1_trb->control = + priv_ep->wa1_trb->control | cpu_to_le32(0x1); + } else { + priv_ep->wa1_trb->control = + priv_ep->wa1_trb->control & cpu_to_le32(~0x1); + } + } +} + +static void cdns3_free_aligned_request_buf(struct work_struct *work) +{ + struct cdns3_device *priv_dev = container_of(work, struct cdns3_device, + aligned_buf_wq); + struct cdns3_aligned_buf *buf, *tmp; + unsigned long flags; + + spin_lock_irqsave(&priv_dev->lock, flags); + + list_for_each_entry_safe(buf, tmp, &priv_dev->aligned_buf_list, list) { + if (!buf->in_use) { + list_del(&buf->list); + + /* + * Re-enable interrupts to free DMA capable memory. + * Driver can't free this memory with disabled + * interrupts. + */ + spin_unlock_irqrestore(&priv_dev->lock, flags); + dma_free_noncoherent(priv_dev->sysdev, buf->size, + buf->buf, buf->dma, buf->dir); + kfree(buf); + spin_lock_irqsave(&priv_dev->lock, flags); + } + } + + spin_unlock_irqrestore(&priv_dev->lock, flags); +} + +static int cdns3_prepare_aligned_request_buf(struct cdns3_request *priv_req) +{ + struct cdns3_endpoint *priv_ep = priv_req->priv_ep; + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct cdns3_aligned_buf *buf; + + /* check if buffer is aligned to 8. */ + if (!((uintptr_t)priv_req->request.buf & 0x7)) + return 0; + + buf = priv_req->aligned_buf; + + if (!buf || priv_req->request.length > buf->size) { + buf = kzalloc_obj(*buf, GFP_ATOMIC); + if (!buf) + return -ENOMEM; + + buf->size = priv_req->request.length; + buf->dir = usb_endpoint_dir_in(priv_ep->endpoint.desc) ? + DMA_TO_DEVICE : DMA_FROM_DEVICE; + + buf->buf = dma_alloc_noncoherent(priv_dev->sysdev, + buf->size, + &buf->dma, + buf->dir, + GFP_ATOMIC); + if (!buf->buf) { + kfree(buf); + return -ENOMEM; + } + + if (priv_req->aligned_buf) { + trace_cdns3_free_aligned_request(priv_req); + priv_req->aligned_buf->in_use = 0; + queue_work(system_freezable_wq, + &priv_dev->aligned_buf_wq); + } + + buf->in_use = 1; + priv_req->aligned_buf = buf; + + list_add_tail(&buf->list, + &priv_dev->aligned_buf_list); + } + + if (priv_ep->dir == USB_DIR_IN) { + /* Make DMA buffer CPU accessible */ + dma_sync_single_for_cpu(priv_dev->sysdev, + buf->dma, buf->size, buf->dir); + memcpy(buf->buf, priv_req->request.buf, + priv_req->request.length); + } + + /* Transfer DMA buffer ownership back to device */ + dma_sync_single_for_device(priv_dev->sysdev, + buf->dma, buf->size, buf->dir); + + priv_req->flags |= REQUEST_UNALIGNED; + trace_cdns3_prepare_aligned_request(priv_req); + + return 0; +} + +static int cdns3_wa1_update_guard(struct cdns3_endpoint *priv_ep, + struct cdns3_trb *trb) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + + if (!priv_ep->wa1_set) { + u32 doorbell; + + doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); + + if (doorbell) { + priv_ep->wa1_cycle_bit = priv_ep->pcs ? TRB_CYCLE : 0; + priv_ep->wa1_set = 1; + priv_ep->wa1_trb = trb; + priv_ep->wa1_trb_index = priv_ep->enqueue; + trace_cdns3_wa1(priv_ep, "set guard"); + return 0; + } + } + return 1; +} + +static void cdns3_wa1_tray_restore_cycle_bit(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep) +{ + int dma_index; + u32 doorbell; + + doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); + dma_index = cdns3_get_dma_pos(priv_dev, priv_ep); + + if (!doorbell || dma_index != priv_ep->wa1_trb_index) + cdns3_wa1_restore_cycle_bit(priv_ep); +} + +static int cdns3_ep_run_stream_transfer(struct cdns3_endpoint *priv_ep, + struct usb_request *request) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct cdns3_request *priv_req; + struct cdns3_trb *trb; + dma_addr_t trb_dma; + int address; + u32 control; + u32 length; + u32 tdl; + unsigned int sg_idx = priv_ep->stream_sg_idx; + + priv_req = to_cdns3_request(request); + address = priv_ep->endpoint.desc->bEndpointAddress; + + priv_ep->flags |= EP_PENDING_REQUEST; + + /* must allocate buffer aligned to 8 */ + if (priv_req->flags & REQUEST_UNALIGNED) + trb_dma = priv_req->aligned_buf->dma; + else + trb_dma = request->dma; + + /* For stream capable endpoints driver use only single TD. */ + trb = priv_ep->trb_pool + priv_ep->enqueue; + priv_req->start_trb = priv_ep->enqueue; + priv_req->end_trb = priv_req->start_trb; + priv_req->trb = trb; + + cdns3_select_ep(priv_ep->cdns3_dev, address); + + control = TRB_TYPE(TRB_NORMAL) | TRB_CYCLE | + TRB_STREAM_ID(priv_req->request.stream_id) | TRB_ISP; + + if (!request->num_sgs) { + trb->buffer = cpu_to_le32(TRB_BUFFER(trb_dma)); + length = request->length; + } else { + trb->buffer = cpu_to_le32(TRB_BUFFER(request->sg[sg_idx].dma_address)); + length = request->sg[sg_idx].length; + } + + tdl = DIV_ROUND_UP(length, priv_ep->endpoint.maxpacket); + + trb->length = cpu_to_le32(TRB_BURST_LEN(16) | TRB_LEN(length)); + + /* + * For DEV_VER_V2 controller version we have enabled + * USB_CONF2_EN_TDL_TRB in DMULT configuration. + * This enables TDL calculation based on TRB, hence setting TDL in TRB. + */ + if (priv_dev->dev_ver >= DEV_VER_V2) { + if (priv_dev->gadget.speed == USB_SPEED_SUPER) + trb->length |= cpu_to_le32(TRB_TDL_SS_SIZE(tdl)); + } + priv_req->flags |= REQUEST_PENDING; + + trb->control = cpu_to_le32(control); + + trace_cdns3_prepare_trb(priv_ep, priv_req->trb); + + /* + * Memory barrier - Cycle Bit must be set before trb->length and + * trb->buffer fields. + */ + wmb(); + + /* always first element */ + writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma), + &priv_dev->regs->ep_traddr); + + if (!(priv_ep->flags & EP_STALLED)) { + trace_cdns3_ring(priv_ep); + /*clearing TRBERR and EP_STS_DESCMIS before seting DRDY*/ + writel(EP_STS_TRBERR | EP_STS_DESCMIS, &priv_dev->regs->ep_sts); + + priv_ep->prime_flag = false; + + /* + * Controller version DEV_VER_V2 tdl calculation + * is based on TRB + */ + + if (priv_dev->dev_ver < DEV_VER_V2) + writel(EP_CMD_TDL_SET(tdl) | EP_CMD_STDL, + &priv_dev->regs->ep_cmd); + else if (priv_dev->dev_ver > DEV_VER_V2) + writel(tdl, &priv_dev->regs->ep_tdl); + + priv_ep->last_stream_id = priv_req->request.stream_id; + writel(EP_CMD_DRDY, &priv_dev->regs->ep_cmd); + writel(EP_CMD_ERDY_SID(priv_req->request.stream_id) | + EP_CMD_ERDY, &priv_dev->regs->ep_cmd); + + trace_cdns3_doorbell_epx(priv_ep->name, + readl(&priv_dev->regs->ep_traddr)); + } + + /* WORKAROUND for transition to L0 */ + __cdns3_gadget_wakeup(priv_dev); + + return 0; +} + +static void cdns3_rearm_drdy_if_needed(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + + if (priv_dev->dev_ver < DEV_VER_V3) + return; + + if (readl(&priv_dev->regs->ep_sts) & EP_STS_TRBERR) { + writel(EP_STS_TRBERR, &priv_dev->regs->ep_sts); + writel(EP_CMD_DRDY, &priv_dev->regs->ep_cmd); + } +} + +/** + * cdns3_ep_run_transfer - start transfer on no-default endpoint hardware + * @priv_ep: endpoint object + * @request: request object + * + * Returns zero on success or negative value on failure + */ +static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, + struct usb_request *request) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct cdns3_request *priv_req; + struct cdns3_trb *trb; + struct cdns3_trb *link_trb = NULL; + dma_addr_t trb_dma; + u32 togle_pcs = 1; + int sg_iter = 0; + int num_trb_req; + int trb_burst; + int num_trb; + int address; + u32 control; + int pcs; + u16 total_tdl = 0; + struct scatterlist *s = NULL; + bool sg_supported = !!(request->num_mapped_sgs); + u32 ioc = request->no_interrupt ? 0 : TRB_IOC; + + num_trb_req = sg_supported ? request->num_mapped_sgs : 1; + + /* ISO transfer require each SOF have a TD, each TD include some TRBs */ + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC) + num_trb = priv_ep->interval * num_trb_req; + else + num_trb = num_trb_req; + + priv_req = to_cdns3_request(request); + address = priv_ep->endpoint.desc->bEndpointAddress; + + priv_ep->flags |= EP_PENDING_REQUEST; + + /* must allocate buffer aligned to 8 */ + if (priv_req->flags & REQUEST_UNALIGNED) + trb_dma = priv_req->aligned_buf->dma; + else + trb_dma = request->dma; + + trb = priv_ep->trb_pool + priv_ep->enqueue; + priv_req->start_trb = priv_ep->enqueue; + priv_req->trb = trb; + + cdns3_select_ep(priv_ep->cdns3_dev, address); + + /* prepare ring */ + if ((priv_ep->enqueue + num_trb) >= (priv_ep->num_trbs - 1)) { + int doorbell, dma_index; + u32 ch_bit = 0; + + doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); + dma_index = cdns3_get_dma_pos(priv_dev, priv_ep); + + /* Driver can't update LINK TRB if it is current processed. */ + if (doorbell && dma_index == priv_ep->num_trbs - 1) { + priv_ep->flags |= EP_DEFERRED_DRDY; + return -ENOBUFS; + } + + /*updating C bt in Link TRB before starting DMA*/ + link_trb = priv_ep->trb_pool + (priv_ep->num_trbs - 1); + /* + * For TRs size equal 2 enabling TRB_CHAIN for epXin causes + * that DMA stuck at the LINK TRB. + * On the other hand, removing TRB_CHAIN for longer TRs for + * epXout cause that DMA stuck after handling LINK TRB. + * To eliminate this strange behavioral driver set TRB_CHAIN + * bit only for TR size > 2. + */ + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC || + TRBS_PER_SEGMENT > 2) + ch_bit = TRB_CHAIN; + + link_trb->control = cpu_to_le32(((priv_ep->pcs) ? TRB_CYCLE : 0) | + TRB_TYPE(TRB_LINK) | TRB_TOGGLE | ch_bit); + + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC) { + /* + * ISO require LINK TRB must be first one of TD. + * Fill LINK TRBs for left trb space to simply software process logic. + */ + while (priv_ep->enqueue) { + *trb = *link_trb; + trace_cdns3_prepare_trb(priv_ep, trb); + + cdns3_ep_inc_enq(priv_ep); + trb = priv_ep->trb_pool + priv_ep->enqueue; + priv_req->trb = trb; + } + } + } + + if (num_trb > priv_ep->free_trbs) { + priv_ep->flags |= EP_RING_FULL; + return -ENOBUFS; + } + + if (priv_dev->dev_ver <= DEV_VER_V2) + togle_pcs = cdns3_wa1_update_guard(priv_ep, trb); + + /* set incorrect Cycle Bit for first trb*/ + control = priv_ep->pcs ? 0 : TRB_CYCLE; + trb->length = 0; + if (priv_dev->dev_ver >= DEV_VER_V2) { + u16 td_size; + + td_size = DIV_ROUND_UP(request->length, + priv_ep->endpoint.maxpacket); + if (priv_dev->gadget.speed == USB_SPEED_SUPER) + trb->length = cpu_to_le32(TRB_TDL_SS_SIZE(td_size)); + else + control |= TRB_TDL_HS_SIZE(td_size); + } + + do { + u32 length; + + if (!(sg_iter % num_trb_req) && sg_supported) + s = request->sg; + + /* fill TRB */ + control |= TRB_TYPE(TRB_NORMAL); + if (sg_supported) { + trb->buffer = cpu_to_le32(TRB_BUFFER(sg_dma_address(s))); + length = sg_dma_len(s); + } else { + trb->buffer = cpu_to_le32(TRB_BUFFER(trb_dma)); + length = request->length; + } + + if (priv_ep->flags & EP_TDLCHK_EN) + total_tdl += DIV_ROUND_UP(length, + priv_ep->endpoint.maxpacket); + + trb_burst = priv_ep->trb_burst_size; + + /* + * Supposed DMA cross 4k bounder problem should be fixed at DEV_VER_V2, but still + * met problem when do ISO transfer if sg enabled. + * + * Data pattern likes below when sg enabled, package size is 1k and mult is 2 + * [UVC Header(8B) ] [data(3k - 8)] ... + * + * The received data at offset 0xd000 will get 0xc000 data, len 0x70. Error happen + * as below pattern: + * 0xd000: wrong + * 0xe000: wrong + * 0xf000: correct + * 0x10000: wrong + * 0x11000: wrong + * 0x12000: correct + * ... + * + * But it is still unclear about why error have not happen below 0xd000, it should + * cross 4k bounder. But anyway, the below code can fix this problem. + * + * To avoid DMA cross 4k bounder at ISO transfer, reduce burst len according to 16. + */ + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && priv_dev->dev_ver <= DEV_VER_V2) + if (ALIGN_DOWN(trb->buffer, SZ_4K) != + ALIGN_DOWN(trb->buffer + length, SZ_4K)) + trb_burst = 16; + + trb->length |= cpu_to_le32(TRB_BURST_LEN(trb_burst) | + TRB_LEN(length)); + pcs = priv_ep->pcs ? TRB_CYCLE : 0; + + /* + * first trb should be prepared as last to avoid processing + * transfer to early + */ + if (sg_iter != 0) + control |= pcs; + + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir) { + control |= ioc | TRB_ISP; + } else { + /* for last element in TD or in SG list */ + if (sg_iter == (num_trb - 1) && sg_iter != 0) + control |= pcs | ioc | TRB_ISP; + } + + if (sg_iter) + trb->control = cpu_to_le32(control); + else + priv_req->trb->control = cpu_to_le32(control); + + if (sg_supported) { + trb->control |= cpu_to_le32(TRB_ISP); + /* Don't set chain bit for last TRB */ + if ((sg_iter % num_trb_req) < num_trb_req - 1) + trb->control |= cpu_to_le32(TRB_CHAIN); + + s = sg_next(s); + } + + control = 0; + ++sg_iter; + priv_req->end_trb = priv_ep->enqueue; + cdns3_ep_inc_enq(priv_ep); + trb = priv_ep->trb_pool + priv_ep->enqueue; + trb->length = 0; + } while (sg_iter < num_trb); + + trb = priv_req->trb; + + priv_req->flags |= REQUEST_PENDING; + priv_req->num_of_trb = num_trb; + + if (sg_iter == 1) + trb->control |= cpu_to_le32(ioc | TRB_ISP); + + if (priv_dev->dev_ver < DEV_VER_V2 && + (priv_ep->flags & EP_TDLCHK_EN)) { + u16 tdl = total_tdl; + u16 old_tdl = EP_CMD_TDL_GET(readl(&priv_dev->regs->ep_cmd)); + + if (tdl > EP_CMD_TDL_MAX) { + tdl = EP_CMD_TDL_MAX; + priv_ep->pending_tdl = total_tdl - EP_CMD_TDL_MAX; + } + + if (old_tdl < tdl) { + tdl -= old_tdl; + writel(EP_CMD_TDL_SET(tdl) | EP_CMD_STDL, + &priv_dev->regs->ep_cmd); + } + } + + /* + * Memory barrier - cycle bit must be set before other filds in trb. + */ + wmb(); + + /* give the TD to the consumer*/ + if (togle_pcs) + trb->control = trb->control ^ cpu_to_le32(1); + + if (priv_dev->dev_ver <= DEV_VER_V2) + cdns3_wa1_tray_restore_cycle_bit(priv_dev, priv_ep); + + if (num_trb > 1) { + int i = 0; + + while (i < num_trb) { + trace_cdns3_prepare_trb(priv_ep, trb + i); + if (trb + i == link_trb) { + trb = priv_ep->trb_pool; + num_trb = num_trb - i; + i = 0; + } else { + i++; + } + } + } else { + trace_cdns3_prepare_trb(priv_ep, priv_req->trb); + } + + /* + * Memory barrier - Cycle Bit must be set before trb->length and + * trb->buffer fields. + */ + wmb(); + + /* + * For DMULT mode we can set address to transfer ring only once after + * enabling endpoint. + */ + if (priv_ep->flags & EP_UPDATE_EP_TRBADDR) { + /* + * Until SW is not ready to handle the OUT transfer the ISO OUT + * Endpoint should be disabled (EP_CFG.ENABLE = 0). + * EP_CFG_ENABLE must be set before updating ep_traddr. + */ + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir && + !(priv_ep->flags & EP_QUIRK_ISO_OUT_EN)) { + priv_ep->flags |= EP_QUIRK_ISO_OUT_EN; + cdns3_set_register_bit(&priv_dev->regs->ep_cfg, + EP_CFG_ENABLE); + } + + writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma + + priv_req->start_trb * TRB_SIZE), + &priv_dev->regs->ep_traddr); + + priv_ep->flags &= ~EP_UPDATE_EP_TRBADDR; + } + + if (!priv_ep->wa1_set && !(priv_ep->flags & EP_STALLED)) { + trace_cdns3_ring(priv_ep); + /*clearing TRBERR and EP_STS_DESCMIS before seting DRDY*/ + writel(EP_STS_TRBERR | EP_STS_DESCMIS, &priv_dev->regs->ep_sts); + writel(EP_CMD_DRDY, &priv_dev->regs->ep_cmd); + cdns3_rearm_drdy_if_needed(priv_ep); + trace_cdns3_doorbell_epx(priv_ep->name, + readl(&priv_dev->regs->ep_traddr)); + } + + /* WORKAROUND for transition to L0 */ + __cdns3_gadget_wakeup(priv_dev); + + return 0; +} + +void cdns3_set_hw_configuration(struct cdns3_device *priv_dev) +{ + struct cdns3_endpoint *priv_ep; + struct usb_ep *ep; + + if (priv_dev->hw_configured_flag) + return; + + writel(USB_CONF_CFGSET, &priv_dev->regs->usb_conf); + + cdns3_set_register_bit(&priv_dev->regs->usb_conf, + USB_CONF_U1EN | USB_CONF_U2EN); + + priv_dev->hw_configured_flag = 1; + + list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) { + if (ep->enabled) { + priv_ep = ep_to_cdns3_ep(ep); + cdns3_start_all_request(priv_dev, priv_ep); + } + } + + cdns3_allow_enable_l1(priv_dev, 1); +} + +/** + * cdns3_trb_handled - check whether trb has been handled by DMA + * + * @priv_ep: extended endpoint object. + * @priv_req: request object for checking + * + * Endpoint must be selected before invoking this function. + * + * Returns false if request has not been handled by DMA, else returns true. + * + * SR - start ring + * ER - end ring + * DQ = priv_ep->dequeue - dequeue position + * EQ = priv_ep->enqueue - enqueue position + * ST = priv_req->start_trb - index of first TRB in transfer ring + * ET = priv_req->end_trb - index of last TRB in transfer ring + * CI = current_index - index of processed TRB by DMA. + * + * As first step, we check if the TRB between the ST and ET. + * Then, we check if cycle bit for index priv_ep->dequeue + * is correct. + * + * some rules: + * 1. priv_ep->dequeue never equals to current_index. + * 2 priv_ep->enqueue never exceed priv_ep->dequeue + * 3. exception: priv_ep->enqueue == priv_ep->dequeue + * and priv_ep->free_trbs is zero. + * This case indicate that TR is full. + * + * At below two cases, the request have been handled. + * Case 1 - priv_ep->dequeue < current_index + * SR ... EQ ... DQ ... CI ... ER + * SR ... DQ ... CI ... EQ ... ER + * + * Case 2 - priv_ep->dequeue > current_index + * This situation takes place when CI go through the LINK TRB at the end of + * transfer ring. + * SR ... CI ... EQ ... DQ ... ER + */ +static bool cdns3_trb_handled(struct cdns3_endpoint *priv_ep, + struct cdns3_request *priv_req) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct cdns3_trb *trb; + int current_index = 0; + int handled = 0; + int doorbell; + + current_index = cdns3_get_dma_pos(priv_dev, priv_ep); + doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); + + /* current trb doesn't belong to this request */ + if (priv_req->start_trb < priv_req->end_trb) { + if (priv_ep->dequeue > priv_req->end_trb) + goto finish; + + if (priv_ep->dequeue < priv_req->start_trb) + goto finish; + } + + if ((priv_req->start_trb > priv_req->end_trb) && + (priv_ep->dequeue > priv_req->end_trb) && + (priv_ep->dequeue < priv_req->start_trb)) + goto finish; + + if ((priv_req->start_trb == priv_req->end_trb) && + (priv_ep->dequeue != priv_req->end_trb)) + goto finish; + + trb = &priv_ep->trb_pool[priv_ep->dequeue]; + + if ((le32_to_cpu(trb->control) & TRB_CYCLE) != priv_ep->ccs) + goto finish; + + if (doorbell == 1 && current_index == priv_ep->dequeue) + goto finish; + + /* The corner case for TRBS_PER_SEGMENT equal 2). */ + if (TRBS_PER_SEGMENT == 2 && priv_ep->type != USB_ENDPOINT_XFER_ISOC) { + handled = 1; + goto finish; + } + + if (priv_ep->enqueue == priv_ep->dequeue && + priv_ep->free_trbs == 0) { + handled = 1; + } else if (priv_ep->dequeue < current_index) { + if ((current_index == (priv_ep->num_trbs - 1)) && + !priv_ep->dequeue) + goto finish; + + handled = 1; + } else if (priv_ep->dequeue > current_index) { + handled = 1; + } + +finish: + trace_cdns3_request_handled(priv_req, current_index, handled); + + return handled; +} + +static void cdns3_transfer_completed(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep) +{ + struct cdns3_request *priv_req; + struct usb_request *request; + struct cdns3_trb *trb; + bool request_handled = false; + bool transfer_end = false; + + while (!list_empty(&priv_ep->pending_req_list)) { + request = cdns3_next_request(&priv_ep->pending_req_list); + priv_req = to_cdns3_request(request); + + trb = priv_ep->trb_pool + priv_ep->dequeue; + + /* The TRB was changed as link TRB, and the request was handled at ep_dequeue */ + while (TRB_FIELD_TO_TYPE(le32_to_cpu(trb->control)) == TRB_LINK) { + + /* ISO ep_traddr may stop at LINK TRB */ + if (priv_ep->dequeue == cdns3_get_dma_pos(priv_dev, priv_ep) && + priv_ep->type == USB_ENDPOINT_XFER_ISOC) + break; + + trace_cdns3_complete_trb(priv_ep, trb); + cdns3_ep_inc_deq(priv_ep); + trb = priv_ep->trb_pool + priv_ep->dequeue; + } + + if (!request->stream_id) { + /* Re-select endpoint. It could be changed by other CPU + * during handling usb_gadget_giveback_request. + */ + cdns3_select_ep(priv_dev, priv_ep->endpoint.address); + + while (cdns3_trb_handled(priv_ep, priv_req)) { + priv_req->finished_trb++; + if (priv_req->finished_trb >= priv_req->num_of_trb) + request_handled = true; + + trb = priv_ep->trb_pool + priv_ep->dequeue; + trace_cdns3_complete_trb(priv_ep, trb); + + if (!transfer_end) + request->actual += + TRB_LEN(le32_to_cpu(trb->length)); + + if (priv_req->num_of_trb > 1 && + le32_to_cpu(trb->control) & TRB_SMM && + le32_to_cpu(trb->control) & TRB_CHAIN) + transfer_end = true; + + cdns3_ep_inc_deq(priv_ep); + } + + if (request_handled) { + /* TRBs are duplicated by priv_ep->interval time for ISO IN */ + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && priv_ep->dir) + request->actual /= priv_ep->interval; + + cdns3_gadget_giveback(priv_ep, priv_req, 0); + request_handled = false; + transfer_end = false; + } else { + goto prepare_next_td; + } + + if (priv_ep->type != USB_ENDPOINT_XFER_ISOC && + TRBS_PER_SEGMENT == 2) + break; + } else { + /* Re-select endpoint. It could be changed by other CPU + * during handling usb_gadget_giveback_request. + */ + cdns3_select_ep(priv_dev, priv_ep->endpoint.address); + + trb = priv_ep->trb_pool; + trace_cdns3_complete_trb(priv_ep, trb); + + if (trb != priv_req->trb) + dev_warn(priv_dev->dev, + "request_trb=0x%p, queue_trb=0x%p\n", + priv_req->trb, trb); + + request->actual += TRB_LEN(le32_to_cpu(trb->length)); + + if (!request->num_sgs || + (request->num_sgs == (priv_ep->stream_sg_idx + 1))) { + priv_ep->stream_sg_idx = 0; + cdns3_gadget_giveback(priv_ep, priv_req, 0); + } else { + priv_ep->stream_sg_idx++; + cdns3_ep_run_stream_transfer(priv_ep, request); + } + break; + } + } + priv_ep->flags &= ~EP_PENDING_REQUEST; + +prepare_next_td: + if (!(priv_ep->flags & EP_STALLED) && + !(priv_ep->flags & EP_STALL_PENDING)) + cdns3_start_all_request(priv_dev, priv_ep); +} + +void cdns3_rearm_transfer(struct cdns3_endpoint *priv_ep, u8 rearm) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + + cdns3_wa1_restore_cycle_bit(priv_ep); + + if (rearm) { + trace_cdns3_ring(priv_ep); + + /* Cycle Bit must be updated before arming DMA. */ + wmb(); + writel(EP_CMD_DRDY, &priv_dev->regs->ep_cmd); + + __cdns3_gadget_wakeup(priv_dev); + + trace_cdns3_doorbell_epx(priv_ep->name, + readl(&priv_dev->regs->ep_traddr)); + } +} + +static void cdns3_reprogram_tdl(struct cdns3_endpoint *priv_ep) +{ + u16 tdl = priv_ep->pending_tdl; + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + + if (tdl > EP_CMD_TDL_MAX) { + tdl = EP_CMD_TDL_MAX; + priv_ep->pending_tdl -= EP_CMD_TDL_MAX; + } else { + priv_ep->pending_tdl = 0; + } + + writel(EP_CMD_TDL_SET(tdl) | EP_CMD_STDL, &priv_dev->regs->ep_cmd); +} + +/** + * cdns3_check_ep_interrupt_proceed - Processes interrupt related to endpoint + * @priv_ep: endpoint object + * + * Returns 0 + */ +static int cdns3_check_ep_interrupt_proceed(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + u32 ep_sts_reg; + struct usb_request *deferred_request; + struct usb_request *pending_request; + u32 tdl = 0; + + cdns3_select_ep(priv_dev, priv_ep->endpoint.address); + + trace_cdns3_epx_irq(priv_dev, priv_ep); + + ep_sts_reg = readl(&priv_dev->regs->ep_sts); + writel(ep_sts_reg, &priv_dev->regs->ep_sts); + + if ((ep_sts_reg & EP_STS_PRIME) && priv_ep->use_streams) { + bool dbusy = !!(ep_sts_reg & EP_STS_DBUSY); + + tdl = cdns3_get_tdl(priv_dev); + + /* + * Continue the previous transfer: + * There is some racing between ERDY and PRIME. The device send + * ERDY and almost in the same time Host send PRIME. It cause + * that host ignore the ERDY packet and driver has to send it + * again. + */ + if (tdl && (dbusy || !EP_STS_BUFFEMPTY(ep_sts_reg) || + EP_STS_HOSTPP(ep_sts_reg))) { + writel(EP_CMD_ERDY | + EP_CMD_ERDY_SID(priv_ep->last_stream_id), + &priv_dev->regs->ep_cmd); + ep_sts_reg &= ~(EP_STS_MD_EXIT | EP_STS_IOC); + } else { + priv_ep->prime_flag = true; + + pending_request = cdns3_next_request(&priv_ep->pending_req_list); + deferred_request = cdns3_next_request(&priv_ep->deferred_req_list); + + if (deferred_request && !pending_request) { + cdns3_start_all_request(priv_dev, priv_ep); + } + } + } + + if (ep_sts_reg & EP_STS_TRBERR) { + if (priv_ep->flags & EP_STALL_PENDING && + !(ep_sts_reg & EP_STS_DESCMIS && + priv_dev->dev_ver < DEV_VER_V2)) { + cdns3_ep_stall_flush(priv_ep); + } + + /* + * For isochronous transfer driver completes request on + * IOC or on TRBERR. IOC appears only when device receive + * OUT data packet. If host disable stream or lost some packet + * then the only way to finish all queued transfer is to do it + * on TRBERR event. + */ + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && + !priv_ep->wa1_set) { + if (!priv_ep->dir) { + u32 ep_cfg = readl(&priv_dev->regs->ep_cfg); + + ep_cfg &= ~EP_CFG_ENABLE; + writel(ep_cfg, &priv_dev->regs->ep_cfg); + priv_ep->flags &= ~EP_QUIRK_ISO_OUT_EN; + priv_ep->flags |= EP_UPDATE_EP_TRBADDR; + } + cdns3_transfer_completed(priv_dev, priv_ep); + } else if (!(priv_ep->flags & EP_STALLED) && + !(priv_ep->flags & EP_STALL_PENDING)) { + if (priv_ep->flags & EP_DEFERRED_DRDY) { + priv_ep->flags &= ~EP_DEFERRED_DRDY; + cdns3_start_all_request(priv_dev, priv_ep); + } else { + cdns3_rearm_transfer(priv_ep, + priv_ep->wa1_set); + } + } + } + + if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP) || + (ep_sts_reg & EP_STS_IOT)) { + if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN) { + if (ep_sts_reg & EP_STS_ISP) + priv_ep->flags |= EP_QUIRK_END_TRANSFER; + else + priv_ep->flags &= ~EP_QUIRK_END_TRANSFER; + } + + if (!priv_ep->use_streams) { + if ((ep_sts_reg & EP_STS_IOC) || + (ep_sts_reg & EP_STS_ISP)) { + cdns3_transfer_completed(priv_dev, priv_ep); + } else if ((priv_ep->flags & EP_TDLCHK_EN) & + priv_ep->pending_tdl) { + /* handle IOT with pending tdl */ + cdns3_reprogram_tdl(priv_ep); + } + } else if (priv_ep->dir == USB_DIR_OUT) { + priv_ep->ep_sts_pending |= ep_sts_reg; + } else if (ep_sts_reg & EP_STS_IOT) { + cdns3_transfer_completed(priv_dev, priv_ep); + } + } + + /* + * MD_EXIT interrupt sets when stream capable endpoint exits + * from MOVE DATA state of Bulk IN/OUT stream protocol state machine + */ + if (priv_ep->dir == USB_DIR_OUT && (ep_sts_reg & EP_STS_MD_EXIT) && + (priv_ep->ep_sts_pending & EP_STS_IOT) && priv_ep->use_streams) { + priv_ep->ep_sts_pending = 0; + cdns3_transfer_completed(priv_dev, priv_ep); + } + + /* + * WA2: this condition should only be meet when + * priv_ep->flags & EP_QUIRK_EXTRA_BUF_DET or + * priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN. + * In other cases this interrupt will be disabled. + */ + if (ep_sts_reg & EP_STS_DESCMIS && priv_dev->dev_ver < DEV_VER_V2 && + !(priv_ep->flags & EP_STALLED)) + cdns3_wa2_descmissing_packet(priv_ep); + + return 0; +} + +static void cdns3_disconnect_gadget(struct cdns3_device *priv_dev) +{ + if (priv_dev->gadget_driver && priv_dev->gadget_driver->disconnect) + priv_dev->gadget_driver->disconnect(&priv_dev->gadget); +} + +/** + * cdns3_check_usb_interrupt_proceed - Processes interrupt related to device + * @priv_dev: extended gadget object + * @usb_ists: bitmap representation of device's reported interrupts + * (usb_ists register value) + */ +static void cdns3_check_usb_interrupt_proceed(struct cdns3_device *priv_dev, + u32 usb_ists) +__must_hold(&priv_dev->lock) +{ + int speed = 0; + + trace_cdns3_usb_irq(priv_dev, usb_ists); + if (usb_ists & USB_ISTS_L1ENTI) { + /* + * WORKAROUND: CDNS3 controller has issue with hardware resuming + * from L1. To fix it, if any DMA transfer is pending driver + * must starts driving resume signal immediately. + */ + if (readl(&priv_dev->regs->drbl)) + __cdns3_gadget_wakeup(priv_dev); + } + + /* Connection detected */ + if (usb_ists & (USB_ISTS_CON2I | USB_ISTS_CONI)) { + speed = cdns3_get_speed(priv_dev); + priv_dev->gadget.speed = speed; + usb_gadget_set_state(&priv_dev->gadget, USB_STATE_POWERED); + cdns3_ep0_config(priv_dev); + } + + /* Disconnection detected */ + if (usb_ists & (USB_ISTS_DIS2I | USB_ISTS_DISI)) { + spin_unlock(&priv_dev->lock); + cdns3_disconnect_gadget(priv_dev); + spin_lock(&priv_dev->lock); + priv_dev->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&priv_dev->gadget, USB_STATE_NOTATTACHED); + cdns3_hw_reset_eps_config(priv_dev); + } + + if (usb_ists & (USB_ISTS_L2ENTI | USB_ISTS_U3ENTI)) { + if (priv_dev->gadget_driver && + priv_dev->gadget_driver->suspend) { + spin_unlock(&priv_dev->lock); + priv_dev->gadget_driver->suspend(&priv_dev->gadget); + spin_lock(&priv_dev->lock); + } + } + + if (usb_ists & (USB_ISTS_L2EXTI | USB_ISTS_U3EXTI)) { + if (priv_dev->gadget_driver && + priv_dev->gadget_driver->resume) { + spin_unlock(&priv_dev->lock); + priv_dev->gadget_driver->resume(&priv_dev->gadget); + spin_lock(&priv_dev->lock); + } + } + + /* reset*/ + if (usb_ists & (USB_ISTS_UWRESI | USB_ISTS_UHRESI | USB_ISTS_U2RESI)) { + if (priv_dev->gadget_driver) { + spin_unlock(&priv_dev->lock); + usb_gadget_udc_reset(&priv_dev->gadget, + priv_dev->gadget_driver); + spin_lock(&priv_dev->lock); + + /*read again to check the actual speed*/ + speed = cdns3_get_speed(priv_dev); + priv_dev->gadget.speed = speed; + cdns3_hw_reset_eps_config(priv_dev); + cdns3_ep0_config(priv_dev); + } + } +} + +/** + * cdns3_device_irq_handler - interrupt handler for device part of controller + * + * @irq: irq number for cdns3 core device + * @data: structure of cdns3 + * + * Returns IRQ_HANDLED or IRQ_NONE + */ +static irqreturn_t cdns3_device_irq_handler(int irq, void *data) +{ + struct cdns3_device *priv_dev = data; + struct cdns *cdns = dev_get_drvdata(priv_dev->dev); + irqreturn_t ret = IRQ_NONE; + u32 reg; + + if (cdns->in_lpm) + return ret; + + /* check USB device interrupt */ + reg = readl(&priv_dev->regs->usb_ists); + if (reg) { + /* After masking interrupts the new interrupts won't be + * reported in usb_ists/ep_ists. In order to not lose some + * of them driver disables only detected interrupts. + * They will be enabled ASAP after clearing source of + * interrupt. This an unusual behavior only applies to + * usb_ists register. + */ + reg = ~reg & readl(&priv_dev->regs->usb_ien); + /* mask deferred interrupt. */ + writel(reg, &priv_dev->regs->usb_ien); + ret = IRQ_WAKE_THREAD; + } + + /* check endpoint interrupt */ + reg = readl(&priv_dev->regs->ep_ists); + if (reg) { + writel(0, &priv_dev->regs->ep_ien); + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +/** + * cdns3_device_thread_irq_handler - interrupt handler for device part + * of controller + * + * @irq: irq number for cdns3 core device + * @data: structure of cdns3 + * + * Returns IRQ_HANDLED or IRQ_NONE + */ +static irqreturn_t cdns3_device_thread_irq_handler(int irq, void *data) +{ + struct cdns3_device *priv_dev = data; + irqreturn_t ret = IRQ_NONE; + unsigned long flags; + unsigned int bit; + unsigned long reg; + + local_bh_disable(); + spin_lock_irqsave(&priv_dev->lock, flags); + + reg = readl(&priv_dev->regs->usb_ists); + if (reg) { + writel(reg, &priv_dev->regs->usb_ists); + writel(USB_IEN_INIT, &priv_dev->regs->usb_ien); + cdns3_check_usb_interrupt_proceed(priv_dev, reg); + ret = IRQ_HANDLED; + } + + reg = readl(&priv_dev->regs->ep_ists); + + /* handle default endpoint OUT */ + if (reg & EP_ISTS_EP_OUT0) { + cdns3_check_ep0_interrupt_proceed(priv_dev, USB_DIR_OUT); + ret = IRQ_HANDLED; + } + + /* handle default endpoint IN */ + if (reg & EP_ISTS_EP_IN0) { + cdns3_check_ep0_interrupt_proceed(priv_dev, USB_DIR_IN); + ret = IRQ_HANDLED; + } + + /* check if interrupt from non default endpoint, if no exit */ + reg &= ~(EP_ISTS_EP_OUT0 | EP_ISTS_EP_IN0); + if (!reg) + goto irqend; + + for_each_set_bit(bit, ®, + sizeof(u32) * BITS_PER_BYTE) { + cdns3_check_ep_interrupt_proceed(priv_dev->eps[bit]); + ret = IRQ_HANDLED; + } + + if (priv_dev->dev_ver < DEV_VER_V2 && priv_dev->using_streams) + cdns3_wa2_check_outq_status(priv_dev); + +irqend: + writel(~0, &priv_dev->regs->ep_ien); + spin_unlock_irqrestore(&priv_dev->lock, flags); + local_bh_enable(); + + return ret; +} + +/** + * cdns3_ep_onchip_buffer_reserve - Try to reserve onchip buf for EP + * + * The real reservation will occur during write to EP_CFG register, + * this function is used to check if the 'size' reservation is allowed. + * + * @priv_dev: extended gadget object + * @size: the size (KB) for EP would like to allocate + * @is_in: endpoint direction + * + * Return 0 if the required size can met or negative value on failure + */ +static int cdns3_ep_onchip_buffer_reserve(struct cdns3_device *priv_dev, + int size, int is_in) +{ + int remained; + + /* 2KB are reserved for EP0*/ + remained = priv_dev->onchip_buffers - priv_dev->onchip_used_size - 2; + + if (is_in) { + if (remained < size) + return -EPERM; + + priv_dev->onchip_used_size += size; + } else { + int required; + + /** + * ALL OUT EPs are shared the same chunk onchip memory, so + * driver checks if it already has assigned enough buffers + */ + if (priv_dev->out_mem_is_allocated >= size) + return 0; + + required = size - priv_dev->out_mem_is_allocated; + + if (required > remained) + return -EPERM; + + priv_dev->out_mem_is_allocated += required; + priv_dev->onchip_used_size += required; + } + + return 0; +} + +static void cdns3_configure_dmult(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep) +{ + struct cdns3_usb_regs __iomem *regs = priv_dev->regs; + + /* For dev_ver > DEV_VER_V2 DMULT is configured per endpoint */ + if (priv_dev->dev_ver <= DEV_VER_V2) + writel(USB_CONF_DMULT, ®s->usb_conf); + + if (priv_dev->dev_ver == DEV_VER_V2) + writel(USB_CONF2_EN_TDL_TRB, ®s->usb_conf2); + + if (priv_dev->dev_ver >= DEV_VER_V3 && priv_ep) { + u32 mask; + + if (priv_ep->dir) + mask = BIT(priv_ep->num + 16); + else + mask = BIT(priv_ep->num); + + if (priv_ep->type != USB_ENDPOINT_XFER_ISOC && !priv_ep->dir) { + cdns3_set_register_bit(®s->tdl_from_trb, mask); + cdns3_set_register_bit(®s->tdl_beh, mask); + cdns3_set_register_bit(®s->tdl_beh2, mask); + cdns3_set_register_bit(®s->dma_adv_td, mask); + } + + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir) + cdns3_set_register_bit(®s->tdl_from_trb, mask); + + cdns3_set_register_bit(®s->dtrans, mask); + } +} + +/** + * cdns3_ep_config - Configure hardware endpoint + * @priv_ep: extended endpoint object + * @enable: set EP_CFG_ENABLE bit in ep_cfg register. + */ +int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable) +{ + bool is_iso_ep = (priv_ep->type == USB_ENDPOINT_XFER_ISOC); + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + u32 bEndpointAddress = priv_ep->num | priv_ep->dir; + u32 max_packet_size = priv_ep->wMaxPacketSize; + u8 maxburst = priv_ep->bMaxBurst; + u32 ep_cfg = 0; + u8 buffering; + int ret; + + buffering = priv_dev->ep_buf_size - 1; + + cdns3_configure_dmult(priv_dev, priv_ep); + + switch (priv_ep->type) { + case USB_ENDPOINT_XFER_INT: + ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_INT); + + if (priv_dev->dev_ver >= DEV_VER_V2 && !priv_ep->dir) + ep_cfg |= EP_CFG_TDL_CHK; + break; + case USB_ENDPOINT_XFER_BULK: + ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_BULK); + + if (priv_dev->dev_ver >= DEV_VER_V2 && !priv_ep->dir) + ep_cfg |= EP_CFG_TDL_CHK; + break; + default: + ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_ISOC); + buffering = (priv_ep->bMaxBurst + 1) * (priv_ep->mult + 1) - 1; + } + + switch (priv_dev->gadget.speed) { + case USB_SPEED_FULL: + max_packet_size = is_iso_ep ? 1023 : 64; + break; + case USB_SPEED_HIGH: + max_packet_size = is_iso_ep ? 1024 : 512; + break; + case USB_SPEED_SUPER: + if (priv_ep->type != USB_ENDPOINT_XFER_ISOC) { + max_packet_size = 1024; + maxburst = priv_dev->ep_buf_size - 1; + } + break; + default: + /* all other speed are not supported */ + return -EINVAL; + } + + if (max_packet_size == 1024) + priv_ep->trb_burst_size = 128; + else if (max_packet_size >= 512) + priv_ep->trb_burst_size = 64; + else + priv_ep->trb_burst_size = 16; + + /* + * In versions preceding DEV_VER_V2, for example, iMX8QM, there exit the bugs + * in the DMA. These bugs occur when the trb_burst_size exceeds 16 and the + * address is not aligned to 128 Bytes (which is a product of the 64-bit AXI + * and AXI maximum burst length of 16 or 0xF+1, dma_axi_ctrl0[3:0]). This + * results in data corruption when it crosses the 4K border. The corruption + * specifically occurs from the position (4K - (address & 0x7F)) to 4K. + * + * So force trb_burst_size to 16 at such platform. + */ + if (priv_dev->dev_ver < DEV_VER_V2) + priv_ep->trb_burst_size = 16; + + buffering = min_t(u8, buffering, EP_CFG_BUFFERING_MAX); + maxburst = min_t(u8, maxburst, EP_CFG_MAXBURST_MAX); + + /* onchip buffer is only allocated before configuration */ + if (!priv_dev->hw_configured_flag) { + ret = cdns3_ep_onchip_buffer_reserve(priv_dev, buffering + 1, + !!priv_ep->dir); + if (ret) { + dev_err(priv_dev->dev, "onchip mem is full, ep is invalid\n"); + return ret; + } + } + + if (enable) + ep_cfg |= EP_CFG_ENABLE; + + if (priv_ep->use_streams && priv_dev->gadget.speed >= USB_SPEED_SUPER) { + if (priv_dev->dev_ver >= DEV_VER_V3) { + u32 mask = BIT(priv_ep->num + (priv_ep->dir ? 16 : 0)); + + /* + * Stream capable endpoints are handled by using ep_tdl + * register. Other endpoints use TDL from TRB feature. + */ + cdns3_clear_register_bit(&priv_dev->regs->tdl_from_trb, + mask); + } + + /* Enable Stream Bit TDL chk and SID chk */ + ep_cfg |= EP_CFG_STREAM_EN | EP_CFG_TDL_CHK | EP_CFG_SID_CHK; + } + + ep_cfg |= EP_CFG_MAXPKTSIZE(max_packet_size) | + EP_CFG_MULT(priv_ep->mult) | /* must match EP setting */ + EP_CFG_BUFFERING(buffering) | + EP_CFG_MAXBURST(maxburst); + + cdns3_select_ep(priv_dev, bEndpointAddress); + writel(ep_cfg, &priv_dev->regs->ep_cfg); + priv_ep->flags |= EP_CONFIGURED; + + dev_dbg(priv_dev->dev, "Configure %s: with val %08x\n", + priv_ep->name, ep_cfg); + + return 0; +} + +/* Find correct direction for HW endpoint according to description */ +static int cdns3_ep_dir_is_correct(struct usb_endpoint_descriptor *desc, + struct cdns3_endpoint *priv_ep) +{ + return (priv_ep->endpoint.caps.dir_in && usb_endpoint_dir_in(desc)) || + (priv_ep->endpoint.caps.dir_out && usb_endpoint_dir_out(desc)); +} + +static struct +cdns3_endpoint *cdns3_find_available_ep(struct cdns3_device *priv_dev, + struct usb_endpoint_descriptor *desc) +{ + struct usb_ep *ep; + struct cdns3_endpoint *priv_ep; + + list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) { + unsigned long num; + int ret; + /* ep name pattern likes epXin or epXout */ + char c[2] = {ep->name[2], '\0'}; + + ret = kstrtoul(c, 10, &num); + if (ret) + return ERR_PTR(ret); + + priv_ep = ep_to_cdns3_ep(ep); + if (cdns3_ep_dir_is_correct(desc, priv_ep)) { + if (!(priv_ep->flags & EP_CLAIMED)) { + priv_ep->num = num; + return priv_ep; + } + } + } + + return ERR_PTR(-ENOENT); +} + +/* + * Cadence IP has one limitation that all endpoints must be configured + * (Type & MaxPacketSize) before setting configuration through hardware + * register, it means we can't change endpoints configuration after + * set_configuration. + * + * This function set EP_CLAIMED flag which is added when the gadget driver + * uses usb_ep_autoconfig to configure specific endpoint; + * When the udc driver receives set_configurion request, + * it goes through all claimed endpoints, and configure all endpoints + * accordingly. + * + * At usb_ep_ops.enable/disable, we only enable and disable endpoint through + * ep_cfg register which can be changed after set_configuration, and do + * some software operation accordingly. + */ +static struct +usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *comp_desc) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + struct cdns3_endpoint *priv_ep; + unsigned long flags; + + priv_ep = cdns3_find_available_ep(priv_dev, desc); + if (IS_ERR(priv_ep)) { + dev_err(priv_dev->dev, "no available ep\n"); + return NULL; + } + + dev_dbg(priv_dev->dev, "match endpoint: %s\n", priv_ep->name); + + spin_lock_irqsave(&priv_dev->lock, flags); + priv_ep->endpoint.desc = desc; + priv_ep->dir = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT; + priv_ep->type = usb_endpoint_type(desc); + priv_ep->flags |= EP_CLAIMED; + priv_ep->interval = desc->bInterval ? BIT(desc->bInterval - 1) : 0; + priv_ep->wMaxPacketSize = usb_endpoint_maxp(desc); + priv_ep->mult = USB_EP_MAXP_MULT(priv_ep->wMaxPacketSize); + priv_ep->wMaxPacketSize &= USB_ENDPOINT_MAXP_MASK; + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && comp_desc) { + priv_ep->mult = USB_SS_MULT(comp_desc->bmAttributes) - 1; + priv_ep->bMaxBurst = comp_desc->bMaxBurst; + } + + spin_unlock_irqrestore(&priv_dev->lock, flags); + return &priv_ep->endpoint; +} + +/** + * cdns3_gadget_ep_alloc_request - Allocates request + * @ep: endpoint object associated with request + * @gfp_flags: gfp flags + * + * Returns allocated request address, NULL on allocation error + */ +struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); + struct cdns3_request *priv_req; + + priv_req = kzalloc_obj(*priv_req, gfp_flags); + if (!priv_req) + return NULL; + + priv_req->priv_ep = priv_ep; + + trace_cdns3_alloc_request(priv_req); + return &priv_req->request; +} + +/** + * cdns3_gadget_ep_free_request - Free memory occupied by request + * @ep: endpoint object associated with request + * @request: request to free memory + */ +void cdns3_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request) +{ + struct cdns3_request *priv_req = to_cdns3_request(request); + + if (priv_req->aligned_buf) + priv_req->aligned_buf->in_use = 0; + + trace_cdns3_free_request(priv_req); + kfree(priv_req); +} + +/** + * cdns3_gadget_ep_enable - Enable endpoint + * @ep: endpoint object + * @desc: endpoint descriptor + * + * Returns 0 on success, error code elsewhere + */ +static int cdns3_gadget_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct cdns3_endpoint *priv_ep; + struct cdns3_device *priv_dev; + const struct usb_ss_ep_comp_descriptor *comp_desc; + u32 reg = EP_STS_EN_TRBERREN; + u32 bEndpointAddress; + unsigned long flags; + int enable = 1; + int ret = 0; + int val; + + if (!ep) { + pr_debug("usbss: ep not configured?\n"); + return -EINVAL; + } + + priv_ep = ep_to_cdns3_ep(ep); + priv_dev = priv_ep->cdns3_dev; + comp_desc = priv_ep->endpoint.comp_desc; + + if (!desc || desc->bDescriptorType != USB_DT_ENDPOINT) { + dev_dbg(priv_dev->dev, "usbss: invalid parameters\n"); + return -EINVAL; + } + + if (!desc->wMaxPacketSize) { + dev_err(priv_dev->dev, "usbss: missing wMaxPacketSize\n"); + return -EINVAL; + } + + if (dev_WARN_ONCE(priv_dev->dev, priv_ep->flags & EP_ENABLED, + "%s is already enabled\n", priv_ep->name)) + return 0; + + spin_lock_irqsave(&priv_dev->lock, flags); + + priv_ep->endpoint.desc = desc; + priv_ep->type = usb_endpoint_type(desc); + priv_ep->interval = desc->bInterval ? BIT(desc->bInterval - 1) : 0; + + if (priv_ep->interval > ISO_MAX_INTERVAL && + priv_ep->type == USB_ENDPOINT_XFER_ISOC) { + dev_err(priv_dev->dev, "Driver is limited to %d period\n", + ISO_MAX_INTERVAL); + + ret = -EINVAL; + goto exit; + } + + bEndpointAddress = priv_ep->num | priv_ep->dir; + cdns3_select_ep(priv_dev, bEndpointAddress); + + /* + * For some versions of controller at some point during ISO OUT traffic + * DMA reads Transfer Ring for the EP which has never got doorbell. + * This issue was detected only on simulation, but to avoid this issue + * driver add protection against it. To fix it driver enable ISO OUT + * endpoint before setting DRBL. This special treatment of ISO OUT + * endpoints are recommended by controller specification. + */ + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir) + enable = 0; + + if (usb_ss_max_streams(comp_desc) && usb_endpoint_xfer_bulk(desc)) { + /* + * Enable stream support (SS mode) related interrupts + * in EP_STS_EN Register + */ + if (priv_dev->gadget.speed >= USB_SPEED_SUPER) { + reg |= EP_STS_EN_IOTEN | EP_STS_EN_PRIMEEEN | + EP_STS_EN_SIDERREN | EP_STS_EN_MD_EXITEN | + EP_STS_EN_STREAMREN; + priv_ep->use_streams = true; + ret = cdns3_ep_config(priv_ep, enable); + priv_dev->using_streams |= true; + } + } else { + ret = cdns3_ep_config(priv_ep, enable); + } + + if (ret) + goto exit; + + ret = cdns3_allocate_trb_pool(priv_ep); + if (ret) + goto exit; + + bEndpointAddress = priv_ep->num | priv_ep->dir; + cdns3_select_ep(priv_dev, bEndpointAddress); + + trace_cdns3_gadget_ep_enable(priv_ep); + + writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd); + + ret = readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, + !(val & (EP_CMD_CSTALL | EP_CMD_EPRST)), + 1, 1000); + + if (unlikely(ret)) { + cdns3_free_trb_pool(priv_ep); + ret = -EINVAL; + goto exit; + } + + /* enable interrupt for selected endpoint */ + cdns3_set_register_bit(&priv_dev->regs->ep_ien, + BIT(cdns3_ep_addr_to_index(bEndpointAddress))); + + if (priv_dev->dev_ver < DEV_VER_V2) + cdns3_wa2_enable_detection(priv_dev, priv_ep, reg); + + writel(reg, &priv_dev->regs->ep_sts_en); + + ep->desc = desc; + priv_ep->flags &= ~(EP_PENDING_REQUEST | EP_STALLED | EP_STALL_PENDING | + EP_QUIRK_ISO_OUT_EN | EP_QUIRK_EXTRA_BUF_EN); + priv_ep->flags |= EP_ENABLED | EP_UPDATE_EP_TRBADDR; + priv_ep->wa1_set = 0; + priv_ep->enqueue = 0; + priv_ep->dequeue = 0; + reg = readl(&priv_dev->regs->ep_sts); + priv_ep->pcs = !!EP_STS_CCS(reg); + priv_ep->ccs = !!EP_STS_CCS(reg); + /* one TRB is reserved for link TRB used in DMULT mode*/ + priv_ep->free_trbs = priv_ep->num_trbs - 1; +exit: + spin_unlock_irqrestore(&priv_dev->lock, flags); + + return ret; +} + +/** + * cdns3_gadget_ep_disable - Disable endpoint + * @ep: endpoint object + * + * Returns 0 on success, error code elsewhere + */ +static int cdns3_gadget_ep_disable(struct usb_ep *ep) +{ + struct cdns3_endpoint *priv_ep; + struct cdns3_request *priv_req; + struct cdns3_device *priv_dev; + struct usb_request *request; + unsigned long flags; + int ret = 0; + u32 ep_cfg; + int val; + + if (!ep) { + pr_err("usbss: invalid parameters\n"); + return -EINVAL; + } + + priv_ep = ep_to_cdns3_ep(ep); + priv_dev = priv_ep->cdns3_dev; + + if (dev_WARN_ONCE(priv_dev->dev, !(priv_ep->flags & EP_ENABLED), + "%s is already disabled\n", priv_ep->name)) + return 0; + + spin_lock_irqsave(&priv_dev->lock, flags); + + trace_cdns3_gadget_ep_disable(priv_ep); + + cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress); + + ep_cfg = readl(&priv_dev->regs->ep_cfg); + ep_cfg &= ~EP_CFG_ENABLE; + writel(ep_cfg, &priv_dev->regs->ep_cfg); + + /** + * Driver needs some time before resetting endpoint. + * It need waits for clearing DBUSY bit or for timeout expired. + * 10us is enough time for controller to stop transfer. + */ + readl_poll_timeout_atomic(&priv_dev->regs->ep_sts, val, + !(val & EP_STS_DBUSY), 1, 10); + writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd); + + readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, + !(val & (EP_CMD_CSTALL | EP_CMD_EPRST)), + 1, 1000); + if (unlikely(ret)) + dev_err(priv_dev->dev, "Timeout: %s resetting failed.\n", + priv_ep->name); + + while (!list_empty(&priv_ep->pending_req_list)) { + request = cdns3_next_request(&priv_ep->pending_req_list); + + cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), + -ESHUTDOWN); + } + + while (!list_empty(&priv_ep->wa2_descmiss_req_list)) { + priv_req = cdns3_next_priv_request(&priv_ep->wa2_descmiss_req_list); + list_del_init(&priv_req->list); + + kfree(priv_req->request.buf); + cdns3_gadget_ep_free_request(&priv_ep->endpoint, + &priv_req->request); + --priv_ep->wa2_counter; + } + + while (!list_empty(&priv_ep->deferred_req_list)) { + request = cdns3_next_request(&priv_ep->deferred_req_list); + + cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), + -ESHUTDOWN); + } + + priv_ep->descmis_req = NULL; + + ep->desc = NULL; + priv_ep->flags &= ~EP_ENABLED; + priv_ep->use_streams = false; + + spin_unlock_irqrestore(&priv_dev->lock, flags); + + return ret; +} + +/** + * __cdns3_gadget_ep_queue - Transfer data on endpoint + * @ep: endpoint object + * @request: request object + * @gfp_flags: gfp flags + * + * Returns 0 on success, error code elsewhere + */ +static int __cdns3_gadget_ep_queue(struct usb_ep *ep, + struct usb_request *request, + gfp_t gfp_flags) +{ + struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct cdns3_request *priv_req; + int ret = 0; + + if (!ep->desc) + return -ESHUTDOWN; + + request->actual = 0; + request->status = -EINPROGRESS; + priv_req = to_cdns3_request(request); + trace_cdns3_ep_queue(priv_req); + + if (priv_dev->dev_ver < DEV_VER_V2) { + ret = cdns3_wa2_gadget_ep_queue(priv_dev, priv_ep, + priv_req); + + if (ret == EINPROGRESS) + return 0; + } + + ret = cdns3_prepare_aligned_request_buf(priv_req); + if (ret < 0) + return ret; + + if (likely(!(priv_req->flags & REQUEST_UNALIGNED))) { + ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request, + usb_endpoint_dir_in(ep->desc)); + if (ret) + return ret; + } + + list_add_tail(&request->list, &priv_ep->deferred_req_list); + + /* + * For stream capable endpoint if prime irq flag is set then only start + * request. + * If hardware endpoint configuration has not been set yet then + * just queue request in deferred list. Transfer will be started in + * cdns3_set_hw_configuration. + */ + if (!request->stream_id) { + if (priv_dev->hw_configured_flag && + !(priv_ep->flags & EP_STALLED) && + !(priv_ep->flags & EP_STALL_PENDING)) + cdns3_start_all_request(priv_dev, priv_ep); + } else { + if (priv_dev->hw_configured_flag && priv_ep->prime_flag) + cdns3_start_all_request(priv_dev, priv_ep); + } + + return 0; +} + +static int cdns3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, + gfp_t gfp_flags) +{ + struct usb_request *zlp_request; + struct cdns3_endpoint *priv_ep; + struct cdns3_device *priv_dev; + unsigned long flags; + int ret; + + if (!request || !ep) + return -EINVAL; + + priv_ep = ep_to_cdns3_ep(ep); + priv_dev = priv_ep->cdns3_dev; + + spin_lock_irqsave(&priv_dev->lock, flags); + + ret = __cdns3_gadget_ep_queue(ep, request, gfp_flags); + + if (ret == 0 && request->zero && request->length && + (request->length % ep->maxpacket == 0)) { + struct cdns3_request *priv_req; + + zlp_request = cdns3_gadget_ep_alloc_request(ep, GFP_ATOMIC); + zlp_request->buf = priv_dev->zlp_buf; + zlp_request->length = 0; + + priv_req = to_cdns3_request(zlp_request); + priv_req->flags |= REQUEST_ZLP; + + dev_dbg(priv_dev->dev, "Queuing ZLP for endpoint: %s\n", + priv_ep->name); + ret = __cdns3_gadget_ep_queue(ep, zlp_request, gfp_flags); + } + + spin_unlock_irqrestore(&priv_dev->lock, flags); + return ret; +} + +/** + * cdns3_gadget_ep_dequeue - Remove request from transfer queue + * @ep: endpoint object associated with request + * @request: request object + * + * Returns 0 on success, error code elsewhere + */ +int cdns3_gadget_ep_dequeue(struct usb_ep *ep, + struct usb_request *request) +{ + struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); + struct cdns3_device *priv_dev; + struct usb_request *req, *req_temp; + struct cdns3_request *priv_req; + struct cdns3_trb *link_trb; + u8 req_on_hw_ring = 0; + unsigned long flags; + int ret = 0; + int val; + + if (!ep || !request || !ep->desc) + return -EINVAL; + + priv_dev = priv_ep->cdns3_dev; + + spin_lock_irqsave(&priv_dev->lock, flags); + + priv_req = to_cdns3_request(request); + + trace_cdns3_ep_dequeue(priv_req); + + cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress); + + list_for_each_entry_safe(req, req_temp, &priv_ep->pending_req_list, + list) { + if (request == req) { + req_on_hw_ring = 1; + goto found; + } + } + + list_for_each_entry_safe(req, req_temp, &priv_ep->deferred_req_list, + list) { + if (request == req) + goto found; + } + + goto not_found; + +found: + link_trb = priv_req->trb; + + /* Update ring only if removed request is on pending_req_list list */ + if (req_on_hw_ring && link_trb) { + /* Stop DMA */ + writel(EP_CMD_DFLUSH, &priv_dev->regs->ep_cmd); + + /* wait for DFLUSH cleared */ + readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, + !(val & EP_CMD_DFLUSH), 1, 1000); + + link_trb->buffer = cpu_to_le32(TRB_BUFFER(priv_ep->trb_pool_dma + + ((priv_req->end_trb + 1) * TRB_SIZE))); + link_trb->control = cpu_to_le32((le32_to_cpu(link_trb->control) & TRB_CYCLE) | + TRB_TYPE(TRB_LINK) | TRB_CHAIN); + + if (priv_ep->wa1_trb == priv_req->trb) + cdns3_wa1_restore_cycle_bit(priv_ep); + } + + cdns3_gadget_giveback(priv_ep, priv_req, -ECONNRESET); + + req = cdns3_next_request(&priv_ep->pending_req_list); + if (req) + cdns3_rearm_transfer(priv_ep, 1); + +not_found: + spin_unlock_irqrestore(&priv_dev->lock, flags); + return ret; +} + +/** + * __cdns3_gadget_ep_set_halt - Sets stall on selected endpoint + * Should be called after acquiring spin_lock and selecting ep + * @priv_ep: endpoint object to set stall on. + */ +void __cdns3_gadget_ep_set_halt(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + + trace_cdns3_halt(priv_ep, 1, 0); + + if (!(priv_ep->flags & EP_STALLED)) { + u32 ep_sts_reg = readl(&priv_dev->regs->ep_sts); + + if (!(ep_sts_reg & EP_STS_DBUSY)) + cdns3_ep_stall_flush(priv_ep); + else + priv_ep->flags |= EP_STALL_PENDING; + } +} + +/** + * __cdns3_gadget_ep_clear_halt - Clears stall on selected endpoint + * Should be called after acquiring spin_lock and selecting ep + * @priv_ep: endpoint object to clear stall on + */ +int __cdns3_gadget_ep_clear_halt(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + struct usb_request *request; + struct cdns3_request *priv_req; + struct cdns3_trb *trb = NULL; + struct cdns3_trb trb_tmp; + int ret; + int val; + + trace_cdns3_halt(priv_ep, 0, 0); + + request = cdns3_next_request(&priv_ep->pending_req_list); + if (request) { + priv_req = to_cdns3_request(request); + trb = priv_req->trb; + if (trb) { + trb_tmp = *trb; + trb->control = trb->control ^ cpu_to_le32(TRB_CYCLE); + } + } + + writel(EP_CMD_CSTALL | EP_CMD_EPRST, &priv_dev->regs->ep_cmd); + + /* wait for EPRST cleared */ + ret = readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, + !(val & EP_CMD_EPRST), 1, 100); + if (ret) + return -EINVAL; + + priv_ep->flags &= ~(EP_STALLED | EP_STALL_PENDING); + + if (request) { + if (trb) { + *trb = trb_tmp; + + /* + * Per datasheet, EPRST causes DMA to reposition to the next TD. + * Manually reset EP_TRADDR to the current TRB to prevent + * the hardware from skipping the interrupted request. + */ + writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma + + priv_req->start_trb * TRB_SIZE), + &priv_dev->regs->ep_traddr); + } + + cdns3_rearm_transfer(priv_ep, 1); + } + + cdns3_start_all_request(priv_dev, priv_ep); + return ret; +} + +/** + * cdns3_gadget_ep_set_halt - Sets/clears stall on selected endpoint + * @ep: endpoint object to set/clear stall on + * @value: 1 for set stall, 0 for clear stall + * + * Returns 0 on success, error code elsewhere + */ +int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value) +{ + struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + unsigned long flags; + int ret = 0; + + if (!(priv_ep->flags & EP_ENABLED)) + return -EPERM; + + spin_lock_irqsave(&priv_dev->lock, flags); + + cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress); + + if (!value) { + priv_ep->flags &= ~EP_WEDGE; + ret = __cdns3_gadget_ep_clear_halt(priv_ep); + } else { + __cdns3_gadget_ep_set_halt(priv_ep); + } + + spin_unlock_irqrestore(&priv_dev->lock, flags); + + return ret; +} + +extern const struct usb_ep_ops cdns3_gadget_ep0_ops; + +static const struct usb_ep_ops cdns3_gadget_ep_ops = { + .enable = cdns3_gadget_ep_enable, + .disable = cdns3_gadget_ep_disable, + .alloc_request = cdns3_gadget_ep_alloc_request, + .free_request = cdns3_gadget_ep_free_request, + .queue = cdns3_gadget_ep_queue, + .dequeue = cdns3_gadget_ep_dequeue, + .set_halt = cdns3_gadget_ep_set_halt, + .set_wedge = cdns3_gadget_ep_set_wedge, +}; + +/** + * cdns3_gadget_get_frame - Returns number of actual ITP frame + * @gadget: gadget object + * + * Returns number of actual ITP frame + */ +static int cdns3_gadget_get_frame(struct usb_gadget *gadget) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + + return readl(&priv_dev->regs->usb_itpn); +} + +int __cdns3_gadget_wakeup(struct cdns3_device *priv_dev) +{ + enum usb_device_speed speed; + + speed = cdns3_get_speed(priv_dev); + + if (speed >= USB_SPEED_SUPER) + return 0; + + /* Start driving resume signaling to indicate remote wakeup. */ + writel(USB_CONF_LGO_L0, &priv_dev->regs->usb_conf); + + return 0; +} + +static int cdns3_gadget_wakeup(struct usb_gadget *gadget) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&priv_dev->lock, flags); + ret = __cdns3_gadget_wakeup(priv_dev); + spin_unlock_irqrestore(&priv_dev->lock, flags); + return ret; +} + +static int cdns3_gadget_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + unsigned long flags; + + spin_lock_irqsave(&priv_dev->lock, flags); + priv_dev->is_selfpowered = !!is_selfpowered; + spin_unlock_irqrestore(&priv_dev->lock, flags); + return 0; +} + +static int cdns3_gadget_pullup(struct usb_gadget *gadget, int is_on) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + + if (is_on) { + writel(USB_CONF_DEVEN, &priv_dev->regs->usb_conf); + } else { + writel(~0, &priv_dev->regs->ep_ists); + writel(~0, &priv_dev->regs->usb_ists); + writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf); + } + + return 0; +} + +static void cdns3_gadget_config(struct cdns3_device *priv_dev) +{ + struct cdns3_usb_regs __iomem *regs = priv_dev->regs; + u32 reg; + + cdns3_ep0_config(priv_dev); + + /* enable interrupts for endpoint 0 (in and out) */ + writel(EP_IEN_EP_OUT0 | EP_IEN_EP_IN0, ®s->ep_ien); + + /* + * Driver needs to modify LFPS minimal U1 Exit time for DEV_VER_TI_V1 + * revision of controller. + */ + if (priv_dev->dev_ver == DEV_VER_TI_V1) { + reg = readl(®s->dbg_link1); + + reg &= ~DBG_LINK1_LFPS_MIN_GEN_U1_EXIT_MASK; + reg |= DBG_LINK1_LFPS_MIN_GEN_U1_EXIT(0x55) | + DBG_LINK1_LFPS_MIN_GEN_U1_EXIT_SET; + writel(reg, ®s->dbg_link1); + } + + /* + * By default some platforms has set protected access to memory. + * This cause problem with cache, so driver restore non-secure + * access to memory. + */ + reg = readl(®s->dma_axi_ctrl); + reg |= DMA_AXI_CTRL_MARPROT(DMA_AXI_CTRL_NON_SECURE) | + DMA_AXI_CTRL_MAWPROT(DMA_AXI_CTRL_NON_SECURE); + writel(reg, ®s->dma_axi_ctrl); + + /* enable generic interrupt*/ + writel(USB_IEN_INIT, ®s->usb_ien); + writel(USB_CONF_CLK2OFFDS | USB_CONF_L1DS, ®s->usb_conf); + /* keep Fast Access bit */ + writel(PUSB_PWR_FST_REG_ACCESS, &priv_dev->regs->usb_pwr); + + cdns3_configure_dmult(priv_dev, NULL); +} + +/** + * cdns3_gadget_udc_start - Gadget start + * @gadget: gadget object + * @driver: driver which operates on this gadget + * + * Returns 0 on success, error code elsewhere + */ +static int cdns3_gadget_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + unsigned long flags; + enum usb_device_speed max_speed = driver->max_speed; + + spin_lock_irqsave(&priv_dev->lock, flags); + priv_dev->gadget_driver = driver; + + /* limit speed if necessary */ + max_speed = min(driver->max_speed, gadget->max_speed); + + switch (max_speed) { + case USB_SPEED_FULL: + writel(USB_CONF_SFORCE_FS, &priv_dev->regs->usb_conf); + writel(USB_CONF_USB3DIS, &priv_dev->regs->usb_conf); + break; + case USB_SPEED_HIGH: + writel(USB_CONF_USB3DIS, &priv_dev->regs->usb_conf); + break; + case USB_SPEED_SUPER: + break; + default: + dev_err(priv_dev->dev, + "invalid maximum_speed parameter %d\n", + max_speed); + fallthrough; + case USB_SPEED_UNKNOWN: + /* default to superspeed */ + max_speed = USB_SPEED_SUPER; + break; + } + + cdns3_gadget_config(priv_dev); + spin_unlock_irqrestore(&priv_dev->lock, flags); + return 0; +} + +/** + * cdns3_gadget_udc_stop - Stops gadget + * @gadget: gadget object + * + * Returns 0 + */ +static int cdns3_gadget_udc_stop(struct usb_gadget *gadget) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + struct cdns3_endpoint *priv_ep; + u32 bEndpointAddress; + struct usb_ep *ep; + int val; + + priv_dev->gadget_driver = NULL; + + priv_dev->onchip_used_size = 0; + priv_dev->out_mem_is_allocated = 0; + priv_dev->gadget.speed = USB_SPEED_UNKNOWN; + + list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) { + priv_ep = ep_to_cdns3_ep(ep); + bEndpointAddress = priv_ep->num | priv_ep->dir; + cdns3_select_ep(priv_dev, bEndpointAddress); + writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd); + readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, + !(val & EP_CMD_EPRST), 1, 100); + + priv_ep->flags &= ~EP_CLAIMED; + } + + /* disable interrupt for device */ + writel(0, &priv_dev->regs->usb_ien); + writel(0, &priv_dev->regs->usb_pwr); + writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf); + + return 0; +} + +/** + * cdns3_gadget_check_config - ensure cdns3 can support the USB configuration + * @gadget: pointer to the USB gadget + * + * Used to record the maximum number of endpoints being used in a USB composite + * device. (across all configurations) This is to be used in the calculation + * of the TXFIFO sizes when resizing internal memory for individual endpoints. + * It will help ensured that the resizing logic reserves enough space for at + * least one max packet. + */ +static int cdns3_gadget_check_config(struct usb_gadget *gadget) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + struct cdns3_endpoint *priv_ep; + struct usb_ep *ep; + int n_in = 0; + int iso = 0; + int out = 1; + int total; + int n; + + list_for_each_entry(ep, &gadget->ep_list, ep_list) { + priv_ep = ep_to_cdns3_ep(ep); + if (!(priv_ep->flags & EP_CLAIMED)) + continue; + + n = (priv_ep->mult + 1) * (priv_ep->bMaxBurst + 1); + if (ep->address & USB_DIR_IN) { + /* + * ISO transfer: DMA start move data when get ISO, only transfer + * data as min(TD size, iso). No benefit for allocate bigger + * internal memory than 'iso'. + */ + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC) + iso += n; + else + n_in++; + } else { + if (priv_ep->type == USB_ENDPOINT_XFER_ISOC) + out = max_t(int, out, n); + } + } + + /* 2KB are reserved for EP0, 1KB for out*/ + total = 2 + n_in + out + iso; + + if (total > priv_dev->onchip_buffers) + return -ENOMEM; + + priv_dev->ep_buf_size = (priv_dev->onchip_buffers - 2 - iso) / (n_in + out); + + return 0; +} + +static const struct usb_gadget_ops cdns3_gadget_ops = { + .get_frame = cdns3_gadget_get_frame, + .wakeup = cdns3_gadget_wakeup, + .set_selfpowered = cdns3_gadget_set_selfpowered, + .pullup = cdns3_gadget_pullup, + .udc_start = cdns3_gadget_udc_start, + .udc_stop = cdns3_gadget_udc_stop, + .match_ep = cdns3_gadget_match_ep, + .check_config = cdns3_gadget_check_config, +}; + +static void cdns3_free_all_eps(struct cdns3_device *priv_dev) +{ + int i; + + /* ep0 OUT point to ep0 IN. */ + priv_dev->eps[16] = NULL; + + for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) + if (priv_dev->eps[i]) { + cdns3_free_trb_pool(priv_dev->eps[i]); + devm_kfree(priv_dev->dev, priv_dev->eps[i]); + } +} + +/** + * cdns3_init_eps - Initializes software endpoints of gadget + * @priv_dev: extended gadget object + * + * Returns 0 on success, error code elsewhere + */ +static int cdns3_init_eps(struct cdns3_device *priv_dev) +{ + u32 ep_enabled_reg, iso_ep_reg; + struct cdns3_endpoint *priv_ep; + int ep_dir, ep_number; + u32 ep_mask; + int ret = 0; + int i; + + /* Read it from USB_CAP3 to USB_CAP5 */ + ep_enabled_reg = readl(&priv_dev->regs->usb_cap3); + iso_ep_reg = readl(&priv_dev->regs->usb_cap4); + + dev_dbg(priv_dev->dev, "Initializing non-zero endpoints\n"); + + for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) { + ep_dir = i >> 4; /* i div 16 */ + ep_number = i & 0xF; /* i % 16 */ + ep_mask = BIT(i); + + if (!(ep_enabled_reg & ep_mask)) + continue; + + if (ep_dir && !ep_number) { + priv_dev->eps[i] = priv_dev->eps[0]; + continue; + } + + priv_ep = devm_kzalloc(priv_dev->dev, sizeof(*priv_ep), + GFP_KERNEL); + if (!priv_ep) + goto err; + + /* set parent of endpoint object */ + priv_ep->cdns3_dev = priv_dev; + priv_dev->eps[i] = priv_ep; + priv_ep->num = ep_number; + priv_ep->dir = ep_dir ? USB_DIR_IN : USB_DIR_OUT; + + if (!ep_number) { + ret = cdns3_init_ep0(priv_dev, priv_ep); + if (ret) { + dev_err(priv_dev->dev, "Failed to init ep0\n"); + goto err; + } + } else { + snprintf(priv_ep->name, sizeof(priv_ep->name), "ep%d%s", + ep_number, !!ep_dir ? "in" : "out"); + priv_ep->endpoint.name = priv_ep->name; + + usb_ep_set_maxpacket_limit(&priv_ep->endpoint, + CDNS3_EP_MAX_PACKET_LIMIT); + priv_ep->endpoint.max_streams = CDNS3_EP_MAX_STREAMS; + priv_ep->endpoint.ops = &cdns3_gadget_ep_ops; + if (ep_dir) + priv_ep->endpoint.caps.dir_in = 1; + else + priv_ep->endpoint.caps.dir_out = 1; + + if (iso_ep_reg & ep_mask) + priv_ep->endpoint.caps.type_iso = 1; + + priv_ep->endpoint.caps.type_bulk = 1; + priv_ep->endpoint.caps.type_int = 1; + + list_add_tail(&priv_ep->endpoint.ep_list, + &priv_dev->gadget.ep_list); + } + + priv_ep->flags = 0; + + dev_dbg(priv_dev->dev, "Initialized %s support: %s %s\n", + priv_ep->name, + priv_ep->endpoint.caps.type_bulk ? "BULK, INT" : "", + priv_ep->endpoint.caps.type_iso ? "ISO" : ""); + + INIT_LIST_HEAD(&priv_ep->pending_req_list); + INIT_LIST_HEAD(&priv_ep->deferred_req_list); + INIT_LIST_HEAD(&priv_ep->wa2_descmiss_req_list); + } + + return 0; +err: + cdns3_free_all_eps(priv_dev); + return -ENOMEM; +} + +static void cdns3_gadget_release(struct device *dev) +{ + struct cdns3_device *priv_dev = container_of(dev, + struct cdns3_device, gadget.dev); + + kfree(priv_dev); +} + +static void cdns3_gadget_exit(struct cdns *cdns) +{ + struct cdns3_device *priv_dev; + + priv_dev = cdns->gadget_dev; + + + pm_runtime_put_autosuspend(cdns->dev); + + usb_del_gadget(&priv_dev->gadget); + devm_free_irq(cdns->dev, cdns->dev_irq, priv_dev); + + cdns3_free_all_eps(priv_dev); + + while (!list_empty(&priv_dev->aligned_buf_list)) { + struct cdns3_aligned_buf *buf; + + buf = cdns3_next_align_buf(&priv_dev->aligned_buf_list); + dma_free_noncoherent(priv_dev->sysdev, buf->size, + buf->buf, + buf->dma, + buf->dir); + + list_del(&buf->list); + kfree(buf); + } + + dma_free_coherent(priv_dev->sysdev, 8, priv_dev->setup_buf, + priv_dev->setup_dma); + dma_pool_destroy(priv_dev->eps_dma_pool); + + kfree(priv_dev->zlp_buf); + usb_put_gadget(&priv_dev->gadget); + cdns->gadget_dev = NULL; + cdns_drd_gadget_off(cdns); +} + +static int cdns3_gadget_start(struct cdns *cdns) +{ + struct cdns3_device *priv_dev; + u32 max_speed; + int ret; + + priv_dev = kzalloc_obj(*priv_dev); + if (!priv_dev) + return -ENOMEM; + + usb_initialize_gadget(cdns->dev, &priv_dev->gadget, + cdns3_gadget_release); + cdns->gadget_dev = priv_dev; + priv_dev->sysdev = cdns->dev; + priv_dev->dev = cdns->dev; + priv_dev->regs = cdns->dev_regs; + + device_property_read_u16(priv_dev->dev, "cdns,on-chip-buff-size", + &priv_dev->onchip_buffers); + + if (priv_dev->onchip_buffers <= 0) { + u32 reg = readl(&priv_dev->regs->usb_cap2); + + priv_dev->onchip_buffers = USB_CAP2_ACTUAL_MEM_SIZE(reg); + } + + if (!priv_dev->onchip_buffers) + priv_dev->onchip_buffers = 256; + + max_speed = usb_get_maximum_speed(cdns->dev); + + /* Check the maximum_speed parameter */ + switch (max_speed) { + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + case USB_SPEED_SUPER: + break; + default: + dev_err(cdns->dev, "invalid maximum_speed parameter %d\n", + max_speed); + fallthrough; + case USB_SPEED_UNKNOWN: + /* default to superspeed */ + max_speed = USB_SPEED_SUPER; + break; + } + + /* fill gadget fields */ + priv_dev->gadget.max_speed = max_speed; + priv_dev->gadget.speed = USB_SPEED_UNKNOWN; + priv_dev->gadget.ops = &cdns3_gadget_ops; + priv_dev->gadget.name = "usb-ss-gadget"; + priv_dev->gadget.quirk_avoids_skb_reserve = 1; + priv_dev->gadget.irq = cdns->dev_irq; + + spin_lock_init(&priv_dev->lock); + INIT_WORK(&priv_dev->pending_status_wq, + cdns3_pending_setup_status_handler); + + INIT_WORK(&priv_dev->aligned_buf_wq, + cdns3_free_aligned_request_buf); + + /* initialize endpoint container */ + INIT_LIST_HEAD(&priv_dev->gadget.ep_list); + INIT_LIST_HEAD(&priv_dev->aligned_buf_list); + priv_dev->eps_dma_pool = dma_pool_create("cdns3_eps_dma_pool", + priv_dev->sysdev, + TRB_RING_SIZE, 8, 0); + if (!priv_dev->eps_dma_pool) { + dev_err(priv_dev->dev, "Failed to create TRB dma pool\n"); + ret = -ENOMEM; + goto err1; + } + + ret = cdns3_init_eps(priv_dev); + if (ret) { + dev_err(priv_dev->dev, "Failed to create endpoints\n"); + goto err1; + } + + /* allocate memory for setup packet buffer */ + priv_dev->setup_buf = dma_alloc_coherent(priv_dev->sysdev, 8, + &priv_dev->setup_dma, GFP_DMA); + if (!priv_dev->setup_buf) { + ret = -ENOMEM; + goto err2; + } + + priv_dev->dev_ver = readl(&priv_dev->regs->usb_cap6); + + dev_dbg(priv_dev->dev, "Device Controller version: %08x\n", + readl(&priv_dev->regs->usb_cap6)); + dev_dbg(priv_dev->dev, "USB Capabilities:: %08x\n", + readl(&priv_dev->regs->usb_cap1)); + dev_dbg(priv_dev->dev, "On-Chip memory configuration: %08x\n", + readl(&priv_dev->regs->usb_cap2)); + + priv_dev->dev_ver = GET_DEV_BASE_VERSION(priv_dev->dev_ver); + if (priv_dev->dev_ver >= DEV_VER_V2) + priv_dev->gadget.sg_supported = 1; + + priv_dev->zlp_buf = kzalloc(CDNS3_EP_ZLP_BUF_SIZE, GFP_KERNEL); + if (!priv_dev->zlp_buf) { + ret = -ENOMEM; + goto err3; + } + + /* add USB gadget device */ + ret = usb_add_gadget(&priv_dev->gadget); + if (ret < 0) { + dev_err(priv_dev->dev, "Failed to add gadget\n"); + goto err4; + } + + return 0; +err4: + kfree(priv_dev->zlp_buf); +err3: + dma_free_coherent(priv_dev->sysdev, 8, priv_dev->setup_buf, + priv_dev->setup_dma); +err2: + cdns3_free_all_eps(priv_dev); +err1: + dma_pool_destroy(priv_dev->eps_dma_pool); + + usb_put_gadget(&priv_dev->gadget); + cdns->gadget_dev = NULL; + return ret; +} + +static int __cdns3_gadget_init(struct cdns *cdns) +{ + int ret = 0; + + /* Ensure 32-bit DMA Mask in case we switched back from Host mode */ + ret = dma_set_mask_and_coherent(cdns->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(cdns->dev, "Failed to set dma mask: %d\n", ret); + return ret; + } + + cdns_drd_gadget_on(cdns); + pm_runtime_get_sync(cdns->dev); + + ret = cdns3_gadget_start(cdns); + if (ret) { + pm_runtime_put_sync(cdns->dev); + cdns_drd_gadget_off(cdns); + return ret; + } + + /* + * Because interrupt line can be shared with other components in + * driver it can't use IRQF_ONESHOT flag here. + */ + ret = devm_request_threaded_irq(cdns->dev, cdns->dev_irq, + cdns3_device_irq_handler, + cdns3_device_thread_irq_handler, + IRQF_SHARED, dev_name(cdns->dev), + cdns->gadget_dev); + + if (ret) + goto err0; + + return 0; +err0: + cdns3_gadget_exit(cdns); + return ret; +} + +static int cdns3_gadget_suspend(struct cdns *cdns, bool do_wakeup) +__must_hold(&cdns->lock) +{ + struct cdns3_device *priv_dev = cdns->gadget_dev; + + spin_unlock(&cdns->lock); + cdns3_disconnect_gadget(priv_dev); + spin_lock(&cdns->lock); + + priv_dev->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&priv_dev->gadget, USB_STATE_NOTATTACHED); + cdns3_hw_reset_eps_config(priv_dev); + + /* disable interrupt for device */ + writel(0, &priv_dev->regs->usb_ien); + + return 0; +} + +static int cdns3_gadget_resume(struct cdns *cdns, bool lost_power) +{ + struct cdns3_device *priv_dev = cdns->gadget_dev; + + if (!priv_dev->gadget_driver) + return 0; + + cdns3_gadget_config(priv_dev); + if (lost_power) + writel(USB_CONF_DEVEN, &priv_dev->regs->usb_conf); + + return 0; +} + +/** + * cdns3_gadget_init - initialize device structure + * + * @cdns: cdns instance + * + * This function initializes the gadget. + */ +int cdns3_gadget_init(struct cdns *cdns) +{ + struct cdns_role_driver *rdrv; + + rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + rdrv->start = __cdns3_gadget_init; + rdrv->stop = cdns3_gadget_exit; + rdrv->suspend = cdns3_gadget_suspend; + rdrv->resume = cdns3_gadget_resume; + rdrv->state = CDNS_ROLE_STATE_INACTIVE; + rdrv->name = "gadget"; + cdns->roles[USB_ROLE_DEVICE] = rdrv; + + return 0; +} +EXPORT_SYMBOL_GPL(cdns3_gadget_init); diff --git a/drivers/usb/cdns3/cdns3-gadget.h b/drivers/usb/cdns3/cdns3-gadget.h new file mode 100644 index 000000000..086a7bb83 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-gadget.h @@ -0,0 +1,1377 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * USBSS device controller driver header file + * + * Copyright (C) 2018-2019 Cadence. + * Copyright (C) 2017-2018 NXP + * + * Author: Pawel Laszczak + * Pawel Jez + * Peter Chen + */ +#ifndef __LINUX_CDNS3_GADGET +#define __LINUX_CDNS3_GADGET +#include +#include + +/* + * USBSS-DEV register interface. + * This corresponds to the USBSS Device Controller Interface + */ + +/** + * struct cdns3_usb_regs - device controller registers. + * @usb_conf: Global Configuration. + * @usb_sts: Global Status. + * @usb_cmd: Global Command. + * @usb_itpn: ITP/SOF number. + * @usb_lpm: Global Command. + * @usb_ien: USB Interrupt Enable. + * @usb_ists: USB Interrupt Status. + * @ep_sel: Endpoint Select. + * @ep_traddr: Endpoint Transfer Ring Address. + * @ep_cfg: Endpoint Configuration. + * @ep_cmd: Endpoint Command. + * @ep_sts: Endpoint Status. + * @ep_sts_sid: Endpoint Status. + * @ep_sts_en: Endpoint Status Enable. + * @drbl: Doorbell. + * @ep_ien: EP Interrupt Enable. + * @ep_ists: EP Interrupt Status. + * @usb_pwr: Global Power Configuration. + * @usb_conf2: Global Configuration 2. + * @usb_cap1: Capability 1. + * @usb_cap2: Capability 2. + * @usb_cap3: Capability 3. + * @usb_cap4: Capability 4. + * @usb_cap5: Capability 5. + * @usb_cap6: Capability 6. + * @usb_cpkt1: Custom Packet 1. + * @usb_cpkt2: Custom Packet 2. + * @usb_cpkt3: Custom Packet 3. + * @ep_dma_ext_addr: Upper address for DMA operations. + * @buf_addr: Address for On-chip Buffer operations. + * @buf_data: Data for On-chip Buffer operations. + * @buf_ctrl: On-chip Buffer Access Control. + * @dtrans: DMA Transfer Mode. + * @tdl_from_trb: Source of TD Configuration. + * @tdl_beh: TDL Behavior Configuration. + * @ep_tdl: Endpoint TDL. + * @tdl_beh2: TDL Behavior 2 Configuration. + * @dma_adv_td: DMA Advance TD Configuration. + * @reserved1: Reserved. + * @cfg_regs: Configuration. + * @reserved2: Reserved. + * @dma_axi_ctrl: AXI Control. + * @dma_axi_id: AXI ID register. + * @dma_axi_cap: AXI Capability. + * @dma_axi_ctrl0: AXI Control 0. + * @dma_axi_ctrl1: AXI Control 1. + */ +struct cdns3_usb_regs { + __le32 usb_conf; + __le32 usb_sts; + __le32 usb_cmd; + __le32 usb_itpn; + __le32 usb_lpm; + __le32 usb_ien; + __le32 usb_ists; + __le32 ep_sel; + __le32 ep_traddr; + __le32 ep_cfg; + __le32 ep_cmd; + __le32 ep_sts; + __le32 ep_sts_sid; + __le32 ep_sts_en; + __le32 drbl; + __le32 ep_ien; + __le32 ep_ists; + __le32 usb_pwr; + __le32 usb_conf2; + __le32 usb_cap1; + __le32 usb_cap2; + __le32 usb_cap3; + __le32 usb_cap4; + __le32 usb_cap5; + __le32 usb_cap6; + __le32 usb_cpkt1; + __le32 usb_cpkt2; + __le32 usb_cpkt3; + __le32 ep_dma_ext_addr; + __le32 buf_addr; + __le32 buf_data; + __le32 buf_ctrl; + __le32 dtrans; + __le32 tdl_from_trb; + __le32 tdl_beh; + __le32 ep_tdl; + __le32 tdl_beh2; + __le32 dma_adv_td; + __le32 reserved1[26]; + __le32 cfg_reg1; + __le32 dbg_link1; + __le32 dbg_link2; + __le32 cfg_regs[74]; + __le32 reserved2[51]; + __le32 dma_axi_ctrl; + __le32 dma_axi_id; + __le32 dma_axi_cap; + __le32 dma_axi_ctrl0; + __le32 dma_axi_ctrl1; +}; + +/* USB_CONF - bitmasks */ +/* Reset USB device configuration. */ +#define USB_CONF_CFGRST BIT(0) +/* Set Configuration. */ +#define USB_CONF_CFGSET BIT(1) +/* Disconnect USB device in SuperSpeed. */ +#define USB_CONF_USB3DIS BIT(3) +/* Disconnect USB device in HS/FS */ +#define USB_CONF_USB2DIS BIT(4) +/* Little Endian access - default */ +#define USB_CONF_LENDIAN BIT(5) +/* + * Big Endian access. Driver assume that byte order for + * SFRs access always is as Little Endian so this bit + * is not used. + */ +#define USB_CONF_BENDIAN BIT(6) +/* Device software reset. */ +#define USB_CONF_SWRST BIT(7) +/* Singular DMA transfer mode. Only for VER < DEV_VER_V3*/ +#define USB_CONF_DSING BIT(8) +/* Multiple DMA transfers mode. Only for VER < DEV_VER_V3 */ +#define USB_CONF_DMULT BIT(9) +/* DMA clock turn-off enable. */ +#define USB_CONF_DMAOFFEN BIT(10) +/* DMA clock turn-off disable. */ +#define USB_CONF_DMAOFFDS BIT(11) +/* Clear Force Full Speed. */ +#define USB_CONF_CFORCE_FS BIT(12) +/* Set Force Full Speed. */ +#define USB_CONF_SFORCE_FS BIT(13) +/* Device enable. */ +#define USB_CONF_DEVEN BIT(14) +/* Device disable. */ +#define USB_CONF_DEVDS BIT(15) +/* L1 LPM state entry enable (used in HS/FS mode). */ +#define USB_CONF_L1EN BIT(16) +/* L1 LPM state entry disable (used in HS/FS mode). */ +#define USB_CONF_L1DS BIT(17) +/* USB 2.0 clock gate disable. */ +#define USB_CONF_CLK2OFFEN BIT(18) +/* USB 2.0 clock gate enable. */ +#define USB_CONF_CLK2OFFDS BIT(19) +/* L0 LPM state entry request (used in HS/FS mode). */ +#define USB_CONF_LGO_L0 BIT(20) +/* USB 3.0 clock gate disable. */ +#define USB_CONF_CLK3OFFEN BIT(21) +/* USB 3.0 clock gate enable. */ +#define USB_CONF_CLK3OFFDS BIT(22) +/* Bit 23 is reserved*/ +/* U1 state entry enable (used in SS mode). */ +#define USB_CONF_U1EN BIT(24) +/* U1 state entry disable (used in SS mode). */ +#define USB_CONF_U1DS BIT(25) +/* U2 state entry enable (used in SS mode). */ +#define USB_CONF_U2EN BIT(26) +/* U2 state entry disable (used in SS mode). */ +#define USB_CONF_U2DS BIT(27) +/* U0 state entry request (used in SS mode). */ +#define USB_CONF_LGO_U0 BIT(28) +/* U1 state entry request (used in SS mode). */ +#define USB_CONF_LGO_U1 BIT(29) +/* U2 state entry request (used in SS mode). */ +#define USB_CONF_LGO_U2 BIT(30) +/* SS.Inactive state entry request (used in SS mode) */ +#define USB_CONF_LGO_SSINACT BIT(31) + +/* USB_STS - bitmasks */ +/* + * Configuration status. + * 1 - device is in the configured state. + * 0 - device is not configured. + */ +#define USB_STS_CFGSTS_MASK BIT(0) +#define USB_STS_CFGSTS(p) ((p) & USB_STS_CFGSTS_MASK) +/* + * On-chip memory overflow. + * 0 - On-chip memory status OK. + * 1 - On-chip memory overflow. + */ +#define USB_STS_OV_MASK BIT(1) +#define USB_STS_OV(p) ((p) & USB_STS_OV_MASK) +/* + * SuperSpeed connection status. + * 0 - USB in SuperSpeed mode disconnected. + * 1 - USB in SuperSpeed mode connected. + */ +#define USB_STS_USB3CONS_MASK BIT(2) +#define USB_STS_USB3CONS(p) ((p) & USB_STS_USB3CONS_MASK) +/* + * DMA transfer configuration status. + * 0 - single request. + * 1 - multiple TRB chain + * Supported only for controller version < DEV_VER_V3 + */ +#define USB_STS_DTRANS_MASK BIT(3) +#define USB_STS_DTRANS(p) ((p) & USB_STS_DTRANS_MASK) +/* + * Device speed. + * 0 - Undefined (value after reset). + * 1 - Low speed + * 2 - Full speed + * 3 - High speed + * 4 - Super speed + */ +#define USB_STS_USBSPEED_MASK GENMASK(6, 4) +#define USB_STS_USBSPEED(p) (((p) & USB_STS_USBSPEED_MASK) >> 4) +#define USB_STS_LS (0x1 << 4) +#define USB_STS_FS (0x2 << 4) +#define USB_STS_HS (0x3 << 4) +#define USB_STS_SS (0x4 << 4) +#define DEV_UNDEFSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == (0x0 << 4)) +#define DEV_LOWSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == USB_STS_LS) +#define DEV_FULLSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == USB_STS_FS) +#define DEV_HIGHSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == USB_STS_HS) +#define DEV_SUPERSPEED(p) (((p) & USB_STS_USBSPEED_MASK) == USB_STS_SS) +/* + * Endianness for SFR access. + * 0 - Little Endian order (default after hardware reset). + * 1 - Big Endian order + */ +#define USB_STS_ENDIAN_MASK BIT(7) +#define USB_STS_ENDIAN(p) ((p) & USB_STS_ENDIAN_MASK) +/* + * HS/FS clock turn-off status. + * 0 - hsfs clock is always on. + * 1 - hsfs clock turn-off in L2 (HS/FS mode) is enabled + * (default after hardware reset). + */ +#define USB_STS_CLK2OFF_MASK BIT(8) +#define USB_STS_CLK2OFF(p) ((p) & USB_STS_CLK2OFF_MASK) +/* + * PCLK clock turn-off status. + * 0 - pclk clock is always on. + * 1 - pclk clock turn-off in U3 (SS mode) is enabled + * (default after hardware reset). + */ +#define USB_STS_CLK3OFF_MASK BIT(9) +#define USB_STS_CLK3OFF(p) ((p) & USB_STS_CLK3OFF_MASK) +/* + * Controller in reset state. + * 0 - Internal reset is active. + * 1 - Internal reset is not active and controller is fully operational. + */ +#define USB_STS_IN_RST_MASK BIT(10) +#define USB_STS_IN_RST(p) ((p) & USB_STS_IN_RST_MASK) +/* + * Status of the "TDL calculation basing on TRB" feature. + * 0 - disabled + * 1 - enabled + * Supported only for DEV_VER_V2 controller version. + */ +#define USB_STS_TDL_TRB_ENABLED BIT(11) +/* + * Device enable Status. + * 0 - USB device is disabled (VBUS input is disconnected from internal logic). + * 1 - USB device is enabled (VBUS input is connected to the internal logic). + */ +#define USB_STS_DEVS_MASK BIT(14) +#define USB_STS_DEVS(p) ((p) & USB_STS_DEVS_MASK) +/* + * Address status. + * 0 - USB device is default state. + * 1 - USB device is at least in address state. + */ +#define USB_STS_ADDRESSED_MASK BIT(15) +#define USB_STS_ADDRESSED(p) ((p) & USB_STS_ADDRESSED_MASK) +/* + * L1 LPM state enable status (used in HS/FS mode). + * 0 - Entering to L1 LPM state disabled. + * 1 - Entering to L1 LPM state enabled. + */ +#define USB_STS_L1ENS_MASK BIT(16) +#define USB_STS_L1ENS(p) ((p) & USB_STS_L1ENS_MASK) +/* + * Internal VBUS connection status (used both in HS/FS and SS mode). + * 0 - internal VBUS is not detected. + * 1 - internal VBUS is detected. + */ +#define USB_STS_VBUSS_MASK BIT(17) +#define USB_STS_VBUSS(p) ((p) & USB_STS_VBUSS_MASK) +/* + * HS/FS LPM state (used in FS/HS mode). + * 0 - L0 State + * 1 - L1 State + * 2 - L2 State + * 3 - L3 State + */ +#define USB_STS_LPMST_MASK GENMASK(19, 18) +#define DEV_L0_STATE(p) (((p) & USB_STS_LPMST_MASK) == (0x0 << 18)) +#define DEV_L1_STATE(p) (((p) & USB_STS_LPMST_MASK) == (0x1 << 18)) +#define DEV_L2_STATE(p) (((p) & USB_STS_LPMST_MASK) == (0x2 << 18)) +#define DEV_L3_STATE(p) (((p) & USB_STS_LPMST_MASK) == (0x3 << 18)) +/* + * Disable HS status (used in FS/HS mode). + * 0 - the disconnect bit for HS/FS mode is set . + * 1 - the disconnect bit for HS/FS mode is not set. + */ +#define USB_STS_USB2CONS_MASK BIT(20) +#define USB_STS_USB2CONS(p) ((p) & USB_STS_USB2CONS_MASK) +/* + * HS/FS mode connection status (used in FS/HS mode). + * 0 - High Speed operations in USB2.0 (FS/HS) mode not disabled. + * 1 - High Speed operations in USB2.0 (FS/HS). + */ +#define USB_STS_DISABLE_HS_MASK BIT(21) +#define USB_STS_DISABLE_HS(p) ((p) & USB_STS_DISABLE_HS_MASK) +/* + * U1 state enable status (used in SS mode). + * 0 - Entering to U1 state disabled. + * 1 - Entering to U1 state enabled. + */ +#define USB_STS_U1ENS_MASK BIT(24) +#define USB_STS_U1ENS(p) ((p) & USB_STS_U1ENS_MASK) +/* + * U2 state enable status (used in SS mode). + * 0 - Entering to U2 state disabled. + * 1 - Entering to U2 state enabled. + */ +#define USB_STS_U2ENS_MASK BIT(25) +#define USB_STS_U2ENS(p) ((p) & USB_STS_U2ENS_MASK) +/* + * SuperSpeed Link LTSSM state. This field reflects USBSS-DEV current + * SuperSpeed link state + */ +#define USB_STS_LST_MASK GENMASK(29, 26) +#define DEV_LST_U0 (((p) & USB_STS_LST_MASK) == (0x0 << 26)) +#define DEV_LST_U1 (((p) & USB_STS_LST_MASK) == (0x1 << 26)) +#define DEV_LST_U2 (((p) & USB_STS_LST_MASK) == (0x2 << 26)) +#define DEV_LST_U3 (((p) & USB_STS_LST_MASK) == (0x3 << 26)) +#define DEV_LST_DISABLED (((p) & USB_STS_LST_MASK) == (0x4 << 26)) +#define DEV_LST_RXDETECT (((p) & USB_STS_LST_MASK) == (0x5 << 26)) +#define DEV_LST_INACTIVE (((p) & USB_STS_LST_MASK) == (0x6 << 26)) +#define DEV_LST_POLLING (((p) & USB_STS_LST_MASK) == (0x7 << 26)) +#define DEV_LST_RECOVERY (((p) & USB_STS_LST_MASK) == (0x8 << 26)) +#define DEV_LST_HOT_RESET (((p) & USB_STS_LST_MASK) == (0x9 << 26)) +#define DEV_LST_COMP_MODE (((p) & USB_STS_LST_MASK) == (0xa << 26)) +#define DEV_LST_LB_STATE (((p) & USB_STS_LST_MASK) == (0xb << 26)) +/* + * DMA clock turn-off status. + * 0 - DMA clock is always on (default after hardware reset). + * 1 - DMA clock turn-off in U1, U2 and U3 (SS mode) is enabled. + */ +#define USB_STS_DMAOFF_MASK BIT(30) +#define USB_STS_DMAOFF(p) ((p) & USB_STS_DMAOFF_MASK) +/* + * SFR Endian status. + * 0 - Little Endian order (default after hardware reset). + * 1 - Big Endian order. + */ +#define USB_STS_ENDIAN2_MASK BIT(31) +#define USB_STS_ENDIAN2(p) ((p) & USB_STS_ENDIAN2_MASK) + +/* USB_CMD - bitmasks */ +/* Set Function Address */ +#define USB_CMD_SET_ADDR BIT(0) +/* + * Function Address This field is saved to the device only when the field + * SET_ADDR is set '1 ' during write to USB_CMD register. + * Software is responsible for entering the address of the device during + * SET_ADDRESS request service. This field should be set immediately after + * the SETUP packet is decoded, and prior to confirmation of the status phase + */ +#define USB_CMD_FADDR_MASK GENMASK(7, 1) +#define USB_CMD_FADDR(p) (((p) << 1) & USB_CMD_FADDR_MASK) +/* Send Function Wake Device Notification TP (used only in SS mode). */ +#define USB_CMD_SDNFW BIT(8) +/* Set Test Mode (used only in HS/FS mode). */ +#define USB_CMD_STMODE BIT(9) +/* Test mode selector (used only in HS/FS mode) */ +#define USB_STS_TMODE_SEL_MASK GENMASK(11, 10) +#define USB_STS_TMODE_SEL(p) (((p) << 10) & USB_STS_TMODE_SEL_MASK) +/* + * Send Latency Tolerance Message Device Notification TP (used only + * in SS mode). + */ +#define USB_CMD_SDNLTM BIT(12) +/* Send Custom Transaction Packet (used only in SS mode) */ +#define USB_CMD_SPKT BIT(13) +/*Device Notification 'Function Wake' - Interface value (only in SS mode. */ +#define USB_CMD_DNFW_INT_MASK GENMASK(23, 16) +#define USB_STS_DNFW_INT(p) (((p) << 16) & USB_CMD_DNFW_INT_MASK) +/* + * Device Notification 'Latency Tolerance Message' -373 BELT value [7:0] + * (used only in SS mode). + */ +#define USB_CMD_DNLTM_BELT_MASK GENMASK(27, 16) +#define USB_STS_DNLTM_BELT(p) (((p) << 16) & USB_CMD_DNLTM_BELT_MASK) + +/* USB_ITPN - bitmasks */ +/* + * ITP(SS) / SOF (HS/FS) number + * In SS mode this field represent number of last ITP received from host. + * In HS/FS mode this field represent number of last SOF received from host. + */ +#define USB_ITPN_MASK GENMASK(13, 0) +#define USB_ITPN(p) ((p) & USB_ITPN_MASK) + +/* USB_LPM - bitmasks */ +/* Host Initiated Resume Duration. */ +#define USB_LPM_HIRD_MASK GENMASK(3, 0) +#define USB_LPM_HIRD(p) ((p) & USB_LPM_HIRD_MASK) +/* Remote Wakeup Enable (bRemoteWake). */ +#define USB_LPM_BRW BIT(4) + +/* USB_IEN - bitmasks */ +/* SS connection interrupt enable */ +#define USB_IEN_CONIEN BIT(0) +/* SS disconnection interrupt enable. */ +#define USB_IEN_DISIEN BIT(1) +/* USB SS warm reset interrupt enable. */ +#define USB_IEN_UWRESIEN BIT(2) +/* USB SS hot reset interrupt enable */ +#define USB_IEN_UHRESIEN BIT(3) +/* SS link U3 state enter interrupt enable (suspend).*/ +#define USB_IEN_U3ENTIEN BIT(4) +/* SS link U3 state exit interrupt enable (wakeup). */ +#define USB_IEN_U3EXTIEN BIT(5) +/* SS link U2 state enter interrupt enable.*/ +#define USB_IEN_U2ENTIEN BIT(6) +/* SS link U2 state exit interrupt enable.*/ +#define USB_IEN_U2EXTIEN BIT(7) +/* SS link U1 state enter interrupt enable.*/ +#define USB_IEN_U1ENTIEN BIT(8) +/* SS link U1 state exit interrupt enable.*/ +#define USB_IEN_U1EXTIEN BIT(9) +/* ITP/SOF packet detected interrupt enable.*/ +#define USB_IEN_ITPIEN BIT(10) +/* Wakeup interrupt enable.*/ +#define USB_IEN_WAKEIEN BIT(11) +/* Send Custom Packet interrupt enable.*/ +#define USB_IEN_SPKTIEN BIT(12) +/* HS/FS mode connection interrupt enable.*/ +#define USB_IEN_CON2IEN BIT(16) +/* HS/FS mode disconnection interrupt enable.*/ +#define USB_IEN_DIS2IEN BIT(17) +/* USB reset (HS/FS mode) interrupt enable.*/ +#define USB_IEN_U2RESIEN BIT(18) +/* LPM L2 state enter interrupt enable.*/ +#define USB_IEN_L2ENTIEN BIT(20) +/* LPM L2 state exit interrupt enable.*/ +#define USB_IEN_L2EXTIEN BIT(21) +/* LPM L1 state enter interrupt enable.*/ +#define USB_IEN_L1ENTIEN BIT(24) +/* LPM L1 state exit interrupt enable.*/ +#define USB_IEN_L1EXTIEN BIT(25) +/* Configuration reset interrupt enable.*/ +#define USB_IEN_CFGRESIEN BIT(26) +/* Start of the USB SS warm reset interrupt enable.*/ +#define USB_IEN_UWRESSIEN BIT(28) +/* End of the USB SS warm reset interrupt enable.*/ +#define USB_IEN_UWRESEIEN BIT(29) + +#define USB_IEN_INIT (USB_IEN_U2RESIEN | USB_ISTS_DIS2I | USB_IEN_CON2IEN \ + | USB_IEN_UHRESIEN | USB_IEN_UWRESIEN | USB_IEN_DISIEN \ + | USB_IEN_CONIEN | USB_IEN_U3EXTIEN | USB_IEN_L2ENTIEN \ + | USB_IEN_L2EXTIEN | USB_IEN_L1ENTIEN | USB_IEN_U3ENTIEN) + +/* USB_ISTS - bitmasks */ +/* SS Connection detected. */ +#define USB_ISTS_CONI BIT(0) +/* SS Disconnection detected. */ +#define USB_ISTS_DISI BIT(1) +/* UUSB warm reset detectede. */ +#define USB_ISTS_UWRESI BIT(2) +/* USB hot reset detected. */ +#define USB_ISTS_UHRESI BIT(3) +/* U3 link state enter detected (suspend).*/ +#define USB_ISTS_U3ENTI BIT(4) +/* U3 link state exit detected (wakeup). */ +#define USB_ISTS_U3EXTI BIT(5) +/* U2 link state enter detected.*/ +#define USB_ISTS_U2ENTI BIT(6) +/* U2 link state exit detected.*/ +#define USB_ISTS_U2EXTI BIT(7) +/* U1 link state enter detected.*/ +#define USB_ISTS_U1ENTI BIT(8) +/* U1 link state exit detected.*/ +#define USB_ISTS_U1EXTI BIT(9) +/* ITP/SOF packet detected.*/ +#define USB_ISTS_ITPI BIT(10) +/* Wakeup detected.*/ +#define USB_ISTS_WAKEI BIT(11) +/* Send Custom Packet detected.*/ +#define USB_ISTS_SPKTI BIT(12) +/* HS/FS mode connection detected.*/ +#define USB_ISTS_CON2I BIT(16) +/* HS/FS mode disconnection detected.*/ +#define USB_ISTS_DIS2I BIT(17) +/* USB reset (HS/FS mode) detected.*/ +#define USB_ISTS_U2RESI BIT(18) +/* LPM L2 state enter detected.*/ +#define USB_ISTS_L2ENTI BIT(20) +/* LPM L2 state exit detected.*/ +#define USB_ISTS_L2EXTI BIT(21) +/* LPM L1 state enter detected.*/ +#define USB_ISTS_L1ENTI BIT(24) +/* LPM L1 state exit detected.*/ +#define USB_ISTS_L1EXTI BIT(25) +/* USB configuration reset detected.*/ +#define USB_ISTS_CFGRESI BIT(26) +/* Start of the USB warm reset detected.*/ +#define USB_ISTS_UWRESSI BIT(28) +/* End of the USB warm reset detected.*/ +#define USB_ISTS_UWRESEI BIT(29) + +/* USB_SEL - bitmasks */ +#define EP_SEL_EPNO_MASK GENMASK(3, 0) +/* Endpoint number. */ +#define EP_SEL_EPNO(p) ((p) & EP_SEL_EPNO_MASK) +/* Endpoint direction bit - 0 - OUT, 1 - IN. */ +#define EP_SEL_DIR BIT(7) + +#define select_ep_in(nr) (EP_SEL_EPNO(p) | EP_SEL_DIR) +#define select_ep_out (EP_SEL_EPNO(p)) + +/* EP_TRADDR - bitmasks */ +/* Transfer Ring address. */ +#define EP_TRADDR_TRADDR(p) ((p)) + +/* EP_CFG - bitmasks */ +/* Endpoint enable */ +#define EP_CFG_ENABLE BIT(0) +/* + * Endpoint type. + * 1 - isochronous + * 2 - bulk + * 3 - interrupt + */ +#define EP_CFG_EPTYPE_MASK GENMASK(2, 1) +#define EP_CFG_EPTYPE(p) (((p) << 1) & EP_CFG_EPTYPE_MASK) +/* Stream support enable (only in SS mode). */ +#define EP_CFG_STREAM_EN BIT(3) +/* TDL check (only in SS mode for BULK EP). */ +#define EP_CFG_TDL_CHK BIT(4) +/* SID check (only in SS mode for BULK OUT EP). */ +#define EP_CFG_SID_CHK BIT(5) +/* DMA transfer endianness. */ +#define EP_CFG_EPENDIAN BIT(7) +/* Max burst size (used only in SS mode). */ +#define EP_CFG_MAXBURST_MASK GENMASK(11, 8) +#define EP_CFG_MAXBURST(p) (((p) << 8) & EP_CFG_MAXBURST_MASK) +#define EP_CFG_MAXBURST_MAX 15 +/* ISO max burst. */ +#define EP_CFG_MULT_MASK GENMASK(15, 14) +#define EP_CFG_MULT(p) (((p) << 14) & EP_CFG_MULT_MASK) +#define EP_CFG_MULT_MAX 2 +/* ISO max burst. */ +#define EP_CFG_MAXPKTSIZE_MASK GENMASK(26, 16) +#define EP_CFG_MAXPKTSIZE(p) (((p) << 16) & EP_CFG_MAXPKTSIZE_MASK) +/* Max number of buffered packets. */ +#define EP_CFG_BUFFERING_MASK GENMASK(31, 27) +#define EP_CFG_BUFFERING(p) (((p) << 27) & EP_CFG_BUFFERING_MASK) +#define EP_CFG_BUFFERING_MAX 15 + +/* EP_CMD - bitmasks */ +/* Endpoint reset. */ +#define EP_CMD_EPRST BIT(0) +/* Endpoint STALL set. */ +#define EP_CMD_SSTALL BIT(1) +/* Endpoint STALL clear. */ +#define EP_CMD_CSTALL BIT(2) +/* Send ERDY TP. */ +#define EP_CMD_ERDY BIT(3) +/* Request complete. */ +#define EP_CMD_REQ_CMPL BIT(5) +/* Transfer descriptor ready. */ +#define EP_CMD_DRDY BIT(6) +/* Data flush. */ +#define EP_CMD_DFLUSH BIT(7) +/* + * Transfer Descriptor Length write (used only for Bulk Stream capable + * endpoints in SS mode). + * Bit Removed from DEV_VER_V3 controller version. + */ +#define EP_CMD_STDL BIT(8) +/* + * Transfer Descriptor Length (used only in SS mode for bulk endpoints). + * Bits Removed from DEV_VER_V3 controller version. + */ +#define EP_CMD_TDL_MASK GENMASK(15, 9) +#define EP_CMD_TDL_SET(p) (((p) << 9) & EP_CMD_TDL_MASK) +#define EP_CMD_TDL_GET(p) (((p) & EP_CMD_TDL_MASK) >> 9) +#define EP_CMD_TDL_MAX (EP_CMD_TDL_MASK >> 9) + +/* ERDY Stream ID value (used in SS mode). */ +#define EP_CMD_ERDY_SID_MASK GENMASK(31, 16) +#define EP_CMD_ERDY_SID(p) (((p) << 16) & EP_CMD_ERDY_SID_MASK) + +/* EP_STS - bitmasks */ +/* Setup transfer complete. */ +#define EP_STS_SETUP BIT(0) +/* Endpoint STALL status. */ +#define EP_STS_STALL(p) ((p) & BIT(1)) +/* Interrupt On Complete. */ +#define EP_STS_IOC BIT(2) +/* Interrupt on Short Packet. */ +#define EP_STS_ISP BIT(3) +/* Transfer descriptor missing. */ +#define EP_STS_DESCMIS BIT(4) +/* Stream Rejected (used only in SS mode) */ +#define EP_STS_STREAMR BIT(5) +/* EXIT from MOVE DATA State (used only for stream transfers in SS mode). */ +#define EP_STS_MD_EXIT BIT(6) +/* TRB error. */ +#define EP_STS_TRBERR BIT(7) +/* Not ready (used only in SS mode). */ +#define EP_STS_NRDY BIT(8) +/* DMA busy bit. */ +#define EP_STS_DBUSY BIT(9) +/* Endpoint Buffer Empty */ +#define EP_STS_BUFFEMPTY(p) ((p) & BIT(10)) +/* Current Cycle Status */ +#define EP_STS_CCS(p) ((p) & BIT(11)) +/* Prime (used only in SS mode. */ +#define EP_STS_PRIME BIT(12) +/* Stream error (used only in SS mode). */ +#define EP_STS_SIDERR BIT(13) +/* OUT size mismatch. */ +#define EP_STS_OUTSMM BIT(14) +/* ISO transmission error. */ +#define EP_STS_ISOERR BIT(15) +/* Host Packet Pending (only for SS mode). */ +#define EP_STS_HOSTPP(p) ((p) & BIT(16)) +/* Stream Protocol State Machine State (only for Bulk stream endpoints). */ +#define EP_STS_SPSMST_MASK GENMASK(18, 17) +#define EP_STS_SPSMST_DISABLED(p) (((p) & EP_STS_SPSMST_MASK) >> 17) +#define EP_STS_SPSMST_IDLE(p) (((p) & EP_STS_SPSMST_MASK) >> 17) +#define EP_STS_SPSMST_START_STREAM(p) (((p) & EP_STS_SPSMST_MASK) >> 17) +#define EP_STS_SPSMST_MOVE_DATA(p) (((p) & EP_STS_SPSMST_MASK) >> 17) +/* Interrupt On Transfer complete. */ +#define EP_STS_IOT BIT(19) +/* OUT queue endpoint number. */ +#define EP_STS_OUTQ_NO_MASK GENMASK(27, 24) +#define EP_STS_OUTQ_NO(p) (((p) & EP_STS_OUTQ_NO_MASK) >> 24) +/* OUT queue valid flag. */ +#define EP_STS_OUTQ_VAL_MASK BIT(28) +#define EP_STS_OUTQ_VAL(p) ((p) & EP_STS_OUTQ_VAL_MASK) +/* SETUP WAIT. */ +#define EP_STS_STPWAIT BIT(31) + +/* EP_STS_SID - bitmasks */ +/* Stream ID (used only in SS mode). */ +#define EP_STS_SID_MASK GENMASK(15, 0) +#define EP_STS_SID(p) ((p) & EP_STS_SID_MASK) + +/* EP_STS_EN - bitmasks */ +/* SETUP interrupt enable. */ +#define EP_STS_EN_SETUPEN BIT(0) +/* OUT transfer missing descriptor enable. */ +#define EP_STS_EN_DESCMISEN BIT(4) +/* Stream Rejected enable. */ +#define EP_STS_EN_STREAMREN BIT(5) +/* Move Data Exit enable.*/ +#define EP_STS_EN_MD_EXITEN BIT(6) +/* TRB enable. */ +#define EP_STS_EN_TRBERREN BIT(7) +/* NRDY enable. */ +#define EP_STS_EN_NRDYEN BIT(8) +/* Prime enable. */ +#define EP_STS_EN_PRIMEEEN BIT(12) +/* Stream error enable. */ +#define EP_STS_EN_SIDERREN BIT(13) +/* OUT size mismatch enable. */ +#define EP_STS_EN_OUTSMMEN BIT(14) +/* ISO transmission error enable. */ +#define EP_STS_EN_ISOERREN BIT(15) +/* Interrupt on Transmission complete enable. */ +#define EP_STS_EN_IOTEN BIT(19) +/* Setup Wait interrupt enable. */ +#define EP_STS_EN_STPWAITEN BIT(31) + +/* DRBL- bitmasks */ +#define DB_VALUE_BY_INDEX(index) (1 << (index)) +#define DB_VALUE_EP0_OUT BIT(0) +#define DB_VALUE_EP0_IN BIT(16) + +/* EP_IEN - bitmasks */ +#define EP_IEN(index) (1 << (index)) +#define EP_IEN_EP_OUT0 BIT(0) +#define EP_IEN_EP_IN0 BIT(16) + +/* EP_ISTS - bitmasks */ +#define EP_ISTS(index) (1 << (index)) +#define EP_ISTS_EP_OUT0 BIT(0) +#define EP_ISTS_EP_IN0 BIT(16) + +/* USB_PWR- bitmasks */ +/*Power Shut Off capability enable*/ +#define PUSB_PWR_PSO_EN BIT(0) +/*Power Shut Off capability disable*/ +#define PUSB_PWR_PSO_DS BIT(1) +/* + * Enables turning-off Reference Clock. + * This bit is optional and implemented only when support for OTG is + * implemented (indicated by OTG_READY bit set to '1'). + */ +#define PUSB_PWR_STB_CLK_SWITCH_EN BIT(8) +/* + * Status bit indicating that operation required by STB_CLK_SWITCH_EN write + * is completed + */ +#define PUSB_PWR_STB_CLK_SWITCH_DONE BIT(9) +/* This bit informs if Fast Registers Access is enabled. */ +#define PUSB_PWR_FST_REG_ACCESS_STAT BIT(30) +/* Fast Registers Access Enable. */ +#define PUSB_PWR_FST_REG_ACCESS BIT(31) + +/* USB_CONF2- bitmasks */ +/* + * Writing 1 disables TDL calculation basing on TRB feature in controller + * for DMULT mode. + * Bit supported only for DEV_VER_V2 version. + */ +#define USB_CONF2_DIS_TDL_TRB BIT(1) +/* + * Writing 1 enables TDL calculation basing on TRB feature in controller + * for DMULT mode. + * Bit supported only for DEV_VER_V2 version. + */ +#define USB_CONF2_EN_TDL_TRB BIT(2) + +/* USB_CAP1- bitmasks */ +/* + * SFR Interface type + * These field reflects type of SFR interface implemented: + * 0x0 - OCP + * 0x1 - AHB, + * 0x2 - PLB + * 0x3 - AXI + * 0x4-0xF - reserved + */ +#define USB_CAP1_SFR_TYPE_MASK GENMASK(3, 0) +#define DEV_SFR_TYPE_OCP(p) (((p) & USB_CAP1_SFR_TYPE_MASK) == 0x0) +#define DEV_SFR_TYPE_AHB(p) (((p) & USB_CAP1_SFR_TYPE_MASK) == 0x1) +#define DEV_SFR_TYPE_PLB(p) (((p) & USB_CAP1_SFR_TYPE_MASK) == 0x2) +#define DEV_SFR_TYPE_AXI(p) (((p) & USB_CAP1_SFR_TYPE_MASK) == 0x3) +/* + * SFR Interface width + * These field reflects width of SFR interface implemented: + * 0x0 - 8 bit interface, + * 0x1 - 16 bit interface, + * 0x2 - 32 bit interface + * 0x3 - 64 bit interface + * 0x4-0xF - reserved + */ +#define USB_CAP1_SFR_WIDTH_MASK GENMASK(7, 4) +#define DEV_SFR_WIDTH_8(p) (((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x0 << 4)) +#define DEV_SFR_WIDTH_16(p) (((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x1 << 4)) +#define DEV_SFR_WIDTH_32(p) (((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x2 << 4)) +#define DEV_SFR_WIDTH_64(p) (((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x3 << 4)) +/* + * DMA Interface type + * These field reflects type of DMA interface implemented: + * 0x0 - OCP + * 0x1 - AHB, + * 0x2 - PLB + * 0x3 - AXI + * 0x4-0xF - reserved + */ +#define USB_CAP1_DMA_TYPE_MASK GENMASK(11, 8) +#define DEV_DMA_TYPE_OCP(p) (((p) & USB_CAP1_DMA_TYPE_MASK) == (0x0 << 8)) +#define DEV_DMA_TYPE_AHB(p) (((p) & USB_CAP1_DMA_TYPE_MASK) == (0x1 << 8)) +#define DEV_DMA_TYPE_PLB(p) (((p) & USB_CAP1_DMA_TYPE_MASK) == (0x2 << 8)) +#define DEV_DMA_TYPE_AXI(p) (((p) & USB_CAP1_DMA_TYPE_MASK) == (0x3 << 8)) +/* + * DMA Interface width + * These field reflects width of DMA interface implemented: + * 0x0 - reserved, + * 0x1 - reserved, + * 0x2 - 32 bit interface + * 0x3 - 64 bit interface + * 0x4-0xF - reserved + */ +#define USB_CAP1_DMA_WIDTH_MASK GENMASK(15, 12) +#define DEV_DMA_WIDTH_32(p) (((p) & USB_CAP1_DMA_WIDTH_MASK) == (0x2 << 12)) +#define DEV_DMA_WIDTH_64(p) (((p) & USB_CAP1_DMA_WIDTH_MASK) == (0x3 << 12)) +/* + * USB3 PHY Interface type + * These field reflects type of USB3 PHY interface implemented: + * 0x0 - USB PIPE, + * 0x1 - RMMI, + * 0x2-0xF - reserved + */ +#define USB_CAP1_U3PHY_TYPE_MASK GENMASK(19, 16) +#define DEV_U3PHY_PIPE(p) (((p) & USB_CAP1_U3PHY_TYPE_MASK) == (0x0 << 16)) +#define DEV_U3PHY_RMMI(p) (((p) & USB_CAP1_U3PHY_TYPE_MASK) == (0x1 << 16)) +/* + * USB3 PHY Interface width + * These field reflects width of USB3 PHY interface implemented: + * 0x0 - 8 bit PIPE interface, + * 0x1 - 16 bit PIPE interface, + * 0x2 - 32 bit PIPE interface, + * 0x3 - 64 bit PIPE interface + * 0x4-0xF - reserved + * Note: When SSIC interface is implemented this field shows the width of + * internal PIPE interface. The RMMI interface is always 20bit wide. + */ +#define USB_CAP1_U3PHY_WIDTH_MASK GENMASK(23, 20) +#define DEV_U3PHY_WIDTH_8(p) \ + (((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x0 << 20)) +#define DEV_U3PHY_WIDTH_16(p) \ + (((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x1 << 16)) +#define DEV_U3PHY_WIDTH_32(p) \ + (((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x2 << 20)) +#define DEV_U3PHY_WIDTH_64(p) \ + (((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x3 << 16)) + +/* + * USB2 PHY Interface enable + * These field informs if USB2 PHY interface is implemented: + * 0x0 - interface NOT implemented, + * 0x1 - interface implemented + */ +#define USB_CAP1_U2PHY_EN(p) ((p) & BIT(24)) +/* + * USB2 PHY Interface type + * These field reflects type of USB2 PHY interface implemented: + * 0x0 - UTMI, + * 0x1 - ULPI + */ +#define DEV_U2PHY_ULPI(p) ((p) & BIT(25)) +/* + * USB2 PHY Interface width + * These field reflects width of USB2 PHY interface implemented: + * 0x0 - 8 bit interface, + * 0x1 - 16 bit interface, + * Note: The ULPI interface is always 8bit wide. + */ +#define DEV_U2PHY_WIDTH_16(p) ((p) & BIT(26)) +/* + * OTG Ready + * 0x0 - pure device mode + * 0x1 - some features and ports for CDNS USB OTG controller are implemented. + */ +#define USB_CAP1_OTG_READY(p) ((p) & BIT(27)) + +/* + * When set, indicates that controller supports automatic internal TDL + * calculation basing on the size provided in TRB (TRB[22:17]) for DMULT mode + * Supported only for DEV_VER_V2 controller version. + */ +#define USB_CAP1_TDL_FROM_TRB(p) ((p) & BIT(28)) + +/* USB_CAP2- bitmasks */ +/* + * The actual size of the connected On-chip RAM memory in kB: + * - 0 means 256 kB (max supported mem size) + * - value other than 0 reflects the mem size in kB + */ +#define USB_CAP2_ACTUAL_MEM_SIZE(p) ((p) & GENMASK(7, 0)) +/* + * Max supported mem size + * These field reflects width of on-chip RAM address bus width, + * which determines max supported mem size: + * 0x0-0x7 - reserved, + * 0x8 - support for 4kB mem, + * 0x9 - support for 8kB mem, + * 0xA - support for 16kB mem, + * 0xB - support for 32kB mem, + * 0xC - support for 64kB mem, + * 0xD - support for 128kB mem, + * 0xE - support for 256kB mem, + * 0xF - reserved + */ +#define USB_CAP2_MAX_MEM_SIZE(p) ((p) & GENMASK(11, 8)) + +/* USB_CAP3- bitmasks */ +#define EP_IS_IMPLEMENTED(reg, index) ((reg) & (1 << (index))) + +/* USB_CAP4- bitmasks */ +#define EP_SUPPORT_ISO(reg, index) ((reg) & (1 << (index))) + +/* USB_CAP5- bitmasks */ +#define EP_SUPPORT_STREAM(reg, index) ((reg) & (1 << (index))) + +/* USB_CAP6- bitmasks */ +/* The USBSS-DEV Controller Internal build number. */ +#define GET_DEV_BASE_VERSION(p) ((p) & GENMASK(23, 0)) +/* The USBSS-DEV Controller version number. */ +#define GET_DEV_CUSTOM_VERSION(p) ((p) & GENMASK(31, 24)) + +#define DEV_VER_NXP_V1 0x00024502 +#define DEV_VER_TI_V1 0x00024509 +#define DEV_VER_V2 0x0002450C +#define DEV_VER_V3 0x0002450d + +/* DBG_LINK1- bitmasks */ +/* + * LFPS_MIN_DET_U1_EXIT value This parameter configures the minimum + * time required for decoding the received LFPS as an LFPS.U1_Exit. + */ +#define DBG_LINK1_LFPS_MIN_DET_U1_EXIT(p) ((p) & GENMASK(7, 0)) +/* + * LFPS_MIN_GEN_U1_EXIT value This parameter configures the minimum time for + * phytxelecidle deassertion when LFPS.U1_Exit + */ +#define DBG_LINK1_LFPS_MIN_GEN_U1_EXIT_MASK GENMASK(15, 8) +#define DBG_LINK1_LFPS_MIN_GEN_U1_EXIT(p) (((p) << 8) & GENMASK(15, 8)) +/* + * RXDET_BREAK_DIS value This parameter configures terminating the Far-end + * Receiver termination detection sequence: + * 0: it is possible that USBSS_DEV will terminate Farend receiver + * termination detection sequence + * 1: USBSS_DEV will not terminate Far-end receiver termination + * detection sequence + */ +#define DBG_LINK1_RXDET_BREAK_DIS BIT(16) +/* LFPS_GEN_PING value This parameter configures the LFPS.Ping generation */ +#define DBG_LINK1_LFPS_GEN_PING(p) (((p) << 17) & GENMASK(21, 17)) +/* + * Set the LFPS_MIN_DET_U1_EXIT value Writing '1' to this bit writes the + * LFPS_MIN_DET_U1_EXIT field value to the device. This bit is automatically + * cleared. Writing '0' has no effect + */ +#define DBG_LINK1_LFPS_MIN_DET_U1_EXIT_SET BIT(24) +/* + * Set the LFPS_MIN_GEN_U1_EXIT value. Writing '1' to this bit writes the + * LFPS_MIN_GEN_U1_EXIT field value to the device. This bit is automatically + * cleared. Writing '0' has no effect + */ +#define DBG_LINK1_LFPS_MIN_GEN_U1_EXIT_SET BIT(25) +/* + * Set the RXDET_BREAK_DIS value Writing '1' to this bit writes + * the RXDET_BREAK_DIS field value to the device. This bit is automatically + * cleared. Writing '0' has no effect + */ +#define DBG_LINK1_RXDET_BREAK_DIS_SET BIT(26) +/* + * Set the LFPS_GEN_PING_SET value Writing '1' to this bit writes + * the LFPS_GEN_PING field value to the device. This bit is automatically + * cleared. Writing '0' has no effect." + */ +#define DBG_LINK1_LFPS_GEN_PING_SET BIT(27) + +/* DMA_AXI_CTRL- bitmasks */ +/* The mawprot pin configuration. */ +#define DMA_AXI_CTRL_MARPROT(p) ((p) & GENMASK(2, 0)) +/* The marprot pin configuration. */ +#define DMA_AXI_CTRL_MAWPROT(p) (((p) & GENMASK(2, 0)) << 16) +#define DMA_AXI_CTRL_NON_SECURE 0x02 + +#define gadget_to_cdns3_device(g) (container_of(g, struct cdns3_device, gadget)) + +#define ep_to_cdns3_ep(ep) (container_of(ep, struct cdns3_endpoint, endpoint)) + +/*-------------------------------------------------------------------------*/ +/* + * USBSS-DEV DMA interface. + */ +#define TRBS_PER_SEGMENT 600 + +#define ISO_MAX_INTERVAL 10 + +#define MAX_TRB_LENGTH BIT(16) + +#if TRBS_PER_SEGMENT < 2 +#error "Incorrect TRBS_PER_SEGMENT. Minimal Transfer Ring size is 2." +#endif + +#define TRBS_PER_STREAM_SEGMENT 2 + +#if TRBS_PER_STREAM_SEGMENT < 2 +#error "Incorrect TRBS_PER_STREAMS_SEGMENT. Minimal Transfer Ring size is 2." +#endif + +/* + *Only for ISOC endpoints - maximum number of TRBs is calculated as + * pow(2, bInterval-1) * number of usb requests. It is limitation made by + * driver to save memory. Controller must prepare TRB for each ITP even + * if bInterval > 1. It's the reason why driver needs so many TRBs for + * isochronous endpoints. + */ +#define TRBS_PER_ISOC_SEGMENT (ISO_MAX_INTERVAL * 8) + +#define GET_TRBS_PER_SEGMENT(ep_type) ((ep_type) == USB_ENDPOINT_XFER_ISOC ? \ + TRBS_PER_ISOC_SEGMENT : TRBS_PER_SEGMENT) +/** + * struct cdns3_trb - represent Transfer Descriptor block. + * @buffer: pointer to buffer data + * @length: length of data + * @control: control flags. + * + * This structure describes transfer block serviced by DMA module. + */ +struct cdns3_trb { + __le32 buffer; + __le32 length; + __le32 control; +}; + +#define TRB_SIZE (sizeof(struct cdns3_trb)) +#define TRB_RING_SIZE (TRB_SIZE * TRBS_PER_SEGMENT) +#define TRB_STREAM_RING_SIZE (TRB_SIZE * TRBS_PER_STREAM_SEGMENT) +#define TRB_ISO_RING_SIZE (TRB_SIZE * TRBS_PER_ISOC_SEGMENT) +#define TRB_CTRL_RING_SIZE (TRB_SIZE * 2) + +/* TRB bit mask */ +#define TRB_TYPE_BITMASK GENMASK(15, 10) +#define TRB_TYPE(p) ((p) << 10) +#define TRB_FIELD_TO_TYPE(p) (((p) & TRB_TYPE_BITMASK) >> 10) + +/* TRB type IDs */ +/* bulk, interrupt, isoc , and control data stage */ +#define TRB_NORMAL 1 +/* TRB for linking ring segments */ +#define TRB_LINK 6 + +/* Cycle bit - indicates TRB ownership by driver or hw*/ +#define TRB_CYCLE BIT(0) +/* + * When set to '1', the device will toggle its interpretation of the Cycle bit + */ +#define TRB_TOGGLE BIT(1) +/* + * The controller will set it if OUTSMM (OUT size mismatch) is detected, + * this bit is for normal TRB + */ +#define TRB_SMM BIT(1) + +/* + * Short Packet (SP). OUT EPs at DMULT=1 only. Indicates if the TRB was + * processed while USB short packet was received. No more buffers defined by + * the TD will be used. DMA will automatically advance to next TD. + * - Shall be set to 0 by Software when putting TRB on the Transfer Ring + * - Shall be set to 1 by Controller when Short Packet condition for this TRB + * is detected independent if ISP is set or not. + */ +#define TRB_SP BIT(1) + +/* Interrupt on short packet*/ +#define TRB_ISP BIT(2) +/*Setting this bit enables FIFO DMA operation mode*/ +#define TRB_FIFO_MODE BIT(3) +/* Set PCIe no snoop attribute */ +#define TRB_CHAIN BIT(4) +/* Interrupt on completion */ +#define TRB_IOC BIT(5) + +/* stream ID bitmasks. */ +#define TRB_STREAM_ID_BITMASK GENMASK(31, 16) +#define TRB_STREAM_ID(p) ((p) << 16) +#define TRB_FIELD_TO_STREAMID(p) (((p) & TRB_STREAM_ID_BITMASK) >> 16) + +/* Size of TD expressed in USB packets for HS/FS mode. */ +#define TRB_TDL_HS_SIZE(p) (((p) << 16) & GENMASK(31, 16)) +#define TRB_TDL_HS_SIZE_GET(p) (((p) & GENMASK(31, 16)) >> 16) + +/* transfer_len bitmasks. */ +#define TRB_LEN(p) ((p) & GENMASK(16, 0)) + +/* Size of TD expressed in USB packets for SS mode. */ +#define TRB_TDL_SS_SIZE(p) (((p) << 17) & GENMASK(23, 17)) +#define TRB_TDL_SS_SIZE_GET(p) (((p) & GENMASK(23, 17)) >> 17) + +/* transfer_len bitmasks - bits 31:24 */ +#define TRB_BURST_LEN(p) ((unsigned int)((p) << 24) & GENMASK(31, 24)) +#define TRB_BURST_LEN_GET(p) (((p) & GENMASK(31, 24)) >> 24) + +/* Data buffer pointer bitmasks*/ +#define TRB_BUFFER(p) ((p) & GENMASK(31, 0)) + +/*-------------------------------------------------------------------------*/ +/* Driver numeric constants */ + +/* Such declaration should be added to ch9.h */ +#define USB_DEVICE_MAX_ADDRESS 127 + +/* Endpoint init values */ +#define CDNS3_EP_MAX_PACKET_LIMIT 1024 +#define CDNS3_EP_MAX_STREAMS 15 +#define CDNS3_EP0_MAX_PACKET_LIMIT 512 + +/* All endpoints including EP0 */ +#define CDNS3_ENDPOINTS_MAX_COUNT 32 +#define CDNS3_EP_ZLP_BUF_SIZE 1024 + +#define CDNS3_MAX_NUM_DESCMISS_BUF 32 +#define CDNS3_DESCMIS_BUF_SIZE 2048 /* Bytes */ +#define CDNS3_WA2_NUM_BUFFERS 128 +/*-------------------------------------------------------------------------*/ +/* Used structs */ + +struct cdns3_device; + +/** + * struct cdns3_endpoint - extended device side representation of USB endpoint. + * @endpoint: usb endpoint + * @pending_req_list: list of requests queuing on transfer ring. + * @deferred_req_list: list of requests waiting for queuing on transfer ring. + * @wa2_descmiss_req_list: list of requests internally allocated by driver. + * @trb_pool: transfer ring - array of transaction buffers + * @trb_pool_dma: dma address of transfer ring + * @cdns3_dev: device associated with this endpoint + * @name: a human readable name e.g. ep1out + * @flags: specify the current state of endpoint + * @descmis_req: internal transfer object used for getting data from on-chip + * buffer. It can happen only if function driver doesn't send usb_request + * object on time. + * @dir: endpoint direction + * @num: endpoint number (1 - 15) + * @type: set to bmAttributes & USB_ENDPOINT_XFERTYPE_MASK + * @interval: interval between packets used for ISOC endpoint. + * @free_trbs: number of free TRBs in transfer ring + * @num_trbs: number of all TRBs in transfer ring + * @alloc_ring_size: size of the allocated TRB ring + * @pcs: producer cycle state + * @ccs: consumer cycle state + * @enqueue: enqueue index in transfer ring + * @dequeue: dequeue index in transfer ring + * @trb_burst_size: number of burst used in trb. + */ +struct cdns3_endpoint { + struct usb_ep endpoint; + struct list_head pending_req_list; + struct list_head deferred_req_list; + struct list_head wa2_descmiss_req_list; + int wa2_counter; + + struct cdns3_trb *trb_pool; + dma_addr_t trb_pool_dma; + + struct cdns3_device *cdns3_dev; + char name[20]; + +#define EP_ENABLED BIT(0) +#define EP_STALLED BIT(1) +#define EP_STALL_PENDING BIT(2) +#define EP_WEDGE BIT(3) +#define EP_TRANSFER_STARTED BIT(4) +#define EP_UPDATE_EP_TRBADDR BIT(5) +#define EP_PENDING_REQUEST BIT(6) +#define EP_RING_FULL BIT(7) +#define EP_CLAIMED BIT(8) +#define EP_DEFERRED_DRDY BIT(9) +#define EP_QUIRK_ISO_OUT_EN BIT(10) +#define EP_QUIRK_END_TRANSFER BIT(11) +#define EP_QUIRK_EXTRA_BUF_DET BIT(12) +#define EP_QUIRK_EXTRA_BUF_EN BIT(13) +#define EP_TDLCHK_EN BIT(15) +#define EP_CONFIGURED BIT(16) + u32 flags; + + struct cdns3_request *descmis_req; + + u8 dir; + u8 num; + u8 type; + u8 mult; + u8 bMaxBurst; + u16 wMaxPacketSize; + int interval; + + int free_trbs; + int num_trbs; + int alloc_ring_size; + u8 pcs; + u8 ccs; + int enqueue; + int dequeue; + u8 trb_burst_size; + + unsigned int wa1_set:1; + struct cdns3_trb *wa1_trb; + unsigned int wa1_trb_index; + unsigned int wa1_cycle_bit:1; + + /* Stream related */ + unsigned int use_streams:1; + unsigned int prime_flag:1; + u32 ep_sts_pending; + u16 last_stream_id; + u16 pending_tdl; + unsigned int stream_sg_idx; +}; + +/** + * struct cdns3_aligned_buf - represent aligned buffer used for DMA transfer + * @buf: aligned to 8 bytes data buffer. Buffer address used in + * TRB shall be aligned to 8. + * @dma: dma address + * @size: size of buffer + * @in_use: inform if this buffer is associated with usb_request + * @list: used to adding instance of this object to list + */ +struct cdns3_aligned_buf { + void *buf; + dma_addr_t dma; + u32 size; + enum dma_data_direction dir; + unsigned in_use:1; + struct list_head list; +}; + +/** + * struct cdns3_request - extended device side representation of usb_request + * object . + * @request: generic usb_request object describing single I/O request. + * @priv_ep: extended representation of usb_ep object + * @trb: the first TRB association with this request + * @start_trb: number of the first TRB in transfer ring + * @end_trb: number of the last TRB in transfer ring + * @aligned_buf: object holds information about aligned buffer associated whit + * this endpoint + * @flags: flag specifying special usage of request + * @list: used by internally allocated request to add to wa2_descmiss_req_list. + * @finished_trb: number of trb has already finished per request + * @num_of_trb: how many trbs in this request + */ +struct cdns3_request { + struct usb_request request; + struct cdns3_endpoint *priv_ep; + struct cdns3_trb *trb; + int start_trb; + int end_trb; + struct cdns3_aligned_buf *aligned_buf; +#define REQUEST_PENDING BIT(0) +#define REQUEST_INTERNAL BIT(1) +#define REQUEST_INTERNAL_CH BIT(2) +#define REQUEST_ZLP BIT(3) +#define REQUEST_UNALIGNED BIT(4) + u32 flags; + struct list_head list; + int finished_trb; + int num_of_trb; +}; + +#define to_cdns3_request(r) (container_of(r, struct cdns3_request, request)) + +/*Stages used during enumeration process.*/ +#define CDNS3_SETUP_STAGE 0x0 +#define CDNS3_DATA_STAGE 0x1 +#define CDNS3_STATUS_STAGE 0x2 + +/** + * struct cdns3_device - represent USB device. + * @dev: pointer to device structure associated whit this controller + * @sysdev: pointer to the DMA capable device + * @gadget: device side representation of the peripheral controller + * @gadget_driver: pointer to the gadget driver + * @dev_ver: device controller version. + * @lock: for synchronizing + * @regs: base address for device side registers + * @setup_buf: used while processing usb control requests + * @setup_dma: dma address for setup_buf + * @zlp_buf - zlp buffer + * @ep0_stage: ep0 stage during enumeration process. + * @ep0_data_dir: direction for control transfer + * @eps: array of pointers to all endpoints with exclusion ep0 + * @aligned_buf_list: list of aligned buffers internally allocated by driver + * @aligned_buf_wq: workqueue freeing no longer used aligned buf. + * @selected_ep: actually selected endpoint. It's used only to improve + * performance. + * @isoch_delay: value from Set Isoch Delay request. Only valid on SS/SSP. + * @u1_allowed: allow device transition to u1 state + * @u2_allowed: allow device transition to u2 state + * @is_selfpowered: device is self powered + * @setup_pending: setup packet is processing by gadget driver + * @hw_configured_flag: hardware endpoint configuration was set. + * @wake_up_flag: allow device to remote up the host + * @status_completion_no_call: indicate that driver is waiting for status s + * stage completion. It's used in deferred SET_CONFIGURATION request. + * @onchip_buffers: number of available on-chip buffers. + * @onchip_used_size: actual size of on-chip memory assigned to endpoints. + * @pending_status_wq: workqueue handling status stage for deferred requests. + * @pending_status_request: request for which status stage was deferred + */ +struct cdns3_device { + struct device *dev; + struct device *sysdev; + + struct usb_gadget gadget; + struct usb_gadget_driver *gadget_driver; + +#define CDNS_REVISION_V0 0x00024501 +#define CDNS_REVISION_V1 0x00024509 + u32 dev_ver; + + /* generic spin-lock for drivers */ + spinlock_t lock; + + struct cdns3_usb_regs __iomem *regs; + + struct dma_pool *eps_dma_pool; + struct usb_ctrlrequest *setup_buf; + dma_addr_t setup_dma; + void *zlp_buf; + + u8 ep0_stage; + int ep0_data_dir; + + struct cdns3_endpoint *eps[CDNS3_ENDPOINTS_MAX_COUNT]; + + struct list_head aligned_buf_list; + struct work_struct aligned_buf_wq; + + u32 selected_ep; + u16 isoch_delay; + + unsigned wait_for_setup:1; + unsigned u1_allowed:1; + unsigned u2_allowed:1; + unsigned is_selfpowered:1; + unsigned setup_pending:1; + unsigned hw_configured_flag:1; + unsigned wake_up_flag:1; + unsigned status_completion_no_call:1; + unsigned using_streams:1; + int out_mem_is_allocated; + + struct work_struct pending_status_wq; + struct usb_request *pending_status_request; + + /*in KB */ + u16 onchip_buffers; + u16 onchip_used_size; + + u16 ep_buf_size; + u16 ep_iso_burst; +}; + +void cdns3_set_register_bit(void __iomem *ptr, u32 mask); +dma_addr_t cdns3_trb_virt_to_dma(struct cdns3_endpoint *priv_ep, + struct cdns3_trb *trb); +enum usb_device_speed cdns3_get_speed(struct cdns3_device *priv_dev); +void cdns3_pending_setup_status_handler(struct work_struct *work); +void cdns3_hw_reset_eps_config(struct cdns3_device *priv_dev); +void cdns3_set_hw_configuration(struct cdns3_device *priv_dev); +void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep); +void cdns3_allow_enable_l1(struct cdns3_device *priv_dev, int enable); +struct usb_request *cdns3_next_request(struct list_head *list); +void cdns3_rearm_transfer(struct cdns3_endpoint *priv_ep, u8 rearm); +int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep); +u8 cdns3_ep_addr_to_index(u8 ep_addr); +int cdns3_gadget_ep_set_wedge(struct usb_ep *ep); +int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value); +void __cdns3_gadget_ep_set_halt(struct cdns3_endpoint *priv_ep); +int __cdns3_gadget_ep_clear_halt(struct cdns3_endpoint *priv_ep); +struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags); +void cdns3_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request); +int cdns3_gadget_ep_dequeue(struct usb_ep *ep, struct usb_request *request); +void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep, + struct cdns3_request *priv_req, + int status); + +int cdns3_init_ep0(struct cdns3_device *priv_dev, + struct cdns3_endpoint *priv_ep); +void cdns3_ep0_config(struct cdns3_device *priv_dev); +int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable); +void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir); +int __cdns3_gadget_wakeup(struct cdns3_device *priv_dev); + +#endif /* __LINUX_CDNS3_GADGET */ diff --git a/drivers/usb/cdns3/cdns3-imx.c b/drivers/usb/cdns3/cdns3-imx.c new file mode 100644 index 000000000..a2f041e17 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-imx.c @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cdns3-imx.c - NXP i.MX specific Glue layer for Cadence USB Controller + * + * Copyright (C) 2019 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "core.h" + +#define USB3_CORE_CTRL1 0x00 +#define USB3_CORE_CTRL2 0x04 +#define USB3_INT_REG 0x08 +#define USB3_CORE_STATUS 0x0c +#define XHCI_DEBUG_LINK_ST 0x10 +#define XHCI_DEBUG_BUS 0x14 +#define USB3_SSPHY_CTRL1 0x40 +#define USB3_SSPHY_CTRL2 0x44 +#define USB3_SSPHY_STATUS 0x4c +#define USB2_PHY_CTRL1 0x50 +#define USB2_PHY_CTRL2 0x54 +#define USB2_PHY_STATUS 0x5c + +/* Register bits definition */ + +/* USB3_CORE_CTRL1 */ +#define SW_RESET_MASK GENMASK(31, 26) +#define PWR_SW_RESET BIT(31) +#define APB_SW_RESET BIT(30) +#define AXI_SW_RESET BIT(29) +#define RW_SW_RESET BIT(28) +#define PHY_SW_RESET BIT(27) +#define PHYAHB_SW_RESET BIT(26) +#define ALL_SW_RESET (PWR_SW_RESET | APB_SW_RESET | AXI_SW_RESET | \ + RW_SW_RESET | PHY_SW_RESET | PHYAHB_SW_RESET) +#define OC_DISABLE BIT(9) +#define MDCTRL_CLK_SEL BIT(7) +#define MODE_STRAP_MASK (0x7) +#define DEV_MODE (1 << 2) +#define HOST_MODE (1 << 1) +#define OTG_MODE (1 << 0) + +/* USB3_INT_REG */ +#define CLK_125_REQ BIT(29) +#define LPM_CLK_REQ BIT(28) +#define DEVU3_WAEKUP_EN BIT(14) +#define OTG_WAKEUP_EN BIT(12) +#define DEV_INT_EN (3 << 8) /* DEV INT b9:8 */ +#define HOST_INT1_EN (1 << 0) /* HOST INT b7:0 */ + +/* USB3_CORE_STATUS */ +#define MDCTRL_CLK_STATUS BIT(15) +#define DEV_POWER_ON_READY BIT(13) +#define HOST_POWER_ON_READY BIT(12) + +/* USB3_SSPHY_STATUS */ +#define CLK_VALID_MASK (0x3f << 26) +#define CLK_VALID_COMPARE_BITS (0xf << 28) +#define PHY_REFCLK_REQ (1 << 0) + +/* OTG registers definition */ +#define OTGSTS 0x4 +/* OTGSTS */ +#define OTG_NRDY BIT(11) + +/* xHCI registers definition */ +#define XECP_PM_PMCSR 0x8018 +#define XECP_AUX_CTRL_REG1 0x8120 + +/* Register bits definition */ +/* XECP_AUX_CTRL_REG1 */ +#define CFG_RXDET_P3_EN BIT(15) + +/* XECP_PM_PMCSR */ +#define PS_MASK GENMASK(1, 0) +#define PS_D0 0 +#define PS_D1 1 + +struct cdns_imx { + struct device *dev; + void __iomem *noncore; + struct clk_bulk_data *clks; + int num_clks; + struct platform_device *cdns3_pdev; +}; + +static inline u32 cdns_imx_readl(struct cdns_imx *data, u32 offset) +{ + return readl(data->noncore + offset); +} + +static inline void cdns_imx_writel(struct cdns_imx *data, u32 offset, u32 value) +{ + writel(value, data->noncore + offset); +} + +static const struct clk_bulk_data imx_cdns3_core_clks[] = { + { .id = "lpm" }, + { .id = "bus" }, + { .id = "aclk" }, + { .id = "ipg" }, + { .id = "core" }, +}; + +static int cdns_imx_noncore_init(struct cdns_imx *data) +{ + u32 value; + int ret; + struct device *dev = data->dev; + + cdns_imx_writel(data, USB3_SSPHY_STATUS, CLK_VALID_MASK); + udelay(1); + ret = readl_poll_timeout(data->noncore + USB3_SSPHY_STATUS, value, + (value & CLK_VALID_COMPARE_BITS) == CLK_VALID_COMPARE_BITS, + 10, 100000); + if (ret) { + dev_err(dev, "wait clkvld timeout\n"); + return ret; + } + + value = cdns_imx_readl(data, USB3_CORE_CTRL1); + value |= ALL_SW_RESET; + cdns_imx_writel(data, USB3_CORE_CTRL1, value); + udelay(1); + + value = cdns_imx_readl(data, USB3_CORE_CTRL1); + value = (value & ~MODE_STRAP_MASK) | OTG_MODE | OC_DISABLE; + cdns_imx_writel(data, USB3_CORE_CTRL1, value); + + value = cdns_imx_readl(data, USB3_INT_REG); + value |= HOST_INT1_EN | DEV_INT_EN; + cdns_imx_writel(data, USB3_INT_REG, value); + + value = cdns_imx_readl(data, USB3_CORE_CTRL1); + value &= ~ALL_SW_RESET; + cdns_imx_writel(data, USB3_CORE_CTRL1, value); + return ret; +} + +static int cdns_imx_platform_suspend(struct device *dev, + bool suspend, bool wakeup); +static struct cdns3_platform_data cdns_imx_pdata = { + .platform_suspend = cdns_imx_platform_suspend, + .quirks = CDNS3_DEFAULT_PM_RUNTIME_ALLOW, +}; + +static const struct of_dev_auxdata cdns_imx_auxdata[] = { + { + .compatible = "cdns,usb3", + .platform_data = &cdns_imx_pdata, + }, + {}, +}; + +static int cdns_imx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct cdns_imx *data; + int ret; + + if (!node) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + platform_set_drvdata(pdev, data); + data->dev = dev; + data->noncore = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(data->noncore)) { + dev_err(dev, "can't map IOMEM resource\n"); + return PTR_ERR(data->noncore); + } + + data->num_clks = ARRAY_SIZE(imx_cdns3_core_clks); + data->clks = devm_kmemdup(dev, imx_cdns3_core_clks, + sizeof(imx_cdns3_core_clks), GFP_KERNEL); + if (!data->clks) + return -ENOMEM; + + ret = devm_clk_bulk_get(dev, data->num_clks, data->clks); + if (ret) + return ret; + + ret = clk_bulk_prepare_enable(data->num_clks, data->clks); + if (ret) + return ret; + + ret = cdns_imx_noncore_init(data); + if (ret) + goto err; + + ret = of_platform_populate(node, NULL, cdns_imx_auxdata, dev); + if (ret) { + dev_err(dev, "failed to create children: %d\n", ret); + goto err; + } + + device_set_wakeup_capable(dev, true); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return ret; +err: + clk_bulk_disable_unprepare(data->num_clks, data->clks); + return ret; +} + +static void cdns_imx_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cdns_imx *data = dev_get_drvdata(dev); + + pm_runtime_get_sync(dev); + of_platform_depopulate(dev); + clk_bulk_disable_unprepare(data->num_clks, data->clks); + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + platform_set_drvdata(pdev, NULL); +} + +#ifdef CONFIG_PM +static void cdns3_set_wakeup(struct cdns_imx *data, bool enable) +{ + u32 value; + + value = cdns_imx_readl(data, USB3_INT_REG); + if (enable) + value |= OTG_WAKEUP_EN | DEVU3_WAEKUP_EN; + else + value &= ~(OTG_WAKEUP_EN | DEVU3_WAEKUP_EN); + + cdns_imx_writel(data, USB3_INT_REG, value); +} + +static int cdns_imx_platform_suspend(struct device *dev, + bool suspend, bool wakeup) +{ + struct cdns *cdns = dev_get_drvdata(dev); + struct device *parent = dev->parent; + struct cdns_imx *data = dev_get_drvdata(parent); + void __iomem *otg_regs = (void __iomem *)(cdns->otg_regs); + void __iomem *xhci_regs = cdns->xhci_regs; + u32 value; + int ret = 0; + + if (cdns->role != USB_ROLE_HOST) + return 0; + + if (suspend) { + /* SW request low power when all usb ports allow to it ??? */ + value = readl(xhci_regs + XECP_PM_PMCSR); + value &= ~PS_MASK; + value |= PS_D1; + writel(value, xhci_regs + XECP_PM_PMCSR); + + /* mdctrl_clk_sel */ + value = cdns_imx_readl(data, USB3_CORE_CTRL1); + value |= MDCTRL_CLK_SEL; + cdns_imx_writel(data, USB3_CORE_CTRL1, value); + + /* wait for mdctrl_clk_status */ + value = cdns_imx_readl(data, USB3_CORE_STATUS); + ret = readl_poll_timeout(data->noncore + USB3_CORE_STATUS, value, + (value & MDCTRL_CLK_STATUS) == MDCTRL_CLK_STATUS, + 10, 100000); + if (ret) + dev_warn(parent, "wait mdctrl_clk_status timeout\n"); + + /* wait lpm_clk_req to be 0 */ + value = cdns_imx_readl(data, USB3_INT_REG); + ret = readl_poll_timeout(data->noncore + USB3_INT_REG, value, + (value & LPM_CLK_REQ) != LPM_CLK_REQ, + 10, 100000); + if (ret) + dev_warn(parent, "wait lpm_clk_req timeout\n"); + + /* wait phy_refclk_req to be 0 */ + value = cdns_imx_readl(data, USB3_SSPHY_STATUS); + ret = readl_poll_timeout(data->noncore + USB3_SSPHY_STATUS, value, + (value & PHY_REFCLK_REQ) != PHY_REFCLK_REQ, + 10, 100000); + if (ret) + dev_warn(parent, "wait phy_refclk_req timeout\n"); + + cdns3_set_wakeup(data, wakeup); + } else { + cdns3_set_wakeup(data, false); + + /* SW request D0 */ + value = readl(xhci_regs + XECP_PM_PMCSR); + value &= ~PS_MASK; + value |= PS_D0; + writel(value, xhci_regs + XECP_PM_PMCSR); + + /* clr CFG_RXDET_P3_EN */ + value = readl(xhci_regs + XECP_AUX_CTRL_REG1); + value &= ~CFG_RXDET_P3_EN; + writel(value, xhci_regs + XECP_AUX_CTRL_REG1); + + /* clear mdctrl_clk_sel */ + value = cdns_imx_readl(data, USB3_CORE_CTRL1); + value &= ~MDCTRL_CLK_SEL; + cdns_imx_writel(data, USB3_CORE_CTRL1, value); + + /* wait CLK_125_REQ to be 1 */ + value = cdns_imx_readl(data, USB3_INT_REG); + ret = readl_poll_timeout(data->noncore + USB3_INT_REG, value, + (value & CLK_125_REQ) == CLK_125_REQ, + 10, 100000); + if (ret) + dev_warn(parent, "wait CLK_125_REQ timeout\n"); + + /* wait for mdctrl_clk_status is cleared */ + value = cdns_imx_readl(data, USB3_CORE_STATUS); + ret = readl_poll_timeout(data->noncore + USB3_CORE_STATUS, value, + (value & MDCTRL_CLK_STATUS) != MDCTRL_CLK_STATUS, + 10, 100000); + if (ret) + dev_warn(parent, "wait mdctrl_clk_status cleared timeout\n"); + + /* Wait until OTG_NRDY is 0 */ + value = readl(otg_regs + OTGSTS); + ret = readl_poll_timeout(otg_regs + OTGSTS, value, + (value & OTG_NRDY) != OTG_NRDY, + 10, 100000); + if (ret) + dev_warn(parent, "wait OTG ready timeout\n"); + } + + return ret; + +} + +static int cdns_imx_resume(struct device *dev) +{ + struct cdns_imx *data = dev_get_drvdata(dev); + + return clk_bulk_prepare_enable(data->num_clks, data->clks); +} + +static int cdns_imx_suspend(struct device *dev) +{ + struct cdns_imx *data = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(data->num_clks, data->clks); + + return 0; +} + + +/* Indicate if the controller was power lost before */ +static inline bool cdns_imx_is_power_lost(struct cdns_imx *data) +{ + u32 value; + + value = cdns_imx_readl(data, USB3_CORE_CTRL1); + if ((value & SW_RESET_MASK) == ALL_SW_RESET) + return true; + else + return false; +} + +static int __maybe_unused cdns_imx_system_suspend(struct device *dev) +{ + pm_runtime_put_sync(dev); + return 0; +} + +static int __maybe_unused cdns_imx_system_resume(struct device *dev) +{ + struct cdns_imx *data = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "Could not get runtime PM.\n"); + return ret; + } + + if (cdns_imx_is_power_lost(data)) { + dev_dbg(dev, "resume from power lost\n"); + ret = cdns_imx_noncore_init(data); + if (ret) + cdns_imx_suspend(dev); + } + + return ret; +} + +#else +static int cdns_imx_platform_suspend(struct device *dev, + bool suspend, bool wakeup) +{ + return 0; +} + +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops cdns_imx_pm_ops = { + SET_RUNTIME_PM_OPS(cdns_imx_suspend, cdns_imx_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(cdns_imx_system_suspend, cdns_imx_system_resume) +}; + +static const struct of_device_id cdns_imx_of_match[] = { + { .compatible = "fsl,imx8qm-usb3", }, + {}, +}; +MODULE_DEVICE_TABLE(of, cdns_imx_of_match); + +static struct platform_driver cdns_imx_driver = { + .probe = cdns_imx_probe, + .remove = cdns_imx_remove, + .driver = { + .name = "cdns3-imx", + .of_match_table = cdns_imx_of_match, + .pm = &cdns_imx_pm_ops, + }, +}; +module_platform_driver(cdns_imx_driver); + +MODULE_ALIAS("platform:cdns3-imx"); +MODULE_AUTHOR("Peter Chen "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Cadence USB3 i.MX Glue Layer"); diff --git a/drivers/usb/cdns3/cdns3-pci-wrap.c b/drivers/usb/cdns3/cdns3-pci-wrap.c new file mode 100644 index 000000000..eb5760f75 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-pci-wrap.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBSS PCI Glue driver + * + * Copyright (C) 2018-2019 Cadence. + * + * Author: Pawel Laszczak + */ + +#include +#include +#include +#include +#include +#include + +struct cdns3_wrap { + struct platform_device *plat_dev; + struct resource dev_res[6]; + int devfn; +}; + +#define RES_IRQ_HOST_ID 0 +#define RES_IRQ_PERIPHERAL_ID 1 +#define RES_IRQ_OTG_ID 2 +#define RES_HOST_ID 3 +#define RES_DEV_ID 4 +#define RES_DRD_ID 5 + +#define PCI_BAR_HOST 0 +#define PCI_BAR_DEV 2 +#define PCI_BAR_OTG 0 + +#define PCI_DEV_FN_HOST_DEVICE 0 +#define PCI_DEV_FN_OTG 1 + +#define PCI_DRIVER_NAME "cdns3-pci-usbss" +#define PLAT_DRIVER_NAME "cdns-usb3" + +static struct pci_dev *cdns3_get_second_fun(struct pci_dev *pdev) +{ + struct pci_dev *func; + + /* + * Gets the second function. + * It's little tricky, but this platform has two function. + * The fist keeps resources for Host/Device while the second + * keeps resources for DRD/OTG. + */ + func = pci_get_device(pdev->vendor, pdev->device, NULL); + if (unlikely(!func)) + return NULL; + + if (func->devfn == pdev->devfn) { + func = pci_get_device(pdev->vendor, pdev->device, func); + if (unlikely(!func)) + return NULL; + } + + if (func->devfn != PCI_DEV_FN_HOST_DEVICE && + func->devfn != PCI_DEV_FN_OTG) { + return NULL; + } + + return func; +} + +static int cdns3_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct platform_device_info plat_info; + struct cdns3_wrap *wrap; + struct resource *res; + struct pci_dev *func; + int err; + + /* + * for GADGET/HOST PCI (devfn) function number is 0, + * for OTG PCI (devfn) function number is 1 + */ + if (!id || (pdev->devfn != PCI_DEV_FN_HOST_DEVICE && + pdev->devfn != PCI_DEV_FN_OTG)) + return -EINVAL; + + func = cdns3_get_second_fun(pdev); + if (unlikely(!func)) + return -EINVAL; + + err = pcim_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "Enabling PCI device has failed %d\n", err); + return err; + } + + pci_set_master(pdev); + + if (pci_is_enabled(func)) { + wrap = pci_get_drvdata(func); + } else { + wrap = kzalloc_obj(*wrap); + if (!wrap) + return -ENOMEM; + } + + res = wrap->dev_res; + + if (pdev->devfn == PCI_DEV_FN_HOST_DEVICE) { + /* function 0: host(BAR_0) + device(BAR_1).*/ + dev_dbg(&pdev->dev, "Initialize Device resources\n"); + res[RES_DEV_ID].start = pci_resource_start(pdev, PCI_BAR_DEV); + res[RES_DEV_ID].end = pci_resource_end(pdev, PCI_BAR_DEV); + res[RES_DEV_ID].name = "dev"; + res[RES_DEV_ID].flags = IORESOURCE_MEM; + dev_dbg(&pdev->dev, "USBSS-DEV physical base addr: %pa\n", + &res[RES_DEV_ID].start); + + res[RES_HOST_ID].start = pci_resource_start(pdev, PCI_BAR_HOST); + res[RES_HOST_ID].end = pci_resource_end(pdev, PCI_BAR_HOST); + res[RES_HOST_ID].name = "xhci"; + res[RES_HOST_ID].flags = IORESOURCE_MEM; + dev_dbg(&pdev->dev, "USBSS-XHCI physical base addr: %pa\n", + &res[RES_HOST_ID].start); + + /* Interrupt for XHCI */ + wrap->dev_res[RES_IRQ_HOST_ID].start = pdev->irq; + wrap->dev_res[RES_IRQ_HOST_ID].name = "host"; + wrap->dev_res[RES_IRQ_HOST_ID].flags = IORESOURCE_IRQ; + + /* Interrupt device. It's the same as for HOST. */ + wrap->dev_res[RES_IRQ_PERIPHERAL_ID].start = pdev->irq; + wrap->dev_res[RES_IRQ_PERIPHERAL_ID].name = "peripheral"; + wrap->dev_res[RES_IRQ_PERIPHERAL_ID].flags = IORESOURCE_IRQ; + } else { + res[RES_DRD_ID].start = pci_resource_start(pdev, PCI_BAR_OTG); + res[RES_DRD_ID].end = pci_resource_end(pdev, PCI_BAR_OTG); + res[RES_DRD_ID].name = "otg"; + res[RES_DRD_ID].flags = IORESOURCE_MEM; + dev_dbg(&pdev->dev, "USBSS-DRD physical base addr: %pa\n", + &res[RES_DRD_ID].start); + + /* Interrupt for OTG/DRD. */ + wrap->dev_res[RES_IRQ_OTG_ID].start = pdev->irq; + wrap->dev_res[RES_IRQ_OTG_ID].name = "otg"; + wrap->dev_res[RES_IRQ_OTG_ID].flags = IORESOURCE_IRQ; + } + + if (pci_is_enabled(func)) { + /* set up platform device info */ + memset(&plat_info, 0, sizeof(plat_info)); + plat_info.parent = &pdev->dev; + plat_info.fwnode = pdev->dev.fwnode; + plat_info.name = PLAT_DRIVER_NAME; + plat_info.id = pdev->devfn; + wrap->devfn = pdev->devfn; + plat_info.res = wrap->dev_res; + plat_info.num_res = ARRAY_SIZE(wrap->dev_res); + plat_info.dma_mask = pdev->dma_mask; + /* register platform device */ + wrap->plat_dev = platform_device_register_full(&plat_info); + if (IS_ERR(wrap->plat_dev)) { + err = PTR_ERR(wrap->plat_dev); + kfree(wrap); + return err; + } + } + + pci_set_drvdata(pdev, wrap); + return err; +} + +static void cdns3_pci_remove(struct pci_dev *pdev) +{ + struct cdns3_wrap *wrap; + struct pci_dev *func; + + func = cdns3_get_second_fun(pdev); + + wrap = (struct cdns3_wrap *)pci_get_drvdata(pdev); + if (wrap->devfn == pdev->devfn) + platform_device_unregister(wrap->plat_dev); + + if (!pci_is_enabled(func)) + kfree(wrap); +} + +static const struct pci_device_id cdns3_pci_ids[] = { + { PCI_VDEVICE(CDNS, PCI_DEVICE_ID_CDNS_USBSS) }, + { 0, } +}; + +static struct pci_driver cdns3_pci_driver = { + .name = PCI_DRIVER_NAME, + .id_table = cdns3_pci_ids, + .probe = cdns3_pci_probe, + .remove = cdns3_pci_remove, +}; + +module_pci_driver(cdns3_pci_driver); +MODULE_DEVICE_TABLE(pci, cdns3_pci_ids); + +MODULE_AUTHOR("Pawel Laszczak "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Cadence USBSS PCI wrapper"); diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c new file mode 100644 index 000000000..e3f32c3e9 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-plat.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBSS DRD Driver. + * + * Copyright (C) 2018-2020 Cadence. + * Copyright (C) 2017-2018 NXP + * Copyright (C) 2019 Texas Instruments + * + * + * Author: Peter Chen + * Pawel Laszczak + * Roger Quadros + */ + +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "gadget-export.h" +#include "host-export.h" +#include "drd.h" + +static int set_phy_power_on(struct cdns *cdns) +{ + int ret; + + ret = phy_power_on(cdns->usb2_phy); + if (ret) + return ret; + + ret = phy_power_on(cdns->usb3_phy); + if (ret) + phy_power_off(cdns->usb2_phy); + + return ret; +} + +static void set_phy_power_off(struct cdns *cdns) +{ + phy_power_off(cdns->usb3_phy); + phy_power_off(cdns->usb2_phy); +} + +static int cdns3_plat_gadget_init(struct cdns *cdns) +{ + if (cdns->version < CDNSP_CONTROLLER_V2) + return cdns3_gadget_init(cdns); + else + return cdnsp_gadget_init(cdns); +} + +static int cdns3_plat_host_init(struct cdns *cdns) +{ + return cdns_host_init(cdns); +} + +/** + * cdns3_plat_probe - probe for cdns3 core device + * @pdev: Pointer to cdns3 core platform device + * + * Returns 0 on success otherwise negative errno + */ +static int cdns3_plat_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct cdns *cdns; + void __iomem *regs; + int ret; + + cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL); + if (!cdns) + return -ENOMEM; + + cdns->dev = dev; + cdns->pdata = dev_get_platdata(dev); + if (cdns->pdata && cdns->pdata->override_apb_timeout) + cdns->override_apb_timeout = cdns->pdata->override_apb_timeout; + + if (device_is_compatible(dev, "cdns,cdnsp")) { + cdns->no_drd = true; + cdns->version = CDNSP_CONTROLLER_V2; + dev_dbg(dev, "No DRD support\n"); + } + + platform_set_drvdata(pdev, cdns); + + ret = platform_get_irq_byname(pdev, "host"); + if (ret < 0) + return ret; + + cdns->xhci_res[0].start = ret; + cdns->xhci_res[0].end = ret; + cdns->xhci_res[0].flags = IORESOURCE_IRQ | irq_get_trigger_type(ret); + cdns->xhci_res[0].name = "host"; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "xhci"); + if (!res) { + dev_err(dev, "couldn't get xhci resource\n"); + return -ENXIO; + } + + cdns->xhci_res[1] = *res; + + cdns->dev_irq = platform_get_irq_byname(pdev, "peripheral"); + + if (cdns->dev_irq < 0) + return dev_err_probe(dev, cdns->dev_irq, + "Failed to get peripheral IRQ\n"); + + regs = devm_platform_ioremap_resource_byname(pdev, "dev"); + if (IS_ERR(regs)) + return dev_err_probe(dev, PTR_ERR(regs), + "Failed to get dev base\n"); + + cdns->dev_regs = regs; + + if (!cdns->no_drd) { + cdns->otg_irq = platform_get_irq_byname(pdev, "otg"); + if (cdns->otg_irq < 0) + return dev_err_probe(dev, cdns->otg_irq, + "Failed to get otg IRQ\n"); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg"); + if (!res) { + dev_err(dev, "couldn't get otg resource\n"); + return -ENXIO; + } + cdns->otg_res = *res; + } + + cdns->phyrst_a_enable = device_property_read_bool(dev, "cdns,phyrst-a-enable"); + + cdns->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup"); + if (cdns->wakeup_irq == -EPROBE_DEFER) + return cdns->wakeup_irq; + + if (cdns->wakeup_irq < 0) { + dev_dbg(dev, "couldn't get wakeup irq\n"); + cdns->wakeup_irq = 0x0; + } + + cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy"); + if (IS_ERR(cdns->usb2_phy)) + return dev_err_probe(dev, PTR_ERR(cdns->usb2_phy), + "Failed to get cdns3,usb2-phy\n"); + + cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy"); + if (IS_ERR(cdns->usb3_phy)) + return dev_err_probe(dev, PTR_ERR(cdns->usb3_phy), + "Failed to get cdns3,usb3-phy\n"); + + ret = phy_init(cdns->usb2_phy); + if (ret) + return ret; + + ret = phy_init(cdns->usb3_phy); + if (ret) + goto err_phy3_init; + + ret = set_phy_power_on(cdns); + if (ret) + goto err_phy_power_on; + + ret = cdns_init(cdns); + if (ret) + goto err_cdns_init; + + cdns->gadget_init = cdns3_plat_gadget_init; + cdns->host_init = cdns3_plat_host_init; + ret = cdns_core_init_role(cdns); + if (ret) + goto err_cdns_init_role; + + device_set_wakeup_capable(dev, true); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + if (!(cdns->pdata && (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW))) + pm_runtime_forbid(dev); + + /* + * The controller needs less time between bus and controller suspend, + * and we also needs a small delay to avoid frequently entering low + * power mode. + */ + pm_runtime_set_autosuspend_delay(dev, 20); + pm_runtime_mark_last_busy(dev); + pm_runtime_use_autosuspend(dev); + + return 0; + +err_cdns_init_role: + if (cdns->role_sw) + usb_role_switch_unregister(cdns->role_sw); +err_cdns_init: + set_phy_power_off(cdns); +err_phy_power_on: + phy_exit(cdns->usb3_phy); +err_phy3_init: + phy_exit(cdns->usb2_phy); + + return ret; +} + +/** + * cdns3_plat_remove() - unbind drd driver and clean up + * @pdev: Pointer to Linux platform device + */ +static void cdns3_plat_remove(struct platform_device *pdev) +{ + struct cdns *cdns = platform_get_drvdata(pdev); + struct device *dev = cdns->dev; + + pm_runtime_get_sync(dev); + if (!(cdns->pdata && (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW))) + pm_runtime_allow(dev); + + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + cdns_remove(cdns); + set_phy_power_off(cdns); + phy_exit(cdns->usb2_phy); + phy_exit(cdns->usb3_phy); +} + +#ifdef CONFIG_PM + +static int cdns3_set_platform_suspend(struct device *dev, + bool suspend, bool wakeup) +{ + struct cdns *cdns = dev_get_drvdata(dev); + int ret = 0; + + if (cdns->pdata && cdns->pdata->platform_suspend) + ret = cdns->pdata->platform_suspend(dev, suspend, wakeup); + + return ret; +} + +static int cdns3_controller_suspend(struct device *dev, pm_message_t msg) +{ + struct cdns *cdns = dev_get_drvdata(dev); + bool wakeup; + unsigned long flags; + + if (cdns->in_lpm) + return 0; + + if (PMSG_IS_AUTO(msg)) + wakeup = true; + else + wakeup = device_may_wakeup(dev); + + cdns3_set_platform_suspend(cdns->dev, true, wakeup); + set_phy_power_off(cdns); + spin_lock_irqsave(&cdns->lock, flags); + cdns->in_lpm = true; + spin_unlock_irqrestore(&cdns->lock, flags); + dev_dbg(cdns->dev, "%s ends\n", __func__); + + return 0; +} + +static int cdns3_controller_resume(struct device *dev, pm_message_t msg) +{ + struct cdns *cdns = dev_get_drvdata(dev); + int ret; + unsigned long flags; + + if (!cdns->in_lpm) + return 0; + + if (cdns_power_is_lost(cdns)) { + phy_exit(cdns->usb2_phy); + ret = phy_init(cdns->usb2_phy); + if (ret) + return ret; + + phy_exit(cdns->usb3_phy); + ret = phy_init(cdns->usb3_phy); + if (ret) + return ret; + } + + ret = set_phy_power_on(cdns); + if (ret) + return ret; + + cdns3_set_platform_suspend(cdns->dev, false, false); + + spin_lock_irqsave(&cdns->lock, flags); + cdns_resume(cdns); + cdns->in_lpm = false; + spin_unlock_irqrestore(&cdns->lock, flags); + cdns_set_active(cdns, !PMSG_IS_AUTO(msg)); + if (cdns->wakeup_pending) { + cdns->wakeup_pending = false; + enable_irq(cdns->wakeup_irq); + } + dev_dbg(cdns->dev, "%s ends\n", __func__); + + return ret; +} + +static int cdns3_plat_runtime_suspend(struct device *dev) +{ + return cdns3_controller_suspend(dev, PMSG_AUTO_SUSPEND); +} + +static int cdns3_plat_runtime_resume(struct device *dev) +{ + return cdns3_controller_resume(dev, PMSG_AUTO_RESUME); +} + +#ifdef CONFIG_PM_SLEEP + +static int cdns3_plat_suspend(struct device *dev) +{ + struct cdns *cdns = dev_get_drvdata(dev); + int ret; + + cdns_suspend(cdns); + + ret = cdns3_controller_suspend(dev, PMSG_SUSPEND); + if (ret) + return ret; + + if (device_may_wakeup(dev) && cdns->wakeup_irq) + enable_irq_wake(cdns->wakeup_irq); + + return ret; +} + +static int cdns3_plat_resume(struct device *dev) +{ + return cdns3_controller_resume(dev, PMSG_RESUME); +} +#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops cdns3_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cdns3_plat_suspend, cdns3_plat_resume) + SET_RUNTIME_PM_OPS(cdns3_plat_runtime_suspend, + cdns3_plat_runtime_resume, NULL) +}; + +#ifdef CONFIG_OF +static const struct of_device_id of_cdns3_match[] = { + { .compatible = "cdns,usb3" }, + { .compatible = "cdns,cdnsp" }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_cdns3_match); +#endif + +static struct platform_driver cdns3_driver = { + .probe = cdns3_plat_probe, + .remove = cdns3_plat_remove, + .driver = { + .name = "cdns-usb3", + .of_match_table = of_match_ptr(of_cdns3_match), + .pm = &cdns3_pm_ops, + }, +}; + +module_platform_driver(cdns3_driver); + +MODULE_ALIAS("platform:cdns3"); diff --git a/drivers/usb/cdns3/cdns3-starfive.c b/drivers/usb/cdns3/cdns3-starfive.c new file mode 100644 index 000000000..2ff7f2b48 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-starfive.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cdns3-starfive.c - StarFive specific Glue layer for Cadence USB Controller + * + * Copyright (C) 2023 StarFive Technology Co., Ltd. + * + * Author: Minda Chen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "core.h" + +#define USB_STRAP_HOST BIT(17) +#define USB_STRAP_DEVICE BIT(18) +#define USB_STRAP_MASK GENMASK(18, 16) + +#define USB_SUSPENDM_HOST BIT(19) +#define USB_SUSPENDM_MASK BIT(19) + +#define USB_MISC_CFG_MASK GENMASK(23, 20) +#define USB_SUSPENDM_BYPS BIT(20) +#define USB_PLL_EN BIT(22) +#define USB_REFCLK_MODE BIT(23) + +struct cdns_starfive { + struct device *dev; + struct regmap *stg_syscon; + struct reset_control *resets; + struct clk_bulk_data *clks; + int num_clks; + u32 stg_usb_mode; +}; + +static void cdns_mode_init(struct platform_device *pdev, + struct cdns_starfive *data) +{ + enum usb_dr_mode mode; + + regmap_update_bits(data->stg_syscon, data->stg_usb_mode, + USB_MISC_CFG_MASK, + USB_SUSPENDM_BYPS | USB_PLL_EN | USB_REFCLK_MODE); + + /* dr mode setting */ + mode = usb_get_dr_mode(&pdev->dev); + + switch (mode) { + case USB_DR_MODE_HOST: + regmap_update_bits(data->stg_syscon, + data->stg_usb_mode, + USB_STRAP_MASK, + USB_STRAP_HOST); + regmap_update_bits(data->stg_syscon, + data->stg_usb_mode, + USB_SUSPENDM_MASK, + USB_SUSPENDM_HOST); + break; + + case USB_DR_MODE_PERIPHERAL: + regmap_update_bits(data->stg_syscon, data->stg_usb_mode, + USB_STRAP_MASK, USB_STRAP_DEVICE); + regmap_update_bits(data->stg_syscon, data->stg_usb_mode, + USB_SUSPENDM_MASK, 0); + break; + default: + break; + } +} + +static int cdns_clk_rst_init(struct cdns_starfive *data) +{ + int ret; + + ret = clk_bulk_prepare_enable(data->num_clks, data->clks); + if (ret) + return dev_err_probe(data->dev, ret, + "failed to enable clocks\n"); + + ret = reset_control_deassert(data->resets); + if (ret) { + dev_err(data->dev, "failed to reset clocks\n"); + goto err_clk_init; + } + + return ret; + +err_clk_init: + clk_bulk_disable_unprepare(data->num_clks, data->clks); + return ret; +} + +static void cdns_clk_rst_deinit(struct cdns_starfive *data) +{ + reset_control_assert(data->resets); + clk_bulk_disable_unprepare(data->num_clks, data->clks); +} + +static int cdns_starfive_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cdns_starfive *data; + unsigned int args; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev; + + data->stg_syscon = + syscon_regmap_lookup_by_phandle_args(pdev->dev.of_node, + "starfive,stg-syscon", 1, &args); + + if (IS_ERR(data->stg_syscon)) + return dev_err_probe(dev, PTR_ERR(data->stg_syscon), + "Failed to parse starfive,stg-syscon\n"); + + data->stg_usb_mode = args; + + data->num_clks = devm_clk_bulk_get_all(data->dev, &data->clks); + if (data->num_clks < 0) + return dev_err_probe(data->dev, -ENODEV, + "Failed to get clocks\n"); + + data->resets = devm_reset_control_array_get_exclusive(data->dev); + if (IS_ERR(data->resets)) + return dev_err_probe(data->dev, PTR_ERR(data->resets), + "Failed to get resets"); + + cdns_mode_init(pdev, data); + ret = cdns_clk_rst_init(data); + if (ret) + return ret; + + ret = of_platform_populate(dev->of_node, NULL, NULL, dev); + if (ret) { + dev_err(dev, "Failed to create children\n"); + cdns_clk_rst_deinit(data); + return ret; + } + + device_set_wakeup_capable(dev, true); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + platform_set_drvdata(pdev, data); + + return 0; +} + +static int cdns_starfive_remove_core(struct device *dev, void *c) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + + return 0; +} + +static void cdns_starfive_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cdns_starfive *data = dev_get_drvdata(dev); + + pm_runtime_get_sync(dev); + device_for_each_child(dev, NULL, cdns_starfive_remove_core); + + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + cdns_clk_rst_deinit(data); + platform_set_drvdata(pdev, NULL); +} + +#ifdef CONFIG_PM +static int cdns_starfive_runtime_resume(struct device *dev) +{ + struct cdns_starfive *data = dev_get_drvdata(dev); + + return clk_bulk_prepare_enable(data->num_clks, data->clks); +} + +static int cdns_starfive_runtime_suspend(struct device *dev) +{ + struct cdns_starfive *data = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(data->num_clks, data->clks); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int cdns_starfive_resume(struct device *dev) +{ + struct cdns_starfive *data = dev_get_drvdata(dev); + + return cdns_clk_rst_init(data); +} + +static int cdns_starfive_suspend(struct device *dev) +{ + struct cdns_starfive *data = dev_get_drvdata(dev); + + cdns_clk_rst_deinit(data); + + return 0; +} +#endif +#endif + +static const struct dev_pm_ops cdns_starfive_pm_ops = { + SET_RUNTIME_PM_OPS(cdns_starfive_runtime_suspend, + cdns_starfive_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(cdns_starfive_suspend, cdns_starfive_resume) +}; + +static const struct of_device_id cdns_starfive_of_match[] = { + { .compatible = "starfive,jh7110-usb", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cdns_starfive_of_match); + +static struct platform_driver cdns_starfive_driver = { + .probe = cdns_starfive_probe, + .remove = cdns_starfive_remove, + .driver = { + .name = "cdns3-starfive", + .of_match_table = cdns_starfive_of_match, + .pm = &cdns_starfive_pm_ops, + }, +}; +module_platform_driver(cdns_starfive_driver); + +MODULE_ALIAS("platform:cdns3-starfive"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Cadence USB3 StarFive Glue Layer"); diff --git a/drivers/usb/cdns3/cdns3-ti.c b/drivers/usb/cdns3/cdns3-ti.c new file mode 100644 index 000000000..302ebf6d8 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-ti.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cdns3-ti.c - TI specific Glue layer for Cadence USB Controller + * + * Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "core.h" + +/* USB Wrapper register offsets */ +#define USBSS_PID 0x0 +#define USBSS_W1 0x4 +#define USBSS_STATIC_CONFIG 0x8 +#define USBSS_PHY_TEST 0xc +#define USBSS_DEBUG_CTRL 0x10 +#define USBSS_DEBUG_INFO 0x14 +#define USBSS_DEBUG_LINK_STATE 0x18 +#define USBSS_DEVICE_CTRL 0x1c + +/* Wrapper 1 register bits */ +#define USBSS_W1_PWRUP_RST BIT(0) +#define USBSS_W1_OVERCURRENT_SEL BIT(8) +#define USBSS_W1_MODESTRAP_SEL BIT(9) +#define USBSS_W1_OVERCURRENT BIT(16) +#define USBSS_W1_MODESTRAP_MASK GENMASK(18, 17) +#define USBSS_W1_MODESTRAP_SHIFT 17 +#define USBSS_W1_USB2_ONLY BIT(19) + +/* Static config register bits */ +#define USBSS1_STATIC_PLL_REF_SEL_MASK GENMASK(8, 5) +#define USBSS1_STATIC_PLL_REF_SEL_SHIFT 5 +#define USBSS1_STATIC_LOOPBACK_MODE_MASK GENMASK(4, 3) +#define USBSS1_STATIC_LOOPBACK_MODE_SHIFT 3 +#define USBSS1_STATIC_VBUS_SEL_MASK GENMASK(2, 1) +#define USBSS1_STATIC_VBUS_SEL_SHIFT 1 +#define USBSS1_STATIC_LANE_REVERSE BIT(0) + +/* Modestrap modes */ +enum modestrap_mode { USBSS_MODESTRAP_MODE_NONE, + USBSS_MODESTRAP_MODE_HOST, + USBSS_MODESTRAP_MODE_PERIPHERAL}; + +struct cdns_ti { + struct device *dev; + void __iomem *usbss; + unsigned usb2_only:1; + unsigned vbus_divider:1; + struct clk *usb2_refclk; + struct clk *lpm_clk; + int usb2_refclk_rate_code; +}; + +static const int cdns_ti_rate_table[] = { /* in KHZ */ + 9600, + 10000, + 12000, + 19200, + 20000, + 24000, + 25000, + 26000, + 38400, + 40000, + 58000, + 50000, + 52000, +}; + +static inline u32 cdns_ti_readl(struct cdns_ti *data, u32 offset) +{ + return readl(data->usbss + offset); +} + +static inline void cdns_ti_writel(struct cdns_ti *data, u32 offset, u32 value) +{ + writel(value, data->usbss + offset); +} + +static struct cdns3_platform_data cdns_ti_pdata = { + .quirks = CDNS3_DRD_SUSPEND_RESIDENCY_ENABLE, /* Errata i2409 */ +}; + +static const struct of_dev_auxdata cdns_ti_auxdata[] = { + { + .compatible = "cdns,usb3", + .platform_data = &cdns_ti_pdata, + }, + {}, +}; + +static void cdns_ti_reset_and_init_hw(struct cdns_ti *data) +{ + u32 reg; + + /* assert RESET */ + reg = cdns_ti_readl(data, USBSS_W1); + reg &= ~USBSS_W1_PWRUP_RST; + cdns_ti_writel(data, USBSS_W1, reg); + + /* set static config */ + reg = cdns_ti_readl(data, USBSS_STATIC_CONFIG); + reg &= ~USBSS1_STATIC_PLL_REF_SEL_MASK; + reg |= data->usb2_refclk_rate_code << USBSS1_STATIC_PLL_REF_SEL_SHIFT; + + reg &= ~USBSS1_STATIC_VBUS_SEL_MASK; + if (data->vbus_divider) + reg |= 1 << USBSS1_STATIC_VBUS_SEL_SHIFT; + + cdns_ti_writel(data, USBSS_STATIC_CONFIG, reg); + reg = cdns_ti_readl(data, USBSS_STATIC_CONFIG); + + /* set USB2_ONLY mode if requested */ + reg = cdns_ti_readl(data, USBSS_W1); + if (data->usb2_only) + reg |= USBSS_W1_USB2_ONLY; + + /* set default modestrap */ + reg |= USBSS_W1_MODESTRAP_SEL; + reg &= ~USBSS_W1_MODESTRAP_MASK; + reg |= USBSS_MODESTRAP_MODE_NONE << USBSS_W1_MODESTRAP_SHIFT; + cdns_ti_writel(data, USBSS_W1, reg); + + /* de-assert RESET */ + reg |= USBSS_W1_PWRUP_RST; + cdns_ti_writel(data, USBSS_W1, reg); +} + +static int cdns_ti_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = pdev->dev.of_node; + struct cdns_ti *data; + unsigned long rate; + int error, i; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + platform_set_drvdata(pdev, data); + + data->dev = dev; + + data->usbss = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(data->usbss)) { + dev_err(dev, "can't map IOMEM resource\n"); + return PTR_ERR(data->usbss); + } + + data->usb2_refclk = devm_clk_get(dev, "ref"); + if (IS_ERR(data->usb2_refclk)) { + dev_err(dev, "can't get usb2_refclk\n"); + return PTR_ERR(data->usb2_refclk); + } + + data->lpm_clk = devm_clk_get(dev, "lpm"); + if (IS_ERR(data->lpm_clk)) { + dev_err(dev, "can't get lpm_clk\n"); + return PTR_ERR(data->lpm_clk); + } + + rate = clk_get_rate(data->usb2_refclk); + rate /= 1000; /* To KHz */ + for (i = 0; i < ARRAY_SIZE(cdns_ti_rate_table); i++) { + if (cdns_ti_rate_table[i] == rate) + break; + } + + if (i == ARRAY_SIZE(cdns_ti_rate_table)) { + dev_err(dev, "unsupported usb2_refclk rate: %lu KHz\n", rate); + return -EINVAL; + } + + data->usb2_refclk_rate_code = i; + data->vbus_divider = device_property_read_bool(dev, "ti,vbus-divider"); + data->usb2_only = device_property_read_bool(dev, "ti,usb2-only"); + + /* + * The call below to pm_runtime_get_sync() MIGHT reset hardware, if it + * detects it as uninitialised. We want to enforce a reset at probe, + * and so do it manually here. This means the first runtime_resume() + * will be a no-op. + */ + cdns_ti_reset_and_init_hw(data); + + pm_runtime_enable(dev); + error = pm_runtime_get_sync(dev); + if (error < 0) { + dev_err(dev, "pm_runtime_get_sync failed: %d\n", error); + goto err; + } + + error = of_platform_populate(node, NULL, cdns_ti_auxdata, dev); + if (error) { + dev_err(dev, "failed to create children: %d\n", error); + goto err; + } + + return 0; + +err: + pm_runtime_put_sync(data->dev); + pm_runtime_disable(data->dev); + + return error; +} + +static int cdns_ti_remove_core(struct device *dev, void *c) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + + return 0; +} + +static void cdns_ti_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + device_for_each_child(dev, NULL, cdns_ti_remove_core); + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + platform_set_drvdata(pdev, NULL); +} + +static int cdns_ti_runtime_resume(struct device *dev) +{ + const u32 mask = USBSS_W1_PWRUP_RST | USBSS_W1_MODESTRAP_SEL; + struct cdns_ti *data = dev_get_drvdata(dev); + u32 w1; + + w1 = cdns_ti_readl(data, USBSS_W1); + if ((w1 & mask) != mask) + cdns_ti_reset_and_init_hw(data); + + return 0; +} + +static const struct dev_pm_ops cdns_ti_pm_ops = { + RUNTIME_PM_OPS(NULL, cdns_ti_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) +}; + +static const struct of_device_id cdns_ti_of_match[] = { + { .compatible = "ti,j721e-usb", }, + { .compatible = "ti,am64-usb", }, + {}, +}; +MODULE_DEVICE_TABLE(of, cdns_ti_of_match); + +static struct platform_driver cdns_ti_driver = { + .probe = cdns_ti_probe, + .remove = cdns_ti_remove, + .driver = { + .name = "cdns3-ti", + .of_match_table = cdns_ti_of_match, + .pm = pm_ptr(&cdns_ti_pm_ops), + }, +}; + +module_platform_driver(cdns_ti_driver); + +MODULE_ALIAS("platform:cdns3-ti"); +MODULE_AUTHOR("Roger Quadros "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Cadence USB3 TI Glue Layer"); diff --git a/drivers/usb/cdns3/cdns3-trace.c b/drivers/usb/cdns3/cdns3-trace.c new file mode 100644 index 000000000..b9858acae --- /dev/null +++ b/drivers/usb/cdns3/cdns3-trace.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USBSS device controller driver Trace Support + * + * Copyright (C) 2018-2019 Cadence. + * + * Author: Pawel Laszczak + */ + +#define CREATE_TRACE_POINTS +#include "cdns3-trace.h" diff --git a/drivers/usb/cdns3/cdns3-trace.h b/drivers/usb/cdns3/cdns3-trace.h new file mode 100644 index 000000000..21a6a2ce7 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-trace.h @@ -0,0 +1,496 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * USBSS device controller driver. + * Trace support header file. + * + * Copyright (C) 2018-2019 Cadence. + * + * Author: Pawel Laszczak + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM cdns3 + +#if !defined(__LINUX_CDNS3_TRACE) || defined(TRACE_HEADER_MULTI_READ) +#define __LINUX_CDNS3_TRACE + +#include +#include +#include +#include +#include "core.h" +#include "cdns3-gadget.h" +#include "cdns3-debug.h" + +#define CDNS3_MSG_MAX 500 + +TRACE_EVENT(cdns3_halt, + TP_PROTO(struct cdns3_endpoint *ep_priv, u8 halt, u8 flush), + TP_ARGS(ep_priv, halt, flush), + TP_STRUCT__entry( + __string(name, ep_priv->name) + __field(u8, halt) + __field(u8, flush) + ), + TP_fast_assign( + __assign_str(name); + __entry->halt = halt; + __entry->flush = flush; + ), + TP_printk("Halt %s for %s: %s", __entry->flush ? " and flush" : "", + __get_str(name), __entry->halt ? "set" : "cleared") +); + +TRACE_EVENT(cdns3_wa1, + TP_PROTO(struct cdns3_endpoint *ep_priv, char *msg), + TP_ARGS(ep_priv, msg), + TP_STRUCT__entry( + __string(ep_name, ep_priv->name) + __string(msg, msg) + ), + TP_fast_assign( + __assign_str(ep_name); + __assign_str(msg); + ), + TP_printk("WA1: %s %s", __get_str(ep_name), __get_str(msg)) +); + +TRACE_EVENT(cdns3_wa2, + TP_PROTO(struct cdns3_endpoint *ep_priv, char *msg), + TP_ARGS(ep_priv, msg), + TP_STRUCT__entry( + __string(ep_name, ep_priv->name) + __string(msg, msg) + ), + TP_fast_assign( + __assign_str(ep_name); + __assign_str(msg); + ), + TP_printk("WA2: %s %s", __get_str(ep_name), __get_str(msg)) +); + +DECLARE_EVENT_CLASS(cdns3_log_doorbell, + TP_PROTO(const char *ep_name, u32 ep_trbaddr), + TP_ARGS(ep_name, ep_trbaddr), + TP_STRUCT__entry( + __string(name, ep_name) + __field(u32, ep_trbaddr) + ), + TP_fast_assign( + __assign_str(name); + __entry->ep_trbaddr = ep_trbaddr; + ), + TP_printk("%s, ep_trbaddr %08x", __get_str(name), + __entry->ep_trbaddr) +); + +DEFINE_EVENT(cdns3_log_doorbell, cdns3_doorbell_ep0, + TP_PROTO(const char *ep_name, u32 ep_trbaddr), + TP_ARGS(ep_name, ep_trbaddr) +); + +DEFINE_EVENT(cdns3_log_doorbell, cdns3_doorbell_epx, + TP_PROTO(const char *ep_name, u32 ep_trbaddr), + TP_ARGS(ep_name, ep_trbaddr) +); + +DECLARE_EVENT_CLASS(cdns3_log_usb_irq, + TP_PROTO(struct cdns3_device *priv_dev, u32 usb_ists), + TP_ARGS(priv_dev, usb_ists), + TP_STRUCT__entry( + __field(enum usb_device_speed, speed) + __field(u32, usb_ists) + ), + TP_fast_assign( + __entry->speed = cdns3_get_speed(priv_dev); + __entry->usb_ists = usb_ists; + ), + TP_printk("%s", cdns3_decode_usb_irq(__get_buf(CDNS3_MSG_MAX), __entry->speed, + __entry->usb_ists)) +); + +DEFINE_EVENT(cdns3_log_usb_irq, cdns3_usb_irq, + TP_PROTO(struct cdns3_device *priv_dev, u32 usb_ists), + TP_ARGS(priv_dev, usb_ists) +); + +DECLARE_EVENT_CLASS(cdns3_log_epx_irq, + TP_PROTO(struct cdns3_device *priv_dev, struct cdns3_endpoint *priv_ep), + TP_ARGS(priv_dev, priv_ep), + TP_STRUCT__entry( + __string(ep_name, priv_ep->name) + __field(u32, ep_sts) + __field(u32, ep_traddr) + __field(u32, ep_last_sid) + __field(u32, use_streams) + ), + TP_fast_assign( + __assign_str(ep_name); + __entry->ep_sts = readl(&priv_dev->regs->ep_sts); + __entry->ep_traddr = readl(&priv_dev->regs->ep_traddr); + __entry->ep_last_sid = priv_ep->last_stream_id; + __entry->use_streams = priv_ep->use_streams; + ), + TP_printk("%s, ep_traddr: %08x ep_last_sid: %08x use_streams: %d", + cdns3_decode_epx_irq(__get_buf(CDNS3_MSG_MAX), + __get_str(ep_name), + __entry->ep_sts), + __entry->ep_traddr, + __entry->ep_last_sid, + __entry->use_streams) +); + +DEFINE_EVENT(cdns3_log_epx_irq, cdns3_epx_irq, + TP_PROTO(struct cdns3_device *priv_dev, struct cdns3_endpoint *priv_ep), + TP_ARGS(priv_dev, priv_ep) +); + +DECLARE_EVENT_CLASS(cdns3_log_ep0_irq, + TP_PROTO(struct cdns3_device *priv_dev, u32 ep_sts), + TP_ARGS(priv_dev, ep_sts), + TP_STRUCT__entry( + __field(int, ep_dir) + __field(u32, ep_sts) + ), + TP_fast_assign( + __entry->ep_dir = priv_dev->selected_ep; + __entry->ep_sts = ep_sts; + ), + TP_printk("%s", cdns3_decode_ep0_irq(__get_buf(CDNS3_MSG_MAX), + __entry->ep_dir, + __entry->ep_sts)) +); + +DEFINE_EVENT(cdns3_log_ep0_irq, cdns3_ep0_irq, + TP_PROTO(struct cdns3_device *priv_dev, u32 ep_sts), + TP_ARGS(priv_dev, ep_sts) +); + +DECLARE_EVENT_CLASS(cdns3_log_ctrl, + TP_PROTO(struct usb_ctrlrequest *ctrl), + TP_ARGS(ctrl), + TP_STRUCT__entry( + __field(u8, bRequestType) + __field(u8, bRequest) + __field(u16, wValue) + __field(u16, wIndex) + __field(u16, wLength) + ), + TP_fast_assign( + __entry->bRequestType = ctrl->bRequestType; + __entry->bRequest = ctrl->bRequest; + __entry->wValue = le16_to_cpu(ctrl->wValue); + __entry->wIndex = le16_to_cpu(ctrl->wIndex); + __entry->wLength = le16_to_cpu(ctrl->wLength); + ), + TP_printk("%s", usb_decode_ctrl(__get_buf(CDNS3_MSG_MAX), CDNS3_MSG_MAX, + __entry->bRequestType, + __entry->bRequest, __entry->wValue, + __entry->wIndex, __entry->wLength) + ) +); + +DEFINE_EVENT(cdns3_log_ctrl, cdns3_ctrl_req, + TP_PROTO(struct usb_ctrlrequest *ctrl), + TP_ARGS(ctrl) +); + +DECLARE_EVENT_CLASS(cdns3_log_request, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req), + TP_STRUCT__entry( + __string(name, req->priv_ep->name) + __field(struct cdns3_request *, req) + __field(void *, buf) + __field(unsigned int, actual) + __field(unsigned int, length) + __field(int, status) + __field(int, zero) + __field(int, short_not_ok) + __field(int, no_interrupt) + __field(int, start_trb) + __field(int, end_trb) + __field(int, flags) + __field(unsigned int, stream_id) + ), + TP_fast_assign( + __assign_str(name); + __entry->req = req; + __entry->buf = req->request.buf; + __entry->actual = req->request.actual; + __entry->length = req->request.length; + __entry->status = req->request.status; + __entry->zero = req->request.zero; + __entry->short_not_ok = req->request.short_not_ok; + __entry->no_interrupt = req->request.no_interrupt; + __entry->start_trb = req->start_trb; + __entry->end_trb = req->end_trb; + __entry->flags = req->flags; + __entry->stream_id = req->request.stream_id; + ), + TP_printk("%s: req: %p, req buff %p, length: %u/%u %s%s%s, status: %d," + " trb: [start:%d, end:%d], flags:%x SID: %u", + __get_str(name), __entry->req, __entry->buf, __entry->actual, + __entry->length, + __entry->zero ? "Z" : "z", + __entry->short_not_ok ? "S" : "s", + __entry->no_interrupt ? "I" : "i", + __entry->status, + __entry->start_trb, + __entry->end_trb, + __entry->flags, + __entry->stream_id + ) +); + +DEFINE_EVENT(cdns3_log_request, cdns3_alloc_request, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdns3_log_request, cdns3_free_request, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdns3_log_request, cdns3_ep_queue, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdns3_log_request, cdns3_ep_dequeue, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdns3_log_request, cdns3_gadget_giveback, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +TRACE_EVENT(cdns3_ep0_queue, + TP_PROTO(struct cdns3_device *dev_priv, struct usb_request *request), + TP_ARGS(dev_priv, request), + TP_STRUCT__entry( + __field(int, dir) + __field(int, length) + ), + TP_fast_assign( + __entry->dir = dev_priv->ep0_data_dir; + __entry->length = request->length; + ), + TP_printk("Queue to ep0%s length: %u", __entry->dir ? "in" : "out", + __entry->length) +); + +DECLARE_EVENT_CLASS(cdns3_log_aligned_request, + TP_PROTO(struct cdns3_request *priv_req), + TP_ARGS(priv_req), + TP_STRUCT__entry( + __string(name, priv_req->priv_ep->name) + __field(struct usb_request *, req) + __field(void *, buf) + __field(dma_addr_t, dma) + __field(void *, aligned_buf) + __field(dma_addr_t, aligned_dma) + __field(u32, aligned_buf_size) + ), + TP_fast_assign( + __assign_str(name); + __entry->req = &priv_req->request; + __entry->buf = priv_req->request.buf; + __entry->dma = priv_req->request.dma; + __entry->aligned_buf = priv_req->aligned_buf->buf; + __entry->aligned_dma = priv_req->aligned_buf->dma; + __entry->aligned_buf_size = priv_req->aligned_buf->size; + ), + TP_printk("%s: req: %p, req buf %p, dma %pad a_buf %p a_dma %pad, size %d", + __get_str(name), __entry->req, __entry->buf, &__entry->dma, + __entry->aligned_buf, &__entry->aligned_dma, + __entry->aligned_buf_size + ) +); + +DEFINE_EVENT(cdns3_log_aligned_request, cdns3_free_aligned_request, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdns3_log_aligned_request, cdns3_prepare_aligned_request, + TP_PROTO(struct cdns3_request *req), + TP_ARGS(req) +); + +DECLARE_EVENT_CLASS(cdns3_log_trb, + TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb), + TP_ARGS(priv_ep, trb), + TP_STRUCT__entry( + __string(name, priv_ep->name) + __field(struct cdns3_trb *, trb) + __field(u32, buffer) + __field(u32, length) + __field(u32, control) + __field(u32, type) + __field(unsigned int, last_stream_id) + ), + TP_fast_assign( + __assign_str(name); + __entry->trb = trb; + __entry->buffer = le32_to_cpu(trb->buffer); + __entry->length = le32_to_cpu(trb->length); + __entry->control = le32_to_cpu(trb->control); + __entry->type = usb_endpoint_type(priv_ep->endpoint.desc); + __entry->last_stream_id = priv_ep->last_stream_id; + ), + TP_printk("%s: trb %p, dma buf: 0x%08x, size: %ld, burst: %d ctrl: 0x%08x (%s%s%s%s%s%s%s) SID:%lu LAST_SID:%u", + __get_str(name), __entry->trb, __entry->buffer, + TRB_LEN(__entry->length), + (u8)TRB_BURST_LEN_GET(__entry->length), + __entry->control, + __entry->control & TRB_CYCLE ? "C=1, " : "C=0, ", + __entry->control & TRB_TOGGLE ? "T=1, " : "T=0, ", + __entry->control & TRB_ISP ? "ISP, " : "", + __entry->control & TRB_FIFO_MODE ? "FIFO, " : "", + __entry->control & TRB_CHAIN ? "CHAIN, " : "", + __entry->control & TRB_IOC ? "IOC, " : "", + TRB_FIELD_TO_TYPE(__entry->control) == TRB_NORMAL ? "Normal" : "LINK", + TRB_FIELD_TO_STREAMID(__entry->control), + __entry->last_stream_id + ) +); + +DEFINE_EVENT(cdns3_log_trb, cdns3_prepare_trb, + TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb), + TP_ARGS(priv_ep, trb) +); + +DEFINE_EVENT(cdns3_log_trb, cdns3_complete_trb, + TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb), + TP_ARGS(priv_ep, trb) +); + +DECLARE_EVENT_CLASS(cdns3_log_ring, + TP_PROTO(struct cdns3_endpoint *priv_ep), + TP_ARGS(priv_ep), + TP_STRUCT__entry( + __dynamic_array(char, buffer, + GET_TRBS_PER_SEGMENT(priv_ep->type) > TRBS_PER_SEGMENT ? + CDNS3_MSG_MAX : + (GET_TRBS_PER_SEGMENT(priv_ep->type) * 65) + CDNS3_MSG_MAX) + ), + TP_fast_assign( + cdns3_dbg_ring(priv_ep, __get_str(buffer)); + ), + + TP_printk("%s", __get_str(buffer)) +); + +DEFINE_EVENT(cdns3_log_ring, cdns3_ring, + TP_PROTO(struct cdns3_endpoint *priv_ep), + TP_ARGS(priv_ep) +); + +DECLARE_EVENT_CLASS(cdns3_log_ep, + TP_PROTO(struct cdns3_endpoint *priv_ep), + TP_ARGS(priv_ep), + TP_STRUCT__entry( + __string(name, priv_ep->name) + __field(unsigned int, maxpacket) + __field(unsigned int, maxpacket_limit) + __field(unsigned int, max_streams) + __field(unsigned int, use_streams) + __field(unsigned int, maxburst) + __field(unsigned int, flags) + __field(unsigned int, dir) + __field(u8, enqueue) + __field(u8, dequeue) + ), + TP_fast_assign( + __assign_str(name); + __entry->maxpacket = priv_ep->endpoint.maxpacket; + __entry->maxpacket_limit = priv_ep->endpoint.maxpacket_limit; + __entry->max_streams = priv_ep->endpoint.max_streams; + __entry->use_streams = priv_ep->use_streams; + __entry->maxburst = priv_ep->endpoint.maxburst; + __entry->flags = priv_ep->flags; + __entry->dir = priv_ep->dir; + __entry->enqueue = priv_ep->enqueue; + __entry->dequeue = priv_ep->dequeue; + ), + TP_printk("%s: mps: %d/%d. streams: %d, stream enable: %d, burst: %d, " + "enq idx: %d, deq idx: %d, flags %s%s%s%s%s%s%s%s, dir: %s", + __get_str(name), __entry->maxpacket, + __entry->maxpacket_limit, __entry->max_streams, + __entry->use_streams, + __entry->maxburst, __entry->enqueue, + __entry->dequeue, + __entry->flags & EP_ENABLED ? "EN | " : "", + __entry->flags & EP_STALLED ? "STALLED | " : "", + __entry->flags & EP_WEDGE ? "WEDGE | " : "", + __entry->flags & EP_TRANSFER_STARTED ? "STARTED | " : "", + __entry->flags & EP_UPDATE_EP_TRBADDR ? "UPD TRB | " : "", + __entry->flags & EP_PENDING_REQUEST ? "REQ PEN | " : "", + __entry->flags & EP_RING_FULL ? "RING FULL |" : "", + __entry->flags & EP_CLAIMED ? "CLAIMED " : "", + __entry->dir ? "IN" : "OUT" + ) +); + +DEFINE_EVENT(cdns3_log_ep, cdns3_gadget_ep_enable, + TP_PROTO(struct cdns3_endpoint *priv_ep), + TP_ARGS(priv_ep) +); + +DEFINE_EVENT(cdns3_log_ep, cdns3_gadget_ep_disable, + TP_PROTO(struct cdns3_endpoint *priv_ep), + TP_ARGS(priv_ep) +); + +DECLARE_EVENT_CLASS(cdns3_log_request_handled, + TP_PROTO(struct cdns3_request *priv_req, int current_index, + int handled), + TP_ARGS(priv_req, current_index, handled), + TP_STRUCT__entry( + __field(struct cdns3_request *, priv_req) + __field(unsigned int, dma_position) + __field(unsigned int, handled) + __field(unsigned int, dequeue_idx) + __field(unsigned int, enqueue_idx) + __field(unsigned int, start_trb) + __field(unsigned int, end_trb) + ), + TP_fast_assign( + __entry->priv_req = priv_req; + __entry->dma_position = current_index; + __entry->handled = handled; + __entry->dequeue_idx = priv_req->priv_ep->dequeue; + __entry->enqueue_idx = priv_req->priv_ep->enqueue; + __entry->start_trb = priv_req->start_trb; + __entry->end_trb = priv_req->end_trb; + ), + TP_printk("Req: %p %s, DMA pos: %d, ep deq: %d, ep enq: %d," + " start trb: %d, end trb: %d", + __entry->priv_req, + __entry->handled ? "handled" : "not handled", + __entry->dma_position, __entry->dequeue_idx, + __entry->enqueue_idx, __entry->start_trb, + __entry->end_trb + ) +); + +DEFINE_EVENT(cdns3_log_request_handled, cdns3_request_handled, + TP_PROTO(struct cdns3_request *priv_req, int current_index, + int handled), + TP_ARGS(priv_req, current_index, handled) +); +#endif /* __LINUX_CDNS3_TRACE */ + +/* this part must be outside header guard */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE cdns3-trace + +#include diff --git a/drivers/usb/cdns3/cdnsp-debug.h b/drivers/usb/cdns3/cdnsp-debug.h new file mode 100644 index 000000000..86860686d --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-debug.h @@ -0,0 +1,584 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence CDNSP DRD Driver. + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + */ +#ifndef __LINUX_CDNSP_DEBUG +#define __LINUX_CDNSP_DEBUG + +static inline const char *cdnsp_trb_comp_code_string(u8 status) +{ + switch (status) { + case COMP_INVALID: + return "Invalid"; + case COMP_SUCCESS: + return "Success"; + case COMP_DATA_BUFFER_ERROR: + return "Data Buffer Error"; + case COMP_BABBLE_DETECTED_ERROR: + return "Babble Detected"; + case COMP_TRB_ERROR: + return "TRB Error"; + case COMP_RESOURCE_ERROR: + return "Resource Error"; + case COMP_NO_SLOTS_AVAILABLE_ERROR: + return "No Slots Available Error"; + case COMP_INVALID_STREAM_TYPE_ERROR: + return "Invalid Stream Type Error"; + case COMP_SLOT_NOT_ENABLED_ERROR: + return "Slot Not Enabled Error"; + case COMP_ENDPOINT_NOT_ENABLED_ERROR: + return "Endpoint Not Enabled Error"; + case COMP_SHORT_PACKET: + return "Short Packet"; + case COMP_RING_UNDERRUN: + return "Ring Underrun"; + case COMP_RING_OVERRUN: + return "Ring Overrun"; + case COMP_VF_EVENT_RING_FULL_ERROR: + return "VF Event Ring Full Error"; + case COMP_PARAMETER_ERROR: + return "Parameter Error"; + case COMP_CONTEXT_STATE_ERROR: + return "Context State Error"; + case COMP_EVENT_RING_FULL_ERROR: + return "Event Ring Full Error"; + case COMP_INCOMPATIBLE_DEVICE_ERROR: + return "Incompatible Device Error"; + case COMP_MISSED_SERVICE_ERROR: + return "Missed Service Error"; + case COMP_COMMAND_RING_STOPPED: + return "Command Ring Stopped"; + case COMP_COMMAND_ABORTED: + return "Command Aborted"; + case COMP_STOPPED: + return "Stopped"; + case COMP_STOPPED_LENGTH_INVALID: + return "Stopped - Length Invalid"; + case COMP_STOPPED_SHORT_PACKET: + return "Stopped - Short Packet"; + case COMP_MAX_EXIT_LATENCY_TOO_LARGE_ERROR: + return "Max Exit Latency Too Large Error"; + case COMP_ISOCH_BUFFER_OVERRUN: + return "Isoch Buffer Overrun"; + case COMP_EVENT_LOST_ERROR: + return "Event Lost Error"; + case COMP_UNDEFINED_ERROR: + return "Undefined Error"; + case COMP_INVALID_STREAM_ID_ERROR: + return "Invalid Stream ID Error"; + default: + return "Unknown!!"; + } +} + +static inline const char *cdnsp_trb_type_string(u8 type) +{ + switch (type) { + case TRB_NORMAL: + return "Normal"; + case TRB_SETUP: + return "Setup Stage"; + case TRB_DATA: + return "Data Stage"; + case TRB_STATUS: + return "Status Stage"; + case TRB_ISOC: + return "Isoch"; + case TRB_LINK: + return "Link"; + case TRB_EVENT_DATA: + return "Event Data"; + case TRB_TR_NOOP: + return "No-Op"; + case TRB_ENABLE_SLOT: + return "Enable Slot Command"; + case TRB_DISABLE_SLOT: + return "Disable Slot Command"; + case TRB_ADDR_DEV: + return "Address Device Command"; + case TRB_CONFIG_EP: + return "Configure Endpoint Command"; + case TRB_EVAL_CONTEXT: + return "Evaluate Context Command"; + case TRB_RESET_EP: + return "Reset Endpoint Command"; + case TRB_STOP_RING: + return "Stop Ring Command"; + case TRB_SET_DEQ: + return "Set TR Dequeue Pointer Command"; + case TRB_RESET_DEV: + return "Reset Device Command"; + case TRB_FORCE_HEADER: + return "Force Header Command"; + case TRB_CMD_NOOP: + return "No-Op Command"; + case TRB_TRANSFER: + return "Transfer Event"; + case TRB_COMPLETION: + return "Command Completion Event"; + case TRB_PORT_STATUS: + return "Port Status Change Event"; + case TRB_HC_EVENT: + return "Device Controller Event"; + case TRB_MFINDEX_WRAP: + return "MFINDEX Wrap Event"; + case TRB_ENDPOINT_NRDY: + return "Endpoint Not ready"; + case TRB_HALT_ENDPOINT: + return "Halt Endpoint"; + default: + return "UNKNOWN"; + } +} + +static inline const char *cdnsp_ring_type_string(enum cdnsp_ring_type type) +{ + switch (type) { + case TYPE_CTRL: + return "CTRL"; + case TYPE_ISOC: + return "ISOC"; + case TYPE_BULK: + return "BULK"; + case TYPE_INTR: + return "INTR"; + case TYPE_STREAM: + return "STREAM"; + case TYPE_COMMAND: + return "CMD"; + case TYPE_EVENT: + return "EVENT"; + } + + return "UNKNOWN"; +} + +static inline char *cdnsp_slot_state_string(u32 state) +{ + switch (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 inline const char *cdnsp_decode_trb(char *str, size_t size, u32 field0, + u32 field1, u32 field2, u32 field3) +{ + int ep_id = TRB_TO_EP_INDEX(field3) - 1; + int type = TRB_FIELD_TO_TYPE(field3); + unsigned int ep_num; + int ret; + u32 temp; + + ep_num = DIV_ROUND_UP(ep_id, 2); + + switch (type) { + case TRB_LINK: + ret = scnprintf(str, size, + "LINK %08x%08x intr %ld type '%s' flags %c:%c:%c:%c", + field1, field0, GET_INTR_TARGET(field2), + cdnsp_trb_type_string(type), + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_TC ? 'T' : 't', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_TRANSFER: + case TRB_COMPLETION: + case TRB_PORT_STATUS: + case TRB_HC_EVENT: + ret = scnprintf(str, size, + "ep%d%s(%d) type '%s' TRB %08x%08x status '%s'" + " len %ld slot %ld flags %c:%c", + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), + cdnsp_trb_type_string(type), field1, field0, + cdnsp_trb_comp_code_string(GET_COMP_CODE(field2)), + EVENT_TRB_LEN(field2), TRB_TO_SLOT_ID(field3), + field3 & EVENT_DATA ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_MFINDEX_WRAP: + ret = scnprintf(str, size, "%s: flags %c", + cdnsp_trb_type_string(type), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_SETUP: + ret = scnprintf(str, size, + "type '%s' bRequestType %02x bRequest %02x " + "wValue %02x%02x wIndex %02x%02x wLength %d " + "length %ld TD size %ld intr %ld Setup ID %ld " + "flags %c:%c:%c", + cdnsp_trb_type_string(type), + field0 & 0xff, + (field0 & 0xff00) >> 8, + (field0 & 0xff000000) >> 24, + (field0 & 0xff0000) >> 16, + (field1 & 0xff00) >> 8, + field1 & 0xff, + (field1 & 0xff000000) >> 16 | + (field1 & 0xff0000) >> 16, + TRB_LEN(field2), GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + TRB_SETUPID_TO_TYPE(field3), + field3 & TRB_IDT ? 'D' : 'd', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_DATA: + ret = scnprintf(str, size, + "type '%s' Buffer %08x%08x length %ld TD size %ld " + "intr %ld flags %c:%c:%c:%c:%c:%c:%c", + cdnsp_trb_type_string(type), + field1, field0, TRB_LEN(field2), + GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + field3 & TRB_IDT ? 'D' : 'i', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_NO_SNOOP ? 'S' : 's', + field3 & TRB_ISP ? 'I' : 'i', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_STATUS: + ret = scnprintf(str, size, + "Buffer %08x%08x length %ld TD size %ld intr" + "%ld type '%s' flags %c:%c:%c:%c", + field1, field0, TRB_LEN(field2), + GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + cdnsp_trb_type_string(type), + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_NORMAL: + case TRB_ISOC: + case TRB_EVENT_DATA: + case TRB_TR_NOOP: + ret = scnprintf(str, size, + "type '%s' Buffer %08x%08x length %ld " + "TD size %ld intr %ld " + "flags %c:%c:%c:%c:%c:%c:%c:%c:%c", + cdnsp_trb_type_string(type), + field1, field0, TRB_LEN(field2), + GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + field3 & TRB_BEI ? 'B' : 'b', + field3 & TRB_IDT ? 'T' : 't', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_NO_SNOOP ? 'S' : 's', + field3 & TRB_ISP ? 'I' : 'i', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c', + !(field3 & TRB_EVENT_INVALIDATE) ? 'V' : 'v'); + break; + case TRB_CMD_NOOP: + case TRB_ENABLE_SLOT: + ret = scnprintf(str, size, "%s: flags %c", + cdnsp_trb_type_string(type), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_DISABLE_SLOT: + ret = scnprintf(str, size, "%s: slot %ld flags %c", + cdnsp_trb_type_string(type), + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_ADDR_DEV: + ret = scnprintf(str, size, + "%s: ctx %08x%08x slot %ld flags %c:%c", + cdnsp_trb_type_string(type), field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_BSR ? 'B' : 'b', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_CONFIG_EP: + ret = scnprintf(str, size, + "%s: ctx %08x%08x slot %ld flags %c:%c", + cdnsp_trb_type_string(type), field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_DC ? 'D' : 'd', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_EVAL_CONTEXT: + ret = scnprintf(str, size, + "%s: ctx %08x%08x slot %ld flags %c", + cdnsp_trb_type_string(type), field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_RESET_EP: + case TRB_HALT_ENDPOINT: + ret = scnprintf(str, size, + "%s: ep%d%s(%d) ctx %08x%08x slot %ld flags %c %c", + cdnsp_trb_type_string(type), + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c', + field3 & TRB_ESP ? 'P' : 'p'); + break; + case TRB_STOP_RING: + ret = scnprintf(str, size, + "%s: ep%d%s(%d) slot %ld sp %d flags %c", + cdnsp_trb_type_string(type), + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), + TRB_TO_SLOT_ID(field3), + TRB_TO_SUSPEND_PORT(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_SET_DEQ: + ret = scnprintf(str, size, + "%s: ep%d%s(%d) deq %08x%08x stream %ld slot %ld flags %c", + cdnsp_trb_type_string(type), + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), field1, field0, + TRB_TO_STREAM_ID(field2), + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_RESET_DEV: + ret = scnprintf(str, size, "%s: slot %ld flags %c", + cdnsp_trb_type_string(type), + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_ENDPOINT_NRDY: + temp = TRB_TO_HOST_STREAM(field2); + + ret = scnprintf(str, size, + "%s: ep%d%s(%d) H_SID %x%s%s D_SID %lx flags %c:%c", + cdnsp_trb_type_string(type), + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), temp, + temp == STREAM_PRIME_ACK ? "(PRIME)" : "", + temp == STREAM_REJECTED ? "(REJECTED)" : "", + TRB_TO_DEV_STREAM(field0), + field3 & TRB_STAT ? 'S' : 's', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + default: + ret = scnprintf(str, size, + "type '%s' -> raw %08x %08x %08x %08x", + cdnsp_trb_type_string(type), + field0, field1, field2, field3); + } + + if (ret == size - 1) + pr_info("CDNSP: buffer may be truncated.\n"); + + return str; +} + +static inline const char *cdnsp_decode_slot_context(u32 info, u32 info2, + u32 int_target, u32 state) +{ + static char str[1024]; + int ret = 0; + u32 speed; + char *s; + + speed = info & DEV_SPEED; + + switch (speed) { + case SLOT_SPEED_FS: + s = "full-speed"; + break; + case SLOT_SPEED_HS: + s = "high-speed"; + break; + case SLOT_SPEED_SS: + s = "super-speed"; + break; + case SLOT_SPEED_SSP: + s = "super-speed plus"; + break; + default: + s = "UNKNOWN speed"; + } + + ret = sprintf(str, "%s Ctx Entries %d", + s, (info & LAST_CTX_MASK) >> 27); + + ret += sprintf(str + ret, " [Intr %ld] Addr %ld State %s", + GET_INTR_TARGET(int_target), state & DEV_ADDR_MASK, + cdnsp_slot_state_string(GET_SLOT_STATE(state))); + + return str; +} + +static inline const char *cdnsp_portsc_link_state_string(u32 portsc) +{ + switch (portsc & PORT_PLS_MASK) { + case XDEV_U0: + return "U0"; + case XDEV_U1: + return "U1"; + case XDEV_U2: + return "U2"; + case XDEV_U3: + return "U3"; + case XDEV_DISABLED: + return "Disabled"; + case XDEV_RXDETECT: + return "RxDetect"; + case XDEV_INACTIVE: + return "Inactive"; + case XDEV_POLLING: + return "Polling"; + case XDEV_RECOVERY: + return "Recovery"; + case XDEV_HOT_RESET: + return "Hot Reset"; + case XDEV_COMP_MODE: + return "Compliance mode"; + case XDEV_TEST_MODE: + return "Test mode"; + case XDEV_RESUME: + return "Resume"; + default: + break; + } + + return "Unknown"; +} + +static inline const char *cdnsp_decode_portsc(char *str, size_t size, + u32 portsc) +{ + int ret; + + ret = scnprintf(str, size, "%s %s %s Link:%s PortSpeed:%d ", + portsc & PORT_POWER ? "Powered" : "Powered-off", + portsc & PORT_CONNECT ? "Connected" : "Not-connected", + portsc & PORT_PED ? "Enabled" : "Disabled", + cdnsp_portsc_link_state_string(portsc), + DEV_PORT_SPEED(portsc)); + + if (portsc & PORT_RESET) + ret += scnprintf(str + ret, size - ret, "In-Reset "); + + ret += scnprintf(str + ret, size - ret, "Change: "); + if (portsc & PORT_CSC) + ret += scnprintf(str + ret, size - ret, "CSC "); + if (portsc & PORT_WRC) + ret += scnprintf(str + ret, size - ret, "WRC "); + if (portsc & PORT_RC) + ret += scnprintf(str + ret, size - ret, "PRC "); + if (portsc & PORT_PLC) + ret += scnprintf(str + ret, size - ret, "PLC "); + if (portsc & PORT_CEC) + ret += scnprintf(str + ret, size - ret, "CEC "); + ret += scnprintf(str + ret, size - ret, "Wake: "); + if (portsc & PORT_WKCONN_E) + ret += scnprintf(str + ret, size - ret, "WCE "); + if (portsc & PORT_WKDISC_E) + ret += scnprintf(str + ret, size - ret, "WDE "); + + return str; +} + +static inline const char *cdnsp_ep_state_string(u8 state) +{ + switch (state) { + case EP_STATE_DISABLED: + return "disabled"; + case EP_STATE_RUNNING: + return "running"; + case EP_STATE_HALTED: + return "halted"; + case EP_STATE_STOPPED: + return "stopped"; + case EP_STATE_ERROR: + return "error"; + default: + return "INVALID"; + } +} + +static inline const char *cdnsp_ep_type_string(u8 type) +{ + switch (type) { + case ISOC_OUT_EP: + return "Isoc OUT"; + case BULK_OUT_EP: + return "Bulk OUT"; + case INT_OUT_EP: + return "Int OUT"; + case CTRL_EP: + return "Ctrl"; + case ISOC_IN_EP: + return "Isoc IN"; + case BULK_IN_EP: + return "Bulk IN"; + case INT_IN_EP: + return "Int IN"; + default: + return "INVALID"; + } +} + +static inline const char *cdnsp_decode_ep_context(char *str, size_t size, + u32 info, u32 info2, + u64 deq, u32 tx_info) +{ + u8 max_pstr, ep_state, interval, ep_type, burst, cerr, mult; + bool lsa, hid; + u16 maxp, avg; + u32 esit; + int ret; + + esit = CTX_TO_MAX_ESIT_PAYLOAD_HI(info) << 16 | + CTX_TO_MAX_ESIT_PAYLOAD_LO(tx_info); + + ep_state = info & EP_STATE_MASK; + max_pstr = CTX_TO_EP_MAXPSTREAMS(info); + interval = CTX_TO_EP_INTERVAL(info); + mult = CTX_TO_EP_MULT(info) + 1; + lsa = !!(info & EP_HAS_LSA); + + cerr = (info2 & (3 << 1)) >> 1; + ep_type = CTX_TO_EP_TYPE(info2); + hid = !!(info2 & (1 << 7)); + burst = CTX_TO_MAX_BURST(info2); + maxp = MAX_PACKET_DECODED(info2); + + avg = EP_AVG_TRB_LENGTH(tx_info); + + ret = scnprintf(str, size, "State %s mult %d max P. Streams %d %s", + cdnsp_ep_state_string(ep_state), mult, + max_pstr, lsa ? "LSA " : ""); + + ret += scnprintf(str + ret, size - ret, + "interval %d us max ESIT payload %d CErr %d ", + (1 << interval) * 125, esit, cerr); + + ret += scnprintf(str + ret, size - ret, + "Type %s %sburst %d maxp %d deq %016llx ", + cdnsp_ep_type_string(ep_type), hid ? "HID" : "", + burst, maxp, deq); + + ret += scnprintf(str + ret, size - ret, "avg trb len %d", avg); + + return str; +} + +#endif /*__LINUX_CDNSP_DEBUG*/ diff --git a/drivers/usb/cdns3/cdnsp-ep0.c b/drivers/usb/cdns3/cdnsp-ep0.c new file mode 100644 index 000000000..5cd9b898c --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-ep0.c @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence CDNSP DRD Driver. + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + */ + +#include +#include +#include + +#include "cdnsp-gadget.h" +#include "cdnsp-trace.h" + +static void cdnsp_ep0_stall(struct cdnsp_device *pdev) +{ + struct cdnsp_request *preq; + struct cdnsp_ep *pep; + + pep = &pdev->eps[0]; + preq = next_request(&pep->pending_list); + + if (pdev->three_stage_setup) { + cdnsp_halt_endpoint(pdev, pep, true); + + if (preq) + cdnsp_gadget_giveback(pep, preq, -ECONNRESET); + } else { + pep->ep_state |= EP0_HALTED_STATUS; + + if (preq) + list_del(&preq->list); + + cdnsp_status_stage(pdev); + } +} + +static int cdnsp_ep0_delegate_req(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + int ret; + + spin_unlock(&pdev->lock); + ret = pdev->gadget_driver->setup(&pdev->gadget, ctrl); + spin_lock(&pdev->lock); + + return ret; +} + +static int cdnsp_ep0_set_config(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + enum usb_device_state state = pdev->gadget.state; + u32 cfg; + int ret; + + cfg = le16_to_cpu(ctrl->wValue); + + switch (state) { + case USB_STATE_ADDRESS: + trace_cdnsp_ep0_set_config("from Address state"); + break; + case USB_STATE_CONFIGURED: + trace_cdnsp_ep0_set_config("from Configured state"); + break; + default: + dev_err(pdev->dev, "Set Configuration - bad device state\n"); + return -EINVAL; + } + + ret = cdnsp_ep0_delegate_req(pdev, ctrl); + if (ret) + return ret; + + if (!cfg) + usb_gadget_set_state(&pdev->gadget, USB_STATE_ADDRESS); + + return 0; +} + +static int cdnsp_ep0_set_address(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + enum usb_device_state state = pdev->gadget.state; + struct cdnsp_slot_ctx *slot_ctx; + unsigned int slot_state; + int ret; + u32 addr; + + addr = le16_to_cpu(ctrl->wValue); + + if (addr > 127) { + dev_err(pdev->dev, "Invalid device address %d\n", addr); + return -EINVAL; + } + + slot_ctx = cdnsp_get_slot_ctx(&pdev->out_ctx); + + if (state == USB_STATE_CONFIGURED) { + dev_err(pdev->dev, "Can't Set Address from Configured State\n"); + return -EINVAL; + } + + pdev->device_address = le16_to_cpu(ctrl->wValue); + + slot_ctx = cdnsp_get_slot_ctx(&pdev->out_ctx); + slot_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); + if (slot_state == SLOT_STATE_ADDRESSED) + cdnsp_reset_device(pdev); + + /*set device address*/ + ret = cdnsp_setup_device(pdev, SETUP_CONTEXT_ADDRESS); + if (ret) + return ret; + + if (addr) + usb_gadget_set_state(&pdev->gadget, USB_STATE_ADDRESS); + else + usb_gadget_set_state(&pdev->gadget, USB_STATE_DEFAULT); + + return 0; +} + +int cdnsp_status_stage(struct cdnsp_device *pdev) +{ + pdev->ep0_stage = CDNSP_STATUS_STAGE; + pdev->ep0_preq.request.length = 0; + + return cdnsp_ep_enqueue(pdev->ep0_preq.pep, &pdev->ep0_preq); +} + +static int cdnsp_w_index_to_ep_index(u16 wIndex) +{ + if (!(wIndex & USB_ENDPOINT_NUMBER_MASK)) + return 0; + + return ((wIndex & USB_ENDPOINT_NUMBER_MASK) * 2) + + (wIndex & USB_ENDPOINT_DIR_MASK ? 1 : 0) - 1; +} + +static int cdnsp_ep0_handle_status(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + struct cdnsp_ep *pep; + __le16 *response; + int ep_sts = 0; + u16 status = 0; + u32 recipient; + + recipient = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recipient) { + case USB_RECIP_DEVICE: + status = pdev->gadget.is_selfpowered; + status |= pdev->may_wakeup << USB_DEVICE_REMOTE_WAKEUP; + + if (pdev->gadget.speed >= USB_SPEED_SUPER) { + status |= pdev->u1_allowed << USB_DEV_STAT_U1_ENABLED; + status |= pdev->u2_allowed << USB_DEV_STAT_U2_ENABLED; + } + break; + case USB_RECIP_INTERFACE: + /* + * Function Remote Wake Capable D0 + * Function Remote Wakeup D1 + */ + return cdnsp_ep0_delegate_req(pdev, ctrl); + case USB_RECIP_ENDPOINT: + ep_sts = cdnsp_w_index_to_ep_index(le16_to_cpu(ctrl->wIndex)); + pep = &pdev->eps[ep_sts]; + ep_sts = GET_EP_CTX_STATE(pep->out_ctx); + + /* check if endpoint is stalled */ + if (ep_sts == EP_STATE_HALTED) + status = BIT(USB_ENDPOINT_HALT); + break; + default: + return -EINVAL; + } + + response = (__le16 *)pdev->setup_buf; + *response = cpu_to_le16(status); + + pdev->ep0_preq.request.length = sizeof(*response); + pdev->ep0_preq.request.buf = pdev->setup_buf; + + return cdnsp_ep_enqueue(pdev->ep0_preq.pep, &pdev->ep0_preq); +} + +static void cdnsp_enter_test_mode(struct cdnsp_device *pdev) +{ + u32 temp; + + temp = readl(&pdev->active_port->regs->portpmsc) & ~GENMASK(31, 28); + temp |= PORT_TEST_MODE(pdev->test_mode); + writel(temp, &pdev->active_port->regs->portpmsc); +} + +static int cdnsp_ep0_handle_feature_device(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + enum usb_device_state state; + enum usb_device_speed speed; + u16 tmode; + + state = pdev->gadget.state; + speed = pdev->gadget.speed; + + switch (le16_to_cpu(ctrl->wValue)) { + case USB_DEVICE_REMOTE_WAKEUP: + pdev->may_wakeup = !!set; + trace_cdnsp_may_wakeup(set); + break; + case USB_DEVICE_U1_ENABLE: + if (state != USB_STATE_CONFIGURED || speed < USB_SPEED_SUPER) + return -EINVAL; + + pdev->u1_allowed = !!set; + trace_cdnsp_u1(set); + break; + case USB_DEVICE_U2_ENABLE: + if (state != USB_STATE_CONFIGURED || speed < USB_SPEED_SUPER) + return -EINVAL; + + pdev->u2_allowed = !!set; + trace_cdnsp_u2(set); + break; + case USB_DEVICE_LTM_ENABLE: + return -EINVAL; + case USB_DEVICE_TEST_MODE: + if (state != USB_STATE_CONFIGURED || speed > USB_SPEED_HIGH) + return -EINVAL; + + tmode = le16_to_cpu(ctrl->wIndex); + + if (!set || (tmode & 0xff) != 0) + return -EINVAL; + + tmode = tmode >> 8; + + if (tmode > USB_TEST_FORCE_ENABLE || tmode < USB_TEST_J) + return -EINVAL; + + pdev->test_mode = tmode; + + /* + * Test mode must be set before Status Stage but controller + * will start testing sequence after Status Stage. + */ + cdnsp_enter_test_mode(pdev); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cdnsp_ep0_handle_feature_intf(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + u16 wValue, wIndex; + int ret; + + wValue = le16_to_cpu(ctrl->wValue); + wIndex = le16_to_cpu(ctrl->wIndex); + + switch (wValue) { + case USB_INTRF_FUNC_SUSPEND: + ret = cdnsp_ep0_delegate_req(pdev, ctrl); + if (ret) + return ret; + + /* + * Remote wakeup is enabled when any function within a device + * is enabled for function remote wakeup. + */ + if (wIndex & USB_INTRF_FUNC_SUSPEND_RW) + pdev->may_wakeup++; + else + if (pdev->may_wakeup > 0) + pdev->may_wakeup--; + + return 0; + default: + return -EINVAL; + } + + return 0; +} + +static int cdnsp_ep0_handle_feature_endpoint(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + struct cdnsp_ep *pep; + u16 wValue; + + wValue = le16_to_cpu(ctrl->wValue); + pep = &pdev->eps[cdnsp_w_index_to_ep_index(le16_to_cpu(ctrl->wIndex))]; + + switch (wValue) { + case USB_ENDPOINT_HALT: + if (!set && (pep->ep_state & EP_WEDGE)) { + /* Resets Sequence Number */ + cdnsp_halt_endpoint(pdev, pep, 0); + cdnsp_halt_endpoint(pdev, pep, 1); + break; + } + + return cdnsp_halt_endpoint(pdev, pep, set); + default: + dev_warn(pdev->dev, "WARN Incorrect wValue %04x\n", wValue); + return -EINVAL; + } + + return 0; +} + +static int cdnsp_ep0_handle_feature(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + return cdnsp_ep0_handle_feature_device(pdev, ctrl, set); + case USB_RECIP_INTERFACE: + return cdnsp_ep0_handle_feature_intf(pdev, ctrl, set); + case USB_RECIP_ENDPOINT: + return cdnsp_ep0_handle_feature_endpoint(pdev, ctrl, set); + default: + return -EINVAL; + } +} + +static int cdnsp_ep0_set_sel(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + enum usb_device_state state = pdev->gadget.state; + u16 wLength; + + if (state == USB_STATE_DEFAULT) + return -EINVAL; + + wLength = le16_to_cpu(ctrl->wLength); + + if (wLength != 6) { + dev_err(pdev->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. + */ + pdev->ep0_preq.request.length = 6; + pdev->ep0_preq.request.buf = pdev->setup_buf; + + return cdnsp_ep_enqueue(pdev->ep0_preq.pep, &pdev->ep0_preq); +} + +static int cdnsp_ep0_set_isoch_delay(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + if (le16_to_cpu(ctrl->wIndex) || le16_to_cpu(ctrl->wLength)) + return -EINVAL; + + pdev->gadget.isoch_delay = le16_to_cpu(ctrl->wValue); + + return 0; +} + +static int cdnsp_ep0_std_request(struct cdnsp_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + int ret; + + switch (ctrl->bRequest) { + case USB_REQ_GET_STATUS: + ret = cdnsp_ep0_handle_status(pdev, ctrl); + break; + case USB_REQ_CLEAR_FEATURE: + ret = cdnsp_ep0_handle_feature(pdev, ctrl, 0); + break; + case USB_REQ_SET_FEATURE: + ret = cdnsp_ep0_handle_feature(pdev, ctrl, 1); + break; + case USB_REQ_SET_ADDRESS: + ret = cdnsp_ep0_set_address(pdev, ctrl); + break; + case USB_REQ_SET_CONFIGURATION: + ret = cdnsp_ep0_set_config(pdev, ctrl); + break; + case USB_REQ_SET_SEL: + ret = cdnsp_ep0_set_sel(pdev, ctrl); + break; + case USB_REQ_SET_ISOCH_DELAY: + ret = cdnsp_ep0_set_isoch_delay(pdev, ctrl); + break; + default: + ret = cdnsp_ep0_delegate_req(pdev, ctrl); + break; + } + + return ret; +} + +void cdnsp_setup_analyze(struct cdnsp_device *pdev) +{ + struct usb_ctrlrequest *ctrl = &pdev->setup; + struct cdnsp_ep *pep; + int ret = -EINVAL; + u16 len; + + trace_cdnsp_ctrl_req(ctrl); + + if (!pdev->gadget_driver) + goto out; + + if (pdev->gadget.state == USB_STATE_NOTATTACHED) { + dev_err(pdev->dev, "ERR: Setup detected in unattached state\n"); + goto out; + } + + pep = &pdev->eps[0]; + + /* Restore the ep0 to Stopped/Running state. */ + if (pep->ep_state & EP_HALTED) { + if (GET_EP_CTX_STATE(pep->out_ctx) == EP_STATE_HALTED) + cdnsp_halt_endpoint(pdev, pep, 0); + + /* + * Halt Endpoint Command for SSP2 for ep0 preserve current + * endpoint state and driver has to synchronize the + * software endpoint state with endpoint output context + * state. + */ + pep->ep_state &= ~EP_HALTED; + pep->ep_state |= EP_STOPPED; + } + + /* + * Finishing previous SETUP transfer by removing request from + * list and informing upper layer + */ + if (!list_empty(&pdev->eps[0].pending_list)) { + struct cdnsp_request *req; + + trace_cdnsp_ep0_request("Remove previous"); + req = next_request(&pdev->eps[0].pending_list); + cdnsp_ep_dequeue(&pdev->eps[0], req); + } + + len = le16_to_cpu(ctrl->wLength); + if (!len) { + pdev->three_stage_setup = false; + pdev->ep0_expect_in = false; + } else { + pdev->three_stage_setup = true; + pdev->ep0_expect_in = !!(ctrl->bRequestType & USB_DIR_IN); + } + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + ret = cdnsp_ep0_std_request(pdev, ctrl); + else + ret = cdnsp_ep0_delegate_req(pdev, ctrl); + + if (ret == USB_GADGET_DELAYED_STATUS) { + trace_cdnsp_ep0_status_stage("delayed"); + return; + } +out: + if (ret < 0) + cdnsp_ep0_stall(pdev); + else if (!len && pdev->ep0_stage != CDNSP_STATUS_STAGE) + cdnsp_status_stage(pdev); +} diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c new file mode 100644 index 000000000..7a516e509 --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -0,0 +1,2095 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence CDNSP DRD Driver. + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "gadget-export.h" +#include "drd.h" +#include "cdnsp-gadget.h" +#include "cdnsp-trace.h" + +unsigned int cdnsp_port_speed(unsigned int port_status) +{ + /*Detect gadget speed based on PORTSC register*/ + if (DEV_SUPERSPEEDPLUS(port_status) || + DEV_SSP_GEN1x2(port_status) || DEV_SSP_GEN2x2(port_status)) + return USB_SPEED_SUPER_PLUS; + else if (DEV_SUPERSPEED(port_status)) + return USB_SPEED_SUPER; + else if (DEV_HIGHSPEED(port_status)) + return USB_SPEED_HIGH; + else if (DEV_FULLSPEED(port_status)) + return USB_SPEED_FULL; + + /* If device is detached then speed will be USB_SPEED_UNKNOWN.*/ + return USB_SPEED_UNKNOWN; +} + +/* + * 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 cdnsp_port_state_to_neutral(u32 state) +{ + /* Save read-only status and port state. */ + return (state & CDNSP_PORT_RO) | (state & CDNSP_PORT_RWS); +} + +/** + * cdnsp_find_next_ext_cap - Find the offset of the extended capabilities + * with capability ID id. + * @base: PCI MMIO registers base address. + * @start: Address at which to start looking, (0 or HCC_PARAMS to start at + * beginning of list) + * @id: Extended capability ID to search for. + * + * Returns the offset of the next matching extended capability structure. + * Some capabilities can occur several times, + * e.g., the EXT_CAPS_PROTOCOL, and this provides a way to find them all. + */ +int cdnsp_find_next_ext_cap(void __iomem *base, u32 start, int id) +{ + u32 offset = start; + u32 next; + u32 val; + + if (!start || start == HCC_PARAMS_OFFSET) { + val = readl(base + HCC_PARAMS_OFFSET); + if (val == ~0) + return 0; + + offset = HCC_EXT_CAPS(val) << 2; + if (!offset) + return 0; + } + + do { + val = readl(base + offset); + if (val == ~0) + return 0; + + if (EXT_CAPS_ID(val) == id && offset != start) + return offset; + + next = EXT_CAPS_NEXT(val); + offset += next << 2; + } while (next); + + return 0; +} + +void cdnsp_set_link_state(struct cdnsp_device *pdev, + __le32 __iomem *port_regs, + u32 link_state) +{ + int port_num = 0xFF; + u32 temp; + + temp = readl(port_regs); + temp = cdnsp_port_state_to_neutral(temp); + temp |= PORT_WKCONN_E | PORT_WKDISC_E; + writel(temp, port_regs); + + temp &= ~PORT_PLS_MASK; + temp |= PORT_LINK_STROBE | link_state; + + if (pdev->active_port) + port_num = pdev->active_port->port_num; + + trace_cdnsp_handle_port_status(port_num, readl(port_regs)); + writel(temp, port_regs); + trace_cdnsp_link_state_changed(port_num, readl(port_regs)); +} + +static void cdnsp_disable_port(struct cdnsp_device *pdev, + struct cdnsp_port *port) +{ + u32 temp; + + if (!port->exist) + return; + + temp = cdnsp_port_state_to_neutral(readl(&port->regs->portsc)); + writel(temp | PORT_PED, &port->regs->portsc); +} + +static void cdnsp_clear_port_change_bit(struct cdnsp_device *pdev, + struct cdnsp_port *port) +{ + u32 portsc; + + if (!port->exist) + return; + + portsc = readl(&port->regs->portsc); + writel(cdnsp_port_state_to_neutral(portsc) | + (portsc & PORT_CHANGE_BITS), &port->regs->portsc); +} + +static void cdnsp_set_apb_timeout_value(struct cdnsp_device *pdev) +{ + struct cdns *cdns = dev_get_drvdata(pdev->dev); + __le32 __iomem *reg; + void __iomem *base; + u32 offset = 0; + u32 val; + + if (!cdns->override_apb_timeout) + return; + + base = &pdev->cap_regs->hc_capbase; + offset = cdnsp_find_next_ext_cap(base, offset, D_XEC_PRE_REGS_CAP); + reg = base + offset + REG_CHICKEN_BITS_3_OFFSET; + + val = readl(reg); + val = CHICKEN_APB_TIMEOUT_SET(val, cdns->override_apb_timeout); + writel(val, reg); +} + +static void cdnsp_set_chicken_bits_2(struct cdnsp_device *pdev, u32 bit) +{ + __le32 __iomem *reg; + void __iomem *base; + u32 offset = 0; + + base = &pdev->cap_regs->hc_capbase; + offset = cdnsp_find_next_ext_cap(base, offset, D_XEC_PRE_REGS_CAP); + reg = base + offset + REG_CHICKEN_BITS_2_OFFSET; + + bit = readl(reg) | bit; + writel(bit, reg); +} + +static void cdnsp_clear_chicken_bits_2(struct cdnsp_device *pdev, u32 bit) +{ + __le32 __iomem *reg; + void __iomem *base; + u32 offset = 0; + + base = &pdev->cap_regs->hc_capbase; + offset = cdnsp_find_next_ext_cap(base, offset, D_XEC_PRE_REGS_CAP); + reg = base + offset + REG_CHICKEN_BITS_2_OFFSET; + + bit = readl(reg) & ~bit; + writel(bit, reg); +} + +/* + * Disable interrupts and begin the controller halting process. + */ +static void cdnsp_quiesce(struct cdnsp_device *pdev) +{ + u32 halted; + u32 mask; + u32 cmd; + + mask = ~(u32)(CDNSP_IRQS); + + halted = readl(&pdev->op_regs->status) & STS_HALT; + if (!halted) + mask &= ~(CMD_R_S | CMD_DEVEN); + + cmd = readl(&pdev->op_regs->command); + cmd &= mask; + writel(cmd, &pdev->op_regs->command); +} + +/* + * Force controller into halt state. + * + * Disable any IRQs and clear the run/stop bit. + * Controller will complete any current and actively pipelined transactions, and + * should halt within 16 ms of the run/stop bit being cleared. + * Read controller Halted bit in the status register to see when the + * controller is finished. + */ +int cdnsp_halt(struct cdnsp_device *pdev) +{ + int ret; + u32 val; + + cdnsp_quiesce(pdev); + + ret = readl_poll_timeout_atomic(&pdev->op_regs->status, val, + val & STS_HALT, 1, + CDNSP_MAX_HALT_USEC); + if (ret) { + dev_err(pdev->dev, "ERROR: Device halt failed\n"); + return ret; + } + + pdev->cdnsp_state |= CDNSP_STATE_HALTED; + + return 0; +} + +/* + * device controller died, register read returns 0xffffffff, or command never + * ends. + */ +void cdnsp_died(struct cdnsp_device *pdev) +{ + dev_err(pdev->dev, "ERROR: CDNSP controller not responding\n"); + pdev->cdnsp_state |= CDNSP_STATE_DYING; + cdnsp_halt(pdev); +} + +/* + * Set the run bit and wait for the device to be running. + */ +static int cdnsp_start(struct cdnsp_device *pdev) +{ + u32 temp; + int ret; + + temp = readl(&pdev->op_regs->command); + temp |= (CMD_R_S | CMD_DEVEN); + writel(temp, &pdev->op_regs->command); + + pdev->cdnsp_state = 0; + + /* + * Wait for the STS_HALT Status bit to be 0 to indicate the device is + * running. + */ + ret = readl_poll_timeout_atomic(&pdev->op_regs->status, temp, + !(temp & STS_HALT), 1, + CDNSP_MAX_HALT_USEC); + if (ret) { + pdev->cdnsp_state = CDNSP_STATE_DYING; + dev_err(pdev->dev, "ERROR: Controller run failed\n"); + } + + return ret; +} + +/* + * Reset a halted controller. + * + * This resets pipelines, timers, counters, state machines, etc. + * Transactions will be terminated immediately, and operational registers + * will be set to their defaults. + */ +int cdnsp_reset(struct cdnsp_device *pdev) +{ + u32 command; + u32 temp; + int ret; + + temp = readl(&pdev->op_regs->status); + + if (temp == ~(u32)0) { + dev_err(pdev->dev, "Device not accessible, reset failed.\n"); + return -ENODEV; + } + + if ((temp & STS_HALT) == 0) { + dev_err(pdev->dev, "Controller not halted, aborting reset.\n"); + return -EINVAL; + } + + command = readl(&pdev->op_regs->command); + command |= CMD_RESET; + writel(command, &pdev->op_regs->command); + + ret = readl_poll_timeout_atomic(&pdev->op_regs->command, temp, + !(temp & CMD_RESET), 1, + 10 * 1000); + if (ret) { + dev_err(pdev->dev, "ERROR: Controller reset failed\n"); + return ret; + } + + /* + * CDNSP cannot write any doorbells or operational registers other + * than status until the "Controller Not Ready" flag is cleared. + */ + ret = readl_poll_timeout_atomic(&pdev->op_regs->status, temp, + !(temp & STS_CNR), 1, + 10 * 1000); + + if (ret) { + dev_err(pdev->dev, "ERROR: Controller not ready to work\n"); + return ret; + } + + dev_dbg(pdev->dev, "Controller ready to work"); + + return ret; +} + +/* + * cdnsp_get_endpoint_index - 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) + */ +static unsigned int + cdnsp_get_endpoint_index(const struct usb_endpoint_descriptor *desc) +{ + unsigned int index = (unsigned int)usb_endpoint_num(desc); + + if (usb_endpoint_xfer_control(desc)) + return index * 2; + + return (index * 2) + (usb_endpoint_dir_in(desc) ? 1 : 0) - 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. + */ +static unsigned int + cdnsp_get_endpoint_flag(const struct usb_endpoint_descriptor *desc) +{ + return 1 << (cdnsp_get_endpoint_index(desc) + 1); +} + +int cdnsp_ep_enqueue(struct cdnsp_ep *pep, struct cdnsp_request *preq) +{ + struct cdnsp_device *pdev = pep->pdev; + struct usb_request *request; + int ret; + + if (preq->epnum == 0 && !list_empty(&pep->pending_list)) { + trace_cdnsp_request_enqueue_busy(preq); + return -EBUSY; + } + + request = &preq->request; + request->actual = 0; + request->status = -EINPROGRESS; + preq->direction = pep->direction; + preq->epnum = pep->number; + preq->td.drbl = 0; + + ret = usb_gadget_map_request_by_dev(pdev->dev, request, pep->direction); + if (ret) { + trace_cdnsp_request_enqueue_error(preq); + return ret; + } + + list_add_tail(&preq->list, &pep->pending_list); + + trace_cdnsp_request_enqueue(preq); + + switch (usb_endpoint_type(pep->endpoint.desc)) { + case USB_ENDPOINT_XFER_CONTROL: + ret = cdnsp_queue_ctrl_tx(pdev, preq); + break; + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + ret = cdnsp_queue_bulk_tx(pdev, preq); + break; + case USB_ENDPOINT_XFER_ISOC: + ret = cdnsp_queue_isoc_tx(pdev, preq); + } + + if (ret) + goto unmap; + + return 0; + +unmap: + usb_gadget_unmap_request_by_dev(pdev->dev, &preq->request, + pep->direction); + list_del(&preq->list); + trace_cdnsp_request_enqueue_error(preq); + + return ret; +} + +/* + * Remove the request's TD from the endpoint ring. This may cause the + * controller to stop USB transfers, potentially stopping in the middle of a + * TRB buffer. The controller 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 request 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 controller is in the middle of processing the request 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 request + * and attempt to cancel. + * + * 2) If the controller 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 controller will need to invalidate the any TRBs it has cached after + * the stop endpoint command. + * + * 3) The TD may have completed by the time the Stop Endpoint Command + * completes, so software needs to handle that case too. + * + */ +int cdnsp_ep_dequeue(struct cdnsp_ep *pep, struct cdnsp_request *preq) +{ + struct cdnsp_device *pdev = pep->pdev; + int ret_stop = 0; + int ret_rem; + + trace_cdnsp_request_dequeue(preq); + + if (GET_EP_CTX_STATE(pep->out_ctx) == EP_STATE_RUNNING) + ret_stop = cdnsp_cmd_stop_ep(pdev, pep); + + ret_rem = cdnsp_remove_request(pdev, preq, pep); + + return ret_rem ? ret_rem : ret_stop; +} + +static void cdnsp_zero_in_ctx(struct cdnsp_device *pdev) +{ + struct cdnsp_input_control_ctx *ctrl_ctx; + struct cdnsp_slot_ctx *slot_ctx; + struct cdnsp_ep_ctx *ep_ctx; + int i; + + ctrl_ctx = cdnsp_get_input_control_ctx(&pdev->in_ctx); + + /* + * 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->drop_flags = 0; + ctrl_ctx->add_flags = 0; + slot_ctx = cdnsp_get_slot_ctx(&pdev->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 < CDNSP_ENDPOINTS_NUM; ++i) { + ep_ctx = cdnsp_get_ep_ctx(&pdev->in_ctx, i); + ep_ctx->ep_info = 0; + ep_ctx->ep_info2 = 0; + ep_ctx->deq = 0; + ep_ctx->tx_info = 0; + } +} + +/* Issue a configure endpoint command and wait for it to finish. */ +static int cdnsp_configure_endpoint(struct cdnsp_device *pdev) +{ + int ret; + + cdnsp_queue_configure_endpoint(pdev, pdev->cmd.in_ctx->dma); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + if (ret) { + dev_err(pdev->dev, + "ERR: unexpected command completion code 0x%x.\n", ret); + return -EINVAL; + } + + return ret; +} + +static void cdnsp_invalidate_ep_events(struct cdnsp_device *pdev, + struct cdnsp_ep *pep) +{ + struct cdnsp_segment *segment; + union cdnsp_trb *event; + u32 cycle_state; + u32 data; + + event = pdev->event_ring->dequeue; + segment = pdev->event_ring->deq_seg; + cycle_state = pdev->event_ring->cycle_state; + + while (1) { + data = le32_to_cpu(event->trans_event.flags); + + /* Check the owner of the TRB. */ + if ((data & TRB_CYCLE) != cycle_state) + break; + + if (TRB_FIELD_TO_TYPE(data) == TRB_TRANSFER && + TRB_TO_EP_ID(data) == (pep->idx + 1)) { + data |= TRB_EVENT_INVALIDATE; + event->trans_event.flags = cpu_to_le32(data); + } + + if (cdnsp_last_trb_on_seg(segment, event)) { + cycle_state ^= 1; + segment = pdev->event_ring->deq_seg->next; + event = segment->trbs; + } else { + event++; + } + } +} + +int cdnsp_wait_for_cmd_compl(struct cdnsp_device *pdev) +{ + struct cdnsp_segment *event_deq_seg; + union cdnsp_trb *cmd_trb; + dma_addr_t cmd_deq_dma; + union cdnsp_trb *event; + u32 cycle_state; + u32 retry = 10; + int ret, val; + u64 cmd_dma; + u32 flags; + + cmd_trb = pdev->cmd.command_trb; + pdev->cmd.status = 0; + + trace_cdnsp_cmd_wait_for_compl(pdev->cmd_ring, &cmd_trb->generic); + + ret = readl_poll_timeout_atomic(&pdev->op_regs->cmd_ring, val, + !CMD_RING_BUSY(val), 1, + CDNSP_CMD_TIMEOUT); + if (ret) { + dev_err(pdev->dev, "ERR: Timeout while waiting for command\n"); + trace_cdnsp_cmd_timeout(pdev->cmd_ring, &cmd_trb->generic); + pdev->cdnsp_state = CDNSP_STATE_DYING; + return -ETIMEDOUT; + } + + event = pdev->event_ring->dequeue; + event_deq_seg = pdev->event_ring->deq_seg; + cycle_state = pdev->event_ring->cycle_state; + + cmd_deq_dma = cdnsp_trb_virt_to_dma(pdev->cmd_ring->deq_seg, cmd_trb); + if (!cmd_deq_dma) + return -EINVAL; + + while (1) { + flags = le32_to_cpu(event->event_cmd.flags); + + /* Check the owner of the TRB. */ + if ((flags & TRB_CYCLE) != cycle_state) { + /* + * Give some extra time to get chance controller + * to finish command before returning error code. + * Checking CMD_RING_BUSY is not sufficient because + * this bit is cleared to '0' when the Command + * Descriptor has been executed by controller + * and not when command completion event has + * be added to event ring. + */ + if (retry--) { + udelay(20); + continue; + } + + return -EINVAL; + } + + cmd_dma = le64_to_cpu(event->event_cmd.cmd_trb); + + /* + * Check whether the completion event is for last queued + * command. + */ + if (TRB_FIELD_TO_TYPE(flags) != TRB_COMPLETION || + cmd_dma != (u64)cmd_deq_dma) { + if (!cdnsp_last_trb_on_seg(event_deq_seg, event)) { + event++; + continue; + } + + if (cdnsp_last_trb_on_ring(pdev->event_ring, + event_deq_seg, event)) + cycle_state ^= 1; + + event_deq_seg = event_deq_seg->next; + event = event_deq_seg->trbs; + continue; + } + + trace_cdnsp_handle_command(pdev->cmd_ring, &cmd_trb->generic); + + pdev->cmd.status = GET_COMP_CODE(le32_to_cpu(event->event_cmd.status)); + if (pdev->cmd.status == COMP_SUCCESS) + return 0; + + return -pdev->cmd.status; + } +} + +int cdnsp_halt_endpoint(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + int value) +{ + int ret; + + trace_cdnsp_ep_halt(value ? "Set" : "Clear"); + + ret = cdnsp_cmd_stop_ep(pdev, pep); + if (ret) + return ret; + + if (value) { + if (GET_EP_CTX_STATE(pep->out_ctx) == EP_STATE_STOPPED) { + cdnsp_queue_halt_endpoint(pdev, pep->idx); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + } + + pep->ep_state |= EP_HALTED; + } else { + cdnsp_queue_reset_ep(pdev, pep->idx); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + trace_cdnsp_handle_cmd_reset_ep(pep->out_ctx); + + if (ret) + return ret; + + pep->ep_state &= ~EP_HALTED; + + if (pep->idx != 0 && !(pep->ep_state & EP_WEDGE)) + cdnsp_ring_doorbell_for_active_rings(pdev, pep); + + pep->ep_state &= ~EP_WEDGE; + } + + return 0; +} + +static int cdnsp_update_eps_configuration(struct cdnsp_device *pdev, + struct cdnsp_ep *pep) +{ + struct cdnsp_input_control_ctx *ctrl_ctx; + struct cdnsp_slot_ctx *slot_ctx; + int ret = 0; + u32 ep_sts; + int i; + + ctrl_ctx = cdnsp_get_input_control_ctx(&pdev->in_ctx); + + /* Don't issue the command if there's no endpoints to update. */ + if (ctrl_ctx->add_flags == 0 && ctrl_ctx->drop_flags == 0) + return 0; + + 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)); + + /* Fix up Context Entries field. Minimum value is EP0 == BIT(1). */ + slot_ctx = cdnsp_get_slot_ctx(&pdev->in_ctx); + for (i = CDNSP_ENDPOINTS_NUM; i >= 1; i--) { + __le32 le32 = cpu_to_le32(BIT(i)); + + if ((pdev->eps[i - 1].ring && !(ctrl_ctx->drop_flags & le32)) || + (ctrl_ctx->add_flags & le32) || i == 1) { + slot_ctx->dev_info &= cpu_to_le32(~LAST_CTX_MASK); + slot_ctx->dev_info |= cpu_to_le32(LAST_CTX(i)); + break; + } + } + + ep_sts = GET_EP_CTX_STATE(pep->out_ctx); + + if ((ctrl_ctx->add_flags != cpu_to_le32(SLOT_FLAG) && + ep_sts == EP_STATE_DISABLED) || + (ep_sts != EP_STATE_DISABLED && ctrl_ctx->drop_flags)) + ret = cdnsp_configure_endpoint(pdev); + + trace_cdnsp_configure_endpoint(cdnsp_get_slot_ctx(&pdev->out_ctx)); + trace_cdnsp_handle_cmd_config_ep(pep->out_ctx); + + cdnsp_zero_in_ctx(pdev); + + return ret; +} + +/* + * 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 + * cdnsp_setup_device(), and then re-set up the configuration. + */ +int cdnsp_reset_device(struct cdnsp_device *pdev) +{ + struct cdnsp_slot_ctx *slot_ctx; + int slot_state; + int ret, i; + + slot_ctx = cdnsp_get_slot_ctx(&pdev->in_ctx); + slot_ctx->dev_info = 0; + pdev->device_address = 0; + + /* If device is not setup, there is no point in resetting it. */ + slot_ctx = cdnsp_get_slot_ctx(&pdev->out_ctx); + slot_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); + trace_cdnsp_reset_device(slot_ctx); + + if (slot_state <= SLOT_STATE_DEFAULT && + pdev->eps[0].ep_state & EP_HALTED) { + cdnsp_halt_endpoint(pdev, &pdev->eps[0], 0); + } + + /* + * During Reset Device command controller shall transition the + * endpoint ep0 to the Running State. + */ + pdev->eps[0].ep_state &= ~(EP_STOPPED | EP_HALTED); + pdev->eps[0].ep_state |= EP_ENABLED; + + if (slot_state <= SLOT_STATE_DEFAULT) + return 0; + + cdnsp_queue_reset_device(pdev); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + + /* + * After Reset Device command all not default endpoints + * are in Disabled state. + */ + for (i = 1; i < CDNSP_ENDPOINTS_NUM; ++i) + pdev->eps[i].ep_state |= EP_STOPPED | EP_UNCONFIGURED; + + trace_cdnsp_handle_cmd_reset_dev(slot_ctx); + + if (ret) + dev_err(pdev->dev, "Reset device failed with error code %d", + ret); + + return ret; +} + +/* + * Sets the MaxPStreams field and the Linear Stream Array field. + * Sets the dequeue pointer to the stream context array. + */ +static void cdnsp_setup_streams_ep_input_ctx(struct cdnsp_device *pdev, + struct cdnsp_ep_ctx *ep_ctx, + struct cdnsp_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; + 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); +} + +/* + * The drivers use this function to prepare a bulk endpoints to use streams. + * + * Don't allow the call to succeed if endpoint only supports one stream + * (which means it doesn't support streams at all). + */ +int cdnsp_alloc_streams(struct cdnsp_device *pdev, struct cdnsp_ep *pep) +{ + unsigned int num_streams = usb_ss_max_streams(pep->endpoint.comp_desc); + unsigned int num_stream_ctxs; + int ret; + + if (num_streams == 0) + return 0; + + if (num_streams > STREAM_NUM_STREAMS) + return -EINVAL; + + /* + * Add two to the number of streams requested to account for + * stream 0 that is reserved for controller usage and one additional + * for TASK SET FULL response. + */ + num_streams += 2; + + /* The stream context array size must be a power of two */ + num_stream_ctxs = roundup_pow_of_two(num_streams); + + trace_cdnsp_stream_number(pep, num_stream_ctxs, num_streams); + + ret = cdnsp_alloc_stream_info(pdev, pep, num_stream_ctxs, num_streams); + if (ret) + return ret; + + cdnsp_setup_streams_ep_input_ctx(pdev, pep->in_ctx, &pep->stream_info); + + pep->ep_state |= EP_HAS_STREAMS; + pep->stream_info.td_count = 0; + pep->stream_info.first_prime_det = 0; + + /* Subtract 1 for stream 0, which drivers can't use. */ + return num_streams - 1; +} + +int cdnsp_disable_slot(struct cdnsp_device *pdev) +{ + int ret; + + cdnsp_queue_slot_control(pdev, TRB_DISABLE_SLOT); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + + pdev->slot_id = 0; + pdev->active_port = NULL; + + trace_cdnsp_handle_cmd_disable_slot(cdnsp_get_slot_ctx(&pdev->out_ctx)); + + memset(pdev->in_ctx.bytes, 0, CDNSP_CTX_SIZE); + memset(pdev->out_ctx.bytes, 0, CDNSP_CTX_SIZE); + + return ret; +} + +int cdnsp_enable_slot(struct cdnsp_device *pdev) +{ + struct cdnsp_slot_ctx *slot_ctx; + int slot_state; + int ret; + + /* If device is not setup, there is no point in resetting it */ + slot_ctx = cdnsp_get_slot_ctx(&pdev->out_ctx); + slot_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); + + if (slot_state != SLOT_STATE_DISABLED) + return 0; + + cdnsp_queue_slot_control(pdev, TRB_ENABLE_SLOT); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + if (ret) + goto show_trace; + + pdev->slot_id = 1; + +show_trace: + trace_cdnsp_handle_cmd_enable_slot(cdnsp_get_slot_ctx(&pdev->out_ctx)); + + return ret; +} + +/* + * Issue an Address Device command with BSR=0 if setup is SETUP_CONTEXT_ONLY + * or with BSR = 1 if set_address is SETUP_CONTEXT_ADDRESS. + */ +int cdnsp_setup_device(struct cdnsp_device *pdev, enum cdnsp_setup_dev setup) +{ + struct cdnsp_input_control_ctx *ctrl_ctx; + struct cdnsp_slot_ctx *slot_ctx; + int dev_state = 0; + int ret; + + if (!pdev->slot_id) { + trace_cdnsp_slot_id("incorrect"); + return -EINVAL; + } + + if (!pdev->active_port->port_num) + return -EINVAL; + + slot_ctx = cdnsp_get_slot_ctx(&pdev->out_ctx); + dev_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); + + if (setup == SETUP_CONTEXT_ONLY && dev_state == SLOT_STATE_DEFAULT) { + trace_cdnsp_slot_already_in_default(slot_ctx); + return 0; + } + + slot_ctx = cdnsp_get_slot_ctx(&pdev->in_ctx); + ctrl_ctx = cdnsp_get_input_control_ctx(&pdev->in_ctx); + + if (!slot_ctx->dev_info || dev_state == SLOT_STATE_DEFAULT) { + ret = cdnsp_setup_addressable_priv_dev(pdev); + if (ret) + return ret; + } + + cdnsp_copy_ep0_dequeue_into_input_ctx(pdev); + + ctrl_ctx->add_flags = cpu_to_le32(SLOT_FLAG | EP0_FLAG); + ctrl_ctx->drop_flags = 0; + + trace_cdnsp_setup_device_slot(slot_ctx); + + cdnsp_queue_address_device(pdev, pdev->in_ctx.dma, setup); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + + trace_cdnsp_handle_cmd_addr_dev(cdnsp_get_slot_ctx(&pdev->out_ctx)); + + /* Zero the input context control for later use. */ + ctrl_ctx->add_flags = 0; + ctrl_ctx->drop_flags = 0; + + return ret; +} + +void cdnsp_set_usb2_hardware_lpm(struct cdnsp_device *pdev, + struct usb_request *req, + int enable) +{ + if (pdev->active_port == &pdev->usb3_port || !pdev->gadget.lpm_capable) + return; + + trace_cdnsp_lpm(enable); + + if (enable) + writel(PORT_BESL(CDNSP_DEFAULT_BESL) | PORT_L1S_NYET | PORT_HLE, + &pdev->active_port->regs->portpmsc); + else + writel(PORT_L1S_NYET, &pdev->active_port->regs->portpmsc); +} + +static int cdnsp_get_frame(struct cdnsp_device *pdev) +{ + return readl(&pdev->run_regs->microframe_index) >> 3; +} + +static int cdnsp_gadget_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct cdnsp_input_control_ctx *ctrl_ctx; + struct cdnsp_device *pdev; + struct cdnsp_ep *pep; + unsigned long flags; + u32 added_ctxs; + int ret; + + if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT || + !desc->wMaxPacketSize) + return -EINVAL; + + pep = to_cdnsp_ep(ep); + pdev = pep->pdev; + pep->ep_state &= ~EP_UNCONFIGURED; + + if (dev_WARN_ONCE(pdev->dev, pep->ep_state & EP_ENABLED, + "%s is already enabled\n", pep->name)) + return 0; + + spin_lock_irqsave(&pdev->lock, flags); + + added_ctxs = cdnsp_get_endpoint_flag(desc); + if (added_ctxs == SLOT_FLAG || added_ctxs == EP0_FLAG) { + dev_err(pdev->dev, "ERROR: Bad endpoint number\n"); + ret = -EINVAL; + goto unlock; + } + + pep->interval = desc->bInterval ? BIT(desc->bInterval - 1) : 0; + + if (pdev->gadget.speed == USB_SPEED_FULL) { + if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_INT) + pep->interval = desc->bInterval << 3; + if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_ISOC) + pep->interval = BIT(desc->bInterval - 1) << 3; + } + + if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_ISOC) { + if (pep->interval > BIT(12)) { + dev_err(pdev->dev, "bInterval %d not supported\n", + desc->bInterval); + ret = -EINVAL; + goto unlock; + } + cdnsp_set_chicken_bits_2(pdev, CHICKEN_XDMA_2_TP_CACHE_DIS); + } + + ret = cdnsp_endpoint_init(pdev, pep, GFP_ATOMIC); + if (ret) + goto unlock; + + ctrl_ctx = cdnsp_get_input_control_ctx(&pdev->in_ctx); + ctrl_ctx->add_flags = cpu_to_le32(added_ctxs); + ctrl_ctx->drop_flags = 0; + + ret = cdnsp_update_eps_configuration(pdev, pep); + if (ret) { + cdnsp_free_endpoint_rings(pdev, pep); + goto unlock; + } + + pep->ep_state |= EP_ENABLED; + pep->ep_state &= ~EP_STOPPED; + +unlock: + trace_cdnsp_ep_enable_end(pep, 0); + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +static int cdnsp_gadget_ep_disable(struct usb_ep *ep) +{ + struct cdnsp_input_control_ctx *ctrl_ctx; + struct cdnsp_request *preq; + struct cdnsp_device *pdev; + struct cdnsp_ep *pep; + unsigned long flags; + u32 drop_flag; + int ret = 0; + + if (!ep) + return -EINVAL; + + pep = to_cdnsp_ep(ep); + pdev = pep->pdev; + + spin_lock_irqsave(&pdev->lock, flags); + + if (!(pep->ep_state & EP_ENABLED)) { + dev_err(pdev->dev, "%s is already disabled\n", pep->name); + ret = -EINVAL; + goto finish; + } + + pep->ep_state |= EP_DIS_IN_RROGRESS; + + /* Endpoint was unconfigured by Reset Device command. */ + if (!(pep->ep_state & EP_UNCONFIGURED)) + cdnsp_cmd_stop_ep(pdev, pep); + + /* Remove all queued USB requests. */ + while (!list_empty(&pep->pending_list)) { + preq = next_request(&pep->pending_list); + cdnsp_ep_dequeue(pep, preq); + } + + cdnsp_invalidate_ep_events(pdev, pep); + + pep->ep_state &= ~EP_DIS_IN_RROGRESS; + drop_flag = cdnsp_get_endpoint_flag(pep->endpoint.desc); + ctrl_ctx = cdnsp_get_input_control_ctx(&pdev->in_ctx); + ctrl_ctx->drop_flags = cpu_to_le32(drop_flag); + ctrl_ctx->add_flags = 0; + + cdnsp_endpoint_zero(pdev, pep); + + if (!(pep->ep_state & EP_UNCONFIGURED)) + ret = cdnsp_update_eps_configuration(pdev, pep); + + cdnsp_free_endpoint_rings(pdev, pep); + + pep->ep_state &= ~(EP_ENABLED | EP_UNCONFIGURED); + pep->ep_state |= EP_STOPPED; + +finish: + trace_cdnsp_ep_disable_end(pep, 0); + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +static struct usb_request *cdnsp_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct cdnsp_ep *pep = to_cdnsp_ep(ep); + struct cdnsp_request *preq; + + preq = kzalloc_obj(*preq, gfp_flags); + if (!preq) + return NULL; + + preq->epnum = pep->number; + preq->pep = pep; + + trace_cdnsp_alloc_request(preq); + + return &preq->request; +} + +static void cdnsp_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request) +{ + struct cdnsp_request *preq = to_cdnsp_request(request); + + trace_cdnsp_free_request(preq); + kfree(preq); +} + +static int cdnsp_gadget_ep_queue(struct usb_ep *ep, + struct usb_request *request, + gfp_t gfp_flags) +{ + struct cdnsp_request *preq; + struct cdnsp_device *pdev; + struct cdnsp_ep *pep; + unsigned long flags; + int ret; + + if (!request || !ep) + return -EINVAL; + + pep = to_cdnsp_ep(ep); + pdev = pep->pdev; + + if (!(pep->ep_state & EP_ENABLED)) { + dev_err(pdev->dev, "%s: can't queue to disabled endpoint\n", + pep->name); + return -EINVAL; + } + + preq = to_cdnsp_request(request); + spin_lock_irqsave(&pdev->lock, flags); + ret = cdnsp_ep_enqueue(pep, preq); + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +static int cdnsp_gadget_ep_dequeue(struct usb_ep *ep, + struct usb_request *request) +{ + struct cdnsp_ep *pep = to_cdnsp_ep(ep); + struct cdnsp_device *pdev = pep->pdev; + unsigned long flags; + int ret; + + if (request->status != -EINPROGRESS) + return 0; + + if (!pep->endpoint.desc) { + dev_err(pdev->dev, + "%s: can't dequeue to disabled endpoint\n", + pep->name); + return -ESHUTDOWN; + } + + /* Requests has been dequeued during disabling endpoint. */ + if (!(pep->ep_state & EP_ENABLED)) + return 0; + + spin_lock_irqsave(&pdev->lock, flags); + ret = cdnsp_ep_dequeue(pep, to_cdnsp_request(request)); + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +static int cdnsp_gadget_ep_set_halt(struct usb_ep *ep, int value) +{ + struct cdnsp_ep *pep = to_cdnsp_ep(ep); + struct cdnsp_device *pdev = pep->pdev; + struct cdnsp_request *preq; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pdev->lock, flags); + + preq = next_request(&pep->pending_list); + if (value) { + if (preq) { + trace_cdnsp_ep_busy_try_halt_again(pep, 0); + ret = -EAGAIN; + goto done; + } + } + + ret = cdnsp_halt_endpoint(pdev, pep, value); + +done: + spin_unlock_irqrestore(&pdev->lock, flags); + return ret; +} + +static int cdnsp_gadget_ep_set_wedge(struct usb_ep *ep) +{ + struct cdnsp_ep *pep = to_cdnsp_ep(ep); + struct cdnsp_device *pdev = pep->pdev; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pdev->lock, flags); + pep->ep_state |= EP_WEDGE; + ret = cdnsp_halt_endpoint(pdev, pep, 1); + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +static const struct usb_ep_ops cdnsp_gadget_ep0_ops = { + .enable = cdnsp_gadget_ep_enable, + .disable = cdnsp_gadget_ep_disable, + .alloc_request = cdnsp_gadget_ep_alloc_request, + .free_request = cdnsp_gadget_ep_free_request, + .queue = cdnsp_gadget_ep_queue, + .dequeue = cdnsp_gadget_ep_dequeue, + .set_halt = cdnsp_gadget_ep_set_halt, + .set_wedge = cdnsp_gadget_ep_set_wedge, +}; + +static const struct usb_ep_ops cdnsp_gadget_ep_ops = { + .enable = cdnsp_gadget_ep_enable, + .disable = cdnsp_gadget_ep_disable, + .alloc_request = cdnsp_gadget_ep_alloc_request, + .free_request = cdnsp_gadget_ep_free_request, + .queue = cdnsp_gadget_ep_queue, + .dequeue = cdnsp_gadget_ep_dequeue, + .set_halt = cdnsp_gadget_ep_set_halt, + .set_wedge = cdnsp_gadget_ep_set_wedge, +}; + +void cdnsp_gadget_giveback(struct cdnsp_ep *pep, + struct cdnsp_request *preq, + int status) +{ + struct cdnsp_device *pdev = pep->pdev; + + list_del(&preq->list); + + if (preq->request.status == -EINPROGRESS) + preq->request.status = status; + + usb_gadget_unmap_request_by_dev(pdev->dev, &preq->request, + preq->direction); + + trace_cdnsp_request_giveback(preq); + + if (preq != &pdev->ep0_preq) { + spin_unlock(&pdev->lock); + usb_gadget_giveback_request(&pep->endpoint, &preq->request); + spin_lock(&pdev->lock); + } +} + +static struct usb_endpoint_descriptor cdnsp_gadget_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, +}; + +static int cdnsp_run(struct cdnsp_device *pdev, + enum usb_device_speed speed) +{ + u32 fs_speed = 0; + u32 temp; + int ret; + + temp = readl(&pdev->ir_set->irq_control); + temp &= ~IMOD_INTERVAL_MASK; + temp |= ((IMOD_DEFAULT_INTERVAL / 250) & IMOD_INTERVAL_MASK); + writel(temp, &pdev->ir_set->irq_control); + + temp = readl(&pdev->port3x_regs->mode_addr); + + switch (speed) { + case USB_SPEED_SUPER_PLUS: + temp |= CFG_3XPORT_SSP_SUPPORT; + break; + case USB_SPEED_SUPER: + temp &= ~CFG_3XPORT_SSP_SUPPORT; + break; + case USB_SPEED_HIGH: + break; + case USB_SPEED_FULL: + fs_speed = PORT_REG6_FORCE_FS; + break; + default: + dev_err(pdev->dev, "invalid maximum_speed parameter %d\n", + speed); + fallthrough; + case USB_SPEED_UNKNOWN: + /* Default to superspeed. */ + speed = USB_SPEED_SUPER; + break; + } + + if (pdev->usb3_port.exist && speed >= USB_SPEED_SUPER) { + writel(temp, &pdev->port3x_regs->mode_addr); + cdnsp_set_link_state(pdev, &pdev->usb3_port.regs->portsc, + XDEV_RXDETECT); + } else { + cdnsp_disable_port(pdev, &pdev->usb3_port); + } + + if (pdev->usb2_port.exist) { + cdnsp_set_link_state(pdev, &pdev->usb2_port.regs->portsc, + XDEV_RXDETECT); + writel(PORT_REG6_L1_L0_HW_EN | fs_speed, &pdev->port20_regs->port_reg6); + } + + if (pdev->eusb_port.exist) + cdnsp_set_link_state(pdev, &pdev->eusb_port.regs->portsc, + XDEV_RXDETECT); + + cdnsp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + + + ret = cdnsp_start(pdev); + if (ret) { + ret = -ENODEV; + goto err; + } + + temp = readl(&pdev->op_regs->command); + temp |= (CMD_INTE); + writel(temp, &pdev->op_regs->command); + + temp = readl(&pdev->ir_set->irq_pending); + writel(IMAN_IE_SET(temp), &pdev->ir_set->irq_pending); + + trace_cdnsp_init("Controller ready to work"); + return 0; +err: + cdnsp_halt(pdev); + return ret; +} + +static int cdnsp_gadget_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + enum usb_device_speed max_speed = driver->max_speed; + struct cdnsp_device *pdev = gadget_to_cdnsp(g); + unsigned long flags; + int ret; + + spin_lock_irqsave(&pdev->lock, flags); + pdev->gadget_driver = driver; + + /* limit speed if necessary */ + max_speed = min(driver->max_speed, g->max_speed); + ret = cdnsp_run(pdev, max_speed); + + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +/* + * Update Event Ring Dequeue Pointer: + * - When all events have finished + * - To avoid "Event Ring Full Error" condition + */ +void cdnsp_update_erst_dequeue(struct cdnsp_device *pdev, + union cdnsp_trb *event_ring_deq, + u8 clear_ehb) +{ + u64 temp_64; + dma_addr_t deq; + + temp_64 = cdnsp_read_64(&pdev->ir_set->erst_dequeue); + + /* If necessary, update the HW's version of the event ring deq ptr. */ + if (event_ring_deq != pdev->event_ring->dequeue) { + deq = cdnsp_trb_virt_to_dma(pdev->event_ring->deq_seg, + pdev->event_ring->dequeue); + temp_64 &= ERST_PTR_MASK; + temp_64 |= ((u64)deq & (u64)~ERST_PTR_MASK); + } + + /* Clear the event handler busy flag (RW1C). */ + if (clear_ehb) + temp_64 |= ERST_EHB; + else + temp_64 &= ~ERST_EHB; + + cdnsp_write_64(temp_64, &pdev->ir_set->erst_dequeue); +} + +static void cdnsp_clear_cmd_ring(struct cdnsp_device *pdev) +{ + struct cdnsp_segment *seg; + u64 val_64; + int i; + + cdnsp_initialize_ring_info(pdev->cmd_ring); + + seg = pdev->cmd_ring->first_seg; + for (i = 0; i < pdev->cmd_ring->num_segs; i++) { + memset(seg->trbs, 0, + sizeof(union cdnsp_trb) * (TRBS_PER_SEGMENT - 1)); + seg = seg->next; + } + + /* Set the address in the Command Ring Control register. */ + val_64 = cdnsp_read_64(&pdev->op_regs->cmd_ring); + val_64 = (val_64 & (u64)CMD_RING_RSVD_BITS) | + (pdev->cmd_ring->first_seg->dma & (u64)~CMD_RING_RSVD_BITS) | + pdev->cmd_ring->cycle_state; + cdnsp_write_64(val_64, &pdev->op_regs->cmd_ring); +} + +static void cdnsp_consume_all_events(struct cdnsp_device *pdev) +{ + struct cdnsp_segment *event_deq_seg; + union cdnsp_trb *event_ring_deq; + union cdnsp_trb *event; + u32 cycle_bit; + + event_ring_deq = pdev->event_ring->dequeue; + event_deq_seg = pdev->event_ring->deq_seg; + event = pdev->event_ring->dequeue; + + /* Update ring dequeue pointer. */ + while (1) { + cycle_bit = (le32_to_cpu(event->event_cmd.flags) & TRB_CYCLE); + + /* Does the controller or driver own the TRB? */ + if (cycle_bit != pdev->event_ring->cycle_state) + break; + + cdnsp_inc_deq(pdev, pdev->event_ring); + + if (!cdnsp_last_trb_on_seg(event_deq_seg, event)) { + event++; + continue; + } + + if (cdnsp_last_trb_on_ring(pdev->event_ring, event_deq_seg, + event)) + cycle_bit ^= 1; + + event_deq_seg = event_deq_seg->next; + event = event_deq_seg->trbs; + } + + cdnsp_update_erst_dequeue(pdev, event_ring_deq, 1); +} + +static void cdnsp_stop(struct cdnsp_device *pdev) +{ + u32 temp; + + /* Remove internally queued request for ep0. */ + if (!list_empty(&pdev->eps[0].pending_list)) { + struct cdnsp_request *req; + + req = next_request(&pdev->eps[0].pending_list); + if (req == &pdev->ep0_preq) + cdnsp_ep_dequeue(&pdev->eps[0], req); + } + + cdnsp_disable_port(pdev, &pdev->usb2_port); + cdnsp_disable_port(pdev, &pdev->usb3_port); + cdnsp_disable_port(pdev, &pdev->eusb_port); + + cdnsp_disable_slot(pdev); + cdnsp_halt(pdev); + + temp = readl(&pdev->op_regs->status); + writel((temp & ~0x1fff) | STS_EINT, &pdev->op_regs->status); + temp = readl(&pdev->ir_set->irq_pending); + writel(IMAN_IE_CLEAR(temp), &pdev->ir_set->irq_pending); + + cdnsp_clear_port_change_bit(pdev, &pdev->usb2_port); + cdnsp_clear_port_change_bit(pdev, &pdev->eusb_port); + cdnsp_clear_port_change_bit(pdev, &pdev->usb3_port); + + /* Clear interrupt line */ + temp = readl(&pdev->ir_set->irq_pending); + temp |= IMAN_IP; + writel(temp, &pdev->ir_set->irq_pending); + + cdnsp_consume_all_events(pdev); + cdnsp_clear_cmd_ring(pdev); + + trace_cdnsp_exit("Controller stopped."); +} + +/* + * Stop controller. + * This function is called by the gadget core when the driver is removed. + * Disable slot, disable IRQs, and quiesce the controller. + */ +static int cdnsp_gadget_udc_stop(struct usb_gadget *g) +{ + struct cdnsp_device *pdev = gadget_to_cdnsp(g); + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + cdnsp_stop(pdev); + pdev->gadget_driver = NULL; + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdnsp_gadget_get_frame(struct usb_gadget *g) +{ + struct cdnsp_device *pdev = gadget_to_cdnsp(g); + + return cdnsp_get_frame(pdev); +} + +static void __cdnsp_gadget_wakeup(struct cdnsp_device *pdev) +{ + struct cdnsp_port_regs __iomem *port_regs; + u32 portpm, portsc; + + port_regs = pdev->active_port->regs; + portsc = readl(&port_regs->portsc) & PORT_PLS_MASK; + + /* Remote wakeup feature is not enabled by host. */ + if (pdev->gadget.speed < USB_SPEED_SUPER && portsc == XDEV_U2) { + portpm = readl(&port_regs->portpmsc); + + if (!(portpm & PORT_RWE)) + return; + } + + if (portsc == XDEV_U3 && !pdev->may_wakeup) + return; + + cdnsp_set_link_state(pdev, &port_regs->portsc, XDEV_U0); + + pdev->cdnsp_state |= CDNSP_WAKEUP_PENDING; +} + +static int cdnsp_gadget_wakeup(struct usb_gadget *g) +{ + struct cdnsp_device *pdev = gadget_to_cdnsp(g); + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + __cdnsp_gadget_wakeup(pdev); + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdnsp_gadget_set_selfpowered(struct usb_gadget *g, + int is_selfpowered) +{ + struct cdnsp_device *pdev = gadget_to_cdnsp(g); + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + g->is_selfpowered = !!is_selfpowered; + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdnsp_gadget_pullup(struct usb_gadget *gadget, int is_on) +{ + struct cdnsp_device *pdev = gadget_to_cdnsp(gadget); + struct cdns *cdns = dev_get_drvdata(pdev->dev); + unsigned long flags; + + trace_cdnsp_pullup(is_on); + + /* + * Disable events handling while controller is being + * enabled/disabled. + */ + disable_irq(cdns->dev_irq); + spin_lock_irqsave(&pdev->lock, flags); + + if (!is_on) { + cdnsp_reset_device(pdev); + cdns_clear_vbus(cdns); + } else { + cdns_set_vbus(cdns); + } + + spin_unlock_irqrestore(&pdev->lock, flags); + enable_irq(cdns->dev_irq); + + return 0; +} + +static const struct usb_gadget_ops cdnsp_gadget_ops = { + .get_frame = cdnsp_gadget_get_frame, + .wakeup = cdnsp_gadget_wakeup, + .set_selfpowered = cdnsp_gadget_set_selfpowered, + .pullup = cdnsp_gadget_pullup, + .udc_start = cdnsp_gadget_udc_start, + .udc_stop = cdnsp_gadget_udc_stop, +}; + +static void cdnsp_get_ep_buffering(struct cdnsp_device *pdev, + struct cdnsp_ep *pep) +{ + void __iomem *reg = &pdev->cap_regs->hc_capbase; + int endpoints; + + reg += cdnsp_find_next_ext_cap(reg, 0, XBUF_CAP_ID); + + if (!pep->direction) { + pep->buffering = readl(reg + XBUF_RX_TAG_MASK_0_OFFSET); + pep->buffering_period = readl(reg + XBUF_RX_TAG_MASK_1_OFFSET); + pep->buffering = (pep->buffering + 1) / 2; + pep->buffering_period = (pep->buffering_period + 1) / 2; + return; + } + + endpoints = HCS_ENDPOINTS(pdev->hcs_params1) / 2; + + /* Set to XBUF_TX_TAG_MASK_0 register. */ + reg += XBUF_TX_CMD_OFFSET + (endpoints * 2 + 2) * sizeof(u32); + /* Set reg to XBUF_TX_TAG_MASK_N related with this endpoint. */ + reg += pep->number * sizeof(u32) * 2; + + pep->buffering = (readl(reg) + 1) / 2; + pep->buffering_period = pep->buffering; +} + +static int cdnsp_gadget_init_endpoints(struct cdnsp_device *pdev) +{ + int max_streams = HCC_MAX_PSA(pdev->hcc_params); + struct cdnsp_ep *pep; + int i; + + INIT_LIST_HEAD(&pdev->gadget.ep_list); + + if (max_streams < STREAM_LOG_STREAMS) { + dev_err(pdev->dev, "Stream size %d not supported\n", + max_streams); + return -EINVAL; + } + + max_streams = STREAM_LOG_STREAMS; + + for (i = 0; i < CDNSP_ENDPOINTS_NUM; i++) { + bool direction = !(i & 1); /* Start from OUT endpoint. */ + u8 epnum = ((i + 1) >> 1); + + if (!CDNSP_IF_EP_EXIST(pdev, epnum, direction)) + continue; + + pep = &pdev->eps[i]; + pep->pdev = pdev; + pep->number = epnum; + pep->direction = direction; /* 0 for OUT, 1 for IN. */ + + /* + * Ep0 is bidirectional, so ep0in and ep0out are represented by + * pdev->eps[0] + */ + if (epnum == 0) { + snprintf(pep->name, sizeof(pep->name), "ep%d%s", + epnum, "BiDir"); + + pep->idx = 0; + usb_ep_set_maxpacket_limit(&pep->endpoint, 512); + pep->endpoint.maxburst = 1; + pep->endpoint.ops = &cdnsp_gadget_ep0_ops; + pep->endpoint.desc = &cdnsp_gadget_ep0_desc; + pep->endpoint.comp_desc = NULL; + pep->endpoint.caps.type_control = true; + pep->endpoint.caps.dir_in = true; + pep->endpoint.caps.dir_out = true; + + pdev->ep0_preq.epnum = pep->number; + pdev->ep0_preq.pep = pep; + pdev->gadget.ep0 = &pep->endpoint; + } else { + snprintf(pep->name, sizeof(pep->name), "ep%d%s", + epnum, (pep->direction) ? "in" : "out"); + + pep->idx = (epnum * 2 + (direction ? 1 : 0)) - 1; + usb_ep_set_maxpacket_limit(&pep->endpoint, 1024); + + pep->endpoint.max_streams = max_streams; + pep->endpoint.ops = &cdnsp_gadget_ep_ops; + list_add_tail(&pep->endpoint.ep_list, + &pdev->gadget.ep_list); + + pep->endpoint.caps.type_iso = true; + pep->endpoint.caps.type_bulk = true; + pep->endpoint.caps.type_int = true; + + pep->endpoint.caps.dir_in = direction; + pep->endpoint.caps.dir_out = !direction; + } + + pep->endpoint.name = pep->name; + pep->in_ctx = cdnsp_get_ep_ctx(&pdev->in_ctx, pep->idx); + pep->out_ctx = cdnsp_get_ep_ctx(&pdev->out_ctx, pep->idx); + cdnsp_get_ep_buffering(pdev, pep); + + dev_dbg(pdev->dev, "Init %s, MPS: %04x SupType: " + "CTRL: %s, INT: %s, BULK: %s, ISOC %s, " + "SupDir IN: %s, OUT: %s\n", + pep->name, 1024, + str_yes_no(pep->endpoint.caps.type_control), + str_yes_no(pep->endpoint.caps.type_int), + str_yes_no(pep->endpoint.caps.type_bulk), + str_yes_no(pep->endpoint.caps.type_iso), + str_yes_no(pep->endpoint.caps.dir_in), + str_yes_no(pep->endpoint.caps.dir_out)); + + INIT_LIST_HEAD(&pep->pending_list); + } + + return 0; +} + +static void cdnsp_gadget_free_endpoints(struct cdnsp_device *pdev) +{ + struct cdnsp_ep *pep; + int i; + + for (i = 0; i < CDNSP_ENDPOINTS_NUM; i++) { + pep = &pdev->eps[i]; + if (pep->number != 0 && pep->out_ctx) + list_del(&pep->endpoint.ep_list); + } +} + +void cdnsp_disconnect_gadget(struct cdnsp_device *pdev) +{ + pdev->cdnsp_state |= CDNSP_STATE_DISCONNECT_PENDING; + + if (pdev->gadget_driver && pdev->gadget_driver->disconnect) { + spin_unlock(&pdev->lock); + pdev->gadget_driver->disconnect(&pdev->gadget); + spin_lock(&pdev->lock); + } + + pdev->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&pdev->gadget, USB_STATE_NOTATTACHED); + + pdev->cdnsp_state &= ~CDNSP_STATE_DISCONNECT_PENDING; +} + +void cdnsp_suspend_gadget(struct cdnsp_device *pdev) +{ + if (pdev->gadget_driver && pdev->gadget_driver->suspend) { + spin_unlock(&pdev->lock); + pdev->gadget_driver->suspend(&pdev->gadget); + spin_lock(&pdev->lock); + } +} + +void cdnsp_resume_gadget(struct cdnsp_device *pdev) +{ + if (pdev->gadget_driver && pdev->gadget_driver->resume) { + spin_unlock(&pdev->lock); + pdev->gadget_driver->resume(&pdev->gadget); + spin_lock(&pdev->lock); + } +} + +void cdnsp_irq_reset(struct cdnsp_device *pdev) +{ + struct cdnsp_port_regs __iomem *port_regs; + + cdnsp_reset_device(pdev); + + port_regs = pdev->active_port->regs; + pdev->gadget.speed = cdnsp_port_speed(readl(port_regs)); + + spin_unlock(&pdev->lock); + usb_gadget_udc_reset(&pdev->gadget, pdev->gadget_driver); + spin_lock(&pdev->lock); + + switch (pdev->gadget.speed) { + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER: + cdnsp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + pdev->gadget.ep0->maxpacket = 512; + break; + case USB_SPEED_HIGH: + case USB_SPEED_FULL: + cdnsp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + pdev->gadget.ep0->maxpacket = 64; + break; + default: + /* Low speed is not supported. */ + dev_err(pdev->dev, "Unknown device speed\n"); + break; + } + + cdnsp_clear_chicken_bits_2(pdev, CHICKEN_XDMA_2_TP_CACHE_DIS); + cdnsp_setup_device(pdev, SETUP_CONTEXT_ONLY); + usb_gadget_set_state(&pdev->gadget, USB_STATE_DEFAULT); +} + +static void cdnsp_get_rev_cap(struct cdnsp_device *pdev) +{ + void __iomem *reg = &pdev->cap_regs->hc_capbase; + + reg += cdnsp_find_next_ext_cap(reg, 0, RTL_REV_CAP); + pdev->rev_cap = reg; + + pdev->rtl_revision = readl(&pdev->rev_cap->rtl_revision); + + dev_info(pdev->dev, "Rev: %08x/%08x, eps: %08x, buff: %08x/%08x\n", + readl(&pdev->rev_cap->ctrl_revision), + readl(&pdev->rev_cap->rtl_revision), + readl(&pdev->rev_cap->ep_supported), + readl(&pdev->rev_cap->rx_buff_size), + readl(&pdev->rev_cap->tx_buff_size)); +} + +static int cdnsp_gen_setup(struct cdnsp_device *pdev) +{ + int ret; + u32 reg; + + pdev->cap_regs = pdev->regs; + pdev->op_regs = pdev->regs + + HC_LENGTH(readl(&pdev->cap_regs->hc_capbase)); + pdev->run_regs = pdev->regs + + (readl(&pdev->cap_regs->run_regs_off) & RTSOFF_MASK); + + /* Cache read-only capability registers */ + pdev->hcs_params1 = readl(&pdev->cap_regs->hcs_params1); + pdev->hcc_params = readl(&pdev->cap_regs->hc_capbase); + pdev->hci_version = HC_VERSION(pdev->hcc_params); + pdev->hcc_params = readl(&pdev->cap_regs->hcc_params); + + /* + * Override the APB timeout value to give the controller more time for + * enabling UTMI clock and synchronizing APB and UTMI clock domains. + * This fix is platform specific and is required to fixes issue with + * reading incorrect value from PORTSC register after resuming + * from L1 state. + */ + cdnsp_set_apb_timeout_value(pdev); + + cdnsp_get_rev_cap(pdev); + + /* Make sure the Device Controller is halted. */ + ret = cdnsp_halt(pdev); + if (ret) + return ret; + + /* Reset the internal controller memory state and registers. */ + ret = cdnsp_reset(pdev); + if (ret) + return ret; + + /* + * Set dma_mask and coherent_dma_mask to 64-bits, + * if controller supports 64-bit addressing. + */ + if (HCC_64BIT_ADDR(pdev->hcc_params) && + !dma_set_mask(pdev->dev, DMA_BIT_MASK(64))) { + dev_dbg(pdev->dev, "Enabling 64-bit DMA addresses.\n"); + dma_set_coherent_mask(pdev->dev, DMA_BIT_MASK(64)); + } else { + /* + * This is to avoid error in cases where a 32-bit USB + * controller is used on a 64-bit capable system. + */ + ret = dma_set_mask(pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + dev_dbg(pdev->dev, "Enabling 32-bit DMA addresses.\n"); + dma_set_coherent_mask(pdev->dev, DMA_BIT_MASK(32)); + } + + spin_lock_init(&pdev->lock); + + ret = cdnsp_mem_init(pdev); + if (ret) + return ret; + + /* + * Software workaround for U1: after transition + * to U1 the controller starts gating clock, and in some cases, + * it causes that controller stack. + */ + reg = readl(&pdev->port3x_regs->mode_2); + reg &= ~CFG_3XPORT_U1_PIPE_CLK_GATE_EN; + writel(reg, &pdev->port3x_regs->mode_2); + + return 0; +} + +static int __cdnsp_gadget_init(struct cdns *cdns) +{ + struct cdnsp_device *pdev; + u32 max_speed; + int ret = -ENOMEM; + + cdns_drd_gadget_on(cdns); + + pdev = kzalloc_obj(*pdev); + if (!pdev) + return -ENOMEM; + + pm_runtime_get_sync(cdns->dev); + + cdns->gadget_dev = pdev; + pdev->dev = cdns->dev; + pdev->regs = cdns->dev_regs; + max_speed = usb_get_maximum_speed(cdns->dev); + + switch (max_speed) { + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + break; + default: + dev_err(cdns->dev, "invalid speed parameter %d\n", max_speed); + fallthrough; + case USB_SPEED_UNKNOWN: + /* Default to SSP */ + max_speed = USB_SPEED_SUPER_PLUS; + break; + } + + pdev->gadget.ops = &cdnsp_gadget_ops; + pdev->gadget.name = "cdnsp-gadget"; + pdev->gadget.speed = USB_SPEED_UNKNOWN; + pdev->gadget.sg_supported = 1; + pdev->gadget.max_speed = max_speed; + pdev->gadget.lpm_capable = 1; + + pdev->setup_buf = kzalloc(CDNSP_EP0_SETUP_SIZE, GFP_KERNEL); + if (!pdev->setup_buf) + goto free_pdev; + + /* + * Controller supports not aligned buffer but it should improve + * performance. + */ + pdev->gadget.quirk_ep_out_aligned_size = true; + + ret = cdnsp_gen_setup(pdev); + if (ret) { + dev_err(pdev->dev, "Generic initialization failed %d\n", ret); + goto free_setup; + } + + ret = cdnsp_gadget_init_endpoints(pdev); + if (ret) { + dev_err(pdev->dev, "failed to initialize endpoints\n"); + goto halt_pdev; + } + + ret = usb_add_gadget_udc(pdev->dev, &pdev->gadget); + if (ret) { + dev_err(pdev->dev, "failed to register udc\n"); + goto free_endpoints; + } + + ret = devm_request_threaded_irq(pdev->dev, cdns->dev_irq, + cdnsp_irq_handler, + cdnsp_thread_irq_handler, IRQF_SHARED, + dev_name(pdev->dev), pdev); + if (ret) + goto del_gadget; + + return 0; + +del_gadget: + usb_del_gadget(&pdev->gadget); + cdnsp_gadget_free_endpoints(pdev); + usb_put_gadget(&pdev->gadget); + goto halt_pdev; +free_endpoints: + cdnsp_gadget_free_endpoints(pdev); +halt_pdev: + cdnsp_halt(pdev); + cdnsp_reset(pdev); + cdnsp_mem_cleanup(pdev); +free_setup: + kfree(pdev->setup_buf); +free_pdev: + kfree(pdev); + + return ret; +} + +static void cdnsp_gadget_exit(struct cdns *cdns) +{ + struct cdnsp_device *pdev = cdns->gadget_dev; + + devm_free_irq(pdev->dev, cdns->dev_irq, pdev); + pm_runtime_put_autosuspend(cdns->dev); + usb_del_gadget(&pdev->gadget); + cdnsp_gadget_free_endpoints(pdev); + usb_put_gadget(&pdev->gadget); + cdnsp_mem_cleanup(pdev); + kfree(pdev); + cdns->gadget_dev = NULL; + cdns_drd_gadget_off(cdns); +} + +static int cdnsp_gadget_suspend(struct cdns *cdns, bool do_wakeup) +{ + struct cdnsp_device *pdev = cdns->gadget_dev; + unsigned long flags; + + if (pdev->link_state == XDEV_U3) + return 0; + + spin_lock_irqsave(&pdev->lock, flags); + cdnsp_disconnect_gadget(pdev); + cdnsp_stop(pdev); + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdnsp_gadget_resume(struct cdns *cdns, bool lost_power) +{ + struct cdnsp_device *pdev = cdns->gadget_dev; + enum usb_device_speed max_speed; + unsigned long flags; + int ret; + + if (!pdev->gadget_driver) + return 0; + + spin_lock_irqsave(&pdev->lock, flags); + max_speed = pdev->gadget_driver->max_speed; + + /* Limit speed if necessary. */ + max_speed = min(max_speed, pdev->gadget.max_speed); + + ret = cdnsp_run(pdev, max_speed); + + if (pdev->link_state == XDEV_U3) + __cdnsp_gadget_wakeup(pdev); + + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +/** + * cdnsp_gadget_init - initialize device structure + * @cdns: cdnsp instance + * + * This function initializes the gadget. + */ +int cdnsp_gadget_init(struct cdns *cdns) +{ + struct cdns_role_driver *rdrv; + + rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + rdrv->start = __cdnsp_gadget_init; + rdrv->stop = cdnsp_gadget_exit; + rdrv->suspend = cdnsp_gadget_suspend; + rdrv->resume = cdnsp_gadget_resume; + rdrv->state = CDNS_ROLE_STATE_INACTIVE; + rdrv->name = "gadget"; + cdns->roles[USB_ROLE_DEVICE] = rdrv; + + return 0; +} +EXPORT_SYMBOL_GPL(cdnsp_gadget_init); diff --git a/drivers/usb/cdns3/cdnsp-gadget.h b/drivers/usb/cdns3/cdnsp-gadget.h new file mode 100644 index 000000000..c44bca348 --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-gadget.h @@ -0,0 +1,1617 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence CDNSP DRD Driver. + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + * Code based on Linux XHCI driver. + * Origin: Copyright (C) 2008 Intel Corp. + */ +#ifndef __LINUX_CDNSP_GADGET_H +#define __LINUX_CDNSP_GADGET_H + +#include +#include +#include + +/* Max number slots - only 1 is allowed. */ +#define CDNSP_DEV_MAX_SLOTS 1 + +#define CDNSP_EP0_SETUP_SIZE 512 + +/* One control and 15 for in and 15 for out endpoints. */ +#define CDNSP_ENDPOINTS_NUM 31 + +/* Best Effort Service Latency. */ +#define CDNSP_DEFAULT_BESL 0 + +/* Device Controller command default timeout value in us */ +#define CDNSP_CMD_TIMEOUT (15 * 1000) + +/* Up to 16 ms to halt an device controller */ +#define CDNSP_MAX_HALT_USEC (16 * 1000) + +#define CDNSP_CTX_SIZE 2112 + +/* + * Controller register interface. + */ + +/** + * struct cdnsp_cap_regs - CDNSP Registers. + * @hc_capbase: Length of the capabilities register and controller + * 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 + * @hcc_params2: HCCPARAMS2 Capability Parameters 2, + */ +struct cdnsp_cap_regs { + __le32 hc_capbase; + __le32 hcs_params1; + __le32 hcs_params2; + __le32 hcs_params3; + __le32 hcc_params; + __le32 db_off; + __le32 run_regs_off; + __le32 hcc_params2; + /* Reserved up to (CAPLENGTH - 0x1C) */ +}; + +/* hc_capbase bitmasks. */ +/* bits 7:0 - how long is the Capabilities register. */ +#define HC_LENGTH(p) (((p) >> 00) & GENMASK(7, 0)) +/* bits 31:16 */ +#define HC_VERSION(p) (((p) >> 16) & GENMASK(15, 1)) + +/* HCSPARAMS1 - hcs_params1 - bitmasks */ +/* bits 0:7, Max Device Endpoints */ +#define HCS_ENDPOINTS_MASK GENMASK(7, 0) +#define HCS_ENDPOINTS(p) (((p) & HCS_ENDPOINTS_MASK) >> 0) + +/* HCCPARAMS offset from PCI base address */ +#define HCC_PARAMS_OFFSET 0x10 + +/* HCCPARAMS - hcc_params - bitmasks */ +/* 1: device controller can use 64-bit address pointers. */ +#define HCC_64BIT_ADDR(p) ((p) & BIT(0)) +/* 1: device controller uses 64-byte Device Context structures. */ +#define HCC_64BYTE_CONTEXT(p) ((p) & BIT(2)) +/* Max size for Primary Stream Arrays - 2^(n+1), where n is bits 12:15. */ +#define HCC_MAX_PSA(p) ((((p) >> 12) & 0xf) + 1) +/* Extended Capabilities pointer from PCI base. */ +#define HCC_EXT_CAPS(p) (((p) & GENMASK(31, 16)) >> 16) + +#define CTX_SIZE(_hcc) (HCC_64BYTE_CONTEXT(_hcc) ? 64 : 32) + +/* db_off bitmask - bits 0:1 reserved. */ +#define DBOFF_MASK GENMASK(31, 2) + +/* run_regs_off bitmask - bits 0:4 reserved. */ +#define RTSOFF_MASK GENMASK(31, 5) + +/** + * struct cdnsp_op_regs - Device Controller Operational Registers. + * @command: USBCMD - Controller command register. + * @status: USBSTS - Controller status register. + * @page_size: This indicates the page size that the device controller supports. + * If bit n is set, the controller supports a page size of 2^(n+12), + * up to a 128MB page size. 4K is the minimum page size. + * @dnctrl: DNCTRL - Device notification control register. + * @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_reg_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. + */ +struct cdnsp_op_regs { + __le32 command; + __le32 status; + __le32 page_size; + __le32 reserved1; + __le32 reserved2; + __le32 dnctrl; + __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_reg_base; +}; + +/* Number of registers per port. */ +#define NUM_PORT_REGS 4 + +/** + * struct cdnsp_port_regs - Port Registers. + * @portsc: PORTSC - Port Status and Control Register. + * @portpmsc: PORTPMSC - Port Power Managements Status and Control Register. + * @portli: PORTLI - Port Link Info register. + */ +struct cdnsp_port_regs { + __le32 portsc; + __le32 portpmsc; + __le32 portli; + __le32 reserved; +}; + +/* + * These bits are Read Only (RO) and should be saved and written to the + * registers: 0 (connect status) and 10:13 (port speed). + * These bits are also sticky - meaning they're in the AUX well and they aren't + * changed by a hot and warm. + */ +#define CDNSP_PORT_RO (PORT_CONNECT | DEV_SPEED_MASK) + +/* + * These bits are RW; writing a 0 clears the bit, writing a 1 sets the bit: + * bits 5:8 (link state), 25:26 ("wake on" enable state) + */ +#define CDNSP_PORT_RWS (PORT_PLS_MASK | PORT_WKCONN_E | PORT_WKDISC_E) + +/* + * These bits are RW; writing a 1 clears the bit, writing a 0 has no effect: + * bits 1 (port enable/disable), 17 ( connect changed), + * 21 (port reset changed) , 22 (Port Link State Change), + */ +#define CDNSP_PORT_RW1CS (PORT_PED | PORT_CSC | PORT_RC | PORT_PLC) + +/* USBCMD - USB command - bitmasks. */ +/* Run/Stop, controller execution - do not write unless controller is halted.*/ +#define CMD_R_S BIT(0) +/* + * Reset device controller - resets internal controller state machine and all + * registers (except PCI config regs). + */ +#define CMD_RESET BIT(1) +/* Event Interrupt Enable - a '1' allows interrupts from the controller. */ +#define CMD_INTE BIT(2) +/* + * Device System Error Interrupt Enable - get out-of-band signal for + * controller errors. + */ +#define CMD_DSEIE BIT(3) +/* device controller save/restore state. */ +#define CMD_CSS BIT(8) +#define CMD_CRS BIT(9) +/* + * Enable Wrap Event - '1' means device controller generates an event + * when MFINDEX wraps. + */ +#define CMD_EWE BIT(10) +/* 1: device enabled */ +#define CMD_DEVEN BIT(17) +/* bits 18:31 are reserved (and should be preserved on writes). */ + +/* Command register values to disable interrupts. */ +#define CDNSP_IRQS (CMD_INTE | CMD_DSEIE | CMD_EWE) + +/* USBSTS - USB status - bitmasks */ +/* controller not running - set to 1 when run/stop bit is cleared. */ +#define STS_HALT BIT(0) +/* + * serious error, e.g. PCI parity error. The controller will clear + * the run/stop bit. + */ +#define STS_FATAL BIT(2) +/* event interrupt - clear this prior to clearing any IP flags in IR set.*/ +#define STS_EINT BIT(3) +/* port change detect */ +#define STS_PCD BIT(4) +/* save state status - '1' means device controller is saving state. */ +#define STS_SSS BIT(8) +/* restore state status - '1' means controllers is restoring state. */ +#define STS_RSS BIT(9) +/* 1: save or restore error */ +#define STS_SRE BIT(10) +/* 1: device Not Ready to accept doorbell or op reg writes after reset. */ +#define STS_CNR BIT(11) +/* 1: internal Device Controller Error.*/ +#define STS_HCE BIT(12) + +/* CRCR - Command Ring Control Register - cmd_ring bitmasks. */ +/* bit 0 is the command ring cycle state. */ +#define CMD_RING_CS BIT(0) +/* stop ring immediately - abort the currently executing command. */ +#define CMD_RING_ABORT BIT(2) +/* + * Command Ring Busy. + * Set when Doorbell register is written with DB for command and cleared when + * the controller reached end of CR. + */ +#define CMD_RING_BUSY(p) ((p) & BIT(4)) +/* 1: command ring is running */ +#define CMD_RING_RUNNING BIT(3) +/* Command Ring pointer - bit mask for the lower 32 bits. */ +#define CMD_RING_RSVD_BITS GENMASK(5, 0) + +/* CONFIG - Configure Register - config_reg bitmasks. */ +/* bits 0:7 - maximum number of device slots enabled. */ +#define MAX_DEVS GENMASK(7, 0) +/* bit 8: U3 Entry Enabled, assert PLC when controller enters U3. */ +#define CONFIG_U3E BIT(8) + +/* PORTSC - Port Status and Control Register - port_reg_base bitmasks */ +/* 1: device connected. */ +#define PORT_CONNECT BIT(0) +/* 1: port enabled. */ +#define PORT_PED BIT(1) +/* 1: port reset signaling asserted. */ +#define PORT_RESET BIT(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 sets the link state. + */ +#define PORT_PLS_MASK GENMASK(8, 5) +#define XDEV_U0 (0x0 << 5) +#define XDEV_U1 (0x1 << 5) +#define XDEV_U2 (0x2 << 5) +#define XDEV_U3 (0x3 << 5) +#define XDEV_DISABLED (0x4 << 5) +#define XDEV_RXDETECT (0x5 << 5) +#define XDEV_INACTIVE (0x6 << 5) +#define XDEV_POLLING (0x7 << 5) +#define XDEV_RECOVERY (0x8 << 5) +#define XDEV_HOT_RESET (0x9 << 5) +#define XDEV_COMP_MODE (0xa << 5) +#define XDEV_TEST_MODE (0xb << 5) +#define XDEV_RESUME (0xf << 5) +/* 1: port has power. */ +#define PORT_POWER BIT(9) +/* + * bits 10:13 indicate device speed: + * 0 - undefined speed - port hasn't be initialized by a reset yet + * 1 - full speed + * 2 - Reserved (Low Speed not supported + * 3 - high speed + * 4 - super speed + * 5 - super speed + * 6-15 reserved + */ +#define DEV_SPEED_MASK GENMASK(13, 10) +#define XDEV_FS (0x1 << 10) +#define XDEV_HS (0x3 << 10) +#define XDEV_SS (0x4 << 10) +#define XDEV_SSP (0x5 << 10) +#define XDEV_SSP1x2 (0x6 << 10) +#define XDEV_SSP2x2 (0x7 << 10) +#define DEV_UNDEFSPEED(p) (((p) & DEV_SPEED_MASK) == (0x0 << 10)) +#define DEV_FULLSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_FS) +#define DEV_HIGHSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_HS) +#define DEV_SUPERSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_SS) +#define DEV_SUPERSPEEDPLUS(p) (((p) & DEV_SPEED_MASK) == XDEV_SSP) +#define DEV_SSP_GEN1x2(p) (((p) & DEV_SPEED_MASK) == XDEV_SSP1x2) +#define DEV_SSP_GEN2x2(p) (((p) & DEV_SPEED_MASK) == XDEV_SSP2x2) +#define DEV_SUPERSPEED_ANY(p) (((p) & DEV_SPEED_MASK) >= XDEV_SS) +#define DEV_PORT_SPEED(p) (((p) >> 10) & 0x0f) +/* Port Link State Write Strobe - set this when changing link state */ +#define PORT_LINK_STROBE BIT(16) +/* 1: connect status change */ +#define PORT_CSC BIT(17) +/* 1: warm reset for a USB 3.0 device is done. */ +#define PORT_WRC BIT(19) +/* 1: reset change - 1 to 0 transition of PORT_RESET */ +#define PORT_RC BIT(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 BIT(22) +/* Port configure error change - port failed to configure its link partner. */ +#define PORT_CEC BIT(23) +/* Wake on connect (enable). */ +#define PORT_WKCONN_E BIT(25) +/* Wake on disconnect (enable). */ +#define PORT_WKDISC_E BIT(26) +/* Indicates if Warm Reset is being received. */ +#define PORT_WR BIT(31) + +#define PORT_CHANGE_BITS (PORT_CSC | PORT_WRC | PORT_RC | PORT_PLC | PORT_CEC) + +/* PORTPMSCUSB3 - Port Power Management Status and Control - bitmasks. */ +/* Enables U1 entry. */ +#define PORT_U1_TIMEOUT_MASK GENMASK(7, 0) +#define PORT_U1_TIMEOUT(p) ((p) & PORT_U1_TIMEOUT_MASK) +/* Enables U2 entry .*/ +#define PORT_U2_TIMEOUT_MASK GENMASK(14, 8) +#define PORT_U2_TIMEOUT(p) (((p) << 8) & PORT_U2_TIMEOUT_MASK) + +/* PORTPMSCUSB2 - Port Power Management Status and Control - bitmasks. */ +#define PORT_L1S_MASK GENMASK(2, 0) +#define PORT_L1S(p) ((p) & PORT_L1S_MASK) +#define PORT_L1S_ACK PORT_L1S(1) +#define PORT_L1S_NYET PORT_L1S(2) +#define PORT_L1S_STALL PORT_L1S(3) +#define PORT_L1S_TIMEOUT PORT_L1S(4) +/* Remote Wake Enable. */ +#define PORT_RWE BIT(3) +/* Best Effort Service Latency (BESL). */ +#define PORT_BESL(p) (((p) << 4) & GENMASK(7, 4)) +/* Hardware LPM Enable (HLE). */ +#define PORT_HLE BIT(16) +/* Received Best Effort Service Latency (BESL). */ +#define PORT_RRBESL(p) (((p) & GENMASK(20, 17)) >> 17) +/* Port Test Control. */ +#define PORT_TEST_MODE_MASK GENMASK(31, 28) +#define PORT_TEST_MODE(p) (((p) << 28) & PORT_TEST_MODE_MASK) + +/** + * struct cdnsp_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 controller 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 driver processes those events and + * updates the dequeue pointer. + */ +struct cdnsp_intr_reg { + __le32 irq_pending; + __le32 irq_control; + __le32 erst_size; + __le32 rsvd; + __le64 erst_base; + __le64 erst_dequeue; +}; + +/* IMAN - Interrupt Management Register - irq_pending bitmasks l. */ +#define IMAN_IE BIT(1) +#define IMAN_IP BIT(0) +/* bits 2:31 need to be preserved */ +#define IMAN_IE_SET(p) ((p) | IMAN_IE) +#define IMAN_IE_CLEAR(p) ((p) & ~IMAN_IE) + +/* IMOD - Interrupter Moderation Register - 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 IMOD_INTERVAL_MASK GENMASK(15, 0) +/* Counter used to count down the time to the next interrupt - HW use only */ +#define IMOD_COUNTER_MASK GENMASK(31, 16) +#define IMOD_DEFAULT_INTERVAL 0 + +/* erst_size bitmasks. */ +/* Preserve bits 16:31 of erst_size. */ +#define ERST_SIZE_MASK GENMASK(31, 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 GENMASK(2, 0) +/* Event Handler Busy (EHB) - is the event ring scheduled to be serviced. */ +#define ERST_EHB BIT(3) +#define ERST_PTR_MASK GENMASK(3, 0) + +/** + * struct cdnsp_run_regs + * @microframe_index: MFINDEX - current microframe number. + * @ir_set: Array of Interrupter registers. + * + * Device Controller Runtime Registers: + * "Software should read and write these registers using only Dword (32 bit) + * or larger accesses" + */ +struct cdnsp_run_regs { + __le32 microframe_index; + __le32 rsvd[7]; + struct cdnsp_intr_reg ir_set[128]; +}; + +/** + * USB2.0 Port Peripheral Configuration Registers. + * @ext_cap: Header register for Extended Capability. + * @port_reg1: Timer Configuration Register. + * @port_reg2: Timer Configuration Register. + * @port_reg3: Timer Configuration Register. + * @port_reg4: Timer Configuration Register. + * @port_reg5: Timer Configuration Register. + * @port_reg6: Chicken bits for USB20PPP. + */ +struct cdnsp_20port_cap { + __le32 ext_cap; + __le32 port_reg1; + __le32 port_reg2; + __le32 port_reg3; + __le32 port_reg4; + __le32 port_reg5; + __le32 port_reg6; +}; + +/* Extended capability register fields */ +#define EXT_CAPS_ID(p) (((p) >> 0) & GENMASK(7, 0)) +#define EXT_CAPS_NEXT(p) (((p) >> 8) & GENMASK(7, 0)) +/* Extended capability IDs - ID 0 reserved */ +#define EXT_CAPS_PROTOCOL 2 + +/* USB 2.0 Port Peripheral Configuration Extended Capability */ +#define EXT_CAP_CFG_DEV_20PORT_CAP_ID 0xC1 +/* + * Setting this bit to '1' enables automatic wakeup from L1 state on transfer + * TRB prepared when USBSSP operates in USB2.0 mode. + */ +#define PORT_REG6_L1_L0_HW_EN BIT(1) +/* + * Setting this bit to '1' forces Full Speed when USBSSP operates in USB2.0 + * mode (disables High Speed). + */ +#define PORT_REG6_FORCE_FS BIT(0) + +/** + * USB3.x Port Peripheral Configuration Registers. + * @ext_cap: Header register for Extended Capability. + * @mode_addr: Miscellaneous 3xPORT operation mode configuration register. + * @mode_2: 3x Port Control Register 2. + */ +struct cdnsp_3xport_cap { + __le32 ext_cap; + __le32 mode_addr; + __le32 reserved[52]; + __le32 mode_2; +}; + +/* Extended Capability Header for 3XPort Configuration Registers. */ +#define D_XEC_CFG_3XPORT_CAP 0xC0 +#define CFG_3XPORT_SSP_SUPPORT BIT(31) +#define CFG_3XPORT_U1_PIPE_CLK_GATE_EN BIT(0) + +/* Revision Extended Capability ID */ +#define RTL_REV_CAP 0xC4 +#define RTL_REV_CAP_RX_BUFF_CMD_SIZE BITMASK(31, 24) +#define RTL_REV_CAP_RX_BUFF_SIZE BITMASK(15, 0) +#define RTL_REV_CAP_TX_BUFF_CMD_SIZE BITMASK(31, 24) +#define RTL_REV_CAP_TX_BUFF_SIZE BITMASK(15, 0) + +#define CDNSP_VER_1 0x00000000 +#define CDNSP_VER_2 0x10000000 + +#define CDNSP_IF_EP_EXIST(pdev, ep_num, dir) \ + (readl(&(pdev)->rev_cap->ep_supported) & \ + (BIT(ep_num) << ((dir) ? 0 : 16))) + +/** + * struct cdnsp_rev_cap - controller capabilities. + * @ext_cap: Header for RTL Revision Extended Capability. + * @rtl_revision: RTL revision. + * @rx_buff_size: Rx buffer sizes. + * @tx_buff_size: Tx buffer sizes. + * @ep_supported: Supported endpoints. + * @ctrl_revision: Controller revision ID. + */ +struct cdnsp_rev_cap { + __le32 ext_cap; + __le32 rtl_revision; + __le32 rx_buff_size; + __le32 tx_buff_size; + __le32 ep_supported; + __le32 ctrl_revision; +}; + +/* USB2.0 Port Peripheral Configuration Registers. */ +#define D_XEC_PRE_REGS_CAP 0xC8 +#define REG_CHICKEN_BITS_2_OFFSET 0x48 +#define CHICKEN_XDMA_2_TP_CACHE_DIS BIT(28) + +#define REG_CHICKEN_BITS_3_OFFSET 0x4C +#define CHICKEN_APB_TIMEOUT_SET(p, val) (((p) & ~GENMASK(21, 0)) | (val)) + +/* XBUF Extended Capability ID. */ +#define XBUF_CAP_ID 0xCB +#define XBUF_RX_TAG_MASK_0_OFFSET 0x1C +#define XBUF_RX_TAG_MASK_1_OFFSET 0x24 +#define XBUF_TX_CMD_OFFSET 0x2C + +/** + * struct cdnsp_doorbell_array. + * @cmd_db: Command ring doorbell register. + * @ep_db: Endpoint ring doorbell register. + * Bits 0 - 7: Endpoint target. + * Bits 8 - 15: RsvdZ. + * Bits 16 - 31: Stream ID. + */ +struct cdnsp_doorbell_array { + __le32 cmd_db; + __le32 ep_db; +}; + +#define DB_VALUE(ep, stream) ((((ep) + 1) & 0xff) | ((stream) << 16)) +#define DB_VALUE_EP0_OUT(ep, stream) ((ep) & 0xff) +#define DB_VALUE_CMD 0x00000000 + +/** + * struct cdnsp_container_ctx. + * @type: Type of context. Used to calculated offsets to contained contexts. + * @size: Size of the context data. + * @ctx_size: context data structure size - 64 or 32 bits. + * @dma: dma address of the bytes. + * @bytes: The raw context data given to HW. + * + * 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 cdnsp_container_ctx { + unsigned int type; +#define CDNSP_CTX_TYPE_DEVICE 0x1 +#define CDNSP_CTX_TYPE_INPUT 0x2 + int size; + int ctx_size; + dma_addr_t dma; + u8 *bytes; +}; + +/** + * struct cdnsp_slot_ctx + * @dev_info: Device speed, and last valid endpoint. + * @dev_port: Device port number that is needed to access the USB device. + * @int_target: Interrupter target number. + * @dev_state: Slot state and device address. + * + * Slot Context - This assumes the controller uses 32-byte context + * structures. If the controller uses 64-byte contexts, there is an additional + * 32 bytes reserved at the end of the slot context for controller internal use. + */ +struct cdnsp_slot_ctx { + __le32 dev_info; + __le32 dev_port; + __le32 int_target; + __le32 dev_state; + /* offset 0x10 to 0x1f reserved for controller internal use. */ + __le32 reserved[4]; +}; + +/* Bits 20:23 in the Slot Context are the speed for the device. */ +#define SLOT_SPEED_FS (XDEV_FS << 10) +#define SLOT_SPEED_HS (XDEV_HS << 10) +#define SLOT_SPEED_SS (XDEV_SS << 10) +#define SLOT_SPEED_SSP (XDEV_SSP << 10) + +/* dev_info bitmasks. */ +/* Device speed - values defined by PORTSC Device Speed field - 20:23. */ +#define DEV_SPEED GENMASK(23, 20) +#define GET_DEV_SPEED(n) (((n) & DEV_SPEED) >> 20) +/* Index of the last valid endpoint context in this device context - 27:31. */ +#define LAST_CTX_MASK ((unsigned int)GENMASK(31, 27)) +#define LAST_CTX(p) ((p) << 27) +#define LAST_CTX_TO_EP_NUM(p) (((p) >> 27) - 1) +#define SLOT_FLAG BIT(0) +#define EP0_FLAG BIT(1) + +/* dev_port bitmasks */ +/* Device port number that is needed to access the USB device. */ +#define DEV_PORT(p) (((p) & 0xff) << 16) + +/* dev_state bitmasks */ +/* USB device address - assigned by the controller. */ +#define DEV_ADDR_MASK GENMASK(7, 0) +/* Slot state */ +#define SLOT_STATE GENMASK(31, 27) +#define GET_SLOT_STATE(p) (((p) & SLOT_STATE) >> 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 cdnsp_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 controller 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 - This assumes the controller uses 32-byte context + * structures. If the controller uses 64-byte contexts, there is an additional + * 32 bytes reserved at the end of the endpoint context for controller internal + * use. + */ +struct cdnsp_ep_ctx { + __le32 ep_info; + __le32 ep_info2; + __le64 deq; + __le32 tx_info; + /* offset 0x14 - 0x1f reserved for controller internal use. */ + __le32 reserved[3]; +}; + +/* ep_info bitmasks. */ +/* + * Endpoint State - bits 0:2: + * 0 - disabled + * 1 - running + * 2 - halted due to halt condition + * 3 - stopped + * 4 - TRB error + * 5-7 - reserved + */ +#define EP_STATE_MASK GENMASK(3, 0) +#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 +#define GET_EP_CTX_STATE(ctx) (le32_to_cpu((ctx)->ep_info) & EP_STATE_MASK) + +/* Mult - Max number of burst within an interval, in EP companion desc. */ +#define EP_MULT(p) (((p) << 8) & GENMASK(9, 8)) +#define CTX_TO_EP_MULT(p) (((p) & GENMASK(9, 8)) >> 8) +/* 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) << 16) & GENMASK(23, 16)) +#define EP_INTERVAL_TO_UFRAMES(p) (1 << (((p) & GENMASK(23, 16)) >> 16)) +#define CTX_TO_EP_INTERVAL(p) (((p) & GENMASK(23, 16)) >> 16) +#define EP_MAXPSTREAMS_MASK GENMASK(14, 10) +#define EP_MAXPSTREAMS(p) (((p) << 10) & EP_MAXPSTREAMS_MASK) +#define CTX_TO_EP_MAXPSTREAMS(p) (((p) & EP_MAXPSTREAMS_MASK) >> 10) +/* Endpoint is set up with a Linear Stream Array (vs. Secondary Stream Array) */ +#define EP_HAS_LSA BIT(15) + +/* ep_info2 bitmasks */ +#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 Device Initiate Disable - for disabling stream selection. */ +#define MAX_BURST(p) (((p) << 8) & GENMASK(15, 8)) +#define CTX_TO_MAX_BURST(p) (((p) & GENMASK(15, 8)) >> 8) +#define MAX_PACKET(p) (((p) << 16) & GENMASK(31, 16)) +#define MAX_PACKET_MASK GENMASK(31, 16) +#define MAX_PACKET_DECODED(p) (((p) & GENMASK(31, 16)) >> 16) + +/* tx_info bitmasks. */ +#define EP_AVG_TRB_LENGTH(p) ((p) & GENMASK(15, 0)) +#define EP_MAX_ESIT_PAYLOAD_LO(p) (((p) << 16) & GENMASK(31, 16)) +#define EP_MAX_ESIT_PAYLOAD_HI(p) ((((p) & GENMASK(23, 16)) >> 16) << 24) +#define CTX_TO_MAX_ESIT_PAYLOAD_LO(p) (((p) & GENMASK(31, 16)) >> 16) +#define CTX_TO_MAX_ESIT_PAYLOAD_HI(p) (((p) & GENMASK(31, 24)) >> 24) + +/* deq bitmasks. */ +#define EP_CTX_CYCLE_MASK BIT(0) +#define CTX_DEQ_MASK (~0xfL) + +/** + * struct cdnsp_input_control_context + * Input control context; + * + * @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 cdnsp_input_control_ctx { + __le32 drop_flags; + __le32 add_flags; + __le32 rsvd2[6]; +}; + +/** + * Represents everything that is needed to issue a command on the command ring. + * + * @in_ctx: Pointer to input context structure. + * @status: Command Completion Code for last command. + * @command_trb: Pointer to command TRB. + */ +struct cdnsp_command { + /* Input context for changing device state. */ + struct cdnsp_container_ctx *in_ctx; + u32 status; + union cdnsp_trb *command_trb; +}; + +/** + * Stream context structure. + * + * @stream_ring: 64-bit stream ring address, cycle state, and stream type. + * @reserved: offset 0x14 - 0x1f reserved for controller internal use. + */ +struct cdnsp_stream_ctx { + __le64 stream_ring; + __le32 reserved[2]; +}; + +/* Stream Context Types - bits 3:1 of stream ctx deq ptr. */ +#define SCT_FOR_CTX(p) (((p) << 1) & GENMASK(3, 1)) +/* 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 + +/** + * struct cdnsp_stream_info: Representing everything that is needed to + * supports stream capable endpoints. + * @stream_rings: Array of pointers containing Transfer rings for all + * supported streams. + * @num_streams: Number of streams, including stream 0. + * @stream_ctx_array: The stream context array may be bigger than the number + * of streams the driver asked for. + * @num_stream_ctxs: Number of streams. + * @ctx_array_dma: Dma address of Context Stream Array. + * @trb_address_map: For mapping physical TRB addresses to segments in + * stream rings. + * @td_count: Number of TDs associated with endpoint. + * @first_prime_det: First PRIME packet detected. + * @drbls_count: Number of allowed doorbells. + */ +struct cdnsp_stream_info { + struct cdnsp_ring **stream_rings; + unsigned int num_streams; + struct cdnsp_stream_ctx *stream_ctx_array; + unsigned int num_stream_ctxs; + dma_addr_t ctx_array_dma; + struct radix_tree_root trb_address_map; + int td_count; + u8 first_prime_det; +#define STREAM_DRBL_FIFO_DEPTH 2 + u8 drbls_count; +}; + +#define STREAM_LOG_STREAMS 4 +#define STREAM_NUM_STREAMS BIT(STREAM_LOG_STREAMS) + +#if STREAM_LOG_STREAMS > 16 && STREAM_LOG_STREAMS < 1 +#error "Not suupported stream value" +#endif + +/** + * struct cdnsp_ep - extended device side representation of USB endpoint. + * @endpoint: usb endpoint + * @pending_req_list: List of requests queuing on transfer ring. + * @pdev: Device associated with this endpoint. + * @number: Endpoint number (1 - 15). + * idx: The device context index (DCI). + * interval: Interval between packets used for ISOC endpoint. + * @name: A human readable name e.g. ep1out. + * @direction: Endpoint direction. + * @buffering: Number of on-chip buffers related to endpoint. + * @buffering_period; Number of on-chip buffers related to periodic endpoint. + * @in_ctx: Pointer to input endpoint context structure. + * @out_ctx: Pointer to output endpoint context structure. + * @ring: Pointer to transfer ring. + * @stream_info: Hold stream information. + * @ep_state: Current state of endpoint. + * @skip: Sometimes the controller can not process isochronous endpoint ring + * quickly enough, and it will miss some isoc tds on the ring and + * generate Missed Service Error Event. + * Set skip flag when receive a Missed Service Error Event and + * process the missed tds on the endpoint ring. + * @wa1_nop_trb: hold pointer to NOP trb. + */ +struct cdnsp_ep { + struct usb_ep endpoint; + struct list_head pending_list; + struct cdnsp_device *pdev; + u8 number; + u8 idx; + u32 interval; + char name[20]; + u8 direction; + u8 buffering; + u8 buffering_period; + struct cdnsp_ep_ctx *in_ctx; + struct cdnsp_ep_ctx *out_ctx; + struct cdnsp_ring *ring; + struct cdnsp_stream_info stream_info; + unsigned int ep_state; +#define EP_ENABLED BIT(0) +#define EP_DIS_IN_RROGRESS BIT(1) +#define EP_HALTED BIT(2) +#define EP_STOPPED BIT(3) +#define EP_WEDGE BIT(4) +#define EP0_HALTED_STATUS BIT(5) +#define EP_HAS_STREAMS BIT(6) +#define EP_UNCONFIGURED BIT(7) + + bool skip; + union cdnsp_trb *wa1_nop_trb; + +}; + +/** + * struct cdnsp_device_context_array + * @dev_context_ptr: Array of 64-bit DMA addresses for device contexts. + * @dma: DMA address for device contexts structure. + */ +struct cdnsp_device_context_array { + __le64 dev_context_ptrs[CDNSP_DEV_MAX_SLOTS + 1]; + dma_addr_t dma; +}; + +/** + * struct cdnsp_transfer_event. + * @buffer: 64-bit buffer address, or immediate data. + * @transfer_len: Data length transferred. + * @flags: Field is interpreted differently based on the type of TRB. + */ +struct cdnsp_transfer_event { + __le64 buffer; + __le32 transfer_len; + __le32 flags; +}; + +/* Invalidate event after disabling endpoint. */ +#define TRB_EVENT_INVALIDATE 8 + +/* Transfer event TRB length bit mask. */ +/* bits 0:23 */ +#define EVENT_TRB_LEN(p) ((p) & GENMASK(23, 0)) +/* 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_INVALID 0 +#define COMP_SUCCESS 1 +#define COMP_DATA_BUFFER_ERROR 2 +#define COMP_BABBLE_DETECTED_ERROR 3 +#define COMP_TRB_ERROR 5 +#define COMP_RESOURCE_ERROR 7 +#define COMP_NO_SLOTS_AVAILABLE_ERROR 9 +#define COMP_INVALID_STREAM_TYPE_ERROR 10 +#define COMP_SLOT_NOT_ENABLED_ERROR 11 +#define COMP_ENDPOINT_NOT_ENABLED_ERROR 12 +#define COMP_SHORT_PACKET 13 +#define COMP_RING_UNDERRUN 14 +#define COMP_RING_OVERRUN 15 +#define COMP_VF_EVENT_RING_FULL_ERROR 16 +#define COMP_PARAMETER_ERROR 17 +#define COMP_CONTEXT_STATE_ERROR 19 +#define COMP_EVENT_RING_FULL_ERROR 21 +#define COMP_INCOMPATIBLE_DEVICE_ERROR 22 +#define COMP_MISSED_SERVICE_ERROR 23 +#define COMP_COMMAND_RING_STOPPED 24 +#define COMP_COMMAND_ABORTED 25 +#define COMP_STOPPED 26 +#define COMP_STOPPED_LENGTH_INVALID 27 +#define COMP_STOPPED_SHORT_PACKET 28 +#define COMP_MAX_EXIT_LATENCY_TOO_LARGE_ERROR 29 +#define COMP_ISOCH_BUFFER_OVERRUN 31 +#define COMP_EVENT_LOST_ERROR 32 +#define COMP_UNDEFINED_ERROR 33 +#define COMP_INVALID_STREAM_ID_ERROR 34 + +/*Transfer Event NRDY bit fields */ +#define TRB_TO_DEV_STREAM(p) ((p) & GENMASK(16, 0)) +#define TRB_TO_HOST_STREAM(p) ((p) & GENMASK(16, 0)) +#define STREAM_PRIME_ACK 0xFFFE +#define STREAM_REJECTED 0xFFFF + +/** Transfer Event bit fields **/ +#define TRB_TO_EP_ID(p) (((p) & GENMASK(20, 16)) >> 16) + +/** + * struct cdnsp_link_trb + * @segment_ptr: 64-bit segment pointer. + * @intr_target: Interrupter target. + * @control: Flags. + */ +struct cdnsp_link_trb { + __le64 segment_ptr; + __le32 intr_target; + __le32 control; +}; + +/* control bitfields */ +#define LINK_TOGGLE BIT(1) + +/** + * struct cdnsp_event_cmd - Command completion event TRB. + * cmd_trb: Pointer to command TRB, or the value passed by the event data trb + * status: Command completion parameters and error code. + * flags: Flags. + */ +struct cdnsp_event_cmd { + __le64 cmd_trb; + __le32 status; + __le32 flags; +}; + +/* flags bitmasks */ + +/* Address device - disable SetAddress. */ +#define TRB_BSR BIT(9) + +/* Configure Endpoint - Deconfigure. */ +#define TRB_DC BIT(9) + +/* Force Header */ +#define TRB_FH_TO_PACKET_TYPE(p) ((p) & GENMASK(4, 0)) +#define TRB_FH_TR_PACKET 0x4 +#define TRB_FH_TO_DEVICE_ADDRESS(p) (((p) << 25) & GENMASK(31, 25)) +#define TRB_FH_TR_PACKET_DEV_NOT 0x6 +#define TRB_FH_TO_NOT_TYPE(p) (((p) << 4) & GENMASK(7, 4)) +#define TRB_FH_TR_PACKET_FUNCTION_WAKE 0x1 +#define TRB_FH_TO_INTERFACE(p) (((p) << 8) & GENMASK(15, 8)) + +enum cdnsp_setup_dev { + SETUP_CONTEXT_ONLY, + SETUP_CONTEXT_ADDRESS, +}; + +/* bits 24:31 are the slot ID. */ +#define TRB_TO_SLOT_ID(p) (((p) & GENMASK(31, 24)) >> 24) +#define SLOT_ID_FOR_TRB(p) (((p) << 24) & GENMASK(31, 24)) + +/* Stop Endpoint TRB - ep_index to endpoint ID for this TRB. */ +#define TRB_TO_EP_INDEX(p) (((p) >> 16) & 0x1f) + +#define EP_ID_FOR_TRB(p) ((((p) + 1) << 16) & GENMASK(20, 16)) + +#define SUSPEND_PORT_FOR_TRB(p) (((p) & 1) << 23) +#define TRB_TO_SUSPEND_PORT(p) (((p) >> 23) & 0x1) +#define LAST_EP_INDEX 30 + +/* Set TR Dequeue Pointer command TRB fields. */ +#define TRB_TO_STREAM_ID(p) ((((p) & GENMASK(31, 16)) >> 16)) +#define STREAM_ID_FOR_TRB(p) ((((p)) << 16) & GENMASK(31, 16)) +#define SCT_FOR_TRB(p) (((p) << 1) & 0x7) + +/* + * Halt Endpoint Command TRB field. + * The ESP bit only exists in the SSP2 controller. + */ +#define TRB_ESP BIT(9) + +/* Link TRB specific fields. */ +#define TRB_TC BIT(1) + +/* Port Status Change Event TRB fields. */ +/* Port ID - bits 31:24. */ +#define GET_PORT_ID(p) (((p) & GENMASK(31, 24)) >> 24) +#define SET_PORT_ID(p) (((p) << 24) & GENMASK(31, 24)) +#define EVENT_DATA BIT(2) + +/* Normal TRB fields. */ +/* transfer_len bitmasks - bits 0:16. */ +#define TRB_LEN(p) ((p) & GENMASK(16, 0)) +/* TD Size, packets remaining in this TD, bits 21:17 (5 bits, so max 31). */ +#define TRB_TD_SIZE(p) (min((p), (u32)31) << 17) +#define GET_TD_SIZE(p) (((p) & GENMASK(21, 17)) >> 17) +/* + * Controller uses the TD_SIZE field for TBC if Extended TBC + * is enabled (ETE). + */ +#define TRB_TD_SIZE_TBC(p) (min((p), (u32)31) << 17) +/* Interrupter Target - which MSI-X vector to target the completion event at. */ +#define TRB_INTR_TARGET(p) (((p) << 22) & GENMASK(31, 22)) +#define GET_INTR_TARGET(p) (((p) & GENMASK(31, 22)) >> 22) +/* + * Total burst count field, Rsvdz on controller with Extended TBC + * enabled (ETE). + */ +#define TRB_TBC(p) (((p) & 0x3) << 7) +#define TRB_TLBPC(p) (((p) & 0xf) << 16) + +/* Cycle bit - indicates TRB ownership by driver or driver.*/ +#define TRB_CYCLE BIT(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 BIT(1) +/* Interrupt on short packet. */ +#define TRB_ISP BIT(2) +/* Set PCIe no snoop attribute. */ +#define TRB_NO_SNOOP BIT(3) +/* Chain multiple TRBs into a TD. */ +#define TRB_CHAIN BIT(4) +/* Interrupt on completion. */ +#define TRB_IOC BIT(5) +/* The buffer pointer contains immediate data. */ +#define TRB_IDT BIT(6) +/* 0 - NRDY during data stage, 1 - NRDY during status stage (only control). */ +#define TRB_STAT BIT(7) +/* Block Event Interrupt. */ +#define TRB_BEI BIT(9) + +/* Control transfer TRB specific fields. */ +#define TRB_DIR_IN BIT(16) + +/* TRB bit mask in Data Stage TRB */ +#define TRB_SETUPID_BITMASK GENMASK(9, 8) +#define TRB_SETUPID(p) ((p) << 8) +#define TRB_SETUPID_TO_TYPE(p) (((p) & TRB_SETUPID_BITMASK) >> 8) + +#define TRB_SETUP_SPEEDID_USB3 0x1 +#define TRB_SETUP_SPEEDID_USB2 0x0 +#define TRB_SETUP_SPEEDID(p) ((p) & (1 << 7)) + +#define TRB_SETUPSTAT_ACK 0x1 +#define TRB_SETUPSTAT_STALL 0x0 +#define TRB_SETUPSTAT(p) ((p) << 6) + +/* Isochronous TRB specific fields */ +#define TRB_SIA BIT(31) +#define TRB_FRAME_ID(p) (((p) << 20) & GENMASK(30, 20)) + +struct cdnsp_generic_trb { + __le32 field[4]; +}; + +union cdnsp_trb { + struct cdnsp_link_trb link; + struct cdnsp_transfer_event trans_event; + struct cdnsp_event_cmd event_cmd; + struct cdnsp_generic_trb generic; +}; + +/* TRB bit mask. */ +#define TRB_TYPE_BITMASK GENMASK(15, 10) +#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 +/* 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 +/* Device Controller Event. */ +#define TRB_HC_EVENT 37 +/* MFINDEX Wrap Event - microframe counter wrapped. */ +#define TRB_MFINDEX_WRAP 39 +/* TRB IDs 40-47 reserved. */ +/* Endpoint Not Ready Event. */ +#define TRB_ENDPOINT_NRDY 48 +/* TRB IDs 49-53 reserved. */ +/* Halt Endpoint Command. */ +#define TRB_HALT_ENDPOINT 54 +/* Doorbell Overflow Event. */ +#define TRB_DRB_OVERFLOW 57 + +#define TRB_TYPE_LINK(x) (((x) & TRB_TYPE_BITMASK) == TRB_TYPE(TRB_LINK)) +#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))) + +/* + * TRBS_PER_SEGMENT must be a multiple of 4. + * The command ring is 64-byte aligned, so it must also be greater than 16. + */ +#define TRBS_PER_SEGMENT 256 +#define TRBS_PER_EVENT_SEGMENT 256 +#define TRBS_PER_EV_DEQ_UPDATE 100 +#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 BIT(TRB_MAX_BUFF_SHIFT) +/* How much data is left before the 64KB boundary? */ +#define TRB_BUFF_LEN_UP_TO_BOUNDARY(addr) (TRB_MAX_BUFF_SIZE - \ + ((addr) & (TRB_MAX_BUFF_SIZE - 1))) + +/** + * struct cdnsp_segment - segment related data. + * @trbs: Array of Transfer Request Blocks. + * @next: Pointer to the next segment. + * @dma: DMA address of current segment. + * @bounce_dma: Bounce buffer DMA address . + * @bounce_buf: Bounce buffer virtual address. + * bounce_offs: Bounce buffer offset. + * bounce_len: Bounce buffer length. + */ +struct cdnsp_segment { + union cdnsp_trb *trbs; + struct cdnsp_segment *next; + dma_addr_t dma; + /* Max packet sized bounce buffer for td-fragmant alignment */ + dma_addr_t bounce_dma; + void *bounce_buf; + unsigned int bounce_offs; + unsigned int bounce_len; +}; + +/** + * struct cdnsp_td - Transfer Descriptor object. + * @td_list: Used for binding TD with ep_ring->td_list. + * @preq: Request associated with this TD + * @start_seg: Segment containing the first_trb in TD. + * @first_trb: First TRB for this TD. + * @last_trb: Last TRB related with TD. + * @bounce_seg: Bounce segment for this TD. + * @request_length_set: actual_length of the request has already been set. + * @drbl - TD has been added to HW scheduler - only for stream capable + * endpoints. + */ +struct cdnsp_td { + struct list_head td_list; + struct cdnsp_request *preq; + struct cdnsp_segment *start_seg; + union cdnsp_trb *first_trb; + union cdnsp_trb *last_trb; + struct cdnsp_segment *bounce_seg; + bool request_length_set; + bool drbl; +}; + +/** + * struct cdnsp_dequeue_state - New dequeue pointer for Transfer Ring. + * @new_deq_seg: New dequeue segment. + * @new_deq_ptr: New dequeue pointer. + * @new_cycle_state: New cycle state. + * @stream_id: stream id for which new dequeue pointer has been selected. + */ +struct cdnsp_dequeue_state { + struct cdnsp_segment *new_deq_seg; + union cdnsp_trb *new_deq_ptr; + int new_cycle_state; + unsigned int stream_id; +}; + +enum cdnsp_ring_type { + TYPE_CTRL = 0, + TYPE_ISOC, + TYPE_BULK, + TYPE_INTR, + TYPE_STREAM, + TYPE_COMMAND, + TYPE_EVENT, +}; + +/** + * struct cdnsp_ring - information describing transfer, command or event ring. + * @first_seg: First segment on transfer ring. + * @last_seg: Last segment on transfer ring. + * @enqueue: SW enqueue pointer address. + * @enq_seg: SW enqueue segment address. + * @dequeue: SW dequeue pointer address. + * @deq_seg: SW dequeue segment address. + * @td_list: transfer descriptor list associated with this ring. + * @cycle_state: Current cycle bit. Write the cycle state into the TRB cycle + * field to give ownership of the TRB to the device controller + * (if we are the producer) or to check if we own the TRB + * (if we are the consumer). + * @stream_id: Stream id + * @stream_active: Stream is active - PRIME packet has been detected. + * @stream_rejected: This ring has been rejected by host. + * @num_tds: Number of TDs associated with ring. + * @num_segs: Number of segments. + * @num_trbs_free: Number of free TRBs on the ring. + * @bounce_buf_len: Length of bounce buffer. + * @type: Ring type - event, transfer, or command ring. + * @last_td_was_short - TD is short TD. + * @trb_address_map: For mapping physical TRB addresses to segments in + * stream rings. + */ +struct cdnsp_ring { + struct cdnsp_segment *first_seg; + struct cdnsp_segment *last_seg; + union cdnsp_trb *enqueue; + struct cdnsp_segment *enq_seg; + union cdnsp_trb *dequeue; + struct cdnsp_segment *deq_seg; + struct list_head td_list; + u32 cycle_state; + unsigned int stream_id; + unsigned int stream_active; + unsigned int stream_rejected; + int num_tds; + unsigned int num_segs; + unsigned int num_trbs_free; + unsigned int bounce_buf_len; + enum cdnsp_ring_type type; + bool last_td_was_short; + struct radix_tree_root *trb_address_map; +}; + +/** + * struct cdnsp_erst_entry - even ring segment table entry object. + * @seg_addr: 64-bit event ring segment address. + * seg_size: Number of TRBs in segment.; + */ +struct cdnsp_erst_entry { + __le64 seg_addr; + __le32 seg_size; + /* Set to zero */ + __le32 rsvd; +}; + +/** + * struct cdnsp_erst - even ring segment table for event ring. + * @entries: Array of event ring segments + * @num_entries: Number of segments in entries array. + * @erst_dma_addr: DMA address for entries array. + */ +struct cdnsp_erst { + struct cdnsp_erst_entry *entries; + unsigned int num_entries; + dma_addr_t erst_dma_addr; +}; + +/** + * struct cdnsp_request - extended device side representation of usb_request + * object . + * @td: Transfer descriptor associated with this request. + * @request: Generic usb_request object describing single I/O request. + * @list: Used to adding request to endpoint pending_list. + * @pep: Extended representation of usb_ep object + * @epnum: Endpoint number associated with usb request. + * @direction: Endpoint direction for usb request. + */ +struct cdnsp_request { + struct cdnsp_td td; + struct usb_request request; + struct list_head list; + struct cdnsp_ep *pep; + u8 epnum; + unsigned direction:1; +}; + +#define ERST_NUM_SEGS 1 + +/* Stages used during enumeration process.*/ +enum cdnsp_ep0_stage { + CDNSP_SETUP_STAGE, + CDNSP_DATA_STAGE, + CDNSP_STATUS_STAGE, +}; + +/** + * struct cdnsp_port - holds information about detected ports. + * @port_num: Port number. + * @exist: Indicate if port exist. + * maj_rev: Major revision. + * min_rev: Minor revision. + */ +struct cdnsp_port { + struct cdnsp_port_regs __iomem *regs; + u8 port_num; + u8 exist; + u8 maj_rev; + u8 min_rev; +}; + +#define CDNSP_EXT_PORT_MAJOR(x) (((x) >> 24) & 0xff) +#define CDNSP_EXT_PORT_MINOR(x) (((x) >> 16) & 0xff) +#define CDNSP_EXT_PORT_OFF(x) ((x) & 0xff) +#define CDNSP_EXT_PORT_COUNT(x) (((x) >> 8) & 0xff) + +/** + * struct cdnsp_device - represent USB device. + * @dev: Pointer to device structure associated whit this controller. + * @gadget: Device side representation of the peripheral controller. + * @gadget_driver: Pointer to the gadget driver. + * @irq: IRQ line number used by device side. + * @regs:IO device memory. + * @cap_regs: Capability registers. + * @op_regs: Operational registers. + * @run_regs: Runtime registers. + * @dba: Device base address register. + * @ir_set: Current interrupter register set. + * @port20_regs: Port 2.0 Peripheral Configuration Registers. + * @port3x_regs: USB3.x Port Peripheral Configuration Registers. + * @rev_cap: Controller Capabilities Registers. + * @hcs_params1: Cached register copies of read-only HCSPARAMS1 + * @hcc_params: Cached register copies of read-only HCCPARAMS1 + * @rtl_revision: Cached controller rtl revision. + * @setup: Temporary buffer for setup packet. + * @ep0_preq: Internal allocated request used during enumeration. + * @ep0_stage: ep0 stage during enumeration process. + * @three_stage_setup: Three state or two state setup. + * @ep0_expect_in: Data IN expected for control transfer. + * @setup_id: Setup identifier. + * @setup_speed - Speed detected for current SETUP packet. + * @setup_buf: Buffer for SETUP packet. + * @device_address: Current device address. + * @may_wakeup: remote wakeup enabled/disabled. + * @lock: Lock used in interrupt thread context. + * @hci_version: device controller version. + * @dcbaa: Device context base address array. + * @cmd_ring: Command ring. + * @cmd: Represent all what is needed to issue command on Command Ring. + * @event_ring: Event ring. + * @erst: Event Ring Segment table + * @slot_id: Current Slot ID. Should be 0 or 1. + * @out_ctx: Output context. + * @in_ctx: Input context. + * @eps: array of endpoints object associated with device. + * @usb2_hw_lpm_capable: hardware lpm is enabled; + * @u1_allowed: Allow device transition to U1 state. + * @u2_allowed: Allow device transition to U2 state + * @device_pool: DMA pool for allocating input and output context. + * @segment_pool: DMA pool for allocating new segments. + * @cdnsp_state: Current state of controller. + * @link_state: Current link state. + * @usb2_port - Port USB 2.0. + * @usb3_port - Port USB 3.0. + * @active_port - Current selected Port. + * @test_mode: selected Test Mode. + */ +struct cdnsp_device { + struct device *dev; + struct usb_gadget gadget; + struct usb_gadget_driver *gadget_driver; + unsigned int irq; + void __iomem *regs; + + /* Registers map */ + struct cdnsp_cap_regs __iomem *cap_regs; + struct cdnsp_op_regs __iomem *op_regs; + struct cdnsp_run_regs __iomem *run_regs; + struct cdnsp_doorbell_array __iomem *dba; + struct cdnsp_intr_reg __iomem *ir_set; + struct cdnsp_20port_cap __iomem *port20_regs; + struct cdnsp_3xport_cap __iomem *port3x_regs; + struct cdnsp_rev_cap __iomem *rev_cap; + + /* Cached register copies of read-only CDNSP data */ + __u32 hcs_params1; + __u32 hcs_params3; + __u32 hcc_params; + #define RTL_REVISION_NEW_LPM 0x2700 + __u32 rtl_revision; + /* Lock used in interrupt thread context. */ + spinlock_t lock; + struct usb_ctrlrequest setup; + struct cdnsp_request ep0_preq; + enum cdnsp_ep0_stage ep0_stage; + u8 three_stage_setup; + u8 ep0_expect_in; + u8 setup_id; + u8 setup_speed; + void *setup_buf; + u8 device_address; + int may_wakeup; + u16 hci_version; + + /* data structures */ + struct cdnsp_device_context_array *dcbaa; + struct cdnsp_ring *cmd_ring; + struct cdnsp_command cmd; + struct cdnsp_ring *event_ring; + struct cdnsp_erst erst; + int slot_id; + + /* + * 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. . + */ + struct cdnsp_container_ctx out_ctx; + struct cdnsp_container_ctx in_ctx; + struct cdnsp_ep eps[CDNSP_ENDPOINTS_NUM]; + u8 usb2_hw_lpm_capable:1; + u8 u1_allowed:1; + u8 u2_allowed:1; + + /* DMA pools */ + struct dma_pool *device_pool; + struct dma_pool *segment_pool; + +#define CDNSP_STATE_HALTED BIT(1) +#define CDNSP_STATE_DYING BIT(2) +#define CDNSP_STATE_DISCONNECT_PENDING BIT(3) +#define CDNSP_WAKEUP_PENDING BIT(4) + unsigned int cdnsp_state; + unsigned int link_state; + + struct cdnsp_port usb2_port; + struct cdnsp_port eusb_port; + struct cdnsp_port usb3_port; + struct cdnsp_port *active_port; + u16 test_mode; +}; + +/* + * Registers should always be accessed with double word or quad word accesses. + * + * 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. controller implementations that do not support 64-bit + * address pointers will ignore the high dword, and write order is irrelevant. + */ +static inline u64 cdnsp_read_64(__le64 __iomem *regs) +{ + return lo_hi_readq(regs); +} + +static inline void cdnsp_write_64(const u64 val, __le64 __iomem *regs) +{ + lo_hi_writeq(val, regs); +} + +/* CDNSP memory management functions. */ +void cdnsp_mem_cleanup(struct cdnsp_device *pdev); +int cdnsp_mem_init(struct cdnsp_device *pdev); +int cdnsp_setup_addressable_priv_dev(struct cdnsp_device *pdev); +void cdnsp_copy_ep0_dequeue_into_input_ctx(struct cdnsp_device *pdev); +void cdnsp_endpoint_zero(struct cdnsp_device *pdev, struct cdnsp_ep *ep); +int cdnsp_endpoint_init(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + gfp_t mem_flags); +int cdnsp_ring_expansion(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + unsigned int num_trbs, gfp_t flags); +struct cdnsp_ring *cdnsp_dma_to_transfer_ring(struct cdnsp_ep *ep, u64 address); +int cdnsp_alloc_stream_info(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + unsigned int num_stream_ctxs, + unsigned int num_streams); +int cdnsp_alloc_streams(struct cdnsp_device *pdev, struct cdnsp_ep *pep); +void cdnsp_free_endpoint_rings(struct cdnsp_device *pdev, struct cdnsp_ep *pep); + +/* Device controller glue. */ +int cdnsp_find_next_ext_cap(void __iomem *base, u32 start, int id); +int cdnsp_halt(struct cdnsp_device *pdev); +void cdnsp_died(struct cdnsp_device *pdev); +int cdnsp_reset(struct cdnsp_device *pdev); +irqreturn_t cdnsp_irq_handler(int irq, void *priv); +int cdnsp_setup_device(struct cdnsp_device *pdev, enum cdnsp_setup_dev setup); +void cdnsp_set_usb2_hardware_lpm(struct cdnsp_device *usbsssp_data, + struct usb_request *req, int enable); +irqreturn_t cdnsp_thread_irq_handler(int irq, void *data); + +/* Ring, segment, TRB, and TD functions. */ +dma_addr_t cdnsp_trb_virt_to_dma(struct cdnsp_segment *seg, + union cdnsp_trb *trb); +bool cdnsp_last_trb_on_seg(struct cdnsp_segment *seg, union cdnsp_trb *trb); +bool cdnsp_last_trb_on_ring(struct cdnsp_ring *ring, + struct cdnsp_segment *seg, + union cdnsp_trb *trb); +int cdnsp_wait_for_cmd_compl(struct cdnsp_device *pdev); +void cdnsp_update_erst_dequeue(struct cdnsp_device *pdev, + union cdnsp_trb *event_ring_deq, + u8 clear_ehb); +void cdnsp_initialize_ring_info(struct cdnsp_ring *ring); +void cdnsp_ring_cmd_db(struct cdnsp_device *pdev); +void cdnsp_queue_slot_control(struct cdnsp_device *pdev, u32 trb_type); +void cdnsp_queue_address_device(struct cdnsp_device *pdev, + dma_addr_t in_ctx_ptr, + enum cdnsp_setup_dev setup); +void cdnsp_queue_stop_endpoint(struct cdnsp_device *pdev, + unsigned int ep_index); +int cdnsp_queue_ctrl_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq); +int cdnsp_queue_bulk_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq); +int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev, + struct cdnsp_request *preq); +void cdnsp_queue_configure_endpoint(struct cdnsp_device *pdev, + dma_addr_t in_ctx_ptr); +void cdnsp_queue_reset_ep(struct cdnsp_device *pdev, unsigned int ep_index); +void cdnsp_queue_halt_endpoint(struct cdnsp_device *pdev, + unsigned int ep_index); +void cdnsp_force_header_wakeup(struct cdnsp_device *pdev, int intf_num); +void cdnsp_queue_reset_device(struct cdnsp_device *pdev); +void cdnsp_queue_new_dequeue_state(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + struct cdnsp_dequeue_state *deq_state); +void cdnsp_ring_doorbell_for_active_rings(struct cdnsp_device *pdev, + struct cdnsp_ep *pep); +void cdnsp_inc_deq(struct cdnsp_device *pdev, struct cdnsp_ring *ring); +void cdnsp_set_link_state(struct cdnsp_device *pdev, + __le32 __iomem *port_regs, u32 link_state); +u32 cdnsp_port_state_to_neutral(u32 state); + +/* CDNSP device controller contexts. */ +int cdnsp_enable_slot(struct cdnsp_device *pdev); +int cdnsp_disable_slot(struct cdnsp_device *pdev); +struct cdnsp_input_control_ctx + *cdnsp_get_input_control_ctx(struct cdnsp_container_ctx *ctx); +struct cdnsp_slot_ctx *cdnsp_get_slot_ctx(struct cdnsp_container_ctx *ctx); +struct cdnsp_ep_ctx *cdnsp_get_ep_ctx(struct cdnsp_container_ctx *ctx, + unsigned int ep_index); +/* CDNSP gadget interface. */ +void cdnsp_suspend_gadget(struct cdnsp_device *pdev); +void cdnsp_resume_gadget(struct cdnsp_device *pdev); +void cdnsp_disconnect_gadget(struct cdnsp_device *pdev); +void cdnsp_gadget_giveback(struct cdnsp_ep *pep, struct cdnsp_request *preq, + int status); +int cdnsp_ep_enqueue(struct cdnsp_ep *pep, struct cdnsp_request *preq); +int cdnsp_ep_dequeue(struct cdnsp_ep *pep, struct cdnsp_request *preq); +unsigned int cdnsp_port_speed(unsigned int port_status); +void cdnsp_irq_reset(struct cdnsp_device *pdev); +int cdnsp_halt_endpoint(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, int value); +int cdnsp_cmd_stop_ep(struct cdnsp_device *pdev, struct cdnsp_ep *pep); +void cdnsp_setup_analyze(struct cdnsp_device *pdev); +int cdnsp_status_stage(struct cdnsp_device *pdev); +int cdnsp_reset_device(struct cdnsp_device *pdev); + +/** + * next_request - gets the next request on the given list + * @list: the request list to operate on + * + * Caller should take care of locking. This function return NULL or the first + * request available on list. + */ +static inline struct cdnsp_request *next_request(struct list_head *list) +{ + return list_first_entry_or_null(list, struct cdnsp_request, list); +} + +#define to_cdnsp_ep(ep) (container_of(ep, struct cdnsp_ep, endpoint)) +#define gadget_to_cdnsp(g) (container_of(g, struct cdnsp_device, gadget)) +#define request_to_cdnsp_request(r) (container_of(r, struct cdnsp_request, \ + request)) +#define to_cdnsp_request(r) (container_of(r, struct cdnsp_request, request)) +int cdnsp_remove_request(struct cdnsp_device *pdev, struct cdnsp_request *preq, + struct cdnsp_ep *pep); + +#endif /* __LINUX_CDNSP_GADGET_H */ diff --git a/drivers/usb/cdns3/cdnsp-mem.c b/drivers/usb/cdns3/cdnsp-mem.c new file mode 100644 index 000000000..83f3384b7 --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-mem.c @@ -0,0 +1,1365 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence CDNSP DRD Driver. + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + * Code based on Linux XHCI driver. + * Origin: Copyright (C) 2008 Intel Corp. + */ + +#include +#include +#include +#include + +#include "cdnsp-gadget.h" +#include "cdnsp-trace.h" + +static void cdnsp_free_stream_info(struct cdnsp_device *pdev, + struct cdnsp_ep *pep); +/* + * 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. + * + * "All components of all Command and Transfer TRBs shall be initialized to '0'" + */ +static struct cdnsp_segment *cdnsp_segment_alloc(struct cdnsp_device *pdev, + unsigned int cycle_state, + unsigned int max_packet, + gfp_t flags) +{ + struct cdnsp_segment *seg; + dma_addr_t dma; + int i; + + seg = kzalloc_obj(*seg, flags); + if (!seg) + return NULL; + + seg->trbs = dma_pool_zalloc(pdev->segment_pool, flags, &dma); + if (!seg->trbs) { + kfree(seg); + return NULL; + } + + if (max_packet) { + seg->bounce_buf = kzalloc(max_packet, flags | GFP_DMA); + if (!seg->bounce_buf) + goto free_dma; + } + + /* 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 |= cpu_to_le32(TRB_CYCLE); + } + seg->dma = dma; + seg->next = NULL; + + return seg; + +free_dma: + dma_pool_free(pdev->segment_pool, seg->trbs, dma); + kfree(seg); + + return NULL; +} + +static void cdnsp_segment_free(struct cdnsp_device *pdev, + struct cdnsp_segment *seg) +{ + if (seg->trbs) + dma_pool_free(pdev->segment_pool, seg->trbs, seg->dma); + + kfree(seg->bounce_buf); + kfree(seg); +} + +static void cdnsp_free_segments_for_ring(struct cdnsp_device *pdev, + struct cdnsp_segment *first) +{ + struct cdnsp_segment *seg; + + seg = first->next; + + while (seg != first) { + struct cdnsp_segment *next = seg->next; + + cdnsp_segment_free(pdev, seg); + seg = next; + } + + cdnsp_segment_free(pdev, 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 cdnsp_link_segments(struct cdnsp_device *pdev, + struct cdnsp_segment *prev, + struct cdnsp_segment *next, + enum cdnsp_ring_type type) +{ + struct cdnsp_link_trb *link; + u32 val; + + if (!prev || !next) + return; + + prev->next = next; + if (type != TYPE_EVENT) { + link = &prev->trbs[TRBS_PER_SEGMENT - 1].link; + 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(link->control); + val &= ~TRB_TYPE_BITMASK; + val |= TRB_TYPE(TRB_LINK); + link->control = cpu_to_le32(val); + } +} + +/* + * Link the ring to the new segments. + * Set Toggle Cycle for the new ring if needed. + */ +static void cdnsp_link_rings(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + struct cdnsp_segment *first, + struct cdnsp_segment *last, + unsigned int num_segs) +{ + struct cdnsp_segment *next; + + if (!ring || !first || !last) + return; + + next = ring->enq_seg->next; + cdnsp_link_segments(pdev, ring->enq_seg, first, ring->type); + cdnsp_link_segments(pdev, 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; + } +} + +/* + * 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 device 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 1KB 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. + */ +static int cdnsp_insert_segment_mapping(struct radix_tree_root *trb_address_map, + struct cdnsp_ring *ring, + struct cdnsp_segment *seg, + gfp_t mem_flags) +{ + unsigned long key; + int ret; + + key = (unsigned long)(seg->dma >> TRB_SEGMENT_SHIFT); + + /* Skip any segments that were already added. */ + if (radix_tree_lookup(trb_address_map, key)) + return 0; + + ret = radix_tree_maybe_preload(mem_flags); + if (ret) + return ret; + + ret = radix_tree_insert(trb_address_map, key, ring); + radix_tree_preload_end(); + + return ret; +} + +static void cdnsp_remove_segment_mapping(struct radix_tree_root *trb_address_map, + struct cdnsp_segment *seg) +{ + unsigned long key; + + key = (unsigned long)(seg->dma >> TRB_SEGMENT_SHIFT); + if (radix_tree_lookup(trb_address_map, key)) + radix_tree_delete(trb_address_map, key); +} + +static int cdnsp_update_stream_segment_mapping(struct radix_tree_root *trb_address_map, + struct cdnsp_ring *ring, + struct cdnsp_segment *first_seg, + struct cdnsp_segment *last_seg, + gfp_t mem_flags) +{ + struct cdnsp_segment *failed_seg; + struct cdnsp_segment *seg; + int ret; + + seg = first_seg; + do { + ret = cdnsp_insert_segment_mapping(trb_address_map, ring, seg, + mem_flags); + if (ret) + goto remove_streams; + if (seg == last_seg) + return 0; + seg = seg->next; + } while (seg != first_seg); + + return 0; + +remove_streams: + failed_seg = seg; + seg = first_seg; + do { + cdnsp_remove_segment_mapping(trb_address_map, seg); + if (seg == failed_seg) + return ret; + seg = seg->next; + } while (seg != first_seg); + + return ret; +} + +static void cdnsp_remove_stream_mapping(struct cdnsp_ring *ring) +{ + struct cdnsp_segment *seg; + + seg = ring->first_seg; + do { + cdnsp_remove_segment_mapping(ring->trb_address_map, seg); + seg = seg->next; + } while (seg != ring->first_seg); +} + +static int cdnsp_update_stream_mapping(struct cdnsp_ring *ring) +{ + return cdnsp_update_stream_segment_mapping(ring->trb_address_map, ring, + ring->first_seg, ring->last_seg, GFP_ATOMIC); +} + +static void cdnsp_ring_free(struct cdnsp_device *pdev, struct cdnsp_ring *ring) +{ + if (!ring) + return; + + trace_cdnsp_ring_free(ring); + + if (ring->first_seg) { + if (ring->type == TYPE_STREAM) + cdnsp_remove_stream_mapping(ring); + + cdnsp_free_segments_for_ring(pdev, ring->first_seg); + } + + kfree(ring); +} + +void cdnsp_initialize_ring_info(struct cdnsp_ring *ring) +{ + 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 = 1; + + /* + * 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 cdnsp_alloc_segments_for_ring(struct cdnsp_device *pdev, + struct cdnsp_segment **first, + struct cdnsp_segment **last, + unsigned int num_segs, + unsigned int cycle_state, + enum cdnsp_ring_type type, + unsigned int max_packet, + gfp_t flags) +{ + struct cdnsp_segment *prev; + + /* Allocate first segment. */ + prev = cdnsp_segment_alloc(pdev, cycle_state, max_packet, flags); + if (!prev) + return -ENOMEM; + + num_segs--; + *first = prev; + + /* Allocate all other segments. */ + while (num_segs > 0) { + struct cdnsp_segment *next; + + next = cdnsp_segment_alloc(pdev, cycle_state, + max_packet, flags); + if (!next) { + cdnsp_free_segments_for_ring(pdev, *first); + return -ENOMEM; + } + + cdnsp_link_segments(pdev, prev, next, type); + + prev = next; + num_segs--; + } + + cdnsp_link_segments(pdev, 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. + */ +static struct cdnsp_ring *cdnsp_ring_alloc(struct cdnsp_device *pdev, + unsigned int num_segs, + enum cdnsp_ring_type type, + unsigned int max_packet, + gfp_t flags) +{ + struct cdnsp_ring *ring; + int ret; + + ring = kzalloc_obj(*(ring), flags); + if (!ring) + return NULL; + + ring->num_segs = num_segs; + ring->bounce_buf_len = max_packet; + INIT_LIST_HEAD(&ring->td_list); + ring->type = type; + + if (num_segs == 0) + return ring; + + ret = cdnsp_alloc_segments_for_ring(pdev, &ring->first_seg, + &ring->last_seg, num_segs, + 1, type, max_packet, flags); + if (ret) + goto fail; + + /* Only event ring does not use link TRB. */ + if (type != TYPE_EVENT) + ring->last_seg->trbs[TRBS_PER_SEGMENT - 1].link.control |= + cpu_to_le32(LINK_TOGGLE); + + cdnsp_initialize_ring_info(ring); + trace_cdnsp_ring_alloc(ring); + return ring; +fail: + kfree(ring); + return NULL; +} + +void cdnsp_free_endpoint_rings(struct cdnsp_device *pdev, struct cdnsp_ep *pep) +{ + cdnsp_ring_free(pdev, pep->ring); + pep->ring = NULL; + cdnsp_free_stream_info(pdev, pep); +} + +/* + * Expand an existing ring. + * Allocate a new ring which has same segment numbers and link the two rings. + */ +int cdnsp_ring_expansion(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + unsigned int num_trbs, + gfp_t flags) +{ + unsigned int num_segs_needed; + struct cdnsp_segment *first; + struct cdnsp_segment *last; + unsigned int num_segs; + 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 = max(ring->num_segs, num_segs_needed); + + ret = cdnsp_alloc_segments_for_ring(pdev, &first, &last, num_segs, + ring->cycle_state, ring->type, + ring->bounce_buf_len, flags); + if (ret) + return -ENOMEM; + + if (ring->type == TYPE_STREAM) + ret = cdnsp_update_stream_segment_mapping(ring->trb_address_map, + ring, first, + last, flags); + + if (ret) { + cdnsp_free_segments_for_ring(pdev, first); + + return ret; + } + + cdnsp_link_rings(pdev, ring, first, last, num_segs); + trace_cdnsp_ring_expansion(ring); + + return 0; +} + +static int cdnsp_init_device_ctx(struct cdnsp_device *pdev) +{ + int size = HCC_64BYTE_CONTEXT(pdev->hcc_params) ? 2048 : 1024; + + pdev->out_ctx.type = CDNSP_CTX_TYPE_DEVICE; + pdev->out_ctx.size = size; + pdev->out_ctx.ctx_size = CTX_SIZE(pdev->hcc_params); + pdev->out_ctx.bytes = dma_pool_zalloc(pdev->device_pool, GFP_ATOMIC, + &pdev->out_ctx.dma); + + if (!pdev->out_ctx.bytes) + return -ENOMEM; + + pdev->in_ctx.type = CDNSP_CTX_TYPE_INPUT; + pdev->in_ctx.ctx_size = pdev->out_ctx.ctx_size; + pdev->in_ctx.size = size + pdev->out_ctx.ctx_size; + pdev->in_ctx.bytes = dma_pool_zalloc(pdev->device_pool, GFP_ATOMIC, + &pdev->in_ctx.dma); + + if (!pdev->in_ctx.bytes) { + dma_pool_free(pdev->device_pool, pdev->out_ctx.bytes, + pdev->out_ctx.dma); + return -ENOMEM; + } + + return 0; +} + +struct cdnsp_input_control_ctx + *cdnsp_get_input_control_ctx(struct cdnsp_container_ctx *ctx) +{ + if (ctx->type != CDNSP_CTX_TYPE_INPUT) + return NULL; + + return (struct cdnsp_input_control_ctx *)ctx->bytes; +} + +struct cdnsp_slot_ctx *cdnsp_get_slot_ctx(struct cdnsp_container_ctx *ctx) +{ + if (ctx->type == CDNSP_CTX_TYPE_DEVICE) + return (struct cdnsp_slot_ctx *)ctx->bytes; + + return (struct cdnsp_slot_ctx *)(ctx->bytes + ctx->ctx_size); +} + +struct cdnsp_ep_ctx *cdnsp_get_ep_ctx(struct cdnsp_container_ctx *ctx, + unsigned int ep_index) +{ + /* Increment ep index by offset of start of ep ctx array. */ + ep_index++; + if (ctx->type == CDNSP_CTX_TYPE_INPUT) + ep_index++; + + return (struct cdnsp_ep_ctx *)(ctx->bytes + (ep_index * ctx->ctx_size)); +} + +static void cdnsp_free_stream_ctx(struct cdnsp_device *pdev, + struct cdnsp_ep *pep) +{ + dma_pool_free(pdev->device_pool, pep->stream_info.stream_ctx_array, + pep->stream_info.ctx_array_dma); +} + +/* The stream context array must be a power of 2. */ +static struct cdnsp_stream_ctx + *cdnsp_alloc_stream_ctx(struct cdnsp_device *pdev, struct cdnsp_ep *pep) +{ + size_t size = sizeof(struct cdnsp_stream_ctx) * + pep->stream_info.num_stream_ctxs; + + if (size > CDNSP_CTX_SIZE) + return NULL; + + /** + * Driver uses intentionally the device_pool to allocated stream + * context array. Device Pool has 2048 bytes of size what gives us + * 128 entries. + */ + return dma_pool_zalloc(pdev->device_pool, GFP_DMA32 | GFP_ATOMIC, + &pep->stream_info.ctx_array_dma); +} + +struct cdnsp_ring *cdnsp_dma_to_transfer_ring(struct cdnsp_ep *pep, u64 address) +{ + if (pep->ep_state & EP_HAS_STREAMS) + return radix_tree_lookup(&pep->stream_info.trb_address_map, + address >> TRB_SEGMENT_SHIFT); + + return pep->ring; +} + +/* + * Change an endpoint's internal structure so it supports stream IDs. + * The number of requested streams includes stream 0, which cannot be used by + * driver. + * + * 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. + */ +int cdnsp_alloc_stream_info(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + unsigned int num_stream_ctxs, + unsigned int num_streams) +{ + struct cdnsp_stream_info *stream_info; + struct cdnsp_ring *cur_ring; + u32 cur_stream; + u64 addr; + int ret; + int mps; + + stream_info = &pep->stream_info; + 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_objs(struct cdnsp_ring *, + num_streams, GFP_ATOMIC); + if (!stream_info->stream_rings) + return -ENOMEM; + + /* Initialize the array of DMA addresses for stream rings for the HW. */ + stream_info->stream_ctx_array = cdnsp_alloc_stream_ctx(pdev, pep); + if (!stream_info->stream_ctx_array) + goto cleanup_stream_rings; + + memset(stream_info->stream_ctx_array, 0, + sizeof(struct cdnsp_stream_ctx) * num_stream_ctxs); + INIT_RADIX_TREE(&stream_info->trb_address_map, GFP_ATOMIC); + mps = usb_endpoint_maxp(pep->endpoint.desc); + + /* + * 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++) { + cur_ring = cdnsp_ring_alloc(pdev, 2, TYPE_STREAM, mps, + GFP_ATOMIC); + stream_info->stream_rings[cur_stream] = cur_ring; + + if (!cur_ring) + goto cleanup_rings; + + cur_ring->stream_id = cur_stream; + cur_ring->trb_address_map = &stream_info->trb_address_map; + + /* 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); + + trace_cdnsp_set_stream_ring(cur_ring); + + ret = cdnsp_update_stream_mapping(cur_ring); + if (ret) + goto cleanup_rings; + } + + return 0; + +cleanup_rings: + for (cur_stream = 1; cur_stream < num_streams; cur_stream++) { + cur_ring = stream_info->stream_rings[cur_stream]; + if (cur_ring) { + cdnsp_ring_free(pdev, cur_ring); + stream_info->stream_rings[cur_stream] = NULL; + } + } + + cdnsp_free_stream_ctx(pdev, pep); + +cleanup_stream_rings: + kfree(pep->stream_info.stream_rings); + + return -ENOMEM; +} + +/* Frees all stream contexts associated with the endpoint. */ +static void cdnsp_free_stream_info(struct cdnsp_device *pdev, + struct cdnsp_ep *pep) +{ + struct cdnsp_stream_info *stream_info = &pep->stream_info; + struct cdnsp_ring *cur_ring; + int cur_stream; + + if (!(pep->ep_state & EP_HAS_STREAMS)) + return; + + for (cur_stream = 1; cur_stream < stream_info->num_streams; + cur_stream++) { + cur_ring = stream_info->stream_rings[cur_stream]; + if (cur_ring) { + cdnsp_ring_free(pdev, cur_ring); + stream_info->stream_rings[cur_stream] = NULL; + } + } + + if (stream_info->stream_ctx_array) + cdnsp_free_stream_ctx(pdev, pep); + + kfree(stream_info->stream_rings); + pep->ep_state &= ~EP_HAS_STREAMS; +} + +/* All the cdnsp_tds in the ring's TD list should be freed at this point.*/ +static void cdnsp_free_priv_device(struct cdnsp_device *pdev) +{ + pdev->dcbaa->dev_context_ptrs[1] = 0; + + cdnsp_free_endpoint_rings(pdev, &pdev->eps[0]); + + if (pdev->in_ctx.bytes) + dma_pool_free(pdev->device_pool, pdev->in_ctx.bytes, + pdev->in_ctx.dma); + + if (pdev->out_ctx.bytes) + dma_pool_free(pdev->device_pool, pdev->out_ctx.bytes, + pdev->out_ctx.dma); + + pdev->in_ctx.bytes = NULL; + pdev->out_ctx.bytes = NULL; +} + +static int cdnsp_alloc_priv_device(struct cdnsp_device *pdev) +{ + int ret; + + ret = cdnsp_init_device_ctx(pdev); + if (ret) + return ret; + + /* Allocate endpoint 0 ring. */ + pdev->eps[0].ring = cdnsp_ring_alloc(pdev, 2, TYPE_CTRL, 0, GFP_ATOMIC); + if (!pdev->eps[0].ring) + goto fail; + + /* Point to output device context in dcbaa. */ + pdev->dcbaa->dev_context_ptrs[1] = cpu_to_le64(pdev->out_ctx.dma); + pdev->cmd.in_ctx = &pdev->in_ctx; + + trace_cdnsp_alloc_priv_device(pdev); + return 0; +fail: + dma_pool_free(pdev->device_pool, pdev->out_ctx.bytes, + pdev->out_ctx.dma); + dma_pool_free(pdev->device_pool, pdev->in_ctx.bytes, + pdev->in_ctx.dma); + + return ret; +} + +void cdnsp_copy_ep0_dequeue_into_input_ctx(struct cdnsp_device *pdev) +{ + struct cdnsp_ep_ctx *ep0_ctx = pdev->eps[0].in_ctx; + struct cdnsp_ring *ep_ring = pdev->eps[0].ring; + dma_addr_t dma; + + dma = cdnsp_trb_virt_to_dma(ep_ring->enq_seg, ep_ring->enqueue); + ep0_ctx->deq = cpu_to_le64(dma | ep_ring->cycle_state); +} + +/* Setup an controller private device for a Set Address command. */ +int cdnsp_setup_addressable_priv_dev(struct cdnsp_device *pdev) +{ + struct cdnsp_slot_ctx *slot_ctx; + struct cdnsp_ep_ctx *ep0_ctx; + u32 max_packets, port; + + ep0_ctx = cdnsp_get_ep_ctx(&pdev->in_ctx, 0); + slot_ctx = cdnsp_get_slot_ctx(&pdev->in_ctx); + + /* Only the control endpoint is valid - one endpoint context. */ + slot_ctx->dev_info |= cpu_to_le32(LAST_CTX(1)); + + switch (pdev->gadget.speed) { + case USB_SPEED_SUPER_PLUS: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_SSP); + max_packets = MAX_PACKET(512); + break; + case USB_SPEED_SUPER: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_SS); + max_packets = MAX_PACKET(512); + break; + case USB_SPEED_HIGH: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_HS); + max_packets = MAX_PACKET(64); + break; + case USB_SPEED_FULL: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_FS); + max_packets = MAX_PACKET(64); + break; + default: + /* Speed was not set , this shouldn't happen. */ + return -EINVAL; + } + + port = DEV_PORT(pdev->active_port->port_num); + slot_ctx->dev_port |= cpu_to_le32(port); + slot_ctx->dev_state = cpu_to_le32((pdev->device_address & + DEV_ADDR_MASK)); + ep0_ctx->tx_info = cpu_to_le32(EP_AVG_TRB_LENGTH(0x8)); + ep0_ctx->ep_info2 = cpu_to_le32(EP_TYPE(CTRL_EP)); + ep0_ctx->ep_info2 |= cpu_to_le32(MAX_BURST(0) | ERROR_COUNT(3) | + max_packets); + + ep0_ctx->deq = cpu_to_le64(pdev->eps[0].ring->first_seg->dma | + pdev->eps[0].ring->cycle_state); + + trace_cdnsp_setup_addressable_priv_device(pdev); + + return 0; +} + +/* + * Convert interval expressed as 2^(bInterval - 1) == interval into + * straight exponent value 2^n == interval. + */ +static unsigned int cdnsp_parse_exponent_interval(struct usb_gadget *g, + struct cdnsp_ep *pep) +{ + unsigned int interval; + + interval = clamp_val(pep->endpoint.desc->bInterval, 1, 16) - 1; + if (interval != pep->endpoint.desc->bInterval - 1) + dev_warn(&g->dev, "ep %s - rounding interval to %d %sframes\n", + pep->name, 1 << interval, + g->speed == USB_SPEED_FULL ? "" : "micro"); + + /* + * Full speed isoc endpoints specify interval in frames, + * not microframes. We are using microframes everywhere, + * so adjust accordingly. + */ + if (g->speed == USB_SPEED_FULL) + interval += 3; /* 1 frame = 2^3 uframes */ + + /* Controller handles only up to 512ms (2^12). */ + if (interval > 12) + interval = 12; + + 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 cdnsp_microframes_to_exponent(struct usb_gadget *g, + struct cdnsp_ep *pep, + unsigned int desc_interval, + unsigned int min_exponent, + unsigned int max_exponent) +{ + unsigned int interval; + + interval = fls(desc_interval) - 1; + return clamp_val(interval, min_exponent, max_exponent); +} + +/* + * Return the polling interval. + * + * The polling interval is expressed in "microframes". If controllers's Interval + * field is set to N, it will service the endpoint every 2^(Interval)*125us. + */ +static unsigned int cdnsp_get_endpoint_interval(struct usb_gadget *g, + struct cdnsp_ep *pep) +{ + unsigned int interval = 0; + + switch (g->speed) { + case USB_SPEED_HIGH: + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER: + if (usb_endpoint_xfer_int(pep->endpoint.desc) || + usb_endpoint_xfer_isoc(pep->endpoint.desc)) + interval = cdnsp_parse_exponent_interval(g, pep); + break; + case USB_SPEED_FULL: + if (usb_endpoint_xfer_isoc(pep->endpoint.desc)) { + interval = cdnsp_parse_exponent_interval(g, pep); + } else if (usb_endpoint_xfer_int(pep->endpoint.desc)) { + interval = pep->endpoint.desc->bInterval << 3; + interval = cdnsp_microframes_to_exponent(g, pep, + interval, + 3, 10); + } + + break; + default: + WARN_ON(1); + } + + return 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 cdnsp_get_endpoint_mult(struct usb_gadget *g, struct cdnsp_ep *pep) +{ + if (g->speed < USB_SPEED_SUPER || + !usb_endpoint_xfer_isoc(pep->endpoint.desc)) + return 0; + + return pep->endpoint.comp_desc->bmAttributes; +} + +static u32 cdnsp_get_endpoint_max_burst(struct usb_gadget *g, + struct cdnsp_ep *pep) +{ + /* Super speed and Plus have max burst in ep companion desc */ + if (g->speed >= USB_SPEED_SUPER) + return pep->endpoint.comp_desc->bMaxBurst; + + if (g->speed == USB_SPEED_HIGH && + (usb_endpoint_xfer_isoc(pep->endpoint.desc) || + usb_endpoint_xfer_int(pep->endpoint.desc))) + return usb_endpoint_maxp_mult(pep->endpoint.desc) - 1; + + return 0; +} + +static u32 cdnsp_get_endpoint_type(const struct usb_endpoint_descriptor *desc) +{ + int in; + + in = usb_endpoint_dir_in(desc); + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_CONTROL: + return CTRL_EP; + case USB_ENDPOINT_XFER_BULK: + return in ? BULK_IN_EP : BULK_OUT_EP; + case USB_ENDPOINT_XFER_ISOC: + return in ? ISOC_IN_EP : ISOC_OUT_EP; + case USB_ENDPOINT_XFER_INT: + return in ? INT_IN_EP : INT_OUT_EP; + } + + return 0; +} + +/* + * 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 cdnsp_get_max_esit_payload(struct usb_gadget *g, + struct cdnsp_ep *pep) +{ + int max_packet; + int max_burst; + + /* Only applies for interrupt or isochronous endpoints*/ + if (usb_endpoint_xfer_control(pep->endpoint.desc) || + usb_endpoint_xfer_bulk(pep->endpoint.desc)) + return 0; + + /* SuperSpeedPlus Isoc ep sending over 48k per EIST. */ + if (g->speed >= USB_SPEED_SUPER_PLUS && + USB_SS_SSP_ISOC_COMP(pep->endpoint.desc->bmAttributes)) + return le16_to_cpu(pep->endpoint.comp_desc->wBytesPerInterval); + /* SuperSpeed or SuperSpeedPlus Isoc ep with less than 48k per esit */ + else if (g->speed >= USB_SPEED_SUPER) + return le16_to_cpu(pep->endpoint.comp_desc->wBytesPerInterval); + + max_packet = usb_endpoint_maxp(pep->endpoint.desc); + max_burst = usb_endpoint_maxp_mult(pep->endpoint.desc); + + /* A 0 in max burst means 1 transfer per ESIT */ + return max_packet * max_burst; +} + +int cdnsp_endpoint_init(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + gfp_t mem_flags) +{ + enum cdnsp_ring_type ring_type; + struct cdnsp_ep_ctx *ep_ctx; + unsigned int err_count = 0; + unsigned int avg_trb_len; + unsigned int max_packet; + unsigned int max_burst; + unsigned int interval; + u32 max_esit_payload; + unsigned int mult; + u32 endpoint_type; + int ret; + + ep_ctx = pep->in_ctx; + + endpoint_type = cdnsp_get_endpoint_type(pep->endpoint.desc); + if (!endpoint_type) + return -EINVAL; + + ring_type = usb_endpoint_type(pep->endpoint.desc); + + /* + * Get values to fill the endpoint context, mostly from ep descriptor. + * The average TRB buffer length for bulk endpoints is unclear as we + * have no clue on scatter gather list entry size. For Isoc and Int, + * set it to max available. + */ + max_esit_payload = cdnsp_get_max_esit_payload(&pdev->gadget, pep); + interval = cdnsp_get_endpoint_interval(&pdev->gadget, pep); + mult = cdnsp_get_endpoint_mult(&pdev->gadget, pep); + max_packet = usb_endpoint_maxp(pep->endpoint.desc); + max_burst = cdnsp_get_endpoint_max_burst(&pdev->gadget, pep); + avg_trb_len = max_esit_payload; + + /* Allow 3 retries for everything but isoc, set CErr = 3. */ + if (!usb_endpoint_xfer_isoc(pep->endpoint.desc)) + err_count = 3; + if (usb_endpoint_xfer_bulk(pep->endpoint.desc) && + pdev->gadget.speed == USB_SPEED_HIGH) + max_packet = 512; + /* Controller spec indicates that ctrl ep avg TRB Length should be 8. */ + if (usb_endpoint_xfer_control(pep->endpoint.desc)) + avg_trb_len = 8; + + /* Set up the endpoint ring. */ + pep->ring = cdnsp_ring_alloc(pdev, 2, ring_type, max_packet, mem_flags); + if (!pep->ring) + return -ENOMEM; + + pep->skip = false; + + /* Fill the endpoint context */ + ep_ctx->ep_info = cpu_to_le32(EP_MAX_ESIT_PAYLOAD_HI(max_esit_payload) | + EP_INTERVAL(interval) | EP_MULT(mult)); + ep_ctx->ep_info2 = cpu_to_le32(EP_TYPE(endpoint_type) | + MAX_PACKET(max_packet) | MAX_BURST(max_burst) | + ERROR_COUNT(err_count)); + ep_ctx->deq = cpu_to_le64(pep->ring->first_seg->dma | + pep->ring->cycle_state); + + ep_ctx->tx_info = cpu_to_le32(EP_MAX_ESIT_PAYLOAD_LO(max_esit_payload) | + EP_AVG_TRB_LENGTH(avg_trb_len)); + + if (usb_endpoint_xfer_bulk(pep->endpoint.desc) && + pdev->gadget.speed > USB_SPEED_HIGH) { + ret = cdnsp_alloc_streams(pdev, pep); + if (ret < 0) + return ret; + } + + return 0; +} + +void cdnsp_endpoint_zero(struct cdnsp_device *pdev, struct cdnsp_ep *pep) +{ + pep->in_ctx->ep_info = 0; + pep->in_ctx->ep_info2 = 0; + pep->in_ctx->deq = 0; + pep->in_ctx->tx_info = 0; +} + +static int cdnsp_alloc_erst(struct cdnsp_device *pdev, + struct cdnsp_ring *evt_ring, + struct cdnsp_erst *erst) +{ + struct cdnsp_erst_entry *entry; + struct cdnsp_segment *seg; + unsigned int val; + size_t size; + + size = sizeof(struct cdnsp_erst_entry) * evt_ring->num_segs; + erst->entries = dma_alloc_coherent(pdev->dev, size, + &erst->erst_dma_addr, GFP_KERNEL); + if (!erst->entries) + return -ENOMEM; + + erst->num_entries = evt_ring->num_segs; + + seg = evt_ring->first_seg; + for (val = 0; val < evt_ring->num_segs; val++) { + entry = &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; + } + + return 0; +} + +static void cdnsp_free_erst(struct cdnsp_device *pdev, struct cdnsp_erst *erst) +{ + size_t size = sizeof(struct cdnsp_erst_entry) * (erst->num_entries); + struct device *dev = pdev->dev; + + if (erst->entries) + dma_free_coherent(dev, size, erst->entries, + erst->erst_dma_addr); + + erst->entries = NULL; +} + +void cdnsp_mem_cleanup(struct cdnsp_device *pdev) +{ + struct device *dev = pdev->dev; + + cdnsp_free_priv_device(pdev); + cdnsp_free_erst(pdev, &pdev->erst); + + if (pdev->event_ring) + cdnsp_ring_free(pdev, pdev->event_ring); + + pdev->event_ring = NULL; + + if (pdev->cmd_ring) + cdnsp_ring_free(pdev, pdev->cmd_ring); + + pdev->cmd_ring = NULL; + + dma_pool_destroy(pdev->segment_pool); + pdev->segment_pool = NULL; + dma_pool_destroy(pdev->device_pool); + pdev->device_pool = NULL; + + dma_free_coherent(dev, sizeof(*pdev->dcbaa), + pdev->dcbaa, pdev->dcbaa->dma); + + pdev->dcbaa = NULL; + memset(&pdev->usb2_port, 0, sizeof(struct cdnsp_port)); + memset(&pdev->eusb_port, 0, sizeof(struct cdnsp_port)); + memset(&pdev->usb3_port, 0, sizeof(struct cdnsp_port)); + pdev->active_port = NULL; +} + +static void cdnsp_set_event_deq(struct cdnsp_device *pdev) +{ + dma_addr_t deq; + u64 temp; + + deq = cdnsp_trb_virt_to_dma(pdev->event_ring->deq_seg, + pdev->event_ring->dequeue); + + /* Update controller event ring dequeue pointer */ + temp = cdnsp_read_64(&pdev->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; + + cdnsp_write_64(((u64)deq & (u64)~ERST_PTR_MASK) | temp, + &pdev->ir_set->erst_dequeue); +} + +static void cdnsp_add_in_port(struct cdnsp_device *pdev, + struct cdnsp_port *port, + __le32 __iomem *addr) +{ + u32 temp, port_offset, port_count; + + temp = readl(addr); + port->maj_rev = CDNSP_EXT_PORT_MAJOR(temp); + port->min_rev = CDNSP_EXT_PORT_MINOR(temp); + + /* Port offset and count in the third dword.*/ + temp = readl(addr + 2); + port_offset = CDNSP_EXT_PORT_OFF(temp); + port_count = CDNSP_EXT_PORT_COUNT(temp); + + if (port == &pdev->eusb_port) { + /* + * If controller has usb2 + eusb port then eusb is as + * second port + */ + if (port_count == 2) + port_offset++; + + if (port_count == 1 && pdev->usb2_port.exist) + return; + } + + trace_cdnsp_port_info(addr, port_offset, port_count, port->maj_rev); + + port->port_num = port_offset; + port->exist = 1; +} + +/* + * Scan the Extended Capabilities for the "Supported Protocol Capabilities" that + * specify what speeds each port is supposed to be. + */ +static int cdnsp_setup_port_arrays(struct cdnsp_device *pdev) +{ + void __iomem *base; + u32 offset; + int i; + + base = &pdev->cap_regs->hc_capbase; + offset = cdnsp_find_next_ext_cap(base, 0, + EXT_CAP_CFG_DEV_20PORT_CAP_ID); + if (offset) + pdev->port20_regs = base + offset; + + offset = 0; + + /* Driver expects max 2 extended protocol capability. */ + for (i = 0; i < 2; i++) { + u32 temp; + + offset = cdnsp_find_next_ext_cap(base, offset, + EXT_CAPS_PROTOCOL); + temp = readl(base + offset); + + if (CDNSP_EXT_PORT_MAJOR(temp) == 0x03 && + !pdev->usb3_port.port_num) + cdnsp_add_in_port(pdev, &pdev->usb3_port, + base + offset); + + if (CDNSP_EXT_PORT_MAJOR(temp) == 0x02) { + if (!pdev->usb2_port.port_num && pdev->port20_regs) + cdnsp_add_in_port(pdev, &pdev->usb2_port, + base + offset); + + if (!pdev->eusb_port.port_num) + cdnsp_add_in_port(pdev, &pdev->eusb_port, + base + offset); + } + } + + if (!pdev->usb2_port.exist && !pdev->eusb_port.exist && + !pdev->usb3_port.exist) { + dev_err(pdev->dev, "Error: No port detected\n"); + return -ENODEV; + } + + if (pdev->usb2_port.exist) { + pdev->usb2_port.regs = (struct cdnsp_port_regs __iomem *) + (&pdev->op_regs->port_reg_base + NUM_PORT_REGS * + (pdev->usb2_port.port_num - 1)); + trace_cdnsp_init("Found USB 2.0 port."); + } + + if (pdev->eusb_port.exist) { + pdev->eusb_port.regs = (struct cdnsp_port_regs __iomem *) + (&pdev->op_regs->port_reg_base + NUM_PORT_REGS * + (pdev->eusb_port.port_num - 1)); + trace_cdnsp_init("Found eUSB 2.0 port."); + } + + if (pdev->usb3_port.exist) { + offset = cdnsp_find_next_ext_cap(base, 0, D_XEC_CFG_3XPORT_CAP); + pdev->port3x_regs = base + offset; + + pdev->usb3_port.regs = (struct cdnsp_port_regs __iomem *) + (&pdev->op_regs->port_reg_base + NUM_PORT_REGS * + (pdev->usb3_port.port_num - 1)); + trace_cdnsp_init("Found USB 3.x port."); + } + + return 0; +} + +/* + * Initialize memory for CDNSP (one-time init). + * + * Program the PAGESIZE register, initialize the device context array, create + * device contexts, set up a command ring segment, create event + * ring (one for now). + */ +int cdnsp_mem_init(struct cdnsp_device *pdev) +{ + struct device *dev = pdev->dev; + int ret = -ENOMEM; + unsigned int val; + dma_addr_t dma; + u32 page_size; + u64 val_64; + + /* + * Use 4K pages, since that's common and the minimum the + * controller supports + */ + page_size = 1 << 12; + + val = readl(&pdev->op_regs->config_reg); + val |= ((val & ~MAX_DEVS) | CDNSP_DEV_MAX_SLOTS) | CONFIG_U3E; + writel(val, &pdev->op_regs->config_reg); + + /* + * Doorbell array must be physically contiguous + * and 64-byte (cache line) aligned. + */ + pdev->dcbaa = dma_alloc_coherent(dev, sizeof(*pdev->dcbaa), + &dma, GFP_KERNEL); + if (!pdev->dcbaa) + return -ENOMEM; + + pdev->dcbaa->dma = dma; + + cdnsp_write_64(dma, &pdev->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 + * and our use of dma addresses in the trb_address_map radix tree needs + * TRB_SEGMENT_SIZE alignment, so driver pick the greater alignment + * need. + */ + pdev->segment_pool = dma_pool_create("CDNSP ring segments", dev, + TRB_SEGMENT_SIZE, TRB_SEGMENT_SIZE, + page_size); + if (!pdev->segment_pool) + goto release_dcbaa; + + pdev->device_pool = dma_pool_create("CDNSP input/output contexts", dev, + CDNSP_CTX_SIZE, 64, page_size); + if (!pdev->device_pool) + goto destroy_segment_pool; + + + /* Set up the command ring to have one segments for now. */ + pdev->cmd_ring = cdnsp_ring_alloc(pdev, 1, TYPE_COMMAND, 0, GFP_KERNEL); + if (!pdev->cmd_ring) + goto destroy_device_pool; + + /* Set the address in the Command Ring Control register */ + val_64 = cdnsp_read_64(&pdev->op_regs->cmd_ring); + val_64 = (val_64 & (u64)CMD_RING_RSVD_BITS) | + (pdev->cmd_ring->first_seg->dma & (u64)~CMD_RING_RSVD_BITS) | + pdev->cmd_ring->cycle_state; + cdnsp_write_64(val_64, &pdev->op_regs->cmd_ring); + + val = readl(&pdev->cap_regs->db_off); + val &= DBOFF_MASK; + pdev->dba = (void __iomem *)pdev->cap_regs + val; + + /* Set ir_set to interrupt register set 0 */ + pdev->ir_set = &pdev->run_regs->ir_set[0]; + + /* + * Event ring setup: Allocate a normal ring, but also setup + * the event ring segment table (ERST). + */ + pdev->event_ring = cdnsp_ring_alloc(pdev, ERST_NUM_SEGS, TYPE_EVENT, + 0, GFP_KERNEL); + if (!pdev->event_ring) + goto free_cmd_ring; + + ret = cdnsp_alloc_erst(pdev, pdev->event_ring, &pdev->erst); + if (ret) + goto free_event_ring; + + /* Set ERST count with the number of entries in the segment table. */ + val = readl(&pdev->ir_set->erst_size); + val &= ERST_SIZE_MASK; + val |= ERST_NUM_SEGS; + writel(val, &pdev->ir_set->erst_size); + + /* Set the segment table base address. */ + val_64 = cdnsp_read_64(&pdev->ir_set->erst_base); + val_64 &= ERST_PTR_MASK; + val_64 |= (pdev->erst.erst_dma_addr & (u64)~ERST_PTR_MASK); + cdnsp_write_64(val_64, &pdev->ir_set->erst_base); + + /* Set the event ring dequeue address. */ + cdnsp_set_event_deq(pdev); + + ret = cdnsp_setup_port_arrays(pdev); + if (ret) + goto free_erst; + + ret = cdnsp_alloc_priv_device(pdev); + if (ret) { + dev_err(pdev->dev, + "Could not allocate cdnsp_device data structures\n"); + goto free_erst; + } + + return 0; + +free_erst: + cdnsp_free_erst(pdev, &pdev->erst); +free_event_ring: + cdnsp_ring_free(pdev, pdev->event_ring); +free_cmd_ring: + cdnsp_ring_free(pdev, pdev->cmd_ring); +destroy_device_pool: + dma_pool_destroy(pdev->device_pool); +destroy_segment_pool: + dma_pool_destroy(pdev->segment_pool); +release_dcbaa: + dma_free_coherent(dev, sizeof(*pdev->dcbaa), pdev->dcbaa, + pdev->dcbaa->dma); + + cdnsp_reset(pdev); + + return ret; +} diff --git a/drivers/usb/cdns3/cdnsp-pci.c b/drivers/usb/cdns3/cdnsp-pci.c new file mode 100644 index 000000000..c38a3dc71 --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-pci.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBSSP PCI Glue driver. + * + * Copyright (C) 2019 Cadence. + * + * Author: Pawel Laszczak + * + */ + +#include +#include +#include +#include +#include +#include + +#include "core.h" + +struct cdnsp_wrap { + struct platform_device *plat_dev; + struct property_entry prop[3]; + struct resource dev_res[6]; + int devfn; +}; + +#define RES_IRQ_HOST_ID 0 +#define RES_IRQ_PERIPHERAL_ID 1 +#define RES_IRQ_OTG_ID 2 +#define RES_HOST_ID 3 +#define RES_DEV_ID 4 +#define RES_DRD_ID 5 +/* DRD PCI configuration - 64-bit addressing */ +/* First PCI function */ +#define PCI_BAR_HOST 0 +#define PCI_BAR_DEV 2 +/* Second PCI function */ +#define PCI_BAR_OTG 0 +/* Device only PCI configuration - 32-bit addressing */ +/* First PCI function */ +#define PCI_BAR_ONLY_DEV 1 + +#define PCI_DEV_FN_HOST_DEVICE 0 +#define PCI_DEV_FN_OTG 1 + +#define PCI_DRIVER_NAME "cdns-pci-usbssp" +#define PLAT_DRIVER_NAME "cdns-usb3" + +#define PCI_DEVICE_ID_CDNS_UDC_USBSSP 0x0400 +#define CHICKEN_APB_TIMEOUT_VALUE 0x1C20 + +static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev) +{ + /* + * Gets the second function. + * Platform has two function. The first keeps resources for + * Host/Device while the second keeps resources for DRD/OTG. + */ + if (pdev->device == PCI_DEVICE_ID_CDNS_USBSSP) + return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_USBSS, NULL); + if (pdev->device == PCI_DEVICE_ID_CDNS_USBSS) + return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_USBSSP, NULL); + + return NULL; +} + +static int cdnsp_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct platform_device_info plat_info; + static struct cdns3_platform_data pdata; + struct cdnsp_wrap *wrap; + struct resource *res; + struct pci_dev *func; + bool no_drd = false; + int ret = 0; + + /* + * For GADGET/HOST PCI (devfn) function number is 0, + * for OTG PCI (devfn) function number is 1. + */ + if (!id || (pdev->devfn != PCI_DEV_FN_HOST_DEVICE && + pdev->devfn != PCI_DEV_FN_OTG)) + return -EINVAL; + + if (pdev->device == PCI_DEVICE_ID_CDNS_UDC_USBSSP) + no_drd = true; + + func = cdnsp_get_second_fun(pdev); + if (!func && !no_drd) + return -EINVAL; + + if ((func && func->class == PCI_CLASS_SERIAL_USB_XHCI) || + pdev->class == PCI_CLASS_SERIAL_USB_XHCI) { + ret = -EINVAL; + goto put_pci; + } + + ret = pcim_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "Enabling PCI device has failed %d\n", ret); + goto put_pci; + } + + pci_set_master(pdev); + + if (func && pci_is_enabled(func)) { + wrap = pci_get_drvdata(func); + } else { + wrap = kzalloc_obj(*wrap); + if (!wrap) { + ret = -ENOMEM; + goto put_pci; + } + } + + res = wrap->dev_res; + + if (pdev->devfn == PCI_DEV_FN_HOST_DEVICE) { + int bar_dev = no_drd ? PCI_BAR_ONLY_DEV : PCI_BAR_DEV; + + /* Function 0: host(BAR_0) + device(BAR_2). */ + dev_dbg(&pdev->dev, "Initialize Device resources\n"); + res[RES_DEV_ID].start = pci_resource_start(pdev, bar_dev); + res[RES_DEV_ID].end = pci_resource_end(pdev, bar_dev); + res[RES_DEV_ID].name = "dev"; + res[RES_DEV_ID].flags = IORESOURCE_MEM; + dev_dbg(&pdev->dev, "USBSSP-DEV physical base addr: %pa\n", + &res[RES_DEV_ID].start); + + res[RES_HOST_ID].start = pci_resource_start(pdev, PCI_BAR_HOST); + res[RES_HOST_ID].end = pci_resource_end(pdev, PCI_BAR_HOST); + res[RES_HOST_ID].name = "xhci"; + res[RES_HOST_ID].flags = IORESOURCE_MEM; + dev_dbg(&pdev->dev, "USBSSP-XHCI physical base addr: %pa\n", + &res[RES_HOST_ID].start); + + /* Interrupt for XHCI */ + wrap->dev_res[RES_IRQ_HOST_ID].start = pdev->irq; + wrap->dev_res[RES_IRQ_HOST_ID].name = "host"; + wrap->dev_res[RES_IRQ_HOST_ID].flags = IORESOURCE_IRQ; + + /* Interrupt for device. It's the same as for HOST. */ + wrap->dev_res[RES_IRQ_PERIPHERAL_ID].start = pdev->irq; + wrap->dev_res[RES_IRQ_PERIPHERAL_ID].name = "peripheral"; + wrap->dev_res[RES_IRQ_PERIPHERAL_ID].flags = IORESOURCE_IRQ; + } else { + res[RES_DRD_ID].start = pci_resource_start(pdev, PCI_BAR_OTG); + res[RES_DRD_ID].end = pci_resource_end(pdev, PCI_BAR_OTG); + res[RES_DRD_ID].name = "otg"; + res[RES_DRD_ID].flags = IORESOURCE_MEM; + dev_dbg(&pdev->dev, "CDNSP-DRD physical base addr: %pa\n", + &res[RES_DRD_ID].start); + + /* Interrupt for OTG/DRD. */ + wrap->dev_res[RES_IRQ_OTG_ID].start = pdev->irq; + wrap->dev_res[RES_IRQ_OTG_ID].name = "otg"; + wrap->dev_res[RES_IRQ_OTG_ID].flags = IORESOURCE_IRQ; + } + + if (no_drd || pci_is_enabled(func)) { + u8 idx = 0; + + /* set up platform device info */ + pdata.override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE; + + if (no_drd) { + wrap->prop[idx++] = PROPERTY_ENTRY_STRING("compatible", + "cdns,cdnsp"); + wrap->prop[idx++] = PROPERTY_ENTRY_STRING("dr_mode", "peripheral"); + } else { + wrap->prop[idx++] = PROPERTY_ENTRY_STRING("dr_mode", "otg"); + wrap->prop[idx++] = PROPERTY_ENTRY_BOOL("usb-role-switch"); + } + + memset(&plat_info, 0, sizeof(plat_info)); + plat_info.parent = &pdev->dev; + plat_info.fwnode = pdev->dev.fwnode; + plat_info.name = PLAT_DRIVER_NAME; + plat_info.id = pdev->devfn; + plat_info.res = wrap->dev_res; + plat_info.num_res = ARRAY_SIZE(wrap->dev_res); + plat_info.dma_mask = pdev->dma_mask; + plat_info.data = &pdata; + plat_info.size_data = sizeof(pdata); + plat_info.properties = wrap->prop; + wrap->devfn = pdev->devfn; + /* register platform device */ + wrap->plat_dev = platform_device_register_full(&plat_info); + if (IS_ERR(wrap->plat_dev)) { + ret = PTR_ERR(wrap->plat_dev); + kfree(wrap); + goto put_pci; + } + } + + pci_set_drvdata(pdev, wrap); +put_pci: + pci_dev_put(func); + return ret; +} + +static void cdnsp_pci_remove(struct pci_dev *pdev) +{ + struct cdnsp_wrap *wrap; + struct pci_dev *func; + + func = cdnsp_get_second_fun(pdev); + wrap = pci_get_drvdata(pdev); + + if (wrap->devfn == pdev->devfn) + platform_device_unregister(wrap->plat_dev); + + if (!func || !pci_is_enabled(func)) + kfree(wrap); + + pci_dev_put(func); +} + +static const struct pci_device_id cdnsp_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_UDC_USBSSP), + .class = PCI_CLASS_SERIAL_USB_DEVICE }, + { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_UDC_USBSSP), + .class = PCI_CLASS_SERIAL_USB_CDNS }, + { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP), + .class = PCI_CLASS_SERIAL_USB_DEVICE }, + { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP), + .class = PCI_CLASS_SERIAL_USB_CDNS }, + { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSS), + .class = PCI_CLASS_SERIAL_USB_CDNS }, + { 0, } +}; + +static struct pci_driver cdnsp_pci_driver = { + .name = PCI_DRIVER_NAME, + .id_table = cdnsp_pci_ids, + .probe = cdnsp_pci_probe, + .remove = cdnsp_pci_remove, +}; + +module_pci_driver(cdnsp_pci_driver); +MODULE_DEVICE_TABLE(pci, cdnsp_pci_ids); + +MODULE_ALIAS("pci:cdnsp"); +MODULE_AUTHOR("Pawel Laszczak "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Cadence CDNSP PCI wrapper"); diff --git a/drivers/usb/cdns3/cdnsp-ring.c b/drivers/usb/cdns3/cdnsp-ring.c new file mode 100644 index 000000000..715658c98 --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-ring.c @@ -0,0 +1,2507 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence CDNSP DRD Driver. + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + * Code based on Linux XHCI driver. + * Origin: Copyright (C) 2008 Intel Corp + */ + +/* + * 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 controller 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. Controller is the consumer for the + * command and endpoint rings; it generates events on the event ring + * for these. + */ + +#include +#include +#include +#include +#include + +#include "cdnsp-trace.h" +#include "cdnsp-gadget.h" + +/* + * Returns zero if the TRB isn't in this segment, otherwise it returns the DMA + * address of the TRB. + */ +dma_addr_t cdnsp_trb_virt_to_dma(struct cdnsp_segment *seg, + union cdnsp_trb *trb) +{ + unsigned long segment_offset = trb - seg->trbs; + + if (trb < seg->trbs || segment_offset >= TRBS_PER_SEGMENT) + return 0; + + return seg->dma + (segment_offset * sizeof(*trb)); +} + +static bool cdnsp_trb_is_noop(union cdnsp_trb *trb) +{ + return TRB_TYPE_NOOP_LE32(trb->generic.field[3]); +} + +static bool cdnsp_trb_is_link(union cdnsp_trb *trb) +{ + return TRB_TYPE_LINK_LE32(trb->link.control); +} + +bool cdnsp_last_trb_on_seg(struct cdnsp_segment *seg, union cdnsp_trb *trb) +{ + return trb == &seg->trbs[TRBS_PER_SEGMENT - 1]; +} + +bool cdnsp_last_trb_on_ring(struct cdnsp_ring *ring, + struct cdnsp_segment *seg, + union cdnsp_trb *trb) +{ + return cdnsp_last_trb_on_seg(seg, trb) && (seg->next == ring->first_seg); +} + +static bool cdnsp_link_trb_toggles_cycle(union cdnsp_trb *trb) +{ + return le32_to_cpu(trb->link.control) & LINK_TOGGLE; +} + +static void cdnsp_trb_to_noop(union cdnsp_trb *trb, u32 noop_type) +{ + if (cdnsp_trb_is_link(trb)) { + /* Unchain chained link TRBs. */ + trb->link.control &= cpu_to_le32(~TRB_CHAIN); + } else { + trb->generic.field[0] = 0; + trb->generic.field[1] = 0; + trb->generic.field[2] = 0; + /* Preserve only the cycle bit of this TRB. */ + trb->generic.field[3] &= cpu_to_le32(TRB_CYCLE); + trb->generic.field[3] |= cpu_to_le32(TRB_TYPE(noop_type)); + } +} + +/* + * 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 cdnsp_next_trb(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + struct cdnsp_segment **seg, + union cdnsp_trb **trb) +{ + if (cdnsp_trb_is_link(*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. + */ +void cdnsp_inc_deq(struct cdnsp_device *pdev, struct cdnsp_ring *ring) +{ + /* event ring doesn't have link trbs, check for last trb. */ + if (ring->type == TYPE_EVENT) { + if (!cdnsp_last_trb_on_seg(ring->deq_seg, ring->dequeue)) { + ring->dequeue++; + goto out; + } + + if (cdnsp_last_trb_on_ring(ring, ring->deq_seg, ring->dequeue)) + ring->cycle_state ^= 1; + + ring->deq_seg = ring->deq_seg->next; + ring->dequeue = ring->deq_seg->trbs; + goto out; + } + + /* All other rings have link trbs. */ + if (!cdnsp_trb_is_link(ring->dequeue)) { + ring->dequeue++; + ring->num_trbs_free++; + } + while (cdnsp_trb_is_link(ring->dequeue)) { + ring->deq_seg = ring->deq_seg->next; + ring->dequeue = ring->deq_seg->trbs; + } +out: + trace_cdnsp_inc_deq(ring); +} + +/* + * 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). + * + * @more_trbs_coming: Will you enqueue more TRBs before ringing the doorbell. + */ +static void cdnsp_inc_enq(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + bool more_trbs_coming) +{ + union cdnsp_trb *next; + u32 chain; + + chain = le32_to_cpu(ring->enqueue->generic.field[3]) & TRB_CHAIN; + + /* If this is not event ring, there is one less usable TRB. */ + if (!cdnsp_trb_is_link(ring->enqueue)) + ring->num_trbs_free--; + next = ++(ring->enqueue); + + /* Update the dequeue pointer further if that was a link TRB */ + while (cdnsp_trb_is_link(next)) { + /* + * If the caller doesn't plan on enqueuing 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 + * cdnsp_prepare_ring() just before we enqueue the TD at the + * top of the ring. + */ + if (!chain && !more_trbs_coming) + break; + + 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 (cdnsp_link_trb_toggles_cycle(next)) + ring->cycle_state ^= 1; + + ring->enq_seg = ring->enq_seg->next; + ring->enqueue = ring->enq_seg->trbs; + next = ring->enqueue; + } + + trace_cdnsp_inc_enq(ring); +} + +/* + * 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. + */ +static bool cdnsp_room_on_ring(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + unsigned int num_trbs) +{ + int num_trbs_in_deq_seg; + + if (ring->num_trbs_free < num_trbs) + return false; + + 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 false; + } + + return true; +} + +/* + * Workaround for L1: controller has issue with resuming from L1 after + * setting doorbell for endpoint during L1 state. This function forces + * resume signal in such case. + */ +static void cdnsp_force_l0_go(struct cdnsp_device *pdev) +{ + if (pdev->active_port != &pdev->usb3_port && pdev->gadget.lpm_capable) + cdnsp_set_link_state(pdev, &pdev->active_port->regs->portsc, XDEV_U0); +} + +/* Ring the doorbell after placing a command on the ring. */ +void cdnsp_ring_cmd_db(struct cdnsp_device *pdev) +{ + writel(DB_VALUE_CMD, &pdev->dba->cmd_db); +} + +/* + * Ring the doorbell after placing a transfer on the ring. + * Returns true if doorbell was set, otherwise false. + */ +static bool cdnsp_ring_ep_doorbell(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + unsigned int stream_id) +{ + __le32 __iomem *reg_addr = &pdev->dba->ep_db; + unsigned int ep_state = pep->ep_state; + unsigned int db_value; + + /* + * Don't ring the doorbell for this endpoint if endpoint is halted or + * disabled. + */ + if (ep_state & EP_HALTED || !(ep_state & EP_ENABLED)) + return false; + + /* For stream capable endpoints driver can ring doorbell only twice. */ + if (pep->ep_state & EP_HAS_STREAMS) { + if (pep->stream_info.drbls_count >= 2) + return false; + + pep->stream_info.drbls_count++; + } + + pep->ep_state &= ~EP_STOPPED; + + if (pep->idx == 0 && pdev->ep0_stage == CDNSP_DATA_STAGE && + !pdev->ep0_expect_in) + db_value = DB_VALUE_EP0_OUT(pep->idx, stream_id); + else + db_value = DB_VALUE(pep->idx, stream_id); + + trace_cdnsp_tr_drbl(pep, stream_id); + + writel(db_value, reg_addr); + + if (pdev->rtl_revision < RTL_REVISION_NEW_LPM) + cdnsp_force_l0_go(pdev); + + /* Doorbell was set. */ + return true; +} + +/* + * Get the right ring for the given pep and stream_id. + * If the endpoint supports streams, boundary check the USB request's stream ID. + * If the endpoint doesn't support streams, return the singular endpoint ring. + */ +static struct cdnsp_ring *cdnsp_get_transfer_ring(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + unsigned int stream_id) +{ + if (!(pep->ep_state & EP_HAS_STREAMS)) + return pep->ring; + + if (stream_id == 0 || stream_id >= pep->stream_info.num_streams) { + dev_err(pdev->dev, "ERR: %s ring doesn't exist for SID: %d.\n", + pep->name, stream_id); + return NULL; + } + + return pep->stream_info.stream_rings[stream_id]; +} + +static struct cdnsp_ring * + cdnsp_request_to_transfer_ring(struct cdnsp_device *pdev, + struct cdnsp_request *preq) +{ + return cdnsp_get_transfer_ring(pdev, preq->pep, + preq->request.stream_id); +} + +/* Ring the doorbell for any rings with pending requests. */ +void cdnsp_ring_doorbell_for_active_rings(struct cdnsp_device *pdev, + struct cdnsp_ep *pep) +{ + struct cdnsp_stream_info *stream_info; + unsigned int stream_id; + int ret; + + if (pep->ep_state & EP_DIS_IN_RROGRESS) + return; + + /* A ring has pending Request if its TD list is not empty. */ + if (!(pep->ep_state & EP_HAS_STREAMS) && pep->number) { + if (pep->ring && !list_empty(&pep->ring->td_list)) + cdnsp_ring_ep_doorbell(pdev, pep, 0); + return; + } + + stream_info = &pep->stream_info; + + for (stream_id = 1; stream_id < stream_info->num_streams; stream_id++) { + struct cdnsp_td *td, *td_temp; + struct cdnsp_ring *ep_ring; + + if (stream_info->drbls_count >= 2) + return; + + ep_ring = cdnsp_get_transfer_ring(pdev, pep, stream_id); + if (!ep_ring) + continue; + + if (!ep_ring->stream_active || ep_ring->stream_rejected) + continue; + + list_for_each_entry_safe(td, td_temp, &ep_ring->td_list, + td_list) { + if (td->drbl) + continue; + + ret = cdnsp_ring_ep_doorbell(pdev, pep, stream_id); + if (ret) + td->drbl = 1; + } + } +} + +/* + * Get the hw dequeue pointer controller stopped on, either directly from the + * endpoint context, or if streams are in use from the stream context. + * The returned hw_dequeue contains the lowest four bits with cycle state + * and possible stream context type. + */ +static u64 cdnsp_get_hw_deq(struct cdnsp_device *pdev, + unsigned int ep_index, + unsigned int stream_id) +{ + struct cdnsp_stream_ctx *st_ctx; + struct cdnsp_ep *pep; + + pep = &pdev->eps[ep_index]; + + if (pep->ep_state & EP_HAS_STREAMS) { + st_ctx = &pep->stream_info.stream_ctx_array[stream_id]; + return le64_to_cpu(st_ctx->stream_ring); + } + + return le64_to_cpu(pep->out_ctx->deq); +} + +/* + * Move the controller endpoint ring dequeue pointer past cur_td. + * Record the new state of the controller endpoint ring dequeue segment, + * dequeue pointer, and new consumer cycle state in state. + * Update 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 + * controller stopped. + * - Then we traverse the ring to find the segment that contains + * the last TRB in the TD. We toggle the controller 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. + */ +static void cdnsp_find_new_dequeue_state(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + unsigned int stream_id, + struct cdnsp_td *cur_td, + struct cdnsp_dequeue_state *state) +{ + bool td_last_trb_found = false; + struct cdnsp_segment *new_seg; + struct cdnsp_ring *ep_ring; + union cdnsp_trb *new_deq; + bool cycle_found = false; + u64 hw_dequeue; + + ep_ring = cdnsp_get_transfer_ring(pdev, pep, stream_id); + if (!ep_ring) + return; + + /* + * Dig out the cycle state saved by the controller during the + * stop endpoint command. + */ + hw_dequeue = cdnsp_get_hw_deq(pdev, pep->idx, stream_id); + new_seg = ep_ring->deq_seg; + new_deq = ep_ring->dequeue; + state->new_cycle_state = hw_dequeue & 0x1; + state->stream_id = stream_id; + + /* + * We want to find the pointer, segment and cycle state of the new trb + * (the one after current TD's last_trb). We know the cycle state at + * hw_dequeue, so walk the ring until both hw_dequeue and last_trb are + * found. + */ + do { + if (!cycle_found && cdnsp_trb_virt_to_dma(new_seg, new_deq) + == (dma_addr_t)(hw_dequeue & ~0xf)) { + cycle_found = true; + + if (td_last_trb_found) + break; + } + + if (new_deq == cur_td->last_trb) + td_last_trb_found = true; + + if (cycle_found && cdnsp_trb_is_link(new_deq) && + cdnsp_link_trb_toggles_cycle(new_deq)) + state->new_cycle_state ^= 0x1; + + cdnsp_next_trb(pdev, ep_ring, &new_seg, &new_deq); + + /* Search wrapped around, bail out. */ + if (new_deq == pep->ring->dequeue) { + dev_err(pdev->dev, + "Error: Failed finding new dequeue state\n"); + state->new_deq_seg = NULL; + state->new_deq_ptr = NULL; + return; + } + + } while (!cycle_found || !td_last_trb_found); + + state->new_deq_seg = new_seg; + state->new_deq_ptr = new_deq; + + trace_cdnsp_new_deq_state(state); +} + +/* + * 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 cdnsp_td_to_noop(struct cdnsp_device *pdev, + struct cdnsp_ring *ep_ring, + struct cdnsp_td *td, + bool flip_cycle) +{ + struct cdnsp_segment *seg = td->start_seg; + union cdnsp_trb *trb = td->first_trb; + + while (1) { + cdnsp_trb_to_noop(trb, TRB_TR_NOOP); + + /* flip cycle if asked to */ + if (flip_cycle && trb != td->first_trb && trb != td->last_trb) + trb->generic.field[3] ^= cpu_to_le32(TRB_CYCLE); + + if (trb == td->last_trb) + break; + + cdnsp_next_trb(pdev, ep_ring, &seg, &trb); + } +} + +/* + * 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. + */ +static struct cdnsp_segment *cdnsp_trb_in_td(struct cdnsp_device *pdev, + struct cdnsp_segment *start_seg, + union cdnsp_trb *start_trb, + union cdnsp_trb *end_trb, + dma_addr_t suspect_dma) +{ + struct cdnsp_segment *cur_seg; + union cdnsp_trb *temp_trb; + dma_addr_t end_seg_dma; + dma_addr_t end_trb_dma; + dma_addr_t start_dma; + + start_dma = cdnsp_trb_virt_to_dma(start_seg, start_trb); + cur_seg = start_seg; + + do { + if (start_dma == 0) + return NULL; + + temp_trb = &cur_seg->trbs[TRBS_PER_SEGMENT - 1]; + /* We may get an event for a Link TRB in the middle of a TD */ + end_seg_dma = cdnsp_trb_virt_to_dma(cur_seg, temp_trb); + /* If the end TRB isn't in this segment, this is set to 0 */ + end_trb_dma = cdnsp_trb_virt_to_dma(cur_seg, end_trb); + + trace_cdnsp_looking_trb_in_td(suspect_dma, start_dma, + end_trb_dma, cur_seg->dma, + end_seg_dma); + + 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; + } + + /* 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 = cdnsp_trb_virt_to_dma(cur_seg, &cur_seg->trbs[0]); + } while (cur_seg != start_seg); + + return NULL; +} + +static void cdnsp_unmap_td_bounce_buffer(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + struct cdnsp_td *td) +{ + struct cdnsp_segment *seg = td->bounce_seg; + struct cdnsp_request *preq; + size_t len; + + if (!seg) + return; + + preq = td->preq; + + trace_cdnsp_bounce_unmap(td->preq, seg->bounce_len, seg->bounce_offs, + seg->bounce_dma, 0); + + if (!preq->direction) { + dma_unmap_single(pdev->dev, seg->bounce_dma, + ring->bounce_buf_len, DMA_TO_DEVICE); + return; + } + + dma_unmap_single(pdev->dev, seg->bounce_dma, ring->bounce_buf_len, + DMA_FROM_DEVICE); + + /* For in transfers we need to copy the data from bounce to sg */ + len = sg_pcopy_from_buffer(preq->request.sg, preq->request.num_sgs, + seg->bounce_buf, seg->bounce_len, + seg->bounce_offs); + if (len != seg->bounce_len) + dev_warn(pdev->dev, "WARN Wrong bounce buffer read length: %zu != %d\n", + len, seg->bounce_len); + + seg->bounce_len = 0; + seg->bounce_offs = 0; +} + +static int cdnsp_cmd_set_deq(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + struct cdnsp_dequeue_state *deq_state) +{ + struct cdnsp_ring *ep_ring; + int ret; + + if (!deq_state->new_deq_ptr || !deq_state->new_deq_seg) { + cdnsp_ring_doorbell_for_active_rings(pdev, pep); + return 0; + } + + cdnsp_queue_new_dequeue_state(pdev, pep, deq_state); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + + trace_cdnsp_handle_cmd_set_deq(cdnsp_get_slot_ctx(&pdev->out_ctx)); + trace_cdnsp_handle_cmd_set_deq_ep(pep->out_ctx); + + /* + * Update the ring's dequeue segment and dequeue pointer + * to reflect the new position. + */ + ep_ring = cdnsp_get_transfer_ring(pdev, pep, deq_state->stream_id); + + if (cdnsp_trb_is_link(ep_ring->dequeue)) { + ep_ring->deq_seg = ep_ring->deq_seg->next; + ep_ring->dequeue = ep_ring->deq_seg->trbs; + } + + while (ep_ring->dequeue != deq_state->new_deq_ptr) { + ep_ring->num_trbs_free++; + ep_ring->dequeue++; + + if (cdnsp_trb_is_link(ep_ring->dequeue)) { + if (ep_ring->dequeue == deq_state->new_deq_ptr) + break; + + ep_ring->deq_seg = ep_ring->deq_seg->next; + ep_ring->dequeue = ep_ring->deq_seg->trbs; + } + } + + /* + * Probably there was TIMEOUT during handling Set Dequeue Pointer + * command. It's critical error and controller will be stopped. + */ + if (ret) + return -ESHUTDOWN; + + /* Restart any rings with pending requests */ + cdnsp_ring_doorbell_for_active_rings(pdev, pep); + + return 0; +} + +int cdnsp_remove_request(struct cdnsp_device *pdev, + struct cdnsp_request *preq, + struct cdnsp_ep *pep) +{ + struct cdnsp_dequeue_state deq_state; + struct cdnsp_td *cur_td = NULL; + struct cdnsp_ring *ep_ring; + struct cdnsp_segment *seg; + int status = -ECONNRESET; + int ret = 0; + u64 hw_deq; + + memset(&deq_state, 0, sizeof(deq_state)); + + trace_cdnsp_remove_request(pep->out_ctx); + trace_cdnsp_remove_request_td(preq); + + cur_td = &preq->td; + ep_ring = cdnsp_request_to_transfer_ring(pdev, preq); + + /* + * If we stopped on the TD we need to cancel, then we have to + * move the controller endpoint ring dequeue pointer past + * this TD. + */ + hw_deq = cdnsp_get_hw_deq(pdev, pep->idx, preq->request.stream_id); + hw_deq &= ~0xf; + + seg = cdnsp_trb_in_td(pdev, cur_td->start_seg, cur_td->first_trb, + cur_td->last_trb, hw_deq); + + if (seg && (pep->ep_state & EP_ENABLED) && + !(pep->ep_state & EP_DIS_IN_RROGRESS)) + cdnsp_find_new_dequeue_state(pdev, pep, preq->request.stream_id, + cur_td, &deq_state); + else + cdnsp_td_to_noop(pdev, ep_ring, cur_td, false); + + /* + * The event handler won't see a completion for this TD anymore, + * so remove it from the endpoint ring's TD list. + */ + list_del_init(&cur_td->td_list); + ep_ring->num_tds--; + pep->stream_info.td_count--; + + /* + * During disconnecting all endpoint will be disabled so we don't + * have to worry about updating dequeue pointer. + */ + if (pdev->cdnsp_state & CDNSP_STATE_DISCONNECT_PENDING || + pep->ep_state & EP_DIS_IN_RROGRESS) { + status = -ESHUTDOWN; + ret = cdnsp_cmd_set_deq(pdev, pep, &deq_state); + } + + cdnsp_unmap_td_bounce_buffer(pdev, ep_ring, cur_td); + cdnsp_gadget_giveback(pep, cur_td->preq, status); + + return ret; +} + +static int cdnsp_update_port_id(struct cdnsp_device *pdev, u32 port_id) +{ + struct cdnsp_port *port = pdev->active_port; + u8 old_port = 0; + + if (port && port->port_num == port_id) + return 0; + + if (port) + old_port = port->port_num; + + if (port_id == pdev->usb2_port.port_num) { + port = &pdev->usb2_port; + } else if (port_id == pdev->eusb_port.port_num) { + port = &pdev->eusb_port; + } else if (port_id == pdev->usb3_port.port_num) { + port = &pdev->usb3_port; + } else { + dev_err(pdev->dev, "Port event with invalid port ID %d\n", + port_id); + return -EINVAL; + } + + if (port_id != old_port) { + if (pdev->slot_id) + cdnsp_disable_slot(pdev); + + pdev->active_port = port; + cdnsp_enable_slot(pdev); + } + + if ((pdev->usb2_port.exist && port_id == pdev->usb2_port.port_num) || + (pdev->eusb_port.exist && port_id == pdev->eusb_port.port_num)) + cdnsp_set_usb2_hardware_lpm(pdev, NULL, 1); + else + writel(PORT_U1_TIMEOUT(1) | PORT_U2_TIMEOUT(1), + &pdev->usb3_port.regs->portpmsc); + + return 0; +} + +static void cdnsp_handle_port_status(struct cdnsp_device *pdev, + union cdnsp_trb *event) +{ + struct cdnsp_port_regs __iomem *port_regs; + u32 portsc, cmd_regs; + bool port2 = false; + u32 link_state; + u32 port_id; + + /* Port status change events always have a successful completion code */ + if (GET_COMP_CODE(le32_to_cpu(event->generic.field[2])) != COMP_SUCCESS) + dev_err(pdev->dev, "ERR: incorrect PSC event\n"); + + port_id = GET_PORT_ID(le32_to_cpu(event->generic.field[0])); + + if (cdnsp_update_port_id(pdev, port_id)) + goto cleanup; + + port_regs = pdev->active_port->regs; + + if (port_id == pdev->usb2_port.port_num || port_id == pdev->eusb_port.port_num) + port2 = true; + +new_event: + portsc = readl(&port_regs->portsc); + writel(cdnsp_port_state_to_neutral(portsc) | + (portsc & PORT_CHANGE_BITS), &port_regs->portsc); + + trace_cdnsp_handle_port_status(pdev->active_port->port_num, portsc); + + pdev->gadget.speed = cdnsp_port_speed(portsc); + link_state = portsc & PORT_PLS_MASK; + + /* Port Link State change detected. */ + if ((portsc & PORT_PLC)) { + if (!(pdev->cdnsp_state & CDNSP_WAKEUP_PENDING) && + link_state == XDEV_RESUME) { + cmd_regs = readl(&pdev->op_regs->command); + if (!(cmd_regs & CMD_R_S)) + goto cleanup; + + if (DEV_SUPERSPEED_ANY(portsc)) { + cdnsp_set_link_state(pdev, &port_regs->portsc, + XDEV_U0); + + cdnsp_resume_gadget(pdev); + } + } + + if ((pdev->cdnsp_state & CDNSP_WAKEUP_PENDING) && + link_state == XDEV_U0) { + pdev->cdnsp_state &= ~CDNSP_WAKEUP_PENDING; + + cdnsp_force_header_wakeup(pdev, 1); + cdnsp_ring_cmd_db(pdev); + cdnsp_wait_for_cmd_compl(pdev); + } + + if (link_state == XDEV_U0 && pdev->link_state == XDEV_U3 && + !DEV_SUPERSPEED_ANY(portsc)) + cdnsp_resume_gadget(pdev); + + if (link_state == XDEV_U3 && pdev->link_state != XDEV_U3) + cdnsp_suspend_gadget(pdev); + + pdev->link_state = link_state; + } + + if (portsc & PORT_CSC) { + /* Detach device. */ + if (pdev->gadget.connected && !(portsc & PORT_CONNECT)) + cdnsp_disconnect_gadget(pdev); + + /* Attach device. */ + if (portsc & PORT_CONNECT) { + if (!port2) + cdnsp_irq_reset(pdev); + + usb_gadget_set_state(&pdev->gadget, USB_STATE_ATTACHED); + } + } + + /* Port reset. */ + if ((portsc & (PORT_RC | PORT_WRC)) && (portsc & PORT_CONNECT)) { + cdnsp_irq_reset(pdev); + pdev->u1_allowed = 0; + pdev->u2_allowed = 0; + pdev->may_wakeup = 0; + } + + if (portsc & PORT_CEC) + dev_err(pdev->dev, "Port Over Current detected\n"); + + if (portsc & PORT_CEC) + dev_err(pdev->dev, "Port Configure Error detected\n"); + + if (readl(&port_regs->portsc) & PORT_CHANGE_BITS) + goto new_event; + +cleanup: + cdnsp_inc_deq(pdev, pdev->event_ring); +} + +static void cdnsp_td_cleanup(struct cdnsp_device *pdev, + struct cdnsp_td *td, + struct cdnsp_ring *ep_ring, + int *status) +{ + struct cdnsp_request *preq = td->preq; + + /* if a bounce buffer was used to align this td then unmap it */ + cdnsp_unmap_td_bounce_buffer(pdev, ep_ring, td); + + /* + * If the controller said we transferred more data than the buffer + * length, Play it safe and say we didn't transfer anything. + */ + if (preq->request.actual > preq->request.length) { + preq->request.actual = 0; + *status = 0; + } + + list_del_init(&td->td_list); + ep_ring->num_tds--; + preq->pep->stream_info.td_count--; + + cdnsp_gadget_giveback(preq->pep, preq, *status); +} + +static void cdnsp_finish_td(struct cdnsp_device *pdev, + struct cdnsp_td *td, + struct cdnsp_transfer_event *event, + struct cdnsp_ep *ep, + int *status) +{ + struct cdnsp_ring *ep_ring; + u32 trb_comp_code; + + ep_ring = cdnsp_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + + if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID || + trb_comp_code == COMP_STOPPED || + trb_comp_code == COMP_STOPPED_SHORT_PACKET) { + /* + * 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. + */ + return; + } + + /* Update ring dequeue pointer */ + while (ep_ring->dequeue != td->last_trb) + cdnsp_inc_deq(pdev, ep_ring); + + cdnsp_inc_deq(pdev, ep_ring); + + cdnsp_td_cleanup(pdev, td, ep_ring, status); +} + +/* sum trb lengths from ring dequeue up to stop_trb, _excluding_ stop_trb */ +static int cdnsp_sum_trb_lengths(struct cdnsp_device *pdev, + struct cdnsp_ring *ring, + union cdnsp_trb *stop_trb) +{ + struct cdnsp_segment *seg = ring->deq_seg; + union cdnsp_trb *trb = ring->dequeue; + u32 sum; + + for (sum = 0; trb != stop_trb; cdnsp_next_trb(pdev, ring, &seg, &trb)) { + if (!cdnsp_trb_is_noop(trb) && !cdnsp_trb_is_link(trb)) + sum += TRB_LEN(le32_to_cpu(trb->generic.field[2])); + } + return sum; +} + +static int cdnsp_giveback_first_trb(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + unsigned int stream_id, + int start_cycle, + struct cdnsp_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); + + if ((pep->ep_state & EP_HAS_STREAMS) && + !pep->stream_info.first_prime_det) { + trace_cdnsp_wait_for_prime(pep, stream_id); + return 0; + } + + return cdnsp_ring_ep_doorbell(pdev, pep, stream_id); +} + +/* + * Process control tds, update USB request status and actual_length. + */ +static void cdnsp_process_ctrl_td(struct cdnsp_device *pdev, + struct cdnsp_td *td, + union cdnsp_trb *event_trb, + struct cdnsp_transfer_event *event, + struct cdnsp_ep *pep, + int *status) +{ + struct cdnsp_ring *ep_ring; + u32 remaining; + u32 trb_type; + + trb_type = TRB_FIELD_TO_TYPE(le32_to_cpu(event_trb->generic.field[3])); + ep_ring = cdnsp_dma_to_transfer_ring(pep, le64_to_cpu(event->buffer)); + remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + + /* + * if on data stage then update the actual_length of the USB + * request and flag it as set, so it won't be overwritten in the event + * for the last TRB. + */ + if (trb_type == TRB_DATA) { + td->request_length_set = true; + td->preq->request.actual = td->preq->request.length - remaining; + } + + /* at status stage */ + if (!td->request_length_set) + td->preq->request.actual = td->preq->request.length; + + if (pdev->ep0_stage == CDNSP_DATA_STAGE && pep->number == 0 && + pdev->three_stage_setup) { + td = list_entry(ep_ring->td_list.next, struct cdnsp_td, + td_list); + pdev->ep0_stage = CDNSP_STATUS_STAGE; + + cdnsp_giveback_first_trb(pdev, pep, 0, ep_ring->cycle_state, + &td->last_trb->generic); + return; + } + + *status = 0; + + cdnsp_finish_td(pdev, td, event, pep, status); +} + +/* + * Process isochronous tds, update usb request status and actual_length. + */ +static void cdnsp_process_isoc_td(struct cdnsp_device *pdev, + struct cdnsp_td *td, + union cdnsp_trb *ep_trb, + struct cdnsp_transfer_event *event, + struct cdnsp_ep *pep, + int status) +{ + struct cdnsp_request *preq = td->preq; + u32 remaining, requested, ep_trb_len; + bool sum_trbs_for_length = false; + struct cdnsp_ring *ep_ring; + u32 trb_comp_code; + u32 td_length; + + ep_ring = cdnsp_dma_to_transfer_ring(pep, le64_to_cpu(event->buffer)); + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + ep_trb_len = TRB_LEN(le32_to_cpu(ep_trb->generic.field[2])); + + requested = preq->request.length; + + /* handle completion code */ + switch (trb_comp_code) { + case COMP_SUCCESS: + preq->request.status = 0; + break; + case COMP_SHORT_PACKET: + preq->request.status = 0; + sum_trbs_for_length = true; + break; + case COMP_ISOCH_BUFFER_OVERRUN: + case COMP_BABBLE_DETECTED_ERROR: + preq->request.status = -EOVERFLOW; + break; + case COMP_STOPPED: + sum_trbs_for_length = true; + break; + case COMP_STOPPED_SHORT_PACKET: + /* field normally containing residue now contains transferred */ + preq->request.status = 0; + requested = remaining; + break; + case COMP_STOPPED_LENGTH_INVALID: + requested = 0; + remaining = 0; + break; + default: + sum_trbs_for_length = true; + preq->request.status = -1; + break; + } + + if (sum_trbs_for_length) { + td_length = cdnsp_sum_trb_lengths(pdev, ep_ring, ep_trb); + td_length += ep_trb_len - remaining; + } else { + td_length = requested; + } + + td->preq->request.actual += td_length; + + cdnsp_finish_td(pdev, td, event, pep, &status); +} + +static void cdnsp_skip_isoc_td(struct cdnsp_device *pdev, + struct cdnsp_td *td, + struct cdnsp_transfer_event *event, + struct cdnsp_ep *pep, + int status) +{ + struct cdnsp_ring *ep_ring; + + ep_ring = cdnsp_dma_to_transfer_ring(pep, le64_to_cpu(event->buffer)); + td->preq->request.status = -EXDEV; + td->preq->request.actual = 0; + + /* Update ring dequeue pointer */ + while (ep_ring->dequeue != td->last_trb) + cdnsp_inc_deq(pdev, ep_ring); + + cdnsp_inc_deq(pdev, ep_ring); + + cdnsp_td_cleanup(pdev, td, ep_ring, &status); +} + +/* + * Process bulk and interrupt tds, update usb request status and actual_length. + */ +static void cdnsp_process_bulk_intr_td(struct cdnsp_device *pdev, + struct cdnsp_td *td, + union cdnsp_trb *ep_trb, + struct cdnsp_transfer_event *event, + struct cdnsp_ep *ep, + int *status) +{ + u32 remaining, requested, ep_trb_len; + struct cdnsp_ring *ep_ring; + u32 trb_comp_code; + + ep_ring = cdnsp_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + ep_trb_len = TRB_LEN(le32_to_cpu(ep_trb->generic.field[2])); + requested = td->preq->request.length; + + switch (trb_comp_code) { + case COMP_SUCCESS: + case COMP_SHORT_PACKET: + *status = 0; + break; + case COMP_STOPPED_SHORT_PACKET: + td->preq->request.actual = remaining; + goto finish_td; + case COMP_STOPPED_LENGTH_INVALID: + /* Stopped on ep trb with invalid length, exclude it. */ + ep_trb_len = 0; + remaining = 0; + break; + } + + if (ep_trb == td->last_trb) + ep_trb_len = requested - remaining; + else + ep_trb_len = cdnsp_sum_trb_lengths(pdev, ep_ring, ep_trb) + + ep_trb_len - remaining; + td->preq->request.actual = ep_trb_len; + +finish_td: + ep->stream_info.drbls_count--; + + cdnsp_finish_td(pdev, td, event, ep, status); +} + +static void cdnsp_handle_tx_nrdy(struct cdnsp_device *pdev, + struct cdnsp_transfer_event *event) +{ + struct cdnsp_generic_trb *generic; + struct cdnsp_ring *ep_ring; + struct cdnsp_ep *pep; + int cur_stream; + int ep_index; + int host_sid; + int dev_sid; + + generic = (struct cdnsp_generic_trb *)event; + ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; + dev_sid = TRB_TO_DEV_STREAM(le32_to_cpu(generic->field[0])); + host_sid = TRB_TO_HOST_STREAM(le32_to_cpu(generic->field[2])); + + pep = &pdev->eps[ep_index]; + + if (!(pep->ep_state & EP_HAS_STREAMS)) + return; + + if (host_sid == STREAM_PRIME_ACK) { + pep->stream_info.first_prime_det = 1; + for (cur_stream = 1; cur_stream < pep->stream_info.num_streams; + cur_stream++) { + ep_ring = pep->stream_info.stream_rings[cur_stream]; + ep_ring->stream_active = 1; + ep_ring->stream_rejected = 0; + } + } + + if (host_sid == STREAM_REJECTED) { + struct cdnsp_td *td, *td_temp; + + pep->stream_info.drbls_count--; + ep_ring = pep->stream_info.stream_rings[dev_sid]; + ep_ring->stream_active = 0; + ep_ring->stream_rejected = 1; + + list_for_each_entry_safe(td, td_temp, &ep_ring->td_list, + td_list) { + td->drbl = 0; + } + } + + cdnsp_ring_doorbell_for_active_rings(pdev, pep); +} + +/* + * If this function returns an error condition, it means it got a Transfer + * event with a corrupted TRB DMA address or endpoint is disabled. + */ +static int cdnsp_handle_tx_event(struct cdnsp_device *pdev, + struct cdnsp_transfer_event *event) +{ + const struct usb_endpoint_descriptor *desc; + bool handling_skipped_tds = false; + struct cdnsp_segment *ep_seg; + struct cdnsp_ring *ep_ring; + int status = -EINPROGRESS; + union cdnsp_trb *ep_trb; + dma_addr_t ep_trb_dma; + struct cdnsp_ep *pep; + struct cdnsp_td *td; + u32 trb_comp_code; + int invalidate; + int ep_index; + + invalidate = le32_to_cpu(event->flags) & TRB_EVENT_INVALIDATE; + ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + ep_trb_dma = le64_to_cpu(event->buffer); + + pep = &pdev->eps[ep_index]; + ep_ring = cdnsp_dma_to_transfer_ring(pep, le64_to_cpu(event->buffer)); + + /* + * If device is disconnect then all requests will be dequeued + * by upper layers as part of disconnect sequence. + * We don't want handle such event to avoid racing. + */ + if (invalidate || !pdev->gadget.connected) + goto cleanup; + + if (GET_EP_CTX_STATE(pep->out_ctx) == EP_STATE_DISABLED) { + trace_cdnsp_ep_disabled(pep->out_ctx); + goto err_out; + } + + /* Some transfer events don't always point to a trb*/ + if (!ep_ring) { + switch (trb_comp_code) { + case COMP_INVALID_STREAM_TYPE_ERROR: + case COMP_INVALID_STREAM_ID_ERROR: + case COMP_RING_UNDERRUN: + case COMP_RING_OVERRUN: + goto cleanup; + default: + dev_err(pdev->dev, "ERROR: %s event for unknown ring\n", + pep->name); + goto err_out; + } + } + + /* Look for some error cases that need special treatment. */ + switch (trb_comp_code) { + case COMP_BABBLE_DETECTED_ERROR: + status = -EOVERFLOW; + break; + case COMP_RING_UNDERRUN: + case COMP_RING_OVERRUN: + /* + * When the Isoch ring is empty, the controller will generate + * a Ring Overrun Event for IN Isoch endpoint or Ring + * Underrun Event for OUT Isoch endpoint. + */ + goto cleanup; + case COMP_MISSED_SERVICE_ERROR: + /* + * When encounter missed service error, one or more isoc tds + * may be missed by controller. + * Set skip flag of the ep_ring; Complete the missed tds as + * short transfer when process the ep_ring next time. + */ + pep->skip = true; + break; + } + + do { + /* + * This TRB should be in the TD at the head of this ring's TD + * list. + */ + if (list_empty(&ep_ring->td_list)) { + /* + * Don't print warnings if it's due to a stopped + * endpoint generating an extra completion event, or + * a event for the last TRB of a short TD we already + * got a short event for. + * The short TD is already removed from the TD list. + */ + if (!(trb_comp_code == COMP_STOPPED || + trb_comp_code == COMP_STOPPED_LENGTH_INVALID || + ep_ring->last_td_was_short)) + trace_cdnsp_trb_without_td(ep_ring, + (struct cdnsp_generic_trb *)event); + + if (pep->skip) { + pep->skip = false; + trace_cdnsp_ep_list_empty_with_skip(pep, 0); + } + + goto cleanup; + } + + td = list_entry(ep_ring->td_list.next, struct cdnsp_td, + td_list); + + /* Is this a TRB in the currently executing TD? */ + ep_seg = cdnsp_trb_in_td(pdev, ep_ring->deq_seg, + ep_ring->dequeue, td->last_trb, + ep_trb_dma); + + desc = td->preq->pep->endpoint.desc; + + if (ep_seg) { + ep_trb = &ep_seg->trbs[(ep_trb_dma - ep_seg->dma) + / sizeof(*ep_trb)]; + + trace_cdnsp_handle_transfer(ep_ring, + (struct cdnsp_generic_trb *)ep_trb); + + if (pep->skip && usb_endpoint_xfer_isoc(desc) && + td->last_trb != ep_trb) + return -EAGAIN; + } + + /* + * Skip the Force Stopped Event. The event_trb(ep_trb_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 (!ep_seg && (trb_comp_code == COMP_STOPPED || + trb_comp_code == COMP_STOPPED_LENGTH_INVALID)) { + pep->skip = false; + goto cleanup; + } + + if (!ep_seg) { + if (!pep->skip || !usb_endpoint_xfer_isoc(desc)) { + /* Something is busted, give up! */ + dev_err(pdev->dev, + "ERROR Transfer event TRB DMA ptr not " + "part of current TD ep_index %d " + "comp_code %u\n", ep_index, + trb_comp_code); + return -EINVAL; + } + + cdnsp_skip_isoc_td(pdev, td, event, pep, status); + goto cleanup; + } + + if (trb_comp_code == COMP_SHORT_PACKET) + ep_ring->last_td_was_short = true; + else + ep_ring->last_td_was_short = false; + + if (pep->skip) { + pep->skip = false; + cdnsp_skip_isoc_td(pdev, td, event, pep, status); + goto cleanup; + } + + if (cdnsp_trb_is_noop(ep_trb)) + goto cleanup; + + if (usb_endpoint_xfer_control(desc)) + cdnsp_process_ctrl_td(pdev, td, ep_trb, event, pep, + &status); + else if (usb_endpoint_xfer_isoc(desc)) + cdnsp_process_isoc_td(pdev, td, ep_trb, event, pep, + status); + else + cdnsp_process_bulk_intr_td(pdev, td, ep_trb, event, pep, + &status); +cleanup: + handling_skipped_tds = pep->skip; + + /* + * Do not update event ring dequeue pointer if we're in a loop + * processing missed tds. + */ + if (!handling_skipped_tds) + cdnsp_inc_deq(pdev, pdev->event_ring); + + /* + * 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 (handling_skipped_tds); + return 0; + +err_out: + dev_err(pdev->dev, "@%016llx %08x %08x %08x %08x\n", + (unsigned long long) + cdnsp_trb_virt_to_dma(pdev->event_ring->deq_seg, + pdev->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)); + return -EINVAL; +} + +/* + * This function handles all events on the event ring. + * Returns true for "possibly more events to process" (caller should call + * again), otherwise false if done. + */ +static bool cdnsp_handle_event(struct cdnsp_device *pdev) +{ + unsigned int comp_code; + union cdnsp_trb *event; + bool update_ptrs = true; + u32 cycle_bit; + int ret = 0; + u32 flags; + + event = pdev->event_ring->dequeue; + flags = le32_to_cpu(event->event_cmd.flags); + cycle_bit = (flags & TRB_CYCLE); + + /* Does the controller or driver own the TRB? */ + if (cycle_bit != pdev->event_ring->cycle_state) + return false; + + trace_cdnsp_handle_event(pdev->event_ring, &event->generic); + + /* + * Barrier between reading the TRB_CYCLE (valid) flag above and any + * reads of the event's flags/data below. + */ + rmb(); + + switch (flags & TRB_TYPE_BITMASK) { + case TRB_TYPE(TRB_COMPLETION): + /* + * Command can't be handled in interrupt context so just + * increment command ring dequeue pointer. + */ + cdnsp_inc_deq(pdev, pdev->cmd_ring); + break; + case TRB_TYPE(TRB_PORT_STATUS): + cdnsp_handle_port_status(pdev, event); + update_ptrs = false; + break; + case TRB_TYPE(TRB_TRANSFER): + ret = cdnsp_handle_tx_event(pdev, &event->trans_event); + if (ret >= 0) + update_ptrs = false; + break; + case TRB_TYPE(TRB_SETUP): + pdev->ep0_stage = CDNSP_SETUP_STAGE; + pdev->setup_id = TRB_SETUPID_TO_TYPE(flags); + pdev->setup_speed = TRB_SETUP_SPEEDID(flags); + pdev->setup = *((struct usb_ctrlrequest *) + &event->trans_event.buffer); + + cdnsp_setup_analyze(pdev); + break; + case TRB_TYPE(TRB_ENDPOINT_NRDY): + cdnsp_handle_tx_nrdy(pdev, &event->trans_event); + break; + case TRB_TYPE(TRB_HC_EVENT): { + comp_code = GET_COMP_CODE(le32_to_cpu(event->generic.field[2])); + + switch (comp_code) { + case COMP_EVENT_RING_FULL_ERROR: + dev_err(pdev->dev, "Event Ring Full\n"); + break; + default: + dev_err(pdev->dev, "Controller error code 0x%02x\n", + comp_code); + } + + break; + } + case TRB_TYPE(TRB_MFINDEX_WRAP): + case TRB_TYPE(TRB_DRB_OVERFLOW): + break; + default: + dev_warn(pdev->dev, "ERROR unknown event type %ld\n", + TRB_FIELD_TO_TYPE(flags)); + } + + if (update_ptrs) + /* Update SW event ring dequeue pointer. */ + cdnsp_inc_deq(pdev, pdev->event_ring); + + /* + * Caller will call us again to check if there are more items + * on the event ring. + */ + return true; +} + +irqreturn_t cdnsp_thread_irq_handler(int irq, void *data) +{ + struct cdnsp_device *pdev = (struct cdnsp_device *)data; + union cdnsp_trb *event_ring_deq; + unsigned long flags; + int counter = 0; + + local_bh_disable(); + spin_lock_irqsave(&pdev->lock, flags); + + if (pdev->cdnsp_state & (CDNSP_STATE_HALTED | CDNSP_STATE_DYING)) { + /* + * While removing or stopping driver there may still be deferred + * not handled interrupt which should not be treated as error. + * Driver should simply ignore it. + */ + if (pdev->gadget_driver) + cdnsp_died(pdev); + + spin_unlock_irqrestore(&pdev->lock, flags); + local_bh_enable(); + return IRQ_HANDLED; + } + + event_ring_deq = pdev->event_ring->dequeue; + + while (cdnsp_handle_event(pdev)) { + if (++counter >= TRBS_PER_EV_DEQ_UPDATE) { + cdnsp_update_erst_dequeue(pdev, event_ring_deq, 0); + event_ring_deq = pdev->event_ring->dequeue; + counter = 0; + } + } + + cdnsp_update_erst_dequeue(pdev, event_ring_deq, 1); + + spin_unlock_irqrestore(&pdev->lock, flags); + local_bh_enable(); + + return IRQ_HANDLED; +} + +irqreturn_t cdnsp_irq_handler(int irq, void *priv) +{ + struct cdnsp_device *pdev = (struct cdnsp_device *)priv; + u32 irq_pending; + u32 status; + + status = readl(&pdev->op_regs->status); + + if (status == ~(u32)0) { + cdnsp_died(pdev); + return IRQ_HANDLED; + } + + if (!(status & STS_EINT)) + return IRQ_NONE; + + writel(status | STS_EINT, &pdev->op_regs->status); + irq_pending = readl(&pdev->ir_set->irq_pending); + irq_pending |= IMAN_IP; + writel(irq_pending, &pdev->ir_set->irq_pending); + + if (status & STS_FATAL) { + cdnsp_died(pdev); + return IRQ_HANDLED; + } + + return IRQ_WAKE_THREAD; +} + +/* + * Generic function for queuing 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 setting doorbell? + */ +static void cdnsp_queue_trb(struct cdnsp_device *pdev, struct cdnsp_ring *ring, + bool more_trbs_coming, u32 field1, u32 field2, + u32 field3, u32 field4) +{ + struct cdnsp_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); + + trace_cdnsp_queue_trb(ring, trb); + cdnsp_inc_enq(pdev, ring, more_trbs_coming); +} + +/* + * Does various checks on the endpoint ring, and makes it ready to + * queue num_trbs. + */ +static int cdnsp_prepare_ring(struct cdnsp_device *pdev, + struct cdnsp_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 controller schedule. */ + switch (ep_state) { + case EP_STATE_STOPPED: + case EP_STATE_RUNNING: + case EP_STATE_HALTED: + break; + default: + dev_err(pdev->dev, "ERROR: incorrect endpoint state\n"); + return -EINVAL; + } + + while (1) { + if (cdnsp_room_on_ring(pdev, ep_ring, num_trbs)) + break; + + trace_cdnsp_no_room_on_ring("try ring expansion"); + + num_trbs_needed = num_trbs - ep_ring->num_trbs_free; + if (cdnsp_ring_expansion(pdev, ep_ring, num_trbs_needed, + mem_flags)) { + dev_err(pdev->dev, "Ring expansion failed\n"); + return -ENOMEM; + } + } + + while (cdnsp_trb_is_link(ep_ring->enqueue)) { + ep_ring->enqueue->link.control |= cpu_to_le32(TRB_CHAIN); + /* The cycle bit must be set as the last operation. */ + wmb(); + ep_ring->enqueue->link.control ^= cpu_to_le32(TRB_CYCLE); + + /* Toggle the cycle bit after the last ring segment. */ + if (cdnsp_link_trb_toggles_cycle(ep_ring->enqueue)) + ep_ring->cycle_state ^= 1; + ep_ring->enq_seg = ep_ring->enq_seg->next; + ep_ring->enqueue = ep_ring->enq_seg->trbs; + } + return 0; +} + +static int cdnsp_prepare_transfer(struct cdnsp_device *pdev, + struct cdnsp_request *preq, + unsigned int num_trbs) +{ + struct cdnsp_ring *ep_ring; + int ret; + + ep_ring = cdnsp_get_transfer_ring(pdev, preq->pep, + preq->request.stream_id); + if (!ep_ring) + return -EINVAL; + + ret = cdnsp_prepare_ring(pdev, ep_ring, + GET_EP_CTX_STATE(preq->pep->out_ctx), + num_trbs, GFP_ATOMIC); + if (ret) + return ret; + + INIT_LIST_HEAD(&preq->td.td_list); + preq->td.preq = preq; + + /* Add this TD to the tail of the endpoint ring's TD list. */ + list_add_tail(&preq->td.td_list, &ep_ring->td_list); + ep_ring->num_tds++; + preq->pep->stream_info.td_count++; + + preq->td.start_seg = ep_ring->enq_seg; + preq->td.first_trb = ep_ring->enqueue; + + return 0; +} + +static unsigned int cdnsp_count_trbs(u64 addr, u64 len) +{ + unsigned int num_trbs; + + num_trbs = DIV_ROUND_UP(len + (addr & (TRB_MAX_BUFF_SIZE - 1)), + TRB_MAX_BUFF_SIZE); + if (num_trbs == 0) + num_trbs++; + + return num_trbs; +} + +static unsigned int count_trbs_needed(struct cdnsp_request *preq) +{ + return cdnsp_count_trbs(preq->request.dma, preq->request.length); +} + +static unsigned int count_sg_trbs_needed(struct cdnsp_request *preq) +{ + unsigned int i, len, full_len, num_trbs = 0; + struct scatterlist *sg; + + full_len = preq->request.length; + + for_each_sg(preq->request.sg, sg, preq->request.num_sgs, i) { + len = sg_dma_len(sg); + num_trbs += cdnsp_count_trbs(sg_dma_address(sg), len); + len = min(len, full_len); + full_len -= len; + if (full_len == 0) + break; + } + + return num_trbs; +} + +static void cdnsp_check_trb_math(struct cdnsp_request *preq, int running_total) +{ + if (running_total != preq->request.length) + dev_err(preq->pep->pdev->dev, + "%s - Miscalculated tx length, " + "queued %#x, asked for %#x (%d)\n", + preq->pep->name, running_total, + preq->request.length, preq->request.actual); +} + +/* + * 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. + * This is taken care of in the TRB_TD_SIZE() macro + * + * The last TRB in a TD must have the TD size set to zero. + */ +static u32 cdnsp_td_remainder(struct cdnsp_device *pdev, + int transferred, + int trb_buff_len, + unsigned int td_total_len, + struct cdnsp_request *preq, + bool more_trbs_coming, + bool zlp) +{ + u32 maxp, total_packet_count; + + /* Before ZLP driver needs set TD_SIZE = 1. */ + if (zlp) + return 1; + + /* One TRB with a zero-length data packet. */ + if (!more_trbs_coming || (transferred == 0 && trb_buff_len == 0) || + trb_buff_len == td_total_len) + return 0; + + maxp = usb_endpoint_maxp(preq->pep->endpoint.desc); + total_packet_count = DIV_ROUND_UP(td_total_len, maxp); + + /* Queuing functions don't count the current TRB into transferred. */ + return (total_packet_count - ((transferred + trb_buff_len) / maxp)); +} + +static int cdnsp_align_td(struct cdnsp_device *pdev, + struct cdnsp_request *preq, u32 enqd_len, + u32 *trb_buff_len, struct cdnsp_segment *seg) +{ + struct device *dev = pdev->dev; + unsigned int unalign; + unsigned int max_pkt; + u32 new_buff_len; + + max_pkt = usb_endpoint_maxp(preq->pep->endpoint.desc); + unalign = (enqd_len + *trb_buff_len) % max_pkt; + + /* We got lucky, last normal TRB data on segment is packet aligned. */ + if (unalign == 0) + return 0; + + /* Is the last nornal TRB alignable by splitting it. */ + if (*trb_buff_len > unalign) { + *trb_buff_len -= unalign; + trace_cdnsp_bounce_align_td_split(preq, *trb_buff_len, + enqd_len, 0, unalign); + return 0; + } + + /* + * We want enqd_len + trb_buff_len to sum up to a number aligned to + * number which is divisible by the endpoint's wMaxPacketSize. IOW: + * (size of currently enqueued TRBs + remainder) % wMaxPacketSize == 0. + */ + new_buff_len = max_pkt - (enqd_len % max_pkt); + + if (new_buff_len > (preq->request.length - enqd_len)) + new_buff_len = (preq->request.length - enqd_len); + + /* Create a max max_pkt sized bounce buffer pointed to by last trb. */ + if (preq->direction) { + sg_pcopy_to_buffer(preq->request.sg, + preq->request.num_mapped_sgs, + seg->bounce_buf, new_buff_len, enqd_len); + seg->bounce_dma = dma_map_single(dev, seg->bounce_buf, + max_pkt, DMA_TO_DEVICE); + } else { + seg->bounce_dma = dma_map_single(dev, seg->bounce_buf, + max_pkt, DMA_FROM_DEVICE); + } + + if (dma_mapping_error(dev, seg->bounce_dma)) { + /* Try without aligning.*/ + dev_warn(pdev->dev, + "Failed mapping bounce buffer, not aligning\n"); + return 0; + } + + *trb_buff_len = new_buff_len; + seg->bounce_len = new_buff_len; + seg->bounce_offs = enqd_len; + + trace_cdnsp_bounce_map(preq, new_buff_len, enqd_len, seg->bounce_dma, + unalign); + + /* + * Bounce buffer successful aligned and seg->bounce_dma will be used + * in transfer TRB as new transfer buffer address. + */ + return 1; +} + +int cdnsp_queue_bulk_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq) +{ + unsigned int enqd_len, block_len, trb_buff_len, full_len; + unsigned int start_cycle, num_sgs = 0; + struct cdnsp_generic_trb *start_trb; + u32 field, length_field, remainder; + struct scatterlist *sg = NULL; + bool more_trbs_coming = true; + bool need_zero_pkt = false; + bool zero_len_trb = false; + struct cdnsp_ring *ring; + bool first_trb = true; + unsigned int num_trbs; + struct cdnsp_ep *pep; + u64 addr, send_addr; + int sent_len, ret; + + ring = cdnsp_request_to_transfer_ring(pdev, preq); + if (!ring) + return -EINVAL; + + full_len = preq->request.length; + + if (preq->request.num_sgs) { + num_sgs = preq->request.num_sgs; + sg = preq->request.sg; + addr = (u64)sg_dma_address(sg); + block_len = sg_dma_len(sg); + num_trbs = count_sg_trbs_needed(preq); + } else { + num_trbs = count_trbs_needed(preq); + addr = (u64)preq->request.dma; + block_len = full_len; + } + + pep = preq->pep; + + /* Deal with request.zero - need one more td/trb. */ + if (preq->request.zero && preq->request.length && + IS_ALIGNED(full_len, usb_endpoint_maxp(pep->endpoint.desc))) { + need_zero_pkt = true; + num_trbs++; + } + + ret = cdnsp_prepare_transfer(pdev, preq, num_trbs); + if (ret) + return ret; + + /* + * workaround 1: STOP EP command on LINK TRB with TC bit set to 1 + * causes that internal cycle bit can have incorrect state after + * command complete. In consequence empty transfer ring can be + * incorrectly detected when EP is resumed. + * NOP TRB before LINK TRB avoid such scenario. STOP EP command is + * then on NOP TRB and internal cycle bit is not changed and have + * correct value. + */ + if (pep->wa1_nop_trb) { + field = le32_to_cpu(pep->wa1_nop_trb->trans_event.flags); + field ^= TRB_CYCLE; + + pep->wa1_nop_trb->trans_event.flags = cpu_to_le32(field); + pep->wa1_nop_trb = NULL; + } + + /* + * 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 = &ring->enqueue->generic; + start_cycle = ring->cycle_state; + send_addr = addr; + + /* Queue the TRBs, even if they are zero-length */ + for (enqd_len = 0; zero_len_trb || first_trb || enqd_len < full_len; + enqd_len += trb_buff_len) { + field = TRB_TYPE(TRB_NORMAL); + + /* TRB buffer should not cross 64KB boundaries */ + trb_buff_len = TRB_BUFF_LEN_UP_TO_BOUNDARY(addr); + trb_buff_len = min(trb_buff_len, block_len); + if (enqd_len + trb_buff_len > full_len) + trb_buff_len = full_len - enqd_len; + + /* Don't change the cycle bit of the first TRB until later */ + if (first_trb) { + first_trb = false; + if (start_cycle == 0) + field |= TRB_CYCLE; + } else { + field |= 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 (enqd_len + trb_buff_len < full_len || need_zero_pkt) { + field |= TRB_CHAIN; + if (cdnsp_trb_is_link(ring->enqueue + 1)) { + if (cdnsp_align_td(pdev, preq, enqd_len, + &trb_buff_len, + ring->enq_seg)) { + send_addr = ring->enq_seg->bounce_dma; + /* Assuming TD won't span 2 segs */ + preq->td.bounce_seg = ring->enq_seg; + } + } + } + + if (enqd_len + trb_buff_len >= full_len) { + if (need_zero_pkt && !zero_len_trb) { + zero_len_trb = true; + } else { + zero_len_trb = false; + field &= ~TRB_CHAIN; + field |= TRB_IOC; + more_trbs_coming = false; + need_zero_pkt = false; + preq->td.last_trb = ring->enqueue; + } + } + + /* Only set interrupt on short packet for OUT endpoints. */ + if (!preq->direction) + field |= TRB_ISP; + + /* Set the TRB length, TD size, and interrupter fields. */ + remainder = cdnsp_td_remainder(pdev, enqd_len, trb_buff_len, + full_len, preq, + more_trbs_coming, + zero_len_trb); + + length_field = TRB_LEN(trb_buff_len) | TRB_TD_SIZE(remainder) | + TRB_INTR_TARGET(0); + + cdnsp_queue_trb(pdev, ring, more_trbs_coming, + lower_32_bits(send_addr), + upper_32_bits(send_addr), + length_field, + field); + + addr += trb_buff_len; + sent_len = trb_buff_len; + while (sg && sent_len >= block_len) { + /* New sg entry */ + --num_sgs; + sent_len -= block_len; + if (num_sgs != 0) { + sg = sg_next(sg); + block_len = sg_dma_len(sg); + addr = (u64)sg_dma_address(sg); + addr += sent_len; + } + } + block_len -= sent_len; + send_addr = addr; + } + + if (cdnsp_trb_is_link(ring->enqueue + 1)) { + field = TRB_TYPE(TRB_TR_NOOP) | TRB_IOC; + if (!ring->cycle_state) + field |= TRB_CYCLE; + + pep->wa1_nop_trb = ring->enqueue; + + cdnsp_queue_trb(pdev, ring, 0, 0x0, 0x0, + TRB_INTR_TARGET(0), field); + } + + cdnsp_check_trb_math(preq, enqd_len); + ret = cdnsp_giveback_first_trb(pdev, pep, preq->request.stream_id, + start_cycle, start_trb); + + if (ret) + preq->td.drbl = 1; + + return 0; +} + +int cdnsp_queue_ctrl_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq) +{ + u32 field, length_field, zlp = 0; + struct cdnsp_ep *pep = preq->pep; + struct cdnsp_ring *ep_ring; + int num_trbs; + u32 maxp; + int ret; + + ep_ring = cdnsp_request_to_transfer_ring(pdev, preq); + if (!ep_ring) + return -EINVAL; + + /* 1 TRB for data, 1 for status */ + num_trbs = (pdev->three_stage_setup) ? 2 : 1; + + maxp = usb_endpoint_maxp(pep->endpoint.desc); + + if (preq->request.zero && preq->request.length && + (preq->request.length % maxp == 0)) { + num_trbs++; + zlp = 1; + } + + ret = cdnsp_prepare_transfer(pdev, preq, num_trbs); + if (ret) + return ret; + + /* If there's data, queue data TRBs */ + if (preq->request.length > 0) { + field = TRB_TYPE(TRB_DATA); + + if (zlp) + field |= TRB_CHAIN; + else + field |= TRB_IOC | (pdev->ep0_expect_in ? 0 : TRB_ISP); + + if (pdev->ep0_expect_in) + field |= TRB_DIR_IN; + + length_field = TRB_LEN(preq->request.length) | + TRB_TD_SIZE(zlp) | TRB_INTR_TARGET(0); + + cdnsp_queue_trb(pdev, ep_ring, true, + lower_32_bits(preq->request.dma), + upper_32_bits(preq->request.dma), length_field, + field | ep_ring->cycle_state | + TRB_SETUPID(pdev->setup_id) | + pdev->setup_speed); + + if (zlp) { + field = TRB_TYPE(TRB_NORMAL) | TRB_IOC; + + if (!pdev->ep0_expect_in) + field = TRB_ISP; + + cdnsp_queue_trb(pdev, ep_ring, true, + lower_32_bits(preq->request.dma), + upper_32_bits(preq->request.dma), 0, + field | ep_ring->cycle_state | + TRB_SETUPID(pdev->setup_id) | + pdev->setup_speed); + } + + pdev->ep0_stage = CDNSP_DATA_STAGE; + } + + /* Save the DMA address of the last TRB in the TD. */ + preq->td.last_trb = ep_ring->enqueue; + + /* Queue status TRB. */ + if (preq->request.length == 0) + field = ep_ring->cycle_state; + else + field = (ep_ring->cycle_state ^ 1); + + if (preq->request.length > 0 && pdev->ep0_expect_in) + field |= TRB_DIR_IN; + + if (pep->ep_state & EP0_HALTED_STATUS) { + pep->ep_state &= ~EP0_HALTED_STATUS; + field |= TRB_SETUPSTAT(TRB_SETUPSTAT_STALL); + } else { + field |= TRB_SETUPSTAT(TRB_SETUPSTAT_ACK); + } + + cdnsp_queue_trb(pdev, ep_ring, false, 0, 0, TRB_INTR_TARGET(0), + field | TRB_IOC | TRB_SETUPID(pdev->setup_id) | + TRB_TYPE(TRB_STATUS) | pdev->setup_speed); + + cdnsp_ring_ep_doorbell(pdev, pep, preq->request.stream_id); + + return 0; +} + +int cdnsp_cmd_stop_ep(struct cdnsp_device *pdev, struct cdnsp_ep *pep) +{ + u32 ep_state = GET_EP_CTX_STATE(pep->out_ctx); + int ret = 0; + + if (ep_state == EP_STATE_STOPPED || ep_state == EP_STATE_DISABLED || + ep_state == EP_STATE_HALTED) { + trace_cdnsp_ep_stopped_or_disabled(pep->out_ctx); + goto ep_stopped; + } + + cdnsp_queue_stop_endpoint(pdev, pep->idx); + cdnsp_ring_cmd_db(pdev); + ret = cdnsp_wait_for_cmd_compl(pdev); + + trace_cdnsp_handle_cmd_stop_ep(pep->out_ctx); + +ep_stopped: + pep->ep_state |= EP_STOPPED; + return ret; +} + +/* + * 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. + */ +static unsigned int cdnsp_get_burst_count(struct cdnsp_device *pdev, + struct cdnsp_request *preq, + unsigned int total_packet_count) +{ + unsigned int max_burst; + + if (pdev->gadget.speed < USB_SPEED_SUPER) + return 0; + + max_burst = preq->pep->endpoint.comp_desc->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 + cdnsp_get_last_burst_packet_count(struct cdnsp_device *pdev, + struct cdnsp_request *preq, + unsigned int total_packet_count) +{ + unsigned int max_burst; + unsigned int residue; + + if (pdev->gadget.speed >= USB_SPEED_SUPER) { + /* bMaxBurst is zero based: 0 means 1 packet per burst. */ + max_burst = preq->pep->endpoint.comp_desc->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; + } + if (total_packet_count == 0) + return 0; + + return total_packet_count - 1; +} + +/* Queue function isoc transfer */ +int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev, + struct cdnsp_request *preq) +{ + unsigned int trb_buff_len, td_len, td_remain_len, block_len; + unsigned int burst_count, last_burst_pkt; + unsigned int total_pkt_count, max_pkt; + struct cdnsp_generic_trb *start_trb; + struct scatterlist *sg = NULL; + bool more_trbs_coming = true; + struct cdnsp_ring *ep_ring; + unsigned int num_sgs = 0; + int running_total = 0; + u32 field, length_field; + u64 addr, send_addr; + int start_cycle; + int trbs_per_td; + int i, sent_len, ret; + + ep_ring = preq->pep->ring; + + td_len = preq->request.length; + + if (preq->request.num_sgs) { + num_sgs = preq->request.num_sgs; + sg = preq->request.sg; + addr = (u64)sg_dma_address(sg); + block_len = sg_dma_len(sg); + trbs_per_td = count_sg_trbs_needed(preq); + } else { + addr = (u64)preq->request.dma; + block_len = td_len; + trbs_per_td = count_trbs_needed(preq); + } + + ret = cdnsp_prepare_transfer(pdev, preq, trbs_per_td); + if (ret) + return ret; + + start_trb = &ep_ring->enqueue->generic; + start_cycle = ep_ring->cycle_state; + td_remain_len = td_len; + send_addr = addr; + + max_pkt = usb_endpoint_maxp(preq->pep->endpoint.desc); + total_pkt_count = DIV_ROUND_UP(td_len, max_pkt); + + /* A zero-length transfer still involves at least one packet. */ + if (total_pkt_count == 0) + total_pkt_count++; + + burst_count = cdnsp_get_burst_count(pdev, preq, total_pkt_count); + last_burst_pkt = cdnsp_get_last_burst_packet_count(pdev, preq, + total_pkt_count); + + /* + * Set isoc specific data for the first TRB in a TD. + * Prevent HW from getting the TRBs by keeping the cycle state + * inverted in the first TDs isoc TRB. + */ + field = TRB_TYPE(TRB_ISOC) | TRB_TLBPC(last_burst_pkt) | + TRB_SIA | TRB_TBC(burst_count); + + if (!start_cycle) + field |= TRB_CYCLE; + + /* Fill the rest of the TRB fields, and remaining normal TRBs. */ + for (i = 0; i < trbs_per_td; i++) { + u32 remainder; + + /* Calculate TRB length. */ + trb_buff_len = TRB_BUFF_LEN_UP_TO_BOUNDARY(addr); + trb_buff_len = min(trb_buff_len, block_len); + if (trb_buff_len > td_remain_len) + trb_buff_len = td_remain_len; + + /* Set the TRB length, TD size, & interrupter fields. */ + remainder = cdnsp_td_remainder(pdev, running_total, + trb_buff_len, td_len, preq, + more_trbs_coming, 0); + + length_field = TRB_LEN(trb_buff_len) | TRB_TD_SIZE(remainder) | + TRB_INTR_TARGET(0); + + /* Only first TRB is isoc, overwrite otherwise. */ + if (i) { + field = TRB_TYPE(TRB_NORMAL) | ep_ring->cycle_state; + length_field |= TRB_TD_SIZE(remainder); + } else { + length_field |= TRB_TD_SIZE_TBC(burst_count); + } + + /* Only set interrupt on short packet for OUT EPs. */ + if (usb_endpoint_dir_out(preq->pep->endpoint.desc)) + field |= TRB_ISP; + + /* Set the chain bit for all except the last TRB. */ + if (i < trbs_per_td - 1) { + more_trbs_coming = true; + field |= TRB_CHAIN; + } else { + more_trbs_coming = false; + preq->td.last_trb = ep_ring->enqueue; + field |= TRB_IOC; + } + + cdnsp_queue_trb(pdev, ep_ring, more_trbs_coming, + lower_32_bits(send_addr), upper_32_bits(send_addr), + length_field, field); + + running_total += trb_buff_len; + addr += trb_buff_len; + td_remain_len -= trb_buff_len; + + sent_len = trb_buff_len; + while (sg && sent_len >= block_len) { + /* New sg entry */ + --num_sgs; + sent_len -= block_len; + if (num_sgs != 0) { + sg = sg_next(sg); + block_len = sg_dma_len(sg); + addr = (u64)sg_dma_address(sg); + addr += sent_len; + } + } + block_len -= sent_len; + send_addr = addr; + } + + /* Check TD length */ + if (running_total != td_len) { + dev_err(pdev->dev, "ISOC TD length unmatch\n"); + ret = -EINVAL; + goto cleanup; + } + + cdnsp_giveback_first_trb(pdev, preq->pep, preq->request.stream_id, + start_cycle, start_trb); + + return 0; + +cleanup: + /* Clean up a partially enqueued isoc transfer. */ + list_del_init(&preq->td.td_list); + ep_ring->num_tds--; + + /* + * 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. + */ + preq->td.last_trb = ep_ring->enqueue; + /* Every TRB except the first & last will have its cycle bit flipped. */ + cdnsp_td_to_noop(pdev, ep_ring, &preq->td, true); + + /* Reset the ring enqueue back to the first TRB and its cycle bit. */ + ep_ring->enqueue = preq->td.first_trb; + ep_ring->enq_seg = preq->td.start_seg; + ep_ring->cycle_state = start_cycle; + return ret; +} + +/**** Command Ring Operations ****/ +/* + * Generic function for queuing a command TRB on the command ring. + * Driver queue only one command to ring in the moment. + */ +static void cdnsp_queue_command(struct cdnsp_device *pdev, + u32 field1, + u32 field2, + u32 field3, + u32 field4) +{ + cdnsp_prepare_ring(pdev, pdev->cmd_ring, EP_STATE_RUNNING, 1, + GFP_ATOMIC); + + pdev->cmd.command_trb = pdev->cmd_ring->enqueue; + + cdnsp_queue_trb(pdev, pdev->cmd_ring, false, field1, field2, + field3, field4 | pdev->cmd_ring->cycle_state); +} + +/* Queue a slot enable or disable request on the command ring */ +void cdnsp_queue_slot_control(struct cdnsp_device *pdev, u32 trb_type) +{ + cdnsp_queue_command(pdev, 0, 0, 0, TRB_TYPE(trb_type) | + SLOT_ID_FOR_TRB(pdev->slot_id)); +} + +/* Queue an address device command TRB */ +void cdnsp_queue_address_device(struct cdnsp_device *pdev, + dma_addr_t in_ctx_ptr, + enum cdnsp_setup_dev setup) +{ + cdnsp_queue_command(pdev, lower_32_bits(in_ctx_ptr), + upper_32_bits(in_ctx_ptr), 0, + TRB_TYPE(TRB_ADDR_DEV) | + SLOT_ID_FOR_TRB(pdev->slot_id) | + (setup == SETUP_CONTEXT_ONLY ? TRB_BSR : 0)); +} + +/* Queue a reset device command TRB */ +void cdnsp_queue_reset_device(struct cdnsp_device *pdev) +{ + cdnsp_queue_command(pdev, 0, 0, 0, TRB_TYPE(TRB_RESET_DEV) | + SLOT_ID_FOR_TRB(pdev->slot_id)); +} + +/* Queue a configure endpoint command TRB */ +void cdnsp_queue_configure_endpoint(struct cdnsp_device *pdev, + dma_addr_t in_ctx_ptr) +{ + cdnsp_queue_command(pdev, lower_32_bits(in_ctx_ptr), + upper_32_bits(in_ctx_ptr), 0, + TRB_TYPE(TRB_CONFIG_EP) | + SLOT_ID_FOR_TRB(pdev->slot_id)); +} + +/* + * Suspend is set to indicate "Stop Endpoint Command" is being issued to stop + * activity on an endpoint that is about to be suspended. + */ +void cdnsp_queue_stop_endpoint(struct cdnsp_device *pdev, unsigned int ep_index) +{ + cdnsp_queue_command(pdev, 0, 0, 0, SLOT_ID_FOR_TRB(pdev->slot_id) | + EP_ID_FOR_TRB(ep_index) | TRB_TYPE(TRB_STOP_RING)); +} + +/* Set Transfer Ring Dequeue Pointer command. */ +void cdnsp_queue_new_dequeue_state(struct cdnsp_device *pdev, + struct cdnsp_ep *pep, + struct cdnsp_dequeue_state *deq_state) +{ + u32 trb_stream_id = STREAM_ID_FOR_TRB(deq_state->stream_id); + u32 trb_slot_id = SLOT_ID_FOR_TRB(pdev->slot_id); + u32 type = TRB_TYPE(TRB_SET_DEQ); + u32 trb_sct = 0; + dma_addr_t addr; + + addr = cdnsp_trb_virt_to_dma(deq_state->new_deq_seg, + deq_state->new_deq_ptr); + + if (deq_state->stream_id) + trb_sct = SCT_FOR_TRB(SCT_PRI_TR); + + cdnsp_queue_command(pdev, lower_32_bits(addr) | trb_sct | + deq_state->new_cycle_state, upper_32_bits(addr), + trb_stream_id, trb_slot_id | + EP_ID_FOR_TRB(pep->idx) | type); +} + +void cdnsp_queue_reset_ep(struct cdnsp_device *pdev, unsigned int ep_index) +{ + return cdnsp_queue_command(pdev, 0, 0, 0, + SLOT_ID_FOR_TRB(pdev->slot_id) | + EP_ID_FOR_TRB(ep_index) | + TRB_TYPE(TRB_RESET_EP)); +} + +/* + * Queue a halt endpoint request on the command ring. + */ +void cdnsp_queue_halt_endpoint(struct cdnsp_device *pdev, unsigned int ep_index) +{ + cdnsp_queue_command(pdev, 0, 0, 0, TRB_TYPE(TRB_HALT_ENDPOINT) | + SLOT_ID_FOR_TRB(pdev->slot_id) | + EP_ID_FOR_TRB(ep_index) | + (!ep_index ? TRB_ESP : 0)); +} + +void cdnsp_force_header_wakeup(struct cdnsp_device *pdev, int intf_num) +{ + u32 lo, mid; + + lo = TRB_FH_TO_PACKET_TYPE(TRB_FH_TR_PACKET) | + TRB_FH_TO_DEVICE_ADDRESS(pdev->device_address); + mid = TRB_FH_TR_PACKET_DEV_NOT | + TRB_FH_TO_NOT_TYPE(TRB_FH_TR_PACKET_FUNCTION_WAKE) | + TRB_FH_TO_INTERFACE(intf_num); + + cdnsp_queue_command(pdev, lo, mid, 0, + TRB_TYPE(TRB_FORCE_HEADER) | SET_PORT_ID(2)); +} diff --git a/drivers/usb/cdns3/cdnsp-trace.c b/drivers/usb/cdns3/cdnsp-trace.c new file mode 100644 index 000000000..e50ab799a --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-trace.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence CDNSP DRD Driver. + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + */ + +#define CREATE_TRACE_POINTS +#include "cdnsp-trace.h" diff --git a/drivers/usb/cdns3/cdnsp-trace.h b/drivers/usb/cdns3/cdnsp-trace.h new file mode 100644 index 000000000..9b33a736c --- /dev/null +++ b/drivers/usb/cdns3/cdnsp-trace.h @@ -0,0 +1,801 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence CDNSP DRD Driver. + * Trace support header file + * + * Copyright (C) 2020 Cadence. + * + * Author: Pawel Laszczak + * + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM cdnsp-dev + +/* + * The TRACE_SYSTEM_VAR defaults to TRACE_SYSTEM, but must be a + * legitimate C variable. It is not exported to user space. + */ +#undef TRACE_SYSTEM_VAR +#define TRACE_SYSTEM_VAR cdnsp_dev + +#if !defined(__CDNSP_DEV_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __CDNSP_DEV_TRACE_H + +#include +#include "cdnsp-gadget.h" +#include "cdnsp-debug.h" + +/* + * There is limitation for single buffer size in TRACEPOINT subsystem. + * By default TRACE_BUF_SIZE is 1024, so no all data will be logged. + * To show more data this must be increased. In most cases the default + * value is sufficient. + */ +#define CDNSP_MSG_MAX 500 + +DECLARE_EVENT_CLASS(cdnsp_log_ep, + TP_PROTO(struct cdnsp_ep *pep, u32 stream_id), + TP_ARGS(pep, stream_id), + TP_STRUCT__entry( + __string(name, pep->name) + __field(unsigned int, state) + __field(u32, stream_id) + __field(u8, enabled) + __field(unsigned int, num_streams) + __field(int, td_count) + __field(u8, first_prime_det) + __field(u8, drbls_count) + ), + TP_fast_assign( + __assign_str(name); + __entry->state = pep->ep_state; + __entry->stream_id = stream_id; + __entry->enabled = pep->ep_state & EP_HAS_STREAMS; + __entry->num_streams = pep->stream_info.num_streams; + __entry->td_count = pep->stream_info.td_count; + __entry->first_prime_det = pep->stream_info.first_prime_det; + __entry->drbls_count = pep->stream_info.drbls_count; + ), + TP_printk("%s: SID: %08x, ep state: %x, stream: enabled: %d num %d " + "tds %d, first prime: %d drbls %d", + __get_str(name), __entry->stream_id, __entry->state, + __entry->enabled, __entry->num_streams, __entry->td_count, + __entry->first_prime_det, __entry->drbls_count) +); + +DEFINE_EVENT(cdnsp_log_ep, cdnsp_tr_drbl, + TP_PROTO(struct cdnsp_ep *pep, u32 stream_id), + TP_ARGS(pep, stream_id) +); + +DEFINE_EVENT(cdnsp_log_ep, cdnsp_wait_for_prime, + TP_PROTO(struct cdnsp_ep *pep, u32 stream_id), + TP_ARGS(pep, stream_id) +); + +DEFINE_EVENT(cdnsp_log_ep, cdnsp_ep_list_empty_with_skip, + TP_PROTO(struct cdnsp_ep *pep, u32 stream_id), + TP_ARGS(pep, stream_id) +); + +DEFINE_EVENT(cdnsp_log_ep, cdnsp_ep_enable_end, + TP_PROTO(struct cdnsp_ep *pep, u32 stream_id), + TP_ARGS(pep, stream_id) +); + +DEFINE_EVENT(cdnsp_log_ep, cdnsp_ep_disable_end, + TP_PROTO(struct cdnsp_ep *pep, u32 stream_id), + TP_ARGS(pep, stream_id) +); + +DEFINE_EVENT(cdnsp_log_ep, cdnsp_ep_busy_try_halt_again, + TP_PROTO(struct cdnsp_ep *pep, u32 stream_id), + TP_ARGS(pep, stream_id) +); + +DECLARE_EVENT_CLASS(cdnsp_log_enable_disable, + TP_PROTO(int set), + TP_ARGS(set), + TP_STRUCT__entry( + __field(int, set) + ), + TP_fast_assign( + __entry->set = set; + ), + TP_printk("%s", __entry->set ? "enabled" : "disabled") +); + +DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_pullup, + TP_PROTO(int set), + TP_ARGS(set) +); + +DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_u1, + TP_PROTO(int set), + TP_ARGS(set) +); + +DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_u2, + TP_PROTO(int set), + TP_ARGS(set) +); + +DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_lpm, + TP_PROTO(int set), + TP_ARGS(set) +); + +DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_may_wakeup, + TP_PROTO(int set), + TP_ARGS(set) +); + +DECLARE_EVENT_CLASS(cdnsp_log_simple, + TP_PROTO(char *msg), + TP_ARGS(msg), + TP_STRUCT__entry( + __string(text, msg) + ), + TP_fast_assign( + __assign_str(text); + ), + TP_printk("%s", __get_str(text)) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_exit, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_init, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_slot_id, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_no_room_on_ring, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_status_stage, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_request, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_set_config, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep_halt, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +TRACE_EVENT(cdnsp_looking_trb_in_td, + TP_PROTO(dma_addr_t suspect, dma_addr_t trb_start, dma_addr_t trb_end, + dma_addr_t curr_seg, dma_addr_t end_seg), + TP_ARGS(suspect, trb_start, trb_end, curr_seg, end_seg), + TP_STRUCT__entry( + __field(dma_addr_t, suspect) + __field(dma_addr_t, trb_start) + __field(dma_addr_t, trb_end) + __field(dma_addr_t, curr_seg) + __field(dma_addr_t, end_seg) + ), + TP_fast_assign( + __entry->suspect = suspect; + __entry->trb_start = trb_start; + __entry->trb_end = trb_end; + __entry->curr_seg = curr_seg; + __entry->end_seg = end_seg; + ), + TP_printk("DMA: suspect event: %pad, trb-start: %pad, trb-end %pad, " + "seg-start %pad, seg-end %pad", + &__entry->suspect, &__entry->trb_start, &__entry->trb_end, + &__entry->curr_seg, &__entry->end_seg) +); + +TRACE_EVENT(cdnsp_port_info, + TP_PROTO(__le32 __iomem *addr, u32 offset, u32 count, u32 rev), + TP_ARGS(addr, offset, count, rev), + TP_STRUCT__entry( + __field(__le32 __iomem *, addr) + __field(u32, offset) + __field(u32, count) + __field(u32, rev) + ), + TP_fast_assign( + __entry->addr = addr; + __entry->offset = offset; + __entry->count = count; + __entry->rev = rev; + ), + TP_printk("Ext Cap %p, port offset = %u, count = %u, rev = 0x%x", + __entry->addr, __entry->offset, __entry->count, __entry->rev) +); + +DECLARE_EVENT_CLASS(cdnsp_log_deq_state, + TP_PROTO(struct cdnsp_dequeue_state *state), + TP_ARGS(state), + TP_STRUCT__entry( + __field(int, new_cycle_state) + __field(struct cdnsp_segment *, new_deq_seg) + __field(dma_addr_t, deq_seg_dma) + __field(union cdnsp_trb *, new_deq_ptr) + __field(dma_addr_t, deq_ptr_dma) + ), + TP_fast_assign( + __entry->new_cycle_state = state->new_cycle_state; + __entry->new_deq_seg = state->new_deq_seg; + __entry->deq_seg_dma = state->new_deq_seg->dma; + __entry->new_deq_ptr = state->new_deq_ptr, + __entry->deq_ptr_dma = cdnsp_trb_virt_to_dma(state->new_deq_seg, + state->new_deq_ptr); + ), + TP_printk("New cycle state = 0x%x, New dequeue segment = %p (0x%pad dma), " + "New dequeue pointer = %p (0x%pad dma)", + __entry->new_cycle_state, __entry->new_deq_seg, + &__entry->deq_seg_dma, __entry->new_deq_ptr, + &__entry->deq_ptr_dma + ) +); + +DEFINE_EVENT(cdnsp_log_deq_state, cdnsp_new_deq_state, + TP_PROTO(struct cdnsp_dequeue_state *state), + TP_ARGS(state) +); + +DECLARE_EVENT_CLASS(cdnsp_log_ctrl, + TP_PROTO(struct usb_ctrlrequest *ctrl), + TP_ARGS(ctrl), + TP_STRUCT__entry( + __field(u8, bRequestType) + __field(u8, bRequest) + __field(u16, wValue) + __field(u16, wIndex) + __field(u16, wLength) + ), + TP_fast_assign( + __entry->bRequestType = ctrl->bRequestType; + __entry->bRequest = ctrl->bRequest; + __entry->wValue = le16_to_cpu(ctrl->wValue); + __entry->wIndex = le16_to_cpu(ctrl->wIndex); + __entry->wLength = le16_to_cpu(ctrl->wLength); + ), + TP_printk("%s", usb_decode_ctrl(__get_buf(CDNSP_MSG_MAX), CDNSP_MSG_MAX, + __entry->bRequestType, + __entry->bRequest, __entry->wValue, + __entry->wIndex, __entry->wLength) + ) +); + +DEFINE_EVENT(cdnsp_log_ctrl, cdnsp_ctrl_req, + TP_PROTO(struct usb_ctrlrequest *ctrl), + TP_ARGS(ctrl) +); + +DECLARE_EVENT_CLASS(cdnsp_log_bounce, + TP_PROTO(struct cdnsp_request *preq, u32 new_buf_len, u32 offset, + dma_addr_t dma, unsigned int unalign), + TP_ARGS(preq, new_buf_len, offset, dma, unalign), + TP_STRUCT__entry( + __string(name, preq->pep->name) + __field(u32, new_buf_len) + __field(u32, offset) + __field(dma_addr_t, dma) + __field(unsigned int, unalign) + ), + TP_fast_assign( + __assign_str(name); + __entry->new_buf_len = new_buf_len; + __entry->offset = offset; + __entry->dma = dma; + __entry->unalign = unalign; + ), + TP_printk("%s buf len %d, offset %d, dma %pad, unalign %d", + __get_str(name), __entry->new_buf_len, + __entry->offset, &__entry->dma, __entry->unalign + ) +); + +DEFINE_EVENT(cdnsp_log_bounce, cdnsp_bounce_align_td_split, + TP_PROTO(struct cdnsp_request *preq, u32 new_buf_len, u32 offset, + dma_addr_t dma, unsigned int unalign), + TP_ARGS(preq, new_buf_len, offset, dma, unalign) +); + +DEFINE_EVENT(cdnsp_log_bounce, cdnsp_bounce_map, + TP_PROTO(struct cdnsp_request *preq, u32 new_buf_len, u32 offset, + dma_addr_t dma, unsigned int unalign), + TP_ARGS(preq, new_buf_len, offset, dma, unalign) +); + +DEFINE_EVENT(cdnsp_log_bounce, cdnsp_bounce_unmap, + TP_PROTO(struct cdnsp_request *preq, u32 new_buf_len, u32 offset, + dma_addr_t dma, unsigned int unalign), + TP_ARGS(preq, new_buf_len, offset, dma, unalign) +); + +DECLARE_EVENT_CLASS(cdnsp_log_trb, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb), + TP_STRUCT__entry( + __field(u32, type) + __field(u32, field0) + __field(u32, field1) + __field(u32, field2) + __field(u32, field3) + __field(union cdnsp_trb *, trb) + __field(dma_addr_t, trb_dma) + ), + TP_fast_assign( + __entry->type = ring->type; + __entry->field0 = le32_to_cpu(trb->field[0]); + __entry->field1 = le32_to_cpu(trb->field[1]); + __entry->field2 = le32_to_cpu(trb->field[2]); + __entry->field3 = le32_to_cpu(trb->field[3]); + __entry->trb = (union cdnsp_trb *)trb; + __entry->trb_dma = cdnsp_trb_virt_to_dma(ring->deq_seg, + (union cdnsp_trb *)trb); + + ), + TP_printk("%s: %s trb: %p(%pad)", cdnsp_ring_type_string(__entry->type), + cdnsp_decode_trb(__get_buf(CDNSP_MSG_MAX), CDNSP_MSG_MAX, + __entry->field0, __entry->field1, + __entry->field2, __entry->field3), + __entry->trb, &__entry->trb_dma + ) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_handle_event, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_trb_without_td, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_handle_command, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_handle_transfer, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_queue_trb, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_cmd_wait_for_compl, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DEFINE_EVENT(cdnsp_log_trb, cdnsp_cmd_timeout, + TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), + TP_ARGS(ring, trb) +); + +DECLARE_EVENT_CLASS(cdnsp_log_pdev, + TP_PROTO(struct cdnsp_device *pdev), + TP_ARGS(pdev), + TP_STRUCT__entry( + __field(struct cdnsp_device *, pdev) + __field(struct usb_gadget *, gadget) + __field(dma_addr_t, out_ctx) + __field(dma_addr_t, in_ctx) + __field(u8, port_num) + ), + TP_fast_assign( + __entry->pdev = pdev; + __entry->gadget = &pdev->gadget; + __entry->in_ctx = pdev->in_ctx.dma; + __entry->out_ctx = pdev->out_ctx.dma; + __entry->port_num = pdev->active_port ? + pdev->active_port->port_num : 0xFF; + ), + TP_printk("pdev %p gadget %p ctx %pad | %pad, port %d ", + __entry->pdev, __entry->gadget, &__entry->in_ctx, + &__entry->out_ctx, __entry->port_num + ) +); + +DEFINE_EVENT(cdnsp_log_pdev, cdnsp_alloc_priv_device, + TP_PROTO(struct cdnsp_device *vdev), + TP_ARGS(vdev) +); + +DEFINE_EVENT(cdnsp_log_pdev, cdnsp_setup_addressable_priv_device, + TP_PROTO(struct cdnsp_device *vdev), + TP_ARGS(vdev) +); + +DECLARE_EVENT_CLASS(cdnsp_log_request, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req), + TP_STRUCT__entry( + __string(name, req->pep->name) + __field(struct usb_request *, request) + __field(struct cdnsp_request *, preq) + __field(void *, buf) + __field(unsigned int, actual) + __field(unsigned int, length) + __field(int, status) + __field(dma_addr_t, dma) + __field(unsigned int, stream_id) + __field(unsigned int, zero) + __field(unsigned int, short_not_ok) + __field(unsigned int, no_interrupt) + __field(struct scatterlist*, sg) + __field(unsigned int, num_sgs) + __field(unsigned int, num_mapped_sgs) + + ), + TP_fast_assign( + __assign_str(name); + __entry->request = &req->request; + __entry->preq = req; + __entry->buf = req->request.buf; + __entry->actual = req->request.actual; + __entry->length = req->request.length; + __entry->status = req->request.status; + __entry->dma = req->request.dma; + __entry->stream_id = req->request.stream_id; + __entry->zero = req->request.zero; + __entry->short_not_ok = req->request.short_not_ok; + __entry->no_interrupt = req->request.no_interrupt; + __entry->sg = req->request.sg; + __entry->num_sgs = req->request.num_sgs; + __entry->num_mapped_sgs = req->request.num_mapped_sgs; + ), + TP_printk("%s; req U:%p/P:%p, req buf %p, length %u/%u, status %d, " + "buf dma (%pad), SID %u, %s%s%s, sg %p, num_sg %d," + " num_m_sg %d", + __get_str(name), __entry->request, __entry->preq, + __entry->buf, __entry->actual, __entry->length, + __entry->status, &__entry->dma, + __entry->stream_id, __entry->zero ? "Z" : "z", + __entry->short_not_ok ? "S" : "s", + __entry->no_interrupt ? "I" : "i", + __entry->sg, __entry->num_sgs, __entry->num_mapped_sgs + ) +); + +DEFINE_EVENT(cdnsp_log_request, cdnsp_request_enqueue, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdnsp_log_request, cdnsp_request_enqueue_busy, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdnsp_log_request, cdnsp_request_enqueue_error, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdnsp_log_request, cdnsp_request_dequeue, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdnsp_log_request, cdnsp_request_giveback, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdnsp_log_request, cdnsp_alloc_request, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(cdnsp_log_request, cdnsp_free_request, + TP_PROTO(struct cdnsp_request *req), + TP_ARGS(req) +); + +DECLARE_EVENT_CLASS(cdnsp_log_ep_ctx, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx), + TP_STRUCT__entry( + __field(u32, info) + __field(u32, info2) + __field(u64, deq) + __field(u32, tx_info) + ), + TP_fast_assign( + __entry->info = le32_to_cpu(ctx->ep_info); + __entry->info2 = le32_to_cpu(ctx->ep_info2); + __entry->deq = le64_to_cpu(ctx->deq); + __entry->tx_info = le32_to_cpu(ctx->tx_info); + ), + TP_printk("%s", cdnsp_decode_ep_context(__get_buf(CDNSP_MSG_MAX), CDNSP_MSG_MAX, + __entry->info, __entry->info2, + __entry->deq, __entry->tx_info) + ) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_ep_disabled, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_ep_stopped_or_disabled, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_remove_request, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_stop_ep, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_set_deq_ep, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_reset_ep, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_config_ep, + TP_PROTO(struct cdnsp_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DECLARE_EVENT_CLASS(cdnsp_log_slot_ctx, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx), + TP_STRUCT__entry( + __field(u32, info) + __field(u32, info2) + __field(u32, int_target) + __field(u32, state) + ), + TP_fast_assign( + __entry->info = le32_to_cpu(ctx->dev_info); + __entry->info2 = le32_to_cpu(ctx->dev_port); + __entry->int_target = le32_to_cpu(ctx->int_target); + __entry->state = le32_to_cpu(ctx->dev_state); + ), + TP_printk("%s", cdnsp_decode_slot_context(__entry->info, + __entry->info2, + __entry->int_target, + __entry->state) + ) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_slot_already_in_default, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_enable_slot, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_disable_slot, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_reset_device, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_setup_device_slot, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_addr_dev, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_reset_dev, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_set_deq, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_configure_endpoint, + TP_PROTO(struct cdnsp_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DECLARE_EVENT_CLASS(cdnsp_log_td_info, + TP_PROTO(struct cdnsp_request *preq), + TP_ARGS(preq), + TP_STRUCT__entry( + __string(name, preq->pep->name) + __field(struct usb_request *, request) + __field(struct cdnsp_request *, preq) + __field(union cdnsp_trb *, first_trb) + __field(union cdnsp_trb *, last_trb) + __field(dma_addr_t, trb_dma) + ), + TP_fast_assign( + __assign_str(name); + __entry->request = &preq->request; + __entry->preq = preq; + __entry->first_trb = preq->td.first_trb; + __entry->last_trb = preq->td.last_trb; + __entry->trb_dma = cdnsp_trb_virt_to_dma(preq->td.start_seg, + preq->td.first_trb) + ), + TP_printk("%s req/preq: %p/%p, first trb %p[vir]/%pad(dma), last trb %p", + __get_str(name), __entry->request, __entry->preq, + __entry->first_trb, &__entry->trb_dma, + __entry->last_trb + ) +); + +DEFINE_EVENT(cdnsp_log_td_info, cdnsp_remove_request_td, + TP_PROTO(struct cdnsp_request *preq), + TP_ARGS(preq) +); + +DECLARE_EVENT_CLASS(cdnsp_log_ring, + TP_PROTO(struct cdnsp_ring *ring), + TP_ARGS(ring), + TP_STRUCT__entry( + __field(u32, type) + __field(void *, ring) + __field(dma_addr_t, enq) + __field(dma_addr_t, deq) + __field(dma_addr_t, enq_seg) + __field(dma_addr_t, deq_seg) + __field(unsigned int, num_segs) + __field(unsigned int, stream_id) + __field(unsigned int, cycle_state) + __field(unsigned int, num_trbs_free) + __field(unsigned int, bounce_buf_len) + ), + TP_fast_assign( + __entry->ring = ring; + __entry->type = ring->type; + __entry->num_segs = ring->num_segs; + __entry->stream_id = ring->stream_id; + __entry->enq_seg = ring->enq_seg->dma; + __entry->deq_seg = ring->deq_seg->dma; + __entry->cycle_state = ring->cycle_state; + __entry->num_trbs_free = ring->num_trbs_free; + __entry->bounce_buf_len = ring->bounce_buf_len; + __entry->enq = cdnsp_trb_virt_to_dma(ring->enq_seg, + ring->enqueue); + __entry->deq = cdnsp_trb_virt_to_dma(ring->deq_seg, + ring->dequeue); + ), + TP_printk("%s %p: enq %pad(%pad) deq %pad(%pad) segs %d stream %d" + " free_trbs %d bounce %d cycle %d", + cdnsp_ring_type_string(__entry->type), __entry->ring, + &__entry->enq, &__entry->enq_seg, + &__entry->deq, &__entry->deq_seg, + __entry->num_segs, + __entry->stream_id, + __entry->num_trbs_free, + __entry->bounce_buf_len, + __entry->cycle_state + ) +); + +DEFINE_EVENT(cdnsp_log_ring, cdnsp_ring_alloc, + TP_PROTO(struct cdnsp_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(cdnsp_log_ring, cdnsp_ring_free, + TP_PROTO(struct cdnsp_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(cdnsp_log_ring, cdnsp_set_stream_ring, + TP_PROTO(struct cdnsp_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(cdnsp_log_ring, cdnsp_ring_expansion, + TP_PROTO(struct cdnsp_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(cdnsp_log_ring, cdnsp_inc_enq, + TP_PROTO(struct cdnsp_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(cdnsp_log_ring, cdnsp_inc_deq, + TP_PROTO(struct cdnsp_ring *ring), + TP_ARGS(ring) +); + +DECLARE_EVENT_CLASS(cdnsp_log_portsc, + TP_PROTO(u32 portnum, u32 portsc), + TP_ARGS(portnum, portsc), + TP_STRUCT__entry( + __field(u32, portnum) + __field(u32, portsc) + ), + TP_fast_assign( + __entry->portnum = portnum; + __entry->portsc = portsc; + ), + TP_printk("port-%d: %s", + __entry->portnum, + cdnsp_decode_portsc(__get_buf(CDNSP_MSG_MAX), CDNSP_MSG_MAX, + __entry->portsc) + ) +); + +DEFINE_EVENT(cdnsp_log_portsc, cdnsp_handle_port_status, + TP_PROTO(u32 portnum, u32 portsc), + TP_ARGS(portnum, portsc) +); + +DEFINE_EVENT(cdnsp_log_portsc, cdnsp_link_state_changed, + TP_PROTO(u32 portnum, u32 portsc), + TP_ARGS(portnum, portsc) +); + +TRACE_EVENT(cdnsp_stream_number, + TP_PROTO(struct cdnsp_ep *pep, int num_stream_ctxs, int num_streams), + TP_ARGS(pep, num_stream_ctxs, num_streams), + TP_STRUCT__entry( + __string(name, pep->name) + __field(int, num_stream_ctxs) + __field(int, num_streams) + ), + TP_fast_assign( + __entry->num_stream_ctxs = num_stream_ctxs; + __entry->num_streams = num_streams; + ), + TP_printk("%s Need %u stream ctx entries for %u stream IDs.", + __get_str(name), __entry->num_stream_ctxs, + __entry->num_streams) +); + +#endif /* __CDNSP_TRACE_H */ + +/* this part must be outside header guard */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE cdnsp-trace + +#include diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c new file mode 100644 index 000000000..504bdf13e --- /dev/null +++ b/drivers/usb/cdns3/core.c @@ -0,0 +1,564 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBSS and USBSSP DRD Driver. + * + * Copyright (C) 2018-2019 Cadence. + * Copyright (C) 2017-2018 NXP + * Copyright (C) 2019 Texas Instruments + * + * Author: Peter Chen + * Pawel Laszczak + * Roger Quadros + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "drd.h" + +static int cdns_idle_init(struct cdns *cdns); + +static int cdns_role_start(struct cdns *cdns, enum usb_role role) +{ + int ret; + + if (WARN_ON(role > USB_ROLE_DEVICE)) + return 0; + + mutex_lock(&cdns->mutex); + cdns->role = role; + mutex_unlock(&cdns->mutex); + + if (!cdns->roles[role]) + return -ENXIO; + + if (cdns->roles[role]->state == CDNS_ROLE_STATE_ACTIVE) + return 0; + + mutex_lock(&cdns->mutex); + ret = cdns->roles[role]->start(cdns); + if (!ret) + cdns->roles[role]->state = CDNS_ROLE_STATE_ACTIVE; + mutex_unlock(&cdns->mutex); + + return ret; +} + +static void cdns_role_stop(struct cdns *cdns) +{ + enum usb_role role = cdns->role; + + if (WARN_ON(role > USB_ROLE_DEVICE)) + return; + + if (cdns->roles[role]->state == CDNS_ROLE_STATE_INACTIVE) + return; + + mutex_lock(&cdns->mutex); + cdns->roles[role]->stop(cdns); + cdns->roles[role]->state = CDNS_ROLE_STATE_INACTIVE; + mutex_unlock(&cdns->mutex); +} + +static void cdns_exit_roles(struct cdns *cdns) +{ + cdns_role_stop(cdns); + if (!cdns->no_drd) + cdns_drd_exit(cdns); +} + +/** + * cdns_core_init_role - initialize role of operation + * @cdns: Pointer to cdns structure + * + * Returns 0 on success otherwise negative errno + */ +int cdns_core_init_role(struct cdns *cdns) +{ + struct device *dev = cdns->dev; + enum usb_dr_mode best_dr_mode; + enum usb_dr_mode dr_mode; + int ret; + + dr_mode = usb_get_dr_mode(dev); + cdns->role = USB_ROLE_NONE; + + /* + * If driver can't read mode by means of usb_get_dr_mode function then + * chooses mode according with Kernel configuration. This setting + * can be restricted later depending on strap pin configuration. + */ + if (dr_mode == USB_DR_MODE_UNKNOWN) { + if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) && + IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) + dr_mode = USB_DR_MODE_OTG; + else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST)) + dr_mode = USB_DR_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) + dr_mode = USB_DR_MODE_PERIPHERAL; + } + + /* + * At this point cdns->dr_mode contains strap configuration. + * Driver try update this setting considering kernel configuration + */ + best_dr_mode = cdns->dr_mode; + + ret = cdns_idle_init(cdns); + if (ret) + return ret; + + if (dr_mode == USB_DR_MODE_OTG) { + best_dr_mode = cdns->dr_mode; + } else if (cdns->dr_mode == USB_DR_MODE_OTG) { + best_dr_mode = dr_mode; + } else if (cdns->dr_mode != dr_mode) { + dev_err(dev, "Incorrect DRD configuration\n"); + return -EINVAL; + } + + dr_mode = best_dr_mode; + + if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { + if (cdns->host_init) + ret = cdns->host_init(cdns); + else + ret = -ENXIO; + + if (ret) { + dev_err(dev, "Host initialization failed with %d\n", + ret); + goto err; + } + } + + if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) { + if (cdns->gadget_init) + ret = cdns->gadget_init(cdns); + else + ret = -ENXIO; + + if (ret) { + dev_err(dev, "Device initialization failed with %d\n", + ret); + goto err; + } + } + + cdns->dr_mode = dr_mode; + + ret = cdns_drd_update_mode(cdns); + if (ret) + goto err; + + /* Initialize idle role to start with */ + ret = cdns_role_start(cdns, USB_ROLE_NONE); + if (ret) + goto err; + + switch (cdns->dr_mode) { + case USB_DR_MODE_OTG: + ret = cdns_hw_role_switch(cdns); + if (ret) + goto err; + break; + case USB_DR_MODE_PERIPHERAL: + ret = cdns_role_start(cdns, USB_ROLE_DEVICE); + if (ret) + goto err; + break; + case USB_DR_MODE_HOST: + ret = cdns_role_start(cdns, USB_ROLE_HOST); + if (ret) + goto err; + break; + default: + ret = -EINVAL; + goto err; + } + + dev_dbg(dev, "Cadence USB3 core: probe succeed\n"); + + return 0; +err: + cdns_exit_roles(cdns); + return ret; +} +EXPORT_SYMBOL_GPL(cdns_core_init_role); + +/** + * cdns_hw_role_state_machine - role switch state machine based on hw events. + * @cdns: Pointer to controller structure. + * + * Returns next role to be entered based on hw events. + */ +static enum usb_role cdns_hw_role_state_machine(struct cdns *cdns) +{ + enum usb_role role = USB_ROLE_NONE; + int id, vbus; + + if (cdns->dr_mode != USB_DR_MODE_OTG) { + if (cdns_is_host(cdns)) + role = USB_ROLE_HOST; + if (cdns_is_device(cdns)) + role = USB_ROLE_DEVICE; + + return role; + } + + id = cdns_get_id(cdns); + vbus = cdns_get_vbus(cdns); + + /* + * Role change state machine + * Inputs: ID, VBUS + * Previous state: cdns->role + * Next state: role + */ + role = cdns->role; + + switch (role) { + case USB_ROLE_NONE: + /* + * Driver treats USB_ROLE_NONE synonymous to IDLE state from + * controller specification. + */ + if (!id) + role = USB_ROLE_HOST; + else if (vbus) + role = USB_ROLE_DEVICE; + break; + case USB_ROLE_HOST: /* from HOST, we can only change to NONE */ + if (id) + role = USB_ROLE_NONE; + break; + case USB_ROLE_DEVICE: /* from GADGET, we can only change to NONE*/ + if (!vbus) + role = USB_ROLE_NONE; + break; + } + + dev_dbg(cdns->dev, "role %d -> %d\n", cdns->role, role); + + return role; +} + +static int cdns_idle_role_start(struct cdns *cdns) +{ + return 0; +} + +static void cdns_idle_role_stop(struct cdns *cdns) +{ + /* Program Lane swap and bring PHY out of RESET */ + phy_reset(cdns->usb3_phy); +} + +static int cdns_idle_init(struct cdns *cdns) +{ + struct cdns_role_driver *rdrv; + + rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + rdrv->start = cdns_idle_role_start; + rdrv->stop = cdns_idle_role_stop; + rdrv->state = CDNS_ROLE_STATE_INACTIVE; + rdrv->suspend = NULL; + rdrv->resume = NULL; + rdrv->name = "idle"; + + cdns->roles[USB_ROLE_NONE] = rdrv; + + return 0; +} + +/** + * cdns_hw_role_switch - switch roles based on HW state + * @cdns: controller + */ +int cdns_hw_role_switch(struct cdns *cdns) +{ + enum usb_role real_role, current_role; + int ret = 0; + + /* Depends on role switch class */ + if (cdns->role_sw) + return 0; + + pm_runtime_get_sync(cdns->dev); + + current_role = cdns->role; + real_role = cdns_hw_role_state_machine(cdns); + + /* Do nothing if nothing changed */ + if (current_role == real_role) + goto exit; + + cdns_role_stop(cdns); + + dev_dbg(cdns->dev, "Switching role %d -> %d", current_role, real_role); + + ret = cdns_role_start(cdns, real_role); + if (ret) { + /* Back to current role */ + dev_err(cdns->dev, "set %d has failed, back to %d\n", + real_role, current_role); + ret = cdns_role_start(cdns, current_role); + if (ret) + dev_err(cdns->dev, "back to %d failed too\n", + current_role); + } +exit: + pm_runtime_put_sync(cdns->dev); + return ret; +} + +/** + * cdns_role_get - get current role of controller. + * + * @sw: pointer to USB role switch structure + * + * Returns role + */ +static enum usb_role cdns_role_get(struct usb_role_switch *sw) +{ + struct cdns *cdns = usb_role_switch_get_drvdata(sw); + + return cdns->role; +} + +/** + * cdns_role_set - set current role of controller. + * + * @sw: pointer to USB role switch structure + * @role: the previous role + * Handles below events: + * - Role switch for dual-role devices + * - USB_ROLE_GADGET <--> USB_ROLE_NONE for peripheral-only devices + */ +static int cdns_role_set(struct usb_role_switch *sw, enum usb_role role) +{ + struct cdns *cdns = usb_role_switch_get_drvdata(sw); + int ret = 0; + + pm_runtime_get_sync(cdns->dev); + + if (cdns->role == role) + goto pm_put; + + if (cdns->dr_mode == USB_DR_MODE_HOST) { + switch (role) { + case USB_ROLE_NONE: + case USB_ROLE_HOST: + break; + default: + goto pm_put; + } + } + + if (cdns->dr_mode == USB_DR_MODE_PERIPHERAL) { + switch (role) { + case USB_ROLE_NONE: + case USB_ROLE_DEVICE: + break; + default: + goto pm_put; + } + } + + cdns_role_stop(cdns); + ret = cdns_role_start(cdns, role); + if (ret) + dev_err(cdns->dev, "set role %d has failed\n", role); + +pm_put: + pm_runtime_put_sync(cdns->dev); + return ret; +} + +/** + * cdns_wakeup_irq - interrupt handler for wakeup events + * @irq: irq number for cdns3/cdnsp core device + * @data: structure of cdns + * + * Returns IRQ_HANDLED or IRQ_NONE + */ +static irqreturn_t cdns_wakeup_irq(int irq, void *data) +{ + struct cdns *cdns = data; + + if (cdns->in_lpm) { + disable_irq_nosync(irq); + cdns->wakeup_pending = true; + if ((cdns->role == USB_ROLE_HOST) && cdns->host_dev) + pm_request_resume(&cdns->host_dev->dev); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +/** + * cdns_init - probe for cdns3/cdnsp core device + * @cdns: Pointer to cdns structure. + * + * Returns 0 on success otherwise negative errno + */ +int cdns_init(struct cdns *cdns) +{ + struct device *dev = cdns->dev; + int ret; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(dev, "error setting dma mask: %d\n", ret); + return ret; + } + + mutex_init(&cdns->mutex); + + if (device_property_read_bool(dev, "usb-role-switch")) { + struct usb_role_switch_desc sw_desc = { }; + + sw_desc.set = cdns_role_set; + sw_desc.get = cdns_role_get; + sw_desc.allow_userspace_control = true; + sw_desc.driver_data = cdns; + sw_desc.fwnode = dev->fwnode; + + cdns->role_sw = usb_role_switch_register(dev, &sw_desc); + if (IS_ERR(cdns->role_sw)) { + dev_warn(dev, "Unable to register Role Switch\n"); + return PTR_ERR(cdns->role_sw); + } + } + + if (cdns->wakeup_irq) { + ret = devm_request_irq(cdns->dev, cdns->wakeup_irq, + cdns_wakeup_irq, + IRQF_SHARED, + dev_name(cdns->dev), cdns); + + if (ret) { + dev_err(cdns->dev, "couldn't register wakeup irq handler\n"); + goto role_switch_unregister; + } + } + + ret = cdns_drd_init(cdns); + if (ret) + goto init_failed; + + spin_lock_init(&cdns->lock); + + return 0; +init_failed: + cdns_drd_exit(cdns); +role_switch_unregister: + if (cdns->role_sw) + usb_role_switch_unregister(cdns->role_sw); + + return ret; +} +EXPORT_SYMBOL_GPL(cdns_init); + +/** + * cdns_remove - unbind drd driver and clean up + * @cdns: Pointer to cdns structure. + * + * Returns 0 on success otherwise negative errno + */ +int cdns_remove(struct cdns *cdns) +{ + cdns_exit_roles(cdns); + usb_role_switch_unregister(cdns->role_sw); + + return 0; +} +EXPORT_SYMBOL_GPL(cdns_remove); + +#ifdef CONFIG_PM_SLEEP +int cdns_suspend(struct cdns *cdns) +{ + struct device *dev = cdns->dev; + unsigned long flags; + + if (pm_runtime_status_suspended(dev)) + pm_runtime_resume(dev); + + if (cdns->roles[cdns->role]->suspend) { + spin_lock_irqsave(&cdns->lock, flags); + cdns->roles[cdns->role]->suspend(cdns, false); + spin_unlock_irqrestore(&cdns->lock, flags); + } + + return 0; +} +EXPORT_SYMBOL_GPL(cdns_suspend); + +int cdns_resume(struct cdns *cdns) +{ + bool power_lost = cdns_power_is_lost(cdns); + enum usb_role real_role; + bool role_changed = false; + int ret = 0; + + if (power_lost) { + if (!cdns->role_sw) { + real_role = cdns_hw_role_state_machine(cdns); + if (real_role != cdns->role) { + ret = cdns_hw_role_switch(cdns); + if (ret) + return ret; + role_changed = true; + } + } + + if (!role_changed) { + if (cdns->role == USB_ROLE_HOST) + ret = cdns_drd_host_on(cdns); + else if (cdns->role == USB_ROLE_DEVICE) + ret = cdns_drd_gadget_on(cdns); + + if (ret) + return ret; + } + } + + if (!role_changed && cdns->roles[cdns->role]->resume) + cdns->roles[cdns->role]->resume(cdns, power_lost); + + return 0; +} +EXPORT_SYMBOL_GPL(cdns_resume); + +void cdns_set_active(struct cdns *cdns, u8 set_active) +{ + struct device *dev = cdns->dev; + + if (set_active) { + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + return; +} +EXPORT_SYMBOL_GPL(cdns_set_active); +#endif /* CONFIG_PM_SLEEP */ + +MODULE_AUTHOR("Peter Chen "); +MODULE_AUTHOR("Pawel Laszczak "); +MODULE_AUTHOR("Roger Quadros "); +MODULE_DESCRIPTION("Cadence USBSS/USBSSP DRD driver (core, DRD, platform, optional host/gadget)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h new file mode 100644 index 000000000..8c492fda9 --- /dev/null +++ b/drivers/usb/cdns3/core.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence USBSS and USBSSP DRD Header File. + * + * Copyright (C) 2017-2018 NXP + * Copyright (C) 2018-2019 Cadence. + * + * Authors: Peter Chen + * Pawel Laszczak + */ +#ifndef __LINUX_CDNS3_CORE_H +#define __LINUX_CDNS3_CORE_H + +#include +#include + +struct cdns; + +/** + * struct cdns_role_driver - host/gadget role driver + * @start: start this role + * @stop: stop this role + * @suspend: suspend callback for this role + * @resume: resume callback for this role + * @irq: irq handler for this role + * @name: role name string (host/gadget) + * @state: current state + */ +struct cdns_role_driver { + int (*start)(struct cdns *cdns); + void (*stop)(struct cdns *cdns); + int (*suspend)(struct cdns *cdns, bool do_wakeup); + int (*resume)(struct cdns *cdns, bool lost_power); + const char *name; +#define CDNS_ROLE_STATE_INACTIVE 0 +#define CDNS_ROLE_STATE_ACTIVE 1 + int state; +}; + +#define CDNS_XHCI_RESOURCES_NUM 2 + +struct cdns3_platform_data { + int (*platform_suspend)(struct device *dev, + bool suspend, bool wakeup); + unsigned long quirks; +#define CDNS3_DEFAULT_PM_RUNTIME_ALLOW BIT(0) +#define CDNS3_DRD_SUSPEND_RESIDENCY_ENABLE BIT(1) + u32 override_apb_timeout; /* 0 = use default (e.g. for PCI) */ +}; + +/** + * struct cdns - Representation of Cadence USB3 DRD controller. + * @dev: pointer to Cadence device struct + * @xhci_regs: pointer to base of xhci registers + * @xhci_res: the resource for xhci + * @dev_regs: pointer to base of dev registers + * @otg_res: the resource for otg + * @otg_v0_regs: pointer to base of v0 otg registers + * @otg_v1_regs: pointer to base of v1 otg registers + * @otg_cdnsp_regs: pointer to base of CDNSP otg registers + * @otg_regs: pointer to base of otg registers + * @otg_irq_regs: pointer to interrupt registers + * @otg_irq: irq number for otg controller + * @dev_irq: irq number for device controller + * @wakeup_irq: irq number for wakeup event, it is optional + * @roles: array of supported roles for this controller + * @role: current role + * @host_dev: the child host device pointer for cdns core + * @gadget_dev: the child gadget device pointer + * @usb2_phy: pointer to USB2 PHY + * @usb3_phy: pointer to USB3 PHY + * @mutex: the mutex for concurrent code at driver + * @dr_mode: supported mode of operation it can be only Host, only Device + * or OTG mode that allow to switch between Device and Host mode. + * This field based on firmware setting, kernel configuration + * and hardware configuration. + * @role_sw: pointer to role switch object. + * @in_lpm: indicate the controller is in low power mode + * @wakeup_pending: wakeup interrupt pending + * @pdata: platform data from glue layer + * @lock: spinlock structure + * @xhci_plat_data: xhci private data structure pointer + * @override_apb_timeout: hold value of APB timeout. For value 0 the default + * value in CHICKEN_BITS_3 will be preserved. + * @gadget_init: pointer to gadget initialization function + * @host_init: pointer to host initialization function + * @no_drd: DRD register block is inaccessible. The controller is hardwired to + * single role (host or device) or the logic for role switching is + * missing. + */ +struct cdns { + struct device *dev; + void __iomem *xhci_regs; + struct resource xhci_res[CDNS_XHCI_RESOURCES_NUM]; + struct cdns3_usb_regs __iomem *dev_regs; + + struct resource otg_res; + struct cdns3_otg_legacy_regs __iomem *otg_v0_regs; + struct cdns3_otg_regs __iomem *otg_v1_regs; + struct cdnsp_otg_regs __iomem *otg_cdnsp_regs; + struct cdns_otg_common_regs __iomem *otg_regs; + struct cdns_otg_irq_regs __iomem *otg_irq_regs; +#define CDNS3_CONTROLLER_V0 0 +#define CDNS3_CONTROLLER_V1 1 +#define CDNSP_CONTROLLER_V2 2 + u32 version; + bool phyrst_a_enable; + + int otg_irq; + int dev_irq; + int wakeup_irq; + struct cdns_role_driver *roles[USB_ROLE_DEVICE + 1]; + enum usb_role role; + struct platform_device *host_dev; + void *gadget_dev; + struct phy *usb2_phy; + struct phy *usb3_phy; + /* mutext used in workqueue*/ + struct mutex mutex; + enum usb_dr_mode dr_mode; + struct usb_role_switch *role_sw; + bool in_lpm; + bool wakeup_pending; + struct cdns3_platform_data *pdata; + spinlock_t lock; + struct xhci_plat_priv *xhci_plat_data; + u32 override_apb_timeout; + int (*gadget_init)(struct cdns *cdns); + int (*host_init)(struct cdns *cdns); + bool no_drd; +}; + +int cdns_hw_role_switch(struct cdns *cdns); +int cdns_init(struct cdns *cdns); +int cdns_remove(struct cdns *cdns); +int cdns_core_init_role(struct cdns *cdns); + +#ifdef CONFIG_PM_SLEEP +int cdns_resume(struct cdns *cdns); +int cdns_suspend(struct cdns *cdns); +void cdns_set_active(struct cdns *cdns, u8 set_active); +#else /* CONFIG_PM_SLEEP */ +static inline int cdns_resume(struct cdns *cdns) +{ return 0; } +static inline void cdns_set_active(struct cdns *cdns, u8 set_active) { } +static inline int cdns_suspend(struct cdns *cdns) +{ return 0; } +#endif /* CONFIG_PM_SLEEP */ +#endif /* __LINUX_CDNS3_CORE_H */ diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c new file mode 100644 index 000000000..d2bb682e4 --- /dev/null +++ b/drivers/usb/cdns3/drd.c @@ -0,0 +1,551 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBSS and USBSSP DRD Driver. + * + * Copyright (C) 2018-2020 Cadence. + * Copyright (C) 2019 Texas Instruments + * + * Author: Pawel Laszczak + * Roger Quadros + * + */ +#include +#include +#include +#include +#include + +#include "drd.h" +#include "core.h" + +/** + * cdns_set_mode - change mode of OTG Core + * @cdns: pointer to context structure + * @mode: selected mode from cdns_role + * + * Returns 0 on success otherwise negative errno + */ +static int cdns_set_mode(struct cdns *cdns, enum usb_dr_mode mode) +{ + void __iomem *override_reg; + u32 reg; + + switch (mode) { + case USB_DR_MODE_PERIPHERAL: + break; + case USB_DR_MODE_HOST: + break; + case USB_DR_MODE_OTG: + dev_dbg(cdns->dev, "Set controller to OTG mode\n"); + + if (cdns->version == CDNSP_CONTROLLER_V2) + override_reg = &cdns->otg_cdnsp_regs->override; + else if (cdns->version == CDNS3_CONTROLLER_V1) + override_reg = &cdns->otg_v1_regs->override; + else + override_reg = &cdns->otg_v0_regs->ctrl1; + + reg = readl(override_reg); + + if (cdns->version != CDNS3_CONTROLLER_V0) + reg |= OVERRIDE_IDPULLUP; + else + reg |= OVERRIDE_IDPULLUP_V0; + + writel(reg, override_reg); + + if (cdns->version == CDNS3_CONTROLLER_V1) { + /* + * Enable work around feature built into the + * controller to address issue with RX Sensitivity + * est (EL_17) for USB2 PHY. The issue only occures + * for 0x0002450D controller version. + */ + if (cdns->phyrst_a_enable) { + reg = readl(&cdns->otg_v1_regs->phyrst_cfg); + reg |= PHYRST_CFG_PHYRST_A_ENABLE; + writel(reg, &cdns->otg_v1_regs->phyrst_cfg); + } + } + + /* + * Hardware specification says: "ID_VALUE must be valid within + * 50ms after idpullup is set to '1" so driver must wait + * 50ms before reading this pin. + */ + usleep_range(50000, 60000); + break; + default: + dev_err(cdns->dev, "Unsupported mode of operation %d\n", mode); + return -EINVAL; + } + + return 0; +} + +int cdns_get_id(struct cdns *cdns) +{ + int id; + + if (cdns->no_drd) + return 0; + + id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE; + dev_dbg(cdns->dev, "OTG ID: %d", id); + + return id; +} + +int cdns_get_vbus(struct cdns *cdns) +{ + int vbus; + + vbus = !!(readl(&cdns->otg_regs->sts) & OTGSTS_VBUS_VALID); + dev_dbg(cdns->dev, "OTG VBUS: %d", vbus); + + return vbus; +} + +void cdns_clear_vbus(struct cdns *cdns) +{ + u32 reg; + + if (cdns->version != CDNSP_CONTROLLER_V2 || cdns->no_drd) + return; + + reg = readl(&cdns->otg_cdnsp_regs->override); + reg |= OVERRIDE_SESS_VLD_SEL; + writel(reg, &cdns->otg_cdnsp_regs->override); +} +EXPORT_SYMBOL_GPL(cdns_clear_vbus); + +void cdns_set_vbus(struct cdns *cdns) +{ + u32 reg; + + if (cdns->version != CDNSP_CONTROLLER_V2 || cdns->no_drd) + return; + + reg = readl(&cdns->otg_cdnsp_regs->override); + reg &= ~OVERRIDE_SESS_VLD_SEL; + writel(reg, &cdns->otg_cdnsp_regs->override); +} +EXPORT_SYMBOL_GPL(cdns_set_vbus); + +bool cdns_is_host(struct cdns *cdns) +{ + if (cdns->dr_mode == USB_DR_MODE_HOST) + return true; + else if (cdns_get_id(cdns) == CDNS3_ID_HOST) + return true; + + return false; +} + +bool cdns_is_device(struct cdns *cdns) +{ + if (cdns->dr_mode == USB_DR_MODE_PERIPHERAL) + return true; + else if (cdns->dr_mode == USB_DR_MODE_OTG) + if (cdns_get_id(cdns) == CDNS3_ID_PERIPHERAL) + return true; + + return false; +} + +/** + * cdns_otg_disable_irq - Disable all OTG interrupts + * @cdns: Pointer to controller context structure + */ +static void cdns_otg_disable_irq(struct cdns *cdns) +{ + if (cdns->version) + writel(0, &cdns->otg_irq_regs->ien); +} + +/** + * cdns_otg_enable_irq - enable id and sess_valid interrupts + * @cdns: Pointer to controller context structure + */ +static void cdns_otg_enable_irq(struct cdns *cdns) +{ + writel(OTGIEN_ID_CHANGE_INT | OTGIEN_VBUSVALID_RISE_INT | + OTGIEN_VBUSVALID_FALL_INT, &cdns->otg_irq_regs->ien); +} + +/** + * cdns_drd_host_on - start host. + * @cdns: Pointer to controller context structure. + * + * Returns 0 on success otherwise negative errno. + */ +int cdns_drd_host_on(struct cdns *cdns) +{ + u32 val, ready_bit; + int ret = 0; + + if (cdns->no_drd) + goto phy_set; + + /* Enable host mode. */ + writel(OTGCMD_HOST_BUS_REQ | OTGCMD_OTG_DIS, + &cdns->otg_regs->cmd); + + if (cdns->version == CDNSP_CONTROLLER_V2) + ready_bit = OTGSTS_CDNSP_XHCI_READY; + else + ready_bit = OTGSTS_CDNS3_XHCI_READY; + + dev_dbg(cdns->dev, "Waiting till Host mode is turned on\n"); + ret = readl_poll_timeout_atomic(&cdns->otg_regs->sts, val, + val & ready_bit, 1, 100000); + + if (ret) + dev_err(cdns->dev, "timeout waiting for xhci_ready\n"); + +phy_set: + phy_set_mode(cdns->usb2_phy, PHY_MODE_USB_HOST); + phy_set_mode(cdns->usb3_phy, PHY_MODE_USB_HOST); + return ret; +} + +/** + * cdns_drd_host_off - stop host. + * @cdns: Pointer to controller context structure. + */ +void cdns_drd_host_off(struct cdns *cdns) +{ + u32 val; + + if (cdns->no_drd) + goto phy_set; + + writel(OTGCMD_HOST_BUS_DROP | OTGCMD_DEV_BUS_DROP | + OTGCMD_DEV_POWER_OFF | OTGCMD_HOST_POWER_OFF, + &cdns->otg_regs->cmd); + + /* Waiting till H_IDLE state.*/ + readl_poll_timeout_atomic(&cdns->otg_regs->state, val, + !(val & OTGSTATE_HOST_STATE_MASK), + 1, 2000000); + +phy_set: + phy_set_mode(cdns->usb2_phy, PHY_MODE_INVALID); + phy_set_mode(cdns->usb3_phy, PHY_MODE_INVALID); +} + +/** + * cdns_drd_gadget_on - start gadget. + * @cdns: Pointer to controller context structure. + * + * Returns 0 on success otherwise negative errno + */ +int cdns_drd_gadget_on(struct cdns *cdns) +{ + u32 reg = OTGCMD_OTG_DIS; + u32 ready_bit; + int ret, val; + + if (cdns->no_drd) + goto phy_set; + + /* switch OTG core */ + writel(OTGCMD_DEV_BUS_REQ | reg, &cdns->otg_regs->cmd); + + dev_dbg(cdns->dev, "Waiting till Device mode is turned on\n"); + + if (cdns->version == CDNSP_CONTROLLER_V2) + ready_bit = OTGSTS_CDNSP_DEV_READY; + else + ready_bit = OTGSTS_CDNS3_DEV_READY; + + ret = readl_poll_timeout_atomic(&cdns->otg_regs->sts, val, + val & ready_bit, 1, 100000); + if (ret) { + dev_err(cdns->dev, "timeout waiting for dev_ready\n"); + return ret; + } + +phy_set: + phy_set_mode(cdns->usb2_phy, PHY_MODE_USB_DEVICE); + phy_set_mode(cdns->usb3_phy, PHY_MODE_USB_DEVICE); + return 0; +} +EXPORT_SYMBOL_GPL(cdns_drd_gadget_on); + +/** + * cdns_drd_gadget_off - stop gadget. + * @cdns: Pointer to controller context structure. + */ +void cdns_drd_gadget_off(struct cdns *cdns) +{ + u32 val; + + if (cdns->no_drd) + goto phy_set; + + /* + * Driver should wait at least 10us after disabling Device + * before turning-off Device (DEV_BUS_DROP). + */ + usleep_range(20, 30); + writel(OTGCMD_HOST_BUS_DROP | OTGCMD_DEV_BUS_DROP | + OTGCMD_DEV_POWER_OFF | OTGCMD_HOST_POWER_OFF, + &cdns->otg_regs->cmd); + /* Waiting till DEV_IDLE state.*/ + readl_poll_timeout_atomic(&cdns->otg_regs->state, val, + !(val & OTGSTATE_DEV_STATE_MASK), + 1, 2000000); + +phy_set: + phy_set_mode(cdns->usb2_phy, PHY_MODE_INVALID); + phy_set_mode(cdns->usb3_phy, PHY_MODE_INVALID); +} +EXPORT_SYMBOL_GPL(cdns_drd_gadget_off); + +/** + * cdns_init_otg_mode - initialize drd controller + * @cdns: Pointer to controller context structure + * + * Returns 0 on success otherwise negative errno + */ +static int cdns_init_otg_mode(struct cdns *cdns) +{ + int ret; + + cdns_otg_disable_irq(cdns); + /* clear all interrupts */ + writel(~0, &cdns->otg_irq_regs->ivect); + + ret = cdns_set_mode(cdns, USB_DR_MODE_OTG); + if (ret) + return ret; + + cdns_otg_enable_irq(cdns); + + return 0; +} + +/** + * cdns_drd_update_mode - initialize mode of operation + * @cdns: Pointer to controller context structure + * + * Returns 0 on success otherwise negative errno + */ +int cdns_drd_update_mode(struct cdns *cdns) +{ + int ret; + + switch (cdns->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + ret = cdns_set_mode(cdns, USB_DR_MODE_PERIPHERAL); + break; + case USB_DR_MODE_HOST: + ret = cdns_set_mode(cdns, USB_DR_MODE_HOST); + break; + case USB_DR_MODE_OTG: + ret = cdns_init_otg_mode(cdns); + break; + default: + dev_err(cdns->dev, "Unsupported mode of operation %d\n", + cdns->dr_mode); + return -EINVAL; + } + + return ret; +} + +static irqreturn_t cdns_drd_thread_irq(int irq, void *data) +{ + struct cdns *cdns = data; + + cdns_hw_role_switch(cdns); + + return IRQ_HANDLED; +} + +/** + * cdns_drd_irq - interrupt handler for OTG events + * + * @irq: irq number for cdns core device + * @data: structure of cdns + * + * Returns IRQ_HANDLED or IRQ_NONE + */ +static irqreturn_t cdns_drd_irq(int irq, void *data) +{ + irqreturn_t ret = IRQ_NONE; + struct cdns *cdns = data; + u32 reg; + + if (cdns->dr_mode != USB_DR_MODE_OTG) + return IRQ_NONE; + + if (cdns->in_lpm) + return ret; + + reg = readl(&cdns->otg_irq_regs->ivect); + + if (!reg) + return IRQ_NONE; + + if (reg & OTGIEN_ID_CHANGE_INT) { + dev_dbg(cdns->dev, "OTG IRQ: new ID: %d\n", + cdns_get_id(cdns)); + + ret = IRQ_WAKE_THREAD; + } + + if (reg & (OTGIEN_VBUSVALID_RISE_INT | OTGIEN_VBUSVALID_FALL_INT)) { + dev_dbg(cdns->dev, "OTG IRQ: new VBUS: %d\n", + cdns_get_vbus(cdns)); + + ret = IRQ_WAKE_THREAD; + } + + writel(~0, &cdns->otg_irq_regs->ivect); + return ret; +} + +int cdns_drd_init(struct cdns *cdns) +{ + void __iomem *regs; + u32 state, reg; + int ret; + + if (cdns->no_drd) { + cdns->dr_mode = usb_get_dr_mode(cdns->dev); + + if (cdns->dr_mode != USB_DR_MODE_HOST && + cdns->dr_mode != USB_DR_MODE_PERIPHERAL) { + dev_err(cdns->dev, "Incorrect dr_mode\n"); + return -EINVAL; + } + + return 0; + } + + regs = devm_ioremap_resource(cdns->dev, &cdns->otg_res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + /* Detection of DRD version. Controller has been released + * in three versions. All are very similar and are software compatible, + * but they have same changes in register maps. + * The first register in oldest version is command register and it's + * read only. Driver should read 0 from it. On the other hand, in v1 + * and v2 the first register contains device ID number which is not + * set to 0. Driver uses this fact to detect the proper version of + * controller. + */ + cdns->otg_v0_regs = regs; + if (!readl(&cdns->otg_v0_regs->cmd)) { + cdns->version = CDNS3_CONTROLLER_V0; + cdns->otg_v1_regs = NULL; + cdns->otg_cdnsp_regs = NULL; + cdns->otg_regs = regs; + cdns->otg_irq_regs = (struct cdns_otg_irq_regs __iomem *) + &cdns->otg_v0_regs->ien; + writel(1, &cdns->otg_v0_regs->simulate); + dev_dbg(cdns->dev, "DRD version v0 (%08x)\n", + readl(&cdns->otg_v0_regs->version)); + } else { + cdns->otg_v0_regs = NULL; + cdns->otg_v1_regs = regs; + cdns->otg_cdnsp_regs = regs; + + cdns->otg_regs = (void __iomem *)&cdns->otg_v1_regs->cmd; + + state = readl(&cdns->otg_cdnsp_regs->did); + + if (OTG_CDNSP_CHECK_DID(state)) { + cdns->otg_irq_regs = (struct cdns_otg_irq_regs __iomem *) + &cdns->otg_cdnsp_regs->ien; + cdns->version = CDNSP_CONTROLLER_V2; + } else if (OTG_CDNS3_CHECK_DID(state)) { + cdns->otg_irq_regs = (struct cdns_otg_irq_regs __iomem *) + &cdns->otg_v1_regs->ien; + writel(1, &cdns->otg_v1_regs->simulate); + + if (cdns->pdata && + (cdns->pdata->quirks & CDNS3_DRD_SUSPEND_RESIDENCY_ENABLE)) { + reg = readl(&cdns->otg_v1_regs->susp_ctrl); + reg |= SUSP_CTRL_SUSPEND_RESIDENCY_ENABLE; + writel(reg, &cdns->otg_v1_regs->susp_ctrl); + } + + cdns->version = CDNS3_CONTROLLER_V1; + } else { + dev_err(cdns->dev, "not supported DID=0x%08x\n", state); + return -EINVAL; + } + + dev_dbg(cdns->dev, "DRD version v1 (ID: %08x, rev: %08x)\n", + readl(&cdns->otg_v1_regs->did), + readl(&cdns->otg_v1_regs->rid)); + } + + state = OTGSTS_STRAP(readl(&cdns->otg_regs->sts)); + + /* Update dr_mode according to STRAP configuration. */ + cdns->dr_mode = USB_DR_MODE_OTG; + + if ((cdns->version == CDNSP_CONTROLLER_V2 && + state == OTGSTS_CDNSP_STRAP_HOST) || + (cdns->version != CDNSP_CONTROLLER_V2 && + state == OTGSTS_STRAP_HOST)) { + dev_dbg(cdns->dev, "Controller strapped to HOST\n"); + cdns->dr_mode = USB_DR_MODE_HOST; + } else if ((cdns->version == CDNSP_CONTROLLER_V2 && + state == OTGSTS_CDNSP_STRAP_GADGET) || + (cdns->version != CDNSP_CONTROLLER_V2 && + state == OTGSTS_STRAP_GADGET)) { + dev_dbg(cdns->dev, "Controller strapped to PERIPHERAL\n"); + cdns->dr_mode = USB_DR_MODE_PERIPHERAL; + } + + ret = devm_request_threaded_irq(cdns->dev, cdns->otg_irq, + cdns_drd_irq, + cdns_drd_thread_irq, + IRQF_SHARED, + dev_name(cdns->dev), cdns); + if (ret) { + dev_err(cdns->dev, "couldn't get otg_irq\n"); + return ret; + } + + state = readl(&cdns->otg_regs->sts); + if (OTGSTS_OTG_NRDY(state)) { + dev_err(cdns->dev, "Cadence USB3 OTG device not ready\n"); + return -ENODEV; + } + + return 0; +} + +int cdns_drd_exit(struct cdns *cdns) +{ + if (cdns->no_drd) + return 0; + + cdns_otg_disable_irq(cdns); + + return 0; +} + +/* Indicate the cdns3 core was power lost before */ +bool cdns_power_is_lost(struct cdns *cdns) +{ + if (cdns->no_drd) + return false; + + if (cdns->version == CDNS3_CONTROLLER_V0) { + if (!(readl(&cdns->otg_v0_regs->simulate) & BIT(0))) + return true; + } else { + if (!(readl(&cdns->otg_v1_regs->simulate) & BIT(0))) + return true; + } + return false; +} +EXPORT_SYMBOL_GPL(cdns_power_is_lost); diff --git a/drivers/usb/cdns3/drd.h b/drivers/usb/cdns3/drd.h new file mode 100644 index 000000000..1e2aee14d --- /dev/null +++ b/drivers/usb/cdns3/drd.h @@ -0,0 +1,226 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence USB3 and USBSSP DRD header file. + * + * Copyright (C) 2018-2020 Cadence. + * + * Author: Pawel Laszczak + */ +#ifndef __LINUX_CDNS3_DRD +#define __LINUX_CDNS3_DRD + +#include +#include "core.h" + +/* DRD register interface for version v1 of cdns3 driver. */ +struct cdns3_otg_regs { + __le32 did; + __le32 rid; + __le32 capabilities; + __le32 reserved1; + __le32 cmd; + __le32 sts; + __le32 state; + __le32 reserved2; + __le32 ien; + __le32 ivect; + __le32 refclk; + __le32 tmr; + __le32 reserved3[4]; + __le32 simulate; + __le32 override; + __le32 susp_ctrl; + __le32 phyrst_cfg; + __le32 anasts; + __le32 adp_ramp_time; + __le32 ctrl1; + __le32 ctrl2; +}; + +/* DRD register interface for version v0 of cdns3 driver. */ +struct cdns3_otg_legacy_regs { + __le32 cmd; + __le32 sts; + __le32 state; + __le32 refclk; + __le32 ien; + __le32 ivect; + __le32 reserved1[3]; + __le32 tmr; + __le32 reserved2[2]; + __le32 version; + __le32 capabilities; + __le32 reserved3[2]; + __le32 simulate; + __le32 reserved4[5]; + __le32 ctrl1; +}; + +/* DRD register interface for cdnsp driver */ +struct cdnsp_otg_regs { + __le32 did; + __le32 rid; + __le32 cfgs1; + __le32 cfgs2; + __le32 cmd; + __le32 sts; + __le32 state; + __le32 ien; + __le32 ivect; + __le32 tmr; + __le32 simulate; + __le32 adpbc_sts; + __le32 adp_ramp_time; + __le32 adpbc_ctrl1; + __le32 adpbc_ctrl2; + __le32 override; + __le32 vbusvalid_dbnc_cfg; + __le32 sessvalid_dbnc_cfg; + __le32 susp_timing_ctrl; +}; + +/* CDNSP driver supports 0x000403xx Cadence USB controller family. */ +#define OTG_CDNSP_CHECK_DID(did) (((did) & GENMASK(31, 8)) == 0x00040300) + +/* CDNS3 driver supports 0x000402xx Cadence USB controller family. */ +#define OTG_CDNS3_CHECK_DID(did) (((did) & GENMASK(31, 8)) == 0x00040200) + +/* + * Common registers interface for both CDNS3 and CDNSP version of DRD. + */ +struct cdns_otg_common_regs { + __le32 cmd; + __le32 sts; + __le32 state; +}; + +/* + * Interrupt related registers. This registers are mapped in different + * location for CDNSP controller. + */ +struct cdns_otg_irq_regs { + __le32 ien; + __le32 ivect; +}; + +/* CDNS_RID - bitmasks */ +#define CDNS_RID(p) ((p) & GENMASK(15, 0)) + +/* CDNS_VID - bitmasks */ +#define CDNS_DID(p) ((p) & GENMASK(31, 0)) + +/* OTGCMD - bitmasks */ +/* "Request the bus for Device mode. */ +#define OTGCMD_DEV_BUS_REQ BIT(0) +/* Request the bus for Host mode */ +#define OTGCMD_HOST_BUS_REQ BIT(1) +/* Enable OTG mode. */ +#define OTGCMD_OTG_EN BIT(2) +/* Disable OTG mode */ +#define OTGCMD_OTG_DIS BIT(3) +/*"Configure OTG as A-Device. */ +#define OTGCMD_A_DEV_EN BIT(4) +/*"Configure OTG as A-Device. */ +#define OTGCMD_A_DEV_DIS BIT(5) +/* Drop the bus for Device mod e. */ +#define OTGCMD_DEV_BUS_DROP BIT(8) +/* Drop the bus for Host mode*/ +#define OTGCMD_HOST_BUS_DROP BIT(9) +/* Power Down USBSS-DEV - only for CDNS3.*/ +#define OTGCMD_DEV_POWER_OFF BIT(11) +/* Power Down CDNSXHCI - only for CDNS3. */ +#define OTGCMD_HOST_POWER_OFF BIT(12) + +/* OTGIEN - bitmasks */ +/* ID change interrupt enable */ +#define OTGIEN_ID_CHANGE_INT BIT(0) +/* Vbusvalid fall detected interrupt enable.*/ +#define OTGIEN_VBUSVALID_RISE_INT BIT(4) +/* Vbusvalid fall detected interrupt enable */ +#define OTGIEN_VBUSVALID_FALL_INT BIT(5) + +/* OTGSTS - bitmasks */ +/* + * Current value of the ID pin. It is only valid when idpullup in + * OTGCTRL1_TYPE register is set to '1'. + */ +#define OTGSTS_ID_VALUE BIT(0) +/* Current value of the vbus_valid */ +#define OTGSTS_VBUS_VALID BIT(1) +/* Current value of the b_sess_vld */ +#define OTGSTS_SESSION_VALID BIT(2) +/*Device mode is active*/ +#define OTGSTS_DEV_ACTIVE BIT(3) +/* Host mode is active. */ +#define OTGSTS_HOST_ACTIVE BIT(4) +/* OTG Controller not ready. */ +#define OTGSTS_OTG_NRDY_MASK BIT(11) +#define OTGSTS_OTG_NRDY(p) ((p) & OTGSTS_OTG_NRDY_MASK) +/* + * Value of the strap pins for: + * CDNS3: + * 000 - no default configuration + * 010 - Controller initiall configured as Host + * 100 - Controller initially configured as Device + * CDNSP: + * 000 - No default configuration. + * 010 - Controller initiall configured as Host. + * 100 - Controller initially configured as Device. + */ +#define OTGSTS_STRAP(p) (((p) & GENMASK(14, 12)) >> 12) +#define OTGSTS_STRAP_NO_DEFAULT_CFG 0x00 +#define OTGSTS_STRAP_HOST_OTG 0x01 +#define OTGSTS_STRAP_HOST 0x02 +#define OTGSTS_STRAP_GADGET 0x04 +#define OTGSTS_CDNSP_STRAP_HOST 0x01 +#define OTGSTS_CDNSP_STRAP_GADGET 0x02 + +/* Host mode is turned on. */ +#define OTGSTS_CDNS3_XHCI_READY BIT(26) +#define OTGSTS_CDNSP_XHCI_READY BIT(27) + +/* "Device mode is turned on .*/ +#define OTGSTS_CDNS3_DEV_READY BIT(27) +#define OTGSTS_CDNSP_DEV_READY BIT(26) + +/* OTGSTATE- bitmasks */ +#define OTGSTATE_DEV_STATE_MASK GENMASK(2, 0) +#define OTGSTATE_HOST_STATE_MASK GENMASK(5, 3) +#define OTGSTATE_HOST_STATE_IDLE 0x0 +#define OTGSTATE_HOST_STATE_VBUS_FALL 0x7 +#define OTGSTATE_HOST_STATE(p) (((p) & OTGSTATE_HOST_STATE_MASK) >> 3) + +/* OTGREFCLK - bitmasks */ +#define OTGREFCLK_STB_CLK_SWITCH_EN BIT(31) + +/* SUPS_CTRL - bitmasks */ +#define SUSP_CTRL_SUSPEND_RESIDENCY_ENABLE BIT(17) + +/* OVERRIDE - bitmasks */ +#define OVERRIDE_IDPULLUP BIT(0) +/* Only for CDNS3_CONTROLLER_V0 version */ +#define OVERRIDE_IDPULLUP_V0 BIT(24) +/* Vbusvalid/Sesvalid override select. */ +#define OVERRIDE_SESS_VLD_SEL BIT(10) + +/* PHYRST_CFG - bitmasks */ +#define PHYRST_CFG_PHYRST_A_ENABLE BIT(0) + +#define CDNS3_ID_PERIPHERAL 1 +#define CDNS3_ID_HOST 0 + +bool cdns_is_host(struct cdns *cdns); +bool cdns_is_device(struct cdns *cdns); +int cdns_get_id(struct cdns *cdns); +int cdns_get_vbus(struct cdns *cdns); +void cdns_clear_vbus(struct cdns *cdns); +void cdns_set_vbus(struct cdns *cdns); +int cdns_drd_init(struct cdns *cdns); +int cdns_drd_exit(struct cdns *cdns); +int cdns_drd_update_mode(struct cdns *cdns); +int cdns_drd_gadget_on(struct cdns *cdns); +void cdns_drd_gadget_off(struct cdns *cdns); +int cdns_drd_host_on(struct cdns *cdns); +void cdns_drd_host_off(struct cdns *cdns); +bool cdns_power_is_lost(struct cdns *cdns); +#endif /* __LINUX_CDNS3_DRD */ diff --git a/drivers/usb/cdns3/gadget-export.h b/drivers/usb/cdns3/gadget-export.h new file mode 100644 index 000000000..60c3177db --- /dev/null +++ b/drivers/usb/cdns3/gadget-export.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence USBSS and USBSSP DRD Driver - Gadget Export APIs. + * + * Copyright (C) 2017 NXP + * Copyright (C) 2017-2018 NXP + * + * Authors: Peter Chen + */ +#ifndef __LINUX_CDNS3_GADGET_EXPORT +#define __LINUX_CDNS3_GADGET_EXPORT + +#if IS_ENABLED(CONFIG_USB_CDNS3_GADGET) + +int cdnsp_gadget_init(struct cdns *cdns); +int cdns3_gadget_init(struct cdns *cdns); +#else + +static inline int cdnsp_gadget_init(struct cdns *cdns) +{ + return -ENXIO; +} + +static inline int cdns3_gadget_init(struct cdns *cdns) +{ + return -ENXIO; +} + +#endif /* CONFIG_USB_CDNS3_GADGET */ + +#endif /* __LINUX_CDNS3_GADGET_EXPORT */ diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h new file mode 100644 index 000000000..34fd1f1ad --- /dev/null +++ b/drivers/usb/cdns3/host-export.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence USBSS and USBSSP DRD Driver - Host Export APIs + * + * Copyright (C) 2017-2018 NXP + * + * Authors: Peter Chen + */ +#ifndef __LINUX_CDNS3_HOST_EXPORT +#define __LINUX_CDNS3_HOST_EXPORT + +#if IS_ENABLED(CONFIG_USB_CDNS3_HOST) + +int cdns_host_init(struct cdns *cdns); + +#else + +static inline int cdns_host_init(struct cdns *cdns) +{ + return -ENXIO; +} + +static inline void cdns_host_exit(struct cdns *cdns) { } + +#endif /* CONFIG_USB_CDNS3_HOST */ + +#endif /* __LINUX_CDNS3_HOST_EXPORT */ diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c new file mode 100644 index 000000000..f0df114c2 --- /dev/null +++ b/drivers/usb/cdns3/host.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBSS and USBSSP DRD Driver - host side + * + * Copyright (C) 2018-2019 Cadence Design Systems. + * Copyright (C) 2017-2018 NXP + * + * Authors: Peter Chen + * Pawel Laszczak + */ + +#include +#include +#include "core.h" +#include "drd.h" +#include "host-export.h" +#include +#include "../host/xhci.h" +#include "../host/xhci-plat.h" + +/* + * The XECP_PORT_CAP_REG and XECP_AUX_CTRL_REG1 exist only + * in Cadence USB3 dual-role controller, so it can't be used + * with Cadence CDNSP dual-role controller. + */ +#define XECP_PORT_CAP_REG 0x8000 +#define XECP_AUX_CTRL_REG1 0x8120 + +#define CFG_RXDET_P3_EN BIT(15) +#define LPM_2_STB_SWITCH_EN BIT(25) + +static void xhci_cdns3_plat_start(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + u32 value; + + /* set usbcmd.EU3S */ + value = readl(&xhci->op_regs->command); + value |= CMD_PM_INDEX; + writel(value, &xhci->op_regs->command); + + if (hcd->regs) { + value = readl(hcd->regs + XECP_AUX_CTRL_REG1); + value |= CFG_RXDET_P3_EN; + writel(value, hcd->regs + XECP_AUX_CTRL_REG1); + + value = readl(hcd->regs + XECP_PORT_CAP_REG); + value |= LPM_2_STB_SWITCH_EN; + writel(value, hcd->regs + XECP_PORT_CAP_REG); + } +} + +static int xhci_cdns3_resume_quirk(struct usb_hcd *hcd) +{ + xhci_cdns3_plat_start(hcd); + return 0; +} + +static const struct xhci_plat_priv xhci_plat_cdns3_xhci = { + .quirks = XHCI_SKIP_PHY_INIT | XHCI_AVOID_BEI, + .plat_start = xhci_cdns3_plat_start, + .resume_quirk = xhci_cdns3_resume_quirk, +}; + +static const struct xhci_plat_priv xhci_plat_cdnsp_xhci = { + .quirks = XHCI_CDNS_SCTX_QUIRK, +}; + +static int __cdns_host_init(struct cdns *cdns) +{ + struct platform_device *xhci; + int ret; + struct usb_hcd *hcd; + + cdns_drd_host_on(cdns); + + xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO); + if (!xhci) { + dev_err(cdns->dev, "couldn't allocate xHCI device\n"); + return -ENOMEM; + } + + xhci->dev.parent = cdns->dev; + cdns->host_dev = xhci; + + ret = platform_device_add_resources(xhci, cdns->xhci_res, + CDNS_XHCI_RESOURCES_NUM); + if (ret) { + dev_err(cdns->dev, "couldn't add resources to xHCI device\n"); + goto err1; + } + + if (cdns->version < CDNSP_CONTROLLER_V2) + cdns->xhci_plat_data = kmemdup(&xhci_plat_cdns3_xhci, + sizeof(struct xhci_plat_priv), GFP_KERNEL); + else + cdns->xhci_plat_data = kmemdup(&xhci_plat_cdnsp_xhci, + sizeof(struct xhci_plat_priv), GFP_KERNEL); + + if (!cdns->xhci_plat_data) { + ret = -ENOMEM; + goto err1; + } + + if (cdns->pdata && (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW)) + cdns->xhci_plat_data->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW; + + ret = platform_device_add_data(xhci, cdns->xhci_plat_data, + sizeof(struct xhci_plat_priv)); + if (ret) + goto free_memory; + + ret = platform_device_add(xhci); + if (ret) { + dev_err(cdns->dev, "failed to register xHCI device\n"); + goto free_memory; + } + + /* Glue needs to access xHCI region register for Power management */ + hcd = platform_get_drvdata(xhci); + if (hcd) + cdns->xhci_regs = hcd->regs; + + return 0; + +free_memory: + kfree(cdns->xhci_plat_data); +err1: + platform_device_put(xhci); + return ret; +} + +static void cdns_host_exit(struct cdns *cdns) +{ + kfree(cdns->xhci_plat_data); + platform_device_unregister(cdns->host_dev); + cdns->host_dev = NULL; + cdns_drd_host_off(cdns); +} + +static int cdns_host_resume(struct cdns *cdns, bool power_lost) +{ + struct usb_hcd *hcd = platform_get_drvdata(cdns->host_dev); + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + priv->power_lost = power_lost; + + return 0; +} + +int cdns_host_init(struct cdns *cdns) +{ + struct cdns_role_driver *rdrv; + + rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + rdrv->start = __cdns_host_init; + rdrv->stop = cdns_host_exit; + rdrv->resume = cdns_host_resume; + rdrv->state = CDNS_ROLE_STATE_INACTIVE; + rdrv->name = "host"; + + cdns->roles[USB_ROLE_HOST] = rdrv; + + return 0; +} diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig new file mode 100644 index 000000000..bab45bc62 --- /dev/null +++ b/drivers/usb/chipidea/Kconfig @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: GPL-2.0 + +config USB_CHIPIDEA + tristate "ChipIdea Highspeed Dual Role Controller" + depends on ((USB_EHCI_HCD && USB_GADGET) || (USB_EHCI_HCD && !USB_GADGET) || (!USB_EHCI_HCD && USB_GADGET)) && HAS_DMA + select EXTCON + select RESET_CONTROLLER + select USB_ULPI_BUS + select USB_ROLE_SWITCH + select USB_TEGRA_PHY if ARCH_TEGRA + help + Say Y here if your system has a dual role high speed USB + controller based on ChipIdea silicon IP. It supports: + Dual-role switch (ID, OTG FSM, sysfs), Host-only, and + Peripheral-only. + + 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 + help + Say Y here to enable device controller functionality of the + ChipIdea driver. + +config USB_CHIPIDEA_HOST + bool "ChipIdea host controller" + depends on USB_EHCI_HCD + select USB_EHCI_ROOT_HUB_TT + help + Say Y here to enable host controller functionality of the + ChipIdea driver. + +config USB_CHIPIDEA_PCI + tristate "Enable PCI glue driver" if EXPERT + depends on USB_PCI + depends on NOP_USB_XCEIV + default USB_CHIPIDEA + +config USB_CHIPIDEA_MSM + tristate "Enable MSM hsusb glue driver" if EXPERT + default USB_CHIPIDEA + +config USB_CHIPIDEA_NPCM + tristate "Enable NPCM hsusb glue driver" if EXPERT + default USB_CHIPIDEA + +config USB_CHIPIDEA_IMX + tristate "Enable i.MX USB glue driver" if EXPERT + depends on OF + default USB_CHIPIDEA + +config USB_CHIPIDEA_GENERIC + tristate "Enable generic USB2 glue driver" if EXPERT + default USB_CHIPIDEA + +config USB_CHIPIDEA_TEGRA + tristate "Enable Tegra USB glue driver" if EXPERT + depends on OF + default USB_CHIPIDEA + +endif diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile new file mode 100644 index 000000000..718cb2460 --- /dev/null +++ b/drivers/usb/chipidea/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 + +# define_trace.h needs to know how to find our header +CFLAGS_trace.o := -I$(src) +obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc.o + +ci_hdrc-y := core.o otg.o debug.o ulpi.o +ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC) += udc.o trace.o +ci_hdrc-$(CONFIG_USB_CHIPIDEA_HOST) += host.o +ci_hdrc-$(CONFIG_USB_OTG_FSM) += otg_fsm.o + +# Glue/Bridge layers go here + +obj-$(CONFIG_USB_CHIPIDEA_GENERIC) += ci_hdrc_usb2.o +obj-$(CONFIG_USB_CHIPIDEA_MSM) += ci_hdrc_msm.o +obj-$(CONFIG_USB_CHIPIDEA_NPCM) += ci_hdrc_npcm.o +obj-$(CONFIG_USB_CHIPIDEA_PCI) += ci_hdrc_pci.o +obj-$(CONFIG_USB_CHIPIDEA_IMX) += usbmisc_imx.o ci_hdrc_imx.o +obj-$(CONFIG_USB_CHIPIDEA_TEGRA) += ci_hdrc_tegra.o diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h new file mode 100644 index 000000000..b1540ce93 --- /dev/null +++ b/drivers/usb/chipidea/bits.h @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * bits.h - register bits of the ChipIdea USB IP core + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + */ + +#ifndef __DRIVERS_USB_CHIPIDEA_BITS_H +#define __DRIVERS_USB_CHIPIDEA_BITS_H + +#include + +/* + * ID + * For 1.x revision, bit24 - bit31 are reserved + * For 2.x revision, bit25 - bit28 are 0x2 + */ +#define TAG (0x1F << 16) +#define REVISION (0xF << 21) +#define VERSION (0xF << 25) +#define CIVERSION (0x7 << 29) + +/* SBUSCFG */ +#define AHBBRST_MASK 0x7 + +/* 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) + +/* TTCTRL */ +#define TTCTRL_TTHA_MASK (0x7fUL << 24) +/* Set non-zero value for internal TT Hub address representation */ +#define TTCTRL_TTHA (0x7fUL << 24) + +/* BURSTSIZE */ +#define RX_BURST_MASK 0xff +#define TX_BURST_MASK 0xff00 + +/* PORTSC */ +#define PORTSC_CCS BIT(0) +#define PORTSC_CSC BIT(1) +#define PORTSC_PEC BIT(3) +#define PORTSC_OCC BIT(5) +#define PORTSC_FPR BIT(6) +#define PORTSC_SUSP BIT(7) +#define PORTSC_HSP BIT(9) +#define PORTSC_PP BIT(12) +#define PORTSC_PTC (0x0FUL << 16) +#define PORTSC_WKCN BIT(20) +#define PORTSC_PHCD(d) ((d) ? BIT(22) : BIT(23)) +/* PTS and PTW for non lpm version only */ +#define PORTSC_PFSC BIT(24) +#define PORTSC_PTS(d) \ + (u32)((((d) & 0x3) << 30) | (((d) & 0x4) ? BIT(25) : 0)) +#define PORTSC_PTW BIT(28) +#define PORTSC_STS BIT(29) + +#define PORTSC_W1C_BITS \ + (PORTSC_CSC | PORTSC_PEC | PORTSC_OCC) + +/* DEVLC */ +#define DEVLC_PFSC BIT(23) +#define DEVLC_PSPD (0x03UL << 25) +#define DEVLC_PSPD_HS (0x02UL << 25) +#define DEVLC_PTW BIT(27) +#define DEVLC_STS BIT(28) +#define DEVLC_PTS(d) (u32)(((d) & 0x7) << 29) + +/* Encoding for DEVLC_PTS and PORTSC_PTS */ +#define PTS_UTMI 0 +#define PTS_ULPI 2 +#define PTS_SERIAL 3 +#define PTS_HSIC 4 + +/* OTGSC */ +#define OTGSC_IDPU BIT(5) +#define OTGSC_HADP BIT(6) +#define OTGSC_HABA BIT(7) +#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_1MSIS BIT(21) +#define OTGSC_DPIS BIT(22) +#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) +#define OTGSC_1MSIE BIT(29) +#define OTGSC_DPIE BIT(30) +#define OTGSC_INT_EN_BITS (OTGSC_IDIE | OTGSC_AVVIE | OTGSC_ASVIE \ + | OTGSC_BSVIE | OTGSC_BSEIE | OTGSC_1MSIE \ + | OTGSC_DPIE) +#define OTGSC_INT_STATUS_BITS (OTGSC_IDIS | OTGSC_AVVIS | OTGSC_ASVIS \ + | OTGSC_BSVIS | OTGSC_BSEIS | OTGSC_1MSIS \ + | OTGSC_DPIS) + +/* 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 000000000..97437de52 --- /dev/null +++ b/drivers/usb/chipidea/ci.h @@ -0,0 +1,495 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ci.h - common structures, functions, and macros of the ChipIdea driver + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + */ + +#ifndef __DRIVERS_USB_CHIPIDEA_CI_H +#define __DRIVERS_USB_CHIPIDEA_CI_H + +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************** + * DEFINE + *****************************************************************************/ +#define TD_PAGE_COUNT 5 +#define CI_HDRC_PAGE_SIZE 4096ul /* page size for TD's */ +#define ENDPT_MAX 32 +#define CI_MAX_REQ_SIZE (4 * CI_HDRC_PAGE_SIZE) +#define CI_MAX_BUF_SIZE (TD_PAGE_COUNT * CI_HDRC_PAGE_SIZE) + +/****************************************************************************** + * REGISTERS + *****************************************************************************/ +/* Identification Registers */ +#define ID_ID 0x0 +#define ID_HWGENERAL 0x4 +#define ID_HWHOST 0x8 +#define ID_HWDEVICE 0xc +#define ID_HWTXBUF 0x10 +#define ID_HWRXBUF 0x14 +#define ID_SBUSCFG 0x90 + +/* register indices */ +enum ci_hw_regs { + CAP_CAPLENGTH, + CAP_HCCPARAMS, + CAP_DCCPARAMS, + CAP_TESTMODE, + CAP_LAST = CAP_TESTMODE, + OP_USBCMD, + OP_USBSTS, + OP_USBINTR, + OP_FRINDEX, + OP_DEVICEADDR, + OP_ENDPTLISTADDR, + OP_TTCTRL, + OP_BURSTSIZE, + OP_ULPI_VIEWPORT, + 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, +}; + +/****************************************************************************** + * STRUCTURES + *****************************************************************************/ +/** + * struct ci_hw_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 ci_hw_ep { + struct usb_ep ep; + u8 dir; + u8 num; + u8 type; + char name[16]; + struct { + struct list_head queue; + struct ci_hw_qh *ptr; + dma_addr_t dma; + } qh; + int wedge; + + /* global resources */ + struct ci_hdrc *ci; + spinlock_t *lock; + struct dma_pool *td_pool; + struct td_node *pending_td; +}; + +enum ci_role { + CI_ROLE_HOST = 0, + CI_ROLE_GADGET, + CI_ROLE_END, +}; + +enum ci_revision { + CI_REVISION_1X = 10, /* Revision 1.x */ + CI_REVISION_20 = 20, /* Revision 2.0 */ + CI_REVISION_21, /* Revision 2.1 */ + CI_REVISION_22, /* Revision 2.2 */ + CI_REVISION_23, /* Revision 2.3 */ + CI_REVISION_24, /* Revision 2.4 */ + CI_REVISION_25, /* Revision 2.5 */ + CI_REVISION_25_PLUS, /* Revision above than 2.5 */ + CI_REVISION_UNKNOWN = 99, /* Unknown Revision */ +}; + +/** + * struct ci_role_driver - host/gadget role driver + * @start: start this role + * @stop: stop this role + * @suspend: system suspend handler for this role + * @resume: system resume handler for this role + * @irq: irq handler for this role + * @name: role name string (host/gadget) + */ +struct ci_role_driver { + int (*start)(struct ci_hdrc *); + void (*stop)(struct ci_hdrc *); + void (*suspend)(struct ci_hdrc *ci); + void (*resume)(struct ci_hdrc *ci, bool power_lost); + irqreturn_t (*irq)(struct ci_hdrc *); + 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[OP_LAST + 1]; +}; + +/** + * struct ci_hdrc - 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 + * @fsm: otg finite state machine + * @otg_fsm_hrtimer: hrtimer for otg fsm timers + * @hr_timeouts: time out list for active otg fsm timers + * @enabled_otg_timer_bits: bits of enabled otg timers + * @next_otg_timer: next nearest enabled timer to be expired + * @work: work for role changing + * @power_lost_work: work for power lost handling + * @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 + * @resume_state: save the state of gadget suspend from + * @hw_ep_max: total number of endpoints supported by hardware + * @ci_hw_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 + * @ulpi: pointer to ULPI device, if any + * @ulpi_ops: ULPI read/write ops for this device + * @phy: pointer to PHY, if any + * @usb_phy: pointer to USB PHY, if any and if using the USB PHY framework + * @hcd: pointer to usb_hcd for ehci host driver + * @id_event: indicates there is an id event, and handled at ci_otg_work + * @b_sess_valid_event: indicates there is a vbus event, and handled + * at ci_otg_work + * @imx28_write_fix: Freescale imx28 needs swp instruction for writing + * @supports_runtime_pm: if runtime pm is supported + * @in_lpm: if the core in low power mode + * @wakeup_int: if wakeup interrupt occur + * @rev: The revision number for controller + * @mutex: protect code from concorrent running when doing role switch + */ +struct ci_hdrc { + 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 usb_otg otg; + struct otg_fsm fsm; + struct hrtimer otg_fsm_hrtimer; + ktime_t hr_timeouts[NUM_OTG_FSM_TIMERS]; + unsigned enabled_otg_timer_bits; + enum otg_fsm_timer next_otg_timer; + struct usb_role_switch *role_switch; + struct work_struct work; + struct work_struct power_lost_work; + struct workqueue_struct *wq; + + struct dma_pool *qh_pool; + struct dma_pool *td_pool; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + enum usb_device_state resume_state; + unsigned hw_ep_max; + struct ci_hw_ep ci_hw_ep[ENDPT_MAX]; + u32 ep0_dir; + struct ci_hw_ep *ep0out, *ep0in; + + struct usb_request *status; + bool setaddr; + u8 address; + u8 remote_wakeup; + u8 suspended; + u8 test_mode; + + struct ci_hdrc_platform_data *platdata; + int vbus_active; + struct ulpi *ulpi; + struct ulpi_ops ulpi_ops; + struct phy *phy; + /* old usb_phy interface */ + struct usb_phy *usb_phy; + struct usb_hcd *hcd; + bool id_event; + bool b_sess_valid_event; + bool imx28_write_fix; + bool has_portsc_pec_bug; + bool has_short_pkt_limit; + bool supports_runtime_pm; + bool in_lpm; + bool wakeup_int; + enum ci_revision rev; + struct mutex mutex; +}; + +static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci) +{ + BUG_ON(ci->role >= CI_ROLE_END || !ci->roles[ci->role]); + return ci->roles[ci->role]; +} + +static inline int ci_role_start(struct ci_hdrc *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) + return ret; + + ci->role = role; + + if (ci->usb_phy) { + if (role == CI_ROLE_HOST) + usb_phy_set_event(ci->usb_phy, USB_EVENT_ID); + else + /* in device mode but vbus is invalid*/ + usb_phy_set_event(ci->usb_phy, USB_EVENT_NONE); + } + + return ret; +} + +static inline void ci_role_stop(struct ci_hdrc *ci) +{ + enum ci_role role = ci->role; + + if (role == CI_ROLE_END) + return; + + ci->role = CI_ROLE_END; + + ci->roles[role]->stop(ci); + + if (ci->usb_phy) + usb_phy_set_event(ci->usb_phy, USB_EVENT_NONE); +} + +static inline enum usb_role ci_role_to_usb_role(struct ci_hdrc *ci) +{ + if (ci->role == CI_ROLE_HOST) + return USB_ROLE_HOST; + else if (ci->role == CI_ROLE_GADGET && ci->vbus_active) + return USB_ROLE_DEVICE; + else + return USB_ROLE_NONE; +} + +static inline enum ci_role usb_role_to_ci_role(enum usb_role role) +{ + if (role == USB_ROLE_HOST) + return CI_ROLE_HOST; + else if (role == USB_ROLE_DEVICE) + return CI_ROLE_GADGET; + else + return CI_ROLE_END; +} + +/** + * hw_read_id_reg: reads from a identification register + * @ci: the controller + * @offset: offset from the beginning of identification registers region + * @mask: bitfield mask + * + * This function returns register contents + */ +static inline u32 hw_read_id_reg(struct ci_hdrc *ci, u32 offset, u32 mask) +{ + return ioread32(ci->hw_bank.abs + offset) & mask; +} + +/** + * hw_write_id_reg: writes to a identification register + * @ci: the controller + * @offset: offset from the beginning of identification registers region + * @mask: bitfield mask + * @data: new value + */ +static inline void hw_write_id_reg(struct ci_hdrc *ci, u32 offset, + u32 mask, u32 data) +{ + if (~mask) + data = (ioread32(ci->hw_bank.abs + offset) & ~mask) + | (data & mask); + + iowrite32(data, ci->hw_bank.abs + offset); +} + +/** + * hw_read: reads from a hw register + * @ci: the controller + * @reg: register index + * @mask: bitfield mask + * + * This function returns register contents + */ +static inline u32 hw_read(struct ci_hdrc *ci, enum ci_hw_regs reg, u32 mask) +{ + return ioread32(ci->hw_bank.regmap[reg]) & mask; +} + +#ifdef CONFIG_SOC_IMX28 +static inline void imx28_ci_writel(u32 val, volatile void __iomem *addr) +{ + __asm__ ("swp %0, %0, [%1]" : : "r"(val), "r"(addr)); +} +#else +static inline void imx28_ci_writel(u32 val, volatile void __iomem *addr) +{ +} +#endif + +static inline void __hw_write(struct ci_hdrc *ci, u32 val, + void __iomem *addr) +{ + if (ci->imx28_write_fix) + imx28_ci_writel(val, addr); + else + iowrite32(val, addr); +} + +/** + * hw_write: writes to a hw register + * @ci: the controller + * @reg: register index + * @mask: bitfield mask + * @data: new value + */ +static inline void hw_write(struct ci_hdrc *ci, enum ci_hw_regs reg, + u32 mask, u32 data) +{ + if (~mask) + data = (ioread32(ci->hw_bank.regmap[reg]) & ~mask) + | (data & mask); + + __hw_write(ci, data, ci->hw_bank.regmap[reg]); +} + +/** + * hw_test_and_clear: tests & clears a hw register + * @ci: the controller + * @reg: register index + * @mask: bitfield mask + * + * This function returns register contents + */ +static inline u32 hw_test_and_clear(struct ci_hdrc *ci, enum ci_hw_regs reg, + u32 mask) +{ + u32 val = ioread32(ci->hw_bank.regmap[reg]) & mask; + + __hw_write(ci, val, ci->hw_bank.regmap[reg]); + return val; +} + +/** + * hw_test_and_write: tests & writes a hw register + * @ci: the controller + * @reg: register index + * @mask: bitfield mask + * @data: new value + * + * This function returns register contents + */ +static inline u32 hw_test_and_write(struct ci_hdrc *ci, enum ci_hw_regs reg, + u32 mask, u32 data) +{ + u32 val = hw_read(ci, reg, ~0); + + hw_write(ci, reg, mask, data); + return (val & mask) >> __ffs(mask); +} + +/** + * ci_otg_is_fsm_mode: runtime check if otg controller + * is in otg fsm mode. + * + * @ci: chipidea device + */ +static inline bool ci_otg_is_fsm_mode(struct ci_hdrc *ci) +{ +#ifdef CONFIG_USB_OTG_FSM + struct usb_otg_caps *otg_caps = &ci->platdata->ci_otg_caps; + + return ci->is_otg && ci->roles[CI_ROLE_HOST] && + ci->roles[CI_ROLE_GADGET] && (otg_caps->srp_support || + otg_caps->hnp_support || otg_caps->adp_support); +#else + return false; +#endif +} + +int ci_ulpi_init(struct ci_hdrc *ci); +void ci_ulpi_exit(struct ci_hdrc *ci); +int ci_ulpi_resume(struct ci_hdrc *ci); + +u32 hw_read_intr_enable(struct ci_hdrc *ci); + +u32 hw_read_intr_status(struct ci_hdrc *ci); + +int hw_device_reset(struct ci_hdrc *ci); + +int hw_port_test_set(struct ci_hdrc *ci, u8 mode); + +u8 hw_port_test_get(struct ci_hdrc *ci); + +void hw_phymode_configure(struct ci_hdrc *ci); + +void ci_platform_configure(struct ci_hdrc *ci); + +void dbg_create_files(struct ci_hdrc *ci); + +void dbg_remove_files(struct ci_hdrc *ci); +#endif /* __DRIVERS_USB_CHIPIDEA_CI_H */ diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c new file mode 100644 index 000000000..282314eea --- /dev/null +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -0,0 +1,786 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2025 NXP + * Copyright (C) 2012 Marek Vasut + * on behalf of DENX Software Engineering GmbH + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" +#include "ci_hdrc_imx.h" + +struct ci_hdrc_imx_platform_flag { + unsigned int flags; +}; + +static const struct ci_hdrc_imx_platform_flag imx23_usb_data = { + .flags = CI_HDRC_TURN_VBUS_EARLY_ON | + CI_HDRC_DISABLE_STREAMING, +}; + +static const struct ci_hdrc_imx_platform_flag imx27_usb_data = { + .flags = CI_HDRC_DISABLE_STREAMING, +}; + +static const struct ci_hdrc_imx_platform_flag imx28_usb_data = { + .flags = CI_HDRC_IMX28_WRITE_FIX | + CI_HDRC_TURN_VBUS_EARLY_ON | + CI_HDRC_DISABLE_STREAMING, +}; + +static const struct ci_hdrc_imx_platform_flag imx6q_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | + CI_HDRC_TURN_VBUS_EARLY_ON | + CI_HDRC_DISABLE_STREAMING, +}; + +static const struct ci_hdrc_imx_platform_flag imx6sl_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | + CI_HDRC_TURN_VBUS_EARLY_ON | + CI_HDRC_DISABLE_HOST_STREAMING, +}; + +static const struct ci_hdrc_imx_platform_flag imx6sx_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | + CI_HDRC_TURN_VBUS_EARLY_ON | + CI_HDRC_DISABLE_HOST_STREAMING, +}; + +static const struct ci_hdrc_imx_platform_flag imx6ul_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | + CI_HDRC_TURN_VBUS_EARLY_ON | + CI_HDRC_DISABLE_DEVICE_STREAMING, +}; + +static const struct ci_hdrc_imx_platform_flag imx7d_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM, +}; + +static const struct ci_hdrc_imx_platform_flag imx7ulp_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | + CI_HDRC_HAS_PORTSC_PEC_MISSED | + CI_HDRC_PMQOS, +}; + +static const struct ci_hdrc_imx_platform_flag imx8ulp_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | + CI_HDRC_HAS_PORTSC_PEC_MISSED, +}; + +static const struct ci_hdrc_imx_platform_flag imx95_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | CI_HDRC_OUT_BAND_WAKEUP, +}; + +static const struct ci_hdrc_imx_platform_flag s32g_usb_data = { + .flags = CI_HDRC_DISABLE_HOST_STREAMING, +}; + +static const struct of_device_id ci_hdrc_imx_dt_ids[] = { + { .compatible = "fsl,imx23-usb", .data = &imx23_usb_data}, + { .compatible = "fsl,imx28-usb", .data = &imx28_usb_data}, + { .compatible = "fsl,imx27-usb", .data = &imx27_usb_data}, + { .compatible = "fsl,imx6q-usb", .data = &imx6q_usb_data}, + { .compatible = "fsl,imx6sl-usb", .data = &imx6sl_usb_data}, + { .compatible = "fsl,imx6sx-usb", .data = &imx6sx_usb_data}, + { .compatible = "fsl,imx6ul-usb", .data = &imx6ul_usb_data}, + { .compatible = "fsl,imx7d-usb", .data = &imx7d_usb_data}, + { .compatible = "fsl,imx7ulp-usb", .data = &imx7ulp_usb_data}, + { .compatible = "fsl,imx8ulp-usb", .data = &imx8ulp_usb_data}, + { .compatible = "fsl,imx95-usb", .data = &imx95_usb_data}, + { .compatible = "nxp,s32g2-usb", .data = &s32g_usb_data}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids); + +struct ci_hdrc_imx_data { + struct usb_phy *phy; + struct platform_device *ci_pdev; + struct clk *clk; + struct clk *clk_wakeup; + struct imx_usbmisc_data *usbmisc_data; + int wakeup_irq; + bool supports_runtime_pm; + bool override_phy_control; + bool in_lpm; + struct pinctrl *pinctrl; + struct pinctrl_state *pinctrl_hsic_active; + struct regulator *hsic_pad_regulator; + /* SoC before i.mx6 (except imx23/imx28) needs three clks */ + bool need_three_clks; + struct clk *clk_ipg; + struct clk *clk_ahb; + struct clk *clk_per; + /* --------------------------------- */ + struct pm_qos_request pm_qos_req; + const struct ci_hdrc_imx_platform_flag *plat_data; +}; + +/* Common functions shared by usbmisc drivers */ + +static struct imx_usbmisc_data *usbmisc_get_init_data(struct device *dev) +{ + struct platform_device *misc_pdev; + struct device_node *np = dev->of_node; + struct of_phandle_args args; + struct imx_usbmisc_data *data; + int ret; + + /* + * In case the fsl,usbmisc property is not present this device doesn't + * need usbmisc. Return NULL (which is no error here) + */ + if (!of_property_present(np, "fsl,usbmisc")) + return NULL; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return ERR_PTR(-ENOMEM); + + 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); + return ERR_PTR(ret); + } + + data->index = args.args[0]; + + misc_pdev = of_find_device_by_node(args.np); + of_node_put(args.np); + + if (!misc_pdev) + return ERR_PTR(-EPROBE_DEFER); + + if (!platform_get_drvdata(misc_pdev)) { + put_device(&misc_pdev->dev); + return ERR_PTR(-EPROBE_DEFER); + } + data->dev = &misc_pdev->dev; + + /* + * Check the various over current related properties. If over current + * detection is disabled we're not interested in the polarity. + */ + if (of_property_read_bool(np, "disable-over-current")) { + data->disable_oc = 1; + } else if (of_property_read_bool(np, "over-current-active-high")) { + data->oc_pol_active_low = 0; + data->oc_pol_configured = 1; + } else if (of_property_read_bool(np, "over-current-active-low")) { + data->oc_pol_active_low = 1; + data->oc_pol_configured = 1; + } else { + dev_warn(dev, "No over current polarity defined\n"); + } + + data->pwr_pol = of_property_read_bool(np, "power-active-high"); + data->evdo = of_property_read_bool(np, "external-vbus-divider"); + + if (of_usb_get_phy_mode(np) == USBPHY_INTERFACE_MODE_ULPI) + data->ulpi = 1; + + if (of_property_read_u32(np, "samsung,picophy-pre-emp-curr-control", + &data->emp_curr_control)) + data->emp_curr_control = -1; + if (of_property_read_u32(np, "samsung,picophy-dc-vol-level-adjust", + &data->dc_vol_level_adjust)) + data->dc_vol_level_adjust = -1; + if (of_property_read_u32(np, "fsl,picophy-rise-fall-time-adjust", + &data->rise_fall_time_adjust)) + data->rise_fall_time_adjust = -1; + + return data; +} + +/* End of common functions shared by usbmisc drivers*/ +static int imx_get_clks(struct device *dev) +{ + struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); + int ret = 0; + + data->clk_ipg = devm_clk_get(dev, "ipg"); + if (IS_ERR(data->clk_ipg)) { + /* If the platform only needs one primary clock */ + data->clk = devm_clk_get(dev, NULL); + if (IS_ERR(data->clk)) { + ret = PTR_ERR(data->clk); + dev_err(dev, + "Failed to get clks, err=%ld,%ld\n", + PTR_ERR(data->clk), PTR_ERR(data->clk_ipg)); + return ret; + } + /* Get wakeup clock. Not all of the platforms need to + * handle this clock. So make it optional. + */ + data->clk_wakeup = devm_clk_get_optional(dev, "usb_wakeup"); + if (IS_ERR(data->clk_wakeup)) + ret = dev_err_probe(dev, PTR_ERR(data->clk_wakeup), + "Failed to get wakeup clk\n"); + return ret; + } + + data->clk_ahb = devm_clk_get(dev, "ahb"); + if (IS_ERR(data->clk_ahb)) { + ret = PTR_ERR(data->clk_ahb); + dev_err(dev, + "Failed to get ahb clock, err=%d\n", ret); + return ret; + } + + data->clk_per = devm_clk_get(dev, "per"); + if (IS_ERR(data->clk_per)) { + ret = PTR_ERR(data->clk_per); + dev_err(dev, + "Failed to get per clock, err=%d\n", ret); + return ret; + } + + data->need_three_clks = true; + return ret; +} + +static int imx_prepare_enable_clks(struct device *dev) +{ + struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); + int ret = 0; + + if (data->need_three_clks) { + ret = clk_prepare_enable(data->clk_ipg); + if (ret) { + dev_err(dev, + "Failed to prepare/enable ipg clk, err=%d\n", + ret); + return ret; + } + + ret = clk_prepare_enable(data->clk_ahb); + if (ret) { + dev_err(dev, + "Failed to prepare/enable ahb clk, err=%d\n", + ret); + clk_disable_unprepare(data->clk_ipg); + return ret; + } + + ret = clk_prepare_enable(data->clk_per); + if (ret) { + dev_err(dev, + "Failed to prepare/enable per clk, err=%d\n", + ret); + clk_disable_unprepare(data->clk_ahb); + clk_disable_unprepare(data->clk_ipg); + return ret; + } + } else { + ret = clk_prepare_enable(data->clk); + if (ret) { + dev_err(dev, + "Failed to prepare/enable clk, err=%d\n", + ret); + return ret; + } + } + + return ret; +} + +static void imx_disable_unprepare_clks(struct device *dev) +{ + struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); + + if (data->need_three_clks) { + clk_disable_unprepare(data->clk_per); + clk_disable_unprepare(data->clk_ahb); + clk_disable_unprepare(data->clk_ipg); + } else { + clk_disable_unprepare(data->clk); + } +} + +static int ci_hdrc_imx_notify_event(struct ci_hdrc *ci, unsigned int event) +{ + struct device *dev = ci->dev->parent; + struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); + int ret = 0; + struct imx_usbmisc_data *mdata = data->usbmisc_data; + + switch (event) { + case CI_HDRC_IMX_HSIC_ACTIVE_EVENT: + if (data->pinctrl) { + ret = pinctrl_select_state(data->pinctrl, + data->pinctrl_hsic_active); + if (ret) + dev_err(dev, + "hsic_active select failed, err=%d\n", + ret); + } + break; + case CI_HDRC_IMX_HSIC_SUSPEND_EVENT: + ret = imx_usbmisc_hsic_set_connect(mdata); + if (ret) + dev_err(dev, + "hsic_set_connect failed, err=%d\n", ret); + break; + case CI_HDRC_CONTROLLER_VBUS_EVENT: + if (ci->vbus_active) + ret = imx_usbmisc_charger_detection(mdata, true); + else + ret = imx_usbmisc_charger_detection(mdata, false); + if (ci->usb_phy) + schedule_work(&ci->usb_phy->chg_work); + break; + case CI_HDRC_CONTROLLER_PULLUP_EVENT: + if (ci->role == CI_ROLE_GADGET && + ci->gadget.speed == USB_SPEED_HIGH) + imx_usbmisc_pullup(data->usbmisc_data, + ci->gadget.connected); + break; + default: + break; + } + + return ret; +} + +static irqreturn_t ci_wakeup_irq_handler(int irq, void *data) +{ + struct ci_hdrc_imx_data *imx_data = data; + + disable_irq_nosync(irq); + pm_runtime_resume(&imx_data->ci_pdev->dev); + + return IRQ_HANDLED; +} + +static void ci_hdrc_imx_disable_regulator(void *arg) +{ + struct ci_hdrc_imx_data *data = arg; + + regulator_disable(data->hsic_pad_regulator); +} + +static int ci_hdrc_imx_probe(struct platform_device *pdev) +{ + struct ci_hdrc_imx_data *data; + struct ci_hdrc_platform_data pdata = { + .name = dev_name(&pdev->dev), + .capoffset = DEF_CAPOFFSET, + .flags = CI_HDRC_HAS_SHORT_PKT_LIMIT, + .notify_event = ci_hdrc_imx_notify_event, + }; + int ret; + const struct ci_hdrc_imx_platform_flag *imx_platform_flag; + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + const char *irq_name; + + imx_platform_flag = of_device_get_match_data(&pdev->dev); + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->plat_data = imx_platform_flag; + pdata.flags |= imx_platform_flag->flags; + platform_set_drvdata(pdev, data); + data->usbmisc_data = usbmisc_get_init_data(dev); + if (IS_ERR(data->usbmisc_data)) + return PTR_ERR(data->usbmisc_data); + + if ((of_usb_get_phy_mode(dev->of_node) == USBPHY_INTERFACE_MODE_HSIC) + && data->usbmisc_data) { + pdata.flags |= CI_HDRC_IMX_IS_HSIC; + data->usbmisc_data->hsic = 1; + data->pinctrl = devm_pinctrl_get(dev); + if (PTR_ERR(data->pinctrl) == -ENODEV) + data->pinctrl = NULL; + else if (IS_ERR(data->pinctrl)) { + ret = dev_err_probe(dev, PTR_ERR(data->pinctrl), + "pinctrl get failed\n"); + goto err_put; + } + + data->hsic_pad_regulator = + devm_regulator_get_optional(dev, "hsic"); + if (PTR_ERR(data->hsic_pad_regulator) == -ENODEV) { + /* no pad regulator is needed */ + data->hsic_pad_regulator = NULL; + } else if (IS_ERR(data->hsic_pad_regulator)) { + ret = dev_err_probe(dev, PTR_ERR(data->hsic_pad_regulator), + "Get HSIC pad regulator error\n"); + goto err_put; + } + + if (data->hsic_pad_regulator) { + ret = regulator_enable(data->hsic_pad_regulator); + if (ret) { + dev_err(dev, + "Failed to enable HSIC pad regulator\n"); + goto err_put; + } + ret = devm_add_action_or_reset(dev, + ci_hdrc_imx_disable_regulator, data); + if (ret) { + dev_err(dev, + "Failed to add regulator devm action\n"); + goto err_put; + } + } + } + + /* HSIC pinctrl handling */ + if (data->pinctrl) { + struct pinctrl_state *pinctrl_hsic_idle; + + pinctrl_hsic_idle = pinctrl_lookup_state(data->pinctrl, "idle"); + if (IS_ERR(pinctrl_hsic_idle)) { + dev_err(dev, + "pinctrl_hsic_idle lookup failed, err=%ld\n", + PTR_ERR(pinctrl_hsic_idle)); + ret = PTR_ERR(pinctrl_hsic_idle); + goto err_put; + } + + ret = pinctrl_select_state(data->pinctrl, pinctrl_hsic_idle); + if (ret) { + dev_err(dev, "hsic_idle select failed, err=%d\n", ret); + goto err_put; + } + + data->pinctrl_hsic_active = pinctrl_lookup_state(data->pinctrl, + "active"); + if (IS_ERR(data->pinctrl_hsic_active)) { + dev_err(dev, + "pinctrl_hsic_active lookup failed, err=%ld\n", + PTR_ERR(data->pinctrl_hsic_active)); + ret = PTR_ERR(data->pinctrl_hsic_active); + goto err_put; + } + } + + if (pdata.flags & CI_HDRC_PMQOS) + cpu_latency_qos_add_request(&data->pm_qos_req, 0); + + ret = imx_get_clks(dev); + if (ret) + goto qos_remove_request; + + ret = imx_prepare_enable_clks(dev); + if (ret) + goto qos_remove_request; + + ret = clk_prepare_enable(data->clk_wakeup); + if (ret) + goto err_wakeup_clk; + + data->phy = devm_usb_get_phy_by_phandle(dev, "fsl,usbphy", 0); + if (IS_ERR(data->phy)) { + ret = PTR_ERR(data->phy); + if (ret != -ENODEV) { + dev_err_probe(dev, ret, "Failed to parse fsl,usbphy\n"); + goto err_clk; + } + data->phy = devm_usb_get_phy_by_phandle(dev, "phys", 0); + if (IS_ERR(data->phy)) { + ret = PTR_ERR(data->phy); + if (ret == -ENODEV) { + data->phy = NULL; + } else { + dev_err_probe(dev, ret, "Failed to parse phys\n"); + goto err_clk; + } + } + } + + pdata.usb_phy = data->phy; + if (data->usbmisc_data) + data->usbmisc_data->usb_phy = data->phy; + + if ((of_device_is_compatible(np, "fsl,imx53-usb") || + of_device_is_compatible(np, "fsl,imx51-usb")) && pdata.usb_phy && + of_usb_get_phy_mode(np) == USBPHY_INTERFACE_MODE_ULPI) { + pdata.flags |= CI_HDRC_OVERRIDE_PHY_CONTROL; + data->override_phy_control = true; + ret = usb_phy_init(pdata.usb_phy); + if (ret) { + dev_err(dev, "Failed to init phy\n"); + goto err_clk; + } + } + + if (pdata.flags & CI_HDRC_SUPPORTS_RUNTIME_PM) + data->supports_runtime_pm = true; + + data->wakeup_irq = platform_get_irq_optional(pdev, 1); + if (data->wakeup_irq > 0) { + irq_name = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", pdata.name); + if (!irq_name) { + ret = dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n"); + goto err_clk; + } + + ret = devm_request_threaded_irq(dev, data->wakeup_irq, + NULL, ci_wakeup_irq_handler, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + irq_name, data); + if (ret) + goto err_clk; + } + + ret = imx_usbmisc_init(data->usbmisc_data); + if (ret) { + dev_err(dev, "usbmisc init failed, ret=%d\n", ret); + goto phy_shutdown; + } + + data->ci_pdev = ci_hdrc_add_device(dev, + pdev->resource, pdev->num_resources, + &pdata); + if (IS_ERR(data->ci_pdev)) { + ret = PTR_ERR(data->ci_pdev); + dev_err_probe(dev, ret, "ci_hdrc_add_device failed\n"); + goto phy_shutdown; + } + + if (data->usbmisc_data) { + if (!IS_ERR(pdata.id_extcon.edev) || + of_property_read_bool(np, "usb-role-switch")) + data->usbmisc_data->ext_id = 1; + + if (!IS_ERR(pdata.vbus_extcon.edev) || + of_property_read_bool(np, "usb-role-switch")) + data->usbmisc_data->ext_vbus = 1; + + /* usbmisc needs to know dr mode to choose wakeup setting */ + data->usbmisc_data->available_role = + ci_hdrc_query_available_role(data->ci_pdev); + } + + ret = imx_usbmisc_init_post(data->usbmisc_data); + if (ret) { + dev_err(dev, "usbmisc post failed, ret=%d\n", ret); + goto disable_device; + } + + if (data->supports_runtime_pm) { + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + device_set_wakeup_capable(dev, true); + + return 0; + +disable_device: + ci_hdrc_remove_device(data->ci_pdev); +phy_shutdown: + if (data->override_phy_control) + usb_phy_shutdown(data->phy); +err_clk: + clk_disable_unprepare(data->clk_wakeup); +err_wakeup_clk: + imx_disable_unprepare_clks(dev); +qos_remove_request: + if (pdata.flags & CI_HDRC_PMQOS) + cpu_latency_qos_remove_request(&data->pm_qos_req); + data->ci_pdev = NULL; +err_put: + if (data->usbmisc_data) + put_device(data->usbmisc_data->dev); + return ret; +} + +static void ci_hdrc_imx_remove(struct platform_device *pdev) +{ + struct ci_hdrc_imx_data *data = platform_get_drvdata(pdev); + + if (data->supports_runtime_pm) { + pm_runtime_get_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + } + if (data->ci_pdev) + ci_hdrc_remove_device(data->ci_pdev); + if (data->override_phy_control) + usb_phy_shutdown(data->phy); + if (data->ci_pdev) { + imx_disable_unprepare_clks(&pdev->dev); + clk_disable_unprepare(data->clk_wakeup); + if (data->plat_data->flags & CI_HDRC_PMQOS) + cpu_latency_qos_remove_request(&data->pm_qos_req); + } + if (data->usbmisc_data) + put_device(data->usbmisc_data->dev); +} + +static void ci_hdrc_imx_shutdown(struct platform_device *pdev) +{ + ci_hdrc_imx_remove(pdev); +} + +static int imx_controller_suspend(struct device *dev, + pm_message_t msg) +{ + struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); + int ret = 0; + + dev_dbg(dev, "at %s\n", __func__); + + ret = imx_usbmisc_suspend(data->usbmisc_data, + PMSG_IS_AUTO(msg) || device_may_wakeup(dev)); + if (ret) { + dev_err(dev, + "usbmisc suspend failed, ret=%d\n", ret); + return ret; + } + + imx_disable_unprepare_clks(dev); + + if (data->wakeup_irq > 0) + enable_irq(data->wakeup_irq); + + if (data->plat_data->flags & CI_HDRC_PMQOS) + cpu_latency_qos_remove_request(&data->pm_qos_req); + + data->in_lpm = true; + + return 0; +} + +static int imx_controller_resume(struct device *dev, + pm_message_t msg) +{ + struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); + int ret = 0; + + dev_dbg(dev, "at %s\n", __func__); + + if (!data->in_lpm) { + WARN_ON(1); + return 0; + } + + if (data->plat_data->flags & CI_HDRC_PMQOS) + cpu_latency_qos_add_request(&data->pm_qos_req, 0); + + if (data->wakeup_irq > 0 && + !irqd_irq_disabled(irq_get_irq_data(data->wakeup_irq))) + disable_irq_nosync(data->wakeup_irq); + + ret = imx_prepare_enable_clks(dev); + if (ret) + return ret; + + data->in_lpm = false; + + ret = imx_usbmisc_resume(data->usbmisc_data, + PMSG_IS_AUTO(msg) || device_may_wakeup(dev)); + if (ret) { + dev_err(dev, "usbmisc resume failed, ret=%d\n", ret); + goto clk_disable; + } + + return 0; + +clk_disable: + imx_disable_unprepare_clks(dev); + return ret; +} + +static int ci_hdrc_imx_suspend(struct device *dev) +{ + int ret; + + struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); + + if (data->in_lpm) + /* The core's suspend doesn't run */ + return 0; + + ret = imx_controller_suspend(dev, PMSG_SUSPEND); + if (ret) + return ret; + + pinctrl_pm_select_sleep_state(dev); + + if (data->wakeup_irq > 0 && device_may_wakeup(dev)) { + enable_irq_wake(data->wakeup_irq); + + if (data->plat_data->flags & CI_HDRC_OUT_BAND_WAKEUP) + device_set_out_band_wakeup(dev); + } + + return ret; +} + +static int ci_hdrc_imx_resume(struct device *dev) +{ + struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); + int ret; + + if (data->wakeup_irq > 0 && device_may_wakeup(dev)) + disable_irq_wake(data->wakeup_irq); + + pinctrl_pm_select_default_state(dev); + ret = imx_controller_resume(dev, PMSG_RESUME); + if (!ret && data->supports_runtime_pm) { + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + return ret; +} + +static int ci_hdrc_imx_runtime_suspend(struct device *dev) +{ + struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); + + if (data->in_lpm) { + WARN_ON(1); + return 0; + } + + return imx_controller_suspend(dev, PMSG_AUTO_SUSPEND); +} + +static int ci_hdrc_imx_runtime_resume(struct device *dev) +{ + return imx_controller_resume(dev, PMSG_AUTO_RESUME); +} + +static const struct dev_pm_ops ci_hdrc_imx_pm_ops = { + SYSTEM_SLEEP_PM_OPS(ci_hdrc_imx_suspend, ci_hdrc_imx_resume) + RUNTIME_PM_OPS(ci_hdrc_imx_runtime_suspend, ci_hdrc_imx_runtime_resume, NULL) +}; +static struct platform_driver ci_hdrc_imx_driver = { + .probe = ci_hdrc_imx_probe, + .remove = ci_hdrc_imx_remove, + .shutdown = ci_hdrc_imx_shutdown, + .driver = { + .name = "imx_usb", + .of_match_table = ci_hdrc_imx_dt_ids, + .pm = pm_ptr(&ci_hdrc_imx_pm_ops), + }, +}; + +module_platform_driver(ci_hdrc_imx_driver); + +MODULE_ALIAS("platform:imx-usb"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CI HDRC i.MX USB binding"); +MODULE_AUTHOR("Marek Vasut "); +MODULE_AUTHOR("Richard Zhao "); diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h new file mode 100644 index 000000000..cb95c84d0 --- /dev/null +++ b/drivers/usb/chipidea/ci_hdrc_imx.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + */ + +#ifndef __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H +#define __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H + +struct imx_usbmisc_data { + struct device *dev; + int index; + + unsigned int disable_oc:1; /* over current detect disabled */ + + /* true if over-current polarity is active low */ + unsigned int oc_pol_active_low:1; + + /* true if dt specifies polarity */ + unsigned int oc_pol_configured:1; + + unsigned int pwr_pol:1; /* power polarity */ + unsigned int evdo:1; /* set external vbus divider option */ + unsigned int ulpi:1; /* connected to an ULPI phy */ + unsigned int hsic:1; /* HSIC controller */ + unsigned int ext_id:1; /* ID from exteranl event */ + unsigned int ext_vbus:1; /* Vbus from exteranl event */ + struct usb_phy *usb_phy; + enum usb_dr_mode available_role; /* runtime usb dr mode */ + int emp_curr_control; + int dc_vol_level_adjust; + int rise_fall_time_adjust; +}; + +int imx_usbmisc_init(struct imx_usbmisc_data *data); +int imx_usbmisc_init_post(struct imx_usbmisc_data *data); +int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *data); +int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect); +int imx_usbmisc_suspend(struct imx_usbmisc_data *data, bool wakeup); +int imx_usbmisc_resume(struct imx_usbmisc_data *data, bool wakeup); +int imx_usbmisc_pullup(struct imx_usbmisc_data *data, bool on); + +#endif /* __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H */ diff --git a/drivers/usb/chipidea/ci_hdrc_msm.c b/drivers/usb/chipidea/ci_hdrc_msm.c new file mode 100644 index 000000000..3ab3daa78 --- /dev/null +++ b/drivers/usb/chipidea/ci_hdrc_msm.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" + +#define HS_PHY_AHB_MODE 0x0098 + +#define HS_PHY_GENCONFIG 0x009c +#define HS_PHY_TXFIFO_IDLE_FORCE_DIS BIT(4) + +#define HS_PHY_GENCONFIG_2 0x00a0 +#define HS_PHY_SESS_VLD_CTRL_EN BIT(7) +#define HS_PHY_ULPI_TX_PKT_EN_CLR_FIX BIT(19) + +#define HSPHY_SESS_VLD_CTRL BIT(25) + +/* Vendor base starts at 0x200 beyond CI base */ +#define HS_PHY_CTRL 0x0040 +#define HS_PHY_SEC_CTRL 0x0078 +#define HS_PHY_DIG_CLAMP_N BIT(16) +#define HS_PHY_POR_ASSERT BIT(0) + +struct ci_hdrc_msm { + struct platform_device *ci; + struct clk *core_clk; + struct clk *iface_clk; + struct clk *fs_clk; + struct ci_hdrc_platform_data pdata; + struct reset_controller_dev rcdev; + bool secondary_phy; + bool hsic; + void __iomem *base; +}; + +static int +ci_hdrc_msm_por_reset(struct reset_controller_dev *r, unsigned long id) +{ + struct ci_hdrc_msm *ci_msm = container_of(r, struct ci_hdrc_msm, rcdev); + void __iomem *addr = ci_msm->base; + u32 val; + + if (id) + addr += HS_PHY_SEC_CTRL; + else + addr += HS_PHY_CTRL; + + val = readl_relaxed(addr); + val |= HS_PHY_POR_ASSERT; + writel(val, addr); + /* + * wait for minimum 10 microseconds as suggested by manual. + * Use a slightly larger value since the exact value didn't + * work 100% of the time. + */ + udelay(12); + val &= ~HS_PHY_POR_ASSERT; + writel(val, addr); + + return 0; +} + +static const struct reset_control_ops ci_hdrc_msm_reset_ops = { + .reset = ci_hdrc_msm_por_reset, +}; + +static int ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event) +{ + struct device *dev = ci->dev->parent; + struct ci_hdrc_msm *msm_ci = dev_get_drvdata(dev); + int ret; + + switch (event) { + case CI_HDRC_CONTROLLER_RESET_EVENT: + dev_dbg(dev, "CI_HDRC_CONTROLLER_RESET_EVENT received\n"); + + hw_phymode_configure(ci); + if (msm_ci->secondary_phy) { + u32 val = readl_relaxed(msm_ci->base + HS_PHY_SEC_CTRL); + val |= HS_PHY_DIG_CLAMP_N; + writel_relaxed(val, msm_ci->base + HS_PHY_SEC_CTRL); + } + + ret = phy_init(ci->phy); + if (ret) + return ret; + + ret = phy_power_on(ci->phy); + if (ret) { + phy_exit(ci->phy); + return ret; + } + + /* use AHB transactor, allow posted data writes */ + hw_write_id_reg(ci, HS_PHY_AHB_MODE, 0xffffffff, 0x8); + + /* workaround for rx buffer collision issue */ + hw_write_id_reg(ci, HS_PHY_GENCONFIG, + HS_PHY_TXFIFO_IDLE_FORCE_DIS, 0); + + if (!msm_ci->hsic) + hw_write_id_reg(ci, HS_PHY_GENCONFIG_2, + HS_PHY_ULPI_TX_PKT_EN_CLR_FIX, 0); + + if (!IS_ERR(ci->platdata->vbus_extcon.edev) || ci->role_switch) { + hw_write_id_reg(ci, HS_PHY_GENCONFIG_2, + HS_PHY_SESS_VLD_CTRL_EN, + HS_PHY_SESS_VLD_CTRL_EN); + hw_write(ci, OP_USBCMD, HSPHY_SESS_VLD_CTRL, + HSPHY_SESS_VLD_CTRL); + + } + break; + case CI_HDRC_CONTROLLER_STOPPED_EVENT: + dev_dbg(dev, "CI_HDRC_CONTROLLER_STOPPED_EVENT received\n"); + phy_power_off(ci->phy); + phy_exit(ci->phy); + break; + default: + dev_dbg(dev, "unknown ci_hdrc event\n"); + break; + } + + return 0; +} + +static int ci_hdrc_msm_mux_phy(struct ci_hdrc_msm *ci, + struct platform_device *pdev) +{ + struct regmap *regmap; + struct device *dev = &pdev->dev; + struct of_phandle_args args; + u32 val; + int ret; + + ret = of_parse_phandle_with_fixed_args(dev->of_node, "phy-select", 2, 0, + &args); + if (ret) + return 0; + + regmap = syscon_node_to_regmap(args.np); + of_node_put(args.np); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = regmap_write(regmap, args.args[0], args.args[1]); + if (ret) + return ret; + + ci->secondary_phy = !!args.args[1]; + if (ci->secondary_phy) { + val = readl_relaxed(ci->base + HS_PHY_SEC_CTRL); + val |= HS_PHY_DIG_CLAMP_N; + writel_relaxed(val, ci->base + HS_PHY_SEC_CTRL); + } + + return 0; +} + +static int ci_hdrc_msm_probe(struct platform_device *pdev) +{ + struct ci_hdrc_msm *ci; + struct platform_device *plat_ci; + struct clk *clk; + struct reset_control *reset; + int ret; + struct device_node *ulpi_node, *phy_node; + + dev_dbg(&pdev->dev, "ci_hdrc_msm_probe\n"); + + ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL); + if (!ci) + return -ENOMEM; + platform_set_drvdata(pdev, ci); + + ci->pdata.name = "ci_hdrc_msm"; + ci->pdata.capoffset = DEF_CAPOFFSET; + ci->pdata.flags = CI_HDRC_REGS_SHARED | CI_HDRC_DISABLE_STREAMING | + CI_HDRC_OVERRIDE_AHB_BURST | + CI_HDRC_OVERRIDE_PHY_CONTROL; + ci->pdata.notify_event = ci_hdrc_msm_notify_event; + + reset = devm_reset_control_get(&pdev->dev, "core"); + if (IS_ERR(reset)) + return PTR_ERR(reset); + + ci->core_clk = clk = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ci->iface_clk = clk = devm_clk_get(&pdev->dev, "iface"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ci->fs_clk = clk = devm_clk_get_optional(&pdev->dev, "fs"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ci->base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(ci->base)) + return PTR_ERR(ci->base); + + ci->rcdev.owner = THIS_MODULE; + ci->rcdev.ops = &ci_hdrc_msm_reset_ops; + ci->rcdev.of_node = pdev->dev.of_node; + ci->rcdev.nr_resets = 2; + ret = devm_reset_controller_register(&pdev->dev, &ci->rcdev); + if (ret) + return ret; + + ret = clk_prepare_enable(ci->fs_clk); + if (ret) + return ret; + + reset_control_assert(reset); + usleep_range(10000, 12000); + reset_control_deassert(reset); + + clk_disable_unprepare(ci->fs_clk); + + ret = clk_prepare_enable(ci->core_clk); + if (ret) + return ret; + + ret = clk_prepare_enable(ci->iface_clk); + if (ret) + goto err_iface; + + ret = ci_hdrc_msm_mux_phy(ci, pdev); + if (ret) + goto err_mux; + + ulpi_node = of_get_child_by_name(pdev->dev.of_node, "ulpi"); + if (ulpi_node) { + phy_node = of_get_next_available_child(ulpi_node, NULL); + ci->hsic = of_device_is_compatible(phy_node, "qcom,usb-hsic-phy"); + of_node_put(phy_node); + } + of_node_put(ulpi_node); + + plat_ci = ci_hdrc_add_device(&pdev->dev, pdev->resource, + pdev->num_resources, &ci->pdata); + if (IS_ERR(plat_ci)) { + ret = PTR_ERR(plat_ci); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n"); + goto err_mux; + } + + ci->ci = plat_ci; + + pm_runtime_set_active(&pdev->dev); + pm_runtime_no_callbacks(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + return 0; + +err_mux: + clk_disable_unprepare(ci->iface_clk); +err_iface: + clk_disable_unprepare(ci->core_clk); + return ret; +} + +static void ci_hdrc_msm_remove(struct platform_device *pdev) +{ + struct ci_hdrc_msm *ci = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + ci_hdrc_remove_device(ci->ci); + clk_disable_unprepare(ci->iface_clk); + clk_disable_unprepare(ci->core_clk); +} + +static const struct of_device_id msm_ci_dt_match[] = { + { .compatible = "qcom,ci-hdrc", }, + { } +}; +MODULE_DEVICE_TABLE(of, msm_ci_dt_match); + +static struct platform_driver ci_hdrc_msm_driver = { + .probe = ci_hdrc_msm_probe, + .remove = ci_hdrc_msm_remove, + .driver = { + .name = "msm_hsusb", + .of_match_table = msm_ci_dt_match, + }, +}; + +module_platform_driver(ci_hdrc_msm_driver); + +MODULE_ALIAS("platform:msm_hsusb"); +MODULE_ALIAS("platform:ci13xxx_msm"); +MODULE_DESCRIPTION("ChipIdea Highspeed Dual Role Controller"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/chipidea/ci_hdrc_npcm.c b/drivers/usb/chipidea/ci_hdrc_npcm.c new file mode 100644 index 000000000..e52a2b05c --- /dev/null +++ b/drivers/usb/chipidea/ci_hdrc_npcm.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2023 Nuvoton Technology corporation. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" + +struct npcm_udc_data { + struct platform_device *ci; + struct clk *core_clk; + struct ci_hdrc_platform_data pdata; +}; + +static int npcm_udc_notify_event(struct ci_hdrc *ci, unsigned int event) +{ + struct device *dev = ci->dev->parent; + + switch (event) { + case CI_HDRC_CONTROLLER_RESET_EVENT: + /* clear all mode bits */ + hw_write(ci, OP_USBMODE, 0xffffffff, 0x0); + break; + default: + dev_dbg(dev, "unknown ci_hdrc event (%d)\n", event); + break; + } + + return 0; +} + +static int npcm_udc_probe(struct platform_device *pdev) +{ + int ret; + struct npcm_udc_data *ci; + struct platform_device *plat_ci; + struct device *dev = &pdev->dev; + + ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL); + if (!ci) + return -ENOMEM; + platform_set_drvdata(pdev, ci); + + ci->core_clk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(ci->core_clk)) + return PTR_ERR(ci->core_clk); + + ret = clk_prepare_enable(ci->core_clk); + if (ret) + return dev_err_probe(dev, ret, "failed to enable the clock: %d\n", ret); + + ci->pdata.name = dev_name(dev); + ci->pdata.capoffset = DEF_CAPOFFSET; + ci->pdata.flags = CI_HDRC_REQUIRES_ALIGNED_DMA | + CI_HDRC_FORCE_VBUS_ACTIVE_ALWAYS; + ci->pdata.phy_mode = USBPHY_INTERFACE_MODE_UTMI; + ci->pdata.notify_event = npcm_udc_notify_event; + + plat_ci = ci_hdrc_add_device(dev, pdev->resource, pdev->num_resources, + &ci->pdata); + if (IS_ERR(plat_ci)) { + ret = PTR_ERR(plat_ci); + dev_err(dev, "failed to register HDRC NPCM device: %d\n", ret); + goto clk_err; + } + + pm_runtime_no_callbacks(dev); + pm_runtime_enable(dev); + + return 0; + +clk_err: + clk_disable_unprepare(ci->core_clk); + return ret; +} + +static void npcm_udc_remove(struct platform_device *pdev) +{ + struct npcm_udc_data *ci = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + ci_hdrc_remove_device(ci->ci); + clk_disable_unprepare(ci->core_clk); +} + +static const struct of_device_id npcm_udc_dt_match[] = { + { .compatible = "nuvoton,npcm750-udc", }, + { .compatible = "nuvoton,npcm845-udc", }, + { } +}; +MODULE_DEVICE_TABLE(of, npcm_udc_dt_match); + +static struct platform_driver npcm_udc_driver = { + .probe = npcm_udc_probe, + .remove = npcm_udc_remove, + .driver = { + .name = "npcm_udc", + .of_match_table = npcm_udc_dt_match, + }, +}; + +module_platform_driver(npcm_udc_driver); + +MODULE_DESCRIPTION("NPCM USB device controller driver"); +MODULE_AUTHOR("Tomer Maimon "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/chipidea/ci_hdrc_pci.c b/drivers/usb/chipidea/ci_hdrc_pci.c new file mode 100644 index 000000000..d63479e1a --- /dev/null +++ b/drivers/usb/chipidea/ci_hdrc_pci.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ci_hdrc_pci.c - MIPS USB IP core family device controller + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + */ + +#include +#include +#include +#include +#include +#include +#include + +/* driver name */ +#define UDC_DRIVER_NAME "ci_hdrc_pci" + +struct ci_hdrc_pci { + struct platform_device *ci; + struct platform_device *phy; +}; + +/****************************************************************************** + * PCI block + *****************************************************************************/ +static struct ci_hdrc_platform_data pci_platdata = { + .name = UDC_DRIVER_NAME, + .capoffset = DEF_CAPOFFSET, +}; + +static struct ci_hdrc_platform_data langwell_pci_platdata = { + .name = UDC_DRIVER_NAME, + .capoffset = 0, +}; + +static struct ci_hdrc_platform_data penwell_pci_platdata = { + .name = UDC_DRIVER_NAME, + .capoffset = 0, + .power_budget = 200, +}; + +/** + * ci_hdrc_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 ci_hdrc_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct ci_hdrc_platform_data *platdata = (void *)id->driver_data; + struct ci_hdrc_pci *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; + } + + ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL); + if (!ci) + return -ENOMEM; + + retval = pcim_enable_device(pdev); + if (retval) + return retval; + + if (!pdev->irq) { + dev_err(&pdev->dev, "No IRQ, check BIOS/PCI setup!"); + return -ENODEV; + } + + pci_set_master(pdev); + pci_try_set_mwi(pdev); + + /* register a nop PHY */ + ci->phy = usb_phy_generic_register(); + if (IS_ERR(ci->phy)) + return PTR_ERR(ci->phy); + + 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; + + ci->ci = ci_hdrc_add_device(&pdev->dev, res, nres, platdata); + if (IS_ERR(ci->ci)) { + dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n"); + usb_phy_generic_unregister(ci->phy); + return PTR_ERR(ci->ci); + } + + pci_set_drvdata(pdev, ci); + + return 0; +} + +/** + * ci_hdrc_pci_remove: PCI remove + * @pdev: USB Device Controller being removed + * + * Reverses the effect of ci_hdrc_pci_probe(), + * first invoking the udc_remove() and then releases + * all PCI resources allocated for this USB device controller + */ +static void ci_hdrc_pci_remove(struct pci_dev *pdev) +{ + struct ci_hdrc_pci *ci = pci_get_drvdata(pdev); + + ci_hdrc_remove_device(ci->ci); + usb_phy_generic_unregister(ci->phy); +} + +/* + * PCI device table + * PCI device structure + * + * Check "pci.h" for details + * + * Note: ehci-pci driver may try to probe the device first. You have to add an + * ID to the bypass_pci_id_table in ehci-pci driver to prevent this. + */ +static const struct pci_device_id ci_hdrc_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_VDEVICE(INTEL, 0x0811), + .driver_data = (kernel_ulong_t)&langwell_pci_platdata, + }, + { + PCI_VDEVICE(INTEL, 0x0829), + .driver_data = (kernel_ulong_t)&penwell_pci_platdata, + }, + { + /* Intel Clovertrail */ + PCI_VDEVICE(INTEL, 0xe006), + .driver_data = (kernel_ulong_t)&penwell_pci_platdata, + }, + { 0 } /* end: all zeroes */ +}; +MODULE_DEVICE_TABLE(pci, ci_hdrc_pci_id_table); + +static struct pci_driver ci_hdrc_pci_driver = { + .name = UDC_DRIVER_NAME, + .id_table = ci_hdrc_pci_id_table, + .probe = ci_hdrc_pci_probe, + .remove = ci_hdrc_pci_remove, +}; + +module_pci_driver(ci_hdrc_pci_driver); + +MODULE_AUTHOR("MIPS - David Lopo "); +MODULE_DESCRIPTION("MIPS CI13XXX USB Peripheral Controller"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ci13xxx_pci"); diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c new file mode 100644 index 000000000..372788f0f --- /dev/null +++ b/drivers/usb/chipidea/ci_hdrc_tegra.c @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016, NVIDIA Corporation + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "../host/ehci.h" + +#include "ci.h" + +struct tegra_usb { + struct ci_hdrc_platform_data data; + struct platform_device *dev; + + const struct tegra_usb_soc_info *soc; + struct usb_phy *phy; + struct clk *clk; + + bool needs_double_reset; +}; + +struct tegra_usb_soc_info { + unsigned long flags; + unsigned int txfifothresh; + enum usb_dr_mode dr_mode; +}; + +static const struct tegra_usb_soc_info tegra20_ehci_soc_info = { + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | + CI_HDRC_OVERRIDE_PHY_CONTROL | + CI_HDRC_SUPPORTS_RUNTIME_PM, + .dr_mode = USB_DR_MODE_HOST, + .txfifothresh = 10, +}; + +static const struct tegra_usb_soc_info tegra30_ehci_soc_info = { + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | + CI_HDRC_OVERRIDE_PHY_CONTROL | + CI_HDRC_SUPPORTS_RUNTIME_PM, + .dr_mode = USB_DR_MODE_HOST, + .txfifothresh = 16, +}; + +static const struct tegra_usb_soc_info tegra20_udc_soc_info = { + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | + CI_HDRC_OVERRIDE_PHY_CONTROL | + CI_HDRC_SUPPORTS_RUNTIME_PM, + .dr_mode = USB_DR_MODE_UNKNOWN, + .txfifothresh = 10, +}; + +static const struct tegra_usb_soc_info tegra30_udc_soc_info = { + .flags = CI_HDRC_REQUIRES_ALIGNED_DMA | + CI_HDRC_OVERRIDE_PHY_CONTROL | + CI_HDRC_SUPPORTS_RUNTIME_PM, + .dr_mode = USB_DR_MODE_UNKNOWN, + .txfifothresh = 16, +}; + +static const struct of_device_id tegra_usb_of_match[] = { + { + .compatible = "nvidia,tegra20-ehci", + .data = &tegra20_ehci_soc_info, + }, { + .compatible = "nvidia,tegra30-ehci", + .data = &tegra30_ehci_soc_info, + }, { + .compatible = "nvidia,tegra20-udc", + .data = &tegra20_udc_soc_info, + }, { + .compatible = "nvidia,tegra30-udc", + .data = &tegra30_udc_soc_info, + }, { + .compatible = "nvidia,tegra114-udc", + .data = &tegra30_udc_soc_info, + }, { + .compatible = "nvidia,tegra124-udc", + .data = &tegra30_udc_soc_info, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, tegra_usb_of_match); + +static int tegra_usb_reset_controller(struct device *dev) +{ + struct reset_control *rst, *rst_utmi; + struct device_node *phy_np; + int err; + + rst = devm_reset_control_get_shared(dev, "usb"); + if (IS_ERR(rst)) { + dev_err(dev, "can't get ehci reset: %pe\n", rst); + return PTR_ERR(rst); + } + + phy_np = of_parse_phandle(dev->of_node, "nvidia,phy", 0); + if (!phy_np) + return -ENOENT; + + /* + * The 1st USB controller contains some UTMI pad registers that are + * global for all the controllers on the chip. Those registers are + * also cleared when reset is asserted to the 1st controller. + */ + rst_utmi = of_reset_control_get_shared(phy_np, "utmi-pads"); + if (IS_ERR(rst_utmi)) { + dev_warn(dev, "can't get utmi-pads reset from the PHY\n"); + dev_warn(dev, "continuing, but please update your DT\n"); + } else { + /* + * PHY driver performs UTMI-pads reset in a case of a + * non-legacy DT. + */ + reset_control_put(rst_utmi); + } + + of_node_put(phy_np); + + /* reset control is shared, hence initialize it first */ + err = reset_control_deassert(rst); + if (err) + return err; + + err = reset_control_assert(rst); + if (err) + return err; + + udelay(1); + + err = reset_control_deassert(rst); + if (err) + return err; + + return 0; +} + +static int tegra_usb_notify_event(struct ci_hdrc *ci, unsigned int event) +{ + struct tegra_usb *usb = dev_get_drvdata(ci->dev->parent); + struct ehci_hcd *ehci; + + switch (event) { + case CI_HDRC_CONTROLLER_RESET_EVENT: + if (ci->hcd) { + ehci = hcd_to_ehci(ci->hcd); + ehci->has_tdi_phy_lpm = false; + ehci_writel(ehci, usb->soc->txfifothresh << 16, + &ehci->regs->txfill_tuning); + } + break; + } + + return 0; +} + +static int tegra_usb_internal_port_reset(struct ehci_hcd *ehci, + u32 __iomem *portsc_reg, + unsigned long *flags) +{ + u32 saved_usbintr, temp; + unsigned int i, tries; + int retval = 0; + + 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); + fsleep(10000); + temp &= ~PORT_RESET; + ehci_writel(ehci, temp, portsc_reg); + fsleep(1000); + tries = 100; + do { + fsleep(1000); + /* + * 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 */ + spin_lock_irqsave(&ehci->lock, *flags); + ehci_writel(ehci, saved_usbintr, &ehci->regs->intr_enable); + + return retval; +} + +static int tegra_ehci_hub_control(struct ci_hdrc *ci, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength, + bool *done, unsigned long *flags) +{ + struct tegra_usb *usb = dev_get_drvdata(ci->dev->parent); + struct ehci_hcd *ehci = hcd_to_ehci(ci->hcd); + u32 __iomem *status_reg; + int retval = 0; + + status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; + + switch (typeReq) { + case SetPortFeature: + if (wValue != USB_PORT_FEAT_RESET || !usb->needs_double_reset) + break; + + /* for USB1 port we need to issue Port Reset twice internally */ + retval = tegra_usb_internal_port_reset(ehci, status_reg, flags); + *done = true; + break; + } + + return retval; +} + +static void tegra_usb_enter_lpm(struct ci_hdrc *ci, bool enable) +{ + /* + * Touching any register which belongs to AHB clock domain will + * hang CPU if USB controller is put into low power mode because + * AHB USB clock is gated on Tegra in the LPM. + * + * Tegra PHY has a separate register for checking the clock status + * and usb_phy_set_suspend() takes care of gating/ungating the clocks + * and restoring the PHY state on Tegra. Hence DEVLC/PORTSC registers + * shouldn't be touched directly by the CI driver. + */ + usb_phy_set_suspend(ci->usb_phy, enable); +} + +static int tegra_usb_probe(struct platform_device *pdev) +{ + const struct tegra_usb_soc_info *soc; + struct tegra_usb *usb; + int err; + + usb = devm_kzalloc(&pdev->dev, sizeof(*usb), GFP_KERNEL); + if (!usb) + return -ENOMEM; + + platform_set_drvdata(pdev, usb); + + soc = of_device_get_match_data(&pdev->dev); + if (!soc) { + dev_err(&pdev->dev, "failed to match OF data\n"); + return -EINVAL; + } + + usb->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "nvidia,phy", 0); + if (IS_ERR(usb->phy)) + return dev_err_probe(&pdev->dev, PTR_ERR(usb->phy), + "failed to get PHY"); + + usb->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(usb->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(usb->clk), + "failed to get clock"); + + err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev); + if (err) + return err; + + pm_runtime_enable(&pdev->dev); + err = pm_runtime_resume_and_get(&pdev->dev); + if (err) + return err; + + if (device_property_present(&pdev->dev, "nvidia,needs-double-reset")) + usb->needs_double_reset = true; + + err = tegra_usb_reset_controller(&pdev->dev); + if (err) { + dev_err_probe(&pdev->dev, err, "failed to reset controller"); + goto fail_power_off; + } + + /* + * USB controller registers shouldn't be touched before PHY is + * initialized, otherwise CPU will hang because clocks are gated. + * PHY driver controls gating of internal USB clocks on Tegra. + */ + err = usb_phy_init(usb->phy); + if (err) + goto fail_power_off; + + /* setup and register ChipIdea HDRC device */ + usb->soc = soc; + usb->data.name = "tegra-usb"; + usb->data.flags = soc->flags; + usb->data.usb_phy = usb->phy; + usb->data.dr_mode = soc->dr_mode; + usb->data.capoffset = DEF_CAPOFFSET; + usb->data.enter_lpm = tegra_usb_enter_lpm; + usb->data.hub_control = tegra_ehci_hub_control; + usb->data.notify_event = tegra_usb_notify_event; + + /* Tegra PHY driver currently doesn't support LPM for ULPI */ + if (of_usb_get_phy_mode(pdev->dev.of_node) == USBPHY_INTERFACE_MODE_ULPI) + usb->data.flags &= ~CI_HDRC_SUPPORTS_RUNTIME_PM; + + usb->dev = ci_hdrc_add_device(&pdev->dev, pdev->resource, + pdev->num_resources, &usb->data); + if (IS_ERR(usb->dev)) { + err = dev_err_probe(&pdev->dev, PTR_ERR(usb->dev), + "failed to add HDRC device"); + goto phy_shutdown; + } + + return 0; + +phy_shutdown: + usb_phy_shutdown(usb->phy); +fail_power_off: + pm_runtime_put_sync_suspend(&pdev->dev); + pm_runtime_force_suspend(&pdev->dev); + + return err; +} + +static void tegra_usb_remove(struct platform_device *pdev) +{ + struct tegra_usb *usb = platform_get_drvdata(pdev); + + ci_hdrc_remove_device(usb->dev); + usb_phy_shutdown(usb->phy); + + pm_runtime_put_sync_suspend(&pdev->dev); + pm_runtime_force_suspend(&pdev->dev); +} + +static int tegra_usb_runtime_resume(struct device *dev) +{ + struct tegra_usb *usb = dev_get_drvdata(dev); + int err; + + err = clk_prepare_enable(usb->clk); + if (err < 0) { + dev_err(dev, "failed to enable clock: %d\n", err); + return err; + } + + return 0; +} + +static int tegra_usb_runtime_suspend(struct device *dev) +{ + struct tegra_usb *usb = dev_get_drvdata(dev); + + clk_disable_unprepare(usb->clk); + + return 0; +} + +static const struct dev_pm_ops tegra_usb_pm = { + RUNTIME_PM_OPS(tegra_usb_runtime_suspend, tegra_usb_runtime_resume, NULL) +}; + +static struct platform_driver tegra_usb_driver = { + .driver = { + .name = "tegra-usb", + .of_match_table = tegra_usb_of_match, + .pm = pm_ptr(&tegra_usb_pm), + }, + .probe = tegra_usb_probe, + .remove = tegra_usb_remove, +}; +module_platform_driver(tegra_usb_driver); + +MODULE_DESCRIPTION("NVIDIA Tegra USB driver"); +MODULE_AUTHOR("Thierry Reding "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/chipidea/ci_hdrc_usb2.c b/drivers/usb/chipidea/ci_hdrc_usb2.c new file mode 100644 index 000000000..8ffa1e95d --- /dev/null +++ b/drivers/usb/chipidea/ci_hdrc_usb2.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2014 Marvell Technology Group Ltd. + * + * Antoine Tenart + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" + +struct ci_hdrc_usb2_priv { + struct platform_device *ci_pdev; + struct clk *clk; +}; + +static const struct ci_hdrc_platform_data ci_default_pdata = { + .capoffset = DEF_CAPOFFSET, + .flags = CI_HDRC_DISABLE_STREAMING, +}; + +static const struct ci_hdrc_platform_data ci_zynq_pdata = { + .capoffset = DEF_CAPOFFSET, + .flags = CI_HDRC_PHY_VBUS_CONTROL, +}; + +static const struct ci_hdrc_platform_data ci_zevio_pdata = { + .capoffset = DEF_CAPOFFSET, + .flags = CI_HDRC_REGS_SHARED | CI_HDRC_FORCE_FULLSPEED, +}; + +static const struct of_device_id ci_hdrc_usb2_of_match[] = { + { .compatible = "chipidea,usb2" }, + { .compatible = "xlnx,zynq-usb-2.20a", .data = &ci_zynq_pdata }, + { .compatible = "lsi,zevio-usb", .data = &ci_zevio_pdata }, + { } +}; +MODULE_DEVICE_TABLE(of, ci_hdrc_usb2_of_match); + +static int ci_hdrc_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ci_hdrc_usb2_priv *priv; + struct ci_hdrc_platform_data *ci_pdata = dev_get_platdata(dev); + const struct ci_hdrc_platform_data *data; + int ret; + + if (!ci_pdata) { + ci_pdata = devm_kmalloc(dev, sizeof(*ci_pdata), GFP_KERNEL); + if (!ci_pdata) + return -ENOMEM; + *ci_pdata = ci_default_pdata; /* struct copy */ + } + + data = device_get_match_data(&pdev->dev); + if (data) + /* struct copy */ + *ci_pdata = *data; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->clk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(dev, "failed to enable the clock: %d\n", ret); + return ret; + } + + ci_pdata->name = dev_name(dev); + + priv->ci_pdev = ci_hdrc_add_device(dev, pdev->resource, + pdev->num_resources, ci_pdata); + if (IS_ERR(priv->ci_pdev)) { + ret = PTR_ERR(priv->ci_pdev); + if (ret != -EPROBE_DEFER) + dev_err(dev, + "failed to register ci_hdrc platform device: %d\n", + ret); + goto clk_err; + } + + platform_set_drvdata(pdev, priv); + + pm_runtime_no_callbacks(dev); + pm_runtime_enable(dev); + + return 0; + +clk_err: + clk_disable_unprepare(priv->clk); + return ret; +} + +static void ci_hdrc_usb2_remove(struct platform_device *pdev) +{ + struct ci_hdrc_usb2_priv *priv = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + ci_hdrc_remove_device(priv->ci_pdev); + clk_disable_unprepare(priv->clk); +} + +static struct platform_driver ci_hdrc_usb2_driver = { + .probe = ci_hdrc_usb2_probe, + .remove = ci_hdrc_usb2_remove, + .driver = { + .name = "chipidea-usb2", + .of_match_table = ci_hdrc_usb2_of_match, + }, +}; +module_platform_driver(ci_hdrc_usb2_driver); + +MODULE_DESCRIPTION("ChipIdea HDRC USB2 binding for ci13xxx"); +MODULE_AUTHOR("Antoine Tenart "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c new file mode 100644 index 000000000..3461dd763 --- /dev/null +++ b/drivers/usb/chipidea/core.c @@ -0,0 +1,1506 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * core.c - ChipIdea USB IP core family device controller + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * Copyright (C) 2020 NXP + * + * Author: David Lopo + * Peter Chen + * + * Main Features: + * - Four transfers are supported, usbtest is passed + * - USB Certification for gadget: CH9 and Mass Storage are passed + * - Low power mode + * - USB wakeup + */ +#include +#include +#include +#include +#include +#include +#include +#include +#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 "otg.h" +#include "otg_fsm.h" + +/* Controller register map */ +static const u8 ci_regs_nolpm[] = { + [CAP_CAPLENGTH] = 0x00U, + [CAP_HCCPARAMS] = 0x08U, + [CAP_DCCPARAMS] = 0x24U, + [CAP_TESTMODE] = 0x38U, + [OP_USBCMD] = 0x00U, + [OP_USBSTS] = 0x04U, + [OP_USBINTR] = 0x08U, + [OP_FRINDEX] = 0x0CU, + [OP_DEVICEADDR] = 0x14U, + [OP_ENDPTLISTADDR] = 0x18U, + [OP_TTCTRL] = 0x1CU, + [OP_BURSTSIZE] = 0x20U, + [OP_ULPI_VIEWPORT] = 0x30U, + [OP_PORTSC] = 0x44U, + [OP_DEVLC] = 0x84U, + [OP_OTGSC] = 0x64U, + [OP_USBMODE] = 0x68U, + [OP_ENDPTSETUPSTAT] = 0x6CU, + [OP_ENDPTPRIME] = 0x70U, + [OP_ENDPTFLUSH] = 0x74U, + [OP_ENDPTSTAT] = 0x78U, + [OP_ENDPTCOMPLETE] = 0x7CU, + [OP_ENDPTCTRL] = 0x80U, +}; + +static const u8 ci_regs_lpm[] = { + [CAP_CAPLENGTH] = 0x00U, + [CAP_HCCPARAMS] = 0x08U, + [CAP_DCCPARAMS] = 0x24U, + [CAP_TESTMODE] = 0xFCU, + [OP_USBCMD] = 0x00U, + [OP_USBSTS] = 0x04U, + [OP_USBINTR] = 0x08U, + [OP_FRINDEX] = 0x0CU, + [OP_DEVICEADDR] = 0x14U, + [OP_ENDPTLISTADDR] = 0x18U, + [OP_TTCTRL] = 0x1CU, + [OP_BURSTSIZE] = 0x20U, + [OP_ULPI_VIEWPORT] = 0x30U, + [OP_PORTSC] = 0x44U, + [OP_DEVLC] = 0x84U, + [OP_OTGSC] = 0xC4U, + [OP_USBMODE] = 0xC8U, + [OP_ENDPTSETUPSTAT] = 0xD8U, + [OP_ENDPTPRIME] = 0xDCU, + [OP_ENDPTFLUSH] = 0xE0U, + [OP_ENDPTSTAT] = 0xE4U, + [OP_ENDPTCOMPLETE] = 0xE8U, + [OP_ENDPTCTRL] = 0xECU, +}; + +static void hw_alloc_regmap(struct ci_hdrc *ci, bool is_lpm) +{ + int i; + + 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]); + +} + +static enum ci_revision ci_get_revision(struct ci_hdrc *ci) +{ + int ver = hw_read_id_reg(ci, ID_ID, VERSION) >> __ffs(VERSION); + enum ci_revision rev = CI_REVISION_UNKNOWN; + + if (ver == 0x2) { + rev = hw_read_id_reg(ci, ID_ID, REVISION) + >> __ffs(REVISION); + rev += CI_REVISION_20; + } else if (ver == 0x0) { + rev = CI_REVISION_1X; + } + + return rev; +} + +/** + * hw_read_intr_enable: returns interrupt enable register + * + * @ci: the controller + * + * This function returns register data + */ +u32 hw_read_intr_enable(struct ci_hdrc *ci) +{ + return hw_read(ci, OP_USBINTR, ~0); +} + +/** + * hw_read_intr_status: returns interrupt status register + * + * @ci: the controller + * + * This function returns register data + */ +u32 hw_read_intr_status(struct ci_hdrc *ci) +{ + return hw_read(ci, OP_USBSTS, ~0); +} + +/** + * hw_port_test_set: writes port test mode (execute without interruption) + * @ci: the controller + * @mode: new value + * + * This function returns an error code + */ +int hw_port_test_set(struct ci_hdrc *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 + * + * @ci: the controller + * + * This function returns port test mode value + */ +u8 hw_port_test_get(struct ci_hdrc *ci) +{ + return hw_read(ci, OP_PORTSC, PORTSC_PTC) >> __ffs(PORTSC_PTC); +} + +static void hw_wait_phy_stable(void) +{ + /* + * The phy needs some delay to output the stable status from low + * power mode. And for OTGSC, the status inputs are debounced + * using a 1 ms time constant, so, delay 2ms for controller to get + * the stable status, like vbus and id when the phy leaves low power. + */ + usleep_range(2000, 2500); +} + +/* The PHY enters/leaves low power mode */ +static void ci_hdrc_enter_lpm_common(struct ci_hdrc *ci, bool enable) +{ + enum ci_hw_regs reg = ci->hw_bank.lpm ? OP_DEVLC : OP_PORTSC; + bool lpm = !!(hw_read(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm))); + + if (enable && !lpm) + hw_write(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm), + PORTSC_PHCD(ci->hw_bank.lpm)); + else if (!enable && lpm) + hw_write(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm), + 0); +} + +static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) +{ + return ci->platdata->enter_lpm(ci, enable); +} + +static int hw_device_init(struct ci_hdrc *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; + if (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; + + ci_hdrc_enter_lpm(ci, false); + + /* Disable all interrupts bits */ + hw_write(ci, OP_USBINTR, 0xffffffff, 0); + + /* Clear all interrupts status bits*/ + hw_write(ci, OP_USBSTS, 0xffffffff, 0xffffffff); + + ci->rev = ci_get_revision(ci); + + dev_dbg(ci->dev, + "revision: %d, lpm: %d; cap: %px op: %px\n", + ci->rev, 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; +} + +void hw_phymode_configure(struct ci_hdrc *ci) +{ + u32 portsc, lpm, sts = 0; + + switch (ci->platdata->phy_mode) { + case USBPHY_INTERFACE_MODE_UTMI: + portsc = PORTSC_PTS(PTS_UTMI); + lpm = DEVLC_PTS(PTS_UTMI); + break; + case USBPHY_INTERFACE_MODE_UTMIW: + portsc = PORTSC_PTS(PTS_UTMI) | PORTSC_PTW; + lpm = DEVLC_PTS(PTS_UTMI) | DEVLC_PTW; + break; + case USBPHY_INTERFACE_MODE_ULPI: + portsc = PORTSC_PTS(PTS_ULPI); + lpm = DEVLC_PTS(PTS_ULPI); + break; + case USBPHY_INTERFACE_MODE_SERIAL: + portsc = PORTSC_PTS(PTS_SERIAL); + lpm = DEVLC_PTS(PTS_SERIAL); + sts = 1; + break; + case USBPHY_INTERFACE_MODE_HSIC: + portsc = PORTSC_PTS(PTS_HSIC); + lpm = DEVLC_PTS(PTS_HSIC); + break; + default: + return; + } + + if (ci->hw_bank.lpm) { + hw_write(ci, OP_DEVLC, DEVLC_PTS(7) | DEVLC_PTW, lpm); + if (sts) + hw_write(ci, OP_DEVLC, DEVLC_STS, DEVLC_STS); + } else { + hw_write(ci, OP_PORTSC, PORTSC_PTS(7) | PORTSC_PTW, portsc); + if (sts) + hw_write(ci, OP_PORTSC, PORTSC_STS, PORTSC_STS); + } +} +EXPORT_SYMBOL_GPL(hw_phymode_configure); + +/** + * _ci_usb_phy_init: initialize phy taking in account both phy and usb_phy + * interfaces + * @ci: the controller + * + * This function returns an error code if the phy failed to init + */ +static int _ci_usb_phy_init(struct ci_hdrc *ci) +{ + int ret; + + if (ci->phy) { + ret = phy_init(ci->phy); + if (ret) + return ret; + + ret = phy_power_on(ci->phy); + if (ret) { + phy_exit(ci->phy); + return ret; + } + } else { + ret = usb_phy_init(ci->usb_phy); + } + + return ret; +} + +/** + * ci_usb_phy_exit: deinitialize phy taking in account both phy and usb_phy + * interfaces + * @ci: the controller + */ +static void ci_usb_phy_exit(struct ci_hdrc *ci) +{ + if (ci->platdata->flags & CI_HDRC_OVERRIDE_PHY_CONTROL) + return; + + if (ci->phy) { + phy_power_off(ci->phy); + phy_exit(ci->phy); + } else { + usb_phy_shutdown(ci->usb_phy); + } +} + +/** + * ci_usb_phy_init: initialize phy according to different phy type + * @ci: the controller + * + * This function returns an error code if usb_phy_init has failed + */ +static int ci_usb_phy_init(struct ci_hdrc *ci) +{ + int ret; + + if (ci->platdata->flags & CI_HDRC_OVERRIDE_PHY_CONTROL) + return 0; + + switch (ci->platdata->phy_mode) { + case USBPHY_INTERFACE_MODE_UTMI: + case USBPHY_INTERFACE_MODE_UTMIW: + case USBPHY_INTERFACE_MODE_HSIC: + ret = _ci_usb_phy_init(ci); + if (!ret) + hw_wait_phy_stable(); + else + return ret; + hw_phymode_configure(ci); + break; + case USBPHY_INTERFACE_MODE_ULPI: + case USBPHY_INTERFACE_MODE_SERIAL: + hw_phymode_configure(ci); + ret = _ci_usb_phy_init(ci); + if (ret) + return ret; + break; + default: + ret = _ci_usb_phy_init(ci); + if (!ret) + hw_wait_phy_stable(); + } + + return ret; +} + + +/** + * ci_platform_configure: do controller configure + * @ci: the controller + * + */ +void ci_platform_configure(struct ci_hdrc *ci) +{ + bool is_device_mode, is_host_mode; + + is_device_mode = hw_read(ci, OP_USBMODE, USBMODE_CM) == USBMODE_CM_DC; + is_host_mode = hw_read(ci, OP_USBMODE, USBMODE_CM) == USBMODE_CM_HC; + + if (is_device_mode) { + phy_set_mode(ci->phy, PHY_MODE_USB_DEVICE); + + if (ci->platdata->flags & CI_HDRC_DISABLE_DEVICE_STREAMING) + hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, + USBMODE_CI_SDIS); + } + + if (is_host_mode) { + phy_set_mode(ci->phy, PHY_MODE_USB_HOST); + + if (ci->platdata->flags & CI_HDRC_DISABLE_HOST_STREAMING) + hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, + USBMODE_CI_SDIS); + } + + if (ci->platdata->flags & CI_HDRC_FORCE_FULLSPEED) { + if (ci->hw_bank.lpm) + hw_write(ci, OP_DEVLC, DEVLC_PFSC, DEVLC_PFSC); + else + hw_write(ci, OP_PORTSC, PORTSC_PFSC, PORTSC_PFSC); + } + + if (ci->platdata->flags & CI_HDRC_SET_NON_ZERO_TTHA) + hw_write(ci, OP_TTCTRL, TTCTRL_TTHA_MASK, TTCTRL_TTHA); + + hw_write(ci, OP_USBCMD, 0xff0000, ci->platdata->itc_setting << 16); + + if (ci->platdata->flags & CI_HDRC_OVERRIDE_AHB_BURST) + hw_write_id_reg(ci, ID_SBUSCFG, AHBBRST_MASK, + ci->platdata->ahb_burst_config); + + /* override burst size, take effect only when ahb_burst_config is 0 */ + if (!hw_read_id_reg(ci, ID_SBUSCFG, AHBBRST_MASK)) { + if (ci->platdata->flags & CI_HDRC_OVERRIDE_TX_BURST) + hw_write(ci, OP_BURSTSIZE, TX_BURST_MASK, + ci->platdata->tx_burst_size << __ffs(TX_BURST_MASK)); + + if (ci->platdata->flags & CI_HDRC_OVERRIDE_RX_BURST) + hw_write(ci, OP_BURSTSIZE, RX_BURST_MASK, + ci->platdata->rx_burst_size); + } +} + +/** + * hw_controller_reset: do controller reset + * @ci: the controller + * + * This function returns an error code + */ +static int hw_controller_reset(struct ci_hdrc *ci) +{ + int count = 0; + + hw_write(ci, OP_USBCMD, USBCMD_RST, USBCMD_RST); + while (hw_read(ci, OP_USBCMD, USBCMD_RST)) { + udelay(10); + if (count++ > 1000) + return -ETIMEDOUT; + } + + return 0; +} + +/** + * hw_device_reset: resets chip (execute without interruption) + * @ci: the controller + * + * This function returns an error code + */ +int hw_device_reset(struct ci_hdrc *ci) +{ + int ret; + + /* should flush & stop before reset */ + hw_write(ci, OP_ENDPTFLUSH, ~0, ~0); + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + + ret = hw_controller_reset(ci); + if (ret) { + dev_err(ci->dev, "error resetting controller, ret=%d\n", ret); + return ret; + } + + if (ci->platdata->notify_event) { + ret = ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_RESET_EVENT); + if (ret) + return ret; + } + + /* USBMODE should be configured step by step */ + hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE); + hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_DC); + /* HW >= 2.3 */ + hw_write(ci, OP_USBMODE, USBMODE_SLOM, USBMODE_SLOM); + + if (hw_read(ci, OP_USBMODE, USBMODE_CM) != USBMODE_CM_DC) { + dev_err(ci->dev, "cannot enter in %s device mode\n", + ci_role(ci)->name); + dev_err(ci->dev, "lpm = %i\n", ci->hw_bank.lpm); + return -ENODEV; + } + + ci_platform_configure(ci); + + return 0; +} + +static irqreturn_t ci_irq_handler(int irq, void *data) +{ + struct ci_hdrc *ci = data; + irqreturn_t ret = IRQ_NONE; + u32 otgsc = 0; + + if (ci->in_lpm) { + /* + * If we already have a wakeup irq pending there, + * let's just return to wait resume finished firstly. + */ + if (ci->wakeup_int) + return IRQ_HANDLED; + + disable_irq_nosync(irq); + ci->wakeup_int = true; + pm_runtime_get(ci->dev); + return IRQ_HANDLED; + } + + if (ci->is_otg) { + otgsc = hw_read_otgsc(ci, ~0); + if (ci_otg_is_fsm_mode(ci)) { + ret = ci_otg_fsm_irq(ci); + if (ret == IRQ_HANDLED) + return ret; + } + + /* + * Handle id change interrupt, it indicates device/host function + * switch. + */ + if ((otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) { + ci->id_event = true; + /* Clear ID change irq status */ + hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS); + } + + /* + * Handle vbus change interrupt, it indicates device connection + * and disconnection events. + */ + if ((otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) { + ci->b_sess_valid_event = true; + /* Clear BSV irq */ + hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS); + } + + if (ci->id_event || ci->b_sess_valid_event) { + ci_otg_queue_work(ci); + return IRQ_HANDLED; + } + } + + /* Handle device/host interrupt */ + if (ci->role != CI_ROLE_END) + ret = ci_role(ci)->irq(ci); + + return ret; +} + +static void ci_irq(struct ci_hdrc *ci) +{ + unsigned long flags; + + local_irq_save(flags); + ci_irq_handler(ci->irq, ci); + local_irq_restore(flags); +} + +static int ci_cable_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct ci_hdrc_cable *cbl = container_of(nb, struct ci_hdrc_cable, nb); + struct ci_hdrc *ci = cbl->ci; + + cbl->connected = event; + cbl->changed = true; + + ci_irq(ci); + return NOTIFY_DONE; +} + +static enum usb_role ci_usb_role_switch_get(struct usb_role_switch *sw) +{ + struct ci_hdrc *ci = usb_role_switch_get_drvdata(sw); + enum usb_role role; + unsigned long flags; + + spin_lock_irqsave(&ci->lock, flags); + role = ci_role_to_usb_role(ci); + spin_unlock_irqrestore(&ci->lock, flags); + + return role; +} + +static int ci_usb_role_switch_set(struct usb_role_switch *sw, + enum usb_role role) +{ + struct ci_hdrc *ci = usb_role_switch_get_drvdata(sw); + struct ci_hdrc_cable *cable; + + cable = &ci->platdata->id_extcon; + cable->changed = true; + cable->connected = (role == USB_ROLE_HOST); + + cable = &ci->platdata->vbus_extcon; + cable->changed = true; + cable->connected = (role == USB_ROLE_DEVICE); + + ci_irq(ci); + return 0; +} + +static enum ci_role ci_get_role(struct ci_hdrc *ci) +{ + enum ci_role role; + + if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { + if (ci->is_otg) { + role = ci_otg_role(ci); + hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); + } else { + /* + * If the controller is not OTG capable, but support + * role switch, the defalt role is gadget, and the + * user can switch it through debugfs. + */ + role = CI_ROLE_GADGET; + } + } else { + role = ci->roles[CI_ROLE_HOST] ? CI_ROLE_HOST + : CI_ROLE_GADGET; + } + + return role; +} + +static int ci_get_platdata(struct device *dev, + struct ci_hdrc_platform_data *platdata) +{ + struct extcon_dev *ext_vbus, *ext_id; + struct ci_hdrc_cable *cable; + int ret; + + if (!platdata->phy_mode) + platdata->phy_mode = of_usb_get_phy_mode(dev->of_node); + + if (!platdata->dr_mode) + platdata->dr_mode = usb_get_dr_mode(dev); + + if (platdata->dr_mode == USB_DR_MODE_UNKNOWN) + platdata->dr_mode = USB_DR_MODE_OTG; + + if (platdata->dr_mode != USB_DR_MODE_PERIPHERAL) { + /* Get the vbus regulator */ + platdata->reg_vbus = devm_regulator_get_optional(dev, "vbus"); + if (PTR_ERR(platdata->reg_vbus) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (PTR_ERR(platdata->reg_vbus) == -ENODEV) { + /* no vbus regulator is needed */ + platdata->reg_vbus = NULL; + } else if (IS_ERR(platdata->reg_vbus)) { + dev_err(dev, "Getting regulator error: %ld\n", + PTR_ERR(platdata->reg_vbus)); + return PTR_ERR(platdata->reg_vbus); + } + /* Get TPL support */ + if (!platdata->tpl_support) + platdata->tpl_support = + of_usb_host_tpl_support(dev->of_node); + } + + if (platdata->dr_mode == USB_DR_MODE_OTG) { + /* We can support HNP and SRP of OTG 2.0 */ + platdata->ci_otg_caps.otg_rev = 0x0200; + platdata->ci_otg_caps.hnp_support = true; + platdata->ci_otg_caps.srp_support = true; + + /* Update otg capabilities by DT properties */ + ret = of_usb_update_otg_caps(dev->of_node, + &platdata->ci_otg_caps); + if (ret) + return ret; + } + + if (usb_get_maximum_speed(dev) == USB_SPEED_FULL) + platdata->flags |= CI_HDRC_FORCE_FULLSPEED; + + of_property_read_u32(dev->of_node, "phy-clkgate-delay-us", + &platdata->phy_clkgate_delay_us); + + platdata->itc_setting = 1; + + of_property_read_u32(dev->of_node, "itc-setting", + &platdata->itc_setting); + + ret = of_property_read_u32(dev->of_node, "ahb-burst-config", + &platdata->ahb_burst_config); + if (!ret) { + platdata->flags |= CI_HDRC_OVERRIDE_AHB_BURST; + } else if (ret != -EINVAL) { + dev_err(dev, "failed to get ahb-burst-config\n"); + return ret; + } + + ret = of_property_read_u32(dev->of_node, "tx-burst-size-dword", + &platdata->tx_burst_size); + if (!ret) { + platdata->flags |= CI_HDRC_OVERRIDE_TX_BURST; + } else if (ret != -EINVAL) { + dev_err(dev, "failed to get tx-burst-size-dword\n"); + return ret; + } + + ret = of_property_read_u32(dev->of_node, "rx-burst-size-dword", + &platdata->rx_burst_size); + if (!ret) { + platdata->flags |= CI_HDRC_OVERRIDE_RX_BURST; + } else if (ret != -EINVAL) { + dev_err(dev, "failed to get rx-burst-size-dword\n"); + return ret; + } + + if (of_property_read_bool(dev->of_node, "non-zero-ttctrl-ttha")) + platdata->flags |= CI_HDRC_SET_NON_ZERO_TTHA; + + ext_id = ERR_PTR(-ENODEV); + ext_vbus = ERR_PTR(-ENODEV); + if (of_property_present(dev->of_node, "extcon")) { + /* Each one of them is not mandatory */ + ext_vbus = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(ext_vbus) && PTR_ERR(ext_vbus) != -ENODEV) + return PTR_ERR(ext_vbus); + + ext_id = extcon_get_edev_by_phandle(dev, 1); + if (IS_ERR(ext_id) && PTR_ERR(ext_id) != -ENODEV) + return PTR_ERR(ext_id); + } + + cable = &platdata->vbus_extcon; + cable->nb.notifier_call = ci_cable_notifier; + cable->edev = ext_vbus; + + if (!IS_ERR(ext_vbus)) { + ret = extcon_get_state(cable->edev, EXTCON_USB); + if (ret) + cable->connected = true; + else + cable->connected = false; + } + + cable = &platdata->id_extcon; + cable->nb.notifier_call = ci_cable_notifier; + cable->edev = ext_id; + + if (!IS_ERR(ext_id)) { + ret = extcon_get_state(cable->edev, EXTCON_USB_HOST); + if (ret) + cable->connected = true; + else + cable->connected = false; + } + + platdata->pctl = devm_pinctrl_get(dev); + if (!IS_ERR(platdata->pctl)) { + struct pinctrl_state *p; + + p = pinctrl_lookup_state(platdata->pctl, "default"); + if (!IS_ERR(p)) + platdata->pins_default = p; + + p = pinctrl_lookup_state(platdata->pctl, "host"); + if (!IS_ERR(p)) + platdata->pins_host = p; + + p = pinctrl_lookup_state(platdata->pctl, "device"); + if (!IS_ERR(p)) + platdata->pins_device = p; + } + + if (!platdata->enter_lpm) + platdata->enter_lpm = ci_hdrc_enter_lpm_common; + + return 0; +} + +static int ci_extcon_register(struct ci_hdrc *ci) +{ + struct ci_hdrc_cable *id, *vbus; + int ret; + + id = &ci->platdata->id_extcon; + id->ci = ci; + if (!IS_ERR_OR_NULL(id->edev)) { + ret = devm_extcon_register_notifier(ci->dev, id->edev, + EXTCON_USB_HOST, &id->nb); + if (ret < 0) { + dev_err(ci->dev, "register ID failed\n"); + return ret; + } + } + + vbus = &ci->platdata->vbus_extcon; + vbus->ci = ci; + if (!IS_ERR_OR_NULL(vbus->edev)) { + ret = devm_extcon_register_notifier(ci->dev, vbus->edev, + EXTCON_USB, &vbus->nb); + if (ret < 0) { + dev_err(ci->dev, "register VBUS failed\n"); + return ret; + } + } + + return 0; +} + +static void ci_power_lost_work(struct work_struct *work) +{ + struct ci_hdrc *ci = container_of(work, struct ci_hdrc, power_lost_work); + enum ci_role role; + + disable_irq_nosync(ci->irq); + pm_runtime_get_sync(ci->dev); + if (!ci_otg_is_fsm_mode(ci)) { + role = ci_get_role(ci); + + if (ci->role != role) { + ci_handle_id_switch(ci); + } else if (role == CI_ROLE_GADGET) { + if (ci->is_otg && hw_read_otgsc(ci, OTGSC_BSV)) + usb_gadget_vbus_connect(&ci->gadget); + } + } + pm_runtime_put_sync(ci->dev); + enable_irq(ci->irq); +} + +static DEFINE_IDA(ci_ida); + +struct platform_device *ci_hdrc_add_device(struct device *dev, + struct resource *res, int nres, + struct ci_hdrc_platform_data *platdata) +{ + struct platform_device *pdev; + int id, ret; + + ret = ci_get_platdata(dev, platdata); + if (ret) + return ERR_PTR(ret); + + id = ida_alloc(&ci_ida, 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; + platform_device_set_of_node_from_dev(pdev, dev); + + 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; + + dev_pm_domain_detach(&pdev->dev, false); + + return pdev; + +err: + platform_device_put(pdev); +put_id: + ida_free(&ci_ida, id); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(ci_hdrc_add_device); + +void ci_hdrc_remove_device(struct platform_device *pdev) +{ + int id = pdev->id; + platform_device_unregister(pdev); + ida_free(&ci_ida, id); +} +EXPORT_SYMBOL_GPL(ci_hdrc_remove_device); + +/** + * ci_hdrc_query_available_role: get runtime available operation mode + * + * The glue layer can get current operation mode (host/peripheral/otg) + * This function should be called after ci core device has created. + * + * @pdev: the platform device of ci core. + * + * Return runtime usb_dr_mode. + */ +enum usb_dr_mode ci_hdrc_query_available_role(struct platform_device *pdev) +{ + struct ci_hdrc *ci = platform_get_drvdata(pdev); + + if (!ci) + return USB_DR_MODE_UNKNOWN; + if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) + return USB_DR_MODE_OTG; + else if (ci->roles[CI_ROLE_HOST]) + return USB_DR_MODE_HOST; + else if (ci->roles[CI_ROLE_GADGET]) + return USB_DR_MODE_PERIPHERAL; + else + return USB_DR_MODE_UNKNOWN; +} +EXPORT_SYMBOL_GPL(ci_hdrc_query_available_role); + +static inline void ci_role_destroy(struct ci_hdrc *ci) +{ + ci_hdrc_gadget_destroy(ci); + ci_hdrc_host_destroy(ci); + if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) + ci_hdrc_otg_destroy(ci); +} + +static void ci_get_otg_capable(struct ci_hdrc *ci) +{ + if (ci->platdata->flags & CI_HDRC_DUAL_ROLE_NOT_OTG) + ci->is_otg = false; + else + ci->is_otg = (hw_read(ci, CAP_DCCPARAMS, + DCCPARAMS_DC | DCCPARAMS_HC) + == (DCCPARAMS_DC | DCCPARAMS_HC)); + if (ci->is_otg) { + dev_dbg(ci->dev, "It is OTG capable controller\n"); + /* Disable and clear all OTG irq */ + hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, + OTGSC_INT_STATUS_BITS); + } +} + +static ssize_t role_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + + if (ci->role != CI_ROLE_END) + return sprintf(buf, "%s\n", ci_role(ci)->name); + + return 0; +} + +static ssize_t role_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + enum ci_role role; + int ret; + + if (!(ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET])) { + dev_warn(dev, "Current configuration is not dual-role, quit\n"); + return -EPERM; + } + + for (role = CI_ROLE_HOST; role < CI_ROLE_END; role++) + if (!strncmp(buf, ci->roles[role]->name, + strlen(ci->roles[role]->name))) + break; + + if (role == CI_ROLE_END) + return -EINVAL; + + mutex_lock(&ci->mutex); + + if (role == ci->role) { + mutex_unlock(&ci->mutex); + return n; + } + + pm_runtime_get_sync(dev); + disable_irq(ci->irq); + ci_role_stop(ci); + ret = ci_role_start(ci, role); + if (!ret && ci->role == CI_ROLE_GADGET) + ci_handle_vbus_change(ci); + enable_irq(ci->irq); + pm_runtime_put_sync(dev); + mutex_unlock(&ci->mutex); + + return (ret == 0) ? n : ret; +} +static DEVICE_ATTR_RW(role); + +static struct attribute *ci_attrs[] = { + &dev_attr_role.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ci); + +static int ci_hdrc_probe(struct platform_device *pdev) +{ + struct usb_role_switch_desc ci_role_switch = {}; + struct device *dev = &pdev->dev; + struct ci_hdrc *ci; + struct resource *res; + void __iomem *base; + int ret; + enum usb_dr_mode dr_mode; + + if (!dev_get_platdata(dev)) { + dev_err(dev, "platform data missing\n"); + return -ENODEV; + } + + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) + return PTR_ERR(base); + + ci = devm_kzalloc(dev, sizeof(*ci), GFP_KERNEL); + if (!ci) + return -ENOMEM; + + spin_lock_init(&ci->lock); + mutex_init(&ci->mutex); + INIT_WORK(&ci->power_lost_work, ci_power_lost_work); + + ci->dev = dev; + ci->platdata = dev_get_platdata(dev); + ci->imx28_write_fix = !!(ci->platdata->flags & + CI_HDRC_IMX28_WRITE_FIX); + ci->supports_runtime_pm = !!(ci->platdata->flags & + CI_HDRC_SUPPORTS_RUNTIME_PM); + ci->has_portsc_pec_bug = !!(ci->platdata->flags & + CI_HDRC_HAS_PORTSC_PEC_MISSED); + ci->has_short_pkt_limit = !!(ci->platdata->flags & + CI_HDRC_HAS_SHORT_PKT_LIMIT); + platform_set_drvdata(pdev, ci); + + ret = hw_device_init(ci, base); + if (ret < 0) { + dev_err(dev, "can't initialize hardware\n"); + return -ENODEV; + } + + ret = ci_ulpi_init(ci); + if (ret) + return ret; + + if (ci->platdata->phy) { + ci->phy = ci->platdata->phy; + } else if (ci->platdata->usb_phy) { + ci->usb_phy = ci->platdata->usb_phy; + } else { + /* Look for a generic PHY first */ + ci->phy = devm_phy_get(dev->parent, "usb-phy"); + + if (PTR_ERR(ci->phy) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto ulpi_exit; + } else if (IS_ERR(ci->phy)) { + ci->phy = NULL; + } + + /* Look for a legacy USB PHY from device-tree next */ + if (!ci->phy) { + ci->usb_phy = devm_usb_get_phy_by_phandle(dev->parent, + "phys", 0); + + if (PTR_ERR(ci->usb_phy) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto ulpi_exit; + } else if (IS_ERR(ci->usb_phy)) { + ci->usb_phy = NULL; + } + } + + /* Look for any registered legacy USB PHY as last resort */ + if (!ci->phy && !ci->usb_phy) { + ci->usb_phy = devm_usb_get_phy(dev->parent, + USB_PHY_TYPE_USB2); + + if (PTR_ERR(ci->usb_phy) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto ulpi_exit; + } else if (IS_ERR(ci->usb_phy)) { + ci->usb_phy = NULL; + } + } + + /* No USB PHY was found in the end */ + if (!ci->phy && !ci->usb_phy) { + ret = -ENXIO; + goto ulpi_exit; + } + } + + ret = ci_usb_phy_init(ci); + if (ret) { + dev_err(dev, "unable to init phy: %d\n", ret); + goto ulpi_exit; + } + + ci->hw_bank.phys = res->start; + + ci->irq = platform_get_irq(pdev, 0); + if (ci->irq < 0) { + ret = ci->irq; + goto deinit_phy; + } + + ci_get_otg_capable(ci); + + dr_mode = ci->platdata->dr_mode; + /* initialize role(s) before the interrupt is requested */ + if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { + ret = ci_hdrc_host_init(ci); + if (ret) { + if (ret == -ENXIO) + dev_info(dev, "doesn't support host\n"); + else + goto deinit_phy; + } + } + + if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) { + ret = ci_hdrc_gadget_init(ci); + if (ret) { + if (ret == -ENXIO) + dev_info(dev, "doesn't support gadget\n"); + else + goto deinit_host; + } + } + + if (!ci->roles[CI_ROLE_HOST] && !ci->roles[CI_ROLE_GADGET]) { + dev_err(dev, "no supported roles\n"); + ret = -ENODEV; + goto deinit_gadget; + } + + if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) { + ret = ci_hdrc_otg_init(ci); + if (ret) { + dev_err(dev, "init otg fails, ret = %d\n", ret); + goto deinit_gadget; + } + } + + if (device_property_read_bool(dev, "usb-role-switch")) { + ci_role_switch.set = ci_usb_role_switch_set; + ci_role_switch.get = ci_usb_role_switch_get; + ci_role_switch.allow_userspace_control = true; + ci_role_switch.fwnode = dev_fwnode(dev); + ci_role_switch.driver_data = ci; + ci->role_switch = usb_role_switch_register(dev, + &ci_role_switch); + if (IS_ERR(ci->role_switch)) { + ret = PTR_ERR(ci->role_switch); + goto deinit_otg; + } + } + + ci->role = ci_get_role(ci); + if (!ci_otg_is_fsm_mode(ci)) { + ret = ci_role_start(ci, ci->role); + if (ret) { + dev_err(dev, "can't start %s role\n", + ci_role(ci)->name); + goto stop; + } + + /* only update vbus status for peripheral */ + if (ci->role == CI_ROLE_GADGET) + ci_handle_vbus_change(ci); + } + + ret = devm_request_irq(dev, ci->irq, ci_irq_handler, IRQF_SHARED, + ci->platdata->name, ci); + if (ret) + goto stop; + + ret = ci_extcon_register(ci); + if (ret) + goto stop; + + if (ci->supports_runtime_pm) { + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, 2000); + pm_runtime_mark_last_busy(ci->dev); + pm_runtime_use_autosuspend(&pdev->dev); + } + + if (ci_otg_is_fsm_mode(ci)) + ci_hdrc_otg_fsm_start(ci); + + device_set_wakeup_capable(&pdev->dev, true); + dbg_create_files(ci); + + return 0; + +stop: + if (ci->role_switch) + usb_role_switch_unregister(ci->role_switch); +deinit_otg: + if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) + ci_hdrc_otg_destroy(ci); +deinit_gadget: + ci_hdrc_gadget_destroy(ci); +deinit_host: + ci_hdrc_host_destroy(ci); +deinit_phy: + ci_usb_phy_exit(ci); +ulpi_exit: + ci_ulpi_exit(ci); + + return ret; +} + +static void ci_hdrc_remove(struct platform_device *pdev) +{ + struct ci_hdrc *ci = platform_get_drvdata(pdev); + + if (ci->role_switch) + usb_role_switch_unregister(ci->role_switch); + + if (ci->supports_runtime_pm) { + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + } + + dbg_remove_files(ci); + ci_role_destroy(ci); + ci_hdrc_enter_lpm(ci, true); + ci_usb_phy_exit(ci); + ci_ulpi_exit(ci); +} + +#ifdef CONFIG_PM +/* Prepare wakeup by SRP before suspend */ +static void ci_otg_fsm_suspend_for_srp(struct ci_hdrc *ci) +{ + if ((ci->fsm.otg->state == OTG_STATE_A_IDLE) && + !hw_read_otgsc(ci, OTGSC_ID)) { + hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, + PORTSC_PP); + hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_WKCN, + PORTSC_WKCN); + } +} + +/* Handle SRP when wakeup by data pulse */ +static void ci_otg_fsm_wakeup_by_srp(struct ci_hdrc *ci) +{ + if ((ci->fsm.otg->state == OTG_STATE_A_IDLE) && + (ci->fsm.a_bus_drop == 1) && (ci->fsm.a_bus_req == 0)) { + if (!hw_read_otgsc(ci, OTGSC_ID)) { + ci->fsm.a_srp_det = 1; + ci->fsm.a_bus_drop = 0; + } else { + ci->fsm.id = 1; + } + ci_otg_queue_work(ci); + } +} + +static void ci_controller_suspend(struct ci_hdrc *ci) +{ + disable_irq(ci->irq); + ci_hdrc_enter_lpm(ci, true); + if (ci->platdata->phy_clkgate_delay_us) + usleep_range(ci->platdata->phy_clkgate_delay_us, + ci->platdata->phy_clkgate_delay_us + 50); + usb_phy_set_suspend(ci->usb_phy, 1); + ci->in_lpm = true; + enable_irq(ci->irq); +} + +/* + * Handle the wakeup interrupt triggered by extcon connector + * We need to call ci_irq again for extcon since the first + * interrupt (wakeup int) only let the controller be out of + * low power mode, but not handle any interrupts. + */ +static void ci_extcon_wakeup_int(struct ci_hdrc *ci) +{ + struct ci_hdrc_cable *cable_id, *cable_vbus; + u32 otgsc = hw_read_otgsc(ci, ~0); + + cable_id = &ci->platdata->id_extcon; + cable_vbus = &ci->platdata->vbus_extcon; + + if ((!IS_ERR(cable_id->edev) || ci->role_switch) + && ci->is_otg && + (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) + ci_irq(ci); + + if ((!IS_ERR(cable_vbus->edev) || ci->role_switch) + && ci->is_otg && + (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) + ci_irq(ci); +} + +static int ci_controller_resume(struct device *dev) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + int ret; + + dev_dbg(dev, "at %s\n", __func__); + + if (!ci->in_lpm) { + WARN_ON(1); + return 0; + } + + ci_hdrc_enter_lpm(ci, false); + + ret = ci_ulpi_resume(ci); + if (ret) + return ret; + + if (ci->usb_phy) { + usb_phy_set_suspend(ci->usb_phy, 0); + usb_phy_set_wakeup(ci->usb_phy, false); + hw_wait_phy_stable(); + } + + ci->in_lpm = false; + if (ci->wakeup_int) { + ci->wakeup_int = false; + pm_runtime_put_autosuspend(ci->dev); + enable_irq(ci->irq); + if (ci_otg_is_fsm_mode(ci)) + ci_otg_fsm_wakeup_by_srp(ci); + ci_extcon_wakeup_int(ci); + } + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int ci_suspend(struct device *dev) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + + if (ci->wq) + flush_workqueue(ci->wq); + /* + * Controller needs to be active during suspend, otherwise the core + * may run resume when the parent is at suspend if other driver's + * suspend fails, it occurs before parent's suspend has not started, + * but the core suspend has finished. + */ + if (ci->in_lpm) + pm_runtime_resume(dev); + + if (ci->in_lpm) { + WARN_ON(1); + return 0; + } + + /* Extra routine per role before system suspend */ + if (ci->role != CI_ROLE_END && ci_role(ci)->suspend) + ci_role(ci)->suspend(ci); + + if (device_may_wakeup(dev)) { + if (ci_otg_is_fsm_mode(ci)) + ci_otg_fsm_suspend_for_srp(ci); + + usb_phy_set_wakeup(ci->usb_phy, true); + enable_irq_wake(ci->irq); + } + + ci_controller_suspend(ci); + + return 0; +} + +static int ci_resume(struct device *dev) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + bool power_lost; + int ret; + + /* Since ASYNCLISTADDR (host mode) and ENDPTLISTADDR (device + * mode) share the same register address. We can check if + * controller resume from power lost based on this address + * due to this register will be reset after power lost. + */ + power_lost = !hw_read(ci, OP_ENDPTLISTADDR, ~0); + + if (device_may_wakeup(dev)) + disable_irq_wake(ci->irq); + + ret = ci_controller_resume(dev); + if (ret) + return ret; + + if (power_lost) { + /* shutdown and re-init for phy */ + ci_usb_phy_exit(ci); + ci_usb_phy_init(ci); + } + + /* Extra routine per role after system resume */ + if (ci->role != CI_ROLE_END && ci_role(ci)->resume) + ci_role(ci)->resume(ci, power_lost); + + if (power_lost) + queue_work(system_freezable_wq, &ci->power_lost_work); + + if (ci->supports_runtime_pm) { + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + return ret; +} +#endif /* CONFIG_PM_SLEEP */ + +static int ci_runtime_suspend(struct device *dev) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + + dev_dbg(dev, "at %s\n", __func__); + + if (ci->in_lpm) { + WARN_ON(1); + return 0; + } + + if (ci_otg_is_fsm_mode(ci)) + ci_otg_fsm_suspend_for_srp(ci); + + usb_phy_set_wakeup(ci->usb_phy, true); + ci_controller_suspend(ci); + + return 0; +} + +static int ci_runtime_resume(struct device *dev) +{ + return ci_controller_resume(dev); +} + +#endif /* CONFIG_PM */ +static const struct dev_pm_ops ci_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ci_suspend, ci_resume) + SET_RUNTIME_PM_OPS(ci_runtime_suspend, ci_runtime_resume, NULL) +}; + +static struct platform_driver ci_hdrc_driver = { + .probe = ci_hdrc_probe, + .remove = ci_hdrc_remove, + .driver = { + .name = "ci_hdrc", + .pm = &ci_pm_ops, + .dev_groups = ci_groups, + }, +}; + +static int __init ci_hdrc_platform_register(void) +{ + ci_hdrc_host_driver_init(); + return platform_driver_register(&ci_hdrc_driver); +} +module_init(ci_hdrc_platform_register); + +static void __exit ci_hdrc_platform_unregister(void) +{ + platform_driver_unregister(&ci_hdrc_driver); +} +module_exit(ci_hdrc_platform_unregister); + +MODULE_ALIAS("platform:ci_hdrc"); +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 000000000..e72c43615 --- /dev/null +++ b/drivers/usb/chipidea/debug.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" +#include "udc.h" +#include "bits.h" +#include "otg.h" + +/* + * ci_device_show: prints information about device capabilities and status + */ +static int ci_device_show(struct seq_file *s, void *data) +{ + struct ci_hdrc *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; +} +DEFINE_SHOW_ATTRIBUTE(ci_device); + +/* + * ci_port_test_show: reads port test mode + */ +static int ci_port_test_show(struct seq_file *s, void *data) +{ + struct ci_hdrc *ci = s->private; + unsigned long flags; + unsigned mode; + + pm_runtime_get_sync(ci->dev); + spin_lock_irqsave(&ci->lock, flags); + mode = hw_port_test_get(ci); + spin_unlock_irqrestore(&ci->lock, flags); + pm_runtime_put_sync(ci->dev); + + 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 ci_hdrc *ci = s->private; + unsigned long flags; + unsigned mode; + char buf[32]; + int ret; + + count = min_t(size_t, sizeof(buf) - 1, count); + if (copy_from_user(buf, ubuf, count)) + return -EFAULT; + + /* sscanf requires a zero terminated string */ + buf[count] = '\0'; + + if (sscanf(buf, "%u", &mode) != 1) + return -EINVAL; + + if (mode > 255) + return -EBADRQC; + + pm_runtime_get_sync(ci->dev); + spin_lock_irqsave(&ci->lock, flags); + ret = hw_port_test_set(ci, mode); + spin_unlock_irqrestore(&ci->lock, flags); + pm_runtime_put_sync(ci->dev); + + 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 ci_hdrc *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 ci_hw_ep *hweprx = &ci->ci_hw_ep[i]; + struct ci_hw_ep *hweptx = + &ci->ci_hw_ep[i + ci->hw_ep_max/2]; + seq_printf(s, "EP=%02i: RX=%08X TX=%08X\n", + i, (u32)hweprx->qh.dma, (u32)hweptx->qh.dma); + for (j = 0; j < (sizeof(struct ci_hw_qh)/sizeof(u32)); j++) + seq_printf(s, " %04X: %08X %08X\n", j, + *((u32 *)hweprx->qh.ptr + j), + *((u32 *)hweptx->qh.ptr + j)); + } + spin_unlock_irqrestore(&ci->lock, flags); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ci_qheads); + +/* + * ci_requests_show: DMA contents of all requests currently queued (all endpts) + */ +static int ci_requests_show(struct seq_file *s, void *data) +{ + struct ci_hdrc *ci = s->private; + unsigned long flags; + struct ci_hw_req *req = NULL; + struct td_node *node, *tmpnode; + unsigned i, j, qsize = sizeof(struct ci_hw_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_entry(req, &ci->ci_hw_ep[i].qh.queue, queue) { + list_for_each_entry_safe(node, tmpnode, &req->tds, td) { + seq_printf(s, "EP=%02i: TD=%08X %s\n", + i % (ci->hw_ep_max / 2), + (u32)node->dma, + ((i < ci->hw_ep_max/2) ? + "RX" : "TX")); + + for (j = 0; j < qsize; j++) + seq_printf(s, " %04X: %08X\n", j, + *((u32 *)node->ptr + j)); + } + } + spin_unlock_irqrestore(&ci->lock, flags); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ci_requests); + +static int ci_otg_show(struct seq_file *s, void *unused) +{ + struct ci_hdrc *ci = s->private; + struct otg_fsm *fsm; + + if (!ci || !ci_otg_is_fsm_mode(ci)) + return 0; + + fsm = &ci->fsm; + + /* ------ State ----- */ + seq_printf(s, "OTG state: %s\n\n", + usb_otg_state_string(ci->otg.state)); + + /* ------ State Machine Variables ----- */ + seq_printf(s, "a_bus_drop: %d\n", fsm->a_bus_drop); + + seq_printf(s, "a_bus_req: %d\n", fsm->a_bus_req); + + seq_printf(s, "a_srp_det: %d\n", fsm->a_srp_det); + + seq_printf(s, "a_vbus_vld: %d\n", fsm->a_vbus_vld); + + seq_printf(s, "b_conn: %d\n", fsm->b_conn); + + seq_printf(s, "adp_change: %d\n", fsm->adp_change); + + seq_printf(s, "power_up: %d\n", fsm->power_up); + + seq_printf(s, "a_bus_resume: %d\n", fsm->a_bus_resume); + + seq_printf(s, "a_bus_suspend: %d\n", fsm->a_bus_suspend); + + seq_printf(s, "a_conn: %d\n", fsm->a_conn); + + seq_printf(s, "b_bus_req: %d\n", fsm->b_bus_req); + + seq_printf(s, "b_bus_suspend: %d\n", fsm->b_bus_suspend); + + seq_printf(s, "b_se0_srp: %d\n", fsm->b_se0_srp); + + seq_printf(s, "b_ssend_srp: %d\n", fsm->b_ssend_srp); + + seq_printf(s, "b_sess_vld: %d\n", fsm->b_sess_vld); + + seq_printf(s, "b_srp_done: %d\n", fsm->b_srp_done); + + seq_printf(s, "drv_vbus: %d\n", fsm->drv_vbus); + + seq_printf(s, "loc_conn: %d\n", fsm->loc_conn); + + seq_printf(s, "loc_sof: %d\n", fsm->loc_sof); + + seq_printf(s, "adp_prb: %d\n", fsm->adp_prb); + + seq_printf(s, "id: %d\n", fsm->id); + + seq_printf(s, "protocol: %d\n", fsm->protocol); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ci_otg); + +static int ci_registers_show(struct seq_file *s, void *unused) +{ + struct ci_hdrc *ci = s->private; + u32 tmp_reg; + + if (!ci || ci->in_lpm) + return -EPERM; + + /* ------ Registers ----- */ + tmp_reg = hw_read_intr_enable(ci); + seq_printf(s, "USBINTR reg: %08x\n", tmp_reg); + + tmp_reg = hw_read_intr_status(ci); + seq_printf(s, "USBSTS reg: %08x\n", tmp_reg); + + tmp_reg = hw_read(ci, OP_USBMODE, ~0); + seq_printf(s, "USBMODE reg: %08x\n", tmp_reg); + + tmp_reg = hw_read(ci, OP_USBCMD, ~0); + seq_printf(s, "USBCMD reg: %08x\n", tmp_reg); + + tmp_reg = hw_read(ci, OP_PORTSC, ~0); + seq_printf(s, "PORTSC reg: %08x\n", tmp_reg); + + if (ci->is_otg) { + tmp_reg = hw_read_otgsc(ci, ~0); + seq_printf(s, "OTGSC reg: %08x\n", tmp_reg); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ci_registers); + +/** + * dbg_create_files: initializes the attribute interface + * @ci: device + * + * This function returns an error code + */ +void dbg_create_files(struct ci_hdrc *ci) +{ + struct dentry *dir; + + dir = debugfs_create_dir(dev_name(ci->dev), usb_debug_root); + + debugfs_create_file("device", S_IRUGO, dir, ci, &ci_device_fops); + debugfs_create_file("port_test", S_IRUGO | S_IWUSR, dir, ci, &ci_port_test_fops); + debugfs_create_file("qheads", S_IRUGO, dir, ci, &ci_qheads_fops); + debugfs_create_file("requests", S_IRUGO, dir, ci, &ci_requests_fops); + + if (ci_otg_is_fsm_mode(ci)) + debugfs_create_file("otg", S_IRUGO, dir, ci, &ci_otg_fops); + + debugfs_create_file("registers", S_IRUGO, dir, ci, &ci_registers_fops); +} + +/** + * dbg_remove_files: destroys the attribute interface + * @ci: device + */ +void dbg_remove_files(struct ci_hdrc *ci) +{ + debugfs_lookup_and_remove(dev_name(ci->dev), usb_debug_root); +} diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c new file mode 100644 index 000000000..ced6076a8 --- /dev/null +++ b/drivers/usb/chipidea/host.c @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * host.c - ChipIdea USB host controller driver + * + * Copyright (c) 2012 Intel Corporation + * + * Author: Alexander Shishkin + */ + +#include +#include +#include +#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 int (*orig_bus_suspend)(struct usb_hcd *hcd); + +struct ehci_ci_priv { + struct regulator *reg_vbus; + bool enabled; +}; + +struct ci_hdrc_dma_aligned_buffer { + void *original_buffer; + u8 data[]; +}; + +static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct ehci_ci_priv *priv = (struct ehci_ci_priv *)ehci->priv; + struct device *dev = hcd->self.controller; + struct ci_hdrc *ci = dev_get_drvdata(dev); + int ret = 0; + int port = HCS_N_PORTS(ehci->hcs_params); + + if (priv->reg_vbus && enable != priv->enabled) { + if (port > 1) { + dev_warn(dev, + "Not support multi-port regulator control\n"); + return 0; + } + if (enable) + ret = regulator_enable(priv->reg_vbus); + else + ret = regulator_disable(priv->reg_vbus); + if (ret) { + dev_err(dev, + "Failed to %s vbus regulator, ret=%d\n", + str_enable_disable(enable), ret); + return ret; + } + priv->enabled = enable; + } + + if (ci->platdata->flags & CI_HDRC_PHY_VBUS_CONTROL) { + if (enable) + usb_phy_vbus_on(ci->usb_phy); + else + usb_phy_vbus_off(ci->usb_phy); + } + + if (enable && (ci->platdata->phy_mode == USBPHY_INTERFACE_MODE_HSIC)) { + /* + * Marvell 28nm HSIC PHY requires forcing the port to HS mode. + * As HSIC is always HS, this should be safe for others. + */ + hw_port_test_set(ci, 5); + hw_port_test_set(ci, 0); + } + return 0; +}; + +static int ehci_ci_reset(struct usb_hcd *hcd) +{ + struct device *dev = hcd->self.controller; + struct ci_hdrc *ci = dev_get_drvdata(dev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int ret; + + ret = ehci_setup(hcd); + if (ret) + return ret; + + ehci->need_io_watchdog = 0; + + if (ci->platdata->notify_event) { + ret = ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_RESET_EVENT); + if (ret) + return ret; + } + + ci_platform_configure(ci); + + return ret; +} + +static const struct ehci_driver_overrides ehci_ci_overrides = { + .extra_priv_size = sizeof(struct ehci_ci_priv), + .port_power = ehci_ci_portpower, + .reset = ehci_ci_reset, +}; + +static irqreturn_t host_irq(struct ci_hdrc *ci) +{ + return usb_hcd_irq(ci->irq, ci->hcd); +} + +static int host_start(struct ci_hdrc *ci) +{ + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + struct ehci_ci_priv *priv; + int ret; + + if (usb_disabled()) + return -ENODEV; + + hcd = __usb_create_hcd(&ci_ehci_hc_driver, ci->dev->parent, + ci->dev, dev_name(ci->dev), NULL); + 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->tpl_support = ci->platdata->tpl_support; + if (ci->phy || ci->usb_phy) { + hcd->skip_phy_initialization = 1; + if (ci->usb_phy) + hcd->usb_phy = ci->usb_phy; + } + + ehci = hcd_to_ehci(hcd); + ehci->caps = ci->hw_bank.cap; + ehci->has_hostpc = ci->hw_bank.lpm; + ehci->has_tdi_phy_lpm = ci->hw_bank.lpm; + ehci->imx28_write_fix = ci->imx28_write_fix; + ehci->has_ci_pec_bug = ci->has_portsc_pec_bug; + + priv = (struct ehci_ci_priv *)ehci->priv; + priv->reg_vbus = NULL; + + if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci)) { + if (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON) { + ret = regulator_enable(ci->platdata->reg_vbus); + if (ret) { + dev_err(ci->dev, + "Failed to enable vbus regulator, ret=%d\n", + ret); + goto put_hcd; + } + } else { + priv->reg_vbus = ci->platdata->reg_vbus; + } + } + + if (ci->platdata->pins_host) + pinctrl_select_state(ci->platdata->pctl, + ci->platdata->pins_host); + + ci->hcd = hcd; + + ret = usb_add_hcd(hcd, 0, 0); + if (ret) { + ci->hcd = NULL; + goto disable_reg; + } else { + struct usb_otg *otg = &ci->otg; + + if (ci_otg_is_fsm_mode(ci)) { + otg->host = &hcd->self; + hcd->self.otg_port = 1; + } + + if (ci->platdata->notify_event && + (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC)) + ci->platdata->notify_event + (ci, CI_HDRC_IMX_HSIC_ACTIVE_EVENT); + } + + return ret; + +disable_reg: + if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci) && + (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON)) + regulator_disable(ci->platdata->reg_vbus); +put_hcd: + usb_put_hcd(hcd); + + return ret; +} + +static void host_stop(struct ci_hdrc *ci) +{ + struct usb_hcd *hcd = ci->hcd; + + if (hcd) { + if (ci->platdata->notify_event) + ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_STOPPED_EVENT); + usb_remove_hcd(hcd); + ci->role = CI_ROLE_END; + synchronize_irq(ci->irq); + usb_put_hcd(hcd); + if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci) && + (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON)) + regulator_disable(ci->platdata->reg_vbus); + } + ci->hcd = NULL; + ci->otg.host = NULL; + + if (ci->platdata->pins_host && ci->platdata->pins_default) + pinctrl_select_state(ci->platdata->pctl, + ci->platdata->pins_default); +} + + +void ci_hdrc_host_destroy(struct ci_hdrc *ci) +{ + if (ci->role == CI_ROLE_HOST && ci->hcd) + host_stop(ci); +} + +/* The below code is based on tegra ehci driver */ +static int ci_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); + unsigned int ports = HCS_N_PORTS(ehci->hcs_params); + u32 __iomem *status_reg; + u32 temp, port_index; + unsigned long flags; + int retval = 0; + bool done = false; + struct device *dev = hcd->self.controller; + struct ci_hdrc *ci = dev_get_drvdata(dev); + + /* + * Avoid out-of-bounds values while calculating the port index + * from wIndex. The compiler doesn't like pointers to invalid + * addresses, even if they are never used. + */ + port_index = (wIndex - 1) & 0xff; + if (port_index >= HCS_N_PORTS_MAX) + port_index = 0; + status_reg = &ehci->regs->port_status[port_index]; + + spin_lock_irqsave(&ehci->lock, flags); + + if (ci->platdata->hub_control) { + retval = ci->platdata->hub_control(ci, typeReq, wValue, wIndex, + buf, wLength, &done, &flags); + if (done) + goto done; + } + + if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { + if (!wIndex || wIndex > ports) { + retval = -EPIPE; + goto done; + } + + 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 (ehci_handshake(ehci, status_reg, PORT_SUSPEND, + PORT_SUSPEND, 5000)) + ehci_err(ehci, "timeout waiting for SUSPEND\n"); + + if (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC) { + if (ci->platdata->notify_event) + ci->platdata->notify_event(ci, + CI_HDRC_IMX_HSIC_SUSPEND_EVENT); + + temp = ehci_readl(ehci, status_reg); + temp &= ~(PORT_WKDISC_E | PORT_WKCONN_E); + ehci_writel(ehci, temp, status_reg); + } + + set_bit(port_index, &ehci->suspended_ports); + goto done; + } + + /* + * After resume has finished, it needs do some post resume + * operation for some SoCs. + */ + else if (typeReq == ClearPortFeature && + wValue == USB_PORT_FEAT_C_SUSPEND) { + /* Make sure the resume has finished, it should be finished */ + if (ehci_handshake(ehci, status_reg, PORT_RESUME, 0, 25000)) + ehci_err(ehci, "timeout waiting for resume\n"); + } + + 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 int ci_ehci_bus_suspend(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct device *dev = hcd->self.controller; + struct ci_hdrc *ci = dev_get_drvdata(dev); + int port; + u32 tmp; + + int ret = orig_bus_suspend(hcd); + + if (ret) + return ret; + + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + u32 __iomem *reg = &ehci->regs->port_status[port]; + u32 portsc = ehci_readl(ehci, reg); + + if (portsc & PORT_CONNECT) { + /* + * For chipidea, the resume signal will be ended + * automatically, so for remote wakeup case, the + * usbcmd.rs may not be set before the resume has + * ended if other resume paths consumes too much + * time (~24ms), in that case, the SOF will not + * send out within 3ms after resume ends, then the + * high speed device will enter full speed mode. + */ + + tmp = ehci_readl(ehci, &ehci->regs->command); + tmp |= CMD_RUN; + ehci_writel(ehci, tmp, &ehci->regs->command); + /* + * It needs a short delay between set RS bit and PHCD. + */ + usleep_range(150, 200); + /* + * Need to clear WKCN and WKOC for imx HSIC, + * otherwise, there will be wakeup event. + */ + if (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC) { + tmp = ehci_readl(ehci, reg); + tmp &= ~(PORT_WKDISC_E | PORT_WKCONN_E); + ehci_writel(ehci, tmp, reg); + } + + break; + } + } + + return 0; +} + +static void ci_hdrc_free_dma_aligned_buffer(struct urb *urb, bool copy_back) +{ + struct ci_hdrc_dma_aligned_buffer *temp; + + if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) + return; + urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; + + temp = container_of(urb->transfer_buffer, + struct ci_hdrc_dma_aligned_buffer, data); + urb->transfer_buffer = temp->original_buffer; + + if (copy_back && usb_urb_dir_in(urb)) { + size_t length; + + if (usb_pipeisoc(urb->pipe)) + length = urb->transfer_buffer_length; + else + length = urb->actual_length; + + memcpy(temp->original_buffer, temp->data, length); + } + + kfree(temp); +} + +static int ci_hdrc_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags) +{ + struct ci_hdrc_dma_aligned_buffer *temp; + + if (urb->num_sgs || urb->sg || urb->transfer_buffer_length == 0) + return 0; + if (IS_ALIGNED((uintptr_t)urb->transfer_buffer, 4) + && IS_ALIGNED(urb->transfer_buffer_length, 4)) + return 0; + + temp = kmalloc(sizeof(*temp) + ALIGN(urb->transfer_buffer_length, 4), mem_flags); + if (!temp) + return -ENOMEM; + + if (usb_urb_dir_out(urb)) + memcpy(temp->data, urb->transfer_buffer, + urb->transfer_buffer_length); + + temp->original_buffer = urb->transfer_buffer; + urb->transfer_buffer = temp->data; + urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; + + return 0; +} + +static int ci_hdrc_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + int ret; + + ret = ci_hdrc_alloc_dma_aligned_buffer(urb, mem_flags); + if (ret) + return ret; + + ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); + if (ret) + ci_hdrc_free_dma_aligned_buffer(urb, false); + + return ret; +} + +static void ci_hdrc_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ + usb_hcd_unmap_urb_for_dma(hcd, urb); + ci_hdrc_free_dma_aligned_buffer(urb, true); +} + +#ifdef CONFIG_PM_SLEEP +static void ci_hdrc_host_suspend(struct ci_hdrc *ci) +{ + ehci_suspend(ci->hcd, device_may_wakeup(ci->dev)); +} + +static void ci_hdrc_host_resume(struct ci_hdrc *ci, bool power_lost) +{ + ehci_resume(ci->hcd, power_lost); +} +#endif + +int ci_hdrc_host_init(struct ci_hdrc *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; +#ifdef CONFIG_PM_SLEEP + rdrv->suspend = ci_hdrc_host_suspend; + rdrv->resume = ci_hdrc_host_resume; +#endif + rdrv->irq = host_irq; + rdrv->name = "host"; + ci->roles[CI_ROLE_HOST] = rdrv; + + if (ci->platdata->flags & CI_HDRC_REQUIRES_ALIGNED_DMA) { + ci_ehci_hc_driver.map_urb_for_dma = ci_hdrc_map_urb_for_dma; + ci_ehci_hc_driver.unmap_urb_for_dma = ci_hdrc_unmap_urb_for_dma; + } + + return 0; +} + +void ci_hdrc_host_driver_init(void) +{ + ehci_init_driver(&ci_ehci_hc_driver, &ehci_ci_overrides); + orig_bus_suspend = ci_ehci_hc_driver.bus_suspend; + ci_ehci_hc_driver.bus_suspend = ci_ehci_bus_suspend; + ci_ehci_hc_driver.hub_control = ci_ehci_hub_control; +} diff --git a/drivers/usb/chipidea/host.h b/drivers/usb/chipidea/host.h new file mode 100644 index 000000000..2625aa01a --- /dev/null +++ b/drivers/usb/chipidea/host.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __DRIVERS_USB_CHIPIDEA_HOST_H +#define __DRIVERS_USB_CHIPIDEA_HOST_H + +#ifdef CONFIG_USB_CHIPIDEA_HOST + +int ci_hdrc_host_init(struct ci_hdrc *ci); +void ci_hdrc_host_destroy(struct ci_hdrc *ci); +void ci_hdrc_host_driver_init(void); + +#else + +static inline int ci_hdrc_host_init(struct ci_hdrc *ci) +{ + return -ENXIO; +} + +static inline void ci_hdrc_host_destroy(struct ci_hdrc *ci) +{ + +} + +static inline void ci_hdrc_host_driver_init(void) +{ + +} + +#endif + +#endif /* __DRIVERS_USB_CHIPIDEA_HOST_H */ diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c new file mode 100644 index 000000000..fecc7d7e2 --- /dev/null +++ b/drivers/usb/chipidea/otg.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * otg.c - ChipIdea USB IP core OTG driver + * + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * Author: Peter Chen + */ + +/* + * This file mainly handles otgsc register, OTG fsm operations for HNP and SRP + * are also included. + */ + +#include +#include +#include + +#include "ci.h" +#include "bits.h" +#include "otg.h" +#include "otg_fsm.h" + +/** + * hw_read_otgsc - returns otgsc register bits value. + * @ci: the controller + * @mask: bitfield mask + */ +u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) +{ + struct ci_hdrc_cable *cable; + u32 val = hw_read(ci, OP_OTGSC, mask); + + /* + * If using extcon framework for VBUS and/or ID signal + * detection overwrite OTGSC register value + */ + cable = &ci->platdata->vbus_extcon; + if (!IS_ERR(cable->edev) || ci->role_switch) { + if (cable->changed) + val |= OTGSC_BSVIS; + else + val &= ~OTGSC_BSVIS; + + if (cable->connected) + val |= OTGSC_BSV; + else + val &= ~OTGSC_BSV; + + if (cable->enabled) + val |= OTGSC_BSVIE; + else + val &= ~OTGSC_BSVIE; + } + + cable = &ci->platdata->id_extcon; + if (!IS_ERR(cable->edev) || ci->role_switch) { + if (cable->changed) + val |= OTGSC_IDIS; + else + val &= ~OTGSC_IDIS; + + if (cable->connected) + val &= ~OTGSC_ID; /* host */ + else + val |= OTGSC_ID; /* device */ + + if (cable->enabled) + val |= OTGSC_IDIE; + else + val &= ~OTGSC_IDIE; + } + + return val & mask; +} + +/** + * hw_write_otgsc - updates target bits of OTGSC register. + * @ci: the controller + * @mask: bitfield mask + * @data: to be written + */ +void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data) +{ + struct ci_hdrc_cable *cable; + + cable = &ci->platdata->vbus_extcon; + if (!IS_ERR(cable->edev) || ci->role_switch) { + if (data & mask & OTGSC_BSVIS) + cable->changed = false; + + /* Don't enable vbus interrupt if using external notifier */ + if (data & mask & OTGSC_BSVIE) { + cable->enabled = true; + data &= ~OTGSC_BSVIE; + } else if (mask & OTGSC_BSVIE) { + cable->enabled = false; + } + } + + cable = &ci->platdata->id_extcon; + if (!IS_ERR(cable->edev) || ci->role_switch) { + if (data & mask & OTGSC_IDIS) + cable->changed = false; + + /* Don't enable id interrupt if using external notifier */ + if (data & mask & OTGSC_IDIE) { + cable->enabled = true; + data &= ~OTGSC_IDIE; + } else if (mask & OTGSC_IDIE) { + cable->enabled = false; + } + } + + hw_write(ci, OP_OTGSC, mask | OTGSC_INT_STATUS_BITS, data); +} + +/** + * ci_otg_role - pick role based on ID pin state + * @ci: the controller + */ +enum ci_role ci_otg_role(struct ci_hdrc *ci) +{ + enum ci_role role = hw_read_otgsc(ci, OTGSC_ID) + ? CI_ROLE_GADGET + : CI_ROLE_HOST; + + return role; +} + +void ci_handle_vbus_change(struct ci_hdrc *ci) +{ + if (ci->role != CI_ROLE_GADGET) + return; + + if (!ci->is_otg) { + if (ci->platdata->flags & CI_HDRC_FORCE_VBUS_ACTIVE_ALWAYS) + usb_gadget_vbus_connect(&ci->gadget); + return; + } + + if (hw_read_otgsc(ci, OTGSC_BSV) && !ci->vbus_active) + usb_gadget_vbus_connect(&ci->gadget); + else if (!hw_read_otgsc(ci, OTGSC_BSV) && ci->vbus_active) + usb_gadget_vbus_disconnect(&ci->gadget); +} + +/** + * hw_wait_vbus_lower_bsv - When we switch to device mode, the vbus value + * should be lower than OTGSC_BSV before connecting + * to host. + * + * @ci: the controller + * + * This function returns an error code if timeout + */ +static int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci) +{ + unsigned long elapse = jiffies + msecs_to_jiffies(5000); + u32 mask = OTGSC_BSV; + + while (hw_read_otgsc(ci, mask)) { + if (time_after(jiffies, elapse)) { + dev_err(ci->dev, "timeout waiting for %08x in OTGSC\n", + mask); + return -ETIMEDOUT; + } + msleep(20); + } + + return 0; +} + +void ci_handle_id_switch(struct ci_hdrc *ci) +{ + enum ci_role role; + + mutex_lock(&ci->mutex); + 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); + + if (ci->vbus_active && ci->role == CI_ROLE_GADGET) + /* + * vbus disconnect event is lost due to role + * switch occurs during system suspend. + */ + usb_gadget_vbus_disconnect(&ci->gadget); + + ci_role_stop(ci); + + if (role == CI_ROLE_GADGET && !ci->role_switch && + IS_ERR(ci->platdata->vbus_extcon.edev)) + /* + * Wait vbus lower than OTGSC_BSV before connecting + * to host. If connecting status is from an external + * connector instead of register, we don't need to + * care vbus on the board, since it will not affect + * external connector status. + */ + hw_wait_vbus_lower_bsv(ci); + + ci_role_start(ci, role); + /* vbus change may have already occurred */ + if (role == CI_ROLE_GADGET) + ci_handle_vbus_change(ci); + } + mutex_unlock(&ci->mutex); +} +/** + * ci_otg_work - perform otg (vbus/id) event handle + * @work: work struct + */ +static void ci_otg_work(struct work_struct *work) +{ + struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work); + + if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) { + enable_irq(ci->irq); + return; + } + + pm_runtime_get_sync(ci->dev); + + if (ci->id_event) { + ci->id_event = false; + ci_handle_id_switch(ci); + } + + if (ci->b_sess_valid_event) { + ci->b_sess_valid_event = false; + ci_handle_vbus_change(ci); + } + + pm_runtime_put_sync(ci->dev); + + enable_irq(ci->irq); +} + + +/** + * ci_hdrc_otg_init - initialize otg struct + * @ci: the controller + */ +int ci_hdrc_otg_init(struct ci_hdrc *ci) +{ + INIT_WORK(&ci->work, ci_otg_work); + ci->wq = create_freezable_workqueue("ci_otg"); + if (!ci->wq) { + dev_err(ci->dev, "can't create workqueue\n"); + return -ENODEV; + } + + if (ci_otg_is_fsm_mode(ci)) + return ci_hdrc_otg_fsm_init(ci); + + return 0; +} + +/** + * ci_hdrc_otg_destroy - destroy otg struct + * @ci: the controller + */ +void ci_hdrc_otg_destroy(struct ci_hdrc *ci) +{ + if (ci->wq) + destroy_workqueue(ci->wq); + + /* Disable all OTG irq and clear status */ + hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, + OTGSC_INT_STATUS_BITS); + if (ci_otg_is_fsm_mode(ci)) + ci_hdrc_otg_fsm_remove(ci); +} diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h new file mode 100644 index 000000000..87629b81e --- /dev/null +++ b/drivers/usb/chipidea/otg.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2013-2014 Freescale Semiconductor, Inc. + * + * Author: Peter Chen + */ + +#ifndef __DRIVERS_USB_CHIPIDEA_OTG_H +#define __DRIVERS_USB_CHIPIDEA_OTG_H + +u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask); +void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data); +int ci_hdrc_otg_init(struct ci_hdrc *ci); +void ci_hdrc_otg_destroy(struct ci_hdrc *ci); +enum ci_role ci_otg_role(struct ci_hdrc *ci); +void ci_handle_vbus_change(struct ci_hdrc *ci); +void ci_handle_id_switch(struct ci_hdrc *ci); +static inline void ci_otg_queue_work(struct ci_hdrc *ci) +{ + disable_irq_nosync(ci->irq); + if (queue_work(ci->wq, &ci->work) == false) + enable_irq(ci->irq); +} + +#endif /* __DRIVERS_USB_CHIPIDEA_OTG_H */ diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c new file mode 100644 index 000000000..929536dc9 --- /dev/null +++ b/drivers/usb/chipidea/otg_fsm.c @@ -0,0 +1,851 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * otg_fsm.c - ChipIdea USB IP core OTG FSM driver + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Jun Li + */ + +/* + * This file mainly handles OTG fsm, it includes OTG fsm operations + * for HNP and SRP. + * + * TODO List + * - ADP + * - OTG test device + */ + +#include +#include +#include +#include +#include + +#include "ci.h" +#include "bits.h" +#include "otg.h" +#include "otg_fsm.h" + +/* Add for otg: interact with user space app */ +static ssize_t +a_bus_req_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + char *next; + unsigned size, t; + struct ci_hdrc *ci = dev_get_drvdata(dev); + + next = buf; + size = PAGE_SIZE; + t = scnprintf(next, size, "%d\n", ci->fsm.a_bus_req); + size -= t; + next += t; + + return PAGE_SIZE - size; +} + +static ssize_t +a_bus_req_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + + if (count > 2) + return -1; + + mutex_lock(&ci->fsm.lock); + if (buf[0] == '0') { + ci->fsm.a_bus_req = 0; + } else if (buf[0] == '1') { + /* If a_bus_drop is TRUE, a_bus_req can't be set */ + if (ci->fsm.a_bus_drop) { + mutex_unlock(&ci->fsm.lock); + return count; + } + ci->fsm.a_bus_req = 1; + if (ci->fsm.otg->state == OTG_STATE_A_PERIPHERAL) { + ci->gadget.host_request_flag = 1; + mutex_unlock(&ci->fsm.lock); + return count; + } + } + + ci_otg_queue_work(ci); + mutex_unlock(&ci->fsm.lock); + + return count; +} +static DEVICE_ATTR_RW(a_bus_req); + +static ssize_t +a_bus_drop_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + char *next; + unsigned size, t; + struct ci_hdrc *ci = dev_get_drvdata(dev); + + next = buf; + size = PAGE_SIZE; + t = scnprintf(next, size, "%d\n", ci->fsm.a_bus_drop); + size -= t; + next += t; + + return PAGE_SIZE - size; +} + +static ssize_t +a_bus_drop_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + + if (count > 2) + return -1; + + mutex_lock(&ci->fsm.lock); + if (buf[0] == '0') { + ci->fsm.a_bus_drop = 0; + } else if (buf[0] == '1') { + ci->fsm.a_bus_drop = 1; + ci->fsm.a_bus_req = 0; + } + + ci_otg_queue_work(ci); + mutex_unlock(&ci->fsm.lock); + + return count; +} +static DEVICE_ATTR_RW(a_bus_drop); + +static ssize_t +b_bus_req_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + char *next; + unsigned size, t; + struct ci_hdrc *ci = dev_get_drvdata(dev); + + next = buf; + size = PAGE_SIZE; + t = scnprintf(next, size, "%d\n", ci->fsm.b_bus_req); + size -= t; + next += t; + + return PAGE_SIZE - size; +} + +static ssize_t +b_bus_req_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + + if (count > 2) + return -1; + + mutex_lock(&ci->fsm.lock); + if (buf[0] == '0') + ci->fsm.b_bus_req = 0; + else if (buf[0] == '1') { + ci->fsm.b_bus_req = 1; + if (ci->fsm.otg->state == OTG_STATE_B_PERIPHERAL) { + ci->gadget.host_request_flag = 1; + mutex_unlock(&ci->fsm.lock); + return count; + } + } + + ci_otg_queue_work(ci); + mutex_unlock(&ci->fsm.lock); + + return count; +} +static DEVICE_ATTR_RW(b_bus_req); + +static ssize_t +a_clr_err_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + + if (count > 2) + return -1; + + mutex_lock(&ci->fsm.lock); + if (buf[0] == '1') + ci->fsm.a_clr_err = 1; + + ci_otg_queue_work(ci); + mutex_unlock(&ci->fsm.lock); + + return count; +} +static DEVICE_ATTR_WO(a_clr_err); + +static struct attribute *inputs_attrs[] = { + &dev_attr_a_bus_req.attr, + &dev_attr_a_bus_drop.attr, + &dev_attr_b_bus_req.attr, + &dev_attr_a_clr_err.attr, + NULL, +}; + +static const struct attribute_group inputs_attr_group = { + .name = "inputs", + .attrs = inputs_attrs, +}; + +/* + * Keep this list in the same order as timers indexed + * by enum otg_fsm_timer in include/linux/usb/otg-fsm.h + */ +static unsigned otg_timer_ms[] = { + TA_WAIT_VRISE, + TA_WAIT_VFALL, + TA_WAIT_BCON, + TA_AIDL_BDIS, + TB_ASE0_BRST, + TA_BIDL_ADIS, + TB_AIDL_BDIS, + TB_SE0_SRP, + TB_SRP_FAIL, + 0, + TB_DATA_PLS, + TB_SSEND_SRP, +}; + +/* + * Add timer to active timer list + */ +static void ci_otg_add_timer(struct ci_hdrc *ci, enum otg_fsm_timer t) +{ + unsigned long flags, timer_sec, timer_nsec; + + if (t >= NUM_OTG_FSM_TIMERS) + return; + + spin_lock_irqsave(&ci->lock, flags); + timer_sec = otg_timer_ms[t] / MSEC_PER_SEC; + timer_nsec = (otg_timer_ms[t] % MSEC_PER_SEC) * NSEC_PER_MSEC; + ci->hr_timeouts[t] = ktime_add(ktime_get(), + ktime_set(timer_sec, timer_nsec)); + ci->enabled_otg_timer_bits |= (1 << t); + if ((ci->next_otg_timer == NUM_OTG_FSM_TIMERS) || + ktime_after(ci->hr_timeouts[ci->next_otg_timer], + ci->hr_timeouts[t])) { + ci->next_otg_timer = t; + hrtimer_start_range_ns(&ci->otg_fsm_hrtimer, + ci->hr_timeouts[t], NSEC_PER_MSEC, + HRTIMER_MODE_ABS); + } + spin_unlock_irqrestore(&ci->lock, flags); +} + +/* + * Remove timer from active timer list + */ +static void ci_otg_del_timer(struct ci_hdrc *ci, enum otg_fsm_timer t) +{ + unsigned long flags, enabled_timer_bits; + enum otg_fsm_timer cur_timer, next_timer = NUM_OTG_FSM_TIMERS; + + if ((t >= NUM_OTG_FSM_TIMERS) || + !(ci->enabled_otg_timer_bits & (1 << t))) + return; + + spin_lock_irqsave(&ci->lock, flags); + ci->enabled_otg_timer_bits &= ~(1 << t); + if (ci->next_otg_timer == t) { + if (ci->enabled_otg_timer_bits == 0) { + spin_unlock_irqrestore(&ci->lock, flags); + /* No enabled timers after delete it */ + hrtimer_cancel(&ci->otg_fsm_hrtimer); + spin_lock_irqsave(&ci->lock, flags); + ci->next_otg_timer = NUM_OTG_FSM_TIMERS; + } else { + /* Find the next timer */ + enabled_timer_bits = ci->enabled_otg_timer_bits; + for_each_set_bit(cur_timer, &enabled_timer_bits, + NUM_OTG_FSM_TIMERS) { + if ((next_timer == NUM_OTG_FSM_TIMERS) || + ktime_before(ci->hr_timeouts[next_timer], + ci->hr_timeouts[cur_timer])) + next_timer = cur_timer; + } + } + } + if (next_timer != NUM_OTG_FSM_TIMERS) { + ci->next_otg_timer = next_timer; + hrtimer_start_range_ns(&ci->otg_fsm_hrtimer, + ci->hr_timeouts[next_timer], NSEC_PER_MSEC, + HRTIMER_MODE_ABS); + } + spin_unlock_irqrestore(&ci->lock, flags); +} + +/* OTG FSM timer handlers */ +static int a_wait_vrise_tmout(struct ci_hdrc *ci) +{ + ci->fsm.a_wait_vrise_tmout = 1; + return 0; +} + +static int a_wait_vfall_tmout(struct ci_hdrc *ci) +{ + ci->fsm.a_wait_vfall_tmout = 1; + return 0; +} + +static int a_wait_bcon_tmout(struct ci_hdrc *ci) +{ + ci->fsm.a_wait_bcon_tmout = 1; + return 0; +} + +static int a_aidl_bdis_tmout(struct ci_hdrc *ci) +{ + ci->fsm.a_aidl_bdis_tmout = 1; + return 0; +} + +static int b_ase0_brst_tmout(struct ci_hdrc *ci) +{ + ci->fsm.b_ase0_brst_tmout = 1; + return 0; +} + +static int a_bidl_adis_tmout(struct ci_hdrc *ci) +{ + ci->fsm.a_bidl_adis_tmout = 1; + return 0; +} + +static int b_aidl_bdis_tmout(struct ci_hdrc *ci) +{ + ci->fsm.a_bus_suspend = 1; + return 0; +} + +static int b_se0_srp_tmout(struct ci_hdrc *ci) +{ + ci->fsm.b_se0_srp = 1; + return 0; +} + +static int b_srp_fail_tmout(struct ci_hdrc *ci) +{ + ci->fsm.b_srp_done = 1; + return 1; +} + +static int b_data_pls_tmout(struct ci_hdrc *ci) +{ + ci->fsm.b_srp_done = 1; + ci->fsm.b_bus_req = 0; + if (ci->fsm.power_up) + ci->fsm.power_up = 0; + hw_write_otgsc(ci, OTGSC_HABA, 0); + pm_runtime_put(ci->dev); + return 0; +} + +static int b_ssend_srp_tmout(struct ci_hdrc *ci) +{ + ci->fsm.b_ssend_srp = 1; + /* only vbus fall below B_sess_vld in b_idle state */ + if (ci->fsm.otg->state == OTG_STATE_B_IDLE) + return 0; + else + return 1; +} + +/* + * Keep this list in the same order as timers indexed + * by enum otg_fsm_timer in include/linux/usb/otg-fsm.h + */ +static int (*otg_timer_handlers[])(struct ci_hdrc *) = { + a_wait_vrise_tmout, /* A_WAIT_VRISE */ + a_wait_vfall_tmout, /* A_WAIT_VFALL */ + a_wait_bcon_tmout, /* A_WAIT_BCON */ + a_aidl_bdis_tmout, /* A_AIDL_BDIS */ + b_ase0_brst_tmout, /* B_ASE0_BRST */ + a_bidl_adis_tmout, /* A_BIDL_ADIS */ + b_aidl_bdis_tmout, /* B_AIDL_BDIS */ + b_se0_srp_tmout, /* B_SE0_SRP */ + b_srp_fail_tmout, /* B_SRP_FAIL */ + NULL, /* A_WAIT_ENUM */ + b_data_pls_tmout, /* B_DATA_PLS */ + b_ssend_srp_tmout, /* B_SSEND_SRP */ +}; + +/* + * Enable the next nearest enabled timer if have + */ +static enum hrtimer_restart ci_otg_hrtimer_func(struct hrtimer *t) +{ + struct ci_hdrc *ci = container_of(t, struct ci_hdrc, otg_fsm_hrtimer); + ktime_t now, *timeout; + unsigned long enabled_timer_bits; + unsigned long flags; + enum otg_fsm_timer cur_timer, next_timer = NUM_OTG_FSM_TIMERS; + int ret = -EINVAL; + + spin_lock_irqsave(&ci->lock, flags); + enabled_timer_bits = ci->enabled_otg_timer_bits; + ci->next_otg_timer = NUM_OTG_FSM_TIMERS; + + now = ktime_get(); + for_each_set_bit(cur_timer, &enabled_timer_bits, NUM_OTG_FSM_TIMERS) { + if (ktime_compare(now, ci->hr_timeouts[cur_timer]) >= 0) { + ci->enabled_otg_timer_bits &= ~(1 << cur_timer); + if (otg_timer_handlers[cur_timer]) + ret = otg_timer_handlers[cur_timer](ci); + } else { + if ((next_timer == NUM_OTG_FSM_TIMERS) || + ktime_before(ci->hr_timeouts[cur_timer], + ci->hr_timeouts[next_timer])) + next_timer = cur_timer; + } + } + /* Enable the next nearest timer */ + if (next_timer < NUM_OTG_FSM_TIMERS) { + timeout = &ci->hr_timeouts[next_timer]; + hrtimer_start_range_ns(&ci->otg_fsm_hrtimer, *timeout, + NSEC_PER_MSEC, HRTIMER_MODE_ABS); + ci->next_otg_timer = next_timer; + } + spin_unlock_irqrestore(&ci->lock, flags); + + if (!ret) + ci_otg_queue_work(ci); + + return HRTIMER_NORESTART; +} + +/* Initialize timers */ +static int ci_otg_init_timers(struct ci_hdrc *ci) +{ + hrtimer_setup(&ci->otg_fsm_hrtimer, ci_otg_hrtimer_func, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + + return 0; +} + +/* -------------------------------------------------------------*/ +/* Operations that will be called from OTG Finite State Machine */ +/* -------------------------------------------------------------*/ +static void ci_otg_fsm_add_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) +{ + struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); + + if (t < NUM_OTG_FSM_TIMERS) + ci_otg_add_timer(ci, t); + return; +} + +static void ci_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) +{ + struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); + + if (t < NUM_OTG_FSM_TIMERS) + ci_otg_del_timer(ci, t); + return; +} + +/* + * A-device drive vbus: turn on vbus regulator and enable port power + * Data pulse irq should be disabled while vbus is on. + */ +static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on) +{ + int ret; + struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); + + if (on) { + /* Enable power */ + hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, + PORTSC_PP); + if (ci->platdata->reg_vbus) { + ret = regulator_enable(ci->platdata->reg_vbus); + if (ret) { + dev_err(ci->dev, + "Failed to enable vbus regulator, ret=%d\n", + ret); + return; + } + } + + if (ci->platdata->flags & CI_HDRC_PHY_VBUS_CONTROL) + usb_phy_vbus_on(ci->usb_phy); + + /* Disable data pulse irq */ + hw_write_otgsc(ci, OTGSC_DPIE, 0); + + fsm->a_srp_det = 0; + fsm->power_up = 0; + } else { + if (ci->platdata->reg_vbus) + regulator_disable(ci->platdata->reg_vbus); + + if (ci->platdata->flags & CI_HDRC_PHY_VBUS_CONTROL) + usb_phy_vbus_off(ci->usb_phy); + + fsm->a_bus_drop = 1; + fsm->a_bus_req = 0; + } +} + +/* + * Control data line by Run Stop bit. + */ +static void ci_otg_loc_conn(struct otg_fsm *fsm, int on) +{ + struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); + + if (on) + hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); + else + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); +} + +/* + * Generate SOF by host. + * In host mode, controller will automatically send SOF. + * Suspend will block the data on the port. + * + * This is controlled through usbcore by usb autosuspend, + * so the usb device class driver need support autosuspend, + * otherwise the bus suspend will not happen. + */ +static void ci_otg_loc_sof(struct otg_fsm *fsm, int on) +{ + struct usb_device *udev; + + if (!fsm->otg->host) + return; + + udev = usb_hub_find_child(fsm->otg->host->root_hub, 1); + if (!udev) + return; + + if (on) { + usb_disable_autosuspend(udev); + } else { + pm_runtime_set_autosuspend_delay(&udev->dev, 0); + usb_enable_autosuspend(udev); + } +} + +/* + * Start SRP pulsing by data-line pulsing, + * no v-bus pulsing followed + */ +static void ci_otg_start_pulse(struct otg_fsm *fsm) +{ + struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); + + /* Hardware Assistant Data pulse */ + hw_write_otgsc(ci, OTGSC_HADP, OTGSC_HADP); + + pm_runtime_get(ci->dev); + ci_otg_add_timer(ci, B_DATA_PLS); +} + +static int ci_otg_start_host(struct otg_fsm *fsm, int on) +{ + struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); + + if (on) { + ci_role_stop(ci); + ci_role_start(ci, CI_ROLE_HOST); + } else { + ci_role_stop(ci); + ci_role_start(ci, CI_ROLE_GADGET); + } + return 0; +} + +static int ci_otg_start_gadget(struct otg_fsm *fsm, int on) +{ + struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); + + if (on) + usb_gadget_vbus_connect(&ci->gadget); + else + usb_gadget_vbus_disconnect(&ci->gadget); + + return 0; +} + +static struct otg_fsm_ops ci_otg_ops = { + .drv_vbus = ci_otg_drv_vbus, + .loc_conn = ci_otg_loc_conn, + .loc_sof = ci_otg_loc_sof, + .start_pulse = ci_otg_start_pulse, + .add_timer = ci_otg_fsm_add_timer, + .del_timer = ci_otg_fsm_del_timer, + .start_host = ci_otg_start_host, + .start_gadget = ci_otg_start_gadget, +}; + +int ci_otg_fsm_work(struct ci_hdrc *ci) +{ + /* + * Don't do fsm transition for B device + * when there is no gadget class driver + */ + if (ci->fsm.id && !(ci->driver) && + ci->fsm.otg->state < OTG_STATE_A_IDLE) + return 0; + + pm_runtime_get_sync(ci->dev); + if (otg_statemachine(&ci->fsm)) { + if (ci->fsm.otg->state == OTG_STATE_A_IDLE) { + /* + * Further state change for cases: + * a_idle to b_idle; or + * a_idle to a_wait_vrise due to ID change(1->0), so + * B-dev becomes A-dev can try to start new session + * consequently; or + * a_idle to a_wait_vrise when power up + */ + if ((ci->fsm.id) || (ci->id_event) || + (ci->fsm.power_up)) { + ci_otg_queue_work(ci); + } else { + /* Enable data pulse irq */ + hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | + PORTSC_PP, 0); + hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); + hw_write_otgsc(ci, OTGSC_DPIE, OTGSC_DPIE); + } + if (ci->id_event) + ci->id_event = false; + } else if (ci->fsm.otg->state == OTG_STATE_B_IDLE) { + if (ci->fsm.b_sess_vld) { + ci->fsm.power_up = 0; + /* + * Further transite to b_periphearl state + * when register gadget driver with vbus on + */ + ci_otg_queue_work(ci); + } + } else if (ci->fsm.otg->state == OTG_STATE_A_HOST) { + pm_runtime_put_autosuspend(ci->dev); + return 0; + } + } + pm_runtime_put_sync(ci->dev); + return 0; +} + +/* + * Update fsm variables in each state if catching expected interrupts, + * called by otg fsm isr. + */ +static void ci_otg_fsm_event(struct ci_hdrc *ci) +{ + u32 intr_sts, otg_bsess_vld, port_conn; + struct otg_fsm *fsm = &ci->fsm; + + intr_sts = hw_read_intr_status(ci); + otg_bsess_vld = hw_read_otgsc(ci, OTGSC_BSV); + port_conn = hw_read(ci, OP_PORTSC, PORTSC_CCS); + + switch (ci->fsm.otg->state) { + case OTG_STATE_A_WAIT_BCON: + if (port_conn) { + fsm->b_conn = 1; + fsm->a_bus_req = 1; + ci_otg_queue_work(ci); + } + break; + case OTG_STATE_B_IDLE: + if (otg_bsess_vld && (intr_sts & USBi_PCI) && port_conn) { + fsm->b_sess_vld = 1; + ci_otg_queue_work(ci); + } + break; + case OTG_STATE_B_PERIPHERAL: + if ((intr_sts & USBi_SLI) && port_conn && otg_bsess_vld) { + ci_otg_add_timer(ci, B_AIDL_BDIS); + } else if (intr_sts & USBi_PCI) { + ci_otg_del_timer(ci, B_AIDL_BDIS); + if (fsm->a_bus_suspend == 1) + fsm->a_bus_suspend = 0; + } + break; + case OTG_STATE_B_HOST: + if ((intr_sts & USBi_PCI) && !port_conn) { + fsm->a_conn = 0; + fsm->b_bus_req = 0; + ci_otg_queue_work(ci); + } + break; + case OTG_STATE_A_PERIPHERAL: + if (intr_sts & USBi_SLI) { + fsm->b_bus_suspend = 1; + /* + * Init a timer to know how long this suspend + * will continue, if time out, indicates B no longer + * wants to be host role + */ + ci_otg_add_timer(ci, A_BIDL_ADIS); + } + + if (intr_sts & USBi_URI) + ci_otg_del_timer(ci, A_BIDL_ADIS); + + if (intr_sts & USBi_PCI) { + if (fsm->b_bus_suspend == 1) { + ci_otg_del_timer(ci, A_BIDL_ADIS); + fsm->b_bus_suspend = 0; + } + } + break; + case OTG_STATE_A_SUSPEND: + if ((intr_sts & USBi_PCI) && !port_conn) { + fsm->b_conn = 0; + + /* if gadget driver is binded */ + if (ci->driver) { + /* A device to be peripheral mode */ + ci->gadget.is_a_peripheral = 1; + } + ci_otg_queue_work(ci); + } + break; + case OTG_STATE_A_HOST: + if ((intr_sts & USBi_PCI) && !port_conn) { + fsm->b_conn = 0; + ci_otg_queue_work(ci); + } + break; + case OTG_STATE_B_WAIT_ACON: + if ((intr_sts & USBi_PCI) && port_conn) { + fsm->a_conn = 1; + ci_otg_queue_work(ci); + } + break; + default: + break; + } +} + +/* + * ci_otg_irq - otg fsm related irq handling + * and also update otg fsm variable by monitoring usb host and udc + * state change interrupts. + * @ci: ci_hdrc + */ +irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci) +{ + irqreturn_t retval = IRQ_NONE; + u32 otgsc, otg_int_src = 0; + struct otg_fsm *fsm = &ci->fsm; + + otgsc = hw_read_otgsc(ci, ~0); + otg_int_src = otgsc & OTGSC_INT_STATUS_BITS & (otgsc >> 8); + fsm->id = (otgsc & OTGSC_ID) ? 1 : 0; + + if (otg_int_src) { + if (otg_int_src & OTGSC_DPIS) { + hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); + fsm->a_srp_det = 1; + fsm->a_bus_drop = 0; + } else if (otg_int_src & OTGSC_IDIS) { + hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS); + if (fsm->id == 0) { + fsm->a_bus_drop = 0; + fsm->a_bus_req = 1; + ci->id_event = true; + } + } else if (otg_int_src & OTGSC_BSVIS) { + hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS); + if (otgsc & OTGSC_BSV) { + fsm->b_sess_vld = 1; + ci_otg_del_timer(ci, B_SSEND_SRP); + ci_otg_del_timer(ci, B_SRP_FAIL); + fsm->b_ssend_srp = 0; + } else { + fsm->b_sess_vld = 0; + if (fsm->id) + ci_otg_add_timer(ci, B_SSEND_SRP); + } + } else if (otg_int_src & OTGSC_AVVIS) { + hw_write_otgsc(ci, OTGSC_AVVIS, OTGSC_AVVIS); + if (otgsc & OTGSC_AVV) { + fsm->a_vbus_vld = 1; + } else { + fsm->a_vbus_vld = 0; + fsm->b_conn = 0; + } + } + ci_otg_queue_work(ci); + return IRQ_HANDLED; + } + + ci_otg_fsm_event(ci); + + return retval; +} + +void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci) +{ + ci_otg_queue_work(ci); +} + +int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci) +{ + int retval = 0; + + if (ci->phy) + ci->otg.phy = ci->phy; + else + ci->otg.usb_phy = ci->usb_phy; + + ci->otg.gadget = &ci->gadget; + ci->fsm.otg = &ci->otg; + ci->fsm.power_up = 1; + ci->fsm.id = hw_read_otgsc(ci, OTGSC_ID) ? 1 : 0; + ci->fsm.otg->state = OTG_STATE_UNDEFINED; + ci->fsm.ops = &ci_otg_ops; + ci->gadget.hnp_polling_support = 1; + ci->fsm.host_req_flag = devm_kzalloc(ci->dev, 1, GFP_KERNEL); + if (!ci->fsm.host_req_flag) + return -ENOMEM; + + mutex_init(&ci->fsm.lock); + + retval = ci_otg_init_timers(ci); + if (retval) { + dev_err(ci->dev, "Couldn't init OTG timers\n"); + return retval; + } + ci->enabled_otg_timer_bits = 0; + ci->next_otg_timer = NUM_OTG_FSM_TIMERS; + + retval = sysfs_create_group(&ci->dev->kobj, &inputs_attr_group); + if (retval < 0) { + dev_dbg(ci->dev, + "Can't register sysfs attr group: %d\n", retval); + return retval; + } + + /* Enable A vbus valid irq */ + hw_write_otgsc(ci, OTGSC_AVVIE, OTGSC_AVVIE); + + if (ci->fsm.id) { + ci->fsm.b_ssend_srp = + hw_read_otgsc(ci, OTGSC_BSV) ? 0 : 1; + ci->fsm.b_sess_vld = + hw_read_otgsc(ci, OTGSC_BSV) ? 1 : 0; + /* Enable BSV irq */ + hw_write_otgsc(ci, OTGSC_BSVIE, OTGSC_BSVIE); + } + + return 0; +} + +void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci) +{ + sysfs_remove_group(&ci->dev->kobj, &inputs_attr_group); +} diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h new file mode 100644 index 000000000..1f5c5ae0e --- /dev/null +++ b/drivers/usb/chipidea/otg_fsm.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Jun Li + */ + +#ifndef __DRIVERS_USB_CHIPIDEA_OTG_FSM_H +#define __DRIVERS_USB_CHIPIDEA_OTG_FSM_H + +#include + +/* + * A-DEVICE timing constants + */ + +/* Wait for VBUS Rise */ +#define TA_WAIT_VRISE (100) /* a_wait_vrise: section 7.1.2 + * a_wait_vrise_tmr: section 7.4.5.1 + * TA_VBUS_RISE <= 100ms, section 4.4 + * Table 4-1: Electrical Characteristics + * ->DC Electrical Timing + */ +/* Wait for VBUS Fall */ +#define TA_WAIT_VFALL (1000) /* a_wait_vfall: section 7.1.7 + * a_wait_vfall_tmr: section: 7.4.5.2 + */ +/* Wait for B-Connect */ +#define TA_WAIT_BCON (10000) /* a_wait_bcon: section 7.1.3 + * TA_WAIT_BCON: should be between 1100 + * and 30000 ms, section 5.5, Table 5-1 + */ +/* A-Idle to B-Disconnect */ +#define TA_AIDL_BDIS (5000) /* a_suspend min 200 ms, section 5.2.1 + * TA_AIDL_BDIS: section 5.5, Table 5-1 + */ +/* B-Idle to A-Disconnect */ +#define TA_BIDL_ADIS (500) /* TA_BIDL_ADIS: section 5.2.1 + * 500ms is used for B switch to host + * for safe + */ + +/* + * B-device timing constants + */ + +/* Data-Line Pulse Time*/ +#define TB_DATA_PLS (10) /* b_srp_init,continue 5~10ms + * section:5.1.3 + */ +/* SRP Fail Time */ +#define TB_SRP_FAIL (6000) /* b_srp_init,fail time 5~6s + * section:5.1.6 + */ +/* A-SE0 to B-Reset */ +#define TB_ASE0_BRST (155) /* minimum 155 ms, section:5.3.1 */ +/* SE0 Time Before SRP */ +#define TB_SE0_SRP (1000) /* b_idle,minimum 1s, section:5.1.2 */ +/* SSEND time before SRP */ +#define TB_SSEND_SRP (1500) /* minimum 1.5 sec, section:5.1.2 */ + +#define TB_AIDL_BDIS (20) /* 4ms ~ 150ms, section 5.2.1 */ + +#if IS_ENABLED(CONFIG_USB_OTG_FSM) + +int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci); +int ci_otg_fsm_work(struct ci_hdrc *ci); +irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci); +void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci); +void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci); + +#else + +static inline int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci) +{ + return 0; +} + +static inline int ci_otg_fsm_work(struct ci_hdrc *ci) +{ + return -ENXIO; +} + +static inline irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci) +{ + return IRQ_NONE; +} + +static inline void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci) +{ + +} + +static inline void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci) +{ + +} + +#endif + +#endif /* __DRIVERS_USB_CHIPIDEA_OTG_FSM_H */ diff --git a/drivers/usb/chipidea/trace.c b/drivers/usb/chipidea/trace.c new file mode 100644 index 000000000..f6402630a --- /dev/null +++ b/drivers/usb/chipidea/trace.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Chipidea Device Mode Trace Support + * + * Copyright (C) 2020 NXP + * + * Author: Peter Chen + */ + +#define CREATE_TRACE_POINTS +#include "trace.h" + +void ci_log(struct ci_hdrc *ci, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + trace_ci_log(ci, &vaf); + va_end(args); +} diff --git a/drivers/usb/chipidea/trace.h b/drivers/usb/chipidea/trace.h new file mode 100644 index 000000000..1875419cd --- /dev/null +++ b/drivers/usb/chipidea/trace.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Trace support header file for device mode + * + * Copyright (C) 2020 NXP + * + * Author: Peter Chen + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM chipidea + +#if !defined(__LINUX_CHIPIDEA_TRACE) || defined(TRACE_HEADER_MULTI_READ) +#define __LINUX_CHIPIDEA_TRACE + +#include +#include +#include +#include "ci.h" +#include "udc.h" + +#define CHIPIDEA_MSG_MAX 500 + +void ci_log(struct ci_hdrc *ci, const char *fmt, ...); + +TRACE_EVENT(ci_log, + TP_PROTO(struct ci_hdrc *ci, struct va_format *vaf), + TP_ARGS(ci, vaf), + TP_STRUCT__entry( + __string(name, dev_name(ci->dev)) + __vstring(msg, vaf->fmt, vaf->va) + ), + TP_fast_assign( + __assign_str(name); + __assign_vstr(msg, vaf->fmt, vaf->va); + ), + TP_printk("%s: %s", __get_str(name), __get_str(msg)) +); + +DECLARE_EVENT_CLASS(ci_log_trb, + TP_PROTO(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, struct td_node *td), + TP_ARGS(hwep, hwreq, td), + TP_STRUCT__entry( + __string(name, hwep->name) + __field(struct td_node *, td) + __field(struct usb_request *, req) + __field(dma_addr_t, dma) + __field(s32, td_remaining_size) + __field(u32, next) + __field(u32, token) + __field(u32, type) + ), + TP_fast_assign( + __assign_str(name); + __entry->req = &hwreq->req; + __entry->td = td; + __entry->dma = td->dma; + __entry->td_remaining_size = td->td_remaining_size; + __entry->next = le32_to_cpu(td->ptr->next); + __entry->token = le32_to_cpu(td->ptr->token); + __entry->type = usb_endpoint_type(hwep->ep.desc); + ), + TP_printk("%s: req: %p, td: %p, td_dma_address: %pad, remaining_size: %d, " + "next: %x, total bytes: %d, status: %lx", + __get_str(name), __entry->req, __entry->td, &__entry->dma, + __entry->td_remaining_size, __entry->next, + (int)((__entry->token & TD_TOTAL_BYTES) >> __ffs(TD_TOTAL_BYTES)), + __entry->token & TD_STATUS + ) +); + +DEFINE_EVENT(ci_log_trb, ci_prepare_td, + TP_PROTO(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, struct td_node *td), + TP_ARGS(hwep, hwreq, td) +); + +DEFINE_EVENT(ci_log_trb, ci_complete_td, + TP_PROTO(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, struct td_node *td), + TP_ARGS(hwep, hwreq, td) +); + +#endif /* __LINUX_CHIPIDEA_TRACE */ + +/* this part must be outside header guard */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#include diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c new file mode 100644 index 000000000..d52f89489 --- /dev/null +++ b/drivers/usb/chipidea/udc.c @@ -0,0 +1,2451 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * udc.c - ChipIdea UDC driver + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" +#include "udc.h" +#include "bits.h" +#include "otg.h" +#include "otg_fsm.h" +#include "trace.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), +}; + +static int reprime_dtd(struct ci_hdrc *ci, struct ci_hw_ep *hwep, + struct td_node *node); +/** + * 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 == TX) ? 16 : 0); +} + +static inline int ep_to_bit(struct ci_hdrc *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) + * @ci: the controller + * @dma: 0 => disable, !0 => enable and set dma engine + * + * This function returns an error code + */ +static int hw_device_state(struct ci_hdrc *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); + } else { + hw_write(ci, OP_USBINTR, ~0, 0); + } + return 0; +} + +/** + * hw_ep_flush: flush endpoint fifo (execute without interruption) + * @ci: the controller + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns an error code + */ +static int hw_ep_flush(struct ci_hdrc *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) + * @ci: the controller + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns an error code + */ +static int hw_ep_disable(struct ci_hdrc *ci, int num, int dir) +{ + hw_write(ci, OP_ENDPTCTRL + num, + (dir == TX) ? ENDPTCTRL_TXE : ENDPTCTRL_RXE, 0); + return 0; +} + +/** + * hw_ep_enable: enables endpoint (execute without interruption) + * @ci: the controller + * @num: endpoint number + * @dir: endpoint direction + * @type: endpoint type + * + * This function returns an error code + */ +static int hw_ep_enable(struct ci_hdrc *ci, int num, int dir, int type) +{ + u32 mask, data; + + if (dir == TX) { + 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 + * @ci: the controller + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns 1 if endpoint halted + */ +static int hw_ep_get_halt(struct ci_hdrc *ci, int num, int dir) +{ + u32 mask = (dir == TX) ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; + + return hw_read(ci, OP_ENDPTCTRL + num, mask) ? 1 : 0; +} + +/** + * hw_ep_prime: primes endpoint (execute without interruption) + * @ci: the controller + * @num: endpoint number + * @dir: endpoint direction + * @is_ctrl: true if control endpoint + * + * This function returns an error code + */ +static int hw_ep_prime(struct ci_hdrc *ci, int num, int dir, int is_ctrl) +{ + int n = hw_ep_bit(num, dir); + + /* Synchronize before ep prime */ + wmb(); + + 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) + * @ci: the controller + * @num: endpoint number + * @dir: endpoint direction + * @value: true => stall, false => unstall + * + * This function returns an error code + */ +static int hw_ep_set_halt(struct ci_hdrc *ci, int num, int dir, int value) +{ + if (value != 0 && value != 1) + return -EINVAL; + + do { + enum ci_hw_regs reg = OP_ENDPTCTRL + num; + u32 mask_xs = (dir == TX) ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; + u32 mask_xr = (dir == TX) ? 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_port_is_high_speed: test if port is high speed + * @ci: the controller + * + * This function returns true if high speed port + */ +static int hw_port_is_high_speed(struct ci_hdrc *ci) +{ + return ci->hw_bank.lpm ? hw_read(ci, OP_DEVLC, DEVLC_PSPD) : + hw_read(ci, OP_PORTSC, PORTSC_HSP); +} + +/** + * hw_test_and_clear_complete: test & clear complete status (execute without + * interruption) + * @ci: the controller + * @n: endpoint number + * + * This function returns complete status + */ +static int hw_test_and_clear_complete(struct ci_hdrc *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) + * @ci: the controller + * + * This function returns active interrutps + */ +static u32 hw_test_and_clear_intr_active(struct ci_hdrc *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) + * @ci: the controller + * + * This function returns guard value + */ +static int hw_test_and_clear_setup_guard(struct ci_hdrc *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) + * @ci: the controller + * + * This function returns guard value + */ +static int hw_test_and_set_setup_guard(struct ci_hdrc *ci) +{ + return hw_test_and_write(ci, OP_USBCMD, USBCMD_SUTW, USBCMD_SUTW); +} + +/** + * hw_usb_set_address: configures USB address (execute without interruption) + * @ci: the controller + * @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 ci_hdrc *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) + * @ci: the controller + * + * This function returns an error code + */ +static int hw_usb_reset(struct ci_hdrc *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 + *****************************************************************************/ + +static int add_td_to_list(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, + unsigned int length, struct scatterlist *s) +{ + int i; + u32 temp; + struct td_node *lastnode, *node = kzalloc_obj(struct td_node, + GFP_ATOMIC); + + if (node == NULL) + return -ENOMEM; + + node->ptr = dma_pool_zalloc(hwep->td_pool, GFP_ATOMIC, &node->dma); + if (node->ptr == NULL) { + kfree(node); + return -ENOMEM; + } + + node->ptr->token = cpu_to_le32(length << __ffs(TD_TOTAL_BYTES)); + node->ptr->token &= cpu_to_le32(TD_TOTAL_BYTES); + node->ptr->token |= cpu_to_le32(TD_STATUS_ACTIVE); + if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == TX) { + u32 mul = hwreq->req.length / hwep->ep.maxpacket; + + if (hwreq->req.length == 0 + || hwreq->req.length % hwep->ep.maxpacket) + mul++; + node->ptr->token |= cpu_to_le32(mul << __ffs(TD_MULTO)); + } + + if (s) { + temp = (u32) (sg_dma_address(s) + hwreq->req.actual); + node->td_remaining_size = CI_MAX_BUF_SIZE - length; + } else { + temp = (u32) (hwreq->req.dma + hwreq->req.actual); + } + + if (length) { + node->ptr->page[0] = cpu_to_le32(temp); + for (i = 1; i < TD_PAGE_COUNT; i++) { + u32 page = temp + i * CI_HDRC_PAGE_SIZE; + page &= ~TD_RESERVED_MASK; + node->ptr->page[i] = cpu_to_le32(page); + } + } + + hwreq->req.actual += length; + + if (!list_empty(&hwreq->tds)) { + /* get the last entry */ + lastnode = list_entry(hwreq->tds.prev, + struct td_node, td); + lastnode->ptr->next = cpu_to_le32(node->dma); + } + + INIT_LIST_HEAD(&node->td); + list_add_tail(&node->td, &hwreq->tds); + + return 0; +} + +/** + * _usb_addr: calculates endpoint address from direction & number + * @ep: endpoint + */ +static inline u8 _usb_addr(struct ci_hw_ep *ep) +{ + return ((ep->dir == TX) ? USB_ENDPOINT_DIR_MASK : 0) | ep->num; +} + +static int prepare_td_for_non_sg(struct ci_hw_ep *hwep, + struct ci_hw_req *hwreq) +{ + unsigned int rest = hwreq->req.length; + int pages = TD_PAGE_COUNT; + int ret = 0; + + if (rest == 0) { + ret = add_td_to_list(hwep, hwreq, 0, NULL); + if (ret < 0) + return ret; + } + + /* + * The first buffer could be not page aligned. + * In that case we have to span into one extra td. + */ + if (hwreq->req.dma % PAGE_SIZE) + pages--; + + while (rest > 0) { + unsigned int count = min(hwreq->req.length - hwreq->req.actual, + (unsigned int)(pages * CI_HDRC_PAGE_SIZE)); + + ret = add_td_to_list(hwep, hwreq, count, NULL); + if (ret < 0) + return ret; + + rest -= count; + } + + if (hwreq->req.zero && hwreq->req.length && hwep->dir == TX + && (hwreq->req.length % hwep->ep.maxpacket == 0)) { + ret = add_td_to_list(hwep, hwreq, 0, NULL); + if (ret < 0) + return ret; + } + + return ret; +} + +static int prepare_td_per_sg(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, + struct scatterlist *s) +{ + unsigned int rest = sg_dma_len(s); + int ret = 0; + + hwreq->req.actual = 0; + while (rest > 0) { + unsigned int count = min_t(unsigned int, rest, + CI_MAX_BUF_SIZE); + + ret = add_td_to_list(hwep, hwreq, count, s); + if (ret < 0) + return ret; + + rest -= count; + } + + return ret; +} + +static void ci_add_buffer_entry(struct td_node *node, struct scatterlist *s) +{ + int empty_td_slot_index = (CI_MAX_BUF_SIZE - node->td_remaining_size) + / CI_HDRC_PAGE_SIZE; + int i; + u32 token; + + token = le32_to_cpu(node->ptr->token) + (sg_dma_len(s) << __ffs(TD_TOTAL_BYTES)); + node->ptr->token = cpu_to_le32(token); + + for (i = empty_td_slot_index; i < TD_PAGE_COUNT; i++) { + u32 page = (u32) sg_dma_address(s) + + (i - empty_td_slot_index) * CI_HDRC_PAGE_SIZE; + + page &= ~TD_RESERVED_MASK; + node->ptr->page[i] = cpu_to_le32(page); + } +} + +static int prepare_td_for_sg(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) +{ + struct usb_request *req = &hwreq->req; + struct scatterlist *s = req->sg; + int ret = 0, i = 0; + struct td_node *node = NULL; + + if (!s || req->zero || req->length == 0) { + dev_err(hwep->ci->dev, "not supported operation for sg\n"); + return -EINVAL; + } + + while (i++ < req->num_mapped_sgs) { + if (sg_dma_address(s) % PAGE_SIZE) { + dev_err(hwep->ci->dev, "not page aligned sg buffer\n"); + return -EINVAL; + } + + if (node && (node->td_remaining_size >= sg_dma_len(s))) { + ci_add_buffer_entry(node, s); + node->td_remaining_size -= sg_dma_len(s); + } else { + ret = prepare_td_per_sg(hwep, hwreq, s); + if (ret) + return ret; + + node = list_entry(hwreq->tds.prev, + struct td_node, td); + } + + s = sg_next(s); + } + + return ret; +} + +/* + * Verify if the scatterlist is valid by iterating each sg entry. + * Return invalid sg entry index which is less than num_sgs. + */ +static int sglist_get_invalid_entry(struct device *dma_dev, u8 dir, + struct usb_request *req) +{ + int i; + struct scatterlist *s = req->sg; + + if (req->num_sgs == 1) + return 1; + + dir = dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + + for (i = 0; i < req->num_sgs; i++, s = sg_next(s)) { + /* Only small sg (generally last sg) may be bounced. If + * that happens. we can't ensure the addr is page-aligned + * after dma map. + */ + if (dma_kmalloc_needs_bounce(dma_dev, s->length, dir)) + break; + + /* Make sure each sg start address (except first sg) is + * page-aligned and end address (except last sg) is also + * page-aligned. + */ + if (i == 0) { + if (!IS_ALIGNED(s->offset + s->length, + CI_HDRC_PAGE_SIZE)) + break; + } else { + if (s->offset) + break; + if (!sg_is_last(s) && !IS_ALIGNED(s->length, + CI_HDRC_PAGE_SIZE)) + break; + } + } + + return i; +} + +static int sglist_do_bounce(struct ci_hw_req *hwreq, int index, + bool copy, unsigned int *bounced) +{ + void *buf; + int i, ret, nents, num_sgs; + unsigned int rest, rounded; + struct scatterlist *sg, *src, *dst; + + nents = index + 1; + ret = sg_alloc_table(&hwreq->sgt, nents, GFP_KERNEL); + if (ret) + return ret; + + sg = src = hwreq->req.sg; + num_sgs = hwreq->req.num_sgs; + rest = hwreq->req.length; + dst = hwreq->sgt.sgl; + + for (i = 0; i < index; i++) { + memcpy(dst, src, sizeof(*src)); + rest -= src->length; + src = sg_next(src); + dst = sg_next(dst); + } + + /* create one bounce buffer */ + rounded = round_up(rest, CI_HDRC_PAGE_SIZE); + buf = kmalloc(rounded, GFP_KERNEL); + if (!buf) { + sg_free_table(&hwreq->sgt); + return -ENOMEM; + } + + sg_set_buf(dst, buf, rounded); + + hwreq->req.sg = hwreq->sgt.sgl; + hwreq->req.num_sgs = nents; + hwreq->sgt.sgl = sg; + hwreq->sgt.nents = num_sgs; + + if (copy) + sg_copy_to_buffer(src, num_sgs - index, buf, rest); + + *bounced = rest; + + return 0; +} + +static void sglist_do_debounce(struct ci_hw_req *hwreq, bool copy) +{ + void *buf; + int i, nents, num_sgs; + struct scatterlist *sg, *src, *dst; + + sg = hwreq->req.sg; + num_sgs = hwreq->req.num_sgs; + src = sg_last(sg, num_sgs); + buf = sg_virt(src); + + if (copy) { + dst = hwreq->sgt.sgl; + for (i = 0; i < num_sgs - 1; i++) + dst = sg_next(dst); + + nents = hwreq->sgt.nents - num_sgs + 1; + sg_copy_from_buffer(dst, nents, buf, sg_dma_len(src)); + } + + hwreq->req.sg = hwreq->sgt.sgl; + hwreq->req.num_sgs = hwreq->sgt.nents; + hwreq->sgt.sgl = sg; + hwreq->sgt.nents = num_sgs; + + kfree(buf); + sg_free_table(&hwreq->sgt); +} + +/** + * _hardware_enqueue: configures a request at hardware level + * @hwep: endpoint + * @hwreq: request + * + * This function returns an error code + */ +static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) +{ + struct ci_hdrc *ci = hwep->ci; + int ret = 0; + struct td_node *firstnode, *lastnode; + unsigned int bounced_size; + struct scatterlist *sg; + + /* don't queue twice */ + if (hwreq->req.status == -EALREADY) + return -EALREADY; + + hwreq->req.status = -EALREADY; + + if (hwreq->req.num_sgs && hwreq->req.length && + ci->has_short_pkt_limit) { + ret = sglist_get_invalid_entry(ci->dev->parent, hwep->dir, + &hwreq->req); + if (ret < hwreq->req.num_sgs) { + ret = sglist_do_bounce(hwreq, ret, hwep->dir == TX, + &bounced_size); + if (ret) + return ret; + } + } + + ret = usb_gadget_map_request_by_dev(ci->dev->parent, + &hwreq->req, hwep->dir); + if (ret) + return ret; + + if (hwreq->sgt.sgl) { + /* We've mapped a bigger buffer, now recover the actual size */ + sg = sg_last(hwreq->req.sg, hwreq->req.num_sgs); + sg_dma_len(sg) = min(sg_dma_len(sg), bounced_size); + } + + if (hwreq->req.num_mapped_sgs) + ret = prepare_td_for_sg(hwep, hwreq); + else + ret = prepare_td_for_non_sg(hwep, hwreq); + + if (ret) + return ret; + + lastnode = list_entry(hwreq->tds.prev, + struct td_node, td); + + lastnode->ptr->next = cpu_to_le32(TD_TERMINATE); + if (!hwreq->req.no_interrupt) + lastnode->ptr->token |= cpu_to_le32(TD_IOC); + + list_for_each_entry_safe(firstnode, lastnode, &hwreq->tds, td) + trace_ci_prepare_td(hwep, hwreq, firstnode); + + firstnode = list_first_entry(&hwreq->tds, struct td_node, td); + + wmb(); + + hwreq->req.actual = 0; + if (!list_empty(&hwep->qh.queue)) { + struct ci_hw_req *hwreqprev; + int n = hw_ep_bit(hwep->num, hwep->dir); + int tmp_stat; + struct td_node *prevlastnode; + u32 next = firstnode->dma & TD_ADDR_MASK; + + hwreqprev = list_entry(hwep->qh.queue.prev, + struct ci_hw_req, queue); + prevlastnode = list_entry(hwreqprev->tds.prev, + struct td_node, td); + + prevlastnode->ptr->next = cpu_to_le32(next); + wmb(); + + if (ci->rev == CI_REVISION_22) { + if (!hw_read(ci, OP_ENDPTSTAT, BIT(n))) + reprime_dtd(ci, hwep, prevlastnode); + } + + 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) && tmp_stat); + hw_write(ci, OP_USBCMD, USBCMD_ATDTW, 0); + if (tmp_stat) + goto done; + + /* OP_ENDPTSTAT will be clear by HW when the endpoint met + * err. This dTD don't push to dQH if current dTD point is + * not the last one in previous request. + */ + if (hwep->qh.ptr->curr != cpu_to_le32(prevlastnode->dma)) + goto done; + } + + /* QH configuration */ + hwep->qh.ptr->td.next = cpu_to_le32(firstnode->dma); + hwep->qh.ptr->td.token &= + cpu_to_le32(~(TD_STATUS_HALTED|TD_STATUS_ACTIVE)); + + if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == RX) { + u32 mul = hwreq->req.length / hwep->ep.maxpacket; + + if (hwreq->req.length == 0 + || hwreq->req.length % hwep->ep.maxpacket) + mul++; + hwep->qh.ptr->cap |= cpu_to_le32(mul << __ffs(QH_MULT)); + } + + ret = hw_ep_prime(ci, hwep->num, hwep->dir, + hwep->type == USB_ENDPOINT_XFER_CONTROL); +done: + return ret; +} + +/** + * free_pending_td: remove a pending request for the endpoint + * @hwep: endpoint + */ +static void free_pending_td(struct ci_hw_ep *hwep) +{ + struct td_node *pending = hwep->pending_td; + + dma_pool_free(hwep->td_pool, pending->ptr, pending->dma); + hwep->pending_td = NULL; + kfree(pending); +} + +static int reprime_dtd(struct ci_hdrc *ci, struct ci_hw_ep *hwep, + struct td_node *node) +{ + hwep->qh.ptr->td.next = cpu_to_le32(node->dma); + hwep->qh.ptr->td.token &= + cpu_to_le32(~(TD_STATUS_HALTED | TD_STATUS_ACTIVE)); + + return hw_ep_prime(ci, hwep->num, hwep->dir, + hwep->type == USB_ENDPOINT_XFER_CONTROL); +} + +/** + * _hardware_dequeue: handles a request at hardware level + * @hwep: endpoint + * @hwreq: request + * + * This function returns an error code + */ +static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) +{ + u32 tmptoken; + struct td_node *node, *tmpnode; + unsigned remaining_length; + unsigned actual = hwreq->req.length; + struct ci_hdrc *ci = hwep->ci; + bool is_isoc = hwep->type == USB_ENDPOINT_XFER_ISOC; + + if (hwreq->req.status != -EALREADY) + return -EINVAL; + + hwreq->req.status = 0; + + list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) { + tmptoken = le32_to_cpu(node->ptr->token); + trace_ci_complete_td(hwep, hwreq, node); + if ((TD_STATUS_ACTIVE & tmptoken) != 0) { + int n = hw_ep_bit(hwep->num, hwep->dir); + + if (ci->rev == CI_REVISION_24 || + ci->rev == CI_REVISION_22 || is_isoc) + if (!hw_read(ci, OP_ENDPTSTAT, BIT(n))) + reprime_dtd(ci, hwep, node); + hwreq->req.status = -EALREADY; + return -EBUSY; + } + + remaining_length = (tmptoken & TD_TOTAL_BYTES); + remaining_length >>= __ffs(TD_TOTAL_BYTES); + actual -= remaining_length; + + hwreq->req.status = tmptoken & TD_STATUS; + if ((TD_STATUS_HALTED & hwreq->req.status)) { + hwreq->req.status = -EPIPE; + break; + } else if ((TD_STATUS_DT_ERR & hwreq->req.status)) { + hwreq->req.status = -EPROTO; + break; + } else if ((TD_STATUS_TR_ERR & hwreq->req.status)) { + if (is_isoc) { + hwreq->req.status = 0; + } else { + hwreq->req.status = -EILSEQ; + break; + } + } + + if (remaining_length && !is_isoc) { + if (hwep->dir == TX) { + hwreq->req.status = -EPROTO; + break; + } + } + /* + * As the hardware could still address the freed td + * which will run the udc unusable, the cleanup of the + * td has to be delayed by one. + */ + if (hwep->pending_td) + free_pending_td(hwep); + + hwep->pending_td = node; + list_del_init(&node->td); + } + + usb_gadget_unmap_request_by_dev(hwep->ci->dev->parent, + &hwreq->req, hwep->dir); + + /* sglist bounced */ + if (hwreq->sgt.sgl) + sglist_do_debounce(hwreq, hwep->dir == RX); + + hwreq->req.actual += actual; + + if (hwreq->req.status) + return hwreq->req.status; + + return hwreq->req.actual; +} + +/** + * _ep_nuke: dequeues all endpoint requests + * @hwep: endpoint + * + * This function returns an error code + * Caller must hold lock + */ +static int _ep_nuke(struct ci_hw_ep *hwep) +__releases(hwep->lock) +__acquires(hwep->lock) +{ + struct td_node *node, *tmpnode; + if (hwep == NULL) + return -EINVAL; + + hw_ep_flush(hwep->ci, hwep->num, hwep->dir); + + while (!list_empty(&hwep->qh.queue)) { + + /* pop oldest request */ + struct ci_hw_req *hwreq = list_entry(hwep->qh.queue.next, + struct ci_hw_req, queue); + + list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) { + dma_pool_free(hwep->td_pool, node->ptr, node->dma); + list_del_init(&node->td); + node->ptr = NULL; + kfree(node); + } + + list_del_init(&hwreq->queue); + hwreq->req.status = -ESHUTDOWN; + + /* Unmap DMA and clean up bounce buffers before giving back */ + usb_gadget_unmap_request_by_dev(hwep->ci->dev->parent, + &hwreq->req, hwep->dir); + + if (hwreq->sgt.sgl) + sglist_do_debounce(hwreq, false); + + if (hwreq->req.complete != NULL) { + spin_unlock(hwep->lock); + usb_gadget_giveback_request(&hwep->ep, &hwreq->req); + spin_lock(hwep->lock); + } + } + + if (hwep->pending_td) + free_pending_td(hwep); + + return 0; +} + +static int _ep_set_halt(struct usb_ep *ep, int value, bool check_transfer) +{ + struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); + int direction, retval = 0; + unsigned long flags; + + if (ep == NULL || hwep->ep.desc == NULL) + return -EINVAL; + + if (usb_endpoint_xfer_isoc(hwep->ep.desc)) + return -EOPNOTSUPP; + + spin_lock_irqsave(hwep->lock, flags); + + if (value && hwep->dir == TX && check_transfer && + !list_empty(&hwep->qh.queue) && + !usb_endpoint_xfer_control(hwep->ep.desc)) { + spin_unlock_irqrestore(hwep->lock, flags); + return -EAGAIN; + } + + direction = hwep->dir; + do { + retval |= hw_ep_set_halt(hwep->ci, hwep->num, hwep->dir, value); + + if (!value) + hwep->wedge = 0; + + if (hwep->type == USB_ENDPOINT_XFER_CONTROL) + hwep->dir = (hwep->dir == TX) ? RX : TX; + + } while (hwep->dir != direction); + + spin_unlock_irqrestore(hwep->lock, flags); + return retval; +} + + +/** + * _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 ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); + unsigned long 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); + + /* 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; + } + + 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); + + 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 ci_hdrc *ci) +__releases(ci->lock) +__acquires(ci->lock) +{ + int retval; + u32 intr; + + spin_unlock(&ci->lock); + if (ci->gadget.speed != USB_SPEED_UNKNOWN) + usb_gadget_udc_reset(&ci->gadget, ci->driver); + + retval = _gadget_stop_activity(&ci->gadget); + if (retval) + goto done; + + retval = hw_usb_reset(ci); + if (retval) + goto done; + + /* clear SLI */ + hw_write(ci, OP_USBSTS, USBi_SLI, USBi_SLI); + intr = hw_read(ci, OP_USBINTR, ~0); + hw_write(ci, OP_USBINTR, ~0, intr | USBi_SLI); + + 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 + * @ep: endpoint + * @req: request + * @gfp_flags: GFP flags (not used) + * + * Caller must hold lock + * This function returns an error code + */ +static int _ep_queue(struct usb_ep *ep, struct usb_request *req, + gfp_t __maybe_unused gfp_flags) +{ + struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); + struct ci_hw_req *hwreq = container_of(req, struct ci_hw_req, req); + struct ci_hdrc *ci = hwep->ci; + int retval = 0; + + if (ep == NULL || req == NULL || hwep->ep.desc == NULL) + return -EINVAL; + + if (hwep->type == USB_ENDPOINT_XFER_CONTROL) { + if (req->length) + hwep = (ci->ep0_dir == RX) ? + ci->ep0out : ci->ep0in; + if (!list_empty(&hwep->qh.queue)) { + _ep_nuke(hwep); + dev_warn(hwep->ci->dev, "endpoint ctrl %X nuked\n", + _usb_addr(hwep)); + } + } + + if (usb_endpoint_xfer_isoc(hwep->ep.desc) && + hwreq->req.length > hwep->ep.mult * hwep->ep.maxpacket) { + dev_err(hwep->ci->dev, "request length too big for isochronous\n"); + return -EMSGSIZE; + } + + if (ci->has_short_pkt_limit && + hwreq->req.length > CI_MAX_REQ_SIZE) { + dev_err(hwep->ci->dev, "request length too big (max 16KB)\n"); + return -EMSGSIZE; + } + + /* first nuke then test link, e.g. previous status has not sent */ + if (!list_empty(&hwreq->queue)) { + dev_err(hwep->ci->dev, "request already in queue\n"); + return -EBUSY; + } + + /* push request */ + hwreq->req.status = -EINPROGRESS; + hwreq->req.actual = 0; + + retval = _hardware_enqueue(hwep, hwreq); + + if (retval == -EALREADY) + retval = 0; + if (!retval) + list_add_tail(&hwreq->queue, &hwep->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 ci_hdrc *ci, + struct usb_ctrlrequest *setup) +__releases(hwep->lock) +__acquires(hwep->lock) +{ + struct ci_hw_ep *hwep = ci->ep0in; + struct usb_request *req = NULL; + gfp_t gfp_flags = GFP_ATOMIC; + int dir, num, retval; + + if (hwep == NULL || setup == NULL) + return -EINVAL; + + spin_unlock(hwep->lock); + req = usb_ep_alloc_request(&hwep->ep, gfp_flags); + spin_lock(hwep->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) { + *(u16 *)req->buf = (ci->remote_wakeup << 1) | + ci->gadget.is_selfpowered; + } 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(&hwep->ep, req, gfp_flags); + if (retval) + goto err_free_buf; + + return 0; + + err_free_buf: + kfree(req->buf); + err_free_req: + spin_unlock(hwep->lock); + usb_ep_free_request(&hwep->ep, req); + spin_lock(hwep->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 ci_hdrc *ci = req->context; + unsigned long flags; + + if (req->status < 0) + return; + + if (ci->setaddr) { + hw_usb_set_address(ci, ci->address); + ci->setaddr = false; + if (ci->address) + usb_gadget_set_state(&ci->gadget, USB_STATE_ADDRESS); + } + + 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 ci_hdrc *ci) +{ + struct ci_hw_ep *hwep; + + /* + * Unexpected USB controller behavior, caused by bad signal integrity + * or ground reference problems, can lead to isr_setup_status_phase + * being called with ci->status equal to NULL. + * If this situation occurs, you should review your USB hardware design. + */ + if (WARN_ON_ONCE(!ci->status)) + return -EPIPE; + + hwep = (ci->ep0_dir == TX) ? ci->ep0out : ci->ep0in; + ci->status->context = ci; + ci->status->complete = isr_setup_status_complete; + + return _ep_queue(&hwep->ep, ci->status, GFP_ATOMIC); +} + +/** + * isr_tr_complete_low: transaction complete low level handler + * @hwep: endpoint + * + * This function returns an error code + * Caller must hold lock + */ +static int isr_tr_complete_low(struct ci_hw_ep *hwep) +__releases(hwep->lock) +__acquires(hwep->lock) +{ + struct ci_hw_req *hwreq, *hwreqtemp; + struct ci_hw_ep *hweptemp = hwep; + int retval = 0; + + list_for_each_entry_safe(hwreq, hwreqtemp, &hwep->qh.queue, + queue) { + retval = _hardware_dequeue(hwep, hwreq); + if (retval < 0) + break; + list_del_init(&hwreq->queue); + if (hwreq->req.complete != NULL) { + spin_unlock(hwep->lock); + if ((hwep->type == USB_ENDPOINT_XFER_CONTROL) && + hwreq->req.length) + hweptemp = hwep->ci->ep0in; + usb_gadget_giveback_request(&hweptemp->ep, &hwreq->req); + spin_lock(hwep->lock); + } + } + + if (retval == -EBUSY) + retval = 0; + + return retval; +} + +static int otg_a_alt_hnp_support(struct ci_hdrc *ci) +{ + dev_warn(&ci->gadget.dev, + "connect the device to an alternate port if you want HNP\n"); + return isr_setup_status_phase(ci); +} + +/** + * isr_setup_packet_handler: setup packet handler + * @ci: UDC descriptor + * + * This function handles setup packet + */ +static void isr_setup_packet_handler(struct ci_hdrc *ci) +__releases(ci->lock) +__acquires(ci->lock) +{ + struct ci_hw_ep *hwep = &ci->ci_hw_ep[0]; + struct usb_ctrlrequest req; + int type, num, dir, err = -EINVAL; + u8 tmode = 0; + + /* + * 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, &hwep->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) ? TX : RX; + num &= USB_ENDPOINT_NUMBER_MASK; + if (dir == TX) + num += ci->hw_ep_max / 2; + if (!ci->ci_hw_ep[num].wedge) { + spin_unlock(&ci->lock); + err = usb_ep_clear_halt( + &ci->ci_hw_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) || + le16_to_cpu(req.wIndex) == OTG_STS_SELECTOR) && + 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) ? TX : RX; + num &= USB_ENDPOINT_NUMBER_MASK; + if (dir == TX) + num += ci->hw_ep_max / 2; + + spin_unlock(&ci->lock); + err = _ep_set_halt(&ci->ci_hw_ep[num].ep, 1, false); + 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 USB_TEST_J: + case USB_TEST_K: + case USB_TEST_SE0_NAK: + case USB_TEST_PACKET: + case USB_TEST_FORCE_ENABLE: + ci->test_mode = tmode; + err = isr_setup_status_phase( + ci); + break; + default: + break; + } + break; + case USB_DEVICE_B_HNP_ENABLE: + if (ci_otg_is_fsm_mode(ci)) { + ci->gadget.b_hnp_enable = 1; + err = isr_setup_status_phase( + ci); + } + break; + case USB_DEVICE_A_ALT_HNP_SUPPORT: + if (ci_otg_is_fsm_mode(ci)) + err = otg_a_alt_hnp_support(ci); + break; + case USB_DEVICE_A_HNP_SUPPORT: + if (ci_otg_is_fsm_mode(ci)) { + ci->gadget.a_hnp_support = 1; + err = isr_setup_status_phase( + ci); + } + 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 (_ep_set_halt(&hwep->ep, 1, false)) + dev_err(ci->dev, "error: _ep_set_halt\n"); + spin_lock(&ci->lock); + } +} + +/** + * isr_tr_complete_handler: transaction complete interrupt handler + * @ci: UDC descriptor + * + * This function handles traffic events + */ +static void isr_tr_complete_handler(struct ci_hdrc *ci) +__releases(ci->lock) +__acquires(ci->lock) +{ + unsigned i; + int err; + + for (i = 0; i < ci->hw_ep_max; i++) { + struct ci_hw_ep *hwep = &ci->ci_hw_ep[i]; + + if (hwep->ep.desc == NULL) + continue; /* not configured */ + + if (hw_test_and_clear_complete(ci, i)) { + err = isr_tr_complete_low(hwep); + if (hwep->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 (_ep_set_halt(&hwep->ep, 1, false)) + dev_err(ci->dev, + "error: _ep_set_halt\n"); + spin_lock(&ci->lock); + } + } + } + + /* Only handle setup packet below */ + if (i == 0 && + hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(0))) + isr_setup_packet_handler(ci); + } +} + +/****************************************************************************** + * 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 ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); + int retval = 0; + unsigned long flags; + u32 cap = 0; + + if (ep == NULL || desc == NULL) + return -EINVAL; + + spin_lock_irqsave(hwep->lock, flags); + + /* only internal SW should enable ctrl endpts */ + + if (!list_empty(&hwep->qh.queue)) { + dev_warn(hwep->ci->dev, "enabling a non-empty endpoint!\n"); + spin_unlock_irqrestore(hwep->lock, flags); + return -EBUSY; + } + + hwep->ep.desc = desc; + + hwep->dir = usb_endpoint_dir_in(desc) ? TX : RX; + hwep->num = usb_endpoint_num(desc); + hwep->type = usb_endpoint_type(desc); + + hwep->ep.maxpacket = usb_endpoint_maxp(desc); + hwep->ep.mult = usb_endpoint_maxp_mult(desc); + + if (hwep->type == USB_ENDPOINT_XFER_CONTROL) + cap |= QH_IOS; + + cap |= QH_ZLT; + cap |= (hwep->ep.maxpacket << __ffs(QH_MAX_PKT)) & QH_MAX_PKT; + /* + * For ISO-TX, we set mult at QH as the largest value, and use + * MultO at TD as real mult value. + */ + if (hwep->type == USB_ENDPOINT_XFER_ISOC && hwep->dir == TX) + cap |= 3 << __ffs(QH_MULT); + + hwep->qh.ptr->cap = cpu_to_le32(cap); + + hwep->qh.ptr->td.next |= cpu_to_le32(TD_TERMINATE); /* needed? */ + + if (hwep->num != 0 && hwep->type == USB_ENDPOINT_XFER_CONTROL) { + dev_err(hwep->ci->dev, "Set control xfer at non-ep0\n"); + retval = -EINVAL; + } + + /* + * Enable endpoints in the HW other than ep0 as ep0 + * is always enabled + */ + if (hwep->num) + retval |= hw_ep_enable(hwep->ci, hwep->num, hwep->dir, + hwep->type); + + spin_unlock_irqrestore(hwep->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 ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); + int direction, retval = 0; + unsigned long flags; + + if (ep == NULL) + return -EINVAL; + else if (hwep->ep.desc == NULL) + return -EBUSY; + + spin_lock_irqsave(hwep->lock, flags); + if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(hwep->lock, flags); + return 0; + } + + /* only internal SW should disable ctrl endpts */ + + direction = hwep->dir; + do { + retval |= _ep_nuke(hwep); + retval |= hw_ep_disable(hwep->ci, hwep->num, hwep->dir); + + if (hwep->type == USB_ENDPOINT_XFER_CONTROL) + hwep->dir = (hwep->dir == TX) ? RX : TX; + + } while (hwep->dir != direction); + + hwep->ep.desc = NULL; + + spin_unlock_irqrestore(hwep->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 ci_hw_req *hwreq; + + if (ep == NULL) + return NULL; + + hwreq = kzalloc_obj(struct ci_hw_req, gfp_flags); + if (hwreq != NULL) { + INIT_LIST_HEAD(&hwreq->queue); + INIT_LIST_HEAD(&hwreq->tds); + } + + return (hwreq == NULL) ? NULL : &hwreq->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 ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); + struct ci_hw_req *hwreq = container_of(req, struct ci_hw_req, req); + struct td_node *node, *tmpnode; + unsigned long flags; + + if (ep == NULL || req == NULL) { + return; + } else if (!list_empty(&hwreq->queue)) { + dev_err(hwep->ci->dev, "freeing queued request\n"); + return; + } + + spin_lock_irqsave(hwep->lock, flags); + + list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) { + dma_pool_free(hwep->td_pool, node->ptr, node->dma); + list_del_init(&node->td); + node->ptr = NULL; + kfree(node); + } + + kfree(hwreq); + + spin_unlock_irqrestore(hwep->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 ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); + int retval = 0; + unsigned long flags; + + if (ep == NULL || req == NULL || hwep->ep.desc == NULL) + return -EINVAL; + + spin_lock_irqsave(hwep->lock, flags); + if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(hwep->lock, flags); + return 0; + } + retval = _ep_queue(ep, req, gfp_flags); + spin_unlock_irqrestore(hwep->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 ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); + struct ci_hw_req *hwreq = container_of(req, struct ci_hw_req, req); + unsigned long flags; + struct td_node *node, *tmpnode; + + if (ep == NULL || req == NULL || hwreq->req.status != -EALREADY || + hwep->ep.desc == NULL || list_empty(&hwreq->queue) || + list_empty(&hwep->qh.queue)) + return -EINVAL; + + spin_lock_irqsave(hwep->lock, flags); + if (hwep->ci->gadget.speed != USB_SPEED_UNKNOWN) + hw_ep_flush(hwep->ci, hwep->num, hwep->dir); + + list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) { + dma_pool_free(hwep->td_pool, node->ptr, node->dma); + list_del(&node->td); + kfree(node); + } + + /* pop request */ + list_del_init(&hwreq->queue); + + usb_gadget_unmap_request(&hwep->ci->gadget, req, hwep->dir); + + if (hwreq->sgt.sgl) + sglist_do_debounce(hwreq, false); + + req->status = -ECONNRESET; + + if (hwreq->req.complete != NULL) { + spin_unlock(hwep->lock); + usb_gadget_giveback_request(&hwep->ep, &hwreq->req); + spin_lock(hwep->lock); + } + + spin_unlock_irqrestore(hwep->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) +{ + return _ep_set_halt(ep, value, true); +} + +/* + * 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 ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); + unsigned long flags; + + if (ep == NULL || hwep->ep.desc == NULL) + return -EINVAL; + + spin_lock_irqsave(hwep->lock, flags); + hwep->wedge = 1; + spin_unlock_irqrestore(hwep->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 ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep); + unsigned long flags; + + if (ep == NULL) { + dev_err(hwep->ci->dev, "%02X: -EINVAL\n", _usb_addr(hwep)); + return; + } + + spin_lock_irqsave(hwep->lock, flags); + if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(hwep->lock, flags); + return; + } + + hw_ep_flush(hwep->ci, hwep->num, hwep->dir); + + spin_unlock_irqrestore(hwep->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 void ci_udc_enable_vbus_irq(struct ci_hdrc *ci, bool enable) +{ + u32 reg = OTGSC_BSVIS; + + if (!ci->is_otg) + return; + + if (enable) + reg |= OTGSC_BSVIE; + + /* Clear pending BSVIS and enable/disable BSVIE */ + hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, reg); +} + +static int ci_udc_get_frame(struct usb_gadget *_gadget) +{ + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + unsigned long flags; + int ret; + + spin_lock_irqsave(&ci->lock, flags); + ret = hw_read(ci, OP_FRINDEX, 0x3fff); + spin_unlock_irqrestore(&ci->lock, flags); + return ret >> 3; +} + +/* + * ci_hdrc_gadget_connect: caller makes sure gadget driver is binded + */ +static void ci_hdrc_gadget_connect(struct usb_gadget *_gadget, int is_active) +{ + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + + if (is_active) { + pm_runtime_get_sync(ci->dev); + hw_device_reset(ci); + spin_lock_irq(&ci->lock); + if (ci->driver) { + hw_device_state(ci, ci->ep0out->qh.dma); + usb_gadget_set_state(_gadget, USB_STATE_POWERED); + spin_unlock_irq(&ci->lock); + usb_udc_vbus_handler(_gadget, true); + } else { + spin_unlock_irq(&ci->lock); + } + } else { + usb_udc_vbus_handler(_gadget, false); + if (ci->driver) + ci->driver->disconnect(&ci->gadget); + hw_device_state(ci, 0); + if (ci->platdata->notify_event) + ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_STOPPED_EVENT); + _gadget_stop_activity(&ci->gadget); + pm_runtime_put_sync(ci->dev); + usb_gadget_set_state(_gadget, USB_STATE_NOTATTACHED); + } +} + +static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&ci->lock, flags); + ci->vbus_active = is_active; + spin_unlock_irqrestore(&ci->lock, flags); + + if (ci->usb_phy) + usb_phy_set_charger_state(ci->usb_phy, is_active ? + USB_CHARGER_PRESENT : USB_CHARGER_ABSENT); + + if (ci->platdata->notify_event) + ret = ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_VBUS_EVENT); + + if (ci->usb_phy) { + if (is_active) + usb_phy_set_event(ci->usb_phy, USB_EVENT_VBUS); + else + usb_phy_set_event(ci->usb_phy, USB_EVENT_NONE); + } + + if (ci->driver) + ci_hdrc_gadget_connect(_gadget, is_active); + + return ret; +} + +static int ci_udc_wakeup(struct usb_gadget *_gadget) +{ + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&ci->lock, flags); + if (ci->gadget.speed == USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(&ci->lock, flags); + return 0; + } + 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 ci_udc_vbus_draw(struct usb_gadget *_gadget, unsigned ma) +{ + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + + if (ci->usb_phy) + return usb_phy_set_power(ci->usb_phy, ma); + return -ENOTSUPP; +} + +static int ci_udc_selfpowered(struct usb_gadget *_gadget, int is_on) +{ + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + struct ci_hw_ep *hwep = ci->ep0in; + unsigned long flags; + + spin_lock_irqsave(hwep->lock, flags); + _gadget->is_selfpowered = (is_on != 0); + spin_unlock_irqrestore(hwep->lock, flags); + + return 0; +} + +/* Change Data+ pullup status + * this func is used by usb_gadget_connect/disconnect + */ +static int ci_udc_pullup(struct usb_gadget *_gadget, int is_on) +{ + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + + /* + * Data+ pullup controlled by OTG state machine in OTG fsm mode; + * and don't touch Data+ in host mode for dual role config. + */ + if (ci_otg_is_fsm_mode(ci) || ci->role == CI_ROLE_HOST) + return 0; + + pm_runtime_get_sync(ci->dev); + if (is_on) + hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); + else + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + + if (ci->platdata->notify_event) { + _gadget->connected = is_on; + ci->platdata->notify_event(ci, CI_HDRC_CONTROLLER_PULLUP_EVENT); + } + pm_runtime_put_sync(ci->dev); + + return 0; +} + +static int ci_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static int ci_udc_stop(struct usb_gadget *gadget); + +/* Match ISOC IN from the highest endpoint */ +static struct usb_ep *ci_udc_match_ep(struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *comp_desc) +{ + struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); + struct usb_ep *ep; + + if (usb_endpoint_xfer_isoc(desc) && usb_endpoint_dir_in(desc)) { + list_for_each_entry_reverse(ep, &ci->gadget.ep_list, ep_list) { + if (ep->caps.dir_in && !ep->claimed) + return ep; + } + } + + return NULL; +} + +/* + * 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 = { + .get_frame = ci_udc_get_frame, + .vbus_session = ci_udc_vbus_session, + .wakeup = ci_udc_wakeup, + .set_selfpowered = ci_udc_selfpowered, + .pullup = ci_udc_pullup, + .vbus_draw = ci_udc_vbus_draw, + .udc_start = ci_udc_start, + .udc_stop = ci_udc_stop, + .match_ep = ci_udc_match_ep, +}; + +static int init_eps(struct ci_hdrc *ci) +{ + int retval = 0, i, j; + + memset(ci->ci_hw_ep, 0, sizeof(ci->ci_hw_ep)); + + 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 ci_hw_ep *hwep = &ci->ci_hw_ep[k]; + + scnprintf(hwep->name, sizeof(hwep->name), "ep%i%s", i, + (j == TX) ? "in" : "out"); + + hwep->ci = ci; + hwep->lock = &ci->lock; + hwep->td_pool = ci->td_pool; + + hwep->ep.name = hwep->name; + hwep->ep.ops = &usb_ep_ops; + + if (i == 0) { + hwep->ep.caps.type_control = true; + } else { + hwep->ep.caps.type_iso = true; + hwep->ep.caps.type_bulk = true; + hwep->ep.caps.type_int = true; + } + + if (j == TX) + hwep->ep.caps.dir_in = true; + else + hwep->ep.caps.dir_out = true; + + /* + * for ep0: maxP defined in desc, for other + * eps, maxP is set by epautoconfig() called + * by gadget layer + */ + usb_ep_set_maxpacket_limit(&hwep->ep, (unsigned short)~0); + + INIT_LIST_HEAD(&hwep->qh.queue); + hwep->qh.ptr = dma_pool_zalloc(ci->qh_pool, GFP_KERNEL, + &hwep->qh.dma); + if (hwep->qh.ptr == NULL) + retval = -ENOMEM; + + /* + * 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 = hwep; + else + ci->ep0in = hwep; + + usb_ep_set_maxpacket_limit(&hwep->ep, CTRL_PAYLOAD_MAX); + continue; + } + + list_add_tail(&hwep->ep.ep_list, &ci->gadget.ep_list); + } + + return retval; +} + +static void destroy_eps(struct ci_hdrc *ci) +{ + int i; + + for (i = 0; i < ci->hw_ep_max; i++) { + struct ci_hw_ep *hwep = &ci->ci_hw_ep[i]; + + if (hwep->pending_td) + free_pending_td(hwep); + dma_pool_free(ci->qh_pool, hwep->qh.ptr, hwep->qh.dma); + } +} + +/** + * ci_udc_start: register a gadget driver + * @gadget: our gadget + * @driver: the driver being registered + * + * Interrupts are enabled here. + */ +static int ci_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); + int retval; + + 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; + + ci->driver = driver; + + /* Start otg fsm for B-device */ + if (ci_otg_is_fsm_mode(ci) && ci->fsm.id) { + ci_hdrc_otg_fsm_start(ci); + return retval; + } + + if (ci->vbus_active) + ci_hdrc_gadget_connect(gadget, 1); + else + usb_udc_vbus_handler(&ci->gadget, false); + + return retval; +} + +static void ci_udc_stop_for_otg_fsm(struct ci_hdrc *ci) +{ + if (!ci_otg_is_fsm_mode(ci)) + return; + + mutex_lock(&ci->fsm.lock); + if (ci->fsm.otg->state == OTG_STATE_A_PERIPHERAL) { + ci->fsm.a_bidl_adis_tmout = 1; + ci_hdrc_otg_fsm_start(ci); + } else if (ci->fsm.otg->state == OTG_STATE_B_PERIPHERAL) { + ci->fsm.protocol = PROTO_UNDEF; + ci->fsm.otg->state = OTG_STATE_UNDEFINED; + } + mutex_unlock(&ci->fsm.lock); +} + +/* + * ci_udc_stop: unregister a gadget driver + */ +static int ci_udc_stop(struct usb_gadget *gadget) +{ + struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); + unsigned long flags; + + spin_lock_irqsave(&ci->lock, flags); + ci->driver = NULL; + + if (ci->vbus_active) { + hw_device_state(ci, 0); + spin_unlock_irqrestore(&ci->lock, flags); + if (ci->platdata->notify_event) + ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_STOPPED_EVENT); + _gadget_stop_activity(&ci->gadget); + spin_lock_irqsave(&ci->lock, flags); + pm_runtime_put(ci->dev); + } + + spin_unlock_irqrestore(&ci->lock, flags); + + ci_udc_stop_for_otg_fsm(ci); + 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 ci_hdrc *ci) +{ + irqreturn_t retval; + u32 intr; + + if (ci == NULL) + return IRQ_HANDLED; + + spin_lock(&ci->lock); + + if (ci->platdata->flags & CI_HDRC_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->usb_phy) + usb_phy_set_event(ci->usb_phy, + USB_EVENT_ENUMERATED); + if (ci->suspended) { + if (ci->driver->resume) { + spin_unlock(&ci->lock); + ci->driver->resume(&ci->gadget); + spin_lock(&ci->lock); + } + ci->suspended = 0; + usb_gadget_set_state(&ci->gadget, + ci->resume_state); + } + } + + if ((USBi_UI | USBi_UEI) & intr) + isr_tr_complete_handler(ci); + + if ((USBi_SLI & intr) && !(ci->suspended)) { + ci->suspended = 1; + ci->resume_state = ci->gadget.state; + if (ci->gadget.speed != USB_SPEED_UNKNOWN && + ci->driver->suspend) { + spin_unlock(&ci->lock); + ci->driver->suspend(&ci->gadget); + spin_lock(&ci->lock); + } + usb_gadget_set_state(&ci->gadget, + USB_STATE_SUSPENDED); + } + 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 ci_hdrc *ci) +{ + struct device *dev = ci->dev; + struct usb_otg_caps *otg_caps = &ci->platdata->ci_otg_caps; + int retval = 0; + + memset(&ci->gadget, 0, sizeof(ci->gadget)); + + ci->gadget.ops = &usb_gadget_ops; + ci->gadget.speed = USB_SPEED_UNKNOWN; + ci->gadget.max_speed = USB_SPEED_HIGH; + ci->gadget.name = ci->platdata->name; + ci->gadget.otg_caps = otg_caps; + ci->gadget.sg_supported = 1; + ci->gadget.irq = ci->irq; + + if (ci->platdata->flags & CI_HDRC_REQUIRES_ALIGNED_DMA) + ci->gadget.quirk_avoids_skb_reserve = 1; + + if (ci->is_otg && (otg_caps->hnp_support || otg_caps->srp_support || + otg_caps->adp_support)) + ci->gadget.is_otg = 1; + + INIT_LIST_HEAD(&ci->gadget.ep_list); + + /* alloc resources */ + ci->qh_pool = dma_pool_create("ci_hw_qh", dev->parent, + sizeof(struct ci_hw_qh), + 64, CI_HDRC_PAGE_SIZE); + if (ci->qh_pool == NULL) + return -ENOMEM; + + ci->td_pool = dma_pool_create("ci_hw_td", dev->parent, + sizeof(struct ci_hw_td), + 64, CI_HDRC_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->platdata->pins_device) + pinctrl_select_state(ci->platdata->pctl, + ci->platdata->pins_device); + + retval = usb_add_gadget_udc(dev, &ci->gadget); + if (retval) + goto destroy_eps; + + ci_udc_enable_vbus_irq(ci, true); + return retval; + +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_stop: deinitialize gadget role + * @ci: chipidea controller + */ +static void udc_stop(struct ci_hdrc *ci) +{ + ci_udc_enable_vbus_irq(ci, false); + usb_del_gadget_udc(&ci->gadget); + ci->vbus_active = 0; + + destroy_eps(ci); + + dma_pool_destroy(ci->td_pool); + dma_pool_destroy(ci->qh_pool); + + if (ci->platdata->pins_device && ci->platdata->pins_default) + pinctrl_select_state(ci->platdata->pctl, + ci->platdata->pins_default); +} + +#ifdef CONFIG_PM_SLEEP +static void udc_suspend(struct ci_hdrc *ci) +{ + /* + * Set OP_ENDPTLISTADDR to be non-zero for + * checking if controller resume from power lost + * in non-host mode. + */ + if (hw_read(ci, OP_ENDPTLISTADDR, ~0) == 0) + hw_write(ci, OP_ENDPTLISTADDR, ~0, ~0); + + if (ci->gadget.connected && + (!ci->suspended || !device_may_wakeup(ci->dev))) + usb_gadget_disconnect(&ci->gadget); +} + +static void udc_resume(struct ci_hdrc *ci, bool power_lost) +{ + if (power_lost) { + ci_udc_enable_vbus_irq(ci, true); + if (ci->vbus_active) + usb_gadget_vbus_disconnect(&ci->gadget); + } else if (ci->vbus_active && ci->driver && + !ci->gadget.connected) { + usb_gadget_connect(&ci->gadget); + } + + /* Restore value 0 if it was set for power lost check */ + if (hw_read(ci, OP_ENDPTLISTADDR, ~0) == 0xFFFFFFFF) + hw_write(ci, OP_ENDPTLISTADDR, ~0, 0); +} +#endif + +/** + * ci_hdrc_gadget_init - initialize device related bits + * @ci: the controller + * + * This function initializes the gadget, if the device is "device capable". + */ +int ci_hdrc_gadget_init(struct ci_hdrc *ci) +{ + struct ci_role_driver *rdrv; + + if (!hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_DC)) + return -ENXIO; + + rdrv = devm_kzalloc(ci->dev, sizeof(*rdrv), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + rdrv->start = udc_start; + rdrv->stop = udc_stop; +#ifdef CONFIG_PM_SLEEP + rdrv->suspend = udc_suspend; + rdrv->resume = udc_resume; +#endif + rdrv->irq = udc_irq; + rdrv->name = "gadget"; + + ci->roles[CI_ROLE_GADGET] = rdrv; + + /* Pull down DP for possible charger detection */ + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + return 0; +} + +/* + * ci_hdrc_gadget_destroy: parent remove must call this to remove UDC + * + * No interrupts active, the IRQ has been released + */ +void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) +{ + struct device *dev = &ci->gadget.dev; + + if (ci->roles[CI_ROLE_GADGET] && device_is_registered(dev)) + udc_stop(ci); +} diff --git a/drivers/usb/chipidea/udc.h b/drivers/usb/chipidea/udc.h new file mode 100644 index 000000000..c8a47389a --- /dev/null +++ b/drivers/usb/chipidea/udc.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * udc.h - ChipIdea UDC structures + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + */ + +#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 ci_hw_td { + /* 0 */ + __le32 next; +#define TD_TERMINATE BIT(0) +#define TD_ADDR_MASK (0xFFFFFFEUL << 5) + /* 1 */ + __le32 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 */ + __le32 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 ci_hw_qh { + /* 0 */ + __le32 cap; +#define QH_IOS BIT(15) +#define QH_MAX_PKT (0x07FFUL << 16) +#define QH_ZLT BIT(29) +#define QH_MULT (0x0003UL << 30) +#define QH_ISO_MULT(x) ((x >> 11) & 0x03) + /* 1 */ + __le32 curr; + /* 2 - 8 */ + struct ci_hw_td td; + /* 9 */ + __le32 RESERVED; + struct usb_ctrlrequest setup; +} __attribute__ ((packed, aligned(4))); + +struct td_node { + struct list_head td; + dma_addr_t dma; + struct ci_hw_td *ptr; + int td_remaining_size; +}; + +/** + * struct ci_hw_req - usb request representation + * @req: request structure for gadget drivers + * @queue: link to QH list + * @tds: link to TD list + * @sgt: hold original sglist when bounce sglist + */ +struct ci_hw_req { + struct usb_request req; + struct list_head queue; + struct list_head tds; + struct sg_table sgt; +}; + +#ifdef CONFIG_USB_CHIPIDEA_UDC + +int ci_hdrc_gadget_init(struct ci_hdrc *ci); +void ci_hdrc_gadget_destroy(struct ci_hdrc *ci); + +#else + +static inline int ci_hdrc_gadget_init(struct ci_hdrc *ci) +{ + return -ENXIO; +} + +static inline void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) +{ + +} + +#endif + +#endif /* __DRIVERS_USB_CHIPIDEA_UDC_H */ diff --git a/drivers/usb/chipidea/ulpi.c b/drivers/usb/chipidea/ulpi.c new file mode 100644 index 000000000..dfec07e8a --- /dev/null +++ b/drivers/usb/chipidea/ulpi.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016 Linaro Ltd. + */ + +#include +#include +#include + +#include "ci.h" + +#define ULPI_WAKEUP BIT(31) +#define ULPI_RUN BIT(30) +#define ULPI_WRITE BIT(29) +#define ULPI_SYNC_STATE BIT(27) +#define ULPI_ADDR(n) ((n) << 16) +#define ULPI_DATA(n) (n) + +static int ci_ulpi_wait(struct ci_hdrc *ci, u32 mask) +{ + unsigned long usec = 10000; + + while (usec--) { + if (!hw_read(ci, OP_ULPI_VIEWPORT, mask)) + return 0; + + udelay(1); + } + + return -ETIMEDOUT; +} + +static int ci_ulpi_read(struct device *dev, u8 addr) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + int ret; + + hw_write(ci, OP_ULPI_VIEWPORT, 0xffffffff, ULPI_WRITE | ULPI_WAKEUP); + ret = ci_ulpi_wait(ci, ULPI_WAKEUP); + if (ret) + return ret; + + hw_write(ci, OP_ULPI_VIEWPORT, 0xffffffff, ULPI_RUN | ULPI_ADDR(addr)); + ret = ci_ulpi_wait(ci, ULPI_RUN); + if (ret) + return ret; + + return hw_read(ci, OP_ULPI_VIEWPORT, GENMASK(15, 8)) >> 8; +} + +static int ci_ulpi_write(struct device *dev, u8 addr, u8 val) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + int ret; + + hw_write(ci, OP_ULPI_VIEWPORT, 0xffffffff, ULPI_WRITE | ULPI_WAKEUP); + ret = ci_ulpi_wait(ci, ULPI_WAKEUP); + if (ret) + return ret; + + hw_write(ci, OP_ULPI_VIEWPORT, 0xffffffff, + ULPI_RUN | ULPI_WRITE | ULPI_ADDR(addr) | val); + return ci_ulpi_wait(ci, ULPI_RUN); +} + +int ci_ulpi_init(struct ci_hdrc *ci) +{ + if (ci->platdata->phy_mode != USBPHY_INTERFACE_MODE_ULPI) + return 0; + + /* + * Set PORTSC correctly so we can read/write ULPI registers for + * identification purposes + */ + hw_phymode_configure(ci); + + ci->ulpi_ops.read = ci_ulpi_read; + ci->ulpi_ops.write = ci_ulpi_write; + ci->ulpi = ulpi_register_interface(ci->dev, &ci->ulpi_ops); + if (IS_ERR(ci->ulpi)) + dev_err(ci->dev, "failed to register ULPI interface"); + + return PTR_ERR_OR_ZERO(ci->ulpi); +} + +void ci_ulpi_exit(struct ci_hdrc *ci) +{ + if (ci->ulpi) { + ulpi_unregister_interface(ci->ulpi); + ci->ulpi = NULL; + } +} + +int ci_ulpi_resume(struct ci_hdrc *ci) +{ + int cnt = 100000; + + if (ci->platdata->phy_mode != USBPHY_INTERFACE_MODE_ULPI) + return 0; + + while (cnt-- > 0) { + if (hw_read(ci, OP_ULPI_VIEWPORT, ULPI_SYNC_STATE)) + return 0; + udelay(1); + } + + return -ETIMEDOUT; +} diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c new file mode 100644 index 000000000..bb027d2bd --- /dev/null +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -0,0 +1,1555 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2025 NXP + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ci_hdrc_imx.h" + +#define MX25_USB_PHY_CTRL_OFFSET 0x08 +#define MX25_BM_EXTERNAL_VBUS_DIVIDER BIT(23) + +#define MX25_EHCI_INTERFACE_SINGLE_UNI (2 << 0) +#define MX25_EHCI_INTERFACE_DIFF_UNI (0 << 0) +#define MX25_EHCI_INTERFACE_MASK (0xf) + +#define MX25_OTG_SIC_SHIFT 29 +#define MX25_OTG_SIC_MASK (0x3 << MX25_OTG_SIC_SHIFT) +#define MX25_OTG_PM_BIT BIT(24) +#define MX25_OTG_PP_BIT BIT(11) +#define MX25_OTG_OCPOL_BIT BIT(3) + +#define MX25_H1_SIC_SHIFT 21 +#define MX25_H1_SIC_MASK (0x3 << MX25_H1_SIC_SHIFT) +#define MX25_H1_PP_BIT BIT(18) +#define MX25_H1_PM_BIT BIT(16) +#define MX25_H1_IPPUE_UP_BIT BIT(7) +#define MX25_H1_IPPUE_DOWN_BIT BIT(6) +#define MX25_H1_TLL_BIT BIT(5) +#define MX25_H1_USBTE_BIT BIT(4) +#define MX25_H1_OCPOL_BIT BIT(2) + +#define MX27_H1_PM_BIT BIT(8) +#define MX27_H2_PM_BIT BIT(16) +#define MX27_OTG_PM_BIT BIT(24) + +#define MX53_USB_OTG_PHY_CTRL_0_OFFSET 0x08 +#define MX53_USB_OTG_PHY_CTRL_1_OFFSET 0x0c +#define MX53_USB_CTRL_1_OFFSET 0x10 +#define MX53_USB_CTRL_1_H2_XCVR_CLK_SEL_MASK (0x11 << 2) +#define MX53_USB_CTRL_1_H2_XCVR_CLK_SEL_ULPI BIT(2) +#define MX53_USB_CTRL_1_H3_XCVR_CLK_SEL_MASK (0x11 << 6) +#define MX53_USB_CTRL_1_H3_XCVR_CLK_SEL_ULPI BIT(6) +#define MX53_USB_UH2_CTRL_OFFSET 0x14 +#define MX53_USB_UH3_CTRL_OFFSET 0x18 +#define MX53_USB_CLKONOFF_CTRL_OFFSET 0x24 +#define MX53_USB_CLKONOFF_CTRL_H2_INT60CKOFF BIT(21) +#define MX53_USB_CLKONOFF_CTRL_H3_INT60CKOFF BIT(22) +#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 MX53_USB_CTRL_1_UH2_ULPI_EN BIT(26) +#define MX53_USB_CTRL_1_UH3_ULPI_EN BIT(27) +#define MX53_USB_UHx_CTRL_WAKE_UP_EN BIT(7) +#define MX53_USB_UHx_CTRL_ULPI_INT_EN BIT(8) +#define MX53_USB_PHYCTRL1_PLLDIV_MASK 0x3 +#define MX53_USB_PLL_DIV_24_MHZ 0x01 + +#define MX6_BM_NON_BURST_SETTING BIT(1) +#define MX6_BM_OVER_CUR_DIS BIT(7) +#define MX6_BM_OVER_CUR_POLARITY BIT(8) +#define MX6_BM_PWR_POLARITY BIT(9) +#define MX6_BM_WAKEUP_ENABLE BIT(10) +#define MX6_BM_UTMI_ON_CLOCK BIT(13) +#define MX6_BM_ID_WAKEUP BIT(16) +#define MX6_BM_VBUS_WAKEUP BIT(17) +#define MX6SX_BM_DPDM_WAKEUP_EN BIT(29) +#define MX6_BM_WAKEUP_INTR BIT(31) + +#define MX6_USB_HSIC_CTRL_OFFSET 0x10 +/* Send resume signal without 480Mhz PHY clock */ +#define MX6SX_BM_HSIC_AUTO_RESUME BIT(23) +/* set before portsc.suspendM = 1 */ +#define MX6_BM_HSIC_DEV_CONN BIT(21) +/* HSIC enable */ +#define MX6_BM_HSIC_EN BIT(12) +/* Force HSIC module 480M clock on, even when in Host is in suspend mode */ +#define MX6_BM_HSIC_CLK_ON BIT(11) + +#define MX6_USB_OTG1_PHY_CTRL 0x18 +/* For imx6dql, it is host-only controller, for later imx6, it is otg's */ +#define MX6_USB_OTG2_PHY_CTRL 0x1c +#define MX6SX_USB_VBUS_WAKEUP_SOURCE(v) (v << 8) +#define MX6SX_USB_VBUS_WAKEUP_SOURCE_VBUS MX6SX_USB_VBUS_WAKEUP_SOURCE(0) +#define MX6SX_USB_VBUS_WAKEUP_SOURCE_AVALID MX6SX_USB_VBUS_WAKEUP_SOURCE(1) +#define MX6SX_USB_VBUS_WAKEUP_SOURCE_BVALID MX6SX_USB_VBUS_WAKEUP_SOURCE(2) +#define MX6SX_USB_VBUS_WAKEUP_SOURCE_SESS_END MX6SX_USB_VBUS_WAKEUP_SOURCE(3) + +#define VF610_OVER_CUR_DIS BIT(7) + +#define MX7D_USBNC_USB_CTRL2 0x4 +#define MX7D_USB_VBUS_WAKEUP_SOURCE_MASK 0x3 +#define MX7D_USB_VBUS_WAKEUP_SOURCE(v) (v << 0) +#define MX7D_USB_VBUS_WAKEUP_SOURCE_VBUS MX7D_USB_VBUS_WAKEUP_SOURCE(0) +#define MX7D_USB_VBUS_WAKEUP_SOURCE_AVALID MX7D_USB_VBUS_WAKEUP_SOURCE(1) +#define MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID MX7D_USB_VBUS_WAKEUP_SOURCE(2) +#define MX7D_USB_VBUS_WAKEUP_SOURCE_SESS_END MX7D_USB_VBUS_WAKEUP_SOURCE(3) +#define MX7D_USBNC_AUTO_RESUME BIT(2) +/* The default DM/DP value is pull-down */ +#define MX7D_USBNC_USB_CTRL2_OPMODE(v) (v << 6) +#define MX7D_USBNC_USB_CTRL2_OPMODE_NON_DRIVING MX7D_USBNC_USB_CTRL2_OPMODE(1) +#define MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_MASK (BIT(7) | BIT(6)) +#define MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_EN BIT(8) +#define MX7D_USBNC_USB_CTRL2_DP_OVERRIDE_VAL BIT(12) +#define MX7D_USBNC_USB_CTRL2_DP_OVERRIDE_EN BIT(13) +#define MX7D_USBNC_USB_CTRL2_DM_OVERRIDE_VAL BIT(14) +#define MX7D_USBNC_USB_CTRL2_DM_OVERRIDE_EN BIT(15) +#define MX7D_USBNC_USB_CTRL2_DP_DM_MASK (BIT(12) | BIT(13) | \ + BIT(14) | BIT(15)) + +#define MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL BIT(0) +#define MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0 BIT(1) +#define MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 BIT(2) +#define MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB BIT(3) +#define MX7D_USB_OTG_PHY_CFG2_DRVVBUS0 BIT(16) + +#define MX7D_USB_OTG_PHY_CFG2 0x34 + +#define MX7D_USB_OTG_PHY_STATUS 0x3c +#define MX7D_USB_OTG_PHY_STATUS_LINE_STATE0 BIT(0) +#define MX7D_USB_OTG_PHY_STATUS_LINE_STATE1 BIT(1) +#define MX7D_USB_OTG_PHY_STATUS_VBUS_VLD BIT(3) +#define MX7D_USB_OTG_PHY_STATUS_CHRGDET BIT(29) + +#define MX7D_USB_OTG_PHY_CFG1 0x30 +#define TXPREEMPAMPTUNE0_BIT 28 +#define TXPREEMPAMPTUNE0_MASK (3 << 28) +#define TXRISETUNE0_BIT 24 +#define TXRISETUNE0_MASK (3 << 24) +#define TXVREFTUNE0_BIT 20 +#define TXVREFTUNE0_MASK (0xf << 20) + +#define MX6_USB_OTG_WAKEUP_BITS (MX6_BM_WAKEUP_ENABLE | MX6_BM_VBUS_WAKEUP | \ + MX6_BM_ID_WAKEUP | MX6SX_BM_DPDM_WAKEUP_EN) + +/* + * HSIO Block Control Register + */ + +#define BLKCTL_USB_WAKEUP_CTRL 0x0 +#define BLKCTL_OTG_WAKE_ENABLE BIT(31) +#define BLKCTL_OTG_VBUS_SESSVALID BIT(4) +#define BLKCTL_OTG_ID_WAKEUP_EN BIT(2) +#define BLKCTL_OTG_VBUS_WAKEUP_EN BIT(1) +#define BLKCTL_OTG_DPDM_WAKEUP_EN BIT(0) + +#define BLKCTL_WAKEUP_SOURCE (BLKCTL_OTG_WAKE_ENABLE | \ + BLKCTL_OTG_ID_WAKEUP_EN | \ + BLKCTL_OTG_VBUS_WAKEUP_EN | \ + BLKCTL_OTG_DPDM_WAKEUP_EN) + +#define S32G_WAKEUP_IE BIT(0) +#define S32G_CORE_IE BIT(1) +#define S32G_PWRFLTEN BIT(7) +#define S32G_WAKEUPCTRL BIT(10) +#define S32G_WAKEUPEN BIT(11) + +/* Workaround errata ERR050474 (handle packages that aren't 4 byte aligned) */ +#define S32G_UCMALLBE BIT(15) + +#define S32G_WAKEUP_BITS (S32G_WAKEUP_IE | S32G_CORE_IE | S32G_WAKEUPEN | \ + S32G_WAKEUPCTRL) + +struct usbmisc_ops { + /* It's called once when probe a usb device */ + int (*init)(struct imx_usbmisc_data *data); + /* It's called once after adding a usb device */ + int (*post)(struct imx_usbmisc_data *data); + /* It's called when we need to enable/disable usb wakeup */ + int (*set_wakeup)(struct imx_usbmisc_data *data, bool enabled); + /* It's called before setting portsc.suspendM */ + int (*hsic_set_connect)(struct imx_usbmisc_data *data); + /* It's called during suspend/resume */ + int (*hsic_set_clk)(struct imx_usbmisc_data *data, bool enabled); + /* usb charger detection */ + int (*charger_detection)(struct imx_usbmisc_data *data); + /* It's called when system resume from usb power lost */ + int (*power_lost_check)(struct imx_usbmisc_data *data); + /* It's called when device controller changed pullup status */ + void (*pullup)(struct imx_usbmisc_data *data, bool on); + /* It's called during suspend/resume to save power */ + void (*vbus_comparator_on)(struct imx_usbmisc_data *data, bool on); +}; + +struct imx_usbmisc { + void __iomem *base; + void __iomem *blkctl; + spinlock_t lock; + const struct usbmisc_ops *ops; +}; + +static inline bool is_imx53_usbmisc(struct imx_usbmisc_data *data); + +static int usbmisc_imx25_init(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val = 0; + + if (data->index > 1) + return -EINVAL; + + spin_lock_irqsave(&usbmisc->lock, flags); + switch (data->index) { + case 0: + val = readl(usbmisc->base); + val &= ~(MX25_OTG_SIC_MASK | MX25_OTG_PP_BIT); + val |= (MX25_EHCI_INTERFACE_DIFF_UNI & MX25_EHCI_INTERFACE_MASK) << MX25_OTG_SIC_SHIFT; + val |= (MX25_OTG_PM_BIT | MX25_OTG_OCPOL_BIT); + + /* + * If the polarity is not configured assume active high for + * historical reasons. + */ + if (data->oc_pol_configured && data->oc_pol_active_low) + val &= ~MX25_OTG_OCPOL_BIT; + + writel(val, usbmisc->base); + break; + case 1: + val = readl(usbmisc->base); + val &= ~(MX25_H1_SIC_MASK | MX25_H1_PP_BIT | MX25_H1_IPPUE_UP_BIT); + val |= (MX25_EHCI_INTERFACE_SINGLE_UNI & MX25_EHCI_INTERFACE_MASK) << MX25_H1_SIC_SHIFT; + val |= (MX25_H1_PM_BIT | MX25_H1_OCPOL_BIT | MX25_H1_TLL_BIT | + MX25_H1_USBTE_BIT | MX25_H1_IPPUE_DOWN_BIT); + + /* + * If the polarity is not configured assume active high for + * historical reasons. + */ + if (data->oc_pol_configured && data->oc_pol_active_low) + val &= ~MX25_H1_OCPOL_BIT; + + writel(val, usbmisc->base); + + break; + } + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + +static int usbmisc_imx25_post(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + void __iomem *reg; + unsigned long flags; + u32 val; + + if (data->index > 2) + return -EINVAL; + + if (data->index) + return 0; + + spin_lock_irqsave(&usbmisc->lock, flags); + reg = usbmisc->base + MX25_USB_PHY_CTRL_OFFSET; + val = readl(reg); + + if (data->evdo) + val |= MX25_BM_EXTERNAL_VBUS_DIVIDER; + else + val &= ~MX25_BM_EXTERNAL_VBUS_DIVIDER; + + writel(val, reg); + spin_unlock_irqrestore(&usbmisc->lock, flags); + usleep_range(5000, 10000); /* needed to stabilize voltage */ + + return 0; +} + +static int usbmisc_imx27_init(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + + switch (data->index) { + case 0: + val = MX27_OTG_PM_BIT; + break; + case 1: + val = MX27_H1_PM_BIT; + break; + case 2: + val = MX27_H2_PM_BIT; + break; + default: + return -EINVAL; + } + + spin_lock_irqsave(&usbmisc->lock, flags); + if (data->disable_oc) + val = readl(usbmisc->base) | val; + else + val = readl(usbmisc->base) & ~val; + writel(val, usbmisc->base); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + +static int usbmisc_imx53_init(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + void __iomem *reg = NULL; + unsigned long flags; + u32 val = 0; + + if (data->index > 3) + return -EINVAL; + + /* Select a 24 MHz reference clock for the PHY */ + val = readl(usbmisc->base + MX53_USB_OTG_PHY_CTRL_1_OFFSET); + val &= ~MX53_USB_PHYCTRL1_PLLDIV_MASK; + val |= MX53_USB_PLL_DIV_24_MHZ; + writel(val, usbmisc->base + MX53_USB_OTG_PHY_CTRL_1_OFFSET); + + spin_lock_irqsave(&usbmisc->lock, flags); + + switch (data->index) { + case 0: + if (data->disable_oc) { + reg = usbmisc->base + MX53_USB_OTG_PHY_CTRL_0_OFFSET; + val = readl(reg) | MX53_BM_OVER_CUR_DIS_OTG; + writel(val, reg); + } + break; + case 1: + if (data->disable_oc) { + reg = usbmisc->base + MX53_USB_OTG_PHY_CTRL_0_OFFSET; + val = readl(reg) | MX53_BM_OVER_CUR_DIS_H1; + writel(val, reg); + } + break; + case 2: + if (data->ulpi) { + /* set USBH2 into ULPI-mode. */ + reg = usbmisc->base + MX53_USB_CTRL_1_OFFSET; + val = readl(reg) | MX53_USB_CTRL_1_UH2_ULPI_EN; + /* select ULPI clock */ + val &= ~MX53_USB_CTRL_1_H2_XCVR_CLK_SEL_MASK; + val |= MX53_USB_CTRL_1_H2_XCVR_CLK_SEL_ULPI; + writel(val, reg); + /* Set interrupt wake up enable */ + reg = usbmisc->base + MX53_USB_UH2_CTRL_OFFSET; + val = readl(reg) | MX53_USB_UHx_CTRL_WAKE_UP_EN + | MX53_USB_UHx_CTRL_ULPI_INT_EN; + writel(val, reg); + if (is_imx53_usbmisc(data)) { + /* Disable internal 60Mhz clock */ + reg = usbmisc->base + + MX53_USB_CLKONOFF_CTRL_OFFSET; + val = readl(reg) | + MX53_USB_CLKONOFF_CTRL_H2_INT60CKOFF; + writel(val, reg); + } + + } + if (data->disable_oc) { + reg = usbmisc->base + MX53_USB_UH2_CTRL_OFFSET; + val = readl(reg) | MX53_BM_OVER_CUR_DIS_UHx; + writel(val, reg); + } + break; + case 3: + if (data->ulpi) { + /* set USBH3 into ULPI-mode. */ + reg = usbmisc->base + MX53_USB_CTRL_1_OFFSET; + val = readl(reg) | MX53_USB_CTRL_1_UH3_ULPI_EN; + /* select ULPI clock */ + val &= ~MX53_USB_CTRL_1_H3_XCVR_CLK_SEL_MASK; + val |= MX53_USB_CTRL_1_H3_XCVR_CLK_SEL_ULPI; + writel(val, reg); + /* Set interrupt wake up enable */ + reg = usbmisc->base + MX53_USB_UH3_CTRL_OFFSET; + val = readl(reg) | MX53_USB_UHx_CTRL_WAKE_UP_EN + | MX53_USB_UHx_CTRL_ULPI_INT_EN; + writel(val, reg); + + if (is_imx53_usbmisc(data)) { + /* Disable internal 60Mhz clock */ + reg = usbmisc->base + + MX53_USB_CLKONOFF_CTRL_OFFSET; + val = readl(reg) | + MX53_USB_CLKONOFF_CTRL_H3_INT60CKOFF; + writel(val, reg); + } + } + if (data->disable_oc) { + reg = usbmisc->base + MX53_USB_UH3_CTRL_OFFSET; + val = readl(reg) | MX53_BM_OVER_CUR_DIS_UHx; + writel(val, reg); + } + break; + } + + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + +static u32 usbmisc_wakeup_setting(struct imx_usbmisc_data *data) +{ + u32 wakeup_setting = MX6_USB_OTG_WAKEUP_BITS; + + if (data->ext_id || data->available_role != USB_DR_MODE_OTG) + wakeup_setting &= ~MX6_BM_ID_WAKEUP; + + if (data->ext_vbus || data->available_role == USB_DR_MODE_HOST) + wakeup_setting &= ~MX6_BM_VBUS_WAKEUP; + + return wakeup_setting; +} + +static int usbmisc_imx6q_set_wakeup + (struct imx_usbmisc_data *data, bool enabled) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + int ret = 0; + + if (data->index > 3) + return -EINVAL; + + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + data->index * 4); + if (enabled) { + val &= ~MX6_USB_OTG_WAKEUP_BITS; + val |= usbmisc_wakeup_setting(data); + } else { + if (val & MX6_BM_WAKEUP_INTR) + pr_debug("wakeup int at ci_hdrc.%d\n", data->index); + val &= ~MX6_USB_OTG_WAKEUP_BITS; + } + writel(val, usbmisc->base + data->index * 4); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return ret; +} + +static int usbmisc_imx6q_init(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 reg; + + if (data->index > 3) + return -EINVAL; + + spin_lock_irqsave(&usbmisc->lock, flags); + + reg = readl(usbmisc->base + data->index * 4); + if (data->disable_oc) { + reg |= MX6_BM_OVER_CUR_DIS; + } else { + reg &= ~MX6_BM_OVER_CUR_DIS; + + /* + * If the polarity is not configured keep it as setup by the + * bootloader. + */ + if (data->oc_pol_configured && data->oc_pol_active_low) + reg |= MX6_BM_OVER_CUR_POLARITY; + else if (data->oc_pol_configured) + reg &= ~MX6_BM_OVER_CUR_POLARITY; + } + /* If the polarity is not set keep it as setup by the bootloader */ + if (data->pwr_pol == 1) + reg |= MX6_BM_PWR_POLARITY; + writel(reg, usbmisc->base + data->index * 4); + + /* SoC non-burst setting */ + reg = readl(usbmisc->base + data->index * 4); + writel(reg | MX6_BM_NON_BURST_SETTING, + usbmisc->base + data->index * 4); + + /* For HSIC controller */ + if (data->hsic) { + reg = readl(usbmisc->base + data->index * 4); + writel(reg | MX6_BM_UTMI_ON_CLOCK, + usbmisc->base + data->index * 4); + reg = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + + (data->index - 2) * 4); + reg |= MX6_BM_HSIC_EN | MX6_BM_HSIC_CLK_ON; + writel(reg, usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + + (data->index - 2) * 4); + } + + spin_unlock_irqrestore(&usbmisc->lock, flags); + + usbmisc_imx6q_set_wakeup(data, false); + + return 0; +} + +static int usbmisc_imx6_hsic_get_reg_offset(struct imx_usbmisc_data *data) +{ + int offset, ret = 0; + + if (data->index == 2 || data->index == 3) { + offset = (data->index - 2) * 4; + } else if (data->index == 0) { + /* + * For SoCs like i.MX7D and later, each USB controller has + * its own non-core register region. For SoCs before i.MX7D, + * the first two USB controllers are non-HSIC controllers. + */ + offset = 0; + } else { + dev_err(data->dev, "index is error for usbmisc\n"); + ret = -EINVAL; + } + + return ret ? ret : offset; +} + +static int usbmisc_imx6_hsic_set_connect(struct imx_usbmisc_data *data) +{ + unsigned long flags; + u32 val; + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + int offset; + + spin_lock_irqsave(&usbmisc->lock, flags); + offset = usbmisc_imx6_hsic_get_reg_offset(data); + if (offset < 0) { + spin_unlock_irqrestore(&usbmisc->lock, flags); + return offset; + } + + val = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + offset); + if (!(val & MX6_BM_HSIC_DEV_CONN)) + writel(val | MX6_BM_HSIC_DEV_CONN, + usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + offset); + + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + +static int usbmisc_imx6_hsic_set_clk(struct imx_usbmisc_data *data, bool on) +{ + unsigned long flags; + u32 val; + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + int offset; + + spin_lock_irqsave(&usbmisc->lock, flags); + offset = usbmisc_imx6_hsic_get_reg_offset(data); + if (offset < 0) { + spin_unlock_irqrestore(&usbmisc->lock, flags); + return offset; + } + + val = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + offset); + val |= MX6_BM_HSIC_EN | MX6_BM_HSIC_CLK_ON; + if (on) + val |= MX6_BM_HSIC_CLK_ON; + else + val &= ~MX6_BM_HSIC_CLK_ON; + + writel(val, usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + offset); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + + +static int usbmisc_imx6sx_init(struct imx_usbmisc_data *data) +{ + void __iomem *reg = NULL; + unsigned long flags; + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + u32 val; + + usbmisc_imx6q_init(data); + + if (data->index == 0 || data->index == 1) { + reg = usbmisc->base + MX6_USB_OTG1_PHY_CTRL + data->index * 4; + spin_lock_irqsave(&usbmisc->lock, flags); + /* Set vbus wakeup source as bvalid */ + val = readl(reg); + writel(val | MX6SX_USB_VBUS_WAKEUP_SOURCE_BVALID, reg); + /* + * Disable dp/dm wakeup in device mode when vbus is + * not there. + */ + val = readl(usbmisc->base + data->index * 4); + writel(val & ~MX6SX_BM_DPDM_WAKEUP_EN, + usbmisc->base + data->index * 4); + spin_unlock_irqrestore(&usbmisc->lock, flags); + } + + /* For HSIC controller */ + if (data->hsic) { + val = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET); + val |= MX6SX_BM_HSIC_AUTO_RESUME; + writel(val, usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET); + } + + return 0; +} + +static int usbmisc_vf610_init(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + u32 reg; + + /* + * Vybrid only has one misc register set, but in two different + * areas. These is reflected in two instances of this driver. + */ + if (data->index >= 1) + return -EINVAL; + + if (data->disable_oc) { + reg = readl(usbmisc->base); + writel(reg | VF610_OVER_CUR_DIS, usbmisc->base); + } + + return 0; +} + +static int usbmisc_s32g_set_wakeup(struct imx_usbmisc_data *data, bool enabled) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&usbmisc->lock, flags); + + reg = readl(usbmisc->base); + if (enabled) + reg |= S32G_WAKEUP_BITS; + else + reg &= ~S32G_WAKEUP_BITS; + + writel(reg, usbmisc->base); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + +static int usbmisc_s32g_init(struct imx_usbmisc_data *data, u32 extra_flags) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&usbmisc->lock, flags); + + reg = readl(usbmisc->base); + + reg |= S32G_PWRFLTEN; + reg |= extra_flags; + + writel(reg, usbmisc->base); + + spin_unlock_irqrestore(&usbmisc->lock, flags); + usbmisc_s32g_set_wakeup(data, false); + + return 0; +} + +static int usbmisc_s32g2_init(struct imx_usbmisc_data *data) +{ + return usbmisc_s32g_init(data, S32G_UCMALLBE); +} + +static int usbmisc_s32g3_init(struct imx_usbmisc_data *data) +{ + return usbmisc_s32g_init(data, 0); +} + +static int usbmisc_imx7d_set_wakeup + (struct imx_usbmisc_data *data, bool enabled) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base); + if (enabled) { + val &= ~MX6_USB_OTG_WAKEUP_BITS; + val |= usbmisc_wakeup_setting(data); + writel(val, usbmisc->base); + } else { + if (val & MX6_BM_WAKEUP_INTR) + dev_dbg(data->dev, "wakeup int\n"); + writel(val & ~MX6_USB_OTG_WAKEUP_BITS, usbmisc->base); + } + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + +static int usbmisc_imx7d_init(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 reg; + + if (data->index >= 1) + return -EINVAL; + + spin_lock_irqsave(&usbmisc->lock, flags); + reg = readl(usbmisc->base); + if (data->disable_oc) { + reg |= MX6_BM_OVER_CUR_DIS; + } else { + reg &= ~MX6_BM_OVER_CUR_DIS; + + /* + * If the polarity is not configured keep it as setup by the + * bootloader. + */ + if (data->oc_pol_configured && data->oc_pol_active_low) + reg |= MX6_BM_OVER_CUR_POLARITY; + else if (data->oc_pol_configured) + reg &= ~MX6_BM_OVER_CUR_POLARITY; + } + /* If the polarity is not set keep it as setup by the bootloader */ + if (data->pwr_pol == 1) + reg |= MX6_BM_PWR_POLARITY; + writel(reg, usbmisc->base); + + /* SoC non-burst setting */ + reg = readl(usbmisc->base); + writel(reg | MX6_BM_NON_BURST_SETTING, usbmisc->base); + + if (!data->hsic) { + reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + reg &= ~MX7D_USB_VBUS_WAKEUP_SOURCE_MASK; + writel(reg | MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID + | MX7D_USBNC_AUTO_RESUME, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + /* PHY tuning for signal quality */ + reg = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG1); + if (data->emp_curr_control >= 0 && + data->emp_curr_control <= + (TXPREEMPAMPTUNE0_MASK >> TXPREEMPAMPTUNE0_BIT)) { + reg &= ~TXPREEMPAMPTUNE0_MASK; + reg |= (data->emp_curr_control << TXPREEMPAMPTUNE0_BIT); + } + + if (data->dc_vol_level_adjust >= 0 && + data->dc_vol_level_adjust <= + (TXVREFTUNE0_MASK >> TXVREFTUNE0_BIT)) { + reg &= ~TXVREFTUNE0_MASK; + reg |= (data->dc_vol_level_adjust << TXVREFTUNE0_BIT); + } + + if (data->rise_fall_time_adjust >= 0 && + data->rise_fall_time_adjust <= + (TXRISETUNE0_MASK >> TXRISETUNE0_BIT)) { + reg &= ~TXRISETUNE0_MASK; + reg |= (data->rise_fall_time_adjust << TXRISETUNE0_BIT); + } + + writel(reg, usbmisc->base + MX7D_USB_OTG_PHY_CFG1); + } + + spin_unlock_irqrestore(&usbmisc->lock, flags); + + usbmisc_imx7d_set_wakeup(data, false); + + return 0; +} + +static int imx7d_charger_secondary_detection(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + struct usb_phy *usb_phy = data->usb_phy; + int val; + unsigned long flags; + + /* Clear VDATSRCENB0 to disable VDP_SRC and IDM_SNK required by BC 1.2 spec */ + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + val &= ~MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0; + writel(val, usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + /* TVDMSRC_DIS */ + msleep(20); + + /* VDM_SRC is connected to D- and IDP_SINK is connected to D+ */ + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + writel(val | MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 | + MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0 | + MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL, + usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + /* TVDMSRC_ON */ + msleep(40); + + /* + * Per BC 1.2, check voltage of D+: + * DCP: if greater than VDAT_REF; + * CDP: if less than VDAT_REF. + */ + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS); + if (val & MX7D_USB_OTG_PHY_STATUS_CHRGDET) { + dev_dbg(data->dev, "It is a dedicate charging port\n"); + usb_phy->chg_type = DCP_TYPE; + } else { + dev_dbg(data->dev, "It is a charging downstream port\n"); + usb_phy->chg_type = CDP_TYPE; + } + + return 0; +} + +static void imx7_disable_charger_detector(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + val &= ~(MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB | + MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 | + MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0 | + MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL); + writel(val, usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + + /* Set OPMODE to be 2'b00 and disable its override */ + val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + val &= ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_MASK; + writel(val, usbmisc->base + MX7D_USBNC_USB_CTRL2); + + val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + writel(val & ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_EN, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + spin_unlock_irqrestore(&usbmisc->lock, flags); +} + +static int imx7d_charger_data_contact_detect(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + int i, data_pin_contact_count = 0; + + /* Enable Data Contact Detect (DCD) per the USB BC 1.2 */ + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + writel(val | MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB, + usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + for (i = 0; i < 100; i = i + 1) { + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS); + if (!(val & MX7D_USB_OTG_PHY_STATUS_LINE_STATE0)) { + if (data_pin_contact_count++ > 5) + /* Data pin makes contact */ + break; + usleep_range(5000, 10000); + } else { + data_pin_contact_count = 0; + usleep_range(5000, 6000); + } + } + + /* Disable DCD after finished data contact check */ + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + writel(val & ~MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB, + usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + if (i == 100) { + dev_err(data->dev, + "VBUS is coming from a dedicated power supply.\n"); + return -ENXIO; + } + + return 0; +} + +static int imx7d_charger_primary_detection(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + struct usb_phy *usb_phy = data->usb_phy; + unsigned long flags; + u32 val; + + /* VDP_SRC is connected to D+ and IDM_SINK is connected to D- */ + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + val &= ~MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL; + writel(val | MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 | + MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0, + usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + /* TVDPSRC_ON */ + msleep(40); + + /* Check if D- is less than VDAT_REF to determine an SDP per BC 1.2 */ + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS); + if (!(val & MX7D_USB_OTG_PHY_STATUS_CHRGDET)) { + dev_dbg(data->dev, "It is a standard downstream port\n"); + usb_phy->chg_type = SDP_TYPE; + } + + return 0; +} + +/* + * Whole charger detection process: + * 1. OPMODE override to be non-driving + * 2. Data contact check + * 3. Primary detection + * 4. Secondary detection + * 5. Disable charger detection + */ +static int imx7d_charger_detection(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + struct usb_phy *usb_phy = data->usb_phy; + unsigned long flags; + u32 val; + int ret; + + /* Check if vbus is valid */ + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS); + if (!(val & MX7D_USB_OTG_PHY_STATUS_VBUS_VLD)) { + dev_err(data->dev, "vbus is error\n"); + return -EINVAL; + } + + /* + * Keep OPMODE to be non-driving mode during the whole + * charger detection process. + */ + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + val &= ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_MASK; + val |= MX7D_USBNC_USB_CTRL2_OPMODE_NON_DRIVING; + writel(val, usbmisc->base + MX7D_USBNC_USB_CTRL2); + + val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + writel(val | MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_EN, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + ret = imx7d_charger_data_contact_detect(data); + if (ret) + return ret; + + ret = imx7d_charger_primary_detection(data); + if (!ret && usb_phy->chg_type != SDP_TYPE) + ret = imx7d_charger_secondary_detection(data); + + imx7_disable_charger_detector(data); + + return ret; +} + +static void usbmisc_imx7d_vbus_comparator_on(struct imx_usbmisc_data *data, + bool on) +{ + unsigned long flags; + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + u32 val; + + if (data->hsic) + return; + + spin_lock_irqsave(&usbmisc->lock, flags); + /* + * Disable VBUS valid comparator when in suspend mode, + * when OTG is disabled and DRVVBUS0 is asserted case + * the Bandgap circuitry and VBUS Valid comparator are + * still powered, even in Suspend or Sleep mode. + */ + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + if (on) + val |= MX7D_USB_OTG_PHY_CFG2_DRVVBUS0; + else + val &= ~MX7D_USB_OTG_PHY_CFG2_DRVVBUS0; + + writel(val, usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + spin_unlock_irqrestore(&usbmisc->lock, flags); +} + +static int usbmisc_imx7ulp_init(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 reg; + + if (data->index >= 1) + return -EINVAL; + + spin_lock_irqsave(&usbmisc->lock, flags); + reg = readl(usbmisc->base); + if (data->disable_oc) { + reg |= MX6_BM_OVER_CUR_DIS; + } else { + reg &= ~MX6_BM_OVER_CUR_DIS; + + /* + * If the polarity is not configured keep it as setup by the + * bootloader. + */ + if (data->oc_pol_configured && data->oc_pol_active_low) + reg |= MX6_BM_OVER_CUR_POLARITY; + else if (data->oc_pol_configured) + reg &= ~MX6_BM_OVER_CUR_POLARITY; + } + /* If the polarity is not set keep it as setup by the bootloader */ + if (data->pwr_pol == 1) + reg |= MX6_BM_PWR_POLARITY; + + writel(reg, usbmisc->base); + + /* SoC non-burst setting */ + reg = readl(usbmisc->base); + writel(reg | MX6_BM_NON_BURST_SETTING, usbmisc->base); + + if (data->hsic) { + reg = readl(usbmisc->base); + writel(reg | MX6_BM_UTMI_ON_CLOCK, usbmisc->base); + + reg = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET); + reg |= MX6_BM_HSIC_EN | MX6_BM_HSIC_CLK_ON; + writel(reg, usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET); + + /* + * For non-HSIC controller, the autoresume is enabled + * at MXS PHY driver (usbphy_ctrl bit18). + */ + reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + writel(reg | MX7D_USBNC_AUTO_RESUME, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + } else { + reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + reg &= ~MX7D_USB_VBUS_WAKEUP_SOURCE_MASK; + writel(reg | MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + } + + spin_unlock_irqrestore(&usbmisc->lock, flags); + + usbmisc_imx7d_set_wakeup(data, false); + + return 0; +} + +static void usbmisc_imx7d_pullup(struct imx_usbmisc_data *data, bool on) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + + if (on) + return; + + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + val &= ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_MASK; + val |= MX7D_USBNC_USB_CTRL2_OPMODE(1); + val |= MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_EN; + writel(val, usbmisc->base + MX7D_USBNC_USB_CTRL2); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + /* Last for at least 1 micro-frame to let host see disconnect signal */ + usleep_range(125, 150); + + spin_lock_irqsave(&usbmisc->lock, flags); + val &= ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_MASK; + val |= MX7D_USBNC_USB_CTRL2_OPMODE(0); + val &= ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_EN; + writel(val, usbmisc->base + MX7D_USBNC_USB_CTRL2); + spin_unlock_irqrestore(&usbmisc->lock, flags); +} + +static int usbmisc_imx7d_power_lost_check(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base); + spin_unlock_irqrestore(&usbmisc->lock, flags); + /* + * Here use a power on reset value to judge + * if the controller experienced a power lost + */ + if (val == 0x30001000) + return 1; + else + return 0; +} + +static int usbmisc_imx6sx_power_lost_check(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + data->index * 4); + spin_unlock_irqrestore(&usbmisc->lock, flags); + /* + * Here use a power on reset value to judge + * if the controller experienced a power lost + */ + if (val == 0x30001000) + return 1; + else + return 0; +} + +static int usbmisc_s32g_power_lost_check(struct imx_usbmisc_data *data) +{ + return 1; +} + +static u32 usbmisc_blkctl_wakeup_setting(struct imx_usbmisc_data *data) +{ + u32 wakeup_setting = BLKCTL_WAKEUP_SOURCE; + + if (data->ext_id || data->available_role != USB_DR_MODE_OTG) + wakeup_setting &= ~BLKCTL_OTG_ID_WAKEUP_EN; + + if (data->ext_vbus || data->available_role == USB_DR_MODE_HOST) + wakeup_setting &= ~BLKCTL_OTG_VBUS_WAKEUP_EN; + + /* Select session valid as VBUS wakeup source */ + wakeup_setting |= BLKCTL_OTG_VBUS_SESSVALID; + + return wakeup_setting; +} + +static int usbmisc_imx95_set_wakeup(struct imx_usbmisc_data *data, bool enabled) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + + if (!usbmisc->blkctl) + return 0; + + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->blkctl + BLKCTL_USB_WAKEUP_CTRL); + val &= ~BLKCTL_WAKEUP_SOURCE; + + if (enabled) + val |= usbmisc_blkctl_wakeup_setting(data); + + writel(val, usbmisc->blkctl + BLKCTL_USB_WAKEUP_CTRL); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + +static const struct usbmisc_ops imx25_usbmisc_ops = { + .init = usbmisc_imx25_init, + .post = usbmisc_imx25_post, +}; + +static const struct usbmisc_ops imx27_usbmisc_ops = { + .init = usbmisc_imx27_init, +}; + +static const struct usbmisc_ops imx51_usbmisc_ops = { + .init = usbmisc_imx53_init, +}; + +static const struct usbmisc_ops imx53_usbmisc_ops = { + .init = usbmisc_imx53_init, +}; + +static const struct usbmisc_ops imx6q_usbmisc_ops = { + .set_wakeup = usbmisc_imx6q_set_wakeup, + .init = usbmisc_imx6q_init, + .hsic_set_connect = usbmisc_imx6_hsic_set_connect, + .hsic_set_clk = usbmisc_imx6_hsic_set_clk, +}; + +static const struct usbmisc_ops vf610_usbmisc_ops = { + .init = usbmisc_vf610_init, +}; + +static const struct usbmisc_ops imx6sx_usbmisc_ops = { + .set_wakeup = usbmisc_imx6q_set_wakeup, + .init = usbmisc_imx6sx_init, + .hsic_set_connect = usbmisc_imx6_hsic_set_connect, + .hsic_set_clk = usbmisc_imx6_hsic_set_clk, + .power_lost_check = usbmisc_imx6sx_power_lost_check, +}; + +static const struct usbmisc_ops imx7d_usbmisc_ops = { + .init = usbmisc_imx7d_init, + .set_wakeup = usbmisc_imx7d_set_wakeup, + .charger_detection = imx7d_charger_detection, + .power_lost_check = usbmisc_imx7d_power_lost_check, + .pullup = usbmisc_imx7d_pullup, + .vbus_comparator_on = usbmisc_imx7d_vbus_comparator_on, +}; + +static const struct usbmisc_ops imx7ulp_usbmisc_ops = { + .init = usbmisc_imx7ulp_init, + .set_wakeup = usbmisc_imx7d_set_wakeup, + .hsic_set_connect = usbmisc_imx6_hsic_set_connect, + .hsic_set_clk = usbmisc_imx6_hsic_set_clk, + .power_lost_check = usbmisc_imx7d_power_lost_check, +}; + +static const struct usbmisc_ops imx94_usbmisc_ops = { + .init = usbmisc_imx7d_init, + .set_wakeup = usbmisc_imx95_set_wakeup, + .charger_detection = imx7d_charger_detection, + .power_lost_check = usbmisc_imx7d_power_lost_check, + .vbus_comparator_on = usbmisc_imx7d_vbus_comparator_on, +}; + +static const struct usbmisc_ops imx95_usbmisc_ops = { + .init = usbmisc_imx7d_init, + .set_wakeup = usbmisc_imx95_set_wakeup, + .charger_detection = imx7d_charger_detection, + .power_lost_check = usbmisc_imx7d_power_lost_check, + .pullup = usbmisc_imx7d_pullup, + .vbus_comparator_on = usbmisc_imx7d_vbus_comparator_on, +}; + +static const struct usbmisc_ops s32g2_usbmisc_ops = { + .init = usbmisc_s32g2_init, + .set_wakeup = usbmisc_s32g_set_wakeup, + .power_lost_check = usbmisc_s32g_power_lost_check, +}; + +static const struct usbmisc_ops s32g3_usbmisc_ops = { + .init = usbmisc_s32g3_init, + .set_wakeup = usbmisc_s32g_set_wakeup, + .power_lost_check = usbmisc_s32g_power_lost_check, +}; + +static inline bool is_imx53_usbmisc(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + + return usbmisc->ops == &imx53_usbmisc_ops; +} + +int imx_usbmisc_init(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc; + + if (!data) + return 0; + + usbmisc = dev_get_drvdata(data->dev); + if (!usbmisc->ops->init) + return 0; + return usbmisc->ops->init(data); +} +EXPORT_SYMBOL_GPL(imx_usbmisc_init); + +int imx_usbmisc_init_post(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc; + int ret = 0; + + if (!data) + return 0; + + usbmisc = dev_get_drvdata(data->dev); + if (usbmisc->ops->post) + ret = usbmisc->ops->post(data); + if (ret) { + dev_err(data->dev, "post init failed, ret=%d\n", ret); + return ret; + } + + if (usbmisc->ops->set_wakeup) + ret = usbmisc->ops->set_wakeup(data, false); + if (ret) { + dev_err(data->dev, "set_wakeup failed, ret=%d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(imx_usbmisc_init_post); + +int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc; + + if (!data) + return 0; + + usbmisc = dev_get_drvdata(data->dev); + if (!usbmisc->ops->hsic_set_connect || !data->hsic) + return 0; + return usbmisc->ops->hsic_set_connect(data); +} +EXPORT_SYMBOL_GPL(imx_usbmisc_hsic_set_connect); + +int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect) +{ + struct imx_usbmisc *usbmisc; + struct usb_phy *usb_phy; + int ret = 0; + + if (!data) + return -EINVAL; + + usbmisc = dev_get_drvdata(data->dev); + usb_phy = data->usb_phy; + if (!usbmisc->ops->charger_detection) + return -ENOTSUPP; + + if (connect) { + ret = usbmisc->ops->charger_detection(data); + if (ret) { + dev_err(data->dev, + "Error occurs during detection: %d\n", + ret); + usb_phy->chg_state = USB_CHARGER_ABSENT; + } else { + usb_phy->chg_state = USB_CHARGER_PRESENT; + } + } else { + usb_phy->chg_state = USB_CHARGER_ABSENT; + usb_phy->chg_type = UNKNOWN_TYPE; + } + return ret; +} +EXPORT_SYMBOL_GPL(imx_usbmisc_charger_detection); + +int imx_usbmisc_pullup(struct imx_usbmisc_data *data, bool on) +{ + struct imx_usbmisc *usbmisc; + + if (!data) + return 0; + + usbmisc = dev_get_drvdata(data->dev); + if (usbmisc->ops->pullup) + usbmisc->ops->pullup(data, on); + + return 0; +} +EXPORT_SYMBOL_GPL(imx_usbmisc_pullup); + +int imx_usbmisc_suspend(struct imx_usbmisc_data *data, bool wakeup) +{ + struct imx_usbmisc *usbmisc; + int ret = 0; + + if (!data) + return 0; + + usbmisc = dev_get_drvdata(data->dev); + + if (usbmisc->ops->vbus_comparator_on) + usbmisc->ops->vbus_comparator_on(data, false); + + if (wakeup && usbmisc->ops->set_wakeup) + ret = usbmisc->ops->set_wakeup(data, true); + if (ret) { + dev_err(data->dev, "set_wakeup failed, ret=%d\n", ret); + return ret; + } + + if (usbmisc->ops->hsic_set_clk && data->hsic) + ret = usbmisc->ops->hsic_set_clk(data, false); + if (ret) { + dev_err(data->dev, "hsic_set_clk failed, ret=%d\n", ret); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(imx_usbmisc_suspend); + +int imx_usbmisc_resume(struct imx_usbmisc_data *data, bool wakeup) +{ + struct imx_usbmisc *usbmisc; + int ret = 0; + + if (!data) + return 0; + + usbmisc = dev_get_drvdata(data->dev); + + if (usbmisc->ops->power_lost_check) + ret = usbmisc->ops->power_lost_check(data); + if (ret > 0) { + /* re-init if resume from power lost */ + ret = imx_usbmisc_init(data); + if (ret) { + dev_err(data->dev, "re-init failed, ret=%d\n", ret); + return ret; + } + } + + if (wakeup && usbmisc->ops->set_wakeup) + ret = usbmisc->ops->set_wakeup(data, false); + if (ret) { + dev_err(data->dev, "set_wakeup failed, ret=%d\n", ret); + return ret; + } + + if (usbmisc->ops->hsic_set_clk && data->hsic) + ret = usbmisc->ops->hsic_set_clk(data, true); + if (ret) { + dev_err(data->dev, "hsic_set_clk failed, ret=%d\n", ret); + goto hsic_set_clk_fail; + } + + if (usbmisc->ops->vbus_comparator_on) + usbmisc->ops->vbus_comparator_on(data, true); + + return 0; + +hsic_set_clk_fail: + if (wakeup && usbmisc->ops->set_wakeup) + usbmisc->ops->set_wakeup(data, true); + return ret; +} +EXPORT_SYMBOL_GPL(imx_usbmisc_resume); + +static const struct of_device_id usbmisc_imx_dt_ids[] = { + { + .compatible = "fsl,imx25-usbmisc", + .data = &imx25_usbmisc_ops, + }, + { + .compatible = "fsl,imx35-usbmisc", + .data = &imx25_usbmisc_ops, + }, + { + .compatible = "fsl,imx27-usbmisc", + .data = &imx27_usbmisc_ops, + }, + { + .compatible = "fsl,imx51-usbmisc", + .data = &imx51_usbmisc_ops, + }, + { + .compatible = "fsl,imx53-usbmisc", + .data = &imx53_usbmisc_ops, + }, + { + .compatible = "fsl,imx6q-usbmisc", + .data = &imx6q_usbmisc_ops, + }, + { + .compatible = "fsl,vf610-usbmisc", + .data = &vf610_usbmisc_ops, + }, + { + .compatible = "fsl,imx6sx-usbmisc", + .data = &imx6sx_usbmisc_ops, + }, + { + .compatible = "fsl,imx6ul-usbmisc", + .data = &imx6sx_usbmisc_ops, + }, + { + .compatible = "fsl,imx7d-usbmisc", + .data = &imx7d_usbmisc_ops, + }, + { + .compatible = "fsl,imx7ulp-usbmisc", + .data = &imx7ulp_usbmisc_ops, + }, + { + .compatible = "fsl,imx8ulp-usbmisc", + .data = &imx7ulp_usbmisc_ops, + }, + { + .compatible = "fsl,imx94-usbmisc", + .data = &imx94_usbmisc_ops, + }, + { + .compatible = "fsl,imx95-usbmisc", + .data = &imx95_usbmisc_ops, + }, + { + .compatible = "nxp,s32g2-usbmisc", + .data = &s32g2_usbmisc_ops, + }, + { + .compatible = "nxp,s32g3-usbmisc", + .data = &s32g3_usbmisc_ops, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, usbmisc_imx_dt_ids); + +static int usbmisc_imx_probe(struct platform_device *pdev) +{ + struct imx_usbmisc *data; + struct resource *res; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + spin_lock_init(&data->lock); + + data->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + data->blkctl = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->blkctl)) + return PTR_ERR(data->blkctl); + } else if (device_is_compatible(&pdev->dev, "fsl,imx95-usbmisc")) { + dev_warn(&pdev->dev, "wakeup setting is missing\n"); + } + + data->ops = of_device_get_match_data(&pdev->dev); + platform_set_drvdata(pdev, data); + + return 0; +} + +static struct platform_driver usbmisc_imx_driver = { + .probe = usbmisc_imx_probe, + .driver = { + .name = "usbmisc_imx", + .of_match_table = usbmisc_imx_dt_ids, + }, +}; + +module_platform_driver(usbmisc_imx_driver); + +MODULE_ALIAS("platform:usbmisc-imx"); +MODULE_LICENSE("GPL"); +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 000000000..d3f5162bd --- /dev/null +++ b/drivers/usb/class/Kconfig @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# 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 + /sys/kernel/debug/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 000000000..5d393a28f --- /dev/null +++ b/drivers/usb/class/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# 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 000000000..7bc5329fa --- /dev/null +++ b/drivers/usb/class/cdc-acm.c @@ -0,0 +1,2189 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#undef DEBUG +#undef VERBOSE_DEBUG + +#include +#include +#include +#include +#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 DEFINE_IDR(acm_minors); +static DEFINE_MUTEX(acm_minors_lock); + +static void acm_tty_set_termios(struct tty_struct *tty, + const struct ktermios *termios_old); + +/* + * acm_minors accessors + */ + +/* + * Look up an ACM structure by minor. If found and not disconnected, increment + * its refcount and return it with its mutex held. + */ +static struct acm *acm_get_by_minor(unsigned int minor) +{ + struct acm *acm; + + mutex_lock(&acm_minors_lock); + acm = idr_find(&acm_minors, minor); + 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_minors_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_minors_lock); + minor = idr_alloc(&acm_minors, acm, 0, ACM_TTY_MINORS, GFP_KERNEL); + mutex_unlock(&acm_minors_lock); + + return minor; +} + +/* Release the minor number associated with 'acm'. */ +static void acm_release_minor(struct acm *acm) +{ + mutex_lock(&acm_minors_lock); + idr_remove(&acm_minors, acm->minor); + mutex_unlock(&acm_minors_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, USB_CTRL_SET_TIMEOUT); + + 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... + */ +static inline int acm_set_control(struct acm *acm, int control) +{ + if (acm->quirks & QUIRK_CONTROL_LINE_STATE) + return -EOPNOTSUPP; + + return 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) + +static void acm_poison_urbs(struct acm *acm) +{ + int i; + + usb_poison_urb(acm->ctrlurb); + for (i = 0; i < ACM_NW; i++) + usb_poison_urb(acm->wb[i].urb); + for (i = 0; i < acm->rx_buflimit; i++) + usb_poison_urb(acm->read_urbs[i]); +} + +static void acm_unpoison_urbs(struct acm *acm) +{ + int i; + + for (i = 0; i < acm->rx_buflimit; i++) + usb_unpoison_urb(acm->read_urbs[i]); + for (i = 0; i < ACM_NW; i++) + usb_unpoison_urb(acm->wb[i].urb); + usb_unpoison_urb(acm->ctrlurb); +} + + +/* + * 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 = true; + wb->len = 0; + 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++) + if(acm->wb[i].use) + n--; + 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 = false; + 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) { + if (rc != -EPERM) + dev_err(&acm->data->dev, + "%s - usb_submit_urb(write bulk) failed: %d\n", + __func__, rc); + acm_write_done(acm, wb); + } + return rc; +} + +/* + * attributes exported through sysfs + */ +static ssize_t bmCapabilities_show +(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_RO(bmCapabilities); + +static ssize_t wCountryCodes_show +(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_RO(wCountryCodes); + +static ssize_t iCountryCodeRelDate_show +(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_RO(iCountryCodeRelDate); +/* + * Interrupt handlers for various ACM device responses + */ + +static void acm_process_notification(struct acm *acm, unsigned char *buf) +{ + int newctrl; + int difference; + unsigned long flags; + struct usb_cdc_notification *dr = (struct usb_cdc_notification *)buf; + unsigned char *data = buf + sizeof(struct usb_cdc_notification); + + 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: + if (le16_to_cpu(dr->wLength) != 2) { + dev_dbg(&acm->control->dev, + "%s - malformed serial state\n", __func__); + break; + } + + newctrl = get_unaligned_le16(data); + dev_dbg(&acm->control->dev, + "%s - serial state: 0x%x\n", __func__, newctrl); + + if (!acm->clocal && (acm->ctrlin & ~newctrl & USB_CDC_SERIAL_STATE_DCD)) { + dev_dbg(&acm->control->dev, + "%s - calling hangup\n", __func__); + tty_port_tty_hangup(&acm->port, false); + } + + difference = acm->ctrlin ^ newctrl; + + if ((difference & USB_CDC_SERIAL_STATE_DCD) && acm->port.tty) { + struct tty_ldisc *ld = tty_ldisc_ref(acm->port.tty); + if (ld) { + if (ld->ops->dcd_change) + ld->ops->dcd_change(acm->port.tty, newctrl & USB_CDC_SERIAL_STATE_DCD); + tty_ldisc_deref(ld); + } + } + + spin_lock_irqsave(&acm->read_lock, flags); + acm->ctrlin = newctrl; + acm->oldcount = acm->iocount; + + if (difference & USB_CDC_SERIAL_STATE_DSR) + acm->iocount.dsr++; + if (difference & USB_CDC_SERIAL_STATE_DCD) + acm->iocount.dcd++; + if (newctrl & USB_CDC_SERIAL_STATE_BREAK) { + acm->iocount.brk++; + tty_insert_flip_char(&acm->port, 0, TTY_BREAK); + } + if (newctrl & USB_CDC_SERIAL_STATE_RING_SIGNAL) + acm->iocount.rng++; + if (newctrl & USB_CDC_SERIAL_STATE_FRAMING) + acm->iocount.frame++; + if (newctrl & USB_CDC_SERIAL_STATE_PARITY) + acm->iocount.parity++; + if (newctrl & USB_CDC_SERIAL_STATE_OVERRUN) + acm->iocount.overrun++; + spin_unlock_irqrestore(&acm->read_lock, flags); + + if (newctrl & USB_CDC_SERIAL_STATE_BREAK) + tty_flip_buffer_push(&acm->port); + + if (difference) + wake_up_all(&acm->wioctl); + + break; + + default: + dev_dbg(&acm->control->dev, + "%s - unknown notification %d received: index %d len %d\n", + __func__, + dr->bNotificationType, dr->wIndex, dr->wLength); + } +} + +/* 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; + unsigned int current_size = urb->actual_length; + unsigned int expected_size, copy_size, alloc_size; + 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); + + if (acm->nb_index == 0) { + /* + * The first chunk of a message must contain at least the + * notification header with the length field, otherwise we + * can't get an expected_size. + */ + if (current_size < sizeof(struct usb_cdc_notification)) { + dev_dbg(&acm->control->dev, "urb too short\n"); + goto exit; + } + dr = urb->transfer_buffer; + } else { + dr = (struct usb_cdc_notification *)acm->notification_buffer; + } + /* size = notification-header + (optional) data */ + expected_size = sizeof(struct usb_cdc_notification) + + le16_to_cpu(dr->wLength); + + if (acm->nb_index != 0 || current_size < expected_size) { + /* notification is transmitted fragmented, reassemble */ + if (acm->nb_size < expected_size) { + u8 *new_buffer; + alloc_size = roundup_pow_of_two(expected_size); + /* Final freeing is done on disconnect. */ + new_buffer = krealloc(acm->notification_buffer, + alloc_size, GFP_ATOMIC); + if (!new_buffer) { + acm->nb_index = 0; + goto exit; + } + + acm->notification_buffer = new_buffer; + acm->nb_size = alloc_size; + dr = (struct usb_cdc_notification *)acm->notification_buffer; + } + + copy_size = min(current_size, + expected_size - acm->nb_index); + + memcpy(&acm->notification_buffer[acm->nb_index], + urb->transfer_buffer, copy_size); + acm->nb_index += copy_size; + current_size = acm->nb_index; + } + + if (current_size >= expected_size) { + /* notification complete */ + acm_process_notification(acm, (unsigned char *)dr); + acm->nb_index = 0; + } + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval && retval != -EPERM && retval != -ENODEV) + dev_err(&acm->control->dev, + "%s - usb_submit_urb failed: %d\n", __func__, retval); + else + dev_vdbg(&acm->control->dev, + "control resubmission terminated %d\n", 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; + + res = usb_submit_urb(acm->read_urbs[index], mem_flags); + if (res) { + if (res != -EPERM && res != -ENODEV) { + dev_err(&acm->data->dev, + "urb %d failed submission with %d\n", + index, res); + } else { + dev_vdbg(&acm->data->dev, "intended failure %d\n", res); + } + set_bit(index, &acm->read_urbs_free); + return res; + } else { + dev_vdbg(&acm->data->dev, "submitted urb %d\n", index); + } + + 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) +{ + unsigned long flags; + + if (!urb->actual_length) + return; + + spin_lock_irqsave(&acm->read_lock, flags); + tty_insert_flip_string(&acm->port, urb->transfer_buffer, + urb->actual_length); + spin_unlock_irqrestore(&acm->read_lock, flags); + + 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; + int status = urb->status; + bool stopped = false; + bool stalled = false; + bool cooldown = false; + + dev_vdbg(&acm->data->dev, "got urb %d, len %d, status %d\n", + rb->index, urb->actual_length, status); + + switch (status) { + case 0: + usb_mark_last_busy(acm->dev); + acm_process_read_urb(acm, urb); + break; + case -EPIPE: + set_bit(EVENT_RX_STALL, &acm->flags); + stalled = true; + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + dev_dbg(&acm->data->dev, + "%s - urb shutting down with status: %d\n", + __func__, status); + stopped = true; + break; + case -EOVERFLOW: + case -EPROTO: + dev_dbg(&acm->data->dev, + "%s - cooling babbling device\n", __func__); + usb_mark_last_busy(acm->dev); + set_bit(rb->index, &acm->urbs_in_error_delay); + set_bit(ACM_ERROR_DELAY, &acm->flags); + cooldown = true; + break; + default: + dev_dbg(&acm->data->dev, + "%s - nonzero urb status received: %d\n", + __func__, status); + break; + } + + /* + * Make sure URB processing is done before marking as free to avoid + * racing with unthrottle() on another CPU. Matches the barriers + * implied by the test_and_clear_bit() in acm_submit_read_urb(). + */ + smp_mb__before_atomic(); + set_bit(rb->index, &acm->read_urbs_free); + /* + * Make sure URB is marked as free before checking the throttled flag + * to avoid racing with unthrottle() on another CPU. Matches the + * smp_mb() in unthrottle(). + */ + smp_mb__after_atomic(); + + if (stopped || stalled || cooldown) { + if (stalled) + schedule_delayed_work(&acm->dwork, 0); + else if (cooldown) + schedule_delayed_work(&acm->dwork, HZ / 2); + return; + } + + if (test_bit(ACM_THROTTLED, &acm->flags)) + return; + + acm_submit_read_urb(acm, rb->index, GFP_ATOMIC); +} + +/* 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; + int status = urb->status; + + if (status || (urb->actual_length != urb->transfer_buffer_length)) + dev_vdbg(&acm->data->dev, "wrote len %d/%d, status %d\n", + urb->actual_length, + urb->transfer_buffer_length, + status); + + spin_lock_irqsave(&acm->write_lock, flags); + acm_write_done(acm, wb); + spin_unlock_irqrestore(&acm->write_lock, flags); + set_bit(EVENT_TTY_WAKEUP, &acm->flags); + schedule_delayed_work(&acm->dwork, 0); +} + +static void acm_softint(struct work_struct *work) +{ + int i; + struct acm *acm = container_of(work, struct acm, dwork.work); + + if (test_bit(EVENT_RX_STALL, &acm->flags)) { + smp_mb(); /* against acm_suspend() */ + if (!acm->susp_count) { + for (i = 0; i < acm->rx_buflimit; i++) + usb_kill_urb(acm->read_urbs[i]); + usb_clear_halt(acm->dev, acm->in); + acm_submit_read_urbs(acm, GFP_KERNEL); + clear_bit(EVENT_RX_STALL, &acm->flags); + } + } + + if (test_and_clear_bit(ACM_ERROR_DELAY, &acm->flags)) { + for (i = 0; i < acm->rx_buflimit; i++) + if (test_and_clear_bit(i, &acm->urbs_in_error_delay)) + acm_submit_read_urb(acm, i, GFP_KERNEL); + } + + if (test_and_clear_bit(EVENT_TTY_WAKEUP, &acm->flags)) + 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; + + acm = acm_get_by_minor(tty->index); + if (!acm) + return -ENODEV; + + retval = tty_standard_install(driver, tty); + if (retval) + goto error_init_termios; + + /* + * Suppress initial echoing for some devices which might send data + * immediately after acm driver has been installed. + */ + if (acm->quirks & DISABLE_ECHO) + tty->termios.c_lflag &= ~ECHO; + + 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; + + return tty_port_open(&acm->port, tty, filp); +} + +static void acm_port_dtr_rts(struct tty_port *port, bool active) +{ + struct acm *acm = container_of(port, struct acm, port); + int val; + int res; + + if (active) + val = USB_CDC_CTRL_DTR | USB_CDC_CTRL_RTS; + else + val = 0; + + /* FIXME: add missing ctrlout locking throughout driver */ + acm->ctrlout = val; + + res = acm_set_control(acm, val); + if (res && (acm->ctrl_caps & USB_CDC_CAP_LINE)) + /* This is broken in too many devices to spam the logs */ + dev_dbg(&acm->control->dev, "failed to set dtr/rts\n"); +} + +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; + + mutex_lock(&acm->mutex); + if (acm->disconnected) + goto disconnected; + + retval = usb_autopm_get_interface(acm->control); + if (retval) + goto error_get_interface; + + set_bit(TTY_NO_WRITE_SPLIT, &tty->flags); + acm->control->needs_remote_wakeup = 1; + + if (!(acm->quirks & ALWAYS_POLL_CTRL)) { + acm->ctrlurb->dev = acm->dev; + retval = usb_submit_urb(acm->ctrlurb, GFP_KERNEL); + if (retval) { + dev_err(&acm->control->dev, + "%s - usb_submit_urb(ctrl irq) failed\n", __func__); + goto error_submit_urb; + } + } + + acm_tty_set_termios(tty, NULL); + + /* + * Unthrottle device in case the TTY was closed while throttled. + */ + clear_bit(ACM_THROTTLED, &acm->flags); + + retval = acm_submit_read_urbs(acm, GFP_KERNEL); + if (retval) + goto error_submit_read_urbs; + + usb_autopm_put_interface(acm->control); + + 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]); + usb_kill_urb(acm->ctrlurb); +error_submit_urb: + usb_autopm_put_interface(acm->control); +error_get_interface: +disconnected: + mutex_unlock(&acm->mutex); + + return usb_translate_errors(retval); +} + +static void acm_port_destruct(struct tty_port *port) +{ + struct acm *acm = container_of(port, struct acm, port); + + if (acm->minor != ACM_MINOR_INVALID) + 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; + + /* + * Need to grab write_lock to prevent race with resume, but no need to + * hold it due to the tty-port initialised flag. + */ + acm_poison_urbs(acm); + spin_lock_irq(&acm->write_lock); + spin_unlock_irq(&acm->write_lock); + + usb_autopm_get_interface_no_resume(acm->control); + acm->control->needs_remote_wakeup = 0; + usb_autopm_put_interface(acm->control); + + for (;;) { + urb = usb_get_from_anchor(&acm->delayed); + if (!urb) + break; + wb = urb->context; + wb->use = false; + usb_autopm_put_interface_async(acm->control); + } + + acm_unpoison_urbs(acm); + + if (acm->quirks & ALWAYS_POLL_CTRL) { + acm->ctrlurb->dev = acm->dev; + if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) + dev_dbg(&acm->control->dev, + "ctrl polling restart failed after port close\n"); + /* port_shutdown() cleared DTR/RTS; restore them */ + acm_set_control(acm, USB_CDC_CTRL_DTR | USB_CDC_CTRL_RTS); + if (acm_submit_read_urbs(acm, GFP_KERNEL)) + dev_dbg(&acm->control->dev, + "read urb restart failed after port close\n"); + } +} + +static void acm_tty_cleanup(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + + tty_port_put(&acm->port); +} + +static void acm_tty_hangup(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + + tty_port_hangup(&acm->port); +} + +static void acm_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct acm *acm = tty->driver_data; + + tty_port_close(&acm->port, tty, filp); +} + +static ssize_t acm_tty_write(struct tty_struct *tty, const u8 *buf, + size_t 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, "%zu bytes from tty layer\n", 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]; + + if (!acm->dev) { + wb->use = false; + spin_unlock_irqrestore(&acm->write_lock, flags); + return -ENODEV; + } + + count = (count > acm->writesize) ? acm->writesize : count; + dev_vdbg(&acm->data->dev, "writing %zu bytes\n", count); + memcpy(wb->buf, buf, count); + wb->len = count; + + stat = usb_autopm_get_interface_async(acm->control); + if (stat) { + wb->use = false; + spin_unlock_irqrestore(&acm->write_lock, flags); + return stat; + } + + if (acm->susp_count) { + usb_anchor_urb(wb->urb, &acm->delayed); + spin_unlock_irqrestore(&acm->write_lock, flags); + return count; + } + + stat = acm_start_wb(acm, wb); + spin_unlock_irqrestore(&acm->write_lock, flags); + + if (stat < 0) + return stat; + return count; +} + +static unsigned 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 void acm_tty_flush_buffer(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + unsigned long flags; + int i; + + spin_lock_irqsave(&acm->write_lock, flags); + for (i = 0; i < ACM_NW; i++) + if (acm->wb[i].use) + usb_unlink_urb(acm->wb[i].urb); + spin_unlock_irqrestore(&acm->write_lock, flags); +} + +static unsigned 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; + + set_bit(ACM_THROTTLED, &acm->flags); +} + +static void acm_tty_unthrottle(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + + clear_bit(ACM_THROTTLED, &acm->flags); + + /* Matches the smp_mb__after_atomic() in acm_read_bulk_callback(). */ + smp_mb(); + + 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; + + if (!(acm->ctrl_caps & USB_CDC_CAP_BRK)) + return -EOPNOTSUPP; + + 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 & USB_CDC_CTRL_DTR ? TIOCM_DTR : 0) | + (acm->ctrlout & USB_CDC_CTRL_RTS ? TIOCM_RTS : 0) | + (acm->ctrlin & USB_CDC_SERIAL_STATE_DSR ? TIOCM_DSR : 0) | + (acm->ctrlin & USB_CDC_SERIAL_STATE_RING_SIGNAL ? TIOCM_RI : 0) | + (acm->ctrlin & USB_CDC_SERIAL_STATE_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 ? USB_CDC_CTRL_DTR : 0) | + (set & TIOCM_RTS ? USB_CDC_CTRL_RTS : 0); + clear = (clear & TIOCM_DTR ? USB_CDC_CTRL_DTR : 0) | + (clear & TIOCM_RTS ? USB_CDC_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 tty_struct *tty, struct serial_struct *ss) +{ + struct acm *acm = tty->driver_data; + + ss->line = acm->minor; + mutex_lock(&acm->port.mutex); + ss->close_delay = jiffies_to_msecs(acm->port.close_delay) / 10; + ss->closing_wait = acm->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : + jiffies_to_msecs(acm->port.closing_wait) / 10; + mutex_unlock(&acm->port.mutex); + return 0; +} + +static int set_serial_info(struct tty_struct *tty, struct serial_struct *ss) +{ + struct acm *acm = tty->driver_data; + unsigned int closing_wait, close_delay; + int retval = 0; + + close_delay = msecs_to_jiffies(ss->close_delay * 10); + closing_wait = ss->closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : + msecs_to_jiffies(ss->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 { + acm->port.close_delay = close_delay; + acm->port.closing_wait = closing_wait; + } + + mutex_unlock(&acm->port.mutex); + return retval; +} + +static int wait_serial_change(struct acm *acm, unsigned long arg) +{ + int rv = 0; + DECLARE_WAITQUEUE(wait, current); + struct async_icount old, new; + + do { + spin_lock_irq(&acm->read_lock); + old = acm->oldcount; + new = acm->iocount; + acm->oldcount = new; + spin_unlock_irq(&acm->read_lock); + + if ((arg & TIOCM_DSR) && + old.dsr != new.dsr) + break; + if ((arg & TIOCM_CD) && + old.dcd != new.dcd) + break; + if ((arg & TIOCM_RI) && + old.rng != new.rng) + break; + + add_wait_queue(&acm->wioctl, &wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + remove_wait_queue(&acm->wioctl, &wait); + if (acm->disconnected) { + if (arg & TIOCM_CD) + break; + else + rv = -ENODEV; + } else { + if (signal_pending(current)) + rv = -ERESTARTSYS; + } + } while (!rv); + + + + return rv; +} + +static int acm_tty_get_icount(struct tty_struct *tty, + struct serial_icounter_struct *icount) +{ + struct acm *acm = tty->driver_data; + + icount->dsr = acm->iocount.dsr; + icount->rng = acm->iocount.rng; + icount->dcd = acm->iocount.dcd; + icount->frame = acm->iocount.frame; + icount->overrun = acm->iocount.overrun; + icount->parity = acm->iocount.parity; + icount->brk = acm->iocount.brk; + + return 0; +} + +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 TIOCMIWAIT: + rv = usb_autopm_get_interface(acm->control); + if (rv < 0) { + rv = -EIO; + break; + } + rv = wait_serial_change(acm, arg); + usb_autopm_put_interface(acm->control); + break; + } + + return rv; +} + +static void acm_tty_set_termios(struct tty_struct *tty, + const 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; + newline.bDataBits = tty_get_char_size(termios->c_cflag); + + /* 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 &= ~USB_CDC_CTRL_DTR; + } else if (termios_old && (termios_old->c_cflag & CBAUD) == B0) { + newctrl |= USB_CDC_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 = { + .dtr_rts = acm_port_dtr_rts, + .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; + + for (wb = &acm->wb[0], i = 0; i < ACM_NW; i++, wb++) + usb_free_coherent(acm->dev, acm->writesize, wb->buf, wb->dmah); +} + +static void acm_read_buffers_free(struct acm *acm) +{ + int i; + + for (i = 0; i < acm->rx_buflimit; i++) + usb_free_coherent(acm->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_call_mgmt_descriptor *cmgmd = 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 usb_cdc_parsed_header h; + struct acm *acm; + int minor; + int ctrlsize, readsize; + u8 *buf; + int call_intf_num = -1; + int data_intf_num = -1; + unsigned long quirks; + int num_rx_buf; + int i; + int combined_interfaces = 0; + struct device *tty_dev; + int rv = -ENOMEM; + int res; + + /* normal quirks */ + quirks = (unsigned long)id->driver_info; + + if (quirks == IGNORE_DEVICE) + return -ENODEV; + + memset(&h, 0x00, sizeof(struct usb_cdc_parsed_header)); + + 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); + /* we would crash */ + if (!data_interface || !control_interface) + return -ENODEV; + goto skip_normal_probe; + } else if (quirks == NO_UNION_12) { + data_interface = usb_ifnum_to_if(usb_dev, 2); + control_interface = usb_ifnum_to_if(usb_dev, 1); + if (!data_interface || !control_interface) + return -ENODEV; + 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; + } + } + + cdc_parse_cdc_header(&h, intf, buffer, buflen); + union_header = h.usb_cdc_union_desc; + cmgmd = h.usb_cdc_call_mgmt_descriptor; + if (cmgmd) + call_intf_num = cmgmd->bDataInterface; + + if (!union_header) { + if (intf->cur_altsetting->desc.bNumEndpoints == 3) { + dev_dbg(&intf->dev, "No union descriptor, assuming single interface\n"); + combined_interfaces = 1; + control_interface = data_interface = intf; + goto look_for_collapsed_interface; + } else if (call_intf_num > 0) { + dev_dbg(&intf->dev, "No union descriptor, using call management descriptor\n"); + data_intf_num = call_intf_num; + data_interface = usb_ifnum_to_if(usb_dev, data_intf_num); + control_interface = intf; + } else { + dev_dbg(&intf->dev, "No union descriptor, giving up\n"); + return -ENODEV; + } + } else { + int class = -1; + + data_intf_num = union_header->bSlaveInterface0; + control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0); + data_interface = usb_ifnum_to_if(usb_dev, data_intf_num); + + if (control_interface) + class = control_interface->cur_altsetting->desc.bInterfaceClass; + + if (class != USB_CLASS_COMM && class != USB_CLASS_CDC_DATA) { + dev_dbg(&intf->dev, "Broken union descriptor, assuming single interface\n"); + combined_interfaces = 1; + control_interface = data_interface = intf; + goto look_for_collapsed_interface; + } + } + + if (!control_interface || !data_interface) { + dev_dbg(&intf->dev, "no interfaces\n"); + return -ENODEV; + } + + if (data_intf_num != call_intf_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: + res = usb_find_common_endpoints(data_interface->cur_altsetting, + &epread, &epwrite, &epctrl, NULL); + if (res) + return res; + + goto made_compressed_probe; + } + +skip_normal_probe: + + /*workaround for switched interfaces */ + if (data_interface->cur_altsetting->desc.bInterfaceClass != USB_CLASS_CDC_DATA) { + if (control_interface->cur_altsetting->desc.bInterfaceClass == USB_CLASS_CDC_DATA) { + dev_dbg(&intf->dev, + "Your device has switched interfaces.\n"); + swap(control_interface, data_interface); + } else if (quirks & VENDOR_CLASS_DATA_IFACE) { + dev_dbg(&intf->dev, + "Vendor-specific data interface class, continuing.\n"); + } else { + return -EINVAL; + } + } + + /* Accept probe requests only for the control interface */ + if (!combined_interfaces && intf != control_interface) + return -ENODEV; + + 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 */ + dev_dbg(&intf->dev, + "The data interface has switched endpoints\n"); + swap(epread, epwrite); + } +made_compressed_probe: + dev_dbg(&intf->dev, "interfaces are valid\n"); + + acm = kzalloc_obj(struct acm); + if (!acm) + return -ENOMEM; + + tty_port_init(&acm->port); + acm->port.ops = &acm_port_ops; + + 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; + + usb_get_intf(acm->control); /* undone in destruct() */ + + minor = acm_alloc_minor(acm); + if (minor < 0) { + acm->minor = ACM_MINOR_INVALID; + goto err_put_port; + } + + acm->minor = minor; + acm->dev = usb_dev; + if (h.usb_cdc_acm_descriptor) + acm->ctrl_caps = h.usb_cdc_acm_descriptor->bmCapabilities; + if (quirks & NO_CAP_LINE) + acm->ctrl_caps &= ~USB_CDC_CAP_LINE; + if (quirks & MISSING_CAP_BRK) + acm->ctrl_caps |= USB_CDC_CAP_BRK; + acm->ctrlsize = ctrlsize; + acm->readsize = readsize; + acm->rx_buflimit = num_rx_buf; + INIT_DELAYED_WORK(&acm->dwork, acm_softint); + init_waitqueue_head(&acm->wioctl); + spin_lock_init(&acm->write_lock); + spin_lock_init(&acm->read_lock); + mutex_init(&acm->mutex); + if (usb_endpoint_xfer_int(epread)) { + acm->bInterval = epread->bInterval; + acm->in = usb_rcvintpipe(usb_dev, epread->bEndpointAddress); + } else { + acm->in = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress); + } + if (usb_endpoint_xfer_int(epwrite)) + acm->out = usb_sndintpipe(usb_dev, epwrite->bEndpointAddress); + else + acm->out = usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress); + init_usb_anchor(&acm->delayed); + acm->quirks = quirks; + + buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma); + if (!buf) + goto err_put_port; + acm->ctrl_buffer = buf; + + if (acm_write_buffers_alloc(acm) < 0) + goto err_free_ctrl_buffer; + + acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL); + if (!acm->ctrlurb) + goto err_free_write_buffers; + + 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) + goto err_free_read_urbs; + rb->index = i; + rb->instance = acm; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + goto err_free_read_urbs; + + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->transfer_dma = rb->dma; + if (usb_endpoint_xfer_int(epread)) + usb_fill_int_urb(urb, acm->dev, acm->in, rb->base, + acm->readsize, + acm_read_bulk_callback, rb, + acm->bInterval); + else + usb_fill_bulk_urb(urb, acm->dev, acm->in, 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) + goto err_free_write_urbs; + + if (usb_endpoint_xfer_int(epwrite)) + usb_fill_int_urb(snd->urb, usb_dev, acm->out, + NULL, acm->writesize, acm_write_bulk, snd, epwrite->bInterval); + else + usb_fill_bulk_urb(snd->urb, usb_dev, acm->out, + NULL, acm->writesize, acm_write_bulk, snd); + snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + if (quirks & SEND_ZERO_PACKET) + snd->urb->transfer_flags |= URB_ZERO_PACKET; + snd->instance = acm; + } + + usb_set_intfdata(intf, acm); + + i = device_create_file(&intf->dev, &dev_attr_bmCapabilities); + if (i < 0) + goto err_free_write_urbs; + + if (h.usb_cdc_country_functional_desc) { /* export the country data */ + struct usb_cdc_country_functional_desc * cfd = + h.usb_cdc_country_functional_desc; + + 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, cfd->wCountryCodes, + 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 : 16); + acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + acm->ctrlurb->transfer_dma = acm->ctrl_dma; + acm->notification_buffer = NULL; + acm->nb_index = 0; + acm->nb_size = 0; + + acm->line.dwDTERate = cpu_to_le32(9600); + acm->line.bDataBits = 8; + acm_set_line(acm, &acm->line); + + if (quirks & ALWAYS_POLL_CTRL) + acm_set_control(acm, USB_CDC_CTRL_DTR | USB_CDC_CTRL_RTS); + + if (!acm->combined_interfaces) { + rv = usb_driver_claim_interface(&acm_driver, data_interface, acm); + if (rv) + goto err_remove_files; + } + + if (quirks & CLEAR_HALT_CONDITIONS) { + /* errors intentionally ignored */ + usb_clear_halt(usb_dev, acm->in); + usb_clear_halt(usb_dev, acm->out); + } + + 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 err_release_data_interface; + } + + dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor); + + if (acm->quirks & ALWAYS_POLL_CTRL) { + acm->ctrlurb->dev = acm->dev; + if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) + dev_warn(&intf->dev, + "failed to start persistent ctrl polling\n"); + if (acm_submit_read_urbs(acm, GFP_KERNEL)) + dev_warn(&intf->dev, + "failed to start persistent bulk read polling\n"); + } + + return 0; + +err_release_data_interface: + if (!acm->combined_interfaces) { + /* Clear driver data so that disconnect() returns early. */ + usb_set_intfdata(data_interface, NULL); + usb_driver_release_interface(&acm_driver, data_interface); + } +err_remove_files: + 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); +err_free_write_urbs: + for (i = 0; i < ACM_NW; i++) + usb_free_urb(acm->wb[i].urb); +err_free_read_urbs: + 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); +err_free_write_buffers: + acm_write_buffers_free(acm); +err_free_ctrl_buffer: + usb_free_coherent(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); +err_put_port: + tty_port_put(&acm->port); + + return rv; +} + +static void acm_disconnect(struct usb_interface *intf) +{ + struct acm *acm = usb_get_intfdata(intf); + int i; + + /* sibling interface is already cleaning up */ + if (!acm) + return; + + acm->disconnected = true; + /* + * there is a circular dependency. acm_softint() can resubmit + * the URBs in error handling so we need to block any + * submission right away + */ + acm_poison_urbs(acm); + mutex_lock(&acm->mutex); + if (acm->country_codes) { + device_remove_file(&acm->control->dev, + &dev_attr_wCountryCodes); + device_remove_file(&acm->control->dev, + &dev_attr_iCountryCodeRelDate); + } + wake_up_all(&acm->wioctl); + 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_port_tty_vhangup(&acm->port); + + cancel_delayed_work_sync(&acm->dwork); + + 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(acm->dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); + acm_read_buffers_free(acm); + + kfree(acm->notification_buffer); + + 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->write_lock); + if (PMSG_IS_AUTO(message)) { + if (acm->transmitting) { + spin_unlock_irq(&acm->write_lock); + return -EBUSY; + } + } + cnt = acm->susp_count++; + spin_unlock_irq(&acm->write_lock); + + if (cnt) + return 0; + + acm_poison_urbs(acm); + cancel_delayed_work_sync(&acm->dwork); + acm->urbs_in_error_delay = 0; + + 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->write_lock); + + if (--acm->susp_count) + goto out; + + acm_unpoison_urbs(acm); + + if (tty_port_initialized(&acm->port) || (acm->quirks & ALWAYS_POLL_CTRL)) { + 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_irq(&acm->write_lock); + + return rv; +} + +static int acm_reset_resume(struct usb_interface *intf) +{ + struct acm *acm = usb_get_intfdata(intf); + + if (tty_port_initialized(&acm->port)) + tty_port_tty_hangup(&acm->port, false); + + return acm_resume(intf); +} + +#endif /* CONFIG_PM */ + +static int acm_pre_reset(struct usb_interface *intf) +{ + struct acm *acm = usb_get_intfdata(intf); + + clear_bit(EVENT_RX_STALL, &acm->flags); + acm->nb_index = 0; /* pending control transfers are lost */ + + return 0; +} + +#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(0x0424, 0x274e), /* Microchip Technology, Inc. (formerly SMSC) */ + .driver_info = DISABLE_ECHO, }, /* DISABLE ECHO in termios flag */ + { USB_DEVICE(0x076d, 0x0006), /* Denso Cradle CU-321 */ + .driver_info = NO_UNION_NORMAL, },/* has no union descriptor */ + { 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(0x045b, 0x023c), /* Renesas R-Car H3 USB Download mode */ + .driver_info = DISABLE_ECHO, /* Don't echo banner */ + }, + { USB_DEVICE(0x045b, 0x0247), /* Renesas R-Car D3 USB Download mode */ + .driver_info = DISABLE_ECHO, /* Don't echo banner */ + }, + { USB_DEVICE(0x045b, 0x0248), /* Renesas R-Car M3-N USB Download mode */ + .driver_info = DISABLE_ECHO, /* Don't echo banner */ + }, + { USB_DEVICE(0x045b, 0x024D), /* Renesas R-Car E3 USB Download mode */ + .driver_info = DISABLE_ECHO, /* Don't echo banner */ + }, + { USB_DEVICE(0x04b8, 0x0d12), /* EPSON HMD Com&Sens */ + .driver_info = NO_UNION_12, /* union descriptor is garbage */ + }, + { USB_DEVICE(0x0e8d, 0x0003), /* FIREFLY, MediaTek Inc; andrey.arapov@gmail.com */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x0e8d, 0x2000), /* MediaTek Inc Preloader */ + .driver_info = DISABLE_ECHO, /* DISABLE ECHO in termios flag */ + }, + { 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(0x11ca, 0x0201), /* VeriFone Mx870 Gadget Serial */ + .driver_info = SINGLE_RX_URB, + }, + { USB_DEVICE(0x1901, 0x0006), /* GE Healthcare Patient Monitor UI Controller */ + .driver_info = DISABLE_ECHO, /* DISABLE ECHO in termios flag */ + }, + { USB_DEVICE(0x1965, 0x0017), /* Uniden BC125AT */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x1965, 0x0018), /* Uniden UBC125XLT */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { 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(0x0572, 0x1349), /* Hiro (Conexant) USB MODEM H50228 */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x20df, 0x0001), /* Simtec Electronics Entropy Key */ + .driver_info = QUIRK_CONTROL_LINE_STATE, }, + { USB_DEVICE(0x2184, 0x001c) }, /* GW Instek AFG-2225 */ + { USB_DEVICE(0x2184, 0x0036) }, /* GW Instek AFG-125 */ + { 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 */ + }, + { USB_DEVICE(0xfff0, 0x0100), /* DATECS FP-2000 */ + .driver_info = NO_UNION_NORMAL, /* reports zero length descriptor */ + }, + { USB_DEVICE(0x09d8, 0x0320), /* Elatec GmbH TWN3 */ + .driver_info = NO_UNION_NORMAL, /* has misplaced union descriptor */ + }, + { USB_DEVICE(0x0c26, 0x0020), /* Icom ICF3400 Serie */ + .driver_info = NO_UNION_NORMAL, /* reports zero length descriptor */ + }, + { USB_DEVICE(0x0ca6, 0xa050), /* Castles VEGA3000 */ + .driver_info = NO_UNION_NORMAL, /* reports zero length descriptor */ + }, + + { USB_DEVICE(0x2912, 0x0001), /* ATOL FPrint */ + .driver_info = CLEAR_HALT_CONDITIONS, + }, + + /* 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(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! */ + +#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 + +#if IS_ENABLED(CONFIG_IR_TOY) + { USB_DEVICE(0x04d8, 0xfd08), + .driver_info = IGNORE_DEVICE, + }, + + { USB_DEVICE(0x04d8, 0xf58b), + .driver_info = IGNORE_DEVICE, + }, +#endif + +#if IS_ENABLED(CONFIG_USB_SERIAL_XR) + { USB_DEVICE(0x04e2, 0x1400), .driver_info = IGNORE_DEVICE }, + { USB_DEVICE(0x04e2, 0x1401), .driver_info = IGNORE_DEVICE }, + { USB_DEVICE(0x04e2, 0x1402), .driver_info = IGNORE_DEVICE }, + { USB_DEVICE(0x04e2, 0x1403), .driver_info = IGNORE_DEVICE }, + { USB_DEVICE(0x04e2, 0x1410), .driver_info = IGNORE_DEVICE }, + { USB_DEVICE(0x04e2, 0x1411), .driver_info = IGNORE_DEVICE }, + { USB_DEVICE(0x04e2, 0x1412), .driver_info = IGNORE_DEVICE }, + { USB_DEVICE(0x04e2, 0x1414), .driver_info = IGNORE_DEVICE }, + { USB_DEVICE(0x04e2, 0x1420), .driver_info = IGNORE_DEVICE }, + { USB_DEVICE(0x04e2, 0x1422), .driver_info = IGNORE_DEVICE }, + { USB_DEVICE(0x04e2, 0x1424), .driver_info = IGNORE_DEVICE }, +#endif + + /*Samsung phone in firmware update mode */ + { USB_DEVICE(0x04e8, 0x685d), + .driver_info = IGNORE_DEVICE, + }, + + /* Exclude Infineon Flash Loader utility */ + { USB_DEVICE(0x058b, 0x0041), + .driver_info = IGNORE_DEVICE, + }, + + /* Exclude ETAS ES58x */ + { USB_DEVICE(0x108c, 0x0159), /* ES581.4 */ + .driver_info = IGNORE_DEVICE, + }, + { USB_DEVICE(0x108c, 0x0168), /* ES582.1 */ + .driver_info = IGNORE_DEVICE, + }, + { USB_DEVICE(0x108c, 0x0169), /* ES584.1 */ + .driver_info = IGNORE_DEVICE, + }, + + { USB_DEVICE(0x1bc7, 0x0021), /* Telit 3G ACM only composition */ + .driver_info = SEND_ZERO_PACKET, + }, + { USB_DEVICE(0x1bc7, 0x0023), /* Telit 3G ACM + ECM composition */ + .driver_info = SEND_ZERO_PACKET, + }, + + /* Exclude Goodix Fingerprint Reader */ + { USB_DEVICE(0x27c6, 0x5395), + .driver_info = IGNORE_DEVICE, + }, + + /* Exclude Heimann Sensor GmbH USB appset demo */ + { USB_DEVICE(0x32a7, 0x0000), + .driver_info = IGNORE_DEVICE, + }, + + /* CH343 supports CAP_BRK, but doesn't advertise it */ + { USB_DEVICE(0x1a86, 0x55d3), .driver_info = MISSING_CAP_BRK, }, + + /* + * Lenovo Yoga Book 9 14IAH10 (83KJ) — INGENIC 17EF:6161 touchscreen + * composite device. The CDC ACM control interface (0) uses a standard + * Union descriptor, but the data interface (1) is declared as vendor- + * specific class (0xff) with no CDC data descriptors, so cdc-acm would + * normally reject it. The firmware also requires continuous polling of + * the notification endpoint (EP 0x82) to suppress a 20-second watchdog + * reset; ALWAYS_POLL_CTRL keeps the ctrlurb active even when no TTY is + * open. Match only the control interface by class to avoid probing the + * vendor-specific data interface. + */ + { USB_DEVICE_INTERFACE_CLASS(0x17ef, 0x6161, USB_CLASS_COMM), + .driver_info = VENDOR_CLASS_DATA_IFACE | ALWAYS_POLL_CTRL }, + + /* 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) }, + + { USB_DEVICE(0x1519, 0x0452), /* Intel 7260 modem */ + .driver_info = SEND_ZERO_PACKET, + }, + + { } +}; + +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 + .pre_reset = acm_pre_reset, + .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, + .flush_buffer = acm_tty_flush_buffer, + .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, + .get_serial = get_serial_info, + .set_serial = set_serial_info, + .get_icount = acm_tty_get_icount, +}; + +/* + * Init / exit. + */ + +static int __init acm_init(void) +{ + int retval; + acm_tty_driver = tty_alloc_driver(ACM_TTY_MINORS, TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV); + if (IS_ERR(acm_tty_driver)) + return PTR_ERR(acm_tty_driver); + 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->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) { + tty_driver_kref_put(acm_tty_driver); + return retval; + } + + retval = usb_register(&acm_driver); + if (retval) { + tty_unregister_driver(acm_tty_driver); + tty_driver_kref_put(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); + tty_driver_kref_put(acm_tty_driver); + idr_destroy(&acm_minors); +} + +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 000000000..01f448a78 --- /dev/null +++ b/drivers/usb/class/cdc-acm.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * + * Includes for cdc-acm.c + * + * Mainly take from usbnet's cdc-ether part + * + */ + +/* + * Major and minor numbers. + */ + +#define ACM_TTY_MAJOR 166 +#define ACM_TTY_MINORS 256 + +#define ACM_MINOR_INVALID ACM_TTY_MINORS + +/* + * Requests. + */ + +#define USB_RT_ACM (USB_TYPE_CLASS | USB_RECIP_INTERFACE) + +/* + * 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 { + u8 *buf; + dma_addr_t dmah; + unsigned int len; + struct urb *urb; + struct acm *instance; + bool use; +}; + +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 */ + unsigned in, out; /* i/o pipes */ + 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; + spinlock_t read_lock; + u8 *notification_buffer; /* to reassemble fragmented notifications */ + unsigned int nb_index; + unsigned int nb_size; + int transmitting; + spinlock_t write_lock; + struct mutex mutex; + bool disconnected; + unsigned long flags; +# define EVENT_TTY_WAKEUP 0 +# define EVENT_RX_STALL 1 +# define ACM_THROTTLED 2 +# define ACM_ERROR_DELAY 3 + unsigned long urbs_in_error_delay; /* these need to be restarted after a delay */ + struct usb_cdc_line_coding line; /* bits, stop, parity */ + struct delayed_work dwork; /* work queue entry for various purposes */ + unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ + unsigned int ctrlout; /* output control lines (DTR, RTS) */ + struct async_icount iocount; /* counters for control line changes */ + struct async_icount oldcount; /* for comparison of counter */ + wait_queue_head_t wioctl; /* for ioctl */ + 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 */ + u8 bInterval; + struct usb_anchor delayed; /* writes queued for a device about to be woken */ + unsigned long quirks; +}; + +/* constants describing various quirks and errors */ +#define NO_UNION_NORMAL BIT(0) +#define SINGLE_RX_URB BIT(1) +#define NO_CAP_LINE BIT(2) +#define IGNORE_DEVICE BIT(3) +#define QUIRK_CONTROL_LINE_STATE BIT(4) +#define CLEAR_HALT_CONDITIONS BIT(5) +#define SEND_ZERO_PACKET BIT(6) +#define DISABLE_ECHO BIT(7) +#define MISSING_CAP_BRK BIT(8) +#define NO_UNION_12 BIT(9) +#define VENDOR_CLASS_DATA_IFACE BIT(10) /* data interface uses vendor-specific class */ +#define ALWAYS_POLL_CTRL BIT(11) /* keep ctrl URB active even without an open TTY */ diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c new file mode 100644 index 000000000..7556c0dac --- /dev/null +++ b/drivers/usb/class/cdc-wdm.c @@ -0,0 +1,1380 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 +#include +#include + +#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_WWAN_IN_USE 11 + +#define WDM_MAX 16 + +/* we cannot wait forever at flush() */ +#define WDM_FLUSH_TIMEOUT (30 * HZ) + +/* 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 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; + struct work_struct service_outs_intr; + int werr; + int rerr; + int resp_count; + + struct list_head device_list; + int (*manage_power)(struct usb_interface *, int); + + enum wwan_port_type wwanp_type; + struct wwan_port *wwanp; +}; + +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; + unsigned long flags; + + desc = urb->context; + spin_lock_irqsave(&desc->iuspin, flags); + desc->werr = urb->status; + spin_unlock_irqrestore(&desc->iuspin, flags); + kfree(desc->outbuf); + desc->outbuf = NULL; + clear_bit(WDM_IN_USE, &desc->flags); + wake_up_all(&desc->wait); +} + +static void wdm_wwan_rx(struct wdm_device *desc, int length); + +static void wdm_in_callback(struct urb *urb) +{ + unsigned long flags; + struct wdm_device *desc = urb->context; + int status = urb->status; + int length = urb->actual_length; + + spin_lock_irqsave(&desc->iuspin, flags); + clear_bit(WDM_RESPONDING, &desc->flags); + + if (status) { + switch (status) { + case -ENOENT: + dev_dbg(&desc->intf->dev, + "nonzero urb status received: -ENOENT\n"); + goto skip_error; + case -ECONNRESET: + dev_dbg(&desc->intf->dev, + "nonzero urb status received: -ECONNRESET\n"); + goto skip_error; + case -ESHUTDOWN: + dev_dbg(&desc->intf->dev, + "nonzero urb status received: -ESHUTDOWN\n"); + 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; + } + } + + if (test_bit(WDM_WWAN_IN_USE, &desc->flags)) { + wdm_wwan_rx(desc, length); + goto out; + } + + /* + * only set a new error if there is no previous error. + * Errors are only cleared during read/open + * Avoid propagating -EPIPE (stall) to userspace since it is + * better handled as an empty read + */ + if (desc->rerr == 0 && status != -EPIPE) + desc->rerr = status; + + if (length == 0) { + dev_dbg(&desc->intf->dev, "received ZLP\n"); + goto skip_zlp; + } + + 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); + smp_wmb(); /* against wdm_read() */ + WRITE_ONCE(desc->length, desc->length + length); + } + } +skip_error: + + if (desc->rerr) { + /* + * If there was a ZLP or an error, userspace may decide to not + * read any data after poll'ing. + * We should respond to further attempts from the device to send + * data, so that we can get unstuck. + */ +skip_zlp: + schedule_work(&desc->service_outs_intr); + } else { + set_bit(WDM_READ, &desc->flags); + wake_up(&desc->wait); + } +out: + spin_unlock_irqrestore(&desc->iuspin, flags); +} + +static void wdm_int_callback(struct urb *urb) +{ + unsigned long flags; + 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_ratelimited(&desc->intf->dev, + "nonzero urb status received: %d\n", status); + break; + } + } + + if (urb->actual_length < sizeof(struct usb_cdc_notification)) { + dev_err_ratelimited(&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\n", + le16_to_cpu(dr->wIndex), le16_to_cpu(dr->wLength)); + break; + + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + + dev_dbg(&desc->intf->dev, + "NOTIFY_NETWORK_CONNECTION %s network\n", + dr->wValue ? "connected to" : "disconnected from"); + goto exit; + case USB_CDC_NOTIFY_SPEED_CHANGE: + dev_dbg(&desc->intf->dev, "SPEED_CHANGE received (len %u)\n", + urb->actual_length); + 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, + le16_to_cpu(dr->wIndex), + le16_to_cpu(dr->wLength)); + goto exit; + } + + spin_lock_irqsave(&desc->iuspin, flags); + responding = test_and_set_bit(WDM_RESPONDING, &desc->flags); + if (!desc->resp_count++ && !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, "submit response URB %d\n", rv); + } + spin_unlock_irqrestore(&desc->iuspin, flags); + 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 poison_urbs(struct wdm_device *desc) +{ + /* the order here is essential */ + usb_poison_urb(desc->command); + usb_poison_urb(desc->validity); + usb_poison_urb(desc->response); +} + +static void unpoison_urbs(struct wdm_device *desc) +{ + /* + * the order here is not essential + * it is symmetrical just to be nice + */ + usb_unpoison_urb(desc->response); + usb_unpoison_urb(desc->validity); + usb_unpoison_urb(desc->command); +} + +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 usb_translate_errors(we); + + buf = memdup_user(buffer, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + /* concurrent writes and disconnect */ + r = mutex_lock_interruptible(&desc->wlock); + rv = -ERESTARTSYS; + if (r) + goto out_free_mem; + + if (test_bit(WDM_DISCONNECTING, &desc->flags)) { + rv = -ENODEV; + goto out_free_mem_lock; + } + + r = usb_autopm_get_interface(desc->intf); + if (r < 0) { + rv = usb_translate_errors(r); + goto out_free_mem_lock; + } + + 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 (test_bit(WDM_DISCONNECTING, &desc->flags)) + r = -ENODEV; + + if (r < 0) { + rv = r; + goto out_free_mem_pm; + } + + 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; /* already converted */ + 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) { + desc->outbuf = NULL; + clear_bit(WDM_IN_USE, &desc->flags); + wake_up_all(&desc->wait); /* for wdm_wait_for_response() */ + dev_err(&desc->intf->dev, "Tx URB error: %d\n", rv); + rv = usb_translate_errors(rv); + goto out_free_mem_pm; + } else { + dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d\n", + le16_to_cpu(req->wIndex)); + } + + usb_autopm_put_interface(desc->intf); + mutex_unlock(&desc->wlock); + return count; + +out_free_mem_pm: + usb_autopm_put_interface(desc->intf); +out_free_mem_lock: + mutex_unlock(&desc->wlock); +out_free_mem: + kfree(buf); + return rv; +} + +/* + * Submit the read urb if resp_count is non-zero. + * + * Called with desc->iuspin locked + */ +static int service_outstanding_interrupt(struct wdm_device *desc) +{ + int rv = 0; + + /* submit read urb only if the device is waiting for it */ + if (!desc->resp_count || !--desc->resp_count) + goto out; + + if (test_bit(WDM_DISCONNECTING, &desc->flags)) { + rv = -ENODEV; + goto out; + } + if (test_bit(WDM_RESETTING, &desc->flags)) { + rv = -EIO; + goto out; + } + + set_bit(WDM_RESPONDING, &desc->flags); + spin_unlock_irq(&desc->iuspin); + rv = usb_submit_urb(desc->response, GFP_KERNEL); + spin_lock_irq(&desc->iuspin); + if (rv) { + if (!test_bit(WDM_DISCONNECTING, &desc->flags)) + dev_err(&desc->intf->dev, + "usb_submit_urb failed with result %d\n", rv); + + /* make sure the next notification trigger a submit */ + clear_bit(WDM_RESPONDING, &desc->flags); + desc->resp_count = 0; + } +out: + return rv; +} + +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 = READ_ONCE(desc->length); + smp_rmb(); /* against wdm_in_callback() */ + 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 = -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 */ + rv = usb_translate_errors(desc->rerr); + desc->rerr = 0; + spin_unlock_irq(&desc->iuspin); + 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; + } + + 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); + service_outstanding_interrupt(desc); + } + spin_unlock_irq(&desc->iuspin); + rv = cntr; + +err: + mutex_unlock(&desc->rlock); + return rv; +} + +static int wdm_wait_for_response(struct file *file, long timeout) +{ + struct wdm_device *desc = file->private_data; + long rv; /* Use long here because (int) MAX_SCHEDULE_TIMEOUT < 0. */ + + /* + * Needs both flags. We cannot do with one because resetting it would + * cause a race with write() yet we need to signal a disconnect. + */ + rv = wait_event_interruptible_timeout(desc->wait, + !test_bit(WDM_IN_USE, &desc->flags) || + test_bit(WDM_DISCONNECTING, &desc->flags), + timeout); + + /* + * To report the correct error. This is best effort. + * We are inevitably racing with the hardware. + */ + if (test_bit(WDM_DISCONNECTING, &desc->flags)) + return -ENODEV; + if (!rv) + return -EIO; + if (rv < 0) + return -EINTR; + + spin_lock_irq(&desc->iuspin); + rv = desc->werr; + desc->werr = 0; + spin_unlock_irq(&desc->iuspin); + + return usb_translate_errors(rv); + +} + +/* + * You need to send a signal when you react to malicious or defective hardware. + * Also, don't abort when fsync() returned -EINVAL, for older kernels which do + * not implement wdm_flush() will return -EINVAL. + */ +static int wdm_fsync(struct file *file, loff_t start, loff_t end, int datasync) +{ + return wdm_wait_for_response(file, MAX_SCHEDULE_TIMEOUT); +} + +/* + * Same with wdm_fsync(), except it uses finite timeout in order to react to + * malicious or defective hardware which ceased communication after close() was + * implicitly called due to process termination. + */ +static int wdm_flush(struct file *file, fl_owner_t id) +{ + return wdm_wait_for_response(file, WDM_FLUSH_TIMEOUT); +} + +static __poll_t wdm_poll(struct file *file, struct poll_table_struct *wait) +{ + struct wdm_device *desc = file->private_data; + unsigned long flags; + __poll_t mask = 0; + + spin_lock_irqsave(&desc->iuspin, flags); + if (test_bit(WDM_DISCONNECTING, &desc->flags)) { + mask = EPOLLHUP | EPOLLERR; + spin_unlock_irqrestore(&desc->iuspin, flags); + goto desc_out; + } + if (test_bit(WDM_READ, &desc->flags)) + mask = EPOLLIN | EPOLLRDNORM; + if (desc->rerr || desc->werr) + mask |= EPOLLERR; + if (!test_bit(WDM_IN_USE, &desc->flags)) + mask |= EPOLLOUT | EPOLLWRNORM; + 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; + + if (test_bit(WDM_WWAN_IN_USE, &desc->flags)) { + rv = -EBUSY; + goto out; + } + smp_rmb(); /* ordered against wdm_wwan_port_stop() */ + 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\n"); + poison_urbs(desc); + spin_lock_irq(&desc->iuspin); + desc->resp_count = 0; + clear_bit(WDM_RESPONDING, &desc->flags); + spin_unlock_irq(&desc->iuspin); + desc->manage_power(desc->intf, 0); + unpoison_urbs(desc); + } 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, + .fsync = wdm_fsync, + .open = wdm_open, + .flush = wdm_flush, + .release = wdm_release, + .poll = wdm_poll, + .unlocked_ioctl = wdm_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .llseek = noop_llseek, +}; + +static struct usb_class_driver wdm_class = { + .name = "cdc-wdm%d", + .fops = &wdm_fops, + .minor_base = WDM_MINOR_BASE, +}; + +/* --- WWAN framework integration --- */ +#ifdef CONFIG_WWAN +static int wdm_wwan_port_start(struct wwan_port *port) +{ + struct wdm_device *desc = wwan_port_get_drvdata(port); + int rv; + + /* The interface is both exposed via the WWAN framework and as a + * legacy usbmisc chardev. If chardev is already open, just fail + * to prevent concurrent usage. Otherwise, switch to WWAN mode. + */ + mutex_lock(&wdm_mutex); + if (desc->count) { + mutex_unlock(&wdm_mutex); + return -EBUSY; + } + set_bit(WDM_WWAN_IN_USE, &desc->flags); + mutex_unlock(&wdm_mutex); + + desc->manage_power(desc->intf, 1); + + /* tx is allowed */ + wwan_port_txon(port); + + /* Start getting events */ + rv = usb_submit_urb(desc->validity, GFP_KERNEL); + if (rv < 0) { + wwan_port_txoff(port); + desc->manage_power(desc->intf, 0); + /* this must be last lest we race with chardev open */ + clear_bit(WDM_WWAN_IN_USE, &desc->flags); + } + + return rv; +} + +static void wdm_wwan_port_stop(struct wwan_port *port) +{ + struct wdm_device *desc = wwan_port_get_drvdata(port); + + /* Stop all transfers and disable WWAN mode */ + poison_urbs(desc); + desc->manage_power(desc->intf, 0); + clear_bit(WDM_READ, &desc->flags); + unpoison_urbs(desc); + smp_wmb(); /* ordered against wdm_open() */ + /* this must be last lest we open a poisoned device */ + clear_bit(WDM_WWAN_IN_USE, &desc->flags); +} + +static void wdm_wwan_port_tx_complete(struct urb *urb) +{ + struct sk_buff *skb = urb->context; + struct wdm_device *desc = skb_shinfo(skb)->destructor_arg; + + usb_autopm_put_interface_async(desc->intf); + wwan_port_txon(desc->wwanp); + kfree_skb(skb); +} + +static int wdm_wwan_port_tx(struct wwan_port *port, struct sk_buff *skb) +{ + struct wdm_device *desc = wwan_port_get_drvdata(port); + struct usb_interface *intf = desc->intf; + struct usb_ctrlrequest *req = desc->orq; + int rv; + + rv = usb_autopm_get_interface(intf); + if (rv) + return rv; + + usb_fill_control_urb( + desc->command, + interface_to_usbdev(intf), + usb_sndctrlpipe(interface_to_usbdev(intf), 0), + (unsigned char *)req, + skb->data, + skb->len, + wdm_wwan_port_tx_complete, + skb + ); + + 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; /* already converted */ + req->wLength = cpu_to_le16(skb->len); + + skb_shinfo(skb)->destructor_arg = desc; + + rv = usb_submit_urb(desc->command, GFP_KERNEL); + if (rv) + usb_autopm_put_interface(intf); + else /* One transfer at a time, stop TX until URB completion */ + wwan_port_txoff(port); + + return rv; +} + +static const struct wwan_port_ops wdm_wwan_port_ops = { + .start = wdm_wwan_port_start, + .stop = wdm_wwan_port_stop, + .tx = wdm_wwan_port_tx, +}; + +static void wdm_wwan_init(struct wdm_device *desc) +{ + struct usb_interface *intf = desc->intf; + struct wwan_port *port; + + /* Only register to WWAN core if protocol/type is known */ + if (desc->wwanp_type == WWAN_PORT_UNKNOWN) { + dev_info(&intf->dev, "Unknown control protocol\n"); + return; + } + + port = wwan_create_port(&intf->dev, desc->wwanp_type, &wdm_wwan_port_ops, + NULL, desc); + if (IS_ERR(port)) { + dev_err(&intf->dev, "%s: Unable to create WWAN port\n", + dev_name(intf->usb_dev)); + return; + } + + desc->wwanp = port; +} + +static void wdm_wwan_deinit(struct wdm_device *desc) +{ + if (!desc->wwanp) + return; + + wwan_remove_port(desc->wwanp); + desc->wwanp = NULL; +} + +static void wdm_wwan_rx(struct wdm_device *desc, int length) +{ + struct wwan_port *port = desc->wwanp; + struct sk_buff *skb; + + /* Forward data to WWAN port */ + skb = alloc_skb(length, GFP_ATOMIC); + if (!skb) + return; + + skb_put_data(skb, desc->inbuf, length); + wwan_port_rx(port, skb); + + /* inbuf has been copied, it is safe to check for outstanding data */ + schedule_work(&desc->service_outs_intr); +} +#else /* CONFIG_WWAN */ +static void wdm_wwan_init(struct wdm_device *desc) {} +static void wdm_wwan_deinit(struct wdm_device *desc) {} +static void wdm_wwan_rx(struct wdm_device *desc, int length) {} +#endif /* CONFIG_WWAN */ + +/* --- 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); + } + } +} + +static void service_interrupt_work(struct work_struct *work) +{ + struct wdm_device *desc; + + desc = container_of(work, struct wdm_device, service_outs_intr); + + spin_lock_irq(&desc->iuspin); + service_outstanding_interrupt(desc); + if (!desc->resp_count && (desc->length || desc->rerr)) { + set_bit(WDM_READ, &desc->flags); + wake_up(&desc->wait); + } + spin_unlock_irq(&desc->iuspin); +} + +/* --- hotplug --- */ + +static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep, + u16 bufsize, enum wwan_port_type type, + int (*manage_power)(struct usb_interface *, int)) +{ + int rv = -ENOMEM; + struct wdm_device *desc; + + desc = kzalloc_obj(struct wdm_device); + 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; + desc->wwanp_type = type; + INIT_WORK(&desc->rxwork, wdm_rxwork); + INIT_WORK(&desc->service_outs_intr, service_interrupt_work); + + if (!usb_endpoint_is_int_in(ep)) { + rv = -EINVAL; + goto err; + } + + desc->wMaxPacketSize = usb_endpoint_maxp(ep); + + desc->orq = kmalloc_obj(struct usb_ctrlrequest); + if (!desc->orq) + goto err; + desc->irq = kmalloc_obj(struct usb_ctrlrequest); + 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; /* already converted */ + 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)); + + wdm_wwan_init(desc); + +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_parsed_header hdr; + u8 *buffer = intf->altsetting->extra; + int buflen = intf->altsetting->extralen; + u16 maxcom = WDM_DEFAULT_BUFSIZE; + + if (!buffer) + goto err; + + cdc_parse_cdc_header(&hdr, intf, buffer, buflen); + + if (hdr.usb_cdc_dmm_desc) + maxcom = le16_to_cpu(hdr.usb_cdc_dmm_desc->wMaxCommand); + + iface = intf->cur_altsetting; + if (iface->desc.bNumEndpoints != 1) + goto err; + ep = &iface->endpoint[0].desc; + + rv = wdm_create(intf, ep, maxcom, WWAN_PORT_UNKNOWN, &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 + * @type: Type/protocol of the transported data (MBIM, QMI...) + * @manage_power: call-back invoked during open and release to + * manage the device's power + * 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, enum wwan_port_type type, + int (*manage_power)(struct usb_interface *, int)) +{ + int rv; + + rv = wdm_create(intf, ep, bufsize, type, 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); + + wdm_wwan_deinit(desc); + + /* 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); + spin_unlock_irqrestore(&desc->iuspin, flags); + wake_up_all(&desc->wait); + mutex_lock(&desc->rlock); + mutex_lock(&desc->wlock); + poison_urbs(desc); + cancel_work_sync(&desc->rxwork); + cancel_work_sync(&desc->service_outs_intr); + 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, "%d open files - postponing cleanup\n", 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 */ + poison_urbs(desc); + cancel_work_sync(&desc->rxwork); + cancel_work_sync(&desc->service_outs_intr); + unpoison_urbs(desc); + } + 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); + poison_urbs(desc); + cancel_work_sync(&desc->rxwork); + cancel_work_sync(&desc->service_outs_intr); + return 0; +} + +static int wdm_post_reset(struct usb_interface *intf) +{ + struct wdm_device *desc = wdm_find_device(intf); + int rv; + + unpoison_urbs(desc); + 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 rv; +} + +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 000000000..746414763 --- /dev/null +++ b/drivers/usb/class/usblp.c @@ -0,0 +1,1488 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#undef DEBUG +#include +#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=USB_CLASS_PRINTER/1/1, 2=USB_CLASS_PRINTER/1/2, + * 3=USB_CLASS_PRINTER/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 + * (USB_CLASS_PRINTER/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) + +static int usblp_hp_channel_change_request(struct usblp *usblp, int channel, u8 *new_channel) +{ + u8 *buf; + int ret; + + buf = kzalloc(1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = usblp_ctrl_msg(usblp, USBLP_REQ_HP_CHANNEL_CHANGE_REQUEST, + USB_TYPE_VENDOR, USB_DIR_IN, USB_RECIP_INTERFACE, + channel, buf, 1); + if (ret == 0) + *new_channel = buf[0]; + + kfree(buf); + + return ret; +} + +/* + * See the description for usblp_select_alts() below for the usage + * explanation. Look into your /sys/kernel/debug/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; + unsigned long flags; + + if (usblp->present && usblp->used) { + if (status) + printk(KERN_WARNING "usblp%d: " + "nonzero read bulk status received: %d\n", + usblp->minor, status); + } + spin_lock_irqsave(&usblp->lock, flags); + if (status < 0) + usblp->rstatus = status; + else + usblp->rstatus = urb->actual_length; + usblp->rcomplete = 1; + wake_up(&usblp->rwait); + spin_unlock_irqrestore(&usblp->lock, flags); + + usb_free_urb(urb); +} + +static void usblp_bulk_write(struct urb *urb) +{ + struct usblp *usblp = urb->context; + int status = urb->status; + unsigned long flags; + + if (usblp->present && usblp->used) { + if (status) + printk(KERN_WARNING "usblp%d: " + "nonzero write bulk status received: %d\n", + usblp->minor, status); + } + spin_lock_irqsave(&usblp->lock, flags); + 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_irqrestore(&usblp->lock, flags); + + 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); + error = usblp_read_status(usblp, usblp->statusbuf); + if (error < 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); + usb_put_intf(usblp->intf); + 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); + + if (!usblp->present) /* finish cleanup from disconnect */ + usblp_cleanup(usblp); /* any URBs must be dead */ + + mutex_unlock(&usblp_mutex); + return 0; +} + +/* No kernel lock - fine */ +static __poll_t usblp_poll(struct file *file, struct poll_table_struct *wait) +{ + struct usblp *usblp = file->private_data; + __poll_t ret = 0; + unsigned long flags; + + /* Should we check file->f_mode & FMODE_WRITE before poll_wait()? */ + poll_wait(file, &usblp->rwait, wait); + poll_wait(file, &usblp->wwait, wait); + + mutex_lock(&usblp->mut); + if (!usblp->present) + ret |= EPOLLHUP; + mutex_unlock(&usblp->mut); + + spin_lock_irqsave(&usblp->lock, flags); + if (usblp->bidir && usblp->rcomplete) + ret |= EPOLLIN | EPOLLRDNORM; + if (usblp->no_paper || usblp->wcomplete) + ret |= EPOLLOUT | EPOLLWRNORM; + 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: + retval = usblp_read_status(usblp, usblp->statusbuf); + if (retval) { + 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; + + writebuf = kmalloc(transfer_length, GFP_KERNEL); + if (writebuf == NULL) + return NULL; + urb = usb_alloc_urb(0, GFP_KERNEL); + if (urb == 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; + } + rv = usblp_wwait(usblp, !!(file->f_flags & O_NONBLOCK)); + if (rv < 0) + goto raise_wait; + + while (writecount < count) { + /* + * Step 1: Submit next block. + */ + transfer_length = count - writecount; + if (transfer_length > USBLP_BUF_SIZE) + transfer_length = USBLP_BUF_SIZE; + + rv = -ENOMEM; + writeurb = usblp_new_writeurb(usblp, transfer_length); + if (writeurb == 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); + + rv = usb_submit_urb(writeurb, GFP_KERNEL); + if (rv < 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 (!usblp->present) { + count = -ENODEV; + goto done; + } + + avail = usblp->rstatus; + if (avail < 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 = min_t(ssize_t, len, avail - usblp->readcount); + if (count != 0 && + copy_to_user(buffer, usblp->readbuf + usblp->readcount, count)) { + count = -EFAULT; + goto done; + } + + usblp->readcount += count; + if (usblp->readcount == 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 (;;) { + if (mutex_lock_interruptible(&usblp->mut)) { + rc = -EINTR; + break; + } + set_current_state(TASK_INTERRUPTIBLE); + 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); + rc = usblp_rtest(usblp, nonblock); + if (rc < 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; + urb = usb_alloc_urb(0, GFP_KERNEL); + if (urb == 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); + rc = usb_submit_urb(urb, GFP_KERNEL); + if (rc < 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(const 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 ieee1284_id_show(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_RO(ieee1284_id); + +static struct attribute *usblp_attrs[] = { + &dev_attr_ieee1284_id.attr, + NULL, +}; +ATTRIBUTE_GROUPS(usblp); + +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_obj(struct usblp); + 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 = usb_get_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. */ + usblp->device_id_string = kmalloc(USBLP_DEVICE_ID_SIZE, GFP_KERNEL); + if (!usblp->device_id_string) { + 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. + */ + usblp->readbuf = kmalloc(USBLP_BUF_SIZE_IN, GFP_KERNEL); + if (!usblp->readbuf) { + retval = -ENOMEM; + goto abort; + } + + /* Allocate buffer for printer status */ + usblp->statusbuf = kzalloc(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); + +#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); +abort: + kfree(usblp->readbuf); + kfree(usblp->statusbuf); + kfree(usblp->device_id_string); + usb_put_intf(usblp->intf); + 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=USB_CLASS_PRINTER/1/1, 2=USB_CLASS_PRINTER/1/2, + * 3=USB_CLASS_PRINTER/1/3). If the device supports the preferred protocol, + * then we bind to it. + * + * The best interface for us is USB_CLASS_PRINTER/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 USB_CLASS_PRINTER/1/3 (MLC/1284.4), so we provide them ioctls + * for this purpose. + * + * Failing USB_CLASS_PRINTER/1/2, we look for USB_CLASS_PRINTER/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 USB_CLASS_PRINTER/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 *epwrite, *epread; + int p, i; + int res; + + 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 != USB_CLASS_PRINTER || + 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 the expected bulk endpoints. */ + if (ifd->desc.bInterfaceProtocol > 1) { + res = usb_find_common_endpoints(ifd, + &epread, &epwrite, NULL, NULL); + } else { + epread = NULL; + res = usb_find_bulk_out_endpoint(ifd, &epwrite); + } + + /* Ignore buggy hardware without the right endpoints. */ + if (res) + continue; + + /* Turn off reads for buggy bidirectional printers. */ + 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; + + /* Don't unnecessarily set the interface if there's a single alt. */ + if (usblp->intf->num_altsetting > 1) { + 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; + + memset(usblp->device_id_string, 0, USBLP_DEVICE_ID_SIZE); + 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(); + } + + 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); + usb_poison_anchored_urbs(&usblp->urbs); + + 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(USB_CLASS_PRINTER, 1, 1) }, + { USB_DEVICE_INFO(USB_CLASS_PRINTER, 1, 2) }, + { USB_DEVICE_INFO(USB_CLASS_PRINTER, 1, 3) }, + { USB_INTERFACE_INFO(USB_CLASS_PRINTER, 1, 1) }, + { USB_INTERFACE_INFO(USB_CLASS_PRINTER, 1, 2) }, + { USB_INTERFACE_INFO(USB_CLASS_PRINTER, 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, + .dev_groups = usblp_groups, + .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 000000000..51cd9320a --- /dev/null +++ b/drivers/usb/class/usbtmc.c @@ -0,0 +1,2623 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + * Copyright (C) 2018 IVI Foundation, Inc. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Increment API VERSION when changing tmc.h with new flags or ioctls + * or when changing a significant behavior of the driver. + */ +#define USBTMC_API_VERSION (3) + +#define USBTMC_HEADER_SIZE 12 +#define USBTMC_MINOR_BASE 176 + +/* Minimum USB timeout (in milliseconds) */ +#define USBTMC_MIN_TIMEOUT 100 +/* Default USB timeout (in milliseconds) */ +#define USBTMC_TIMEOUT 5000 + +/* Max number of urbs used in write transfers */ +#define MAX_URBS_IN_FLIGHT 16 +/* I/O buffer size used in generic read/write functions */ +#define USBTMC_BUFSIZE (4096) + +/* + * 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 { + struct usb_device *usb_dev; + struct usb_interface *intf; + struct list_head file_list; + + unsigned int bulk_in; + unsigned int bulk_out; + + u8 bTag; + u8 bTag_last_write; /* needed for abort */ + u8 bTag_last_read; /* needed for abort */ + + /* packet size of IN bulk */ + u16 wMaxPacketSize; + + /* data for interrupt in endpoint handling */ + u8 bNotify1; + u8 bNotify2; + u16 ifnum; + u8 iin_bTag; + u8 *iin_buffer; + atomic_t iin_data_valid; + unsigned int iin_ep; + int iin_ep_present; + int iin_interval; + struct urb *iin_urb; + u16 iin_wMaxPacketSize; + + /* coalesced usb488_caps from usbtmc_dev_capabilities */ + __u8 usb488_caps; + + 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 */ + wait_queue_head_t waitq; + struct fasync_struct *fasync; + spinlock_t dev_lock; /* lock for file_list */ +}; +#define to_usbtmc_data(d) container_of(d, struct usbtmc_device_data, kref) + +/* + * This structure holds private data for each USBTMC file handle. + */ +struct usbtmc_file_data { + struct usbtmc_device_data *data; + struct list_head file_elem; + + u32 timeout; + u8 srq_byte; + atomic_t srq_asserted; + atomic_t closing; + u8 bmTransferAttributes; /* member of DEV_DEP_MSG_IN */ + + u8 eom_val; + u8 term_char; + bool term_char_enabled; + bool auto_abort; + + spinlock_t err_lock; /* lock for errors */ + + struct usb_anchor submitted; + + /* data for generic_write */ + struct semaphore limit_write_sem; + u32 out_transfer_size; + int out_status; + + /* data for generic_read */ + u32 in_transfer_size; + int in_status; + int in_urbs_used; + struct usb_anchor in_anchor; + wait_queue_head_t wait_bulk_in; +}; + +/* Forward declarations */ +static struct usb_driver usbtmc_driver; +static void usbtmc_draw_down(struct usbtmc_file_data *file_data); + +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; + struct usbtmc_file_data *file_data; + + intf = usb_find_interface(&usbtmc_driver, iminor(inode)); + if (!intf) { + pr_err("can not find device for minor %d", iminor(inode)); + return -ENODEV; + } + + file_data = kzalloc_obj(*file_data); + if (!file_data) + return -ENOMEM; + + spin_lock_init(&file_data->err_lock); + sema_init(&file_data->limit_write_sem, MAX_URBS_IN_FLIGHT); + init_usb_anchor(&file_data->submitted); + init_usb_anchor(&file_data->in_anchor); + init_waitqueue_head(&file_data->wait_bulk_in); + + data = usb_get_intfdata(intf); + /* Protect reference to data from file structure until release */ + kref_get(&data->kref); + + mutex_lock(&data->io_mutex); + file_data->data = data; + + atomic_set(&file_data->closing, 0); + + file_data->timeout = USBTMC_TIMEOUT; + file_data->term_char = '\n'; + file_data->term_char_enabled = 0; + file_data->auto_abort = 0; + file_data->eom_val = 1; + + INIT_LIST_HEAD(&file_data->file_elem); + spin_lock_irq(&data->dev_lock); + list_add_tail(&file_data->file_elem, &data->file_list); + spin_unlock_irq(&data->dev_lock); + mutex_unlock(&data->io_mutex); + + /* Store pointer in file structure's private data field */ + filp->private_data = file_data; + + return 0; +} + +/* + * usbtmc_flush - called before file handle is closed + */ +static int usbtmc_flush(struct file *file, fl_owner_t id) +{ + struct usbtmc_file_data *file_data; + struct usbtmc_device_data *data; + + file_data = file->private_data; + if (file_data == NULL) + return -ENODEV; + + atomic_set(&file_data->closing, 1); + data = file_data->data; + + /* wait for io to stop */ + mutex_lock(&data->io_mutex); + + usbtmc_draw_down(file_data); + + spin_lock_irq(&file_data->err_lock); + file_data->in_status = 0; + file_data->in_transfer_size = 0; + file_data->in_urbs_used = 0; + file_data->out_status = 0; + file_data->out_transfer_size = 0; + spin_unlock_irq(&file_data->err_lock); + + wake_up_interruptible_all(&data->waitq); + mutex_unlock(&data->io_mutex); + + return 0; +} + +static int usbtmc_release(struct inode *inode, struct file *file) +{ + struct usbtmc_file_data *file_data = file->private_data; + + /* prevent IO _AND_ usbtmc_interrupt */ + mutex_lock(&file_data->data->io_mutex); + spin_lock_irq(&file_data->data->dev_lock); + + list_del(&file_data->file_elem); + + spin_unlock_irq(&file_data->data->dev_lock); + + /* flush anchored URBs */ + usbtmc_draw_down(file_data); + mutex_unlock(&file_data->data->io_mutex); + + kref_put(&file_data->data->kref, usbtmc_delete); + file_data->data = NULL; + kfree(file_data); + return 0; +} + +static int usbtmc_ioctl_abort_bulk_in_tag(struct usbtmc_device_data *data, + u8 tag) +{ + u8 *buffer; + struct device *dev; + int rv; + int n; + int actual; + + dev = &data->intf->dev; + buffer = kmalloc(USBTMC_BUFSIZE, 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, + tag, data->bulk_in, + buffer, 2, USB_CTRL_GET_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 with tag %02x\n", + buffer[0], buffer[1]); + + if (buffer[0] == USBTMC_STATUS_FAILED) { + /* No transfer in progress and the Bulk-OUT FIFO is empty. */ + rv = 0; + goto exit; + } + + if (buffer[0] == USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS) { + /* The device returns this status if either: + * - There is a transfer in progress, but the specified bTag + * does not match. + * - There is no transfer in progress, but the Bulk-OUT FIFO + * is not empty. + */ + rv = -ENOMSG; + goto exit; + } + + if (buffer[0] != USBTMC_STATUS_SUCCESS) { + dev_err(dev, "INITIATE_ABORT_BULK_IN returned %x\n", + buffer[0]); + rv = -EPERM; + goto exit; + } + + n = 0; + +usbtmc_abort_bulk_in_status: + dev_dbg(dev, "Reading from bulk in EP\n"); + + /* Data must be present. So use low timeout 300 ms */ + actual = 0; + rv = usb_bulk_msg(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, + data->bulk_in), + buffer, USBTMC_BUFSIZE, + &actual, 300); + + print_hex_dump_debug("usbtmc ", DUMP_PREFIX_NONE, 16, 1, + buffer, actual, true); + + n++; + + if (rv < 0) { + dev_err(dev, "usb_bulk_msg returned %d\n", rv); + if (rv != -ETIMEDOUT) + goto exit; + } + + if (actual == USBTMC_BUFSIZE) + goto usbtmc_abort_bulk_in_status; + + if (n >= USBTMC_MAX_READS_TO_CLEAR_BULK_IN) { + dev_err(dev, "Couldn't clear device buffer within %d cycles\n", + USBTMC_MAX_READS_TO_CLEAR_BULK_IN); + rv = -EPERM; + goto exit; + } + + 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, + USB_CTRL_GET_TIMEOUT); + + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "CHECK_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, "CHECK_ABORT_BULK_IN returned %x\n", buffer[0]); + rv = -EPERM; + goto exit; + } + + if ((buffer[1] & 1) > 0) { + /* The device has 1 or more queued packets the Host can read */ + goto usbtmc_abort_bulk_in_status; + } + + /* The Host must send CHECK_ABORT_BULK_IN_STATUS at a later time. */ + rv = -EAGAIN; +exit: + kfree(buffer); + return rv; +} + +static int usbtmc_ioctl_abort_bulk_in(struct usbtmc_device_data *data) +{ + return usbtmc_ioctl_abort_bulk_in_tag(data, data->bTag_last_read); +} + +static int usbtmc_ioctl_abort_bulk_out_tag(struct usbtmc_device_data *data, + u8 tag) +{ + 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, + tag, data->bulk_out, + buffer, 2, USB_CTRL_GET_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: + /* do not stress device with subsequent requests */ + msleep(50); + 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, + USB_CTRL_GET_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 int usbtmc_ioctl_abort_bulk_out(struct usbtmc_device_data *data) +{ + return usbtmc_ioctl_abort_bulk_out_tag(data, data->bTag_last_write); +} + +static int usbtmc_get_stb(struct usbtmc_file_data *file_data, __u8 *stb) +{ + struct usbtmc_device_data *data = file_data->data; + struct device *dev = &data->intf->dev; + u8 *buffer; + u8 tag; + int rv; + long wait_rv; + unsigned long expire; + + dev_dbg(dev, "Enter ioctl_read_stb iin_ep_present: %d\n", + data->iin_ep_present); + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + atomic_set(&data->iin_data_valid, 0); + + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC488_REQUEST_READ_STATUS_BYTE, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + data->iin_bTag, + data->ifnum, + buffer, 0x03, USB_CTRL_GET_TIMEOUT); + if (rv < 0) { + dev_err(dev, "stb usb_control_msg returned %d\n", rv); + goto exit; + } + + if (buffer[0] != USBTMC_STATUS_SUCCESS) { + dev_err(dev, "control status returned %x\n", buffer[0]); + rv = -EIO; + goto exit; + } + + if (data->iin_ep_present) { + expire = msecs_to_jiffies(file_data->timeout); + wait_rv = wait_event_interruptible_timeout( + data->waitq, + atomic_read(&data->iin_data_valid) != 0, + expire); + if (wait_rv < 0) { + dev_dbg(dev, "wait interrupted %ld\n", wait_rv); + rv = wait_rv; + goto exit; + } + + if (wait_rv == 0) { + dev_dbg(dev, "wait timed out\n"); + rv = -ETIMEDOUT; + goto exit; + } + + tag = data->bNotify1 & 0x7f; + if (tag != data->iin_bTag) { + dev_err(dev, "expected bTag %x got %x\n", + data->iin_bTag, tag); + } + + *stb = data->bNotify2; + } else { + *stb = buffer[2]; + } + + dev_dbg(dev, "stb:0x%02x received %d\n", (unsigned int)*stb, rv); + + rv = 0; + + exit: + /* bump interrupt bTag */ + data->iin_bTag += 1; + if (data->iin_bTag > 127) + /* 1 is for SRQ see USBTMC-USB488 subclass spec section 4.3.1 */ + data->iin_bTag = 2; + + kfree(buffer); + return rv; +} + +static int usbtmc488_ioctl_read_stb(struct usbtmc_file_data *file_data, + void __user *arg) +{ + int srq_asserted = 0; + __u8 stb; + int rv; + + rv = usbtmc_get_stb(file_data, &stb); + + if (rv < 0) + return rv; + + srq_asserted = atomic_xchg(&file_data->srq_asserted, srq_asserted); + if (srq_asserted) + stb |= 0x40; /* Set RQS bit */ + + rv = put_user(stb, (__u8 __user *)arg); + + return rv; + +} + +static int usbtmc_ioctl_get_srq_stb(struct usbtmc_file_data *file_data, + void __user *arg) +{ + struct usbtmc_device_data *data = file_data->data; + struct device *dev = &data->intf->dev; + int srq_asserted = 0; + __u8 stb = 0; + int rv; + + spin_lock_irq(&data->dev_lock); + srq_asserted = atomic_xchg(&file_data->srq_asserted, srq_asserted); + + if (srq_asserted) { + stb = file_data->srq_byte; + spin_unlock_irq(&data->dev_lock); + rv = put_user(stb, (__u8 __user *)arg); + } else { + spin_unlock_irq(&data->dev_lock); + rv = -ENOMSG; + } + + dev_dbg(dev, "stb:0x%02x with srq received %d\n", (unsigned int)stb, rv); + + return rv; +} + +static int usbtmc488_ioctl_wait_srq(struct usbtmc_file_data *file_data, + __u32 __user *arg) +{ + struct usbtmc_device_data *data = file_data->data; + struct device *dev = &data->intf->dev; + u32 timeout; + unsigned long expire; + long wait_rv; + + if (!data->iin_ep_present) { + dev_dbg(dev, "no interrupt endpoint present\n"); + return -EFAULT; + } + + if (get_user(timeout, arg)) + return -EFAULT; + + expire = msecs_to_jiffies(timeout); + + mutex_unlock(&data->io_mutex); + + wait_rv = wait_event_interruptible_timeout( + data->waitq, + atomic_read(&file_data->srq_asserted) != 0 || + atomic_read(&file_data->closing), + expire); + + mutex_lock(&data->io_mutex); + + /* Note! disconnect or close could be called in the meantime */ + if (atomic_read(&file_data->closing) || data->zombie) + return -ENODEV; + + if (wait_rv < 0) { + dev_dbg(dev, "%s - wait interrupted %ld\n", __func__, wait_rv); + return wait_rv; + } + + if (wait_rv == 0) { + dev_dbg(dev, "%s - wait timed out\n", __func__); + return -ETIMEDOUT; + } + + dev_dbg(dev, "%s - srq asserted\n", __func__); + return 0; +} + +static int usbtmc488_ioctl_simple(struct usbtmc_device_data *data, + void __user *arg, unsigned int cmd) +{ + struct device *dev = &data->intf->dev; + __u8 val; + u8 *buffer; + u16 wValue; + int rv; + + if (!(data->usb488_caps & USBTMC488_CAPABILITY_SIMPLE)) + return -EINVAL; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + if (cmd == USBTMC488_REQUEST_REN_CONTROL) { + rv = copy_from_user(&val, arg, sizeof(val)); + if (rv) { + rv = -EFAULT; + goto exit; + } + wValue = val ? 1 : 0; + } else { + wValue = 0; + } + + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + cmd, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + wValue, + data->ifnum, + buffer, 0x01, USB_CTRL_GET_TIMEOUT); + if (rv < 0) { + dev_err(dev, "simple usb_control_msg failed %d\n", rv); + goto exit; + } else if (rv != 1) { + dev_warn(dev, "simple usb_control_msg returned %d\n", rv); + rv = -EIO; + goto exit; + } + + if (buffer[0] != USBTMC_STATUS_SUCCESS) { + dev_err(dev, "simple control status returned %x\n", buffer[0]); + rv = -EIO; + goto exit; + } + rv = 0; + + exit: + kfree(buffer); + return rv; +} + +/* + * Sends a TRIGGER Bulk-OUT command message + * See the USBTMC-USB488 specification, Table 2. + * + * Also updates bTag_last_write. + */ +static int usbtmc488_ioctl_trigger(struct usbtmc_file_data *file_data) +{ + struct usbtmc_device_data *data = file_data->data; + int retval; + u8 *buffer; + int actual; + + buffer = kzalloc(USBTMC_HEADER_SIZE, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + buffer[0] = 128; + buffer[1] = data->bTag; + buffer[2] = ~data->bTag; + + retval = usb_bulk_msg_killable(data->usb_dev, + usb_sndbulkpipe(data->usb_dev, + data->bulk_out), + buffer, USBTMC_HEADER_SIZE, + &actual, file_data->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++; + + kfree(buffer); + if (retval < 0) { + dev_err(&data->intf->dev, "%s returned %d\n", + __func__, retval); + return retval; + } + + return 0; +} + +static struct urb *usbtmc_create_urb(void) +{ + const size_t bufsize = USBTMC_BUFSIZE; + u8 *dmabuf = NULL; + struct urb *urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!urb) + return NULL; + + dmabuf = kzalloc(bufsize, GFP_KERNEL); + if (!dmabuf) { + usb_free_urb(urb); + return NULL; + } + + urb->transfer_buffer = dmabuf; + urb->transfer_buffer_length = bufsize; + urb->transfer_flags |= URB_FREE_BUFFER; + return urb; +} + +static void usbtmc_read_bulk_cb(struct urb *urb) +{ + struct usbtmc_file_data *file_data = urb->context; + int status = urb->status; + unsigned long flags; + + /* sync/async unlink faults aren't errors */ + if (status) { + if (!(/* status == -ENOENT || */ + status == -ECONNRESET || + status == -EREMOTEIO || /* Short packet */ + status == -ESHUTDOWN)) + dev_err(&file_data->data->intf->dev, + "%s - nonzero read bulk status received: %d\n", + __func__, status); + + spin_lock_irqsave(&file_data->err_lock, flags); + if (!file_data->in_status) + file_data->in_status = status; + spin_unlock_irqrestore(&file_data->err_lock, flags); + } + + spin_lock_irqsave(&file_data->err_lock, flags); + file_data->in_transfer_size += urb->actual_length; + dev_dbg(&file_data->data->intf->dev, + "%s - total size: %u current: %d status: %d\n", + __func__, file_data->in_transfer_size, + urb->actual_length, status); + spin_unlock_irqrestore(&file_data->err_lock, flags); + usb_anchor_urb(urb, &file_data->in_anchor); + + wake_up_interruptible(&file_data->wait_bulk_in); + wake_up_interruptible(&file_data->data->waitq); +} + +static inline bool usbtmc_do_transfer(struct usbtmc_file_data *file_data) +{ + bool data_or_error; + + spin_lock_irq(&file_data->err_lock); + data_or_error = !usb_anchor_empty(&file_data->in_anchor) + || file_data->in_status; + spin_unlock_irq(&file_data->err_lock); + dev_dbg(&file_data->data->intf->dev, "%s: returns %d\n", __func__, + data_or_error); + return data_or_error; +} + +static ssize_t usbtmc_generic_read(struct usbtmc_file_data *file_data, + void __user *user_buffer, + u32 transfer_size, + u32 *transferred, + u32 flags) +{ + struct usbtmc_device_data *data = file_data->data; + struct device *dev = &data->intf->dev; + u32 done = 0; + u32 remaining; + const u32 bufsize = USBTMC_BUFSIZE; + int retval = 0; + u32 max_transfer_size; + unsigned long expire; + int bufcount = 1; + int again = 0; + long wait_rv; + + /* mutex already locked */ + + *transferred = done; + + max_transfer_size = transfer_size; + + if (flags & USBTMC_FLAG_IGNORE_TRAILER) { + /* The device may send extra alignment bytes (up to + * wMaxPacketSize – 1) to avoid sending a zero-length + * packet + */ + remaining = transfer_size; + if ((max_transfer_size % data->wMaxPacketSize) == 0) + max_transfer_size += (data->wMaxPacketSize - 1); + } else { + /* round down to bufsize to avoid truncated data left */ + if (max_transfer_size > bufsize) { + max_transfer_size = + roundup(max_transfer_size + 1 - bufsize, + bufsize); + } + remaining = max_transfer_size; + } + + spin_lock_irq(&file_data->err_lock); + + if (file_data->in_status) { + /* return the very first error */ + retval = file_data->in_status; + spin_unlock_irq(&file_data->err_lock); + goto error; + } + + if (flags & USBTMC_FLAG_ASYNC) { + if (usb_anchor_empty(&file_data->in_anchor)) + again = 1; + + if (file_data->in_urbs_used == 0) { + file_data->in_transfer_size = 0; + file_data->in_status = 0; + } + } else { + file_data->in_transfer_size = 0; + file_data->in_status = 0; + } + + if (max_transfer_size == 0) { + bufcount = 0; + } else { + bufcount = roundup(max_transfer_size, bufsize) / bufsize; + if (bufcount > file_data->in_urbs_used) + bufcount -= file_data->in_urbs_used; + else + bufcount = 0; + + if (bufcount + file_data->in_urbs_used > MAX_URBS_IN_FLIGHT) { + bufcount = MAX_URBS_IN_FLIGHT - + file_data->in_urbs_used; + } + } + spin_unlock_irq(&file_data->err_lock); + + dev_dbg(dev, "%s: requested=%u flags=0x%X size=%u bufs=%d used=%d\n", + __func__, transfer_size, flags, + max_transfer_size, bufcount, file_data->in_urbs_used); + + while (bufcount > 0) { + u8 *dmabuf = NULL; + struct urb *urb = usbtmc_create_urb(); + + if (!urb) { + retval = -ENOMEM; + goto error; + } + + dmabuf = urb->transfer_buffer; + + usb_fill_bulk_urb(urb, data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, data->bulk_in), + dmabuf, bufsize, + usbtmc_read_bulk_cb, file_data); + + usb_anchor_urb(urb, &file_data->submitted); + retval = usb_submit_urb(urb, GFP_KERNEL); + /* urb is anchored. We can release our reference. */ + usb_free_urb(urb); + if (unlikely(retval)) { + usb_unanchor_urb(urb); + goto error; + } + file_data->in_urbs_used++; + bufcount--; + } + + if (again) { + dev_dbg(dev, "%s: ret=again\n", __func__); + return -EAGAIN; + } + + if (user_buffer == NULL) + return -EINVAL; + + expire = msecs_to_jiffies(file_data->timeout); + + while (max_transfer_size > 0) { + u32 this_part; + struct urb *urb = NULL; + + if (!(flags & USBTMC_FLAG_ASYNC)) { + dev_dbg(dev, "%s: before wait time %lu\n", + __func__, expire); + wait_rv = wait_event_interruptible_timeout( + file_data->wait_bulk_in, + usbtmc_do_transfer(file_data), + expire); + + dev_dbg(dev, "%s: wait returned %ld\n", + __func__, wait_rv); + + if (wait_rv < 0) { + retval = wait_rv; + goto error; + } + + if (wait_rv == 0) { + retval = -ETIMEDOUT; + goto error; + } + + } + + urb = usb_get_from_anchor(&file_data->in_anchor); + if (!urb) { + if (!(flags & USBTMC_FLAG_ASYNC)) { + /* synchronous case: must not happen */ + retval = -EFAULT; + goto error; + } + + /* asynchronous case: ready, do not block or wait */ + *transferred = done; + dev_dbg(dev, "%s: (async) done=%u ret=0\n", + __func__, done); + return 0; + } + + file_data->in_urbs_used--; + + if (max_transfer_size > urb->actual_length) + max_transfer_size -= urb->actual_length; + else + max_transfer_size = 0; + + if (remaining > urb->actual_length) + this_part = urb->actual_length; + else + this_part = remaining; + + print_hex_dump_debug("usbtmc ", DUMP_PREFIX_NONE, 16, 1, + urb->transfer_buffer, urb->actual_length, true); + + if (copy_to_user(user_buffer + done, + urb->transfer_buffer, this_part)) { + usb_free_urb(urb); + retval = -EFAULT; + goto error; + } + + remaining -= this_part; + done += this_part; + + spin_lock_irq(&file_data->err_lock); + if (urb->status) { + /* return the very first error */ + retval = file_data->in_status; + spin_unlock_irq(&file_data->err_lock); + usb_free_urb(urb); + goto error; + } + spin_unlock_irq(&file_data->err_lock); + + if (urb->actual_length < bufsize) { + /* short packet or ZLP received => ready */ + usb_free_urb(urb); + retval = 1; + break; + } + + if (!(flags & USBTMC_FLAG_ASYNC) && + max_transfer_size > (bufsize * file_data->in_urbs_used)) { + /* resubmit, since other buffers still not enough */ + usb_anchor_urb(urb, &file_data->submitted); + retval = usb_submit_urb(urb, GFP_KERNEL); + if (unlikely(retval)) { + usb_unanchor_urb(urb); + usb_free_urb(urb); + goto error; + } + file_data->in_urbs_used++; + } + usb_free_urb(urb); + retval = 0; + } + +error: + *transferred = done; + + dev_dbg(dev, "%s: before kill\n", __func__); + /* Attention: killing urbs can take long time (2 ms) */ + usb_kill_anchored_urbs(&file_data->submitted); + dev_dbg(dev, "%s: after kill\n", __func__); + usb_scuttle_anchored_urbs(&file_data->in_anchor); + file_data->in_urbs_used = 0; + file_data->in_status = 0; /* no spinlock needed here */ + dev_dbg(dev, "%s: done=%u ret=%d\n", __func__, done, retval); + + return retval; +} + +static ssize_t usbtmc_ioctl_generic_read(struct usbtmc_file_data *file_data, + void __user *arg) +{ + struct usbtmc_message msg; + ssize_t retval = 0; + + /* mutex already locked */ + + if (copy_from_user(&msg, arg, sizeof(struct usbtmc_message))) + return -EFAULT; + + retval = usbtmc_generic_read(file_data, msg.message, + msg.transfer_size, &msg.transferred, + msg.flags); + + if (put_user(msg.transferred, + &((struct usbtmc_message __user *)arg)->transferred)) + return -EFAULT; + + return retval; +} + +static void usbtmc_write_bulk_cb(struct urb *urb) +{ + struct usbtmc_file_data *file_data = urb->context; + int wakeup = 0; + unsigned long flags; + + spin_lock_irqsave(&file_data->err_lock, flags); + file_data->out_transfer_size += urb->actual_length; + + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dev_err(&file_data->data->intf->dev, + "%s - nonzero write bulk status received: %d\n", + __func__, urb->status); + + if (!file_data->out_status) { + file_data->out_status = urb->status; + wakeup = 1; + } + } + spin_unlock_irqrestore(&file_data->err_lock, flags); + + dev_dbg(&file_data->data->intf->dev, + "%s - write bulk total size: %u\n", + __func__, file_data->out_transfer_size); + + up(&file_data->limit_write_sem); + if (usb_anchor_empty(&file_data->submitted) || wakeup) + wake_up_interruptible(&file_data->data->waitq); +} + +static ssize_t usbtmc_generic_write(struct usbtmc_file_data *file_data, + const void __user *user_buffer, + u32 transfer_size, + u32 *transferred, + u32 flags) +{ + struct usbtmc_device_data *data = file_data->data; + struct device *dev; + u32 done = 0; + u32 remaining; + unsigned long expire; + const u32 bufsize = USBTMC_BUFSIZE; + struct urb *urb = NULL; + int retval = 0; + u32 timeout; + + *transferred = 0; + + /* Get pointer to private data structure */ + dev = &data->intf->dev; + + dev_dbg(dev, "%s: size=%u flags=0x%X sema=%u\n", + __func__, transfer_size, flags, + file_data->limit_write_sem.count); + + if (flags & USBTMC_FLAG_APPEND) { + spin_lock_irq(&file_data->err_lock); + retval = file_data->out_status; + spin_unlock_irq(&file_data->err_lock); + if (retval < 0) + return retval; + } else { + spin_lock_irq(&file_data->err_lock); + file_data->out_transfer_size = 0; + file_data->out_status = 0; + spin_unlock_irq(&file_data->err_lock); + } + + remaining = transfer_size; + if (remaining > INT_MAX) + remaining = INT_MAX; + + timeout = file_data->timeout; + expire = msecs_to_jiffies(timeout); + + while (remaining > 0) { + u32 this_part, aligned; + u8 *buffer = NULL; + + if (flags & USBTMC_FLAG_ASYNC) { + if (down_trylock(&file_data->limit_write_sem)) { + retval = (done)?(0):(-EAGAIN); + goto exit; + } + } else { + retval = down_timeout(&file_data->limit_write_sem, + expire); + if (retval < 0) { + retval = -ETIMEDOUT; + goto error; + } + } + + spin_lock_irq(&file_data->err_lock); + retval = file_data->out_status; + spin_unlock_irq(&file_data->err_lock); + if (retval < 0) { + up(&file_data->limit_write_sem); + goto error; + } + + /* prepare next urb to send */ + urb = usbtmc_create_urb(); + if (!urb) { + retval = -ENOMEM; + up(&file_data->limit_write_sem); + goto error; + } + buffer = urb->transfer_buffer; + + if (remaining > bufsize) + this_part = bufsize; + else + this_part = remaining; + + if (copy_from_user(buffer, user_buffer + done, this_part)) { + retval = -EFAULT; + up(&file_data->limit_write_sem); + goto error; + } + + print_hex_dump_debug("usbtmc ", DUMP_PREFIX_NONE, + 16, 1, buffer, this_part, true); + + /* fill bulk with 32 bit alignment to meet USBTMC specification + * (size + 3 & ~3) rounds up and simplifies user code + */ + aligned = (this_part + 3) & ~3; + dev_dbg(dev, "write(size:%u align:%u done:%u)\n", + (unsigned int)this_part, + (unsigned int)aligned, + (unsigned int)done); + + usb_fill_bulk_urb(urb, data->usb_dev, + usb_sndbulkpipe(data->usb_dev, data->bulk_out), + urb->transfer_buffer, aligned, + usbtmc_write_bulk_cb, file_data); + + usb_anchor_urb(urb, &file_data->submitted); + retval = usb_submit_urb(urb, GFP_KERNEL); + if (unlikely(retval)) { + usb_unanchor_urb(urb); + up(&file_data->limit_write_sem); + goto error; + } + + usb_free_urb(urb); + urb = NULL; /* urb will be finally released by usb driver */ + + remaining -= this_part; + done += this_part; + } + + /* All urbs are on the fly */ + if (!(flags & USBTMC_FLAG_ASYNC)) { + if (!usb_wait_anchor_empty_timeout(&file_data->submitted, + timeout)) { + retval = -ETIMEDOUT; + goto error; + } + } + + retval = 0; + goto exit; + +error: + usb_kill_anchored_urbs(&file_data->submitted); +exit: + usb_free_urb(urb); + + spin_lock_irq(&file_data->err_lock); + if (!(flags & USBTMC_FLAG_ASYNC)) + done = file_data->out_transfer_size; + if (!retval && file_data->out_status) + retval = file_data->out_status; + spin_unlock_irq(&file_data->err_lock); + + *transferred = done; + + dev_dbg(dev, "%s: done=%u, retval=%d, urbstat=%d\n", + __func__, done, retval, file_data->out_status); + + return retval; +} + +static ssize_t usbtmc_ioctl_generic_write(struct usbtmc_file_data *file_data, + void __user *arg) +{ + struct usbtmc_message msg; + ssize_t retval = 0; + + /* mutex already locked */ + + if (copy_from_user(&msg, arg, sizeof(struct usbtmc_message))) + return -EFAULT; + + retval = usbtmc_generic_write(file_data, msg.message, + msg.transfer_size, &msg.transferred, + msg.flags); + + if (put_user(msg.transferred, + &((struct usbtmc_message __user *)arg)->transferred)) + return -EFAULT; + + return retval; +} + +/* + * Get the generic write result + */ +static ssize_t usbtmc_ioctl_write_result(struct usbtmc_file_data *file_data, + void __user *arg) +{ + u32 transferred; + int retval; + + spin_lock_irq(&file_data->err_lock); + transferred = file_data->out_transfer_size; + retval = file_data->out_status; + spin_unlock_irq(&file_data->err_lock); + + if (put_user(transferred, (__u32 __user *)arg)) + return -EFAULT; + + return retval; +} + +/* + * Sends a REQUEST_DEV_DEP_MSG_IN message on the Bulk-OUT endpoint. + * @transfer_size: number of bytes to request from the device. + * + * See the USBTMC specification, Table 4. + * + * Also updates bTag_last_write. + */ +static int send_request_dev_dep_msg_in(struct usbtmc_file_data *file_data, + u32 transfer_size) +{ + struct usbtmc_device_data *data = file_data->data; + int retval; + u8 *buffer; + int actual; + + buffer = kmalloc(USBTMC_HEADER_SIZE, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + /* Setup IO buffer for REQUEST_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] = transfer_size >> 0; + buffer[5] = transfer_size >> 8; + buffer[6] = transfer_size >> 16; + buffer[7] = transfer_size >> 24; + buffer[8] = file_data->term_char_enabled * 2; + /* Use term character? */ + buffer[9] = file_data->term_char; + buffer[10] = 0; /* Reserved */ + buffer[11] = 0; /* Reserved */ + + /* Send bulk URB */ + retval = usb_bulk_msg_killable(data->usb_dev, + usb_sndbulkpipe(data->usb_dev, + data->bulk_out), + buffer, USBTMC_HEADER_SIZE, + &actual, file_data->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++; + + kfree(buffer); + if (retval < 0) + dev_err(&data->intf->dev, "%s returned %d\n", + __func__, retval); + + return retval; +} + +static ssize_t usbtmc_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + struct usbtmc_file_data *file_data; + struct usbtmc_device_data *data; + struct device *dev; + const u32 bufsize = USBTMC_BUFSIZE; + u32 n_characters; + u8 *buffer; + int actual; + u32 done = 0; + u32 remaining; + int retval; + + /* Get pointer to private data structure */ + file_data = filp->private_data; + data = file_data->data; + dev = &data->intf->dev; + + buffer = kmalloc(bufsize, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + retval = mutex_lock_interruptible(&data->io_mutex); + if (retval < 0) + goto exit_nolock; + + if (data->zombie) { + retval = -ENODEV; + goto exit; + } + + if (count > INT_MAX) + count = INT_MAX; + + dev_dbg(dev, "%s(count:%zu)\n", __func__, count); + + retval = send_request_dev_dep_msg_in(file_data, count); + + if (retval < 0) { + if (file_data->auto_abort) + usbtmc_ioctl_abort_bulk_out(data); + goto exit; + } + + /* Loop until we have fetched everything we requested */ + remaining = count; + actual = 0; + + /* Send bulk URB */ + retval = usb_bulk_msg_killable(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, + data->bulk_in), + buffer, bufsize, &actual, + file_data->timeout); + + dev_dbg(dev, "%s: bulk_msg retval(%u), actual(%d)\n", + __func__, retval, actual); + + /* Store bTag (in case we need to abort) */ + data->bTag_last_read = data->bTag; + + if (retval < 0) { + if (file_data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } + + /* Sanity checks for the header */ + if (actual < USBTMC_HEADER_SIZE) { + dev_err(dev, "Device sent too small first packet: %u < %u\n", + actual, USBTMC_HEADER_SIZE); + if (file_data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } + + if (buffer[0] != 2) { + dev_err(dev, "Device sent reply with wrong MsgID: %u != 2\n", + buffer[0]); + if (file_data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } + + if (buffer[1] != data->bTag_last_write) { + dev_err(dev, "Device sent reply with wrong bTag: %u != %u\n", + buffer[1], data->bTag_last_write); + if (file_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); + + file_data->bmTransferAttributes = buffer[8]; + + dev_dbg(dev, "Bulk-IN header: N_characters(%u), bTransAttr(%u)\n", + n_characters, buffer[8]); + + if (n_characters > remaining) { + dev_err(dev, "Device wants to return more data than requested: %u > %zu\n", + n_characters, count); + if (file_data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } + + print_hex_dump_debug("usbtmc ", DUMP_PREFIX_NONE, + 16, 1, buffer, actual, true); + + remaining = n_characters; + + /* Remove the USBTMC header */ + actual -= USBTMC_HEADER_SIZE; + + /* Remove padding if it exists */ + if (actual > remaining) + actual = remaining; + + remaining -= actual; + + /* Copy buffer to user space */ + if (copy_to_user(buf, &buffer[USBTMC_HEADER_SIZE], actual)) { + /* There must have been an addressing problem */ + retval = -EFAULT; + goto exit; + } + + if ((actual + USBTMC_HEADER_SIZE) == bufsize) { + retval = usbtmc_generic_read(file_data, buf + actual, + remaining, + &done, + USBTMC_FLAG_IGNORE_TRAILER); + if (retval < 0) + goto exit; + } + done += actual; + + /* Update file position value */ + *f_pos = *f_pos + done; + retval = done; + +exit: + mutex_unlock(&data->io_mutex); +exit_nolock: + 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_file_data *file_data; + struct usbtmc_device_data *data; + struct urb *urb = NULL; + ssize_t retval = 0; + u8 *buffer; + u32 remaining, done; + u32 transfersize, aligned, buflen; + + file_data = filp->private_data; + data = file_data->data; + + mutex_lock(&data->io_mutex); + + if (data->zombie) { + retval = -ENODEV; + goto exit; + } + + done = 0; + + spin_lock_irq(&file_data->err_lock); + file_data->out_transfer_size = 0; + file_data->out_status = 0; + spin_unlock_irq(&file_data->err_lock); + + if (!count) + goto exit; + + if (down_trylock(&file_data->limit_write_sem)) { + /* previous calls were async */ + retval = -EBUSY; + goto exit; + } + + urb = usbtmc_create_urb(); + if (!urb) { + retval = -ENOMEM; + up(&file_data->limit_write_sem); + goto exit; + } + + buffer = urb->transfer_buffer; + buflen = urb->transfer_buffer_length; + + if (count > INT_MAX) { + transfersize = INT_MAX; + buffer[8] = 0; + } else { + transfersize = count; + buffer[8] = file_data->eom_val; + } + + /* 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] = transfersize >> 0; + buffer[5] = transfersize >> 8; + buffer[6] = transfersize >> 16; + buffer[7] = transfersize >> 24; + /* buffer[8] is set above... */ + buffer[9] = 0; /* Reserved */ + buffer[10] = 0; /* Reserved */ + buffer[11] = 0; /* Reserved */ + + remaining = transfersize; + + if (transfersize + USBTMC_HEADER_SIZE > buflen) { + transfersize = buflen - USBTMC_HEADER_SIZE; + aligned = buflen; + } else { + aligned = (transfersize + (USBTMC_HEADER_SIZE + 3)) & ~3; + } + + if (copy_from_user(&buffer[USBTMC_HEADER_SIZE], buf, transfersize)) { + retval = -EFAULT; + up(&file_data->limit_write_sem); + goto exit; + } + + dev_dbg(&data->intf->dev, "%s(size:%u align:%u)\n", __func__, + (unsigned int)transfersize, (unsigned int)aligned); + + print_hex_dump_debug("usbtmc ", DUMP_PREFIX_NONE, + 16, 1, buffer, aligned, true); + + usb_fill_bulk_urb(urb, data->usb_dev, + usb_sndbulkpipe(data->usb_dev, data->bulk_out), + urb->transfer_buffer, aligned, + usbtmc_write_bulk_cb, file_data); + + usb_anchor_urb(urb, &file_data->submitted); + retval = usb_submit_urb(urb, GFP_KERNEL); + if (unlikely(retval)) { + usb_unanchor_urb(urb); + up(&file_data->limit_write_sem); + goto exit; + } + + remaining -= transfersize; + + data->bTag_last_write = data->bTag; + data->bTag++; + + if (!data->bTag) + data->bTag++; + + /* call generic_write even when remaining = 0 */ + retval = usbtmc_generic_write(file_data, buf + transfersize, remaining, + &done, USBTMC_FLAG_APPEND); + /* truncate alignment bytes */ + if (done > remaining) + done = remaining; + + /*add size of first urb*/ + done += transfersize; + + if (retval < 0) { + usb_kill_anchored_urbs(&file_data->submitted); + + dev_err(&data->intf->dev, + "Unable to send data, error %d\n", (int)retval); + if (file_data->auto_abort) + usbtmc_ioctl_abort_bulk_out(data); + goto exit; + } + + retval = done; +exit: + usb_free_urb(urb); + mutex_unlock(&data->io_mutex); + return retval; +} + +static int usbtmc_ioctl_clear(struct usbtmc_device_data *data) +{ + struct device *dev; + u8 *buffer; + int rv; + int n; + int actual = 0; + + dev = &data->intf->dev; + + dev_dbg(dev, "Sending INITIATE_CLEAR request\n"); + + buffer = kmalloc(USBTMC_BUFSIZE, 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, USB_CTRL_GET_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; + } + + 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, USB_CTRL_GET_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) != 0) { + do { + dev_dbg(dev, "Reading from bulk in EP\n"); + + actual = 0; + rv = usb_bulk_msg(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, + data->bulk_in), + buffer, USBTMC_BUFSIZE, + &actual, USB_CTRL_GET_TIMEOUT); + + print_hex_dump_debug("usbtmc ", DUMP_PREFIX_NONE, + 16, 1, buffer, actual, true); + + n++; + + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", + rv); + goto exit; + } + } while ((actual == USBTMC_BUFSIZE) && + (n < USBTMC_MAX_READS_TO_CLEAR_BULK_IN)); + } else { + /* do not stress device with subsequent requests */ + msleep(50); + n++; + } + + if (n >= USBTMC_MAX_READS_TO_CLEAR_BULK_IN) { + 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_clear_halt 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, "%s returned %d\n", __func__, rv); + return rv; +} + +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, "%s returned %d\n", __func__, rv); + return rv; +} + +static int usbtmc_ioctl_cancel_io(struct usbtmc_file_data *file_data) +{ + spin_lock_irq(&file_data->err_lock); + file_data->in_status = -ECANCELED; + file_data->out_status = -ECANCELED; + spin_unlock_irq(&file_data->err_lock); + usb_kill_anchored_urbs(&file_data->submitted); + return 0; +} + +static int usbtmc_ioctl_cleanup_io(struct usbtmc_file_data *file_data) +{ + usb_kill_anchored_urbs(&file_data->submitted); + usb_scuttle_anchored_urbs(&file_data->in_anchor); + spin_lock_irq(&file_data->err_lock); + file_data->in_status = 0; + file_data->in_transfer_size = 0; + file_data->out_status = 0; + file_data->out_transfer_size = 0; + spin_unlock_irq(&file_data->err_lock); + + file_data->in_urbs_used = 0; + 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, USB_CTRL_GET_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]; + data->usb488_caps = (buffer[14] & 0x07) | ((buffer[15] & 0x0f) << 4); + rv = 0; + +err_out: + kfree(buffer); + return rv; +} + +#define capability_attribute(name) \ +static ssize_t name##_show(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_RO(name) + +capability_attribute(interface_capabilities); +capability_attribute(device_capabilities); +capability_attribute(usb488_interface_capabilities); +capability_attribute(usb488_device_capabilities); + +static struct attribute *usbtmc_attrs[] = { + &dev_attr_interface_capabilities.attr, + &dev_attr_device_capabilities.attr, + &dev_attr_usb488_interface_capabilities.attr, + &dev_attr_usb488_device_capabilities.attr, + NULL, +}; +ATTRIBUTE_GROUPS(usbtmc); + +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, USB_CTRL_GET_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 int usbtmc_ioctl_request(struct usbtmc_device_data *data, + void __user *arg) +{ + struct device *dev = &data->intf->dev; + struct usbtmc_ctrlrequest request; + u8 *buffer = NULL; + int rv; + unsigned int is_in, pipe; + + if (copy_from_user(&request, arg, sizeof(struct usbtmc_ctrlrequest))) + return -EFAULT; + + if (request.req.wLength > USBTMC_BUFSIZE) + return -EMSGSIZE; + if (request.req.wLength == 0) /* Length-0 requests are never IN */ + request.req.bRequestType &= ~USB_DIR_IN; + + is_in = request.req.bRequestType & USB_DIR_IN; + + if (request.req.wLength) { + buffer = kmalloc(request.req.wLength, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + if (!is_in) { + /* Send control data to device */ + if (copy_from_user(buffer, request.data, + request.req.wLength)) { + rv = -EFAULT; + goto exit; + } + } + } + + if (is_in) + pipe = usb_rcvctrlpipe(data->usb_dev, 0); + else + pipe = usb_sndctrlpipe(data->usb_dev, 0); + rv = usb_control_msg(data->usb_dev, + pipe, + request.req.bRequest, + request.req.bRequestType, + request.req.wValue, + request.req.wIndex, + buffer, request.req.wLength, USB_CTRL_GET_TIMEOUT); + + if (rv < 0) { + dev_err(dev, "%s failed %d\n", __func__, rv); + goto exit; + } + + if (rv && is_in) { + /* Read control data from device */ + if (copy_to_user(request.data, buffer, rv)) + rv = -EFAULT; + } + + exit: + kfree(buffer); + return rv; +} + +/* + * Get the usb timeout value + */ +static int usbtmc_ioctl_get_timeout(struct usbtmc_file_data *file_data, + void __user *arg) +{ + u32 timeout; + + timeout = file_data->timeout; + + return put_user(timeout, (__u32 __user *)arg); +} + +/* + * Set the usb timeout value + */ +static int usbtmc_ioctl_set_timeout(struct usbtmc_file_data *file_data, + void __user *arg) +{ + u32 timeout; + + if (get_user(timeout, (__u32 __user *)arg)) + return -EFAULT; + + /* Note that timeout = 0 means + * MAX_SCHEDULE_TIMEOUT in usb_control_msg + */ + if (timeout < USBTMC_MIN_TIMEOUT) + return -EINVAL; + + file_data->timeout = timeout; + + return 0; +} + +/* + * enables/disables sending EOM on write + */ +static int usbtmc_ioctl_eom_enable(struct usbtmc_file_data *file_data, + void __user *arg) +{ + u8 eom_enable; + + if (copy_from_user(&eom_enable, arg, sizeof(eom_enable))) + return -EFAULT; + + if (eom_enable > 1) + return -EINVAL; + + file_data->eom_val = eom_enable; + + return 0; +} + +/* + * Configure termination character for read() + */ +static int usbtmc_ioctl_config_termc(struct usbtmc_file_data *file_data, + void __user *arg) +{ + struct usbtmc_termchar termc; + + if (copy_from_user(&termc, arg, sizeof(termc))) + return -EFAULT; + + if ((termc.term_char_enabled > 1) || + (termc.term_char_enabled && + !(file_data->data->capabilities.device_capabilities & 1))) + return -EINVAL; + + file_data->term_char = termc.term_char; + file_data->term_char_enabled = termc.term_char_enabled; + + return 0; +} + +static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct usbtmc_file_data *file_data; + struct usbtmc_device_data *data; + int retval = -EBADRQC; + __u8 tmp_byte; + + file_data = file->private_data; + data = file_data->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; + + case USBTMC_IOCTL_CTRL_REQUEST: + retval = usbtmc_ioctl_request(data, (void __user *)arg); + break; + + case USBTMC_IOCTL_GET_TIMEOUT: + retval = usbtmc_ioctl_get_timeout(file_data, + (void __user *)arg); + break; + + case USBTMC_IOCTL_SET_TIMEOUT: + retval = usbtmc_ioctl_set_timeout(file_data, + (void __user *)arg); + break; + + case USBTMC_IOCTL_EOM_ENABLE: + retval = usbtmc_ioctl_eom_enable(file_data, + (void __user *)arg); + break; + + case USBTMC_IOCTL_CONFIG_TERMCHAR: + retval = usbtmc_ioctl_config_termc(file_data, + (void __user *)arg); + break; + + case USBTMC_IOCTL_WRITE: + retval = usbtmc_ioctl_generic_write(file_data, + (void __user *)arg); + break; + + case USBTMC_IOCTL_READ: + retval = usbtmc_ioctl_generic_read(file_data, + (void __user *)arg); + break; + + case USBTMC_IOCTL_WRITE_RESULT: + retval = usbtmc_ioctl_write_result(file_data, + (void __user *)arg); + break; + + case USBTMC_IOCTL_API_VERSION: + retval = put_user(USBTMC_API_VERSION, + (__u32 __user *)arg); + break; + + case USBTMC488_IOCTL_GET_CAPS: + retval = put_user(data->usb488_caps, + (unsigned char __user *)arg); + break; + + case USBTMC488_IOCTL_READ_STB: + retval = usbtmc488_ioctl_read_stb(file_data, + (void __user *)arg); + break; + + case USBTMC488_IOCTL_REN_CONTROL: + retval = usbtmc488_ioctl_simple(data, (void __user *)arg, + USBTMC488_REQUEST_REN_CONTROL); + break; + + case USBTMC488_IOCTL_GOTO_LOCAL: + retval = usbtmc488_ioctl_simple(data, (void __user *)arg, + USBTMC488_REQUEST_GOTO_LOCAL); + break; + + case USBTMC488_IOCTL_LOCAL_LOCKOUT: + retval = usbtmc488_ioctl_simple(data, (void __user *)arg, + USBTMC488_REQUEST_LOCAL_LOCKOUT); + break; + + case USBTMC488_IOCTL_TRIGGER: + retval = usbtmc488_ioctl_trigger(file_data); + break; + + case USBTMC488_IOCTL_WAIT_SRQ: + retval = usbtmc488_ioctl_wait_srq(file_data, + (__u32 __user *)arg); + break; + + case USBTMC_IOCTL_MSG_IN_ATTR: + retval = put_user(file_data->bmTransferAttributes, + (__u8 __user *)arg); + break; + + case USBTMC_IOCTL_AUTO_ABORT: + retval = get_user(tmp_byte, (unsigned char __user *)arg); + if (retval == 0) + file_data->auto_abort = !!tmp_byte; + break; + + case USBTMC_IOCTL_GET_STB: + retval = usbtmc_get_stb(file_data, &tmp_byte); + if (!retval) + retval = put_user(tmp_byte, (__u8 __user *)arg); + break; + + case USBTMC_IOCTL_GET_SRQ_STB: + retval = usbtmc_ioctl_get_srq_stb(file_data, + (void __user *)arg); + break; + + case USBTMC_IOCTL_CANCEL_IO: + retval = usbtmc_ioctl_cancel_io(file_data); + break; + + case USBTMC_IOCTL_CLEANUP_IO: + retval = usbtmc_ioctl_cleanup_io(file_data); + break; + } + +skip_io_on_zombie: + mutex_unlock(&data->io_mutex); + return retval; +} + +static int usbtmc_fasync(int fd, struct file *file, int on) +{ + struct usbtmc_file_data *file_data = file->private_data; + + return fasync_helper(fd, file, on, &file_data->data->fasync); +} + +static __poll_t usbtmc_poll(struct file *file, poll_table *wait) +{ + struct usbtmc_file_data *file_data = file->private_data; + struct usbtmc_device_data *data = file_data->data; + __poll_t mask; + + mutex_lock(&data->io_mutex); + + if (data->zombie) { + mask = EPOLLHUP | EPOLLERR; + goto no_poll; + } + + poll_wait(file, &data->waitq, wait); + + /* Note that EPOLLPRI is now assigned to SRQ, and + * EPOLLIN|EPOLLRDNORM to normal read data. + */ + mask = 0; + if (atomic_read(&file_data->srq_asserted)) + mask |= EPOLLPRI; + + /* Note that the anchor submitted includes all urbs for BULK IN + * and OUT. So EPOLLOUT is signaled when BULK OUT is empty and + * all BULK IN urbs are completed and moved to in_anchor. + */ + if (usb_anchor_empty(&file_data->submitted)) + mask |= (EPOLLOUT | EPOLLWRNORM); + if (!usb_anchor_empty(&file_data->in_anchor)) + mask |= (EPOLLIN | EPOLLRDNORM); + + spin_lock_irq(&file_data->err_lock); + if (file_data->in_status || file_data->out_status) + mask |= EPOLLERR; + spin_unlock_irq(&file_data->err_lock); + + dev_dbg(&data->intf->dev, "poll mask = %x\n", mask); + +no_poll: + mutex_unlock(&data->io_mutex); + return mask; +} + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .read = usbtmc_read, + .write = usbtmc_write, + .open = usbtmc_open, + .release = usbtmc_release, + .flush = usbtmc_flush, + .unlocked_ioctl = usbtmc_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .fasync = usbtmc_fasync, + .poll = usbtmc_poll, + .llseek = default_llseek, +}; + +static struct usb_class_driver usbtmc_class = { + .name = "usbtmc%d", + .fops = &fops, + .minor_base = USBTMC_MINOR_BASE, +}; + +static void usbtmc_interrupt(struct urb *urb) +{ + struct usbtmc_device_data *data = urb->context; + struct device *dev = &data->intf->dev; + int status = urb->status; + int rv; + + dev_dbg(&data->intf->dev, "int status: %d len %d\n", + status, urb->actual_length); + + switch (status) { + case 0: /* SUCCESS */ + /* ensure at least two bytes of headers were transferred */ + if (urb->actual_length < 2) { + dev_warn(dev, + "actual length %d not sufficient for interrupt headers\n", + urb->actual_length); + goto exit; + } + + /* check for valid STB notification */ + if (data->iin_buffer[0] > 0x81) { + data->bNotify1 = data->iin_buffer[0]; + data->bNotify2 = data->iin_buffer[1]; + atomic_set(&data->iin_data_valid, 1); + wake_up_interruptible(&data->waitq); + goto exit; + } + /* check for SRQ notification */ + if (data->iin_buffer[0] == 0x81) { + unsigned long flags; + struct list_head *elem; + + if (data->fasync) + kill_fasync(&data->fasync, + SIGIO, POLL_PRI); + + spin_lock_irqsave(&data->dev_lock, flags); + list_for_each(elem, &data->file_list) { + struct usbtmc_file_data *file_data; + + file_data = list_entry(elem, + struct usbtmc_file_data, + file_elem); + file_data->srq_byte = data->iin_buffer[1]; + atomic_set(&file_data->srq_asserted, 1); + } + spin_unlock_irqrestore(&data->dev_lock, flags); + + dev_dbg(dev, "srq received bTag %x stb %x\n", + (unsigned int)data->iin_buffer[0], + (unsigned int)data->iin_buffer[1]); + wake_up_interruptible_all(&data->waitq); + goto exit; + } + dev_warn(dev, "invalid notification: %x\n", + data->iin_buffer[0]); + break; + case -EOVERFLOW: + dev_err(dev, "overflow with length %d, actual length is %d\n", + data->iin_wMaxPacketSize, urb->actual_length); + fallthrough; + default: + /* urb terminated, clean up */ + dev_dbg(dev, "urb terminated, status: %d\n", status); + return; + } +exit: + rv = usb_submit_urb(urb, GFP_ATOMIC); + if (rv) + dev_err(dev, "usb_submit_urb failed: %d\n", rv); +} + +static void usbtmc_free_int(struct usbtmc_device_data *data) +{ + if (!data->iin_ep_present || !data->iin_urb) + return; + usb_kill_urb(data->iin_urb); + kfree(data->iin_buffer); + data->iin_buffer = NULL; + usb_free_urb(data->iin_urb); + data->iin_urb = NULL; + kref_put(&data->kref, usbtmc_delete); +} + +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 *bulk_in, *bulk_out, *int_in; + int retcode; + + dev_dbg(&intf->dev, "%s called\n", __func__); + + data = kzalloc_obj(*data); + if (!data) + return -ENOMEM; + + data->intf = intf; + data->usb_dev = usb_get_dev(interface_to_usbdev(intf)); + usb_set_intfdata(intf, data); + kref_init(&data->kref); + mutex_init(&data->io_mutex); + init_waitqueue_head(&data->waitq); + atomic_set(&data->iin_data_valid, 0); + INIT_LIST_HEAD(&data->file_list); + spin_lock_init(&data->dev_lock); + + data->zombie = 0; + + /* Initialize USBTMC bTag and other fields */ + data->bTag = 1; + /* 2 <= bTag <= 127 USBTMC-USB488 subclass specification 4.3.1 */ + data->iin_bTag = 2; + + /* USBTMC devices have only one setting, so use that */ + iface_desc = data->intf->cur_altsetting; + data->ifnum = iface_desc->desc.bInterfaceNumber; + + /* Find bulk endpoints */ + retcode = usb_find_common_endpoints(iface_desc, + &bulk_in, &bulk_out, NULL, NULL); + if (retcode) { + dev_err(&intf->dev, "bulk endpoints not found\n"); + goto err_put; + } + + retcode = -EINVAL; + data->bulk_in = bulk_in->bEndpointAddress; + data->wMaxPacketSize = usb_endpoint_maxp(bulk_in); + if (!data->wMaxPacketSize) + goto err_put; + dev_dbg(&intf->dev, "Found bulk in endpoint at %u\n", data->bulk_in); + + data->bulk_out = bulk_out->bEndpointAddress; + dev_dbg(&intf->dev, "Found Bulk out endpoint at %u\n", data->bulk_out); + + /* Find int endpoint */ + retcode = usb_find_int_in_endpoint(iface_desc, &int_in); + if (!retcode) { + data->iin_ep_present = 1; + data->iin_ep = int_in->bEndpointAddress; + data->iin_wMaxPacketSize = usb_endpoint_maxp(int_in); + data->iin_interval = int_in->bInterval; + /* wMaxPacketSize should be 0x02 or more as per USB488 Table 22 */ + if (iface_desc->desc.bInterfaceProtocol == 1 && + data->iin_wMaxPacketSize < 2) { + retcode = -EINVAL; + goto err_put; + } + dev_dbg(&intf->dev, "Found Int in endpoint at %u\n", + data->iin_ep); + } + + retcode = get_capabilities(data); + if (retcode) + dev_err(&intf->dev, "can't read capabilities\n"); + + if (data->iin_ep_present) { + /* allocate int urb */ + data->iin_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!data->iin_urb) { + retcode = -ENOMEM; + goto error_register; + } + + /* Protect interrupt in endpoint data until iin_urb is freed */ + kref_get(&data->kref); + + /* allocate buffer for interrupt in */ + data->iin_buffer = kmalloc(data->iin_wMaxPacketSize, + GFP_KERNEL); + if (!data->iin_buffer) { + retcode = -ENOMEM; + goto error_register; + } + + /* fill interrupt urb */ + usb_fill_int_urb(data->iin_urb, data->usb_dev, + usb_rcvintpipe(data->usb_dev, data->iin_ep), + data->iin_buffer, data->iin_wMaxPacketSize, + usbtmc_interrupt, + data, data->iin_interval); + + retcode = usb_submit_urb(data->iin_urb, GFP_KERNEL); + if (retcode) { + dev_err(&intf->dev, "Failed to submit iin_urb\n"); + goto error_register; + } + } + + 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: + usbtmc_free_int(data); +err_put: + kref_put(&data->kref, usbtmc_delete); + return retcode; +} + +static void usbtmc_disconnect(struct usb_interface *intf) +{ + struct usbtmc_device_data *data = usb_get_intfdata(intf); + struct list_head *elem; + + usb_deregister_dev(intf, &usbtmc_class); + mutex_lock(&data->io_mutex); + data->zombie = 1; + wake_up_interruptible_all(&data->waitq); + list_for_each(elem, &data->file_list) { + struct usbtmc_file_data *file_data; + + file_data = list_entry(elem, + struct usbtmc_file_data, + file_elem); + usb_kill_anchored_urbs(&file_data->submitted); + usb_scuttle_anchored_urbs(&file_data->in_anchor); + } + mutex_unlock(&data->io_mutex); + usbtmc_free_int(data); + kref_put(&data->kref, usbtmc_delete); +} + +static void usbtmc_draw_down(struct usbtmc_file_data *file_data) +{ + int time; + + time = usb_wait_anchor_empty_timeout(&file_data->submitted, 1000); + if (!time) + usb_kill_anchored_urbs(&file_data->submitted); + usb_scuttle_anchored_urbs(&file_data->in_anchor); +} + +static int usbtmc_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usbtmc_device_data *data = usb_get_intfdata(intf); + struct list_head *elem; + + if (!data) + return 0; + + mutex_lock(&data->io_mutex); + list_for_each(elem, &data->file_list) { + struct usbtmc_file_data *file_data; + + file_data = list_entry(elem, + struct usbtmc_file_data, + file_elem); + usbtmc_draw_down(file_data); + } + + if (data->iin_ep_present && data->iin_urb) + usb_kill_urb(data->iin_urb); + + mutex_unlock(&data->io_mutex); + return 0; +} + +static int usbtmc_resume(struct usb_interface *intf) +{ + struct usbtmc_device_data *data = usb_get_intfdata(intf); + int retcode = 0; + + if (data->iin_ep_present && data->iin_urb) + retcode = usb_submit_urb(data->iin_urb, GFP_KERNEL); + if (retcode) + dev_err(&intf->dev, "Failed to submit iin_urb\n"); + + return retcode; +} + +static int usbtmc_pre_reset(struct usb_interface *intf) +{ + struct usbtmc_device_data *data = usb_get_intfdata(intf); + struct list_head *elem; + + if (!data) + return 0; + + mutex_lock(&data->io_mutex); + + list_for_each(elem, &data->file_list) { + struct usbtmc_file_data *file_data; + + file_data = list_entry(elem, + struct usbtmc_file_data, + file_elem); + usbtmc_ioctl_cancel_io(file_data); + } + + return 0; +} + +static int usbtmc_post_reset(struct usb_interface *intf) +{ + struct usbtmc_device_data *data = usb_get_intfdata(intf); + + mutex_unlock(&data->io_mutex); + + 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, + .pre_reset = usbtmc_pre_reset, + .post_reset = usbtmc_post_reset, + .dev_groups = usbtmc_groups, +}; + +module_usb_driver(usbtmc_driver); + +MODULE_DESCRIPTION("USB Test & Measurement class driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/common/Kconfig b/drivers/usb/common/Kconfig new file mode 100644 index 000000000..b85662243 --- /dev/null +++ b/drivers/usb/common/Kconfig @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: GPL-2.0 + +config USB_COMMON + tristate + + +config USB_LED_TRIG + bool "USB LED Triggers" + depends on LEDS_CLASS && USB_COMMON && LEDS_TRIGGERS + help + This option adds LED triggers for USB host and/or gadget activity. + + Say Y here if you are working on a system with led-class supported + LEDs and you want to use them as activity indicators for USB host or + gadget. + +config USB_ULPI_BUS + tristate "USB ULPI PHY interface support" + select USB_COMMON + help + UTMI+ Low Pin Interface (ULPI) is specification for a commonly used + USB 2.0 PHY interface. The ULPI specification defines a standard set + of registers that can be used to detect the vendor and product which + allows ULPI to be handled as a bus. This module is the driver for that + bus. + + The ULPI interfaces (the buses) are registered by the drivers for USB + controllers which support ULPI register access and have ULPI PHY + attached to them. The ULPI PHY drivers themselves are normal PHY + drivers. + + ULPI PHYs provide often functions such as ADP sensing/probing (OTG + protocol) and USB charger detection. + + To compile this driver as a module, choose M here: the module will + be called ulpi. + +config USB_CONN_GPIO + tristate "USB GPIO Based Connection Detection Driver" + depends on GPIOLIB + select USB_ROLE_SWITCH + select POWER_SUPPLY + help + The driver supports USB role switch between host and device via GPIO + based USB cable detection, used typically if an input GPIO is used + to detect USB ID pin, and another input GPIO may be also used to detect + Vbus pin at the same time, it also can be used to enable/disable + device if an input GPIO is only used to detect Vbus pin. + + To compile the driver as a module, choose M here: the module will + be called usb-conn-gpio.ko diff --git a/drivers/usb/common/Makefile b/drivers/usb/common/Makefile new file mode 100644 index 000000000..8ac4d21ef --- /dev/null +++ b/drivers/usb/common/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the usb common parts. +# + +obj-$(CONFIG_USB_COMMON) += usb-common.o +usb-common-y += common.o +usb-common-$(CONFIG_TRACING) += debug.o +usb-common-$(CONFIG_USB_LED_TRIG) += led.o + +obj-$(CONFIG_USB_CONN_GPIO) += usb-conn-gpio.o +obj-$(CONFIG_USB_OTG_FSM) += usb-otg-fsm.o +obj-$(CONFIG_USB_ULPI_BUS) += ulpi.o diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c new file mode 100644 index 000000000..fc0845f68 --- /dev/null +++ b/drivers/usb/common/common.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Provides code common for host and device side USB. + * + * 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 +#include +#include +#include +#include +#include +#include "common.h" + +static const char *const ep_type_names[] = { + [USB_ENDPOINT_XFER_CONTROL] = "ctrl", + [USB_ENDPOINT_XFER_ISOC] = "isoc", + [USB_ENDPOINT_XFER_BULK] = "bulk", + [USB_ENDPOINT_XFER_INT] = "intr", +}; + +/** + * usb_ep_type_string() - Returns human readable-name of the endpoint type. + * @ep_type: The endpoint type to return human-readable name for. If it's not + * any of the types: USB_ENDPOINT_XFER_{CONTROL, ISOC, BULK, INT}, + * usually got by usb_endpoint_type(), the string 'unknown' will be returned. + */ +const char *usb_ep_type_string(int ep_type) +{ + if (ep_type < 0 || ep_type >= ARRAY_SIZE(ep_type_names)) + return "unknown"; + + return ep_type_names[ep_type]; +} +EXPORT_SYMBOL_GPL(usb_ep_type_string); + +/** + * usb_otg_state_string() - returns human readable name of OTG state. + * @state: the OTG state to return the human readable name of. If it's not + * any of the states defined in usb_otg_state enum, 'UNDEFINED' will be + * returned. + */ +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); + +static const char *const speed_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", + [USB_SPEED_SUPER_PLUS] = "super-speed-plus", +}; + +static const char *const ssp_rate[] = { + [USB_SSP_GEN_UNKNOWN] = "UNKNOWN", + [USB_SSP_GEN_2x1] = "super-speed-plus-gen2x1", + [USB_SSP_GEN_1x2] = "super-speed-plus-gen1x2", + [USB_SSP_GEN_2x2] = "super-speed-plus-gen2x2", +}; + +/** + * usb_speed_string() - Returns human readable-name of the speed. + * @speed: The speed to return human-readable name for. If it's not + * any of the speeds defined in usb_device_speed enum, string for + * USB_SPEED_UNKNOWN will be returned. + */ +const char *usb_speed_string(enum usb_device_speed speed) +{ + if (speed < 0 || speed >= ARRAY_SIZE(speed_names)) + speed = USB_SPEED_UNKNOWN; + return speed_names[speed]; +} +EXPORT_SYMBOL_GPL(usb_speed_string); + +/** + * usb_get_maximum_speed - Get maximum requested speed for a given USB + * controller. + * @dev: Pointer to the given USB controller device + * + * The function gets the maximum speed string from property "maximum-speed", + * and returns the corresponding enum usb_device_speed. + */ +enum usb_device_speed usb_get_maximum_speed(struct device *dev) +{ + const char *p = "maximum-speed"; + int ret; + + ret = device_property_match_property_string(dev, p, ssp_rate, ARRAY_SIZE(ssp_rate)); + if (ret > 0) + return USB_SPEED_SUPER_PLUS; + + ret = device_property_match_property_string(dev, p, speed_names, ARRAY_SIZE(speed_names)); + if (ret > 0) + return ret; + + return USB_SPEED_UNKNOWN; +} +EXPORT_SYMBOL_GPL(usb_get_maximum_speed); + +/** + * usb_get_maximum_ssp_rate - Get the signaling rate generation and lane count + * of a SuperSpeed Plus capable device. + * @dev: Pointer to the given USB controller device + * + * If the string from "maximum-speed" property is super-speed-plus-genXxY where + * 'X' is the generation number and 'Y' is the number of lanes, then this + * function returns the corresponding enum usb_ssp_rate. + */ +enum usb_ssp_rate usb_get_maximum_ssp_rate(struct device *dev) +{ + const char *maximum_speed; + int ret; + + ret = device_property_read_string(dev, "maximum-speed", &maximum_speed); + if (ret < 0) + return USB_SSP_GEN_UNKNOWN; + + ret = match_string(ssp_rate, ARRAY_SIZE(ssp_rate), maximum_speed); + return (ret < 0) ? USB_SSP_GEN_UNKNOWN : ret; +} +EXPORT_SYMBOL_GPL(usb_get_maximum_ssp_rate); + +/** + * usb_state_string - Returns human readable name for the state. + * @state: The state to return a human-readable name for. If it's not + * any of the states devices in usb_device_state_string enum, + * the string UNKNOWN will be returned. + */ +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] = "addressed", + [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); + +static const char *const usb_dr_modes[] = { + [USB_DR_MODE_UNKNOWN] = "", + [USB_DR_MODE_HOST] = "host", + [USB_DR_MODE_PERIPHERAL] = "peripheral", + [USB_DR_MODE_OTG] = "otg", +}; + +/** + * usb_get_dr_mode_from_string() - Get dual role mode for given string + * @str: String to find the corresponding dual role mode for + * + * This function performs a lookup for the given string and returns the + * corresponding enum usb_dr_mode. If no match for the string could be found, + * 'USB_DR_MODE_UNKNOWN' is returned. + */ +static enum usb_dr_mode usb_get_dr_mode_from_string(const char *str) +{ + int ret; + + ret = match_string(usb_dr_modes, ARRAY_SIZE(usb_dr_modes), str); + return (ret < 0) ? USB_DR_MODE_UNKNOWN : ret; +} + +enum usb_dr_mode usb_get_dr_mode(struct device *dev) +{ + const char *dr_mode; + int err; + + err = device_property_read_string(dev, "dr_mode", &dr_mode); + if (err < 0) + return USB_DR_MODE_UNKNOWN; + + return usb_get_dr_mode_from_string(dr_mode); +} +EXPORT_SYMBOL_GPL(usb_get_dr_mode); + +/** + * usb_get_role_switch_default_mode - Get default mode for given device + * @dev: Pointer to the given device + * + * The function gets string from property 'role-switch-default-mode', + * and returns the corresponding enum usb_dr_mode. + */ +enum usb_dr_mode usb_get_role_switch_default_mode(struct device *dev) +{ + const char *str; + int ret; + + ret = device_property_read_string(dev, "role-switch-default-mode", &str); + if (ret < 0) + return USB_DR_MODE_UNKNOWN; + + return usb_get_dr_mode_from_string(str); +} +EXPORT_SYMBOL_GPL(usb_get_role_switch_default_mode); + +/** + * usb_decode_interval - Decode bInterval into the time expressed in 1us unit + * @epd: The descriptor of the endpoint + * @speed: The speed that the endpoint works as + * + * Function returns the interval expressed in 1us unit for servicing + * endpoint for data transfers. + */ +unsigned int usb_decode_interval(const struct usb_endpoint_descriptor *epd, + enum usb_device_speed speed) +{ + unsigned int interval = 0; + + switch (usb_endpoint_type(epd)) { + case USB_ENDPOINT_XFER_CONTROL: + /* uframes per NAK */ + if (speed == USB_SPEED_HIGH) + interval = epd->bInterval; + break; + case USB_ENDPOINT_XFER_ISOC: + interval = 1 << (epd->bInterval - 1); + break; + case USB_ENDPOINT_XFER_BULK: + /* uframes per NAK */ + if (speed == USB_SPEED_HIGH && usb_endpoint_dir_out(epd)) + interval = epd->bInterval; + break; + case USB_ENDPOINT_XFER_INT: + if (speed >= USB_SPEED_HIGH) + interval = 1 << (epd->bInterval - 1); + else + interval = epd->bInterval; + break; + } + + interval *= (speed >= USB_SPEED_HIGH) ? 125 : 1000; + + return interval; +} +EXPORT_SYMBOL_GPL(usb_decode_interval); + +#ifdef CONFIG_OF +/** + * of_usb_get_dr_mode_by_phy - Get dual role mode for the controller device + * which is associated with the given phy device_node + * @np: Pointer to the given phy device_node + * @arg0: phandle args[0] for phy's with #phy-cells >= 1, or -1 for + * phys which do not have phy-cells + * + * In dts a usb controller associates with phy devices. The function gets + * the string from property 'dr_mode' of the controller associated with the + * given phy device node, and returns the correspondig enum usb_dr_mode. + */ +enum usb_dr_mode of_usb_get_dr_mode_by_phy(struct device_node *np, int arg0) +{ + struct device_node *controller; + struct of_phandle_args args; + const char *dr_mode; + int index; + int err; + + for_each_node_with_property(controller, "phys") { + if (!of_device_is_available(controller)) + continue; + index = 0; + do { + if (arg0 == -1) { + args.np = of_parse_phandle(controller, "phys", + index); + args.args_count = 0; + } else { + err = of_parse_phandle_with_args(controller, + "phys", "#phy-cells", + index, &args); + if (err) + break; + } + + of_node_put(args.np); + if (args.np == np && (args.args_count == 0 || + args.args[0] == arg0)) + goto finish; + index++; + } while (args.np); + } + +finish: + err = of_property_read_string(controller, "dr_mode", &dr_mode); + of_node_put(controller); + + if (err < 0) + return USB_DR_MODE_UNKNOWN; + + return usb_get_dr_mode_from_string(dr_mode); +} +EXPORT_SYMBOL_GPL(of_usb_get_dr_mode_by_phy); + +/** + * of_usb_host_tpl_support - to get if Targeted Peripheral List is supported + * for given targeted hosts (non-PC hosts) + * @np: Pointer to the given device_node + * + * The function gets if the targeted hosts support TPL or not + */ +bool of_usb_host_tpl_support(struct device_node *np) +{ + return of_property_read_bool(np, "tpl-support"); +} +EXPORT_SYMBOL_GPL(of_usb_host_tpl_support); + +/** + * of_usb_update_otg_caps - to update usb otg capabilities according to + * the passed properties in DT. + * @np: Pointer to the given device_node + * @otg_caps: Pointer to the target usb_otg_caps to be set + * + * The function updates the otg capabilities + */ +int of_usb_update_otg_caps(struct device_node *np, + struct usb_otg_caps *otg_caps) +{ + u32 otg_rev; + + if (!otg_caps) + return -EINVAL; + + if (!of_property_read_u32(np, "otg-rev", &otg_rev)) { + switch (otg_rev) { + case 0x0100: + case 0x0120: + case 0x0130: + case 0x0200: + /* Choose the lesser one if it's already been set */ + if (otg_caps->otg_rev) + otg_caps->otg_rev = min_t(u16, otg_rev, + otg_caps->otg_rev); + else + otg_caps->otg_rev = otg_rev; + break; + default: + pr_err("%pOF: unsupported otg-rev: 0x%x\n", + np, otg_rev); + return -EINVAL; + } + } else { + /* + * otg-rev is mandatory for otg properties, if not passed + * we set it to be 0 and assume it's a legacy otg device. + * Non-dt platform can set it afterwards. + */ + otg_caps->otg_rev = 0; + } + + if (of_property_read_bool(np, "hnp-disable")) + otg_caps->hnp_support = false; + if (of_property_read_bool(np, "srp-disable")) + otg_caps->srp_support = false; + if (of_property_read_bool(np, "adp-disable") || + (otg_caps->otg_rev < 0x0200)) + otg_caps->adp_support = false; + + return 0; +} +EXPORT_SYMBOL_GPL(of_usb_update_otg_caps); + +/** + * usb_of_get_companion_dev - Find the companion device + * @dev: the device pointer to find a companion + * + * Find the companion device from platform bus. + * + * Takes a reference to the returned struct device which needs to be dropped + * after use. + * + * Return: On success, a pointer to the companion device, %NULL on failure. + */ +struct device *usb_of_get_companion_dev(struct device *dev) +{ + struct device_node *node; + struct platform_device *pdev = NULL; + + node = of_parse_phandle(dev->of_node, "companion", 0); + if (node) + pdev = of_find_device_by_node(node); + + of_node_put(node); + + return pdev ? &pdev->dev : NULL; +} +EXPORT_SYMBOL_GPL(usb_of_get_companion_dev); +#endif + +struct dentry *usb_debug_root; +EXPORT_SYMBOL_GPL(usb_debug_root); + +DEFINE_MUTEX(usb_dynids_lock); +EXPORT_SYMBOL_GPL(usb_dynids_lock); + +static int __init usb_common_init(void) +{ + usb_debug_root = debugfs_create_dir("usb", NULL); + ledtrig_usb_init(); + return 0; +} + +static void __exit usb_common_exit(void) +{ + ledtrig_usb_exit(); + debugfs_remove_recursive(usb_debug_root); +} + +subsys_initcall(usb_common_init); +module_exit(usb_common_exit); + +MODULE_DESCRIPTION("Common code for host and device side USB"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/common/common.h b/drivers/usb/common/common.h new file mode 100644 index 000000000..424a91316 --- /dev/null +++ b/drivers/usb/common/common.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __LINUX_USB_COMMON_H +#define __LINUX_USB_COMMON_H + +#if defined(CONFIG_USB_LED_TRIG) +void ledtrig_usb_init(void); +void ledtrig_usb_exit(void); +#else +static inline void ledtrig_usb_init(void) { } +static inline void ledtrig_usb_exit(void) { } +#endif + +#endif /* __LINUX_USB_COMMON_H */ diff --git a/drivers/usb/common/debug.c b/drivers/usb/common/debug.c new file mode 100644 index 000000000..f204cec8d --- /dev/null +++ b/drivers/usb/common/debug.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common USB debugging functions + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + */ + +#include +#include + +static void usb_decode_get_status(__u8 bRequestType, __u16 wIndex, + __u16 wLength, char *str, size_t size) +{ + switch (bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + snprintf(str, size, "Get Device Status(Length = %d)", wLength); + break; + case USB_RECIP_INTERFACE: + snprintf(str, size, + "Get Interface Status(Intf = %d, Length = %d)", + wIndex, wLength); + break; + case USB_RECIP_ENDPOINT: + snprintf(str, size, "Get Endpoint Status(ep%d%s)", + wIndex & ~USB_DIR_IN, + wIndex & USB_DIR_IN ? "in" : "out"); + break; + } +} + +static const char *usb_decode_device_feature(u16 wValue) +{ + switch (wValue) { + case USB_DEVICE_SELF_POWERED: + return "Self Powered"; + case USB_DEVICE_REMOTE_WAKEUP: + return "Remote Wakeup"; + case USB_DEVICE_TEST_MODE: + return "Test Mode"; + case USB_DEVICE_U1_ENABLE: + return "U1 Enable"; + case USB_DEVICE_U2_ENABLE: + return "U2 Enable"; + case USB_DEVICE_LTM_ENABLE: + return "LTM Enable"; + default: + return "UNKNOWN"; + } +} + +static const char *usb_decode_test_mode(u16 wIndex) +{ + switch (wIndex) { + case USB_TEST_J: + return ": TEST_J"; + case USB_TEST_K: + return ": TEST_K"; + case USB_TEST_SE0_NAK: + return ": TEST_SE0_NAK"; + case USB_TEST_PACKET: + return ": TEST_PACKET"; + case USB_TEST_FORCE_ENABLE: + return ": TEST_FORCE_EN"; + default: + return ": UNKNOWN"; + } +} + +static void usb_decode_set_clear_feature(__u8 bRequestType, + __u8 bRequest, __u16 wValue, + __u16 wIndex, char *str, size_t size) +{ + switch (bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + snprintf(str, size, "%s Device Feature(%s%s)", + bRequest == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set", + usb_decode_device_feature(wValue), + wValue == USB_DEVICE_TEST_MODE ? + usb_decode_test_mode(wIndex) : ""); + break; + case USB_RECIP_INTERFACE: + snprintf(str, size, "%s Interface Feature(%s)", + bRequest == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set", + wValue == USB_INTRF_FUNC_SUSPEND ? + "Function Suspend" : "UNKNOWN"); + break; + case USB_RECIP_ENDPOINT: + snprintf(str, size, "%s Endpoint Feature(%s ep%d%s)", + bRequest == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set", + wValue == USB_ENDPOINT_HALT ? "Halt" : "UNKNOWN", + wIndex & ~USB_DIR_IN, + wIndex & USB_DIR_IN ? "in" : "out"); + break; + } +} + +static void usb_decode_set_address(__u16 wValue, char *str, size_t size) +{ + snprintf(str, size, "Set Address(Addr = %02x)", wValue); +} + +static void usb_decode_get_set_descriptor(__u8 bRequestType, __u8 bRequest, + __u16 wValue, __u16 wIndex, + __u16 wLength, char *str, size_t size) +{ + char *s; + + switch (wValue >> 8) { + case USB_DT_DEVICE: + s = "Device"; + break; + case USB_DT_CONFIG: + s = "Configuration"; + break; + case USB_DT_STRING: + s = "String"; + break; + case USB_DT_INTERFACE: + s = "Interface"; + break; + case USB_DT_ENDPOINT: + s = "Endpoint"; + break; + case USB_DT_DEVICE_QUALIFIER: + s = "Device Qualifier"; + break; + case USB_DT_OTHER_SPEED_CONFIG: + s = "Other Speed Config"; + break; + case USB_DT_INTERFACE_POWER: + s = "Interface Power"; + break; + case USB_DT_OTG: + s = "OTG"; + break; + case USB_DT_DEBUG: + s = "Debug"; + break; + case USB_DT_INTERFACE_ASSOCIATION: + s = "Interface Association"; + break; + case USB_DT_BOS: + s = "BOS"; + break; + case USB_DT_DEVICE_CAPABILITY: + s = "Device Capability"; + break; + case USB_DT_PIPE_USAGE: + s = "Pipe Usage"; + break; + case USB_DT_SS_ENDPOINT_COMP: + s = "SS Endpoint Companion"; + break; + case USB_DT_SSP_ISOC_ENDPOINT_COMP: + s = "SSP Isochronous Endpoint Companion"; + break; + default: + s = "UNKNOWN"; + break; + } + + snprintf(str, size, "%s %s Descriptor(Index = %d, Length = %d)", + bRequest == USB_REQ_GET_DESCRIPTOR ? "Get" : "Set", + s, wValue & 0xff, wLength); +} + +static void usb_decode_get_configuration(__u16 wLength, char *str, size_t size) +{ + snprintf(str, size, "Get Configuration(Length = %d)", wLength); +} + +static void usb_decode_set_configuration(__u8 wValue, char *str, size_t size) +{ + snprintf(str, size, "Set Configuration(Config = %d)", wValue); +} + +static void usb_decode_get_intf(__u16 wIndex, __u16 wLength, char *str, + size_t size) +{ + snprintf(str, size, "Get Interface(Intf = %d, Length = %d)", + wIndex, wLength); +} + +static void usb_decode_set_intf(__u8 wValue, __u16 wIndex, char *str, + size_t size) +{ + snprintf(str, size, "Set Interface(Intf = %d, Alt.Setting = %d)", + wIndex, wValue); +} + +static void usb_decode_synch_frame(__u16 wIndex, __u16 wLength, + char *str, size_t size) +{ + snprintf(str, size, "Synch Frame(Endpoint = %d, Length = %d)", + wIndex, wLength); +} + +static void usb_decode_set_sel(__u16 wLength, char *str, size_t size) +{ + snprintf(str, size, "Set SEL(Length = %d)", wLength); +} + +static void usb_decode_set_isoch_delay(__u8 wValue, char *str, size_t size) +{ + snprintf(str, size, "Set Isochronous Delay(Delay = %d ns)", wValue); +} + +static void usb_decode_ctrl_generic(char *str, size_t size, __u8 bRequestType, + __u8 bRequest, __u16 wValue, __u16 wIndex, + __u16 wLength) +{ + u8 recip = bRequestType & USB_RECIP_MASK; + u8 type = bRequestType & USB_TYPE_MASK; + + snprintf(str, size, + "Type=%s Recipient=%s Dir=%s bRequest=%u wValue=%u wIndex=%u wLength=%u", + (type == USB_TYPE_STANDARD) ? "Standard" : + (type == USB_TYPE_VENDOR) ? "Vendor" : + (type == USB_TYPE_CLASS) ? "Class" : "Unknown", + (recip == USB_RECIP_DEVICE) ? "Device" : + (recip == USB_RECIP_INTERFACE) ? "Interface" : + (recip == USB_RECIP_ENDPOINT) ? "Endpoint" : "Unknown", + (bRequestType & USB_DIR_IN) ? "IN" : "OUT", + bRequest, wValue, wIndex, wLength); +} + +static void usb_decode_ctrl_standard(char *str, size_t size, __u8 bRequestType, + __u8 bRequest, __u16 wValue, __u16 wIndex, + __u16 wLength) +{ + switch (bRequest) { + case USB_REQ_GET_STATUS: + usb_decode_get_status(bRequestType, wIndex, wLength, str, size); + break; + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + usb_decode_set_clear_feature(bRequestType, bRequest, wValue, + wIndex, str, size); + break; + case USB_REQ_SET_ADDRESS: + usb_decode_set_address(wValue, str, size); + break; + case USB_REQ_GET_DESCRIPTOR: + case USB_REQ_SET_DESCRIPTOR: + usb_decode_get_set_descriptor(bRequestType, bRequest, wValue, + wIndex, wLength, str, size); + break; + case USB_REQ_GET_CONFIGURATION: + usb_decode_get_configuration(wLength, str, size); + break; + case USB_REQ_SET_CONFIGURATION: + usb_decode_set_configuration(wValue, str, size); + break; + case USB_REQ_GET_INTERFACE: + usb_decode_get_intf(wIndex, wLength, str, size); + break; + case USB_REQ_SET_INTERFACE: + usb_decode_set_intf(wValue, wIndex, str, size); + break; + case USB_REQ_SYNCH_FRAME: + usb_decode_synch_frame(wIndex, wLength, str, size); + break; + case USB_REQ_SET_SEL: + usb_decode_set_sel(wLength, str, size); + break; + case USB_REQ_SET_ISOCH_DELAY: + usb_decode_set_isoch_delay(wValue, str, size); + break; + default: + usb_decode_ctrl_generic(str, size, bRequestType, bRequest, + wValue, wIndex, wLength); + break; + } +} + +/** + * usb_decode_ctrl - Returns human readable representation of control request. + * @str: buffer to return a human-readable representation of control request. + * This buffer should have about 200 bytes. + * @size: size of str buffer. + * @bRequestType: matches the USB bmRequestType field + * @bRequest: matches the USB bRequest field + * @wValue: matches the USB wValue field (CPU byte order) + * @wIndex: matches the USB wIndex field (CPU byte order) + * @wLength: matches the USB wLength field (CPU byte order) + * + * Function returns decoded, formatted and human-readable description of + * control request packet. + * + * The usage scenario for this is for tracepoints, so function as a return + * use the same value as in parameters. This approach allows to use this + * function in TP_printk + * + * Important: wValue, wIndex, wLength parameters before invoking this function + * should be processed by le16_to_cpu macro. + */ +const char *usb_decode_ctrl(char *str, size_t size, __u8 bRequestType, + __u8 bRequest, __u16 wValue, __u16 wIndex, + __u16 wLength) +{ + switch (bRequestType & USB_TYPE_MASK) { + case USB_TYPE_STANDARD: + usb_decode_ctrl_standard(str, size, bRequestType, bRequest, + wValue, wIndex, wLength); + break; + case USB_TYPE_VENDOR: + case USB_TYPE_CLASS: + default: + usb_decode_ctrl_generic(str, size, bRequestType, bRequest, + wValue, wIndex, wLength); + break; + } + + return str; +} +EXPORT_SYMBOL_GPL(usb_decode_ctrl); diff --git a/drivers/usb/common/led.c b/drivers/usb/common/led.c new file mode 100644 index 000000000..1de18d90b --- /dev/null +++ b/drivers/usb/common/led.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LED Triggers for USB Activity + * + * Copyright 2014 Michal Sojka + */ + +#include +#include +#include +#include +#include +#include "common.h" + +#define BLINK_DELAY 30 + +DEFINE_LED_TRIGGER(ledtrig_usb_gadget); +DEFINE_LED_TRIGGER(ledtrig_usb_host); + +void usb_led_activity(enum usb_led_event ev) +{ + struct led_trigger *trig = NULL; + + switch (ev) { + case USB_LED_EVENT_GADGET: + trig = ledtrig_usb_gadget; + break; + case USB_LED_EVENT_HOST: + trig = ledtrig_usb_host; + break; + } + /* led_trigger_blink_oneshot() handles trig == NULL gracefully */ + led_trigger_blink_oneshot(trig, BLINK_DELAY, BLINK_DELAY, 0); +} +EXPORT_SYMBOL_GPL(usb_led_activity); + + +void __init ledtrig_usb_init(void) +{ + led_trigger_register_simple("usb-gadget", &ledtrig_usb_gadget); + led_trigger_register_simple("usb-host", &ledtrig_usb_host); +} + +void __exit ledtrig_usb_exit(void) +{ + led_trigger_unregister_simple(ledtrig_usb_gadget); + led_trigger_unregister_simple(ledtrig_usb_host); +} diff --git a/drivers/usb/common/ulpi.c b/drivers/usb/common/ulpi.c new file mode 100644 index 000000000..7e43429e9 --- /dev/null +++ b/drivers/usb/common/ulpi.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ulpi.c - USB ULPI PHY bus + * + * Copyright (C) 2015 Intel Corporation + * + * Author: Heikki Krogerus + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* -------------------------------------------------------------------------- */ + +int ulpi_read(struct ulpi *ulpi, u8 addr) +{ + return ulpi->ops->read(ulpi->dev.parent, addr); +} +EXPORT_SYMBOL_GPL(ulpi_read); + +int ulpi_write(struct ulpi *ulpi, u8 addr, u8 val) +{ + return ulpi->ops->write(ulpi->dev.parent, addr, val); +} +EXPORT_SYMBOL_GPL(ulpi_write); + +/* -------------------------------------------------------------------------- */ + +static int ulpi_match(struct device *dev, const struct device_driver *driver) +{ + struct ulpi_driver *drv = to_ulpi_driver(driver); + struct ulpi *ulpi = to_ulpi_dev(dev); + const struct ulpi_device_id *id; + + /* + * Some ULPI devices don't have a vendor id + * or provide an id_table so rely on OF match. + */ + if (ulpi->id.vendor == 0 || !drv->id_table) + return of_driver_match_device(dev, driver); + + for (id = drv->id_table; id->vendor; id++) + if (id->vendor == ulpi->id.vendor && + id->product == ulpi->id.product) + return 1; + + return 0; +} + +static int ulpi_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + const struct ulpi *ulpi = to_ulpi_dev(dev); + int ret; + + ret = of_device_uevent_modalias(dev, env); + if (ret != -ENODEV) + return ret; + + if (add_uevent_var(env, "MODALIAS=ulpi:v%04xp%04x", + ulpi->id.vendor, ulpi->id.product)) + return -ENOMEM; + return 0; +} + +static int ulpi_probe(struct device *dev) +{ + struct ulpi_driver *drv = to_ulpi_driver(dev->driver); + int ret; + + ret = of_clk_set_defaults(dev->of_node, false); + if (ret < 0) + return ret; + + return drv->probe(to_ulpi_dev(dev)); +} + +static void ulpi_remove(struct device *dev) +{ + struct ulpi_driver *drv = to_ulpi_driver(dev->driver); + + if (drv->remove) + drv->remove(to_ulpi_dev(dev)); +} + +static const struct bus_type ulpi_bus = { + .name = "ulpi", + .match = ulpi_match, + .uevent = ulpi_uevent, + .probe = ulpi_probe, + .remove = ulpi_remove, +}; + +/* -------------------------------------------------------------------------- */ + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int len; + struct ulpi *ulpi = to_ulpi_dev(dev); + + len = of_device_modalias(dev, buf, PAGE_SIZE); + if (len != -ENODEV) + return len; + + return sprintf(buf, "ulpi:v%04xp%04x\n", + ulpi->id.vendor, ulpi->id.product); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *ulpi_dev_attrs[] = { + &dev_attr_modalias.attr, + NULL +}; + +static const struct attribute_group ulpi_dev_attr_group = { + .attrs = ulpi_dev_attrs, +}; + +static const struct attribute_group *ulpi_dev_attr_groups[] = { + &ulpi_dev_attr_group, + NULL +}; + +static void ulpi_dev_release(struct device *dev) +{ + of_node_put(dev->of_node); + kfree(to_ulpi_dev(dev)); +} + +static const struct device_type ulpi_dev_type = { + .name = "ulpi_device", + .groups = ulpi_dev_attr_groups, + .release = ulpi_dev_release, +}; + +/* -------------------------------------------------------------------------- */ + +/** + * __ulpi_register_driver - register a driver with the ULPI bus + * @drv: driver being registered + * @module: ends up being THIS_MODULE + * + * Registers a driver with the ULPI bus. + */ +int __ulpi_register_driver(struct ulpi_driver *drv, struct module *module) +{ + if (!drv->probe) + return -EINVAL; + + drv->driver.owner = module; + drv->driver.bus = &ulpi_bus; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__ulpi_register_driver); + +/** + * ulpi_unregister_driver - unregister a driver with the ULPI bus + * @drv: driver to unregister + * + * Unregisters a driver with the ULPI bus. + */ +void ulpi_unregister_driver(struct ulpi_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(ulpi_unregister_driver); + +/* -------------------------------------------------------------------------- */ + +static int ulpi_of_register(struct ulpi *ulpi) +{ + struct device_node *np = NULL, *child; + struct device *parent; + + /* Find a ulpi bus underneath the parent or the grandparent */ + parent = ulpi->dev.parent; + if (parent->of_node) + np = of_get_child_by_name(parent->of_node, "ulpi"); + else if (parent->parent && parent->parent->of_node) + np = of_get_child_by_name(parent->parent->of_node, "ulpi"); + if (!np) + return 0; + + child = of_get_next_available_child(np, NULL); + of_node_put(np); + if (!child) + return -EINVAL; + + ulpi->dev.of_node = child; + + return 0; +} + +static int ulpi_read_id(struct ulpi *ulpi) +{ + int ret; + + /* Test the interface */ + ret = ulpi_write(ulpi, ULPI_SCRATCH, 0xaa); + if (ret < 0) + goto err; + + ret = ulpi_read(ulpi, ULPI_SCRATCH); + if (ret < 0) + return ret; + + if (ret != 0xaa) + goto err; + + ulpi->id.vendor = ulpi_read(ulpi, ULPI_VENDOR_ID_LOW); + ulpi->id.vendor |= ulpi_read(ulpi, ULPI_VENDOR_ID_HIGH) << 8; + + ulpi->id.product = ulpi_read(ulpi, ULPI_PRODUCT_ID_LOW); + ulpi->id.product |= ulpi_read(ulpi, ULPI_PRODUCT_ID_HIGH) << 8; + + /* Some ULPI devices don't have a vendor id so rely on OF match */ + if (ulpi->id.vendor == 0) + goto err; + + request_module("ulpi:v%04xp%04x", ulpi->id.vendor, ulpi->id.product); + return 0; +err: + of_request_module(ulpi->dev.of_node); + return 0; +} + +static int ulpi_regs_show(struct seq_file *seq, void *data) +{ + struct ulpi *ulpi = seq->private; + +#define ulpi_print(name, reg) do { \ + int ret = ulpi_read(ulpi, reg); \ + if (ret < 0) \ + return ret; \ + seq_printf(seq, name " %.02x\n", ret); \ +} while (0) + + ulpi_print("Vendor ID Low ", ULPI_VENDOR_ID_LOW); + ulpi_print("Vendor ID High ", ULPI_VENDOR_ID_HIGH); + ulpi_print("Product ID Low ", ULPI_PRODUCT_ID_LOW); + ulpi_print("Product ID High ", ULPI_PRODUCT_ID_HIGH); + ulpi_print("Function Control ", ULPI_FUNC_CTRL); + ulpi_print("Interface Control ", ULPI_IFC_CTRL); + ulpi_print("OTG Control ", ULPI_OTG_CTRL); + ulpi_print("USB Interrupt Enable Rising ", ULPI_USB_INT_EN_RISE); + ulpi_print("USB Interrupt Enable Falling", ULPI_USB_INT_EN_FALL); + ulpi_print("USB Interrupt Status ", ULPI_USB_INT_STS); + ulpi_print("USB Interrupt Latch ", ULPI_USB_INT_LATCH); + ulpi_print("Debug ", ULPI_DEBUG); + ulpi_print("Scratch Register ", ULPI_SCRATCH); + ulpi_print("Carkit Control ", ULPI_CARKIT_CTRL); + ulpi_print("Carkit Interrupt Delay ", ULPI_CARKIT_INT_DELAY); + ulpi_print("Carkit Interrupt Enable ", ULPI_CARKIT_INT_EN); + ulpi_print("Carkit Interrupt Status ", ULPI_CARKIT_INT_STS); + ulpi_print("Carkit Interrupt Latch ", ULPI_CARKIT_INT_LATCH); + ulpi_print("Carkit Pulse Control ", ULPI_CARKIT_PLS_CTRL); + ulpi_print("Transmit Positive Width ", ULPI_TX_POS_WIDTH); + ulpi_print("Transmit Negative Width ", ULPI_TX_NEG_WIDTH); + ulpi_print("Receive Polarity Recovery ", ULPI_POLARITY_RECOVERY); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ulpi_regs); + +static struct dentry *ulpi_root; + +static int ulpi_register(struct device *dev, struct ulpi *ulpi) +{ + int ret; + struct dentry *root; + + ulpi->dev.parent = dev; /* needed early for ops */ + ulpi->dev.bus = &ulpi_bus; + ulpi->dev.type = &ulpi_dev_type; + + device_initialize(&ulpi->dev); + + dev_set_name(&ulpi->dev, "%s.ulpi", dev_name(dev)); + + ACPI_COMPANION_SET(&ulpi->dev, ACPI_COMPANION(dev)); + + ret = ulpi_of_register(ulpi); + if (ret) + return ret; + + ret = ulpi_read_id(ulpi); + if (ret) + return ret; + + ret = device_add(&ulpi->dev); + if (ret) + return ret; + + root = debugfs_create_dir(dev_name(&ulpi->dev), ulpi_root); + debugfs_create_file("regs", 0444, root, ulpi, &ulpi_regs_fops); + + dev_dbg(&ulpi->dev, "registered ULPI PHY: vendor %04x, product %04x\n", + ulpi->id.vendor, ulpi->id.product); + + return 0; +} + +/** + * ulpi_register_interface - instantiate new ULPI device + * @dev: USB controller's device interface + * @ops: ULPI register access + * + * Allocates and registers a ULPI device and an interface for it. Called from + * the USB controller that provides the ULPI interface. + */ +struct ulpi *ulpi_register_interface(struct device *dev, + const struct ulpi_ops *ops) +{ + struct ulpi *ulpi; + int ret; + + ulpi = kzalloc_obj(*ulpi); + if (!ulpi) + return ERR_PTR(-ENOMEM); + + ulpi->ops = ops; + + ret = ulpi_register(dev, ulpi); + if (ret) { + put_device(&ulpi->dev); + return ERR_PTR(ret); + } + + return ulpi; +} +EXPORT_SYMBOL_GPL(ulpi_register_interface); + +/** + * ulpi_unregister_interface - unregister ULPI interface + * @ulpi: struct ulpi_interface + * + * Unregisters a ULPI device and it's interface that was created with + * ulpi_create_interface(). + */ +void ulpi_unregister_interface(struct ulpi *ulpi) +{ + debugfs_lookup_and_remove(dev_name(&ulpi->dev), ulpi_root); + device_unregister(&ulpi->dev); +} +EXPORT_SYMBOL_GPL(ulpi_unregister_interface); + +/* -------------------------------------------------------------------------- */ + +static int __init ulpi_init(void) +{ + int ret; + + ulpi_root = debugfs_create_dir(KBUILD_MODNAME, NULL); + ret = bus_register(&ulpi_bus); + if (ret) + debugfs_remove(ulpi_root); + return ret; +} +subsys_initcall(ulpi_init); + +static void __exit ulpi_exit(void) +{ + bus_unregister(&ulpi_bus); + debugfs_remove(ulpi_root); +} +module_exit(ulpi_exit); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB ULPI PHY bus"); diff --git a/drivers/usb/common/usb-conn-gpio.c b/drivers/usb/common/usb-conn-gpio.c new file mode 100644 index 000000000..421c3af38 --- /dev/null +++ b/drivers/usb/common/usb-conn-gpio.c @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB GPIO Based Connection Detection Driver + * + * Copyright (C) 2019 MediaTek Inc. + * + * Author: Chunfeng Yun + * + * Some code borrowed from drivers/extcon/extcon-usb-gpio.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_IDA(usb_conn_ida); + +#define USB_GPIO_DEB_MS 20 /* ms */ +#define USB_GPIO_DEB_US ((USB_GPIO_DEB_MS) * 1000) /* us */ + +#define USB_CONN_IRQF \ + (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT) + +struct usb_conn_info { + struct device *dev; + int conn_id; /* store the IDA-allocated ID */ + struct usb_role_switch *role_sw; + enum usb_role last_role; + struct regulator *vbus; + struct delayed_work dw_det; + unsigned long debounce_jiffies; + + struct gpio_desc *id_gpiod; + struct gpio_desc *vbus_gpiod; + int id_irq; + int vbus_irq; + + struct power_supply_desc desc; + struct power_supply *charger; + bool initial_detection; +}; + +/* + * "DEVICE" = VBUS and "HOST" = !ID, so we have: + * Both "DEVICE" and "HOST" can't be set as active at the same time + * so if "HOST" is active (i.e. ID is 0) we keep "DEVICE" inactive + * even if VBUS is on. + * + * Role | ID | VBUS + * ------------------------------------ + * [1] DEVICE | H | H + * [2] NONE | H | L + * [3] HOST | L | H + * [4] HOST | L | L + * + * In case we have only one of these signals: + * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1 + * - ID only - we want to distinguish between [1] and [4], so VBUS = ID + */ +static void usb_conn_detect_cable(struct work_struct *work) +{ + struct usb_conn_info *info; + enum usb_role role; + int id, vbus, ret; + + info = container_of(to_delayed_work(work), + struct usb_conn_info, dw_det); + + /* check ID and VBUS */ + id = info->id_gpiod ? + gpiod_get_value_cansleep(info->id_gpiod) : 1; + vbus = info->vbus_gpiod ? + gpiod_get_value_cansleep(info->vbus_gpiod) : id; + + if (!id) + role = USB_ROLE_HOST; + else if (vbus) + role = USB_ROLE_DEVICE; + else + role = USB_ROLE_NONE; + + dev_dbg(info->dev, "role %s -> %s, gpios: id %d, vbus %d\n", + usb_role_string(info->last_role), usb_role_string(role), id, vbus); + + if (!info->initial_detection && info->last_role == role) { + dev_warn(info->dev, "repeated role: %s\n", usb_role_string(role)); + return; + } + + info->initial_detection = false; + + if (info->last_role == USB_ROLE_HOST && info->vbus) + regulator_disable(info->vbus); + + ret = usb_role_switch_set_role(info->role_sw, role); + if (ret) + dev_err(info->dev, "failed to set role: %d\n", ret); + + if (role == USB_ROLE_HOST && info->vbus) { + ret = regulator_enable(info->vbus); + if (ret) + dev_err(info->dev, "enable vbus regulator failed\n"); + } + + info->last_role = role; + + if (info->vbus) + dev_dbg(info->dev, "vbus regulator is %s\n", + str_enabled_disabled(regulator_is_enabled(info->vbus))); + + power_supply_changed(info->charger); +} + +static void usb_conn_queue_dwork(struct usb_conn_info *info, + unsigned long delay) +{ + queue_delayed_work(system_power_efficient_wq, &info->dw_det, delay); +} + +static irqreturn_t usb_conn_isr(int irq, void *dev_id) +{ + struct usb_conn_info *info = dev_id; + + usb_conn_queue_dwork(info, info->debounce_jiffies); + + return IRQ_HANDLED; +} + +static enum power_supply_property usb_charger_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int usb_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct usb_conn_info *info = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = info->last_role == USB_ROLE_DEVICE; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int usb_conn_psy_register(struct usb_conn_info *info) +{ + struct device *dev = info->dev; + struct power_supply_desc *desc = &info->desc; + struct power_supply_config cfg = { + .fwnode = dev_fwnode(dev), + }; + + info->conn_id = ida_alloc(&usb_conn_ida, GFP_KERNEL); + if (info->conn_id < 0) + return info->conn_id; + + desc->name = devm_kasprintf(dev, GFP_KERNEL, "usb-charger-%d", + info->conn_id); + if (!desc->name) { + ida_free(&usb_conn_ida, info->conn_id); + return -ENOMEM; + } + + desc->properties = usb_charger_properties; + desc->num_properties = ARRAY_SIZE(usb_charger_properties); + desc->get_property = usb_charger_get_property; + desc->type = POWER_SUPPLY_TYPE_USB; + cfg.drv_data = info; + + info->charger = devm_power_supply_register(dev, desc, &cfg); + if (IS_ERR(info->charger)) { + dev_err(dev, "Unable to register charger %d\n", info->conn_id); + ida_free(&usb_conn_ida, info->conn_id); + } + + return PTR_ERR_OR_ZERO(info->charger); +} + +static int usb_conn_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usb_conn_info *info; + int ret = 0; + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = dev; + info->id_gpiod = devm_gpiod_get_optional(dev, "id", GPIOD_IN); + if (IS_ERR(info->id_gpiod)) + return PTR_ERR(info->id_gpiod); + + info->vbus_gpiod = devm_gpiod_get_optional(dev, "vbus", GPIOD_IN); + if (IS_ERR(info->vbus_gpiod)) + return PTR_ERR(info->vbus_gpiod); + + if (!info->id_gpiod && !info->vbus_gpiod) { + dev_err(dev, "failed to get gpios\n"); + return -ENODEV; + } + + if (info->id_gpiod) + ret = gpiod_set_debounce(info->id_gpiod, USB_GPIO_DEB_US); + if (!ret && info->vbus_gpiod) + ret = gpiod_set_debounce(info->vbus_gpiod, USB_GPIO_DEB_US); + if (ret < 0) + info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEB_MS); + + INIT_DELAYED_WORK(&info->dw_det, usb_conn_detect_cable); + + info->vbus = devm_regulator_get_optional(dev, "vbus"); + if (PTR_ERR(info->vbus) == -ENODEV) + info->vbus = NULL; + + if (IS_ERR(info->vbus)) + return dev_err_probe(dev, PTR_ERR(info->vbus), "failed to get vbus\n"); + + info->role_sw = usb_role_switch_get(dev); + if (IS_ERR(info->role_sw)) + return dev_err_probe(dev, PTR_ERR(info->role_sw), + "failed to get role switch\n"); + + ret = usb_conn_psy_register(info); + if (ret) + goto put_role_sw; + + if (info->id_gpiod) { + info->id_irq = gpiod_to_irq(info->id_gpiod); + if (info->id_irq < 0) { + dev_err(dev, "failed to get ID IRQ\n"); + ret = info->id_irq; + goto put_role_sw; + } + + ret = devm_request_threaded_irq(dev, info->id_irq, NULL, + usb_conn_isr, USB_CONN_IRQF, + pdev->name, info); + if (ret < 0) { + dev_err(dev, "failed to request ID IRQ\n"); + goto put_role_sw; + } + } + + if (info->vbus_gpiod) { + info->vbus_irq = gpiod_to_irq(info->vbus_gpiod); + if (info->vbus_irq < 0) { + dev_err(dev, "failed to get VBUS IRQ\n"); + ret = info->vbus_irq; + goto put_role_sw; + } + + ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL, + usb_conn_isr, USB_CONN_IRQF, + pdev->name, info); + if (ret < 0) { + dev_err(dev, "failed to request VBUS IRQ\n"); + goto put_role_sw; + } + } + + platform_set_drvdata(pdev, info); + device_set_wakeup_capable(&pdev->dev, true); + + /* Perform initial detection */ + info->initial_detection = true; + usb_conn_queue_dwork(info, 0); + + return 0; + +put_role_sw: + usb_role_switch_put(info->role_sw); + return ret; +} + +static void usb_conn_remove(struct platform_device *pdev) +{ + struct usb_conn_info *info = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&info->dw_det); + + if (info->charger) + ida_free(&usb_conn_ida, info->conn_id); + + if (info->last_role == USB_ROLE_HOST && info->vbus) + regulator_disable(info->vbus); + + usb_role_switch_put(info->role_sw); +} + +static int __maybe_unused usb_conn_suspend(struct device *dev) +{ + struct usb_conn_info *info = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) { + if (info->id_gpiod) + enable_irq_wake(info->id_irq); + if (info->vbus_gpiod) + enable_irq_wake(info->vbus_irq); + return 0; + } + + if (info->id_gpiod) + disable_irq(info->id_irq); + if (info->vbus_gpiod) + disable_irq(info->vbus_irq); + + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static int __maybe_unused usb_conn_resume(struct device *dev) +{ + struct usb_conn_info *info = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) { + if (info->id_gpiod) + disable_irq_wake(info->id_irq); + if (info->vbus_gpiod) + disable_irq_wake(info->vbus_irq); + return 0; + } + + pinctrl_pm_select_default_state(dev); + + if (info->id_gpiod) + enable_irq(info->id_irq); + if (info->vbus_gpiod) + enable_irq(info->vbus_irq); + + usb_conn_queue_dwork(info, 0); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(usb_conn_pm_ops, + usb_conn_suspend, usb_conn_resume); + +static const struct of_device_id usb_conn_dt_match[] = { + { .compatible = "gpio-usb-b-connector", }, + { } +}; +MODULE_DEVICE_TABLE(of, usb_conn_dt_match); + +static struct platform_driver usb_conn_driver = { + .probe = usb_conn_probe, + .remove = usb_conn_remove, + .driver = { + .name = "usb-conn-gpio", + .pm = &usb_conn_pm_ops, + .of_match_table = usb_conn_dt_match, + }, +}; + +module_platform_driver(usb_conn_driver); + +MODULE_AUTHOR("Chunfeng Yun "); +MODULE_DESCRIPTION("USB GPIO based connection detection driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/common/usb-otg-fsm.c b/drivers/usb/common/usb-otg-fsm.c new file mode 100644 index 000000000..e11803225 --- /dev/null +++ b/drivers/usb/common/usb-otg-fsm.c @@ -0,0 +1,453 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * OTG Finite State Machine from OTG spec + * + * Copyright (C) 2007,2008 Freescale Semiconductor, Inc. + * + * Author: Li Yang + * Jerry Huang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef VERBOSE +#define VDBG(fmt, args...) pr_debug("[%s] " fmt, \ + __func__, ## args) +#else +#define VDBG(stuff...) do {} while (0) +#endif + +/* 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 = otg_start_host(fsm, 0); + else if (fsm->protocol == PROTO_GADGET) + ret = otg_start_gadget(fsm, 0); + if (ret) + return ret; + + /* start new protocol */ + if (protocol == PROTO_HOST) + ret = otg_start_host(fsm, 1); + else if (protocol == PROTO_GADGET) + ret = otg_start_gadget(fsm, 1); + if (ret) + return ret; + + fsm->protocol = protocol; + return 0; + } + + return 0; +} + +/* Called when leaving a state. Do state clean up jobs here */ +static 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); + fsm->b_se0_srp = 0; + fsm->adp_sns = 0; + fsm->adp_prb = 0; + break; + case OTG_STATE_B_SRP_INIT: + fsm->data_pulse = 0; + fsm->b_srp_done = 0; + break; + case OTG_STATE_B_PERIPHERAL: + if (fsm->otg->gadget) + fsm->otg->gadget->host_request_flag = 0; + break; + case OTG_STATE_B_WAIT_ACON: + otg_del_timer(fsm, B_ASE0_BRST); + fsm->b_ase0_brst_tmout = 0; + break; + case OTG_STATE_B_HOST: + break; + case OTG_STATE_A_IDLE: + fsm->adp_prb = 0; + break; + case OTG_STATE_A_WAIT_VRISE: + otg_del_timer(fsm, A_WAIT_VRISE); + fsm->a_wait_vrise_tmout = 0; + break; + case OTG_STATE_A_WAIT_BCON: + otg_del_timer(fsm, A_WAIT_BCON); + fsm->a_wait_bcon_tmout = 0; + break; + case OTG_STATE_A_HOST: + otg_del_timer(fsm, A_WAIT_ENUM); + break; + case OTG_STATE_A_SUSPEND: + otg_del_timer(fsm, A_AIDL_BDIS); + fsm->a_aidl_bdis_tmout = 0; + fsm->a_suspend_req_inf = 0; + break; + case OTG_STATE_A_PERIPHERAL: + otg_del_timer(fsm, A_BIDL_ADIS); + fsm->a_bidl_adis_tmout = 0; + if (fsm->otg->gadget) + fsm->otg->gadget->host_request_flag = 0; + break; + case OTG_STATE_A_WAIT_VFALL: + otg_del_timer(fsm, A_WAIT_VFALL); + fsm->a_wait_vfall_tmout = 0; + otg_del_timer(fsm, A_WAIT_VRISE); + break; + case OTG_STATE_A_VBUS_ERR: + break; + default: + break; + } +} + +static void otg_hnp_polling_work(struct work_struct *work) +{ + struct otg_fsm *fsm = container_of(to_delayed_work(work), + struct otg_fsm, hnp_polling_work); + struct usb_device *udev; + enum usb_otg_state state = fsm->otg->state; + u8 flag; + int retval; + + if (state != OTG_STATE_A_HOST && state != OTG_STATE_B_HOST) + return; + + udev = usb_hub_find_child(fsm->otg->host->root_hub, 1); + if (!udev) { + dev_err(fsm->otg->host->controller, + "no usb dev connected, can't start HNP polling\n"); + return; + } + + *fsm->host_req_flag = 0; + /* Get host request flag from connected USB device */ + retval = usb_control_msg(udev, + usb_rcvctrlpipe(udev, 0), + USB_REQ_GET_STATUS, + USB_DIR_IN | USB_RECIP_DEVICE, + 0, + OTG_STS_SELECTOR, + fsm->host_req_flag, + 1, + USB_CTRL_GET_TIMEOUT); + if (retval != 1) { + dev_err(&udev->dev, "Get one byte OTG status failed\n"); + return; + } + + flag = *fsm->host_req_flag; + if (flag == 0) { + /* Continue HNP polling */ + schedule_delayed_work(&fsm->hnp_polling_work, + msecs_to_jiffies(T_HOST_REQ_POLL)); + return; + } else if (flag != HOST_REQUEST_FLAG) { + dev_err(&udev->dev, "host request flag %d is invalid\n", flag); + return; + } + + /* Host request flag is set */ + if (state == OTG_STATE_A_HOST) { + /* Set b_hnp_enable */ + if (!fsm->otg->host->b_hnp_enable) { + retval = usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE, 0, + USB_DEVICE_B_HNP_ENABLE, + 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); + if (retval >= 0) + fsm->otg->host->b_hnp_enable = 1; + } + fsm->a_bus_req = 0; + } else if (state == OTG_STATE_B_HOST) { + fsm->b_bus_req = 0; + } + + otg_statemachine(fsm); +} + +static void otg_start_hnp_polling(struct otg_fsm *fsm) +{ + /* + * The memory of host_req_flag should be allocated by + * controller driver, otherwise, hnp polling is not started. + */ + if (!fsm->host_req_flag) + return; + + if (!fsm->hnp_work_inited) { + INIT_DELAYED_WORK(&fsm->hnp_polling_work, otg_hnp_polling_work); + fsm->hnp_work_inited = true; + } + + schedule_delayed_work(&fsm->hnp_polling_work, + msecs_to_jiffies(T_HOST_REQ_POLL)); +} + +/* Called when entering a state */ +static int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state) +{ + if (fsm->otg->state == new_state) + return 0; + VDBG("Set state: %s\n", usb_otg_state_string(new_state)); + otg_leave_state(fsm, fsm->otg->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); + /* + * Driver is responsible for starting ADP probing + * if ADP sensing times out. + */ + otg_start_adp_sns(fsm); + otg_set_protocol(fsm, PROTO_UNDEF); + otg_add_timer(fsm, B_SE0_SRP); + 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); + break; + case OTG_STATE_B_PERIPHERAL: + otg_chrg_vbus(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_GADGET); + otg_loc_conn(fsm, 1); + 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); + 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); + otg_start_hnp_polling(fsm); + 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_start_adp_prb(fsm); + 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); + 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); + 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_inf) + otg_add_timer(fsm, A_WAIT_ENUM); + otg_start_hnp_polling(fsm); + 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); + + break; + case OTG_STATE_A_PERIPHERAL: + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_GADGET); + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 1); + otg_add_timer(fsm, A_BIDL_ADIS); + 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); + otg_add_timer(fsm, A_WAIT_VFALL); + 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->state = new_state; + fsm->state_changed = 1; + return 0; +} + +/* State change judgement */ +int otg_statemachine(struct otg_fsm *fsm) +{ + enum usb_otg_state state; + + mutex_lock(&fsm->lock); + + state = fsm->otg->state; + fsm->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->adp_change || fsm->power_up) && + fsm->b_ssend_srp && 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 || fsm->test_device) + 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 || fsm->adp_change || fsm->power_up)) + otg_set_state(fsm, OTG_STATE_A_WAIT_VRISE); + break; + case OTG_STATE_A_WAIT_VRISE: + if (fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (fsm->id || fsm->a_bus_drop || + fsm->a_wait_vrise_tmout) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + 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->id || fsm->a_bus_drop) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + else if ((!fsm->a_bus_req || fsm->a_suspend_req_inf) && + fsm->otg->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_SUSPEND); + else if (!fsm->b_conn) + 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->a_bidl_adis_tmout || 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->a_wait_vfall_tmout) + 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; + } + mutex_unlock(&fsm->lock); + + VDBG("quit statemachine, changed = %d\n", fsm->state_changed); + return fsm->state_changed; +} +EXPORT_SYMBOL_GPL(otg_statemachine); +MODULE_DESCRIPTION("OTG Finite State Machine"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig new file mode 100644 index 000000000..58e3ca7e4 --- /dev/null +++ b/drivers/usb/core/Kconfig @@ -0,0 +1,145 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# USB Core configuration +# +config USB_ANNOUNCE_NEW_DEVICES + bool "USB announce new devices" + 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 persistence + 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/driver-api/usb/persist.rst 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_FEW_INIT_RETRIES + bool "Limit USB device initialization to only a few retries" + help + When a new USB device is detected, the kernel tries very hard + to initialize and enumerate it, with lots of nested retry loops. + This almost always works, but when it fails it can take a long time. + This option tells the kernel to make only a few retry attempts, + so that the total time required for a failed initialization is + no more than 30 seconds (as required by the USB OTG spec). + + Say N here unless you require new-device enumeration failure to + occur within 30 seconds (as might be needed in an embedded + application). + +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 + 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_PRODUCTLIST + bool "Rely on OTG and EH Targeted Peripherals List" + depends on USB + help + If you say Y here, the "otg_productlist.h" file will be used as a + product list, so USB peripherals not listed there will be + rejected during enumeration. This behavior is required by the + USB OTG and EH 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. + +config USB_OTG_DISABLE_EXTERNAL_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. + +config USB_OTG_FSM + tristate "USB 2.0 OTG FSM implementation" + depends on USB && USB_OTG + select USB_PHY + help + Implements OTG Finite State Machine as specified in On-The-Go + and Embedded Host Supplement to the USB Revision 2.0 Specification. + +config USB_LEDS_TRIGGER_USBPORT + tristate "USB port LED trigger" + depends on USB && LEDS_TRIGGERS + help + This driver allows LEDs to be controlled by USB events. Enabling this + trigger allows specifying list of USB ports that should turn on LED + when some USB device gets connected. + +config USB_AUTOSUSPEND_DELAY + int "Default autosuspend delay" + depends on USB + default 2 + help + The default autosuspend delay in seconds. Can be overridden + with the usbcore.autosuspend command line or module parameter. + + The default value Linux has always had is 2 seconds. Change + this value if you want a different delay and cannot modify + the command line or module parameter. + +config USB_DEFAULT_AUTHORIZATION_MODE + int "Default authorization mode for USB devices" + range 0 2 + default 1 + depends on USB + help + Select the default USB device authorization mode. Can be overridden + with usbcore.authorized_default command line or module parameter. + + This option allows you to choose whether USB devices that are + connected to the system can be used by default, or if they are + locked down. + + With value 0 all connected USB devices with the exception of root + hub require user space authorization before they can be used. + + With value 1 (default) no user space authorization is required to + use connected USB devices. + + With value 2 all connected USB devices with exception of internal + USB devices require user space authorization before they can be + used. Note that in this mode the differentiation between internal + and external USB devices relies on ACPI, and on systems without + ACPI selecting value 2 is analogous to selecting value 0. + + If unsure, keep the default value. diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile new file mode 100644 index 000000000..60ea76160 --- /dev/null +++ b/drivers/usb/core/Makefile @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for USB Core files and filesystem +# + +# define_trace.h needs to know how to find our header +CFLAGS_trace.o := -I$(src) + +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 += phy.o port.o trace.o + +usbcore-$(CONFIG_OF) += of.o +usbcore-$(CONFIG_USB_XHCI_SIDEBAND) += offload.o +usbcore-$(CONFIG_USB_PCI) += hcd-pci.o +usbcore-$(CONFIG_ACPI) += usb-acpi.o + +ifdef CONFIG_USB_ONBOARD_DEV +usbcore-y += ../misc/onboard_usb_dev_pdevs.o +endif + +obj-$(CONFIG_USB) += usbcore.o + +obj-$(CONFIG_USB_LEDS_TRIGGER_USBPORT) += ledtrig-usbport.o diff --git a/drivers/usb/core/buffer.c b/drivers/usb/core/buffer.c new file mode 100644 index 000000000..87230869e --- /dev/null +++ b/drivers/usb/core/buffer.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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, regardless of bus type. + * + * Released under the GPLv2 only. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * DMA-Coherent Buffers + */ + +/* FIXME tune these based on pool statistics ... */ +static size_t pool_max[HCD_BUFFER_POOLS] = { + 32, 128, 512, 2048, +}; + +void __init usb_init_pool_max(void) +{ + /* + * The pool_max values must never be smaller than + * ARCH_DMA_MINALIGN. + */ + if (ARCH_DMA_MINALIGN <= 32) + ; /* Original value is okay */ + else if (ARCH_DMA_MINALIGN <= 64) + pool_max[0] = 64; + else if (ARCH_DMA_MINALIGN <= 128) + pool_max[0] = 0; /* Don't use this pool */ + else + BUILD_BUG(); /* We don't allow this */ +} + +/* SETUP primitives */ + +/** + * hcd_buffer_create - initialize buffer pools + * @hcd: the bus whose buffer pools are to be initialized + * + * Context: task context, might sleep + * + * 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. + * + * Call hcd_buffer_destroy() to clean up after using those pools. + * + * Return: 0 if successful. A negative errno value otherwise. + */ +int hcd_buffer_create(struct usb_hcd *hcd) +{ + char name[16]; + int i, size; + + if (hcd->localmem_pool || !hcd_uses_dma(hcd)) + 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.sysdev, + 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: task context, might sleep + * + * This frees the buffer pools created by hcd_buffer_create(). + */ +void hcd_buffer_destroy(struct usb_hcd *hcd) +{ + int i; + + if (!IS_ENABLED(CONFIG_HAS_DMA)) + return; + + for (i = 0; i < HCD_BUFFER_POOLS; i++) { + dma_pool_destroy(hcd->pool[i]); + 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; + + if (size == 0) + return NULL; + + if (hcd->localmem_pool) + return gen_pool_dma_alloc(hcd->localmem_pool, size, dma); + + /* some USB hosts just use PIO */ + if (!hcd_uses_dma(hcd)) { + *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.sysdev, 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 (hcd->localmem_pool) { + gen_pool_free(hcd->localmem_pool, (unsigned long)addr, size); + return; + } + + if (!hcd_uses_dma(hcd)) { + 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.sysdev, size, addr, dma); +} + +void *hcd_buffer_alloc_pages(struct usb_hcd *hcd, + size_t size, gfp_t mem_flags, dma_addr_t *dma) +{ + if (size == 0) + return NULL; + + if (hcd->localmem_pool) + return gen_pool_dma_alloc_align(hcd->localmem_pool, + size, dma, PAGE_SIZE); + + /* some USB hosts just use PIO */ + if (!hcd_uses_dma(hcd)) { + *dma = DMA_MAPPING_ERROR; + return (void *)__get_free_pages(mem_flags, + get_order(size)); + } + + return dma_alloc_coherent(hcd->self.sysdev, + size, dma, mem_flags); +} + +void hcd_buffer_free_pages(struct usb_hcd *hcd, + size_t size, void *addr, dma_addr_t dma) +{ + if (!addr) + return; + + if (hcd->localmem_pool) { + gen_pool_free(hcd->localmem_pool, + (unsigned long)addr, size); + return; + } + + if (!hcd_uses_dma(hcd)) { + free_pages((unsigned long)addr, get_order(size)); + return; + } + + dma_free_coherent(hcd->self.sysdev, size, addr, dma); +} diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c new file mode 100644 index 000000000..0e79b7f8a --- /dev/null +++ b/drivers/usb/core/config.c @@ -0,0 +1,1163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Released under the GPLv2 only. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "usb.h" + + +#define USB_MAXALTSETTING 128 /* Hard limit */ + +#define USB_MAXCONFIG 8 /* Arbitrary limit */ + +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_ssp_isoc_endpoint_companion(struct device *ddev, + int cfgno, int inum, int asnum, struct usb_host_endpoint *ep, + unsigned char *buffer, int size) +{ + struct usb_ssp_isoc_ep_comp_descriptor *desc; + + /* + * The SuperSpeedPlus Isoc endpoint companion descriptor immediately + * follows the SuperSpeed Endpoint Companion descriptor + */ + desc = (struct usb_ssp_isoc_ep_comp_descriptor *) buffer; + if (size < USB_DT_SSP_ISOC_EP_COMP_SIZE || + desc->bDescriptorType != USB_DT_SSP_ISOC_ENDPOINT_COMP) { + dev_notice(ddev, "Invalid SuperSpeedPlus isoc endpoint companion for config %d interface %d altsetting %d ep 0x%X.\n", + cfgno, inum, asnum, ep->desc.bEndpointAddress); + return; + } + memcpy(&ep->ssp_isoc_ep_comp, desc, USB_DT_SSP_ISOC_EP_COMP_SIZE); +} + +static void usb_parse_eusb2_isoc_endpoint_companion(struct device *ddev, + int cfgno, int inum, int asnum, struct usb_host_endpoint *ep, + unsigned char *buffer, int size) +{ + struct usb_eusb2_isoc_ep_comp_descriptor *desc; + struct usb_descriptor_header *h; + + /* + * eUSB2 isochronous endpoint companion descriptor for this endpoint + * shall be declared before the next endpoint or interface descriptor + */ + while (size >= USB_DT_EUSB2_ISOC_EP_COMP_SIZE) { + h = (struct usb_descriptor_header *)buffer; + + if (h->bDescriptorType == USB_DT_EUSB2_ISOC_ENDPOINT_COMP) { + desc = (struct usb_eusb2_isoc_ep_comp_descriptor *)buffer; + ep->eusb2_isoc_ep_comp = *desc; + return; + } + if (h->bDescriptorType == USB_DT_ENDPOINT || + h->bDescriptorType == USB_DT_INTERFACE) + break; + + buffer += h->bLength; + size -= h->bLength; + } + + dev_notice(ddev, "No eUSB2 isoc ep 0x%X companion for config %d interface %d altsetting %d\n", + ep->desc.bEndpointAddress, cfgno, inum, asnum); +} + +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 (size < USB_DT_SS_EP_COMP_SIZE) { + dev_notice(ddev, + "invalid SuperSpeed endpoint companion descriptor " + "of length %d, skipping\n", size); + return; + } + + if (desc->bDescriptorType != USB_DT_SS_ENDPOINT_COMP) { + dev_notice(ddev, "No SuperSpeed endpoint companion for config %d interface %d altsetting %d ep 0x%X: 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; + } + buffer += desc->bLength; + size -= desc->bLength; + 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_notice(ddev, "Control endpoint with bMaxBurst = %d in config %d interface %d altsetting %d ep 0x%X: setting to zero\n", + desc->bMaxBurst, cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ss_ep_comp.bMaxBurst = 0; + } else if (desc->bMaxBurst > 15) { + dev_notice(ddev, "Endpoint with bMaxBurst = %d in config %d interface %d altsetting %d ep 0x%X: 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_notice(ddev, "%s endpoint with bmAttributes = %d in config %d interface %d altsetting %d ep 0x%X: setting to zero\n", + usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Interrupt", + 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_notice(ddev, "Bulk endpoint with more than 65536 streams in config %d interface %d altsetting %d ep 0x%X: setting to max\n", + cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ss_ep_comp.bmAttributes = 16; + } else if (usb_endpoint_xfer_isoc(&ep->desc) && + !USB_SS_SSP_ISOC_COMP(desc->bmAttributes) && + USB_SS_MULT(desc->bmAttributes) > 3) { + dev_notice(ddev, "Isoc endpoint has Mult of %d in config %d interface %d altsetting %d ep 0x%X: setting to 3\n", + USB_SS_MULT(desc->bmAttributes), + cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ss_ep_comp.bmAttributes = 2; + } + + if (usb_endpoint_xfer_isoc(&ep->desc)) + max_tx = (desc->bMaxBurst + 1) * + (USB_SS_MULT(desc->bmAttributes)) * + 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; + /* + * wBytesPerInterval > max_tx is bogus, but USB3 spec doesn't forbid the opposite. + * Experience shows that wBytesPerInterval < wMaxPacketSize on common interrupt IN + * endpoints is usually bogus too, and recent HCs enforce interrupt BW limits. + */ + if (le16_to_cpu(desc->wBytesPerInterval) > max_tx || + (le16_to_cpu(desc->wBytesPerInterval) < usb_endpoint_maxp(&ep->desc) && + usb_endpoint_is_int_in(&ep->desc))) { + dev_notice(ddev, "%s endpoint with wBytesPerInterval of %d in config %d interface %d altsetting %d ep 0x%X: 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); + } + /* Parse a possible SuperSpeedPlus isoc ep companion descriptor */ + if (usb_endpoint_xfer_isoc(&ep->desc) && + USB_SS_SSP_ISOC_COMP(desc->bmAttributes)) + usb_parse_ssp_isoc_endpoint_companion(ddev, cfgno, inum, asnum, + ep, buffer, size); +} + +static const unsigned short low_speed_maxpacket_maxes[4] = { + [USB_ENDPOINT_XFER_CONTROL] = 8, + [USB_ENDPOINT_XFER_ISOC] = 0, + [USB_ENDPOINT_XFER_BULK] = 0, + [USB_ENDPOINT_XFER_INT] = 8, +}; +static const unsigned short full_speed_maxpacket_maxes[4] = { + [USB_ENDPOINT_XFER_CONTROL] = 64, + [USB_ENDPOINT_XFER_ISOC] = 1023, + [USB_ENDPOINT_XFER_BULK] = 64, + [USB_ENDPOINT_XFER_INT] = 64, +}; +static const unsigned short high_speed_maxpacket_maxes[4] = { + [USB_ENDPOINT_XFER_CONTROL] = 64, + [USB_ENDPOINT_XFER_ISOC] = 1024, + + /* Bulk should be 512, but some devices use 1024: we will warn below */ + [USB_ENDPOINT_XFER_BULK] = 1024, + [USB_ENDPOINT_XFER_INT] = 1024, +}; +static const unsigned short super_speed_maxpacket_maxes[4] = { + [USB_ENDPOINT_XFER_CONTROL] = 512, + [USB_ENDPOINT_XFER_ISOC] = 1024, + [USB_ENDPOINT_XFER_BULK] = 1024, + [USB_ENDPOINT_XFER_INT] = 1024, +}; + +static bool endpoint_is_duplicate(struct usb_endpoint_descriptor *e1, + struct usb_endpoint_descriptor *e2) +{ + if (e1->bEndpointAddress == e2->bEndpointAddress) + return true; + + if (usb_endpoint_xfer_control(e1) || usb_endpoint_xfer_control(e2)) { + if (usb_endpoint_num(e1) == usb_endpoint_num(e2)) + return true; + } + + return false; +} + +/* + * Check for duplicate endpoint addresses in other interfaces and in the + * altsetting currently being parsed. + */ +static bool config_endpoint_is_duplicate(struct usb_host_config *config, + int inum, int asnum, struct usb_endpoint_descriptor *d) +{ + struct usb_endpoint_descriptor *epd; + struct usb_interface_cache *intfc; + struct usb_host_interface *alt; + int i, j, k; + + for (i = 0; i < config->desc.bNumInterfaces; ++i) { + intfc = config->intf_cache[i]; + + for (j = 0; j < intfc->num_altsetting; ++j) { + alt = &intfc->altsetting[j]; + + if (alt->desc.bInterfaceNumber == inum && + alt->desc.bAlternateSetting != asnum) + continue; + + for (k = 0; k < alt->desc.bNumEndpoints; ++k) { + epd = &alt->endpoint[k].desc; + + if (endpoint_is_duplicate(epd, d)) + return true; + } + } + } + + return false; +} + +static int usb_parse_endpoint(struct device *ddev, int cfgno, + struct usb_host_config *config, int inum, int asnum, + struct usb_host_interface *ifp, int num_ep, + unsigned char *buffer, int size) +{ + struct usb_device *udev = to_usb_device(ddev); + unsigned char *buffer0 = buffer; + struct usb_endpoint_descriptor *d; + struct usb_host_endpoint *endpoint; + int n, i, j, retval; + unsigned int maxp; + const unsigned short *maxpacket_maxes; + u16 bcdUSB; + + d = (struct usb_endpoint_descriptor *) buffer; + bcdUSB = le16_to_cpu(udev->descriptor.bcdUSB); + 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_notice(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 = usb_endpoint_num(d); + if (i == 0) { + dev_notice(ddev, "config %d interface %d altsetting %d has an " + "invalid descriptor for endpoint zero, skipping\n", + cfgno, inum, asnum); + 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; + + /* Save a copy of the descriptor and use it instead of the original */ + endpoint = &ifp->endpoint[ifp->desc.bNumEndpoints]; + memcpy(&endpoint->desc, d, n); + d = &endpoint->desc; + + /* Clear the reserved bits in bEndpointAddress */ + i = d->bEndpointAddress & + (USB_ENDPOINT_DIR_MASK | USB_ENDPOINT_NUMBER_MASK); + if (i != d->bEndpointAddress) { + dev_notice(ddev, "config %d interface %d altsetting %d has an endpoint descriptor with address 0x%X, changing to 0x%X\n", + cfgno, inum, asnum, d->bEndpointAddress, i); + endpoint->desc.bEndpointAddress = i; + } + + /* Check for duplicate endpoint addresses */ + if (config_endpoint_is_duplicate(config, inum, asnum, d)) { + dev_notice(ddev, "config %d interface %d altsetting %d has a duplicate endpoint with address 0x%X, skipping\n", + cfgno, inum, asnum, d->bEndpointAddress); + goto skip_to_next_endpoint_or_interface_descriptor; + } + + /* Ignore some endpoints */ + if (udev->quirks & USB_QUIRK_ENDPOINT_IGNORE) { + if (usb_endpoint_is_ignored(udev, ifp, d)) { + dev_notice(ddev, "config %d interface %d altsetting %d has an ignored endpoint with address 0x%X, skipping\n", + cfgno, inum, asnum, + d->bEndpointAddress); + goto skip_to_next_endpoint_or_interface_descriptor; + } + } + + /* Accept this endpoint */ + ++ifp->desc.bNumEndpoints; + INIT_LIST_HEAD(&endpoint->urb_list); + + /* + * Fix up bInterval values outside the legal range. + * Use 10 or 8 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 (udev->speed) { + case USB_SPEED_SUPER_PLUS: + 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 an + * 8-ms default value otherwise. + */ + n = fls(d->bInterval*8); + if (n == 0) + n = 7; /* 8 ms = 2^(7-1) uframes */ + j = 16; + + /* + * Adjust bInterval for quirked devices. + */ + /* + * This quirk fixes bIntervals reported in ms. + */ + if (udev->quirks & USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL) { + n = clamp(fls(d->bInterval) + 3, i, j); + i = j = n; + } + /* + * This quirk fixes bIntervals reported in + * linear microframes. + */ + if (udev->quirks & USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL) { + n = clamp(fls(d->bInterval), i, j); + i = j = n; + } + 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 = 10; + break; + } + } else if (usb_endpoint_xfer_isoc(d)) { + i = 1; + j = 16; + switch (udev->speed) { + case USB_SPEED_HIGH: + n = 7; /* 8 ms = 2^(7-1) uframes */ + break; + default: /* USB_SPEED_FULL */ + n = 4; /* 8 ms = 2^(4-1) frames */ + break; + } + } + if (d->bInterval < i || d->bInterval > j) { + dev_notice(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 (udev->speed == USB_SPEED_LOW && usb_endpoint_xfer_bulk(d)) { + dev_notice(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); + } + + /* + * Validate the wMaxPacketSize field. + * eUSB2 devices (see USB 2.0 Double Isochronous IN ECN 9.6.6 Endpoint) + * and devices with isochronous endpoints in altsetting 0 (see USB 2.0 + * end of section 5.6.3) have wMaxPacketSize = 0. + * So don't warn about those. + */ + maxp = le16_to_cpu(endpoint->desc.wMaxPacketSize); + + if (maxp == 0 && bcdUSB != 0x0220 && + !(usb_endpoint_xfer_isoc(d) && asnum == 0)) + dev_notice(ddev, "config %d interface %d altsetting %d endpoint 0x%X has invalid wMaxPacketSize 0\n", + cfgno, inum, asnum, d->bEndpointAddress); + + /* Find the highest legal maxpacket size for this endpoint */ + i = 0; /* additional transactions per microframe */ + switch (udev->speed) { + case USB_SPEED_LOW: + maxpacket_maxes = low_speed_maxpacket_maxes; + break; + case USB_SPEED_FULL: + maxpacket_maxes = full_speed_maxpacket_maxes; + break; + case USB_SPEED_HIGH: + /* Multiple-transactions bits are allowed only for HS periodic endpoints */ + if (usb_endpoint_xfer_int(d) || usb_endpoint_xfer_isoc(d)) { + i = maxp & USB_EP_MAXP_MULT_MASK; + maxp &= ~i; + } + fallthrough; + default: + maxpacket_maxes = high_speed_maxpacket_maxes; + break; + case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + maxpacket_maxes = super_speed_maxpacket_maxes; + break; + } + j = maxpacket_maxes[usb_endpoint_type(&endpoint->desc)]; + + if (maxp > j) { + dev_notice(ddev, "config %d interface %d altsetting %d endpoint 0x%X has invalid maxpacket %d, setting to %d\n", + cfgno, inum, asnum, d->bEndpointAddress, maxp, j); + maxp = j; + endpoint->desc.wMaxPacketSize = cpu_to_le16(i | maxp); + } + + /* + * 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 (udev->speed == USB_SPEED_HIGH && usb_endpoint_xfer_bulk(d)) { + if (maxp != 512) + dev_notice(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 eUSB2 periodic endpoint companion descriptor */ + if (udev->speed == USB_SPEED_HIGH && bcdUSB == 0x0220 && + !le16_to_cpu(d->wMaxPacketSize) && usb_endpoint_is_isoc_in(d)) + usb_parse_eusb2_isoc_endpoint_companion(ddev, cfgno, inum, asnum, + endpoint, buffer, size); + + /* Parse a possible SuperSpeed endpoint companion descriptor */ + if (udev->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, str_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_notice(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, str_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_notice(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, config, inum, asnum, + alt, num_ep, buffer, size); + if (retval < 0) + return retval; + ++n; + + buffer += retval; + size -= retval; + } + + if (n != num_ep_orig) + dev_notice(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, str_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 retval; + u8 inums[USB_MAXINTERFACES], nalts[USB_MAXINTERFACES]; + unsigned iad_num = 0; + + memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE); + nintf = nintf_orig = config->desc.bNumInterfaces; + config->desc.bNumInterfaces = 0; // Adjusted later + + if (config->desc.bDescriptorType != USB_DT_CONFIG || + config->desc.bLength < USB_DT_CONFIG_SIZE || + config->desc.bLength > size) { + dev_notice(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; + + if (nintf > USB_MAXINTERFACES) { + dev_notice(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_notice(ddev, "config %d descriptor has %d excess " + "byte%s, ignoring\n", + cfgno, size2, str_plural(size2)); + break; + } + + header = (struct usb_descriptor_header *) buffer2; + if ((header->bLength > size2) || (header->bLength < 2)) { + dev_notice(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_notice(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_notice(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_notice(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) { + struct usb_interface_assoc_descriptor *d; + + d = (struct usb_interface_assoc_descriptor *)header; + if (d->bLength < USB_DT_INTERFACE_ASSOCIATION_SIZE) { + dev_notice(ddev, + "config %d has an invalid interface association descriptor of length %d, skipping\n", + cfgno, d->bLength); + continue; + } + + if (iad_num == USB_MAXIADS) { + dev_notice(ddev, "found more Interface " + "Association Descriptors " + "than allocated for in " + "configuration %d\n", cfgno); + } else { + config->intf_assoc[iad_num] = d; + iad_num++; + } + + } else if (header->bDescriptorType == USB_DT_DEVICE || + header->bDescriptorType == USB_DT_CONFIG) + dev_notice(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_notice(ddev, "config %d has %d interface%s, different from " + "the descriptor's value: %d\n", + cfgno, n, str_plural(n), nintf_orig); + else if (n == 0) + dev_notice(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_notice(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_notice(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; + } + + intfc = kzalloc_flex(*intfc, altsetting, j); + config->intf_cache[i] = intfc; + 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, str_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_notice(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; + unsigned int cfgno, length; + unsigned char *bigbuffer; + struct usb_config_descriptor *desc; + int result; + size_t usb_config_req_size; + + /* + * We usually start by grabbing the first 9-bytes descriptor so we know + * how long the whole configuration is. Some devices with quirky + * firmware will fail enumeration, so if the quirk is set, use 255 instead, + * mirroring the behavior of Windows. + */ + if (dev->quirks & USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE) + usb_config_req_size = 255; + else + usb_config_req_size = USB_DT_CONFIG_SIZE; + + if (ncfg > USB_MAXCONFIG) { + dev_notice(ddev, "too many configurations: %d, " + "using maximum allowed: %d\n", ncfg, USB_MAXCONFIG); + dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG; + } + + if (ncfg < 1 && dev->quirks & USB_QUIRK_FORCE_ONE_CONFIG) { + dev_info(ddev, "Device claims zero configurations, forcing to 1\n"); + dev->descriptor.bNumConfigurations = 1; + ncfg = 1; + } else 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) + return -ENOMEM; + + length = ncfg * sizeof(char *); + dev->rawdescriptors = kzalloc(length, GFP_KERNEL); + if (!dev->rawdescriptors) + return -ENOMEM; + + desc = kmalloc(usb_config_req_size, GFP_KERNEL); + if (!desc) + return -ENOMEM; + + for (cfgno = 0; cfgno < ncfg; cfgno++) { + result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno, + desc, usb_config_req_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_notice(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 (asked for %zu, got %i)\n", + cfgno, usb_config_req_size, result); + result = -EINVAL; + goto err; + } + length = max_t(int, le16_to_cpu(desc->wTotalLength), + USB_DT_CONFIG_SIZE); + + bigbuffer = kmalloc(length, GFP_KERNEL); + if (!bigbuffer) { + result = -ENOMEM; + goto err; + } + + if (dev->quirks & USB_QUIRK_DELAY_INIT) + msleep(200); + + /* Skip the second read if we already got everything */ + if (result >= length) { + memcpy(bigbuffer, desc, length); + goto store_and_parse; + } + + /* Get the whole thing */ + 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_notice(ddev, "config index %d descriptor too short " + "(expected %i, got %i)\n", cfgno, length, result); + length = result; + } + +store_and_parse: + dev->rawdescriptors[cfgno] = bigbuffer; + + result = usb_parse_configuration(dev, cfgno, + &dev->config[cfgno], bigbuffer, length); + if (result < 0) { + ++cfgno; + goto err; + } + } + +err: + kfree(desc); + dev->descriptor.bNumConfigurations = cfgno; + + return result; +} + +void usb_release_bos_descriptor(struct usb_device *dev) +{ + if (dev->bos) { + kfree(dev->bos->desc); + kfree(dev->bos); + dev->bos = NULL; + } +} + +static const __u8 bos_desc_len[256] = { + [USB_CAP_TYPE_WIRELESS_USB] = USB_DT_USB_WIRELESS_CAP_SIZE, + [USB_CAP_TYPE_EXT] = USB_DT_USB_EXT_CAP_SIZE, + [USB_SS_CAP_TYPE] = USB_DT_USB_SS_CAP_SIZE, + [USB_SSP_CAP_TYPE] = USB_DT_USB_SSP_CAP_SIZE(1), + [CONTAINER_ID_TYPE] = USB_DT_USB_SS_CONTN_ID_SIZE, + [USB_PTM_CAP_TYPE] = USB_DT_USB_PTM_ID_SIZE, +}; + +/* 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; + struct usb_ssp_cap_descriptor *ssp_cap; + unsigned char *buffer, *buffer0; + int length, total_len, num, i, ssac; + __u8 cap_type; + int ret; + + if (dev->quirks & USB_QUIRK_NO_BOS) { + dev_dbg(ddev, "skipping BOS descriptor\n"); + return -ENOMSG; + } + + bos = kzalloc_obj(*bos); + 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 || bos->bLength < USB_DT_BOS_SIZE) { + dev_notice(ddev, "unable to get BOS descriptor or descriptor too short\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_obj(*dev->bos); + 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_notice(ddev, "unable to get BOS descriptor set\n"); + if (ret >= 0) + ret = -ENOMSG; + goto err; + } + + buffer0 = buffer; + total_len -= length; + buffer += length; + + for (i = 0; i < num; i++) { + cap = (struct usb_dev_cap_header *)buffer; + + if (total_len < sizeof(*cap) || total_len < cap->bLength) { + dev->bos->desc->bNumDeviceCaps = i; + break; + } + cap_type = cap->bDevCapabilityType; + length = cap->bLength; + if (bos_desc_len[cap_type] && length < bos_desc_len[cap_type]) { + dev->bos->desc->bNumDeviceCaps = i; + break; + } + + if (cap->bDescriptorType != USB_DT_DEVICE_CAPABILITY) { + dev_notice(ddev, "descriptor type invalid, skip\n"); + goto skip_to_next_descriptor; + } + + switch (cap_type) { + 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 USB_SSP_CAP_TYPE: + ssp_cap = (struct usb_ssp_cap_descriptor *)buffer; + ssac = (le32_to_cpu(ssp_cap->bmAttributes) & + USB_SSP_SUBLINK_SPEED_ATTRIBS); + if (length >= USB_DT_USB_SSP_CAP_SIZE(ssac)) + dev->bos->ssp_cap = ssp_cap; + break; + case CONTAINER_ID_TYPE: + dev->bos->ss_id = + (struct usb_ss_container_id_descriptor *)buffer; + break; + case USB_PTM_CAP_TYPE: + dev->bos->ptm_cap = + (struct usb_ptm_cap_descriptor *)buffer; + break; + default: + break; + } + +skip_to_next_descriptor: + total_len -= length; + buffer += length; + } + dev->bos->desc->wTotalLength = cpu_to_le16(buffer - buffer0); + + 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 000000000..6f0354aba --- /dev/null +++ b/drivers/usb/core/devices.c @@ -0,0 +1,546 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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) + * + ************************************************************* + * + * /devices contains USB topology, device, config, class, + * interface, & endpoint data. + * + * I considered using /dev/bus/usb/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 /sys/kernel/debug/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"; + +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_PERSONAL_HEALTHCARE, "perhc"}, + {USB_CLASS_AUDIO_VIDEO, "av"}, + {USB_CLASS_BILLBOARD, "blbrd"}, + {USB_CLASS_USB_TYPE_C_BRIDGE, "bridg"}, + {USB_CLASS_WIRELESS_CONTROLLER, "wlcon"}, + {USB_CLASS_MISC, "misc"}, + {USB_CLASS_APP_SPEC, "app."}, + {USB_CLASS_VENDOR_SPEC, "vend."}, + {-1, "unk."} /* leave as last */ +}; + +/*****************************************************************/ + +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) + bandwidth = usb_endpoint_maxp_mult(desc); + + /* this isn't checking for illegal values */ + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_CONTROL: + type = "Ctrl"; + dir = 'B'; /* ctrl is bidirectional */ + break; + case USB_ENDPOINT_XFER_ISOC: + type = "Isoc"; + break; + case USB_ENDPOINT_XFER_BULK: + type = "Bulk"; + break; + case USB_ENDPOINT_XFER_INT: + type = "Int."; + break; + default: /* "can't happen" */ + return start; + } + + interval = usb_decode_interval(desc, speed); + 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) * + 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++) { + 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++) { + 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; + + start = usb_dump_device_descriptor(start, end, &dev->descriptor); + + start = usb_dump_device_strings(start, end, dev); + + for (i = 0; i < dev->descriptor.bNumConfigurations; i++) { + start = usb_dump_config(dev->speed, + start, end, dev->config + i, + /* active ? */ + (dev->config + i) == dev->actconfig); + } + return start; +} + +/*****************************************************************/ + +/* 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 = kmalloc(PAGE_SIZE << 1, GFP_NOIO); + 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_HIGH: + speed = "480"; break; + case USB_SPEED_SUPER: + speed = "5000"; break; + case USB_SPEED_SUPER_PLUS: + speed = "10000"; 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)) { + kfree(pages_start); + return -EFAULT; + } + *nbytes -= length; + *file_offset += length; + total_written += length; + *buffer += length; + *skip_bytes = 0; + } else + *skip_bytes -= length; + + kfree(pages_start); + + /* 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; + int id; + + if (*ppos < 0) + return -EINVAL; + if (nbytes <= 0) + return 0; + + mutex_lock(&usb_bus_idr_lock); + /* print devices for all busses */ + idr_for_each_entry(&usb_bus_idr, bus, id) { + /* 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_idr_lock); + return ret; + } + total_written += ret; + } + mutex_unlock(&usb_bus_idr_lock); + return total_written; +} + +const struct file_operations usbfs_devices_fops = { + .llseek = no_seek_end_llseek, + .read = usb_device_read, +}; diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c new file mode 100644 index 000000000..101cb9425 --- /dev/null +++ b/drivers/usb/core/devio.c @@ -0,0 +1,2933 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*****************************************************************************/ + +/* + * devio.c -- User space communication with USB devices. + * + * Copyright (C) 1999-2000 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * 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 +#include /* for usbcore internals */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usb.h" + +#ifdef CONFIG_PM +#define MAYBE_CAP_SUSPEND USBDEVFS_CAP_SUSPEND +#else +#define MAYBE_CAP_SUSPEND 0 +#endif + +#define USB_MAXBUS 64 +#define USB_DEVICE_MAX (USB_MAXBUS * 128) +#define USB_SG_SIZE 16384 /* split-size for large txs */ + +/* Mutual exclusion for ps->list in resume vs. release and remove */ +static DEFINE_MUTEX(usbfs_mutex); + +struct usb_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; + struct list_head memory_list; + wait_queue_head_t wait; /* wake up if a request completed */ + wait_queue_head_t wait_for_resume; /* wake up upon runtime resume */ + unsigned int discsignr; + struct pid *disc_pid; + const struct cred *cred; + sigval_t disccontext; + unsigned long ifclaimed; + u32 disabled_bulk_eps; + unsigned long interface_allowed_mask; + int not_yet_resumed; + bool suspend_allowed; + bool privileges_dropped; +}; + +struct usb_memory { + struct list_head memlist; + int vma_use_count; + int urb_use_count; + u32 size; + void *mem; + dma_addr_t dma_handle; + unsigned long vm_start; + struct usb_dev_state *ps; +}; + +struct async { + struct list_head asynclist; + struct usb_dev_state *ps; + struct pid *pid; + const struct cred *cred; + unsigned int signr; + unsigned int ifnum; + void __user *userbuffer; + void __user *userurb; + sigval_t userurb_sigval; + struct urb *urb; + struct usb_memory *usbm; + unsigned int mem_usage; + int status; + 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"); + +static unsigned usbfs_snoop_max = 65536; +module_param(usbfs_snoop_max, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(usbfs_snoop_max, + "maximum number of bytes to print while snooping"); + +#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 u32 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 arithmetic overflow */ +#define USBFS_XFER_MAX (UINT_MAX / 2 - 1000000) + +static DEFINE_SPINLOCK(usbfs_memory_usage_lock); +static u64 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(u64 amount) +{ + u64 lim, total_mem; + unsigned long flags; + int ret; + + lim = READ_ONCE(usbfs_memory_mb); + lim <<= 20; + + ret = 0; + spin_lock_irqsave(&usbfs_memory_usage_lock, flags); + total_mem = usbfs_memory_usage + amount; + if (lim > 0 && total_mem > lim) + ret = -ENOMEM; + else + usbfs_memory_usage = total_mem; + spin_unlock_irqrestore(&usbfs_memory_usage_lock, flags); + + return ret; +} + +/* Memory for a transfer is being deallocated */ +static void usbfs_decrease_memory_usage(u64 amount) +{ + unsigned long flags; + + spin_lock_irqsave(&usbfs_memory_usage_lock, flags); + if (amount > usbfs_memory_usage) + usbfs_memory_usage = 0; + else + usbfs_memory_usage -= amount; + spin_unlock_irqrestore(&usbfs_memory_usage_lock, flags); +} + +static int connected(struct usb_dev_state *ps) +{ + return (!list_empty(&ps->list) && + ps->dev->state != USB_STATE_NOTATTACHED); +} + +static void dec_usb_memory_use_count(struct usb_memory *usbm, int *count) +{ + struct usb_dev_state *ps = usbm->ps; + struct usb_hcd *hcd = bus_to_hcd(ps->dev->bus); + unsigned long flags; + + spin_lock_irqsave(&ps->lock, flags); + --*count; + if (usbm->urb_use_count == 0 && usbm->vma_use_count == 0) { + list_del(&usbm->memlist); + spin_unlock_irqrestore(&ps->lock, flags); + + hcd_buffer_free_pages(hcd, usbm->size, + usbm->mem, usbm->dma_handle); + usbfs_decrease_memory_usage( + usbm->size + sizeof(struct usb_memory)); + kfree(usbm); + } else { + spin_unlock_irqrestore(&ps->lock, flags); + } +} + +static void usbdev_vm_open(struct vm_area_struct *vma) +{ + struct usb_memory *usbm = vma->vm_private_data; + unsigned long flags; + + spin_lock_irqsave(&usbm->ps->lock, flags); + ++usbm->vma_use_count; + spin_unlock_irqrestore(&usbm->ps->lock, flags); +} + +static void usbdev_vm_close(struct vm_area_struct *vma) +{ + struct usb_memory *usbm = vma->vm_private_data; + + dec_usb_memory_use_count(usbm, &usbm->vma_use_count); +} + +static const struct vm_operations_struct usbdev_vm_ops = { + .open = usbdev_vm_open, + .close = usbdev_vm_close +}; + +static int usbdev_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct usb_memory *usbm = NULL; + struct usb_dev_state *ps = file->private_data; + struct usb_hcd *hcd = bus_to_hcd(ps->dev->bus); + size_t size = vma->vm_end - vma->vm_start; + void *mem; + unsigned long flags; + dma_addr_t dma_handle = DMA_MAPPING_ERROR; + int ret; + + if (!(file->f_mode & FMODE_WRITE)) + return -EPERM; + + ret = usbfs_increase_memory_usage(size + sizeof(struct usb_memory)); + if (ret) + goto error; + + usbm = kzalloc_obj(struct usb_memory); + if (!usbm) { + ret = -ENOMEM; + goto error_decrease_mem; + } + + mem = hcd_buffer_alloc_pages(hcd, + size, GFP_USER | __GFP_NOWARN, &dma_handle); + if (!mem) { + ret = -ENOMEM; + goto error_free_usbm; + } + + memset(mem, 0, size); + + usbm->mem = mem; + usbm->dma_handle = dma_handle; + usbm->size = size; + usbm->ps = ps; + usbm->vm_start = vma->vm_start; + usbm->vma_use_count = 1; + INIT_LIST_HEAD(&usbm->memlist); + + /* + * In DMA-unavailable cases, hcd_buffer_alloc_pages allocates + * normal pages and assigns DMA_MAPPING_ERROR to dma_handle. Check + * whether we are in such cases, and then use remap_pfn_range (or + * dma_mmap_coherent) to map normal (or DMA) pages into the user + * space, respectively. + */ + if (dma_handle == DMA_MAPPING_ERROR) { + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(usbm->mem) >> PAGE_SHIFT, + size, vma->vm_page_prot) < 0) { + dec_usb_memory_use_count(usbm, &usbm->vma_use_count); + return -EAGAIN; + } + } else { + if (dma_mmap_coherent(hcd->self.sysdev, vma, mem, dma_handle, + size)) { + dec_usb_memory_use_count(usbm, &usbm->vma_use_count); + return -EAGAIN; + } + } + + vm_flags_set(vma, VM_IO | VM_DONTEXPAND | VM_DONTDUMP); + vma->vm_ops = &usbdev_vm_ops; + vma->vm_private_data = usbm; + + spin_lock_irqsave(&ps->lock, flags); + list_add_tail(&usbm->memlist, &ps->memory_list); + spin_unlock_irqrestore(&ps->lock, flags); + + return 0; + +error_free_usbm: + kfree(usbm); +error_decrease_mem: + usbfs_decrease_memory_usage(size + sizeof(struct usb_memory)); +error: + return ret; +} + +static ssize_t usbdev_read(struct file *file, char __user *buf, size_t nbytes, + loff_t *ppos) +{ + struct usb_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_obj(struct async); + 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); + if (as->usbm == NULL) + kfree(as->urb->transfer_buffer); + else + dec_usb_memory_use_count(as->usbm, &as->usbm->urb_use_count); + + 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 usb_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 usb_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 usb_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 usb_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 %px, ep%d %s-%s, " + "length %u\n", + userurb, ep, t, d, length); + else + dev_info(&udev->dev, "userurb %px, 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); + } + + data_len = min(data_len, usbfs_snoop_max); + 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; + + len = min(len, usbfs_snoop_max); + if (!usbfs_snoop || len == 0) + 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 usb_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_reverse(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 usb_dev_state *ps = as->ps; + struct pid *pid = NULL; + const struct cred *cred = NULL; + unsigned long flags; + sigval_t addr; + int signr, errno; + + spin_lock_irqsave(&ps->lock, flags); + list_move_tail(&as->asynclist, &ps->async_completed); + as->status = urb->status; + signr = as->signr; + if (signr) { + errno = as->status; + addr = as->userurb_sigval; + pid = get_pid(as->pid); + cred = get_cred(as->cred); + } + snoop(&urb->dev->dev, "urb complete\n"); + snoop_urb(urb->dev, as->userurb, urb->pipe, urb->actual_length, + as->status, COMPLETE, NULL, 0); + if (usb_urb_dir_in(urb)) + 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); + + wake_up(&ps->wait); + spin_unlock_irqrestore(&ps->lock, flags); + + if (signr) { + kill_pid_usb_asyncio(signr, errno, addr, pid, cred); + put_pid(pid); + put_cred(cred); + } +} + +static void destroy_async(struct usb_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_last_entry(list, 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 usb_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 usb_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 usb_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); +} + +/* We don't care about suspend/resume of claimed interfaces */ +static int driver_suspend(struct usb_interface *intf, pm_message_t msg) +{ + return 0; +} + +static int driver_resume(struct usb_interface *intf) +{ + return 0; +} + +#ifdef CONFIG_PM +/* The following routines apply to the entire device, not interfaces */ +void usbfs_notify_suspend(struct usb_device *udev) +{ + /* We don't need to handle this */ +} + +void usbfs_notify_resume(struct usb_device *udev) +{ + struct usb_dev_state *ps; + + /* Protect against simultaneous remove or release */ + mutex_lock(&usbfs_mutex); + list_for_each_entry(ps, &udev->filelist, list) { + WRITE_ONCE(ps->not_yet_resumed, 0); + wake_up_all(&ps->wait_for_resume); + } + mutex_unlock(&usbfs_mutex); +} +#endif + +struct usb_driver usbfs_driver = { + .name = "usbfs", + .probe = driver_probe, + .disconnect = driver_disconnect, + .suspend = driver_suspend, + .resume = driver_resume, + .supports_autosuspend = 1, +}; + +static int claimintf(struct usb_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; + + if (ps->privileges_dropped && + !test_bit(ifnum, &ps->interface_allowed_mask)) + return -EACCES; + + intf = usb_ifnum_to_if(dev, ifnum); + if (!intf) + err = -ENOENT; + else { + unsigned int old_suppress; + + /* suppress uevents while claiming interface */ + old_suppress = dev_get_uevent_suppress(&intf->dev); + dev_set_uevent_suppress(&intf->dev, 1); + err = usb_driver_claim_interface(&usbfs_driver, intf, ps); + dev_set_uevent_suppress(&intf->dev, old_suppress); + } + if (err == 0) + set_bit(ifnum, &ps->ifclaimed); + return err; +} + +static int releaseintf(struct usb_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)) { + unsigned int old_suppress; + + /* suppress uevents while releasing interface */ + old_suppress = dev_get_uevent_suppress(&intf->dev); + dev_set_uevent_suppress(&intf->dev, 1); + usb_driver_release_interface(&usbfs_driver, intf); + dev_set_uevent_suppress(&intf->dev, old_suppress); + err = 0; + } + return err; +} + +static int checkintf(struct usb_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 usb_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, which we always want to allow as it is used + * to query things like ink level, etc. + */ + 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) + return 0; + } + + 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 struct usb_host_endpoint *ep_to_host_endpoint(struct usb_device *dev, + unsigned char ep) +{ + if (ep & USB_ENDPOINT_DIR_MASK) + return dev->ep_in[ep & USB_ENDPOINT_NUMBER_MASK]; + else + return dev->ep_out[ep & USB_ENDPOINT_NUMBER_MASK]; +} + +static int parse_usbdevfs_streams(struct usb_dev_state *ps, + struct usbdevfs_streams __user *streams, + unsigned int *num_streams_ret, + unsigned int *num_eps_ret, + struct usb_host_endpoint ***eps_ret, + struct usb_interface **intf_ret) +{ + unsigned int i, num_streams, num_eps; + struct usb_host_endpoint **eps; + struct usb_interface *intf = NULL; + unsigned char ep; + int ifnum, ret; + + if (get_user(num_streams, &streams->num_streams) || + get_user(num_eps, &streams->num_eps)) + return -EFAULT; + + if (num_eps < 1 || num_eps > USB_MAXENDPOINTS) + return -EINVAL; + + /* The XHCI controller allows max 2 ^ 16 streams */ + if (num_streams_ret && (num_streams < 2 || num_streams > 65536)) + return -EINVAL; + + eps = kmalloc_objs(*eps, num_eps); + if (!eps) + return -ENOMEM; + + for (i = 0; i < num_eps; i++) { + if (get_user(ep, &streams->eps[i])) { + ret = -EFAULT; + goto error; + } + eps[i] = ep_to_host_endpoint(ps->dev, ep); + if (!eps[i]) { + ret = -EINVAL; + goto error; + } + + /* usb_alloc/free_streams operate on an usb_interface */ + ifnum = findintfep(ps->dev, ep); + if (ifnum < 0) { + ret = ifnum; + goto error; + } + + if (i == 0) { + ret = checkintf(ps, ifnum); + if (ret < 0) + goto error; + intf = usb_ifnum_to_if(ps->dev, ifnum); + } else { + /* Verify all eps belong to the same interface */ + if (ifnum != intf->altsetting->desc.bInterfaceNumber) { + ret = -EINVAL; + goto error; + } + } + } + + if (num_streams_ret) + *num_streams_ret = num_streams; + *num_eps_ret = num_eps; + *eps_ret = eps; + *intf_ret = intf; + + return 0; + +error: + kfree(eps); + return ret; +} + +static struct usb_device *usbdev_lookup_by_devt(dev_t devt) +{ + struct device *dev; + + dev = bus_find_device_by_devt(&usb_bus_type, devt); + if (!dev) + return NULL; + return to_usb_device(dev); +} + +/* + * file operations + */ +static int usbdev_open(struct inode *inode, struct file *file) +{ + struct usb_device *dev = NULL; + struct usb_dev_state *ps; + int ret; + + ret = -ENOMEM; + ps = kzalloc_obj(struct usb_dev_state); + if (!ps) + goto out_free_ps; + + ret = -ENODEV; + + /* usbdev device-node */ + if (imajor(inode) == USB_DEVICE_MAJOR) + dev = usbdev_lookup_by_devt(inode->i_rdev); + 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; + ps->interface_allowed_mask = 0xFFFFFFFF; /* 32 bits */ + spin_lock_init(&ps->lock); + INIT_LIST_HEAD(&ps->list); + INIT_LIST_HEAD(&ps->async_pending); + INIT_LIST_HEAD(&ps->async_completed); + INIT_LIST_HEAD(&ps->memory_list); + init_waitqueue_head(&ps->wait); + init_waitqueue_head(&ps->wait_for_resume); + ps->disc_pid = get_pid(task_pid(current)); + ps->cred = get_current_cred(); + smp_wmb(); + + /* Can't race with resume; the device is already active */ + 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 usb_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); + + /* Protect against simultaneous resume */ + mutex_lock(&usbfs_mutex); + list_del_init(&ps->list); + mutex_unlock(&usbfs_mutex); + + for (ifnum = 0; ps->ifclaimed && ifnum < 8*sizeof(ps->ifclaimed); + ifnum++) { + if (test_bit(ifnum, &ps->ifclaimed)) + releaseintf(ps, ifnum); + } + destroy_all_async(ps); + if (!ps->suspend_allowed) + usb_autosuspend_device(dev); + usb_unlock_device(dev); + put_pid(ps->disc_pid); + put_cred(ps->cred); + + as = async_getcompleted(ps); + while (as) { + free_async(as); + as = async_getcompleted(ps); + } + usb_put_dev(dev); + + kfree(ps); + return 0; +} + +static void usbfs_blocking_completion(struct urb *urb) +{ + complete((struct completion *) urb->context); +} + +/* + * Much like usb_start_wait_urb, but returns status separately from + * actual_length and uses a killable wait. + */ +static int usbfs_start_wait_urb(struct urb *urb, int timeout, + unsigned int *actlen) +{ + DECLARE_COMPLETION_ONSTACK(ctx); + unsigned long expire; + int rc; + + urb->context = &ctx; + urb->complete = usbfs_blocking_completion; + *actlen = 0; + rc = usb_submit_urb(urb, GFP_KERNEL); + if (unlikely(rc)) + return rc; + + expire = (timeout ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT); + rc = wait_for_completion_killable_timeout(&ctx, expire); + if (rc <= 0) { + usb_kill_urb(urb); + *actlen = urb->actual_length; + if (urb->status != -ENOENT) + ; /* Completed before it was killed */ + else if (rc < 0) + return -EINTR; + else + return -ETIMEDOUT; + } + *actlen = urb->actual_length; + return urb->status; +} + +static int do_proc_control(struct usb_dev_state *ps, + struct usbdevfs_ctrltransfer *ctrl) +{ + struct usb_device *dev = ps->dev; + unsigned int tmo; + unsigned char *tbuf; + unsigned int wLength, actlen; + int i, pipe, ret; + struct urb *urb = NULL; + struct usb_ctrlrequest *dr = NULL; + + 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; + + ret = -ENOMEM; + tbuf = (unsigned char *)__get_free_page(GFP_KERNEL); + if (!tbuf) + goto done; + urb = usb_alloc_urb(0, GFP_NOIO); + if (!urb) + goto done; + dr = kmalloc_obj(struct usb_ctrlrequest, GFP_NOIO); + if (!dr) + goto done; + + dr->bRequestType = ctrl->bRequestType; + dr->bRequest = ctrl->bRequest; + dr->wValue = cpu_to_le16(ctrl->wValue); + dr->wIndex = cpu_to_le16(ctrl->wIndex); + dr->wLength = cpu_to_le16(ctrl->wLength); + + tmo = ctrl->timeout; + snoop(&dev->dev, "control urb: bRequestType=%02x " + "bRequest=%02x wValue=%04x " + "wIndex=%04x wLength=%04x\n", + ctrl->bRequestType, ctrl->bRequest, ctrl->wValue, + ctrl->wIndex, ctrl->wLength); + + if ((ctrl->bRequestType & USB_DIR_IN) && wLength) { + pipe = usb_rcvctrlpipe(dev, 0); + usb_fill_control_urb(urb, dev, pipe, (unsigned char *) dr, tbuf, + wLength, NULL, NULL); + snoop_urb(dev, NULL, pipe, wLength, tmo, SUBMIT, NULL, 0); + + usb_unlock_device(dev); + i = usbfs_start_wait_urb(urb, tmo, &actlen); + + /* Linger a bit, prior to the next control message. */ + if (dev->quirks & USB_QUIRK_DELAY_CTRL_MSG) + msleep(200); + usb_lock_device(dev); + snoop_urb(dev, NULL, pipe, actlen, i, COMPLETE, tbuf, actlen); + if (!i && actlen) { + if (copy_to_user(ctrl->data, tbuf, actlen)) { + ret = -EFAULT; + goto done; + } + } + } else { + if (wLength) { + if (copy_from_user(tbuf, ctrl->data, wLength)) { + ret = -EFAULT; + goto done; + } + } + pipe = usb_sndctrlpipe(dev, 0); + usb_fill_control_urb(urb, dev, pipe, (unsigned char *) dr, tbuf, + wLength, NULL, NULL); + snoop_urb(dev, NULL, pipe, wLength, tmo, SUBMIT, tbuf, wLength); + + usb_unlock_device(dev); + i = usbfs_start_wait_urb(urb, tmo, &actlen); + + /* Linger a bit, prior to the next control message. */ + if (dev->quirks & USB_QUIRK_DELAY_CTRL_MSG) + msleep(200); + usb_lock_device(dev); + snoop_urb(dev, NULL, pipe, actlen, i, 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 < 0 ? i : actlen); + + done: + kfree(dr); + usb_free_urb(urb); + free_page((unsigned long) tbuf); + usbfs_decrease_memory_usage(PAGE_SIZE + sizeof(struct urb) + + sizeof(struct usb_ctrlrequest)); + return ret; +} + +static int proc_control(struct usb_dev_state *ps, void __user *arg) +{ + struct usbdevfs_ctrltransfer ctrl; + + if (copy_from_user(&ctrl, arg, sizeof(ctrl))) + return -EFAULT; + return do_proc_control(ps, &ctrl); +} + +static int do_proc_bulk(struct usb_dev_state *ps, + struct usbdevfs_bulktransfer *bulk) +{ + struct usb_device *dev = ps->dev; + unsigned int tmo, len1, len2, pipe; + unsigned char *tbuf; + int i, ret; + struct urb *urb = NULL; + struct usb_host_endpoint *ep; + + ret = findintfep(ps->dev, bulk->ep); + if (ret < 0) + return ret; + ret = checkintf(ps, ret); + if (ret) + return ret; + + len1 = bulk->len; + if (len1 >= (INT_MAX - sizeof(struct urb))) + return -EINVAL; + + if (bulk->ep & USB_DIR_IN) + pipe = usb_rcvbulkpipe(dev, bulk->ep & 0x7f); + else + pipe = usb_sndbulkpipe(dev, bulk->ep & 0x7f); + ep = usb_pipe_endpoint(dev, pipe); + if (!ep || !usb_endpoint_maxp(&ep->desc)) + return -EINVAL; + ret = usbfs_increase_memory_usage(len1 + sizeof(struct urb)); + if (ret) + return ret; + + /* + * len1 can be almost arbitrarily large. Don't WARN if it's + * too big, just fail the request. + */ + ret = -ENOMEM; + tbuf = kmalloc(len1, GFP_KERNEL | __GFP_NOWARN); + if (!tbuf) + goto done; + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + goto done; + + if ((ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_INT) { + pipe = (pipe & ~(3 << 30)) | (PIPE_INTERRUPT << 30); + usb_fill_int_urb(urb, dev, pipe, tbuf, len1, + NULL, NULL, ep->desc.bInterval); + } else { + usb_fill_bulk_urb(urb, dev, pipe, tbuf, len1, NULL, NULL); + } + + tmo = bulk->timeout; + if (bulk->ep & 0x80) { + snoop_urb(dev, NULL, pipe, len1, tmo, SUBMIT, NULL, 0); + + usb_unlock_device(dev); + i = usbfs_start_wait_urb(urb, tmo, &len2); + 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 = usbfs_start_wait_urb(urb, tmo, &len2); + usb_lock_device(dev); + snoop_urb(dev, NULL, pipe, len2, i, COMPLETE, NULL, 0); + } + ret = (i < 0 ? i : len2); + done: + usb_free_urb(urb); + kfree(tbuf); + usbfs_decrease_memory_usage(len1 + sizeof(struct urb)); + return ret; +} + +static int proc_bulk(struct usb_dev_state *ps, void __user *arg) +{ + struct usbdevfs_bulktransfer bulk; + + if (copy_from_user(&bulk, arg, sizeof(bulk))) + return -EFAULT; + return do_proc_bulk(ps, &bulk); +} + +static void check_reset_of_active_ep(struct usb_device *udev, + unsigned int epnum, char *ioctl_name) +{ + struct usb_host_endpoint **eps; + struct usb_host_endpoint *ep; + + eps = (epnum & USB_DIR_IN) ? udev->ep_in : udev->ep_out; + ep = eps[epnum & 0x0f]; + if (ep && !list_empty(&ep->urb_list)) + dev_warn(&udev->dev, "Process %d (%s) called USBDEVFS_%s for active endpoint 0x%02x\n", + task_pid_nr(current), current->comm, + ioctl_name, epnum); +} + +static int proc_resetep(struct usb_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; + check_reset_of_active_ep(ps->dev, ep, "RESETEP"); + usb_reset_endpoint(ps->dev, ep); + return 0; +} + +static int proc_clearhalt(struct usb_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; + check_reset_of_active_ep(ps->dev, ep, "CLEAR_HALT"); + 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 usb_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 { + strscpy(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 usb_dev_state *ps, void __user *arg) +{ + struct usbdevfs_connectinfo ci; + + memset(&ci, 0, sizeof(ci)); + ci.devnum = ps->dev->devnum; + ci.slow = ps->dev->speed == USB_SPEED_LOW; + + if (copy_to_user(arg, &ci, sizeof(ci))) + return -EFAULT; + return 0; +} + +static int proc_conninfo_ex(struct usb_dev_state *ps, + void __user *arg, size_t size) +{ + struct usbdevfs_conninfo_ex ci; + struct usb_device *udev = ps->dev; + + if (size < sizeof(ci.size)) + return -EINVAL; + + memset(&ci, 0, sizeof(ci)); + ci.size = sizeof(ci); + ci.busnum = udev->bus->busnum; + ci.devnum = udev->devnum; + ci.speed = udev->speed; + + while (udev && udev->portnum != 0) { + if (++ci.num_ports <= ARRAY_SIZE(ci.ports)) + ci.ports[ARRAY_SIZE(ci.ports) - ci.num_ports] = + udev->portnum; + udev = udev->parent; + } + + if (ci.num_ports < ARRAY_SIZE(ci.ports)) + memmove(&ci.ports[0], + &ci.ports[ARRAY_SIZE(ci.ports) - ci.num_ports], + ci.num_ports); + + if (copy_to_user(arg, &ci, min(sizeof(ci), size))) + return -EFAULT; + + return 0; +} + +static int proc_resetdevice(struct usb_dev_state *ps) +{ + struct usb_host_config *actconfig = ps->dev->actconfig; + struct usb_interface *interface; + int i, number; + + /* Don't allow a device reset if the process has dropped the + * privilege to do such things and any of the interfaces are + * currently claimed. + */ + if (ps->privileges_dropped && actconfig) { + for (i = 0; i < actconfig->desc.bNumInterfaces; ++i) { + interface = actconfig->interface[i]; + number = interface->cur_altsetting->desc.bInterfaceNumber; + if (usb_interface_claimed(interface) && + !test_bit(number, &ps->ifclaimed)) { + dev_warn(&ps->dev->dev, + "usbfs: interface %d claimed by %s while '%s' resets device\n", + number, interface->dev.driver->name, current->comm); + return -EACCES; + } + } + } + + return usb_reset_device(ps->dev); +} + +static int proc_setintf(struct usb_dev_state *ps, void __user *arg) +{ + struct usbdevfs_setinterface setintf; + int ret; + + if (copy_from_user(&setintf, arg, sizeof(setintf))) + return -EFAULT; + ret = checkintf(ps, setintf.interface); + if (ret) + return ret; + + destroy_async_on_interface(ps, setintf.interface); + + return usb_set_interface(ps->dev, setintf.interface, + setintf.altsetting); +} + +static int proc_setconfig(struct usb_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 struct usb_memory * +find_memory_area(struct usb_dev_state *ps, const struct usbdevfs_urb *uurb) +{ + struct usb_memory *usbm = NULL, *iter; + unsigned long flags; + unsigned long uurb_start = (unsigned long)uurb->buffer; + + spin_lock_irqsave(&ps->lock, flags); + list_for_each_entry(iter, &ps->memory_list, memlist) { + if (uurb_start >= iter->vm_start && + uurb_start < iter->vm_start + iter->size) { + if (uurb->buffer_length > iter->vm_start + iter->size - + uurb_start) { + usbm = ERR_PTR(-EINVAL); + } else { + usbm = iter; + usbm->urb_use_count++; + } + break; + } + } + spin_unlock_irqrestore(&ps->lock, flags); + return usbm; +} + +static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb, + struct usbdevfs_iso_packet_desc __user *iso_frame_desc, + void __user *arg, sigval_t userurb_sigval) +{ + 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, num_sgs = 0, ifnum = -1; + int number_of_packets = 0; + unsigned int stream_id = 0; + void *buf; + bool is_in; + bool allow_short = false; + bool allow_zero = false; + unsigned long mask = USBDEVFS_URB_SHORT_NOT_OK | + USBDEVFS_URB_BULK_CONTINUATION | + USBDEVFS_URB_NO_FSBR | + USBDEVFS_URB_ZERO_PACKET | + USBDEVFS_URB_NO_INTERRUPT; + /* USBDEVFS_URB_ISO_ASAP is a special case */ + if (uurb->type == USBDEVFS_URB_TYPE_ISO) + mask |= USBDEVFS_URB_ISO_ASAP; + + if (uurb->flags & ~mask) + return -EINVAL; + + if ((unsigned int)uurb->buffer_length >= USBFS_XFER_MAX) + 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; + } + ep = ep_to_host_endpoint(ps->dev, uurb->endpoint); + if (!ep) + return -ENOENT; + is_in = (uurb->endpoint & USB_ENDPOINT_DIR_MASK) != 0; + + 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_obj(struct usb_ctrlrequest); + if (!dr) + return -ENOMEM; + if (copy_from_user(dr, uurb->buffer, 8)) { + ret = -EFAULT; + goto error; + } + if (uurb->buffer_length < (le16_to_cpu(dr->wLength) + 8)) { + ret = -EINVAL; + goto error; + } + ret = check_ctrlrecip(ps, dr->bRequestType, dr->bRequest, + le16_to_cpu(dr->wIndex)); + if (ret) + goto error; + uurb->buffer_length = le16_to_cpu(dr->wLength); + uurb->buffer += 8; + if ((dr->bRequestType & USB_DIR_IN) && uurb->buffer_length) { + is_in = true; + uurb->endpoint |= USB_DIR_IN; + } else { + is_in = false; + uurb->endpoint &= ~USB_DIR_IN; + } + if (is_in) + allow_short = true; + snoop(&ps->dev->dev, "control urb: bRequestType=%02x " + "bRequest=%02x wValue=%04x " + "wIndex=%04x wLength=%04x\n", + dr->bRequestType, dr->bRequest, + __le16_to_cpu(dr->wValue), + __le16_to_cpu(dr->wIndex), + __le16_to_cpu(dr->wLength)); + u = sizeof(struct usb_ctrlrequest); + break; + + case USBDEVFS_URB_TYPE_BULK: + if (!is_in) + allow_zero = true; + else + allow_short = true; + 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; + } + 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; + if (ep->streams) + stream_id = uurb->stream_id; + break; + + case USBDEVFS_URB_TYPE_INTERRUPT: + if (!usb_endpoint_xfer_int(&ep->desc)) + return -EINVAL; + interrupt_urb: + if (!is_in) + allow_zero = true; + else + allow_short = true; + 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; + number_of_packets = uurb->number_of_packets; + isofrmlen = sizeof(struct usbdevfs_iso_packet_desc) * + number_of_packets; + isopkt = memdup_user(iso_frame_desc, isofrmlen); + if (IS_ERR(isopkt)) { + ret = PTR_ERR(isopkt); + isopkt = NULL; + goto error; + } + for (totlen = u = 0; u < number_of_packets; u++) { + /* + * arbitrary limit need for USB 3.1 Gen2 + * sizemax: 96 DPs at SSP, 96 * 1024 = 98304 + */ + if (isopkt[u].length > 98304) { + 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 > 0 && + !access_ok(uurb->buffer, uurb->buffer_length)) { + ret = -EFAULT; + goto error; + } + as = alloc_async(number_of_packets); + if (!as) { + ret = -ENOMEM; + goto error; + } + + as->usbm = find_memory_area(ps, uurb); + if (IS_ERR(as->usbm)) { + ret = PTR_ERR(as->usbm); + as->usbm = NULL; + goto error; + } + + /* do not use SG buffers when memory mapped segments + * are in use + */ + if (as->usbm) + num_sgs = 0; + + u += sizeof(struct async) + sizeof(struct urb) + + (as->usbm ? 0 : 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_objs(struct scatterlist, num_sgs, + GFP_KERNEL | __GFP_NOWARN); + 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) { + if (as->usbm) { + unsigned long uurb_start = (unsigned long)uurb->buffer; + + as->urb->transfer_buffer = as->usbm->mem + + (uurb_start - as->usbm->vm_start); + } else { + as->urb->transfer_buffer = kmalloc(uurb->buffer_length, + GFP_KERNEL | __GFP_NOWARN); + 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 (allow_short && uurb->flags & USBDEVFS_URB_SHORT_NOT_OK) + u |= URB_SHORT_NOT_OK; + if (allow_zero && 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; + + if (!allow_short && uurb->flags & USBDEVFS_URB_SHORT_NOT_OK) + dev_warn(&ps->dev->dev, "Requested nonsensical USBDEVFS_URB_SHORT_NOT_OK.\n"); + if (!allow_zero && uurb->flags & USBDEVFS_URB_ZERO_PACKET) + dev_warn(&ps->dev->dev, "Requested nonsensical USBDEVFS_URB_ZERO_PACKET.\n"); + + 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 = number_of_packets; + as->urb->stream_id = stream_id; + + if (ep->desc.bInterval) { + if (uurb->type == USBDEVFS_URB_TYPE_ISO || + ps->dev->speed == USB_SPEED_HIGH || + ps->dev->speed >= USB_SPEED_SUPER) + 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 < 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; + as->userurb_sigval = userurb_sigval; + if (as->usbm) { + unsigned long uurb_start = (unsigned long)uurb->buffer; + + as->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + as->urb->transfer_dma = as->usbm->dma_handle + + (uurb_start - as->usbm->vm_start); + } else if (is_in && uurb->buffer_length > 0) + as->userbuffer = uurb->buffer; + as->signr = uurb->signr; + as->ifnum = ifnum; + as->pid = get_pid(task_pid(current)); + as->cred = get_current_cred(); + 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 usb_dev_state *ps, void __user *arg) +{ + struct usbdevfs_urb uurb; + sigval_t userurb_sigval; + + if (copy_from_user(&uurb, arg, sizeof(uurb))) + return -EFAULT; + + memset(&userurb_sigval, 0, sizeof(userurb_sigval)); + userurb_sigval.sival_ptr = arg; + + return proc_do_submiturb(ps, &uurb, + (((struct usbdevfs_urb __user *)arg)->iso_frame_desc), + arg, userurb_sigval); +} + +static int proc_unlinkurb(struct usb_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 void compute_isochronous_actual_length(struct urb *urb) +{ + unsigned int i; + + if (urb->number_of_packets > 0) { + urb->actual_length = 0; + for (i = 0; i < urb->number_of_packets; i++) + urb->actual_length += + urb->iso_frame_desc[i].actual_length; + } +} + +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; + + compute_isochronous_actual_length(urb); + 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 usb_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 || !connected(ps)) + 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 usb_dev_state *ps, void __user *arg) +{ + struct async *as = reap_as(ps); + + if (as) { + int retval; + + snoop(&ps->dev->dev, "reap %px\n", as->userurb); + retval = processcompl(as, (void __user * __user *)arg); + free_async(as); + return retval; + } + if (signal_pending(current)) + return -EINTR; + return -ENODEV; +} + +static int proc_reapurbnonblock(struct usb_dev_state *ps, void __user *arg) +{ + int retval; + struct async *as; + + as = async_getcompleted(ps); + if (as) { + snoop(&ps->dev->dev, "reap %px\n", as->userurb); + retval = processcompl(as, (void __user * __user *)arg); + free_async(as); + } else { + retval = (connected(ps) ? -EAGAIN : -ENODEV); + } + return retval; +} + +#ifdef CONFIG_COMPAT +static int proc_control_compat(struct usb_dev_state *ps, + struct usbdevfs_ctrltransfer32 __user *p32) +{ + struct usbdevfs_ctrltransfer ctrl; + u32 udata; + + if (copy_from_user(&ctrl, p32, sizeof(*p32) - sizeof(compat_caddr_t)) || + get_user(udata, &p32->data)) + return -EFAULT; + ctrl.data = compat_ptr(udata); + return do_proc_control(ps, &ctrl); +} + +static int proc_bulk_compat(struct usb_dev_state *ps, + struct usbdevfs_bulktransfer32 __user *p32) +{ + struct usbdevfs_bulktransfer bulk; + compat_caddr_t addr; + + if (get_user(bulk.ep, &p32->ep) || + get_user(bulk.len, &p32->len) || + get_user(bulk.timeout, &p32->timeout) || + get_user(addr, &p32->data)) + return -EFAULT; + bulk.data = compat_ptr(addr); + return do_proc_bulk(ps, &bulk); +} + +static int proc_disconnectsignal_compat(struct usb_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.sival_int = ds.context; + return 0; +} + +static int get_urb32(struct usbdevfs_urb *kurb, + struct usbdevfs_urb32 __user *uurb) +{ + struct usbdevfs_urb32 urb32; + if (copy_from_user(&urb32, uurb, sizeof(*uurb))) + return -EFAULT; + kurb->type = urb32.type; + kurb->endpoint = urb32.endpoint; + kurb->status = urb32.status; + kurb->flags = urb32.flags; + kurb->buffer = compat_ptr(urb32.buffer); + kurb->buffer_length = urb32.buffer_length; + kurb->actual_length = urb32.actual_length; + kurb->start_frame = urb32.start_frame; + kurb->number_of_packets = urb32.number_of_packets; + kurb->error_count = urb32.error_count; + kurb->signr = urb32.signr; + kurb->usercontext = compat_ptr(urb32.usercontext); + return 0; +} + +static int proc_submiturb_compat(struct usb_dev_state *ps, void __user *arg) +{ + struct usbdevfs_urb uurb; + sigval_t userurb_sigval; + + if (get_urb32(&uurb, (struct usbdevfs_urb32 __user *)arg)) + return -EFAULT; + + memset(&userurb_sigval, 0, sizeof(userurb_sigval)); + userurb_sigval.sival_int = ptr_to_compat(arg); + + return proc_do_submiturb(ps, &uurb, + ((struct usbdevfs_urb32 __user *)arg)->iso_frame_desc, + arg, userurb_sigval); +} + +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; + + compute_isochronous_actual_length(urb); + 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 usb_dev_state *ps, void __user *arg) +{ + struct async *as = reap_as(ps); + + if (as) { + int retval; + + snoop(&ps->dev->dev, "reap %px\n", as->userurb); + retval = processcompl_compat(as, (void __user * __user *)arg); + free_async(as); + return retval; + } + if (signal_pending(current)) + return -EINTR; + return -ENODEV; +} + +static int proc_reapurbnonblock_compat(struct usb_dev_state *ps, void __user *arg) +{ + int retval; + struct async *as; + + as = async_getcompleted(ps); + if (as) { + snoop(&ps->dev->dev, "reap %px\n", as->userurb); + retval = processcompl_compat(as, (void __user * __user *)arg); + free_async(as); + } else { + retval = (connected(ps) ? -EAGAIN : -ENODEV); + } + return retval; +} + + +#endif + +static int proc_disconnectsignal(struct usb_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.sival_ptr = ds.context; + return 0; +} + +static int proc_claiminterface(struct usb_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 usb_dev_state *ps, void __user *arg) +{ + unsigned int ifnum; + int ret; + + if (get_user(ifnum, (unsigned int __user *)arg)) + return -EFAULT; + ret = releaseintf(ps, ifnum); + if (ret < 0) + return ret; + destroy_async_on_interface(ps, ifnum); + return 0; +} + +static int proc_ioctl(struct usb_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; + + if (ps->privileges_dropped) + return -EACCES; + + if (!connected(ps)) + return -ENODEV; + + if (ps->dev->state != USB_STATE_CONFIGURED) + return -EHOSTUNREACH; + + intf = usb_ifnum_to_if(ps->dev, ctl->ifno); + if (!intf) + return -EINVAL; + + /* alloc buffer */ + size = _IOC_SIZE(ctl->ioctl_code); + if (size > 0) { + buf = kmalloc(size, GFP_KERNEL); + if (buf == 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); + } + } + + 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 usb_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 usb_dev_state *ps, compat_uptr_t arg) +{ + struct usbdevfs_ioctl32 ioc32; + struct usbdevfs_ioctl ctrl; + + if (copy_from_user(&ioc32, compat_ptr(arg), sizeof(ioc32))) + return -EFAULT; + ctrl.ifno = ioc32.ifno; + ctrl.ioctl_code = ioc32.ioctl_code; + ctrl.data = compat_ptr(ioc32.data); + return proc_ioctl(ps, &ctrl); +} +#endif + +static int proc_claim_port(struct usb_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 usb_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 usb_dev_state *ps, void __user *arg) +{ + __u32 caps; + + caps = USBDEVFS_CAP_ZERO_PACKET | USBDEVFS_CAP_NO_PACKET_SIZE_LIM | + USBDEVFS_CAP_REAP_AFTER_DISCONNECT | USBDEVFS_CAP_MMAP | + USBDEVFS_CAP_DROP_PRIVILEGES | + USBDEVFS_CAP_CONNINFO_EX | MAYBE_CAP_SUSPEND; + 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 usb_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 (ps->privileges_dropped) + return -EACCES; + + 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); +} + +static int proc_alloc_streams(struct usb_dev_state *ps, void __user *arg) +{ + unsigned num_streams, num_eps; + struct usb_host_endpoint **eps; + struct usb_interface *intf; + int r; + + r = parse_usbdevfs_streams(ps, arg, &num_streams, &num_eps, + &eps, &intf); + if (r) + return r; + + destroy_async_on_interface(ps, + intf->altsetting[0].desc.bInterfaceNumber); + + r = usb_alloc_streams(intf, eps, num_eps, num_streams, GFP_KERNEL); + kfree(eps); + return r; +} + +static int proc_free_streams(struct usb_dev_state *ps, void __user *arg) +{ + unsigned num_eps; + struct usb_host_endpoint **eps; + struct usb_interface *intf; + int r; + + r = parse_usbdevfs_streams(ps, arg, NULL, &num_eps, &eps, &intf); + if (r) + return r; + + destroy_async_on_interface(ps, + intf->altsetting[0].desc.bInterfaceNumber); + + r = usb_free_streams(intf, eps, num_eps, GFP_KERNEL); + kfree(eps); + return r; +} + +static int proc_drop_privileges(struct usb_dev_state *ps, void __user *arg) +{ + u32 data; + + if (copy_from_user(&data, arg, sizeof(data))) + return -EFAULT; + + /* This is a one way operation. Once privileges are + * dropped, you cannot regain them. You may however reissue + * this ioctl to shrink the allowed interfaces mask. + */ + ps->interface_allowed_mask &= data; + ps->privileges_dropped = true; + + return 0; +} + +static int proc_forbid_suspend(struct usb_dev_state *ps) +{ + int ret = 0; + + if (ps->suspend_allowed) { + ret = usb_autoresume_device(ps->dev); + if (ret == 0) + ps->suspend_allowed = false; + else if (ret != -ENODEV) + ret = -EIO; + } + return ret; +} + +static int proc_allow_suspend(struct usb_dev_state *ps) +{ + if (!connected(ps)) + return -ENODEV; + + WRITE_ONCE(ps->not_yet_resumed, 1); + if (!ps->suspend_allowed) { + usb_autosuspend_device(ps->dev); + ps->suspend_allowed = true; + } + return 0; +} + +static int proc_wait_for_resume(struct usb_dev_state *ps) +{ + int ret; + + usb_unlock_device(ps->dev); + ret = wait_event_interruptible(ps->wait_for_resume, + READ_ONCE(ps->not_yet_resumed) == 0); + usb_lock_device(ps->dev); + + if (ret != 0) + return -EINTR; + return proc_forbid_suspend(ps); +} + +/* + * 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 usb_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); + + /* Reap operations are allowed even after disconnection */ + switch (cmd) { + case USBDEVFS_REAPURB: + snoop(&dev->dev, "%s: REAPURB\n", __func__); + ret = proc_reapurb(ps, p); + goto done; + + case USBDEVFS_REAPURBNDELAY: + snoop(&dev->dev, "%s: REAPURBNDELAY\n", __func__); + ret = proc_reapurbnonblock(ps, p); + goto done; + +#ifdef CONFIG_COMPAT + case USBDEVFS_REAPURB32: + snoop(&dev->dev, "%s: REAPURB32\n", __func__); + ret = proc_reapurb_compat(ps, p); + goto done; + + case USBDEVFS_REAPURBNDELAY32: + snoop(&dev->dev, "%s: REAPURBNDELAY32\n", __func__); + ret = proc_reapurbnonblock_compat(ps, p); + goto done; +#endif + } + + 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_set_mtime_to_ts(inode, + inode_set_ctime_current(inode)); + break; + + case USBDEVFS_BULK: + snoop(&dev->dev, "%s: BULK\n", __func__); + ret = proc_bulk(ps, p); + if (ret >= 0) + inode_set_mtime_to_ts(inode, + inode_set_ctime_current(inode)); + break; + + case USBDEVFS_RESETEP: + snoop(&dev->dev, "%s: RESETEP\n", __func__); + ret = proc_resetep(ps, p); + if (ret >= 0) + inode_set_mtime_to_ts(inode, + inode_set_ctime_current(inode)); + 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_set_mtime_to_ts(inode, + inode_set_ctime_current(inode)); + 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_set_mtime_to_ts(inode, + inode_set_ctime_current(inode)); + break; + +#ifdef CONFIG_COMPAT + case USBDEVFS_CONTROL32: + snoop(&dev->dev, "%s: CONTROL32\n", __func__); + ret = proc_control_compat(ps, p); + if (ret >= 0) + inode_set_mtime_to_ts(inode, + inode_set_ctime_current(inode)); + break; + + case USBDEVFS_BULK32: + snoop(&dev->dev, "%s: BULK32\n", __func__); + ret = proc_bulk_compat(ps, p); + if (ret >= 0) + inode_set_mtime_to_ts(inode, + inode_set_ctime_current(inode)); + 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_set_mtime_to_ts(inode, + inode_set_ctime_current(inode)); + 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 %px\n", __func__, p); + ret = proc_unlinkurb(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; + case USBDEVFS_ALLOC_STREAMS: + ret = proc_alloc_streams(ps, p); + break; + case USBDEVFS_FREE_STREAMS: + ret = proc_free_streams(ps, p); + break; + case USBDEVFS_DROP_PRIVILEGES: + ret = proc_drop_privileges(ps, p); + break; + case USBDEVFS_GET_SPEED: + ret = ps->dev->speed; + break; + case USBDEVFS_FORBID_SUSPEND: + ret = proc_forbid_suspend(ps); + break; + case USBDEVFS_ALLOW_SUSPEND: + ret = proc_allow_suspend(ps); + break; + case USBDEVFS_WAIT_FOR_RESUME: + ret = proc_wait_for_resume(ps); + break; + } + + /* Handle variable-length commands */ + switch (cmd & ~IOCSIZE_MASK) { + case USBDEVFS_CONNINFO_EX(0): + ret = proc_conninfo_ex(ps, p, _IOC_SIZE(cmd)); + break; + } + + done: + usb_unlock_device(dev); + if (ret >= 0) + inode_set_atime_to_ts(inode, current_time(inode)); + 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; +} + +/* No kernel lock - fine */ +static __poll_t usbdev_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct usb_dev_state *ps = file->private_data; + __poll_t mask = 0; + + poll_wait(file, &ps->wait, wait); + if (file->f_mode & FMODE_WRITE && !list_empty(&ps->async_completed)) + mask |= EPOLLOUT | EPOLLWRNORM; + if (!connected(ps)) + mask |= EPOLLHUP; + if (list_empty(&ps->list)) + mask |= EPOLLERR; + return mask; +} + +const struct file_operations usbdev_file_operations = { + .owner = THIS_MODULE, + .llseek = no_seek_end_llseek, + .read = usbdev_read, + .poll = usbdev_poll, + .unlocked_ioctl = usbdev_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .mmap = usbdev_mmap, + .open = usbdev_open, + .release = usbdev_release, +}; + +static void usbdev_remove(struct usb_device *udev) +{ + struct usb_dev_state *ps; + + /* Protect against simultaneous resume */ + mutex_lock(&usbfs_mutex); + while (!list_empty(&udev->filelist)) { + ps = list_entry(udev->filelist.next, struct usb_dev_state, list); + destroy_all_async(ps); + wake_up_all(&ps->wait); + WRITE_ONCE(ps->not_yet_resumed, 0); + wake_up_all(&ps->wait_for_resume); + list_del_init(&ps->list); + if (ps->discsignr) + kill_pid_usb_asyncio(ps->discsignr, EPIPE, ps->disccontext, + ps->disc_pid, ps->cred); + } + mutex_unlock(&usbfs_mutex); +} + +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 000000000..7f33fe5ba --- /dev/null +++ b/drivers/usb/core/driver.c @@ -0,0 +1,2085 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drivers/usb/core/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 + * + * Released under the GPLv2 only. + * + * 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, + const struct usb_device_id *id_table, + struct device_driver *driver, + const char *buf, size_t count) +{ + struct usb_dynid *dynid; + u32 idVendor = 0; + u32 idProduct = 0; + unsigned int bInterfaceClass = 0; + u32 refVendor, refProduct; + int fields = 0; + int retval = 0; + + fields = sscanf(buf, "%x %x %x %x %x", &idVendor, &idProduct, + &bInterfaceClass, &refVendor, &refProduct); + if (fields < 2) + return -EINVAL; + + dynid = kzalloc_obj(*dynid); + 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 > 2 && bInterfaceClass) { + if (bInterfaceClass > 255) { + retval = -EINVAL; + goto fail; + } + + dynid->id.bInterfaceClass = (u8)bInterfaceClass; + dynid->id.match_flags |= USB_DEVICE_ID_MATCH_INT_CLASS; + } + + if (fields > 4) { + const struct usb_device_id *id = id_table; + + if (!id) { + retval = -ENODEV; + goto fail; + } + + for (; id->match_flags; id++) + if (id->idVendor == refVendor && id->idProduct == refProduct) + break; + + if (id->match_flags) { + dynid->id.driver_info = id->driver_info; + } else { + retval = -ENODEV; + goto fail; + } + } + + mutex_lock(&usb_dynids_lock); + list_add_tail(&dynid->node, &dynids->list); + mutex_unlock(&usb_dynids_lock); + + retval = driver_attach(driver); + + if (retval) + return retval; + return count; + +fail: + kfree(dynid); + return retval; +} +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; + + guard(mutex)(&usb_dynids_lock); + list_for_each_entry(dynid, &dynids->list, node) + if (dynid->id.bInterfaceClass != 0) + count += sysfs_emit_at(buf, count, "%04x %04x %02x\n", + dynid->id.idVendor, dynid->id.idProduct, + dynid->id.bInterfaceClass); + else + count += sysfs_emit_at(buf, count, "%04x %04x\n", + dynid->id.idVendor, dynid->id.idProduct); + return count; +} +EXPORT_SYMBOL_GPL(usb_show_dynids); + +static ssize_t new_id_show(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 new_id_store(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, usb_drv->id_table, driver, buf, count); +} +static DRIVER_ATTR_RW(new_id); + +/* + * Remove a USB device ID from this driver + */ +static ssize_t remove_id_store(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; + + guard(mutex)(&usb_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; + } + } + return count; +} + +static ssize_t remove_id_show(struct device_driver *driver, char *buf) +{ + return new_id_show(driver, buf); +} +static DRIVER_ATTR_RW(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->driver, + &driver_attr_new_id); + if (error == 0) { + error = driver_create_file(&usb_drv->driver, + &driver_attr_remove_id); + if (error) + driver_remove_file(&usb_drv->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->driver, + &driver_attr_remove_id); + driver_remove_file(&usb_drv->driver, + &driver_attr_new_id); + } +} + +static void usb_free_dynids(struct usb_driver *usb_drv) +{ + struct usb_dynid *dynid, *n; + + guard(mutex)(&usb_dynids_lock); + list_for_each_entry_safe(dynid, n, &usb_drv->dynids.list, node) { + list_del(&dynid->node); + kfree(dynid); + } +} + +static const struct usb_device_id *usb_match_dynamic_id(struct usb_interface *intf, + const struct usb_driver *drv, + struct usb_device_id *id_copy) +{ + struct usb_dynid *dynid; + + guard(mutex)(&usb_dynids_lock); + list_for_each_entry(dynid, &drv->dynids.list, node) { + if (usb_match_one_id(intf, &dynid->id)) { + *id_copy = dynid->id; + return id_copy; + } + } + 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) + return error; + + if (udriver->generic_subclass) + error = usb_generic_driver_probe(udev); + if (error) + return error; + + /* Probe the USB device with the driver in hand, but only + * defer to a generic driver in case the current USB + * device driver has an id_table or a match function; i.e., + * when the device driver was explicitly matched against + * a device. + * + * If the device driver does not have either of these, + * then we assume that it can bind to any device and is + * not truly a more specialized/non-generic driver, so a + * return value of -ENODEV should not force the device + * to be handled by the generic USB driver, as there + * can still be another, more specialized, device driver. + * + * This accommodates the usbip driver. + * + * TODO: What if, in the future, there are multiple + * specialized USB device drivers for a particular device? + * In such cases, there is a need to try all matching + * specialised device drivers prior to setting the + * use_generic_driver bit. + */ + if (udriver->probe) + error = udriver->probe(udev); + else if (!udriver->generic_subclass) + error = -EINVAL; + if (error == -ENODEV && udriver != &usb_generic_driver && + (udriver->id_table || udriver->match)) { + udev->use_generic_driver = 1; + return -EPROBE_DEFER; + } + 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); + + if (udriver->disconnect) + udriver->disconnect(udev); + if (udriver->generic_subclass) + usb_generic_driver_disconnect(udev); + if (!udriver->supports_autosuspend) + usb_autosuspend_device(udev); + return 0; +} + +/* 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; + struct usb_device_id id_copy; + int error = -ENODEV; + int lpm_disable_error = -ENODEV; + + dev_dbg(dev, "%s\n", __func__); + + intf->needs_binding = 0; + + if (usb_device_is_owned(udev)) + return error; + + if (udev->authorized == 0) { + dev_info(&intf->dev, "Device is not authorized for usage\n"); + return error; + } else if (intf->authorized == 0) { + dev_info(&intf->dev, "Interface %d is not authorized for usage\n", + intf->altsetting->desc.bInterfaceNumber); + return error; + } + + id = usb_match_dynamic_id(intf, driver, &id_copy); + if (!id) + id = usb_match_id(intf, driver->id_table); + 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. + */ + if (driver->disable_hub_initiated_lpm) { + lpm_disable_error = usb_unlocked_disable_lpm(udev); + if (lpm_disable_error) { + 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; + + /* 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_host_endpoint *ep, **eps = NULL; + struct usb_device *udev; + int i, j, error, r; + int lpm_disable_error = -ENODEV; + + intf->condition = USB_INTERFACE_UNBINDING; + + /* Autoresume for set_interface call below */ + udev = interface_to_usbdev(intf); + error = usb_autoresume_device(udev); + + /* If hub-initiated LPM policy may change, 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. + */ + if (driver->disable_hub_initiated_lpm) + lpm_disable_error = usb_unlocked_disable_lpm(udev); + + /* + * Terminate all URBs for this interface unless the driver + * supports "soft" unbinding and the device is still present. + */ + if (!driver->soft_unbind || udev->state == USB_STATE_NOTATTACHED) + usb_disable_interface(udev, intf, false); + + driver->disconnect(intf); + + /* Free streams */ + for (i = 0, j = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) { + ep = &intf->cur_altsetting->endpoint[i]; + if (ep->streams == 0) + continue; + if (j == 0) { + eps = kmalloc_array(USB_MAXENDPOINTS, sizeof(void *), + GFP_KERNEL); + if (!eps) + break; + } + eps[j++] = ep; + } + if (j) { + usb_free_streams(intf, eps, j, GFP_KERNEL); + kfree(eps); + } + + /* 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); + + if (!error) + usb_autosuspend_device(udev); + + return 0; +} + +static void usb_shutdown_interface(struct device *dev) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_driver *driver; + + if (!dev->driver) + return; + + driver = to_usb_driver(dev->driver); + if (driver->shutdown) + driver->shutdown(intf); +} + +/** + * 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 + * @data: 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. + * + * 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. + * + * Return: 0 on success. + */ +int usb_driver_claim_interface(struct usb_driver *driver, + struct usb_interface *iface, void *data) +{ + struct device *dev; + int retval = 0; + + if (!iface) + return -ENODEV; + + dev = &iface->dev; + if (dev->driver) + return -EBUSY; + + /* reject claim if interface is not authorized */ + if (!iface->authorized) + return -ENODEV; + + dev->driver = &driver->driver; + usb_set_intfdata(iface, data); + iface->needs_binding = 0; + + iface->condition = USB_INTERFACE_BOUND; + + /* 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); + + if (retval) { + dev->driver = NULL; + usb_set_intfdata(iface, NULL); + iface->needs_remote_wakeup = 0; + iface->condition = USB_INTERFACE_UNBOUND; + + /* + * Unbound interfaces are always runtime-PM-disabled + * and runtime-PM-suspended + */ + if (driver->supports_autosuspend) + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + } + + 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->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. + * + * Return: The first matching usb_device_id, or %NULL. + * + * 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); + +const struct usb_device_id *usb_device_match_id(struct usb_device *udev, + const struct usb_device_id *id) +{ + if (!id) + return NULL; + + for (; id->idVendor || id->idProduct ; id++) { + if (usb_match_device(udev, id)) + return id; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(usb_device_match_id); + +bool usb_driver_applicable(struct usb_device *udev, + const struct usb_device_driver *udrv) +{ + if (udrv->id_table && udrv->match) + return usb_device_match_id(udev, udrv->id_table) != NULL && + udrv->match(udev); + + if (udrv->id_table) + return usb_device_match_id(udev, udrv->id_table) != NULL; + + if (udrv->match) + return udrv->match(udev); + + return false; +} + +static int usb_device_match(struct device *dev, const struct device_driver *drv) +{ + /* devices and interfaces are handled separately */ + if (is_usb_device(dev)) { + struct usb_device *udev; + const struct usb_device_driver *udrv; + + /* interface drivers never match devices */ + if (!is_usb_device_driver(drv)) + return 0; + + udev = to_usb_device(dev); + udrv = to_usb_device_driver(drv); + + /* If the device driver under consideration does not have a + * id_table or a match function, then let the driver's probe + * function decide. + */ + if (!udrv->id_table && !udrv->match) + return 1; + + return usb_driver_applicable(udev, udrv); + + } else if (is_usb_interface(dev)) { + struct usb_interface *intf; + const struct usb_driver *usb_drv; + const struct usb_device_id *id; + struct usb_device_id id_copy; + + /* 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, &id_copy); + if (id) + return 1; + } + + return 0; +} + +static int usb_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + const struct usb_device *usb_dev; + + if (is_usb_device(dev)) { + usb_dev = to_usb_device(dev); + } else if (is_usb_interface(dev)) { + const 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; +} + +static int __usb_bus_reprobe_drivers(struct device *dev, void *data) +{ + struct usb_device_driver *new_udriver = data; + struct usb_device *udev; + int ret; + + /* Don't reprobe if current driver isn't usb_generic_driver */ + if (dev->driver != &usb_generic_driver.driver) + return 0; + + udev = to_usb_device(dev); + if (!usb_driver_applicable(udev, new_udriver)) + return 0; + + ret = device_reprobe(dev); + if (ret && ret != -EPROBE_DEFER) + dev_err(dev, "Failed to reprobe device (error %d)\n", ret); + + return 0; +} + +bool is_usb_device_driver(const struct device_driver *drv) +{ + return drv->probe == usb_probe_device; +} + +/** + * 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. + * + * Return: 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->driver.name = new_udriver->name; + new_udriver->driver.bus = &usb_bus_type; + new_udriver->driver.probe = usb_probe_device; + new_udriver->driver.remove = usb_unbind_device; + new_udriver->driver.owner = owner; + new_udriver->driver.dev_groups = new_udriver->dev_groups; + + retval = driver_register(&new_udriver->driver); + + if (!retval) { + pr_info("%s: registered new device driver %s\n", + usbcore_name, new_udriver->name); + /* + * Check whether any device could be better served with + * this new driver + */ + bus_for_each_dev(&usb_bus_type, NULL, new_udriver, + __usb_bus_reprobe_drivers); + } else { + pr_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->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. + * + * Return: 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->driver.name = new_driver->name; + new_driver->driver.bus = &usb_bus_type; + new_driver->driver.probe = usb_probe_interface; + new_driver->driver.remove = usb_unbind_interface; + new_driver->driver.shutdown = usb_shutdown_interface; + new_driver->driver.owner = owner; + new_driver->driver.mod_name = mod_name; + new_driver->driver.dev_groups = new_driver->dev_groups; + INIT_LIST_HEAD(&new_driver->dynids.list); + + retval = driver_register(&new_driver->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); + + return 0; + +out_newid: + driver_unregister(&new_driver->driver); +out: + pr_err("%s: error %d registering interface driver %s\n", + usbcore_name, retval, new_driver->name); + return retval; +} +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->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 && rc != -EPROBE_DEFER) + 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; + } + if (udriver->suspend) + status = udriver->suspend(udev, msg); + if (status == 0 && udriver->generic_subclass) + status = usb_generic_driver_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); + if (udriver->generic_subclass) + status = usb_generic_driver_resume(udev, msg); + if (status == 0 && udriver->resume) + 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_dbg(&intf->dev, "no reset_resume for driver %s?\n", + 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. + * + * Return: 0 if the suspend succeeded. + */ +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; + bool offload_active = false; + + if (udev->state == USB_STATE_NOTATTACHED || + udev->state == USB_STATE_SUSPENDED) + goto done; + + usb_offload_set_pm_locked(udev, true); + if (msg.event == PM_EVENT_SUSPEND && usb_offload_check(udev)) { + dev_dbg(&udev->dev, "device offloaded, skip suspend.\n"); + offload_active = true; + } + + /* 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]; + /* + * Don't suspend interfaces with remote wakeup while + * the controller is active. This preserves pending + * interrupt urbs, allowing interrupt events to be + * handled during system suspend. + */ + if (offload_active && intf->needs_remote_wakeup) { + dev_dbg(&intf->dev, + "device offloaded, skip suspend.\n"); + continue; + } + 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) { + if (!offload_active) + 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 device is inaccessible, don't try to resume + * suspended interfaces and just return the error. + */ + if (status && status != -EBUSY) { + int err; + u16 devstat; + + err = usb_get_std_status(udev, USB_RECIP_DEVICE, 0, + &devstat); + if (err) { + dev_err(&udev->dev, + "Failed to suspend device, error %d\n", + status); + goto done; + } + } + } + + /* 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; + if (!offload_active) { + 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: + if (status != 0) + usb_offload_set_pm_locked(udev, false); + 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 + * 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. + * + * Return: 0 on success. + */ +static int usb_resume_both(struct usb_device *udev, pm_message_t msg) +{ + int status = 0; + int i; + struct usb_interface *intf; + bool offload_active = false; + + if (udev->state == USB_STATE_NOTATTACHED) { + status = -ENODEV; + goto done; + } + udev->can_submit = 1; + if (msg.event == PM_EVENT_RESUME) + offload_active = usb_offload_check(udev); + + /* Resume the device */ + if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume) { + if (!offload_active) + status = usb_resume_device(udev, msg); + else + dev_dbg(&udev->dev, + "device offloaded, skip resume.\n"); + } + + /* Resume the interfaces */ + if (status == 0 && udev->actconfig) { + for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { + intf = udev->actconfig->interface[i]; + /* + * Interfaces with remote wakeup aren't suspended + * while the controller is active. This preserves + * pending interrupt urbs, allowing interrupt events + * to be handled during system suspend. + */ + if (offload_active && intf->needs_remote_wakeup) { + dev_dbg(&intf->dev, + "device offloaded, skip resume.\n"); + continue; + } + 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); + usb_offload_set_pm_locked(udev, false); + if (!status) + udev->reset_resume = 0; + return status; +} + +static void choose_wakeup(struct usb_device *udev, pm_message_t msg) +{ + int w; + + /* + * For FREEZE/QUIESCE, disable remote wakeups so no interrupts get + * generated. + */ + if (msg.event == PM_EVENT_FREEZE || msg.event == PM_EVENT_QUIESCE) { + w = 0; + + } else { + /* + * 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); + int r; + + 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); + r = usb_suspend_both(udev, msg); + if (r) + return r; + + if (udev->quirks & USB_QUIRK_DISCONNECT_SUSPEND) + usb_port_disable(udev); + + return 0; +} + +/* 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; +} + +/** + * 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. + * + * Return: 0 on success. A negative error code otherwise. + */ +int usb_autoresume_device(struct usb_device *udev) +{ + int status; + + status = pm_runtime_resume_and_get(&udev->dev); + dev_vdbg(&udev->dev, "%s: cnt %d -> %d\n", + __func__, atomic_read(&udev->dev.power.usage_count), + status); + 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); + 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); + + usb_mark_last_busy(udev); + pm_runtime_put(&intf->dev); + dev_vdbg(&intf->dev, "%s: cnt %d\n", + __func__, atomic_read(&intf->dev.power.usage_count)); +} +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); + 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. + * + * Return: 0 on success. + */ +int usb_autopm_get_interface(struct usb_interface *intf) +{ + int status; + + status = pm_runtime_resume_and_get(&intf->dev); + dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n", + __func__, atomic_read(&intf->dev.power.usage_count), + status); + 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. + * + * Return: 0 on success. A negative error code otherwise. + */ +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); + 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); + 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; + + if (udev->state == USB_STATE_NOTATTACHED) + return -ENODEV; + + /* 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; + } + + /* + * If the device is a direct child of the root hub and the HCD + * doesn't handle wakeup requests, don't allow autosuspend when + * wakeup is needed. + */ + if (w && udev->parent == udev->bus->root_hub && + bus_to_hcd(udev->bus)->cant_recv_wakeups) { + dev_dbg(&udev->dev, "HCD doesn't handle wakeup requests\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); + /* Tell the core not to suspend it, though. */ + return -EBUSY; +} + +static 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; +} + +int usb_enable_usb2_hardware_lpm(struct usb_device *udev) +{ + if (!udev->usb2_hw_lpm_capable || + !udev->usb2_hw_lpm_allowed || + udev->usb2_hw_lpm_enabled) + return 0; + + return usb_set_usb2_hardware_lpm(udev, 1); +} + +int usb_disable_usb2_hardware_lpm(struct usb_device *udev) +{ + if (!udev->usb2_hw_lpm_enabled) + return 0; + + return usb_set_usb2_hardware_lpm(udev, 0); +} + +#endif /* CONFIG_PM */ + +const struct bus_type usb_bus_type = { + .name = "usb", + .match = usb_device_match, + .uevent = usb_uevent, + .need_parent_lock = true, +}; diff --git a/drivers/usb/core/endpoint.c b/drivers/usb/core/endpoint.c new file mode 100644 index 000000000..e00eaf9e2 --- /dev/null +++ b/drivers/usb/core/endpoint.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drivers/usb/core/endpoint.c + * + * (C) Copyright 2002,2004,2006 Greg Kroah-Hartman + * (C) Copyright 2002,2004 IBM Corp. + * (C) Copyright 2006 Novell Inc. + * + * Released under the GPLv2 only. + * + * 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) + +#define usb_ep_attr(field, format_string) \ +static ssize_t field##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct ep_device *ep = to_ep_device(dev); \ + return sysfs_emit(buf, format_string, ep->desc->field); \ +} \ +static DEVICE_ATTR_RO(field) + +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 wMaxPacketSize_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ep_device *ep = to_ep_device(dev); + return sysfs_emit(buf, "%04x\n", usb_endpoint_maxp(ep->desc)); +} +static DEVICE_ATTR_RO(wMaxPacketSize); + +static ssize_t type_show(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 sysfs_emit(buf, "%s\n", type); +} +static DEVICE_ATTR_RO(type); + +static ssize_t interval_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ep_device *ep = to_ep_device(dev); + unsigned int interval; + char unit; + + interval = usb_decode_interval(ep->desc, ep->udev->speed); + if (interval % 1000) { + unit = 'u'; + } else { + unit = 'm'; + interval /= 1000; + } + + return sysfs_emit(buf, "%d%cs\n", interval, unit); +} +static DEVICE_ATTR_RO(interval); + +static ssize_t direction_show(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 sysfs_emit(buf, "%s\n", direction); +} +static DEVICE_ATTR_RO(direction); + +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 const 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); +} + +const 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_obj(*ep_dev); + 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 000000000..a88ced93b --- /dev/null +++ b/drivers/usb/core/file.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + * + * Released under the GPLv2 only. + */ + +#include +#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 err = -ENODEV; + const struct file_operations *new_fops; + + down_read(&minor_rwsem); + new_fops = fops_get(usb_minors[iminor(inode)]); + + if (!new_fops) + goto done; + + replace_fops(file, new_fops); + /* Curiouser and curiouser... NULL ->open() as "no device" ? */ + if (file->f_op->open) + err = file->f_op->open(inode, file); + done: + up_read(&minor_rwsem); + return err; +} + +static const struct file_operations usb_fops = { + .owner = THIS_MODULE, + .open = usb_open, + .llseek = noop_llseek, +}; + +static char *usb_devnode(const 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); +} + +const struct class usbmisc_class = { + .name = "usbmisc", + .devnode = usb_devnode, +}; + +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. + * + * Return: -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 = 0; + int minor_base = class_driver->minor_base; + int minor; + char name[20]; + +#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; + + 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; + } + if (intf->minor < 0) { + up_write(&minor_rwsem); + return -EXFULL; + } + + /* create a usb class device for this usb interface */ + snprintf(name, sizeof(name), class_driver->name, minor - minor_base); + intf->usb_dev = device_create(&usbmisc_class, &intf->dev, + MKDEV(USB_MAJOR, minor), class_driver, + "%s", kbasename(name)); + if (IS_ERR(intf->usb_dev)) { + usb_minors[minor] = NULL; + intf->minor = -1; + retval = PTR_ERR(intf->usb_dev); + } + up_write(&minor_rwsem); + 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); + device_destroy(&usbmisc_class, MKDEV(USB_MAJOR, intf->minor)); + + down_write(&minor_rwsem); + usb_minors[intf->minor] = NULL; + up_write(&minor_rwsem); + + intf->usb_dev = NULL; + intf->minor = -1; +} +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 000000000..a48994e11 --- /dev/null +++ b/drivers/usb/core/generic.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drivers/usb/core/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 + * + * Released under the GPLv2 only. + */ + +#include +#include +#include +#include +#include "usb.h" + +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; +} + +static bool is_audio(struct usb_interface_descriptor *desc) +{ + return desc->bInterfaceClass == USB_CLASS_AUDIO; +} + +static bool is_uac3_config(struct usb_interface_descriptor *desc) +{ + return desc->bInterfaceProtocol == UAC_VERSION_3; +} + +int usb_choose_configuration(struct usb_device *udev) +{ + int i; + int num_configs; + int insufficient_power = 0; + struct usb_host_config *c, *best; + struct usb_device_driver *udriver; + + /* + * If a USB device (not an interface) doesn't have a driver then the + * kernel has no business trying to select or install a configuration + * for it. + */ + if (!udev->dev.driver) + return -1; + udriver = to_usb_device_driver(udev->dev.driver); + + if (usb_device_is_owned(udev)) + return 0; + + if (udriver->choose_configuration) { + i = udriver->choose_configuration(udev); + if (i >= 0) + return i; + } + + 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; + } + + /* + * Select first configuration as default for audio so that + * devices that don't comply with UAC3 protocol are supported. + * But, still iterate through other configurations and + * select UAC3 compliant config if present. + */ + if (desc && is_audio(desc)) { + /* Always prefer the first found UAC3 config */ + if (is_uac3_config(desc)) { + best = c; + break; + } + + /* If there is no UAC3 config, prefer the first config */ + else if (i == 0) + best = c; + + /* Unconditional continue, because the rest of the code + * in the loop is irrelevant for audio devices, and + * because it can reassign best, which for audio devices + * we don't want. + */ + 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, str_plural(insufficient_power)); + + if (best) { + i = best->desc.bConfigurationValue; + dev_dbg(&udev->dev, + "configuration #%d chosen from %d choice%s\n", + i, num_configs, str_plural(num_configs)); + } else { + i = -1; + dev_warn(&udev->dev, + "no configuration chosen from %d choice%s\n", + num_configs, str_plural(num_configs)); + } + return i; +} +EXPORT_SYMBOL_GPL(usb_choose_configuration); + +static int __check_for_non_generic_match(struct device_driver *drv, void *data) +{ + struct usb_device *udev = data; + struct usb_device_driver *udrv; + + if (!is_usb_device_driver(drv)) + return 0; + udrv = to_usb_device_driver(drv); + if (udrv == &usb_generic_driver) + return 0; + return usb_driver_applicable(udev, udrv); +} + +static bool usb_generic_driver_match(struct usb_device *udev) +{ + if (udev->use_generic_driver) + return true; + + /* + * If any other driver wants the device, leave the device to this other + * driver. + */ + if (bus_for_each_drv(&usb_bus_type, NULL, udev, __check_for_non_generic_match)) + return false; + + return true; +} + +int usb_generic_driver_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_info(&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; +} + +void usb_generic_driver_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 + +int usb_generic_driver_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 USB2 devices don't need to do anything for FREEZE + * or PRETHAW. USB3 devices don't support global suspend and + * needs to be selectively suspended. + */ + else if ((msg.event == PM_EVENT_FREEZE || msg.event == PM_EVENT_PRETHAW) + && (udev->speed < USB_SPEED_SUPER)) + rc = 0; + else + rc = usb_port_suspend(udev, msg); + + if (rc == 0) + usbfs_notify_suspend(udev); + return rc; +} + +int usb_generic_driver_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); + + if (rc == 0) + usbfs_notify_resume(udev); + return rc; +} + +#endif /* CONFIG_PM */ + +struct usb_device_driver usb_generic_driver = { + .name = "usb", + .match = usb_generic_driver_match, + .probe = usb_generic_driver_probe, + .disconnect = usb_generic_driver_disconnect, +#ifdef CONFIG_PM + .suspend = usb_generic_driver_suspend, + .resume = usb_generic_driver_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 000000000..cd2234759 --- /dev/null +++ b/drivers/usb/core/hcd-pci.c @@ -0,0 +1,652 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright David Brownell 2000-2002 + */ + +#include +#include +#include +#include +#include + +#include +#include + +#ifdef CONFIG_PPC_PMAC +#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 device should be either UHCI,OHCI or EHCI host + * controller, otherwise skip. + */ + if (companion->class != CL_UHCI && companion->class != CL_OHCI && + companion->class != CL_EHCI) + 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 + * @driver: USB HC driver handle + * + * Context: task context, might sleep + * + * 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(). + * + * Return: 0 if successful. + */ +int usb_hcd_pci_probe(struct pci_dev *dev, const struct hc_driver *driver) +{ + struct usb_hcd *hcd; + int retval; + int hcd_irq = 0; + + if (usb_disabled()) + return -ENODEV; + + if (!driver) + return -EINVAL; + + if (pci_enable_device(dev) < 0) + return -ENODEV; + + /* + * 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) { + retval = pci_alloc_irq_vectors(dev, 1, 1, + PCI_IRQ_INTX | PCI_IRQ_MSI); + if (retval < 0) { + 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 = pci_irq_vector(dev, 0); + } + + hcd = usb_create_hcd(driver, &dev->dev, pci_name(dev)); + if (!hcd) { + retval = -ENOMEM; + goto free_irq_vectors; + } + + hcd->amd_resume_bug = usb_hcd_amd_resume_bug(dev, driver); + + if (driver->flags & HCD_MEMORY) { + /* XHCI, EHCI, OHCI */ + hcd->rsrc_start = pci_resource_start(dev, 0); + hcd->rsrc_len = pci_resource_len(dev, 0); + if (!devm_request_mem_region(&dev->dev, hcd->rsrc_start, + hcd->rsrc_len, driver->description)) { + dev_dbg(&dev->dev, "controller already in use\n"); + retval = -EBUSY; + goto put_hcd; + } + hcd->regs = devm_ioremap(&dev->dev, hcd->rsrc_start, + hcd->rsrc_len); + if (hcd->regs == NULL) { + dev_dbg(&dev->dev, "error mapping memory\n"); + retval = -EFAULT; + goto put_hcd; + } + + } else { + /* UHCI */ + int region; + + for (region = 0; region < PCI_STD_NUM_BARS; 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 (devm_request_region(&dev->dev, hcd->rsrc_start, + hcd->rsrc_len, driver->description)) + break; + } + if (region == PCI_STD_NUM_BARS) { + 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 put_hcd; + device_wakeup_enable(hcd->self.controller); + + if (pci_dev_run_wake(dev)) + pm_runtime_put_noidle(&dev->dev); + return retval; + +put_hcd: + usb_put_hcd(hcd); +free_irq_vectors: + if ((driver->flags & HCD_MASK) < HCD_USB3) + pci_free_irq_vectors(dev); +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: task context, might sleep + * + * 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; + int hcd_driver_flags; + + hcd = pci_get_drvdata(dev); + if (!hcd) + return; + + hcd_driver_flags = hcd->driver->flags; + + 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); + } + usb_put_hcd(hcd); + if ((hcd_driver_flags & HCD_MASK) < HCD_USB3) + pci_free_irq_vectors(dev); + 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); + if (usb_hcd_is_primary_hcd(hcd) && hcd->irq > 0) + free_irq(hcd->irq, 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 usb_hcd *hcd = dev_get_drvdata(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; +} + +static int suspend_common(struct device *dev, pm_message_t msg) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct usb_hcd *hcd = pci_get_drvdata(pci_dev); + bool do_wakeup; + int retval; + + if (PMSG_IS_AUTO(msg)) + do_wakeup = true; + else if (PMSG_NO_WAKEUP(msg)) + do_wakeup = false; + else + do_wakeup = device_may_wakeup(dev); + + /* 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(dev, 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, msg); + 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_irq_vector(pci_dev, 0)); + + /* 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, pm_message_t msg) +{ + 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 && msg.event != PM_EVENT_AUTO_RESUME) + for_each_companion(pci_dev, hcd, + ehci_wait_for_companions); + + retval = hcd->driver->pci_resume(hcd, msg); + if (retval) { + dev_err(dev, "PCI post-resume error %d!\n", retval); + usb_hc_died(hcd); + } + } + return retval; +} + +#ifdef CONFIG_PM_SLEEP + +static int hcd_pci_suspend(struct device *dev) +{ + return suspend_common(dev, PMSG_SUSPEND); +} + +static int hcd_pci_freeze(struct device *dev) +{ + return suspend_common(dev, PMSG_FREEZE); +} + +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(dev, pci_prepare_to_sleep, retval); + return retval; + } + + powermac_set_asic(pci_dev, 0); + return retval; +} + +static int hcd_pci_poweroff_late(struct device *dev) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + struct usb_hcd *hcd = pci_get_drvdata(pci_dev); + + if (hcd->driver->pci_poweroff_late && !HCD_DEAD(hcd)) + return hcd->driver->pci_poweroff_late(hcd, device_may_wakeup(dev)); + + return 0; +} + +static int hcd_pci_resume_noirq(struct device *dev) +{ + powermac_set_asic(to_pci_dev(dev), 1); + return 0; +} + +static int hcd_pci_resume(struct device *dev) +{ + return resume_common(dev, PMSG_RESUME); +} + +static int hcd_pci_restore(struct device *dev) +{ + return resume_common(dev, PMSG_RESTORE); +} + +#else + +#define hcd_pci_suspend NULL +#define hcd_pci_freeze NULL +#define hcd_pci_suspend_noirq NULL +#define hcd_pci_poweroff_late NULL +#define hcd_pci_resume_noirq NULL +#define hcd_pci_resume NULL +#define hcd_pci_restore NULL + +#endif /* CONFIG_PM_SLEEP */ + +static int hcd_pci_runtime_suspend(struct device *dev) +{ + int retval; + + retval = suspend_common(dev, PMSG_AUTO_SUSPEND); + 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, PMSG_AUTO_RESUME); + dev_dbg(dev, "hcd_pci_runtime_resume: %d\n", retval); + return retval; +} + +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 = hcd_pci_freeze, + .freeze_noirq = check_root_hub_suspended, + .thaw_noirq = NULL, + .thaw = hcd_pci_resume, + .poweroff = hcd_pci_suspend, + .poweroff_late = hcd_pci_poweroff_late, + .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 000000000..8e8a5f59b --- /dev/null +++ b/drivers/usb/core/hcd.c @@ -0,0 +1,3194 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (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 + */ + +#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 "usb.h" +#include "phy.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. + */ + +/*-------------------------------------------------------------------------*/ + +/* host controllers we manage */ +DEFINE_IDR (usb_bus_idr); +EXPORT_SYMBOL_GPL (usb_bus_idr); + +/* used when allocating bus numbers */ +#define USB_MAXBUS 64 + +/* used when updating list of hcds */ +DEFINE_MUTEX(usb_bus_idr_lock); /* exported only for usbfs */ +EXPORT_SYMBOL_GPL (usb_bus_idr_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); + +/*-------------------------------------------------------------------------*/ + +/* + * Sharable chunks of root hub code. + */ + +/*-------------------------------------------------------------------------*/ +#define KERNEL_REL bin2bcd(LINUX_VERSION_MAJOR) +#define KERNEL_VER bin2bcd(LINUX_VERSION_PATCHLEVEL) + +/* usb 3.1 root hub device descriptor */ +static const u8 usb31_rh_dev_descriptor[18] = { + 0x12, /* __u8 bLength; */ + USB_DT_DEVICE, /* __u8 bDescriptorType; Device */ + 0x10, 0x03, /* __le16 bcdUSB; v3.1 */ + + 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* __u8 bDeviceSubClass; */ + 0x03, /* __u8 bDeviceProtocol; USB 3 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 3.0 root hub device descriptor */ +static const u8 usb3_rh_dev_descriptor[18] = { + 0x12, /* __u8 bLength; */ + USB_DT_DEVICE, /* __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; */ + USB_DT_DEVICE, /* __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; */ + USB_DT_DEVICE, /* __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; */ + USB_DT_CONFIG, /* __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; */ + USB_DT_INTERFACE, /* __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; */ + USB_DT_ENDPOINT, /* __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; */ + USB_DT_CONFIG, /* __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; */ + USB_DT_INTERFACE, /* __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; */ + USB_DT_ENDPOINT, /* __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; */ + USB_DT_CONFIG, /* __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; */ + USB_DT_INTERFACE, /* __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; */ + USB_DT_ENDPOINT, /* __u8 ep_bDescriptorType; Endpoint */ + 0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* __u8 ep_bmAttributes; Interrupt */ + 0x02, 0x00, /* __le16 ep_wMaxPacketSize; 2 bytes per USB3 10.15.1 */ + 0x0c, /* __u8 ep_bInterval; (256ms -- usb 2.0 spec) */ + + /* one SuperSpeed endpoint companion descriptor */ + 0x06, /* __u8 ss_bLength */ + USB_DT_SS_ENDPOINT_COMP, /* __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 (leftover from wireless USB) + * 0 is unauthorized for all devices + * 1 is authorized for all devices + * 2 is authorized for internal devices + */ +#define USB_AUTHORIZE_WIRED -1 +#define USB_AUTHORIZE_NONE 0 +#define USB_AUTHORIZE_ALL 1 +#define USB_AUTHORIZE_INTERNAL 2 + +static int authorized_default = CONFIG_USB_DEFAULT_AUTHORIZATION_MODE; +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 (default), 2 is authorized for internal devices, -1 is authorized (same as 1)"); +/*-------------------------------------------------------------------------*/ + +/** + * 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. + * + * Return: The number of bytes filled in: 2 + 2*strlen(s) or @len, + * whichever is less. + * + * Note: + * 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. + * + * Return: 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[160]; + 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, gfp_t mem_flags) +{ + struct usb_ctrlrequest *cmd; + u16 typeReq, wValue, wIndex, wLength; + u8 *ubuf = urb->transfer_buffer; + unsigned len = 0; + int status; + u8 patch_wakeup = 0; + u8 patch_protocol = 0; + u16 tbuf_size; + u8 *tbuf = NULL; + const u8 *bufp; + + 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; + + /* + * tbuf should be at least as big as the + * USB hub descriptor. + */ + tbuf_size = max_t(u16, sizeof(struct usb_hub_descriptor), wLength); + tbuf = kzalloc(tbuf_size, mem_flags); + if (!tbuf) { + status = -ENOMEM; + goto err_alloc; + } + + bufp = tbuf; + + + 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_USB32: + case HCD_USB31: + bufp = usb31_rh_dev_descriptor; + break; + 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_USB32: + case HCD_USB31: + 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: + len = 4; + break; + case GetPortStatus: + if (wValue == HUB_PORT_STATUS) + len = 4; + else + /* other port status types return 8 bytes */ + len = 8; + 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; + } + + kfree(tbuf); + err_alloc: + + /* any errors get returned through the urb completion */ + spin_lock_irq(&hcd_root_hub_lock); + usb_hcd_unlink_urb_from_ep(hcd, urb); + usb_hcd_giveback_urb(hcd, urb, status); + 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. + * + * Completion handler may not sleep. See usb_hcd_giveback_urb() for details. + */ +void usb_hcd_poll_rh_status(struct usb_hcd *hcd) +{ + struct urb *urb; + int length; + int status; + 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; + if (urb->transfer_buffer_length >= length) { + status = 0; + } else { + status = -EOVERFLOW; + length = urb->transfer_buffer_length; + } + urb->actual_length = length; + memcpy(urb->transfer_buffer, buffer, length); + + usb_hcd_unlink_urb_from_ep(hcd, urb); + usb_hcd_giveback_urb(hcd, urb, status); + } 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 (struct timer_list *t) +{ + struct usb_hcd *_hcd = timer_container_of(_hcd, t, rh_timer); + + usb_hcd_poll_rh_status(_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, gfp_t mem_flags) +{ + 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, mem_flags); + 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) + timer_delete(&hcd->rh_timer); + if (urb == hcd->status_urb) { + hcd->status_urb = NULL; + usb_hcd_unlink_urb_from_ep(hcd, urb); + usb_hcd_giveback_urb(hcd, urb, status); + } + } + done: + spin_unlock_irqrestore(&hcd_root_hub_lock, flags); + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +/** + * 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(bus->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; + mutex_init(&bus->devnum_next_mutex); +} + +/*-------------------------------------------------------------------------*/ + +/** + * usb_register_bus - registers the USB host controller with the usb core + * @bus: pointer to the bus to register + * + * Context: task context, might sleep. + * + * Assigns a bus number, and links the controller into usbcore data + * structures so that it can be seen by scanning the bus list. + * + * Return: 0 if successful. A negative error code otherwise. + */ +static int usb_register_bus(struct usb_bus *bus) +{ + int result = -E2BIG; + int busnum; + + mutex_lock(&usb_bus_idr_lock); + busnum = idr_alloc(&usb_bus_idr, bus, 1, USB_MAXBUS, GFP_KERNEL); + if (busnum < 0) { + pr_err("%s: failed to get bus number\n", usbcore_name); + goto error_find_busnum; + } + bus->busnum = busnum; + mutex_unlock(&usb_bus_idr_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_idr_lock); + return result; +} + +/** + * usb_deregister_bus - deregisters the USB host controller + * @bus: pointer to the bus to deregister + * + * Context: task context, might sleep. + * + * 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_idr_lock); + idr_remove(&usb_bus_idr, bus->busnum); + mutex_unlock(&usb_bus_idr_lock); + + usb_notify_remove_bus(bus); +} + +/** + * 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). + * + * Return: 0 if successful. A negative error code otherwise. + */ +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; + struct usb_device_descriptor *descr; + const int devnum = 1; + int retval; + + usb_dev->devnum = devnum; + usb_dev->bus->devnum_next = devnum + 1; + set_bit(devnum, usb_dev->bus->devmap); + usb_set_device_state(usb_dev, USB_STATE_ADDRESS); + + mutex_lock(&usb_bus_idr_lock); + + usb_dev->ep0.desc.wMaxPacketSize = cpu_to_le16(64); + descr = usb_get_device_descriptor(usb_dev); + if (IS_ERR(descr)) { + retval = PTR_ERR(descr); + mutex_unlock(&usb_bus_idr_lock); + dev_dbg (parent_dev, "can't read %s device descriptor %d\n", + dev_name(&usb_dev->dev), retval); + return retval; + } + usb_dev->descriptor = *descr; + kfree(descr); + + if (le16_to_cpu(usb_dev->descriptor.bcdUSB) >= 0x0201) { + retval = usb_get_bos_descriptor(usb_dev); + if (!retval) { + usb_dev->lpm_capable = usb_device_supports_lpm(usb_dev); + } else if (usb_dev->speed >= USB_SPEED_SUPER) { + mutex_unlock(&usb_bus_idr_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_idr_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. + * + * Return: Approximate bus time in nanoseconds for a periodic transaction. + * + * Note: + * 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. + * + * Return: 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. + * + * Return: 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, + * or have restrictions on addressable DRAM. + * 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 capabilities we provide dma + * bounce buffers. This feature can be enabled by initializing + * hcd->localmem_pool using usb_hcd_setup_local_mem(). + * + * The initialized hcd->localmem_pool then tells the usb code to allocate all + * data for dma using the genalloc API. + * + * 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 + * + * - So we use that, even though the primary requirement + * is that the memory be "local" (hence addressable + * 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(unsigned long), + 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 (IS_ENABLED(CONFIG_HAS_DMA) && + (urb->transfer_flags & URB_SETUP_MAP_SINGLE)) + dma_unmap_single(hcd->self.sysdev, + 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 (IS_ENABLED(CONFIG_HAS_DMA) && + (urb->transfer_flags & URB_DMA_MAP_SG)) { + dma_unmap_sg(hcd->self.sysdev, + urb->sg, + urb->num_sgs, + dir); + } else if (IS_ENABLED(CONFIG_HAS_DMA) && + (urb->transfer_flags & URB_DMA_MAP_PAGE)) { + dma_unmap_page(hcd->self.sysdev, + urb->transfer_dma, + urb->transfer_buffer_length, + dir); + } else if (IS_ENABLED(CONFIG_HAS_DMA) && + (urb->transfer_flags & URB_DMA_MAP_SINGLE)) { + dma_unmap_single(hcd->self.sysdev, + 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); + } else if ((urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) && urb->sgt) { + dma_sync_sgtable_for_cpu(hcd->self.sysdev, urb->sgt, dir); + if (dir == DMA_FROM_DEVICE) + invalidate_kernel_vmap_range(urb->transfer_buffer, + urb->transfer_buffer_length); + } + + /* 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->localmem_pool) { + 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; + } else if (hcd_uses_dma(hcd)) { + if (object_is_on_stack(urb->setup_packet)) { + WARN_ONCE(1, "setup packet is on stack\n"); + return -EAGAIN; + } + + urb->setup_dma = dma_map_single( + hcd->self.sysdev, + urb->setup_packet, + sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + if (dma_mapping_error(hcd->self.sysdev, + urb->setup_dma)) + return -EAGAIN; + urb->transfer_flags |= URB_SETUP_MAP_SINGLE; + } + } + + dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) { + if (!urb->sgt) + return 0; + + if (dir == DMA_TO_DEVICE) + flush_kernel_vmap_range(urb->transfer_buffer, + urb->transfer_buffer_length); + dma_sync_sgtable_for_device(hcd->self.sysdev, urb->sgt, dir); + } else if (urb->transfer_buffer_length != 0) { + if (hcd->localmem_pool) { + 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; + } else if (hcd_uses_dma(hcd)) { + 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.sysdev, + urb->sg, + urb->num_sgs, + dir); + if (!n) + 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.sysdev, + sg_page(sg), + sg->offset, + urb->transfer_buffer_length, + dir); + if (dma_mapping_error(hcd->self.sysdev, + urb->transfer_dma)) + ret = -EAGAIN; + else + urb->transfer_flags |= URB_DMA_MAP_PAGE; + } else if (object_is_on_stack(urb->transfer_buffer)) { + WARN_ONCE(1, "transfer buffer is on stack\n"); + ret = -EAGAIN; + } else { + urb->transfer_dma = dma_map_single( + hcd->self.sysdev, + urb->transfer_buffer, + urb->transfer_buffer_length, + dir); + if (dma_mapping_error(hcd->self.sysdev, + urb->transfer_dma)) + ret = -EAGAIN; + else + urb->transfer_flags |= URB_DMA_MAP_SINGLE; + } + } + 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, mem_flags); + } 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); + /* + * Order the write of urb->use_count above before the read + * of urb->reject below. Pairs with the memory barriers in + * usb_kill_urb() and usb_poison_urb(). + */ + smp_mb__after_atomic(); + + 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; + struct usb_device *udev = urb->dev; + 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(udev); + } + spin_unlock_irqrestore(&hcd_urb_unlink_lock, flags); + if (retval == 0) { + hcd = bus_to_hcd(urb->dev->bus); + retval = unlink1(hcd, urb, status); + if (retval == 0) + retval = -EINPROGRESS; + else if (retval != -EIDRM && retval != -EBUSY) + dev_dbg(&udev->dev, "hcd_unlink_urb %p fail %d\n", + urb, retval); + usb_put_dev(udev); + } + return retval; +} + +/*-------------------------------------------------------------------------*/ + +static void __usb_hcd_giveback_urb(struct urb *urb) +{ + struct usb_hcd *hcd = bus_to_hcd(urb->dev->bus); + struct usb_anchor *anchor = urb->anchor; + int status = urb->unlinked; + + urb->hcpriv = NULL; + 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_anchor_suspend_wakeups(anchor); + usb_unanchor_urb(urb); + if (likely(status == 0)) + usb_led_activity(USB_LED_EVENT_HOST); + + /* pass ownership to the completion handler */ + urb->status = status; + /* + * This function can be called in task context inside another remote + * coverage collection section, but kcov doesn't support that kind of + * recursion yet. Only collect coverage in softirq context for now. + */ + kcov_remote_start_usb_softirq((u64)urb->dev->bus->busnum); + urb->complete(urb); + kcov_remote_stop_softirq(); + + usb_anchor_resume_wakeups(anchor); + atomic_dec(&urb->use_count); + /* + * Order the write of urb->use_count above before the read + * of urb->reject below. Pairs with the memory barriers in + * usb_kill_urb() and usb_poison_urb(). + */ + smp_mb__after_atomic(); + + if (unlikely(atomic_read(&urb->reject))) + wake_up(&usb_kill_urb_queue); + usb_put_urb(urb); +} + +static void usb_giveback_urb_bh(struct work_struct *work) +{ + struct giveback_urb_bh *bh = + container_of(work, struct giveback_urb_bh, bh); + struct list_head local_list; + + spin_lock_irq(&bh->lock); + bh->running = true; + list_replace_init(&bh->head, &local_list); + spin_unlock_irq(&bh->lock); + + while (!list_empty(&local_list)) { + struct urb *urb; + + urb = list_entry(local_list.next, struct urb, urb_list); + list_del_init(&urb->urb_list); + bh->completing_ep = urb->ep; + __usb_hcd_giveback_urb(urb); + bh->completing_ep = NULL; + } + + /* + * giveback new URBs next time to prevent this function + * from not exiting for a long time. + */ + spin_lock_irq(&bh->lock); + if (!list_empty(&bh->head)) { + if (bh->high_prio) + queue_work(system_bh_highpri_wq, &bh->bh); + else + queue_work(system_bh_wq, &bh->bh); + } + bh->running = false; + spin_unlock_irq(&bh->lock); +} + +/** + * 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: atomic. The completion callback is invoked either in a work queue + * (BH) context or in the caller's context, depending on whether the HCD_BH + * flag is set in the @hcd structure, except that URBs submitted to the + * root hub always complete in BH context. + * + * 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) +{ + struct giveback_urb_bh *bh; + bool running; + + /* pass status to BH via unlinked */ + if (likely(!urb->unlinked)) + urb->unlinked = status; + + if (!hcd_giveback_urb_in_bh(hcd) && !is_root_hub(urb->dev)) { + __usb_hcd_giveback_urb(urb); + return; + } + + if (usb_pipeisoc(urb->pipe) || usb_pipeint(urb->pipe)) + bh = &hcd->high_prio_bh; + else + bh = &hcd->low_prio_bh; + + spin_lock(&bh->lock); + list_add_tail(&urb->urb_list, &bh->head); + running = bh->running; + spin_unlock(&bh->lock); + + if (running) + ; + else if (bh->high_prio) + queue_work(system_bh_highpri_wq, &bh->bh); + else + queue_work(system_bh_wq, &bh->bh); +} +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_reverse(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", + usb_ep_type_string(usb_endpoint_type(&ep->desc))); + 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. + * + * Return: 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. + * + * Return: On success, the number of allocated streams. On failure, a negative + * error code. + */ +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, ret; + + 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; + + for (i = 0; i < num_eps; i++) { + /* Streams only apply to bulk endpoints. */ + if (!usb_endpoint_xfer_bulk(&eps[i]->desc)) + return -EINVAL; + /* Re-alloc is not allowed */ + if (eps[i]->streams) + return -EINVAL; + } + + ret = hcd->driver->alloc_streams(hcd, dev, eps, num_eps, + num_streams, mem_flags); + if (ret < 0) + return ret; + + for (i = 0; i < num_eps; i++) + eps[i]->streams = ret; + + return ret; +} +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. + * + * Return: 0 on success. On failure, a negative error code. + */ +int 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, ret; + + dev = interface_to_usbdev(interface); + hcd = bus_to_hcd(dev->bus); + if (dev->speed < USB_SPEED_SUPER) + return -EINVAL; + + /* Double-free is not allowed */ + for (i = 0; i < num_eps; i++) + if (!eps[i] || !eps[i]->streams) + return -EINVAL; + + ret = hcd->driver->free_streams(hcd, dev, eps, num_eps, mem_flags); + if (ret < 0) + return ret; + + for (i = 0; i < num_eps; i++) + eps[i]->streams = 0; + + return ret; +} +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_USB_HCD_TEST_MODE + +static void usb_ehset_completion(struct urb *urb) +{ + struct completion *done = urb->context; + + complete(done); +} +/* + * Allocate and initialize a control URB. This request will be used by the + * EHSET SINGLE_STEP_SET_FEATURE test in which the DATA and STATUS stages + * of the GetDescriptor request are sent 15 seconds after the SETUP stage. + * Return NULL if failed. + */ +static struct urb *request_single_step_set_feature_urb( + struct usb_device *udev, + void *dr, + void *buf, + struct completion *done) +{ + struct urb *urb; + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return NULL; + + urb->pipe = usb_rcvctrlpipe(udev, 0); + + urb->ep = &udev->ep0; + urb->dev = udev; + urb->setup_packet = (void *)dr; + urb->transfer_buffer = buf; + urb->transfer_buffer_length = USB_DT_DEVICE_SIZE; + urb->complete = usb_ehset_completion; + urb->status = -EINPROGRESS; + urb->actual_length = 0; + urb->transfer_flags = URB_DIR_IN | URB_NO_TRANSFER_DMA_MAP; + usb_get_urb(urb); + atomic_inc(&urb->use_count); + atomic_inc(&urb->dev->urbnum); + if (map_urb_for_dma(hcd, urb, GFP_KERNEL)) { + usb_put_urb(urb); + usb_free_urb(urb); + return NULL; + } + + urb->context = done; + return urb; +} + +int ehset_single_step_set_feature(struct usb_hcd *hcd, int port) +{ + int retval = -ENOMEM; + struct usb_ctrlrequest *dr; + struct urb *urb; + struct usb_device *udev; + struct usb_device_descriptor *buf; + DECLARE_COMPLETION_ONSTACK(done); + + /* Obtain udev of the rhub's child port */ + udev = usb_hub_find_child(hcd->self.root_hub, port); + if (!udev) { + dev_err(hcd->self.controller, "No device attached to the RootHub\n"); + return -ENODEV; + } + buf = kmalloc(USB_DT_DEVICE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + dr = kmalloc_obj(struct usb_ctrlrequest); + if (!dr) { + kfree(buf); + return -ENOMEM; + } + + /* Fill Setup packet for GetDescriptor */ + dr->bRequestType = USB_DIR_IN; + dr->bRequest = USB_REQ_GET_DESCRIPTOR; + dr->wValue = cpu_to_le16(USB_DT_DEVICE << 8); + dr->wIndex = 0; + dr->wLength = cpu_to_le16(USB_DT_DEVICE_SIZE); + urb = request_single_step_set_feature_urb(udev, dr, buf, &done); + if (!urb) + goto cleanup; + + /* Submit just the SETUP stage */ + retval = hcd->driver->submit_single_step_set_feature(hcd, urb, 1); + if (retval) + goto out1; + if (!wait_for_completion_timeout(&done, msecs_to_jiffies(2000))) { + usb_kill_urb(urb); + retval = -ETIMEDOUT; + dev_err(hcd->self.controller, + "%s SETUP stage timed out on ep0\n", __func__); + goto out1; + } + msleep(15 * 1000); + + /* Complete remaining DATA and STATUS stages using the same URB */ + urb->status = -EINPROGRESS; + urb->transfer_flags &= ~URB_NO_TRANSFER_DMA_MAP; + usb_get_urb(urb); + atomic_inc(&urb->use_count); + atomic_inc(&urb->dev->urbnum); + if (map_urb_for_dma(hcd, urb, GFP_KERNEL)) { + usb_put_urb(urb); + goto out1; + } + + retval = hcd->driver->submit_single_step_set_feature(hcd, urb, 0); + if (!retval && !wait_for_completion_timeout(&done, + msecs_to_jiffies(2000))) { + usb_kill_urb(urb); + retval = -ETIMEDOUT; + dev_err(hcd->self.controller, + "%s IN stage timed out on ep0\n", __func__); + } +out1: + usb_free_urb(urb); +cleanup: + kfree(dr); + kfree(buf); + return retval; +} +EXPORT_SYMBOL_GPL(ehset_single_step_set_feature); +#endif /* CONFIG_USB_HCD_TEST_MODE */ + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM + +int hcd_bus_suspend(struct usb_device *rhdev, pm_message_t msg) +{ + struct usb_hcd *hcd = bus_to_hcd(rhdev->bus); + 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; + + if (!PMSG_IS_AUTO(msg)) + usb_phy_roothub_suspend(hcd->self.sysdev, + hcd->phy_roothub); + + /* 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 = bus_to_hcd(rhdev->bus); + 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 (!PMSG_IS_AUTO(msg)) { + status = usb_phy_roothub_resume(hcd->self.sysdev, + hcd->phy_roothub); + if (status) + return status; + } + + 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) + status = usb_phy_roothub_calibrate(hcd->phy_roothub); + + 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; + usb_phy_roothub_suspend(hcd->self.sysdev, hcd->phy_roothub); + dev_dbg(&rhdev->dev, "bus %s fail, err %d\n", + "resume", status); + if (status != -ESHUTDOWN) + usb_hc_died(hcd); + } + return status; +} + +/* 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_remote_wakeup(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) { + pm_wakeup_event(&hcd->self.root_hub->dev, 0); + set_bit(HCD_FLAG_WAKEUP_PENDING, &hcd->flags); + queue_work(system_freezable_wq, &hcd->wakeup_work); + } + spin_unlock_irqrestore (&hcd_root_hub_lock, flags); +} +EXPORT_SYMBOL_GPL(usb_hcd_resume_root_hub); + +#endif /* CONFIG_PM */ + +/*-------------------------------------------------------------------------*/ + +#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: atomic + * + * Starts enumeration, with an immediate reset followed later by + * hub_wq 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. + * + * Return: 0 if successful. + */ +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 = bus_to_hcd(bus); + if (port_num && hcd->driver->start_port_reset) + status = hcd->driver->start_port_reset(hcd, port_num); + + /* allocate hub_wq 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. + * + * Return: %IRQ_HANDLED if the IRQ was handled. %IRQ_NONE otherwise. + */ +irqreturn_t usb_hcd_irq (int irq, void *__hcd) +{ + struct usb_hcd *hcd = __hcd; + irqreturn_t rc; + + 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; + + return rc; +} +EXPORT_SYMBOL_GPL(usb_hcd_irq); + +/*-------------------------------------------------------------------------*/ + +/* Workqueue routine for when the root-hub has died. */ +static void hcd_died_work(struct work_struct *work) +{ + struct usb_hcd *hcd = container_of(work, struct usb_hcd, died_work); + static char *env[] = { + "ERROR=DEAD", + NULL + }; + + /* Notify user space that the host controller has died */ + kobject_uevent_env(&hcd->self.root_hub->dev.kobj, KOBJ_OFFLINE, env); +} + +/** + * 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 hub_wq clean up old urbs and devices */ + usb_set_device_state (hcd->self.root_hub, + USB_STATE_NOTATTACHED); + usb_kick_hub_wq(hcd->self.root_hub); + } + if (usb_hcd_is_primary_hcd(hcd) && hcd->shared_hcd) { + hcd = hcd->shared_hcd; + 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 hub_wq clean up old urbs and devices */ + usb_set_device_state(hcd->self.root_hub, + USB_STATE_NOTATTACHED); + usb_kick_hub_wq(hcd->self.root_hub); + } + } + + /* Handle the case where this function gets called with a shared HCD */ + if (usb_hcd_is_primary_hcd(hcd)) + schedule_work(&hcd->died_work); + else + schedule_work(&hcd->primary_hcd->died_work); + + spin_unlock_irqrestore (&hcd_root_hub_lock, flags); + /* Make sure that the other roothub is also deallocated. */ +} +EXPORT_SYMBOL_GPL (usb_hc_died); + +/*-------------------------------------------------------------------------*/ + +static void init_giveback_urb_bh(struct giveback_urb_bh *bh) +{ + + spin_lock_init(&bh->lock); + INIT_LIST_HEAD(&bh->head); + INIT_WORK(&bh->bh, usb_giveback_urb_bh); +} + +struct usb_hcd *__usb_create_hcd(const struct hc_driver *driver, + struct device *sysdev, 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) + return NULL; + if (primary_hcd == NULL) { + hcd->address0_mutex = kmalloc_obj(*hcd->address0_mutex); + if (!hcd->address0_mutex) { + kfree(hcd); + dev_dbg(dev, "hcd address0 mutex alloc failed\n"); + return NULL; + } + mutex_init(hcd->address0_mutex); + hcd->bandwidth_mutex = kmalloc_obj(*hcd->bandwidth_mutex); + if (!hcd->bandwidth_mutex) { + kfree(hcd->address0_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 { + mutex_lock(&usb_port_peer_mutex); + hcd->address0_mutex = primary_hcd->address0_mutex; + 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; + mutex_unlock(&usb_port_peer_mutex); + } + + kref_init(&hcd->kref); + + usb_bus_init(&hcd->self); + hcd->self.controller = dev; + hcd->self.sysdev = sysdev; + hcd->self.bus_name = bus_name; + + timer_setup(&hcd->rh_timer, rh_timer_func, 0); +#ifdef CONFIG_PM + INIT_WORK(&hcd->wakeup_work, hcd_resume_work); +#endif + + INIT_WORK(&hcd->died_work, hcd_died_work); + + 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_hcd); + +/** + * 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: task context, might sleep. + * + * 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. + * + * Return: On success, a pointer to the created and initialized HCD structure. + * On failure (e.g. if memory is unavailable), %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) +{ + return __usb_create_hcd(driver, dev, dev, bus_name, primary_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: task context, might sleep. + * + * 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. + * + * Return: On success, a pointer to the created and initialized HCD + * structure. On failure (e.g. if memory is unavailable), %NULL. + */ +struct usb_hcd *usb_create_hcd(const struct hc_driver *driver, + struct device *dev, const char *bus_name) +{ + return __usb_create_hcd(driver, dev, 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 deallocate the bandwidth_mutex only when the last HCD is + * freed. When hcd_release() is called for either hcd in a peer set, + * invalidate the peer's ->shared_hcd and ->primary_hcd pointers. + */ +static void hcd_release(struct kref *kref) +{ + struct usb_hcd *hcd = container_of (kref, struct usb_hcd, kref); + + mutex_lock(&usb_port_peer_mutex); + if (hcd->shared_hcd) { + struct usb_hcd *peer = hcd->shared_hcd; + + peer->shared_hcd = NULL; + peer->primary_hcd = NULL; + } else { + kfree(hcd->address0_mutex); + kfree(hcd->bandwidth_mutex); + } + mutex_unlock(&usb_port_peer_mutex); + 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) { + + 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 port", + (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 port", + (unsigned long long)hcd->rsrc_start); + } + return 0; +} + +/* + * Before we free this root hub, flush in-flight peering attempts + * and disable peer lookups + */ +static void usb_put_invalidate_rhdev(struct usb_hcd *hcd) +{ + struct usb_device *rhdev; + + mutex_lock(&usb_port_peer_mutex); + rhdev = hcd->self.root_hub; + hcd->self.root_hub = NULL; + mutex_unlock(&usb_port_peer_mutex); + usb_put_dev(rhdev); +} + +/** + * usb_stop_hcd - Halt the HCD + * @hcd: the usb_hcd that has to be halted + * + * Stop the root-hub polling timer and invoke the HCD's ->stop callback. + */ +static void usb_stop_hcd(struct usb_hcd *hcd) +{ + hcd->rh_pollable = 0; + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + timer_delete_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); + timer_delete_sync(&hcd->rh_timer); +} + +/** + * 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; + struct usb_hcd *shared_hcd; + int skip_phy_initialization; + + if (usb_hcd_is_primary_hcd(hcd)) + skip_phy_initialization = hcd->skip_phy_initialization; + else + skip_phy_initialization = hcd->primary_hcd->skip_phy_initialization; + + if (!skip_phy_initialization) { + if (usb_hcd_is_primary_hcd(hcd)) { + hcd->phy_roothub = usb_phy_roothub_alloc(hcd->self.sysdev); + if (IS_ERR(hcd->phy_roothub)) + return PTR_ERR(hcd->phy_roothub); + } else { + hcd->phy_roothub = usb_phy_roothub_alloc_usb3_phy(hcd->self.sysdev); + if (IS_ERR(hcd->phy_roothub)) + return PTR_ERR(hcd->phy_roothub); + } + + retval = usb_phy_roothub_init(hcd->phy_roothub); + if (retval) + return retval; + + retval = usb_phy_roothub_set_mode(hcd->phy_roothub, + PHY_MODE_USB_HOST_SS); + if (retval) + retval = usb_phy_roothub_set_mode(hcd->phy_roothub, + PHY_MODE_USB_HOST); + if (retval) + goto err_usb_phy_roothub_power_on; + + retval = usb_phy_roothub_power_on(hcd->phy_roothub); + if (retval) + goto err_usb_phy_roothub_power_on; + } + + dev_info(hcd->self.controller, "%s\n", hcd->product_desc); + + switch (authorized_default) { + case USB_AUTHORIZE_NONE: + hcd->dev_policy = USB_DEVICE_AUTHORIZE_NONE; + break; + + case USB_AUTHORIZE_INTERNAL: + hcd->dev_policy = USB_DEVICE_AUTHORIZE_INTERNAL; + break; + + case USB_AUTHORIZE_ALL: + case USB_AUTHORIZE_WIRED: + default: + hcd->dev_policy = USB_DEVICE_AUTHORIZE_ALL; + break; + } + + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + /* per default all interfaces are authorized */ + set_bit(HCD_FLAG_INTF_AUTHORIZED, &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 hub_wq + * starts talking to them. (Note, bus id is assigned early too.) + */ + retval = hcd_buffer_create(hcd); + if (retval != 0) { + dev_dbg(hcd->self.sysdev, "pool alloc failed\n"); + goto err_create_buf; + } + + retval = usb_register_bus(&hcd->self); + if (retval < 0) + goto err_register_bus; + + rhdev = usb_alloc_dev(NULL, &hcd->self, 0); + if (rhdev == NULL) { + dev_err(hcd->self.sysdev, "unable to allocate root hub\n"); + retval = -ENOMEM; + goto err_allocate_root_hub; + } + mutex_lock(&usb_port_peer_mutex); + hcd->self.root_hub = rhdev; + mutex_unlock(&usb_port_peer_mutex); + + rhdev->rx_lanes = 1; + rhdev->tx_lanes = 1; + rhdev->ssp_rate = USB_SSP_GEN_UNKNOWN; + + 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; + case HCD_USB32: + rhdev->rx_lanes = 2; + rhdev->tx_lanes = 2; + rhdev->ssp_rate = USB_SSP_GEN_2x2; + rhdev->speed = USB_SPEED_SUPER_PLUS; + break; + case HCD_USB31: + rhdev->ssp_rate = USB_SSP_GEN_2x1; + rhdev->speed = USB_SPEED_SUPER_PLUS; + 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); + if (retval < 0) { + dev_err(hcd->self.controller, "can't setup: %d\n", + retval); + goto err_hcd_driver_setup; + } + } + hcd->rh_pollable = 1; + + retval = usb_phy_roothub_calibrate(hcd->phy_roothub); + if (retval) + goto err_hcd_driver_setup; + + /* 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"); + + /* initialize BHs */ + init_giveback_urb_bh(&hcd->high_prio_bh); + hcd->high_prio_bh.high_prio = true; + init_giveback_urb_bh(&hcd->low_prio_bh); + + /* 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 the shared HCD roothub */ + shared_hcd = hcd->shared_hcd; + if (!usb_hcd_is_primary_hcd(hcd) && shared_hcd && HCD_DEFER_RH_REGISTER(shared_hcd)) { + retval = register_root_hub(shared_hcd); + if (retval != 0) + goto err_register_root_hub; + + if (shared_hcd->uses_new_polling && HCD_POLL_RH(shared_hcd)) + usb_hcd_poll_rh_status(shared_hcd); + } + + /* starting here, usbcore will pay attention to this root hub */ + if (!HCD_DEFER_RH_REGISTER(hcd)) { + retval = register_root_hub(hcd); + if (retval != 0) + goto err_register_root_hub; + + if (hcd->uses_new_polling && HCD_POLL_RH(hcd)) + usb_hcd_poll_rh_status(hcd); + } + + return retval; + +err_register_root_hub: + usb_stop_hcd(hcd); +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_invalidate_rhdev(hcd); +err_allocate_root_hub: + usb_deregister_bus(&hcd->self); +err_register_bus: + hcd_buffer_destroy(hcd); +err_create_buf: + usb_phy_roothub_power_off(hcd->phy_roothub); +err_usb_phy_roothub_power_on: + usb_phy_roothub_exit(hcd->phy_roothub); + + return retval; +} +EXPORT_SYMBOL_GPL(usb_add_hcd); + +/** + * usb_remove_hcd - shutdown processing for generic HCDs + * @hcd: the usb_hcd structure to remove + * + * Context: task context, might sleep. + * + * 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; + bool rh_registered; + + if (!hcd) { + pr_debug("%s: hcd is NULL\n", __func__); + return; + } + rhdev = hcd->self.root_hub; + + dev_info(hcd->self.controller, "remove, state %x\n", hcd->state); + + usb_get_dev(rhdev); + 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); + rh_registered = hcd->rh_registered; + hcd->rh_registered = 0; + spin_unlock_irq (&hcd_root_hub_lock); + +#ifdef CONFIG_PM + cancel_work_sync(&hcd->wakeup_work); +#endif + cancel_work_sync(&hcd->died_work); + + mutex_lock(&usb_bus_idr_lock); + if (rh_registered) + usb_disconnect(&rhdev); /* Sets rhdev to NULL */ + mutex_unlock(&usb_bus_idr_lock); + + /* + * flush_work() isn't needed here because: + * - driver's disconnect() called from usb_disconnect() should + * make sure its URBs are completed during the disconnect() + * callback + * + * - it is too late to run complete() here since driver may have + * been removed already now + */ + + /* 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. + */ + usb_stop_hcd(hcd); + + if (usb_hcd_is_primary_hcd(hcd)) { + if (hcd->irq > 0) + free_irq(hcd->irq, hcd); + } + + usb_deregister_bus(&hcd->self); + hcd_buffer_destroy(hcd); + + usb_phy_roothub_power_off(hcd->phy_roothub); + usb_phy_roothub_exit(hcd->phy_roothub); + + usb_put_invalidate_rhdev(hcd); + hcd->flags = 0; +} +EXPORT_SYMBOL_GPL(usb_remove_hcd); + +void +usb_hcd_platform_shutdown(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + + /* No need for pm_runtime_put(), we're shutting down */ + pm_runtime_get_sync(&dev->dev); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} +EXPORT_SYMBOL_GPL(usb_hcd_platform_shutdown); + +int usb_hcd_setup_local_mem(struct usb_hcd *hcd, phys_addr_t phys_addr, + dma_addr_t dma, size_t size) +{ + int err; + void *local_mem; + + hcd->localmem_pool = devm_gen_pool_create(hcd->self.sysdev, 4, + dev_to_node(hcd->self.sysdev), + dev_name(hcd->self.sysdev)); + if (IS_ERR(hcd->localmem_pool)) + return PTR_ERR(hcd->localmem_pool); + + /* + * if a physical SRAM address was passed, map it, otherwise + * allocate system memory as a buffer. + */ + if (phys_addr) + local_mem = devm_memremap(hcd->self.sysdev, phys_addr, + size, MEMREMAP_WC); + else + local_mem = dmam_alloc_attrs(hcd->self.sysdev, size, &dma, + GFP_KERNEL, + DMA_ATTR_WRITE_COMBINE); + + if (IS_ERR_OR_NULL(local_mem)) { + if (!local_mem) + return -ENOMEM; + + return PTR_ERR(local_mem); + } + + /* + * Here we pass a dma_addr_t but the arg type is a phys_addr_t. + * It's not backed by system memory and thus there's no kernel mapping + * for it. + */ + err = gen_pool_add_virt(hcd->localmem_pool, (unsigned long)local_mem, + dma, size, dev_to_node(hcd->self.sysdev)); + if (err < 0) { + dev_err(hcd->self.sysdev, "gen_pool_add_virt failed with %d\n", + err); + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_hcd_setup_local_mem); + +/*-------------------------------------------------------------------------*/ + +#if IS_ENABLED(CONFIG_USB_MON) + +const 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(const 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 000000000..3345b3298 --- /dev/null +++ b/drivers/usb/core/hub.c @@ -0,0 +1,6580 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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) + * + * Released under the GPLv2 only. + */ + +#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 "hub.h" +#include "phy.h" +#include "otg_productlist.h" +#include "trace.h" + +#define USB_VENDOR_GENESYS_LOGIC 0x05e3 +#define USB_VENDOR_SMSC 0x0424 +#define USB_PRODUCT_USB5534B 0x5534 +#define USB_VENDOR_CYPRESS 0x04b4 +#define USB_PRODUCT_CY7C65632 0x6570 +#define USB_VENDOR_TEXAS_INSTRUMENTS 0x0451 +#define USB_PRODUCT_TUSB8041_USB3 0x8140 +#define USB_PRODUCT_TUSB8041_USB2 0x8142 +#define USB_VENDOR_MICROCHIP 0x0424 +#define USB_PRODUCT_USB4913 0x4913 +#define USB_PRODUCT_USB4914 0x4914 +#define USB_PRODUCT_USB4915 0x4915 +#define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND BIT(0) +#define HUB_QUIRK_DISABLE_AUTOSUSPEND BIT(1) +#define HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL BIT(2) + +#define USB_TP_TRANSMISSION_DELAY 40 /* ns */ +#define USB_TP_TRANSMISSION_DELAY_MAX 65535 /* ns */ +#define USB_PING_RESPONSE_TIME 400 /* ns */ +#define USB_REDUCE_FRAME_INTR_BINTERVAL 9 + +/* + * The SET_ADDRESS request timeout will be 500 ms when + * USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT quirk flag is set. + */ +#define USB_SHORT_SET_ADDRESS_REQ_TIMEOUT 500 /* ms */ + +/* + * Give SS hubs 200ms time after wake to train downstream links before + * assuming no port activity and allowing hub to runtime suspend back. + */ +#define USB_SS_PORT_U0_WAKE_TIME 200 /* ms */ + +/* 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); + +/* workqueue to process hub events */ +static struct workqueue_struct *hub_wq; +static void hub_event(struct work_struct *work); + +/* synchronize hub-port add/remove and peering operations */ +DEFINE_MUTEX(usb_port_peer_mutex); + +/* cycle leds on hubs that aren't blinking for attention */ +static bool blinkenlights; +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; +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 = true; +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 int hub_port_disable(struct usb_hub *hub, int port1, int set_state); +static bool hub_port_warm_reset_required(struct usb_hub *hub, int port1, + u16 portstatus); + +static inline char *portspeed(struct usb_hub *hub, int portstatus) +{ + if (hub_is_superspeedplus(hub->hdev)) + return "10.0 Gb/s"; + 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]); +} + +int usb_device_supports_lpm(struct usb_device *udev) +{ + /* Some devices have trouble with LPM */ + if (udev->quirks & USB_QUIRK_NO_LPM) + return 0; + + /* Skip if the device BOS descriptor couldn't be read */ + if (!udev->bos) + return 0; + + /* USB 2.1 (and greater) devices indicate LPM support through + * their USB 2.0 Extended Capabilities BOS descriptor. + */ + if (udev->speed == USB_SPEED_HIGH || udev->speed == USB_SPEED_FULL) { + if (udev->bos->ext_cap && + (USB_LPM_SUPPORT & + le32_to_cpu(udev->bos->ext_cap->bmAttributes))) + return 1; + return 0; + } + + /* + * According to the USB 3.0 spec, all USB 3.0 devices must support LPM. + * However, there are some that don't, and they set the U1/U2 exit + * latencies to zero. + */ + if (!udev->bos->ss_cap) { + dev_info(&udev->dev, "No LPM exit latency info found, disabling LPM.\n"); + return 0; + } + + if (udev->bos->ss_cap->bU1devExitLat == 0 && + udev->bos->ss_cap->bU2DevExitLat == 0) { + if (udev->parent) + dev_info(&udev->dev, "LPM exit latency is zeroed, disabling LPM.\n"); + else + dev_info(&udev->dev, "We don't know the algorithms for LPM for this host, disabling LPM.\n"); + return 0; + } + + if (!udev->parent || udev->parent->lpm_capable) + return 1; + return 0; +} + +/* + * Set the Maximum Exit Latency (MEL) for the host to wakup up the path from + * U1/U2, send a PING to the device and receive a PING_RESPONSE. + * See USB 3.1 section C.1.5.2 + */ +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; + + /* + * tMEL1. time to transition path from host to device into U0. + * MEL for parent already contains the delay up to parent, so only add + * the exit latency for the last link (pick the slower exit latency), + * and the hub header decode latency. See USB 3.1 section C 2.2.1 + * Store MEL in nanoseconds + */ + total_mel = hub_lpm_params->mel + + max(udev_exit_latency, hub_exit_latency) * 1000 + + hub->descriptor->u.ss.bHubHdrDecLat * 100; + + /* + * tMEL2. Time to submit PING packet. Sum of tTPTransmissionDelay for + * each link + wHubDelay for each hub. Add only for last link. + * tMEL4, the time for PING_RESPONSE to traverse upstream is similar. + * Multiply by 2 to include it as well. + */ + total_mel += (__le16_to_cpu(hub->descriptor->u.ss.wHubDelay) + + USB_TP_TRANSMISSION_DELAY) * 2; + + /* + * tMEL3, tPingResponse. Time taken by device to generate PING_RESPONSE + * after receiving PING. Also add 2100ns as stated in USB 3.1 C 1.5.2.4 + * to cover the delay if the PING_RESPONSE is queued behind a Max Packet + * Size DP. + * Note these delays should be added only once for the entire path, so + * add them to the MEL of the device connected to the roothub. + */ + if (!hub->hdev->parent) + total_mel += USB_PING_RESPONSE_TIME + 2100; + + 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. + */ + first_link_pel = max(udev_exit_latency, 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. + */ + udev_lpm_params->pel = max(first_link_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; + + /* Skip if the device BOS descriptor couldn't be read */ + if (!udev->bos) + 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 = le16_to_cpu(udev->bos->ss_cap->bU2DevExitLat); + hub_u1_del = udev->parent->bos->ss_cap->bU1devExitLat; + hub_u2_del = le16_to_cpu(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, + struct usb_hub_descriptor *desc) +{ + 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, desc, size, + USB_CTRL_GET_TIMEOUT); + if (hub_is_superspeed(hdev)) { + if (ret == size) + return ret; + } else if (ret >= USB_DT_HUB_NONVAR_SIZE + 2) { + /* Make sure we have the DeviceRemovable field. */ + size = USB_DT_HUB_NONVAR_SIZE + desc->bNbrPorts / 8 + 1; + if (ret < size) + return -EMSGSIZE; + 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); +} + +static char *to_led_name(int selector) +{ + switch (selector) { + case HUB_LED_AMBER: + return "amber"; + case HUB_LED_GREEN: + return "green"; + case HUB_LED_OFF: + return "off"; + case HUB_LED_AUTO: + return "auto"; + default: + return "??"; + } +} + +/* + * 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) +{ + struct usb_port *port_dev = hub->ports[port1 - 1]; + int status; + + status = set_port_feature(hub->hdev, (selector << 8) | port1, + USB_PORT_FEAT_INDICATOR); + dev_dbg(&port_dev->dev, "indicator %s status %d\n", + to_led_name(selector), 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 < hdev->maxchild; 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 %= hdev->maxchild; + set_port_led(hub, cursor + 1, HUB_LED_GREEN); + hub->indicator[cursor] = INDICATOR_CYCLE; + changed++; + } + if (changed) + queue_delayed_work(system_power_efficient_wq, + &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 + * USB 3.1 takes into use the wValue and wLength fields, spec Section 10.16.2.6 + */ +static int get_port_status(struct usb_device *hdev, int port1, + void *data, u16 value, u16 length) +{ + 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, value, + port1, data, length, USB_STS_TIMEOUT); + } + return status; +} + +static int hub_ext_port_status(struct usb_hub *hub, int port1, int type, + u16 *status, u16 *change, u32 *ext_status) +{ + int ret; + int len = 4; + + if (type != HUB_PORT_STATUS) + len = 8; + + mutex_lock(&hub->status_mutex); + ret = get_port_status(hub->hdev, port1, &hub->status->port, type, len); + if (ret < len) { + if (ret >= 0) + ret = -EIO; + if (ret != -ENODEV) + dev_dbg(hub->intfdev, + "get_port_status failed: err = %d\n", ret); + } else { + *status = le16_to_cpu(hub->status->port.wPortStatus); + *change = le16_to_cpu(hub->status->port.wPortChange); + if (type != HUB_PORT_STATUS && ext_status) + *ext_status = le32_to_cpu( + hub->status->port.dwExtPortStatus); + ret = 0; + } + mutex_unlock(&hub->status_mutex); + + /* + * There is no need to lock status_mutex here, because status_mutex + * protects hub->status, and the phy driver only checks the port + * status without changing the status. + */ + if (!ret) { + struct usb_device *hdev = hub->hdev; + + /* + * Only roothub will be notified of connection changes, + * since the USB PHY only cares about changes at the next + * level. + */ + if (is_root_hub(hdev)) { + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); + bool connect; + bool connect_change; + + connect_change = *change & USB_PORT_STAT_C_CONNECTION; + connect = *status & USB_PORT_STAT_CONNECTION; + if (connect_change && connect) + usb_phy_roothub_notify_connect(hcd->phy_roothub, port1 - 1); + else if (connect_change) + usb_phy_roothub_notify_disconnect(hcd->phy_roothub, port1 - 1); + } + } + + return ret; +} + +int usb_hub_port_status(struct usb_hub *hub, int port1, + u16 *status, u16 *change) +{ + return hub_ext_port_status(hub, port1, HUB_PORT_STATUS, + status, change, NULL); +} + +static void hub_resubmit_irq_urb(struct usb_hub *hub) +{ + unsigned long flags; + int status; + + spin_lock_irqsave(&hub->irq_urb_lock, flags); + + if (hub->quiescing) { + spin_unlock_irqrestore(&hub->irq_urb_lock, flags); + return; + } + + status = usb_submit_urb(hub->urb, GFP_ATOMIC); + if (status && status != -ENODEV && status != -EPERM && + status != -ESHUTDOWN) { + dev_err(hub->intfdev, "resubmit --> %d\n", status); + mod_timer(&hub->irq_urb_retry, jiffies + HZ); + } + + spin_unlock_irqrestore(&hub->irq_urb_lock, flags); +} + +static void hub_retry_irq_urb(struct timer_list *t) +{ + struct usb_hub *hub = timer_container_of(hub, t, irq_urb_retry); + + hub_resubmit_irq_urb(hub); +} + + +static void kick_hub_wq(struct usb_hub *hub) +{ + struct usb_interface *intf; + + if (hub->disconnected || work_pending(&hub->events)) + return; + + /* + * Suppress autosuspend until the event is proceed. + * + * Be careful and make sure that the symmetric operation is + * always called. We are here only when there is no pending + * work for this hub. Therefore put the interface either when + * the new work is called or when it is canceled. + */ + intf = to_usb_interface(hub->intfdev); + usb_autopm_get_interface_no_resume(intf); + hub_get(hub); + + if (queue_work(hub_wq, &hub->events)) + return; + + /* the work has already been scheduled */ + usb_autopm_put_interface_async(intf); + hub_put(hub); +} + +void usb_kick_hub_wq(struct usb_device *hdev) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + + if (hub) + kick_hub_wq(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; + struct usb_port *port_dev; + unsigned long flags; + + if (!hdev) + return; + + spin_lock_irqsave(&device_state_lock, flags); + hub = usb_hub_to_struct_hub(hdev); + if (hub) { + port_dev = hub->ports[portnum - 1]; + if (port_dev && port_dev->child) + pm_wakeup_event(&port_dev->child->dev, 0); + + set_bit(portnum, hub->wakeup_bits); + kick_hub_wq(hub); + } + spin_unlock_irqrestore(&device_state_lock, flags); +} +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; + fallthrough; + + /* let hub_wq 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 hub_wq figure it out */ + kick_hub_wq(hub); + +resubmit: + hub_resubmit_irq_urb(hub); +} + +/* 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 hub_wq 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: USB device belonging to the usb hub + * @hub: 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. + * + * Return: 0 if successful. A negative error code otherwise. + */ +int usb_hub_set_port_power(struct usb_device *hdev, struct usb_hub *hub, + int port1, bool set) +{ + int ret; + + 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) + return ret; + + if (set) + set_bit(port1, hub->power_bits); + else + clear_bit(port1, hub->power_bits); + return 0; +} + +/** + * 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. + * + * Return: 0 if successful. A negative error code otherwise. + */ +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. + */ + clear = kmalloc_obj(*clear, GFP_ATOMIC); + if (clear == 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 |= ((u16)udev->devaddr) << 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); + +static void hub_power_on(struct usb_hub *hub, bool do_delay) +{ + int port1; + + /* 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 (hub_is_port_power_switchable(hub)) + 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->hdev->maxchild; port1++) + if (test_bit(port1, hub->power_bits)) + set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER); + else + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_POWER); + if (do_delay) + msleep(hub_power_on_good_delay(hub)); +} + +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 < (int)sizeof(hub->status->hub)) { + if (ret >= 0) + ret = -EIO; + if (ret != -ENODEV) + dev_dbg(hub->intfdev, + "get_hub_status failed: err = %d\n", 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); +} + +/* + * Disable a port and mark a logical connect-change event, so that some + * time later hub_wq 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->ports[port1 - 1]->dev, "logical disconnect\n"); + 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 + * hub_wq reactivates the port later (timer, SRP, etc). + * Powerdown must be optional, because of reset/DFU. + */ + + set_bit(port1, hub->change_bits); + kick_hub_wq(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, hub_wq 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. + * + * Return: 0 if successful. A negative error code otherwise. + */ +int usb_remove_device(struct usb_device *udev) +{ + struct usb_hub *hub; + struct usb_interface *intf; + int ret; + + 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); + + ret = usb_autopm_get_interface(intf); + if (ret < 0) + return ret; + + 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 || type == HUB_INIT3) { + device_lock(&hdev->dev); + + /* Was the hub disconnected while we were waiting? */ + if (hub->disconnected) + goto disconnected; + if (type == HUB_INIT2) + goto init2; + goto init3; + } + + hub_get(hub); + + /* 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_good_delay(hub); + + hub_power_on(hub, false); + INIT_DELAYED_WORK(&hub->init_work, hub_init_func2); + queue_delayed_work(system_power_efficient_wq, + &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); + } + /* Give some time on remote wakeup to let links to transit to U0 */ + } else if (hub_is_superspeed(hub->hdev)) + msleep(20); + + init2: + + /* + * Check each port and set hub->change_bits to let hub_wq know + * which ports need attention. + */ + for (port1 = 1; port1 <= hdev->maxchild; ++port1) { + struct usb_port *port_dev = hub->ports[port1 - 1]; + struct usb_device *udev = port_dev->child; + u16 portstatus, portchange; + + portstatus = portchange = 0; + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); + if (status) + goto abort; + + if (udev || (portstatus & USB_PORT_STAT_CONNECTION)) + dev_dbg(&port_dev->dev, "status %04x change %04x\n", + 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, just pretend + * power was lost + */ + portstatus &= ~USB_PORT_STAT_ENABLE; + if (!hub_is_superspeed(hdev)) + usb_clear_port_feature(hdev, port1, + USB_PORT_FEAT_ENABLE); + } + + /* Make sure a warm-reset request is handled by port_event */ + if (type == HUB_RESUME && + hub_port_warm_reset_required(hub, port1, portstatus)) + set_bit(port1, hub->event_bits); + + /* + * Add debounce if USB3 link is in polling/link training state. + * Link will automatically transition to Enabled state after + * link training completes. + */ + if (hub_is_superspeed(hdev) && + ((portstatus & USB_PORT_STAT_LINK_STATE) == + USB_SS_PORT_LS_POLLING)) + need_debounce_delay = true; + + /* 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 hub_wq to disconnect the device or + * check for a new connection or over current condition. + * Based on USB2.0 Spec Section 11.12.5, + * C_PORT_OVER_CURRENT could be set while + * PORT_OVER_CURRENT is not. So check for any of them. + */ + if (udev || (portstatus & USB_PORT_STAT_CONNECTION) || + (portchange & USB_PORT_STAT_C_CONNECTION) || + (portstatus & USB_PORT_STAT_OVERCURRENT) || + (portchange & USB_PORT_STAT_C_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 hub_wq + * 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->event_bits); + + } else if (udev->persist_enabled) { +#ifdef CONFIG_PM + udev->reset_resume = 1; +#endif + /* Don't set the change_bits when the device + * was powered off. + */ + if (test_bit(port1, hub->power_bits)) + set_bit(port1, hub->change_bits); + + } else { + /* The power session is gone; tell hub_wq */ + 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 hub_wq do them + * one at a time later on. + * + * If any port-status changes do occur during this delay, hub_wq + * 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) { + INIT_DELAYED_WORK(&hub->init_work, hub_init_func3); + queue_delayed_work(system_power_efficient_wq, + &hub->init_work, + msecs_to_jiffies(delay)); + device_unlock(&hdev->dev); + 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) + queue_delayed_work(system_power_efficient_wq, + &hub->leds, LED_CYCLE_PERIOD); + + /* Scan all ports that need attention */ + kick_hub_wq(hub); + abort: + if (type == HUB_INIT2 || type == HUB_INIT3) { + /* Allow autosuspend if it was suppressed */ + disconnected: + usb_autopm_put_interface_async(to_usb_interface(hub->intfdev)); + device_unlock(&hdev->dev); + } + + if (type == HUB_RESUME && hub_is_superspeed(hub->hdev)) { + /* give usb3 downstream links training time after hub resume */ + usb_autopm_get_interface_no_resume( + to_usb_interface(hub->intfdev)); + + queue_delayed_work(system_power_efficient_wq, + &hub->post_resume_work, + msecs_to_jiffies(USB_SS_PORT_U0_WAKE_TIME)); + return; + } + + hub_put(hub); +} + +/* 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); +} + +static void hub_post_resume(struct work_struct *ws) +{ + struct usb_hub *hub = container_of(ws, struct usb_hub, post_resume_work.work); + + usb_autopm_put_interface_async(to_usb_interface(hub->intfdev)); + hub_put(hub); +} + +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; + unsigned long flags; + int i; + + /* hub_wq and related activity won't re-trigger */ + spin_lock_irqsave(&hub->irq_urb_lock, flags); + hub->quiescing = 1; + spin_unlock_irqrestore(&hub->irq_urb_lock, flags); + + 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 hub_wq and related activity */ + timer_delete_sync(&hub->irq_urb_retry); + flush_delayed_work(&hub->post_resume_work); + 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); +} + +static void hub_pm_barrier_for_all_ports(struct usb_hub *hub) +{ + int i; + + for (i = 0; i < hub->hdev->maxchild; ++i) + pm_runtime_barrier(&hub->ports[i]->dev); +} + +/* 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); + hub->in_reset = 1; + hub_pm_barrier_for_all_ports(hub); + 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->in_reset = 0; + hub_pm_barrier_for_all_ports(hub); + 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; + unsigned maxchild; + + hub->buffer = kmalloc_obj(*hub->buffer); + if (!hub->buffer) { + ret = -ENOMEM; + goto fail; + } + + hub->status = kmalloc_obj(*hub->status); + if (!hub->status) { + ret = -ENOMEM; + goto fail; + } + mutex_init(&hub->status_mutex); + + hub->descriptor = kzalloc_obj(*hub->descriptor); + if (!hub->descriptor) { + ret = -ENOMEM; + goto fail; + } + + /* Request the entire hub descriptor. + * hub->descriptor can handle USB_MAXCHILDREN ports, + * but a (non-SS) 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; + } + + maxchild = USB_MAXCHILDREN; + if (hub_is_superspeed(hdev)) + maxchild = min_t(unsigned, maxchild, USB_SS_MAXPORTS); + + if (hub->descriptor->bNbrPorts > maxchild) { + 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; + } + + /* + * Accumulate wHubDelay + 40ns for every hub in the tree of devices. + * The resulting value will be used for SetIsochDelay() request. + */ + if (hub_is_superspeed(hdev) || hub_is_superspeedplus(hdev)) { + u32 delay = __le16_to_cpu(hub->descriptor->u.ss.wHubDelay); + + if (hdev->parent) + delay += hdev->parent->hub_delay; + + delay += USB_TP_TRANSMISSION_DELAY; + hdev->hub_delay = min_t(u32, delay, USB_TP_TRANSMISSION_DELAY_MAX); + } + + maxchild = hub->descriptor->bNbrPorts; + dev_info(hub_dev, "%d port%s detected\n", maxchild, + str_plural(maxchild)); + + hub->ports = kzalloc_objs(struct usb_port *, maxchild); + 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))) { + char portstr[USB_MAXCHILDREN + 1]; + + for (i = 0; i < maxchild; i++) + portstr[i] = hub->descriptor->u.hs.DeviceRemovable + [((i + 1) / 8)] & (1 << ((i + 1) % 8)) + ? 'F' : 'R'; + portstr[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_std_status(hdev, USB_RECIP_DEVICE, 0, &hubstatus); + if (ret) { + message = "can't get hub status"; + goto fail; + } + 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 * 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 < 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); + + 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); + + 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; + + mutex_lock(&usb_port_peer_mutex); + for (i = 0; i < 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); + break; + } + } + hdev->maxchild = i; + for (i = 0; i < hdev->maxchild; i++) { + struct usb_port *port_dev = hub->ports[i]; + + pm_runtime_put(&port_dev->dev); + } + + mutex_unlock(&usb_port_peer_mutex); + if (ret < 0) + goto fail; + + /* Update the HCD's internal representation of this hub before hub_wq + * 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; + } + } + + usb_hub_adjust_deviceremovable(hdev, hub->descriptor); + + hub_activate(hub, HUB_INIT); + return 0; + +fail: + 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_dev(hub->hdev); + usb_put_intf(to_usb_interface(hub->intfdev)); + kfree(hub); +} + +void hub_get(struct usb_hub *hub) +{ + kref_get(&hub->kref); +} + +void hub_put(struct usb_hub *hub) +{ + kref_put(&hub->kref, hub_release); +} + +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; + + /* + * Stop adding new hub events. We do not want to block here and thus + * will not try to remove any pending work item. + */ + hub->disconnected = 1; + + /* Disconnect all children and quiesce the hub */ + hub->error = 0; + hub_quiesce(hub, HUB_DISCONNECT); + + mutex_lock(&usb_port_peer_mutex); + + /* 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); + + mutex_unlock(&usb_port_peer_mutex); + + 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); + + if (hub->quirk_disable_autosuspend) + usb_autopm_put_interface(intf); + + onboard_dev_destroy_pdevs(&hub->onboard_devs); + + hub_put(hub); +} + +static bool hub_descriptor_is_sane(struct usb_host_interface *desc) +{ + /* 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) + return false; + + /* Multiple endpoints? What kind of mutant ninja-hub is this? */ + if (desc->desc.bNumEndpoints != 1) + return false; + + /* If the first endpoint is not interrupt IN, we'd better punt! */ + if (!usb_endpoint_is_int_in(&desc->endpoint[0].desc)) + return false; + + return true; +} + +static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_host_interface *desc; + struct usb_device *hdev; + struct usb_hub *hub; + + desc = intf->cur_altsetting; + hdev = interface_to_usbdev(intf); + + /* + * The USB 2.0 spec prohibits hubs from having more than one + * configuration or interface, and we rely on this prohibition. + * Refuse to accept a device that violates it. + */ + if (hdev->descriptor.bNumConfigurations > 1 || + hdev->actconfig->desc.bNumInterfaces > 1) { + dev_err(&intf->dev, "Invalid hub with more than one config or interface\n"); + return -EINVAL; + } + + /* + * 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 + 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_DISABLE_EXTERNAL_HUB + if (hdev->parent) { + dev_warn(&intf->dev, "ignoring external hub\n"); + return -ENODEV; + } +#endif + + if (!hub_descriptor_is_sane(desc)) { + dev_err(&intf->dev, "bad descriptor, ignoring hub\n"); + return -EIO; + } + + /* We found a hub */ + dev_info(&intf->dev, "USB hub found\n"); + + hub = kzalloc_obj(*hub); + if (!hub) + return -ENOMEM; + + kref_init(&hub->kref); + hub->intfdev = &intf->dev; + hub->hdev = hdev; + INIT_DELAYED_WORK(&hub->leds, led_work); + INIT_DELAYED_WORK(&hub->init_work, NULL); + INIT_DELAYED_WORK(&hub->post_resume_work, hub_post_resume); + INIT_WORK(&hub->events, hub_event); + INIT_LIST_HEAD(&hub->onboard_devs); + spin_lock_init(&hub->irq_urb_lock); + timer_setup(&hub->irq_urb_retry, hub_retry_irq_urb, 0); + usb_get_intf(intf); + usb_get_dev(hdev); + + 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 (id->driver_info & HUB_QUIRK_DISABLE_AUTOSUSPEND) { + hub->quirk_disable_autosuspend = 1; + usb_autopm_get_interface_no_resume(intf); + } + + if ((id->driver_info & HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL) && + desc->endpoint[0].desc.bInterval > USB_REDUCE_FRAME_INTR_BINTERVAL) { + desc->endpoint[0].desc.bInterval = + USB_REDUCE_FRAME_INTR_BINTERVAL; + /* Tell the HCD about the interrupt ep's new bInterval */ + usb_set_interface(hdev, 0, 0); + } + + if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) { + onboard_dev_create_pdevs(hdev, &hub->onboard_devs); + + 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 usb_dev_state ***ppowner) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + + if (hdev->state == USB_STATE_NOTATTACHED) + return -ENODEV; + if (port1 == 0 || port1 > hdev->maxchild) + return -EINVAL; + + /* Devices not managed by the hub driver + * will always have maxchild equal to 0. + */ + *ppowner = &(hub->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 usb_dev_state *owner) +{ + int rc; + struct usb_dev_state **powner; + + rc = find_port_owner(hdev, port1, &powner); + if (rc) + return rc; + if (*powner) + return -EBUSY; + *powner = owner; + return rc; +} +EXPORT_SYMBOL_GPL(usb_hub_claim_port); + +int usb_hub_release_port(struct usb_device *hdev, unsigned port1, + struct usb_dev_state *owner) +{ + int rc; + struct usb_dev_state **powner; + + rc = find_port_owner(hdev, port1, &powner); + if (rc) + return rc; + if (*powner != owner) + return -ENOENT; + *powner = NULL; + return rc; +} +EXPORT_SYMBOL_GPL(usb_hub_release_port); + +void usb_hub_release_all_ports(struct usb_device *hdev, struct usb_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 update_port_device_state(struct usb_device *udev) +{ + struct usb_hub *hub; + struct usb_port *port_dev; + + if (udev->parent) { + hub = usb_hub_to_struct_hub(udev->parent); + + /* + * The Link Layer Validation System Driver (lvstest) + * has a test step to unbind the hub before running the + * rest of the procedure. This triggers hub_disconnect + * which will set the hub's maxchild to 0, further + * resulting in usb_hub_to_struct_hub returning NULL. + */ + if (hub) { + port_dev = hub->ports[udev->portnum - 1]; + WRITE_ONCE(port_dev->state, udev->state); + sysfs_notify_dirent(port_dev->state_kn); + } + } +} + +static void update_usb_device_state(struct usb_device *udev, + enum usb_device_state new_state) +{ + 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; + update_port_device_state(udev); + trace_usb_set_device_state(udev); +} + +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); + } + update_usb_device_state(udev, 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; + } + update_usb_device_state(udev, 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. + * + * 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; + + /* be safe when more hub events are proceed in parallel */ + mutex_lock(&bus->devnum_next_mutex); + + /* Try to allocate the next devnum beginning at bus->devnum_next. */ + devnum = find_next_zero_bit(bus->devmap, 128, bus->devnum_next); + if (devnum >= 128) + devnum = find_next_zero_bit(bus->devmap, 128, 1); + bus->devnum_next = (devnum >= 127 ? 1 : devnum + 1); + if (devnum < 128) { + set_bit(devnum, bus->devmap); + udev->devnum = devnum; + } + mutex_unlock(&bus->devnum_next_mutex); +} + +static void release_devnum(struct usb_device *udev) +{ + if (udev->devnum > 0) { + clear_bit(udev->devnum, udev->bus->devmap); + udev->devnum = -1; + } +} + +static void update_devnum(struct usb_device *udev, int devnum) +{ + udev->devnum = devnum; + if (!udev->devaddr) + udev->devaddr = (u8)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); +} + +static void hub_disconnect_children(struct usb_device *udev) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(udev); + int i; + + /* 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); + } +} + +/** + * usb_disconnect - disconnect a device (usbcore-internal) + * @pdev: pointer to device being disconnected + * + * Context: task context, might sleep + * + * 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 the caller must hold the usb_bus_idr_lock, + * which protects the set of root hubs as well as the list of buses. + * + * 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_port *port_dev = NULL; + struct usb_device *udev = *pdev; + struct usb_hub *hub = NULL; + int port1 = 1; + + /* 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); + + /* + * Ensure that the pm runtime code knows that the USB device + * is in the process of being disconnected. + */ + pm_runtime_barrier(&udev->dev); + + usb_lock_device(udev); + + hub_disconnect_children(udev); + + /* 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) { + port1 = udev->portnum; + hub = usb_hub_to_struct_hub(udev->parent); + port_dev = hub->ports[port1 - 1]; + + sysfs_remove_link(&udev->dev.kobj, "port"); + sysfs_remove_link(&port_dev->dev.kobj, "device"); + + /* + * As usb_port_runtime_resume() de-references udev, make + * sure no resumes occur during removal + */ + if (!test_and_set_bit(port1, hub->child_usage_bits)) + pm_runtime_get_sync(&port_dev->dev); + + typec_deattach(port_dev->connector, &udev->dev); + } + + usb_remove_ep_devs(&udev->ep0); + usb_unlock_device(udev); + + if (udev->usb4_link) + device_link_del(udev->usb4_link); + + /* 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); + + if (port_dev && test_and_clear_bit(port1, hub->child_usage_bits)) + pm_runtime_put(&port_dev->dev); + + 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_ids(struct usb_device *udev) +{ + u16 bcdDevice = le16_to_cpu(udev->descriptor.bcdDevice); + + dev_info(&udev->dev, + "New USB device found, idVendor=%04x, idProduct=%04x, bcdDevice=%2x.%02x\n", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct), + bcdDevice >> 8, bcdDevice & 0xff); +} + +static void announce_device_strings(struct usb_device *udev) +{ + 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_ids(struct usb_device *udev) { } +static inline void announce_device_strings(struct usb_device *udev) { } +#endif + + +/** + * usb_enumerate_device_otg - FIXME (usbcore-internal) + * @udev: newly addressed device (in ADDRESS state) + * + * Finish enumeration for On-The-Go devices + * + * Return: 0 if successful. A negative error code otherwise. + */ +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; + unsigned port1 = udev->portnum; + + /* descriptor may appear anywhere in config */ + err = __usb_get_extra_descriptor(udev->rawdescriptors[0], + le16_to_cpu(udev->config[0].desc.wTotalLength), + USB_DT_OTG, (void **) &desc, sizeof(*desc)); + if (err || !(desc->bmAttributes & USB_OTG_HNP)) + return 0; + + 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, + USB_DEVICE_B_HNP_ENABLE, + 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); + if (err < 0) { + /* + * OTG MESSAGE: report errors here, + * customize to match your product. + */ + dev_err(&udev->dev, "can't set HNP mode: %d\n", + err); + bus->b_hnp_enable = 0; + } + } else if (desc->bLength == sizeof + (struct usb_otg_descriptor)) { + /* + * We are operating on a legacy OTP device + * These should be told that they are operating + * on the wrong port if we have another port that does + * support HNP + */ + if (bus->otg_port != 0) { + /* Set a_alt_hnp_support for legacy otg device */ + err = usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE, 0, + USB_DEVICE_A_ALT_HNP_SUPPORT, + 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); + if (err < 0) + dev_err(&udev->dev, + "set a_alt_hnp_support failed: %d\n", + err); + } + } + } +#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() -- all comments that apply there + * 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. + * + * Return: 0 if successful. A negative error code otherwise. + */ +static int usb_enumerate_device(struct usb_device *udev) +{ + int err; + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + 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; + + if (IS_ENABLED(CONFIG_USB_OTG_PRODUCTLIST) && hcd->tpl_support && + !is_targeted(udev)) { + /* Maybe it can talk to us, though we can't talk to it. + * (Includes HNP test device.) + */ + if (IS_ENABLED(CONFIG_USB_OTG) && (udev->bus->b_hnp_enable + || udev->bus->is_b_host)) { + err = usb_port_suspend(udev, PMSG_AUTO_SUSPEND); + if (err < 0) + dev_dbg(&udev->dev, "HNP fail, %d\n", err); + } + return -ENOTSUPP; + } + + 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; + + dev_set_removable(&udev->dev, DEVICE_REMOVABLE_UNKNOWN); + + if (!hdev) + return; + + hub = usb_hub_to_struct_hub(udev->parent); + + /* + * If the platform firmware has provided information about a port, + * use that to determine whether it's removable. + */ + switch (hub->ports[udev->portnum - 1]->connect_type) { + case USB_PORT_CONNECT_TYPE_HOT_PLUG: + dev_set_removable(&udev->dev, DEVICE_REMOVABLE); + return; + case USB_PORT_CONNECT_TYPE_HARD_WIRED: + case USB_PORT_NOT_USED: + dev_set_removable(&udev->dev, DEVICE_FIXED); + return; + default: + break; + } + + /* + * Otherwise, check whether the hub knows whether a port is removable + * or not + */ + 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) + dev_set_removable(&udev->dev, DEVICE_REMOVABLE); + else + dev_set_removable(&udev->dev, 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_idr_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. + * + * 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. + * + * Return: Whether the device is configured properly or not. Zero if the + * interface was registered with the driver core; else a negative errno + * value. + * + */ +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); + } + + /* Announce the device identity */ + announce_device_ids(udev); + + /* 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))); + + /* Announce the device's product, manufacturer and serial number */ + announce_device_strings(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 or firmware marks this port as non-removable */ + 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); + int port1 = udev->portnum; + struct usb_port *port_dev = hub->ports[port1 - 1]; + + err = sysfs_create_link(&udev->dev.kobj, + &port_dev->dev.kobj, "port"); + if (err) + goto out_del_dev; + + err = sysfs_create_link(&port_dev->dev.kobj, + &udev->dev.kobj, "device"); + if (err) { + sysfs_remove_link(&udev->dev.kobj, "port"); + goto out_del_dev; + } + + if (!test_and_set_bit(port1, hub->child_usage_bits)) + pm_runtime_get_sync(&port_dev->dev); + + typec_attach(port_dev->connector, &udev->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; + +out_del_dev: + device_del(&udev->dev); +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. + * + * Return: 0. + */ +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; + } + + 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"); + + usb_autosuspend_device(usb_dev); +error_autoresume: +out_authorized: + usb_unlock_device(usb_dev); /* complements locktree */ + return result; +} + +/** + * get_port_ssp_rate - Match the extended port status to SSP rate + * @hdev: The hub device + * @ext_portstatus: extended port status + * + * Match the extended port status speed id to the SuperSpeed Plus sublink speed + * capability attributes. Base on the number of connected lanes and speed, + * return the corresponding enum usb_ssp_rate. + */ +static enum usb_ssp_rate get_port_ssp_rate(struct usb_device *hdev, + u32 ext_portstatus) +{ + struct usb_ssp_cap_descriptor *ssp_cap; + u32 attr; + u8 speed_id; + u8 ssac; + u8 lanes; + int i; + + if (!hdev->bos) + goto out; + + ssp_cap = hdev->bos->ssp_cap; + if (!ssp_cap) + goto out; + + speed_id = ext_portstatus & USB_EXT_PORT_STAT_RX_SPEED_ID; + lanes = USB_EXT_PORT_RX_LANES(ext_portstatus) + 1; + + ssac = le32_to_cpu(ssp_cap->bmAttributes) & + USB_SSP_SUBLINK_SPEED_ATTRIBS; + + for (i = 0; i <= ssac; i++) { + u8 ssid; + + attr = le32_to_cpu(ssp_cap->bmSublinkSpeedAttr[i]); + ssid = FIELD_GET(USB_SSP_SUBLINK_SPEED_SSID, attr); + if (speed_id == ssid) { + u16 mantissa; + u8 lse; + u8 type; + + /* + * Note: currently asymmetric lane types are only + * applicable for SSIC operate in SuperSpeed protocol + */ + type = FIELD_GET(USB_SSP_SUBLINK_SPEED_ST, attr); + if (type == USB_SSP_SUBLINK_SPEED_ST_ASYM_RX || + type == USB_SSP_SUBLINK_SPEED_ST_ASYM_TX) + goto out; + + if (FIELD_GET(USB_SSP_SUBLINK_SPEED_LP, attr) != + USB_SSP_SUBLINK_SPEED_LP_SSP) + goto out; + + lse = FIELD_GET(USB_SSP_SUBLINK_SPEED_LSE, attr); + mantissa = FIELD_GET(USB_SSP_SUBLINK_SPEED_LSM, attr); + + /* Convert to Gbps */ + for (; lse < USB_SSP_SUBLINK_SPEED_LSE_GBPS; lse++) + mantissa /= 1000; + + if (mantissa >= 10 && lanes == 1) + return USB_SSP_GEN_2x1; + + if (mantissa >= 10 && lanes == 2) + return USB_SSP_GEN_2x2; + + if (mantissa >= 5 && lanes == 2) + return USB_SSP_GEN_1x2; + + goto out; + } + } + +out: + return USB_SSP_GEN_UNKNOWN; +} + +#ifdef CONFIG_USB_FEW_INIT_RETRIES +#define PORT_RESET_TRIES 2 +#define SET_ADDRESS_TRIES 1 +#define GET_DESCRIPTOR_TRIES 1 +#define GET_MAXPACKET0_TRIES 1 +#define PORT_INIT_TRIES 4 + +#else +#define PORT_RESET_TRIES 5 +#define SET_ADDRESS_TRIES 2 +#define GET_DESCRIPTOR_TRIES 2 +#define GET_MAXPACKET0_TRIES 3 +#define PORT_INIT_TRIES 4 +#endif /* CONFIG_USB_FEW_INIT_RETRIES */ + +#define DETECT_DISCONNECT_TRIES 5 + +#define HUB_ROOT_RESET_TIME 60 /* 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 bool use_new_scheme(struct usb_device *udev, int retry, + struct usb_port *port_dev) +{ + int old_scheme_first_port = + (port_dev->quirks & USB_PORT_QUIRK_OLD_SCHEME) || + old_scheme_first; + + /* + * "New scheme" enumeration causes an extra state transition to be + * exposed to an xhci host and causes USB3 devices to receive control + * commands in the default state. This has been seen to cause + * enumeration failures, so disable this enumeration scheme for USB3 + * devices. + */ + if (udev->speed >= USB_SPEED_SUPER) + return false; + + /* + * If use_both_schemes is set, use the first scheme (whichever + * it is) for the larger half of the retries, then use the other + * scheme. Otherwise, use the first scheme for all the retries. + */ + if (use_both_schemes && retry >= (PORT_INIT_TRIES + 1) / 2) + return old_scheme_first_port; /* Second half */ + return !old_scheme_first_port; /* First half or all */ +} + +/* Is a USB 3.0 port in the Inactive or Compliance Mode state? + * Port warm reset is required to recover + */ +static bool hub_port_warm_reset_required(struct usb_hub *hub, int port1, + u16 portstatus) +{ + u16 link_state; + + if (!hub_is_superspeed(hub->hdev)) + return false; + + if (test_bit(port1, hub->warm_reset_bits)) + return true; + + link_state = portstatus & USB_PORT_STAT_LINK_STATE; + return link_state == USB_SS_PORT_LS_SS_INACTIVE + || 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; + u32 ext_portstatus = 0; + + 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 */ + if (hub_is_superspeedplus(hub->hdev)) + ret = hub_ext_port_status(hub, port1, + HUB_EXT_PORT_STATUS, + &portstatus, &portchange, + &ext_portstatus); + else + ret = usb_hub_port_status(hub, port1, &portstatus, + &portchange); + if (ret < 0) + return ret; + + /* + * The port state is unknown until the reset completes. + * + * On top of that, some chips may require additional time + * to re-establish a connection after the reset is complete, + * so also wait for the connection to be re-established. + */ + if (!(portstatus & USB_PORT_STAT_RESET) && + (portstatus & USB_PORT_STAT_CONNECTION)) + 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->ports[port1 - 1]->dev, + "not %sreset yet, waiting %dms\n", + warm ? "warm " : "", delay); + } + + if ((portstatus & USB_PORT_STAT_RESET)) + return -EBUSY; + + if (hub_port_warm_reset_required(hub, port1, portstatus)) + return -ENOTCONN; + + /* Device went away? */ + if (!(portstatus & USB_PORT_STAT_CONNECTION)) + return -ENOTCONN; + + /* Retry if connect change is set but status is still connected. + * 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)) { + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_CONNECTION); + return -EAGAIN; + } + + if (!(portstatus & USB_PORT_STAT_ENABLE)) + return -EBUSY; + + if (!udev) + return 0; + + if (hub_is_superspeedplus(hub->hdev)) { + /* extended portstatus Rx and Tx lane count are zero based */ + udev->rx_lanes = USB_EXT_PORT_RX_LANES(ext_portstatus) + 1; + udev->tx_lanes = USB_EXT_PORT_TX_LANES(ext_portstatus) + 1; + udev->ssp_rate = get_port_ssp_rate(hub->hdev, ext_portstatus); + } else { + udev->rx_lanes = 1; + udev->tx_lanes = 1; + udev->ssp_rate = USB_SSP_GEN_UNKNOWN; + } + if (udev->ssp_rate != USB_SSP_GEN_UNKNOWN) + udev->speed = USB_SPEED_SUPER_PLUS; + 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; +} + +/* 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; + struct usb_port *port_dev = hub->ports[port1 - 1]; + int reset_recovery_time; + + 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. + */ + if (usb_hub_port_status(hub, port1, &portstatus, + &portchange) == 0) + if (hub_port_warm_reset_required(hub, port1, + portstatus)) + warm = true; + } + clear_bit(port1, hub->warm_reset_bits); + + /* 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(&port_dev->dev, + "cannot %sreset (err = %d)\n", + warm ? "warm " : "", 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, and bail out after several + * reset attempts to avoid warm reset loop. + */ + if (status == 0 || status == -ENOTCONN || status == -ENODEV || + (status == -EBUSY && i == PORT_RESET_TRIES - 1)) { + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_RESET); + + if (!hub_is_superspeed(hub->hdev)) + goto done; + + 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); + + if (udev) + usb_clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_CONNECTION); + + /* + * If a USB 3.0 device migrates from reset to an error + * state, re-issue the warm reset. + */ + if (usb_hub_port_status(hub, port1, + &portstatus, &portchange) < 0) + goto done; + + if (!hub_port_warm_reset_required(hub, port1, + 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(&port_dev->dev, + "hot reset failed, warm reset\n"); + warm = true; + } + } + + dev_dbg(&port_dev->dev, + "not enabled, trying %sreset again...\n", + warm ? "warm " : ""); + delay = HUB_LONG_RESET_TIME; + } + + dev_err_ratelimited(&port_dev->dev, "Cannot enable. Maybe the USB cable is bad?\n"); + +done: + if (status == 0) { + if (port_dev->quirks & USB_PORT_QUIRK_FAST_ENUM) + usleep_range(10000, 12000); + else { + /* TRSTRCY = 10 ms; plus some extra */ + reset_recovery_time = 10 + 40; + + /* Hub needs extra delay after resetting its port. */ + if (hub->hdev->quirks & USB_QUIRK_HUB_SLOW_RESET) + reset_recovery_time += 100; + + msleep(reset_recovery_time); + } + + 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); + + usb_set_device_state(udev, USB_STATE_DEFAULT); + } + } else { + if (udev) + usb_set_device_state(udev, USB_STATE_NOTATTACHED); + } + + if (!hub_is_superspeed(hub->hdev)) + up_read(&ehci_cf_port_reset_rwsem); + + return status; +} + +/* + * hub_port_stop_enumerate - stop USB enumeration or ignore port events + * @hub: target hub + * @port1: port num of the port + * @retries: port retries number of hub_port_init() + * + * Return: + * true: ignore port actions/events or give up connection attempts. + * false: keep original behavior. + * + * This function will be based on retries to check whether the port which is + * marked with early_stop attribute would stop enumeration or ignore events. + * + * Note: + * This function didn't change anything if early_stop is not set, and it will + * prevent all connection attempts when early_stop is set and the attempts of + * the port are more than 1. + */ +static bool hub_port_stop_enumerate(struct usb_hub *hub, int port1, int retries) +{ + struct usb_port *port_dev = hub->ports[port1 - 1]; + + if (port_dev->early_stop) { + if (port_dev->ignore_event) + return true; + + /* + * We want unsuccessful attempts to fail quickly. + * Since some devices may need one failure during + * port initialization, we allow two tries but no + * more. + */ + if (retries < 2) + return false; + + port_dev->ignore_event = 1; + } else + port_dev->ignore_event = 0; + + return port_dev->ignore_event; +} + +/* Check if a port is power on */ +int usb_port_is_power_on(struct usb_hub *hub, unsigned int 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; +} + +static void usb_lock_port(struct usb_port *port_dev) + __acquires(&port_dev->status_lock) +{ + mutex_lock(&port_dev->status_lock); + __acquire(&port_dev->status_lock); +} + +static void usb_unlock_port(struct usb_port *port_dev) + __releases(&port_dev->status_lock) +{ + mutex_unlock(&port_dev->status_lock); + __release(&port_dev->status_lock); +} + +#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, u16 portchange, u16 portstatus) +{ + struct usb_port *port_dev = hub->ports[port1 - 1]; + int retries = 3; + + retry: + /* Is a warm reset needed to recover the connection? */ + if (status == 0 && udev->reset_resume + && hub_port_warm_reset_required(hub, port1, portstatus)) { + /* pass */; + } + /* Is the device still present? */ + else if (status || port_is_suspended(hub, portstatus) || + !usb_port_is_power_on(hub, portstatus)) { + if (status >= 0) + status = -ENODEV; + } else if (!(portstatus & USB_PORT_STAT_CONNECTION)) { + if (retries--) { + usleep_range(200, 300); + status = usb_hub_port_status(hub, port1, &portstatus, + &portchange); + goto retry; + } + 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(&port_dev->dev, "status %04x.%04x after resume, %d\n", + 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); + + /* + * Whatever made this reset-resume necessary may have + * turned on the port1 bit in hub->change_bits. But after + * a successful reset-resume we want the bit to be clear; + * if it was on it would indicate that something happened + * following the reset-resume. + */ + clear_bit(port1, hub->change_bits); + } + + 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); + +/* + * usb_enable_remote_wakeup - enable remote wakeup for a device + * @udev: target device + * + * For USB-2 devices: Set the device's remote wakeup feature. + * + * For USB-3 devices: Assume there's only one function on the device and + * enable remote wake for the first interface. FIXME if the interface + * association descriptor shows there's more than one function. + */ +static int usb_enable_remote_wakeup(struct usb_device *udev) +{ + if (udev->speed < USB_SPEED_SUPER) + return 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 + return 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); +} + +/* + * usb_disable_remote_wakeup - disable remote wakeup for a device + * @udev: target device + * + * For USB-2 devices: Clear the device's remote wakeup feature. + * + * For USB-3 devices: Assume there's only one function on the 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_remote_wakeup(struct usb_device *udev) +{ + if (udev->speed < USB_SPEED_SUPER) + return 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 + return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE, USB_RECIP_INTERFACE, + USB_INTRF_FUNC_SUSPEND, 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); +} + +/* Count of wakeup-enabled devices at or below udev */ +unsigned usb_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); +} +EXPORT_SYMBOL_GPL(usb_wakeup_enabled_descendants); + +/* + * 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 hub_wq + * 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; + + usb_lock_port(port_dev); + + /* 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) { + status = usb_enable_remote_wakeup(udev); + 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 */ + usb_disable_usb2_hardware_lpm(udev); + + 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; + } + + /* 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) || usb_wakeup_enabled_descendants(udev) > 0) + status = set_port_feature(hub->hdev, port1, + USB_PORT_FEAT_SUSPEND); + else { + really_suspend = false; + status = 0; + } + if (status) { + /* Check if the port has been suspended for the timeout case + * to prevent the suspended port from incorrect handling. + */ + if (status == -ETIMEDOUT) { + int ret; + u16 portstatus, portchange; + + portstatus = portchange = 0; + ret = usb_hub_port_status(hub, port1, &portstatus, + &portchange); + + dev_dbg(&port_dev->dev, + "suspend timeout, status %04x\n", portstatus); + + if (ret == 0 && port_is_suspended(hub, portstatus)) { + status = 0; + goto suspend_done; + } + } + + dev_dbg(&port_dev->dev, "can't suspend, status %d\n", status); + + /* Try to enable USB3 LTM again */ + usb_enable_ltm(udev); + err_ltm: + /* Try to enable USB2 hardware LPM again */ + usb_enable_usb2_hardware_lpm(udev); + + if (udev->do_remote_wakeup) + (void) usb_disable_remote_wakeup(udev); + err_wakeup: + + /* System sleep transitions should never fail */ + if (!PMSG_IS_AUTO(msg)) + status = 0; + } else { + suspend_done: + 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 + && test_and_clear_bit(port1, hub->child_usage_bits)) + pm_runtime_put_sync(&port_dev->dev); + + usb_mark_last_busy(hub->hdev); + + usb_unlock_port(port_dev); + 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 hub_wq 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) { + /* + * If the device morphs or switches modes when it is reset, + * we don't want to perform a reset-resume. We'll fail the + * resume, which will cause a logical disconnect, and then + * the device will be rediscovered. + */ + retry_reset_resume: + if (udev->quirks & USB_QUIRK_RESET) + status = -ENODEV; + else + 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_std_status(udev, USB_RECIP_DEVICE, 0, &devstatus); + + /* 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 (udev->speed < USB_SPEED_SUPER) { + if (devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)) + status = usb_disable_remote_wakeup(udev); + } else { + status = usb_get_std_status(udev, USB_RECIP_INTERFACE, 0, + &devstatus); + if (!status && devstatus & (USB_INTRF_STAT_FUNC_RW_CAP + | USB_INTRF_STAT_FUNC_RW)) + status = usb_disable_remote_wakeup(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 CCS 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. + * + * There are also some 2.0 hard drive based devices and 3.0 thumb + * drives that, when plugged into a 2.0 only port, take a long + * time to set CCS after VBUS enable. + * + * 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. + */ +static int wait_for_connected(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; + if (!usb_port_is_power_on(hub, *portstatus)) { + status = -ENODEV; + break; + } + msleep(20); + delay_ms += 20; + status = usb_hub_port_status(hub, port1, portstatus, portchange); + } + dev_dbg(&udev->dev, "Waited %dms for CONNECT\n", delay_ms); + 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 (!test_and_set_bit(port1, hub->child_usage_bits)) { + status = pm_runtime_resume_and_get(&port_dev->dev); + if (status < 0) { + dev_dbg(&udev->dev, "can't resume usb port, status %d\n", + status); + return status; + } + } + + usb_lock_port(port_dev); + + /* Skip the initial Clear-Suspend step for a remote wakeup */ + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); + if (status == 0 && !port_is_suspended(hub, portstatus)) { + if (portchange & USB_PORT_STAT_C_SUSPEND) + pm_wakeup_event(&udev->dev, 0); + goto SuspendCleared; + } + + /* 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(&port_dev->dev, "can't resume, status %d\n", status); + } else { + /* drive resume for USB_RESUME_TIMEOUT msec */ + dev_dbg(&udev->dev, "usb %sresume\n", + (PMSG_IS_AUTO(msg) ? "auto-" : "")); + msleep(USB_RESUME_TIMEOUT); + + /* Virtual root hubs can trigger on GET_PORT_STATUS to + * stop resume signaling. Then finish the resume + * sequence. + */ + status = usb_hub_port_status(hub, port1, &portstatus, &portchange); + } + + 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); + } + + /* TRSMRCY = 10 msec */ + msleep(10); + } + + if (udev->persist_enabled) + status = wait_for_connected(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 */ + usb_enable_usb2_hardware_lpm(udev); + + /* Try to enable USB3 LTM */ + usb_enable_ltm(udev); + } + + usb_unlock_port(port_dev); + + return status; +} + +int usb_remote_wakeup(struct usb_device *udev) +{ + int status = 0; + + usb_lock_device(udev); + 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); + } + } + usb_unlock_device(udev); + return status; +} + +/* 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) + __must_hold(&port_dev->status_lock) +{ + struct usb_port *port_dev = hub->ports[port - 1]; + struct usb_device *hdev; + struct usb_device *udev; + int connect_change = 0; + u16 link_state; + int ret; + + hdev = hub->hdev; + udev = port_dev->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 { + link_state = portstatus & USB_PORT_STAT_LINK_STATE; + if (!udev || udev->state != USB_STATE_SUSPENDED || + (link_state != USB_SS_PORT_LS_U0 && + link_state != USB_SS_PORT_LS_U1 && + link_state != USB_SS_PORT_LS_U2)) + return 0; + } + + if (udev) { + /* TRSMRCY = 10 msec */ + msleep(10); + + usb_unlock_port(port_dev); + ret = usb_remote_wakeup(udev); + usb_lock_port(port_dev); + if (ret < 0) + connect_change = 1; + } else { + ret = -ENODEV; + hub_port_disable(hub, port, 1); + } + dev_dbg(&port_dev->dev, "resume, status %d\n", ret); + return connect_change; +} + +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 = usb_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; + + /* + * 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_port *port_dev = hub->ports[port1 - 1]; + struct usb_device *udev = port_dev->child; + + if (udev && udev->can_submit) { + dev_warn(&port_dev->dev, "device %s not suspended yet\n", + dev_name(&udev->dev)); + if (PMSG_IS_AUTO(msg)) + return -EBUSY; + } + if (udev) + hub->wakeup_enabled_descendants += + usb_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++) { + 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 hub_wq and related activity */ + hub_quiesce(hub, HUB_SUSPEND); + return 0; +} + +/* Report wakeup requests from the ports of a resuming root hub */ +static void report_wakeup_requests(struct usb_hub *hub) +{ + struct usb_device *hdev = hub->hdev; + struct usb_device *udev; + struct usb_hcd *hcd; + unsigned long resuming_ports; + int i; + + if (hdev->parent) + return; /* Not a root hub */ + + hcd = bus_to_hcd(hdev->bus); + if (hcd->driver->get_resuming_ports) { + + /* + * The get_resuming_ports() method returns a bitmap (origin 0) + * of ports which have started wakeup signaling but have not + * yet finished resuming. During system resume we will + * resume all the enabled ports, regardless of any wakeup + * signals, which means the wakeup requests would be lost. + * To prevent this, report them to the PM core here. + */ + resuming_ports = hcd->driver->get_resuming_ports(hcd); + for (i = 0; i < hdev->maxchild; ++i) { + if (test_bit(i, &resuming_ports)) { + udev = hub->ports[i]->child; + if (udev) + pm_wakeup_event(&udev->dev, 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); + + /* + * This should be called only for system resume, not runtime resume. + * We can't tell the difference here, so some wakeup requests will be + * reported at the wrong time or more than once. This shouldn't + * matter much, so long as they do get reported. + */ + report_wakeup_requests(hub); + 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_notice(&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) +{ + 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; + + if (!udev->parent || udev->speed < USB_SPEED_SUPER || !udev->lpm_capable) + return 0; + + /* 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 (u1_sel > USB3_LPM_MAX_U1_SEL_PEL || + u1_pel > USB3_LPM_MAX_U1_SEL_PEL || + u2_sel > USB3_LPM_MAX_U2_SEL_PEL || + u2_pel > USB3_LPM_MAX_U2_SEL_PEL) { + dev_dbg(&udev->dev, "Device-initiated U1/U2 disabled due to long SEL or PEL\n"); + return -EINVAL; + } + + /* + * 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_obj(*(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); + + if (ret > 0) + udev->lpm_devinit_allow = 1; + + 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__, str_enable_disable(enable)); + return -EINVAL; + } + + if (udev->state != USB_STATE_CONFIGURED) { + dev_dbg(&udev->dev, "%s: Can't %s %s state " + "for unconfigured device.\n", + __func__, str_enable_disable(enable), + usb3_lpm_names[state]); + return -EINVAL; + } + + 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", + str_enable_disable(enable), 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; +} + +/* + * Don't allow device intiated U1/U2 if device isn't in the configured state, + * or the system exit latency + one bus interval is greater than the minimum + * service interval of any active periodic endpoint. See USB 3.2 section 9.4.9 + */ +static bool usb_device_may_initiate_lpm(struct usb_device *udev, + enum usb3_link_state state) +{ + unsigned int sel; /* us */ + int i, j; + + if (!udev->lpm_devinit_allow || !udev->actconfig) + return false; + + if (state == USB3_LPM_U1) + sel = DIV_ROUND_UP(udev->u1_params.sel, 1000); + else if (state == USB3_LPM_U2) + sel = DIV_ROUND_UP(udev->u2_params.sel, 1000); + else + return false; + + for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { + struct usb_interface *intf; + struct usb_endpoint_descriptor *desc; + unsigned int interval; + + intf = udev->actconfig->interface[i]; + if (!intf) + continue; + + for (j = 0; j < intf->cur_altsetting->desc.bNumEndpoints; j++) { + desc = &intf->cur_altsetting->endpoint[j].desc; + + if (usb_endpoint_xfer_int(desc) || + usb_endpoint_xfer_isoc(desc)) { + interval = (1 << (desc->bInterval - 1)) * 125; + if (sel + 125 > interval) + return false; + } + } + } + return true; +} + +/* + * 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 the control transfer to enable device-initiated U1/U2 entry fails, then + * hub-initiated U1/U2 will be disabled. + * + * 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 int usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev, + enum usb3_link_state state) +{ + int timeout; + __u8 u1_mel; + __le16 u2_mel; + + /* Skip if the device BOS descriptor couldn't be read */ + if (!udev->bos) + return -EINVAL; + + u1_mel = udev->bos->ss_cap->bU1devExitLat; + 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 -EINVAL; + + /* 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 -EINVAL; + + if (timeout < 0) { + dev_warn(&udev->dev, "Could not enable %s link state, " + "xHCI error %i.\n", usb3_lpm_names[state], + timeout); + return timeout; + } + + 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); + return -EBUSY; + } + + if (state == USB3_LPM_U1) + udev->usb3_lpm_u1_enabled = 1; + else if (state == USB3_LPM_U2) + udev->usb3_lpm_u2_enabled = 1; + + return 0; +} +/* + * 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) +{ + switch (state) { + case USB3_LPM_U1: + case USB3_LPM_U2: + 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; + + 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]); + + /* As soon as usb_set_lpm_timeout(0) return 0, hub initiated LPM + * is disabled. Hub will disallows link to enter U1/U2 as well, + * even device is initiating LPM. Hence LPM is disabled if hub LPM + * timeout set to 0, no matter device-initiated LPM is disabled or + * not. + */ + if (state == USB3_LPM_U1) + udev->usb3_lpm_u1_enabled = 0; + else if (state == USB3_LPM_U2) + udev->usb3_lpm_u2_enabled = 0; + + 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; + int err; + + if (!udev || !udev->parent || + udev->speed < USB_SPEED_SUPER || + !udev->lpm_capable || + udev->state < USB_STATE_CONFIGURED) + 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 disable_failed; + if (usb_disable_link_state(hcd, udev, USB3_LPM_U2)) + goto disable_failed; + + err = usb_set_device_initiated_lpm(udev, USB3_LPM_U1, false); + if (!err) + usb_set_device_initiated_lpm(udev, USB3_LPM_U2, false); + + return 0; + +disable_failed: + udev->lpm_disable_count--; + + 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; + struct usb_hub *hub; + struct usb_port *port_dev; + + if (!udev || !udev->parent || + udev->speed < USB_SPEED_SUPER || + !udev->lpm_capable || + udev->state < USB_STATE_CONFIGURED) + 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; + + hub = usb_hub_to_struct_hub(udev->parent); + if (!hub) + return; + + port_dev = hub->ports[udev->portnum - 1]; + + if (port_dev->usb3_lpm_u1_permit) + if (usb_enable_link_state(hcd, udev, USB3_LPM_U1)) + return; + + if (port_dev->usb3_lpm_u2_permit) + if (usb_enable_link_state(hcd, udev, USB3_LPM_U2)) + return; + + /* + * Enable device initiated U1/U2 with a SetFeature(U1/U2_ENABLE) request + * if system exit latency is short enough and device is configured + */ + if (usb_device_may_initiate_lpm(udev, USB3_LPM_U1)) { + if (usb_set_device_initiated_lpm(udev, USB3_LPM_U1, true)) + return; + + if (usb_device_may_initiate_lpm(udev, USB3_LPM_U2)) + usb_set_device_initiated_lpm(udev, USB3_LPM_U2, true); + } +} +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); + +/* usb3 devices use U3 for disabled, make sure remote wakeup is disabled */ +static void hub_usb3_port_prepare_disable(struct usb_hub *hub, + struct usb_port *port_dev) +{ + struct usb_device *udev = port_dev->child; + int ret; + + if (udev && udev->port_is_suspended && udev->do_remote_wakeup) { + ret = hub_set_port_link_state(hub, port_dev->portnum, + USB_SS_PORT_LS_U0); + if (!ret) { + msleep(USB_RESUME_TIMEOUT); + ret = usb_disable_remote_wakeup(udev); + } + if (ret) + dev_warn(&udev->dev, + "Port disable: can't disable remote wake\n"); + udev->do_remote_wakeup = 0; + } +} + +#else /* CONFIG_PM */ + +#define hub_suspend NULL +#define hub_resume NULL +#define hub_reset_resume NULL + +static inline void hub_usb3_port_prepare_disable(struct usb_hub *hub, + struct usb_port *port_dev) { } + +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); + +static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port, + u16 portstatus, u16 portchange) +{ + return 0; +} + +static int usb_req_set_sel(struct usb_device *udev) +{ + return 0; +} + +#endif /* CONFIG_PM */ + +/* + * USB-3 does not have a similar link state as USB-2 that will avoid negotiating + * a connection with a plugged-in cable but will signal the host when the cable + * is unplugged. Disable remote wake and set link state to U3 for USB-3 devices + */ +static int hub_port_disable(struct usb_hub *hub, int port1, int set_state) +{ + struct usb_port *port_dev = hub->ports[port1 - 1]; + struct usb_device *hdev = hub->hdev; + int ret = 0; + + if (!hub->error) { + if (hub_is_superspeed(hub->hdev)) { + hub_usb3_port_prepare_disable(hub, port_dev); + ret = hub_set_port_link_state(hub, port_dev->portnum, + USB_SS_PORT_LS_U3); + } else { + ret = usb_clear_port_feature(hdev, port1, + USB_PORT_FEAT_ENABLE); + } + } + if (port_dev->child && set_state) + usb_set_device_state(port_dev->child, USB_STATE_NOTATTACHED); + if (ret && ret != -ENODEV) + dev_err(&port_dev->dev, "cannot disable (err = %d)\n", ret); + return ret; +} + +/* + * usb_port_disable - disable a usb device's upstream port + * @udev: device to disable + * Context: @udev locked, must be able to sleep. + * + * Disables a USB device that isn't in active use. + */ +int usb_port_disable(struct usb_device *udev) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); + + return hub_port_disable(hub, udev->portnum, 0); +} + +/* 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; + u16 portchange, portstatus; + unsigned connection = 0xffff; + int total_time, stable_time = 0; + struct usb_port *port_dev = hub->ports[port1 - 1]; + + for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) { + ret = usb_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(&port_dev->dev, "debounce total %dms stable %dms status 0x%x\n", + 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); + +static int hub_set_address(struct usb_device *udev, int devnum) +{ + int retval; + unsigned int timeout_ms = USB_CTRL_SET_TIMEOUT; + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); + + if (hub->hdev->quirks & USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT) + timeout_ms = USB_SHORT_SET_ADDRESS_REQ_TIMEOUT; + + /* + * 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, timeout_ms); + else + retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_SET_ADDRESS, 0, devnum, 0, + NULL, 0, timeout_ms); + 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; +} + +/* + * There are reports of USB 3.0 devices that say they support USB 2.0 Link PM + * when they're plugged into a USB 2.0 port, but they don't work when LPM is + * enabled. + * + * Only enable USB 2.0 Link PM if the port is internal (hardwired), or the + * device says it supports the new USB 2.0 Link PM errata by setting the BESL + * support bit in the BOS descriptor. + */ +static void hub_set_initial_usb2_lpm_policy(struct usb_device *udev) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); + int connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN; + + if (!udev->usb2_hw_lpm_capable || !udev->bos) + return; + + if (hub) + connect_type = hub->ports[udev->portnum - 1]->connect_type; + + if ((udev->bos->ext_cap->bmAttributes & cpu_to_le32(USB_BESL_SUPPORT)) || + connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED) { + udev->usb2_hw_lpm_allowed = 1; + usb_enable_usb2_hardware_lpm(udev); + } +} + +static int hub_enable_device(struct usb_device *udev) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + + if (!hcd->driver->enable_device) + return 0; + if (udev->state == USB_STATE_ADDRESS) + return 0; + if (udev->state != USB_STATE_DEFAULT) + return -EINVAL; + + return hcd->driver->enable_device(hcd, udev); +} + +/* + * Get the bMaxPacketSize0 value during initialization by reading the + * device's device descriptor. Since we don't already know this value, + * the transfer is unsafe and it ignores I/O errors, only testing for + * reasonable received values. + * + * For "old scheme" initialization, size will be 8 so we read just the + * start of the device descriptor, which should work okay regardless of + * the actual bMaxPacketSize0 value. For "new scheme" initialization, + * size will be 64 (and buf will point to a sufficiently large buffer), + * which might not be kosher according to the USB spec but it's what + * Windows does and what many devices expect. + * + * Returns: bMaxPacketSize0 or a negative error code. + */ +static int get_bMaxPacketSize0(struct usb_device *udev, + struct usb_device_descriptor *buf, int size, bool first_time) +{ + int i, rc; + + /* + * 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 (i = 0; i < GET_MAXPACKET0_TRIES; ++i) { + /* Start with invalid values in case the transfer fails */ + buf->bDescriptorType = buf->bMaxPacketSize0 = 0; + rc = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, + USB_DT_DEVICE << 8, 0, + buf, size, + initial_descriptor_timeout); + switch (buf->bMaxPacketSize0) { + case 8: case 16: case 32: case 64: case 9: + if (buf->bDescriptorType == USB_DT_DEVICE) { + rc = buf->bMaxPacketSize0; + break; + } + fallthrough; + default: + if (rc >= 0) + rc = -EPROTO; + break; + } + + /* + * Some devices time out if they are powered on + * when already connected. They need a second + * reset, so return early. But only on the first + * attempt, lest we get into a time-out/reset loop. + */ + if (rc > 0 || (rc == -ETIMEDOUT && first_time && + udev->speed > USB_SPEED_FULL)) + break; + } + return rc; +} + +#define GET_DESCRIPTOR_BUFSIZE 64 + +/* 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 and + * the port lock. For a newly detected device that is not accessible + * through any global pointers, it's not necessary to lock the device, + * but it is still necessary to lock the port. + * + * For a newly detected device, @dev_descr must be NULL. The device + * descriptor retrieved from the device will then be stored in + * @udev->descriptor. For an already existing device, @dev_descr + * must be non-NULL. The device descriptor will be stored there, + * not in @udev->descriptor, because descriptors for registered + * devices are meant to be immutable. + */ +static int +hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, + int retry_counter, struct usb_device_descriptor *dev_descr) +{ + struct usb_device *hdev = hub->hdev; + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); + struct usb_port *port_dev = hub->ports[port1 - 1]; + int retries, operations, retval, i; + unsigned delay = HUB_SHORT_RESET_TIME; + enum usb_device_speed oldspeed = udev->speed; + const char *speed; + int devnum = udev->devnum; + const char *driver_name; + bool do_new_scheme; + const bool initial = !dev_descr; + int maxp0; + struct usb_device_descriptor *buf, *descr; + + buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO); + if (!buf) + return -ENOMEM; + + /* 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; + + /* 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; + + /* Don't allow speed changes at reset, except usb 3.0 to faster */ + if (oldspeed != USB_SPEED_UNKNOWN && oldspeed != udev->speed && + !(oldspeed == USB_SPEED_SUPER && udev->speed > oldspeed)) { + dev_dbg(&udev->dev, "device reset changed speed!\n"); + goto fail; + } + oldspeed = udev->speed; + + if (initial) { + /* USB 2.0 section 5.5.3 talks about ep0 maxpacket ... + * it's fixed size except for full speed devices. + */ + switch (udev->speed) { + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER: + 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; + } + } + + speed = usb_speed_string(udev->speed); + + /* + * The controller driver may be NULL if the controller device + * is the middle device between platform device and roothub. + * This middle device may not need a device driver due to + * all hardware control can be at platform device driver, this + * platform device is usually a dual-role USB controller device. + */ + if (udev->bus->controller->driver) + driver_name = udev->bus->controller->driver->name; + else + driver_name = udev->bus->sysdev->driver->name; + + if (udev->speed < USB_SPEED_SUPER) + dev_info(&udev->dev, + "%s %s USB device number %d using %s\n", + (initial ? "new" : "reset"), speed, + devnum, driver_name); + + if (initial) { + /* 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. + */ + do_new_scheme = use_new_scheme(udev, retry_counter, port_dev); + + for (retries = 0; retries < GET_DESCRIPTOR_TRIES; (++retries, msleep(100))) { + if (hub_port_stop_enumerate(hub, port1, retries)) { + retval = -ENODEV; + break; + } + + if (do_new_scheme) { + retval = hub_enable_device(udev); + if (retval < 0) { + dev_err(&udev->dev, + "hub failed to enable device, error %d\n", + retval); + goto fail; + } + + maxp0 = get_bMaxPacketSize0(udev, buf, + GET_DESCRIPTOR_BUFSIZE, retries == 0); + if (maxp0 > 0 && !initial && + maxp0 != udev->descriptor.bMaxPacketSize0) { + dev_err(&udev->dev, "device reset changed ep0 maxpacket size!\n"); + retval = -ENODEV; + goto fail; + } + + 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 (maxp0 < 0) { + if (maxp0 != -ENODEV) + dev_err(&udev->dev, "device descriptor read/64, error %d\n", + maxp0); + retval = maxp0; + continue; + } + } + + for (operations = 0; operations < SET_ADDRESS_TRIES; ++operations) { + 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%s%s USB device number %d using %s\n", + (udev->config) ? "reset" : "new", + (udev->speed == USB_SPEED_SUPER_PLUS) ? + " Plus" : "", + (udev->ssp_rate == USB_SSP_GEN_2x2) ? + " Gen 2x2" : + (udev->ssp_rate == USB_SSP_GEN_2x1) ? + " Gen 2x1" : + (udev->ssp_rate == USB_SSP_GEN_1x2) ? + " Gen 1x2" : "", + devnum, 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 (do_new_scheme) + break; + + maxp0 = get_bMaxPacketSize0(udev, buf, 8, retries == 0); + if (maxp0 < 0) { + retval = maxp0; + if (retval != -ENODEV) + dev_err(&udev->dev, + "device descriptor read/8, error %d\n", + retval); + } else { + u32 delay; + + if (!initial && maxp0 != udev->descriptor.bMaxPacketSize0) { + dev_err(&udev->dev, "device reset changed ep0 maxpacket size!\n"); + retval = -ENODEV; + goto fail; + } + + delay = udev->parent->hub_delay; + udev->hub_delay = min_t(u32, delay, + USB_TP_TRANSMISSION_DELAY_MAX); + retval = usb_set_isoch_delay(udev); + if (retval) { + dev_dbg(&udev->dev, + "Failed set isoch delay, error %d\n", + retval); + retval = 0; + } + break; + } + } + if (retval) + goto fail; + + /* + * Check the ep0 maxpacket guess and correct it if necessary. + * maxp0 is the value stored in the device descriptor; + * i is the value it encodes (logarithmic for SuperSpeed or greater). + */ + i = maxp0; + if (udev->speed >= USB_SPEED_SUPER) { + if (maxp0 <= 16) + i = 1 << maxp0; + else + i = 0; /* Invalid */ + } + if (usb_endpoint_maxp(&udev->ep0.desc) == i) { + ; /* Initial ep0 maxpacket guess is right */ + } else if (((udev->speed == USB_SPEED_FULL || + udev->speed == USB_SPEED_HIGH) && + (i == 8 || i == 16 || i == 32 || i == 64)) || + (udev->speed >= USB_SPEED_SUPER && i > 0)) { + /* Initial guess is wrong; use the descriptor's value */ + 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); + } else { + /* Initial guess is wrong and descriptor's value is invalid */ + dev_err(&udev->dev, "Invalid ep0 maxpacket: %d\n", maxp0); + retval = -EMSGSIZE; + goto fail; + } + + descr = usb_get_device_descriptor(udev); + if (IS_ERR(descr)) { + retval = PTR_ERR(descr); + if (retval != -ENODEV) + dev_err(&udev->dev, "device descriptor read/all, error %d\n", + retval); + goto fail; + } + if (initial) + udev->descriptor = *descr; + else + *dev_descr = *descr; + kfree(descr); + + /* + * 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; + } + + usb_detect_quirks(udev); + + if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0201) { + retval = usb_get_bos_descriptor(udev); + if (!retval) { + udev->lpm_capable = usb_device_supports_lpm(udev); + udev->lpm_disable_count = 1; + usb_set_lpm_parameters(udev); + usb_req_set_sel(udev); + } + } + + retval = 0; + /* notify HCD that we have a device connected and addressed */ + if (hcd->driver->update_device) + hcd->driver->update_device(hcd, udev); + hub_set_initial_usb2_lpm_policy(udev); +fail: + if (retval) { + hub_port_disable(hub, port1, 0); + update_devnum(udev, devnum); /* for disconnect processing */ + } + kfree(buf); + return retval; +} + +static void +check_highspeed(struct usb_hub *hub, struct usb_device *udev, int port1) +{ + struct usb_qualifier_descriptor *qual; + int status; + + if (udev->quirks & USB_QUIRK_DEVICE_QUALIFIER) + return; + + qual = kmalloc_obj(*qual); + 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; + queue_delayed_work(system_power_efficient_wq, + &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_port *port_dev = hub->ports[port1 - 1]; + struct usb_device *udev = port_dev->child; + unsigned unit_load; + int delta; + + 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(&port_dev->dev, "%dmA is over %umA budget!\n", + delta, hub->mA_per_port); + remaining -= delta; + } + if (remaining < 0) { + dev_warn(hub->intfdev, "%dmA over power budget!\n", + -remaining); + remaining = 0; + } + return remaining; +} + + +static int descriptors_changed(struct usb_device *udev, + struct usb_device_descriptor *new_device_descriptor, + struct usb_host_bos *old_bos) +{ + int changed = 0; + unsigned index; + unsigned serial_len = 0; + unsigned len; + unsigned old_length; + int length; + char *buf; + + if (memcmp(&udev->descriptor, new_device_descriptor, + sizeof(*new_device_descriptor)) != 0) + return 1; + + if ((old_bos && !udev->bos) || (!old_bos && udev->bos)) + return 1; + if (udev->bos) { + len = le16_to_cpu(udev->bos->desc->wTotalLength); + if (len != le16_to_cpu(old_bos->desc->wTotalLength)) + return 1; + if (memcmp(udev->bos->desc, old_bos->desc, len)) + 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) + /* 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 + 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; +} + +static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus, + u16 portchange) +{ + int status = -ENODEV; + int i; + unsigned unit_load; + struct usb_device *hdev = hub->hdev; + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); + struct usb_port *port_dev = hub->ports[port1 - 1]; + struct usb_device *udev = port_dev->child; + static int unreliable_port = -1; + bool retry_locked; + + /* Disconnect any existing devices under this port */ + if (udev) { + if (hcd->usb_phy && !hdev->parent) + usb_phy_notify_disconnect(hcd->usb_phy, udev->speed); + usb_disconnect(&port_dev->child); + } + + /* 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 && + port1 != unreliable_port && + printk_ratelimit()) + dev_err(&port_dev->dev, "connect-debounce failed\n"); + portstatus &= ~USB_PORT_STAT_CONNECTION; + unreliable_port = port1; + } 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) + * but only if the port isn't owned by someone else. + */ + if (hub_is_port_power_switchable(hub) + && !usb_port_is_power_on(hub, portstatus) + && !port_dev->port_owner) + 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 < PORT_INIT_TRIES; i++) { + if (hub_port_stop_enumerate(hub, port1, i)) { + status = -ENODEV; + break; + } + + usb_lock_port(port_dev); + mutex_lock(hcd->address0_mutex); + retry_locked = true; + /* 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(&port_dev->dev, + "couldn't allocate usb_device\n"); + mutex_unlock(hcd->address0_mutex); + usb_unlock_port(port_dev); + goto done; + } + + usb_set_device_state(udev, USB_STATE_POWERED); + udev->bus_mA = hub->mA_per_port; + udev->level = hdev->level + 1; + + /* Devices connected to SuperSpeed hubs are USB 3.0 or later */ + 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, NULL); + if (status < 0) + goto loop; + + mutex_unlock(hcd->address0_mutex); + usb_unlock_port(port_dev); + retry_locked = false; + + if (udev->quirks & USB_QUIRK_DELAY_INIT) + msleep(2000); + + /* 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_std_status(udev, USB_RECIP_DEVICE, 0, + &devstat); + if (status) { + dev_dbg(&udev->dev, "get status %d ?\n", status); + goto loop_disable; + } + 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; + queue_delayed_work( + system_power_efficient_wq, + &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; + + mutex_lock(&usb_port_peer_mutex); + + /* 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 + port_dev->child = udev; + spin_unlock_irq(&device_state_lock); + mutex_unlock(&usb_port_peer_mutex); + + /* Run it through the hoops (find a driver, etc) */ + if (!status) { + status = usb_new_device(udev); + if (status) { + mutex_lock(&usb_port_peer_mutex); + spin_lock_irq(&device_state_lock); + port_dev->child = NULL; + spin_unlock_irq(&device_state_lock); + mutex_unlock(&usb_port_peer_mutex); + } else { + if (hcd->usb_phy && !hdev->parent) + usb_phy_notify_connect(hcd->usb_phy, + udev->speed); + } + } + + if (status) + goto loop_disable; + + status = hub_power_remaining(hub); + if (status) + dev_dbg(hub->intfdev, "%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); + if (retry_locked) { + mutex_unlock(hcd->address0_mutex); + usb_unlock_port(port_dev); + } + usb_put_dev(udev); + if ((status == -ENOTCONN) || (status == -ENOTSUPP)) + break; + + /* When halfway through our retry count, power-cycle the port */ + if (i == (PORT_INIT_TRIES - 1) / 2) { + dev_info(&port_dev->dev, "attempt power cycle\n"); + usb_hub_set_port_power(hdev, hub, port1, false); + msleep(2 * hub_power_on_good_delay(hub)); + usb_hub_set_port_power(hdev, hub, port1, true); + msleep(hub_power_on_good_delay(hub)); + } + } + if (hub->hdev->parent || + !hcd->driver->port_handed_over || + !(hcd->driver->port_handed_over)(hcd, port1)) { + if (status != -ENOTCONN && status != -ENODEV) + dev_err(&port_dev->dev, + "unable to enumerate USB device\n"); + } + +done: + hub_port_disable(hub, port1, 1); + if (hcd->driver->relinquish_port && !hub->hdev->parent) { + if (status != -ENOTCONN && status != -ENODEV) + hcd->driver->relinquish_port(hcd, port1); + } +} + +/* 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) + __must_hold(&port_dev->status_lock) +{ + struct usb_port *port_dev = hub->ports[port1 - 1]; + struct usb_device *udev = port_dev->child; + struct usb_device_descriptor *descr; + int status = -ENODEV; + + dev_dbg(&port_dev->dev, "status %04x, change %04x, %s\n", 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 (hub->hdev->bus->is_b_host) + portchange &= ~(USB_PORT_STAT_C_CONNECTION | + USB_PORT_STAT_C_ENABLE); +#endif + + /* Try to resuscitate an existing device */ + if ((portstatus & USB_PORT_STAT_CONNECTION) && udev && + udev->state != USB_STATE_NOTATTACHED) { + if (portstatus & USB_PORT_STAT_ENABLE) { + /* + * USB-3 connections are initialized automatically by + * the hostcontroller hardware. Therefore check for + * changed device descriptors before resuscitating the + * device. + */ + descr = usb_get_device_descriptor(udev); + if (IS_ERR(descr)) { + dev_dbg(&udev->dev, + "can't read device descriptor %ld\n", + PTR_ERR(descr)); + } else { + if (descriptors_changed(udev, descr, + udev->bos)) { + dev_dbg(&udev->dev, + "device descriptor has changed\n"); + } else { + status = 0; /* Nothing to do */ + } + kfree(descr); + } +#ifdef CONFIG_PM + } else if (udev->state == USB_STATE_SUSPENDED && + udev->persist_enabled) { + /* For a suspended device, treat this as a + * remote wakeup event. + */ + usb_unlock_port(port_dev); + status = usb_remote_wakeup(udev); + usb_lock_port(port_dev); +#endif + } else { + /* Don't resuscitate */; + } + } + clear_bit(port1, hub->change_bits); + + /* successfully revalidated the connection */ + if (status == 0) + return; + + usb_unlock_port(port_dev); + hub_port_connect(hub, port1, portstatus, portchange); + usb_lock_port(port_dev); +} + +/* Handle notifying userspace about hub over-current events */ +static void port_over_current_notify(struct usb_port *port_dev) +{ + char *envp[3] = { NULL, NULL, NULL }; + struct device *hub_dev; + char *port_dev_path; + + sysfs_notify(&port_dev->dev.kobj, NULL, "over_current_count"); + + hub_dev = port_dev->dev.parent; + + if (!hub_dev) + return; + + port_dev_path = kobject_get_path(&port_dev->dev.kobj, GFP_KERNEL); + if (!port_dev_path) + return; + + envp[0] = kasprintf(GFP_KERNEL, "OVER_CURRENT_PORT=%s", port_dev_path); + if (!envp[0]) + goto exit; + + envp[1] = kasprintf(GFP_KERNEL, "OVER_CURRENT_COUNT=%u", + port_dev->over_current_count); + if (!envp[1]) + goto exit; + + kobject_uevent_env(&hub_dev->kobj, KOBJ_CHANGE, envp); + +exit: + kfree(envp[1]); + kfree(envp[0]); + kfree(port_dev_path); +} + +static void port_event(struct usb_hub *hub, int port1) + __must_hold(&port_dev->status_lock) +{ + int connect_change; + struct usb_port *port_dev = hub->ports[port1 - 1]; + struct usb_device *udev = port_dev->child; + struct usb_device *hdev = hub->hdev; + u16 portstatus, portchange; + int i = 0; + int err; + + connect_change = test_bit(port1, hub->change_bits); + clear_bit(port1, hub->event_bits); + clear_bit(port1, hub->wakeup_bits); + + if (usb_hub_port_status(hub, port1, &portstatus, &portchange) < 0) + return; + + if (portchange & USB_PORT_STAT_C_CONNECTION) { + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION); + connect_change = 1; + } + + if (portchange & USB_PORT_STAT_C_ENABLE) { + if (!connect_change) + dev_dbg(&port_dev->dev, "enable change, status %08x\n", + portstatus); + usb_clear_port_feature(hdev, port1, 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 && udev) { + dev_err(&port_dev->dev, "disabled by hub (EMI?), re-enabling...\n"); + connect_change = 1; + } + } + + if (portchange & USB_PORT_STAT_C_OVERCURRENT) { + u16 status = 0, unused; + port_dev->over_current_count++; + port_over_current_notify(port_dev); + + dev_dbg(&port_dev->dev, "over-current change #%u\n", + port_dev->over_current_count); + usb_clear_port_feature(hdev, port1, + USB_PORT_FEAT_C_OVER_CURRENT); + msleep(100); /* Cool down */ + hub_power_on(hub, true); + usb_hub_port_status(hub, port1, &status, &unused); + if (status & USB_PORT_STAT_OVERCURRENT) + dev_err(&port_dev->dev, "over-current condition\n"); + } + + if (portchange & USB_PORT_STAT_C_RESET) { + dev_dbg(&port_dev->dev, "reset change\n"); + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_RESET); + } + if ((portchange & USB_PORT_STAT_C_BH_RESET) + && hub_is_superspeed(hdev)) { + dev_dbg(&port_dev->dev, "warm reset change\n"); + usb_clear_port_feature(hdev, port1, + USB_PORT_FEAT_C_BH_PORT_RESET); + } + if (portchange & USB_PORT_STAT_C_LINK_STATE) { + dev_dbg(&port_dev->dev, "link state change\n"); + usb_clear_port_feature(hdev, port1, + USB_PORT_FEAT_C_PORT_LINK_STATE); + } + if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) { + dev_warn(&port_dev->dev, "config error\n"); + usb_clear_port_feature(hdev, port1, + USB_PORT_FEAT_C_PORT_CONFIG_ERROR); + } + + /* skip port actions that require the port to be powered on */ + if (!pm_runtime_active(&port_dev->dev)) + return; + + /* skip port actions if ignore_event and early_stop are true */ + if (port_dev->ignore_event && port_dev->early_stop) + return; + + if (hub_handle_remote_wakeup(hub, port1, portstatus, portchange)) + connect_change = 1; + + /* + * Avoid trying to recover a USB3 SS.Inactive port with a warm reset if + * the device was disconnected. A 12ms disconnect detect timer in + * SS.Inactive state transitions the port to RxDetect automatically. + * SS.Inactive link error state is common during device disconnect. + */ + while (hub_port_warm_reset_required(hub, port1, portstatus)) { + if ((i++ < DETECT_DISCONNECT_TRIES) && udev) { + u16 unused; + + msleep(20); + usb_hub_port_status(hub, port1, &portstatus, &unused); + dev_dbg(&port_dev->dev, "Wait for inactive link disconnect detect\n"); + continue; + } else if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION) + || udev->state == USB_STATE_NOTATTACHED) { + dev_dbg(&port_dev->dev, "do warm reset, port only\n"); + err = hub_port_reset(hub, port1, NULL, + HUB_BH_RESET_TIME, true); + if (!udev && err == -ENOTCONN) + connect_change = 0; + else if (err < 0) + hub_port_disable(hub, port1, 1); + } else { + dev_dbg(&port_dev->dev, "do warm reset, full device\n"); + usb_unlock_port(port_dev); + usb_lock_device(udev); + usb_reset_device(udev); + usb_unlock_device(udev); + usb_lock_port(port_dev); + connect_change = 0; + } + break; + } + + if (connect_change) + hub_port_connect_change(hub, port1, portstatus, portchange); +} + +static void hub_event(struct work_struct *work) +{ + struct usb_device *hdev; + struct usb_interface *intf; + struct usb_hub *hub; + struct device *hub_dev; + u16 hubstatus; + u16 hubchange; + int i, ret; + + hub = container_of(work, struct usb_hub, events); + hdev = hub->hdev; + hub_dev = hub->intfdev; + intf = to_usb_interface(hub_dev); + + kcov_remote_start_usb((u64)hdev->bus->busnum); + + dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n", + hdev->state, hdev->maxchild, + /* 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 out_hdev_lock; + + /* If the hub has died, clean up after it */ + if (hdev->state == USB_STATE_NOTATTACHED) { + hub->error = -ENODEV; + hub_quiesce(hub, HUB_DISCONNECT); + goto out_hdev_lock; + } + + /* Autoresume */ + ret = usb_autopm_get_interface(intf); + if (ret) { + dev_dbg(hub_dev, "Can't autoresume: %d\n", ret); + goto out_hdev_lock; + } + + /* If this is an inactive hub, do nothing */ + if (hub->quiescing) + goto out_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 out_autopm; + } + + hub->nerrors = 0; + hub->error = 0; + } + + /* deal with port status changes */ + for (i = 1; i <= hdev->maxchild; i++) { + struct usb_port *port_dev = hub->ports[i - 1]; + + if (test_bit(i, hub->event_bits) + || test_bit(i, hub->change_bits) + || test_bit(i, hub->wakeup_bits)) { + /* + * The get_noresume and barrier ensure that if + * the port was in the process of resuming, we + * flush that work and keep the port active for + * the duration of the port_event(). However, + * if the port is runtime pm suspended + * (powered-off), we leave it in that state, run + * an abbreviated port_event(), and move on. + */ + pm_runtime_get_noresume(&port_dev->dev); + pm_runtime_barrier(&port_dev->dev); + usb_lock_port(port_dev); + port_event(hub, i); + usb_unlock_port(port_dev); + pm_runtime_put_sync(&port_dev->dev); + } + } + + /* 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"); + } + } + +out_autopm: + /* Balance the usb_autopm_get_interface() above */ + usb_autopm_put_interface_no_suspend(intf); +out_hdev_lock: + usb_unlock_device(hdev); + + /* Balance the stuff in kick_hub_wq() and allow autosuspend */ + usb_autopm_put_interface(intf); + hub_put(hub); + + kcov_remote_stop(); +} + +static const struct usb_device_id hub_id_table[] = { + { .match_flags = USB_DEVICE_ID_MATCH_VENDOR + | USB_DEVICE_ID_MATCH_PRODUCT + | USB_DEVICE_ID_MATCH_INT_CLASS, + .idVendor = USB_VENDOR_SMSC, + .idProduct = USB_PRODUCT_USB5534B, + .bInterfaceClass = USB_CLASS_HUB, + .driver_info = HUB_QUIRK_DISABLE_AUTOSUSPEND}, + { .match_flags = USB_DEVICE_ID_MATCH_VENDOR + | USB_DEVICE_ID_MATCH_PRODUCT, + .idVendor = USB_VENDOR_CYPRESS, + .idProduct = USB_PRODUCT_CY7C65632, + .driver_info = HUB_QUIRK_DISABLE_AUTOSUSPEND}, + { .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_VENDOR + | USB_DEVICE_ID_MATCH_PRODUCT, + .idVendor = USB_VENDOR_TEXAS_INSTRUMENTS, + .idProduct = USB_PRODUCT_TUSB8041_USB2, + .driver_info = HUB_QUIRK_DISABLE_AUTOSUSPEND}, + { .match_flags = USB_DEVICE_ID_MATCH_VENDOR + | USB_DEVICE_ID_MATCH_PRODUCT, + .idVendor = USB_VENDOR_TEXAS_INSTRUMENTS, + .idProduct = USB_PRODUCT_TUSB8041_USB3, + .driver_info = HUB_QUIRK_DISABLE_AUTOSUSPEND}, + { .match_flags = USB_DEVICE_ID_MATCH_VENDOR + | USB_DEVICE_ID_MATCH_PRODUCT, + .idVendor = USB_VENDOR_MICROCHIP, + .idProduct = USB_PRODUCT_USB4913, + .driver_info = HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL}, + { .match_flags = USB_DEVICE_ID_MATCH_VENDOR + | USB_DEVICE_ID_MATCH_PRODUCT, + .idVendor = USB_VENDOR_MICROCHIP, + .idProduct = USB_PRODUCT_USB4914, + .driver_info = HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL}, + { .match_flags = USB_DEVICE_ID_MATCH_VENDOR + | USB_DEVICE_ID_MATCH_PRODUCT, + .idVendor = USB_VENDOR_MICROCHIP, + .idProduct = USB_PRODUCT_USB4915, + .driver_info = HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL}, + { .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; + } + + /* + * The workqueue needs to be freezable to avoid interfering 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. + */ + hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE | WQ_PERCPU, 0); + if (hub_wq) + return 0; + + /* Fall through if kernel_thread failed */ + usb_deregister(&hub_driver); + pr_err("%s: can't allocate workqueue for usb hub\n", usbcore_name); + + return -1; +} + +void usb_hub_cleanup(void) +{ + destroy_workqueue(hub_wq); + + /* + * 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() */ + +/** + * hub_hc_release_resources - clear resources used by host controller + * @udev: pointer to device being released + * + * Context: task context, might sleep + * + * Function releases the host controller resources in correct order before + * making any operation on resuming usb device. The host controller resources + * allocated for devices in tree should be released starting from the last + * usb device in tree toward the root hub. This function is used only during + * resuming device when usb device require reinitialization – that is, when + * flag udev->reset_resume is set. + * + * This call is synchronous, and may not be used in an interrupt context. + */ +static void hub_hc_release_resources(struct usb_device *udev) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(udev); + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + int i; + + /* Release up resources for all children before this device */ + for (i = 0; i < udev->maxchild; i++) + if (hub->ports[i]->child) + hub_hc_release_resources(hub->ports[i]->child); + + if (hcd->driver->reset_device) + hcd->driver->reset_device(hcd, udev); +} + +/** + * 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 hub_wq 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. + * + * Return: 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. + * + * Note: + * The caller must own the device lock and the port lock, the latter is + * taken by usb_reset_device(). For example, it's safe to use + * usb_reset_device() 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. The autoresume handler is expected to have already + * acquired the port lock before calling this routine. + */ +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; + struct usb_interface *intf; + struct usb_host_bos *bos; + int i, j, 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) + return -EISDIR; + + parent_hub = usb_hub_to_struct_hub(parent_hdev); + + /* Disable USB2 hardware LPM. + * It will be re-enabled by the enumeration process. + */ + usb_disable_usb2_hardware_lpm(udev); + + bos = udev->bos; + udev->bos = NULL; + + if (udev->reset_resume) + hub_hc_release_resources(udev); + + mutex_lock(hcd->address0_mutex); + + for (i = 0; i < PORT_INIT_TRIES; ++i) { + if (hub_port_stop_enumerate(parent_hub, port1, i)) { + ret = -ENODEV; + break; + } + + /* 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, &descriptor); + if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV) + break; + } + mutex_unlock(hcd->address0_mutex); + + if (ret < 0) + goto re_enumerate; + + /* Device might have changed firmware (DFU or similar) */ + if (descriptors_changed(udev, &descriptor, bos)) { + dev_info(&udev->dev, "device firmware changed\n"); + goto re_enumerate; + } + + /* Restore the device's previous configuration */ + if (!udev->actconfig) + goto done; + + /* + * Some devices can't handle setting default altsetting 0 with a + * Set-Interface request. Disable host-side endpoints of those + * interfaces here. Enable and reset them back after host has set + * its internal endpoint structures during usb_hcd_alloc_bandwith() + */ + for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { + intf = udev->actconfig->interface[i]; + if (intf->cur_altsetting->desc.bAlternateSetting == 0) + usb_disable_interface(udev, intf, true); + } + + 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_descriptor *desc; + + intf = config->interface[i]; + desc = &intf->cur_altsetting->desc; + if (desc->bAlternateSetting == 0) { + 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; + } + /* Resetting also frees any allocated streams */ + for (j = 0; j < intf->cur_altsetting->desc.bNumEndpoints; j++) + intf->cur_altsetting->endpoint[j].streams = 0; + } + +done: + /* Now that the alt settings are re-installed, enable LTM and LPM. */ + usb_enable_usb2_hardware_lpm(udev); + usb_unlocked_enable_lpm(udev); + usb_enable_ltm(udev); + usb_release_bos_descriptor(udev); + udev->bos = bos; + return 0; + +re_enumerate: + usb_release_bos_descriptor(udev); + udev->bos = bos; + 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 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: The same as for usb_reset_and_verify_device(). + * However, if a reset is already in progress (for instance, if a + * driver doesn't have pre_reset() or post_reset() callbacks, and while + * being unbound or re-bound during the ongoing reset its disconnect() + * or probe() routine tries to perform a second, nested reset), the + * routine returns -EINPROGRESS. + * + * Note: + * 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_port *port_dev; + struct usb_host_config *config = udev->actconfig; + struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); + + if (udev->state == USB_STATE_NOTATTACHED) { + dev_dbg(&udev->dev, "device reset not allowed in state %d\n", + udev->state); + return -EINVAL; + } + + if (!udev->parent) { + /* this requires hcd-specific logic; see ohci_restart() */ + dev_dbg(&udev->dev, "%s for root hub!\n", __func__); + return -EISDIR; + } + + if (udev->reset_in_progress) + return -EINPROGRESS; + udev->reset_in_progress = 1; + + port_dev = hub->ports[udev->portnum - 1]; + + /* + * 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); + } + } + } + + usb_lock_port(port_dev); + ret = usb_reset_and_verify_device(udev); + usb_unlock_port(port_dev); + + 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; + } + } + + /* If the reset failed, hub_wq will unbind drivers later */ + if (ret == 0) + usb_unbind_and_rebind_marked_interfaces(udev); + } + + usb_autosuspend_device(udev); + memalloc_noio_restore(noio_flag); + udev->reset_in_progress = 0; + 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 the reset is delayed so long that the interface is unbound from + * its driver, the reset will be skipped. + * + * - This function can be called during .probe(). It can also be called + * during .disconnect(), but doing so is pointless because the reset + * will not occur. If you really want to reset the device during + * .disconnect(), call usb_reset_device() directly -- but watch out + * for nested unbinding issues! + */ +void usb_queue_reset_device(struct usb_interface *iface) +{ + if (schedule_work(&iface->reset_ws)) + usb_get_intf(iface); +} +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); + +void usb_hub_adjust_deviceremovable(struct usb_device *hdev, + struct usb_hub_descriptor *desc) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + enum usb_port_connect_type connect_type; + int i; + + if (!hub) + return; + + if (!hub_is_superspeed(hdev)) { + for (i = 1; i <= hdev->maxchild; i++) { + struct usb_port *port_dev = hub->ports[i - 1]; + + connect_type = port_dev->connect_type; + if (connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED) { + u8 mask = 1 << (i%8); + + if (!(desc->u.hs.DeviceRemovable[i/8] & mask)) { + dev_dbg(&port_dev->dev, "DeviceRemovable is changed to 1 according to platform information.\n"); + 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++) { + struct usb_port *port_dev = hub->ports[i - 1]; + + connect_type = port_dev->connect_type; + if (connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED) { + u16 mask = 1 << i; + + if (!(port_removable & mask)) { + dev_dbg(&port_dev->dev, "DeviceRemovable is changed to 1 according to platform information.\n"); + 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 + * invalid. + */ +acpi_handle usb_get_hub_port_acpi_handle(struct usb_device *hdev, + int port1) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + + if (!hub) + return NULL; + + return 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 000000000..9ebc5ef54 --- /dev/null +++ b/drivers/usb/core/hub.h @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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. + */ + +#include +#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 */ + + unsigned long event_bits[1]; /* status change bitmask */ + unsigned long change_bits[1]; /* ports with logical connect + status change */ + unsigned long removed_bits[1]; /* ports with a "removed" + device present */ + unsigned long wakeup_bits[1]; /* ports that have signaled + remote wakeup */ + unsigned long power_bits[1]; /* ports that are powered */ + unsigned long child_usage_bits[1]; /* ports powered on for + children */ + unsigned long warm_reset_bits[1]; /* ports requesting warm + reset recovery */ +#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 in_reset:1; + unsigned quirk_disable_autosuspend: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 delayed_work post_resume_work; + struct work_struct events; + spinlock_t irq_urb_lock; + struct timer_list irq_urb_retry; + struct usb_port **ports; + struct list_head onboard_devs; +}; + +/** + * struct usb port - kernel's representation of a usb port + * @child: usb device attached to the port + * @dev: generic device interface + * @port_owner: port's owner + * @peer: related usb2 and usb3 ports (share the same connector) + * @connector: USB Type-C connector + * @req: default pm qos request for hubs without port power control + * @connect_type: port's connect type + * @state: device state of the usb device attached to the port + * @state_kn: kernfs_node of the sysfs attribute that accesses @state + * @location: opaque representation of platform connector location + * @status_lock: synchronize port_event() vs usb_port_{suspend|resume} + * @portnum: port index num based one + * @is_superspeed cache super-speed status + * @usb3_lpm_u1_permit: whether USB3 U1 LPM is permitted. + * @usb3_lpm_u2_permit: whether USB3 U2 LPM is permitted. + * @early_stop: whether port initialization will be stopped earlier. + * @ignore_event: whether events of the port are ignored. + */ +struct usb_port { + struct usb_device *child; + struct device dev; + struct usb_dev_state *port_owner; + struct usb_port *peer; + struct typec_connector *connector; + struct dev_pm_qos_request *req; + enum usb_port_connect_type connect_type; + enum usb_device_state state; + struct kernfs_node *state_kn; + usb_port_location_t location; + struct mutex status_lock; + u32 over_current_count; + u8 portnum; + u32 quirks; + unsigned int early_stop:1; + unsigned int ignore_event:1; + unsigned int is_superspeed:1; + unsigned int usb3_lpm_u1_permit:1; + unsigned int usb3_lpm_u2_permit: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, struct usb_hub *hub, + int port1, bool set); +extern struct usb_hub *usb_hub_to_struct_hub(struct usb_device *hdev); +extern void hub_get(struct usb_hub *hub); +extern void hub_put(struct usb_hub *hub); +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); +extern int usb_hub_port_status(struct usb_hub *hub, int port1, + u16 *status, u16 *change); +extern int usb_port_is_power_on(struct usb_hub *hub, unsigned int portstatus); + +static inline bool hub_is_port_power_switchable(struct usb_hub *hub) +{ + __le16 hcs; + + if (!hub) + return false; + hcs = hub->descriptor->wHubCharacteristics; + return (le16_to_cpu(hcs) & HUB_CHAR_LPSM) < HUB_CHAR_NO_LPSM; +} + +static inline int hub_is_superspeed(struct usb_device *hdev) +{ + return hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS; +} + +static inline int hub_is_superspeedplus(struct usb_device *hdev) +{ + return (hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS && + le16_to_cpu(hdev->descriptor.bcdUSB) >= 0x0310 && + hdev->bos && hdev->bos->ssp_cap); +} + +static inline unsigned hub_power_on_good_delay(struct usb_hub *hub) +{ + unsigned delay = hub->descriptor->bPwrOn2PwrGood * 2; + + if (!hub->hdev->parent) /* root hub */ + return delay; + else /* Wait at least 100 msec for power to become stable */ + return max(delay, 100U); +} + +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/ledtrig-usbport.c b/drivers/usb/core/ledtrig-usbport.c new file mode 100644 index 000000000..888164477 --- /dev/null +++ b/drivers/usb/core/ledtrig-usbport.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB port LED trigger + * + * Copyright (C) 2016 Rafał Miłecki + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct usbport_trig_data { + struct led_classdev *led_cdev; + struct list_head ports; + struct notifier_block nb; + int count; /* Amount of connected matching devices */ +}; + +struct usbport_trig_port { + struct usbport_trig_data *data; + struct usb_device *hub; + int portnum; + char *port_name; + bool observed; + struct device_attribute attr; + struct list_head list; +}; + +/*************************************** + * Helpers + ***************************************/ + +/* + * usbport_trig_usb_dev_observed - Check if dev is connected to observed port + */ +static bool usbport_trig_usb_dev_observed(struct usbport_trig_data *usbport_data, + struct usb_device *usb_dev) +{ + struct usbport_trig_port *port; + + if (!usb_dev->parent) + return false; + + list_for_each_entry(port, &usbport_data->ports, list) { + if (usb_dev->parent == port->hub && + usb_dev->portnum == port->portnum) + return port->observed; + } + + return false; +} + +static int usbport_trig_usb_dev_check(struct usb_device *usb_dev, void *data) +{ + struct usbport_trig_data *usbport_data = data; + + if (usbport_trig_usb_dev_observed(usbport_data, usb_dev)) + usbport_data->count++; + + return 0; +} + +/* + * usbport_trig_update_count - Recalculate amount of connected matching devices + */ +static void usbport_trig_update_count(struct usbport_trig_data *usbport_data) +{ + struct led_classdev *led_cdev = usbport_data->led_cdev; + + usbport_data->count = 0; + usb_for_each_dev(usbport_data, usbport_trig_usb_dev_check); + led_set_brightness(led_cdev, usbport_data->count ? LED_FULL : LED_OFF); +} + +/*************************************** + * Device attr + ***************************************/ + +static ssize_t usbport_trig_port_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usbport_trig_port *port = container_of(attr, + struct usbport_trig_port, + attr); + + return sysfs_emit(buf, "%d\n", port->observed) + 1; +} + +static ssize_t usbport_trig_port_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usbport_trig_port *port = container_of(attr, + struct usbport_trig_port, + attr); + + if (!strcmp(buf, "0") || !strcmp(buf, "0\n")) + port->observed = 0; + else if (!strcmp(buf, "1") || !strcmp(buf, "1\n")) + port->observed = 1; + else + return -EINVAL; + + usbport_trig_update_count(port->data); + + return size; +} + +static struct attribute *ports_attrs[] = { + NULL, +}; + +static const struct attribute_group ports_group = { + .name = "ports", + .attrs = ports_attrs, +}; + +/*************************************** + * Adding & removing ports + ***************************************/ + +/* + * usbport_trig_port_observed - Check if port should be observed + */ +static bool usbport_trig_port_observed(struct usbport_trig_data *usbport_data, + struct usb_device *usb_dev, int port1) +{ + struct device *dev = usbport_data->led_cdev->dev; + struct device_node *led_np = dev->of_node; + struct of_phandle_args args; + struct device_node *port_np; + int count, i; + + if (!led_np) + return false; + + /* + * Get node of port being added + * + * FIXME: This is really the device node of the connected device + */ + port_np = usb_of_get_device_node(usb_dev, port1); + if (!port_np) + return false; + + of_node_put(port_np); + + /* Amount of trigger sources for this LED */ + count = of_count_phandle_with_args(led_np, "trigger-sources", + "#trigger-source-cells"); + if (count < 0) { + dev_warn(dev, "Failed to get trigger sources for %pOF\n", + led_np); + return false; + } + + /* Check list of sources for this specific port */ + for (i = 0; i < count; i++) { + int err; + + err = of_parse_phandle_with_args(led_np, "trigger-sources", + "#trigger-source-cells", i, + &args); + if (err) { + dev_err(dev, "Failed to get trigger source phandle at index %d: %d\n", + i, err); + continue; + } + + of_node_put(args.np); + + if (args.np == port_np) + return true; + } + + return false; +} + +static int usbport_trig_add_port(struct usbport_trig_data *usbport_data, + struct usb_device *usb_dev, + const char *hub_name, int portnum) +{ + struct led_classdev *led_cdev = usbport_data->led_cdev; + struct usbport_trig_port *port; + size_t len; + int err; + + port = kzalloc_obj(*port); + if (!port) { + err = -ENOMEM; + goto err_out; + } + + port->data = usbport_data; + port->hub = usb_dev; + port->portnum = portnum; + port->observed = usbport_trig_port_observed(usbport_data, usb_dev, + portnum); + + len = strlen(hub_name) + 8; + port->port_name = kzalloc(len, GFP_KERNEL); + if (!port->port_name) { + err = -ENOMEM; + goto err_free_port; + } + snprintf(port->port_name, len, "%s-port%d", hub_name, portnum); + + sysfs_attr_init(&port->attr.attr); + port->attr.attr.name = port->port_name; + port->attr.attr.mode = S_IRUSR | S_IWUSR; + port->attr.show = usbport_trig_port_show; + port->attr.store = usbport_trig_port_store; + + err = sysfs_add_file_to_group(&led_cdev->dev->kobj, &port->attr.attr, + ports_group.name); + if (err) + goto err_free_port_name; + + list_add_tail(&port->list, &usbport_data->ports); + + return 0; + +err_free_port_name: + kfree(port->port_name); +err_free_port: + kfree(port); +err_out: + return err; +} + +static int usbport_trig_add_usb_dev_ports(struct usb_device *usb_dev, + void *data) +{ + struct usbport_trig_data *usbport_data = data; + int i; + + for (i = 1; i <= usb_dev->maxchild; i++) + usbport_trig_add_port(usbport_data, usb_dev, + dev_name(&usb_dev->dev), i); + + return 0; +} + +static void usbport_trig_remove_port(struct usbport_trig_data *usbport_data, + struct usbport_trig_port *port) +{ + struct led_classdev *led_cdev = usbport_data->led_cdev; + + list_del(&port->list); + sysfs_remove_file_from_group(&led_cdev->dev->kobj, &port->attr.attr, + ports_group.name); + kfree(port->port_name); + kfree(port); +} + +static void usbport_trig_remove_usb_dev_ports(struct usbport_trig_data *usbport_data, + struct usb_device *usb_dev) +{ + struct usbport_trig_port *port, *tmp; + + list_for_each_entry_safe(port, tmp, &usbport_data->ports, list) { + if (port->hub == usb_dev) + usbport_trig_remove_port(usbport_data, port); + } +} + +/*************************************** + * Init, exit, etc. + ***************************************/ + +static int usbport_trig_notify(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct usbport_trig_data *usbport_data = + container_of(nb, struct usbport_trig_data, nb); + struct led_classdev *led_cdev = usbport_data->led_cdev; + struct usb_device *usb_dev = data; + bool observed; + + observed = usbport_trig_usb_dev_observed(usbport_data, usb_dev); + + switch (action) { + case USB_DEVICE_ADD: + usbport_trig_add_usb_dev_ports(usb_dev, usbport_data); + if (observed && usbport_data->count++ == 0) + led_set_brightness(led_cdev, LED_FULL); + return NOTIFY_OK; + case USB_DEVICE_REMOVE: + usbport_trig_remove_usb_dev_ports(usbport_data, usb_dev); + if (observed && --usbport_data->count == 0) + led_set_brightness(led_cdev, LED_OFF); + return NOTIFY_OK; + } + + return NOTIFY_DONE; +} + +static int usbport_trig_activate(struct led_classdev *led_cdev) +{ + struct usbport_trig_data *usbport_data; + int err; + + usbport_data = kzalloc_obj(*usbport_data); + if (!usbport_data) + return -ENOMEM; + usbport_data->led_cdev = led_cdev; + + /* List of ports */ + INIT_LIST_HEAD(&usbport_data->ports); + err = sysfs_create_group(&led_cdev->dev->kobj, &ports_group); + if (err) + goto err_free; + usb_for_each_dev(usbport_data, usbport_trig_add_usb_dev_ports); + usbport_trig_update_count(usbport_data); + + /* Notifications */ + usbport_data->nb.notifier_call = usbport_trig_notify; + led_set_trigger_data(led_cdev, usbport_data); + usb_register_notify(&usbport_data->nb); + return 0; + +err_free: + kfree(usbport_data); + return err; +} + +static void usbport_trig_deactivate(struct led_classdev *led_cdev) +{ + struct usbport_trig_data *usbport_data = led_get_trigger_data(led_cdev); + struct usbport_trig_port *port, *tmp; + + list_for_each_entry_safe(port, tmp, &usbport_data->ports, list) { + usbport_trig_remove_port(usbport_data, port); + } + + sysfs_remove_group(&led_cdev->dev->kobj, &ports_group); + + usb_unregister_notify(&usbport_data->nb); + + kfree(usbport_data); +} + +static struct led_trigger usbport_led_trigger = { + .name = "usbport", + .activate = usbport_trig_activate, + .deactivate = usbport_trig_deactivate, +}; + +module_led_trigger(usbport_led_trigger); + +MODULE_AUTHOR("Rafał Miłecki "); +MODULE_DESCRIPTION("USB port trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c new file mode 100644 index 000000000..75e2bfd74 --- /dev/null +++ b/drivers/usb/core/message.c @@ -0,0 +1,2521 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * message.c - synchronous message handling + * + * Released under the GPLv2 only. + */ + +#include +#include /* for scatterlist macros */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for usbcore internals */ +#include +#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. + * Whether or not the wait is killable depends on the flag passed in. + * For example, compare usb_bulk_msg() and usb_bulk_msg_killable(). + * + * For non-killable waits, we enforce a maximum limit on the timeout value. + */ +static int usb_start_wait_urb(struct urb *urb, int timeout, int *actual_length, + bool killable) +{ + struct api_context ctx; + unsigned long expire; + int retval; + long rc; + + init_completion(&ctx.done); + urb->context = &ctx; + urb->actual_length = 0; + retval = usb_submit_urb(urb, GFP_NOIO); + if (unlikely(retval)) + goto out; + + if (!killable && (timeout <= 0 || timeout > USB_MAX_SYNCHRONOUS_TIMEOUT)) + timeout = USB_MAX_SYNCHRONOUS_TIMEOUT; + expire = (timeout > 0) ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT; + if (killable) + rc = wait_for_completion_killable_timeout(&ctx.done, expire); + else + rc = wait_for_completion_timeout(&ctx.done, expire); + if (rc <= 0) { + usb_kill_urb(urb); + if (ctx.status != -ENOENT) + retval = ctx.status; + else if (rc == 0) + retval = -ETIMEDOUT; + else + retval = rc; + + dev_dbg(&urb->dev->dev, + "%s timed out or killed 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, false); + 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 + * + * Context: task context, might sleep. + * + * This function sends a simple control message to a specified endpoint and + * waits for the message to complete, or timeout. + * + * Don't use this function from within an interrupt context. 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. + * + * Return: If successful, the number of bytes transferred. Otherwise, a negative + * error number. + */ +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_obj(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); + + /* Linger a bit, prior to the next control message. */ + if (dev->quirks & USB_QUIRK_DELAY_CTRL_MSG) + msleep(200); + + kfree(dr); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_control_msg); + +/** + * usb_control_msg_send - Builds a control "send" message, sends it off and waits for completion + * @dev: pointer to the usb device to send the message to + * @endpoint: endpoint 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 + * @driver_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 + * @memflags: the flags for memory allocation for buffers + * + * Context: !in_interrupt () + * + * This function sends a control message to a specified endpoint that is not + * expected to fill in a response (i.e. a "send message") and waits for the + * message to complete, or timeout. + * + * Do not use this function from within an interrupt context. 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. + * + * The data pointer can be made to a reference on the stack, or anywhere else, + * as it will not be modified at all. This does not have the restriction that + * usb_control_msg() has where the data pointer must be to dynamically allocated + * memory (i.e. memory that can be successfully DMAed to a device). + * + * Return: If successful, 0 is returned, Otherwise, a negative error number. + */ +int usb_control_msg_send(struct usb_device *dev, __u8 endpoint, __u8 request, + __u8 requesttype, __u16 value, __u16 index, + const void *driver_data, __u16 size, int timeout, + gfp_t memflags) +{ + unsigned int pipe = usb_sndctrlpipe(dev, endpoint); + int ret; + u8 *data = NULL; + + if (size) { + data = kmemdup(driver_data, size, memflags); + if (!data) + return -ENOMEM; + } + + ret = usb_control_msg(dev, pipe, request, requesttype, value, index, + data, size, timeout); + kfree(data); + + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(usb_control_msg_send); + +/** + * usb_control_msg_recv - Builds a control "receive" message, sends it off and waits for completion + * @dev: pointer to the usb device to send the message to + * @endpoint: endpoint 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 + * @driver_data: pointer to the data to be filled in by the message + * @size: length in bytes of the data to be received + * @timeout: time in msecs to wait for the message to complete before timing out + * @memflags: the flags for memory allocation for buffers + * + * Context: !in_interrupt () + * + * This function sends a control message to a specified endpoint that is + * expected to fill in a response (i.e. a "receive message") and waits for the + * message to complete, or timeout. + * + * Do not use this function from within an interrupt context. 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. + * + * The data pointer can be made to a reference on the stack, or anywhere else + * that can be successfully written to. This function does not have the + * restriction that usb_control_msg() has where the data pointer must be to + * dynamically allocated memory (i.e. memory that can be successfully DMAed to a + * device). + * + * The "whole" message must be properly received from the device in order for + * this function to be successful. If a device returns less than the expected + * amount of data, then the function will fail. Do not use this for messages + * where a variable amount of data might be returned. + * + * Return: If successful, 0 is returned, Otherwise, a negative error number. + */ +int usb_control_msg_recv(struct usb_device *dev, __u8 endpoint, __u8 request, + __u8 requesttype, __u16 value, __u16 index, + void *driver_data, __u16 size, int timeout, + gfp_t memflags) +{ + unsigned int pipe = usb_rcvctrlpipe(dev, endpoint); + int ret; + u8 *data; + + if (!size || !driver_data) + return -EINVAL; + + data = kmalloc(size, memflags); + if (!data) + return -ENOMEM; + + ret = usb_control_msg(dev, pipe, request, requesttype, value, index, + data, size, timeout); + + if (ret < 0) + goto exit; + + if (ret == size) { + memcpy(driver_data, data, size); + ret = 0; + } else { + ret = -EREMOTEIO; + } + +exit: + kfree(data); + return ret; +} +EXPORT_SYMBOL_GPL(usb_control_msg_recv); + +/** + * 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 + * + * Context: task context, might sleep. + * + * This function sends a simple interrupt message to a specified endpoint and + * waits for the message to complete, or timeout. + * + * Don't use this function from within an interrupt context. 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. + * + * Return: + * If successful, 0. Otherwise a negative error number. The number of actual + * bytes transferred will be stored in the @actual_length parameter. + */ +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 + * + * Context: task context, might sleep. + * + * This function sends a simple bulk message to a specified endpoint + * and waits for the message to complete, or timeout. + * + * Don't use this function from within an interrupt context. 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. + * + * Return: + * If successful, 0. Otherwise a negative error number. The number of actual + * bytes transferred will be stored in the @actual_length parameter. + * + */ +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, false); +} +EXPORT_SYMBOL_GPL(usb_bulk_msg); + +/** + * usb_bulk_msg_killable - Builds a bulk urb, sends it off and waits for completion in a killable state + * @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 as long as possible) + * + * Context: task context, might sleep. + * + * This function is just like usb_blk_msg(), except that it waits in a + * killable state and there is no limit on the timeout length. + * + * Return: + * If successful, 0. Otherwise a negative error number. The number of actual + * bytes transferred will be stored in the @actual_length parameter. + * + */ +int usb_bulk_msg_killable(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, true); +} +EXPORT_SYMBOL_GPL(usb_bulk_msg_killable); + +/*-------------------------------------------------------------------*/ + +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) +{ + unsigned long flags; + struct usb_sg_request *io = urb->context; + int status = urb->status; + + spin_lock_irqsave(&io->lock, flags); + + /* 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_irqrestore(&io->lock, flags); + for (i = 0, found = 0; i < io->entries; i++) { + if (!io->urbs[i]) + continue; + if (found) { + usb_block_urb(io->urbs[i]); + 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_irqsave(&io->lock, flags); + } + + /* on the last completion, signal usb_sg_wait() */ + io->bytes += urb->actual_length; + io->count--; + if (!io->count) + complete(&io->complete); + + spin_unlock_irqrestore(&io->lock, flags); +} + + +/** + * 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 + * + * 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. + * + * Return: Zero for success, else a negative errno value. + */ +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_objs(*io->urbs, io->entries, 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: task context, might sleep. + * + * 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; + spin_unlock_irq(&io->lock); + + retval = usb_submit_urb(io->urbs[i], GFP_NOIO); + + 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; + int i, retval; + + spin_lock_irqsave(&io->lock, flags); + if (io->status || io->count == 0) { + spin_unlock_irqrestore(&io->lock, flags); + return; + } + /* shut everything down */ + io->status = -ECONNRESET; + io->count++; /* Keep the request alive until we're done */ + spin_unlock_irqrestore(&io->lock, flags); + + for (i = io->entries - 1; i >= 0; --i) { + usb_block_urb(io->urbs[i]); + + 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_irqsave(&io->lock, flags); + io->count--; + if (!io->count) + complete(&io->complete); + 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: task context, might sleep. + * + * 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. + * + * Return: 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; + + if (size <= 0) /* No point in asking for no data */ + return -EINVAL; + + 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: task context, might sleep. + * + * 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. + * + * Return: 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; + + if (size <= 0) /* No point in asking for no data */ + return -EINVAL; + + 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, + "language id specifier not provided by device, defaulting to English\n"); + 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_info(&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: task context, might sleep. + * + * 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. + * + * Return: 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) + return -EINVAL; + buf[0] = 0; + if (index <= 0 || index >= 256) + return -EINVAL; + 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 16-bit character, when UTF-8-encoded, 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 + * + * Return: 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) + return NULL; + + len = usb_string(udev, index, buf, MAX_USB_STRING_SIZE); + if (len <= 0) { + kfree(buf); + return NULL; + } + + smallbuf = krealloc(buf, len + 1, GFP_NOIO); + if (unlikely(!smallbuf)) + return buf; + return smallbuf; +} +EXPORT_SYMBOL_GPL(usb_cache_string); + +/* + * usb_get_device_descriptor - read the device descriptor + * @udev: the device whose device descriptor should be read + * + * Context: task context, might sleep. + * + * 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. + * + * Returns: a pointer to a dynamically allocated usb_device_descriptor + * structure (which the caller must deallocate), or an ERR_PTR value. + */ +struct usb_device_descriptor *usb_get_device_descriptor(struct usb_device *udev) +{ + struct usb_device_descriptor *desc; + int ret; + + desc = kmalloc_obj(*desc, GFP_NOIO); + if (!desc) + return ERR_PTR(-ENOMEM); + + ret = usb_get_descriptor(udev, USB_DT_DEVICE, 0, desc, sizeof(*desc)); + if (ret == sizeof(*desc)) + return desc; + + if (ret >= 0) + ret = -EMSGSIZE; + kfree(desc); + return ERR_PTR(ret); +} + +/* + * usb_set_isoch_delay - informs the device of the packet transmit delay + * @dev: the device whose delay is to be informed + * Context: task context, might sleep + * + * Since this is an optional request, we don't bother if it fails. + */ +int usb_set_isoch_delay(struct usb_device *dev) +{ + /* skip hub devices */ + if (dev->descriptor.bDeviceClass == USB_CLASS_HUB) + return 0; + + /* skip non-SS/non-SSP devices */ + if (dev->speed < USB_SPEED_SUPER) + return 0; + + return usb_control_msg_send(dev, 0, + USB_REQ_SET_ISOCH_DELAY, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + dev->hub_delay, 0, NULL, 0, + USB_CTRL_SET_TIMEOUT, + GFP_NOIO); +} + +/** + * usb_get_status - issues a GET_STATUS call + * @dev: the device whose status is being checked + * @recip: USB_RECIP_*; for device, interface, or endpoint + * @type: USB_STATUS_TYPE_*; for standard or PTM status types + * @target: zero (for device), else interface or endpoint number + * @data: pointer to two bytes of bitmap data + * + * Context: task context, might sleep. + * + * 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 0 and the status value in *@data (in host byte order) on success, + * or else the status code from the underlying usb_control_msg() call. + */ +int usb_get_status(struct usb_device *dev, int recip, int type, int target, + void *data) +{ + int ret; + void *status; + int length; + + switch (type) { + case USB_STATUS_TYPE_STANDARD: + length = 2; + break; + case USB_STATUS_TYPE_PTM: + if (recip != USB_RECIP_DEVICE) + return -EINVAL; + + length = 4; + break; + default: + return -EINVAL; + } + + status = kmalloc(length, GFP_KERNEL); + if (!status) + return -ENOMEM; + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_STATUS, USB_DIR_IN | recip, USB_STATUS_TYPE_STANDARD, + target, status, length, USB_CTRL_GET_TIMEOUT); + + switch (ret) { + case 4: + if (type != USB_STATUS_TYPE_PTM) { + ret = -EIO; + break; + } + + *(u32 *) data = le32_to_cpu(*(__le32 *) status); + ret = 0; + break; + case 2: + if (type != USB_STATUS_TYPE_STANDARD) { + ret = -EIO; + break; + } + + *(u16 *) data = le16_to_cpu(*(__le16 *) status); + ret = 0; + break; + default: + ret = -EIO; + } + + 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: task context, might sleep. + * + * 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. + * If a thread in your driver uses this call, make sure your disconnect() + * method can wait for it to complete. + * + * Return: 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_send(dev, 0, + USB_REQ_CLEAR_FEATURE, USB_RECIP_ENDPOINT, + USB_ENDPOINT_HALT, endp, NULL, 0, + USB_CTRL_SET_TIMEOUT, GFP_NOIO); + + /* don't un-halt or force to DATA0 except on success */ + if (result) + 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 && epnum != 0) + dev->ep_out[epnum] = NULL; + } else { + ep = dev->ep_in[epnum]; + if (reset_hardware && epnum != 0) + 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_endpoints -- Disable all endpoints for a device + * @dev: the device whose endpoints are being disabled + * @skip_ep0: 0 to disable endpoint 0, 1 to skip it. + */ +static void usb_disable_device_endpoints(struct usb_device *dev, int skip_ep0) +{ + struct usb_hcd *hcd = bus_to_hcd(dev->bus); + int i; + + 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_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; + + /* 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_disable_usb2_hardware_lpm(dev); + 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"); + + usb_disable_device_endpoints(dev, skip_ep0); +} + +/** + * 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: task context, might sleep. + * + * 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. + * + * xHCI reserves bandwidth and configures the alternate setting in + * usb_hcd_alloc_bandwidth(). If it fails the original interface altsetting + * may be disabled. Drivers cannot rely on any particular alternate + * setting being in effect after a failure. + * + * 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). If a thread in your driver uses this call, + * make sure your disconnect() method can wait for it to complete. + * + * Return: 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 i, ret, 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; + } + /* + * usb3 hosts configure the interface in usb_hcd_alloc_bandwidth, + * including freeing dropped endpoint ring buffers. + * Make sure the interface endpoints are flushed before that + */ + usb_disable_interface(dev, iface, false); + + /* 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; + } + /* Changing alt-setting also frees any allocated streams */ + for (i = 0; i < iface->cur_altsetting->desc.bNumEndpoints; i++) + iface->cur_altsetting->endpoint[i].streams = 0; + + 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_send(dev, 0, + USB_REQ_SET_INTERFACE, + USB_RECIP_INTERFACE, alternate, + interface, NULL, 0, 5000, + GFP_NOIO); + + /* 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) { + /* 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) { + 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. + * + * Return: Zero on success, else a negative error code. + * + * If this routine fails the device will probably be in an unusable state + * with endpoints disabled, and interfaces only partially enabled. + */ +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 + */ + + usb_disable_device_endpoints(dev, 1); /* skip ep0*/ + + 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; + } + + /* xHCI adds all endpoints in usb_hcd_alloc_bandwidth */ + retval = usb_hcd_alloc_bandwidth(dev, config, NULL, NULL); + if (retval < 0) { + usb_enable_lpm(dev); + mutex_unlock(hcd->bandwidth_mutex); + return retval; + } + retval = usb_control_msg_send(dev, 0, USB_REQ_SET_CONFIGURATION, 0, + config->desc.bConfigurationValue, 0, + NULL, 0, USB_CTRL_SET_TIMEOUT, + GFP_NOIO); + if (retval) { + usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL); + usb_enable_lpm(dev); + mutex_unlock(hcd->bandwidth_mutex); + return retval; + } + 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); + usb_put_dev(interface_to_usbdev(intf)); + of_node_put(dev->of_node); + kfree(intf); +} + +/* + * usb_deauthorize_interface - deauthorize an USB interface + * + * @intf: USB interface structure + */ +void usb_deauthorize_interface(struct usb_interface *intf) +{ + struct device *dev = &intf->dev; + + device_lock(dev->parent); + + if (intf->authorized) { + device_lock(dev); + intf->authorized = 0; + device_unlock(dev); + + usb_forced_unbind_intf(intf); + } + + device_unlock(dev->parent); +} + +/* + * usb_authorize_interface - authorize an USB interface + * + * @intf: USB interface structure + */ +void usb_authorize_interface(struct usb_interface *intf) +{ + struct device *dev = &intf->dev; + + if (!intf->authorized) { + device_lock(dev); + intf->authorized = 1; /* authorize interface */ + device_unlock(dev); + } +} + +static int usb_if_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + const struct usb_device *usb_dev; + const struct usb_interface *intf; + const 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; +} + +const 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 + * 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) { + usb_reset_device(udev); + usb_unlock_device(udev); + } + usb_put_intf(iface); /* Undo _get_ in usb_queue_reset_device() */ +} + +/* + * Internal function to set the wireless_status sysfs attribute + * See usb_set_wireless_status() for more details + */ +static void __usb_wireless_status_intf(struct work_struct *ws) +{ + struct usb_interface *iface = + container_of(ws, struct usb_interface, wireless_status_work); + + device_lock(iface->dev.parent); + if (iface->sysfs_files_created) + usb_update_wireless_status_attr(iface); + device_unlock(iface->dev.parent); + usb_put_intf(iface); /* Undo _get_ in usb_set_wireless_status() */ +} + +/** + * usb_set_wireless_status - sets the wireless_status struct member + * @iface: the interface to modify + * @status: the new wireless status + * + * Set the wireless_status struct member to the new value, and emit + * sysfs changes as necessary. + * + * Returns: 0 on success, -EALREADY if already set. + */ +int usb_set_wireless_status(struct usb_interface *iface, + enum usb_wireless_status status) +{ + if (iface->wireless_status == status) + return -EALREADY; + + usb_get_intf(iface); + iface->wireless_status = status; + schedule_work(&iface->wireless_status_work); + + return 0; +} +EXPORT_SYMBOL_GPL(usb_set_wireless_status); + +/* + * usb_set_configuration - Makes a particular device setting be current + * @dev: the device whose configuration is being updated + * @configuration: the configuration being chosen. + * + * Context: task context, might sleep. Caller holds 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_objs(*new_interfaces, nintf, GFP_NOIO); + if (!new_interfaces) + return -ENOMEM; + + for (; n < nintf; ++n) { + new_interfaces[n] = kzalloc_obj(struct usb_interface, + GFP_NOIO); + if (!new_interfaces[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; + u8 ifnum; + + cp->interface[i] = intf = new_interfaces[i]; + intfc = cp->intf_cache[i]; + intf->altsetting = intfc->altsetting; + intf->num_altsetting = intfc->num_altsetting; + intf->authorized = !!HCD_INTF_AUTHORIZED(hcd); + 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]; + + ifnum = alt->desc.bInterfaceNumber; + intf->intf_assoc = find_iad(dev, cp, ifnum); + intf->cur_altsetting = alt; + usb_enable_interface(dev, intf, true); + intf->dev.parent = &dev->dev; + if (usb_of_has_combined_node(dev)) { + device_set_of_node_from_dev(&intf->dev, &dev->dev); + } else { + intf->dev.of_node = usb_of_get_interface_node(dev, + configuration, ifnum); + } + ACPI_COMPANION_SET(&intf->dev, ACPI_COMPANION(&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; + INIT_WORK(&intf->reset_ws, __usb_queue_reset_device); + INIT_WORK(&intf->wireless_status_work, __usb_wireless_status_intf); + 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, ifnum); + usb_get_dev(dev); + } + kfree(new_interfaces); + + ret = usb_control_msg_send(dev, 0, USB_REQ_SET_CONFIGURATION, 0, + configuration, 0, NULL, 0, + USB_CTRL_SET_TIMEOUT, GFP_NOIO); + if (ret && 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]; + + if (intf->dev.of_node && + !of_device_is_available(intf->dev.of_node)) { + dev_info(&dev->dev, "skipping disabled interface %d\n", + intf->cur_altsetting->desc.bInterfaceNumber); + continue; + } + + 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; +} +EXPORT_SYMBOL_GPL(usb_set_configuration); + +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. + * + * Return: 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_obj(*req); + 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); + +/** + * cdc_parse_cdc_header - parse the extra headers present in CDC devices + * @hdr: the place to put the results of the parsing + * @intf: the interface for which parsing is requested + * @buffer: pointer to the extra headers to be parsed + * @buflen: length of the extra headers + * + * This evaluates the extra headers present in CDC devices which + * bind the interfaces for data and control and provide details + * about the capabilities of the device. + * + * Return: number of descriptors parsed or -EINVAL + * if the header is contradictory beyond salvage + */ + +int cdc_parse_cdc_header(struct usb_cdc_parsed_header *hdr, + struct usb_interface *intf, + u8 *buffer, + int buflen) +{ + /* duplicates are ignored */ + struct usb_cdc_union_desc *union_header = NULL; + + /* duplicates are not tolerated */ + struct usb_cdc_header_desc *header = NULL; + struct usb_cdc_ether_desc *ether = NULL; + struct usb_cdc_mdlm_detail_desc *detail = NULL; + struct usb_cdc_mdlm_desc *desc = NULL; + + unsigned int elength; + int cnt = 0; + + memset(hdr, 0x00, sizeof(struct usb_cdc_parsed_header)); + hdr->phonet_magic_present = false; + while (buflen > 0) { + elength = buffer[0]; + if (!elength) { + dev_err(&intf->dev, "skipping garbage byte\n"); + elength = 1; + goto next_desc; + } + if ((buflen < elength) || (elength < 3)) { + dev_err(&intf->dev, "invalid descriptor buffer length\n"); + break; + } + 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 (elength < sizeof(struct usb_cdc_union_desc)) + goto next_desc; + 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: + if (elength < sizeof(struct usb_cdc_country_functional_desc)) + goto next_desc; + hdr->usb_cdc_country_functional_desc = + (struct usb_cdc_country_functional_desc *)buffer; + break; + case USB_CDC_HEADER_TYPE: + if (elength != sizeof(struct usb_cdc_header_desc)) + goto next_desc; + if (header) + return -EINVAL; + header = (struct usb_cdc_header_desc *)buffer; + break; + case USB_CDC_ACM_TYPE: + if (elength < sizeof(struct usb_cdc_acm_descriptor)) + goto next_desc; + hdr->usb_cdc_acm_descriptor = + (struct usb_cdc_acm_descriptor *)buffer; + break; + case USB_CDC_ETHERNET_TYPE: + if (elength != sizeof(struct usb_cdc_ether_desc)) + goto next_desc; + if (ether) + return -EINVAL; + ether = (struct usb_cdc_ether_desc *)buffer; + break; + case USB_CDC_CALL_MANAGEMENT_TYPE: + if (elength < sizeof(struct usb_cdc_call_mgmt_descriptor)) + goto next_desc; + hdr->usb_cdc_call_mgmt_descriptor = + (struct usb_cdc_call_mgmt_descriptor *)buffer; + break; + case USB_CDC_DMM_TYPE: + if (elength < sizeof(struct usb_cdc_dmm_desc)) + goto next_desc; + hdr->usb_cdc_dmm_desc = + (struct usb_cdc_dmm_desc *)buffer; + break; + case USB_CDC_MDLM_TYPE: + if (elength < sizeof(struct usb_cdc_mdlm_desc)) + goto next_desc; + if (desc) + return -EINVAL; + desc = (struct usb_cdc_mdlm_desc *)buffer; + break; + case USB_CDC_MDLM_DETAIL_TYPE: + if (elength < sizeof(struct usb_cdc_mdlm_detail_desc)) + goto next_desc; + if (detail) + return -EINVAL; + detail = (struct usb_cdc_mdlm_detail_desc *)buffer; + break; + case USB_CDC_NCM_TYPE: + if (elength < sizeof(struct usb_cdc_ncm_desc)) + goto next_desc; + hdr->usb_cdc_ncm_desc = (struct usb_cdc_ncm_desc *)buffer; + break; + case USB_CDC_MBIM_TYPE: + if (elength < sizeof(struct usb_cdc_mbim_desc)) + goto next_desc; + + hdr->usb_cdc_mbim_desc = (struct usb_cdc_mbim_desc *)buffer; + break; + case USB_CDC_MBIM_EXTENDED_TYPE: + if (elength < sizeof(struct usb_cdc_mbim_extended_desc)) + goto next_desc; + hdr->usb_cdc_mbim_extended_desc = + (struct usb_cdc_mbim_extended_desc *)buffer; + break; + case CDC_PHONET_MAGIC_NUMBER: + hdr->phonet_magic_present = true; + break; + default: + /* + * there are LOTS more CDC descriptors that + * could legitimately be found here. + */ + dev_dbg(&intf->dev, "Ignoring descriptor: type %02x, length %ud\n", + buffer[2], elength); + goto next_desc; + } + cnt++; +next_desc: + buflen -= elength; + buffer += elength; + } + hdr->usb_cdc_union_desc = union_header; + hdr->usb_cdc_header_desc = header; + hdr->usb_cdc_mdlm_detail_desc = detail; + hdr->usb_cdc_mdlm_desc = desc; + hdr->usb_cdc_ether_desc = ether; + return cnt; +} + +EXPORT_SYMBOL(cdc_parse_cdc_header); diff --git a/drivers/usb/core/notify.c b/drivers/usb/core/notify.c new file mode 100644 index 000000000..e61436637 --- /dev/null +++ b/drivers/usb/core/notify.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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. + * + * Released under the GPLv2 only. + */ + + +#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) +{ + blocking_notifier_call_chain(&usb_notifier_list, + USB_DEVICE_REMOVE, udev); +} + +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/of.c b/drivers/usb/core/of.c new file mode 100644 index 000000000..a6ee20d87 --- /dev/null +++ b/drivers/usb/core/of.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * of.c The helpers for hcd device tree support + * + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Author: Peter Chen + * Copyright (C) 2017 Johan Hovold + */ + +#include +#include +#include + +/** + * usb_of_get_device_node() - get a USB device node + * @hub: hub to which device is connected + * @port1: one-based index of port + * + * Look up the node of a USB device given its parent hub device and one-based + * port number. + * + * Return: A pointer to the node with incremented refcount if found, or + * %NULL otherwise. + */ +struct device_node *usb_of_get_device_node(struct usb_device *hub, int port1) +{ + struct device_node *node; + u32 reg; + + for_each_child_of_node(hub->dev.of_node, node) { + if (of_property_read_u32(node, "reg", ®)) + continue; + + if (reg == port1) + return node; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(usb_of_get_device_node); + +/** + * usb_of_has_combined_node() - determine whether a device has a combined node + * @udev: USB device + * + * Determine whether a USB device has a so called combined node which is + * shared with its sole interface. This is the case if and only if the device + * has a node and its descriptors report the following: + * + * 1) bDeviceClass is 0 or 9, and + * 2) bNumConfigurations is 1, and + * 3) bNumInterfaces is 1. + * + * Return: True iff the device has a device node and its descriptors match the + * criteria for a combined node. + */ +bool usb_of_has_combined_node(struct usb_device *udev) +{ + struct usb_device_descriptor *ddesc = &udev->descriptor; + struct usb_config_descriptor *cdesc; + + if (!udev->dev.of_node) + return false; + + switch (ddesc->bDeviceClass) { + case USB_CLASS_PER_INTERFACE: + case USB_CLASS_HUB: + if (ddesc->bNumConfigurations == 1) { + cdesc = &udev->config->desc; + if (cdesc->bNumInterfaces == 1) + return true; + } + } + + return false; +} +EXPORT_SYMBOL_GPL(usb_of_has_combined_node); + +static bool usb_of_has_devices_or_graph(const struct usb_device *hub) +{ + const struct device_node *np = hub->dev.of_node; + + if (of_graph_is_present(np)) + return true; + + for_each_child_of_node_scoped(np, child) + if (of_property_present(child, "reg")) + return true; + + return false; +} + +/** + * usb_of_get_connect_type() - get a USB hub's port connect_type + * @hub: hub to which port is for @port1 + * @port1: one-based index of port + * + * Get the connect_type of @port1 based on the device node for @hub. If the + * port is described in the OF graph, the connect_type is "hotplug". If the + * @hub has a child device has with a 'reg' property equal to @port1 the + * connect_type is "hard-wired". If there isn't an OF graph or child node at + * all then the connect_type is "unknown". Otherwise, the port is considered + * "unused" because it isn't described at all. + * + * Return: A connect_type for @port1 based on the device node for @hub. + */ +enum usb_port_connect_type usb_of_get_connect_type(struct usb_device *hub, int port1) +{ + struct device_node *np, *child, *ep, *remote_np; + enum usb_port_connect_type connect_type; + + /* Only set connect_type if binding has ports/hardwired devices. */ + if (!usb_of_has_devices_or_graph(hub)) + return USB_PORT_CONNECT_TYPE_UNKNOWN; + + /* Assume port is unused if there's a graph or a child node. */ + connect_type = USB_PORT_NOT_USED; + + np = hub->dev.of_node; + /* + * Hotplug ports are connected to an available remote node, e.g. + * usb-a-connector compatible node, in the OF graph. + */ + if (of_graph_is_present(np)) { + ep = of_graph_get_endpoint_by_regs(np, port1, -1); + if (ep) { + remote_np = of_graph_get_remote_port_parent(ep); + of_node_put(ep); + if (of_device_is_available(remote_np)) + connect_type = USB_PORT_CONNECT_TYPE_HOT_PLUG; + of_node_put(remote_np); + } + } + + /* + * Hard-wired ports are child nodes with a reg property corresponding + * to the port number, i.e. a usb device. + */ + child = usb_of_get_device_node(hub, port1); + if (of_device_is_available(child)) + connect_type = USB_PORT_CONNECT_TYPE_HARD_WIRED; + of_node_put(child); + + return connect_type; +} +EXPORT_SYMBOL_GPL(usb_of_get_connect_type); + +/** + * usb_of_get_interface_node() - get a USB interface node + * @udev: USB device of interface + * @config: configuration value + * @ifnum: interface number + * + * Look up the node of a USB interface given its USB device, configuration + * value and interface number. + * + * Return: A pointer to the node with incremented refcount if found, or + * %NULL otherwise. + */ +struct device_node * +usb_of_get_interface_node(struct usb_device *udev, u8 config, u8 ifnum) +{ + struct device_node *node; + u32 reg[2]; + + for_each_child_of_node(udev->dev.of_node, node) { + if (of_property_read_u32_array(node, "reg", reg, 2)) + continue; + + if (reg[0] == ifnum && reg[1] == config) + return node; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(usb_of_get_interface_node); diff --git a/drivers/usb/core/offload.c b/drivers/usb/core/offload.c new file mode 100644 index 000000000..9db3cfedd --- /dev/null +++ b/drivers/usb/core/offload.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * offload.c - USB offload related functions + * + * Copyright (c) 2025, Google LLC. + * + * Author: Guan-Yu Lin + */ + +#include + +#include "usb.h" + +/** + * usb_offload_get - increment the offload_usage of a USB device + * @udev: the USB device to increment its offload_usage + * + * Incrementing the offload_usage of a usb_device indicates that offload is + * enabled on this usb_device; that is, another entity is actively handling USB + * transfers. This information allows the USB driver to adjust its power + * management policy based on offload activity. + * + * Return: 0 on success. A negative error code otherwise. + */ +int usb_offload_get(struct usb_device *udev) +{ + int ret = 0; + + if (!usb_get_dev(udev)) + return -ENODEV; + + if (pm_runtime_get_if_active(&udev->dev) != 1) { + ret = -EBUSY; + goto err_rpm; + } + + spin_lock(&udev->offload_lock); + + if (udev->offload_pm_locked) { + ret = -EAGAIN; + goto err; + } + + udev->offload_usage++; + +err: + spin_unlock(&udev->offload_lock); + pm_runtime_put_autosuspend(&udev->dev); +err_rpm: + usb_put_dev(udev); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_offload_get); + +/** + * usb_offload_put - drop the offload_usage of a USB device + * @udev: the USB device to drop its offload_usage + * + * The inverse operation of usb_offload_get, which drops the offload_usage of + * a USB device. This information allows the USB driver to adjust its power + * management policy based on offload activity. + * + * Return: 0 on success. A negative error code otherwise. + */ +int usb_offload_put(struct usb_device *udev) +{ + int ret = 0; + + if (!usb_get_dev(udev)) + return -ENODEV; + + if (pm_runtime_get_if_active(&udev->dev) != 1) { + ret = -EBUSY; + goto err_rpm; + } + + spin_lock(&udev->offload_lock); + + if (udev->offload_pm_locked) { + ret = -EAGAIN; + goto err; + } + + /* Drop the count when it wasn't 0, ignore the operation otherwise. */ + if (udev->offload_usage) + udev->offload_usage--; + +err: + spin_unlock(&udev->offload_lock); + pm_runtime_put_autosuspend(&udev->dev); +err_rpm: + usb_put_dev(udev); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_offload_put); + +/** + * usb_offload_check - check offload activities on a USB device + * @udev: the USB device to check its offload activity. + * + * Check if there are any offload activity on the USB device right now. This + * information could be used for power management or other forms of resource + * management. + * + * The caller must hold @udev's device lock. In addition, the caller should + * ensure the device itself and the downstream usb devices are all marked as + * "offload_pm_locked" to ensure the correctness of the return value. + * + * Returns true on any offload activity, false otherwise. + */ +bool usb_offload_check(struct usb_device *udev) __must_hold(&udev->dev->mutex) +{ + struct usb_device *child; + bool active = false; + int port1; + + if (udev->offload_usage) + return true; + + usb_hub_for_each_child(udev, port1, child) { + usb_lock_device(child); + active = usb_offload_check(child); + usb_unlock_device(child); + + if (active) + break; + } + + return active; +} +EXPORT_SYMBOL_GPL(usb_offload_check); + +/** + * usb_offload_set_pm_locked - set the PM lock state of a USB device + * @udev: the USB device to modify + * @locked: the new lock state + * + * Setting @locked to true prevents offload_usage from being modified. This + * ensures that offload activities cannot be started or stopped during critical + * power management transitions, maintaining a stable state for the duration + * of the transition. + */ +void usb_offload_set_pm_locked(struct usb_device *udev, bool locked) +{ + spin_lock(&udev->offload_lock); + udev->offload_pm_locked = locked; + spin_unlock(&udev->offload_lock); +} +EXPORT_SYMBOL_GPL(usb_offload_set_pm_locked); diff --git a/drivers/usb/core/otg_productlist.h b/drivers/usb/core/otg_productlist.h new file mode 100644 index 000000000..db67df29f --- /dev/null +++ b/drivers/usb/core/otg_productlist.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* Copyright (C) 2004 Texas Instruments */ + +/* + * This OTG and Embedded Host list is "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 productlist_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 IS_ENABLED(CONFIG_USB_TEST) +/* gadget zero, for testing */ +{ USB_DEVICE(0x0525, 0xa4a0), }, +#endif + +{ } /* Terminating entry */ +}; + +static int is_targeted(struct usb_device *dev) +{ + struct usb_device_id *id = productlist_table; + + /* 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; + + /* OTG PET device is always targeted (see OTG 2.0 ECN 6.4.2) */ + if ((le16_to_cpu(dev->descriptor.idVendor) == 0x1a0a && + le16_to_cpu(dev->descriptor.idProduct) == 0x0200)) + return 1; + + /* NOTE: can't use usb_match_id() since interface caches + * aren't set up yet. this is cut/paste from that code. + */ + for (id = productlist_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)); + + return 0; +} + diff --git a/drivers/usb/core/phy.c b/drivers/usb/core/phy.c new file mode 100644 index 000000000..4d966cc9c --- /dev/null +++ b/drivers/usb/core/phy.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * A wrapper for multiple PHYs which passes all phy_* function calls to + * multiple (actual) PHY devices. This is comes handy when initializing + * all PHYs on a HCD and to keep them all in the same state. + * + * Copyright (C) 2018 Martin Blumenstingl + */ + +#include +#include +#include +#include + +#include "phy.h" + +struct usb_phy_roothub { + struct phy *phy; + struct list_head list; +}; + +/* Allocate the roothub_entry by specific name of phy */ +static int usb_phy_roothub_add_phy_by_name(struct device *dev, const char *name, + struct list_head *list) +{ + struct usb_phy_roothub *roothub_entry; + struct phy *phy; + + phy = devm_of_phy_get(dev, dev->of_node, name); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + roothub_entry = devm_kzalloc(dev, sizeof(*roothub_entry), GFP_KERNEL); + if (!roothub_entry) + return -ENOMEM; + + INIT_LIST_HEAD(&roothub_entry->list); + + roothub_entry->phy = phy; + + list_add_tail(&roothub_entry->list, list); + + return 0; +} + +static int usb_phy_roothub_add_phy(struct device *dev, int index, + struct list_head *list) +{ + struct usb_phy_roothub *roothub_entry; + struct phy *phy; + + phy = devm_of_phy_get_by_index(dev, dev->of_node, index); + if (IS_ERR(phy)) { + if (PTR_ERR(phy) == -ENODEV) + return 0; + else + return PTR_ERR(phy); + } + + roothub_entry = devm_kzalloc(dev, sizeof(*roothub_entry), GFP_KERNEL); + if (!roothub_entry) + return -ENOMEM; + + INIT_LIST_HEAD(&roothub_entry->list); + + roothub_entry->phy = phy; + + list_add_tail(&roothub_entry->list, list); + + return 0; +} + +struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev) +{ + struct usb_phy_roothub *phy_roothub; + int i, num_phys, err; + + if (!IS_ENABLED(CONFIG_GENERIC_PHY)) + return NULL; + + num_phys = of_count_phandle_with_args(dev->of_node, "phys", + "#phy-cells"); + if (num_phys <= 0) + return NULL; + + phy_roothub = devm_kzalloc(dev, sizeof(*phy_roothub), GFP_KERNEL); + if (!phy_roothub) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&phy_roothub->list); + + if (!usb_phy_roothub_add_phy_by_name(dev, "usb2-phy", &phy_roothub->list)) + return phy_roothub; + + for (i = 0; i < num_phys; i++) { + err = usb_phy_roothub_add_phy(dev, i, &phy_roothub->list); + if (err) + return ERR_PTR(err); + } + + return phy_roothub; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc); + +/** + * usb_phy_roothub_alloc_usb3_phy - alloc the roothub + * @dev: the device of the host controller + * + * Allocate the usb phy roothub if the host use a generic usb3-phy. + * + * Return: On success, a pointer to the usb_phy_roothub. Otherwise, + * %NULL if no use usb3 phy or %-ENOMEM if out of memory. + */ +struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev) +{ + struct usb_phy_roothub *phy_roothub; + int num_phys, usb2_phy_index; + + if (!IS_ENABLED(CONFIG_GENERIC_PHY)) + return NULL; + + num_phys = of_count_phandle_with_args(dev->of_node, "phys", + "#phy-cells"); + if (num_phys <= 0) + return NULL; + + /* + * If 'usb2-phy' is not present, usb_phy_roothub_alloc() added + * all PHYs to the primary HCD's phy_roothub already, so skip + * adding 'usb3-phy' here to avoid double use of that. + */ + usb2_phy_index = of_property_match_string(dev->of_node, "phy-names", + "usb2-phy"); + if (usb2_phy_index < 0) + return NULL; + + phy_roothub = devm_kzalloc(dev, sizeof(*phy_roothub), GFP_KERNEL); + if (!phy_roothub) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&phy_roothub->list); + + if (!usb_phy_roothub_add_phy_by_name(dev, "usb3-phy", &phy_roothub->list)) + return phy_roothub; + + return NULL; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc_usb3_phy); + +int usb_phy_roothub_init(struct usb_phy_roothub *phy_roothub) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_init(roothub_entry->phy); + if (err) + goto err_exit_phys; + } + + return 0; + +err_exit_phys: + list_for_each_entry_continue_reverse(roothub_entry, head, list) + phy_exit(roothub_entry->phy); + + return err; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_init); + +int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err, ret = 0; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_exit(roothub_entry->phy); + if (err) + ret = err; + } + + return ret; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_exit); + +int usb_phy_roothub_set_mode(struct usb_phy_roothub *phy_roothub, + enum phy_mode mode) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_set_mode(roothub_entry->phy, mode); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_set_mode); + +int usb_phy_roothub_calibrate(struct usb_phy_roothub *phy_roothub) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_calibrate(roothub_entry->phy); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_calibrate); + +/** + * usb_phy_roothub_notify_connect() - connect notification + * @phy_roothub: the phy of roothub, if the host use a generic phy. + * @port: the port index for connect + * + * If the phy needs to get connection status, the callback can be used. + * Returns: %0 if successful, a negative error code otherwise + */ +int usb_phy_roothub_notify_connect(struct usb_phy_roothub *phy_roothub, int port) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_notify_connect(roothub_entry->phy, port); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_notify_connect); + +/** + * usb_phy_roothub_notify_disconnect() - disconnect notification + * @phy_roothub: the phy of roothub, if the host use a generic phy. + * @port: the port index for disconnect + * + * If the phy needs to get connection status, the callback can be used. + * Returns: %0 if successful, a negative error code otherwise + */ +int usb_phy_roothub_notify_disconnect(struct usb_phy_roothub *phy_roothub, int port) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_notify_disconnect(roothub_entry->phy, port); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_notify_disconnect); + +int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_power_on(roothub_entry->phy); + if (err) + goto err_out; + } + + return 0; + +err_out: + list_for_each_entry_continue_reverse(roothub_entry, head, list) + phy_power_off(roothub_entry->phy); + + return err; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_power_on); + +void usb_phy_roothub_power_off(struct usb_phy_roothub *phy_roothub) +{ + struct usb_phy_roothub *roothub_entry; + + if (!phy_roothub) + return; + + list_for_each_entry_reverse(roothub_entry, &phy_roothub->list, list) + phy_power_off(roothub_entry->phy); +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_power_off); + +int usb_phy_roothub_suspend(struct device *controller_dev, + struct usb_phy_roothub *phy_roothub) +{ + usb_phy_roothub_power_off(phy_roothub); + + /* keep the PHYs initialized so the device can wake up the system */ + if (device_may_wakeup(controller_dev)) + return 0; + + return usb_phy_roothub_exit(phy_roothub); +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_suspend); + +int usb_phy_roothub_resume(struct device *controller_dev, + struct usb_phy_roothub *phy_roothub) +{ + int err; + + /* if the device can't wake up the system _exit was called */ + if (!device_may_wakeup(controller_dev)) { + err = usb_phy_roothub_init(phy_roothub); + if (err) + return err; + } + + err = usb_phy_roothub_power_on(phy_roothub); + + /* undo _init if _power_on failed */ + if (err && !device_may_wakeup(controller_dev)) + usb_phy_roothub_exit(phy_roothub); + + return err; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_resume); diff --git a/drivers/usb/core/phy.h b/drivers/usb/core/phy.h new file mode 100644 index 000000000..88b49c0ea --- /dev/null +++ b/drivers/usb/core/phy.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * USB roothub wrapper + * + * Copyright (C) 2018 Martin Blumenstingl + */ + +#ifndef __USB_CORE_PHY_H_ +#define __USB_CORE_PHY_H_ + +struct device; +struct usb_phy_roothub; + +struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev); +struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev); + +int usb_phy_roothub_init(struct usb_phy_roothub *phy_roothub); +int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub); + +int usb_phy_roothub_set_mode(struct usb_phy_roothub *phy_roothub, + enum phy_mode mode); +int usb_phy_roothub_calibrate(struct usb_phy_roothub *phy_roothub); +int usb_phy_roothub_notify_connect(struct usb_phy_roothub *phy_roothub, int port); +int usb_phy_roothub_notify_disconnect(struct usb_phy_roothub *phy_roothub, int port); +int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub); +void usb_phy_roothub_power_off(struct usb_phy_roothub *phy_roothub); + +int usb_phy_roothub_suspend(struct device *controller_dev, + struct usb_phy_roothub *phy_roothub); +int usb_phy_roothub_resume(struct device *controller_dev, + struct usb_phy_roothub *phy_roothub); + +#endif /* __USB_CORE_PHY_H_ */ diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c new file mode 100644 index 000000000..b4452b665 --- /dev/null +++ b/drivers/usb/core/port.c @@ -0,0 +1,866 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * usb port device code + * + * Copyright (C) 2012 Intel Corp + * + * Author: Lan Tianyu + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "hub.h" + +static int usb_port_block_power_off; + +static const struct attribute_group *port_dev_group[]; + +static bool usb_port_allow_power_off(struct usb_device *hdev, + struct usb_hub *hub, + struct usb_port *port_dev) +{ + if (hub_is_port_power_switchable(hub)) + return true; + + if (!IS_ENABLED(CONFIG_ACPI)) + return false; + + return port_dev->connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED && + usb_acpi_power_manageable(hdev, port_dev->portnum - 1); +} + +static ssize_t early_stop_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + + return sysfs_emit(buf, "%s\n", str_yes_no(port_dev->early_stop)); +} + +static ssize_t early_stop_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_port *port_dev = to_usb_port(dev); + bool value; + + if (kstrtobool(buf, &value)) + return -EINVAL; + + if (value) + port_dev->early_stop = 1; + else + port_dev->early_stop = 0; + + return count; +} +static DEVICE_ATTR_RW(early_stop); + +static ssize_t disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + struct usb_device *hdev = to_usb_device(dev->parent->parent); + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + struct usb_interface *intf = to_usb_interface(dev->parent); + int port1 = port_dev->portnum; + u16 portstatus, unused; + bool disabled; + int rc; + struct kernfs_node *kn; + + if (!hub) + return -ENODEV; + hub_get(hub); + rc = usb_autopm_get_interface(intf); + if (rc < 0) + goto out_hub_get; + + /* + * Prevent deadlock if another process is concurrently + * trying to unregister hdev. + */ + kn = sysfs_break_active_protection(&dev->kobj, &attr->attr); + if (!kn) { + rc = -ENODEV; + goto out_autopm; + } + usb_lock_device(hdev); + if (hub->disconnected) { + rc = -ENODEV; + goto out_hdev_lock; + } + + usb_hub_port_status(hub, port1, &portstatus, &unused); + disabled = !usb_port_is_power_on(hub, portstatus); + + out_hdev_lock: + usb_unlock_device(hdev); + sysfs_unbreak_active_protection(kn); + out_autopm: + usb_autopm_put_interface(intf); + out_hub_get: + hub_put(hub); + + if (rc) + return rc; + + return sysfs_emit(buf, "%s\n", disabled ? "1" : "0"); +} + +static ssize_t disable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_port *port_dev = to_usb_port(dev); + struct usb_device *hdev = to_usb_device(dev->parent->parent); + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + struct usb_interface *intf = to_usb_interface(dev->parent); + int port1 = port_dev->portnum; + bool disabled; + int rc; + struct kernfs_node *kn; + + if (!hub) + return -ENODEV; + rc = kstrtobool(buf, &disabled); + if (rc) + return rc; + + hub_get(hub); + rc = usb_autopm_get_interface(intf); + if (rc < 0) + goto out_hub_get; + + /* + * Prevent deadlock if another process is concurrently + * trying to unregister hdev. + */ + kn = sysfs_break_active_protection(&dev->kobj, &attr->attr); + if (!kn) { + rc = -ENODEV; + goto out_autopm; + } + usb_lock_device(hdev); + if (hub->disconnected) { + rc = -ENODEV; + goto out_hdev_lock; + } + + if (disabled && port_dev->child) + usb_disconnect(&port_dev->child); + + rc = usb_hub_set_port_power(hdev, hub, port1, !disabled); + msleep(2 * hub_power_on_good_delay(hub)); + + if (disabled) { + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION); + if (!port_dev->is_superspeed) + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); + } + + if (!rc) + rc = count; + + out_hdev_lock: + usb_unlock_device(hdev); + sysfs_unbreak_active_protection(kn); + out_autopm: + usb_autopm_put_interface(intf); + out_hub_get: + hub_put(hub); + + return rc; +} +static DEVICE_ATTR_RW(disable); + +static ssize_t location_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + + return sysfs_emit(buf, "0x%08x\n", port_dev->location); +} +static DEVICE_ATTR_RO(location); + +static ssize_t connect_type_show(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 sysfs_emit(buf, "%s\n", result); +} +static DEVICE_ATTR_RO(connect_type); + +static ssize_t state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + enum usb_device_state state = READ_ONCE(port_dev->state); + + return sysfs_emit(buf, "%s\n", usb_state_string(state)); +} +static DEVICE_ATTR_RO(state); + +static ssize_t over_current_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + + return sysfs_emit(buf, "%u\n", port_dev->over_current_count); +} +static DEVICE_ATTR_RO(over_current_count); + +static ssize_t quirks_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + + return sysfs_emit(buf, "%08x\n", port_dev->quirks); +} + +static ssize_t quirks_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_port *port_dev = to_usb_port(dev); + u32 value; + + if (kstrtou32(buf, 16, &value)) + return -EINVAL; + + port_dev->quirks = value; + return count; +} +static DEVICE_ATTR_RW(quirks); + +static ssize_t usb3_lpm_permit_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + const char *p; + + if (port_dev->usb3_lpm_u1_permit) { + if (port_dev->usb3_lpm_u2_permit) + p = "u1_u2"; + else + p = "u1"; + } else { + if (port_dev->usb3_lpm_u2_permit) + p = "u2"; + else + p = "0"; + } + + return sysfs_emit(buf, "%s\n", p); +} + +static ssize_t usb3_lpm_permit_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_port *port_dev = to_usb_port(dev); + struct usb_device *udev = port_dev->child; + struct usb_hcd *hcd; + + if (!strncmp(buf, "u1_u2", 5)) { + port_dev->usb3_lpm_u1_permit = 1; + port_dev->usb3_lpm_u2_permit = 1; + + } else if (!strncmp(buf, "u1", 2)) { + port_dev->usb3_lpm_u1_permit = 1; + port_dev->usb3_lpm_u2_permit = 0; + + } else if (!strncmp(buf, "u2", 2)) { + port_dev->usb3_lpm_u1_permit = 0; + port_dev->usb3_lpm_u2_permit = 1; + + } else if (!strncmp(buf, "0", 1)) { + port_dev->usb3_lpm_u1_permit = 0; + port_dev->usb3_lpm_u2_permit = 0; + } else + return -EINVAL; + + /* If device is connected to the port, disable or enable lpm + * to make new u1 u2 setting take effect immediately. + */ + if (udev) { + hcd = bus_to_hcd(udev->bus); + if (!hcd) + return -EINVAL; + usb_lock_device(udev); + mutex_lock(hcd->bandwidth_mutex); + if (!usb_disable_lpm(udev)) + usb_enable_lpm(udev); + mutex_unlock(hcd->bandwidth_mutex); + usb_unlock_device(udev); + } + + return count; +} +static DEVICE_ATTR_RW(usb3_lpm_permit); + +static struct attribute *port_dev_attrs[] = { + &dev_attr_connect_type.attr, + &dev_attr_state.attr, + &dev_attr_location.attr, + &dev_attr_quirks.attr, + &dev_attr_over_current_count.attr, + &dev_attr_disable.attr, + &dev_attr_early_stop.attr, + NULL, +}; + +static const 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 struct attribute *port_dev_usb3_attrs[] = { + &dev_attr_usb3_lpm_permit.attr, + NULL, +}; + +static const struct attribute_group port_dev_usb3_attr_grp = { + .attrs = port_dev_usb3_attrs, +}; + +static const struct attribute_group *port_dev_usb3_group[] = { + &port_dev_attr_grp, + &port_dev_usb3_attr_grp, + NULL, +}; + +static void usb_port_device_release(struct device *dev) +{ + struct usb_port *port_dev = to_usb_port(dev); + + kfree(port_dev->req); + kfree(port_dev); +} + +#ifdef CONFIG_PM +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); + struct usb_device *udev = port_dev->child; + struct usb_port *peer = port_dev->peer; + int port1 = port_dev->portnum; + int retval; + + if (!hub) + return -EINVAL; + if (hub->in_reset) { + set_bit(port1, hub->power_bits); + return 0; + } + + /* + * Power on our usb3 peer before this usb2 port to prevent a usb3 + * device from degrading to its usb2 connection + */ + if (!port_dev->is_superspeed && peer) + pm_runtime_get_sync(&peer->dev); + + retval = usb_autopm_get_interface(intf); + if (retval < 0) + return retval; + + retval = usb_hub_set_port_power(hdev, hub, port1, true); + msleep(hub_power_on_good_delay(hub)); + if (udev && !retval) { + /* + * Our preference is to simply wait for the port to reconnect, + * as that is the lowest latency method to restart the port. + * However, there are cases where toggling port power results in + * the host port and the device port getting out of sync causing + * a link training live lock. Upon timeout, flag the port as + * needing warm reset recovery (to be performed later by + * usb_port_resume() as requested via usb_wakeup_notification()) + */ + if (hub_port_debounce_be_connected(hub, port1) < 0) { + dev_dbg(&port_dev->dev, "reconnect timeout\n"); + if (hub_is_superspeed(hdev)) + set_bit(port1, hub->warm_reset_bits); + } + + /* Force the child awake to revalidate after the power loss. */ + if (!test_and_set_bit(port1, hub->child_usage_bits)) { + pm_runtime_get_noresume(&port_dev->dev); + pm_request_resume(&udev->dev); + } + } + + 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); + struct usb_port *peer = port_dev->peer; + int port1 = port_dev->portnum; + int retval; + + if (!hub) + return -EINVAL; + if (hub->in_reset) + return -EBUSY; + + if (dev_pm_qos_flags(&port_dev->dev, PM_QOS_FLAG_NO_POWER_OFF) + == PM_QOS_FLAGS_ALL) + return -EAGAIN; + + if (usb_port_block_power_off) + return -EBUSY; + + retval = usb_autopm_get_interface(intf); + if (retval < 0) + return retval; + + retval = usb_hub_set_port_power(hdev, hub, port1, false); + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION); + if (!port_dev->is_superspeed) + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); + usb_autopm_put_interface(intf); + + /* + * Our peer usb3 port may now be able to suspend, so + * asynchronously queue a suspend request to observe that this + * usb2 port is now off. + */ + if (!port_dev->is_superspeed && peer) + pm_runtime_put(&peer->dev); + + return retval; +} +#endif + +static void usb_port_shutdown(struct device *dev) +{ + struct usb_port *port_dev = to_usb_port(dev); + struct usb_device *udev = port_dev->child; + + if (udev && !udev->port_is_suspended) { + usb_disable_usb2_hardware_lpm(udev); + usb_unlocked_disable_lpm(udev); + } +} + +static const struct dev_pm_ops usb_port_pm_ops = { +#ifdef CONFIG_PM + .runtime_suspend = usb_port_runtime_suspend, + .runtime_resume = usb_port_runtime_resume, +#endif +}; + +const struct device_type usb_port_device_type = { + .name = "usb_port", + .release = usb_port_device_release, + .pm = &usb_port_pm_ops, +}; + +static struct device_driver usb_port_driver = { + .name = "usb", + .owner = THIS_MODULE, + .shutdown = usb_port_shutdown, +}; + +static int link_peers(struct usb_port *left, struct usb_port *right) +{ + struct usb_port *ss_port, *hs_port; + int rc; + + if (left->peer == right && right->peer == left) + return 0; + + if (left->peer || right->peer) { + struct usb_port *lpeer = left->peer; + struct usb_port *rpeer = right->peer; + char *method; + + if (left->location && left->location == right->location) + method = "location"; + else + method = "default"; + + pr_debug("usb: failed to peer %s and %s by %s (%s:%s) (%s:%s)\n", + dev_name(&left->dev), dev_name(&right->dev), method, + dev_name(&left->dev), + lpeer ? dev_name(&lpeer->dev) : "none", + dev_name(&right->dev), + rpeer ? dev_name(&rpeer->dev) : "none"); + return -EBUSY; + } + + rc = sysfs_create_link(&left->dev.kobj, &right->dev.kobj, "peer"); + if (rc) + return rc; + rc = sysfs_create_link(&right->dev.kobj, &left->dev.kobj, "peer"); + if (rc) { + sysfs_remove_link(&left->dev.kobj, "peer"); + return rc; + } + + /* + * We need to wake the HiSpeed port to make sure we don't race + * setting ->peer with usb_port_runtime_suspend(). Otherwise we + * may miss a suspend event for the SuperSpeed port. + */ + if (left->is_superspeed) { + ss_port = left; + WARN_ON(right->is_superspeed); + hs_port = right; + } else { + ss_port = right; + WARN_ON(!right->is_superspeed); + hs_port = left; + } + pm_runtime_get_sync(&hs_port->dev); + + left->peer = right; + right->peer = left; + + /* + * The SuperSpeed reference is dropped when the HiSpeed port in + * this relationship suspends, i.e. when it is safe to allow a + * SuperSpeed connection to drop since there is no risk of a + * device degrading to its powered-off HiSpeed connection. + * + * Also, drop the HiSpeed ref taken above. + */ + pm_runtime_get_sync(&ss_port->dev); + pm_runtime_put(&hs_port->dev); + + return 0; +} + +static void link_peers_report(struct usb_port *left, struct usb_port *right) +{ + int rc; + + rc = link_peers(left, right); + if (rc == 0) { + dev_dbg(&left->dev, "peered to %s\n", dev_name(&right->dev)); + } else { + dev_dbg(&left->dev, "failed to peer to %s (%d)\n", + dev_name(&right->dev), rc); + pr_warn_once("usb: port power management may be unreliable\n"); + usb_port_block_power_off = 1; + } +} + +static void unlink_peers(struct usb_port *left, struct usb_port *right) +{ + struct usb_port *ss_port, *hs_port; + + WARN(right->peer != left || left->peer != right, + "%s and %s are not peers?\n", + dev_name(&left->dev), dev_name(&right->dev)); + + /* + * We wake the HiSpeed port to make sure we don't race its + * usb_port_runtime_resume() event which takes a SuperSpeed ref + * when ->peer is !NULL. + */ + if (left->is_superspeed) { + ss_port = left; + hs_port = right; + } else { + ss_port = right; + hs_port = left; + } + + pm_runtime_get_sync(&hs_port->dev); + + sysfs_remove_link(&left->dev.kobj, "peer"); + right->peer = NULL; + sysfs_remove_link(&right->dev.kobj, "peer"); + left->peer = NULL; + + /* Drop the SuperSpeed ref held on behalf of the active HiSpeed port */ + pm_runtime_put(&ss_port->dev); + + /* Drop the ref taken above */ + pm_runtime_put(&hs_port->dev); +} + +/* + * For each usb hub device in the system check to see if it is in the + * peer domain of the given port_dev, and if it is check to see if it + * has a port that matches the given port by location + */ +static int match_location(struct usb_device *peer_hdev, void *p) +{ + int port1; + struct usb_hcd *hcd, *peer_hcd; + struct usb_port *port_dev = p, *peer; + struct usb_hub *peer_hub = usb_hub_to_struct_hub(peer_hdev); + struct usb_device *hdev = to_usb_device(port_dev->dev.parent->parent); + + if (!peer_hub || port_dev->connect_type == USB_PORT_NOT_USED) + return 0; + + hcd = bus_to_hcd(hdev->bus); + peer_hcd = bus_to_hcd(peer_hdev->bus); + /* peer_hcd is provisional until we verify it against the known peer */ + if (peer_hcd != hcd->shared_hcd) + return 0; + + for (port1 = 1; port1 <= peer_hdev->maxchild; port1++) { + peer = peer_hub->ports[port1 - 1]; + if (peer && peer->connect_type != USB_PORT_NOT_USED && + peer->location == port_dev->location) { + link_peers_report(port_dev, peer); + return 1; /* done */ + } + } + + return 0; +} + +/* + * Find the peer port either via explicit platform firmware "location" + * data, the peer hcd for root hubs, or the upstream peer relationship + * for all other hubs. + */ +static void find_and_link_peer(struct usb_hub *hub, int port1) +{ + struct usb_port *port_dev = hub->ports[port1 - 1], *peer; + struct usb_device *hdev = hub->hdev; + struct usb_device *peer_hdev; + struct usb_hub *peer_hub; + + /* + * If location data is available then we can only peer this port + * by a location match, not the default peer (lest we create a + * situation where we need to go back and undo a default peering + * when the port is later peered by location data) + */ + if (port_dev->location) { + /* we link the peer in match_location() if found */ + usb_for_each_dev(port_dev, match_location); + return; + } else if (!hdev->parent) { + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); + struct usb_hcd *peer_hcd = hcd->shared_hcd; + + if (!peer_hcd) + return; + + peer_hdev = peer_hcd->self.root_hub; + } else { + struct usb_port *upstream; + struct usb_device *parent = hdev->parent; + struct usb_hub *parent_hub = usb_hub_to_struct_hub(parent); + + if (!parent_hub) + return; + + upstream = parent_hub->ports[hdev->portnum - 1]; + if (!upstream || !upstream->peer) + return; + + peer_hdev = upstream->peer->child; + } + + peer_hub = usb_hub_to_struct_hub(peer_hdev); + if (!peer_hub || port1 > peer_hdev->maxchild) + return; + + /* + * we found a valid default peer, last check is to make sure it + * does not have location data + */ + peer = peer_hub->ports[port1 - 1]; + if (peer && peer->location == 0) + link_peers_report(port_dev, peer); +} + +static int connector_bind(struct device *dev, struct device *connector, void *data) +{ + struct usb_port *port_dev = to_usb_port(dev); + int ret; + + ret = sysfs_create_link(&dev->kobj, &connector->kobj, "connector"); + if (ret) + return ret; + + ret = sysfs_create_link(&connector->kobj, &dev->kobj, dev_name(dev)); + if (ret) { + sysfs_remove_link(&dev->kobj, "connector"); + return ret; + } + + port_dev->connector = data; + + /* + * If there is already USB device connected to the port, letting the + * Type-C connector know about it immediately. + */ + if (port_dev->child) + typec_attach(port_dev->connector, &port_dev->child->dev); + + return 0; +} + +static void connector_unbind(struct device *dev, struct device *connector, void *data) +{ + struct usb_port *port_dev = to_usb_port(dev); + + sysfs_remove_link(&connector->kobj, dev_name(dev)); + sysfs_remove_link(&dev->kobj, "connector"); + if (port_dev->child) + typec_deattach(port_dev->connector, &port_dev->child->dev); + port_dev->connector = NULL; +} + +static const struct component_ops connector_ops = { + .bind = connector_bind, + .unbind = connector_unbind, +}; + +int usb_hub_create_port_device(struct usb_hub *hub, int port1) +{ + struct usb_port *port_dev; + struct usb_device *hdev = hub->hdev; + int retval; + + port_dev = kzalloc_obj(*port_dev); + if (!port_dev) + return -ENOMEM; + + port_dev->req = kzalloc_obj(*(port_dev->req)); + if (!port_dev->req) { + kfree(port_dev); + return -ENOMEM; + } + + port_dev->connect_type = usb_of_get_connect_type(hdev, port1); + hub->ports[port1 - 1] = port_dev; + port_dev->portnum = port1; + set_bit(port1, hub->power_bits); + port_dev->dev.parent = hub->intfdev; + if (hub_is_superspeed(hdev)) { + port_dev->is_superspeed = 1; + port_dev->usb3_lpm_u1_permit = 1; + port_dev->usb3_lpm_u2_permit = 1; + port_dev->dev.groups = port_dev_usb3_group; + } else + port_dev->dev.groups = port_dev_group; + port_dev->dev.type = &usb_port_device_type; + port_dev->dev.driver = &usb_port_driver; + dev_set_name(&port_dev->dev, "%s-port%d", dev_name(&hub->hdev->dev), + port1); + mutex_init(&port_dev->status_lock); + retval = device_register(&port_dev->dev); + if (retval) { + put_device(&port_dev->dev); + return retval; + } + + port_dev->state_kn = sysfs_get_dirent(port_dev->dev.kobj.sd, "state"); + if (!port_dev->state_kn) { + dev_err(&port_dev->dev, "failed to sysfs_get_dirent 'state'\n"); + retval = -ENODEV; + goto err_unregister; + } + + /* Set default policy of port-poweroff disabled. */ + retval = dev_pm_qos_add_request(&port_dev->dev, port_dev->req, + DEV_PM_QOS_FLAGS, PM_QOS_FLAG_NO_POWER_OFF); + if (retval < 0) { + goto err_put_kn; + } + + retval = component_add(&port_dev->dev, &connector_ops); + if (retval) { + dev_warn(&port_dev->dev, "failed to add component\n"); + goto err_put_kn; + } + + find_and_link_peer(hub, port1); + + /* + * Enable runtime pm and hold a refernce that hub_configure() + * will drop once the PM_QOS_NO_POWER_OFF flag state has been set + * and the hub has been fully registered (hdev->maxchild set). + */ + pm_runtime_set_active(&port_dev->dev); + pm_runtime_get_noresume(&port_dev->dev); + pm_runtime_enable(&port_dev->dev); + device_enable_async_suspend(&port_dev->dev); + + /* + * Keep hidden the ability to enable port-poweroff if neither the + * USB hub nor platform firmware can manage downstream port power. + */ + if (!usb_port_allow_power_off(hdev, hub, port_dev)) + return 0; + + /* Attempt to let userspace take over the policy. */ + retval = dev_pm_qos_expose_flags(&port_dev->dev, + PM_QOS_FLAG_NO_POWER_OFF); + if (retval < 0) { + dev_warn(&port_dev->dev, "failed to expose pm_qos_no_poweroff\n"); + return 0; + } + + /* Userspace owns the policy, drop the kernel 'no_poweroff' request. */ + retval = dev_pm_qos_remove_request(port_dev->req); + if (retval >= 0) { + kfree(port_dev->req); + port_dev->req = NULL; + } + return 0; + +err_put_kn: + sysfs_put(port_dev->state_kn); +err_unregister: + device_unregister(&port_dev->dev); + + return retval; +} + +void usb_hub_remove_port_device(struct usb_hub *hub, int port1) +{ + struct usb_port *port_dev = hub->ports[port1 - 1]; + struct usb_port *peer; + + peer = port_dev->peer; + if (peer) + unlink_peers(port_dev, peer); + component_del(&port_dev->dev, &connector_ops); + sysfs_put(port_dev->state_kn); + device_unregister(&port_dev->dev); +} diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c new file mode 100644 index 000000000..b5b577f0b --- /dev/null +++ b/drivers/usb/core/quirks.c @@ -0,0 +1,817 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB device quirk handling logic and table + * + * Copyright (c) 2007 Oliver Neukum + * Copyright (c) 2007 Greg Kroah-Hartman + */ + +#include +#include +#include +#include +#include "usb.h" + +struct quirk_entry { + u16 vid; + u16 pid; + u32 flags; +}; + +static DEFINE_MUTEX(quirk_mutex); + +static struct quirk_entry *quirk_list; +static unsigned int quirk_count; + +static char quirks_param[128]; + +static int quirks_param_set(const char *value, const struct kernel_param *kp) +{ + char *val, *p, *field; + u16 vid, pid; + u32 flags; + size_t i; + int err; + + val = kstrdup(value, GFP_KERNEL); + if (!val) + return -ENOMEM; + + err = param_set_copystring(val, kp); + if (err) { + kfree(val); + return err; + } + + mutex_lock(&quirk_mutex); + + if (!*val) { + quirk_count = 0; + kfree(quirk_list); + quirk_list = NULL; + goto unlock; + } + + for (quirk_count = 1, i = 0; val[i]; i++) + if (val[i] == ',') + quirk_count++; + + if (quirk_list) { + kfree(quirk_list); + quirk_list = NULL; + } + + quirk_list = kzalloc_objs(struct quirk_entry, quirk_count); + if (!quirk_list) { + quirk_count = 0; + mutex_unlock(&quirk_mutex); + kfree(val); + return -ENOMEM; + } + + for (i = 0, p = val; p && *p;) { + /* Each entry consists of VID:PID:flags */ + field = strsep(&p, ":"); + if (!field) + break; + + if (kstrtou16(field, 16, &vid)) + break; + + field = strsep(&p, ":"); + if (!field) + break; + + if (kstrtou16(field, 16, &pid)) + break; + + field = strsep(&p, ","); + if (!field || !*field) + break; + + /* Collect the flags */ + for (flags = 0; *field; field++) { + switch (*field) { + case 'a': + flags |= USB_QUIRK_STRING_FETCH_255; + break; + case 'b': + flags |= USB_QUIRK_RESET_RESUME; + break; + case 'c': + flags |= USB_QUIRK_NO_SET_INTF; + break; + case 'd': + flags |= USB_QUIRK_CONFIG_INTF_STRINGS; + break; + case 'e': + flags |= USB_QUIRK_RESET; + break; + case 'f': + flags |= USB_QUIRK_HONOR_BNUMINTERFACES; + break; + case 'g': + flags |= USB_QUIRK_DELAY_INIT; + break; + case 'h': + flags |= USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL; + break; + case 'i': + flags |= USB_QUIRK_DEVICE_QUALIFIER; + break; + case 'j': + flags |= USB_QUIRK_IGNORE_REMOTE_WAKEUP; + break; + case 'k': + flags |= USB_QUIRK_NO_LPM; + break; + case 'l': + flags |= USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL; + break; + case 'm': + flags |= USB_QUIRK_DISCONNECT_SUSPEND; + break; + case 'n': + flags |= USB_QUIRK_DELAY_CTRL_MSG; + break; + case 'o': + flags |= USB_QUIRK_HUB_SLOW_RESET; + break; + case 'p': + flags |= USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT; + break; + case 'q': + flags |= USB_QUIRK_FORCE_ONE_CONFIG; + break; + case 'r': + flags |= USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE; + break; + /* Ignore unrecognized flag characters */ + } + } + + quirk_list[i++] = (struct quirk_entry) + { .vid = vid, .pid = pid, .flags = flags }; + } + + if (i < quirk_count) + quirk_count = i; + +unlock: + mutex_unlock(&quirk_mutex); + kfree(val); + + return 0; +} + +static const struct kernel_param_ops quirks_param_ops = { + .set = quirks_param_set, + .get = param_get_string, +}; + +static struct kparam_string quirks_param_string = { + .maxlen = sizeof(quirks_param), + .string = quirks_param, +}; + +device_param_cb(quirks, &quirks_param_ops, &quirks_param_string, 0644); +MODULE_PARM_DESC(quirks, "Add/modify USB quirks by specifying quirks=vendorID:productID:quirks"); + +/* 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 }, + + /* WORLDE Controller KS49 or Prodipe MIDI 49C USB controller */ + { USB_DEVICE(0x0218, 0x0201), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + + /* WORLDE easy key (easykey.25) MIDI controller */ + { USB_DEVICE(0x0218, 0x0401), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + + /* HP 5300/5370C scanner */ + { USB_DEVICE(0x03f0, 0x0701), .driver_info = + USB_QUIRK_STRING_FETCH_255 }, + + /* HP v222w 16GB Mini USB Drive */ + { USB_DEVICE(0x03f0, 0x3f40), .driver_info = USB_QUIRK_DELAY_INIT }, + + /* Huawei 4G LTE module ME906S */ + { USB_DEVICE(0x03f0, 0xa31d), .driver_info = + USB_QUIRK_DISCONNECT_SUSPEND }, + + /* Creative SB Audigy 2 NX */ + { USB_DEVICE(0x041e, 0x3020), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* USB3503 */ + { USB_DEVICE(0x0424, 0x3503), .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 }, + + /* Microsoft Surface Dock Ethernet (RTL8153 GigE) */ + { USB_DEVICE(0x045e, 0x07c6), .driver_info = USB_QUIRK_NO_LPM }, + + /* Cherry Stream G230 2.0 (G85-231) and 3.0 (G85-232) */ + { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Logitech HD Webcam C270 */ + { USB_DEVICE(0x046d, 0x0825), .driver_info = USB_QUIRK_RESET_RESUME | + USB_QUIRK_NO_LPM}, + + /* Logitech HD Pro Webcams C920, C920-C, C922, C925e and C930e */ + { USB_DEVICE(0x046d, 0x082d), .driver_info = USB_QUIRK_DELAY_INIT }, + { USB_DEVICE(0x046d, 0x0841), .driver_info = USB_QUIRK_DELAY_INIT }, + { USB_DEVICE(0x046d, 0x0843), .driver_info = USB_QUIRK_DELAY_INIT }, + { USB_DEVICE(0x046d, 0x085b), .driver_info = USB_QUIRK_DELAY_INIT }, + { USB_DEVICE(0x046d, 0x085c), .driver_info = USB_QUIRK_DELAY_INIT }, + + /* Logitech ConferenceCam CC3000e */ + { USB_DEVICE(0x046d, 0x0847), .driver_info = USB_QUIRK_DELAY_INIT }, + { USB_DEVICE(0x046d, 0x0848), .driver_info = USB_QUIRK_DELAY_INIT }, + + /* Logitech PTZ Pro Camera */ + { USB_DEVICE(0x046d, 0x0853), .driver_info = USB_QUIRK_DELAY_INIT }, + + /* Logitech Screen Share */ + { USB_DEVICE(0x046d, 0x086c), .driver_info = USB_QUIRK_NO_LPM }, + + /* 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 }, + + /* Plantronic Audio 655 DSP */ + { USB_DEVICE(0x047f, 0xc008), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Plantronic Audio 648 USB */ + { USB_DEVICE(0x047f, 0xc013), .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 T5 EVO Portable SSD */ + { USB_DEVICE(0x04e8, 0x6200), .driver_info = USB_QUIRK_NO_LPM }, + + /* Samsung Android phone modem - ID conflict with SPH-I500 */ + { USB_DEVICE(0x04e8, 0x6601), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + + /* Elan Touchscreen */ + { USB_DEVICE(0x04f3, 0x0089), .driver_info = + USB_QUIRK_DEVICE_QUALIFIER }, + + { USB_DEVICE(0x04f3, 0x009b), .driver_info = + USB_QUIRK_DEVICE_QUALIFIER }, + + { USB_DEVICE(0x04f3, 0x010c), .driver_info = + USB_QUIRK_DEVICE_QUALIFIER }, + + { USB_DEVICE(0x04f3, 0x0125), .driver_info = + USB_QUIRK_DEVICE_QUALIFIER }, + + { USB_DEVICE(0x04f3, 0x016f), .driver_info = + USB_QUIRK_DEVICE_QUALIFIER }, + + { USB_DEVICE(0x04f3, 0x0381), .driver_info = + USB_QUIRK_NO_LPM }, + + { USB_DEVICE(0x04f3, 0x21b8), .driver_info = + USB_QUIRK_DEVICE_QUALIFIER }, + + /* 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 }, + + /* appletouch */ + { USB_DEVICE(0x05ac, 0x021a), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Genesys Logic hub, internally used by KY-688 USB 3.1 Type-C Hub */ + { USB_DEVICE(0x05e3, 0x0612), .driver_info = USB_QUIRK_NO_LPM }, + + /* ELSA MicroLink 56K */ + { USB_DEVICE(0x05cc, 0x2267), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Genesys Logic hub, internally used by Moshi USB to Ethernet Adapter */ + { USB_DEVICE(0x05e3, 0x0616), .driver_info = USB_QUIRK_NO_LPM }, + + /* Avision AV600U */ + { USB_DEVICE(0x0638, 0x0a13), .driver_info = + USB_QUIRK_STRING_FETCH_255 }, + + /* Prolific Single-LUN Mass Storage Card Reader */ + { USB_DEVICE(0x067b, 0x2731), .driver_info = USB_QUIRK_DELAY_INIT | + USB_QUIRK_NO_LPM }, + + /* Saitek Cyborg Gold Joystick */ + { USB_DEVICE(0x06a3, 0x0006), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + + /* Agfa SNAPSCAN 1212U */ + { USB_DEVICE(0x06bd, 0x0001), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* 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 }, + + /* Guillemot Hercules DJ Console audio card (BZ 208357) */ + { USB_DEVICE(0x06f8, 0xb000), .driver_info = + USB_QUIRK_ENDPOINT_IGNORE }, + + /* Midiman M-Audio Keystation 88es */ + { USB_DEVICE(0x0763, 0x0192), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* SanDisk Ultra Fit and Ultra Flair */ + { USB_DEVICE(0x0781, 0x5583), .driver_info = USB_QUIRK_NO_LPM }, + { USB_DEVICE(0x0781, 0x5591), .driver_info = USB_QUIRK_NO_LPM }, + + /* SanDisk Corp. SanDisk 3.2Gen1 */ + { USB_DEVICE(0x0781, 0x5596), .driver_info = USB_QUIRK_DELAY_INIT }, + { USB_DEVICE(0x0781, 0x55a3), .driver_info = USB_QUIRK_DELAY_INIT }, + + /* SanDisk Extreme 55AE */ + { USB_DEVICE(0x0781, 0x55ae), .driver_info = USB_QUIRK_NO_LPM }, + + /* Avermedia Live Gamer Ultra 2.1 (GC553G2) - BOS descriptor fetch hangs at SuperSpeed Plus */ + { USB_DEVICE(0x07ca, 0x2553), .driver_info = USB_QUIRK_NO_BOS }, + + /* Realforce 87U Keyboard */ + { USB_DEVICE(0x0853, 0x011b), .driver_info = USB_QUIRK_NO_LPM }, + + /* M-Systems Flash Disk Pioneers */ + { USB_DEVICE(0x08ec, 0x1000), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Baum Vario Ultra */ + { USB_DEVICE(0x0904, 0x6101), .driver_info = + USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL }, + { USB_DEVICE(0x0904, 0x6102), .driver_info = + USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL }, + { USB_DEVICE(0x0904, 0x6103), .driver_info = + USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL }, + + /* Silicon Motion Flash Drive */ + { USB_DEVICE(0x090c, 0x1000), .driver_info = USB_QUIRK_DELAY_INIT }, + { USB_DEVICE(0x090c, 0x2000), .driver_info = USB_QUIRK_DELAY_INIT }, + + /* Sound Devices USBPre2 */ + { USB_DEVICE(0x0926, 0x0202), .driver_info = + USB_QUIRK_ENDPOINT_IGNORE }, + + /* Sound Devices MixPre-D */ + { USB_DEVICE(0x0926, 0x0208), .driver_info = + USB_QUIRK_ENDPOINT_IGNORE }, + + /* Keytouch QWERTY Panel keyboard */ + { USB_DEVICE(0x0926, 0x3333), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + + /* Kingston DataTraveler 3.0 */ + { USB_DEVICE(0x0951, 0x1666), .driver_info = USB_QUIRK_NO_LPM }, + + /* TOSHIBA TransMemory-Mx */ + { USB_DEVICE(0x0930, 0x1408), .driver_info = USB_QUIRK_NO_LPM }, + + /* NVIDIA Jetson devices in Force Recovery mode */ + { USB_DEVICE(0x0955, 0x7018), .driver_info = USB_QUIRK_RESET_RESUME }, + { USB_DEVICE(0x0955, 0x7019), .driver_info = USB_QUIRK_RESET_RESUME }, + { USB_DEVICE(0x0955, 0x7418), .driver_info = USB_QUIRK_RESET_RESUME }, + { USB_DEVICE(0x0955, 0x7721), .driver_info = USB_QUIRK_RESET_RESUME }, + { USB_DEVICE(0x0955, 0x7c18), .driver_info = USB_QUIRK_RESET_RESUME }, + { USB_DEVICE(0x0955, 0x7e19), .driver_info = USB_QUIRK_RESET_RESUME }, + { USB_DEVICE(0x0955, 0x7f21), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* X-Rite/Gretag-Macbeth Eye-One Pro display colorimeter */ + { USB_DEVICE(0x0971, 0x2000), .driver_info = USB_QUIRK_NO_SET_INTF }, + + /* ELMO L-12F document camera */ + { USB_DEVICE(0x09a1, 0x0028), .driver_info = USB_QUIRK_DELAY_CTRL_MSG }, + + /* 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 }, + + /* ASUS Base Station(T100) */ + { USB_DEVICE(0x0b05, 0x17e0), .driver_info = + USB_QUIRK_IGNORE_REMOTE_WAKEUP }, + + /* ASUS TUF 4K PRO - BOS descriptor fetch hangs at SuperSpeed Plus */ + { USB_DEVICE(0x0b05, 0x1ab9), .driver_info = USB_QUIRK_NO_BOS }, + + /* Realtek Semiconductor Corp. Mass Storage Device (Multicard Reader)*/ + { USB_DEVICE(0x0bda, 0x0151), .driver_info = USB_QUIRK_CONFIG_INTF_STRINGS }, + + /* Realtek hub in Dell WD19 (Type-C) */ + { USB_DEVICE(0x0bda, 0x0487), .driver_info = USB_QUIRK_NO_LPM }, + + /* Generic RTL8153 based ethernet adapters */ + { USB_DEVICE(0x0bda, 0x8153), .driver_info = USB_QUIRK_NO_LPM }, + + /* SONiX USB DEVICE Touchpad */ + { USB_DEVICE(0x0c45, 0x7056), .driver_info = + USB_QUIRK_IGNORE_REMOTE_WAKEUP }, + + /* Elgato 4K X - BOS descriptor fetch hangs at SuperSpeed Plus */ + { USB_DEVICE(0x0fd9, 0x009b), .driver_info = USB_QUIRK_NO_BOS }, + + /* Sony Xperia XZ1 Compact (lilac) smartphone in fastboot mode */ + { USB_DEVICE(0x0fce, 0x0dde), .driver_info = USB_QUIRK_NO_LPM }, + + /* Action Semiconductor flash disk */ + { USB_DEVICE(0x10d6, 0x2200), .driver_info = + USB_QUIRK_STRING_FETCH_255 }, + + /* novation SoundControl XL */ + { USB_DEVICE(0x1235, 0x0061), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Focusrite Scarlett Solo USB */ + { USB_DEVICE(0x1235, 0x8211), .driver_info = + USB_QUIRK_DISCONNECT_SUSPEND }, + + /* Huawei 4G LTE module */ + { USB_DEVICE(0x12d1, 0x15bb), .driver_info = + USB_QUIRK_DISCONNECT_SUSPEND }, + { USB_DEVICE(0x12d1, 0x15c1), .driver_info = + USB_QUIRK_DISCONNECT_SUSPEND }, + { USB_DEVICE(0x12d1, 0x15c3), .driver_info = + USB_QUIRK_DISCONNECT_SUSPEND }, + + /* SKYMEDI USB_DRIVE */ + { USB_DEVICE(0x1516, 0x8628), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Razer - Razer Blade Keyboard */ + { USB_DEVICE(0x1532, 0x0116), .driver_info = + USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL }, + /* Razer - Razer Kiyo Pro Webcam */ + { USB_DEVICE(0x1532, 0x0e05), .driver_info = USB_QUIRK_NO_LPM }, + + /* Lenovo ThinkPad OneLink+ Dock twin hub controllers (VIA Labs VL812) */ + { USB_DEVICE(0x17ef, 0x1018), .driver_info = USB_QUIRK_RESET_RESUME }, + { USB_DEVICE(0x17ef, 0x1019), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Lenovo USB-C to Ethernet Adapter RTL8153-04 */ + { USB_DEVICE(0x17ef, 0x720c), .driver_info = USB_QUIRK_NO_LPM }, + + /* Lenovo Powered USB-C Travel Hub (4X90S92381, RTL8153 GigE) */ + { USB_DEVICE(0x17ef, 0x721e), .driver_info = USB_QUIRK_NO_LPM }, + + /* Lenovo ThinkCenter A630Z TI024Gen3 usb-audio */ + { USB_DEVICE(0x17ef, 0xa012), .driver_info = + USB_QUIRK_DISCONNECT_SUSPEND }, + + /* Lenovo ThinkPad USB-C Dock Gen2 Ethernet (RTL8153 GigE) */ + { USB_DEVICE(0x17ef, 0xa387), .driver_info = USB_QUIRK_NO_LPM }, + + /* Lenovo ThinkPad USB-C Dock Gen2 USB 3.1 and USB 2.0 hub controllers */ + { USB_DEVICE(0x17ef, 0xa391), .driver_info = USB_QUIRK_NO_LPM }, + { USB_DEVICE(0x17ef, 0xa392), .driver_info = USB_QUIRK_NO_LPM }, + + /* BUILDWIN Photo Frame */ + { USB_DEVICE(0x1908, 0x1315), .driver_info = + USB_QUIRK_HONOR_BNUMINTERFACES }, + + /* Protocol and OTG Electrical Test Device */ + { USB_DEVICE(0x1a0a, 0x0200), .driver_info = + USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL }, + + /* Terminus Technology Inc. Hub */ + { USB_DEVICE(0x1a40, 0x0101), .driver_info = USB_QUIRK_HUB_SLOW_RESET }, + + /* Corsair K70 RGB */ + { USB_DEVICE(0x1b1c, 0x1b13), .driver_info = USB_QUIRK_DELAY_INIT | + USB_QUIRK_DELAY_CTRL_MSG }, + + /* Corsair Strafe */ + { USB_DEVICE(0x1b1c, 0x1b15), .driver_info = USB_QUIRK_DELAY_INIT | + USB_QUIRK_DELAY_CTRL_MSG }, + + /* Corsair Strafe RGB */ + { USB_DEVICE(0x1b1c, 0x1b20), .driver_info = USB_QUIRK_DELAY_INIT | + USB_QUIRK_DELAY_CTRL_MSG }, + + /* Corsair K70 LUX RGB */ + { USB_DEVICE(0x1b1c, 0x1b33), .driver_info = USB_QUIRK_DELAY_INIT }, + + /* Corsair K70 LUX */ + { USB_DEVICE(0x1b1c, 0x1b36), .driver_info = USB_QUIRK_DELAY_INIT }, + + /* Corsair K70 RGB RAPDIFIRE */ + { USB_DEVICE(0x1b1c, 0x1b38), .driver_info = USB_QUIRK_DELAY_INIT | + USB_QUIRK_DELAY_CTRL_MSG }, + + /* START BP-850k Printer */ + { USB_DEVICE(0x1bc3, 0x0003), .driver_info = USB_QUIRK_NO_SET_INTF }, + + /* MIDI keyboard WORLDE MINI */ + { USB_DEVICE(0x1c75, 0x0204), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + + /* Acer C120 LED Projector */ + { USB_DEVICE(0x1de1, 0xc102), .driver_info = USB_QUIRK_NO_LPM }, + + /* Blackmagic Design Intensity Shuttle */ + { USB_DEVICE(0x1edb, 0xbd3b), .driver_info = USB_QUIRK_NO_LPM }, + + /* Blackmagic Design UltraStudio SDI */ + { USB_DEVICE(0x1edb, 0xbd4f), .driver_info = USB_QUIRK_NO_LPM }, + + /* Teclast disk */ + { USB_DEVICE(0x1f75, 0x0917), .driver_info = USB_QUIRK_NO_LPM }, + + /* Hauppauge HVR-950q */ + { USB_DEVICE(0x2040, 0x7200), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + + /* VLI disk */ + { USB_DEVICE(0x2109, 0x0711), .driver_info = USB_QUIRK_NO_LPM }, + + /* VIA Labs, Inc. USB2.0 Hub */ + { USB_DEVICE(0x2109, 0x2817), .driver_info = USB_QUIRK_NO_LPM }, + + /* Raydium Touchscreen */ + { USB_DEVICE(0x2386, 0x3114), .driver_info = USB_QUIRK_NO_LPM }, + + { USB_DEVICE(0x2386, 0x3119), .driver_info = USB_QUIRK_NO_LPM }, + + { USB_DEVICE(0x2386, 0x350e), .driver_info = USB_QUIRK_NO_LPM }, + + /* ShanWan Wireless Gamepad */ + { USB_DEVICE(0x2563, 0x0575), .driver_info = USB_QUIRK_WINDOWS_CONFIG_REQ_SIZE }, + + /* UGREEN 35871 - BOS descriptor fetch hangs at SuperSpeed Plus */ + { USB_DEVICE(0x2b89, 0x5871), .driver_info = USB_QUIRK_NO_BOS }, + + /* APTIV AUTOMOTIVE HUB */ + { USB_DEVICE(0x2c48, 0x0132), .driver_info = + USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT }, + + /* DJI CineSSD */ + { USB_DEVICE(0x2ca3, 0x0031), .driver_info = USB_QUIRK_NO_LPM }, + + /* Alcor Link AK9563 SC Reader used in 2022 Lenovo ThinkPads */ + { USB_DEVICE(0x2ce3, 0x9563), .driver_info = USB_QUIRK_NO_LPM }, + + /* ezcap401 - BOS descriptor fetch hangs at SuperSpeed Plus */ + { USB_DEVICE(0x32ed, 0x0401), .driver_info = USB_QUIRK_NO_BOS }, + + /* DELL USB GEN2 */ + { USB_DEVICE(0x413c, 0xb062), .driver_info = USB_QUIRK_NO_LPM | USB_QUIRK_RESET_RESUME }, + + /* VCOM device */ + { USB_DEVICE(0x4296, 0x7570), .driver_info = USB_QUIRK_CONFIG_INTF_STRINGS }, + + /* Noji-MCS SmartCard Reader */ + { USB_DEVICE(0x5131, 0x2007), .driver_info = USB_QUIRK_FORCE_ONE_CONFIG }, + + /* 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 }, + + { } /* terminating entry must be last */ +}; + +static const struct usb_device_id usb_amd_resume_quirk_list[] = { + /* Lenovo Mouse with Pixart controller */ + { USB_DEVICE(0x17ef, 0x602e), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Pixart Mouse */ + { USB_DEVICE(0x093a, 0x2500), .driver_info = USB_QUIRK_RESET_RESUME }, + { USB_DEVICE(0x093a, 0x2510), .driver_info = USB_QUIRK_RESET_RESUME }, + { USB_DEVICE(0x093a, 0x2521), .driver_info = USB_QUIRK_RESET_RESUME }, + { USB_DEVICE(0x03f0, 0x2b4a), .driver_info = USB_QUIRK_RESET_RESUME }, + + /* Logitech Optical Mouse M90/M100 */ + { USB_DEVICE(0x046d, 0xc05a), .driver_info = USB_QUIRK_RESET_RESUME }, + + { } /* terminating entry must be last */ +}; + +/* + * Entries for endpoints that should be ignored when parsing configuration + * descriptors. + * + * Matched for devices with USB_QUIRK_ENDPOINT_IGNORE. + */ +static const struct usb_device_id usb_endpoint_ignore[] = { + { USB_DEVICE_INTERFACE_NUMBER(0x06f8, 0xb000, 5), .driver_info = 0x01 }, + { USB_DEVICE_INTERFACE_NUMBER(0x06f8, 0xb000, 5), .driver_info = 0x81 }, + { USB_DEVICE_INTERFACE_NUMBER(0x0926, 0x0202, 1), .driver_info = 0x85 }, + { USB_DEVICE_INTERFACE_NUMBER(0x0926, 0x0208, 1), .driver_info = 0x85 }, + { } +}; + +bool usb_endpoint_is_ignored(struct usb_device *udev, + struct usb_host_interface *intf, + struct usb_endpoint_descriptor *epd) +{ + const struct usb_device_id *id; + unsigned int address; + + for (id = usb_endpoint_ignore; id->match_flags; ++id) { + if (!usb_match_device(udev, id)) + continue; + + if (!usb_match_one_id_intf(udev, intf, id)) + continue; + + address = id->driver_info; + if (address == epd->bEndpointAddress) + return true; + } + + return false; +} + +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 int usb_amd_resume_quirk(struct usb_device *udev) +{ + struct usb_hcd *hcd; + + hcd = bus_to_hcd(udev->bus); + /* The device should be attached directly to root hub */ + if (udev->level == 1 && hcd->amd_resume_bug == 1) + return 1; + + return 0; +} + +static u32 usb_detect_static_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; +} + +static u32 usb_detect_dynamic_quirks(struct usb_device *udev) +{ + u16 vid = le16_to_cpu(udev->descriptor.idVendor); + u16 pid = le16_to_cpu(udev->descriptor.idProduct); + int i, flags = 0; + + mutex_lock(&quirk_mutex); + + for (i = 0; i < quirk_count; i++) { + if (vid == quirk_list[i].vid && pid == quirk_list[i].pid) { + flags = quirk_list[i].flags; + break; + } + } + + mutex_unlock(&quirk_mutex); + + return flags; +} + +/* + * 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_static_quirks(udev, usb_quirk_list); + + /* + * Pixart-based mice would trigger remote wakeup issue on AMD + * Yangtze chipset, so set them as RESET_RESUME flag. + */ + if (usb_amd_resume_quirk(udev)) + udev->quirks |= usb_detect_static_quirks(udev, + usb_amd_resume_quirk_list); + + udev->quirks ^= usb_detect_dynamic_quirks(udev); + + if (udev->quirks) + dev_dbg(&udev->dev, "USB quirks for this device: 0x%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_static_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; +} + +void usb_release_quirk_list(void) +{ + mutex_lock(&quirk_mutex); + kfree(quirk_list); + quirk_list = NULL; + mutex_unlock(&quirk_mutex); +} diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c new file mode 100644 index 000000000..d22dc7845 --- /dev/null +++ b/drivers/usb/core/sysfs.c @@ -0,0 +1,1373 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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. + * + * Released under the GPLv2 only. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include "usb.h" + +/* Active configuration fields */ +#define usb_actconfig_show(field, format_string) \ +static ssize_t field##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_device *udev; \ + struct usb_host_config *actconfig; \ + ssize_t rc; \ + \ + udev = to_usb_device(dev); \ + rc = usb_lock_device_interruptible(udev); \ + if (rc < 0) \ + return -EINTR; \ + actconfig = udev->actconfig; \ + if (actconfig) \ + rc = sysfs_emit(buf, format_string, \ + actconfig->desc.field); \ + usb_unlock_device(udev); \ + return rc; \ +} \ + +#define usb_actconfig_attr(field, format_string) \ + usb_actconfig_show(field, format_string) \ + static DEVICE_ATTR_RO(field) + +usb_actconfig_attr(bNumInterfaces, "%2d\n"); +usb_actconfig_attr(bmAttributes, "%2x\n"); + +static ssize_t bMaxPower_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + struct usb_host_config *actconfig; + ssize_t rc; + + udev = to_usb_device(dev); + rc = usb_lock_device_interruptible(udev); + if (rc < 0) + return -EINTR; + actconfig = udev->actconfig; + if (actconfig) + rc = sysfs_emit(buf, "%dmA\n", usb_get_max_power(udev, actconfig)); + usb_unlock_device(udev); + return rc; +} +static DEVICE_ATTR_RO(bMaxPower); + +static ssize_t configuration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + struct usb_host_config *actconfig; + ssize_t rc; + + udev = to_usb_device(dev); + rc = usb_lock_device_interruptible(udev); + if (rc < 0) + return -EINTR; + actconfig = udev->actconfig; + if (actconfig && actconfig->string) + rc = sysfs_emit(buf, "%s\n", actconfig->string); + usb_unlock_device(udev); + return rc; +} +static DEVICE_ATTR_RO(configuration); + +/* configuration value is always present, and r/w */ +usb_actconfig_show(bConfigurationValue, "%u\n"); + +static ssize_t bConfigurationValue_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + int config, value, rc; + + if (sscanf(buf, "%d", &config) != 1 || config < -1 || config > 255) + return -EINVAL; + rc = usb_lock_device_interruptible(udev); + if (rc < 0) + return -EINTR; + 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, + bConfigurationValue_show, bConfigurationValue_store); + +#ifdef CONFIG_OF +static ssize_t devspec_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct device_node *of_node = dev->of_node; + + return sysfs_emit(buf, "%pOF\n", of_node); +} +static DEVICE_ATTR_RO(devspec); +#endif + +/* String fields */ +#define usb_string_attr(name) \ +static ssize_t name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_device *udev; \ + int retval; \ + \ + udev = to_usb_device(dev); \ + retval = usb_lock_device_interruptible(udev); \ + if (retval < 0) \ + return -EINTR; \ + retval = sysfs_emit(buf, "%s\n", udev->name); \ + usb_unlock_device(udev); \ + return retval; \ +} \ +static DEVICE_ATTR_RO(name) + +usb_string_attr(product); +usb_string_attr(manufacturer); +usb_string_attr(serial); + +static ssize_t speed_show(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_SUPER: + speed = "5000"; + break; + case USB_SPEED_SUPER_PLUS: + if (udev->ssp_rate == USB_SSP_GEN_2x2) + speed = "20000"; + else + speed = "10000"; + break; + default: + speed = "unknown"; + } + return sysfs_emit(buf, "%s\n", speed); +} +static DEVICE_ATTR_RO(speed); + +static ssize_t rx_lanes_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sysfs_emit(buf, "%d\n", udev->rx_lanes); +} +static DEVICE_ATTR_RO(rx_lanes); + +static ssize_t tx_lanes_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sysfs_emit(buf, "%d\n", udev->tx_lanes); +} +static DEVICE_ATTR_RO(tx_lanes); + +static ssize_t busnum_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sysfs_emit(buf, "%d\n", udev->bus->busnum); +} +static DEVICE_ATTR_RO(busnum); + +static ssize_t devnum_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sysfs_emit(buf, "%d\n", udev->devnum); +} +static DEVICE_ATTR_RO(devnum); + +static ssize_t devpath_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sysfs_emit(buf, "%s\n", udev->devpath); +} +static DEVICE_ATTR_RO(devpath); + +static ssize_t version_show(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 sysfs_emit(buf, "%2x.%02x\n", bcdUSB >> 8, bcdUSB & 0xff); +} +static DEVICE_ATTR_RO(version); + +static ssize_t maxchild_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sysfs_emit(buf, "%d\n", udev->maxchild); +} +static DEVICE_ATTR_RO(maxchild); + +static ssize_t quirks_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sysfs_emit(buf, "0x%x\n", udev->quirks); +} +static DEVICE_ATTR_RO(quirks); + +static ssize_t avoid_reset_quirk_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sysfs_emit(buf, "%d\n", !!(udev->quirks & USB_QUIRK_RESET)); +} + +static ssize_t avoid_reset_quirk_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + bool val; + int rc; + + if (kstrtobool(buf, &val) != 0) + return -EINVAL; + rc = usb_lock_device_interruptible(udev); + if (rc < 0) + return -EINTR; + if (val) + udev->quirks |= USB_QUIRK_RESET; + else + udev->quirks &= ~USB_QUIRK_RESET; + usb_unlock_device(udev); + return count; +} +static DEVICE_ATTR_RW(avoid_reset_quirk); + +static ssize_t urbnum_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sysfs_emit(buf, "%d\n", atomic_read(&udev->urbnum)); +} +static DEVICE_ATTR_RO(urbnum); + +static ssize_t ltm_capable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (usb_device_supports_ltm(to_usb_device(dev))) + return sysfs_emit(buf, "%s\n", "yes"); + return sysfs_emit(buf, "%s\n", "no"); +} +static DEVICE_ATTR_RO(ltm_capable); + +#ifdef CONFIG_PM + +static ssize_t persist_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + + return sysfs_emit(buf, "%d\n", udev->persist_enabled); +} + +static ssize_t persist_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + bool value; + int rc; + + /* Hubs are always enabled for USB_PERSIST */ + if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) + return -EPERM; + + if (kstrtobool(buf, &value) != 0) + return -EINVAL; + + rc = usb_lock_device_interruptible(udev); + if (rc < 0) + return -EINTR; + udev->persist_enabled = !!value; + usb_unlock_device(udev); + return count; +} +static DEVICE_ATTR_RW(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); +} + +static ssize_t connected_duration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + + return sysfs_emit(buf, "%u\n", + jiffies_to_msecs(jiffies - udev->connect_time)); +} +static DEVICE_ATTR_RO(connected_duration); + +/* + * 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 active_duration_show(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 sysfs_emit(buf, "%u\n", duration); +} +static DEVICE_ATTR_RO(active_duration); + +static ssize_t autosuspend_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%d\n", dev->power.autosuspend_delay / 1000); +} + +static ssize_t autosuspend_store(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_RW(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 level_show(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 sysfs_emit(buf, "%s\n", p); +} + +static ssize_t level_store(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; + int rv; + + warn_level(); + cp = memchr(buf, '\n', count); + if (cp) + len = cp - buf; + + rv = usb_lock_device_interruptible(udev); + if (rv < 0) + return -EINTR; + + 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_RW(level); + +static ssize_t usb2_hardware_lpm_show(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_allowed == 1) + p = "enabled"; + else + p = "disabled"; + + return sysfs_emit(buf, "%s\n", p); +} + +static ssize_t usb2_hardware_lpm_store(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; + + ret = usb_lock_device_interruptible(udev); + if (ret < 0) + return -EINTR; + + ret = kstrtobool(buf, &value); + + if (!ret) { + udev->usb2_hw_lpm_allowed = value; + if (value) + ret = usb_enable_usb2_hardware_lpm(udev); + else + ret = usb_disable_usb2_hardware_lpm(udev); + } + + usb_unlock_device(udev); + + if (!ret) + return count; + + return ret; +} +static DEVICE_ATTR_RW(usb2_hardware_lpm); + +static ssize_t usb2_lpm_l1_timeout_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + return sysfs_emit(buf, "%d\n", udev->l1_params.timeout); +} + +static ssize_t usb2_lpm_l1_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + u16 timeout; + + if (kstrtou16(buf, 0, &timeout)) + return -EINVAL; + + udev->l1_params.timeout = timeout; + + return count; +} +static DEVICE_ATTR_RW(usb2_lpm_l1_timeout); + +static ssize_t usb2_lpm_besl_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + return sysfs_emit(buf, "%d\n", udev->l1_params.besl); +} + +static ssize_t usb2_lpm_besl_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + u8 besl; + + if (kstrtou8(buf, 0, &besl) || besl > 15) + return -EINVAL; + + udev->l1_params.besl = besl; + + return count; +} +static DEVICE_ATTR_RW(usb2_lpm_besl); + +static ssize_t usb3_hardware_lpm_u1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + const char *p; + int rc; + + rc = usb_lock_device_interruptible(udev); + if (rc < 0) + return -EINTR; + + if (udev->usb3_lpm_u1_enabled) + p = "enabled"; + else + p = "disabled"; + + usb_unlock_device(udev); + + return sysfs_emit(buf, "%s\n", p); +} +static DEVICE_ATTR_RO(usb3_hardware_lpm_u1); + +static ssize_t usb3_hardware_lpm_u2_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + const char *p; + int rc; + + rc = usb_lock_device_interruptible(udev); + if (rc < 0) + return -EINTR; + + if (udev->usb3_lpm_u2_enabled) + p = "enabled"; + else + p = "disabled"; + + usb_unlock_device(udev); + + return sysfs_emit(buf, "%s\n", p); +} +static DEVICE_ATTR_RO(usb3_hardware_lpm_u2); + +static struct attribute *usb2_hardware_lpm_attr[] = { + &dev_attr_usb2_hardware_lpm.attr, + &dev_attr_usb2_lpm_l1_timeout.attr, + &dev_attr_usb2_lpm_besl.attr, + NULL, +}; +static const struct attribute_group usb2_hardware_lpm_attr_group = { + .name = power_group_name, + .attrs = usb2_hardware_lpm_attr, +}; + +static struct attribute *usb3_hardware_lpm_attr[] = { + &dev_attr_usb3_hardware_lpm_u1.attr, + &dev_attr_usb3_hardware_lpm_u2.attr, + NULL, +}; +static const struct attribute_group usb3_hardware_lpm_attr_group = { + .name = power_group_name, + .attrs = usb3_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 const 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); + if ((udev->speed == USB_SPEED_SUPER || + udev->speed == USB_SPEED_SUPER_PLUS) && + udev->lpm_capable == 1) + rc = sysfs_merge_group(&dev->kobj, + &usb3_hardware_lpm_attr_group); + } + + return rc; +} + +static void remove_power_attributes(struct device *dev) +{ + sysfs_unmerge_group(&dev->kobj, &usb3_hardware_lpm_attr_group); + sysfs_unmerge_group(&dev->kobj, &usb2_hardware_lpm_attr_group); + sysfs_unmerge_group(&dev->kobj, &power_attr_group); +} + +#else + +#define add_persist_attributes(dev) 0 +#define remove_persist_attributes(dev) do {} while (0) + +#define add_power_attributes(dev) 0 +#define remove_power_attributes(dev) do {} while (0) + +#endif /* CONFIG_PM */ + + +/* Descriptor fields */ +#define usb_descriptor_attr_le16(field, format_string) \ +static ssize_t \ +field##_show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct usb_device *udev; \ + \ + udev = to_usb_device(dev); \ + return sysfs_emit(buf, format_string, \ + le16_to_cpu(udev->descriptor.field)); \ +} \ +static DEVICE_ATTR_RO(field) + +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 \ +field##_show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct usb_device *udev; \ + \ + udev = to_usb_device(dev); \ + return sysfs_emit(buf, format_string, udev->descriptor.field); \ +} \ +static DEVICE_ATTR_RO(field) + +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 authorized_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *usb_dev = to_usb_device(dev); + return sysfs_emit(buf, "%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 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); + bool val; + + if (kstrtobool(buf, &val) != 0) + result = -EINVAL; + else if (val) + result = usb_authorize_device(usb_dev); + else + result = usb_deauthorize_device(usb_dev); + return result < 0 ? result : size; +} +static DEVICE_ATTR_IGNORE_LOCKDEP(authorized, S_IRUGO | S_IWUSR, + authorized_show, authorized_store); + +/* "Safely remove a device" */ +static ssize_t 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, S_IWUSR, NULL, 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_rx_lanes.attr, + &dev_attr_tx_lanes.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_ltm_capable.attr, +#ifdef CONFIG_OF + &dev_attr_devspec.attr, +#endif + NULL, +}; +static const 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 = kobj_to_dev(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 const struct attribute_group dev_string_attr_grp = { + .attrs = dev_string_attrs, + .is_visible = dev_string_attrs_are_visible, +}; + +/* Binary descriptors */ + +static ssize_t +descriptors_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(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 const BIN_ATTR_RO(descriptors, 18 + 65535); /* dev descr + max-size raw descriptor */ + +static ssize_t +bos_descriptors_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct usb_device *udev = to_usb_device(dev); + struct usb_host_bos *bos; + struct usb_bos_descriptor *desc; + size_t desclen, n = 0; + int rc; + + rc = usb_lock_device_interruptible(udev); + if (rc < 0) + return -EINTR; + bos = udev->bos; + if (bos) { + desc = bos->desc; + desclen = le16_to_cpu(desc->wTotalLength); + if (off < desclen) { + n = min(count, desclen - (size_t) off); + memcpy(buf, (void *) desc + off, n); + } + } + usb_unlock_device(udev); + return n; +} +static const BIN_ATTR_RO(bos_descriptors, 65535); /* max-size BOS */ + +/* When modifying this list, be sure to modify dev_bin_attrs_are_visible() + * accordingly. + */ +static const struct bin_attribute *const dev_bin_attrs[] = { + &bin_attr_descriptors, + &bin_attr_bos_descriptors, + NULL +}; + +static umode_t dev_bin_attrs_are_visible(struct kobject *kobj, + const struct bin_attribute *a, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct usb_device *udev = to_usb_device(dev); + + /* + * There's no need to check if the descriptors attribute should + * be visible because all devices have a device descriptor. The + * bos_descriptors attribute should be visible if and only if + * the device has a BOS, so check if it exists here. + */ + if (a == &bin_attr_bos_descriptors) { + if (udev->bos == NULL) + return 0; + } + return a->attr.mode; +} + +static const struct attribute_group dev_bin_attr_grp = { + .bin_attrs = dev_bin_attrs, + .is_bin_visible = dev_bin_attrs_are_visible, +}; + +const struct attribute_group *usb_device_groups[] = { + &dev_attr_grp, + &dev_string_attr_grp, + &dev_bin_attr_grp, + NULL +}; + +/* + * Show & store the current value of authorized_default + */ +static ssize_t 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 *hcd; + + hcd = bus_to_hcd(usb_bus); + return sysfs_emit(buf, "%u\n", hcd->dev_policy); +} + +static ssize_t authorized_default_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + unsigned int val; + struct usb_device *rh_usb_dev = to_usb_device(dev); + struct usb_bus *usb_bus = rh_usb_dev->bus; + struct usb_hcd *hcd; + + hcd = bus_to_hcd(usb_bus); + result = sscanf(buf, "%u\n", &val); + if (result == 1) { + hcd->dev_policy = val <= USB_DEVICE_AUTHORIZE_INTERNAL ? + val : USB_DEVICE_AUTHORIZE_ALL; + result = size; + } else { + result = -EINVAL; + } + return result; +} +static DEVICE_ATTR_RW(authorized_default); + +/* + * interface_authorized_default_show - show default authorization status + * for USB interfaces + * + * note: interface_authorized_default is the default value + * for initializing the authorized attribute of interfaces + */ +static ssize_t interface_authorized_default_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *usb_dev = to_usb_device(dev); + struct usb_hcd *hcd = bus_to_hcd(usb_dev->bus); + + return sysfs_emit(buf, "%u\n", !!HCD_INTF_AUTHORIZED(hcd)); +} + +/* + * interface_authorized_default_store - store default authorization status + * for USB interfaces + * + * note: interface_authorized_default is the default value + * for initializing the authorized attribute of interfaces + */ +static ssize_t interface_authorized_default_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_device *usb_dev = to_usb_device(dev); + struct usb_hcd *hcd = bus_to_hcd(usb_dev->bus); + int rc = count; + bool val; + + if (kstrtobool(buf, &val) != 0) + return -EINVAL; + + if (val) + set_bit(HCD_FLAG_INTF_AUTHORIZED, &hcd->flags); + else + clear_bit(HCD_FLAG_INTF_AUTHORIZED, &hcd->flags); + + return rc; +} +static DEVICE_ATTR_RW(interface_authorized_default); + +/* Group all the USB bus attributes */ +static struct attribute *usb_bus_attrs[] = { + &dev_attr_authorized_default.attr, + &dev_attr_interface_authorized_default.attr, + NULL, +}; + +static const struct attribute_group usb_bus_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = usb_bus_attrs, +}; + + +static int add_default_authorized_attributes(struct device *dev) +{ + int rc = 0; + + if (is_usb_device(dev)) + rc = sysfs_create_group(&dev->kobj, &usb_bus_attr_group); + + return rc; +} + +static void remove_default_authorized_attributes(struct device *dev) +{ + if (is_usb_device(dev)) { + sysfs_remove_group(&dev->kobj, &usb_bus_attr_group); + } +} + +int usb_create_sysfs_dev_files(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + int retval; + + retval = add_persist_attributes(dev); + if (retval) + goto error; + + retval = add_power_attributes(dev); + if (retval) + goto error; + + if (is_root_hub(udev)) { + retval = add_default_authorized_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; + + if (is_root_hub(udev)) + remove_default_authorized_attributes(dev); + + remove_power_attributes(dev); + remove_persist_attributes(dev); +} + +/* Interface Association Descriptor fields */ +#define usb_intf_assoc_attr(field, format_string) \ +static ssize_t \ +iad_##field##_show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + \ + return sysfs_emit(buf, format_string, \ + intf->intf_assoc->field); \ +} \ +static DEVICE_ATTR_RO(iad_##field) + +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 \ +field##_show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + \ + return sysfs_emit(buf, format_string, \ + intf->cur_altsetting->desc.field); \ +} \ +static DEVICE_ATTR_RO(field) + +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 interface_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_interface *intf; + char *string; + + intf = to_usb_interface(dev); + string = READ_ONCE(intf->cur_altsetting->string); + if (!string) + return 0; + return sysfs_emit(buf, "%s\n", string); +} +static DEVICE_ATTR_RO(interface); + +static ssize_t modalias_show(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 = READ_ONCE(intf->cur_altsetting); + + return sysfs_emit(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_RO(modalias); + +static ssize_t supports_autosuspend_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int s; + + s = device_lock_interruptible(dev); + if (s < 0) + return -EINTR; + /* Devices will be autosuspended even when an interface isn't claimed */ + s = (!dev->driver || to_usb_driver(dev->driver)->supports_autosuspend); + device_unlock(dev); + + return sysfs_emit(buf, "%u\n", s); +} +static DEVICE_ATTR_RO(supports_autosuspend); + +/* + * interface_authorized_show - show authorization status of an USB interface + * 1 is authorized, 0 is deauthorized + */ +static ssize_t interface_authorized_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + + return sysfs_emit(buf, "%u\n", intf->authorized); +} + +/* + * interface_authorized_store - authorize or deauthorize an USB interface + */ +static ssize_t interface_authorized_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + bool val; + struct kernfs_node *kn; + + if (kstrtobool(buf, &val) != 0) + return -EINVAL; + + if (val) { + usb_authorize_interface(intf); + } else { + /* + * Prevent deadlock if another process is concurrently + * trying to unregister intf. + */ + kn = sysfs_break_active_protection(&dev->kobj, &attr->attr); + if (kn) { + usb_deauthorize_interface(intf); + sysfs_unbreak_active_protection(kn); + } + } + + return count; +} +static struct device_attribute dev_attr_interface_authorized = + __ATTR(authorized, S_IRUGO | S_IWUSR, + interface_authorized_show, interface_authorized_store); + +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, + &dev_attr_interface_authorized.attr, + NULL, +}; +static const 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 = kobj_to_dev(kobj); + struct usb_interface *intf = to_usb_interface(dev); + + if (intf->intf_assoc == NULL) + return 0; + return a->mode; +} + +static const struct attribute_group intf_assoc_attr_grp = { + .attrs = intf_assoc_attrs, + .is_visible = intf_assoc_attrs_are_visible, +}; + +static ssize_t wireless_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf; + + intf = to_usb_interface(dev); + if (intf->wireless_status == USB_WIRELESS_STATUS_DISCONNECTED) + return sysfs_emit(buf, "%s\n", "disconnected"); + return sysfs_emit(buf, "%s\n", "connected"); +} +static DEVICE_ATTR_RO(wireless_status); + +static struct attribute *intf_wireless_status_attrs[] = { + &dev_attr_wireless_status.attr, + NULL +}; + +static umode_t intf_wireless_status_attr_is_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct usb_interface *intf = to_usb_interface(dev); + + if (a != &dev_attr_wireless_status.attr || + intf->wireless_status != USB_WIRELESS_STATUS_NA) + return a->mode; + return 0; +} + +static const struct attribute_group intf_wireless_status_attr_grp = { + .attrs = intf_wireless_status_attrs, + .is_visible = intf_wireless_status_attr_is_visible, +}; + +int usb_update_wireless_status_attr(struct usb_interface *intf) +{ + struct device *dev = &intf->dev; + int ret; + + ret = sysfs_update_group(&dev->kobj, &intf_wireless_status_attr_grp); + if (ret < 0) + return ret; + + sysfs_notify(&dev->kobj, NULL, "wireless_status"); + kobject_uevent(&dev->kobj, KOBJ_CHANGE); + + return 0; +} + +const struct attribute_group *usb_interface_groups[] = { + &intf_attr_grp, + &intf_assoc_attr_grp, + &intf_wireless_status_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)) { + /* This is not a serious error */ + dev_dbg(&intf->dev, "interface string descriptor file not created\n"); + } + 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/trace.c b/drivers/usb/core/trace.c new file mode 100644 index 000000000..607bcf639 --- /dev/null +++ b/drivers/usb/core/trace.c @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 Google LLC + */ +#define CREATE_TRACE_POINTS +#include "trace.h" diff --git a/drivers/usb/core/trace.h b/drivers/usb/core/trace.h new file mode 100644 index 000000000..903e57dc2 --- /dev/null +++ b/drivers/usb/core/trace.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2025 Google LLC + */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM usbcore + +#if !defined(_USB_CORE_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _USB_CORE_TRACE_H + +#include +#include +#include + +DECLARE_EVENT_CLASS(usb_core_log_usb_device, + TP_PROTO(struct usb_device *udev), + TP_ARGS(udev), + TP_STRUCT__entry( + __string(name, dev_name(&udev->dev)) + __field(enum usb_device_speed, speed) + __field(enum usb_device_state, state) + __field(unsigned short, bus_mA) + __field(unsigned, authorized) + ), + TP_fast_assign( + __assign_str(name); + __entry->speed = udev->speed; + __entry->state = udev->state; + __entry->bus_mA = udev->bus_mA; + __entry->authorized = udev->authorized; + ), + TP_printk("usb %s speed %s state %s %dmA [%s]", + __get_str(name), + usb_speed_string(__entry->speed), + usb_state_string(__entry->state), + __entry->bus_mA, + __entry->authorized ? "authorized" : "unauthorized") +); + +DEFINE_EVENT(usb_core_log_usb_device, usb_set_device_state, + TP_PROTO(struct usb_device *udev), + TP_ARGS(udev) +); + +DEFINE_EVENT(usb_core_log_usb_device, usb_alloc_dev, + TP_PROTO(struct usb_device *udev), + TP_ARGS(udev) +); + + +#endif /* _USB_CORE_TRACE_H */ + +/* this part has to be here */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#include diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c new file mode 100644 index 000000000..c06b44ca5 --- /dev/null +++ b/drivers/usb/core/urb.c @@ -0,0 +1,1021 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Released under the GPLv2 only. + */ + +#include +#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->urb_list); + 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, increments the usage counter, and returns a pointer to it. + * + * 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. + * + * Return: A pointer to the new urb, or %NULL if no memory is available. + */ +struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags) +{ + struct urb *urb; + + urb = kmalloc_flex(*urb, iso_frame_desc, iso_packets, mem_flags); + if (!urb) + 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. + * + * Return: A pointer to the urb with the incremented reference counter. + */ +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); + +static int usb_anchor_check_wakeup(struct usb_anchor *anchor) +{ + return atomic_read(&anchor->suspend_wakeups) == 0 && + list_empty(&anchor->urb_list); +} + +/* 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 (usb_anchor_check_wakeup(anchor)) + 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); + +/*-------------------------------------------------------------------*/ + +static const int pipetypes[4] = { + PIPE_CONTROL, PIPE_ISOCHRONOUS, PIPE_BULK, PIPE_INTERRUPT +}; + +/** + * usb_pipe_type_check - sanity check of a specific pipe for a usb device + * @dev: struct usb_device to be checked + * @pipe: pipe to check + * + * This performs a light-weight sanity check for the endpoint in the + * given usb device. It returns 0 if the pipe is valid for the specific usb + * device, otherwise a negative error code. + */ +int usb_pipe_type_check(struct usb_device *dev, unsigned int pipe) +{ + const struct usb_host_endpoint *ep; + + ep = usb_pipe_endpoint(dev, pipe); + if (!ep) + return -EINVAL; + if (usb_pipetype(pipe) != pipetypes[usb_endpoint_type(&ep->desc)]) + return -EINVAL; + return 0; +} +EXPORT_SYMBOL_GPL(usb_pipe_type_check); + +/** + * usb_urb_ep_type_check - sanity check of endpoint in the given urb + * @urb: urb to be checked + * + * This performs a light-weight sanity check for the endpoint in the + * given urb. It returns 0 if the urb contains a valid endpoint, otherwise + * a negative error code. + */ +int usb_urb_ep_type_check(const struct urb *urb) +{ + return usb_pipe_type_check(urb->dev, urb->pipe); +} +EXPORT_SYMBOL_GPL(usb_urb_ep_type_check); + +/** + * 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. + * + * 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. + * + * Return: + * 0 on successful submissions. A negative error number otherwise. + * + * 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 callbacks). 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; + unsigned int allowed; + bool is_eusb2_isoch_double; + + 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; + dev_WARN_ONCE(&dev->dev, (usb_pipeout(urb->pipe) != is_out), + "BOGUS control dir, pipe %x doesn't match bRequestType %x\n", + urb->pipe, setup->bRequestType); + if (le16_to_cpu(setup->wLength) != urb->transfer_buffer_length) { + dev_dbg(&dev->dev, "BOGUS control len %d doesn't match transfer length %d\n", + le16_to_cpu(setup->wLength), + urb->transfer_buffer_length); + return -EBADR; + } + } 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); + kmsan_handle_urb(urb, is_out); + + if (xfertype != USB_ENDPOINT_XFER_CONTROL && + dev->state < USB_STATE_CONFIGURED) + return -ENODEV; + + max = usb_endpoint_maxp(&ep->desc); + is_eusb2_isoch_double = usb_endpoint_is_hs_isoc_double(dev, ep); + if (!max && !is_eusb2_isoch_double) { + 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; + } + + if (dev->speed == USB_SPEED_SUPER_PLUS && + USB_SS_SSP_ISOC_COMP(ep->ss_ep_comp.bmAttributes)) { + struct usb_ssp_isoc_ep_comp_descriptor *isoc_ep_comp; + + isoc_ep_comp = &ep->ssp_isoc_ep_comp; + max = le32_to_cpu(isoc_ep_comp->dwBytesPerInterval); + } + + /* High speed, 1-3 packets/uframe, max 6 for eUSB2 double bw */ + if (dev->speed == USB_SPEED_HIGH) { + if (is_eusb2_isoch_double) + max = le32_to_cpu(ep->eusb2_isoc_ep_comp.dwBytesPerInterval); + else + max *= usb_endpoint_maxp_mult(&ep->desc); + } + + 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; + } + } else if (urb->num_sgs && !urb->dev->bus->no_sg_constraint) { + struct scatterlist *sg; + int i; + + for_each_sg(urb->sg, sg, urb->num_sgs - 1, i) + if (sg->length % max) + return -EINVAL; + } + + /* the I/O buffer must be mapped/unmapped, except when length=0 */ + if (urb->transfer_buffer_length > INT_MAX) + return -EMSGSIZE; + + /* + * stuff that drivers shouldn't do, but which shouldn't + * cause problems in HCDs if they get it wrong. + */ + + /* Check that the pipe's type matches the endpoint's type */ + if (usb_pipe_type_check(urb->dev, urb->pipe)) + dev_warn_once(&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: + case USB_ENDPOINT_XFER_INT: + if (is_out) + allowed |= URB_ZERO_PACKET; + 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); + + /* + * 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? */ + if (urb->interval <= 0) + return -EINVAL; + + /* too big? */ + switch (dev->speed) { + case USB_SPEED_SUPER_PLUS: + 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_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; + } + /* 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(), 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. + * + * Return: -EINPROGRESS on success. See description for other values on + * failure. + * + * 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); + /* + * Order the write of urb->reject above before the read + * of urb->use_count below. Pairs with the barriers in + * __usb_hcd_giveback_urb() and usb_hcd_submit_urb(). + */ + smp_mb__after_atomic(); + + 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); + /* + * Order the write of urb->reject above before the read + * of urb->use_count below. Pairs with the barriers in + * __usb_hcd_giveback_urb() and usb_hcd_submit_urb(). + */ + smp_mb__after_atomic(); + + 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 - kill all URBs associated with an anchor + * @anchor: anchor the requests are bound to + * + * This kills all outstanding URBs starting from the back of the queue, + * with guarantee that no completer callbacks will take place from the + * anchor after this function returns. + * + * 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; + int surely_empty; + + do { + spin_lock_irq(&anchor->lock); + while (!list_empty(&anchor->urb_list)) { + victim = list_entry(anchor->urb_list.prev, + struct urb, anchor_list); + /* 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); + } + surely_empty = usb_anchor_check_wakeup(anchor); + + spin_unlock_irq(&anchor->lock); + cpu_relax(); + } while (!surely_empty); +} +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; + int surely_empty; + + do { + 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); + /* 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); + } + surely_empty = usb_anchor_check_wakeup(anchor); + + spin_unlock_irq(&anchor->lock); + cpu_relax(); + } while (!surely_empty); +} +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_anchor_suspend_wakeups + * @anchor: the anchor you want to suspend wakeups on + * + * Call this to stop the last urb being unanchored from waking up any + * usb_wait_anchor_empty_timeout waiters. This is used in the hcd urb give- + * back path to delay waking up until after the completion handler has run. + */ +void usb_anchor_suspend_wakeups(struct usb_anchor *anchor) +{ + if (anchor) + atomic_inc(&anchor->suspend_wakeups); +} +EXPORT_SYMBOL_GPL(usb_anchor_suspend_wakeups); + +/** + * usb_anchor_resume_wakeups + * @anchor: the anchor you want to resume wakeups on + * + * Allow usb_wait_anchor_empty_timeout waiters to be woken up again, and + * wake up any current waiters if the anchor is empty. + */ +void usb_anchor_resume_wakeups(struct usb_anchor *anchor) +{ + if (!anchor) + return; + + atomic_dec(&anchor->suspend_wakeups); + if (usb_anchor_check_wakeup(anchor)) + wake_up(&anchor->wait); +} +EXPORT_SYMBOL_GPL(usb_anchor_resume_wakeups); + +/** + * 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 + * + * Return: Non-zero if the anchor became unused. Zero on timeout. + */ +int usb_wait_anchor_empty_timeout(struct usb_anchor *anchor, + unsigned int timeout) +{ + return wait_event_timeout(anchor->wait, + usb_anchor_check_wakeup(anchor), + 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 + * + * Return: The oldest urb from @anchor, or %NULL if @anchor has no + * urbs associated with 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; + int surely_empty; + + do { + 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); + } + surely_empty = usb_anchor_check_wakeup(anchor); + + spin_unlock_irqrestore(&anchor->lock, flags); + cpu_relax(); + } while (!surely_empty); +} + +EXPORT_SYMBOL_GPL(usb_scuttle_anchored_urbs); + +/** + * usb_anchor_empty - is an anchor empty + * @anchor: the anchor you want to query + * + * Return: 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 000000000..489dbdc96 --- /dev/null +++ b/drivers/usb/core/usb-acpi.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB-ACPI glue code + * + * Copyright 2012 Red Hat + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hub.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); + +#define UUID_USB_CONTROLLER_DSM "ce2ee385-00e6-48cb-9f05-2edb927c4899" +#define USB_DSM_DISABLE_U1_U2_FOR_PORT 5 + +/** + * usb_acpi_port_lpm_incapable - check if lpm should be disabled for a port. + * @hdev: USB device belonging to the usb hub + * @index: zero based port index + * + * Some USB3 ports may not support USB3 link power management U1/U2 states + * due to different retimer setup. ACPI provides _DSM method which returns 0x01 + * if U1 and U2 states should be disabled. Evaluate _DSM with: + * Arg0: UUID = ce2ee385-00e6-48cb-9f05-2edb927c4899 + * Arg1: Revision ID = 0 + * Arg2: Function Index = 5 + * Arg3: (empty) + * + * Return 1 if USB3 port is LPM incapable, negative on error, otherwise 0 + */ + +int usb_acpi_port_lpm_incapable(struct usb_device *hdev, int index) +{ + union acpi_object *obj; + acpi_handle port_handle; + int port1 = index + 1; + guid_t guid; + int ret; + + ret = guid_parse(UUID_USB_CONTROLLER_DSM, &guid); + if (ret) + return ret; + + port_handle = usb_get_hub_port_acpi_handle(hdev, port1); + if (!port_handle) { + dev_dbg(&hdev->dev, "port-%d no acpi handle\n", port1); + return -ENODEV; + } + + if (!acpi_check_dsm(port_handle, &guid, 0, + BIT(USB_DSM_DISABLE_U1_U2_FOR_PORT))) { + dev_dbg(&hdev->dev, "port-%d no _DSM function %d\n", + port1, USB_DSM_DISABLE_U1_U2_FOR_PORT); + return -ENODEV; + } + + obj = acpi_evaluate_dsm_typed(port_handle, &guid, 0, + USB_DSM_DISABLE_U1_U2_FOR_PORT, NULL, + ACPI_TYPE_INTEGER); + if (!obj) { + dev_dbg(&hdev->dev, "evaluate port-%d _DSM failed\n", port1); + return -EINVAL; + } + + if (obj->integer.value == 0x01) + ret = 1; + + ACPI_FREE(obj); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_acpi_port_lpm_incapable); + +/** + * 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) +{ + struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + struct usb_port *port_dev; + acpi_handle port_handle; + unsigned char state; + int port1 = index + 1; + int error = -EINVAL; + + if (!hub) + return -ENODEV; + port_dev = hub->ports[port1 - 1]; + + 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(&port_dev->dev, "acpi: power was set to %d\n", enable); + else + dev_dbg(&port_dev->dev, "acpi: power failed to be set\n"); + + return error; +} +EXPORT_SYMBOL_GPL(usb_acpi_set_power_state); + +/** + * usb_acpi_add_usb4_devlink - add device link to USB4 Host Interface for tunneled USB3 devices + * + * @udev: Tunneled USB3 device connected to a roothub. + * + * Adds a device link between a tunneled USB3 device and the USB4 Host Interface + * device to ensure correct runtime PM suspend and resume order. This function + * should only be called for tunneled USB3 devices. + * The USB4 Host Interface this tunneled device depends on is found from the roothub + * port ACPI device specific data _DSD entry. + * + * Return: negative error code on failure, 0 otherwise + */ +static int usb_acpi_add_usb4_devlink(struct usb_device *udev) +{ + struct device_link *link; + struct usb_port *port_dev; + struct usb_hub *hub; + + if (!udev->parent || udev->parent->parent) + return 0; + + hub = usb_hub_to_struct_hub(udev->parent); + if (!hub) + return 0; + port_dev = hub->ports[udev->portnum - 1]; + + struct fwnode_handle *nhi_fwnode __free(fwnode_handle) = + fwnode_find_reference(dev_fwnode(&port_dev->dev), "usb4-host-interface", 0); + + if (IS_ERR(nhi_fwnode) || !nhi_fwnode->dev) + return 0; + + link = device_link_add(&port_dev->child->dev, nhi_fwnode->dev, + DL_FLAG_STATELESS | + DL_FLAG_RPM_ACTIVE | + DL_FLAG_PM_RUNTIME); + if (!link) { + dev_err(&port_dev->dev, "Failed to created device link from %s to %s\n", + dev_name(&port_dev->child->dev), dev_name(nhi_fwnode->dev)); + return -EINVAL; + } + + dev_dbg(&port_dev->dev, "Created device link from %s to %s\n", + dev_name(&port_dev->child->dev), dev_name(nhi_fwnode->dev)); + + udev->usb4_link = link; + + return 0; +} + +/* + * Private to usb-acpi, all the core needs to know is that + * port_dev->location is non-zero when it has been set by the firmware. + */ +#define USB_ACPI_LOCATION_VALID (1 << 31) + +static void +usb_acpi_get_connect_type(struct usb_port *port_dev, acpi_handle *handle) +{ + enum usb_port_connect_type connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *upc = NULL; + struct acpi_pld_info *pld = NULL; + acpi_status status; + + /* + * According to 9.14 in ACPI Spec 6.2. _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. + */ + + if (acpi_get_physical_device_location(handle, &pld) && pld) + port_dev->location = USB_ACPI_LOCATION_VALID | + pld->group_token << 8 | pld->group_position; + + status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer); + if (ACPI_FAILURE(status)) + goto out; + + upc = buffer.pointer; + if (!upc || (upc->type != ACPI_TYPE_PACKAGE) || upc->package.count != 4) + goto out; + + /* UPC states port is connectable */ + if (upc->package.elements[0].integer.value) + if (!pld) + ; /* keep connect_type as unknown */ + else if (pld->user_visible) + connect_type = USB_PORT_CONNECT_TYPE_HOT_PLUG; + else + connect_type = USB_PORT_CONNECT_TYPE_HARD_WIRED; + else + connect_type = USB_PORT_NOT_USED; +out: + port_dev->connect_type = connect_type; + kfree(upc); + ACPI_FREE(pld); +} + +static struct acpi_device * +usb_acpi_get_companion_for_port(struct usb_port *port_dev) +{ + struct usb_device *udev; + struct acpi_device *adev; + acpi_handle *parent_handle; + int port1; + + /* Get the struct usb_device point of port's hub */ + udev = to_usb_device(port_dev->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) { + adev = ACPI_COMPANION(&udev->dev); + port1 = usb_hcd_find_raw_port_number(bus_to_hcd(udev->bus), + port_dev->portnum); + } else { + parent_handle = usb_get_hub_port_acpi_handle(udev->parent, + udev->portnum); + if (!parent_handle) + return NULL; + + adev = acpi_fetch_acpi_dev(parent_handle); + port1 = port_dev->portnum; + } + + return acpi_find_child_by_adr(adev, port1); +} + +static struct acpi_device * +usb_acpi_find_companion_for_port(struct usb_port *port_dev) +{ + struct acpi_device *adev; + + adev = usb_acpi_get_companion_for_port(port_dev); + if (!adev) + return NULL; + + usb_acpi_get_connect_type(port_dev, adev->handle); + + return adev; +} + +static struct acpi_device * +usb_acpi_find_companion_for_device(struct usb_device *udev) +{ + struct acpi_device *adev; + struct usb_port *port_dev; + struct usb_hub *hub; + + if (!udev->parent) { + /* + * root hub is only child (_ADR=0) under its parent, the HC. + * sysdev pointer is the HC as seen from firmware. + */ + adev = ACPI_COMPANION(udev->bus->sysdev); + return acpi_find_child_device(adev, 0, false); + } + + hub = usb_hub_to_struct_hub(udev->parent); + if (!hub) + return NULL; + + + /* Tunneled USB3 devices depend on USB4 Host Interface, set device link to it */ + if (udev->speed >= USB_SPEED_SUPER && + udev->tunnel_mode != USB_LINK_NATIVE) + usb_acpi_add_usb4_devlink(udev); + + /* + * This is an embedded USB device connected to a port and such + * devices share port's ACPI companion. + */ + port_dev = hub->ports[udev->portnum - 1]; + return usb_acpi_get_companion_for_port(port_dev); +} + +static struct acpi_device *usb_acpi_find_companion(struct device *dev) +{ + /* + * The USB hierarchy like following: + * + * Device (EHC1) + * Device (HUBN) + * Device (PR01) + * Device (PR11) + * Device (PR12) + * Device (FN12) + * Device (FN13) + * Device (PR13) + * ... + * where HUBN is root hub, and PRNN are USB ports and devices + * connected to them, and FNNN are individualk functions for + * connected composite USB devices. PRNN and FNNN may contain + * _CRS and other methods describing sideband resources for + * the connected device. + * + * On the kernel side both root hub and embedded USB devices are + * represented as instances of usb_device structure, and ports + * are represented as usb_port structures, so the whole process + * is split into 2 parts: finding companions for devices and + * finding companions for ports. + * + * Note that we do not handle individual functions of composite + * devices yet, for that we would need to assign companions to + * devices corresponding to USB interfaces. + */ + if (is_usb_device(dev)) + return usb_acpi_find_companion_for_device(to_usb_device(dev)); + else if (is_usb_port(dev)) + return usb_acpi_find_companion_for_port(to_usb_port(dev)); + + return NULL; +} + +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_companion = usb_acpi_find_companion, +}; + +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 000000000..df166cafe --- /dev/null +++ b/drivers/usb/core/usb.c @@ -0,0 +1,1294 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + * + * Released under the GPLv2 only. + * + * 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, + * with no callbacks. Callbacks are evil. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "hub.h" +#include "trace.h" + +const char *usbcore_name = "usbcore"; + +static bool nousb; /* Disable USB when built into kernel image */ + +module_param(nousb, bool, 0444); + +/* + * for external read access to + */ +int usb_disabled(void) +{ + return nousb; +} +EXPORT_SYMBOL_GPL(usb_disabled); + +#ifdef CONFIG_PM +/* Default delay value, in seconds */ +static int usb_autosuspend_delay = CONFIG_USB_AUTOSUSPEND_DELAY; +module_param_named(autosuspend, usb_autosuspend_delay, int, 0644); +MODULE_PARM_DESC(autosuspend, "default autosuspend delay"); + +#else +#define usb_autosuspend_delay 0 +#endif + +static bool match_endpoint(struct usb_endpoint_descriptor *epd, + struct usb_endpoint_descriptor **bulk_in, + struct usb_endpoint_descriptor **bulk_out, + struct usb_endpoint_descriptor **int_in, + struct usb_endpoint_descriptor **int_out) +{ + switch (usb_endpoint_type(epd)) { + case USB_ENDPOINT_XFER_BULK: + if (usb_endpoint_dir_in(epd)) { + if (bulk_in && !*bulk_in) { + *bulk_in = epd; + break; + } + } else { + if (bulk_out && !*bulk_out) { + *bulk_out = epd; + break; + } + } + + return false; + case USB_ENDPOINT_XFER_INT: + if (usb_endpoint_dir_in(epd)) { + if (int_in && !*int_in) { + *int_in = epd; + break; + } + } else { + if (int_out && !*int_out) { + *int_out = epd; + break; + } + } + + return false; + default: + return false; + } + + return (!bulk_in || *bulk_in) && (!bulk_out || *bulk_out) && + (!int_in || *int_in) && (!int_out || *int_out); +} + +/** + * usb_find_common_endpoints() -- look up common endpoint descriptors + * @alt: alternate setting to search + * @bulk_in: pointer to descriptor pointer, or NULL + * @bulk_out: pointer to descriptor pointer, or NULL + * @int_in: pointer to descriptor pointer, or NULL + * @int_out: pointer to descriptor pointer, or NULL + * + * Search the alternate setting's endpoint descriptors for the first bulk-in, + * bulk-out, interrupt-in and interrupt-out endpoints and return them in the + * provided pointers (unless they are NULL). + * + * If a requested endpoint is not found, the corresponding pointer is set to + * NULL. + * + * Return: Zero if all requested descriptors were found, or -ENXIO otherwise. + */ +int usb_find_common_endpoints(struct usb_host_interface *alt, + struct usb_endpoint_descriptor **bulk_in, + struct usb_endpoint_descriptor **bulk_out, + struct usb_endpoint_descriptor **int_in, + struct usb_endpoint_descriptor **int_out) +{ + struct usb_endpoint_descriptor *epd; + int i; + + if (bulk_in) + *bulk_in = NULL; + if (bulk_out) + *bulk_out = NULL; + if (int_in) + *int_in = NULL; + if (int_out) + *int_out = NULL; + + for (i = 0; i < alt->desc.bNumEndpoints; ++i) { + epd = &alt->endpoint[i].desc; + + if (match_endpoint(epd, bulk_in, bulk_out, int_in, int_out)) + return 0; + } + + return -ENXIO; +} +EXPORT_SYMBOL_GPL(usb_find_common_endpoints); + +/** + * usb_find_common_endpoints_reverse() -- look up common endpoint descriptors + * @alt: alternate setting to search + * @bulk_in: pointer to descriptor pointer, or NULL + * @bulk_out: pointer to descriptor pointer, or NULL + * @int_in: pointer to descriptor pointer, or NULL + * @int_out: pointer to descriptor pointer, or NULL + * + * Search the alternate setting's endpoint descriptors for the last bulk-in, + * bulk-out, interrupt-in and interrupt-out endpoints and return them in the + * provided pointers (unless they are NULL). + * + * If a requested endpoint is not found, the corresponding pointer is set to + * NULL. + * + * Return: Zero if all requested descriptors were found, or -ENXIO otherwise. + */ +int usb_find_common_endpoints_reverse(struct usb_host_interface *alt, + struct usb_endpoint_descriptor **bulk_in, + struct usb_endpoint_descriptor **bulk_out, + struct usb_endpoint_descriptor **int_in, + struct usb_endpoint_descriptor **int_out) +{ + struct usb_endpoint_descriptor *epd; + int i; + + if (bulk_in) + *bulk_in = NULL; + if (bulk_out) + *bulk_out = NULL; + if (int_in) + *int_in = NULL; + if (int_out) + *int_out = NULL; + + for (i = alt->desc.bNumEndpoints - 1; i >= 0; --i) { + epd = &alt->endpoint[i].desc; + + if (match_endpoint(epd, bulk_in, bulk_out, int_in, int_out)) + return 0; + } + + return -ENXIO; +} +EXPORT_SYMBOL_GPL(usb_find_common_endpoints_reverse); + +/** + * usb_find_endpoint() - Given an endpoint address, search for the endpoint's + * usb_host_endpoint structure in an interface's current altsetting. + * @intf: the interface whose current altsetting should be searched + * @ep_addr: the endpoint address (number and direction) to find + * + * Search the altsetting's list of endpoints for one with the specified address. + * + * Return: Pointer to the usb_host_endpoint if found, %NULL otherwise. + */ +static const struct usb_host_endpoint *usb_find_endpoint( + const struct usb_interface *intf, unsigned int ep_addr) +{ + int n; + const struct usb_host_endpoint *ep; + + n = intf->cur_altsetting->desc.bNumEndpoints; + ep = intf->cur_altsetting->endpoint; + for (; n > 0; (--n, ++ep)) { + if (ep->desc.bEndpointAddress == ep_addr) + return ep; + } + return NULL; +} + +/** + * usb_check_bulk_endpoints - Check whether an interface's current altsetting + * contains a set of bulk endpoints with the given addresses. + * @intf: the interface whose current altsetting should be searched + * @ep_addrs: 0-terminated array of the endpoint addresses (number and + * direction) to look for + * + * Search for endpoints with the specified addresses and check their types. + * + * Return: %true if all the endpoints are found and are bulk, %false otherwise. + */ +bool usb_check_bulk_endpoints( + const struct usb_interface *intf, const u8 *ep_addrs) +{ + const struct usb_host_endpoint *ep; + + for (; *ep_addrs; ++ep_addrs) { + ep = usb_find_endpoint(intf, *ep_addrs); + if (!ep || !usb_endpoint_xfer_bulk(&ep->desc)) + return false; + } + return true; +} +EXPORT_SYMBOL_GPL(usb_check_bulk_endpoints); + +/** + * usb_check_int_endpoints - Check whether an interface's current altsetting + * contains a set of interrupt endpoints with the given addresses. + * @intf: the interface whose current altsetting should be searched + * @ep_addrs: 0-terminated array of the endpoint addresses (number and + * direction) to look for + * + * Search for endpoints with the specified addresses and check their types. + * + * Return: %true if all the endpoints are found and are interrupt, + * %false otherwise. + */ +bool usb_check_int_endpoints( + const struct usb_interface *intf, const u8 *ep_addrs) +{ + const struct usb_host_endpoint *ep; + + for (; *ep_addrs; ++ep_addrs) { + ep = usb_find_endpoint(intf, *ep_addrs); + if (!ep || !usb_endpoint_xfer_int(&ep->desc)) + return false; + } + return true; +} +EXPORT_SYMBOL_GPL(usb_check_int_endpoints); + +/** + * 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. + * + * Return: The alternate setting, if found. %NULL otherwise. + */ +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; + + if (!config) + return NULL; + 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 + * to find the interface object with the particular interface number. + * + * 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! + * + * Return: A pointer to the interface that has @ifnum as interface number, + * if found. %NULL otherwise. + */ +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. + * + * 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! + * + * Return: A pointer to the entry of the altsetting array of @intf that + * has @altnum as the alternate setting number. %NULL if not found. + */ +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, const void *data) +{ + const 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. + * + * Return: A pointer to the interface with the matching major and @minor. + */ +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->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); + +struct each_dev_arg { + void *data; + int (*fn)(struct usb_device *, void *); +}; + +static int __each_dev(struct device *dev, void *data) +{ + struct each_dev_arg *arg = (struct each_dev_arg *)data; + + /* There are struct usb_interface on the same bus, filter them out */ + if (!is_usb_device(dev)) + return 0; + + return arg->fn(to_usb_device(dev), arg->data); +} + +/** + * usb_for_each_dev - iterate over all USB devices in the system + * @data: data pointer that will be handed to the callback function + * @fn: callback function to be called for each USB device + * + * Iterate over all USB devices and call @fn for each, passing it @data. If it + * returns anything other than 0, we break the iteration prematurely and return + * that value. + */ +int usb_for_each_dev(void *data, int (*fn)(struct usb_device *, void *)) +{ + struct each_dev_arg arg = {data, fn}; + + return bus_for_each_dev(&usb_bus_type, NULL, &arg, __each_dev); +} +EXPORT_SYMBOL_GPL(usb_for_each_dev); + +/** + * 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); + of_node_put(dev->of_node); + usb_put_hcd(hcd); + kfree(udev->product); + kfree(udev->manufacturer); + kfree(udev->serial); + kfree(udev); +} + +static int usb_dev_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + const 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, + .runtime_suspend = usb_runtime_suspend, + .runtime_resume = usb_runtime_resume, + .runtime_idle = usb_runtime_idle, +}; + +#endif /* CONFIG_PM */ + + +static char *usb_devnode(const struct device *dev, + umode_t *mode, kuid_t *uid, kgid_t *gid) +{ + const 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); +} + +const 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 +}; + +static bool usb_dev_authorized(struct usb_device *dev, struct usb_hcd *hcd) +{ + struct usb_hub *hub; + + if (!dev->parent) + return true; /* Root hub always ok [and always wired] */ + + switch (hcd->dev_policy) { + case USB_DEVICE_AUTHORIZE_NONE: + default: + return false; + + case USB_DEVICE_AUTHORIZE_ALL: + return true; + + case USB_DEVICE_AUTHORIZE_INTERNAL: + hub = usb_hub_to_struct_hub(dev->parent); + return hub->ports[dev->portnum - 1]->connect_type == + USB_PORT_CONNECT_TYPE_HARD_WIRED; + } +} + +/** + * 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: task context, might sleep. + * + * 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. + * + * Return: On success, a pointer to the allocated usb device. %NULL on + * failure. + */ +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 raw_port = port1; + + dev = kzalloc_obj(*dev); + 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; + set_dev_node(&dev->dev, dev_to_node(bus->sysdev)); + dev->state = USB_STATE_ATTACHED; + dev->lpm_disable_count = 1; + spin_lock_init(&dev->offload_lock); + dev->offload_usage = 0; + 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; + device_set_of_node_from_dev(&dev->dev, bus->sysdev); + dev_set_name(&dev->dev, "usb%d", bus->busnum); + } else { + int n; + + /* match any labeling on the hubs; it's one-based */ + if (parent->devpath[0] == '0') { + n = snprintf(dev->devpath, sizeof(dev->devpath), "%d", port1); + /* Root ports are not counted in route string */ + dev->route = 0; + } else { + n = 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)); + } + if (n >= sizeof(dev->devpath)) { + usb_put_hcd(bus_to_hcd(bus)); + usb_put_dev(dev); + return NULL; + } + + dev->dev.parent = &parent->dev; + dev_set_name(&dev->dev, "%d-%s", bus->busnum, dev->devpath); + + if (!parent->parent) { + /* device under root hub's port */ + raw_port = usb_hcd_find_raw_port_number(usb_hcd, + port1); + } + dev->dev.of_node = usb_of_get_device_node(parent, raw_port); + + /* 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 + + dev->authorized = usb_dev_authorized(dev, usb_hcd); + trace_usb_alloc_dev(dev); + return dev; +} +EXPORT_SYMBOL_GPL(usb_alloc_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. + * However, if a driver does not access the usb_device structure after + * its disconnect() method returns then refcounting is not necessary, + * because the USB core guarantees that a usb_device will not be + * deallocated until after all of its interface drivers have been unbound. + * + * Return: A pointer to the device with the incremented reference counter. + */ +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. + * However, if a driver does not access the usb_interface structure after + * its disconnect() method returns then refcounting is not necessary, + * because the USB core guarantees that a usb_interface will not be + * deallocated until after its driver has been unbound. + * + * Return: A pointer to the interface with the incremented reference counter. + */ +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_intf_get_dma_device - acquire a reference on the usb interface's DMA endpoint + * @intf: the usb interface + * + * While a USB device cannot perform DMA operations by itself, many USB + * controllers can. A call to usb_intf_get_dma_device() returns the DMA endpoint + * for the given USB interface, if any. The returned device structure must be + * released with put_device(). + * + * See also usb_get_dma_device(). + * + * Returns: A reference to the usb interface's DMA endpoint; or NULL if none + * exists. + */ +struct device *usb_intf_get_dma_device(struct usb_interface *intf) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct device *dmadev; + + if (!udev->bus) + return NULL; + + dmadev = get_device(udev->bus->sysdev); + if (!dmadev || !dmadev->dma_mask) { + put_device(dmadev); + return NULL; + } + + return dmadev; +} +EXPORT_SYMBOL_GPL(usb_intf_get_dma_device); + +/* 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 + * 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. + * + * Return: 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 + * + * Return: The current frame number for the USB host controller used + * with the given USB device. This can be used when scheduling + * isochronous requests. + * + * Note: 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, size_t minsize) +{ + struct usb_descriptor_header *header; + + while (size >= sizeof(struct usb_descriptor_header)) { + header = (struct usb_descriptor_header *)buffer; + + if (header->bLength < 2 || header->bLength > size) { + printk(KERN_ERR + "%s: bogus descriptor, type %d length %d\n", + usbcore_name, + header->bDescriptorType, + header->bLength); + return -1; + } + + if (header->bDescriptorType == type && header->bLength >= minsize) { + *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: 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). + * + * Note: + * 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_alloc_noncoherent - allocate dma-noncoherent 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 + * @dir: DMA transfer direction + * @table: used to return sg_table of allocated memory + * + * To explicit manage the memory ownership for the kernel vs the device by + * USB core, the user needs save sg_table to urb->sgt. Then USB core will + * do DMA sync for CPU and device properly. + * + * When the buffer is no longer used, free it with usb_free_noncoherent(). + * + * Return: 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). + */ +void *usb_alloc_noncoherent(struct usb_device *dev, size_t size, + gfp_t mem_flags, dma_addr_t *dma, + enum dma_data_direction dir, + struct sg_table **table) +{ + struct device *dmadev; + struct sg_table *sgt; + void *buffer; + + if (!dev || !dev->bus) + return NULL; + + dmadev = bus_to_hcd(dev->bus)->self.sysdev; + + sgt = dma_alloc_noncontiguous(dmadev, size, dir, mem_flags, 0); + if (!sgt) + return NULL; + + buffer = dma_vmap_noncontiguous(dmadev, size, sgt); + if (!buffer) { + dma_free_noncontiguous(dmadev, size, sgt, dir); + return NULL; + } + + *table = sgt; + *dma = sg_dma_address(sgt->sgl); + + return buffer; +} +EXPORT_SYMBOL_GPL(usb_alloc_noncoherent); + +/** + * usb_free_noncoherent - free memory allocated with usb_alloc_noncoherent() + * @dev: device the buffer was used with + * @size: requested buffer size + * @addr: CPU address of buffer + * @dir: DMA transfer direction + * @table: describe the allocated and DMA mapped memory, + * + * This reclaims an I/O buffer, letting it be reused. The memory must have + * been allocated using usb_alloc_noncoherent(), and the parameters must match + * those provided in that allocation request. + */ +void usb_free_noncoherent(struct usb_device *dev, size_t size, + void *addr, enum dma_data_direction dir, + struct sg_table *table) +{ + struct device *dmadev; + + if (!dev || !dev->bus) + return; + if (!addr) + return; + + dmadev = bus_to_hcd(dev->bus)->self.sysdev; + dma_vunmap_noncontiguous(dmadev, addr); + dma_free_noncontiguous(dmadev, size, table, dir); +} +EXPORT_SYMBOL_GPL(usb_free_noncoherent); + +/** + * usb_endpoint_max_periodic_payload - Get maximum payload bytes per service + * interval + * @udev: The USB device + * @ep: The endpoint + * + * Returns: the maximum number of bytes isochronous or interrupt endpoint @ep + * can transfer during a service interval, or 0 for other endpoints. + */ +u32 usb_endpoint_max_periodic_payload(struct usb_device *udev, + const struct usb_host_endpoint *ep) +{ + if (!usb_endpoint_xfer_isoc(&ep->desc) && + !usb_endpoint_xfer_int(&ep->desc)) + return 0; + + switch (udev->speed) { + case USB_SPEED_SUPER_PLUS: + if (USB_SS_SSP_ISOC_COMP(ep->ss_ep_comp.bmAttributes)) + return le32_to_cpu(ep->ssp_isoc_ep_comp.dwBytesPerInterval); + fallthrough; + case USB_SPEED_SUPER: + return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval); + default: + if (usb_endpoint_is_hs_isoc_double(udev, ep)) + return le32_to_cpu(ep->eusb2_isoc_ep_comp.dwBytesPerInterval); + return usb_endpoint_maxp(&ep->desc) * usb_endpoint_maxp_mult(&ep->desc); + } +} +EXPORT_SYMBOL_GPL(usb_endpoint_max_periodic_payload); + +/** + * usb_endpoint_is_hs_isoc_double - Tell whether an endpoint uses USB 2 + * Isochronous Double IN Bandwidth + * @udev: The USB device + * @ep: The endpoint + * + * Returns: true if an endpoint @ep conforms to USB 2 Isochronous Double IN + * Bandwidth ECN, false otherwise. + */ +bool usb_endpoint_is_hs_isoc_double(struct usb_device *udev, + const struct usb_host_endpoint *ep) +{ + return ep->eusb2_isoc_ep_comp.bDescriptorType && + le16_to_cpu(udev->descriptor.bcdUSB) == 0x220 && + usb_endpoint_is_isoc_in(&ep->desc) && + !le16_to_cpu(ep->desc.wMaxPacketSize); +} +EXPORT_SYMBOL_GPL(usb_endpoint_is_hs_isoc_double); + +/* + * 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, +}; + +static void usb_debugfs_init(void) +{ + debugfs_create_file("devices", 0444, usb_debug_root, NULL, + &usbfs_devices_fops); +} + +static void usb_debugfs_cleanup(void) +{ + debugfs_lookup_and_remove("devices", usb_debug_root); +} + +/* + * Init + */ +static int __init usb_init(void) +{ + int retval; + if (usb_disabled()) { + pr_info("%s: USB support disabled\n", usbcore_name); + return 0; + } + usb_init_pool_max(); + + usb_debugfs_init(); + + 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 = class_register(&usbmisc_class); + if (retval) + goto class_register_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: + class_unregister(&usbmisc_class); +class_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 (usb_disabled()) + return; + + usb_release_quirk_list(); + usb_deregister_device_driver(&usb_generic_driver); + usb_major_cleanup(); + usb_deregister(&usbfs_driver); + usb_devio_cleanup(); + usb_hub_cleanup(); + class_unregister(&usbmisc_class); + bus_unregister_notifier(&usb_bus_type, &usb_bus_nb); + bus_unregister(&usb_bus_type); + usb_acpi_unregister(); + usb_debugfs_cleanup(); + idr_destroy(&usb_bus_idr); +} + +subsys_initcall(usb_init); +module_exit(usb_exit); +MODULE_DESCRIPTION("USB core host-side support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h new file mode 100644 index 000000000..a9b37aeb5 --- /dev/null +++ b/drivers/usb/core/usb.h @@ -0,0 +1,217 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Released under the GPLv2 only. + */ + +#include +#include + +struct usb_hub_descriptor; +struct usb_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_update_wireless_status_attr(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_deauthorize_interface(struct usb_interface *); +extern void usb_authorize_interface(struct usb_interface *); +extern void usb_detect_quirks(struct usb_device *udev); +extern void usb_detect_interface_quirks(struct usb_device *udev); +extern void usb_release_quirk_list(void); +extern bool usb_endpoint_is_ignored(struct usb_device *udev, + struct usb_host_interface *intf, + struct usb_endpoint_descriptor *epd); +extern int usb_remove_device(struct usb_device *udev); + +extern struct usb_device_descriptor *usb_get_device_descriptor( + struct usb_device *udev); +extern int usb_set_isoch_delay(struct usb_device *dev); +extern int usb_get_bos_descriptor(struct usb_device *dev); +extern void usb_release_bos_descriptor(struct usb_device *dev); +extern int usb_set_configuration(struct usb_device *dev, int configuration); +extern int usb_choose_configuration(struct usb_device *udev); +extern int usb_generic_driver_probe(struct usb_device *udev); +extern void usb_generic_driver_disconnect(struct usb_device *udev); +extern int usb_generic_driver_suspend(struct usb_device *udev, + pm_message_t msg); +extern int usb_generic_driver_resume(struct usb_device *udev, + pm_message_t msg); + +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_hub_wq(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 const struct usb_device_id *usb_device_match_id(struct usb_device *udev, + const struct usb_device_id *id); +extern bool usb_driver_applicable(struct usb_device *udev, + const struct usb_device_driver *udrv); +extern void usb_forced_unbind_intf(struct usb_interface *intf); +extern void usb_unbind_and_rebind_marked_interfaces(struct usb_device *udev); + +extern void usb_hub_release_all_ports(struct usb_device *hdev, + struct usb_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); +extern int usb_device_supports_lpm(struct usb_device *udev); +extern int usb_port_disable(struct usb_device *udev); + +#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); + +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_enable_usb2_hardware_lpm(struct usb_device *udev); +extern int usb_disable_usb2_hardware_lpm(struct usb_device *udev); + +extern void usbfs_notify_suspend(struct usb_device *udev); +extern void usbfs_notify_resume(struct usb_device *udev); + +#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; +} + +#define usb_autosuspend_device(udev) do {} while (0) +static inline int usb_autoresume_device(struct usb_device *udev) +{ + return 0; +} + +static inline int usb_enable_usb2_hardware_lpm(struct usb_device *udev) +{ + return 0; +} + +static inline int usb_disable_usb2_hardware_lpm(struct usb_device *udev) +{ + return 0; +} + +#endif + +extern const struct class usbmisc_class; +extern const struct bus_type usb_bus_type; +extern struct mutex usb_port_peer_mutex; +extern const struct device_type usb_device_type; +extern const struct device_type usb_if_device_type; +extern const struct device_type usb_ep_device_type; +extern const 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; +} + +static inline int is_root_hub(struct usb_device *udev) +{ + return (udev->parent == NULL); +} + +extern bool is_usb_device_driver(const struct device_driver *drv); + +/* 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 usb_driver usbfs_driver; +extern const struct file_operations usbfs_devices_fops; +extern const struct file_operations usbdev_file_operations; + +extern int usb_devio_init(void); +extern void usb_devio_cleanup(void); + +/* + * Firmware specific cookie identifying a port's location. '0' == no location + * data available + */ +typedef u32 usb_port_location_t; + +/* 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 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/dwc2/Kconfig b/drivers/usb/dwc2/Kconfig new file mode 100644 index 000000000..d7af55a8e --- /dev/null +++ b/drivers/usb/dwc2/Kconfig @@ -0,0 +1,97 @@ +# SPDX-License-Identifier: GPL-2.0 + +config USB_DWC2 + tristate "DesignWare USB2 DRD Core Support" + depends on HAS_DMA + depends on USB || USB_GADGET + depends on HAS_IOMEM + select USB_ROLE_SWITCH + help + Say Y here if your system has a Dual Role Hi-Speed USB + controller based on the DesignWare HSOTG IP Core. + + For host mode, if you choose to build the driver as dynamically + linked modules, the core module will be called dwc2.ko, the PCI + bus interface module (if you have a PCI bus system) will be + called dwc2_pci.ko, and the platform interface module (for + controllers directly connected to the CPU) will be called + dwc2_platform.ko. For all modes(host, gadget and dual-role), there + will be an additional module named dwc2.ko. + +if USB_DWC2 + +choice + prompt "DWC2 Mode Selection" + default USB_DWC2_DUAL_ROLE if (USB && USB_GADGET) + default USB_DWC2_HOST if (USB && !USB_GADGET) + default USB_DWC2_PERIPHERAL if (!USB && USB_GADGET) + +config USB_DWC2_HOST + bool "Host only mode" + depends on USB=y || (USB_DWC2=m && USB) + help + The Designware USB2.0 high-speed host controller + integrated into many SoCs. Select this option if you want the + driver to operate in Host-only mode. + +comment "Gadget/Dual-role mode requires USB Gadget support to be enabled" + +config USB_DWC2_PERIPHERAL + bool "Gadget only mode" + depends on USB_GADGET=y || USB_GADGET=USB_DWC2 + help + The Designware USB2.0 high-speed gadget controller + integrated into many SoCs. Select this option if you want the + driver to operate in Peripheral-only mode. This option requires + USB_GADGET to be enabled. + +config USB_DWC2_DUAL_ROLE + bool "Dual Role mode" + depends on (USB=y && USB_GADGET=y) || (USB_DWC2=m && USB && USB_GADGET) + help + Select this option if you want the driver to work in a dual-role + mode. In this mode both host and gadget features are enabled, and + the role will be determined by the cable that gets plugged-in. This + option requires USB_GADGET to be enabled. +endchoice + +config USB_DWC2_PCI + tristate "DWC2 PCI" + depends on USB_PCI + depends on USB_GADGET || !USB_GADGET + select NOP_USB_XCEIV + help + The Designware USB2.0 PCI interface module for controllers + connected to a PCI bus. + +config USB_DWC2_DEBUG + bool "Enable Debugging Messages" + help + Say Y here to enable debugging messages in the DWC2 Driver. + +config USB_DWC2_VERBOSE + bool "Enable Verbose Debugging Messages" + depends on USB_DWC2_DEBUG + help + Say Y here to enable verbose debugging messages in the DWC2 Driver. + WARNING: Enabling this will quickly fill your message log. + If in doubt, say N. + +config USB_DWC2_TRACK_MISSED_SOFS + bool "Enable Missed SOF Tracking" + help + Say Y here to enable logging of missed SOF events to the dmesg log. + WARNING: This feature is still experimental. + If in doubt, say N. + +config USB_DWC2_DEBUG_PERIODIC + bool "Enable Debugging Messages For Periodic Transfers" + depends on USB_DWC2_DEBUG || USB_DWC2_VERBOSE + default y + help + Say N here to disable (verbose) debugging messages to be + logged for periodic transfers. This allows better debugging of + non-periodic transfers, but of course the debug logs will be + incomplete. Note that this also disables some debug messages + for which the transfer type cannot be deduced. +endif diff --git a/drivers/usb/dwc2/Makefile b/drivers/usb/dwc2/Makefile new file mode 100644 index 000000000..2bcd6945d --- /dev/null +++ b/drivers/usb/dwc2/Makefile @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0 +ccflags-$(CONFIG_USB_DWC2_DEBUG) += -DDEBUG +ccflags-$(CONFIG_USB_DWC2_VERBOSE) += -DVERBOSE_DEBUG + +obj-$(CONFIG_USB_DWC2) += dwc2.o +dwc2-y := core.o core_intr.o platform.o drd.o +dwc2-y += params.o + +ifneq ($(filter y,$(CONFIG_USB_DWC2_HOST) $(CONFIG_USB_DWC2_DUAL_ROLE)),) + dwc2-y += hcd.o hcd_intr.o + dwc2-y += hcd_queue.o hcd_ddma.o +endif + +ifneq ($(filter y,$(CONFIG_USB_DWC2_PERIPHERAL) $(CONFIG_USB_DWC2_DUAL_ROLE)),) + dwc2-y += gadget.o +endif + +ifneq ($(CONFIG_DEBUG_FS),) + dwc2-y += debugfs.o +endif + +# NOTE: The previous s3c-hsotg peripheral mode only driver has been moved to +# this location and renamed gadget.c. When building for dynamically linked +# modules, dwc2.ko will get built for host mode, peripheral mode, and dual-role +# mode. The PCI bus interface module will called dwc2_pci.ko and the platform +# interface module will be called dwc2_platform.ko. + +obj-$(CONFIG_USB_DWC2_PCI) += dwc2_pci.o +dwc2_pci-y := pci.o diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c new file mode 100644 index 000000000..f375c5185 --- /dev/null +++ b/drivers/usb/dwc2/core.c @@ -0,0 +1,1218 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * core.c - DesignWare HS OTG Controller common routines + * + * Copyright (C) 2004-2013 Synopsys, Inc. + */ + +/* + * The Core code 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "core.h" +#include "hcd.h" + +/** + * dwc2_backup_global_registers() - Backup global controller registers. + * When suspending usb bus, registers needs to be backuped + * if controller power is disabled once suspended. + * + * @hsotg: Programming view of the DWC_otg controller + */ +int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg) +{ + struct dwc2_gregs_backup *gr; + + dev_dbg(hsotg->dev, "%s\n", __func__); + + /* Backup global regs */ + gr = &hsotg->gr_backup; + + gr->gintsts = dwc2_readl(hsotg, GINTSTS); + gr->gotgctl = dwc2_readl(hsotg, GOTGCTL); + gr->gintmsk = dwc2_readl(hsotg, GINTMSK); + gr->gahbcfg = dwc2_readl(hsotg, GAHBCFG); + gr->gusbcfg = dwc2_readl(hsotg, GUSBCFG); + gr->grxfsiz = dwc2_readl(hsotg, GRXFSIZ); + gr->gnptxfsiz = dwc2_readl(hsotg, GNPTXFSIZ); + gr->gdfifocfg = dwc2_readl(hsotg, GDFIFOCFG); + gr->pcgcctl1 = dwc2_readl(hsotg, PCGCCTL1); + gr->glpmcfg = dwc2_readl(hsotg, GLPMCFG); + gr->gi2cctl = dwc2_readl(hsotg, GI2CCTL); + gr->pcgcctl = dwc2_readl(hsotg, PCGCTL); + + gr->valid = true; + return 0; +} + +/** + * dwc2_restore_global_registers() - Restore controller global registers. + * When resuming usb bus, device registers needs to be restored + * if controller power were disabled. + * + * @hsotg: Programming view of the DWC_otg controller + */ +int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg) +{ + struct dwc2_gregs_backup *gr; + + dev_dbg(hsotg->dev, "%s\n", __func__); + + /* Restore global regs */ + gr = &hsotg->gr_backup; + if (!gr->valid) { + dev_err(hsotg->dev, "%s: no global registers to restore\n", + __func__); + return -EINVAL; + } + gr->valid = false; + + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + dwc2_writel(hsotg, gr->gotgctl, GOTGCTL); + dwc2_writel(hsotg, gr->gintmsk, GINTMSK); + dwc2_writel(hsotg, gr->gusbcfg, GUSBCFG); + dwc2_writel(hsotg, gr->gahbcfg, GAHBCFG); + dwc2_writel(hsotg, gr->grxfsiz, GRXFSIZ); + dwc2_writel(hsotg, gr->gnptxfsiz, GNPTXFSIZ); + dwc2_writel(hsotg, gr->gdfifocfg, GDFIFOCFG); + dwc2_writel(hsotg, gr->pcgcctl1, PCGCCTL1); + dwc2_writel(hsotg, gr->glpmcfg, GLPMCFG); + dwc2_writel(hsotg, gr->pcgcctl, PCGCTL); + dwc2_writel(hsotg, gr->gi2cctl, GI2CCTL); + + return 0; +} + +/** + * dwc2_exit_partial_power_down() - Exit controller from Partial Power Down. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by Reset. + * @restore: Controller registers need to be restored + */ +int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, int rem_wakeup, + bool restore) +{ + struct dwc2_gregs_backup *gr; + + gr = &hsotg->gr_backup; + + /* + * Restore host or device regisers with the same mode core enterted + * to partial power down by checking "GOTGCTL_CURMODE_HOST" backup + * value of the "gotgctl" register. + */ + if (gr->gotgctl & GOTGCTL_CURMODE_HOST) + return dwc2_host_exit_partial_power_down(hsotg, rem_wakeup, + restore); + else + return dwc2_gadget_exit_partial_power_down(hsotg, restore); +} + +/** + * dwc2_enter_partial_power_down() - Put controller in Partial Power Down. + * + * @hsotg: Programming view of the DWC_otg controller + */ +int dwc2_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ + if (dwc2_is_host_mode(hsotg)) + return dwc2_host_enter_partial_power_down(hsotg); + else + return dwc2_gadget_enter_partial_power_down(hsotg); +} + +/** + * dwc2_restore_essential_regs() - Restore essiential regs of core. + * + * @hsotg: Programming view of the DWC_otg controller + * @rmode: Restore mode, enabled in case of remote-wakeup. + * @is_host: Host or device mode. + */ +static void dwc2_restore_essential_regs(struct dwc2_hsotg *hsotg, int rmode, + int is_host) +{ + u32 pcgcctl; + struct dwc2_gregs_backup *gr; + struct dwc2_dregs_backup *dr; + struct dwc2_hregs_backup *hr; + + gr = &hsotg->gr_backup; + dr = &hsotg->dr_backup; + hr = &hsotg->hr_backup; + + dev_dbg(hsotg->dev, "%s: restoring essential regs\n", __func__); + + /* Load restore values for [31:14] bits */ + pcgcctl = (gr->pcgcctl & 0xffffc000); + /* If High Speed */ + if (is_host) { + if (!(pcgcctl & PCGCTL_P2HD_PRT_SPD_MASK)) + pcgcctl |= BIT(17); + } else { + if (!(pcgcctl & PCGCTL_P2HD_DEV_ENUM_SPD_MASK)) + pcgcctl |= BIT(17); + } + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + /* Umnask global Interrupt in GAHBCFG and restore it */ + dwc2_writel(hsotg, gr->gahbcfg | GAHBCFG_GLBL_INTR_EN, GAHBCFG); + + /* Clear all pending interupts */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + + /* Unmask restore done interrupt */ + dwc2_writel(hsotg, GINTSTS_RESTOREDONE, GINTMSK); + + /* Restore GUSBCFG and HCFG/DCFG */ + dwc2_writel(hsotg, gr->gusbcfg, GUSBCFG); + + if (is_host) { + dwc2_writel(hsotg, hr->hcfg, HCFG); + if (rmode) + pcgcctl |= PCGCTL_RESTOREMODE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(10); + + pcgcctl |= PCGCTL_ESS_REG_RESTORED; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(10); + } else { + dwc2_writel(hsotg, dr->dcfg, DCFG); + if (!rmode) + pcgcctl |= PCGCTL_RESTOREMODE | PCGCTL_RSTPDWNMODULE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(10); + + pcgcctl |= PCGCTL_ESS_REG_RESTORED; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(10); + } +} + +/** + * dwc2_hib_restore_common() - Common part of restore routine. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: Remote-wakeup, enabled in case of remote-wakeup. + * @is_host: Host or device mode. + */ +void dwc2_hib_restore_common(struct dwc2_hsotg *hsotg, int rem_wakeup, + int is_host) +{ + u32 gpwrdn; + + /* Switch-on voltage to the core */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_PWRDNSWTCH; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Reset core */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_PWRDNRSTN; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Enable restore from PMU */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_RESTORE; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Disable Power Down Clamp */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_PWRDNCLMP; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(50); + + if (!is_host && rem_wakeup) + udelay(70); + + /* Deassert reset core */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PWRDNRSTN; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Reset ULPI latch */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_ULPI_LATCH_EN_DURING_HIB_ENTRY; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + + /* Disable PMU interrupt */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_PMUINTSEL; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Set Restore Essential Regs bit in PCGCCTL register */ + dwc2_restore_essential_regs(hsotg, rem_wakeup, is_host); + + /* + * Wait For Restore_done Interrupt. This mechanism of polling the + * interrupt is introduced to avoid any possible race conditions + */ + if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, GINTSTS_RESTOREDONE, + 20000)) { + dev_dbg(hsotg->dev, + "%s: Restore Done wasn't generated here\n", + __func__); + } else { + dev_dbg(hsotg->dev, "restore done generated here\n"); + + /* + * To avoid restore done interrupt storm after restore is + * generated clear GINTSTS_RESTOREDONE bit. + */ + dwc2_writel(hsotg, GINTSTS_RESTOREDONE, GINTSTS); + } +} + +/** + * dwc2_wait_for_mode() - Waits for the controller mode. + * @hsotg: Programming view of the DWC_otg controller. + * @host_mode: If true, waits for host mode, otherwise device mode. + */ +static void dwc2_wait_for_mode(struct dwc2_hsotg *hsotg, + bool host_mode) +{ + ktime_t start; + ktime_t end; + unsigned int timeout = 110; + + dev_vdbg(hsotg->dev, "Waiting for %s mode\n", + host_mode ? "host" : "device"); + + start = ktime_get(); + + while (1) { + s64 ms; + + if (dwc2_is_host_mode(hsotg) == host_mode) { + dev_vdbg(hsotg->dev, "%s mode set\n", + host_mode ? "Host" : "Device"); + break; + } + + end = ktime_get(); + ms = ktime_to_ms(ktime_sub(end, start)); + + if (ms >= (s64)timeout) { + dev_warn(hsotg->dev, "%s: Couldn't set %s mode\n", + __func__, host_mode ? "host" : "device"); + break; + } + + usleep_range(1000, 2000); + } +} + +/** + * dwc2_iddig_filter_enabled() - Returns true if the IDDIG debounce + * filter is enabled. + * + * @hsotg: Programming view of DWC_otg controller + */ +static bool dwc2_iddig_filter_enabled(struct dwc2_hsotg *hsotg) +{ + u32 gsnpsid; + u32 ghwcfg4; + + if (!dwc2_hw_is_otg(hsotg)) + return false; + + /* Check if core configuration includes the IDDIG filter. */ + ghwcfg4 = dwc2_readl(hsotg, GHWCFG4); + if (!(ghwcfg4 & GHWCFG4_IDDIG_FILT_EN)) + return false; + + /* + * Check if the IDDIG debounce filter is bypassed. Available + * in core version >= 3.10a. + */ + gsnpsid = dwc2_readl(hsotg, GSNPSID); + if (gsnpsid >= DWC2_CORE_REV_3_10a) { + u32 gotgctl = dwc2_readl(hsotg, GOTGCTL); + + if (gotgctl & GOTGCTL_DBNCE_FLTR_BYPASS) + return false; + } + + return true; +} + +/* + * dwc2_enter_hibernation() - Common function to enter hibernation. + * + * @hsotg: Programming view of the DWC_otg controller + * @is_host: True if core is in host mode. + * + * Return: 0 if successful, negative error code otherwise + */ +int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg, int is_host) +{ + if (is_host) + return dwc2_host_enter_hibernation(hsotg); + else + return dwc2_gadget_enter_hibernation(hsotg); +} + +/* + * dwc2_exit_hibernation() - Common function to exit from hibernation. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: Remote-wakeup, enabled in case of remote-wakeup. + * @reset: Enabled in case of restore with reset. + * @is_host: True if core is in host mode. + * + * Return: 0 if successful, negative error code otherwise + */ +int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, + int reset, int is_host) +{ + if (is_host) + return dwc2_host_exit_hibernation(hsotg, rem_wakeup, reset); + else + return dwc2_gadget_exit_hibernation(hsotg, rem_wakeup, reset); +} + +/* + * Do core a soft reset of the core. Be careful with this because it + * resets all the internal state machines of the core. + */ +int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait) +{ + u32 greset; + bool wait_for_host_mode = false; + + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + /* + * If the current mode is host, either due to the force mode + * bit being set (which persists after core reset) or the + * connector id pin, a core soft reset will temporarily reset + * the mode to device. A delay from the IDDIG debounce filter + * will occur before going back to host mode. + * + * Determine whether we will go back into host mode after a + * reset and account for this delay after the reset. + */ + if (dwc2_iddig_filter_enabled(hsotg)) { + u32 gotgctl = dwc2_readl(hsotg, GOTGCTL); + u32 gusbcfg = dwc2_readl(hsotg, GUSBCFG); + + if (!(gotgctl & GOTGCTL_CONID_B) || + (gusbcfg & GUSBCFG_FORCEHOSTMODE)) { + wait_for_host_mode = true; + } + } + + /* Core Soft Reset */ + greset = dwc2_readl(hsotg, GRSTCTL); + greset |= GRSTCTL_CSFTRST; + dwc2_writel(hsotg, greset, GRSTCTL); + + if ((hsotg->hw_params.snpsid & DWC2_CORE_REV_MASK) < + (DWC2_CORE_REV_4_20a & DWC2_CORE_REV_MASK)) { + if (dwc2_hsotg_wait_bit_clear(hsotg, GRSTCTL, + GRSTCTL_CSFTRST, 10000)) { + dev_warn(hsotg->dev, "%s: HANG! Soft Reset timeout GRSTCTL_CSFTRST\n", + __func__); + return -EBUSY; + } + } else { + if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, + GRSTCTL_CSFTRST_DONE, 10000)) { + dev_warn(hsotg->dev, "%s: HANG! Soft Reset timeout GRSTCTL_CSFTRST_DONE\n", + __func__); + return -EBUSY; + } + greset = dwc2_readl(hsotg, GRSTCTL); + greset &= ~GRSTCTL_CSFTRST; + greset |= GRSTCTL_CSFTRST_DONE; + dwc2_writel(hsotg, greset, GRSTCTL); + } + + /* + * Switching from device mode to host mode by disconnecting + * device cable core enters and exits form hibernation. + * However, the fifo map remains not cleared. It results + * to a WARNING (WARNING: CPU: 5 PID: 0 at drivers/usb/dwc2/ + * gadget.c:307 dwc2_hsotg_init_fifo+0x12/0x152 [dwc2]) + * if in host mode we disconnect the micro a to b host + * cable. Because core reset occurs. + * To avoid the WARNING, fifo_map should be cleared + * in dwc2_core_reset() function by taking into account configs. + * fifo_map must be cleared only if driver is configured in + * "CONFIG_USB_DWC2_PERIPHERAL" or "CONFIG_USB_DWC2_DUAL_ROLE" + * mode. + */ + dwc2_clear_fifo_map(hsotg); + + /* Wait for AHB master IDLE state */ + if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, GRSTCTL_AHBIDLE, 10000)) { + dev_warn(hsotg->dev, "%s: HANG! AHB Idle timeout GRSTCTL GRSTCTL_AHBIDLE\n", + __func__); + return -EBUSY; + } + + if (wait_for_host_mode && !skip_wait) + dwc2_wait_for_mode(hsotg, true); + + return 0; +} + +/** + * dwc2_force_mode() - Force the mode of the controller. + * + * Forcing the mode is needed for two cases: + * + * 1) If the dr_mode is set to either HOST or PERIPHERAL we force the + * controller to stay in a particular mode regardless of ID pin + * changes. We do this once during probe. + * + * 2) During probe we want to read reset values of the hw + * configuration registers that are only available in either host or + * device mode. We may need to force the mode if the current mode does + * not allow us to access the register in the mode that we want. + * + * In either case it only makes sense to force the mode if the + * controller hardware is OTG capable. + * + * Checks are done in this function to determine whether doing a force + * would be valid or not. + * + * If a force is done, it requires a IDDIG debounce filter delay if + * the filter is configured and enabled. We poll the current mode of + * the controller to account for this delay. + * + * @hsotg: Programming view of DWC_otg controller + * @host: Host mode flag + */ +void dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host) +{ + u32 gusbcfg; + u32 set; + u32 clear; + + dev_dbg(hsotg->dev, "Forcing mode to %s\n", host ? "host" : "device"); + + /* + * Force mode has no effect if the hardware is not OTG. + */ + if (!dwc2_hw_is_otg(hsotg)) + return; + + /* + * If dr_mode is either peripheral or host only, there is no + * need to ever force the mode to the opposite mode. + */ + if (WARN_ON(host && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)) + return; + + if (WARN_ON(!host && hsotg->dr_mode == USB_DR_MODE_HOST)) + return; + + gusbcfg = dwc2_readl(hsotg, GUSBCFG); + + set = host ? GUSBCFG_FORCEHOSTMODE : GUSBCFG_FORCEDEVMODE; + clear = host ? GUSBCFG_FORCEDEVMODE : GUSBCFG_FORCEHOSTMODE; + + gusbcfg &= ~clear; + gusbcfg |= set; + dwc2_writel(hsotg, gusbcfg, GUSBCFG); + + dwc2_wait_for_mode(hsotg, host); + return; +} + +/** + * dwc2_clear_force_mode() - Clears the force mode bits. + * + * After clearing the bits, wait up to 100 ms to account for any + * potential IDDIG filter delay. We can't know if we expect this delay + * or not because the value of the connector ID status is affected by + * the force mode. We only need to call this once during probe if + * dr_mode == OTG. + * + * @hsotg: Programming view of DWC_otg controller + */ +static void dwc2_clear_force_mode(struct dwc2_hsotg *hsotg) +{ + u32 gusbcfg; + + if (!dwc2_hw_is_otg(hsotg)) + return; + + dev_dbg(hsotg->dev, "Clearing force mode bits\n"); + + gusbcfg = dwc2_readl(hsotg, GUSBCFG); + gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; + gusbcfg &= ~GUSBCFG_FORCEDEVMODE; + dwc2_writel(hsotg, gusbcfg, GUSBCFG); + + if (dwc2_iddig_filter_enabled(hsotg)) + msleep(100); +} + +/* + * Sets or clears force mode based on the dr_mode parameter. + */ +void dwc2_force_dr_mode(struct dwc2_hsotg *hsotg) +{ + switch (hsotg->dr_mode) { + case USB_DR_MODE_HOST: + dwc2_force_mode(hsotg, true); + /* + * NOTE: This is required for some rockchip soc based + * platforms on their host-only dwc2. + */ + if (!dwc2_hw_is_otg(hsotg)) + msleep(50); + + break; + case USB_DR_MODE_PERIPHERAL: + dwc2_force_mode(hsotg, false); + break; + case USB_DR_MODE_OTG: + dwc2_clear_force_mode(hsotg); + break; + default: + dev_warn(hsotg->dev, "%s() Invalid dr_mode=%d\n", + __func__, hsotg->dr_mode); + break; + } +} + +/* + * dwc2_enable_acg - enable active clock gating feature + */ +void dwc2_enable_acg(struct dwc2_hsotg *hsotg) +{ + if (hsotg->params.acg_enable) { + u32 pcgcctl1 = dwc2_readl(hsotg, PCGCCTL1); + + dev_dbg(hsotg->dev, "Enabling Active Clock Gating\n"); + pcgcctl1 |= PCGCCTL1_GATEEN; + dwc2_writel(hsotg, pcgcctl1, PCGCCTL1); + } +} + +/** + * dwc2_dump_host_registers() - Prints the host registers + * + * @hsotg: Programming view of DWC_otg controller + * + * NOTE: This function will be removed once the peripheral controller code + * is integrated and the driver is stable + */ +void dwc2_dump_host_registers(struct dwc2_hsotg *hsotg) +{ +#ifdef DEBUG + u32 __iomem *addr; + int i; + + dev_dbg(hsotg->dev, "Host Global Registers\n"); + addr = hsotg->regs + HCFG; + dev_dbg(hsotg->dev, "HCFG @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HCFG)); + addr = hsotg->regs + HFIR; + dev_dbg(hsotg->dev, "HFIR @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HFIR)); + addr = hsotg->regs + HFNUM; + dev_dbg(hsotg->dev, "HFNUM @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HFNUM)); + addr = hsotg->regs + HPTXSTS; + dev_dbg(hsotg->dev, "HPTXSTS @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HPTXSTS)); + addr = hsotg->regs + HAINT; + dev_dbg(hsotg->dev, "HAINT @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HAINT)); + addr = hsotg->regs + HAINTMSK; + dev_dbg(hsotg->dev, "HAINTMSK @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HAINTMSK)); + if (hsotg->params.dma_desc_enable) { + addr = hsotg->regs + HFLBADDR; + dev_dbg(hsotg->dev, "HFLBADDR @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HFLBADDR)); + } + + addr = hsotg->regs + HPRT0; + dev_dbg(hsotg->dev, "HPRT0 @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HPRT0)); + + for (i = 0; i < hsotg->params.host_channels; i++) { + dev_dbg(hsotg->dev, "Host Channel %d Specific Registers\n", i); + addr = hsotg->regs + HCCHAR(i); + dev_dbg(hsotg->dev, "HCCHAR @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HCCHAR(i))); + addr = hsotg->regs + HCSPLT(i); + dev_dbg(hsotg->dev, "HCSPLT @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HCSPLT(i))); + addr = hsotg->regs + HCINT(i); + dev_dbg(hsotg->dev, "HCINT @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HCINT(i))); + addr = hsotg->regs + HCINTMSK(i); + dev_dbg(hsotg->dev, "HCINTMSK @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HCINTMSK(i))); + addr = hsotg->regs + HCTSIZ(i); + dev_dbg(hsotg->dev, "HCTSIZ @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HCTSIZ(i))); + addr = hsotg->regs + HCDMA(i); + dev_dbg(hsotg->dev, "HCDMA @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HCDMA(i))); + if (hsotg->params.dma_desc_enable) { + addr = hsotg->regs + HCDMAB(i); + dev_dbg(hsotg->dev, "HCDMAB @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, + HCDMAB(i))); + } + } +#endif +} + +/** + * dwc2_dump_global_registers() - Prints the core global registers + * + * @hsotg: Programming view of DWC_otg controller + * + * NOTE: This function will be removed once the peripheral controller code + * is integrated and the driver is stable + */ +void dwc2_dump_global_registers(struct dwc2_hsotg *hsotg) +{ +#ifdef DEBUG + u32 __iomem *addr; + + dev_dbg(hsotg->dev, "Core Global Registers\n"); + addr = hsotg->regs + GOTGCTL; + dev_dbg(hsotg->dev, "GOTGCTL @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GOTGCTL)); + addr = hsotg->regs + GOTGINT; + dev_dbg(hsotg->dev, "GOTGINT @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GOTGINT)); + addr = hsotg->regs + GAHBCFG; + dev_dbg(hsotg->dev, "GAHBCFG @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GAHBCFG)); + addr = hsotg->regs + GUSBCFG; + dev_dbg(hsotg->dev, "GUSBCFG @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GUSBCFG)); + addr = hsotg->regs + GRSTCTL; + dev_dbg(hsotg->dev, "GRSTCTL @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GRSTCTL)); + addr = hsotg->regs + GINTSTS; + dev_dbg(hsotg->dev, "GINTSTS @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GINTSTS)); + addr = hsotg->regs + GINTMSK; + dev_dbg(hsotg->dev, "GINTMSK @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GINTMSK)); + addr = hsotg->regs + GRXSTSR; + dev_dbg(hsotg->dev, "GRXSTSR @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GRXSTSR)); + addr = hsotg->regs + GRXFSIZ; + dev_dbg(hsotg->dev, "GRXFSIZ @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GRXFSIZ)); + addr = hsotg->regs + GNPTXFSIZ; + dev_dbg(hsotg->dev, "GNPTXFSIZ @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GNPTXFSIZ)); + addr = hsotg->regs + GNPTXSTS; + dev_dbg(hsotg->dev, "GNPTXSTS @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GNPTXSTS)); + addr = hsotg->regs + GI2CCTL; + dev_dbg(hsotg->dev, "GI2CCTL @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GI2CCTL)); + addr = hsotg->regs + GPVNDCTL; + dev_dbg(hsotg->dev, "GPVNDCTL @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GPVNDCTL)); + addr = hsotg->regs + GGPIO; + dev_dbg(hsotg->dev, "GGPIO @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GGPIO)); + addr = hsotg->regs + GUID; + dev_dbg(hsotg->dev, "GUID @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GUID)); + addr = hsotg->regs + GSNPSID; + dev_dbg(hsotg->dev, "GSNPSID @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GSNPSID)); + addr = hsotg->regs + GHWCFG1; + dev_dbg(hsotg->dev, "GHWCFG1 @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GHWCFG1)); + addr = hsotg->regs + GHWCFG2; + dev_dbg(hsotg->dev, "GHWCFG2 @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GHWCFG2)); + addr = hsotg->regs + GHWCFG3; + dev_dbg(hsotg->dev, "GHWCFG3 @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GHWCFG3)); + addr = hsotg->regs + GHWCFG4; + dev_dbg(hsotg->dev, "GHWCFG4 @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GHWCFG4)); + addr = hsotg->regs + GLPMCFG; + dev_dbg(hsotg->dev, "GLPMCFG @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GLPMCFG)); + addr = hsotg->regs + GPWRDN; + dev_dbg(hsotg->dev, "GPWRDN @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GPWRDN)); + addr = hsotg->regs + GDFIFOCFG; + dev_dbg(hsotg->dev, "GDFIFOCFG @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, GDFIFOCFG)); + addr = hsotg->regs + HPTXFSIZ; + dev_dbg(hsotg->dev, "HPTXFSIZ @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, HPTXFSIZ)); + + addr = hsotg->regs + PCGCTL; + dev_dbg(hsotg->dev, "PCGCTL @0x%08lX : 0x%08X\n", + (unsigned long)addr, dwc2_readl(hsotg, PCGCTL)); +#endif +} + +/** + * dwc2_flush_tx_fifo() - Flushes a Tx FIFO + * + * @hsotg: Programming view of DWC_otg controller + * @num: Tx FIFO to flush + */ +void dwc2_flush_tx_fifo(struct dwc2_hsotg *hsotg, const int num) +{ + u32 greset; + + dev_vdbg(hsotg->dev, "Flush Tx FIFO %d\n", num); + + /* Wait for AHB master IDLE state */ + if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, GRSTCTL_AHBIDLE, 10000)) + dev_warn(hsotg->dev, "%s: HANG! AHB Idle GRSCTL\n", + __func__); + + greset = GRSTCTL_TXFFLSH; + greset |= num << GRSTCTL_TXFNUM_SHIFT & GRSTCTL_TXFNUM_MASK; + dwc2_writel(hsotg, greset, GRSTCTL); + + if (dwc2_hsotg_wait_bit_clear(hsotg, GRSTCTL, GRSTCTL_TXFFLSH, 10000)) + dev_warn(hsotg->dev, "%s: HANG! timeout GRSTCTL GRSTCTL_TXFFLSH\n", + __func__); + + /* Wait for at least 3 PHY Clocks */ + udelay(1); +} + +/** + * dwc2_flush_rx_fifo() - Flushes the Rx FIFO + * + * @hsotg: Programming view of DWC_otg controller + */ +void dwc2_flush_rx_fifo(struct dwc2_hsotg *hsotg) +{ + u32 greset; + + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + /* Wait for AHB master IDLE state */ + if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, GRSTCTL_AHBIDLE, 10000)) + dev_warn(hsotg->dev, "%s: HANG! AHB Idle GRSCTL\n", + __func__); + + greset = GRSTCTL_RXFFLSH; + dwc2_writel(hsotg, greset, GRSTCTL); + + /* Wait for RxFIFO flush done */ + if (dwc2_hsotg_wait_bit_clear(hsotg, GRSTCTL, GRSTCTL_RXFFLSH, 10000)) + dev_warn(hsotg->dev, "%s: HANG! timeout GRSTCTL GRSTCTL_RXFFLSH\n", + __func__); + + /* Wait for at least 3 PHY Clocks */ + udelay(1); +} + +bool dwc2_is_controller_alive(struct dwc2_hsotg *hsotg) +{ + if (dwc2_readl(hsotg, GSNPSID) == 0xffffffff) + return false; + else + return true; +} + +/** + * dwc2_enable_global_interrupts() - Enables the controller's Global + * Interrupt in the AHB Config register + * + * @hsotg: Programming view of DWC_otg controller + */ +void dwc2_enable_global_interrupts(struct dwc2_hsotg *hsotg) +{ + u32 ahbcfg = dwc2_readl(hsotg, GAHBCFG); + + ahbcfg |= GAHBCFG_GLBL_INTR_EN; + dwc2_writel(hsotg, ahbcfg, GAHBCFG); +} + +/** + * dwc2_disable_global_interrupts() - Disables the controller's Global + * Interrupt in the AHB Config register + * + * @hsotg: Programming view of DWC_otg controller + */ +void dwc2_disable_global_interrupts(struct dwc2_hsotg *hsotg) +{ + u32 ahbcfg = dwc2_readl(hsotg, GAHBCFG); + + ahbcfg &= ~GAHBCFG_GLBL_INTR_EN; + dwc2_writel(hsotg, ahbcfg, GAHBCFG); +} + +/* Returns the controller's GHWCFG2.OTG_MODE. */ +unsigned int dwc2_op_mode(struct dwc2_hsotg *hsotg) +{ + u32 ghwcfg2 = dwc2_readl(hsotg, GHWCFG2); + + return (ghwcfg2 & GHWCFG2_OP_MODE_MASK) >> + GHWCFG2_OP_MODE_SHIFT; +} + +/* Returns true if the controller is capable of DRD. */ +bool dwc2_hw_is_otg(struct dwc2_hsotg *hsotg) +{ + unsigned int op_mode = dwc2_op_mode(hsotg); + + return (op_mode == GHWCFG2_OP_MODE_HNP_SRP_CAPABLE) || + (op_mode == GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE) || + (op_mode == GHWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE); +} + +/* Returns true if the controller is host-only. */ +bool dwc2_hw_is_host(struct dwc2_hsotg *hsotg) +{ + unsigned int op_mode = dwc2_op_mode(hsotg); + + return (op_mode == GHWCFG2_OP_MODE_SRP_CAPABLE_HOST) || + (op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST); +} + +/* Returns true if the controller is device-only. */ +bool dwc2_hw_is_device(struct dwc2_hsotg *hsotg) +{ + unsigned int op_mode = dwc2_op_mode(hsotg); + + return (op_mode == GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE) || + (op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE); +} + +/** + * dwc2_hsotg_wait_bit_set - Waits for bit to be set. + * @hsotg: Programming view of DWC_otg controller. + * @offset: Register's offset where bit/bits must be set. + * @mask: Mask of the bit/bits which must be set. + * @timeout: Timeout to wait. + * + * Return: 0 if bit/bits are set or -ETIMEDOUT in case of timeout. + */ +int dwc2_hsotg_wait_bit_set(struct dwc2_hsotg *hsotg, u32 offset, u32 mask, + u32 timeout) +{ + u32 i; + + for (i = 0; i < timeout; i++) { + if (dwc2_readl(hsotg, offset) & mask) + return 0; + udelay(1); + } + + return -ETIMEDOUT; +} + +/** + * dwc2_hsotg_wait_bit_clear - Waits for bit to be clear. + * @hsotg: Programming view of DWC_otg controller. + * @offset: Register's offset where bit/bits must be set. + * @mask: Mask of the bit/bits which must be set. + * @timeout: Timeout to wait. + * + * Return: 0 if bit/bits are set or -ETIMEDOUT in case of timeout. + */ +int dwc2_hsotg_wait_bit_clear(struct dwc2_hsotg *hsotg, u32 offset, u32 mask, + u32 timeout) +{ + u32 i; + + for (i = 0; i < timeout; i++) { + if (!(dwc2_readl(hsotg, offset) & mask)) + return 0; + udelay(1); + } + + return -ETIMEDOUT; +} + +/* + * Initializes the FSLSPClkSel field of the HCFG register depending on the + * PHY type + */ +void dwc2_init_fs_ls_pclk_sel(struct dwc2_hsotg *hsotg) +{ + u32 hcfg, val; + + if ((hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_ULPI && + hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED && + hsotg->params.ulpi_fs_ls) || + hsotg->params.phy_type == DWC2_PHY_TYPE_PARAM_FS) { + /* Full speed PHY */ + val = HCFG_FSLSPCLKSEL_48_MHZ; + } else { + /* High speed PHY running at full speed or high speed */ + val = HCFG_FSLSPCLKSEL_30_60_MHZ; + } + + dev_dbg(hsotg->dev, "Initializing HCFG.FSLSPClkSel to %08x\n", val); + hcfg = dwc2_readl(hsotg, HCFG); + hcfg &= ~HCFG_FSLSPCLKSEL_MASK; + hcfg |= val << HCFG_FSLSPCLKSEL_SHIFT; + dwc2_writel(hsotg, hcfg, HCFG); +} + +static void dwc2_set_clock_switch_timer(struct dwc2_hsotg *hsotg) +{ + u32 grstctl, gsnpsid, val = 0; + + gsnpsid = dwc2_readl(hsotg, GSNPSID); + + /* + * Applicable only to HSOTG core v5.00a or higher. + * Not applicable to HS/FS IOT devices. + */ + if ((gsnpsid & ~DWC2_CORE_REV_MASK) != DWC2_OTG_ID || + gsnpsid < DWC2_CORE_REV_5_00a) + return; + + if ((hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI && + hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_NOT_SUPPORTED) || + (hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_ULPI && + hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_NOT_SUPPORTED) || + (hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_NOT_SUPPORTED && + hsotg->hw_params.fs_phy_type != GHWCFG2_FS_PHY_TYPE_NOT_SUPPORTED)) { + val = GRSTCTL_CLOCK_SWITH_TIMER_VALUE_DIS; + } + + if (hsotg->params.speed == DWC2_SPEED_PARAM_LOW && + hsotg->hw_params.hs_phy_type != GHWCFG2_HS_PHY_TYPE_NOT_SUPPORTED && + hsotg->hw_params.fs_phy_type != GHWCFG2_FS_PHY_TYPE_NOT_SUPPORTED) { + val = GRSTCTL_CLOCK_SWITH_TIMER_VALUE_147; + } + + grstctl = dwc2_readl(hsotg, GRSTCTL); + grstctl &= ~GRSTCTL_CLOCK_SWITH_TIMER_MASK; + grstctl |= GRSTCTL_CLOCK_SWITH_TIMER(val); + dwc2_writel(hsotg, grstctl, GRSTCTL); +} + +static int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) +{ + u32 usbcfg, ggpio, i2cctl; + int retval = 0; + + /* + * core_init() is now called on every switch so only call the + * following for the first time through + */ + if (select_phy) { + dev_dbg(hsotg->dev, "FS PHY selected\n"); + + usbcfg = dwc2_readl(hsotg, GUSBCFG); + if (!(usbcfg & GUSBCFG_PHYSEL)) { + usbcfg |= GUSBCFG_PHYSEL; + dwc2_writel(hsotg, usbcfg, GUSBCFG); + + dwc2_set_clock_switch_timer(hsotg); + + /* Reset after a PHY select */ + retval = dwc2_core_reset(hsotg, false); + + if (retval) { + dev_err(hsotg->dev, + "%s: Reset failed, aborting", __func__); + return retval; + } + } + + if (hsotg->params.activate_stm_fs_transceiver) { + ggpio = dwc2_readl(hsotg, GGPIO); + if (!(ggpio & GGPIO_STM32_OTG_GCCFG_PWRDWN)) { + dev_dbg(hsotg->dev, "Activating transceiver\n"); + /* + * STM32F4x9 uses the GGPIO register as general + * core configuration register. + */ + ggpio |= GGPIO_STM32_OTG_GCCFG_PWRDWN; + dwc2_writel(hsotg, ggpio, GGPIO); + } + } + } + + /* + * 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 (dwc2_is_host_mode(hsotg)) + dwc2_init_fs_ls_pclk_sel(hsotg); + + if (hsotg->params.i2c_enable) { + dev_dbg(hsotg->dev, "FS PHY enabling I2C\n"); + + /* Program GUSBCFG.OtgUtmiFsSel to I2C */ + usbcfg = dwc2_readl(hsotg, GUSBCFG); + usbcfg |= GUSBCFG_OTG_UTMI_FS_SEL; + dwc2_writel(hsotg, usbcfg, GUSBCFG); + + /* Program GI2CCTL.I2CEn */ + i2cctl = dwc2_readl(hsotg, GI2CCTL); + i2cctl &= ~GI2CCTL_I2CDEVADDR_MASK; + i2cctl |= 1 << GI2CCTL_I2CDEVADDR_SHIFT; + i2cctl &= ~GI2CCTL_I2CEN; + dwc2_writel(hsotg, i2cctl, GI2CCTL); + i2cctl |= GI2CCTL_I2CEN; + dwc2_writel(hsotg, i2cctl, GI2CCTL); + } + + return retval; +} + +static int dwc2_hs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) +{ + u32 usbcfg, usbcfg_old; + int retval = 0; + + if (!select_phy) + return 0; + + usbcfg = dwc2_readl(hsotg, GUSBCFG); + usbcfg_old = usbcfg; + + /* + * HS PHY parameters. These parameters are preserved during soft reset + * so only program the first time. Do a soft reset immediately after + * setting phyif. + */ + switch (hsotg->params.phy_type) { + case DWC2_PHY_TYPE_PARAM_ULPI: + /* ULPI interface */ + dev_dbg(hsotg->dev, "HS ULPI PHY selected\n"); + usbcfg |= GUSBCFG_ULPI_UTMI_SEL; + usbcfg &= ~(GUSBCFG_PHYIF16 | GUSBCFG_DDRSEL); + if (hsotg->params.phy_ulpi_ddr) + usbcfg |= GUSBCFG_DDRSEL; + + /* Set external VBUS indicator as needed. */ + if (hsotg->params.oc_disable) + usbcfg |= (GUSBCFG_ULPI_INT_VBUS_IND | + GUSBCFG_INDICATORPASSTHROUGH); + break; + case DWC2_PHY_TYPE_PARAM_UTMI: + /* UTMI+ interface */ + dev_dbg(hsotg->dev, "HS UTMI+ PHY selected\n"); + usbcfg &= ~(GUSBCFG_ULPI_UTMI_SEL | GUSBCFG_PHYIF16); + if (hsotg->params.phy_utmi_width == 16) + usbcfg |= GUSBCFG_PHYIF16; + break; + default: + dev_err(hsotg->dev, "FS PHY selected at HS!\n"); + break; + } + + if (usbcfg != usbcfg_old) { + dwc2_writel(hsotg, usbcfg, GUSBCFG); + + /* Reset after setting the PHY parameters */ + retval = dwc2_core_reset(hsotg, false); + if (retval) { + dev_err(hsotg->dev, + "%s: Reset failed, aborting", __func__); + return retval; + } + } + + return retval; +} + +static void dwc2_set_turnaround_time(struct dwc2_hsotg *hsotg) +{ + u32 usbcfg; + + if (hsotg->params.phy_type != DWC2_PHY_TYPE_PARAM_UTMI) + return; + + usbcfg = dwc2_readl(hsotg, GUSBCFG); + + usbcfg &= ~GUSBCFG_USBTRDTIM_MASK; + if (hsotg->params.phy_utmi_width == 16) + usbcfg |= 5 << GUSBCFG_USBTRDTIM_SHIFT; + else + usbcfg |= 9 << GUSBCFG_USBTRDTIM_SHIFT; + + dwc2_writel(hsotg, usbcfg, GUSBCFG); +} + +int dwc2_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) +{ + u32 usbcfg; + u32 otgctl; + int retval = 0; + + if ((hsotg->params.speed == DWC2_SPEED_PARAM_FULL || + hsotg->params.speed == DWC2_SPEED_PARAM_LOW) && + hsotg->params.phy_type == DWC2_PHY_TYPE_PARAM_FS) { + /* If FS/LS mode with FS/LS PHY */ + retval = dwc2_fs_phy_init(hsotg, select_phy); + if (retval) + return retval; + } else { + /* High speed PHY */ + retval = dwc2_hs_phy_init(hsotg, select_phy); + if (retval) + return retval; + + if (dwc2_is_device_mode(hsotg)) + dwc2_set_turnaround_time(hsotg); + } + + if (hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_ULPI && + hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED && + hsotg->params.ulpi_fs_ls) { + dev_dbg(hsotg->dev, "Setting ULPI FSLS\n"); + usbcfg = dwc2_readl(hsotg, GUSBCFG); + usbcfg |= GUSBCFG_ULPI_FS_LS; + usbcfg |= GUSBCFG_ULPI_CLK_SUSP_M; + dwc2_writel(hsotg, usbcfg, GUSBCFG); + } else { + usbcfg = dwc2_readl(hsotg, GUSBCFG); + usbcfg &= ~GUSBCFG_ULPI_FS_LS; + usbcfg &= ~GUSBCFG_ULPI_CLK_SUSP_M; + dwc2_writel(hsotg, usbcfg, GUSBCFG); + } + + if (!hsotg->params.activate_ingenic_overcurrent_detection) { + if (dwc2_is_host_mode(hsotg)) { + otgctl = readl(hsotg->regs + GOTGCTL); + otgctl |= GOTGCTL_VBVALOEN | GOTGCTL_VBVALOVAL; + writel(otgctl, hsotg->regs + GOTGCTL); + } + } + + return retval; +} + +MODULE_DESCRIPTION("DESIGNWARE HS OTG Core"); +MODULE_AUTHOR("Synopsys, Inc."); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h new file mode 100644 index 000000000..767251aa1 --- /dev/null +++ b/drivers/usb/dwc2/core.h @@ -0,0 +1,1571 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */ +/* + * core.h - DesignWare HS OTG Controller common declarations + * + * Copyright (C) 2004-2013 Synopsys, Inc. + */ + +#ifndef __DWC2_CORE_H__ +#define __DWC2_CORE_H__ + +#include +#include +#include +#include +#include +#include +#include +#include "hw.h" + +/* + * Suggested defines for tracers: + * - no_printk: Disable tracing + * - pr_info: Print this info to the console + * - trace_printk: Print this info to trace buffer (good for verbose logging) + */ + +#define DWC2_TRACE_SCHEDULER no_printk +#define DWC2_TRACE_SCHEDULER_VB no_printk + +/* Detailed scheduler tracing, but won't overwhelm console */ +#define dwc2_sch_dbg(hsotg, fmt, ...) \ + DWC2_TRACE_SCHEDULER(pr_fmt("%s: SCH: " fmt), \ + dev_name(hsotg->dev), ##__VA_ARGS__) + +/* Verbose scheduler tracing */ +#define dwc2_sch_vdbg(hsotg, fmt, ...) \ + DWC2_TRACE_SCHEDULER_VB(pr_fmt("%s: SCH: " fmt), \ + dev_name(hsotg->dev), ##__VA_ARGS__) + +/* Maximum number of Endpoints/HostChannels */ +#define MAX_EPS_CHANNELS 16 + +/* dwc2-hsotg declarations */ +static const char * const dwc2_hsotg_supply_names[] = { + "vusb_d", /* digital USB supply, 1.2V */ + "vusb_a", /* analog USB supply, 1.1V */ +}; + +#define DWC2_NUM_SUPPLIES ARRAY_SIZE(dwc2_hsotg_supply_names) + +/* + * 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 dwc2_hsotg; +struct dwc2_hsotg_req; + +/** + * struct dwc2_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. + * @dir_in: Set to true if this endpoint is of the IN direction, which + * means that it is sending data to the Host. + * @map_dir: Set to the value of dir_in when the DMA buffer is mapped. + * @index: The index for the endpoint registers. + * @mc: Multi Count - number of transactions per microframe + * @interval: Interval for periodic endpoints, in frames or microframes. + * @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 + * @isochronous: Set if this is a isochronous ep + * @send_zlp: Set if we need to send a zero-length packet. + * @wedged: Set if ep is wedged. + * @desc_list_dma: The DMA address of descriptor chain currently in use. + * @desc_list: Pointer to descriptor DMA chain head currently in use. + * @desc_count: Count of entries within the DMA descriptor chain of EP. + * @next_desc: index of next free descriptor in the ISOC chain under SW control. + * @compl_desc: index of next descriptor to be completed by xFerComplete + * @total_data: The total number of data bytes done. + * @fifo_size: The size of the FIFO (for periodic IN endpoints) + * @fifo_index: For Dedicated FIFO operation, only FIFO0 can be used for EP0. + * @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 + * @target_frame: Targeted frame num to setup next ISOC transfer + * @frame_overrun: Indicates SOF number overrun in DSTS + * + * This is the driver's state for each registered endpoint, 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 dwc2_hsotg_ep { + struct usb_ep ep; + struct list_head queue; + struct dwc2_hsotg *parent; + struct dwc2_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 short fifo_index; + + unsigned char dir_in; + unsigned char map_dir; + unsigned char index; + unsigned char mc; + u16 interval; + + unsigned int halted:1; + unsigned int periodic:1; + unsigned int isochronous:1; + unsigned int send_zlp:1; + unsigned int wedged:1; + unsigned int target_frame; +#define TARGET_FRAME_INITIAL 0xFFFFFFFF + bool frame_overrun; + + dma_addr_t desc_list_dma; + struct dwc2_dma_desc *desc_list; + u8 desc_count; + + unsigned int next_desc; + unsigned int compl_desc; + + char name[10]; +}; + +/** + * struct dwc2_hsotg_req - data transfer request + * @req: The USB gadget request + * @queue: The list of requests for the endpoint this is queued for. + * @saved_req_buf: variable to save req.buf when bounce buffers are used. + */ +struct dwc2_hsotg_req { + struct usb_request req; + struct list_head queue; + void *saved_req_buf; +}; + +#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) +#define call_gadget(_hs, _entry) \ +do { \ + 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); \ + } \ +} while (0) +#else +#define call_gadget(_hs, _entry) do {} while (0) +#endif + +struct dwc2_hsotg; +struct dwc2_host_chan; + +/* Device States */ +enum dwc2_lx_state { + DWC2_L0, /* On state */ + DWC2_L1, /* LPM sleep state */ + DWC2_L2, /* USB suspend state */ + DWC2_L3, /* Off state */ +}; + +/* Gadget ep0 states */ +enum dwc2_ep0_state { + DWC2_EP0_SETUP, + DWC2_EP0_DATA_IN, + DWC2_EP0_DATA_OUT, + DWC2_EP0_STATUS_IN, + DWC2_EP0_STATUS_OUT, +}; + +/** + * struct dwc2_core_params - Parameters for configuring the core + * + * @otg_caps: Specifies the OTG capabilities. OTG caps from the platform parameters, + * used to setup the: + * - HNP and SRP capable + * - SRP Only capable + * - No HNP/SRP capable (always available) + * Defaults to best available option + * - OTG revision number the device is compliant with, in binary-coded + * decimal (i.e. 2.0 is 0200H). (see struct usb_otg_caps) + * @host_dma: 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 (always available) + * 1 - DMA (default, if available) + * @dma_desc_enable: When DMA mode is enabled, specifies whether to use + * address DMA mode or descriptor DMA mode for accessing + * the data FIFOs. The driver will automatically detect the + * value for this if none is specified. + * 0 - Address DMA + * 1 - Descriptor DMA (default, if available) + * @dma_desc_fs_enable: When DMA mode is enabled, specifies whether to use + * address DMA mode or descriptor DMA mode for accessing + * the data FIFOs in Full Speed mode only. The driver + * will automatically detect the value for this if none is + * specified. + * 0 - Address DMA + * 1 - Descriptor DMA in FS (default, if available) + * @speed: 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. + * 0 - High Speed + * (default when phy_type is UTMI+ or ULPI) + * 1 - Full Speed + * (default when phy_type is Full Speed) + * @enable_dynamic_fifo: 0 - Use coreConsultant-specified FIFO size parameters + * 1 - Allow dynamic FIFO sizing (default, if available) + * @en_multiple_tx_fifo: Specifies whether dedicated per-endpoint transmit FIFOs + * are enabled for non-periodic IN endpoints in device + * mode. + * @host_rx_fifo_size: Number of 4-byte words in the Rx FIFO in host mode when + * dynamic FIFO sizing is enabled + * 16 to 32768 + * Actual maximum value is autodetected and also + * the default. + * @host_nperio_tx_fifo_size: Number of 4-byte words in the non-periodic Tx FIFO + * in host mode when dynamic FIFO sizing is enabled + * 16 to 32768 + * Actual maximum value is autodetected and also + * the default. + * @host_perio_tx_fifo_size: Number of 4-byte words in the periodic Tx FIFO in + * host mode when dynamic FIFO sizing is enabled + * 16 to 32768 + * Actual maximum value is autodetected and also + * the default. + * @max_transfer_size: The maximum transfer size supported, in bytes + * 2047 to 65,535 + * Actual maximum value is autodetected and also + * the default. + * @max_packet_count: The maximum number of packets in a transfer + * 15 to 511 + * Actual maximum value is autodetected and also + * the default. + * @host_channels: The number of host channel registers to use + * 1 to 16 + * Actual maximum value is autodetected and also + * the default. + * @phy_type: Specifies the type of PHY interface to use. By default, + * the driver will automatically detect the phy_type. + * 0 - Full Speed Phy + * 1 - UTMI+ Phy + * 2 - ULPI Phy + * Defaults to best available option (2, 1, then 0) + * @phy_utmi_width: Specifies the UTMI+ Data Width (in bits). 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 (default 16 if available) + * @eusb2_disc: Specifies whether eUSB2 PHY disconnect support flow + * applicable or no. Applicable in device mode of HSOTG + * and HS IOT cores v5.00 or higher. + * 0 - eUSB2 PHY disconnect support flow not applicable + * 1 - eUSB2 PHY disconnect support flow applicable + * @phy_ulpi_ddr: 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 + * @phy_ulpi_ext_vbus: For a ULPI phy, specifies whether to use the internal or + * external supply to drive the VBus + * 0 - Internal supply (default) + * 1 - External supply + * @i2c_enable: Specifies whether to use the I2Cinterface for a full + * speed PHY. This parameter is only applicable if phy_type + * is FS. + * 0 - No (default) + * 1 - Yes + * @ipg_isoc_en: Indicates the IPG supports is enabled or disabled. + * 0 - Disable (default) + * 1 - Enable + * @acg_enable: For enabling Active Clock Gating in the controller + * 0 - No + * 1 - Yes + * @ulpi_fs_ls: Make ULPI phy operate in FS/LS mode only + * 0 - No (default) + * 1 - Yes + * @host_support_fs_ls_low_power: 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 + * @host_ls_low_power_phy_clk: 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. + * 0 - 48 MHz + * (default when phy_type is UTMI+ or ULPI) + * 1 - 6 MHz + * (default when phy_type is Full Speed) + * @oc_disable: Flag to disable overcurrent condition. + * 0 - Allow overcurrent condition to get detected + * 1 - Disable overcurrent condtion to get detected + * @ts_dline: Enable Term Select Dline pulsing + * 0 - No (default) + * 1 - Yes + * @reload_ctl: Allow dynamic reloading of HFIR register during runtime + * 0 - No (default for core < 2.92a) + * 1 - Yes (default for core >= 2.92a) + * @ahbcfg: This field allows the default value of the GAHBCFG + * register to be overridden + * -1 - GAHBCFG value will be set to 0x06 + * (INCR, default) + * all others - GAHBCFG value will be overridden with + * this value + * Not all bits can be controlled like this, the + * bits defined by GAHBCFG_CTRL_MASK are controlled + * by the driver and are ignored in this + * configuration value. + * @uframe_sched: True to enable the microframe scheduler + * @external_id_pin_ctl: Specifies whether ID pin is handled externally. + * Disable CONIDSTSCHNG controller interrupt in such + * case. + * 0 - No (default) + * 1 - Yes + * @power_down: Specifies whether the controller support power_down. + * If power_down is enabled, the controller will enter + * power_down in both peripheral and host mode when + * needed. + * 0 - No (default) + * 1 - Partial power down + * 2 - Hibernation + * @no_clock_gating: Specifies whether to avoid clock gating feature. + * 0 - No (use clock gating) + * 1 - Yes (avoid it) + * @lpm: Enable LPM support. + * 0 - No + * 1 - Yes + * @lpm_clock_gating: Enable core PHY clock gating. + * 0 - No + * 1 - Yes + * @besl: Enable LPM Errata support. + * 0 - No + * 1 - Yes + * @hird_threshold_en: HIRD or HIRD Threshold enable. + * 0 - No + * 1 - Yes + * @hird_threshold: Value of BESL or HIRD Threshold. + * @ref_clk_per: Indicates in terms of pico seconds the period + * of ref_clk. + * 62500 - 16MHz + * 58823 - 17MHz + * 52083 - 19.2MHz + * 50000 - 20MHz + * 41666 - 24MHz + * 33333 - 30MHz (default) + * 25000 - 40MHz + * @sof_cnt_wkup_alert: Indicates in term of number of SOF's after which + * the controller should generate an interrupt if the + * device had been in L1 state until that period. + * This is used by SW to initiate Remote WakeUp in the + * controller so as to sync to the uF number from the host. + * @activate_stm_fs_transceiver: Activate internal transceiver using GGPIO + * register. + * 0 - Deactivate the transceiver (default) + * 1 - Activate the transceiver + * @activate_stm_id_vb_detection: Activate external ID pin and Vbus level + * detection using GGPIO register. + * 0 - Deactivate the external level detection (default) + * 1 - Activate the external level detection + * @activate_ingenic_overcurrent_detection: Activate Ingenic overcurrent + * detection. + * 0 - Deactivate the overcurrent detection + * 1 - Activate the overcurrent detection (default) + * @g_dma: Enables gadget dma usage (default: autodetect). + * @g_dma_desc: Enables gadget descriptor DMA (default: autodetect). + * @g_rx_fifo_size: The periodic rx fifo size for the device, in + * DWORDS from 16-32768 (default: 2048 if + * possible, otherwise autodetect). + * @g_np_tx_fifo_size: The non-periodic tx fifo size for the device in + * DWORDS from 16-32768 (default: 1024 if + * possible, otherwise autodetect). + * @g_tx_fifo_size: An array of TX fifo sizes in dedicated fifo + * mode. Each value corresponds to one EP + * starting from EP1 (max 15 values). Sizes are + * in DWORDS with possible values from + * 16-32768 (default: 256, 256, 256, 256, 768, + * 768, 768, 768, 0, 0, 0, 0, 0, 0, 0). + * @change_speed_quirk: Change speed configuration to DWC2_SPEED_PARAM_FULL + * while full&low speed device connect. And change speed + * back to DWC2_SPEED_PARAM_HIGH while device is gone. + * 0 - No (default) + * 1 - Yes + * @service_interval: Enable service interval based scheduling. + * 0 - No + * 1 - Yes + * + * The following parameters may be specified when starting the module. These + * parameters define how the DWC_otg controller should be configured. A + * value of -1 (or any other out of range value) for any parameter means + * to read the value from hardware (if possible) or use the builtin + * default described above. + */ +struct dwc2_core_params { + struct usb_otg_caps otg_caps; + u8 phy_type; +#define DWC2_PHY_TYPE_PARAM_FS 0 +#define DWC2_PHY_TYPE_PARAM_UTMI 1 +#define DWC2_PHY_TYPE_PARAM_ULPI 2 + + u8 speed; +#define DWC2_SPEED_PARAM_HIGH 0 +#define DWC2_SPEED_PARAM_FULL 1 +#define DWC2_SPEED_PARAM_LOW 2 + + u8 phy_utmi_width; + bool eusb2_disc; + bool phy_ulpi_ddr; + bool phy_ulpi_ext_vbus; + bool enable_dynamic_fifo; + bool en_multiple_tx_fifo; + bool i2c_enable; + bool acg_enable; + bool ulpi_fs_ls; + bool ts_dline; + bool reload_ctl; + bool uframe_sched; + bool external_id_pin_ctl; + + int power_down; +#define DWC2_POWER_DOWN_PARAM_NONE 0 +#define DWC2_POWER_DOWN_PARAM_PARTIAL 1 +#define DWC2_POWER_DOWN_PARAM_HIBERNATION 2 + bool no_clock_gating; + + bool lpm; + bool lpm_clock_gating; + bool besl; + bool hird_threshold_en; + bool service_interval; + u8 hird_threshold; + bool activate_stm_fs_transceiver; + bool activate_stm_id_vb_detection; + bool activate_ingenic_overcurrent_detection; + bool ipg_isoc_en; + u16 max_packet_count; + u32 max_transfer_size; + u32 ahbcfg; + + /* GREFCLK parameters */ + u32 ref_clk_per; + u16 sof_cnt_wkup_alert; + + /* Host parameters */ + bool host_dma; + bool dma_desc_enable; + bool dma_desc_fs_enable; + bool host_support_fs_ls_low_power; + bool host_ls_low_power_phy_clk; + bool oc_disable; + + u8 host_channels; + u16 host_rx_fifo_size; + u16 host_nperio_tx_fifo_size; + u16 host_perio_tx_fifo_size; + + /* Gadget parameters */ + bool g_dma; + bool g_dma_desc; + u32 g_rx_fifo_size; + u32 g_np_tx_fifo_size; + u32 g_tx_fifo_size[MAX_EPS_CHANNELS]; + + bool change_speed_quirk; +}; + +/** + * struct dwc2_hw_params - Autodetected parameters. + * + * These parameters are the various parameters read from hardware + * registers during initialization. They typically contain the best + * supported or maximum value that can be configured in the + * corresponding dwc2_core_params value. + * + * The values that are not in dwc2_core_params are documented below. + * + * @op_mode: Mode of Operation + * 0 - HNP- and SRP-Capable OTG (Host & Device) + * 1 - SRP-Capable OTG (Host & Device) + * 2 - Non-HNP and Non-SRP Capable OTG (Host & Device) + * 3 - SRP-Capable Device + * 4 - Non-OTG Device + * 5 - SRP-Capable Host + * 6 - Non-OTG Host + * @arch: Architecture + * 0 - Slave only + * 1 - External DMA + * 2 - Internal DMA + * @ipg_isoc_en: This feature indicates that the controller supports + * the worst-case scenario of Rx followed by Rx + * Interpacket Gap (IPG) (32 bitTimes) as per the utmi + * specification for any token following ISOC OUT token. + * 0 - Don't support + * 1 - Support + * @power_optimized: Are power optimizations enabled? + * @num_dev_ep: Number of device endpoints available + * @num_dev_in_eps: Number of device IN endpoints available + * @num_dev_perio_in_ep: Number of device periodic IN endpoints + * available + * @dev_token_q_depth: Device Mode IN Token Sequence Learning Queue + * Depth + * 0 to 30 + * @host_perio_tx_q_depth: + * Host Mode Periodic Request Queue Depth + * 2, 4 or 8 + * @nperio_tx_q_depth: + * Non-Periodic Request Queue Depth + * 2, 4 or 8 + * @hs_phy_type: High-speed PHY interface type + * 0 - High-speed interface not supported + * 1 - UTMI+ + * 2 - ULPI + * 3 - UTMI+ and ULPI + * @fs_phy_type: Full-speed PHY interface type + * 0 - Full speed interface not supported + * 1 - Dedicated full speed interface + * 2 - FS pins shared with UTMI+ pins + * 3 - FS pins shared with ULPI pins + * @total_fifo_size: Total internal RAM for FIFOs (bytes) + * @hibernation: Is hibernation enabled? + * @utmi_phy_data_width: UTMI+ PHY data width + * 0 - 8 bits + * 1 - 16 bits + * 2 - 8 or 16 bits + * @snpsid: Value from SNPSID register + * @dev_ep_dirs: Direction of device endpoints (GHWCFG1) + * @g_tx_fifo_size: Power-on values of TxFIFO sizes + * @dma_desc_enable: When DMA mode is enabled, specifies whether to use + * address DMA mode or descriptor DMA mode for accessing + * the data FIFOs. The driver will automatically detect the + * value for this if none is specified. + * 0 - Address DMA + * 1 - Descriptor DMA (default, if available) + * @enable_dynamic_fifo: 0 - Use coreConsultant-specified FIFO size parameters + * 1 - Allow dynamic FIFO sizing (default, if available) + * @en_multiple_tx_fifo: Specifies whether dedicated per-endpoint transmit FIFOs + * are enabled for non-periodic IN endpoints in device + * mode. + * @host_nperio_tx_fifo_size: Number of 4-byte words in the non-periodic Tx FIFO + * in host mode when dynamic FIFO sizing is enabled + * 16 to 32768 + * Actual maximum value is autodetected and also + * the default. + * @host_perio_tx_fifo_size: Number of 4-byte words in the periodic Tx FIFO in + * host mode when dynamic FIFO sizing is enabled + * 16 to 32768 + * Actual maximum value is autodetected and also + * the default. + * @max_transfer_size: The maximum transfer size supported, in bytes + * 2047 to 65,535 + * Actual maximum value is autodetected and also + * the default. + * @max_packet_count: The maximum number of packets in a transfer + * 15 to 511 + * Actual maximum value is autodetected and also + * the default. + * @host_channels: The number of host channel registers to use + * 1 to 16 + * Actual maximum value is autodetected and also + * the default. + * @dev_nperio_tx_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 + * Actual maximum value is autodetected and also + * the default. + * @i2c_enable: Specifies whether to use the I2Cinterface for a full + * speed PHY. This parameter is only applicable if phy_type + * is FS. + * 0 - No (default) + * 1 - Yes + * @acg_enable: For enabling Active Clock Gating in the controller + * 0 - Disable + * 1 - Enable + * @lpm_mode: For enabling Link Power Management in the controller + * 0 - Disable + * 1 - Enable + * @rx_fifo_size: Number of 4-byte words in the Rx FIFO when dynamic + * FIFO sizing is enabled 16 to 32768 + * Actual maximum value is autodetected and also + * the default. + * @service_interval_mode: For enabling service interval based scheduling in the + * controller. + * 0 - Disable + * 1 - Enable + */ +struct dwc2_hw_params { + unsigned op_mode:3; + unsigned arch:2; + unsigned dma_desc_enable:1; + unsigned enable_dynamic_fifo:1; + unsigned en_multiple_tx_fifo:1; + unsigned rx_fifo_size:16; + unsigned host_nperio_tx_fifo_size:16; + unsigned dev_nperio_tx_fifo_size:16; + unsigned host_perio_tx_fifo_size:16; + unsigned nperio_tx_q_depth:3; + unsigned host_perio_tx_q_depth:3; + unsigned dev_token_q_depth:5; + unsigned max_transfer_size:26; + unsigned max_packet_count:11; + unsigned host_channels:5; + unsigned hs_phy_type:2; + unsigned fs_phy_type:2; + unsigned i2c_enable:1; + unsigned acg_enable:1; + unsigned num_dev_ep:4; + unsigned num_dev_in_eps : 4; + unsigned num_dev_perio_in_ep:4; + unsigned total_fifo_size:16; + unsigned power_optimized:1; + unsigned hibernation:1; + unsigned utmi_phy_data_width:2; + unsigned lpm_mode:1; + unsigned ipg_isoc_en:1; + unsigned service_interval_mode:1; + u32 snpsid; + u32 dev_ep_dirs; + u32 g_tx_fifo_size[MAX_EPS_CHANNELS]; +}; + +/* Size of control and EP0 buffers */ +#define DWC2_CTRL_BUFF_SIZE 8 + +/** + * struct dwc2_gregs_backup - Holds global registers state before + * entering partial power down + * @gintsts: Backup of GINTSTS register + * @gotgctl: Backup of GOTGCTL register + * @gintmsk: Backup of GINTMSK register + * @gahbcfg: Backup of GAHBCFG register + * @gusbcfg: Backup of GUSBCFG register + * @grxfsiz: Backup of GRXFSIZ register + * @gnptxfsiz: Backup of GNPTXFSIZ register + * @gi2cctl: Backup of GI2CCTL register + * @glpmcfg: Backup of GLPMCFG register + * @gdfifocfg: Backup of GDFIFOCFG register + * @pcgcctl: Backup of PCGCCTL register + * @pcgcctl1: Backup of PCGCCTL1 register + * @dtxfsiz: Backup of DTXFSIZ registers for each endpoint + * @gpwrdn: Backup of GPWRDN register + * @valid: True if registers values backuped. + */ +struct dwc2_gregs_backup { + u32 gintsts; + u32 gotgctl; + u32 gintmsk; + u32 gahbcfg; + u32 gusbcfg; + u32 grxfsiz; + u32 gnptxfsiz; + u32 gi2cctl; + u32 glpmcfg; + u32 pcgcctl; + u32 pcgcctl1; + u32 gdfifocfg; + u32 gpwrdn; + bool valid; +}; + +/** + * struct dwc2_dregs_backup - Holds device registers state before + * entering partial power down + * @dcfg: Backup of DCFG register + * @dctl: Backup of DCTL register + * @daintmsk: Backup of DAINTMSK register + * @diepmsk: Backup of DIEPMSK register + * @doepmsk: Backup of DOEPMSK register + * @diepctl: Backup of DIEPCTL register + * @dieptsiz: Backup of DIEPTSIZ register + * @diepdma: Backup of DIEPDMA register + * @doepctl: Backup of DOEPCTL register + * @doeptsiz: Backup of DOEPTSIZ register + * @doepdma: Backup of DOEPDMA register + * @dtxfsiz: Backup of DTXFSIZ registers for each endpoint + * @valid: True if registers values backuped. + */ +struct dwc2_dregs_backup { + u32 dcfg; + u32 dctl; + u32 daintmsk; + u32 diepmsk; + u32 doepmsk; + u32 diepctl[MAX_EPS_CHANNELS]; + u32 dieptsiz[MAX_EPS_CHANNELS]; + u32 diepdma[MAX_EPS_CHANNELS]; + u32 doepctl[MAX_EPS_CHANNELS]; + u32 doeptsiz[MAX_EPS_CHANNELS]; + u32 doepdma[MAX_EPS_CHANNELS]; + u32 dtxfsiz[MAX_EPS_CHANNELS]; + bool valid; +}; + +/** + * struct dwc2_hregs_backup - Holds host registers state before + * entering partial power down + * @hcfg: Backup of HCFG register + * @hflbaddr: Backup of HFLBADDR register + * @haintmsk: Backup of HAINTMSK register + * @hcchar: Backup of HCCHAR register + * @hcsplt: Backup of HCSPLT register + * @hcintmsk: Backup of HCINTMSK register + * @hctsiz: Backup of HCTSIZ register + * @hdma: Backup of HCDMA register + * @hcdmab: Backup of HCDMAB register + * @hprt0: Backup of HPTR0 register + * @hfir: Backup of HFIR register + * @hptxfsiz: Backup of HPTXFSIZ register + * @valid: True if registers values backuped. + */ +struct dwc2_hregs_backup { + u32 hcfg; + u32 hflbaddr; + u32 haintmsk; + u32 hcchar[MAX_EPS_CHANNELS]; + u32 hcsplt[MAX_EPS_CHANNELS]; + u32 hcintmsk[MAX_EPS_CHANNELS]; + u32 hctsiz[MAX_EPS_CHANNELS]; + u32 hcidma[MAX_EPS_CHANNELS]; + u32 hcidmab[MAX_EPS_CHANNELS]; + u32 hprt0; + u32 hfir; + u32 hptxfsiz; + bool valid; +}; + +/* + * Constants related to high speed periodic scheduling + * + * We have a periodic schedule that is DWC2_HS_SCHEDULE_UFRAMES long. From a + * reservation point of view it's assumed that the schedule goes right back to + * the beginning after the end of the schedule. + * + * What does that mean for scheduling things with a long interval? It means + * we'll reserve time for them in every possible microframe that they could + * ever be scheduled in. ...but we'll still only actually schedule them as + * often as they were requested. + * + * We keep our schedule in a "bitmap" structure. This simplifies having + * to keep track of and merge intervals: we just let the bitmap code do most + * of the heavy lifting. In a way scheduling is much like memory allocation. + * + * We schedule 100us per uframe or 80% of 125us (the maximum amount you're + * supposed to schedule for periodic transfers). That's according to spec. + * + * Note that though we only schedule 80% of each microframe, the bitmap that we + * keep the schedule in is tightly packed (AKA it doesn't have 100us worth of + * space for each uFrame). + * + * Requirements: + * - DWC2_HS_SCHEDULE_UFRAMES must even divide 0x4000 (HFNUM_MAX_FRNUM + 1) + * - DWC2_HS_SCHEDULE_UFRAMES must be 8 times DWC2_LS_SCHEDULE_FRAMES (probably + * could be any multiple of 8 times DWC2_LS_SCHEDULE_FRAMES, but there might + * be bugs). The 8 comes from the USB spec: number of microframes per frame. + */ +#define DWC2_US_PER_UFRAME 125 +#define DWC2_HS_PERIODIC_US_PER_UFRAME 100 + +#define DWC2_HS_SCHEDULE_UFRAMES 8 +#define DWC2_HS_SCHEDULE_US (DWC2_HS_SCHEDULE_UFRAMES * \ + DWC2_HS_PERIODIC_US_PER_UFRAME) + +/* + * Constants related to low speed scheduling + * + * For high speed we schedule every 1us. For low speed that's a bit overkill, + * so we make up a unit called a "slice" that's worth 25us. There are 40 + * slices in a full frame and we can schedule 36 of those (90%) for periodic + * transfers. + * + * Our low speed schedule can be as short as 1 frame or could be longer. When + * we only schedule 1 frame it means that we'll need to reserve a time every + * frame even for things that only transfer very rarely, so something that runs + * every 2048 frames will get time reserved in every frame. Our low speed + * schedule can be longer and we'll be able to handle more overlap, but that + * will come at increased memory cost and increased time to schedule. + * + * Note: one other advantage of a short low speed schedule is that if we mess + * up and miss scheduling we can jump in and use any of the slots that we + * happened to reserve. + * + * With 25 us per slice and 1 frame in the schedule, we only need 4 bytes for + * the schedule. There will be one schedule per TT. + * + * Requirements: + * - DWC2_US_PER_SLICE must evenly divide DWC2_LS_PERIODIC_US_PER_FRAME. + */ +#define DWC2_US_PER_SLICE 25 +#define DWC2_SLICES_PER_UFRAME (DWC2_US_PER_UFRAME / DWC2_US_PER_SLICE) + +#define DWC2_ROUND_US_TO_SLICE(us) \ + (DIV_ROUND_UP((us), DWC2_US_PER_SLICE) * \ + DWC2_US_PER_SLICE) + +#define DWC2_LS_PERIODIC_US_PER_FRAME \ + 900 +#define DWC2_LS_PERIODIC_SLICES_PER_FRAME \ + (DWC2_LS_PERIODIC_US_PER_FRAME / \ + DWC2_US_PER_SLICE) + +#define DWC2_LS_SCHEDULE_FRAMES 1 +#define DWC2_LS_SCHEDULE_SLICES (DWC2_LS_SCHEDULE_FRAMES * \ + DWC2_LS_PERIODIC_SLICES_PER_FRAME) + +/** + * struct dwc2_hsotg - Holds the state of the driver, including the non-periodic + * and periodic schedules + * + * These are common for both host and peripheral modes: + * + * @dev: The struct device pointer + * @regs: Pointer to controller regs + * @hw_params: Parameters that were autodetected from the + * hardware registers + * @params: Parameters that define how the core should be configured + * @op_state: The operational State, during transitions (a_host=> + * a_peripheral and b_device=>b_host) this may not match + * the core, but allows the software to determine + * transitions + * @dr_mode: Requested mode of operation, one of following: + * - USB_DR_MODE_PERIPHERAL + * - USB_DR_MODE_HOST + * - USB_DR_MODE_OTG + * @role_sw: usb_role_switch handle + * @role_sw_default_mode: default operation mode of controller while usb role + * is USB_ROLE_NONE + * @hcd_enabled: Host mode sub-driver initialization indicator. + * @gadget_enabled: Peripheral mode sub-driver initialization indicator. + * @ll_hw_enabled: Status of low-level hardware resources. + * @hibernated: True if core is hibernated + * @in_ppd: True if core is partial power down mode. + * @bus_suspended: True if bus is suspended + * @reset_phy_on_wake: Quirk saying that we should assert PHY reset on a + * remote wakeup. + * @phy_off_for_suspend: Status of whether we turned the PHY off at suspend. + * @need_phy_for_wake: Quirk saying that we should keep the PHY on at + * suspend if we need USB to wake us up. + * @frame_number: Frame number read from the core. For both device + * and host modes. The value ranges are from 0 + * to HFNUM_MAX_FRNUM. + * @phy: The otg phy transceiver structure for phy control. + * @uphy: The otg phy transceiver structure for old USB phy + * control. + * @plat: The platform specific configuration data. This can be + * removed once all SoCs support usb transceiver. + * @supplies: Definition of USB power supplies + * @vbus_supply: Regulator supplying vbus. + * @usb33d: Optional 3.3v regulator used on some stm32 devices to + * supply ID and VBUS detection hardware. + * @lock: Spinlock that protects all the driver data structures + * @priv: Stores a pointer to the struct usb_hcd + * @queuing_high_bandwidth: True if multiple packets of a high-bandwidth + * transfer are in process of being queued + * @srp_success: Stores status of SRP request in the case of a FS PHY + * with an I2C interface + * @wq_otg: Workqueue object used for handling of some interrupts + * @wf_otg: Work object for handling Connector ID Status Change + * interrupt + * @wkp_timer: Timer object for handling Wakeup Detected interrupt + * @lx_state: Lx state of connected device + * @gr_backup: Backup of global registers during suspend + * @dr_backup: Backup of device registers during suspend + * @hr_backup: Backup of host registers during suspend + * @needs_byte_swap: Specifies whether the opposite endianness. + * + * These are for host mode: + * + * @flags: Flags for handling root port state changes + * @flags.d32: Contain all root port flags + * @flags.b: Separate root port flags from each other + * @flags.b.port_connect_status_change: True if root port connect status + * changed + * @flags.b.port_connect_status: True if device connected to root port + * @flags.b.port_reset_change: True if root port reset status changed + * @flags.b.port_enable_change: True if root port enable status changed + * @flags.b.port_suspend_change: True if root port suspend status changed + * @flags.b.port_over_current_change: True if root port over current state + * changed. + * @flags.b.port_l1_change: True if root port l1 status changed + * @flags.b.reserved: Reserved bits of root port register + * @non_periodic_sched_inactive: Inactive QHs in the non-periodic schedule. + * Transfers associated with these QHs are not currently + * assigned to a host channel. + * @non_periodic_sched_active: Active QHs in the non-periodic schedule. + * Transfers associated with these QHs are currently + * assigned to a host channel. + * @non_periodic_qh_ptr: Pointer to next QH to process in the active + * non-periodic schedule + * @non_periodic_sched_waiting: Waiting QHs in the non-periodic schedule. + * Transfers associated with these QHs are not currently + * assigned to a host channel. + * @periodic_sched_inactive: Inactive QHs 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. + * @periodic_sched_ready: 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. + * @periodic_sched_assigned: 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. + * @periodic_sched_queued: 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. + * @split_order: List keeping track of channels doing splits, in order. + * @periodic_usecs: 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. + * @hs_periodic_bitmap: Bitmap used by the microframe scheduler any time the + * host is in high speed mode; low speed schedules are + * stored elsewhere since we need one per TT. + * @periodic_qh_count: Count of periodic QHs, if using several eps. Used for + * SOF enable/disable. + * @free_hc_list: Free host channels in the controller. This is a list of + * struct dwc2_host_chan items. + * @periodic_channels: 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 is available for non-periodic transactions. + * @non_periodic_channels: Number of host channels assigned to non-periodic + * transfers + * @available_host_channels: Number of host channels available for the + * microframe scheduler to use + * @hc_ptr_array: 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. + * @status_buf: Buffer used for data received during the status phase of + * a control transfer. + * @status_buf_dma: DMA address for status_buf + * @start_work: Delayed work for handling host A-cable connection + * @reset_work: Delayed work for handling a port reset + * @phy_reset_work: Work structure for doing a PHY reset + * @otg_port: OTG port number + * @frame_list: Frame list + * @frame_list_dma: Frame list DMA address + * @frame_list_sz: Frame list size + * @desc_gen_cache: Kmem cache for generic descriptors + * @desc_hsisoc_cache: Kmem cache for hs isochronous descriptors + * @unaligned_cache: Kmem cache for DMA mode to handle non-aligned buf + * + * These are for peripheral mode: + * + * @driver: USB gadget driver + * @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. + * @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. + * @ep0_state: EP0 control transfers state + * @delayed_status: true when gadget driver asks for delayed status + * @test_mode: USB test mode requested by the host + * @remote_wakeup_allowed: True if device is allowed to wake-up host by + * remote-wakeup signalling + * @setup_desc_dma: EP0 setup stage desc chain DMA address + * @setup_desc: EP0 setup stage desc chain pointer + * @ctrl_in_desc_dma: EP0 IN data phase desc chain DMA address + * @ctrl_in_desc: EP0 IN data phase desc chain pointer + * @ctrl_out_desc_dma: EP0 OUT data phase desc chain DMA address + * @ctrl_out_desc: EP0 OUT data phase desc chain pointer + * @irq: Interrupt request line number + * @clk: Pointer to otg clock + * @utmi_clk: Pointer to utmi_clk clock + * @reset: Pointer to dwc2 reset controller + * @reset_ecc: Pointer to dwc2 optional reset controller in Stratix10. + * @regset: A pointer to a struct debugfs_regset32, which contains + * a pointer to an array of register definitions, the + * array size and the base address where the register bank + * is to be found. + * @last_frame_num: Number of last frame. Range from 0 to 32768 + * @frame_num_array: Used only if CONFIG_USB_DWC2_TRACK_MISSED_SOFS is + * defined, for missed SOFs tracking. Array holds that + * frame numbers, which not equal to last_frame_num +1 + * @last_frame_num_array: Used only if CONFIG_USB_DWC2_TRACK_MISSED_SOFS is + * defined, for missed SOFs tracking. + * If current_frame_number != last_frame_num+1 + * then last_frame_num added to this array + * @frame_num_idx: Actual size of frame_num_array and last_frame_num_array + * @dumped_frame_num_array: 1 - if missed SOFs frame numbers dumbed + * 0 - if missed SOFs frame numbers not dumbed + * @fifo_mem: Total internal RAM for FIFOs (bytes) + * @fifo_map: Each bit intend for concrete fifo. If that bit is set, + * then that fifo is used + * @gadget: Represents a usb gadget device + * @connected: Used in slave mode. True if device connected with host + * @eps_in: The IN endpoints being supplied to the gadget framework + * @eps_out: The OUT endpoints being supplied to the gadget framework + * @new_connection: Used in host mode. True if there are new connected + * device + * @enabled: Indicates the enabling state of controller + * + */ +struct dwc2_hsotg { + struct device *dev; + void __iomem *regs; + /** Params detected from hardware */ + struct dwc2_hw_params hw_params; + /** Params to actually use */ + struct dwc2_core_params params; + enum usb_otg_state op_state; + enum usb_dr_mode dr_mode; + struct usb_role_switch *role_sw; + enum usb_dr_mode role_sw_default_mode; + unsigned int hcd_enabled:1; + unsigned int gadget_enabled:1; + unsigned int ll_hw_enabled:1; + unsigned int hibernated:1; + unsigned int in_ppd:1; + bool bus_suspended; + unsigned int reset_phy_on_wake:1; + unsigned int need_phy_for_wake:1; + unsigned int phy_off_for_suspend:1; + u16 frame_number; + + struct phy *phy; + struct usb_phy *uphy; + struct dwc2_hsotg_plat *plat; + struct regulator_bulk_data supplies[DWC2_NUM_SUPPLIES]; + struct regulator *vbus_supply; + struct regulator *usb33d; + + spinlock_t lock; + void *priv; + int irq; + struct clk *clk; + struct clk *utmi_clk; + struct reset_control *reset; + struct reset_control *reset_ecc; + + unsigned int queuing_high_bandwidth:1; + unsigned int srp_success:1; + + struct workqueue_struct *wq_otg; + struct work_struct wf_otg; + struct timer_list wkp_timer; + enum dwc2_lx_state lx_state; + struct dwc2_gregs_backup gr_backup; + struct dwc2_dregs_backup dr_backup; + struct dwc2_hregs_backup hr_backup; + + struct dentry *debug_root; + struct debugfs_regset32 *regset; + bool needs_byte_swap; + + /* DWC OTG HW Release versions */ +#define DWC2_CORE_REV_4_30a 0x4f54430a +#define DWC2_CORE_REV_2_71a 0x4f54271a +#define DWC2_CORE_REV_2_72a 0x4f54272a +#define DWC2_CORE_REV_2_80a 0x4f54280a +#define DWC2_CORE_REV_2_90a 0x4f54290a +#define DWC2_CORE_REV_2_91a 0x4f54291a +#define DWC2_CORE_REV_2_92a 0x4f54292a +#define DWC2_CORE_REV_2_94a 0x4f54294a +#define DWC2_CORE_REV_3_00a 0x4f54300a +#define DWC2_CORE_REV_3_10a 0x4f54310a +#define DWC2_CORE_REV_4_00a 0x4f54400a +#define DWC2_CORE_REV_4_20a 0x4f54420a +#define DWC2_CORE_REV_5_00a 0x4f54500a +#define DWC2_FS_IOT_REV_1_00a 0x5531100a +#define DWC2_HS_IOT_REV_1_00a 0x5532100a +#define DWC2_HS_IOT_REV_5_00a 0x5532500a +#define DWC2_CORE_REV_MASK 0x0000ffff + + /* DWC OTG HW Core ID */ +#define DWC2_OTG_ID 0x4f540000 +#define DWC2_FS_IOT_ID 0x55310000 +#define DWC2_HS_IOT_ID 0x55320000 + +#define DWC2_RESTORE_DCTL BIT(0) +#define DWC2_RESTORE_DCFG BIT(1) + +#if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) + union dwc2_hcd_internal_flags { + u32 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:25; + } b; + } flags; + + struct list_head non_periodic_sched_inactive; + struct list_head non_periodic_sched_waiting; + struct list_head non_periodic_sched_active; + struct list_head *non_periodic_qh_ptr; + struct list_head periodic_sched_inactive; + struct list_head periodic_sched_ready; + struct list_head periodic_sched_assigned; + struct list_head periodic_sched_queued; + struct list_head split_order; + u16 periodic_usecs; + DECLARE_BITMAP(hs_periodic_bitmap, DWC2_HS_SCHEDULE_US); + u16 periodic_qh_count; + bool new_connection; + + u16 last_frame_num; + +#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS +#define FRAME_NUM_ARRAY_SIZE 1000 + u16 *frame_num_array; + u16 *last_frame_num_array; + int frame_num_idx; + int dumped_frame_num_array; +#endif + + struct list_head free_hc_list; + int periodic_channels; + int non_periodic_channels; + int available_host_channels; + struct dwc2_host_chan *hc_ptr_array[MAX_EPS_CHANNELS]; + u8 *status_buf; + dma_addr_t status_buf_dma; +#define DWC2_HCD_STATUS_BUF_SIZE 64 + + struct delayed_work start_work; + struct delayed_work reset_work; + struct work_struct phy_reset_work; + u8 otg_port; + u32 *frame_list; + dma_addr_t frame_list_dma; + u32 frame_list_sz; + struct kmem_cache *desc_gen_cache; + struct kmem_cache *desc_hsisoc_cache; + struct kmem_cache *unaligned_cache; +#define DWC2_KMEM_UNALIGNED_BUF_SIZE 1024 + +#endif /* CONFIG_USB_DWC2_HOST || CONFIG_USB_DWC2_DUAL_ROLE */ + +#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) + /* Gadget structures */ + struct usb_gadget_driver *driver; + int fifo_mem; + unsigned int dedicated_fifos:1; + unsigned char num_of_eps; + u32 fifo_map; + + struct usb_request *ep0_reply; + struct usb_request *ctrl_req; + void *ep0_buff; + void *ctrl_buff; + enum dwc2_ep0_state ep0_state; + unsigned delayed_status : 1; + u8 test_mode; + + dma_addr_t setup_desc_dma[2]; + struct dwc2_dma_desc *setup_desc[2]; + dma_addr_t ctrl_in_desc_dma; + struct dwc2_dma_desc *ctrl_in_desc; + dma_addr_t ctrl_out_desc_dma; + struct dwc2_dma_desc *ctrl_out_desc; + + struct usb_gadget gadget; + unsigned int enabled:1; + unsigned int connected:1; + unsigned int remote_wakeup_allowed:1; + struct dwc2_hsotg_ep *eps_in[MAX_EPS_CHANNELS]; + struct dwc2_hsotg_ep *eps_out[MAX_EPS_CHANNELS]; +#endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */ +}; + +/* Normal architectures just use readl/write */ +static inline u32 dwc2_readl(struct dwc2_hsotg *hsotg, u32 offset) +{ + u32 val; + + val = readl(hsotg->regs + offset); + if (hsotg->needs_byte_swap) + return swab32(val); + else + return val; +} + +static inline void dwc2_writel(struct dwc2_hsotg *hsotg, u32 value, u32 offset) +{ + if (hsotg->needs_byte_swap) + writel(swab32(value), hsotg->regs + offset); + else + writel(value, hsotg->regs + offset); + +#ifdef DWC2_LOG_WRITES + pr_info("info:: wrote %08x to %p\n", value, hsotg->regs + offset); +#endif +} + +static inline void dwc2_readl_rep(struct dwc2_hsotg *hsotg, u32 offset, + void *buffer, unsigned int count) +{ + if (count) { + u32 *buf = buffer; + + do { + u32 x = dwc2_readl(hsotg, offset); + *buf++ = x; + } while (--count); + } +} + +static inline void dwc2_writel_rep(struct dwc2_hsotg *hsotg, u32 offset, + const void *buffer, unsigned int count) +{ + if (count) { + const u32 *buf = buffer; + + do { + dwc2_writel(hsotg, *buf++, offset); + } while (--count); + } +} + +/* Reasons for halting a host channel */ +enum dwc2_halt_status { + DWC2_HC_XFER_NO_HALT_STATUS, + DWC2_HC_XFER_COMPLETE, + DWC2_HC_XFER_URB_COMPLETE, + DWC2_HC_XFER_ACK, + DWC2_HC_XFER_NAK, + DWC2_HC_XFER_NYET, + DWC2_HC_XFER_STALL, + DWC2_HC_XFER_XACT_ERR, + DWC2_HC_XFER_FRAME_OVERRUN, + DWC2_HC_XFER_BABBLE_ERR, + DWC2_HC_XFER_DATA_TOGGLE_ERR, + DWC2_HC_XFER_AHB_ERR, + DWC2_HC_XFER_PERIODIC_INCOMPLETE, + DWC2_HC_XFER_URB_DEQUEUE, +}; + +/* Core version information */ +static inline bool dwc2_is_iot(struct dwc2_hsotg *hsotg) +{ + return (hsotg->hw_params.snpsid & 0xfff00000) == 0x55300000; +} + +static inline bool dwc2_is_fs_iot(struct dwc2_hsotg *hsotg) +{ + return (hsotg->hw_params.snpsid & 0xffff0000) == 0x55310000; +} + +static inline bool dwc2_is_hs_iot(struct dwc2_hsotg *hsotg) +{ + return (hsotg->hw_params.snpsid & 0xffff0000) == 0x55320000; +} + +/* + * The following functions support initialization of the core driver component + * and the DWC_otg controller + */ +int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait); +int dwc2_enter_partial_power_down(struct dwc2_hsotg *hsotg); +int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, int rem_wakeup, + bool restore); +int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg, int is_host); +int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, + int reset, int is_host); +void dwc2_init_fs_ls_pclk_sel(struct dwc2_hsotg *hsotg); +int dwc2_phy_init(struct dwc2_hsotg *hsotg, bool select_phy); + +void dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host); +void dwc2_force_dr_mode(struct dwc2_hsotg *hsotg); + +bool dwc2_is_controller_alive(struct dwc2_hsotg *hsotg); + +int dwc2_check_core_version(struct dwc2_hsotg *hsotg); + +/* + * Common core Functions. + * The following functions support managing the DWC_otg controller in either + * device or host mode. + */ +void dwc2_read_packet(struct dwc2_hsotg *hsotg, u8 *dest, u16 bytes); +void dwc2_flush_tx_fifo(struct dwc2_hsotg *hsotg, const int num); +void dwc2_flush_rx_fifo(struct dwc2_hsotg *hsotg); + +void dwc2_enable_global_interrupts(struct dwc2_hsotg *hcd); +void dwc2_disable_global_interrupts(struct dwc2_hsotg *hcd); + +void dwc2_hib_restore_common(struct dwc2_hsotg *hsotg, int rem_wakeup, + int is_host); +int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg); +int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg); + +void dwc2_enable_acg(struct dwc2_hsotg *hsotg); +void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup); + +/* This function should be called on every hardware interrupt. */ +irqreturn_t dwc2_handle_common_intr(int irq, void *dev); + +/* The device ID match table */ +extern const struct of_device_id dwc2_of_match_table[]; +extern const struct acpi_device_id dwc2_acpi_match[]; +extern const struct pci_device_id dwc2_pci_ids[]; + +int dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg); +int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg); + +/* Common polling functions */ +int dwc2_hsotg_wait_bit_set(struct dwc2_hsotg *hs_otg, u32 reg, u32 bit, + u32 timeout); +int dwc2_hsotg_wait_bit_clear(struct dwc2_hsotg *hs_otg, u32 reg, u32 bit, + u32 timeout); +/* Parameters */ +int dwc2_get_hwparams(struct dwc2_hsotg *hsotg); +int dwc2_init_params(struct dwc2_hsotg *hsotg); + +/* + * The following functions check the controller's OTG operation mode + * capability (GHWCFG2.OTG_MODE). + * + * These functions can be used before the internal hsotg->hw_params + * are read in and cached so they always read directly from the + * GHWCFG2 register. + */ +unsigned int dwc2_op_mode(struct dwc2_hsotg *hsotg); +bool dwc2_hw_is_otg(struct dwc2_hsotg *hsotg); +bool dwc2_hw_is_host(struct dwc2_hsotg *hsotg); +bool dwc2_hw_is_device(struct dwc2_hsotg *hsotg); + +/* + * Returns the mode of operation, host or device + */ +static inline int dwc2_is_host_mode(struct dwc2_hsotg *hsotg) +{ + return (dwc2_readl(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) != 0; +} + +static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg) +{ + return (dwc2_readl(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) == 0; +} + +int dwc2_drd_init(struct dwc2_hsotg *hsotg); +void dwc2_drd_suspend(struct dwc2_hsotg *hsotg); +void dwc2_drd_resume(struct dwc2_hsotg *hsotg); +void dwc2_drd_exit(struct dwc2_hsotg *hsotg); + +/* + * Dump core registers and SPRAM + */ +void dwc2_dump_dev_registers(struct dwc2_hsotg *hsotg); +void dwc2_dump_host_registers(struct dwc2_hsotg *hsotg); +void dwc2_dump_global_registers(struct dwc2_hsotg *hsotg); + +/* Gadget defines */ +#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) +int dwc2_hsotg_remove(struct dwc2_hsotg *hsotg); +int dwc2_hsotg_suspend(struct dwc2_hsotg *dwc2); +int dwc2_hsotg_resume(struct dwc2_hsotg *dwc2); +int dwc2_gadget_init(struct dwc2_hsotg *hsotg); +void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, + bool reset); +void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg); +void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg); +void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2); +int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode); +#define dwc2_is_device_connected(hsotg) (hsotg->connected) +#define dwc2_is_device_enabled(hsotg) (hsotg->enabled) +int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg); +int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg, unsigned int flags); +int dwc2_gadget_enter_hibernation(struct dwc2_hsotg *hsotg); +int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg, + int rem_wakeup, int reset); +int dwc2_gadget_enter_partial_power_down(struct dwc2_hsotg *hsotg); +int dwc2_gadget_exit_partial_power_down(struct dwc2_hsotg *hsotg, + bool restore); +void dwc2_gadget_enter_clock_gating(struct dwc2_hsotg *hsotg); +void dwc2_gadget_exit_clock_gating(struct dwc2_hsotg *hsotg, + int rem_wakeup); +int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg); +int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg); +int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg); +void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg); +void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg); +int dwc2_gadget_backup_critical_registers(struct dwc2_hsotg *hsotg); +int dwc2_gadget_restore_critical_registers(struct dwc2_hsotg *hsotg, + unsigned int flags); +static inline void dwc2_clear_fifo_map(struct dwc2_hsotg *hsotg) +{ hsotg->fifo_map = 0; } +#else +static inline int dwc2_hsotg_remove(struct dwc2_hsotg *dwc2) +{ return 0; } +static inline int dwc2_hsotg_suspend(struct dwc2_hsotg *dwc2) +{ return 0; } +static inline int dwc2_hsotg_resume(struct dwc2_hsotg *dwc2) +{ return 0; } +static inline int dwc2_gadget_init(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, + bool reset) {} +static inline void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2) {} +static inline int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, + int testmode) +{ return 0; } +#define dwc2_is_device_connected(hsotg) (0) +#define dwc2_is_device_enabled(hsotg) (0) +static inline int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg, + unsigned int flags) +{ return 0; } +static inline int dwc2_gadget_enter_hibernation(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg, + int rem_wakeup, int reset) +{ return 0; } +static inline int dwc2_gadget_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_gadget_exit_partial_power_down(struct dwc2_hsotg *hsotg, + bool restore) +{ return 0; } +static inline void dwc2_gadget_enter_clock_gating(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_gadget_exit_clock_gating(struct dwc2_hsotg *hsotg, + int rem_wakeup) {} +static inline int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg) {} +static inline int dwc2_gadget_backup_critical_registers(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_gadget_restore_critical_registers(struct dwc2_hsotg *hsotg, + unsigned int flags) +{ return 0; } +static inline void dwc2_clear_fifo_map(struct dwc2_hsotg *hsotg) {} +#endif + +#if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) +int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg); +int dwc2_hcd_get_future_frame_number(struct dwc2_hsotg *hsotg, int us); +void dwc2_hcd_connect(struct dwc2_hsotg *hsotg); +void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force); +void dwc2_hcd_start(struct dwc2_hsotg *hsotg); +int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup); +int dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex); +int dwc2_port_resume(struct dwc2_hsotg *hsotg); +int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg); +int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg); +int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg); +int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, + int rem_wakeup, int reset); +int dwc2_host_enter_partial_power_down(struct dwc2_hsotg *hsotg); +int dwc2_host_exit_partial_power_down(struct dwc2_hsotg *hsotg, + int rem_wakeup, bool restore); +void dwc2_host_enter_clock_gating(struct dwc2_hsotg *hsotg); +void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup); +bool dwc2_host_can_poweroff_phy(struct dwc2_hsotg *dwc2); +int dwc2_host_backup_critical_registers(struct dwc2_hsotg *hsotg); +int dwc2_host_restore_critical_registers(struct dwc2_hsotg *hsotg); +static inline void dwc2_host_schedule_phy_reset(struct dwc2_hsotg *hsotg) +{ schedule_work(&hsotg->phy_reset_work); } +#else +static inline int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_hcd_get_future_frame_number(struct dwc2_hsotg *hsotg, + int us) +{ return 0; } +static inline void dwc2_hcd_connect(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force) {} +static inline void dwc2_hcd_start(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_hcd_remove(struct dwc2_hsotg *hsotg) {} +static inline int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup) +{ return 0; } +static inline int dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) +{ return 0; } +static inline int dwc2_port_resume(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_hcd_init(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, + int rem_wakeup, int reset) +{ return 0; } +static inline int dwc2_host_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_host_exit_partial_power_down(struct dwc2_hsotg *hsotg, + int rem_wakeup, bool restore) +{ return 0; } +static inline void dwc2_host_enter_clock_gating(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg, + int rem_wakeup) {} +static inline bool dwc2_host_can_poweroff_phy(struct dwc2_hsotg *dwc2) +{ return false; } +static inline int dwc2_host_backup_critical_registers(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_host_restore_critical_registers(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline void dwc2_host_schedule_phy_reset(struct dwc2_hsotg *hsotg) {} + +#endif + +#endif /* __DWC2_CORE_H__ */ diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c new file mode 100644 index 000000000..9565e6a52 --- /dev/null +++ b/drivers/usb/dwc2/core_intr.c @@ -0,0 +1,908 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * core_intr.c - DesignWare HS OTG Controller common interrupt handling + * + * Copyright (C) 2004-2013 Synopsys, Inc. + */ + +/* + * This file contains the common interrupt handlers + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "core.h" +#include "hcd.h" + +static const char *dwc2_op_state_str(struct dwc2_hsotg *hsotg) +{ + switch (hsotg->op_state) { + case OTG_STATE_A_HOST: + return "a_host"; + case OTG_STATE_A_SUSPEND: + return "a_suspend"; + case OTG_STATE_A_PERIPHERAL: + return "a_peripheral"; + case OTG_STATE_B_PERIPHERAL: + return "b_peripheral"; + case OTG_STATE_B_HOST: + return "b_host"; + default: + return "unknown"; + } +} + +/** + * dwc2_handle_usb_port_intr - handles OTG PRTINT interrupts. + * When the PRTINT interrupt fires, there are certain status bits in the Host + * Port that needs to get cleared. + * + * @hsotg: Programming view of DWC_otg controller + */ +static void dwc2_handle_usb_port_intr(struct dwc2_hsotg *hsotg) +{ + u32 hprt0 = dwc2_readl(hsotg, HPRT0); + + if (hprt0 & HPRT0_ENACHG) { + hprt0 &= ~HPRT0_ENA; + dwc2_writel(hsotg, hprt0, HPRT0); + } +} + +/** + * dwc2_handle_mode_mismatch_intr() - Logs a mode mismatch warning message + * + * @hsotg: Programming view of DWC_otg controller + */ +static void dwc2_handle_mode_mismatch_intr(struct dwc2_hsotg *hsotg) +{ + /* Clear interrupt */ + dwc2_writel(hsotg, GINTSTS_MODEMIS, GINTSTS); + + dev_warn(hsotg->dev, "Mode Mismatch Interrupt: currently in %s mode\n", + dwc2_is_host_mode(hsotg) ? "Host" : "Device"); +} + +/** + * dwc2_handle_otg_intr() - Handles the OTG Interrupts. It reads the OTG + * Interrupt Register (GOTGINT) to determine what interrupt has occurred. + * + * @hsotg: Programming view of DWC_otg controller + */ +static void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) +{ + u32 gotgint; + u32 gotgctl; + u32 gintmsk; + u32 pcgctl; + + gotgint = dwc2_readl(hsotg, GOTGINT); + gotgctl = dwc2_readl(hsotg, GOTGCTL); + dev_dbg(hsotg->dev, "++OTG Interrupt gotgint=%0x [%s]\n", gotgint, + dwc2_op_state_str(hsotg)); + + if (gotgint & GOTGINT_SES_END_DET) { + dev_dbg(hsotg->dev, + " ++OTG Interrupt: Session End Detected++ (%s)\n", + dwc2_op_state_str(hsotg)); + gotgctl = dwc2_readl(hsotg, GOTGCTL); + + if (dwc2_is_device_mode(hsotg)) { + if (hsotg->params.eusb2_disc) { + /* Clear the Gate hclk. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_GATEHCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + /* Clear Phy Clock bit. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + } + dwc2_hsotg_disconnect(hsotg); + } + + if (hsotg->op_state == OTG_STATE_B_HOST) { + hsotg->op_state = OTG_STATE_B_PERIPHERAL; + } else { + /* + * If not B_HOST and Device HNP still set, HNP did + * not succeed! + */ + if (gotgctl & GOTGCTL_DEVHNPEN) { + dev_dbg(hsotg->dev, "Session End Detected\n"); + dev_err(hsotg->dev, + "Device Not Connected/Responding!\n"); + } + + /* + * If Session End Detected the B-Cable has been + * disconnected + */ + /* Reset to a clean state */ + hsotg->lx_state = DWC2_L3; + } + + gotgctl = dwc2_readl(hsotg, GOTGCTL); + gotgctl &= ~GOTGCTL_DEVHNPEN; + dwc2_writel(hsotg, gotgctl, GOTGCTL); + } + + if (gotgint & GOTGINT_SES_REQ_SUC_STS_CHNG) { + dev_dbg(hsotg->dev, + " ++OTG Interrupt: Session Request Success Status Change++\n"); + gotgctl = dwc2_readl(hsotg, GOTGCTL); + if (gotgctl & GOTGCTL_SESREQSCS) { + if (hsotg->params.phy_type == DWC2_PHY_TYPE_PARAM_FS && + hsotg->params.i2c_enable) { + hsotg->srp_success = 1; + } else { + /* Clear Session Request */ + gotgctl = dwc2_readl(hsotg, GOTGCTL); + gotgctl &= ~GOTGCTL_SESREQ; + dwc2_writel(hsotg, gotgctl, GOTGCTL); + } + } + } + + if (gotgint & GOTGINT_HST_NEG_SUC_STS_CHNG) { + /* + * Print statements during the HNP interrupt handling + * can cause it to fail + */ + gotgctl = dwc2_readl(hsotg, GOTGCTL); + /* + * WA for 3.00a- HW is not setting cur_mode, even sometimes + * this does not help + */ + if (hsotg->hw_params.snpsid >= DWC2_CORE_REV_3_00a) + udelay(100); + if (gotgctl & GOTGCTL_HSTNEGSCS) { + if (dwc2_is_host_mode(hsotg)) { + hsotg->op_state = OTG_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 = dwc2_readl(hsotg, GINTMSK); + gintmsk &= ~GINTSTS_SOF; + dwc2_writel(hsotg, gintmsk, GINTMSK); + + /* + * Call callback function with spin lock + * released + */ + spin_unlock(&hsotg->lock); + + /* Initialize the Core for Host mode */ + dwc2_hcd_start(hsotg); + spin_lock(&hsotg->lock); + hsotg->op_state = OTG_STATE_B_HOST; + } + } else { + gotgctl = dwc2_readl(hsotg, GOTGCTL); + gotgctl &= ~(GOTGCTL_HNPREQ | GOTGCTL_DEVHNPEN); + dwc2_writel(hsotg, gotgctl, GOTGCTL); + dev_dbg(hsotg->dev, "HNP Failed\n"); + dev_err(hsotg->dev, + "Device Not Connected/Responding\n"); + } + } + + if (gotgint & GOTGINT_HST_NEG_DET) { + /* + * 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. + */ + dev_dbg(hsotg->dev, + " ++OTG Interrupt: Host Negotiation Detected++ (%s)\n", + (dwc2_is_host_mode(hsotg) ? "Host" : "Device")); + if (dwc2_is_device_mode(hsotg)) { + dev_dbg(hsotg->dev, "a_suspend->a_peripheral (%d)\n", + hsotg->op_state); + spin_unlock(&hsotg->lock); + dwc2_hcd_disconnect(hsotg, false); + spin_lock(&hsotg->lock); + hsotg->op_state = OTG_STATE_A_PERIPHERAL; + } else { + /* Need to disable SOF interrupt immediately */ + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk &= ~GINTSTS_SOF; + dwc2_writel(hsotg, gintmsk, GINTMSK); + spin_unlock(&hsotg->lock); + dwc2_hcd_start(hsotg); + spin_lock(&hsotg->lock); + hsotg->op_state = OTG_STATE_A_HOST; + } + } + + if (gotgint & GOTGINT_A_DEV_TOUT_CHG) + dev_dbg(hsotg->dev, + " ++OTG Interrupt: A-Device Timeout Change++\n"); + if (gotgint & GOTGINT_DBNCE_DONE) + dev_dbg(hsotg->dev, " ++OTG Interrupt: Debounce Done++\n"); + + /* Clear GOTGINT */ + dwc2_writel(hsotg, gotgint, GOTGINT); +} + +/** + * dwc2_handle_conn_id_status_change_intr() - Handles the Connector ID Status + * Change Interrupt + * + * @hsotg: Programming view of DWC_otg controller + * + * Reads the OTG Interrupt Register (GOTCTL) to determine whether this is a + * Device to Host Mode transition or a Host to Device Mode transition. This only + * occurs when the cable is connected/removed from the PHY connector. + */ +static void dwc2_handle_conn_id_status_change_intr(struct dwc2_hsotg *hsotg) +{ + u32 gintmsk; + + /* Clear interrupt */ + dwc2_writel(hsotg, GINTSTS_CONIDSTSCHNG, GINTSTS); + + /* Need to disable SOF interrupt immediately */ + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk &= ~GINTSTS_SOF; + dwc2_writel(hsotg, gintmsk, GINTMSK); + + dev_dbg(hsotg->dev, " ++Connector ID Status Change Interrupt++ (%s)\n", + dwc2_is_host_mode(hsotg) ? "Host" : "Device"); + + /* + * Need to schedule a work, as there are possible DELAY function calls. + */ + if (hsotg->wq_otg) + queue_work(hsotg->wq_otg, &hsotg->wf_otg); +} + +/** + * dwc2_handle_session_req_intr() - 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 + * + * @hsotg: Programming view of DWC_otg controller + * + * This handler responds by turning on bus power. If the DWC_otg controller is + * in low power mode, this handler brings the controller out of low power mode + * before turning on bus power. + */ +static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) +{ + int ret; + u32 hprt0; + + /* Clear interrupt */ + dwc2_writel(hsotg, GINTSTS_SESSREQINT, GINTSTS); + + dev_dbg(hsotg->dev, "Session request interrupt - lx_state=%d\n", + hsotg->lx_state); + + if (dwc2_is_device_mode(hsotg)) { + if (hsotg->lx_state != DWC2_L0) { + if (hsotg->in_ppd) { + ret = dwc2_exit_partial_power_down(hsotg, 0, + true); + if (ret) + dev_err(hsotg->dev, + "exit power_down failed\n"); + } + + /* Exit gadget mode clock gating. */ + if (hsotg->params.power_down == + DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended && + !hsotg->params.no_clock_gating) + dwc2_gadget_exit_clock_gating(hsotg, 0); + } + + /* + * Report disconnect if there is any previous session + * established + */ + dwc2_hsotg_disconnect(hsotg); + } else { + /* Turn on the port power bit. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_PWR; + dwc2_writel(hsotg, hprt0, HPRT0); + /* Connect hcd after port power is set. */ + dwc2_hcd_connect(hsotg); + } +} + +/** + * dwc2_wakeup_from_lpm_l1 - Exit the device from LPM L1 state + * @hsotg: Programming view of DWC_otg controller + * @remotewakeup: Whether this is a remote wakeup + */ +void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup) +{ + u32 glpmcfg; + u32 pcgctl; + u32 dctl; + + if (hsotg->lx_state != DWC2_L1) { + dev_err(hsotg->dev, "Core isn't in DWC2_L1 state\n"); + return; + } + + glpmcfg = dwc2_readl(hsotg, GLPMCFG); + if (dwc2_is_device_mode(hsotg)) { + dev_dbg(hsotg->dev, "Exit from L1 state, remotewakeup=%d\n", remotewakeup); + glpmcfg &= ~GLPMCFG_ENBLSLPM; + glpmcfg &= ~GLPMCFG_HIRD_THRES_MASK; + dwc2_writel(hsotg, glpmcfg, GLPMCFG); + + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_ENBL_SLEEP_GATING; + dwc2_writel(hsotg, pcgctl, PCGCTL); + + glpmcfg = dwc2_readl(hsotg, GLPMCFG); + if (glpmcfg & GLPMCFG_ENBESL) { + glpmcfg |= GLPMCFG_RSTRSLPSTS; + dwc2_writel(hsotg, glpmcfg, GLPMCFG); + } + + if (remotewakeup) { + if (dwc2_hsotg_wait_bit_set(hsotg, GLPMCFG, GLPMCFG_L1RESUMEOK, 1000)) { + dev_warn(hsotg->dev, "%s: timeout GLPMCFG_L1RESUMEOK\n", __func__); + goto fail; + return; + } + + dctl = dwc2_readl(hsotg, DCTL); + dctl |= DCTL_RMTWKUPSIG; + dwc2_writel(hsotg, dctl, DCTL); + + if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, GINTSTS_WKUPINT, 1000)) { + dev_warn(hsotg->dev, "%s: timeout GINTSTS_WKUPINT\n", __func__); + goto fail; + return; + } + } + + glpmcfg = dwc2_readl(hsotg, GLPMCFG); + if (glpmcfg & GLPMCFG_COREL1RES_MASK || glpmcfg & GLPMCFG_SLPSTS || + glpmcfg & GLPMCFG_L1RESUMEOK) { + goto fail; + return; + } + + /* Inform gadget to exit from L1 */ + call_gadget(hsotg, resume); + /* Change to L0 state */ + hsotg->lx_state = DWC2_L0; + hsotg->bus_suspended = false; +fail: dwc2_gadget_init_lpm(hsotg); + } else { + /* TODO */ + dev_err(hsotg->dev, "Host side LPM is not supported.\n"); + return; + } +} + +/* + * 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. + */ +static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) +{ + int ret; + + /* Clear interrupt */ + dwc2_writel(hsotg, GINTSTS_WKUPINT, GINTSTS); + + dev_dbg(hsotg->dev, "++Resume or Remote Wakeup Detected Interrupt++\n"); + dev_dbg(hsotg->dev, "%s lxstate = %d\n", __func__, hsotg->lx_state); + + if (hsotg->lx_state == DWC2_L1) { + dwc2_wakeup_from_lpm_l1(hsotg, false); + return; + } + + if (dwc2_is_device_mode(hsotg)) { + dev_dbg(hsotg->dev, "DSTS=0x%0x\n", + dwc2_readl(hsotg, DSTS)); + if (hsotg->lx_state == DWC2_L2) { + if (hsotg->in_ppd) { + u32 dctl = dwc2_readl(hsotg, DCTL); + /* Clear Remote Wakeup Signaling */ + dctl &= ~DCTL_RMTWKUPSIG; + dwc2_writel(hsotg, dctl, DCTL); + ret = dwc2_exit_partial_power_down(hsotg, 1, + true); + if (ret) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + call_gadget(hsotg, resume); + } + + /* Exit gadget mode clock gating. */ + if (hsotg->params.power_down == + DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended && + !hsotg->params.no_clock_gating) + dwc2_gadget_exit_clock_gating(hsotg, 0); + } else { + /* Change to L0 state */ + hsotg->lx_state = DWC2_L0; + } + } else { + if (hsotg->lx_state == DWC2_L2) { + if (hsotg->in_ppd) { + ret = dwc2_exit_partial_power_down(hsotg, 1, + true); + if (ret) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + } + + if (hsotg->params.power_down == + DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended && + !hsotg->params.no_clock_gating) + dwc2_host_exit_clock_gating(hsotg, 1); + + /* + * If we've got this quirk then the PHY is stuck upon + * wakeup. Assert reset. This will propagate out and + * eventually we'll re-enumerate the device. Not great + * but the best we can do. We can't call phy_reset() + * at interrupt time but there's no hurry, so we'll + * schedule it for later. + */ + if (hsotg->reset_phy_on_wake) + dwc2_host_schedule_phy_reset(hsotg); + + mod_timer(&hsotg->wkp_timer, + jiffies + msecs_to_jiffies(71)); + } else { + /* Change to L0 state */ + hsotg->lx_state = DWC2_L0; + } + } +} + +/* + * This interrupt indicates that a device has been disconnected from the + * root port + */ +static void dwc2_handle_disconnect_intr(struct dwc2_hsotg *hsotg) +{ + dwc2_writel(hsotg, GINTSTS_DISCONNINT, GINTSTS); + + dev_dbg(hsotg->dev, "++Disconnect Detected Interrupt++ (%s) %s\n", + dwc2_is_host_mode(hsotg) ? "Host" : "Device", + dwc2_op_state_str(hsotg)); + + if (hsotg->op_state == OTG_STATE_A_HOST) + dwc2_hcd_disconnect(hsotg, false); +} + +/* + * 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. + */ +static void dwc2_handle_usb_suspend_intr(struct dwc2_hsotg *hsotg) +{ + u32 dsts; + int ret; + + /* Clear interrupt */ + dwc2_writel(hsotg, GINTSTS_USBSUSP, GINTSTS); + + dev_dbg(hsotg->dev, "USB SUSPEND\n"); + + if (dwc2_is_device_mode(hsotg)) { + /* + * Check the Device status register to determine if the Suspend + * state is active + */ + dsts = dwc2_readl(hsotg, DSTS); + dev_dbg(hsotg->dev, "%s: DSTS=0x%0x\n", __func__, dsts); + dev_dbg(hsotg->dev, + "DSTS.Suspend Status=%d HWCFG4.Power Optimize=%d HWCFG4.Hibernation=%d\n", + !!(dsts & DSTS_SUSPSTS), + hsotg->hw_params.power_optimized, + hsotg->hw_params.hibernation); + + /* Ignore suspend request before enumeration */ + if (!dwc2_is_device_connected(hsotg)) { + dev_dbg(hsotg->dev, + "ignore suspend request before enumeration\n"); + return; + } + if (dsts & DSTS_SUSPSTS) { + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: + ret = dwc2_enter_partial_power_down(hsotg); + if (ret) + dev_err(hsotg->dev, + "enter partial_power_down failed\n"); + + udelay(100); + + /* Ask phy to be suspended */ + if (!IS_ERR_OR_NULL(hsotg->uphy)) + usb_phy_set_suspend(hsotg->uphy, true); + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + ret = dwc2_enter_hibernation(hsotg, 0); + if (ret) + dev_err(hsotg->dev, + "enter hibernation failed\n"); + break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If neither hibernation nor partial power down are supported, + * clock gating is used to save power. + */ + if (!hsotg->params.no_clock_gating) + dwc2_gadget_enter_clock_gating(hsotg); + } + + /* + * Change to L2 (suspend) state before releasing + * spinlock + */ + hsotg->lx_state = DWC2_L2; + + /* Call gadget suspend callback */ + call_gadget(hsotg, suspend); + } + } else { + if (hsotg->op_state == OTG_STATE_A_PERIPHERAL) { + dev_dbg(hsotg->dev, "a_peripheral->a_host\n"); + + /* Change to L2 (suspend) state */ + hsotg->lx_state = DWC2_L2; + /* Clear the a_peripheral flag, back to a_host */ + spin_unlock(&hsotg->lock); + dwc2_hcd_start(hsotg); + spin_lock(&hsotg->lock); + hsotg->op_state = OTG_STATE_A_HOST; + } + } +} + +/** + * dwc2_handle_lpm_intr - GINTSTS_LPMTRANRCVD Interrupt handler + * + * @hsotg: Programming view of DWC_otg controller + * + */ +static void dwc2_handle_lpm_intr(struct dwc2_hsotg *hsotg) +{ + u32 glpmcfg; + u32 pcgcctl; + u32 hird; + u32 hird_thres; + u32 hird_thres_en; + u32 enslpm; + + /* Clear interrupt */ + dwc2_writel(hsotg, GINTSTS_LPMTRANRCVD, GINTSTS); + + glpmcfg = dwc2_readl(hsotg, GLPMCFG); + + if (!(glpmcfg & GLPMCFG_LPMCAP)) { + dev_err(hsotg->dev, "Unexpected LPM interrupt\n"); + return; + } + + hird = (glpmcfg & GLPMCFG_HIRD_MASK) >> GLPMCFG_HIRD_SHIFT; + hird_thres = (glpmcfg & GLPMCFG_HIRD_THRES_MASK & + ~GLPMCFG_HIRD_THRES_EN) >> GLPMCFG_HIRD_THRES_SHIFT; + hird_thres_en = glpmcfg & GLPMCFG_HIRD_THRES_EN; + enslpm = glpmcfg & GLPMCFG_ENBLSLPM; + + if (dwc2_is_device_mode(hsotg)) { + dev_dbg(hsotg->dev, "HIRD_THRES_EN = %d\n", hird_thres_en); + + if (hird_thres_en && hird >= hird_thres) { + dev_dbg(hsotg->dev, "L1 with utmi_l1_suspend_n\n"); + } else if (enslpm) { + dev_dbg(hsotg->dev, "L1 with utmi_sleep_n\n"); + } else { + dev_dbg(hsotg->dev, "Entering Sleep with L1 Gating\n"); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl |= PCGCTL_ENBL_SLEEP_GATING; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + } + /** + * Examine prt_sleep_sts after TL1TokenTetry period max (10 us) + */ + udelay(10); + + glpmcfg = dwc2_readl(hsotg, GLPMCFG); + + if (glpmcfg & GLPMCFG_SLPSTS) { + /* Save the current state */ + hsotg->lx_state = DWC2_L1; + dev_dbg(hsotg->dev, + "Core is in L1 sleep glpmcfg=%08x\n", glpmcfg); + + /* Inform gadget that we are in L1 state */ + call_gadget(hsotg, suspend); + } + } +} + +#define GINTMSK_COMMON (GINTSTS_WKUPINT | GINTSTS_SESSREQINT | \ + GINTSTS_CONIDSTSCHNG | GINTSTS_OTGINT | \ + GINTSTS_MODEMIS | GINTSTS_DISCONNINT | \ + GINTSTS_USBSUSP | GINTSTS_PRTINT | \ + GINTSTS_LPMTRANRCVD) + +/* + * This function returns the Core Interrupt register + */ +static u32 dwc2_read_common_intr(struct dwc2_hsotg *hsotg) +{ + u32 gintsts; + u32 gintmsk; + u32 gahbcfg; + u32 gintmsk_common = GINTMSK_COMMON; + + gintsts = dwc2_readl(hsotg, GINTSTS); + gintmsk = dwc2_readl(hsotg, GINTMSK); + gahbcfg = dwc2_readl(hsotg, GAHBCFG); + + /* If any common interrupts set */ + if (gintsts & gintmsk_common) + dev_dbg(hsotg->dev, "gintsts=%08x gintmsk=%08x\n", + gintsts, gintmsk); + + if (gahbcfg & GAHBCFG_GLBL_INTR_EN) + return gintsts & gintmsk & gintmsk_common; + else + return 0; +} + +/** + * dwc_handle_gpwrdn_disc_det() - Handles the gpwrdn disconnect detect. + * Exits hibernation without restoring registers. + * + * @hsotg: Programming view of DWC_otg controller + * @gpwrdn: GPWRDN register + */ +static inline void dwc_handle_gpwrdn_disc_det(struct dwc2_hsotg *hsotg, + u32 gpwrdn) +{ + u32 gpwrdn_tmp; + + /* Switch-on voltage to the core */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PWRDNSWTCH; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + udelay(5); + + /* Reset core */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PWRDNRSTN; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + udelay(5); + + /* Disable Power Down Clamp */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PWRDNCLMP; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + udelay(5); + + /* Deassert reset core */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp |= GPWRDN_PWRDNRSTN; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + udelay(5); + + /* Disable PMU interrupt */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PMUINTSEL; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + + /* Reset ULPI latch */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_ULPI_LATCH_EN_DURING_HIB_ENTRY; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + + /* De-assert Wakeup Logic */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PMUACTV; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + + hsotg->hibernated = 0; + hsotg->bus_suspended = 0; + + if (gpwrdn & GPWRDN_IDSTS) { + hsotg->op_state = OTG_STATE_B_PERIPHERAL; + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + dwc2_hsotg_core_init_disconnected(hsotg, false); + dwc2_hsotg_core_connect(hsotg); + } else { + hsotg->op_state = OTG_STATE_A_HOST; + + /* Initialize the Core for Host mode */ + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + dwc2_hcd_start(hsotg); + } +} + +/* + * GPWRDN interrupt handler. + * + * The GPWRDN interrupts are those that occur in both Host and + * Device mode while core is in hibernated state. + */ +static int dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg) +{ + u32 gpwrdn; + int linestate; + int ret = 0; + + gpwrdn = dwc2_readl(hsotg, GPWRDN); + /* clear all interrupt */ + dwc2_writel(hsotg, gpwrdn, GPWRDN); + linestate = (gpwrdn & GPWRDN_LINESTATE_MASK) >> GPWRDN_LINESTATE_SHIFT; + dev_dbg(hsotg->dev, + "%s: dwc2_handle_gpwrdwn_intr called gpwrdn= %08x\n", __func__, + gpwrdn); + + if ((gpwrdn & GPWRDN_DISCONN_DET) && + (gpwrdn & GPWRDN_DISCONN_DET_MSK) && !linestate) { + dev_dbg(hsotg->dev, "%s: GPWRDN_DISCONN_DET\n", __func__); + /* + * Call disconnect detect function to exit from + * hibernation + */ + dwc_handle_gpwrdn_disc_det(hsotg, gpwrdn); + } else if ((gpwrdn & GPWRDN_LNSTSCHG) && + (gpwrdn & GPWRDN_LNSTSCHG_MSK) && linestate) { + dev_dbg(hsotg->dev, "%s: GPWRDN_LNSTSCHG\n", __func__); + if (hsotg->hw_params.hibernation && + hsotg->hibernated) { + if (gpwrdn & GPWRDN_IDSTS) { + ret = dwc2_exit_hibernation(hsotg, 0, 0, 0); + if (ret) + dev_err(hsotg->dev, + "exit hibernation failed.\n"); + call_gadget(hsotg, resume); + } else { + ret = dwc2_exit_hibernation(hsotg, 1, 0, 1); + if (ret) + dev_err(hsotg->dev, + "exit hibernation failed.\n"); + } + } + } else if ((gpwrdn & GPWRDN_RST_DET) && + (gpwrdn & GPWRDN_RST_DET_MSK)) { + dev_dbg(hsotg->dev, "%s: GPWRDN_RST_DET\n", __func__); + if (!linestate) { + ret = dwc2_exit_hibernation(hsotg, 0, 1, 0); + if (ret) + dev_err(hsotg->dev, + "exit hibernation failed.\n"); + } + } else if ((gpwrdn & GPWRDN_STS_CHGINT) && + (gpwrdn & GPWRDN_STS_CHGINT_MSK)) { + dev_dbg(hsotg->dev, "%s: GPWRDN_STS_CHGINT\n", __func__); + /* + * As GPWRDN_STS_CHGINT exit from hibernation flow is + * the same as in GPWRDN_DISCONN_DET flow. Call + * disconnect detect helper function to exit from + * hibernation. + */ + dwc_handle_gpwrdn_disc_det(hsotg, gpwrdn); + } + + return ret; +} + +/* + * 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 + * - OTG Interrupt + * - Connector ID Status Change Interrupt + * - Disconnect Interrupt + * - Session Request Interrupt + * - Resume / Remote Wakeup Detected Interrupt + * - Suspend Interrupt + */ +irqreturn_t dwc2_handle_common_intr(int irq, void *dev) +{ + struct dwc2_hsotg *hsotg = dev; + u32 gintsts; + irqreturn_t retval = IRQ_NONE; + + spin_lock(&hsotg->lock); + + if (!dwc2_is_controller_alive(hsotg)) { + dev_warn(hsotg->dev, "Controller is dead\n"); + goto out; + } + + /* Reading current frame number value in device or host modes. */ + if (dwc2_is_device_mode(hsotg)) + hsotg->frame_number = (dwc2_readl(hsotg, DSTS) + & DSTS_SOFFN_MASK) >> DSTS_SOFFN_SHIFT; + else + hsotg->frame_number = (dwc2_readl(hsotg, HFNUM) + & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT; + + gintsts = dwc2_read_common_intr(hsotg); + if (gintsts & ~GINTSTS_PRTINT) + retval = IRQ_HANDLED; + + /* In case of hibernated state gintsts must not work */ + if (hsotg->hibernated) { + dwc2_handle_gpwrdn_intr(hsotg); + retval = IRQ_HANDLED; + goto out; + } + + if (gintsts & GINTSTS_MODEMIS) + dwc2_handle_mode_mismatch_intr(hsotg); + if (gintsts & GINTSTS_OTGINT) + dwc2_handle_otg_intr(hsotg); + if (gintsts & GINTSTS_CONIDSTSCHNG) + dwc2_handle_conn_id_status_change_intr(hsotg); + if (gintsts & GINTSTS_DISCONNINT) + dwc2_handle_disconnect_intr(hsotg); + if (gintsts & GINTSTS_SESSREQINT) + dwc2_handle_session_req_intr(hsotg); + if (gintsts & GINTSTS_WKUPINT) + dwc2_handle_wakeup_detected_intr(hsotg); + if (gintsts & GINTSTS_USBSUSP) + dwc2_handle_usb_suspend_intr(hsotg); + if (gintsts & GINTSTS_LPMTRANRCVD) + dwc2_handle_lpm_intr(hsotg); + + if (gintsts & GINTSTS_PRTINT) { + /* + * The port interrupt occurs while in device mode with HPRT0 + * Port Enable/Disable + */ + if (dwc2_is_device_mode(hsotg)) { + dev_dbg(hsotg->dev, + " --Port interrupt received in Device mode--\n"); + dwc2_handle_usb_port_intr(hsotg); + retval = IRQ_HANDLED; + } + } + +out: + spin_unlock(&hsotg->lock); + return retval; +} diff --git a/drivers/usb/dwc2/debug.h b/drivers/usb/dwc2/debug.h new file mode 100644 index 000000000..47252c56d --- /dev/null +++ b/drivers/usb/dwc2/debug.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * debug.h - Designware USB2 DRD controller debug header + * + * Copyright (C) 2015 Intel Corporation + * Mian Yousaf Kaukab + */ + +#include "core.h" + +#ifdef CONFIG_DEBUG_FS +int dwc2_debugfs_init(struct dwc2_hsotg *hsotg); +void dwc2_debugfs_exit(struct dwc2_hsotg *hsotg); +#else +static inline int dwc2_debugfs_init(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline void dwc2_debugfs_exit(struct dwc2_hsotg *hsotg) +{ } +#endif diff --git a/drivers/usb/dwc2/debugfs.c b/drivers/usb/dwc2/debugfs.c new file mode 100644 index 000000000..3116ac727 --- /dev/null +++ b/drivers/usb/dwc2/debugfs.c @@ -0,0 +1,813 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * debugfs.c - Designware USB2 DRD controller debugfs + * + * Copyright (C) 2015 Intel Corporation + * Mian Yousaf Kaukab + */ + +#include +#include +#include +#include + +#include "core.h" +#include "debug.h" + +#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) + +/** + * testmode_write() - change usb test mode state. + * @file: The file to write to. + * @ubuf: The buffer where user wrote. + * @count: The ubuf size. + * @ppos: Unused parameter. + */ +static ssize_t testmode_write(struct file *file, const char __user *ubuf, size_t + count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct dwc2_hsotg *hsotg = 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 = USB_TEST_J; + else if (!strncmp(buf, "test_k", 6)) + testmode = USB_TEST_K; + else if (!strncmp(buf, "test_se0_nak", 12)) + testmode = USB_TEST_SE0_NAK; + else if (!strncmp(buf, "test_packet", 11)) + testmode = USB_TEST_PACKET; + else if (!strncmp(buf, "test_force_enable", 17)) + testmode = USB_TEST_FORCE_ENABLE; + else + testmode = 0; + + spin_lock_irqsave(&hsotg->lock, flags); + dwc2_hsotg_set_test_mode(hsotg, testmode); + spin_unlock_irqrestore(&hsotg->lock, flags); + return count; +} + +/** + * testmode_show() - debugfs: show usb test mode state + * @s: The seq file to write to. + * @unused: Unused parameter. + * + * This debugfs entry shows which usb test mode is currently enabled. + */ +static int testmode_show(struct seq_file *s, void *unused) +{ + struct dwc2_hsotg *hsotg = s->private; + unsigned long flags; + int dctl; + + spin_lock_irqsave(&hsotg->lock, flags); + dctl = dwc2_readl(hsotg, DCTL); + dctl &= DCTL_TSTCTL_MASK; + dctl >>= DCTL_TSTCTL_SHIFT; + spin_unlock_irqrestore(&hsotg->lock, flags); + + switch (dctl) { + case 0: + seq_puts(s, "no test\n"); + break; + case USB_TEST_J: + seq_puts(s, "test_j\n"); + break; + case USB_TEST_K: + seq_puts(s, "test_k\n"); + break; + case USB_TEST_SE0_NAK: + seq_puts(s, "test_se0_nak\n"); + break; + case USB_TEST_PACKET: + seq_puts(s, "test_packet\n"); + break; + case USB_TEST_FORCE_ENABLE: + seq_puts(s, "test_force_enable\n"); + break; + default: + seq_printf(s, "UNKNOWN %d\n", dctl); + } + + return 0; +} + +static int testmode_open(struct inode *inode, struct file *file) +{ + return single_open(file, testmode_show, inode->i_private); +} + +static const struct file_operations testmode_fops = { + .owner = THIS_MODULE, + .open = testmode_open, + .write = testmode_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * 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 dwc2_hsotg *hsotg = seq->private; + int idx; + + seq_printf(seq, "DCFG=0x%08x, DCTL=0x%08x, DSTS=0x%08x\n", + dwc2_readl(hsotg, DCFG), + dwc2_readl(hsotg, DCTL), + dwc2_readl(hsotg, DSTS)); + + seq_printf(seq, "DIEPMSK=0x%08x, DOEPMASK=0x%08x\n", + dwc2_readl(hsotg, DIEPMSK), dwc2_readl(hsotg, DOEPMSK)); + + seq_printf(seq, "GINTMSK=0x%08x, GINTSTS=0x%08x\n", + dwc2_readl(hsotg, GINTMSK), + dwc2_readl(hsotg, GINTSTS)); + + seq_printf(seq, "DAINTMSK=0x%08x, DAINT=0x%08x\n", + dwc2_readl(hsotg, DAINTMSK), + dwc2_readl(hsotg, DAINT)); + + seq_printf(seq, "GNPTXSTS=0x%08x, GRXSTSR=%08x\n", + dwc2_readl(hsotg, GNPTXSTS), + dwc2_readl(hsotg, GRXSTSR)); + + seq_puts(seq, "\nEndpoint status:\n"); + + for (idx = 0; idx < hsotg->num_of_eps; idx++) { + u32 in, out; + + in = dwc2_readl(hsotg, DIEPCTL(idx)); + out = dwc2_readl(hsotg, DOEPCTL(idx)); + + seq_printf(seq, "ep%d: DIEPCTL=0x%08x, DOEPCTL=0x%08x", + idx, in, out); + + in = dwc2_readl(hsotg, DIEPTSIZ(idx)); + out = dwc2_readl(hsotg, DOEPTSIZ(idx)); + + seq_printf(seq, ", DIEPTSIZ=0x%08x, DOEPTSIZ=0x%08x", + in, out); + + seq_puts(seq, "\n"); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(state); + +/** + * 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 dwc2_hsotg *hsotg = seq->private; + int fifo_count = dwc2_hsotg_tx_fifo_count(hsotg); + u32 val; + int idx; + + seq_puts(seq, "Non-periodic FIFOs:\n"); + seq_printf(seq, "RXFIFO: Size %d\n", dwc2_readl(hsotg, GRXFSIZ)); + + val = dwc2_readl(hsotg, GNPTXFSIZ); + seq_printf(seq, "NPTXFIFO: Size %d, Start 0x%08x\n", + val >> FIFOSIZE_DEPTH_SHIFT, + val & FIFOSIZE_STARTADDR_MASK); + + seq_puts(seq, "\nPeriodic TXFIFOs:\n"); + + for (idx = 1; idx <= fifo_count; idx++) { + val = dwc2_readl(hsotg, DPTXFSIZN(idx)); + + seq_printf(seq, "\tDPTXFIFO%2d: Size %d, Start 0x%08x\n", idx, + val >> FIFOSIZE_DEPTH_SHIFT, + val & FIFOSIZE_STARTADDR_MASK); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(fifo); + +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 dwc2_hsotg_ep *ep = seq->private; + struct dwc2_hsotg *hsotg = ep->parent; + struct dwc2_hsotg_req *req; + 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", + dwc2_readl(hsotg, DIEPCTL(index)), + dwc2_readl(hsotg, DOEPCTL(index))); + + seq_printf(seq, "\tDIEPDMA=0x%08x, DOEPDMA=0x%08x\n", + dwc2_readl(hsotg, DIEPDMA(index)), + dwc2_readl(hsotg, DOEPDMA(index))); + + seq_printf(seq, "\tDIEPINT=0x%08x, DOEPINT=0x%08x\n", + dwc2_readl(hsotg, DIEPINT(index)), + dwc2_readl(hsotg, DOEPINT(index))); + + seq_printf(seq, "\tDIEPTSIZ=0x%08x, DOEPTSIZ=0x%08x\n", + dwc2_readl(hsotg, DIEPTSIZ(index)), + dwc2_readl(hsotg, DOEPTSIZ(index))); + + seq_puts(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_puts(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; +} +DEFINE_SHOW_ATTRIBUTE(ep); + +/** + * dwc2_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 dwc2_hsotg_create_debug(struct dwc2_hsotg *hsotg) +{ + struct dentry *root; + unsigned int epidx; + + root = hsotg->debug_root; + + /* create general state file */ + debugfs_create_file("state", 0444, root, hsotg, &state_fops); + debugfs_create_file("testmode", 0644, root, hsotg, &testmode_fops); + debugfs_create_file("fifo", 0444, root, hsotg, &fifo_fops); + + /* Create one file for each out endpoint */ + for (epidx = 0; epidx < hsotg->num_of_eps; epidx++) { + struct dwc2_hsotg_ep *ep; + + ep = hsotg->eps_out[epidx]; + if (ep) + debugfs_create_file(ep->name, 0444, root, ep, &ep_fops); + } + /* Create one file for each in endpoint. EP0 is handled with out eps */ + for (epidx = 1; epidx < hsotg->num_of_eps; epidx++) { + struct dwc2_hsotg_ep *ep; + + ep = hsotg->eps_in[epidx]; + if (ep) + debugfs_create_file(ep->name, 0444, root, ep, &ep_fops); + } +} +#else +static inline void dwc2_hsotg_create_debug(struct dwc2_hsotg *hsotg) {} +#endif + +/* dwc2_hsotg_delete_debug is removed as cleanup in done in dwc2_debugfs_exit */ + +#define dump_register(nm) \ +{ \ + .name = #nm, \ + .offset = nm, \ +} + +static const struct debugfs_reg32 dwc2_regs[] = { + /* + * Accessing registers like this can trigger mode mismatch interrupt. + * However, according to dwc2 databook, the register access, in this + * case, is completed on the processor bus but is ignored by the core + * and does not affect its operation. + */ + dump_register(GOTGCTL), + dump_register(GOTGINT), + dump_register(GAHBCFG), + dump_register(GUSBCFG), + dump_register(GRSTCTL), + dump_register(GINTSTS), + dump_register(GINTMSK), + dump_register(GRXSTSR), + /* Omit GRXSTSP */ + dump_register(GRXFSIZ), + dump_register(GNPTXFSIZ), + dump_register(GNPTXSTS), + dump_register(GI2CCTL), + dump_register(GPVNDCTL), + dump_register(GGPIO), + dump_register(GUID), + dump_register(GSNPSID), + dump_register(GHWCFG1), + dump_register(GHWCFG2), + dump_register(GHWCFG3), + dump_register(GHWCFG4), + dump_register(GLPMCFG), + dump_register(GPWRDN), + dump_register(GDFIFOCFG), + dump_register(ADPCTL), + dump_register(HPTXFSIZ), + dump_register(DPTXFSIZN(1)), + dump_register(DPTXFSIZN(2)), + dump_register(DPTXFSIZN(3)), + dump_register(DPTXFSIZN(4)), + dump_register(DPTXFSIZN(5)), + dump_register(DPTXFSIZN(6)), + dump_register(DPTXFSIZN(7)), + dump_register(DPTXFSIZN(8)), + dump_register(DPTXFSIZN(9)), + dump_register(DPTXFSIZN(10)), + dump_register(DPTXFSIZN(11)), + dump_register(DPTXFSIZN(12)), + dump_register(DPTXFSIZN(13)), + dump_register(DPTXFSIZN(14)), + dump_register(DPTXFSIZN(15)), + dump_register(DCFG), + dump_register(DCTL), + dump_register(DSTS), + dump_register(DIEPMSK), + dump_register(DOEPMSK), + dump_register(DAINT), + dump_register(DAINTMSK), + dump_register(DTKNQR1), + dump_register(DTKNQR2), + dump_register(DTKNQR3), + dump_register(DTKNQR4), + dump_register(DVBUSDIS), + dump_register(DVBUSPULSE), + dump_register(DIEPCTL(0)), + dump_register(DIEPCTL(1)), + dump_register(DIEPCTL(2)), + dump_register(DIEPCTL(3)), + dump_register(DIEPCTL(4)), + dump_register(DIEPCTL(5)), + dump_register(DIEPCTL(6)), + dump_register(DIEPCTL(7)), + dump_register(DIEPCTL(8)), + dump_register(DIEPCTL(9)), + dump_register(DIEPCTL(10)), + dump_register(DIEPCTL(11)), + dump_register(DIEPCTL(12)), + dump_register(DIEPCTL(13)), + dump_register(DIEPCTL(14)), + dump_register(DIEPCTL(15)), + dump_register(DOEPCTL(0)), + dump_register(DOEPCTL(1)), + dump_register(DOEPCTL(2)), + dump_register(DOEPCTL(3)), + dump_register(DOEPCTL(4)), + dump_register(DOEPCTL(5)), + dump_register(DOEPCTL(6)), + dump_register(DOEPCTL(7)), + dump_register(DOEPCTL(8)), + dump_register(DOEPCTL(9)), + dump_register(DOEPCTL(10)), + dump_register(DOEPCTL(11)), + dump_register(DOEPCTL(12)), + dump_register(DOEPCTL(13)), + dump_register(DOEPCTL(14)), + dump_register(DOEPCTL(15)), + dump_register(DIEPINT(0)), + dump_register(DIEPINT(1)), + dump_register(DIEPINT(2)), + dump_register(DIEPINT(3)), + dump_register(DIEPINT(4)), + dump_register(DIEPINT(5)), + dump_register(DIEPINT(6)), + dump_register(DIEPINT(7)), + dump_register(DIEPINT(8)), + dump_register(DIEPINT(9)), + dump_register(DIEPINT(10)), + dump_register(DIEPINT(11)), + dump_register(DIEPINT(12)), + dump_register(DIEPINT(13)), + dump_register(DIEPINT(14)), + dump_register(DIEPINT(15)), + dump_register(DOEPINT(0)), + dump_register(DOEPINT(1)), + dump_register(DOEPINT(2)), + dump_register(DOEPINT(3)), + dump_register(DOEPINT(4)), + dump_register(DOEPINT(5)), + dump_register(DOEPINT(6)), + dump_register(DOEPINT(7)), + dump_register(DOEPINT(8)), + dump_register(DOEPINT(9)), + dump_register(DOEPINT(10)), + dump_register(DOEPINT(11)), + dump_register(DOEPINT(12)), + dump_register(DOEPINT(13)), + dump_register(DOEPINT(14)), + dump_register(DOEPINT(15)), + dump_register(DIEPTSIZ(0)), + dump_register(DIEPTSIZ(1)), + dump_register(DIEPTSIZ(2)), + dump_register(DIEPTSIZ(3)), + dump_register(DIEPTSIZ(4)), + dump_register(DIEPTSIZ(5)), + dump_register(DIEPTSIZ(6)), + dump_register(DIEPTSIZ(7)), + dump_register(DIEPTSIZ(8)), + dump_register(DIEPTSIZ(9)), + dump_register(DIEPTSIZ(10)), + dump_register(DIEPTSIZ(11)), + dump_register(DIEPTSIZ(12)), + dump_register(DIEPTSIZ(13)), + dump_register(DIEPTSIZ(14)), + dump_register(DIEPTSIZ(15)), + dump_register(DOEPTSIZ(0)), + dump_register(DOEPTSIZ(1)), + dump_register(DOEPTSIZ(2)), + dump_register(DOEPTSIZ(3)), + dump_register(DOEPTSIZ(4)), + dump_register(DOEPTSIZ(5)), + dump_register(DOEPTSIZ(6)), + dump_register(DOEPTSIZ(7)), + dump_register(DOEPTSIZ(8)), + dump_register(DOEPTSIZ(9)), + dump_register(DOEPTSIZ(10)), + dump_register(DOEPTSIZ(11)), + dump_register(DOEPTSIZ(12)), + dump_register(DOEPTSIZ(13)), + dump_register(DOEPTSIZ(14)), + dump_register(DOEPTSIZ(15)), + dump_register(DIEPDMA(0)), + dump_register(DIEPDMA(1)), + dump_register(DIEPDMA(2)), + dump_register(DIEPDMA(3)), + dump_register(DIEPDMA(4)), + dump_register(DIEPDMA(5)), + dump_register(DIEPDMA(6)), + dump_register(DIEPDMA(7)), + dump_register(DIEPDMA(8)), + dump_register(DIEPDMA(9)), + dump_register(DIEPDMA(10)), + dump_register(DIEPDMA(11)), + dump_register(DIEPDMA(12)), + dump_register(DIEPDMA(13)), + dump_register(DIEPDMA(14)), + dump_register(DIEPDMA(15)), + dump_register(DOEPDMA(0)), + dump_register(DOEPDMA(1)), + dump_register(DOEPDMA(2)), + dump_register(DOEPDMA(3)), + dump_register(DOEPDMA(4)), + dump_register(DOEPDMA(5)), + dump_register(DOEPDMA(6)), + dump_register(DOEPDMA(7)), + dump_register(DOEPDMA(8)), + dump_register(DOEPDMA(9)), + dump_register(DOEPDMA(10)), + dump_register(DOEPDMA(11)), + dump_register(DOEPDMA(12)), + dump_register(DOEPDMA(13)), + dump_register(DOEPDMA(14)), + dump_register(DOEPDMA(15)), + dump_register(DTXFSTS(0)), + dump_register(DTXFSTS(1)), + dump_register(DTXFSTS(2)), + dump_register(DTXFSTS(3)), + dump_register(DTXFSTS(4)), + dump_register(DTXFSTS(5)), + dump_register(DTXFSTS(6)), + dump_register(DTXFSTS(7)), + dump_register(DTXFSTS(8)), + dump_register(DTXFSTS(9)), + dump_register(DTXFSTS(10)), + dump_register(DTXFSTS(11)), + dump_register(DTXFSTS(12)), + dump_register(DTXFSTS(13)), + dump_register(DTXFSTS(14)), + dump_register(DTXFSTS(15)), + dump_register(PCGCTL), + dump_register(HCFG), + dump_register(HFIR), + dump_register(HFNUM), + dump_register(HPTXSTS), + dump_register(HAINT), + dump_register(HAINTMSK), + dump_register(HFLBADDR), + dump_register(HPRT0), + dump_register(HCCHAR(0)), + dump_register(HCCHAR(1)), + dump_register(HCCHAR(2)), + dump_register(HCCHAR(3)), + dump_register(HCCHAR(4)), + dump_register(HCCHAR(5)), + dump_register(HCCHAR(6)), + dump_register(HCCHAR(7)), + dump_register(HCCHAR(8)), + dump_register(HCCHAR(9)), + dump_register(HCCHAR(10)), + dump_register(HCCHAR(11)), + dump_register(HCCHAR(12)), + dump_register(HCCHAR(13)), + dump_register(HCCHAR(14)), + dump_register(HCCHAR(15)), + dump_register(HCSPLT(0)), + dump_register(HCSPLT(1)), + dump_register(HCSPLT(2)), + dump_register(HCSPLT(3)), + dump_register(HCSPLT(4)), + dump_register(HCSPLT(5)), + dump_register(HCSPLT(6)), + dump_register(HCSPLT(7)), + dump_register(HCSPLT(8)), + dump_register(HCSPLT(9)), + dump_register(HCSPLT(10)), + dump_register(HCSPLT(11)), + dump_register(HCSPLT(12)), + dump_register(HCSPLT(13)), + dump_register(HCSPLT(14)), + dump_register(HCSPLT(15)), + dump_register(HCINT(0)), + dump_register(HCINT(1)), + dump_register(HCINT(2)), + dump_register(HCINT(3)), + dump_register(HCINT(4)), + dump_register(HCINT(5)), + dump_register(HCINT(6)), + dump_register(HCINT(7)), + dump_register(HCINT(8)), + dump_register(HCINT(9)), + dump_register(HCINT(10)), + dump_register(HCINT(11)), + dump_register(HCINT(12)), + dump_register(HCINT(13)), + dump_register(HCINT(14)), + dump_register(HCINT(15)), + dump_register(HCINTMSK(0)), + dump_register(HCINTMSK(1)), + dump_register(HCINTMSK(2)), + dump_register(HCINTMSK(3)), + dump_register(HCINTMSK(4)), + dump_register(HCINTMSK(5)), + dump_register(HCINTMSK(6)), + dump_register(HCINTMSK(7)), + dump_register(HCINTMSK(8)), + dump_register(HCINTMSK(9)), + dump_register(HCINTMSK(10)), + dump_register(HCINTMSK(11)), + dump_register(HCINTMSK(12)), + dump_register(HCINTMSK(13)), + dump_register(HCINTMSK(14)), + dump_register(HCINTMSK(15)), + dump_register(HCTSIZ(0)), + dump_register(HCTSIZ(1)), + dump_register(HCTSIZ(2)), + dump_register(HCTSIZ(3)), + dump_register(HCTSIZ(4)), + dump_register(HCTSIZ(5)), + dump_register(HCTSIZ(6)), + dump_register(HCTSIZ(7)), + dump_register(HCTSIZ(8)), + dump_register(HCTSIZ(9)), + dump_register(HCTSIZ(10)), + dump_register(HCTSIZ(11)), + dump_register(HCTSIZ(12)), + dump_register(HCTSIZ(13)), + dump_register(HCTSIZ(14)), + dump_register(HCTSIZ(15)), + dump_register(HCDMA(0)), + dump_register(HCDMA(1)), + dump_register(HCDMA(2)), + dump_register(HCDMA(3)), + dump_register(HCDMA(4)), + dump_register(HCDMA(5)), + dump_register(HCDMA(6)), + dump_register(HCDMA(7)), + dump_register(HCDMA(8)), + dump_register(HCDMA(9)), + dump_register(HCDMA(10)), + dump_register(HCDMA(11)), + dump_register(HCDMA(12)), + dump_register(HCDMA(13)), + dump_register(HCDMA(14)), + dump_register(HCDMA(15)), + dump_register(HCDMAB(0)), + dump_register(HCDMAB(1)), + dump_register(HCDMAB(2)), + dump_register(HCDMAB(3)), + dump_register(HCDMAB(4)), + dump_register(HCDMAB(5)), + dump_register(HCDMAB(6)), + dump_register(HCDMAB(7)), + dump_register(HCDMAB(8)), + dump_register(HCDMAB(9)), + dump_register(HCDMAB(10)), + dump_register(HCDMAB(11)), + dump_register(HCDMAB(12)), + dump_register(HCDMAB(13)), + dump_register(HCDMAB(14)), + dump_register(HCDMAB(15)), +}; + +#define print_param(_seq, _ptr, _param) \ +seq_printf((_seq), "%-30s: %d\n", #_param, (_ptr)->_param) + +#define print_param_hex(_seq, _ptr, _param) \ +seq_printf((_seq), "%-30s: 0x%x\n", #_param, (_ptr)->_param) + +static int params_show(struct seq_file *seq, void *v) +{ + struct dwc2_hsotg *hsotg = seq->private; + struct dwc2_core_params *p = &hsotg->params; + int i; + + print_param(seq, p, otg_caps.hnp_support); + print_param(seq, p, otg_caps.srp_support); + print_param(seq, p, otg_caps.otg_rev); + print_param(seq, p, dma_desc_enable); + print_param(seq, p, dma_desc_fs_enable); + print_param(seq, p, speed); + print_param(seq, p, enable_dynamic_fifo); + print_param(seq, p, en_multiple_tx_fifo); + print_param(seq, p, host_rx_fifo_size); + print_param(seq, p, host_nperio_tx_fifo_size); + print_param(seq, p, host_perio_tx_fifo_size); + print_param(seq, p, max_transfer_size); + print_param(seq, p, max_packet_count); + print_param(seq, p, host_channels); + print_param(seq, p, phy_type); + print_param(seq, p, phy_utmi_width); + print_param(seq, p, eusb2_disc); + print_param(seq, p, phy_ulpi_ddr); + print_param(seq, p, phy_ulpi_ext_vbus); + print_param(seq, p, i2c_enable); + print_param(seq, p, ipg_isoc_en); + print_param(seq, p, ulpi_fs_ls); + print_param(seq, p, host_support_fs_ls_low_power); + print_param(seq, p, host_ls_low_power_phy_clk); + print_param(seq, p, activate_stm_fs_transceiver); + print_param(seq, p, activate_stm_id_vb_detection); + print_param(seq, p, ts_dline); + print_param(seq, p, reload_ctl); + print_param_hex(seq, p, ahbcfg); + print_param(seq, p, uframe_sched); + print_param(seq, p, external_id_pin_ctl); + print_param(seq, p, power_down); + print_param(seq, p, no_clock_gating); + print_param(seq, p, lpm); + print_param(seq, p, lpm_clock_gating); + print_param(seq, p, besl); + print_param(seq, p, hird_threshold_en); + print_param(seq, p, hird_threshold); + print_param(seq, p, service_interval); + print_param(seq, p, host_dma); + print_param(seq, p, g_dma); + print_param(seq, p, g_dma_desc); + print_param(seq, p, g_rx_fifo_size); + print_param(seq, p, g_np_tx_fifo_size); + + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + char str[32]; + + snprintf(str, 32, "g_tx_fifo_size[%d]", i); + seq_printf(seq, "%-30s: %d\n", str, p->g_tx_fifo_size[i]); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(params); + +static int hw_params_show(struct seq_file *seq, void *v) +{ + struct dwc2_hsotg *hsotg = seq->private; + struct dwc2_hw_params *hw = &hsotg->hw_params; + + print_param(seq, hw, op_mode); + print_param(seq, hw, arch); + print_param(seq, hw, dma_desc_enable); + print_param(seq, hw, enable_dynamic_fifo); + print_param(seq, hw, en_multiple_tx_fifo); + print_param(seq, hw, rx_fifo_size); + print_param(seq, hw, host_nperio_tx_fifo_size); + print_param(seq, hw, dev_nperio_tx_fifo_size); + print_param(seq, hw, host_perio_tx_fifo_size); + print_param(seq, hw, nperio_tx_q_depth); + print_param(seq, hw, host_perio_tx_q_depth); + print_param(seq, hw, dev_token_q_depth); + print_param(seq, hw, max_transfer_size); + print_param(seq, hw, max_packet_count); + print_param(seq, hw, host_channels); + print_param(seq, hw, hs_phy_type); + print_param(seq, hw, fs_phy_type); + print_param(seq, hw, i2c_enable); + print_param(seq, hw, num_dev_ep); + print_param(seq, hw, num_dev_perio_in_ep); + print_param(seq, hw, total_fifo_size); + print_param(seq, hw, power_optimized); + print_param(seq, hw, utmi_phy_data_width); + print_param_hex(seq, hw, snpsid); + print_param_hex(seq, hw, dev_ep_dirs); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(hw_params); + +static int dr_mode_show(struct seq_file *seq, void *v) +{ + struct dwc2_hsotg *hsotg = seq->private; + const char *dr_mode = ""; + + device_property_read_string(hsotg->dev, "dr_mode", &dr_mode); + seq_printf(seq, "%s\n", dr_mode); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(dr_mode); + +int dwc2_debugfs_init(struct dwc2_hsotg *hsotg) +{ + int ret; + struct dentry *root; + + root = debugfs_create_dir(dev_name(hsotg->dev), usb_debug_root); + hsotg->debug_root = root; + + debugfs_create_file("params", 0444, root, hsotg, ¶ms_fops); + debugfs_create_file("hw_params", 0444, root, hsotg, &hw_params_fops); + debugfs_create_file("dr_mode", 0444, root, hsotg, &dr_mode_fops); + + /* Add gadget debugfs nodes */ + dwc2_hsotg_create_debug(hsotg); + + hsotg->regset = devm_kzalloc(hsotg->dev, sizeof(*hsotg->regset), + GFP_KERNEL); + if (!hsotg->regset) { + ret = -ENOMEM; + goto err; + } + + hsotg->regset->regs = dwc2_regs; + hsotg->regset->nregs = ARRAY_SIZE(dwc2_regs); + hsotg->regset->base = hsotg->regs; + + debugfs_create_regset32("regdump", 0444, root, hsotg->regset); + + return 0; +err: + debugfs_remove_recursive(hsotg->debug_root); + return ret; +} + +void dwc2_debugfs_exit(struct dwc2_hsotg *hsotg) +{ + debugfs_remove_recursive(hsotg->debug_root); + hsotg->debug_root = NULL; +} diff --git a/drivers/usb/dwc2/drd.c b/drivers/usb/dwc2/drd.c new file mode 100644 index 000000000..1ad8fa3f8 --- /dev/null +++ b/drivers/usb/dwc2/drd.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drd.c - DesignWare USB2 DRD Controller Dual-role support + * + * Copyright (C) 2020 STMicroelectronics + * + * Author(s): Amelie Delaunay + */ + +#include +#include +#include +#include +#include "core.h" + +#define dwc2_ovr_gotgctl(gotgctl) \ + ((gotgctl) |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN | \ + GOTGCTL_DBNCE_FLTR_BYPASS) + +static void dwc2_ovr_init(struct dwc2_hsotg *hsotg) +{ + unsigned long flags; + u32 gotgctl; + + spin_lock_irqsave(&hsotg->lock, flags); + + gotgctl = dwc2_readl(hsotg, GOTGCTL); + dwc2_ovr_gotgctl(gotgctl); + gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL); + if (hsotg->role_sw_default_mode == USB_DR_MODE_HOST) + gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL; + else if (hsotg->role_sw_default_mode == USB_DR_MODE_PERIPHERAL) + gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL; + dwc2_writel(hsotg, gotgctl, GOTGCTL); + + spin_unlock_irqrestore(&hsotg->lock, flags); + + dwc2_force_mode(hsotg, (hsotg->dr_mode == USB_DR_MODE_HOST) || + (hsotg->role_sw_default_mode == USB_DR_MODE_HOST)); +} + +static int dwc2_ovr_avalid(struct dwc2_hsotg *hsotg, bool valid) +{ + u32 gotgctl = dwc2_readl(hsotg, GOTGCTL); + + /* Check if A-Session is already in the right state */ + if ((valid && (gotgctl & GOTGCTL_ASESVLD)) || + (!valid && !(gotgctl & GOTGCTL_ASESVLD))) + return -EALREADY; + + /* Always enable overrides to handle the resume case */ + dwc2_ovr_gotgctl(gotgctl); + + gotgctl &= ~GOTGCTL_BVALOVAL; + if (valid) + gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL; + else + gotgctl &= ~(GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL); + dwc2_writel(hsotg, gotgctl, GOTGCTL); + + return 0; +} + +static int dwc2_ovr_bvalid(struct dwc2_hsotg *hsotg, bool valid) +{ + u32 gotgctl = dwc2_readl(hsotg, GOTGCTL); + + /* Check if B-Session is already in the right state */ + if ((valid && (gotgctl & GOTGCTL_BSESVLD)) || + (!valid && !(gotgctl & GOTGCTL_BSESVLD))) + return -EALREADY; + + /* Always enable overrides to handle the resume case */ + dwc2_ovr_gotgctl(gotgctl); + + gotgctl &= ~GOTGCTL_AVALOVAL; + if (valid) + gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL; + else + gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL); + dwc2_writel(hsotg, gotgctl, GOTGCTL); + + return 0; +} + +static int dwc2_drd_role_sw_set(struct usb_role_switch *sw, enum usb_role role) +{ + struct dwc2_hsotg *hsotg = usb_role_switch_get_drvdata(sw); + unsigned long flags; + int already = 0; + + /* Skip session not in line with dr_mode */ + if ((role == USB_ROLE_DEVICE && hsotg->dr_mode == USB_DR_MODE_HOST) || + (role == USB_ROLE_HOST && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)) + return -EINVAL; + +#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) + /* Skip session if core is in test mode */ + if (role == USB_ROLE_NONE && hsotg->test_mode) { + dev_dbg(hsotg->dev, "Core is in test mode\n"); + return -EBUSY; + } +#endif + + /* + * In case of USB_DR_MODE_PERIPHERAL, clock is disabled at the end of + * the probe and enabled on udc_start. + * If role-switch set is called before the udc_start, we need to enable + * the clock to read/write GOTGCTL and GUSBCFG registers to override + * mode and sessions. It is the case if cable is plugged at boot. + */ + if (!hsotg->ll_hw_enabled && hsotg->clk) { + int ret = clk_prepare_enable(hsotg->clk); + + if (ret) + return ret; + } + + spin_lock_irqsave(&hsotg->lock, flags); + + if (role == USB_ROLE_NONE) { + /* default operation mode when usb role is USB_ROLE_NONE */ + if (hsotg->role_sw_default_mode == USB_DR_MODE_HOST) + role = USB_ROLE_HOST; + else if (hsotg->role_sw_default_mode == USB_DR_MODE_PERIPHERAL) + role = USB_ROLE_DEVICE; + } + + if ((IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)) && + dwc2_is_device_mode(hsotg) && + hsotg->lx_state == DWC2_L2 && + hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE && + hsotg->bus_suspended && + !hsotg->params.no_clock_gating) + dwc2_gadget_exit_clock_gating(hsotg, 0); + + if (role == USB_ROLE_HOST) { + already = dwc2_ovr_avalid(hsotg, true); + } else if (role == USB_ROLE_DEVICE) { + already = dwc2_ovr_bvalid(hsotg, true); + if (dwc2_is_device_enabled(hsotg)) { + /* This clear DCTL.SFTDISCON bit */ + dwc2_hsotg_core_connect(hsotg); + } + } else { + if (dwc2_is_device_mode(hsotg)) { + if (!dwc2_ovr_bvalid(hsotg, false)) + /* This set DCTL.SFTDISCON bit */ + dwc2_hsotg_core_disconnect(hsotg); + } else { + dwc2_ovr_avalid(hsotg, false); + } + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + + if (!already && hsotg->dr_mode == USB_DR_MODE_OTG) + /* This will raise a Connector ID Status Change Interrupt */ + dwc2_force_mode(hsotg, role == USB_ROLE_HOST); + + if (!hsotg->ll_hw_enabled && hsotg->clk) + clk_disable_unprepare(hsotg->clk); + + dev_dbg(hsotg->dev, "%s-session valid\n", + role == USB_ROLE_NONE ? "No" : + role == USB_ROLE_HOST ? "A" : "B"); + + return 0; +} + +int dwc2_drd_init(struct dwc2_hsotg *hsotg) +{ + struct usb_role_switch_desc role_sw_desc = {0}; + struct usb_role_switch *role_sw; + int ret; + + if (!device_property_read_bool(hsotg->dev, "usb-role-switch")) + return 0; + + hsotg->role_sw_default_mode = usb_get_role_switch_default_mode(hsotg->dev); + role_sw_desc.driver_data = hsotg; + role_sw_desc.fwnode = dev_fwnode(hsotg->dev); + role_sw_desc.set = dwc2_drd_role_sw_set; + role_sw_desc.allow_userspace_control = true; + + role_sw = usb_role_switch_register(hsotg->dev, &role_sw_desc); + if (IS_ERR(role_sw)) { + ret = PTR_ERR(role_sw); + dev_err(hsotg->dev, + "failed to register role switch: %d\n", ret); + return ret; + } + + hsotg->role_sw = role_sw; + + /* Enable override and initialize values */ + dwc2_ovr_init(hsotg); + + return 0; +} + +void dwc2_drd_suspend(struct dwc2_hsotg *hsotg) +{ + u32 gintsts, gintmsk; + + if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) { + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk &= ~GINTSTS_CONIDSTSCHNG; + dwc2_writel(hsotg, gintmsk, GINTMSK); + gintsts = dwc2_readl(hsotg, GINTSTS); + dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS); + } +} + +void dwc2_drd_resume(struct dwc2_hsotg *hsotg) +{ + u32 gintsts, gintmsk; + enum usb_role role; + + if (hsotg->role_sw) { + /* get last known role (as the get ops isn't implemented by this driver) */ + role = usb_role_switch_get_role(hsotg->role_sw); + + if (role == USB_ROLE_NONE) { + if (hsotg->role_sw_default_mode == USB_DR_MODE_HOST) + role = USB_ROLE_HOST; + else if (hsotg->role_sw_default_mode == USB_DR_MODE_PERIPHERAL) + role = USB_ROLE_DEVICE; + } + + /* restore last role that may have been lost */ + if (role == USB_ROLE_HOST) + dwc2_ovr_avalid(hsotg, true); + else if (role == USB_ROLE_DEVICE) + dwc2_ovr_bvalid(hsotg, true); + + dwc2_force_mode(hsotg, role == USB_ROLE_HOST); + + dev_dbg(hsotg->dev, "resuming %s-session valid\n", + role == USB_ROLE_NONE ? "No" : + role == USB_ROLE_HOST ? "A" : "B"); + } + + if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) { + gintsts = dwc2_readl(hsotg, GINTSTS); + dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS); + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk |= GINTSTS_CONIDSTSCHNG; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } +} + +void dwc2_drd_exit(struct dwc2_hsotg *hsotg) +{ + if (hsotg->role_sw) + usb_role_switch_unregister(hsotg->role_sw); +} diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c new file mode 100644 index 000000000..102169b40 --- /dev/null +++ b/drivers/usb/dwc2/gadget.c @@ -0,0 +1,5748 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +#include "core.h" +#include "hw.h" + +/* conversion functions */ +static inline struct dwc2_hsotg_req *our_req(struct usb_request *req) +{ + return container_of(req, struct dwc2_hsotg_req, req); +} + +static inline struct dwc2_hsotg_ep *our_ep(struct usb_ep *ep) +{ + return container_of(ep, struct dwc2_hsotg_ep, ep); +} + +static inline struct dwc2_hsotg *to_hsotg(struct usb_gadget *gadget) +{ + return container_of(gadget, struct dwc2_hsotg, gadget); +} + +static inline void dwc2_set_bit(struct dwc2_hsotg *hsotg, u32 offset, u32 val) +{ + dwc2_writel(hsotg, dwc2_readl(hsotg, offset) | val, offset); +} + +static inline void dwc2_clear_bit(struct dwc2_hsotg *hsotg, u32 offset, u32 val) +{ + dwc2_writel(hsotg, dwc2_readl(hsotg, offset) & ~val, offset); +} + +static inline struct dwc2_hsotg_ep *index_to_ep(struct dwc2_hsotg *hsotg, + u32 ep_index, u32 dir_in) +{ + if (dir_in) + return hsotg->eps_in[ep_index]; + else + return hsotg->eps_out[ep_index]; +} + +/* forward declaration of functions */ +static void dwc2_hsotg_dump(struct dwc2_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). + * + * g_using_dma is set depending on dts flag. + */ +static inline bool using_dma(struct dwc2_hsotg *hsotg) +{ + return hsotg->params.g_dma; +} + +/* + * using_desc_dma - return the descriptor DMA status of the driver. + * @hsotg: The driver state. + * + * Return true if we're using descriptor DMA. + */ +static inline bool using_desc_dma(struct dwc2_hsotg *hsotg) +{ + return hsotg->params.g_dma_desc; +} + +/** + * dwc2_gadget_incr_frame_num - Increments the targeted frame number. + * @hs_ep: The endpoint + * + * This function will also check if the frame number overruns DSTS_SOFFN_LIMIT. + * If an overrun occurs it will wrap the value and set the frame_overrun flag. + */ +static inline void dwc2_gadget_incr_frame_num(struct dwc2_hsotg_ep *hs_ep) +{ + struct dwc2_hsotg *hsotg = hs_ep->parent; + u16 limit = DSTS_SOFFN_LIMIT; + + if (hsotg->gadget.speed != USB_SPEED_HIGH) + limit >>= 3; + + hs_ep->target_frame += hs_ep->interval; + if (hs_ep->target_frame > limit) { + hs_ep->frame_overrun = true; + hs_ep->target_frame &= limit; + } else { + hs_ep->frame_overrun = false; + } +} + +/** + * dwc2_gadget_dec_frame_num_by_one - Decrements the targeted frame number + * by one. + * @hs_ep: The endpoint. + * + * This function used in service interval based scheduling flow to calculate + * descriptor frame number filed value. For service interval mode frame + * number in descriptor should point to last (u)frame in the interval. + * + */ +static inline void dwc2_gadget_dec_frame_num_by_one(struct dwc2_hsotg_ep *hs_ep) +{ + struct dwc2_hsotg *hsotg = hs_ep->parent; + u16 limit = DSTS_SOFFN_LIMIT; + + if (hsotg->gadget.speed != USB_SPEED_HIGH) + limit >>= 3; + + if (hs_ep->target_frame) + hs_ep->target_frame -= 1; + else + hs_ep->target_frame = limit; +} + +/** + * dwc2_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 dwc2_hsotg_en_gsint(struct dwc2_hsotg *hsotg, u32 ints) +{ + u32 gsintmsk = dwc2_readl(hsotg, GINTMSK); + u32 new_gsintmsk; + + new_gsintmsk = gsintmsk | ints; + + if (new_gsintmsk != gsintmsk) { + dev_dbg(hsotg->dev, "gsintmsk now 0x%08x\n", new_gsintmsk); + dwc2_writel(hsotg, new_gsintmsk, GINTMSK); + } +} + +/** + * dwc2_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 dwc2_hsotg_disable_gsint(struct dwc2_hsotg *hsotg, u32 ints) +{ + u32 gsintmsk = dwc2_readl(hsotg, GINTMSK); + u32 new_gsintmsk; + + new_gsintmsk = gsintmsk & ~ints; + + if (new_gsintmsk != gsintmsk) + dwc2_writel(hsotg, new_gsintmsk, GINTMSK); +} + +/** + * dwc2_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 dwc2_hsotg_ctrl_epint(struct dwc2_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 = dwc2_readl(hsotg, DAINTMSK); + if (en) + daint |= bit; + else + daint &= ~bit; + dwc2_writel(hsotg, daint, DAINTMSK); + local_irq_restore(flags); +} + +/** + * dwc2_hsotg_tx_fifo_count - return count of TX FIFOs in device mode + * + * @hsotg: Programming view of the DWC_otg controller + */ +int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg) +{ + if (hsotg->hw_params.en_multiple_tx_fifo) + /* In dedicated FIFO mode we need count of IN EPs */ + return hsotg->hw_params.num_dev_in_eps; + else + /* In shared FIFO mode we need count of Periodic IN EPs */ + return hsotg->hw_params.num_dev_perio_in_ep; +} + +/** + * dwc2_hsotg_tx_fifo_total_depth - return total FIFO depth available for + * device mode TX FIFOs + * + * @hsotg: Programming view of the DWC_otg controller + */ +int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg) +{ + int addr; + int tx_addr_max; + u32 np_tx_fifo_size; + + np_tx_fifo_size = min_t(u32, hsotg->hw_params.dev_nperio_tx_fifo_size, + hsotg->params.g_np_tx_fifo_size); + + /* Get Endpoint Info Control block size in DWORDs. */ + tx_addr_max = hsotg->hw_params.total_fifo_size; + + addr = hsotg->params.g_rx_fifo_size + np_tx_fifo_size; + if (tx_addr_max <= addr) + return 0; + + return tx_addr_max - addr; +} + +/** + * dwc2_gadget_wkup_alert_handler - Handler for WKUP_ALERT interrupt + * + * @hsotg: Programming view of the DWC_otg controller + * + */ +static void dwc2_gadget_wkup_alert_handler(struct dwc2_hsotg *hsotg) +{ + u32 gintsts2; + u32 gintmsk2; + + gintsts2 = dwc2_readl(hsotg, GINTSTS2); + gintmsk2 = dwc2_readl(hsotg, GINTMSK2); + gintsts2 &= gintmsk2; + + if (gintsts2 & GINTSTS2_WKUP_ALERT_INT) { + dev_dbg(hsotg->dev, "%s: Wkup_Alert_Int\n", __func__); + dwc2_set_bit(hsotg, GINTSTS2, GINTSTS2_WKUP_ALERT_INT); + dwc2_set_bit(hsotg, DCTL, DCTL_RMTWKUPSIG); + } +} + +/** + * dwc2_hsotg_tx_fifo_average_depth - returns average depth of device mode + * TX FIFOs + * + * @hsotg: Programming view of the DWC_otg controller + */ +int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg) +{ + int tx_fifo_count; + int tx_fifo_depth; + + tx_fifo_depth = dwc2_hsotg_tx_fifo_total_depth(hsotg); + + tx_fifo_count = dwc2_hsotg_tx_fifo_count(hsotg); + + if (!tx_fifo_count) + return tx_fifo_depth; + else + return tx_fifo_depth / tx_fifo_count; +} + +/** + * dwc2_hsotg_init_fifo - initialise non-periodic FIFOs + * @hsotg: The device instance. + */ +static void dwc2_hsotg_init_fifo(struct dwc2_hsotg *hsotg) +{ + unsigned int ep; + unsigned int addr; + int timeout; + + u32 val; + u32 *txfsz = hsotg->params.g_tx_fifo_size; + + /* Reset fifo map if not correctly cleared during previous session */ + WARN_ON(hsotg->fifo_map); + hsotg->fifo_map = 0; + + /* set RX/NPTX FIFO sizes */ + dwc2_writel(hsotg, hsotg->params.g_rx_fifo_size, GRXFSIZ); + dwc2_writel(hsotg, (hsotg->params.g_rx_fifo_size << + FIFOSIZE_STARTADDR_SHIFT) | + (hsotg->params.g_np_tx_fifo_size << FIFOSIZE_DEPTH_SHIFT), + 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 = hsotg->params.g_rx_fifo_size + hsotg->params.g_np_tx_fifo_size; + + /* + * Configure fifos sizes from provided configuration and assign + * them to endpoints dynamically according to maxpacket size value of + * given endpoint. + */ + for (ep = 1; ep < MAX_EPS_CHANNELS; ep++) { + if (!txfsz[ep]) + continue; + val = addr; + val |= txfsz[ep] << FIFOSIZE_DEPTH_SHIFT; + WARN_ONCE(addr + txfsz[ep] > hsotg->fifo_mem, + "insufficient fifo memory"); + addr += txfsz[ep]; + + dwc2_writel(hsotg, val, DPTXFSIZN(ep)); + val = dwc2_readl(hsotg, DPTXFSIZN(ep)); + } + + dwc2_writel(hsotg, hsotg->hw_params.total_fifo_size | + addr << GDFIFOCFG_EPINFOBASE_SHIFT, + GDFIFOCFG); + /* + * according to p428 of the design guide, we need to ensure that + * all fifos are flushed before continuing + */ + + dwc2_writel(hsotg, GRSTCTL_TXFNUM(0x10) | GRSTCTL_TXFFLSH | + GRSTCTL_RXFFLSH, GRSTCTL); + + /* wait until the fifos are both flushed */ + timeout = 100; + while (1) { + val = dwc2_readl(hsotg, 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); + break; + } + + udelay(1); + } + + dev_dbg(hsotg->dev, "FIFOs reset, timeout at %d\n", timeout); +} + +/** + * dwc2_hsotg_ep_alloc_request - allocate USB rerequest structure + * @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 *dwc2_hsotg_ep_alloc_request(struct usb_ep *ep, + gfp_t flags) +{ + struct dwc2_hsotg_req *req; + + req = kzalloc_obj(*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 dwc2_hsotg_ep *hs_ep) +{ + return hs_ep->periodic; +} + +/** + * dwc2_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 dwc2_hsotg_map_dma(), called for the completion + * of a request to ensure the buffer is ready for access by the caller. + */ +static void dwc2_hsotg_unmap_dma(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep, + struct dwc2_hsotg_req *hs_req) +{ + struct usb_request *req = &hs_req->req; + + usb_gadget_unmap_request(&hsotg->gadget, req, hs_ep->map_dir); +} + +/* + * dwc2_gadget_alloc_ctrl_desc_chains - allocate DMA descriptor chains + * for Control endpoint + * @hsotg: The device state. + * + * This function will allocate 4 descriptor chains for EP 0: 2 for + * Setup stage, per one for IN and OUT data/status transactions. + */ +static int dwc2_gadget_alloc_ctrl_desc_chains(struct dwc2_hsotg *hsotg) +{ + hsotg->setup_desc[0] = + dmam_alloc_coherent(hsotg->dev, + sizeof(struct dwc2_dma_desc), + &hsotg->setup_desc_dma[0], + GFP_KERNEL); + if (!hsotg->setup_desc[0]) + goto fail; + + hsotg->setup_desc[1] = + dmam_alloc_coherent(hsotg->dev, + sizeof(struct dwc2_dma_desc), + &hsotg->setup_desc_dma[1], + GFP_KERNEL); + if (!hsotg->setup_desc[1]) + goto fail; + + hsotg->ctrl_in_desc = + dmam_alloc_coherent(hsotg->dev, + sizeof(struct dwc2_dma_desc), + &hsotg->ctrl_in_desc_dma, + GFP_KERNEL); + if (!hsotg->ctrl_in_desc) + goto fail; + + hsotg->ctrl_out_desc = + dmam_alloc_coherent(hsotg->dev, + sizeof(struct dwc2_dma_desc), + &hsotg->ctrl_out_desc_dma, + GFP_KERNEL); + if (!hsotg->ctrl_out_desc) + goto fail; + + return 0; + +fail: + return -ENOMEM; +} + +/** + * dwc2_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 dwc2_hsotg_write_fifo(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep, + struct dwc2_hsotg_req *hs_req) +{ + bool periodic = is_ep_periodic(hs_ep); + u32 gnptxsts = dwc2_readl(hsotg, GNPTXSTS); + int buf_pos = hs_req->req.actual; + int to_write = hs_ep->size_loaded; + void *data; + int can_write; + int pkt_round; + int max_transfer; + + 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 = dwc2_readl(hsotg, 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) { + dwc2_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) { + dwc2_hsotg_en_gsint(hsotg, GINTSTS_PTXFEMP); + return -ENOSPC; + } + } else if (hsotg->dedicated_fifos && hs_ep->index != 0) { + can_write = dwc2_readl(hsotg, + DTXFSTS(hs_ep->fifo_index)); + + can_write &= 0xffff; + can_write *= 4; + } else { + if (GNPTXSTS_NP_TXQ_SPC_AVAIL_GET(gnptxsts) == 0) { + dev_dbg(hsotg->dev, + "%s: no queue slots available (0x%08x)\n", + __func__, gnptxsts); + + dwc2_hsotg_en_gsint(hsotg, GINTSTS_NPTXFEMP); + return -ENOSPC; + } + + can_write = GNPTXSTS_NP_TXF_SPC_AVAIL_GET(gnptxsts); + can_write *= 4; /* fifo size is in 32bit quantities. */ + } + + max_transfer = hs_ep->ep.maxpacket * hs_ep->mc; + + dev_dbg(hsotg->dev, "%s: GNPTXSTS=%08x, can=%d, to=%d, max_transfer %d\n", + __func__, gnptxsts, can_write, to_write, max_transfer); + + /* + * 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 && !periodic) + 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 > max_transfer) { + to_write = max_transfer; + + /* it's needed only when we do not use dedicated fifos */ + if (!hsotg->dedicated_fifos) + dwc2_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 % max_transfer; + + /* + * 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. + */ + + /* it's needed only when we do not use dedicated fifos */ + if (!hsotg->dedicated_fifos) + dwc2_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; + + dwc2_writel_rep(hsotg, 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 int get_ep_limit(struct dwc2_hsotg_ep *hs_ep) +{ + int index = hs_ep->index; + unsigned int maxsize; + unsigned int 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; +} + +/** + * dwc2_hsotg_read_frameno - read current frame number + * @hsotg: The device instance + * + * Return the current frame number + */ +static u32 dwc2_hsotg_read_frameno(struct dwc2_hsotg *hsotg) +{ + u32 dsts; + + dsts = dwc2_readl(hsotg, DSTS); + dsts &= DSTS_SOFFN_MASK; + dsts >>= DSTS_SOFFN_SHIFT; + + return dsts; +} + +/** + * dwc2_gadget_get_chain_limit - get the maximum data payload value of the + * DMA descriptor chain prepared for specific endpoint + * @hs_ep: The endpoint + * + * Return the maximum data that can be queued in one go on a given endpoint + * depending on its descriptor chain capacity so that transfers that + * are too long can be split. + */ +static unsigned int dwc2_gadget_get_chain_limit(struct dwc2_hsotg_ep *hs_ep) +{ + const struct usb_endpoint_descriptor *ep_desc = hs_ep->ep.desc; + int is_isoc = hs_ep->isochronous; + unsigned int maxsize; + u32 mps = hs_ep->ep.maxpacket; + int dir_in = hs_ep->dir_in; + + if (is_isoc) + maxsize = (hs_ep->dir_in ? DEV_DMA_ISOC_TX_NBYTES_LIMIT : + DEV_DMA_ISOC_RX_NBYTES_LIMIT) * + MAX_DMA_DESC_NUM_HS_ISOC; + else + maxsize = DEV_DMA_NBYTES_LIMIT * MAX_DMA_DESC_NUM_GENERIC; + + /* Interrupt OUT EP with mps not multiple of 4 */ + if (hs_ep->index) + if (usb_endpoint_xfer_int(ep_desc) && !dir_in && (mps % 4)) + maxsize = mps * MAX_DMA_DESC_NUM_GENERIC; + + return maxsize; +} + +/* + * dwc2_gadget_get_desc_params - get DMA descriptor parameters. + * @hs_ep: The endpoint + * @mask: RX/TX bytes mask to be defined + * + * Returns maximum data payload for one descriptor after analyzing endpoint + * characteristics. + * DMA descriptor transfer bytes limit depends on EP type: + * Control out - MPS, + * Isochronous - descriptor rx/tx bytes bitfield limit, + * Control In/Bulk/Interrupt - multiple of mps. This will allow to not + * have concatenations from various descriptors within one packet. + * Interrupt OUT - if mps not multiple of 4 then a single packet corresponds + * to a single descriptor. + * + * Selects corresponding mask for RX/TX bytes as well. + */ +static u32 dwc2_gadget_get_desc_params(struct dwc2_hsotg_ep *hs_ep, u32 *mask) +{ + const struct usb_endpoint_descriptor *ep_desc = hs_ep->ep.desc; + u32 mps = hs_ep->ep.maxpacket; + int dir_in = hs_ep->dir_in; + u32 desc_size = 0; + + if (!hs_ep->index && !dir_in) { + desc_size = mps; + *mask = DEV_DMA_NBYTES_MASK; + } else if (hs_ep->isochronous) { + if (dir_in) { + desc_size = DEV_DMA_ISOC_TX_NBYTES_LIMIT; + *mask = DEV_DMA_ISOC_TX_NBYTES_MASK; + } else { + desc_size = DEV_DMA_ISOC_RX_NBYTES_LIMIT; + *mask = DEV_DMA_ISOC_RX_NBYTES_MASK; + } + } else { + desc_size = DEV_DMA_NBYTES_LIMIT; + *mask = DEV_DMA_NBYTES_MASK; + + /* Round down desc_size to be mps multiple */ + desc_size -= desc_size % mps; + } + + /* Interrupt OUT EP with mps not multiple of 4 */ + if (hs_ep->index) + if (usb_endpoint_xfer_int(ep_desc) && !dir_in && (mps % 4)) { + desc_size = mps; + *mask = DEV_DMA_NBYTES_MASK; + } + + return desc_size; +} + +static void dwc2_gadget_fill_nonisoc_xfer_ddma_one(struct dwc2_hsotg_ep *hs_ep, + struct dwc2_dma_desc **desc, + dma_addr_t dma_buff, + unsigned int len, + bool true_last) +{ + int dir_in = hs_ep->dir_in; + u32 mps = hs_ep->ep.maxpacket; + u32 maxsize = 0; + u32 offset = 0; + u32 mask = 0; + int i; + + maxsize = dwc2_gadget_get_desc_params(hs_ep, &mask); + + hs_ep->desc_count = (len / maxsize) + + ((len % maxsize) ? 1 : 0); + if (len == 0) + hs_ep->desc_count = 1; + + for (i = 0; i < hs_ep->desc_count; ++i) { + (*desc)->status = 0; + (*desc)->status |= (DEV_DMA_BUFF_STS_HBUSY + << DEV_DMA_BUFF_STS_SHIFT); + + if (len > maxsize) { + if (!hs_ep->index && !dir_in) + (*desc)->status |= (DEV_DMA_L | DEV_DMA_IOC); + + (*desc)->status |= + maxsize << DEV_DMA_NBYTES_SHIFT & mask; + (*desc)->buf = dma_buff + offset; + + len -= maxsize; + offset += maxsize; + } else { + if (true_last) + (*desc)->status |= (DEV_DMA_L | DEV_DMA_IOC); + + if (dir_in) + (*desc)->status |= (len % mps) ? DEV_DMA_SHORT : + ((hs_ep->send_zlp && true_last) ? + DEV_DMA_SHORT : 0); + + (*desc)->status |= + len << DEV_DMA_NBYTES_SHIFT & mask; + (*desc)->buf = dma_buff + offset; + } + + (*desc)->status &= ~DEV_DMA_BUFF_STS_MASK; + (*desc)->status |= (DEV_DMA_BUFF_STS_HREADY + << DEV_DMA_BUFF_STS_SHIFT); + (*desc)++; + } +} + +/* + * dwc2_gadget_config_nonisoc_xfer_ddma - prepare non ISOC DMA desc chain. + * @hs_ep: The endpoint + * @ureq: Request to transfer + * @offset: offset in bytes + * @len: Length of the transfer + * + * This function will iterate over descriptor chain and fill its entries + * with corresponding information based on transfer data. + */ +static void dwc2_gadget_config_nonisoc_xfer_ddma(struct dwc2_hsotg_ep *hs_ep, + dma_addr_t dma_buff, + unsigned int len) +{ + struct usb_request *ureq = NULL; + struct dwc2_dma_desc *desc = hs_ep->desc_list; + struct scatterlist *sg; + int i; + u8 desc_count = 0; + + if (hs_ep->req) + ureq = &hs_ep->req->req; + + /* non-DMA sg buffer */ + if (!ureq || !ureq->num_sgs) { + dwc2_gadget_fill_nonisoc_xfer_ddma_one(hs_ep, &desc, + dma_buff, len, true); + return; + } + + /* DMA sg buffer */ + for_each_sg(ureq->sg, sg, ureq->num_mapped_sgs, i) { + dwc2_gadget_fill_nonisoc_xfer_ddma_one(hs_ep, &desc, + sg_dma_address(sg) + sg->offset, sg_dma_len(sg), + (i == (ureq->num_mapped_sgs - 1))); + desc_count += hs_ep->desc_count; + } + + hs_ep->desc_count = desc_count; +} + +/* + * dwc2_gadget_fill_isoc_desc - fills next isochronous descriptor in chain. + * @hs_ep: The isochronous endpoint. + * @dma_buff: usb requests dma buffer. + * @len: usb request transfer length. + * + * Fills next free descriptor with the data of the arrived usb request, + * frame info, sets Last and IOC bits increments next_desc. If filled + * descriptor is not the first one, removes L bit from the previous descriptor + * status. + */ +static int dwc2_gadget_fill_isoc_desc(struct dwc2_hsotg_ep *hs_ep, + dma_addr_t dma_buff, unsigned int len) +{ + struct dwc2_dma_desc *desc; + struct dwc2_hsotg *hsotg = hs_ep->parent; + u32 index; + u32 mask = 0; + u8 pid = 0; + + dwc2_gadget_get_desc_params(hs_ep, &mask); + + index = hs_ep->next_desc; + desc = &hs_ep->desc_list[index]; + + /* Check if descriptor chain full */ + if ((desc->status >> DEV_DMA_BUFF_STS_SHIFT) == + DEV_DMA_BUFF_STS_HREADY) { + dev_dbg(hsotg->dev, "%s: desc chain full\n", __func__); + return 1; + } + + /* Clear L bit of previous desc if more than one entries in the chain */ + if (hs_ep->next_desc) + hs_ep->desc_list[index - 1].status &= ~DEV_DMA_L; + + dev_dbg(hsotg->dev, "%s: Filling ep %d, dir %s isoc desc # %d\n", + __func__, hs_ep->index, hs_ep->dir_in ? "in" : "out", index); + + desc->status = 0; + desc->status |= (DEV_DMA_BUFF_STS_HBUSY << DEV_DMA_BUFF_STS_SHIFT); + + desc->buf = dma_buff; + desc->status |= (DEV_DMA_L | DEV_DMA_IOC | + ((len << DEV_DMA_NBYTES_SHIFT) & mask)); + + if (hs_ep->dir_in) { + if (len) + pid = DIV_ROUND_UP(len, hs_ep->ep.maxpacket); + else + pid = 1; + desc->status |= ((pid << DEV_DMA_ISOC_PID_SHIFT) & + DEV_DMA_ISOC_PID_MASK) | + ((len % hs_ep->ep.maxpacket) ? + DEV_DMA_SHORT : 0) | + ((hs_ep->target_frame << + DEV_DMA_ISOC_FRNUM_SHIFT) & + DEV_DMA_ISOC_FRNUM_MASK); + } + + desc->status &= ~DEV_DMA_BUFF_STS_MASK; + desc->status |= (DEV_DMA_BUFF_STS_HREADY << DEV_DMA_BUFF_STS_SHIFT); + + /* Increment frame number by interval for IN */ + if (hs_ep->dir_in) + dwc2_gadget_incr_frame_num(hs_ep); + + /* Update index of last configured entry in the chain */ + hs_ep->next_desc++; + if (hs_ep->next_desc >= MAX_DMA_DESC_NUM_HS_ISOC) + hs_ep->next_desc = 0; + + return 0; +} + +/* + * dwc2_gadget_start_isoc_ddma - start isochronous transfer in DDMA + * @hs_ep: The isochronous endpoint. + * + * Prepare descriptor chain for isochronous endpoints. Afterwards + * write DMA address to HW and enable the endpoint. + */ +static void dwc2_gadget_start_isoc_ddma(struct dwc2_hsotg_ep *hs_ep) +{ + struct dwc2_hsotg *hsotg = hs_ep->parent; + struct dwc2_hsotg_req *hs_req, *treq; + int index = hs_ep->index; + int ret; + int i; + u32 dma_reg; + u32 depctl; + u32 ctrl; + struct dwc2_dma_desc *desc; + + if (list_empty(&hs_ep->queue)) { + hs_ep->target_frame = TARGET_FRAME_INITIAL; + dev_dbg(hsotg->dev, "%s: No requests in queue\n", __func__); + return; + } + + /* Initialize descriptor chain by Host Busy status */ + for (i = 0; i < MAX_DMA_DESC_NUM_HS_ISOC; i++) { + desc = &hs_ep->desc_list[i]; + desc->status = 0; + desc->status |= (DEV_DMA_BUFF_STS_HBUSY + << DEV_DMA_BUFF_STS_SHIFT); + } + + hs_ep->next_desc = 0; + list_for_each_entry_safe(hs_req, treq, &hs_ep->queue, queue) { + dma_addr_t dma_addr = hs_req->req.dma; + + if (hs_req->req.num_sgs) { + WARN_ON(hs_req->req.num_sgs > 1); + dma_addr = sg_dma_address(hs_req->req.sg); + } + ret = dwc2_gadget_fill_isoc_desc(hs_ep, dma_addr, + hs_req->req.length); + if (ret) + break; + } + + hs_ep->compl_desc = 0; + depctl = hs_ep->dir_in ? DIEPCTL(index) : DOEPCTL(index); + dma_reg = hs_ep->dir_in ? DIEPDMA(index) : DOEPDMA(index); + + /* write descriptor chain address to control register */ + dwc2_writel(hsotg, hs_ep->desc_list_dma, dma_reg); + + ctrl = dwc2_readl(hsotg, depctl); + ctrl |= DXEPCTL_EPENA | DXEPCTL_CNAK; + dwc2_writel(hsotg, ctrl, depctl); +} + +static bool dwc2_gadget_target_frame_elapsed(struct dwc2_hsotg_ep *hs_ep); +static void dwc2_hsotg_complete_request(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep, + struct dwc2_hsotg_req *hs_req, + int result); + +/** + * dwc2_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 dwc2_hsotg_start_req(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep, + struct dwc2_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 int length; + unsigned int packets; + unsigned int maxreq; + unsigned int dma_reg; + + 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; + } + } + + dma_reg = dir_in ? DIEPDMA(index) : DOEPDMA(index); + 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__, dwc2_readl(hsotg, epctrl_reg), index, + hs_ep->dir_in ? "in" : "out"); + + /* If endpoint is stalled, we will restart request later */ + ctrl = dwc2_readl(hsotg, epctrl_reg); + + if (index && 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 (!using_desc_dma(hsotg)) + maxreq = get_ep_limit(hs_ep); + else + maxreq = dwc2_gadget_get_chain_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) + if (hs_ep->isochronous) + epsize = DXEPTSIZ_MC(packets); + else + epsize = DXEPTSIZ_MC(1); + else + epsize = 0; + + /* + * zero length packet should be programmed on its own and should not + * be counted in DIEPTSIZ.PktCnt with other packets. + */ + if (dir_in && ureq->zero && !continuing) { + /* Test if zlp is actually required. */ + if ((ureq->length >= hs_ep->ep.maxpacket) && + !(ureq->length % hs_ep->ep.maxpacket)) + hs_ep->send_zlp = 1; + } + + 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; + + if (using_desc_dma(hsotg)) { + u32 offset = 0; + u32 mps = hs_ep->ep.maxpacket; + + /* Adjust length: EP0 - MPS, other OUT EPs - multiple of MPS */ + if (!dir_in) { + if (!index) + length = mps; + else if (length % mps) + length += (mps - (length % mps)); + } + + if (continuing) + offset = ureq->actual; + + /* Fill DDMA chain entries */ + dwc2_gadget_config_nonisoc_xfer_ddma(hs_ep, ureq->dma + offset, + length); + + /* write descriptor chain address to control register */ + dwc2_writel(hsotg, hs_ep->desc_list_dma, dma_reg); + + dev_dbg(hsotg->dev, "%s: %08x pad => 0x%08x\n", + __func__, (u32)hs_ep->desc_list_dma, dma_reg); + } else { + /* write size / packets */ + dwc2_writel(hsotg, epsize, epsize_reg); + + if (using_dma(hsotg) && !continuing && (length != 0)) { + /* + * write DMA address to control register, buffer + * already synced by dwc2_hsotg_ep_queue(). + */ + + dwc2_writel(hsotg, ureq->dma, dma_reg); + + dev_dbg(hsotg->dev, "%s: %pad => 0x%08x\n", + __func__, &ureq->dma, dma_reg); + } + } + + if (hs_ep->isochronous) { + if (!dwc2_gadget_target_frame_elapsed(hs_ep)) { + if (hs_ep->interval == 1) { + if (hs_ep->target_frame & 0x1) + ctrl |= DXEPCTL_SETODDFR; + else + ctrl |= DXEPCTL_SETEVENFR; + } + ctrl |= DXEPCTL_CNAK; + } else { + hs_req->req.frame_number = hs_ep->target_frame; + hs_req->req.actual = 0; + dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, -ENODATA); + return; + } + } + + ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */ + + dev_dbg(hsotg->dev, "ep0 state:%d\n", hsotg->ep0_state); + + /* For Setup request do not clear NAK */ + if (!(index == 0 && hsotg->ep0_state == DWC2_EP0_SETUP)) + ctrl |= DXEPCTL_CNAK; /* clear NAK set by core */ + + dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x\n", __func__, ctrl); + dwc2_writel(hsotg, ctrl, 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; + + dwc2_hsotg_write_fifo(hsotg, hs_ep, hs_req); + } + + /* + * 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 (!(dwc2_readl(hsotg, epctrl_reg) & DXEPCTL_EPENA)) + dev_dbg(hsotg->dev, + "ep%d: failed to become enabled (DXEPCTL=0x%08x)?\n", + index, dwc2_readl(hsotg, epctrl_reg)); + + dev_dbg(hsotg->dev, "%s: DXEPCTL=0x%08x\n", + __func__, dwc2_readl(hsotg, epctrl_reg)); + + /* enable ep interrupts */ + dwc2_hsotg_ctrl_epint(hsotg, hs_ep->index, hs_ep->dir_in, 1); +} + +/** + * dwc2_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 dwc2_hsotg_map_dma(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep, + struct usb_request *req) +{ + int ret; + + hs_ep->map_dir = hs_ep->dir_in; + 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 dwc2_hsotg_handle_unaligned_buf_start(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep, + struct dwc2_hsotg_req *hs_req) +{ + void *req_buf = hs_req->req.buf; + + /* If dma is not being used or buffer is aligned */ + if (!using_dma(hsotg) || !((long)req_buf & 3)) + return 0; + + WARN_ON(hs_req->saved_req_buf); + + dev_dbg(hsotg->dev, "%s: %s: buf=%p length=%d\n", __func__, + hs_ep->ep.name, req_buf, hs_req->req.length); + + hs_req->req.buf = kmalloc(hs_req->req.length, GFP_ATOMIC); + if (!hs_req->req.buf) { + hs_req->req.buf = req_buf; + dev_err(hsotg->dev, + "%s: unable to allocate memory for bounce buffer\n", + __func__); + return -ENOMEM; + } + + /* Save actual buffer */ + hs_req->saved_req_buf = req_buf; + + if (hs_ep->dir_in) + memcpy(hs_req->req.buf, req_buf, hs_req->req.length); + return 0; +} + +static void +dwc2_hsotg_handle_unaligned_buf_complete(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep, + struct dwc2_hsotg_req *hs_req) +{ + /* If dma is not being used or buffer was aligned */ + if (!using_dma(hsotg) || !hs_req->saved_req_buf) + return; + + dev_dbg(hsotg->dev, "%s: %s: status=%d actual-length=%d\n", __func__, + hs_ep->ep.name, hs_req->req.status, hs_req->req.actual); + + /* Copy data from bounce buffer on successful out transfer */ + if (!hs_ep->dir_in && !hs_req->req.status) + memcpy(hs_req->saved_req_buf, hs_req->req.buf, + hs_req->req.actual); + + /* Free bounce buffer */ + kfree(hs_req->req.buf); + + hs_req->req.buf = hs_req->saved_req_buf; + hs_req->saved_req_buf = NULL; +} + +/** + * dwc2_gadget_target_frame_elapsed - Checks target frame + * @hs_ep: The driver endpoint to check + * + * Returns 1 if targeted frame elapsed. If returned 1 then we need to drop + * corresponding transfer. + */ +static bool dwc2_gadget_target_frame_elapsed(struct dwc2_hsotg_ep *hs_ep) +{ + struct dwc2_hsotg *hsotg = hs_ep->parent; + u32 target_frame = hs_ep->target_frame; + u32 current_frame = hsotg->frame_number; + bool frame_overrun = hs_ep->frame_overrun; + u16 limit = DSTS_SOFFN_LIMIT; + + if (hsotg->gadget.speed != USB_SPEED_HIGH) + limit >>= 3; + + if (!frame_overrun && current_frame >= target_frame) + return true; + + if (frame_overrun && current_frame >= target_frame && + ((current_frame - target_frame) < limit / 2)) + return true; + + return false; +} + +/* + * dwc2_gadget_set_ep0_desc_chain - Set EP's desc chain pointers + * @hsotg: The driver state + * @hs_ep: the ep descriptor chain is for + * + * Called to update EP0 structure's pointers depend on stage of + * control transfer. + */ +static int dwc2_gadget_set_ep0_desc_chain(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep) +{ + switch (hsotg->ep0_state) { + case DWC2_EP0_SETUP: + case DWC2_EP0_STATUS_OUT: + hs_ep->desc_list = hsotg->setup_desc[0]; + hs_ep->desc_list_dma = hsotg->setup_desc_dma[0]; + break; + case DWC2_EP0_DATA_IN: + case DWC2_EP0_STATUS_IN: + hs_ep->desc_list = hsotg->ctrl_in_desc; + hs_ep->desc_list_dma = hsotg->ctrl_in_desc_dma; + break; + case DWC2_EP0_DATA_OUT: + hs_ep->desc_list = hsotg->ctrl_out_desc; + hs_ep->desc_list_dma = hsotg->ctrl_out_desc_dma; + break; + default: + dev_err(hsotg->dev, "invalid EP 0 state in queue %d\n", + hsotg->ep0_state); + return -EINVAL; + } + + return 0; +} + +static int dwc2_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, + gfp_t gfp_flags) +{ + struct dwc2_hsotg_req *hs_req = our_req(req); + struct dwc2_hsotg_ep *hs_ep = our_ep(ep); + struct dwc2_hsotg *hs = hs_ep->parent; + bool first; + int ret; + u32 maxsize = 0; + u32 mask = 0; + + + 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); + + if (hs->lx_state == DWC2_L1) { + dwc2_wakeup_from_lpm_l1(hs, true); + } + + /* Prevent new request submission when controller is suspended */ + if (hs->lx_state != DWC2_L0) { + dev_dbg(hs->dev, "%s: submit request only in active state\n", + __func__); + return -EAGAIN; + } + + /* initialise status of the request */ + INIT_LIST_HEAD(&hs_req->queue); + req->actual = 0; + req->status = -EINPROGRESS; + + /* Don't queue ISOC request if length greater than mps*mc */ + if (hs_ep->isochronous && + req->length > (hs_ep->mc * hs_ep->ep.maxpacket)) { + dev_err(hs->dev, "req length > maxpacket*mc\n"); + return -EINVAL; + } + + /* In DDMA mode for ISOC's don't queue request if length greater + * than descriptor limits. + */ + if (using_desc_dma(hs) && hs_ep->isochronous) { + maxsize = dwc2_gadget_get_desc_params(hs_ep, &mask); + if (hs_ep->dir_in && req->length > maxsize) { + dev_err(hs->dev, "wrong length %d (maxsize=%d)\n", + req->length, maxsize); + return -EINVAL; + } + + if (!hs_ep->dir_in && req->length > hs_ep->ep.maxpacket) { + dev_err(hs->dev, "ISOC OUT: wrong length %d (mps=%d)\n", + req->length, hs_ep->ep.maxpacket); + return -EINVAL; + } + } + + ret = dwc2_hsotg_handle_unaligned_buf_start(hs, hs_ep, hs_req); + if (ret) + return ret; + + /* if we're using DMA, sync the buffers as necessary */ + if (using_dma(hs)) { + ret = dwc2_hsotg_map_dma(hs, hs_ep, req); + if (ret) + return ret; + } + /* If using descriptor DMA configure EP0 descriptor chain pointers */ + if (using_desc_dma(hs) && !hs_ep->index) { + ret = dwc2_gadget_set_ep0_desc_chain(hs, hs_ep); + if (ret) + return ret; + } + + first = list_empty(&hs_ep->queue); + list_add_tail(&hs_req->queue, &hs_ep->queue); + + /* + * Handle DDMA isochronous transfers separately - just add new entry + * to the descriptor chain. + * Transfer will be started once SW gets either one of NAK or + * OutTknEpDis interrupts. + */ + if (using_desc_dma(hs) && hs_ep->isochronous) { + if (hs_ep->target_frame != TARGET_FRAME_INITIAL) { + dma_addr_t dma_addr = hs_req->req.dma; + + if (hs_req->req.num_sgs) { + WARN_ON(hs_req->req.num_sgs > 1); + dma_addr = sg_dma_address(hs_req->req.sg); + } + dwc2_gadget_fill_isoc_desc(hs_ep, dma_addr, + hs_req->req.length); + } + return 0; + } + + /* Change EP direction if status phase request is after data out */ + if (!hs_ep->index && !req->length && !hs_ep->dir_in && + hs->ep0_state == DWC2_EP0_DATA_OUT) + hs_ep->dir_in = 1; + + if (first) { + if (!hs_ep->isochronous) { + dwc2_hsotg_start_req(hs, hs_ep, hs_req, false); + return 0; + } + + /* Update current frame number value. */ + hs->frame_number = dwc2_hsotg_read_frameno(hs); + while (dwc2_gadget_target_frame_elapsed(hs_ep)) { + dwc2_gadget_incr_frame_num(hs_ep); + /* Update current frame number value once more as it + * changes here. + */ + hs->frame_number = dwc2_hsotg_read_frameno(hs); + } + + if (hs_ep->target_frame != TARGET_FRAME_INITIAL) + dwc2_hsotg_start_req(hs, hs_ep, hs_req, false); + } + return 0; +} + +static int dwc2_hsotg_ep_queue_lock(struct usb_ep *ep, struct usb_request *req, + gfp_t gfp_flags) +{ + struct dwc2_hsotg_ep *hs_ep = our_ep(ep); + struct dwc2_hsotg *hs = hs_ep->parent; + unsigned long flags; + int ret; + + spin_lock_irqsave(&hs->lock, flags); + ret = dwc2_hsotg_ep_queue(ep, req, gfp_flags); + spin_unlock_irqrestore(&hs->lock, flags); + + return ret; +} + +static void dwc2_hsotg_ep_free_request(struct usb_ep *ep, + struct usb_request *req) +{ + struct dwc2_hsotg_req *hs_req = our_req(req); + + kfree(hs_req); +} + +/** + * dwc2_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 dwc2_hsotg_complete_oursetup(struct usb_ep *ep, + struct usb_request *req) +{ + struct dwc2_hsotg_ep *hs_ep = our_ep(ep); + struct dwc2_hsotg *hsotg = hs_ep->parent; + + dev_dbg(hsotg->dev, "%s: ep %p, req %p\n", __func__, ep, req); + + dwc2_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 dwc2_hsotg_ep *ep_from_windex(struct dwc2_hsotg *hsotg, + u32 windex) +{ + 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; + + return index_to_ep(hsotg, idx, dir); +} + +/** + * dwc2_hsotg_set_test_mode - Enable usb Test Modes + * @hsotg: The driver state. + * @testmode: requested usb test mode + * Enable usb Test Mode requested by the Host. + */ +int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode) +{ + int dctl = dwc2_readl(hsotg, DCTL); + + dctl &= ~DCTL_TSTCTL_MASK; + switch (testmode) { + case USB_TEST_J: + case USB_TEST_K: + case USB_TEST_SE0_NAK: + case USB_TEST_PACKET: + case USB_TEST_FORCE_ENABLE: + dctl |= testmode << DCTL_TSTCTL_SHIFT; + break; + default: + return -EINVAL; + } + dwc2_writel(hsotg, dctl, DCTL); + return 0; +} + +/** + * dwc2_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 dwc2_hsotg_send_reply(struct dwc2_hsotg *hsotg, + struct dwc2_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 = dwc2_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; + /* + * zero flag is for sending zlp in DATA IN stage. It has no impact on + * STATUS stage. + */ + req->zero = 0; + req->complete = dwc2_hsotg_complete_oursetup; + + if (length) + memcpy(req->buf, buff, length); + + ret = dwc2_hsotg_ep_queue(&ep->ep, req, GFP_ATOMIC); + if (ret) { + dev_warn(hsotg->dev, "%s: cannot queue req\n", __func__); + return ret; + } + + return 0; +} + +/** + * dwc2_hsotg_process_req_status - process request GET_STATUS + * @hsotg: The device state + * @ctrl: USB control request + */ +static int dwc2_hsotg_process_req_status(struct dwc2_hsotg *hsotg, + struct usb_ctrlrequest *ctrl) +{ + struct dwc2_hsotg_ep *ep0 = hsotg->eps_out[0]; + struct dwc2_hsotg_ep *ep; + __le16 reply; + u16 status; + 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: + status = hsotg->gadget.is_selfpowered << + USB_DEVICE_SELF_POWERED; + status |= hsotg->remote_wakeup_allowed << + USB_DEVICE_REMOTE_WAKEUP; + reply = cpu_to_le16(status); + 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 = dwc2_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 dwc2_hsotg_ep_sethalt(struct usb_ep *ep, int value, bool now); + +/** + * 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 dwc2_hsotg_req *get_ep_head(struct dwc2_hsotg_ep *hs_ep) +{ + return list_first_entry_or_null(&hs_ep->queue, struct dwc2_hsotg_req, + queue); +} + +/** + * dwc2_gadget_start_next_request - Starts next request from ep queue + * @hs_ep: Endpoint structure + * + * If queue is empty and EP is ISOC-OUT - unmasks OUTTKNEPDIS which is masked + * in its handler. Hence we need to unmask it here to be able to do + * resynchronization. + */ +static void dwc2_gadget_start_next_request(struct dwc2_hsotg_ep *hs_ep) +{ + struct dwc2_hsotg *hsotg = hs_ep->parent; + int dir_in = hs_ep->dir_in; + struct dwc2_hsotg_req *hs_req; + + if (!list_empty(&hs_ep->queue)) { + hs_req = get_ep_head(hs_ep); + dwc2_hsotg_start_req(hsotg, hs_ep, hs_req, false); + return; + } + if (!hs_ep->isochronous) + return; + + if (dir_in) { + dev_dbg(hsotg->dev, "%s: No more ISOC-IN requests\n", + __func__); + } else { + dev_dbg(hsotg->dev, "%s: No more ISOC-OUT requests\n", + __func__); + } +} + +/** + * dwc2_hsotg_process_req_feature - process request {SET,CLEAR}_FEATURE + * @hsotg: The device state + * @ctrl: USB control request + */ +static int dwc2_hsotg_process_req_feature(struct dwc2_hsotg *hsotg, + struct usb_ctrlrequest *ctrl) +{ + struct dwc2_hsotg_ep *ep0 = hsotg->eps_out[0]; + struct dwc2_hsotg_req *hs_req; + bool set = (ctrl->bRequest == USB_REQ_SET_FEATURE); + struct dwc2_hsotg_ep *ep; + int ret; + bool halted; + u32 recip; + u32 wValue; + u32 wIndex; + + dev_dbg(hsotg->dev, "%s: %s_FEATURE\n", + __func__, set ? "SET" : "CLEAR"); + + wValue = le16_to_cpu(ctrl->wValue); + wIndex = le16_to_cpu(ctrl->wIndex); + recip = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recip) { + case USB_RECIP_DEVICE: + switch (wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + if (set) + hsotg->remote_wakeup_allowed = 1; + else + hsotg->remote_wakeup_allowed = 0; + break; + + case USB_DEVICE_TEST_MODE: + if ((wIndex & 0xff) != 0) + return -EINVAL; + if (!set) + return -EINVAL; + + hsotg->test_mode = wIndex >> 8; + break; + default: + return -ENOENT; + } + + ret = dwc2_hsotg_send_reply(hsotg, ep0, NULL, 0); + if (ret) { + dev_err(hsotg->dev, + "%s: failed to send reply\n", __func__); + return ret; + } + break; + + case USB_RECIP_ENDPOINT: + ep = ep_from_windex(hsotg, wIndex); + if (!ep) { + dev_dbg(hsotg->dev, "%s: no endpoint for 0x%04x\n", + __func__, wIndex); + return -ENOENT; + } + + switch (wValue) { + case USB_ENDPOINT_HALT: + halted = ep->halted; + + if (!ep->wedged) + dwc2_hsotg_ep_sethalt(&ep->ep, set, true); + + ret = dwc2_hsotg_send_reply(hsotg, ep0, NULL, 0); + if (ret) { + dev_err(hsotg->dev, + "%s: failed to send reply\n", __func__); + return ret; + } + + /* + * we have to complete all requests for ep if it was + * halted, and the halt was cleared by CLEAR_FEATURE + */ + + if (!set && halted) { + /* + * 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); + if (hs_req->req.complete) { + spin_unlock(&hsotg->lock); + usb_gadget_giveback_request( + &ep->ep, &hs_req->req); + spin_lock(&hsotg->lock); + } + } + + /* If we have pending request, then start it */ + if (!ep->req) + dwc2_gadget_start_next_request(ep); + } + + break; + + default: + return -ENOENT; + } + break; + default: + return -ENOENT; + } + return 1; +} + +static void dwc2_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg); + +/** + * dwc2_hsotg_stall_ep0 - stall ep0 + * @hsotg: The device state + * + * Set stall for ep0 as response for setup request. + */ +static void dwc2_hsotg_stall_ep0(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hsotg_ep *ep0 = hsotg->eps_out[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 = dwc2_readl(hsotg, reg); + ctrl |= DXEPCTL_STALL; + ctrl |= DXEPCTL_CNAK; + dwc2_writel(hsotg, ctrl, reg); + + dev_dbg(hsotg->dev, + "written DXEPCTL=0x%08x to %08x (DXEPCTL=0x%08x)\n", + ctrl, reg, dwc2_readl(hsotg, reg)); + + /* + * complete won't be called, so we enqueue + * setup request here + */ + dwc2_hsotg_enqueue_setup(hsotg); +} + +/** + * dwc2_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 dwc2_hsotg_process_control(struct dwc2_hsotg *hsotg, + struct usb_ctrlrequest *ctrl) +{ + struct dwc2_hsotg_ep *ep0 = hsotg->eps_out[0]; + int ret = 0; + u32 dcfg; + + dev_dbg(hsotg->dev, + "ctrl Type=%02x, Req=%02x, V=%04x, I=%04x, L=%04x\n", + ctrl->bRequestType, ctrl->bRequest, ctrl->wValue, + ctrl->wIndex, ctrl->wLength); + + if (ctrl->wLength == 0) { + ep0->dir_in = 1; + hsotg->ep0_state = DWC2_EP0_STATUS_IN; + } else if (ctrl->bRequestType & USB_DIR_IN) { + ep0->dir_in = 1; + hsotg->ep0_state = DWC2_EP0_DATA_IN; + } else { + ep0->dir_in = 0; + hsotg->ep0_state = DWC2_EP0_DATA_OUT; + } + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + hsotg->connected = 1; + dcfg = dwc2_readl(hsotg, DCFG); + dcfg &= ~DCFG_DEVADDR_MASK; + dcfg |= (le16_to_cpu(ctrl->wValue) << + DCFG_DEVADDR_SHIFT) & DCFG_DEVADDR_MASK; + dwc2_writel(hsotg, dcfg, DCFG); + + dev_info(hsotg->dev, "new address %d\n", ctrl->wValue); + + ret = dwc2_hsotg_send_reply(hsotg, ep0, NULL, 0); + return; + + case USB_REQ_GET_STATUS: + ret = dwc2_hsotg_process_req_status(hsotg, ctrl); + break; + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + ret = dwc2_hsotg_process_req_feature(hsotg, ctrl); + break; + } + } + + /* as a fallback, try delivering it to the driver to deal with */ + + if (ret == 0 && hsotg->driver) { + spin_unlock(&hsotg->lock); + ret = hsotg->driver->setup(&hsotg->gadget, ctrl); + spin_lock(&hsotg->lock); + if (ret < 0) + dev_dbg(hsotg->dev, "driver->setup() ret %d\n", ret); + } + + hsotg->delayed_status = false; + if (ret == USB_GADGET_DELAYED_STATUS) + hsotg->delayed_status = true; + + /* + * 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) + dwc2_hsotg_stall_ep0(hsotg); +} + +/** + * dwc2_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 dwc2_hsotg_complete_setup(struct usb_ep *ep, + struct usb_request *req) +{ + struct dwc2_hsotg_ep *hs_ep = our_ep(ep); + struct dwc2_hsotg *hsotg = hs_ep->parent; + + if (req->status < 0) { + dev_dbg(hsotg->dev, "%s: failed %d\n", __func__, req->status); + return; + } + + spin_lock(&hsotg->lock); + if (req->actual == 0) + dwc2_hsotg_enqueue_setup(hsotg); + else + dwc2_hsotg_process_control(hsotg, req->buf); + spin_unlock(&hsotg->lock); +} + +/** + * dwc2_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 dwc2_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg) +{ + struct usb_request *req = hsotg->ctrl_req; + struct dwc2_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 = dwc2_hsotg_complete_setup; + + if (!list_empty(&hs_req->queue)) { + dev_dbg(hsotg->dev, "%s already queued???\n", __func__); + return; + } + + hsotg->eps_out[0]->dir_in = 0; + hsotg->eps_out[0]->send_zlp = 0; + hsotg->ep0_state = DWC2_EP0_SETUP; + + ret = dwc2_hsotg_ep_queue(&hsotg->eps_out[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. + */ + } +} + +static void dwc2_hsotg_program_zlp(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep) +{ + u32 ctrl; + u8 index = hs_ep->index; + u32 epctl_reg = hs_ep->dir_in ? DIEPCTL(index) : DOEPCTL(index); + u32 epsiz_reg = hs_ep->dir_in ? DIEPTSIZ(index) : DOEPTSIZ(index); + + if (hs_ep->dir_in) + dev_dbg(hsotg->dev, "Sending zero-length packet on ep%d\n", + index); + else + dev_dbg(hsotg->dev, "Receiving zero-length packet on ep%d\n", + index); + if (using_desc_dma(hsotg)) { + /* Not specific buffer needed for ep0 ZLP */ + dma_addr_t dma = hs_ep->desc_list_dma; + + if (!index) + dwc2_gadget_set_ep0_desc_chain(hsotg, hs_ep); + + dwc2_gadget_config_nonisoc_xfer_ddma(hs_ep, dma, 0); + } else { + dwc2_writel(hsotg, DXEPTSIZ_MC(1) | DXEPTSIZ_PKTCNT(1) | + DXEPTSIZ_XFERSIZE(0), + epsiz_reg); + } + + ctrl = dwc2_readl(hsotg, epctl_reg); + ctrl |= DXEPCTL_CNAK; /* clear NAK set by core */ + ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */ + ctrl |= DXEPCTL_USBACTEP; + dwc2_writel(hsotg, ctrl, epctl_reg); +} + +/** + * dwc2_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 dwc2_hsotg_complete_request(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep, + struct dwc2_hsotg_req *hs_req, + int result) +{ + 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; + + if (using_dma(hsotg)) + dwc2_hsotg_unmap_dma(hsotg, hs_ep, hs_req); + + dwc2_hsotg_handle_unaligned_buf_complete(hsotg, hs_ep, hs_req); + + hs_ep->req = NULL; + list_del_init(&hs_req->queue); + + /* + * 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); + usb_gadget_giveback_request(&hs_ep->ep, &hs_req->req); + spin_lock(&hsotg->lock); + } + + /* In DDMA don't need to proceed to starting of next ISOC request */ + if (using_desc_dma(hsotg) && hs_ep->isochronous) + return; + + /* + * 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) + dwc2_gadget_start_next_request(hs_ep); +} + +/* + * dwc2_gadget_complete_isoc_request_ddma - complete an isoc request in DDMA + * @hs_ep: The endpoint the request was on. + * + * Get first request from the ep queue, determine descriptor on which complete + * happened. SW discovers which descriptor currently in use by HW, adjusts + * dma_address and calculates index of completed descriptor based on the value + * of DEPDMA register. Update actual length of request, giveback to gadget. + */ +static void dwc2_gadget_complete_isoc_request_ddma(struct dwc2_hsotg_ep *hs_ep) +{ + struct dwc2_hsotg *hsotg = hs_ep->parent; + struct dwc2_hsotg_req *hs_req; + struct usb_request *ureq; + u32 desc_sts; + u32 mask; + + desc_sts = hs_ep->desc_list[hs_ep->compl_desc].status; + + /* Process only descriptors with buffer status set to DMA done */ + while ((desc_sts & DEV_DMA_BUFF_STS_MASK) >> + DEV_DMA_BUFF_STS_SHIFT == DEV_DMA_BUFF_STS_DMADONE) { + + hs_req = get_ep_head(hs_ep); + if (!hs_req) { + dev_warn(hsotg->dev, "%s: ISOC EP queue empty\n", __func__); + return; + } + ureq = &hs_req->req; + + /* Check completion status */ + if ((desc_sts & DEV_DMA_STS_MASK) >> DEV_DMA_STS_SHIFT == + DEV_DMA_STS_SUCC) { + mask = hs_ep->dir_in ? DEV_DMA_ISOC_TX_NBYTES_MASK : + DEV_DMA_ISOC_RX_NBYTES_MASK; + ureq->actual = ureq->length - ((desc_sts & mask) >> + DEV_DMA_ISOC_NBYTES_SHIFT); + + /* Adjust actual len for ISOC Out if len is + * not align of 4 + */ + if (!hs_ep->dir_in && ureq->length & 0x3) + ureq->actual += 4 - (ureq->length & 0x3); + + /* Set actual frame number for completed transfers */ + ureq->frame_number = + (desc_sts & DEV_DMA_ISOC_FRNUM_MASK) >> + DEV_DMA_ISOC_FRNUM_SHIFT; + } + + dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + + hs_ep->compl_desc++; + if (hs_ep->compl_desc > (MAX_DMA_DESC_NUM_HS_ISOC - 1)) + hs_ep->compl_desc = 0; + desc_sts = hs_ep->desc_list[hs_ep->compl_desc].status; + } +} + +/* + * dwc2_gadget_handle_isoc_bna - handle BNA interrupt for ISOC. + * @hs_ep: The isochronous endpoint. + * + * If EP ISOC OUT then need to flush RX FIFO to remove source of BNA + * interrupt. Reset target frame and next_desc to allow to start + * ISOC's on NAK interrupt for IN direction or on OUTTKNEPDIS + * interrupt for OUT direction. + */ +static void dwc2_gadget_handle_isoc_bna(struct dwc2_hsotg_ep *hs_ep) +{ + struct dwc2_hsotg *hsotg = hs_ep->parent; + + if (!hs_ep->dir_in) + dwc2_flush_rx_fifo(hsotg); + dwc2_hsotg_complete_request(hsotg, hs_ep, get_ep_head(hs_ep), 0); + + hs_ep->target_frame = TARGET_FRAME_INITIAL; + hs_ep->next_desc = 0; + hs_ep->compl_desc = 0; +} + +/** + * dwc2_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 dwc2_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) +{ + struct dwc2_hsotg_ep *hs_ep = hsotg->eps_out[ep_idx]; + struct dwc2_hsotg_req *hs_req = hs_ep->req; + int to_read; + int max_req; + int read_ptr; + + if (!hs_req) { + u32 epctl = dwc2_readl(hsotg, DOEPCTL(ep_idx)); + int ptr; + + dev_dbg(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)dwc2_readl(hsotg, EPFIFO(ep_idx)); + + 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. + */ + dwc2_readl_rep(hsotg, EPFIFO(ep_idx), + hs_req->req.buf + read_ptr, to_read); +} + +/** + * dwc2_hsotg_ep0_zlp - send/receive zero-length packet on control endpoint + * @hsotg: The device instance + * @dir_in: If IN zlp + * + * 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 dwc2_hsotg_ep0_zlp(struct dwc2_hsotg *hsotg, bool dir_in) +{ + /* eps_out[0] is used in both directions */ + hsotg->eps_out[0]->dir_in = dir_in; + hsotg->ep0_state = dir_in ? DWC2_EP0_STATUS_IN : DWC2_EP0_STATUS_OUT; + + dwc2_hsotg_program_zlp(hsotg, hsotg->eps_out[0]); +} + +/* + * dwc2_gadget_get_xfersize_ddma - get transferred bytes amount from desc + * @hs_ep - The endpoint on which transfer went + * + * Iterate over endpoints descriptor chain and get info on bytes remained + * in DMA descriptors after transfer has completed. Used for non isoc EPs. + */ +static unsigned int dwc2_gadget_get_xfersize_ddma(struct dwc2_hsotg_ep *hs_ep) +{ + const struct usb_endpoint_descriptor *ep_desc = hs_ep->ep.desc; + struct dwc2_hsotg *hsotg = hs_ep->parent; + unsigned int bytes_rem = 0; + unsigned int bytes_rem_correction = 0; + struct dwc2_dma_desc *desc = hs_ep->desc_list; + int i; + u32 status; + u32 mps = hs_ep->ep.maxpacket; + int dir_in = hs_ep->dir_in; + + if (!desc) + return -EINVAL; + + /* Interrupt OUT EP with mps not multiple of 4 */ + if (hs_ep->index) + if (usb_endpoint_xfer_int(ep_desc) && !dir_in && (mps % 4)) + bytes_rem_correction = 4 - (mps % 4); + + for (i = 0; i < hs_ep->desc_count; ++i) { + status = desc->status; + bytes_rem += status & DEV_DMA_NBYTES_MASK; + bytes_rem -= bytes_rem_correction; + + if (status & DEV_DMA_STS_MASK) + dev_err(hsotg->dev, "descriptor %d closed with %x\n", + i, status & DEV_DMA_STS_MASK); + + if (status & DEV_DMA_L) + break; + + desc++; + } + + return bytes_rem; +} + +/** + * dwc2_hsotg_handle_outdone - handle receiving OutDone/SetupDone from RXFIFO + * @hsotg: The device instance + * @epnum: The endpoint received from + * + * 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 dwc2_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, int epnum) +{ + u32 epsize = dwc2_readl(hsotg, DOEPTSIZ(epnum)); + struct dwc2_hsotg_ep *hs_ep = hsotg->eps_out[epnum]; + struct dwc2_hsotg_req *hs_req = hs_ep->req; + struct usb_request *req = &hs_req->req; + unsigned int size_left = DXEPTSIZ_XFERSIZE_GET(epsize); + int result = 0; + + if (!hs_req) { + dev_dbg(hsotg->dev, "%s: no request active\n", __func__); + return; + } + + if (epnum == 0 && hsotg->ep0_state == DWC2_EP0_STATUS_OUT) { + dev_dbg(hsotg->dev, "zlp packet received\n"); + dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + dwc2_hsotg_enqueue_setup(hsotg); + return; + } + + if (using_desc_dma(hsotg)) + size_left = dwc2_gadget_get_xfersize_ddma(hs_ep); + + if (using_dma(hsotg)) { + unsigned int 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) { + dwc2_hsotg_start_req(hsotg, hs_ep, hs_req, true); + return; + } + + 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. + */ + } + + /* DDMA IN status phase will start from StsPhseRcvd interrupt */ + if (!using_desc_dma(hsotg) && epnum == 0 && + hsotg->ep0_state == DWC2_EP0_DATA_OUT) { + /* Move to STATUS IN */ + if (!hsotg->delayed_status) + dwc2_hsotg_ep0_zlp(hsotg, true); + } + + /* Set actual frame number for completed transfers */ + if (!using_desc_dma(hsotg) && hs_ep->isochronous) { + req->frame_number = hs_ep->target_frame; + dwc2_gadget_incr_frame_num(hs_ep); + } + + dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, result); +} + +/** + * dwc2_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 dwc2_hsotg_handle_rx(struct dwc2_hsotg *hsotg) +{ + u32 grxstsr = dwc2_readl(hsotg, 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; + + dev_dbg(hsotg->dev, "%s: GRXSTSP=0x%08x (%d@%d)\n", + __func__, grxstsr, size, epnum); + + switch ((status & GRXSTS_PKTSTS_MASK) >> GRXSTS_PKTSTS_SHIFT) { + case GRXSTS_PKTSTS_GLOBALOUTNAK: + dev_dbg(hsotg->dev, "GLOBALOUTNAK\n"); + break; + + case GRXSTS_PKTSTS_OUTDONE: + dev_dbg(hsotg->dev, "OutDone (Frame=0x%08x)\n", + dwc2_hsotg_read_frameno(hsotg)); + + if (!using_dma(hsotg)) + dwc2_hsotg_handle_outdone(hsotg, epnum); + break; + + case GRXSTS_PKTSTS_SETUPDONE: + dev_dbg(hsotg->dev, + "SetupDone (Frame=0x%08x, DOPEPCTL=0x%08x)\n", + dwc2_hsotg_read_frameno(hsotg), + dwc2_readl(hsotg, DOEPCTL(0))); + /* + * Call dwc2_hsotg_handle_outdone here if it was not called from + * GRXSTS_PKTSTS_OUTDONE. That is, if the core didn't + * generate GRXSTS_PKTSTS_OUTDONE for setup packet. + */ + if (hsotg->ep0_state == DWC2_EP0_SETUP) + dwc2_hsotg_handle_outdone(hsotg, epnum); + break; + + case GRXSTS_PKTSTS_OUTRX: + dwc2_hsotg_rx_data(hsotg, epnum, size); + break; + + case GRXSTS_PKTSTS_SETUPRX: + dev_dbg(hsotg->dev, + "SetupRX (Frame=0x%08x, DOPEPCTL=0x%08x)\n", + dwc2_hsotg_read_frameno(hsotg), + dwc2_readl(hsotg, DOEPCTL(0))); + + WARN_ON(hsotg->ep0_state != DWC2_EP0_SETUP); + + dwc2_hsotg_rx_data(hsotg, epnum, size); + break; + + default: + dev_warn(hsotg->dev, "%s: unknown status %08x\n", + __func__, grxstsr); + + dwc2_hsotg_dump(hsotg); + break; + } +} + +/** + * dwc2_hsotg_ep0_mps - turn max packet size into register setting + * @mps: The maximum packet size in bytes. + */ +static u32 dwc2_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; +} + +/** + * dwc2_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 + * @mc: The multicount value + * @dir_in: True if direction is in. + * + * Configure the maximum packet size for the given endpoint, updating + * the hardware control registers to reflect this. + */ +static void dwc2_hsotg_set_ep_maxpacket(struct dwc2_hsotg *hsotg, + unsigned int ep, unsigned int mps, + unsigned int mc, unsigned int dir_in) +{ + struct dwc2_hsotg_ep *hs_ep; + u32 reg; + + hs_ep = index_to_ep(hsotg, ep, dir_in); + if (!hs_ep) + return; + + if (ep == 0) { + u32 mps_bytes = mps; + + /* EP0 is a special case */ + mps = dwc2_hsotg_ep0_mps(mps_bytes); + if (mps > 3) + goto bad_mps; + hs_ep->ep.maxpacket = mps_bytes; + hs_ep->mc = 1; + } else { + if (mps > 1024) + goto bad_mps; + hs_ep->mc = mc; + if (mc > 3) + goto bad_mps; + hs_ep->ep.maxpacket = mps; + } + + if (dir_in) { + reg = dwc2_readl(hsotg, DIEPCTL(ep)); + reg &= ~DXEPCTL_MPS_MASK; + reg |= mps; + dwc2_writel(hsotg, reg, DIEPCTL(ep)); + } else { + reg = dwc2_readl(hsotg, DOEPCTL(ep)); + reg &= ~DXEPCTL_MPS_MASK; + reg |= mps; + dwc2_writel(hsotg, reg, DOEPCTL(ep)); + } + + return; + +bad_mps: + dev_err(hsotg->dev, "ep%d: bad mps of %d\n", ep, mps); +} + +/** + * dwc2_hsotg_txfifo_flush - flush Tx FIFO + * @hsotg: The driver state + * @idx: The index for the endpoint (0..15) + */ +static void dwc2_hsotg_txfifo_flush(struct dwc2_hsotg *hsotg, unsigned int idx) +{ + dwc2_writel(hsotg, GRSTCTL_TXFNUM(idx) | GRSTCTL_TXFFLSH, + GRSTCTL); + + /* wait until the fifo is flushed */ + if (dwc2_hsotg_wait_bit_clear(hsotg, GRSTCTL, GRSTCTL_TXFFLSH, 100)) + dev_warn(hsotg->dev, "%s: timeout flushing fifo GRSTCTL_TXFFLSH\n", + __func__); +} + +/** + * dwc2_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 dwc2_hsotg_trytx(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep) +{ + struct dwc2_hsotg_req *hs_req = hs_ep->req; + + if (!hs_ep->dir_in || !hs_req) { + /** + * if request is not enqueued, we disable interrupts + * for endpoints, excepting ep0 + */ + if (hs_ep->index != 0) + dwc2_hsotg_ctrl_epint(hsotg, hs_ep->index, + hs_ep->dir_in, 0); + 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 dwc2_hsotg_write_fifo(hsotg, hs_ep, hs_req); + } + + return 0; +} + +/** + * dwc2_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 dwc2_hsotg_complete_in(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep) +{ + struct dwc2_hsotg_req *hs_req = hs_ep->req; + u32 epsize = dwc2_readl(hsotg, 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 (hs_ep->index == 0 && hsotg->ep0_state == DWC2_EP0_STATUS_IN) { + dev_dbg(hsotg->dev, "zlp packet sent\n"); + + /* + * While send zlp for DWC2_EP0_STATUS_IN EP direction was + * changed to IN. Change back to complete OUT transfer request + */ + hs_ep->dir_in = 0; + + dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + if (hsotg->test_mode) { + int ret; + + ret = dwc2_hsotg_set_test_mode(hsotg, hsotg->test_mode); + if (ret < 0) { + dev_dbg(hsotg->dev, "Invalid Test #%d\n", + hsotg->test_mode); + dwc2_hsotg_stall_ep0(hsotg); + return; + } + } + dwc2_hsotg_enqueue_setup(hsotg); + 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). + */ + if (using_desc_dma(hsotg)) { + size_left = dwc2_gadget_get_xfersize_ddma(hs_ep); + if (size_left < 0) + dev_err(hsotg->dev, "error parsing DDMA results %d\n", + size_left); + } else { + 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); + + if (!size_left && hs_req->req.actual < hs_req->req.length) { + dev_dbg(hsotg->dev, "%s trying more for req...\n", __func__); + dwc2_hsotg_start_req(hsotg, hs_ep, hs_req, true); + return; + } + + /* Zlp for all endpoints in non DDMA, for ep0 only in DATA IN stage */ + if (hs_ep->send_zlp) { + hs_ep->send_zlp = 0; + if (!using_desc_dma(hsotg)) { + dwc2_hsotg_program_zlp(hsotg, hs_ep); + /* transfer will be completed on next complete interrupt */ + return; + } + } + + if (hs_ep->index == 0 && hsotg->ep0_state == DWC2_EP0_DATA_IN) { + /* Move to STATUS OUT */ + dwc2_hsotg_ep0_zlp(hsotg, false); + return; + } + + /* Set actual frame number for completed transfers */ + if (!using_desc_dma(hsotg) && hs_ep->isochronous) { + hs_req->req.frame_number = hs_ep->target_frame; + dwc2_gadget_incr_frame_num(hs_ep); + } + + dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); +} + +/** + * dwc2_gadget_read_ep_interrupts - reads interrupts for given ep + * @hsotg: The device state. + * @idx: Index of ep. + * @dir_in: Endpoint direction 1-in 0-out. + * + * Reads for endpoint with given index and direction, by masking + * epint_reg with coresponding mask. + */ +static u32 dwc2_gadget_read_ep_interrupts(struct dwc2_hsotg *hsotg, + unsigned int idx, int dir_in) +{ + u32 epmsk_reg = dir_in ? DIEPMSK : DOEPMSK; + u32 epint_reg = dir_in ? DIEPINT(idx) : DOEPINT(idx); + u32 ints; + u32 mask; + u32 diepempmsk; + + mask = dwc2_readl(hsotg, epmsk_reg); + diepempmsk = dwc2_readl(hsotg, DIEPEMPMSK); + mask |= ((diepempmsk >> idx) & 0x1) ? DIEPMSK_TXFIFOEMPTY : 0; + mask |= DXEPINT_SETUP_RCVD; + + ints = dwc2_readl(hsotg, epint_reg); + ints &= mask; + return ints; +} + +/** + * dwc2_gadget_handle_ep_disabled - handle DXEPINT_EPDISBLD + * @hs_ep: The endpoint on which interrupt is asserted. + * + * This interrupt indicates that the endpoint has been disabled per the + * application's request. + * + * For IN endpoints flushes txfifo, in case of BULK clears DCTL_CGNPINNAK, + * in case of ISOC completes current request. + * + * For ISOC-OUT endpoints completes expired requests. If there is remaining + * request starts it. + */ +static void dwc2_gadget_handle_ep_disabled(struct dwc2_hsotg_ep *hs_ep) +{ + struct dwc2_hsotg *hsotg = hs_ep->parent; + struct dwc2_hsotg_req *hs_req; + unsigned char idx = hs_ep->index; + int dir_in = hs_ep->dir_in; + u32 epctl_reg = dir_in ? DIEPCTL(idx) : DOEPCTL(idx); + int dctl = dwc2_readl(hsotg, DCTL); + + dev_dbg(hsotg->dev, "%s: EPDisbld\n", __func__); + + if (dir_in) { + int epctl = dwc2_readl(hsotg, epctl_reg); + + dwc2_hsotg_txfifo_flush(hsotg, hs_ep->fifo_index); + + if ((epctl & DXEPCTL_STALL) && (epctl & DXEPCTL_EPTYPE_BULK)) { + int dctl = dwc2_readl(hsotg, DCTL); + + dctl |= DCTL_CGNPINNAK; + dwc2_writel(hsotg, dctl, DCTL); + } + } else { + + if (dctl & DCTL_GOUTNAKSTS) { + dctl |= DCTL_CGOUTNAK; + dwc2_writel(hsotg, dctl, DCTL); + } + } + + if (!hs_ep->isochronous) + return; + + if (list_empty(&hs_ep->queue)) { + dev_dbg(hsotg->dev, "%s: complete_ep 0x%p, ep->queue empty!\n", + __func__, hs_ep); + return; + } + + do { + hs_req = get_ep_head(hs_ep); + if (hs_req) { + hs_req->req.frame_number = hs_ep->target_frame; + hs_req->req.actual = 0; + dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, + -ENODATA); + } + dwc2_gadget_incr_frame_num(hs_ep); + /* Update current frame number value. */ + hsotg->frame_number = dwc2_hsotg_read_frameno(hsotg); + } while (dwc2_gadget_target_frame_elapsed(hs_ep)); +} + +/** + * dwc2_gadget_handle_out_token_ep_disabled - handle DXEPINT_OUTTKNEPDIS + * @ep: The endpoint on which interrupt is asserted. + * + * This is starting point for ISOC-OUT transfer, synchronization done with + * first out token received from host while corresponding EP is disabled. + * + * Device does not know initial frame in which out token will come. For this + * HW generates OUTTKNEPDIS - out token is received while EP is disabled. Upon + * getting this interrupt SW starts calculation for next transfer frame. + */ +static void dwc2_gadget_handle_out_token_ep_disabled(struct dwc2_hsotg_ep *ep) +{ + struct dwc2_hsotg *hsotg = ep->parent; + struct dwc2_hsotg_req *hs_req; + int dir_in = ep->dir_in; + + if (dir_in || !ep->isochronous) + return; + + if (using_desc_dma(hsotg)) { + if (ep->target_frame == TARGET_FRAME_INITIAL) { + /* Start first ISO Out */ + ep->target_frame = hsotg->frame_number; + dwc2_gadget_start_isoc_ddma(ep); + } + return; + } + + if (ep->target_frame == TARGET_FRAME_INITIAL) { + u32 ctrl; + + ep->target_frame = hsotg->frame_number; + if (ep->interval > 1) { + ctrl = dwc2_readl(hsotg, DOEPCTL(ep->index)); + if (ep->target_frame & 0x1) + ctrl |= DXEPCTL_SETODDFR; + else + ctrl |= DXEPCTL_SETEVENFR; + + dwc2_writel(hsotg, ctrl, DOEPCTL(ep->index)); + } + } + + while (dwc2_gadget_target_frame_elapsed(ep)) { + hs_req = get_ep_head(ep); + if (hs_req) { + hs_req->req.frame_number = ep->target_frame; + hs_req->req.actual = 0; + dwc2_hsotg_complete_request(hsotg, ep, hs_req, -ENODATA); + } + + dwc2_gadget_incr_frame_num(ep); + /* Update current frame number value. */ + hsotg->frame_number = dwc2_hsotg_read_frameno(hsotg); + } + + if (!ep->req) + dwc2_gadget_start_next_request(ep); + +} + +static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep); + +/** + * dwc2_gadget_handle_nak - handle NAK interrupt + * @hs_ep: The endpoint on which interrupt is asserted. + * + * This is starting point for ISOC-IN transfer, synchronization done with + * first IN token received from host while corresponding EP is disabled. + * + * Device does not know when first one token will arrive from host. On first + * token arrival HW generates 2 interrupts: 'in token received while FIFO empty' + * and 'NAK'. NAK interrupt for ISOC-IN means that token has arrived and ZLP was + * sent in response to that as there was no data in FIFO. SW is basing on this + * interrupt to obtain frame in which token has come and then based on the + * interval calculates next frame for transfer. + */ +static void dwc2_gadget_handle_nak(struct dwc2_hsotg_ep *hs_ep) +{ + struct dwc2_hsotg *hsotg = hs_ep->parent; + struct dwc2_hsotg_req *hs_req; + int dir_in = hs_ep->dir_in; + u32 ctrl; + + if (!dir_in || !hs_ep->isochronous) + return; + + if (hs_ep->target_frame == TARGET_FRAME_INITIAL) { + + if (using_desc_dma(hsotg)) { + hs_ep->target_frame = hsotg->frame_number; + dwc2_gadget_incr_frame_num(hs_ep); + + /* In service interval mode target_frame must + * be set to last (u)frame of the service interval. + */ + if (hsotg->params.service_interval) { + /* Set target_frame to the first (u)frame of + * the service interval + */ + hs_ep->target_frame &= ~hs_ep->interval + 1; + + /* Set target_frame to the last (u)frame of + * the service interval + */ + dwc2_gadget_incr_frame_num(hs_ep); + dwc2_gadget_dec_frame_num_by_one(hs_ep); + } + + dwc2_gadget_start_isoc_ddma(hs_ep); + return; + } + + hs_ep->target_frame = hsotg->frame_number; + if (hs_ep->interval > 1) { + u32 ctrl = dwc2_readl(hsotg, + DIEPCTL(hs_ep->index)); + if (hs_ep->target_frame & 0x1) + ctrl |= DXEPCTL_SETODDFR; + else + ctrl |= DXEPCTL_SETEVENFR; + + dwc2_writel(hsotg, ctrl, DIEPCTL(hs_ep->index)); + } + } + + if (using_desc_dma(hsotg)) + return; + + ctrl = dwc2_readl(hsotg, DIEPCTL(hs_ep->index)); + if (ctrl & DXEPCTL_EPENA) + dwc2_hsotg_ep_stop_xfr(hsotg, hs_ep); + else + dwc2_hsotg_txfifo_flush(hsotg, hs_ep->fifo_index); + + while (dwc2_gadget_target_frame_elapsed(hs_ep)) { + hs_req = get_ep_head(hs_ep); + if (hs_req) { + hs_req->req.frame_number = hs_ep->target_frame; + hs_req->req.actual = 0; + dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, -ENODATA); + } + + dwc2_gadget_incr_frame_num(hs_ep); + /* Update current frame number value. */ + hsotg->frame_number = dwc2_hsotg_read_frameno(hsotg); + } + + if (!hs_ep->req) + dwc2_gadget_start_next_request(hs_ep); +} + +/** + * dwc2_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 dwc2_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, + int dir_in) +{ + struct dwc2_hsotg_ep *hs_ep = index_to_ep(hsotg, idx, dir_in); + 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 = dwc2_gadget_read_ep_interrupts(hsotg, idx, dir_in); + + /* Clear endpoint interrupts */ + dwc2_writel(hsotg, ints, epint_reg); + + if (!hs_ep) { + dev_err(hsotg->dev, "%s:Interrupt for unconfigured ep%d(%s)\n", + __func__, idx, dir_in ? "in" : "out"); + return; + } + + dev_dbg(hsotg->dev, "%s: ep%d(%s) DxEPINT=0x%08x\n", + __func__, idx, dir_in ? "in" : "out", ints); + + /* Don't process XferCompl interrupt if it is a setup packet */ + if (idx == 0 && (ints & (DXEPINT_SETUP | DXEPINT_SETUP_RCVD))) + ints &= ~DXEPINT_XFERCOMPL; + + /* + * Don't process XferCompl interrupt in DDMA if EP0 is still in SETUP + * stage and xfercomplete was generated without SETUP phase done + * interrupt. SW should parse received setup packet only after host's + * exit from setup phase of control transfer. + */ + if (using_desc_dma(hsotg) && idx == 0 && !hs_ep->dir_in && + hsotg->ep0_state == DWC2_EP0_SETUP && !(ints & DXEPINT_SETUP)) + ints &= ~DXEPINT_XFERCOMPL; + + if (ints & DXEPINT_XFERCOMPL) { + dev_dbg(hsotg->dev, + "%s: XferCompl: DxEPCTL=0x%08x, DXEPTSIZ=%08x\n", + __func__, dwc2_readl(hsotg, epctl_reg), + dwc2_readl(hsotg, epsiz_reg)); + + /* In DDMA handle isochronous requests separately */ + if (using_desc_dma(hsotg) && hs_ep->isochronous) { + dwc2_gadget_complete_isoc_request_ddma(hs_ep); + } else if (dir_in) { + /* + * We get OutDone from the FIFO, so we only + * need to look at completing IN requests here + * if operating slave mode + */ + if (!hs_ep->isochronous || !(ints & DXEPINT_NAKINTRPT)) + dwc2_hsotg_complete_in(hsotg, hs_ep); + + if (idx == 0 && !hs_ep->req) + dwc2_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. + */ + if (!hs_ep->isochronous || !(ints & DXEPINT_OUTTKNEPDIS)) + dwc2_hsotg_handle_outdone(hsotg, idx); + } + } + + if (ints & DXEPINT_EPDISBLD) + dwc2_gadget_handle_ep_disabled(hs_ep); + + if (ints & DXEPINT_OUTTKNEPDIS) + dwc2_gadget_handle_out_token_ep_disabled(hs_ep); + + if (ints & DXEPINT_NAKINTRPT) + dwc2_gadget_handle_nak(hs_ep); + + 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 + dwc2_hsotg_handle_outdone(hsotg, 0); + } + } + + if (ints & DXEPINT_STSPHSERCVD) { + dev_dbg(hsotg->dev, "%s: StsPhseRcvd\n", __func__); + + /* Safety check EP0 state when STSPHSERCVD asserted */ + if (hsotg->ep0_state == DWC2_EP0_DATA_OUT) { + /* Move to STATUS IN for DDMA */ + if (using_desc_dma(hsotg)) { + if (!hsotg->delayed_status) + dwc2_hsotg_ep0_zlp(hsotg, true); + else + /* In case of 3 stage Control Write with delayed + * status, when Status IN transfer started + * before STSPHSERCVD asserted, NAKSTS bit not + * cleared by CNAK in dwc2_hsotg_start_req() + * function. Clear now NAKSTS to allow complete + * transfer. + */ + dwc2_set_bit(hsotg, DIEPCTL(0), + DXEPCTL_CNAK); + } + } + + } + + if (ints & DXEPINT_BACK2BACKSETUP) + dev_dbg(hsotg->dev, "%s: B2BSetup/INEPNakEff\n", __func__); + + if (ints & DXEPINT_BNAINTR) { + dev_dbg(hsotg->dev, "%s: BNA interrupt\n", __func__); + if (hs_ep->isochronous) + dwc2_gadget_handle_isoc_bna(hs_ep); + } + + if (dir_in && !hs_ep->isochronous) { + /* not sure if this is important, but we'll clear it anyway */ + if (ints & DXEPINT_INTKNTXFEMP) { + dev_dbg(hsotg->dev, "%s: ep%d: INTknTXFEmpMsk\n", + __func__, idx); + } + + /* this probably means something bad is happening */ + if (ints & DXEPINT_INTKNEPMIS) { + dev_warn(hsotg->dev, "%s: ep%d: INTknEP\n", + __func__, idx); + } + + /* FIFO has space or is empty (see GAHBCFG) */ + if (hsotg->dedicated_fifos && + ints & DXEPINT_TXFEMP) { + dev_dbg(hsotg->dev, "%s: ep%d: TxFIFOEmpty\n", + __func__, idx); + if (!using_dma(hsotg)) + dwc2_hsotg_trytx(hsotg, hs_ep); + } + } +} + +/** + * dwc2_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 dwc2_hsotg_irq_enumdone(struct dwc2_hsotg *hsotg) +{ + u32 dsts = dwc2_readl(hsotg, DSTS); + int ep0_mps = 0, ep_mps = 8; + + /* + * 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) >> DSTS_ENUMSPD_SHIFT) { + case DSTS_ENUMSPD_FS: + case DSTS_ENUMSPD_FS48: + hsotg->gadget.speed = USB_SPEED_FULL; + ep0_mps = EP0_MPS_LIMIT; + ep_mps = 1023; + break; + + case DSTS_ENUMSPD_HS: + hsotg->gadget.speed = USB_SPEED_HIGH; + ep0_mps = EP0_MPS_LIMIT; + ep_mps = 1024; + break; + + case DSTS_ENUMSPD_LS: + hsotg->gadget.speed = USB_SPEED_LOW; + ep0_mps = 8; + ep_mps = 8; + /* + * 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; + /* Initialize ep0 for both in and out directions */ + dwc2_hsotg_set_ep_maxpacket(hsotg, 0, ep0_mps, 0, 1); + dwc2_hsotg_set_ep_maxpacket(hsotg, 0, ep0_mps, 0, 0); + for (i = 1; i < hsotg->num_of_eps; i++) { + if (hsotg->eps_in[i]) + dwc2_hsotg_set_ep_maxpacket(hsotg, i, ep_mps, + 0, 1); + if (hsotg->eps_out[i]) + dwc2_hsotg_set_ep_maxpacket(hsotg, i, ep_mps, + 0, 0); + } + } + + /* ensure after enumeration our EP0 is active */ + + dwc2_hsotg_enqueue_setup(hsotg); + + dev_dbg(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n", + dwc2_readl(hsotg, DIEPCTL0), + dwc2_readl(hsotg, 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. + * + * Go through the requests on the given endpoint and mark them + * completed with the given result code. + */ +static void kill_all_requests(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *ep, + int result) +{ + unsigned int size; + + ep->req = NULL; + + while (!list_empty(&ep->queue)) { + struct dwc2_hsotg_req *req = get_ep_head(ep); + + dwc2_hsotg_complete_request(hsotg, ep, req, result); + } + + if (!hsotg->dedicated_fifos) + return; + size = (dwc2_readl(hsotg, DTXFSTS(ep->fifo_index)) & 0xffff) * 4; + if (size < ep->fifo_size) + dwc2_hsotg_txfifo_flush(hsotg, ep->fifo_index); +} + +/** + * dwc2_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. + */ +void dwc2_hsotg_disconnect(struct dwc2_hsotg *hsotg) +{ + unsigned int ep; + + if (!hsotg->connected) + return; + + hsotg->connected = 0; + hsotg->test_mode = 0; + + /* all endpoints should be shutdown */ + for (ep = 0; ep < hsotg->num_of_eps; ep++) { + if (hsotg->eps_in[ep]) + kill_all_requests(hsotg, hsotg->eps_in[ep], + -ESHUTDOWN); + if (hsotg->eps_out[ep]) + kill_all_requests(hsotg, hsotg->eps_out[ep], + -ESHUTDOWN); + } + + call_gadget(hsotg, disconnect); + hsotg->lx_state = DWC2_L3; + + usb_gadget_set_state(&hsotg->gadget, USB_STATE_NOTATTACHED); +} + +/** + * dwc2_hsotg_irq_fifoempty - TX FIFO empty interrupt handler + * @hsotg: The device state: + * @periodic: True if this is a periodic FIFO interrupt + */ +static void dwc2_hsotg_irq_fifoempty(struct dwc2_hsotg *hsotg, bool periodic) +{ + struct dwc2_hsotg_ep *ep; + int epno, ret; + + /* look through for any more data to transmit */ + for (epno = 0; epno < hsotg->num_of_eps; epno++) { + ep = index_to_ep(hsotg, epno, 1); + + if (!ep) + continue; + + if (!ep->dir_in) + continue; + + if ((periodic && !ep->periodic) || + (!periodic && ep->periodic)) + continue; + + ret = dwc2_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) + +static int dwc2_hsotg_ep_disable(struct usb_ep *ep); +/** + * dwc2_hsotg_core_init_disconnected - issue softreset to the core + * @hsotg: The device state + * @is_usb_reset: Usb resetting flag + * + * Issue a soft reset to the core, and await the core finishing it. + */ +void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, + bool is_usb_reset) +{ + u32 intmsk; + u32 val; + u32 usbcfg; + u32 dcfg = 0; + int ep; + + /* Kill any ep0 requests as controller will be reinitialized */ + kill_all_requests(hsotg, hsotg->eps_out[0], -ECONNRESET); + + if (!is_usb_reset) { + if (dwc2_core_reset(hsotg, true)) + return; + } else { + /* all endpoints should be shutdown */ + for (ep = 1; ep < hsotg->num_of_eps; ep++) { + if (hsotg->eps_in[ep]) + dwc2_hsotg_ep_disable(&hsotg->eps_in[ep]->ep); + if (hsotg->eps_out[ep]) + dwc2_hsotg_ep_disable(&hsotg->eps_out[ep]->ep); + } + } + + /* + * we must now enable ep0 ready for host detection and then + * set configuration. + */ + + /* keep other bits untouched (so e.g. forced modes are not lost) */ + usbcfg = dwc2_readl(hsotg, GUSBCFG); + usbcfg &= ~GUSBCFG_TOUTCAL_MASK; + usbcfg |= GUSBCFG_TOUTCAL(7); + + /* remove the HNP/SRP and set the PHY */ + usbcfg &= ~(GUSBCFG_SRPCAP | GUSBCFG_HNPCAP); + dwc2_writel(hsotg, usbcfg, GUSBCFG); + + dwc2_phy_init(hsotg, true); + + dwc2_hsotg_init_fifo(hsotg); + + if (!is_usb_reset) { + dwc2_set_bit(hsotg, DCTL, DCTL_SFTDISCON); + if (hsotg->params.eusb2_disc) + dwc2_set_bit(hsotg, GOTGCTL, GOTGCTL_EUSB2_DISC_SUPP); + } + + dcfg |= DCFG_EPMISCNT(1); + + switch (hsotg->params.speed) { + case DWC2_SPEED_PARAM_LOW: + dcfg |= DCFG_DEVSPD_LS; + break; + case DWC2_SPEED_PARAM_FULL: + if (hsotg->params.phy_type == DWC2_PHY_TYPE_PARAM_FS) + dcfg |= DCFG_DEVSPD_FS48; + else + dcfg |= DCFG_DEVSPD_FS; + break; + default: + dcfg |= DCFG_DEVSPD_HS; + } + + if (hsotg->params.ipg_isoc_en) + dcfg |= DCFG_IPG_ISOC_SUPPORDED; + + dwc2_writel(hsotg, dcfg, DCFG); + + /* Clear any pending OTG interrupts */ + dwc2_writel(hsotg, 0xffffffff, GOTGINT); + + /* Clear any pending interrupts */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + intmsk = GINTSTS_ERLYSUSP | GINTSTS_SESSREQINT | + GINTSTS_GOUTNAKEFF | GINTSTS_GINNAKEFF | + GINTSTS_USBRST | GINTSTS_RESETDET | + GINTSTS_ENUMDONE | GINTSTS_OTGINT | + GINTSTS_USBSUSP | GINTSTS_WKUPINT | + GINTSTS_LPMTRANRCVD; + + if (!using_desc_dma(hsotg)) + intmsk |= GINTSTS_INCOMPL_SOIN | GINTSTS_INCOMPL_SOOUT; + + if (!hsotg->params.external_id_pin_ctl) + intmsk |= GINTSTS_CONIDSTSCHNG; + + dwc2_writel(hsotg, intmsk, GINTMSK); + + if (using_dma(hsotg)) { + dwc2_writel(hsotg, GAHBCFG_GLBL_INTR_EN | GAHBCFG_DMA_EN | + hsotg->params.ahbcfg, + GAHBCFG); + + /* Set DDMA mode support in the core if needed */ + if (using_desc_dma(hsotg)) + dwc2_set_bit(hsotg, DCFG, DCFG_DESCDMA_EN); + + } else { + dwc2_writel(hsotg, ((hsotg->dedicated_fifos) ? + (GAHBCFG_NP_TXF_EMP_LVL | + GAHBCFG_P_TXF_EMP_LVL) : 0) | + GAHBCFG_GLBL_INTR_EN, GAHBCFG); + } + + /* + * If INTknTXFEmpMsk is enabled, it's important to disable ep interrupts + * when we have no data to transfer. Otherwise we get being flooded by + * interrupts. + */ + + dwc2_writel(hsotg, ((hsotg->dedicated_fifos && !using_dma(hsotg)) ? + DIEPMSK_TXFIFOEMPTY | DIEPMSK_INTKNTXFEMPMSK : 0) | + DIEPMSK_EPDISBLDMSK | DIEPMSK_XFERCOMPLMSK | + DIEPMSK_TIMEOUTMSK | DIEPMSK_AHBERRMSK, + DIEPMSK); + + /* + * don't need XferCompl, we get that from RXFIFO in slave mode. In + * DMA mode we may need this and StsPhseRcvd. + */ + dwc2_writel(hsotg, (using_dma(hsotg) ? (DIEPMSK_XFERCOMPLMSK | + DOEPMSK_STSPHSERCVDMSK) : 0) | + DOEPMSK_EPDISBLDMSK | DOEPMSK_AHBERRMSK | + DOEPMSK_SETUPMSK, + DOEPMSK); + + /* Enable BNA interrupt for DDMA */ + if (using_desc_dma(hsotg)) { + dwc2_set_bit(hsotg, DOEPMSK, DOEPMSK_BNAMSK); + dwc2_set_bit(hsotg, DIEPMSK, DIEPMSK_BNAININTRMSK); + } + + /* Enable Service Interval mode if supported */ + if (using_desc_dma(hsotg) && hsotg->params.service_interval) + dwc2_set_bit(hsotg, DCTL, DCTL_SERVICE_INTERVAL_SUPPORTED); + + dwc2_writel(hsotg, 0, DAINTMSK); + + dev_dbg(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n", + dwc2_readl(hsotg, DIEPCTL0), + dwc2_readl(hsotg, DOEPCTL0)); + + /* enable in and out endpoint interrupts */ + dwc2_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)) + dwc2_hsotg_en_gsint(hsotg, GINTSTS_RXFLVL); + + /* Enable interrupts for EP0 in and out */ + dwc2_hsotg_ctrl_epint(hsotg, 0, 0, 1); + dwc2_hsotg_ctrl_epint(hsotg, 0, 1, 1); + + if (!is_usb_reset) { + dwc2_set_bit(hsotg, DCTL, DCTL_PWRONPRGDONE); + udelay(10); /* see openiboot */ + dwc2_clear_bit(hsotg, DCTL, DCTL_PWRONPRGDONE); + } + + dev_dbg(hsotg->dev, "DCTL=0x%08x\n", dwc2_readl(hsotg, DCTL)); + + /* + * DxEPCTL_USBActEp says RO in manual, but seems to be set by + * writing to the EPCTL register.. + */ + + /* set to read 1 8byte packet */ + dwc2_writel(hsotg, DXEPTSIZ_MC(1) | DXEPTSIZ_PKTCNT(1) | + DXEPTSIZ_XFERSIZE(8), DOEPTSIZ0); + + dwc2_writel(hsotg, dwc2_hsotg_ep0_mps(hsotg->eps_out[0]->ep.maxpacket) | + DXEPCTL_CNAK | DXEPCTL_EPENA | + DXEPCTL_USBACTEP, + DOEPCTL0); + + /* enable, but don't activate EP0in */ + dwc2_writel(hsotg, dwc2_hsotg_ep0_mps(hsotg->eps_out[0]->ep.maxpacket) | + DXEPCTL_USBACTEP, DIEPCTL0); + + /* clear global NAKs */ + val = DCTL_CGOUTNAK | DCTL_CGNPINNAK; + if (!is_usb_reset) + val |= DCTL_SFTDISCON; + dwc2_set_bit(hsotg, DCTL, val); + + /* configure the core to support LPM */ + dwc2_gadget_init_lpm(hsotg); + + /* program GREFCLK register if needed */ + if (using_desc_dma(hsotg) && hsotg->params.service_interval) + dwc2_gadget_program_ref_clk(hsotg); + + /* must be at-least 3ms to allow bus to see disconnect */ + mdelay(3); + + hsotg->lx_state = DWC2_L0; + + dwc2_hsotg_enqueue_setup(hsotg); + + dev_dbg(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n", + dwc2_readl(hsotg, DIEPCTL0), + dwc2_readl(hsotg, DOEPCTL0)); +} + +void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) +{ + /* set the soft-disconnect bit */ + dwc2_set_bit(hsotg, DCTL, DCTL_SFTDISCON); +} + +void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg) +{ + /* remove the soft-disconnect and let's go */ + if (!hsotg->role_sw || (dwc2_readl(hsotg, GOTGCTL) & GOTGCTL_BSESVLD)) + dwc2_clear_bit(hsotg, DCTL, DCTL_SFTDISCON); +} + +/** + * dwc2_gadget_handle_incomplete_isoc_in - handle incomplete ISO IN Interrupt. + * @hsotg: The device state: + * + * 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 following actions will be taken: + * - Determine the EP + * - Disable EP; when 'Endpoint Disabled' interrupt is received Flush FIFO + */ +static void dwc2_gadget_handle_incomplete_isoc_in(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hsotg_ep *hs_ep; + u32 epctrl; + u32 daintmsk; + u32 idx; + + dev_dbg(hsotg->dev, "Incomplete isoc in interrupt received:\n"); + + daintmsk = dwc2_readl(hsotg, DAINTMSK); + + for (idx = 1; idx < hsotg->num_of_eps; idx++) { + hs_ep = hsotg->eps_in[idx]; + /* Proceed only unmasked ISOC EPs */ + if ((BIT(idx) & ~daintmsk) || !hs_ep->isochronous) + continue; + + epctrl = dwc2_readl(hsotg, DIEPCTL(idx)); + if ((epctrl & DXEPCTL_EPENA) && + dwc2_gadget_target_frame_elapsed(hs_ep)) { + epctrl |= DXEPCTL_SNAK; + epctrl |= DXEPCTL_EPDIS; + dwc2_writel(hsotg, epctrl, DIEPCTL(idx)); + } + } + + /* Clear interrupt */ + dwc2_writel(hsotg, GINTSTS_INCOMPL_SOIN, GINTSTS); +} + +/** + * dwc2_gadget_handle_incomplete_isoc_out - handle incomplete ISO OUT Interrupt + * @hsotg: The device state: + * + * This interrupt indicates one of the following conditions occurred while + * transmitting an ISOC transaction. + * - Corrupted OUT Token for ISOC EP. + * - Packet not complete in FIFO. + * + * The following actions will be taken: + * - Determine the EP + * - Set DCTL_SGOUTNAK and unmask GOUTNAKEFF if target frame elapsed. + */ +static void dwc2_gadget_handle_incomplete_isoc_out(struct dwc2_hsotg *hsotg) +{ + u32 gintsts; + u32 gintmsk; + u32 daintmsk; + u32 epctrl; + struct dwc2_hsotg_ep *hs_ep; + int idx; + + dev_dbg(hsotg->dev, "%s: GINTSTS_INCOMPL_SOOUT\n", __func__); + + daintmsk = dwc2_readl(hsotg, DAINTMSK); + daintmsk >>= DAINT_OUTEP_SHIFT; + + for (idx = 1; idx < hsotg->num_of_eps; idx++) { + hs_ep = hsotg->eps_out[idx]; + /* Proceed only unmasked ISOC EPs */ + if ((BIT(idx) & ~daintmsk) || !hs_ep->isochronous) + continue; + + epctrl = dwc2_readl(hsotg, DOEPCTL(idx)); + if ((epctrl & DXEPCTL_EPENA) && + dwc2_gadget_target_frame_elapsed(hs_ep)) { + /* Unmask GOUTNAKEFF interrupt */ + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk |= GINTSTS_GOUTNAKEFF; + dwc2_writel(hsotg, gintmsk, GINTMSK); + + gintsts = dwc2_readl(hsotg, GINTSTS); + if (!(gintsts & GINTSTS_GOUTNAKEFF)) { + dwc2_set_bit(hsotg, DCTL, DCTL_SGOUTNAK); + break; + } + } + } + + /* Clear interrupt */ + dwc2_writel(hsotg, GINTSTS_INCOMPL_SOOUT, GINTSTS); +} + +/** + * dwc2_hsotg_irq - handle device interrupt + * @irq: The IRQ number triggered + * @pw: The pw value when registered the handler. + */ +static irqreturn_t dwc2_hsotg_irq(int irq, void *pw) +{ + struct dwc2_hsotg *hsotg = pw; + int retry_count = 8; + u32 gintsts; + u32 gintmsk; + + if (!dwc2_is_device_mode(hsotg)) + return IRQ_NONE; + + spin_lock(&hsotg->lock); +irq_retry: + gintsts = dwc2_readl(hsotg, GINTSTS); + gintmsk = dwc2_readl(hsotg, GINTMSK); + + dev_dbg(hsotg->dev, "%s: %08x %08x (%08x) retry %d\n", + __func__, gintsts, gintsts & gintmsk, gintmsk, retry_count); + + gintsts &= gintmsk; + + if (gintsts & GINTSTS_RESETDET) { + dev_dbg(hsotg->dev, "%s: USBRstDet\n", __func__); + + dwc2_writel(hsotg, GINTSTS_RESETDET, GINTSTS); + + /* This event must be used only if controller is suspended */ + if (hsotg->in_ppd && hsotg->lx_state == DWC2_L2) + dwc2_exit_partial_power_down(hsotg, 0, true); + + /* Exit gadget mode clock gating. */ + if (hsotg->params.power_down == + DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended && + !hsotg->params.no_clock_gating) + dwc2_gadget_exit_clock_gating(hsotg, 0); + + hsotg->lx_state = DWC2_L0; + } + + if (gintsts & (GINTSTS_USBRST | GINTSTS_RESETDET)) { + u32 usb_status = dwc2_readl(hsotg, GOTGCTL); + u32 connected = hsotg->connected; + + dev_dbg(hsotg->dev, "%s: USBRst\n", __func__); + dev_dbg(hsotg->dev, "GNPTXSTS=%08x\n", + dwc2_readl(hsotg, GNPTXSTS)); + + dwc2_writel(hsotg, GINTSTS_USBRST, GINTSTS); + + /* Report disconnection if it is not already done. */ + dwc2_hsotg_disconnect(hsotg); + + /* Reset device address to zero */ + dwc2_clear_bit(hsotg, DCFG, DCFG_DEVADDR_MASK); + + if (usb_status & GOTGCTL_BSESVLD && connected) + dwc2_hsotg_core_init_disconnected(hsotg, true); + } + + if (gintsts & GINTSTS_ENUMDONE) { + dwc2_writel(hsotg, GINTSTS_ENUMDONE, GINTSTS); + + dwc2_hsotg_irq_enumdone(hsotg); + } + + if (gintsts & (GINTSTS_OEPINT | GINTSTS_IEPINT)) { + u32 daint = dwc2_readl(hsotg, DAINT); + u32 daintmsk = dwc2_readl(hsotg, DAINTMSK); + u32 daint_out, daint_in; + int ep; + + daint &= daintmsk; + daint_out = daint >> DAINT_OUTEP_SHIFT; + daint_in = daint & ~(daint_out << DAINT_OUTEP_SHIFT); + + dev_dbg(hsotg->dev, "%s: daint=%08x\n", __func__, daint); + + for (ep = 0; ep < hsotg->num_of_eps && daint_out; + ep++, daint_out >>= 1) { + if (daint_out & 1) + dwc2_hsotg_epint(hsotg, ep, 0); + } + + for (ep = 0; ep < hsotg->num_of_eps && daint_in; + ep++, daint_in >>= 1) { + if (daint_in & 1) + dwc2_hsotg_epint(hsotg, ep, 1); + } + } + + /* 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 + */ + + dwc2_hsotg_disable_gsint(hsotg, GINTSTS_NPTXFEMP); + dwc2_hsotg_irq_fifoempty(hsotg, false); + } + + if (gintsts & GINTSTS_PTXFEMP) { + dev_dbg(hsotg->dev, "PTxFEmp\n"); + + /* See note in GINTSTS_NPTxFEmp */ + + dwc2_hsotg_disable_gsint(hsotg, GINTSTS_PTXFEMP); + dwc2_hsotg_irq_fifoempty(hsotg, true); + } + + if (gintsts & GINTSTS_RXFLVL) { + /* + * note, since GINTSTS_RxFLvl doubles as FIFO-not-empty, + * we need to retry dwc2_hsotg_handle_rx if this is still + * set. + */ + + dwc2_hsotg_handle_rx(hsotg); + } + + if (gintsts & GINTSTS_ERLYSUSP) { + dev_dbg(hsotg->dev, "GINTSTS_ErlySusp\n"); + dwc2_writel(hsotg, GINTSTS_ERLYSUSP, GINTSTS); + } + + /* + * 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) { + u8 idx; + u32 epctrl; + u32 gintmsk; + u32 daintmsk; + struct dwc2_hsotg_ep *hs_ep; + + daintmsk = dwc2_readl(hsotg, DAINTMSK); + daintmsk >>= DAINT_OUTEP_SHIFT; + /* Mask this interrupt */ + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk &= ~GINTSTS_GOUTNAKEFF; + dwc2_writel(hsotg, gintmsk, GINTMSK); + + dev_dbg(hsotg->dev, "GOUTNakEff triggered\n"); + for (idx = 1; idx < hsotg->num_of_eps; idx++) { + hs_ep = hsotg->eps_out[idx]; + /* Proceed only unmasked ISOC EPs */ + if (BIT(idx) & ~daintmsk) + continue; + + epctrl = dwc2_readl(hsotg, DOEPCTL(idx)); + + //ISOC Ep's only + if ((epctrl & DXEPCTL_EPENA) && hs_ep->isochronous) { + epctrl |= DXEPCTL_SNAK; + epctrl |= DXEPCTL_EPDIS; + dwc2_writel(hsotg, epctrl, DOEPCTL(idx)); + continue; + } + + //Non-ISOC EP's + if (hs_ep->halted) { + if (!(epctrl & DXEPCTL_EPENA)) + epctrl |= DXEPCTL_EPENA; + epctrl |= DXEPCTL_EPDIS; + epctrl |= DXEPCTL_STALL; + dwc2_writel(hsotg, epctrl, DOEPCTL(idx)); + } + } + + /* This interrupt bit is cleared in DXEPINT_EPDISBLD handler */ + } + + if (gintsts & GINTSTS_GINNAKEFF) { + dev_info(hsotg->dev, "GINNakEff triggered\n"); + + dwc2_set_bit(hsotg, DCTL, DCTL_CGNPINNAK); + + dwc2_hsotg_dump(hsotg); + } + + if (gintsts & GINTSTS_INCOMPL_SOIN) + dwc2_gadget_handle_incomplete_isoc_in(hsotg); + + if (gintsts & GINTSTS_INCOMPL_SOOUT) + dwc2_gadget_handle_incomplete_isoc_out(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; + + /* Check WKUP_ALERT interrupt*/ + if (hsotg->params.service_interval) + dwc2_gadget_wkup_alert_handler(hsotg); + + spin_unlock(&hsotg->lock); + + return IRQ_HANDLED; +} + +static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep) +{ + u32 epctrl_reg; + u32 epint_reg; + + epctrl_reg = hs_ep->dir_in ? DIEPCTL(hs_ep->index) : + DOEPCTL(hs_ep->index); + epint_reg = hs_ep->dir_in ? DIEPINT(hs_ep->index) : + DOEPINT(hs_ep->index); + + dev_dbg(hsotg->dev, "%s: stopping transfer on %s\n", __func__, + hs_ep->name); + + if (hs_ep->dir_in) { + if (hsotg->dedicated_fifos || hs_ep->periodic) { + dwc2_set_bit(hsotg, epctrl_reg, DXEPCTL_SNAK); + /* Wait for Nak effect */ + if (dwc2_hsotg_wait_bit_set(hsotg, epint_reg, + DXEPINT_INEPNAKEFF, 100)) + dev_warn(hsotg->dev, + "%s: timeout DIEPINT.NAKEFF\n", + __func__); + } else { + dwc2_set_bit(hsotg, DCTL, DCTL_SGNPINNAK); + /* Wait for Nak effect */ + if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, + GINTSTS_GINNAKEFF, 100)) + dev_warn(hsotg->dev, + "%s: timeout GINTSTS.GINNAKEFF\n", + __func__); + } + } else { + /* Mask GINTSTS_GOUTNAKEFF interrupt */ + dwc2_hsotg_disable_gsint(hsotg, GINTSTS_GOUTNAKEFF); + + if (!(dwc2_readl(hsotg, GINTSTS) & GINTSTS_GOUTNAKEFF)) + dwc2_set_bit(hsotg, DCTL, DCTL_SGOUTNAK); + + if (!using_dma(hsotg)) { + /* Wait for GINTSTS_RXFLVL interrupt */ + if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, + GINTSTS_RXFLVL, 100)) { + dev_warn(hsotg->dev, "%s: timeout GINTSTS.RXFLVL\n", + __func__); + } else { + /* + * Pop GLOBAL OUT NAK status packet from RxFIFO + * to assert GOUTNAKEFF interrupt + */ + dwc2_readl(hsotg, GRXSTSP); + } + } + + /* Wait for global nak to take effect */ + if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, + GINTSTS_GOUTNAKEFF, 100)) + dev_warn(hsotg->dev, "%s: timeout GINTSTS.GOUTNAKEFF\n", + __func__); + } + + /* Disable ep */ + dwc2_set_bit(hsotg, epctrl_reg, DXEPCTL_EPDIS | DXEPCTL_SNAK); + + /* Wait for ep to be disabled */ + if (dwc2_hsotg_wait_bit_set(hsotg, epint_reg, DXEPINT_EPDISBLD, 100)) + dev_warn(hsotg->dev, + "%s: timeout DOEPCTL.EPDisable\n", __func__); + + /* Clear EPDISBLD interrupt */ + dwc2_set_bit(hsotg, epint_reg, DXEPINT_EPDISBLD); + + if (hs_ep->dir_in) { + unsigned short fifo_index; + + if (hsotg->dedicated_fifos || hs_ep->periodic) + fifo_index = hs_ep->fifo_index; + else + fifo_index = 0; + + /* Flush TX FIFO */ + dwc2_flush_tx_fifo(hsotg, fifo_index); + + /* Clear Global In NP NAK in Shared FIFO for non periodic ep */ + if (!hsotg->dedicated_fifos && !hs_ep->periodic) + dwc2_set_bit(hsotg, DCTL, DCTL_CGNPINNAK); + + } else { + /* Remove global NAKs */ + dwc2_set_bit(hsotg, DCTL, DCTL_CGOUTNAK); + } +} + +/** + * dwc2_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 dwc2_hsotg_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct dwc2_hsotg_ep *hs_ep = our_ep(ep); + struct dwc2_hsotg *hsotg = hs_ep->parent; + unsigned long flags; + unsigned int index = hs_ep->index; + u32 epctrl_reg; + u32 epctrl; + u32 mps; + u32 mc; + u32 mask; + unsigned int dir_in; + unsigned int i, val, size; + int ret = 0; + unsigned char ep_type; + int desc_num; + + 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 */ + if (index == 0) { + dev_err(hsotg->dev, "%s: called for EP 0\n", __func__); + return -EINVAL; + } + + 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; + } + + ep_type = usb_endpoint_type(desc); + mps = usb_endpoint_maxp(desc); + mc = usb_endpoint_maxp_mult(desc); + + /* ISOC IN in DDMA supported bInterval up to 10 */ + if (using_desc_dma(hsotg) && ep_type == USB_ENDPOINT_XFER_ISOC && + dir_in && desc->bInterval > 10) { + dev_err(hsotg->dev, + "%s: ISOC IN, DDMA: bInterval>10 not supported!\n", __func__); + return -EINVAL; + } + + /* High bandwidth ISOC OUT in DDMA not supported */ + if (using_desc_dma(hsotg) && ep_type == USB_ENDPOINT_XFER_ISOC && + !dir_in && mc > 1) { + dev_err(hsotg->dev, + "%s: ISOC OUT, DDMA: HB not supported!\n", __func__); + return -EINVAL; + } + + /* note, we handle this here instead of dwc2_hsotg_set_ep_maxpacket */ + + epctrl_reg = dir_in ? DIEPCTL(index) : DOEPCTL(index); + epctrl = dwc2_readl(hsotg, epctrl_reg); + + dev_dbg(hsotg->dev, "%s: read DxEPCTL=0x%08x from 0x%08x\n", + __func__, epctrl, epctrl_reg); + + if (using_desc_dma(hsotg) && ep_type == USB_ENDPOINT_XFER_ISOC) + desc_num = MAX_DMA_DESC_NUM_HS_ISOC; + else + desc_num = MAX_DMA_DESC_NUM_GENERIC; + + /* Allocate DMA descriptor chain for non-ctrl endpoints */ + if (using_desc_dma(hsotg) && !hs_ep->desc_list) { + hs_ep->desc_list = dmam_alloc_coherent(hsotg->dev, + desc_num * sizeof(struct dwc2_dma_desc), + &hs_ep->desc_list_dma, GFP_ATOMIC); + if (!hs_ep->desc_list) { + ret = -ENOMEM; + goto error2; + } + } + + 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; + + /* update the endpoint state */ + dwc2_hsotg_set_ep_maxpacket(hsotg, hs_ep->index, mps, mc, dir_in); + + /* default, set to non-periodic */ + hs_ep->isochronous = 0; + hs_ep->periodic = 0; + hs_ep->halted = 0; + hs_ep->wedged = 0; + hs_ep->interval = desc->bInterval; + + switch (ep_type) { + case USB_ENDPOINT_XFER_ISOC: + epctrl |= DXEPCTL_EPTYPE_ISO; + epctrl |= DXEPCTL_SETEVENFR; + hs_ep->isochronous = 1; + hs_ep->interval = 1 << (desc->bInterval - 1); + hs_ep->target_frame = TARGET_FRAME_INITIAL; + hs_ep->next_desc = 0; + hs_ep->compl_desc = 0; + if (dir_in) { + hs_ep->periodic = 1; + mask = dwc2_readl(hsotg, DIEPMSK); + mask |= DIEPMSK_NAKMSK; + dwc2_writel(hsotg, mask, DIEPMSK); + } else { + epctrl |= DXEPCTL_SNAK; + mask = dwc2_readl(hsotg, DOEPMSK); + mask |= DOEPMSK_OUTTKNEPDISMSK; + dwc2_writel(hsotg, mask, DOEPMSK); + } + break; + + case USB_ENDPOINT_XFER_BULK: + epctrl |= DXEPCTL_EPTYPE_BULK; + break; + + case USB_ENDPOINT_XFER_INT: + if (dir_in) + hs_ep->periodic = 1; + + if (hsotg->gadget.speed == USB_SPEED_HIGH) + hs_ep->interval = 1 << (desc->bInterval - 1); + + epctrl |= DXEPCTL_EPTYPE_INTERRUPT; + 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) { + unsigned fifo_count = dwc2_hsotg_tx_fifo_count(hsotg); + u32 fifo_index = 0; + u32 fifo_size = UINT_MAX; + + size = hs_ep->ep.maxpacket * hs_ep->mc; + for (i = 1; i <= fifo_count; ++i) { + if (hsotg->fifo_map & (1 << i)) + continue; + val = dwc2_readl(hsotg, DPTXFSIZN(i)); + val = (val >> FIFOSIZE_DEPTH_SHIFT) * 4; + if (val < size) + continue; + /* Search for smallest acceptable fifo */ + if (val < fifo_size) { + fifo_size = val; + fifo_index = i; + } + } + if (!fifo_index) { + dev_err(hsotg->dev, + "%s: No suitable fifo found\n", __func__); + ret = -ENOMEM; + goto error1; + } + epctrl &= ~(DXEPCTL_TXFNUM_LIMIT << DXEPCTL_TXFNUM_SHIFT); + hsotg->fifo_map |= 1 << fifo_index; + epctrl |= DXEPCTL_TXFNUM(fifo_index); + hs_ep->fifo_index = fifo_index; + hs_ep->fifo_size = fifo_size; + } + + /* for non control endpoints, set PID to D0 */ + if (index && !hs_ep->isochronous) + epctrl |= DXEPCTL_SETD0PID; + + /* WA for Full speed ISOC IN in DDMA mode. + * By Clear NAK status of EP, core will send ZLP + * to IN token and assert NAK interrupt relying + * on TxFIFO status only + */ + + if (hsotg->gadget.speed == USB_SPEED_FULL && + hs_ep->isochronous && dir_in) { + /* The WA applies only to core versions from 2.72a + * to 4.00a (including both). Also for FS_IOT_1.00a + * and HS_IOT_1.00a. + */ + u32 gsnpsid = dwc2_readl(hsotg, GSNPSID); + + if ((gsnpsid >= DWC2_CORE_REV_2_72a && + gsnpsid <= DWC2_CORE_REV_4_00a) || + gsnpsid == DWC2_FS_IOT_REV_1_00a || + gsnpsid == DWC2_HS_IOT_REV_1_00a) + epctrl |= DXEPCTL_CNAK; + } + + dev_dbg(hsotg->dev, "%s: write DxEPCTL=0x%08x\n", + __func__, epctrl); + + dwc2_writel(hsotg, epctrl, epctrl_reg); + dev_dbg(hsotg->dev, "%s: read DxEPCTL=0x%08x\n", + __func__, dwc2_readl(hsotg, epctrl_reg)); + + /* enable the endpoint interrupt */ + dwc2_hsotg_ctrl_epint(hsotg, index, dir_in, 1); + +error1: + spin_unlock_irqrestore(&hsotg->lock, flags); + +error2: + if (ret && using_desc_dma(hsotg) && hs_ep->desc_list) { + dmam_free_coherent(hsotg->dev, desc_num * + sizeof(struct dwc2_dma_desc), + hs_ep->desc_list, hs_ep->desc_list_dma); + hs_ep->desc_list = NULL; + } + + return ret; +} + +/** + * dwc2_hsotg_ep_disable - disable given endpoint + * @ep: The endpoint to disable. + */ +static int dwc2_hsotg_ep_disable(struct usb_ep *ep) +{ + struct dwc2_hsotg_ep *hs_ep = our_ep(ep); + struct dwc2_hsotg *hsotg = hs_ep->parent; + int dir_in = hs_ep->dir_in; + int index = hs_ep->index; + u32 epctrl_reg; + u32 ctrl; + + dev_dbg(hsotg->dev, "%s(ep %p)\n", __func__, ep); + + if (ep == &hsotg->eps_out[0]->ep) { + dev_err(hsotg->dev, "%s: called for ep0\n", __func__); + return -EINVAL; + } + + if (hsotg->op_state != OTG_STATE_B_PERIPHERAL) { + dev_err(hsotg->dev, "%s: called in host mode?\n", __func__); + return -EINVAL; + } + + epctrl_reg = dir_in ? DIEPCTL(index) : DOEPCTL(index); + + ctrl = dwc2_readl(hsotg, epctrl_reg); + + if (ctrl & DXEPCTL_EPENA) + dwc2_hsotg_ep_stop_xfr(hsotg, hs_ep); + + ctrl &= ~DXEPCTL_EPENA; + ctrl &= ~DXEPCTL_USBACTEP; + ctrl |= DXEPCTL_SNAK; + + dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x\n", __func__, ctrl); + dwc2_writel(hsotg, ctrl, epctrl_reg); + + /* disable endpoint interrupts */ + dwc2_hsotg_ctrl_epint(hsotg, hs_ep->index, hs_ep->dir_in, 0); + + /* terminate all requests with shutdown */ + kill_all_requests(hsotg, hs_ep, -ESHUTDOWN); + + hsotg->fifo_map &= ~(1 << hs_ep->fifo_index); + hs_ep->fifo_index = 0; + hs_ep->fifo_size = 0; + + return 0; +} + +static int dwc2_hsotg_ep_disable_lock(struct usb_ep *ep) +{ + struct dwc2_hsotg_ep *hs_ep = our_ep(ep); + struct dwc2_hsotg *hsotg = hs_ep->parent; + unsigned long flags; + int ret; + + spin_lock_irqsave(&hsotg->lock, flags); + ret = dwc2_hsotg_ep_disable(ep); + spin_unlock_irqrestore(&hsotg->lock, flags); + return ret; +} + +/** + * 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 dwc2_hsotg_ep *ep, struct dwc2_hsotg_req *test) +{ + struct dwc2_hsotg_req *req, *treq; + + list_for_each_entry_safe(req, treq, &ep->queue, queue) { + if (req == test) + return true; + } + + return false; +} + +/** + * dwc2_hsotg_ep_dequeue - dequeue given endpoint + * @ep: The endpoint to dequeue. + * @req: The request to be removed from a queue. + */ +static int dwc2_hsotg_ep_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + struct dwc2_hsotg_req *hs_req = our_req(req); + struct dwc2_hsotg_ep *hs_ep = our_ep(ep); + struct dwc2_hsotg *hs = hs_ep->parent; + unsigned long flags; + + dev_dbg(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; + } + + /* Dequeue already started request */ + if (req == &hs_ep->req->req) + dwc2_hsotg_ep_stop_xfr(hs, hs_ep); + + dwc2_hsotg_complete_request(hs, hs_ep, hs_req, -ECONNRESET); + spin_unlock_irqrestore(&hs->lock, flags); + + return 0; +} + +/** + * dwc2_gadget_ep_set_wedge - set wedge on a given endpoint + * @ep: The endpoint to be wedged. + * + */ +static int dwc2_gadget_ep_set_wedge(struct usb_ep *ep) +{ + struct dwc2_hsotg_ep *hs_ep = our_ep(ep); + struct dwc2_hsotg *hs = hs_ep->parent; + + unsigned long flags; + int ret; + + spin_lock_irqsave(&hs->lock, flags); + hs_ep->wedged = 1; + ret = dwc2_hsotg_ep_sethalt(ep, 1, false); + spin_unlock_irqrestore(&hs->lock, flags); + + return ret; +} + +/** + * dwc2_hsotg_ep_sethalt - set halt on a given endpoint + * @ep: The endpoint to set halt. + * @value: Set or unset the halt. + * @now: If true, stall the endpoint now. Otherwise return -EAGAIN if + * the endpoint is busy processing requests. + * + * We need to stall the endpoint immediately if request comes from set_feature + * protocol command handler. + */ +static int dwc2_hsotg_ep_sethalt(struct usb_ep *ep, int value, bool now) +{ + struct dwc2_hsotg_ep *hs_ep = our_ep(ep); + struct dwc2_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); + + if (index == 0) { + if (value) + dwc2_hsotg_stall_ep0(hs); + else + dev_warn(hs->dev, + "%s: can't clear halt on ep0\n", __func__); + return 0; + } + + if (hs_ep->isochronous) { + dev_err(hs->dev, "%s is Isochronous Endpoint\n", ep->name); + return -EINVAL; + } + + if (!now && value && !list_empty(&hs_ep->queue)) { + dev_dbg(hs->dev, "%s request is pending, cannot halt\n", + ep->name); + return -EAGAIN; + } + + if (hs_ep->dir_in) { + epreg = DIEPCTL(index); + epctl = dwc2_readl(hs, epreg); + + if (value) { + epctl |= DXEPCTL_STALL | DXEPCTL_SNAK; + if (epctl & DXEPCTL_EPENA) + epctl |= DXEPCTL_EPDIS; + } else { + epctl &= ~DXEPCTL_STALL; + hs_ep->wedged = 0; + xfertype = epctl & DXEPCTL_EPTYPE_MASK; + if (xfertype == DXEPCTL_EPTYPE_BULK || + xfertype == DXEPCTL_EPTYPE_INTERRUPT) + epctl |= DXEPCTL_SETD0PID; + } + dwc2_writel(hs, epctl, epreg); + } else { + epreg = DOEPCTL(index); + epctl = dwc2_readl(hs, epreg); + + if (value) { + /* Unmask GOUTNAKEFF interrupt */ + dwc2_hsotg_en_gsint(hs, GINTSTS_GOUTNAKEFF); + + if (!(dwc2_readl(hs, GINTSTS) & GINTSTS_GOUTNAKEFF)) + dwc2_set_bit(hs, DCTL, DCTL_SGOUTNAK); + // STALL bit will be set in GOUTNAKEFF interrupt handler + } else { + epctl &= ~DXEPCTL_STALL; + hs_ep->wedged = 0; + xfertype = epctl & DXEPCTL_EPTYPE_MASK; + if (xfertype == DXEPCTL_EPTYPE_BULK || + xfertype == DXEPCTL_EPTYPE_INTERRUPT) + epctl |= DXEPCTL_SETD0PID; + dwc2_writel(hs, epctl, epreg); + } + } + + hs_ep->halted = value; + return 0; +} + +/** + * dwc2_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 dwc2_hsotg_ep_sethalt_lock(struct usb_ep *ep, int value) +{ + struct dwc2_hsotg_ep *hs_ep = our_ep(ep); + struct dwc2_hsotg *hs = hs_ep->parent; + unsigned long flags; + int ret; + + spin_lock_irqsave(&hs->lock, flags); + ret = dwc2_hsotg_ep_sethalt(ep, value, false); + spin_unlock_irqrestore(&hs->lock, flags); + + return ret; +} + +static const struct usb_ep_ops dwc2_hsotg_ep_ops = { + .enable = dwc2_hsotg_ep_enable, + .disable = dwc2_hsotg_ep_disable_lock, + .alloc_request = dwc2_hsotg_ep_alloc_request, + .free_request = dwc2_hsotg_ep_free_request, + .queue = dwc2_hsotg_ep_queue_lock, + .dequeue = dwc2_hsotg_ep_dequeue, + .set_halt = dwc2_hsotg_ep_sethalt_lock, + .set_wedge = dwc2_gadget_ep_set_wedge, + /* note, don't believe we have any call for the fifo routines */ +}; + +/** + * dwc2_hsotg_init - initialize the usb core + * @hsotg: The driver state + */ +static void dwc2_hsotg_init(struct dwc2_hsotg *hsotg) +{ + /* unmask subset of endpoint interrupts */ + + dwc2_writel(hsotg, DIEPMSK_TIMEOUTMSK | DIEPMSK_AHBERRMSK | + DIEPMSK_EPDISBLDMSK | DIEPMSK_XFERCOMPLMSK, + DIEPMSK); + + dwc2_writel(hsotg, DOEPMSK_SETUPMSK | DOEPMSK_AHBERRMSK | + DOEPMSK_EPDISBLDMSK | DOEPMSK_XFERCOMPLMSK, + DOEPMSK); + + dwc2_writel(hsotg, 0, DAINTMSK); + + /* Be in disconnected state until gadget is registered */ + dwc2_set_bit(hsotg, DCTL, DCTL_SFTDISCON); + + /* setup fifos */ + + dev_dbg(hsotg->dev, "GRXFSIZ=0x%08x, GNPTXFSIZ=0x%08x\n", + dwc2_readl(hsotg, GRXFSIZ), + dwc2_readl(hsotg, GNPTXFSIZ)); + + dwc2_hsotg_init_fifo(hsotg); + + if (using_dma(hsotg)) + dwc2_set_bit(hsotg, GAHBCFG, GAHBCFG_DMA_EN); +} + +/** + * dwc2_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 dwc2_hsotg_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct dwc2_hsotg *hsotg = to_hsotg(gadget); + unsigned long flags; + int ret; + + if (!hsotg) { + pr_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); + + hsotg->driver = driver; + hsotg->gadget.dev.of_node = hsotg->dev->of_node; + hsotg->gadget.speed = USB_SPEED_UNKNOWN; + + if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) { + ret = dwc2_lowlevel_hw_enable(hsotg); + if (ret) + goto err; + } + + if (!IS_ERR_OR_NULL(hsotg->uphy)) + otg_set_peripheral(hsotg->uphy->otg, &hsotg->gadget); + + spin_lock_irqsave(&hsotg->lock, flags); + if (dwc2_hw_is_device(hsotg)) { + dwc2_hsotg_init(hsotg); + dwc2_hsotg_core_init_disconnected(hsotg, false); + } + + hsotg->enabled = 0; + spin_unlock_irqrestore(&hsotg->lock, flags); + + gadget->sg_supported = using_desc_dma(hsotg); + dev_info(hsotg->dev, "bound driver %s\n", driver->driver.name); + + return 0; + +err: + hsotg->driver = NULL; + return ret; +} + +/** + * dwc2_hsotg_udc_stop - stop the udc + * @gadget: The usb gadget state + * + * Stop udc hw block and stay tunned for future transmissions + */ +static int dwc2_hsotg_udc_stop(struct usb_gadget *gadget) +{ + struct dwc2_hsotg *hsotg = to_hsotg(gadget); + unsigned long flags; + int ep; + + if (!hsotg) + return -ENODEV; + + /* Exit clock gating when driver is stopped. */ + if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE && + hsotg->bus_suspended && !hsotg->params.no_clock_gating) { + spin_lock_irqsave(&hsotg->lock, flags); + dwc2_gadget_exit_clock_gating(hsotg, 0); + spin_unlock_irqrestore(&hsotg->lock, flags); + } + + /* all endpoints should be shutdown */ + for (ep = 1; ep < hsotg->num_of_eps; ep++) { + if (hsotg->eps_in[ep]) + dwc2_hsotg_ep_disable_lock(&hsotg->eps_in[ep]->ep); + if (hsotg->eps_out[ep]) + dwc2_hsotg_ep_disable_lock(&hsotg->eps_out[ep]->ep); + } + + spin_lock_irqsave(&hsotg->lock, flags); + + hsotg->driver = NULL; + hsotg->gadget.dev.of_node = NULL; + hsotg->gadget.speed = USB_SPEED_UNKNOWN; + hsotg->enabled = 0; + + spin_unlock_irqrestore(&hsotg->lock, flags); + + if (!IS_ERR_OR_NULL(hsotg->uphy)) + otg_set_peripheral(hsotg->uphy->otg, NULL); + + if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) + dwc2_lowlevel_hw_disable(hsotg); + + return 0; +} + +/** + * dwc2_hsotg_gadget_getframe - read the frame number + * @gadget: The usb gadget state + * + * Read the {micro} frame number + */ +static int dwc2_hsotg_gadget_getframe(struct usb_gadget *gadget) +{ + return dwc2_hsotg_read_frameno(to_hsotg(gadget)); +} + +/** + * dwc2_hsotg_set_selfpowered - set if device is self/bus powered + * @gadget: The usb gadget state + * @is_selfpowered: Whether the device is self-powered + * + * Set if the device is self or bus powered. + */ +static int dwc2_hsotg_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct dwc2_hsotg *hsotg = to_hsotg(gadget); + unsigned long flags; + + spin_lock_irqsave(&hsotg->lock, flags); + gadget->is_selfpowered = !!is_selfpowered; + spin_unlock_irqrestore(&hsotg->lock, flags); + + return 0; +} + +/** + * dwc2_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 dwc2_hsotg_pullup(struct usb_gadget *gadget, int is_on) +{ + struct dwc2_hsotg *hsotg = to_hsotg(gadget); + unsigned long flags; + int ret = 0; + + dev_dbg(hsotg->dev, "%s: is_on: %d op_state: %d\n", __func__, is_on, + hsotg->op_state); + + /* Don't modify pullup state while in host mode */ + if (hsotg->op_state != OTG_STATE_B_PERIPHERAL) { + hsotg->enabled = is_on; + return 0; + } + + spin_lock_irqsave(&hsotg->lock, flags); + if (hsotg->in_ppd) { + ret = dwc2_exit_partial_power_down(hsotg, 0, true); + if (ret) { + dev_err(hsotg->dev, "exit partial_power_down failed\n"); + goto exit; + } + } + if (is_on) { + hsotg->enabled = 1; + dwc2_hsotg_core_init_disconnected(hsotg, false); + /* Enable ACG feature in device mode,if supported */ + dwc2_enable_acg(hsotg); + dwc2_hsotg_core_connect(hsotg); + } else { + dwc2_hsotg_core_disconnect(hsotg); + dwc2_hsotg_disconnect(hsotg); + hsotg->enabled = 0; + } + + hsotg->gadget.speed = USB_SPEED_UNKNOWN; +exit: + spin_unlock_irqrestore(&hsotg->lock, flags); + + return ret; +} + +static int dwc2_hsotg_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct dwc2_hsotg *hsotg = to_hsotg(gadget); + unsigned long flags; + + dev_dbg(hsotg->dev, "%s: is_active: %d\n", __func__, is_active); + spin_lock_irqsave(&hsotg->lock, flags); + + /* + * If controller is in partial power down state, it must exit from + * that state before being initialized / de-initialized + */ + if (hsotg->lx_state == DWC2_L2 && hsotg->in_ppd) + /* + * No need to check the return value as + * registers are not being restored. + */ + dwc2_exit_partial_power_down(hsotg, 0, false); + + if (is_active) { + hsotg->op_state = OTG_STATE_B_PERIPHERAL; + + dwc2_hsotg_core_init_disconnected(hsotg, false); + if (hsotg->enabled) { + /* Enable ACG feature in device mode,if supported */ + dwc2_enable_acg(hsotg); + dwc2_hsotg_core_connect(hsotg); + } + } else { + dwc2_hsotg_core_disconnect(hsotg); + dwc2_hsotg_disconnect(hsotg); + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + return 0; +} + +/** + * dwc2_hsotg_vbus_draw - report bMaxPower field + * @gadget: The usb gadget state + * @mA: Amount of current + * + * Report how much power the device may consume to the phy. + */ +static int dwc2_hsotg_vbus_draw(struct usb_gadget *gadget, unsigned int mA) +{ + struct dwc2_hsotg *hsotg = to_hsotg(gadget); + + if (IS_ERR_OR_NULL(hsotg->uphy)) + return -ENOTSUPP; + return usb_phy_set_power(hsotg->uphy, mA); +} + +static void dwc2_gadget_set_speed(struct usb_gadget *g, enum usb_device_speed speed) +{ + struct dwc2_hsotg *hsotg = to_hsotg(g); + unsigned long flags; + + spin_lock_irqsave(&hsotg->lock, flags); + switch (speed) { + case USB_SPEED_HIGH: + hsotg->params.speed = DWC2_SPEED_PARAM_HIGH; + break; + case USB_SPEED_FULL: + hsotg->params.speed = DWC2_SPEED_PARAM_FULL; + break; + case USB_SPEED_LOW: + hsotg->params.speed = DWC2_SPEED_PARAM_LOW; + break; + default: + dev_err(hsotg->dev, "invalid speed (%d)\n", speed); + } + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +static const struct usb_gadget_ops dwc2_hsotg_gadget_ops = { + .get_frame = dwc2_hsotg_gadget_getframe, + .set_selfpowered = dwc2_hsotg_set_selfpowered, + .udc_start = dwc2_hsotg_udc_start, + .udc_stop = dwc2_hsotg_udc_stop, + .pullup = dwc2_hsotg_pullup, + .udc_set_speed = dwc2_gadget_set_speed, + .vbus_session = dwc2_hsotg_vbus_session, + .vbus_draw = dwc2_hsotg_vbus_draw, +}; + +/** + * dwc2_hsotg_initep - initialise a single endpoint + * @hsotg: The device state. + * @hs_ep: The endpoint to be initialised. + * @epnum: The endpoint number + * @dir_in: True if direction is in. + * + * 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 dwc2_hsotg_initep(struct dwc2_hsotg *hsotg, + struct dwc2_hsotg_ep *hs_ep, + int epnum, + bool dir_in) +{ + char *dir; + + if (epnum == 0) + dir = ""; + else if (dir_in) + dir = "in"; + else + dir = "out"; + + hs_ep->dir_in = dir_in; + 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; + + if (hsotg->params.speed == DWC2_SPEED_PARAM_LOW) + usb_ep_set_maxpacket_limit(&hs_ep->ep, 8); + else + usb_ep_set_maxpacket_limit(&hs_ep->ep, + epnum ? 1024 : EP0_MPS_LIMIT); + hs_ep->ep.ops = &dwc2_hsotg_ep_ops; + + if (epnum == 0) { + hs_ep->ep.caps.type_control = true; + } else { + if (hsotg->params.speed != DWC2_SPEED_PARAM_LOW) { + hs_ep->ep.caps.type_iso = true; + hs_ep->ep.caps.type_bulk = true; + } + hs_ep->ep.caps.type_int = true; + } + + if (dir_in) + hs_ep->ep.caps.dir_in = true; + else + hs_ep->ep.caps.dir_out = true; + + /* + * 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); + + if (dir_in) + dwc2_writel(hsotg, next, DIEPCTL(epnum)); + else + dwc2_writel(hsotg, next, DOEPCTL(epnum)); + } +} + +/** + * dwc2_hsotg_hw_cfg - read HW configuration registers + * @hsotg: Programming view of the DWC_otg controller + * + * Read the USB core HW configuration registers + */ +static int dwc2_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) +{ + u32 cfg; + u32 ep_type; + u32 i; + + /* check hardware configuration */ + + hsotg->num_of_eps = hsotg->hw_params.num_dev_ep; + + /* Add ep0 */ + hsotg->num_of_eps++; + + hsotg->eps_in[0] = devm_kzalloc(hsotg->dev, + sizeof(struct dwc2_hsotg_ep), + GFP_KERNEL); + if (!hsotg->eps_in[0]) + return -ENOMEM; + /* Same dwc2_hsotg_ep is used in both directions for ep0 */ + hsotg->eps_out[0] = hsotg->eps_in[0]; + + cfg = hsotg->hw_params.dev_ep_dirs; + for (i = 1, cfg >>= 2; i < hsotg->num_of_eps; i++, cfg >>= 2) { + ep_type = cfg & 3; + /* Direction in or both */ + if (!(ep_type & 2)) { + hsotg->eps_in[i] = devm_kzalloc(hsotg->dev, + sizeof(struct dwc2_hsotg_ep), GFP_KERNEL); + if (!hsotg->eps_in[i]) + return -ENOMEM; + } + /* Direction out or both */ + if (!(ep_type & 1)) { + hsotg->eps_out[i] = devm_kzalloc(hsotg->dev, + sizeof(struct dwc2_hsotg_ep), GFP_KERNEL); + if (!hsotg->eps_out[i]) + return -ENOMEM; + } + } + + hsotg->fifo_mem = hsotg->hw_params.total_fifo_size; + hsotg->dedicated_fifos = hsotg->hw_params.en_multiple_tx_fifo; + + dev_info(hsotg->dev, "EPs: %d, %s fifos, %d entries in SPRAM\n", + hsotg->num_of_eps, + hsotg->dedicated_fifos ? "dedicated" : "shared", + hsotg->fifo_mem); + return 0; +} + +/** + * dwc2_hsotg_dump - dump state of the udc + * @hsotg: Programming view of the DWC_otg controller + * + */ +static void dwc2_hsotg_dump(struct dwc2_hsotg *hsotg) +{ +#ifdef DEBUG + struct device *dev = hsotg->dev; + u32 val; + int idx; + + dev_info(dev, "DCFG=0x%08x, DCTL=0x%08x, DIEPMSK=%08x\n", + dwc2_readl(hsotg, DCFG), dwc2_readl(hsotg, DCTL), + dwc2_readl(hsotg, DIEPMSK)); + + dev_info(dev, "GAHBCFG=0x%08x, GHWCFG1=0x%08x\n", + dwc2_readl(hsotg, GAHBCFG), dwc2_readl(hsotg, GHWCFG1)); + + dev_info(dev, "GRXFSIZ=0x%08x, GNPTXFSIZ=0x%08x\n", + dwc2_readl(hsotg, GRXFSIZ), dwc2_readl(hsotg, GNPTXFSIZ)); + + /* show periodic fifo settings */ + + for (idx = 1; idx < hsotg->num_of_eps; idx++) { + val = dwc2_readl(hsotg, DPTXFSIZN(idx)); + dev_info(dev, "DPTx[%d] FSize=%d, StAddr=0x%08x\n", idx, + val >> FIFOSIZE_DEPTH_SHIFT, + val & FIFOSIZE_STARTADDR_MASK); + } + + for (idx = 0; idx < hsotg->num_of_eps; idx++) { + dev_info(dev, + "ep%d-in: EPCTL=0x%08x, SIZ=0x%08x, DMA=0x%08x\n", idx, + dwc2_readl(hsotg, DIEPCTL(idx)), + dwc2_readl(hsotg, DIEPTSIZ(idx)), + dwc2_readl(hsotg, DIEPDMA(idx))); + + val = dwc2_readl(hsotg, DOEPCTL(idx)); + dev_info(dev, + "ep%d-out: EPCTL=0x%08x, SIZ=0x%08x, DMA=0x%08x\n", + idx, dwc2_readl(hsotg, DOEPCTL(idx)), + dwc2_readl(hsotg, DOEPTSIZ(idx)), + dwc2_readl(hsotg, DOEPDMA(idx))); + } + + dev_info(dev, "DVBUSDIS=0x%08x, DVBUSPULSE=%08x\n", + dwc2_readl(hsotg, DVBUSDIS), dwc2_readl(hsotg, DVBUSPULSE)); +#endif +} + +/** + * dwc2_gadget_init - init function for gadget + * @hsotg: Programming view of the DWC_otg controller + * + */ +int dwc2_gadget_init(struct dwc2_hsotg *hsotg) +{ + struct device *dev = hsotg->dev; + int epnum; + int ret; + + /* Dump fifo information */ + dev_dbg(dev, "NonPeriodic TXFIFO size: %d\n", + hsotg->params.g_np_tx_fifo_size); + dev_dbg(dev, "RXFIFO size: %d\n", hsotg->params.g_rx_fifo_size); + + switch (hsotg->params.speed) { + case DWC2_SPEED_PARAM_LOW: + hsotg->gadget.max_speed = USB_SPEED_LOW; + break; + case DWC2_SPEED_PARAM_FULL: + hsotg->gadget.max_speed = USB_SPEED_FULL; + break; + default: + hsotg->gadget.max_speed = USB_SPEED_HIGH; + break; + } + + hsotg->gadget.ops = &dwc2_hsotg_gadget_ops; + hsotg->gadget.name = dev_name(dev); + hsotg->gadget.otg_caps = &hsotg->params.otg_caps; + hsotg->remote_wakeup_allowed = 0; + + if (hsotg->params.lpm) + hsotg->gadget.lpm_capable = true; + + if (hsotg->dr_mode == USB_DR_MODE_OTG) + hsotg->gadget.is_otg = 1; + else if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) + hsotg->op_state = OTG_STATE_B_PERIPHERAL; + + ret = dwc2_hsotg_hw_cfg(hsotg); + if (ret) { + dev_err(hsotg->dev, "Hardware configuration failed: %d\n", ret); + return ret; + } + + hsotg->ctrl_buff = devm_kzalloc(hsotg->dev, + DWC2_CTRL_BUFF_SIZE, GFP_KERNEL); + if (!hsotg->ctrl_buff) + return -ENOMEM; + + hsotg->ep0_buff = devm_kzalloc(hsotg->dev, + DWC2_CTRL_BUFF_SIZE, GFP_KERNEL); + if (!hsotg->ep0_buff) + return -ENOMEM; + + if (using_desc_dma(hsotg)) { + ret = dwc2_gadget_alloc_ctrl_desc_chains(hsotg); + if (ret < 0) + return ret; + } + + ret = devm_request_irq(hsotg->dev, hsotg->irq, dwc2_hsotg_irq, + IRQF_SHARED, dev_name(hsotg->dev), hsotg); + if (ret < 0) { + dev_err(dev, "cannot claim IRQ for gadget\n"); + return ret; + } + + /* 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"); + return -EINVAL; + } + + /* setup endpoint information */ + + INIT_LIST_HEAD(&hsotg->gadget.ep_list); + hsotg->gadget.ep0 = &hsotg->eps_out[0]->ep; + + /* allocate EP0 request */ + + hsotg->ctrl_req = dwc2_hsotg_ep_alloc_request(&hsotg->eps_out[0]->ep, + GFP_KERNEL); + if (!hsotg->ctrl_req) { + dev_err(dev, "failed to allocate ctrl req\n"); + return -ENOMEM; + } + + /* initialise the endpoints now the core has been initialised */ + for (epnum = 0; epnum < hsotg->num_of_eps; epnum++) { + if (hsotg->eps_in[epnum]) + dwc2_hsotg_initep(hsotg, hsotg->eps_in[epnum], + epnum, 1); + if (hsotg->eps_out[epnum]) + dwc2_hsotg_initep(hsotg, hsotg->eps_out[epnum], + epnum, 0); + } + + dwc2_hsotg_dump(hsotg); + + return 0; +} + +/** + * dwc2_hsotg_remove - remove function for hsotg driver + * @hsotg: Programming view of the DWC_otg controller + * + */ +int dwc2_hsotg_remove(struct dwc2_hsotg *hsotg) +{ + usb_del_gadget_udc(&hsotg->gadget); + dwc2_hsotg_ep_free_request(&hsotg->eps_out[0]->ep, hsotg->ctrl_req); + + return 0; +} + +int dwc2_hsotg_suspend(struct dwc2_hsotg *hsotg) +{ + unsigned long flags; + + if (hsotg->lx_state != DWC2_L0) + return 0; + + if (hsotg->driver) { + int ep; + + dev_info(hsotg->dev, "suspending usb gadget %s\n", + hsotg->driver->driver.name); + + spin_lock_irqsave(&hsotg->lock, flags); + if (hsotg->enabled) + dwc2_hsotg_core_disconnect(hsotg); + dwc2_hsotg_disconnect(hsotg); + hsotg->gadget.speed = USB_SPEED_UNKNOWN; + spin_unlock_irqrestore(&hsotg->lock, flags); + + for (ep = 1; ep < hsotg->num_of_eps; ep++) { + if (hsotg->eps_in[ep]) + dwc2_hsotg_ep_disable_lock(&hsotg->eps_in[ep]->ep); + if (hsotg->eps_out[ep]) + dwc2_hsotg_ep_disable_lock(&hsotg->eps_out[ep]->ep); + } + } + + return 0; +} + +int dwc2_hsotg_resume(struct dwc2_hsotg *hsotg) +{ + unsigned long flags; + + if (hsotg->lx_state == DWC2_L2) + return 0; + + if (hsotg->driver) { + dev_info(hsotg->dev, "resuming usb gadget %s\n", + hsotg->driver->driver.name); + + spin_lock_irqsave(&hsotg->lock, flags); + dwc2_hsotg_core_init_disconnected(hsotg, false); + if (hsotg->enabled) { + /* Enable ACG feature in device mode,if supported */ + dwc2_enable_acg(hsotg); + dwc2_hsotg_core_connect(hsotg); + } + spin_unlock_irqrestore(&hsotg->lock, flags); + } + + return 0; +} + +/** + * dwc2_backup_device_registers() - Backup controller device registers. + * When suspending usb bus, registers needs to be backuped + * if controller power is disabled once suspended. + * + * @hsotg: Programming view of the DWC_otg controller + */ +int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg) +{ + struct dwc2_dregs_backup *dr; + int i; + + dev_dbg(hsotg->dev, "%s\n", __func__); + + /* Backup dev regs */ + dr = &hsotg->dr_backup; + + dr->dcfg = dwc2_readl(hsotg, DCFG); + dr->dctl = dwc2_readl(hsotg, DCTL); + dr->daintmsk = dwc2_readl(hsotg, DAINTMSK); + dr->diepmsk = dwc2_readl(hsotg, DIEPMSK); + dr->doepmsk = dwc2_readl(hsotg, DOEPMSK); + + for (i = 0; i < hsotg->num_of_eps; i++) { + /* Backup IN EPs */ + dr->diepctl[i] = dwc2_readl(hsotg, DIEPCTL(i)); + + /* Ensure DATA PID is correctly configured */ + if (dr->diepctl[i] & DXEPCTL_DPID) + dr->diepctl[i] |= DXEPCTL_SETD1PID; + else + dr->diepctl[i] |= DXEPCTL_SETD0PID; + + dr->dieptsiz[i] = dwc2_readl(hsotg, DIEPTSIZ(i)); + dr->diepdma[i] = dwc2_readl(hsotg, DIEPDMA(i)); + + /* Backup OUT EPs */ + dr->doepctl[i] = dwc2_readl(hsotg, DOEPCTL(i)); + + /* Ensure DATA PID is correctly configured */ + if (dr->doepctl[i] & DXEPCTL_DPID) + dr->doepctl[i] |= DXEPCTL_SETD1PID; + else + dr->doepctl[i] |= DXEPCTL_SETD0PID; + + dr->doeptsiz[i] = dwc2_readl(hsotg, DOEPTSIZ(i)); + dr->doepdma[i] = dwc2_readl(hsotg, DOEPDMA(i)); + dr->dtxfsiz[i] = dwc2_readl(hsotg, DPTXFSIZN(i)); + } + dr->valid = true; + return 0; +} + +/** + * dwc2_restore_device_registers() - Restore controller device registers. + * When resuming usb bus, device registers needs to be restored + * if controller power were disabled. + * + * @hsotg: Programming view of the DWC_otg controller + * @flags: Defines which registers should be restored. + * + * Return: 0 if successful, negative error code otherwise + */ +int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg, unsigned int flags) +{ + struct dwc2_dregs_backup *dr; + int i; + + dev_dbg(hsotg->dev, "%s\n", __func__); + + /* Restore dev regs */ + dr = &hsotg->dr_backup; + if (!dr->valid) { + dev_err(hsotg->dev, "%s: no device registers to restore\n", + __func__); + return -EINVAL; + } + dr->valid = false; + + if (flags & DWC2_RESTORE_DCFG) + dwc2_writel(hsotg, dr->dcfg, DCFG); + + if (flags & DWC2_RESTORE_DCTL) + dwc2_writel(hsotg, dr->dctl, DCTL); + + dwc2_writel(hsotg, dr->daintmsk, DAINTMSK); + dwc2_writel(hsotg, dr->diepmsk, DIEPMSK); + dwc2_writel(hsotg, dr->doepmsk, DOEPMSK); + + for (i = 0; i < hsotg->num_of_eps; i++) { + /* Restore IN EPs */ + dwc2_writel(hsotg, dr->dieptsiz[i], DIEPTSIZ(i)); + dwc2_writel(hsotg, dr->diepdma[i], DIEPDMA(i)); + dwc2_writel(hsotg, dr->doeptsiz[i], DOEPTSIZ(i)); + /** WA for enabled EPx's IN in DDMA mode. On entering to + * hibernation wrong value read and saved from DIEPDMAx, + * as result BNA interrupt asserted on hibernation exit + * by restoring from saved area. + */ + if (using_desc_dma(hsotg) && + (dr->diepctl[i] & DXEPCTL_EPENA)) + dr->diepdma[i] = hsotg->eps_in[i]->desc_list_dma; + dwc2_writel(hsotg, dr->dtxfsiz[i], DPTXFSIZN(i)); + dwc2_writel(hsotg, dr->diepctl[i], DIEPCTL(i)); + /* Restore OUT EPs */ + dwc2_writel(hsotg, dr->doeptsiz[i], DOEPTSIZ(i)); + /* WA for enabled EPx's OUT in DDMA mode. On entering to + * hibernation wrong value read and saved from DOEPDMAx, + * as result BNA interrupt asserted on hibernation exit + * by restoring from saved area. + */ + if (using_desc_dma(hsotg) && + (dr->doepctl[i] & DXEPCTL_EPENA)) + dr->doepdma[i] = hsotg->eps_out[i]->desc_list_dma; + dwc2_writel(hsotg, dr->doepdma[i], DOEPDMA(i)); + dwc2_writel(hsotg, dr->doepctl[i], DOEPCTL(i)); + } + + return 0; +} + +/** + * dwc2_gadget_init_lpm - Configure the core to support LPM in device mode + * + * @hsotg: Programming view of DWC_otg controller + * + */ +void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg) +{ + u32 val; + + if (!hsotg->params.lpm) + return; + + val = GLPMCFG_LPMCAP | GLPMCFG_APPL1RES; + val |= hsotg->params.hird_threshold_en ? GLPMCFG_HIRD_THRES_EN : 0; + val |= hsotg->params.lpm_clock_gating ? GLPMCFG_ENBLSLPM : 0; + val |= hsotg->params.hird_threshold << GLPMCFG_HIRD_THRES_SHIFT; + val |= hsotg->params.besl ? GLPMCFG_ENBESL : 0; + val |= GLPMCFG_LPM_REJECT_CTRL_CONTROL; + val |= GLPMCFG_LPM_ACCEPT_CTRL_ISOC; + dwc2_writel(hsotg, val, GLPMCFG); + dev_dbg(hsotg->dev, "GLPMCFG=0x%08x\n", dwc2_readl(hsotg, GLPMCFG)); + + /* Unmask WKUP_ALERT Interrupt */ + if (hsotg->params.service_interval) + dwc2_set_bit(hsotg, GINTMSK2, GINTMSK2_WKUP_ALERT_INT_MSK); +} + +/** + * dwc2_gadget_program_ref_clk - Program GREFCLK register in device mode + * + * @hsotg: Programming view of DWC_otg controller + * + */ +void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg) +{ + u32 val = 0; + + val |= GREFCLK_REF_CLK_MODE; + val |= hsotg->params.ref_clk_per << GREFCLK_REFCLKPER_SHIFT; + val |= hsotg->params.sof_cnt_wkup_alert << + GREFCLK_SOF_CNT_WKUP_ALERT_SHIFT; + + dwc2_writel(hsotg, val, GREFCLK); + dev_dbg(hsotg->dev, "GREFCLK=0x%08x\n", dwc2_readl(hsotg, GREFCLK)); +} + +int dwc2_gadget_backup_critical_registers(struct dwc2_hsotg *hsotg) +{ + int ret; + + /* Backup all registers */ + ret = dwc2_backup_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup global registers\n", + __func__); + return ret; + } + + ret = dwc2_backup_device_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup device registers\n", + __func__); + return ret; + } + + return 0; +} + +int dwc2_gadget_restore_critical_registers(struct dwc2_hsotg *hsotg, + unsigned int flags) +{ + int ret; + + ret = dwc2_restore_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore registers\n", + __func__); + return ret; + } + ret = dwc2_restore_device_registers(hsotg, flags); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore device registers\n", + __func__); + return ret; + } + + return 0; +} + +/** + * dwc2_gadget_enter_hibernation() - Put controller in Hibernation. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return non-zero if failed to enter to hibernation. + */ +int dwc2_gadget_enter_hibernation(struct dwc2_hsotg *hsotg) +{ + u32 gpwrdn; + u32 gusbcfg; + u32 pcgcctl; + int ret = 0; + + /* Change to L2(suspend) state */ + hsotg->lx_state = DWC2_L2; + dev_dbg(hsotg->dev, "Start of hibernation completed\n"); + ret = dwc2_gadget_backup_critical_registers(hsotg); + if (ret) + return ret; + + gpwrdn = GPWRDN_PWRDNRSTN; + udelay(10); + gusbcfg = dwc2_readl(hsotg, GUSBCFG); + if (gusbcfg & GUSBCFG_ULPI_UTMI_SEL) { + /* ULPI interface */ + gpwrdn |= GPWRDN_ULPI_LATCH_EN_DURING_HIB_ENTRY; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Suspend the Phy Clock */ + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(10); + + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PMUACTV; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + } else { + /* UTMI+ Interface */ + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PMUACTV; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(10); + } + + /* Set flag to indicate that we are in hibernation */ + hsotg->hibernated = 1; + + /* Enable interrupts from wake up logic */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PMUINTSEL; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Unmask device mode interrupts in GPWRDN */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_RST_DET_MSK; + gpwrdn |= GPWRDN_LNSTSCHG_MSK; + gpwrdn |= GPWRDN_STS_CHGINT_MSK; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Enable Power Down Clamp */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PWRDNCLMP; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Switch off VDD */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PWRDNSWTCH; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Save gpwrdn register for further usage if stschng interrupt */ + hsotg->gr_backup.gpwrdn = dwc2_readl(hsotg, GPWRDN); + dev_dbg(hsotg->dev, "Hibernation completed\n"); + + return ret; +} + +/** + * dwc2_gadget_exit_hibernation() + * This function is for exiting from Device mode hibernation by host initiated + * resume/reset and device initiated remote-wakeup. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by Device or Host. + * @reset: indicates whether resume is initiated by Reset. + * + * Return non-zero if failed to exit from hibernation. + */ +int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg, + int rem_wakeup, int reset) +{ + u32 pcgcctl; + u32 gpwrdn; + u32 dctl; + int ret = 0; + unsigned int flags = 0; + struct dwc2_gregs_backup *gr; + struct dwc2_dregs_backup *dr; + + gr = &hsotg->gr_backup; + dr = &hsotg->dr_backup; + + if (!hsotg->hibernated) { + dev_dbg(hsotg->dev, "Already exited from Hibernation\n"); + return 1; + } + dev_dbg(hsotg->dev, + "%s: called with rem_wakeup = %d reset = %d\n", + __func__, rem_wakeup, reset); + + dwc2_hib_restore_common(hsotg, rem_wakeup, 0); + + if (!reset) { + /* Clear all pending interupts */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + } + + /* De-assert Restore */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_RESTORE; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + if (!rem_wakeup) { + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_RSTPDWNMODULE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + } + + /* Restore GUSBCFG, DCFG and DCTL */ + dwc2_writel(hsotg, gr->gusbcfg, GUSBCFG); + dwc2_writel(hsotg, dr->dcfg, DCFG); + dwc2_writel(hsotg, dr->dctl, DCTL); + + /* On USB Reset, reset device address to zero */ + if (reset) + dwc2_clear_bit(hsotg, DCFG, DCFG_DEVADDR_MASK); + + /* Reset ULPI latch */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_ULPI_LATCH_EN_DURING_HIB_ENTRY; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + + /* De-assert Wakeup Logic */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_PMUACTV; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + + if (rem_wakeup) { + udelay(10); + /* Start Remote Wakeup Signaling */ + dwc2_writel(hsotg, dr->dctl | DCTL_RMTWKUPSIG, DCTL); + } else { + udelay(50); + /* Set Device programming done bit */ + dctl = dwc2_readl(hsotg, DCTL); + dctl |= DCTL_PWRONPRGDONE; + dwc2_writel(hsotg, dctl, DCTL); + flags |= DWC2_RESTORE_DCTL; + } + /* Wait for interrupts which must be cleared */ + mdelay(2); + /* Clear all pending interupts */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + + /* Restore global registers */ + ret = dwc2_gadget_restore_critical_registers(hsotg, flags); + if (ret) + return ret; + + if (rem_wakeup) { + mdelay(10); + dctl = dwc2_readl(hsotg, DCTL); + dctl &= ~DCTL_RMTWKUPSIG; + dwc2_writel(hsotg, dctl, DCTL); + } + + hsotg->hibernated = 0; + hsotg->lx_state = DWC2_L0; + dev_dbg(hsotg->dev, "Hibernation recovery completes here\n"); + + return ret; +} + +/** + * dwc2_gadget_enter_partial_power_down() - Put controller in partial + * power down. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return: non-zero if failed to enter device partial power down. + * + * This function is for entering device mode partial power down. + */ +int dwc2_gadget_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ + u32 pcgcctl; + int ret = 0; + + dev_dbg(hsotg->dev, "Entering device partial power down started.\n"); + + /* Backup all registers */ + ret = dwc2_gadget_backup_critical_registers(hsotg); + if (ret) + return ret; + + /* + * Clear any pending interrupts since dwc2 will not be able to + * clear them after entering partial_power_down. + */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + + /* Put the controller in low power state */ + pcgcctl = dwc2_readl(hsotg, PCGCTL); + + pcgcctl |= PCGCTL_PWRCLMP; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl |= PCGCTL_RSTPDWNMODULE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + /* Set in_ppd flag to 1 as here core enters suspend. */ + hsotg->in_ppd = 1; + hsotg->lx_state = DWC2_L2; + + dev_dbg(hsotg->dev, "Entering device partial power down completed.\n"); + + return ret; +} + +/* + * dwc2_gadget_exit_partial_power_down() - Exit controller from device partial + * power down. + * + * @hsotg: Programming view of the DWC_otg controller + * @restore: indicates whether need to restore the registers or not. + * + * Return: non-zero if failed to exit device partial power down. + * + * This function is for exiting from device mode partial power down. + */ +int dwc2_gadget_exit_partial_power_down(struct dwc2_hsotg *hsotg, + bool restore) +{ + u32 pcgcctl; + u32 dctl; + int ret = 0; + + dev_dbg(hsotg->dev, "Exiting device partial Power Down started.\n"); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_PWRCLMP; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_RSTPDWNMODULE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + udelay(100); + if (restore) { + ret = dwc2_gadget_restore_critical_registers(hsotg, DWC2_RESTORE_DCTL | + DWC2_RESTORE_DCFG); + if (ret) + return ret; + } + + /* Set the Power-On Programming done bit */ + dctl = dwc2_readl(hsotg, DCTL); + dctl |= DCTL_PWRONPRGDONE; + dwc2_writel(hsotg, dctl, DCTL); + + /* Set in_ppd flag to 0 as here core exits from suspend. */ + hsotg->in_ppd = 0; + hsotg->lx_state = DWC2_L0; + + dev_dbg(hsotg->dev, "Exiting device partial Power Down completed.\n"); + return ret; +} + +/** + * dwc2_gadget_enter_clock_gating() - Put controller in clock gating. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return: non-zero if failed to enter device partial power down. + * + * This function is for entering device mode clock gating. + */ +void dwc2_gadget_enter_clock_gating(struct dwc2_hsotg *hsotg) +{ + u32 pcgctl; + + dev_dbg(hsotg->dev, "Entering device clock gating.\n"); + + /* Set the Phy Clock bit as suspend is received. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + /* Set the Gate hclk as suspend is received. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl |= PCGCTL_GATEHCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + hsotg->lx_state = DWC2_L2; + hsotg->bus_suspended = true; +} + +/* + * dwc2_gadget_exit_clock_gating() - Exit controller from device clock gating. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether remote wake up is enabled. + * + * This function is for exiting from device mode clock gating. + */ +void dwc2_gadget_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup) +{ + u32 pcgctl; + u32 dctl; + + dev_dbg(hsotg->dev, "Exiting device clock gating.\n"); + + /* Clear the Gate hclk. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_GATEHCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + /* Phy Clock bit. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + if (rem_wakeup) { + /* Set Remote Wakeup Signaling */ + dctl = dwc2_readl(hsotg, DCTL); + dctl |= DCTL_RMTWKUPSIG; + dwc2_writel(hsotg, dctl, DCTL); + } + + /* Change to L0 state */ + call_gadget(hsotg, resume); + hsotg->lx_state = DWC2_L0; + hsotg->bus_suspended = false; +} diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c new file mode 100644 index 000000000..2414291aa --- /dev/null +++ b/drivers/usb/dwc2/hcd.c @@ -0,0 +1,5997 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * hcd.c - DesignWare HS OTG Controller host-mode routines + * + * Copyright (C) 2004-2013 Synopsys, Inc. + */ + +/* + * This file contains the core HCD code, and implements the Linux hc_driver + * API + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "core.h" +#include "hcd.h" + +/* + * ========================================================================= + * Host Core Layer Functions + * ========================================================================= + */ + +/** + * dwc2_enable_common_interrupts() - Initializes the commmon interrupts, + * used in both device and host modes + * + * @hsotg: Programming view of the DWC_otg controller + */ +static void dwc2_enable_common_interrupts(struct dwc2_hsotg *hsotg) +{ + u32 intmsk; + + /* Clear any pending OTG Interrupts */ + dwc2_writel(hsotg, 0xffffffff, GOTGINT); + + /* Clear any pending interrupts */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + + /* Enable the interrupts in the GINTMSK */ + intmsk = GINTSTS_MODEMIS | GINTSTS_OTGINT; + + if (!hsotg->params.host_dma) + intmsk |= GINTSTS_RXFLVL; + if (!hsotg->params.external_id_pin_ctl) + intmsk |= GINTSTS_CONIDSTSCHNG; + + intmsk |= GINTSTS_WKUPINT | GINTSTS_USBSUSP | + GINTSTS_SESSREQINT; + + if (dwc2_is_device_mode(hsotg) && hsotg->params.lpm) + intmsk |= GINTSTS_LPMTRANRCVD; + + dwc2_writel(hsotg, intmsk, GINTMSK); +} + +static int dwc2_gahbcfg_init(struct dwc2_hsotg *hsotg) +{ + u32 ahbcfg = dwc2_readl(hsotg, GAHBCFG); + + switch (hsotg->hw_params.arch) { + case GHWCFG2_EXT_DMA_ARCH: + dev_err(hsotg->dev, "External DMA Mode not supported\n"); + return -EINVAL; + + case GHWCFG2_INT_DMA_ARCH: + dev_dbg(hsotg->dev, "Internal DMA Mode\n"); + if (hsotg->params.ahbcfg != -1) { + ahbcfg &= GAHBCFG_CTRL_MASK; + ahbcfg |= hsotg->params.ahbcfg & + ~GAHBCFG_CTRL_MASK; + } + break; + + case GHWCFG2_SLAVE_ONLY_ARCH: + default: + dev_dbg(hsotg->dev, "Slave Only Mode\n"); + break; + } + + if (hsotg->params.host_dma) + ahbcfg |= GAHBCFG_DMA_EN; + else + hsotg->params.dma_desc_enable = false; + + dwc2_writel(hsotg, ahbcfg, GAHBCFG); + + return 0; +} + +static void dwc2_gusbcfg_init(struct dwc2_hsotg *hsotg) +{ + u32 usbcfg; + + usbcfg = dwc2_readl(hsotg, GUSBCFG); + usbcfg &= ~(GUSBCFG_HNPCAP | GUSBCFG_SRPCAP); + + switch (hsotg->hw_params.op_mode) { + case GHWCFG2_OP_MODE_HNP_SRP_CAPABLE: + if (hsotg->params.otg_caps.hnp_support && + hsotg->params.otg_caps.srp_support) + usbcfg |= GUSBCFG_HNPCAP; + fallthrough; + + case GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE: + case GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE: + case GHWCFG2_OP_MODE_SRP_CAPABLE_HOST: + if (hsotg->params.otg_caps.srp_support) + usbcfg |= GUSBCFG_SRPCAP; + break; + + case GHWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE: + case GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE: + case GHWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST: + default: + break; + } + + dwc2_writel(hsotg, usbcfg, GUSBCFG); +} + +static int dwc2_vbus_supply_init(struct dwc2_hsotg *hsotg) +{ + if (hsotg->vbus_supply) + return regulator_enable(hsotg->vbus_supply); + + return 0; +} + +static int dwc2_vbus_supply_exit(struct dwc2_hsotg *hsotg) +{ + if (hsotg->vbus_supply) + return regulator_disable(hsotg->vbus_supply); + + return 0; +} + +/** + * dwc2_enable_host_interrupts() - Enables the Host mode interrupts + * + * @hsotg: Programming view of DWC_otg controller + */ +static void dwc2_enable_host_interrupts(struct dwc2_hsotg *hsotg) +{ + u32 intmsk; + + dev_dbg(hsotg->dev, "%s()\n", __func__); + + /* Disable all interrupts */ + dwc2_writel(hsotg, 0, GINTMSK); + dwc2_writel(hsotg, 0, HAINTMSK); + + /* Enable the common interrupts */ + dwc2_enable_common_interrupts(hsotg); + + /* Enable host mode interrupts without disturbing common interrupts */ + intmsk = dwc2_readl(hsotg, GINTMSK); + intmsk |= GINTSTS_DISCONNINT | GINTSTS_PRTINT | GINTSTS_HCHINT; + dwc2_writel(hsotg, intmsk, GINTMSK); +} + +/** + * dwc2_disable_host_interrupts() - Disables the Host Mode interrupts + * + * @hsotg: Programming view of DWC_otg controller + */ +static void dwc2_disable_host_interrupts(struct dwc2_hsotg *hsotg) +{ + u32 intmsk = dwc2_readl(hsotg, GINTMSK); + + /* Disable host mode interrupts without disturbing common interrupts */ + intmsk &= ~(GINTSTS_SOF | GINTSTS_PRTINT | GINTSTS_HCHINT | + GINTSTS_PTXFEMP | GINTSTS_NPTXFEMP | GINTSTS_DISCONNINT); + dwc2_writel(hsotg, intmsk, GINTMSK); +} + +/* + * dwc2_calculate_dynamic_fifo() - Calculates the default fifo size + * For system that have a total fifo depth that is smaller than the default + * RX + TX fifo size. + * + * @hsotg: Programming view of DWC_otg controller + */ +static void dwc2_calculate_dynamic_fifo(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *params = &hsotg->params; + struct dwc2_hw_params *hw = &hsotg->hw_params; + u32 rxfsiz, nptxfsiz, ptxfsiz, total_fifo_size; + + total_fifo_size = hw->total_fifo_size; + rxfsiz = params->host_rx_fifo_size; + nptxfsiz = params->host_nperio_tx_fifo_size; + ptxfsiz = params->host_perio_tx_fifo_size; + + /* + * Will use Method 2 defined in the DWC2 spec: minimum FIFO depth + * allocation with support for high bandwidth endpoints. Synopsys + * defines MPS(Max Packet size) for a periodic EP=1024, and for + * non-periodic as 512. + */ + if (total_fifo_size < (rxfsiz + nptxfsiz + ptxfsiz)) { + /* + * For Buffer DMA mode/Scatter Gather DMA mode + * 2 * ((Largest Packet size / 4) + 1 + 1) + n + * with n = number of host channel. + * 2 * ((1024/4) + 2) = 516 + */ + rxfsiz = 516 + hw->host_channels; + + /* + * min non-periodic tx fifo depth + * 2 * (largest non-periodic USB packet used / 4) + * 2 * (512/4) = 256 + */ + nptxfsiz = 256; + + /* + * min periodic tx fifo depth + * (largest packet size*MC)/4 + * (1024 * 3)/4 = 768 + */ + ptxfsiz = 768; + + params->host_rx_fifo_size = rxfsiz; + params->host_nperio_tx_fifo_size = nptxfsiz; + params->host_perio_tx_fifo_size = ptxfsiz; + } + + /* + * If the summation of RX, NPTX and PTX fifo sizes is still + * bigger than the total_fifo_size, then we have a problem. + * + * We won't be able to allocate as many endpoints. Right now, + * we're just printing an error message, but ideally this FIFO + * allocation algorithm would be improved in the future. + * + * FIXME improve this FIFO allocation algorithm. + */ + if (unlikely(total_fifo_size < (rxfsiz + nptxfsiz + ptxfsiz))) + dev_err(hsotg->dev, "invalid fifo sizes\n"); +} + +static void dwc2_config_fifos(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *params = &hsotg->params; + u32 nptxfsiz, hptxfsiz, dfifocfg, grxfsiz; + + if (!params->enable_dynamic_fifo) + return; + + dwc2_calculate_dynamic_fifo(hsotg); + + /* Rx FIFO */ + grxfsiz = dwc2_readl(hsotg, GRXFSIZ); + dev_dbg(hsotg->dev, "initial grxfsiz=%08x\n", grxfsiz); + grxfsiz &= ~GRXFSIZ_DEPTH_MASK; + grxfsiz |= params->host_rx_fifo_size << + GRXFSIZ_DEPTH_SHIFT & GRXFSIZ_DEPTH_MASK; + dwc2_writel(hsotg, grxfsiz, GRXFSIZ); + dev_dbg(hsotg->dev, "new grxfsiz=%08x\n", + dwc2_readl(hsotg, GRXFSIZ)); + + /* Non-periodic Tx FIFO */ + dev_dbg(hsotg->dev, "initial gnptxfsiz=%08x\n", + dwc2_readl(hsotg, GNPTXFSIZ)); + nptxfsiz = params->host_nperio_tx_fifo_size << + FIFOSIZE_DEPTH_SHIFT & FIFOSIZE_DEPTH_MASK; + nptxfsiz |= params->host_rx_fifo_size << + FIFOSIZE_STARTADDR_SHIFT & FIFOSIZE_STARTADDR_MASK; + dwc2_writel(hsotg, nptxfsiz, GNPTXFSIZ); + dev_dbg(hsotg->dev, "new gnptxfsiz=%08x\n", + dwc2_readl(hsotg, GNPTXFSIZ)); + + /* Periodic Tx FIFO */ + dev_dbg(hsotg->dev, "initial hptxfsiz=%08x\n", + dwc2_readl(hsotg, HPTXFSIZ)); + hptxfsiz = params->host_perio_tx_fifo_size << + FIFOSIZE_DEPTH_SHIFT & FIFOSIZE_DEPTH_MASK; + hptxfsiz |= (params->host_rx_fifo_size + + params->host_nperio_tx_fifo_size) << + FIFOSIZE_STARTADDR_SHIFT & FIFOSIZE_STARTADDR_MASK; + dwc2_writel(hsotg, hptxfsiz, HPTXFSIZ); + dev_dbg(hsotg->dev, "new hptxfsiz=%08x\n", + dwc2_readl(hsotg, HPTXFSIZ)); + + if (hsotg->params.en_multiple_tx_fifo && + hsotg->hw_params.snpsid >= DWC2_CORE_REV_2_91a) { + /* + * This feature was implemented in 2.91a version + * Global DFIFOCFG calculation for Host mode - + * include RxFIFO, NPTXFIFO and HPTXFIFO + */ + dfifocfg = dwc2_readl(hsotg, GDFIFOCFG); + dfifocfg &= ~GDFIFOCFG_EPINFOBASE_MASK; + dfifocfg |= (params->host_rx_fifo_size + + params->host_nperio_tx_fifo_size + + params->host_perio_tx_fifo_size) << + GDFIFOCFG_EPINFOBASE_SHIFT & + GDFIFOCFG_EPINFOBASE_MASK; + dwc2_writel(hsotg, dfifocfg, GDFIFOCFG); + } +} + +/** + * dwc2_calc_frame_interval() - Calculates the correct frame Interval value for + * the HFIR register according to PHY type and speed + * + * @hsotg: Programming view of DWC_otg controller + * + * NOTE: The caller can modify the value of the HFIR register only after the + * Port Enable bit of the Host Port Control and Status register (HPRT.EnaPort) + * has been set + */ +u32 dwc2_calc_frame_interval(struct dwc2_hsotg *hsotg) +{ + u32 usbcfg; + u32 hprt0; + int clock = 60; /* default value */ + + usbcfg = dwc2_readl(hsotg, GUSBCFG); + hprt0 = dwc2_readl(hsotg, HPRT0); + + if (!(usbcfg & GUSBCFG_PHYSEL) && (usbcfg & GUSBCFG_ULPI_UTMI_SEL) && + !(usbcfg & GUSBCFG_PHYIF16)) + clock = 60; + if ((usbcfg & GUSBCFG_PHYSEL) && hsotg->hw_params.fs_phy_type == + GHWCFG2_FS_PHY_TYPE_SHARED_ULPI) + clock = 48; + if (!(usbcfg & GUSBCFG_PHY_LP_CLK_SEL) && !(usbcfg & GUSBCFG_PHYSEL) && + !(usbcfg & GUSBCFG_ULPI_UTMI_SEL) && (usbcfg & GUSBCFG_PHYIF16)) + clock = 30; + if (!(usbcfg & GUSBCFG_PHY_LP_CLK_SEL) && !(usbcfg & GUSBCFG_PHYSEL) && + !(usbcfg & GUSBCFG_ULPI_UTMI_SEL) && !(usbcfg & GUSBCFG_PHYIF16)) + clock = 60; + if ((usbcfg & GUSBCFG_PHY_LP_CLK_SEL) && !(usbcfg & GUSBCFG_PHYSEL) && + !(usbcfg & GUSBCFG_ULPI_UTMI_SEL) && (usbcfg & GUSBCFG_PHYIF16)) + clock = 48; + if ((usbcfg & GUSBCFG_PHYSEL) && !(usbcfg & GUSBCFG_PHYIF16) && + hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_SHARED_UTMI) + clock = 48; + if ((usbcfg & GUSBCFG_PHYSEL) && + hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED) + clock = 48; + + if ((hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT == HPRT0_SPD_HIGH_SPEED) + /* High speed case */ + return 125 * clock - 1; + + /* FS/LS case */ + return 1000 * clock - 1; +} + +/** + * dwc2_read_packet() - Reads a packet from the Rx FIFO into the destination + * buffer + * + * @hsotg: Programming view of DWC_otg controller + * @dest: Destination buffer for the packet + * @bytes: Number of bytes to copy to the destination + */ +void dwc2_read_packet(struct dwc2_hsotg *hsotg, u8 *dest, u16 bytes) +{ + u32 *data_buf = (u32 *)dest; + int word_count = (bytes + 3) / 4; + int i; + + /* + * Todo: Account for the case where dest is not dword aligned. This + * requires reading data from the FIFO into a u32 temp buffer, then + * moving it into the data buffer. + */ + + dev_vdbg(hsotg->dev, "%s(%p,%p,%d)\n", __func__, hsotg, dest, bytes); + + for (i = 0; i < word_count; i++, data_buf++) + *data_buf = dwc2_readl(hsotg, HCFIFO(0)); +} + +/** + * dwc2_dump_channel_info() - Prints the state of a host channel + * + * @hsotg: Programming view of DWC_otg controller + * @chan: Pointer to the channel to dump + * + * Must be called with interrupt disabled and spinlock held + * + * NOTE: This function will be removed once the peripheral controller code + * is integrated and the driver is stable + */ +static void dwc2_dump_channel_info(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) +{ +#ifdef VERBOSE_DEBUG + int num_channels = hsotg->params.host_channels; + struct dwc2_qh *qh; + u32 hcchar; + u32 hcsplt; + u32 hctsiz; + u32 hc_dma; + int i; + + if (!chan) + return; + + hcchar = dwc2_readl(hsotg, HCCHAR(chan->hc_num)); + hcsplt = dwc2_readl(hsotg, HCSPLT(chan->hc_num)); + hctsiz = dwc2_readl(hsotg, HCTSIZ(chan->hc_num)); + hc_dma = dwc2_readl(hsotg, HCDMA(chan->hc_num)); + + dev_dbg(hsotg->dev, " Assigned to channel %p:\n", chan); + dev_dbg(hsotg->dev, " hcchar 0x%08x, hcsplt 0x%08x\n", + hcchar, hcsplt); + dev_dbg(hsotg->dev, " hctsiz 0x%08x, hc_dma 0x%08x\n", + hctsiz, hc_dma); + dev_dbg(hsotg->dev, " dev_addr: %d, ep_num: %d, ep_is_in: %d\n", + chan->dev_addr, chan->ep_num, chan->ep_is_in); + dev_dbg(hsotg->dev, " ep_type: %d\n", chan->ep_type); + dev_dbg(hsotg->dev, " max_packet: %d\n", chan->max_packet); + dev_dbg(hsotg->dev, " data_pid_start: %d\n", chan->data_pid_start); + dev_dbg(hsotg->dev, " xfer_started: %d\n", chan->xfer_started); + dev_dbg(hsotg->dev, " halt_status: %d\n", chan->halt_status); + dev_dbg(hsotg->dev, " xfer_buf: %p\n", chan->xfer_buf); + dev_dbg(hsotg->dev, " xfer_dma: %08lx\n", + (unsigned long)chan->xfer_dma); + dev_dbg(hsotg->dev, " xfer_len: %d\n", chan->xfer_len); + dev_dbg(hsotg->dev, " qh: %p\n", chan->qh); + dev_dbg(hsotg->dev, " NP inactive sched:\n"); + list_for_each_entry(qh, &hsotg->non_periodic_sched_inactive, + qh_list_entry) + dev_dbg(hsotg->dev, " %p\n", qh); + dev_dbg(hsotg->dev, " NP waiting sched:\n"); + list_for_each_entry(qh, &hsotg->non_periodic_sched_waiting, + qh_list_entry) + dev_dbg(hsotg->dev, " %p\n", qh); + dev_dbg(hsotg->dev, " NP active sched:\n"); + list_for_each_entry(qh, &hsotg->non_periodic_sched_active, + qh_list_entry) + dev_dbg(hsotg->dev, " %p\n", qh); + dev_dbg(hsotg->dev, " Channels:\n"); + for (i = 0; i < num_channels; i++) { + struct dwc2_host_chan *chan = hsotg->hc_ptr_array[i]; + + dev_dbg(hsotg->dev, " %2d: %p\n", i, chan); + } +#endif /* VERBOSE_DEBUG */ +} + +static int _dwc2_hcd_start(struct usb_hcd *hcd); + +static void dwc2_host_start(struct dwc2_hsotg *hsotg) +{ + struct usb_hcd *hcd = dwc2_hsotg_to_hcd(hsotg); + + hcd->self.is_b_host = dwc2_hcd_is_b_host(hsotg); + _dwc2_hcd_start(hcd); +} + +static void dwc2_host_disconnect(struct dwc2_hsotg *hsotg) +{ + struct usb_hcd *hcd = dwc2_hsotg_to_hcd(hsotg); + + hcd->self.is_b_host = 0; +} + +static void dwc2_host_hub_info(struct dwc2_hsotg *hsotg, void *context, + int *hub_addr, int *hub_port) +{ + struct urb *urb = context; + + if (urb->dev->tt) + *hub_addr = urb->dev->tt->hub->devnum; + else + *hub_addr = 0; + *hub_port = urb->dev->ttport; +} + +/* + * ========================================================================= + * Low Level Host Channel Access Functions + * ========================================================================= + */ + +static void dwc2_hc_enable_slave_ints(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) +{ + u32 hcintmsk = HCINTMSK_CHHLTD; + + switch (chan->ep_type) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + dev_vdbg(hsotg->dev, "control/bulk\n"); + hcintmsk |= HCINTMSK_XFERCOMPL; + hcintmsk |= HCINTMSK_STALL; + hcintmsk |= HCINTMSK_XACTERR; + hcintmsk |= HCINTMSK_DATATGLERR; + if (chan->ep_is_in) { + hcintmsk |= HCINTMSK_BBLERR; + } else { + hcintmsk |= HCINTMSK_NAK; + hcintmsk |= HCINTMSK_NYET; + if (chan->do_ping) + hcintmsk |= HCINTMSK_ACK; + } + + if (chan->do_split) { + hcintmsk |= HCINTMSK_NAK; + if (chan->complete_split) + hcintmsk |= HCINTMSK_NYET; + else + hcintmsk |= HCINTMSK_ACK; + } + + if (chan->error_state) + hcintmsk |= HCINTMSK_ACK; + break; + + case USB_ENDPOINT_XFER_INT: + if (dbg_perio()) + dev_vdbg(hsotg->dev, "intr\n"); + hcintmsk |= HCINTMSK_XFERCOMPL; + hcintmsk |= HCINTMSK_NAK; + hcintmsk |= HCINTMSK_STALL; + hcintmsk |= HCINTMSK_XACTERR; + hcintmsk |= HCINTMSK_DATATGLERR; + hcintmsk |= HCINTMSK_FRMOVRUN; + + if (chan->ep_is_in) + hcintmsk |= HCINTMSK_BBLERR; + if (chan->error_state) + hcintmsk |= HCINTMSK_ACK; + if (chan->do_split) { + if (chan->complete_split) + hcintmsk |= HCINTMSK_NYET; + else + hcintmsk |= HCINTMSK_ACK; + } + break; + + case USB_ENDPOINT_XFER_ISOC: + if (dbg_perio()) + dev_vdbg(hsotg->dev, "isoc\n"); + hcintmsk |= HCINTMSK_XFERCOMPL; + hcintmsk |= HCINTMSK_FRMOVRUN; + hcintmsk |= HCINTMSK_ACK; + + if (chan->ep_is_in) { + hcintmsk |= HCINTMSK_XACTERR; + hcintmsk |= HCINTMSK_BBLERR; + } + break; + default: + dev_err(hsotg->dev, "## Unknown EP type ##\n"); + break; + } + + dwc2_writel(hsotg, hcintmsk, HCINTMSK(chan->hc_num)); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "set HCINTMSK to %08x\n", hcintmsk); +} + +static void dwc2_hc_enable_dma_ints(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) +{ + u32 hcintmsk = HCINTMSK_CHHLTD; + + /* + * For Descriptor DMA mode core halts the channel on AHB error. + * Interrupt is not required. + */ + if (!hsotg->params.dma_desc_enable) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "desc DMA disabled\n"); + hcintmsk |= HCINTMSK_AHBERR; + } else { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "desc DMA enabled\n"); + if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) + hcintmsk |= HCINTMSK_XFERCOMPL; + } + + if (chan->error_state && !chan->do_split && + chan->ep_type != USB_ENDPOINT_XFER_ISOC) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "setting ACK\n"); + hcintmsk |= HCINTMSK_ACK; + if (chan->ep_is_in) { + hcintmsk |= HCINTMSK_DATATGLERR; + if (chan->ep_type != USB_ENDPOINT_XFER_INT) + hcintmsk |= HCINTMSK_NAK; + } + } + + dwc2_writel(hsotg, hcintmsk, HCINTMSK(chan->hc_num)); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "set HCINTMSK to %08x\n", hcintmsk); +} + +static void dwc2_hc_enable_ints(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) +{ + u32 intmsk; + + if (hsotg->params.host_dma) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "DMA enabled\n"); + dwc2_hc_enable_dma_ints(hsotg, chan); + } else { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "DMA disabled\n"); + dwc2_hc_enable_slave_ints(hsotg, chan); + } + + /* Enable the top level host channel interrupt */ + intmsk = dwc2_readl(hsotg, HAINTMSK); + intmsk |= 1 << chan->hc_num; + dwc2_writel(hsotg, intmsk, HAINTMSK); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "set HAINTMSK to %08x\n", intmsk); + + /* Make sure host channel interrupts are enabled */ + intmsk = dwc2_readl(hsotg, GINTMSK); + intmsk |= GINTSTS_HCHINT; + dwc2_writel(hsotg, intmsk, GINTMSK); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "set GINTMSK to %08x\n", intmsk); +} + +/** + * dwc2_hc_init() - Prepares a host channel for transferring packets to/from + * a specific endpoint + * + * @hsotg: Programming view of DWC_otg controller + * @chan: Information needed to initialize the host channel + * + * The HCCHARn register is set up with the characteristics specified in chan. + * Host channel interrupts that may need to be serviced while this transfer is + * in progress are enabled. + */ +static void dwc2_hc_init(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan) +{ + u8 hc_num = chan->hc_num; + u32 hcintmsk; + u32 hcchar; + u32 hcsplt = 0; + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + /* Clear old interrupt conditions for this host channel */ + hcintmsk = 0xffffffff; + hcintmsk &= ~HCINTMSK_RESERVED14_31; + dwc2_writel(hsotg, hcintmsk, HCINT(hc_num)); + + /* Enable channel interrupts required for this transfer */ + dwc2_hc_enable_ints(hsotg, chan); + + /* + * Program the HCCHARn register with the endpoint characteristics for + * the current transfer + */ + hcchar = chan->dev_addr << HCCHAR_DEVADDR_SHIFT & HCCHAR_DEVADDR_MASK; + hcchar |= chan->ep_num << HCCHAR_EPNUM_SHIFT & HCCHAR_EPNUM_MASK; + if (chan->ep_is_in) + hcchar |= HCCHAR_EPDIR; + if (chan->speed == USB_SPEED_LOW) + hcchar |= HCCHAR_LSPDDEV; + hcchar |= chan->ep_type << HCCHAR_EPTYPE_SHIFT & HCCHAR_EPTYPE_MASK; + hcchar |= chan->max_packet << HCCHAR_MPS_SHIFT & HCCHAR_MPS_MASK; + dwc2_writel(hsotg, hcchar, HCCHAR(hc_num)); + if (dbg_hc(chan)) { + dev_vdbg(hsotg->dev, "set HCCHAR(%d) to %08x\n", + hc_num, hcchar); + + dev_vdbg(hsotg->dev, "%s: Channel %d\n", + __func__, hc_num); + dev_vdbg(hsotg->dev, " Dev Addr: %d\n", + chan->dev_addr); + dev_vdbg(hsotg->dev, " Ep Num: %d\n", + chan->ep_num); + dev_vdbg(hsotg->dev, " Is In: %d\n", + chan->ep_is_in); + dev_vdbg(hsotg->dev, " Is Low Speed: %d\n", + chan->speed == USB_SPEED_LOW); + dev_vdbg(hsotg->dev, " Ep Type: %d\n", + chan->ep_type); + dev_vdbg(hsotg->dev, " Max Pkt: %d\n", + chan->max_packet); + } + + /* Program the HCSPLT register for SPLITs */ + if (chan->do_split) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, + "Programming HC %d with split --> %s\n", + hc_num, + chan->complete_split ? "CSPLIT" : "SSPLIT"); + if (chan->complete_split) + hcsplt |= HCSPLT_COMPSPLT; + hcsplt |= chan->xact_pos << HCSPLT_XACTPOS_SHIFT & + HCSPLT_XACTPOS_MASK; + hcsplt |= chan->hub_addr << HCSPLT_HUBADDR_SHIFT & + HCSPLT_HUBADDR_MASK; + hcsplt |= chan->hub_port << HCSPLT_PRTADDR_SHIFT & + HCSPLT_PRTADDR_MASK; + if (dbg_hc(chan)) { + dev_vdbg(hsotg->dev, " comp split %d\n", + chan->complete_split); + dev_vdbg(hsotg->dev, " xact pos %d\n", + chan->xact_pos); + dev_vdbg(hsotg->dev, " hub addr %d\n", + chan->hub_addr); + dev_vdbg(hsotg->dev, " hub port %d\n", + chan->hub_port); + dev_vdbg(hsotg->dev, " is_in %d\n", + chan->ep_is_in); + dev_vdbg(hsotg->dev, " Max Pkt %d\n", + chan->max_packet); + dev_vdbg(hsotg->dev, " xferlen %d\n", + chan->xfer_len); + } + } + + dwc2_writel(hsotg, hcsplt, HCSPLT(hc_num)); +} + +/** + * dwc2_hc_halt() - Attempts to halt a host channel + * + * @hsotg: Controller register interface + * @chan: Host channel to halt + * @halt_status: Reason for halting the 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. + */ +void dwc2_hc_halt(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan, + enum dwc2_halt_status halt_status) +{ + u32 nptxsts, hptxsts, hcchar; + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + /* + * In buffer DMA or external DMA mode channel can't be halted + * for non-split periodic channels. At the end of the next + * uframe/frame (in the worst case), the core generates a channel + * halted and disables the channel automatically. + */ + if ((hsotg->params.g_dma && !hsotg->params.g_dma_desc) || + hsotg->hw_params.arch == GHWCFG2_EXT_DMA_ARCH) { + if (!chan->do_split && + (chan->ep_type == USB_ENDPOINT_XFER_ISOC || + chan->ep_type == USB_ENDPOINT_XFER_INT)) { + dev_err(hsotg->dev, "%s() Channel can't be halted\n", + __func__); + return; + } + } + + if (halt_status == DWC2_HC_XFER_NO_HALT_STATUS) + dev_err(hsotg->dev, "!!! halt_status = %d !!!\n", halt_status); + + if (halt_status == DWC2_HC_XFER_URB_DEQUEUE || + halt_status == DWC2_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. + */ + u32 hcintmsk = HCINTMSK_CHHLTD; + + dev_vdbg(hsotg->dev, "dequeue/error\n"); + dwc2_writel(hsotg, hcintmsk, HCINTMSK(chan->hc_num)); + + /* + * Make sure no other interrupts besides halt are currently + * pending. Handling another interrupt could cause a crash due + * to the QTD and QH state. + */ + dwc2_writel(hsotg, ~hcintmsk, HCINT(chan->hc_num)); + + /* + * Make sure the halt status is set to URB_DEQUEUE or AHB_ERR + * even if the channel was already halted for some other + * reason + */ + chan->halt_status = halt_status; + + hcchar = dwc2_readl(hsotg, HCCHAR(chan->hc_num)); + if (!(hcchar & HCCHAR_CHENA)) { + /* + * 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 (chan->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. + */ + dev_vdbg(hsotg->dev, + "*** %s: Channel %d, chan->halt_pending already set ***\n", + __func__, chan->hc_num); + return; + } + + hcchar = dwc2_readl(hsotg, HCCHAR(chan->hc_num)); + + /* No need to set the bit in DDMA for disabling the channel */ + /* TODO check it everywhere channel is disabled */ + if (!hsotg->params.dma_desc_enable) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "desc DMA disabled\n"); + hcchar |= HCCHAR_CHENA; + } else { + if (dbg_hc(chan)) + dev_dbg(hsotg->dev, "desc DMA enabled\n"); + } + hcchar |= HCCHAR_CHDIS; + + if (!hsotg->params.host_dma) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "DMA not enabled\n"); + hcchar |= HCCHAR_CHENA; + + /* Check for space in the request queue to issue the halt */ + if (chan->ep_type == USB_ENDPOINT_XFER_CONTROL || + chan->ep_type == USB_ENDPOINT_XFER_BULK) { + dev_vdbg(hsotg->dev, "control/bulk\n"); + nptxsts = dwc2_readl(hsotg, GNPTXSTS); + if ((nptxsts & TXSTS_QSPCAVAIL_MASK) == 0) { + dev_vdbg(hsotg->dev, "Disabling channel\n"); + hcchar &= ~HCCHAR_CHENA; + } + } else { + if (dbg_perio()) + dev_vdbg(hsotg->dev, "isoc/intr\n"); + hptxsts = dwc2_readl(hsotg, HPTXSTS); + if ((hptxsts & TXSTS_QSPCAVAIL_MASK) == 0 || + hsotg->queuing_high_bandwidth) { + if (dbg_perio()) + dev_vdbg(hsotg->dev, "Disabling channel\n"); + hcchar &= ~HCCHAR_CHENA; + } + } + } else { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "DMA enabled\n"); + } + + dwc2_writel(hsotg, hcchar, HCCHAR(chan->hc_num)); + chan->halt_status = halt_status; + + if (hcchar & HCCHAR_CHENA) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "Channel enabled\n"); + chan->halt_pending = 1; + chan->halt_on_queue = 0; + } else { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "Channel disabled\n"); + chan->halt_on_queue = 1; + } + + if (dbg_hc(chan)) { + dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, + chan->hc_num); + dev_vdbg(hsotg->dev, " hcchar: 0x%08x\n", + hcchar); + dev_vdbg(hsotg->dev, " halt_pending: %d\n", + chan->halt_pending); + dev_vdbg(hsotg->dev, " halt_on_queue: %d\n", + chan->halt_on_queue); + dev_vdbg(hsotg->dev, " halt_status: %d\n", + chan->halt_status); + } +} + +/** + * dwc2_hc_cleanup() - Clears the transfer state for a host channel + * + * @hsotg: Programming view of DWC_otg controller + * @chan: Identifies the host channel to clean up + * + * This function is normally called after a transfer is done and the host + * channel is being released + */ +void dwc2_hc_cleanup(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan) +{ + u32 hcintmsk; + + chan->xfer_started = 0; + + list_del_init(&chan->split_order_list_entry); + + /* + * Clear channel interrupt enables and any unhandled channel interrupt + * conditions + */ + dwc2_writel(hsotg, 0, HCINTMSK(chan->hc_num)); + hcintmsk = 0xffffffff; + hcintmsk &= ~HCINTMSK_RESERVED14_31; + dwc2_writel(hsotg, hcintmsk, HCINT(chan->hc_num)); +} + +/** + * dwc2_hc_set_even_odd_frame() - Sets the channel property that indicates in + * which frame a periodic transfer should occur + * + * @hsotg: Programming view of DWC_otg controller + * @chan: Identifies the host channel to set up and its properties + * @hcchar: Current value of the HCCHAR register for the specified host channel + * + * This function has no effect on non-periodic transfers + */ +static void dwc2_hc_set_even_odd_frame(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, u32 *hcchar) +{ + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) { + int host_speed; + int xfer_ns; + int xfer_us; + int bytes_in_fifo; + u16 fifo_space; + u16 frame_number; + u16 wire_frame; + + /* + * Try to figure out if we're an even or odd frame. If we set + * even and the current frame number is even the transfer + * will happen immediately. Similar if both are odd. If one is + * even and the other is odd then the transfer will happen when + * the frame number ticks. + * + * There's a bit of a balancing act to get this right. + * Sometimes we may want to send data in the current frame (AK + * right away). We might want to do this if the frame number + * _just_ ticked, but we might also want to do this in order + * to continue a split transaction that happened late in a + * microframe (so we didn't know to queue the next transfer + * until the frame number had ticked). The problem is that we + * need a lot of knowledge to know if there's actually still + * time to send things or if it would be better to wait until + * the next frame. + * + * We can look at how much time is left in the current frame + * and make a guess about whether we'll have time to transfer. + * We'll do that. + */ + + /* Get speed host is running at */ + host_speed = (chan->speed != USB_SPEED_HIGH && + !chan->do_split) ? chan->speed : USB_SPEED_HIGH; + + /* See how many bytes are in the periodic FIFO right now */ + fifo_space = (dwc2_readl(hsotg, HPTXSTS) & + TXSTS_FSPCAVAIL_MASK) >> TXSTS_FSPCAVAIL_SHIFT; + bytes_in_fifo = sizeof(u32) * + (hsotg->params.host_perio_tx_fifo_size - + fifo_space); + + /* + * Roughly estimate bus time for everything in the periodic + * queue + our new transfer. This is "rough" because we're + * using a function that makes takes into account IN/OUT + * and INT/ISO and we're just slamming in one value for all + * transfers. This should be an over-estimate and that should + * be OK, but we can probably tighten it. + */ + xfer_ns = usb_calc_bus_time(host_speed, false, false, + chan->xfer_len + bytes_in_fifo); + xfer_us = NS_TO_US(xfer_ns); + + /* See what frame number we'll be at by the time we finish */ + frame_number = dwc2_hcd_get_future_frame_number(hsotg, xfer_us); + + /* This is when we were scheduled to be on the wire */ + wire_frame = dwc2_frame_num_inc(chan->qh->next_active_frame, 1); + + /* + * If we'd finish _after_ the frame we're scheduled in then + * it's hopeless. Just schedule right away and hope for the + * best. Note that it _might_ be wise to call back into the + * scheduler to pick a better frame, but this is better than + * nothing. + */ + if (dwc2_frame_num_gt(frame_number, wire_frame)) { + dwc2_sch_vdbg(hsotg, + "QH=%p EO MISS fr=%04x=>%04x (%+d)\n", + chan->qh, wire_frame, frame_number, + dwc2_frame_num_dec(frame_number, + wire_frame)); + wire_frame = frame_number; + + /* + * We picked a different frame number; communicate this + * back to the scheduler so it doesn't try to schedule + * another in the same frame. + * + * Remember that next_active_frame is 1 before the wire + * frame. + */ + chan->qh->next_active_frame = + dwc2_frame_num_dec(frame_number, 1); + } + + if (wire_frame & 1) + *hcchar |= HCCHAR_ODDFRM; + else + *hcchar &= ~HCCHAR_ODDFRM; + } +} + +static void dwc2_set_pid_isoc(struct dwc2_host_chan *chan) +{ + /* Set up the initial PID for the transfer */ + if (chan->speed == USB_SPEED_HIGH) { + if (chan->ep_is_in) { + if (chan->multi_count == 1) + chan->data_pid_start = DWC2_HC_PID_DATA0; + else if (chan->multi_count == 2) + chan->data_pid_start = DWC2_HC_PID_DATA1; + else + chan->data_pid_start = DWC2_HC_PID_DATA2; + } else { + if (chan->multi_count == 1) + chan->data_pid_start = DWC2_HC_PID_DATA0; + else + chan->data_pid_start = DWC2_HC_PID_MDATA; + } + } else { + chan->data_pid_start = DWC2_HC_PID_DATA0; + } +} + +/** + * dwc2_hc_write_packet() - Writes a packet into the Tx FIFO associated with + * the Host Channel + * + * @hsotg: Programming view of DWC_otg controller + * @chan: Information needed to initialize the host channel + * + * This function should only be called in Slave mode. 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. + * + * Upon return the xfer_buf and xfer_count fields in chan are incremented by + * the number of bytes written to the Tx FIFO. + */ +static void dwc2_hc_write_packet(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) +{ + u32 i; + u32 remaining_count; + u32 byte_count; + u32 dword_count; + u32 *data_buf = (u32 *)chan->xfer_buf; + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + remaining_count = chan->xfer_len - chan->xfer_count; + if (remaining_count > chan->max_packet) + byte_count = chan->max_packet; + else + byte_count = remaining_count; + + dword_count = (byte_count + 3) / 4; + + if (((unsigned long)data_buf & 0x3) == 0) { + /* xfer_buf is DWORD aligned */ + for (i = 0; i < dword_count; i++, data_buf++) + dwc2_writel(hsotg, *data_buf, HCFIFO(chan->hc_num)); + } else { + /* xfer_buf is not DWORD aligned */ + for (i = 0; i < dword_count; i++, data_buf++) { + u32 data = data_buf[0] | data_buf[1] << 8 | + data_buf[2] << 16 | data_buf[3] << 24; + dwc2_writel(hsotg, data, HCFIFO(chan->hc_num)); + } + } + + chan->xfer_count += byte_count; + chan->xfer_buf += byte_count; +} + +/** + * dwc2_hc_do_ping() - Starts a PING transfer + * + * @hsotg: Programming view of DWC_otg controller + * @chan: Information needed to initialize the host channel + * + * This function should only be called in Slave mode. The Do Ping bit is set in + * the HCTSIZ register, then the channel is enabled. + */ +static void dwc2_hc_do_ping(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) +{ + u32 hcchar; + u32 hctsiz; + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, + chan->hc_num); + + hctsiz = TSIZ_DOPNG; + hctsiz |= 1 << TSIZ_PKTCNT_SHIFT; + dwc2_writel(hsotg, hctsiz, HCTSIZ(chan->hc_num)); + + hcchar = dwc2_readl(hsotg, HCCHAR(chan->hc_num)); + hcchar |= HCCHAR_CHENA; + hcchar &= ~HCCHAR_CHDIS; + dwc2_writel(hsotg, hcchar, HCCHAR(chan->hc_num)); +} + +/** + * dwc2_hc_start_transfer() - Does the setup for a data transfer for a host + * channel and starts the transfer + * + * @hsotg: Programming view of DWC_otg controller + * @chan: 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. + * + * This function 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 are 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. + */ +static void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) +{ + u32 max_hc_xfer_size = hsotg->params.max_transfer_size; + u16 max_hc_pkt_count = hsotg->params.max_packet_count; + u32 hcchar; + u32 hctsiz = 0; + u16 num_packets; + u32 ec_mc; + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + if (chan->do_ping) { + if (!hsotg->params.host_dma) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "ping, no DMA\n"); + dwc2_hc_do_ping(hsotg, chan); + chan->xfer_started = 1; + return; + } + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "ping, DMA\n"); + + hctsiz |= TSIZ_DOPNG; + } + + if (chan->do_split) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "split\n"); + num_packets = 1; + + if (chan->complete_split && !chan->ep_is_in) + /* + * For CSPLIT OUT Transfer, set the size to 0 so the + * core doesn't expect any data written to the FIFO + */ + chan->xfer_len = 0; + else if (chan->ep_is_in || chan->xfer_len > chan->max_packet) + chan->xfer_len = chan->max_packet; + else if (!chan->ep_is_in && chan->xfer_len > 188) + chan->xfer_len = 188; + + hctsiz |= chan->xfer_len << TSIZ_XFERSIZE_SHIFT & + TSIZ_XFERSIZE_MASK; + + /* For split set ec_mc for immediate retries */ + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) + ec_mc = 3; + else + ec_mc = 1; + } else { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "no split\n"); + /* + * Ensure that the transfer length and packet count will fit + * in the widths allocated for them in the HCTSIZn register + */ + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_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.) + */ + u32 max_periodic_len = + chan->multi_count * chan->max_packet; + + if (chan->xfer_len > max_periodic_len) + chan->xfer_len = max_periodic_len; + } else if (chan->xfer_len > max_hc_xfer_size) { + /* + * Make sure that xfer_len is a multiple of max packet + * size + */ + chan->xfer_len = + max_hc_xfer_size - chan->max_packet + 1; + } + + if (chan->xfer_len > 0) { + num_packets = (chan->xfer_len + chan->max_packet - 1) / + chan->max_packet; + if (num_packets > max_hc_pkt_count) { + num_packets = max_hc_pkt_count; + chan->xfer_len = num_packets * chan->max_packet; + } else if (chan->ep_is_in) { + /* + * Always program an integral # of max packets + * for IN transfers. + * Note: This assumes that the input buffer is + * aligned and sized accordingly. + */ + chan->xfer_len = num_packets * chan->max_packet; + } + } else { + /* Need 1 packet for transfer length of 0 */ + num_packets = 1; + } + + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) + /* + * Make sure that the multi_count field matches the + * actual transfer length + */ + chan->multi_count = num_packets; + + if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) + dwc2_set_pid_isoc(chan); + + hctsiz |= chan->xfer_len << TSIZ_XFERSIZE_SHIFT & + TSIZ_XFERSIZE_MASK; + + /* The ec_mc gets the multi_count for non-split */ + ec_mc = chan->multi_count; + } + + chan->start_pkt_count = num_packets; + hctsiz |= num_packets << TSIZ_PKTCNT_SHIFT & TSIZ_PKTCNT_MASK; + hctsiz |= chan->data_pid_start << TSIZ_SC_MC_PID_SHIFT & + TSIZ_SC_MC_PID_MASK; + dwc2_writel(hsotg, hctsiz, HCTSIZ(chan->hc_num)); + if (dbg_hc(chan)) { + dev_vdbg(hsotg->dev, "Wrote %08x to HCTSIZ(%d)\n", + hctsiz, chan->hc_num); + + dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, + chan->hc_num); + dev_vdbg(hsotg->dev, " Xfer Size: %d\n", + (hctsiz & TSIZ_XFERSIZE_MASK) >> + TSIZ_XFERSIZE_SHIFT); + dev_vdbg(hsotg->dev, " Num Pkts: %d\n", + (hctsiz & TSIZ_PKTCNT_MASK) >> + TSIZ_PKTCNT_SHIFT); + dev_vdbg(hsotg->dev, " Start PID: %d\n", + (hctsiz & TSIZ_SC_MC_PID_MASK) >> + TSIZ_SC_MC_PID_SHIFT); + } + + if (hsotg->params.host_dma) { + dma_addr_t dma_addr; + + if (chan->align_buf) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "align_buf\n"); + dma_addr = chan->align_buf; + } else { + dma_addr = chan->xfer_dma; + } + dwc2_writel(hsotg, (u32)dma_addr, HCDMA(chan->hc_num)); + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "Wrote %08lx to HCDMA(%d)\n", + (unsigned long)dma_addr, chan->hc_num); + } + + /* Start the split */ + if (chan->do_split) { + u32 hcsplt = dwc2_readl(hsotg, HCSPLT(chan->hc_num)); + + hcsplt |= HCSPLT_SPLTENA; + dwc2_writel(hsotg, hcsplt, HCSPLT(chan->hc_num)); + } + + hcchar = dwc2_readl(hsotg, HCCHAR(chan->hc_num)); + hcchar &= ~HCCHAR_MULTICNT_MASK; + hcchar |= (ec_mc << HCCHAR_MULTICNT_SHIFT) & HCCHAR_MULTICNT_MASK; + dwc2_hc_set_even_odd_frame(hsotg, chan, &hcchar); + + if (hcchar & HCCHAR_CHDIS) + dev_warn(hsotg->dev, + "%s: chdis set, channel %d, hcchar 0x%08x\n", + __func__, chan->hc_num, hcchar); + + /* Set host channel enable after all other setup is complete */ + hcchar |= HCCHAR_CHENA; + hcchar &= ~HCCHAR_CHDIS; + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, " Multi Cnt: %d\n", + (hcchar & HCCHAR_MULTICNT_MASK) >> + HCCHAR_MULTICNT_SHIFT); + + dwc2_writel(hsotg, hcchar, HCCHAR(chan->hc_num)); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "Wrote %08x to HCCHAR(%d)\n", hcchar, + chan->hc_num); + + chan->xfer_started = 1; + chan->requests++; + + if (!hsotg->params.host_dma && + !chan->ep_is_in && chan->xfer_len > 0) + /* Load OUT packet into the appropriate Tx FIFO */ + dwc2_hc_write_packet(hsotg, chan); +} + +/** + * dwc2_hc_start_transfer_ddma() - Does the setup for a data transfer for a + * host channel and starts the transfer in Descriptor DMA mode + * + * @hsotg: Programming view of DWC_otg controller + * @chan: Information needed to initialize the host channel + * + * 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. + */ +void dwc2_hc_start_transfer_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) +{ + u32 hcchar; + u32 hctsiz = 0; + + if (chan->do_ping) + hctsiz |= TSIZ_DOPNG; + + if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) + dwc2_set_pid_isoc(chan); + + /* Packet Count and Xfer Size are not used in Descriptor DMA mode */ + hctsiz |= chan->data_pid_start << TSIZ_SC_MC_PID_SHIFT & + TSIZ_SC_MC_PID_MASK; + + /* 0 - 1 descriptor, 1 - 2 descriptors, etc */ + hctsiz |= (chan->ntd - 1) << TSIZ_NTD_SHIFT & TSIZ_NTD_MASK; + + /* Non-zero only for high-speed interrupt endpoints */ + hctsiz |= chan->schinfo << TSIZ_SCHINFO_SHIFT & TSIZ_SCHINFO_MASK; + + if (dbg_hc(chan)) { + dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, + chan->hc_num); + dev_vdbg(hsotg->dev, " Start PID: %d\n", + chan->data_pid_start); + dev_vdbg(hsotg->dev, " NTD: %d\n", chan->ntd - 1); + } + + dwc2_writel(hsotg, hctsiz, HCTSIZ(chan->hc_num)); + + dma_sync_single_for_device(hsotg->dev, chan->desc_list_addr, + chan->desc_list_sz, DMA_TO_DEVICE); + + dwc2_writel(hsotg, chan->desc_list_addr, HCDMA(chan->hc_num)); + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "Wrote %pad to HCDMA(%d)\n", + &chan->desc_list_addr, chan->hc_num); + + hcchar = dwc2_readl(hsotg, HCCHAR(chan->hc_num)); + hcchar &= ~HCCHAR_MULTICNT_MASK; + hcchar |= chan->multi_count << HCCHAR_MULTICNT_SHIFT & + HCCHAR_MULTICNT_MASK; + + if (hcchar & HCCHAR_CHDIS) + dev_warn(hsotg->dev, + "%s: chdis set, channel %d, hcchar 0x%08x\n", + __func__, chan->hc_num, hcchar); + + /* Set host channel enable after all other setup is complete */ + hcchar |= HCCHAR_CHENA; + hcchar &= ~HCCHAR_CHDIS; + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, " Multi Cnt: %d\n", + (hcchar & HCCHAR_MULTICNT_MASK) >> + HCCHAR_MULTICNT_SHIFT); + + dwc2_writel(hsotg, hcchar, HCCHAR(chan->hc_num)); + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "Wrote %08x to HCCHAR(%d)\n", hcchar, + chan->hc_num); + + chan->xfer_started = 1; + chan->requests++; +} + +/** + * dwc2_hc_continue_transfer() - Continues a data transfer that was started by + * a previous call to dwc2_hc_start_transfer() + * + * @hsotg: Programming view of DWC_otg controller + * @chan: Information needed to initialize the host channel + * + * 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 + */ +static int dwc2_hc_continue_transfer(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan) +{ + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, + chan->hc_num); + + if (chan->do_split) + /* SPLITs always queue just once per channel */ + return 0; + + if (chan->data_pid_start == DWC2_HC_PID_SETUP) + /* SETUPs are queued only once since they can't be NAK'd */ + return 0; + + if (chan->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 NAK'd 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. + */ + u32 hcchar = dwc2_readl(hsotg, HCCHAR(chan->hc_num)); + + dwc2_hc_set_even_odd_frame(hsotg, chan, &hcchar); + hcchar |= HCCHAR_CHENA; + hcchar &= ~HCCHAR_CHDIS; + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, " IN xfer: hcchar = 0x%08x\n", + hcchar); + dwc2_writel(hsotg, hcchar, HCCHAR(chan->hc_num)); + chan->requests++; + return 1; + } + + /* OUT transfers */ + + if (chan->xfer_count < chan->xfer_len) { + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) { + u32 hcchar = dwc2_readl(hsotg, + HCCHAR(chan->hc_num)); + + dwc2_hc_set_even_odd_frame(hsotg, chan, + &hcchar); + } + + /* Load OUT packet into the appropriate Tx FIFO */ + dwc2_hc_write_packet(hsotg, chan); + chan->requests++; + return 1; + } + + return 0; +} + +/* + * ========================================================================= + * HCD + * ========================================================================= + */ + +/* + * Processes all the URBs in a single list of QHs. Completes them with + * -ETIMEDOUT and frees the QTD. + * + * Must be called with interrupt disabled and spinlock held + */ +static void dwc2_kill_urbs_in_qh_list(struct dwc2_hsotg *hsotg, + struct list_head *qh_list) +{ + struct dwc2_qh *qh, *qh_tmp; + struct dwc2_qtd *qtd, *qtd_tmp; + + list_for_each_entry_safe(qh, qh_tmp, qh_list, qh_list_entry) { + list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, + qtd_list_entry) { + dwc2_host_complete(hsotg, qtd, -ECONNRESET); + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); + } + } +} + +static void dwc2_qh_list_free(struct dwc2_hsotg *hsotg, + struct list_head *qh_list) +{ + struct dwc2_qtd *qtd, *qtd_tmp; + struct dwc2_qh *qh, *qh_tmp; + unsigned long flags; + + if (!qh_list->next) + /* The list hasn't been initialized yet */ + return; + + spin_lock_irqsave(&hsotg->lock, flags); + + /* Ensure there are no QTDs or URBs left */ + dwc2_kill_urbs_in_qh_list(hsotg, qh_list); + + list_for_each_entry_safe(qh, qh_tmp, qh_list, qh_list_entry) { + dwc2_hcd_qh_unlink(hsotg, qh); + + /* Free each QTD in the QH's QTD list */ + list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, + qtd_list_entry) + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); + + if (qh->channel && qh->channel->qh == qh) + qh->channel->qh = NULL; + + spin_unlock_irqrestore(&hsotg->lock, flags); + dwc2_hcd_qh_free(hsotg, qh); + spin_lock_irqsave(&hsotg->lock, flags); + } + + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +/* + * 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. + * + * Must be called with interrupt disabled and spinlock held + */ +static void dwc2_kill_all_urbs(struct dwc2_hsotg *hsotg) +{ + dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_inactive); + dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_waiting); + dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_active); + dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_inactive); + dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_ready); + dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_assigned); + dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_queued); +} + +/** + * dwc2_hcd_start() - Starts the HCD when switching to Host mode + * + * @hsotg: Pointer to struct dwc2_hsotg + */ +void dwc2_hcd_start(struct dwc2_hsotg *hsotg) +{ + u32 hprt0; + + if (hsotg->op_state == OTG_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 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_RST; + dwc2_writel(hsotg, hprt0, HPRT0); + } + + queue_delayed_work(hsotg->wq_otg, &hsotg->start_work, + msecs_to_jiffies(50)); +} + +/* Must be called with interrupt disabled and spinlock held */ +static void dwc2_hcd_cleanup_channels(struct dwc2_hsotg *hsotg) +{ + int num_channels = hsotg->params.host_channels; + struct dwc2_host_chan *channel; + u32 hcchar; + int i; + + if (!hsotg->params.host_dma) { + /* Flush out any channel requests in slave mode */ + for (i = 0; i < num_channels; i++) { + channel = hsotg->hc_ptr_array[i]; + if (!list_empty(&channel->hc_list_entry)) + continue; + hcchar = dwc2_readl(hsotg, HCCHAR(i)); + if (hcchar & HCCHAR_CHENA) { + hcchar &= ~(HCCHAR_CHENA | HCCHAR_EPDIR); + hcchar |= HCCHAR_CHDIS; + dwc2_writel(hsotg, hcchar, HCCHAR(i)); + } + } + } + + for (i = 0; i < num_channels; i++) { + channel = hsotg->hc_ptr_array[i]; + if (!list_empty(&channel->hc_list_entry)) + continue; + hcchar = dwc2_readl(hsotg, HCCHAR(i)); + if (hcchar & HCCHAR_CHENA) { + /* Halt the channel */ + hcchar |= HCCHAR_CHDIS; + dwc2_writel(hsotg, hcchar, HCCHAR(i)); + } + + dwc2_hc_cleanup(hsotg, channel); + list_add_tail(&channel->hc_list_entry, &hsotg->free_hc_list); + /* + * Added for Descriptor DMA to prevent channel double cleanup in + * release_channel_ddma(), which is called from ep_disable when + * device disconnects + */ + channel->qh = NULL; + } + /* All channels have been freed, mark them available */ + if (hsotg->params.uframe_sched) { + hsotg->available_host_channels = + hsotg->params.host_channels; + } else { + hsotg->non_periodic_channels = 0; + hsotg->periodic_channels = 0; + } +} + +/** + * dwc2_hcd_connect() - Handles connect of the HCD + * + * @hsotg: Pointer to struct dwc2_hsotg + * + * Must be called with interrupt disabled and spinlock held + */ +void dwc2_hcd_connect(struct dwc2_hsotg *hsotg) +{ + if (hsotg->lx_state != DWC2_L0) + usb_hcd_resume_root_hub(hsotg->priv); + + hsotg->flags.b.port_connect_status_change = 1; + hsotg->flags.b.port_connect_status = 1; +} + +/** + * dwc2_hcd_disconnect() - Handles disconnect of the HCD + * + * @hsotg: Pointer to struct dwc2_hsotg + * @force: If true, we won't try to reconnect even if we see device connected. + * + * Must be called with interrupt disabled and spinlock held + */ +void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force) +{ + u32 intr; + u32 hprt0; + + /* Set status flags for the hub driver */ + hsotg->flags.b.port_connect_status_change = 1; + hsotg->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 = dwc2_readl(hsotg, GINTMSK); + intr &= ~(GINTSTS_NPTXFEMP | GINTSTS_PTXFEMP | GINTSTS_HCHINT); + dwc2_writel(hsotg, intr, GINTMSK); + intr = GINTSTS_NPTXFEMP | GINTSTS_PTXFEMP | GINTSTS_HCHINT; + dwc2_writel(hsotg, intr, GINTSTS); + + /* + * 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 (dwc2_is_device_mode(hsotg)) { + if (hsotg->op_state != OTG_STATE_A_SUSPEND) { + dev_dbg(hsotg->dev, "Disconnect: PortPower off\n"); + dwc2_writel(hsotg, 0, HPRT0); + } + + dwc2_disable_host_interrupts(hsotg); + } + + /* Respond with an error status to all URBs in the schedule */ + dwc2_kill_all_urbs(hsotg); + + if (dwc2_is_host_mode(hsotg)) + /* Clean up any host channels that were in use */ + dwc2_hcd_cleanup_channels(hsotg); + + dwc2_host_disconnect(hsotg); + + /* + * Add an extra check here to see if we're actually connected but + * we don't have a detection interrupt pending. This can happen if: + * 1. hardware sees connect + * 2. hardware sees disconnect + * 3. hardware sees connect + * 4. dwc2_port_intr() - clears connect interrupt + * 5. dwc2_handle_common_intr() - calls here + * + * Without the extra check here we will end calling disconnect + * and won't get any future interrupts to handle the connect. + */ + if (!force) { + hprt0 = dwc2_readl(hsotg, HPRT0); + if (!(hprt0 & HPRT0_CONNDET) && (hprt0 & HPRT0_CONNSTS)) + dwc2_hcd_connect(hsotg); + } +} + +/** + * dwc2_hcd_rem_wakeup() - Handles Remote Wakeup + * + * @hsotg: Pointer to struct dwc2_hsotg + */ +static void dwc2_hcd_rem_wakeup(struct dwc2_hsotg *hsotg) +{ + if (hsotg->bus_suspended) { + hsotg->flags.b.port_suspend_change = 1; + usb_hcd_resume_root_hub(hsotg->priv); + } + + if (hsotg->lx_state == DWC2_L1) + hsotg->flags.b.port_l1_change = 1; +} + +/** + * dwc2_hcd_stop() - Halts the DWC_otg host mode operations in a clean manner + * + * @hsotg: Pointer to struct dwc2_hsotg + * + * Must be called with interrupt disabled and spinlock held + */ +void dwc2_hcd_stop(struct dwc2_hsotg *hsotg) +{ + dev_dbg(hsotg->dev, "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 */ + dwc2_disable_host_interrupts(hsotg); + + /* Turn off the vbus power */ + dev_dbg(hsotg->dev, "PortPower off\n"); + dwc2_writel(hsotg, 0, HPRT0); +} + +/* Caller must hold driver lock */ +static int dwc2_hcd_urb_enqueue(struct dwc2_hsotg *hsotg, + struct dwc2_hcd_urb *urb, struct dwc2_qh *qh, + struct dwc2_qtd *qtd) +{ + u32 intr_mask; + int retval; + int dev_speed; + + if (!hsotg->flags.b.port_connect_status) { + /* No longer connected */ + dev_err(hsotg->dev, "Not connected\n"); + return -ENODEV; + } + + dev_speed = dwc2_host_get_speed(hsotg, urb->priv); + + /* Some configurations cannot support LS traffic on a FS root port */ + if ((dev_speed == USB_SPEED_LOW) && + (hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED) && + (hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI)) { + u32 hprt0 = dwc2_readl(hsotg, HPRT0); + u32 prtspd = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; + + if (prtspd == HPRT0_SPD_FULL_SPEED) + return -ENODEV; + } + + if (!qtd) + return -EINVAL; + + dwc2_hcd_qtd_init(qtd, urb); + retval = dwc2_hcd_qtd_add(hsotg, qtd, qh); + if (retval) { + dev_err(hsotg->dev, + "DWC OTG HCD URB Enqueue failed adding QTD. Error status %d\n", + retval); + return retval; + } + + intr_mask = dwc2_readl(hsotg, GINTMSK); + if (!(intr_mask & GINTSTS_SOF)) { + enum dwc2_transaction_type tr_type; + + if (qtd->qh->ep_type == USB_ENDPOINT_XFER_BULK && + !(qtd->urb->flags & URB_GIVEBACK_ASAP)) + /* + * Do not schedule SG transactions until qtd has + * URB_GIVEBACK_ASAP set + */ + return 0; + + tr_type = dwc2_hcd_select_transactions(hsotg); + if (tr_type != DWC2_TRANSACTION_NONE) + dwc2_hcd_queue_transactions(hsotg, tr_type); + } + + return 0; +} + +/* Must be called with interrupt disabled and spinlock held */ +static int dwc2_hcd_urb_dequeue(struct dwc2_hsotg *hsotg, + struct dwc2_hcd_urb *urb) +{ + struct dwc2_qh *qh; + struct dwc2_qtd *urb_qtd; + + urb_qtd = urb->qtd; + if (!urb_qtd) { + dev_dbg(hsotg->dev, "## Urb QTD is NULL ##\n"); + return -EINVAL; + } + + qh = urb_qtd->qh; + if (!qh) { + dev_dbg(hsotg->dev, "## Urb QTD QH is NULL ##\n"); + return -EINVAL; + } + + urb->priv = NULL; + + if (urb_qtd->in_process && qh->channel) { + dwc2_dump_channel_info(hsotg, qh->channel); + + /* The QTD is in process (it has been assigned to a channel) */ + if (hsotg->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. + */ + dwc2_hc_halt(hsotg, qh->channel, + DWC2_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 (!hsotg->params.dma_desc_enable) { + u8 in_process = urb_qtd->in_process; + + dwc2_hcd_qtd_unlink_and_free(hsotg, urb_qtd, qh); + if (in_process) { + dwc2_hcd_qh_deactivate(hsotg, qh, 0); + qh->channel = NULL; + } else if (list_empty(&qh->qtd_list)) { + dwc2_hcd_qh_unlink(hsotg, qh); + } + } else { + dwc2_hcd_qtd_unlink_and_free(hsotg, urb_qtd, qh); + } + + return 0; +} + +/* Must NOT be called with interrupt disabled or spinlock held */ +static int dwc2_hcd_endpoint_disable(struct dwc2_hsotg *hsotg, + struct usb_host_endpoint *ep, int retry) +{ + struct dwc2_qtd *qtd, *qtd_tmp; + struct dwc2_qh *qh; + unsigned long flags; + int rc; + + spin_lock_irqsave(&hsotg->lock, flags); + + qh = ep->hcpriv; + if (!qh) { + rc = -EINVAL; + goto err; + } + + while (!list_empty(&qh->qtd_list) && retry--) { + if (retry == 0) { + dev_err(hsotg->dev, + "## timeout in dwc2_hcd_endpoint_disable() ##\n"); + rc = -EBUSY; + goto err; + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + msleep(20); + spin_lock_irqsave(&hsotg->lock, flags); + qh = ep->hcpriv; + if (!qh) { + rc = -EINVAL; + goto err; + } + } + + dwc2_hcd_qh_unlink(hsotg, qh); + + /* Free each QTD in the QH's QTD list */ + list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); + + ep->hcpriv = NULL; + + if (qh->channel && qh->channel->qh == qh) + qh->channel->qh = NULL; + + spin_unlock_irqrestore(&hsotg->lock, flags); + + dwc2_hcd_qh_free(hsotg, qh); + + return 0; + +err: + ep->hcpriv = NULL; + spin_unlock_irqrestore(&hsotg->lock, flags); + + return rc; +} + +/* Must be called with interrupt disabled and spinlock held */ +static int dwc2_hcd_endpoint_reset(struct dwc2_hsotg *hsotg, + struct usb_host_endpoint *ep) +{ + struct dwc2_qh *qh = ep->hcpriv; + + if (!qh) + return -EINVAL; + + qh->data_toggle = DWC2_HC_PID_DATA0; + + return 0; +} + +/** + * dwc2_core_init() - Initializes the DWC_otg controller registers and + * prepares the core for device mode or host mode operation + * + * @hsotg: Programming view of the DWC_otg controller + * @initial_setup: If true then this is the first init for this instance. + */ +int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup) +{ + u32 usbcfg, otgctl; + int retval; + + dev_dbg(hsotg->dev, "%s(%p)\n", __func__, hsotg); + + usbcfg = dwc2_readl(hsotg, GUSBCFG); + + /* Set ULPI External VBUS bit if needed */ + usbcfg &= ~GUSBCFG_ULPI_EXT_VBUS_DRV; + if (hsotg->params.phy_ulpi_ext_vbus) + usbcfg |= GUSBCFG_ULPI_EXT_VBUS_DRV; + + /* Set external TS Dline pulsing bit if needed */ + usbcfg &= ~GUSBCFG_TERMSELDLPULSE; + if (hsotg->params.ts_dline) + usbcfg |= GUSBCFG_TERMSELDLPULSE; + + dwc2_writel(hsotg, usbcfg, GUSBCFG); + + /* + * Reset the Controller + * + * We only need to reset the controller if this is a re-init. + * For the first init we know for sure that earlier code reset us (it + * needed to in order to properly detect various parameters). + */ + if (!initial_setup) { + retval = dwc2_core_reset(hsotg, false); + if (retval) { + dev_err(hsotg->dev, "%s(): Reset failed, aborting\n", + __func__); + return retval; + } + } + + /* + * This needs to happen in FS mode before any other programming occurs + */ + retval = dwc2_phy_init(hsotg, initial_setup); + if (retval) + return retval; + + /* Program the GAHBCFG Register */ + retval = dwc2_gahbcfg_init(hsotg); + if (retval) + return retval; + + /* Program the GUSBCFG register */ + dwc2_gusbcfg_init(hsotg); + + /* Program the GOTGCTL register */ + otgctl = dwc2_readl(hsotg, GOTGCTL); + otgctl &= ~GOTGCTL_OTGVER; + dwc2_writel(hsotg, otgctl, GOTGCTL); + + /* Clear the SRP success bit for FS-I2c */ + hsotg->srp_success = 0; + + /* Enable common interrupts */ + dwc2_enable_common_interrupts(hsotg); + + /* + * Do device or host initialization based on mode during PCD and + * HCD initialization + */ + if (dwc2_is_host_mode(hsotg)) { + dev_dbg(hsotg->dev, "Host Mode\n"); + hsotg->op_state = OTG_STATE_A_HOST; + } else { + dev_dbg(hsotg->dev, "Device Mode\n"); + hsotg->op_state = OTG_STATE_B_PERIPHERAL; + } + + return 0; +} + +/** + * dwc2_core_host_init() - Initializes the DWC_otg controller registers for + * Host mode + * + * @hsotg: Programming view of DWC_otg controller + * + * This function flushes the Tx and Rx FIFOs and flushes any entries in the + * request queues. Host channels are reset to ensure that they are ready for + * performing transfers. + */ +static void dwc2_core_host_init(struct dwc2_hsotg *hsotg) +{ + u32 hcfg, hfir, otgctl, usbcfg; + + dev_dbg(hsotg->dev, "%s(%p)\n", __func__, hsotg); + + /* Set HS/FS Timeout Calibration to 7 (max available value). + * The number of PHY clocks that the application programs in + * this field is added to the high/full speed interpacket timeout + * duration in the core to account for any additional delays + * introduced by the PHY. This can be required, because the delay + * introduced by the PHY in generating the linestate condition + * can vary from one PHY to another. + */ + usbcfg = dwc2_readl(hsotg, GUSBCFG); + usbcfg |= GUSBCFG_TOUTCAL(7); + dwc2_writel(hsotg, usbcfg, GUSBCFG); + + /* Restart the Phy Clock */ + dwc2_writel(hsotg, 0, PCGCTL); + + /* Initialize Host Configuration Register */ + dwc2_init_fs_ls_pclk_sel(hsotg); + if (hsotg->params.speed == DWC2_SPEED_PARAM_FULL || + hsotg->params.speed == DWC2_SPEED_PARAM_LOW) { + hcfg = dwc2_readl(hsotg, HCFG); + hcfg |= HCFG_FSLSSUPP; + dwc2_writel(hsotg, hcfg, HCFG); + } + + /* + * 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 (hsotg->params.reload_ctl) { + hfir = dwc2_readl(hsotg, HFIR); + hfir |= HFIR_RLDCTRL; + dwc2_writel(hsotg, hfir, HFIR); + } + + if (hsotg->params.dma_desc_enable) { + u32 op_mode = hsotg->hw_params.op_mode; + + if (hsotg->hw_params.snpsid < DWC2_CORE_REV_2_90a || + !hsotg->hw_params.dma_desc_enable || + op_mode == GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE || + op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE || + op_mode == GHWCFG2_OP_MODE_UNDEFINED) { + dev_err(hsotg->dev, + "Hardware does not support descriptor DMA mode -\n"); + dev_err(hsotg->dev, + "falling back to buffer DMA mode.\n"); + hsotg->params.dma_desc_enable = false; + } else { + hcfg = dwc2_readl(hsotg, HCFG); + hcfg |= HCFG_DESCDMA; + dwc2_writel(hsotg, hcfg, HCFG); + } + } + + /* Configure data FIFO sizes */ + dwc2_config_fifos(hsotg); + + /* TODO - check this */ + /* Clear Host Set HNP Enable in the OTG Control Register */ + otgctl = dwc2_readl(hsotg, GOTGCTL); + otgctl &= ~GOTGCTL_HSTSETHNPEN; + dwc2_writel(hsotg, otgctl, GOTGCTL); + + /* Make sure the FIFOs are flushed */ + dwc2_flush_tx_fifo(hsotg, 0x10 /* all TX FIFOs */); + dwc2_flush_rx_fifo(hsotg); + + /* Clear Host Set HNP Enable in the OTG Control Register */ + otgctl = dwc2_readl(hsotg, GOTGCTL); + otgctl &= ~GOTGCTL_HSTSETHNPEN; + dwc2_writel(hsotg, otgctl, GOTGCTL); + + if (!hsotg->params.dma_desc_enable) { + int num_channels, i; + u32 hcchar; + + /* Flush out any leftover queued requests */ + num_channels = hsotg->params.host_channels; + for (i = 0; i < num_channels; i++) { + hcchar = dwc2_readl(hsotg, HCCHAR(i)); + if (hcchar & HCCHAR_CHENA) { + hcchar &= ~HCCHAR_CHENA; + hcchar |= HCCHAR_CHDIS; + hcchar &= ~HCCHAR_EPDIR; + dwc2_writel(hsotg, hcchar, HCCHAR(i)); + } + } + + /* Halt all channels to put them into a known state */ + for (i = 0; i < num_channels; i++) { + hcchar = dwc2_readl(hsotg, HCCHAR(i)); + if (hcchar & HCCHAR_CHENA) { + hcchar |= HCCHAR_CHENA | HCCHAR_CHDIS; + hcchar &= ~HCCHAR_EPDIR; + dwc2_writel(hsotg, hcchar, HCCHAR(i)); + dev_dbg(hsotg->dev, "%s: Halt channel %d\n", + __func__, i); + + if (dwc2_hsotg_wait_bit_clear(hsotg, HCCHAR(i), + HCCHAR_CHENA, + 1000)) { + dev_warn(hsotg->dev, + "Unable to clear enable on channel %d\n", + i); + } + } + } + } + + /* Enable ACG feature in host mode, if supported */ + dwc2_enable_acg(hsotg); + + /* Turn on the vbus power */ + dev_dbg(hsotg->dev, "Init: Port Power? op_state=%d\n", hsotg->op_state); + if (hsotg->op_state == OTG_STATE_A_HOST) { + u32 hprt0 = dwc2_read_hprt0(hsotg); + + dev_dbg(hsotg->dev, "Init: Power Port (%d)\n", + !!(hprt0 & HPRT0_PWR)); + if (!(hprt0 & HPRT0_PWR)) { + hprt0 |= HPRT0_PWR; + dwc2_writel(hsotg, hprt0, HPRT0); + } + } + + dwc2_enable_host_interrupts(hsotg); +} + +/* + * Initializes dynamic portions of the DWC_otg HCD state + * + * Must be called with interrupt disabled and spinlock held + */ +static void dwc2_hcd_reinit(struct dwc2_hsotg *hsotg) +{ + struct dwc2_host_chan *chan, *chan_tmp; + int num_channels; + int i; + + hsotg->flags.d32 = 0; + hsotg->non_periodic_qh_ptr = &hsotg->non_periodic_sched_active; + + if (hsotg->params.uframe_sched) { + hsotg->available_host_channels = + hsotg->params.host_channels; + } else { + hsotg->non_periodic_channels = 0; + hsotg->periodic_channels = 0; + } + + /* + * Put all channels in the free channel list and clean up channel + * states + */ + list_for_each_entry_safe(chan, chan_tmp, &hsotg->free_hc_list, + hc_list_entry) + list_del_init(&chan->hc_list_entry); + + num_channels = hsotg->params.host_channels; + for (i = 0; i < num_channels; i++) { + chan = hsotg->hc_ptr_array[i]; + list_add_tail(&chan->hc_list_entry, &hsotg->free_hc_list); + dwc2_hc_cleanup(hsotg, chan); + } + + /* Initialize the DWC core for host mode operation */ + dwc2_core_host_init(hsotg); +} + +static void dwc2_hc_init_split(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + struct dwc2_qtd *qtd, struct dwc2_hcd_urb *urb) +{ + int hub_addr, hub_port; + + chan->do_split = 1; + chan->xact_pos = qtd->isoc_split_pos; + chan->complete_split = qtd->complete_split; + dwc2_host_hub_info(hsotg, urb->priv, &hub_addr, &hub_port); + chan->hub_addr = (u8)hub_addr; + chan->hub_port = (u8)hub_port; +} + +static void dwc2_hc_init_xfer(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + struct dwc2_qtd *qtd) +{ + struct dwc2_hcd_urb *urb = qtd->urb; + struct dwc2_hcd_iso_packet_desc *frame_desc; + + switch (dwc2_hcd_get_pipe_type(&urb->pipe_info)) { + case USB_ENDPOINT_XFER_CONTROL: + chan->ep_type = USB_ENDPOINT_XFER_CONTROL; + + switch (qtd->control_phase) { + case DWC2_CONTROL_SETUP: + dev_vdbg(hsotg->dev, " Control setup transaction\n"); + chan->do_ping = 0; + chan->ep_is_in = 0; + chan->data_pid_start = DWC2_HC_PID_SETUP; + if (hsotg->params.host_dma) + chan->xfer_dma = urb->setup_dma; + else + chan->xfer_buf = urb->setup_packet; + chan->xfer_len = 8; + break; + + case DWC2_CONTROL_DATA: + dev_vdbg(hsotg->dev, " Control data transaction\n"); + chan->data_pid_start = qtd->data_toggle; + break; + + case DWC2_CONTROL_STATUS: + /* + * Direction is opposite of data direction or IN if no + * data + */ + dev_vdbg(hsotg->dev, " Control status transaction\n"); + if (urb->length == 0) + chan->ep_is_in = 1; + else + chan->ep_is_in = + dwc2_hcd_is_pipe_out(&urb->pipe_info); + if (chan->ep_is_in) + chan->do_ping = 0; + chan->data_pid_start = DWC2_HC_PID_DATA1; + chan->xfer_len = 0; + if (hsotg->params.host_dma) + chan->xfer_dma = hsotg->status_buf_dma; + else + chan->xfer_buf = hsotg->status_buf; + break; + } + break; + + case USB_ENDPOINT_XFER_BULK: + chan->ep_type = USB_ENDPOINT_XFER_BULK; + break; + + case USB_ENDPOINT_XFER_INT: + chan->ep_type = USB_ENDPOINT_XFER_INT; + break; + + case USB_ENDPOINT_XFER_ISOC: + chan->ep_type = USB_ENDPOINT_XFER_ISOC; + if (hsotg->params.dma_desc_enable) + break; + + frame_desc = &urb->iso_descs[qtd->isoc_frame_index]; + frame_desc->status = 0; + + if (hsotg->params.host_dma) { + chan->xfer_dma = urb->dma; + chan->xfer_dma += frame_desc->offset + + qtd->isoc_split_offset; + } else { + chan->xfer_buf = urb->buf; + chan->xfer_buf += frame_desc->offset + + qtd->isoc_split_offset; + } + + chan->xfer_len = frame_desc->length - qtd->isoc_split_offset; + + if (chan->xact_pos == DWC2_HCSPLT_XACTPOS_ALL) { + if (chan->xfer_len <= 188) + chan->xact_pos = DWC2_HCSPLT_XACTPOS_ALL; + else + chan->xact_pos = DWC2_HCSPLT_XACTPOS_BEGIN; + } + break; + } +} + +static int dwc2_alloc_split_dma_aligned_buf(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh, + struct dwc2_host_chan *chan) +{ + if (!hsotg->unaligned_cache || + chan->max_packet > DWC2_KMEM_UNALIGNED_BUF_SIZE) + return -ENOMEM; + + if (!qh->dw_align_buf) { + qh->dw_align_buf = kmem_cache_alloc(hsotg->unaligned_cache, + GFP_ATOMIC | GFP_DMA); + if (!qh->dw_align_buf) + return -ENOMEM; + } + + qh->dw_align_buf_dma = dma_map_single(hsotg->dev, qh->dw_align_buf, + DWC2_KMEM_UNALIGNED_BUF_SIZE, + DMA_FROM_DEVICE); + + if (dma_mapping_error(hsotg->dev, qh->dw_align_buf_dma)) { + dev_err(hsotg->dev, "can't map align_buf\n"); + chan->align_buf = 0; + return -EINVAL; + } + + chan->align_buf = qh->dw_align_buf_dma; + return 0; +} + +#define DWC2_USB_DMA_ALIGN 4 + +static void dwc2_free_dma_aligned_buffer(struct urb *urb) +{ + void *stored_xfer_buffer; + size_t length; + + if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) + return; + + /* Restore urb->transfer_buffer from the end of the allocated area */ + memcpy(&stored_xfer_buffer, + PTR_ALIGN(urb->transfer_buffer + urb->transfer_buffer_length, + dma_get_cache_alignment()), + sizeof(urb->transfer_buffer)); + + if (usb_urb_dir_in(urb)) { + if (usb_pipeisoc(urb->pipe)) + length = urb->transfer_buffer_length; + else + length = urb->actual_length; + + memcpy(stored_xfer_buffer, urb->transfer_buffer, length); + } + kfree(urb->transfer_buffer); + urb->transfer_buffer = stored_xfer_buffer; + + urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; +} + +static int dwc2_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags) +{ + void *kmalloc_ptr; + size_t kmalloc_size; + + if (urb->num_sgs || urb->sg || + urb->transfer_buffer_length == 0 || + !((uintptr_t)urb->transfer_buffer & (DWC2_USB_DMA_ALIGN - 1))) + return 0; + + /* + * Allocate a buffer with enough padding for original transfer_buffer + * pointer. This allocation is guaranteed to be aligned properly for + * DMA + */ + kmalloc_size = urb->transfer_buffer_length + + (dma_get_cache_alignment() - 1) + + sizeof(urb->transfer_buffer); + + kmalloc_ptr = kmalloc(kmalloc_size, mem_flags); + if (!kmalloc_ptr) + return -ENOMEM; + + /* + * Position value of original urb->transfer_buffer pointer to the end + * of allocation for later referencing + */ + memcpy(PTR_ALIGN(kmalloc_ptr + urb->transfer_buffer_length, + dma_get_cache_alignment()), + &urb->transfer_buffer, sizeof(urb->transfer_buffer)); + + if (usb_urb_dir_out(urb)) + memcpy(kmalloc_ptr, urb->transfer_buffer, + urb->transfer_buffer_length); + urb->transfer_buffer = kmalloc_ptr; + + urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; + + return 0; +} + +static int dwc2_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + int ret; + + /* We assume setup_dma is always aligned; warn if not */ + WARN_ON_ONCE(urb->setup_dma && + (urb->setup_dma & (DWC2_USB_DMA_ALIGN - 1))); + + ret = dwc2_alloc_dma_aligned_buffer(urb, mem_flags); + if (ret) + return ret; + + ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); + if (ret) + dwc2_free_dma_aligned_buffer(urb); + + return ret; +} + +static void dwc2_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ + usb_hcd_unmap_urb_for_dma(hcd, urb); + dwc2_free_dma_aligned_buffer(urb); +} + +/** + * dwc2_assign_and_init_hc() - 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. + * + * @hsotg: The HCD state structure + * @qh: Transactions from the first QTD for this QH are selected and assigned + * to a free host channel + */ +static int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + struct dwc2_host_chan *chan; + struct dwc2_hcd_urb *urb; + struct dwc2_qtd *qtd; + + if (dbg_qh(qh)) + dev_vdbg(hsotg->dev, "%s(%p,%p)\n", __func__, hsotg, qh); + + if (list_empty(&qh->qtd_list)) { + dev_dbg(hsotg->dev, "No QTDs in QH list\n"); + return -ENOMEM; + } + + if (list_empty(&hsotg->free_hc_list)) { + dev_dbg(hsotg->dev, "No free channel to assign\n"); + return -ENOMEM; + } + + chan = list_first_entry(&hsotg->free_hc_list, struct dwc2_host_chan, + hc_list_entry); + + /* Remove host channel from free list */ + list_del_init(&chan->hc_list_entry); + + qtd = list_first_entry(&qh->qtd_list, struct dwc2_qtd, qtd_list_entry); + urb = qtd->urb; + qh->channel = chan; + 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. + */ + chan->dev_addr = dwc2_hcd_get_dev_addr(&urb->pipe_info); + chan->ep_num = dwc2_hcd_get_ep_num(&urb->pipe_info); + chan->speed = qh->dev_speed; + chan->max_packet = qh->maxp; + + chan->xfer_started = 0; + chan->halt_status = DWC2_HC_XFER_NO_HALT_STATUS; + chan->error_state = (qtd->error_count > 0); + chan->halt_on_queue = 0; + chan->halt_pending = 0; + chan->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. + */ + + chan->ep_is_in = (dwc2_hcd_is_pipe_in(&urb->pipe_info) != 0); + if (chan->ep_is_in) + chan->do_ping = 0; + else + chan->do_ping = qh->ping_state; + + chan->data_pid_start = qh->data_toggle; + chan->multi_count = 1; + + if (urb->actual_length > urb->length && + !dwc2_hcd_is_pipe_in(&urb->pipe_info)) + urb->actual_length = urb->length; + + if (hsotg->params.host_dma) + chan->xfer_dma = urb->dma + urb->actual_length; + else + chan->xfer_buf = (u8 *)urb->buf + urb->actual_length; + + chan->xfer_len = urb->length - urb->actual_length; + chan->xfer_count = 0; + + /* Set the split attributes if required */ + if (qh->do_split) + dwc2_hc_init_split(hsotg, chan, qtd, urb); + else + chan->do_split = 0; + + /* Set the transfer attributes */ + dwc2_hc_init_xfer(hsotg, chan, qtd); + + /* For non-dword aligned buffers */ + if (hsotg->params.host_dma && qh->do_split && + chan->ep_is_in && (chan->xfer_dma & 0x3)) { + dev_vdbg(hsotg->dev, "Non-aligned buffer\n"); + if (dwc2_alloc_split_dma_aligned_buf(hsotg, qh, chan)) { + dev_err(hsotg->dev, + "Failed to allocate memory to handle non-aligned buffer\n"); + /* Add channel back to free list */ + chan->align_buf = 0; + chan->multi_count = 0; + list_add_tail(&chan->hc_list_entry, + &hsotg->free_hc_list); + qtd->in_process = 0; + qh->channel = NULL; + return -ENOMEM; + } + } else { + /* + * We assume that DMA is always aligned in non-split + * case or split out case. Warn if not. + */ + WARN_ON_ONCE(hsotg->params.host_dma && + (chan->xfer_dma & 0x3)); + chan->align_buf = 0; + } + + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) + /* + * This value may be modified when the transfer is started + * to reflect the actual transfer length + */ + chan->multi_count = qh->maxp_mult; + + if (hsotg->params.dma_desc_enable) { + chan->desc_list_addr = qh->desc_list_dma; + chan->desc_list_sz = qh->desc_list_sz; + } + + dwc2_hc_init(hsotg, chan); + chan->qh = qh; + + return 0; +} + +/** + * dwc2_hcd_select_transactions() - Selects transactions from the HCD transfer + * schedule and assigns them to available host channels. Called from the HCD + * interrupt handler functions. + * + * @hsotg: The HCD state structure + * + * Return: The types of new transactions that were assigned to host channels + */ +enum dwc2_transaction_type dwc2_hcd_select_transactions( + struct dwc2_hsotg *hsotg) +{ + enum dwc2_transaction_type ret_val = DWC2_TRANSACTION_NONE; + struct list_head *qh_ptr; + struct dwc2_qh *qh; + int num_channels; + +#ifdef DWC2_DEBUG_SOF + dev_vdbg(hsotg->dev, " Select Transactions\n"); +#endif + + /* Process entries in the periodic ready list */ + qh_ptr = hsotg->periodic_sched_ready.next; + while (qh_ptr != &hsotg->periodic_sched_ready) { + if (list_empty(&hsotg->free_hc_list)) + break; + if (hsotg->params.uframe_sched) { + if (hsotg->available_host_channels <= 1) + break; + hsotg->available_host_channels--; + } + qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); + if (dwc2_assign_and_init_hc(hsotg, qh)) { + if (hsotg->params.uframe_sched) + hsotg->available_host_channels++; + break; + } + + /* + * Move the QH from the periodic ready schedule to the + * periodic assigned schedule + */ + qh_ptr = qh_ptr->next; + list_move_tail(&qh->qh_list_entry, + &hsotg->periodic_sched_assigned); + ret_val = DWC2_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. + */ + num_channels = hsotg->params.host_channels; + qh_ptr = hsotg->non_periodic_sched_inactive.next; + while (qh_ptr != &hsotg->non_periodic_sched_inactive) { + if (!hsotg->params.uframe_sched && + hsotg->non_periodic_channels >= num_channels - + hsotg->periodic_channels) + break; + if (list_empty(&hsotg->free_hc_list)) + break; + qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); + if (hsotg->params.uframe_sched) { + if (hsotg->available_host_channels < 1) + break; + hsotg->available_host_channels--; + } + + if (dwc2_assign_and_init_hc(hsotg, qh)) { + if (hsotg->params.uframe_sched) + hsotg->available_host_channels++; + break; + } + + /* + * Move the QH from the non-periodic inactive schedule to the + * non-periodic active schedule + */ + qh_ptr = qh_ptr->next; + list_move_tail(&qh->qh_list_entry, + &hsotg->non_periodic_sched_active); + + if (ret_val == DWC2_TRANSACTION_NONE) + ret_val = DWC2_TRANSACTION_NON_PERIODIC; + else + ret_val = DWC2_TRANSACTION_ALL; + + if (!hsotg->params.uframe_sched) + hsotg->non_periodic_channels++; + } + + return ret_val; +} + +/** + * dwc2_queue_transaction() - Attempts to queue a single transaction request for + * a host channel associated with either a periodic or non-periodic transfer + * + * @hsotg: The HCD state structure + * @chan: Host channel descriptor associated with either a periodic or + * non-periodic transfer + * @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 + * + * 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. + * + * Must be called with interrupt disabled and spinlock held + */ +static int dwc2_queue_transaction(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + u16 fifo_dwords_avail) +{ + int retval = 0; + + if (chan->do_split) + /* Put ourselves on the list to keep order straight */ + list_move_tail(&chan->split_order_list_entry, + &hsotg->split_order); + + if (hsotg->params.host_dma && chan->qh) { + if (hsotg->params.dma_desc_enable) { + if (!chan->xfer_started || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) { + dwc2_hcd_start_xfer_ddma(hsotg, chan->qh); + chan->qh->ping_state = 0; + } + } else if (!chan->xfer_started) { + dwc2_hc_start_transfer(hsotg, chan); + chan->qh->ping_state = 0; + } + } else if (chan->halt_pending) { + /* Don't queue a request if the channel has been halted */ + } else if (chan->halt_on_queue) { + dwc2_hc_halt(hsotg, chan, chan->halt_status); + } else if (chan->do_ping) { + if (!chan->xfer_started) + dwc2_hc_start_transfer(hsotg, chan); + } else if (!chan->ep_is_in || + chan->data_pid_start == DWC2_HC_PID_SETUP) { + if ((fifo_dwords_avail * 4) >= chan->max_packet) { + if (!chan->xfer_started) { + dwc2_hc_start_transfer(hsotg, chan); + retval = 1; + } else { + retval = dwc2_hc_continue_transfer(hsotg, chan); + } + } else { + retval = -1; + } + } else { + if (!chan->xfer_started) { + dwc2_hc_start_transfer(hsotg, chan); + retval = 1; + } else { + retval = dwc2_hc_continue_transfer(hsotg, chan); + } + } + + 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. + * + * Must be called with interrupt disabled and spinlock held + */ +static void dwc2_process_periodic_channels(struct dwc2_hsotg *hsotg) +{ + struct list_head *qh_ptr; + struct dwc2_qh *qh; + u32 tx_status; + u32 fspcavail; + u32 gintmsk; + int status; + bool no_queue_space = false; + bool no_fifo_space = false; + u32 qspcavail; + + /* If empty list then just adjust interrupt enables */ + if (list_empty(&hsotg->periodic_sched_assigned)) + goto exit; + + if (dbg_perio()) + dev_vdbg(hsotg->dev, "Queue periodic transactions\n"); + + tx_status = dwc2_readl(hsotg, HPTXSTS); + qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> + TXSTS_QSPCAVAIL_SHIFT; + fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> + TXSTS_FSPCAVAIL_SHIFT; + + if (dbg_perio()) { + dev_vdbg(hsotg->dev, " P Tx Req Queue Space Avail (before queue): %d\n", + qspcavail); + dev_vdbg(hsotg->dev, " P Tx FIFO Space Avail (before queue): %d\n", + fspcavail); + } + + qh_ptr = hsotg->periodic_sched_assigned.next; + while (qh_ptr != &hsotg->periodic_sched_assigned) { + tx_status = dwc2_readl(hsotg, HPTXSTS); + qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> + TXSTS_QSPCAVAIL_SHIFT; + if (qspcavail == 0) { + no_queue_space = true; + break; + } + + qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); + if (!qh->channel) { + qh_ptr = qh_ptr->next; + continue; + } + + /* Make sure EP's TT buffer is clean before queueing qtds */ + if (qh->tt_buffer_dirty) { + qh_ptr = qh_ptr->next; + continue; + } + + /* + * 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 (!hsotg->params.host_dma && + qh->channel->multi_count > 1) + hsotg->queuing_high_bandwidth = 1; + + fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> + TXSTS_FSPCAVAIL_SHIFT; + status = dwc2_queue_transaction(hsotg, qh->channel, fspcavail); + if (status < 0) { + no_fifo_space = true; + 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 (hsotg->params.host_dma || 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 + */ + list_move_tail(&qh->qh_list_entry, + &hsotg->periodic_sched_queued); + + /* done queuing high bandwidth */ + hsotg->queuing_high_bandwidth = 0; + } + } + +exit: + if (no_queue_space || no_fifo_space || + (!hsotg->params.host_dma && + !list_empty(&hsotg->periodic_sched_assigned))) { + /* + * 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.) + */ + gintmsk = dwc2_readl(hsotg, GINTMSK); + if (!(gintmsk & GINTSTS_PTXFEMP)) { + gintmsk |= GINTSTS_PTXFEMP; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } + } 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. + */ + gintmsk = dwc2_readl(hsotg, GINTMSK); + if (gintmsk & GINTSTS_PTXFEMP) { + gintmsk &= ~GINTSTS_PTXFEMP; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } + } +} + +/* + * 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. + * + * Must be called with interrupt disabled and spinlock held + */ +static void dwc2_process_non_periodic_channels(struct dwc2_hsotg *hsotg) +{ + struct list_head *orig_qh_ptr; + struct dwc2_qh *qh; + u32 tx_status; + u32 qspcavail; + u32 fspcavail; + u32 gintmsk; + int status; + int no_queue_space = 0; + int no_fifo_space = 0; + int more_to_do = 0; + + dev_vdbg(hsotg->dev, "Queue non-periodic transactions\n"); + + tx_status = dwc2_readl(hsotg, GNPTXSTS); + qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> + TXSTS_QSPCAVAIL_SHIFT; + fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> + TXSTS_FSPCAVAIL_SHIFT; + dev_vdbg(hsotg->dev, " NP Tx Req Queue Space Avail (before queue): %d\n", + qspcavail); + dev_vdbg(hsotg->dev, " NP Tx FIFO Space Avail (before queue): %d\n", + fspcavail); + + /* + * Keep track of the starting point. Skip over the start-of-list + * entry. + */ + if (hsotg->non_periodic_qh_ptr == &hsotg->non_periodic_sched_active) + hsotg->non_periodic_qh_ptr = hsotg->non_periodic_qh_ptr->next; + orig_qh_ptr = hsotg->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 = dwc2_readl(hsotg, GNPTXSTS); + qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> + TXSTS_QSPCAVAIL_SHIFT; + if (!hsotg->params.host_dma && qspcavail == 0) { + no_queue_space = 1; + break; + } + + qh = list_entry(hsotg->non_periodic_qh_ptr, struct dwc2_qh, + qh_list_entry); + if (!qh->channel) + goto next; + + /* Make sure EP's TT buffer is clean before queueing qtds */ + if (qh->tt_buffer_dirty) + goto next; + + fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> + TXSTS_FSPCAVAIL_SHIFT; + status = dwc2_queue_transaction(hsotg, qh->channel, fspcavail); + + if (status > 0) { + more_to_do = 1; + } else if (status < 0) { + no_fifo_space = 1; + break; + } +next: + /* Advance to next QH, skipping start-of-list entry */ + hsotg->non_periodic_qh_ptr = hsotg->non_periodic_qh_ptr->next; + if (hsotg->non_periodic_qh_ptr == + &hsotg->non_periodic_sched_active) + hsotg->non_periodic_qh_ptr = + hsotg->non_periodic_qh_ptr->next; + } while (hsotg->non_periodic_qh_ptr != orig_qh_ptr); + + if (!hsotg->params.host_dma) { + tx_status = dwc2_readl(hsotg, GNPTXSTS); + qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> + TXSTS_QSPCAVAIL_SHIFT; + fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> + TXSTS_FSPCAVAIL_SHIFT; + dev_vdbg(hsotg->dev, + " NP Tx Req Queue Space Avail (after queue): %d\n", + qspcavail); + dev_vdbg(hsotg->dev, + " NP Tx FIFO Space Avail (after queue): %d\n", + fspcavail); + + 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.) + */ + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk |= GINTSTS_NPTXFEMP; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } 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. + */ + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk &= ~GINTSTS_NPTXFEMP; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } + } +} + +/** + * dwc2_hcd_queue_transactions() - Processes the currently active host channels + * and queues transactions for these channels to the DWC_otg controller. Called + * from the HCD interrupt handler functions. + * + * @hsotg: The HCD state structure + * @tr_type: The type(s) of transactions to queue (non-periodic, periodic, + * or both) + * + * Must be called with interrupt disabled and spinlock held + */ +void dwc2_hcd_queue_transactions(struct dwc2_hsotg *hsotg, + enum dwc2_transaction_type tr_type) +{ +#ifdef DWC2_DEBUG_SOF + dev_vdbg(hsotg->dev, "Queue Transactions\n"); +#endif + /* Process host channels associated with periodic transfers */ + if (tr_type == DWC2_TRANSACTION_PERIODIC || + tr_type == DWC2_TRANSACTION_ALL) + dwc2_process_periodic_channels(hsotg); + + /* Process host channels associated with non-periodic transfers */ + if (tr_type == DWC2_TRANSACTION_NON_PERIODIC || + tr_type == DWC2_TRANSACTION_ALL) { + if (!list_empty(&hsotg->non_periodic_sched_active)) { + dwc2_process_non_periodic_channels(hsotg); + } else { + /* + * Ensure NP Tx FIFO empty interrupt is disabled when + * there are no non-periodic transfers to process + */ + u32 gintmsk = dwc2_readl(hsotg, GINTMSK); + + gintmsk &= ~GINTSTS_NPTXFEMP; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } + } +} + +static void dwc2_conn_id_status_change(struct work_struct *work) +{ + struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg, + wf_otg); + u32 count = 0; + u32 gotgctl; + unsigned long flags; + + dev_dbg(hsotg->dev, "%s()\n", __func__); + + gotgctl = dwc2_readl(hsotg, GOTGCTL); + dev_dbg(hsotg->dev, "gotgctl=%0x\n", gotgctl); + dev_dbg(hsotg->dev, "gotgctl.b.conidsts=%d\n", + !!(gotgctl & GOTGCTL_CONID_B)); + + /* B-Device connector (Device Mode) */ + if (gotgctl & GOTGCTL_CONID_B) { + dwc2_vbus_supply_exit(hsotg); + /* Wait for switch to device mode */ + dev_dbg(hsotg->dev, "connId B\n"); + if (hsotg->bus_suspended) { + dev_info(hsotg->dev, + "Do port resume before switching to device mode\n"); + dwc2_port_resume(hsotg); + } + while (!dwc2_is_device_mode(hsotg)) { + dev_info(hsotg->dev, + "Waiting for Peripheral Mode, Mode=%s\n", + dwc2_is_host_mode(hsotg) ? "Host" : + "Peripheral"); + msleep(20); + /* + * Sometimes the initial GOTGCTRL read is wrong, so + * check it again and jump to host mode if that was + * the case. + */ + gotgctl = dwc2_readl(hsotg, GOTGCTL); + if (!(gotgctl & GOTGCTL_CONID_B)) + goto host; + if (++count > 250) + break; + } + if (count > 250) + dev_err(hsotg->dev, + "Connection id status change timed out\n"); + + /* + * Exit Partial Power Down without restoring registers. + * No need to check the return value as registers + * are not being restored. + */ + if (hsotg->in_ppd && hsotg->lx_state == DWC2_L2) + dwc2_exit_partial_power_down(hsotg, 0, false); + + hsotg->op_state = OTG_STATE_B_PERIPHERAL; + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + spin_lock_irqsave(&hsotg->lock, flags); + dwc2_hsotg_core_init_disconnected(hsotg, false); + spin_unlock_irqrestore(&hsotg->lock, flags); + /* Enable ACG feature in device mode,if supported */ + dwc2_enable_acg(hsotg); + dwc2_hsotg_core_connect(hsotg); + } else { +host: + /* A-Device connector (Host Mode) */ + dev_dbg(hsotg->dev, "connId A\n"); + while (!dwc2_is_host_mode(hsotg)) { + dev_info(hsotg->dev, "Waiting for Host Mode, Mode=%s\n", + dwc2_is_host_mode(hsotg) ? + "Host" : "Peripheral"); + msleep(20); + if (++count > 250) + break; + } + if (count > 250) + dev_err(hsotg->dev, + "Connection id status change timed out\n"); + + spin_lock_irqsave(&hsotg->lock, flags); + dwc2_hsotg_disconnect(hsotg); + spin_unlock_irqrestore(&hsotg->lock, flags); + + hsotg->op_state = OTG_STATE_A_HOST; + /* Initialize the Core for Host mode */ + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + dwc2_hcd_start(hsotg); + } +} + +static void dwc2_wakeup_detected(struct timer_list *t) +{ + struct dwc2_hsotg *hsotg = timer_container_of(hsotg, t, wkp_timer); + u32 hprt0; + + dev_dbg(hsotg->dev, "%s()\n", __func__); + + /* + * Clear the Resume after 70ms. (Need 20 ms minimum. Use 70 ms + * so that OPT tests pass with all PHYs.) + */ + hprt0 = dwc2_read_hprt0(hsotg); + dev_dbg(hsotg->dev, "Resume: HPRT0=%0x\n", hprt0); + hprt0 &= ~HPRT0_RES; + dwc2_writel(hsotg, hprt0, HPRT0); + dev_dbg(hsotg->dev, "Clear Resume: HPRT0=%0x\n", + dwc2_readl(hsotg, HPRT0)); + + dwc2_hcd_rem_wakeup(hsotg); + hsotg->bus_suspended = false; + + /* Change to L0 state */ + hsotg->lx_state = DWC2_L0; +} + +static int dwc2_host_is_b_hnp_enabled(struct dwc2_hsotg *hsotg) +{ + struct usb_hcd *hcd = dwc2_hsotg_to_hcd(hsotg); + + return hcd->self.b_hnp_enable; +} + +/** + * dwc2_port_suspend() - Put controller in suspend mode for host. + * + * @hsotg: Programming view of the DWC_otg controller + * @windex: The control request wIndex field + * + * Return: non-zero if failed to enter suspend mode for host. + * + * This function is for entering Host mode suspend. + * Must NOT be called with interrupt disabled or spinlock held. + */ +int dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) +{ + unsigned long flags; + u32 pcgctl; + u32 gotgctl; + int ret = 0; + + dev_dbg(hsotg->dev, "%s()\n", __func__); + + spin_lock_irqsave(&hsotg->lock, flags); + + if (windex == hsotg->otg_port && dwc2_host_is_b_hnp_enabled(hsotg)) { + gotgctl = dwc2_readl(hsotg, GOTGCTL); + gotgctl |= GOTGCTL_HSTSETHNPEN; + dwc2_writel(hsotg, gotgctl, GOTGCTL); + hsotg->op_state = OTG_STATE_A_SUSPEND; + } + + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: + ret = dwc2_enter_partial_power_down(hsotg); + if (ret) + dev_err(hsotg->dev, + "enter partial_power_down failed.\n"); + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + /* + * Perform spin unlock and lock because in + * "dwc2_host_enter_hibernation()" function there is a spinlock + * logic which prevents servicing of any IRQ during entering + * hibernation. + */ + spin_unlock_irqrestore(&hsotg->lock, flags); + ret = dwc2_enter_hibernation(hsotg, 1); + if (ret) + dev_err(hsotg->dev, "enter hibernation failed.\n"); + spin_lock_irqsave(&hsotg->lock, flags); + break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If not hibernation nor partial power down are supported, + * clock gating is used to save power. + */ + if (!hsotg->params.no_clock_gating) + dwc2_host_enter_clock_gating(hsotg); + break; + } + + /* For HNP the bus must be suspended for at least 200ms */ + if (dwc2_host_is_b_hnp_enabled(hsotg)) { + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + + spin_unlock_irqrestore(&hsotg->lock, flags); + + msleep(200); + } else { + spin_unlock_irqrestore(&hsotg->lock, flags); + } + + return ret; +} + +/** + * dwc2_port_resume() - Exit controller from suspend mode for host. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return: non-zero if failed to exit suspend mode for host. + * + * This function is for exiting Host mode suspend. + * Must NOT be called with interrupt disabled or spinlock held. + */ +int dwc2_port_resume(struct dwc2_hsotg *hsotg) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&hsotg->lock, flags); + + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: + ret = dwc2_exit_partial_power_down(hsotg, 0, true); + if (ret) + dev_err(hsotg->dev, + "exit partial_power_down failed.\n"); + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + /* Exit host hibernation. */ + ret = dwc2_exit_hibernation(hsotg, 0, 0, 1); + if (ret) + dev_err(hsotg->dev, "exit hibernation failed.\n"); + break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If not hibernation nor partial power down are supported, + * port resume is done using the clock gating programming flow. + */ + spin_unlock_irqrestore(&hsotg->lock, flags); + dwc2_host_exit_clock_gating(hsotg, 0); + spin_lock_irqsave(&hsotg->lock, flags); + break; + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + + return ret; +} + +/* Handles hub class-specific requests */ +static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, + u16 wvalue, u16 windex, char *buf, u16 wlength) +{ + struct usb_hub_descriptor *hub_desc; + int retval = 0; + u32 hprt0; + u32 port_status; + u32 speed; + u32 pcgctl; + u32 pwr; + + switch (typereq) { + case ClearHubFeature: + dev_dbg(hsotg->dev, "ClearHubFeature %1xh\n", wvalue); + + switch (wvalue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* Nothing required here */ + break; + + default: + retval = -EINVAL; + dev_err(hsotg->dev, + "ClearHubFeature request %1xh unknown\n", + wvalue); + } + break; + + case ClearPortFeature: + if (wvalue != USB_PORT_FEAT_L1) + if (!windex || windex > 1) + goto error; + switch (wvalue) { + case USB_PORT_FEAT_ENABLE: + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_ENABLE\n"); + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_ENA; + dwc2_writel(hsotg, hprt0, HPRT0); + break; + + case USB_PORT_FEAT_SUSPEND: + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_SUSPEND\n"); + + if (hsotg->bus_suspended) + retval = dwc2_port_resume(hsotg); + break; + + case USB_PORT_FEAT_POWER: + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_POWER\n"); + hprt0 = dwc2_read_hprt0(hsotg); + pwr = hprt0 & HPRT0_PWR; + hprt0 &= ~HPRT0_PWR; + dwc2_writel(hsotg, hprt0, HPRT0); + if (pwr) + dwc2_vbus_supply_exit(hsotg); + break; + + case USB_PORT_FEAT_INDICATOR: + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_INDICATOR\n"); + /* Port indicator not supported */ + break; + + case USB_PORT_FEAT_C_CONNECTION: + /* + * Clears driver's internal Connect Status Change flag + */ + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_C_CONNECTION\n"); + hsotg->flags.b.port_connect_status_change = 0; + break; + + case USB_PORT_FEAT_C_RESET: + /* Clears driver's internal Port Reset Change flag */ + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_C_RESET\n"); + hsotg->flags.b.port_reset_change = 0; + break; + + case USB_PORT_FEAT_C_ENABLE: + /* + * Clears the driver's internal Port Enable/Disable + * Change flag + */ + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_C_ENABLE\n"); + hsotg->flags.b.port_enable_change = 0; + break; + + case USB_PORT_FEAT_C_SUSPEND: + /* + * Clears the driver's internal Port Suspend Change + * flag, which is set when resume signaling on the host + * port is complete + */ + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_C_SUSPEND\n"); + hsotg->flags.b.port_suspend_change = 0; + break; + + case USB_PORT_FEAT_C_PORT_L1: + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_C_PORT_L1\n"); + hsotg->flags.b.port_l1_change = 0; + break; + + case USB_PORT_FEAT_C_OVER_CURRENT: + dev_dbg(hsotg->dev, + "ClearPortFeature USB_PORT_FEAT_C_OVER_CURRENT\n"); + hsotg->flags.b.port_over_current_change = 0; + break; + + default: + retval = -EINVAL; + dev_err(hsotg->dev, + "ClearPortFeature request %1xh unknown or unsupported\n", + wvalue); + } + break; + + case GetHubDescriptor: + dev_dbg(hsotg->dev, "GetHubDescriptor\n"); + hub_desc = (struct usb_hub_descriptor *)buf; + hub_desc->bDescLength = 9; + hub_desc->bDescriptorType = USB_DT_HUB; + hub_desc->bNbrPorts = 1; + hub_desc->wHubCharacteristics = + cpu_to_le16(HUB_CHAR_COMMON_LPSM | + HUB_CHAR_INDV_PORT_OCPM); + hub_desc->bPwrOn2PwrGood = 1; + hub_desc->bHubContrCurrent = 0; + hub_desc->u.hs.DeviceRemovable[0] = 0; + hub_desc->u.hs.DeviceRemovable[1] = 0xff; + break; + + case GetHubStatus: + dev_dbg(hsotg->dev, "GetHubStatus\n"); + memset(buf, 0, 4); + break; + + case GetPortStatus: + dev_vdbg(hsotg->dev, + "GetPortStatus wIndex=0x%04x flags=0x%08x\n", windex, + hsotg->flags.d32); + if (!windex || windex > 1) + goto error; + + port_status = 0; + if (hsotg->flags.b.port_connect_status_change) + port_status |= USB_PORT_STAT_C_CONNECTION << 16; + if (hsotg->flags.b.port_enable_change) + port_status |= USB_PORT_STAT_C_ENABLE << 16; + if (hsotg->flags.b.port_suspend_change) + port_status |= USB_PORT_STAT_C_SUSPEND << 16; + if (hsotg->flags.b.port_l1_change) + port_status |= USB_PORT_STAT_C_L1 << 16; + if (hsotg->flags.b.port_reset_change) + port_status |= USB_PORT_STAT_C_RESET << 16; + if (hsotg->flags.b.port_over_current_change) { + dev_warn(hsotg->dev, "Overcurrent change detected\n"); + port_status |= USB_PORT_STAT_C_OVERCURRENT << 16; + } + + if (dwc2_is_device_mode(hsotg)) { + /* + * 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 = cpu_to_le32(port_status); + break; + } + + hprt0 = dwc2_readl(hsotg, HPRT0); + dev_vdbg(hsotg->dev, " HPRT0: 0x%08x\n", hprt0); + + if (hprt0 & HPRT0_CONNSTS) + port_status |= USB_PORT_STAT_CONNECTION; + if (hprt0 & HPRT0_ENA) + port_status |= USB_PORT_STAT_ENABLE; + if (hprt0 & HPRT0_SUSP) + port_status |= USB_PORT_STAT_SUSPEND; + if (hprt0 & HPRT0_OVRCURRACT) + port_status |= USB_PORT_STAT_OVERCURRENT; + if (hprt0 & HPRT0_RST) + port_status |= USB_PORT_STAT_RESET; + if (hprt0 & HPRT0_PWR) + port_status |= USB_PORT_STAT_POWER; + + speed = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; + if (speed == HPRT0_SPD_HIGH_SPEED) + port_status |= USB_PORT_STAT_HIGH_SPEED; + else if (speed == HPRT0_SPD_LOW_SPEED) + port_status |= USB_PORT_STAT_LOW_SPEED; + + if (hprt0 & HPRT0_TSTCTL_MASK) + port_status |= USB_PORT_STAT_TEST; + /* USB_PORT_FEAT_INDICATOR unsupported always 0 */ + + if (hsotg->params.dma_desc_fs_enable) { + /* + * Enable descriptor DMA only if a full speed + * device is connected. + */ + if (hsotg->new_connection && + ((port_status & + (USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_HIGH_SPEED | + USB_PORT_STAT_LOW_SPEED)) == + USB_PORT_STAT_CONNECTION)) { + u32 hcfg; + + dev_info(hsotg->dev, "Enabling descriptor DMA mode\n"); + hsotg->params.dma_desc_enable = true; + hcfg = dwc2_readl(hsotg, HCFG); + hcfg |= HCFG_DESCDMA; + dwc2_writel(hsotg, hcfg, HCFG); + hsotg->new_connection = false; + } + } + + dev_vdbg(hsotg->dev, "port_status=%08x\n", port_status); + *(__le32 *)buf = cpu_to_le32(port_status); + break; + + case SetHubFeature: + dev_dbg(hsotg->dev, "SetHubFeature\n"); + /* No HUB features supported */ + break; + + case SetPortFeature: + dev_dbg(hsotg->dev, "SetPortFeature\n"); + if (wvalue != USB_PORT_FEAT_TEST && (!windex || windex > 1)) + goto error; + + if (dwc2_is_device_mode(hsotg)) { + /* + * 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. + */ + break; + } + + switch (wvalue) { + case USB_PORT_FEAT_SUSPEND: + dev_dbg(hsotg->dev, + "SetPortFeature - USB_PORT_FEAT_SUSPEND\n"); + if (windex != hsotg->otg_port) + goto error; + if (!hsotg->bus_suspended) + retval = dwc2_port_suspend(hsotg, windex); + break; + + case USB_PORT_FEAT_POWER: + dev_dbg(hsotg->dev, + "SetPortFeature - USB_PORT_FEAT_POWER\n"); + hprt0 = dwc2_read_hprt0(hsotg); + pwr = hprt0 & HPRT0_PWR; + hprt0 |= HPRT0_PWR; + dwc2_writel(hsotg, hprt0, HPRT0); + if (!pwr) + dwc2_vbus_supply_init(hsotg); + break; + + case USB_PORT_FEAT_RESET: + dev_dbg(hsotg->dev, + "SetPortFeature - USB_PORT_FEAT_RESET\n"); + + hprt0 = dwc2_read_hprt0(hsotg); + + if (hsotg->hibernated) { + retval = dwc2_exit_hibernation(hsotg, 0, 1, 1); + if (retval) + dev_err(hsotg->dev, + "exit hibernation failed\n"); + } + + if (hsotg->in_ppd) { + retval = dwc2_exit_partial_power_down(hsotg, 1, + true); + if (retval) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + } + + if (hsotg->params.power_down == + DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended) + dwc2_host_exit_clock_gating(hsotg, 0); + + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~(PCGCTL_ENBL_SLEEP_GATING | PCGCTL_STOPPCLK); + dwc2_writel(hsotg, pcgctl, PCGCTL); + /* ??? Original driver does this */ + dwc2_writel(hsotg, 0, PCGCTL); + + hprt0 = dwc2_read_hprt0(hsotg); + pwr = hprt0 & HPRT0_PWR; + /* Clear suspend bit if resetting from suspend state */ + hprt0 &= ~HPRT0_SUSP; + + /* + * 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 (!dwc2_hcd_is_b_host(hsotg)) { + hprt0 |= HPRT0_PWR | HPRT0_RST; + dev_dbg(hsotg->dev, + "In host mode, hprt0=%08x\n", hprt0); + dwc2_writel(hsotg, hprt0, HPRT0); + if (!pwr) + dwc2_vbus_supply_init(hsotg); + } + + /* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */ + msleep(50); + hprt0 &= ~HPRT0_RST; + dwc2_writel(hsotg, hprt0, HPRT0); + hsotg->lx_state = DWC2_L0; /* Now back to On state */ + break; + + case USB_PORT_FEAT_INDICATOR: + dev_dbg(hsotg->dev, + "SetPortFeature - USB_PORT_FEAT_INDICATOR\n"); + /* Not supported */ + break; + + case USB_PORT_FEAT_TEST: + hprt0 = dwc2_read_hprt0(hsotg); + dev_dbg(hsotg->dev, + "SetPortFeature - USB_PORT_FEAT_TEST\n"); + hprt0 &= ~HPRT0_TSTCTL_MASK; + hprt0 |= (windex >> 8) << HPRT0_TSTCTL_SHIFT; + dwc2_writel(hsotg, hprt0, HPRT0); + break; + + default: + retval = -EINVAL; + dev_err(hsotg->dev, + "SetPortFeature %1xh unknown or unsupported\n", + wvalue); + break; + } + break; + + default: +error: + retval = -EINVAL; + dev_dbg(hsotg->dev, + "Unknown hub control request: %1xh wIndex: %1xh wValue: %1xh\n", + typereq, windex, wvalue); + break; + } + + return retval; +} + +static int dwc2_hcd_is_status_changed(struct dwc2_hsotg *hsotg, int port) +{ + int retval; + + if (port != 1) + return -EINVAL; + + retval = (hsotg->flags.b.port_connect_status_change || + hsotg->flags.b.port_reset_change || + hsotg->flags.b.port_enable_change || + hsotg->flags.b.port_suspend_change || + hsotg->flags.b.port_over_current_change); + + if (retval) { + dev_dbg(hsotg->dev, + "DWC OTG HCD HUB STATUS DATA: Root port status changed\n"); + dev_dbg(hsotg->dev, " port_connect_status_change: %d\n", + hsotg->flags.b.port_connect_status_change); + dev_dbg(hsotg->dev, " port_reset_change: %d\n", + hsotg->flags.b.port_reset_change); + dev_dbg(hsotg->dev, " port_enable_change: %d\n", + hsotg->flags.b.port_enable_change); + dev_dbg(hsotg->dev, " port_suspend_change: %d\n", + hsotg->flags.b.port_suspend_change); + dev_dbg(hsotg->dev, " port_over_current_change: %d\n", + hsotg->flags.b.port_over_current_change); + } + + return retval; +} + +int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg) +{ + u32 hfnum = dwc2_readl(hsotg, HFNUM); + +#ifdef DWC2_DEBUG_SOF + dev_vdbg(hsotg->dev, "DWC OTG HCD GET FRAME NUMBER %d\n", + (hfnum & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT); +#endif + return (hfnum & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT; +} + +int dwc2_hcd_get_future_frame_number(struct dwc2_hsotg *hsotg, int us) +{ + u32 hprt = dwc2_readl(hsotg, HPRT0); + u32 hfir = dwc2_readl(hsotg, HFIR); + u32 hfnum = dwc2_readl(hsotg, HFNUM); + unsigned int us_per_frame; + unsigned int frame_number; + unsigned int remaining; + unsigned int interval; + unsigned int phy_clks; + + /* High speed has 125 us per (micro) frame; others are 1 ms per */ + us_per_frame = (hprt & HPRT0_SPD_MASK) ? 1000 : 125; + + /* Extract fields */ + frame_number = (hfnum & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT; + remaining = (hfnum & HFNUM_FRREM_MASK) >> HFNUM_FRREM_SHIFT; + interval = (hfir & HFIR_FRINT_MASK) >> HFIR_FRINT_SHIFT; + + /* + * Number of phy clocks since the last tick of the frame number after + * "us" has passed. + */ + phy_clks = (interval - remaining) + + DIV_ROUND_UP(interval * us, us_per_frame); + + return dwc2_frame_num_inc(frame_number, phy_clks / interval); +} + +int dwc2_hcd_is_b_host(struct dwc2_hsotg *hsotg) +{ + return hsotg->op_state == OTG_STATE_B_HOST; +} + +static struct dwc2_hcd_urb *dwc2_hcd_urb_alloc(struct dwc2_hsotg *hsotg, + int iso_desc_count, + gfp_t mem_flags) +{ + struct dwc2_hcd_urb *urb; + + urb = kzalloc_flex(*urb, iso_descs, iso_desc_count, mem_flags); + if (urb) + urb->packet_count = iso_desc_count; + return urb; +} + +static void dwc2_hcd_urb_set_pipeinfo(struct dwc2_hsotg *hsotg, + struct dwc2_hcd_urb *urb, u8 dev_addr, + u8 ep_num, u8 ep_type, u8 ep_dir, + u16 maxp, u16 maxp_mult) +{ + if (dbg_perio() || + ep_type == USB_ENDPOINT_XFER_BULK || + ep_type == USB_ENDPOINT_XFER_CONTROL) + dev_vdbg(hsotg->dev, + "addr=%d, ep_num=%d, ep_dir=%1x, ep_type=%1x, maxp=%d (%d mult)\n", + dev_addr, ep_num, ep_dir, ep_type, maxp, maxp_mult); + urb->pipe_info.dev_addr = dev_addr; + urb->pipe_info.ep_num = ep_num; + urb->pipe_info.pipe_type = ep_type; + urb->pipe_info.pipe_dir = ep_dir; + urb->pipe_info.maxp = maxp; + urb->pipe_info.maxp_mult = maxp_mult; +} + +/* + * NOTE: This function will be removed once the peripheral controller code + * is integrated and the driver is stable + */ +void dwc2_hcd_dump_state(struct dwc2_hsotg *hsotg) +{ +#ifdef DEBUG + struct dwc2_host_chan *chan; + struct dwc2_hcd_urb *urb; + struct dwc2_qtd *qtd; + int num_channels; + u32 np_tx_status; + u32 p_tx_status; + int i; + + num_channels = hsotg->params.host_channels; + dev_dbg(hsotg->dev, "\n"); + dev_dbg(hsotg->dev, + "************************************************************\n"); + dev_dbg(hsotg->dev, "HCD State:\n"); + dev_dbg(hsotg->dev, " Num channels: %d\n", num_channels); + + for (i = 0; i < num_channels; i++) { + chan = hsotg->hc_ptr_array[i]; + dev_dbg(hsotg->dev, " Channel %d:\n", i); + dev_dbg(hsotg->dev, + " dev_addr: %d, ep_num: %d, ep_is_in: %d\n", + chan->dev_addr, chan->ep_num, chan->ep_is_in); + dev_dbg(hsotg->dev, " speed: %d\n", chan->speed); + dev_dbg(hsotg->dev, " ep_type: %d\n", chan->ep_type); + dev_dbg(hsotg->dev, " max_packet: %d\n", chan->max_packet); + dev_dbg(hsotg->dev, " data_pid_start: %d\n", + chan->data_pid_start); + dev_dbg(hsotg->dev, " multi_count: %d\n", chan->multi_count); + dev_dbg(hsotg->dev, " xfer_started: %d\n", + chan->xfer_started); + dev_dbg(hsotg->dev, " xfer_buf: %p\n", chan->xfer_buf); + dev_dbg(hsotg->dev, " xfer_dma: %08lx\n", + (unsigned long)chan->xfer_dma); + dev_dbg(hsotg->dev, " xfer_len: %d\n", chan->xfer_len); + dev_dbg(hsotg->dev, " xfer_count: %d\n", chan->xfer_count); + dev_dbg(hsotg->dev, " halt_on_queue: %d\n", + chan->halt_on_queue); + dev_dbg(hsotg->dev, " halt_pending: %d\n", + chan->halt_pending); + dev_dbg(hsotg->dev, " halt_status: %d\n", chan->halt_status); + dev_dbg(hsotg->dev, " do_split: %d\n", chan->do_split); + dev_dbg(hsotg->dev, " complete_split: %d\n", + chan->complete_split); + dev_dbg(hsotg->dev, " hub_addr: %d\n", chan->hub_addr); + dev_dbg(hsotg->dev, " hub_port: %d\n", chan->hub_port); + dev_dbg(hsotg->dev, " xact_pos: %d\n", chan->xact_pos); + dev_dbg(hsotg->dev, " requests: %d\n", chan->requests); + dev_dbg(hsotg->dev, " qh: %p\n", chan->qh); + + if (chan->xfer_started) { + u32 hfnum, hcchar, hctsiz, hcint, hcintmsk; + + hfnum = dwc2_readl(hsotg, HFNUM); + hcchar = dwc2_readl(hsotg, HCCHAR(i)); + hctsiz = dwc2_readl(hsotg, HCTSIZ(i)); + hcint = dwc2_readl(hsotg, HCINT(i)); + hcintmsk = dwc2_readl(hsotg, HCINTMSK(i)); + dev_dbg(hsotg->dev, " hfnum: 0x%08x\n", hfnum); + dev_dbg(hsotg->dev, " hcchar: 0x%08x\n", hcchar); + dev_dbg(hsotg->dev, " hctsiz: 0x%08x\n", hctsiz); + dev_dbg(hsotg->dev, " hcint: 0x%08x\n", hcint); + dev_dbg(hsotg->dev, " hcintmsk: 0x%08x\n", hcintmsk); + } + + if (!(chan->xfer_started && chan->qh)) + continue; + + list_for_each_entry(qtd, &chan->qh->qtd_list, qtd_list_entry) { + if (!qtd->in_process) + break; + urb = qtd->urb; + dev_dbg(hsotg->dev, " URB Info:\n"); + dev_dbg(hsotg->dev, " qtd: %p, urb: %p\n", + qtd, urb); + if (urb) { + dev_dbg(hsotg->dev, + " Dev: %d, EP: %d %s\n", + dwc2_hcd_get_dev_addr(&urb->pipe_info), + dwc2_hcd_get_ep_num(&urb->pipe_info), + dwc2_hcd_is_pipe_in(&urb->pipe_info) ? + "IN" : "OUT"); + dev_dbg(hsotg->dev, + " Max packet size: %d (%d mult)\n", + dwc2_hcd_get_maxp(&urb->pipe_info), + dwc2_hcd_get_maxp_mult(&urb->pipe_info)); + dev_dbg(hsotg->dev, + " transfer_buffer: %p\n", + urb->buf); + dev_dbg(hsotg->dev, + " transfer_dma: %08lx\n", + (unsigned long)urb->dma); + dev_dbg(hsotg->dev, + " transfer_buffer_length: %d\n", + urb->length); + dev_dbg(hsotg->dev, " actual_length: %d\n", + urb->actual_length); + } + } + } + + dev_dbg(hsotg->dev, " non_periodic_channels: %d\n", + hsotg->non_periodic_channels); + dev_dbg(hsotg->dev, " periodic_channels: %d\n", + hsotg->periodic_channels); + dev_dbg(hsotg->dev, " periodic_usecs: %d\n", hsotg->periodic_usecs); + np_tx_status = dwc2_readl(hsotg, GNPTXSTS); + dev_dbg(hsotg->dev, " NP Tx Req Queue Space Avail: %d\n", + (np_tx_status & TXSTS_QSPCAVAIL_MASK) >> TXSTS_QSPCAVAIL_SHIFT); + dev_dbg(hsotg->dev, " NP Tx FIFO Space Avail: %d\n", + (np_tx_status & TXSTS_FSPCAVAIL_MASK) >> TXSTS_FSPCAVAIL_SHIFT); + p_tx_status = dwc2_readl(hsotg, HPTXSTS); + dev_dbg(hsotg->dev, " P Tx Req Queue Space Avail: %d\n", + (p_tx_status & TXSTS_QSPCAVAIL_MASK) >> TXSTS_QSPCAVAIL_SHIFT); + dev_dbg(hsotg->dev, " P Tx FIFO Space Avail: %d\n", + (p_tx_status & TXSTS_FSPCAVAIL_MASK) >> TXSTS_FSPCAVAIL_SHIFT); + dwc2_dump_global_registers(hsotg); + dwc2_dump_host_registers(hsotg); + dev_dbg(hsotg->dev, + "************************************************************\n"); + dev_dbg(hsotg->dev, "\n"); +#endif +} + +struct wrapper_priv_data { + struct dwc2_hsotg *hsotg; +}; + +/* Gets the dwc2_hsotg from a usb_hcd */ +static struct dwc2_hsotg *dwc2_hcd_to_hsotg(struct usb_hcd *hcd) +{ + struct wrapper_priv_data *p; + + p = (struct wrapper_priv_data *)&hcd->hcd_priv; + return p->hsotg; +} + +/** + * dwc2_host_get_tt_info() - Get the dwc2_tt associated with context + * + * This will get the dwc2_tt structure (and ttport) associated with the given + * context (which is really just a struct urb pointer). + * + * The first time this is called for a given TT we allocate memory for our + * structure. When everyone is done and has called dwc2_host_put_tt_info() + * then the refcount for the structure will go to 0 and we'll free it. + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @context: The priv pointer from a struct dwc2_hcd_urb. + * @mem_flags: Flags for allocating memory. + * @ttport: We'll return this device's port number here. That's used to + * reference into the bitmap if we're on a multi_tt hub. + * + * Return: a pointer to a struct dwc2_tt. Don't forget to call + * dwc2_host_put_tt_info()! Returns NULL upon memory alloc failure. + */ + +struct dwc2_tt *dwc2_host_get_tt_info(struct dwc2_hsotg *hsotg, void *context, + gfp_t mem_flags, int *ttport) +{ + struct urb *urb = context; + struct dwc2_tt *dwc_tt = NULL; + + if (urb->dev->tt) { + *ttport = urb->dev->ttport; + + dwc_tt = urb->dev->tt->hcpriv; + if (!dwc_tt) { + size_t bitmap_size; + + /* + * For single_tt we need one schedule. For multi_tt + * we need one per port. + */ + bitmap_size = DWC2_ELEMENTS_PER_LS_BITMAP * + sizeof(dwc_tt->periodic_bitmaps[0]); + if (urb->dev->tt->multi) + bitmap_size *= urb->dev->tt->hub->maxchild; + + dwc_tt = kzalloc(sizeof(*dwc_tt) + bitmap_size, + mem_flags); + if (!dwc_tt) + return NULL; + + dwc_tt->usb_tt = urb->dev->tt; + dwc_tt->usb_tt->hcpriv = dwc_tt; + } + + dwc_tt->refcount++; + } + + return dwc_tt; +} + +/** + * dwc2_host_put_tt_info() - Put the dwc2_tt from dwc2_host_get_tt_info() + * + * Frees resources allocated by dwc2_host_get_tt_info() if all current holders + * of the structure are done. + * + * It's OK to call this with NULL. + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @dwc_tt: The pointer returned by dwc2_host_get_tt_info. + */ +void dwc2_host_put_tt_info(struct dwc2_hsotg *hsotg, struct dwc2_tt *dwc_tt) +{ + /* Model kfree and make put of NULL a no-op */ + if (!dwc_tt) + return; + + WARN_ON(dwc_tt->refcount < 1); + + dwc_tt->refcount--; + if (!dwc_tt->refcount) { + dwc_tt->usb_tt->hcpriv = NULL; + kfree(dwc_tt); + } +} + +int dwc2_host_get_speed(struct dwc2_hsotg *hsotg, void *context) +{ + struct urb *urb = context; + + return urb->dev->speed; +} + +static void dwc2_allocate_bus_bandwidth(struct usb_hcd *hcd, u16 bw, + struct urb *urb) +{ + struct usb_bus *bus = hcd_to_bus(hcd); + + if (urb->interval) + bus->bandwidth_allocated += bw / urb->interval; + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) + bus->bandwidth_isoc_reqs++; + else + bus->bandwidth_int_reqs++; +} + +static void dwc2_free_bus_bandwidth(struct usb_hcd *hcd, u16 bw, + struct urb *urb) +{ + struct usb_bus *bus = hcd_to_bus(hcd); + + if (urb->interval) + bus->bandwidth_allocated -= bw / urb->interval; + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) + bus->bandwidth_isoc_reqs--; + else + bus->bandwidth_int_reqs--; +} + +/* + * Sets the final status of an URB and returns it to the upper layer. Any + * required cleanup of the URB is performed. + * + * Must be called with interrupt disabled and spinlock held + */ +void dwc2_host_complete(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, + int status) +{ + struct urb *urb; + int i; + + if (!qtd) { + dev_dbg(hsotg->dev, "## %s: qtd is NULL ##\n", __func__); + return; + } + + if (!qtd->urb) { + dev_dbg(hsotg->dev, "## %s: qtd->urb is NULL ##\n", __func__); + return; + } + + urb = qtd->urb->priv; + if (!urb) { + dev_dbg(hsotg->dev, "## %s: urb->priv is NULL ##\n", __func__); + return; + } + + urb->actual_length = dwc2_hcd_urb_get_actual_length(qtd->urb); + + if (dbg_urb(urb)) + dev_vdbg(hsotg->dev, + "%s: urb %p device %d ep %d-%s status %d actual %d\n", + __func__, urb, usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "IN" : "OUT", status, + urb->actual_length); + + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + if (!hsotg->params.dma_desc_enable) + urb->start_frame = qtd->qh->start_active_frame; + urb->error_count = dwc2_hcd_urb_get_error_count(qtd->urb); + for (i = 0; i < urb->number_of_packets; ++i) { + urb->iso_frame_desc[i].actual_length = + dwc2_hcd_urb_get_iso_desc_actual_length( + qtd->urb, i); + urb->iso_frame_desc[i].status = + dwc2_hcd_urb_get_iso_desc_status(qtd->urb, i); + } + } + + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS && dbg_perio()) { + for (i = 0; i < urb->number_of_packets; i++) + dev_vdbg(hsotg->dev, " ISO Desc %d status %d\n", + i, urb->iso_frame_desc[i].status); + } + + urb->status = status; + 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 = urb->ep; + + if (ep) + dwc2_free_bus_bandwidth(dwc2_hsotg_to_hcd(hsotg), + dwc2_hcd_get_ep_bandwidth(hsotg, ep), + urb); + } + + usb_hcd_unlink_urb_from_ep(dwc2_hsotg_to_hcd(hsotg), urb); + urb->hcpriv = NULL; + kfree(qtd->urb); + qtd->urb = NULL; + + usb_hcd_giveback_urb(dwc2_hsotg_to_hcd(hsotg), urb, status); +} + +/* + * Work queue function for starting the HCD when A-Cable is connected + */ +static void dwc2_hcd_start_func(struct work_struct *work) +{ + struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg, + start_work.work); + + dev_dbg(hsotg->dev, "%s() %p\n", __func__, hsotg); + dwc2_host_start(hsotg); +} + +/* + * Reset work queue function + */ +static void dwc2_hcd_reset_func(struct work_struct *work) +{ + struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg, + reset_work.work); + unsigned long flags; + u32 hprt0; + + dev_dbg(hsotg->dev, "USB RESET function called\n"); + + spin_lock_irqsave(&hsotg->lock, flags); + + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 &= ~HPRT0_RST; + dwc2_writel(hsotg, hprt0, HPRT0); + hsotg->flags.b.port_reset_change = 1; + + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +static void dwc2_hcd_phy_reset_func(struct work_struct *work) +{ + struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg, + phy_reset_work); + int ret; + + ret = phy_reset(hsotg->phy); + if (ret) + dev_warn(hsotg->dev, "PHY reset failed\n"); +} + +/* + * ========================================================================= + * 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. + */ +static int _dwc2_hcd_start(struct usb_hcd *hcd) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + struct usb_bus *bus = hcd_to_bus(hcd); + unsigned long flags; + u32 hprt0; + int ret; + + dev_dbg(hsotg->dev, "DWC OTG HCD START\n"); + + spin_lock_irqsave(&hsotg->lock, flags); + hsotg->lx_state = DWC2_L0; + hcd->state = HC_STATE_RUNNING; + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + if (dwc2_is_device_mode(hsotg)) { + spin_unlock_irqrestore(&hsotg->lock, flags); + return 0; /* why 0 ?? */ + } + + dwc2_hcd_reinit(hsotg); + + hprt0 = dwc2_read_hprt0(hsotg); + /* Has vbus power been turned on in dwc2_core_host_init ? */ + if (hprt0 & HPRT0_PWR) { + /* Enable external vbus supply before resuming root hub */ + spin_unlock_irqrestore(&hsotg->lock, flags); + ret = dwc2_vbus_supply_init(hsotg); + if (ret) + return ret; + spin_lock_irqsave(&hsotg->lock, flags); + } + + /* Initialize and connect root hub if one is not already attached */ + if (bus->root_hub) { + dev_dbg(hsotg->dev, "DWC OTG HCD Has Root Hub\n"); + /* Inform the HUB driver to resume */ + usb_hcd_resume_root_hub(hcd); + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + + return 0; +} + +/* + * Halts the DWC_otg host mode operations in a clean manner. USB transfers are + * stopped. + */ +static void _dwc2_hcd_stop(struct usb_hcd *hcd) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + unsigned long flags; + u32 hprt0; + + /* Turn off all host-specific interrupts */ + dwc2_disable_host_interrupts(hsotg); + + /* Wait for interrupt processing to finish */ + synchronize_irq(hcd->irq); + + spin_lock_irqsave(&hsotg->lock, flags); + hprt0 = dwc2_read_hprt0(hsotg); + /* Ensure hcd is disconnected */ + dwc2_hcd_disconnect(hsotg, true); + dwc2_hcd_stop(hsotg); + hsotg->lx_state = DWC2_L3; + hcd->state = HC_STATE_HALT; + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + spin_unlock_irqrestore(&hsotg->lock, flags); + + /* keep balanced supply init/exit by checking HPRT0_PWR */ + if (hprt0 & HPRT0_PWR) + dwc2_vbus_supply_exit(hsotg); + + usleep_range(1000, 3000); +} + +static int _dwc2_hcd_suspend(struct usb_hcd *hcd) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&hsotg->lock, flags); + + if (dwc2_is_device_mode(hsotg)) + goto unlock; + + if (hsotg->lx_state != DWC2_L0) + goto unlock; + + if (!HCD_HW_ACCESSIBLE(hcd)) + goto unlock; + + if (hsotg->op_state == OTG_STATE_B_PERIPHERAL) + goto unlock; + + if (hsotg->bus_suspended) + goto skip_power_saving; + + if (!(dwc2_read_hprt0(hsotg) & HPRT0_CONNSTS)) + goto skip_power_saving; + + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: + /* Enter partial_power_down */ + ret = dwc2_enter_partial_power_down(hsotg); + if (ret) + dev_err(hsotg->dev, + "enter partial_power_down failed\n"); + /* After entering suspend, hardware is not accessible */ + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + /* Enter hibernation */ + spin_unlock_irqrestore(&hsotg->lock, flags); + ret = dwc2_enter_hibernation(hsotg, 1); + if (ret) + dev_err(hsotg->dev, "enter hibernation failed\n"); + spin_lock_irqsave(&hsotg->lock, flags); + + /* After entering suspend, hardware is not accessible */ + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If not hibernation nor partial power down are supported, + * clock gating is used to save power. + */ + if (!hsotg->params.no_clock_gating) { + dwc2_host_enter_clock_gating(hsotg); + + /* After entering suspend, hardware is not accessible */ + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + } + break; + default: + goto skip_power_saving; + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + dwc2_vbus_supply_exit(hsotg); + spin_lock_irqsave(&hsotg->lock, flags); + + /* Ask phy to be suspended */ + if (!IS_ERR_OR_NULL(hsotg->uphy)) { + spin_unlock_irqrestore(&hsotg->lock, flags); + usb_phy_set_suspend(hsotg->uphy, true); + spin_lock_irqsave(&hsotg->lock, flags); + } + +skip_power_saving: + hsotg->lx_state = DWC2_L2; +unlock: + spin_unlock_irqrestore(&hsotg->lock, flags); + + return ret; +} + +static int _dwc2_hcd_resume(struct usb_hcd *hcd) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + unsigned long flags; + u32 hprt0; + int ret = 0; + + spin_lock_irqsave(&hsotg->lock, flags); + + if (dwc2_is_device_mode(hsotg)) + goto unlock; + + if (hsotg->lx_state != DWC2_L2) + goto unlock; + + hprt0 = dwc2_read_hprt0(hsotg); + + /* + * Added port connection status checking which prevents exiting from + * Partial Power Down mode from _dwc2_hcd_resume() if not in Partial + * Power Down mode. + */ + if (hprt0 & HPRT0_CONNSTS) { + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + hsotg->lx_state = DWC2_L0; + goto unlock; + } + + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: + ret = dwc2_exit_partial_power_down(hsotg, 0, true); + if (ret) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + /* + * Set HW accessible bit before powering on the controller + * since an interrupt may rise. + */ + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + ret = dwc2_exit_hibernation(hsotg, 0, 0, 1); + if (ret) + dev_err(hsotg->dev, "exit hibernation failed.\n"); + + /* + * Set HW accessible bit before powering on the controller + * since an interrupt may rise. + */ + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If not hibernation nor partial power down are supported, + * port resume is done using the clock gating programming flow. + */ + spin_unlock_irqrestore(&hsotg->lock, flags); + dwc2_host_exit_clock_gating(hsotg, 0); + + /* + * Initialize the Core for Host mode, as after system resume + * the global interrupts are disabled. + */ + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + dwc2_hcd_reinit(hsotg); + spin_lock_irqsave(&hsotg->lock, flags); + + /* + * Set HW accessible bit before powering on the controller + * since an interrupt may rise. + */ + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + break; + default: + hsotg->lx_state = DWC2_L0; + goto unlock; + } + + /* Change Root port status, as port status change occurred after resume.*/ + hsotg->flags.b.port_suspend_change = 1; + + /* + * Enable power if not already done. + * This must not be spinlocked since duration + * of this call is unknown. + */ + if (!IS_ERR_OR_NULL(hsotg->uphy)) { + spin_unlock_irqrestore(&hsotg->lock, flags); + usb_phy_set_suspend(hsotg->uphy, false); + spin_lock_irqsave(&hsotg->lock, flags); + } + + /* Enable external vbus supply after resuming the port. */ + spin_unlock_irqrestore(&hsotg->lock, flags); + dwc2_vbus_supply_init(hsotg); + + /* Wait for controller to correctly update D+/D- level */ + usleep_range(3000, 5000); + spin_lock_irqsave(&hsotg->lock, flags); + + /* + * Clear Port Enable and Port Status changes. + * Enable Port Power. + */ + dwc2_writel(hsotg, HPRT0_PWR | HPRT0_CONNDET | + HPRT0_ENACHG, HPRT0); + + /* Wait for controller to detect Port Connect */ + spin_unlock_irqrestore(&hsotg->lock, flags); + usleep_range(5000, 7000); + spin_lock_irqsave(&hsotg->lock, flags); +unlock: + spin_unlock_irqrestore(&hsotg->lock, flags); + + return ret; +} + +/* Returns the current frame number */ +static int _dwc2_hcd_get_frame_number(struct usb_hcd *hcd) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + + return dwc2_hcd_get_frame_number(hsotg); +} + +static void dwc2_dump_urb_info(struct usb_hcd *hcd, struct urb *urb, + char *fn_name) +{ +#ifdef VERBOSE_DEBUG + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + char *pipetype = NULL; + char *speed = NULL; + + dev_vdbg(hsotg->dev, "%s, urb %p\n", fn_name, urb); + dev_vdbg(hsotg->dev, " Device address: %d\n", + usb_pipedevice(urb->pipe)); + dev_vdbg(hsotg->dev, " Endpoint: %d, %s\n", + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "IN" : "OUT"); + + 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; + } + + dev_vdbg(hsotg->dev, " Endpoint type: %s %s (%s)\n", pipetype, + usb_urb_dir_in(urb) ? "IN" : "OUT", usb_pipein(urb->pipe) ? + "IN" : "OUT"); + + 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; + } + + dev_vdbg(hsotg->dev, " Speed: %s\n", speed); + dev_vdbg(hsotg->dev, " Max packet size: %d (%d mult)\n", + usb_endpoint_maxp(&urb->ep->desc), + usb_endpoint_maxp_mult(&urb->ep->desc)); + + dev_vdbg(hsotg->dev, " Data buffer length: %d\n", + urb->transfer_buffer_length); + dev_vdbg(hsotg->dev, " Transfer buffer: %p, Transfer DMA: %08lx\n", + urb->transfer_buffer, (unsigned long)urb->transfer_dma); + dev_vdbg(hsotg->dev, " Setup buffer: %p, Setup DMA: %08lx\n", + urb->setup_packet, (unsigned long)urb->setup_dma); + dev_vdbg(hsotg->dev, " Interval: %d\n", urb->interval); + + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + int i; + + for (i = 0; i < urb->number_of_packets; i++) { + dev_vdbg(hsotg->dev, " ISO Desc %d:\n", i); + dev_vdbg(hsotg->dev, " 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 _dwc2_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + struct usb_host_endpoint *ep = urb->ep; + struct dwc2_hcd_urb *dwc2_urb; + int i; + int retval; + int alloc_bandwidth = 0; + u8 ep_type = 0; + u32 tflags = 0; + void *buf; + unsigned long flags; + struct dwc2_qh *qh; + bool qh_allocated = false; + struct dwc2_qtd *qtd; + struct dwc2_gregs_backup *gr; + + gr = &hsotg->gr_backup; + + if (dbg_urb(urb)) { + dev_vdbg(hsotg->dev, "DWC OTG HCD URB Enqueue\n"); + dwc2_dump_urb_info(hcd, urb, "urb_enqueue"); + } + + if (hsotg->hibernated) { + if (gr->gotgctl & GOTGCTL_CURMODE_HOST) + retval = dwc2_exit_hibernation(hsotg, 0, 0, 1); + else + retval = dwc2_exit_hibernation(hsotg, 0, 0, 0); + + if (retval) + dev_err(hsotg->dev, + "exit hibernation failed.\n"); + } + + if (hsotg->in_ppd) { + retval = dwc2_exit_partial_power_down(hsotg, 0, true); + if (retval) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + } + + if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE && + hsotg->bus_suspended && !hsotg->params.no_clock_gating) { + if (dwc2_is_device_mode(hsotg)) + dwc2_gadget_exit_clock_gating(hsotg, 0); + else + dwc2_host_exit_clock_gating(hsotg, 0); + } + + if (!ep) + return -EINVAL; + + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS || + usb_pipetype(urb->pipe) == PIPE_INTERRUPT) { + spin_lock_irqsave(&hsotg->lock, flags); + if (!dwc2_hcd_is_bandwidth_allocated(hsotg, ep)) + alloc_bandwidth = 1; + spin_unlock_irqrestore(&hsotg->lock, flags); + } + + 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; + } + + dwc2_urb = dwc2_hcd_urb_alloc(hsotg, urb->number_of_packets, + mem_flags); + if (!dwc2_urb) + return -ENOMEM; + + dwc2_hcd_urb_set_pipeinfo(hsotg, dwc2_urb, usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), ep_type, + usb_pipein(urb->pipe), + usb_endpoint_maxp(&ep->desc), + usb_endpoint_maxp_mult(&ep->desc)); + + buf = urb->transfer_buffer; + + if (hcd_uses_dma(hcd)) { + if (!buf && (urb->transfer_dma & 3)) { + dev_err(hsotg->dev, + "%s: unaligned transfer with no transfer_buffer", + __func__); + retval = -EINVAL; + goto fail0; + } + } + + if (!(urb->transfer_flags & URB_NO_INTERRUPT)) + tflags |= URB_GIVEBACK_ASAP; + if (urb->transfer_flags & URB_ZERO_PACKET) + tflags |= URB_SEND_ZERO_PACKET; + + dwc2_urb->priv = urb; + dwc2_urb->buf = buf; + dwc2_urb->dma = urb->transfer_dma; + dwc2_urb->length = urb->transfer_buffer_length; + dwc2_urb->setup_packet = urb->setup_packet; + dwc2_urb->setup_dma = urb->setup_dma; + dwc2_urb->flags = tflags; + dwc2_urb->interval = urb->interval; + dwc2_urb->status = -EINPROGRESS; + + for (i = 0; i < urb->number_of_packets; ++i) + dwc2_hcd_urb_set_iso_desc_params(dwc2_urb, i, + urb->iso_frame_desc[i].offset, + urb->iso_frame_desc[i].length); + + urb->hcpriv = dwc2_urb; + qh = (struct dwc2_qh *)ep->hcpriv; + /* Create QH for the endpoint if it doesn't exist */ + if (!qh) { + qh = dwc2_hcd_qh_create(hsotg, dwc2_urb, mem_flags); + if (!qh) { + retval = -ENOMEM; + goto fail0; + } + ep->hcpriv = qh; + qh_allocated = true; + } + + qtd = kzalloc_obj(*qtd, mem_flags); + if (!qtd) { + retval = -ENOMEM; + goto fail1; + } + + spin_lock_irqsave(&hsotg->lock, flags); + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (retval) + goto fail2; + + retval = dwc2_hcd_urb_enqueue(hsotg, dwc2_urb, qh, qtd); + if (retval) + goto fail3; + + if (alloc_bandwidth) { + dwc2_allocate_bus_bandwidth(hcd, + dwc2_hcd_get_ep_bandwidth(hsotg, ep), + urb); + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + + return 0; + +fail3: + dwc2_urb->priv = NULL; + usb_hcd_unlink_urb_from_ep(hcd, urb); + if (qh_allocated && qh->channel && qh->channel->qh == qh) + qh->channel->qh = NULL; +fail2: + urb->hcpriv = NULL; + spin_unlock_irqrestore(&hsotg->lock, flags); + kfree(qtd); +fail1: + if (qh_allocated) { + struct dwc2_qtd *qtd2, *qtd2_tmp; + + ep->hcpriv = NULL; + dwc2_hcd_qh_unlink(hsotg, qh); + /* Free each QTD in the QH's QTD list */ + list_for_each_entry_safe(qtd2, qtd2_tmp, &qh->qtd_list, + qtd_list_entry) + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd2, qh); + dwc2_hcd_qh_free(hsotg, qh); + } +fail0: + kfree(dwc2_urb); + + return retval; +} + +/* + * Aborts/cancels a USB transfer request. Always returns 0 to indicate success. + */ +static int _dwc2_hcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, + int status) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + int rc; + unsigned long flags; + int urb_status; + + dev_dbg(hsotg->dev, "DWC OTG HCD URB Dequeue\n"); + dwc2_dump_urb_info(hcd, urb, "urb_dequeue"); + + spin_lock_irqsave(&hsotg->lock, flags); + + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc) + goto out; + + if (!urb->hcpriv) { + dev_dbg(hsotg->dev, "## urb->hcpriv is NULL ##\n"); + goto out; + } + + rc = dwc2_hcd_urb_dequeue(hsotg, urb->hcpriv); + + usb_hcd_unlink_urb_from_ep(hcd, urb); + + kfree(urb->hcpriv); + urb->hcpriv = NULL; + + /* Higher layer software sets URB status */ + spin_unlock(&hsotg->lock); + urb_status = urb->status; + usb_hcd_giveback_urb(hcd, urb, status); + spin_lock(&hsotg->lock); + + dev_dbg(hsotg->dev, "Called usb_hcd_giveback_urb()\n"); + dev_dbg(hsotg->dev, " urb->status = %d\n", urb_status); +out: + spin_unlock_irqrestore(&hsotg->lock, flags); + + return rc; +} + +/* + * 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 _dwc2_hcd_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + + dev_dbg(hsotg->dev, + "DWC OTG HCD EP DISABLE: bEndpointAddress=0x%02x, ep->hcpriv=%p\n", + ep->desc.bEndpointAddress, ep->hcpriv); + dwc2_hcd_endpoint_disable(hsotg, ep, 250); +} + +/* + * 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 _dwc2_hcd_endpoint_reset(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + unsigned long flags; + + dev_dbg(hsotg->dev, + "DWC OTG HCD EP RESET: bEndpointAddress=0x%02x\n", + ep->desc.bEndpointAddress); + + spin_lock_irqsave(&hsotg->lock, flags); + dwc2_hcd_endpoint_reset(hsotg, ep); + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +/* + * 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 _dwc2_hcd_irq(struct usb_hcd *hcd) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + + return dwc2_handle_hcd_intr(hsotg); +} + +/* + * 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. + */ +static int _dwc2_hcd_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + + buf[0] = dwc2_hcd_is_status_changed(hsotg, 1) << 1; + return buf[0] != 0; +} + +/* Handles hub class-specific requests */ +static int _dwc2_hcd_hub_control(struct usb_hcd *hcd, u16 typereq, u16 wvalue, + u16 windex, char *buf, u16 wlength) +{ + int retval = dwc2_hcd_hub_control(dwc2_hcd_to_hsotg(hcd), typereq, + wvalue, windex, buf, wlength); + return retval; +} + +/* Handles hub TT buffer clear completions */ +static void _dwc2_hcd_clear_tt_buffer_complete(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + struct dwc2_qh *qh; + unsigned long flags; + + qh = ep->hcpriv; + if (!qh) + return; + + spin_lock_irqsave(&hsotg->lock, flags); + qh->tt_buffer_dirty = 0; + + if (hsotg->flags.b.port_connect_status) + dwc2_hcd_queue_transactions(hsotg, DWC2_TRANSACTION_ALL); + + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +/* + * HPRT0_SPD_HIGH_SPEED: high speed + * HPRT0_SPD_FULL_SPEED: full speed + */ +static void dwc2_change_bus_speed(struct usb_hcd *hcd, int speed) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + + if (hsotg->params.speed == speed) + return; + + hsotg->params.speed = speed; + queue_work(hsotg->wq_otg, &hsotg->wf_otg); +} + +static void dwc2_free_dev(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + + if (!hsotg->params.change_speed_quirk) + return; + + /* + * On removal, set speed to default high-speed. + */ + if (udev->parent && udev->parent->speed > USB_SPEED_UNKNOWN && + udev->parent->speed < USB_SPEED_HIGH) { + dev_info(hsotg->dev, "Set speed to default high-speed\n"); + dwc2_change_bus_speed(hcd, HPRT0_SPD_HIGH_SPEED); + } +} + +static int dwc2_reset_device(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + + if (!hsotg->params.change_speed_quirk) + return 0; + + if (udev->speed == USB_SPEED_HIGH) { + dev_info(hsotg->dev, "Set speed to high-speed\n"); + dwc2_change_bus_speed(hcd, HPRT0_SPD_HIGH_SPEED); + } else if ((udev->speed == USB_SPEED_FULL || + udev->speed == USB_SPEED_LOW)) { + /* + * Change speed setting to full-speed if there's + * a full-speed or low-speed device plugged in. + */ + dev_info(hsotg->dev, "Set speed to full-speed\n"); + dwc2_change_bus_speed(hcd, HPRT0_SPD_FULL_SPEED); + } + + return 0; +} + +static struct hc_driver dwc2_hc_driver = { + .description = "dwc2_hsotg", + .product_desc = "DWC OTG Controller", + .hcd_priv_size = sizeof(struct wrapper_priv_data), + + .irq = _dwc2_hcd_irq, + .flags = HCD_MEMORY | HCD_USB2 | HCD_BH, + + .start = _dwc2_hcd_start, + .stop = _dwc2_hcd_stop, + .urb_enqueue = _dwc2_hcd_urb_enqueue, + .urb_dequeue = _dwc2_hcd_urb_dequeue, + .endpoint_disable = _dwc2_hcd_endpoint_disable, + .endpoint_reset = _dwc2_hcd_endpoint_reset, + .get_frame_number = _dwc2_hcd_get_frame_number, + + .hub_status_data = _dwc2_hcd_hub_status_data, + .hub_control = _dwc2_hcd_hub_control, + .clear_tt_buffer_complete = _dwc2_hcd_clear_tt_buffer_complete, + + .bus_suspend = _dwc2_hcd_suspend, + .bus_resume = _dwc2_hcd_resume, + + .map_urb_for_dma = dwc2_map_urb_for_dma, + .unmap_urb_for_dma = dwc2_unmap_urb_for_dma, +}; + +/* + * Frees secondary storage associated with the dwc2_hsotg structure contained + * in the struct usb_hcd field + */ +static void dwc2_hcd_free(struct dwc2_hsotg *hsotg) +{ + u32 ahbcfg; + u32 dctl; + int i; + + dev_dbg(hsotg->dev, "DWC OTG HCD FREE\n"); + + /* Free memory for QH/QTD lists */ + dwc2_qh_list_free(hsotg, &hsotg->non_periodic_sched_inactive); + dwc2_qh_list_free(hsotg, &hsotg->non_periodic_sched_waiting); + dwc2_qh_list_free(hsotg, &hsotg->non_periodic_sched_active); + dwc2_qh_list_free(hsotg, &hsotg->periodic_sched_inactive); + dwc2_qh_list_free(hsotg, &hsotg->periodic_sched_ready); + dwc2_qh_list_free(hsotg, &hsotg->periodic_sched_assigned); + dwc2_qh_list_free(hsotg, &hsotg->periodic_sched_queued); + + /* Free memory for the host channels */ + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + struct dwc2_host_chan *chan = hsotg->hc_ptr_array[i]; + + if (chan) { + dev_dbg(hsotg->dev, "HCD Free channel #%i, chan=%p\n", + i, chan); + hsotg->hc_ptr_array[i] = NULL; + kfree(chan); + } + } + + if (hsotg->params.host_dma) { + if (hsotg->status_buf) { + dma_free_coherent(hsotg->dev, DWC2_HCD_STATUS_BUF_SIZE, + hsotg->status_buf, + hsotg->status_buf_dma); + hsotg->status_buf = NULL; + } + } else { + kfree(hsotg->status_buf); + hsotg->status_buf = NULL; + } + + ahbcfg = dwc2_readl(hsotg, GAHBCFG); + + /* Disable all interrupts */ + ahbcfg &= ~GAHBCFG_GLBL_INTR_EN; + dwc2_writel(hsotg, ahbcfg, GAHBCFG); + dwc2_writel(hsotg, 0, GINTMSK); + + if (hsotg->hw_params.snpsid >= DWC2_CORE_REV_3_00a) { + dctl = dwc2_readl(hsotg, DCTL); + dctl |= DCTL_SFTDISCON; + dwc2_writel(hsotg, dctl, DCTL); + } + + if (hsotg->wq_otg) { + if (!cancel_work_sync(&hsotg->wf_otg)) + flush_workqueue(hsotg->wq_otg); + destroy_workqueue(hsotg->wq_otg); + } + + cancel_work_sync(&hsotg->phy_reset_work); + + timer_delete(&hsotg->wkp_timer); +} + +static void dwc2_hcd_release(struct dwc2_hsotg *hsotg) +{ + /* Turn off all host-specific interrupts */ + dwc2_disable_host_interrupts(hsotg); + + dwc2_hcd_free(hsotg); +} + +/* + * Initializes the HCD. This function allocates memory for and initializes the + * static parts of the usb_hcd and dwc2_hsotg 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 dwc2_hcd_init(struct dwc2_hsotg *hsotg) +{ + struct platform_device *pdev = to_platform_device(hsotg->dev); + struct resource *res; + struct usb_hcd *hcd; + struct dwc2_host_chan *channel; + u32 hcfg; + int i, num_channels; + int retval; + + if (usb_disabled()) + return -ENODEV; + + dev_dbg(hsotg->dev, "DWC OTG HCD INIT\n"); + + retval = -ENOMEM; + + hcfg = dwc2_readl(hsotg, HCFG); + dev_dbg(hsotg->dev, "hcfg=%08x\n", hcfg); + +#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS + hsotg->frame_num_array = kcalloc(FRAME_NUM_ARRAY_SIZE, + sizeof(*hsotg->frame_num_array), + GFP_KERNEL); + if (!hsotg->frame_num_array) + goto error1; + hsotg->last_frame_num_array = + kcalloc(FRAME_NUM_ARRAY_SIZE, + sizeof(*hsotg->last_frame_num_array), GFP_KERNEL); + if (!hsotg->last_frame_num_array) + goto error1; +#endif + hsotg->last_frame_num = HFNUM_MAX_FRNUM; + + /* Check if the bus driver or platform code has setup a dma_mask */ + if (hsotg->params.host_dma && + !hsotg->dev->dma_mask) { + dev_warn(hsotg->dev, + "dma_mask not set, disabling DMA\n"); + hsotg->params.host_dma = false; + hsotg->params.dma_desc_enable = false; + } + + /* Set device flags indicating whether the HCD supports DMA */ + if (hsotg->params.host_dma) { + if (dma_set_mask(hsotg->dev, DMA_BIT_MASK(32)) < 0) + dev_warn(hsotg->dev, "can't set DMA mask\n"); + if (dma_set_coherent_mask(hsotg->dev, DMA_BIT_MASK(32)) < 0) + dev_warn(hsotg->dev, "can't set coherent DMA mask\n"); + } + + if (hsotg->params.change_speed_quirk) { + dwc2_hc_driver.free_dev = dwc2_free_dev; + dwc2_hc_driver.reset_device = dwc2_reset_device; + } + + if (hsotg->params.host_dma) + dwc2_hc_driver.flags |= HCD_DMA; + + hcd = usb_create_hcd(&dwc2_hc_driver, hsotg->dev, dev_name(hsotg->dev)); + if (!hcd) + goto error1; + + hcd->has_tt = 1; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + retval = -EINVAL; + goto error2; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + ((struct wrapper_priv_data *)&hcd->hcd_priv)->hsotg = hsotg; + hsotg->priv = hcd; + + /* + * Disable the global interrupt until all the interrupt handlers are + * installed + */ + dwc2_disable_global_interrupts(hsotg); + + /* Initialize the DWC_otg core, and select the Phy type */ + retval = dwc2_core_init(hsotg, true); + if (retval) + goto error2; + + /* Create new workqueue and init work */ + retval = -ENOMEM; + hsotg->wq_otg = alloc_ordered_workqueue("dwc2", 0); + if (!hsotg->wq_otg) { + dev_err(hsotg->dev, "Failed to create workqueue\n"); + goto error2; + } + INIT_WORK(&hsotg->wf_otg, dwc2_conn_id_status_change); + + timer_setup(&hsotg->wkp_timer, dwc2_wakeup_detected, 0); + + /* Initialize the non-periodic schedule */ + INIT_LIST_HEAD(&hsotg->non_periodic_sched_inactive); + INIT_LIST_HEAD(&hsotg->non_periodic_sched_waiting); + INIT_LIST_HEAD(&hsotg->non_periodic_sched_active); + + /* Initialize the periodic schedule */ + INIT_LIST_HEAD(&hsotg->periodic_sched_inactive); + INIT_LIST_HEAD(&hsotg->periodic_sched_ready); + INIT_LIST_HEAD(&hsotg->periodic_sched_assigned); + INIT_LIST_HEAD(&hsotg->periodic_sched_queued); + + INIT_LIST_HEAD(&hsotg->split_order); + + /* + * Create a host channel descriptor for each host channel implemented + * in the controller. Initialize the channel descriptor array. + */ + INIT_LIST_HEAD(&hsotg->free_hc_list); + num_channels = hsotg->params.host_channels; + memset(&hsotg->hc_ptr_array[0], 0, sizeof(hsotg->hc_ptr_array)); + + for (i = 0; i < num_channels; i++) { + channel = kzalloc_obj(*channel); + if (!channel) + goto error3; + channel->hc_num = i; + INIT_LIST_HEAD(&channel->split_order_list_entry); + hsotg->hc_ptr_array[i] = channel; + } + + /* Initialize work */ + INIT_DELAYED_WORK(&hsotg->start_work, dwc2_hcd_start_func); + INIT_DELAYED_WORK(&hsotg->reset_work, dwc2_hcd_reset_func); + INIT_WORK(&hsotg->phy_reset_work, dwc2_hcd_phy_reset_func); + + /* + * 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 (hsotg->params.host_dma) + hsotg->status_buf = dma_alloc_coherent(hsotg->dev, + DWC2_HCD_STATUS_BUF_SIZE, + &hsotg->status_buf_dma, GFP_KERNEL); + else + hsotg->status_buf = kzalloc(DWC2_HCD_STATUS_BUF_SIZE, + GFP_KERNEL); + + if (!hsotg->status_buf) + goto error3; + + /* + * Create kmem caches to handle descriptor buffers in descriptor + * DMA mode. + * Alignment must be set to 512 bytes. + */ + if (hsotg->params.dma_desc_enable || + hsotg->params.dma_desc_fs_enable) { + hsotg->desc_gen_cache = kmem_cache_create("dwc2-gen-desc", + sizeof(struct dwc2_dma_desc) * + MAX_DMA_DESC_NUM_GENERIC, 512, SLAB_CACHE_DMA, + NULL); + if (!hsotg->desc_gen_cache) { + dev_err(hsotg->dev, + "unable to create dwc2 generic desc cache\n"); + + /* + * Disable descriptor dma mode since it will not be + * usable. + */ + hsotg->params.dma_desc_enable = false; + hsotg->params.dma_desc_fs_enable = false; + } + + hsotg->desc_hsisoc_cache = kmem_cache_create("dwc2-hsisoc-desc", + sizeof(struct dwc2_dma_desc) * + MAX_DMA_DESC_NUM_HS_ISOC, 512, 0, NULL); + if (!hsotg->desc_hsisoc_cache) { + dev_err(hsotg->dev, + "unable to create dwc2 hs isoc desc cache\n"); + + kmem_cache_destroy(hsotg->desc_gen_cache); + + /* + * Disable descriptor dma mode since it will not be + * usable. + */ + hsotg->params.dma_desc_enable = false; + hsotg->params.dma_desc_fs_enable = false; + } + } + + if (hsotg->params.host_dma) { + /* + * Create kmem caches to handle non-aligned buffer + * in Buffer DMA mode. + */ + hsotg->unaligned_cache = kmem_cache_create("dwc2-unaligned-dma", + DWC2_KMEM_UNALIGNED_BUF_SIZE, 4, + SLAB_CACHE_DMA, NULL); + if (!hsotg->unaligned_cache) + dev_err(hsotg->dev, + "unable to create dwc2 unaligned cache\n"); + } + + hsotg->otg_port = 1; + hsotg->frame_list = NULL; + hsotg->frame_list_dma = 0; + hsotg->periodic_qh_count = 0; + + /* Initiate lx_state to L3 disconnected state */ + hsotg->lx_state = DWC2_L3; + + hcd->self.otg_port = hsotg->otg_port; + + /* Don't support SG list at this point */ + hcd->self.sg_tablesize = 0; + + hcd->tpl_support = of_usb_host_tpl_support(hsotg->dev->of_node); + + if (!IS_ERR_OR_NULL(hsotg->uphy)) + otg_set_host(hsotg->uphy->otg, &hcd->self); + + /* + * 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, hsotg->irq, IRQF_SHARED); + if (retval < 0) + goto error4; + + device_wakeup_enable(hcd->self.controller); + + dwc2_hcd_dump_state(hsotg); + + dwc2_enable_global_interrupts(hsotg); + + return 0; + +error4: + kmem_cache_destroy(hsotg->unaligned_cache); + kmem_cache_destroy(hsotg->desc_hsisoc_cache); + kmem_cache_destroy(hsotg->desc_gen_cache); +error3: + dwc2_hcd_release(hsotg); +error2: + usb_put_hcd(hcd); +error1: + +#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS + kfree(hsotg->last_frame_num_array); + kfree(hsotg->frame_num_array); +#endif + + dev_err(hsotg->dev, "%s() FAILED, returning %d\n", __func__, retval); + return retval; +} + +/* + * Removes the HCD. + * Frees memory and resources associated with the HCD and deregisters the bus. + */ +void dwc2_hcd_remove(struct dwc2_hsotg *hsotg) +{ + struct usb_hcd *hcd; + + dev_dbg(hsotg->dev, "DWC OTG HCD REMOVE\n"); + + hcd = dwc2_hsotg_to_hcd(hsotg); + dev_dbg(hsotg->dev, "hsotg->hcd = %p\n", hcd); + + if (!hcd) { + dev_dbg(hsotg->dev, "%s: dwc2_hsotg_to_hcd(hsotg) NULL!\n", + __func__); + return; + } + + if (!IS_ERR_OR_NULL(hsotg->uphy)) + otg_set_host(hsotg->uphy->otg, NULL); + + usb_remove_hcd(hcd); + hsotg->priv = NULL; + + kmem_cache_destroy(hsotg->unaligned_cache); + kmem_cache_destroy(hsotg->desc_hsisoc_cache); + kmem_cache_destroy(hsotg->desc_gen_cache); + + dwc2_hcd_release(hsotg); + usb_put_hcd(hcd); + +#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS + kfree(hsotg->last_frame_num_array); + kfree(hsotg->frame_num_array); +#endif +} + +/** + * dwc2_backup_host_registers() - Backup controller host registers. + * When suspending usb bus, registers needs to be backuped + * if controller power is disabled once suspended. + * + * @hsotg: Programming view of the DWC_otg controller + */ +int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hregs_backup *hr; + int i; + + dev_dbg(hsotg->dev, "%s\n", __func__); + + /* Backup Host regs */ + hr = &hsotg->hr_backup; + hr->hcfg = dwc2_readl(hsotg, HCFG); + hr->hflbaddr = dwc2_readl(hsotg, HFLBADDR); + hr->haintmsk = dwc2_readl(hsotg, HAINTMSK); + for (i = 0; i < hsotg->params.host_channels; ++i) { + hr->hcchar[i] = dwc2_readl(hsotg, HCCHAR(i)); + hr->hcsplt[i] = dwc2_readl(hsotg, HCSPLT(i)); + hr->hcintmsk[i] = dwc2_readl(hsotg, HCINTMSK(i)); + hr->hctsiz[i] = dwc2_readl(hsotg, HCTSIZ(i)); + hr->hcidma[i] = dwc2_readl(hsotg, HCDMA(i)); + hr->hcidmab[i] = dwc2_readl(hsotg, HCDMAB(i)); + } + + hr->hprt0 = dwc2_read_hprt0(hsotg); + hr->hfir = dwc2_readl(hsotg, HFIR); + hr->hptxfsiz = dwc2_readl(hsotg, HPTXFSIZ); + hr->valid = true; + + return 0; +} + +/** + * dwc2_restore_host_registers() - Restore controller host registers. + * When resuming usb bus, device registers needs to be restored + * if controller power were disabled. + * + * @hsotg: Programming view of the DWC_otg controller + */ +int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hregs_backup *hr; + int i; + + dev_dbg(hsotg->dev, "%s\n", __func__); + + /* Restore host regs */ + hr = &hsotg->hr_backup; + if (!hr->valid) { + dev_err(hsotg->dev, "%s: no host registers to restore\n", + __func__); + return -EINVAL; + } + hr->valid = false; + + dwc2_writel(hsotg, hr->hcfg, HCFG); + dwc2_writel(hsotg, hr->hflbaddr, HFLBADDR); + dwc2_writel(hsotg, hr->haintmsk, HAINTMSK); + + for (i = 0; i < hsotg->params.host_channels; ++i) { + dwc2_writel(hsotg, hr->hcchar[i], HCCHAR(i)); + dwc2_writel(hsotg, hr->hcsplt[i], HCSPLT(i)); + dwc2_writel(hsotg, hr->hcintmsk[i], HCINTMSK(i)); + dwc2_writel(hsotg, hr->hctsiz[i], HCTSIZ(i)); + dwc2_writel(hsotg, hr->hcidma[i], HCDMA(i)); + dwc2_writel(hsotg, hr->hcidmab[i], HCDMAB(i)); + } + + dwc2_writel(hsotg, hr->hprt0, HPRT0); + dwc2_writel(hsotg, hr->hfir, HFIR); + dwc2_writel(hsotg, hr->hptxfsiz, HPTXFSIZ); + hsotg->frame_number = 0; + + return 0; +} + +int dwc2_host_backup_critical_registers(struct dwc2_hsotg *hsotg) +{ + int ret; + + /* Backup all registers */ + ret = dwc2_backup_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup global registers\n", + __func__); + return ret; + } + + ret = dwc2_backup_host_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup host registers\n", + __func__); + return ret; + } + + return 0; +} + +int dwc2_host_restore_critical_registers(struct dwc2_hsotg *hsotg) +{ + int ret; + + ret = dwc2_restore_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore registers\n", + __func__); + return ret; + } + + ret = dwc2_restore_host_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore host registers\n", + __func__); + return ret; + } + + return 0; +} + +/** + * dwc2_host_enter_hibernation() - Put controller in Hibernation. + * + * @hsotg: Programming view of the DWC_otg controller + */ +int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg) +{ + unsigned long flags; + int ret = 0; + u32 hprt0; + u32 pcgcctl; + u32 gusbcfg; + u32 gpwrdn; + + dev_dbg(hsotg->dev, "Preparing host for hibernation\n"); + ret = dwc2_host_backup_critical_registers(hsotg); + if (ret) + return ret; + + /* Enter USB Suspend Mode */ + hprt0 = dwc2_readl(hsotg, HPRT0); + hprt0 |= HPRT0_SUSP; + hprt0 &= ~HPRT0_ENA; + dwc2_writel(hsotg, hprt0, HPRT0); + + /* Wait for the HPRT0.PrtSusp register field to be set */ + if (dwc2_hsotg_wait_bit_set(hsotg, HPRT0, HPRT0_SUSP, 5000)) + dev_warn(hsotg->dev, "Suspend wasn't generated\n"); + + /* + * We need to disable interrupts to prevent servicing of any IRQ + * during going to hibernation + */ + spin_lock_irqsave(&hsotg->lock, flags); + hsotg->lx_state = DWC2_L2; + + gusbcfg = dwc2_readl(hsotg, GUSBCFG); + if (gusbcfg & GUSBCFG_ULPI_UTMI_SEL) { + /* ULPI interface */ + udelay(10); + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_ULPI_LATCH_EN_DURING_HIB_ENTRY; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + /* Suspend the Phy Clock */ + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(10); + + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PMUACTV; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + } else { + /* UTMI+ Interface */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PMUACTV; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(10); + } + + /* Enable interrupts from wake up logic */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PMUINTSEL; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Unmask host mode interrupts in GPWRDN */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_DISCONN_DET_MSK; + gpwrdn |= GPWRDN_LNSTSCHG_MSK; + gpwrdn |= GPWRDN_STS_CHGINT_MSK; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Enable Power Down Clamp */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PWRDNCLMP; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Switch off VDD */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn |= GPWRDN_PWRDNSWTCH; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + + hsotg->hibernated = 1; + hsotg->bus_suspended = 1; + dev_dbg(hsotg->dev, "Host hibernation completed\n"); + spin_unlock_irqrestore(&hsotg->lock, flags); + return ret; +} + +/* + * dwc2_host_exit_hibernation() + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by Device or Host. + * @param reset: indicates whether resume is initiated by Reset. + * + * Return: non-zero if failed to enter to hibernation. + * + * This function is for exiting from Host mode hibernation by + * Host Initiated Resume/Reset and Device Initiated Remote-Wakeup. + */ +int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, + int reset) +{ + u32 gpwrdn; + u32 hprt0; + int ret = 0; + struct dwc2_gregs_backup *gr; + struct dwc2_hregs_backup *hr; + + gr = &hsotg->gr_backup; + hr = &hsotg->hr_backup; + + dev_dbg(hsotg->dev, + "%s: called with rem_wakeup = %d reset = %d\n", + __func__, rem_wakeup, reset); + + dwc2_hib_restore_common(hsotg, rem_wakeup, 1); + hsotg->hibernated = 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) + */ + mdelay(100); + + /* Clear all pending interupts */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + + /* De-assert Restore */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_RESTORE; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + + /* Restore GUSBCFG, HCFG */ + dwc2_writel(hsotg, gr->gusbcfg, GUSBCFG); + dwc2_writel(hsotg, hr->hcfg, HCFG); + + /* Reset ULPI latch */ + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_ULPI_LATCH_EN_DURING_HIB_ENTRY; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + + /* De-assert Wakeup Logic */ + if (!(rem_wakeup && hsotg->hw_params.snpsid >= DWC2_CORE_REV_4_30a)) { + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_PMUACTV; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + } + + hprt0 = hr->hprt0; + hprt0 |= HPRT0_PWR; + hprt0 &= ~HPRT0_ENA; + hprt0 &= ~HPRT0_SUSP; + dwc2_writel(hsotg, hprt0, HPRT0); + + hprt0 = hr->hprt0; + hprt0 |= HPRT0_PWR; + hprt0 &= ~HPRT0_ENA; + hprt0 &= ~HPRT0_SUSP; + + if (reset) { + hprt0 |= HPRT0_RST; + dwc2_writel(hsotg, hprt0, HPRT0); + + /* Wait for Resume time and then program HPRT again */ + mdelay(60); + hprt0 &= ~HPRT0_RST; + dwc2_writel(hsotg, hprt0, HPRT0); + } else { + hprt0 |= HPRT0_RES; + dwc2_writel(hsotg, hprt0, HPRT0); + + /* De-assert Wakeup Logic */ + if ((rem_wakeup && hsotg->hw_params.snpsid >= DWC2_CORE_REV_4_30a)) { + gpwrdn = dwc2_readl(hsotg, GPWRDN); + gpwrdn &= ~GPWRDN_PMUACTV; + dwc2_writel(hsotg, gpwrdn, GPWRDN); + udelay(10); + } + /* Wait for Resume time and then program HPRT again */ + mdelay(100); + hprt0 &= ~HPRT0_RES; + dwc2_writel(hsotg, hprt0, HPRT0); + } + /* Clear all interrupt status */ + hprt0 = dwc2_readl(hsotg, HPRT0); + hprt0 |= HPRT0_CONNDET; + hprt0 |= HPRT0_ENACHG; + hprt0 &= ~HPRT0_ENA; + dwc2_writel(hsotg, hprt0, HPRT0); + + hprt0 = dwc2_readl(hsotg, HPRT0); + + /* Clear all pending interupts */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + + /* Restore global registers */ + ret = dwc2_host_restore_critical_registers(hsotg); + if (ret) + return ret; + + if (rem_wakeup) { + dwc2_hcd_rem_wakeup(hsotg); + /* + * Change "port_connect_status_change" flag to re-enumerate, + * because after exit from hibernation port connection status + * is not detected. + */ + hsotg->flags.b.port_connect_status_change = 1; + } + + hsotg->hibernated = 0; + hsotg->bus_suspended = 0; + hsotg->lx_state = DWC2_L0; + dev_dbg(hsotg->dev, "Host hibernation restore complete\n"); + return ret; +} + +bool dwc2_host_can_poweroff_phy(struct dwc2_hsotg *dwc2) +{ + struct usb_device *root_hub = dwc2_hsotg_to_hcd(dwc2)->self.root_hub; + + /* If the controller isn't allowed to wakeup then we can power off. */ + if (!device_may_wakeup(dwc2->dev)) + return true; + + /* + * We don't want to power off the PHY if something under the + * root hub has wakeup enabled. + */ + if (usb_wakeup_enabled_descendants(root_hub)) + return false; + + /* No reason to keep the PHY powered, so allow poweroff */ + return true; +} + +/** + * dwc2_host_enter_partial_power_down() - Put controller in partial + * power down. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return: non-zero if failed to enter host partial power down. + * + * This function is for entering Host mode partial power down. + */ +int dwc2_host_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ + u32 pcgcctl; + u32 hprt0; + int ret = 0; + + dev_dbg(hsotg->dev, "Entering host partial power down started.\n"); + + /* Put this port in suspend mode. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_SUSP; + dwc2_writel(hsotg, hprt0, HPRT0); + udelay(5); + + /* Wait for the HPRT0.PrtSusp register field to be set */ + if (dwc2_hsotg_wait_bit_set(hsotg, HPRT0, HPRT0_SUSP, 3000)) + dev_warn(hsotg->dev, "Suspend wasn't generated\n"); + + /* Backup all registers */ + ret = dwc2_host_backup_critical_registers(hsotg); + if (ret) + return ret; + + /* + * Clear any pending interrupts since dwc2 will not be able to + * clear them after entering partial_power_down. + */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + + /* Put the controller in low power state */ + pcgcctl = dwc2_readl(hsotg, PCGCTL); + + pcgcctl |= PCGCTL_PWRCLMP; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl |= PCGCTL_RSTPDWNMODULE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + /* Set in_ppd flag to 1 as here core enters suspend. */ + hsotg->in_ppd = 1; + hsotg->lx_state = DWC2_L2; + hsotg->bus_suspended = true; + + dev_dbg(hsotg->dev, "Entering host partial power down completed.\n"); + + return ret; +} + +/* + * dwc2_host_exit_partial_power_down() - Exit controller from host partial + * power down. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by Reset. + * @restore: indicates whether need to restore the registers or not. + * + * Return: non-zero if failed to exit host partial power down. + * + * This function is for exiting from Host mode partial power down. + */ +int dwc2_host_exit_partial_power_down(struct dwc2_hsotg *hsotg, + int rem_wakeup, bool restore) +{ + u32 pcgcctl; + int ret = 0; + u32 hprt0; + + dev_dbg(hsotg->dev, "Exiting host partial power down started.\n"); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_PWRCLMP; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_RSTPDWNMODULE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + udelay(100); + if (restore) { + ret = dwc2_host_restore_critical_registers(hsotg); + if (ret) + return ret; + } + + /* Drive resume signaling and exit suspend mode on the port. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_RES; + hprt0 &= ~HPRT0_SUSP; + dwc2_writel(hsotg, hprt0, HPRT0); + udelay(5); + + if (!rem_wakeup) { + /* Stop driveing resume signaling on the port. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 &= ~HPRT0_RES; + dwc2_writel(hsotg, hprt0, HPRT0); + + hsotg->bus_suspended = false; + } else { + /* Turn on the port power bit. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_PWR; + dwc2_writel(hsotg, hprt0, HPRT0); + + /* Connect hcd. */ + dwc2_hcd_connect(hsotg); + + mod_timer(&hsotg->wkp_timer, + jiffies + msecs_to_jiffies(71)); + } + + /* Set lx_state to and in_ppd to 0 as here core exits from suspend. */ + hsotg->in_ppd = 0; + hsotg->lx_state = DWC2_L0; + + dev_dbg(hsotg->dev, "Exiting host partial power down completed.\n"); + return ret; +} + +/** + * dwc2_host_enter_clock_gating() - Put controller in clock gating. + * + * @hsotg: Programming view of the DWC_otg controller + * + * This function is for entering Host mode clock gating. + */ +void dwc2_host_enter_clock_gating(struct dwc2_hsotg *hsotg) +{ + u32 hprt0; + u32 pcgctl; + + dev_dbg(hsotg->dev, "Entering host clock gating.\n"); + + /* Put this port in suspend mode. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_SUSP; + dwc2_writel(hsotg, hprt0, HPRT0); + + /* Set the Phy Clock bit as suspend is received. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + /* Set the Gate hclk as suspend is received. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl |= PCGCTL_GATEHCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + hsotg->bus_suspended = true; + hsotg->lx_state = DWC2_L2; +} + +/** + * dwc2_host_exit_clock_gating() - Exit controller from clock gating. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by remote wakeup + * + * This function is for exiting Host mode clock gating. + */ +void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup) +{ + u32 hprt0; + u32 pcgctl; + + dev_dbg(hsotg->dev, "Exiting host clock gating.\n"); + + /* Clear the Gate hclk. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_GATEHCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + /* Phy Clock bit. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + /* Drive resume signaling and exit suspend mode on the port. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_RES; + hprt0 &= ~HPRT0_SUSP; + dwc2_writel(hsotg, hprt0, HPRT0); + udelay(5); + + if (!rem_wakeup) { + /* In case of port resume need to wait for 40 ms */ + msleep(USB_RESUME_TIMEOUT); + + /* Stop driveing resume signaling on the port. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 &= ~HPRT0_RES; + dwc2_writel(hsotg, hprt0, HPRT0); + + hsotg->bus_suspended = false; + hsotg->lx_state = DWC2_L0; + } else { + mod_timer(&hsotg->wkp_timer, + jiffies + msecs_to_jiffies(71)); + } +} diff --git a/drivers/usb/dwc2/hcd.h b/drivers/usb/dwc2/hcd.h new file mode 100644 index 000000000..b7254d94f --- /dev/null +++ b/drivers/usb/dwc2/hcd.h @@ -0,0 +1,787 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */ +/* + * hcd.h - DesignWare HS OTG Controller host-mode declarations + * + * Copyright (C) 2004-2013 Synopsys, Inc. + */ + +#ifndef __DWC2_HCD_H__ +#define __DWC2_HCD_H__ + +/* + * 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 dwc2_qh; + +/** + * struct dwc2_host_chan - Software host channel descriptor + * + * @hc_num: Host channel number, used for register address lookup + * @dev_addr: Address of the device + * @ep_num: Endpoint of the device + * @ep_is_in: Endpoint direction + * @speed: Device speed. One of the following values: + * - USB_SPEED_LOW + * - USB_SPEED_FULL + * - USB_SPEED_HIGH + * @ep_type: Endpoint type. One of the following values: + * - USB_ENDPOINT_XFER_CONTROL: 0 + * - USB_ENDPOINT_XFER_ISOC: 1 + * - USB_ENDPOINT_XFER_BULK: 2 + * - USB_ENDPOINT_XFER_INTR: 3 + * @max_packet: Max packet size in bytes + * @data_pid_start: PID for initial transaction. + * 0: DATA0 + * 1: DATA2 + * 2: DATA1 + * 3: MDATA (non-Control EP), + * SETUP (Control EP) + * @multi_count: Number of additional periodic transactions per + * (micro)frame + * @xfer_buf: Pointer to current transfer buffer position + * @xfer_dma: DMA address of xfer_buf + * @align_buf: In Buffer DMA mode this will be used if xfer_buf is not + * DWORD aligned + * @xfer_len: Total number of bytes to transfer + * @xfer_count: Number of bytes transferred so far + * @start_pkt_count: Packet count at start of transfer + * @xfer_started: True if the transfer has been started + * @do_ping: True if a PING request should be issued on this channel + * @error_state: True if the error count for this transaction is non-zero + * @halt_on_queue: True if 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. + * @halt_pending: True if the host channel has been halted, but the core + * is not finished flushing queued requests + * @do_split: Enable split for the channel + * @complete_split: Enable complete split + * @hub_addr: Address of high speed hub for the split + * @hub_port: Port of the low/full speed device for the split + * @xact_pos: Split transaction position. One of the following values: + * - DWC2_HCSPLT_XACTPOS_MID + * - DWC2_HCSPLT_XACTPOS_BEGIN + * - DWC2_HCSPLT_XACTPOS_END + * - DWC2_HCSPLT_XACTPOS_ALL + * @requests: Number of requests issued for this channel since it was + * assigned to the current transfer (not counting PINGs) + * @schinfo: Scheduling micro-frame bitmap + * @ntd: Number of transfer descriptors for the transfer + * @halt_status: Reason for halting the host channel + * @hcint: Contents of the HCINT register when the interrupt came + * @qh: QH for the transfer being processed by this channel + * @hc_list_entry: For linking to list of host channels + * @desc_list_addr: Current QH's descriptor list DMA address + * @desc_list_sz: Current QH's descriptor list size + * @split_order_list_entry: List entry for keeping track of the order of splits + * + * 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. + */ +struct dwc2_host_chan { + u8 hc_num; + + unsigned dev_addr:7; + unsigned ep_num:4; + unsigned ep_is_in:1; + unsigned speed:4; + unsigned ep_type:2; + unsigned max_packet:11; + unsigned data_pid_start:2; +#define DWC2_HC_PID_DATA0 TSIZ_SC_MC_PID_DATA0 +#define DWC2_HC_PID_DATA2 TSIZ_SC_MC_PID_DATA2 +#define DWC2_HC_PID_DATA1 TSIZ_SC_MC_PID_DATA1 +#define DWC2_HC_PID_MDATA TSIZ_SC_MC_PID_MDATA +#define DWC2_HC_PID_SETUP TSIZ_SC_MC_PID_SETUP + + unsigned multi_count:2; + + u8 *xfer_buf; + dma_addr_t xfer_dma; + dma_addr_t align_buf; + u32 xfer_len; + u32 xfer_count; + u16 start_pkt_count; + u8 xfer_started; + u8 do_ping; + u8 error_state; + u8 halt_on_queue; + u8 halt_pending; + u8 do_split; + u8 complete_split; + u8 hub_addr; + u8 hub_port; + u8 xact_pos; +#define DWC2_HCSPLT_XACTPOS_MID HCSPLT_XACTPOS_MID +#define DWC2_HCSPLT_XACTPOS_END HCSPLT_XACTPOS_END +#define DWC2_HCSPLT_XACTPOS_BEGIN HCSPLT_XACTPOS_BEGIN +#define DWC2_HCSPLT_XACTPOS_ALL HCSPLT_XACTPOS_ALL + + u8 requests; + u8 schinfo; + u16 ntd; + enum dwc2_halt_status halt_status; + u32 hcint; + struct dwc2_qh *qh; + struct list_head hc_list_entry; + dma_addr_t desc_list_addr; + u32 desc_list_sz; + struct list_head split_order_list_entry; +}; + +struct dwc2_hcd_pipe_info { + u8 dev_addr; + u8 ep_num; + u8 pipe_type; + u8 pipe_dir; + u16 maxp; + u16 maxp_mult; +}; + +struct dwc2_hcd_iso_packet_desc { + u32 offset; + u32 length; + u32 actual_length; + u32 status; +}; + +struct dwc2_qtd; + +struct dwc2_hcd_urb { + void *priv; + struct dwc2_qtd *qtd; + void *buf; + dma_addr_t dma; + void *setup_packet; + dma_addr_t setup_dma; + u32 length; + u32 actual_length; + u32 status; + u32 error_count; + u32 packet_count; + u32 flags; + u16 interval; + struct dwc2_hcd_pipe_info pipe_info; + struct dwc2_hcd_iso_packet_desc iso_descs[]; +}; + +/* Phases for control transfers */ +enum dwc2_control_phase { + DWC2_CONTROL_SETUP, + DWC2_CONTROL_DATA, + DWC2_CONTROL_STATUS, +}; + +/* Transaction types */ +enum dwc2_transaction_type { + DWC2_TRANSACTION_NONE, + DWC2_TRANSACTION_PERIODIC, + DWC2_TRANSACTION_NON_PERIODIC, + DWC2_TRANSACTION_ALL, +}; + +/* The number of elements per LS bitmap (per port on multi_tt) */ +#define DWC2_ELEMENTS_PER_LS_BITMAP DIV_ROUND_UP(DWC2_LS_SCHEDULE_SLICES, \ + BITS_PER_LONG) + +/** + * struct dwc2_tt - dwc2 data associated with a usb_tt + * + * @refcount: Number of Queue Heads (QHs) holding a reference. + * @usb_tt: Pointer back to the official usb_tt. + * @periodic_bitmaps: Bitmap for which parts of the 1ms frame are accounted + * for already. Each is DWC2_ELEMENTS_PER_LS_BITMAP + * elements (so sizeof(long) times that in bytes). + * + * This structure is stored in the hcpriv of the official usb_tt. + */ +struct dwc2_tt { + int refcount; + struct usb_tt *usb_tt; + unsigned long periodic_bitmaps[]; +}; + +/** + * struct dwc2_hs_transfer_time - Info about a transfer on the high speed bus. + * + * @start_schedule_us: The start time on the main bus schedule. Note that + * the main bus schedule is tightly packed and this + * time should be interpreted as tightly packed (so + * uFrame 0 starts at 0 us, uFrame 1 starts at 100 us + * instead of 125 us). + * @duration_us: How long this transfer goes. + */ + +struct dwc2_hs_transfer_time { + u32 start_schedule_us; + u16 duration_us; +}; + +/** + * struct dwc2_qh - Software queue head structure + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @ep_type: Endpoint type. One of the following values: + * - USB_ENDPOINT_XFER_CONTROL + * - USB_ENDPOINT_XFER_BULK + * - USB_ENDPOINT_XFER_INT + * - USB_ENDPOINT_XFER_ISOC + * @ep_is_in: Endpoint direction + * @maxp: Value from wMaxPacketSize field of Endpoint Descriptor + * @maxp_mult: Multiplier for maxp + * @dev_speed: Device speed. One of the following values: + * - USB_SPEED_LOW + * - USB_SPEED_FULL + * - USB_SPEED_HIGH + * @data_toggle: Determines the PID of the next data packet for + * non-controltransfers. Ignored for control transfers. + * One of the following values: + * - DWC2_HC_PID_DATA0 + * - DWC2_HC_PID_DATA1 + * @ping_state: Ping state + * @do_split: Full/low speed endpoint on high-speed hub requires split + * @td_first: Index of first activated isochronous transfer descriptor + * @td_last: Index of last activated isochronous transfer descriptor + * @host_us: Bandwidth in microseconds per transfer as seen by host + * @device_us: Bandwidth in microseconds per transfer as seen by device + * @host_interval: Interval between transfers as seen by the host. If + * the host is high speed and the device is low speed this + * will be 8 times device interval. + * @device_interval: Interval between transfers as seen by the device. + * interval. + * @next_active_frame: (Micro)frame _before_ we next need to put something on + * the bus. We'll move the qh to active here. If the + * host is in high speed mode this will be a uframe. If + * the host is in low speed mode this will be a full frame. + * @start_active_frame: If we are partway through a split transfer, this will be + * what next_active_frame was when we started. Otherwise + * it should always be the same as next_active_frame. + * @num_hs_transfers: Number of transfers in hs_transfers. + * Normally this is 1 but can be more than one for splits. + * Always >= 1 unless the host is in low/full speed mode. + * @hs_transfers: Transfers that are scheduled as seen by the high speed + * bus. Not used if host is in low or full speed mode (but + * note that it IS USED if the device is low or full speed + * as long as the HOST is in high speed mode). + * @ls_start_schedule_slice: Start time (in slices) on the low speed bus + * schedule that's being used by this device. This + * will be on the periodic_bitmap in a + * "struct dwc2_tt". Not used if this device is high + * speed. Note that this is in "schedule slice" which + * is tightly packed. + * @ntd: Actual number of transfer descriptors in a list + * @dw_align_buf: Used instead of original buffer if its physical address + * is not dword-aligned + * @dw_align_buf_dma: DMA address for dw_align_buf + * @qtd_list: List of QTDs for this QH + * @channel: Host channel currently processing transfers for this QH + * @qh_list_entry: Entry for QH in either the periodic or non-periodic + * schedule + * @desc_list: List of transfer descriptors + * @desc_list_dma: Physical address of desc_list + * @desc_list_sz: Size of descriptors list + * @n_bytes: Xfer Bytes array. Each element corresponds to a transfer + * descriptor and indicates original XferSize value for the + * descriptor + * @unreserve_timer: Timer for releasing periodic reservation. + * @wait_timer: Timer used to wait before re-queuing. + * @dwc_tt: Pointer to our tt info (or NULL if no tt). + * @ttport: Port number within our tt. + * @tt_buffer_dirty True if clear_tt_buffer_complete is pending + * @unreserve_pending: True if we planned to unreserve but haven't yet. + * @schedule_low_speed: True if we have a low/full speed component (either the + * host is in low/full speed mode or do_split). + * @want_wait: We should wait before re-queuing; only matters for non- + * periodic transfers and is ignored for periodic ones. + * @wait_timer_cancel: Set to true to cancel the wait_timer. + * + * @tt_buffer_dirty: True if EP's TT buffer is not clean. + * 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. + */ +struct dwc2_qh { + struct dwc2_hsotg *hsotg; + u8 ep_type; + u8 ep_is_in; + u16 maxp; + u16 maxp_mult; + u8 dev_speed; + u8 data_toggle; + u8 ping_state; + u8 do_split; + u8 td_first; + u8 td_last; + u16 host_us; + u16 device_us; + u16 host_interval; + u16 device_interval; + u16 next_active_frame; + u16 start_active_frame; + s16 num_hs_transfers; + struct dwc2_hs_transfer_time hs_transfers[DWC2_HS_SCHEDULE_UFRAMES]; + u32 ls_start_schedule_slice; + u16 ntd; + u8 *dw_align_buf; + dma_addr_t dw_align_buf_dma; + struct list_head qtd_list; + struct dwc2_host_chan *channel; + struct list_head qh_list_entry; + struct dwc2_dma_desc *desc_list; + dma_addr_t desc_list_dma; + u32 desc_list_sz; + u32 *n_bytes; + struct timer_list unreserve_timer; + struct hrtimer wait_timer; + struct dwc2_tt *dwc_tt; + int ttport; + unsigned tt_buffer_dirty:1; + unsigned unreserve_pending:1; + unsigned schedule_low_speed:1; + unsigned want_wait:1; + unsigned wait_timer_cancel:1; +}; + +/** + * struct dwc2_qtd - Software queue transfer descriptor (QTD) + * + * @control_phase: Current phase for control transfers (Setup, Data, or + * Status) + * @in_process: Indicates if this QTD is currently processed by HW + * @data_toggle: 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: + * - DWC2_HC_PID_DATA0 + * - DWC2_HC_PID_DATA1 + * @complete_split: Keeps track of the current split type for FS/LS + * endpoints on a HS Hub + * @isoc_split_pos: Position of the ISOC split in full/low speed + * @isoc_frame_index: 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. + * @isoc_split_offset: Position of the ISOC split in the buffer for the + * current frame + * @ssplit_out_xfer_count: How many bytes transferred during SSPLIT OUT + * @error_count: Holds the number of bus errors that have occurred for + * a transaction within this transfer + * @n_desc: Number of DMA descriptors for this QTD + * @isoc_frame_index_last: Last activated frame (packet) index, used in + * descriptor DMA mode only + * @num_naks: Number of NAKs received on this QTD. + * @urb: URB for this transfer + * @qh: Queue head for this QTD + * @qtd_list_entry: For linking to the QH's list of QTDs + * @isoc_td_first: Index of first activated isochronous transfer + * descriptor in Descriptor DMA mode + * @isoc_td_last: Index of last activated isochronous transfer + * descriptor in Descriptor DMA mode + * + * 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. + */ +struct dwc2_qtd { + enum dwc2_control_phase control_phase; + u8 in_process; + u8 data_toggle; + u8 complete_split; + u8 isoc_split_pos; + u16 isoc_frame_index; + u16 isoc_split_offset; + u16 isoc_td_last; + u16 isoc_td_first; + u32 ssplit_out_xfer_count; + u8 error_count; + u8 n_desc; + u16 isoc_frame_index_last; + u16 num_naks; + struct dwc2_hcd_urb *urb; + struct dwc2_qh *qh; + struct list_head qtd_list_entry; +}; + +#ifdef DEBUG +struct hc_xfer_info { + struct dwc2_hsotg *hsotg; + struct dwc2_host_chan *chan; +}; +#endif + +u32 dwc2_calc_frame_interval(struct dwc2_hsotg *hsotg); + +/* Gets the struct usb_hcd that contains a struct dwc2_hsotg */ +static inline struct usb_hcd *dwc2_hsotg_to_hcd(struct dwc2_hsotg *hsotg) +{ + return (struct usb_hcd *)hsotg->priv; +} + +/* + * Inline 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. + */ +static inline void disable_hc_int(struct dwc2_hsotg *hsotg, int chnum, u32 intr) +{ + u32 mask = dwc2_readl(hsotg, HCINTMSK(chnum)); + + mask &= ~intr; + dwc2_writel(hsotg, mask, HCINTMSK(chnum)); +} + +void dwc2_hc_cleanup(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan); +void dwc2_hc_halt(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan, + enum dwc2_halt_status halt_status); +void dwc2_hc_start_transfer_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan); + +/* + * 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 written back. + */ +static inline u32 dwc2_read_hprt0(struct dwc2_hsotg *hsotg) +{ + u32 hprt0 = dwc2_readl(hsotg, HPRT0); + + hprt0 &= ~(HPRT0_ENA | HPRT0_CONNDET | HPRT0_ENACHG | HPRT0_OVRCURRCHG); + return hprt0; +} + +static inline u8 dwc2_hcd_get_ep_num(struct dwc2_hcd_pipe_info *pipe) +{ + return pipe->ep_num; +} + +static inline u8 dwc2_hcd_get_pipe_type(struct dwc2_hcd_pipe_info *pipe) +{ + return pipe->pipe_type; +} + +static inline u16 dwc2_hcd_get_maxp(struct dwc2_hcd_pipe_info *pipe) +{ + return pipe->maxp; +} + +static inline u16 dwc2_hcd_get_maxp_mult(struct dwc2_hcd_pipe_info *pipe) +{ + return pipe->maxp_mult; +} + +static inline u8 dwc2_hcd_get_dev_addr(struct dwc2_hcd_pipe_info *pipe) +{ + return pipe->dev_addr; +} + +static inline u8 dwc2_hcd_is_pipe_isoc(struct dwc2_hcd_pipe_info *pipe) +{ + return pipe->pipe_type == USB_ENDPOINT_XFER_ISOC; +} + +static inline u8 dwc2_hcd_is_pipe_int(struct dwc2_hcd_pipe_info *pipe) +{ + return pipe->pipe_type == USB_ENDPOINT_XFER_INT; +} + +static inline u8 dwc2_hcd_is_pipe_bulk(struct dwc2_hcd_pipe_info *pipe) +{ + return pipe->pipe_type == USB_ENDPOINT_XFER_BULK; +} + +static inline u8 dwc2_hcd_is_pipe_control(struct dwc2_hcd_pipe_info *pipe) +{ + return pipe->pipe_type == USB_ENDPOINT_XFER_CONTROL; +} + +static inline u8 dwc2_hcd_is_pipe_in(struct dwc2_hcd_pipe_info *pipe) +{ + return pipe->pipe_dir == USB_DIR_IN; +} + +static inline u8 dwc2_hcd_is_pipe_out(struct dwc2_hcd_pipe_info *pipe) +{ + return !dwc2_hcd_is_pipe_in(pipe); +} + +int dwc2_hcd_init(struct dwc2_hsotg *hsotg); +void dwc2_hcd_remove(struct dwc2_hsotg *hsotg); + +/* Transaction Execution Functions */ +enum dwc2_transaction_type dwc2_hcd_select_transactions( + struct dwc2_hsotg *hsotg); +void dwc2_hcd_queue_transactions(struct dwc2_hsotg *hsotg, + enum dwc2_transaction_type tr_type); + +/* Schedule Queue Functions */ +/* Implemented in hcd_queue.c */ +struct dwc2_qh *dwc2_hcd_qh_create(struct dwc2_hsotg *hsotg, + struct dwc2_hcd_urb *urb, + gfp_t mem_flags); +void dwc2_hcd_qh_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh); +int dwc2_hcd_qh_add(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh); +void dwc2_hcd_qh_unlink(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh); +void dwc2_hcd_qh_deactivate(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + int sched_csplit); + +void dwc2_hcd_qtd_init(struct dwc2_qtd *qtd, struct dwc2_hcd_urb *urb); +int dwc2_hcd_qtd_add(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, + struct dwc2_qh *qh); + +/* Unlinks and frees a QTD */ +static inline void dwc2_hcd_qtd_unlink_and_free(struct dwc2_hsotg *hsotg, + struct dwc2_qtd *qtd, + struct dwc2_qh *qh) +{ + list_del(&qtd->qtd_list_entry); + kfree(qtd); +} + +/* Descriptor DMA support functions */ +void dwc2_hcd_start_xfer_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh); +void dwc2_hcd_complete_xfer_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + enum dwc2_halt_status halt_status); + +int dwc2_hcd_qh_init_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + gfp_t mem_flags); +void dwc2_hcd_qh_free_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh); + +/* Check if QH is non-periodic */ +#define dwc2_qh_is_non_per(_qh_ptr_) \ + ((_qh_ptr_)->ep_type == USB_ENDPOINT_XFER_BULK || \ + (_qh_ptr_)->ep_type == USB_ENDPOINT_XFER_CONTROL) + +#ifdef CONFIG_USB_DWC2_DEBUG_PERIODIC +static inline bool dbg_hc(struct dwc2_host_chan *hc) { return true; } +static inline bool dbg_qh(struct dwc2_qh *qh) { return true; } +static inline bool dbg_urb(struct urb *urb) { return true; } +static inline bool dbg_perio(void) { return true; } +#else /* !CONFIG_USB_DWC2_DEBUG_PERIODIC */ +static inline bool dbg_hc(struct dwc2_host_chan *hc) +{ + return hc->ep_type == USB_ENDPOINT_XFER_BULK || + hc->ep_type == USB_ENDPOINT_XFER_CONTROL; +} + +static inline bool dbg_qh(struct dwc2_qh *qh) +{ + return qh->ep_type == USB_ENDPOINT_XFER_BULK || + qh->ep_type == USB_ENDPOINT_XFER_CONTROL; +} + +static inline bool dbg_urb(struct urb *urb) +{ + return usb_pipetype(urb->pipe) == PIPE_BULK || + usb_pipetype(urb->pipe) == PIPE_CONTROL; +} + +static inline bool dbg_perio(void) { return false; } +#endif + +/* + * Returns true if frame1 index is greater than frame2 index. The comparison + * is done modulo FRLISTEN_64_SIZE. This accounts for the rollover of the + * frame number when the max index frame number is reached. + */ +static inline bool dwc2_frame_idx_num_gt(u16 fr_idx1, u16 fr_idx2) +{ + u16 diff = fr_idx1 - fr_idx2; + u16 sign = diff & (FRLISTEN_64_SIZE >> 1); + + return diff && !sign; +} + +/* + * Returns true if frame1 is less than or equal to frame2. The comparison is + * done modulo HFNUM_MAX_FRNUM. This accounts for the rollover of the + * frame number when the max frame number is reached. + */ +static inline int dwc2_frame_num_le(u16 frame1, u16 frame2) +{ + return ((frame2 - frame1) & HFNUM_MAX_FRNUM) <= (HFNUM_MAX_FRNUM >> 1); +} + +/* + * Returns true if frame1 is greater than frame2. The comparison is done + * modulo HFNUM_MAX_FRNUM. This accounts for the rollover of the frame + * number when the max frame number is reached. + */ +static inline int dwc2_frame_num_gt(u16 frame1, u16 frame2) +{ + return (frame1 != frame2) && + ((frame1 - frame2) & HFNUM_MAX_FRNUM) < (HFNUM_MAX_FRNUM >> 1); +} + +/* + * Increments frame by the amount specified by inc. The addition is done + * modulo HFNUM_MAX_FRNUM. Returns the incremented value. + */ +static inline u16 dwc2_frame_num_inc(u16 frame, u16 inc) +{ + return (frame + inc) & HFNUM_MAX_FRNUM; +} + +static inline u16 dwc2_frame_num_dec(u16 frame, u16 dec) +{ + return (frame + HFNUM_MAX_FRNUM + 1 - dec) & HFNUM_MAX_FRNUM; +} + +static inline u16 dwc2_full_frame_num(u16 frame) +{ + return (frame & HFNUM_MAX_FRNUM) >> 3; +} + +static inline u16 dwc2_micro_frame_num(u16 frame) +{ + return frame & 0x7; +} + +/* + * Returns the Core Interrupt Status register contents, ANDed with the Core + * Interrupt Mask register contents + */ +static inline u32 dwc2_read_core_intr(struct dwc2_hsotg *hsotg) +{ + return dwc2_readl(hsotg, GINTSTS) & + dwc2_readl(hsotg, GINTMSK); +} + +static inline u32 dwc2_hcd_urb_get_status(struct dwc2_hcd_urb *dwc2_urb) +{ + return dwc2_urb->status; +} + +static inline u32 dwc2_hcd_urb_get_actual_length( + struct dwc2_hcd_urb *dwc2_urb) +{ + return dwc2_urb->actual_length; +} + +static inline u32 dwc2_hcd_urb_get_error_count(struct dwc2_hcd_urb *dwc2_urb) +{ + return dwc2_urb->error_count; +} + +static inline void dwc2_hcd_urb_set_iso_desc_params( + struct dwc2_hcd_urb *dwc2_urb, int desc_num, u32 offset, + u32 length) +{ + dwc2_urb->iso_descs[desc_num].offset = offset; + dwc2_urb->iso_descs[desc_num].length = length; +} + +static inline u32 dwc2_hcd_urb_get_iso_desc_status( + struct dwc2_hcd_urb *dwc2_urb, int desc_num) +{ + return dwc2_urb->iso_descs[desc_num].status; +} + +static inline u32 dwc2_hcd_urb_get_iso_desc_actual_length( + struct dwc2_hcd_urb *dwc2_urb, int desc_num) +{ + return dwc2_urb->iso_descs[desc_num].actual_length; +} + +static inline int dwc2_hcd_is_bandwidth_allocated(struct dwc2_hsotg *hsotg, + struct usb_host_endpoint *ep) +{ + struct dwc2_qh *qh = ep->hcpriv; + + if (qh && !list_empty(&qh->qh_list_entry)) + return 1; + + return 0; +} + +static inline u16 dwc2_hcd_get_ep_bandwidth(struct dwc2_hsotg *hsotg, + struct usb_host_endpoint *ep) +{ + struct dwc2_qh *qh = ep->hcpriv; + + if (!qh) { + WARN_ON(1); + return 0; + } + + return qh->host_us; +} + +void dwc2_hcd_save_data_toggle(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd); + +/* HCD Core API */ + +/** + * dwc2_handle_hcd_intr() - Called on every hardware interrupt + * + * @hsotg: The DWC2 HCD + * + * Returns IRQ_HANDLED if interrupt is handled + * Return IRQ_NONE if interrupt is not handled + */ +irqreturn_t dwc2_handle_hcd_intr(struct dwc2_hsotg *hsotg); + +/** + * dwc2_hcd_stop() - Halts the DWC_otg host mode operation + * + * @hsotg: The DWC2 HCD + */ +void dwc2_hcd_stop(struct dwc2_hsotg *hsotg); + +/** + * dwc2_hcd_is_b_host() - Returns 1 if core currently is acting as B host, + * and 0 otherwise + * + * @hsotg: The DWC2 HCD + */ +int dwc2_hcd_is_b_host(struct dwc2_hsotg *hsotg); + +/** + * dwc2_hcd_dump_state() - Dumps hsotg state + * + * @hsotg: The DWC2 HCD + * + * NOTE: This function will be removed once the peripheral controller code + * is integrated and the driver is stable + */ +void dwc2_hcd_dump_state(struct dwc2_hsotg *hsotg); + +/* URB interface */ + +/* Transfer flags */ +#define URB_GIVEBACK_ASAP 0x1 +#define URB_SEND_ZERO_PACKET 0x2 + +/* Host driver callbacks */ +struct dwc2_tt *dwc2_host_get_tt_info(struct dwc2_hsotg *hsotg, + void *context, gfp_t mem_flags, + int *ttport); + +void dwc2_host_put_tt_info(struct dwc2_hsotg *hsotg, + struct dwc2_tt *dwc_tt); +int dwc2_host_get_speed(struct dwc2_hsotg *hsotg, void *context); +void dwc2_host_complete(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, + int status); + +#endif /* __DWC2_HCD_H__ */ diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c new file mode 100644 index 000000000..994a78ad0 --- /dev/null +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -0,0 +1,1354 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * hcd_ddma.c - DesignWare HS OTG Controller descriptor DMA routines + * + * Copyright (C) 2004-2013 Synopsys, Inc. + */ + +/* + * This file contains the Descriptor DMA implementation for Host mode + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "core.h" +#include "hcd.h" + +static u16 dwc2_frame_list_idx(u16 frame) +{ + return frame & (FRLISTEN_64_SIZE - 1); +} + +static u16 dwc2_desclist_idx_inc(u16 idx, u16 inc, u8 speed) +{ + return (idx + inc) & + ((speed == USB_SPEED_HIGH ? MAX_DMA_DESC_NUM_HS_ISOC : + MAX_DMA_DESC_NUM_GENERIC) - 1); +} + +static u16 dwc2_desclist_idx_dec(u16 idx, u16 inc, u8 speed) +{ + return (idx - inc) & + ((speed == USB_SPEED_HIGH ? MAX_DMA_DESC_NUM_HS_ISOC : + MAX_DMA_DESC_NUM_GENERIC) - 1); +} + +static u16 dwc2_max_desc_num(struct dwc2_qh *qh) +{ + return (qh->ep_type == USB_ENDPOINT_XFER_ISOC && + qh->dev_speed == USB_SPEED_HIGH) ? + MAX_DMA_DESC_NUM_HS_ISOC : MAX_DMA_DESC_NUM_GENERIC; +} + +static u16 dwc2_frame_incr_val(struct dwc2_qh *qh) +{ + return qh->dev_speed == USB_SPEED_HIGH ? + (qh->host_interval + 8 - 1) / 8 : qh->host_interval; +} + +static int dwc2_desc_list_alloc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + gfp_t flags) +{ + struct kmem_cache *desc_cache; + + if (qh->ep_type == USB_ENDPOINT_XFER_ISOC && + qh->dev_speed == USB_SPEED_HIGH) + desc_cache = hsotg->desc_hsisoc_cache; + else + desc_cache = hsotg->desc_gen_cache; + + qh->desc_list_sz = sizeof(struct dwc2_dma_desc) * + dwc2_max_desc_num(qh); + + qh->desc_list = kmem_cache_zalloc(desc_cache, flags | GFP_DMA); + if (!qh->desc_list) + return -ENOMEM; + + qh->desc_list_dma = dma_map_single(hsotg->dev, qh->desc_list, + qh->desc_list_sz, + DMA_TO_DEVICE); + + qh->n_bytes = kcalloc(dwc2_max_desc_num(qh), sizeof(u32), flags); + if (!qh->n_bytes) { + dma_unmap_single(hsotg->dev, qh->desc_list_dma, + qh->desc_list_sz, + DMA_FROM_DEVICE); + kmem_cache_free(desc_cache, qh->desc_list); + qh->desc_list = NULL; + return -ENOMEM; + } + + return 0; +} + +static void dwc2_desc_list_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + struct kmem_cache *desc_cache; + + if (qh->ep_type == USB_ENDPOINT_XFER_ISOC && + qh->dev_speed == USB_SPEED_HIGH) + desc_cache = hsotg->desc_hsisoc_cache; + else + desc_cache = hsotg->desc_gen_cache; + + if (qh->desc_list) { + dma_unmap_single(hsotg->dev, qh->desc_list_dma, + qh->desc_list_sz, DMA_FROM_DEVICE); + kmem_cache_free(desc_cache, qh->desc_list); + qh->desc_list = NULL; + } + + kfree(qh->n_bytes); + qh->n_bytes = NULL; +} + +static int dwc2_frame_list_alloc(struct dwc2_hsotg *hsotg, gfp_t mem_flags) +{ + if (hsotg->frame_list) + return 0; + + hsotg->frame_list_sz = 4 * FRLISTEN_64_SIZE; + hsotg->frame_list = kzalloc(hsotg->frame_list_sz, GFP_ATOMIC | GFP_DMA); + if (!hsotg->frame_list) + return -ENOMEM; + + hsotg->frame_list_dma = dma_map_single(hsotg->dev, hsotg->frame_list, + hsotg->frame_list_sz, + DMA_TO_DEVICE); + + return 0; +} + +static void dwc2_frame_list_free(struct dwc2_hsotg *hsotg) +{ + unsigned long flags; + + spin_lock_irqsave(&hsotg->lock, flags); + + if (!hsotg->frame_list) { + spin_unlock_irqrestore(&hsotg->lock, flags); + return; + } + + dma_unmap_single(hsotg->dev, hsotg->frame_list_dma, + hsotg->frame_list_sz, DMA_FROM_DEVICE); + + kfree(hsotg->frame_list); + hsotg->frame_list = NULL; + + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +static void dwc2_per_sched_enable(struct dwc2_hsotg *hsotg, u32 fr_list_en) +{ + u32 hcfg; + unsigned long flags; + + spin_lock_irqsave(&hsotg->lock, flags); + + hcfg = dwc2_readl(hsotg, HCFG); + if (hcfg & HCFG_PERSCHEDENA) { + /* already enabled */ + spin_unlock_irqrestore(&hsotg->lock, flags); + return; + } + + dwc2_writel(hsotg, hsotg->frame_list_dma, HFLBADDR); + + hcfg &= ~HCFG_FRLISTEN_MASK; + hcfg |= fr_list_en | HCFG_PERSCHEDENA; + dev_vdbg(hsotg->dev, "Enabling Periodic schedule\n"); + dwc2_writel(hsotg, hcfg, HCFG); + + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +static void dwc2_per_sched_disable(struct dwc2_hsotg *hsotg) +{ + u32 hcfg; + unsigned long flags; + + spin_lock_irqsave(&hsotg->lock, flags); + + hcfg = dwc2_readl(hsotg, HCFG); + if (!(hcfg & HCFG_PERSCHEDENA)) { + /* already disabled */ + spin_unlock_irqrestore(&hsotg->lock, flags); + return; + } + + hcfg &= ~HCFG_PERSCHEDENA; + dev_vdbg(hsotg->dev, "Disabling Periodic schedule\n"); + dwc2_writel(hsotg, hcfg, HCFG); + + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +/* + * Activates/Deactivates FrameList entries for the channel based on endpoint + * servicing period + */ +static void dwc2_update_frame_list(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + int enable) +{ + struct dwc2_host_chan *chan; + u16 i, j, inc; + + if (!hsotg) { + pr_err("hsotg = %p\n", hsotg); + return; + } + + if (!qh->channel) { + dev_err(hsotg->dev, "qh->channel = %p\n", qh->channel); + return; + } + + if (!hsotg->frame_list) { + dev_err(hsotg->dev, "hsotg->frame_list = %p\n", + hsotg->frame_list); + return; + } + + chan = qh->channel; + inc = dwc2_frame_incr_val(qh); + if (qh->ep_type == USB_ENDPOINT_XFER_ISOC) + i = dwc2_frame_list_idx(qh->next_active_frame); + else + i = 0; + + j = i; + do { + if (enable) + hsotg->frame_list[j] |= 1 << chan->hc_num; + else + hsotg->frame_list[j] &= ~(1 << chan->hc_num); + j = (j + inc) & (FRLISTEN_64_SIZE - 1); + } while (j != i); + + /* + * Sync frame list since controller will access it if periodic + * channel is currently enabled. + */ + dma_sync_single_for_device(hsotg->dev, + hsotg->frame_list_dma, + hsotg->frame_list_sz, + DMA_TO_DEVICE); + + if (!enable) + return; + + chan->schinfo = 0; + if (chan->speed == USB_SPEED_HIGH && qh->host_interval) { + j = 1; + /* TODO - check this */ + inc = (8 + qh->host_interval - 1) / qh->host_interval; + for (i = 0; i < inc; i++) { + chan->schinfo |= j; + j = j << qh->host_interval; + } + } else { + chan->schinfo = 0xff; + } +} + +static void dwc2_release_channel_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + struct dwc2_host_chan *chan = qh->channel; + + if (dwc2_qh_is_non_per(qh)) { + if (hsotg->params.uframe_sched) + hsotg->available_host_channels++; + else + hsotg->non_periodic_channels--; + } else { + dwc2_update_frame_list(hsotg, qh, 0); + hsotg->available_host_channels++; + } + + /* + * The condition is added to prevent double cleanup try in case of + * device disconnect. See channel cleanup in dwc2_hcd_disconnect(). + */ + if (chan->qh) { + if (!list_empty(&chan->hc_list_entry)) + list_del(&chan->hc_list_entry); + dwc2_hc_cleanup(hsotg, chan); + list_add_tail(&chan->hc_list_entry, &hsotg->free_hc_list); + chan->qh = NULL; + } + + qh->channel = NULL; + qh->ntd = 0; + + if (qh->desc_list) + memset(qh->desc_list, 0, sizeof(struct dwc2_dma_desc) * + dwc2_max_desc_num(qh)); +} + +/** + * dwc2_hcd_qh_init_ddma() - Initializes a QH structure's Descriptor DMA + * related members + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: The QH to init + * @mem_flags: Indicates the type of memory allocation + * + * Return: 0 if successful, negative error code otherwise + * + * Allocates memory for the descriptor list. For the first periodic QH, + * allocates memory for the FrameList and enables periodic scheduling. + */ +int dwc2_hcd_qh_init_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + gfp_t mem_flags) +{ + int retval; + + if (qh->do_split) { + dev_err(hsotg->dev, + "SPLIT Transfers are not supported in Descriptor DMA mode.\n"); + retval = -EINVAL; + goto err0; + } + + retval = dwc2_desc_list_alloc(hsotg, qh, mem_flags); + if (retval) + goto err0; + + if (qh->ep_type == USB_ENDPOINT_XFER_ISOC || + qh->ep_type == USB_ENDPOINT_XFER_INT) { + if (!hsotg->frame_list) { + retval = dwc2_frame_list_alloc(hsotg, mem_flags); + if (retval) + goto err1; + /* Enable periodic schedule on first periodic QH */ + dwc2_per_sched_enable(hsotg, HCFG_FRLISTEN_64); + } + } + + qh->ntd = 0; + return 0; + +err1: + dwc2_desc_list_free(hsotg, qh); +err0: + return retval; +} + +/** + * dwc2_hcd_qh_free_ddma() - Frees a QH structure's Descriptor DMA related + * members + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: The QH to free + * + * Frees descriptor list memory associated with the QH. If QH is periodic and + * the last, frees FrameList memory and disables periodic scheduling. + */ +void dwc2_hcd_qh_free_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + unsigned long flags; + + dwc2_desc_list_free(hsotg, 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. + */ + spin_lock_irqsave(&hsotg->lock, flags); + if (qh->channel) + dwc2_release_channel_ddma(hsotg, qh); + spin_unlock_irqrestore(&hsotg->lock, flags); + + if ((qh->ep_type == USB_ENDPOINT_XFER_ISOC || + qh->ep_type == USB_ENDPOINT_XFER_INT) && + (hsotg->params.uframe_sched || + !hsotg->periodic_channels) && hsotg->frame_list) { + dwc2_per_sched_disable(hsotg); + dwc2_frame_list_free(hsotg); + } +} + +static u8 dwc2_frame_to_desc_idx(struct dwc2_qh *qh, u16 frame_idx) +{ + if (qh->dev_speed == USB_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 u16 dwc2_calc_starting_frame(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh, u16 *skip_frames) +{ + u16 frame; + + hsotg->frame_number = dwc2_hcd_get_frame_number(hsotg); + + /* + * next_active_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 == USB_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 (dwc2_micro_frame_num(hsotg->frame_number) >= 5) { + *skip_frames = 2 * 8; + frame = dwc2_frame_num_inc(hsotg->frame_number, + *skip_frames); + } else { + *skip_frames = 1 * 8; + frame = dwc2_frame_num_inc(hsotg->frame_number, + *skip_frames); + } + + frame = dwc2_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 = dwc2_frame_num_inc(hsotg->frame_number, 2); + } + + return frame; +} + +/* + * Calculate initial descriptor index for isochronous transfer based on + * scheduled frame + */ +static u16 dwc2_recalc_initial_desc_idx(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + u16 frame, fr_idx, fr_idx_tmp, skip_frames; + + /* + * 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 = dwc2_calc_starting_frame(hsotg, qh, &skip_frames); + /* + * Calculate initial descriptor index based on FrameList current + * bitmap and servicing period + */ + fr_idx_tmp = dwc2_frame_list_idx(frame); + fr_idx = (FRLISTEN_64_SIZE + + dwc2_frame_list_idx(qh->next_active_frame) - + fr_idx_tmp) % dwc2_frame_incr_val(qh); + fr_idx = (fr_idx + fr_idx_tmp) % FRLISTEN_64_SIZE; + } else { + qh->next_active_frame = dwc2_calc_starting_frame(hsotg, qh, + &skip_frames); + fr_idx = dwc2_frame_list_idx(qh->next_active_frame); + } + + qh->td_first = qh->td_last = dwc2_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 dwc2_fill_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, + struct dwc2_qtd *qtd, + struct dwc2_qh *qh, u32 max_xfer_size, + u16 idx) +{ + struct dwc2_dma_desc *dma_desc = &qh->desc_list[idx]; + struct dwc2_hcd_iso_packet_desc *frame_desc; + + memset(dma_desc, 0, sizeof(*dma_desc)); + 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->buf = (u32)(qtd->urb->dma + frame_desc->offset); + dma_desc->status = qh->n_bytes[idx] << HOST_DMA_ISOC_NBYTES_SHIFT & + HOST_DMA_ISOC_NBYTES_MASK; + + /* Set active bit */ + dma_desc->status |= HOST_DMA_A; + + qh->ntd++; + qtd->isoc_frame_index_last++; + +#ifdef ISOC_URB_GIVEBACK_ASAP + /* Set IOC for each descriptor corresponding to last frame of URB */ + if (qtd->isoc_frame_index_last == qtd->urb->packet_count) + dma_desc->status |= HOST_DMA_IOC; +#endif + + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + + (idx * sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), + DMA_TO_DEVICE); +} + +static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh, u16 skip_frames) +{ + struct dwc2_qtd *qtd; + u32 max_xfer_size; + u16 idx, inc, n_desc = 0, ntd_max = 0; + u16 cur_idx; + u16 next_idx; + + idx = qh->td_last; + inc = qh->host_interval; + hsotg->frame_number = dwc2_hcd_get_frame_number(hsotg); + cur_idx = idx; + next_idx = dwc2_desclist_idx_inc(qh->td_last, inc, qh->dev_speed); + + /* + * Ensure current frame number didn't overstep last scheduled + * descriptor. If it happens, the only way to recover is to move + * qh->td_last to current frame number + 1. + * So that next isoc descriptor will be scheduled on frame number + 1 + * and not on a past frame. + */ + if (dwc2_frame_idx_num_gt(cur_idx, next_idx) || (cur_idx == next_idx)) { + if (inc < 32) { + dev_vdbg(hsotg->dev, + "current frame number overstep last descriptor\n"); + qh->td_last = dwc2_desclist_idx_inc(cur_idx, inc, + qh->dev_speed); + idx = qh->td_last; + } + } + + if (qh->host_interval) { + ntd_max = (dwc2_max_desc_num(qh) + qh->host_interval - 1) / + qh->host_interval; + if (skip_frames && !qh->channel) + ntd_max -= skip_frames / qh->host_interval; + } + + max_xfer_size = qh->dev_speed == USB_SPEED_HIGH ? + MAX_ISOC_XFER_SIZE_HS : MAX_ISOC_XFER_SIZE_FS; + + list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) { + if (qtd->in_process && + qtd->isoc_frame_index_last == + qtd->urb->packet_count) + continue; + + qtd->isoc_td_first = idx; + while (qh->ntd < ntd_max && qtd->isoc_frame_index_last < + qtd->urb->packet_count) { + dwc2_fill_host_isoc_dma_desc(hsotg, qtd, qh, + max_xfer_size, idx); + idx = dwc2_desclist_idx_inc(idx, inc, qh->dev_speed); + n_desc++; + } + qtd->isoc_td_last = idx; + qtd->in_process = 1; + } + + qh->td_last = idx; + +#ifdef ISOC_URB_GIVEBACK_ASAP + /* Set IOC for last descriptor if descriptor list is full */ + if (qh->ntd == ntd_max) { + idx = dwc2_desclist_idx_dec(qh->td_last, inc, qh->dev_speed); + qh->desc_list[idx].status |= HOST_DMA_IOC; + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + (idx * + sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), + DMA_TO_DEVICE); + } +#else + /* + * Set IOC bit only for one descriptor. Always try to be ahead of HW + * processing, i.e. on IOC generation driver activates next descriptor + * but core continues to process descriptors following the one with IOC + * set. + */ + + if (n_desc > DESCNUM_THRESHOLD) + /* + * Move IOC "up". Required even if there is only one QTD + * in the list, because QTDs might 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 = dwc2_desclist_idx_dec(idx, inc * ((qh->ntd + 1) / 2), + qh->dev_speed); + else + /* + * Set the IOC for the latest descriptor if either number of + * descriptors is not greater than threshold or no more new + * descriptors activated + */ + idx = dwc2_desclist_idx_dec(qh->td_last, inc, qh->dev_speed); + + qh->desc_list[idx].status |= HOST_DMA_IOC; + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + + (idx * sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), + DMA_TO_DEVICE); +#endif +} + +static void dwc2_fill_host_dma_desc(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + struct dwc2_qtd *qtd, struct dwc2_qh *qh, + int n_desc) +{ + struct dwc2_dma_desc *dma_desc = &qh->desc_list[n_desc]; + int len = chan->xfer_len; + + if (len > HOST_DMA_NBYTES_LIMIT - (chan->max_packet - 1)) + len = HOST_DMA_NBYTES_LIMIT - (chan->max_packet - 1); + + if (chan->ep_is_in) { + int num_packets; + + if (len > 0 && chan->max_packet) + num_packets = (len + chan->max_packet - 1) + / chan->max_packet; + else + /* Need 1 packet for transfer length of 0 */ + num_packets = 1; + + /* Always program an integral # of packets for IN transfers */ + len = num_packets * chan->max_packet; + } + + dma_desc->status = len << HOST_DMA_NBYTES_SHIFT & HOST_DMA_NBYTES_MASK; + qh->n_bytes[n_desc] = len; + + if (qh->ep_type == USB_ENDPOINT_XFER_CONTROL && + qtd->control_phase == DWC2_CONTROL_SETUP) + dma_desc->status |= HOST_DMA_SUP; + + dma_desc->buf = (u32)chan->xfer_dma; + + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + + (n_desc * sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), + DMA_TO_DEVICE); + + /* + * Last (or only) descriptor of IN transfer with actual size less + * than MaxPacket + */ + if (len > chan->xfer_len) { + chan->xfer_len = 0; + } else { + chan->xfer_dma += len; + chan->xfer_len -= len; + } +} + +static void dwc2_init_non_isoc_dma_desc(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + struct dwc2_qtd *qtd; + struct dwc2_host_chan *chan = qh->channel; + int n_desc = 0; + + dev_vdbg(hsotg->dev, "%s(): qh=%p dma=%08lx len=%d\n", __func__, qh, + (unsigned long)chan->xfer_dma, chan->xfer_len); + + /* + * Start with chan->xfer_dma initialized in assign_and_init_hc(), then + * if SG transfer consists of multiple URBs, this pointer is re-assigned + * to the buffer of the currently processed QTD. For non-SG request + * there is always one QTD active. + */ + + list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) { + dev_vdbg(hsotg->dev, "qtd=%p\n", qtd); + + if (n_desc) { + /* SG request - more than 1 QTD */ + chan->xfer_dma = qtd->urb->dma + + qtd->urb->actual_length; + chan->xfer_len = qtd->urb->length - + qtd->urb->actual_length; + dev_vdbg(hsotg->dev, "buf=%08lx len=%d\n", + (unsigned long)chan->xfer_dma, chan->xfer_len); + } + + qtd->n_desc = 0; + do { + if (n_desc > 1) { + qh->desc_list[n_desc - 1].status |= HOST_DMA_A; + dev_vdbg(hsotg->dev, + "set A bit in desc %d (%p)\n", + n_desc - 1, + &qh->desc_list[n_desc - 1]); + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + + ((n_desc - 1) * + sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), + DMA_TO_DEVICE); + } + dwc2_fill_host_dma_desc(hsotg, chan, qtd, qh, n_desc); + dev_vdbg(hsotg->dev, + "desc %d (%p) buf=%08x status=%08x\n", + n_desc, &qh->desc_list[n_desc], + qh->desc_list[n_desc].buf, + qh->desc_list[n_desc].status); + qtd->n_desc++; + n_desc++; + } while (chan->xfer_len > 0 && + n_desc != MAX_DMA_DESC_NUM_GENERIC); + + dev_vdbg(hsotg->dev, "n_desc=%d\n", n_desc); + qtd->in_process = 1; + if (qh->ep_type == USB_ENDPOINT_XFER_CONTROL) + break; + if (n_desc == MAX_DMA_DESC_NUM_GENERIC) + break; + } + + if (n_desc) { + qh->desc_list[n_desc - 1].status |= + HOST_DMA_IOC | HOST_DMA_EOL | HOST_DMA_A; + dev_vdbg(hsotg->dev, "set IOC/EOL/A bits in desc %d (%p)\n", + n_desc - 1, &qh->desc_list[n_desc - 1]); + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma + (n_desc - 1) * + sizeof(struct dwc2_dma_desc), + sizeof(struct dwc2_dma_desc), + DMA_TO_DEVICE); + if (n_desc > 1) { + qh->desc_list[0].status |= HOST_DMA_A; + dev_vdbg(hsotg->dev, "set A bit in desc 0 (%p)\n", + &qh->desc_list[0]); + dma_sync_single_for_device(hsotg->dev, + qh->desc_list_dma, + sizeof(struct dwc2_dma_desc), + DMA_TO_DEVICE); + } + chan->ntd = n_desc; + } +} + +/** + * dwc2_hcd_start_xfer_ddma() - Starts a transfer in Descriptor DMA mode + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: The QH to init + * + * Return: 0 if successful, negative error code otherwise + * + * 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. + * + * For Isochronous endpoints the starting descriptor index is calculated based + * on the scheduled frame, but only on the first transfer descriptor within a + * session. Then the transfer is started via enabling the channel. + * + * For Isochronous endpoints the channel is not halted on XferComplete + * interrupt so remains assigned to the endpoint(QH) until session is done. + */ +void dwc2_hcd_start_xfer_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + /* Channel is already assigned */ + struct dwc2_host_chan *chan = qh->channel; + u16 skip_frames = 0; + + switch (chan->ep_type) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + dwc2_init_non_isoc_dma_desc(hsotg, qh); + dwc2_hc_start_transfer_ddma(hsotg, chan); + break; + case USB_ENDPOINT_XFER_INT: + dwc2_init_non_isoc_dma_desc(hsotg, qh); + dwc2_update_frame_list(hsotg, qh, 1); + dwc2_hc_start_transfer_ddma(hsotg, chan); + break; + case USB_ENDPOINT_XFER_ISOC: + if (!qh->ntd) + skip_frames = dwc2_recalc_initial_desc_idx(hsotg, qh); + dwc2_init_isoc_dma_desc(hsotg, qh, skip_frames); + + if (!chan->xfer_started) { + dwc2_update_frame_list(hsotg, qh, 1); + + /* + * Always set to max, instead of actual size. Otherwise + * ntd will be changed with channel being enabled. Not + * recommended. + */ + chan->ntd = dwc2_max_desc_num(qh); + + /* Enable channel only once for ISOC */ + dwc2_hc_start_transfer_ddma(hsotg, chan); + } + + break; + default: + break; + } +} + +#define DWC2_CMPL_DONE 1 +#define DWC2_CMPL_STOP 2 + +static int dwc2_cmpl_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + struct dwc2_qtd *qtd, + struct dwc2_qh *qh, u16 idx) +{ + struct dwc2_dma_desc *dma_desc; + struct dwc2_hcd_iso_packet_desc *frame_desc; + u16 frame_desc_idx; + struct urb *usb_urb; + u16 remain = 0; + int rc = 0; + + if (!qtd->urb) + return -EINVAL; + + usb_urb = qtd->urb->priv; + + dma_sync_single_for_cpu(hsotg->dev, qh->desc_list_dma + (idx * + sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), + DMA_FROM_DEVICE); + + dma_desc = &qh->desc_list[idx]; + frame_desc_idx = (idx - qtd->isoc_td_first) & (usb_urb->number_of_packets - 1); + + frame_desc = &qtd->urb->iso_descs[frame_desc_idx]; + if (idx == qtd->isoc_td_first) + usb_urb->start_frame = dwc2_hcd_get_frame_number(hsotg); + dma_desc->buf = (u32)(qtd->urb->dma + frame_desc->offset); + if (chan->ep_is_in) + remain = (dma_desc->status & HOST_DMA_ISOC_NBYTES_MASK) >> + HOST_DMA_ISOC_NBYTES_SHIFT; + + if ((dma_desc->status & HOST_DMA_STS_MASK) == HOST_DMA_STS_PKTERR) { + /* + * XactError, or unable to complete all the transactions + * in the scheduled micro-frame/frame, both indicated by + * HOST_DMA_STS_PKTERR + */ + qtd->urb->error_count++; + frame_desc->actual_length = qh->n_bytes[idx] - remain; + frame_desc->status = -EPROTO; + } else { + /* Success */ + frame_desc->actual_length = qh->n_bytes[idx] - remain; + frame_desc->status = 0; + } + + if (++qtd->isoc_frame_index == usb_urb->number_of_packets) { + /* + * urb->status is not used for isoc transfers here. The + * individual frame_desc status are used instead. + */ + dwc2_host_complete(hsotg, qtd, 0); + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); + + /* + * This check is necessary because urb_dequeue can be called + * from urb complete callback (sound driver for example). All + * pending URBs are dequeued there, so no need for further + * processing. + */ + if (chan->halt_status == DWC2_HC_XFER_URB_DEQUEUE) + return -1; + rc = DWC2_CMPL_DONE; + } + + qh->ntd--; + + /* Stop if IOC requested descriptor reached */ + if (dma_desc->status & HOST_DMA_IOC) + rc = DWC2_CMPL_STOP; + + return rc; +} + +static void dwc2_complete_isoc_xfer_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + enum dwc2_halt_status halt_status) +{ + struct dwc2_hcd_iso_packet_desc *frame_desc; + struct dwc2_qtd *qtd, *qtd_tmp; + struct dwc2_qh *qh; + u16 idx; + int rc; + + qh = chan->qh; + idx = qh->td_first; + + if (chan->halt_status == DWC2_HC_XFER_URB_DEQUEUE) { + list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) + qtd->in_process = 0; + return; + } + + if (halt_status == DWC2_HC_XFER_AHB_ERR || + halt_status == DWC2_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 not. + * 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 == DWC2_HC_XFER_AHB_ERR ? + -EIO : -EOVERFLOW; + + list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, + qtd_list_entry) { + if (qtd->urb) { + for (idx = 0; idx < qtd->urb->packet_count; + idx++) { + frame_desc = &qtd->urb->iso_descs[idx]; + frame_desc->status = err; + } + + dwc2_host_complete(hsotg, qtd, err); + } + + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); + } + + return; + } + + list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) { + if (!qtd->in_process) + break; + + /* + * Ensure idx corresponds to descriptor where first urb of this + * qtd was added. In fact, during isoc desc init, dwc2 may skip + * an index if current frame number is already over this index. + */ + if (idx != qtd->isoc_td_first) { + dev_vdbg(hsotg->dev, + "try to complete %d instead of %d\n", + idx, qtd->isoc_td_first); + idx = qtd->isoc_td_first; + } + + do { + struct dwc2_qtd *qtd_next; + u16 cur_idx; + + rc = dwc2_cmpl_host_isoc_dma_desc(hsotg, chan, qtd, qh, + idx); + if (rc < 0) + return; + idx = dwc2_desclist_idx_inc(idx, qh->host_interval, + chan->speed); + if (rc == 0) + continue; + + if (rc == DWC2_CMPL_DONE || rc == DWC2_CMPL_STOP) + goto stop_scan; + + /* rc == DWC2_CMPL_STOP */ + + if (qh->host_interval >= 32) + goto stop_scan; + + qh->td_first = idx; + cur_idx = dwc2_frame_list_idx(hsotg->frame_number); + qtd_next = list_first_entry(&qh->qtd_list, + struct dwc2_qtd, + qtd_list_entry); + if (dwc2_frame_idx_num_gt(cur_idx, + qtd_next->isoc_td_last)) + break; + + goto stop_scan; + + } while (idx != qh->td_first); + } + +stop_scan: + qh->td_first = idx; +} + +static int dwc2_update_non_isoc_urb_state_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + struct dwc2_qtd *qtd, + struct dwc2_dma_desc *dma_desc, + enum dwc2_halt_status halt_status, + u32 n_bytes, int *xfer_done) +{ + struct dwc2_hcd_urb *urb = qtd->urb; + u16 remain = 0; + + if (chan->ep_is_in) + remain = (dma_desc->status & HOST_DMA_NBYTES_MASK) >> + HOST_DMA_NBYTES_SHIFT; + + dev_vdbg(hsotg->dev, "remain=%d dwc2_urb=%p\n", remain, urb); + + if (halt_status == DWC2_HC_XFER_AHB_ERR) { + dev_err(hsotg->dev, "EIO\n"); + urb->status = -EIO; + return 1; + } + + if ((dma_desc->status & HOST_DMA_STS_MASK) == HOST_DMA_STS_PKTERR) { + switch (halt_status) { + case DWC2_HC_XFER_STALL: + dev_vdbg(hsotg->dev, "Stall\n"); + urb->status = -EPIPE; + break; + case DWC2_HC_XFER_BABBLE_ERR: + dev_err(hsotg->dev, "Babble\n"); + urb->status = -EOVERFLOW; + break; + case DWC2_HC_XFER_XACT_ERR: + dev_err(hsotg->dev, "XactErr\n"); + urb->status = -EPROTO; + break; + default: + dev_err(hsotg->dev, + "%s: Unhandled descriptor error status (%d)\n", + __func__, halt_status); + break; + } + return 1; + } + + if (dma_desc->status & HOST_DMA_A) { + dev_vdbg(hsotg->dev, + "Active descriptor encountered on channel %d\n", + chan->hc_num); + return 0; + } + + if (chan->ep_type == USB_ENDPOINT_XFER_CONTROL) { + if (qtd->control_phase == DWC2_CONTROL_DATA) { + urb->actual_length += n_bytes - remain; + if (remain || urb->actual_length >= urb->length) { + /* + * For Control Data stage do not set urb->status + * to 0, to prevent URB callback. Set it when + * Status phase is done. See below. + */ + *xfer_done = 1; + } + } else if (qtd->control_phase == DWC2_CONTROL_STATUS) { + urb->status = 0; + *xfer_done = 1; + } + /* No handling for SETUP stage */ + } else { + /* BULK and INTR */ + urb->actual_length += n_bytes - remain; + dev_vdbg(hsotg->dev, "length=%d actual=%d\n", urb->length, + urb->actual_length); + if (remain || urb->actual_length >= urb->length) { + urb->status = 0; + *xfer_done = 1; + } + } + + return 0; +} + +static int dwc2_process_non_isoc_desc(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + int chnum, struct dwc2_qtd *qtd, + int desc_num, + enum dwc2_halt_status halt_status, + int *xfer_done) +{ + struct dwc2_qh *qh = chan->qh; + struct dwc2_hcd_urb *urb = qtd->urb; + struct dwc2_dma_desc *dma_desc; + u32 n_bytes; + int failed; + + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + if (!urb) + return -EINVAL; + + dma_sync_single_for_cpu(hsotg->dev, + qh->desc_list_dma + (desc_num * + sizeof(struct dwc2_dma_desc)), + sizeof(struct dwc2_dma_desc), + DMA_FROM_DEVICE); + + dma_desc = &qh->desc_list[desc_num]; + n_bytes = qh->n_bytes[desc_num]; + dev_vdbg(hsotg->dev, + "qtd=%p dwc2_urb=%p desc_num=%d desc=%p n_bytes=%d\n", + qtd, urb, desc_num, dma_desc, n_bytes); + failed = dwc2_update_non_isoc_urb_state_ddma(hsotg, chan, qtd, dma_desc, + halt_status, n_bytes, + xfer_done); + if (failed || (*xfer_done && urb->status != -EINPROGRESS)) { + dwc2_host_complete(hsotg, qtd, urb->status); + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); + dev_vdbg(hsotg->dev, "failed=%1x xfer_done=%1x\n", + failed, *xfer_done); + return failed; + } + + if (qh->ep_type == USB_ENDPOINT_XFER_CONTROL) { + switch (qtd->control_phase) { + case DWC2_CONTROL_SETUP: + if (urb->length > 0) + qtd->control_phase = DWC2_CONTROL_DATA; + else + qtd->control_phase = DWC2_CONTROL_STATUS; + dev_vdbg(hsotg->dev, + " Control setup transaction done\n"); + break; + case DWC2_CONTROL_DATA: + if (*xfer_done) { + qtd->control_phase = DWC2_CONTROL_STATUS; + dev_vdbg(hsotg->dev, + " Control data transfer done\n"); + } else if (desc_num + 1 == qtd->n_desc) { + /* + * Last descriptor for Control data stage which + * is not completed yet + */ + dwc2_hcd_save_data_toggle(hsotg, chan, chnum, + qtd); + } + break; + default: + break; + } + } + + return 0; +} + +static void dwc2_complete_non_isoc_xfer_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + int chnum, + enum dwc2_halt_status halt_status) +{ + struct list_head *qtd_item, *qtd_tmp; + struct dwc2_qh *qh = chan->qh; + struct dwc2_qtd *qtd = NULL; + int xfer_done; + int desc_num = 0; + + if (chan->halt_status == DWC2_HC_XFER_URB_DEQUEUE) { + list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) + qtd->in_process = 0; + return; + } + + list_for_each_safe(qtd_item, qtd_tmp, &qh->qtd_list) { + int i; + int qtd_desc_count; + + qtd = list_entry(qtd_item, struct dwc2_qtd, qtd_list_entry); + xfer_done = 0; + qtd_desc_count = qtd->n_desc; + + for (i = 0; i < qtd_desc_count; i++) { + if (dwc2_process_non_isoc_desc(hsotg, chan, chnum, qtd, + desc_num, halt_status, + &xfer_done)) { + qtd = NULL; + goto stop_scan; + } + + desc_num++; + } + } + +stop_scan: + if (qh->ep_type != USB_ENDPOINT_XFER_CONTROL) { + /* + * Resetting the data toggle for bulk and interrupt endpoints + * in case of stall. See handle_hc_stall_intr(). + */ + if (halt_status == DWC2_HC_XFER_STALL) + qh->data_toggle = DWC2_HC_PID_DATA0; + else + dwc2_hcd_save_data_toggle(hsotg, chan, chnum, NULL); + } + + if (halt_status == DWC2_HC_XFER_COMPLETE) { + if (chan->hcint & HCINTMSK_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; + } + } +} + +/** + * dwc2_hcd_complete_xfer_ddma() - Scans the descriptor list, updates URB's + * status and calls completion routine for the URB if it's done. Called from + * interrupt handlers. + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @chan: Host channel the transfer is completed on + * @chnum: Index of Host channel registers + * @halt_status: Reason the channel is being halted or just XferComplete + * for isochronous transfers + * + * 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. + */ +void dwc2_hcd_complete_xfer_ddma(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + enum dwc2_halt_status halt_status) +{ + struct dwc2_qh *qh = chan->qh; + int continue_isoc_xfer = 0; + enum dwc2_transaction_type tr_type; + + if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) { + dwc2_complete_isoc_xfer_ddma(hsotg, chan, halt_status); + + /* Release the channel if halted or session completed */ + if (halt_status != DWC2_HC_XFER_COMPLETE || + list_empty(&qh->qtd_list)) { + struct dwc2_qtd *qtd, *qtd_tmp; + + /* + * Kill all remainings QTDs since channel has been + * halted. + */ + list_for_each_entry_safe(qtd, qtd_tmp, + &qh->qtd_list, + qtd_list_entry) { + dwc2_host_complete(hsotg, qtd, + -ECONNRESET); + dwc2_hcd_qtd_unlink_and_free(hsotg, + qtd, qh); + } + + /* Halt the channel if session completed */ + if (halt_status == DWC2_HC_XFER_COMPLETE) + dwc2_hc_halt(hsotg, chan, halt_status); + dwc2_release_channel_ddma(hsotg, qh); + dwc2_hcd_qh_unlink(hsotg, qh); + } else { + /* Keep in assigned schedule to continue transfer */ + list_move_tail(&qh->qh_list_entry, + &hsotg->periodic_sched_assigned); + /* + * If channel has been halted during giveback of urb + * then prevent any new scheduling. + */ + if (!chan->halt_status) + 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 + */ + dwc2_complete_non_isoc_xfer_ddma(hsotg, chan, chnum, + halt_status); + dwc2_release_channel_ddma(hsotg, qh); + dwc2_hcd_qh_unlink(hsotg, qh); + + if (!list_empty(&qh->qtd_list)) { + /* + * Add back to inactive non-periodic schedule on normal + * completion + */ + dwc2_hcd_qh_add(hsotg, qh); + } + } + + tr_type = dwc2_hcd_select_transactions(hsotg); + if (tr_type != DWC2_TRANSACTION_NONE || continue_isoc_xfer) { + if (continue_isoc_xfer) { + if (tr_type == DWC2_TRANSACTION_NONE) + tr_type = DWC2_TRANSACTION_PERIODIC; + else if (tr_type == DWC2_TRANSACTION_NON_PERIODIC) + tr_type = DWC2_TRANSACTION_ALL; + } + dwc2_hcd_queue_transactions(hsotg, tr_type); + } +} diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c new file mode 100644 index 000000000..f380275ac --- /dev/null +++ b/drivers/usb/dwc2/hcd_intr.c @@ -0,0 +1,2268 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * hcd_intr.c - DesignWare HS OTG Controller host-mode interrupt handling + * + * Copyright (C) 2004-2013 Synopsys, Inc. + */ + +/* + * This file contains the interrupt handlers for Host mode + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "core.h" +#include "hcd.h" + +/* + * If we get this many NAKs on a split transaction we'll slow down + * retransmission. A 1 here means delay after the first NAK. + */ +#define DWC2_NAKS_BEFORE_DELAY 3 + +/* This function is for debug only */ +static void dwc2_track_missed_sofs(struct dwc2_hsotg *hsotg) +{ + u16 curr_frame_number = hsotg->frame_number; + u16 expected = dwc2_frame_num_inc(hsotg->last_frame_num, 1); + + if (expected != curr_frame_number) + dwc2_sch_vdbg(hsotg, "MISSED SOF %04x != %04x\n", + expected, curr_frame_number); + +#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS + if (hsotg->frame_num_idx < FRAME_NUM_ARRAY_SIZE) { + if (expected != curr_frame_number) { + hsotg->frame_num_array[hsotg->frame_num_idx] = + curr_frame_number; + hsotg->last_frame_num_array[hsotg->frame_num_idx] = + hsotg->last_frame_num; + hsotg->frame_num_idx++; + } + } else if (!hsotg->dumped_frame_num_array) { + int i; + + dev_info(hsotg->dev, "Frame Last Frame\n"); + dev_info(hsotg->dev, "----- ----------\n"); + for (i = 0; i < FRAME_NUM_ARRAY_SIZE; i++) { + dev_info(hsotg->dev, "0x%04x 0x%04x\n", + hsotg->frame_num_array[i], + hsotg->last_frame_num_array[i]); + } + hsotg->dumped_frame_num_array = 1; + } +#endif + hsotg->last_frame_num = curr_frame_number; +} + +static void dwc2_hc_handle_tt_clear(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + struct dwc2_qtd *qtd) +{ + struct usb_device *root_hub = dwc2_hsotg_to_hcd(hsotg)->self.root_hub; + struct urb *usb_urb; + + if (!chan->qh) + return; + + if (chan->qh->dev_speed == USB_SPEED_HIGH) + return; + + if (!qtd->urb) + return; + + usb_urb = qtd->urb->priv; + if (!usb_urb || !usb_urb->dev || !usb_urb->dev->tt) + return; + + /* + * The root hub doesn't really have a TT, but Linux thinks it + * does because how could you have a "high speed hub" that + * directly talks directly to low speed devices without a TT? + * It's all lies. Lies, I tell you. + */ + if (usb_urb->dev->tt->hub == root_hub) + return; + + if (qtd->urb->status != -EPIPE && qtd->urb->status != -EREMOTEIO) { + chan->qh->tt_buffer_dirty = 1; + if (usb_hub_clear_tt_buffer(usb_urb)) + /* Clear failed; let's hope things work anyway */ + chan->qh->tt_buffer_dirty = 0; + } +} + +/* + * 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. + */ +static void dwc2_sof_intr(struct dwc2_hsotg *hsotg) +{ + struct list_head *qh_entry; + struct dwc2_qh *qh; + enum dwc2_transaction_type tr_type; + + /* Clear interrupt */ + dwc2_writel(hsotg, GINTSTS_SOF, GINTSTS); + +#ifdef DEBUG_SOF + dev_vdbg(hsotg->dev, "--Start of Frame Interrupt--\n"); +#endif + + hsotg->frame_number = dwc2_hcd_get_frame_number(hsotg); + + dwc2_track_missed_sofs(hsotg); + + /* Determine whether any periodic QHs should be executed */ + qh_entry = hsotg->periodic_sched_inactive.next; + while (qh_entry != &hsotg->periodic_sched_inactive) { + qh = list_entry(qh_entry, struct dwc2_qh, qh_list_entry); + qh_entry = qh_entry->next; + if (dwc2_frame_num_le(qh->next_active_frame, + hsotg->frame_number)) { + dwc2_sch_vdbg(hsotg, "QH=%p ready fn=%04x, nxt=%04x\n", + qh, hsotg->frame_number, + qh->next_active_frame); + + /* + * Move QH to the ready list to be executed next + * (micro)frame + */ + list_move_tail(&qh->qh_list_entry, + &hsotg->periodic_sched_ready); + } + } + tr_type = dwc2_hcd_select_transactions(hsotg); + if (tr_type != DWC2_TRANSACTION_NONE) + dwc2_hcd_queue_transactions(hsotg, tr_type); +} + +/* + * Handles the Rx FIFO 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. + */ +static void dwc2_rx_fifo_level_intr(struct dwc2_hsotg *hsotg) +{ + u32 grxsts, chnum, bcnt, dpid, pktsts; + struct dwc2_host_chan *chan; + + if (dbg_perio()) + dev_vdbg(hsotg->dev, "--RxFIFO Level Interrupt--\n"); + + grxsts = dwc2_readl(hsotg, GRXSTSP); + chnum = (grxsts & GRXSTS_HCHNUM_MASK) >> GRXSTS_HCHNUM_SHIFT; + chan = hsotg->hc_ptr_array[chnum]; + if (!chan) { + dev_err(hsotg->dev, "Unable to get corresponding channel\n"); + return; + } + + bcnt = (grxsts & GRXSTS_BYTECNT_MASK) >> GRXSTS_BYTECNT_SHIFT; + dpid = (grxsts & GRXSTS_DPID_MASK) >> GRXSTS_DPID_SHIFT; + pktsts = (grxsts & GRXSTS_PKTSTS_MASK) >> GRXSTS_PKTSTS_SHIFT; + + /* Packet Status */ + if (dbg_perio()) { + dev_vdbg(hsotg->dev, " Ch num = %d\n", chnum); + dev_vdbg(hsotg->dev, " Count = %d\n", bcnt); + dev_vdbg(hsotg->dev, " DPID = %d, chan.dpid = %d\n", dpid, + chan->data_pid_start); + dev_vdbg(hsotg->dev, " PStatus = %d\n", pktsts); + } + + switch (pktsts) { + case GRXSTS_PKTSTS_HCHIN: + /* Read the data into the host buffer */ + if (bcnt > 0) { + dwc2_read_packet(hsotg, chan->xfer_buf, bcnt); + + /* Update the HC fields for the next packet received */ + chan->xfer_count += bcnt; + chan->xfer_buf += bcnt; + } + break; + case GRXSTS_PKTSTS_HCHIN_XFER_COMP: + case GRXSTS_PKTSTS_DATATOGGLEERR: + case GRXSTS_PKTSTS_HCHHALTED: + /* Handled in interrupt, just ignore data */ + break; + default: + dev_err(hsotg->dev, + "RxFIFO Level Interrupt: Unknown status %d\n", pktsts); + break; + } +} + +/* + * 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. + */ +static void dwc2_np_tx_fifo_empty_intr(struct dwc2_hsotg *hsotg) +{ + dev_vdbg(hsotg->dev, "--Non-Periodic TxFIFO Empty Interrupt--\n"); + dwc2_hcd_queue_transactions(hsotg, DWC2_TRANSACTION_NON_PERIODIC); +} + +/* + * 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. + */ +static void dwc2_perio_tx_fifo_empty_intr(struct dwc2_hsotg *hsotg) +{ + if (dbg_perio()) + dev_vdbg(hsotg->dev, "--Periodic TxFIFO Empty Interrupt--\n"); + dwc2_hcd_queue_transactions(hsotg, DWC2_TRANSACTION_PERIODIC); +} + +static void dwc2_hprt0_enable(struct dwc2_hsotg *hsotg, u32 hprt0, + u32 *hprt0_modify) +{ + struct dwc2_core_params *params = &hsotg->params; + int do_reset = 0; + u32 usbcfg; + u32 prtspd; + u32 hcfg; + u32 fslspclksel; + u32 hfir; + + dev_vdbg(hsotg->dev, "%s(%p)\n", __func__, hsotg); + + /* Every time when port enables calculate HFIR.FrInterval */ + hfir = dwc2_readl(hsotg, HFIR); + hfir &= ~HFIR_FRINT_MASK; + hfir |= dwc2_calc_frame_interval(hsotg) << HFIR_FRINT_SHIFT & + HFIR_FRINT_MASK; + dwc2_writel(hsotg, hfir, HFIR); + + /* Check if we need to adjust the PHY clock speed for low power */ + if (!params->host_support_fs_ls_low_power) { + /* Port has been enabled, set the reset change flag */ + hsotg->flags.b.port_reset_change = 1; + return; + } + + usbcfg = dwc2_readl(hsotg, GUSBCFG); + prtspd = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; + + if (prtspd == HPRT0_SPD_LOW_SPEED || prtspd == HPRT0_SPD_FULL_SPEED) { + /* Low power */ + if (!(usbcfg & GUSBCFG_PHY_LP_CLK_SEL)) { + /* Set PHY low power clock select for FS/LS devices */ + usbcfg |= GUSBCFG_PHY_LP_CLK_SEL; + dwc2_writel(hsotg, usbcfg, GUSBCFG); + do_reset = 1; + } + + hcfg = dwc2_readl(hsotg, HCFG); + fslspclksel = (hcfg & HCFG_FSLSPCLKSEL_MASK) >> + HCFG_FSLSPCLKSEL_SHIFT; + + if (prtspd == HPRT0_SPD_LOW_SPEED && + params->host_ls_low_power_phy_clk) { + /* 6 MHZ */ + dev_vdbg(hsotg->dev, + "FS_PHY programming HCFG to 6 MHz\n"); + if (fslspclksel != HCFG_FSLSPCLKSEL_6_MHZ) { + fslspclksel = HCFG_FSLSPCLKSEL_6_MHZ; + hcfg &= ~HCFG_FSLSPCLKSEL_MASK; + hcfg |= fslspclksel << HCFG_FSLSPCLKSEL_SHIFT; + dwc2_writel(hsotg, hcfg, HCFG); + do_reset = 1; + } + } else { + /* 48 MHZ */ + dev_vdbg(hsotg->dev, + "FS_PHY programming HCFG to 48 MHz\n"); + if (fslspclksel != HCFG_FSLSPCLKSEL_48_MHZ) { + fslspclksel = HCFG_FSLSPCLKSEL_48_MHZ; + hcfg &= ~HCFG_FSLSPCLKSEL_MASK; + hcfg |= fslspclksel << HCFG_FSLSPCLKSEL_SHIFT; + dwc2_writel(hsotg, hcfg, HCFG); + do_reset = 1; + } + } + } else { + /* Not low power */ + if (usbcfg & GUSBCFG_PHY_LP_CLK_SEL) { + usbcfg &= ~GUSBCFG_PHY_LP_CLK_SEL; + dwc2_writel(hsotg, usbcfg, GUSBCFG); + do_reset = 1; + } + } + + if (do_reset) { + *hprt0_modify |= HPRT0_RST; + dwc2_writel(hsotg, *hprt0_modify, HPRT0); + queue_delayed_work(hsotg->wq_otg, &hsotg->reset_work, + msecs_to_jiffies(60)); + } else { + /* Port has been enabled, set the reset change flag */ + hsotg->flags.b.port_reset_change = 1; + } +} + +/* + * There are multiple conditions that can cause a port interrupt. This function + * determines which interrupt conditions have occurred and handles them + * appropriately. + */ +static void dwc2_port_intr(struct dwc2_hsotg *hsotg) +{ + u32 hprt0; + u32 hprt0_modify; + + dev_vdbg(hsotg->dev, "--Port Interrupt--\n"); + + hprt0 = dwc2_readl(hsotg, HPRT0); + hprt0_modify = hprt0; + + /* + * Clear appropriate bits in HPRT0 to clear the interrupt bit in + * GINTSTS + */ + hprt0_modify &= ~(HPRT0_ENA | HPRT0_CONNDET | HPRT0_ENACHG | + HPRT0_OVRCURRCHG); + + /* + * Port Connect Detected + * Set flag and clear if detected + */ + if (hprt0 & HPRT0_CONNDET) { + dwc2_writel(hsotg, hprt0_modify | HPRT0_CONNDET, HPRT0); + + dev_vdbg(hsotg->dev, + "--Port Interrupt HPRT0=0x%08x Port Connect Detected--\n", + hprt0); + dwc2_hcd_connect(hsotg); + + /* + * The Hub driver asserts a reset when it sees port connect + * status change flag + */ + } + + /* + * Port Enable Changed + * Clear if detected - Set internal flag if disabled + */ + if (hprt0 & HPRT0_ENACHG) { + dwc2_writel(hsotg, hprt0_modify | HPRT0_ENACHG, HPRT0); + dev_vdbg(hsotg->dev, + " --Port Interrupt HPRT0=0x%08x Port Enable Changed (now %d)--\n", + hprt0, !!(hprt0 & HPRT0_ENA)); + if (hprt0 & HPRT0_ENA) { + hsotg->new_connection = true; + dwc2_hprt0_enable(hsotg, hprt0, &hprt0_modify); + } else { + hsotg->flags.b.port_enable_change = 1; + if (hsotg->params.dma_desc_fs_enable) { + u32 hcfg; + + hsotg->params.dma_desc_enable = false; + hsotg->new_connection = false; + hcfg = dwc2_readl(hsotg, HCFG); + hcfg &= ~HCFG_DESCDMA; + dwc2_writel(hsotg, hcfg, HCFG); + } + } + } + + /* Overcurrent Change Interrupt */ + if (hprt0 & HPRT0_OVRCURRCHG) { + dwc2_writel(hsotg, hprt0_modify | HPRT0_OVRCURRCHG, + HPRT0); + dev_vdbg(hsotg->dev, + " --Port Interrupt HPRT0=0x%08x Port Overcurrent Changed--\n", + hprt0); + hsotg->flags.b.port_over_current_change = 1; + } +} + +/* + * 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 DWC2_HC_XFER_COMPLETE, *short_read + * is set to 1 upon return if less than the requested number of bytes were + * transferred. short_read may also be NULL on entry, in which case it remains + * unchanged. + */ +static u32 dwc2_get_actual_xfer_length(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd, + enum dwc2_halt_status halt_status, + int *short_read) +{ + u32 hctsiz, count, length; + + hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); + + if (halt_status == DWC2_HC_XFER_COMPLETE) { + if (chan->ep_is_in) { + count = (hctsiz & TSIZ_XFERSIZE_MASK) >> + TSIZ_XFERSIZE_SHIFT; + length = chan->xfer_len - count; + if (short_read) + *short_read = (count != 0); + } else if (chan->qh->do_split) { + length = qtd->ssplit_out_xfer_count; + } else { + length = chan->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). + */ + count = (hctsiz & TSIZ_PKTCNT_MASK) >> TSIZ_PKTCNT_SHIFT; + length = (chan->start_pkt_count - count) * chan->max_packet; + } + + return length; +} + +/** + * dwc2_update_urb_state() - 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. + * + * @hsotg: Programming view of the DWC_otg controller + * @chan: Programming view of host channel + * @chnum: Channel number + * @urb: Processing URB + * @qtd: Queue transfer descriptor + * + * Return: 1 if the data transfer specified by the URB is completely finished, + * 0 otherwise + */ +static int dwc2_update_urb_state(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_hcd_urb *urb, + struct dwc2_qtd *qtd) +{ + u32 hctsiz; + int xfer_done = 0; + int short_read = 0; + int xfer_length = dwc2_get_actual_xfer_length(hsotg, chan, chnum, qtd, + DWC2_HC_XFER_COMPLETE, + &short_read); + + if (urb->actual_length + xfer_length > urb->length) { + dev_dbg(hsotg->dev, "%s(): trimming xfer length\n", __func__); + xfer_length = urb->length - urb->actual_length; + } + + dev_vdbg(hsotg->dev, "urb->actual_length=%d xfer_length=%d\n", + urb->actual_length, xfer_length); + urb->actual_length += xfer_length; + + if (xfer_length && chan->ep_type == USB_ENDPOINT_XFER_BULK && + (urb->flags & URB_SEND_ZERO_PACKET) && + urb->actual_length >= urb->length && + !(urb->length % chan->max_packet)) { + xfer_done = 0; + } else if (short_read || urb->actual_length >= urb->length) { + xfer_done = 1; + urb->status = 0; + } + + hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); + dev_vdbg(hsotg->dev, "DWC_otg: %s: %s, channel %d\n", + __func__, (chan->ep_is_in ? "IN" : "OUT"), chnum); + dev_vdbg(hsotg->dev, " chan->xfer_len %d\n", chan->xfer_len); + dev_vdbg(hsotg->dev, " hctsiz.xfersize %d\n", + (hctsiz & TSIZ_XFERSIZE_MASK) >> TSIZ_XFERSIZE_SHIFT); + dev_vdbg(hsotg->dev, " urb->transfer_buffer_length %d\n", urb->length); + dev_vdbg(hsotg->dev, " urb->actual_length %d\n", urb->actual_length); + dev_vdbg(hsotg->dev, " short_read %d, xfer_done %d\n", short_read, + xfer_done); + + 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 dwc2_hcd_save_data_toggle(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ + u32 hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); + u32 pid = (hctsiz & TSIZ_SC_MC_PID_MASK) >> TSIZ_SC_MC_PID_SHIFT; + + if (chan->ep_type != USB_ENDPOINT_XFER_CONTROL) { + if (!chan->qh) { + dev_err(hsotg->dev, "chan->qh must be specified for non-control eps\n"); + return; + } + + if (pid == TSIZ_SC_MC_PID_DATA0) + chan->qh->data_toggle = DWC2_HC_PID_DATA0; + else + chan->qh->data_toggle = DWC2_HC_PID_DATA1; + } else { + if (!qtd) { + dev_err(hsotg->dev, "qtd must be specified for control eps\n"); + return; + } + + if (pid == TSIZ_SC_MC_PID_DATA0) + qtd->data_toggle = DWC2_HC_PID_DATA0; + else + qtd->data_toggle = DWC2_HC_PID_DATA1; + } +} + +/** + * dwc2_update_isoc_urb_state() - 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. + * + * @hsotg: Programming view of the DWC_otg controller + * @chan: Programming view of host channel + * @chnum: Channel number + * @halt_status: Reason for halting a host channel + * @qtd: Queue transfer descriptor + * + * Return: DWC2_HC_XFER_COMPLETE if there are more frames remaining to be + * transferred in the URB. Otherwise return DWC2_HC_XFER_URB_COMPLETE. + */ +static enum dwc2_halt_status dwc2_update_isoc_urb_state( + struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan, + int chnum, struct dwc2_qtd *qtd, + enum dwc2_halt_status halt_status) +{ + struct dwc2_hcd_iso_packet_desc *frame_desc; + struct dwc2_hcd_urb *urb = qtd->urb; + + if (!urb) + return DWC2_HC_XFER_NO_HALT_STATUS; + + frame_desc = &urb->iso_descs[qtd->isoc_frame_index]; + + switch (halt_status) { + case DWC2_HC_XFER_COMPLETE: + frame_desc->status = 0; + frame_desc->actual_length = dwc2_get_actual_xfer_length(hsotg, + chan, chnum, qtd, halt_status, NULL); + break; + case DWC2_HC_XFER_FRAME_OVERRUN: + urb->error_count++; + if (chan->ep_is_in) + frame_desc->status = -ENOSR; + else + frame_desc->status = -ECOMM; + frame_desc->actual_length = 0; + break; + case DWC2_HC_XFER_BABBLE_ERR: + urb->error_count++; + frame_desc->status = -EOVERFLOW; + /* Don't need to update actual_length in this case */ + break; + case DWC2_HC_XFER_XACT_ERR: + urb->error_count++; + frame_desc->status = -EPROTO; + frame_desc->actual_length = dwc2_get_actual_xfer_length(hsotg, + chan, chnum, qtd, halt_status, NULL); + + /* Skip whole frame */ + if (chan->qh->do_split && + chan->ep_type == USB_ENDPOINT_XFER_ISOC && chan->ep_is_in && + hsotg->params.host_dma) { + qtd->complete_split = 0; + qtd->isoc_split_offset = 0; + } + + break; + default: + dev_err(hsotg->dev, "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. + */ + dwc2_host_complete(hsotg, qtd, 0); + halt_status = DWC2_HC_XFER_URB_COMPLETE; + } else { + halt_status = DWC2_HC_XFER_COMPLETE; + } + + return halt_status; +} + +/* + * 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 dwc2_deactivate_qh(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + int free_qtd) +{ + int continue_split = 0; + struct dwc2_qtd *qtd; + + if (dbg_qh(qh)) + dev_vdbg(hsotg->dev, " %s(%p,%p,%d)\n", __func__, + hsotg, qh, free_qtd); + + if (list_empty(&qh->qtd_list)) { + dev_dbg(hsotg->dev, "## QTD list empty ##\n"); + goto no_qtd; + } + + qtd = list_first_entry(&qh->qtd_list, struct dwc2_qtd, qtd_list_entry); + + if (qtd->complete_split) + continue_split = 1; + else if (qtd->isoc_split_pos == DWC2_HCSPLT_XACTPOS_MID || + qtd->isoc_split_pos == DWC2_HCSPLT_XACTPOS_END) + continue_split = 1; + + if (free_qtd) { + dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); + continue_split = 0; + } + +no_qtd: + qh->channel = NULL; + dwc2_hcd_qh_deactivate(hsotg, qh, continue_split); +} + +/** + * dwc2_release_channel() - Releases a host channel for use by other transfers + * + * @hsotg: The HCD state structure + * @chan: The host channel to release + * @qtd: The QTD associated with the host channel. This QTD may be + * freed if the transfer is complete or an error has occurred. + * @halt_status: Reason the channel is being released. This status + * determines the actions taken by this function. + * + * Also attempts to select and queue more transactions since at least one host + * channel is available. + */ +static void dwc2_release_channel(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + struct dwc2_qtd *qtd, + enum dwc2_halt_status halt_status) +{ + enum dwc2_transaction_type tr_type; + u32 haintmsk; + int free_qtd = 0; + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, " %s: channel %d, halt_status %d\n", + __func__, chan->hc_num, halt_status); + + switch (halt_status) { + case DWC2_HC_XFER_URB_COMPLETE: + free_qtd = 1; + break; + case DWC2_HC_XFER_AHB_ERR: + case DWC2_HC_XFER_STALL: + case DWC2_HC_XFER_BABBLE_ERR: + free_qtd = 1; + break; + case DWC2_HC_XFER_XACT_ERR: + if (qtd && qtd->error_count >= 3) { + dev_vdbg(hsotg->dev, + " Complete URB with transaction error\n"); + free_qtd = 1; + dwc2_host_complete(hsotg, qtd, -EPROTO); + } + break; + case DWC2_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 DWC2_HC_XFER_PERIODIC_INCOMPLETE: + dev_vdbg(hsotg->dev, " Complete URB with I/O error\n"); + free_qtd = 1; + dwc2_host_complete(hsotg, qtd, -EIO); + break; + case DWC2_HC_XFER_NO_HALT_STATUS: + default: + break; + } + + dwc2_deactivate_qh(hsotg, chan->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. + */ + if (!list_empty(&chan->hc_list_entry)) + list_del(&chan->hc_list_entry); + dwc2_hc_cleanup(hsotg, chan); + list_add_tail(&chan->hc_list_entry, &hsotg->free_hc_list); + + if (hsotg->params.uframe_sched) { + hsotg->available_host_channels++; + } else { + switch (chan->ep_type) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + hsotg->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; + } + } + + haintmsk = dwc2_readl(hsotg, HAINTMSK); + haintmsk &= ~(1 << chan->hc_num); + dwc2_writel(hsotg, haintmsk, HAINTMSK); + + /* Try to queue more transfers now that there's a free channel */ + tr_type = dwc2_hcd_select_transactions(hsotg); + if (tr_type != DWC2_TRANSACTION_NONE) + dwc2_hcd_queue_transactions(hsotg, 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 dwc2_halt_channel(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, struct dwc2_qtd *qtd, + enum dwc2_halt_status halt_status) +{ + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + if (hsotg->params.host_dma) { + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "DMA enabled\n"); + dwc2_release_channel(hsotg, chan, qtd, halt_status); + return; + } + + /* Slave mode processing */ + dwc2_hc_halt(hsotg, chan, halt_status); + + if (chan->halt_on_queue) { + u32 gintmsk; + + dev_vdbg(hsotg->dev, "Halt on queue\n"); + if (chan->ep_type == USB_ENDPOINT_XFER_CONTROL || + chan->ep_type == USB_ENDPOINT_XFER_BULK) { + dev_vdbg(hsotg->dev, "control/bulk\n"); + /* + * Make sure the Non-periodic Tx FIFO empty interrupt + * is enabled so that the non-periodic schedule will + * be processed + */ + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk |= GINTSTS_NPTXFEMP; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } else { + dev_vdbg(hsotg->dev, "isoc/intr\n"); + /* + * 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. + */ + list_move_tail(&chan->qh->qh_list_entry, + &hsotg->periodic_sched_assigned); + + /* + * Make sure the Periodic Tx FIFO Empty interrupt is + * enabled so that the periodic schedule will be + * processed + */ + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk |= GINTSTS_PTXFEMP; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } + } +} + +/* + * 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 dwc2_complete_non_periodic_xfer(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, + int chnum, struct dwc2_qtd *qtd, + enum dwc2_halt_status halt_status) +{ + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + qtd->error_count = 0; + + if (chan->hcint & HCINTMSK_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. + */ + dev_vdbg(hsotg->dev, "got NYET\n"); + chan->qh->ping_state = 1; + } + + /* + * 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 dwc2_hcd_queue_transactions, which also enables the + * Tx FIFO Empty interrupt if necessary. + */ + if (chan->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.) + */ + dwc2_halt_channel(hsotg, chan, qtd, halt_status); + } else { + /* + * The channel is automatically disabled by the core for OUT + * transfers in Slave mode + */ + dwc2_release_channel(hsotg, chan, 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 dwc2_complete_periodic_xfer(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd, + enum dwc2_halt_status halt_status) +{ + u32 hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); + + qtd->error_count = 0; + + if (!chan->ep_is_in || (hctsiz & TSIZ_PKTCNT_MASK) == 0) + /* Core halts channel in these cases */ + dwc2_release_channel(hsotg, chan, qtd, halt_status); + else + /* Flush any outstanding requests from the Tx queue */ + dwc2_halt_channel(hsotg, chan, qtd, halt_status); +} + +static int dwc2_xfercomp_isoc_split_in(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ + struct dwc2_hcd_iso_packet_desc *frame_desc; + u32 len; + u32 hctsiz; + u32 pid; + + if (!qtd->urb) + return 0; + + frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index]; + len = dwc2_get_actual_xfer_length(hsotg, chan, chnum, qtd, + DWC2_HC_XFER_COMPLETE, NULL); + if (!len && !qtd->isoc_split_offset) { + qtd->complete_split = 0; + return 0; + } + + frame_desc->actual_length += len; + + if (chan->align_buf) { + dev_vdbg(hsotg->dev, "non-aligned buffer\n"); + dma_unmap_single(hsotg->dev, chan->qh->dw_align_buf_dma, + DWC2_KMEM_UNALIGNED_BUF_SIZE, DMA_FROM_DEVICE); + memcpy(qtd->urb->buf + (chan->xfer_dma - qtd->urb->dma), + chan->qh->dw_align_buf, len); + } + + qtd->isoc_split_offset += len; + + hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); + pid = (hctsiz & TSIZ_SC_MC_PID_MASK) >> TSIZ_SC_MC_PID_SHIFT; + + if (frame_desc->actual_length >= frame_desc->length || pid == 0) { + 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) { + dwc2_host_complete(hsotg, qtd, 0); + dwc2_release_channel(hsotg, chan, qtd, + DWC2_HC_XFER_URB_COMPLETE); + } else { + dwc2_release_channel(hsotg, chan, qtd, + DWC2_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 void dwc2_hc_xfercomp_intr(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ + struct dwc2_hcd_urb *urb = qtd->urb; + enum dwc2_halt_status halt_status = DWC2_HC_XFER_COMPLETE; + int pipe_type; + int urb_xfer_done; + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, + "--Host Channel %d Interrupt: Transfer Complete--\n", + chnum); + + if (!urb) + goto handle_xfercomp_done; + + pipe_type = dwc2_hcd_get_pipe_type(&urb->pipe_info); + + if (hsotg->params.dma_desc_enable) { + dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, halt_status); + if (pipe_type == USB_ENDPOINT_XFER_ISOC) + /* Do not disable the interrupt, just clear it */ + return; + goto handle_xfercomp_done; + } + + /* Handle xfer complete on CSPLIT */ + if (chan->qh->do_split) { + if (chan->ep_type == USB_ENDPOINT_XFER_ISOC && chan->ep_is_in && + hsotg->params.host_dma) { + if (qtd->complete_split && + dwc2_xfercomp_isoc_split_in(hsotg, chan, chnum, + qtd)) + goto handle_xfercomp_done; + } else { + qtd->complete_split = 0; + } + } + + /* Update the QTD and URB states */ + switch (pipe_type) { + case USB_ENDPOINT_XFER_CONTROL: + switch (qtd->control_phase) { + case DWC2_CONTROL_SETUP: + if (urb->length > 0) + qtd->control_phase = DWC2_CONTROL_DATA; + else + qtd->control_phase = DWC2_CONTROL_STATUS; + dev_vdbg(hsotg->dev, + " Control setup transaction done\n"); + halt_status = DWC2_HC_XFER_COMPLETE; + break; + case DWC2_CONTROL_DATA: + urb_xfer_done = dwc2_update_urb_state(hsotg, chan, + chnum, urb, qtd); + if (urb_xfer_done) { + qtd->control_phase = DWC2_CONTROL_STATUS; + dev_vdbg(hsotg->dev, + " Control data transfer done\n"); + } else { + dwc2_hcd_save_data_toggle(hsotg, chan, chnum, + qtd); + } + halt_status = DWC2_HC_XFER_COMPLETE; + break; + case DWC2_CONTROL_STATUS: + dev_vdbg(hsotg->dev, " Control transfer complete\n"); + if (urb->status == -EINPROGRESS) + urb->status = 0; + dwc2_host_complete(hsotg, qtd, urb->status); + halt_status = DWC2_HC_XFER_URB_COMPLETE; + break; + } + + dwc2_complete_non_periodic_xfer(hsotg, chan, chnum, qtd, + halt_status); + break; + case USB_ENDPOINT_XFER_BULK: + dev_vdbg(hsotg->dev, " Bulk transfer complete\n"); + urb_xfer_done = dwc2_update_urb_state(hsotg, chan, chnum, urb, + qtd); + if (urb_xfer_done) { + dwc2_host_complete(hsotg, qtd, urb->status); + halt_status = DWC2_HC_XFER_URB_COMPLETE; + } else { + halt_status = DWC2_HC_XFER_COMPLETE; + } + + dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); + dwc2_complete_non_periodic_xfer(hsotg, chan, chnum, qtd, + halt_status); + break; + case USB_ENDPOINT_XFER_INT: + dev_vdbg(hsotg->dev, " Interrupt transfer complete\n"); + urb_xfer_done = dwc2_update_urb_state(hsotg, chan, chnum, urb, + qtd); + + /* + * Interrupt URB is done on the first transfer complete + * interrupt + */ + if (urb_xfer_done) { + dwc2_host_complete(hsotg, qtd, urb->status); + halt_status = DWC2_HC_XFER_URB_COMPLETE; + } else { + halt_status = DWC2_HC_XFER_COMPLETE; + } + + dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); + dwc2_complete_periodic_xfer(hsotg, chan, chnum, qtd, + halt_status); + break; + case USB_ENDPOINT_XFER_ISOC: + if (dbg_perio()) + dev_vdbg(hsotg->dev, " Isochronous transfer complete\n"); + if (qtd->isoc_split_pos == DWC2_HCSPLT_XACTPOS_ALL) + halt_status = dwc2_update_isoc_urb_state(hsotg, chan, + chnum, qtd, + DWC2_HC_XFER_COMPLETE); + dwc2_complete_periodic_xfer(hsotg, chan, chnum, qtd, + halt_status); + break; + } + +handle_xfercomp_done: + disable_hc_int(hsotg, chnum, HCINTMSK_XFERCOMPL); +} + +/* + * Handles a host channel STALL interrupt. This handler may be called in + * either DMA mode or Slave mode. + */ +static void dwc2_hc_stall_intr(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ + struct dwc2_hcd_urb *urb = qtd->urb; + int pipe_type; + + dev_dbg(hsotg->dev, "--Host Channel %d Interrupt: STALL Received--\n", + chnum); + + if (hsotg->params.dma_desc_enable) { + dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, + DWC2_HC_XFER_STALL); + goto handle_stall_done; + } + + if (!urb) + goto handle_stall_halt; + + pipe_type = dwc2_hcd_get_pipe_type(&urb->pipe_info); + + if (pipe_type == USB_ENDPOINT_XFER_CONTROL) + dwc2_host_complete(hsotg, qtd, -EPIPE); + + if (pipe_type == USB_ENDPOINT_XFER_BULK || + pipe_type == USB_ENDPOINT_XFER_INT) { + dwc2_host_complete(hsotg, qtd, -EPIPE); + /* + * 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. + */ + chan->qh->data_toggle = 0; + } + +handle_stall_halt: + dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_STALL); + +handle_stall_done: + disable_hc_int(hsotg, chnum, HCINTMSK_STALL); +} + +/* + * 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 dwc2_update_urb_state_abn(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_hcd_urb *urb, + struct dwc2_qtd *qtd, + enum dwc2_halt_status halt_status) +{ + u32 xfer_length = dwc2_get_actual_xfer_length(hsotg, chan, chnum, + qtd, halt_status, NULL); + u32 hctsiz; + + if (urb->actual_length + xfer_length > urb->length) { + dev_warn(hsotg->dev, "%s(): trimming xfer length\n", __func__); + xfer_length = urb->length - urb->actual_length; + } + + urb->actual_length += xfer_length; + + hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); + dev_vdbg(hsotg->dev, "DWC_otg: %s: %s, channel %d\n", + __func__, (chan->ep_is_in ? "IN" : "OUT"), chnum); + dev_vdbg(hsotg->dev, " chan->start_pkt_count %d\n", + chan->start_pkt_count); + dev_vdbg(hsotg->dev, " hctsiz.pktcnt %d\n", + (hctsiz & TSIZ_PKTCNT_MASK) >> TSIZ_PKTCNT_SHIFT); + dev_vdbg(hsotg->dev, " chan->max_packet %d\n", chan->max_packet); + dev_vdbg(hsotg->dev, " bytes_transferred %d\n", + xfer_length); + dev_vdbg(hsotg->dev, " urb->actual_length %d\n", + urb->actual_length); + dev_vdbg(hsotg->dev, " urb->transfer_buffer_length %d\n", + urb->length); +} + +/* + * Handles a host channel NAK interrupt. This handler may be called in either + * DMA mode or Slave mode. + */ +static void dwc2_hc_nak_intr(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ + if (!qtd) { + dev_dbg(hsotg->dev, "%s: qtd is NULL\n", __func__); + return; + } + + if (!qtd->urb) { + dev_dbg(hsotg->dev, "%s: qtd->urb is NULL\n", __func__); + return; + } + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "--Host Channel %d Interrupt: NAK Received--\n", + chnum); + + /* + * Handle NAK for IN/OUT SSPLIT/CSPLIT transfers, bulk, control, and + * interrupt. Re-start the SSPLIT transfer. + * + * Normally for non-periodic transfers we'll retry right away, but to + * avoid interrupt storms we'll wait before retrying if we've got + * several NAKs. If we didn't do this we'd retry directly from the + * interrupt handler and could end up quickly getting another + * interrupt (another NAK), which we'd retry. Note that we do not + * delay retries for IN parts of control requests, as those are expected + * to complete fairly quickly, and if we delay them we risk confusing + * the device and cause it issue STALL. + * + * Note that in DMA mode software only gets involved to re-send NAKed + * transfers for split transactions, so we only need to apply this + * delaying logic when handling splits. In non-DMA mode presumably we + * might want a similar delay if someone can demonstrate this problem + * affects that code path too. + */ + if (chan->do_split) { + if (chan->complete_split) + qtd->error_count = 0; + qtd->complete_split = 0; + qtd->num_naks++; + qtd->qh->want_wait = qtd->num_naks >= DWC2_NAKS_BEFORE_DELAY && + !(chan->ep_type == USB_ENDPOINT_XFER_CONTROL && + chan->ep_is_in); + dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_NAK); + goto handle_nak_done; + } + + switch (dwc2_hcd_get_pipe_type(&qtd->urb->pipe_info)) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + if (hsotg->params.host_dma && chan->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; + break; + } + + /* + * 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 (!chan->qh->ping_state) { + dwc2_update_urb_state_abn(hsotg, chan, chnum, qtd->urb, + qtd, DWC2_HC_XFER_NAK); + dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); + + if (chan->speed == USB_SPEED_HIGH) + chan->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 + */ + dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_NAK); + break; + case USB_ENDPOINT_XFER_INT: + qtd->error_count = 0; + dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_NAK); + break; + case USB_ENDPOINT_XFER_ISOC: + /* Should never get called for isochronous transfers */ + dev_err(hsotg->dev, "NACK interrupt for ISOC transfer\n"); + break; + } + +handle_nak_done: + disable_hc_int(hsotg, chnum, HCINTMSK_NAK); +} + +/* + * 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 void dwc2_hc_ack_intr(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ + struct dwc2_hcd_iso_packet_desc *frame_desc; + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "--Host Channel %d Interrupt: ACK Received--\n", + chnum); + + if (chan->do_split) { + /* Handle ACK on SSPLIT. ACK should not occur in CSPLIT. */ + if (!chan->ep_is_in && + chan->data_pid_start != DWC2_HC_PID_SETUP) + qtd->ssplit_out_xfer_count = chan->xfer_len; + + if (chan->ep_type != USB_ENDPOINT_XFER_ISOC || chan->ep_is_in) { + qtd->complete_split = 1; + dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_ACK); + } else { + /* ISOC OUT */ + switch (chan->xact_pos) { + case DWC2_HCSPLT_XACTPOS_ALL: + break; + case DWC2_HCSPLT_XACTPOS_END: + qtd->isoc_split_pos = DWC2_HCSPLT_XACTPOS_ALL; + qtd->isoc_split_offset = 0; + break; + case DWC2_HCSPLT_XACTPOS_BEGIN: + case DWC2_HCSPLT_XACTPOS_MID: + /* + * For BEGIN or MID, calculate the length for + * the next microframe to determine the correct + * SSPLIT token, either MID or END + */ + 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 = + DWC2_HCSPLT_XACTPOS_END; + else + qtd->isoc_split_pos = + DWC2_HCSPLT_XACTPOS_MID; + break; + } + } + } else { + qtd->error_count = 0; + + if (chan->qh->ping_state) { + chan->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. + */ + dwc2_halt_channel(hsotg, chan, qtd, DWC2_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(hsotg, chnum, HCINTMSK_ACK); +} + +/* + * 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 void dwc2_hc_nyet_intr(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "--Host Channel %d Interrupt: NYET Received--\n", + chnum); + + /* + * NYET on CSPLIT + * re-do the CSPLIT immediately on non-periodic + */ + if (chan->do_split && chan->complete_split) { + if (chan->ep_is_in && chan->ep_type == USB_ENDPOINT_XFER_ISOC && + hsotg->params.host_dma) { + qtd->complete_split = 0; + qtd->isoc_split_offset = 0; + qtd->isoc_frame_index++; + if (qtd->urb && + qtd->isoc_frame_index == qtd->urb->packet_count) { + dwc2_host_complete(hsotg, qtd, 0); + dwc2_release_channel(hsotg, chan, qtd, + DWC2_HC_XFER_URB_COMPLETE); + } else { + dwc2_release_channel(hsotg, chan, qtd, + DWC2_HC_XFER_NO_HALT_STATUS); + } + goto handle_nyet_done; + } + + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_ISOC) { + struct dwc2_qh *qh = chan->qh; + bool past_end; + + if (!hsotg->params.uframe_sched) { + int frnum = dwc2_hcd_get_frame_number(hsotg); + + /* Don't have num_hs_transfers; simple logic */ + past_end = dwc2_full_frame_num(frnum) != + dwc2_full_frame_num(qh->next_active_frame); + } else { + int end_frnum; + + /* + * Figure out the end frame based on + * schedule. + * + * We don't want to go on trying again + * and again forever. Let's stop when + * we've done all the transfers that + * were scheduled. + * + * We're going to be comparing + * start_active_frame and + * next_active_frame, both of which + * are 1 before the time the packet + * goes on the wire, so that cancels + * out. Basically if had 1 transfer + * and we saw 1 NYET then we're done. + * We're getting a NYET here so if + * next >= (start + num_transfers) + * we're done. The complexity is that + * for all but ISOC_OUT we skip one + * slot. + */ + end_frnum = dwc2_frame_num_inc( + qh->start_active_frame, + qh->num_hs_transfers); + + if (qh->ep_type != USB_ENDPOINT_XFER_ISOC || + qh->ep_is_in) + end_frnum = + dwc2_frame_num_inc(end_frnum, 1); + + past_end = dwc2_frame_num_le( + end_frnum, qh->next_active_frame); + } + + if (past_end) { + /* 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; + dwc2_halt_channel(hsotg, chan, qtd, + DWC2_HC_XFER_XACT_ERR); + /* Todo: add support for isoc release */ + goto handle_nyet_done; + } + } + + dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_NYET); + goto handle_nyet_done; + } + + chan->qh->ping_state = 1; + qtd->error_count = 0; + + dwc2_update_urb_state_abn(hsotg, chan, chnum, qtd->urb, qtd, + DWC2_HC_XFER_NYET); + dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); + + /* + * Halt the channel and re-start the transfer so the PING protocol + * will start + */ + dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_NYET); + +handle_nyet_done: + disable_hc_int(hsotg, chnum, HCINTMSK_NYET); +} + +/* + * Handles a host channel babble interrupt. This handler may be called in + * either DMA mode or Slave mode. + */ +static void dwc2_hc_babble_intr(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ + dev_dbg(hsotg->dev, "--Host Channel %d Interrupt: Babble Error--\n", + chnum); + + dwc2_hc_handle_tt_clear(hsotg, chan, qtd); + + if (hsotg->params.dma_desc_enable) { + dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, + DWC2_HC_XFER_BABBLE_ERR); + goto disable_int; + } + + if (chan->ep_type != USB_ENDPOINT_XFER_ISOC) { + dwc2_host_complete(hsotg, qtd, -EOVERFLOW); + dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_BABBLE_ERR); + } else { + enum dwc2_halt_status halt_status; + + halt_status = dwc2_update_isoc_urb_state(hsotg, chan, chnum, + qtd, DWC2_HC_XFER_BABBLE_ERR); + dwc2_halt_channel(hsotg, chan, qtd, halt_status); + } + +disable_int: + disable_hc_int(hsotg, chnum, HCINTMSK_BBLERR); +} + +/* + * Handles a host channel AHB error interrupt. This handler is only called in + * DMA mode. + */ +static void dwc2_hc_ahberr_intr(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ + struct dwc2_hcd_urb *urb = qtd->urb; + char *pipetype, *speed; + u32 hcchar; + u32 hcsplt; + u32 hctsiz; + u32 hc_dma; + + dev_dbg(hsotg->dev, "--Host Channel %d Interrupt: AHB Error--\n", + chnum); + + if (!urb) + goto handle_ahberr_halt; + + dwc2_hc_handle_tt_clear(hsotg, chan, qtd); + + hcchar = dwc2_readl(hsotg, HCCHAR(chnum)); + hcsplt = dwc2_readl(hsotg, HCSPLT(chnum)); + hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); + hc_dma = dwc2_readl(hsotg, HCDMA(chnum)); + + dev_err(hsotg->dev, "AHB ERROR, Channel %d\n", chnum); + dev_err(hsotg->dev, " hcchar 0x%08x, hcsplt 0x%08x\n", hcchar, hcsplt); + dev_err(hsotg->dev, " hctsiz 0x%08x, hc_dma 0x%08x\n", hctsiz, hc_dma); + dev_err(hsotg->dev, " Device address: %d\n", + dwc2_hcd_get_dev_addr(&urb->pipe_info)); + dev_err(hsotg->dev, " Endpoint: %d, %s\n", + dwc2_hcd_get_ep_num(&urb->pipe_info), + dwc2_hcd_is_pipe_in(&urb->pipe_info) ? "IN" : "OUT"); + + switch (dwc2_hcd_get_pipe_type(&urb->pipe_info)) { + case USB_ENDPOINT_XFER_CONTROL: + pipetype = "CONTROL"; + break; + case USB_ENDPOINT_XFER_BULK: + pipetype = "BULK"; + break; + case USB_ENDPOINT_XFER_INT: + pipetype = "INTERRUPT"; + break; + case USB_ENDPOINT_XFER_ISOC: + pipetype = "ISOCHRONOUS"; + break; + default: + pipetype = "UNKNOWN"; + break; + } + + dev_err(hsotg->dev, " Endpoint type: %s\n", pipetype); + + switch (chan->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; + } + + dev_err(hsotg->dev, " Speed: %s\n", speed); + + dev_err(hsotg->dev, " Max packet size: %d (mult %d)\n", + dwc2_hcd_get_maxp(&urb->pipe_info), + dwc2_hcd_get_maxp_mult(&urb->pipe_info)); + dev_err(hsotg->dev, " Data buffer length: %d\n", urb->length); + dev_err(hsotg->dev, " Transfer buffer: %p, Transfer DMA: %08lx\n", + urb->buf, (unsigned long)urb->dma); + dev_err(hsotg->dev, " Setup buffer: %p, Setup DMA: %08lx\n", + urb->setup_packet, (unsigned long)urb->setup_dma); + dev_err(hsotg->dev, " Interval: %d\n", urb->interval); + + /* Core halts the channel for Descriptor DMA mode */ + if (hsotg->params.dma_desc_enable) { + dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, + DWC2_HC_XFER_AHB_ERR); + goto handle_ahberr_done; + } + + dwc2_host_complete(hsotg, qtd, -EIO); + +handle_ahberr_halt: + /* + * Force a channel halt. Don't call dwc2_halt_channel because that won't + * write to the HCCHARn register in DMA mode to force the halt. + */ + dwc2_hc_halt(hsotg, chan, DWC2_HC_XFER_AHB_ERR); + +handle_ahberr_done: + disable_hc_int(hsotg, chnum, HCINTMSK_AHBERR); +} + +/* + * Handles a host channel transaction error interrupt. This handler may be + * called in either DMA mode or Slave mode. + */ +static void dwc2_hc_xacterr_intr(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ + dev_dbg(hsotg->dev, + "--Host Channel %d Interrupt: Transaction Error--\n", chnum); + + dwc2_hc_handle_tt_clear(hsotg, chan, qtd); + + if (hsotg->params.dma_desc_enable) { + dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, + DWC2_HC_XFER_XACT_ERR); + goto handle_xacterr_done; + } + + switch (dwc2_hcd_get_pipe_type(&qtd->urb->pipe_info)) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + qtd->error_count++; + if (!chan->qh->ping_state) { + dwc2_update_urb_state_abn(hsotg, chan, chnum, qtd->urb, + qtd, DWC2_HC_XFER_XACT_ERR); + dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); + if (!chan->ep_is_in && chan->speed == USB_SPEED_HIGH) + chan->qh->ping_state = 1; + } + + /* + * Halt the channel so the transfer can be re-started from + * the appropriate point or the PING protocol will start + */ + dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_XACT_ERR); + break; + case USB_ENDPOINT_XFER_INT: + qtd->error_count++; + if (chan->do_split && chan->complete_split) + qtd->complete_split = 0; + dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_XACT_ERR); + break; + case USB_ENDPOINT_XFER_ISOC: + { + enum dwc2_halt_status halt_status; + + halt_status = dwc2_update_isoc_urb_state(hsotg, chan, + chnum, qtd, DWC2_HC_XFER_XACT_ERR); + dwc2_halt_channel(hsotg, chan, qtd, halt_status); + } + break; + } + +handle_xacterr_done: + disable_hc_int(hsotg, chnum, HCINTMSK_XACTERR); +} + +/* + * Handles a host channel frame overrun interrupt. This handler may be called + * in either DMA mode or Slave mode. + */ +static void dwc2_hc_frmovrun_intr(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ + enum dwc2_halt_status halt_status; + + if (dbg_hc(chan)) + dev_dbg(hsotg->dev, "--Host Channel %d Interrupt: Frame Overrun--\n", + chnum); + + dwc2_hc_handle_tt_clear(hsotg, chan, qtd); + + switch (dwc2_hcd_get_pipe_type(&qtd->urb->pipe_info)) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + break; + case USB_ENDPOINT_XFER_INT: + dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_FRAME_OVERRUN); + break; + case USB_ENDPOINT_XFER_ISOC: + halt_status = dwc2_update_isoc_urb_state(hsotg, chan, chnum, + qtd, DWC2_HC_XFER_FRAME_OVERRUN); + dwc2_halt_channel(hsotg, chan, qtd, halt_status); + break; + } + + disable_hc_int(hsotg, chnum, HCINTMSK_FRMOVRUN); +} + +/* + * Handles a host channel data toggle error interrupt. This handler may be + * called in either DMA mode or Slave mode. + */ +static void dwc2_hc_datatglerr_intr(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ + dev_dbg(hsotg->dev, + "--Host Channel %d Interrupt: Data Toggle Error--\n", chnum); + + if (chan->ep_is_in) + qtd->error_count = 0; + else + dev_err(hsotg->dev, + "Data Toggle Error on OUT transfer, channel %d\n", + chnum); + + dwc2_hc_handle_tt_clear(hsotg, chan, qtd); + disable_hc_int(hsotg, chnum, HCINTMSK_DATATGLERR); +} + +/* + * 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: true if halt status is ok, false otherwise + */ +static bool dwc2_halt_status_ok(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ +#ifdef DEBUG + u32 hcchar; + u32 hctsiz; + u32 hcintmsk; + u32 hcsplt; + + if (chan->halt_status == DWC2_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 = dwc2_readl(hsotg, HCCHAR(chnum)); + hctsiz = dwc2_readl(hsotg, HCTSIZ(chnum)); + hcintmsk = dwc2_readl(hsotg, HCINTMSK(chnum)); + hcsplt = dwc2_readl(hsotg, HCSPLT(chnum)); + dev_dbg(hsotg->dev, + "%s: chan->halt_status DWC2_HC_XFER_NO_HALT_STATUS,\n", + __func__); + dev_dbg(hsotg->dev, + "channel %d, hcchar 0x%08x, hctsiz 0x%08x,\n", + chnum, hcchar, hctsiz); + dev_dbg(hsotg->dev, + "hcint 0x%08x, hcintmsk 0x%08x, hcsplt 0x%08x,\n", + chan->hcint, hcintmsk, hcsplt); + if (qtd) + dev_dbg(hsotg->dev, "qtd->complete_split %d\n", + qtd->complete_split); + dev_warn(hsotg->dev, + "%s: no halt status, channel %d, ignoring interrupt\n", + __func__, chnum); + return false; + } + + /* + * 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 = dwc2_readl(hsotg, HCCHAR(chnum)); + if (hcchar & HCCHAR_CHDIS) { + dev_warn(hsotg->dev, + "%s: hcchar.chdis set unexpectedly, hcchar 0x%08x, trying to halt again\n", + __func__, hcchar); + chan->halt_pending = 0; + dwc2_halt_channel(hsotg, chan, qtd, chan->halt_status); + return false; + } +#endif + + return true; +} + +/* + * Handles a host Channel Halted interrupt in DMA mode. This handler + * determines the reason the channel halted and proceeds accordingly. + */ +static void dwc2_hc_chhltd_intr_dma(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ + u32 hcintmsk; + int out_nak_enh = 0; + + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, + "--Host Channel %d Interrupt: DMA Channel Halted--\n", + chnum); + + /* + * For core with OUT NAK enhancement, the flow for high-speed + * CONTROL/BULK OUT is handled a little differently + */ + if (hsotg->hw_params.snpsid >= DWC2_CORE_REV_2_71a) { + if (chan->speed == USB_SPEED_HIGH && !chan->ep_is_in && + (chan->ep_type == USB_ENDPOINT_XFER_CONTROL || + chan->ep_type == USB_ENDPOINT_XFER_BULK)) { + out_nak_enh = 1; + } + } + + if (chan->halt_status == DWC2_HC_XFER_URB_DEQUEUE || + (chan->halt_status == DWC2_HC_XFER_AHB_ERR && + !hsotg->params.dma_desc_enable)) { + if (hsotg->params.dma_desc_enable) + dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, + chan->halt_status); + else + /* + * 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. + */ + dwc2_release_channel(hsotg, chan, qtd, + chan->halt_status); + return; + } + + hcintmsk = dwc2_readl(hsotg, HCINTMSK(chnum)); + + if (chan->hcint & HCINTMSK_XFERCOMPL) { + /* + * 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 (chan->ep_type == USB_ENDPOINT_XFER_ISOC && !chan->ep_is_in) + dwc2_hc_ack_intr(hsotg, chan, chnum, qtd); + dwc2_hc_xfercomp_intr(hsotg, chan, chnum, qtd); + } else if (chan->hcint & HCINTMSK_STALL) { + dwc2_hc_stall_intr(hsotg, chan, chnum, qtd); + } else if ((chan->hcint & HCINTMSK_XACTERR) && + !hsotg->params.dma_desc_enable) { + if (out_nak_enh) { + if (chan->hcint & + (HCINTMSK_NYET | HCINTMSK_NAK | HCINTMSK_ACK)) { + dev_vdbg(hsotg->dev, + "XactErr with NYET/NAK/ACK\n"); + qtd->error_count = 0; + } else { + dev_vdbg(hsotg->dev, + "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. + */ + dwc2_hc_xacterr_intr(hsotg, chan, chnum, qtd); + } else if ((chan->hcint & HCINTMSK_XCS_XACT) && + hsotg->params.dma_desc_enable) { + dwc2_hc_xacterr_intr(hsotg, chan, chnum, qtd); + } else if ((chan->hcint & HCINTMSK_AHBERR) && + hsotg->params.dma_desc_enable) { + dwc2_hc_ahberr_intr(hsotg, chan, chnum, qtd); + } else if (chan->hcint & HCINTMSK_BBLERR) { + dwc2_hc_babble_intr(hsotg, chan, chnum, qtd); + } else if (chan->hcint & HCINTMSK_FRMOVRUN) { + dwc2_hc_frmovrun_intr(hsotg, chan, chnum, qtd); + } else if (!out_nak_enh) { + if (chan->hcint & HCINTMSK_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. + */ + dwc2_hc_nyet_intr(hsotg, chan, chnum, qtd); + } else if ((chan->hcint & HCINTMSK_NAK) && + !(hcintmsk & HCINTMSK_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. + */ + dwc2_hc_nak_intr(hsotg, chan, chnum, qtd); + } else if ((chan->hcint & HCINTMSK_ACK) && + !(hcintmsk & HCINTMSK_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. + */ + dwc2_hc_ack_intr(hsotg, chan, chnum, qtd); + } else { + if (chan->ep_type == USB_ENDPOINT_XFER_INT || + chan->ep_type == USB_ENDPOINT_XFER_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. + */ + dev_dbg(hsotg->dev, + "%s: Halt channel %d (assume incomplete periodic transfer)\n", + __func__, chnum); + dwc2_halt_channel(hsotg, chan, qtd, + DWC2_HC_XFER_PERIODIC_INCOMPLETE); + } else { + dev_err(hsotg->dev, + "%s: Channel %d - ChHltd set, but reason is unknown\n", + __func__, chnum); + dev_err(hsotg->dev, + "hcint 0x%08x, intsts 0x%08x\n", + chan->hcint, + dwc2_readl(hsotg, GINTSTS)); + goto error; + } + } + } else { + dev_info(hsotg->dev, + "NYET/NAK/ACK/other in non-error case, 0x%08x\n", + chan->hcint); +error: + /* Failthrough: use 3-strikes rule */ + qtd->error_count++; + dwc2_update_urb_state_abn(hsotg, chan, chnum, qtd->urb, + qtd, DWC2_HC_XFER_XACT_ERR); + /* + * We can get here after a completed transaction + * (urb->actual_length >= urb->length) which was not reported + * as completed. If that is the case, and we do not abort + * the transfer, a transfer of size 0 will be enqueued + * subsequently. If urb->actual_length is not DMA-aligned, + * the buffer will then point to an unaligned address, and + * the resulting behavior is undefined. Bail out in that + * situation. + */ + if (qtd->urb->actual_length >= qtd->urb->length) + qtd->error_count = 3; + dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); + dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_XACT_ERR); + } +} + +/* + * 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 void dwc2_hc_chhltd_intr(struct dwc2_hsotg *hsotg, + struct dwc2_host_chan *chan, int chnum, + struct dwc2_qtd *qtd) +{ + if (dbg_hc(chan)) + dev_vdbg(hsotg->dev, "--Host Channel %d Interrupt: Channel Halted--\n", + chnum); + + if (hsotg->params.host_dma) { + dwc2_hc_chhltd_intr_dma(hsotg, chan, chnum, qtd); + } else { + if (!dwc2_halt_status_ok(hsotg, chan, chnum, qtd)) + return; + dwc2_release_channel(hsotg, chan, qtd, chan->halt_status); + } +} + +/* + * Check if the given qtd is still the top of the list (and thus valid). + * + * If dwc2_hcd_qtd_unlink_and_free() has been called since we grabbed + * the qtd from the top of the list, this will return false (otherwise true). + */ +static bool dwc2_check_qtd_still_ok(struct dwc2_qtd *qtd, struct dwc2_qh *qh) +{ + struct dwc2_qtd *cur_head; + + if (!qh) + return false; + + cur_head = list_first_entry(&qh->qtd_list, struct dwc2_qtd, + qtd_list_entry); + return (cur_head == qtd); +} + +/* Handles interrupt for a specific Host Channel */ +static void dwc2_hc_n_intr(struct dwc2_hsotg *hsotg, int chnum) +{ + struct dwc2_qtd *qtd; + struct dwc2_host_chan *chan; + u32 hcint, hcintraw, hcintmsk; + + chan = hsotg->hc_ptr_array[chnum]; + + hcintraw = dwc2_readl(hsotg, HCINT(chnum)); + hcintmsk = dwc2_readl(hsotg, HCINTMSK(chnum)); + hcint = hcintraw & hcintmsk; + dwc2_writel(hsotg, hcint, HCINT(chnum)); + + if (!chan) { + dev_err(hsotg->dev, "## hc_ptr_array for channel is NULL ##\n"); + return; + } + + if (dbg_hc(chan)) { + dev_vdbg(hsotg->dev, "--Host Channel Interrupt--, Channel %d\n", + chnum); + dev_vdbg(hsotg->dev, + " hcint 0x%08x, hcintmsk 0x%08x, hcint&hcintmsk 0x%08x\n", + hcintraw, hcintmsk, hcint); + } + + /* + * If we got an interrupt after someone called + * dwc2_hcd_endpoint_disable() we don't want to crash below + */ + if (!chan->qh) { + dev_warn(hsotg->dev, "Interrupt on disabled channel\n"); + return; + } + + chan->hcint = hcintraw; + + /* + * If the channel was halted due to a dequeue, the qtd list might + * be empty or at least the first entry will not be the active qtd. + * In this case, take a shortcut and just release the channel. + */ + if (chan->halt_status == DWC2_HC_XFER_URB_DEQUEUE) { + /* + * If the channel was halted, this should be the only + * interrupt unmasked + */ + WARN_ON(hcint != HCINTMSK_CHHLTD); + if (hsotg->params.dma_desc_enable) + dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, + chan->halt_status); + else + dwc2_release_channel(hsotg, chan, NULL, + chan->halt_status); + return; + } + + if (list_empty(&chan->qh->qtd_list)) { + /* + * TODO: Will this ever happen with the + * DWC2_HC_XFER_URB_DEQUEUE handling above? + */ + dev_dbg(hsotg->dev, "## no QTD queued for channel %d ##\n", + chnum); + dev_dbg(hsotg->dev, + " hcint 0x%08x, hcintmsk 0x%08x, hcint&hcintmsk 0x%08x\n", + chan->hcint, hcintmsk, hcint); + chan->halt_status = DWC2_HC_XFER_NO_HALT_STATUS; + disable_hc_int(hsotg, chnum, HCINTMSK_CHHLTD); + chan->hcint = 0; + return; + } + + qtd = list_first_entry(&chan->qh->qtd_list, struct dwc2_qtd, + qtd_list_entry); + + if (!hsotg->params.host_dma) { + if ((hcint & HCINTMSK_CHHLTD) && hcint != HCINTMSK_CHHLTD) + hcint &= ~HCINTMSK_CHHLTD; + } + + if (hcint & HCINTMSK_XFERCOMPL) { + dwc2_hc_xfercomp_intr(hsotg, chan, chnum, 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 &= ~HCINTMSK_NYET; + } + + if (hcint & HCINTMSK_CHHLTD) { + dwc2_hc_chhltd_intr(hsotg, chan, chnum, qtd); + if (!dwc2_check_qtd_still_ok(qtd, chan->qh)) + goto exit; + } + if (hcint & HCINTMSK_AHBERR) { + dwc2_hc_ahberr_intr(hsotg, chan, chnum, qtd); + if (!dwc2_check_qtd_still_ok(qtd, chan->qh)) + goto exit; + } + if (hcint & HCINTMSK_STALL) { + dwc2_hc_stall_intr(hsotg, chan, chnum, qtd); + if (!dwc2_check_qtd_still_ok(qtd, chan->qh)) + goto exit; + } + if (hcint & HCINTMSK_NAK) { + dwc2_hc_nak_intr(hsotg, chan, chnum, qtd); + if (!dwc2_check_qtd_still_ok(qtd, chan->qh)) + goto exit; + } + if (hcint & HCINTMSK_ACK) { + dwc2_hc_ack_intr(hsotg, chan, chnum, qtd); + if (!dwc2_check_qtd_still_ok(qtd, chan->qh)) + goto exit; + } + if (hcint & HCINTMSK_NYET) { + dwc2_hc_nyet_intr(hsotg, chan, chnum, qtd); + if (!dwc2_check_qtd_still_ok(qtd, chan->qh)) + goto exit; + } + if (hcint & HCINTMSK_XACTERR) { + dwc2_hc_xacterr_intr(hsotg, chan, chnum, qtd); + if (!dwc2_check_qtd_still_ok(qtd, chan->qh)) + goto exit; + } + if (hcint & HCINTMSK_BBLERR) { + dwc2_hc_babble_intr(hsotg, chan, chnum, qtd); + if (!dwc2_check_qtd_still_ok(qtd, chan->qh)) + goto exit; + } + if (hcint & HCINTMSK_FRMOVRUN) { + dwc2_hc_frmovrun_intr(hsotg, chan, chnum, qtd); + if (!dwc2_check_qtd_still_ok(qtd, chan->qh)) + goto exit; + } + if (hcint & HCINTMSK_DATATGLERR) { + dwc2_hc_datatglerr_intr(hsotg, chan, chnum, qtd); + if (!dwc2_check_qtd_still_ok(qtd, chan->qh)) + goto exit; + } + +exit: + chan->hcint = 0; +} + +/* + * 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. + */ +static void dwc2_hc_intr(struct dwc2_hsotg *hsotg) +{ + u32 haint; + int i; + struct dwc2_host_chan *chan, *chan_tmp; + + haint = dwc2_readl(hsotg, HAINT); + if (dbg_perio()) { + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + dev_vdbg(hsotg->dev, "HAINT=%08x\n", haint); + } + + /* + * According to USB 2.0 spec section 11.18.8, a host must + * issue complete-split transactions in a microframe for a + * set of full-/low-speed endpoints in the same relative + * order as the start-splits were issued in a microframe for. + */ + list_for_each_entry_safe(chan, chan_tmp, &hsotg->split_order, + split_order_list_entry) { + int hc_num = chan->hc_num; + + if (haint & (1 << hc_num)) { + dwc2_hc_n_intr(hsotg, hc_num); + haint &= ~(1 << hc_num); + } + } + + for (i = 0; i < hsotg->params.host_channels; i++) { + if (haint & (1 << i)) + dwc2_hc_n_intr(hsotg, i); + } +} + +/* This function handles interrupts for the HCD */ +irqreturn_t dwc2_handle_hcd_intr(struct dwc2_hsotg *hsotg) +{ + u32 gintsts, dbg_gintsts; + irqreturn_t retval = IRQ_HANDLED; + + if (!dwc2_is_controller_alive(hsotg)) { + dev_warn(hsotg->dev, "Controller is dead\n"); + return retval; + } else { + retval = IRQ_NONE; + } + + spin_lock(&hsotg->lock); + + /* Check if HOST Mode */ + if (dwc2_is_host_mode(hsotg)) { + gintsts = dwc2_read_core_intr(hsotg); + if (!gintsts) { + spin_unlock(&hsotg->lock); + return retval; + } + + retval = IRQ_HANDLED; + + dbg_gintsts = gintsts; +#ifndef DEBUG_SOF + dbg_gintsts &= ~GINTSTS_SOF; +#endif + if (!dbg_perio()) + dbg_gintsts &= ~(GINTSTS_HCHINT | GINTSTS_RXFLVL | + GINTSTS_PTXFEMP); + + /* Only print if there are any non-suppressed interrupts left */ + if (dbg_gintsts) + dev_vdbg(hsotg->dev, + "DWC OTG HCD Interrupt Detected gintsts&gintmsk=0x%08x\n", + gintsts); + + if (gintsts & GINTSTS_SOF) + dwc2_sof_intr(hsotg); + if (gintsts & GINTSTS_RXFLVL) + dwc2_rx_fifo_level_intr(hsotg); + if (gintsts & GINTSTS_NPTXFEMP) + dwc2_np_tx_fifo_empty_intr(hsotg); + if (gintsts & GINTSTS_PRTINT) + dwc2_port_intr(hsotg); + if (gintsts & GINTSTS_HCHINT) + dwc2_hc_intr(hsotg); + if (gintsts & GINTSTS_PTXFEMP) + dwc2_perio_tx_fifo_empty_intr(hsotg); + + if (dbg_gintsts) { + dev_vdbg(hsotg->dev, + "DWC OTG HCD Finished Servicing Interrupts\n"); + dev_vdbg(hsotg->dev, + "DWC OTG HCD gintsts=0x%08x gintmsk=0x%08x\n", + dwc2_readl(hsotg, GINTSTS), + dwc2_readl(hsotg, GINTMSK)); + } + } + + spin_unlock(&hsotg->lock); + + return retval; +} diff --git a/drivers/usb/dwc2/hcd_queue.c b/drivers/usb/dwc2/hcd_queue.c new file mode 100644 index 000000000..4f00ff1dc --- /dev/null +++ b/drivers/usb/dwc2/hcd_queue.c @@ -0,0 +1,2032 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * hcd_queue.c - DesignWare HS OTG Controller host queuing routines + * + * Copyright (C) 2004-2013 Synopsys, Inc. + */ + +/* + * This file contains the functions to manage Queue Heads and Queue + * Transfer Descriptors for Host mode + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "core.h" +#include "hcd.h" + +/* Wait this long before releasing periodic reservation */ +#define DWC2_UNRESERVE_DELAY (msecs_to_jiffies(5)) + +/* If we get a NAK, wait this long before retrying */ +#define DWC2_RETRY_WAIT_DELAY (1 * NSEC_PER_MSEC) + +/** + * dwc2_periodic_channel_available() - Checks that a channel is available for a + * periodic transfer + * + * @hsotg: The HCD state structure for the DWC OTG controller + * + * Return: 0 if successful, negative error code otherwise + */ +static int dwc2_periodic_channel_available(struct dwc2_hsotg *hsotg) +{ + /* + * Currently assuming that there is a dedicated host channel for + * each periodic transaction plus at least one host channel for + * non-periodic transactions + */ + int status; + int num_channels; + + num_channels = hsotg->params.host_channels; + if ((hsotg->periodic_channels + hsotg->non_periodic_channels < + num_channels) && (hsotg->periodic_channels < num_channels - 1)) { + status = 0; + } else { + dev_dbg(hsotg->dev, + "%s: Total channels: %d, Periodic: %d, Non-periodic: %d\n", + __func__, num_channels, + hsotg->periodic_channels, hsotg->non_periodic_channels); + status = -ENOSPC; + } + + return status; +} + +/** + * dwc2_check_periodic_bandwidth() - Checks that there is sufficient bandwidth + * for the specified QH in the periodic schedule + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: QH containing periodic bandwidth required + * + * Return: 0 if successful, negative error code otherwise + * + * For simplicity, this calculation assumes that all the transfers in the + * periodic schedule may occur in the same (micro)frame + */ +static int dwc2_check_periodic_bandwidth(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + int status; + s16 max_claimed_usecs; + + status = 0; + + if (qh->dev_speed == USB_SPEED_HIGH || qh->do_split) { + /* + * High speed mode + * Max periodic usecs is 80% x 125 usec = 100 usec + */ + max_claimed_usecs = 100 - qh->host_us; + } else { + /* + * Full speed mode + * Max periodic usecs is 90% x 1000 usec = 900 usec + */ + max_claimed_usecs = 900 - qh->host_us; + } + + if (hsotg->periodic_usecs > max_claimed_usecs) { + dev_err(hsotg->dev, + "%s: already claimed usecs %d, required usecs %d\n", + __func__, hsotg->periodic_usecs, qh->host_us); + status = -ENOSPC; + } + + return status; +} + +/** + * pmap_schedule() - Schedule time in a periodic bitmap (pmap). + * + * @map: The bitmap representing the schedule; will be updated + * upon success. + * @bits_per_period: The schedule represents several periods. This is how many + * bits are in each period. It's assumed that the beginning + * of the schedule will repeat after its end. + * @periods_in_map: The number of periods in the schedule. + * @num_bits: The number of bits we need per period we want to reserve + * in this function call. + * @interval: How often we need to be scheduled for the reservation this + * time. 1 means every period. 2 means every other period. + * ...you get the picture? + * @start: The bit number to start at. Normally 0. Must be within + * the interval or we return failure right away. + * @only_one_period: Normally we'll allow picking a start anywhere within the + * first interval, since we can still make all repetition + * requirements by doing that. However, if you pass true + * here then we'll return failure if we can't fit within + * the period that "start" is in. + * + * The idea here is that we want to schedule time for repeating events that all + * want the same resource. The resource is divided into fixed-sized periods + * and the events want to repeat every "interval" periods. The schedule + * granularity is one bit. + * + * To keep things "simple", we'll represent our schedule with a bitmap that + * contains a fixed number of periods. This gets rid of a lot of complexity + * but does mean that we need to handle things specially (and non-ideally) if + * the number of the periods in the schedule doesn't match well with the + * intervals that we're trying to schedule. + * + * Here's an explanation of the scheme we'll implement, assuming 8 periods. + * - If interval is 1, we need to take up space in each of the 8 + * periods we're scheduling. Easy. + * - If interval is 2, we need to take up space in half of the + * periods. Again, easy. + * - If interval is 3, we actually need to fall back to interval 1. + * Why? Because we might need time in any period. AKA for the + * first 8 periods, we'll be in slot 0, 3, 6. Then we'll be + * in slot 1, 4, 7. Then we'll be in 2, 5. Then we'll be back to + * 0, 3, and 6. Since we could be in any frame we need to reserve + * for all of them. Sucks, but that's what you gotta do. Note that + * if we were instead scheduling 8 * 3 = 24 we'd do much better, but + * then we need more memory and time to do scheduling. + * - If interval is 4, easy. + * - If interval is 5, we again need interval 1. The schedule will be + * 0, 5, 2, 7, 4, 1, 6, 3, 0 + * - If interval is 6, we need interval 2. 0, 6, 4, 2. + * - If interval is 7, we need interval 1. + * - If interval is 8, we need interval 8. + * + * If you do the math, you'll see that we need to pretend that interval is + * equal to the greatest_common_divisor(interval, periods_in_map). + * + * Note that at the moment this function tends to front-pack the schedule. + * In some cases that's really non-ideal (it's hard to schedule things that + * need to repeat every period). In other cases it's perfect (you can easily + * schedule bigger, less often repeating things). + * + * Here's the algorithm in action (8 periods, 5 bits per period): + * |** | |** | |** | |** | | OK 2 bits, intv 2 at 0 + * |*****| ***|*****| ***|*****| ***|*****| ***| OK 3 bits, intv 3 at 2 + * |*****|* ***|*****| ***|*****|* ***|*****| ***| OK 1 bits, intv 4 at 5 + * |** |* |** | |** |* |** | | Remv 3 bits, intv 3 at 2 + * |*** |* |*** | |*** |* |*** | | OK 1 bits, intv 6 at 2 + * |**** |* * |**** | * |**** |* * |**** | * | OK 1 bits, intv 1 at 3 + * |**** |**** |**** | *** |**** |**** |**** | *** | OK 2 bits, intv 2 at 6 + * |*****|*****|*****| ****|*****|*****|*****| ****| OK 1 bits, intv 1 at 4 + * |*****|*****|*****| ****|*****|*****|*****| ****| FAIL 1 bits, intv 1 + * | ***|*****| ***| ****| ***|*****| ***| ****| Remv 2 bits, intv 2 at 0 + * | ***| ****| ***| ****| ***| ****| ***| ****| Remv 1 bits, intv 4 at 5 + * | **| ****| **| ****| **| ****| **| ****| Remv 1 bits, intv 6 at 2 + * | *| ** *| *| ** *| *| ** *| *| ** *| Remv 1 bits, intv 1 at 3 + * | *| *| *| *| *| *| *| *| Remv 2 bits, intv 2 at 6 + * | | | | | | | | | Remv 1 bits, intv 1 at 4 + * |** | |** | |** | |** | | OK 2 bits, intv 2 at 0 + * |*** | |** | |*** | |** | | OK 1 bits, intv 4 at 2 + * |*****| |** **| |*****| |** **| | OK 2 bits, intv 2 at 3 + * |*****|* |** **| |*****|* |** **| | OK 1 bits, intv 4 at 5 + * |*****|*** |** **| ** |*****|*** |** **| ** | OK 2 bits, intv 2 at 6 + * |*****|*****|** **| ****|*****|*****|** **| ****| OK 2 bits, intv 2 at 8 + * |*****|*****|*****| ****|*****|*****|*****| ****| OK 1 bits, intv 4 at 12 + * + * This function is pretty generic and could be easily abstracted if anything + * needed similar scheduling. + * + * Returns either -ENOSPC or a >= 0 start bit which should be passed to the + * unschedule routine. The map bitmap will be updated on a non-error result. + */ +static int pmap_schedule(unsigned long *map, int bits_per_period, + int periods_in_map, int num_bits, + int interval, int start, bool only_one_period) +{ + int interval_bits; + int to_reserve; + int first_end; + int i; + + if (num_bits > bits_per_period) + return -ENOSPC; + + /* Adjust interval as per description */ + interval = gcd(interval, periods_in_map); + + interval_bits = bits_per_period * interval; + to_reserve = periods_in_map / interval; + + /* If start has gotten us past interval then we can't schedule */ + if (start >= interval_bits) + return -ENOSPC; + + if (only_one_period) + /* Must fit within same period as start; end at begin of next */ + first_end = (start / bits_per_period + 1) * bits_per_period; + else + /* Can fit anywhere in the first interval */ + first_end = interval_bits; + + /* + * We'll try to pick the first repetition, then see if that time + * is free for each of the subsequent repetitions. If it's not + * we'll adjust the start time for the next search of the first + * repetition. + */ + while (start + num_bits <= first_end) { + int end; + + /* Need to stay within this period */ + end = (start / bits_per_period + 1) * bits_per_period; + + /* Look for num_bits us in this microframe starting at start */ + start = bitmap_find_next_zero_area(map, end, start, num_bits, + 0); + + /* + * We should get start >= end if we fail. We might be + * able to check the next microframe depending on the + * interval, so continue on (start already updated). + */ + if (start >= end) { + start = end; + continue; + } + + /* At this point we have a valid point for first one */ + for (i = 1; i < to_reserve; i++) { + int ith_start = start + interval_bits * i; + int ith_end = end + interval_bits * i; + int ret; + + /* Use this as a dumb "check if bits are 0" */ + ret = bitmap_find_next_zero_area( + map, ith_start + num_bits, ith_start, num_bits, + 0); + + /* We got the right place, continue checking */ + if (ret == ith_start) + continue; + + /* Move start up for next time and exit for loop */ + ith_start = bitmap_find_next_zero_area( + map, ith_end, ith_start, num_bits, 0); + if (ith_start >= ith_end) + /* Need a while new period next time */ + start = end; + else + start = ith_start - interval_bits * i; + break; + } + + /* If didn't exit the for loop with a break, we have success */ + if (i == to_reserve) + break; + } + + if (start + num_bits > first_end) + return -ENOSPC; + + for (i = 0; i < to_reserve; i++) { + int ith_start = start + interval_bits * i; + + bitmap_set(map, ith_start, num_bits); + } + + return start; +} + +/** + * pmap_unschedule() - Undo work done by pmap_schedule() + * + * @map: See pmap_schedule(). + * @bits_per_period: See pmap_schedule(). + * @periods_in_map: See pmap_schedule(). + * @num_bits: The number of bits that was passed to schedule. + * @interval: The interval that was passed to schedule. + * @start: The return value from pmap_schedule(). + */ +static void pmap_unschedule(unsigned long *map, int bits_per_period, + int periods_in_map, int num_bits, + int interval, int start) +{ + int interval_bits; + int to_release; + int i; + + /* Adjust interval as per description in pmap_schedule() */ + interval = gcd(interval, periods_in_map); + + interval_bits = bits_per_period * interval; + to_release = periods_in_map / interval; + + for (i = 0; i < to_release; i++) { + int ith_start = start + interval_bits * i; + + bitmap_clear(map, ith_start, num_bits); + } +} + +/** + * dwc2_get_ls_map() - Get the map used for the given qh + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + * + * We'll always get the periodic map out of our TT. Note that even if we're + * running the host straight in low speed / full speed mode it appears as if + * a TT is allocated for us, so we'll use it. If that ever changes we can + * add logic here to get a map out of "hsotg" if !qh->do_split. + * + * Returns: the map or NULL if a map couldn't be found. + */ +static unsigned long *dwc2_get_ls_map(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + unsigned long *map; + + /* Don't expect to be missing a TT and be doing low speed scheduling */ + if (WARN_ON(!qh->dwc_tt)) + return NULL; + + /* Get the map and adjust if this is a multi_tt hub */ + map = qh->dwc_tt->periodic_bitmaps; + if (qh->dwc_tt->usb_tt->multi) + map += DWC2_ELEMENTS_PER_LS_BITMAP * (qh->ttport - 1); + + return map; +} + +#ifdef DWC2_PRINT_SCHEDULE +/* + * pmap_print() - Print the given periodic map + * + * Will attempt to print out the periodic schedule. + * + * @map: See pmap_schedule(). + * @bits_per_period: See pmap_schedule(). + * @periods_in_map: See pmap_schedule(). + * @period_name: The name of 1 period, like "uFrame" + * @units: The name of the units, like "us". + * @print_fn: The function to call for printing. + * @print_data: Opaque data to pass to the print function. + */ +static void pmap_print(unsigned long *map, int bits_per_period, + int periods_in_map, const char *period_name, + const char *units, + void (*print_fn)(const char *str, void *data), + void *print_data) +{ + int period; + + for (period = 0; period < periods_in_map; period++) { + DECLARE_SEQ_BUF(buf, 64); + int period_start = period * bits_per_period; + int period_end = period_start + bits_per_period; + int start = 0; + int count = 0; + bool printed = false; + int i; + + for (i = period_start; i < period_end + 1; i++) { + /* Handle case when ith bit is set */ + if (i < period_end && + bitmap_find_next_zero_area(map, i + 1, + i, 1, 0) != i) { + if (count == 0) + start = i - period_start; + count++; + continue; + } + + /* ith bit isn't set; don't care if count == 0 */ + if (count == 0) + continue; + + if (!printed) + seq_buf_printf(&buf, "%s %d: ", + period_name, period); + else + seq_buf_puts(&buf, ", "); + printed = true; + + seq_buf_printf(&buf, "%d %s -%3d %s", start, + units, start + count - 1, units); + count = 0; + } + + if (printed) + print_fn(seq_buf_str(&buf), print_data); + } +} + +struct dwc2_qh_print_data { + struct dwc2_hsotg *hsotg; + struct dwc2_qh *qh; +}; + +/** + * dwc2_qh_print() - Helper function for dwc2_qh_schedule_print() + * + * @str: The string to print + * @data: A pointer to a struct dwc2_qh_print_data + */ +static void dwc2_qh_print(const char *str, void *data) +{ + struct dwc2_qh_print_data *print_data = data; + + dwc2_sch_dbg(print_data->hsotg, "QH=%p ...%s\n", print_data->qh, str); +} + +/** + * dwc2_qh_schedule_print() - Print the periodic schedule + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH to print. + */ +static void dwc2_qh_schedule_print(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + struct dwc2_qh_print_data print_data = { hsotg, qh }; + int i; + + /* + * The printing functions are quite slow and inefficient. + * If we don't have tracing turned on, don't run unless the special + * define is turned on. + */ + + if (qh->schedule_low_speed) { + unsigned long *map = dwc2_get_ls_map(hsotg, qh); + + dwc2_sch_dbg(hsotg, "QH=%p LS/FS trans: %d=>%d us @ %d us", + qh, qh->device_us, + DWC2_ROUND_US_TO_SLICE(qh->device_us), + DWC2_US_PER_SLICE * qh->ls_start_schedule_slice); + + if (map) { + dwc2_sch_dbg(hsotg, + "QH=%p Whole low/full speed map %p now:\n", + qh, map); + pmap_print(map, DWC2_LS_PERIODIC_SLICES_PER_FRAME, + DWC2_LS_SCHEDULE_FRAMES, "Frame ", "slices", + dwc2_qh_print, &print_data); + } + } + + for (i = 0; i < qh->num_hs_transfers; i++) { + struct dwc2_hs_transfer_time *trans_time = qh->hs_transfers + i; + int uframe = trans_time->start_schedule_us / + DWC2_HS_PERIODIC_US_PER_UFRAME; + int rel_us = trans_time->start_schedule_us % + DWC2_HS_PERIODIC_US_PER_UFRAME; + + dwc2_sch_dbg(hsotg, + "QH=%p HS trans #%d: %d us @ uFrame %d + %d us\n", + qh, i, trans_time->duration_us, uframe, rel_us); + } + if (qh->num_hs_transfers) { + dwc2_sch_dbg(hsotg, "QH=%p Whole high speed map now:\n", qh); + pmap_print(hsotg->hs_periodic_bitmap, + DWC2_HS_PERIODIC_US_PER_UFRAME, + DWC2_HS_SCHEDULE_UFRAMES, "uFrame", "us", + dwc2_qh_print, &print_data); + } +} +#else +static inline void dwc2_qh_schedule_print(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) {}; +#endif + +/** + * dwc2_ls_pmap_schedule() - Schedule a low speed QH + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + * @search_slice: We'll start trying to schedule at the passed slice. + * Remember that slices are the units of the low speed + * schedule (think 25us or so). + * + * Wraps pmap_schedule() with the right parameters for low speed scheduling. + * + * Normally we schedule low speed devices on the map associated with the TT. + * + * Returns: 0 for success or an error code. + */ +static int dwc2_ls_pmap_schedule(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + int search_slice) +{ + int slices = DIV_ROUND_UP(qh->device_us, DWC2_US_PER_SLICE); + unsigned long *map = dwc2_get_ls_map(hsotg, qh); + int slice; + + if (!map) + return -EINVAL; + + /* + * Schedule on the proper low speed map with our low speed scheduling + * parameters. Note that we use the "device_interval" here since + * we want the low speed interval and the only way we'd be in this + * function is if the device is low speed. + * + * If we happen to be doing low speed and high speed scheduling for the + * same transaction (AKA we have a split) we always do low speed first. + * That means we can always pass "false" for only_one_period (that + * parameters is only useful when we're trying to get one schedule to + * match what we already planned in the other schedule). + */ + slice = pmap_schedule(map, DWC2_LS_PERIODIC_SLICES_PER_FRAME, + DWC2_LS_SCHEDULE_FRAMES, slices, + qh->device_interval, search_slice, false); + + if (slice < 0) + return slice; + + qh->ls_start_schedule_slice = slice; + return 0; +} + +/** + * dwc2_ls_pmap_unschedule() - Undo work done by dwc2_ls_pmap_schedule() + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + */ +static void dwc2_ls_pmap_unschedule(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + int slices = DIV_ROUND_UP(qh->device_us, DWC2_US_PER_SLICE); + unsigned long *map = dwc2_get_ls_map(hsotg, qh); + + /* Schedule should have failed, so no worries about no error code */ + if (!map) + return; + + pmap_unschedule(map, DWC2_LS_PERIODIC_SLICES_PER_FRAME, + DWC2_LS_SCHEDULE_FRAMES, slices, qh->device_interval, + qh->ls_start_schedule_slice); +} + +/** + * dwc2_hs_pmap_schedule - Schedule in the main high speed schedule + * + * This will schedule something on the main dwc2 schedule. + * + * We'll start looking in qh->hs_transfers[index].start_schedule_us. We'll + * update this with the result upon success. We also use the duration from + * the same structure. + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + * @only_one_period: If true we will limit ourselves to just looking at + * one period (aka one 100us chunk). This is used if we have + * already scheduled something on the low speed schedule and + * need to find something that matches on the high speed one. + * @index: The index into qh->hs_transfers that we're working with. + * + * Returns: 0 for success or an error code. Upon success the + * dwc2_hs_transfer_time specified by "index" will be updated. + */ +static int dwc2_hs_pmap_schedule(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + bool only_one_period, int index) +{ + struct dwc2_hs_transfer_time *trans_time = qh->hs_transfers + index; + int us; + + us = pmap_schedule(hsotg->hs_periodic_bitmap, + DWC2_HS_PERIODIC_US_PER_UFRAME, + DWC2_HS_SCHEDULE_UFRAMES, trans_time->duration_us, + qh->host_interval, trans_time->start_schedule_us, + only_one_period); + + if (us < 0) + return us; + + trans_time->start_schedule_us = us; + return 0; +} + +/** + * dwc2_hs_pmap_unschedule() - Undo work done by dwc2_hs_pmap_schedule() + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + * @index: Transfer index + */ +static void dwc2_hs_pmap_unschedule(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh, int index) +{ + struct dwc2_hs_transfer_time *trans_time = qh->hs_transfers + index; + + pmap_unschedule(hsotg->hs_periodic_bitmap, + DWC2_HS_PERIODIC_US_PER_UFRAME, + DWC2_HS_SCHEDULE_UFRAMES, trans_time->duration_us, + qh->host_interval, trans_time->start_schedule_us); +} + +/** + * dwc2_uframe_schedule_split - Schedule a QH for a periodic split xfer. + * + * This is the most complicated thing in USB. We have to find matching time + * in both the global high speed schedule for the port and the low speed + * schedule for the TT associated with the given device. + * + * Being here means that the host must be running in high speed mode and the + * device is in low or full speed mode (and behind a hub). + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + */ +static int dwc2_uframe_schedule_split(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + int bytecount = qh->maxp_mult * qh->maxp; + int ls_search_slice; + int err = 0; + int host_interval_in_sched; + + /* + * The interval (how often to repeat) in the actual host schedule. + * See pmap_schedule() for gcd() explanation. + */ + host_interval_in_sched = gcd(qh->host_interval, + DWC2_HS_SCHEDULE_UFRAMES); + + /* + * We always try to find space in the low speed schedule first, then + * try to find high speed time that matches. If we don't, we'll bump + * up the place we start searching in the low speed schedule and try + * again. To start we'll look right at the beginning of the low speed + * schedule. + * + * Note that this will tend to front-load the high speed schedule. + * We may eventually want to try to avoid this by either considering + * both schedules together or doing some sort of round robin. + */ + ls_search_slice = 0; + + while (ls_search_slice < DWC2_LS_SCHEDULE_SLICES) { + int start_s_uframe; + int ssplit_s_uframe; + int second_s_uframe; + int rel_uframe; + int first_count; + int middle_count; + int end_count; + int first_data_bytes; + int other_data_bytes; + int i; + + if (qh->schedule_low_speed) { + err = dwc2_ls_pmap_schedule(hsotg, qh, ls_search_slice); + + /* + * If we got an error here there's no other magic we + * can do, so bail. All the looping above is only + * helpful to redo things if we got a low speed slot + * and then couldn't find a matching high speed slot. + */ + if (err) + return err; + } else { + /* Must be missing the tt structure? Why? */ + WARN_ON_ONCE(1); + } + + /* + * This will give us a number 0 - 7 if + * DWC2_LS_SCHEDULE_FRAMES == 1, or 0 - 15 if == 2, or ... + */ + start_s_uframe = qh->ls_start_schedule_slice / + DWC2_SLICES_PER_UFRAME; + + /* Get a number that's always 0 - 7 */ + rel_uframe = (start_s_uframe % 8); + + /* + * If we were going to start in uframe 7 then we would need to + * issue a start split in uframe 6, which spec says is not OK. + * Move on to the next full frame (assuming there is one). + * + * See 11.18.4 Host Split Transaction Scheduling Requirements + * bullet 1. + */ + if (rel_uframe == 7) { + if (qh->schedule_low_speed) + dwc2_ls_pmap_unschedule(hsotg, qh); + ls_search_slice = + (qh->ls_start_schedule_slice / + DWC2_LS_PERIODIC_SLICES_PER_FRAME + 1) * + DWC2_LS_PERIODIC_SLICES_PER_FRAME; + continue; + } + + /* + * For ISOC in: + * - start split (frame -1) + * - complete split w/ data (frame +1) + * - complete split w/ data (frame +2) + * - ... + * - complete split w/ data (frame +num_data_packets) + * - complete split w/ data (frame +num_data_packets+1) + * - complete split w/ data (frame +num_data_packets+2, max 8) + * ...though if frame was "0" then max is 7... + * + * For ISOC out we might need to do: + * - start split w/ data (frame -1) + * - start split w/ data (frame +0) + * - ... + * - start split w/ data (frame +num_data_packets-2) + * + * For INTERRUPT in we might need to do: + * - start split (frame -1) + * - complete split w/ data (frame +1) + * - complete split w/ data (frame +2) + * - complete split w/ data (frame +3, max 8) + * + * For INTERRUPT out we might need to do: + * - start split w/ data (frame -1) + * - complete split (frame +1) + * - complete split (frame +2) + * - complete split (frame +3, max 8) + * + * Start adjusting! + */ + ssplit_s_uframe = (start_s_uframe + + host_interval_in_sched - 1) % + host_interval_in_sched; + if (qh->ep_type == USB_ENDPOINT_XFER_ISOC && !qh->ep_is_in) + second_s_uframe = start_s_uframe; + else + second_s_uframe = start_s_uframe + 1; + + /* First data transfer might not be all 188 bytes. */ + first_data_bytes = 188 - + DIV_ROUND_UP(188 * (qh->ls_start_schedule_slice % + DWC2_SLICES_PER_UFRAME), + DWC2_SLICES_PER_UFRAME); + if (first_data_bytes > bytecount) + first_data_bytes = bytecount; + other_data_bytes = bytecount - first_data_bytes; + + /* + * For now, skip OUT xfers where first xfer is partial + * + * Main dwc2 code assumes: + * - INT transfers never get split in two. + * - ISOC transfers can always transfer 188 bytes the first + * time. + * + * Until that code is fixed, try again if the first transfer + * couldn't transfer everything. + * + * This code can be removed if/when the rest of dwc2 handles + * the above cases. Until it's fixed we just won't be able + * to schedule quite as tightly. + */ + if (!qh->ep_is_in && + (first_data_bytes != min_t(int, 188, bytecount))) { + dwc2_sch_dbg(hsotg, + "QH=%p avoiding broken 1st xfer (%d, %d)\n", + qh, first_data_bytes, bytecount); + if (qh->schedule_low_speed) + dwc2_ls_pmap_unschedule(hsotg, qh); + ls_search_slice = (start_s_uframe + 1) * + DWC2_SLICES_PER_UFRAME; + continue; + } + + /* Start by assuming transfers for the bytes */ + qh->num_hs_transfers = 1 + DIV_ROUND_UP(other_data_bytes, 188); + + /* + * Everything except ISOC OUT has extra transfers. Rules are + * complicated. See 11.18.4 Host Split Transaction Scheduling + * Requirements bullet 3. + */ + if (qh->ep_type == USB_ENDPOINT_XFER_INT) { + if (rel_uframe == 6) + qh->num_hs_transfers += 2; + else + qh->num_hs_transfers += 3; + + if (qh->ep_is_in) { + /* + * First is start split, middle/end is data. + * Allocate full data bytes for all data. + */ + first_count = 4; + middle_count = bytecount; + end_count = bytecount; + } else { + /* + * First is data, middle/end is complete. + * First transfer and second can have data. + * Rest should just have complete split. + */ + first_count = first_data_bytes; + middle_count = max_t(int, 4, other_data_bytes); + end_count = 4; + } + } else { + if (qh->ep_is_in) { + int last; + + /* Account for the start split */ + qh->num_hs_transfers++; + + /* Calculate "L" value from spec */ + last = rel_uframe + qh->num_hs_transfers + 1; + + /* Start with basic case */ + if (last <= 6) + qh->num_hs_transfers += 2; + else + qh->num_hs_transfers += 1; + + /* Adjust downwards */ + if (last >= 6 && rel_uframe == 0) + qh->num_hs_transfers--; + + /* 1st = start; rest can contain data */ + first_count = 4; + middle_count = min_t(int, 188, bytecount); + end_count = middle_count; + } else { + /* All contain data, last might be smaller */ + first_count = first_data_bytes; + middle_count = min_t(int, 188, + other_data_bytes); + end_count = other_data_bytes % 188; + } + } + + /* Assign durations per uFrame */ + qh->hs_transfers[0].duration_us = HS_USECS_ISO(first_count); + for (i = 1; i < qh->num_hs_transfers - 1; i++) + qh->hs_transfers[i].duration_us = + HS_USECS_ISO(middle_count); + if (qh->num_hs_transfers > 1) + qh->hs_transfers[qh->num_hs_transfers - 1].duration_us = + HS_USECS_ISO(end_count); + + /* + * Assign start us. The call below to dwc2_hs_pmap_schedule() + * will start with these numbers but may adjust within the same + * microframe. + */ + qh->hs_transfers[0].start_schedule_us = + ssplit_s_uframe * DWC2_HS_PERIODIC_US_PER_UFRAME; + for (i = 1; i < qh->num_hs_transfers; i++) + qh->hs_transfers[i].start_schedule_us = + ((second_s_uframe + i - 1) % + DWC2_HS_SCHEDULE_UFRAMES) * + DWC2_HS_PERIODIC_US_PER_UFRAME; + + /* Try to schedule with filled in hs_transfers above */ + for (i = 0; i < qh->num_hs_transfers; i++) { + err = dwc2_hs_pmap_schedule(hsotg, qh, true, i); + if (err) + break; + } + + /* If we scheduled all w/out breaking out then we're all good */ + if (i == qh->num_hs_transfers) + break; + + for (; i >= 0; i--) + dwc2_hs_pmap_unschedule(hsotg, qh, i); + + if (qh->schedule_low_speed) + dwc2_ls_pmap_unschedule(hsotg, qh); + + /* Try again starting in the next microframe */ + ls_search_slice = (start_s_uframe + 1) * DWC2_SLICES_PER_UFRAME; + } + + if (ls_search_slice >= DWC2_LS_SCHEDULE_SLICES) + return -ENOSPC; + + return 0; +} + +/** + * dwc2_uframe_schedule_hs - Schedule a QH for a periodic high speed xfer. + * + * Basically this just wraps dwc2_hs_pmap_schedule() to provide a clean + * interface. + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + */ +static int dwc2_uframe_schedule_hs(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + /* In non-split host and device time are the same */ + WARN_ON(qh->host_us != qh->device_us); + WARN_ON(qh->host_interval != qh->device_interval); + WARN_ON(qh->num_hs_transfers != 1); + + /* We'll have one transfer; init start to 0 before calling scheduler */ + qh->hs_transfers[0].start_schedule_us = 0; + qh->hs_transfers[0].duration_us = qh->host_us; + + return dwc2_hs_pmap_schedule(hsotg, qh, false, 0); +} + +/** + * dwc2_uframe_schedule_ls - Schedule a QH for a periodic low/full speed xfer. + * + * Basically this just wraps dwc2_ls_pmap_schedule() to provide a clean + * interface. + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + */ +static int dwc2_uframe_schedule_ls(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + /* In non-split host and device time are the same */ + WARN_ON(qh->host_us != qh->device_us); + WARN_ON(qh->host_interval != qh->device_interval); + WARN_ON(!qh->schedule_low_speed); + + /* Run on the main low speed schedule (no split = no hub = no TT) */ + return dwc2_ls_pmap_schedule(hsotg, qh, 0); +} + +/** + * dwc2_uframe_schedule - Schedule a QH for a periodic xfer. + * + * Calls one of the 3 sub-function depending on what type of transfer this QH + * is for. Also adds some printing. + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + */ +static int dwc2_uframe_schedule(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + int ret; + + if (qh->dev_speed == USB_SPEED_HIGH) + ret = dwc2_uframe_schedule_hs(hsotg, qh); + else if (!qh->do_split) + ret = dwc2_uframe_schedule_ls(hsotg, qh); + else + ret = dwc2_uframe_schedule_split(hsotg, qh); + + if (ret) + dwc2_sch_dbg(hsotg, "QH=%p Failed to schedule %d\n", qh, ret); + else + dwc2_qh_schedule_print(hsotg, qh); + + return ret; +} + +/** + * dwc2_uframe_unschedule - Undoes dwc2_uframe_schedule(). + * + * @hsotg: The HCD state structure for the DWC OTG controller. + * @qh: QH for the periodic transfer. + */ +static void dwc2_uframe_unschedule(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + int i; + + for (i = 0; i < qh->num_hs_transfers; i++) + dwc2_hs_pmap_unschedule(hsotg, qh, i); + + if (qh->schedule_low_speed) + dwc2_ls_pmap_unschedule(hsotg, qh); + + dwc2_sch_dbg(hsotg, "QH=%p Unscheduled\n", qh); +} + +/** + * dwc2_pick_first_frame() - Choose 1st frame for qh that's already scheduled + * + * Takes a qh that has already been scheduled (which means we know we have the + * bandwdith reserved for us) and set the next_active_frame and the + * start_active_frame. + * + * This is expected to be called on qh's that weren't previously actively + * running. It just picks the next frame that we can fit into without any + * thought about the past. + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: QH for a periodic endpoint + * + */ +static void dwc2_pick_first_frame(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + u16 frame_number; + u16 earliest_frame; + u16 next_active_frame; + u16 relative_frame; + u16 interval; + + /* + * Use the real frame number rather than the cached value as of the + * last SOF to give us a little extra slop. + */ + frame_number = dwc2_hcd_get_frame_number(hsotg); + + /* + * We wouldn't want to start any earlier than the next frame just in + * case the frame number ticks as we're doing this calculation. + * + * NOTE: if we could quantify how long till we actually get scheduled + * we might be able to avoid the "+ 1" by looking at the upper part of + * HFNUM (the FRREM field). For now we'll just use the + 1 though. + */ + earliest_frame = dwc2_frame_num_inc(frame_number, 1); + next_active_frame = earliest_frame; + + /* Get the "no microframe scheduler" out of the way... */ + if (!hsotg->params.uframe_sched) { + if (qh->do_split) + /* Splits are active at microframe 0 minus 1 */ + next_active_frame |= 0x7; + goto exit; + } + + if (qh->dev_speed == USB_SPEED_HIGH || qh->do_split) { + /* + * We're either at high speed or we're doing a split (which + * means we're talking high speed to a hub). In any case + * the first frame should be based on when the first scheduled + * event is. + */ + WARN_ON(qh->num_hs_transfers < 1); + + relative_frame = qh->hs_transfers[0].start_schedule_us / + DWC2_HS_PERIODIC_US_PER_UFRAME; + + /* Adjust interval as per high speed schedule */ + interval = gcd(qh->host_interval, DWC2_HS_SCHEDULE_UFRAMES); + + } else { + /* + * Low or full speed directly on dwc2. Just about the same + * as high speed but on a different schedule and with slightly + * different adjustments. Note that this works because when + * the host and device are both low speed then frames in the + * controller tick at low speed. + */ + relative_frame = qh->ls_start_schedule_slice / + DWC2_LS_PERIODIC_SLICES_PER_FRAME; + interval = gcd(qh->host_interval, DWC2_LS_SCHEDULE_FRAMES); + } + + /* Scheduler messed up if frame is past interval */ + WARN_ON(relative_frame >= interval); + + /* + * We know interval must divide (HFNUM_MAX_FRNUM + 1) now that we've + * done the gcd(), so it's safe to move to the beginning of the current + * interval like this. + * + * After this we might be before earliest_frame, but don't worry, + * we'll fix it... + */ + next_active_frame = (next_active_frame / interval) * interval; + + /* + * Actually choose to start at the frame number we've been + * scheduled for. + */ + next_active_frame = dwc2_frame_num_inc(next_active_frame, + relative_frame); + + /* + * We actually need 1 frame before since the next_active_frame is + * the frame number we'll be put on the ready list and we won't be on + * the bus until 1 frame later. + */ + next_active_frame = dwc2_frame_num_dec(next_active_frame, 1); + + /* + * By now we might actually be before the earliest_frame. Let's move + * up intervals until we're not. + */ + while (dwc2_frame_num_gt(earliest_frame, next_active_frame)) + next_active_frame = dwc2_frame_num_inc(next_active_frame, + interval); + +exit: + qh->next_active_frame = next_active_frame; + qh->start_active_frame = next_active_frame; + + dwc2_sch_vdbg(hsotg, "QH=%p First fn=%04x nxt=%04x\n", + qh, frame_number, qh->next_active_frame); +} + +/** + * dwc2_do_reserve() - Make a periodic reservation + * + * Try to allocate space in the periodic schedule. Depending on parameters + * this might use the microframe scheduler or the dumb scheduler. + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: QH for the periodic transfer. + * + * Returns: 0 upon success; error upon failure. + */ +static int dwc2_do_reserve(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + int status; + + if (hsotg->params.uframe_sched) { + status = dwc2_uframe_schedule(hsotg, qh); + } else { + status = dwc2_periodic_channel_available(hsotg); + if (status) { + dev_info(hsotg->dev, + "%s: No host channel available for periodic transfer\n", + __func__); + return status; + } + + status = dwc2_check_periodic_bandwidth(hsotg, qh); + } + + if (status) { + dev_dbg(hsotg->dev, + "%s: Insufficient periodic bandwidth for periodic transfer\n", + __func__); + return status; + } + + if (!hsotg->params.uframe_sched) + /* Reserve periodic channel */ + hsotg->periodic_channels++; + + /* Update claimed usecs per (micro)frame */ + hsotg->periodic_usecs += qh->host_us; + + dwc2_pick_first_frame(hsotg, qh); + + return 0; +} + +/** + * dwc2_do_unreserve() - Actually release the periodic reservation + * + * This function actually releases the periodic bandwidth that was reserved + * by the given qh. + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: QH for the periodic transfer. + */ +static void dwc2_do_unreserve(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + assert_spin_locked(&hsotg->lock); + + WARN_ON(!qh->unreserve_pending); + + /* No more unreserve pending--we're doing it */ + qh->unreserve_pending = false; + + if (WARN_ON(!list_empty(&qh->qh_list_entry))) + list_del_init(&qh->qh_list_entry); + + /* Update claimed usecs per (micro)frame */ + hsotg->periodic_usecs -= qh->host_us; + + if (hsotg->params.uframe_sched) { + dwc2_uframe_unschedule(hsotg, qh); + } else { + /* Release periodic channel reservation */ + hsotg->periodic_channels--; + } +} + +/** + * dwc2_unreserve_timer_fn() - Timer function to release periodic reservation + * + * According to the kernel doc for usb_submit_urb() (specifically the part about + * "Reserved Bandwidth Transfers"), we need to keep a reservation active as + * long as a device driver keeps submitting. Since we're using HCD_BH to give + * back the URB we need to give the driver a little bit of time before we + * release the reservation. This worker is called after the appropriate + * delay. + * + * @t: Address to a qh unreserve_work. + */ +static void dwc2_unreserve_timer_fn(struct timer_list *t) +{ + struct dwc2_qh *qh = timer_container_of(qh, t, unreserve_timer); + struct dwc2_hsotg *hsotg = qh->hsotg; + unsigned long flags; + + /* + * Wait for the lock, or for us to be scheduled again. We + * could be scheduled again if: + * - We started executing but didn't get the lock yet. + * - A new reservation came in, but cancel didn't take effect + * because we already started executing. + * - The timer has been kicked again. + * In that case cancel and wait for the next call. + */ + while (!spin_trylock_irqsave(&hsotg->lock, flags)) { + if (timer_pending(&qh->unreserve_timer)) + return; + } + + /* + * Might be no more unreserve pending if: + * - We started executing but didn't get the lock yet. + * - A new reservation came in, but cancel didn't take effect + * because we already started executing. + * + * We can't put this in the loop above because unreserve_pending needs + * to be accessed under lock, so we can only check it once we got the + * lock. + */ + if (qh->unreserve_pending) + dwc2_do_unreserve(hsotg, qh); + + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +/** + * dwc2_check_max_xfer_size() - 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 + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: QH for a periodic endpoint + * + * Return: 0 if successful, negative error code otherwise + */ +static int dwc2_check_max_xfer_size(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + u32 max_xfer_size; + u32 max_channel_xfer_size; + int status = 0; + + max_xfer_size = qh->maxp * qh->maxp_mult; + max_channel_xfer_size = hsotg->params.max_transfer_size; + + if (max_xfer_size > max_channel_xfer_size) { + dev_err(hsotg->dev, + "%s: Periodic xfer length %d > max xfer length for channel %d\n", + __func__, max_xfer_size, max_channel_xfer_size); + status = -ENOSPC; + } + + return status; +} + +/** + * dwc2_schedule_periodic() - Schedules an interrupt or isochronous transfer in + * the periodic schedule + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: QH for the periodic transfer. The QH should already contain the + * scheduling information. + * + * Return: 0 if successful, negative error code otherwise + */ +static int dwc2_schedule_periodic(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + int status; + + status = dwc2_check_max_xfer_size(hsotg, qh); + if (status) { + dev_dbg(hsotg->dev, + "%s: Channel max transfer size too small for periodic transfer\n", + __func__); + return status; + } + + /* Cancel pending unreserve; if canceled OK, unreserve was pending */ + if (timer_delete(&qh->unreserve_timer)) + WARN_ON(!qh->unreserve_pending); + + /* + * Only need to reserve if there's not an unreserve pending, since if an + * unreserve is pending then by definition our old reservation is still + * valid. Unreserve might still be pending even if we didn't cancel if + * dwc2_unreserve_timer_fn() already started. Code in the timer handles + * that case. + */ + if (!qh->unreserve_pending) { + status = dwc2_do_reserve(hsotg, qh); + if (status) + return status; + } else { + /* + * It might have been a while, so make sure that frame_number + * is still good. Note: we could also try to use the similar + * dwc2_next_periodic_start() but that schedules much more + * tightly and we might need to hurry and queue things up. + */ + if (dwc2_frame_num_le(qh->next_active_frame, + hsotg->frame_number)) + dwc2_pick_first_frame(hsotg, qh); + } + + qh->unreserve_pending = 0; + + if (hsotg->params.dma_desc_enable) + /* Don't rely on SOF and start in ready schedule */ + list_add_tail(&qh->qh_list_entry, &hsotg->periodic_sched_ready); + else + /* Always start in inactive schedule */ + list_add_tail(&qh->qh_list_entry, + &hsotg->periodic_sched_inactive); + + return 0; +} + +/** + * dwc2_deschedule_periodic() - Removes an interrupt or isochronous transfer + * from the periodic schedule + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: QH for the periodic transfer + */ +static void dwc2_deschedule_periodic(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh) +{ + bool did_modify; + + assert_spin_locked(&hsotg->lock); + + /* + * Schedule the unreserve to happen in a little bit. Cases here: + * - Unreserve worker might be sitting there waiting to grab the lock. + * In this case it will notice it's been schedule again and will + * quit. + * - Unreserve worker might not be scheduled. + * + * We should never already be scheduled since dwc2_schedule_periodic() + * should have canceled the scheduled unreserve timer (hence the + * warning on did_modify). + * + * We add + 1 to the timer to guarantee that at least 1 jiffy has + * passed (otherwise if the jiffy counter might tick right after we + * read it and we'll get no delay). + */ + did_modify = mod_timer(&qh->unreserve_timer, + jiffies + DWC2_UNRESERVE_DELAY + 1); + WARN_ON(did_modify); + qh->unreserve_pending = 1; + + list_del_init(&qh->qh_list_entry); +} + +/** + * dwc2_wait_timer_fn() - Timer function to re-queue after waiting + * + * As per the spec, a NAK indicates that "a function is temporarily unable to + * transmit or receive data, but will eventually be able to do so without need + * of host intervention". + * + * That means that when we encounter a NAK we're supposed to retry. + * + * ...but if we retry right away (from the interrupt handler that saw the NAK) + * then we can end up with an interrupt storm (if the other side keeps NAKing + * us) because on slow enough CPUs it could take us longer to get out of the + * interrupt routine than it takes for the device to send another NAK. That + * leads to a constant stream of NAK interrupts and the CPU locks. + * + * ...so instead of retrying right away in the case of a NAK we'll set a timer + * to retry some time later. This function handles that timer and moves the + * qh back to the "inactive" list, then queues transactions. + * + * @t: Pointer to wait_timer in a qh. + * + * Return: HRTIMER_NORESTART to not automatically restart this timer. + */ +static enum hrtimer_restart dwc2_wait_timer_fn(struct hrtimer *t) +{ + struct dwc2_qh *qh = container_of(t, struct dwc2_qh, wait_timer); + struct dwc2_hsotg *hsotg = qh->hsotg; + unsigned long flags; + + spin_lock_irqsave(&hsotg->lock, flags); + + /* + * We'll set wait_timer_cancel to true if we want to cancel this + * operation in dwc2_hcd_qh_unlink(). + */ + if (!qh->wait_timer_cancel) { + enum dwc2_transaction_type tr_type; + + qh->want_wait = false; + + list_move(&qh->qh_list_entry, + &hsotg->non_periodic_sched_inactive); + + tr_type = dwc2_hcd_select_transactions(hsotg); + if (tr_type != DWC2_TRANSACTION_NONE) + dwc2_hcd_queue_transactions(hsotg, tr_type); + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + return HRTIMER_NORESTART; +} + +/** + * dwc2_qh_init() - Initializes a QH structure + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: The QH to init + * @urb: Holds the information about the device/endpoint needed to initialize + * the QH + * @mem_flags: Flags for allocating memory. + */ +static void dwc2_qh_init(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + struct dwc2_hcd_urb *urb, gfp_t mem_flags) +{ + int dev_speed = dwc2_host_get_speed(hsotg, urb->priv); + u8 ep_type = dwc2_hcd_get_pipe_type(&urb->pipe_info); + bool ep_is_in = !!dwc2_hcd_is_pipe_in(&urb->pipe_info); + bool ep_is_isoc = (ep_type == USB_ENDPOINT_XFER_ISOC); + bool ep_is_int = (ep_type == USB_ENDPOINT_XFER_INT); + u32 hprt = dwc2_readl(hsotg, HPRT0); + u32 prtspd = (hprt & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; + bool do_split = (prtspd == HPRT0_SPD_HIGH_SPEED && + dev_speed != USB_SPEED_HIGH); + int maxp = dwc2_hcd_get_maxp(&urb->pipe_info); + int maxp_mult = dwc2_hcd_get_maxp_mult(&urb->pipe_info); + int bytecount = maxp_mult * maxp; + char *speed, *type; + + /* Initialize QH */ + qh->hsotg = hsotg; + timer_setup(&qh->unreserve_timer, dwc2_unreserve_timer_fn, 0); + hrtimer_setup(&qh->wait_timer, &dwc2_wait_timer_fn, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + qh->ep_type = ep_type; + qh->ep_is_in = ep_is_in; + + qh->data_toggle = DWC2_HC_PID_DATA0; + qh->maxp = maxp; + qh->maxp_mult = maxp_mult; + INIT_LIST_HEAD(&qh->qtd_list); + INIT_LIST_HEAD(&qh->qh_list_entry); + + qh->do_split = do_split; + qh->dev_speed = dev_speed; + + if (ep_is_int || ep_is_isoc) { + /* Compute scheduling parameters once and save them */ + int host_speed = do_split ? USB_SPEED_HIGH : dev_speed; + struct dwc2_tt *dwc_tt = dwc2_host_get_tt_info(hsotg, urb->priv, + mem_flags, + &qh->ttport); + int device_ns; + + qh->dwc_tt = dwc_tt; + + qh->host_us = NS_TO_US(usb_calc_bus_time(host_speed, ep_is_in, + ep_is_isoc, bytecount)); + device_ns = usb_calc_bus_time(dev_speed, ep_is_in, + ep_is_isoc, bytecount); + + if (do_split && dwc_tt) + device_ns += dwc_tt->usb_tt->think_time; + qh->device_us = NS_TO_US(device_ns); + + qh->device_interval = urb->interval; + qh->host_interval = urb->interval * (do_split ? 8 : 1); + + /* + * Schedule low speed if we're running the host in low or + * full speed OR if we've got a "TT" to deal with to access this + * device. + */ + qh->schedule_low_speed = prtspd != HPRT0_SPD_HIGH_SPEED || + dwc_tt; + + if (do_split) { + /* We won't know num transfers until we schedule */ + qh->num_hs_transfers = -1; + } else if (dev_speed == USB_SPEED_HIGH) { + qh->num_hs_transfers = 1; + } else { + qh->num_hs_transfers = 0; + } + + /* We'll schedule later when we have something to do */ + } + + switch (dev_speed) { + case USB_SPEED_LOW: + speed = "low"; + break; + case USB_SPEED_FULL: + speed = "full"; + break; + case USB_SPEED_HIGH: + speed = "high"; + break; + default: + speed = "?"; + break; + } + + switch (qh->ep_type) { + case USB_ENDPOINT_XFER_ISOC: + type = "isochronous"; + break; + case USB_ENDPOINT_XFER_INT: + type = "interrupt"; + break; + case USB_ENDPOINT_XFER_CONTROL: + type = "control"; + break; + case USB_ENDPOINT_XFER_BULK: + type = "bulk"; + break; + default: + type = "?"; + break; + } + + dwc2_sch_dbg(hsotg, "QH=%p Init %s, %s speed, %d bytes:\n", qh, type, + speed, bytecount); + dwc2_sch_dbg(hsotg, "QH=%p ...addr=%d, ep=%d, %s\n", qh, + dwc2_hcd_get_dev_addr(&urb->pipe_info), + dwc2_hcd_get_ep_num(&urb->pipe_info), + ep_is_in ? "IN" : "OUT"); + if (ep_is_int || ep_is_isoc) { + dwc2_sch_dbg(hsotg, + "QH=%p ...duration: host=%d us, device=%d us\n", + qh, qh->host_us, qh->device_us); + dwc2_sch_dbg(hsotg, "QH=%p ...interval: host=%d, device=%d\n", + qh, qh->host_interval, qh->device_interval); + if (qh->schedule_low_speed) + dwc2_sch_dbg(hsotg, "QH=%p ...low speed schedule=%p\n", + qh, dwc2_get_ls_map(hsotg, qh)); + } +} + +/** + * dwc2_hcd_qh_create() - Allocates and initializes a QH + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @urb: Holds the information about the device/endpoint needed + * to initialize the QH + * @mem_flags: Flags for allocating memory. + * + * Return: Pointer to the newly allocated QH, or NULL on error + */ +struct dwc2_qh *dwc2_hcd_qh_create(struct dwc2_hsotg *hsotg, + struct dwc2_hcd_urb *urb, + gfp_t mem_flags) +{ + struct dwc2_qh *qh; + + if (!urb->priv) + return NULL; + + /* Allocate memory */ + qh = kzalloc_obj(*qh, mem_flags); + if (!qh) + return NULL; + + dwc2_qh_init(hsotg, qh, urb, mem_flags); + + if (hsotg->params.dma_desc_enable && + dwc2_hcd_qh_init_ddma(hsotg, qh, mem_flags) < 0) { + dwc2_hcd_qh_free(hsotg, qh); + return NULL; + } + + return qh; +} + +/** + * dwc2_hcd_qh_free() - Frees the QH + * + * @hsotg: HCD instance + * @qh: The QH to free + * + * QH should already be removed from the list. QTD list should already be empty + * if called from URB Dequeue. + * + * Must NOT be called with interrupt disabled or spinlock held + */ +void dwc2_hcd_qh_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + /* Make sure any unreserve work is finished. */ + if (timer_delete_sync(&qh->unreserve_timer)) { + unsigned long flags; + + spin_lock_irqsave(&hsotg->lock, flags); + dwc2_do_unreserve(hsotg, qh); + spin_unlock_irqrestore(&hsotg->lock, flags); + } + + /* + * We don't have the lock so we can safely wait until the wait timer + * finishes. Of course, at this point in time we'd better have set + * wait_timer_active to false so if this timer was still pending it + * won't do anything anyway, but we want it to finish before we free + * memory. + */ + hrtimer_cancel(&qh->wait_timer); + + dwc2_host_put_tt_info(hsotg, qh->dwc_tt); + + if (qh->desc_list) + dwc2_hcd_qh_free_ddma(hsotg, qh); + else if (hsotg->unaligned_cache && qh->dw_align_buf) + kmem_cache_free(hsotg->unaligned_cache, qh->dw_align_buf); + + kfree(qh); +} + +/** + * dwc2_hcd_qh_add() - 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. + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh: The QH to add + * + * Return: 0 if successful, negative error code otherwise + */ +int dwc2_hcd_qh_add(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + int status; + u32 intr_mask; + ktime_t delay; + + if (dbg_qh(qh)) + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + if (!list_empty(&qh->qh_list_entry)) + /* QH already in a schedule */ + return 0; + + /* Add the new QH to the appropriate schedule */ + if (dwc2_qh_is_non_per(qh)) { + /* Schedule right away */ + qh->start_active_frame = hsotg->frame_number; + qh->next_active_frame = qh->start_active_frame; + + if (qh->want_wait) { + list_add_tail(&qh->qh_list_entry, + &hsotg->non_periodic_sched_waiting); + qh->wait_timer_cancel = false; + delay = ktime_set(0, DWC2_RETRY_WAIT_DELAY); + hrtimer_start(&qh->wait_timer, delay, HRTIMER_MODE_REL); + } else { + list_add_tail(&qh->qh_list_entry, + &hsotg->non_periodic_sched_inactive); + } + return 0; + } + + status = dwc2_schedule_periodic(hsotg, qh); + if (status) + return status; + if (!hsotg->periodic_qh_count) { + intr_mask = dwc2_readl(hsotg, GINTMSK); + intr_mask |= GINTSTS_SOF; + dwc2_writel(hsotg, intr_mask, GINTMSK); + } + hsotg->periodic_qh_count++; + + return 0; +} + +/** + * dwc2_hcd_qh_unlink() - Removes a QH from either the non-periodic or periodic + * schedule. Memory is not freed. + * + * @hsotg: The HCD state structure + * @qh: QH to remove from schedule + */ +void dwc2_hcd_qh_unlink(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ + u32 intr_mask; + + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + /* If the wait_timer is pending, this will stop it from acting */ + qh->wait_timer_cancel = true; + + if (list_empty(&qh->qh_list_entry)) + /* QH is not in a schedule */ + return; + + if (dwc2_qh_is_non_per(qh)) { + if (hsotg->non_periodic_qh_ptr == &qh->qh_list_entry) + hsotg->non_periodic_qh_ptr = + hsotg->non_periodic_qh_ptr->next; + list_del_init(&qh->qh_list_entry); + return; + } + + dwc2_deschedule_periodic(hsotg, qh); + hsotg->periodic_qh_count--; + if (!hsotg->periodic_qh_count && + !hsotg->params.dma_desc_enable) { + intr_mask = dwc2_readl(hsotg, GINTMSK); + intr_mask &= ~GINTSTS_SOF; + dwc2_writel(hsotg, intr_mask, GINTMSK); + } +} + +/** + * dwc2_next_for_periodic_split() - Set next_active_frame midway thru a split. + * + * This is called for setting next_active_frame for periodic splits for all but + * the first packet of the split. Confusing? I thought so... + * + * Periodic splits are single low/full speed transfers that we end up splitting + * up into several high speed transfers. They always fit into one full (1 ms) + * frame but might be split over several microframes (125 us each). We to put + * each of the parts on a very specific high speed frame. + * + * This function figures out where the next active uFrame needs to be. + * + * @hsotg: The HCD state structure + * @qh: QH for the periodic transfer. + * @frame_number: The current frame number. + * + * Return: number missed by (or 0 if we didn't miss). + */ +static int dwc2_next_for_periodic_split(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh, u16 frame_number) +{ + u16 old_frame = qh->next_active_frame; + u16 prev_frame_number = dwc2_frame_num_dec(frame_number, 1); + int missed = 0; + u16 incr; + + /* + * See dwc2_uframe_schedule_split() for split scheduling. + * + * Basically: increment 1 normally, but 2 right after the start split + * (except for ISOC out). + */ + if (old_frame == qh->start_active_frame && + !(qh->ep_type == USB_ENDPOINT_XFER_ISOC && !qh->ep_is_in)) + incr = 2; + else + incr = 1; + + qh->next_active_frame = dwc2_frame_num_inc(old_frame, incr); + + /* + * Note that it's OK for frame_number to be 1 frame past + * next_active_frame. Remember that next_active_frame is supposed to + * be 1 frame _before_ when we want to be scheduled. If we're 1 frame + * past it just means schedule ASAP. + * + * It's _not_ OK, however, if we're more than one frame past. + */ + if (dwc2_frame_num_gt(prev_frame_number, qh->next_active_frame)) { + /* + * OOPS, we missed. That's actually pretty bad since + * the hub will be unhappy; try ASAP I guess. + */ + missed = dwc2_frame_num_dec(prev_frame_number, + qh->next_active_frame); + qh->next_active_frame = frame_number; + } + + return missed; +} + +/** + * dwc2_next_periodic_start() - Set next_active_frame for next transfer start + * + * This is called for setting next_active_frame for a periodic transfer for + * all cases other than midway through a periodic split. This will also update + * start_active_frame. + * + * Since we _always_ keep start_active_frame as the start of the previous + * transfer this is normally pretty easy: we just add our interval to + * start_active_frame and we've got our answer. + * + * The tricks come into play if we miss. In that case we'll look for the next + * slot we can fit into. + * + * @hsotg: The HCD state structure + * @qh: QH for the periodic transfer. + * @frame_number: The current frame number. + * + * Return: number missed by (or 0 if we didn't miss). + */ +static int dwc2_next_periodic_start(struct dwc2_hsotg *hsotg, + struct dwc2_qh *qh, u16 frame_number) +{ + int missed = 0; + u16 interval = qh->host_interval; + u16 prev_frame_number = dwc2_frame_num_dec(frame_number, 1); + + qh->start_active_frame = dwc2_frame_num_inc(qh->start_active_frame, + interval); + + /* + * The dwc2_frame_num_gt() function used below won't work terribly well + * with if we just incremented by a really large intervals since the + * frame counter only goes to 0x3fff. It's terribly unlikely that we + * will have missed in this case anyway. Just go to exit. If we want + * to try to do better we'll need to keep track of a bigger counter + * somewhere in the driver and handle overflows. + */ + if (interval >= 0x1000) + goto exit; + + /* + * Test for misses, which is when it's too late to schedule. + * + * A few things to note: + * - We compare against prev_frame_number since start_active_frame + * and next_active_frame are always 1 frame before we want things + * to be active and we assume we can still get scheduled in the + * current frame number. + * - It's possible for start_active_frame (now incremented) to be + * next_active_frame if we got an EO MISS (even_odd miss) which + * basically means that we detected there wasn't enough time for + * the last packet and dwc2_hc_set_even_odd_frame() rescheduled us + * at the last second. We want to make sure we don't schedule + * another transfer for the same frame. My test webcam doesn't seem + * terribly upset by missing a transfer but really doesn't like when + * we do two transfers in the same frame. + * - Some misses are expected. Specifically, in order to work + * perfectly dwc2 really needs quite spectacular interrupt latency + * requirements. It needs to be able to handle its interrupts + * completely within 125 us of them being asserted. That not only + * means that the dwc2 interrupt handler needs to be fast but it + * means that nothing else in the system has to block dwc2 for a long + * time. We can help with the dwc2 parts of this, but it's hard to + * guarantee that a system will have interrupt latency < 125 us, so + * we have to be robust to some misses. + */ + if (qh->start_active_frame == qh->next_active_frame || + dwc2_frame_num_gt(prev_frame_number, qh->start_active_frame)) { + u16 ideal_start = qh->start_active_frame; + int periods_in_map; + + /* + * Adjust interval as per gcd with map size. + * See pmap_schedule() for more details here. + */ + if (qh->do_split || qh->dev_speed == USB_SPEED_HIGH) + periods_in_map = DWC2_HS_SCHEDULE_UFRAMES; + else + periods_in_map = DWC2_LS_SCHEDULE_FRAMES; + interval = gcd(interval, periods_in_map); + + do { + qh->start_active_frame = dwc2_frame_num_inc( + qh->start_active_frame, interval); + } while (dwc2_frame_num_gt(prev_frame_number, + qh->start_active_frame)); + + missed = dwc2_frame_num_dec(qh->start_active_frame, + ideal_start); + } + +exit: + qh->next_active_frame = qh->start_active_frame; + + return missed; +} + +/* + * 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 dwc2_hcd_qh_deactivate(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, + int sched_next_periodic_split) +{ + u16 old_frame = qh->next_active_frame; + u16 frame_number; + int missed; + + if (dbg_qh(qh)) + dev_vdbg(hsotg->dev, "%s()\n", __func__); + + if (dwc2_qh_is_non_per(qh)) { + dwc2_hcd_qh_unlink(hsotg, qh); + if (!list_empty(&qh->qtd_list)) + /* Add back to inactive/waiting non-periodic schedule */ + dwc2_hcd_qh_add(hsotg, qh); + return; + } + + /* + * Use the real frame number rather than the cached value as of the + * last SOF just to get us a little closer to reality. Note that + * means we don't actually know if we've already handled the SOF + * interrupt for this frame. + */ + frame_number = dwc2_hcd_get_frame_number(hsotg); + + if (sched_next_periodic_split) + missed = dwc2_next_for_periodic_split(hsotg, qh, frame_number); + else + missed = dwc2_next_periodic_start(hsotg, qh, frame_number); + + dwc2_sch_vdbg(hsotg, + "QH=%p next(%d) fn=%04x, sch=%04x=>%04x (%+d) miss=%d %s\n", + qh, sched_next_periodic_split, frame_number, old_frame, + qh->next_active_frame, + dwc2_frame_num_dec(qh->next_active_frame, old_frame), + missed, missed ? "MISS" : ""); + + if (list_empty(&qh->qtd_list)) { + dwc2_hcd_qh_unlink(hsotg, qh); + return; + } + + /* + * Remove from periodic_sched_queued and move to + * appropriate queue + * + * Note: we purposely use the frame_number from the "hsotg" structure + * since we know SOF interrupt will handle future frames. + */ + if (dwc2_frame_num_le(qh->next_active_frame, hsotg->frame_number)) + list_move_tail(&qh->qh_list_entry, + &hsotg->periodic_sched_ready); + else + list_move_tail(&qh->qh_list_entry, + &hsotg->periodic_sched_inactive); +} + +/** + * dwc2_hcd_qtd_init() - Initializes a QTD structure + * + * @qtd: The QTD to initialize + * @urb: The associated URB + */ +void dwc2_hcd_qtd_init(struct dwc2_qtd *qtd, struct dwc2_hcd_urb *urb) +{ + qtd->urb = urb; + if (dwc2_hcd_get_pipe_type(&urb->pipe_info) == + USB_ENDPOINT_XFER_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 = DWC2_HC_PID_DATA1; + qtd->control_phase = DWC2_CONTROL_SETUP; + } + + /* Start split */ + qtd->complete_split = 0; + qtd->isoc_split_pos = DWC2_HCSPLT_XACTPOS_ALL; + qtd->isoc_split_offset = 0; + qtd->in_process = 0; + + /* Store the qtd ptr in the urb to reference the QTD */ + urb->qtd = qtd; +} + +/** + * dwc2_hcd_qtd_add() - Adds a QTD to the QTD-list of a QH + * Caller must hold driver lock. + * + * @hsotg: The DWC HCD structure + * @qtd: The QTD to add + * @qh: Queue head to add qtd to + * + * Return: 0 if successful, negative error code otherwise + * + * 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. + */ +int dwc2_hcd_qtd_add(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, + struct dwc2_qh *qh) +{ + int retval; + + if (unlikely(!qh)) { + dev_err(hsotg->dev, "%s: Invalid QH\n", __func__); + retval = -EINVAL; + goto fail; + } + + retval = dwc2_hcd_qh_add(hsotg, qh); + if (retval) + goto fail; + + qtd->qh = qh; + list_add_tail(&qtd->qtd_list_entry, &qh->qtd_list); + + return 0; +fail: + return retval; +} diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h new file mode 100644 index 000000000..fe8c87a2f --- /dev/null +++ b/drivers/usb/dwc2/hw.h @@ -0,0 +1,889 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */ +/* + * hw.h - DesignWare HS OTG Controller hardware definitions + * + * Copyright 2004-2013 Synopsys, Inc. + */ + +#ifndef __DWC2_HW_H__ +#define __DWC2_HW_H__ + +#define HSOTG_REG(x) (x) + +#define GOTGCTL HSOTG_REG(0x000) +#define GOTGCTL_EUSB2_DISC_SUPP BIT(28) +#define GOTGCTL_CHIRPEN BIT(27) +#define GOTGCTL_MULT_VALID_BC_MASK (0x1f << 22) +#define GOTGCTL_MULT_VALID_BC_SHIFT 22 +#define GOTGCTL_CURMODE_HOST BIT(21) +#define GOTGCTL_OTGVER BIT(20) +#define GOTGCTL_BSESVLD BIT(19) +#define GOTGCTL_ASESVLD BIT(18) +#define GOTGCTL_DBNC_SHORT BIT(17) +#define GOTGCTL_CONID_B BIT(16) +#define GOTGCTL_DBNCE_FLTR_BYPASS BIT(15) +#define GOTGCTL_DEVHNPEN BIT(11) +#define GOTGCTL_HSTSETHNPEN BIT(10) +#define GOTGCTL_HNPREQ BIT(9) +#define GOTGCTL_HSTNEGSCS BIT(8) +#define GOTGCTL_BVALOVAL BIT(7) +#define GOTGCTL_BVALOEN BIT(6) +#define GOTGCTL_AVALOVAL BIT(5) +#define GOTGCTL_AVALOEN BIT(4) +#define GOTGCTL_VBVALOVAL BIT(3) +#define GOTGCTL_VBVALOEN BIT(2) +#define GOTGCTL_SESREQ BIT(1) +#define GOTGCTL_SESREQSCS BIT(0) + +#define GOTGINT HSOTG_REG(0x004) +#define GOTGINT_DBNCE_DONE BIT(19) +#define GOTGINT_A_DEV_TOUT_CHG BIT(18) +#define GOTGINT_HST_NEG_DET BIT(17) +#define GOTGINT_HST_NEG_SUC_STS_CHNG BIT(9) +#define GOTGINT_SES_REQ_SUC_STS_CHNG BIT(8) +#define GOTGINT_SES_END_DET BIT(2) + +#define GAHBCFG HSOTG_REG(0x008) +#define GAHBCFG_AHB_SINGLE BIT(23) +#define GAHBCFG_NOTI_ALL_DMA_WRIT BIT(22) +#define GAHBCFG_REM_MEM_SUPP BIT(21) +#define GAHBCFG_P_TXF_EMP_LVL BIT(8) +#define GAHBCFG_NP_TXF_EMP_LVL BIT(7) +#define GAHBCFG_DMA_EN BIT(5) +#define GAHBCFG_HBSTLEN_MASK (0xf << 1) +#define GAHBCFG_HBSTLEN_SHIFT 1 +#define GAHBCFG_HBSTLEN_SINGLE 0 +#define GAHBCFG_HBSTLEN_INCR 1 +#define GAHBCFG_HBSTLEN_INCR4 3 +#define GAHBCFG_HBSTLEN_INCR8 5 +#define GAHBCFG_HBSTLEN_INCR16 7 +#define GAHBCFG_GLBL_INTR_EN BIT(0) +#define GAHBCFG_CTRL_MASK (GAHBCFG_P_TXF_EMP_LVL | \ + GAHBCFG_NP_TXF_EMP_LVL | \ + GAHBCFG_DMA_EN | \ + GAHBCFG_GLBL_INTR_EN) + +#define GUSBCFG HSOTG_REG(0x00C) +#define GUSBCFG_FORCEDEVMODE BIT(30) +#define GUSBCFG_FORCEHOSTMODE BIT(29) +#define GUSBCFG_TXENDDELAY BIT(28) +#define GUSBCFG_ICTRAFFICPULLREMOVE BIT(27) +#define GUSBCFG_ICUSBCAP BIT(26) +#define GUSBCFG_ULPI_INT_PROT_DIS BIT(25) +#define GUSBCFG_INDICATORPASSTHROUGH BIT(24) +#define GUSBCFG_INDICATORCOMPLEMENT BIT(23) +#define GUSBCFG_TERMSELDLPULSE BIT(22) +#define GUSBCFG_ULPI_INT_VBUS_IND BIT(21) +#define GUSBCFG_ULPI_EXT_VBUS_DRV BIT(20) +#define GUSBCFG_ULPI_CLK_SUSP_M BIT(19) +#define GUSBCFG_ULPI_AUTO_RES BIT(18) +#define GUSBCFG_ULPI_FS_LS BIT(17) +#define GUSBCFG_OTG_UTMI_FS_SEL BIT(16) +#define GUSBCFG_PHY_LP_CLK_SEL BIT(15) +#define GUSBCFG_USBTRDTIM_MASK (0xf << 10) +#define GUSBCFG_USBTRDTIM_SHIFT 10 +#define GUSBCFG_HNPCAP BIT(9) +#define GUSBCFG_SRPCAP BIT(8) +#define GUSBCFG_DDRSEL BIT(7) +#define GUSBCFG_PHYSEL BIT(6) +#define GUSBCFG_FSINTF BIT(5) +#define GUSBCFG_ULPI_UTMI_SEL BIT(4) +#define GUSBCFG_PHYIF16 BIT(3) +#define GUSBCFG_PHYIF8 (0 << 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 BIT(31) +#define GRSTCTL_DMAREQ BIT(30) +#define GRSTCTL_CSFTRST_DONE BIT(29) +#define GRSTCTL_CLOCK_SWITH_TIMER_MASK (0x7 << 11) +#define GRSTCTL_CLOCK_SWITH_TIMER_SHIFT 11 +#define GRSTCTL_CLOCK_SWITH_TIMER_VALUE_19 0x0 +#define GRSTCTL_CLOCK_SWITH_TIMER_VALUE_15 0x1 +#define GRSTCTL_CLOCK_SWITH_TIMER_VALUE_147 0x2 +#define GRSTCTL_CLOCK_SWITH_TIMER_VALUE_50 0x3 +#define GRSTCTL_CLOCK_SWITH_TIMER_VALUE_100 0x4 +#define GRSTCTL_CLOCK_SWITH_TIMER_VALUE_125 0x5 +#define GRSTCTL_CLOCK_SWITH_TIMER_VALUE_200 0x6 +#define GRSTCTL_CLOCK_SWITH_TIMER_VALUE_DIS 0x7 +#define GRSTCTL_CLOCK_SWITH_TIMER(_x) ((_x) << 11) +#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 BIT(5) +#define GRSTCTL_RXFFLSH BIT(4) +#define GRSTCTL_IN_TKNQ_FLSH BIT(3) +#define GRSTCTL_FRMCNTRRST BIT(2) +#define GRSTCTL_HSFTRST BIT(1) +#define GRSTCTL_CSFTRST BIT(0) + +#define GINTSTS HSOTG_REG(0x014) +#define GINTMSK HSOTG_REG(0x018) +#define GINTSTS_WKUPINT BIT(31) +#define GINTSTS_SESSREQINT BIT(30) +#define GINTSTS_DISCONNINT BIT(29) +#define GINTSTS_CONIDSTSCHNG BIT(28) +#define GINTSTS_LPMTRANRCVD BIT(27) +#define GINTSTS_PTXFEMP BIT(26) +#define GINTSTS_HCHINT BIT(25) +#define GINTSTS_PRTINT BIT(24) +#define GINTSTS_RESETDET BIT(23) +#define GINTSTS_FET_SUSP BIT(22) +#define GINTSTS_INCOMPL_IP BIT(21) +#define GINTSTS_INCOMPL_SOOUT BIT(21) +#define GINTSTS_INCOMPL_SOIN BIT(20) +#define GINTSTS_OEPINT BIT(19) +#define GINTSTS_IEPINT BIT(18) +#define GINTSTS_EPMIS BIT(17) +#define GINTSTS_RESTOREDONE BIT(16) +#define GINTSTS_EOPF BIT(15) +#define GINTSTS_ISOUTDROP BIT(14) +#define GINTSTS_ENUMDONE BIT(13) +#define GINTSTS_USBRST BIT(12) +#define GINTSTS_USBSUSP BIT(11) +#define GINTSTS_ERLYSUSP BIT(10) +#define GINTSTS_I2CINT BIT(9) +#define GINTSTS_ULPI_CK_INT BIT(8) +#define GINTSTS_GOUTNAKEFF BIT(7) +#define GINTSTS_GINNAKEFF BIT(6) +#define GINTSTS_NPTXFEMP BIT(5) +#define GINTSTS_RXFLVL BIT(4) +#define GINTSTS_SOF BIT(3) +#define GINTSTS_OTGINT BIT(2) +#define GINTSTS_MODEMIS BIT(1) +#define GINTSTS_CURMODE_HOST BIT(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 1 +#define GRXSTS_PKTSTS_OUTRX 2 +#define GRXSTS_PKTSTS_HCHIN 2 +#define GRXSTS_PKTSTS_OUTDONE 3 +#define GRXSTS_PKTSTS_HCHIN_XFER_COMP 3 +#define GRXSTS_PKTSTS_SETUPDONE 4 +#define GRXSTS_PKTSTS_DATATOGGLEERR 5 +#define GRXSTS_PKTSTS_SETUPRX 6 +#define GRXSTS_PKTSTS_HCHHALTED 7 +#define GRXSTS_HCHNUM_MASK (0xf << 0) +#define GRXSTS_HCHNUM_SHIFT 0 +#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 GRXFSIZ_DEPTH_MASK (0xffff << 0) +#define GRXFSIZ_DEPTH_SHIFT 0 + +#define GNPTXFSIZ HSOTG_REG(0x028) +/* Use FIFOSIZE_* constants to access this register */ + +#define GNPTXSTS HSOTG_REG(0x02C) +#define GNPTXSTS_NP_TXQ_TOP_MASK (0x7f << 24) +#define GNPTXSTS_NP_TXQ_TOP_SHIFT 24 +#define GNPTXSTS_NP_TXQ_SPC_AVAIL_MASK (0xff << 16) +#define GNPTXSTS_NP_TXQ_SPC_AVAIL_SHIFT 16 +#define GNPTXSTS_NP_TXQ_SPC_AVAIL_GET(_v) (((_v) >> 16) & 0xff) +#define GNPTXSTS_NP_TXF_SPC_AVAIL_MASK (0xffff << 0) +#define GNPTXSTS_NP_TXF_SPC_AVAIL_SHIFT 0 +#define GNPTXSTS_NP_TXF_SPC_AVAIL_GET(_v) (((_v) >> 0) & 0xffff) + +#define GI2CCTL HSOTG_REG(0x0030) +#define GI2CCTL_BSYDNE BIT(31) +#define GI2CCTL_RW BIT(30) +#define GI2CCTL_I2CDATSE0 BIT(28) +#define GI2CCTL_I2CDEVADDR_MASK (0x3 << 26) +#define GI2CCTL_I2CDEVADDR_SHIFT 26 +#define GI2CCTL_I2CSUSPCTL BIT(25) +#define GI2CCTL_ACK BIT(24) +#define GI2CCTL_I2CEN BIT(23) +#define GI2CCTL_ADDR_MASK (0x7f << 16) +#define GI2CCTL_ADDR_SHIFT 16 +#define GI2CCTL_REGADDR_MASK (0xff << 8) +#define GI2CCTL_REGADDR_SHIFT 8 +#define GI2CCTL_RWDATA_MASK (0xff << 0) +#define GI2CCTL_RWDATA_SHIFT 0 + +#define GPVNDCTL HSOTG_REG(0x0034) +#define GGPIO HSOTG_REG(0x0038) +#define GGPIO_STM32_OTG_GCCFG_PWRDWN BIT(16) +#define GGPIO_STM32_OTG_GCCFG_VBDEN BIT(21) +#define GGPIO_STM32_OTG_GCCFG_IDEN BIT(22) + +#define GUID HSOTG_REG(0x003c) +#define GSNPSID HSOTG_REG(0x0040) +#define GHWCFG1 HSOTG_REG(0x0044) +#define GSNPSID_ID_MASK GENMASK(31, 16) + +#define GHWCFG2 HSOTG_REG(0x0048) +#define GHWCFG2_OTG_ENABLE_IC_USB BIT(31) +#define GHWCFG2_DEV_TOKEN_Q_DEPTH_MASK (0x1f << 26) +#define GHWCFG2_DEV_TOKEN_Q_DEPTH_SHIFT 26 +#define GHWCFG2_HOST_PERIO_TX_Q_DEPTH_MASK (0x3 << 24) +#define GHWCFG2_HOST_PERIO_TX_Q_DEPTH_SHIFT 24 +#define GHWCFG2_NONPERIO_TX_Q_DEPTH_MASK (0x3 << 22) +#define GHWCFG2_NONPERIO_TX_Q_DEPTH_SHIFT 22 +#define GHWCFG2_MULTI_PROC_INT BIT(20) +#define GHWCFG2_DYNAMIC_FIFO BIT(19) +#define GHWCFG2_PERIO_EP_SUPPORTED BIT(18) +#define GHWCFG2_NUM_HOST_CHAN_MASK (0xf << 14) +#define GHWCFG2_NUM_HOST_CHAN_SHIFT 14 +#define GHWCFG2_NUM_DEV_EP_MASK (0xf << 10) +#define GHWCFG2_NUM_DEV_EP_SHIFT 10 +#define GHWCFG2_FS_PHY_TYPE_MASK (0x3 << 8) +#define GHWCFG2_FS_PHY_TYPE_SHIFT 8 +#define GHWCFG2_FS_PHY_TYPE_NOT_SUPPORTED 0 +#define GHWCFG2_FS_PHY_TYPE_DEDICATED 1 +#define GHWCFG2_FS_PHY_TYPE_SHARED_UTMI 2 +#define GHWCFG2_FS_PHY_TYPE_SHARED_ULPI 3 +#define GHWCFG2_HS_PHY_TYPE_MASK (0x3 << 6) +#define GHWCFG2_HS_PHY_TYPE_SHIFT 6 +#define GHWCFG2_HS_PHY_TYPE_NOT_SUPPORTED 0 +#define GHWCFG2_HS_PHY_TYPE_UTMI 1 +#define GHWCFG2_HS_PHY_TYPE_ULPI 2 +#define GHWCFG2_HS_PHY_TYPE_UTMI_ULPI 3 +#define GHWCFG2_POINT2POINT BIT(5) +#define GHWCFG2_ARCHITECTURE_MASK (0x3 << 3) +#define GHWCFG2_ARCHITECTURE_SHIFT 3 +#define GHWCFG2_SLAVE_ONLY_ARCH 0 +#define GHWCFG2_EXT_DMA_ARCH 1 +#define GHWCFG2_INT_DMA_ARCH 2 +#define GHWCFG2_OP_MODE_MASK (0x7 << 0) +#define GHWCFG2_OP_MODE_SHIFT 0 +#define GHWCFG2_OP_MODE_HNP_SRP_CAPABLE 0 +#define GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE 1 +#define GHWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE 2 +#define GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE 3 +#define GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE 4 +#define GHWCFG2_OP_MODE_SRP_CAPABLE_HOST 5 +#define GHWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST 6 +#define GHWCFG2_OP_MODE_UNDEFINED 7 + +#define GHWCFG3 HSOTG_REG(0x004c) +#define GHWCFG3_DFIFO_DEPTH_MASK (0xffff << 16) +#define GHWCFG3_DFIFO_DEPTH_SHIFT 16 +#define GHWCFG3_OTG_LPM_EN BIT(15) +#define GHWCFG3_BC_SUPPORT BIT(14) +#define GHWCFG3_OTG_ENABLE_HSIC BIT(13) +#define GHWCFG3_ADP_SUPP BIT(12) +#define GHWCFG3_SYNCH_RESET_TYPE BIT(11) +#define GHWCFG3_OPTIONAL_FEATURES BIT(10) +#define GHWCFG3_VENDOR_CTRL_IF BIT(9) +#define GHWCFG3_I2C BIT(8) +#define GHWCFG3_OTG_FUNC BIT(7) +#define GHWCFG3_PACKET_SIZE_CNTR_WIDTH_MASK (0x7 << 4) +#define GHWCFG3_PACKET_SIZE_CNTR_WIDTH_SHIFT 4 +#define GHWCFG3_XFER_SIZE_CNTR_WIDTH_MASK (0xf << 0) +#define GHWCFG3_XFER_SIZE_CNTR_WIDTH_SHIFT 0 + +#define GHWCFG4 HSOTG_REG(0x0050) +#define GHWCFG4_DESC_DMA_DYN BIT(31) +#define GHWCFG4_DESC_DMA BIT(30) +#define GHWCFG4_NUM_IN_EPS_MASK (0xf << 26) +#define GHWCFG4_NUM_IN_EPS_SHIFT 26 +#define GHWCFG4_DED_FIFO_EN BIT(25) +#define GHWCFG4_DED_FIFO_SHIFT 25 +#define GHWCFG4_SESSION_END_FILT_EN BIT(24) +#define GHWCFG4_B_VALID_FILT_EN BIT(23) +#define GHWCFG4_A_VALID_FILT_EN BIT(22) +#define GHWCFG4_VBUS_VALID_FILT_EN BIT(21) +#define GHWCFG4_IDDIG_FILT_EN BIT(20) +#define GHWCFG4_NUM_DEV_MODE_CTRL_EP_MASK (0xf << 16) +#define GHWCFG4_NUM_DEV_MODE_CTRL_EP_SHIFT 16 +#define GHWCFG4_UTMI_PHY_DATA_WIDTH_MASK (0x3 << 14) +#define GHWCFG4_UTMI_PHY_DATA_WIDTH_SHIFT 14 +#define GHWCFG4_UTMI_PHY_DATA_WIDTH_8 0 +#define GHWCFG4_UTMI_PHY_DATA_WIDTH_16 1 +#define GHWCFG4_UTMI_PHY_DATA_WIDTH_8_OR_16 2 +#define GHWCFG4_ACG_SUPPORTED BIT(12) +#define GHWCFG4_IPG_ISOC_SUPPORTED BIT(11) +#define GHWCFG4_SERVICE_INTERVAL_SUPPORTED BIT(10) +#define GHWCFG4_XHIBER BIT(7) +#define GHWCFG4_HIBER BIT(6) +#define GHWCFG4_MIN_AHB_FREQ BIT(5) +#define GHWCFG4_POWER_OPTIMIZ BIT(4) +#define GHWCFG4_NUM_DEV_PERIO_IN_EP_MASK (0xf << 0) +#define GHWCFG4_NUM_DEV_PERIO_IN_EP_SHIFT 0 + +#define GLPMCFG HSOTG_REG(0x0054) +#define GLPMCFG_INVSELHSIC BIT(31) +#define GLPMCFG_HSICCON BIT(30) +#define GLPMCFG_RSTRSLPSTS BIT(29) +#define GLPMCFG_ENBESL BIT(28) +#define GLPMCFG_LPM_RETRYCNT_STS_MASK (0x7 << 25) +#define GLPMCFG_LPM_RETRYCNT_STS_SHIFT 25 +#define GLPMCFG_SNDLPM BIT(24) +#define GLPMCFG_RETRY_CNT_MASK (0x7 << 21) +#define GLPMCFG_RETRY_CNT_SHIFT 21 +#define GLPMCFG_LPM_REJECT_CTRL_CONTROL BIT(21) +#define GLPMCFG_LPM_ACCEPT_CTRL_ISOC BIT(22) +#define GLPMCFG_LPM_CHNL_INDX_MASK (0xf << 17) +#define GLPMCFG_LPM_CHNL_INDX_SHIFT 17 +#define GLPMCFG_L1RESUMEOK BIT(16) +#define GLPMCFG_SLPSTS BIT(15) +#define GLPMCFG_COREL1RES_MASK (0x3 << 13) +#define GLPMCFG_COREL1RES_SHIFT 13 +#define GLPMCFG_HIRD_THRES_MASK (0x1f << 8) +#define GLPMCFG_HIRD_THRES_SHIFT 8 +#define GLPMCFG_HIRD_THRES_EN (0x10 << 8) +#define GLPMCFG_ENBLSLPM BIT(7) +#define GLPMCFG_BREMOTEWAKE BIT(6) +#define GLPMCFG_HIRD_MASK (0xf << 2) +#define GLPMCFG_HIRD_SHIFT 2 +#define GLPMCFG_APPL1RES BIT(1) +#define GLPMCFG_LPMCAP BIT(0) + +#define GPWRDN HSOTG_REG(0x0058) + +#define GPWRDN_ULPI_LATCH_EN_DURING_HIB_ENTRY BIT(29) +#define GPWRDN_MULT_VAL_ID_BC_MASK (0x1f << 24) +#define GPWRDN_MULT_VAL_ID_BC_SHIFT 24 +#define GPWRDN_ADP_INT BIT(23) +#define GPWRDN_BSESSVLD BIT(22) +#define GPWRDN_IDSTS BIT(21) +#define GPWRDN_LINESTATE_MASK (0x3 << 19) +#define GPWRDN_LINESTATE_SHIFT 19 +#define GPWRDN_STS_CHGINT_MSK BIT(18) +#define GPWRDN_STS_CHGINT BIT(17) +#define GPWRDN_SRP_DET_MSK BIT(16) +#define GPWRDN_SRP_DET BIT(15) +#define GPWRDN_CONNECT_DET_MSK BIT(14) +#define GPWRDN_CONNECT_DET BIT(13) +#define GPWRDN_DISCONN_DET_MSK BIT(12) +#define GPWRDN_DISCONN_DET BIT(11) +#define GPWRDN_RST_DET_MSK BIT(10) +#define GPWRDN_RST_DET BIT(9) +#define GPWRDN_LNSTSCHG_MSK BIT(8) +#define GPWRDN_LNSTSCHG BIT(7) +#define GPWRDN_DIS_VBUS BIT(6) +#define GPWRDN_PWRDNSWTCH BIT(5) +#define GPWRDN_PWRDNRSTN BIT(4) +#define GPWRDN_PWRDNCLMP BIT(3) +#define GPWRDN_RESTORE BIT(2) +#define GPWRDN_PMUACTV BIT(1) +#define GPWRDN_PMUINTSEL BIT(0) + +#define GDFIFOCFG HSOTG_REG(0x005c) +#define GDFIFOCFG_EPINFOBASE_MASK (0xffff << 16) +#define GDFIFOCFG_EPINFOBASE_SHIFT 16 +#define GDFIFOCFG_GDFIFOCFG_MASK (0xffff << 0) +#define GDFIFOCFG_GDFIFOCFG_SHIFT 0 + +#define ADPCTL HSOTG_REG(0x0060) +#define ADPCTL_AR_MASK (0x3 << 27) +#define ADPCTL_AR_SHIFT 27 +#define ADPCTL_ADP_TMOUT_INT_MSK BIT(26) +#define ADPCTL_ADP_SNS_INT_MSK BIT(25) +#define ADPCTL_ADP_PRB_INT_MSK BIT(24) +#define ADPCTL_ADP_TMOUT_INT BIT(23) +#define ADPCTL_ADP_SNS_INT BIT(22) +#define ADPCTL_ADP_PRB_INT BIT(21) +#define ADPCTL_ADPENA BIT(20) +#define ADPCTL_ADPRES BIT(19) +#define ADPCTL_ENASNS BIT(18) +#define ADPCTL_ENAPRB BIT(17) +#define ADPCTL_RTIM_MASK (0x7ff << 6) +#define ADPCTL_RTIM_SHIFT 6 +#define ADPCTL_PRB_PER_MASK (0x3 << 4) +#define ADPCTL_PRB_PER_SHIFT 4 +#define ADPCTL_PRB_DELTA_MASK (0x3 << 2) +#define ADPCTL_PRB_DELTA_SHIFT 2 +#define ADPCTL_PRB_DSCHRG_MASK (0x3 << 0) +#define ADPCTL_PRB_DSCHRG_SHIFT 0 + +#define GREFCLK HSOTG_REG(0x0064) +#define GREFCLK_REFCLKPER_MASK (0x1ffff << 15) +#define GREFCLK_REFCLKPER_SHIFT 15 +#define GREFCLK_REF_CLK_MODE BIT(14) +#define GREFCLK_SOF_CNT_WKUP_ALERT_MASK (0x3ff) +#define GREFCLK_SOF_CNT_WKUP_ALERT_SHIFT 0 + +#define GINTMSK2 HSOTG_REG(0x0068) +#define GINTMSK2_WKUP_ALERT_INT_MSK BIT(0) + +#define GINTSTS2 HSOTG_REG(0x006c) +#define GINTSTS2_WKUP_ALERT_INT BIT(0) + +#define HPTXFSIZ HSOTG_REG(0x100) +/* Use FIFOSIZE_* constants to access this register */ + +#define DPTXFSIZN(_a) HSOTG_REG(0x104 + (((_a) - 1) * 4)) +/* Use FIFOSIZE_* constants to access this register */ + +/* These apply to the GNPTXFSIZ, HPTXFSIZ and DPTXFSIZN registers */ +#define FIFOSIZE_DEPTH_MASK (0xffff << 16) +#define FIFOSIZE_DEPTH_SHIFT 16 +#define FIFOSIZE_STARTADDR_MASK (0xffff << 0) +#define FIFOSIZE_STARTADDR_SHIFT 0 +#define FIFOSIZE_DEPTH_GET(_x) (((_x) >> 16) & 0xffff) + +/* Device mode registers */ + +#define DCFG HSOTG_REG(0x800) +#define DCFG_DESCDMA_EN BIT(23) +#define DCFG_EPMISCNT_MASK (0x1f << 18) +#define DCFG_EPMISCNT_SHIFT 18 +#define DCFG_EPMISCNT_LIMIT 0x1f +#define DCFG_EPMISCNT(_x) ((_x) << 18) +#define DCFG_IPG_ISOC_SUPPORDED BIT(17) +#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_NZ_STS_OUT_HSHK BIT(2) +#define DCFG_DEVSPD_MASK (0x3 << 0) +#define DCFG_DEVSPD_SHIFT 0 +#define DCFG_DEVSPD_HS 0 +#define DCFG_DEVSPD_FS 1 +#define DCFG_DEVSPD_LS 2 +#define DCFG_DEVSPD_FS48 3 + +#define DCTL HSOTG_REG(0x804) +#define DCTL_SERVICE_INTERVAL_SUPPORTED BIT(19) +#define DCTL_PWRONPRGDONE BIT(11) +#define DCTL_CGOUTNAK BIT(10) +#define DCTL_SGOUTNAK BIT(9) +#define DCTL_CGNPINNAK BIT(8) +#define DCTL_SGNPINNAK BIT(7) +#define DCTL_TSTCTL_MASK (0x7 << 4) +#define DCTL_TSTCTL_SHIFT 4 +#define DCTL_GOUTNAKSTS BIT(3) +#define DCTL_GNPINNAKSTS BIT(2) +#define DCTL_SFTDISCON BIT(1) +#define DCTL_RMTWKUPSIG BIT(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 BIT(3) +#define DSTS_ENUMSPD_MASK (0x3 << 1) +#define DSTS_ENUMSPD_SHIFT 1 +#define DSTS_ENUMSPD_HS 0 +#define DSTS_ENUMSPD_FS 1 +#define DSTS_ENUMSPD_LS 2 +#define DSTS_ENUMSPD_FS48 3 +#define DSTS_SUSPSTS BIT(0) + +#define DIEPMSK HSOTG_REG(0x810) +#define DIEPMSK_NAKMSK BIT(13) +#define DIEPMSK_BNAININTRMSK BIT(9) +#define DIEPMSK_TXFIFOUNDRNMSK BIT(8) +#define DIEPMSK_TXFIFOEMPTY BIT(7) +#define DIEPMSK_INEPNAKEFFMSK BIT(6) +#define DIEPMSK_INTKNEPMISMSK BIT(5) +#define DIEPMSK_INTKNTXFEMPMSK BIT(4) +#define DIEPMSK_TIMEOUTMSK BIT(3) +#define DIEPMSK_AHBERRMSK BIT(2) +#define DIEPMSK_EPDISBLDMSK BIT(1) +#define DIEPMSK_XFERCOMPLMSK BIT(0) + +#define DOEPMSK HSOTG_REG(0x814) +#define DOEPMSK_BNAMSK BIT(9) +#define DOEPMSK_BACK2BACKSETUP BIT(6) +#define DOEPMSK_STSPHSERCVDMSK BIT(5) +#define DOEPMSK_OUTTKNEPDISMSK BIT(4) +#define DOEPMSK_SETUPMSK BIT(3) +#define DOEPMSK_AHBERRMSK BIT(2) +#define DOEPMSK_EPDISBLDMSK BIT(1) +#define DOEPMSK_XFERCOMPLMSK BIT(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 DIEPEMPMSK HSOTG_REG(0x834) + +#define DVBUSDIS HSOTG_REG(0x828) +#define DVBUSPULSE HSOTG_REG(0x82C) + +#define DIEPCTL0 HSOTG_REG(0x900) +#define DIEPCTL(_a) HSOTG_REG(0x900 + ((_a) * 0x20)) + +#define DOEPCTL0 HSOTG_REG(0xB00) +#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 different for EP0 + */ +#define D0EPCTL_MPS_MASK (0x3 << 0) +#define D0EPCTL_MPS_SHIFT 0 +#define D0EPCTL_MPS_64 0 +#define D0EPCTL_MPS_32 1 +#define D0EPCTL_MPS_16 2 +#define D0EPCTL_MPS_8 3 + +#define DXEPCTL_EPENA BIT(31) +#define DXEPCTL_EPDIS BIT(30) +#define DXEPCTL_SETD1PID BIT(29) +#define DXEPCTL_SETODDFR BIT(29) +#define DXEPCTL_SETD0PID BIT(28) +#define DXEPCTL_SETEVENFR BIT(28) +#define DXEPCTL_SNAK BIT(27) +#define DXEPCTL_CNAK BIT(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 BIT(21) +#define DXEPCTL_SNP BIT(20) +#define DXEPCTL_EPTYPE_MASK (0x3 << 18) +#define DXEPCTL_EPTYPE_CONTROL (0x0 << 18) +#define DXEPCTL_EPTYPE_ISO (0x1 << 18) +#define DXEPCTL_EPTYPE_BULK (0x2 << 18) +#define DXEPCTL_EPTYPE_INTERRUPT (0x3 << 18) + +#define DXEPCTL_NAKSTS BIT(17) +#define DXEPCTL_DPID BIT(16) +#define DXEPCTL_EOFRNUM BIT(16) +#define DXEPCTL_USBACTEP BIT(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_SETUP_RCVD BIT(15) +#define DXEPINT_NYETINTRPT BIT(14) +#define DXEPINT_NAKINTRPT BIT(13) +#define DXEPINT_BBLEERRINTRPT BIT(12) +#define DXEPINT_PKTDRPSTS BIT(11) +#define DXEPINT_BNAINTR BIT(9) +#define DXEPINT_TXFIFOUNDRN BIT(8) +#define DXEPINT_OUTPKTERR BIT(8) +#define DXEPINT_TXFEMP BIT(7) +#define DXEPINT_INEPNAKEFF BIT(6) +#define DXEPINT_BACK2BACKSETUP BIT(6) +#define DXEPINT_INTKNEPMIS BIT(5) +#define DXEPINT_STSPHSERCVD BIT(5) +#define DXEPINT_INTKNTXFEMP BIT(4) +#define DXEPINT_OUTTKNEPDIS BIT(4) +#define DXEPINT_TIMEOUT BIT(3) +#define DXEPINT_SETUP BIT(3) +#define DXEPINT_AHBERR BIT(2) +#define DXEPINT_EPDISBLD BIT(1) +#define DXEPINT_XFERCOMPL BIT(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 BIT(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_LIMIT 0x3ff +#define DXEPTSIZ_PKTCNT_GET(_v) (((_v) >> 19) & 0x3ff) +#define DXEPTSIZ_PKTCNT(_x) ((_x) << 19) +#define DXEPTSIZ_XFERSIZE_MASK (0x7ffff << 0) +#define DXEPTSIZ_XFERSIZE_SHIFT 0 +#define DXEPTSIZ_XFERSIZE_LIMIT 0x7ffff +#define DXEPTSIZ_XFERSIZE_GET(_v) (((_v) >> 0) & 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 PCGCTL HSOTG_REG(0x0e00) +#define PCGCTL_IF_DEV_MODE BIT(31) +#define PCGCTL_P2HD_PRT_SPD_MASK (0x3 << 29) +#define PCGCTL_P2HD_PRT_SPD_SHIFT 29 +#define PCGCTL_P2HD_DEV_ENUM_SPD_MASK (0x3 << 27) +#define PCGCTL_P2HD_DEV_ENUM_SPD_SHIFT 27 +#define PCGCTL_MAC_DEV_ADDR_MASK (0x7f << 20) +#define PCGCTL_MAC_DEV_ADDR_SHIFT 20 +#define PCGCTL_MAX_TERMSEL BIT(19) +#define PCGCTL_MAX_XCVRSELECT_MASK (0x3 << 17) +#define PCGCTL_MAX_XCVRSELECT_SHIFT 17 +#define PCGCTL_PORT_POWER BIT(16) +#define PCGCTL_PRT_CLK_SEL_MASK (0x3 << 14) +#define PCGCTL_PRT_CLK_SEL_SHIFT 14 +#define PCGCTL_ESS_REG_RESTORED BIT(13) +#define PCGCTL_EXTND_HIBER_SWITCH BIT(12) +#define PCGCTL_EXTND_HIBER_PWRCLMP BIT(11) +#define PCGCTL_ENBL_EXTND_HIBER BIT(10) +#define PCGCTL_RESTOREMODE BIT(9) +#define PCGCTL_RESETAFTSUSP BIT(8) +#define PCGCTL_DEEP_SLEEP BIT(7) +#define PCGCTL_PHY_IN_SLEEP BIT(6) +#define PCGCTL_ENBL_SLEEP_GATING BIT(5) +#define PCGCTL_RSTPDWNMODULE BIT(3) +#define PCGCTL_PWRCLMP BIT(2) +#define PCGCTL_GATEHCLK BIT(1) +#define PCGCTL_STOPPCLK BIT(0) + +#define PCGCCTL1 HSOTG_REG(0xe04) +#define PCGCCTL1_TIMER (0x3 << 1) +#define PCGCCTL1_GATEEN BIT(0) + +#define EPFIFO(_a) HSOTG_REG(0x1000 + ((_a) * 0x1000)) + +/* Host Mode Registers */ + +#define HCFG HSOTG_REG(0x0400) +#define HCFG_MODECHTIMEN BIT(31) +#define HCFG_PERSCHEDENA BIT(26) +#define HCFG_FRLISTEN_MASK (0x3 << 24) +#define HCFG_FRLISTEN_SHIFT 24 +#define HCFG_FRLISTEN_8 (0 << 24) +#define FRLISTEN_8_SIZE 8 +#define HCFG_FRLISTEN_16 BIT(24) +#define FRLISTEN_16_SIZE 16 +#define HCFG_FRLISTEN_32 (2 << 24) +#define FRLISTEN_32_SIZE 32 +#define HCFG_FRLISTEN_64 (3 << 24) +#define FRLISTEN_64_SIZE 64 +#define HCFG_DESCDMA BIT(23) +#define HCFG_RESVALID_MASK (0xff << 8) +#define HCFG_RESVALID_SHIFT 8 +#define HCFG_ENA32KHZ BIT(7) +#define HCFG_FSLSSUPP BIT(2) +#define HCFG_FSLSPCLKSEL_MASK (0x3 << 0) +#define HCFG_FSLSPCLKSEL_SHIFT 0 +#define HCFG_FSLSPCLKSEL_30_60_MHZ 0 +#define HCFG_FSLSPCLKSEL_48_MHZ 1 +#define HCFG_FSLSPCLKSEL_6_MHZ 2 + +#define HFIR HSOTG_REG(0x0404) +#define HFIR_FRINT_MASK (0xffff << 0) +#define HFIR_FRINT_SHIFT 0 +#define HFIR_RLDCTRL BIT(16) + +#define HFNUM HSOTG_REG(0x0408) +#define HFNUM_FRREM_MASK (0xffff << 16) +#define HFNUM_FRREM_SHIFT 16 +#define HFNUM_FRNUM_MASK (0xffff << 0) +#define HFNUM_FRNUM_SHIFT 0 +#define HFNUM_MAX_FRNUM 0x3fff + +#define HPTXSTS HSOTG_REG(0x0410) +#define TXSTS_QTOP_ODD BIT(31) +#define TXSTS_QTOP_CHNEP_MASK (0xf << 27) +#define TXSTS_QTOP_CHNEP_SHIFT 27 +#define TXSTS_QTOP_TOKEN_MASK (0x3 << 25) +#define TXSTS_QTOP_TOKEN_SHIFT 25 +#define TXSTS_QTOP_TERMINATE BIT(24) +#define TXSTS_QSPCAVAIL_MASK (0x7f << 16) +#define TXSTS_QSPCAVAIL_SHIFT 16 +#define TXSTS_FSPCAVAIL_MASK (0xffff << 0) +#define TXSTS_FSPCAVAIL_SHIFT 0 + +#define HAINT HSOTG_REG(0x0414) +#define HAINTMSK HSOTG_REG(0x0418) +#define HFLBADDR HSOTG_REG(0x041c) + +#define HPRT0 HSOTG_REG(0x0440) +#define HPRT0_SPD_MASK (0x3 << 17) +#define HPRT0_SPD_SHIFT 17 +#define HPRT0_SPD_HIGH_SPEED 0 +#define HPRT0_SPD_FULL_SPEED 1 +#define HPRT0_SPD_LOW_SPEED 2 +#define HPRT0_TSTCTL_MASK (0xf << 13) +#define HPRT0_TSTCTL_SHIFT 13 +#define HPRT0_PWR BIT(12) +#define HPRT0_LNSTS_MASK (0x3 << 10) +#define HPRT0_LNSTS_SHIFT 10 +#define HPRT0_RST BIT(8) +#define HPRT0_SUSP BIT(7) +#define HPRT0_RES BIT(6) +#define HPRT0_OVRCURRCHG BIT(5) +#define HPRT0_OVRCURRACT BIT(4) +#define HPRT0_ENACHG BIT(3) +#define HPRT0_ENA BIT(2) +#define HPRT0_CONNDET BIT(1) +#define HPRT0_CONNSTS BIT(0) + +#define HCCHAR(_ch) HSOTG_REG(0x0500 + 0x20 * (_ch)) +#define HCCHAR_CHENA BIT(31) +#define HCCHAR_CHDIS BIT(30) +#define HCCHAR_ODDFRM BIT(29) +#define HCCHAR_DEVADDR_MASK (0x7f << 22) +#define HCCHAR_DEVADDR_SHIFT 22 +#define HCCHAR_MULTICNT_MASK (0x3 << 20) +#define HCCHAR_MULTICNT_SHIFT 20 +#define HCCHAR_EPTYPE_MASK (0x3 << 18) +#define HCCHAR_EPTYPE_SHIFT 18 +#define HCCHAR_LSPDDEV BIT(17) +#define HCCHAR_EPDIR BIT(15) +#define HCCHAR_EPNUM_MASK (0xf << 11) +#define HCCHAR_EPNUM_SHIFT 11 +#define HCCHAR_MPS_MASK (0x7ff << 0) +#define HCCHAR_MPS_SHIFT 0 + +#define HCSPLT(_ch) HSOTG_REG(0x0504 + 0x20 * (_ch)) +#define HCSPLT_SPLTENA BIT(31) +#define HCSPLT_COMPSPLT BIT(16) +#define HCSPLT_XACTPOS_MASK (0x3 << 14) +#define HCSPLT_XACTPOS_SHIFT 14 +#define HCSPLT_XACTPOS_MID 0 +#define HCSPLT_XACTPOS_END 1 +#define HCSPLT_XACTPOS_BEGIN 2 +#define HCSPLT_XACTPOS_ALL 3 +#define HCSPLT_HUBADDR_MASK (0x7f << 7) +#define HCSPLT_HUBADDR_SHIFT 7 +#define HCSPLT_PRTADDR_MASK (0x7f << 0) +#define HCSPLT_PRTADDR_SHIFT 0 + +#define HCINT(_ch) HSOTG_REG(0x0508 + 0x20 * (_ch)) +#define HCINTMSK(_ch) HSOTG_REG(0x050c + 0x20 * (_ch)) +#define HCINTMSK_RESERVED14_31 (0x3ffff << 14) +#define HCINTMSK_FRM_LIST_ROLL BIT(13) +#define HCINTMSK_XCS_XACT BIT(12) +#define HCINTMSK_BNA BIT(11) +#define HCINTMSK_DATATGLERR BIT(10) +#define HCINTMSK_FRMOVRUN BIT(9) +#define HCINTMSK_BBLERR BIT(8) +#define HCINTMSK_XACTERR BIT(7) +#define HCINTMSK_NYET BIT(6) +#define HCINTMSK_ACK BIT(5) +#define HCINTMSK_NAK BIT(4) +#define HCINTMSK_STALL BIT(3) +#define HCINTMSK_AHBERR BIT(2) +#define HCINTMSK_CHHLTD BIT(1) +#define HCINTMSK_XFERCOMPL BIT(0) + +#define HCTSIZ(_ch) HSOTG_REG(0x0510 + 0x20 * (_ch)) +#define TSIZ_DOPNG BIT(31) +#define TSIZ_SC_MC_PID_MASK (0x3 << 29) +#define TSIZ_SC_MC_PID_SHIFT 29 +#define TSIZ_SC_MC_PID_DATA0 0 +#define TSIZ_SC_MC_PID_DATA2 1 +#define TSIZ_SC_MC_PID_DATA1 2 +#define TSIZ_SC_MC_PID_MDATA 3 +#define TSIZ_SC_MC_PID_SETUP 3 +#define TSIZ_PKTCNT_MASK (0x3ff << 19) +#define TSIZ_PKTCNT_SHIFT 19 +#define TSIZ_NTD_MASK (0xff << 8) +#define TSIZ_NTD_SHIFT 8 +#define TSIZ_SCHINFO_MASK (0xff << 0) +#define TSIZ_SCHINFO_SHIFT 0 +#define TSIZ_XFERSIZE_MASK (0x7ffff << 0) +#define TSIZ_XFERSIZE_SHIFT 0 + +#define HCDMA(_ch) HSOTG_REG(0x0514 + 0x20 * (_ch)) + +#define HCDMAB(_ch) HSOTG_REG(0x051c + 0x20 * (_ch)) + +#define HCFIFO(_ch) HSOTG_REG(0x1000 + 0x1000 * (_ch)) + +/** + * struct dwc2_dma_desc - DMA descriptor structure, + * used for both host and gadget modes + * + * @status: DMA descriptor status quadlet + * @buf: DMA descriptor data buffer pointer + * + * DMA Descriptor structure contains two quadlets: + * Status quadlet and Data buffer pointer. + */ +struct dwc2_dma_desc { + u32 status; + u32 buf; +} __packed; + +/* Host Mode DMA descriptor status quadlet */ + +#define HOST_DMA_A BIT(31) +#define HOST_DMA_STS_MASK (0x3 << 28) +#define HOST_DMA_STS_SHIFT 28 +#define HOST_DMA_STS_PKTERR BIT(28) +#define HOST_DMA_EOL BIT(26) +#define HOST_DMA_IOC BIT(25) +#define HOST_DMA_SUP BIT(24) +#define HOST_DMA_ALT_QTD BIT(23) +#define HOST_DMA_QTD_OFFSET_MASK (0x3f << 17) +#define HOST_DMA_QTD_OFFSET_SHIFT 17 +#define HOST_DMA_ISOC_NBYTES_MASK (0xfff << 0) +#define HOST_DMA_ISOC_NBYTES_SHIFT 0 +#define HOST_DMA_NBYTES_MASK (0x1ffff << 0) +#define HOST_DMA_NBYTES_SHIFT 0 +#define HOST_DMA_NBYTES_LIMIT 131071 + +/* Device Mode DMA descriptor status quadlet */ + +#define DEV_DMA_BUFF_STS_MASK (0x3 << 30) +#define DEV_DMA_BUFF_STS_SHIFT 30 +#define DEV_DMA_BUFF_STS_HREADY 0 +#define DEV_DMA_BUFF_STS_DMABUSY 1 +#define DEV_DMA_BUFF_STS_DMADONE 2 +#define DEV_DMA_BUFF_STS_HBUSY 3 +#define DEV_DMA_STS_MASK (0x3 << 28) +#define DEV_DMA_STS_SHIFT 28 +#define DEV_DMA_STS_SUCC 0 +#define DEV_DMA_STS_BUFF_FLUSH 1 +#define DEV_DMA_STS_BUFF_ERR 3 +#define DEV_DMA_L BIT(27) +#define DEV_DMA_SHORT BIT(26) +#define DEV_DMA_IOC BIT(25) +#define DEV_DMA_SR BIT(24) +#define DEV_DMA_MTRF BIT(23) +#define DEV_DMA_ISOC_PID_MASK (0x3 << 23) +#define DEV_DMA_ISOC_PID_SHIFT 23 +#define DEV_DMA_ISOC_PID_DATA0 0 +#define DEV_DMA_ISOC_PID_DATA2 1 +#define DEV_DMA_ISOC_PID_DATA1 2 +#define DEV_DMA_ISOC_PID_MDATA 3 +#define DEV_DMA_ISOC_FRNUM_MASK (0x7ff << 12) +#define DEV_DMA_ISOC_FRNUM_SHIFT 12 +#define DEV_DMA_ISOC_TX_NBYTES_MASK (0xfff << 0) +#define DEV_DMA_ISOC_TX_NBYTES_LIMIT 0xfff +#define DEV_DMA_ISOC_RX_NBYTES_MASK (0x7ff << 0) +#define DEV_DMA_ISOC_RX_NBYTES_LIMIT 0x7ff +#define DEV_DMA_ISOC_NBYTES_SHIFT 0 +#define DEV_DMA_NBYTES_MASK (0xffff << 0) +#define DEV_DMA_NBYTES_SHIFT 0 +#define DEV_DMA_NBYTES_LIMIT 0xffff + +#define MAX_DMA_DESC_NUM_GENERIC 64 +#define MAX_DMA_DESC_NUM_HS_ISOC 256 + +#endif /* __DWC2_HW_H__ */ diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c new file mode 100644 index 000000000..091bfcfef --- /dev/null +++ b/drivers/usb/dwc2/params.c @@ -0,0 +1,1083 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * Copyright (C) 2004-2016 Synopsys, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "core.h" + +#define PCI_PRODUCT_ID_HAPS_HSOTG 0xabc0 +#define PCI_DEVICE_ID_LOONGSON_DWC2 0x7a04 + +static void dwc2_set_bcm_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->host_rx_fifo_size = 774; + p->max_transfer_size = 65535; + p->max_packet_count = 511; + p->ahbcfg = 0x10; +} + +static void dwc2_set_his_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->host_rx_fifo_size = 512; + p->host_nperio_tx_fifo_size = 512; + p->host_perio_tx_fifo_size = 512; + p->max_transfer_size = 65535; + p->max_packet_count = 511; + p->host_channels = 16; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->phy_utmi_width = 8; + p->i2c_enable = false; + p->reload_ctl = false; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << + GAHBCFG_HBSTLEN_SHIFT; + p->change_speed_quirk = true; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; +} + +static void dwc2_set_jz4775_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->phy_utmi_width = 16; + p->activate_ingenic_overcurrent_detection = + !device_property_read_bool(hsotg->dev, "disable-over-current"); +} + +static void dwc2_set_loongson_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->phy_utmi_width = 8; + p->power_down = DWC2_POWER_DOWN_PARAM_PARTIAL; +} + +static void dwc2_set_x1600_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->host_channels = 16; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->phy_utmi_width = 16; + p->activate_ingenic_overcurrent_detection = + !device_property_read_bool(hsotg->dev, "disable-over-current"); +} + +static void dwc2_set_x2000_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->host_rx_fifo_size = 1024; + p->host_nperio_tx_fifo_size = 1024; + p->host_perio_tx_fifo_size = 1024; + p->host_channels = 16; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->phy_utmi_width = 16; + p->activate_ingenic_overcurrent_detection = + !device_property_read_bool(hsotg->dev, "disable-over-current"); +} + +static void dwc2_set_s3c6400_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; + p->no_clock_gating = true; + p->phy_utmi_width = 8; +} + +static void dwc2_set_socfpga_agilex_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; + p->no_clock_gating = true; +} + +static void dwc2_set_rk_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->host_rx_fifo_size = 525; + p->host_nperio_tx_fifo_size = 128; + p->host_perio_tx_fifo_size = 256; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << + GAHBCFG_HBSTLEN_SHIFT; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; + p->lpm = false; + p->lpm_clock_gating = false; + p->besl = false; + p->hird_threshold_en = false; + p->no_clock_gating = true; +} + +static void dwc2_set_ltq_danube_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; +} + +static void dwc2_set_ltq_ase_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->host_rx_fifo_size = 288; + p->host_nperio_tx_fifo_size = 128; + p->host_perio_tx_fifo_size = 96; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << + GAHBCFG_HBSTLEN_SHIFT; +} + +static void dwc2_set_ltq_xrx200_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->host_rx_fifo_size = 288; + p->host_nperio_tx_fifo_size = 128; + p->host_perio_tx_fifo_size = 136; +} + +static void dwc2_set_amlogic_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->host_rx_fifo_size = 512; + p->host_nperio_tx_fifo_size = 500; + p->host_perio_tx_fifo_size = 500; + p->host_channels = 16; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR8 << + GAHBCFG_HBSTLEN_SHIFT; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; +} + +static void dwc2_set_amlogic_g12a_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->lpm = false; + p->lpm_clock_gating = false; + p->besl = false; + p->hird_threshold_en = false; +} + +static void dwc2_set_amlogic_a1_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->host_rx_fifo_size = 192; + p->host_nperio_tx_fifo_size = 128; + p->host_perio_tx_fifo_size = 128; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->phy_utmi_width = 8; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR8 << GAHBCFG_HBSTLEN_SHIFT; + p->lpm = false; + p->lpm_clock_gating = false; + p->besl = false; + p->hird_threshold_en = false; +} + +static void dwc2_set_amcc_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; +} + +static void dwc2_set_cv1800_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->host_dma = false; + p->g_dma = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->phy_utmi_width = 16; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; + p->lpm = false; + p->lpm_clock_gating = false; + p->besl = false; + p->hird_threshold_en = false; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; +} + +static void dwc2_set_stm32f4x9_fsotg_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->speed = DWC2_SPEED_PARAM_FULL; + p->host_rx_fifo_size = 128; + p->host_nperio_tx_fifo_size = 96; + p->host_perio_tx_fifo_size = 96; + p->max_packet_count = 256; + p->phy_type = DWC2_PHY_TYPE_PARAM_FS; + p->i2c_enable = false; + p->activate_stm_fs_transceiver = true; +} + +static void dwc2_set_stm32f7_hsotg_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->host_rx_fifo_size = 622; + p->host_nperio_tx_fifo_size = 128; + p->host_perio_tx_fifo_size = 256; +} + +static void dwc2_set_stm32mp15_fsotg_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->otg_caps.otg_rev = 0x200; + p->speed = DWC2_SPEED_PARAM_FULL; + p->host_rx_fifo_size = 128; + p->host_nperio_tx_fifo_size = 96; + p->host_perio_tx_fifo_size = 96; + p->max_packet_count = 256; + p->phy_type = DWC2_PHY_TYPE_PARAM_FS; + p->i2c_enable = false; + p->activate_stm_fs_transceiver = true; + p->activate_stm_id_vb_detection = true; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; + p->host_support_fs_ls_low_power = true; + p->host_ls_low_power_phy_clk = true; +} + +static void dwc2_set_stm32mp15_hsotg_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->otg_caps.srp_support = false; + p->otg_caps.otg_rev = 0x200; + p->activate_stm_id_vb_detection = !device_property_read_bool(hsotg->dev, "usb-role-switch"); + p->host_rx_fifo_size = 440; + p->host_nperio_tx_fifo_size = 256; + p->host_perio_tx_fifo_size = 256; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; + p->lpm = false; + p->lpm_clock_gating = false; + p->besl = false; + p->hird_threshold_en = false; +} + +const struct of_device_id dwc2_of_match_table[] = { + { .compatible = "brcm,bcm2835-usb", .data = dwc2_set_bcm_params }, + { .compatible = "hisilicon,hi6220-usb", .data = dwc2_set_his_params }, + { .compatible = "ingenic,jz4775-otg", .data = dwc2_set_jz4775_params }, + { .compatible = "ingenic,jz4780-otg", .data = dwc2_set_jz4775_params }, + { .compatible = "ingenic,x1000-otg", .data = dwc2_set_jz4775_params }, + { .compatible = "ingenic,x1600-otg", .data = dwc2_set_x1600_params }, + { .compatible = "ingenic,x1700-otg", .data = dwc2_set_x1600_params }, + { .compatible = "ingenic,x1830-otg", .data = dwc2_set_x1600_params }, + { .compatible = "ingenic,x2000-otg", .data = dwc2_set_x2000_params }, + { .compatible = "rockchip,rk3066-usb", .data = dwc2_set_rk_params }, + { .compatible = "lantiq,danube-usb", .data = &dwc2_set_ltq_danube_params }, + { .compatible = "lantiq,ase-usb", .data = &dwc2_set_ltq_ase_params }, + { .compatible = "lantiq,arx100-usb", .data = &dwc2_set_ltq_ase_params }, + { .compatible = "lantiq,xrx200-usb", .data = &dwc2_set_ltq_xrx200_params }, + { .compatible = "lantiq,xrx300-usb", .data = &dwc2_set_ltq_xrx200_params }, + { .compatible = "snps,dwc2" }, + { .compatible = "samsung,s3c6400-hsotg", + .data = dwc2_set_s3c6400_params }, + { .compatible = "amlogic,meson8-usb", + .data = dwc2_set_amlogic_params }, + { .compatible = "amlogic,meson8b-usb", + .data = dwc2_set_amlogic_params }, + { .compatible = "amlogic,meson-gxbb-usb", + .data = dwc2_set_amlogic_params }, + { .compatible = "amlogic,meson-g12a-usb", + .data = dwc2_set_amlogic_g12a_params }, + { .compatible = "amlogic,meson-a1-usb", + .data = dwc2_set_amlogic_a1_params }, + { .compatible = "amcc,dwc-otg", .data = dwc2_set_amcc_params }, + { .compatible = "apm,apm82181-dwc-otg", .data = dwc2_set_amcc_params }, + { .compatible = "sophgo,cv1800b-usb", + .data = dwc2_set_cv1800_params }, + { .compatible = "st,stm32f4x9-fsotg", + .data = dwc2_set_stm32f4x9_fsotg_params }, + { .compatible = "st,stm32f4x9-hsotg" }, + { .compatible = "st,stm32f7-hsotg", + .data = dwc2_set_stm32f7_hsotg_params }, + { .compatible = "st,stm32mp15-fsotg", + .data = dwc2_set_stm32mp15_fsotg_params }, + { .compatible = "st,stm32mp15-hsotg", + .data = dwc2_set_stm32mp15_hsotg_params }, + { .compatible = "intel,socfpga-agilex-hsotg", + .data = dwc2_set_socfpga_agilex_params }, + {}, +}; +MODULE_DEVICE_TABLE(of, dwc2_of_match_table); + +const struct acpi_device_id dwc2_acpi_match[] = { + /* This ID refers to the same USB IP as of_device_id brcm,bcm2835-usb */ + { "BCM2848", (kernel_ulong_t)dwc2_set_bcm_params }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, dwc2_acpi_match); + +const struct pci_device_id dwc2_pci_ids[] = { + { + PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, PCI_PRODUCT_ID_HAPS_HSOTG), + }, + { + PCI_DEVICE(PCI_VENDOR_ID_STMICRO, + PCI_DEVICE_ID_STMICRO_USB_OTG), + }, + { + PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DWC2), + .driver_data = (unsigned long)dwc2_set_loongson_params, + }, + { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, dwc2_pci_ids); +EXPORT_SYMBOL_GPL(dwc2_pci_ids); + +static void dwc2_set_param_otg_cap(struct dwc2_hsotg *hsotg) +{ + switch (hsotg->hw_params.op_mode) { + case GHWCFG2_OP_MODE_HNP_SRP_CAPABLE: + hsotg->params.otg_caps.hnp_support = true; + hsotg->params.otg_caps.srp_support = true; + break; + case GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE: + case GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE: + case GHWCFG2_OP_MODE_SRP_CAPABLE_HOST: + hsotg->params.otg_caps.hnp_support = false; + hsotg->params.otg_caps.srp_support = true; + break; + default: + hsotg->params.otg_caps.hnp_support = false; + hsotg->params.otg_caps.srp_support = false; + break; + } +} + +static void dwc2_set_param_phy_type(struct dwc2_hsotg *hsotg) +{ + int val; + u32 hs_phy_type = hsotg->hw_params.hs_phy_type; + + val = DWC2_PHY_TYPE_PARAM_FS; + if (hs_phy_type != GHWCFG2_HS_PHY_TYPE_NOT_SUPPORTED) { + if (hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI || + hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI_ULPI) + val = DWC2_PHY_TYPE_PARAM_UTMI; + else + val = DWC2_PHY_TYPE_PARAM_ULPI; + } + + if (dwc2_is_fs_iot(hsotg)) + hsotg->params.phy_type = DWC2_PHY_TYPE_PARAM_FS; + + hsotg->params.phy_type = val; +} + +static void dwc2_set_param_speed(struct dwc2_hsotg *hsotg) +{ + int val; + + val = hsotg->params.phy_type == DWC2_PHY_TYPE_PARAM_FS ? + DWC2_SPEED_PARAM_FULL : DWC2_SPEED_PARAM_HIGH; + + if (dwc2_is_fs_iot(hsotg)) + val = DWC2_SPEED_PARAM_FULL; + + if (dwc2_is_hs_iot(hsotg)) + val = DWC2_SPEED_PARAM_HIGH; + + hsotg->params.speed = val; +} + +static void dwc2_set_param_phy_utmi_width(struct dwc2_hsotg *hsotg) +{ + int val; + + val = (hsotg->hw_params.utmi_phy_data_width == + GHWCFG4_UTMI_PHY_DATA_WIDTH_8) ? 8 : 16; + + if (hsotg->phy) { + /* + * If using the generic PHY framework, check if the PHY bus + * width is 8-bit and set the phyif appropriately. + */ + if (phy_get_bus_width(hsotg->phy) == 8) + val = 8; + } + + hsotg->params.phy_utmi_width = val; +} + +static void dwc2_set_param_tx_fifo_sizes(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + int depth_average; + int fifo_count; + int i; + + fifo_count = dwc2_hsotg_tx_fifo_count(hsotg); + + memset(p->g_tx_fifo_size, 0, sizeof(p->g_tx_fifo_size)); + depth_average = dwc2_hsotg_tx_fifo_average_depth(hsotg); + for (i = 1; i <= fifo_count; i++) + p->g_tx_fifo_size[i] = depth_average; +} + +static void dwc2_set_param_power_down(struct dwc2_hsotg *hsotg) +{ + int val; + + if (hsotg->hw_params.hibernation) + val = DWC2_POWER_DOWN_PARAM_HIBERNATION; + else if (hsotg->hw_params.power_optimized) + val = DWC2_POWER_DOWN_PARAM_PARTIAL; + else + val = DWC2_POWER_DOWN_PARAM_NONE; + + hsotg->params.power_down = val; +} + +static void dwc2_set_param_lpm(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->lpm = hsotg->hw_params.lpm_mode; + if (p->lpm) { + p->lpm_clock_gating = true; + p->besl = true; + p->hird_threshold_en = true; + p->hird_threshold = 4; + } else { + p->lpm_clock_gating = false; + p->besl = false; + p->hird_threshold_en = false; + } +} + +/** + * dwc2_set_default_params() - Set all core parameters to their + * auto-detected default values. + * + * @hsotg: Programming view of the DWC_otg controller + * + */ +static void dwc2_set_default_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hw_params *hw = &hsotg->hw_params; + struct dwc2_core_params *p = &hsotg->params; + bool dma_capable = !(hw->arch == GHWCFG2_SLAVE_ONLY_ARCH); + + dwc2_set_param_otg_cap(hsotg); + dwc2_set_param_phy_type(hsotg); + dwc2_set_param_speed(hsotg); + dwc2_set_param_phy_utmi_width(hsotg); + dwc2_set_param_power_down(hsotg); + dwc2_set_param_lpm(hsotg); + p->phy_ulpi_ddr = false; + p->phy_ulpi_ext_vbus = false; + p->eusb2_disc = false; + + p->enable_dynamic_fifo = hw->enable_dynamic_fifo; + p->en_multiple_tx_fifo = hw->en_multiple_tx_fifo; + p->i2c_enable = hw->i2c_enable; + p->acg_enable = hw->acg_enable; + p->ulpi_fs_ls = false; + p->ts_dline = false; + p->reload_ctl = (hw->snpsid >= DWC2_CORE_REV_2_92a); + p->uframe_sched = true; + p->external_id_pin_ctl = false; + p->ipg_isoc_en = false; + p->service_interval = false; + p->max_packet_count = hw->max_packet_count; + p->max_transfer_size = hw->max_transfer_size; + p->ahbcfg = GAHBCFG_HBSTLEN_INCR << GAHBCFG_HBSTLEN_SHIFT; + p->ref_clk_per = 33333; + p->sof_cnt_wkup_alert = 100; + + if ((hsotg->dr_mode == USB_DR_MODE_HOST) || + (hsotg->dr_mode == USB_DR_MODE_OTG)) { + p->host_dma = dma_capable; + p->dma_desc_enable = false; + p->dma_desc_fs_enable = false; + p->host_support_fs_ls_low_power = false; + p->host_ls_low_power_phy_clk = false; + p->host_channels = hw->host_channels; + p->host_rx_fifo_size = hw->rx_fifo_size; + p->host_nperio_tx_fifo_size = hw->host_nperio_tx_fifo_size; + p->host_perio_tx_fifo_size = hw->host_perio_tx_fifo_size; + } + + if ((hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) || + (hsotg->dr_mode == USB_DR_MODE_OTG)) { + p->g_dma = dma_capable; + p->g_dma_desc = hw->dma_desc_enable; + + /* + * The values for g_rx_fifo_size (2048) and + * g_np_tx_fifo_size (1024) come from the legacy s3c + * gadget driver. These defaults have been hard-coded + * for some time so many platforms depend on these + * values. Leave them as defaults for now and only + * auto-detect if the hardware does not support the + * default. + */ + p->g_rx_fifo_size = 2048; + p->g_np_tx_fifo_size = 1024; + dwc2_set_param_tx_fifo_sizes(hsotg); + } +} + +/** + * dwc2_get_device_properties() - Read in device properties. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Read in the device properties and adjust core parameters if needed. + */ +static void dwc2_get_device_properties(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + int num; + + if ((hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) || + (hsotg->dr_mode == USB_DR_MODE_OTG)) { + device_property_read_u32(hsotg->dev, "g-rx-fifo-size", + &p->g_rx_fifo_size); + + device_property_read_u32(hsotg->dev, "g-np-tx-fifo-size", + &p->g_np_tx_fifo_size); + + num = device_property_count_u32(hsotg->dev, "g-tx-fifo-size"); + if (num > 0) { + num = min(num, 15); + memset(p->g_tx_fifo_size, 0, + sizeof(p->g_tx_fifo_size)); + device_property_read_u32_array(hsotg->dev, + "g-tx-fifo-size", + &p->g_tx_fifo_size[1], + num); + } + + of_usb_update_otg_caps(hsotg->dev->of_node, &p->otg_caps); + } + + p->oc_disable = of_property_read_bool(hsotg->dev->of_node, "disable-over-current"); +} + +static void dwc2_check_param_otg_cap(struct dwc2_hsotg *hsotg) +{ + int valid = 1; + + if (hsotg->params.otg_caps.hnp_support && hsotg->params.otg_caps.srp_support) { + /* check HNP && SRP capable */ + if (hsotg->hw_params.op_mode != GHWCFG2_OP_MODE_HNP_SRP_CAPABLE) + valid = 0; + } else if (!hsotg->params.otg_caps.hnp_support) { + /* check SRP only capable */ + if (hsotg->params.otg_caps.srp_support) { + switch (hsotg->hw_params.op_mode) { + case GHWCFG2_OP_MODE_HNP_SRP_CAPABLE: + case GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE: + case GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE: + case GHWCFG2_OP_MODE_SRP_CAPABLE_HOST: + break; + default: + valid = 0; + break; + } + } + /* else: NO HNP && NO SRP capable: always valid */ + } else { + valid = 0; + } + + if (!valid) + dwc2_set_param_otg_cap(hsotg); +} + +static void dwc2_check_param_phy_type(struct dwc2_hsotg *hsotg) +{ + int valid = 0; + u32 hs_phy_type; + u32 fs_phy_type; + + hs_phy_type = hsotg->hw_params.hs_phy_type; + fs_phy_type = hsotg->hw_params.fs_phy_type; + + switch (hsotg->params.phy_type) { + case DWC2_PHY_TYPE_PARAM_FS: + if (fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED) + valid = 1; + break; + case DWC2_PHY_TYPE_PARAM_UTMI: + if ((hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI) || + (hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI_ULPI)) + valid = 1; + break; + case DWC2_PHY_TYPE_PARAM_ULPI: + if ((hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI) || + (hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI_ULPI)) + valid = 1; + break; + default: + break; + } + + if (!valid) + dwc2_set_param_phy_type(hsotg); +} + +static void dwc2_check_param_speed(struct dwc2_hsotg *hsotg) +{ + int valid = 1; + int phy_type = hsotg->params.phy_type; + int speed = hsotg->params.speed; + + switch (speed) { + case DWC2_SPEED_PARAM_HIGH: + if ((hsotg->params.speed == DWC2_SPEED_PARAM_HIGH) && + (phy_type == DWC2_PHY_TYPE_PARAM_FS)) + valid = 0; + break; + case DWC2_SPEED_PARAM_FULL: + case DWC2_SPEED_PARAM_LOW: + break; + default: + valid = 0; + break; + } + + if (!valid) + dwc2_set_param_speed(hsotg); +} + +static void dwc2_check_param_phy_utmi_width(struct dwc2_hsotg *hsotg) +{ + int valid = 0; + int param = hsotg->params.phy_utmi_width; + int width = hsotg->hw_params.utmi_phy_data_width; + + switch (width) { + case GHWCFG4_UTMI_PHY_DATA_WIDTH_8: + valid = (param == 8); + break; + case GHWCFG4_UTMI_PHY_DATA_WIDTH_16: + valid = (param == 16); + break; + case GHWCFG4_UTMI_PHY_DATA_WIDTH_8_OR_16: + valid = (param == 8 || param == 16); + break; + } + + if (!valid) + dwc2_set_param_phy_utmi_width(hsotg); +} + +static void dwc2_check_param_power_down(struct dwc2_hsotg *hsotg) +{ + int param = hsotg->params.power_down; + + switch (param) { + case DWC2_POWER_DOWN_PARAM_NONE: + break; + case DWC2_POWER_DOWN_PARAM_PARTIAL: + if (hsotg->hw_params.power_optimized) + break; + dev_dbg(hsotg->dev, + "Partial power down isn't supported by HW\n"); + param = DWC2_POWER_DOWN_PARAM_NONE; + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + if (hsotg->hw_params.hibernation) + break; + dev_dbg(hsotg->dev, + "Hibernation isn't supported by HW\n"); + param = DWC2_POWER_DOWN_PARAM_NONE; + break; + default: + dev_err(hsotg->dev, + "%s: Invalid parameter power_down=%d\n", + __func__, param); + param = DWC2_POWER_DOWN_PARAM_NONE; + break; + } + + hsotg->params.power_down = param; +} + +static void dwc2_check_param_tx_fifo_sizes(struct dwc2_hsotg *hsotg) +{ + int fifo_count; + int fifo; + int min; + u32 total = 0; + u32 dptxfszn; + + fifo_count = dwc2_hsotg_tx_fifo_count(hsotg); + min = hsotg->hw_params.en_multiple_tx_fifo ? 16 : 4; + + for (fifo = 1; fifo <= fifo_count; fifo++) + total += hsotg->params.g_tx_fifo_size[fifo]; + + if (total > dwc2_hsotg_tx_fifo_total_depth(hsotg) || !total) { + dev_warn(hsotg->dev, "%s: Invalid parameter g-tx-fifo-size, setting to default average\n", + __func__); + dwc2_set_param_tx_fifo_sizes(hsotg); + } + + for (fifo = 1; fifo <= fifo_count; fifo++) { + dptxfszn = hsotg->hw_params.g_tx_fifo_size[fifo]; + + if (hsotg->params.g_tx_fifo_size[fifo] < min || + hsotg->params.g_tx_fifo_size[fifo] > dptxfszn) { + dev_warn(hsotg->dev, "%s: Invalid parameter g_tx_fifo_size[%d]=%d\n", + __func__, fifo, + hsotg->params.g_tx_fifo_size[fifo]); + hsotg->params.g_tx_fifo_size[fifo] = dptxfszn; + } + } +} + +static void dwc2_check_param_eusb2_disc(struct dwc2_hsotg *hsotg) +{ + u32 gsnpsid; + + if (!hsotg->params.eusb2_disc) + return; + gsnpsid = dwc2_readl(hsotg, GSNPSID); + /* + * eusb2_disc not supported by FS IOT devices. + * For other cores, it supported starting from version 5.00a + */ + if ((gsnpsid & ~DWC2_CORE_REV_MASK) == DWC2_FS_IOT_ID || + (gsnpsid & DWC2_CORE_REV_MASK) < + (DWC2_CORE_REV_5_00a & DWC2_CORE_REV_MASK)) { + hsotg->params.eusb2_disc = false; + return; + } +} + +#define CHECK_RANGE(_param, _min, _max, _def) do { \ + if ((int)(hsotg->params._param) < (_min) || \ + (hsotg->params._param) > (_max)) { \ + dev_warn(hsotg->dev, "%s: Invalid parameter %s=%d\n", \ + __func__, #_param, hsotg->params._param); \ + hsotg->params._param = (_def); \ + } \ + } while (0) + +#define CHECK_BOOL(_param, _check) do { \ + if (hsotg->params._param && !(_check)) { \ + dev_warn(hsotg->dev, "%s: Invalid parameter %s=%d\n", \ + __func__, #_param, hsotg->params._param); \ + hsotg->params._param = false; \ + } \ + } while (0) + +static void dwc2_check_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hw_params *hw = &hsotg->hw_params; + struct dwc2_core_params *p = &hsotg->params; + bool dma_capable = !(hw->arch == GHWCFG2_SLAVE_ONLY_ARCH); + + dwc2_check_param_otg_cap(hsotg); + dwc2_check_param_phy_type(hsotg); + dwc2_check_param_speed(hsotg); + dwc2_check_param_phy_utmi_width(hsotg); + dwc2_check_param_power_down(hsotg); + dwc2_check_param_eusb2_disc(hsotg); + + CHECK_BOOL(enable_dynamic_fifo, hw->enable_dynamic_fifo); + CHECK_BOOL(en_multiple_tx_fifo, hw->en_multiple_tx_fifo); + CHECK_BOOL(i2c_enable, hw->i2c_enable); + CHECK_BOOL(ipg_isoc_en, hw->ipg_isoc_en); + CHECK_BOOL(acg_enable, hw->acg_enable); + CHECK_BOOL(reload_ctl, (hsotg->hw_params.snpsid > DWC2_CORE_REV_2_92a)); + CHECK_BOOL(lpm, (hsotg->hw_params.snpsid >= DWC2_CORE_REV_2_80a)); + CHECK_BOOL(lpm, hw->lpm_mode); + CHECK_BOOL(lpm_clock_gating, hsotg->params.lpm); + CHECK_BOOL(besl, hsotg->params.lpm); + CHECK_BOOL(besl, (hsotg->hw_params.snpsid >= DWC2_CORE_REV_3_00a)); + CHECK_BOOL(hird_threshold_en, hsotg->params.lpm); + CHECK_RANGE(hird_threshold, 0, hsotg->params.besl ? 12 : 7, 0); + CHECK_BOOL(service_interval, hw->service_interval_mode); + CHECK_RANGE(max_packet_count, + 15, hw->max_packet_count, + hw->max_packet_count); + CHECK_RANGE(max_transfer_size, + 2047, hw->max_transfer_size, + hw->max_transfer_size); + + if ((hsotg->dr_mode == USB_DR_MODE_HOST) || + (hsotg->dr_mode == USB_DR_MODE_OTG)) { + CHECK_BOOL(host_dma, dma_capable); + CHECK_BOOL(dma_desc_enable, p->host_dma); + CHECK_BOOL(dma_desc_fs_enable, p->dma_desc_enable); + CHECK_BOOL(host_ls_low_power_phy_clk, + p->phy_type == DWC2_PHY_TYPE_PARAM_FS); + CHECK_RANGE(host_channels, + 1, hw->host_channels, + hw->host_channels); + CHECK_RANGE(host_rx_fifo_size, + 16, hw->rx_fifo_size, + hw->rx_fifo_size); + CHECK_RANGE(host_nperio_tx_fifo_size, + 16, hw->host_nperio_tx_fifo_size, + hw->host_nperio_tx_fifo_size); + CHECK_RANGE(host_perio_tx_fifo_size, + 16, hw->host_perio_tx_fifo_size, + hw->host_perio_tx_fifo_size); + } + + if ((hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) || + (hsotg->dr_mode == USB_DR_MODE_OTG)) { + CHECK_BOOL(g_dma, dma_capable); + CHECK_BOOL(g_dma_desc, (p->g_dma && hw->dma_desc_enable)); + CHECK_RANGE(g_rx_fifo_size, + 16, hw->rx_fifo_size, + hw->rx_fifo_size); + CHECK_RANGE(g_np_tx_fifo_size, + 16, hw->dev_nperio_tx_fifo_size, + hw->dev_nperio_tx_fifo_size); + dwc2_check_param_tx_fifo_sizes(hsotg); + } +} + +/* + * Gets host hardware parameters. Forces host mode if not currently in + * host mode. Should be called immediately after a core soft reset in + * order to get the reset values. + */ +static void dwc2_get_host_hwparams(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hw_params *hw = &hsotg->hw_params; + u32 gnptxfsiz; + u32 hptxfsiz; + + if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) + return; + + dwc2_force_mode(hsotg, true); + + gnptxfsiz = dwc2_readl(hsotg, GNPTXFSIZ); + hptxfsiz = dwc2_readl(hsotg, HPTXFSIZ); + + hw->host_nperio_tx_fifo_size = (gnptxfsiz & FIFOSIZE_DEPTH_MASK) >> + FIFOSIZE_DEPTH_SHIFT; + hw->host_perio_tx_fifo_size = (hptxfsiz & FIFOSIZE_DEPTH_MASK) >> + FIFOSIZE_DEPTH_SHIFT; +} + +/* + * Gets device hardware parameters. Forces device mode if not + * currently in device mode. Should be called immediately after a core + * soft reset in order to get the reset values. + */ +static void dwc2_get_dev_hwparams(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hw_params *hw = &hsotg->hw_params; + u32 gnptxfsiz; + int fifo, fifo_count; + + if (hsotg->dr_mode == USB_DR_MODE_HOST) + return; + + dwc2_force_mode(hsotg, false); + + gnptxfsiz = dwc2_readl(hsotg, GNPTXFSIZ); + + fifo_count = dwc2_hsotg_tx_fifo_count(hsotg); + + for (fifo = 1; fifo <= fifo_count; fifo++) { + hw->g_tx_fifo_size[fifo] = + (dwc2_readl(hsotg, DPTXFSIZN(fifo)) & + FIFOSIZE_DEPTH_MASK) >> FIFOSIZE_DEPTH_SHIFT; + } + + hw->dev_nperio_tx_fifo_size = (gnptxfsiz & FIFOSIZE_DEPTH_MASK) >> + FIFOSIZE_DEPTH_SHIFT; +} + +/** + * dwc2_get_hwparams() - During device initialization, read various hardware + * configuration registers and interpret the contents. + * + * @hsotg: Programming view of the DWC_otg controller + * + */ +int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hw_params *hw = &hsotg->hw_params; + unsigned int width; + u32 hwcfg1, hwcfg2, hwcfg3, hwcfg4; + u32 grxfsiz; + + hwcfg1 = dwc2_readl(hsotg, GHWCFG1); + hwcfg2 = dwc2_readl(hsotg, GHWCFG2); + hwcfg3 = dwc2_readl(hsotg, GHWCFG3); + hwcfg4 = dwc2_readl(hsotg, GHWCFG4); + grxfsiz = dwc2_readl(hsotg, GRXFSIZ); + + /* hwcfg1 */ + hw->dev_ep_dirs = hwcfg1; + + /* hwcfg2 */ + hw->op_mode = (hwcfg2 & GHWCFG2_OP_MODE_MASK) >> + GHWCFG2_OP_MODE_SHIFT; + hw->arch = (hwcfg2 & GHWCFG2_ARCHITECTURE_MASK) >> + GHWCFG2_ARCHITECTURE_SHIFT; + hw->enable_dynamic_fifo = !!(hwcfg2 & GHWCFG2_DYNAMIC_FIFO); + hw->host_channels = 1 + ((hwcfg2 & GHWCFG2_NUM_HOST_CHAN_MASK) >> + GHWCFG2_NUM_HOST_CHAN_SHIFT); + hw->hs_phy_type = (hwcfg2 & GHWCFG2_HS_PHY_TYPE_MASK) >> + GHWCFG2_HS_PHY_TYPE_SHIFT; + hw->fs_phy_type = (hwcfg2 & GHWCFG2_FS_PHY_TYPE_MASK) >> + GHWCFG2_FS_PHY_TYPE_SHIFT; + hw->num_dev_ep = (hwcfg2 & GHWCFG2_NUM_DEV_EP_MASK) >> + GHWCFG2_NUM_DEV_EP_SHIFT; + hw->nperio_tx_q_depth = + (hwcfg2 & GHWCFG2_NONPERIO_TX_Q_DEPTH_MASK) >> + GHWCFG2_NONPERIO_TX_Q_DEPTH_SHIFT << 1; + hw->host_perio_tx_q_depth = + (hwcfg2 & GHWCFG2_HOST_PERIO_TX_Q_DEPTH_MASK) >> + GHWCFG2_HOST_PERIO_TX_Q_DEPTH_SHIFT << 1; + hw->dev_token_q_depth = + (hwcfg2 & GHWCFG2_DEV_TOKEN_Q_DEPTH_MASK) >> + GHWCFG2_DEV_TOKEN_Q_DEPTH_SHIFT; + + /* hwcfg3 */ + width = (hwcfg3 & GHWCFG3_XFER_SIZE_CNTR_WIDTH_MASK) >> + GHWCFG3_XFER_SIZE_CNTR_WIDTH_SHIFT; + hw->max_transfer_size = (1 << (width + 11)) - 1; + width = (hwcfg3 & GHWCFG3_PACKET_SIZE_CNTR_WIDTH_MASK) >> + GHWCFG3_PACKET_SIZE_CNTR_WIDTH_SHIFT; + hw->max_packet_count = (1 << (width + 4)) - 1; + hw->i2c_enable = !!(hwcfg3 & GHWCFG3_I2C); + hw->total_fifo_size = (hwcfg3 & GHWCFG3_DFIFO_DEPTH_MASK) >> + GHWCFG3_DFIFO_DEPTH_SHIFT; + hw->lpm_mode = !!(hwcfg3 & GHWCFG3_OTG_LPM_EN); + + /* hwcfg4 */ + hw->en_multiple_tx_fifo = !!(hwcfg4 & GHWCFG4_DED_FIFO_EN); + hw->num_dev_perio_in_ep = (hwcfg4 & GHWCFG4_NUM_DEV_PERIO_IN_EP_MASK) >> + GHWCFG4_NUM_DEV_PERIO_IN_EP_SHIFT; + hw->num_dev_in_eps = (hwcfg4 & GHWCFG4_NUM_IN_EPS_MASK) >> + GHWCFG4_NUM_IN_EPS_SHIFT; + hw->dma_desc_enable = !!(hwcfg4 & GHWCFG4_DESC_DMA); + hw->power_optimized = !!(hwcfg4 & GHWCFG4_POWER_OPTIMIZ); + hw->hibernation = !!(hwcfg4 & GHWCFG4_HIBER); + hw->utmi_phy_data_width = (hwcfg4 & GHWCFG4_UTMI_PHY_DATA_WIDTH_MASK) >> + GHWCFG4_UTMI_PHY_DATA_WIDTH_SHIFT; + hw->acg_enable = !!(hwcfg4 & GHWCFG4_ACG_SUPPORTED); + hw->ipg_isoc_en = !!(hwcfg4 & GHWCFG4_IPG_ISOC_SUPPORTED); + hw->service_interval_mode = !!(hwcfg4 & + GHWCFG4_SERVICE_INTERVAL_SUPPORTED); + + /* fifo sizes */ + hw->rx_fifo_size = (grxfsiz & GRXFSIZ_DEPTH_MASK) >> + GRXFSIZ_DEPTH_SHIFT; + /* + * Host specific hardware parameters. Reading these parameters + * requires the controller to be in host mode. The mode will + * be forced, if necessary, to read these values. + */ + dwc2_get_host_hwparams(hsotg); + dwc2_get_dev_hwparams(hsotg); + + return 0; +} + +static int dwc2_limit_speed(struct dwc2_hsotg *hsotg) +{ + enum usb_device_speed usb_speed; + + usb_speed = usb_get_maximum_speed(hsotg->dev); + switch (usb_speed) { + case USB_SPEED_LOW: + dev_err(hsotg->dev, "Maximum speed cannot be forced to low-speed\n"); + return -EINVAL; + case USB_SPEED_FULL: + if (hsotg->params.speed == DWC2_SPEED_PARAM_LOW) + break; + hsotg->params.speed = DWC2_SPEED_PARAM_FULL; + break; + default: + break; + } + + return 0; +} + +typedef void (*set_params_cb)(struct dwc2_hsotg *data); + +int dwc2_init_params(struct dwc2_hsotg *hsotg) +{ + set_params_cb set_params; + int ret; + + dwc2_set_default_params(hsotg); + dwc2_get_device_properties(hsotg); + + set_params = device_get_match_data(hsotg->dev); + if (set_params) { + set_params(hsotg); + } else { + const struct pci_device_id *pmatch = + pci_match_id(dwc2_pci_ids, to_pci_dev(hsotg->dev->parent)); + + if (pmatch && pmatch->driver_data) { + set_params = (set_params_cb)pmatch->driver_data; + set_params(hsotg); + } + } + + ret = dwc2_limit_speed(hsotg); + if (ret) + return ret; + + dwc2_check_params(hsotg); + + return 0; +} diff --git a/drivers/usb/dwc2/pci.c b/drivers/usb/dwc2/pci.c new file mode 100644 index 000000000..f3a1e4232 --- /dev/null +++ b/drivers/usb/dwc2/pci.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * pci.c - DesignWare HS OTG Controller PCI driver + * + * Copyright (C) 2004-2013 Synopsys, Inc. + */ + +/* + * Provides the initialization and cleanup entry points for the DWC_otg PCI + * driver + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "core.h" + +static const char dwc2_driver_name[] = "dwc2-pci"; + +struct dwc2_pci_glue { + struct platform_device *dwc2; + struct platform_device *phy; +}; + +/** + * dwc2_pci_remove() - Provides the cleanup entry points for the DWC_otg PCI + * driver + * + * @pci: The programming view of DWC_otg PCI + */ +static void dwc2_pci_remove(struct pci_dev *pci) +{ + struct dwc2_pci_glue *glue = pci_get_drvdata(pci); + + platform_device_unregister(glue->dwc2); + usb_phy_generic_unregister(glue->phy); + pci_set_drvdata(pci, NULL); +} + +static int dwc2_pci_probe(struct pci_dev *pci, + const struct pci_device_id *id) +{ + struct resource res[2]; + struct platform_device *dwc2; + struct platform_device *phy; + int ret; + struct device *dev = &pci->dev; + struct dwc2_pci_glue *glue; + + ret = pcim_enable_device(pci); + if (ret) { + dev_err(dev, "failed to enable pci device\n"); + return -ENODEV; + } + + pci_set_master(pci); + + phy = usb_phy_generic_register(); + if (IS_ERR(phy)) { + dev_err(dev, "error registering generic PHY (%ld)\n", + PTR_ERR(phy)); + return PTR_ERR(phy); + } + + dwc2 = platform_device_alloc("dwc2", PLATFORM_DEVID_AUTO); + if (!dwc2) { + dev_err(dev, "couldn't allocate dwc2 device\n"); + ret = -ENOMEM; + goto err; + } + + 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 = "dwc2"; + res[0].flags = IORESOURCE_MEM; + + res[1].start = pci->irq; + res[1].name = "dwc2"; + res[1].flags = IORESOURCE_IRQ; + + ret = platform_device_add_resources(dwc2, res, ARRAY_SIZE(res)); + if (ret) { + dev_err(dev, "couldn't add resources to dwc2 device\n"); + goto err; + } + + dwc2->dev.parent = dev; + + glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); + if (!glue) { + ret = -ENOMEM; + goto err; + } + + ret = platform_device_add(dwc2); + if (ret) { + dev_err(dev, "failed to register dwc2 device\n"); + goto err; + } + + glue->phy = phy; + glue->dwc2 = dwc2; + pci_set_drvdata(pci, glue); + + return 0; +err: + usb_phy_generic_unregister(phy); + platform_device_put(dwc2); + return ret; +} + +static struct pci_driver dwc2_pci_driver = { + .name = dwc2_driver_name, + .id_table = dwc2_pci_ids, + .probe = dwc2_pci_probe, + .remove = dwc2_pci_remove, +}; + +module_pci_driver(dwc2_pci_driver); + +MODULE_DESCRIPTION("DESIGNWARE HS OTG PCI Bus Glue"); +MODULE_AUTHOR("Synopsys, Inc."); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c new file mode 100644 index 000000000..ef0d73077 --- /dev/null +++ b/drivers/usb/dwc2/platform.c @@ -0,0 +1,811 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * platform.c - DesignWare HS OTG Controller platform driver + * + * Copyright (C) Matthijs Kooijman + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "core.h" +#include "hcd.h" +#include "debug.h" + +static const char dwc2_driver_name[] = "dwc2"; + +/* + * Check the dr_mode against the module configuration and hardware + * capabilities. + * + * The hardware, module, and dr_mode, can each be set to host, device, + * or otg. Check that all these values are compatible and adjust the + * value of dr_mode if possible. + * + * actual + * HW MOD dr_mode dr_mode + * ------------------------------ + * HST HST any : HST + * HST DEV any : --- + * HST OTG any : HST + * + * DEV HST any : --- + * DEV DEV any : DEV + * DEV OTG any : DEV + * + * OTG HST any : HST + * OTG DEV any : DEV + * OTG OTG any : dr_mode + */ +static int dwc2_get_dr_mode(struct dwc2_hsotg *hsotg) +{ + enum usb_dr_mode mode; + + hsotg->dr_mode = usb_get_dr_mode(hsotg->dev); + if (hsotg->dr_mode == USB_DR_MODE_UNKNOWN) + hsotg->dr_mode = USB_DR_MODE_OTG; + + mode = hsotg->dr_mode; + + if (dwc2_hw_is_device(hsotg)) { + if (IS_ENABLED(CONFIG_USB_DWC2_HOST)) { + dev_err(hsotg->dev, + "Controller does not support host mode.\n"); + return -EINVAL; + } + mode = USB_DR_MODE_PERIPHERAL; + } else if (dwc2_hw_is_host(hsotg)) { + if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL)) { + dev_err(hsotg->dev, + "Controller does not support device mode.\n"); + return -EINVAL; + } + mode = USB_DR_MODE_HOST; + } else { + if (IS_ENABLED(CONFIG_USB_DWC2_HOST)) + mode = USB_DR_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL)) + mode = USB_DR_MODE_PERIPHERAL; + } + + if (mode != hsotg->dr_mode) { + dev_warn(hsotg->dev, + "Configuration mismatch. dr_mode forced to %s\n", + mode == USB_DR_MODE_HOST ? "host" : "device"); + + hsotg->dr_mode = mode; + } + + return 0; +} + +static int __dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg) +{ + struct platform_device *pdev = to_platform_device(hsotg->dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(hsotg->supplies), + hsotg->supplies); + if (ret) + return ret; + + if (hsotg->utmi_clk) { + ret = clk_prepare_enable(hsotg->utmi_clk); + if (ret) + goto err_dis_reg; + } + + if (hsotg->clk) { + ret = clk_prepare_enable(hsotg->clk); + if (ret) + goto err_dis_utmi_clk; + } + + if (hsotg->uphy) { + ret = usb_phy_init(hsotg->uphy); + } else if (hsotg->plat && hsotg->plat->phy_init) { + ret = hsotg->plat->phy_init(pdev, hsotg->plat->phy_type); + } else { + ret = phy_init(hsotg->phy); + if (ret == 0) { + ret = phy_power_on(hsotg->phy); + if (ret) + phy_exit(hsotg->phy); + } + } + + if (ret) + goto err_dis_clk; + + return 0; + +err_dis_clk: + if (hsotg->clk) + clk_disable_unprepare(hsotg->clk); + +err_dis_utmi_clk: + if (hsotg->utmi_clk) + clk_disable_unprepare(hsotg->utmi_clk); + +err_dis_reg: + regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); + + return ret; +} + +/** + * dwc2_lowlevel_hw_enable - enable platform lowlevel hw resources + * @hsotg: The driver state + * + * A wrapper for platform code responsible for controlling + * low-level USB platform resources (phy, clock, regulators) + */ +int dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg) +{ + int ret = __dwc2_lowlevel_hw_enable(hsotg); + + if (ret == 0) + hsotg->ll_hw_enabled = true; + return ret; +} + +static int __dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg) +{ + struct platform_device *pdev = to_platform_device(hsotg->dev); + int ret = 0; + + if (hsotg->uphy) { + usb_phy_shutdown(hsotg->uphy); + } else if (hsotg->plat && hsotg->plat->phy_exit) { + ret = hsotg->plat->phy_exit(pdev, hsotg->plat->phy_type); + } else { + ret = phy_power_off(hsotg->phy); + if (ret == 0) + ret = phy_exit(hsotg->phy); + } + if (ret) + return ret; + + if (hsotg->clk) + clk_disable_unprepare(hsotg->clk); + + if (hsotg->utmi_clk) + clk_disable_unprepare(hsotg->utmi_clk); + + return regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); +} + +/** + * dwc2_lowlevel_hw_disable - disable platform lowlevel hw resources + * @hsotg: The driver state + * + * A wrapper for platform code responsible for controlling + * low-level USB platform resources (phy, clock, regulators) + */ +int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg) +{ + int ret = __dwc2_lowlevel_hw_disable(hsotg); + + if (ret == 0) + hsotg->ll_hw_enabled = false; + return ret; +} + +static void dwc2_reset_control_assert(void *data) +{ + reset_control_assert(data); +} + +static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg) +{ + int i, ret; + + hsotg->reset = devm_reset_control_get_optional(hsotg->dev, "dwc2"); + if (IS_ERR(hsotg->reset)) + return dev_err_probe(hsotg->dev, PTR_ERR(hsotg->reset), + "error getting reset control\n"); + + reset_control_deassert(hsotg->reset); + ret = devm_add_action_or_reset(hsotg->dev, dwc2_reset_control_assert, + hsotg->reset); + if (ret) + return ret; + + hsotg->reset_ecc = devm_reset_control_get_optional(hsotg->dev, "dwc2-ecc"); + if (IS_ERR(hsotg->reset_ecc)) + return dev_err_probe(hsotg->dev, PTR_ERR(hsotg->reset_ecc), + "error getting reset control for ecc\n"); + + reset_control_deassert(hsotg->reset_ecc); + ret = devm_add_action_or_reset(hsotg->dev, dwc2_reset_control_assert, + hsotg->reset_ecc); + if (ret) + return ret; + + /* + * Attempt to find a generic PHY, then look for an old style + * USB PHY and then fall back to pdata + */ + hsotg->phy = devm_phy_get(hsotg->dev, "usb2-phy"); + if (IS_ERR(hsotg->phy)) { + ret = PTR_ERR(hsotg->phy); + switch (ret) { + case -ENODEV: + case -ENOSYS: + hsotg->phy = NULL; + break; + default: + return dev_err_probe(hsotg->dev, ret, "error getting phy\n"); + } + } + + if (!hsotg->phy) { + hsotg->uphy = devm_usb_get_phy(hsotg->dev, USB_PHY_TYPE_USB2); + if (IS_ERR(hsotg->uphy)) { + ret = PTR_ERR(hsotg->uphy); + switch (ret) { + case -ENODEV: + case -ENXIO: + hsotg->uphy = NULL; + break; + default: + return dev_err_probe(hsotg->dev, ret, "error getting usb phy\n"); + } + } + } + + hsotg->plat = dev_get_platdata(hsotg->dev); + + /* Clock */ + hsotg->clk = devm_clk_get_optional(hsotg->dev, "otg"); + if (IS_ERR(hsotg->clk)) + return dev_err_probe(hsotg->dev, PTR_ERR(hsotg->clk), "cannot get otg clock\n"); + + hsotg->utmi_clk = devm_clk_get_optional(hsotg->dev, "utmi"); + if (IS_ERR(hsotg->utmi_clk)) + return dev_err_probe(hsotg->dev, PTR_ERR(hsotg->utmi_clk), + "cannot get utmi clock\n"); + + /* Regulators */ + for (i = 0; i < ARRAY_SIZE(hsotg->supplies); i++) + hsotg->supplies[i].supply = dwc2_hsotg_supply_names[i]; + + ret = devm_regulator_bulk_get(hsotg->dev, ARRAY_SIZE(hsotg->supplies), + hsotg->supplies); + if (ret) + return dev_err_probe(hsotg->dev, ret, "failed to request supplies\n"); + + return 0; +} + +/** + * dwc2_driver_remove() - Called when the DWC_otg core is unregistered with the + * DWC_otg driver + * + * @dev: Platform device + * + * This routine is called, 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. + */ +static void dwc2_driver_remove(struct platform_device *dev) +{ + struct dwc2_hsotg *hsotg = platform_get_drvdata(dev); + struct dwc2_gregs_backup *gr; + int ret = 0; + + gr = &hsotg->gr_backup; + + /* Exit Hibernation when driver is removed. */ + if (hsotg->hibernated) { + if (gr->gotgctl & GOTGCTL_CURMODE_HOST) + ret = dwc2_exit_hibernation(hsotg, 0, 0, 1); + else + ret = dwc2_exit_hibernation(hsotg, 0, 0, 0); + + if (ret) + dev_err(hsotg->dev, + "exit hibernation failed.\n"); + } + + /* Exit Partial Power Down when driver is removed. */ + if (hsotg->in_ppd) { + ret = dwc2_exit_partial_power_down(hsotg, 0, true); + if (ret) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + } + + /* Exit clock gating when driver is removed. */ + if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE && + hsotg->bus_suspended && !hsotg->params.no_clock_gating) { + if (dwc2_is_device_mode(hsotg)) + dwc2_gadget_exit_clock_gating(hsotg, 0); + else + dwc2_host_exit_clock_gating(hsotg, 0); + } + + dwc2_debugfs_exit(hsotg); + if (hsotg->hcd_enabled) + dwc2_hcd_remove(hsotg); + if (hsotg->gadget_enabled) + dwc2_hsotg_remove(hsotg); + + dwc2_drd_exit(hsotg); + + if (hsotg->params.activate_stm_id_vb_detection) + regulator_disable(hsotg->usb33d); + + if (hsotg->ll_hw_enabled) + dwc2_lowlevel_hw_disable(hsotg); +} + +/** + * dwc2_driver_shutdown() - Called on device shutdown + * + * @dev: Platform device + * + * In specific conditions (involving usb hubs) dwc2 devices can create a + * lot of interrupts, even to the point of overwhelming devices running + * at low frequencies. Some devices need to do special clock handling + * at shutdown-time which may bring the system clock below the threshold + * of being able to handle the dwc2 interrupts. Disabling dwc2-irqs + * prevents reboots/poweroffs from getting stuck in such cases. + */ +static void dwc2_driver_shutdown(struct platform_device *dev) +{ + struct dwc2_hsotg *hsotg = platform_get_drvdata(dev); + + if (hsotg->ll_hw_enabled) { + dwc2_disable_global_interrupts(hsotg); + synchronize_irq(hsotg->irq); + dwc2_lowlevel_hw_disable(hsotg); + } +} + +/** + * dwc2_check_core_endianness() - Returns true if core and AHB have + * opposite endianness. + * @hsotg: Programming view of the DWC_otg controller. + */ +static bool dwc2_check_core_endianness(struct dwc2_hsotg *hsotg) +{ + u32 snpsid; + + snpsid = ioread32(hsotg->regs + GSNPSID); + if ((snpsid & GSNPSID_ID_MASK) == DWC2_OTG_ID || + (snpsid & GSNPSID_ID_MASK) == DWC2_FS_IOT_ID || + (snpsid & GSNPSID_ID_MASK) == DWC2_HS_IOT_ID) + return false; + return true; +} + +/** + * dwc2_check_core_version() - Check core version + * + * @hsotg: Programming view of the DWC_otg controller + * + */ +int dwc2_check_core_version(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hw_params *hw = &hsotg->hw_params; + + /* + * Attempt to ensure this device is really a DWC_otg Controller. + * Read and verify the GSNPSID register contents. The value should be + * 0x45f4xxxx, 0x5531xxxx or 0x5532xxxx + */ + + hw->snpsid = dwc2_readl(hsotg, GSNPSID); + if ((hw->snpsid & GSNPSID_ID_MASK) != DWC2_OTG_ID && + (hw->snpsid & GSNPSID_ID_MASK) != DWC2_FS_IOT_ID && + (hw->snpsid & GSNPSID_ID_MASK) != DWC2_HS_IOT_ID) { + dev_err(hsotg->dev, "Bad value for GSNPSID: 0x%08x\n", + hw->snpsid); + return -ENODEV; + } + + dev_dbg(hsotg->dev, "Core Release: %1x.%1x%1x%1x (snpsid=%x)\n", + hw->snpsid >> 12 & 0xf, hw->snpsid >> 8 & 0xf, + hw->snpsid >> 4 & 0xf, hw->snpsid & 0xf, hw->snpsid); + return 0; +} + +/** + * dwc2_driver_probe() - Called when the DWC_otg core is bound to the DWC_otg + * driver + * + * @dev: Platform device + * + * This routine creates the driver components required to control the device + * (core, HCD, and PCD) and initializes the device. The driver components are + * stored in a dwc2_hsotg structure. A reference to the dwc2_hsotg is saved + * in the device private data. This allows the driver to access the dwc2_hsotg + * structure on subsequent calls to driver methods for this device. + */ +static int dwc2_driver_probe(struct platform_device *dev) +{ + struct dwc2_hsotg *hsotg; + struct resource *res; + int retval; + + hsotg = devm_kzalloc(&dev->dev, sizeof(*hsotg), GFP_KERNEL); + if (!hsotg) + return -ENOMEM; + + hsotg->dev = &dev->dev; + + /* + * Use reasonable defaults so platforms don't have to provide these. + */ + if (!dev->dev.dma_mask) + dev->dev.dma_mask = &dev->dev.coherent_dma_mask; + retval = dma_set_coherent_mask(&dev->dev, DMA_BIT_MASK(32)); + if (retval) { + dev_err(&dev->dev, "can't set coherent DMA mask: %d\n", retval); + return retval; + } + + hsotg->regs = devm_platform_get_and_ioremap_resource(dev, 0, &res); + if (IS_ERR(hsotg->regs)) + return PTR_ERR(hsotg->regs); + + dev_dbg(&dev->dev, "mapped PA %08lx to VA %p\n", + (unsigned long)res->start, hsotg->regs); + + retval = dwc2_lowlevel_hw_init(hsotg); + if (retval) + return retval; + + spin_lock_init(&hsotg->lock); + + hsotg->vbus_supply = devm_regulator_get_optional(hsotg->dev, "vbus"); + if (IS_ERR(hsotg->vbus_supply)) { + retval = PTR_ERR(hsotg->vbus_supply); + hsotg->vbus_supply = NULL; + if (retval != -ENODEV) + return retval; + } + + retval = dwc2_lowlevel_hw_enable(hsotg); + if (retval) + return retval; + + hsotg->needs_byte_swap = dwc2_check_core_endianness(hsotg); + + retval = dwc2_get_dr_mode(hsotg); + if (retval) + goto error; + + hsotg->need_phy_for_wake = + of_property_read_bool(dev->dev.of_node, + "snps,need-phy-for-wake"); + + /* + * Before performing any core related operations + * check core version. + */ + retval = dwc2_check_core_version(hsotg); + if (retval) + goto error; + + /* + * Reset before dwc2_get_hwparams() then it could get power-on real + * reset value form registers. + */ + retval = dwc2_core_reset(hsotg, false); + if (retval) + goto error; + + /* Detect config values from hardware */ + retval = dwc2_get_hwparams(hsotg); + if (retval) + goto error; + + hsotg->irq = platform_get_irq(dev, 0); + if (hsotg->irq < 0) { + retval = hsotg->irq; + goto error; + } + + dev_dbg(hsotg->dev, "registering common handler for irq%d\n", + hsotg->irq); + retval = devm_request_irq(hsotg->dev, hsotg->irq, + dwc2_handle_common_intr, IRQF_SHARED, + dev_name(hsotg->dev), hsotg); + if (retval) + goto error; + + /* + * For OTG cores, set the force mode bits to reflect the value + * of dr_mode. Force mode bits should not be touched at any + * other time after this. + */ + dwc2_force_dr_mode(hsotg); + + retval = dwc2_init_params(hsotg); + if (retval) + goto error; + + if (hsotg->params.activate_stm_id_vb_detection) { + u32 ggpio; + + hsotg->usb33d = devm_regulator_get(hsotg->dev, "usb33d"); + if (IS_ERR(hsotg->usb33d)) { + retval = PTR_ERR(hsotg->usb33d); + dev_err_probe(hsotg->dev, retval, "failed to request usb33d supply\n"); + goto error; + } + retval = regulator_enable(hsotg->usb33d); + if (retval) { + dev_err_probe(hsotg->dev, retval, "failed to enable usb33d supply\n"); + goto error; + } + + ggpio = dwc2_readl(hsotg, GGPIO); + ggpio |= GGPIO_STM32_OTG_GCCFG_IDEN; + ggpio |= GGPIO_STM32_OTG_GCCFG_VBDEN; + dwc2_writel(hsotg, ggpio, GGPIO); + + /* ID/VBUS detection startup time */ + usleep_range(5000, 7000); + } + + retval = dwc2_drd_init(hsotg); + if (retval) { + dev_err_probe(hsotg->dev, retval, "failed to initialize dual-role\n"); + goto error_init; + } + + if (hsotg->dr_mode != USB_DR_MODE_HOST) { + retval = dwc2_gadget_init(hsotg); + if (retval) + goto error_drd; + hsotg->gadget_enabled = 1; + } + + /* + * If we need PHY for wakeup we must be wakeup capable. + * When we have a device that can wake without the PHY we + * can adjust this condition. + */ + if (hsotg->need_phy_for_wake) + device_set_wakeup_capable(&dev->dev, true); + + hsotg->reset_phy_on_wake = + of_property_read_bool(dev->dev.of_node, + "snps,reset-phy-on-wake"); + if (hsotg->reset_phy_on_wake && !hsotg->phy) { + dev_warn(hsotg->dev, + "Quirk reset-phy-on-wake only supports generic PHYs\n"); + hsotg->reset_phy_on_wake = false; + } + + if (hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) { + retval = dwc2_hcd_init(hsotg); + if (retval) { + if (hsotg->gadget_enabled) + dwc2_hsotg_remove(hsotg); + goto error_drd; + } + hsotg->hcd_enabled = 1; + } + + platform_set_drvdata(dev, hsotg); + hsotg->hibernated = 0; + + dwc2_debugfs_init(hsotg); + + /* Gadget code manages lowlevel hw on its own */ + if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL) + dwc2_lowlevel_hw_disable(hsotg); + +#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) + /* Postponed adding a new gadget to the udc class driver list */ + if (hsotg->gadget_enabled) { + retval = usb_add_gadget_udc(hsotg->dev, &hsotg->gadget); + if (retval) { + hsotg->gadget.udc = NULL; + dwc2_hsotg_remove(hsotg); + goto error_debugfs; + } + } +#endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */ + return 0; + +#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) +error_debugfs: + dwc2_debugfs_exit(hsotg); + if (hsotg->hcd_enabled) + dwc2_hcd_remove(hsotg); +#endif +error_drd: + dwc2_drd_exit(hsotg); + +error_init: + if (hsotg->params.activate_stm_id_vb_detection) + regulator_disable(hsotg->usb33d); +error: + if (hsotg->ll_hw_enabled) + dwc2_lowlevel_hw_disable(hsotg); + return retval; +} + +static int __maybe_unused dwc2_suspend(struct device *dev) +{ + struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev); + bool is_device_mode; + int ret = 0; + + if (!dwc2->ll_hw_enabled) + return 0; + + is_device_mode = dwc2_is_device_mode(dwc2); + if (is_device_mode) + dwc2_hsotg_suspend(dwc2); + + dwc2_drd_suspend(dwc2); + + if (dwc2->params.activate_stm_id_vb_detection) { + unsigned long flags; + u32 ggpio, gotgctl; + + /* + * Need to force the mode to the current mode to avoid Mode + * Mismatch Interrupt when ID detection will be disabled. + */ + dwc2_force_mode(dwc2, !is_device_mode); + + spin_lock_irqsave(&dwc2->lock, flags); + gotgctl = dwc2_readl(dwc2, GOTGCTL); + /* bypass debounce filter, enable overrides */ + gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS; + gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN; + /* Force A / B session if needed */ + if (gotgctl & GOTGCTL_ASESVLD) + gotgctl |= GOTGCTL_AVALOVAL; + if (gotgctl & GOTGCTL_BSESVLD) + gotgctl |= GOTGCTL_BVALOVAL; + dwc2_writel(dwc2, gotgctl, GOTGCTL); + spin_unlock_irqrestore(&dwc2->lock, flags); + + ggpio = dwc2_readl(dwc2, GGPIO); + ggpio &= ~GGPIO_STM32_OTG_GCCFG_IDEN; + ggpio &= ~GGPIO_STM32_OTG_GCCFG_VBDEN; + dwc2_writel(dwc2, ggpio, GGPIO); + + regulator_disable(dwc2->usb33d); + } + + if (is_device_mode) + ret = dwc2_gadget_backup_critical_registers(dwc2); + else + ret = dwc2_host_backup_critical_registers(dwc2); + + if (ret) + return ret; + + if (dwc2->ll_hw_enabled && + (is_device_mode || dwc2_host_can_poweroff_phy(dwc2))) { + ret = __dwc2_lowlevel_hw_disable(dwc2); + dwc2->phy_off_for_suspend = true; + } + + return ret; +} + +static int dwc2_restore_critical_registers(struct dwc2_hsotg *hsotg) +{ + struct dwc2_gregs_backup *gr; + + gr = &hsotg->gr_backup; + + if (!gr->valid) { + dev_err(hsotg->dev, "No valid register backup, failed to restore\n"); + return -EINVAL; + } + + if (gr->gintsts & GINTSTS_CURMODE_HOST) + return dwc2_host_restore_critical_registers(hsotg); + + return dwc2_gadget_restore_critical_registers(hsotg, DWC2_RESTORE_DCTL | + DWC2_RESTORE_DCFG); +} + +static int __maybe_unused dwc2_resume(struct device *dev) +{ + struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev); + int ret = 0; + + if (!dwc2->ll_hw_enabled) + return 0; + + if (dwc2->phy_off_for_suspend && dwc2->ll_hw_enabled) { + ret = __dwc2_lowlevel_hw_enable(dwc2); + if (ret) + return ret; + } + dwc2->phy_off_for_suspend = false; + + /* + * During suspend it's possible that the power domain for the + * DWC2 controller is disabled and all register values get lost. + * In case the GUSBCFG register is not initialized, it's clear the + * registers must be restored. + */ + if (!(dwc2_readl(dwc2, GUSBCFG) & GUSBCFG_TOUTCAL_MASK)) { + ret = dwc2_restore_critical_registers(dwc2); + if (ret) + return ret; + } + + if (dwc2->params.activate_stm_id_vb_detection) { + unsigned long flags; + u32 ggpio, gotgctl; + + ret = regulator_enable(dwc2->usb33d); + if (ret) + return ret; + + ggpio = dwc2_readl(dwc2, GGPIO); + ggpio |= GGPIO_STM32_OTG_GCCFG_IDEN; + ggpio |= GGPIO_STM32_OTG_GCCFG_VBDEN; + dwc2_writel(dwc2, ggpio, GGPIO); + + /* ID/VBUS detection startup time */ + usleep_range(5000, 7000); + + spin_lock_irqsave(&dwc2->lock, flags); + gotgctl = dwc2_readl(dwc2, GOTGCTL); + gotgctl &= ~GOTGCTL_DBNCE_FLTR_BYPASS; + gotgctl &= ~(GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | + GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL); + dwc2_writel(dwc2, gotgctl, GOTGCTL); + spin_unlock_irqrestore(&dwc2->lock, flags); + } + + if (!dwc2->role_sw) { + /* Need to restore FORCEDEVMODE/FORCEHOSTMODE */ + dwc2_force_dr_mode(dwc2); + } else { + dwc2_drd_resume(dwc2); + } + + if (dwc2_is_device_mode(dwc2)) + ret = dwc2_hsotg_resume(dwc2); + + return ret; +} + +static const struct dev_pm_ops dwc2_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc2_suspend, dwc2_resume) +}; + +static struct platform_driver dwc2_platform_driver = { + .driver = { + .name = dwc2_driver_name, + .of_match_table = dwc2_of_match_table, + .acpi_match_table = ACPI_PTR(dwc2_acpi_match), + .pm = &dwc2_dev_pm_ops, + }, + .probe = dwc2_driver_probe, + .remove = dwc2_driver_remove, + .shutdown = dwc2_driver_shutdown, +}; + +module_platform_driver(dwc2_platform_driver); diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig new file mode 100644 index 000000000..18169727a --- /dev/null +++ b/drivers/usb/dwc3/Kconfig @@ -0,0 +1,237 @@ +# SPDX-License-Identifier: GPL-2.0 + +config USB_DWC3 + tristate "DesignWare USB3 DRD Core Support" + depends on (USB || USB_GADGET) && HAS_DMA + depends on (EXTCON || EXTCON=n) + select USB_XHCI_PLATFORM if USB_XHCI_HCD + select USB_ROLE_SWITCH if USB_DWC3_DUAL_ROLE + 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 as a dynamically linked + module, the module will be called dwc3.ko. + +if USB_DWC3 + +config USB_DWC3_ULPI + bool "Register ULPI PHY Interface" + depends on USB_ULPI_BUS=y || USB_ULPI_BUS=USB_DWC3 + help + Select this if you have ULPI type PHY attached to your DWC3 + controller. + +choice + prompt "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 + +comment "Platform Glue Driver Support" + +config USB_DWC3_OMAP + tristate "Texas Instruments OMAP5 and similar Platforms" + depends on ARCH_OMAP2PLUS || COMPILE_TEST + depends on EXTCON || !EXTCON + depends on OF + default USB_DWC3 + help + Some platforms from Texas Instruments like OMAP5, DRA7xxx and + AM437x use this IP for USB2/3 functionality. + + Say 'Y' or 'M' here if you have one such device + +config USB_DWC3_EXYNOS + tristate "Samsung Exynos SoC Platform" + depends on (ARCH_EXYNOS || COMPILE_TEST) && OF + default USB_DWC3 + help + Recent Samsung Exynos SoCs (Exynos5250, Exynos5410, Exynos542x, + Exynos5800, Exynos5433, Exynos7) ship with one DesignWare Core USB3 + IP inside, say 'Y' or 'M' if you have one such device. + +config USB_DWC3_PCI + tristate "PCIe-based Platforms" + depends on USB_PCI && ACPI + default USB_DWC3 + help + If you're using the DesignWare Core IP with a PCIe (but not HAPS + platform), please say 'Y' or 'M' here. + +config USB_DWC3_HAPS + tristate "Synopsys PCIe-based HAPS Platforms" + depends on USB_PCI + default USB_DWC3 + help + If you're using the DesignWare Core IP with a Synopsys PCIe HAPS + platform, please say 'Y' or 'M' here. + +config USB_DWC3_KEYSTONE + tristate "Texas Instruments Keystone2/AM654 Platforms" + depends on ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST + default USB_DWC3 + help + Support of USB2/3 functionality in TI Keystone2 and AM654 platforms. + Say 'Y' or 'M' here if you have one such device + +config USB_DWC3_MESON_G12A + tristate "Amlogic Meson G12A Platforms" + depends on OF && COMMON_CLK + depends on ARCH_MESON || COMPILE_TEST + default USB_DWC3 + select USB_ROLE_SWITCH + select REGMAP_MMIO + help + Support USB2/3 functionality in Amlogic G12A platforms. + Say 'Y' or 'M' if you have one such device. + +config USB_DWC3_OF_SIMPLE + tristate "Generic OF Simple Glue Layer" + depends on OF && COMMON_CLK + default USB_DWC3 + help + Support USB2/3 functionality in simple SoC integrations. + Currently supports Xilinx and Qualcomm DWC USB3 IP. + Say 'Y' or 'M' if you have one such device. + +config USB_DWC3_ST + tristate "STMicroelectronics Platforms" + depends on (ARCH_STI || COMPILE_TEST) && OF + default USB_DWC3 + help + STMicroelectronics SoCs with one DesignWare Core USB3 IP + inside (i.e. STiH407). + Say 'Y' or 'M' if you have one such device. + +config USB_DWC3_QCOM + tristate "Qualcomm Platform" + depends on ARCH_QCOM || COMPILE_TEST + depends on EXTCON || !EXTCON + depends on OF + default USB_DWC3 + help + Some Qualcomm SoCs use DesignWare Core IP for USB2/3 + functionality. + This driver also handles Qscratch wrapper which is needed + for peripheral mode support. + Say 'Y' or 'M' if you have one such device. + +config USB_DWC3_IMX8MP + tristate "NXP iMX8MP Platform" + depends on OF && COMMON_CLK + depends on (ARCH_MXC && ARM64) || COMPILE_TEST + default USB_DWC3 + help + NXP iMX8M Plus SoC use DesignWare Core IP for USB2/3 + functionality. + Say 'Y' or 'M' if you have one such device. + +config USB_DWC3_IMX + tristate "NXP iMX Platform" + depends on OF && COMMON_CLK + depends on (ARCH_MXC && ARM64) || COMPILE_TEST + default USB_DWC3 + help + NXP iMX SoC use DesignWare Core IP for USB2/3 + functionality. + This driver also handles the wakeup feature outside + of DesignWare Core. + Say 'Y' or 'M' if you have one such device. + +config USB_DWC3_XILINX + tristate "Xilinx Platforms" + depends on (ARCH_ZYNQMP || COMPILE_TEST) && OF + default USB_DWC3 + help + Support Xilinx SoCs with DesignWare Core USB3 IP. + This driver handles ZynqMP SoC operations. + Say 'Y' or 'M' if you have one such device. + +config USB_DWC3_AM62 + tristate "Texas Instruments AM62 Platforms" + depends on ARCH_K3 || COMPILE_TEST + default USB_DWC3 + help + Support TI's AM62 platforms with DesignWare Core USB3 IP. + The Designware Core USB3 IP is programmed to operate in + in USB 2.0 mode only. + Say 'Y' or 'M' here if you have one such device + +config USB_DWC3_OCTEON + tristate "Cavium Octeon Platforms" + depends on CAVIUM_OCTEON_SOC || COMPILE_TEST + default USB_DWC3 + help + Support Cavium Octeon platforms with DesignWare Core USB3 IP. + Only the host mode is currently supported. + Say 'Y' or 'M' here if you have one such device. + +config USB_DWC3_RTK + tristate "Realtek DWC3 Platform Driver" + depends on OF && ARCH_REALTEK + default USB_DWC3 + select USB_ROLE_SWITCH + help + RTK DHC RTD SoCs with DesignWare Core USB3 IP inside, + and IP Core configured for USB 2.0 and USB 3.0 in host + or dual-role mode. + Say 'Y' or 'M' if you have such device. + +config USB_DWC3_GENERIC_PLAT + tristate "DWC3 Generic Platform Driver" + depends on OF && COMMON_CLK + default USB_DWC3 + help + Support USB3 functionality in simple SoC integrations. + Currently supports SpacemiT DWC USB3. Platforms using + dwc3-of-simple can easily switch to dwc3-generic by flattening + the dwc3 child node in the device tree. + Say 'Y' or 'M' here if your platform integrates DWC3 in a similar way. + +config USB_DWC3_APPLE + tristate "Apple Silicon DWC3 Platform Driver" + depends on OF && ARCH_APPLE + default USB_DWC3 + select USB_ROLE_SWITCH + help + Support Apple Silicon SoCs with DesignWare Core USB3 IP. + The DesignWare Core USB3 IP has to be used in dual-role + mode on these machines. + Say 'Y' or 'M' if you have such device. + +config USB_DWC3_GOOGLE + tristate "Google Platform" + help + Support the DesignWare Core USB3 IP found on Google Tensor SoCs, + starting with the G5 generation (Laguna). This driver includes + support for hibernation in host mode. + Say 'Y' or 'M' if you have one such device. + + To compile this driver as a module, choose M here: the + module will be called dwc3-google.ko. + +endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile new file mode 100644 index 000000000..f37971197 --- /dev/null +++ b/drivers/usb/dwc3/Makefile @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: GPL-2.0 +# define_trace.h needs to know how to find our header +CFLAGS_trace.o := -I$(src) + +obj-$(CONFIG_USB_DWC3) += dwc3.o + +dwc3-y := core.o + +ifneq ($(CONFIG_TRACING),) + dwc3-y += trace.o +endif + +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_USB_DWC3_DUAL_ROLE),) + dwc3-y += drd.o +endif + +ifneq ($(CONFIG_USB_DWC3_ULPI),) + dwc3-y += ulpi.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. +## + +obj-$(CONFIG_USB_DWC3_AM62) += dwc3-am62.o +obj-$(CONFIG_USB_DWC3_APPLE) += dwc3-apple.o +obj-$(CONFIG_USB_DWC3_OMAP) += dwc3-omap.o +obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o +obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o +obj-$(CONFIG_USB_DWC3_HAPS) += dwc3-haps.o +obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o +obj-$(CONFIG_USB_DWC3_MESON_G12A) += dwc3-meson-g12a.o +obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o +obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o +obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o +obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom-legacy.o +obj-$(CONFIG_USB_DWC3_IMX8MP) += dwc3-imx8mp.o +obj-$(CONFIG_USB_DWC3_IMX) += dwc3-imx.o +obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o +obj-$(CONFIG_USB_DWC3_OCTEON) += dwc3-octeon.o +obj-$(CONFIG_USB_DWC3_RTK) += dwc3-rtk.o +obj-$(CONFIG_USB_DWC3_GENERIC_PLAT) += dwc3-generic-plat.o +obj-$(CONFIG_USB_DWC3_GOOGLE) += dwc3-google.o diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c new file mode 100644 index 000000000..fd5c2cd36 --- /dev/null +++ b/drivers/usb/dwc3/core.c @@ -0,0 +1,2936 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * core.c - DesignWare USB3 DRD Controller Core file + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + */ + +#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 "core.h" +#include "gadget.h" +#include "glue.h" +#include "io.h" + +#include "debug.h" +#include "../host/xhci-ext-caps.h" + +#define DWC3_DEFAULT_AUTOSUSPEND_DELAY 5000 /* ms */ + +/** + * dwc3_get_dr_mode - Validates and sets dr_mode + * @dwc: pointer to our context structure + */ +static int dwc3_get_dr_mode(struct dwc3 *dwc) +{ + enum usb_dr_mode mode; + struct device *dev = dwc->dev; + unsigned int hw_mode; + + if (dwc->dr_mode == USB_DR_MODE_UNKNOWN) + dwc->dr_mode = USB_DR_MODE_OTG; + + mode = dwc->dr_mode; + hw_mode = DWC3_GHWPARAMS0_MODE(dwc->hwparams.hwparams0); + + switch (hw_mode) { + case DWC3_GHWPARAMS0_MODE_GADGET: + if (IS_ENABLED(CONFIG_USB_DWC3_HOST)) { + dev_err(dev, + "Controller does not support host mode.\n"); + return -EINVAL; + } + mode = USB_DR_MODE_PERIPHERAL; + break; + case DWC3_GHWPARAMS0_MODE_HOST: + if (IS_ENABLED(CONFIG_USB_DWC3_GADGET)) { + dev_err(dev, + "Controller does not support device mode.\n"); + return -EINVAL; + } + mode = USB_DR_MODE_HOST; + break; + default: + if (IS_ENABLED(CONFIG_USB_DWC3_HOST)) + mode = USB_DR_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_DWC3_GADGET)) + mode = USB_DR_MODE_PERIPHERAL; + + /* + * DWC_usb31 and DWC_usb3 v3.30a and higher do not support OTG + * mode. If the controller supports DRD but the dr_mode is not + * specified or set to OTG, then set the mode to peripheral. + */ + if (mode == USB_DR_MODE_OTG && !dwc->edev && + (!IS_ENABLED(CONFIG_USB_ROLE_SWITCH) || + !device_property_read_bool(dwc->dev, "usb-role-switch")) && + !DWC3_VER_IS_PRIOR(DWC3, 330A)) + mode = USB_DR_MODE_PERIPHERAL; + } + + if (mode != dwc->dr_mode) { + dev_warn(dev, + "Configuration mismatch. dr_mode forced to %s\n", + mode == USB_DR_MODE_HOST ? "host" : "gadget"); + + dwc->dr_mode = mode; + } + + return 0; +} + +void dwc3_enable_susphy(struct dwc3 *dwc, bool enable) +{ + u32 reg; + int i; + + for (i = 0; i < dwc->num_usb3_ports; i++) { + reg = dwc3_readl(dwc, DWC3_GUSB3PIPECTL(i)); + if (enable && !dwc->dis_u3_susphy_quirk) + reg |= DWC3_GUSB3PIPECTL_SUSPHY; + else + reg &= ~DWC3_GUSB3PIPECTL_SUSPHY; + + dwc3_writel(dwc, DWC3_GUSB3PIPECTL(i), reg); + } + + for (i = 0; i < dwc->num_usb2_ports; i++) { + reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(i)); + if (enable && !dwc->dis_u2_susphy_quirk) + reg |= DWC3_GUSB2PHYCFG_SUSPHY; + else + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + + dwc3_writel(dwc, DWC3_GUSB2PHYCFG(i), reg); + } +} +EXPORT_SYMBOL_GPL(dwc3_enable_susphy); + +void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy) +{ + unsigned int hw_mode; + u32 reg; + + reg = dwc3_readl(dwc, DWC3_GCTL); + + /* + * For DRD controllers, GUSB3PIPECTL.SUSPENDENABLE and + * GUSB2PHYCFG.SUSPHY should be cleared during mode switching, + * and they can be set after core initialization. + */ + hw_mode = DWC3_GHWPARAMS0_MODE(dwc->hwparams.hwparams0); + if (hw_mode == DWC3_GHWPARAMS0_MODE_DRD && !ignore_susphy) { + if (DWC3_GCTL_PRTCAP(reg) != mode) + dwc3_enable_susphy(dwc, false); + } + + reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG)); + reg |= DWC3_GCTL_PRTCAPDIR(mode); + dwc3_writel(dwc, DWC3_GCTL, reg); + + dwc->current_dr_role = mode; + trace_dwc3_set_prtcap(dwc, mode); +} +EXPORT_SYMBOL_GPL(dwc3_set_prtcap); + +static void __dwc3_set_mode(struct work_struct *work) +{ + struct dwc3 *dwc = work_to_dwc(work); + unsigned long flags; + int ret; + u32 reg; + u32 desired_dr_role; + int i; + + mutex_lock(&dwc->mutex); + spin_lock_irqsave(&dwc->lock, flags); + desired_dr_role = dwc->desired_dr_role; + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_get_sync(dwc->dev); + + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_OTG) + dwc3_otg_update(dwc, 0); + + if (!desired_dr_role) + goto out; + + if (desired_dr_role == dwc->current_dr_role) + goto out; + + if (desired_dr_role == DWC3_GCTL_PRTCAP_OTG && dwc->edev) + goto out; + + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_HOST: + dwc3_host_exit(dwc); + break; + case DWC3_GCTL_PRTCAP_DEVICE: + dwc3_gadget_exit(dwc); + dwc3_event_buffers_cleanup(dwc); + break; + case DWC3_GCTL_PRTCAP_OTG: + dwc3_otg_exit(dwc); + spin_lock_irqsave(&dwc->lock, flags); + dwc->desired_otg_role = DWC3_OTG_ROLE_IDLE; + spin_unlock_irqrestore(&dwc->lock, flags); + dwc3_otg_update(dwc, 1); + break; + default: + break; + } + + /* + * When current_dr_role is not set, there's no role switching. + * Only perform GCTL.CoreSoftReset when there's DRD role switching. + */ + if (dwc->current_dr_role && ((DWC3_IP_IS(DWC3) || + DWC3_VER_IS_PRIOR(DWC31, 190A)) && + desired_dr_role != DWC3_GCTL_PRTCAP_OTG)) { + reg = dwc3_readl(dwc, DWC3_GCTL); + reg |= DWC3_GCTL_CORESOFTRESET; + dwc3_writel(dwc, DWC3_GCTL, reg); + + /* + * Wait for internal clocks to synchronized. DWC_usb31 and + * DWC_usb32 may need at least 50ms (less for DWC_usb3). To + * keep it consistent across different IPs, let's wait up to + * 100ms before clearing GCTL.CORESOFTRESET. + */ + msleep(100); + + reg = dwc3_readl(dwc, DWC3_GCTL); + reg &= ~DWC3_GCTL_CORESOFTRESET; + dwc3_writel(dwc, DWC3_GCTL, reg); + } + + spin_lock_irqsave(&dwc->lock, flags); + + dwc3_set_prtcap(dwc, desired_dr_role, false); + + spin_unlock_irqrestore(&dwc->lock, flags); + + switch (desired_dr_role) { + case DWC3_GCTL_PRTCAP_HOST: + ret = dwc3_host_init(dwc); + if (ret) { + dev_err(dwc->dev, "failed to initialize host\n"); + } else { + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, true); + + for (i = 0; i < dwc->num_usb2_ports; i++) + phy_set_mode(dwc->usb2_generic_phy[i], PHY_MODE_USB_HOST); + for (i = 0; i < dwc->num_usb3_ports; i++) + phy_set_mode(dwc->usb3_generic_phy[i], PHY_MODE_USB_HOST); + + if (dwc->dis_split_quirk) { + reg = dwc3_readl(dwc, DWC3_GUCTL3); + reg |= DWC3_GUCTL3_SPLITDISABLE; + dwc3_writel(dwc, DWC3_GUCTL3, reg); + } + } + break; + case DWC3_GCTL_PRTCAP_DEVICE: + dwc3_core_soft_reset(dwc); + + dwc3_event_buffers_setup(dwc); + + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, false); + phy_set_mode(dwc->usb2_generic_phy[0], PHY_MODE_USB_DEVICE); + phy_set_mode(dwc->usb3_generic_phy[0], PHY_MODE_USB_DEVICE); + + ret = dwc3_gadget_init(dwc); + if (ret) + dev_err(dwc->dev, "failed to initialize peripheral\n"); + break; + case DWC3_GCTL_PRTCAP_OTG: + dwc3_otg_init(dwc); + dwc3_otg_update(dwc, 0); + break; + default: + break; + } + +out: + pm_runtime_put_autosuspend(dwc->dev); + mutex_unlock(&dwc->mutex); +} + +void dwc3_set_mode(struct dwc3 *dwc, u32 mode) +{ + unsigned long flags; + + if (dwc->dr_mode != USB_DR_MODE_OTG) + return; + + spin_lock_irqsave(&dwc->lock, flags); + dwc->desired_dr_role = mode; + spin_unlock_irqrestore(&dwc->lock, flags); + + queue_work(system_freezable_wq, &dwc->drd_work); +} + +u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type) +{ + struct dwc3 *dwc = dep->dwc; + u32 reg; + + dwc3_writel(dwc, DWC3_GDBGFIFOSPACE, + DWC3_GDBGFIFOSPACE_NUM(dep->number) | + DWC3_GDBGFIFOSPACE_TYPE(type)); + + reg = dwc3_readl(dwc, DWC3_GDBGFIFOSPACE); + + return DWC3_GDBGFIFOSPACE_SPACE_AVAILABLE(reg); +} + +/** + * dwc3_core_soft_reset - Issues core soft reset and PHY reset + * @dwc: pointer to our context structure + */ +int dwc3_core_soft_reset(struct dwc3 *dwc) +{ + u32 reg; + int retries = 1000; + + /* + * We're resetting only the device side because, if we're in host mode, + * XHCI driver will reset the host block. If dwc3 was configured for + * host-only mode, then we can return early. + */ + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST) + return 0; + + reg = dwc3_readl(dwc, DWC3_DCTL); + reg |= DWC3_DCTL_CSFTRST; + reg &= ~DWC3_DCTL_RUN_STOP; + dwc3_gadget_dctl_write_safe(dwc, reg); + + /* + * For DWC_usb31 controller 1.90a and later, the DCTL.CSFRST bit + * is cleared only after all the clocks are synchronized. This can + * take a little more than 50ms. Set the polling rate at 20ms + * for 10 times instead. + */ + if (DWC3_VER_IS_WITHIN(DWC31, 190A, ANY) || DWC3_IP_IS(DWC32)) + retries = 10; + + do { + reg = dwc3_readl(dwc, DWC3_DCTL); + if (!(reg & DWC3_DCTL_CSFTRST)) + goto done; + + if (DWC3_VER_IS_WITHIN(DWC31, 190A, ANY) || DWC3_IP_IS(DWC32)) + msleep(20); + else + udelay(1); + } while (--retries); + + dev_warn(dwc->dev, "DWC3 controller soft reset failed.\n"); + return -ETIMEDOUT; + +done: + /* + * For DWC_usb31 controller 1.80a and prior, once DCTL.CSFRST bit + * is cleared, we must wait at least 50ms before accessing the PHY + * domain (synchronization delay). + */ + if (DWC3_VER_IS_WITHIN(DWC31, ANY, 180A)) + msleep(50); + + return 0; +} + +/* + * dwc3_frame_length_adjustment - Adjusts frame length if required + * @dwc3: Pointer to our controller context structure + */ +static void dwc3_frame_length_adjustment(struct dwc3 *dwc) +{ + u32 reg; + u32 dft; + + if (DWC3_VER_IS_PRIOR(DWC3, 250A)) + return; + + if (dwc->fladj == 0) + return; + + reg = dwc3_readl(dwc, DWC3_GFLADJ); + dft = reg & DWC3_GFLADJ_30MHZ_MASK; + if (dft != dwc->fladj) { + reg &= ~DWC3_GFLADJ_30MHZ_MASK; + reg |= DWC3_GFLADJ_30MHZ_SDBND_SEL | dwc->fladj; + dwc3_writel(dwc, DWC3_GFLADJ, reg); + } +} + +/** + * dwc3_ref_clk_period - Reference clock period configuration + * Default reference clock period depends on hardware + * configuration. For systems with reference clock that differs + * from the default, this will set clock period in DWC3_GUCTL + * register. + * @dwc: Pointer to our controller context structure + */ +static void dwc3_ref_clk_period(struct dwc3 *dwc) +{ + unsigned long period; + unsigned long fladj; + unsigned long decr; + unsigned long rate; + u32 reg; + + if (dwc->ref_clk) { + rate = clk_get_rate(dwc->ref_clk); + if (!rate) + return; + period = NSEC_PER_SEC / rate; + } else if (dwc->ref_clk_per) { + period = dwc->ref_clk_per; + rate = NSEC_PER_SEC / period; + } else { + return; + } + + reg = dwc3_readl(dwc, DWC3_GUCTL); + FIELD_MODIFY(DWC3_GUCTL_REFCLKPER_MASK, ®, period); + dwc3_writel(dwc, DWC3_GUCTL, reg); + + if (DWC3_VER_IS_PRIOR(DWC3, 250A)) + return; + + /* + * The calculation below is + * + * 125000 * (NSEC_PER_SEC / (rate * period) - 1) + * + * but rearranged for fixed-point arithmetic. The division must be + * 64-bit because 125000 * NSEC_PER_SEC doesn't fit in 32 bits (and + * neither does rate * period). + * + * Note that rate * period ~= NSEC_PER_SECOND, minus the number of + * nanoseconds of error caused by the truncation which happened during + * the division when calculating rate or period (whichever one was + * derived from the other). We first calculate the relative error, then + * scale it to units of 8 ppm. + */ + fladj = div64_u64(125000ULL * NSEC_PER_SEC, (u64)rate * period); + fladj -= 125000; + + /* + * The documented 240MHz constant is scaled by 2 to get PLS1 as well. + */ + decr = 480000000 / rate; + + reg = dwc3_readl(dwc, DWC3_GFLADJ); + FIELD_MODIFY(DWC3_GFLADJ_REFCLK_FLADJ_MASK, ®, fladj); + FIELD_MODIFY(DWC3_GFLADJ_240MHZDECR, ®, decr >> 1); + FIELD_MODIFY(DWC3_GFLADJ_240MHZDECR_PLS1, ®, decr & 1); + + if (dwc->gfladj_refclk_lpm_sel) + reg |= DWC3_GFLADJ_REFCLK_LPM_SEL; + + dwc3_writel(dwc, DWC3_GFLADJ, 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->sysdev, 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 int 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->cache = devm_kzalloc(dwc->dev, length, GFP_KERNEL); + if (!evt->cache) + return ERR_PTR(-ENOMEM); + + evt->buf = dma_alloc_coherent(dwc->sysdev, 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; + + evt = dwc->ev_buf; + if (evt) + dwc3_free_one_event_buffer(dwc, evt); +} + +/** + * dwc3_alloc_event_buffers - Allocate one event buffer 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 int length) +{ + struct dwc3_event_buffer *evt; + unsigned int hw_mode; + + hw_mode = DWC3_GHWPARAMS0_MODE(dwc->hwparams.hwparams0); + if (hw_mode == DWC3_GHWPARAMS0_MODE_HOST) { + dwc->ev_buf = NULL; + return 0; + } + + 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_buf = 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. + */ +int dwc3_event_buffers_setup(struct dwc3 *dwc) +{ + struct dwc3_event_buffer *evt; + u32 reg; + + if (!dwc->ev_buf) + return 0; + + evt = dwc->ev_buf; + evt->lpos = 0; + dwc3_writel(dwc, DWC3_GEVNTADRLO(0), + lower_32_bits(evt->dma)); + dwc3_writel(dwc, DWC3_GEVNTADRHI(0), + upper_32_bits(evt->dma)); + dwc3_writel(dwc, DWC3_GEVNTSIZ(0), + DWC3_GEVNTSIZ_SIZE(evt->length)); + + /* Clear any stale event */ + reg = dwc3_readl(dwc, DWC3_GEVNTCOUNT(0)); + dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), reg); + return 0; +} + +void dwc3_event_buffers_cleanup(struct dwc3 *dwc) +{ + struct dwc3_event_buffer *evt; + u32 reg; + + if (!dwc->ev_buf) + return; + /* + * Exynos platforms may not be able to access event buffer if the + * controller failed to halt on dwc3_core_exit(). + */ + reg = dwc3_readl(dwc, DWC3_DSTS); + if (!(reg & DWC3_DSTS_DEVCTRLHLT)) + return; + + evt = dwc->ev_buf; + + evt->lpos = 0; + + dwc3_writel(dwc, DWC3_GEVNTADRLO(0), 0); + dwc3_writel(dwc, DWC3_GEVNTADRHI(0), 0); + dwc3_writel(dwc, DWC3_GEVNTSIZ(0), DWC3_GEVNTSIZ_INTMASK + | DWC3_GEVNTSIZ_SIZE(0)); + + /* Clear any stale event */ + reg = dwc3_readl(dwc, DWC3_GEVNTCOUNT(0)); + dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), reg); +} + +static void dwc3_core_num_eps(struct dwc3 *dwc) +{ + struct dwc3_hwparams *parms = &dwc->hwparams; + + dwc->num_eps = DWC3_NUM_EPS(parms); +} + +static void dwc3_cache_hwparams(struct dwc3 *dwc) +{ + struct dwc3_hwparams *parms = &dwc->hwparams; + + parms->hwparams0 = dwc3_readl(dwc, DWC3_GHWPARAMS0); + parms->hwparams1 = dwc3_readl(dwc, DWC3_GHWPARAMS1); + parms->hwparams2 = dwc3_readl(dwc, DWC3_GHWPARAMS2); + parms->hwparams3 = dwc3_readl(dwc, DWC3_GHWPARAMS3); + parms->hwparams4 = dwc3_readl(dwc, DWC3_GHWPARAMS4); + parms->hwparams5 = dwc3_readl(dwc, DWC3_GHWPARAMS5); + parms->hwparams6 = dwc3_readl(dwc, DWC3_GHWPARAMS6); + parms->hwparams7 = dwc3_readl(dwc, DWC3_GHWPARAMS7); + parms->hwparams8 = dwc3_readl(dwc, DWC3_GHWPARAMS8); + + if (DWC3_IP_IS(DWC32)) + parms->hwparams9 = dwc3_readl(dwc, DWC3_GHWPARAMS9); +} + +static void dwc3_config_soc_bus(struct dwc3 *dwc) +{ + if (dwc->gsbuscfg0_reqinfo != DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED) { + u32 reg; + + reg = dwc3_readl(dwc, DWC3_GSBUSCFG0); + reg &= ~DWC3_GSBUSCFG0_REQINFO(~0); + reg |= DWC3_GSBUSCFG0_REQINFO(dwc->gsbuscfg0_reqinfo); + dwc3_writel(dwc, DWC3_GSBUSCFG0, reg); + } +} + +static int dwc3_core_ulpi_init(struct dwc3 *dwc) +{ + int intf; + int ret = 0; + + intf = DWC3_GHWPARAMS3_HSPHY_IFC(dwc->hwparams.hwparams3); + + if (intf == DWC3_GHWPARAMS3_HSPHY_IFC_ULPI || + (intf == DWC3_GHWPARAMS3_HSPHY_IFC_UTMI_ULPI && + dwc->hsphy_interface && + !strncmp(dwc->hsphy_interface, "ulpi", 4))) + ret = dwc3_ulpi_init(dwc); + + return ret; +} + +static int dwc3_ss_phy_setup(struct dwc3 *dwc, int index) +{ + u32 reg; + + reg = dwc3_readl(dwc, DWC3_GUSB3PIPECTL(index)); + + /* + * Make sure UX_EXIT_PX is cleared as that causes issues with some + * PHYs. Also, this bit is not supposed to be used in normal operation. + */ + reg &= ~DWC3_GUSB3PIPECTL_UX_EXIT_PX; + + /* Ensure the GUSB3PIPECTL.SUSPENDENABLE is cleared prior to phy init. */ + reg &= ~DWC3_GUSB3PIPECTL_SUSPHY; + + if (dwc->u2ss_inp3_quirk) + reg |= DWC3_GUSB3PIPECTL_U2SSINP3OK; + + if (dwc->dis_rxdet_inp3_quirk) + reg |= DWC3_GUSB3PIPECTL_DISRXDETINP3; + + if (dwc->req_p1p2p3_quirk) + reg |= DWC3_GUSB3PIPECTL_REQP1P2P3; + + if (dwc->del_p1p2p3_quirk) + reg |= DWC3_GUSB3PIPECTL_DEP1P2P3_EN; + + if (dwc->del_phy_power_chg_quirk) + reg |= DWC3_GUSB3PIPECTL_DEPOCHANGE; + + if (dwc->lfps_filter_quirk) + reg |= DWC3_GUSB3PIPECTL_LFPSFILT; + + if (dwc->rx_detect_poll_quirk) + reg |= DWC3_GUSB3PIPECTL_RX_DETOPOLL; + + if (dwc->tx_de_emphasis_quirk) + reg |= DWC3_GUSB3PIPECTL_TX_DEEPH(dwc->tx_de_emphasis); + + if (dwc->dis_del_phy_power_chg_quirk) + reg &= ~DWC3_GUSB3PIPECTL_DEPOCHANGE; + + dwc3_writel(dwc, DWC3_GUSB3PIPECTL(index), reg); + + return 0; +} + +static int dwc3_hs_phy_setup(struct dwc3 *dwc, int index) +{ + u32 reg; + + reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(index)); + + /* Select the HS PHY interface */ + switch (DWC3_GHWPARAMS3_HSPHY_IFC(dwc->hwparams.hwparams3)) { + case DWC3_GHWPARAMS3_HSPHY_IFC_UTMI_ULPI: + if (dwc->hsphy_interface && + !strncmp(dwc->hsphy_interface, "utmi", 4)) { + reg &= ~DWC3_GUSB2PHYCFG_ULPI_UTMI; + break; + } else if (dwc->hsphy_interface && + !strncmp(dwc->hsphy_interface, "ulpi", 4)) { + reg |= DWC3_GUSB2PHYCFG_ULPI_UTMI; + dwc3_writel(dwc, DWC3_GUSB2PHYCFG(index), reg); + } else { + /* Relying on default value. */ + if (!(reg & DWC3_GUSB2PHYCFG_ULPI_UTMI)) + break; + } + fallthrough; + case DWC3_GHWPARAMS3_HSPHY_IFC_ULPI: + default: + break; + } + + switch (dwc->hsphy_mode) { + case USBPHY_INTERFACE_MODE_UTMI: + reg &= ~(DWC3_GUSB2PHYCFG_PHYIF_MASK | + DWC3_GUSB2PHYCFG_USBTRDTIM_MASK); + reg |= DWC3_GUSB2PHYCFG_PHYIF(UTMI_PHYIF_8_BIT) | + DWC3_GUSB2PHYCFG_USBTRDTIM(USBTRDTIM_UTMI_8_BIT); + break; + case USBPHY_INTERFACE_MODE_UTMIW: + reg &= ~(DWC3_GUSB2PHYCFG_PHYIF_MASK | + DWC3_GUSB2PHYCFG_USBTRDTIM_MASK); + reg |= DWC3_GUSB2PHYCFG_PHYIF(UTMI_PHYIF_16_BIT) | + DWC3_GUSB2PHYCFG_USBTRDTIM(USBTRDTIM_UTMI_16_BIT); + break; + default: + break; + } + + /* Ensure the GUSB2PHYCFG.SUSPHY is cleared prior to phy init. */ + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + + if (dwc->dis_enblslpm_quirk) + reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM; + else + reg |= DWC3_GUSB2PHYCFG_ENBLSLPM; + + if (dwc->dis_u2_freeclk_exists_quirk || dwc->gfladj_refclk_lpm_sel) + reg &= ~DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS; + + /* + * Some ULPI USB PHY does not support internal VBUS supply, to drive + * the CPEN pin requires the configuration of the ULPI DRVVBUSEXTERNAL + * bit of OTG_CTRL register. Controller configures the USB2 PHY + * ULPIEXTVBUSDRV bit[17] of the GUSB2PHYCFG register to drive vBus + * with an external supply. + */ + if (dwc->ulpi_ext_vbus_drv) + reg |= DWC3_GUSB2PHYCFG_ULPIEXTVBUSDRV; + + dwc3_writel(dwc, DWC3_GUSB2PHYCFG(index), reg); + + return 0; +} + +static void dwc3_ulpi_setup(struct dwc3 *dwc) +{ + int index; + u32 reg; + + /* Don't do anything if there is no ULPI PHY */ + if (!dwc->ulpi) + return; + + if (dwc->enable_usb2_transceiver_delay) { + for (index = 0; index < dwc->num_usb2_ports; index++) { + reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(index)); + reg |= DWC3_GUSB2PHYCFG_XCVRDLY; + dwc3_writel(dwc, DWC3_GUSB2PHYCFG(index), reg); + } + } +} + +/** + * dwc3_phy_setup - Configure USB PHY Interface of DWC3 Core + * @dwc: Pointer to our controller context structure + * + * Returns 0 on success. The USB PHY interfaces are configured but not + * initialized. The PHY interfaces and the PHYs get initialized together with + * the core in dwc3_core_init. + */ +static int dwc3_phy_setup(struct dwc3 *dwc) +{ + int i; + int ret; + + for (i = 0; i < dwc->num_usb3_ports; i++) { + ret = dwc3_ss_phy_setup(dwc, i); + if (ret) + return ret; + } + + for (i = 0; i < dwc->num_usb2_ports; i++) { + ret = dwc3_hs_phy_setup(dwc, i); + if (ret) + return ret; + } + + return 0; +} + +static int dwc3_phy_init(struct dwc3 *dwc) +{ + int ret; + int i; + int j; + + usb_phy_init(dwc->usb2_phy); + usb_phy_init(dwc->usb3_phy); + + for (i = 0; i < dwc->num_usb2_ports; i++) { + ret = phy_init(dwc->usb2_generic_phy[i]); + if (ret < 0) + goto err_exit_usb2_phy; + } + + for (j = 0; j < dwc->num_usb3_ports; j++) { + ret = phy_init(dwc->usb3_generic_phy[j]); + if (ret < 0) + goto err_exit_usb3_phy; + } + + /* + * Above DWC_usb3.0 1.94a, it is recommended to set + * DWC3_GUSB3PIPECTL_SUSPHY and DWC3_GUSB2PHYCFG_SUSPHY to '0' during + * coreConsultant configuration. So default value will be '0' when the + * core is reset. Application needs to set it to '1' after the core + * initialization is completed. + * + * Certain phy requires to be in P0 power state during initialization. + * Make sure GUSB3PIPECTL.SUSPENDENABLE and GUSB2PHYCFG.SUSPHY are clear + * prior to phy init to maintain in the P0 state. + * + * After phy initialization, some phy operations can only be executed + * while in lower P states. Ensure GUSB3PIPECTL.SUSPENDENABLE and + * GUSB2PHYCFG.SUSPHY are set soon after initialization to avoid + * blocking phy ops. + */ + if (!DWC3_VER_IS_WITHIN(DWC3, ANY, 194A)) + dwc3_enable_susphy(dwc, true); + + return 0; + +err_exit_usb3_phy: + while (--j >= 0) + phy_exit(dwc->usb3_generic_phy[j]); + +err_exit_usb2_phy: + while (--i >= 0) + phy_exit(dwc->usb2_generic_phy[i]); + + usb_phy_shutdown(dwc->usb3_phy); + usb_phy_shutdown(dwc->usb2_phy); + + return ret; +} + +static void dwc3_phy_exit(struct dwc3 *dwc) +{ + int i; + + for (i = 0; i < dwc->num_usb3_ports; i++) + phy_exit(dwc->usb3_generic_phy[i]); + + for (i = 0; i < dwc->num_usb2_ports; i++) + phy_exit(dwc->usb2_generic_phy[i]); + + usb_phy_shutdown(dwc->usb3_phy); + usb_phy_shutdown(dwc->usb2_phy); +} + +static int dwc3_phy_power_on(struct dwc3 *dwc) +{ + int ret; + int i; + int j; + + usb_phy_set_suspend(dwc->usb2_phy, 0); + usb_phy_set_suspend(dwc->usb3_phy, 0); + + for (i = 0; i < dwc->num_usb2_ports; i++) { + ret = phy_power_on(dwc->usb2_generic_phy[i]); + if (ret < 0) + goto err_power_off_usb2_phy; + } + + for (j = 0; j < dwc->num_usb3_ports; j++) { + ret = phy_power_on(dwc->usb3_generic_phy[j]); + if (ret < 0) + goto err_power_off_usb3_phy; + } + + return 0; + +err_power_off_usb3_phy: + while (--j >= 0) + phy_power_off(dwc->usb3_generic_phy[j]); + +err_power_off_usb2_phy: + while (--i >= 0) + phy_power_off(dwc->usb2_generic_phy[i]); + + usb_phy_set_suspend(dwc->usb3_phy, 1); + usb_phy_set_suspend(dwc->usb2_phy, 1); + + return ret; +} + +static void dwc3_phy_power_off(struct dwc3 *dwc) +{ + int i; + + for (i = 0; i < dwc->num_usb3_ports; i++) + phy_power_off(dwc->usb3_generic_phy[i]); + + for (i = 0; i < dwc->num_usb2_ports; i++) + phy_power_off(dwc->usb2_generic_phy[i]); + + usb_phy_set_suspend(dwc->usb3_phy, 1); + usb_phy_set_suspend(dwc->usb2_phy, 1); +} + +static int dwc3_clk_enable(struct dwc3 *dwc) +{ + int ret; + + ret = clk_prepare_enable(dwc->bus_clk); + if (ret) + return ret; + + ret = clk_prepare_enable(dwc->ref_clk); + if (ret) + goto disable_bus_clk; + + ret = clk_prepare_enable(dwc->susp_clk); + if (ret) + goto disable_ref_clk; + + ret = clk_prepare_enable(dwc->utmi_clk); + if (ret) + goto disable_susp_clk; + + ret = clk_prepare_enable(dwc->pipe_clk); + if (ret) + goto disable_utmi_clk; + + return 0; + +disable_utmi_clk: + clk_disable_unprepare(dwc->utmi_clk); +disable_susp_clk: + clk_disable_unprepare(dwc->susp_clk); +disable_ref_clk: + clk_disable_unprepare(dwc->ref_clk); +disable_bus_clk: + clk_disable_unprepare(dwc->bus_clk); + return ret; +} + +static void dwc3_clk_disable(struct dwc3 *dwc) +{ + clk_disable_unprepare(dwc->pipe_clk); + clk_disable_unprepare(dwc->utmi_clk); + clk_disable_unprepare(dwc->susp_clk); + clk_disable_unprepare(dwc->ref_clk); + clk_disable_unprepare(dwc->bus_clk); +} + +void dwc3_core_exit(struct dwc3 *dwc) +{ + dwc3_event_buffers_cleanup(dwc); + dwc3_phy_power_off(dwc); + dwc3_phy_exit(dwc); + dwc3_clk_disable(dwc); + reset_control_assert(dwc->reset); +} +EXPORT_SYMBOL_GPL(dwc3_core_exit); + +static bool dwc3_core_is_valid(struct dwc3 *dwc) +{ + u32 reg; + + reg = dwc3_readl(dwc, DWC3_GSNPSID); + dwc->ip = DWC3_GSNPS_ID(reg); + if (dwc->ip == DWC4_IP) + dwc->ip = DWC32_IP; + + /* This should read as U3 followed by revision number */ + if (DWC3_IP_IS(DWC3)) { + dwc->revision = reg; + } else if (DWC3_IP_IS(DWC31) || DWC3_IP_IS(DWC32)) { + dwc->revision = dwc3_readl(dwc, DWC3_VER_NUMBER); + dwc->version_type = dwc3_readl(dwc, DWC3_VER_TYPE); + } else { + return false; + } + + return true; +} + +static void dwc3_core_setup_global_control(struct dwc3 *dwc) +{ + unsigned int power_opt; + unsigned int hw_mode; + u32 reg; + + reg = dwc3_readl(dwc, DWC3_GCTL); + reg &= ~DWC3_GCTL_SCALEDOWN_MASK; + hw_mode = DWC3_GHWPARAMS0_MODE(dwc->hwparams.hwparams0); + power_opt = DWC3_GHWPARAMS1_EN_PWROPT(dwc->hwparams.hwparams1); + + switch (power_opt) { + case DWC3_GHWPARAMS1_EN_PWROPT_CLK: + /** + * WORKAROUND: DWC3 revisions between 2.10a and 2.50a have an + * issue which would cause xHCI compliance tests to fail. + * + * Because of that we cannot enable clock gating on such + * configurations. + * + * Refers to: + * + * STAR#9000588375: Clock Gating, SOF Issues when ref_clk-Based + * SOF/ITP Mode Used + */ + if ((dwc->dr_mode == USB_DR_MODE_HOST || + dwc->dr_mode == USB_DR_MODE_OTG) && + DWC3_VER_IS_WITHIN(DWC3, 210A, 250A)) + reg |= DWC3_GCTL_DSBLCLKGTNG | DWC3_GCTL_SOFITPSYNC; + else + reg &= ~DWC3_GCTL_DSBLCLKGTNG; + break; + case DWC3_GHWPARAMS1_EN_PWROPT_HIB: + /* + * REVISIT Enabling this bit so that host-mode hibernation + * will work. Device-mode hibernation is not yet implemented. + */ + reg |= DWC3_GCTL_GBLHIBERNATIONEN; + break; + default: + /* nothing */ + break; + } + + /* + * This is a workaround for STAR#4846132, which only affects + * DWC_usb31 version2.00a operating in host mode. + * + * There is a problem in DWC_usb31 version 2.00a operating + * in host mode that would cause a CSR read timeout When CSR + * read coincides with RAM Clock Gating Entry. By disable + * Clock Gating, sacrificing power consumption for normal + * operation. + */ + if (power_opt != DWC3_GHWPARAMS1_EN_PWROPT_NO && + hw_mode != DWC3_GHWPARAMS0_MODE_GADGET && DWC3_VER_IS(DWC31, 200A)) + reg |= DWC3_GCTL_DSBLCLKGTNG; + + /* check if current dwc3 is on simulation board */ + if (dwc->hwparams.hwparams6 & DWC3_GHWPARAMS6_EN_FPGA) { + dev_info(dwc->dev, "Running with FPGA optimizations\n"); + dwc->is_fpga = true; + } + + WARN_ONCE(dwc->disable_scramble_quirk && !dwc->is_fpga, + "disable_scramble cannot be used on non-FPGA builds\n"); + + if (dwc->disable_scramble_quirk && dwc->is_fpga) + reg |= DWC3_GCTL_DISSCRAMBLE; + else + reg &= ~DWC3_GCTL_DISSCRAMBLE; + + if (dwc->u2exit_lfps_quirk) + reg |= DWC3_GCTL_U2EXIT_LFPS; + + /* + * 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 (DWC3_VER_IS_PRIOR(DWC3, 190A)) + reg |= DWC3_GCTL_U2RSTECN; + + dwc3_writel(dwc, DWC3_GCTL, reg); +} + +static int dwc3_core_get_phy(struct dwc3 *dwc); +static int dwc3_core_ulpi_init(struct dwc3 *dwc); + +/* set global incr burst type configuration registers */ +static void dwc3_set_incr_burst_type(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + /* incrx_mode : for INCR burst type. */ + bool incrx_mode; + /* incrx_size : for size of INCRX burst. */ + u32 incrx_size; + u32 *vals; + u32 cfg; + int ntype; + int ret; + int i; + + cfg = dwc3_readl(dwc, DWC3_GSBUSCFG0); + + /* + * Handle property "snps,incr-burst-type-adjustment". + * Get the number of value from this property: + * result <= 0, means this property is not supported. + * result = 1, means INCRx burst mode supported. + * result > 1, means undefined length burst mode supported. + */ + ntype = device_property_count_u32(dev, "snps,incr-burst-type-adjustment"); + if (ntype <= 0) + return; + + vals = kcalloc(ntype, sizeof(u32), GFP_KERNEL); + if (!vals) + return; + + /* Get INCR burst type, and parse it */ + ret = device_property_read_u32_array(dev, + "snps,incr-burst-type-adjustment", vals, ntype); + if (ret) { + kfree(vals); + dev_err(dev, "Error to get property\n"); + return; + } + + incrx_size = *vals; + + if (ntype > 1) { + /* INCRX (undefined length) burst mode */ + incrx_mode = INCRX_UNDEF_LENGTH_BURST_MODE; + for (i = 1; i < ntype; i++) { + if (vals[i] > incrx_size) + incrx_size = vals[i]; + } + } else { + /* INCRX burst mode */ + incrx_mode = INCRX_BURST_MODE; + } + + kfree(vals); + + /* Enable Undefined Length INCR Burst and Enable INCRx Burst */ + cfg &= ~DWC3_GSBUSCFG0_INCRBRST_MASK; + if (incrx_mode) + cfg |= DWC3_GSBUSCFG0_INCRBRSTENA; + switch (incrx_size) { + case 256: + cfg |= DWC3_GSBUSCFG0_INCR256BRSTENA; + break; + case 128: + cfg |= DWC3_GSBUSCFG0_INCR128BRSTENA; + break; + case 64: + cfg |= DWC3_GSBUSCFG0_INCR64BRSTENA; + break; + case 32: + cfg |= DWC3_GSBUSCFG0_INCR32BRSTENA; + break; + case 16: + cfg |= DWC3_GSBUSCFG0_INCR16BRSTENA; + break; + case 8: + cfg |= DWC3_GSBUSCFG0_INCR8BRSTENA; + break; + case 4: + cfg |= DWC3_GSBUSCFG0_INCR4BRSTENA; + break; + case 1: + break; + default: + dev_err(dev, "Invalid property\n"); + break; + } + + dwc3_writel(dwc, DWC3_GSBUSCFG0, cfg); +} + +static void dwc3_set_power_down_clk_scale(struct dwc3 *dwc) +{ + u32 scale; + u32 reg; + + if (!dwc->susp_clk) + return; + + /* + * The power down scale field specifies how many suspend_clk + * periods fit into a 16KHz clock period. When performing + * the division, round up the remainder. + * + * The power down scale value is calculated using the fastest + * frequency of the suspend_clk. If it isn't fixed (but within + * the accuracy requirement), the driver may not know the max + * rate of the suspend_clk, so only update the power down scale + * if the default is less than the calculated value from + * clk_get_rate() or if the default is questionably high + * (3x or more) to be within the requirement. + */ + scale = DIV_ROUND_UP(clk_get_rate(dwc->susp_clk), 16000); + reg = dwc3_readl(dwc, DWC3_GCTL); + if ((reg & DWC3_GCTL_PWRDNSCALE_MASK) < DWC3_GCTL_PWRDNSCALE(scale) || + (reg & DWC3_GCTL_PWRDNSCALE_MASK) > DWC3_GCTL_PWRDNSCALE(scale*3)) { + reg &= ~(DWC3_GCTL_PWRDNSCALE_MASK); + reg |= DWC3_GCTL_PWRDNSCALE(scale); + dwc3_writel(dwc, DWC3_GCTL, reg); + } +} + +static void dwc3_config_threshold(struct dwc3 *dwc) +{ + u32 reg; + u8 rx_thr_num; + u8 rx_maxburst; + u8 tx_thr_num; + u8 tx_maxburst; + + /* + * Must config both number of packets and max burst settings to enable + * RX and/or TX threshold. + */ + if (!DWC3_IP_IS(DWC3) && dwc->dr_mode == USB_DR_MODE_HOST) { + rx_thr_num = dwc->rx_thr_num_pkt_prd; + rx_maxburst = dwc->rx_max_burst_prd; + tx_thr_num = dwc->tx_thr_num_pkt_prd; + tx_maxburst = dwc->tx_max_burst_prd; + + if (rx_thr_num && rx_maxburst) { + reg = dwc3_readl(dwc, DWC3_GRXTHRCFG); + reg |= DWC31_RXTHRNUMPKTSEL_PRD; + + reg &= ~DWC31_RXTHRNUMPKT_PRD(~0); + reg |= DWC31_RXTHRNUMPKT_PRD(rx_thr_num); + + reg &= ~DWC31_MAXRXBURSTSIZE_PRD(~0); + reg |= DWC31_MAXRXBURSTSIZE_PRD(rx_maxburst); + + dwc3_writel(dwc, DWC3_GRXTHRCFG, reg); + } + + if (tx_thr_num && tx_maxburst) { + reg = dwc3_readl(dwc, DWC3_GTXTHRCFG); + reg |= DWC31_TXTHRNUMPKTSEL_PRD; + + reg &= ~DWC31_TXTHRNUMPKT_PRD(~0); + reg |= DWC31_TXTHRNUMPKT_PRD(tx_thr_num); + + reg &= ~DWC31_MAXTXBURSTSIZE_PRD(~0); + reg |= DWC31_MAXTXBURSTSIZE_PRD(tx_maxburst); + + dwc3_writel(dwc, DWC3_GTXTHRCFG, reg); + } + } + + rx_thr_num = dwc->rx_thr_num_pkt; + rx_maxburst = dwc->rx_max_burst; + tx_thr_num = dwc->tx_thr_num_pkt; + tx_maxburst = dwc->tx_max_burst; + + if (DWC3_IP_IS(DWC3)) { + if (rx_thr_num && rx_maxburst) { + reg = dwc3_readl(dwc, DWC3_GRXTHRCFG); + reg |= DWC3_GRXTHRCFG_PKTCNTSEL; + + reg &= ~DWC3_GRXTHRCFG_RXPKTCNT(~0); + reg |= DWC3_GRXTHRCFG_RXPKTCNT(rx_thr_num); + + reg &= ~DWC3_GRXTHRCFG_MAXRXBURSTSIZE(~0); + reg |= DWC3_GRXTHRCFG_MAXRXBURSTSIZE(rx_maxburst); + + dwc3_writel(dwc, DWC3_GRXTHRCFG, reg); + } + + if (tx_thr_num && tx_maxburst) { + reg = dwc3_readl(dwc, DWC3_GTXTHRCFG); + reg |= DWC3_GTXTHRCFG_PKTCNTSEL; + + reg &= ~DWC3_GTXTHRCFG_TXPKTCNT(~0); + reg |= DWC3_GTXTHRCFG_TXPKTCNT(tx_thr_num); + + reg &= ~DWC3_GTXTHRCFG_MAXTXBURSTSIZE(~0); + reg |= DWC3_GTXTHRCFG_MAXTXBURSTSIZE(tx_maxburst); + + dwc3_writel(dwc, DWC3_GTXTHRCFG, reg); + } + } else { + if (rx_thr_num && rx_maxburst) { + reg = dwc3_readl(dwc, DWC3_GRXTHRCFG); + reg |= DWC31_GRXTHRCFG_PKTCNTSEL; + + reg &= ~DWC31_GRXTHRCFG_RXPKTCNT(~0); + reg |= DWC31_GRXTHRCFG_RXPKTCNT(rx_thr_num); + + reg &= ~DWC31_GRXTHRCFG_MAXRXBURSTSIZE(~0); + reg |= DWC31_GRXTHRCFG_MAXRXBURSTSIZE(rx_maxburst); + + dwc3_writel(dwc, DWC3_GRXTHRCFG, reg); + } + + if (tx_thr_num && tx_maxburst) { + reg = dwc3_readl(dwc, DWC3_GTXTHRCFG); + reg |= DWC31_GTXTHRCFG_PKTCNTSEL; + + reg &= ~DWC31_GTXTHRCFG_TXPKTCNT(~0); + reg |= DWC31_GTXTHRCFG_TXPKTCNT(tx_thr_num); + + reg &= ~DWC31_GTXTHRCFG_MAXTXBURSTSIZE(~0); + reg |= DWC31_GTXTHRCFG_MAXTXBURSTSIZE(tx_maxburst); + + dwc3_writel(dwc, DWC3_GTXTHRCFG, reg); + } + } +} + +/** + * dwc3_core_init - Low-level initialization of DWC3 Core + * @dwc: Pointer to our controller context structure + * + * Returns 0 on success otherwise negative errno. + */ +int dwc3_core_init(struct dwc3 *dwc) +{ + unsigned int hw_mode; + u32 reg; + int ret; + + hw_mode = DWC3_GHWPARAMS0_MODE(dwc->hwparams.hwparams0); + + ret = dwc3_phy_setup(dwc); + if (ret) + return ret; + + if (!dwc->ulpi_ready) { + ret = dwc3_core_ulpi_init(dwc); + if (ret) { + if (ret == -ETIMEDOUT) { + dwc3_core_soft_reset(dwc); + ret = -EPROBE_DEFER; + } + return ret; + } + dwc->ulpi_ready = true; + } + + dwc3_ulpi_setup(dwc); + + if (!dwc->phys_ready) { + ret = dwc3_core_get_phy(dwc); + if (ret) + goto err_exit_ulpi; + dwc->phys_ready = true; + } + + ret = dwc3_phy_init(dwc); + if (ret) + goto err_exit_ulpi; + + ret = dwc3_core_soft_reset(dwc); + if (ret) + goto err_exit_phy; + + /* + * Write Linux Version Code to our GUID register so it's easy to figure + * out which kernel version a bug was found. + */ + dwc3_writel(dwc, DWC3_GUID, LINUX_VERSION_CODE); + + dwc3_core_setup_global_control(dwc); + dwc3_core_num_eps(dwc); + + /* Set power down scale of suspend_clk */ + dwc3_set_power_down_clk_scale(dwc); + + /* Adjust Frame Length */ + dwc3_frame_length_adjustment(dwc); + + /* Adjust Reference Clock Period */ + dwc3_ref_clk_period(dwc); + + dwc3_set_incr_burst_type(dwc); + + dwc3_config_soc_bus(dwc); + + ret = dwc3_phy_power_on(dwc); + if (ret) + goto err_exit_phy; + + ret = dwc3_event_buffers_setup(dwc); + if (ret) { + dev_err(dwc->dev, "failed to setup event buffers\n"); + goto err_power_off_phy; + } + + /* + * ENDXFER polling is available on version 3.10a and later of + * the DWC_usb3 controller. It is NOT available in the + * DWC_usb31 controller. + */ + if (DWC3_VER_IS_WITHIN(DWC3, 310A, ANY)) { + reg = dwc3_readl(dwc, DWC3_GUCTL2); + reg |= DWC3_GUCTL2_RST_ACTBITLATER; + dwc3_writel(dwc, DWC3_GUCTL2, reg); + } + + /* + * STAR 9001285599: This issue affects DWC_usb3 version 3.20a + * only. If the PM TIMER ECM is enabled through GUCTL2[19], the + * link compliance test (TD7.21) may fail. If the ECN is not + * enabled (GUCTL2[19] = 0), the controller will use the old timer + * value (5us), which is still acceptable for the link compliance + * test. Therefore, do not enable PM TIMER ECM in 3.20a by + * setting GUCTL2[19] by default; instead, use GUCTL2[19] = 0. + */ + if (DWC3_VER_IS(DWC3, 320A)) { + reg = dwc3_readl(dwc, DWC3_GUCTL2); + reg &= ~DWC3_GUCTL2_LC_TIMER; + dwc3_writel(dwc, DWC3_GUCTL2, reg); + } + + /* + * When configured in HOST mode, after issuing U3/L2 exit controller + * fails to send proper CRC checksum in CRC5 field. Because of this + * behaviour Transaction Error is generated, resulting in reset and + * re-enumeration of usb device attached. All the termsel, xcvrsel, + * opmode becomes 0 during end of resume. Enabling bit 10 of GUCTL1 + * will correct this problem. This option is to support certain + * legacy ULPI PHYs. + */ + if (dwc->resume_hs_terminations) { + reg = dwc3_readl(dwc, DWC3_GUCTL1); + reg |= DWC3_GUCTL1_RESUME_OPMODE_HS_HOST; + dwc3_writel(dwc, DWC3_GUCTL1, reg); + } + + if (!DWC3_VER_IS_PRIOR(DWC3, 250A)) { + reg = dwc3_readl(dwc, DWC3_GUCTL1); + + /* + * Enable hardware control of sending remote wakeup + * in HS when the device is in the L1 state. + */ + if (!DWC3_VER_IS_PRIOR(DWC3, 290A)) + reg |= DWC3_GUCTL1_DEV_L1_EXIT_BY_HW; + + /* + * Decouple USB 2.0 L1 & L2 events which will allow for + * gadget driver to only receive U3/L2 suspend & wakeup + * events and prevent the more frequent L1 LPM transitions + * from interrupting the driver. + */ + if (!DWC3_VER_IS_PRIOR(DWC3, 300A)) + reg |= DWC3_GUCTL1_DEV_DECOUPLE_L1L2_EVT; + + if (dwc->dis_tx_ipgap_linecheck_quirk) + reg |= DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS; + + if (dwc->parkmode_disable_ss_quirk) + reg |= DWC3_GUCTL1_PARKMODE_DISABLE_SS; + + if (dwc->parkmode_disable_hs_quirk) + reg |= DWC3_GUCTL1_PARKMODE_DISABLE_HS; + + if (DWC3_VER_IS_WITHIN(DWC3, 290A, ANY)) { + if (dwc->maximum_speed == USB_SPEED_FULL || + dwc->maximum_speed == USB_SPEED_HIGH) + reg |= DWC3_GUCTL1_DEV_FORCE_20_CLK_FOR_30_CLK; + else + reg &= ~DWC3_GUCTL1_DEV_FORCE_20_CLK_FOR_30_CLK; + } + + dwc3_writel(dwc, DWC3_GUCTL1, reg); + } + + dwc3_config_threshold(dwc); + + if (hw_mode != DWC3_GHWPARAMS0_MODE_GADGET && + (DWC3_IP_IS(DWC31)) && + dwc->maximum_speed == USB_SPEED_SUPER) { + int i; + + for (i = 0; i < dwc->num_usb3_ports; i++) { + reg = dwc3_readl(dwc, DWC3_LLUCTL(i)); + reg |= DWC3_LLUCTL_FORCE_GEN1; + dwc3_writel(dwc, DWC3_LLUCTL(i), reg); + } + } + + /* + * STAR 9001346572: This issue affects DWC_usb31 versions 1.80a and + * prior. When an active endpoint not currently cached in the host + * controller is chosen to be cached to the same index as an endpoint + * receiving NAKs, the endpoint receiving NAKs enters continuous + * retry mode. This prevents it from being evicted from the host + * controller cache, blocking the new endpoint from being cached and + * serviced. + * + * To resolve this, for controller versions 1.70a and 1.80a, set the + * GUCTL3 bit[16] (USB2.0 Internal Retry Disable) to 1. This bit + * disables the USB2.0 internal retry feature. The GUCTL3[16] register + * function is available only from version 1.70a. + */ + if (DWC3_VER_IS_WITHIN(DWC31, 170A, 180A)) { + reg = dwc3_readl(dwc, DWC3_GUCTL3); + reg |= DWC3_GUCTL3_USB20_RETRY_DISABLE; + dwc3_writel(dwc, DWC3_GUCTL3, reg); + } + + return 0; + +err_power_off_phy: + dwc3_phy_power_off(dwc); +err_exit_phy: + dwc3_phy_exit(dwc); +err_exit_ulpi: + dwc3_ulpi_exit(dwc); + + return ret; +} +EXPORT_SYMBOL_GPL(dwc3_core_init); + +static int dwc3_core_get_phy(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + struct device_node *node = dev->of_node; + char phy_name[9]; + int ret; + u8 i; + + 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 (ret == -ENXIO || ret == -ENODEV) + dwc->usb2_phy = NULL; + else + return dev_err_probe(dev, ret, "no usb2 phy configured\n"); + } + + if (IS_ERR(dwc->usb3_phy)) { + ret = PTR_ERR(dwc->usb3_phy); + if (ret == -ENXIO || ret == -ENODEV) + dwc->usb3_phy = NULL; + else + return dev_err_probe(dev, ret, "no usb3 phy configured\n"); + } + + for (i = 0; i < dwc->num_usb2_ports; i++) { + if (dwc->num_usb2_ports == 1) + snprintf(phy_name, sizeof(phy_name), "usb2-phy"); + else + snprintf(phy_name, sizeof(phy_name), "usb2-%u", i); + + dwc->usb2_generic_phy[i] = devm_phy_get(dev, phy_name); + if (IS_ERR(dwc->usb2_generic_phy[i])) { + ret = PTR_ERR(dwc->usb2_generic_phy[i]); + if (ret == -ENOSYS || ret == -ENODEV) + dwc->usb2_generic_phy[i] = NULL; + else + return dev_err_probe(dev, ret, "failed to lookup phy %s\n", + phy_name); + } + } + + for (i = 0; i < dwc->num_usb3_ports; i++) { + if (dwc->num_usb3_ports == 1) + snprintf(phy_name, sizeof(phy_name), "usb3-phy"); + else + snprintf(phy_name, sizeof(phy_name), "usb3-%u", i); + + dwc->usb3_generic_phy[i] = devm_phy_get(dev, phy_name); + if (IS_ERR(dwc->usb3_generic_phy[i])) { + ret = PTR_ERR(dwc->usb3_generic_phy[i]); + if (ret == -ENOSYS || ret == -ENODEV) + dwc->usb3_generic_phy[i] = NULL; + else + return dev_err_probe(dev, ret, "failed to lookup phy %s\n", + phy_name); + } + } + + return 0; +} + +static int dwc3_core_init_mode(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + int ret; + int i; + + switch (dwc->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE, false); + + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, false); + phy_set_mode(dwc->usb2_generic_phy[0], PHY_MODE_USB_DEVICE); + phy_set_mode(dwc->usb3_generic_phy[0], PHY_MODE_USB_DEVICE); + + ret = dwc3_gadget_init(dwc); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize gadget\n"); + break; + case USB_DR_MODE_HOST: + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST, false); + + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, true); + for (i = 0; i < dwc->num_usb2_ports; i++) + phy_set_mode(dwc->usb2_generic_phy[i], PHY_MODE_USB_HOST); + for (i = 0; i < dwc->num_usb3_ports; i++) + phy_set_mode(dwc->usb3_generic_phy[i], PHY_MODE_USB_HOST); + + ret = dwc3_host_init(dwc); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize host\n"); + break; + case USB_DR_MODE_OTG: + INIT_WORK(&dwc->drd_work, __dwc3_set_mode); + ret = dwc3_drd_init(dwc); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize dual-role\n"); + break; + default: + dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode); + return -EINVAL; + } + + return 0; +} + +static void dwc3_core_exit_mode(struct dwc3 *dwc) +{ + switch (dwc->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + dwc3_gadget_exit(dwc); + break; + case USB_DR_MODE_HOST: + dwc3_host_exit(dwc); + break; + case USB_DR_MODE_OTG: + dwc3_drd_exit(dwc); + break; + default: + /* do nothing */ + break; + } + + /* de-assert DRVVBUS for HOST and OTG mode */ + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE, true); +} + +static void dwc3_get_software_properties(struct dwc3 *dwc, + const struct dwc3_properties *properties) +{ + struct device *tmpdev; + u16 gsbuscfg0_reqinfo; + int ret; + + if (properties->needs_full_reinit) + dwc->needs_full_reinit = true; + + dwc->gsbuscfg0_reqinfo = DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED; + + if (properties->gsbuscfg0_reqinfo != + DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED) { + dwc->gsbuscfg0_reqinfo = properties->gsbuscfg0_reqinfo; + return; + } + + /* + * Iterate over all parent nodes for finding swnode properties + * and non-DT (non-ABI) properties. + */ + for (tmpdev = dwc->dev; tmpdev; tmpdev = tmpdev->parent) { + ret = device_property_read_u16(tmpdev, + "snps,gsbuscfg0-reqinfo", + &gsbuscfg0_reqinfo); + if (!ret) + dwc->gsbuscfg0_reqinfo = gsbuscfg0_reqinfo; + } +} + +static void dwc3_get_properties(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + u8 lpm_nyet_threshold; + u8 tx_de_emphasis; + u8 hird_threshold; + u8 rx_thr_num_pkt = 0; + u8 rx_max_burst = 0; + u8 tx_thr_num_pkt = 0; + u8 tx_max_burst = 0; + u8 rx_thr_num_pkt_prd = 0; + u8 rx_max_burst_prd = 0; + u8 tx_thr_num_pkt_prd = 0; + u8 tx_max_burst_prd = 0; + u8 tx_fifo_resize_max_num; + u16 num_hc_interrupters; + + /* default to highest possible threshold */ + lpm_nyet_threshold = 0xf; + + /* default to -3.5dB de-emphasis */ + tx_de_emphasis = 1; + + /* + * default to assert utmi_sleep_n and use maximum allowed HIRD + * threshold value of 0b1100 + */ + hird_threshold = 12; + + /* + * default to a TXFIFO size large enough to fit 6 max packets. This + * allows for systems with larger bus latencies to have some headroom + * for endpoints that have a large bMaxBurst value. + */ + tx_fifo_resize_max_num = 6; + + /* default to a single XHCI interrupter */ + num_hc_interrupters = 1; + + dwc->maximum_speed = usb_get_maximum_speed(dev); + dwc->max_ssp_rate = usb_get_maximum_ssp_rate(dev); + dwc->dr_mode = usb_get_dr_mode(dev); + dwc->hsphy_mode = of_usb_get_phy_mode(dev->of_node); + + dwc->sysdev_is_parent = device_property_read_bool(dev, + "linux,sysdev_is_parent"); + if (dwc->sysdev_is_parent) + dwc->sysdev = dwc->dev->parent; + else + dwc->sysdev = dwc->dev; + + dwc->sys_wakeup = device_may_wakeup(dwc->sysdev); + + dwc->has_lpm_erratum = device_property_read_bool(dev, + "snps,has-lpm-erratum"); + device_property_read_u8(dev, "snps,lpm-nyet-threshold", + &lpm_nyet_threshold); + dwc->is_utmi_l1_suspend = device_property_read_bool(dev, + "snps,is-utmi-l1-suspend"); + device_property_read_u8(dev, "snps,hird-threshold", + &hird_threshold); + dwc->dis_start_transfer_quirk = device_property_read_bool(dev, + "snps,dis-start-transfer-quirk"); + dwc->usb3_lpm_capable = device_property_read_bool(dev, + "snps,usb3_lpm_capable"); + dwc->usb2_lpm_disable = device_property_read_bool(dev, + "snps,usb2-lpm-disable"); + dwc->usb2_gadget_lpm_disable = device_property_read_bool(dev, + "snps,usb2-gadget-lpm-disable"); + device_property_read_u8(dev, "snps,rx-thr-num-pkt", + &rx_thr_num_pkt); + device_property_read_u8(dev, "snps,rx-max-burst", + &rx_max_burst); + device_property_read_u8(dev, "snps,tx-thr-num-pkt", + &tx_thr_num_pkt); + device_property_read_u8(dev, "snps,tx-max-burst", + &tx_max_burst); + device_property_read_u8(dev, "snps,rx-thr-num-pkt-prd", + &rx_thr_num_pkt_prd); + device_property_read_u8(dev, "snps,rx-max-burst-prd", + &rx_max_burst_prd); + device_property_read_u8(dev, "snps,tx-thr-num-pkt-prd", + &tx_thr_num_pkt_prd); + device_property_read_u8(dev, "snps,tx-max-burst-prd", + &tx_max_burst_prd); + device_property_read_u16(dev, "num-hc-interrupters", + &num_hc_interrupters); + /* DWC3 core allowed to have a max of 8 interrupters */ + if (num_hc_interrupters > 8) + num_hc_interrupters = 8; + + dwc->do_fifo_resize = device_property_read_bool(dev, + "tx-fifo-resize"); + if (dwc->do_fifo_resize) + device_property_read_u8(dev, "tx-fifo-max-num", + &tx_fifo_resize_max_num); + + dwc->disable_scramble_quirk = device_property_read_bool(dev, + "snps,disable_scramble_quirk"); + dwc->u2exit_lfps_quirk = device_property_read_bool(dev, + "snps,u2exit_lfps_quirk"); + dwc->u2ss_inp3_quirk = device_property_read_bool(dev, + "snps,u2ss_inp3_quirk"); + dwc->req_p1p2p3_quirk = device_property_read_bool(dev, + "snps,req_p1p2p3_quirk"); + dwc->del_p1p2p3_quirk = device_property_read_bool(dev, + "snps,del_p1p2p3_quirk"); + dwc->del_phy_power_chg_quirk = device_property_read_bool(dev, + "snps,del_phy_power_chg_quirk"); + dwc->lfps_filter_quirk = device_property_read_bool(dev, + "snps,lfps_filter_quirk"); + dwc->rx_detect_poll_quirk = device_property_read_bool(dev, + "snps,rx_detect_poll_quirk"); + dwc->dis_u3_susphy_quirk = device_property_read_bool(dev, + "snps,dis_u3_susphy_quirk"); + dwc->dis_u2_susphy_quirk = device_property_read_bool(dev, + "snps,dis_u2_susphy_quirk"); + dwc->dis_enblslpm_quirk = device_property_read_bool(dev, + "snps,dis_enblslpm_quirk"); + dwc->dis_u1_entry_quirk = device_property_read_bool(dev, + "snps,dis-u1-entry-quirk"); + dwc->dis_u2_entry_quirk = device_property_read_bool(dev, + "snps,dis-u2-entry-quirk"); + dwc->dis_rxdet_inp3_quirk = device_property_read_bool(dev, + "snps,dis_rxdet_inp3_quirk"); + dwc->dis_u2_freeclk_exists_quirk = device_property_read_bool(dev, + "snps,dis-u2-freeclk-exists-quirk"); + dwc->dis_del_phy_power_chg_quirk = device_property_read_bool(dev, + "snps,dis-del-phy-power-chg-quirk"); + dwc->dis_tx_ipgap_linecheck_quirk = device_property_read_bool(dev, + "snps,dis-tx-ipgap-linecheck-quirk"); + dwc->resume_hs_terminations = device_property_read_bool(dev, + "snps,resume-hs-terminations"); + dwc->ulpi_ext_vbus_drv = device_property_read_bool(dev, + "snps,ulpi-ext-vbus-drv"); + dwc->parkmode_disable_ss_quirk = device_property_read_bool(dev, + "snps,parkmode-disable-ss-quirk"); + dwc->parkmode_disable_hs_quirk = device_property_read_bool(dev, + "snps,parkmode-disable-hs-quirk"); + dwc->gfladj_refclk_lpm_sel = device_property_read_bool(dev, + "snps,gfladj-refclk-lpm-sel-quirk"); + + dwc->tx_de_emphasis_quirk = device_property_read_bool(dev, + "snps,tx_de_emphasis_quirk"); + device_property_read_u8(dev, "snps,tx_de_emphasis", + &tx_de_emphasis); + device_property_read_string(dev, "snps,hsphy_interface", + &dwc->hsphy_interface); + device_property_read_u32(dev, "snps,quirk-frame-length-adjustment", + &dwc->fladj); + device_property_read_u32(dev, "snps,ref-clock-period-ns", + &dwc->ref_clk_per); + + dwc->dis_metastability_quirk = device_property_read_bool(dev, + "snps,dis_metastability_quirk"); + + dwc->dis_split_quirk = device_property_read_bool(dev, + "snps,dis-split-quirk"); + + dwc->lpm_nyet_threshold = lpm_nyet_threshold; + dwc->tx_de_emphasis = tx_de_emphasis; + + dwc->hird_threshold = hird_threshold; + + dwc->rx_thr_num_pkt = rx_thr_num_pkt; + dwc->rx_max_burst = rx_max_burst; + + dwc->tx_thr_num_pkt = tx_thr_num_pkt; + dwc->tx_max_burst = tx_max_burst; + + dwc->rx_thr_num_pkt_prd = rx_thr_num_pkt_prd; + dwc->rx_max_burst_prd = rx_max_burst_prd; + + dwc->tx_thr_num_pkt_prd = tx_thr_num_pkt_prd; + dwc->tx_max_burst_prd = tx_max_burst_prd; + + dwc->tx_fifo_resize_max_num = tx_fifo_resize_max_num; + + dwc->num_hc_interrupters = num_hc_interrupters; +} + +/* check whether the core supports IMOD */ +bool dwc3_has_imod(struct dwc3 *dwc) +{ + return DWC3_VER_IS_WITHIN(DWC3, 300A, ANY) || + DWC3_VER_IS_WITHIN(DWC31, 120A, ANY) || + DWC3_IP_IS(DWC32); +} + +static void dwc3_check_params(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + unsigned int hwparam_gen = + DWC3_GHWPARAMS3_SSPHY_IFC(dwc->hwparams.hwparams3); + + /* + * Enable IMOD for all supporting controllers. + * + * Particularly, DWC_usb3 v3.00a must enable this feature for + * the following reason: + * + * Workaround for STAR 9000961433 which affects only version + * 3.00a of the DWC_usb3 core. This prevents the controller + * interrupt from being masked while handling events. IMOD + * allows us to work around this issue. Enable it for the + * affected version. + */ + if (dwc3_has_imod((dwc))) + dwc->imod_interval = 1; + + /* Check the maximum_speed parameter */ + switch (dwc->maximum_speed) { + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + break; + case USB_SPEED_SUPER: + if (hwparam_gen == DWC3_GHWPARAMS3_SSPHY_IFC_DIS) + dev_warn(dev, "UDC doesn't support Gen 1\n"); + break; + case USB_SPEED_SUPER_PLUS: + if ((DWC3_IP_IS(DWC32) && + hwparam_gen == DWC3_GHWPARAMS3_SSPHY_IFC_DIS) || + (!DWC3_IP_IS(DWC32) && + hwparam_gen != DWC3_GHWPARAMS3_SSPHY_IFC_GEN2)) + dev_warn(dev, "UDC doesn't support SSP\n"); + break; + default: + dev_err(dev, "invalid maximum_speed parameter %d\n", + dwc->maximum_speed); + fallthrough; + case USB_SPEED_UNKNOWN: + switch (hwparam_gen) { + case DWC3_GHWPARAMS3_SSPHY_IFC_GEN2: + dwc->maximum_speed = USB_SPEED_SUPER_PLUS; + break; + case DWC3_GHWPARAMS3_SSPHY_IFC_GEN1: + if (DWC3_IP_IS(DWC32)) + dwc->maximum_speed = USB_SPEED_SUPER_PLUS; + else + dwc->maximum_speed = USB_SPEED_SUPER; + break; + case DWC3_GHWPARAMS3_SSPHY_IFC_DIS: + dwc->maximum_speed = USB_SPEED_HIGH; + break; + default: + dwc->maximum_speed = USB_SPEED_SUPER; + break; + } + break; + } + + /* + * Currently the controller does not have visibility into the HW + * parameter to determine the maximum number of lanes the HW supports. + * If the number of lanes is not specified in the device property, then + * set the default to support dual-lane for DWC_usb32 and single-lane + * for DWC_usb31 for super-speed-plus. + */ + if (dwc->maximum_speed == USB_SPEED_SUPER_PLUS) { + switch (dwc->max_ssp_rate) { + case USB_SSP_GEN_2x1: + if (hwparam_gen == DWC3_GHWPARAMS3_SSPHY_IFC_GEN1) + dev_warn(dev, "UDC only supports Gen 1\n"); + break; + case USB_SSP_GEN_1x2: + case USB_SSP_GEN_2x2: + if (DWC3_IP_IS(DWC31)) + dev_warn(dev, "UDC only supports single lane\n"); + break; + case USB_SSP_GEN_UNKNOWN: + default: + switch (hwparam_gen) { + case DWC3_GHWPARAMS3_SSPHY_IFC_GEN2: + if (DWC3_IP_IS(DWC32)) + dwc->max_ssp_rate = USB_SSP_GEN_2x2; + else + dwc->max_ssp_rate = USB_SSP_GEN_2x1; + break; + case DWC3_GHWPARAMS3_SSPHY_IFC_GEN1: + if (DWC3_IP_IS(DWC32)) + dwc->max_ssp_rate = USB_SSP_GEN_1x2; + break; + } + break; + } + } +} + +static struct extcon_dev *dwc3_get_extcon(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + struct device_node *np_phy; + struct extcon_dev *edev = NULL; + const char *name; + + if (device_property_present(dev, "extcon")) + return extcon_get_edev_by_phandle(dev, 0); + + /* + * Device tree platforms should get extcon via phandle. + * On ACPI platforms, we get the name from a device property. + * This device property is for kernel internal use only and + * is expected to be set by the glue code. + */ + if (device_property_read_string(dev, "linux,extcon-name", &name) == 0) + return extcon_get_extcon_dev(name); + + /* + * Check explicitly if "usb-role-switch" is used since + * extcon_find_edev_by_node() can not be used to check the absence of + * an extcon device. In the absence of an device it will always return + * EPROBE_DEFER. + */ + if (IS_ENABLED(CONFIG_USB_ROLE_SWITCH) && + device_property_read_bool(dev, "usb-role-switch")) + return NULL; + + /* + * Try to get an extcon device from the USB PHY controller's "port" + * node. Check if it has the "port" node first, to avoid printing the + * error message from underlying code, as it's a valid case: extcon + * device (and "port" node) may be missing in case of "usb-role-switch" + * or OTG mode. + */ + np_phy = of_parse_phandle(dev->of_node, "phys", 0); + if (of_graph_is_present(np_phy)) { + struct device_node *np_conn; + + np_conn = of_graph_get_remote_node(np_phy, -1, -1); + if (np_conn) + edev = extcon_find_edev_by_node(np_conn); + of_node_put(np_conn); + } + of_node_put(np_phy); + + return edev; +} + +static int dwc3_get_clocks(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + + if (!dev->of_node) + return 0; + + /* + * Clocks are optional, but new DT platforms should support all clocks + * as required by the DT-binding. + * Some devices have different clock names in legacy device trees, + * check for them to retain backwards compatibility. + */ + dwc->bus_clk = devm_clk_get_optional(dev, "bus_early"); + if (IS_ERR(dwc->bus_clk)) { + return dev_err_probe(dev, PTR_ERR(dwc->bus_clk), + "could not get bus clock\n"); + } + + if (dwc->bus_clk == NULL) { + dwc->bus_clk = devm_clk_get_optional(dev, "bus_clk"); + if (IS_ERR(dwc->bus_clk)) { + return dev_err_probe(dev, PTR_ERR(dwc->bus_clk), + "could not get bus clock\n"); + } + } + + dwc->ref_clk = devm_clk_get_optional(dev, "ref"); + if (IS_ERR(dwc->ref_clk)) { + return dev_err_probe(dev, PTR_ERR(dwc->ref_clk), + "could not get ref clock\n"); + } + + if (dwc->ref_clk == NULL) { + dwc->ref_clk = devm_clk_get_optional(dev, "ref_clk"); + if (IS_ERR(dwc->ref_clk)) { + return dev_err_probe(dev, PTR_ERR(dwc->ref_clk), + "could not get ref clock\n"); + } + } + + dwc->susp_clk = devm_clk_get_optional(dev, "suspend"); + if (IS_ERR(dwc->susp_clk)) { + return dev_err_probe(dev, PTR_ERR(dwc->susp_clk), + "could not get suspend clock\n"); + } + + if (dwc->susp_clk == NULL) { + dwc->susp_clk = devm_clk_get_optional(dev, "suspend_clk"); + if (IS_ERR(dwc->susp_clk)) { + return dev_err_probe(dev, PTR_ERR(dwc->susp_clk), + "could not get suspend clock\n"); + } + } + + /* specific to Rockchip RK3588 */ + dwc->utmi_clk = devm_clk_get_optional(dev, "utmi"); + if (IS_ERR(dwc->utmi_clk)) { + return dev_err_probe(dev, PTR_ERR(dwc->utmi_clk), + "could not get utmi clock\n"); + } + + /* specific to Rockchip RK3588 */ + dwc->pipe_clk = devm_clk_get_optional(dev, "pipe"); + if (IS_ERR(dwc->pipe_clk)) { + return dev_err_probe(dev, PTR_ERR(dwc->pipe_clk), + "could not get pipe clock\n"); + } + + return 0; +} + +static int dwc3_get_num_ports(struct dwc3 *dwc) +{ + void __iomem *base; + u8 major_revision; + u32 offset; + u32 val; + + /* + * Remap xHCI address space to access XHCI ext cap regs since it is + * needed to get information on number of ports present. + */ + base = ioremap(dwc->xhci_resources[0].start, + resource_size(&dwc->xhci_resources[0])); + if (!base) + return -ENOMEM; + + offset = 0; + do { + offset = xhci_find_next_ext_cap(base, offset, + XHCI_EXT_CAPS_PROTOCOL); + if (!offset) + break; + + val = readl(base + offset); + major_revision = XHCI_EXT_PORT_MAJOR(val); + + val = readl(base + offset + 0x08); + if (major_revision == 0x03) { + dwc->num_usb3_ports += XHCI_EXT_PORT_COUNT(val); + } else if (major_revision <= 0x02) { + dwc->num_usb2_ports += XHCI_EXT_PORT_COUNT(val); + } else { + dev_warn(dwc->dev, "unrecognized port major revision %d\n", + major_revision); + } + } while (1); + + dev_dbg(dwc->dev, "hs-ports: %u ss-ports: %u\n", + dwc->num_usb2_ports, dwc->num_usb3_ports); + + iounmap(base); + + if (dwc->num_usb2_ports > DWC3_USB2_MAX_PORTS || + dwc->num_usb3_ports > DWC3_USB3_MAX_PORTS) + return -EINVAL; + + return 0; +} + +static void dwc3_vbus_draw_work(struct work_struct *work) +{ + struct dwc3 *dwc = container_of(work, struct dwc3, vbus_draw_work); + union power_supply_propval val = {0}; + int ret; + + val.intval = 1000 * (dwc->current_limit); + ret = power_supply_set_property(dwc->usb_psy, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val); + + if (ret < 0) + dev_dbg(dwc->dev, "Error (%d) setting vbus draw (%d mA)\n", + ret, dwc->current_limit); +} + +static int dwc3_psy_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct dwc3 *dwc = container_of(nb, struct dwc3, psy_nb); + struct power_supply *psy = data; + unsigned long flags; + + if (dwc->usb_psy) + return NOTIFY_DONE; + + if (strcmp(psy->desc->name, dwc->usb_psy_name) != 0) + return NOTIFY_DONE; + + /* Explicitly get the reference for this psy */ + psy = power_supply_get_by_name(dwc->usb_psy_name); + if (!psy) + return NOTIFY_DONE; + + spin_lock_irqsave(&dwc->lock, flags); + /* + * The USB power_supply may already be set. This can happen if notifier + * callbacks for the USB power_supply race, or if a previous notifier + * callback has already successfully fetched and associated the instance. + * In such cases, release the newly acquired reference and ignore + * subsequent notifications until the notifier is unregistered. + */ + if (dwc->usb_psy) { + spin_unlock_irqrestore(&dwc->lock, flags); + power_supply_put(psy); + return NOTIFY_DONE; + } + + dwc->usb_psy = psy; + if (dwc->current_limit != DWC3_CURRENT_UNSPECIFIED) + schedule_work(&dwc->vbus_draw_work); + spin_unlock_irqrestore(&dwc->lock, flags); + + return NOTIFY_OK; +} + +static void dwc3_get_usb_power_supply(struct dwc3 *dwc) +{ + struct power_supply *psy; + unsigned long flags; + int ret; + + ret = device_property_read_string(dwc->dev, "usb-psy-name", &dwc->usb_psy_name); + if (ret < 0) + return; + + INIT_WORK(&dwc->vbus_draw_work, dwc3_vbus_draw_work); + + dwc->current_limit = DWC3_CURRENT_UNSPECIFIED; + dwc->psy_nb.notifier_call = dwc3_psy_notifier; + ret = power_supply_reg_notifier(&dwc->psy_nb); + if (ret) { + dev_err(dwc->dev, "Failed to register power supply notifier: %d\n", ret); + dwc->psy_nb.notifier_call = NULL; + return; + } + + psy = power_supply_get_by_name(dwc->usb_psy_name); + if (!psy) + return; + + /* Unregister the notifier now that we have the power supply */ + power_supply_unreg_notifier(&dwc->psy_nb); + dwc->psy_nb.notifier_call = NULL; + + spin_lock_irqsave(&dwc->lock, flags); + /* + * It is possible that the notifier callback ran before we reached here + * and successfully fetched the power supply. In that case we need to + * release the above reference. + */ + if (dwc->usb_psy) { + spin_unlock_irqrestore(&dwc->lock, flags); + power_supply_put(psy); + return; + } + + dwc->usb_psy = psy; + spin_unlock_irqrestore(&dwc->lock, flags); +} + +int dwc3_core_probe(const struct dwc3_probe_data *data) +{ + struct dwc3 *dwc = data->dwc; + struct device *dev = dwc->dev; + struct resource dwc_res; + unsigned int hw_mode; + void __iomem *regs; + struct resource *res = data->res; + int ret; + + 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. + */ + dwc_res = *res; + dwc_res.start += DWC3_GLOBALS_REGS_START; + + if (dev->of_node) { + struct device_node *parent = of_get_parent(dev->of_node); + + if (of_device_is_compatible(parent, "realtek,rtd-dwc3")) { + dwc_res.start -= DWC3_GLOBALS_REGS_START; + dwc_res.start += DWC3_RTK_RTD_GLOBALS_REGS_START; + } + + of_node_put(parent); + } + + regs = devm_ioremap_resource(dev, &dwc_res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + dwc->regs = regs; + dwc->regs_size = resource_size(&dwc_res); + + dwc3_get_properties(dwc); + + dwc3_get_software_properties(dwc, &data->properties); + + spin_lock_init(&dwc->lock); + + dwc3_get_usb_power_supply(dwc); + + if (!data->ignore_clocks_and_resets) { + dwc->reset = devm_reset_control_array_get_optional_shared(dev); + if (IS_ERR(dwc->reset)) { + ret = PTR_ERR(dwc->reset); + goto err_put_psy; + } + + ret = dwc3_get_clocks(dwc); + if (ret) + goto err_put_psy; + } + + ret = reset_control_deassert(dwc->reset); + if (ret) + goto err_put_psy; + + ret = dwc3_clk_enable(dwc); + if (ret) + goto err_assert_reset; + + if (!dwc3_core_is_valid(dwc)) { + dev_err(dwc->dev, "this is not a DesignWare USB3 DRD Core\n"); + ret = -ENODEV; + goto err_disable_clks; + } + + dev_set_drvdata(dev, dwc); + dwc3_cache_hwparams(dwc); + + if (!dev_is_pci(dwc->sysdev) && + DWC3_GHWPARAMS0_AWIDTH(dwc->hwparams.hwparams0) == 64) { + ret = dma_set_mask_and_coherent(dwc->sysdev, DMA_BIT_MASK(64)); + if (ret) + goto err_disable_clks; + } + + /* + * Currently only DWC3 controllers that are host-only capable + * can have more than one port. + */ + hw_mode = DWC3_GHWPARAMS0_MODE(dwc->hwparams.hwparams0); + if (hw_mode == DWC3_GHWPARAMS0_MODE_HOST) { + ret = dwc3_get_num_ports(dwc); + if (ret) + goto err_disable_clks; + } else { + dwc->num_usb2_ports = 1; + dwc->num_usb3_ports = 1; + } + + mutex_init(&dwc->mutex); + + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, DWC3_DEFAULT_AUTOSUSPEND_DELAY); + pm_runtime_enable(dev); + + pm_runtime_forbid(dev); + + 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 err_allow_rpm; + } + + dwc->edev = dwc3_get_extcon(dwc); + if (IS_ERR(dwc->edev)) { + ret = dev_err_probe(dwc->dev, PTR_ERR(dwc->edev), "failed to get extcon\n"); + goto err_free_event_buffers; + } + + ret = dwc3_get_dr_mode(dwc); + if (ret) + goto err_free_event_buffers; + + ret = dwc3_core_init(dwc); + if (ret) { + dev_err_probe(dev, ret, "failed to initialize core\n"); + goto err_free_event_buffers; + } + + dwc3_check_params(dwc); + dwc3_debugfs_init(dwc); + + if (!data->skip_core_init_mode) { + ret = dwc3_core_init_mode(dwc); + if (ret) + goto err_exit_debugfs; + } + + pm_runtime_put(dev); + + dma_set_max_seg_size(dev, UINT_MAX); + + return 0; + +err_exit_debugfs: + dwc3_debugfs_exit(dwc); + dwc3_event_buffers_cleanup(dwc); + dwc3_phy_power_off(dwc); + dwc3_phy_exit(dwc); + dwc3_ulpi_exit(dwc); +err_free_event_buffers: + dwc3_free_event_buffers(dwc); +err_allow_rpm: + pm_runtime_allow(dev); + pm_runtime_disable(dev); + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); +err_disable_clks: + dwc3_clk_disable(dwc); +err_assert_reset: + reset_control_assert(dwc->reset); +err_put_psy: + if (dwc->psy_nb.notifier_call) + power_supply_unreg_notifier(&dwc->psy_nb); + if (dwc->usb_psy) + power_supply_put(dwc->usb_psy); + + return ret; +} +EXPORT_SYMBOL_GPL(dwc3_core_probe); + +static int dwc3_probe(struct platform_device *pdev) +{ + struct dwc3_probe_data probe_data = {}; + struct resource *res; + struct dwc3 *dwc; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory resource\n"); + return -ENODEV; + } + + dwc = devm_kzalloc(&pdev->dev, sizeof(*dwc), GFP_KERNEL); + if (!dwc) + return -ENOMEM; + + dwc->dev = &pdev->dev; + dwc->glue_ops = NULL; + + probe_data.dwc = dwc; + probe_data.res = res; + probe_data.properties = DWC3_DEFAULT_PROPERTIES; + + return dwc3_core_probe(&probe_data); +} + +void dwc3_core_remove(struct dwc3 *dwc) +{ + pm_runtime_get_sync(dwc->dev); + + dwc3_core_exit_mode(dwc); + dwc3_debugfs_exit(dwc); + + dwc3_core_exit(dwc); + dwc3_ulpi_exit(dwc); + + pm_runtime_allow(dwc->dev); + pm_runtime_disable(dwc->dev); + pm_runtime_dont_use_autosuspend(dwc->dev); + pm_runtime_put_noidle(dwc->dev); + /* + * HACK: Clear the driver data, which is currently accessed by parent + * glue drivers, before allowing the parent to suspend. + */ + dev_set_drvdata(dwc->dev, NULL); + pm_runtime_set_suspended(dwc->dev); + + dwc3_free_event_buffers(dwc); + + if (dwc->psy_nb.notifier_call) + power_supply_unreg_notifier(&dwc->psy_nb); + + if (dwc->usb_psy) { + cancel_work_sync(&dwc->vbus_draw_work); + power_supply_put(dwc->usb_psy); + } +} +EXPORT_SYMBOL_GPL(dwc3_core_remove); + +static void dwc3_remove(struct platform_device *pdev) +{ + dwc3_core_remove(platform_get_drvdata(pdev)); +} + +#ifdef CONFIG_PM +static int dwc3_core_init_for_resume(struct dwc3 *dwc) +{ + int ret; + + ret = reset_control_deassert(dwc->reset); + if (ret) + return ret; + + ret = dwc3_clk_enable(dwc); + if (ret) + goto assert_reset; + + ret = dwc3_core_init(dwc); + if (ret) + goto disable_clks; + + return 0; + +disable_clks: + dwc3_clk_disable(dwc); +assert_reset: + reset_control_assert(dwc->reset); + + return ret; +} + +static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg) +{ + u32 reg; + int i; + int ret; + + if (!pm_runtime_suspended(dwc->dev) && !PMSG_IS_AUTO(msg)) { + dwc->susphy_state = (dwc3_readl(dwc, DWC3_GUSB2PHYCFG(0)) & + DWC3_GUSB2PHYCFG_SUSPHY) || + (dwc3_readl(dwc, DWC3_GUSB3PIPECTL(0)) & + DWC3_GUSB3PIPECTL_SUSPHY); + /* + * TI AM62 platform requires SUSPHY to be + * enabled for system suspend to work. + */ + if (!dwc->susphy_state) + dwc3_enable_susphy(dwc, true); + } + + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_DEVICE: + if (pm_runtime_suspended(dwc->dev)) + break; + ret = dwc3_gadget_suspend(dwc); + if (ret) + return ret; + synchronize_irq(dwc->irq_gadget); + dwc3_core_exit(dwc); + break; + case DWC3_GCTL_PRTCAP_HOST: + if (!PMSG_IS_AUTO(msg) && + (!device_may_wakeup(dwc->dev) || dwc->needs_full_reinit)) { + dwc3_core_exit(dwc); + break; + } + + /* Let controller to suspend HSPHY before PHY driver suspends */ + if (dwc->dis_u2_susphy_quirk || + dwc->dis_enblslpm_quirk) { + for (i = 0; i < dwc->num_usb2_ports; i++) { + reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(i)); + reg |= DWC3_GUSB2PHYCFG_ENBLSLPM | + DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc, DWC3_GUSB2PHYCFG(i), reg); + } + + /* Give some time for USB2 PHY to suspend */ + usleep_range(5000, 6000); + } + + for (i = 0; i < dwc->num_usb2_ports; i++) + phy_pm_runtime_put_sync(dwc->usb2_generic_phy[i]); + for (i = 0; i < dwc->num_usb3_ports; i++) + phy_pm_runtime_put_sync(dwc->usb3_generic_phy[i]); + break; + case DWC3_GCTL_PRTCAP_OTG: + /* do nothing during runtime_suspend */ + if (PMSG_IS_AUTO(msg)) + break; + + if (dwc->current_otg_role == DWC3_OTG_ROLE_DEVICE) { + ret = dwc3_gadget_suspend(dwc); + if (ret) + return ret; + synchronize_irq(dwc->irq_gadget); + } + + dwc3_otg_exit(dwc); + dwc3_core_exit(dwc); + break; + default: + /* do nothing */ + break; + } + + return 0; +} + +static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg) +{ + int ret; + u32 reg; + int i; + + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_DEVICE: + ret = dwc3_core_init_for_resume(dwc); + if (ret) + return ret; + + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE, true); + dwc3_gadget_resume(dwc); + break; + case DWC3_GCTL_PRTCAP_HOST: + if (!PMSG_IS_AUTO(msg) && + (!device_may_wakeup(dwc->dev) || dwc->needs_full_reinit)) { + ret = dwc3_core_init_for_resume(dwc); + if (ret) + return ret; + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST, true); + break; + } + /* Restore GUSB2PHYCFG bits that were modified in suspend */ + for (i = 0; i < dwc->num_usb2_ports; i++) { + reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(i)); + if (dwc->dis_u2_susphy_quirk) + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + + if (dwc->dis_enblslpm_quirk) + reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM; + + dwc3_writel(dwc, DWC3_GUSB2PHYCFG(i), reg); + } + + for (i = 0; i < dwc->num_usb2_ports; i++) + phy_pm_runtime_get_sync(dwc->usb2_generic_phy[i]); + for (i = 0; i < dwc->num_usb3_ports; i++) + phy_pm_runtime_get_sync(dwc->usb3_generic_phy[i]); + break; + case DWC3_GCTL_PRTCAP_OTG: + /* nothing to do on runtime_resume */ + if (PMSG_IS_AUTO(msg)) + break; + + ret = dwc3_core_init_for_resume(dwc); + if (ret) + return ret; + + dwc3_set_prtcap(dwc, dwc->current_dr_role, true); + + dwc3_otg_init(dwc); + if (dwc->current_otg_role == DWC3_OTG_ROLE_HOST) { + dwc3_otg_host_init(dwc); + } else if (dwc->current_otg_role == DWC3_OTG_ROLE_DEVICE) { + dwc3_gadget_resume(dwc); + } + + break; + default: + /* do nothing */ + break; + } + + if (!PMSG_IS_AUTO(msg)) { + /* restore SUSPHY state to that before system suspend. */ + dwc3_enable_susphy(dwc, dwc->susphy_state); + } + + return 0; +} + +static int dwc3_runtime_checks(struct dwc3 *dwc) +{ + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_DEVICE: + if (dwc->connected) + return -EBUSY; + break; + case DWC3_GCTL_PRTCAP_HOST: + default: + /* do nothing */ + break; + } + + return 0; +} + +int dwc3_runtime_suspend(struct dwc3 *dwc) +{ + int ret; + + if (dwc3_runtime_checks(dwc)) + return -EBUSY; + + ret = dwc3_suspend_common(dwc, PMSG_AUTO_SUSPEND); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(dwc3_runtime_suspend); + +int dwc3_runtime_resume(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + int ret; + + ret = dwc3_resume_common(dwc, PMSG_AUTO_RESUME); + if (ret) + return ret; + + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_DEVICE: + if (dwc->pending_events) { + pm_runtime_put(dev); + dwc->pending_events = false; + enable_irq(dwc->irq_gadget); + } + break; + case DWC3_GCTL_PRTCAP_HOST: + default: + /* do nothing */ + break; + } + + pm_runtime_mark_last_busy(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(dwc3_runtime_resume); + +int dwc3_runtime_idle(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_DEVICE: + if (dwc3_runtime_checks(dwc)) + return -EBUSY; + break; + case DWC3_GCTL_PRTCAP_HOST: + default: + /* do nothing */ + break; + } + + pm_runtime_autosuspend(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(dwc3_runtime_idle); + +static int dwc3_plat_runtime_suspend(struct device *dev) +{ + return dwc3_runtime_suspend(dev_get_drvdata(dev)); +} + +static int dwc3_plat_runtime_resume(struct device *dev) +{ + return dwc3_runtime_resume(dev_get_drvdata(dev)); +} + +static int dwc3_plat_runtime_idle(struct device *dev) +{ + return dwc3_runtime_idle(dev_get_drvdata(dev)); +} +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_SLEEP +int dwc3_pm_suspend(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + int ret; + + ret = dwc3_suspend_common(dwc, PMSG_SUSPEND); + if (ret) + return ret; + + pinctrl_pm_select_sleep_state(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(dwc3_pm_suspend); + +int dwc3_pm_resume(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + int ret = 0; + + pinctrl_pm_select_default_state(dev); + + pm_runtime_disable(dev); + ret = pm_runtime_set_active(dev); + if (ret) + goto out; + + ret = dwc3_resume_common(dwc, PMSG_RESUME); + if (ret) + pm_runtime_set_suspended(dev); + +out: + pm_runtime_enable(dev); + + return ret; +} +EXPORT_SYMBOL_GPL(dwc3_pm_resume); + +void dwc3_pm_complete(struct dwc3 *dwc) +{ + u32 reg; + + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST && + dwc->dis_split_quirk) { + reg = dwc3_readl(dwc, DWC3_GUCTL3); + reg |= DWC3_GUCTL3_SPLITDISABLE; + dwc3_writel(dwc, DWC3_GUCTL3, reg); + } +} +EXPORT_SYMBOL_GPL(dwc3_pm_complete); + +int dwc3_pm_prepare(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + + /* + * Indicate to the PM core that it may safely leave the device in + * runtime suspend if runtime-suspended already in device mode. + */ + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE && + pm_runtime_suspended(dev) && + !dev_pinctrl(dev)) + return 1; + + return 0; +} +EXPORT_SYMBOL_GPL(dwc3_pm_prepare); + +static int dwc3_plat_suspend(struct device *dev) +{ + return dwc3_pm_suspend(dev_get_drvdata(dev)); +} + +static int dwc3_plat_resume(struct device *dev) +{ + return dwc3_pm_resume(dev_get_drvdata(dev)); +} + +static void dwc3_plat_complete(struct device *dev) +{ + dwc3_pm_complete(dev_get_drvdata(dev)); +} + +static int dwc3_plat_prepare(struct device *dev) +{ + return dwc3_pm_prepare(dev_get_drvdata(dev)); +} +#else +#define dwc3_plat_complete NULL +#define dwc3_plat_prepare NULL +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops dwc3_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_plat_suspend, dwc3_plat_resume) + .complete = dwc3_plat_complete, + .prepare = dwc3_plat_prepare, + /* + * Runtime suspend halts the controller on disconnection. It relies on + * platforms with custom connection notification to start the controller + * again. + */ + SET_RUNTIME_PM_OPS(dwc3_plat_runtime_suspend, dwc3_plat_runtime_resume, + dwc3_plat_runtime_idle) +}; + +#ifdef CONFIG_OF +static const struct of_device_id of_dwc3_match[] = { + { + .compatible = "snps,dwc3" + }, + { + .compatible = "synopsys,dwc3" + }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_dwc3_match); +#endif + +#ifdef CONFIG_ACPI + +#define ACPI_ID_INTEL_BSW "808622B7" + +static const struct acpi_device_id dwc3_acpi_match[] = { + { ACPI_ID_INTEL_BSW, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, dwc3_acpi_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), + .acpi_match_table = ACPI_PTR(dwc3_acpi_match), + .pm = &dwc3_dev_pm_ops, + }, +}; + +module_platform_driver(dwc3_driver); + +MODULE_ALIAS("platform:dwc3"); +MODULE_AUTHOR("Felipe Balbi "); +MODULE_LICENSE("GPL v2"); +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 000000000..608daeb7e --- /dev/null +++ b/drivers/usb/dwc3/core.h @@ -0,0 +1,1752 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * core.h - DesignWare USB3 DRD Core Header + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + */ + +#ifndef __DRIVERS_USB_DWC3_CORE_H +#define __DRIVERS_USB_DWC3_CORE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +/* + * DWC3 Multiport controllers support up to 15 High-Speed PHYs + * and 4 SuperSpeed PHYs. + */ +#define DWC3_USB2_MAX_PORTS 15 +#define DWC3_USB3_MAX_PORTS 4 + +#define DWC3_MSG_MAX 500 + +/* Global constants */ +#define DWC3_PULL_UP_TIMEOUT 500 /* ms */ +#define DWC3_BOUNCE_SIZE 1024 /* size of a superspeed bulk */ +#define DWC3_EP0_SETUP_SIZE 512 +#define DWC3_ENDPOINTS_NUM 32 +#define DWC3_XHCI_RESOURCES_NUM 2 +#define DWC3_ISOC_MAX_RETRIES 5 + +#define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ +#define DWC3_EVENT_BUFFERS_SIZE 4096 +#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_SUSPEND 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 + +/* Controller's role while using the OTG block */ +#define DWC3_OTG_ROLE_IDLE 0 +#define DWC3_OTG_ROLE_HOST 1 +#define DWC3_OTG_ROLE_DEVICE 2 + +#define DWC3_GEVNTCOUNT_MASK 0xfffc +#define DWC3_GEVNTCOUNT_EHB BIT(31) +#define DWC3_GSNPSID_MASK 0xffff0000 +#define DWC3_GSNPSREV_MASK 0xffff +#define DWC3_GSNPS_ID(p) (((p) & DWC3_GSNPSID_MASK) >> 16) + +/* DWC3 registers memory space boundaries */ +#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 + +#define DWC3_RTK_RTD_GLOBALS_REGS_START 0x8100 + +/* 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_GUCTL1 0xc11c +#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_GDBGBMU 0xc16c +#define DWC3_GDBGLSPMUX 0xc170 +#define DWC3_GDBGLSP 0xc174 +#define DWC3_GDBGEPINFO0 0xc178 +#define DWC3_GDBGEPINFO1 0xc17c +#define DWC3_GPRTBIMAP_HS0 0xc180 +#define DWC3_GPRTBIMAP_HS1 0xc184 +#define DWC3_GPRTBIMAP_FS0 0xc188 +#define DWC3_GPRTBIMAP_FS1 0xc18c +#define DWC3_GUCTL2 0xc19c + +#define DWC3_VER_NUMBER 0xc1a0 +#define DWC3_VER_TYPE 0xc1a4 + +#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 +#define DWC3_GUCTL3 0xc60c +#define DWC3_GFLADJ 0xc630 +#define DWC3_GHWPARAMS9 0xc6e0 + +/* 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_DCFG1 0xc740 /* DWC_usb32 only */ + +#define DWC3_DEP_BASE(n) (0xc800 + ((n) * 0x10)) +#define DWC3_DEPCMDPAR2(n) (DWC3_DEP_BASE(n) + 0x00) +#define DWC3_DEPCMDPAR1(n) (DWC3_DEP_BASE(n) + 0x04) +#define DWC3_DEPCMDPAR0(n) (DWC3_DEP_BASE(n) + 0x08) +#define DWC3_DEPCMD(n) (DWC3_DEP_BASE(n) + 0x0c) + +#define DWC3_DEV_IMOD(n) (0xca00 + ((n) * 0x4)) + +/* OTG Registers */ +#define DWC3_OCFG 0xcc00 +#define DWC3_OCTL 0xcc04 +#define DWC3_OEVT 0xcc08 +#define DWC3_OEVTEN 0xcc0C +#define DWC3_OSTS 0xcc10 + +#define DWC3_LLUCTL(n) (0xd024 + ((n) * 0x80)) + +/* Bit fields */ + +/* Global SoC Bus Configuration INCRx Register 0 */ +#define DWC3_GSBUSCFG0_INCR256BRSTENA (1 << 7) /* INCR256 burst */ +#define DWC3_GSBUSCFG0_INCR128BRSTENA (1 << 6) /* INCR128 burst */ +#define DWC3_GSBUSCFG0_INCR64BRSTENA (1 << 5) /* INCR64 burst */ +#define DWC3_GSBUSCFG0_INCR32BRSTENA (1 << 4) /* INCR32 burst */ +#define DWC3_GSBUSCFG0_INCR16BRSTENA (1 << 3) /* INCR16 burst */ +#define DWC3_GSBUSCFG0_INCR8BRSTENA (1 << 2) /* INCR8 burst */ +#define DWC3_GSBUSCFG0_INCR4BRSTENA (1 << 1) /* INCR4 burst */ +#define DWC3_GSBUSCFG0_INCRBRSTENA (1 << 0) /* undefined length enable */ +#define DWC3_GSBUSCFG0_INCRBRST_MASK 0xff + +/* Global SoC Bus Configuration Register: AHB-prot/AXI-cache/OCP-ReqInfo */ +#define DWC3_GSBUSCFG0_REQINFO(n) (((n) & 0xffff) << 16) +#define DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED 0xffffffff + +/* Global Debug LSP MUX Select */ +#define DWC3_GDBGLSPMUX_ENDBC BIT(15) /* Host only */ +#define DWC3_GDBGLSPMUX_HOSTSELECT(n) ((n) & 0x3fff) +#define DWC3_GDBGLSPMUX_DEVSELECT(n) (((n) & 0xf) << 4) +#define DWC3_GDBGLSPMUX_EPSELECT(n) ((n) & 0xf) + +/* Global Debug Queue/FIFO Space Available Register */ +#define DWC3_GDBGFIFOSPACE_NUM(n) ((n) & 0x1f) +#define DWC3_GDBGFIFOSPACE_TYPE(n) (((n) << 5) & 0x1e0) +#define DWC3_GDBGFIFOSPACE_SPACE_AVAILABLE(n) (((n) >> 16) & 0xffff) + +#define DWC3_TXFIFO 0 +#define DWC3_RXFIFO 1 +#define DWC3_TXREQQ 2 +#define DWC3_RXREQQ 3 +#define DWC3_RXINFOQ 4 +#define DWC3_PSTATQ 5 +#define DWC3_DESCFETCHQ 6 +#define DWC3_EVENTQ 7 +#define DWC3_AUXEVENTQ 8 + +/* Global RX Threshold Configuration Register */ +#define DWC3_GRXTHRCFG_MAXRXBURSTSIZE(n) (((n) & 0x1f) << 19) +#define DWC3_GRXTHRCFG_RXPKTCNT(n) (((n) & 0xf) << 24) +#define DWC3_GRXTHRCFG_PKTCNTSEL BIT(29) + +/* Global TX Threshold Configuration Register */ +#define DWC3_GTXTHRCFG_MAXTXBURSTSIZE(n) (((n) & 0xff) << 16) +#define DWC3_GTXTHRCFG_TXPKTCNT(n) (((n) & 0xf) << 24) +#define DWC3_GTXTHRCFG_PKTCNTSEL BIT(29) + +/* Global RX Threshold Configuration Register for DWC_usb31 only */ +#define DWC31_GRXTHRCFG_MAXRXBURSTSIZE(n) (((n) & 0x1f) << 16) +#define DWC31_GRXTHRCFG_RXPKTCNT(n) (((n) & 0x1f) << 21) +#define DWC31_GRXTHRCFG_PKTCNTSEL BIT(26) +#define DWC31_RXTHRNUMPKTSEL_HS_PRD BIT(15) +#define DWC31_RXTHRNUMPKT_HS_PRD(n) (((n) & 0x3) << 13) +#define DWC31_RXTHRNUMPKTSEL_PRD BIT(10) +#define DWC31_RXTHRNUMPKT_PRD(n) (((n) & 0x1f) << 5) +#define DWC31_MAXRXBURSTSIZE_PRD(n) ((n) & 0x1f) + +/* Global TX Threshold Configuration Register for DWC_usb31 only */ +#define DWC31_GTXTHRCFG_MAXTXBURSTSIZE(n) (((n) & 0x1f) << 16) +#define DWC31_GTXTHRCFG_TXPKTCNT(n) (((n) & 0x1f) << 21) +#define DWC31_GTXTHRCFG_PKTCNTSEL BIT(26) +#define DWC31_TXTHRNUMPKTSEL_HS_PRD BIT(15) +#define DWC31_TXTHRNUMPKT_HS_PRD(n) (((n) & 0x3) << 13) +#define DWC31_TXTHRNUMPKTSEL_PRD BIT(10) +#define DWC31_TXTHRNUMPKT_PRD(n) (((n) & 0x1f) << 5) +#define DWC31_MAXTXBURSTSIZE_PRD(n) ((n) & 0x1f) + +/* Global Configuration Register */ +#define DWC3_GCTL_PWRDNSCALE(n) ((n) << 19) +#define DWC3_GCTL_PWRDNSCALE_MASK GENMASK(31, 19) +#define DWC3_GCTL_U2RSTECN BIT(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 BIT(11) +#define DWC3_GCTL_SOFITPSYNC BIT(10) +#define DWC3_GCTL_SCALEDOWN(n) ((n) << 4) +#define DWC3_GCTL_SCALEDOWN_MASK DWC3_GCTL_SCALEDOWN(3) +#define DWC3_GCTL_DISSCRAMBLE BIT(3) +#define DWC3_GCTL_U2EXIT_LFPS BIT(2) +#define DWC3_GCTL_GBLHIBERNATIONEN BIT(1) +#define DWC3_GCTL_DSBLCLKGTNG BIT(0) + +/* Global User Control 1 Register */ +#define DWC3_GUCTL1_DEV_DECOUPLE_L1L2_EVT BIT(31) +#define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28) +#define DWC3_GUCTL1_DEV_FORCE_20_CLK_FOR_30_CLK BIT(26) +#define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24) +#define DWC3_GUCTL1_PARKMODE_DISABLE_SS BIT(17) +#define DWC3_GUCTL1_PARKMODE_DISABLE_HS BIT(16) +#define DWC3_GUCTL1_RESUME_OPMODE_HS_HOST BIT(10) + +/* Global Status Register */ +#define DWC3_GSTS_OTG_IP BIT(10) +#define DWC3_GSTS_BC_IP BIT(9) +#define DWC3_GSTS_ADP_IP BIT(8) +#define DWC3_GSTS_HOST_IP BIT(7) +#define DWC3_GSTS_DEVICE_IP BIT(6) +#define DWC3_GSTS_CSR_TIMEOUT BIT(5) +#define DWC3_GSTS_BUS_ERR_ADDR_VLD BIT(4) +#define DWC3_GSTS_CURMOD(n) ((n) & 0x3) +#define DWC3_GSTS_CURMOD_DEVICE 0 +#define DWC3_GSTS_CURMOD_HOST 1 + +/* Global USB2 PHY Configuration Register */ +#define DWC3_GUSB2PHYCFG_PHYSOFTRST BIT(31) +#define DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS BIT(30) +#define DWC3_GUSB2PHYCFG_ULPIEXTVBUSDRV BIT(17) +#define DWC3_GUSB2PHYCFG_SUSPHY BIT(6) +#define DWC3_GUSB2PHYCFG_ULPI_UTMI BIT(4) +#define DWC3_GUSB2PHYCFG_ENBLSLPM BIT(8) +#define DWC3_GUSB2PHYCFG_XCVRDLY BIT(9) +#define DWC3_GUSB2PHYCFG_PHYIF(n) (n << 3) +#define DWC3_GUSB2PHYCFG_PHYIF_MASK DWC3_GUSB2PHYCFG_PHYIF(1) +#define DWC3_GUSB2PHYCFG_USBTRDTIM(n) (n << 10) +#define DWC3_GUSB2PHYCFG_USBTRDTIM_MASK DWC3_GUSB2PHYCFG_USBTRDTIM(0xf) +#define USBTRDTIM_UTMI_8_BIT 9 +#define USBTRDTIM_UTMI_16_BIT 5 +#define UTMI_PHYIF_16_BIT 1 +#define UTMI_PHYIF_8_BIT 0 + +/* Global USB2 PHY Vendor Control Register */ +#define DWC3_GUSB2PHYACC_NEWREGREQ BIT(25) +#define DWC3_GUSB2PHYACC_DONE BIT(24) +#define DWC3_GUSB2PHYACC_BUSY BIT(23) +#define DWC3_GUSB2PHYACC_WRITE BIT(22) +#define DWC3_GUSB2PHYACC_ADDR(n) (n << 16) +#define DWC3_GUSB2PHYACC_EXTEND_ADDR(n) (n << 8) +#define DWC3_GUSB2PHYACC_DATA(n) (n & 0xff) + +/* Global USB3 PIPE Control Register */ +#define DWC3_GUSB3PIPECTL_PHYSOFTRST BIT(31) +#define DWC3_GUSB3PIPECTL_U2SSINP3OK BIT(29) +#define DWC3_GUSB3PIPECTL_DISRXDETINP3 BIT(28) +#define DWC3_GUSB3PIPECTL_UX_EXIT_PX BIT(27) +#define DWC3_GUSB3PIPECTL_REQP1P2P3 BIT(24) +#define DWC3_GUSB3PIPECTL_DEP1P2P3(n) ((n) << 19) +#define DWC3_GUSB3PIPECTL_DEP1P2P3_MASK DWC3_GUSB3PIPECTL_DEP1P2P3(7) +#define DWC3_GUSB3PIPECTL_DEP1P2P3_EN DWC3_GUSB3PIPECTL_DEP1P2P3(1) +#define DWC3_GUSB3PIPECTL_DEPOCHANGE BIT(18) +#define DWC3_GUSB3PIPECTL_SUSPHY BIT(17) +#define DWC3_GUSB3PIPECTL_LFPSFILT BIT(9) +#define DWC3_GUSB3PIPECTL_RX_DETOPOLL BIT(8) +#define DWC3_GUSB3PIPECTL_TX_DEEPH_MASK DWC3_GUSB3PIPECTL_TX_DEEPH(3) +#define DWC3_GUSB3PIPECTL_TX_DEEPH(n) ((n) << 1) + +/* Global TX Fifo Size Register */ +#define DWC31_GTXFIFOSIZ_TXFRAMNUM BIT(15) /* DWC_usb31 only */ +#define DWC31_GTXFIFOSIZ_TXFDEP(n) ((n) & 0x7fff) /* DWC_usb31 only */ +#define DWC3_GTXFIFOSIZ_TXFDEP(n) ((n) & 0xffff) +#define DWC3_GTXFIFOSIZ_TXFSTADDR(n) ((n) & 0xffff0000) + +/* Global RX Fifo Size Register */ +#define DWC31_GRXFIFOSIZ_RXFDEP(n) ((n) & 0x7fff) /* DWC_usb31 only */ +#define DWC3_GRXFIFOSIZ_RXFDEP(n) ((n) & 0xffff) + +/* Global Event Size Registers */ +#define DWC3_GEVNTSIZ_INTMASK BIT(31) +#define DWC3_GEVNTSIZ_SIZE(n) ((n) & 0xffff) + +/* Global HWPARAMS0 Register */ +#define DWC3_GHWPARAMS0_MODE(n) ((n) & 0x3) +#define DWC3_GHWPARAMS0_MODE_GADGET 0 +#define DWC3_GHWPARAMS0_MODE_HOST 1 +#define DWC3_GHWPARAMS0_MODE_DRD 2 +#define DWC3_GHWPARAMS0_MBUS_TYPE(n) (((n) >> 3) & 0x7) +#define DWC3_GHWPARAMS0_SBUS_TYPE(n) (((n) >> 6) & 0x3) +#define DWC3_GHWPARAMS0_MDWIDTH(n) (((n) >> 8) & 0xff) +#define DWC3_GHWPARAMS0_SDWIDTH(n) (((n) >> 16) & 0xff) +#define DWC3_GHWPARAMS0_AWIDTH(n) (((n) >> 24) & 0xff) + +/* 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) +#define DWC3_GHWPARAMS1_ENDBC BIT(31) + +/* Global HWPARAMS3 Register */ +#define DWC3_GHWPARAMS3_SSPHY_IFC(n) ((n) & 3) +#define DWC3_GHWPARAMS3_SSPHY_IFC_DIS 0 +#define DWC3_GHWPARAMS3_SSPHY_IFC_GEN1 1 +#define DWC3_GHWPARAMS3_SSPHY_IFC_GEN2 2 /* DWC_usb31 only */ +#define DWC3_GHWPARAMS3_HSPHY_IFC(n) (((n) & (3 << 2)) >> 2) +#define DWC3_GHWPARAMS3_HSPHY_IFC_DIS 0 +#define DWC3_GHWPARAMS3_HSPHY_IFC_UTMI 1 +#define DWC3_GHWPARAMS3_HSPHY_IFC_ULPI 2 +#define DWC3_GHWPARAMS3_HSPHY_IFC_UTMI_ULPI 3 +#define DWC3_GHWPARAMS3_FSPHY_IFC(n) (((n) & (3 << 4)) >> 4) +#define DWC3_GHWPARAMS3_FSPHY_IFC_DIS 0 +#define DWC3_GHWPARAMS3_FSPHY_IFC_ENA 1 + +/* Global HWPARAMS4 Register */ +#define DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(n) (((n) & (0x0f << 13)) >> 13) +#define DWC3_MAX_HIBER_SCRATCHBUFS 15 + +/* Global HWPARAMS6 Register */ +#define DWC3_GHWPARAMS6_BCSUPPORT BIT(14) +#define DWC3_GHWPARAMS6_OTG3SUPPORT BIT(13) +#define DWC3_GHWPARAMS6_ADPSUPPORT BIT(12) +#define DWC3_GHWPARAMS6_HNPSUPPORT BIT(11) +#define DWC3_GHWPARAMS6_SRPSUPPORT BIT(10) +#define DWC3_GHWPARAMS6_EN_FPGA BIT(7) + +/* DWC_usb32 only */ +#define DWC3_GHWPARAMS6_MDWIDTH(n) ((n) & (0x3 << 8)) + +/* Global HWPARAMS7 Register */ +#define DWC3_GHWPARAMS7_RAM1_DEPTH(n) ((n) & 0xffff) +#define DWC3_GHWPARAMS7_RAM2_DEPTH(n) (((n) >> 16) & 0xffff) + +/* Global HWPARAMS9 Register */ +#define DWC3_GHWPARAMS9_DEV_TXF_FLUSH_BYPASS BIT(0) +#define DWC3_GHWPARAMS9_DEV_MST BIT(1) + +/* Global Frame Length Adjustment Register */ +#define DWC3_GFLADJ_30MHZ_SDBND_SEL BIT(7) +#define DWC3_GFLADJ_30MHZ_MASK 0x3f +#define DWC3_GFLADJ_REFCLK_FLADJ_MASK GENMASK(21, 8) +#define DWC3_GFLADJ_REFCLK_LPM_SEL BIT(23) +#define DWC3_GFLADJ_240MHZDECR GENMASK(30, 24) +#define DWC3_GFLADJ_240MHZDECR_PLS1 BIT(31) + +/* Global User Control Register*/ +#define DWC3_GUCTL_REFCLKPER_MASK 0xffc00000 +#define DWC3_GUCTL_REFCLKPER_SEL 22 + +/* Global User Control Register 2 */ +#define DWC3_GUCTL2_RST_ACTBITLATER BIT(14) +#define DWC3_GUCTL2_LC_TIMER BIT(19) + +/* Global User Control Register 3 */ +#define DWC3_GUCTL3_SPLITDISABLE BIT(14) +#define DWC3_GUCTL3_USB20_RETRY_DISABLE BIT(16) + +/* Device Configuration Register */ +#define DWC3_DCFG_NUMLANES(n) (((n) & 0x3) << 30) /* DWC_usb32 only */ + +#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_PLUS (5 << 0) /* DWC_usb31 only */ +#define DWC3_DCFG_SUPERSPEED (4 << 0) +#define DWC3_DCFG_HIGHSPEED (0 << 0) +#define DWC3_DCFG_FULLSPEED BIT(0) + +#define DWC3_DCFG_NUMP_SHIFT 17 +#define DWC3_DCFG_NUMP(n) (((n) >> DWC3_DCFG_NUMP_SHIFT) & 0x1f) +#define DWC3_DCFG_NUMP_MASK (0x1f << DWC3_DCFG_NUMP_SHIFT) +#define DWC3_DCFG_LPM_CAP BIT(22) +#define DWC3_DCFG_IGNSTRMPP BIT(23) + +/* Device Control Register */ +#define DWC3_DCTL_RUN_STOP BIT(31) +#define DWC3_DCTL_CSFTRST BIT(30) +#define DWC3_DCTL_LSFTRST BIT(29) + +#define DWC3_DCTL_HIRD_THRES_MASK (0x1f << 24) +#define DWC3_DCTL_HIRD_THRES(n) ((n) << 24) + +#define DWC3_DCTL_APPL1RES BIT(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_NYET_THRES_MASK (0xf << 20) +#define DWC3_DCTL_NYET_THRES(n) (((n) & 0xf) << 20) + +#define DWC3_DCTL_KEEP_CONNECT BIT(19) +#define DWC3_DCTL_L1_HIBER_EN BIT(18) +#define DWC3_DCTL_CRS BIT(17) +#define DWC3_DCTL_CSS BIT(16) + +#define DWC3_DCTL_INITU2ENA BIT(12) +#define DWC3_DCTL_ACCEPTU2ENA BIT(11) +#define DWC3_DCTL_INITU1ENA BIT(10) +#define DWC3_DCTL_ACCEPTU1ENA BIT(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 BIT(12) +#define DWC3_DEVTEN_EVNTOVERFLOWEN BIT(11) +#define DWC3_DEVTEN_CMDCMPLTEN BIT(10) +#define DWC3_DEVTEN_ERRTICERREN BIT(9) +#define DWC3_DEVTEN_SOFEN BIT(7) +#define DWC3_DEVTEN_U3L2L1SUSPEN BIT(6) +#define DWC3_DEVTEN_HIBERNATIONREQEVTEN BIT(5) +#define DWC3_DEVTEN_WKUPEVTEN BIT(4) +#define DWC3_DEVTEN_ULSTCNGEN BIT(3) +#define DWC3_DEVTEN_CONNECTDONEEN BIT(2) +#define DWC3_DEVTEN_USBRSTEN BIT(1) +#define DWC3_DEVTEN_DISCONNEVTEN BIT(0) + +#define DWC3_DSTS_CONNLANES(n) (((n) >> 30) & 0x3) /* DWC_usb32 only */ + +/* Device Status Register */ +#define DWC3_DSTS_DCNRD BIT(29) + +/* This applies for core versions 1.87a and earlier */ +#define DWC3_DSTS_PWRUPREQ BIT(24) + +/* These apply for core versions 1.94a and later */ +#define DWC3_DSTS_RSS BIT(25) +#define DWC3_DSTS_SSS BIT(24) + +#define DWC3_DSTS_COREIDLE BIT(23) +#define DWC3_DSTS_DEVCTRLHLT BIT(22) + +#define DWC3_DSTS_USBLNKST_MASK (0x0f << 18) +#define DWC3_DSTS_USBLNKST(n) (((n) & DWC3_DSTS_USBLNKST_MASK) >> 18) + +#define DWC3_DSTS_RXFIFOEMPTY BIT(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_PLUS (5 << 0) /* DWC_usb31 only */ +#define DWC3_DSTS_SUPERSPEED (4 << 0) +#define DWC3_DSTS_HIGHSPEED (0 << 0) +#define DWC3_DSTS_FULLSPEED BIT(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_SET_ENDPOINT_PRIME 0x0d +#define DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK 0x10 +#define DWC3_DGCMD_DEV_NOTIFICATION 0x07 + +#define DWC3_DGCMD_STATUS(n) (((n) >> 12) & 0x0F) +#define DWC3_DGCMD_CMDACT BIT(10) +#define DWC3_DGCMD_CMDIOC BIT(8) + +/* Device Generic Command Parameter Register */ +#define DWC3_DGCMDPAR_FORCE_LINKPM_ACCEPT BIT(0) +#define DWC3_DGCMDPAR_FIFO_NUM(n) ((n) << 0) +#define DWC3_DGCMDPAR_RX_FIFO (0 << 5) +#define DWC3_DGCMDPAR_TX_FIFO BIT(5) +#define DWC3_DGCMDPAR_LOOPBACK_DIS (0 << 0) +#define DWC3_DGCMDPAR_LOOPBACK_ENA BIT(0) +#define DWC3_DGCMDPAR_DN_FUNC_WAKE BIT(0) +#define DWC3_DGCMDPAR_INTF_SEL(n) ((n) << 4) + +/* 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) >> 12) & 0x0F) +#define DWC3_DEPCMD_HIPRI_FORCERM BIT(11) +#define DWC3_DEPCMD_CLEARPENDIN BIT(11) +#define DWC3_DEPCMD_CMDACT BIT(10) +#define DWC3_DEPCMD_CMDIOC BIT(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) + +#define DWC3_DEPCMD_CMD(x) ((x) & 0xf) + +/* The EP number goes 0..31 so ep0 is always out and ep1 is always in */ +#define DWC3_DALEPENA_EP(n) BIT(n) + +/* DWC_usb32 DCFG1 config */ +#define DWC3_DCFG1_DIS_MST_ENH BIT(1) + +#define DWC3_DEPCMD_TYPE_CONTROL 0 +#define DWC3_DEPCMD_TYPE_ISOC 1 +#define DWC3_DEPCMD_TYPE_BULK 2 +#define DWC3_DEPCMD_TYPE_INTR 3 + +#define DWC3_DEV_IMOD_COUNT_SHIFT 16 +#define DWC3_DEV_IMOD_COUNT_MASK (0xffff << 16) +#define DWC3_DEV_IMOD_INTERVAL_SHIFT 0 +#define DWC3_DEV_IMOD_INTERVAL_MASK (0xffff << 0) + +/* OTG Configuration Register */ +#define DWC3_OCFG_DISPWRCUTTOFF BIT(5) +#define DWC3_OCFG_HIBDISMASK BIT(4) +#define DWC3_OCFG_SFTRSTMASK BIT(3) +#define DWC3_OCFG_OTGVERSION BIT(2) +#define DWC3_OCFG_HNPCAP BIT(1) +#define DWC3_OCFG_SRPCAP BIT(0) + +/* OTG CTL Register */ +#define DWC3_OCTL_OTG3GOERR BIT(7) +#define DWC3_OCTL_PERIMODE BIT(6) +#define DWC3_OCTL_PRTPWRCTL BIT(5) +#define DWC3_OCTL_HNPREQ BIT(4) +#define DWC3_OCTL_SESREQ BIT(3) +#define DWC3_OCTL_TERMSELIDPULSE BIT(2) +#define DWC3_OCTL_DEVSETHNPEN BIT(1) +#define DWC3_OCTL_HSTSETHNPEN BIT(0) + +/* OTG Event Register */ +#define DWC3_OEVT_DEVICEMODE BIT(31) +#define DWC3_OEVT_XHCIRUNSTPSET BIT(27) +#define DWC3_OEVT_DEVRUNSTPSET BIT(26) +#define DWC3_OEVT_HIBENTRY BIT(25) +#define DWC3_OEVT_CONIDSTSCHNG BIT(24) +#define DWC3_OEVT_HRRCONFNOTIF BIT(23) +#define DWC3_OEVT_HRRINITNOTIF BIT(22) +#define DWC3_OEVT_ADEVIDLE BIT(21) +#define DWC3_OEVT_ADEVBHOSTEND BIT(20) +#define DWC3_OEVT_ADEVHOST BIT(19) +#define DWC3_OEVT_ADEVHNPCHNG BIT(18) +#define DWC3_OEVT_ADEVSRPDET BIT(17) +#define DWC3_OEVT_ADEVSESSENDDET BIT(16) +#define DWC3_OEVT_BDEVBHOSTEND BIT(11) +#define DWC3_OEVT_BDEVHNPCHNG BIT(10) +#define DWC3_OEVT_BDEVSESSVLDDET BIT(9) +#define DWC3_OEVT_BDEVVBUSCHNG BIT(8) +#define DWC3_OEVT_BSESSVLD BIT(3) +#define DWC3_OEVT_HSTNEGSTS BIT(2) +#define DWC3_OEVT_SESREQSTS BIT(1) +#define DWC3_OEVT_ERROR BIT(0) + +/* OTG Event Enable Register */ +#define DWC3_OEVTEN_XHCIRUNSTPSETEN BIT(27) +#define DWC3_OEVTEN_DEVRUNSTPSETEN BIT(26) +#define DWC3_OEVTEN_HIBENTRYEN BIT(25) +#define DWC3_OEVTEN_CONIDSTSCHNGEN BIT(24) +#define DWC3_OEVTEN_HRRCONFNOTIFEN BIT(23) +#define DWC3_OEVTEN_HRRINITNOTIFEN BIT(22) +#define DWC3_OEVTEN_ADEVIDLEEN BIT(21) +#define DWC3_OEVTEN_ADEVBHOSTENDEN BIT(20) +#define DWC3_OEVTEN_ADEVHOSTEN BIT(19) +#define DWC3_OEVTEN_ADEVHNPCHNGEN BIT(18) +#define DWC3_OEVTEN_ADEVSRPDETEN BIT(17) +#define DWC3_OEVTEN_ADEVSESSENDDETEN BIT(16) +#define DWC3_OEVTEN_BDEVBHOSTENDEN BIT(11) +#define DWC3_OEVTEN_BDEVHNPCHNGEN BIT(10) +#define DWC3_OEVTEN_BDEVSESSVLDDETEN BIT(9) +#define DWC3_OEVTEN_BDEVVBUSCHNGEN BIT(8) + +/* OTG Status Register */ +#define DWC3_OSTS_DEVRUNSTP BIT(13) +#define DWC3_OSTS_XHCIRUNSTP BIT(12) +#define DWC3_OSTS_PERIPHERALSTATE BIT(4) +#define DWC3_OSTS_XHCIPRTPOWER BIT(3) +#define DWC3_OSTS_BSESVLD BIT(2) +#define DWC3_OSTS_VBUSVLD BIT(1) +#define DWC3_OSTS_CONIDSTS BIT(0) + +/* Force Gen1 speed on Gen2 link */ +#define DWC3_LLUCTL_FORCE_GEN1 BIT(10) + +/* Structures */ + +struct dwc3_trb; + +/** + * struct dwc3_event_buffer - Software event buffer representation + * @buf: _THE_ buffer + * @cache: The buffer cache used in the threaded interrupt + * @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; + void *cache; + unsigned int 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 BIT(0) +#define DWC3_EP_FLAG_WEDGED BIT(1) + +#define DWC3_EP_DIRECTION_TX true +#define DWC3_EP_DIRECTION_RX false + +#define DWC3_TRB_NUM 256 + +/** + * struct dwc3_ep - device side endpoint representation + * @endpoint: usb endpoint + * @nostream_work: work for handling bulk NoStream + * @cancelled_list: list of cancelled requests for this endpoint + * @pending_list: list of pending requests for this endpoint + * @started_list: list of started requests on this endpoint + * @trb_pool: array of transaction buffers + * @trb_pool_dma: dma address of @trb_pool + * @trb_enqueue: enqueue 'pointer' into TRB array + * @trb_dequeue: dequeue 'pointer' into TRB array + * @dwc: pointer to DWC controller + * @saved_state: ep state saved during hibernation + * @flags: endpoint flags (wedged, stalled, ...) + * @number: endpoint number (1 - 15) + * @type: set to bmAttributes & USB_ENDPOINT_XFERTYPE_MASK + * @resource_index: Resource transfer index + * @frame_number: set to the frame number we want this transfer to start (ISOC) + * @interval: the interval 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 + * @combo_num: the test combination BIT[15:14] of the frame number to test + * isochronous START TRANSFER command failure workaround + * @start_cmd_status: the status of testing START TRANSFER command with + * combo_num = 'b00 + */ +struct dwc3_ep { + struct usb_ep endpoint; + struct delayed_work nostream_work; + struct list_head cancelled_list; + struct list_head pending_list; + struct list_head started_list; + + struct dwc3_trb *trb_pool; + dma_addr_t trb_pool_dma; + struct dwc3 *dwc; + + u32 saved_state; + unsigned int flags; +#define DWC3_EP_ENABLED BIT(0) +#define DWC3_EP_STALL BIT(1) +#define DWC3_EP_WEDGE BIT(2) +#define DWC3_EP_TRANSFER_STARTED BIT(3) +#define DWC3_EP_END_TRANSFER_PENDING BIT(4) +#define DWC3_EP_PENDING_REQUEST BIT(5) +#define DWC3_EP_DELAY_START BIT(6) +#define DWC3_EP_WAIT_TRANSFER_COMPLETE BIT(7) +#define DWC3_EP_IGNORE_NEXT_NOSTREAM BIT(8) +#define DWC3_EP_FORCE_RESTART_STREAM BIT(9) +#define DWC3_EP_STREAM_PRIMED BIT(10) +#define DWC3_EP_PENDING_CLEAR_STALL BIT(11) +#define DWC3_EP_TXFIFO_RESIZED BIT(12) +#define DWC3_EP_DELAY_STOP BIT(13) +#define DWC3_EP_RESOURCE_ALLOCATED BIT(14) + + /* This last one is specific to EP0 */ +#define DWC3_EP0_DIR_IN BIT(31) + + /* + * IMPORTANT: we *know* we have 256 TRBs in our @trb_pool, so we will + * use a u8 type here. If anybody decides to increase number of TRBs to + * anything larger than 256 - I can't see why people would want to do + * this though - then this type needs to be changed. + * + * By using u8 types we ensure that our % operator when incrementing + * enqueue and dequeue get optimized away by the compiler. + */ + u8 trb_enqueue; + u8 trb_dequeue; + + u8 number; + u8 type; + u8 resource_index; + u32 frame_number; + u32 interval; + + char name[20]; + + unsigned direction:1; + unsigned stream_capable:1; + + /* For isochronous START TRANSFER workaround only */ + u8 combo_num; + int start_cmd_status; +}; + +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 BIT(0) +#define DWC3_TRB_CTRL_LST BIT(1) +#define DWC3_TRB_CTRL_CHN BIT(2) +#define DWC3_TRB_CTRL_CSP BIT(3) +#define DWC3_TRB_CTRL_TRBCTL(n) (((n) & 0x3f) << 4) +#define DWC3_TRB_CTRL_ISP_IMI BIT(10) +#define DWC3_TRB_CTRL_IOC BIT(11) +#define DWC3_TRB_CTRL_SID_SOFN(n) (((n) & 0xffff) << 14) +#define DWC3_TRB_CTRL_GET_SID_SOFN(n) (((n) & (0xffff << 14)) >> 14) + +#define DWC3_TRBCTL_TYPE(n) ((n) & (0x3f << 4)) +#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 + * @ctrl: DWC-F + */ +struct dwc3_trb { + u32 bpl; + u32 bph; + u32 size; + u32 ctrl; +} __packed; + +/** + * struct dwc3_hwparams - copy of HWPARAMS registers + * @hwparams0: GHWPARAMS0 + * @hwparams1: GHWPARAMS1 + * @hwparams2: GHWPARAMS2 + * @hwparams3: GHWPARAMS3 + * @hwparams4: GHWPARAMS4 + * @hwparams5: GHWPARAMS5 + * @hwparams6: GHWPARAMS6 + * @hwparams7: GHWPARAMS7 + * @hwparams8: GHWPARAMS8 + * @hwparams9: GHWPARAMS9 + */ +struct dwc3_hwparams { + u32 hwparams0; + u32 hwparams1; + u32 hwparams2; + u32 hwparams3; + u32 hwparams4; + u32 hwparams5; + u32 hwparams6; + u32 hwparams7; + u32 hwparams8; + u32 hwparams9; +}; + +/* HWPARAMS0 */ +#define DWC3_MODE(n) ((n) & 0x7) + +/* HWPARAMS1 */ +#define DWC3_SPRAM_TYPE(n) (((n) >> 23) & 1) +#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) + +/* HWPARAMS6 */ +#define DWC3_RAM0_DEPTH(n) (((n) & (0xffff0000)) >> 16) + +/* HWPARAMS7 */ +#define DWC3_RAM1_DEPTH(n) ((n) & 0xffff) + +/* HWPARAMS9 */ +#define DWC3_MST_CAPABLE(p) (!!((p)->hwparams9 & \ + DWC3_GHWPARAMS9_DEV_MST)) + +/** + * struct dwc3_request - representation of a transfer request + * @request: struct usb_request to be transferred + * @list: a list_head used for request queueing + * @dep: struct dwc3_ep owning this request + * @start_sg: pointer to the sg which should be queued next + * @num_pending_sgs: counter to pending sgs + * @remaining: amount of data remaining + * @status: internal dwc3 request status tracking + * @epnum: endpoint number to which this request refers + * @trb: pointer to struct dwc3_trb + * @trb_dma: DMA address of @trb + * @num_trbs: number of TRBs used by this request + * @direction: IN or OUT direction flag + * @mapped: true when request has been dma-mapped + */ +struct dwc3_request { + struct usb_request request; + struct list_head list; + struct dwc3_ep *dep; + struct scatterlist *start_sg; + + unsigned int num_pending_sgs; + unsigned int remaining; + + unsigned int status; +#define DWC3_REQUEST_STATUS_QUEUED 0 +#define DWC3_REQUEST_STATUS_STARTED 1 +#define DWC3_REQUEST_STATUS_DISCONNECTED 2 +#define DWC3_REQUEST_STATUS_DEQUEUED 3 +#define DWC3_REQUEST_STATUS_STALLED 4 +#define DWC3_REQUEST_STATUS_COMPLETED 5 +#define DWC3_REQUEST_STATUS_UNKNOWN -1 + + u8 epnum; + struct dwc3_trb *trb; + dma_addr_t trb_dma; + + unsigned int num_trbs; + + unsigned int direction:1; + unsigned int mapped: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_glue_ops - The ops indicate the notifications that + * need to be passed on to glue layer + * @pre_set_role: Notify glue of role switch notifications + * @pre_run_stop: Notify run stop enable/disable information to glue + */ +struct dwc3_glue_ops { + void (*pre_set_role)(struct dwc3 *dwc, enum usb_role role); + void (*pre_run_stop)(struct dwc3 *dwc, bool is_on); +}; + +/** + * struct dwc3 - representation of our controller + * @drd_work: workqueue used for role swapping + * @ep0_trb: trb which is used for the ctrl_req + * @bounce: address of bounce buffer + * @setup_buf: used while precessing STD USB requests + * @ep0_trb_addr: dma address of @ep0_trb + * @bounce_addr: dma address of @bounce + * @ep0_usb_req: dummy req used while handling STD USB requests + * @ep0_in_setup: one control transfer is completed and enter setup phase + * @lock: for synchronizing + * @mutex: for mode switching + * @dev: pointer to our struct device + * @sysdev: pointer to the DMA-capable device + * @xhci: pointer to our xHCI child + * @xhci_resources: struct resources for our @xhci child + * @ev_buf: struct dwc3_event_buffer pointer + * @eps: endpoint array + * @gadget: device side representation of the peripheral controller + * @gadget_driver: pointer to the gadget driver + * @glue_ops: Vendor callbacks for flattened device implementations. + * @bus_clk: clock for accessing the registers + * @ref_clk: reference clock + * @susp_clk: clock used when the SS phy is in low power (S3) state + * @utmi_clk: clock used for USB2 PHY communication + * @pipe_clk: clock used for USB3 PHY communication + * @reset: reset control + * @regs: base address for our registers + * @regs_size: address space size + * @fladj: frame length adjustment + * @ref_clk_per: reference clock period configuration + * @irq_gadget: peripheral controller's IRQ number + * @otg_irq: IRQ number for OTG IRQs + * @current_otg_role: current role of operation while using the OTG block + * @desired_otg_role: desired role of operation while using the OTG block + * @otg_restart_host: flag that OTG controller needs to restart host + * @u1u2: only used on revisions <1.83a for workaround + * @maximum_speed: maximum speed requested (mainly for testing purposes) + * @max_ssp_rate: SuperSpeed Plus maximum signaling rate and lane count + * @gadget_max_speed: maximum gadget speed requested + * @gadget_ssp_rate: Gadget driver's maximum supported SuperSpeed Plus signaling + * rate and lane count. + * @ip: controller's ID + * @revision: controller's version of an IP + * @version_type: VERSIONTYPE register contents, a sub release of a revision + * @dr_mode: requested mode of operation + * @current_dr_role: current role of operation when in dual-role mode + * @desired_dr_role: desired role of operation when in dual-role mode + * @edev: extcon handle + * @edev_nb: extcon notifier + * @hsphy_mode: UTMI phy mode, one of following: + * - USBPHY_INTERFACE_MODE_UTMI + * - USBPHY_INTERFACE_MODE_UTMIW + * @role_sw: usb_role_switch handle + * @role_switch_default_mode: default operation mode of controller while + * usb role is USB_ROLE_NONE. + * @usb_psy: pointer to power supply interface. + * @usb_psy_name: name of the USB power supply + * @psy_nb: power supply notifier block + * @vbus_draw_work: Work to set the vbus drawing limit + * @current_limit: How much current to draw from vbus, in milliAmperes. + * @usb2_phy: pointer to USB2 PHY + * @usb3_phy: pointer to USB3 PHY + * @usb2_generic_phy: pointer to array of USB2 PHYs + * @usb3_generic_phy: pointer to array of USB3 PHYs + * @num_usb2_ports: number of USB2 ports + * @num_usb3_ports: number of USB3 ports + * @phys_ready: flag to indicate that PHYs are ready + * @ulpi: pointer to ulpi interface + * @ulpi_ready: flag to indicate that ULPI is initialized + * @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_eps: number of 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) + * @hwparams: copy of hwparams registers + * @regset: debugfs pointer to regdump file + * @dbg_lsp_select: current debug lsp mux register selection + * @test_mode: true when we're entering a USB test mode + * @test_mode_nr: test feature selector + * @lpm_nyet_threshold: LPM NYET response threshold + * @hird_threshold: HIRD threshold + * @rx_thr_num_pkt: USB receive packet count + * @rx_max_burst: max USB receive burst size + * @tx_thr_num_pkt: USB transmit packet count + * @tx_max_burst: max USB transmit burst size + * @rx_thr_num_pkt_prd: periodic ESS receive packet count + * @rx_max_burst_prd: max periodic ESS receive burst size + * @tx_thr_num_pkt_prd: periodic ESS transmit packet count + * @tx_max_burst_prd: max periodic ESS transmit burst size + * @tx_fifo_resize_max_num: max number of fifos allocated during txfifo resize + * @clear_stall_protocol: endpoint number that requires a delayed status phase + * @num_hc_interrupters: number of host controller interrupters + * @hsphy_interface: "utmi" or "ulpi" + * @connected: true when we're connected to a host, false otherwise + * @softconnect: true when gadget connect is called, false when disconnect runs + * @delayed_status: true when gadget driver asks for delayed status + * @ep0_bounced: true when we used bounce buffer + * @ep0_expect_in: true when we expect a DATA IN transfer + * @sysdev_is_parent: true when dwc3 device has a parent driver + * @has_lpm_erratum: true when core was configured with LPM Erratum. Note that + * there's now way for software to detect this in runtime. + * @is_utmi_l1_suspend: the core asserts output signal + * 0 - utmi_sleep_n + * 1 - utmi_l1_suspend_n + * @is_fpga: true when we are using the FPGA board + * @pending_events: true when we have pending IRQs to be handled + * @do_fifo_resize: true when txfifo resizing is enabled for dwc3 endpoints + * @pullups_connected: true when Run/Stop bit is set + * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround + * @three_stage_setup: set if we perform a three phase setup + * @dis_start_transfer_quirk: set if start_transfer failure SW workaround is + * not needed for DWC_usb31 version 1.70a-ea06 and below + * @usb3_lpm_capable: set if hadrware supports Link Power Management + * @usb2_lpm_disable: set to disable usb2 lpm for host + * @usb2_gadget_lpm_disable: set to disable usb2 lpm for gadget + * @needs_full_reinit: set to indicate the core may lose power and need full + * initialization during system pm + * @disable_scramble_quirk: set if we enable the disable scramble quirk + * @u2exit_lfps_quirk: set if we enable u2exit lfps quirk + * @u2ss_inp3_quirk: set if we enable P3 OK for U2/SS Inactive quirk + * @req_p1p2p3_quirk: set if we enable request p1p2p3 quirk + * @del_p1p2p3_quirk: set if we enable delay p1p2p3 quirk + * @del_phy_power_chg_quirk: set if we enable delay phy power change quirk + * @lfps_filter_quirk: set if we enable LFPS filter quirk + * @rx_detect_poll_quirk: set if we enable rx_detect to polling lfps quirk + * @dis_u3_susphy_quirk: set if we disable usb3 suspend phy + * @dis_u2_susphy_quirk: set if we disable usb2 suspend phy + * @dis_enblslpm_quirk: set if we clear enblslpm in GUSB2PHYCFG, + * disabling the suspend signal to the PHY. + * @dis_u1_entry_quirk: set if link entering into U1 state needs to be disabled. + * @dis_u2_entry_quirk: set if link entering into U2 state needs to be disabled. + * @dis_rxdet_inp3_quirk: set if we disable Rx.Detect in P3 + * @async_callbacks: if set, indicate that async callbacks will be used. + * + * @dis_u2_freeclk_exists_quirk : set if we clear u2_freeclk_exists + * in GUSB2PHYCFG, specify that USB2 PHY doesn't + * provide a free-running PHY clock. + * @dis_del_phy_power_chg_quirk: set if we disable delay phy power + * change quirk. + * @dis_tx_ipgap_linecheck_quirk: set if we disable u2mac linestate + * check during HS transmit. + * @resume_hs_terminations: Set if we enable quirk for fixing improper crc + * generation after resume from suspend. + * @ulpi_ext_vbus_drv: Set to confiure the upli chip to drives CPEN pin + * VBUS with an external supply. + * @parkmode_disable_ss_quirk: set if we need to disable all SuperSpeed + * instances in park mode. + * @parkmode_disable_hs_quirk: set if we need to disable all HighSpeed + * instances in park mode. + * @gfladj_refclk_lpm_sel: set if we need to enable SOF/ITP counter + * running based on ref_clk + * @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk + * @tx_de_emphasis: Tx de-emphasis value + * 0 - -6dB de-emphasis + * 1 - -3.5dB de-emphasis + * 2 - No de-emphasis + * 3 - Reserved + * @dis_metastability_quirk: set to disable metastability quirk. + * @dis_split_quirk: set to disable split boundary. + * @enable_usb2_transceiver_delay: Set to insert a delay before the + * assertion of the TxValid signal during a HS Chirp. + * @sys_wakeup: set if the device may do system wakeup. + * @wakeup_configured: set if the device is configured for remote wakeup. + * @suspended: set to track suspend event due to U3/L2. + * @susphy_state: state of DWC3_GUSB2PHYCFG_SUSPHY + DWC3_GUSB3PIPECTL_SUSPHY + * before PM suspend. + * @imod_interval: set the interrupt moderation interval in 250ns + * increments or 0 to disable. + * @max_cfg_eps: current max number of IN eps used across all USB configs. + * @last_fifo_depth: last fifo depth used to determine next fifo ram start + * address. + * @num_ep_resized: carries the current number endpoints which have had its tx + * fifo resized. + * @debug_root: root debugfs directory for this device to put its files in. + * @gsbuscfg0_reqinfo: store GSBUSCFG0.DATRDREQINFO, DESRDREQINFO, + * DATWRREQINFO, and DESWRREQINFO value passed from + * glue driver. + * @wakeup_pending_funcs: Indicates whether any interface has requested for + * function wakeup in bitmap format where bit position + * represents interface_id. + */ +struct dwc3 { + struct work_struct drd_work; + struct dwc3_trb *ep0_trb; + void *bounce; + u8 *setup_buf; + dma_addr_t ep0_trb_addr; + dma_addr_t bounce_addr; + struct dwc3_request ep0_usb_req; + struct completion ep0_in_setup; + + /* device lock */ + spinlock_t lock; + + /* mode switching lock */ + struct mutex mutex; + + struct device *dev; + struct device *sysdev; + + struct platform_device *xhci; + struct resource xhci_resources[DWC3_XHCI_RESOURCES_NUM]; + + struct dwc3_event_buffer *ev_buf; + struct dwc3_ep *eps[DWC3_ENDPOINTS_NUM]; + + struct usb_gadget *gadget; + struct usb_gadget_driver *gadget_driver; + + const struct dwc3_glue_ops *glue_ops; + + struct clk *bus_clk; + struct clk *ref_clk; + struct clk *susp_clk; + struct clk *utmi_clk; + struct clk *pipe_clk; + + struct reset_control *reset; + + struct usb_phy *usb2_phy; + struct usb_phy *usb3_phy; + + struct phy *usb2_generic_phy[DWC3_USB2_MAX_PORTS]; + struct phy *usb3_generic_phy[DWC3_USB3_MAX_PORTS]; + + u8 num_usb2_ports; + u8 num_usb3_ports; + + bool phys_ready; + + struct ulpi *ulpi; + bool ulpi_ready; + + void __iomem *regs; + size_t regs_size; + + enum usb_dr_mode dr_mode; + u32 current_dr_role; + u32 desired_dr_role; + struct extcon_dev *edev; + struct notifier_block edev_nb; + enum usb_phy_interface hsphy_mode; + struct usb_role_switch *role_sw; + enum usb_dr_mode role_switch_default_mode; + + struct power_supply *usb_psy; + const char *usb_psy_name; + struct notifier_block psy_nb; + struct work_struct vbus_draw_work; + unsigned int current_limit; + +#define DWC3_CURRENT_UNSPECIFIED UINT_MAX + + u32 fladj; + u32 ref_clk_per; + u32 irq_gadget; + u32 otg_irq; + u32 current_otg_role; + u32 desired_otg_role; + bool otg_restart_host; + u32 u1u2; + u32 maximum_speed; + u32 gadget_max_speed; + enum usb_ssp_rate max_ssp_rate; + enum usb_ssp_rate gadget_ssp_rate; + + u32 ip; + +#define DWC3_IP 0x5533 +#define DWC31_IP 0x3331 +#define DWC32_IP 0x3332 +#define DWC4_IP 0x3430 + + u32 revision; + +#define DWC3_REVISION_ANY 0x0 +#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 +#define DWC3_REVISION_260A 0x5533260a +#define DWC3_REVISION_270A 0x5533270a +#define DWC3_REVISION_280A 0x5533280a +#define DWC3_REVISION_290A 0x5533290a +#define DWC3_REVISION_300A 0x5533300a +#define DWC3_REVISION_310A 0x5533310a +#define DWC3_REVISION_320A 0x5533320a +#define DWC3_REVISION_330A 0x5533330a + +#define DWC31_REVISION_ANY 0x0 +#define DWC31_REVISION_110A 0x3131302a +#define DWC31_REVISION_120A 0x3132302a +#define DWC31_REVISION_160A 0x3136302a +#define DWC31_REVISION_170A 0x3137302a +#define DWC31_REVISION_180A 0x3138302a +#define DWC31_REVISION_190A 0x3139302a +#define DWC31_REVISION_200A 0x3230302a + +#define DWC32_REVISION_ANY 0x0 +#define DWC32_REVISION_100A 0x3130302a + + u32 version_type; + +#define DWC31_VERSIONTYPE_ANY 0x0 +#define DWC31_VERSIONTYPE_EA01 0x65613031 +#define DWC31_VERSIONTYPE_EA02 0x65613032 +#define DWC31_VERSIONTYPE_EA03 0x65613033 +#define DWC31_VERSIONTYPE_EA04 0x65613034 +#define DWC31_VERSIONTYPE_EA05 0x65613035 +#define DWC31_VERSIONTYPE_EA06 0x65613036 + + enum dwc3_ep0_next ep0_next_event; + enum dwc3_ep0_state ep0state; + enum dwc3_link_state link_state; + + u16 u2sel; + u16 u2pel; + u8 u1sel; + u8 u1pel; + + u8 speed; + + u8 num_eps; + + struct dwc3_hwparams hwparams; + struct debugfs_regset32 *regset; + + u32 dbg_lsp_select; + + u8 test_mode; + u8 test_mode_nr; + u8 lpm_nyet_threshold; + u8 hird_threshold; + u8 rx_thr_num_pkt; + u8 rx_max_burst; + u8 tx_thr_num_pkt; + u8 tx_max_burst; + u8 rx_thr_num_pkt_prd; + u8 rx_max_burst_prd; + u8 tx_thr_num_pkt_prd; + u8 tx_max_burst_prd; + u8 tx_fifo_resize_max_num; + u8 clear_stall_protocol; + u16 num_hc_interrupters; + + const char *hsphy_interface; + + unsigned connected:1; + unsigned softconnect:1; + unsigned delayed_status:1; + unsigned ep0_bounced:1; + unsigned ep0_expect_in:1; + unsigned sysdev_is_parent:1; + unsigned has_lpm_erratum:1; + unsigned is_utmi_l1_suspend:1; + unsigned is_fpga:1; + unsigned pending_events:1; + unsigned do_fifo_resize:1; + unsigned pullups_connected:1; + unsigned setup_packet_pending:1; + unsigned three_stage_setup:1; + unsigned dis_start_transfer_quirk:1; + unsigned usb3_lpm_capable:1; + unsigned usb2_lpm_disable:1; + unsigned usb2_gadget_lpm_disable:1; + unsigned needs_full_reinit:1; + + unsigned disable_scramble_quirk:1; + unsigned u2exit_lfps_quirk:1; + unsigned u2ss_inp3_quirk:1; + unsigned req_p1p2p3_quirk:1; + unsigned del_p1p2p3_quirk:1; + unsigned del_phy_power_chg_quirk:1; + unsigned lfps_filter_quirk:1; + unsigned rx_detect_poll_quirk:1; + unsigned dis_u3_susphy_quirk:1; + unsigned dis_u2_susphy_quirk:1; + unsigned dis_enblslpm_quirk:1; + unsigned dis_u1_entry_quirk:1; + unsigned dis_u2_entry_quirk:1; + unsigned dis_rxdet_inp3_quirk:1; + unsigned dis_u2_freeclk_exists_quirk:1; + unsigned dis_del_phy_power_chg_quirk:1; + unsigned dis_tx_ipgap_linecheck_quirk:1; + unsigned resume_hs_terminations:1; + unsigned ulpi_ext_vbus_drv:1; + unsigned parkmode_disable_ss_quirk:1; + unsigned parkmode_disable_hs_quirk:1; + unsigned gfladj_refclk_lpm_sel:1; + + unsigned tx_de_emphasis_quirk:1; + unsigned tx_de_emphasis:2; + + unsigned dis_metastability_quirk:1; + + unsigned dis_split_quirk:1; + unsigned enable_usb2_transceiver_delay:1; + unsigned async_callbacks:1; + unsigned sys_wakeup:1; + unsigned wakeup_configured:1; + unsigned suspended:1; + unsigned susphy_state:1; + + u16 imod_interval; + + int max_cfg_eps; + int last_fifo_depth; + int num_ep_resized; + struct dentry *debug_root; + u32 gsbuscfg0_reqinfo; + u32 wakeup_pending_funcs; +}; + +#define INCRX_BURST_MODE 0 +#define INCRX_UNDEF_LENGTH_BURST_MODE 1 + +#define work_to_dwc(w) (container_of((w), struct dwc3, drd_work)) + +/* -------------------------------------------------------------------------- */ + +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_depevt - 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 BIT(3) + +/* Within XferComplete or XferInProgress */ +#define DEPEVT_STATUS_BUSERR BIT(0) +#define DEPEVT_STATUS_SHORT BIT(1) +#define DEPEVT_STATUS_IOC BIT(2) +#define DEPEVT_STATUS_LST BIT(3) /* XferComplete */ +#define DEPEVT_STATUS_MISSED_ISOC BIT(3) /* XferInProgress */ + +/* Stream event only */ +#define DEPEVT_STREAMEVT_FOUND 1 +#define DEPEVT_STREAMEVT_NOTFOUND 2 + +/* Stream event parameter */ +#define DEPEVT_STREAM_PRIME 0xfffe +#define DEPEVT_STREAM_NOSTREAM 0x0 + +/* Control-only Status */ +#define DEPEVT_STATUS_CONTROL_DATA 1 +#define DEPEVT_STATUS_CONTROL_STATUS 2 +#define DEPEVT_STATUS_CONTROL_PHASE(n) ((n) & 3) + +/* In response to Start Transfer */ +#define DEPEVT_TRANSFER_NO_RESOURCE 1 +#define DEPEVT_TRANSFER_BUS_EXPIRY 2 + + u32 parameters:16; + +/* For Command Complete Events */ +#define DEPEVT_PARAMETER_CMD(n) (((n) & (0xf << 8)) >> 8) +} __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 - Suspend (EOPF on revisions 2.10a and prior) + * 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; +}; + +/** + * struct dwc3_gadget_ep_cmd_params - representation of endpoint command + * parameters + * @param2: third parameter + * @param1: second parameter + * @param0: first parameter + */ +struct dwc3_gadget_ep_cmd_params { + u32 param2; + u32 param1; + u32 param0; +}; + +/* + * 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_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy); +void dwc3_set_mode(struct dwc3 *dwc, u32 mode); +u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type); + +#define DWC3_IP_IS(_ip) \ + (dwc->ip == _ip##_IP) + +#define DWC3_VER_IS(_ip, _ver) \ + (DWC3_IP_IS(_ip) && dwc->revision == _ip##_REVISION_##_ver) + +#define DWC3_VER_IS_PRIOR(_ip, _ver) \ + (DWC3_IP_IS(_ip) && dwc->revision < _ip##_REVISION_##_ver) + +#define DWC3_VER_IS_WITHIN(_ip, _from, _to) \ + (DWC3_IP_IS(_ip) && \ + dwc->revision >= _ip##_REVISION_##_from && \ + (!(_ip##_REVISION_##_to) || \ + dwc->revision <= _ip##_REVISION_##_to)) + +#define DWC3_VER_TYPE_IS_WITHIN(_ip, _ver, _from, _to) \ + (DWC3_VER_IS(_ip, _ver) && \ + dwc->version_type >= _ip##_VERSIONTYPE_##_from && \ + (!(_ip##_VERSIONTYPE_##_to) || \ + dwc->version_type <= _ip##_VERSIONTYPE_##_to)) + +/** + * dwc3_mdwidth - get MDWIDTH value in bits + * @dwc: pointer to our context structure + * + * Return MDWIDTH configuration value in bits. + */ +static inline u32 dwc3_mdwidth(struct dwc3 *dwc) +{ + u32 mdwidth; + + mdwidth = DWC3_GHWPARAMS0_MDWIDTH(dwc->hwparams.hwparams0); + if (DWC3_IP_IS(DWC32)) + mdwidth += DWC3_GHWPARAMS6_MDWIDTH(dwc->hwparams.hwparams6); + + return mdwidth; +} + +bool dwc3_has_imod(struct dwc3 *dwc); + +int dwc3_event_buffers_setup(struct dwc3 *dwc); +void dwc3_event_buffers_cleanup(struct dwc3 *dwc); + +int dwc3_core_soft_reset(struct dwc3 *dwc); +void dwc3_enable_susphy(struct dwc3 *dwc, bool enable); + +static inline void dwc3_pre_set_role(struct dwc3 *dwc, enum usb_role role) +{ + if (dwc->glue_ops && dwc->glue_ops->pre_set_role) + dwc->glue_ops->pre_set_role(dwc, role); +} + +static inline void dwc3_pre_run_stop(struct dwc3 *dwc, bool is_on) +{ + if (dwc->glue_ops && dwc->glue_ops->pre_run_stop) + dwc->glue_ops->pre_run_stop(dwc, is_on); +} + +#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); +int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode); +int dwc3_gadget_get_link_state(struct dwc3 *dwc); +int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state); +int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd, + struct dwc3_gadget_ep_cmd_params *params); +int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned int cmd, + u32 param); +void dwc3_gadget_clear_tx_fifos(struct dwc3 *dwc); +void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep, int status); +#else +static inline int dwc3_gadget_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_gadget_exit(struct dwc3 *dwc) +{ } +static inline int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode) +{ return 0; } +static inline int dwc3_gadget_get_link_state(struct dwc3 *dwc) +{ return 0; } +static inline int dwc3_gadget_set_link_state(struct dwc3 *dwc, + enum dwc3_link_state state) +{ return 0; } + +static inline int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd, + struct dwc3_gadget_ep_cmd_params *params) +{ return 0; } +static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, + int cmd, u32 param) +{ return 0; } +static inline void dwc3_gadget_clear_tx_fifos(struct dwc3 *dwc) +{ } +#endif + +#if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) +int dwc3_drd_init(struct dwc3 *dwc); +void dwc3_drd_exit(struct dwc3 *dwc); +void dwc3_otg_init(struct dwc3 *dwc); +void dwc3_otg_exit(struct dwc3 *dwc); +void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus); +void dwc3_otg_host_init(struct dwc3 *dwc); +#else +static inline int dwc3_drd_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_drd_exit(struct dwc3 *dwc) +{ } +static inline void dwc3_otg_init(struct dwc3 *dwc) +{ } +static inline void dwc3_otg_exit(struct dwc3 *dwc) +{ } +static inline void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus) +{ } +static inline void dwc3_otg_host_init(struct dwc3 *dwc) +{ } +#endif + +/* power management interface */ +#if !IS_ENABLED(CONFIG_USB_DWC3_HOST) +int dwc3_gadget_suspend(struct dwc3 *dwc); +int dwc3_gadget_resume(struct dwc3 *dwc); +#else +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) */ + +#if IS_ENABLED(CONFIG_USB_DWC3_ULPI) +int dwc3_ulpi_init(struct dwc3 *dwc); +void dwc3_ulpi_exit(struct dwc3 *dwc); +#else +static inline int dwc3_ulpi_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_ulpi_exit(struct dwc3 *dwc) +{ } +#endif + +#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 000000000..6e1cdcdce --- /dev/null +++ b/drivers/usb/dwc3/debug.h @@ -0,0 +1,450 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * debug.h - DesignWare USB3 DRD Controller Debug Header + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + */ + +#ifndef __DWC3_DEBUG_H +#define __DWC3_DEBUG_H + +#include "core.h" + +/** + * dwc3_mode_string - returns mode name + * @mode: GCTL.PrtCapDir value + */ +static inline const char *dwc3_mode_string(u32 mode) +{ + switch (mode) { + case DWC3_GCTL_PRTCAP_HOST: + return "host"; + case DWC3_GCTL_PRTCAP_DEVICE: + return "device"; + case DWC3_GCTL_PRTCAP_OTG: + return "otg"; + default: + return "UNKNOWN"; + } +} + +/** + * dwc3_gadget_ep_cmd_string - returns endpoint command string + * @cmd: command code + */ +static inline 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"; + } +} + +/** + * dwc3_gadget_generic_cmd_string - returns generic command string + * @cmd: command code + */ +static inline const char * +dwc3_gadget_generic_cmd_string(u8 cmd) +{ + switch (cmd) { + case DWC3_DGCMD_SET_LMP: + return "Set LMP"; + case DWC3_DGCMD_SET_PERIODIC_PAR: + return "Set Periodic Parameters"; + case DWC3_DGCMD_XMIT_FUNCTION: + return "Transmit Function Wake Device Notification"; + case DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO: + return "Set Scratchpad Buffer Array Address Lo"; + case DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI: + return "Set Scratchpad Buffer Array Address Hi"; + case DWC3_DGCMD_SELECTED_FIFO_FLUSH: + return "Selected FIFO Flush"; + case DWC3_DGCMD_ALL_FIFO_FLUSH: + return "All FIFO Flush"; + case DWC3_DGCMD_SET_ENDPOINT_NRDY: + return "Set Endpoint NRDY"; + case DWC3_DGCMD_SET_ENDPOINT_PRIME: + return "Set Endpoint Prime"; + case DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK: + return "Run SoC Bus Loopback Test"; + case DWC3_DGCMD_DEV_NOTIFICATION: + return "Device Notification"; + default: + return "UNKNOWN"; + } +} + +/** + * dwc3_gadget_link_string - returns link name + * @link_state: link state code + */ +static inline const char * +dwc3_gadget_link_string(enum dwc3_link_state link_state) +{ + switch (link_state) { + case DWC3_LINK_STATE_U0: + return "U0"; + case DWC3_LINK_STATE_U1: + return "U1"; + case DWC3_LINK_STATE_U2: + return "U2"; + case DWC3_LINK_STATE_U3: + return "U3"; + case DWC3_LINK_STATE_SS_DIS: + return "SS.Disabled"; + case DWC3_LINK_STATE_RX_DET: + return "RX.Detect"; + case DWC3_LINK_STATE_SS_INACT: + return "SS.Inactive"; + case DWC3_LINK_STATE_POLL: + return "Polling"; + case DWC3_LINK_STATE_RECOV: + return "Recovery"; + case DWC3_LINK_STATE_HRESET: + return "Hot Reset"; + case DWC3_LINK_STATE_CMPLY: + return "Compliance"; + case DWC3_LINK_STATE_LPBK: + return "Loopback"; + case DWC3_LINK_STATE_RESET: + return "Reset"; + case DWC3_LINK_STATE_RESUME: + return "Resume"; + default: + return "UNKNOWN link state"; + } +} + +/** + * dwc3_gadget_hs_link_string - returns highspeed and below link name + * @link_state: link state code + */ +static inline const char * +dwc3_gadget_hs_link_string(enum dwc3_link_state link_state) +{ + switch (link_state) { + case DWC3_LINK_STATE_U0: + return "On"; + case DWC3_LINK_STATE_U2: + return "Sleep"; + case DWC3_LINK_STATE_U3: + return "Suspend"; + case DWC3_LINK_STATE_SS_DIS: + return "Disconnected"; + case DWC3_LINK_STATE_RX_DET: + return "Early Suspend"; + case DWC3_LINK_STATE_RECOV: + return "Recovery"; + case DWC3_LINK_STATE_RESET: + return "Reset"; + case DWC3_LINK_STATE_RESUME: + return "Resume"; + default: + return "UNKNOWN link state"; + } +} + +/** + * dwc3_trb_type_string - returns TRB type as a string + * @type: the type of the TRB + */ +static inline const char *dwc3_trb_type_string(unsigned int type) +{ + switch (type) { + case DWC3_TRBCTL_NORMAL: + return "normal"; + case DWC3_TRBCTL_CONTROL_SETUP: + return "setup"; + case DWC3_TRBCTL_CONTROL_STATUS2: + return "status2"; + case DWC3_TRBCTL_CONTROL_STATUS3: + return "status3"; + case DWC3_TRBCTL_CONTROL_DATA: + return "data"; + case DWC3_TRBCTL_ISOCHRONOUS_FIRST: + return "isoc-first"; + case DWC3_TRBCTL_ISOCHRONOUS: + return "isoc"; + case DWC3_TRBCTL_LINK_TRB: + return "link"; + default: + return "UNKNOWN"; + } +} + +static inline 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"; + } +} + +/** + * dwc3_gadget_event_string - returns event name + * @event: the event code + */ +static inline const char *dwc3_gadget_event_string(char *str, size_t size, + const struct dwc3_event_devt *event) +{ + enum dwc3_link_state state = event->event_info & DWC3_LINK_STATE_MASK; + + switch (event->type) { + case DWC3_DEVICE_EVENT_DISCONNECT: + snprintf(str, size, "Disconnect: [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_RESET: + snprintf(str, size, "Reset [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_CONNECT_DONE: + snprintf(str, size, "Connection Done [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE: + snprintf(str, size, "Link Change [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_WAKEUP: + snprintf(str, size, "WakeUp [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_SUSPEND: + snprintf(str, size, "Suspend [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_SOF: + snprintf(str, size, "Start-Of-Frame [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_ERRATIC_ERROR: + snprintf(str, size, "Erratic Error [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_CMD_CMPL: + snprintf(str, size, "Command Complete [%s]", + dwc3_gadget_link_string(state)); + break; + case DWC3_DEVICE_EVENT_OVERFLOW: + snprintf(str, size, "Overflow [%s]", + dwc3_gadget_link_string(state)); + break; + default: + snprintf(str, size, "UNKNOWN"); + } + + return str; +} + +/** + * dwc3_ep_event_string - returns event name + * @event: then event code + */ +static inline const char *dwc3_ep_event_string(char *str, size_t size, + const struct dwc3_event_depevt *event, u32 ep0state) +{ + u8 epnum = event->endpoint_number; + size_t len; + int status; + + len = scnprintf(str, size, "ep%d%s: ", epnum >> 1, + (epnum & 1) ? "in" : "out"); + + status = event->status; + + switch (event->endpoint_event) { + case DWC3_DEPEVT_XFERCOMPLETE: + len += scnprintf(str + len, size - len, + "Transfer Complete (%c%c%c)", + status & DEPEVT_STATUS_SHORT ? 'S' : 's', + status & DEPEVT_STATUS_IOC ? 'I' : 'i', + status & DEPEVT_STATUS_LST ? 'L' : 'l'); + + if (epnum <= 1) + scnprintf(str + len, size - len, " [%s]", + dwc3_ep0_state_string(ep0state)); + break; + case DWC3_DEPEVT_XFERINPROGRESS: + scnprintf(str + len, size - len, + "Transfer In Progress [%08x] (%c%c%c)", + event->parameters, + status & DEPEVT_STATUS_SHORT ? 'S' : 's', + status & DEPEVT_STATUS_IOC ? 'I' : 'i', + status & DEPEVT_STATUS_LST ? 'M' : 'm'); + break; + case DWC3_DEPEVT_XFERNOTREADY: + len += scnprintf(str + len, size - len, + "Transfer Not Ready [%08x]%s", + event->parameters, + status & DEPEVT_STATUS_TRANSFER_ACTIVE ? + " (Active)" : " (Not Active)"); + + /* Control Endpoints */ + if (epnum <= 1) { + int phase = DEPEVT_STATUS_CONTROL_PHASE(event->status); + + switch (phase) { + case DEPEVT_STATUS_CONTROL_DATA: + scnprintf(str + len, size - len, + " [Data Phase]"); + break; + case DEPEVT_STATUS_CONTROL_STATUS: + scnprintf(str + len, size - len, + " [Status Phase]"); + } + } + break; + case DWC3_DEPEVT_RXTXFIFOEVT: + scnprintf(str + len, size - len, "FIFO"); + break; + case DWC3_DEPEVT_STREAMEVT: + status = event->status; + + switch (status) { + case DEPEVT_STREAMEVT_FOUND: + scnprintf(str + len, size - len, " Stream %d Found", + event->parameters); + break; + case DEPEVT_STREAMEVT_NOTFOUND: + default: + scnprintf(str + len, size - len, " Stream Not Found"); + break; + } + + break; + case DWC3_DEPEVT_EPCMDCMPLT: + scnprintf(str + len, size - len, "Endpoint Command Complete"); + break; + default: + scnprintf(str + len, size - len, "UNKNOWN"); + } + + return str; +} + +/** + * dwc3_gadget_event_type_string - return event name + * @event: the event code + */ +static inline const char *dwc3_gadget_event_type_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 "Connect Done"; + case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE: + return "Link Status Change"; + case DWC3_DEVICE_EVENT_WAKEUP: + return "Wake-Up"; + case DWC3_DEVICE_EVENT_HIBER_REQ: + return "Hibernation"; + case DWC3_DEVICE_EVENT_SUSPEND: + return "Suspend"; + 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"; + default: + return "UNKNOWN"; + } +} + +static inline const char *dwc3_decode_event(char *str, size_t size, u32 event, + u32 ep0state) +{ + union dwc3_event evt; + + memcpy(&evt, &event, sizeof(event)); + + if (evt.type.is_devspec) + return dwc3_gadget_event_string(str, size, &evt.devt); + else + return dwc3_ep_event_string(str, size, &evt.depevt, ep0state); +} + +static inline const char *dwc3_ep_cmd_status_string(int status) +{ + switch (status) { + case -ETIMEDOUT: + return "Timed Out"; + case 0: + return "Successful"; + case DEPEVT_TRANSFER_NO_RESOURCE: + return "No Resource"; + case DEPEVT_TRANSFER_BUS_EXPIRY: + return "Bus Expiry"; + default: + return "UNKNOWN"; + } +} + +static inline const char *dwc3_gadget_generic_cmd_status_string(int status) +{ + switch (status) { + case -ETIMEDOUT: + return "Timed Out"; + case 0: + return "Successful"; + case 1: + return "Error"; + default: + return "UNKNOWN"; + } +} + + +#ifdef CONFIG_DEBUG_FS +extern void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep); +extern void dwc3_debugfs_remove_endpoint_dir(struct dwc3_ep *dep); +extern void dwc3_debugfs_init(struct dwc3 *d); +extern void dwc3_debugfs_exit(struct dwc3 *d); +#else +static inline void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep) +{ } +static inline void dwc3_debugfs_remove_endpoint_dir(struct dwc3_ep *dep) +{ } +static inline void dwc3_debugfs_init(struct dwc3 *d) +{ } +static inline void dwc3_debugfs_exit(struct dwc3 *d) +{ } +#endif +#endif /* __DWC3_DEBUG_H */ diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c new file mode 100644 index 000000000..65d909e4b --- /dev/null +++ b/drivers/usb/dwc3/debugfs.c @@ -0,0 +1,1039 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * debugfs.c - DesignWare USB3 DRD Controller DebugFS file + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "core.h" +#include "gadget.h" +#include "io.h" +#include "debug.h" + +#define DWC3_LSP_MUX_UNSELECTED 0xfffff + +#define dump_register(nm) \ +{ \ + .name = __stringify(nm), \ + .offset = DWC3_ ##nm, \ +} + +#define dump_ep_register_set(n) \ + { \ + .name = "DEPCMDPAR2("__stringify(n)")", \ + .offset = DWC3_DEPCMDPAR2(n), \ + }, \ + { \ + .name = "DEPCMDPAR1("__stringify(n)")", \ + .offset = DWC3_DEPCMDPAR1(n), \ + }, \ + { \ + .name = "DEPCMDPAR0("__stringify(n)")", \ + .offset = DWC3_DEPCMDPAR0(n), \ + }, \ + { \ + .name = "DEPCMD("__stringify(n)")", \ + .offset = DWC3_DEPCMD(n), \ + } + + +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(GUCTL1), + 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(GDBGBMU), + dump_register(GPRTBIMAP_HS0), + dump_register(GPRTBIMAP_HS1), + dump_register(GPRTBIMAP_FS0), + dump_register(GPRTBIMAP_FS1), + dump_register(GUCTL2), + dump_register(VER_NUMBER), + dump_register(VER_TYPE), + + 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(GUCTL3), + dump_register(GFLADJ), + dump_register(DCFG), + dump_register(DCTL), + dump_register(DEVTEN), + dump_register(DSTS), + dump_register(DGCMDPAR), + dump_register(DGCMD), + dump_register(DALEPENA), + + dump_ep_register_set(0), + dump_ep_register_set(1), + dump_ep_register_set(2), + dump_ep_register_set(3), + dump_ep_register_set(4), + dump_ep_register_set(5), + dump_ep_register_set(6), + dump_ep_register_set(7), + dump_ep_register_set(8), + dump_ep_register_set(9), + dump_ep_register_set(10), + dump_ep_register_set(11), + dump_ep_register_set(12), + dump_ep_register_set(13), + dump_ep_register_set(14), + dump_ep_register_set(15), + dump_ep_register_set(16), + dump_ep_register_set(17), + dump_ep_register_set(18), + dump_ep_register_set(19), + dump_ep_register_set(20), + dump_ep_register_set(21), + dump_ep_register_set(22), + dump_ep_register_set(23), + dump_ep_register_set(24), + dump_ep_register_set(25), + dump_ep_register_set(26), + dump_ep_register_set(27), + dump_ep_register_set(28), + dump_ep_register_set(29), + dump_ep_register_set(30), + dump_ep_register_set(31), + + dump_register(OCFG), + dump_register(OCTL), + dump_register(OEVT), + dump_register(OEVTEN), + dump_register(OSTS), +}; + +static void dwc3_host_lsp(struct seq_file *s) +{ + struct dwc3 *dwc = s->private; + bool dbc_enabled; + u32 sel; + u32 reg; + u32 val; + + dbc_enabled = !!(dwc->hwparams.hwparams1 & DWC3_GHWPARAMS1_ENDBC); + + sel = dwc->dbg_lsp_select; + if (sel == DWC3_LSP_MUX_UNSELECTED) { + seq_puts(s, "Write LSP selection to print for host\n"); + return; + } + + reg = DWC3_GDBGLSPMUX_HOSTSELECT(sel); + + dwc3_writel(dwc, DWC3_GDBGLSPMUX, reg); + val = dwc3_readl(dwc, DWC3_GDBGLSP); + seq_printf(s, "GDBGLSP[%d] = 0x%08x\n", sel, val); + + if (dbc_enabled && sel < 256) { + reg |= DWC3_GDBGLSPMUX_ENDBC; + dwc3_writel(dwc, DWC3_GDBGLSPMUX, reg); + val = dwc3_readl(dwc, DWC3_GDBGLSP); + seq_printf(s, "GDBGLSP_DBC[%d] = 0x%08x\n", sel, val); + } +} + +static void dwc3_gadget_lsp(struct seq_file *s) +{ + struct dwc3 *dwc = s->private; + int i; + u32 reg; + + for (i = 0; i < 16; i++) { + reg = DWC3_GDBGLSPMUX_DEVSELECT(i); + dwc3_writel(dwc, DWC3_GDBGLSPMUX, reg); + reg = dwc3_readl(dwc, DWC3_GDBGLSP); + seq_printf(s, "GDBGLSP[%d] = 0x%08x\n", i, reg); + } +} + +static int dwc3_lsp_show(struct seq_file *s, void *unused) +{ + struct dwc3 *dwc = s->private; + unsigned int current_mode; + unsigned long flags; + u32 reg; + int ret; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc, DWC3_GSTS); + current_mode = DWC3_GSTS_CURMOD(reg); + + switch (current_mode) { + case DWC3_GSTS_CURMOD_HOST: + dwc3_host_lsp(s); + break; + case DWC3_GSTS_CURMOD_DEVICE: + dwc3_gadget_lsp(s); + break; + default: + seq_puts(s, "Mode is unknown, no LSP register printed\n"); + break; + } + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_put_sync(dwc->dev); + + return 0; +} + +static int dwc3_lsp_open(struct inode *inode, struct file *file) +{ + return single_open(file, dwc3_lsp_show, inode->i_private); +} + +static ssize_t dwc3_lsp_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; + char buf[32] = { 0 }; + u32 sel; + int ret; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + ret = kstrtouint(buf, 0, &sel); + if (ret) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + dwc->dbg_lsp_select = sel; + spin_unlock_irqrestore(&dwc->lock, flags); + + return count; +} + +static const struct file_operations dwc3_lsp_fops = { + .open = dwc3_lsp_open, + .write = dwc3_lsp_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int dwc3_mode_show(struct seq_file *s, void *unused) +{ + struct dwc3 *dwc = s->private; + unsigned long flags; + u32 reg; + u32 mode; + int ret; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc, DWC3_GCTL); + spin_unlock_irqrestore(&dwc->lock, flags); + + mode = DWC3_GCTL_PRTCAP(reg); + switch (mode) { + case DWC3_GCTL_PRTCAP_HOST: + case DWC3_GCTL_PRTCAP_DEVICE: + case DWC3_GCTL_PRTCAP_OTG: + seq_printf(s, "%s\n", dwc3_mode_string(mode)); + break; + default: + seq_printf(s, "UNKNOWN %08x\n", mode); + } + + pm_runtime_put_sync(dwc->dev); + + 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; + u32 mode = 0; + char buf[32]; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (dwc->dr_mode != USB_DR_MODE_OTG) + return count; + + 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; + + dwc3_set_mode(dwc, mode); + + 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; + int ret; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc, DWC3_DCTL); + reg &= DWC3_DCTL_TSTCTRL_MASK; + reg >>= 1; + spin_unlock_irqrestore(&dwc->lock, flags); + + switch (reg) { + case 0: + seq_puts(s, "no test\n"); + break; + case USB_TEST_J: + seq_puts(s, "test_j\n"); + break; + case USB_TEST_K: + seq_puts(s, "test_k\n"); + break; + case USB_TEST_SE0_NAK: + seq_puts(s, "test_se0_nak\n"); + break; + case USB_TEST_PACKET: + seq_puts(s, "test_packet\n"); + break; + case USB_TEST_FORCE_ENABLE: + seq_puts(s, "test_force_enable\n"); + break; + default: + seq_printf(s, "UNKNOWN %d\n", reg); + } + + pm_runtime_put_sync(dwc->dev); + + 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]; + int ret; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "test_j", 6)) + testmode = USB_TEST_J; + else if (!strncmp(buf, "test_k", 6)) + testmode = USB_TEST_K; + else if (!strncmp(buf, "test_se0_nak", 12)) + testmode = USB_TEST_SE0_NAK; + else if (!strncmp(buf, "test_packet", 11)) + testmode = USB_TEST_PACKET; + else if (!strncmp(buf, "test_force_enable", 17)) + testmode = USB_TEST_FORCE_ENABLE; + else + testmode = 0; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + dwc3_gadget_set_test_mode(dwc, testmode); + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_put_sync(dwc->dev); + + 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; + u8 speed; + int ret; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc, DWC3_GSTS); + if (DWC3_GSTS_CURMOD(reg) != DWC3_GSTS_CURMOD_DEVICE) { + seq_puts(s, "Not available\n"); + spin_unlock_irqrestore(&dwc->lock, flags); + pm_runtime_put_sync(dwc->dev); + return 0; + } + + reg = dwc3_readl(dwc, DWC3_DSTS); + state = DWC3_DSTS_USBLNKST(reg); + speed = reg & DWC3_DSTS_CONNECTSPD; + + seq_printf(s, "%s\n", (speed >= DWC3_DSTS_SUPERSPEED) ? + dwc3_gadget_link_string(state) : + dwc3_gadget_hs_link_string(state)); + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_put_sync(dwc->dev); + + 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]; + u32 reg; + u8 speed; + int ret; + + 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; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc, DWC3_GSTS); + if (DWC3_GSTS_CURMOD(reg) != DWC3_GSTS_CURMOD_DEVICE) { + spin_unlock_irqrestore(&dwc->lock, flags); + pm_runtime_put_sync(dwc->dev); + return -EINVAL; + } + + reg = dwc3_readl(dwc, DWC3_DSTS); + speed = reg & DWC3_DSTS_CONNECTSPD; + + if (speed < DWC3_DSTS_SUPERSPEED && + state != DWC3_LINK_STATE_RECOV) { + spin_unlock_irqrestore(&dwc->lock, flags); + pm_runtime_put_sync(dwc->dev); + return -EINVAL; + } + + dwc3_gadget_set_link_state(dwc, state); + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_put_sync(dwc->dev); + + 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, +}; + +struct dwc3_ep_file_map { + const char name[25]; + const struct file_operations *const fops; +}; + +static int dwc3_tx_fifo_size_show(struct seq_file *s, void *unused) +{ + struct dwc3_ep *dep = s->private; + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + u32 mdwidth; + u32 val; + int ret; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + val = dwc3_core_fifo_space(dep, DWC3_TXFIFO); + + /* Convert to bytes */ + mdwidth = dwc3_mdwidth(dwc); + + val *= mdwidth; + val >>= 3; + seq_printf(s, "%u\n", val); + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_put_sync(dwc->dev); + + return 0; +} + +static int dwc3_rx_fifo_size_show(struct seq_file *s, void *unused) +{ + struct dwc3_ep *dep = s->private; + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + u32 mdwidth; + u32 val; + int ret; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + val = dwc3_core_fifo_space(dep, DWC3_RXFIFO); + + /* Convert to bytes */ + mdwidth = dwc3_mdwidth(dwc); + + val *= mdwidth; + val >>= 3; + seq_printf(s, "%u\n", val); + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_put_sync(dwc->dev); + + return 0; +} + +static int dwc3_tx_request_queue_show(struct seq_file *s, void *unused) +{ + struct dwc3_ep *dep = s->private; + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + u32 val; + int ret; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + val = dwc3_core_fifo_space(dep, DWC3_TXREQQ); + seq_printf(s, "%u\n", val); + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_put_sync(dwc->dev); + + return 0; +} + +static int dwc3_rx_request_queue_show(struct seq_file *s, void *unused) +{ + struct dwc3_ep *dep = s->private; + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + u32 val; + int ret; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + val = dwc3_core_fifo_space(dep, DWC3_RXREQQ); + seq_printf(s, "%u\n", val); + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_put_sync(dwc->dev); + + return 0; +} + +static int dwc3_rx_info_queue_show(struct seq_file *s, void *unused) +{ + struct dwc3_ep *dep = s->private; + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + u32 val; + int ret; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + val = dwc3_core_fifo_space(dep, DWC3_RXINFOQ); + seq_printf(s, "%u\n", val); + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_put_sync(dwc->dev); + + return 0; +} + +static int dwc3_descriptor_fetch_queue_show(struct seq_file *s, void *unused) +{ + struct dwc3_ep *dep = s->private; + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + u32 val; + int ret; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + val = dwc3_core_fifo_space(dep, DWC3_DESCFETCHQ); + seq_printf(s, "%u\n", val); + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_put_sync(dwc->dev); + + return 0; +} + +static int dwc3_event_queue_show(struct seq_file *s, void *unused) +{ + struct dwc3_ep *dep = s->private; + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + u32 val; + int ret; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + val = dwc3_core_fifo_space(dep, DWC3_EVENTQ); + seq_printf(s, "%u\n", val); + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_put_sync(dwc->dev); + + return 0; +} + +static int dwc3_transfer_type_show(struct seq_file *s, void *unused) +{ + struct dwc3_ep *dep = s->private; + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + if (!(dep->flags & DWC3_EP_ENABLED) || !dep->endpoint.desc) { + seq_puts(s, "--\n"); + goto out; + } + + switch (usb_endpoint_type(dep->endpoint.desc)) { + case USB_ENDPOINT_XFER_CONTROL: + seq_puts(s, "control\n"); + break; + case USB_ENDPOINT_XFER_ISOC: + seq_puts(s, "isochronous\n"); + break; + case USB_ENDPOINT_XFER_BULK: + seq_puts(s, "bulk\n"); + break; + case USB_ENDPOINT_XFER_INT: + seq_puts(s, "interrupt\n"); + break; + default: + seq_puts(s, "--\n"); + } + +out: + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + +static int dwc3_trb_ring_show(struct seq_file *s, void *unused) +{ + struct dwc3_ep *dep = s->private; + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + int i; + int ret; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + if (dep->number <= 1) { + seq_puts(s, "--\n"); + goto out; + } + + seq_puts(s, "buffer_addr,size,type,ioc,isp_imi,csp,chn,lst,hwo\n"); + + for (i = 0; i < DWC3_TRB_NUM; i++) { + struct dwc3_trb *trb = &dep->trb_pool[i]; + unsigned int type = DWC3_TRBCTL_TYPE(trb->ctrl); + + seq_printf(s, "%08x%08x,%d,%s,%d,%d,%d,%d,%d,%d %c%c\n", + trb->bph, trb->bpl, trb->size, + dwc3_trb_type_string(type), + !!(trb->ctrl & DWC3_TRB_CTRL_IOC), + !!(trb->ctrl & DWC3_TRB_CTRL_ISP_IMI), + !!(trb->ctrl & DWC3_TRB_CTRL_CSP), + !!(trb->ctrl & DWC3_TRB_CTRL_CHN), + !!(trb->ctrl & DWC3_TRB_CTRL_LST), + !!(trb->ctrl & DWC3_TRB_CTRL_HWO), + dep->trb_enqueue == i ? 'E' : ' ', + dep->trb_dequeue == i ? 'D' : ' '); + } + +out: + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_put_sync(dwc->dev); + + return 0; +} + +static int dwc3_ep_info_register_show(struct seq_file *s, void *unused) +{ + struct dwc3_ep *dep = s->private; + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + u64 ep_info; + u32 lower_32_bits; + u32 upper_32_bits; + u32 reg; + int ret; + + ret = pm_runtime_resume_and_get(dwc->dev); + if (ret < 0) + return ret; + + spin_lock_irqsave(&dwc->lock, flags); + reg = DWC3_GDBGLSPMUX_EPSELECT(dep->number); + dwc3_writel(dwc, DWC3_GDBGLSPMUX, reg); + + lower_32_bits = dwc3_readl(dwc, DWC3_GDBGEPINFO0); + upper_32_bits = dwc3_readl(dwc, DWC3_GDBGEPINFO1); + + ep_info = ((u64)upper_32_bits << 32) | lower_32_bits; + seq_printf(s, "0x%016llx\n", ep_info); + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_put_sync(dwc->dev); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(dwc3_tx_fifo_size); +DEFINE_SHOW_ATTRIBUTE(dwc3_rx_fifo_size); +DEFINE_SHOW_ATTRIBUTE(dwc3_tx_request_queue); +DEFINE_SHOW_ATTRIBUTE(dwc3_rx_request_queue); +DEFINE_SHOW_ATTRIBUTE(dwc3_rx_info_queue); +DEFINE_SHOW_ATTRIBUTE(dwc3_descriptor_fetch_queue); +DEFINE_SHOW_ATTRIBUTE(dwc3_event_queue); +DEFINE_SHOW_ATTRIBUTE(dwc3_transfer_type); +DEFINE_SHOW_ATTRIBUTE(dwc3_trb_ring); +DEFINE_SHOW_ATTRIBUTE(dwc3_ep_info_register); + +static const struct dwc3_ep_file_map dwc3_ep_file_map[] = { + { "tx_fifo_size", &dwc3_tx_fifo_size_fops, }, + { "rx_fifo_size", &dwc3_rx_fifo_size_fops, }, + { "tx_request_queue", &dwc3_tx_request_queue_fops, }, + { "rx_request_queue", &dwc3_rx_request_queue_fops, }, + { "rx_info_queue", &dwc3_rx_info_queue_fops, }, + { "descriptor_fetch_queue", &dwc3_descriptor_fetch_queue_fops, }, + { "event_queue", &dwc3_event_queue_fops, }, + { "transfer_type", &dwc3_transfer_type_fops, }, + { "trb_ring", &dwc3_trb_ring_fops, }, + { "GDBGEPINFO", &dwc3_ep_info_register_fops, }, +}; + +void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep) +{ + struct dentry *dir; + int i; + + dir = debugfs_create_dir(dep->name, dep->dwc->debug_root); + for (i = 0; i < ARRAY_SIZE(dwc3_ep_file_map); i++) { + const struct file_operations *fops = dwc3_ep_file_map[i].fops; + const char *name = dwc3_ep_file_map[i].name; + + debugfs_create_file(name, 0444, dir, dep, fops); + } +} + +void dwc3_debugfs_remove_endpoint_dir(struct dwc3_ep *dep) +{ + debugfs_lookup_and_remove(dep->name, dep->dwc->debug_root); +} + +void dwc3_debugfs_init(struct dwc3 *dwc) +{ + struct dentry *root; + + dwc->regset = kzalloc_obj(*dwc->regset); + if (!dwc->regset) + return; + + dwc->dbg_lsp_select = DWC3_LSP_MUX_UNSELECTED; + + dwc->regset->regs = dwc3_regs; + dwc->regset->nregs = ARRAY_SIZE(dwc3_regs); + dwc->regset->base = dwc->regs - DWC3_GLOBALS_REGS_START; + dwc->regset->dev = dwc->dev; + + root = debugfs_create_dir(dev_name(dwc->dev), usb_debug_root); + dwc->debug_root = root; + debugfs_create_regset32("regdump", 0444, root, dwc->regset); + debugfs_create_file("lsp_dump", 0644, root, dwc, &dwc3_lsp_fops); + + if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)) + debugfs_create_file("mode", 0644, root, dwc, + &dwc3_mode_fops); + + if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) || + IS_ENABLED(CONFIG_USB_DWC3_GADGET)) { + debugfs_create_file("testmode", 0644, root, dwc, + &dwc3_testmode_fops); + debugfs_create_file("link_state", 0644, root, dwc, + &dwc3_link_state_fops); + } +} + +void dwc3_debugfs_exit(struct dwc3 *dwc) +{ + debugfs_lookup_and_remove(dev_name(dwc->dev), usb_debug_root); + kfree(dwc->regset); +} diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c new file mode 100644 index 000000000..f3e37d383 --- /dev/null +++ b/drivers/usb/dwc3/drd.c @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drd.c - DesignWare USB3 DRD Controller Dual-role support + * + * Copyright (C) 2017 Texas Instruments Incorporated - https://www.ti.com + * + * Authors: Roger Quadros + */ + +#include +#include +#include +#include + +#include "debug.h" +#include "core.h" +#include "gadget.h" + +static void dwc3_otg_disable_events(struct dwc3 *dwc, u32 disable_mask) +{ + u32 reg = dwc3_readl(dwc, DWC3_OEVTEN); + + reg &= ~(disable_mask); + dwc3_writel(dwc, DWC3_OEVTEN, reg); +} + +static void dwc3_otg_enable_events(struct dwc3 *dwc, u32 enable_mask) +{ + u32 reg = dwc3_readl(dwc, DWC3_OEVTEN); + + reg |= (enable_mask); + dwc3_writel(dwc, DWC3_OEVTEN, reg); +} + +static void dwc3_otg_clear_events(struct dwc3 *dwc) +{ + u32 reg = dwc3_readl(dwc, DWC3_OEVT); + + dwc3_writel(dwc, DWC3_OEVTEN, reg); +} + +#define DWC3_OTG_ALL_EVENTS (DWC3_OEVTEN_XHCIRUNSTPSETEN | \ + DWC3_OEVTEN_DEVRUNSTPSETEN | DWC3_OEVTEN_HIBENTRYEN | \ + DWC3_OEVTEN_CONIDSTSCHNGEN | DWC3_OEVTEN_HRRCONFNOTIFEN | \ + DWC3_OEVTEN_HRRINITNOTIFEN | DWC3_OEVTEN_ADEVIDLEEN | \ + DWC3_OEVTEN_ADEVBHOSTENDEN | DWC3_OEVTEN_ADEVHOSTEN | \ + DWC3_OEVTEN_ADEVHNPCHNGEN | DWC3_OEVTEN_ADEVSRPDETEN | \ + DWC3_OEVTEN_ADEVSESSENDDETEN | DWC3_OEVTEN_BDEVBHOSTENDEN | \ + DWC3_OEVTEN_BDEVHNPCHNGEN | DWC3_OEVTEN_BDEVSESSVLDDETEN | \ + DWC3_OEVTEN_BDEVVBUSCHNGEN) + +static irqreturn_t dwc3_otg_thread_irq(int irq, void *_dwc) +{ + struct dwc3 *dwc = _dwc; + + spin_lock(&dwc->lock); + if (dwc->otg_restart_host) { + dwc3_otg_host_init(dwc); + dwc->otg_restart_host = false; + } + + spin_unlock(&dwc->lock); + + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); + + return IRQ_HANDLED; +} + +static irqreturn_t dwc3_otg_irq(int irq, void *_dwc) +{ + u32 reg; + struct dwc3 *dwc = _dwc; + irqreturn_t ret = IRQ_NONE; + + reg = dwc3_readl(dwc, DWC3_OEVT); + if (reg) { + /* ignore non OTG events, we can't disable them in OEVTEN */ + if (!(reg & DWC3_OTG_ALL_EVENTS)) { + dwc3_writel(dwc, DWC3_OEVT, reg); + return IRQ_NONE; + } + + if (dwc->current_otg_role == DWC3_OTG_ROLE_HOST && + !(reg & DWC3_OEVT_DEVICEMODE)) + dwc->otg_restart_host = true; + dwc3_writel(dwc, DWC3_OEVT, reg); + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +static void dwc3_otgregs_init(struct dwc3 *dwc) +{ + u32 reg; + + /* + * Prevent host/device reset from resetting OTG core. + * If we don't do this then xhci_reset (USBCMD.HCRST) will reset + * the signal outputs sent to the PHY, the OTG FSM logic of the + * core and also the resets to the VBUS filters inside the core. + */ + reg = dwc3_readl(dwc, DWC3_OCFG); + reg |= DWC3_OCFG_SFTRSTMASK; + dwc3_writel(dwc, DWC3_OCFG, reg); + + /* Disable hibernation for simplicity */ + reg = dwc3_readl(dwc, DWC3_GCTL); + reg &= ~DWC3_GCTL_GBLHIBERNATIONEN; + dwc3_writel(dwc, DWC3_GCTL, reg); + + /* + * Initialize OTG registers as per + * Figure 11-4 OTG Driver Overall Programming Flow + */ + /* OCFG.SRPCap = 0, OCFG.HNPCap = 0 */ + reg = dwc3_readl(dwc, DWC3_OCFG); + reg &= ~(DWC3_OCFG_SRPCAP | DWC3_OCFG_HNPCAP); + dwc3_writel(dwc, DWC3_OCFG, reg); + /* OEVT = FFFF */ + dwc3_otg_clear_events(dwc); + /* OEVTEN = 0 */ + dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); + /* OEVTEN.ConIDStsChngEn = 1. Instead we enable all events */ + dwc3_otg_enable_events(dwc, DWC3_OTG_ALL_EVENTS); + /* + * OCTL.PeriMode = 1, OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0, + * OCTL.HNPReq = 0 + */ + reg = dwc3_readl(dwc, DWC3_OCTL); + reg |= DWC3_OCTL_PERIMODE; + reg &= ~(DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN | + DWC3_OCTL_HNPREQ); + dwc3_writel(dwc, DWC3_OCTL, reg); +} + +static int dwc3_otg_get_irq(struct dwc3 *dwc) +{ + struct platform_device *dwc3_pdev = to_platform_device(dwc->dev); + int irq; + + irq = platform_get_irq_byname_optional(dwc3_pdev, "otg"); + if (irq > 0) + goto out; + + if (irq == -EPROBE_DEFER) + goto out; + + irq = platform_get_irq_byname_optional(dwc3_pdev, "dwc_usb3"); + if (irq > 0) + goto out; + + if (irq == -EPROBE_DEFER) + goto out; + + irq = platform_get_irq(dwc3_pdev, 0); + if (irq > 0) + goto out; + + if (!irq) + irq = -EINVAL; + +out: + return irq; +} + +void dwc3_otg_init(struct dwc3 *dwc) +{ + u32 reg; + + /* + * As per Figure 11-4 OTG Driver Overall Programming Flow, + * block "Initialize GCTL for OTG operation". + */ + /* GCTL.PrtCapDir=2'b11 */ + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG, true); + /* GUSB2PHYCFG0.SusPHY=0 */ + reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(0)); + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc, DWC3_GUSB2PHYCFG(0), reg); + + /* Initialize OTG registers */ + dwc3_otgregs_init(dwc); +} + +void dwc3_otg_exit(struct dwc3 *dwc) +{ + /* disable all OTG IRQs */ + dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); + /* clear all events */ + dwc3_otg_clear_events(dwc); +} + +/* should be called before Host controller driver is started */ +void dwc3_otg_host_init(struct dwc3 *dwc) +{ + u32 reg; + + /* As per Figure 11-10 A-Device Flow Diagram */ + /* OCFG.HNPCap = 0, OCFG.SRPCap = 0. Already 0 */ + + /* + * OCTL.PeriMode=0, OCTL.TermSelDLPulse = 0, + * OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0 + */ + reg = dwc3_readl(dwc, DWC3_OCTL); + reg &= ~(DWC3_OCTL_PERIMODE | DWC3_OCTL_TERMSELIDPULSE | + DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN); + dwc3_writel(dwc, DWC3_OCTL, reg); + + /* + * OCFG.DisPrtPwrCutoff = 0/1 + */ + reg = dwc3_readl(dwc, DWC3_OCFG); + reg &= ~DWC3_OCFG_DISPWRCUTTOFF; + dwc3_writel(dwc, DWC3_OCFG, reg); + + /* + * OCFG.SRPCap = 1, OCFG.HNPCap = GHWPARAMS6.HNP_CAP + * We don't want SRP/HNP for simple dual-role so leave + * these disabled. + */ + + /* + * OEVTEN.OTGADevHostEvntEn = 1 + * OEVTEN.OTGADevSessEndDetEvntEn = 1 + * We don't want HNP/role-swap so leave these disabled. + */ + + /* GUSB2PHYCFG.ULPIAutoRes = 1/0, GUSB2PHYCFG.SusPHY = 1 */ + if (!dwc->dis_u2_susphy_quirk) { + reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(0)); + reg |= DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc, DWC3_GUSB2PHYCFG(0), reg); + } + + /* Set Port Power to enable VBUS: OCTL.PrtPwrCtl = 1 */ + reg = dwc3_readl(dwc, DWC3_OCTL); + reg |= DWC3_OCTL_PRTPWRCTL; + dwc3_writel(dwc, DWC3_OCTL, reg); +} + +/* should be called after Host controller driver is stopped */ +static void dwc3_otg_host_exit(struct dwc3 *dwc) +{ + u32 reg; + + /* + * Exit from A-device flow as per + * Figure 11-4 OTG Driver Overall Programming Flow + */ + + /* + * OEVTEN.OTGADevBHostEndEvntEn=0, OEVTEN.OTGADevHNPChngEvntEn=0 + * OEVTEN.OTGADevSessEndDetEvntEn=0, + * OEVTEN.OTGADevHostEvntEn = 0 + * But we don't disable any OTG events + */ + + /* OCTL.HstSetHNPEn = 0, OCTL.PrtPwrCtl=0 */ + reg = dwc3_readl(dwc, DWC3_OCTL); + reg &= ~(DWC3_OCTL_HSTSETHNPEN | DWC3_OCTL_PRTPWRCTL); + dwc3_writel(dwc, DWC3_OCTL, reg); +} + +/* should be called before the gadget controller driver is started */ +static void dwc3_otg_device_init(struct dwc3 *dwc) +{ + u32 reg; + + /* As per Figure 11-20 B-Device Flow Diagram */ + + /* + * OCFG.HNPCap = GHWPARAMS6.HNP_CAP, OCFG.SRPCap = 1 + * but we keep them 0 for simple dual-role operation. + */ + reg = dwc3_readl(dwc, DWC3_OCFG); + /* OCFG.OTGSftRstMsk = 0/1 */ + reg |= DWC3_OCFG_SFTRSTMASK; + dwc3_writel(dwc, DWC3_OCFG, reg); + /* + * OCTL.PeriMode = 1 + * OCTL.TermSelDLPulse = 0/1, OCTL.HNPReq = 0 + * OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0 + */ + reg = dwc3_readl(dwc, DWC3_OCTL); + reg |= DWC3_OCTL_PERIMODE; + reg &= ~(DWC3_OCTL_TERMSELIDPULSE | DWC3_OCTL_HNPREQ | + DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN); + dwc3_writel(dwc, DWC3_OCTL, reg); + /* OEVTEN.OTGBDevSesVldDetEvntEn = 1 */ + dwc3_otg_enable_events(dwc, DWC3_OEVTEN_BDEVSESSVLDDETEN); + /* GUSB2PHYCFG.ULPIAutoRes = 0, GUSB2PHYCFG0.SusPHY = 1 */ + if (!dwc->dis_u2_susphy_quirk) { + reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(0)); + reg |= DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc, DWC3_GUSB2PHYCFG(0), reg); + } + /* GCTL.GblHibernationEn = 0. Already 0. */ +} + +/* should be called after the gadget controller driver is stopped */ +static void dwc3_otg_device_exit(struct dwc3 *dwc) +{ + u32 reg; + + /* + * Exit from B-device flow as per + * Figure 11-4 OTG Driver Overall Programming Flow + */ + + /* + * OEVTEN.OTGBDevHNPChngEvntEn = 0 + * OEVTEN.OTGBDevVBusChngEvntEn = 0 + * OEVTEN.OTGBDevBHostEndEvntEn = 0 + */ + dwc3_otg_disable_events(dwc, DWC3_OEVTEN_BDEVHNPCHNGEN | + DWC3_OEVTEN_BDEVVBUSCHNGEN | + DWC3_OEVTEN_BDEVBHOSTENDEN); + + /* OCTL.DevSetHNPEn = 0, OCTL.HNPReq = 0, OCTL.PeriMode=1 */ + reg = dwc3_readl(dwc, DWC3_OCTL); + reg &= ~(DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HNPREQ); + reg |= DWC3_OCTL_PERIMODE; + dwc3_writel(dwc, DWC3_OCTL, reg); +} + +void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus) +{ + int ret; + u32 reg; + int id; + unsigned long flags; + int i; + + if (dwc->dr_mode != USB_DR_MODE_OTG) + return; + + /* don't do anything if debug user changed role to not OTG */ + if (dwc->current_dr_role != DWC3_GCTL_PRTCAP_OTG) + return; + + if (!ignore_idstatus) { + reg = dwc3_readl(dwc, DWC3_OSTS); + id = !!(reg & DWC3_OSTS_CONIDSTS); + + dwc->desired_otg_role = id ? DWC3_OTG_ROLE_DEVICE : + DWC3_OTG_ROLE_HOST; + } + + if (dwc->desired_otg_role == dwc->current_otg_role) + return; + + switch (dwc->current_otg_role) { + case DWC3_OTG_ROLE_HOST: + dwc3_host_exit(dwc); + spin_lock_irqsave(&dwc->lock, flags); + dwc3_otg_host_exit(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + break; + case DWC3_OTG_ROLE_DEVICE: + dwc3_gadget_exit(dwc); + spin_lock_irqsave(&dwc->lock, flags); + dwc3_event_buffers_cleanup(dwc); + dwc3_otg_device_exit(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + break; + default: + break; + } + + spin_lock_irqsave(&dwc->lock, flags); + + dwc->current_otg_role = dwc->desired_otg_role; + + spin_unlock_irqrestore(&dwc->lock, flags); + + switch (dwc->desired_otg_role) { + case DWC3_OTG_ROLE_HOST: + spin_lock_irqsave(&dwc->lock, flags); + dwc3_otgregs_init(dwc); + dwc3_otg_host_init(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + dwc3_pre_set_role(dwc, USB_ROLE_HOST); + ret = dwc3_host_init(dwc); + if (ret) { + dev_err(dwc->dev, "failed to initialize host\n"); + } else { + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, true); + for (i = 0; i < dwc->num_usb2_ports; i++) { + if (dwc->usb2_generic_phy[i]) { + phy_set_mode(dwc->usb2_generic_phy[i], + PHY_MODE_USB_HOST); + } + } + } + break; + case DWC3_OTG_ROLE_DEVICE: + spin_lock_irqsave(&dwc->lock, flags); + dwc3_otgregs_init(dwc); + dwc3_otg_device_init(dwc); + dwc3_event_buffers_setup(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, false); + if (dwc->usb2_generic_phy[0]) + phy_set_mode(dwc->usb2_generic_phy[0], PHY_MODE_USB_DEVICE); + dwc3_pre_set_role(dwc, USB_ROLE_DEVICE); + ret = dwc3_gadget_init(dwc); + if (ret) + dev_err(dwc->dev, "failed to initialize peripheral\n"); + break; + default: + break; + } +} + +static void dwc3_drd_update(struct dwc3 *dwc) +{ + int id; + + if (dwc->edev) { + id = extcon_get_state(dwc->edev, EXTCON_USB_HOST); + if (id < 0) + id = 0; + dwc3_set_mode(dwc, id ? + DWC3_GCTL_PRTCAP_HOST : + DWC3_GCTL_PRTCAP_DEVICE); + } +} + +static int dwc3_drd_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb); + u32 mode = event ? DWC3_GCTL_PRTCAP_HOST : DWC3_GCTL_PRTCAP_DEVICE; + enum usb_role role = mode == DWC3_GCTL_PRTCAP_HOST ? + USB_ROLE_HOST : USB_ROLE_DEVICE; + + dwc3_pre_set_role(dwc, role); + dwc3_set_mode(dwc, mode); + + return NOTIFY_DONE; +} + +#if IS_ENABLED(CONFIG_USB_ROLE_SWITCH) +#define ROLE_SWITCH 1 +static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, + enum usb_role role) +{ + struct dwc3 *dwc = usb_role_switch_get_drvdata(sw); + u32 mode; + + switch (role) { + case USB_ROLE_HOST: + mode = DWC3_GCTL_PRTCAP_HOST; + break; + case USB_ROLE_DEVICE: + mode = DWC3_GCTL_PRTCAP_DEVICE; + break; + default: + if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) + mode = DWC3_GCTL_PRTCAP_HOST; + else + mode = DWC3_GCTL_PRTCAP_DEVICE; + break; + } + + dwc3_pre_set_role(dwc, role); + dwc3_set_mode(dwc, mode); + return 0; +} + +static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw) +{ + struct dwc3 *dwc = usb_role_switch_get_drvdata(sw); + unsigned long flags; + enum usb_role role; + + spin_lock_irqsave(&dwc->lock, flags); + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_HOST: + role = USB_ROLE_HOST; + break; + case DWC3_GCTL_PRTCAP_DEVICE: + role = USB_ROLE_DEVICE; + break; + case DWC3_GCTL_PRTCAP_OTG: + role = dwc->current_otg_role; + break; + default: + if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) + role = USB_ROLE_HOST; + else + role = USB_ROLE_DEVICE; + break; + } + spin_unlock_irqrestore(&dwc->lock, flags); + return role; +} + +static int dwc3_setup_role_switch(struct dwc3 *dwc) +{ + struct usb_role_switch_desc dwc3_role_switch = {NULL}; + u32 mode; + + dwc->role_switch_default_mode = usb_get_role_switch_default_mode(dwc->dev); + if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) { + mode = DWC3_GCTL_PRTCAP_HOST; + } else { + dwc->role_switch_default_mode = USB_DR_MODE_PERIPHERAL; + mode = DWC3_GCTL_PRTCAP_DEVICE; + } + dwc3_set_mode(dwc, mode); + + dwc3_role_switch.fwnode = dev_fwnode(dwc->dev); + dwc3_role_switch.set = dwc3_usb_role_switch_set; + dwc3_role_switch.get = dwc3_usb_role_switch_get; + dwc3_role_switch.driver_data = dwc; + dwc3_role_switch.allow_userspace_control = true; + dwc->role_sw = usb_role_switch_register(dwc->dev, &dwc3_role_switch); + if (IS_ERR(dwc->role_sw)) + return PTR_ERR(dwc->role_sw); + + if (dwc->dev->of_node) { + /* populate connector entry */ + int ret = devm_of_platform_populate(dwc->dev); + + if (ret) { + usb_role_switch_unregister(dwc->role_sw); + dwc->role_sw = NULL; + dev_err(dwc->dev, "DWC3 platform devices creation failed: %i\n", ret); + return ret; + } + } + + return 0; +} +#else +#define ROLE_SWITCH 0 +#define dwc3_setup_role_switch(x) 0 +#endif + +int dwc3_drd_init(struct dwc3 *dwc) +{ + int ret, irq; + + if (ROLE_SWITCH && + device_property_read_bool(dwc->dev, "usb-role-switch")) + return dwc3_setup_role_switch(dwc); + + if (dwc->edev) { + dwc->edev_nb.notifier_call = dwc3_drd_notifier; + ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + if (ret < 0) { + dev_err(dwc->dev, "couldn't register cable notifier\n"); + return ret; + } + + dwc3_drd_update(dwc); + } else { + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG, true); + + /* use OTG block to get ID event */ + irq = dwc3_otg_get_irq(dwc); + if (irq < 0) + return irq; + + dwc->otg_irq = irq; + + /* disable all OTG IRQs */ + dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); + /* clear all events */ + dwc3_otg_clear_events(dwc); + + ret = request_threaded_irq(dwc->otg_irq, dwc3_otg_irq, + dwc3_otg_thread_irq, + IRQF_SHARED, "dwc3-otg", dwc); + if (ret) { + dev_err(dwc->dev, "failed to request irq #%d --> %d\n", + dwc->otg_irq, ret); + ret = -ENODEV; + return ret; + } + + dwc3_otg_init(dwc); + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); + } + + return 0; +} + +void dwc3_drd_exit(struct dwc3 *dwc) +{ + unsigned long flags; + + if (dwc->role_sw) + usb_role_switch_unregister(dwc->role_sw); + + if (dwc->edev) + extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + + cancel_work_sync(&dwc->drd_work); + + /* debug user might have changed role, clean based on current role */ + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_HOST: + dwc3_host_exit(dwc); + break; + case DWC3_GCTL_PRTCAP_DEVICE: + dwc3_gadget_exit(dwc); + dwc3_event_buffers_cleanup(dwc); + break; + case DWC3_GCTL_PRTCAP_OTG: + dwc3_otg_exit(dwc); + spin_lock_irqsave(&dwc->lock, flags); + dwc->desired_otg_role = DWC3_OTG_ROLE_IDLE; + spin_unlock_irqrestore(&dwc->lock, flags); + dwc3_otg_update(dwc, 1); + break; + default: + break; + } + + if (dwc->otg_irq) + free_irq(dwc->otg_irq, dwc); +} diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c new file mode 100644 index 000000000..632634d6e --- /dev/null +++ b/drivers/usb/dwc3/dwc3-am62.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-am62.c - TI specific Glue layer for AM62 DWC3 USB Controller + * + * Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" + +/* USB WRAPPER register offsets */ +#define USBSS_PID 0x0 +#define USBSS_OVERCURRENT_CTRL 0x4 +#define USBSS_PHY_CONFIG 0x8 +#define USBSS_PHY_TEST 0xc +#define USBSS_CORE_STAT 0x14 +#define USBSS_HOST_VBUS_CTRL 0x18 +#define USBSS_MODE_CONTROL 0x1c +#define USBSS_WAKEUP_CONFIG 0x30 +#define USBSS_WAKEUP_STAT 0x34 +#define USBSS_OVERRIDE_CONFIG 0x38 +#define USBSS_IRQ_MISC_STATUS_RAW 0x430 +#define USBSS_IRQ_MISC_STATUS 0x434 +#define USBSS_IRQ_MISC_ENABLE_SET 0x438 +#define USBSS_IRQ_MISC_ENABLE_CLR 0x43c +#define USBSS_IRQ_MISC_EOI 0x440 +#define USBSS_INTR_TEST 0x490 +#define USBSS_VBUS_FILTER 0x614 +#define USBSS_VBUS_STAT 0x618 +#define USBSS_DEBUG_CFG 0x708 +#define USBSS_DEBUG_DATA 0x70c +#define USBSS_HOST_HUB_CTRL 0x714 + +/* PHY CONFIG register bits */ +#define USBSS_PHY_VBUS_SEL_MASK GENMASK(2, 1) +#define USBSS_PHY_VBUS_SEL_SHIFT 1 +#define USBSS_PHY_LANE_REVERSE BIT(0) + +/* CORE STAT register bits */ +#define USBSS_CORE_OPERATIONAL_MODE_MASK GENMASK(13, 12) +#define USBSS_CORE_OPERATIONAL_MODE_SHIFT 12 + +/* MODE CONTROL register bits */ +#define USBSS_MODE_VALID BIT(0) + +/* WAKEUP CONFIG register bits */ +#define USBSS_WAKEUP_CFG_OVERCURRENT_EN BIT(3) +#define USBSS_WAKEUP_CFG_LINESTATE_EN BIT(2) +#define USBSS_WAKEUP_CFG_SESSVALID_EN BIT(1) +#define USBSS_WAKEUP_CFG_VBUSVALID_EN BIT(0) + +#define USBSS_WAKEUP_CFG_ALL (USBSS_WAKEUP_CFG_VBUSVALID_EN | \ + USBSS_WAKEUP_CFG_SESSVALID_EN | \ + USBSS_WAKEUP_CFG_LINESTATE_EN | \ + USBSS_WAKEUP_CFG_OVERCURRENT_EN) + +#define USBSS_WAKEUP_CFG_NONE 0 + +/* WAKEUP STAT register bits */ +#define USBSS_WAKEUP_STAT_OVERCURRENT BIT(4) +#define USBSS_WAKEUP_STAT_LINESTATE BIT(3) +#define USBSS_WAKEUP_STAT_SESSVALID BIT(2) +#define USBSS_WAKEUP_STAT_VBUSVALID BIT(1) +#define USBSS_WAKEUP_STAT_CLR BIT(0) + +/* IRQ_MISC_STATUS_RAW register bits */ +#define USBSS_IRQ_MISC_RAW_VBUSVALID BIT(22) +#define USBSS_IRQ_MISC_RAW_SESSVALID BIT(20) + +/* IRQ_MISC_STATUS register bits */ +#define USBSS_IRQ_MISC_VBUSVALID BIT(22) +#define USBSS_IRQ_MISC_SESSVALID BIT(20) + +/* IRQ_MISC_ENABLE_SET register bits */ +#define USBSS_IRQ_MISC_ENABLE_SET_VBUSVALID BIT(22) +#define USBSS_IRQ_MISC_ENABLE_SET_SESSVALID BIT(20) + +/* IRQ_MISC_ENABLE_CLR register bits */ +#define USBSS_IRQ_MISC_ENABLE_CLR_VBUSVALID BIT(22) +#define USBSS_IRQ_MISC_ENABLE_CLR_SESSVALID BIT(20) + +/* IRQ_MISC_EOI register bits */ +#define USBSS_IRQ_MISC_EOI_VECTOR BIT(0) + +/* VBUS_STAT register bits */ +#define USBSS_VBUS_STAT_SESSVALID BIT(2) +#define USBSS_VBUS_STAT_VBUSVALID BIT(0) + +/* USB_PHY_CTRL register bits in CTRL_MMR */ +#define PHY_CORE_VOLTAGE_MASK BIT(31) +#define PHY_PLL_REFCLK_MASK GENMASK(3, 0) + +/* USB PHY2 register offsets */ +#define USB_PHY_PLL_REG12 0x130 +#define USB_PHY_PLL_LDO_REF_EN BIT(5) +#define USB_PHY_PLL_LDO_REF_EN_EN BIT(4) + +#define DWC3_AM62_AUTOSUSPEND_DELAY 100 + +#define USBSS_DEBUG_CFG_OFF 0x0 +#define USBSS_DEBUG_CFG_DISABLED 0x7 + +struct dwc3_am62 { + struct device *dev; + void __iomem *usbss; + struct clk *usb2_refclk; + int rate_code; + struct regmap *syscon; + unsigned int offset; + unsigned int vbus_divider; + u32 wakeup_stat; + void __iomem *phy_regs; +}; + +static const int dwc3_ti_rate_table[] = { /* in KHZ */ + 9600, + 10000, + 12000, + 19200, + 20000, + 24000, + 25000, + 26000, + 38400, + 40000, + 58000, + 50000, + 52000, +}; + +static inline u32 dwc3_ti_readl(struct dwc3_am62 *am62, u32 offset) +{ + return readl((am62->usbss) + offset); +} + +static inline void dwc3_ti_writel(struct dwc3_am62 *am62, u32 offset, u32 value) +{ + writel(value, (am62->usbss) + offset); +} + +static int phy_syscon_pll_refclk(struct dwc3_am62 *am62) +{ + struct device *dev = am62->dev; + struct device_node *node = dev->of_node; + struct regmap *syscon; + int ret; + + syscon = syscon_regmap_lookup_by_phandle_args(node, "ti,syscon-phy-pll-refclk", + 1, &am62->offset); + if (IS_ERR(syscon)) { + dev_err(dev, "unable to get ti,syscon-phy-pll-refclk regmap\n"); + return PTR_ERR(syscon); + } + + am62->syscon = syscon; + + /* Core voltage. PHY_CORE_VOLTAGE bit Recommended to be 0 always */ + ret = regmap_update_bits(am62->syscon, am62->offset, PHY_CORE_VOLTAGE_MASK, 0); + if (ret) { + dev_err(dev, "failed to set phy core voltage\n"); + return ret; + } + + ret = regmap_update_bits(am62->syscon, am62->offset, PHY_PLL_REFCLK_MASK, am62->rate_code); + if (ret) { + dev_err(dev, "failed to set phy pll reference clock rate\n"); + return ret; + } + + return 0; +} + +static int dwc3_ti_init(struct dwc3_am62 *am62) +{ + int ret; + u32 reg; + + /* Read the syscon property and set the rate code */ + ret = phy_syscon_pll_refclk(am62); + if (ret) + return ret; + + /* Workaround Errata i2409 */ + if (am62->phy_regs) { + reg = readl(am62->phy_regs + USB_PHY_PLL_REG12); + reg |= USB_PHY_PLL_LDO_REF_EN | USB_PHY_PLL_LDO_REF_EN_EN; + writel(reg, am62->phy_regs + USB_PHY_PLL_REG12); + } + + /* VBUS divider select */ + reg = dwc3_ti_readl(am62, USBSS_PHY_CONFIG); + if (am62->vbus_divider) + reg |= 1 << USBSS_PHY_VBUS_SEL_SHIFT; + + dwc3_ti_writel(am62, USBSS_PHY_CONFIG, reg); + + ret = clk_prepare_enable(am62->usb2_refclk); + if (ret) + return ret; + + /* Set mode valid bit to indicate role is valid */ + reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL); + reg |= USBSS_MODE_VALID; + dwc3_ti_writel(am62, USBSS_MODE_CONTROL, reg); + + return 0; +} + +static int dwc3_ti_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = pdev->dev.of_node; + struct dwc3_am62 *am62; + unsigned long rate; + int i, ret; + + am62 = devm_kzalloc(dev, sizeof(*am62), GFP_KERNEL); + if (!am62) + return -ENOMEM; + + am62->dev = dev; + platform_set_drvdata(pdev, am62); + + am62->usbss = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(am62->usbss)) { + dev_err(dev, "can't map IOMEM resource\n"); + return PTR_ERR(am62->usbss); + } + + am62->usb2_refclk = devm_clk_get(dev, "ref"); + if (IS_ERR(am62->usb2_refclk)) { + dev_err(dev, "can't get usb2_refclk\n"); + return PTR_ERR(am62->usb2_refclk); + } + + /* Calculate the rate code */ + rate = clk_get_rate(am62->usb2_refclk); + rate /= 1000; // To KHz + for (i = 0; i < ARRAY_SIZE(dwc3_ti_rate_table); i++) { + if (dwc3_ti_rate_table[i] == rate) + break; + } + + if (i == ARRAY_SIZE(dwc3_ti_rate_table)) { + dev_err(dev, "unsupported usb2_refclk rate: %lu KHz\n", rate); + return -EINVAL; + } + + am62->rate_code = i; + + am62->phy_regs = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(am62->phy_regs)) { + dev_err(dev, "can't map PHY IOMEM resource. Won't apply i2409 fix.\n"); + am62->phy_regs = NULL; + } + + am62->vbus_divider = device_property_read_bool(dev, "ti,vbus-divider"); + + ret = dwc3_ti_init(am62); + if (ret) + return ret; + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + /* + * Don't ignore its dependencies with its children + */ + pm_suspend_ignore_children(dev, false); + pm_runtime_get_noresume(dev); + + ret = of_platform_populate(node, NULL, NULL, dev); + if (ret) { + dev_err(dev, "failed to create dwc3 core: %d\n", ret); + goto err_pm_disable; + } + + /* Device has capability to wakeup system from sleep */ + device_set_wakeup_capable(dev, true); + ret = device_wakeup_enable(dev); + if (ret) + dev_err(dev, "couldn't enable device as a wakeup source: %d\n", ret); + + /* Setting up autosuspend */ + pm_runtime_set_autosuspend_delay(dev, DWC3_AM62_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(dev); + pm_runtime_put_autosuspend(dev); + return 0; + +err_pm_disable: + clk_disable_unprepare(am62->usb2_refclk); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + return ret; +} + +static void dwc3_ti_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dwc3_am62 *am62 = platform_get_drvdata(pdev); + u32 reg; + + pm_runtime_get_sync(dev); + device_init_wakeup(dev, false); + of_platform_depopulate(dev); + + /* Clear mode valid bit */ + reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL); + reg &= ~USBSS_MODE_VALID; + dwc3_ti_writel(am62, USBSS_MODE_CONTROL, reg); + + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_set_suspended(dev); +} + +#ifdef CONFIG_PM +static int dwc3_ti_suspend_common(struct device *dev) +{ + struct dwc3_am62 *am62 = dev_get_drvdata(dev); + u32 reg, current_prtcap_dir; + + if (device_may_wakeup(dev)) { + reg = dwc3_ti_readl(am62, USBSS_CORE_STAT); + current_prtcap_dir = (reg & USBSS_CORE_OPERATIONAL_MODE_MASK) + >> USBSS_CORE_OPERATIONAL_MODE_SHIFT; + /* Set wakeup config enable bits */ + reg = dwc3_ti_readl(am62, USBSS_WAKEUP_CONFIG); + if (current_prtcap_dir == DWC3_GCTL_PRTCAP_HOST) { + reg = USBSS_WAKEUP_CFG_LINESTATE_EN | USBSS_WAKEUP_CFG_OVERCURRENT_EN; + } else { + reg = USBSS_WAKEUP_CFG_VBUSVALID_EN | USBSS_WAKEUP_CFG_SESSVALID_EN; + /* + * Enable LINESTATE wake up only if connected to bus + * and in U2/L3 state else it causes spurious wake-up. + */ + } + dwc3_ti_writel(am62, USBSS_WAKEUP_CONFIG, reg); + /* clear wakeup status so we know what caused the wake up */ + dwc3_ti_writel(am62, USBSS_WAKEUP_STAT, USBSS_WAKEUP_STAT_CLR); + } + + /* just to track if module resets on suspend */ + dwc3_ti_writel(am62, USBSS_DEBUG_CFG, USBSS_DEBUG_CFG_DISABLED); + + clk_disable_unprepare(am62->usb2_refclk); + + return 0; +} + +static int dwc3_ti_resume_common(struct device *dev) +{ + struct dwc3_am62 *am62 = dev_get_drvdata(dev); + u32 reg; + int ret; + + reg = dwc3_ti_readl(am62, USBSS_DEBUG_CFG); + if (reg != USBSS_DEBUG_CFG_DISABLED) { + /* lost power/context */ + ret = dwc3_ti_init(am62); + if (ret) + return ret; + } else { + dwc3_ti_writel(am62, USBSS_DEBUG_CFG, USBSS_DEBUG_CFG_OFF); + ret = clk_prepare_enable(am62->usb2_refclk); + if (ret) + return ret; + } + + if (device_may_wakeup(dev)) { + /* Clear wakeup config enable bits */ + dwc3_ti_writel(am62, USBSS_WAKEUP_CONFIG, USBSS_WAKEUP_CFG_NONE); + } + + reg = dwc3_ti_readl(am62, USBSS_WAKEUP_STAT); + am62->wakeup_stat = reg; + + return 0; +} + +static UNIVERSAL_DEV_PM_OPS(dwc3_ti_pm_ops, dwc3_ti_suspend_common, + dwc3_ti_resume_common, NULL); + +#define DEV_PM_OPS (&dwc3_ti_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + +static const struct of_device_id dwc3_ti_of_match[] = { + { .compatible = "ti,am62-usb"}, + {}, +}; +MODULE_DEVICE_TABLE(of, dwc3_ti_of_match); + +static struct platform_driver dwc3_ti_driver = { + .probe = dwc3_ti_probe, + .remove = dwc3_ti_remove, + .driver = { + .name = "dwc3-am62", + .pm = DEV_PM_OPS, + .of_match_table = dwc3_ti_of_match, + }, +}; + +module_platform_driver(dwc3_ti_driver); + +MODULE_ALIAS("platform:dwc3-am62"); +MODULE_AUTHOR("Aswath Govindraju "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DesignWare USB3 TI Glue Layer"); diff --git a/drivers/usb/dwc3/dwc3-apple.c b/drivers/usb/dwc3/dwc3-apple.c new file mode 100644 index 000000000..40c3ccfdd --- /dev/null +++ b/drivers/usb/dwc3/dwc3-apple.c @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Apple Silicon DWC3 Glue driver + * Copyright (C) The Asahi Linux Contributors + * + * Based on: + * - dwc3-qcom.c Copyright (c) 2018, The Linux Foundation. All rights reserved. + * - dwc3-of-simple.c Copyright (c) 2015 Texas Instruments Incorporated - https://www.ti.com + */ + +#include +#include +#include +#include +#include + +#include "glue.h" + +/* + * This platform requires a very specific sequence of operations to bring up dwc3 and its USB3 PHY: + * + * 1) The PHY itself has to be brought up; for this we need to know the mode (USB3, + * USB3+DisplayPort, USB4, etc) and the lane orientation. This happens through typec_mux_set. + * 2) DWC3 has to be brought up but we must not touch the gadget area or start xhci yet. + * 3) The PHY bring-up has to be finalized and dwc3's PIPE interface has to be switched to the + * USB3 PHY, this is done inside phy_set_mode. + * 4) We can now initialize xhci or gadget mode. + * + * We can switch 1 and 2 but 3 has to happen after (1 and 2) and 4 has to happen after 3. + * + * And then to bring this all down again: + * + * 1) DWC3 has to exit host or gadget mode and must no longer touch those registers + * 2) The PHY has to switch dwc3's PIPE interface back to the dummy backend + * 3) The PHY itself can be shut down, this happens from typec_mux_set + * + * We also can't transition the PHY from one mode to another while dwc3 is up and running (this is + * slightly wrong, some transitions are possible, others aren't but because we have no documentation + * for this I'd rather play it safe). + * + * After both the PHY and dwc3 are initialized we will only ever see a single "new device connected" + * event. If we just keep them running only the first device plugged in will ever work. XHCI's port + * status register actually does show the correct state but no interrupt ever comes in. In gadget + * mode we don't even get a USBDisconnected event and everything looks like there's still something + * connected on the other end. + * This can be partially explained because the USB2 D+/D- lines are connected through a stateful + * eUSB2 repeater which in turn is controlled by a variant of the TI TPS6598x USB PD chip which + * resets the repeater out-of-band everytime the CC lines are (dis)connected. This then requires a + * PHY reset to make sure the PHY and the eUSB2 repeater state are synchronized again. + * + * And to make this all extra fun: If we get the order of some of this wrong either the port is just + * broken until a phy+dwc3 reset, or it's broken until a full SoC reset (likely because we can't + * reset some parts of the PHY), or some watchdog kicks in after a few seconds and forces a full SoC + * reset (mostly seen this with USB4/Thunderbolt but there's clearly some watchdog that hates + * invalid states). + * + * Hence there's really no good way to keep dwc3 fully up and running after we disconnect a cable + * because then we can't shut down the PHY anymore. And if we kept the PHY running in whatever mode + * it was until the next cable is connected we'd need to tear it all down and bring it back up again + * anyway to detect and use the next device. + * + * Instead, we just shut down everything when a cable is disconnected and transition to + * DWC3_APPLE_NO_CABLE. + * During initial probe we don't have any information about the connected cable and can't bring up + * the PHY properly and thus also can't fully bring up dwc3. Instead, we just keep everything off + * and defer the first dwc3 probe until we get the first cable connected event. Until then we stay + * in DWC3_APPLE_PROBE_PENDING. + * Once a cable is connected we then keep track of the controller mode here by transitioning to + * DWC3_APPLE_HOST or DWC3_APPLE_DEVICE. + */ +enum dwc3_apple_state { + DWC3_APPLE_PROBE_PENDING, /* Before first cable connection, dwc3_core_probe not called */ + DWC3_APPLE_NO_CABLE, /* No cable connected, dwc3 suspended after dwc3_core_exit */ + DWC3_APPLE_HOST, /* Cable connected, dwc3 in host mode */ + DWC3_APPLE_DEVICE, /* Cable connected, dwc3 in device mode */ +}; + +/** + * struct dwc3_apple - Apple-specific DWC3 USB controller + * @dwc: Core DWC3 structure + * @dev: Pointer to the device structure + * @mmio_resource: Resource to be passed to dwc3_core_probe + * @apple_regs: Apple-specific DWC3 registers + * @reset: Reset control + * @role_sw: USB role switch + * @lock: Mutex for synchronizing access + * @state: Current state of the controller, see documentation for the enum for details + */ +struct dwc3_apple { + struct dwc3 dwc; + + struct device *dev; + struct resource *mmio_resource; + void __iomem *apple_regs; + + struct reset_control *reset; + struct usb_role_switch *role_sw; + + struct mutex lock; + + enum dwc3_apple_state state; +}; + +#define to_dwc3_apple(d) container_of((d), struct dwc3_apple, dwc) + +/* + * Apple Silicon dwc3 vendor-specific registers + * + * These registers were identified by tracing XNU's memory access patterns and correlating them with + * debug output over serial to determine their names. We don't exactly know what these do but + * without these USB3 devices sometimes don't work. + */ +#define APPLE_DWC3_REGS_START 0xcd00 +#define APPLE_DWC3_REGS_END 0xcdff + +#define APPLE_DWC3_CIO_LFPS_OFFSET 0xcd38 +#define APPLE_DWC3_CIO_LFPS_OFFSET_VALUE 0xf800f80 + +#define APPLE_DWC3_CIO_BW_NGT_OFFSET 0xcd3c +#define APPLE_DWC3_CIO_BW_NGT_OFFSET_VALUE 0xfc00fc0 + +#define APPLE_DWC3_CIO_LINK_TIMER 0xcd40 +#define APPLE_DWC3_CIO_PENDING_HP_TIMER GENMASK(23, 16) +#define APPLE_DWC3_CIO_PENDING_HP_TIMER_VALUE 0x14 +#define APPLE_DWC3_CIO_PM_LC_TIMER GENMASK(15, 8) +#define APPLE_DWC3_CIO_PM_LC_TIMER_VALUE 0xa +#define APPLE_DWC3_CIO_PM_ENTRY_TIMER GENMASK(7, 0) +#define APPLE_DWC3_CIO_PM_ENTRY_TIMER_VALUE 0x10 + +static inline void dwc3_apple_writel(struct dwc3_apple *appledwc, u32 offset, u32 value) +{ + writel(value, appledwc->apple_regs + offset - APPLE_DWC3_REGS_START); +} + +static inline u32 dwc3_apple_readl(struct dwc3_apple *appledwc, u32 offset) +{ + return readl(appledwc->apple_regs + offset - APPLE_DWC3_REGS_START); +} + +static inline void dwc3_apple_mask(struct dwc3_apple *appledwc, u32 offset, u32 mask, u32 value) +{ + u32 reg; + + reg = dwc3_apple_readl(appledwc, offset); + reg &= ~mask; + reg |= value; + dwc3_apple_writel(appledwc, offset, reg); +} + +static void dwc3_apple_setup_cio(struct dwc3_apple *appledwc) +{ + dwc3_apple_writel(appledwc, APPLE_DWC3_CIO_LFPS_OFFSET, APPLE_DWC3_CIO_LFPS_OFFSET_VALUE); + dwc3_apple_writel(appledwc, APPLE_DWC3_CIO_BW_NGT_OFFSET, + APPLE_DWC3_CIO_BW_NGT_OFFSET_VALUE); + dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PENDING_HP_TIMER, + FIELD_PREP(APPLE_DWC3_CIO_PENDING_HP_TIMER, + APPLE_DWC3_CIO_PENDING_HP_TIMER_VALUE)); + dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PM_LC_TIMER, + FIELD_PREP(APPLE_DWC3_CIO_PM_LC_TIMER, APPLE_DWC3_CIO_PM_LC_TIMER_VALUE)); + dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PM_ENTRY_TIMER, + FIELD_PREP(APPLE_DWC3_CIO_PM_ENTRY_TIMER, + APPLE_DWC3_CIO_PM_ENTRY_TIMER_VALUE)); +} + +static void dwc3_apple_set_ptrcap(struct dwc3_apple *appledwc, u32 mode) +{ + guard(spinlock_irqsave)(&appledwc->dwc.lock); + dwc3_set_prtcap(&appledwc->dwc, mode, false); +} + +static int dwc3_apple_core_probe(struct dwc3_apple *appledwc) +{ + struct dwc3_probe_data probe_data = {}; + int ret; + + lockdep_assert_held(&appledwc->lock); + WARN_ON_ONCE(appledwc->state != DWC3_APPLE_PROBE_PENDING); + + appledwc->dwc.dev = appledwc->dev; + probe_data.dwc = &appledwc->dwc; + probe_data.res = appledwc->mmio_resource; + probe_data.ignore_clocks_and_resets = true; + probe_data.skip_core_init_mode = true; + probe_data.properties = DWC3_DEFAULT_PROPERTIES; + + ret = dwc3_core_probe(&probe_data); + if (ret) + return ret; + + appledwc->state = DWC3_APPLE_NO_CABLE; + return 0; +} + +static int dwc3_apple_core_init(struct dwc3_apple *appledwc) +{ + int ret; + + lockdep_assert_held(&appledwc->lock); + + switch (appledwc->state) { + case DWC3_APPLE_PROBE_PENDING: + ret = dwc3_apple_core_probe(appledwc); + if (ret) + dev_err(appledwc->dev, "Failed to probe DWC3 Core, err=%d\n", ret); + break; + case DWC3_APPLE_NO_CABLE: + ret = dwc3_core_init(&appledwc->dwc); + if (ret) + dev_err(appledwc->dev, "Failed to initialize DWC3 Core, err=%d\n", ret); + break; + default: + /* Unreachable unless there's a bug in this driver */ + WARN_ON_ONCE(1); + ret = -EINVAL; + break; + } + + return ret; +} + +static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state state) +{ + int ret, ret_reset; + + lockdep_assert_held(&appledwc->lock); + + /* + * The USB2 PHY on this platform must be configured for host or device mode while it is + * still powered off and before dwc3 tries to access it. Otherwise, the new configuration + * will sometimes only take affect after the *next* time dwc3 is brought up which causes + * the connected device to just not work. + * The USB3 PHY must be configured later after dwc3 has already been initialized. + */ + switch (state) { + case DWC3_APPLE_HOST: + phy_set_mode(appledwc->dwc.usb2_generic_phy[0], PHY_MODE_USB_HOST); + break; + case DWC3_APPLE_DEVICE: + phy_set_mode(appledwc->dwc.usb2_generic_phy[0], PHY_MODE_USB_DEVICE); + break; + default: + /* Unreachable unless there's a bug in this driver */ + return -EINVAL; + } + + ret = reset_control_deassert(appledwc->reset); + if (ret) { + dev_err(appledwc->dev, "Failed to deassert reset, err=%d\n", ret); + return ret; + } + + ret = dwc3_apple_core_init(appledwc); + if (ret) + goto reset_assert; + + /* + * Now that the core is initialized and already went through dwc3_core_soft_reset we can + * configure some unknown Apple-specific settings and then bring up xhci or gadget mode. + */ + dwc3_apple_setup_cio(appledwc); + + switch (state) { + case DWC3_APPLE_HOST: + appledwc->dwc.dr_mode = USB_DR_MODE_HOST; + dwc3_apple_set_ptrcap(appledwc, DWC3_GCTL_PRTCAP_HOST); + /* + * This platform requires SUSPHY to be enabled here already in order to properly + * configure the PHY and switch dwc3's PIPE interface to USB3 PHY. The USB2 PHY + * has already been configured to the correct mode earlier. + */ + dwc3_enable_susphy(&appledwc->dwc, true); + phy_set_mode(appledwc->dwc.usb3_generic_phy[0], PHY_MODE_USB_HOST); + ret = dwc3_host_init(&appledwc->dwc); + if (ret) { + dev_err(appledwc->dev, "Failed to initialize host, ret=%d\n", ret); + goto core_exit; + } + + break; + case DWC3_APPLE_DEVICE: + appledwc->dwc.dr_mode = USB_DR_MODE_PERIPHERAL; + dwc3_apple_set_ptrcap(appledwc, DWC3_GCTL_PRTCAP_DEVICE); + /* + * This platform requires SUSPHY to be enabled here already in order to properly + * configure the PHY and switch dwc3's PIPE interface to USB3 PHY. The USB2 PHY + * has already been configured to the correct mode earlier. + */ + dwc3_enable_susphy(&appledwc->dwc, true); + phy_set_mode(appledwc->dwc.usb3_generic_phy[0], PHY_MODE_USB_DEVICE); + ret = dwc3_gadget_init(&appledwc->dwc); + if (ret) { + dev_err(appledwc->dev, "Failed to initialize gadget, ret=%d\n", ret); + goto core_exit; + } + break; + default: + /* Unreachable unless there's a bug in this driver */ + WARN_ON_ONCE(1); + ret = -EINVAL; + goto core_exit; + } + + appledwc->state = state; + return 0; + +core_exit: + dwc3_core_exit(&appledwc->dwc); +reset_assert: + ret_reset = reset_control_assert(appledwc->reset); + if (ret_reset) + dev_warn(appledwc->dev, "Failed to assert reset, err=%d\n", ret_reset); + + return ret; +} + +static int dwc3_apple_exit(struct dwc3_apple *appledwc) +{ + int ret = 0; + + lockdep_assert_held(&appledwc->lock); + + switch (appledwc->state) { + case DWC3_APPLE_PROBE_PENDING: + case DWC3_APPLE_NO_CABLE: + /* Nothing to do if we're already off */ + return 0; + case DWC3_APPLE_DEVICE: + dwc3_gadget_exit(&appledwc->dwc); + break; + case DWC3_APPLE_HOST: + dwc3_host_exit(&appledwc->dwc); + break; + } + + /* + * This platform requires SUSPHY to be enabled in order to properly power down the PHY + * and switch dwc3's PIPE interface back to a dummy PHY (i.e. no USB3 support and USB2 via + * a different PHY connected through ULPI). + */ + dwc3_enable_susphy(&appledwc->dwc, true); + dwc3_core_exit(&appledwc->dwc); + appledwc->state = DWC3_APPLE_NO_CABLE; + + ret = reset_control_assert(appledwc->reset); + if (ret) { + dev_err(appledwc->dev, "Failed to assert reset, err=%d\n", ret); + return ret; + } + + return 0; +} + +static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role) +{ + struct dwc3_apple *appledwc = usb_role_switch_get_drvdata(sw); + int ret; + + guard(mutex)(&appledwc->lock); + + /* + * Skip role switches if appledwc is already in the desired state. The + * USB-C port controller on M2 and M1/M2 Pro/Max/Ultra devices issues + * additional interrupts which results in usb_role_switch_set_role() + * calls with the current role. + * Ignore those calls here to ensure the USB-C port controller and + * appledwc are in a consistent state. + * This matches the behaviour in __dwc3_set_mode(). + * Do no handle USB_ROLE_NONE for DWC3_APPLE_NO_CABLE and + * DWC3_APPLE_PROBE_PENDING since that is no-op anyway. + */ + if (appledwc->state == DWC3_APPLE_HOST && role == USB_ROLE_HOST) + return 0; + if (appledwc->state == DWC3_APPLE_DEVICE && role == USB_ROLE_DEVICE) + return 0; + + /* + * We need to tear all of dwc3 down and re-initialize it every time a cable is + * connected or disconnected or when the mode changes. See the documentation for enum + * dwc3_apple_state for details. + */ + ret = dwc3_apple_exit(appledwc); + if (ret) + return ret; + + switch (role) { + case USB_ROLE_NONE: + /* Nothing to do if no cable is connected */ + return 0; + case USB_ROLE_HOST: + return dwc3_apple_init(appledwc, DWC3_APPLE_HOST); + case USB_ROLE_DEVICE: + return dwc3_apple_init(appledwc, DWC3_APPLE_DEVICE); + default: + dev_err(appledwc->dev, "Invalid target role: %d\n", role); + return -EINVAL; + } +} + +static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw) +{ + struct dwc3_apple *appledwc = usb_role_switch_get_drvdata(sw); + + guard(mutex)(&appledwc->lock); + + switch (appledwc->state) { + case DWC3_APPLE_HOST: + return USB_ROLE_HOST; + case DWC3_APPLE_DEVICE: + return USB_ROLE_DEVICE; + case DWC3_APPLE_NO_CABLE: + case DWC3_APPLE_PROBE_PENDING: + return USB_ROLE_NONE; + default: + /* Unreachable unless there's a bug in this driver */ + dev_err(appledwc->dev, "Invalid internal state: %d\n", appledwc->state); + return USB_ROLE_NONE; + } +} + +static int dwc3_apple_setup_role_switch(struct dwc3_apple *appledwc) +{ + struct usb_role_switch_desc dwc3_role_switch = { NULL }; + + dwc3_role_switch.fwnode = dev_fwnode(appledwc->dev); + dwc3_role_switch.set = dwc3_usb_role_switch_set; + dwc3_role_switch.get = dwc3_usb_role_switch_get; + dwc3_role_switch.driver_data = appledwc; + appledwc->role_sw = usb_role_switch_register(appledwc->dev, &dwc3_role_switch); + if (IS_ERR(appledwc->role_sw)) + return PTR_ERR(appledwc->role_sw); + + return 0; +} + +static int dwc3_apple_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dwc3_apple *appledwc; + int ret; + + appledwc = devm_kzalloc(&pdev->dev, sizeof(*appledwc), GFP_KERNEL); + if (!appledwc) + return -ENOMEM; + + appledwc->dev = &pdev->dev; + mutex_init(&appledwc->lock); + + appledwc->reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(appledwc->reset)) + return dev_err_probe(&pdev->dev, PTR_ERR(appledwc->reset), + "Failed to get reset control\n"); + + ret = reset_control_assert(appledwc->reset); + if (ret) { + dev_err(&pdev->dev, "Failed to assert reset, err=%d\n", ret); + return ret; + } + + appledwc->mmio_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dwc3-core"); + if (!appledwc->mmio_resource) { + dev_err(dev, "Failed to get DWC3 MMIO\n"); + return -EINVAL; + } + + appledwc->apple_regs = devm_platform_ioremap_resource_byname(pdev, "dwc3-apple"); + if (IS_ERR(appledwc->apple_regs)) + return dev_err_probe(dev, PTR_ERR(appledwc->apple_regs), + "Failed to map Apple-specific MMIO\n"); + + /* + * On this platform, DWC3 can only be brought up after parts of the PHY have been + * initialized with knowledge of the target mode and cable orientation from typec_set_mux. + * Since this has not happened here we cannot setup DWC3 yet and instead defer this until + * the first cable is connected. See the documentation for enum dwc3_apple_state for + * details. + */ + appledwc->state = DWC3_APPLE_PROBE_PENDING; + ret = dwc3_apple_setup_role_switch(appledwc); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to setup role switch\n"); + + return 0; +} + +static void dwc3_apple_remove(struct platform_device *pdev) +{ + struct dwc3 *dwc = platform_get_drvdata(pdev); + struct dwc3_apple *appledwc = to_dwc3_apple(dwc); + + guard(mutex)(&appledwc->lock); + + usb_role_switch_unregister(appledwc->role_sw); + + /* + * If we're still in DWC3_APPLE_PROBE_PENDING we never got any cable connected event and + * dwc3_core_probe was never called and there's hence no need to call dwc3_core_remove. + * dwc3_apple_exit can be called unconditionally because it checks the state itself. + */ + dwc3_apple_exit(appledwc); + if (appledwc->state != DWC3_APPLE_PROBE_PENDING) + dwc3_core_remove(&appledwc->dwc); +} + +static const struct of_device_id dwc3_apple_of_match[] = { + { .compatible = "apple,t8103-dwc3" }, + {} +}; +MODULE_DEVICE_TABLE(of, dwc3_apple_of_match); + +static struct platform_driver dwc3_apple_driver = { + .probe = dwc3_apple_probe, + .remove = dwc3_apple_remove, + .driver = { + .name = "dwc3-apple", + .of_match_table = dwc3_apple_of_match, + }, +}; + +module_platform_driver(dwc3_apple_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sven Peter "); +MODULE_DESCRIPTION("DesignWare DWC3 Apple Silicon Glue Driver"); diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c new file mode 100644 index 000000000..e934f94e8 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-exynos.c - Samsung Exynos DWC3 Specific Glue layer + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Anton Tikhomirov + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DWC3_EXYNOS_MAX_CLOCKS 4 + +struct dwc3_exynos_driverdata { + const char *clk_names[DWC3_EXYNOS_MAX_CLOCKS]; + int num_clks; + int suspend_clk_idx; +}; + +struct dwc3_exynos { + struct device *dev; + + const char **clk_names; + struct clk *clks[DWC3_EXYNOS_MAX_CLOCKS]; + int num_clks; + int suspend_clk_idx; + + struct regulator *vdd33; + struct regulator *vdd10; +}; + +static int dwc3_exynos_probe(struct platform_device *pdev) +{ + struct dwc3_exynos *exynos; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + const struct dwc3_exynos_driverdata *driver_data; + int i, ret; + + exynos = devm_kzalloc(dev, sizeof(*exynos), GFP_KERNEL); + if (!exynos) + return -ENOMEM; + + driver_data = of_device_get_match_data(dev); + exynos->dev = dev; + exynos->num_clks = driver_data->num_clks; + exynos->clk_names = (const char **)driver_data->clk_names; + exynos->suspend_clk_idx = driver_data->suspend_clk_idx; + + platform_set_drvdata(pdev, exynos); + + for (i = 0; i < exynos->num_clks; i++) { + exynos->clks[i] = devm_clk_get(dev, exynos->clk_names[i]); + if (IS_ERR(exynos->clks[i])) { + dev_err(dev, "failed to get clock: %s\n", + exynos->clk_names[i]); + return PTR_ERR(exynos->clks[i]); + } + } + + for (i = 0; i < exynos->num_clks; i++) { + ret = clk_prepare_enable(exynos->clks[i]); + if (ret) { + while (i-- > 0) + clk_disable_unprepare(exynos->clks[i]); + return ret; + } + } + + if (exynos->suspend_clk_idx >= 0) + clk_prepare_enable(exynos->clks[exynos->suspend_clk_idx]); + + exynos->vdd33 = devm_regulator_get(dev, "vdd33"); + if (IS_ERR(exynos->vdd33)) { + ret = PTR_ERR(exynos->vdd33); + goto vdd33_err; + } + ret = regulator_enable(exynos->vdd33); + if (ret) { + dev_err(dev, "Failed to enable VDD33 supply\n"); + goto vdd33_err; + } + + exynos->vdd10 = devm_regulator_get(dev, "vdd10"); + if (IS_ERR(exynos->vdd10)) { + ret = PTR_ERR(exynos->vdd10); + goto vdd10_err; + } + ret = regulator_enable(exynos->vdd10); + if (ret) { + dev_err(dev, "Failed to enable VDD10 supply\n"); + goto vdd10_err; + } + + if (node) { + ret = of_platform_populate(node, NULL, NULL, dev); + if (ret) { + dev_err(dev, "failed to add dwc3 core\n"); + goto populate_err; + } + } else { + dev_err(dev, "no device node, failed to add dwc3 core\n"); + ret = -ENODEV; + goto populate_err; + } + + return 0; + +populate_err: + regulator_disable(exynos->vdd10); +vdd10_err: + regulator_disable(exynos->vdd33); +vdd33_err: + for (i = exynos->num_clks - 1; i >= 0; i--) + clk_disable_unprepare(exynos->clks[i]); + + if (exynos->suspend_clk_idx >= 0) + clk_disable_unprepare(exynos->clks[exynos->suspend_clk_idx]); + + return ret; +} + +static void dwc3_exynos_remove(struct platform_device *pdev) +{ + struct dwc3_exynos *exynos = platform_get_drvdata(pdev); + int i; + + of_platform_depopulate(&pdev->dev); + + for (i = exynos->num_clks - 1; i >= 0; i--) + clk_disable_unprepare(exynos->clks[i]); + + if (exynos->suspend_clk_idx >= 0) + clk_disable_unprepare(exynos->clks[exynos->suspend_clk_idx]); + + regulator_disable(exynos->vdd33); + regulator_disable(exynos->vdd10); +} + +static const struct dwc3_exynos_driverdata exynos2200_drvdata = { + .clk_names = { "link_aclk" }, + .num_clks = 1, + .suspend_clk_idx = -1, +}; + +static const struct dwc3_exynos_driverdata exynos5250_drvdata = { + .clk_names = { "usbdrd30" }, + .num_clks = 1, + .suspend_clk_idx = -1, +}; + +static const struct dwc3_exynos_driverdata exynos5433_drvdata = { + .clk_names = { "aclk", "susp_clk", "pipe_pclk", "phyclk" }, + .num_clks = 4, + .suspend_clk_idx = 1, +}; + +static const struct dwc3_exynos_driverdata exynos7_drvdata = { + .clk_names = { "usbdrd30", "usbdrd30_susp_clk", "usbdrd30_axius_clk" }, + .num_clks = 3, + .suspend_clk_idx = 1, +}; + +static const struct dwc3_exynos_driverdata exynos7870_drvdata = { + .clk_names = { "bus_early", "ref", "ctrl" }, + .num_clks = 3, + .suspend_clk_idx = -1, +}; + +static const struct dwc3_exynos_driverdata exynos850_drvdata = { + .clk_names = { "bus_early", "ref" }, + .num_clks = 2, + .suspend_clk_idx = -1, +}; + +static const struct dwc3_exynos_driverdata gs101_drvdata = { + .clk_names = { "bus_early", "susp_clk", "link_aclk", "link_pclk" }, + .num_clks = 4, + .suspend_clk_idx = 1, +}; + +static const struct dwc3_exynos_driverdata exynosautov920_drvdata = { + .clk_names = { "ref", "susp_clk"}, + .num_clks = 2, + .suspend_clk_idx = 1, +}; + +static const struct of_device_id exynos_dwc3_match[] = { + { + .compatible = "samsung,exynos2200-dwusb3", + .data = &exynos2200_drvdata, + }, { + .compatible = "samsung,exynos5250-dwusb3", + .data = &exynos5250_drvdata, + }, { + .compatible = "samsung,exynos5433-dwusb3", + .data = &exynos5433_drvdata, + }, { + .compatible = "samsung,exynos7-dwusb3", + .data = &exynos7_drvdata, + }, { + .compatible = "samsung,exynos7870-dwusb3", + .data = &exynos7870_drvdata, + }, { + .compatible = "samsung,exynos850-dwusb3", + .data = &exynos850_drvdata, + }, { + .compatible = "samsung,exynosautov920-dwusb3", + .data = &exynosautov920_drvdata, + }, { + .compatible = "google,gs101-dwusb3", + .data = &gs101_drvdata, + }, { + } +}; +MODULE_DEVICE_TABLE(of, exynos_dwc3_match); + +static int dwc3_exynos_suspend(struct device *dev) +{ + struct dwc3_exynos *exynos = dev_get_drvdata(dev); + int i; + + for (i = exynos->num_clks - 1; i >= 0; i--) + clk_disable_unprepare(exynos->clks[i]); + + regulator_disable(exynos->vdd33); + regulator_disable(exynos->vdd10); + + return 0; +} + +static int dwc3_exynos_resume(struct device *dev) +{ + struct dwc3_exynos *exynos = dev_get_drvdata(dev); + int i, ret; + + ret = regulator_enable(exynos->vdd33); + if (ret) { + dev_err(dev, "Failed to enable VDD33 supply\n"); + return ret; + } + ret = regulator_enable(exynos->vdd10); + if (ret) { + dev_err(dev, "Failed to enable VDD10 supply\n"); + return ret; + } + + for (i = 0; i < exynos->num_clks; i++) { + ret = clk_prepare_enable(exynos->clks[i]); + if (ret) { + while (i-- > 0) + clk_disable_unprepare(exynos->clks[i]); + return ret; + } + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(dwc3_exynos_dev_pm_ops, + dwc3_exynos_suspend, dwc3_exynos_resume); + +static struct platform_driver dwc3_exynos_driver = { + .probe = dwc3_exynos_probe, + .remove = dwc3_exynos_remove, + .driver = { + .name = "exynos-dwc3", + .of_match_table = exynos_dwc3_match, + .pm = pm_sleep_ptr(&dwc3_exynos_dev_pm_ops), + }, +}; + +module_platform_driver(dwc3_exynos_driver); + +MODULE_AUTHOR("Anton Tikhomirov "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 Exynos Glue Layer"); diff --git a/drivers/usb/dwc3/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c new file mode 100644 index 000000000..ca69ac0eb --- /dev/null +++ b/drivers/usb/dwc3/dwc3-generic-plat.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dwc3-generic-plat.c - DesignWare USB3 generic platform driver + * + * Copyright (C) 2025 Ze Huang + * + * Inspired by dwc3-qcom.c and dwc3-of-simple.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include "glue.h" + +#define EIC7700_HSP_BUS_FILTER_EN BIT(0) +#define EIC7700_HSP_BUS_CLKEN_GM BIT(9) +#define EIC7700_HSP_BUS_CLKEN_GS BIT(16) +#define EIC7700_HSP_AXI_LP_XM_CSYSREQ BIT(0) +#define EIC7700_HSP_AXI_LP_XS_CSYSREQ BIT(16) + +struct dwc3_generic { + struct device *dev; + struct dwc3 dwc; + struct clk_bulk_data *clks; + int num_clocks; + struct reset_control *resets; +}; + +struct dwc3_generic_config { + int (*init)(struct dwc3_generic *dwc3g); + struct dwc3_properties properties; +}; + +#define to_dwc3_generic(d) container_of((d), struct dwc3_generic, dwc) + +static void dwc3_generic_reset_control_assert(void *data) +{ + reset_control_assert(data); +} + +static int dwc3_eic7700_init(struct dwc3_generic *dwc3g) +{ + struct device *dev = dwc3g->dev; + struct regmap *regmap; + u32 hsp_usb_axi_lp; + u32 hsp_usb_bus; + u32 args[2]; + u32 val; + + regmap = syscon_regmap_lookup_by_phandle_args(dev->of_node, + "eswin,hsp-sp-csr", + ARRAY_SIZE(args), args); + if (IS_ERR(regmap)) { + dev_err(dev, "No hsp-sp-csr phandle specified\n"); + return PTR_ERR(regmap); + } + + hsp_usb_bus = args[0]; + hsp_usb_axi_lp = args[1]; + + regmap_read(regmap, hsp_usb_bus, &val); + regmap_write(regmap, hsp_usb_bus, val | EIC7700_HSP_BUS_FILTER_EN | + EIC7700_HSP_BUS_CLKEN_GM | EIC7700_HSP_BUS_CLKEN_GS); + + regmap_write(regmap, hsp_usb_axi_lp, EIC7700_HSP_AXI_LP_XM_CSYSREQ | + EIC7700_HSP_AXI_LP_XS_CSYSREQ); + return 0; +} + +static int dwc3_spacemit_k1_init(struct dwc3_generic *dwc3g) +{ + struct device *dev = dwc3g->dev; + + if (usb_get_dr_mode(dev) == USB_DR_MODE_HOST) { + int ret = devm_regulator_get_enable_optional(dev, "vbus"); + + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to enable VBUS\n"); + } + + return 0; +} + +static int dwc3_generic_probe(struct platform_device *pdev) +{ + const struct dwc3_generic_config *plat_config; + struct dwc3_probe_data probe_data = {}; + struct device *dev = &pdev->dev; + struct dwc3_generic *dwc3g; + struct resource *res; + int ret; + + dwc3g = devm_kzalloc(dev, sizeof(*dwc3g), GFP_KERNEL); + if (!dwc3g) + return -ENOMEM; + + dwc3g->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory resource\n"); + return -ENODEV; + } + + dwc3g->resets = devm_reset_control_array_get_optional_exclusive(dev); + if (IS_ERR(dwc3g->resets)) + return dev_err_probe(dev, PTR_ERR(dwc3g->resets), "failed to get resets\n"); + + ret = reset_control_assert(dwc3g->resets); + if (ret) + return dev_err_probe(dev, ret, "failed to assert resets\n"); + + /* Not strict timing, just for safety */ + udelay(2); + + ret = reset_control_deassert(dwc3g->resets); + if (ret) + return dev_err_probe(dev, ret, "failed to deassert resets\n"); + + ret = devm_add_action_or_reset(dev, dwc3_generic_reset_control_assert, dwc3g->resets); + if (ret) + return ret; + + ret = devm_clk_bulk_get_all_enabled(dwc3g->dev, &dwc3g->clks); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get clocks\n"); + + dwc3g->num_clocks = ret; + dwc3g->dwc.dev = dev; + probe_data.dwc = &dwc3g->dwc; + probe_data.res = res; + probe_data.ignore_clocks_and_resets = true; + + plat_config = of_device_get_match_data(dev); + if (!plat_config) { + probe_data.properties = DWC3_DEFAULT_PROPERTIES; + goto core_probe; + } + + probe_data.properties = plat_config->properties; + if (plat_config->init) { + ret = plat_config->init(dwc3g); + if (ret) + return dev_err_probe(dev, ret, + "failed to init platform\n"); + } + +core_probe: + ret = dwc3_core_probe(&probe_data); + if (ret) + return dev_err_probe(dev, ret, "failed to register DWC3 Core\n"); + + return 0; +} + +static void dwc3_generic_remove(struct platform_device *pdev) +{ + struct dwc3 *dwc = platform_get_drvdata(pdev); + + dwc3_core_remove(dwc); +} + +static int dwc3_generic_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_generic *dwc3g = to_dwc3_generic(dwc); + int ret; + + ret = dwc3_pm_suspend(dwc); + if (ret) + return ret; + + clk_bulk_disable_unprepare(dwc3g->num_clocks, dwc3g->clks); + + return 0; +} + +static int dwc3_generic_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_generic *dwc3g = to_dwc3_generic(dwc); + int ret; + + ret = clk_bulk_prepare_enable(dwc3g->num_clocks, dwc3g->clks); + if (ret) + return ret; + + ret = dwc3_pm_resume(dwc); + if (ret) + return ret; + + return 0; +} + +static int dwc3_generic_runtime_suspend(struct device *dev) +{ + return dwc3_runtime_suspend(dev_get_drvdata(dev)); +} + +static int dwc3_generic_runtime_resume(struct device *dev) +{ + return dwc3_runtime_resume(dev_get_drvdata(dev)); +} + +static int dwc3_generic_runtime_idle(struct device *dev) +{ + return dwc3_runtime_idle(dev_get_drvdata(dev)); +} + +static const struct dev_pm_ops dwc3_generic_dev_pm_ops = { + SYSTEM_SLEEP_PM_OPS(dwc3_generic_suspend, dwc3_generic_resume) + RUNTIME_PM_OPS(dwc3_generic_runtime_suspend, dwc3_generic_runtime_resume, + dwc3_generic_runtime_idle) +}; + +static const struct dwc3_generic_config spacemit_k1_dwc3 = { + .init = dwc3_spacemit_k1_init, + .properties = DWC3_DEFAULT_PROPERTIES, +}; + +static const struct dwc3_generic_config fsl_ls1028_dwc3 = { + .properties.gsbuscfg0_reqinfo = 0x2222, +}; + +static const struct dwc3_generic_config eic7700_dwc3 = { + .init = dwc3_eic7700_init, + .properties = DWC3_DEFAULT_PROPERTIES, +}; + +static const struct of_device_id dwc3_generic_of_match[] = { + { .compatible = "spacemit,k1-dwc3", &spacemit_k1_dwc3}, + { .compatible = "spacemit,k3-dwc3", }, + { .compatible = "fsl,ls1028a-dwc3", &fsl_ls1028_dwc3}, + { .compatible = "eswin,eic7700-dwc3", &eic7700_dwc3}, + { .compatible = "starfive,jhb100-dwc3", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dwc3_generic_of_match); + +static struct platform_driver dwc3_generic_driver = { + .probe = dwc3_generic_probe, + .remove = dwc3_generic_remove, + .driver = { + .name = "dwc3-generic-plat", + .of_match_table = dwc3_generic_of_match, + .pm = pm_ptr(&dwc3_generic_dev_pm_ops), + }, +}; +module_platform_driver(dwc3_generic_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DesignWare USB3 generic platform driver"); diff --git a/drivers/usb/dwc3/dwc3-google.c b/drivers/usb/dwc3/dwc3-google.c new file mode 100644 index 000000000..60ee4cc99 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-google.c @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-google.c - Google DWC3 Specific Glue Layer + * + * Copyright (c) 2025, Google LLC + * Author: Roy Luo + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "core.h" +#include "glue.h" + +/* HOST CFG registers */ +#define HC_STATUS_OFFSET 0x0 +#define HC_STATUS_CURRENT_POWER_STATE_U2PMU GENMASK(1, 0) +#define HC_STATUS_CURRENT_POWER_STATE_U3PMU GENMASK(4, 3) + +#define HOST_CFG1_OFFSET 0x4 +#define HOST_CFG1_PME_EN BIT(3) +#define HOST_CFG1_PM_POWER_STATE_REQUEST GENMASK(5, 4) +#define HOST_CFG1_PM_POWER_STATE_D0 0x0 +#define HOST_CFG1_PM_POWER_STATE_D3 0x3 + +/* USBINT registers */ +#define USBINT_CFG1_OFFSET 0x0 +#define USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK BIT(2) +#define USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK BIT(3) +#define USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN BIT(8) +#define USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN BIT(9) +#define USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR BIT(14) +#define USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR BIT(15) + +#define USBINT_STATUS_OFFSET 0x4 +#define USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW BIT(2) +#define USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW BIT(3) + +#define USBCS_TOP_CTRL_CFG1_OFFSET 0xc +#define USBCS_TOP_CTRL_CFG1_USB2ONLY_MODE BIT(5) + +#define DWC3_GOOGLE_MAX_RESETS 4 + +struct dwc3_google { + struct device *dev; + struct dwc3 dwc; + struct clk_bulk_data *clks; + int num_clks; + struct reset_control_bulk_data rsts[DWC3_GOOGLE_MAX_RESETS]; + int num_rsts; + struct reset_control *non_sticky_rst; + struct device *usb_psw_pd; + struct device_link *usb_psw_pd_dl; + struct notifier_block usb_psw_pd_nb; + struct device *usb_top_pd; + struct device_link *usb_top_pd_dl; + struct regmap *usb_cfg_regmap; + unsigned int host_cfg_offset; + unsigned int usbint_cfg_offset; + int hs_pme_irq; + int ss_pme_irq; + bool is_usb2only; + bool is_hibernation; +}; + +#define to_dwc3_google(d) container_of_const((d), struct dwc3_google, dwc) + +static int dwc3_google_rst_init(struct dwc3_google *google) +{ + int ret; + + google->num_rsts = 4; + google->rsts[0].id = "non_sticky"; + google->rsts[1].id = "sticky"; + google->rsts[2].id = "drd_bus"; + google->rsts[3].id = "top"; + + ret = devm_reset_control_bulk_get_exclusive(google->dev, + google->num_rsts, + google->rsts); + + if (ret < 0) + return ret; + + google->non_sticky_rst = google->rsts[0].rstc; + + return 0; +} + +static int dwc3_google_set_pmu_state(struct dwc3_google *google, int state) +{ + u32 reg; + int ret; + + regmap_read(google->usb_cfg_regmap, + google->host_cfg_offset + HOST_CFG1_OFFSET, ®); + + FIELD_MODIFY(HOST_CFG1_PM_POWER_STATE_REQUEST, ®, state); + reg |= HOST_CFG1_PME_EN; + regmap_write(google->usb_cfg_regmap, + google->host_cfg_offset + HOST_CFG1_OFFSET, reg); + + ret = regmap_read_poll_timeout(google->usb_cfg_regmap, + google->host_cfg_offset + HC_STATUS_OFFSET, reg, + (FIELD_GET(HC_STATUS_CURRENT_POWER_STATE_U2PMU, + reg) == state && + FIELD_GET(HC_STATUS_CURRENT_POWER_STATE_U3PMU, + reg) == state), + 10, 10000); + + if (ret) + dev_err(google->dev, "failed to set PMU state %d\n", state); + + return ret; +} + +/* + * Clear pme interrupts and report their status. + * The hardware requires write-1 then write-0 sequence to clear the interrupt bits. + */ +static u32 dwc3_google_clear_pme_irqs(struct dwc3_google *google) +{ + u32 irq_status, reg_set, reg_clear; + + regmap_read(google->usb_cfg_regmap, + google->usbint_cfg_offset + USBINT_STATUS_OFFSET, &irq_status); + + irq_status &= (USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW | + USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW); + if (!irq_status) + return irq_status; + + regmap_read(google->usb_cfg_regmap, + google->usbint_cfg_offset + USBINT_CFG1_OFFSET, ®_set); + + reg_clear = reg_set; + if (irq_status & USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW) { + reg_set |= USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR; + reg_clear &= ~USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR; + } + if (irq_status & USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW) { + reg_set |= USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR; + reg_clear &= ~USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR; + } + + regmap_write(google->usb_cfg_regmap, + google->usbint_cfg_offset + USBINT_CFG1_OFFSET, reg_set); + regmap_write(google->usb_cfg_regmap, + google->usbint_cfg_offset + USBINT_CFG1_OFFSET, reg_clear); + + return irq_status; +} + +static void dwc3_google_enable_pme_irq(struct dwc3_google *google) +{ + u32 reg; + + regmap_read(google->usb_cfg_regmap, + google->usbint_cfg_offset + USBINT_CFG1_OFFSET, ®); + reg &= ~(USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK | + USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK); + reg |= (USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN | + USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN); + regmap_write(google->usb_cfg_regmap, + google->usbint_cfg_offset + USBINT_CFG1_OFFSET, reg); + + enable_irq(google->hs_pme_irq); + enable_irq(google->ss_pme_irq); + enable_irq_wake(google->hs_pme_irq); + enable_irq_wake(google->ss_pme_irq); +} + +static void dwc3_google_disable_pme_irq(struct dwc3_google *google) +{ + u32 reg; + + regmap_read(google->usb_cfg_regmap, + google->usbint_cfg_offset + USBINT_CFG1_OFFSET, ®); + reg &= ~(USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN | + USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN); + reg |= (USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK | + USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK); + regmap_write(google->usb_cfg_regmap, + google->usbint_cfg_offset + USBINT_CFG1_OFFSET, reg); + + disable_irq_wake(google->hs_pme_irq); + disable_irq_wake(google->ss_pme_irq); + disable_irq_nosync(google->hs_pme_irq); + disable_irq_nosync(google->ss_pme_irq); +} + +static irqreturn_t dwc3_google_resume_irq(int irq, void *data) +{ + struct dwc3_google *google = data; + struct dwc3 *dwc = &google->dwc; + u32 irq_status, dr_role; + + irq_status = dwc3_google_clear_pme_irqs(google); + dr_role = dwc->current_dr_role; + + if (!irq_status || !google->is_hibernation || + dr_role != DWC3_GCTL_PRTCAP_HOST) { + dev_dbg(google->dev, "spurious pme irq %d, hibernation %d, dr_role %u\n", + irq, google->is_hibernation, dr_role); + return IRQ_HANDLED; + } + + if (dwc->xhci) + pm_runtime_resume(&dwc->xhci->dev); + + return IRQ_HANDLED; +} + +static int dwc3_google_request_irq(struct dwc3_google *google, struct platform_device *pdev, + const char *irq_name, const char *req_name) +{ + int ret; + int irq; + + irq = platform_get_irq_byname(pdev, irq_name); + if (irq < 0) + return irq; + + irq_set_status_flags(irq, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(google->dev, irq, NULL, + dwc3_google_resume_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + req_name, google); + if (ret < 0) { + dev_err(google->dev, "failed to request irq %s\n", req_name); + return ret; + } + + return irq; +} + +static int dwc3_google_usb_psw_pd_notifier(struct notifier_block *nb, unsigned long action, void *d) +{ + struct dwc3_google *google = container_of(nb, struct dwc3_google, usb_psw_pd_nb); + int ret; + + if (!google->is_hibernation) + return NOTIFY_OK; + + if (action == GENPD_NOTIFY_OFF) { + dev_dbg(google->dev, "enter D3 power state\n"); + dwc3_google_set_pmu_state(google, HOST_CFG1_PM_POWER_STATE_D3); + ret = reset_control_assert(google->non_sticky_rst); + if (ret) + dev_err(google->dev, "non sticky reset assert failed: %d\n", ret); + } else if (action == GENPD_NOTIFY_ON) { + dev_dbg(google->dev, "enter D0 power state\n"); + dwc3_google_clear_pme_irqs(google); + ret = reset_control_deassert(google->non_sticky_rst); + if (ret) + dev_err(google->dev, "non sticky reset deassert failed: %d\n", ret); + dwc3_google_set_pmu_state(google, HOST_CFG1_PM_POWER_STATE_D0); + } + + return NOTIFY_OK; +} + +static void dwc3_google_pm_domain_deinit(struct dwc3_google *google) +{ + if (google->usb_top_pd_dl) + device_link_del(google->usb_top_pd_dl); + + if (!IS_ERR_OR_NULL(google->usb_top_pd)) { + device_set_wakeup_capable(google->usb_top_pd, false); + dev_pm_domain_detach(google->usb_top_pd, true); + } + + if (google->usb_psw_pd_dl) + device_link_del(google->usb_psw_pd_dl); + + if (!IS_ERR_OR_NULL(google->usb_psw_pd)) { + dev_pm_genpd_remove_notifier(google->usb_psw_pd); + dev_pm_domain_detach(google->usb_psw_pd, true); + } +} + +static int dwc3_google_pm_domain_init(struct dwc3_google *google) +{ + int ret; + + /* + * Establish PM RUNTIME link between dwc dev and its power domain usb_psw_pd, + * register notifier block to handle hibernation. + */ + google->usb_psw_pd = dev_pm_domain_attach_by_name(google->dev, "psw"); + if (IS_ERR_OR_NULL(google->usb_psw_pd)) { + dev_err(google->dev, "failed to get psw pd"); + ret = google->usb_psw_pd ? PTR_ERR(google->usb_psw_pd) : -ENODATA; + return ret; + } + + google->usb_psw_pd_nb.notifier_call = dwc3_google_usb_psw_pd_notifier; + ret = dev_pm_genpd_add_notifier(google->usb_psw_pd, &google->usb_psw_pd_nb); + if (ret) { + dev_err(google->dev, "failed to add psw pd notifier"); + goto err; + } + + google->usb_psw_pd_dl = device_link_add(google->dev, google->usb_psw_pd, + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!google->usb_psw_pd_dl) { + dev_err(google->usb_psw_pd, "failed to add device link"); + ret = -ENODEV; + goto err; + } + + /* + * usb_top_pd is the parent power domain of usb_psw_pd. Keeping usb_top_pd on + * while usb_psw_pd is off places the controller in a power-gated state, + * essential for hibernation. Acquire a handle to usb_top_pd and sets it as + * wakeup-capable to allow the domain to be left on during system suspend. + */ + google->usb_top_pd = dev_pm_domain_attach_by_name(google->dev, "top"); + if (IS_ERR_OR_NULL(google->usb_top_pd)) { + dev_err(google->dev, "failed to get top pd"); + ret = google->usb_top_pd ? PTR_ERR(google->usb_top_pd) : -ENODATA; + goto err; + } + device_set_wakeup_capable(google->usb_top_pd, true); + + google->usb_top_pd_dl = device_link_add(google->dev, google->usb_top_pd, + DL_FLAG_STATELESS); + if (!google->usb_top_pd_dl) { + dev_err(google->usb_top_pd, "failed to add device link"); + ret = -ENODEV; + goto err; + } + + return 0; + +err: + dwc3_google_pm_domain_deinit(google); + + return ret; +} + +static void dwc3_google_program_usb2only(struct dwc3_google *google) +{ + u32 reg; + + regmap_read(google->usb_cfg_regmap, + google->usbint_cfg_offset + USBCS_TOP_CTRL_CFG1_OFFSET, ®); + reg |= USBCS_TOP_CTRL_CFG1_USB2ONLY_MODE; + regmap_write(google->usb_cfg_regmap, + google->usbint_cfg_offset + USBCS_TOP_CTRL_CFG1_OFFSET, reg); +} + +static int dwc3_google_probe(struct platform_device *pdev) +{ + struct dwc3_probe_data probe_data = {}; + struct device *dev = &pdev->dev; + struct dwc3_google *google; + struct resource *res; + int ret; + u32 args[2]; + + google = devm_kzalloc(&pdev->dev, sizeof(*google), GFP_KERNEL); + if (!google) + return -ENOMEM; + + google->dev = &pdev->dev; + + ret = dwc3_google_pm_domain_init(google); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to init pdom\n"); + + google->usb_cfg_regmap = + syscon_regmap_lookup_by_phandle_args(dev->of_node, + "google,usb-cfg-csr", + ARRAY_SIZE(args), args); + if (IS_ERR(google->usb_cfg_regmap)) { + ret = dev_err_probe(dev, PTR_ERR(google->usb_cfg_regmap), + "invalid usb cfg csr\n"); + goto err_deinit_pdom; + } + + google->host_cfg_offset = args[0]; + google->usbint_cfg_offset = args[1]; + + if (device_property_match_string(dev, "phy-names", "usb3-phy") < 0) { + google->is_usb2only = true; + dwc3_google_program_usb2only(google); + } + + ret = devm_clk_bulk_get_all_enabled(dev, &google->clks); + if (ret < 0) { + ret = dev_err_probe(dev, ret, "failed to get and enable clks\n"); + goto err_deinit_pdom; + } + google->num_clks = ret; + + ret = dwc3_google_rst_init(google); + if (ret) { + ret = dev_err_probe(dev, ret, "failed to get resets\n"); + goto err_deinit_pdom; + } + + ret = reset_control_bulk_deassert(google->num_rsts, google->rsts); + if (ret) { + ret = dev_err_probe(dev, ret, "failed to deassert rsts\n"); + goto err_deinit_pdom; + } + + ret = dwc3_google_request_irq(google, pdev, "hs_pme", "USB HS wakeup"); + if (ret < 0) { + ret = dev_err_probe(dev, ret, "failed to request hs pme irq"); + goto err_reset_assert; + } + google->hs_pme_irq = ret; + + ret = dwc3_google_request_irq(google, pdev, "ss_pme", "USB SS wakeup"); + if (ret < 0) { + ret = dev_err_probe(dev, ret, "failed to request ss pme irq"); + goto err_reset_assert; + } + google->ss_pme_irq = ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = dev_err_probe(dev, -ENODEV, "invalid memory\n"); + goto err_reset_assert; + } + + device_init_wakeup(dev, true); + + google->dwc.dev = dev; + probe_data.dwc = &google->dwc; + probe_data.res = res; + probe_data.ignore_clocks_and_resets = true; + ret = dwc3_core_probe(&probe_data); + if (ret) { + ret = dev_err_probe(dev, ret, "failed to register DWC3 Core\n"); + goto err_reset_assert; + } + + return 0; + +err_reset_assert: + reset_control_bulk_assert(google->num_rsts, google->rsts); + +err_deinit_pdom: + dwc3_google_pm_domain_deinit(google); + + return ret; +} + +static void dwc3_google_remove(struct platform_device *pdev) +{ + struct dwc3 *dwc = platform_get_drvdata(pdev); + struct dwc3_google *google = to_dwc3_google(dwc); + + dwc3_core_remove(&google->dwc); + + reset_control_bulk_assert(google->num_rsts, google->rsts); + + dwc3_google_pm_domain_deinit(google); +} + +static int dwc3_google_suspend(struct dwc3_google *google, pm_message_t msg) +{ + if (pm_runtime_suspended(google->dev)) + return 0; + + if (google->dwc.current_dr_role == DWC3_GCTL_PRTCAP_HOST) { + /* + * Follow dwc3_suspend_common() guidelines for deciding between + * a full teardown and hibernation. + */ + if (PMSG_IS_AUTO(msg) || device_may_wakeup(google->dev)) { + dev_dbg(google->dev, "enter hibernation"); + pm_runtime_get_sync(google->usb_top_pd); + device_wakeup_enable(google->usb_top_pd); + dwc3_google_enable_pme_irq(google); + google->is_hibernation = true; + return 0; + } + } + + reset_control_bulk_assert(google->num_rsts, google->rsts); + clk_bulk_disable_unprepare(google->num_clks, google->clks); + + return 0; +} + +static int dwc3_google_resume(struct dwc3_google *google, pm_message_t msg) +{ + int ret; + + if (google->is_hibernation) { + dev_dbg(google->dev, "exit hibernation"); + dwc3_google_disable_pme_irq(google); + device_wakeup_disable(google->usb_top_pd); + pm_runtime_put_sync(google->usb_top_pd); + google->is_hibernation = false; + return 0; + } + + if (google->is_usb2only) + dwc3_google_program_usb2only(google); + + ret = clk_bulk_prepare_enable(google->num_clks, google->clks); + if (ret) + return ret; + + ret = reset_control_bulk_deassert(google->num_rsts, google->rsts); + if (ret) { + clk_bulk_disable_unprepare(google->num_clks, google->clks); + return ret; + } + + return 0; +} + +static int dwc3_google_pm_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_google *google = to_dwc3_google(dwc); + int ret; + + ret = dwc3_pm_suspend(&google->dwc); + if (ret) + return ret; + + return dwc3_google_suspend(google, PMSG_SUSPEND); +} + +static int dwc3_google_pm_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_google *google = to_dwc3_google(dwc); + int ret; + + ret = dwc3_google_resume(google, PMSG_RESUME); + if (ret) + return ret; + + return dwc3_pm_resume(&google->dwc); +} + +static void dwc3_google_complete(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + + dwc3_pm_complete(dwc); +} + +static int dwc3_google_prepare(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + + return dwc3_pm_prepare(dwc); +} + +static int dwc3_google_runtime_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_google *google = to_dwc3_google(dwc); + int ret; + + ret = dwc3_runtime_suspend(&google->dwc); + if (ret) + return ret; + + return dwc3_google_suspend(google, PMSG_AUTO_SUSPEND); +} + +static int dwc3_google_runtime_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_google *google = to_dwc3_google(dwc); + int ret; + + ret = dwc3_google_resume(google, PMSG_AUTO_RESUME); + if (ret) + return ret; + + return dwc3_runtime_resume(&google->dwc); +} + +static int dwc3_google_runtime_idle(struct device *dev) +{ + return dwc3_runtime_idle(dev_get_drvdata(dev)); +} + +static const struct dev_pm_ops dwc3_google_dev_pm_ops = { + SYSTEM_SLEEP_PM_OPS(dwc3_google_pm_suspend, dwc3_google_pm_resume) + RUNTIME_PM_OPS(dwc3_google_runtime_suspend, dwc3_google_runtime_resume, + dwc3_google_runtime_idle) + .complete = pm_sleep_ptr(dwc3_google_complete), + .prepare = pm_sleep_ptr(dwc3_google_prepare), +}; + +static const struct of_device_id dwc3_google_of_match[] = { + { .compatible = "google,lga-dwc3" }, + { } +}; +MODULE_DEVICE_TABLE(of, dwc3_google_of_match); + +static struct platform_driver dwc3_google_driver = { + .probe = dwc3_google_probe, + .remove = dwc3_google_remove, + .driver = { + .name = "dwc3-google", + .pm = pm_ptr(&dwc3_google_dev_pm_ops), + .of_match_table = dwc3_google_of_match, + }, +}; + +module_platform_driver(dwc3_google_driver); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DesignWare DWC3 Google Glue Driver"); diff --git a/drivers/usb/dwc3/dwc3-haps.c b/drivers/usb/dwc3/dwc3-haps.c new file mode 100644 index 000000000..f6e3817fa --- /dev/null +++ b/drivers/usb/dwc3/dwc3-haps.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-haps.c - Synopsys HAPS PCI Specific glue layer + * + * Copyright (C) 2018 Synopsys, Inc. + * + * Authors: Thinh Nguyen , + * John Youn + */ + +#include +#include +#include +#include +#include +#include + +/** + * struct dwc3_haps - Driver private structure + * @dwc3: child dwc3 platform_device + * @pci: our link to PCI bus + */ +struct dwc3_haps { + struct platform_device *dwc3; + struct pci_dev *pci; +}; + +static const struct property_entry initial_properties[] = { + PROPERTY_ENTRY_BOOL("snps,usb3_lpm_capable"), + PROPERTY_ENTRY_BOOL("snps,has-lpm-erratum"), + PROPERTY_ENTRY_BOOL("snps,dis_enblslpm_quirk"), + PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"), + { }, +}; + +static const struct software_node dwc3_haps_swnode = { + .properties = initial_properties, +}; + +static int dwc3_haps_probe(struct pci_dev *pci, + const struct pci_device_id *id) +{ + struct dwc3_haps *dwc; + struct device *dev = &pci->dev; + struct resource res[2]; + int ret; + + ret = pcim_enable_device(pci); + if (ret) { + dev_err(dev, "failed to enable pci device\n"); + return -ENODEV; + } + + pci_set_master(pci); + + dwc = devm_kzalloc(dev, sizeof(*dwc), GFP_KERNEL); + if (!dwc) + return -ENOMEM; + + dwc->dwc3 = platform_device_alloc("dwc3", PLATFORM_DEVID_AUTO); + if (!dwc->dwc3) + return -ENOMEM; + + 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(dwc->dwc3, res, ARRAY_SIZE(res)); + if (ret) { + dev_err(dev, "couldn't add resources to dwc3 device\n"); + goto err; + } + + dwc->pci = pci; + dwc->dwc3->dev.parent = dev; + + ret = device_add_software_node(&dwc->dwc3->dev, &dwc3_haps_swnode); + if (ret) + goto err; + + ret = platform_device_add(dwc->dwc3); + if (ret) { + dev_err(dev, "failed to register dwc3 device\n"); + goto err; + } + + pci_set_drvdata(pci, dwc); + + return 0; +err: + device_remove_software_node(&dwc->dwc3->dev); + platform_device_put(dwc->dwc3); + return ret; +} + +static void dwc3_haps_remove(struct pci_dev *pci) +{ + struct dwc3_haps *dwc = pci_get_drvdata(pci); + + device_remove_software_node(&dwc->dwc3->dev); + platform_device_unregister(dwc->dwc3); +} + +static const struct pci_device_id dwc3_haps_id_table[] = { + { + PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, + PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3), + /* + * i.MX6QP and i.MX7D platform use a PCIe controller with the + * same VID and PID as this USB controller. The system may + * incorrectly match this driver to that PCIe controller. To + * workaround this, specifically use class type USB to prevent + * incorrect driver matching. + */ + .class = (PCI_CLASS_SERIAL_USB << 8), + .class_mask = 0xffff00, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, + PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3_AXI), + }, + { + PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, + PCI_DEVICE_ID_SYNOPSYS_HAPSUSB31), + }, + { } /* Terminating Entry */ +}; +MODULE_DEVICE_TABLE(pci, dwc3_haps_id_table); + +static struct pci_driver dwc3_haps_driver = { + .name = "dwc3-haps", + .id_table = dwc3_haps_id_table, + .probe = dwc3_haps_probe, + .remove = dwc3_haps_remove, +}; + +MODULE_AUTHOR("Thinh Nguyen "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Synopsys HAPS PCI Glue Layer"); + +module_pci_driver(dwc3_haps_driver); diff --git a/drivers/usb/dwc3/dwc3-imx.c b/drivers/usb/dwc3/dwc3-imx.c new file mode 100644 index 000000000..973a486b5 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-imx.c @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-imx.c - NXP i.MX Soc USB3 Specific Glue layer + * + * Copyright 2026 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "glue.h" + +/* USB wakeup registers */ +#define USB_WAKEUP_CTRL 0x00 + +/* Global wakeup interrupt enable, also used to clear interrupt */ +#define USB_WAKEUP_EN BIT(31) +/* Wakeup from connect or disconnect, only for superspeed */ +#define USB_WAKEUP_SS_CONN BIT(5) +/* 0 select vbus_valid, 1 select sessvld */ +#define USB_WAKEUP_VBUS_SRC_SESS_VAL BIT(4) +/* Enable signal for wake up from u3 state */ +#define USB_WAKEUP_U3_EN BIT(3) +/* Enable signal for wake up from id change */ +#define USB_WAKEUP_ID_EN BIT(2) +/* Enable signal for wake up from vbus change */ +#define USB_WAKEUP_VBUS_EN BIT(1) +/* Enable signal for wake up from dp/dm change */ +#define USB_WAKEUP_DPDM_EN BIT(0) + +#define USB_WAKEUP_EN_MASK GENMASK(5, 0) + +/* USB glue registers */ +#define USB_CTRL0 0x00 +#define USB_CTRL1 0x04 + +#define USB_CTRL0_PORTPWR_EN BIT(12) /* 1 - PPC enabled (default) */ +#define USB_CTRL0_USB3_FIXED BIT(22) /* 1 - USB3 permanent attached */ +#define USB_CTRL0_USB2_FIXED BIT(23) /* 1 - USB2 permanent attached */ + +#define USB_CTRL1_OC_POLARITY BIT(16) /* 0 - HIGH / 1 - LOW */ +#define USB_CTRL1_PWR_POLARITY BIT(17) /* 0 - HIGH / 1 - LOW */ + +struct dwc3_imx { + struct dwc3 dwc; + struct device *dev; + void __iomem *blkctl_base; + void __iomem *glue_base; + struct clk *hsio_clk; + struct clk *suspend_clk; + int irq; + bool pm_suspended; + bool wakeup_pending; + unsigned permanent_attached:1; + unsigned disable_pwr_ctrl:1; + unsigned overcur_active_low:1; + unsigned power_active_low:1; +}; + +#define to_dwc3_imx(d) container_of((d), struct dwc3_imx, dwc) + +static void dwc3_imx_get_property(struct dwc3_imx *dwc_imx) +{ + struct device *dev = dwc_imx->dev; + + dwc_imx->permanent_attached = + device_property_read_bool(dev, "fsl,permanently-attached"); + dwc_imx->disable_pwr_ctrl = + device_property_read_bool(dev, "fsl,disable-port-power-control"); + dwc_imx->overcur_active_low = + device_property_read_bool(dev, "fsl,over-current-active-low"); + dwc_imx->power_active_low = + device_property_read_bool(dev, "fsl,power-active-low"); +} + +static void dwc3_imx_configure_glue(struct dwc3_imx *dwc_imx) +{ + u32 value; + + if (!dwc_imx->glue_base) + return; + + value = readl(dwc_imx->glue_base + USB_CTRL0); + + if (dwc_imx->permanent_attached) + value |= USB_CTRL0_USB2_FIXED | USB_CTRL0_USB3_FIXED; + else + value &= ~(USB_CTRL0_USB2_FIXED | USB_CTRL0_USB3_FIXED); + + if (dwc_imx->disable_pwr_ctrl) + value &= ~USB_CTRL0_PORTPWR_EN; + else + value |= USB_CTRL0_PORTPWR_EN; + + writel(value, dwc_imx->glue_base + USB_CTRL0); + + value = readl(dwc_imx->glue_base + USB_CTRL1); + if (dwc_imx->overcur_active_low) + value |= USB_CTRL1_OC_POLARITY; + else + value &= ~USB_CTRL1_OC_POLARITY; + + if (dwc_imx->power_active_low) + value |= USB_CTRL1_PWR_POLARITY; + else + value &= ~USB_CTRL1_PWR_POLARITY; + + writel(value, dwc_imx->glue_base + USB_CTRL1); +} + +static void dwc3_imx_wakeup_enable(struct dwc3_imx *dwc_imx, pm_message_t msg) +{ + struct dwc3 *dwc = &dwc_imx->dwc; + u32 val; + + val = readl(dwc_imx->blkctl_base + USB_WAKEUP_CTRL); + + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST && dwc->xhci) { + val |= USB_WAKEUP_EN | USB_WAKEUP_DPDM_EN; + if (PMSG_IS_AUTO(msg)) + val |= USB_WAKEUP_SS_CONN | USB_WAKEUP_U3_EN; + } else { + val |= USB_WAKEUP_EN | USB_WAKEUP_VBUS_EN | + USB_WAKEUP_VBUS_SRC_SESS_VAL; + } + + writel(val, dwc_imx->blkctl_base + USB_WAKEUP_CTRL); +} + +static void dwc3_imx_wakeup_disable(struct dwc3_imx *dwc_imx) +{ + u32 val; + + val = readl(dwc_imx->blkctl_base + USB_WAKEUP_CTRL); + val &= ~(USB_WAKEUP_EN | USB_WAKEUP_EN_MASK); + writel(val, dwc_imx->blkctl_base + USB_WAKEUP_CTRL); +} + +static irqreturn_t dwc3_imx_interrupt(int irq, void *data) +{ + struct dwc3_imx *dwc_imx = data; + struct dwc3 *dwc = &dwc_imx->dwc; + + if (!dwc_imx->pm_suspended) + return IRQ_HANDLED; + + disable_irq_nosync(dwc_imx->irq); + dwc_imx->wakeup_pending = true; + + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST && dwc->xhci) + pm_runtime_resume(&dwc->xhci->dev); + else if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) + pm_runtime_get(dwc->dev); + + return IRQ_HANDLED; +} + +static void dwc3_imx_pre_set_role(struct dwc3 *dwc, enum usb_role role) +{ + if (role == USB_ROLE_HOST) + /* + * For xhci host, we need disable dwc core auto + * suspend, because during this auto suspend delay(5s), + * xhci host RUN_STOP is cleared and wakeup is not + * enabled, if device is inserted, xhci host can't + * response the connection. + */ + pm_runtime_dont_use_autosuspend(dwc->dev); + else + pm_runtime_use_autosuspend(dwc->dev); +} + +static struct dwc3_glue_ops dwc3_imx_glue_ops = { + .pre_set_role = dwc3_imx_pre_set_role, +}; + +static const struct property_entry dwc3_imx_properties[] = { + PROPERTY_ENTRY_BOOL("xhci-missing-cas-quirk"), + PROPERTY_ENTRY_BOOL("xhci-skip-phy-init-quirk"), + {}, +}; + +static const struct software_node dwc3_imx_swnode = { + .properties = dwc3_imx_properties, +}; + +static int dwc3_imx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dwc3_imx *dwc_imx; + struct dwc3 *dwc; + struct resource *res; + const char *irq_name; + struct dwc3_probe_data probe_data = {}; + int ret, irq; + + dwc_imx = devm_kzalloc(dev, sizeof(*dwc_imx), GFP_KERNEL); + if (!dwc_imx) + return -ENOMEM; + + platform_set_drvdata(pdev, dwc_imx); + dwc_imx->dev = dev; + + dwc3_imx_get_property(dwc_imx); + + dwc_imx->blkctl_base = devm_platform_ioremap_resource_byname(pdev, "blkctl"); + if (IS_ERR(dwc_imx->blkctl_base)) + return PTR_ERR(dwc_imx->blkctl_base); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "glue"); + if (!res) { + dev_warn(dev, "Base address for glue layer missing\n"); + } else { + dwc_imx->glue_base = devm_ioremap_resource(dev, res); + if (IS_ERR(dwc_imx->glue_base)) + return PTR_ERR(dwc_imx->glue_base); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); + if (!res) + return dev_err_probe(dev, -ENODEV, "missing core memory resource\n"); + + dwc_imx->hsio_clk = devm_clk_get_enabled(dev, "hsio"); + if (IS_ERR(dwc_imx->hsio_clk)) + return dev_err_probe(dev, PTR_ERR(dwc_imx->hsio_clk), + "Failed to get hsio clk\n"); + + dwc_imx->suspend_clk = devm_clk_get_enabled(dev, "suspend"); + if (IS_ERR(dwc_imx->suspend_clk)) + return dev_err_probe(dev, PTR_ERR(dwc_imx->suspend_clk), + "Failed to get suspend clk\n"); + + irq = platform_get_irq_byname(pdev, "wakeup"); + if (irq < 0) + return irq; + dwc_imx->irq = irq; + + irq_name = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", dev_name(dev)); + if (!irq_name) + return dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n"); + + ret = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx_interrupt, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + irq_name, dwc_imx); + if (ret) + return dev_err_probe(dev, ret, "failed to request IRQ #%d\n", irq); + + ret = device_add_software_node(dev, &dwc3_imx_swnode); + if (ret) + return dev_err_probe(dev, ret, "failed to add software node\n"); + + dwc3_imx_configure_glue(dwc_imx); + + dwc = &dwc_imx->dwc; + dwc->dev = dev; + dwc->glue_ops = &dwc3_imx_glue_ops; + + probe_data.res = res; + probe_data.dwc = dwc; + probe_data.properties = DWC3_DEFAULT_PROPERTIES; + probe_data.properties.needs_full_reinit = true; + + ret = dwc3_core_probe(&probe_data); + if (ret) { + device_remove_software_node(dev); + return ret; + } + + device_set_wakeup_capable(dev, true); + return 0; +} + +static void dwc3_imx_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dwc3 *dwc = dev_get_drvdata(dev); + + dwc3_core_remove(dwc); + device_remove_software_node(dev); +} + +static void dwc3_imx_suspend(struct dwc3_imx *dwc_imx, pm_message_t msg) +{ + if (dwc_imx->pm_suspended) + return; + + if (PMSG_IS_AUTO(msg) || device_may_wakeup(dwc_imx->dev)) + dwc3_imx_wakeup_enable(dwc_imx, msg); + + enable_irq(dwc_imx->irq); + dwc_imx->pm_suspended = true; +} + +static void dwc3_imx_resume(struct dwc3_imx *dwc_imx, pm_message_t msg) +{ + struct dwc3 *dwc = &dwc_imx->dwc; + + if (!dwc_imx->pm_suspended) + return; + + dwc_imx->pm_suspended = false; + if (!dwc_imx->wakeup_pending) + disable_irq_nosync(dwc_imx->irq); + + dwc3_imx_wakeup_disable(dwc_imx); + + /* Upon power loss any previous configuration is lost, restore it */ + dwc3_imx_configure_glue(dwc_imx); + + if (dwc_imx->wakeup_pending) { + dwc_imx->wakeup_pending = false; + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) + pm_runtime_put_autosuspend(dwc->dev); + else + /* + * Add wait for xhci switch from suspend + * clock to normal clock to detect connection. + */ + usleep_range(9000, 10000); + } +} + +static int dwc3_imx_runtime_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc); + int ret; + + ret = dwc3_runtime_suspend(dwc); + if (ret) + return ret; + + dwc3_imx_suspend(dwc_imx, PMSG_AUTO_SUSPEND); + return 0; +} + +static int dwc3_imx_runtime_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc); + + dwc3_imx_resume(dwc_imx, PMSG_AUTO_RESUME); + return dwc3_runtime_resume(dwc); +} + +static int dwc3_imx_runtime_idle(struct device *dev) +{ + return dwc3_runtime_idle(dev_get_drvdata(dev)); +} + +static int dwc3_imx_pm_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc); + int ret; + + ret = dwc3_pm_suspend(dwc); + if (ret) + return ret; + + dwc3_imx_suspend(dwc_imx, PMSG_SUSPEND); + + if (device_may_wakeup(dev)) { + enable_irq_wake(dwc_imx->irq); + device_set_out_band_wakeup(dev); + } else { + clk_disable_unprepare(dwc_imx->suspend_clk); + } + + clk_disable_unprepare(dwc_imx->hsio_clk); + + return 0; +} + +static int dwc3_imx_pm_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc); + int ret; + + if (device_may_wakeup(dwc_imx->dev)) { + disable_irq_wake(dwc_imx->irq); + } else { + ret = clk_prepare_enable(dwc_imx->suspend_clk); + if (ret) + return ret; + } + + ret = clk_prepare_enable(dwc_imx->hsio_clk); + if (ret) { + clk_disable_unprepare(dwc_imx->suspend_clk); + return ret; + } + + dwc3_imx_resume(dwc_imx, PMSG_RESUME); + + ret = dwc3_pm_resume(dwc); + if (ret) + return ret; + + return 0; +} + +static void dwc3_imx_complete(struct device *dev) +{ + dwc3_pm_complete(dev_get_drvdata(dev)); +} + +static int dwc3_imx_prepare(struct device *dev) +{ + return dwc3_pm_prepare(dev_get_drvdata(dev)); +} + +static const struct dev_pm_ops dwc3_imx_dev_pm_ops = { + SYSTEM_SLEEP_PM_OPS(dwc3_imx_pm_suspend, dwc3_imx_pm_resume) + RUNTIME_PM_OPS(dwc3_imx_runtime_suspend, dwc3_imx_runtime_resume, + dwc3_imx_runtime_idle) + .complete = pm_sleep_ptr(dwc3_imx_complete), + .prepare = pm_sleep_ptr(dwc3_imx_prepare), +}; + +static const struct of_device_id dwc3_imx_of_match[] = { + { .compatible = "nxp,imx8mp-dwc3", }, + {}, +}; +MODULE_DEVICE_TABLE(of, dwc3_imx_of_match); + +static struct platform_driver dwc3_imx_driver = { + .probe = dwc3_imx_probe, + .remove = dwc3_imx_remove, + .driver = { + .name = "imx-dwc3", + .pm = pm_ptr(&dwc3_imx_dev_pm_ops), + .of_match_table = dwc3_imx_of_match, + }, +}; + +module_platform_driver(dwc3_imx_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DesignWare USB3 i.MX Glue Layer"); diff --git a/drivers/usb/dwc3/dwc3-imx8mp.c b/drivers/usb/dwc3/dwc3-imx8mp.c new file mode 100644 index 000000000..1cf96540b --- /dev/null +++ b/drivers/usb/dwc3/dwc3-imx8mp.c @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-imx8mp.c - NXP imx8mp Specific Glue layer + * + * Copyright (c) 2020 NXP. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" + +/* USB wakeup registers */ +#define USB_WAKEUP_CTRL 0x00 + +/* Global wakeup interrupt enable, also used to clear interrupt */ +#define USB_WAKEUP_EN BIT(31) +/* Wakeup from connect or disconnect, only for superspeed */ +#define USB_WAKEUP_SS_CONN BIT(5) +/* 0 select vbus_valid, 1 select sessvld */ +#define USB_WAKEUP_VBUS_SRC_SESS_VAL BIT(4) +/* Enable signal for wake up from u3 state */ +#define USB_WAKEUP_U3_EN BIT(3) +/* Enable signal for wake up from id change */ +#define USB_WAKEUP_ID_EN BIT(2) +/* Enable signal for wake up from vbus change */ +#define USB_WAKEUP_VBUS_EN BIT(1) +/* Enable signal for wake up from dp/dm change */ +#define USB_WAKEUP_DPDM_EN BIT(0) + +#define USB_WAKEUP_EN_MASK GENMASK(5, 0) + +/* USB glue registers */ +#define USB_CTRL0 0x00 +#define USB_CTRL1 0x04 + +#define USB_CTRL0_PORTPWR_EN BIT(12) /* 1 - PPC enabled (default) */ +#define USB_CTRL0_USB3_FIXED BIT(22) /* 1 - USB3 permanent attached */ +#define USB_CTRL0_USB2_FIXED BIT(23) /* 1 - USB2 permanent attached */ + +#define USB_CTRL1_OC_POLARITY BIT(16) /* 0 - HIGH / 1 - LOW */ +#define USB_CTRL1_PWR_POLARITY BIT(17) /* 0 - HIGH / 1 - LOW */ + +struct dwc3_imx8mp { + struct device *dev; + struct platform_device *dwc3_pdev; + void __iomem *hsio_blk_base; + void __iomem *glue_base; + struct clk *hsio_clk; + struct clk *suspend_clk; + int irq; + bool pm_suspended; + bool wakeup_pending; +}; + +static void imx8mp_configure_glue(struct dwc3_imx8mp *dwc3_imx) +{ + struct device *dev = dwc3_imx->dev; + u32 value; + + if (!dwc3_imx->glue_base) + return; + + value = readl(dwc3_imx->glue_base + USB_CTRL0); + + if (device_property_read_bool(dev, "fsl,permanently-attached")) + value |= (USB_CTRL0_USB2_FIXED | USB_CTRL0_USB3_FIXED); + else + value &= ~(USB_CTRL0_USB2_FIXED | USB_CTRL0_USB3_FIXED); + + if (device_property_read_bool(dev, "fsl,disable-port-power-control")) + value &= ~(USB_CTRL0_PORTPWR_EN); + else + value |= USB_CTRL0_PORTPWR_EN; + + writel(value, dwc3_imx->glue_base + USB_CTRL0); + + value = readl(dwc3_imx->glue_base + USB_CTRL1); + if (device_property_read_bool(dev, "fsl,over-current-active-low")) + value |= USB_CTRL1_OC_POLARITY; + else + value &= ~USB_CTRL1_OC_POLARITY; + + if (device_property_read_bool(dev, "fsl,power-active-low")) + value |= USB_CTRL1_PWR_POLARITY; + else + value &= ~USB_CTRL1_PWR_POLARITY; + + writel(value, dwc3_imx->glue_base + USB_CTRL1); +} + +static void dwc3_imx8mp_wakeup_enable(struct dwc3_imx8mp *dwc3_imx, + pm_message_t msg) +{ + struct dwc3 *dwc3 = platform_get_drvdata(dwc3_imx->dwc3_pdev); + u32 val; + + if (!dwc3) + return; + + val = readl(dwc3_imx->hsio_blk_base + USB_WAKEUP_CTRL); + + if ((dwc3->current_dr_role == DWC3_GCTL_PRTCAP_HOST) && dwc3->xhci) { + val |= USB_WAKEUP_EN | USB_WAKEUP_DPDM_EN; + if (PMSG_IS_AUTO(msg)) + val |= USB_WAKEUP_SS_CONN | USB_WAKEUP_U3_EN; + } else { + val |= USB_WAKEUP_EN | USB_WAKEUP_VBUS_EN | + USB_WAKEUP_VBUS_SRC_SESS_VAL; + } + + writel(val, dwc3_imx->hsio_blk_base + USB_WAKEUP_CTRL); +} + +static void dwc3_imx8mp_wakeup_disable(struct dwc3_imx8mp *dwc3_imx) +{ + u32 val; + + val = readl(dwc3_imx->hsio_blk_base + USB_WAKEUP_CTRL); + val &= ~(USB_WAKEUP_EN | USB_WAKEUP_EN_MASK); + writel(val, dwc3_imx->hsio_blk_base + USB_WAKEUP_CTRL); +} + +static const struct property_entry dwc3_imx8mp_properties[] = { + PROPERTY_ENTRY_BOOL("xhci-missing-cas-quirk"), + PROPERTY_ENTRY_BOOL("xhci-skip-phy-init-quirk"), + {}, +}; + +static const struct software_node dwc3_imx8mp_swnode = { + .properties = dwc3_imx8mp_properties, +}; + +static irqreturn_t dwc3_imx8mp_interrupt(int irq, void *_dwc3_imx) +{ + struct dwc3_imx8mp *dwc3_imx = _dwc3_imx; + struct dwc3 *dwc = platform_get_drvdata(dwc3_imx->dwc3_pdev); + + if (!dwc3_imx->pm_suspended) + return IRQ_HANDLED; + + disable_irq_nosync(dwc3_imx->irq); + dwc3_imx->wakeup_pending = true; + + if ((dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST) && dwc->xhci) + pm_runtime_resume(&dwc->xhci->dev); + else if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) + pm_runtime_get(dwc->dev); + + return IRQ_HANDLED; +} + +static void dwc3_imx_pre_set_role(struct dwc3 *dwc, enum usb_role role) +{ + if (role == USB_ROLE_HOST) + /* + * For xhci host, we need disable dwc core auto + * suspend, because during this auto suspend delay(5s), + * xhci host RUN_STOP is cleared and wakeup is not + * enabled, if device is inserted, xhci host can't + * response the connection. + */ + pm_runtime_dont_use_autosuspend(dwc->dev); + else + pm_runtime_use_autosuspend(dwc->dev); +} + +struct dwc3_glue_ops dwc3_imx_glue_ops = { + .pre_set_role = dwc3_imx_pre_set_role, +}; + +static int dwc3_imx8mp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct dwc3_imx8mp *dwc3_imx; + struct dwc3 *dwc3; + struct resource *res; + int err, irq; + + if (!node) { + dev_err(dev, "device node not found\n"); + return -EINVAL; + } + + dwc3_imx = devm_kzalloc(dev, sizeof(*dwc3_imx), GFP_KERNEL); + if (!dwc3_imx) + return -ENOMEM; + + platform_set_drvdata(pdev, dwc3_imx); + + dwc3_imx->dev = dev; + + dwc3_imx->hsio_blk_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dwc3_imx->hsio_blk_base)) + return PTR_ERR(dwc3_imx->hsio_blk_base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_warn(dev, "Base address for glue layer missing. Continuing without, some features are missing though."); + } else { + dwc3_imx->glue_base = devm_ioremap_resource(dev, res); + if (IS_ERR(dwc3_imx->glue_base)) + return PTR_ERR(dwc3_imx->glue_base); + } + + dwc3_imx->hsio_clk = devm_clk_get_enabled(dev, "hsio"); + if (IS_ERR(dwc3_imx->hsio_clk)) + return dev_err_probe(dev, PTR_ERR(dwc3_imx->hsio_clk), + "Failed to get hsio clk\n"); + + dwc3_imx->suspend_clk = devm_clk_get_enabled(dev, "suspend"); + if (IS_ERR(dwc3_imx->suspend_clk)) + return dev_err_probe(dev, PTR_ERR(dwc3_imx->suspend_clk), + "Failed to get suspend clk\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + dwc3_imx->irq = irq; + + struct device_node *dwc3_np __free(device_node) = of_get_compatible_child(node, + "snps,dwc3"); + if (!dwc3_np) + return dev_err_probe(dev, -ENODEV, "failed to find dwc3 core child\n"); + + imx8mp_configure_glue(dwc3_imx); + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + err = pm_runtime_get_sync(dev); + if (err < 0) + goto disable_rpm; + + err = device_add_software_node(dev, &dwc3_imx8mp_swnode); + if (err) { + err = -ENODEV; + dev_err(dev, "failed to add software node\n"); + goto disable_rpm; + } + + err = of_platform_populate(node, NULL, NULL, dev); + if (err) { + dev_err(&pdev->dev, "failed to create dwc3 core\n"); + goto remove_swnode; + } + + dwc3_imx->dwc3_pdev = of_find_device_by_node(dwc3_np); + if (!dwc3_imx->dwc3_pdev) { + dev_err(dev, "failed to get dwc3 platform device\n"); + err = -ENODEV; + goto depopulate; + } + + dwc3 = platform_get_drvdata(dwc3_imx->dwc3_pdev); + if (!dwc3) { + err = dev_err_probe(dev, -EPROBE_DEFER, "failed to get dwc3 platform data\n"); + goto put_dwc3; + } + + dwc3->glue_ops = &dwc3_imx_glue_ops; + + if (dwc3->dr_mode == USB_DR_MODE_HOST) + pm_runtime_dont_use_autosuspend(dwc3->dev); + + err = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx8mp_interrupt, + IRQF_ONESHOT, dev_name(dev), dwc3_imx); + if (err) { + dev_err(dev, "failed to request IRQ #%d --> %d\n", irq, err); + goto put_dwc3; + } + + device_set_wakeup_capable(dev, true); + pm_runtime_put(dev); + + return 0; + +put_dwc3: + put_device(&dwc3_imx->dwc3_pdev->dev); +depopulate: + of_platform_depopulate(dev); +remove_swnode: + device_remove_software_node(dev); +disable_rpm: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + + return err; +} + +static void dwc3_imx8mp_remove(struct platform_device *pdev) +{ + struct dwc3_imx8mp *dwc3_imx = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + put_device(&dwc3_imx->dwc3_pdev->dev); + + pm_runtime_get_sync(dev); + of_platform_depopulate(dev); + device_remove_software_node(dev); + + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); +} + +static int dwc3_imx8mp_suspend(struct dwc3_imx8mp *dwc3_imx, pm_message_t msg) +{ + if (dwc3_imx->pm_suspended) + return 0; + + /* Wakeup enable */ + if (PMSG_IS_AUTO(msg) || device_may_wakeup(dwc3_imx->dev)) + dwc3_imx8mp_wakeup_enable(dwc3_imx, msg); + + dwc3_imx->pm_suspended = true; + + return 0; +} + +static int dwc3_imx8mp_resume(struct dwc3_imx8mp *dwc3_imx, pm_message_t msg) +{ + struct dwc3 *dwc = platform_get_drvdata(dwc3_imx->dwc3_pdev); + int ret = 0; + + if (!dwc3_imx->pm_suspended) + return 0; + + /* Wakeup disable */ + dwc3_imx8mp_wakeup_disable(dwc3_imx); + dwc3_imx->pm_suspended = false; + + /* Upon power loss any previous configuration is lost, restore it */ + imx8mp_configure_glue(dwc3_imx); + + if (dwc3_imx->wakeup_pending) { + dwc3_imx->wakeup_pending = false; + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) { + pm_runtime_put_autosuspend(dwc->dev); + } else { + /* + * Add wait for xhci switch from suspend + * clock to normal clock to detect connection. + */ + usleep_range(9000, 10000); + } + enable_irq(dwc3_imx->irq); + } + + return ret; +} + +static int dwc3_imx8mp_pm_suspend(struct device *dev) +{ + struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); + int ret; + + ret = dwc3_imx8mp_suspend(dwc3_imx, PMSG_SUSPEND); + + if (device_may_wakeup(dwc3_imx->dev)) { + enable_irq_wake(dwc3_imx->irq); + + if (device_is_compatible(dev, "fsl,imx95-dwc3")) + device_set_out_band_wakeup(dev); + + } else { + clk_disable_unprepare(dwc3_imx->suspend_clk); + } + + clk_disable_unprepare(dwc3_imx->hsio_clk); + dev_dbg(dev, "dwc3 imx8mp pm suspend.\n"); + + return ret; +} + +static int dwc3_imx8mp_pm_resume(struct device *dev) +{ + struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); + int ret; + + if (device_may_wakeup(dwc3_imx->dev)) { + disable_irq_wake(dwc3_imx->irq); + } else { + ret = clk_prepare_enable(dwc3_imx->suspend_clk); + if (ret) + return ret; + } + + ret = clk_prepare_enable(dwc3_imx->hsio_clk); + if (ret) { + clk_disable_unprepare(dwc3_imx->suspend_clk); + return ret; + } + + ret = dwc3_imx8mp_resume(dwc3_imx, PMSG_RESUME); + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + dev_dbg(dev, "dwc3 imx8mp pm resume.\n"); + + return ret; +} + +static int dwc3_imx8mp_runtime_suspend(struct device *dev) +{ + struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); + + dev_dbg(dev, "dwc3 imx8mp runtime suspend.\n"); + + return dwc3_imx8mp_suspend(dwc3_imx, PMSG_AUTO_SUSPEND); +} + +static int dwc3_imx8mp_runtime_resume(struct device *dev) +{ + struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); + + dev_dbg(dev, "dwc3 imx8mp runtime resume.\n"); + + return dwc3_imx8mp_resume(dwc3_imx, PMSG_AUTO_RESUME); +} + +static const struct dev_pm_ops dwc3_imx8mp_dev_pm_ops = { + SYSTEM_SLEEP_PM_OPS(dwc3_imx8mp_pm_suspend, dwc3_imx8mp_pm_resume) + RUNTIME_PM_OPS(dwc3_imx8mp_runtime_suspend, dwc3_imx8mp_runtime_resume, + NULL) +}; + +static const struct of_device_id dwc3_imx8mp_of_match[] = { + { .compatible = "fsl,imx8mp-dwc3", }, + {}, +}; +MODULE_DEVICE_TABLE(of, dwc3_imx8mp_of_match); + +static struct platform_driver dwc3_imx8mp_driver = { + .probe = dwc3_imx8mp_probe, + .remove = dwc3_imx8mp_remove, + .driver = { + .name = "imx8mp-dwc3", + .pm = pm_ptr(&dwc3_imx8mp_dev_pm_ops), + .of_match_table = dwc3_imx8mp_of_match, + }, +}; + +module_platform_driver(dwc3_imx8mp_driver); + +MODULE_ALIAS("platform:imx8mp-dwc3"); +MODULE_AUTHOR("jun.li@nxp.com"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 imx8mp Glue Layer"); diff --git a/drivers/usb/dwc3/dwc3-keystone.c b/drivers/usb/dwc3/dwc3-keystone.c new file mode 100644 index 000000000..7ee161016 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-keystone.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-keystone.c - Keystone Specific Glue layer + * + * Copyright (C) 2010-2013 Texas Instruments Incorporated - https://www.ti.com + * + * Author: WingMan Kwok + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* USBSS register offsets */ +#define USBSS_REVISION 0x0000 +#define USBSS_SYSCONFIG 0x0010 +#define USBSS_IRQ_EOI 0x0018 +#define USBSS_IRQSTATUS_RAW_0 0x0020 +#define USBSS_IRQSTATUS_0 0x0024 +#define USBSS_IRQENABLE_SET_0 0x0028 +#define USBSS_IRQENABLE_CLR_0 0x002c + +/* IRQ register bits */ +#define USBSS_IRQ_EOI_LINE(n) BIT(n) +#define USBSS_IRQ_EVENT_ST BIT(0) +#define USBSS_IRQ_COREIRQ_EN BIT(0) +#define USBSS_IRQ_COREIRQ_CLR BIT(0) + +struct dwc3_keystone { + struct device *dev; + void __iomem *usbss; + struct phy *usb3_phy; +}; + +static inline u32 kdwc3_readl(void __iomem *base, u32 offset) +{ + return readl(base + offset); +} + +static inline void kdwc3_writel(void __iomem *base, u32 offset, u32 value) +{ + writel(value, base + offset); +} + +static void kdwc3_enable_irqs(struct dwc3_keystone *kdwc) +{ + u32 val; + + val = kdwc3_readl(kdwc->usbss, USBSS_IRQENABLE_SET_0); + val |= USBSS_IRQ_COREIRQ_EN; + kdwc3_writel(kdwc->usbss, USBSS_IRQENABLE_SET_0, val); +} + +static void kdwc3_disable_irqs(struct dwc3_keystone *kdwc) +{ + u32 val; + + val = kdwc3_readl(kdwc->usbss, USBSS_IRQENABLE_SET_0); + val &= ~USBSS_IRQ_COREIRQ_EN; + kdwc3_writel(kdwc->usbss, USBSS_IRQENABLE_SET_0, val); +} + +static irqreturn_t dwc3_keystone_interrupt(int irq, void *_kdwc) +{ + struct dwc3_keystone *kdwc = _kdwc; + + kdwc3_writel(kdwc->usbss, USBSS_IRQENABLE_CLR_0, USBSS_IRQ_COREIRQ_CLR); + kdwc3_writel(kdwc->usbss, USBSS_IRQSTATUS_0, USBSS_IRQ_EVENT_ST); + kdwc3_writel(kdwc->usbss, USBSS_IRQENABLE_SET_0, USBSS_IRQ_COREIRQ_EN); + kdwc3_writel(kdwc->usbss, USBSS_IRQ_EOI, USBSS_IRQ_EOI_LINE(0)); + + return IRQ_HANDLED; +} + +static int kdwc3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = pdev->dev.of_node; + struct dwc3_keystone *kdwc; + int error, irq; + + kdwc = devm_kzalloc(dev, sizeof(*kdwc), GFP_KERNEL); + if (!kdwc) + return -ENOMEM; + + platform_set_drvdata(pdev, kdwc); + + kdwc->dev = dev; + + kdwc->usbss = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(kdwc->usbss)) + return PTR_ERR(kdwc->usbss); + + /* PSC dependency on AM65 needs SERDES0 to be powered before USB0 */ + kdwc->usb3_phy = devm_phy_optional_get(dev, "usb3-phy"); + if (IS_ERR(kdwc->usb3_phy)) + return dev_err_probe(dev, PTR_ERR(kdwc->usb3_phy), "couldn't get usb3 phy\n"); + + phy_pm_runtime_get_sync(kdwc->usb3_phy); + + error = phy_reset(kdwc->usb3_phy); + if (error < 0) { + dev_err(dev, "usb3 phy reset failed: %d\n", error); + return error; + } + + error = phy_init(kdwc->usb3_phy); + if (error < 0) { + dev_err(dev, "usb3 phy init failed: %d\n", error); + return error; + } + + error = phy_power_on(kdwc->usb3_phy); + if (error < 0) { + dev_err(dev, "usb3 phy power on failed: %d\n", error); + phy_exit(kdwc->usb3_phy); + return error; + } + + pm_runtime_enable(kdwc->dev); + error = pm_runtime_get_sync(kdwc->dev); + if (error < 0) { + dev_err(kdwc->dev, "pm_runtime_get_sync failed, error %d\n", + error); + goto err_irq; + } + + /* IRQ processing not required currently for AM65 */ + if (of_device_is_compatible(node, "ti,am654-dwc3")) + goto skip_irq; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + error = irq; + goto err_irq; + } + + error = devm_request_irq(dev, irq, dwc3_keystone_interrupt, IRQF_SHARED, + dev_name(dev), kdwc); + if (error) { + dev_err(dev, "failed to request IRQ #%d --> %d\n", + irq, error); + goto err_irq; + } + + kdwc3_enable_irqs(kdwc); + +skip_irq: + error = of_platform_populate(node, NULL, NULL, dev); + if (error) { + dev_err(&pdev->dev, "failed to create dwc3 core\n"); + goto err_core; + } + + return 0; + +err_core: + kdwc3_disable_irqs(kdwc); +err_irq: + pm_runtime_put_sync(kdwc->dev); + pm_runtime_disable(kdwc->dev); + phy_power_off(kdwc->usb3_phy); + phy_exit(kdwc->usb3_phy); + phy_pm_runtime_put_sync(kdwc->usb3_phy); + + return error; +} + +static int kdwc3_remove_core(struct device *dev, void *c) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + + return 0; +} + +static void kdwc3_remove(struct platform_device *pdev) +{ + struct dwc3_keystone *kdwc = platform_get_drvdata(pdev); + struct device_node *node = pdev->dev.of_node; + + if (!of_device_is_compatible(node, "ti,am654-dwc3")) + kdwc3_disable_irqs(kdwc); + + device_for_each_child(&pdev->dev, NULL, kdwc3_remove_core); + pm_runtime_put_sync(kdwc->dev); + pm_runtime_disable(kdwc->dev); + + phy_power_off(kdwc->usb3_phy); + phy_exit(kdwc->usb3_phy); + phy_pm_runtime_put_sync(kdwc->usb3_phy); +} + +static const struct of_device_id kdwc3_of_match[] = { + { .compatible = "ti,keystone-dwc3", }, + { .compatible = "ti,am654-dwc3" }, + {}, +}; +MODULE_DEVICE_TABLE(of, kdwc3_of_match); + +static struct platform_driver kdwc3_driver = { + .probe = kdwc3_probe, + .remove = kdwc3_remove, + .driver = { + .name = "keystone-dwc3", + .of_match_table = kdwc3_of_match, + }, +}; + +module_platform_driver(kdwc3_driver); + +MODULE_ALIAS("platform:keystone-dwc3"); +MODULE_AUTHOR("WingMan Kwok "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 KEYSTONE Glue Layer"); diff --git a/drivers/usb/dwc3/dwc3-meson-g12a.c b/drivers/usb/dwc3/dwc3-meson-g12a.c new file mode 100644 index 000000000..4d611c08e --- /dev/null +++ b/drivers/usb/dwc3/dwc3-meson-g12a.c @@ -0,0 +1,989 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Glue for Amlogic G12A SoCs + * + * Copyright (c) 2019 BayLibre, SAS + * Author: Neil Armstrong + */ + +/* + * The USB is organized with a glue around the DWC3 Controller IP as : + * - Control registers for each USB2 Ports + * - Control registers for the USB PHY layer + * - SuperSpeed PHY can be enabled only if port is used + * - Dynamic OTG switching with ID change interrupt + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* USB2 Ports Control Registers, offsets are per-port */ + +#define U2P_REG_SIZE 0x20 + +#define U2P_R0 0x0 + #define U2P_R0_HOST_DEVICE BIT(0) + #define U2P_R0_POWER_OK BIT(1) + #define U2P_R0_HAST_MODE BIT(2) + #define U2P_R0_POWER_ON_RESET BIT(3) + #define U2P_R0_ID_PULLUP BIT(4) + #define U2P_R0_DRV_VBUS BIT(5) + +#define U2P_R1 0x4 + #define U2P_R1_PHY_READY BIT(0) + #define U2P_R1_ID_DIG BIT(1) + #define U2P_R1_OTG_SESSION_VALID BIT(2) + #define U2P_R1_VBUS_VALID BIT(3) + +/* USB Glue Control Registers */ + +#define G12A_GLUE_OFFSET 0x80 + +#define USB_R0 0x00 + #define USB_R0_P30_LANE0_TX2RX_LOOPBACK BIT(17) + #define USB_R0_P30_LANE0_EXT_PCLK_REQ BIT(18) + #define USB_R0_P30_PCS_RX_LOS_MASK_VAL_MASK GENMASK(28, 19) + #define USB_R0_U2D_SS_SCALEDOWN_MODE_MASK GENMASK(30, 29) + #define USB_R0_U2D_ACT BIT(31) + +#define USB_R1 0x04 + #define USB_R1_U3H_BIGENDIAN_GS BIT(0) + #define USB_R1_U3H_PME_ENABLE BIT(1) + #define USB_R1_U3H_HUB_PORT_OVERCURRENT_MASK GENMASK(4, 2) + #define USB_R1_U3H_HUB_PORT_PERM_ATTACH_MASK GENMASK(9, 7) + #define USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK GENMASK(13, 12) + #define USB_R1_U3H_HOST_U3_PORT_DISABLE BIT(16) + #define USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT BIT(17) + #define USB_R1_U3H_HOST_MSI_ENABLE BIT(18) + #define USB_R1_U3H_FLADJ_30MHZ_REG_MASK GENMASK(24, 19) + #define USB_R1_P30_PCS_TX_SWING_FULL_MASK GENMASK(31, 25) + +#define USB_R2 0x08 + #define USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK GENMASK(25, 20) + #define USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK GENMASK(31, 26) + +#define USB_R3 0x0c + #define USB_R3_P30_SSC_ENABLE BIT(0) + #define USB_R3_P30_SSC_RANGE_MASK GENMASK(3, 1) + #define USB_R3_P30_SSC_REF_CLK_SEL_MASK GENMASK(12, 4) + #define USB_R3_P30_REF_SSP_EN BIT(13) + +#define USB_R4 0x10 + #define USB_R4_P21_PORT_RESET_0 BIT(0) + #define USB_R4_P21_SLEEP_M0 BIT(1) + #define USB_R4_MEM_PD_MASK GENMASK(3, 2) + #define USB_R4_P21_ONLY BIT(4) + +#define USB_R5 0x14 + #define USB_R5_ID_DIG_SYNC BIT(0) + #define USB_R5_ID_DIG_REG BIT(1) + #define USB_R5_ID_DIG_CFG_MASK GENMASK(3, 2) + #define USB_R5_ID_DIG_EN_0 BIT(4) + #define USB_R5_ID_DIG_EN_1 BIT(5) + #define USB_R5_ID_DIG_CURR BIT(6) + #define USB_R5_ID_DIG_IRQ BIT(7) + #define USB_R5_ID_DIG_TH_MASK GENMASK(15, 8) + #define USB_R5_ID_DIG_CNT_MASK GENMASK(23, 16) + +#define PHY_COUNT 3 +#define USB2_OTG_PHY 1 + +static struct clk_bulk_data meson_gxl_clocks[] = { + { .id = "usb_ctrl" }, + { .id = "ddr" }, +}; + +static struct clk_bulk_data meson_g12a_clocks[] = { + { .id = NULL }, +}; + +static struct clk_bulk_data meson_a1_clocks[] = { + { .id = "usb_ctrl" }, + { .id = "usb_bus" }, + { .id = "xtal_usb_ctrl" }, +}; + +static const char * const meson_gxm_phy_names[] = { + "usb2-phy0", "usb2-phy1", "usb2-phy2", +}; + +static const char * const meson_g12a_phy_names[] = { + "usb2-phy0", "usb2-phy1", "usb3-phy0", +}; + +/* + * Amlogic A1 has a single physical PHY, in slot 1, but still has the + * two U2 PHY controls register blocks like G12A. + * AXG has the similar scheme, thus needs the same tweak. + * Handling the first PHY on slot 1 would need a large amount of code + * changes, and the current management is generic enough to handle it + * correctly when only the "usb2-phy1" phy is specified on-par with the + * DT bindings. + */ +static const char * const meson_a1_phy_names[] = { + "usb2-phy0", "usb2-phy1" +}; + +struct dwc3_meson_g12a; + +struct dwc3_meson_g12a_drvdata { + bool otg_phy_host_port_disable; + struct clk_bulk_data *clks; + int num_clks; + const char * const *phy_names; + int num_phys; + int (*setup_regmaps)(struct dwc3_meson_g12a *priv, void __iomem *base); + int (*usb2_init_phy)(struct dwc3_meson_g12a *priv, int i, + enum phy_mode mode); + int (*set_phy_mode)(struct dwc3_meson_g12a *priv, int i, + enum phy_mode mode); + int (*usb_init)(struct dwc3_meson_g12a *priv); + int (*usb_post_init)(struct dwc3_meson_g12a *priv); +}; + +static int dwc3_meson_gxl_setup_regmaps(struct dwc3_meson_g12a *priv, + void __iomem *base); +static int dwc3_meson_g12a_setup_regmaps(struct dwc3_meson_g12a *priv, + void __iomem *base); + +static int dwc3_meson_g12a_usb2_init_phy(struct dwc3_meson_g12a *priv, int i, + enum phy_mode mode); +static int dwc3_meson_gxl_usb2_init_phy(struct dwc3_meson_g12a *priv, int i, + enum phy_mode mode); + +static int dwc3_meson_g12a_set_phy_mode(struct dwc3_meson_g12a *priv, + int i, enum phy_mode mode); +static int dwc3_meson_gxl_set_phy_mode(struct dwc3_meson_g12a *priv, + int i, enum phy_mode mode); + +static int dwc3_meson_g12a_usb_init(struct dwc3_meson_g12a *priv); +static int dwc3_meson_gxl_usb_init(struct dwc3_meson_g12a *priv); + +static int dwc3_meson_gxl_usb_post_init(struct dwc3_meson_g12a *priv); + +/* + * For GXL and GXM SoCs: + * USB Phy muxing between the DWC2 Device controller and the DWC3 Host + * controller is buggy when switching from Device to Host when USB port + * is unpopulated, it causes the DWC3 to hard crash. + * When populated (including OTG switching with ID pin), the switch works + * like a charm like on the G12A platforms. + * In order to still switch from Host to Device on an USB Type-A port, + * an U2_PORT_DISABLE bit has been added to disconnect the DWC3 Host + * controller from the port, but when used the DWC3 controller must be + * reset to recover usage of the port. + */ + +static const struct dwc3_meson_g12a_drvdata gxl_drvdata = { + .otg_phy_host_port_disable = true, + .clks = meson_gxl_clocks, + .num_clks = ARRAY_SIZE(meson_g12a_clocks), + .phy_names = meson_a1_phy_names, + .num_phys = ARRAY_SIZE(meson_a1_phy_names), + .setup_regmaps = dwc3_meson_gxl_setup_regmaps, + .usb2_init_phy = dwc3_meson_gxl_usb2_init_phy, + .set_phy_mode = dwc3_meson_gxl_set_phy_mode, + .usb_init = dwc3_meson_gxl_usb_init, + .usb_post_init = dwc3_meson_gxl_usb_post_init, +}; + +static const struct dwc3_meson_g12a_drvdata gxm_drvdata = { + .otg_phy_host_port_disable = true, + .clks = meson_gxl_clocks, + .num_clks = ARRAY_SIZE(meson_g12a_clocks), + .phy_names = meson_gxm_phy_names, + .num_phys = ARRAY_SIZE(meson_gxm_phy_names), + .setup_regmaps = dwc3_meson_gxl_setup_regmaps, + .usb2_init_phy = dwc3_meson_gxl_usb2_init_phy, + .set_phy_mode = dwc3_meson_gxl_set_phy_mode, + .usb_init = dwc3_meson_gxl_usb_init, + .usb_post_init = dwc3_meson_gxl_usb_post_init, +}; + +static const struct dwc3_meson_g12a_drvdata axg_drvdata = { + .clks = meson_gxl_clocks, + .num_clks = ARRAY_SIZE(meson_gxl_clocks), + .phy_names = meson_a1_phy_names, + .num_phys = ARRAY_SIZE(meson_a1_phy_names), + .setup_regmaps = dwc3_meson_gxl_setup_regmaps, + .usb2_init_phy = dwc3_meson_gxl_usb2_init_phy, + .set_phy_mode = dwc3_meson_gxl_set_phy_mode, + .usb_init = dwc3_meson_g12a_usb_init, + .usb_post_init = dwc3_meson_gxl_usb_post_init, +}; + +static const struct dwc3_meson_g12a_drvdata g12a_drvdata = { + .clks = meson_g12a_clocks, + .num_clks = ARRAY_SIZE(meson_g12a_clocks), + .phy_names = meson_g12a_phy_names, + .num_phys = ARRAY_SIZE(meson_g12a_phy_names), + .setup_regmaps = dwc3_meson_g12a_setup_regmaps, + .usb2_init_phy = dwc3_meson_g12a_usb2_init_phy, + .set_phy_mode = dwc3_meson_g12a_set_phy_mode, + .usb_init = dwc3_meson_g12a_usb_init, +}; + +static const struct dwc3_meson_g12a_drvdata a1_drvdata = { + .clks = meson_a1_clocks, + .num_clks = ARRAY_SIZE(meson_a1_clocks), + .phy_names = meson_a1_phy_names, + .num_phys = ARRAY_SIZE(meson_a1_phy_names), + .setup_regmaps = dwc3_meson_g12a_setup_regmaps, + .usb2_init_phy = dwc3_meson_g12a_usb2_init_phy, + .set_phy_mode = dwc3_meson_g12a_set_phy_mode, + .usb_init = dwc3_meson_g12a_usb_init, +}; + +struct dwc3_meson_g12a { + struct device *dev; + struct regmap *u2p_regmap[PHY_COUNT]; + struct regmap *usb_glue_regmap; + struct reset_control *reset; + struct phy *phys[PHY_COUNT]; + enum usb_dr_mode otg_mode; + enum phy_mode otg_phy_mode; + unsigned int usb2_ports; + unsigned int usb3_ports; + struct regulator *vbus; + struct usb_role_switch_desc switch_desc; + struct usb_role_switch *role_switch; + const struct dwc3_meson_g12a_drvdata *drvdata; +}; + +static int dwc3_meson_gxl_set_phy_mode(struct dwc3_meson_g12a *priv, + int i, enum phy_mode mode) +{ + return phy_set_mode(priv->phys[i], mode); +} + +static int dwc3_meson_gxl_usb2_init_phy(struct dwc3_meson_g12a *priv, int i, + enum phy_mode mode) +{ + /* On GXL PHY must be started in device mode for DWC2 init */ + return priv->drvdata->set_phy_mode(priv, i, + (i == USB2_OTG_PHY) ? PHY_MODE_USB_DEVICE + : PHY_MODE_USB_HOST); +} + +static int dwc3_meson_g12a_set_phy_mode(struct dwc3_meson_g12a *priv, + int i, enum phy_mode mode) +{ + if (mode == PHY_MODE_USB_HOST) + regmap_update_bits(priv->u2p_regmap[i], U2P_R0, + U2P_R0_HOST_DEVICE, + U2P_R0_HOST_DEVICE); + else + regmap_update_bits(priv->u2p_regmap[i], U2P_R0, + U2P_R0_HOST_DEVICE, 0); + + return 0; +} + +static int dwc3_meson_g12a_usb2_init_phy(struct dwc3_meson_g12a *priv, int i, + enum phy_mode mode) +{ + int ret; + + regmap_update_bits(priv->u2p_regmap[i], U2P_R0, + U2P_R0_POWER_ON_RESET, + U2P_R0_POWER_ON_RESET); + + if (i == USB2_OTG_PHY) { + regmap_update_bits(priv->u2p_regmap[i], U2P_R0, + U2P_R0_ID_PULLUP | U2P_R0_DRV_VBUS, + U2P_R0_ID_PULLUP | U2P_R0_DRV_VBUS); + + ret = priv->drvdata->set_phy_mode(priv, i, mode); + } else + ret = priv->drvdata->set_phy_mode(priv, i, + PHY_MODE_USB_HOST); + + if (ret) + return ret; + + regmap_update_bits(priv->u2p_regmap[i], U2P_R0, + U2P_R0_POWER_ON_RESET, 0); + + return 0; +} + +static int dwc3_meson_g12a_usb2_init(struct dwc3_meson_g12a *priv, + enum phy_mode mode) +{ + int i, ret; + + for (i = 0; i < priv->drvdata->num_phys; ++i) { + if (!priv->phys[i]) + continue; + + if (!strstr(priv->drvdata->phy_names[i], "usb2")) + continue; + + ret = priv->drvdata->usb2_init_phy(priv, i, mode); + if (ret) + return ret; + } + + return 0; +} + +static void dwc3_meson_g12a_usb3_init(struct dwc3_meson_g12a *priv) +{ + regmap_update_bits(priv->usb_glue_regmap, USB_R3, + USB_R3_P30_SSC_RANGE_MASK | + USB_R3_P30_REF_SSP_EN, + USB_R3_P30_SSC_ENABLE | + FIELD_PREP(USB_R3_P30_SSC_RANGE_MASK, 2) | + USB_R3_P30_REF_SSP_EN); + udelay(2); + + regmap_update_bits(priv->usb_glue_regmap, USB_R2, + USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK, + FIELD_PREP(USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK, 0x15)); + + regmap_update_bits(priv->usb_glue_regmap, USB_R2, + USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK, + FIELD_PREP(USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK, 0x20)); + + udelay(2); + + regmap_update_bits(priv->usb_glue_regmap, USB_R1, + USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT, + USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT); + + regmap_update_bits(priv->usb_glue_regmap, USB_R1, + USB_R1_P30_PCS_TX_SWING_FULL_MASK, + FIELD_PREP(USB_R1_P30_PCS_TX_SWING_FULL_MASK, 127)); +} + +static void dwc3_meson_g12a_usb_otg_apply_mode(struct dwc3_meson_g12a *priv, + enum phy_mode mode) +{ + if (mode == PHY_MODE_USB_DEVICE) { + if (priv->otg_mode != USB_DR_MODE_OTG && + priv->drvdata->otg_phy_host_port_disable) + /* Isolate the OTG PHY port from the Host Controller */ + regmap_update_bits(priv->usb_glue_regmap, USB_R1, + USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK, + FIELD_PREP(USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK, + BIT(USB2_OTG_PHY))); + + regmap_update_bits(priv->usb_glue_regmap, USB_R0, + USB_R0_U2D_ACT, USB_R0_U2D_ACT); + regmap_update_bits(priv->usb_glue_regmap, USB_R0, + USB_R0_U2D_SS_SCALEDOWN_MODE_MASK, 0); + regmap_update_bits(priv->usb_glue_regmap, USB_R4, + USB_R4_P21_SLEEP_M0, USB_R4_P21_SLEEP_M0); + } else { + if (priv->otg_mode != USB_DR_MODE_OTG && + priv->drvdata->otg_phy_host_port_disable) { + regmap_update_bits(priv->usb_glue_regmap, USB_R1, + USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK, 0); + msleep(500); + } + regmap_update_bits(priv->usb_glue_regmap, USB_R0, + USB_R0_U2D_ACT, 0); + regmap_update_bits(priv->usb_glue_regmap, USB_R4, + USB_R4_P21_SLEEP_M0, 0); + } +} + +static int dwc3_meson_g12a_usb_init_glue(struct dwc3_meson_g12a *priv, + enum phy_mode mode) +{ + int ret; + + ret = dwc3_meson_g12a_usb2_init(priv, mode); + if (ret) + return ret; + + regmap_update_bits(priv->usb_glue_regmap, USB_R1, + USB_R1_U3H_FLADJ_30MHZ_REG_MASK, + FIELD_PREP(USB_R1_U3H_FLADJ_30MHZ_REG_MASK, 0x20)); + + regmap_update_bits(priv->usb_glue_regmap, USB_R5, + USB_R5_ID_DIG_EN_0, + USB_R5_ID_DIG_EN_0); + regmap_update_bits(priv->usb_glue_regmap, USB_R5, + USB_R5_ID_DIG_EN_1, + USB_R5_ID_DIG_EN_1); + regmap_update_bits(priv->usb_glue_regmap, USB_R5, + USB_R5_ID_DIG_TH_MASK, + FIELD_PREP(USB_R5_ID_DIG_TH_MASK, 0xff)); + + /* If we have an actual SuperSpeed port, initialize it */ + if (priv->usb3_ports) + dwc3_meson_g12a_usb3_init(priv); + + dwc3_meson_g12a_usb_otg_apply_mode(priv, mode); + + return 0; +} + +static const struct regmap_config phy_meson_g12a_usb_glue_regmap_conf = { + .name = "usb-glue", + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = USB_R5, +}; + +static int dwc3_meson_g12a_get_phys(struct dwc3_meson_g12a *priv) +{ + const char *phy_name; + int i; + + for (i = 0 ; i < priv->drvdata->num_phys ; ++i) { + phy_name = priv->drvdata->phy_names[i]; + priv->phys[i] = devm_phy_optional_get(priv->dev, phy_name); + if (!priv->phys[i]) + continue; + + if (IS_ERR(priv->phys[i])) + return PTR_ERR(priv->phys[i]); + + if (strstr(phy_name, "usb3")) + priv->usb3_ports++; + else + priv->usb2_ports++; + } + + dev_info(priv->dev, "USB2 ports: %d\n", priv->usb2_ports); + dev_info(priv->dev, "USB3 ports: %d\n", priv->usb3_ports); + + return 0; +} + +static enum phy_mode dwc3_meson_g12a_get_id(struct dwc3_meson_g12a *priv) +{ + u32 reg; + + regmap_read(priv->usb_glue_regmap, USB_R5, ®); + + if (reg & (USB_R5_ID_DIG_SYNC | USB_R5_ID_DIG_REG)) + return PHY_MODE_USB_DEVICE; + + return PHY_MODE_USB_HOST; +} + +static int dwc3_meson_g12a_otg_mode_set(struct dwc3_meson_g12a *priv, + enum phy_mode mode) +{ + int ret; + + if (!priv->phys[USB2_OTG_PHY]) + return -EINVAL; + + if (mode == PHY_MODE_USB_HOST) + dev_info(priv->dev, "switching to Host Mode\n"); + else + dev_info(priv->dev, "switching to Device Mode\n"); + + if (priv->vbus) { + if (mode == PHY_MODE_USB_DEVICE) + ret = regulator_disable(priv->vbus); + else + ret = regulator_enable(priv->vbus); + if (ret) + return ret; + } + + priv->otg_phy_mode = mode; + + ret = priv->drvdata->set_phy_mode(priv, USB2_OTG_PHY, mode); + if (ret) + return ret; + + dwc3_meson_g12a_usb_otg_apply_mode(priv, mode); + + return 0; +} + +static int dwc3_meson_g12a_role_set(struct usb_role_switch *sw, + enum usb_role role) +{ + struct dwc3_meson_g12a *priv = usb_role_switch_get_drvdata(sw); + enum phy_mode mode; + + if (role == USB_ROLE_NONE) + return 0; + + mode = (role == USB_ROLE_HOST) ? PHY_MODE_USB_HOST + : PHY_MODE_USB_DEVICE; + + if (mode == priv->otg_phy_mode) + return 0; + + if (priv->drvdata->otg_phy_host_port_disable) + dev_warn_once(priv->dev, "Broken manual OTG switch\n"); + + return dwc3_meson_g12a_otg_mode_set(priv, mode); +} + +static enum usb_role dwc3_meson_g12a_role_get(struct usb_role_switch *sw) +{ + struct dwc3_meson_g12a *priv = usb_role_switch_get_drvdata(sw); + + return priv->otg_phy_mode == PHY_MODE_USB_HOST ? + USB_ROLE_HOST : USB_ROLE_DEVICE; +} + +static irqreturn_t dwc3_meson_g12a_irq_thread(int irq, void *data) +{ + struct dwc3_meson_g12a *priv = data; + enum phy_mode otg_id; + + otg_id = dwc3_meson_g12a_get_id(priv); + if (otg_id != priv->otg_phy_mode) { + if (dwc3_meson_g12a_otg_mode_set(priv, otg_id)) + dev_warn(priv->dev, "Failed to switch OTG mode\n"); + } + + regmap_update_bits(priv->usb_glue_regmap, USB_R5, + USB_R5_ID_DIG_IRQ, 0); + + return IRQ_HANDLED; +} + +static struct device *dwc3_meson_g12_find_child(struct device *dev, + const char *compatible) +{ + struct platform_device *pdev; + struct device_node *np; + + np = of_get_compatible_child(dev->of_node, compatible); + if (!np) + return NULL; + + pdev = of_find_device_by_node(np); + of_node_put(np); + if (!pdev) + return NULL; + + return &pdev->dev; +} + +static int dwc3_meson_g12a_otg_init(struct platform_device *pdev, + struct dwc3_meson_g12a *priv) +{ + enum phy_mode otg_id; + int ret, irq; + struct device *dev = &pdev->dev; + + if (priv->otg_mode == USB_DR_MODE_OTG) { + /* Ack irq before registering */ + regmap_update_bits(priv->usb_glue_regmap, USB_R5, + USB_R5_ID_DIG_IRQ, 0); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + dwc3_meson_g12a_irq_thread, + IRQF_ONESHOT, pdev->name, priv); + if (ret) + return ret; + } + + /* Setup OTG mode corresponding to the ID pin */ + if (priv->otg_mode == USB_DR_MODE_OTG) { + otg_id = dwc3_meson_g12a_get_id(priv); + if (otg_id != priv->otg_phy_mode) { + if (dwc3_meson_g12a_otg_mode_set(priv, otg_id)) + dev_warn(dev, "Failed to switch OTG mode\n"); + } + } + + /* Setup role switcher */ + priv->switch_desc.usb2_port = dwc3_meson_g12_find_child(dev, + "snps,dwc3"); + priv->switch_desc.udc = dwc3_meson_g12_find_child(dev, "snps,dwc2"); + priv->switch_desc.allow_userspace_control = true; + priv->switch_desc.set = dwc3_meson_g12a_role_set; + priv->switch_desc.get = dwc3_meson_g12a_role_get; + priv->switch_desc.driver_data = priv; + + priv->role_switch = usb_role_switch_register(dev, &priv->switch_desc); + if (IS_ERR(priv->role_switch)) + dev_warn(dev, "Unable to register Role Switch\n"); + + return 0; +} + +static int dwc3_meson_gxl_setup_regmaps(struct dwc3_meson_g12a *priv, + void __iomem *base) +{ + /* GXL controls the PHY mode in the PHY registers unlike G12A */ + priv->usb_glue_regmap = devm_regmap_init_mmio(priv->dev, base, + &phy_meson_g12a_usb_glue_regmap_conf); + return PTR_ERR_OR_ZERO(priv->usb_glue_regmap); +} + +static int dwc3_meson_g12a_setup_regmaps(struct dwc3_meson_g12a *priv, + void __iomem *base) +{ + int i; + + priv->usb_glue_regmap = devm_regmap_init_mmio(priv->dev, + base + G12A_GLUE_OFFSET, + &phy_meson_g12a_usb_glue_regmap_conf); + if (IS_ERR(priv->usb_glue_regmap)) + return PTR_ERR(priv->usb_glue_regmap); + + /* Create a regmap for each USB2 PHY control register set */ + for (i = 0; i < priv->drvdata->num_phys; i++) { + struct regmap_config u2p_regmap_config = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = U2P_R1, + }; + + if (!strstr(priv->drvdata->phy_names[i], "usb2")) + continue; + + u2p_regmap_config.name = devm_kasprintf(priv->dev, GFP_KERNEL, + "u2p-%d", i); + if (!u2p_regmap_config.name) + return -ENOMEM; + + priv->u2p_regmap[i] = devm_regmap_init_mmio(priv->dev, + base + (i * U2P_REG_SIZE), + &u2p_regmap_config); + if (IS_ERR(priv->u2p_regmap[i])) + return PTR_ERR(priv->u2p_regmap[i]); + } + + return 0; +} + +static int dwc3_meson_g12a_usb_init(struct dwc3_meson_g12a *priv) +{ + return dwc3_meson_g12a_usb_init_glue(priv, priv->otg_phy_mode); +} + +static int dwc3_meson_gxl_usb_init(struct dwc3_meson_g12a *priv) +{ + return dwc3_meson_g12a_usb_init_glue(priv, PHY_MODE_USB_DEVICE); +} + +static int dwc3_meson_gxl_usb_post_init(struct dwc3_meson_g12a *priv) +{ + int ret; + + ret = priv->drvdata->set_phy_mode(priv, USB2_OTG_PHY, + priv->otg_phy_mode); + if (ret) + return ret; + + dwc3_meson_g12a_usb_otg_apply_mode(priv, priv->otg_phy_mode); + + return 0; +} + +static int dwc3_meson_g12a_probe(struct platform_device *pdev) +{ + struct dwc3_meson_g12a *priv; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + void __iomem *base; + int ret, i; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->drvdata = of_device_get_match_data(&pdev->dev); + priv->dev = dev; + + priv->vbus = devm_regulator_get_optional(dev, "vbus"); + if (IS_ERR(priv->vbus)) { + if (PTR_ERR(priv->vbus) == -EPROBE_DEFER) + return PTR_ERR(priv->vbus); + priv->vbus = NULL; + } + + ret = devm_clk_bulk_get(dev, + priv->drvdata->num_clks, + priv->drvdata->clks); + if (ret) + return ret; + + ret = clk_bulk_prepare_enable(priv->drvdata->num_clks, + priv->drvdata->clks); + if (ret) + return ret; + + platform_set_drvdata(pdev, priv); + + priv->reset = devm_reset_control_get_shared(dev, NULL); + if (IS_ERR(priv->reset)) { + ret = PTR_ERR(priv->reset); + dev_err(dev, "failed to get device reset, err=%d\n", ret); + goto err_disable_clks; + } + + ret = reset_control_reset(priv->reset); + if (ret) + goto err_disable_clks; + + ret = dwc3_meson_g12a_get_phys(priv); + if (ret) + goto err_rearm; + + ret = priv->drvdata->setup_regmaps(priv, base); + if (ret) + goto err_rearm; + + if (priv->vbus) { + ret = regulator_enable(priv->vbus); + if (ret) + goto err_rearm; + } + + /* Get dr_mode */ + priv->otg_mode = usb_get_dr_mode(dev); + + if (priv->otg_mode == USB_DR_MODE_PERIPHERAL) + priv->otg_phy_mode = PHY_MODE_USB_DEVICE; + else + priv->otg_phy_mode = PHY_MODE_USB_HOST; + + ret = priv->drvdata->usb_init(priv); + if (ret) + goto err_disable_regulator; + + /* Init PHYs */ + for (i = 0 ; i < PHY_COUNT ; ++i) { + ret = phy_init(priv->phys[i]); + if (ret) + goto err_disable_regulator; + } + + /* Set PHY Power */ + for (i = 0 ; i < PHY_COUNT ; ++i) { + ret = phy_power_on(priv->phys[i]); + if (ret) + goto err_phys_exit; + } + + if (priv->drvdata->usb_post_init) { + ret = priv->drvdata->usb_post_init(priv); + if (ret) + goto err_phys_power; + } + + ret = of_platform_populate(np, NULL, NULL, dev); + if (ret) + goto err_phys_power; + + ret = dwc3_meson_g12a_otg_init(pdev, priv); + if (ret) + goto err_plat_depopulate; + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + return 0; + +err_plat_depopulate: + of_platform_depopulate(dev); + +err_phys_power: + for (i = 0 ; i < PHY_COUNT ; ++i) + phy_power_off(priv->phys[i]); + +err_phys_exit: + for (i = 0 ; i < PHY_COUNT ; ++i) + phy_exit(priv->phys[i]); + +err_disable_regulator: + if (priv->vbus) + regulator_disable(priv->vbus); + +err_rearm: + reset_control_rearm(priv->reset); + +err_disable_clks: + clk_bulk_disable_unprepare(priv->drvdata->num_clks, + priv->drvdata->clks); + + return ret; +} + +static void dwc3_meson_g12a_remove(struct platform_device *pdev) +{ + struct dwc3_meson_g12a *priv = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + int i; + + usb_role_switch_unregister(priv->role_switch); + + put_device(priv->switch_desc.udc); + put_device(priv->switch_desc.usb2_port); + + of_platform_depopulate(dev); + + for (i = 0 ; i < PHY_COUNT ; ++i) { + phy_power_off(priv->phys[i]); + phy_exit(priv->phys[i]); + } + + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + pm_runtime_set_suspended(dev); + + reset_control_rearm(priv->reset); + + clk_bulk_disable_unprepare(priv->drvdata->num_clks, + priv->drvdata->clks); +} + +static int __maybe_unused dwc3_meson_g12a_runtime_suspend(struct device *dev) +{ + struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(priv->drvdata->num_clks, + priv->drvdata->clks); + + return 0; +} + +static int __maybe_unused dwc3_meson_g12a_runtime_resume(struct device *dev) +{ + struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); + + return clk_bulk_prepare_enable(priv->drvdata->num_clks, + priv->drvdata->clks); +} + +static int __maybe_unused dwc3_meson_g12a_suspend(struct device *dev) +{ + struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); + int i, ret; + + if (priv->vbus && priv->otg_phy_mode == PHY_MODE_USB_HOST) { + ret = regulator_disable(priv->vbus); + if (ret) + return ret; + } + + for (i = 0 ; i < PHY_COUNT ; ++i) { + phy_power_off(priv->phys[i]); + phy_exit(priv->phys[i]); + } + + reset_control_rearm(priv->reset); + + return 0; +} + +static int __maybe_unused dwc3_meson_g12a_resume(struct device *dev) +{ + struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); + int i, ret; + + ret = reset_control_reset(priv->reset); + if (ret) + return ret; + + ret = priv->drvdata->usb_init(priv); + if (ret) + goto err_rearm; + + /* Init PHYs */ + for (i = 0 ; i < PHY_COUNT ; ++i) { + ret = phy_init(priv->phys[i]); + if (ret) + goto err_rearm; + } + + /* Set PHY Power */ + for (i = 0 ; i < PHY_COUNT ; ++i) { + ret = phy_power_on(priv->phys[i]); + if (ret) + goto err_rearm; + } + + if (priv->vbus && priv->otg_phy_mode == PHY_MODE_USB_HOST) { + ret = regulator_enable(priv->vbus); + if (ret) + goto err_rearm; + } + + if (priv->drvdata->usb_post_init) { + ret = priv->drvdata->usb_post_init(priv); + if (ret) + goto err_rearm; + } + + return 0; + +err_rearm: + reset_control_rearm(priv->reset); + return ret; +} + +static const struct dev_pm_ops dwc3_meson_g12a_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_meson_g12a_suspend, dwc3_meson_g12a_resume) + SET_RUNTIME_PM_OPS(dwc3_meson_g12a_runtime_suspend, + dwc3_meson_g12a_runtime_resume, NULL) +}; + +static const struct of_device_id dwc3_meson_g12a_match[] = { + { + .compatible = "amlogic,meson-gxl-usb-ctrl", + .data = &gxl_drvdata, + }, + { + .compatible = "amlogic,meson-gxm-usb-ctrl", + .data = &gxm_drvdata, + }, + { + .compatible = "amlogic,meson-axg-usb-ctrl", + .data = &axg_drvdata, + }, + { + .compatible = "amlogic,meson-g12a-usb-ctrl", + .data = &g12a_drvdata, + }, + { + .compatible = "amlogic,meson-a1-usb-ctrl", + .data = &a1_drvdata, + }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dwc3_meson_g12a_match); + +static struct platform_driver dwc3_meson_g12a_driver = { + .probe = dwc3_meson_g12a_probe, + .remove = dwc3_meson_g12a_remove, + .driver = { + .name = "dwc3-meson-g12a", + .of_match_table = dwc3_meson_g12a_match, + .pm = &dwc3_meson_g12a_dev_pm_ops, + }, +}; + +module_platform_driver(dwc3_meson_g12a_driver); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Amlogic Meson G12A USB Glue Layer"); +MODULE_AUTHOR("Neil Armstrong "); diff --git a/drivers/usb/dwc3/dwc3-octeon.c b/drivers/usb/dwc3/dwc3-octeon.c new file mode 100644 index 000000000..2201f0f34 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-octeon.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DWC3 glue for Cavium Octeon III SOCs. + * + * Copyright (C) 2010-2017 Cavium Networks + * Copyright (C) 2023 RACOM s.r.o. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * USB Control Register + */ +#define USBDRD_UCTL_CTL 0x00 +/* BIST fast-clear mode select. A BIST run with this bit set + * clears all entries in USBH RAMs to 0x0. + */ +# define USBDRD_UCTL_CTL_CLEAR_BIST BIT_ULL(63) +/* 1 = Start BIST and cleared by hardware */ +# define USBDRD_UCTL_CTL_START_BIST BIT_ULL(62) +/* Reference clock select for SuperSpeed and HighSpeed PLLs: + * 0x0 = Both PLLs use DLMC_REF_CLK0 for reference clock + * 0x1 = Both PLLs use DLMC_REF_CLK1 for reference clock + * 0x2 = SuperSpeed PLL uses DLMC_REF_CLK0 for reference clock & + * HighSpeed PLL uses PLL_REF_CLK for reference clck + * 0x3 = SuperSpeed PLL uses DLMC_REF_CLK1 for reference clock & + * HighSpeed PLL uses PLL_REF_CLK for reference clck + */ +# define USBDRD_UCTL_CTL_REF_CLK_SEL GENMASK_ULL(61, 60) +/* 1 = Spread-spectrum clock enable, 0 = SS clock disable */ +# define USBDRD_UCTL_CTL_SSC_EN BIT_ULL(59) +/* Spread-spectrum clock modulation range: + * 0x0 = -4980 ppm downspread + * 0x1 = -4492 ppm downspread + * 0x2 = -4003 ppm downspread + * 0x3 - 0x7 = Reserved + */ +# define USBDRD_UCTL_CTL_SSC_RANGE GENMASK_ULL(58, 56) +/* Enable non-standard oscillator frequencies: + * [55:53] = modules -1 + * [52:47] = 2's complement push amount, 0 = Feature disabled + */ +# define USBDRD_UCTL_CTL_SSC_REF_CLK_SEL GENMASK_ULL(55, 47) +/* Reference clock multiplier for non-standard frequencies: + * 0x19 = 100MHz on DLMC_REF_CLK* if REF_CLK_SEL = 0x0 or 0x1 + * 0x28 = 125MHz on DLMC_REF_CLK* if REF_CLK_SEL = 0x0 or 0x1 + * 0x32 = 50MHz on DLMC_REF_CLK* if REF_CLK_SEL = 0x0 or 0x1 + * Other Values = Reserved + */ +# define USBDRD_UCTL_CTL_MPLL_MULTIPLIER GENMASK_ULL(46, 40) +/* Enable reference clock to prescaler for SuperSpeed functionality. + * Should always be set to "1" + */ +# define USBDRD_UCTL_CTL_REF_SSP_EN BIT_ULL(39) +/* Divide the reference clock by 2 before entering the + * REF_CLK_FSEL divider: + * If REF_CLK_SEL = 0x0 or 0x1, then only 0x0 is legal + * If REF_CLK_SEL = 0x2 or 0x3, then: + * 0x1 = DLMC_REF_CLK* is 125MHz + * 0x0 = DLMC_REF_CLK* is another supported frequency + */ +# define USBDRD_UCTL_CTL_REF_CLK_DIV2 BIT_ULL(38) +/* Select reference clock freqnuency for both PLL blocks: + * 0x27 = REF_CLK_SEL is 0x0 or 0x1 + * 0x07 = REF_CLK_SEL is 0x2 or 0x3 + */ +# define USBDRD_UCTL_CTL_REF_CLK_FSEL GENMASK_ULL(37, 32) +/* Controller clock enable. */ +# define USBDRD_UCTL_CTL_H_CLK_EN BIT_ULL(30) +/* Select bypass input to controller clock divider: + * 0x0 = Use divided coprocessor clock from H_CLKDIV + * 0x1 = Use clock from GPIO pins + */ +# define USBDRD_UCTL_CTL_H_CLK_BYP_SEL BIT_ULL(29) +/* Reset controller clock divider. */ +# define USBDRD_UCTL_CTL_H_CLKDIV_RST BIT_ULL(28) +/* Clock divider select: + * 0x0 = divide by 1 + * 0x1 = divide by 2 + * 0x2 = divide by 4 + * 0x3 = divide by 6 + * 0x4 = divide by 8 + * 0x5 = divide by 16 + * 0x6 = divide by 24 + * 0x7 = divide by 32 + */ +# define USBDRD_UCTL_CTL_H_CLKDIV_SEL GENMASK_ULL(26, 24) +/* USB3 port permanently attached: 0x0 = No, 0x1 = Yes */ +# define USBDRD_UCTL_CTL_USB3_PORT_PERM_ATTACH BIT_ULL(21) +/* USB2 port permanently attached: 0x0 = No, 0x1 = Yes */ +# define USBDRD_UCTL_CTL_USB2_PORT_PERM_ATTACH BIT_ULL(20) +/* Disable SuperSpeed PHY: 0x0 = No, 0x1 = Yes */ +# define USBDRD_UCTL_CTL_USB3_PORT_DISABLE BIT_ULL(18) +/* Disable HighSpeed PHY: 0x0 = No, 0x1 = Yes */ +# define USBDRD_UCTL_CTL_USB2_PORT_DISABLE BIT_ULL(16) +/* Enable PHY SuperSpeed block power: 0x0 = No, 0x1 = Yes */ +# define USBDRD_UCTL_CTL_SS_POWER_EN BIT_ULL(14) +/* Enable PHY HighSpeed block power: 0x0 = No, 0x1 = Yes */ +# define USBDRD_UCTL_CTL_HS_POWER_EN BIT_ULL(12) +/* Enable USB UCTL interface clock: 0xx = No, 0x1 = Yes */ +# define USBDRD_UCTL_CTL_CSCLK_EN BIT_ULL(4) +/* Controller mode: 0x0 = Host, 0x1 = Device */ +# define USBDRD_UCTL_CTL_DRD_MODE BIT_ULL(3) +/* PHY reset */ +# define USBDRD_UCTL_CTL_UPHY_RST BIT_ULL(2) +/* Software reset UAHC */ +# define USBDRD_UCTL_CTL_UAHC_RST BIT_ULL(1) +/* Software resets UCTL */ +# define USBDRD_UCTL_CTL_UCTL_RST BIT_ULL(0) + +#define USBDRD_UCTL_BIST_STATUS 0x08 +#define USBDRD_UCTL_SPARE0 0x10 +#define USBDRD_UCTL_INTSTAT 0x30 +#define USBDRD_UCTL_PORT_CFG_HS(port) (0x40 + (0x20 * port)) +#define USBDRD_UCTL_PORT_CFG_SS(port) (0x48 + (0x20 * port)) +#define USBDRD_UCTL_PORT_CR_DBG_CFG(port) (0x50 + (0x20 * port)) +#define USBDRD_UCTL_PORT_CR_DBG_STATUS(port) (0x58 + (0x20 * port)) + +/* + * UCTL Configuration Register + */ +#define USBDRD_UCTL_HOST_CFG 0xe0 +/* Indicates minimum value of all received BELT values */ +# define USBDRD_UCTL_HOST_CFG_HOST_CURRENT_BELT GENMASK_ULL(59, 48) +/* HS jitter adjustment */ +# define USBDRD_UCTL_HOST_CFG_FLA GENMASK_ULL(37, 32) +/* Bus-master enable: 0x0 = Disabled (stall DMAs), 0x1 = enabled */ +# define USBDRD_UCTL_HOST_CFG_BME BIT_ULL(28) +/* Overcurrent protection enable: 0x0 = unavailable, 0x1 = available */ +# define USBDRD_UCTL_HOST_OCI_EN BIT_ULL(27) +/* Overcurrent sene selection: + * 0x0 = Overcurrent indication from off-chip is active-low + * 0x1 = Overcurrent indication from off-chip is active-high + */ +# define USBDRD_UCTL_HOST_OCI_ACTIVE_HIGH_EN BIT_ULL(26) +/* Port power control enable: 0x0 = unavailable, 0x1 = available */ +# define USBDRD_UCTL_HOST_PPC_EN BIT_ULL(25) +/* Port power control sense selection: + * 0x0 = Port power to off-chip is active-low + * 0x1 = Port power to off-chip is active-high + */ +# define USBDRD_UCTL_HOST_PPC_ACTIVE_HIGH_EN BIT_ULL(24) + +/* + * UCTL Shim Features Register + */ +#define USBDRD_UCTL_SHIM_CFG 0xe8 +/* Out-of-bound UAHC register access: 0 = read, 1 = write */ +# define USBDRD_UCTL_SHIM_CFG_XS_NCB_OOB_WRN BIT_ULL(63) +/* SRCID error log for out-of-bound UAHC register access: + * [59:58] = chipID + * [57] = Request source: 0 = core, 1 = NCB-device + * [56:51] = Core/NCB-device number, [56] always 0 for NCB devices + * [50:48] = SubID + */ +# define USBDRD_UCTL_SHIM_CFG_XS_NCB_OOB_OSRC GENMASK_ULL(59, 48) +/* Error log for bad UAHC DMA access: 0 = Read log, 1 = Write log */ +# define USBDRD_UCTL_SHIM_CFG_XM_BAD_DMA_WRN BIT_ULL(47) +/* Encoded error type for bad UAHC DMA */ +# define USBDRD_UCTL_SHIM_CFG_XM_BAD_DMA_TYPE GENMASK_ULL(43, 40) +/* Select the IOI read command used by DMA accesses */ +# define USBDRD_UCTL_SHIM_CFG_DMA_READ_CMD BIT_ULL(12) +/* Select endian format for DMA accesses to the L2C: + * 0x0 = Little endian + * 0x1 = Big endian + * 0x2 = Reserved + * 0x3 = Reserved + */ +# define USBDRD_UCTL_SHIM_CFG_DMA_ENDIAN_MODE GENMASK_ULL(9, 8) +/* Select endian format for IOI CSR access to UAHC: + * 0x0 = Little endian + * 0x1 = Big endian + * 0x2 = Reserved + * 0x3 = Reserved + */ +# define USBDRD_UCTL_SHIM_CFG_CSR_ENDIAN_MODE GENMASK_ULL(1, 0) + +#define USBDRD_UCTL_ECC 0xf0 +#define USBDRD_UCTL_SPARE1 0xf8 + +struct dwc3_octeon { + struct device *dev; + void __iomem *base; +}; + +#define DWC3_GPIO_POWER_NONE (-1) + +#ifdef CONFIG_CAVIUM_OCTEON_SOC +#include +static inline uint64_t dwc3_octeon_readq(void __iomem *addr) +{ + return cvmx_readq_csr(addr); +} + +static inline void dwc3_octeon_writeq(void __iomem *base, uint64_t val) +{ + cvmx_writeq_csr(base, val); +} + +static void dwc3_octeon_config_gpio(int index, int gpio) +{ + union cvmx_gpio_bit_cfgx gpio_bit; + + if ((OCTEON_IS_MODEL(OCTEON_CN73XX) || + OCTEON_IS_MODEL(OCTEON_CNF75XX)) + && gpio <= 31) { + gpio_bit.u64 = cvmx_read_csr(CVMX_GPIO_BIT_CFGX(gpio)); + gpio_bit.s.tx_oe = 1; + gpio_bit.s.output_sel = (index == 0 ? 0x14 : 0x15); + cvmx_write_csr(CVMX_GPIO_BIT_CFGX(gpio), gpio_bit.u64); + } else if (gpio <= 15) { + gpio_bit.u64 = cvmx_read_csr(CVMX_GPIO_BIT_CFGX(gpio)); + gpio_bit.s.tx_oe = 1; + gpio_bit.s.output_sel = (index == 0 ? 0x14 : 0x19); + cvmx_write_csr(CVMX_GPIO_BIT_CFGX(gpio), gpio_bit.u64); + } else { + gpio_bit.u64 = cvmx_read_csr(CVMX_GPIO_XBIT_CFGX(gpio)); + gpio_bit.s.tx_oe = 1; + gpio_bit.s.output_sel = (index == 0 ? 0x14 : 0x19); + cvmx_write_csr(CVMX_GPIO_XBIT_CFGX(gpio), gpio_bit.u64); + } +} +#else +static inline uint64_t dwc3_octeon_readq(void __iomem *addr) +{ + return 0; +} + +static inline void dwc3_octeon_writeq(void __iomem *base, uint64_t val) { } + +static inline void dwc3_octeon_config_gpio(int index, int gpio) { } + +static uint64_t octeon_get_io_clock_rate(void) +{ + return 150000000; +} +#endif + +static int dwc3_octeon_get_divider(void) +{ + static const uint8_t clk_div[] = { 1, 2, 4, 6, 8, 16, 24, 32 }; + int div = 0; + + while (div < ARRAY_SIZE(clk_div)) { + uint64_t rate = octeon_get_io_clock_rate() / clk_div[div]; + if (rate <= 300000000 && rate >= 150000000) + return div; + div++; + } + + return -EINVAL; +} + +static int dwc3_octeon_setup(struct dwc3_octeon *octeon, + int ref_clk_sel, int ref_clk_fsel, int mpll_mul, + int power_gpio, int power_active_low) +{ + u64 val; + int div; + struct device *dev = octeon->dev; + void __iomem *uctl_ctl_reg = octeon->base + USBDRD_UCTL_CTL; + void __iomem *uctl_host_cfg_reg = octeon->base + USBDRD_UCTL_HOST_CFG; + + /* + * Step 1: Wait for all voltages to be stable...that surely + * happened before starting the kernel. SKIP + */ + + /* Step 2: Select GPIO for overcurrent indication, if desired. SKIP */ + + /* Step 3: Assert all resets. */ + val = dwc3_octeon_readq(uctl_ctl_reg); + val |= USBDRD_UCTL_CTL_UPHY_RST | + USBDRD_UCTL_CTL_UAHC_RST | + USBDRD_UCTL_CTL_UCTL_RST; + dwc3_octeon_writeq(uctl_ctl_reg, val); + + /* Step 4a: Reset the clock dividers. */ + val = dwc3_octeon_readq(uctl_ctl_reg); + val |= USBDRD_UCTL_CTL_H_CLKDIV_RST; + dwc3_octeon_writeq(uctl_ctl_reg, val); + + /* Step 4b: Select controller clock frequency. */ + div = dwc3_octeon_get_divider(); + if (div < 0) { + dev_err(dev, "clock divider invalid\n"); + return div; + } + val = dwc3_octeon_readq(uctl_ctl_reg); + FIELD_MODIFY(USBDRD_UCTL_CTL_H_CLKDIV_SEL, &val, div); + val |= USBDRD_UCTL_CTL_H_CLK_EN; + dwc3_octeon_writeq(uctl_ctl_reg, val); + val = dwc3_octeon_readq(uctl_ctl_reg); + if ((div != FIELD_GET(USBDRD_UCTL_CTL_H_CLKDIV_SEL, val)) || + (!(FIELD_GET(USBDRD_UCTL_CTL_H_CLK_EN, val)))) { + dev_err(dev, "clock init failure (UCTL_CTL=%016llx)\n", val); + return -EINVAL; + } + + /* Step 4c: Deassert the controller clock divider reset. */ + val &= ~USBDRD_UCTL_CTL_H_CLKDIV_RST; + dwc3_octeon_writeq(uctl_ctl_reg, val); + + /* Step 5a: Reference clock configuration. */ + val = dwc3_octeon_readq(uctl_ctl_reg); + val &= ~USBDRD_UCTL_CTL_REF_CLK_DIV2; + FIELD_MODIFY(USBDRD_UCTL_CTL_REF_CLK_SEL, &val, ref_clk_sel); + + FIELD_MODIFY(USBDRD_UCTL_CTL_REF_CLK_FSEL, &val, ref_clk_fsel); + + FIELD_MODIFY(USBDRD_UCTL_CTL_MPLL_MULTIPLIER, &val, mpll_mul); + + /* Step 5b: Configure and enable spread-spectrum for SuperSpeed. */ + val |= USBDRD_UCTL_CTL_SSC_EN; + + /* Step 5c: Enable SuperSpeed. */ + val |= USBDRD_UCTL_CTL_REF_SSP_EN; + + /* Step 5d: Configure PHYs. SKIP */ + + /* Step 6a & 6b: Power up PHYs. */ + val |= USBDRD_UCTL_CTL_HS_POWER_EN; + val |= USBDRD_UCTL_CTL_SS_POWER_EN; + dwc3_octeon_writeq(uctl_ctl_reg, val); + + /* Step 7: Wait 10 controller-clock cycles to take effect. */ + udelay(10); + + /* Step 8a: Deassert UCTL reset signal. */ + val = dwc3_octeon_readq(uctl_ctl_reg); + val &= ~USBDRD_UCTL_CTL_UCTL_RST; + dwc3_octeon_writeq(uctl_ctl_reg, val); + + /* Step 8b: Wait 10 controller-clock cycles. */ + udelay(10); + + /* Step 8c: Setup power control. */ + val = dwc3_octeon_readq(uctl_host_cfg_reg); + val |= USBDRD_UCTL_HOST_PPC_EN; + if (power_gpio == DWC3_GPIO_POWER_NONE) { + val &= ~USBDRD_UCTL_HOST_PPC_EN; + } else { + val |= USBDRD_UCTL_HOST_PPC_EN; + dwc3_octeon_config_gpio(((__force uintptr_t)octeon->base >> 24) & 1, + power_gpio); + dev_dbg(dev, "power control is using gpio%d\n", power_gpio); + } + if (power_active_low) + val &= ~USBDRD_UCTL_HOST_PPC_ACTIVE_HIGH_EN; + else + val |= USBDRD_UCTL_HOST_PPC_ACTIVE_HIGH_EN; + dwc3_octeon_writeq(uctl_host_cfg_reg, val); + + /* Step 8d: Deassert UAHC reset signal. */ + val = dwc3_octeon_readq(uctl_ctl_reg); + val &= ~USBDRD_UCTL_CTL_UAHC_RST; + dwc3_octeon_writeq(uctl_ctl_reg, val); + + /* Step 8e: Wait 10 controller-clock cycles. */ + udelay(10); + + /* Step 9: Enable conditional coprocessor clock of UCTL. */ + val = dwc3_octeon_readq(uctl_ctl_reg); + val |= USBDRD_UCTL_CTL_CSCLK_EN; + dwc3_octeon_writeq(uctl_ctl_reg, val); + + /*Step 10: Set for host mode only. */ + val = dwc3_octeon_readq(uctl_ctl_reg); + val &= ~USBDRD_UCTL_CTL_DRD_MODE; + dwc3_octeon_writeq(uctl_ctl_reg, val); + + return 0; +} + +static void dwc3_octeon_set_endian_mode(struct dwc3_octeon *octeon) +{ + u64 val; + void __iomem *uctl_shim_cfg_reg = octeon->base + USBDRD_UCTL_SHIM_CFG; + + val = dwc3_octeon_readq(uctl_shim_cfg_reg); + val &= ~USBDRD_UCTL_SHIM_CFG_DMA_ENDIAN_MODE; + val &= ~USBDRD_UCTL_SHIM_CFG_CSR_ENDIAN_MODE; +#ifdef __BIG_ENDIAN + val |= FIELD_PREP(USBDRD_UCTL_SHIM_CFG_DMA_ENDIAN_MODE, 1); + val |= FIELD_PREP(USBDRD_UCTL_SHIM_CFG_CSR_ENDIAN_MODE, 1); +#endif + dwc3_octeon_writeq(uctl_shim_cfg_reg, val); +} + +static void dwc3_octeon_phy_reset(struct dwc3_octeon *octeon) +{ + u64 val; + void __iomem *uctl_ctl_reg = octeon->base + USBDRD_UCTL_CTL; + + val = dwc3_octeon_readq(uctl_ctl_reg); + val &= ~USBDRD_UCTL_CTL_UPHY_RST; + dwc3_octeon_writeq(uctl_ctl_reg, val); +} + +static int dwc3_octeon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct dwc3_octeon *octeon; + const char *hs_clock_type, *ss_clock_type; + int ref_clk_sel, ref_clk_fsel, mpll_mul; + int power_active_low, power_gpio; + int err, len; + u32 clock_rate, gpio_pwr[3]; + + if (of_property_read_u32(node, "refclk-frequency", &clock_rate)) { + dev_err(dev, "No UCTL \"refclk-frequency\"\n"); + return -EINVAL; + } + if (of_property_read_string(node, "refclk-type-ss", &ss_clock_type)) { + dev_err(dev, "No UCTL \"refclk-type-ss\"\n"); + return -EINVAL; + } + if (of_property_read_string(node, "refclk-type-hs", &hs_clock_type)) { + dev_err(dev, "No UCTL \"refclk-type-hs\"\n"); + return -EINVAL; + } + + ref_clk_sel = 2; + if (strcmp("dlmc_ref_clk0", ss_clock_type) == 0) { + if (strcmp(hs_clock_type, "dlmc_ref_clk0") == 0) + ref_clk_sel = 0; + else if (strcmp(hs_clock_type, "pll_ref_clk")) + dev_warn(dev, "Invalid HS clock type %s, using pll_ref_clk instead\n", + hs_clock_type); + } else if (strcmp(ss_clock_type, "dlmc_ref_clk1") == 0) { + if (strcmp(hs_clock_type, "dlmc_ref_clk1") == 0) { + ref_clk_sel = 1; + } else { + ref_clk_sel = 3; + if (strcmp(hs_clock_type, "pll_ref_clk")) + dev_warn(dev, "Invalid HS clock type %s, using pll_ref_clk instead\n", + hs_clock_type); + } + } else { + dev_warn(dev, "Invalid SS clock type %s, using dlmc_ref_clk0 instead\n", + ss_clock_type); + } + + ref_clk_fsel = 0x07; + switch (clock_rate) { + default: + dev_warn(dev, "Invalid ref_clk %u, using 100000000 instead\n", + clock_rate); + fallthrough; + case 100000000: + mpll_mul = 0x19; + if (ref_clk_sel < 2) + ref_clk_fsel = 0x27; + break; + case 50000000: + mpll_mul = 0x32; + break; + case 125000000: + mpll_mul = 0x28; + break; + } + + power_gpio = DWC3_GPIO_POWER_NONE; + power_active_low = 0; + len = of_property_read_variable_u32_array(node, "power", gpio_pwr, 2, 3); + if (len > 0) { + if (len == 3) + power_active_low = gpio_pwr[2] & 0x01; + power_gpio = gpio_pwr[1]; + } + + octeon = devm_kzalloc(dev, sizeof(*octeon), GFP_KERNEL); + if (!octeon) + return -ENOMEM; + + octeon->dev = dev; + octeon->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(octeon->base)) + return PTR_ERR(octeon->base); + + err = dwc3_octeon_setup(octeon, ref_clk_sel, ref_clk_fsel, mpll_mul, + power_gpio, power_active_low); + if (err) + return err; + + dwc3_octeon_set_endian_mode(octeon); + dwc3_octeon_phy_reset(octeon); + + platform_set_drvdata(pdev, octeon); + + return of_platform_populate(node, NULL, NULL, dev); +} + +static void dwc3_octeon_remove(struct platform_device *pdev) +{ + struct dwc3_octeon *octeon = platform_get_drvdata(pdev); + + of_platform_depopulate(octeon->dev); +} + +static const struct of_device_id dwc3_octeon_of_match[] = { + { .compatible = "cavium,octeon-7130-usb-uctl" }, + { }, +}; +MODULE_DEVICE_TABLE(of, dwc3_octeon_of_match); + +static struct platform_driver dwc3_octeon_driver = { + .probe = dwc3_octeon_probe, + .remove = dwc3_octeon_remove, + .driver = { + .name = "dwc3-octeon", + .of_match_table = dwc3_octeon_of_match, + }, +}; +module_platform_driver(dwc3_octeon_driver); + +MODULE_ALIAS("platform:dwc3-octeon"); +MODULE_AUTHOR("Ladislav Michl "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DesignWare USB3 OCTEON III Glue Layer"); diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c new file mode 100644 index 000000000..c11614333 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-of-simple.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-of-simple.c - OF glue layer for simple integrations + * + * Copyright (c) 2015 Texas Instruments Incorporated - https://www.ti.com + * + * Author: Felipe Balbi + * + * This is a combination of the old dwc3-qcom.c by Ivan T. Ivanov + * and the original patch adding support for Xilinx' SoC + * by Subbaraya Sundeep Bhatta + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct dwc3_of_simple { + struct device *dev; + struct clk_bulk_data *clks; + int num_clocks; + struct reset_control *resets; + bool need_reset; +}; + +static int dwc3_of_simple_probe(struct platform_device *pdev) +{ + struct dwc3_of_simple *simple; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + + int ret; + + simple = devm_kzalloc(dev, sizeof(*simple), GFP_KERNEL); + if (!simple) + return -ENOMEM; + + platform_set_drvdata(pdev, simple); + simple->dev = dev; + + /* + * Some controllers need to toggle the usb3-otg reset before trying to + * initialize the PHY, otherwise the PHY times out. + */ + if (of_device_is_compatible(np, "rockchip,rk3399-dwc3")) + simple->need_reset = true; + + simple->resets = of_reset_control_array_get_optional_exclusive(np); + if (IS_ERR(simple->resets)) { + ret = PTR_ERR(simple->resets); + dev_err(dev, "failed to get device resets, err=%d\n", ret); + return ret; + } + + ret = reset_control_deassert(simple->resets); + if (ret) + goto err_resetc_put; + + ret = clk_bulk_get_all(simple->dev, &simple->clks); + if (ret < 0) + goto err_resetc_assert; + + simple->num_clocks = ret; + ret = clk_bulk_prepare_enable(simple->num_clocks, simple->clks); + if (ret) + goto err_clk_put_all; + + ret = of_platform_populate(np, NULL, NULL, dev); + if (ret) + goto err_clk_disable; + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + return 0; + +err_clk_disable: + clk_bulk_disable_unprepare(simple->num_clocks, simple->clks); +err_clk_put_all: + clk_bulk_put_all(simple->num_clocks, simple->clks); + +err_resetc_assert: + reset_control_assert(simple->resets); + +err_resetc_put: + reset_control_put(simple->resets); + return ret; +} + +static void __dwc3_of_simple_teardown(struct dwc3_of_simple *simple) +{ + of_platform_depopulate(simple->dev); + + clk_bulk_disable_unprepare(simple->num_clocks, simple->clks); + clk_bulk_put_all(simple->num_clocks, simple->clks); + simple->num_clocks = 0; + + reset_control_assert(simple->resets); + + reset_control_put(simple->resets); + + pm_runtime_disable(simple->dev); + pm_runtime_put_noidle(simple->dev); + pm_runtime_set_suspended(simple->dev); +} + +static void dwc3_of_simple_remove(struct platform_device *pdev) +{ + struct dwc3_of_simple *simple = platform_get_drvdata(pdev); + + __dwc3_of_simple_teardown(simple); +} + +static void dwc3_of_simple_shutdown(struct platform_device *pdev) +{ + struct dwc3_of_simple *simple = platform_get_drvdata(pdev); + + __dwc3_of_simple_teardown(simple); +} + +static int __maybe_unused dwc3_of_simple_runtime_suspend(struct device *dev) +{ + struct dwc3_of_simple *simple = dev_get_drvdata(dev); + + clk_bulk_disable(simple->num_clocks, simple->clks); + + return 0; +} + +static int __maybe_unused dwc3_of_simple_runtime_resume(struct device *dev) +{ + struct dwc3_of_simple *simple = dev_get_drvdata(dev); + + return clk_bulk_enable(simple->num_clocks, simple->clks); +} + +static int __maybe_unused dwc3_of_simple_suspend(struct device *dev) +{ + struct dwc3_of_simple *simple = dev_get_drvdata(dev); + + if (simple->need_reset) + reset_control_assert(simple->resets); + + return 0; +} + +static int __maybe_unused dwc3_of_simple_resume(struct device *dev) +{ + struct dwc3_of_simple *simple = dev_get_drvdata(dev); + + if (simple->need_reset) + reset_control_deassert(simple->resets); + + return 0; +} + +static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_of_simple_suspend, dwc3_of_simple_resume) + SET_RUNTIME_PM_OPS(dwc3_of_simple_runtime_suspend, + dwc3_of_simple_runtime_resume, NULL) +}; + +static const struct of_device_id of_dwc3_simple_match[] = { + { .compatible = "rockchip,rk3399-dwc3" }, + { .compatible = "sprd,sc9860-dwc3" }, + { .compatible = "allwinner,sun50i-h6-dwc3" }, + { .compatible = "hisilicon,hi3670-dwc3" }, + { .compatible = "hisilicon,hi3798mv200-dwc3" }, + { .compatible = "intel,keembay-dwc3" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, of_dwc3_simple_match); + +static struct platform_driver dwc3_of_simple_driver = { + .probe = dwc3_of_simple_probe, + .remove = dwc3_of_simple_remove, + .shutdown = dwc3_of_simple_shutdown, + .driver = { + .name = "dwc3-of-simple", + .of_match_table = of_dwc3_simple_match, + .pm = &dwc3_of_simple_dev_pm_ops, + }, +}; + +module_platform_driver(dwc3_of_simple_driver); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 OF Simple Glue Layer"); +MODULE_AUTHOR("Felipe Balbi "); diff --git a/drivers/usb/dwc3/dwc3-omap.c b/drivers/usb/dwc3/dwc3-omap.c new file mode 100644 index 000000000..fe74d11bb --- /dev/null +++ b/drivers/usb/dwc3/dwc3-omap.c @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-omap.c - OMAP Specific Glue layer + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + */ + +#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_EOI_OFFSET 0x0008 +#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_IRQ0_OFFSET 0x0004 +#define USBOTGSS_IRQSTATUS_RAW_1 0x0030 +#define USBOTGSS_IRQSTATUS_1 0x0034 +#define USBOTGSS_IRQENABLE_SET_1 0x0038 +#define USBOTGSS_IRQENABLE_CLR_1 0x003c +#define USBOTGSS_IRQSTATUS_RAW_2 0x0040 +#define USBOTGSS_IRQSTATUS_2 0x0044 +#define USBOTGSS_IRQENABLE_SET_2 0x0048 +#define USBOTGSS_IRQENABLE_CLR_2 0x004c +#define USBOTGSS_IRQSTATUS_RAW_3 0x0050 +#define USBOTGSS_IRQSTATUS_3 0x0054 +#define USBOTGSS_IRQENABLE_SET_3 0x0058 +#define USBOTGSS_IRQENABLE_CLR_3 0x005c +#define USBOTGSS_IRQSTATUS_EOI_MISC 0x0030 +#define USBOTGSS_IRQSTATUS_RAW_MISC 0x0034 +#define USBOTGSS_IRQSTATUS_MISC 0x0038 +#define USBOTGSS_IRQENABLE_SET_MISC 0x003c +#define USBOTGSS_IRQENABLE_CLR_MISC 0x0040 +#define USBOTGSS_IRQMISC_OFFSET 0x03fc +#define USBOTGSS_UTMI_OTG_STATUS 0x0080 +#define USBOTGSS_UTMI_OTG_CTRL 0x0084 +#define USBOTGSS_UTMI_OTG_OFFSET 0x0480 +#define USBOTGSS_TXFIFO_DEPTH 0x0508 +#define USBOTGSS_RXFIFO_DEPTH 0x050c +#define USBOTGSS_MMRAM_OFFSET 0x0100 +#define USBOTGSS_FLADJ 0x0104 +#define USBOTGSS_DEBUG_CFG 0x0108 +#define USBOTGSS_DEBUG_DATA 0x010c +#define USBOTGSS_DEV_EBC_EN 0x0110 +#define USBOTGSS_DEBUG_OFFSET 0x0600 + +/* SYSCONFIG REGISTER */ +#define USBOTGSS_SYSCONFIG_DMADISABLE BIT(16) + +/* IRQ_EOI REGISTER */ +#define USBOTGSS_IRQ_EOI_LINE_NUMBER BIT(0) + +/* IRQS0 BITS */ +#define USBOTGSS_IRQO_COREIRQ_ST BIT(0) + +/* IRQMISC BITS */ +#define USBOTGSS_IRQMISC_DMADISABLECLR BIT(17) +#define USBOTGSS_IRQMISC_OEVT BIT(16) +#define USBOTGSS_IRQMISC_DRVVBUS_RISE BIT(13) +#define USBOTGSS_IRQMISC_CHRGVBUS_RISE BIT(12) +#define USBOTGSS_IRQMISC_DISCHRGVBUS_RISE BIT(11) +#define USBOTGSS_IRQMISC_IDPULLUP_RISE BIT(8) +#define USBOTGSS_IRQMISC_DRVVBUS_FALL BIT(5) +#define USBOTGSS_IRQMISC_CHRGVBUS_FALL BIT(4) +#define USBOTGSS_IRQMISC_DISCHRGVBUS_FALL BIT(3) +#define USBOTGSS_IRQMISC_IDPULLUP_FALL BIT(0) + +/* UTMI_OTG_STATUS REGISTER */ +#define USBOTGSS_UTMI_OTG_STATUS_DRVVBUS BIT(5) +#define USBOTGSS_UTMI_OTG_STATUS_CHRGVBUS BIT(4) +#define USBOTGSS_UTMI_OTG_STATUS_DISCHRGVBUS BIT(3) +#define USBOTGSS_UTMI_OTG_STATUS_IDPULLUP BIT(0) + +/* UTMI_OTG_CTRL REGISTER */ +#define USBOTGSS_UTMI_OTG_CTRL_SW_MODE BIT(31) +#define USBOTGSS_UTMI_OTG_CTRL_POWERPRESENT BIT(9) +#define USBOTGSS_UTMI_OTG_CTRL_TXBITSTUFFENABLE BIT(8) +#define USBOTGSS_UTMI_OTG_CTRL_IDDIG BIT(4) +#define USBOTGSS_UTMI_OTG_CTRL_SESSEND BIT(3) +#define USBOTGSS_UTMI_OTG_CTRL_SESSVALID BIT(2) +#define USBOTGSS_UTMI_OTG_CTRL_VBUSVALID BIT(1) + +enum dwc3_omap_utmi_mode { + DWC3_OMAP_UTMI_MODE_UNKNOWN = 0, + DWC3_OMAP_UTMI_MODE_HW, + DWC3_OMAP_UTMI_MODE_SW, +}; + +struct dwc3_omap { + struct device *dev; + + int irq; + void __iomem *base; + + u32 utmi_otg_ctrl; + u32 utmi_otg_offset; + u32 irqmisc_offset; + u32 irq_eoi_offset; + u32 debug_offset; + u32 irq0_offset; + + struct extcon_dev *edev; + struct notifier_block vbus_nb; + struct notifier_block id_nb; + + struct regulator *vbus_reg; +}; + +enum omap_dwc3_vbus_id_status { + OMAP_DWC3_ID_FLOAT, + OMAP_DWC3_ID_GROUND, + OMAP_DWC3_VBUS_OFF, + OMAP_DWC3_VBUS_VALID, +}; + +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); +} + +static u32 dwc3_omap_read_utmi_ctrl(struct dwc3_omap *omap) +{ + return dwc3_omap_readl(omap->base, USBOTGSS_UTMI_OTG_CTRL + + omap->utmi_otg_offset); +} + +static void dwc3_omap_write_utmi_ctrl(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_UTMI_OTG_CTRL + + omap->utmi_otg_offset, value); + +} + +static u32 dwc3_omap_read_irq0_status(struct dwc3_omap *omap) +{ + return dwc3_omap_readl(omap->base, USBOTGSS_IRQSTATUS_RAW_0 - + omap->irq0_offset); +} + +static void dwc3_omap_write_irq0_status(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQSTATUS_0 - + omap->irq0_offset, value); + +} + +static u32 dwc3_omap_read_irqmisc_status(struct dwc3_omap *omap) +{ + return dwc3_omap_readl(omap->base, USBOTGSS_IRQSTATUS_RAW_MISC + + omap->irqmisc_offset); +} + +static void dwc3_omap_write_irqmisc_status(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQSTATUS_MISC + + omap->irqmisc_offset, value); + +} + +static void dwc3_omap_write_irqmisc_set(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_MISC + + omap->irqmisc_offset, value); + +} + +static void dwc3_omap_write_irq0_set(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_0 - + omap->irq0_offset, value); +} + +static void dwc3_omap_write_irqmisc_clr(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_CLR_MISC + + omap->irqmisc_offset, value); +} + +static void dwc3_omap_write_irq0_clr(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_CLR_0 - + omap->irq0_offset, value); +} + +static void dwc3_omap_set_mailbox(struct dwc3_omap *omap, + enum omap_dwc3_vbus_id_status status) +{ + int ret; + u32 val; + + switch (status) { + case OMAP_DWC3_ID_GROUND: + if (omap->vbus_reg) { + ret = regulator_enable(omap->vbus_reg); + if (ret) { + dev_err(omap->dev, "regulator enable failed\n"); + return; + } + } + + val = dwc3_omap_read_utmi_ctrl(omap); + val &= ~USBOTGSS_UTMI_OTG_CTRL_IDDIG; + dwc3_omap_write_utmi_ctrl(omap, val); + break; + + case OMAP_DWC3_VBUS_VALID: + val = dwc3_omap_read_utmi_ctrl(omap); + val &= ~USBOTGSS_UTMI_OTG_CTRL_SESSEND; + val |= USBOTGSS_UTMI_OTG_CTRL_VBUSVALID + | USBOTGSS_UTMI_OTG_CTRL_SESSVALID; + dwc3_omap_write_utmi_ctrl(omap, val); + break; + + case OMAP_DWC3_ID_FLOAT: + if (omap->vbus_reg && regulator_is_enabled(omap->vbus_reg)) + regulator_disable(omap->vbus_reg); + val = dwc3_omap_read_utmi_ctrl(omap); + val |= USBOTGSS_UTMI_OTG_CTRL_IDDIG; + dwc3_omap_write_utmi_ctrl(omap, val); + break; + + case OMAP_DWC3_VBUS_OFF: + val = dwc3_omap_read_utmi_ctrl(omap); + val &= ~(USBOTGSS_UTMI_OTG_CTRL_SESSVALID + | USBOTGSS_UTMI_OTG_CTRL_VBUSVALID); + val |= USBOTGSS_UTMI_OTG_CTRL_SESSEND; + dwc3_omap_write_utmi_ctrl(omap, val); + break; + + default: + dev_WARN(omap->dev, "invalid state\n"); + } +} + +static void dwc3_omap_enable_irqs(struct dwc3_omap *omap); +static void dwc3_omap_disable_irqs(struct dwc3_omap *omap); + +static irqreturn_t dwc3_omap_interrupt(int irq, void *_omap) +{ + struct dwc3_omap *omap = _omap; + + if (dwc3_omap_read_irqmisc_status(omap) || + dwc3_omap_read_irq0_status(omap)) { + /* mask irqs */ + dwc3_omap_disable_irqs(omap); + return IRQ_WAKE_THREAD; + } + + return IRQ_NONE; +} + +static irqreturn_t dwc3_omap_interrupt_thread(int irq, void *_omap) +{ + struct dwc3_omap *omap = _omap; + u32 reg; + + /* clear irq status flags */ + reg = dwc3_omap_read_irqmisc_status(omap); + dwc3_omap_write_irqmisc_status(omap, reg); + + reg = dwc3_omap_read_irq0_status(omap); + dwc3_omap_write_irq0_status(omap, reg); + + /* unmask irqs */ + dwc3_omap_enable_irqs(omap); + + return IRQ_HANDLED; +} + +static void dwc3_omap_enable_irqs(struct dwc3_omap *omap) +{ + u32 reg; + + /* enable all IRQs */ + reg = USBOTGSS_IRQO_COREIRQ_ST; + dwc3_omap_write_irq0_set(omap, reg); + + reg = (USBOTGSS_IRQMISC_OEVT | + USBOTGSS_IRQMISC_DRVVBUS_RISE | + USBOTGSS_IRQMISC_CHRGVBUS_RISE | + USBOTGSS_IRQMISC_DISCHRGVBUS_RISE | + USBOTGSS_IRQMISC_IDPULLUP_RISE | + USBOTGSS_IRQMISC_DRVVBUS_FALL | + USBOTGSS_IRQMISC_CHRGVBUS_FALL | + USBOTGSS_IRQMISC_DISCHRGVBUS_FALL | + USBOTGSS_IRQMISC_IDPULLUP_FALL); + + dwc3_omap_write_irqmisc_set(omap, reg); +} + +static void dwc3_omap_disable_irqs(struct dwc3_omap *omap) +{ + u32 reg; + + /* disable all IRQs */ + reg = USBOTGSS_IRQO_COREIRQ_ST; + dwc3_omap_write_irq0_clr(omap, reg); + + reg = (USBOTGSS_IRQMISC_OEVT | + USBOTGSS_IRQMISC_DRVVBUS_RISE | + USBOTGSS_IRQMISC_CHRGVBUS_RISE | + USBOTGSS_IRQMISC_DISCHRGVBUS_RISE | + USBOTGSS_IRQMISC_IDPULLUP_RISE | + USBOTGSS_IRQMISC_DRVVBUS_FALL | + USBOTGSS_IRQMISC_CHRGVBUS_FALL | + USBOTGSS_IRQMISC_DISCHRGVBUS_FALL | + USBOTGSS_IRQMISC_IDPULLUP_FALL); + + dwc3_omap_write_irqmisc_clr(omap, reg); +} + +static int dwc3_omap_id_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3_omap *omap = container_of(nb, struct dwc3_omap, id_nb); + + if (event) + dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_GROUND); + else + dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_FLOAT); + + return NOTIFY_DONE; +} + +static int dwc3_omap_vbus_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3_omap *omap = container_of(nb, struct dwc3_omap, vbus_nb); + + if (event) + dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_VALID); + else + dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_OFF); + + return NOTIFY_DONE; +} + +static void dwc3_omap_map_offset(struct dwc3_omap *omap) +{ + struct device_node *node = omap->dev->of_node; + + /* + * Differentiate between OMAP5 and AM437x. + * + * For OMAP5(ES2.0) and AM437x wrapper revision is same, even + * though there are changes in wrapper register offsets. + * + * Using dt compatible to differentiate AM437x. + */ + if (of_device_is_compatible(node, "ti,am437x-dwc3")) { + omap->irq_eoi_offset = USBOTGSS_EOI_OFFSET; + omap->irq0_offset = USBOTGSS_IRQ0_OFFSET; + omap->irqmisc_offset = USBOTGSS_IRQMISC_OFFSET; + omap->utmi_otg_offset = USBOTGSS_UTMI_OTG_OFFSET; + omap->debug_offset = USBOTGSS_DEBUG_OFFSET; + } +} + +static void dwc3_omap_set_utmi_mode(struct dwc3_omap *omap) +{ + u32 reg; + struct device_node *node = omap->dev->of_node; + u32 utmi_mode = 0; + + reg = dwc3_omap_read_utmi_ctrl(omap); + + of_property_read_u32(node, "utmi-mode", &utmi_mode); + + switch (utmi_mode) { + case DWC3_OMAP_UTMI_MODE_SW: + reg |= USBOTGSS_UTMI_OTG_CTRL_SW_MODE; + break; + case DWC3_OMAP_UTMI_MODE_HW: + reg &= ~USBOTGSS_UTMI_OTG_CTRL_SW_MODE; + break; + default: + dev_WARN(omap->dev, "UNKNOWN utmi mode %d\n", utmi_mode); + } + + dwc3_omap_write_utmi_ctrl(omap, reg); +} + +static int dwc3_omap_extcon_register(struct dwc3_omap *omap) +{ + int ret; + struct device_node *node = omap->dev->of_node; + struct extcon_dev *edev; + + if (of_property_present(node, "extcon")) { + edev = extcon_get_edev_by_phandle(omap->dev, 0); + if (IS_ERR(edev)) { + dev_vdbg(omap->dev, "couldn't get extcon device\n"); + return -EPROBE_DEFER; + } + + omap->vbus_nb.notifier_call = dwc3_omap_vbus_notifier; + ret = devm_extcon_register_notifier(omap->dev, edev, + EXTCON_USB, &omap->vbus_nb); + if (ret < 0) + dev_vdbg(omap->dev, "failed to register notifier for USB\n"); + + omap->id_nb.notifier_call = dwc3_omap_id_notifier; + ret = devm_extcon_register_notifier(omap->dev, edev, + EXTCON_USB_HOST, &omap->id_nb); + if (ret < 0) + dev_vdbg(omap->dev, "failed to register notifier for USB-HOST\n"); + + if (extcon_get_state(edev, EXTCON_USB) == true) + dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_VALID); + else + dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_OFF); + + if (extcon_get_state(edev, EXTCON_USB_HOST) == true) + dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_GROUND); + else + dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_FLOAT); + + omap->edev = edev; + } + + return 0; +} + +static int dwc3_omap_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + + struct dwc3_omap *omap; + struct device *dev = &pdev->dev; + struct regulator *vbus_reg; + + int ret; + int irq; + + 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) + return -ENOMEM; + + platform_set_drvdata(pdev, omap); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + vbus_reg = devm_regulator_get_optional(dev, "vbus"); + if (IS_ERR(vbus_reg)) { + if (PTR_ERR(vbus_reg) != -ENODEV) + return dev_err_probe(dev, PTR_ERR(vbus_reg), "vbus init failed\n"); + vbus_reg = NULL; + } + + omap->dev = dev; + omap->irq = irq; + omap->base = base; + omap->vbus_reg = vbus_reg; + + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "get_sync failed with err %d\n", ret); + goto err1; + } + + dwc3_omap_map_offset(omap); + dwc3_omap_set_utmi_mode(omap); + + ret = dwc3_omap_extcon_register(omap); + if (ret < 0) + goto err1; + + ret = of_platform_populate(node, NULL, NULL, dev); + if (ret) { + dev_err(&pdev->dev, "failed to create dwc3 core\n"); + goto err1; + } + + ret = devm_request_threaded_irq(dev, omap->irq, dwc3_omap_interrupt, + dwc3_omap_interrupt_thread, IRQF_SHARED, + "dwc3-omap", omap); + if (ret) { + dev_err(dev, "failed to request IRQ #%d --> %d\n", + omap->irq, ret); + goto err2; + } + dwc3_omap_enable_irqs(omap); + return 0; + +err2: + of_platform_depopulate(dev); +err1: + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + return ret; +} + +static void dwc3_omap_remove(struct platform_device *pdev) +{ + struct dwc3_omap *omap = platform_get_drvdata(pdev); + + dwc3_omap_disable_irqs(omap); + disable_irq(omap->irq); + of_platform_depopulate(omap->dev); + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); +} + +static const struct of_device_id of_dwc3_match[] = { + { + .compatible = "ti,dwc3" + }, + { + .compatible = "ti,am437x-dwc3" + }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_dwc3_match); + +#ifdef CONFIG_PM_SLEEP +static int dwc3_omap_suspend(struct device *dev) +{ + struct dwc3_omap *omap = dev_get_drvdata(dev); + + omap->utmi_otg_ctrl = dwc3_omap_read_utmi_ctrl(omap); + dwc3_omap_disable_irqs(omap); + + return 0; +} + +static int dwc3_omap_resume(struct device *dev) +{ + struct dwc3_omap *omap = dev_get_drvdata(dev); + + dwc3_omap_write_utmi_ctrl(omap, omap->utmi_otg_ctrl); + dwc3_omap_enable_irqs(omap); + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} + +static void dwc3_omap_complete(struct device *dev) +{ + struct dwc3_omap *omap = dev_get_drvdata(dev); + + if (extcon_get_state(omap->edev, EXTCON_USB)) + dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_VALID); + else + dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_OFF); + + if (extcon_get_state(omap->edev, EXTCON_USB_HOST)) + dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_GROUND); + else + dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_FLOAT); +} + +static const struct dev_pm_ops dwc3_omap_dev_pm_ops = { + + SET_SYSTEM_SLEEP_PM_OPS(dwc3_omap_suspend, dwc3_omap_resume) + .complete = dwc3_omap_complete, +}; + +#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("GPL v2"); +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 000000000..6c1cbb722 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -0,0 +1,552 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-pci.c - PCI Specific glue layer + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PCI_DEVICE_ID_INTEL_CMLLP 0x02ee +#define PCI_DEVICE_ID_INTEL_CMLH 0x06ee +#define PCI_DEVICE_ID_INTEL_BXT 0x0aaa +#define PCI_DEVICE_ID_INTEL_BYT 0x0f37 +#define PCI_DEVICE_ID_INTEL_MRFLD 0x119e +#define PCI_DEVICE_ID_INTEL_BXT_M 0x1aaa +#define PCI_DEVICE_ID_INTEL_BSW 0x22b7 +#define PCI_DEVICE_ID_INTEL_GLK 0x31aa +#define PCI_DEVICE_ID_INTEL_ICLLP 0x34ee +#define PCI_DEVICE_ID_INTEL_TGPH 0x43ee +#define PCI_DEVICE_ID_INTEL_ADL 0x460e +#define PCI_DEVICE_ID_INTEL_ADLN 0x465e +#define PCI_DEVICE_ID_INTEL_EHL 0x4b7e +#define PCI_DEVICE_ID_INTEL_WCL 0x4d7e +#define PCI_DEVICE_ID_INTEL_JSP 0x4dee +#define PCI_DEVICE_ID_INTEL_ADL_PCH 0x51ee +#define PCI_DEVICE_ID_INTEL_ADLN_PCH 0x54ee +#define PCI_DEVICE_ID_INTEL_APL 0x5aaa +#define PCI_DEVICE_ID_INTEL_NVLS_PCH 0x6e6f +#define PCI_DEVICE_ID_INTEL_ARLH_PCH 0x777e +#define PCI_DEVICE_ID_INTEL_RPLS 0x7a61 +#define PCI_DEVICE_ID_INTEL_MTL 0x7e7e +#define PCI_DEVICE_ID_INTEL_ADLS 0x7ae1 +#define PCI_DEVICE_ID_INTEL_MTLM 0x7eb1 +#define PCI_DEVICE_ID_INTEL_MTLP 0x7ec1 +#define PCI_DEVICE_ID_INTEL_MTLS 0x7f6f +#define PCI_DEVICE_ID_INTEL_TGL 0x9a15 +#define PCI_DEVICE_ID_INTEL_SPTLP 0x9d30 +#define PCI_DEVICE_ID_INTEL_CNPLP 0x9dee +#define PCI_DEVICE_ID_INTEL_TGPLP 0xa0ee +#define PCI_DEVICE_ID_INTEL_SPTH 0xa130 +#define PCI_DEVICE_ID_INTEL_KBP 0xa2b0 +#define PCI_DEVICE_ID_INTEL_CNPH 0xa36e +#define PCI_DEVICE_ID_INTEL_CNPV 0xa3b0 +#define PCI_DEVICE_ID_INTEL_RPL 0xa70e +#define PCI_DEVICE_ID_INTEL_NVLH 0xd37f +#define PCI_DEVICE_ID_INTEL_PTLH 0xe332 +#define PCI_DEVICE_ID_INTEL_PTLH_PCH 0xe37e +#define PCI_DEVICE_ID_INTEL_PTLU 0xe432 +#define PCI_DEVICE_ID_INTEL_PTLU_PCH 0xe47e +#define PCI_DEVICE_ID_AMD_MR 0x163a + +#define PCI_INTEL_BXT_DSM_GUID "732b85d5-b7a7-4a1b-9ba0-4bbd00ffd511" +#define PCI_INTEL_BXT_FUNC_PMU_PWR 4 +#define PCI_INTEL_BXT_STATE_D0 0 +#define PCI_INTEL_BXT_STATE_D3 3 + +#define GP_RWBAR 1 +#define GP_RWREG1 0xa0 +#define GP_RWREG1_ULPI_REFCLK_DISABLE (1 << 17) + +/** + * struct dwc3_pci - Driver private structure + * @dwc3: child dwc3 platform_device + * @pci: our link to PCI bus + * @guid: _DSM GUID + * @has_dsm_for_pm: true for devices which need to run _DSM on runtime PM + * @wakeup_work: work for asynchronous resume + */ +struct dwc3_pci { + struct platform_device *dwc3; + struct pci_dev *pci; + + guid_t guid; + + unsigned int has_dsm_for_pm:1; + struct work_struct wakeup_work; +}; + +static const struct acpi_gpio_params reset_gpios = { 0, 0, false }; +static const struct acpi_gpio_params cs_gpios = { 1, 0, false }; + +static const struct acpi_gpio_mapping acpi_dwc3_byt_gpios[] = { + { "reset-gpios", &reset_gpios, 1 }, + { "cs-gpios", &cs_gpios, 1 }, + { }, +}; + +static struct gpiod_lookup_table platform_bytcr_gpios = { + .dev_id = "0000:00:16.0", + .table = { + GPIO_LOOKUP("INT33FC:00", 54, "cs", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("INT33FC:02", 14, "reset", GPIO_ACTIVE_HIGH), + {} + }, +}; + +static int dwc3_byt_enable_ulpi_refclock(struct pci_dev *pci) +{ + void __iomem *reg; + u32 value; + + reg = pcim_iomap(pci, GP_RWBAR, 0); + if (!reg) + return -ENOMEM; + + value = readl(reg + GP_RWREG1); + if (!(value & GP_RWREG1_ULPI_REFCLK_DISABLE)) + goto unmap; /* ULPI refclk already enabled */ + + value &= ~GP_RWREG1_ULPI_REFCLK_DISABLE; + writel(value, reg + GP_RWREG1); + /* This comes from the Intel Android x86 tree w/o any explanation */ + msleep(100); +unmap: + pcim_iounmap(pci, reg); + return 0; +} + +static const struct property_entry dwc3_pci_intel_properties[] = { + PROPERTY_ENTRY_STRING("dr_mode", "peripheral"), + PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"), + {} +}; + +static const struct property_entry dwc3_pci_intel_phy_charger_detect_properties[] = { + PROPERTY_ENTRY_STRING("dr_mode", "peripheral"), + PROPERTY_ENTRY_BOOL("snps,dis_u2_susphy_quirk"), + PROPERTY_ENTRY_BOOL("linux,phy_charger_detect"), + PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"), + {} +}; + +static const struct property_entry dwc3_pci_intel_byt_properties[] = { + PROPERTY_ENTRY_STRING("dr_mode", "peripheral"), + PROPERTY_ENTRY_BOOL("snps,dis_u2_susphy_quirk"), + PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"), + {} +}; + +/* + * Intel Merrifield SoC uses these endpoints for tracing and they cannot + * be re-allocated if being used because the side band flow control signals + * are hard wired to certain endpoints: + * - 1 High BW Bulk IN (IN#1) (RTIT) + * - 1 1KB BW Bulk IN (IN#8) + 1 1KB BW Bulk OUT (Run Control) (OUT#8) + */ +static const u8 dwc3_pci_mrfld_reserved_endpoints[] = { 3, 16, 17 }; + +static const struct property_entry dwc3_pci_mrfld_properties[] = { + PROPERTY_ENTRY_STRING("dr_mode", "otg"), + PROPERTY_ENTRY_STRING("linux,extcon-name", "mrfld_bcove_pwrsrc"), + PROPERTY_ENTRY_BOOL("snps,dis_u3_susphy_quirk"), + PROPERTY_ENTRY_BOOL("snps,dis_u2_susphy_quirk"), + PROPERTY_ENTRY_U8_ARRAY("snps,reserved-endpoints", dwc3_pci_mrfld_reserved_endpoints), + PROPERTY_ENTRY_BOOL("snps,usb2-gadget-lpm-disable"), + PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"), + {} +}; + +static const struct property_entry dwc3_pci_amd_properties[] = { + PROPERTY_ENTRY_BOOL("snps,has-lpm-erratum"), + PROPERTY_ENTRY_U8("snps,lpm-nyet-threshold", 0xf), + PROPERTY_ENTRY_BOOL("snps,u2exit_lfps_quirk"), + PROPERTY_ENTRY_BOOL("snps,u2ss_inp3_quirk"), + PROPERTY_ENTRY_BOOL("snps,req_p1p2p3_quirk"), + PROPERTY_ENTRY_BOOL("snps,del_p1p2p3_quirk"), + PROPERTY_ENTRY_BOOL("snps,del_phy_power_chg_quirk"), + PROPERTY_ENTRY_BOOL("snps,lfps_filter_quirk"), + PROPERTY_ENTRY_BOOL("snps,rx_detect_poll_quirk"), + PROPERTY_ENTRY_BOOL("snps,tx_de_emphasis_quirk"), + PROPERTY_ENTRY_U8("snps,tx_de_emphasis", 1), + /* FIXME these quirks should be removed when AMD NL tapes out */ + PROPERTY_ENTRY_BOOL("snps,disable_scramble_quirk"), + PROPERTY_ENTRY_BOOL("snps,dis_u3_susphy_quirk"), + PROPERTY_ENTRY_BOOL("snps,dis_u2_susphy_quirk"), + PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"), + {} +}; + +static const struct property_entry dwc3_pci_mr_properties[] = { + PROPERTY_ENTRY_STRING("dr_mode", "otg"), + PROPERTY_ENTRY_BOOL("usb-role-switch"), + PROPERTY_ENTRY_STRING("role-switch-default-mode", "host"), + PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"), + {} +}; + +static const struct software_node dwc3_pci_intel_swnode = { + .properties = dwc3_pci_intel_properties, +}; + +static const struct software_node dwc3_pci_intel_phy_charger_detect_swnode = { + .properties = dwc3_pci_intel_phy_charger_detect_properties, +}; + +static const struct software_node dwc3_pci_intel_byt_swnode = { + .properties = dwc3_pci_intel_byt_properties, +}; + +static const struct software_node dwc3_pci_intel_mrfld_swnode = { + .properties = dwc3_pci_mrfld_properties, +}; + +static const struct software_node dwc3_pci_amd_swnode = { + .properties = dwc3_pci_amd_properties, +}; + +static const struct software_node dwc3_pci_amd_mr_swnode = { + .properties = dwc3_pci_mr_properties, +}; + +static int dwc3_pci_quirks(struct dwc3_pci *dwc, + const struct software_node *swnode) +{ + struct pci_dev *pdev = dwc->pci; + + if (pdev->vendor == PCI_VENDOR_ID_INTEL) { + if (pdev->device == PCI_DEVICE_ID_INTEL_BXT || + pdev->device == PCI_DEVICE_ID_INTEL_BXT_M || + pdev->device == PCI_DEVICE_ID_INTEL_EHL) { + guid_parse(PCI_INTEL_BXT_DSM_GUID, &dwc->guid); + dwc->has_dsm_for_pm = true; + } + + if (pdev->device == PCI_DEVICE_ID_INTEL_BYT) { + struct gpio_desc *gpio; + const char *bios_ver; + int ret; + + /* On BYT the FW does not always enable the refclock */ + ret = dwc3_byt_enable_ulpi_refclock(pdev); + if (ret) + return ret; + + ret = devm_acpi_dev_add_driver_gpios(&pdev->dev, + acpi_dwc3_byt_gpios); + if (ret) + dev_dbg(&pdev->dev, "failed to add mapping table\n"); + + /* + * A lot of BYT devices lack ACPI resource entries for + * the GPIOs. If the ACPI entry for the GPIO controller + * is present add a fallback mapping to the reference + * design GPIOs which all boards seem to use. + */ + if (acpi_dev_present("INT33FC", NULL, -1)) + gpiod_add_lookup_table(&platform_bytcr_gpios); + + /* + * These GPIOs will turn on the USB2 PHY. Note that we have to + * put the gpio descriptors again here because the phy driver + * might want to grab them, too. + */ + gpio = gpiod_get_optional(&pdev->dev, "cs", GPIOD_OUT_LOW); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + + gpiod_set_value_cansleep(gpio, 1); + gpiod_put(gpio); + + gpio = gpiod_get_optional(&pdev->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + + if (gpio) { + gpiod_set_value_cansleep(gpio, 1); + gpiod_put(gpio); + usleep_range(10000, 11000); + } + + /* + * Make the pdev name predictable (only 1 DWC3 on BYT) + * and patch the phy dev-name into the lookup table so + * that the phy-driver can get the GPIOs. + */ + dwc->dwc3->id = PLATFORM_DEVID_NONE; + platform_bytcr_gpios.dev_id = "dwc3.ulpi"; + + /* + * Some Android tablets with a Crystal Cove PMIC + * (INT33FD), rely on the TUSB1211 phy for charger + * detection. These can be identified by them _not_ + * using the standard ACPI battery and ac drivers. + */ + bios_ver = dmi_get_system_info(DMI_BIOS_VERSION); + if (acpi_dev_present("INT33FD", "1", 2) && + acpi_quirk_skip_acpi_ac_and_battery() && + /* Lenovo Yoga Tablet 2 Pro 1380 uses LC824206XA instead */ + !(bios_ver && + strstarts(bios_ver, "BLADE_21.X64.0005.R00.1504101516"))) { + dev_info(&pdev->dev, "Using TUSB1211 phy for charger detection\n"); + swnode = &dwc3_pci_intel_phy_charger_detect_swnode; + } + } + } + + return device_add_software_node(&dwc->dwc3->dev, swnode); +} + +#ifdef CONFIG_PM +static void dwc3_pci_resume_work(struct work_struct *work) +{ + struct dwc3_pci *dwc = container_of(work, struct dwc3_pci, wakeup_work); + struct platform_device *dwc3 = dwc->dwc3; + int ret; + + ret = pm_runtime_get_sync(&dwc3->dev); + if (ret < 0) { + pm_runtime_put_sync_autosuspend(&dwc3->dev); + return; + } + + pm_runtime_put_sync_autosuspend(&dwc3->dev); +} +#endif + +static int dwc3_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) +{ + struct dwc3_pci *dwc; + struct resource res[2]; + int ret; + struct device *dev = &pci->dev; + + ret = pcim_enable_device(pci); + if (ret) { + dev_err(dev, "failed to enable pci device\n"); + return -ENODEV; + } + + pci_set_master(pci); + + dwc = devm_kzalloc(dev, sizeof(*dwc), GFP_KERNEL); + if (!dwc) + return -ENOMEM; + + dwc->dwc3 = platform_device_alloc("dwc3", PLATFORM_DEVID_AUTO); + if (!dwc->dwc3) + return -ENOMEM; + + 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(dwc->dwc3, res, ARRAY_SIZE(res)); + if (ret) { + dev_err(dev, "couldn't add resources to dwc3 device\n"); + goto err; + } + + dwc->pci = pci; + dwc->dwc3->dev.parent = dev; + ACPI_COMPANION_SET(&dwc->dwc3->dev, ACPI_COMPANION(dev)); + + ret = dwc3_pci_quirks(dwc, (void *)id->driver_data); + if (ret) + goto err; + + ret = platform_device_add(dwc->dwc3); + if (ret) { + dev_err(dev, "failed to register dwc3 device\n"); + goto err; + } + + device_init_wakeup(dev, true); + pci_set_drvdata(pci, dwc); + pm_runtime_put(dev); +#ifdef CONFIG_PM + INIT_WORK(&dwc->wakeup_work, dwc3_pci_resume_work); +#endif + + return 0; +err: + device_remove_software_node(&dwc->dwc3->dev); + platform_device_put(dwc->dwc3); + return ret; +} + +static void dwc3_pci_remove(struct pci_dev *pci) +{ + struct dwc3_pci *dwc = pci_get_drvdata(pci); + struct pci_dev *pdev = dwc->pci; + + if (pdev->device == PCI_DEVICE_ID_INTEL_BYT) + gpiod_remove_lookup_table(&platform_bytcr_gpios); +#ifdef CONFIG_PM + cancel_work_sync(&dwc->wakeup_work); +#endif + device_init_wakeup(&pci->dev, false); + pm_runtime_get(&pci->dev); + device_remove_software_node(&dwc->dwc3->dev); + platform_device_unregister(dwc->dwc3); +} + +static const struct pci_device_id dwc3_pci_id_table[] = { + { PCI_DEVICE_DATA(INTEL, CMLLP, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, CMLH, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, BXT, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, BYT, &dwc3_pci_intel_byt_swnode) }, + { PCI_DEVICE_DATA(INTEL, MRFLD, &dwc3_pci_intel_mrfld_swnode) }, + { PCI_DEVICE_DATA(INTEL, BXT_M, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, BSW, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, GLK, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, ICLLP, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, TGPH, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, ADL, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, ADLN, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, EHL, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, WCL, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, JSP, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, ADL_PCH, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, ADLN_PCH, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, APL, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, NVLS_PCH, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, ARLH_PCH, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, RPLS, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, MTL, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, ADLS, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, MTLM, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, MTLP, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, MTLS, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, TGL, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, SPTLP, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, CNPLP, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, TGPLP, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, SPTH, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, KBP, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, CNPH, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, CNPV, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, RPL, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, NVLH, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, PTLH, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, PTLH_PCH, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, PTLU, &dwc3_pci_intel_swnode) }, + { PCI_DEVICE_DATA(INTEL, PTLU_PCH, &dwc3_pci_intel_swnode) }, + + { PCI_DEVICE_DATA(AMD, NL_USB, &dwc3_pci_amd_swnode) }, + { PCI_DEVICE_DATA(AMD, MR, &dwc3_pci_amd_mr_swnode) }, + + { } /* Terminating Entry */ +}; +MODULE_DEVICE_TABLE(pci, dwc3_pci_id_table); + +#if defined(CONFIG_PM) || defined(CONFIG_PM_SLEEP) +static int dwc3_pci_dsm(struct dwc3_pci *dwc, int param) +{ + union acpi_object *obj; + union acpi_object tmp; + union acpi_object argv4 = ACPI_INIT_DSM_ARGV4(1, &tmp); + + if (!dwc->has_dsm_for_pm) + return 0; + + tmp.type = ACPI_TYPE_INTEGER; + tmp.integer.value = param; + + obj = acpi_evaluate_dsm(ACPI_HANDLE(&dwc->pci->dev), &dwc->guid, + 1, PCI_INTEL_BXT_FUNC_PMU_PWR, &argv4); + if (!obj) { + dev_err(&dwc->pci->dev, "failed to evaluate _DSM\n"); + return -EIO; + } + + ACPI_FREE(obj); + + return 0; +} +#endif /* CONFIG_PM || CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM +static int dwc3_pci_runtime_suspend(struct device *dev) +{ + struct dwc3_pci *dwc = dev_get_drvdata(dev); + + if (device_can_wakeup(dev)) + return dwc3_pci_dsm(dwc, PCI_INTEL_BXT_STATE_D3); + + return -EBUSY; +} + +static int dwc3_pci_runtime_resume(struct device *dev) +{ + struct dwc3_pci *dwc = dev_get_drvdata(dev); + int ret; + + ret = dwc3_pci_dsm(dwc, PCI_INTEL_BXT_STATE_D0); + if (ret) + return ret; + + queue_work(pm_wq, &dwc->wakeup_work); + + return 0; +} +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_SLEEP +static int dwc3_pci_suspend(struct device *dev) +{ + struct dwc3_pci *dwc = dev_get_drvdata(dev); + + return dwc3_pci_dsm(dwc, PCI_INTEL_BXT_STATE_D3); +} + +static int dwc3_pci_resume(struct device *dev) +{ + struct dwc3_pci *dwc = dev_get_drvdata(dev); + + return dwc3_pci_dsm(dwc, PCI_INTEL_BXT_STATE_D0); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops dwc3_pci_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_pci_suspend, dwc3_pci_resume) + SET_RUNTIME_PM_OPS(dwc3_pci_runtime_suspend, dwc3_pci_runtime_resume, + NULL) +}; + +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 = &dwc3_pci_dev_pm_ops, + } +}; + +MODULE_AUTHOR("Felipe Balbi "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 PCI Glue Layer"); + +module_pci_driver(dwc3_pci_driver); diff --git a/drivers/usb/dwc3/dwc3-qcom-legacy.c b/drivers/usb/dwc3/dwc3-qcom-legacy.c new file mode 100644 index 000000000..d3fad0fcf --- /dev/null +++ b/drivers/usb/dwc3/dwc3-qcom-legacy.c @@ -0,0 +1,935 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * Inspired by dwc3-of-simple.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "core.h" + +/* USB QSCRATCH Hardware registers */ +#define QSCRATCH_HS_PHY_CTRL 0x10 +#define UTMI_OTG_VBUS_VALID BIT(20) +#define SW_SESSVLD_SEL BIT(28) + +#define QSCRATCH_SS_PHY_CTRL 0x30 +#define LANE0_PWR_PRESENT BIT(24) + +#define QSCRATCH_GENERAL_CFG 0x08 +#define PIPE_UTMI_CLK_SEL BIT(0) +#define PIPE3_PHYSTATUS_SW BIT(3) +#define PIPE_UTMI_CLK_DIS BIT(8) + +#define PWR_EVNT_LPM_IN_L2_MASK BIT(4) +#define PWR_EVNT_LPM_OUT_L2_MASK BIT(5) + +#define SDM845_QSCRATCH_BASE_OFFSET 0xf8800 +#define SDM845_QSCRATCH_SIZE 0x400 +#define SDM845_DWC3_CORE_SIZE 0xcd00 + +/* Interconnect path bandwidths in MBps */ +#define USB_MEMORY_AVG_HS_BW MBps_to_icc(240) +#define USB_MEMORY_PEAK_HS_BW MBps_to_icc(700) +#define USB_MEMORY_AVG_SS_BW MBps_to_icc(1000) +#define USB_MEMORY_PEAK_SS_BW MBps_to_icc(2500) +#define APPS_USB_AVG_BW 0 +#define APPS_USB_PEAK_BW MBps_to_icc(40) + +/* Qualcomm SoCs with multiport support has up to 4 ports */ +#define DWC3_QCOM_MAX_PORTS 4 + +static const u32 pwr_evnt_irq_stat_reg[DWC3_QCOM_MAX_PORTS] = { + 0x58, + 0x1dc, + 0x228, + 0x238, +}; + +struct dwc3_qcom_port { + int qusb2_phy_irq; + int dp_hs_phy_irq; + int dm_hs_phy_irq; + int ss_phy_irq; + enum usb_device_speed usb2_speed; +}; + +struct dwc3_qcom { + struct device *dev; + void __iomem *qscratch_base; + struct platform_device *dwc3; + struct clk **clks; + int num_clocks; + struct reset_control *resets; + struct dwc3_qcom_port ports[DWC3_QCOM_MAX_PORTS]; + u8 num_ports; + + struct extcon_dev *edev; + struct extcon_dev *host_edev; + struct notifier_block vbus_nb; + struct notifier_block host_nb; + + enum usb_dr_mode mode; + bool is_suspended; + bool pm_suspended; + struct icc_path *icc_path_ddr; + struct icc_path *icc_path_apps; +}; + +static inline void dwc3_qcom_setbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg |= val; + writel(reg, base + offset); + + /* ensure that above write is through */ + readl(base + offset); +} + +static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg &= ~val; + writel(reg, base + offset); + + /* ensure that above write is through */ + readl(base + offset); +} + +static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable) +{ + if (enable) { + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL, + LANE0_PWR_PRESENT); + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL, + UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL); + } else { + dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL, + LANE0_PWR_PRESENT); + dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL, + UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL); + } +} + +static int dwc3_qcom_vbus_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, vbus_nb); + + /* enable vbus override for device mode */ + dwc3_qcom_vbus_override_enable(qcom, event); + qcom->mode = event ? USB_DR_MODE_PERIPHERAL : USB_DR_MODE_HOST; + + return NOTIFY_DONE; +} + +static int dwc3_qcom_host_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, host_nb); + + /* disable vbus override in host mode */ + dwc3_qcom_vbus_override_enable(qcom, !event); + qcom->mode = event ? USB_DR_MODE_HOST : USB_DR_MODE_PERIPHERAL; + + return NOTIFY_DONE; +} + +static int dwc3_qcom_register_extcon(struct dwc3_qcom *qcom) +{ + struct device *dev = qcom->dev; + struct extcon_dev *host_edev; + int ret; + + if (!of_property_present(dev->of_node, "extcon")) + return 0; + + qcom->edev = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(qcom->edev)) + return dev_err_probe(dev, PTR_ERR(qcom->edev), + "Failed to get extcon\n"); + + qcom->vbus_nb.notifier_call = dwc3_qcom_vbus_notifier; + + qcom->host_edev = extcon_get_edev_by_phandle(dev, 1); + if (IS_ERR(qcom->host_edev)) + qcom->host_edev = NULL; + + ret = devm_extcon_register_notifier(dev, qcom->edev, EXTCON_USB, + &qcom->vbus_nb); + if (ret < 0) { + dev_err(dev, "VBUS notifier register failed\n"); + return ret; + } + + if (qcom->host_edev) + host_edev = qcom->host_edev; + else + host_edev = qcom->edev; + + qcom->host_nb.notifier_call = dwc3_qcom_host_notifier; + ret = devm_extcon_register_notifier(dev, host_edev, EXTCON_USB_HOST, + &qcom->host_nb); + if (ret < 0) { + dev_err(dev, "Host notifier register failed\n"); + return ret; + } + + /* Update initial VBUS override based on extcon state */ + if (extcon_get_state(qcom->edev, EXTCON_USB) || + !extcon_get_state(host_edev, EXTCON_USB_HOST)) + dwc3_qcom_vbus_notifier(&qcom->vbus_nb, true, qcom->edev); + else + dwc3_qcom_vbus_notifier(&qcom->vbus_nb, false, qcom->edev); + + return 0; +} + +static int dwc3_qcom_interconnect_enable(struct dwc3_qcom *qcom) +{ + int ret; + + ret = icc_enable(qcom->icc_path_ddr); + if (ret) + return ret; + + ret = icc_enable(qcom->icc_path_apps); + if (ret) + icc_disable(qcom->icc_path_ddr); + + return ret; +} + +static int dwc3_qcom_interconnect_disable(struct dwc3_qcom *qcom) +{ + int ret; + + ret = icc_disable(qcom->icc_path_ddr); + if (ret) + return ret; + + ret = icc_disable(qcom->icc_path_apps); + if (ret) + icc_enable(qcom->icc_path_ddr); + + return ret; +} + +/** + * dwc3_qcom_interconnect_init() - Get interconnect path handles + * and set bandwidth. + * @qcom: Pointer to the concerned usb core. + * + */ +static int dwc3_qcom_interconnect_init(struct dwc3_qcom *qcom) +{ + enum usb_device_speed max_speed; + struct device *dev = qcom->dev; + int ret; + + qcom->icc_path_ddr = of_icc_get(dev, "usb-ddr"); + if (IS_ERR(qcom->icc_path_ddr)) { + return dev_err_probe(dev, PTR_ERR(qcom->icc_path_ddr), + "failed to get usb-ddr path\n"); + } + + qcom->icc_path_apps = of_icc_get(dev, "apps-usb"); + if (IS_ERR(qcom->icc_path_apps)) { + ret = dev_err_probe(dev, PTR_ERR(qcom->icc_path_apps), + "failed to get apps-usb path\n"); + goto put_path_ddr; + } + + max_speed = usb_get_maximum_speed(&qcom->dwc3->dev); + if (max_speed >= USB_SPEED_SUPER || max_speed == USB_SPEED_UNKNOWN) { + ret = icc_set_bw(qcom->icc_path_ddr, + USB_MEMORY_AVG_SS_BW, USB_MEMORY_PEAK_SS_BW); + } else { + ret = icc_set_bw(qcom->icc_path_ddr, + USB_MEMORY_AVG_HS_BW, USB_MEMORY_PEAK_HS_BW); + } + if (ret) { + dev_err(dev, "failed to set bandwidth for usb-ddr path: %d\n", ret); + goto put_path_apps; + } + + ret = icc_set_bw(qcom->icc_path_apps, APPS_USB_AVG_BW, APPS_USB_PEAK_BW); + if (ret) { + dev_err(dev, "failed to set bandwidth for apps-usb path: %d\n", ret); + goto put_path_apps; + } + + return 0; + +put_path_apps: + icc_put(qcom->icc_path_apps); +put_path_ddr: + icc_put(qcom->icc_path_ddr); + return ret; +} + +/** + * dwc3_qcom_interconnect_exit() - Release interconnect path handles + * @qcom: Pointer to the concerned usb core. + * + * This function is used to release interconnect path handle. + */ +static void dwc3_qcom_interconnect_exit(struct dwc3_qcom *qcom) +{ + icc_put(qcom->icc_path_ddr); + icc_put(qcom->icc_path_apps); +} + +/* Only usable in contexts where the role can not change. */ +static bool dwc3_qcom_is_host(struct dwc3_qcom *qcom) +{ + struct dwc3 *dwc; + + /* + * FIXME: Fix this layering violation. + */ + dwc = platform_get_drvdata(qcom->dwc3); + + /* Core driver may not have probed yet. */ + if (!dwc) + return false; + + return dwc->xhci; +} + +static enum usb_device_speed dwc3_qcom_read_usb2_speed(struct dwc3_qcom *qcom, int port_index) +{ + struct dwc3 *dwc = platform_get_drvdata(qcom->dwc3); + struct usb_device *udev; + struct usb_hcd __maybe_unused *hcd; + + /* + * FIXME: Fix this layering violation. + */ + hcd = platform_get_drvdata(dwc->xhci); + +#ifdef CONFIG_USB + udev = usb_hub_find_child(hcd->self.root_hub, port_index + 1); +#else + udev = NULL; +#endif + if (!udev) + return USB_SPEED_UNKNOWN; + + return udev->speed; +} + +static void dwc3_qcom_enable_wakeup_irq(int irq, unsigned int polarity) +{ + if (!irq) + return; + + if (polarity) + irq_set_irq_type(irq, polarity); + + enable_irq(irq); + enable_irq_wake(irq); +} + +static void dwc3_qcom_disable_wakeup_irq(int irq) +{ + if (!irq) + return; + + disable_irq_wake(irq); + disable_irq_nosync(irq); +} + +static void dwc3_qcom_disable_port_interrupts(struct dwc3_qcom_port *port) +{ + dwc3_qcom_disable_wakeup_irq(port->qusb2_phy_irq); + + if (port->usb2_speed == USB_SPEED_LOW) { + dwc3_qcom_disable_wakeup_irq(port->dm_hs_phy_irq); + } else if ((port->usb2_speed == USB_SPEED_HIGH) || + (port->usb2_speed == USB_SPEED_FULL)) { + dwc3_qcom_disable_wakeup_irq(port->dp_hs_phy_irq); + } else { + dwc3_qcom_disable_wakeup_irq(port->dp_hs_phy_irq); + dwc3_qcom_disable_wakeup_irq(port->dm_hs_phy_irq); + } + + dwc3_qcom_disable_wakeup_irq(port->ss_phy_irq); +} + +static void dwc3_qcom_enable_port_interrupts(struct dwc3_qcom_port *port) +{ + dwc3_qcom_enable_wakeup_irq(port->qusb2_phy_irq, 0); + + /* + * Configure DP/DM line interrupts based on the USB2 device attached to + * the root hub port. When HS/FS device is connected, configure the DP line + * as falling edge to detect both disconnect and remote wakeup scenarios. When + * LS device is connected, configure DM line as falling edge to detect both + * disconnect and remote wakeup. When no device is connected, configure both + * DP and DM lines as rising edge to detect HS/HS/LS device connect scenario. + */ + + if (port->usb2_speed == USB_SPEED_LOW) { + dwc3_qcom_enable_wakeup_irq(port->dm_hs_phy_irq, + IRQ_TYPE_EDGE_FALLING); + } else if ((port->usb2_speed == USB_SPEED_HIGH) || + (port->usb2_speed == USB_SPEED_FULL)) { + dwc3_qcom_enable_wakeup_irq(port->dp_hs_phy_irq, + IRQ_TYPE_EDGE_FALLING); + } else { + dwc3_qcom_enable_wakeup_irq(port->dp_hs_phy_irq, + IRQ_TYPE_EDGE_RISING); + dwc3_qcom_enable_wakeup_irq(port->dm_hs_phy_irq, + IRQ_TYPE_EDGE_RISING); + } + + dwc3_qcom_enable_wakeup_irq(port->ss_phy_irq, 0); +} + +static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom) +{ + int i; + + for (i = 0; i < qcom->num_ports; i++) + dwc3_qcom_disable_port_interrupts(&qcom->ports[i]); +} + +static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom) +{ + int i; + + for (i = 0; i < qcom->num_ports; i++) + dwc3_qcom_enable_port_interrupts(&qcom->ports[i]); +} + +static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup) +{ + u32 val; + int i, ret; + + if (qcom->is_suspended) + return 0; + + for (i = 0; i < qcom->num_ports; i++) { + val = readl(qcom->qscratch_base + pwr_evnt_irq_stat_reg[i]); + if (!(val & PWR_EVNT_LPM_IN_L2_MASK)) + dev_err(qcom->dev, "port-%d HS-PHY not in L2\n", i + 1); + } + + for (i = qcom->num_clocks - 1; i >= 0; i--) + clk_disable_unprepare(qcom->clks[i]); + + ret = dwc3_qcom_interconnect_disable(qcom); + if (ret) + dev_warn(qcom->dev, "failed to disable interconnect: %d\n", ret); + + /* + * The role is stable during suspend as role switching is done from a + * freezable workqueue. + */ + if (dwc3_qcom_is_host(qcom) && wakeup) { + for (i = 0; i < qcom->num_ports; i++) + qcom->ports[i].usb2_speed = dwc3_qcom_read_usb2_speed(qcom, i); + dwc3_qcom_enable_interrupts(qcom); + } + + qcom->is_suspended = true; + + return 0; +} + +static int dwc3_qcom_resume(struct dwc3_qcom *qcom, bool wakeup) +{ + int ret; + int i; + + if (!qcom->is_suspended) + return 0; + + if (dwc3_qcom_is_host(qcom) && wakeup) + dwc3_qcom_disable_interrupts(qcom); + + for (i = 0; i < qcom->num_clocks; i++) { + ret = clk_prepare_enable(qcom->clks[i]); + if (ret < 0) { + while (--i >= 0) + clk_disable_unprepare(qcom->clks[i]); + return ret; + } + } + + ret = dwc3_qcom_interconnect_enable(qcom); + if (ret) + dev_warn(qcom->dev, "failed to enable interconnect: %d\n", ret); + + /* Clear existing events from PHY related to L2 in/out */ + for (i = 0; i < qcom->num_ports; i++) { + dwc3_qcom_setbits(qcom->qscratch_base, + pwr_evnt_irq_stat_reg[i], + PWR_EVNT_LPM_IN_L2_MASK | PWR_EVNT_LPM_OUT_L2_MASK); + } + + qcom->is_suspended = false; + + return 0; +} + +static irqreturn_t qcom_dwc3_resume_irq(int irq, void *data) +{ + struct dwc3_qcom *qcom = data; + struct dwc3 *dwc = platform_get_drvdata(qcom->dwc3); + + /* If pm_suspended then let pm_resume take care of resuming h/w */ + if (qcom->pm_suspended) + return IRQ_HANDLED; + + /* + * This is safe as role switching is done from a freezable workqueue + * and the wakeup interrupts are disabled as part of resume. + */ + if (dwc3_qcom_is_host(qcom)) + pm_runtime_resume(&dwc->xhci->dev); + + return IRQ_HANDLED; +} + +static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom) +{ + /* Configure dwc3 to use UTMI clock as PIPE clock not present */ + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG, + PIPE_UTMI_CLK_DIS); + + usleep_range(100, 1000); + + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG, + PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW); + + usleep_range(100, 1000); + + dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG, + PIPE_UTMI_CLK_DIS); +} + +static int dwc3_qcom_request_irq(struct dwc3_qcom *qcom, int irq, + const char *name) +{ + int ret; + + /* Keep wakeup interrupts disabled until suspend */ + ret = devm_request_threaded_irq(qcom->dev, irq, NULL, + qcom_dwc3_resume_irq, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + name, qcom); + if (ret) + dev_err(qcom->dev, "failed to request irq %s: %d\n", name, ret); + + return ret; +} + +static int dwc3_qcom_setup_port_irq(struct platform_device *pdev, int port_index, bool is_multiport) +{ + struct dwc3_qcom *qcom = platform_get_drvdata(pdev); + const char *irq_name; + int irq; + int ret; + + if (is_multiport) + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dp_hs_phy_%d", port_index + 1); + else + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dp_hs_phy_irq"); + if (!irq_name) + return -ENOMEM; + + irq = platform_get_irq_byname_optional(pdev, irq_name); + if (irq > 0) { + ret = dwc3_qcom_request_irq(qcom, irq, irq_name); + if (ret) + return ret; + qcom->ports[port_index].dp_hs_phy_irq = irq; + } + + if (is_multiport) + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dm_hs_phy_%d", port_index + 1); + else + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dm_hs_phy_irq"); + if (!irq_name) + return -ENOMEM; + + irq = platform_get_irq_byname_optional(pdev, irq_name); + if (irq > 0) { + ret = dwc3_qcom_request_irq(qcom, irq, irq_name); + if (ret) + return ret; + qcom->ports[port_index].dm_hs_phy_irq = irq; + } + + if (is_multiport) + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ss_phy_%d", port_index + 1); + else + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ss_phy_irq"); + if (!irq_name) + return -ENOMEM; + + irq = platform_get_irq_byname_optional(pdev, irq_name); + if (irq > 0) { + ret = dwc3_qcom_request_irq(qcom, irq, irq_name); + if (ret) + return ret; + qcom->ports[port_index].ss_phy_irq = irq; + } + + if (is_multiport) + return 0; + + irq = platform_get_irq_byname_optional(pdev, "qusb2_phy"); + if (irq > 0) { + ret = dwc3_qcom_request_irq(qcom, irq, "qusb2_phy"); + if (ret) + return ret; + qcom->ports[port_index].qusb2_phy_irq = irq; + } + + return 0; +} + +static int dwc3_qcom_find_num_ports(struct platform_device *pdev) +{ + char irq_name[14]; + int port_num; + int irq; + + irq = platform_get_irq_byname_optional(pdev, "dp_hs_phy_1"); + if (irq <= 0) + return 1; + + for (port_num = 2; port_num <= DWC3_QCOM_MAX_PORTS; port_num++) { + sprintf(irq_name, "dp_hs_phy_%d", port_num); + + irq = platform_get_irq_byname_optional(pdev, irq_name); + if (irq <= 0) + return port_num - 1; + } + + return DWC3_QCOM_MAX_PORTS; +} + +static int dwc3_qcom_setup_irq(struct platform_device *pdev) +{ + struct dwc3_qcom *qcom = platform_get_drvdata(pdev); + bool is_multiport; + int ret; + int i; + + qcom->num_ports = dwc3_qcom_find_num_ports(pdev); + is_multiport = (qcom->num_ports > 1); + + for (i = 0; i < qcom->num_ports; i++) { + ret = dwc3_qcom_setup_port_irq(pdev, i, is_multiport); + if (ret) + return ret; + } + + return 0; +} + +static int dwc3_qcom_clk_init(struct dwc3_qcom *qcom, int count) +{ + struct device *dev = qcom->dev; + struct device_node *np = dev->of_node; + int i; + + if (!np || !count) + return 0; + + if (count < 0) + return count; + + qcom->num_clocks = count; + + qcom->clks = devm_kcalloc(dev, qcom->num_clocks, + sizeof(struct clk *), GFP_KERNEL); + if (!qcom->clks) + return -ENOMEM; + + for (i = 0; i < qcom->num_clocks; i++) { + struct clk *clk; + int ret; + + clk = of_clk_get(np, i); + if (IS_ERR(clk)) { + while (--i >= 0) + clk_put(qcom->clks[i]); + return PTR_ERR(clk); + } + + ret = clk_prepare_enable(clk); + if (ret < 0) { + while (--i >= 0) { + clk_disable_unprepare(qcom->clks[i]); + clk_put(qcom->clks[i]); + } + clk_put(clk); + + return ret; + } + + qcom->clks[i] = clk; + } + + return 0; +} + +static int dwc3_qcom_of_register_core(struct platform_device *pdev) +{ + struct dwc3_qcom *qcom = platform_get_drvdata(pdev); + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + int ret; + + struct device_node *dwc3_np __free(device_node) = of_get_compatible_child(np, + "snps,dwc3"); + if (!dwc3_np) { + dev_err(dev, "failed to find dwc3 core child\n"); + return -ENODEV; + } + + ret = of_platform_populate(np, NULL, NULL, dev); + if (ret) { + dev_err(dev, "failed to register dwc3 core - %d\n", ret); + return ret; + } + + qcom->dwc3 = of_find_device_by_node(dwc3_np); + if (!qcom->dwc3) { + ret = -ENODEV; + dev_err(dev, "failed to get dwc3 platform device\n"); + of_platform_depopulate(dev); + } + + return ret; +} + +static int dwc3_qcom_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct dwc3_qcom *qcom; + int ret, i; + bool ignore_pipe_clk; + bool wakeup_source; + + qcom = devm_kzalloc(&pdev->dev, sizeof(*qcom), GFP_KERNEL); + if (!qcom) + return -ENOMEM; + + platform_set_drvdata(pdev, qcom); + qcom->dev = &pdev->dev; + + qcom->resets = devm_reset_control_array_get_optional_exclusive(dev); + if (IS_ERR(qcom->resets)) { + return dev_err_probe(&pdev->dev, PTR_ERR(qcom->resets), + "failed to get resets\n"); + } + + ret = reset_control_assert(qcom->resets); + if (ret) { + dev_err(&pdev->dev, "failed to assert resets, err=%d\n", ret); + return ret; + } + + usleep_range(10, 1000); + + ret = reset_control_deassert(qcom->resets); + if (ret) { + dev_err(&pdev->dev, "failed to deassert resets, err=%d\n", ret); + goto reset_assert; + } + + ret = dwc3_qcom_clk_init(qcom, of_clk_get_parent_count(np)); + if (ret) { + dev_err_probe(dev, ret, "failed to get clocks\n"); + goto reset_assert; + } + + qcom->qscratch_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(qcom->qscratch_base)) { + ret = PTR_ERR(qcom->qscratch_base); + goto clk_disable; + } + + ret = dwc3_qcom_setup_irq(pdev); + if (ret) { + dev_err(dev, "failed to setup IRQs, err=%d\n", ret); + goto clk_disable; + } + + /* + * Disable pipe_clk requirement if specified. Used when dwc3 + * operates without SSPHY and only HS/FS/LS modes are supported. + */ + ignore_pipe_clk = device_property_read_bool(dev, + "qcom,select-utmi-as-pipe-clk"); + if (ignore_pipe_clk) + dwc3_qcom_select_utmi_clk(qcom); + + ret = dwc3_qcom_of_register_core(pdev); + if (ret) { + dev_err(dev, "failed to register DWC3 Core, err=%d\n", ret); + goto clk_disable; + } + + ret = dwc3_qcom_interconnect_init(qcom); + if (ret) + goto depopulate; + + qcom->mode = usb_get_dr_mode(&qcom->dwc3->dev); + + /* enable vbus override for device mode */ + if (qcom->mode != USB_DR_MODE_HOST) + dwc3_qcom_vbus_override_enable(qcom, true); + + /* register extcon to override sw_vbus on Vbus change later */ + ret = dwc3_qcom_register_extcon(qcom); + if (ret) + goto interconnect_exit; + + wakeup_source = of_property_read_bool(dev->of_node, "wakeup-source"); + device_init_wakeup(&pdev->dev, wakeup_source); + device_init_wakeup(&qcom->dwc3->dev, wakeup_source); + + qcom->is_suspended = false; + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_forbid(dev); + + return 0; + +interconnect_exit: + dwc3_qcom_interconnect_exit(qcom); +depopulate: + of_platform_depopulate(&pdev->dev); + platform_device_put(qcom->dwc3); +clk_disable: + for (i = qcom->num_clocks - 1; i >= 0; i--) { + clk_disable_unprepare(qcom->clks[i]); + clk_put(qcom->clks[i]); + } +reset_assert: + reset_control_assert(qcom->resets); + + return ret; +} + +static void dwc3_qcom_remove(struct platform_device *pdev) +{ + struct dwc3_qcom *qcom = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + int i; + + of_platform_depopulate(&pdev->dev); + platform_device_put(qcom->dwc3); + + for (i = qcom->num_clocks - 1; i >= 0; i--) { + clk_disable_unprepare(qcom->clks[i]); + clk_put(qcom->clks[i]); + } + qcom->num_clocks = 0; + + dwc3_qcom_interconnect_exit(qcom); + reset_control_assert(qcom->resets); + + pm_runtime_allow(dev); + pm_runtime_disable(dev); +} + +static int __maybe_unused dwc3_qcom_pm_suspend(struct device *dev) +{ + struct dwc3_qcom *qcom = dev_get_drvdata(dev); + bool wakeup = device_may_wakeup(dev); + int ret; + + ret = dwc3_qcom_suspend(qcom, wakeup); + if (ret) + return ret; + + qcom->pm_suspended = true; + + return 0; +} + +static int __maybe_unused dwc3_qcom_pm_resume(struct device *dev) +{ + struct dwc3_qcom *qcom = dev_get_drvdata(dev); + bool wakeup = device_may_wakeup(dev); + int ret; + + ret = dwc3_qcom_resume(qcom, wakeup); + if (ret) + return ret; + + qcom->pm_suspended = false; + + return 0; +} + +static int __maybe_unused dwc3_qcom_runtime_suspend(struct device *dev) +{ + struct dwc3_qcom *qcom = dev_get_drvdata(dev); + + return dwc3_qcom_suspend(qcom, true); +} + +static int __maybe_unused dwc3_qcom_runtime_resume(struct device *dev) +{ + struct dwc3_qcom *qcom = dev_get_drvdata(dev); + + return dwc3_qcom_resume(qcom, true); +} + +static const struct dev_pm_ops dwc3_qcom_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_qcom_pm_suspend, dwc3_qcom_pm_resume) + SET_RUNTIME_PM_OPS(dwc3_qcom_runtime_suspend, dwc3_qcom_runtime_resume, + NULL) +}; + +static const struct of_device_id dwc3_qcom_of_match[] = { + { .compatible = "qcom,dwc3" }, + { } +}; +MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match); + +static struct platform_driver dwc3_qcom_driver = { + .probe = dwc3_qcom_probe, + .remove = dwc3_qcom_remove, + .driver = { + .name = "dwc3-qcom-legacy", + .pm = &dwc3_qcom_dev_pm_ops, + .of_match_table = dwc3_qcom_of_match, + }, +}; + +module_platform_driver(dwc3_qcom_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare DWC3 QCOM legacy glue Driver"); diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c new file mode 100644 index 000000000..ac68b4218 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -0,0 +1,861 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * Inspired by dwc3-of-simple.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "core.h" +#include "glue.h" + +/* USB QSCRATCH Hardware registers */ +#define QSCRATCH_HS_PHY_CTRL 0x10 +#define UTMI_OTG_VBUS_VALID BIT(20) +#define SW_SESSVLD_SEL BIT(28) + +#define QSCRATCH_SS_PHY_CTRL 0x30 +#define LANE0_PWR_PRESENT BIT(24) + +#define QSCRATCH_GENERAL_CFG 0x08 +#define PIPE_UTMI_CLK_SEL BIT(0) +#define PIPE3_PHYSTATUS_SW BIT(3) +#define PIPE_UTMI_CLK_DIS BIT(8) + +#define PWR_EVNT_LPM_IN_L2_MASK BIT(4) +#define PWR_EVNT_LPM_OUT_L2_MASK BIT(5) + +#define SDM845_QSCRATCH_BASE_OFFSET 0xf8800 +#define SDM845_QSCRATCH_SIZE 0x400 +#define SDM845_DWC3_CORE_SIZE 0xcd00 + +/* Interconnect path bandwidths in MBps */ +#define USB_MEMORY_AVG_HS_BW MBps_to_icc(240) +#define USB_MEMORY_PEAK_HS_BW MBps_to_icc(700) +#define USB_MEMORY_AVG_SS_BW MBps_to_icc(1000) +#define USB_MEMORY_PEAK_SS_BW MBps_to_icc(2500) +#define APPS_USB_AVG_BW 0 +#define APPS_USB_PEAK_BW MBps_to_icc(40) + +/* Qualcomm SoCs with multiport support has up to 4 ports */ +#define DWC3_QCOM_MAX_PORTS 4 + +static const u32 pwr_evnt_irq_stat_reg[DWC3_QCOM_MAX_PORTS] = { + 0x58, + 0x1dc, + 0x228, + 0x238, +}; + +struct dwc3_qcom_port { + int qusb2_phy_irq; + int dp_hs_phy_irq; + int dm_hs_phy_irq; + int ss_phy_irq; + enum usb_device_speed usb2_speed; +}; + +struct dwc3_qcom { + struct device *dev; + void __iomem *qscratch_base; + struct dwc3 dwc; + struct clk_bulk_data *clks; + int num_clocks; + struct reset_control *resets; + struct dwc3_qcom_port ports[DWC3_QCOM_MAX_PORTS]; + u8 num_ports; + + enum usb_dr_mode mode; + bool is_suspended; + bool pm_suspended; + struct icc_path *icc_path_ddr; + struct icc_path *icc_path_apps; + + enum usb_role current_role; +}; + +#define to_dwc3_qcom(d) container_of((d), struct dwc3_qcom, dwc) + +static inline void dwc3_qcom_setbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg |= val; + writel(reg, base + offset); + + /* ensure that above write is through */ + readl(base + offset); +} + +static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg &= ~val; + writel(reg, base + offset); + + /* ensure that above write is through */ + readl(base + offset); +} + +static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable) +{ + if (enable) { + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL, + LANE0_PWR_PRESENT); + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL, + UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL); + } else { + dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL, + LANE0_PWR_PRESENT); + dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL, + UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL); + } +} + +static int dwc3_qcom_interconnect_enable(struct dwc3_qcom *qcom) +{ + int ret; + + ret = icc_enable(qcom->icc_path_ddr); + if (ret) + return ret; + + ret = icc_enable(qcom->icc_path_apps); + if (ret) + icc_disable(qcom->icc_path_ddr); + + return ret; +} + +static int dwc3_qcom_interconnect_disable(struct dwc3_qcom *qcom) +{ + int ret; + + ret = icc_disable(qcom->icc_path_ddr); + if (ret) + return ret; + + ret = icc_disable(qcom->icc_path_apps); + if (ret) + icc_enable(qcom->icc_path_ddr); + + return ret; +} + +/** + * dwc3_qcom_interconnect_init() - Get interconnect path handles + * and set bandwidth. + * @qcom: Pointer to the concerned usb core. + * + */ +static int dwc3_qcom_interconnect_init(struct dwc3_qcom *qcom) +{ + enum usb_device_speed max_speed; + struct device *dev = qcom->dev; + int ret; + + qcom->icc_path_ddr = of_icc_get(dev, "usb-ddr"); + if (IS_ERR(qcom->icc_path_ddr)) { + return dev_err_probe(dev, PTR_ERR(qcom->icc_path_ddr), + "failed to get usb-ddr path\n"); + } + + qcom->icc_path_apps = of_icc_get(dev, "apps-usb"); + if (IS_ERR(qcom->icc_path_apps)) { + ret = dev_err_probe(dev, PTR_ERR(qcom->icc_path_apps), + "failed to get apps-usb path\n"); + goto put_path_ddr; + } + + max_speed = usb_get_maximum_speed(qcom->dwc.dev); + if (max_speed >= USB_SPEED_SUPER || max_speed == USB_SPEED_UNKNOWN) { + ret = icc_set_bw(qcom->icc_path_ddr, + USB_MEMORY_AVG_SS_BW, USB_MEMORY_PEAK_SS_BW); + } else { + ret = icc_set_bw(qcom->icc_path_ddr, + USB_MEMORY_AVG_HS_BW, USB_MEMORY_PEAK_HS_BW); + } + if (ret) { + dev_err(dev, "failed to set bandwidth for usb-ddr path: %d\n", ret); + goto put_path_apps; + } + + ret = icc_set_bw(qcom->icc_path_apps, APPS_USB_AVG_BW, APPS_USB_PEAK_BW); + if (ret) { + dev_err(dev, "failed to set bandwidth for apps-usb path: %d\n", ret); + goto put_path_apps; + } + + return 0; + +put_path_apps: + icc_put(qcom->icc_path_apps); +put_path_ddr: + icc_put(qcom->icc_path_ddr); + return ret; +} + +/** + * dwc3_qcom_interconnect_exit() - Release interconnect path handles + * @qcom: Pointer to the concerned usb core. + * + * This function is used to release interconnect path handle. + */ +static void dwc3_qcom_interconnect_exit(struct dwc3_qcom *qcom) +{ + icc_put(qcom->icc_path_ddr); + icc_put(qcom->icc_path_apps); +} + +/* Only usable in contexts where the role can not change. */ +static bool dwc3_qcom_is_host(struct dwc3_qcom *qcom) +{ + return qcom->dwc.xhci; +} + +static enum usb_device_speed dwc3_qcom_read_usb2_speed(struct dwc3_qcom *qcom, int port_index) +{ + struct usb_device *udev; + struct usb_hcd __maybe_unused *hcd; + struct dwc3 *dwc = &qcom->dwc; + + /* + * FIXME: Fix this layering violation. + */ + hcd = platform_get_drvdata(dwc->xhci); + +#ifdef CONFIG_USB + udev = usb_hub_find_child(hcd->self.root_hub, port_index + 1); +#else + udev = NULL; +#endif + if (!udev) + return USB_SPEED_UNKNOWN; + + return udev->speed; +} + +static void dwc3_qcom_enable_wakeup_irq(int irq, unsigned int polarity) +{ + if (!irq) + return; + + if (polarity) + irq_set_irq_type(irq, polarity); + + enable_irq(irq); + enable_irq_wake(irq); +} + +static void dwc3_qcom_disable_wakeup_irq(int irq) +{ + if (!irq) + return; + + disable_irq_wake(irq); + disable_irq_nosync(irq); +} + +static void dwc3_qcom_disable_port_interrupts(struct dwc3_qcom_port *port) +{ + dwc3_qcom_disable_wakeup_irq(port->qusb2_phy_irq); + + if (port->usb2_speed == USB_SPEED_LOW) { + dwc3_qcom_disable_wakeup_irq(port->dm_hs_phy_irq); + } else if ((port->usb2_speed == USB_SPEED_HIGH) || + (port->usb2_speed == USB_SPEED_FULL)) { + dwc3_qcom_disable_wakeup_irq(port->dp_hs_phy_irq); + } else { + dwc3_qcom_disable_wakeup_irq(port->dp_hs_phy_irq); + dwc3_qcom_disable_wakeup_irq(port->dm_hs_phy_irq); + } + + dwc3_qcom_disable_wakeup_irq(port->ss_phy_irq); +} + +static void dwc3_qcom_enable_port_interrupts(struct dwc3_qcom_port *port) +{ + dwc3_qcom_enable_wakeup_irq(port->qusb2_phy_irq, 0); + + /* + * Configure DP/DM line interrupts based on the USB2 device attached to + * the root hub port. When HS/FS device is connected, configure the DP line + * as falling edge to detect both disconnect and remote wakeup scenarios. When + * LS device is connected, configure DM line as falling edge to detect both + * disconnect and remote wakeup. When no device is connected, configure both + * DP and DM lines as rising edge to detect HS/HS/LS device connect scenario. + */ + + if (port->usb2_speed == USB_SPEED_LOW) { + dwc3_qcom_enable_wakeup_irq(port->dm_hs_phy_irq, + IRQ_TYPE_EDGE_FALLING); + } else if ((port->usb2_speed == USB_SPEED_HIGH) || + (port->usb2_speed == USB_SPEED_FULL)) { + dwc3_qcom_enable_wakeup_irq(port->dp_hs_phy_irq, + IRQ_TYPE_EDGE_FALLING); + } else { + dwc3_qcom_enable_wakeup_irq(port->dp_hs_phy_irq, + IRQ_TYPE_EDGE_RISING); + dwc3_qcom_enable_wakeup_irq(port->dm_hs_phy_irq, + IRQ_TYPE_EDGE_RISING); + } + + dwc3_qcom_enable_wakeup_irq(port->ss_phy_irq, 0); +} + +static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom) +{ + int i; + + for (i = 0; i < qcom->num_ports; i++) + dwc3_qcom_disable_port_interrupts(&qcom->ports[i]); +} + +static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom) +{ + int i; + + for (i = 0; i < qcom->num_ports; i++) + dwc3_qcom_enable_port_interrupts(&qcom->ports[i]); +} + +static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup) +{ + u32 val; + int i, ret; + + if (qcom->is_suspended) + return 0; + + for (i = 0; i < qcom->num_ports; i++) { + val = readl(qcom->qscratch_base + pwr_evnt_irq_stat_reg[i]); + if (!(val & PWR_EVNT_LPM_IN_L2_MASK)) + dev_err(qcom->dev, "port-%d HS-PHY not in L2\n", i + 1); + } + clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks); + + ret = dwc3_qcom_interconnect_disable(qcom); + if (ret) + dev_warn(qcom->dev, "failed to disable interconnect: %d\n", ret); + + /* + * The role is stable during suspend as role switching is done from a + * freezable workqueue. + */ + if (dwc3_qcom_is_host(qcom) && wakeup) { + for (i = 0; i < qcom->num_ports; i++) + qcom->ports[i].usb2_speed = dwc3_qcom_read_usb2_speed(qcom, i); + dwc3_qcom_enable_interrupts(qcom); + } + + qcom->is_suspended = true; + + return 0; +} + +static int dwc3_qcom_resume(struct dwc3_qcom *qcom, bool wakeup) +{ + int ret; + int i; + + if (!qcom->is_suspended) + return 0; + + if (dwc3_qcom_is_host(qcom) && wakeup) + dwc3_qcom_disable_interrupts(qcom); + + ret = clk_bulk_prepare_enable(qcom->num_clocks, qcom->clks); + if (ret < 0) + return ret; + + ret = dwc3_qcom_interconnect_enable(qcom); + if (ret) + dev_warn(qcom->dev, "failed to enable interconnect: %d\n", ret); + + /* Clear existing events from PHY related to L2 in/out */ + for (i = 0; i < qcom->num_ports; i++) { + dwc3_qcom_setbits(qcom->qscratch_base, + pwr_evnt_irq_stat_reg[i], + PWR_EVNT_LPM_IN_L2_MASK | PWR_EVNT_LPM_OUT_L2_MASK); + } + + qcom->is_suspended = false; + + return 0; +} + +static irqreturn_t qcom_dwc3_resume_irq(int irq, void *data) +{ + struct dwc3_qcom *qcom = data; + struct dwc3 *dwc = &qcom->dwc; + + /* If pm_suspended then let pm_resume take care of resuming h/w */ + if (qcom->pm_suspended) + return IRQ_HANDLED; + + /* + * This is safe as role switching is done from a freezable workqueue + * and the wakeup interrupts are disabled as part of resume. + */ + if (dwc3_qcom_is_host(qcom)) + pm_runtime_resume(&dwc->xhci->dev); + + return IRQ_HANDLED; +} + +static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom) +{ + /* Configure dwc3 to use UTMI clock as PIPE clock not present */ + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG, + PIPE_UTMI_CLK_DIS); + + usleep_range(100, 1000); + + dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG, + PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW); + + usleep_range(100, 1000); + + dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG, + PIPE_UTMI_CLK_DIS); +} + +static int dwc3_qcom_request_irq(struct dwc3_qcom *qcom, int irq, + const char *name) +{ + int ret; + + /* Keep wakeup interrupts disabled until suspend */ + ret = devm_request_threaded_irq(qcom->dev, irq, NULL, + qcom_dwc3_resume_irq, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + name, qcom); + if (ret) + dev_err(qcom->dev, "failed to request irq %s: %d\n", name, ret); + + return ret; +} + +static int dwc3_qcom_setup_port_irq(struct dwc3_qcom *qcom, + struct platform_device *pdev, + int port_index, bool is_multiport) +{ + const char *irq_name; + int irq; + int ret; + + if (is_multiport) + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dp_hs_phy_%d", port_index + 1); + else + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dp_hs_phy_irq"); + if (!irq_name) + return -ENOMEM; + + irq = platform_get_irq_byname_optional(pdev, irq_name); + if (irq > 0) { + ret = dwc3_qcom_request_irq(qcom, irq, irq_name); + if (ret) + return ret; + qcom->ports[port_index].dp_hs_phy_irq = irq; + } + + if (is_multiport) + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dm_hs_phy_%d", port_index + 1); + else + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dm_hs_phy_irq"); + if (!irq_name) + return -ENOMEM; + + irq = platform_get_irq_byname_optional(pdev, irq_name); + if (irq > 0) { + ret = dwc3_qcom_request_irq(qcom, irq, irq_name); + if (ret) + return ret; + qcom->ports[port_index].dm_hs_phy_irq = irq; + } + + if (is_multiport) + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ss_phy_%d", port_index + 1); + else + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ss_phy_irq"); + if (!irq_name) + return -ENOMEM; + + irq = platform_get_irq_byname_optional(pdev, irq_name); + if (irq > 0) { + ret = dwc3_qcom_request_irq(qcom, irq, irq_name); + if (ret) + return ret; + qcom->ports[port_index].ss_phy_irq = irq; + } + + if (is_multiport) + return 0; + + irq = platform_get_irq_byname_optional(pdev, "qusb2_phy"); + if (irq > 0) { + ret = dwc3_qcom_request_irq(qcom, irq, "qusb2_phy"); + if (ret) + return ret; + qcom->ports[port_index].qusb2_phy_irq = irq; + } + + return 0; +} + +static int dwc3_qcom_find_num_ports(struct platform_device *pdev) +{ + char irq_name[14]; + int port_num; + int irq; + + irq = platform_get_irq_byname_optional(pdev, "dp_hs_phy_1"); + if (irq < 0) + return 1; + + for (port_num = 2; port_num <= DWC3_QCOM_MAX_PORTS; port_num++) { + sprintf(irq_name, "dp_hs_phy_%d", port_num); + + irq = platform_get_irq_byname_optional(pdev, irq_name); + if (irq < 0) + return port_num - 1; + } + + return DWC3_QCOM_MAX_PORTS; +} + +static int dwc3_qcom_setup_irq(struct dwc3_qcom *qcom, struct platform_device *pdev) +{ + bool is_multiport; + int ret; + int i; + + qcom->num_ports = dwc3_qcom_find_num_ports(pdev); + is_multiport = (qcom->num_ports > 1); + + for (i = 0; i < qcom->num_ports; i++) { + ret = dwc3_qcom_setup_port_irq(qcom, pdev, i, is_multiport); + if (ret) + return ret; + } + + return 0; +} + +static void dwc3_qcom_set_role_notifier(struct dwc3 *dwc, enum usb_role next_role) +{ + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); + + if (qcom->current_role == next_role) + return; + + if (pm_runtime_resume_and_get(qcom->dev)) { + dev_dbg(qcom->dev, "Failed to resume device\n"); + return; + } + + if (qcom->current_role == USB_ROLE_DEVICE) + dwc3_qcom_vbus_override_enable(qcom, false); + else if (qcom->current_role != USB_ROLE_DEVICE) + dwc3_qcom_vbus_override_enable(qcom, true); + + pm_runtime_mark_last_busy(qcom->dev); + pm_runtime_put_sync(qcom->dev); + + /* + * Current role changes via usb_role_switch_set_role callback protected + * internally by mutex lock. + */ + qcom->current_role = next_role; +} + +static void dwc3_qcom_run_stop_notifier(struct dwc3 *dwc, bool is_on) +{ + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); + + /* + * When autosuspend is enabled and controller goes to suspend + * after removing UDC from userspace, the next UDC write needs + * setting of QSCRATCH VBUS_VALID to "1" to generate a connect + * done event. + */ + if (!is_on) + return; + + dwc3_qcom_vbus_override_enable(qcom, true); + pm_runtime_mark_last_busy(qcom->dev); +} + +static struct dwc3_glue_ops dwc3_qcom_glue_ops = { + .pre_set_role = dwc3_qcom_set_role_notifier, + .pre_run_stop = dwc3_qcom_run_stop_notifier, +}; + +static int dwc3_qcom_probe(struct platform_device *pdev) +{ + struct dwc3_probe_data probe_data = {}; + struct device *dev = &pdev->dev; + struct dwc3_qcom *qcom; + struct resource res; + struct resource *r; + int ret; + bool ignore_pipe_clk; + bool wakeup_source; + + qcom = devm_kzalloc(&pdev->dev, sizeof(*qcom), GFP_KERNEL); + if (!qcom) + return -ENOMEM; + + qcom->dev = &pdev->dev; + + qcom->resets = devm_reset_control_array_get_optional_exclusive(dev); + if (IS_ERR(qcom->resets)) { + return dev_err_probe(&pdev->dev, PTR_ERR(qcom->resets), + "failed to get resets\n"); + } + + ret = devm_clk_bulk_get_all(&pdev->dev, &qcom->clks); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get clocks\n"); + qcom->num_clocks = ret; + + ret = reset_control_assert(qcom->resets); + if (ret) { + dev_err(&pdev->dev, "failed to assert resets, err=%d\n", ret); + return ret; + } + + usleep_range(10, 1000); + + ret = reset_control_deassert(qcom->resets); + if (ret) { + dev_err(&pdev->dev, "failed to deassert resets, err=%d\n", ret); + return ret; + } + + ret = clk_bulk_prepare_enable(qcom->num_clocks, qcom->clks); + if (ret < 0) + return ret; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + ret = -EINVAL; + goto clk_disable; + } + res = *r; + res.end = res.start + SDM845_QSCRATCH_BASE_OFFSET; + + qcom->qscratch_base = devm_ioremap(dev, res.end, SDM845_QSCRATCH_SIZE); + if (!qcom->qscratch_base) { + dev_err(dev, "failed to map qscratch region\n"); + ret = -ENOMEM; + goto clk_disable; + } + + ret = dwc3_qcom_setup_irq(qcom, pdev); + if (ret) { + dev_err(dev, "failed to setup IRQs, err=%d\n", ret); + goto clk_disable; + } + + /* + * Disable pipe_clk requirement if specified. Used when dwc3 + * operates without SSPHY and only HS/FS/LS modes are supported. + */ + ignore_pipe_clk = device_property_read_bool(dev, + "qcom,select-utmi-as-pipe-clk"); + if (ignore_pipe_clk) + dwc3_qcom_select_utmi_clk(qcom); + + qcom->mode = usb_get_dr_mode(dev); + + if (qcom->mode == USB_DR_MODE_HOST) { + qcom->current_role = USB_ROLE_HOST; + } else if (qcom->mode == USB_DR_MODE_PERIPHERAL) { + qcom->current_role = USB_ROLE_DEVICE; + dwc3_qcom_vbus_override_enable(qcom, true); + } else { + if ((device_property_read_bool(dev, "usb-role-switch")) && + (usb_get_role_switch_default_mode(dev) == USB_DR_MODE_HOST)) + qcom->current_role = USB_ROLE_HOST; + else + qcom->current_role = USB_ROLE_DEVICE; + } + + qcom->dwc.glue_ops = &dwc3_qcom_glue_ops; + + qcom->dwc.dev = dev; + probe_data.dwc = &qcom->dwc; + probe_data.res = &res; + probe_data.ignore_clocks_and_resets = true; + probe_data.properties = DWC3_DEFAULT_PROPERTIES; + ret = dwc3_core_probe(&probe_data); + if (ret) { + ret = dev_err_probe(dev, ret, "failed to register DWC3 Core\n"); + goto clk_disable; + } + + ret = dwc3_qcom_interconnect_init(qcom); + if (ret) + goto remove_core; + + wakeup_source = of_property_read_bool(dev->of_node, "wakeup-source"); + device_init_wakeup(&pdev->dev, wakeup_source); + + qcom->is_suspended = false; + + return 0; + +remove_core: + dwc3_core_remove(&qcom->dwc); +clk_disable: + clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks); + + return ret; +} + +static void dwc3_qcom_remove(struct platform_device *pdev) +{ + struct dwc3 *dwc = platform_get_drvdata(pdev); + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); + + if (pm_runtime_resume_and_get(qcom->dev) < 0) + return; + + dwc3_core_remove(&qcom->dwc); + clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks); + dwc3_qcom_interconnect_exit(qcom); + + pm_runtime_put_noidle(qcom->dev); +} + +static int dwc3_qcom_pm_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); + bool wakeup = device_may_wakeup(dev); + int ret; + + ret = dwc3_pm_suspend(&qcom->dwc); + if (ret) + return ret; + + ret = dwc3_qcom_suspend(qcom, wakeup); + if (ret) + return ret; + + qcom->pm_suspended = true; + + return 0; +} + +static int dwc3_qcom_pm_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); + bool wakeup = device_may_wakeup(dev); + int ret; + + ret = dwc3_qcom_resume(qcom, wakeup); + if (ret) + return ret; + + qcom->pm_suspended = false; + + ret = dwc3_pm_resume(&qcom->dwc); + if (ret) + return ret; + + return 0; +} + +static void dwc3_qcom_complete(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + + dwc3_pm_complete(dwc); +} + +static int dwc3_qcom_prepare(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + + return dwc3_pm_prepare(dwc); +} + +static int dwc3_qcom_runtime_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); + int ret; + + ret = dwc3_runtime_suspend(&qcom->dwc); + if (ret) + return ret; + + return dwc3_qcom_suspend(qcom, true); +} + +static int dwc3_qcom_runtime_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); + int ret; + + ret = dwc3_qcom_resume(qcom, true); + if (ret) + return ret; + + return dwc3_runtime_resume(&qcom->dwc); +} + +static int dwc3_qcom_runtime_idle(struct device *dev) +{ + return dwc3_runtime_idle(dev_get_drvdata(dev)); +} + +static const struct dev_pm_ops dwc3_qcom_dev_pm_ops = { + SYSTEM_SLEEP_PM_OPS(dwc3_qcom_pm_suspend, dwc3_qcom_pm_resume) + RUNTIME_PM_OPS(dwc3_qcom_runtime_suspend, dwc3_qcom_runtime_resume, + dwc3_qcom_runtime_idle) + .complete = pm_sleep_ptr(dwc3_qcom_complete), + .prepare = pm_sleep_ptr(dwc3_qcom_prepare), +}; + +static const struct of_device_id dwc3_qcom_of_match[] = { + { .compatible = "qcom,snps-dwc3" }, + { } +}; +MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match); + +static struct platform_driver dwc3_qcom_driver = { + .probe = dwc3_qcom_probe, + .remove = dwc3_qcom_remove, + .shutdown = dwc3_qcom_remove, + .driver = { + .name = "dwc3-qcom", + .pm = pm_ptr(&dwc3_qcom_dev_pm_ops), + .of_match_table = dwc3_qcom_of_match, + }, +}; + +module_platform_driver(dwc3_qcom_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare DWC3 QCOM Glue Driver"); diff --git a/drivers/usb/dwc3/dwc3-rtk.c b/drivers/usb/dwc3/dwc3-rtk.c new file mode 100644 index 000000000..56c53e0c0 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-rtk.c @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-rtk.c - Realtek DWC3 Specific Glue layer + * + * Copyright (C) 2023 Realtek Semiconductor Corporation + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" + +#define WRAP_CTR_REG 0x0 +#define DISABLE_MULTI_REQ BIT(1) +#define DESC_R2W_MULTI_DISABLE BIT(9) +#define FORCE_PIPE3_PHY_STATUS_TO_0 BIT(13) + +#define WRAP_USB2_PHY_UTMI_REG 0x8 +#define TXHSVM_EN BIT(3) + +#define WRAP_PHY_PIPE_REG 0xC +#define RESET_DISABLE_PIPE3_P0 BIT(0) +#define CLOCK_ENABLE_FOR_PIPE3_PCLK BIT(1) + +#define WRAP_USB_HMAC_CTR0_REG 0x60 +#define U3PORT_DIS BIT(8) + +#define WRAP_USB2_PHY_REG 0x70 +#define USB2_PHY_EN_PHY_PLL_PORT0 BIT(12) +#define USB2_PHY_EN_PHY_PLL_PORT1 BIT(13) +#define USB2_PHY_SWITCH_MASK 0x707 +#define USB2_PHY_SWITCH_DEVICE 0x0 +#define USB2_PHY_SWITCH_HOST 0x606 + +#define WRAP_APHY_REG 0x128 +#define USB3_MBIAS_ENABLE BIT(1) + +/* pm control */ +#define WRAP_USB_DBUS_PWR_CTRL_REG 0x160 +#define USB_DBUS_PWR_CTRL_REG 0x0 +#define DBUS_PWR_CTRL_EN BIT(0) + +struct dwc3_rtk { + struct device *dev; + void __iomem *regs; + size_t regs_size; + void __iomem *pm_base; + + struct dwc3 *dwc; + + enum usb_role cur_role; + struct usb_role_switch *role_switch; +}; + +static void switch_usb2_role(struct dwc3_rtk *rtk, enum usb_role role) +{ + void __iomem *reg; + int val; + + reg = rtk->regs + WRAP_USB2_PHY_REG; + val = ~USB2_PHY_SWITCH_MASK & readl(reg); + + switch (role) { + case USB_ROLE_DEVICE: + writel(USB2_PHY_SWITCH_DEVICE | val, reg); + break; + case USB_ROLE_HOST: + writel(USB2_PHY_SWITCH_HOST | val, reg); + break; + default: + dev_dbg(rtk->dev, "%s: role=%d\n", __func__, role); + break; + } +} + +static void switch_dwc3_role(struct dwc3_rtk *rtk, enum usb_role role) +{ + if (!rtk->dwc->role_sw) + return; + + usb_role_switch_set_role(rtk->dwc->role_sw, role); +} + +static enum usb_role dwc3_rtk_get_role(struct dwc3_rtk *rtk) +{ + enum usb_role role; + + role = rtk->cur_role; + + if (rtk->dwc && rtk->dwc->role_sw) + role = usb_role_switch_get_role(rtk->dwc->role_sw); + else + dev_dbg(rtk->dev, "%s not usb_role_switch role=%d\n", __func__, role); + + return role; +} + +static void dwc3_rtk_set_role(struct dwc3_rtk *rtk, enum usb_role role) +{ + rtk->cur_role = role; + + switch_dwc3_role(rtk, role); + mdelay(10); + switch_usb2_role(rtk, role); +} + +#if IS_ENABLED(CONFIG_USB_ROLE_SWITCH) +static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role) +{ + struct dwc3_rtk *rtk = usb_role_switch_get_drvdata(sw); + + dwc3_rtk_set_role(rtk, role); + + return 0; +} + +static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw) +{ + struct dwc3_rtk *rtk = usb_role_switch_get_drvdata(sw); + + return dwc3_rtk_get_role(rtk); +} + +static int dwc3_rtk_setup_role_switch(struct dwc3_rtk *rtk) +{ + struct usb_role_switch_desc dwc3_role_switch = {NULL}; + + dwc3_role_switch.name = dev_name(rtk->dev); + dwc3_role_switch.driver_data = rtk; + dwc3_role_switch.allow_userspace_control = true; + dwc3_role_switch.fwnode = dev_fwnode(rtk->dev); + dwc3_role_switch.set = dwc3_usb_role_switch_set; + dwc3_role_switch.get = dwc3_usb_role_switch_get; + rtk->role_switch = usb_role_switch_register(rtk->dev, &dwc3_role_switch); + if (IS_ERR(rtk->role_switch)) + return PTR_ERR(rtk->role_switch); + + return 0; +} + +static int dwc3_rtk_remove_role_switch(struct dwc3_rtk *rtk) +{ + if (rtk->role_switch) + usb_role_switch_unregister(rtk->role_switch); + + rtk->role_switch = NULL; + + return 0; +} +#else +#define dwc3_rtk_setup_role_switch(x) 0 +#define dwc3_rtk_remove_role_switch(x) 0 +#endif + +static const char *const speed_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", + [USB_SPEED_SUPER_PLUS] = "super-speed-plus", +}; + +static enum usb_device_speed __get_dwc3_maximum_speed(struct device_node *np) +{ + const char *maximum_speed; + int ret; + + struct device_node *dwc3_np __free(device_node) = of_get_compatible_child(np, + "snps,dwc3"); + if (!dwc3_np) + return USB_SPEED_UNKNOWN; + + ret = of_property_read_string(dwc3_np, "maximum-speed", &maximum_speed); + if (ret < 0) + return USB_SPEED_UNKNOWN; + + ret = match_string(speed_names, ARRAY_SIZE(speed_names), maximum_speed); + + return (ret < 0) ? USB_SPEED_UNKNOWN : ret; +} + +static int dwc3_rtk_init(struct dwc3_rtk *rtk) +{ + struct device *dev = rtk->dev; + void __iomem *reg; + int val; + enum usb_device_speed maximum_speed; + const struct soc_device_attribute rtk_soc_kylin_a00[] = { + { .family = "Realtek Kylin", .revision = "A00", }, + { /* empty */ } }; + const struct soc_device_attribute rtk_soc_hercules[] = { + { .family = "Realtek Hercules", }, { /* empty */ } }; + const struct soc_device_attribute rtk_soc_thor[] = { + { .family = "Realtek Thor", }, { /* empty */ } }; + + if (soc_device_match(rtk_soc_kylin_a00)) { + reg = rtk->regs + WRAP_CTR_REG; + val = readl(reg); + writel(DISABLE_MULTI_REQ | val, reg); + dev_info(dev, "[bug fixed] 1295/1296 A00: add workaround to disable multiple request for D-Bus"); + } + + if (soc_device_match(rtk_soc_hercules)) { + reg = rtk->regs + WRAP_USB2_PHY_REG; + val = readl(reg); + writel(USB2_PHY_EN_PHY_PLL_PORT1 | val, reg); + dev_info(dev, "[bug fixed] 1395 add workaround to disable usb2 port 2 suspend!"); + } + + reg = rtk->regs + WRAP_USB2_PHY_UTMI_REG; + val = readl(reg); + writel(TXHSVM_EN | val, reg); + + maximum_speed = __get_dwc3_maximum_speed(dev->of_node); + if (maximum_speed != USB_SPEED_UNKNOWN && maximum_speed <= USB_SPEED_HIGH) { + if (soc_device_match(rtk_soc_thor)) { + reg = rtk->regs + WRAP_USB_HMAC_CTR0_REG; + val = readl(reg); + writel(U3PORT_DIS | val, reg); + } else { + reg = rtk->regs + WRAP_CTR_REG; + val = readl(reg); + writel(FORCE_PIPE3_PHY_STATUS_TO_0 | val, reg); + + reg = rtk->regs + WRAP_PHY_PIPE_REG; + val = ~CLOCK_ENABLE_FOR_PIPE3_PCLK & readl(reg); + writel(RESET_DISABLE_PIPE3_P0 | val, reg); + + reg = rtk->regs + WRAP_USB_HMAC_CTR0_REG; + val = readl(reg); + writel(U3PORT_DIS | val, reg); + + reg = rtk->regs + WRAP_APHY_REG; + val = readl(reg); + writel(~USB3_MBIAS_ENABLE & val, reg); + + dev_dbg(rtk->dev, "%s: disable usb 3.0 phy\n", __func__); + } + } + + reg = rtk->regs + WRAP_CTR_REG; + val = readl(reg); + writel(DESC_R2W_MULTI_DISABLE | val, reg); + + /* Set phy Dp/Dm initial state to host mode to avoid the Dp glitch */ + reg = rtk->regs + WRAP_USB2_PHY_REG; + val = ~USB2_PHY_SWITCH_MASK & readl(reg); + writel(USB2_PHY_SWITCH_HOST | val, reg); + + if (rtk->pm_base) { + reg = rtk->pm_base + USB_DBUS_PWR_CTRL_REG; + val = DBUS_PWR_CTRL_EN | readl(reg); + writel(val, reg); + } + + return 0; +} + +static int dwc3_rtk_probe_dwc3_core(struct dwc3_rtk *rtk) +{ + struct device *dev = rtk->dev; + struct device_node *node = dev->of_node; + struct platform_device *dwc3_pdev; + struct device *dwc3_dev; + enum usb_dr_mode dr_mode; + int ret = 0; + + ret = dwc3_rtk_init(rtk); + if (ret) + return -EINVAL; + + ret = of_platform_populate(node, NULL, NULL, dev); + if (ret) { + dev_err(dev, "failed to add dwc3 core\n"); + return ret; + } + + struct device_node *dwc3_node __free(device_node) = of_get_compatible_child(node, + "snps,dwc3"); + if (!dwc3_node) { + dev_err(dev, "failed to find dwc3 core node\n"); + ret = -ENODEV; + goto depopulate; + } + + dwc3_pdev = of_find_device_by_node(dwc3_node); + if (!dwc3_pdev) { + dev_err(dev, "failed to find dwc3 core platform_device\n"); + ret = -ENODEV; + goto depopulate; + } + + dwc3_dev = &dwc3_pdev->dev; + rtk->dwc = platform_get_drvdata(dwc3_pdev); + if (!rtk->dwc) { + dev_err(dev, "failed to find dwc3 core\n"); + ret = -ENODEV; + goto err_pdev_put; + } + + dr_mode = usb_get_dr_mode(dwc3_dev); + if (dr_mode != rtk->dwc->dr_mode) { + dev_info(dev, "dts set dr_mode=%d, but dwc3 set dr_mode=%d\n", + dr_mode, rtk->dwc->dr_mode); + dr_mode = rtk->dwc->dr_mode; + } + + switch (dr_mode) { + case USB_DR_MODE_PERIPHERAL: + rtk->cur_role = USB_ROLE_DEVICE; + break; + case USB_DR_MODE_HOST: + rtk->cur_role = USB_ROLE_HOST; + break; + default: + dev_dbg(rtk->dev, "%s: dr_mode=%d\n", __func__, dr_mode); + break; + } + + if (device_property_read_bool(dwc3_dev, "usb-role-switch")) { + ret = dwc3_rtk_setup_role_switch(rtk); + if (ret) { + dev_err(dev, "dwc3_rtk_setup_role_switch fail=%d\n", ret); + goto err_pdev_put; + } + rtk->cur_role = dwc3_rtk_get_role(rtk); + } + + switch_usb2_role(rtk, rtk->cur_role); + + platform_device_put(dwc3_pdev); + + return 0; + +err_pdev_put: + platform_device_put(dwc3_pdev); +depopulate: + of_platform_depopulate(dev); + + return ret; +} + +static int dwc3_rtk_probe(struct platform_device *pdev) +{ + struct dwc3_rtk *rtk; + struct device *dev = &pdev->dev; + struct resource *res; + void __iomem *regs; + + rtk = devm_kzalloc(dev, sizeof(*rtk), GFP_KERNEL); + if (!rtk) + return -ENOMEM; + + platform_set_drvdata(pdev, rtk); + + rtk->dev = dev; + + regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + rtk->regs = regs; + rtk->regs_size = resource_size(res); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + rtk->pm_base = devm_ioremap_resource(dev, res); + if (IS_ERR(rtk->pm_base)) + return PTR_ERR(rtk->pm_base); + } + + return dwc3_rtk_probe_dwc3_core(rtk); +} + +static void dwc3_rtk_remove(struct platform_device *pdev) +{ + struct dwc3_rtk *rtk = platform_get_drvdata(pdev); + + rtk->dwc = NULL; + + dwc3_rtk_remove_role_switch(rtk); + + of_platform_depopulate(rtk->dev); +} + +static void dwc3_rtk_shutdown(struct platform_device *pdev) +{ + struct dwc3_rtk *rtk = platform_get_drvdata(pdev); + + of_platform_depopulate(rtk->dev); +} + +static const struct of_device_id rtk_dwc3_match[] = { + { .compatible = "realtek,rtd-dwc3" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtk_dwc3_match); + +#ifdef CONFIG_PM_SLEEP +static int dwc3_rtk_suspend(struct device *dev) +{ + return 0; +} + +static int dwc3_rtk_resume(struct device *dev) +{ + struct dwc3_rtk *rtk = dev_get_drvdata(dev); + + dwc3_rtk_init(rtk); + + switch_usb2_role(rtk, rtk->cur_role); + + /* 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_rtk_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_rtk_suspend, dwc3_rtk_resume) +}; + +#define DEV_PM_OPS (&dwc3_rtk_dev_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static struct platform_driver dwc3_rtk_driver = { + .probe = dwc3_rtk_probe, + .remove = dwc3_rtk_remove, + .driver = { + .name = "rtk-dwc3", + .of_match_table = rtk_dwc3_match, + .pm = DEV_PM_OPS, + }, + .shutdown = dwc3_rtk_shutdown, +}; + +module_platform_driver(dwc3_rtk_driver); + +MODULE_AUTHOR("Stanley Chang "); +MODULE_DESCRIPTION("DesignWare USB3 Realtek Glue Layer"); +MODULE_ALIAS("platform:rtk-dwc3"); +MODULE_LICENSE("GPL"); +MODULE_SOFTDEP("pre: phy_rtk_usb2 phy_rtk_usb3"); diff --git a/drivers/usb/dwc3/dwc3-st.c b/drivers/usb/dwc3/dwc3-st.c new file mode 100644 index 000000000..5d513deca --- /dev/null +++ b/drivers/usb/dwc3/dwc3-st.c @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * dwc3-st.c Support for dwc3 platform devices on ST Microelectronics platforms + * + * This is a small driver for the dwc3 to provide the glue logic + * to configure the controller. Tested on STi platforms. + * + * Copyright (C) 2014 Stmicroelectronics + * + * Author: Giuseppe Cavallaro + * Contributors: Aymen Bouattay + * Peter Griffin + * + * Inspired by dwc3-omap.c and dwc3-exynos.c. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "io.h" + +/* glue registers */ +#define CLKRST_CTRL 0x00 +#define AUX_CLK_EN BIT(0) +#define SW_PIPEW_RESET_N BIT(4) +#define EXT_CFG_RESET_N BIT(8) +/* + * 1'b0 : The host controller complies with the xHCI revision 0.96 + * 1'b1 : The host controller complies with the xHCI revision 1.0 + */ +#define XHCI_REVISION BIT(12) + +#define USB2_VBUS_MNGMNT_SEL1 0x2C +/* + * For all fields in USB2_VBUS_MNGMNT_SEL1 + * 2’b00 : Override value from Reg 0x30 is selected + * 2’b01 : utmiotg_ from usb3_top is selected + * 2’b10 : pipew_ from PIPEW instance is selected + * 2’b11 : value is 1'b0 + */ +#define USB2_VBUS_REG30 0x0 +#define USB2_VBUS_UTMIOTG 0x1 +#define USB2_VBUS_PIPEW 0x2 +#define USB2_VBUS_ZERO 0x3 + +#define SEL_OVERRIDE_VBUSVALID(n) (n << 0) +#define SEL_OVERRIDE_POWERPRESENT(n) (n << 4) +#define SEL_OVERRIDE_BVALID(n) (n << 8) + +/* Static DRD configuration */ +#define USB3_CONTROL_MASK 0xf77 + +#define USB3_DEVICE_NOT_HOST BIT(0) +#define USB3_FORCE_VBUSVALID BIT(1) +#define USB3_DELAY_VBUSVALID BIT(2) +#define USB3_SEL_FORCE_OPMODE BIT(4) +#define USB3_FORCE_OPMODE(n) (n << 5) +#define USB3_SEL_FORCE_DPPULLDOWN2 BIT(8) +#define USB3_FORCE_DPPULLDOWN2 BIT(9) +#define USB3_SEL_FORCE_DMPULLDOWN2 BIT(10) +#define USB3_FORCE_DMPULLDOWN2 BIT(11) + +/** + * struct st_dwc3 - dwc3-st driver private structure + * @dev: device pointer + * @glue_base: ioaddr for the glue registers + * @regmap: regmap pointer for getting syscfg + * @syscfg_reg_off: usb syscfg control offset + * @dr_mode: drd static host/device config + * @rstc_pwrdn: rest controller for powerdown signal + * @rstc_rst: reset controller for softreset signal + */ + +struct st_dwc3 { + struct device *dev; + void __iomem *glue_base; + struct regmap *regmap; + int syscfg_reg_off; + enum usb_dr_mode dr_mode; + struct reset_control *rstc_pwrdn; + struct reset_control *rstc_rst; +}; + +static inline u32 st_dwc3_readl(void __iomem *base, u32 offset) +{ + return readl_relaxed(base + offset); +} + +static inline void st_dwc3_writel(void __iomem *base, u32 offset, u32 value) +{ + writel_relaxed(value, base + offset); +} + +/** + * st_dwc3_drd_init: program the port + * @dwc3_data: driver private structure + * Description: this function is to program the port as either host or device + * according to the static configuration passed from devicetree. + * OTG and dual role are not yet supported! + */ +static int st_dwc3_drd_init(struct st_dwc3 *dwc3_data) +{ + u32 val; + int err; + + err = regmap_read(dwc3_data->regmap, dwc3_data->syscfg_reg_off, &val); + if (err) + return err; + + val &= USB3_CONTROL_MASK; + + switch (dwc3_data->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + + val &= ~(USB3_DELAY_VBUSVALID + | USB3_SEL_FORCE_OPMODE | USB3_FORCE_OPMODE(0x3) + | USB3_SEL_FORCE_DPPULLDOWN2 | USB3_FORCE_DPPULLDOWN2 + | USB3_SEL_FORCE_DMPULLDOWN2 | USB3_FORCE_DMPULLDOWN2); + + /* + * USB3_PORT2_FORCE_VBUSVALID When '1' and when + * USB3_PORT2_DEVICE_NOT_HOST = 1, forces VBUSVLDEXT2 input + * of the pico PHY to 1. + */ + + val |= USB3_DEVICE_NOT_HOST | USB3_FORCE_VBUSVALID; + break; + + case USB_DR_MODE_HOST: + + val &= ~(USB3_DEVICE_NOT_HOST | USB3_FORCE_VBUSVALID + | USB3_SEL_FORCE_OPMODE | USB3_FORCE_OPMODE(0x3) + | USB3_SEL_FORCE_DPPULLDOWN2 | USB3_FORCE_DPPULLDOWN2 + | USB3_SEL_FORCE_DMPULLDOWN2 | USB3_FORCE_DMPULLDOWN2); + + /* + * USB3_DELAY_VBUSVALID is ANDed with USB_C_VBUSVALID. Thus, + * when set to ‘0‘, it can delay the arrival of VBUSVALID + * information to VBUSVLDEXT2 input of the pico PHY. + * We don't want to do that so we set the bit to '1'. + */ + + val |= USB3_DELAY_VBUSVALID; + break; + + default: + dev_err(dwc3_data->dev, "Unsupported mode of operation %d\n", + dwc3_data->dr_mode); + return -EINVAL; + } + + return regmap_write(dwc3_data->regmap, dwc3_data->syscfg_reg_off, val); +} + +/** + * st_dwc3_init: init the controller via glue logic + * @dwc3_data: driver private structure + */ +static void st_dwc3_init(struct st_dwc3 *dwc3_data) +{ + u32 reg = st_dwc3_readl(dwc3_data->glue_base, CLKRST_CTRL); + + reg |= AUX_CLK_EN | EXT_CFG_RESET_N | XHCI_REVISION; + reg &= ~SW_PIPEW_RESET_N; + st_dwc3_writel(dwc3_data->glue_base, CLKRST_CTRL, reg); + + /* configure mux for vbus, powerpresent and bvalid signals */ + reg = st_dwc3_readl(dwc3_data->glue_base, USB2_VBUS_MNGMNT_SEL1); + + reg |= SEL_OVERRIDE_VBUSVALID(USB2_VBUS_UTMIOTG) | + SEL_OVERRIDE_POWERPRESENT(USB2_VBUS_UTMIOTG) | + SEL_OVERRIDE_BVALID(USB2_VBUS_UTMIOTG); + + st_dwc3_writel(dwc3_data->glue_base, USB2_VBUS_MNGMNT_SEL1, reg); + + reg = st_dwc3_readl(dwc3_data->glue_base, CLKRST_CTRL); + reg |= SW_PIPEW_RESET_N; + st_dwc3_writel(dwc3_data->glue_base, CLKRST_CTRL, reg); +} + +static int st_dwc3_probe(struct platform_device *pdev) +{ + struct st_dwc3 *dwc3_data; + struct resource *res; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct platform_device *child_pdev; + struct regmap *regmap; + int ret; + + dwc3_data = devm_kzalloc(dev, sizeof(*dwc3_data), GFP_KERNEL); + if (!dwc3_data) + return -ENOMEM; + + dwc3_data->glue_base = + devm_platform_ioremap_resource_byname(pdev, "reg-glue"); + if (IS_ERR(dwc3_data->glue_base)) + return PTR_ERR(dwc3_data->glue_base); + + regmap = syscon_regmap_lookup_by_phandle(node, "st,syscfg"); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + dwc3_data->dev = dev; + dwc3_data->regmap = regmap; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "syscfg-reg"); + if (!res) + return -ENXIO; + + dwc3_data->syscfg_reg_off = res->start; + + dev_vdbg(dev, "glue-logic addr 0x%p, syscfg-reg offset 0x%x\n", + dwc3_data->glue_base, dwc3_data->syscfg_reg_off); + + struct device_node *child __free(device_node) = of_get_compatible_child(node, + "snps,dwc3"); + if (!child) { + dev_err(dev, "failed to find dwc3 core node\n"); + return -ENODEV; + } + + dwc3_data->rstc_pwrdn = + devm_reset_control_get_exclusive(dev, "powerdown"); + if (IS_ERR(dwc3_data->rstc_pwrdn)) + return dev_err_probe(dev, PTR_ERR(dwc3_data->rstc_pwrdn), + "could not get power controller\n"); + + /* Manage PowerDown */ + reset_control_deassert(dwc3_data->rstc_pwrdn); + + dwc3_data->rstc_rst = + devm_reset_control_get_shared(dev, "softreset"); + if (IS_ERR(dwc3_data->rstc_rst)) { + ret = dev_err_probe(dev, PTR_ERR(dwc3_data->rstc_rst), + "could not get reset controller\n"); + goto undo_powerdown; + } + + /* Manage SoftReset */ + reset_control_deassert(dwc3_data->rstc_rst); + + /* Allocate and initialize the core */ + ret = of_platform_populate(node, NULL, NULL, dev); + if (ret) { + dev_err(dev, "failed to add dwc3 core\n"); + goto undo_softreset; + } + + child_pdev = of_find_device_by_node(child); + if (!child_pdev) { + dev_err(dev, "failed to find dwc3 core device\n"); + ret = -ENODEV; + goto depopulate; + } + + dwc3_data->dr_mode = usb_get_dr_mode(&child_pdev->dev); + platform_device_put(child_pdev); + + /* + * Configure the USB port as device or host according to the static + * configuration passed from DT. + * DRD is the only mode currently supported so this will be enhanced + * as soon as OTG is available. + */ + ret = st_dwc3_drd_init(dwc3_data); + if (ret) { + dev_err(dev, "drd initialisation failed\n"); + goto depopulate; + } + + /* ST glue logic init */ + st_dwc3_init(dwc3_data); + + platform_set_drvdata(pdev, dwc3_data); + return 0; + +depopulate: + of_platform_depopulate(dev); +undo_softreset: + reset_control_assert(dwc3_data->rstc_rst); +undo_powerdown: + reset_control_assert(dwc3_data->rstc_pwrdn); + return ret; +} + +static void st_dwc3_remove(struct platform_device *pdev) +{ + struct st_dwc3 *dwc3_data = platform_get_drvdata(pdev); + + of_platform_depopulate(&pdev->dev); + + reset_control_assert(dwc3_data->rstc_pwrdn); + reset_control_assert(dwc3_data->rstc_rst); +} + +static int st_dwc3_suspend(struct device *dev) +{ + struct st_dwc3 *dwc3_data = dev_get_drvdata(dev); + + reset_control_assert(dwc3_data->rstc_pwrdn); + reset_control_assert(dwc3_data->rstc_rst); + + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static int st_dwc3_resume(struct device *dev) +{ + struct st_dwc3 *dwc3_data = dev_get_drvdata(dev); + int ret; + + pinctrl_pm_select_default_state(dev); + + reset_control_deassert(dwc3_data->rstc_pwrdn); + reset_control_deassert(dwc3_data->rstc_rst); + + ret = st_dwc3_drd_init(dwc3_data); + if (ret) { + dev_err(dev, "drd initialisation failed\n"); + return ret; + } + + /* ST glue logic init */ + st_dwc3_init(dwc3_data); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(st_dwc3_dev_pm_ops, st_dwc3_suspend, st_dwc3_resume); + +static const struct of_device_id st_dwc3_match[] = { + { .compatible = "st,stih407-dwc3" }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, st_dwc3_match); + +static struct platform_driver st_dwc3_driver = { + .probe = st_dwc3_probe, + .remove = st_dwc3_remove, + .driver = { + .name = "usb-st-dwc3", + .of_match_table = st_dwc3_match, + .pm = pm_sleep_ptr(&st_dwc3_dev_pm_ops), + }, +}; + +module_platform_driver(st_dwc3_driver); + +MODULE_AUTHOR("Giuseppe Cavallaro "); +MODULE_DESCRIPTION("DesignWare USB3 STi Glue Layer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c new file mode 100644 index 000000000..b832505e1 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-xilinx.c @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-xilinx.c - Xilinx DWC3 controller specific glue driver + * + * Authors: Manish Narani + * Anurag Kumar Vulisha + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* USB phy reset mask register */ +#define XLNX_USB_PHY_RST_EN 0x001C +#define XLNX_PHY_RST_MASK 0x1 + +/* Xilinx USB 3.0 IP Register */ +#define XLNX_USB_TRAFFIC_ROUTE_CONFIG 0x005C +#define XLNX_USB_TRAFFIC_ROUTE_FPD 0x1 + +/* USB 2.0 IP Register */ +#define XLNX_USB2_TRAFFIC_ROUTE_CONFIG 0x0044 + +#define XLNX_USB_FPD_PIPE_CLK 0x7c +#define PIPE_CLK_DESELECT 1 +#define PIPE_CLK_SELECT 0 +#define XLNX_USB_FPD_POWER_PRSNT 0x80 +#define FPD_POWER_PRSNT_OPTION BIT(0) + +struct dwc3_xlnx { + int num_clocks; + struct clk_bulk_data *clks; + struct device *dev; + void __iomem *regs; + int (*pltfm_init)(struct dwc3_xlnx *data); + struct phy *usb3_phy; +}; + +static void dwc3_xlnx_mask_phy_rst(struct dwc3_xlnx *priv_data, bool mask) +{ + u32 reg; + + /* + * Enable or disable ULPI PHY reset from USB Controller. + * This does not actually reset the phy, but just controls + * whether USB controller can or cannot reset ULPI PHY. + */ + reg = readl(priv_data->regs + XLNX_USB_PHY_RST_EN); + + if (mask) + reg &= ~XLNX_PHY_RST_MASK; + else + reg |= XLNX_PHY_RST_MASK; + + writel(reg, priv_data->regs + XLNX_USB_PHY_RST_EN); +} + +static void dwc3_xlnx_set_coherency(struct dwc3_xlnx *priv_data, u32 coherency_offset) +{ + struct device *dev = priv_data->dev; + u32 reg; + + /* + * This routes the USB DMA traffic to go through FPD path instead + * of reaching DDR directly. This traffic routing is needed to + * make SMMU and CCI work with USB DMA. + */ + if (of_dma_is_coherent(dev->of_node) || device_iommu_mapped(dev)) { + reg = readl(priv_data->regs + coherency_offset); + reg |= XLNX_USB_TRAFFIC_ROUTE_FPD; + writel(reg, priv_data->regs + coherency_offset); + } +} + +static int dwc3_xlnx_init_versal(struct dwc3_xlnx *priv_data) +{ + struct device *dev = priv_data->dev; + struct reset_control *crst; + int ret; + + crst = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(crst)) + return dev_err_probe(dev, PTR_ERR(crst), "failed to get reset signal\n"); + + dwc3_xlnx_mask_phy_rst(priv_data, false); + + /* assert and deassert reset */ + ret = reset_control_reset(crst); + if (ret) + return dev_err_probe(dev, ret, "failed to assert and deassert reset\n"); + + dwc3_xlnx_mask_phy_rst(priv_data, true); + dwc3_xlnx_set_coherency(priv_data, XLNX_USB2_TRAFFIC_ROUTE_CONFIG); + + return 0; +} + +static int dwc3_xlnx_init_zynqmp(struct dwc3_xlnx *priv_data) +{ + struct device *dev = priv_data->dev; + struct reset_control *crst, *hibrst, *apbrst; + struct gpio_desc *reset_gpio; + int ret = 0; + + priv_data->usb3_phy = devm_phy_optional_get(dev, "usb3-phy"); + if (IS_ERR(priv_data->usb3_phy)) { + ret = PTR_ERR(priv_data->usb3_phy); + dev_err_probe(dev, ret, + "failed to get USB3 PHY\n"); + goto err; + } + + crst = devm_reset_control_get_exclusive(dev, "usb_crst"); + if (IS_ERR(crst)) { + ret = PTR_ERR(crst); + dev_err_probe(dev, ret, + "failed to get core reset signal\n"); + goto err; + } + + hibrst = devm_reset_control_get_exclusive(dev, "usb_hibrst"); + if (IS_ERR(hibrst)) { + ret = PTR_ERR(hibrst); + dev_err_probe(dev, ret, + "failed to get hibernation reset signal\n"); + goto err; + } + + apbrst = devm_reset_control_get_exclusive(dev, "usb_apbrst"); + if (IS_ERR(apbrst)) { + ret = PTR_ERR(apbrst); + dev_err_probe(dev, ret, + "failed to get APB reset signal\n"); + goto err; + } + + /* + * Asserting the core resets is not required unless a USB3 PHY is used. + * They may also break the configuration if USB3 is actually in use but + * the usb3-phy entry is missing from the device tree. Therefore, skip + * a full reset cycle and just deassert the resets if the phy is + * absent. + */ + if (priv_data->usb3_phy) { + ret = reset_control_assert(crst); + if (ret < 0) { + dev_err(dev, "Failed to assert core reset\n"); + goto err; + } + + ret = reset_control_assert(hibrst); + if (ret < 0) { + dev_err(dev, "Failed to assert hibernation reset\n"); + goto err; + } + + ret = reset_control_assert(apbrst); + if (ret < 0) { + dev_err(dev, "Failed to assert APB reset\n"); + goto err; + } + } + + ret = phy_init(priv_data->usb3_phy); + if (ret < 0) + goto err; + + ret = reset_control_deassert(apbrst); + if (ret < 0) { + dev_err(dev, "Failed to release APB reset\n"); + goto err_phy_exit; + } + + if (priv_data->usb3_phy) { + /* Set PIPE Power Present signal in FPD Power Present Register */ + writel(FPD_POWER_PRSNT_OPTION, priv_data->regs + XLNX_USB_FPD_POWER_PRSNT); + /* Set the PIPE Clock Select bit in FPD PIPE Clock register */ + writel(PIPE_CLK_SELECT, priv_data->regs + XLNX_USB_FPD_PIPE_CLK); + } else { + /* Deselect the PIPE Clock Select bit in FPD PIPE Clock register */ + writel(PIPE_CLK_DESELECT, priv_data->regs + XLNX_USB_FPD_PIPE_CLK); + } + + ret = reset_control_deassert(crst); + if (ret < 0) { + dev_err(dev, "Failed to release core reset\n"); + goto err_phy_exit; + } + + ret = reset_control_deassert(hibrst); + if (ret < 0) { + dev_err(dev, "Failed to release hibernation reset\n"); + goto err_phy_exit; + } + + ret = phy_power_on(priv_data->usb3_phy); + if (ret < 0) + goto err_phy_exit; + + /* ulpi reset via gpio-modepin or gpio-framework driver */ + reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(reset_gpio)) { + ret = PTR_ERR(reset_gpio); + goto err_phy_power_off; + } + + if (reset_gpio) { + usleep_range(5000, 10000); + gpiod_set_value_cansleep(reset_gpio, 0); + usleep_range(5000, 10000); + } + + dwc3_xlnx_set_coherency(priv_data, XLNX_USB_TRAFFIC_ROUTE_CONFIG); + + return 0; + +err_phy_power_off: + phy_power_off(priv_data->usb3_phy); +err_phy_exit: + phy_exit(priv_data->usb3_phy); +err: + return ret; +} + +static const struct of_device_id dwc3_xlnx_of_match[] = { + { + .compatible = "xlnx,zynqmp-dwc3", + .data = &dwc3_xlnx_init_zynqmp, + }, + { + .compatible = "xlnx,versal-dwc3", + .data = &dwc3_xlnx_init_versal, + }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dwc3_xlnx_of_match); + +static int dwc3_set_swnode(struct device *dev) +{ + struct device_node *np = dev->of_node, *dwc3_np; + struct property_entry props[2]; + int prop_idx = 0, ret = 0; + + dwc3_np = of_get_compatible_child(np, "snps,dwc3"); + if (!dwc3_np) { + ret = -ENODEV; + dev_err(dev, "failed to find dwc3 core child\n"); + return ret; + } + + memset(props, 0, sizeof(struct property_entry) * ARRAY_SIZE(props)); + if (of_dma_is_coherent(dwc3_np)) + props[prop_idx++] = PROPERTY_ENTRY_U16("snps,gsbuscfg0-reqinfo", + 0xffff); + of_node_put(dwc3_np); + + if (prop_idx) + ret = device_create_managed_software_node(dev, props, NULL); + + return ret; +} + +static int dwc3_xlnx_probe(struct platform_device *pdev) +{ + struct dwc3_xlnx *priv_data; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + const struct of_device_id *match; + void __iomem *regs; + int ret; + + priv_data = devm_kzalloc(dev, sizeof(*priv_data), GFP_KERNEL); + if (!priv_data) + return -ENOMEM; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return dev_err_probe(dev, PTR_ERR(regs), "failed to map registers\n"); + + match = of_match_node(dwc3_xlnx_of_match, pdev->dev.of_node); + + priv_data->pltfm_init = match->data; + priv_data->regs = regs; + priv_data->dev = dev; + + platform_set_drvdata(pdev, priv_data); + + ret = devm_clk_bulk_get_all(priv_data->dev, &priv_data->clks); + if (ret < 0) + return ret; + + priv_data->num_clocks = ret; + + ret = clk_bulk_prepare_enable(priv_data->num_clocks, priv_data->clks); + if (ret) + return ret; + + ret = priv_data->pltfm_init(priv_data); + if (ret) + goto err_clk_put; + + ret = dwc3_set_swnode(dev); + if (ret) + goto err_clk_put; + + ret = of_platform_populate(np, NULL, NULL, dev); + if (ret) + goto err_clk_put; + + pm_runtime_set_active(dev); + ret = devm_pm_runtime_enable(dev); + if (ret < 0) + goto err_pm_set_suspended; + + pm_suspend_ignore_children(dev, false); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + goto err_pm_set_suspended; + + return 0; + +err_pm_set_suspended: + of_platform_depopulate(dev); + pm_runtime_set_suspended(dev); + +err_clk_put: + clk_bulk_disable_unprepare(priv_data->num_clocks, priv_data->clks); + + return ret; +} + +static void dwc3_xlnx_remove(struct platform_device *pdev) +{ + struct dwc3_xlnx *priv_data = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + of_platform_depopulate(dev); + + clk_bulk_disable_unprepare(priv_data->num_clocks, priv_data->clks); + priv_data->num_clocks = 0; + + pm_runtime_put_noidle(dev); + pm_runtime_set_suspended(dev); +} + +static int __maybe_unused dwc3_xlnx_runtime_suspend(struct device *dev) +{ + struct dwc3_xlnx *priv_data = dev_get_drvdata(dev); + + clk_bulk_disable(priv_data->num_clocks, priv_data->clks); + + return 0; +} + +static int __maybe_unused dwc3_xlnx_runtime_resume(struct device *dev) +{ + struct dwc3_xlnx *priv_data = dev_get_drvdata(dev); + + return clk_bulk_enable(priv_data->num_clocks, priv_data->clks); +} + +static int __maybe_unused dwc3_xlnx_runtime_idle(struct device *dev) +{ + pm_runtime_autosuspend(dev); + + return 0; +} + +static int __maybe_unused dwc3_xlnx_suspend(struct device *dev) +{ + struct dwc3_xlnx *priv_data = dev_get_drvdata(dev); + + phy_exit(priv_data->usb3_phy); + + /* Disable the clocks */ + clk_bulk_disable(priv_data->num_clocks, priv_data->clks); + + return 0; +} + +static int __maybe_unused dwc3_xlnx_resume(struct device *dev) +{ + struct dwc3_xlnx *priv_data = dev_get_drvdata(dev); + int ret; + + ret = clk_bulk_enable(priv_data->num_clocks, priv_data->clks); + if (ret) + return ret; + + ret = phy_init(priv_data->usb3_phy); + if (ret < 0) + return ret; + + ret = phy_power_on(priv_data->usb3_phy); + if (ret < 0) { + phy_exit(priv_data->usb3_phy); + return ret; + } + + return 0; +} + +static const struct dev_pm_ops dwc3_xlnx_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_xlnx_suspend, dwc3_xlnx_resume) + SET_RUNTIME_PM_OPS(dwc3_xlnx_runtime_suspend, + dwc3_xlnx_runtime_resume, dwc3_xlnx_runtime_idle) +}; + +static struct platform_driver dwc3_xlnx_driver = { + .probe = dwc3_xlnx_probe, + .remove = dwc3_xlnx_remove, + .shutdown = dwc3_xlnx_remove, + .driver = { + .name = "dwc3-xilinx", + .of_match_table = dwc3_xlnx_of_match, + .pm = &dwc3_xlnx_dev_pm_ops, + }, +}; + +module_platform_driver(dwc3_xlnx_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Xilinx DWC3 controller specific glue driver"); +MODULE_AUTHOR("Manish Narani "); +MODULE_AUTHOR("Anurag Kumar Vulisha "); diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c new file mode 100644 index 000000000..bfe616194 --- /dev/null +++ b/drivers/usb/dwc3/ep0.c @@ -0,0 +1,1233 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ep0.c - DesignWare USB3 DRD Controller Endpoint 0 Handling + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "core.h" +#include "debug.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 int dwc3_ep0_delegate_req(struct dwc3 *dwc, + struct usb_ctrlrequest *ctrl); + +static void dwc3_ep0_prepare_one_trb(struct dwc3_ep *dep, + dma_addr_t buf_dma, u32 len, u32 type, bool chain) +{ + struct dwc3_trb *trb; + struct dwc3 *dwc; + + dwc = dep->dwc; + trb = &dwc->ep0_trb[dep->trb_enqueue]; + + if (chain) + dep->trb_enqueue++; + + 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_ISP_IMI); + + if (chain) + trb->ctrl |= DWC3_TRB_CTRL_CHN; + else + trb->ctrl |= (DWC3_TRB_CTRL_IOC + | DWC3_TRB_CTRL_LST); + + trace_dwc3_prepare_trb(dep, trb); +} + +static int dwc3_ep0_start_trans(struct dwc3_ep *dep) +{ + struct dwc3_gadget_ep_cmd_params params; + struct dwc3 *dwc; + int ret; + + if (dep->flags & DWC3_EP_TRANSFER_STARTED) + return 0; + + dwc = dep->dwc; + + 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(dep, DWC3_DEPCMD_STARTTRANSFER, ¶ms); + if (ret < 0) + return ret; + + 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; + req->status = DWC3_REQUEST_STATUS_QUEUED; + + list_add_tail(&req->list, &dep->pending_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 int 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 int direction; + + direction = !dwc->ep0_expect_in; + dwc->delayed_status = false; + usb_gadget_set_state(dwc->gadget, USB_STATE_CONFIGURED); + + if (dwc->ep0state == EP0_STATUS_PHASE) + __dwc3_ep0_do_control_status(dwc, dwc->eps[direction]); + + 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 issuing 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 int 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 || !dwc->pullups_connected || !dwc->connected) { + dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", + dep->name); + ret = -ESHUTDOWN; + goto out; + } + + /* we share one TRB for ep0/1 */ + if (!list_empty(&dep->pending_list)) { + ret = -EBUSY; + goto out; + } + + ret = __dwc3_gadget_ep0_queue(dep, req); + +out: + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +void dwc3_ep0_stall_and_restart(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + + /* reinitialize physical ep1 */ + dep = dwc->eps[1]; + dep->flags &= DWC3_EP_RESOURCE_ALLOCATED; + 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_RESOURCE_ALLOCATED | DWC3_EP_TRANSFER_STARTED; + dep->flags |= DWC3_EP_ENABLED; + dwc->delayed_status = false; + + if (!list_empty(&dep->pending_list)) { + struct dwc3_request *req; + + req = next_request(&dep->pending_list); + if (!dwc->connected) + dwc3_gadget_giveback(dep, req, -ESHUTDOWN); + else + dwc3_gadget_giveback(dep, req, -ECONNRESET); + } + + dwc->eps[0]->trb_enqueue = 0; + dwc->eps[1]->trb_enqueue = 0; + 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; +} + +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; + unsigned long flags; + int ret; + + spin_lock_irqsave(&dwc->lock, flags); + ret = __dwc3_gadget_ep0_set_halt(ep, value); + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +void dwc3_ep0_out_start(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + int ret; + int i; + + complete(&dwc->ep0_in_setup); + + dep = dwc->eps[0]; + dwc3_ep0_prepare_one_trb(dep, dwc->ep0_trb_addr, 8, + DWC3_TRBCTL_CONTROL_SETUP, false); + ret = dwc3_ep0_start_trans(dep); + if (ret < 0) + dev_err(dwc->dev, "ep0 out start transfer failed: %d\n", ret); + + for (i = 2; i < DWC3_ENDPOINTS_NUM; i++) { + struct dwc3_ep *dwc3_ep; + + dwc3_ep = dwc->eps[i]; + if (!dwc3_ep) + continue; + + if (!(dwc3_ep->flags & DWC3_EP_DELAY_STOP)) + continue; + + dwc3_ep->flags &= ~DWC3_EP_DELAY_STOP; + if (dwc->connected) + dwc3_stop_active_transfer(dwc3_ep, true, true); + else + dwc3_remove_requests(dwc, dwc3_ep, -ESHUTDOWN); + } +} + +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 == NULL) + return NULL; + + 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 value; + u32 reg; + u16 usb_status = 0; + __le16 *response_pkt; + + /* We don't support PTM_STATUS */ + value = le16_to_cpu(ctrl->wValue); + if (value != 0) + return -EINVAL; + + 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->gadget->is_selfpowered; + + if ((dwc->speed == DWC3_DSTS_SUPERSPEED) || + (dwc->speed == DWC3_DSTS_SUPERSPEED_PLUS)) { + reg = dwc3_readl(dwc, 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; + } else { + usb_status |= dwc->gadget->wakeup_armed << + USB_DEVICE_REMOTE_WAKEUP; + } + + break; + + case USB_RECIP_INTERFACE: + /* + * Function Remote Wake Capable D0 + * Function Remote Wakeup D1 + */ + return dwc3_ep0_delegate_req(dwc, ctrl); + + 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_u1(struct dwc3 *dwc, enum usb_device_state state, + int set) +{ + u32 reg; + + if (state != USB_STATE_CONFIGURED) + return -EINVAL; + if ((dwc->speed != DWC3_DSTS_SUPERSPEED) && + (dwc->speed != DWC3_DSTS_SUPERSPEED_PLUS)) + return -EINVAL; + if (set && dwc->dis_u1_entry_quirk) + return -EINVAL; + + reg = dwc3_readl(dwc, DWC3_DCTL); + if (set) + reg |= DWC3_DCTL_INITU1ENA; + else + reg &= ~DWC3_DCTL_INITU1ENA; + dwc3_writel(dwc, DWC3_DCTL, reg); + + return 0; +} + +static int dwc3_ep0_handle_u2(struct dwc3 *dwc, enum usb_device_state state, + int set) +{ + u32 reg; + + + if (state != USB_STATE_CONFIGURED) + return -EINVAL; + if ((dwc->speed != DWC3_DSTS_SUPERSPEED) && + (dwc->speed != DWC3_DSTS_SUPERSPEED_PLUS)) + return -EINVAL; + if (set && dwc->dis_u2_entry_quirk) + return -EINVAL; + + reg = dwc3_readl(dwc, DWC3_DCTL); + if (set) + reg |= DWC3_DCTL_INITU2ENA; + else + reg &= ~DWC3_DCTL_INITU2ENA; + dwc3_writel(dwc, DWC3_DCTL, reg); + + return 0; +} + +static int dwc3_ep0_handle_test(struct dwc3 *dwc, enum usb_device_state state, + u32 wIndex, int set) +{ + if ((wIndex & 0xff) != 0) + return -EINVAL; + if (!set) + return -EINVAL; + + switch (wIndex >> 8) { + case USB_TEST_J: + case USB_TEST_K: + case USB_TEST_SE0_NAK: + case USB_TEST_PACKET: + case USB_TEST_FORCE_ENABLE: + dwc->test_mode_nr = wIndex >> 8; + dwc->test_mode = true; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int dwc3_ep0_handle_device(struct dwc3 *dwc, + struct usb_ctrlrequest *ctrl, int set) +{ + enum usb_device_state state; + u32 wValue; + u32 wIndex; + int ret = 0; + + wValue = le16_to_cpu(ctrl->wValue); + wIndex = le16_to_cpu(ctrl->wIndex); + state = dwc->gadget->state; + + switch (wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + if (dwc->wakeup_configured) + dwc->gadget->wakeup_armed = set; + else + ret = -EINVAL; + break; + /* + * 9.4.1 says only for SS, in AddressState only for + * default control pipe + */ + case USB_DEVICE_U1_ENABLE: + ret = dwc3_ep0_handle_u1(dwc, state, set); + break; + case USB_DEVICE_U2_ENABLE: + ret = dwc3_ep0_handle_u2(dwc, state, set); + break; + case USB_DEVICE_LTM_ENABLE: + ret = -EINVAL; + break; + case USB_DEVICE_TEST_MODE: + ret = dwc3_ep0_handle_test(dwc, state, wIndex, set); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int dwc3_ep0_handle_intf(struct dwc3 *dwc, + struct usb_ctrlrequest *ctrl, int set) +{ + u32 wValue; + int ret = 0; + + wValue = le16_to_cpu(ctrl->wValue); + + switch (wValue) { + case USB_INTRF_FUNC_SUSPEND: + ret = dwc3_ep0_delegate_req(dwc, ctrl); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int dwc3_ep0_handle_endpoint(struct dwc3 *dwc, + struct usb_ctrlrequest *ctrl, int set) +{ + struct dwc3_ep *dep; + u32 wValue; + int ret; + + wValue = le16_to_cpu(ctrl->wValue); + + switch (wValue) { + case USB_ENDPOINT_HALT: + dep = dwc3_wIndex_to_dep(dwc, ctrl->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; + + /* ClearFeature(Halt) may need delayed status */ + if (!set && (dep->flags & DWC3_EP_END_TRANSFER_PENDING)) + return USB_GADGET_DELAYED_STATUS; + + break; + default: + return -EINVAL; + } + + return 0; +} + +static int dwc3_ep0_handle_feature(struct dwc3 *dwc, + struct usb_ctrlrequest *ctrl, int set) +{ + u32 recip; + int ret; + + recip = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recip) { + case USB_RECIP_DEVICE: + ret = dwc3_ep0_handle_device(dwc, ctrl, set); + break; + case USB_RECIP_INTERFACE: + ret = dwc3_ep0_handle_intf(dwc, ctrl, set); + break; + case USB_RECIP_ENDPOINT: + ret = dwc3_ep0_handle_endpoint(dwc, ctrl, set); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +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_err(dwc->dev, "invalid device address %d\n", addr); + return -EINVAL; + } + + if (state == USB_STATE_CONFIGURED) { + dev_err(dwc->dev, "can't SetAddress() from Configured State\n"); + return -EINVAL; + } + + reg = dwc3_readl(dwc, DWC3_DCFG); + reg &= ~(DWC3_DCFG_DEVADDR_MASK); + reg |= DWC3_DCFG_DEVADDR(addr); + dwc3_writel(dwc, 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 = -EINVAL; + + if (dwc->async_callbacks) { + 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; + + cfg = le16_to_cpu(ctrl->wValue); + + switch (state) { + case USB_STATE_DEFAULT: + return -EINVAL; + + case USB_STATE_ADDRESS: + dwc3_gadget_start_config(dwc, 2); + dwc3_gadget_clear_tx_fifos(dwc); + + 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))) { + + /* + * only change state if set_config has already + * been processed. If gadget driver returns + * USB_GADGET_DELAYED_STATUS, we will wait + * to change the state on the next usb_ep_queue() + */ + if (ret == 0) + 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, DWC3_DCTL); + if (!dwc->dis_u1_entry_quirk) + reg |= DWC3_DCTL_ACCEPTU1ENA; + if (!dwc->dis_u2_entry_quirk) + reg |= DWC3_DCTL_ACCEPTU2ENA; + dwc3_writel(dwc, DWC3_DCTL, reg); + } + break; + + case USB_STATE_CONFIGURED: + ret = dwc3_ep0_delegate_req(dwc, ctrl); + if (!cfg && !ret) + 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; + __le16 u2sel; + __le16 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, 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; + + if (state == USB_STATE_DEFAULT) + return -EINVAL; + + 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; + + dwc->gadget->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: + ret = dwc3_ep0_handle_status(dwc, ctrl); + break; + case USB_REQ_CLEAR_FEATURE: + ret = dwc3_ep0_handle_feature(dwc, ctrl, 0); + break; + case USB_REQ_SET_FEATURE: + ret = dwc3_ep0_handle_feature(dwc, ctrl, 1); + break; + case USB_REQ_SET_ADDRESS: + ret = dwc3_ep0_set_address(dwc, ctrl); + break; + case USB_REQ_SET_CONFIGURATION: + ret = dwc3_ep0_set_config(dwc, ctrl); + break; + case USB_REQ_SET_SEL: + ret = dwc3_ep0_set_sel(dwc, ctrl); + break; + case USB_REQ_SET_ISOCH_DELAY: + ret = dwc3_ep0_set_isoch_delay(dwc, ctrl); + break; + default: + 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 = (void *) dwc->ep0_trb; + int ret = -EINVAL; + u32 len; + + if (!dwc->gadget_driver || !dwc->softconnect || !dwc->connected) + goto out; + + trace_dwc3_ctrl_req(dwc, ctrl); + + 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; + struct usb_request *ur; + struct dwc3_trb *trb; + struct dwc3_ep *ep0; + u32 transferred = 0; + u32 status; + u32 length; + u8 epnum; + + epnum = event->endpoint_number; + ep0 = dwc->eps[0]; + + dwc->ep0_next_event = DWC3_EP0_NRDY_STATUS; + trb = dwc->ep0_trb; + trace_dwc3_complete_trb(ep0, trb); + + r = next_request(&ep0->pending_list); + if (!r) + return; + + status = DWC3_TRB_SIZE_TRBSTS(trb->size); + if (status == DWC3_TRBSTS_SETUP_PENDING) { + dwc->setup_packet_pending = true; + if (r) + dwc3_gadget_giveback(ep0, r, -ECONNRESET); + + return; + } + + ur = &r->request; + + length = trb->size & DWC3_TRB_SIZE_MASK; + transferred = ur->length - length; + ur->actual += transferred; + + if ((IS_ALIGNED(ur->length, ep0->endpoint.maxpacket) && + ur->length && ur->zero) || dwc->ep0_bounced) { + trb++; + trb->ctrl &= ~DWC3_TRB_CTRL_HWO; + trace_dwc3_complete_trb(ep0, trb); + + if (r->direction) + dwc->eps[1]->trb_enqueue = 0; + else + dwc->eps[0]->trb_enqueue = 0; + + dwc->ep0_bounced = false; + } + + if ((epnum & 1) && ur->actual < ur->length) + dwc3_ep0_stall_and_restart(dwc); + else + 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; + + trace_dwc3_complete_trb(dep, trb); + + if (!list_empty(&dep->pending_list)) { + r = next_request(&dep->pending_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_err(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) + dwc->setup_packet_pending = true; + + 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_TRANSFER_STARTED; + dep->resource_index = 0; + dwc->setup_packet_pending = false; + + switch (dwc->ep0state) { + case EP0_SETUP_PHASE: + dwc3_ep0_inspect_setup(dwc, event); + break; + + case EP0_DATA_PHASE: + dwc3_ep0_complete_data(dwc, event); + break; + + case EP0_STATUS_PHASE: + 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) +{ + unsigned int trb_length = 0; + int ret; + + req->direction = !!dep->number; + + if (req->request.length == 0) { + if (!req->direction) + trb_length = dep->endpoint.maxpacket; + + dwc3_ep0_prepare_one_trb(dep, dwc->bounce_addr, trb_length, + DWC3_TRBCTL_CONTROL_DATA, false); + ret = dwc3_ep0_start_trans(dep); + } else if (!IS_ALIGNED(req->request.length, dep->endpoint.maxpacket) + && (dep->number == 0)) { + u32 maxpacket; + u32 rem; + + ret = usb_gadget_map_request_by_dev(dwc->sysdev, + &req->request, dep->number); + if (ret) + return; + + maxpacket = dep->endpoint.maxpacket; + rem = req->request.length % maxpacket; + dwc->ep0_bounced = true; + + /* prepare normal TRB */ + dwc3_ep0_prepare_one_trb(dep, req->request.dma, + req->request.length, + DWC3_TRBCTL_CONTROL_DATA, + true); + + req->trb = &dwc->ep0_trb[dep->trb_enqueue - 1]; + + /* Now prepare one extra TRB to align transfer size */ + dwc3_ep0_prepare_one_trb(dep, dwc->bounce_addr, + maxpacket - rem, + DWC3_TRBCTL_CONTROL_DATA, + false); + ret = dwc3_ep0_start_trans(dep); + } else if (IS_ALIGNED(req->request.length, dep->endpoint.maxpacket) && + req->request.length && req->request.zero) { + + ret = usb_gadget_map_request_by_dev(dwc->sysdev, + &req->request, dep->number); + if (ret) + return; + + /* prepare normal TRB */ + dwc3_ep0_prepare_one_trb(dep, req->request.dma, + req->request.length, + DWC3_TRBCTL_CONTROL_DATA, + true); + + req->trb = &dwc->ep0_trb[dep->trb_enqueue - 1]; + + if (!req->direction) + trb_length = dep->endpoint.maxpacket; + + /* Now prepare one extra TRB to align transfer size */ + dwc3_ep0_prepare_one_trb(dep, dwc->bounce_addr, + trb_length, DWC3_TRBCTL_CONTROL_DATA, + false); + ret = dwc3_ep0_start_trans(dep); + } else { + ret = usb_gadget_map_request_by_dev(dwc->sysdev, + &req->request, dep->number); + if (ret) + return; + + dwc3_ep0_prepare_one_trb(dep, req->request.dma, + req->request.length, DWC3_TRBCTL_CONTROL_DATA, + false); + + req->trb = &dwc->ep0_trb[dep->trb_enqueue]; + + ret = dwc3_ep0_start_trans(dep); + } + + if (ret < 0) + dev_err(dwc->dev, + "ep0 data phase start transfer failed: %d\n", ret); +} + +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; + + dwc3_ep0_prepare_one_trb(dep, dwc->ep0_trb_addr, 0, type, false); + return dwc3_ep0_start_trans(dep); +} + +static void __dwc3_ep0_do_control_status(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + int ret; + + ret = dwc3_ep0_start_control_status(dep); + if (ret) + dev_err(dwc->dev, + "ep0 status phase start transfer failed: %d\n", ret); +} + +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); +} + +void dwc3_ep0_send_delayed_status(struct dwc3 *dwc) +{ + unsigned int direction = !dwc->ep0_expect_in; + + dwc->delayed_status = false; + dwc->clear_stall_protocol = 0; + + if (dwc->ep0state != EP0_STATUS_PHASE) + return; + + __dwc3_ep0_do_control_status(dwc, dwc->eps[direction]); +} + +void dwc3_ep0_end_control_data(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + struct dwc3_gadget_ep_cmd_params params; + u32 cmd; + int ret; + + /* + * For status/DATA OUT stage, TRB will be queued on ep0 out + * endpoint for which resource index is zero. Hence allow + * queuing ENDXFER command for ep0 out endpoint. + */ + if (!dep->resource_index && dep->number) + 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(dep, cmd, ¶ms); + if (ret) + dev_err_ratelimited(dwc->dev, + "ep0 data phase end transfer failed: %d\n", ret); + + dep->resource_index = 0; +} + +static void dwc3_ep0_xfernotready(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + switch (event->status) { + case DEPEVT_STATUS_CONTROL_DATA: + if (!dwc->softconnect || !dwc->connected) + return; + /* + * 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_err(dwc->dev, "unexpected 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; + + if (dwc->setup_packet_pending) { + dwc3_ep0_stall_and_restart(dwc); + return; + } + + dwc->ep0state = EP0_STATUS_PHASE; + + if (dwc->delayed_status) { + struct dwc3_ep *dep = dwc->eps[0]; + + WARN_ON_ONCE(event->endpoint_number != 1); + /* + * We should handle the delay STATUS phase here if the + * request for handling delay STATUS has been queued + * into the list. + */ + if (!list_empty(&dep->pending_list)) { + dwc->delayed_status = false; + usb_gadget_set_state(dwc->gadget, + USB_STATE_CONFIGURED); + dwc3_ep0_do_control_status(dwc, event); + } + + return; + } + + dwc3_ep0_do_control_status(dwc, event); + } +} + +void dwc3_ep0_interrupt(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + struct dwc3_ep *dep = dwc->eps[event->endpoint_number]; + u8 cmd; + + 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: + break; + case DWC3_DEPEVT_EPCMDCMPLT: + cmd = DEPEVT_PARAMETER_CMD(event->parameters); + + if (cmd == DWC3_DEPCMD_ENDTRANSFER) { + dep->flags &= ~DWC3_EP_END_TRANSFER_PENDING; + dep->flags &= ~DWC3_EP_TRANSFER_STARTED; + } + break; + default: + dev_err(dwc->dev, "unknown endpoint event %d\n", event->endpoint_event); + break; + } +} diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c new file mode 100644 index 000000000..fa944856f --- /dev/null +++ b/drivers/usb/dwc3/gadget.c @@ -0,0 +1,4910 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * gadget.c - DesignWare USB3 DRD Controller Gadget Framework Link + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "debug.h" +#include "core.h" +#include "gadget.h" +#include "io.h" + +#define DWC3_ALIGN_FRAME(d, n) (((d)->frame_number + ((d)->interval * (n))) \ + & ~((d)->interval - 1)) + +/** + * 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, DWC3_DCTL); + reg &= ~DWC3_DCTL_TSTCTRL_MASK; + + switch (mode) { + case USB_TEST_J: + case USB_TEST_K: + case USB_TEST_SE0_NAK: + case USB_TEST_PACKET: + case USB_TEST_FORCE_ENABLE: + reg |= mode << 1; + break; + default: + return -EINVAL; + } + + dwc3_gadget_dctl_write_safe(dwc, reg); + + return 0; +} + +/** + * dwc3_gadget_get_link_state - gets current state of usb link + * @dwc: pointer to our context structure + * + * Caller should take care of locking. This function will + * return the link state on success (>= 0) or -ETIMEDOUT. + */ +int dwc3_gadget_get_link_state(struct dwc3 *dwc) +{ + u32 reg; + + reg = dwc3_readl(dwc, DWC3_DSTS); + + return DWC3_DSTS_USBLNKST(reg); +} + +/** + * 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 (!DWC3_VER_IS_PRIOR(DWC3, 194A)) { + while (--retries) { + reg = dwc3_readl(dwc, DWC3_DSTS); + if (reg & DWC3_DSTS_DCNRD) + udelay(5); + else + break; + } + + if (retries <= 0) + return -ETIMEDOUT; + } + + reg = dwc3_readl(dwc, DWC3_DCTL); + reg &= ~DWC3_DCTL_ULSTCHNGREQ_MASK; + + /* set no action before sending new link state change */ + dwc3_writel(dwc, DWC3_DCTL, reg); + + /* set requested state */ + reg |= DWC3_DCTL_ULSTCHNGREQ(state); + dwc3_writel(dwc, DWC3_DCTL, reg); + + /* + * The following code is racy when called from dwc3_gadget_wakeup, + * and is not needed, at least on newer versions + */ + if (!DWC3_VER_IS_PRIOR(DWC3, 194A)) + return 0; + + /* wait for a change in DSTS */ + retries = 10000; + while (--retries) { + reg = dwc3_readl(dwc, DWC3_DSTS); + + if (DWC3_DSTS_USBLNKST(reg) == state) + return 0; + + udelay(5); + } + + return -ETIMEDOUT; +} + +static void dwc3_ep0_reset_state(struct dwc3 *dwc) +{ + unsigned int dir; + + if (dwc->ep0state != EP0_SETUP_PHASE) { + dir = !!dwc->ep0_expect_in; + if (dwc->ep0state == EP0_DATA_PHASE) + dwc3_ep0_end_control_data(dwc, dwc->eps[dir]); + else + dwc3_ep0_end_control_data(dwc, dwc->eps[!dir]); + + dwc->eps[0]->trb_enqueue = 0; + dwc->eps[1]->trb_enqueue = 0; + + dwc3_ep0_stall_and_restart(dwc); + } +} + +/** + * dwc3_ep_inc_trb - increment a trb index. + * @index: Pointer to the TRB index to increment. + * + * The index should never point to the link TRB. After incrementing, + * if it is point to the link TRB, wrap around to the beginning. The + * link TRB is always at the last TRB entry. + */ +static void dwc3_ep_inc_trb(u8 *index) +{ + (*index)++; + if (*index == (DWC3_TRB_NUM - 1)) + *index = 0; +} + +/** + * dwc3_ep_inc_enq - increment endpoint's enqueue pointer + * @dep: The endpoint whose enqueue pointer we're incrementing + */ +static void dwc3_ep_inc_enq(struct dwc3_ep *dep) +{ + dwc3_ep_inc_trb(&dep->trb_enqueue); +} + +/** + * dwc3_ep_inc_deq - increment endpoint's dequeue pointer + * @dep: The endpoint whose enqueue pointer we're incrementing + */ +static void dwc3_ep_inc_deq(struct dwc3_ep *dep) +{ + dwc3_ep_inc_trb(&dep->trb_dequeue); +} + +static void dwc3_gadget_del_and_unmap_request(struct dwc3_ep *dep, + struct dwc3_request *req, int status) +{ + struct dwc3 *dwc = dep->dwc; + + list_del(&req->list); + req->remaining = 0; + req->num_trbs = 0; + + if (req->request.status == -EINPROGRESS) + req->request.status = status; + + if (req->trb) + usb_gadget_unmap_request_by_dev(dwc->sysdev, + &req->request, req->direction); + + req->trb = NULL; + trace_dwc3_gadget_giveback(req); + + if (dep->number > 1) + pm_runtime_put(dwc->dev); +} + +/** + * dwc3_gadget_giveback - call struct usb_request's ->complete callback + * @dep: The endpoint to whom the request belongs to + * @req: The request we're giving back + * @status: completion code for the request + * + * Must be called with controller's lock held and interrupts disabled. This + * function will unmap @req and call its ->complete() callback to notify upper + * layers that it has completed. + */ +void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, + int status) +{ + struct dwc3 *dwc = dep->dwc; + + /* + * The request might have been processed and completed while the + * spinlock was released. Skip processing if already completed. + */ + if (req->status == DWC3_REQUEST_STATUS_COMPLETED) + return; + + dwc3_gadget_del_and_unmap_request(dep, req, status); + req->status = DWC3_REQUEST_STATUS_COMPLETED; + + spin_unlock(&dwc->lock); + usb_gadget_giveback_request(&dep->endpoint, &req->request); + spin_lock(&dwc->lock); +} + +/** + * dwc3_send_gadget_generic_command - issue a generic command for the controller + * @dwc: pointer to the controller context + * @cmd: the command to be issued + * @param: command parameter + * + * Caller should take care of locking. Issue @cmd with a given @param to @dwc + * and wait for its completion. + */ +int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned int cmd, + u32 param) +{ + u32 timeout = 500; + int status = 0; + int ret = 0; + u32 reg; + + dwc3_writel(dwc, DWC3_DGCMDPAR, param); + dwc3_writel(dwc, DWC3_DGCMD, cmd | DWC3_DGCMD_CMDACT); + + do { + reg = dwc3_readl(dwc, DWC3_DGCMD); + if (!(reg & DWC3_DGCMD_CMDACT)) { + status = DWC3_DGCMD_STATUS(reg); + if (status) + ret = -EINVAL; + break; + } + } while (--timeout); + + if (!timeout) { + ret = -ETIMEDOUT; + status = -ETIMEDOUT; + } + + trace_dwc3_gadget_generic_cmd(dwc, cmd, param, status); + + return ret; +} + +/** + * dwc3_send_gadget_ep_cmd - issue an endpoint command + * @dep: the endpoint to which the command is going to be issued + * @cmd: the command to be issued + * @params: parameters to the command + * + * Caller should handle locking. This function will issue @cmd with given + * @params to @dep and wait for its completion. + * + * According to the programming guide, if the link state is in L1/L2/U3, + * then sending the Start Transfer command may not complete. The + * programming guide suggested to bring the link state back to ON/U0 by + * performing remote wakeup prior to sending the command. However, don't + * initiate remote wakeup when the user/function does not send wakeup + * request via wakeup ops. Send the command when it's allowed. + * + * Notes: + * For L1 link state, issuing a command requires the clearing of + * GUSB2PHYCFG.SUSPENDUSB2, which turns on the signal required to complete + * the given command (usually within 50us). This should happen within the + * command timeout set by driver. No additional step is needed. + * + * For L2 or U3 link state, the gadget is in USB suspend. Care should be + * taken when sending Start Transfer command to ensure that it's done after + * USB resume. + */ +int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd, + struct dwc3_gadget_ep_cmd_params *params) +{ + const struct usb_endpoint_descriptor *desc = dep->endpoint.desc; + struct dwc3 *dwc = dep->dwc; + u32 timeout = 5000; + u32 saved_config = 0; + u32 reg; + + int cmd_status = 0; + int ret = -EINVAL; + u8 epnum = dep->number; + + /* + * When operating in USB 2.0 speeds (HS/FS), if GUSB2PHYCFG.ENBLSLPM or + * GUSB2PHYCFG.SUSPHY is set, it must be cleared before issuing an + * endpoint command. + * + * Save and clear both GUSB2PHYCFG.ENBLSLPM and GUSB2PHYCFG.SUSPHY + * settings. Restore them after the command is completed. + * + * DWC_usb3 3.30a and DWC_usb31 1.90a programming guide section 3.2.2 + */ + if (dwc->gadget->speed <= USB_SPEED_HIGH || + DWC3_DEPCMD_CMD(cmd) == DWC3_DEPCMD_ENDTRANSFER) { + reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(0)); + if (unlikely(reg & DWC3_GUSB2PHYCFG_SUSPHY)) { + saved_config |= DWC3_GUSB2PHYCFG_SUSPHY; + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + } + + if (reg & DWC3_GUSB2PHYCFG_ENBLSLPM) { + saved_config |= DWC3_GUSB2PHYCFG_ENBLSLPM; + reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM; + } + + if (saved_config) + dwc3_writel(dwc, DWC3_GUSB2PHYCFG(0), reg); + } + + /* + * For some commands such as Update Transfer command, DEPCMDPARn + * registers are reserved. Since the driver often sends Update Transfer + * command, don't write to DEPCMDPARn to avoid register write delays and + * improve performance. + */ + if (DWC3_DEPCMD_CMD(cmd) != DWC3_DEPCMD_UPDATETRANSFER) { + dwc3_writel(dwc, DWC3_DEPCMDPAR0(epnum), params->param0); + dwc3_writel(dwc, DWC3_DEPCMDPAR1(epnum), params->param1); + dwc3_writel(dwc, DWC3_DEPCMDPAR2(epnum), params->param2); + } + + /* + * Synopsys Databook 2.60a states in section 6.3.2.5.6 of that if we're + * not relying on XferNotReady, we can make use of a special "No + * Response Update Transfer" command where we should clear both CmdAct + * and CmdIOC bits. + * + * With this, we don't need to wait for command completion and can + * straight away issue further commands to the endpoint. + * + * NOTICE: We're making an assumption that control endpoints will never + * make use of Update Transfer command. This is a safe assumption + * because we can never have more than one request at a time with + * Control Endpoints. If anybody changes that assumption, this chunk + * needs to be updated accordingly. + */ + if (DWC3_DEPCMD_CMD(cmd) == DWC3_DEPCMD_UPDATETRANSFER && + !usb_endpoint_xfer_isoc(desc)) + cmd &= ~(DWC3_DEPCMD_CMDIOC | DWC3_DEPCMD_CMDACT); + else + cmd |= DWC3_DEPCMD_CMDACT; + + dwc3_writel(dwc, DWC3_DEPCMD(epnum), cmd); + + if (!(cmd & DWC3_DEPCMD_CMDACT) || + (DWC3_DEPCMD_CMD(cmd) == DWC3_DEPCMD_ENDTRANSFER && + !(cmd & DWC3_DEPCMD_CMDIOC))) { + ret = 0; + goto skip_status; + } + + do { + reg = dwc3_readl(dwc, DWC3_DEPCMD(epnum)); + if (!(reg & DWC3_DEPCMD_CMDACT)) { + cmd_status = DWC3_DEPCMD_STATUS(reg); + + switch (cmd_status) { + case 0: + ret = 0; + break; + case DEPEVT_TRANSFER_NO_RESOURCE: + dev_WARN(dwc->dev, "No resource for %s\n", + dep->name); + ret = -EINVAL; + break; + case DEPEVT_TRANSFER_BUS_EXPIRY: + /* + * SW issues START TRANSFER command to + * isochronous ep with future frame interval. If + * future interval time has already passed when + * core receives the command, it will respond + * with an error status of 'Bus Expiry'. + * + * Instead of always returning -EINVAL, let's + * give a hint to the gadget driver that this is + * the case by returning -EAGAIN. + */ + ret = -EAGAIN; + break; + default: + dev_WARN(dwc->dev, "UNKNOWN cmd status\n"); + } + + break; + } + } while (--timeout); + + if (timeout == 0) { + ret = -ETIMEDOUT; + cmd_status = -ETIMEDOUT; + } + +skip_status: + trace_dwc3_gadget_ep_cmd(dep, cmd, params, cmd_status); + + if (DWC3_DEPCMD_CMD(cmd) == DWC3_DEPCMD_STARTTRANSFER) { + if (ret == 0) + dep->flags |= DWC3_EP_TRANSFER_STARTED; + + if (ret != -ETIMEDOUT) + dwc3_gadget_ep_get_transfer_index(dep); + } + + if (DWC3_DEPCMD_CMD(cmd) == DWC3_DEPCMD_ENDTRANSFER && + !(cmd & DWC3_DEPCMD_CMDIOC)) + mdelay(1); + + if (saved_config) { + reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(0)); + reg |= saved_config; + dwc3_writel(dwc, DWC3_GUSB2PHYCFG(0), reg); + } + + return ret; +} + +static int dwc3_send_clear_stall_ep_cmd(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + struct dwc3_gadget_ep_cmd_params params; + u32 cmd = DWC3_DEPCMD_CLEARSTALL; + + /* + * As of core revision 2.60a the recommended programming model + * is to set the ClearPendIN bit when issuing a Clear Stall EP + * command for IN endpoints. This is to prevent an issue where + * some (non-compliant) hosts may not send ACK TPs for pending + * IN transfers due to a mishandled error condition. Synopsys + * STAR 9000614252. + */ + if (dep->direction && + !DWC3_VER_IS_PRIOR(DWC3, 260A) && + (dwc->gadget->speed >= USB_SPEED_SUPER)) + cmd |= DWC3_DEPCMD_CLEARPENDIN; + + memset(¶ms, 0, sizeof(params)); + + return dwc3_send_gadget_ep_cmd(dep, cmd, ¶ms); +} + +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; + + dep->trb_pool = dma_alloc_coherent(dwc->sysdev, + 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->sysdev, 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_set_xfer_resource(struct dwc3_ep *dep) +{ + struct dwc3_gadget_ep_cmd_params params; + int ret; + + if (dep->flags & DWC3_EP_RESOURCE_ALLOCATED) + return 0; + + memset(¶ms, 0x00, sizeof(params)); + + params.param0 = DWC3_DEPXFERCFG_NUM_XFER_RES(1); + + ret = dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETTRANSFRESOURCE, + ¶ms); + if (ret) + return ret; + + dep->flags |= DWC3_EP_RESOURCE_ALLOCATED; + return 0; +} + +/** + * dwc3_gadget_start_config - reset endpoint resources + * @dwc: pointer to the DWC3 context + * @resource_index: DEPSTARTCFG.XferRscIdx value (must be 0 or 2) + * + * Set resource_index=0 to reset all endpoints' resources allocation. Do this as + * part of the power-on/soft-reset initialization. + * + * Set resource_index=2 to reset only non-control endpoints' resources. Do this + * on receiving the SET_CONFIGURATION request or hibernation resume. + */ +int dwc3_gadget_start_config(struct dwc3 *dwc, unsigned int resource_index) +{ + struct dwc3_gadget_ep_cmd_params params; + struct dwc3_ep *dep; + u32 cmd; + int i; + int ret; + + if (resource_index != 0 && resource_index != 2) + return -EINVAL; + + memset(¶ms, 0x00, sizeof(params)); + cmd = DWC3_DEPCMD_DEPSTARTCFG; + cmd |= DWC3_DEPCMD_PARAM(resource_index); + + ret = dwc3_send_gadget_ep_cmd(dwc->eps[0], cmd, ¶ms); + if (ret) + return ret; + + /* Reset resource allocation flags */ + for (i = resource_index; i < dwc->num_eps; i++) { + dep = dwc->eps[i]; + if (!dep) + continue; + + dep->flags &= ~DWC3_EP_RESOURCE_ALLOCATED; + } + + return 0; +} + +static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action) +{ + const struct usb_ss_ep_comp_descriptor *comp_desc; + const struct usb_endpoint_descriptor *desc; + struct dwc3_gadget_ep_cmd_params params; + struct dwc3 *dwc = dep->dwc; + + comp_desc = dep->endpoint.comp_desc; + desc = dep->endpoint.desc; + + 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; + + params.param0 |= DWC3_DEPCFG_BURST_SIZE(burst - 1); + } + + params.param0 |= action; + if (action == DWC3_DEPCFG_ACTION_RESTORE) + params.param2 |= dep->saved_state; + + if (usb_endpoint_xfer_control(desc)) + params.param1 = DWC3_DEPCFG_XFER_COMPLETE_EN; + + if (dep->number <= 1 || usb_endpoint_xfer_isoc(desc)) + params.param1 |= 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_XFER_COMPLETE_EN + | DWC3_DEPCFG_STREAM_EVENT_EN; + dep->stream_capable = true; + } + + if (!usb_endpoint_xfer_control(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) { + u8 bInterval_m1; + + /* + * Valid range for DEPCFG.bInterval_m1 is from 0 to 13. + * + * NOTE: The programming guide incorrectly stated bInterval_m1 + * must be set to 0 when operating in fullspeed. Internally the + * controller does not have this limitation. See DWC_usb3x + * programming guide section 3.2.2.1. + */ + bInterval_m1 = min_t(u8, desc->bInterval - 1, 13); + + if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_INT && + dwc->gadget->speed == USB_SPEED_FULL) + dep->interval = desc->bInterval; + else + dep->interval = 1 << (desc->bInterval - 1); + + params.param1 |= DWC3_DEPCFG_BINTERVAL_M1(bInterval_m1); + } + + return dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETEPCONFIG, ¶ms); +} + +/** + * dwc3_gadget_calc_tx_fifo_size - calculates the txfifo size value + * @dwc: pointer to the DWC3 context + * @mult: multiplier to be used when calculating the fifo_size + * + * Calculates the size value based on the equation below: + * + * DWC3 revision 280A and prior: + * fifo_size = mult * (max_packet / mdwidth) + 1; + * + * DWC3 revision 290A and onwards: + * fifo_size = mult * ((max_packet + mdwidth)/mdwidth + 1) + 1 + * + * The max packet size is set to 1024, as the txfifo requirements mainly apply + * to super speed USB use cases. However, it is safe to overestimate the fifo + * allocations for other scenarios, i.e. high speed USB. + */ +static int dwc3_gadget_calc_tx_fifo_size(struct dwc3 *dwc, int mult) +{ + int max_packet = 1024; + int fifo_size; + int mdwidth; + + mdwidth = dwc3_mdwidth(dwc); + + /* MDWIDTH is represented in bits, we need it in bytes */ + mdwidth >>= 3; + + if (DWC3_VER_IS_PRIOR(DWC3, 290A)) + fifo_size = mult * (max_packet / mdwidth) + 1; + else + fifo_size = mult * ((max_packet + mdwidth) / mdwidth) + 1; + return fifo_size; +} + +/** + * dwc3_gadget_calc_ram_depth - calculates the ram depth for txfifo + * @dwc: pointer to the DWC3 context + */ +static int dwc3_gadget_calc_ram_depth(struct dwc3 *dwc) +{ + int ram_depth; + int fifo_0_start; + bool is_single_port_ram; + + /* Check supporting RAM type by HW */ + is_single_port_ram = DWC3_SPRAM_TYPE(dwc->hwparams.hwparams1); + + /* + * If a single port RAM is utilized, then allocate TxFIFOs from + * RAM0. otherwise, allocate them from RAM1. + */ + ram_depth = is_single_port_ram ? DWC3_RAM0_DEPTH(dwc->hwparams.hwparams6) : + DWC3_RAM1_DEPTH(dwc->hwparams.hwparams7); + + /* + * In a single port RAM configuration, the available RAM is shared + * between the RX and TX FIFOs. This means that the txfifo can begin + * at a non-zero address. + */ + if (is_single_port_ram) { + u32 reg; + + /* Check if TXFIFOs start at non-zero addr */ + reg = dwc3_readl(dwc, DWC3_GTXFIFOSIZ(0)); + fifo_0_start = DWC3_GTXFIFOSIZ_TXFSTADDR(reg); + + ram_depth -= (fifo_0_start >> 16); + } + + return ram_depth; +} + +/** + * dwc3_gadget_clear_tx_fifos - Clears txfifo allocation + * @dwc: pointer to the DWC3 context + * + * Iterates through all the endpoint registers and clears the previous txfifo + * allocations. + */ +void dwc3_gadget_clear_tx_fifos(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + int fifo_depth; + int size; + int num; + + if (!dwc->do_fifo_resize) + return; + + /* Read ep0IN related TXFIFO size */ + dep = dwc->eps[1]; + size = dwc3_readl(dwc, DWC3_GTXFIFOSIZ(0)); + if (DWC3_IP_IS(DWC3)) + fifo_depth = DWC3_GTXFIFOSIZ_TXFDEP(size); + else + fifo_depth = DWC31_GTXFIFOSIZ_TXFDEP(size); + + dwc->last_fifo_depth = fifo_depth; + /* Clear existing TXFIFO for all IN eps except ep0 */ + for (num = 3; num < min_t(int, dwc->num_eps, DWC3_ENDPOINTS_NUM); num += 2) { + dep = dwc->eps[num]; + if (!dep) + continue; + + /* Don't change TXFRAMNUM on usb31 version */ + size = DWC3_IP_IS(DWC3) ? 0 : + dwc3_readl(dwc, DWC3_GTXFIFOSIZ(num >> 1)) & + DWC31_GTXFIFOSIZ_TXFRAMNUM; + + dwc3_writel(dwc, DWC3_GTXFIFOSIZ(num >> 1), size); + dep->flags &= ~DWC3_EP_TXFIFO_RESIZED; + } + dwc->num_ep_resized = 0; +} + +/* + * 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 general, FIFO depths are represented with the following equation: + * + * fifo_size = mult * ((max_packet + mdwidth)/mdwidth + 1) + 1 + * + * In conjunction with dwc3_gadget_check_config(), this resizing logic will + * ensure that all endpoints will have enough internal memory for one max + * packet per endpoint. + */ +static int dwc3_gadget_resize_tx_fifos(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + int fifo_0_start; + int ram_depth; + int fifo_size; + int min_depth; + int num_in_ep; + int remaining; + int num_fifos = 1; + int fifo; + int tmp; + + if (!dwc->do_fifo_resize) + return 0; + + /* resize IN endpoints except ep0 */ + if (!usb_endpoint_dir_in(dep->endpoint.desc) || dep->number <= 1) + return 0; + + /* bail if already resized */ + if (dep->flags & DWC3_EP_TXFIFO_RESIZED) + return 0; + + ram_depth = dwc3_gadget_calc_ram_depth(dwc); + + switch (dwc->gadget->speed) { + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER: + if (usb_endpoint_xfer_bulk(dep->endpoint.desc) || + usb_endpoint_xfer_isoc(dep->endpoint.desc)) + num_fifos = min_t(unsigned int, + dep->endpoint.maxburst, + dwc->tx_fifo_resize_max_num); + break; + case USB_SPEED_HIGH: + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + num_fifos = min_t(unsigned int, + usb_endpoint_maxp_mult(dep->endpoint.desc) + 1, + dwc->tx_fifo_resize_max_num); + break; + } + fallthrough; + case USB_SPEED_FULL: + if (usb_endpoint_xfer_bulk(dep->endpoint.desc)) + num_fifos = 2; + break; + default: + break; + } + + /* FIFO size for a single buffer */ + fifo = dwc3_gadget_calc_tx_fifo_size(dwc, 1); + + /* Calculate the number of remaining EPs w/o any FIFO */ + num_in_ep = dwc->max_cfg_eps; + num_in_ep -= dwc->num_ep_resized; + + /* Reserve at least one FIFO for the number of IN EPs */ + min_depth = num_in_ep * (fifo + 1); + remaining = ram_depth - min_depth - dwc->last_fifo_depth; + remaining = max_t(int, 0, remaining); + /* + * We've already reserved 1 FIFO per EP, so check what we can fit in + * addition to it. If there is not enough remaining space, allocate + * all the remaining space to the EP. + */ + fifo_size = (num_fifos - 1) * fifo; + if (remaining < fifo_size) + fifo_size = remaining; + + fifo_size += fifo; + /* Last increment according to the TX FIFO size equation */ + fifo_size++; + + /* Check if TXFIFOs start at non-zero addr */ + tmp = dwc3_readl(dwc, DWC3_GTXFIFOSIZ(0)); + fifo_0_start = DWC3_GTXFIFOSIZ_TXFSTADDR(tmp); + + fifo_size |= (fifo_0_start + (dwc->last_fifo_depth << 16)); + if (DWC3_IP_IS(DWC3)) + dwc->last_fifo_depth += DWC3_GTXFIFOSIZ_TXFDEP(fifo_size); + else + dwc->last_fifo_depth += DWC31_GTXFIFOSIZ_TXFDEP(fifo_size); + + /* Check fifo size allocation doesn't exceed available RAM size. */ + if (dwc->last_fifo_depth >= ram_depth) { + dev_err(dwc->dev, "Fifosize(%d) > RAM size(%d) %s depth:%d\n", + dwc->last_fifo_depth, ram_depth, + dep->endpoint.name, fifo_size); + if (DWC3_IP_IS(DWC3)) + fifo_size = DWC3_GTXFIFOSIZ_TXFDEP(fifo_size); + else + fifo_size = DWC31_GTXFIFOSIZ_TXFDEP(fifo_size); + + dwc->last_fifo_depth -= fifo_size; + return -ENOMEM; + } + + dwc3_writel(dwc, DWC3_GTXFIFOSIZ(dep->number >> 1), fifo_size); + dep->flags |= DWC3_EP_TXFIFO_RESIZED; + dwc->num_ep_resized++; + + return 0; +} + +/** + * __dwc3_gadget_ep_enable - initializes a hw endpoint + * @dep: endpoint to be initialized + * @action: one of INIT, MODIFY or RESTORE + * + * Caller should take care of locking. Execute all necessary commands to + * initialize a HW endpoint so it can be used by a gadget driver. + */ +static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action) +{ + const struct usb_endpoint_descriptor *desc = dep->endpoint.desc; + struct dwc3 *dwc = dep->dwc; + + u32 reg; + int ret; + + if (!(dep->flags & DWC3_EP_ENABLED)) { + ret = dwc3_gadget_resize_tx_fifos(dep); + if (ret) + return ret; + } + + ret = dwc3_gadget_set_ep_config(dep, action); + if (ret) + return ret; + + ret = dwc3_gadget_set_xfer_resource(dep); + if (ret) + return ret; + + if (!(dep->flags & DWC3_EP_ENABLED)) { + struct dwc3_trb *trb_st_hw; + struct dwc3_trb *trb_link; + + dep->type = usb_endpoint_type(desc); + dep->flags |= DWC3_EP_ENABLED; + + reg = dwc3_readl(dwc, DWC3_DALEPENA); + reg |= DWC3_DALEPENA_EP(dep->number); + dwc3_writel(dwc, DWC3_DALEPENA, reg); + + dep->trb_dequeue = 0; + dep->trb_enqueue = 0; + + if (usb_endpoint_xfer_control(desc)) + goto out; + + /* Initialize the TRB ring */ + memset(dep->trb_pool, 0, + sizeof(struct dwc3_trb) * DWC3_TRB_NUM); + + /* Link TRB. The HWO bit is never reset */ + trb_st_hw = &dep->trb_pool[0]; + + trb_link = &dep->trb_pool[DWC3_TRB_NUM - 1]; + 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; + } + + /* + * Issue StartTransfer here with no-op TRB so we can always rely on No + * Response Update Transfer command. + */ + if (usb_endpoint_xfer_bulk(desc) || + usb_endpoint_xfer_int(desc)) { + struct dwc3_gadget_ep_cmd_params params; + struct dwc3_trb *trb; + dma_addr_t trb_dma; + u32 cmd; + + memset(¶ms, 0, sizeof(params)); + trb = &dep->trb_pool[0]; + trb_dma = dwc3_trb_dma_offset(dep, trb); + + params.param0 = upper_32_bits(trb_dma); + params.param1 = lower_32_bits(trb_dma); + + cmd = DWC3_DEPCMD_STARTTRANSFER; + + ret = dwc3_send_gadget_ep_cmd(dep, cmd, ¶ms); + if (ret < 0) + return ret; + + if (dep->stream_capable) { + /* + * For streams, at start, there maybe a race where the + * host primes the endpoint before the function driver + * queues a request to initiate a stream. In that case, + * the controller will not see the prime to generate the + * ERDY and start stream. To workaround this, issue a + * no-op TRB as normal, but end it immediately. As a + * result, when the function driver queues the request, + * the next START_TRANSFER command will cause the + * controller to generate an ERDY to initiate the + * stream. + */ + dwc3_stop_active_transfer(dep, true, true); + + /* + * All stream eps will reinitiate stream on NoStream + * rejection. + * + * However, if the controller is capable of + * TXF_FLUSH_BYPASS, then IN direction endpoints will + * automatically restart the stream without the driver + * initiation. + */ + if (!dep->direction || + !(dwc->hwparams.hwparams9 & + DWC3_GHWPARAMS9_DEV_TXF_FLUSH_BYPASS)) + dep->flags |= DWC3_EP_FORCE_RESTART_STREAM; + } + } + +out: + trace_dwc3_gadget_ep_enable(dep); + + return 0; +} + +void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep, int status) +{ + struct dwc3_request *req; + + dwc3_stop_active_transfer(dep, true, false); + + /* If endxfer is delayed, avoid unmapping requests */ + if (dep->flags & DWC3_EP_DELAY_STOP) + return; + + /* - giveback all requests to gadget driver */ + while (!list_empty(&dep->started_list)) { + req = next_request(&dep->started_list); + + dwc3_gadget_giveback(dep, req, status); + } + + while (!list_empty(&dep->pending_list)) { + req = next_request(&dep->pending_list); + + dwc3_gadget_giveback(dep, req, status); + } + + while (!list_empty(&dep->cancelled_list)) { + req = next_request(&dep->cancelled_list); + + dwc3_gadget_giveback(dep, req, status); + } +} + +/** + * __dwc3_gadget_ep_disable - disables a hw endpoint + * @dep: the endpoint to disable + * + * This function undoes what __dwc3_gadget_ep_enable did and also removes + * requests which are currently being processed by 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; + u32 mask; + + trace_dwc3_gadget_ep_disable(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, DWC3_DALEPENA); + reg &= ~DWC3_DALEPENA_EP(dep->number); + dwc3_writel(dwc, DWC3_DALEPENA, reg); + + dwc3_remove_requests(dwc, dep, -ESHUTDOWN); + + dep->stream_capable = false; + dep->type = 0; + mask = DWC3_EP_TXFIFO_RESIZED | DWC3_EP_RESOURCE_ALLOCATED; + /* + * dwc3_remove_requests() can exit early if DWC3 EP delayed stop is + * set. Do not clear DEP flags, so that the end transfer command will + * be reattempted during the next SETUP stage. + */ + if (dep->flags & DWC3_EP_DELAY_STOP) + mask |= (DWC3_EP_DELAY_STOP | DWC3_EP_TRANSFER_STARTED); + dep->flags &= mask; + + /* Clear out the ep descriptors for non-ep0 */ + if (dep->number > 1) { + dep->endpoint.comp_desc = NULL; + dep->endpoint.desc = NULL; + } + + 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 (dev_WARN_ONCE(dwc->dev, dep->flags & DWC3_EP_ENABLED, + "%s is already enabled\n", + dep->name)) + return 0; + + spin_lock_irqsave(&dwc->lock, flags); + ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); + 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 (dev_WARN_ONCE(dwc->dev, !(dep->flags & DWC3_EP_ENABLED), + "%s is already disabled\n", + dep->name)) + return 0; + + 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); + + req = kzalloc_obj(*req, gfp_flags); + if (!req) + return NULL; + + req->direction = dep->direction; + req->epnum = dep->number; + req->dep = dep; + req->status = DWC3_REQUEST_STATUS_UNKNOWN; + + trace_dwc3_alloc_request(req); + + 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); + + trace_dwc3_free_request(req); + kfree(req); +} + +/** + * dwc3_ep_prev_trb - returns the previous TRB in the ring + * @dep: The endpoint with the TRB ring + * @index: The index of the current TRB in the ring + * + * Returns the TRB prior to the one pointed to by the index. If the + * index is 0, we will wrap backwards, skip the link TRB, and return + * the one just before that. + */ +static struct dwc3_trb *dwc3_ep_prev_trb(struct dwc3_ep *dep, u8 index) +{ + u8 tmp = index; + + if (!tmp) + tmp = DWC3_TRB_NUM - 1; + + return &dep->trb_pool[tmp - 1]; +} + +static u32 dwc3_calc_trbs_left(struct dwc3_ep *dep) +{ + u8 trbs_left; + + /* + * If the enqueue & dequeue are equal then the TRB ring is either full + * or empty. It's considered full when there are DWC3_TRB_NUM-1 of TRBs + * pending to be processed by the driver. + */ + if (dep->trb_enqueue == dep->trb_dequeue) { + struct dwc3_request *req; + + /* + * If there is any request remained in the started_list with + * active TRBs at this point, then there is no TRB available. + */ + req = next_request(&dep->started_list); + if (req && req->num_trbs) + return 0; + + return DWC3_TRB_NUM - 1; + } + + trbs_left = dep->trb_dequeue - dep->trb_enqueue; + trbs_left &= (DWC3_TRB_NUM - 1); + + if (dep->trb_dequeue < dep->trb_enqueue) + trbs_left--; + + return trbs_left; +} + +/** + * dwc3_prepare_one_trb - setup one TRB from one request + * @dep: endpoint for which this request is prepared + * @req: dwc3_request pointer + * @trb_length: buffer size of the TRB + * @chain: should this TRB be chained to the next? + * @node: only for isochronous endpoints. First TRB needs different type. + * @use_bounce_buffer: set to use bounce buffer + * @must_interrupt: set to interrupt on TRB completion + */ +static void dwc3_prepare_one_trb(struct dwc3_ep *dep, + struct dwc3_request *req, unsigned int trb_length, + unsigned int chain, unsigned int node, bool use_bounce_buffer, + bool must_interrupt) +{ + struct dwc3_trb *trb; + dma_addr_t dma; + unsigned int stream_id = req->request.stream_id; + unsigned int short_not_ok = req->request.short_not_ok; + unsigned int no_interrupt = req->request.no_interrupt; + unsigned int is_last = req->request.is_last; + struct dwc3 *dwc = dep->dwc; + struct usb_gadget *gadget = dwc->gadget; + enum usb_device_speed speed = gadget->speed; + + if (use_bounce_buffer) + dma = dep->dwc->bounce_addr; + else if (req->request.num_sgs > 0) + dma = sg_dma_address(req->start_sg); + else + dma = req->request.dma; + + trb = &dep->trb_pool[dep->trb_enqueue]; + + if (!req->trb) { + dwc3_gadget_move_started_request(req); + req->trb = trb; + req->trb_dma = dwc3_trb_dma_offset(dep, trb); + } + + req->num_trbs++; + + trb->size = DWC3_TRB_SIZE_LENGTH(trb_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; + + /* + * USB Specification 2.0 Section 5.9.2 states that: "If + * there is only a single transaction in the microframe, + * only a DATA0 data packet PID is used. If there are + * two transactions per microframe, DATA1 is used for + * the first transaction data packet and DATA0 is used + * for the second transaction data packet. If there are + * three transactions per microframe, DATA2 is used for + * the first transaction data packet, DATA1 is used for + * the second, and DATA0 is used for the third." + * + * IOW, we should satisfy the following cases: + * + * 1) length <= maxpacket + * - DATA0 + * + * 2) maxpacket < length <= (2 * maxpacket) + * - DATA1, DATA0 + * + * 3) (2 * maxpacket) < length <= (3 * maxpacket) + * - DATA2, DATA1, DATA0 + */ + if (speed == USB_SPEED_HIGH) { + struct usb_ep *ep = &dep->endpoint; + unsigned int mult = 2; + unsigned int maxp = usb_endpoint_maxp(ep->desc); + + if (req->request.length <= (2 * maxp)) + mult--; + + if (req->request.length <= maxp) + mult--; + + trb->size |= DWC3_TRB_SIZE_PCM1(mult); + } + } else { + trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS; + } + + if (!no_interrupt && !chain) + trb->ctrl |= DWC3_TRB_CTRL_ISP_IMI; + 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 :) + */ + dev_WARN(dwc->dev, "Unknown endpoint type %d\n", + usb_endpoint_type(dep->endpoint.desc)); + } + + /* + * Enable Continue on Short Packet + * when endpoint is not a stream capable + */ + if (usb_endpoint_dir_out(dep->endpoint.desc)) { + if (!dep->stream_capable) + trb->ctrl |= DWC3_TRB_CTRL_CSP; + + if (short_not_ok) + trb->ctrl |= DWC3_TRB_CTRL_ISP_IMI; + } + + /* All TRBs setup for MST must set CSP=1 when LST=0 */ + if (dep->stream_capable && DWC3_MST_CAPABLE(&dwc->hwparams)) + trb->ctrl |= DWC3_TRB_CTRL_CSP; + + if ((!no_interrupt && !chain) || must_interrupt) + trb->ctrl |= DWC3_TRB_CTRL_IOC; + + if (chain) + trb->ctrl |= DWC3_TRB_CTRL_CHN; + else if (dep->stream_capable && is_last && + !DWC3_MST_CAPABLE(&dwc->hwparams)) + trb->ctrl |= DWC3_TRB_CTRL_LST; + + if (usb_endpoint_xfer_bulk(dep->endpoint.desc) && dep->stream_capable) + trb->ctrl |= DWC3_TRB_CTRL_SID_SOFN(stream_id); + + /* + * As per data book 4.2.3.2TRB Control Bit Rules section + * + * The controller autonomously checks the HWO field of a TRB to determine if the + * entire TRB is valid. Therefore, software must ensure that the rest of the TRB + * is valid before setting the HWO field to '1'. In most systems, this means that + * software must update the fourth DWORD of a TRB last. + * + * However there is a possibility of CPU re-ordering here which can cause + * controller to observe the HWO bit set prematurely. + * Add a write memory barrier to prevent CPU re-ordering. + */ + wmb(); + trb->ctrl |= DWC3_TRB_CTRL_HWO; + + dwc3_ep_inc_enq(dep); + + trace_dwc3_prepare_trb(dep, trb); +} + +static bool dwc3_needs_extra_trb(struct dwc3_ep *dep, struct dwc3_request *req) +{ + unsigned int maxp = usb_endpoint_maxp(dep->endpoint.desc); + unsigned int rem = req->request.length % maxp; + + if ((req->request.length && req->request.zero && !rem && + !usb_endpoint_xfer_isoc(dep->endpoint.desc)) || + (!req->direction && rem)) + return true; + + return false; +} + +/** + * dwc3_prepare_last_sg - prepare TRBs for the last SG entry + * @dep: The endpoint that the request belongs to + * @req: The request to prepare + * @entry_length: The last SG entry size + * @node: Indicates whether this is not the first entry (for isoc only) + * + * Return the number of TRBs prepared. + */ +static int dwc3_prepare_last_sg(struct dwc3_ep *dep, + struct dwc3_request *req, unsigned int entry_length, + unsigned int node) +{ + unsigned int maxp = usb_endpoint_maxp(dep->endpoint.desc); + unsigned int rem = req->request.length % maxp; + unsigned int num_trbs = 1; + bool needs_extra_trb; + + if (dwc3_needs_extra_trb(dep, req)) + num_trbs++; + + if (dwc3_calc_trbs_left(dep) < num_trbs) + return 0; + + needs_extra_trb = num_trbs > 1; + + /* Prepare a normal TRB */ + if (req->direction || req->request.length) + dwc3_prepare_one_trb(dep, req, entry_length, + needs_extra_trb, node, false, false); + + /* Prepare extra TRBs for ZLP and MPS OUT transfer alignment */ + if ((!req->direction && !req->request.length) || needs_extra_trb) + dwc3_prepare_one_trb(dep, req, + req->direction ? 0 : maxp - rem, + false, 1, true, false); + + return num_trbs; +} + +static int dwc3_prepare_trbs_sg(struct dwc3_ep *dep, + struct dwc3_request *req) +{ + struct scatterlist *sg = req->start_sg; + struct scatterlist *s; + int i; + unsigned int length = req->request.length; + unsigned int remaining = req->num_pending_sgs; + unsigned int num_queued_sgs = req->request.num_mapped_sgs - remaining; + unsigned int num_trbs = req->num_trbs; + bool needs_extra_trb = dwc3_needs_extra_trb(dep, req); + + /* + * If we resume preparing the request, then get the remaining length of + * the request and resume where we left off. + */ + for_each_sg(req->request.sg, s, num_queued_sgs, i) + length -= sg_dma_len(s); + + for_each_sg(sg, s, remaining, i) { + unsigned int num_trbs_left = dwc3_calc_trbs_left(dep); + unsigned int trb_length; + bool must_interrupt = false; + bool last_sg = false; + + trb_length = min_t(unsigned int, length, sg_dma_len(s)); + + length -= trb_length; + + /* + * IOMMU driver is coalescing the list of sgs which shares a + * page boundary into one and giving it to USB driver. With + * this the number of sgs mapped is not equal to the number of + * sgs passed. So mark the chain bit to false if it isthe last + * mapped sg. + */ + if ((i == remaining - 1) || !length) + last_sg = true; + + if (!num_trbs_left) + break; + + if (last_sg) { + if (!dwc3_prepare_last_sg(dep, req, trb_length, i)) + break; + } else { + /* + * Look ahead to check if we have enough TRBs for the + * next SG entry. If not, set interrupt on this TRB to + * resume preparing the next SG entry when more TRBs are + * free. + */ + if (num_trbs_left == 1 || (needs_extra_trb && + num_trbs_left <= 2 && + sg_dma_len(sg_next(s)) >= length)) { + struct dwc3_request *r; + + /* Check if previous requests already set IOC */ + list_for_each_entry(r, &dep->started_list, list) { + if (r != req && !r->request.no_interrupt) + break; + + if (r == req) + must_interrupt = true; + } + } + + dwc3_prepare_one_trb(dep, req, trb_length, 1, i, false, + must_interrupt); + } + + /* + * There can be a situation where all sgs in sglist are not + * queued because of insufficient trb number. To handle this + * case, update start_sg to next sg to be queued, so that + * we have free trbs we can continue queuing from where we + * previously stopped + */ + if (!last_sg) + req->start_sg = sg_next(s); + + req->num_pending_sgs--; + + /* + * The number of pending SG entries may not correspond to the + * number of mapped SG entries. If all the data are queued, then + * don't include unused SG entries. + */ + if (length == 0) { + req->num_pending_sgs = 0; + break; + } + + if (must_interrupt) + break; + } + + return req->num_trbs - num_trbs; +} + +static int dwc3_prepare_trbs_linear(struct dwc3_ep *dep, + struct dwc3_request *req) +{ + return dwc3_prepare_last_sg(dep, req, req->request.length, 0); +} + +/* + * dwc3_prepare_trbs - setup TRBs from requests + * @dep: endpoint for which requests are being prepared + * + * 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. + * + * Returns the number of TRBs prepared or negative errno. + */ +static int dwc3_prepare_trbs(struct dwc3_ep *dep) +{ + struct dwc3_request *req, *n; + int ret = 0; + + BUILD_BUG_ON_NOT_POWER_OF_2(DWC3_TRB_NUM); + + /* + * We can get in a situation where there's a request in the started list + * but there weren't enough TRBs to fully kick it in the first time + * around, so it has been waiting for more TRBs to be freed up. + * + * In that case, we should check if we have a request with pending_sgs + * in the started list and prepare TRBs for that request first, + * otherwise we will prepare TRBs completely out of order and that will + * break things. + */ + list_for_each_entry(req, &dep->started_list, list) { + if (req->num_pending_sgs > 0) { + ret = dwc3_prepare_trbs_sg(dep, req); + if (!ret || req->num_pending_sgs) + return ret; + } + + if (!dwc3_calc_trbs_left(dep)) + return ret; + + /* + * Don't prepare beyond a transfer. In DWC_usb32, its transfer + * burst capability may try to read and use TRBs beyond the + * active transfer instead of stopping. + */ + if (dep->stream_capable && req->request.is_last && + !DWC3_MST_CAPABLE(&dep->dwc->hwparams)) + return ret; + } + + list_for_each_entry_safe(req, n, &dep->pending_list, list) { + struct dwc3 *dwc = dep->dwc; + + ret = usb_gadget_map_request_by_dev(dwc->sysdev, &req->request, + dep->direction); + if (ret) + return ret; + + req->start_sg = req->request.sg; + req->num_pending_sgs = req->request.num_mapped_sgs; + + if (req->num_pending_sgs > 0) { + ret = dwc3_prepare_trbs_sg(dep, req); + if (req->num_pending_sgs) + return ret; + } else { + ret = dwc3_prepare_trbs_linear(dep, req); + } + + if (!ret || !dwc3_calc_trbs_left(dep)) + return ret; + + /* + * Don't prepare beyond a transfer. In DWC_usb32, its transfer + * burst capability may try to read and use TRBs beyond the + * active transfer instead of stopping. + */ + if (dep->stream_capable && req->request.is_last && + !DWC3_MST_CAPABLE(&dwc->hwparams)) + return ret; + } + + return ret; +} + +static void dwc3_gadget_ep_cleanup_cancelled_requests(struct dwc3_ep *dep); + +static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep) +{ + struct dwc3_gadget_ep_cmd_params params; + struct dwc3_request *req; + int starting; + int ret; + u32 cmd; + + /* + * Note that it's normal to have no new TRBs prepared (i.e. ret == 0). + * This happens when we need to stop and restart a transfer such as in + * the case of reinitiating a stream or retrying an isoc transfer. + */ + ret = dwc3_prepare_trbs(dep); + if (ret < 0) + return ret; + + starting = !(dep->flags & DWC3_EP_TRANSFER_STARTED); + + /* + * If there's no new TRB prepared and we don't need to restart a + * transfer, there's no need to update the transfer. + */ + if (!ret && !starting) + return 0; + + req = next_request(&dep->started_list); + if (!req) { + dep->flags |= DWC3_EP_PENDING_REQUEST; + return 0; + } + + memset(¶ms, 0, sizeof(params)); + + if (starting) { + params.param0 = upper_32_bits(req->trb_dma); + params.param1 = lower_32_bits(req->trb_dma); + cmd = DWC3_DEPCMD_STARTTRANSFER; + + if (dep->stream_capable) + cmd |= DWC3_DEPCMD_PARAM(req->request.stream_id); + + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) + cmd |= DWC3_DEPCMD_PARAM(dep->frame_number); + } else { + cmd = DWC3_DEPCMD_UPDATETRANSFER | + DWC3_DEPCMD_PARAM(dep->resource_index); + } + + ret = dwc3_send_gadget_ep_cmd(dep, cmd, ¶ms); + if (ret < 0) { + struct dwc3_request *tmp; + + if (ret == -EAGAIN) + return ret; + + dwc3_stop_active_transfer(dep, true, true); + + list_for_each_entry_safe(req, tmp, &dep->started_list, list) + dwc3_gadget_move_cancelled_request(req, DWC3_REQUEST_STATUS_DEQUEUED); + + /* If ep isn't started, then there's no end transfer pending */ + if (!(dep->flags & DWC3_EP_END_TRANSFER_PENDING)) + dwc3_gadget_ep_cleanup_cancelled_requests(dep); + + return ret; + } + + if (dep->stream_capable && req->request.is_last && + !DWC3_MST_CAPABLE(&dep->dwc->hwparams)) + dep->flags |= DWC3_EP_WAIT_TRANSFER_COMPLETE; + + return 0; +} + +static int __dwc3_gadget_get_frame(struct dwc3 *dwc) +{ + u32 reg; + + reg = dwc3_readl(dwc, DWC3_DSTS); + return DWC3_DSTS_SOFFN(reg); +} + +/** + * __dwc3_stop_active_transfer - stop the current active transfer + * @dep: isoc endpoint + * @force: set forcerm bit in the command + * @interrupt: command complete interrupt after End Transfer command + * + * When setting force, the ForceRM bit will be set. In that case + * the controller won't update the TRB progress on command + * completion. It also won't clear the HWO bit in the TRB. + * The command will also not complete immediately in that case. + */ +static int __dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, bool interrupt) +{ + struct dwc3_gadget_ep_cmd_params params; + u32 cmd; + int ret; + + cmd = DWC3_DEPCMD_ENDTRANSFER; + cmd |= force ? DWC3_DEPCMD_HIPRI_FORCERM : 0; + cmd |= interrupt ? DWC3_DEPCMD_CMDIOC : 0; + cmd |= DWC3_DEPCMD_PARAM(dep->resource_index); + memset(¶ms, 0, sizeof(params)); + ret = dwc3_send_gadget_ep_cmd(dep, cmd, ¶ms); + /* + * If the End Transfer command was timed out while the device is + * not in SETUP phase, it's possible that an incoming Setup packet + * may prevent the command's completion. Let's retry when the + * ep0state returns to EP0_SETUP_PHASE. + */ + if (ret == -ETIMEDOUT && dep->dwc->ep0state != EP0_SETUP_PHASE) { + dep->flags |= DWC3_EP_DELAY_STOP; + return 0; + } + + if (ret) + dev_err_ratelimited(dep->dwc->dev, + "end transfer failed: %d\n", ret); + + dep->resource_index = 0; + + if (!interrupt) + dep->flags &= ~DWC3_EP_TRANSFER_STARTED; + else if (!ret) + dep->flags |= DWC3_EP_END_TRANSFER_PENDING; + + dep->flags &= ~DWC3_EP_DELAY_STOP; + return ret; +} + +/** + * dwc3_gadget_start_isoc_quirk - workaround invalid frame number + * @dep: isoc endpoint + * + * This function tests for the correct combination of BIT[15:14] from the 16-bit + * microframe number reported by the XferNotReady event for the future frame + * number to start the isoc transfer. + * + * In DWC_usb31 version 1.70a-ea06 and prior, for highspeed and fullspeed + * isochronous IN, BIT[15:14] of the 16-bit microframe number reported by the + * XferNotReady event are invalid. The driver uses this number to schedule the + * isochronous transfer and passes it to the START TRANSFER command. Because + * this number is invalid, the command may fail. If BIT[15:14] matches the + * internal 16-bit microframe, the START TRANSFER command will pass and the + * transfer will start at the scheduled time, if it is off by 1, the command + * will still pass, but the transfer will start 2 seconds in the future. For all + * other conditions, the START TRANSFER command will fail with bus-expiry. + * + * In order to workaround this issue, we can test for the correct combination of + * BIT[15:14] by sending START TRANSFER commands with different values of + * BIT[15:14]: 'b00, 'b01, 'b10, and 'b11. Each combination is 2^14 uframe apart + * (or 2 seconds). 4 seconds into the future will result in a bus-expiry status. + * As the result, within the 4 possible combinations for BIT[15:14], there will + * be 2 successful and 2 failure START COMMAND status. One of the 2 successful + * command status will result in a 2-second delay start. The smaller BIT[15:14] + * value is the correct combination. + * + * Since there are only 4 outcomes and the results are ordered, we can simply + * test 2 START TRANSFER commands with BIT[15:14] combinations 'b00 and 'b01 to + * deduce the smaller successful combination. + * + * Let test0 = test status for combination 'b00 and test1 = test status for 'b01 + * of BIT[15:14]. The correct combination is as follow: + * + * if test0 fails and test1 passes, BIT[15:14] is 'b01 + * if test0 fails and test1 fails, BIT[15:14] is 'b10 + * if test0 passes and test1 fails, BIT[15:14] is 'b11 + * if test0 passes and test1 passes, BIT[15:14] is 'b00 + * + * Synopsys STAR 9001202023: Wrong microframe number for isochronous IN + * endpoints. + */ +static int dwc3_gadget_start_isoc_quirk(struct dwc3_ep *dep) +{ + int cmd_status = 0; + bool test0; + bool test1; + + while (dep->combo_num < 2) { + struct dwc3_gadget_ep_cmd_params params; + u32 test_frame_number; + u32 cmd; + + /* + * Check if we can start isoc transfer on the next interval or + * 4 uframes in the future with BIT[15:14] as dep->combo_num + */ + test_frame_number = dep->frame_number & DWC3_FRNUMBER_MASK; + test_frame_number |= dep->combo_num << 14; + test_frame_number += max_t(u32, 4, dep->interval); + + params.param0 = upper_32_bits(dep->dwc->bounce_addr); + params.param1 = lower_32_bits(dep->dwc->bounce_addr); + + cmd = DWC3_DEPCMD_STARTTRANSFER; + cmd |= DWC3_DEPCMD_PARAM(test_frame_number); + cmd_status = dwc3_send_gadget_ep_cmd(dep, cmd, ¶ms); + + /* Redo if some other failure beside bus-expiry is received */ + if (cmd_status && cmd_status != -EAGAIN) { + dep->start_cmd_status = 0; + dep->combo_num = 0; + return 0; + } + + /* Store the first test status */ + if (dep->combo_num == 0) + dep->start_cmd_status = cmd_status; + + dep->combo_num++; + + /* + * End the transfer if the START_TRANSFER command is successful + * to wait for the next XferNotReady to test the command again + */ + if (cmd_status == 0) { + dwc3_stop_active_transfer(dep, true, true); + return 0; + } + } + + /* test0 and test1 are both completed at this point */ + test0 = (dep->start_cmd_status == 0); + test1 = (cmd_status == 0); + + if (!test0 && test1) + dep->combo_num = 1; + else if (!test0 && !test1) + dep->combo_num = 2; + else if (test0 && !test1) + dep->combo_num = 3; + else if (test0 && test1) + dep->combo_num = 0; + + dep->frame_number &= DWC3_FRNUMBER_MASK; + dep->frame_number |= dep->combo_num << 14; + dep->frame_number += max_t(u32, 4, dep->interval); + + /* Reinitialize test variables */ + dep->start_cmd_status = 0; + dep->combo_num = 0; + + return __dwc3_gadget_kick_transfer(dep); +} + +static int __dwc3_gadget_start_isoc(struct dwc3_ep *dep) +{ + const struct usb_endpoint_descriptor *desc = dep->endpoint.desc; + struct dwc3 *dwc = dep->dwc; + int ret; + int i; + + if (list_empty(&dep->pending_list) && + list_empty(&dep->started_list)) { + dep->flags |= DWC3_EP_PENDING_REQUEST; + return -EAGAIN; + } + + if (!dwc->dis_start_transfer_quirk && + (DWC3_VER_IS_PRIOR(DWC31, 170A) || + DWC3_VER_TYPE_IS_WITHIN(DWC31, 170A, EA01, EA06))) { + if (dwc->gadget->speed <= USB_SPEED_HIGH && dep->direction) + return dwc3_gadget_start_isoc_quirk(dep); + } + + if (desc->bInterval <= 14 && + dwc->gadget->speed >= USB_SPEED_HIGH) { + u32 frame = __dwc3_gadget_get_frame(dwc); + bool rollover = frame < + (dep->frame_number & DWC3_FRNUMBER_MASK); + + /* + * frame_number is set from XferNotReady and may be already + * out of date. DSTS only provides the lower 14 bit of the + * current frame number. So add the upper two bits of + * frame_number and handle a possible rollover. + * This will provide the correct frame_number unless more than + * rollover has happened since XferNotReady. + */ + + dep->frame_number = (dep->frame_number & ~DWC3_FRNUMBER_MASK) | + frame; + if (rollover) + dep->frame_number += BIT(14); + } + + for (i = 0; i < DWC3_ISOC_MAX_RETRIES; i++) { + int future_interval = i + 1; + + /* Give the controller at least 500us to schedule transfers */ + if (desc->bInterval < 3) + future_interval += 3 - desc->bInterval; + + dep->frame_number = DWC3_ALIGN_FRAME(dep, future_interval); + + ret = __dwc3_gadget_kick_transfer(dep); + if (ret != -EAGAIN) + break; + } + + /* + * After a number of unsuccessful start attempts due to bus-expiry + * status, issue END_TRANSFER command and retry on the next XferNotReady + * event. + */ + if (ret == -EAGAIN) + ret = __dwc3_stop_active_transfer(dep, false, true); + + return ret; +} + +static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) +{ + struct dwc3 *dwc = dep->dwc; + + if (!dep->endpoint.desc || !dwc->pullups_connected || !dwc->connected) { + dev_dbg(dwc->dev, "%s: can't queue to disabled endpoint\n", + dep->name); + return -ESHUTDOWN; + } + + if (WARN(req->dep != dep, "request %p belongs to '%s'\n", + &req->request, req->dep->name)) + return -EINVAL; + + if (WARN(req->status < DWC3_REQUEST_STATUS_COMPLETED, + "%s: request %p already in flight\n", + dep->name, &req->request)) + return -EINVAL; + + pm_runtime_get(dwc->dev); + + req->request.actual = 0; + req->request.status = -EINPROGRESS; + + trace_dwc3_ep_queue(req); + + list_add_tail(&req->list, &dep->pending_list); + req->status = DWC3_REQUEST_STATUS_QUEUED; + + if (dep->flags & DWC3_EP_WAIT_TRANSFER_COMPLETE) + return 0; + + /* + * Start the transfer only after the END_TRANSFER is completed + * and endpoint STALL is cleared. + */ + if ((dep->flags & DWC3_EP_END_TRANSFER_PENDING) || + (dep->flags & DWC3_EP_WEDGE) || + (dep->flags & DWC3_EP_DELAY_STOP) || + (dep->flags & DWC3_EP_STALL)) { + dep->flags |= DWC3_EP_DELAY_START; + return 0; + } + + /* + * NOTICE: Isochronous endpoints should NEVER be prestarted. We must + * wait for a XferNotReady event so we will know what's the current + * (micro-)frame number. + * + * Without this trick, we are very, very likely gonna get Bus Expiry + * errors which will force us issue EndTransfer command. + */ + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + if (!(dep->flags & DWC3_EP_TRANSFER_STARTED)) { + if ((dep->flags & DWC3_EP_PENDING_REQUEST)) + return __dwc3_gadget_start_isoc(dep); + + return 0; + } + } + + __dwc3_gadget_kick_transfer(dep); + + 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; + + spin_lock_irqsave(&dwc->lock, flags); + ret = __dwc3_gadget_ep_queue(dep, req); + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static void dwc3_gadget_ep_skip_trbs(struct dwc3_ep *dep, struct dwc3_request *req) +{ + int i; + + /* If req->trb is not set, then the request has not started */ + if (!req->trb) + return; + + /* + * If request was already started, this means we had to + * stop the transfer. With that we also need to ignore + * all TRBs used by the request, however TRBs can only + * be modified after completion of END_TRANSFER + * command. So what we do here is that we wait for + * END_TRANSFER completion and only after that, we jump + * over TRBs by clearing HWO and incrementing dequeue + * pointer. + */ + for (i = 0; i < req->num_trbs; i++) { + struct dwc3_trb *trb; + + trb = &dep->trb_pool[dep->trb_dequeue]; + trb->ctrl &= ~DWC3_TRB_CTRL_HWO; + dwc3_ep_inc_deq(dep); + } + + req->num_trbs = 0; +} + +static void dwc3_gadget_ep_cleanup_cancelled_requests(struct dwc3_ep *dep) +{ + struct dwc3_request *req; + struct dwc3 *dwc = dep->dwc; + + while (!list_empty(&dep->cancelled_list)) { + req = next_request(&dep->cancelled_list); + dwc3_gadget_ep_skip_trbs(dep, req); + switch (req->status) { + case DWC3_REQUEST_STATUS_DISCONNECTED: + dwc3_gadget_giveback(dep, req, -ESHUTDOWN); + break; + case DWC3_REQUEST_STATUS_DEQUEUED: + dwc3_gadget_giveback(dep, req, -ECONNRESET); + break; + case DWC3_REQUEST_STATUS_STALLED: + dwc3_gadget_giveback(dep, req, -EPIPE); + break; + default: + dev_err(dwc->dev, "request cancelled with wrong reason:%d\n", req->status); + dwc3_gadget_giveback(dep, req, -ECONNRESET); + break; + } + /* + * The endpoint is disabled, let the dwc3_remove_requests() + * handle the cleanup. + */ + if (!dep->endpoint.desc) + break; + } +} + +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; + + trace_dwc3_ep_dequeue(req); + + spin_lock_irqsave(&dwc->lock, flags); + + list_for_each_entry(r, &dep->cancelled_list, list) { + if (r == req) + goto out; + } + + list_for_each_entry(r, &dep->pending_list, list) { + if (r == req) { + /* + * Explicitly check for EP0/1 as dequeue for those + * EPs need to be handled differently. Control EP + * only deals with one USB req, and giveback will + * occur during dwc3_ep0_stall_and_restart(). EP0 + * requests are never added to started_list. + */ + if (dep->number > 1) + dwc3_gadget_giveback(dep, req, -ECONNRESET); + else + dwc3_ep0_reset_state(dwc); + goto out; + } + } + + list_for_each_entry(r, &dep->started_list, list) { + if (r == req) { + struct dwc3_request *t; + + /* wait until it is processed */ + dwc3_stop_active_transfer(dep, true, true); + + /* + * Remove any started request if the transfer is + * cancelled. + */ + list_for_each_entry_safe(r, t, &dep->started_list, list) + dwc3_gadget_move_cancelled_request(r, + DWC3_REQUEST_STATUS_DEQUEUED); + + dep->flags &= ~DWC3_EP_WAIT_TRANSFER_COMPLETE; + + goto out; + } + } + + dev_err(dwc->dev, "request %p was not queued to %s\n", + request, ep->name); + ret = -EINVAL; +out: + 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; + struct dwc3_request *req; + struct dwc3_request *tmp; + int ret; + + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + dev_err(dwc->dev, "%s is of Isochronous type\n", dep->name); + return -EINVAL; + } + + memset(¶ms, 0x00, sizeof(params)); + + if (value) { + struct dwc3_trb *trb; + + unsigned int transfer_in_flight; + unsigned int started; + + if (dep->number > 1) + trb = dwc3_ep_prev_trb(dep, dep->trb_enqueue); + else + trb = &dwc->ep0_trb[dep->trb_enqueue]; + + transfer_in_flight = trb->ctrl & DWC3_TRB_CTRL_HWO; + started = !list_empty(&dep->started_list); + + if (!protocol && ((dep->direction && transfer_in_flight) || + (!dep->direction && started))) { + return -EAGAIN; + } + + ret = dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETSTALL, + ¶ms); + if (ret) + dev_err(dwc->dev, "failed to set STALL on %s\n", + dep->name); + else + dep->flags |= DWC3_EP_STALL; + } else { + /* + * Don't issue CLEAR_STALL command to control endpoints. The + * controller automatically clears the STALL when it receives + * the SETUP token. + */ + if (dep->number <= 1) { + dep->flags &= ~(DWC3_EP_STALL | DWC3_EP_WEDGE); + return 0; + } + + dwc3_stop_active_transfer(dep, true, true); + + list_for_each_entry_safe(req, tmp, &dep->started_list, list) + dwc3_gadget_move_cancelled_request(req, DWC3_REQUEST_STATUS_STALLED); + + if (dep->flags & DWC3_EP_END_TRANSFER_PENDING || + (dep->flags & DWC3_EP_DELAY_STOP)) { + dep->flags |= DWC3_EP_PENDING_CLEAR_STALL; + if (protocol) + dwc->clear_stall_protocol = dep->number; + + return 0; + } + + dwc3_gadget_ep_cleanup_cancelled_requests(dep); + + ret = dwc3_send_clear_stall_ep_cmd(dep); + if (ret) { + dev_err(dwc->dev, "failed to clear STALL on %s\n", + dep->name); + return ret; + } + + dep->flags &= ~(DWC3_EP_STALL | DWC3_EP_WEDGE); + + if ((dep->flags & DWC3_EP_DELAY_START) && + !usb_endpoint_xfer_isoc(dep->endpoint.desc)) + __dwc3_gadget_kick_transfer(dep); + + dep->flags &= ~DWC3_EP_DELAY_START; + } + + 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); + ret = __dwc3_gadget_ep_set_halt(dep, value, false); + 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; + int ret; + + spin_lock_irqsave(&dwc->lock, flags); + dep->flags |= DWC3_EP_WEDGE; + + if (dep->number == 0 || dep->number == 1) + ret = __dwc3_gadget_ep0_set_halt(ep, 1); + else + ret = __dwc3_gadget_ep_set_halt(dep, 1, false); + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +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 void dwc3_gadget_enable_linksts_evts(struct dwc3 *dwc, bool set) +{ + u32 reg; + + if (DWC3_VER_IS_PRIOR(DWC3, 250A)) + return; + + reg = dwc3_readl(dwc, DWC3_DEVTEN); + if (set) + reg |= DWC3_DEVTEN_ULSTCNGEN; + else + reg &= ~DWC3_DEVTEN_ULSTCNGEN; + + dwc3_writel(dwc, DWC3_DEVTEN, reg); +} + +static int dwc3_gadget_get_frame(struct usb_gadget *g) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + + return __dwc3_gadget_get_frame(dwc); +} + +static int __dwc3_gadget_wakeup(struct dwc3 *dwc) +{ + int ret; + u32 reg; + + u8 link_state; + + /* + * 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, DWC3_DSTS); + + link_state = DWC3_DSTS_USBLNKST(reg); + + switch (link_state) { + case DWC3_LINK_STATE_RESET: + case DWC3_LINK_STATE_RX_DET: /* in HS, means Early Suspend */ + case DWC3_LINK_STATE_U3: /* in HS, means SUSPEND */ + case DWC3_LINK_STATE_U2: /* in HS, means Sleep (L1) */ + case DWC3_LINK_STATE_U1: + case DWC3_LINK_STATE_RESUME: + break; + default: + return -EINVAL; + } + + dwc3_gadget_enable_linksts_evts(dwc, true); + + 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"); + dwc3_gadget_enable_linksts_evts(dwc, false); + return ret; + } + + /* Recent versions do this automatically */ + if (DWC3_VER_IS_PRIOR(DWC3, 194A)) { + /* write zeroes to Link Change Request */ + reg = dwc3_readl(dwc, DWC3_DCTL); + reg &= ~DWC3_DCTL_ULSTCHNGREQ_MASK; + dwc3_writel(dwc, DWC3_DCTL, reg); + } + + /* + * Since link status change events are enabled we will receive + * an U0 event when wakeup is successful. + */ + return 0; +} + +static int dwc3_gadget_wakeup(struct usb_gadget *g) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + int ret; + + if (!dwc->wakeup_configured) { + dev_err(dwc->dev, "remote wakeup not configured\n"); + return -EINVAL; + } + + spin_lock_irqsave(&dwc->lock, flags); + if (!dwc->gadget->wakeup_armed) { + dev_err(dwc->dev, "not armed for remote wakeup\n"); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EINVAL; + } + ret = __dwc3_gadget_wakeup(dwc); + + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static void dwc3_resume_gadget(struct dwc3 *dwc); + +static int dwc3_gadget_func_wakeup(struct usb_gadget *g, int intf_id) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + int ret; + int link_state; + + if (!dwc->wakeup_configured) { + dev_err(dwc->dev, "remote wakeup not configured\n"); + return -EINVAL; + } + + spin_lock_irqsave(&dwc->lock, flags); + /* + * If the link is in U3, signal for remote wakeup and wait for the + * link to transition to U0 before sending device notification. + */ + link_state = dwc3_gadget_get_link_state(dwc); + if (link_state == DWC3_LINK_STATE_U3) { + dwc->wakeup_pending_funcs |= BIT(intf_id); + ret = __dwc3_gadget_wakeup(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + return ret; + } + + ret = dwc3_send_gadget_generic_command(dwc, DWC3_DGCMD_DEV_NOTIFICATION, + DWC3_DGCMDPAR_DN_FUNC_WAKE | + DWC3_DGCMDPAR_INTF_SEL(intf_id)); + if (ret) + dev_err(dwc->dev, "function remote wakeup failed, ret:%d\n", ret); + + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static int dwc3_gadget_set_remote_wakeup(struct usb_gadget *g, int set) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + dwc->wakeup_configured = !!set; + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + +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); + g->is_selfpowered = !!is_selfpowered; + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + +static void dwc3_stop_active_transfers(struct dwc3 *dwc) +{ + u32 epnum; + + for (epnum = 2; epnum < dwc->num_eps; epnum++) { + struct dwc3_ep *dep; + + dep = dwc->eps[epnum]; + if (!dep) + continue; + + dwc3_remove_requests(dwc, dep, -ESHUTDOWN); + } +} + +static void __dwc3_gadget_set_ssp_rate(struct dwc3 *dwc) +{ + enum usb_ssp_rate ssp_rate = dwc->gadget_ssp_rate; + u32 reg; + + if (ssp_rate == USB_SSP_GEN_UNKNOWN) + ssp_rate = dwc->max_ssp_rate; + + reg = dwc3_readl(dwc, DWC3_DCFG); + reg &= ~DWC3_DCFG_SPEED_MASK; + reg &= ~DWC3_DCFG_NUMLANES(~0); + + if (ssp_rate == USB_SSP_GEN_1x2) + reg |= DWC3_DCFG_SUPERSPEED; + else if (dwc->max_ssp_rate != USB_SSP_GEN_1x2) + reg |= DWC3_DCFG_SUPERSPEED_PLUS; + + if (ssp_rate != USB_SSP_GEN_2x1 && + dwc->max_ssp_rate != USB_SSP_GEN_2x1) + reg |= DWC3_DCFG_NUMLANES(1); + + dwc3_writel(dwc, DWC3_DCFG, reg); +} + +static void __dwc3_gadget_set_speed(struct dwc3 *dwc) +{ + enum usb_device_speed speed; + u32 reg; + + speed = dwc->gadget_max_speed; + if (speed == USB_SPEED_UNKNOWN || speed > dwc->maximum_speed) + speed = dwc->maximum_speed; + + if (speed == USB_SPEED_SUPER_PLUS && + DWC3_IP_IS(DWC32)) { + __dwc3_gadget_set_ssp_rate(dwc); + return; + } + + reg = dwc3_readl(dwc, 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 (DWC3_VER_IS_PRIOR(DWC3, 220A) && + !dwc->dis_metastability_quirk) { + reg |= DWC3_DCFG_SUPERSPEED; + } else { + switch (speed) { + case USB_SPEED_FULL: + reg |= DWC3_DCFG_FULLSPEED; + break; + case USB_SPEED_HIGH: + reg |= DWC3_DCFG_HIGHSPEED; + break; + case USB_SPEED_SUPER: + reg |= DWC3_DCFG_SUPERSPEED; + break; + case USB_SPEED_SUPER_PLUS: + if (DWC3_IP_IS(DWC3)) + reg |= DWC3_DCFG_SUPERSPEED; + else + reg |= DWC3_DCFG_SUPERSPEED_PLUS; + break; + default: + dev_err(dwc->dev, "invalid speed (%d)\n", speed); + + if (DWC3_IP_IS(DWC3)) + reg |= DWC3_DCFG_SUPERSPEED; + else + reg |= DWC3_DCFG_SUPERSPEED_PLUS; + } + } + + if (DWC3_IP_IS(DWC32) && + speed > USB_SPEED_UNKNOWN && + speed < USB_SPEED_SUPER_PLUS) + reg &= ~DWC3_DCFG_NUMLANES(~0); + + dwc3_writel(dwc, DWC3_DCFG, reg); +} + +static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on) +{ + u32 reg; + u32 timeout = 2000; + u32 saved_config = 0; + + if (pm_runtime_suspended(dwc->dev)) + return 0; + + /* + * When operating in USB 2.0 speeds (HS/FS), ensure that + * GUSB2PHYCFG.ENBLSLPM and GUSB2PHYCFG.SUSPHY are cleared before starting + * or stopping the controller. This resolves timeout issues that occur + * during frequent role switches between host and device modes. + * + * Save and clear these settings, then restore them after completing the + * controller start or stop sequence. + * + * This solution was discovered through experimentation as it is not + * mentioned in the dwc3 programming guide. It has been tested on an + * Exynos platforms. + */ + reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(0)); + if (reg & DWC3_GUSB2PHYCFG_SUSPHY) { + saved_config |= DWC3_GUSB2PHYCFG_SUSPHY; + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + } + + if (reg & DWC3_GUSB2PHYCFG_ENBLSLPM) { + saved_config |= DWC3_GUSB2PHYCFG_ENBLSLPM; + reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM; + } + + if (saved_config) + dwc3_writel(dwc, DWC3_GUSB2PHYCFG(0), reg); + + reg = dwc3_readl(dwc, DWC3_DCTL); + if (is_on) { + if (DWC3_VER_IS_WITHIN(DWC3, ANY, 187A)) { + reg &= ~DWC3_DCTL_TRGTULST_MASK; + reg |= DWC3_DCTL_TRGTULST_RX_DET; + } + + if (!DWC3_VER_IS_PRIOR(DWC3, 194A)) + reg &= ~DWC3_DCTL_KEEP_CONNECT; + reg |= DWC3_DCTL_RUN_STOP; + + __dwc3_gadget_set_speed(dwc); + dwc->pullups_connected = true; + } else { + reg &= ~DWC3_DCTL_RUN_STOP; + + dwc->pullups_connected = false; + } + + dwc3_pre_run_stop(dwc, is_on); + dwc3_gadget_dctl_write_safe(dwc, reg); + + do { + usleep_range(1000, 2000); + reg = dwc3_readl(dwc, DWC3_DSTS); + reg &= DWC3_DSTS_DEVCTRLHLT; + } while (--timeout && !(!is_on ^ !reg)); + + if (saved_config) { + reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(0)); + reg |= saved_config; + dwc3_writel(dwc, DWC3_GUSB2PHYCFG(0), reg); + } + + if (!timeout) + return -ETIMEDOUT; + + return 0; +} + +static void dwc3_gadget_disable_irq(struct dwc3 *dwc); +static void __dwc3_gadget_stop(struct dwc3 *dwc); +static int __dwc3_gadget_start(struct dwc3 *dwc); + +static int dwc3_gadget_soft_disconnect(struct dwc3 *dwc) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&dwc->lock, flags); + if (!dwc->pullups_connected) { + spin_unlock_irqrestore(&dwc->lock, flags); + return 0; + } + + dwc->connected = false; + + /* + * Attempt to end pending SETUP status phase, and not wait for the + * function to do so. + */ + if (dwc->delayed_status) + dwc3_ep0_send_delayed_status(dwc); + + /* + * In the Synopsys DesignWare Cores USB3 Databook Rev. 3.30a + * Section 4.1.8 Table 4-7, it states that for a device-initiated + * disconnect, the SW needs to ensure that it sends "a DEPENDXFER + * command for any active transfers" before clearing the RunStop + * bit. + */ + dwc3_stop_active_transfers(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + + /* + * Per databook, when we want to stop the gadget, if a control transfer + * is still in process, complete it and get the core into setup phase. + * In case the host is unresponsive to a SETUP transaction, forcefully + * stall the transfer, and move back to the SETUP phase, so that any + * pending endxfers can be executed. + */ + if (dwc->ep0state != EP0_SETUP_PHASE) { + reinit_completion(&dwc->ep0_in_setup); + + ret = wait_for_completion_timeout(&dwc->ep0_in_setup, + msecs_to_jiffies(DWC3_PULL_UP_TIMEOUT)); + if (ret == 0) { + dev_warn(dwc->dev, "wait for SETUP phase timed out\n"); + spin_lock_irqsave(&dwc->lock, flags); + dwc3_ep0_reset_state(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + } + } + + /* + * Note: if the GEVNTCOUNT indicates events in the event buffer, the + * driver needs to acknowledge them before the controller can halt. + * Simply let the interrupt handler acknowledges and handle the + * remaining event generated by the controller while polling for + * DSTS.DEVCTLHLT. + */ + ret = dwc3_gadget_run_stop(dwc, false); + + /* + * Stop the gadget after controller is halted, so that if needed, the + * events to update EP0 state can still occur while the run/stop + * routine polls for the halted state. DEVTEN is cleared as part of + * gadget stop. + */ + spin_lock_irqsave(&dwc->lock, flags); + __dwc3_gadget_stop(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + + usb_gadget_set_state(dwc->gadget, USB_STATE_NOTATTACHED); + + return ret; +} + +static int dwc3_gadget_soft_connect(struct dwc3 *dwc) +{ + int ret; + + /* + * In the Synopsys DWC_usb31 1.90a programming guide section + * 4.1.9, it specifies that for a reconnect after a + * device-initiated disconnect requires a core soft reset + * (DCTL.CSftRst) before enabling the run/stop bit. + */ + ret = dwc3_core_soft_reset(dwc); + if (ret) + return ret; + + dwc3_event_buffers_setup(dwc); + __dwc3_gadget_start(dwc); + return dwc3_gadget_run_stop(dwc, true); +} + +static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + int ret; + + is_on = !!is_on; + + dwc->softconnect = is_on; + + /* + * Avoid issuing a runtime resume if the device is already in the + * suspended state during gadget disconnect. DWC3 gadget was already + * halted/stopped during runtime suspend. + */ + if (!is_on) { + pm_runtime_barrier(dwc->dev); + if (pm_runtime_suspended(dwc->dev)) + return 0; + } + + /* + * Check the return value for successful resume, or error. For a + * successful resume, the DWC3 runtime PM resume routine will handle + * the run stop sequence, so avoid duplicate operations here. + */ + ret = pm_runtime_get_sync(dwc->dev); + if (!ret || ret < 0) { + pm_runtime_put(dwc->dev); + if (ret < 0) + pm_runtime_set_suspended(dwc->dev); + return ret; + } + + if (dwc->pullups_connected == is_on) { + pm_runtime_put(dwc->dev); + return 0; + } + + synchronize_irq(dwc->irq_gadget); + + if (!is_on) + ret = dwc3_gadget_soft_disconnect(dwc); + else + ret = dwc3_gadget_soft_connect(dwc); + + pm_runtime_put(dwc->dev); + + 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_EVNTOVERFLOWEN | + DWC3_DEVTEN_CMDCMPLTEN | + DWC3_DEVTEN_ERRTICERREN | + DWC3_DEVTEN_WKUPEVTEN | + DWC3_DEVTEN_CONNECTDONEEN | + DWC3_DEVTEN_USBRSTEN | + DWC3_DEVTEN_DISCONNEVTEN); + + if (DWC3_VER_IS_PRIOR(DWC3, 250A)) + reg |= DWC3_DEVTEN_ULSTCNGEN; + + /* On 2.30a and above this bit enables U3/L2-L1 Suspend Events */ + if (!DWC3_VER_IS_PRIOR(DWC3, 230A)) + reg |= DWC3_DEVTEN_U3L2L1SUSPEN; + + dwc3_writel(dwc, DWC3_DEVTEN, reg); +} + +static void dwc3_gadget_disable_irq(struct dwc3 *dwc) +{ + /* mask all interrupts */ + dwc3_writel(dwc, DWC3_DEVTEN, 0x00); +} + +static irqreturn_t dwc3_interrupt(int irq, void *_dwc); +static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc); + +/** + * dwc3_gadget_setup_nump - calculate and initialize NUMP field of %DWC3_DCFG + * @dwc: pointer to our context structure + * + * The following looks like complex but it's actually very simple. In order to + * calculate the number of packets we can burst at once on OUT transfers, we're + * gonna use RxFIFO size. + * + * To calculate RxFIFO size we need two numbers: + * MDWIDTH = size, in bits, of the internal memory bus + * RAM2_DEPTH = depth, in MDWIDTH, of internal RAM2 (where RxFIFO sits) + * + * Given these two numbers, the formula is simple: + * + * RxFIFO Size = (RAM2_DEPTH * MDWIDTH / 8) - 24 - 16; + * + * 24 bytes is for 3x SETUP packets + * 16 bytes is a clock domain crossing tolerance + * + * Given RxFIFO Size, NUMP = RxFIFOSize / 1024; + */ +static void dwc3_gadget_setup_nump(struct dwc3 *dwc) +{ + u32 ram2_depth; + u32 mdwidth; + u32 nump; + u32 reg; + + ram2_depth = DWC3_GHWPARAMS7_RAM2_DEPTH(dwc->hwparams.hwparams7); + mdwidth = dwc3_mdwidth(dwc); + + nump = ((ram2_depth * mdwidth / 8) - 24 - 16) / 1024; + nump = min_t(u32, nump, 16); + + /* update NumP */ + reg = dwc3_readl(dwc, DWC3_DCFG); + reg &= ~DWC3_DCFG_NUMP_MASK; + reg |= nump << DWC3_DCFG_NUMP_SHIFT; + dwc3_writel(dwc, DWC3_DCFG, reg); +} + +static int __dwc3_gadget_start(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + int ret = 0; + u32 reg; + + /* + * Use IMOD if enabled via dwc->imod_interval. Otherwise, if + * the core supports IMOD, disable it. + */ + if (dwc->imod_interval) { + dwc3_writel(dwc, DWC3_DEV_IMOD(0), dwc->imod_interval); + dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), DWC3_GEVNTCOUNT_EHB); + } else if (dwc3_has_imod(dwc)) { + dwc3_writel(dwc, DWC3_DEV_IMOD(0), 0); + } + + /* + * We are telling dwc3 that we want to use DCFG.NUMP as ACK TP's NUMP + * field instead of letting dwc3 itself calculate that automatically. + * + * This way, we maximize the chances that we'll be able to get several + * bursts of data without going through any sort of endpoint throttling. + */ + reg = dwc3_readl(dwc, DWC3_GRXTHRCFG); + if (DWC3_IP_IS(DWC3)) + reg &= ~DWC3_GRXTHRCFG_PKTCNTSEL; + else + reg &= ~DWC31_GRXTHRCFG_PKTCNTSEL; + + dwc3_writel(dwc, DWC3_GRXTHRCFG, reg); + + dwc3_gadget_setup_nump(dwc); + + /* + * Currently the controller handles single stream only. So, Ignore + * Packet Pending bit for stream selection and don't search for another + * stream if the host sends Data Packet with PP=0 (for OUT direction) or + * ACK with NumP=0 and PP=0 (for IN direction). This slightly improves + * the stream performance. + */ + reg = dwc3_readl(dwc, DWC3_DCFG); + reg |= DWC3_DCFG_IGNSTRMPP; + dwc3_writel(dwc, DWC3_DCFG, reg); + + /* Enable MST by default if the device is capable of MST */ + if (DWC3_MST_CAPABLE(&dwc->hwparams)) { + reg = dwc3_readl(dwc, DWC3_DCFG1); + reg &= ~DWC3_DCFG1_DIS_MST_ENH; + dwc3_writel(dwc, DWC3_DCFG1, reg); + } + + /* Start with SuperSpeed Default */ + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + + ret = dwc3_gadget_start_config(dwc, 0); + if (ret) { + dev_err(dwc->dev, "failed to config endpoints\n"); + return ret; + } + + dep = dwc->eps[0]; + dep->flags = 0; + ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); + if (ret) { + dev_err(dwc->dev, "failed to enable %s\n", dep->name); + goto err0; + } + + dep = dwc->eps[1]; + dep->flags = 0; + ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); + if (ret) { + dev_err(dwc->dev, "failed to enable %s\n", dep->name); + goto err1; + } + + /* begin to receive SETUP packets */ + dwc->ep0state = EP0_SETUP_PHASE; + dwc->ep0_bounced = false; + dwc->link_state = DWC3_LINK_STATE_SS_DIS; + dwc->delayed_status = false; + dwc3_ep0_out_start(dwc); + + dwc3_gadget_enable_irq(dwc); + dwc3_enable_susphy(dwc, true); + + return 0; + +err1: + __dwc3_gadget_ep_disable(dwc->eps[0]); + +err0: + return ret; +} + +static int dwc3_gadget_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + int ret; + int irq; + + irq = dwc->irq_gadget; + ret = request_threaded_irq(irq, dwc3_interrupt, dwc3_thread_interrupt, + IRQF_SHARED, "dwc3", dwc->ev_buf); + if (ret) { + dev_err(dwc->dev, "failed to request irq #%d --> %d\n", + irq, ret); + return ret; + } + + spin_lock_irqsave(&dwc->lock, flags); + dwc->gadget_driver = driver; + spin_unlock_irqrestore(&dwc->lock, flags); + + if (dwc->sys_wakeup) + device_wakeup_enable(dwc->sysdev); + + return 0; +} + +static void __dwc3_gadget_stop(struct dwc3 *dwc) +{ + dwc3_gadget_disable_irq(dwc); + __dwc3_gadget_ep_disable(dwc->eps[0]); + __dwc3_gadget_ep_disable(dwc->eps[1]); +} + +static int dwc3_gadget_stop(struct usb_gadget *g) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + + if (dwc->sys_wakeup) + device_wakeup_disable(dwc->sysdev); + + spin_lock_irqsave(&dwc->lock, flags); + dwc->gadget_driver = NULL; + dwc->max_cfg_eps = 0; + spin_unlock_irqrestore(&dwc->lock, flags); + + free_irq(dwc->irq_gadget, dwc->ev_buf); + + return 0; +} + +static void dwc3_gadget_config_params(struct usb_gadget *g, + struct usb_dcd_config_params *params) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + + params->besl_baseline = USB_DEFAULT_BESL_UNSPECIFIED; + params->besl_deep = USB_DEFAULT_BESL_UNSPECIFIED; + + /* Recommended BESL */ + if (!dwc->dis_enblslpm_quirk) { + /* + * If the recommended BESL baseline is 0 or if the BESL deep is + * less than 2, Microsoft's Windows 10 host usb stack will issue + * a usb reset immediately after it receives the extended BOS + * descriptor and the enumeration will fail. To maintain + * compatibility with the Windows' usb stack, let's set the + * recommended BESL baseline to 1 and clamp the BESL deep to be + * within 2 to 15. + */ + params->besl_baseline = 1; + if (dwc->is_utmi_l1_suspend) + params->besl_deep = + clamp_t(u8, dwc->hird_threshold, 2, 15); + } + + /* U1 Device exit Latency */ + if (dwc->dis_u1_entry_quirk) + params->bU1devExitLat = 0; + else + params->bU1devExitLat = DWC3_DEFAULT_U1_DEV_EXIT_LAT; + + /* U2 Device exit Latency */ + if (dwc->dis_u2_entry_quirk) + params->bU2DevExitLat = 0; + else + params->bU2DevExitLat = + cpu_to_le16(DWC3_DEFAULT_U2_DEV_EXIT_LAT); +} + +static void dwc3_gadget_set_speed(struct usb_gadget *g, + enum usb_device_speed speed) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + dwc->gadget_max_speed = speed; + spin_unlock_irqrestore(&dwc->lock, flags); +} + +static void dwc3_gadget_set_ssp_rate(struct usb_gadget *g, + enum usb_ssp_rate rate) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + dwc->gadget_max_speed = USB_SPEED_SUPER_PLUS; + dwc->gadget_ssp_rate = rate; + spin_unlock_irqrestore(&dwc->lock, flags); +} + +static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + + if (dwc->usb2_phy) + return usb_phy_set_power(dwc->usb2_phy, mA); + + spin_lock_irqsave(&dwc->lock, flags); + if (!dwc->usb_psy) { + if (!dwc->psy_nb.notifier_call) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -EOPNOTSUPP; + } + dwc->current_limit = mA; + spin_unlock_irqrestore(&dwc->lock, flags); + dev_dbg(dwc->dev, "Stored VBUS draw: %u mA (power supply not ready)\n", mA); + return 0; + } + + dwc->current_limit = mA; + schedule_work(&dwc->vbus_draw_work); + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + +/** + * dwc3_gadget_check_config - ensure dwc3 can support the USB configuration + * @g: pointer to the USB gadget + * + * Used to record the maximum number of endpoints being used in a USB composite + * device. (across all configurations) This is to be used in the calculation + * of the TXFIFO sizes when resizing internal memory for individual endpoints. + * It will help ensured that the resizing logic reserves enough space for at + * least one max packet. + */ +static int dwc3_gadget_check_config(struct usb_gadget *g) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + struct usb_ep *ep; + int fifo_size = 0; + int ram_depth; + int ep_num = 0; + + if (!dwc->do_fifo_resize) + return 0; + + list_for_each_entry(ep, &g->ep_list, ep_list) { + /* Only interested in the IN endpoints */ + if (ep->claimed && (ep->address & USB_DIR_IN)) + ep_num++; + } + + if (ep_num <= dwc->max_cfg_eps) + return 0; + + /* Update the max number of eps in the composition */ + dwc->max_cfg_eps = ep_num; + + fifo_size = dwc3_gadget_calc_tx_fifo_size(dwc, dwc->max_cfg_eps); + /* Based on the equation, increment by one for every ep */ + fifo_size += dwc->max_cfg_eps; + + /* Check if we can fit a single fifo per endpoint */ + ram_depth = dwc3_gadget_calc_ram_depth(dwc); + if (fifo_size > ram_depth) + return -ENOMEM; + + return 0; +} + +static void dwc3_gadget_async_callbacks(struct usb_gadget *g, bool enable) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + dwc->async_callbacks = enable; + spin_unlock_irqrestore(&dwc->lock, flags); +} + +static const struct usb_gadget_ops dwc3_gadget_ops = { + .get_frame = dwc3_gadget_get_frame, + .wakeup = dwc3_gadget_wakeup, + .func_wakeup = dwc3_gadget_func_wakeup, + .set_remote_wakeup = dwc3_gadget_set_remote_wakeup, + .set_selfpowered = dwc3_gadget_set_selfpowered, + .pullup = dwc3_gadget_pullup, + .udc_start = dwc3_gadget_start, + .udc_stop = dwc3_gadget_stop, + .udc_set_speed = dwc3_gadget_set_speed, + .udc_set_ssp_rate = dwc3_gadget_set_ssp_rate, + .get_config_params = dwc3_gadget_config_params, + .vbus_draw = dwc3_gadget_vbus_draw, + .check_config = dwc3_gadget_check_config, + .udc_async_callbacks = dwc3_gadget_async_callbacks, +}; + +/* -------------------------------------------------------------------------- */ + +static int dwc3_gadget_init_control_endpoint(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + + usb_ep_set_maxpacket_limit(&dep->endpoint, 512); + dep->endpoint.maxburst = 1; + dep->endpoint.ops = &dwc3_gadget_ep0_ops; + if (!dep->direction) + dwc->gadget->ep0 = &dep->endpoint; + + dep->endpoint.caps.type_control = true; + + return 0; +} + +static int dwc3_gadget_init_in_endpoint(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + u32 mdwidth; + int size; + int maxpacket; + + mdwidth = dwc3_mdwidth(dwc); + + /* MDWIDTH is represented in bits, we need it in bytes */ + mdwidth /= 8; + + size = dwc3_readl(dwc, DWC3_GTXFIFOSIZ(dep->number >> 1)); + if (DWC3_IP_IS(DWC3)) + size = DWC3_GTXFIFOSIZ_TXFDEP(size); + else + size = DWC31_GTXFIFOSIZ_TXFDEP(size); + + /* + * maxpacket size is determined as part of the following, after assuming + * a mult value of one maxpacket: + * DWC3 revision 280A and prior: + * fifo_size = mult * (max_packet / mdwidth) + 1; + * maxpacket = mdwidth * (fifo_size - 1); + * + * DWC3 revision 290A and onwards: + * fifo_size = mult * ((max_packet + mdwidth)/mdwidth + 1) + 1 + * maxpacket = mdwidth * ((fifo_size - 1) - 1) - mdwidth; + */ + if (DWC3_VER_IS_PRIOR(DWC3, 290A)) + maxpacket = mdwidth * (size - 1); + else + maxpacket = mdwidth * ((size - 1) - 1) - mdwidth; + + /* Functionally, space for one max packet is sufficient */ + size = min_t(int, maxpacket, 1024); + usb_ep_set_maxpacket_limit(&dep->endpoint, size); + + dep->endpoint.max_streams = 16; + dep->endpoint.ops = &dwc3_gadget_ep_ops; + list_add_tail(&dep->endpoint.ep_list, + &dwc->gadget->ep_list); + dep->endpoint.caps.type_iso = true; + dep->endpoint.caps.type_bulk = true; + dep->endpoint.caps.type_int = true; + + return dwc3_alloc_trb_pool(dep); +} + +static int dwc3_gadget_init_out_endpoint(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + u32 mdwidth; + int size; + + mdwidth = dwc3_mdwidth(dwc); + + /* MDWIDTH is represented in bits, convert to bytes */ + mdwidth /= 8; + + /* All OUT endpoints share a single RxFIFO space */ + size = dwc3_readl(dwc, DWC3_GRXFIFOSIZ(0)); + if (DWC3_IP_IS(DWC3)) + size = DWC3_GRXFIFOSIZ_RXFDEP(size); + else + size = DWC31_GRXFIFOSIZ_RXFDEP(size); + + /* FIFO depth is in MDWDITH bytes */ + size *= mdwidth; + + /* + * To meet performance requirement, a minimum recommended RxFIFO size + * is defined as follow: + * RxFIFO size >= (3 x MaxPacketSize) + + * (3 x 8 bytes setup packets size) + (16 bytes clock crossing margin) + * + * Then calculate the max packet limit as below. + */ + size -= (3 * 8) + 16; + if (size < 0) + size = 0; + else + size /= 3; + + usb_ep_set_maxpacket_limit(&dep->endpoint, size); + dep->endpoint.max_streams = 16; + dep->endpoint.ops = &dwc3_gadget_ep_ops; + list_add_tail(&dep->endpoint.ep_list, + &dwc->gadget->ep_list); + dep->endpoint.caps.type_iso = true; + dep->endpoint.caps.type_bulk = true; + dep->endpoint.caps.type_int = true; + + return dwc3_alloc_trb_pool(dep); +} + +#define nostream_work_to_dep(w) (container_of(to_delayed_work(w), struct dwc3_ep, nostream_work)) +static void dwc3_nostream_work(struct work_struct *work) +{ + struct dwc3_ep *dep = nostream_work_to_dep(work); + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + if (dep->flags & DWC3_EP_STREAM_PRIMED) + goto out; + + if ((dep->flags & DWC3_EP_IGNORE_NEXT_NOSTREAM) || + (!DWC3_MST_CAPABLE(&dwc->hwparams) && + !(dep->flags & DWC3_EP_WAIT_TRANSFER_COMPLETE))) + goto out; + /* + * If the host rejects a stream due to no active stream, by the + * USB and xHCI spec, the endpoint will be put back to idle + * state. When the host is ready (buffer added/updated), it will + * prime the endpoint to inform the usb device controller. This + * triggers the device controller to issue ERDY to restart the + * stream. However, some hosts don't follow this and keep the + * endpoint in the idle state. No prime will come despite host + * streams are updated, and the device controller will not be + * triggered to generate ERDY to move the next stream data. To + * workaround this and maintain compatibility with various + * hosts, force to reinitiate the stream until the host is ready + * instead of waiting for the host to prime the endpoint. + */ + if (DWC3_VER_IS_WITHIN(DWC32, 100A, ANY)) { + unsigned int cmd = DWC3_DGCMD_SET_ENDPOINT_PRIME; + + dwc3_send_gadget_generic_command(dwc, cmd, dep->number); + } else { + dep->flags |= DWC3_EP_DELAY_START; + dwc3_stop_active_transfer(dep, true, true); + spin_unlock_irqrestore(&dwc->lock, flags); + return; + } +out: + dep->flags &= ~DWC3_EP_IGNORE_NEXT_NOSTREAM; + spin_unlock_irqrestore(&dwc->lock, flags); +} + +static int dwc3_gadget_init_endpoint(struct dwc3 *dwc, u8 epnum) +{ + struct dwc3_ep *dep; + bool direction = epnum & 1; + int ret; + u8 num = epnum >> 1; + + dep = kzalloc_obj(*dep); + if (!dep) + return -ENOMEM; + + dep->dwc = dwc; + dep->number = epnum; + dep->direction = direction; + dwc->eps[epnum] = dep; + dep->combo_num = 0; + dep->start_cmd_status = 0; + + snprintf(dep->name, sizeof(dep->name), "ep%u%s", num, + direction ? "in" : "out"); + + dep->endpoint.name = dep->name; + + if (!(dep->number > 1)) { + dep->endpoint.desc = &dwc3_gadget_ep0_desc; + dep->endpoint.comp_desc = NULL; + } + + if (num == 0) + ret = dwc3_gadget_init_control_endpoint(dep); + else if (direction) + ret = dwc3_gadget_init_in_endpoint(dep); + else + ret = dwc3_gadget_init_out_endpoint(dep); + + if (ret) + return ret; + + dep->endpoint.caps.dir_in = direction; + dep->endpoint.caps.dir_out = !direction; + + INIT_LIST_HEAD(&dep->pending_list); + INIT_LIST_HEAD(&dep->started_list); + INIT_LIST_HEAD(&dep->cancelled_list); + INIT_DELAYED_WORK(&dep->nostream_work, dwc3_nostream_work); + + dwc3_debugfs_create_endpoint_dir(dep); + + return 0; +} + +static int dwc3_gadget_get_reserved_endpoints(struct dwc3 *dwc, const char *propname, + u8 *eps, u8 num) +{ + u8 count; + int ret; + + if (!device_property_present(dwc->dev, propname)) + return 0; + + ret = device_property_count_u8(dwc->dev, propname); + if (ret < 0) + return ret; + count = ret; + + ret = device_property_read_u8_array(dwc->dev, propname, eps, min(num, count)); + if (ret) + return ret; + + return count; +} + +static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 total) +{ + const char *propname = "snps,reserved-endpoints"; + u8 epnum; + u8 reserved_eps[DWC3_ENDPOINTS_NUM]; + u8 count; + u8 num; + int ret; + + INIT_LIST_HEAD(&dwc->gadget->ep_list); + + ret = dwc3_gadget_get_reserved_endpoints(dwc, propname, + reserved_eps, ARRAY_SIZE(reserved_eps)); + if (ret < 0) { + dev_err(dwc->dev, "failed to read %s\n", propname); + return ret; + } + count = ret; + + for (epnum = 0; epnum < total; epnum++) { + for (num = 0; num < count; num++) { + if (epnum == reserved_eps[num]) + break; + } + if (num < count) + continue; + + ret = dwc3_gadget_init_endpoint(dwc, epnum); + if (ret) + 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); + } + + dwc3_debugfs_remove_endpoint_dir(dep); + cancel_delayed_work_sync(&dep->nostream_work); + kfree(dep); + } +} + +/* -------------------------------------------------------------------------- */ + +static int dwc3_gadget_ep_reclaim_completed_trb(struct dwc3_ep *dep, + struct dwc3_request *req, struct dwc3_trb *trb, + const struct dwc3_event_depevt *event, int status) +{ + unsigned int count; + + dwc3_ep_inc_deq(dep); + + trace_dwc3_complete_trb(dep, trb); + req->num_trbs--; + + /* + * If we're in the middle of series of chained TRBs and we + * receive a short transfer along the way, DWC3 will skip + * through all TRBs including the last TRB in the chain (the + * where CHN bit is zero. DWC3 will also avoid clearing HWO + * bit and SW has to do it manually. + * + * We're going to do that here to avoid problems of HW trying + * to use bogus TRBs for transfers. + */ + if (trb->ctrl & DWC3_TRB_CTRL_HWO) + trb->ctrl &= ~DWC3_TRB_CTRL_HWO; + + /* + * For isochronous transfers, the first TRB in a service interval must + * have the Isoc-First type. Track and report its interval frame number. + */ + if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && + (trb->ctrl & DWC3_TRBCTL_ISOCHRONOUS_FIRST)) { + unsigned int frame_number; + + frame_number = DWC3_TRB_CTRL_GET_SID_SOFN(trb->ctrl); + frame_number &= ~(dep->interval - 1); + req->request.frame_number = frame_number; + } + + /* + * We use bounce buffer for requests that needs extra TRB or OUT ZLP. If + * this TRB points to the bounce buffer address, it's a MPS alignment + * TRB. Don't add it to req->remaining calculation. + */ + if (trb->bpl == lower_32_bits(dep->dwc->bounce_addr) && + trb->bph == upper_32_bits(dep->dwc->bounce_addr)) { + trb->ctrl &= ~DWC3_TRB_CTRL_HWO; + return 1; + } + + count = trb->size & DWC3_TRB_SIZE_MASK; + req->remaining += count; + + if ((trb->ctrl & DWC3_TRB_CTRL_HWO) && status != -ESHUTDOWN) + return 1; + + if (event->status & DEPEVT_STATUS_SHORT && + !(trb->ctrl & DWC3_TRB_CTRL_CHN)) + return 1; + + if ((trb->ctrl & DWC3_TRB_CTRL_ISP_IMI) && + DWC3_TRB_SIZE_TRBSTS(trb->size) == DWC3_TRBSTS_MISSED_ISOC) + return 1; + + if ((trb->ctrl & DWC3_TRB_CTRL_IOC) || + (trb->ctrl & DWC3_TRB_CTRL_LST)) + return 1; + + return 0; +} + +static int dwc3_gadget_ep_reclaim_trb_sg(struct dwc3_ep *dep, + struct dwc3_request *req, const struct dwc3_event_depevt *event, + int status) +{ + struct dwc3_trb *trb; + unsigned int num_completed_trbs = req->num_trbs; + unsigned int i; + int ret = 0; + + for (i = 0; i < num_completed_trbs; i++) { + trb = &dep->trb_pool[dep->trb_dequeue]; + + ret = dwc3_gadget_ep_reclaim_completed_trb(dep, req, + trb, event, status); + if (ret) + break; + } + + return ret; +} + +static bool dwc3_gadget_ep_request_completed(struct dwc3_request *req) +{ + return req->num_pending_sgs == 0 && req->num_trbs == 0; +} + +static int dwc3_gadget_ep_cleanup_completed_request(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event, + struct dwc3_request *req, int status) +{ + int request_status; + int ret; + + ret = dwc3_gadget_ep_reclaim_trb_sg(dep, req, event, status); + + req->request.actual = req->request.length - req->remaining; + + if (!dwc3_gadget_ep_request_completed(req)) + goto out; + + /* + * The event status only reflects the status of the TRB with IOC set. + * For the requests that don't set interrupt on completion, the driver + * needs to check and return the status of the completed TRBs associated + * with the request. Use the status of the last TRB of the request. + */ + if (req->request.no_interrupt) { + struct dwc3_trb *trb; + + trb = dwc3_ep_prev_trb(dep, dep->trb_dequeue); + switch (DWC3_TRB_SIZE_TRBSTS(trb->size)) { + case DWC3_TRBSTS_MISSED_ISOC: + /* Isoc endpoint only */ + request_status = -EXDEV; + break; + case DWC3_TRB_STS_XFER_IN_PROG: + /* Applicable when End Transfer with ForceRM=0 */ + case DWC3_TRBSTS_SETUP_PENDING: + /* Control endpoint only */ + case DWC3_TRBSTS_OK: + default: + request_status = 0; + break; + } + } else { + request_status = status; + } + + dwc3_gadget_giveback(dep, req, request_status); + +out: + return ret; +} + +static void dwc3_gadget_ep_cleanup_completed_requests(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event, int status) +{ + struct dwc3_request *req; + + while (!list_empty(&dep->started_list)) { + int ret; + + req = next_request(&dep->started_list); + ret = dwc3_gadget_ep_cleanup_completed_request(dep, event, + req, status); + if (ret) + break; + /* + * The endpoint is disabled, let the dwc3_remove_requests() + * handle the cleanup. + */ + if (!dep->endpoint.desc) + break; + } +} + +static bool dwc3_gadget_ep_should_continue(struct dwc3_ep *dep) +{ + struct dwc3_request *req; + struct dwc3 *dwc = dep->dwc; + + if (!dep->endpoint.desc || !dwc->pullups_connected || + !dwc->connected) + return false; + + if (!list_empty(&dep->pending_list)) + return true; + + /* + * We only need to check the first entry of the started list. We can + * assume the completed requests are removed from the started list. + */ + req = next_request(&dep->started_list); + if (!req) + return false; + + return !dwc3_gadget_ep_request_completed(req); +} + +static void dwc3_gadget_endpoint_frame_from_event(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event) +{ + dep->frame_number = event->parameters; +} + +static bool dwc3_gadget_endpoint_trbs_complete(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event, int status) +{ + struct dwc3 *dwc = dep->dwc; + bool no_started_trb = true; + + dwc3_gadget_ep_cleanup_completed_requests(dep, event, status); + + if (dep->flags & DWC3_EP_END_TRANSFER_PENDING) + goto out; + + if (!dep->endpoint.desc) + return no_started_trb; + + if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && + list_empty(&dep->started_list) && + (list_empty(&dep->pending_list) || status == -EXDEV)) + dwc3_stop_active_transfer(dep, true, true); + else if (dwc3_gadget_ep_should_continue(dep)) + if (__dwc3_gadget_kick_transfer(dep) == 0) + no_started_trb = false; + +out: + /* + * WORKAROUND: This is the 2nd half of U1/U2 -> U0 workaround. + * See dwc3_gadget_linksts_change_interrupt() for 1st half. + */ + if (DWC3_VER_IS_PRIOR(DWC3, 183A)) { + u32 reg; + int i; + + for (i = 0; i < DWC3_ENDPOINTS_NUM; i++) { + dep = dwc->eps[i]; + if (!dep) + continue; + + if (!(dep->flags & DWC3_EP_ENABLED)) + continue; + + if (!list_empty(&dep->started_list)) + return no_started_trb; + } + + reg = dwc3_readl(dwc, DWC3_DCTL); + reg |= dwc->u1u2; + dwc3_writel(dwc, DWC3_DCTL, reg); + + dwc->u1u2 = 0; + } + + return no_started_trb; +} + +static void dwc3_gadget_endpoint_transfer_in_progress(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event) +{ + int status = 0; + + if (!dep->endpoint.desc) + return; + + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) + dwc3_gadget_endpoint_frame_from_event(dep, event); + + if (event->status & DEPEVT_STATUS_BUSERR) + status = -ECONNRESET; + + if (event->status & DEPEVT_STATUS_MISSED_ISOC) + status = -EXDEV; + + dwc3_gadget_endpoint_trbs_complete(dep, event, status); +} + +static void dwc3_gadget_endpoint_transfer_complete(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event) +{ + int status = 0; + + dep->flags &= ~DWC3_EP_TRANSFER_STARTED; + + if (event->status & DEPEVT_STATUS_BUSERR) + status = -ECONNRESET; + + if (dwc3_gadget_endpoint_trbs_complete(dep, event, status)) + dep->flags &= ~DWC3_EP_WAIT_TRANSFER_COMPLETE; +} + +static void dwc3_gadget_endpoint_transfer_not_ready(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event) +{ + /* + * During a device-initiated disconnect, a late xferNotReady event can + * be generated after the End Transfer command resets the event filter, + * but before the controller is halted. Ignore it to prevent a new + * transfer from starting. + */ + if (!dep->dwc->connected) + return; + + dwc3_gadget_endpoint_frame_from_event(dep, event); + + /* + * The XferNotReady event is generated only once before the endpoint + * starts. It will be generated again when END_TRANSFER command is + * issued. For some controller versions, the XferNotReady event may be + * generated while the END_TRANSFER command is still in process. Ignore + * it and wait for the next XferNotReady event after the command is + * completed. + */ + if (dep->flags & DWC3_EP_END_TRANSFER_PENDING) + return; + + (void) __dwc3_gadget_start_isoc(dep); +} + +static void dwc3_gadget_endpoint_command_complete(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event) +{ + u8 cmd = DEPEVT_PARAMETER_CMD(event->parameters); + + if (cmd != DWC3_DEPCMD_ENDTRANSFER) + return; + + /* + * The END_TRANSFER command will cause the controller to generate a + * NoStream Event, and it's not due to the host DP NoStream rejection. + * Ignore the next NoStream event. + */ + if (dep->stream_capable) + dep->flags |= DWC3_EP_IGNORE_NEXT_NOSTREAM; + + dep->flags &= ~DWC3_EP_END_TRANSFER_PENDING; + dep->flags &= ~DWC3_EP_TRANSFER_STARTED; + dwc3_gadget_ep_cleanup_cancelled_requests(dep); + + if (dep->flags & DWC3_EP_PENDING_CLEAR_STALL) { + struct dwc3 *dwc = dep->dwc; + + dep->flags &= ~DWC3_EP_PENDING_CLEAR_STALL; + if (dwc3_send_clear_stall_ep_cmd(dep)) { + struct usb_ep *ep0 = &dwc->eps[0]->endpoint; + + dev_err(dwc->dev, "failed to clear STALL on %s\n", dep->name); + if (dwc->delayed_status) + __dwc3_gadget_ep0_set_halt(ep0, 1); + return; + } + + dep->flags &= ~(DWC3_EP_STALL | DWC3_EP_WEDGE); + if (dwc->clear_stall_protocol == dep->number) + dwc3_ep0_send_delayed_status(dwc); + } + + if ((dep->flags & DWC3_EP_DELAY_START) && + !usb_endpoint_xfer_isoc(dep->endpoint.desc)) + __dwc3_gadget_kick_transfer(dep); + + dep->flags &= ~DWC3_EP_DELAY_START; +} + +static void dwc3_gadget_endpoint_stream_event(struct dwc3_ep *dep, + const struct dwc3_event_depevt *event) +{ + if (event->status == DEPEVT_STREAMEVT_FOUND) { + cancel_delayed_work(&dep->nostream_work); + dep->flags |= DWC3_EP_STREAM_PRIMED; + dep->flags &= ~DWC3_EP_IGNORE_NEXT_NOSTREAM; + return; + } + + /* Note: NoStream rejection event param value is 0 and not 0xFFFF */ + switch (event->parameters) { + case DEPEVT_STREAM_PRIME: + cancel_delayed_work(&dep->nostream_work); + dep->flags |= DWC3_EP_STREAM_PRIMED; + dep->flags &= ~DWC3_EP_IGNORE_NEXT_NOSTREAM; + break; + case DEPEVT_STREAM_NOSTREAM: + dep->flags &= ~DWC3_EP_STREAM_PRIMED; + if (dep->flags & DWC3_EP_FORCE_RESTART_STREAM) + queue_delayed_work(system_percpu_wq, &dep->nostream_work, + msecs_to_jiffies(100)); + break; + } +} + +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) { + dev_warn(dwc->dev, "spurious event, endpoint %u is not allocated\n", epnum); + return; + } + + if (!(dep->flags & DWC3_EP_ENABLED)) { + if ((epnum > 1) && !(dep->flags & DWC3_EP_TRANSFER_STARTED)) + return; + + /* Handle only EPCMDCMPLT when EP disabled */ + if ((event->endpoint_event != DWC3_DEPEVT_EPCMDCMPLT) && + !(epnum <= 1 && event->endpoint_event == DWC3_DEPEVT_XFERCOMPLETE)) + return; + } + + if (epnum == 0 || epnum == 1) { + dwc3_ep0_interrupt(dwc, event); + return; + } + + switch (event->endpoint_event) { + case DWC3_DEPEVT_XFERINPROGRESS: + dwc3_gadget_endpoint_transfer_in_progress(dep, event); + break; + case DWC3_DEPEVT_XFERNOTREADY: + dwc3_gadget_endpoint_transfer_not_ready(dep, event); + break; + case DWC3_DEPEVT_EPCMDCMPLT: + dwc3_gadget_endpoint_command_complete(dep, event); + break; + case DWC3_DEPEVT_XFERCOMPLETE: + dwc3_gadget_endpoint_transfer_complete(dep, event); + break; + case DWC3_DEPEVT_STREAMEVT: + dwc3_gadget_endpoint_stream_event(dep, event); + break; + case DWC3_DEPEVT_RXTXFIFOEVT: + break; + default: + dev_err(dwc->dev, "unknown endpoint event %d\n", event->endpoint_event); + break; + } +} + +static bool dwc3_prepare_disconnect_gadget(struct dwc3 *dwc, + struct usb_gadget_driver **driver, + struct usb_gadget **gadget) +{ + if (!dwc->async_callbacks || !dwc->gadget_driver || + !dwc->gadget_driver->disconnect) + return false; + + *driver = dwc->gadget_driver; + *gadget = dwc->gadget; + + return true; +} + +static void dwc3_disconnect_gadget(struct dwc3 *dwc) +{ + struct usb_gadget_driver *driver; + struct usb_gadget *gadget; + + if (dwc3_prepare_disconnect_gadget(dwc, &driver, &gadget)) { + spin_unlock(&dwc->lock); + driver->disconnect(gadget); + spin_lock(&dwc->lock); + } +} + +static void dwc3_disconnect_gadget_sleepable(struct dwc3 *dwc) +{ + struct usb_gadget_driver *driver; + struct usb_gadget *gadget; + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + if (!dwc3_prepare_disconnect_gadget(dwc, &driver, &gadget)) { + spin_unlock_irqrestore(&dwc->lock, flags); + return; + } + + spin_unlock_irqrestore(&dwc->lock, flags); + driver->disconnect(gadget); +} + +static void dwc3_suspend_gadget(struct dwc3 *dwc) +{ + if (dwc->async_callbacks && dwc->gadget_driver->suspend) { + spin_unlock(&dwc->lock); + dwc->gadget_driver->suspend(dwc->gadget); + spin_lock(&dwc->lock); + } +} + +static void dwc3_resume_gadget(struct dwc3 *dwc) +{ + if (dwc->async_callbacks && dwc->gadget_driver->resume) { + spin_unlock(&dwc->lock); + dwc->gadget_driver->resume(dwc->gadget); + spin_lock(&dwc->lock); + } +} + +static void dwc3_reset_gadget(struct dwc3 *dwc) +{ + if (!dwc->gadget_driver) + return; + + if (dwc->async_callbacks && dwc->gadget->speed != USB_SPEED_UNKNOWN) { + spin_unlock(&dwc->lock); + usb_gadget_udc_reset(dwc->gadget, dwc->gadget_driver); + spin_lock(&dwc->lock); + } +} + +void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, + bool interrupt) +{ + struct dwc3 *dwc = dep->dwc; + + /* + * Only issue End Transfer command to the control endpoint of a started + * Data Phase. Typically we should only do so in error cases such as + * invalid/unexpected direction as described in the control transfer + * flow of the programming guide. + */ + if (dep->number <= 1 && dwc->ep0state != EP0_DATA_PHASE) + return; + + if (interrupt && (dep->flags & DWC3_EP_DELAY_STOP)) + return; + + if (!(dep->flags & DWC3_EP_TRANSFER_STARTED) || + (dep->flags & DWC3_EP_END_TRANSFER_PENDING)) + return; + + /* + * If a Setup packet is received but yet to DMA out, the controller will + * not process the End Transfer command of any endpoint. Polling of its + * DEPCMD.CmdAct may block setting up TRB for Setup packet, causing a + * timeout. Delay issuing the End Transfer command until the Setup TRB is + * prepared. + */ + if (dwc->ep0state != EP0_SETUP_PHASE && !dwc->delayed_status) { + dep->flags |= DWC3_EP_DELAY_STOP; + 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. + * + * 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 issuing EndTransfer with + * CMDIOC bit set and delay kicking transfer until the + * EndTransfer command had completed. + * + * As of IP version 3.10a of the DWC_usb3 IP, the controller + * supports a mode to work around the above limitation. The + * software can poll the CMDACT bit in the DEPCMD register + * after issuing a EndTransfer command. This mode is enabled + * by writing GUCTL2[14]. This polling is already done in the + * dwc3_send_gadget_ep_cmd() function so if the mode is + * enabled, the EndTransfer command will have completed upon + * returning from this function. + * + * This mode is NOT available on the DWC_usb31 IP. In this + * case, if the IOC bit is not set, then delay by 1ms + * after issuing the EndTransfer command. This allows for the + * controller to handle the command completely before DWC3 + * remove requests attempts to unmap USB request buffers. + */ + + __dwc3_stop_active_transfer(dep, force, interrupt); +} + +static void dwc3_clear_stall_all_ep(struct dwc3 *dwc) +{ + u32 epnum; + + for (epnum = 1; epnum < DWC3_ENDPOINTS_NUM; epnum++) { + struct dwc3_ep *dep; + int ret; + + dep = dwc->eps[epnum]; + if (!dep) + continue; + + if (!(dep->flags & DWC3_EP_STALL)) + continue; + + dep->flags &= ~DWC3_EP_STALL; + + ret = dwc3_send_clear_stall_ep_cmd(dep); + if (ret) + dev_err_ratelimited(dwc->dev, + "failed to clear STALL on %s\n", dep->name); + } +} + +static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc) +{ + int reg; + + dwc->suspended = false; + + dwc3_gadget_set_link_state(dwc, DWC3_LINK_STATE_RX_DET); + + reg = dwc3_readl(dwc, DWC3_DCTL); + reg &= ~DWC3_DCTL_INITU1ENA; + reg &= ~DWC3_DCTL_INITU2ENA; + dwc3_gadget_dctl_write_safe(dwc, reg); + + dwc->connected = false; + + dwc3_disconnect_gadget(dwc); + + dwc->gadget->speed = USB_SPEED_UNKNOWN; + dwc->setup_packet_pending = false; + dwc->gadget->wakeup_armed = false; + dwc3_gadget_enable_linksts_evts(dwc, false); + usb_gadget_set_state(dwc->gadget, USB_STATE_NOTATTACHED); + + dwc3_ep0_reset_state(dwc); + + /* + * Request PM idle to address condition where usage count is + * already decremented to zero, but waiting for the disconnect + * interrupt to set dwc->connected to FALSE. + */ + pm_request_idle(dwc->dev); +} + +static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) +{ + u32 reg; + + dwc->suspended = false; + + /* + * Ideally, dwc3_reset_gadget() would trigger the function + * drivers to stop any active transfers through ep disable. + * However, for functions which defer ep disable, such as mass + * storage, we will need to rely on the call to stop active + * transfers here, and avoid allowing of request queuing. + */ + dwc->connected = false; + + /* + * 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 SETUP_PENDING + * status for EP0 TRBs 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 (DWC3_VER_IS_PRIOR(DWC3, 188A)) { + if (dwc->setup_packet_pending) + dwc3_gadget_disconnect_interrupt(dwc); + } + + dwc3_reset_gadget(dwc); + + /* + * From SNPS databook section 8.1.2, the EP0 should be in setup + * phase. So ensure that EP0 is in setup phase by issuing a stall + * and restart if EP0 is not in setup phase. + */ + dwc3_ep0_reset_state(dwc); + + /* + * In the Synopsis DesignWare Cores USB3 Databook Rev. 3.30a + * Section 4.1.2 Table 4-2, it states that during a USB reset, the SW + * needs to ensure that it sends "a DEPENDXFER command for any active + * transfers." + */ + dwc3_stop_active_transfers(dwc); + dwc->connected = true; + + reg = dwc3_readl(dwc, DWC3_DCTL); + reg &= ~DWC3_DCTL_TSTCTRL_MASK; + dwc3_gadget_dctl_write_safe(dwc, reg); + dwc->test_mode = false; + dwc->gadget->wakeup_armed = false; + dwc3_gadget_enable_linksts_evts(dwc, false); + dwc3_clear_stall_all_ep(dwc); + + /* Reset device address to zero */ + reg = dwc3_readl(dwc, DWC3_DCFG); + reg &= ~(DWC3_DCFG_DEVADDR_MASK); + dwc3_writel(dwc, DWC3_DCFG, reg); +} + +static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + int ret; + u32 reg; + u8 lanes = 1; + u8 speed; + + if (!dwc->softconnect) + return; + + reg = dwc3_readl(dwc, DWC3_DSTS); + speed = reg & DWC3_DSTS_CONNECTSPD; + dwc->speed = speed; + + if (DWC3_IP_IS(DWC32)) + lanes = DWC3_DSTS_CONNLANES(reg) + 1; + + dwc->gadget->ssp_rate = USB_SSP_GEN_UNKNOWN; + + /* + * RAMClkSel is reset to 0 after USB reset, so it must be reprogrammed + * each time on Connect Done. + * + * Currently we always use the reset value. If any platform + * wants to set this to a different value, we need to add a + * setting and update GCTL.RAMCLKSEL here. + */ + + switch (speed) { + case DWC3_DSTS_SUPERSPEED_PLUS: + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + dwc->gadget->ep0->maxpacket = 512; + dwc->gadget->speed = USB_SPEED_SUPER_PLUS; + + if (lanes > 1) + dwc->gadget->ssp_rate = USB_SSP_GEN_2x2; + else + dwc->gadget->ssp_rate = USB_SSP_GEN_2x1; + break; + case DWC3_DSTS_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 (DWC3_VER_IS_PRIOR(DWC3, 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; + + if (lanes > 1) { + dwc->gadget->speed = USB_SPEED_SUPER_PLUS; + dwc->gadget->ssp_rate = USB_SSP_GEN_1x2; + } + break; + case DWC3_DSTS_HIGHSPEED: + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + dwc->gadget->ep0->maxpacket = 64; + dwc->gadget->speed = USB_SPEED_HIGH; + break; + case DWC3_DSTS_FULLSPEED: + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + dwc->gadget->ep0->maxpacket = 64; + dwc->gadget->speed = USB_SPEED_FULL; + break; + } + + dwc->eps[1]->endpoint.maxpacket = dwc->gadget->ep0->maxpacket; + + /* Enable USB2 LPM Capability */ + + if (!DWC3_VER_IS_WITHIN(DWC3, ANY, 194A) && + !dwc->usb2_gadget_lpm_disable && + (speed != DWC3_DSTS_SUPERSPEED) && + (speed != DWC3_DSTS_SUPERSPEED_PLUS)) { + reg = dwc3_readl(dwc, DWC3_DCFG); + reg |= DWC3_DCFG_LPM_CAP; + dwc3_writel(dwc, DWC3_DCFG, reg); + + reg = dwc3_readl(dwc, DWC3_DCTL); + reg &= ~(DWC3_DCTL_HIRD_THRES_MASK | DWC3_DCTL_L1_HIBER_EN); + + reg |= DWC3_DCTL_HIRD_THRES(dwc->hird_threshold | + (dwc->is_utmi_l1_suspend << 4)); + + /* + * When dwc3 revisions >= 2.40a, LPM Erratum is enabled and + * DCFG.LPMCap is set, core responses with an ACK and the + * BESL value in the LPM token is less than or equal to LPM + * NYET threshold. + */ + WARN_ONCE(DWC3_VER_IS_PRIOR(DWC3, 240A) && dwc->has_lpm_erratum, + "LPM Erratum not available on dwc3 revisions < 2.40a\n"); + + if (dwc->has_lpm_erratum && !DWC3_VER_IS_PRIOR(DWC3, 240A)) { + reg &= ~DWC3_DCTL_NYET_THRES_MASK; + reg |= DWC3_DCTL_NYET_THRES(dwc->lpm_nyet_threshold); + } + + dwc3_gadget_dctl_write_safe(dwc, reg); + } else { + if (dwc->usb2_gadget_lpm_disable) { + reg = dwc3_readl(dwc, DWC3_DCFG); + reg &= ~DWC3_DCFG_LPM_CAP; + dwc3_writel(dwc, DWC3_DCFG, reg); + } + + reg = dwc3_readl(dwc, DWC3_DCTL); + reg &= ~DWC3_DCTL_HIRD_THRES_MASK; + dwc3_gadget_dctl_write_safe(dwc, reg); + } + + dep = dwc->eps[0]; + ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_MODIFY); + 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_DEPCFG_ACTION_MODIFY); + 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, unsigned int evtinfo) +{ + dwc->suspended = false; + + /* + * TODO take core out of low power mode when that's + * implemented. + */ + + if (dwc->async_callbacks && dwc->gadget_driver->resume) { + spin_unlock(&dwc->lock); + dwc->gadget_driver->resume(dwc->gadget); + spin_lock(&dwc->lock); + } + + dwc->link_state = evtinfo & DWC3_LINK_STATE_MASK; +} + +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; + int ret; + int intf_id; + + /* + * 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 (DWC3_VER_IS_PRIOR(DWC3, 250A) && + (pwropt != DWC3_GHWPARAMS1_EN_PWROPT_HIB)) { + if ((dwc->link_state == DWC3_LINK_STATE_U3) && + (next == DWC3_LINK_STATE_RESUME)) { + 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 (DWC3_VER_IS_PRIOR(DWC3, 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, 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_gadget_dctl_write_safe(dwc, reg); + break; + default: + /* do nothing */ + break; + } + } + } + + switch (next) { + case DWC3_LINK_STATE_U0: + if (dwc->gadget->wakeup_armed || dwc->wakeup_pending_funcs) { + dwc3_gadget_enable_linksts_evts(dwc, false); + dwc3_resume_gadget(dwc); + dwc->suspended = false; + } + break; + case DWC3_LINK_STATE_U1: + if (dwc->speed == USB_SPEED_SUPER) + dwc3_suspend_gadget(dwc); + break; + case DWC3_LINK_STATE_U2: + case DWC3_LINK_STATE_U3: + dwc3_suspend_gadget(dwc); + break; + case DWC3_LINK_STATE_RESUME: + dwc3_resume_gadget(dwc); + break; + default: + /* do nothing */ + break; + } + + dwc->link_state = next; + + /* Proceed with func wakeup if any interfaces that has requested */ + while (dwc->wakeup_pending_funcs && (next == DWC3_LINK_STATE_U0)) { + intf_id = ffs(dwc->wakeup_pending_funcs) - 1; + ret = dwc3_send_gadget_generic_command(dwc, DWC3_DGCMD_DEV_NOTIFICATION, + DWC3_DGCMDPAR_DN_FUNC_WAKE | + DWC3_DGCMDPAR_INTF_SEL(intf_id)); + if (ret) + dev_err(dwc->dev, "Failed to send DN wake for intf %d\n", intf_id); + + dwc->wakeup_pending_funcs &= ~BIT(intf_id); + } +} + +static void dwc3_gadget_suspend_interrupt(struct dwc3 *dwc, + unsigned int evtinfo) +{ + enum dwc3_link_state next = evtinfo & DWC3_LINK_STATE_MASK; + + if (!dwc->suspended && next == DWC3_LINK_STATE_U3) { + dwc->suspended = true; + dwc3_suspend_gadget(dwc); + } + + dwc->link_state = next; +} + +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, event->event_info); + break; + case DWC3_DEVICE_EVENT_HIBER_REQ: + dev_WARN_ONCE(dwc->dev, true, "unexpected hibernation event\n"); + break; + case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE: + dwc3_gadget_linksts_change_interrupt(dwc, event->event_info); + break; + case DWC3_DEVICE_EVENT_SUSPEND: + /* It changed to be suspend event for version 2.30a and above */ + if (!DWC3_VER_IS_PRIOR(DWC3, 230A)) + dwc3_gadget_suspend_interrupt(dwc, event->event_info); + break; + case DWC3_DEVICE_EVENT_SOF: + case DWC3_DEVICE_EVENT_ERRATIC_ERROR: + case DWC3_DEVICE_EVENT_CMD_CMPL: + case DWC3_DEVICE_EVENT_OVERFLOW: + break; + default: + dev_WARN(dwc->dev, "UNKNOWN IRQ %d\n", event->type); + } +} + +static void dwc3_process_event_entry(struct dwc3 *dwc, + const union dwc3_event *event) +{ + trace_dwc3_event(event->raw, dwc); + + if (!event->type.is_devspec) + dwc3_endpoint_interrupt(dwc, &event->depevt); + else if (event->type.type == DWC3_EVENT_TYPE_DEV) + dwc3_gadget_interrupt(dwc, &event->devt); + else + dev_err(dwc->dev, "UNKNOWN IRQ type %d\n", event->raw); +} + +static irqreturn_t dwc3_process_event_buf(struct dwc3_event_buffer *evt) +{ + struct dwc3 *dwc = evt->dwc; + irqreturn_t ret = IRQ_NONE; + int left; + + left = evt->count; + + if (!(evt->flags & DWC3_EVENT_PENDING)) + return IRQ_NONE; + + while (left > 0) { + union dwc3_event event; + + event.raw = *(u32 *) (evt->cache + 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) % evt->length; + left -= 4; + } + + evt->count = 0; + ret = IRQ_HANDLED; + + /* Unmask interrupt */ + dwc3_writel(dwc, DWC3_GEVNTSIZ(0), + DWC3_GEVNTSIZ_SIZE(evt->length)); + + evt->flags &= ~DWC3_EVENT_PENDING; + /* + * Add an explicit write memory barrier to make sure that the update of + * clearing DWC3_EVENT_PENDING is observed in dwc3_check_event_buf() + */ + wmb(); + + if (dwc->imod_interval) { + dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), DWC3_GEVNTCOUNT_EHB); + dwc3_writel(dwc, DWC3_DEV_IMOD(0), dwc->imod_interval); + } + + return ret; +} + +static irqreturn_t dwc3_thread_interrupt(int irq, void *_evt) +{ + struct dwc3_event_buffer *evt = _evt; + struct dwc3 *dwc = evt->dwc; + unsigned long flags; + irqreturn_t ret = IRQ_NONE; + + local_bh_disable(); + spin_lock_irqsave(&dwc->lock, flags); + ret = dwc3_process_event_buf(evt); + spin_unlock_irqrestore(&dwc->lock, flags); + local_bh_enable(); + + return ret; +} + +static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) +{ + struct dwc3 *dwc = evt->dwc; + u32 amount; + u32 count; + + if (pm_runtime_suspended(dwc->dev)) { + dwc->pending_events = true; + /* + * Trigger runtime resume. The get() function will be balanced + * after processing the pending events in dwc3_process_pending + * events(). + */ + pm_runtime_get(dwc->dev); + disable_irq_nosync(dwc->irq_gadget); + return IRQ_HANDLED; + } + + /* + * With PCIe legacy interrupt, test shows that top-half irq handler can + * be called again after HW interrupt deassertion. Check if bottom-half + * irq event handler completes before caching new event to prevent + * losing events. + */ + if (evt->flags & DWC3_EVENT_PENDING) + return IRQ_HANDLED; + + count = dwc3_readl(dwc, DWC3_GEVNTCOUNT(0)); + count &= DWC3_GEVNTCOUNT_MASK; + if (!count) + return IRQ_NONE; + + if (count > evt->length) { + dev_err_ratelimited(dwc->dev, "invalid count(%u) > evt->length(%u)\n", + count, evt->length); + return IRQ_NONE; + } + + evt->count = count; + evt->flags |= DWC3_EVENT_PENDING; + + /* Mask interrupt */ + dwc3_writel(dwc, DWC3_GEVNTSIZ(0), + DWC3_GEVNTSIZ_INTMASK | DWC3_GEVNTSIZ_SIZE(evt->length)); + + amount = min(count, evt->length - evt->lpos); + memcpy(evt->cache + evt->lpos, evt->buf + evt->lpos, amount); + + if (amount < count) + memcpy(evt->cache, evt->buf, count - amount); + + dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), count); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t dwc3_interrupt(int irq, void *_evt) +{ + struct dwc3_event_buffer *evt = _evt; + + return dwc3_check_event_buf(evt); +} + +static int dwc3_gadget_get_irq(struct dwc3 *dwc) +{ + struct platform_device *dwc3_pdev = to_platform_device(dwc->dev); + int irq; + + irq = platform_get_irq_byname_optional(dwc3_pdev, "peripheral"); + if (irq > 0) + goto out; + + if (irq == -EPROBE_DEFER) + goto out; + + irq = platform_get_irq_byname_optional(dwc3_pdev, "dwc_usb3"); + if (irq > 0) + goto out; + + if (irq == -EPROBE_DEFER) + goto out; + + irq = platform_get_irq(dwc3_pdev, 0); + +out: + return irq; +} + +static void dwc_gadget_release(struct device *dev) +{ + struct usb_gadget *gadget = container_of(dev, struct usb_gadget, dev); + + kfree(gadget); +} + +/** + * 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) +{ + int ret; + int irq; + struct device *dev; + + irq = dwc3_gadget_get_irq(dwc); + if (irq < 0) { + ret = irq; + goto err0; + } + + dwc->irq_gadget = irq; + + dwc->ep0_trb = dma_alloc_coherent(dwc->sysdev, + sizeof(*dwc->ep0_trb) * 2, + &dwc->ep0_trb_addr, GFP_KERNEL); + if (!dwc->ep0_trb) { + dev_err(dwc->dev, "failed to allocate ep0 trb\n"); + ret = -ENOMEM; + goto err0; + } + + dwc->setup_buf = kzalloc(DWC3_EP0_SETUP_SIZE, GFP_KERNEL); + if (!dwc->setup_buf) { + ret = -ENOMEM; + goto err1; + } + + dwc->bounce = dma_alloc_coherent(dwc->sysdev, DWC3_BOUNCE_SIZE, + &dwc->bounce_addr, GFP_KERNEL); + if (!dwc->bounce) { + ret = -ENOMEM; + goto err2; + } + + init_completion(&dwc->ep0_in_setup); + dwc->gadget = kzalloc_obj(struct usb_gadget); + if (!dwc->gadget) { + ret = -ENOMEM; + goto err3; + } + + + usb_initialize_gadget(dwc->dev, dwc->gadget, dwc_gadget_release); + dev = &dwc->gadget->dev; + dev->platform_data = dwc; + dwc->gadget->ops = &dwc3_gadget_ops; + dwc->gadget->speed = USB_SPEED_UNKNOWN; + dwc->gadget->ssp_rate = USB_SSP_GEN_UNKNOWN; + dwc->gadget->sg_supported = true; + dwc->gadget->name = "dwc3-gadget"; + dwc->gadget->lpm_capable = !dwc->usb2_gadget_lpm_disable; + dwc->gadget->wakeup_capable = true; + + /* + * FIXME We might be setting max_speed to dis_metastability_quirk) + dev_info(dwc->dev, "changing max_speed on rev %08x\n", + dwc->revision); + + dwc->gadget->max_speed = dwc->maximum_speed; + dwc->gadget->max_ssp_rate = dwc->max_ssp_rate; + + /* + * 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, dwc->num_eps); + if (ret) + goto err4; + + ret = usb_add_gadget(dwc->gadget); + if (ret) { + dev_err(dwc->dev, "failed to add gadget\n"); + goto err5; + } + + if (DWC3_IP_IS(DWC32) && dwc->maximum_speed == USB_SPEED_SUPER_PLUS) + dwc3_gadget_set_ssp_rate(dwc->gadget, dwc->max_ssp_rate); + else + dwc3_gadget_set_speed(dwc->gadget, dwc->maximum_speed); + + /* No system wakeup if no gadget driver bound */ + if (dwc->sys_wakeup) + device_wakeup_disable(dwc->sysdev); + + return 0; + +err5: + dwc3_gadget_free_endpoints(dwc); +err4: + usb_put_gadget(dwc->gadget); + dwc->gadget = NULL; +err3: + dma_free_coherent(dwc->sysdev, DWC3_BOUNCE_SIZE, dwc->bounce, + dwc->bounce_addr); + +err2: + kfree(dwc->setup_buf); + +err1: + dma_free_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2, + dwc->ep0_trb, dwc->ep0_trb_addr); + +err0: + return ret; +} +EXPORT_SYMBOL_GPL(dwc3_gadget_init); + +/* -------------------------------------------------------------------------- */ + +void dwc3_gadget_exit(struct dwc3 *dwc) +{ + if (!dwc->gadget) + return; + + dwc3_enable_susphy(dwc, true); + usb_del_gadget(dwc->gadget); + dwc3_gadget_free_endpoints(dwc); + usb_put_gadget(dwc->gadget); + dma_free_coherent(dwc->sysdev, DWC3_BOUNCE_SIZE, dwc->bounce, + dwc->bounce_addr); + kfree(dwc->setup_buf); + dma_free_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2, + dwc->ep0_trb, dwc->ep0_trb_addr); +} +EXPORT_SYMBOL_GPL(dwc3_gadget_exit); + +int dwc3_gadget_suspend(struct dwc3 *dwc) +{ + int ret; + + ret = dwc3_gadget_soft_disconnect(dwc); + /* + * Attempt to reset the controller's state. Likely no + * communication can be established until the host + * performs a port reset. + */ + if (ret && dwc->softconnect) { + dwc3_gadget_soft_connect(dwc); + return -EAGAIN; + } + + dwc3_disconnect_gadget_sleepable(dwc); + + return 0; +} + +int dwc3_gadget_resume(struct dwc3 *dwc) +{ + if (!dwc->gadget_driver || !dwc->softconnect) + return 0; + + return dwc3_gadget_soft_connect(dwc); +} diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h new file mode 100644 index 000000000..45f113b3c --- /dev/null +++ b/drivers/usb/dwc3/gadget.h @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * gadget.h - DesignWare USB3 DRD Gadget Header + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + */ + +#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) (dev_get_platdata(&g->dev)) + +/* DEPCFG parameter 1 */ +#define DWC3_DEPCFG_INT_NUM(n) (((n) & 0x1f) << 0) +#define DWC3_DEPCFG_XFER_COMPLETE_EN BIT(8) +#define DWC3_DEPCFG_XFER_IN_PROGRESS_EN BIT(9) +#define DWC3_DEPCFG_XFER_NOT_READY_EN BIT(10) +#define DWC3_DEPCFG_FIFO_ERROR_EN BIT(11) +#define DWC3_DEPCFG_STREAM_EVENT_EN BIT(13) +#define DWC3_DEPCFG_BINTERVAL_M1(n) (((n) & 0xff) << 16) +#define DWC3_DEPCFG_STREAM_CAPABLE BIT(24) +#define DWC3_DEPCFG_EP_NUMBER(n) (((n) & 0x1f) << 25) +#define DWC3_DEPCFG_BULK_BASED BIT(30) +#define DWC3_DEPCFG_FIFO_BASED BIT(31) + +/* DEPCFG parameter 0 */ +#define DWC3_DEPCFG_EP_TYPE(n) (((n) & 0x3) << 1) +#define DWC3_DEPCFG_MAX_PACKET_SIZE(n) (((n) & 0x7ff) << 3) +#define DWC3_DEPCFG_FIFO_NUMBER(n) (((n) & 0x1f) << 17) +#define DWC3_DEPCFG_BURST_SIZE(n) (((n) & 0xf) << 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 BIT(31) +/* These apply for core versions 1.94a and later */ +#define DWC3_DEPCFG_ACTION_INIT (0 << 30) +#define DWC3_DEPCFG_ACTION_RESTORE BIT(30) +#define DWC3_DEPCFG_ACTION_MODIFY (2 << 30) + +/* DEPXFERCFG parameter 0 */ +#define DWC3_DEPXFERCFG_NUM_XFER_RES(n) ((n) & 0xffff) + +/* U1 Device exit Latency */ +#define DWC3_DEFAULT_U1_DEV_EXIT_LAT 0x0A /* Less then 10 microsec */ + +/* U2 Device exit Latency */ +#define DWC3_DEFAULT_U2_DEV_EXIT_LAT 0x1FF /* Less then 511 microsec */ + +/* Frame/Microframe Number Mask */ +#define DWC3_FRNUMBER_MASK 0x3fff +/* -------------------------------------------------------------------------- */ + +#define to_dwc3_request(r) (container_of(r, struct dwc3_request, request)) + +/** + * next_request - gets the next request on the given list + * @list: the request list to operate on + * + * Caller should take care of locking. This function return %NULL or the first + * request available on @list. + */ +static inline struct dwc3_request *next_request(struct list_head *list) +{ + return list_first_entry_or_null(list, struct dwc3_request, list); +} + +/** + * dwc3_gadget_move_started_request - move @req to the started_list + * @req: the request to be moved + * + * Caller should take care of locking. This function will move @req from its + * current list to the endpoint's started_list. + */ +static inline void dwc3_gadget_move_started_request(struct dwc3_request *req) +{ + struct dwc3_ep *dep = req->dep; + + req->status = DWC3_REQUEST_STATUS_STARTED; + list_move_tail(&req->list, &dep->started_list); +} + +/** + * dwc3_gadget_move_cancelled_request - move @req to the cancelled_list + * @req: the request to be moved + * @reason: cancelled reason for the dwc3 request + * + * Caller should take care of locking. This function will move @req from its + * current list to the endpoint's cancelled_list. + */ +static inline void dwc3_gadget_move_cancelled_request(struct dwc3_request *req, + unsigned int reason) +{ + struct dwc3_ep *dep = req->dep; + + req->status = reason; + list_move_tail(&req->list, &dep->cancelled_list); +} + +void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, + int status); + +void dwc3_ep0_interrupt(struct dwc3 *dwc, + const struct dwc3_event_depevt *event); +void dwc3_ep0_out_start(struct dwc3 *dwc); +void dwc3_ep0_end_control_data(struct dwc3 *dwc, struct dwc3_ep *dep); +void dwc3_ep0_stall_and_restart(struct dwc3 *dwc); +int __dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value); +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); +void dwc3_ep0_send_delayed_status(struct dwc3 *dwc); +void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, bool interrupt); +int dwc3_gadget_start_config(struct dwc3 *dwc, unsigned int resource_index); + +/** + * dwc3_gadget_ep_get_transfer_index - Gets transfer index from HW + * @dep: dwc3 endpoint + * + * Caller should take care of locking. Returns the transfer resource + * index for a given endpoint. + */ +static inline void dwc3_gadget_ep_get_transfer_index(struct dwc3_ep *dep) +{ + u32 res_id; + + res_id = dwc3_readl(dep->dwc, DWC3_DEPCMD(dep->number)); + dep->resource_index = DWC3_DEPCMD_GET_RSC_IDX(res_id); +} + +/** + * dwc3_gadget_dctl_write_safe - write to DCTL safe from link state change + * @dwc: pointer to our context structure + * @value: value to write to DCTL + * + * Use this function when doing read-modify-write to DCTL. It will not + * send link state change request. + */ +static inline void dwc3_gadget_dctl_write_safe(struct dwc3 *dwc, u32 value) +{ + value &= ~DWC3_DCTL_ULSTCHNGREQ_MASK; + dwc3_writel(dwc, DWC3_DCTL, value); +} + +#endif /* __DRIVERS_USB_DWC3_GADGET_H */ diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h new file mode 100644 index 000000000..d738e1739 --- /dev/null +++ b/drivers/usb/dwc3/glue.h @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * glue.h - DesignWare USB3 DRD glue header + */ + +#ifndef __DRIVERS_USB_DWC3_GLUE_H +#define __DRIVERS_USB_DWC3_GLUE_H + +#include +#include "core.h" + +/** + * dwc3_properties: DWC3 core properties + * @gsbuscfg0_reqinfo: Value to be programmed in the GSBUSCFG0.REQINFO field + * @needs_full_reinit: indicate the controller may not remain power during system + * pm and need full initialization + */ +struct dwc3_properties { + u32 gsbuscfg0_reqinfo; + unsigned needs_full_reinit:1; +}; + +#define DWC3_DEFAULT_PROPERTIES ((struct dwc3_properties){ \ + .gsbuscfg0_reqinfo = DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED, \ + }) + +/** + * dwc3_probe_data: Initialization parameters passed to dwc3_core_probe() + * @dwc: Reference to dwc3 context structure + * @res: resource for the DWC3 core mmio region + * @ignore_clocks_and_resets: clocks and resets defined for the device should + * be ignored by the DWC3 core, as they are managed by the glue + * @skip_core_init_mode: Skip the finial initialization of the target mode, as + * it must be managed by the glue + * @properties: dwc3 software manage properties + */ +struct dwc3_probe_data { + struct dwc3 *dwc; + struct resource *res; + bool ignore_clocks_and_resets; + bool skip_core_init_mode; + struct dwc3_properties properties; +}; + +/** + * dwc3_core_probe - Initialize the core dwc3 driver + * @data: Initialization and configuration parameters for the controller + * + * Initializes the DesignWare USB3 core driver by setting up resources, + * registering interrupts, performing hardware setup, and preparing + * the controller for operation in the appropriate mode (host, gadget, + * or OTG). This is the main initialization function called by glue + * layer drivers to set up the core controller. + * + * Return: 0 on success, negative error code on failure + */ +int dwc3_core_probe(const struct dwc3_probe_data *data); + +/** + * dwc3_core_remove - Deinitialize and remove the core dwc3 driver + * @dwc: Pointer to DWC3 controller context + * + * Cleans up resources and disables the dwc3 core driver. This should be called + * during driver removal or when the glue layer needs to shut down the + * controller completely. + */ +void dwc3_core_remove(struct dwc3 *dwc); + +/* + * The following callbacks are provided for glue drivers to call from their + * own pm callbacks provided in struct dev_pm_ops. Glue drivers can perform + * platform-specific work before or after calling these functions and delegate + * the core suspend/resume operations to the core driver. + */ +int dwc3_runtime_suspend(struct dwc3 *dwc); +int dwc3_runtime_resume(struct dwc3 *dwc); +int dwc3_runtime_idle(struct dwc3 *dwc); +int dwc3_pm_suspend(struct dwc3 *dwc); +int dwc3_pm_resume(struct dwc3 *dwc); +void dwc3_pm_complete(struct dwc3 *dwc); +int dwc3_pm_prepare(struct dwc3 *dwc); + + +/* All of the following functions must only be used with skip_core_init_mode */ + +/** + * dwc3_core_init - Initialize DWC3 core hardware + * @dwc: Pointer to DWC3 controller context + * + * Configures and initializes the core hardware, usually done by dwc3_core_probe. + * This function is provided for platforms that use skip_core_init_mode and need + * to finalize the core initialization after some platform-specific setup. + * It must only be called when using skip_core_init_mode and before + * dwc3_host_init or dwc3_gadget_init. + * + * Return: 0 on success, negative error code on failure + */ +int dwc3_core_init(struct dwc3 *dwc); + +/** + * dwc3_core_exit - Shut down DWC3 core hardware + * @dwc: Pointer to DWC3 controller context + * + * Disables and cleans up the core hardware state. This is usually handled + * internally by dwc3 and must only be called when using skip_core_init_mode + * and only after dwc3_core_init. Afterwards, dwc3_core_init may be called + * again. + */ +void dwc3_core_exit(struct dwc3 *dwc); + +/** + * dwc3_host_init - Initialize host mode operation + * @dwc: Pointer to DWC3 controller context + * + * Initializes the controller for USB host mode operation, usually done by + * dwc3_core_probe or from within the dwc3 USB role switch callback. + * This function is provided for platforms that use skip_core_init_mode and need + * to finalize the host initialization after some platform-specific setup. + * It must not be called before dwc3_core_init or when skip_core_init_mode is + * not used. It must also not be called when gadget or host mode has already + * been initialized. + * + * Return: 0 on success, negative error code on failure + */ +int dwc3_host_init(struct dwc3 *dwc); + +/** + * dwc3_host_exit - Shut down host mode operation + * @dwc: Pointer to DWC3 controller context + * + * Disables and cleans up host mode resources, usually done by + * the dwc3 USB role switch callback before switching controller mode. + * It must only be called when skip_core_init_mode is used and only after + * dwc3_host_init. + */ +void dwc3_host_exit(struct dwc3 *dwc); + +/** + * dwc3_gadget_init - Initialize gadget mode operation + * @dwc: Pointer to DWC3 controller context + * + * Initializes the controller for USB gadget mode operation, usually done by + * dwc3_core_probe or from within the dwc3 USB role switch callback. This + * function is provided for platforms that use skip_core_init_mode and need to + * finalize the gadget initialization after some platform-specific setup. + * It must not be called before dwc3_core_init or when skip_core_init_mode is + * not used. It must also not be called when gadget or host mode has already + * been initialized. + * + * Return: 0 on success, negative error code on failure + */ +int dwc3_gadget_init(struct dwc3 *dwc); + +/** + * dwc3_gadget_exit - Shut down gadget mode operation + * @dwc: Pointer to DWC3 controller context + * + * Disables and cleans up gadget mode resources, usually done by + * the dwc3 USB role switch callback before switching controller mode. + * It must only be called when skip_core_init_mode is used and only after + * dwc3_gadget_init. + */ +void dwc3_gadget_exit(struct dwc3 *dwc); + +/** + * dwc3_enable_susphy - Control SUSPHY status for all USB ports + * @dwc: Pointer to DWC3 controller context + * @enable: True to enable SUSPHY, false to disable + * + * Enables or disables the USB3 PHY SUSPEND and USB2 PHY SUSPHY feature for + * all available ports. + * This is usually handled by the dwc3 core code and should only be used + * when skip_core_init_mode is used and the glue layer needs to manage SUSPHY + * settings itself, e.g., due to platform-specific requirements during mode + * switches. + */ +void dwc3_enable_susphy(struct dwc3 *dwc, bool enable); + +/** + * dwc3_set_prtcap - Set the USB controller PRTCAP mode + * @dwc: Pointer to DWC3 controller context + * @mode: Target mode, must be one of DWC3_GCTL_PRTCAP_{HOST,DEVICE,OTG} + * @ignore_susphy: If true, skip disabling the SUSPHY and keep the current state + * + * Updates PRTCAP of the controller and current_dr_role inside the dwc3 + * structure. For DRD controllers, this also disables SUSPHY unless explicitly + * told to skip via the ignore_susphy parameter. + * + * This is usually handled by the dwc3 core code and should only be used + * when skip_core_init_mode is used and the glue layer needs to manage mode + * transitions itself due to platform-specific requirements. It must be called + * with the correct mode before calling dwc3_host_init or dwc3_gadget_init. + */ +void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy); + +#endif diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c new file mode 100644 index 000000000..c5674161b --- /dev/null +++ b/drivers/usb/dwc3/host.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * host.c - DesignWare USB3 DRD Controller Host Glue + * + * Copyright (C) 2011 Texas Instruments Incorporated - https://www.ti.com + * + * Authors: Felipe Balbi , + */ + +#include +#include +#include +#include +#include +#include + +#include "../host/xhci-port.h" +#include "../host/xhci-ext-caps.h" +#include "../host/xhci-caps.h" +#include "../host/xhci-plat.h" +#include "core.h" + +#define XHCI_HCSPARAMS1 0x4 +#define XHCI_PORTSC_BASE 0x400 + +/** + * dwc3_power_off_all_roothub_ports - Power off all Root hub ports + * @dwc: Pointer to our controller context structure + */ +static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) +{ + void __iomem *xhci_regs; + u32 op_regs_base; + int port_num; + u32 offset; + u32 reg; + int i; + + /* xhci regs are not mapped yet, do it temporarily here */ + if (dwc->xhci_resources[0].start) { + if (dwc->xhci_resources[0].flags & IORESOURCE_MEM_NONPOSTED) + xhci_regs = ioremap_np(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END); + else + xhci_regs = ioremap(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END); + if (!xhci_regs) { + dev_err(dwc->dev, "Failed to ioremap xhci_regs\n"); + return; + } + + op_regs_base = FIELD_GET(HC_LENGTH, readl(xhci_regs)); + reg = readl(xhci_regs + XHCI_HCSPARAMS1); + port_num = FIELD_GET(HCS_MAX_PORTS, reg); + + for (i = 1; i <= port_num; i++) { + offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1); + reg = readl(xhci_regs + offset); + reg &= ~PORT_POWER; + writel(reg, xhci_regs + offset); + } + + iounmap(xhci_regs); + } else { + dev_err(dwc->dev, "xhci base reg invalid\n"); + } +} + +static void dwc3_xhci_plat_start(struct usb_hcd *hcd) +{ + struct platform_device *pdev; + struct dwc3 *dwc; + + if (!usb_hcd_is_primary_hcd(hcd)) + return; + + pdev = to_platform_device(hcd->self.controller); + dwc = dev_get_drvdata(pdev->dev.parent); + + dwc3_enable_susphy(dwc, true); +} + +static const struct xhci_plat_priv dwc3_xhci_plat_quirk = { + .plat_start = dwc3_xhci_plat_start, +}; + +static void dwc3_host_fill_xhci_irq_res(struct dwc3 *dwc, + int irq, char *name) +{ + struct platform_device *pdev = to_platform_device(dwc->dev); + struct device_node *np = dev_of_node(&pdev->dev); + + dwc->xhci_resources[1].start = irq; + dwc->xhci_resources[1].end = irq; + dwc->xhci_resources[1].flags = IORESOURCE_IRQ | irq_get_trigger_type(irq); + if (!name && np) + dwc->xhci_resources[1].name = of_node_full_name(pdev->dev.of_node); + else + dwc->xhci_resources[1].name = name; +} + +static int dwc3_host_get_irq(struct dwc3 *dwc) +{ + struct platform_device *dwc3_pdev = to_platform_device(dwc->dev); + int irq; + + irq = platform_get_irq_byname_optional(dwc3_pdev, "host"); + if (irq > 0) { + dwc3_host_fill_xhci_irq_res(dwc, irq, "host"); + goto out; + } + + if (irq == -EPROBE_DEFER) + goto out; + + irq = platform_get_irq_byname_optional(dwc3_pdev, "dwc_usb3"); + if (irq > 0) { + dwc3_host_fill_xhci_irq_res(dwc, irq, "dwc_usb3"); + goto out; + } + + if (irq == -EPROBE_DEFER) + goto out; + + irq = platform_get_irq(dwc3_pdev, 0); + if (irq > 0) + dwc3_host_fill_xhci_irq_res(dwc, irq, NULL); + +out: + return irq; +} + +int dwc3_host_init(struct dwc3 *dwc) +{ + struct property_entry props[6]; + struct platform_device *xhci; + int ret, irq; + int prop_idx = 0; + + /* + * Some platforms need to power off all Root hub ports immediately after DWC3 set to host + * mode to avoid VBUS glitch happen when xhci get reset later. + */ + dwc3_power_off_all_roothub_ports(dwc); + + irq = dwc3_host_get_irq(dwc); + if (irq < 0) + return irq; + + xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO); + if (!xhci) { + dev_err(dwc->dev, "couldn't allocate xHCI device\n"); + return -ENOMEM; + } + + xhci->dev.parent = dwc->dev; + + 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 err; + } + + memset(props, 0, sizeof(struct property_entry) * ARRAY_SIZE(props)); + + props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-sg-trb-cache-size-quirk"); + + props[prop_idx++] = PROPERTY_ENTRY_BOOL("write-64-hi-lo-quirk"); + + if (dwc->usb3_lpm_capable) + props[prop_idx++] = PROPERTY_ENTRY_BOOL("usb3-lpm-capable"); + + if (dwc->usb2_lpm_disable) + props[prop_idx++] = PROPERTY_ENTRY_BOOL("usb2-lpm-disable"); + + /** + * WORKAROUND: dwc3 revisions <=3.00a have a limitation + * where Port Disable command doesn't work. + * + * The suggested workaround is that we avoid Port Disable + * completely. + * + * This following flag tells XHCI to do just that. + */ + if (DWC3_VER_IS_WITHIN(DWC3, ANY, 300A)) + props[prop_idx++] = PROPERTY_ENTRY_BOOL("quirk-broken-port-ped"); + + props[prop_idx++] = PROPERTY_ENTRY_U16("num-hc-interrupters", + dwc->num_hc_interrupters); + + if (prop_idx) { + ret = device_create_managed_software_node(&xhci->dev, props, NULL); + if (ret) { + dev_err(dwc->dev, "failed to add properties to xHCI\n"); + goto err; + } + } + + ret = platform_device_add_data(xhci, &dwc3_xhci_plat_quirk, + sizeof(struct xhci_plat_priv)); + if (ret) + goto err; + + ret = platform_device_add(xhci); + if (ret) { + dev_err(dwc->dev, "failed to register xHCI device\n"); + goto err; + } + + if (dwc->sys_wakeup) { + /* Restore wakeup setting if switched from device */ + device_wakeup_enable(dwc->sysdev); + + /* Pass on wakeup setting to the new xhci platform device */ + device_init_wakeup(&xhci->dev, true); + } + + return 0; +err: + platform_device_put(xhci); + return ret; +} +EXPORT_SYMBOL_GPL(dwc3_host_init); + +void dwc3_host_exit(struct dwc3 *dwc) +{ + if (dwc->sys_wakeup) + device_init_wakeup(&dwc->xhci->dev, false); + + dwc3_enable_susphy(dwc, true); + platform_device_unregister(dwc->xhci); + dwc->xhci = NULL; +} +EXPORT_SYMBOL_GPL(dwc3_host_exit); diff --git a/drivers/usb/dwc3/io.h b/drivers/usb/dwc3/io.h new file mode 100644 index 000000000..cad9a2ae1 --- /dev/null +++ b/drivers/usb/dwc3/io.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * io.h - DesignWare USB3 DRD IO Header + * + * Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com + * + * Authors: Felipe Balbi , + * Sebastian Andrzej Siewior + */ + +#ifndef __DRIVERS_USB_DWC3_IO_H +#define __DRIVERS_USB_DWC3_IO_H + +#include +#include "trace.h" +#include "debug.h" +#include "core.h" + +static inline u32 dwc3_readl(struct dwc3 *dwc, u32 offset) +{ + u32 value; + void __iomem *base = dwc->regs; + + /* + * 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. + */ + value = readl(base + offset - DWC3_GLOBALS_REGS_START); + + /* + * When tracing we want to make it easy to find the correct address on + * documentation, so we revert it back to the proper addresses, the + * same way they are described on SNPS documentation + */ + trace_dwc3_readl(dwc, base - DWC3_GLOBALS_REGS_START, offset, value); + + return value; +} + +static inline void dwc3_writel(struct dwc3 *dwc, u32 offset, u32 value) +{ + void __iomem *base = dwc->regs; + + /* + * 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); + + /* + * When tracing we want to make it easy to find the correct address on + * documentation, so we revert it back to the proper addresses, the + * same way they are described on SNPS documentation + */ + trace_dwc3_writel(dwc, base - DWC3_GLOBALS_REGS_START, offset, value); +} + +#endif /* __DRIVERS_USB_DWC3_IO_H */ diff --git a/drivers/usb/dwc3/trace.c b/drivers/usb/dwc3/trace.c new file mode 100644 index 000000000..088995885 --- /dev/null +++ b/drivers/usb/dwc3/trace.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * trace.c - DesignWare USB3 DRD Controller Trace Support + * + * Copyright (C) 2014 Texas Instruments Incorporated - https://www.ti.com + * + * Author: Felipe Balbi + */ + +#define CREATE_TRACE_POINTS +#include "trace.h" diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h new file mode 100644 index 000000000..5253da23d --- /dev/null +++ b/drivers/usb/dwc3/trace.h @@ -0,0 +1,387 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * trace.h - DesignWare USB3 DRD Controller Trace Support + * + * Copyright (C) 2014 Texas Instruments Incorporated - https://www.ti.com + * + * Author: Felipe Balbi + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM dwc3 + +#if !defined(__DWC3_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __DWC3_TRACE_H + +#include +#include +#include +#include "core.h" +#include "debug.h" + +DECLARE_EVENT_CLASS(dwc3_log_set_prtcap, + TP_PROTO(struct dwc3 *dwc, u32 mode), + TP_ARGS(dwc, mode), + TP_STRUCT__entry( + __field(phys_addr_t, base_address) + __field(u32, mode) + ), + TP_fast_assign( + __entry->base_address = dwc->xhci_resources[0].start; + __entry->mode = mode; + ), + TP_printk("%pa: mode %s", &__entry->base_address, dwc3_mode_string(__entry->mode)) +); + +DEFINE_EVENT(dwc3_log_set_prtcap, dwc3_set_prtcap, + TP_PROTO(struct dwc3 *dwc, u32 mode), + TP_ARGS(dwc, mode) +); + +DECLARE_EVENT_CLASS(dwc3_log_io, + TP_PROTO(struct dwc3 *dwc, void *base, u32 offset, u32 value), + TP_ARGS(dwc, base, offset, value), + TP_STRUCT__entry( + __field(phys_addr_t, base_address) + __field(void *, base) + __field(u32, offset) + __field(u32, value) + ), + TP_fast_assign( + __entry->base_address = dwc->xhci_resources[0].start; + __entry->base = base; + __entry->offset = offset; + __entry->value = value; + ), + TP_printk("%pa: addr %p offset %04x value %08x", + &__entry->base_address, + __entry->base + __entry->offset, + __entry->offset, + __entry->value) +); + +DEFINE_EVENT(dwc3_log_io, dwc3_readl, + TP_PROTO(struct dwc3 *dwc, void __iomem *base, u32 offset, u32 value), + TP_ARGS(dwc, base, offset, value) +); + +DEFINE_EVENT(dwc3_log_io, dwc3_writel, + TP_PROTO(struct dwc3 *dwc, void __iomem *base, u32 offset, u32 value), + TP_ARGS(dwc, base, offset, value) +); + +DECLARE_EVENT_CLASS(dwc3_log_event, + TP_PROTO(u32 event, struct dwc3 *dwc), + TP_ARGS(event, dwc), + TP_STRUCT__entry( + __field(phys_addr_t, base_address) + __field(u32, event) + __field(u32, ep0state) + ), + TP_fast_assign( + __entry->base_address = dwc->xhci_resources[0].start; + __entry->event = event; + __entry->ep0state = dwc->ep0state; + ), + TP_printk("%pa: event (%08x): %s", &__entry->base_address, __entry->event, + dwc3_decode_event(__get_buf(DWC3_MSG_MAX), DWC3_MSG_MAX, + __entry->event, __entry->ep0state)) +); + +DEFINE_EVENT(dwc3_log_event, dwc3_event, + TP_PROTO(u32 event, struct dwc3 *dwc), + TP_ARGS(event, dwc) +); + +DECLARE_EVENT_CLASS(dwc3_log_ctrl, + TP_PROTO(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl), + TP_ARGS(dwc, ctrl), + TP_STRUCT__entry( + __field(phys_addr_t, base_address) + __field(__u8, bRequestType) + __field(__u8, bRequest) + __field(__u16, wValue) + __field(__u16, wIndex) + __field(__u16, wLength) + ), + TP_fast_assign( + __entry->base_address = dwc->xhci_resources[0].start; + __entry->bRequestType = ctrl->bRequestType; + __entry->bRequest = ctrl->bRequest; + __entry->wValue = le16_to_cpu(ctrl->wValue); + __entry->wIndex = le16_to_cpu(ctrl->wIndex); + __entry->wLength = le16_to_cpu(ctrl->wLength); + ), + TP_printk("%pa: %s", &__entry->base_address, usb_decode_ctrl(__get_buf(DWC3_MSG_MAX), + DWC3_MSG_MAX, + __entry->bRequestType, + __entry->bRequest, __entry->wValue, + __entry->wIndex, __entry->wLength) + ) +); + +DEFINE_EVENT(dwc3_log_ctrl, dwc3_ctrl_req, + TP_PROTO(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl), + TP_ARGS(dwc, ctrl) +); + +DECLARE_EVENT_CLASS(dwc3_log_request, + TP_PROTO(struct dwc3_request *req), + TP_ARGS(req), + TP_STRUCT__entry( + __field(phys_addr_t, base_address) + __string(name, req->dep->name) + __field(struct dwc3_request *, req) + __field(unsigned int, actual) + __field(unsigned int, length) + __field(int, status) + __field(int, zero) + __field(int, short_not_ok) + __field(int, no_interrupt) + ), + TP_fast_assign( + __entry->base_address = req->dep->dwc->xhci_resources[0].start; + __entry->req = req; + __entry->actual = req->request.actual; + __entry->length = req->request.length; + __entry->status = req->request.status; + __entry->zero = req->request.zero; + __entry->short_not_ok = req->request.short_not_ok; + __entry->no_interrupt = req->request.no_interrupt; + ), + TP_printk("%pa: %s: req %p length %u/%u %s%s%s ==> %d", + &__entry->base_address, + __get_str(name), __entry->req, + __entry->actual, __entry->length, + __entry->zero ? "Z" : "z", + __entry->short_not_ok ? "S" : "s", + __entry->no_interrupt ? "i" : "I", + __entry->status + ) +); + +DEFINE_EVENT(dwc3_log_request, dwc3_alloc_request, + TP_PROTO(struct dwc3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(dwc3_log_request, dwc3_free_request, + TP_PROTO(struct dwc3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(dwc3_log_request, dwc3_ep_queue, + TP_PROTO(struct dwc3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(dwc3_log_request, dwc3_ep_dequeue, + TP_PROTO(struct dwc3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(dwc3_log_request, dwc3_gadget_giveback, + TP_PROTO(struct dwc3_request *req), + TP_ARGS(req) +); + +DECLARE_EVENT_CLASS(dwc3_log_generic_cmd, + TP_PROTO(struct dwc3 *dwc, unsigned int cmd, u32 param, int status), + TP_ARGS(dwc, cmd, param, status), + TP_STRUCT__entry( + __field(phys_addr_t, base_address) + __field(unsigned int, cmd) + __field(u32, param) + __field(int, status) + ), + TP_fast_assign( + __entry->base_address = dwc->xhci_resources[0].start; + __entry->cmd = cmd; + __entry->param = param; + __entry->status = status; + ), + TP_printk("%pa: cmd '%s' [%x] param %08x --> status: %s", + &__entry->base_address, dwc3_gadget_generic_cmd_string(__entry->cmd), + __entry->cmd, __entry->param, + dwc3_gadget_generic_cmd_status_string(__entry->status) + ) +); + +DEFINE_EVENT(dwc3_log_generic_cmd, dwc3_gadget_generic_cmd, + TP_PROTO(struct dwc3 *dwc, unsigned int cmd, u32 param, int status), + TP_ARGS(dwc, cmd, param, status) +); + +DECLARE_EVENT_CLASS(dwc3_log_gadget_ep_cmd, + TP_PROTO(struct dwc3_ep *dep, unsigned int cmd, + struct dwc3_gadget_ep_cmd_params *params, int cmd_status), + TP_ARGS(dep, cmd, params, cmd_status), + TP_STRUCT__entry( + __field(phys_addr_t, base_address) + __string(name, dep->name) + __field(unsigned int, cmd) + __field(u32, param0) + __field(u32, param1) + __field(u32, param2) + __field(int, cmd_status) + ), + TP_fast_assign( + __entry->base_address = dep->dwc->xhci_resources[0].start; + __assign_str(name); + __entry->cmd = cmd; + __entry->param0 = params->param0; + __entry->param1 = params->param1; + __entry->param2 = params->param2; + __entry->cmd_status = cmd_status; + ), + TP_printk("%pa: %s: cmd '%s' [%x] params %08x %08x %08x --> status: %s", + &__entry->base_address, __get_str(name), + dwc3_gadget_ep_cmd_string(__entry->cmd), + __entry->cmd, __entry->param0, + __entry->param1, __entry->param2, + dwc3_ep_cmd_status_string(__entry->cmd_status) + ) +); + +DEFINE_EVENT(dwc3_log_gadget_ep_cmd, dwc3_gadget_ep_cmd, + TP_PROTO(struct dwc3_ep *dep, unsigned int cmd, + struct dwc3_gadget_ep_cmd_params *params, int cmd_status), + TP_ARGS(dep, cmd, params, cmd_status) +); + +DECLARE_EVENT_CLASS(dwc3_log_trb, + TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb), + TP_ARGS(dep, trb), + TP_STRUCT__entry( + __field(phys_addr_t, base_address) + __string(name, dep->name) + __field(struct dwc3_trb *, trb) + __field(u32, bpl) + __field(u32, bph) + __field(u32, size) + __field(u32, ctrl) + __field(u32, type) + __field(u32, enqueue) + __field(u32, dequeue) + ), + TP_fast_assign( + __entry->base_address = dep->dwc->xhci_resources[0].start; + __assign_str(name); + __entry->trb = trb; + __entry->bpl = trb->bpl; + __entry->bph = trb->bph; + __entry->size = trb->size; + __entry->ctrl = trb->ctrl; + __entry->type = usb_endpoint_type(dep->endpoint.desc); + __entry->enqueue = dep->trb_enqueue; + __entry->dequeue = dep->trb_dequeue; + ), + TP_printk("%pa: %s: trb %p (E%d:D%d) buf %08x%08x size %s%d ctrl %08x sofn %08x (%c%c%c%c:%c%c:%s)", + &__entry->base_address, __get_str(name), __entry->trb, __entry->enqueue, + __entry->dequeue, __entry->bph, __entry->bpl, + ({char *s; + int pcm = ((__entry->size >> 24) & 3) + 1; + + switch (__entry->type) { + case USB_ENDPOINT_XFER_INT: + case USB_ENDPOINT_XFER_ISOC: + switch (pcm) { + case 1: + s = "1x "; + break; + case 2: + s = "2x "; + break; + case 3: + default: + s = "3x "; + break; + } + break; + default: + s = ""; + } s; }), + DWC3_TRB_SIZE_LENGTH(__entry->size), __entry->ctrl, + DWC3_TRB_CTRL_GET_SID_SOFN(__entry->ctrl), + __entry->ctrl & DWC3_TRB_CTRL_HWO ? 'H' : 'h', + __entry->ctrl & DWC3_TRB_CTRL_LST ? 'L' : 'l', + __entry->ctrl & DWC3_TRB_CTRL_CHN ? 'C' : 'c', + __entry->ctrl & DWC3_TRB_CTRL_CSP ? 'S' : 's', + __entry->ctrl & DWC3_TRB_CTRL_ISP_IMI ? 'S' : 's', + __entry->ctrl & DWC3_TRB_CTRL_IOC ? 'C' : 'c', + dwc3_trb_type_string(DWC3_TRBCTL_TYPE(__entry->ctrl)) + ) +); + +DEFINE_EVENT(dwc3_log_trb, dwc3_prepare_trb, + TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb), + TP_ARGS(dep, trb) +); + +DEFINE_EVENT(dwc3_log_trb, dwc3_complete_trb, + TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb), + TP_ARGS(dep, trb) +); + +DECLARE_EVENT_CLASS(dwc3_log_ep, + TP_PROTO(struct dwc3_ep *dep), + TP_ARGS(dep), + TP_STRUCT__entry( + __field(phys_addr_t, base_address) + __string(name, dep->name) + __field(unsigned int, maxpacket) + __field(unsigned int, maxpacket_limit) + __field(unsigned int, max_streams) + __field(unsigned int, maxburst) + __field(unsigned int, flags) + __field(unsigned int, direction) + __field(u8, trb_enqueue) + __field(u8, trb_dequeue) + ), + TP_fast_assign( + __entry->base_address = dep->dwc->xhci_resources[0].start; + __assign_str(name); + __entry->maxpacket = dep->endpoint.maxpacket; + __entry->maxpacket_limit = dep->endpoint.maxpacket_limit; + __entry->max_streams = dep->endpoint.max_streams; + __entry->maxburst = dep->endpoint.maxburst; + __entry->flags = dep->flags; + __entry->direction = dep->direction; + __entry->trb_enqueue = dep->trb_enqueue; + __entry->trb_dequeue = dep->trb_dequeue; + ), + TP_printk("%pa: %s: mps %d/%d streams %d burst %d ring %d/%d flags %c:%c%c%c%c:%c", + &__entry->base_address, __get_str(name), __entry->maxpacket, + __entry->maxpacket_limit, __entry->max_streams, + __entry->maxburst, __entry->trb_enqueue, + __entry->trb_dequeue, + __entry->flags & DWC3_EP_ENABLED ? 'E' : 'e', + __entry->flags & DWC3_EP_STALL ? 'S' : 's', + __entry->flags & DWC3_EP_WEDGE ? 'W' : 'w', + __entry->flags & DWC3_EP_TRANSFER_STARTED ? 'B' : 'b', + __entry->flags & DWC3_EP_PENDING_REQUEST ? 'P' : 'p', + __entry->direction ? '<' : '>' + ) +); + +DEFINE_EVENT(dwc3_log_ep, dwc3_gadget_ep_enable, + TP_PROTO(struct dwc3_ep *dep), + TP_ARGS(dep) +); + +DEFINE_EVENT(dwc3_log_ep, dwc3_gadget_ep_disable, + TP_PROTO(struct dwc3_ep *dep), + TP_ARGS(dep) +); + +#endif /* __DWC3_TRACE_H */ + +/* this part has to be here */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#include diff --git a/drivers/usb/dwc3/ulpi.c b/drivers/usb/dwc3/ulpi.c new file mode 100644 index 000000000..a256b7f5d --- /dev/null +++ b/drivers/usb/dwc3/ulpi.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ulpi.c - DesignWare USB3 Controller's ULPI PHY interface + * + * Copyright (C) 2015 Intel Corporation + * + * Author: Heikki Krogerus + */ + +#include +#include +#include +#include + +#include "core.h" +#include "io.h" + +#define USB_VENDOR_MICROCHIP 0x0424 + +#define DWC3_ULPI_ADDR(a) \ + ((a >= ULPI_EXT_VENDOR_SPECIFIC) ? \ + DWC3_GUSB2PHYACC_ADDR(ULPI_ACCESS_EXTENDED) | \ + DWC3_GUSB2PHYACC_EXTEND_ADDR(a) : DWC3_GUSB2PHYACC_ADDR(a)) + +#define DWC3_ULPI_BASE_DELAY DIV_ROUND_UP(NSEC_PER_SEC, 60000000L) + +static int dwc3_ulpi_busyloop(struct dwc3 *dwc, u8 addr, bool read) +{ + unsigned long ns = 5L * DWC3_ULPI_BASE_DELAY; + unsigned int count = 10000; + u32 reg; + + if (addr >= ULPI_EXT_VENDOR_SPECIFIC) + ns += DWC3_ULPI_BASE_DELAY; + + if (read) + ns += DWC3_ULPI_BASE_DELAY; + + reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(0)); + if (reg & DWC3_GUSB2PHYCFG_SUSPHY) + usleep_range(1000, 1200); + + while (count--) { + ndelay(ns); + reg = dwc3_readl(dwc, DWC3_GUSB2PHYACC(0)); + if (reg & DWC3_GUSB2PHYACC_DONE) + return 0; + cpu_relax(); + } + + return -ETIMEDOUT; +} + +static int dwc3_ulpi_read(struct device *dev, u8 addr) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + u32 reg; + int ret; + + reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr); + dwc3_writel(dwc, DWC3_GUSB2PHYACC(0), reg); + + ret = dwc3_ulpi_busyloop(dwc, addr, true); + if (ret) + return ret; + + reg = dwc3_readl(dwc, DWC3_GUSB2PHYACC(0)); + + return DWC3_GUSB2PHYACC_DATA(reg); +} + +static int dwc3_ulpi_write(struct device *dev, u8 addr, u8 val) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + u32 reg; + + reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr); + reg |= DWC3_GUSB2PHYACC_WRITE | val; + dwc3_writel(dwc, DWC3_GUSB2PHYACC(0), reg); + + return dwc3_ulpi_busyloop(dwc, addr, false); +} + +static const struct ulpi_ops dwc3_ulpi_ops = { + .read = dwc3_ulpi_read, + .write = dwc3_ulpi_write, +}; + +static void dwc3_ulpi_detect_config(struct dwc3 *dwc) +{ + struct ulpi *ulpi = dwc->ulpi; + + switch (ulpi->id.vendor) { + case USB_VENDOR_MICROCHIP: + switch (ulpi->id.product) { + case 0x0009: + /* Microchip USB3340 ULPI PHY */ + dwc->enable_usb2_transceiver_delay = true; + break; + default: + break; + } + break; + default: + break; + } +} + +int dwc3_ulpi_init(struct dwc3 *dwc) +{ + /* Register the interface */ + dwc->ulpi = ulpi_register_interface(dwc->dev, &dwc3_ulpi_ops); + if (IS_ERR(dwc->ulpi)) { + dev_err(dwc->dev, "failed to register ULPI interface"); + return PTR_ERR(dwc->ulpi); + } + + dwc3_ulpi_detect_config(dwc); + + return 0; +} + +void dwc3_ulpi_exit(struct dwc3 *dwc) +{ + if (dwc->ulpi) { + ulpi_unregister_interface(dwc->ulpi); + dwc->ulpi = NULL; + } +} diff --git a/drivers/usb/early/Makefile b/drivers/usb/early/Makefile new file mode 100644 index 000000000..7b77b49d3 --- /dev/null +++ b/drivers/usb/early/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for early USB devices +# + +obj-$(CONFIG_EARLY_PRINTK_DBGP) += ehci-dbgp.o +obj-$(CONFIG_EARLY_PRINTK_USB_XDBC) += xhci-dbc.o diff --git a/drivers/usb/early/ehci-dbgp.c b/drivers/usb/early/ehci-dbgp.c new file mode 100644 index 000000000..45b42d8f6 --- /dev/null +++ b/drivers/usb/early/ehci-dbgp.c @@ -0,0 +1,1092 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 ret; + + ret = readl_poll_timeout_atomic(&ehci_debug->control, ctrl, + (ctrl & DBGP_DONE), 1, DBGP_TIMEOUT); + if (ret) + 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; + } + 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; + } + 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; + 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) { + 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); + if (early_dbgp_console.index != -1) + kgdbdbgp_io_ops.cons = &early_dbgp_console; + + 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; +} +device_initcall(kgdbdbgp_start_thread); +#endif /* CONFIG_KGDB */ diff --git a/drivers/usb/early/xhci-dbc.c b/drivers/usb/early/xhci-dbc.c new file mode 100644 index 000000000..41118bba9 --- /dev/null +++ b/drivers/usb/early/xhci-dbc.c @@ -0,0 +1,1011 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xhci-dbc.c - xHCI debug capability early driver + * + * Copyright (C) 2016 Intel Corporation + * + * Author: Lu Baolu + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../host/xhci.h" +#include "xhci-dbc.h" + +static struct xdbc_state xdbc; +static bool early_console_keep; + +#ifdef XDBC_TRACE +#define xdbc_trace trace_printk +#else +static inline void xdbc_trace(const char *fmt, ...) { } +#endif /* XDBC_TRACE */ + +static void __iomem * __init xdbc_map_pci_mmio(u32 bus, u32 dev, u32 func) +{ + u64 val64, sz64, mask64; + void __iomem *base; + u32 val, sz; + u8 byte; + + val = read_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0); + write_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0, ~0); + sz = read_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0); + write_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0, val); + + if (val == 0xffffffff || sz == 0xffffffff) { + pr_notice("invalid mmio bar\n"); + return NULL; + } + + val64 = val & PCI_BASE_ADDRESS_MEM_MASK; + sz64 = sz & PCI_BASE_ADDRESS_MEM_MASK; + mask64 = PCI_BASE_ADDRESS_MEM_MASK; + + if ((val & PCI_BASE_ADDRESS_MEM_TYPE_MASK) == PCI_BASE_ADDRESS_MEM_TYPE_64) { + val = read_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0 + 4); + write_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0 + 4, ~0); + sz = read_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0 + 4); + write_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0 + 4, val); + + val64 |= (u64)val << 32; + sz64 |= (u64)sz << 32; + mask64 |= ~0ULL << 32; + } + + sz64 &= mask64; + + if (!sz64) { + pr_notice("invalid mmio address\n"); + return NULL; + } + + sz64 = 1ULL << __ffs64(sz64); + + /* Check if the mem space is enabled: */ + byte = read_pci_config_byte(bus, dev, func, PCI_COMMAND); + if (!(byte & PCI_COMMAND_MEMORY)) { + byte |= PCI_COMMAND_MEMORY; + write_pci_config_byte(bus, dev, func, PCI_COMMAND, byte); + } + + xdbc.xhci_start = val64; + xdbc.xhci_length = sz64; + base = early_ioremap(val64, sz64); + + return base; +} + +static void * __init xdbc_get_page(dma_addr_t *dma_addr) +{ + void *virt; + + virt = memblock_alloc(PAGE_SIZE, PAGE_SIZE); + if (!virt) + return NULL; + + if (dma_addr) + *dma_addr = (dma_addr_t)__pa(virt); + + return virt; +} + +static u32 __init xdbc_find_dbgp(int xdbc_num, u32 *b, u32 *d, u32 *f) +{ + u32 bus, dev, func, class; + + for (bus = 0; bus < XDBC_PCI_MAX_BUSES; bus++) { + for (dev = 0; dev < XDBC_PCI_MAX_DEVICES; dev++) { + for (func = 0; func < XDBC_PCI_MAX_FUNCTION; func++) { + + class = read_pci_config(bus, dev, func, PCI_CLASS_REVISION); + if ((class >> 8) != PCI_CLASS_SERIAL_USB_XHCI) + continue; + + if (xdbc_num-- != 0) + continue; + + *b = bus; + *d = dev; + *f = func; + + return 0; + } + } + } + + return -1; +} + +static int handshake(void __iomem *ptr, u32 mask, u32 done, int wait, int delay) +{ + u32 result; + + /* Can not use readl_poll_timeout_atomic() for early boot things */ + do { + result = readl(ptr); + result &= mask; + if (result == done) + return 0; + udelay(delay); + wait -= delay; + } while (wait > 0); + + return -ETIMEDOUT; +} + +static void __init xdbc_bios_handoff(void) +{ + int offset, timeout; + u32 val; + + offset = xhci_find_next_ext_cap(xdbc.xhci_base, 0, XHCI_EXT_CAPS_LEGACY); + val = readl(xdbc.xhci_base + offset); + + if (val & XHCI_HC_BIOS_OWNED) { + writel(val | XHCI_HC_OS_OWNED, xdbc.xhci_base + offset); + timeout = handshake(xdbc.xhci_base + offset, XHCI_HC_BIOS_OWNED, 0, 5000, 10); + + if (timeout) { + pr_notice("failed to hand over xHCI control from BIOS\n"); + writel(val & ~XHCI_HC_BIOS_OWNED, xdbc.xhci_base + offset); + } + } + + /* Disable BIOS SMIs and clear all SMI events: */ + val = readl(xdbc.xhci_base + offset + XHCI_LEGACY_CONTROL_OFFSET); + val &= XHCI_LEGACY_DISABLE_SMI; + val |= XHCI_LEGACY_SMI_EVENTS; + writel(val, xdbc.xhci_base + offset + XHCI_LEGACY_CONTROL_OFFSET); +} + +static int __init +xdbc_alloc_ring(struct xdbc_segment *seg, struct xdbc_ring *ring) +{ + seg->trbs = xdbc_get_page(&seg->dma); + if (!seg->trbs) + return -ENOMEM; + + ring->segment = seg; + + return 0; +} + +static void __init xdbc_free_ring(struct xdbc_ring *ring) +{ + struct xdbc_segment *seg = ring->segment; + + if (!seg) + return; + + memblock_phys_free(seg->dma, PAGE_SIZE); + ring->segment = NULL; +} + +static void xdbc_reset_ring(struct xdbc_ring *ring) +{ + struct xdbc_segment *seg = ring->segment; + struct xdbc_trb *link_trb; + + memset(seg->trbs, 0, PAGE_SIZE); + + ring->enqueue = seg->trbs; + ring->dequeue = seg->trbs; + ring->cycle_state = 1; + + if (ring != &xdbc.evt_ring) { + link_trb = &seg->trbs[XDBC_TRBS_PER_SEGMENT - 1]; + link_trb->field[0] = cpu_to_le32(lower_32_bits(seg->dma)); + link_trb->field[1] = cpu_to_le32(upper_32_bits(seg->dma)); + link_trb->field[3] = cpu_to_le32(TRB_TYPE(TRB_LINK)) | cpu_to_le32(LINK_TOGGLE); + } +} + +static inline void xdbc_put_utf16(u16 *s, const char *c, size_t size) +{ + int i; + + for (i = 0; i < size; i++) + s[i] = cpu_to_le16(c[i]); +} + +static void xdbc_mem_init(void) +{ + struct xdbc_ep_context *ep_in, *ep_out; + struct usb_string_descriptor *s_desc; + struct xdbc_erst_entry *entry; + struct xdbc_strings *strings; + struct xdbc_context *ctx; + unsigned int max_burst; + u32 string_length; + int index = 0; + u32 dev_info; + + xdbc_reset_ring(&xdbc.evt_ring); + xdbc_reset_ring(&xdbc.in_ring); + xdbc_reset_ring(&xdbc.out_ring); + memset(xdbc.table_base, 0, PAGE_SIZE); + memset(xdbc.out_buf, 0, PAGE_SIZE); + + /* Initialize event ring segment table: */ + xdbc.erst_size = 16; + xdbc.erst_base = xdbc.table_base + index * XDBC_TABLE_ENTRY_SIZE; + xdbc.erst_dma = xdbc.table_dma + index * XDBC_TABLE_ENTRY_SIZE; + + index += XDBC_ERST_ENTRY_NUM; + entry = (struct xdbc_erst_entry *)xdbc.erst_base; + + entry->seg_addr = cpu_to_le64(xdbc.evt_seg.dma); + entry->seg_size = cpu_to_le32(XDBC_TRBS_PER_SEGMENT); + entry->__reserved_0 = 0; + + /* Initialize ERST registers: */ + writel(1, &xdbc.xdbc_reg->ersts); + xdbc_write64(xdbc.erst_dma, &xdbc.xdbc_reg->erstba); + xdbc_write64(xdbc.evt_seg.dma, &xdbc.xdbc_reg->erdp); + + /* Debug capability contexts: */ + xdbc.dbcc_size = 64 * 3; + xdbc.dbcc_base = xdbc.table_base + index * XDBC_TABLE_ENTRY_SIZE; + xdbc.dbcc_dma = xdbc.table_dma + index * XDBC_TABLE_ENTRY_SIZE; + + index += XDBC_DBCC_ENTRY_NUM; + + /* Popluate the strings: */ + xdbc.string_size = sizeof(struct xdbc_strings); + xdbc.string_base = xdbc.table_base + index * XDBC_TABLE_ENTRY_SIZE; + xdbc.string_dma = xdbc.table_dma + index * XDBC_TABLE_ENTRY_SIZE; + strings = (struct xdbc_strings *)xdbc.string_base; + + index += XDBC_STRING_ENTRY_NUM; + + /* Serial string: */ + s_desc = (struct usb_string_descriptor *)strings->serial; + s_desc->bLength = (strlen(XDBC_STRING_SERIAL) + 1) * 2; + s_desc->bDescriptorType = USB_DT_STRING; + + xdbc_put_utf16(s_desc->wData, XDBC_STRING_SERIAL, strlen(XDBC_STRING_SERIAL)); + string_length = s_desc->bLength; + string_length <<= 8; + + /* Product string: */ + s_desc = (struct usb_string_descriptor *)strings->product; + s_desc->bLength = (strlen(XDBC_STRING_PRODUCT) + 1) * 2; + s_desc->bDescriptorType = USB_DT_STRING; + + xdbc_put_utf16(s_desc->wData, XDBC_STRING_PRODUCT, strlen(XDBC_STRING_PRODUCT)); + string_length += s_desc->bLength; + string_length <<= 8; + + /* Manufacture string: */ + s_desc = (struct usb_string_descriptor *)strings->manufacturer; + s_desc->bLength = (strlen(XDBC_STRING_MANUFACTURER) + 1) * 2; + s_desc->bDescriptorType = USB_DT_STRING; + + xdbc_put_utf16(s_desc->wData, XDBC_STRING_MANUFACTURER, strlen(XDBC_STRING_MANUFACTURER)); + string_length += s_desc->bLength; + string_length <<= 8; + + /* String0: */ + strings->string0[0] = 4; + strings->string0[1] = USB_DT_STRING; + strings->string0[2] = 0x09; + strings->string0[3] = 0x04; + + string_length += 4; + + /* Populate info Context: */ + ctx = (struct xdbc_context *)xdbc.dbcc_base; + + ctx->info.string0 = cpu_to_le64(xdbc.string_dma); + ctx->info.manufacturer = cpu_to_le64(xdbc.string_dma + XDBC_MAX_STRING_LENGTH); + ctx->info.product = cpu_to_le64(xdbc.string_dma + XDBC_MAX_STRING_LENGTH * 2); + ctx->info.serial = cpu_to_le64(xdbc.string_dma + XDBC_MAX_STRING_LENGTH * 3); + ctx->info.length = cpu_to_le32(string_length); + + /* Populate bulk out endpoint context: */ + max_burst = DEBUG_MAX_BURST(readl(&xdbc.xdbc_reg->control)); + ep_out = (struct xdbc_ep_context *)&ctx->out; + + ep_out->ep_info1 = 0; + ep_out->ep_info2 = cpu_to_le32(EP_TYPE(BULK_OUT_EP) | MAX_PACKET(1024) | MAX_BURST(max_burst)); + ep_out->deq = cpu_to_le64(xdbc.out_seg.dma | xdbc.out_ring.cycle_state); + + /* Populate bulk in endpoint context: */ + ep_in = (struct xdbc_ep_context *)&ctx->in; + + ep_in->ep_info1 = 0; + ep_in->ep_info2 = cpu_to_le32(EP_TYPE(BULK_IN_EP) | MAX_PACKET(1024) | MAX_BURST(max_burst)); + ep_in->deq = cpu_to_le64(xdbc.in_seg.dma | xdbc.in_ring.cycle_state); + + /* Set DbC context and info registers: */ + xdbc_write64(xdbc.dbcc_dma, &xdbc.xdbc_reg->dccp); + + dev_info = cpu_to_le32((XDBC_VENDOR_ID << 16) | XDBC_PROTOCOL); + writel(dev_info, &xdbc.xdbc_reg->devinfo1); + + dev_info = cpu_to_le32((XDBC_DEVICE_REV << 16) | XDBC_PRODUCT_ID); + writel(dev_info, &xdbc.xdbc_reg->devinfo2); + + xdbc.in_buf = xdbc.out_buf + XDBC_MAX_PACKET; + xdbc.in_dma = xdbc.out_dma + XDBC_MAX_PACKET; +} + +static void xdbc_do_reset_debug_port(u32 id, u32 count) +{ + void __iomem *ops_reg; + void __iomem *portsc; + u32 val, cap_length; + int i; + + cap_length = readl(xdbc.xhci_base) & 0xff; + ops_reg = xdbc.xhci_base + cap_length; + + id--; + for (i = id; i < (id + count); i++) { + portsc = ops_reg + 0x400 + i * 0x10; + val = readl(portsc); + if (!(val & PORT_CONNECT)) + writel(val | PORT_RESET, portsc); + } +} + +static void xdbc_reset_debug_port(void) +{ + u32 val, port_offset, port_count; + int offset = 0; + + do { + offset = xhci_find_next_ext_cap(xdbc.xhci_base, offset, XHCI_EXT_CAPS_PROTOCOL); + if (!offset) + break; + + val = readl(xdbc.xhci_base + offset); + if (XHCI_EXT_PORT_MAJOR(val) != 0x3) + continue; + + val = readl(xdbc.xhci_base + offset + 8); + port_offset = XHCI_EXT_PORT_OFF(val); + port_count = XHCI_EXT_PORT_COUNT(val); + + xdbc_do_reset_debug_port(port_offset, port_count); + } while (1); +} + +static void +xdbc_queue_trb(struct xdbc_ring *ring, u32 field1, u32 field2, u32 field3, u32 field4) +{ + struct xdbc_trb *trb, *link_trb; + + trb = ring->enqueue; + 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); + + ++(ring->enqueue); + if (ring->enqueue >= &ring->segment->trbs[TRBS_PER_SEGMENT - 1]) { + link_trb = ring->enqueue; + if (ring->cycle_state) + link_trb->field[3] |= cpu_to_le32(TRB_CYCLE); + else + link_trb->field[3] &= cpu_to_le32(~TRB_CYCLE); + + ring->enqueue = ring->segment->trbs; + ring->cycle_state ^= 1; + } +} + +static void xdbc_ring_doorbell(int target) +{ + writel(DOOR_BELL_TARGET(target), &xdbc.xdbc_reg->doorbell); +} + +static int xdbc_start(void) +{ + u32 ctrl, status; + int ret; + + ctrl = readl(&xdbc.xdbc_reg->control); + writel(ctrl | CTRL_DBC_ENABLE | CTRL_PORT_ENABLE, &xdbc.xdbc_reg->control); + ret = handshake(&xdbc.xdbc_reg->control, CTRL_DBC_ENABLE, CTRL_DBC_ENABLE, 100000, 100); + if (ret) { + xdbc_trace("failed to initialize hardware\n"); + return ret; + } + + /* Reset port to avoid bus hang: */ + if (xdbc.vendor == PCI_VENDOR_ID_INTEL) + xdbc_reset_debug_port(); + + /* Wait for port connection: */ + ret = handshake(&xdbc.xdbc_reg->portsc, PORTSC_CONN_STATUS, PORTSC_CONN_STATUS, 5000000, 100); + if (ret) { + xdbc_trace("waiting for connection timed out\n"); + return ret; + } + + /* Wait for debug device to be configured: */ + ret = handshake(&xdbc.xdbc_reg->control, CTRL_DBC_RUN, CTRL_DBC_RUN, 5000000, 100); + if (ret) { + xdbc_trace("waiting for device configuration timed out\n"); + return ret; + } + + /* Check port number: */ + status = readl(&xdbc.xdbc_reg->status); + if (!DCST_DEBUG_PORT(status)) { + xdbc_trace("invalid root hub port number\n"); + return -ENODEV; + } + + xdbc.port_number = DCST_DEBUG_PORT(status); + + xdbc_trace("DbC is running now, control 0x%08x port ID %d\n", + readl(&xdbc.xdbc_reg->control), xdbc.port_number); + + return 0; +} + +static int xdbc_bulk_transfer(void *data, int size, bool read) +{ + struct xdbc_ring *ring; + struct xdbc_trb *trb; + u32 length, control; + u32 cycle; + u64 addr; + + if (size > XDBC_MAX_PACKET) { + xdbc_trace("bad parameter, size %d\n", size); + return -EINVAL; + } + + if (!(xdbc.flags & XDBC_FLAGS_INITIALIZED) || + !(xdbc.flags & XDBC_FLAGS_CONFIGURED) || + (!read && (xdbc.flags & XDBC_FLAGS_OUT_STALL)) || + (read && (xdbc.flags & XDBC_FLAGS_IN_STALL))) { + + xdbc_trace("connection not ready, flags %08x\n", xdbc.flags); + return -EIO; + } + + ring = (read ? &xdbc.in_ring : &xdbc.out_ring); + trb = ring->enqueue; + cycle = ring->cycle_state; + length = TRB_LEN(size); + control = TRB_TYPE(TRB_NORMAL) | TRB_IOC; + + if (cycle) + control &= cpu_to_le32(~TRB_CYCLE); + else + control |= cpu_to_le32(TRB_CYCLE); + + if (read) { + memset(xdbc.in_buf, 0, XDBC_MAX_PACKET); + addr = xdbc.in_dma; + xdbc.flags |= XDBC_FLAGS_IN_PROCESS; + } else { + memcpy_and_pad(xdbc.out_buf, XDBC_MAX_PACKET, data, size, 0); + addr = xdbc.out_dma; + xdbc.flags |= XDBC_FLAGS_OUT_PROCESS; + } + + xdbc_queue_trb(ring, lower_32_bits(addr), upper_32_bits(addr), length, control); + + /* + * Add a barrier between writes of trb fields and flipping + * the cycle bit: + */ + wmb(); + if (cycle) + trb->field[3] |= cpu_to_le32(cycle); + else + trb->field[3] &= cpu_to_le32(~TRB_CYCLE); + + xdbc_ring_doorbell(read ? IN_EP_DOORBELL : OUT_EP_DOORBELL); + + return size; +} + +static int xdbc_handle_external_reset(void) +{ + int ret = 0; + + xdbc.flags = 0; + writel(0, &xdbc.xdbc_reg->control); + ret = handshake(&xdbc.xdbc_reg->control, CTRL_DBC_ENABLE, 0, 100000, 10); + if (ret) + goto reset_out; + + xdbc_mem_init(); + + ret = xdbc_start(); + if (ret < 0) + goto reset_out; + + xdbc_trace("dbc recovered\n"); + + xdbc.flags |= XDBC_FLAGS_INITIALIZED | XDBC_FLAGS_CONFIGURED; + + xdbc_bulk_transfer(NULL, XDBC_MAX_PACKET, true); + + return 0; + +reset_out: + xdbc_trace("failed to recover from external reset\n"); + return ret; +} + +static int __init xdbc_early_setup(void) +{ + int ret; + + writel(0, &xdbc.xdbc_reg->control); + ret = handshake(&xdbc.xdbc_reg->control, CTRL_DBC_ENABLE, 0, 100000, 100); + if (ret) + return ret; + + /* Allocate the table page: */ + xdbc.table_base = xdbc_get_page(&xdbc.table_dma); + if (!xdbc.table_base) + return -ENOMEM; + + /* Get and store the transfer buffer: */ + xdbc.out_buf = xdbc_get_page(&xdbc.out_dma); + if (!xdbc.out_buf) + return -ENOMEM; + + /* Allocate the event ring: */ + ret = xdbc_alloc_ring(&xdbc.evt_seg, &xdbc.evt_ring); + if (ret < 0) + return ret; + + /* Allocate IN/OUT endpoint transfer rings: */ + ret = xdbc_alloc_ring(&xdbc.in_seg, &xdbc.in_ring); + if (ret < 0) + return ret; + + ret = xdbc_alloc_ring(&xdbc.out_seg, &xdbc.out_ring); + if (ret < 0) + return ret; + + xdbc_mem_init(); + + ret = xdbc_start(); + if (ret < 0) { + writel(0, &xdbc.xdbc_reg->control); + return ret; + } + + xdbc.flags |= XDBC_FLAGS_INITIALIZED | XDBC_FLAGS_CONFIGURED; + + xdbc_bulk_transfer(NULL, XDBC_MAX_PACKET, true); + + return 0; +} + +int __init early_xdbc_parse_parameter(char *s, int keep_early) +{ + unsigned long dbgp_num = 0; + u32 bus, dev, func, offset; + char *e; + int ret; + + if (!early_pci_allowed()) + return -EPERM; + + early_console_keep = keep_early; + + if (xdbc.xdbc_reg) + return 0; + + if (*s) { + dbgp_num = simple_strtoul(s, &e, 10); + if (s == e) + dbgp_num = 0; + } + + pr_notice("dbgp_num: %lu\n", dbgp_num); + + /* Locate the host controller: */ + ret = xdbc_find_dbgp(dbgp_num, &bus, &dev, &func); + if (ret) { + pr_notice("failed to locate xhci host\n"); + return -ENODEV; + } + + xdbc.vendor = read_pci_config_16(bus, dev, func, PCI_VENDOR_ID); + xdbc.device = read_pci_config_16(bus, dev, func, PCI_DEVICE_ID); + xdbc.bus = bus; + xdbc.dev = dev; + xdbc.func = func; + + /* Map the IO memory: */ + xdbc.xhci_base = xdbc_map_pci_mmio(bus, dev, func); + if (!xdbc.xhci_base) + return -EINVAL; + + /* Locate DbC registers: */ + offset = xhci_find_next_ext_cap(xdbc.xhci_base, 0, XHCI_EXT_CAPS_DEBUG); + if (!offset) { + pr_notice("xhci host doesn't support debug capability\n"); + early_iounmap(xdbc.xhci_base, xdbc.xhci_length); + xdbc.xhci_base = NULL; + xdbc.xhci_length = 0; + + return -ENODEV; + } + xdbc.xdbc_reg = (struct xdbc_regs __iomem *)(xdbc.xhci_base + offset); + + return 0; +} + +int __init early_xdbc_setup_hardware(void) +{ + int ret; + + if (!xdbc.xdbc_reg) + return -ENODEV; + + xdbc_bios_handoff(); + + raw_spin_lock_init(&xdbc.lock); + + ret = xdbc_early_setup(); + if (ret) { + pr_notice("failed to setup the connection to host\n"); + + xdbc_free_ring(&xdbc.evt_ring); + xdbc_free_ring(&xdbc.out_ring); + xdbc_free_ring(&xdbc.in_ring); + + if (xdbc.table_dma) + memblock_phys_free(xdbc.table_dma, PAGE_SIZE); + + if (xdbc.out_dma) + memblock_phys_free(xdbc.out_dma, PAGE_SIZE); + + xdbc.table_base = NULL; + xdbc.out_buf = NULL; + + early_iounmap(xdbc.xhci_base, xdbc.xhci_length); + xdbc.xhci_base = NULL; + xdbc.xhci_length = 0; + } + + return ret; +} + +static void xdbc_handle_port_status(struct xdbc_trb *evt_trb) +{ + u32 port_reg; + + port_reg = readl(&xdbc.xdbc_reg->portsc); + if (port_reg & PORTSC_CONN_CHANGE) { + xdbc_trace("connect status change event\n"); + + /* Check whether cable unplugged: */ + if (!(port_reg & PORTSC_CONN_STATUS)) { + xdbc.flags = 0; + xdbc_trace("cable unplugged\n"); + } + } + + if (port_reg & PORTSC_RESET_CHANGE) + xdbc_trace("port reset change event\n"); + + if (port_reg & PORTSC_LINK_CHANGE) + xdbc_trace("port link status change event\n"); + + if (port_reg & PORTSC_CONFIG_CHANGE) + xdbc_trace("config error change\n"); + + /* Write back the value to clear RW1C bits: */ + writel(port_reg, &xdbc.xdbc_reg->portsc); +} + +static void xdbc_handle_tx_event(struct xdbc_trb *evt_trb) +{ + u32 comp_code; + int ep_id; + + comp_code = GET_COMP_CODE(le32_to_cpu(evt_trb->field[2])); + ep_id = TRB_TO_EP_ID(le32_to_cpu(evt_trb->field[3])); + + switch (comp_code) { + case COMP_SUCCESS: + case COMP_SHORT_PACKET: + break; + case COMP_TRB_ERROR: + case COMP_BABBLE_DETECTED_ERROR: + case COMP_USB_TRANSACTION_ERROR: + case COMP_STALL_ERROR: + default: + if (ep_id == XDBC_EPID_OUT || ep_id == XDBC_EPID_OUT_INTEL) + xdbc.flags |= XDBC_FLAGS_OUT_STALL; + if (ep_id == XDBC_EPID_IN || ep_id == XDBC_EPID_IN_INTEL) + xdbc.flags |= XDBC_FLAGS_IN_STALL; + + xdbc_trace("endpoint %d stalled\n", ep_id); + break; + } + + if (ep_id == XDBC_EPID_IN || ep_id == XDBC_EPID_IN_INTEL) { + xdbc.flags &= ~XDBC_FLAGS_IN_PROCESS; + xdbc_bulk_transfer(NULL, XDBC_MAX_PACKET, true); + } else if (ep_id == XDBC_EPID_OUT || ep_id == XDBC_EPID_OUT_INTEL) { + xdbc.flags &= ~XDBC_FLAGS_OUT_PROCESS; + } else { + xdbc_trace("invalid endpoint id %d\n", ep_id); + } +} + +static void xdbc_handle_events(void) +{ + struct xdbc_trb *evt_trb; + bool update_erdp = false; + u32 reg; + u8 cmd; + + cmd = read_pci_config_byte(xdbc.bus, xdbc.dev, xdbc.func, PCI_COMMAND); + if (!(cmd & PCI_COMMAND_MASTER)) { + cmd |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY; + write_pci_config_byte(xdbc.bus, xdbc.dev, xdbc.func, PCI_COMMAND, cmd); + } + + if (!(xdbc.flags & XDBC_FLAGS_INITIALIZED)) + return; + + /* Handle external reset events: */ + reg = readl(&xdbc.xdbc_reg->control); + if (!(reg & CTRL_DBC_ENABLE)) { + if (xdbc_handle_external_reset()) { + xdbc_trace("failed to recover connection\n"); + return; + } + } + + /* Handle configure-exit event: */ + reg = readl(&xdbc.xdbc_reg->control); + if (reg & CTRL_DBC_RUN_CHANGE) { + writel(reg, &xdbc.xdbc_reg->control); + if (reg & CTRL_DBC_RUN) + xdbc.flags |= XDBC_FLAGS_CONFIGURED; + else + xdbc.flags &= ~XDBC_FLAGS_CONFIGURED; + } + + /* Handle endpoint stall event: */ + reg = readl(&xdbc.xdbc_reg->control); + if (reg & CTRL_HALT_IN_TR) { + xdbc.flags |= XDBC_FLAGS_IN_STALL; + } else { + xdbc.flags &= ~XDBC_FLAGS_IN_STALL; + if (!(xdbc.flags & XDBC_FLAGS_IN_PROCESS)) + xdbc_bulk_transfer(NULL, XDBC_MAX_PACKET, true); + } + + if (reg & CTRL_HALT_OUT_TR) + xdbc.flags |= XDBC_FLAGS_OUT_STALL; + else + xdbc.flags &= ~XDBC_FLAGS_OUT_STALL; + + /* Handle the events in the event ring: */ + evt_trb = xdbc.evt_ring.dequeue; + while ((le32_to_cpu(evt_trb->field[3]) & TRB_CYCLE) == xdbc.evt_ring.cycle_state) { + /* + * Add a barrier between reading the cycle flag and any + * reads of the event's flags/data below: + */ + rmb(); + + switch ((le32_to_cpu(evt_trb->field[3]) & TRB_TYPE_BITMASK)) { + case TRB_TYPE(TRB_PORT_STATUS): + xdbc_handle_port_status(evt_trb); + break; + case TRB_TYPE(TRB_TRANSFER): + xdbc_handle_tx_event(evt_trb); + break; + default: + break; + } + + ++(xdbc.evt_ring.dequeue); + if (xdbc.evt_ring.dequeue == &xdbc.evt_seg.trbs[TRBS_PER_SEGMENT]) { + xdbc.evt_ring.dequeue = xdbc.evt_seg.trbs; + xdbc.evt_ring.cycle_state ^= 1; + } + + evt_trb = xdbc.evt_ring.dequeue; + update_erdp = true; + } + + /* Update event ring dequeue pointer: */ + if (update_erdp) + xdbc_write64(__pa(xdbc.evt_ring.dequeue), &xdbc.xdbc_reg->erdp); +} + +static int xdbc_bulk_write(const char *bytes, int size) +{ + int ret, timeout = 0; + unsigned long flags; + +retry: + if (in_nmi()) { + if (!raw_spin_trylock_irqsave(&xdbc.lock, flags)) + return -EAGAIN; + } else { + raw_spin_lock_irqsave(&xdbc.lock, flags); + } + + xdbc_handle_events(); + + /* Check completion of the previous request: */ + if ((xdbc.flags & XDBC_FLAGS_OUT_PROCESS) && (timeout < 2000000)) { + raw_spin_unlock_irqrestore(&xdbc.lock, flags); + udelay(100); + timeout += 100; + goto retry; + } + + if (xdbc.flags & XDBC_FLAGS_OUT_PROCESS) { + raw_spin_unlock_irqrestore(&xdbc.lock, flags); + xdbc_trace("previous transfer not completed yet\n"); + + return -ETIMEDOUT; + } + + ret = xdbc_bulk_transfer((void *)bytes, size, false); + raw_spin_unlock_irqrestore(&xdbc.lock, flags); + + return ret; +} + +static void early_xdbc_write(struct console *con, const char *str, u32 n) +{ + /* static variables are zeroed, so buf is always NULL terminated */ + static char buf[XDBC_MAX_PACKET + 1]; + int chunk, ret; + int use_cr = 0; + + if (!xdbc.xdbc_reg) + return; + + while (n > 0) { + for (chunk = 0; chunk < XDBC_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 = xdbc_bulk_write(buf, chunk); + if (ret < 0) + xdbc_trace("missed message {%s}\n", buf); + } + } +} + +static struct console early_xdbc_console = { + .name = "earlyxdbc", + .write = early_xdbc_write, + .flags = CON_PRINTBUFFER, + .index = -1, +}; + +void __init early_xdbc_register_console(void) +{ + if (early_console) + return; + + early_console = &early_xdbc_console; + if (early_console_keep) + early_console->flags &= ~CON_BOOT; + else + early_console->flags |= CON_BOOT; + register_console(early_console); +} + +static void xdbc_unregister_console(void) +{ + if (console_is_registered(&early_xdbc_console)) + unregister_console(&early_xdbc_console); +} + +static int xdbc_scrub_function(void *ptr) +{ + unsigned long flags; + + while (true) { + raw_spin_lock_irqsave(&xdbc.lock, flags); + xdbc_handle_events(); + + if (!(xdbc.flags & XDBC_FLAGS_INITIALIZED)) { + raw_spin_unlock_irqrestore(&xdbc.lock, flags); + break; + } + + raw_spin_unlock_irqrestore(&xdbc.lock, flags); + schedule_timeout_interruptible(1); + } + + xdbc_unregister_console(); + writel(0, &xdbc.xdbc_reg->control); + xdbc_trace("dbc scrub function exits\n"); + + return 0; +} + +static int __init xdbc_init(void) +{ + unsigned long flags; + void __iomem *base; + int ret = 0; + u32 offset; + + if (!(xdbc.flags & XDBC_FLAGS_INITIALIZED)) + return 0; + + /* + * It's time to shut down the DbC, so that the debug + * port can be reused by the host controller: + */ + if (early_xdbc_console.index == -1 || + (early_xdbc_console.flags & CON_BOOT)) { + xdbc_trace("hardware not used anymore\n"); + goto free_and_quit; + } + + base = ioremap(xdbc.xhci_start, xdbc.xhci_length); + if (!base) { + xdbc_trace("failed to remap the io address\n"); + ret = -ENOMEM; + goto free_and_quit; + } + + raw_spin_lock_irqsave(&xdbc.lock, flags); + early_iounmap(xdbc.xhci_base, xdbc.xhci_length); + xdbc.xhci_base = base; + offset = xhci_find_next_ext_cap(xdbc.xhci_base, 0, XHCI_EXT_CAPS_DEBUG); + xdbc.xdbc_reg = (struct xdbc_regs __iomem *)(xdbc.xhci_base + offset); + raw_spin_unlock_irqrestore(&xdbc.lock, flags); + + kthread_run(xdbc_scrub_function, NULL, "%s", "xdbc"); + + return 0; + +free_and_quit: + xdbc_free_ring(&xdbc.evt_ring); + xdbc_free_ring(&xdbc.out_ring); + xdbc_free_ring(&xdbc.in_ring); + memblock_phys_free(xdbc.table_dma, PAGE_SIZE); + memblock_phys_free(xdbc.out_dma, PAGE_SIZE); + writel(0, &xdbc.xdbc_reg->control); + early_iounmap(xdbc.xhci_base, xdbc.xhci_length); + + return ret; +} +subsys_initcall(xdbc_init); diff --git a/drivers/usb/early/xhci-dbc.h b/drivers/usb/early/xhci-dbc.h new file mode 100644 index 000000000..8b4d71de4 --- /dev/null +++ b/drivers/usb/early/xhci-dbc.h @@ -0,0 +1,222 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xhci-dbc.h - xHCI debug capability early driver + * + * Copyright (C) 2016 Intel Corporation + * + * Author: Lu Baolu + */ + +#ifndef __LINUX_XHCI_DBC_H +#define __LINUX_XHCI_DBC_H + +#include +#include + +/* + * xHCI Debug Capability Register interfaces: + */ +struct xdbc_regs { + __le32 capability; + __le32 doorbell; + __le32 ersts; /* Event Ring Segment Table Size*/ + __le32 __reserved_0; /* 0c~0f reserved bits */ + __le64 erstba; /* Event Ring Segment Table Base Address */ + __le64 erdp; /* Event Ring Dequeue Pointer */ + __le32 control; + __le32 status; + __le32 portsc; /* Port status and control */ + __le32 __reserved_1; /* 2b~28 reserved bits */ + __le64 dccp; /* Debug Capability Context Pointer */ + __le32 devinfo1; /* Device Descriptor Info Register 1 */ + __le32 devinfo2; /* Device Descriptor Info Register 2 */ +}; + +#define DEBUG_MAX_BURST(p) (((p) >> 16) & 0xff) + +#define CTRL_DBC_RUN BIT(0) +#define CTRL_PORT_ENABLE BIT(1) +#define CTRL_HALT_OUT_TR BIT(2) +#define CTRL_HALT_IN_TR BIT(3) +#define CTRL_DBC_RUN_CHANGE BIT(4) +#define CTRL_DBC_ENABLE BIT(31) + +#define DCST_DEBUG_PORT(p) (((p) >> 24) & 0xff) + +#define PORTSC_CONN_STATUS BIT(0) +#define PORTSC_CONN_CHANGE BIT(17) +#define PORTSC_RESET_CHANGE BIT(21) +#define PORTSC_LINK_CHANGE BIT(22) +#define PORTSC_CONFIG_CHANGE BIT(23) + +/* + * xHCI Debug Capability data structures: + */ +struct xdbc_trb { + __le32 field[4]; +}; + +struct xdbc_erst_entry { + __le64 seg_addr; + __le32 seg_size; + __le32 __reserved_0; +}; + +struct xdbc_info_context { + __le64 string0; + __le64 manufacturer; + __le64 product; + __le64 serial; + __le32 length; + __le32 __reserved_0[7]; +}; + +struct xdbc_ep_context { + __le32 ep_info1; + __le32 ep_info2; + __le64 deq; + __le32 tx_info; + __le32 __reserved_0[11]; +}; + +struct xdbc_context { + struct xdbc_info_context info; + struct xdbc_ep_context out; + struct xdbc_ep_context in; +}; + +#define XDBC_INFO_CONTEXT_SIZE 48 +#define XDBC_MAX_STRING_LENGTH 64 +#define XDBC_STRING_MANUFACTURER "Linux Foundation" +#define XDBC_STRING_PRODUCT "Linux USB GDB Target" +#define XDBC_STRING_SERIAL "0001" + +struct xdbc_strings { + char string0[XDBC_MAX_STRING_LENGTH]; + char manufacturer[XDBC_MAX_STRING_LENGTH]; + char product[XDBC_MAX_STRING_LENGTH]; + char serial[XDBC_MAX_STRING_LENGTH]; +}; + +#define XDBC_PROTOCOL 1 /* GNU Remote Debug Command Set */ +#define XDBC_VENDOR_ID 0x1d6b /* Linux Foundation 0x1d6b */ +#define XDBC_PRODUCT_ID 0x0011 /* __le16 idProduct; device 0011 */ +#define XDBC_DEVICE_REV 0x0010 /* 0.10 */ + +/* + * xHCI Debug Capability software state structures: + */ +struct xdbc_segment { + struct xdbc_trb *trbs; + dma_addr_t dma; +}; + +#define XDBC_TRBS_PER_SEGMENT 256 + +struct xdbc_ring { + struct xdbc_segment *segment; + struct xdbc_trb *enqueue; + struct xdbc_trb *dequeue; + u32 cycle_state; +}; + +/* + * These are the "Endpoint ID" (also known as "Context Index") values for the + * OUT Transfer Ring and the IN Transfer Ring of a Debug Capability Context data + * structure. + * According to the "eXtensible Host Controller Interface for Universal Serial + * Bus (xHCI)" specification, section "7.6.3.2 Endpoint Contexts and Transfer + * Rings", these should be 0 and 1, and those are the values AMD machines give + * you; but Intel machines seem to use the formula from section "4.5.1 Device + * Context Index", which is supposed to be used for the Device Context only. + * Luckily the values from Intel don't overlap with those from AMD, so we can + * just test for both. + */ +#define XDBC_EPID_OUT 0 +#define XDBC_EPID_IN 1 +#define XDBC_EPID_OUT_INTEL 2 +#define XDBC_EPID_IN_INTEL 3 + +struct xdbc_state { + u16 vendor; + u16 device; + u32 bus; + u32 dev; + u32 func; + void __iomem *xhci_base; + u64 xhci_start; + size_t xhci_length; + int port_number; + + /* DbC register base */ + struct xdbc_regs __iomem *xdbc_reg; + + /* DbC table page */ + dma_addr_t table_dma; + void *table_base; + + /* event ring segment table */ + dma_addr_t erst_dma; + size_t erst_size; + void *erst_base; + + /* event ring segments */ + struct xdbc_ring evt_ring; + struct xdbc_segment evt_seg; + + /* debug capability contexts */ + dma_addr_t dbcc_dma; + size_t dbcc_size; + void *dbcc_base; + + /* descriptor strings */ + dma_addr_t string_dma; + size_t string_size; + void *string_base; + + /* bulk OUT endpoint */ + struct xdbc_ring out_ring; + struct xdbc_segment out_seg; + void *out_buf; + dma_addr_t out_dma; + + /* bulk IN endpoint */ + struct xdbc_ring in_ring; + struct xdbc_segment in_seg; + void *in_buf; + dma_addr_t in_dma; + + u32 flags; + + /* spinlock for early_xdbc_write() reentrancy */ + raw_spinlock_t lock; +}; + +#define XDBC_PCI_MAX_BUSES 256 +#define XDBC_PCI_MAX_DEVICES 32 +#define XDBC_PCI_MAX_FUNCTION 8 + +#define XDBC_TABLE_ENTRY_SIZE 64 +#define XDBC_ERST_ENTRY_NUM 1 +#define XDBC_DBCC_ENTRY_NUM 3 +#define XDBC_STRING_ENTRY_NUM 4 + +/* Bits definitions for xdbc_state.flags: */ +#define XDBC_FLAGS_INITIALIZED BIT(0) +#define XDBC_FLAGS_IN_STALL BIT(1) +#define XDBC_FLAGS_OUT_STALL BIT(2) +#define XDBC_FLAGS_IN_PROCESS BIT(3) +#define XDBC_FLAGS_OUT_PROCESS BIT(4) +#define XDBC_FLAGS_CONFIGURED BIT(5) + +#define XDBC_MAX_PACKET 1024 + +/* Door bell target: */ +#define OUT_EP_DOORBELL 0 +#define IN_EP_DOORBELL 1 +#define DOOR_BELL_TARGET(p) (((p) & 0xff) << 8) + +#define xdbc_read64(regs) xhci_read_64(NULL, (regs)) +#define xdbc_write64(val, regs) xhci_write_64(NULL, (val), (regs)) + +#endif /* __LINUX_XHCI_DBC_H */ diff --git a/drivers/usb/fotg210/Kconfig b/drivers/usb/fotg210/Kconfig new file mode 100644 index 000000000..87a162582 --- /dev/null +++ b/drivers/usb/fotg210/Kconfig @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0 + +config USB_FOTG210 + tristate "Faraday FOTG210 USB2 Dual Role controller" + depends on USB || USB_GADGET + depends on HAS_DMA && HAS_IOMEM + depends on ARCH_GEMINI || COMPILE_TEST + default ARCH_GEMINI + select MFD_SYSCON + help + Faraday FOTG210 is a dual-mode USB controller that can act + in both host controller and peripheral controller mode. + +if USB_FOTG210 + +config USB_FOTG210_HCD + bool "Faraday FOTG210 USB Host Controller support" + depends on USB=y || USB=USB_FOTG210 + help + Faraday FOTG210 is an OTG controller which can be configured as + an USB2.0 host. It is designed to meet USB2.0 EHCI specification + with minor modification. + + To compile this driver as a module, choose M here: the + module will be called fotg210-hcd. + +config USB_FOTG210_UDC + depends on USB_GADGET=y || USB_GADGET=USB_FOTG210 + bool "Faraday FOTG210 USB Peripheral Controller support" + help + Faraday USB2.0 OTG controller which can be configured as + high speed or full speed USB device. This driver supports + Bulk Transfer so far. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "fotg210-udc". + +endif diff --git a/drivers/usb/fotg210/Makefile b/drivers/usb/fotg210/Makefile new file mode 100644 index 000000000..8f5b0fb9b --- /dev/null +++ b/drivers/usb/fotg210/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_USB_FOTG210) += fotg210.o +fotg210-y := fotg210-core.o +fotg210-$(CONFIG_USB_FOTG210_HCD) += fotg210-hcd.o +fotg210-$(CONFIG_USB_FOTG210_UDC) += fotg210-udc.o diff --git a/drivers/usb/fotg210/fotg210-core.c b/drivers/usb/fotg210/fotg210-core.c new file mode 100644 index 000000000..7fb4d4715 --- /dev/null +++ b/drivers/usb/fotg210/fotg210-core.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Central probing code for the FOTG210 dual role driver + * We register one driver for the hardware and then we decide + * whether to proceed with probing the host or the peripheral + * driver. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fotg210.h" + +/* Role Register 0x80 */ +#define FOTG210_RR 0x80 +#define FOTG210_RR_ID BIT(21) /* 1 = B-device, 0 = A-device */ +#define FOTG210_RR_CROLE BIT(20) /* 1 = device, 0 = host */ + +/* + * Gemini-specific initialization function, only executed on the + * Gemini SoC using the global misc control register. + * + * The gemini USB blocks are connected to either Mini-A (host mode) or + * Mini-B (peripheral mode) plugs. There is no role switch support on the + * Gemini SoC, just either-or. + */ +#define GEMINI_GLOBAL_MISC_CTRL 0x30 +#define GEMINI_MISC_USB0_WAKEUP BIT(14) +#define GEMINI_MISC_USB1_WAKEUP BIT(15) +#define GEMINI_MISC_USB0_VBUS_ON BIT(22) +#define GEMINI_MISC_USB1_VBUS_ON BIT(23) +#define GEMINI_MISC_USB0_MINI_B BIT(29) +#define GEMINI_MISC_USB1_MINI_B BIT(30) + +static int fotg210_gemini_init(struct fotg210 *fotg, struct resource *res, + enum usb_dr_mode mode) +{ + struct device *dev = fotg->dev; + struct device_node *np = dev->of_node; + struct regmap *map; + bool wakeup; + u32 mask, val; + int ret; + + map = syscon_regmap_lookup_by_phandle(np, "syscon"); + if (IS_ERR(map)) + return dev_err_probe(dev, PTR_ERR(map), "no syscon\n"); + fotg->map = map; + wakeup = of_property_read_bool(np, "wakeup-source"); + + /* + * Figure out if this is USB0 or USB1 by simply checking the + * physical base address. + */ + mask = 0; + if (res->start == 0x69000000) { + fotg->port = GEMINI_PORT_1; + mask = GEMINI_MISC_USB1_VBUS_ON | GEMINI_MISC_USB1_MINI_B | + GEMINI_MISC_USB1_WAKEUP; + if (mode == USB_DR_MODE_HOST) + val = GEMINI_MISC_USB1_VBUS_ON; + else + val = GEMINI_MISC_USB1_MINI_B; + if (wakeup) + val |= GEMINI_MISC_USB1_WAKEUP; + } else { + fotg->port = GEMINI_PORT_0; + mask = GEMINI_MISC_USB0_VBUS_ON | GEMINI_MISC_USB0_MINI_B | + GEMINI_MISC_USB0_WAKEUP; + if (mode == USB_DR_MODE_HOST) + val = GEMINI_MISC_USB0_VBUS_ON; + else + val = GEMINI_MISC_USB0_MINI_B; + if (wakeup) + val |= GEMINI_MISC_USB0_WAKEUP; + } + + ret = regmap_update_bits(map, GEMINI_GLOBAL_MISC_CTRL, mask, val); + if (ret) { + dev_err(dev, "failed to initialize Gemini PHY\n"); + return ret; + } + + dev_info(dev, "initialized Gemini PHY in %s mode\n", + (mode == USB_DR_MODE_HOST) ? "host" : "gadget"); + return 0; +} + +/** + * fotg210_vbus() - Called by gadget driver to enable/disable VBUS + * @fotg: pointer to a private fotg210 object + * @enable: true to enable VBUS, false to disable VBUS + */ +void fotg210_vbus(struct fotg210 *fotg, bool enable) +{ + u32 mask; + u32 val; + int ret; + + switch (fotg->port) { + case GEMINI_PORT_0: + mask = GEMINI_MISC_USB0_VBUS_ON; + val = enable ? GEMINI_MISC_USB0_VBUS_ON : 0; + break; + case GEMINI_PORT_1: + mask = GEMINI_MISC_USB1_VBUS_ON; + val = enable ? GEMINI_MISC_USB1_VBUS_ON : 0; + break; + default: + return; + } + ret = regmap_update_bits(fotg->map, GEMINI_GLOBAL_MISC_CTRL, mask, val); + if (ret) + dev_err(fotg->dev, "failed to %s VBUS\n", + str_enable_disable(enable)); + dev_info(fotg->dev, "%s: %s VBUS\n", __func__, str_enable_disable(enable)); +} + +static int fotg210_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + enum usb_dr_mode mode; + struct fotg210 *fotg; + u32 val; + int ret; + + fotg = devm_kzalloc(dev, sizeof(*fotg), GFP_KERNEL); + if (!fotg) + return -ENOMEM; + fotg->dev = dev; + + fotg->base = devm_platform_get_and_ioremap_resource(pdev, 0, &fotg->res); + if (IS_ERR(fotg->base)) + return PTR_ERR(fotg->base); + + fotg->pclk = devm_clk_get_optional_enabled(dev, "PCLK"); + if (IS_ERR(fotg->pclk)) + return PTR_ERR(fotg->pclk); + + mode = usb_get_dr_mode(dev); + + if (of_device_is_compatible(dev->of_node, "cortina,gemini-usb")) { + ret = fotg210_gemini_init(fotg, fotg->res, mode); + if (ret) + return ret; + } + + val = readl(fotg->base + FOTG210_RR); + if (mode == USB_DR_MODE_PERIPHERAL) { + if (!(val & FOTG210_RR_CROLE)) + dev_err(dev, "block not in device role\n"); + ret = fotg210_udc_probe(pdev, fotg); + } else { + if (val & FOTG210_RR_CROLE) + dev_err(dev, "block not in host role\n"); + ret = fotg210_hcd_probe(pdev, fotg); + } + + return ret; +} + +static void fotg210_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + enum usb_dr_mode mode; + + mode = usb_get_dr_mode(dev); + + if (mode == USB_DR_MODE_PERIPHERAL) + fotg210_udc_remove(pdev); + else + fotg210_hcd_remove(pdev); +} + +#ifdef CONFIG_OF +static const struct of_device_id fotg210_of_match[] = { + { .compatible = "faraday,fotg200" }, + { .compatible = "faraday,fotg210" }, + /* TODO: can we also handle FUSB220? */ + {}, +}; +MODULE_DEVICE_TABLE(of, fotg210_of_match); +#endif + +static struct platform_driver fotg210_driver = { + .driver = { + .name = "fotg210", + .of_match_table = of_match_ptr(fotg210_of_match), + }, + .probe = fotg210_probe, + .remove = fotg210_remove, +}; + +static int __init fotg210_init(void) +{ + if (IS_ENABLED(CONFIG_USB_FOTG210_HCD) && !usb_disabled()) + fotg210_hcd_init(); + return platform_driver_register(&fotg210_driver); +} +module_init(fotg210_init); + +static void __exit fotg210_cleanup(void) +{ + platform_driver_unregister(&fotg210_driver); + if (IS_ENABLED(CONFIG_USB_FOTG210_HCD)) + fotg210_hcd_cleanup(); +} +module_exit(fotg210_cleanup); + +MODULE_AUTHOR("Yuan-Hsin Chen, Feng-Hsin Chiang"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("FOTG210 Dual Role Controller Driver"); diff --git a/drivers/usb/fotg210/fotg210-hcd.c b/drivers/usb/fotg210/fotg210-hcd.c new file mode 100644 index 000000000..956be5b56 --- /dev/null +++ b/drivers/usb/fotg210/fotg210-hcd.c @@ -0,0 +1,5643 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Faraday FOTG210 EHCI-like driver + * + * Copyright (c) 2013 Faraday Technology Corporation + * + * Author: Yuan-Hsin Chen + * Feng-Hsin Chiang + * Po-Yu Chuang + * + * Most of code borrowed from the Linux-3.7 EHCI driver + */ +#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 "fotg210.h" + +static const char hcd_name[] = "fotg210_hcd"; + +#undef FOTG210_URB_TRACE +#define FOTG210_STATS + +/* magic numbers that can affect system performance */ +#define FOTG210_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define FOTG210_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ +#define FOTG210_TUNE_RL_TT 0 +#define FOTG210_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define FOTG210_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 FOTG210_TUNE_FLS 1 /* (medium) 512-frame schedule */ + +/* 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 link power management(LPM) feature */ +static unsigned int hird; +module_param(hird, int, S_IRUGO); +MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us"); + +#define INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT) + +#include "fotg210-hcd.h" + +#define fotg210_dbg(fotg210, fmt, args...) \ + dev_dbg(fotg210_to_hcd(fotg210)->self.controller, fmt, ## args) +#define fotg210_err(fotg210, fmt, args...) \ + dev_err(fotg210_to_hcd(fotg210)->self.controller, fmt, ## args) +#define fotg210_info(fotg210, fmt, args...) \ + dev_info(fotg210_to_hcd(fotg210)->self.controller, fmt, ## args) +#define fotg210_warn(fotg210, fmt, args...) \ + dev_warn(fotg210_to_hcd(fotg210)->self.controller, fmt, ## args) + +/* 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 fotg210_hcd *fotg210, char *label) +{ + u32 params = fotg210_readl(fotg210, &fotg210->caps->hcs_params); + + fotg210_dbg(fotg210, "%s hcs_params 0x%x ports=%d\n", label, params, + HCS_N_PORTS(params)); +} + +/* 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 fotg210_hcd *fotg210, char *label) +{ + u32 params = fotg210_readl(fotg210, &fotg210->caps->hcc_params); + + fotg210_dbg(fotg210, "%s hcc_params %04x uframes %s%s\n", label, + params, + HCC_PGM_FRAMELISTLEN(params) ? "256/512/1024" : "1024", + HCC_CANPARK(params) ? " park" : ""); +} + +static void __maybe_unused +dbg_qtd(const char *label, struct fotg210_hcd *fotg210, struct fotg210_qtd *qtd) +{ + fotg210_dbg(fotg210, "%s td %p n%08x %08x t%08x p0=%08x\n", label, qtd, + hc32_to_cpup(fotg210, &qtd->hw_next), + hc32_to_cpup(fotg210, &qtd->hw_alt_next), + hc32_to_cpup(fotg210, &qtd->hw_token), + hc32_to_cpup(fotg210, &qtd->hw_buf[0])); + if (qtd->hw_buf[1]) + fotg210_dbg(fotg210, " p1=%08x p2=%08x p3=%08x p4=%08x\n", + hc32_to_cpup(fotg210, &qtd->hw_buf[1]), + hc32_to_cpup(fotg210, &qtd->hw_buf[2]), + hc32_to_cpup(fotg210, &qtd->hw_buf[3]), + hc32_to_cpup(fotg210, &qtd->hw_buf[4])); +} + +static void __maybe_unused +dbg_qh(const char *label, struct fotg210_hcd *fotg210, struct fotg210_qh *qh) +{ + struct fotg210_qh_hw *hw = qh->hw; + + fotg210_dbg(fotg210, "%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", fotg210, (struct fotg210_qtd *) &hw->hw_qtd_next); +} + +static void __maybe_unused +dbg_itd(const char *label, struct fotg210_hcd *fotg210, struct fotg210_itd *itd) +{ + fotg210_dbg(fotg210, "%s[%d] itd %p, next %08x, urb %p\n", label, + itd->frame, itd, hc32_to_cpu(fotg210, itd->hw_next), + itd->urb); + + fotg210_dbg(fotg210, + " trans: %08x %08x %08x %08x %08x %08x %08x %08x\n", + hc32_to_cpu(fotg210, itd->hw_transaction[0]), + hc32_to_cpu(fotg210, itd->hw_transaction[1]), + hc32_to_cpu(fotg210, itd->hw_transaction[2]), + hc32_to_cpu(fotg210, itd->hw_transaction[3]), + hc32_to_cpu(fotg210, itd->hw_transaction[4]), + hc32_to_cpu(fotg210, itd->hw_transaction[5]), + hc32_to_cpu(fotg210, itd->hw_transaction[6]), + hc32_to_cpu(fotg210, itd->hw_transaction[7])); + + fotg210_dbg(fotg210, + " buf: %08x %08x %08x %08x %08x %08x %08x\n", + hc32_to_cpu(fotg210, itd->hw_bufp[0]), + hc32_to_cpu(fotg210, itd->hw_bufp[1]), + hc32_to_cpu(fotg210, itd->hw_bufp[2]), + hc32_to_cpu(fotg210, itd->hw_bufp[3]), + hc32_to_cpu(fotg210, itd->hw_bufp[4]), + hc32_to_cpu(fotg210, itd->hw_bufp[5]), + hc32_to_cpu(fotg210, itd->hw_bufp[6])); + + fotg210_dbg(fotg210, " 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 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", + 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 __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", + 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 %07x %s=%d ithresh=%d%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_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 char *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; + } + + scnprintf(buf, len, "%s%sport:%d status %06x %d sig=%s%s%s%s%s%s%s%s", + label, label[0] ? " " : "", port, status, + status >> 25, /*device address */ + sig, + (status & PORT_RESET) ? " RESET" : "", + (status & PORT_SUSPEND) ? " SUSPEND" : "", + (status & PORT_RESUME) ? " RESUME" : "", + (status & PORT_PEC) ? " PEC" : "", + (status & PORT_PE) ? " PE" : "", + (status & PORT_CSC) ? " CSC" : "", + (status & PORT_CONNECT) ? " CONNECT" : ""); + + return buf; +} + +/* functions have the "wrong" filename when they're output... */ +#define dbg_status(fotg210, label, status) { \ + char _buf[80]; \ + dbg_status_buf(_buf, sizeof(_buf), label, status); \ + fotg210_dbg(fotg210, "%s\n", _buf); \ +} + +#define dbg_cmd(fotg210, label, command) { \ + char _buf[80]; \ + dbg_command_buf(_buf, sizeof(_buf), label, command); \ + fotg210_dbg(fotg210, "%s\n", _buf); \ +} + +#define dbg_port(fotg210, label, port, status) { \ + char _buf[80]; \ + fotg210_dbg(fotg210, "%s\n", \ + dbg_port_buf(_buf, sizeof(_buf), label, port, status));\ +} + +/* 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 *fotg210_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; +}; + +static inline char speed_char(u32 scratch) +{ + switch (scratch & (3 << 12)) { + case QH_FULL_SPEED: + return 'f'; + + case QH_LOW_SPEED: + return 'l'; + + case QH_HIGH_SPEED: + return 'h'; + + default: + return '?'; + } +} + +static inline char token_mark(struct fotg210_hcd *fotg210, __hc32 token) +{ + __u32 v = hc32_to_cpu(fotg210, 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 fotg210_hcd *fotg210, struct fotg210_qh *qh, + char **nextp, unsigned *sizep) +{ + u32 scratch; + u32 hw_curr; + struct fotg210_qtd *td; + unsigned temp; + unsigned size = *sizep; + char *next = *nextp; + char mark; + __le32 list_end = FOTG210_LIST_END(fotg210); + struct fotg210_qh_hw *hw = qh->hw; + + if (hw->hw_qtd_next == list_end) /* NEC does this */ + mark = '@'; + else + mark = token_mark(fotg210, hw->hw_token); + if (mark == '/') { /* qh_alt_next controls qh advance? */ + if ((hw->hw_alt_next & QTD_MASK(fotg210)) == + fotg210->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(fotg210, &hw->hw_info1); + hw_curr = (mark == '*') ? hc32_to_cpup(fotg210, &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(fotg210, &hw->hw_info2), + hc32_to_cpup(fotg210, &hw->hw_token), mark, + (cpu_to_hc32(fotg210, QTD_TOGGLE) & hw->hw_token) + ? "data1" : "data0", + (hc32_to_cpup(fotg210, &hw->hw_alt_next) >> 1) & 0x0f); + size -= temp; + next += temp; + + /* hc may be modifying the list as we read it ... */ + list_for_each_entry(td, &qh->qtd_list, qtd_list) { + scratch = hc32_to_cpup(fotg210, &td->hw_token); + mark = ' '; + if (hw_curr == td->qtd_dma) + mark = '*'; + else if (hw->hw_qtd_next == cpu_to_hc32(fotg210, td->qtd_dma)) + mark = '+'; + else if (QTD_LENGTH(scratch)) { + if (td->hw_alt_next == fotg210->async->hw->hw_alt_next) + mark = '#'; + else if (td->hw_alt_next != list_end) + mark = '/'; + } + temp = scnprintf(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); + size -= temp; + next += temp; + } + + temp = scnprintf(next, size, "\n"); + + size -= temp; + next += temp; + + *sizep = size; + *nextp = next; +} + +static ssize_t fill_async_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct fotg210_hcd *fotg210; + unsigned long flags; + unsigned temp, size; + char *next; + struct fotg210_qh *qh; + + hcd = bus_to_hcd(buf->bus); + fotg210 = hcd_to_fotg210(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(&fotg210->lock, flags); + for (qh = fotg210->async->qh_next.qh; size > 0 && qh; + qh = qh->qh_next.qh) + qh_lines(fotg210, qh, &next, &size); + if (fotg210->async_unlink && size > 0) { + temp = scnprintf(next, size, "\nunlink =\n"); + size -= temp; + next += temp; + + for (qh = fotg210->async_unlink; size > 0 && qh; + qh = qh->unlink_next) + qh_lines(fotg210, qh, &next, &size); + } + spin_unlock_irqrestore(&fotg210->lock, flags); + + return strlen(buf->output_buf); +} + +/* count tds, get ep direction */ +static unsigned output_buf_tds_dir(char *buf, struct fotg210_hcd *fotg210, + struct fotg210_qh_hw *hw, struct fotg210_qh *qh, unsigned size) +{ + u32 scratch = hc32_to_cpup(fotg210, &hw->hw_info1); + struct fotg210_qtd *qtd; + char *type = ""; + unsigned temp = 0; + + /* count tds, get ep direction */ + list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { + temp++; + switch ((hc32_to_cpu(fotg210, qtd->hw_token) >> 8) & 0x03) { + case 0: + type = "out"; + continue; + case 1: + type = "in"; + continue; + } + } + + return scnprintf(buf, size, "(%c%d ep%d%s [%d/%d] q%d p%d)", + speed_char(scratch), scratch & 0x007f, + (scratch >> 8) & 0x000f, type, qh->usecs, + qh->c_usecs, temp, (scratch >> 16) & 0x7ff); +} + +#define DBG_SCHED_LIMIT 64 +static ssize_t fill_periodic_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct fotg210_hcd *fotg210; + unsigned long flags; + union fotg210_shadow p, *seen; + unsigned temp, size, seen_count; + char *next; + unsigned i; + __hc32 tag; + + seen = kmalloc_objs(*seen, DBG_SCHED_LIMIT, GFP_ATOMIC); + if (!seen) + return 0; + + seen_count = 0; + + hcd = bus_to_hcd(buf->bus); + fotg210 = hcd_to_fotg210(hcd); + next = buf->output_buf; + size = buf->alloc_size; + + temp = scnprintf(next, size, "size = %d\n", fotg210->periodic_size); + size -= temp; + next += temp; + + /* dump a snapshot of the periodic schedule. + * iso changes, interrupt usually doesn't. + */ + spin_lock_irqsave(&fotg210->lock, flags); + for (i = 0; i < fotg210->periodic_size; i++) { + p = fotg210->pshadow[i]; + if (likely(!p.ptr)) + continue; + + tag = Q_NEXT_TYPE(fotg210, fotg210->periodic[i]); + + temp = scnprintf(next, size, "%4d: ", i); + size -= temp; + next += temp; + + do { + struct fotg210_qh_hw *hw; + + switch (hc32_to_cpu(fotg210, tag)) { + case Q_TYPE_QH: + hw = p.qh->hw; + temp = scnprintf(next, size, " qh%d-%04x/%p", + p.qh->period, + hc32_to_cpup(fotg210, + &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) { + temp = output_buf_tds_dir(next, + fotg210, hw, + p.qh, size); + + if (seen_count < DBG_SCHED_LIMIT) + seen[seen_count++].qh = p.qh; + } else + temp = 0; + tag = Q_NEXT_TYPE(fotg210, 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(fotg210, 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(fotg210, p.itd->hw_next); + p = p.itd->itd_next; + break; + } + size -= temp; + next += temp; + } while (p.ptr); + + temp = scnprintf(next, size, "\n"); + size -= temp; + next += temp; + } + spin_unlock_irqrestore(&fotg210->lock, flags); + kfree(seen); + + return buf->alloc_size - size; +} +#undef DBG_SCHED_LIMIT + +static const char *rh_state_string(struct fotg210_hcd *fotg210) +{ + switch (fotg210->rh_state) { + case FOTG210_RH_HALTED: + return "halted"; + case FOTG210_RH_SUSPENDED: + return "suspended"; + case FOTG210_RH_RUNNING: + return "running"; + case FOTG210_RH_STOPPING: + return "stopping"; + } + return "?"; +} + +static ssize_t fill_registers_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct fotg210_hcd *fotg210; + unsigned long flags; + unsigned temp, size, i; + char *next, scratch[80]; + static const char fmt[] = "%*s\n"; + static const char label[] = ""; + + hcd = bus_to_hcd(buf->bus); + fotg210 = hcd_to_fotg210(hcd); + next = buf->output_buf; + size = buf->alloc_size; + + spin_lock_irqsave(&fotg210->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(fotg210, fotg210_readl(fotg210, + &fotg210->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(fotg210)); + size -= temp; + next += temp; + + /* FIXME interpret both types of params */ + i = fotg210_readl(fotg210, &fotg210->caps->hcs_params); + temp = scnprintf(next, size, "structural params 0x%08x\n", i); + size -= temp; + next += temp; + + i = fotg210_readl(fotg210, &fotg210->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, + fotg210_readl(fotg210, &fotg210->regs->status)); + temp = scnprintf(next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = dbg_command_buf(scratch, sizeof(scratch), label, + fotg210_readl(fotg210, &fotg210->regs->command)); + temp = scnprintf(next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = dbg_intr_buf(scratch, sizeof(scratch), label, + fotg210_readl(fotg210, &fotg210->regs->intr_enable)); + temp = scnprintf(next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = scnprintf(next, size, "uframe %04x\n", + fotg210_read_frame_index(fotg210)); + size -= temp; + next += temp; + + if (fotg210->async_unlink) { + temp = scnprintf(next, size, "async unlink qh %p\n", + fotg210->async_unlink); + size -= temp; + next += temp; + } + +#ifdef FOTG210_STATS + temp = scnprintf(next, size, + "irq normal %ld err %ld iaa %ld(lost %ld)\n", + fotg210->stats.normal, fotg210->stats.error, + fotg210->stats.iaa, fotg210->stats.lost_iaa); + size -= temp; + next += temp; + + temp = scnprintf(next, size, "complete %ld unlink %ld\n", + fotg210->stats.complete, fotg210->stats.unlink); + size -= temp; + next += temp; +#endif + +done: + spin_unlock_irqrestore(&fotg210->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_obj(struct debug_buffer); + + 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 fotg210_hcd *fotg210) +{ + struct usb_bus *bus = &fotg210_to_hcd(fotg210)->self; + struct dentry *root; + + root = debugfs_create_dir(bus->bus_name, fotg210_debug_root); + + debugfs_create_file("async", S_IRUGO, root, bus, &debug_async_fops); + debugfs_create_file("periodic", S_IRUGO, root, bus, + &debug_periodic_fops); + debugfs_create_file("registers", S_IRUGO, root, bus, + &debug_registers_fops); +} + +static inline void remove_debug_files(struct fotg210_hcd *fotg210) +{ + struct usb_bus *bus = &fotg210_to_hcd(fotg210)->self; + + debugfs_lookup_and_remove(bus->bus_name, fotg210_debug_root); +} + +/* 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 fotg210_hcd *fotg210, void __iomem *ptr, + u32 mask, u32 done, int usec) +{ + u32 result; + int ret; + + ret = readl_poll_timeout_atomic(ptr, result, + ((result & mask) == done || + result == U32_MAX), 1, usec); + if (result == U32_MAX) /* card removed */ + return -ENODEV; + + return ret; +} + +/* 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 fotg210_halt(struct fotg210_hcd *fotg210) +{ + u32 temp; + + spin_lock_irq(&fotg210->lock); + + /* disable any irqs left enabled by previous code */ + fotg210_writel(fotg210, 0, &fotg210->regs->intr_enable); + + /* + * This routine gets called during probe before fotg210->command + * has been initialized, so we can't rely on its value. + */ + fotg210->command &= ~CMD_RUN; + temp = fotg210_readl(fotg210, &fotg210->regs->command); + temp &= ~(CMD_RUN | CMD_IAAD); + fotg210_writel(fotg210, temp, &fotg210->regs->command); + + spin_unlock_irq(&fotg210->lock); + synchronize_irq(fotg210_to_hcd(fotg210)->irq); + + return handshake(fotg210, &fotg210->regs->status, + STS_HALT, STS_HALT, 16 * 125); +} + +/* Reset a non-running (STS_HALT == 1) controller. + * Must be called with interrupts enabled and the lock not held. + */ +static int fotg210_reset(struct fotg210_hcd *fotg210) +{ + int retval; + u32 command = fotg210_readl(fotg210, &fotg210->regs->command); + + /* If the EHCI debug controller is active, special care must be + * taken before and after a host controller reset + */ + if (fotg210->debug && !dbgp_reset_prep(fotg210_to_hcd(fotg210))) + fotg210->debug = NULL; + + command |= CMD_RESET; + dbg_cmd(fotg210, "reset", command); + fotg210_writel(fotg210, command, &fotg210->regs->command); + fotg210->rh_state = FOTG210_RH_HALTED; + fotg210->next_statechange = jiffies; + retval = handshake(fotg210, &fotg210->regs->command, + CMD_RESET, 0, 250 * 1000); + + if (retval) + return retval; + + if (fotg210->debug) + dbgp_external_startup(fotg210_to_hcd(fotg210)); + + fotg210->port_c_suspend = fotg210->suspended_ports = + fotg210->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 fotg210_quiesce(struct fotg210_hcd *fotg210) +{ + u32 temp; + + if (fotg210->rh_state != FOTG210_RH_RUNNING) + return; + + /* wait for any schedule enables/disables to take effect */ + temp = (fotg210->command << 10) & (STS_ASS | STS_PSS); + handshake(fotg210, &fotg210->regs->status, STS_ASS | STS_PSS, temp, + 16 * 125); + + /* then disable anything that's still active */ + spin_lock_irq(&fotg210->lock); + fotg210->command &= ~(CMD_ASE | CMD_PSE); + fotg210_writel(fotg210, fotg210->command, &fotg210->regs->command); + spin_unlock_irq(&fotg210->lock); + + /* hardware can take 16 microframes to turn off ... */ + handshake(fotg210, &fotg210->regs->status, STS_ASS | STS_PSS, 0, + 16 * 125); +} + +static void end_unlink_async(struct fotg210_hcd *fotg210); +static void unlink_empty_async(struct fotg210_hcd *fotg210); +static void fotg210_work(struct fotg210_hcd *fotg210); +static void start_unlink_intr(struct fotg210_hcd *fotg210, + struct fotg210_qh *qh); +static void end_unlink_intr(struct fotg210_hcd *fotg210, struct fotg210_qh *qh); + +/* Set a bit in the USBCMD register */ +static void fotg210_set_command_bit(struct fotg210_hcd *fotg210, u32 bit) +{ + fotg210->command |= bit; + fotg210_writel(fotg210, fotg210->command, &fotg210->regs->command); + + /* unblock posted write */ + fotg210_readl(fotg210, &fotg210->regs->command); +} + +/* Clear a bit in the USBCMD register */ +static void fotg210_clear_command_bit(struct fotg210_hcd *fotg210, u32 bit) +{ + fotg210->command &= ~bit; + fotg210_writel(fotg210, fotg210->command, &fotg210->regs->command); + + /* unblock posted write */ + fotg210_readl(fotg210, &fotg210->regs->command); +} + +/* EHCI timer support... Now using hrtimers. + * + * Lots of different events are triggered from fotg210->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 + * fotg210->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 + * fotg210->next_hrtimer_event. Whenever fotg210->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 fotg210_hrtimer_event in fotg210.h. + */ +static unsigned event_delays_ns[] = { + 1 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_POLL_ASS */ + 1 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_POLL_PSS */ + 1 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_POLL_DEAD */ + 1125 * NSEC_PER_USEC, /* FOTG210_HRTIMER_UNLINK_INTR */ + 2 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_FREE_ITDS */ + 6 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_ASYNC_UNLINKS */ + 10 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_IAA_WATCHDOG */ + 10 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_DISABLE_PERIODIC */ + 15 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_DISABLE_ASYNC */ + 100 * NSEC_PER_MSEC, /* FOTG210_HRTIMER_IO_WATCHDOG */ +}; + +/* Enable a pending hrtimer event */ +static void fotg210_enable_event(struct fotg210_hcd *fotg210, unsigned event, + bool resched) +{ + ktime_t *timeout = &fotg210->hr_timeouts[event]; + + if (resched) + *timeout = ktime_add(ktime_get(), event_delays_ns[event]); + fotg210->enabled_hrtimer_events |= (1 << event); + + /* Track only the lowest-numbered pending event */ + if (event < fotg210->next_hrtimer_event) { + fotg210->next_hrtimer_event = event; + hrtimer_start_range_ns(&fotg210->hrtimer, *timeout, + NSEC_PER_MSEC, HRTIMER_MODE_ABS); + } +} + + +/* Poll the STS_ASS status bit; see when it agrees with CMD_ASE */ +static void fotg210_poll_ASS(struct fotg210_hcd *fotg210) +{ + unsigned actual, want; + + /* Don't enable anything if the controller isn't running (e.g., died) */ + if (fotg210->rh_state != FOTG210_RH_RUNNING) + return; + + want = (fotg210->command & CMD_ASE) ? STS_ASS : 0; + actual = fotg210_readl(fotg210, &fotg210->regs->status) & STS_ASS; + + if (want != actual) { + + /* Poll again later, but give up after about 20 ms */ + if (fotg210->ASS_poll_count++ < 20) { + fotg210_enable_event(fotg210, FOTG210_HRTIMER_POLL_ASS, + true); + return; + } + fotg210_dbg(fotg210, "Waited too long for the async schedule status (%x/%x), giving up\n", + want, actual); + } + fotg210->ASS_poll_count = 0; + + /* The status is up-to-date; restart or stop the schedule as needed */ + if (want == 0) { /* Stopped */ + if (fotg210->async_count > 0) + fotg210_set_command_bit(fotg210, CMD_ASE); + + } else { /* Running */ + if (fotg210->async_count == 0) { + + /* Turn off the schedule after a while */ + fotg210_enable_event(fotg210, + FOTG210_HRTIMER_DISABLE_ASYNC, + true); + } + } +} + +/* Turn off the async schedule after a brief delay */ +static void fotg210_disable_ASE(struct fotg210_hcd *fotg210) +{ + fotg210_clear_command_bit(fotg210, CMD_ASE); +} + + +/* Poll the STS_PSS status bit; see when it agrees with CMD_PSE */ +static void fotg210_poll_PSS(struct fotg210_hcd *fotg210) +{ + unsigned actual, want; + + /* Don't do anything if the controller isn't running (e.g., died) */ + if (fotg210->rh_state != FOTG210_RH_RUNNING) + return; + + want = (fotg210->command & CMD_PSE) ? STS_PSS : 0; + actual = fotg210_readl(fotg210, &fotg210->regs->status) & STS_PSS; + + if (want != actual) { + + /* Poll again later, but give up after about 20 ms */ + if (fotg210->PSS_poll_count++ < 20) { + fotg210_enable_event(fotg210, FOTG210_HRTIMER_POLL_PSS, + true); + return; + } + fotg210_dbg(fotg210, "Waited too long for the periodic schedule status (%x/%x), giving up\n", + want, actual); + } + fotg210->PSS_poll_count = 0; + + /* The status is up-to-date; restart or stop the schedule as needed */ + if (want == 0) { /* Stopped */ + if (fotg210->periodic_count > 0) + fotg210_set_command_bit(fotg210, CMD_PSE); + + } else { /* Running */ + if (fotg210->periodic_count == 0) { + + /* Turn off the schedule after a while */ + fotg210_enable_event(fotg210, + FOTG210_HRTIMER_DISABLE_PERIODIC, + true); + } + } +} + +/* Turn off the periodic schedule after a brief delay */ +static void fotg210_disable_PSE(struct fotg210_hcd *fotg210) +{ + fotg210_clear_command_bit(fotg210, CMD_PSE); +} + + +/* Poll the STS_HALT status bit; see when a dead controller stops */ +static void fotg210_handle_controller_death(struct fotg210_hcd *fotg210) +{ + if (!(fotg210_readl(fotg210, &fotg210->regs->status) & STS_HALT)) { + + /* Give up after a few milliseconds */ + if (fotg210->died_poll_count++ < 5) { + /* Try again later */ + fotg210_enable_event(fotg210, + FOTG210_HRTIMER_POLL_DEAD, true); + return; + } + fotg210_warn(fotg210, "Waited too long for the controller to stop, giving up\n"); + } + + /* Clean up the mess */ + fotg210->rh_state = FOTG210_RH_HALTED; + fotg210_writel(fotg210, 0, &fotg210->regs->intr_enable); + fotg210_work(fotg210); + end_unlink_async(fotg210); + + /* 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 fotg210_handle_intr_unlinks(struct fotg210_hcd *fotg210) +{ + bool stopped = (fotg210->rh_state < FOTG210_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. + */ + fotg210->intr_unlinking = true; + while (fotg210->intr_unlink) { + struct fotg210_qh *qh = fotg210->intr_unlink; + + if (!stopped && qh->unlink_cycle == fotg210->intr_unlink_cycle) + break; + fotg210->intr_unlink = qh->unlink_next; + qh->unlink_next = NULL; + end_unlink_intr(fotg210, qh); + } + + /* Handle remaining entries later */ + if (fotg210->intr_unlink) { + fotg210_enable_event(fotg210, FOTG210_HRTIMER_UNLINK_INTR, + true); + ++fotg210->intr_unlink_cycle; + } + fotg210->intr_unlinking = false; +} + + +/* Start another free-iTDs/siTDs cycle */ +static void start_free_itds(struct fotg210_hcd *fotg210) +{ + if (!(fotg210->enabled_hrtimer_events & + BIT(FOTG210_HRTIMER_FREE_ITDS))) { + fotg210->last_itd_to_free = list_entry( + fotg210->cached_itd_list.prev, + struct fotg210_itd, itd_list); + fotg210_enable_event(fotg210, FOTG210_HRTIMER_FREE_ITDS, true); + } +} + +/* Wait for controller to stop using old iTDs and siTDs */ +static void end_free_itds(struct fotg210_hcd *fotg210) +{ + struct fotg210_itd *itd, *n; + + if (fotg210->rh_state < FOTG210_RH_RUNNING) + fotg210->last_itd_to_free = NULL; + + list_for_each_entry_safe(itd, n, &fotg210->cached_itd_list, itd_list) { + list_del(&itd->itd_list); + dma_pool_free(fotg210->itd_pool, itd, itd->itd_dma); + if (itd == fotg210->last_itd_to_free) + break; + } + + if (!list_empty(&fotg210->cached_itd_list)) + start_free_itds(fotg210); +} + + +/* Handle lost (or very late) IAA interrupts */ +static void fotg210_iaa_watchdog(struct fotg210_hcd *fotg210) +{ + if (fotg210->rh_state != FOTG210_RH_RUNNING) + return; + + /* + * 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 (fotg210->async_iaa) { + u32 cmd, status; + + /* 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 = fotg210_readl(fotg210, &fotg210->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 = fotg210_readl(fotg210, &fotg210->regs->status); + if ((status & STS_IAA) || !(cmd & CMD_IAAD)) { + INCR(fotg210->stats.lost_iaa); + fotg210_writel(fotg210, STS_IAA, + &fotg210->regs->status); + } + + fotg210_dbg(fotg210, "IAA watchdog: status %x cmd %x\n", + status, cmd); + end_unlink_async(fotg210); + } +} + + +/* Enable the I/O watchdog, if appropriate */ +static void turn_on_io_watchdog(struct fotg210_hcd *fotg210) +{ + /* Not needed if the controller isn't running or it's already enabled */ + if (fotg210->rh_state != FOTG210_RH_RUNNING || + (fotg210->enabled_hrtimer_events & + BIT(FOTG210_HRTIMER_IO_WATCHDOG))) + return; + + /* + * Isochronous transfers always need the watchdog. + * For other sorts we use it only if the flag is set. + */ + if (fotg210->isoc_count > 0 || (fotg210->need_io_watchdog && + fotg210->async_count + fotg210->intr_count > 0)) + fotg210_enable_event(fotg210, FOTG210_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 fotg210_hrtimer_event in fotg210.h. + */ +static void (*event_handlers[])(struct fotg210_hcd *) = { + fotg210_poll_ASS, /* FOTG210_HRTIMER_POLL_ASS */ + fotg210_poll_PSS, /* FOTG210_HRTIMER_POLL_PSS */ + fotg210_handle_controller_death, /* FOTG210_HRTIMER_POLL_DEAD */ + fotg210_handle_intr_unlinks, /* FOTG210_HRTIMER_UNLINK_INTR */ + end_free_itds, /* FOTG210_HRTIMER_FREE_ITDS */ + unlink_empty_async, /* FOTG210_HRTIMER_ASYNC_UNLINKS */ + fotg210_iaa_watchdog, /* FOTG210_HRTIMER_IAA_WATCHDOG */ + fotg210_disable_PSE, /* FOTG210_HRTIMER_DISABLE_PERIODIC */ + fotg210_disable_ASE, /* FOTG210_HRTIMER_DISABLE_ASYNC */ + fotg210_work, /* FOTG210_HRTIMER_IO_WATCHDOG */ +}; + +static enum hrtimer_restart fotg210_hrtimer_func(struct hrtimer *t) +{ + struct fotg210_hcd *fotg210 = + container_of(t, struct fotg210_hcd, hrtimer); + ktime_t now; + unsigned long events; + unsigned long flags; + unsigned e; + + spin_lock_irqsave(&fotg210->lock, flags); + + events = fotg210->enabled_hrtimer_events; + fotg210->enabled_hrtimer_events = 0; + fotg210->next_hrtimer_event = FOTG210_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, FOTG210_HRTIMER_NUM_EVENTS) { + if (ktime_compare(now, fotg210->hr_timeouts[e]) >= 0) + event_handlers[e](fotg210); + else + fotg210_enable_event(fotg210, e, false); + } + + spin_unlock_irqrestore(&fotg210->lock, flags); + return HRTIMER_NORESTART; +} + +#define fotg210_bus_suspend NULL +#define fotg210_bus_resume NULL + +static int check_reset_complete(struct fotg210_hcd *fotg210, 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! */ + fotg210_dbg(fotg210, "Failed to enable port %d on root hub TT\n", + index + 1); + else + fotg210_dbg(fotg210, "port %d reset complete, port enabled\n", + index + 1); + + return port_status; +} + + +/* build "status change" packet (one or two bytes) from HC registers */ + +static int fotg210_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); + u32 temp, status; + u32 mask; + int retval = 1; + unsigned long flags; + + /* init status to no-changes */ + buf[0] = 0; + + /* Inform the core about resumes-in-progress by returning + * a non-zero value even if there are no status changes. + */ + status = fotg210->resuming_ports; + + 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(&fotg210->lock, flags); + + temp = fotg210_readl(fotg210, &fotg210->regs->port_status); + + /* + * Return status information even for ports with OWNER set. + * Otherwise hub_wq 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(0, &fotg210->port_c_suspend) || + (fotg210->reset_done[0] && + time_after_eq(jiffies, fotg210->reset_done[0]))) { + buf[0] |= 1 << 1; + status = STS_PCD; + } + /* FIXME autosuspend idle root hubs */ + spin_unlock_irqrestore(&fotg210->lock, flags); + return status ? retval : 0; +} + +static void fotg210_hub_descriptor(struct fotg210_hcd *fotg210, + struct usb_hub_descriptor *desc) +{ + int ports = HCS_N_PORTS(fotg210->hcs_params); + u16 temp; + + desc->bDescriptorType = USB_DT_HUB; + desc->bPwrOn2PwrGood = 10; /* fotg210 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 = HUB_CHAR_INDV_PORT_OCPM; /* per-port overcurrent reporting */ + temp |= HUB_CHAR_NO_LPSM; /* no power switching */ + desc->wHubCharacteristics = cpu_to_le16(temp); +} + +static int fotg210_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); + int ports = HCS_N_PORTS(fotg210->hcs_params); + u32 __iomem *status_reg = &fotg210->regs->port_status; + 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(&fotg210->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 = fotg210_readl(fotg210, status_reg); + temp &= ~PORT_RWC_BITS; + + /* + * Even if OWNER is set, so the port is owned by the + * companion controller, hub_wq needs to be able to clear + * the port-change status bits (especially + * USB_PORT_STAT_C_CONNECTION). + */ + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + fotg210_writel(fotg210, temp & ~PORT_PE, status_reg); + break; + case USB_PORT_FEAT_C_ENABLE: + fotg210_writel(fotg210, temp | PORT_PEC, status_reg); + break; + case USB_PORT_FEAT_SUSPEND: + if (temp & PORT_RESET) + goto error; + if (!(temp & PORT_SUSPEND)) + break; + if ((temp & PORT_PE) == 0) + goto error; + + /* resume signaling for 20 msec */ + fotg210_writel(fotg210, temp | PORT_RESUME, status_reg); + fotg210->reset_done[wIndex] = jiffies + + msecs_to_jiffies(USB_RESUME_TIMEOUT); + break; + case USB_PORT_FEAT_C_SUSPEND: + clear_bit(wIndex, &fotg210->port_c_suspend); + break; + case USB_PORT_FEAT_C_CONNECTION: + fotg210_writel(fotg210, temp | PORT_CSC, status_reg); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + fotg210_writel(fotg210, temp | OTGISR_OVC, + &fotg210->regs->otgisr); + break; + case USB_PORT_FEAT_C_RESET: + /* GetPortStatus clears reset */ + break; + default: + goto error; + } + fotg210_readl(fotg210, &fotg210->regs->command); + break; + case GetHubDescriptor: + fotg210_hub_descriptor(fotg210, (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 = fotg210_readl(fotg210, 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; + + temp1 = fotg210_readl(fotg210, &fotg210->regs->otgisr); + if (temp1 & OTGISR_OVC) + status |= USB_PORT_STAT_C_OVERCURRENT << 16; + + /* whoever resumes must GetPortStatus to complete it!! */ + if (temp & PORT_RESUME) { + + /* Remote Wakeup received? */ + if (!fotg210->reset_done[wIndex]) { + /* resume signaling for 20 msec */ + fotg210->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + /* check the port again */ + mod_timer(&fotg210_to_hcd(fotg210)->rh_timer, + fotg210->reset_done[wIndex]); + } + + /* resume completed? */ + else if (time_after_eq(jiffies, + fotg210->reset_done[wIndex])) { + clear_bit(wIndex, &fotg210->suspended_ports); + set_bit(wIndex, &fotg210->port_c_suspend); + fotg210->reset_done[wIndex] = 0; + + /* stop resume signaling */ + temp = fotg210_readl(fotg210, status_reg); + fotg210_writel(fotg210, temp & + ~(PORT_RWC_BITS | PORT_RESUME), + status_reg); + clear_bit(wIndex, &fotg210->resuming_ports); + retval = handshake(fotg210, status_reg, + PORT_RESUME, 0, 2000);/* 2ms */ + if (retval != 0) { + fotg210_err(fotg210, + "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, + fotg210->reset_done[wIndex])) { + status |= USB_PORT_STAT_C_RESET << 16; + fotg210->reset_done[wIndex] = 0; + clear_bit(wIndex, &fotg210->resuming_ports); + + /* force reset to complete */ + fotg210_writel(fotg210, + 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(fotg210, status_reg, + PORT_RESET, 0, 1000); + if (retval != 0) { + fotg210_err(fotg210, "port %d reset error %d\n", + wIndex + 1, retval); + goto error; + } + + /* see what we found out */ + temp = check_reset_complete(fotg210, wIndex, status_reg, + fotg210_readl(fotg210, status_reg)); + + /* restart schedule */ + fotg210->command |= CMD_RUN; + fotg210_writel(fotg210, fotg210->command, &fotg210->regs->command); + } + + if (!(temp & (PORT_RESUME|PORT_RESET))) { + fotg210->reset_done[wIndex] = 0; + clear_bit(wIndex, &fotg210->resuming_ports); + } + + /* transfer dedicated ports to the companion hc */ + if ((temp & PORT_CONNECT) && + test_bit(wIndex, &fotg210->companion_ports)) { + temp &= ~PORT_RWC_BITS; + fotg210_writel(fotg210, temp, status_reg); + fotg210_dbg(fotg210, "port %d --> companion\n", + wIndex + 1); + temp = fotg210_readl(fotg210, status_reg); + } + + /* + * Even if OWNER is set, there's no harm letting hub_wq + * see the wPortStatus values (they should all be 0 except + * for PORT_POWER anyway). + */ + + if (temp & PORT_CONNECT) { + status |= USB_PORT_STAT_CONNECTION; + status |= fotg210_port_speed(fotg210, 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, &fotg210->suspended_ports)) { + clear_bit(wIndex, &fotg210->suspended_ports); + clear_bit(wIndex, &fotg210->resuming_ports); + fotg210->reset_done[wIndex] = 0; + if (temp & PORT_PE) + set_bit(wIndex, &fotg210->port_c_suspend); + } + + temp1 = fotg210_readl(fotg210, &fotg210->regs->otgisr); + if (temp1 & OTGISR_OVC) + status |= USB_PORT_STAT_OVERCURRENT; + if (temp & PORT_RESET) + status |= USB_PORT_STAT_RESET; + if (test_bit(wIndex, &fotg210->port_c_suspend)) + status |= USB_PORT_STAT_C_SUSPEND << 16; + + if (status & ~0xffff) /* only if wPortChange is interesting */ + dbg_port(fotg210, "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 (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = fotg210_readl(fotg210, status_reg); + temp &= ~PORT_RWC_BITS; + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + 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 + */ + fotg210_writel(fotg210, temp | PORT_SUSPEND, + status_reg); + set_bit(wIndex, &fotg210->suspended_ports); + 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. + */ + fotg210_dbg(fotg210, "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 + */ + fotg210->reset_done[wIndex] = jiffies + + msecs_to_jiffies(50); + fotg210_writel(fotg210, 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(&fotg210->lock, flags); + fotg210_quiesce(fotg210); + spin_lock_irqsave(&fotg210->lock, flags); + + /* Put all enabled ports into suspend */ + temp = fotg210_readl(fotg210, status_reg) & + ~PORT_RWC_BITS; + if (temp & PORT_PE) + fotg210_writel(fotg210, temp | PORT_SUSPEND, + status_reg); + + spin_unlock_irqrestore(&fotg210->lock, flags); + fotg210_halt(fotg210); + spin_lock_irqsave(&fotg210->lock, flags); + + temp = fotg210_readl(fotg210, status_reg); + temp |= selector << 16; + fotg210_writel(fotg210, temp, status_reg); + break; + + default: + goto error; + } + fotg210_readl(fotg210, &fotg210->regs->command); + break; + + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore(&fotg210->lock, flags); + return retval; +} + +static void __maybe_unused fotg210_relinquish_port(struct usb_hcd *hcd, + int portnum) +{ + return; +} + +static int __maybe_unused fotg210_port_handed_over(struct usb_hcd *hcd, + int portnum) +{ + return 0; +} + +/* 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 fotg210_qtd_init(struct fotg210_hcd *fotg210, + struct fotg210_qtd *qtd, dma_addr_t dma) +{ + memset(qtd, 0, sizeof(*qtd)); + qtd->qtd_dma = dma; + qtd->hw_token = cpu_to_hc32(fotg210, QTD_STS_HALT); + qtd->hw_next = FOTG210_LIST_END(fotg210); + qtd->hw_alt_next = FOTG210_LIST_END(fotg210); + INIT_LIST_HEAD(&qtd->qtd_list); +} + +static struct fotg210_qtd *fotg210_qtd_alloc(struct fotg210_hcd *fotg210, + gfp_t flags) +{ + struct fotg210_qtd *qtd; + dma_addr_t dma; + + qtd = dma_pool_alloc(fotg210->qtd_pool, flags, &dma); + if (qtd != NULL) + fotg210_qtd_init(fotg210, qtd, dma); + + return qtd; +} + +static inline void fotg210_qtd_free(struct fotg210_hcd *fotg210, + struct fotg210_qtd *qtd) +{ + dma_pool_free(fotg210->qtd_pool, qtd, qtd->qtd_dma); +} + + +static void qh_destroy(struct fotg210_hcd *fotg210, struct fotg210_qh *qh) +{ + /* clean qtds first, and know this is not linked */ + if (!list_empty(&qh->qtd_list) || qh->qh_next.ptr) { + fotg210_dbg(fotg210, "unused qh not empty!\n"); + BUG(); + } + if (qh->dummy) + fotg210_qtd_free(fotg210, qh->dummy); + dma_pool_free(fotg210->qh_pool, qh->hw, qh->qh_dma); + kfree(qh); +} + +static struct fotg210_qh *fotg210_qh_alloc(struct fotg210_hcd *fotg210, + gfp_t flags) +{ + struct fotg210_qh *qh; + dma_addr_t dma; + + qh = kzalloc_obj(*qh, GFP_ATOMIC); + if (!qh) + goto done; + qh->hw = (struct fotg210_qh_hw *) + dma_pool_zalloc(fotg210->qh_pool, flags, &dma); + if (!qh->hw) + goto fail; + qh->qh_dma = dma; + INIT_LIST_HEAD(&qh->qtd_list); + + /* dummy td enables safe urb queuing */ + qh->dummy = fotg210_qtd_alloc(fotg210, flags); + if (qh->dummy == NULL) { + fotg210_dbg(fotg210, "no dummy td\n"); + goto fail1; + } +done: + return qh; +fail1: + dma_pool_free(fotg210->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 fotg210_mem_cleanup(struct fotg210_hcd *fotg210) +{ + if (fotg210->async) + qh_destroy(fotg210, fotg210->async); + fotg210->async = NULL; + + if (fotg210->dummy) + qh_destroy(fotg210, fotg210->dummy); + fotg210->dummy = NULL; + + /* DMA consistent memory and pools */ + dma_pool_destroy(fotg210->qtd_pool); + fotg210->qtd_pool = NULL; + + dma_pool_destroy(fotg210->qh_pool); + fotg210->qh_pool = NULL; + + dma_pool_destroy(fotg210->itd_pool); + fotg210->itd_pool = NULL; + + if (fotg210->periodic) + dma_free_coherent(fotg210_to_hcd(fotg210)->self.controller, + fotg210->periodic_size * sizeof(u32), + fotg210->periodic, fotg210->periodic_dma); + fotg210->periodic = NULL; + + /* shadow periodic table */ + kfree(fotg210->pshadow); + fotg210->pshadow = NULL; +} + +/* remember to add cleanup code (above) if you add anything here */ +static int fotg210_mem_init(struct fotg210_hcd *fotg210, gfp_t flags) +{ + int i; + + /* QTDs for control/bulk/intr transfers */ + fotg210->qtd_pool = dma_pool_create("fotg210_qtd", + fotg210_to_hcd(fotg210)->self.controller, + sizeof(struct fotg210_qtd), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */); + if (!fotg210->qtd_pool) + goto fail; + + /* QHs for control/bulk/intr transfers */ + fotg210->qh_pool = dma_pool_create("fotg210_qh", + fotg210_to_hcd(fotg210)->self.controller, + sizeof(struct fotg210_qh_hw), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */); + if (!fotg210->qh_pool) + goto fail; + + fotg210->async = fotg210_qh_alloc(fotg210, flags); + if (!fotg210->async) + goto fail; + + /* ITD for high speed ISO transfers */ + fotg210->itd_pool = dma_pool_create("fotg210_itd", + fotg210_to_hcd(fotg210)->self.controller, + sizeof(struct fotg210_itd), + 64 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */); + if (!fotg210->itd_pool) + goto fail; + + /* Hardware periodic table */ + fotg210->periodic = + dma_alloc_coherent(fotg210_to_hcd(fotg210)->self.controller, + fotg210->periodic_size * sizeof(__le32), + &fotg210->periodic_dma, 0); + if (fotg210->periodic == NULL) + goto fail; + + for (i = 0; i < fotg210->periodic_size; i++) + fotg210->periodic[i] = FOTG210_LIST_END(fotg210); + + /* software shadow of hardware table */ + fotg210->pshadow = kcalloc(fotg210->periodic_size, sizeof(void *), + flags); + if (fotg210->pshadow != NULL) + return 0; + +fail: + fotg210_dbg(fotg210, "couldn't init memory\n"); + fotg210_mem_cleanup(fotg210); + return -ENOMEM; +} +/* 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) 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 fotg210_hcd *fotg210, struct fotg210_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(fotg210, (u32)addr); + qtd->hw_buf_hi[0] = cpu_to_hc32(fotg210, (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(fotg210, (u32)addr); + qtd->hw_buf_hi[i] = cpu_to_hc32(fotg210, + (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(fotg210, (count << 16) | token); + qtd->length = count; + + return count; +} + +static inline void qh_update(struct fotg210_hcd *fotg210, + struct fotg210_qh *qh, struct fotg210_qtd *qtd) +{ + struct fotg210_qh_hw *hw = qh->hw; + + /* writes to an active overlay are unsafe */ + BUG_ON(qh->qh_state != QH_STATE_IDLE); + + hw->hw_qtd_next = QTD_NEXT(fotg210, qtd->qtd_dma); + hw->hw_alt_next = FOTG210_LIST_END(fotg210); + + /* 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(fotg210, QH_TOGGLE_CTL))) { + unsigned is_out, epnum; + + is_out = qh->is_out; + epnum = (hc32_to_cpup(fotg210, &hw->hw_info1) >> 8) & 0x0f; + if (unlikely(!usb_gettoggle(qh->dev, epnum, is_out))) { + hw->hw_token &= ~cpu_to_hc32(fotg210, QTD_TOGGLE); + usb_settoggle(qh->dev, epnum, is_out, 1); + } + } + + hw->hw_token &= cpu_to_hc32(fotg210, 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 fotg210_hcd *fotg210, struct fotg210_qh *qh) +{ + struct fotg210_qtd *qtd; + + if (list_empty(&qh->qtd_list)) + qtd = qh->dummy; + else { + qtd = list_entry(qh->qtd_list.next, + struct fotg210_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 (cpu_to_hc32(fotg210, qtd->qtd_dma) == qh->hw->hw_current) { + qh->hw->hw_qtd_next = qtd->hw_next; + qtd = NULL; + } + } + + if (qtd) + qh_update(fotg210, qh, qtd); +} + +static void qh_link_async(struct fotg210_hcd *fotg210, struct fotg210_qh *qh); + +static void fotg210_clear_tt_buffer_complete(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); + struct fotg210_qh *qh = ep->hcpriv; + unsigned long flags; + + spin_lock_irqsave(&fotg210->lock, flags); + qh->clearing_tt = 0; + if (qh->qh_state == QH_STATE_IDLE && !list_empty(&qh->qtd_list) + && fotg210->rh_state == FOTG210_RH_RUNNING) + qh_link_async(fotg210, qh); + spin_unlock_irqrestore(&fotg210->lock, flags); +} + +static void fotg210_clear_tt_buffer(struct fotg210_hcd *fotg210, + struct fotg210_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) { + 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); + + if (urb->dev->tt->hub != + fotg210_to_hcd(fotg210)->self.root_hub) { + if (usb_hub_clear_tt_buffer(urb) == 0) + qh->clearing_tt = 1; + } + } +} + +static int qtd_copy_status(struct fotg210_hcd *fotg210, 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 */ + fotg210_dbg(fotg210, "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; + } + + fotg210_dbg(fotg210, + "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 fotg210_urb_done(struct fotg210_hcd *fotg210, struct urb *urb, + int status) +__releases(fotg210->lock) +__acquires(fotg210->lock) +{ + if (likely(urb->hcpriv != NULL)) { + struct fotg210_qh *qh = (struct fotg210_qh *) urb->hcpriv; + + /* S-mask in a QH means it's an interrupt urb */ + if ((qh->hw->hw_info2 & cpu_to_hc32(fotg210, QH_SMASK)) != 0) { + + /* ... update hc-wide periodic stats (for usbfs) */ + fotg210_to_hcd(fotg210)->self.bandwidth_int_reqs--; + } + } + + if (unlikely(urb->unlinked)) { + INCR(fotg210->stats.unlink); + } else { + /* report non-error and short read status as zero */ + if (status == -EINPROGRESS || status == -EREMOTEIO) + status = 0; + INCR(fotg210->stats.complete); + } + +#ifdef FOTG210_URB_TRACE + fotg210_dbg(fotg210, + "%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(fotg210_to_hcd(fotg210), urb); + spin_unlock(&fotg210->lock); + usb_hcd_giveback_urb(fotg210_to_hcd(fotg210), urb, status); + spin_lock(&fotg210->lock); +} + +static int qh_schedule(struct fotg210_hcd *fotg210, struct fotg210_qh *qh); + +/* 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 fotg210_hcd *fotg210, + struct fotg210_qh *qh) +{ + struct fotg210_qtd *last, *end = qh->dummy; + struct fotg210_qtd *qtd, *tmp; + int last_status; + int stopped; + unsigned count = 0; + u8 state; + struct fotg210_qh_hw *hw = qh->hw; + + 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. + * + * 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->needs_rescan = 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_entry_safe(qtd, tmp, &qh->qtd_list, qtd_list) { + struct urb *urb; + u32 token = 0; + + urb = qtd->urb; + + /* clean up any state from previous QTD ...*/ + if (last) { + if (likely(last->urb != urb)) { + fotg210_urb_done(fotg210, last->urb, + last_status); + count++; + last_status = -EINPROGRESS; + } + fotg210_qtd_free(fotg210, 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(fotg210, 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) + fotg210_dbg(fotg210, + "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) { + fotg210_dbg(fotg210, + "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 | + (FOTG210_TUNE_CERR << 10); + qtd->hw_token = cpu_to_hc32(fotg210, + token); + wmb(); + hw->hw_token = cpu_to_hc32(fotg210, + 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 & + FOTG210_LIST_END(fotg210))) { + stopped = 1; + } + + /* stop scanning when we reach qtds the hc is using */ + } else if (likely(!stopped + && fotg210->rh_state >= FOTG210_RH_RUNNING)) { + break; + + /* scan the whole queue for unlinks whenever it stops */ + } else { + stopped = 1; + + /* cancel everything if we halt, suspend, etc */ + if (fotg210->rh_state < FOTG210_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; + + /* qh unlinked; token in overlay may be most current */ + if (state == QH_STATE_IDLE && + cpu_to_hc32(fotg210, qtd->qtd_dma) + == hw->hw_current) { + token = hc32_to_cpu(fotg210, hw->hw_token); + + /* An unlink may leave an incomplete + * async transaction in the TT buffer. + * We have to clear it. + */ + fotg210_clear_tt_buffer(fotg210, 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(fotg210, urb, + qtd->length, token); + if (last_status == -EREMOTEIO && + (qtd->hw_alt_next & + FOTG210_LIST_END(fotg210))) + 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) + fotg210_clear_tt_buffer(fotg210, 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 fotg210_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)) { + fotg210_urb_done(fotg210, last->urb, last_status); + count++; + fotg210_qtd_free(fotg210, last); + } + + /* Do we need to rescan for URBs dequeued during a giveback? */ + if (unlikely(qh->needs_rescan)) { + /* If the QH is already unlinked, do the rescan now. */ + if (state == QH_STATE_IDLE) + goto rescan; + + /* Otherwise we have to wait until the QH is fully unlinked. + * Our caller will start an unlink if qh->needs_rescan is + * set. But if an unlink has already started, nothing needs + * to be done. + */ + if (state != QH_STATE_LINKED) + qh->needs_rescan = 0; + } + + /* 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 || hw->hw_qtd_next == FOTG210_LIST_END(fotg210)) { + switch (state) { + case QH_STATE_IDLE: + qh_refresh(fotg210, qh); + break; + case QH_STATE_LINKED: + /* 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 ... + */ + + /* Tell the caller to start an unlink */ + qh->needs_rescan = 1; + break; + /* otherwise, unlink already started */ + } + } + + return count; +} + +/* 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 fotg210_hcd *fotg210, struct urb *urb, + struct list_head *head) +{ + struct fotg210_qtd *qtd, *temp; + + list_for_each_entry_safe(qtd, temp, head, qtd_list) { + list_del(&qtd->qtd_list); + fotg210_qtd_free(fotg210, qtd); + } +} + +/* create a list of filled qtds for this URB; won't link into qh. + */ +static struct list_head *qh_urb_transaction(struct fotg210_hcd *fotg210, + struct urb *urb, struct list_head *head, gfp_t flags) +{ + struct fotg210_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 = fotg210_qtd_alloc(fotg210, flags); + if (unlikely(!qtd)) + return NULL; + list_add_tail(&qtd->qtd_list, head); + qtd->urb = urb; + + token = QTD_STS_ACTIVE; + token |= (FOTG210_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(fotg210, 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 = fotg210_qtd_alloc(fotg210, flags); + if (unlikely(!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(fotg210, 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 = usb_maxpacket(urb->dev, 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 + */ + for (;;) { + int this_qtd_len; + + this_qtd_len = qtd_fill(fotg210, 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 = fotg210->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 = fotg210_qtd_alloc(fotg210, flags); + if (unlikely(!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(fotg210, 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 = FOTG210_LIST_END(fotg210); + + /* + * 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 = fotg210_qtd_alloc(fotg210, flags); + if (unlikely(!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(fotg210, qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + + /* never any data in such packets */ + qtd_fill(fotg210, 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(fotg210, QTD_IOC); + return head; + +cleanup: + qtd_list_free(fotg210, 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 fotg210_qh *qh_make(struct fotg210_hcd *fotg210, struct urb *urb, + gfp_t flags) +{ + struct fotg210_qh *qh = fotg210_qh_alloc(fotg210, flags); + struct usb_host_endpoint *ep; + u32 info1 = 0, info2 = 0; + int is_input, type; + int maxp = 0; + int mult; + struct usb_tt *tt = urb->dev->tt; + struct fotg210_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); + ep = usb_pipe_endpoint(urb->dev, urb->pipe); + maxp = usb_endpoint_maxp(&ep->desc); + mult = usb_endpoint_maxp_mult(&ep->desc); + + /* 1024 byte maxpacket is a hardware ceiling. High bandwidth + * acts like up to 3KB, but is built from smaller packets. + */ + if (maxp > 1024) { + fotg210_dbg(fotg210, "bogus qh maxpacket %d\n", 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, mult * 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 > fotg210->periodic_size) { + qh->period = fotg210->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, maxp)); + qh->period = urb->interval; + if (qh->period > fotg210->periodic_size) { + qh->period = fotg210->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; + fallthrough; + + case USB_SPEED_FULL: + /* EPS 0 means "full" */ + if (type != PIPE_INTERRUPT) + info1 |= (FOTG210_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 |= (FOTG210_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 (fotg210_has_fsl_portno_bug(fotg210)) + 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 != fotg210_to_hcd(fotg210)->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 |= (FOTG210_TUNE_RL_HS << 28); + info1 |= 64 << 16; /* usb2 fixed maxpacket */ + info1 |= QH_TOGGLE_CTL; /* toggle from qtd */ + info2 |= (FOTG210_TUNE_MULT_HS << 30); + } else if (type == PIPE_BULK) { + info1 |= (FOTG210_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 |= maxp << 16; + info2 |= (FOTG210_TUNE_MULT_HS << 30); + } else { /* PIPE_INTERRUPT */ + info1 |= maxp << 16; + info2 |= mult << 30; + } + break; + default: + fotg210_dbg(fotg210, "bogus dev %p speed %d\n", urb->dev, + urb->dev->speed); +done: + qh_destroy(fotg210, 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; + hw = qh->hw; + hw->hw_info1 = cpu_to_hc32(fotg210, info1); + hw->hw_info2 = cpu_to_hc32(fotg210, info2); + qh->is_out = !is_input; + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), !is_input, 1); + qh_refresh(fotg210, qh); + return qh; +} + +static void enable_async(struct fotg210_hcd *fotg210) +{ + if (fotg210->async_count++) + return; + + /* Stop waiting to turn off the async schedule */ + fotg210->enabled_hrtimer_events &= ~BIT(FOTG210_HRTIMER_DISABLE_ASYNC); + + /* Don't start the schedule until ASS is 0 */ + fotg210_poll_ASS(fotg210); + turn_on_io_watchdog(fotg210); +} + +static void disable_async(struct fotg210_hcd *fotg210) +{ + if (--fotg210->async_count) + return; + + /* The async schedule and async_unlink list are supposed to be empty */ + WARN_ON(fotg210->async->qh_next.qh || fotg210->async_unlink); + + /* Don't turn off the schedule until ASS is 1 */ + fotg210_poll_ASS(fotg210); +} + +/* move qh (and its qtds) onto async queue; maybe enable queue. */ + +static void qh_link_async(struct fotg210_hcd *fotg210, struct fotg210_qh *qh) +{ + __hc32 dma = QH_NEXT(fotg210, qh->qh_dma); + struct fotg210_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(fotg210, qh); + + /* splice right after start */ + head = fotg210->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->xacterrs = 0; + qh->qh_state = QH_STATE_LINKED; + /* qtd completions reported later by interrupt */ + + enable_async(fotg210); +} + +/* 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 fotg210_qh *qh_append_tds(struct fotg210_hcd *fotg210, + struct urb *urb, struct list_head *qtd_list, + int epnum, void **ptr) +{ + struct fotg210_qh *qh = NULL; + __hc32 qh_addr_mask = cpu_to_hc32(fotg210, 0x7f); + + qh = (struct fotg210_qh *) *ptr; + if (unlikely(qh == NULL)) { + /* can't sleep here, we have fotg210->lock... */ + qh = qh_make(fotg210, urb, GFP_ATOMIC); + *ptr = qh; + } + if (likely(qh != NULL)) { + struct fotg210_qtd *qtd; + + if (unlikely(list_empty(qtd_list))) + qtd = NULL; + else + qtd = list_entry(qtd_list->next, struct fotg210_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 fotg210_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(fotg210); + + 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); + + fotg210_qtd_init(fotg210, 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 fotg210_qtd, qtd_list); + qtd->hw_next = QTD_NEXT(fotg210, dma); + + /* let the hc process these next qtds */ + wmb(); + dummy->hw_token = token; + + urb->hcpriv = qh; + } + } + return qh; +} + +static int submit_async(struct fotg210_hcd *fotg210, struct urb *urb, + struct list_head *qtd_list, gfp_t mem_flags) +{ + int epnum; + unsigned long flags; + struct fotg210_qh *qh = NULL; + int rc; + + epnum = urb->ep->desc.bEndpointAddress; + +#ifdef FOTG210_URB_TRACE + { + struct fotg210_qtd *qtd; + + qtd = list_entry(qtd_list->next, struct fotg210_qtd, qtd_list); + fotg210_dbg(fotg210, + "%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(&fotg210->lock, flags); + if (unlikely(!HCD_HW_ACCESSIBLE(fotg210_to_hcd(fotg210)))) { + rc = -ESHUTDOWN; + goto done; + } + rc = usb_hcd_link_urb_to_ep(fotg210_to_hcd(fotg210), urb); + if (unlikely(rc)) + goto done; + + qh = qh_append_tds(fotg210, urb, qtd_list, epnum, &urb->ep->hcpriv); + if (unlikely(qh == NULL)) { + usb_hcd_unlink_urb_from_ep(fotg210_to_hcd(fotg210), 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(fotg210, qh); +done: + spin_unlock_irqrestore(&fotg210->lock, flags); + if (unlikely(qh == NULL)) + qtd_list_free(fotg210, urb, qtd_list); + return rc; +} + +static void single_unlink_async(struct fotg210_hcd *fotg210, + struct fotg210_qh *qh) +{ + struct fotg210_qh *prev; + + /* Add to the end of the list of QHs waiting for the next IAAD */ + qh->qh_state = QH_STATE_UNLINK; + if (fotg210->async_unlink) + fotg210->async_unlink_last->unlink_next = qh; + else + fotg210->async_unlink = qh; + fotg210->async_unlink_last = qh; + + /* Unlink it from the schedule */ + prev = fotg210->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 (fotg210->qh_scan_next == qh) + fotg210->qh_scan_next = qh->qh_next.qh; +} + +static void start_iaa_cycle(struct fotg210_hcd *fotg210, bool nested) +{ + /* + * Do nothing if an IAA cycle is already running or + * if one will be started shortly. + */ + if (fotg210->async_iaa || fotg210->async_unlinking) + return; + + /* Do all the waiting QHs at once */ + fotg210->async_iaa = fotg210->async_unlink; + fotg210->async_unlink = NULL; + + /* If the controller isn't running, we don't have to wait for it */ + if (unlikely(fotg210->rh_state < FOTG210_RH_RUNNING)) { + if (!nested) /* Avoid recursion */ + end_unlink_async(fotg210); + + /* Otherwise start a new IAA cycle */ + } else if (likely(fotg210->rh_state == FOTG210_RH_RUNNING)) { + /* Make sure the unlinks are all visible to the hardware */ + wmb(); + + fotg210_writel(fotg210, fotg210->command | CMD_IAAD, + &fotg210->regs->command); + fotg210_readl(fotg210, &fotg210->regs->command); + fotg210_enable_event(fotg210, FOTG210_HRTIMER_IAA_WATCHDOG, + true); + } +} + +/* the async qh for the qtds being unlinked are now gone from the HC */ + +static void end_unlink_async(struct fotg210_hcd *fotg210) +{ + struct fotg210_qh *qh; + + /* Process the idle QHs */ +restart: + fotg210->async_unlinking = true; + while (fotg210->async_iaa) { + qh = fotg210->async_iaa; + fotg210->async_iaa = qh->unlink_next; + qh->unlink_next = NULL; + + qh->qh_state = QH_STATE_IDLE; + qh->qh_next.qh = NULL; + + qh_completions(fotg210, qh); + if (!list_empty(&qh->qtd_list) && + fotg210->rh_state == FOTG210_RH_RUNNING) + qh_link_async(fotg210, qh); + disable_async(fotg210); + } + fotg210->async_unlinking = false; + + /* Start a new IAA cycle if any QHs are waiting for it */ + if (fotg210->async_unlink) { + start_iaa_cycle(fotg210, true); + if (unlikely(fotg210->rh_state < FOTG210_RH_RUNNING)) + goto restart; + } +} + +static void unlink_empty_async(struct fotg210_hcd *fotg210) +{ + struct fotg210_qh *qh, *next; + bool stopped = (fotg210->rh_state < FOTG210_RH_RUNNING); + bool check_unlinks_later = false; + + /* Unlink all the async QHs that have been empty for a timer cycle */ + next = fotg210->async->qh_next.qh; + while (next) { + qh = next; + next = qh->qh_next.qh; + + if (list_empty(&qh->qtd_list) && + qh->qh_state == QH_STATE_LINKED) { + if (!stopped && qh->unlink_cycle == + fotg210->async_unlink_cycle) + check_unlinks_later = true; + else + single_unlink_async(fotg210, qh); + } + } + + /* Start a new IAA cycle if any QHs are waiting for it */ + if (fotg210->async_unlink) + start_iaa_cycle(fotg210, false); + + /* QHs that haven't been empty for long enough will be handled later */ + if (check_unlinks_later) { + fotg210_enable_event(fotg210, FOTG210_HRTIMER_ASYNC_UNLINKS, + true); + ++fotg210->async_unlink_cycle; + } +} + +/* makes sure the async qh will become idle */ +/* caller must own fotg210->lock */ + +static void start_unlink_async(struct fotg210_hcd *fotg210, + struct fotg210_qh *qh) +{ + /* + * If the QH isn't linked then there's nothing we can do + * unless we were called during a giveback, in which case + * qh_completions() has to deal with it. + */ + if (qh->qh_state != QH_STATE_LINKED) { + if (qh->qh_state == QH_STATE_COMPLETING) + qh->needs_rescan = 1; + return; + } + + single_unlink_async(fotg210, qh); + start_iaa_cycle(fotg210, false); +} + +static void scan_async(struct fotg210_hcd *fotg210) +{ + struct fotg210_qh *qh; + bool check_unlinks_later = false; + + fotg210->qh_scan_next = fotg210->async->qh_next.qh; + while (fotg210->qh_scan_next) { + qh = fotg210->qh_scan_next; + fotg210->qh_scan_next = qh->qh_next.qh; +rescan: + /* 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 fotg210->qh_scan_next + * always holds the next qh to scan; if the next qh + * gets unlinked then fotg210->qh_scan_next is adjusted + * in single_unlink_async(). + */ + temp = qh_completions(fotg210, qh); + if (qh->needs_rescan) { + start_unlink_async(fotg210, qh); + } else if (list_empty(&qh->qtd_list) + && qh->qh_state == QH_STATE_LINKED) { + qh->unlink_cycle = fotg210->async_unlink_cycle; + check_unlinks_later = true; + } else if (temp != 0) + goto rescan; + } + } + + /* + * 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 && fotg210->rh_state == FOTG210_RH_RUNNING && + !(fotg210->enabled_hrtimer_events & + BIT(FOTG210_HRTIMER_ASYNC_UNLINKS))) { + fotg210_enable_event(fotg210, + FOTG210_HRTIMER_ASYNC_UNLINKS, true); + ++fotg210->async_unlink_cycle; + } +} +/* 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 fotg210_get_frame(struct usb_hcd *hcd); + +/* periodic_next_shadow - return "next" pointer on shadow list + * @periodic: host pointer to qh/itd + * @tag: hardware tag for type of this record + */ +static union fotg210_shadow *periodic_next_shadow(struct fotg210_hcd *fotg210, + union fotg210_shadow *periodic, __hc32 tag) +{ + switch (hc32_to_cpu(fotg210, tag)) { + case Q_TYPE_QH: + return &periodic->qh->qh_next; + case Q_TYPE_FSTN: + return &periodic->fstn->fstn_next; + default: + return &periodic->itd->itd_next; + } +} + +static __hc32 *shadow_next_periodic(struct fotg210_hcd *fotg210, + union fotg210_shadow *periodic, __hc32 tag) +{ + switch (hc32_to_cpu(fotg210, tag)) { + /* our fotg210_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 fotg210->lock */ +static void periodic_unlink(struct fotg210_hcd *fotg210, unsigned frame, + void *ptr) +{ + union fotg210_shadow *prev_p = &fotg210->pshadow[frame]; + __hc32 *hw_p = &fotg210->periodic[frame]; + union fotg210_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(fotg210, prev_p, + Q_NEXT_TYPE(fotg210, *hw_p)); + hw_p = shadow_next_periodic(fotg210, &here, + Q_NEXT_TYPE(fotg210, *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(fotg210, &here, + Q_NEXT_TYPE(fotg210, *hw_p)); + + *hw_p = *shadow_next_periodic(fotg210, &here, + Q_NEXT_TYPE(fotg210, *hw_p)); +} + +/* how many of the uframe's 125 usecs are allocated? */ +static unsigned short periodic_usecs(struct fotg210_hcd *fotg210, + unsigned frame, unsigned uframe) +{ + __hc32 *hw_p = &fotg210->periodic[frame]; + union fotg210_shadow *q = &fotg210->pshadow[frame]; + unsigned usecs = 0; + struct fotg210_qh_hw *hw; + + while (q->ptr) { + switch (hc32_to_cpu(fotg210, Q_NEXT_TYPE(fotg210, *hw_p))) { + case Q_TYPE_QH: + hw = q->qh->hw; + /* is it in the S-mask? */ + if (hw->hw_info2 & cpu_to_hc32(fotg210, 1 << uframe)) + usecs += q->qh->usecs; + /* ... or C-mask? */ + if (hw->hw_info2 & cpu_to_hc32(fotg210, + 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 != FOTG210_LIST_END(fotg210)) + fotg210_dbg(fotg210, "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; + } + } + if (usecs > fotg210->uframe_periodic_max) + fotg210_err(fotg210, "uframe %d sched overrun: %d usecs\n", + frame * 8 + uframe, usecs); + 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; +} + +/* 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 fotg210_hcd *fotg210, 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 < fotg210->periodic_size; frame += period) { + union fotg210_shadow here; + __hc32 type; + struct fotg210_qh_hw *hw; + + here = fotg210->pshadow[frame]; + type = Q_NEXT_TYPE(fotg210, fotg210->periodic[frame]); + while (here.ptr) { + switch (hc32_to_cpu(fotg210, type)) { + case Q_TYPE_ITD: + type = Q_NEXT_TYPE(fotg210, 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(fotg210, + hw->hw_info2); + /* "knows" no gap is needed */ + mask |= mask >> 8; + if (mask & uf_mask) + break; + } + type = Q_NEXT_TYPE(fotg210, hw->hw_next); + here = here.qh->qh_next; + continue; + /* case Q_TYPE_FSTN: */ + default: + fotg210_dbg(fotg210, + "periodic frame %d bogus type %d\n", + frame, type); + } + + /* collision or error */ + return 0; + } + } + + /* no collision */ + return 1; +} + +static void enable_periodic(struct fotg210_hcd *fotg210) +{ + if (fotg210->periodic_count++) + return; + + /* Stop waiting to turn off the periodic schedule */ + fotg210->enabled_hrtimer_events &= + ~BIT(FOTG210_HRTIMER_DISABLE_PERIODIC); + + /* Don't start the schedule until PSS is 0 */ + fotg210_poll_PSS(fotg210); + turn_on_io_watchdog(fotg210); +} + +static void disable_periodic(struct fotg210_hcd *fotg210) +{ + if (--fotg210->periodic_count) + return; + + /* Don't turn off the schedule until PSS is 1 */ + fotg210_poll_PSS(fotg210); +} + +/* 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; fotg210 0.96+) + */ +static void qh_link_periodic(struct fotg210_hcd *fotg210, struct fotg210_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(fotg210, &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 < fotg210->periodic_size; i += period) { + union fotg210_shadow *prev = &fotg210->pshadow[i]; + __hc32 *hw_p = &fotg210->periodic[i]; + union fotg210_shadow here = *prev; + __hc32 type = 0; + + /* skip the iso nodes at list head */ + while (here.ptr) { + type = Q_NEXT_TYPE(fotg210, *hw_p); + if (type == cpu_to_hc32(fotg210, Q_TYPE_QH)) + break; + prev = periodic_next_shadow(fotg210, prev, type); + hw_p = shadow_next_periodic(fotg210, &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(fotg210, qh->qh_dma); + } + } + qh->qh_state = QH_STATE_LINKED; + qh->xacterrs = 0; + + /* update per-qh bandwidth for usbfs */ + fotg210_to_hcd(fotg210)->self.bandwidth_allocated += qh->period + ? ((qh->usecs + qh->c_usecs) / qh->period) + : (qh->usecs * 8); + + list_add(&qh->intr_node, &fotg210->intr_qh_list); + + /* maybe enable periodic schedule processing */ + ++fotg210->intr_count; + enable_periodic(fotg210); +} + +static void qh_unlink_periodic(struct fotg210_hcd *fotg210, + struct fotg210_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 */ + period = qh->period; + if (!period) + period = 1; + + for (i = qh->start; i < fotg210->periodic_size; i += period) + periodic_unlink(fotg210, i, qh); + + /* update per-qh bandwidth for usbfs */ + fotg210_to_hcd(fotg210)->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(fotg210, &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 (fotg210->qh_scan_next == qh) + fotg210->qh_scan_next = list_entry(qh->intr_node.next, + struct fotg210_qh, intr_node); + list_del(&qh->intr_node); +} + +static void start_unlink_intr(struct fotg210_hcd *fotg210, + struct fotg210_qh *qh) +{ + /* If the QH isn't linked then there's nothing we can do + * unless we were called during a giveback, in which case + * qh_completions() has to deal with it. + */ + if (qh->qh_state != QH_STATE_LINKED) { + if (qh->qh_state == QH_STATE_COMPLETING) + qh->needs_rescan = 1; + return; + } + + qh_unlink_periodic(fotg210, 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 = fotg210->intr_unlink_cycle; + + /* New entries go at the end of the intr_unlink list */ + if (fotg210->intr_unlink) + fotg210->intr_unlink_last->unlink_next = qh; + else + fotg210->intr_unlink = qh; + fotg210->intr_unlink_last = qh; + + if (fotg210->intr_unlinking) + ; /* Avoid recursive calls */ + else if (fotg210->rh_state < FOTG210_RH_RUNNING) + fotg210_handle_intr_unlinks(fotg210); + else if (fotg210->intr_unlink == qh) { + fotg210_enable_event(fotg210, FOTG210_HRTIMER_UNLINK_INTR, + true); + ++fotg210->intr_unlink_cycle; + } +} + +static void end_unlink_intr(struct fotg210_hcd *fotg210, struct fotg210_qh *qh) +{ + struct fotg210_qh_hw *hw = qh->hw; + int rc; + + qh->qh_state = QH_STATE_IDLE; + hw->hw_next = FOTG210_LIST_END(fotg210); + + qh_completions(fotg210, qh); + + /* reschedule QH iff another request is queued */ + if (!list_empty(&qh->qtd_list) && + fotg210->rh_state == FOTG210_RH_RUNNING) { + rc = qh_schedule(fotg210, 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 + */ + if (rc != 0) + fotg210_err(fotg210, "can't reschedule qh %p, err %d\n", + qh, rc); + } + + /* maybe turn off periodic schedule */ + --fotg210->intr_count; + disable_periodic(fotg210); +} + +static int check_period(struct fotg210_hcd *fotg210, 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 = fotg210->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(fotg210, frame, + uframe); + if (claimed > usecs) + return 0; + } + } while ((frame += 1) < fotg210->periodic_size); + + /* just check the specified uframe, at that period */ + } else { + do { + claimed = periodic_usecs(fotg210, frame, uframe); + if (claimed > usecs) + return 0; + } while ((frame += period) < fotg210->periodic_size); + } + + /* success! */ + return 1; +} + +static int check_intr_schedule(struct fotg210_hcd *fotg210, unsigned frame, + unsigned uframe, const struct fotg210_qh *qh, __hc32 *c_maskp) +{ + int retval = -ENOSPC; + u8 mask = 0; + + if (qh->c_usecs && uframe >= 6) /* FSTN territory? */ + goto done; + + if (!check_period(fotg210, frame, uframe, qh->period, qh->usecs)) + goto done; + if (!qh->c_usecs) { + retval = 0; + *c_maskp = 0; + goto done; + } + + /* 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(fotg210, mask << 8); + + mask |= 1 << uframe; + if (tt_no_collision(fotg210, qh->period, qh->dev, frame, mask)) { + if (!check_period(fotg210, frame, uframe + qh->gap_uf + 1, + qh->period, qh->c_usecs)) + goto done; + if (!check_period(fotg210, frame, uframe + qh->gap_uf, + qh->period, qh->c_usecs)) + goto done; + retval = 0; + } +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 fotg210_hcd *fotg210, struct fotg210_qh *qh) +{ + int status; + unsigned uframe; + __hc32 c_mask; + unsigned frame; /* 0..(qh->period - 1), or NO_FRAME */ + struct fotg210_qh_hw *hw = qh->hw; + + qh_refresh(fotg210, qh); + hw->hw_next = FOTG210_LIST_END(fotg210); + frame = qh->start; + + /* reuse the previous schedule slots, if we can */ + if (frame < qh->period) { + uframe = ffs(hc32_to_cpup(fotg210, &hw->hw_info2) & QH_SMASK); + status = check_intr_schedule(fotg210, 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 = ++fotg210->random_frame % qh->period; + for (uframe = 0; uframe < 8; uframe++) { + status = check_intr_schedule(fotg210, + frame, uframe, qh, + &c_mask); + if (status == 0) + break; + } + } + + /* qh->period == 0 means every uframe */ + } else { + frame = 0; + status = check_intr_schedule(fotg210, 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(fotg210, ~(QH_CMASK | QH_SMASK)); + hw->hw_info2 |= qh->period + ? cpu_to_hc32(fotg210, 1 << uframe) + : cpu_to_hc32(fotg210, QH_SMASK); + hw->hw_info2 |= c_mask; + } else + fotg210_dbg(fotg210, "reused qh %p schedule\n", qh); + + /* stuff into the periodic schedule */ + qh_link_periodic(fotg210, qh); +done: + return status; +} + +static int intr_submit(struct fotg210_hcd *fotg210, struct urb *urb, + struct list_head *qtd_list, gfp_t mem_flags) +{ + unsigned epnum; + unsigned long flags; + struct fotg210_qh *qh; + int status; + struct list_head empty; + + /* get endpoint and transfer/schedule data */ + epnum = urb->ep->desc.bEndpointAddress; + + spin_lock_irqsave(&fotg210->lock, flags); + + if (unlikely(!HCD_HW_ACCESSIBLE(fotg210_to_hcd(fotg210)))) { + status = -ESHUTDOWN; + goto done_not_linked; + } + status = usb_hcd_link_urb_to_ep(fotg210_to_hcd(fotg210), urb); + if (unlikely(status)) + goto done_not_linked; + + /* get qh and force any scheduling errors */ + INIT_LIST_HEAD(&empty); + qh = qh_append_tds(fotg210, urb, &empty, epnum, &urb->ep->hcpriv); + if (qh == NULL) { + status = -ENOMEM; + goto done; + } + if (qh->qh_state == QH_STATE_IDLE) { + status = qh_schedule(fotg210, qh); + if (status) + goto done; + } + + /* then queue the urb's tds to the qh */ + qh = qh_append_tds(fotg210, urb, qtd_list, epnum, &urb->ep->hcpriv); + BUG_ON(qh == NULL); + + /* ... update usbfs periodic stats */ + fotg210_to_hcd(fotg210)->self.bandwidth_int_reqs++; + +done: + if (unlikely(status)) + usb_hcd_unlink_urb_from_ep(fotg210_to_hcd(fotg210), urb); +done_not_linked: + spin_unlock_irqrestore(&fotg210->lock, flags); + if (status) + qtd_list_free(fotg210, urb, qtd_list); + + return status; +} + +static void scan_intr(struct fotg210_hcd *fotg210) +{ + struct fotg210_qh *qh; + + list_for_each_entry_safe(qh, fotg210->qh_scan_next, + &fotg210->intr_qh_list, intr_node) { +rescan: + /* 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 fotg210->qh_scan_next + * always holds the next qh to scan; if the next qh + * gets unlinked then fotg210->qh_scan_next is adjusted + * in qh_unlink_periodic(). + */ + temp = qh_completions(fotg210, qh); + if (unlikely(qh->needs_rescan || + (list_empty(&qh->qtd_list) && + qh->qh_state == QH_STATE_LINKED))) + start_unlink_intr(fotg210, qh); + else if (temp != 0) + goto rescan; + } + } +} + +/* fotg210_iso_stream ops work with both ITD and SITD */ + +static struct fotg210_iso_stream *iso_stream_alloc(gfp_t mem_flags) +{ + struct fotg210_iso_stream *stream; + + stream = kzalloc_obj(*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 fotg210_hcd *fotg210, + struct fotg210_iso_stream *stream, struct usb_device *dev, + int pipe, unsigned interval) +{ + u32 buf1; + unsigned epnum, maxp; + int is_input; + long bandwidth; + unsigned multi; + struct usb_host_endpoint *ep; + + /* + * 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; + ep = usb_pipe_endpoint(dev, pipe); + maxp = usb_endpoint_maxp(&ep->desc); + if (is_input) + buf1 = (1 << 11); + else + buf1 = 0; + + multi = usb_endpoint_maxp_mult(&ep->desc); + buf1 |= maxp; + maxp *= multi; + + stream->buf0 = cpu_to_hc32(fotg210, (epnum << 8) | dev->devnum); + stream->buf1 = cpu_to_hc32(fotg210, buf1); + stream->buf2 = cpu_to_hc32(fotg210, multi); + + /* usbfs wants to report the average usecs per frame tied up + * when transfers on this endpoint are scheduled ... + */ + if (dev->speed == USB_SPEED_FULL) { + interval <<= 3; + stream->usecs = NS_TO_US(usb_calc_bus_time(dev->speed, + is_input, 1, maxp)); + stream->usecs /= 8; + } else { + stream->highspeed = 1; + stream->usecs = HS_USECS_ISO(maxp); + } + bandwidth = stream->usecs * 8; + bandwidth /= interval; + + stream->bandwidth = bandwidth; + stream->udev = dev; + stream->bEndpointAddress = is_input | epnum; + stream->interval = interval; + stream->maxp = maxp; +} + +static struct fotg210_iso_stream *iso_stream_find(struct fotg210_hcd *fotg210, + struct urb *urb) +{ + unsigned epnum; + struct fotg210_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(&fotg210->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(fotg210, stream, urb->dev, urb->pipe, + urb->interval); + } + + /* if dev->ep[epnum] is a QH, hw is set */ + } else if (unlikely(stream->hw != NULL)) { + fotg210_dbg(fotg210, "dev %s ep%d%s, not iso??\n", + urb->dev->devpath, epnum, + usb_pipein(urb->pipe) ? "in" : "out"); + stream = NULL; + } + + spin_unlock_irqrestore(&fotg210->lock, flags); + return stream; +} + +/* fotg210_iso_sched ops can be ITD-only or SITD-only */ + +static struct fotg210_iso_sched *iso_sched_alloc(unsigned packets, + gfp_t mem_flags) +{ + struct fotg210_iso_sched *iso_sched; + + iso_sched = kzalloc_flex(*iso_sched, packet, packets, mem_flags); + if (likely(iso_sched != NULL)) + INIT_LIST_HEAD(&iso_sched->td_list); + + return iso_sched; +} + +static inline void itd_sched_init(struct fotg210_hcd *fotg210, + struct fotg210_iso_sched *iso_sched, + struct fotg210_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 fotg210_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 = FOTG210_ISOC_ACTIVE; + trans |= buf & 0x0fff; + if (unlikely(((i + 1) == urb->number_of_packets)) + && !(urb->transfer_flags & URB_NO_INTERRUPT)) + trans |= FOTG210_ITD_IOC; + trans |= length << 16; + uframe->transaction = cpu_to_hc32(fotg210, 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 fotg210_iso_stream *stream, + struct fotg210_iso_sched *iso_sched) +{ + if (!iso_sched) + return; + /* caller must hold fotg210->lock!*/ + list_splice(&iso_sched->td_list, &stream->free_list); + kfree(iso_sched); +} + +static int itd_urb_transaction(struct fotg210_iso_stream *stream, + struct fotg210_hcd *fotg210, struct urb *urb, gfp_t mem_flags) +{ + struct fotg210_itd *itd; + dma_addr_t itd_dma; + int i; + unsigned num_itds; + struct fotg210_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(fotg210, 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(&fotg210->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 fotg210_itd, itd_list); + if (itd->frame == fotg210->now_frame) + goto alloc_itd; + list_del(&itd->itd_list); + itd_dma = itd->itd_dma; + } else { +alloc_itd: + spin_unlock_irqrestore(&fotg210->lock, flags); + itd = dma_pool_alloc(fotg210->itd_pool, mem_flags, + &itd_dma); + spin_lock_irqsave(&fotg210->lock, flags); + if (!itd) { + iso_sched_free(stream, sched); + spin_unlock_irqrestore(&fotg210->lock, flags); + return -ENOMEM; + } + } + + memset(itd, 0, sizeof(*itd)); + itd->itd_dma = itd_dma; + list_add(&itd->itd_list, &sched->td_list); + } + spin_unlock_irqrestore(&fotg210->lock, flags); + + /* temporarily store schedule info in hcpriv */ + urb->hcpriv = sched; + urb->error_count = 0; + return 0; +} + +static inline int itd_slot_ok(struct fotg210_hcd *fotg210, u32 mod, u32 uframe, + u8 usecs, u32 period) +{ + uframe %= period; + do { + /* can't commit more than uframe_periodic_max usec */ + if (periodic_usecs(fotg210, uframe >> 3, uframe & 0x7) + > (fotg210->uframe_periodic_max - usecs)) + return 0; + + /* we know urb->interval is 2^N uframes */ + uframe += period; + } while (uframe < mod); + 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 fotg210'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 FOTG210_TUNE_FLS and the slop). Or, write a smarter scheduler! + */ + +#define SCHEDULE_SLOP 80 /* microframes */ + +static int iso_stream_schedule(struct fotg210_hcd *fotg210, struct urb *urb, + struct fotg210_iso_stream *stream) +{ + u32 now, next, start, period, span; + int status; + unsigned mod = fotg210->periodic_size << 3; + struct fotg210_iso_sched *sched = urb->hcpriv; + + period = urb->interval; + span = sched->span; + + if (span > mod - SCHEDULE_SLOP) { + fotg210_dbg(fotg210, "iso request %p too long\n", urb); + status = -EFBIG; + goto fail; + } + + now = fotg210_read_frame_index(fotg210) & (mod - 1); + + /* Typical case: reuse current schedule, stream is still active. + * Hopefully there are no gaps from the host falling behind + * (irq delays etc), but if there are we'll take the next + * slot in the schedule, implicitly assuming URB_ISO_ASAP. + */ + if (likely(!list_empty(&stream->td_list))) { + u32 excess; + + /* For high speed devices, allow scheduling within the + * isochronous scheduling threshold. For full speed devices + * and Intel PCI-based controllers, don't (work around for + * Intel ICH9 bug). + */ + if (!stream->highspeed && fotg210->fs_i_thresh) + next = now + fotg210->i_thresh; + else + next = now; + + /* Fell behind (by up to twice the slop amount)? + * We decide based on the time of the last currently-scheduled + * slot, not the time of the next available slot. + */ + excess = (stream->next_uframe - period - next) & (mod - 1); + if (excess >= mod - 2 * SCHEDULE_SLOP) + start = next + excess - mod + period * + DIV_ROUND_UP(mod - excess, period); + else + start = next + excess + period; + if (start - now >= mod) { + fotg210_dbg(fotg210, "request %p would overflow (%d+%d >= %d)\n", + urb, start - now - period, period, + mod); + status = -EFBIG; + goto fail; + } + } + + /* need to schedule; when's the next (u)frame we could start? + * this is bigger than fotg210->i_thresh allows; scheduling itself + * isn't free, the slop 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; + + start = SCHEDULE_SLOP + (now & ~0x07); + + /* NOTE: assumes URB_ISO_ASAP, to limit complexity/bugs */ + + /* 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 (itd_slot_ok(fotg210, mod, start, + stream->usecs, period)) + done = 1; + } while (start > next && !done); + + /* no room in the schedule */ + if (!done) { + fotg210_dbg(fotg210, "iso resched full %p (now %d max %d)\n", + urb, now, now + mod); + status = -ENOSPC; + goto fail; + } + } + + /* Tried to schedule too far into the future? */ + if (unlikely(start - now + span - period >= + mod - 2 * SCHEDULE_SLOP)) { + fotg210_dbg(fotg210, "request %p would overflow (%d+%d >= %d)\n", + urb, start - now, span - period, + mod - 2 * SCHEDULE_SLOP); + 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 (fotg210->isoc_count == 0) + fotg210->next_frame = now >> 3; + return 0; + +fail: + return status; +} + +static inline void itd_init(struct fotg210_hcd *fotg210, + struct fotg210_iso_stream *stream, struct fotg210_itd *itd) +{ + int i; + + /* it's been recently zeroed */ + itd->hw_next = FOTG210_LIST_END(fotg210); + 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 fotg210_hcd *fotg210, + struct fotg210_itd *itd, struct fotg210_iso_sched *iso_sched, + unsigned index, u16 uframe) +{ + struct fotg210_iso_packet *uf = &iso_sched->packet[index]; + unsigned pg = itd->pg; + + uframe &= 0x07; + itd->index[uframe] = index; + + itd->hw_transaction[uframe] = uf->transaction; + itd->hw_transaction[uframe] |= cpu_to_hc32(fotg210, pg << 12); + itd->hw_bufp[pg] |= cpu_to_hc32(fotg210, uf->bufp & ~(u32)0); + itd->hw_bufp_hi[pg] |= cpu_to_hc32(fotg210, (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(fotg210, bufp & ~(u32)0); + itd->hw_bufp_hi[pg] |= cpu_to_hc32(fotg210, (u32)(bufp >> 32)); + } +} + +static inline void itd_link(struct fotg210_hcd *fotg210, unsigned frame, + struct fotg210_itd *itd) +{ + union fotg210_shadow *prev = &fotg210->pshadow[frame]; + __hc32 *hw_p = &fotg210->periodic[frame]; + union fotg210_shadow here = *prev; + __hc32 type = 0; + + /* skip any iso nodes which might belong to previous microframes */ + while (here.ptr) { + type = Q_NEXT_TYPE(fotg210, *hw_p); + if (type == cpu_to_hc32(fotg210, Q_TYPE_QH)) + break; + prev = periodic_next_shadow(fotg210, prev, type); + hw_p = shadow_next_periodic(fotg210, &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(fotg210, itd->itd_dma | Q_TYPE_ITD); +} + +/* fit urb's itds into the selected schedule slot; activate as needed */ +static void itd_link_urb(struct fotg210_hcd *fotg210, struct urb *urb, + unsigned mod, struct fotg210_iso_stream *stream) +{ + int packet; + unsigned next_uframe, uframe, frame; + struct fotg210_iso_sched *iso_sched = urb->hcpriv; + struct fotg210_itd *itd; + + next_uframe = stream->next_uframe & (mod - 1); + + if (unlikely(list_empty(&stream->td_list))) { + fotg210_to_hcd(fotg210)->self.bandwidth_allocated + += stream->bandwidth; + fotg210_dbg(fotg210, + "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); + } + + /* fill iTDs uframe by uframe */ + for (packet = 0, itd = NULL; packet < urb->number_of_packets;) { + if (itd == NULL) { + /* ASSERT: we have all necessary itds */ + + /* ASSERT: no itds for this endpoint in this uframe */ + + itd = list_entry(iso_sched->td_list.next, + struct fotg210_itd, itd_list); + list_move_tail(&itd->itd_list, &stream->td_list); + itd->stream = stream; + itd->urb = urb; + itd_init(fotg210, stream, itd); + } + + uframe = next_uframe & 0x07; + frame = next_uframe >> 3; + + itd_patch(fotg210, 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(fotg210, frame & (fotg210->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 = NULL; + + ++fotg210->isoc_count; + enable_periodic(fotg210); +} + +#define ISO_ERRS (FOTG210_ISOC_BUF_ERR | FOTG210_ISOC_BABBLE |\ + FOTG210_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 fotg210_hcd *fotg210, struct fotg210_itd *itd) +{ + struct urb *urb = itd->urb; + struct usb_iso_packet_descriptor *desc; + u32 t; + unsigned uframe; + int urb_index = -1; + struct fotg210_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(fotg210, &itd->hw_transaction[uframe]); + itd->hw_transaction[uframe] = 0; + + /* report transfer status */ + if (unlikely(t & ISO_ERRS)) { + urb->error_count++; + if (t & FOTG210_ISOC_BUF_ERR) + desc->status = usb_pipein(urb->pipe) + ? -ENOSR /* hc couldn't read */ + : -ECOMM; /* hc couldn't write */ + else if (t & FOTG210_ISOC_BABBLE) + desc->status = -EOVERFLOW; + else /* (t & FOTG210_ISOC_XACTERR) */ + desc->status = -EPROTO; + + /* HC need not update length with this error */ + if (!(t & FOTG210_ISOC_BABBLE)) { + desc->actual_length = FOTG210_ITD_LENGTH(t); + urb->actual_length += desc->actual_length; + } + } else if (likely((t & FOTG210_ISOC_ACTIVE) == 0)) { + desc->status = 0; + desc->actual_length = FOTG210_ITD_LENGTH(t); + urb->actual_length += desc->actual_length; + } else { + /* URB was too late */ + desc->status = -EXDEV; + } + } + + /* 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; + fotg210_urb_done(fotg210, urb, 0); + retval = true; + urb = NULL; + + --fotg210->isoc_count; + disable_periodic(fotg210); + + if (unlikely(list_is_singular(&stream->td_list))) { + fotg210_to_hcd(fotg210)->self.bandwidth_allocated + -= stream->bandwidth; + fotg210_dbg(fotg210, + "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, + &fotg210->cached_itd_list); + start_free_itds(fotg210); + } + + return retval; +} + +static int itd_submit(struct fotg210_hcd *fotg210, struct urb *urb, + gfp_t mem_flags) +{ + int status = -EINVAL; + unsigned long flags; + struct fotg210_iso_stream *stream; + + /* Get iso_stream head */ + stream = iso_stream_find(fotg210, urb); + if (unlikely(stream == NULL)) { + fotg210_dbg(fotg210, "can't get iso stream\n"); + return -ENOMEM; + } + if (unlikely(urb->interval != stream->interval && + fotg210_port_speed(fotg210, 0) == + USB_PORT_STAT_HIGH_SPEED)) { + fotg210_dbg(fotg210, "can't change iso interval %d --> %d\n", + stream->interval, urb->interval); + goto done; + } + +#ifdef FOTG210_URB_TRACE + fotg210_dbg(fotg210, + "%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, fotg210, urb, mem_flags); + if (unlikely(status < 0)) { + fotg210_dbg(fotg210, "can't init itds\n"); + goto done; + } + + /* schedule ... need to lock */ + spin_lock_irqsave(&fotg210->lock, flags); + if (unlikely(!HCD_HW_ACCESSIBLE(fotg210_to_hcd(fotg210)))) { + status = -ESHUTDOWN; + goto done_not_linked; + } + status = usb_hcd_link_urb_to_ep(fotg210_to_hcd(fotg210), urb); + if (unlikely(status)) + goto done_not_linked; + status = iso_stream_schedule(fotg210, urb, stream); + if (likely(status == 0)) + itd_link_urb(fotg210, urb, fotg210->periodic_size << 3, stream); + else + usb_hcd_unlink_urb_from_ep(fotg210_to_hcd(fotg210), urb); +done_not_linked: + if (status < 0) { + iso_sched_free(stream, urb->hcpriv); + urb->hcpriv = NULL; + } + spin_unlock_irqrestore(&fotg210->lock, flags); +done: + return status; +} + +static inline int scan_frame_queue(struct fotg210_hcd *fotg210, unsigned frame, + unsigned now_frame, bool live) +{ + unsigned uf; + bool modified; + union fotg210_shadow q, *q_p; + __hc32 type, *hw_p; + + /* scan each element in frame's queue for completions */ + q_p = &fotg210->pshadow[frame]; + hw_p = &fotg210->periodic[frame]; + q.ptr = q_p->ptr; + type = Q_NEXT_TYPE(fotg210, *hw_p); + modified = false; + + while (q.ptr) { + switch (hc32_to_cpu(fotg210, 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(fotg210)) + break; + } + if (uf < 8) { + q_p = &q.itd->itd_next; + hw_p = &q.itd->hw_next; + type = Q_NEXT_TYPE(fotg210, + 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; + *hw_p = q.itd->hw_next; + type = Q_NEXT_TYPE(fotg210, q.itd->hw_next); + wmb(); + modified = itd_complete(fotg210, q.itd); + q = *q_p; + break; + default: + fotg210_dbg(fotg210, "corrupt type %d frame %d shadow %p\n", + type, frame, q.ptr); + fallthrough; + 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 && fotg210->isoc_count > 0)) + return -EINVAL; + } + return 0; +} + +static void scan_isoc(struct fotg210_hcd *fotg210) +{ + unsigned uf, now_frame, frame, ret; + unsigned fmask = fotg210->periodic_size - 1; + bool 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 (fotg210->rh_state >= FOTG210_RH_RUNNING) { + uf = fotg210_read_frame_index(fotg210); + now_frame = (uf >> 3) & fmask; + live = true; + } else { + now_frame = (fotg210->next_frame - 1) & fmask; + live = false; + } + fotg210->now_frame = now_frame; + + frame = fotg210->next_frame; + for (;;) { + ret = 1; + while (ret != 0) + ret = scan_frame_queue(fotg210, frame, + now_frame, live); + + /* Stop when we have reached the current frame */ + if (frame == now_frame) + break; + frame = (frame + 1) & fmask; + } + fotg210->next_frame = now_frame; +} + +/* Display / Set uframe_periodic_max + */ +static ssize_t uframe_periodic_max_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fotg210_hcd *fotg210; + + fotg210 = hcd_to_fotg210(bus_to_hcd(dev_get_drvdata(dev))); + return sysfs_emit(buf, "%d\n", fotg210->uframe_periodic_max); +} + +static ssize_t uframe_periodic_max_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fotg210_hcd *fotg210; + unsigned uframe_periodic_max; + unsigned frame, uframe; + unsigned short allocated_max; + unsigned long flags; + ssize_t ret; + + fotg210 = hcd_to_fotg210(bus_to_hcd(dev_get_drvdata(dev))); + + ret = kstrtouint(buf, 0, &uframe_periodic_max); + if (ret) + return ret; + + if (uframe_periodic_max < 100 || uframe_periodic_max >= 125) { + fotg210_info(fotg210, "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(&fotg210->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 < fotg210->uframe_periodic_max) { + allocated_max = 0; + + for (frame = 0; frame < fotg210->periodic_size; ++frame) + for (uframe = 0; uframe < 7; ++uframe) + allocated_max = max(allocated_max, + periodic_usecs(fotg210, frame, + uframe)); + + if (allocated_max > uframe_periodic_max) { + fotg210_info(fotg210, + "cannot decrease uframe_periodic_max because periodic bandwidth is already allocated (%u > %u)\n", + allocated_max, uframe_periodic_max); + goto out_unlock; + } + } + + /* increasing is always ok */ + + fotg210_info(fotg210, + "setting max periodic bandwidth to %u%% (== %u usec/uframe)\n", + 100 * uframe_periodic_max/125, uframe_periodic_max); + + if (uframe_periodic_max != 100) + fotg210_warn(fotg210, "max periodic bandwidth set is non-standard\n"); + + fotg210->uframe_periodic_max = uframe_periodic_max; + ret = count; + +out_unlock: + spin_unlock_irqrestore(&fotg210->lock, flags); + return ret; +} + +static DEVICE_ATTR_RW(uframe_periodic_max); + +static inline int create_sysfs_files(struct fotg210_hcd *fotg210) +{ + struct device *controller = fotg210_to_hcd(fotg210)->self.controller; + + return device_create_file(controller, &dev_attr_uframe_periodic_max); +} + +static inline void remove_sysfs_files(struct fotg210_hcd *fotg210) +{ + struct device *controller = fotg210_to_hcd(fotg210)->self.controller; + + device_remove_file(controller, &dev_attr_uframe_periodic_max); +} +/* 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 fotg210_turn_off_all_ports(struct fotg210_hcd *fotg210) +{ + u32 __iomem *status_reg = &fotg210->regs->port_status; + + fotg210_writel(fotg210, PORT_RWC_BITS, status_reg); +} + +/* 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 fotg210_silence_controller(struct fotg210_hcd *fotg210) +{ + fotg210_halt(fotg210); + + spin_lock_irq(&fotg210->lock); + fotg210->rh_state = FOTG210_RH_HALTED; + fotg210_turn_off_all_ports(fotg210); + spin_unlock_irq(&fotg210->lock); +} + +/* fotg210_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 fotg210_shutdown(struct usb_hcd *hcd) +{ + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); + + spin_lock_irq(&fotg210->lock); + fotg210->shutdown = true; + fotg210->rh_state = FOTG210_RH_STOPPING; + fotg210->enabled_hrtimer_events = 0; + spin_unlock_irq(&fotg210->lock); + + fotg210_silence_controller(fotg210); + + hrtimer_cancel(&fotg210->hrtimer); +} + +/* fotg210_work is called from some interrupts, timers, and so on. + * it calls driver completion functions, after dropping fotg210->lock. + */ +static void fotg210_work(struct fotg210_hcd *fotg210) +{ + /* another CPU may drop fotg210->lock during a schedule scan while + * it reports urb completions. this flag guards against bogus + * attempts at re-entrant schedule scanning. + */ + if (fotg210->scanning) { + fotg210->need_rescan = true; + return; + } + fotg210->scanning = true; + +rescan: + fotg210->need_rescan = false; + if (fotg210->async_count) + scan_async(fotg210); + if (fotg210->intr_count > 0) + scan_intr(fotg210); + if (fotg210->isoc_count > 0) + scan_isoc(fotg210); + if (fotg210->need_rescan) + goto rescan; + fotg210->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(fotg210); +} + +/* Called when the fotg210_hcd module is removed. + */ +static void fotg210_stop(struct usb_hcd *hcd) +{ + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); + + fotg210_dbg(fotg210, "stop\n"); + + /* no more interrupts ... */ + + spin_lock_irq(&fotg210->lock); + fotg210->enabled_hrtimer_events = 0; + spin_unlock_irq(&fotg210->lock); + + fotg210_quiesce(fotg210); + fotg210_silence_controller(fotg210); + fotg210_reset(fotg210); + + hrtimer_cancel(&fotg210->hrtimer); + remove_sysfs_files(fotg210); + remove_debug_files(fotg210); + + /* root hub is shut down separately (first, when possible) */ + spin_lock_irq(&fotg210->lock); + end_free_itds(fotg210); + spin_unlock_irq(&fotg210->lock); + fotg210_mem_cleanup(fotg210); + +#ifdef FOTG210_STATS + fotg210_dbg(fotg210, "irq normal %ld err %ld iaa %ld (lost %ld)\n", + fotg210->stats.normal, fotg210->stats.error, + fotg210->stats.iaa, fotg210->stats.lost_iaa); + fotg210_dbg(fotg210, "complete %ld unlink %ld\n", + fotg210->stats.complete, fotg210->stats.unlink); +#endif + + dbg_status(fotg210, "fotg210_stop completed", + fotg210_readl(fotg210, &fotg210->regs->status)); +} + +/* one-time init, only for memory state */ +static int hcd_fotg210_init(struct usb_hcd *hcd) +{ + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); + u32 temp; + int retval; + u32 hcc_params; + struct fotg210_qh_hw *hw; + + spin_lock_init(&fotg210->lock); + + /* + * keep io watchdog by default, those good HCDs could turn off it later + */ + fotg210->need_io_watchdog = 1; + + hrtimer_setup(&fotg210->hrtimer, fotg210_hrtimer_func, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + fotg210->next_hrtimer_event = FOTG210_HRTIMER_NO_EVENT; + + hcc_params = fotg210_readl(fotg210, &fotg210->caps->hcc_params); + + /* + * by default set standard 80% (== 100 usec/uframe) max periodic + * bandwidth as required by USB 2.0 + */ + fotg210->uframe_periodic_max = 100; + + /* + * hw default: 1K periodic list heads, one per frame. + * periodic_size can shrink by USBCMD update if hcc_params allows. + */ + fotg210->periodic_size = DEFAULT_I_TDPS; + INIT_LIST_HEAD(&fotg210->intr_qh_list); + INIT_LIST_HEAD(&fotg210->cached_itd_list); + + if (HCC_PGM_FRAMELISTLEN(hcc_params)) { + /* periodic schedule size can be smaller than default */ + switch (FOTG210_TUNE_FLS) { + case 0: + fotg210->periodic_size = 1024; + break; + case 1: + fotg210->periodic_size = 512; + break; + case 2: + fotg210->periodic_size = 256; + break; + default: + BUG(); + } + } + retval = fotg210_mem_init(fotg210, GFP_KERNEL); + if (retval < 0) + return retval; + + /* controllers may cache some of the periodic schedule ... */ + fotg210->i_thresh = 2; + + /* + * 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. + */ + fotg210->async->qh_next.qh = NULL; + hw = fotg210->async->hw; + hw->hw_next = QH_NEXT(fotg210, fotg210->async->qh_dma); + hw->hw_info1 = cpu_to_hc32(fotg210, QH_HEAD); + hw->hw_token = cpu_to_hc32(fotg210, QTD_STS_HALT); + hw->hw_qtd_next = FOTG210_LIST_END(fotg210); + fotg210->async->qh_state = QH_STATE_LINKED; + hw->hw_alt_next = QTD_NEXT(fotg210, fotg210->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_t(unsigned, park, 3); + temp |= CMD_PARK; + temp |= park << 8; + } + fotg210_dbg(fotg210, "park %d\n", park); + } + if (HCC_PGM_FRAMELISTLEN(hcc_params)) { + /* periodic schedule size can be smaller than default */ + temp &= ~(3 << 2); + temp |= (FOTG210_TUNE_FLS << 2); + } + fotg210->command = temp; + + /* Accept arbitrarily long scatter-gather lists */ + if (!hcd->localmem_pool) + hcd->self.sg_tablesize = ~0; + return 0; +} + +/* start HC running; it's halted, hcd_fotg210_init() has been run (once) */ +static int fotg210_run(struct usb_hcd *hcd) +{ + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); + u32 temp; + + hcd->uses_new_polling = 1; + + /* EHCI spec section 4.1 */ + + fotg210_writel(fotg210, fotg210->periodic_dma, + &fotg210->regs->frame_list); + fotg210_writel(fotg210, (u32)fotg210->async->qh_dma, + &fotg210->regs->async_next); + + /* + * hcc_params controls whether fotg210->regs->segment must (!!!) + * be used; it constrains QH/ITD/SITD and QTD locations. + * dma_pool consistent memory always uses segment zero. + * streaming mappings for I/O buffers, like dma_map_single(), + * can return segments above 4GB, if the device allows. + * + * NOTE: the dma mask is visible through dev->dma_mask, 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. + */ + fotg210_readl(fotg210, &fotg210->caps->hcc_params); + + /* + * Philips, Intel, and maybe others need CMD_RUN before the + * root hub will detect new devices (why?); NEC doesn't + */ + fotg210->command &= ~(CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET); + fotg210->command |= CMD_RUN; + fotg210_writel(fotg210, fotg210->command, &fotg210->regs->command); + dbg_cmd(fotg210, "init", fotg210->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); + fotg210->rh_state = FOTG210_RH_RUNNING; + /* unblock posted writes */ + fotg210_readl(fotg210, &fotg210->regs->command); + usleep_range(5000, 10000); + up_write(&ehci_cf_port_reset_rwsem); + fotg210->last_periodic_enable = ktime_get_real(); + + temp = HC_VERSION(fotg210, + fotg210_readl(fotg210, &fotg210->caps->hc_capbase)); + fotg210_info(fotg210, + "USB %x.%x started, EHCI %x.%02x\n", + ((fotg210->sbrn & 0xf0) >> 4), (fotg210->sbrn & 0x0f), + temp >> 8, temp & 0xff); + + fotg210_writel(fotg210, INTR_MASK, + &fotg210->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(fotg210); + create_sysfs_files(fotg210); + + return 0; +} + +static int fotg210_setup(struct usb_hcd *hcd) +{ + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); + int retval; + + fotg210->regs = (void __iomem *)fotg210->caps + + HC_LENGTH(fotg210, + fotg210_readl(fotg210, &fotg210->caps->hc_capbase)); + dbg_hcs_params(fotg210, "reset"); + dbg_hcc_params(fotg210, "reset"); + + /* cache this readonly data; minimize chip reads */ + fotg210->hcs_params = fotg210_readl(fotg210, + &fotg210->caps->hcs_params); + + fotg210->sbrn = HCD_USB2; + + /* data structure init */ + retval = hcd_fotg210_init(hcd); + if (retval) + return retval; + + retval = fotg210_halt(fotg210); + if (retval) + return retval; + + fotg210_reset(fotg210); + + return 0; +} + +static irqreturn_t fotg210_irq(struct usb_hcd *hcd) +{ + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); + u32 status, masked_status, pcd_status = 0, cmd; + int bh; + + spin_lock(&fotg210->lock); + + status = fotg210_readl(fotg210, &fotg210->regs->status); + + /* e.g. cardbus physical eject */ + if (status == ~(u32) 0) { + fotg210_dbg(fotg210, "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(fotg210->rh_state == FOTG210_RH_HALTED)) { + spin_unlock(&fotg210->lock); + return IRQ_NONE; + } + + /* clear (just) interrupts */ + fotg210_writel(fotg210, masked_status, &fotg210->regs->status); + cmd = fotg210_readl(fotg210, &fotg210->regs->command); + bh = 0; + + /* unrequested/ignored: Frame List Rollover */ + dbg_status(fotg210, "irq", status); + + /* 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)) + INCR(fotg210->stats.normal); + else + INCR(fotg210->stats.error); + bh = 1; + } + + /* complete the unlinking of some qh [4.15.2.3] */ + if (status & STS_IAA) { + + /* Turn off the IAA watchdog */ + fotg210->enabled_hrtimer_events &= + ~BIT(FOTG210_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 (fotg210->next_hrtimer_event == FOTG210_HRTIMER_IAA_WATCHDOG) + ++fotg210->next_hrtimer_event; + + /* guard against (alleged) silicon errata */ + if (cmd & CMD_IAAD) + fotg210_dbg(fotg210, "IAA with IAAD still set?\n"); + if (fotg210->async_iaa) { + INCR(fotg210->stats.iaa); + end_unlink_async(fotg210); + } else + fotg210_dbg(fotg210, "IAA with nothing unlinked?\n"); + } + + /* remote wakeup [4.3.1] */ + if (status & STS_PCD) { + int pstatus; + u32 __iomem *status_reg = &fotg210->regs->port_status; + + /* kick root hub later */ + pcd_status = status; + + /* resume root hub? */ + if (fotg210->rh_state == FOTG210_RH_SUSPENDED) + usb_hcd_resume_root_hub(hcd); + + pstatus = fotg210_readl(fotg210, status_reg); + + if (test_bit(0, &fotg210->suspended_ports) && + ((pstatus & PORT_RESUME) || + !(pstatus & PORT_SUSPEND)) && + (pstatus & PORT_PE) && + fotg210->reset_done[0] == 0) { + + /* start 20 msec resume signaling from this port, + * and make hub_wq collect PORT_STAT_C_SUSPEND to + * stop that signaling. Use 5 ms extra for safety, + * like usb_port_resume() does. + */ + fotg210->reset_done[0] = jiffies + msecs_to_jiffies(25); + set_bit(0, &fotg210->resuming_ports); + fotg210_dbg(fotg210, "port 1 remote wakeup\n"); + mod_timer(&hcd->rh_timer, fotg210->reset_done[0]); + } + } + + /* PCI errors [4.15.2.4] */ + if (unlikely((status & STS_FATAL) != 0)) { + fotg210_err(fotg210, "fatal error\n"); + dbg_cmd(fotg210, "fatal", cmd); + dbg_status(fotg210, "fatal", status); +dead: + usb_hc_died(hcd); + + /* Don't let the controller do anything more */ + fotg210->shutdown = true; + fotg210->rh_state = FOTG210_RH_STOPPING; + fotg210->command &= ~(CMD_RUN | CMD_ASE | CMD_PSE); + fotg210_writel(fotg210, fotg210->command, + &fotg210->regs->command); + fotg210_writel(fotg210, 0, &fotg210->regs->intr_enable); + fotg210_handle_controller_death(fotg210); + + /* Handle completions when the controller stops */ + bh = 0; + } + + if (bh) + fotg210_work(fotg210); + spin_unlock(&fotg210->lock); + 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 fotg210_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct fotg210_hcd *fotg210 = hcd_to_fotg210(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(fotg210, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return submit_async(fotg210, urb, &qtd_list, mem_flags); + + case PIPE_INTERRUPT: + if (!qh_urb_transaction(fotg210, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return intr_submit(fotg210, urb, &qtd_list, mem_flags); + + case PIPE_ISOCHRONOUS: + return itd_submit(fotg210, urb, mem_flags); + } +} + +/* remove from hardware lists + * completions normally happen asynchronously + */ + +static int fotg210_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); + struct fotg210_qh *qh; + unsigned long flags; + int rc; + + spin_lock_irqsave(&fotg210->lock, flags); + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc) + goto done; + + switch (usb_pipetype(urb->pipe)) { + /* case PIPE_CONTROL: */ + /* case PIPE_BULK:*/ + default: + qh = (struct fotg210_qh *) urb->hcpriv; + if (!qh) + break; + switch (qh->qh_state) { + case QH_STATE_LINKED: + case QH_STATE_COMPLETING: + start_unlink_async(fotg210, qh); + 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(fotg210, qh); + break; + } + break; + + case PIPE_INTERRUPT: + qh = (struct fotg210_qh *) urb->hcpriv; + if (!qh) + break; + switch (qh->qh_state) { + case QH_STATE_LINKED: + case QH_STATE_COMPLETING: + start_unlink_intr(fotg210, qh); + break; + case QH_STATE_IDLE: + qh_completions(fotg210, qh); + break; + default: + fotg210_dbg(fotg210, "bogus qh %p state %d\n", + qh, qh->qh_state); + goto done; + } + break; + + case PIPE_ISOCHRONOUS: + /* itd... */ + + /* wait till next completion, do it then. */ + /* completion irqs can wait up to 1024 msec, */ + break; + } +done: + spin_unlock_irqrestore(&fotg210->lock, flags); + return rc; +} + +/* bulk qh holds the data toggle */ + +static void fotg210_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); + unsigned long flags; + struct fotg210_qh *qh, *tmp; + + /* ASSERT: any requests/urbs are being unlinked */ + /* ASSERT: nobody can be submitting urbs for this any more */ + +rescan: + spin_lock_irqsave(&fotg210->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 fotg210_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; + } + + if (fotg210->rh_state < FOTG210_RH_RUNNING) + qh->qh_state = QH_STATE_IDLE; + switch (qh->qh_state) { + case QH_STATE_LINKED: + case QH_STATE_COMPLETING: + for (tmp = fotg210->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(fotg210, qh); + fallthrough; + case QH_STATE_UNLINK: /* wait for hw to finish? */ + case QH_STATE_UNLINK_WAIT: +idle_timeout: + spin_unlock_irqrestore(&fotg210->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(fotg210, qh); + break; + } + fallthrough; + default: + /* caller was supposed to have unlinked any requests; + * that's not our job. just leak this memory. + */ + fotg210_err(fotg210, "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(&fotg210->lock, flags); +} + +static void fotg210_endpoint_reset(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); + struct fotg210_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(&fotg210->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 if (qh->qh_state == QH_STATE_LINKED || + qh->qh_state == QH_STATE_COMPLETING) { + + /* The toggle value in the QH can't be updated + * while the QH is active. Unlink it now; + * re-linking will call qh_refresh(). + */ + if (eptype == USB_ENDPOINT_XFER_BULK) + start_unlink_async(fotg210, qh); + else + start_unlink_intr(fotg210, qh); + } + } + spin_unlock_irqrestore(&fotg210->lock, flags); +} + +static int fotg210_get_frame(struct usb_hcd *hcd) +{ + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); + + return (fotg210_read_frame_index(fotg210) >> 3) % + fotg210->periodic_size; +} + +/* The EHCI in ChipIdea HDRC cannot be a separate module or device, + * because its registers (and irq) are shared between host/gadget/otg + * functions and in order to facilitate role switching we cannot + * give the fotg210 driver exclusive access to those. + */ + +static const struct hc_driver fotg210_fotg210_hc_driver = { + .description = hcd_name, + .product_desc = "Faraday USB2.0 Host Controller", + .hcd_priv_size = sizeof(struct fotg210_hcd), + + /* + * generic hardware linkage + */ + .irq = fotg210_irq, + .flags = HCD_MEMORY | HCD_DMA | HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = hcd_fotg210_init, + .start = fotg210_run, + .stop = fotg210_stop, + .shutdown = fotg210_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = fotg210_urb_enqueue, + .urb_dequeue = fotg210_urb_dequeue, + .endpoint_disable = fotg210_endpoint_disable, + .endpoint_reset = fotg210_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = fotg210_get_frame, + + /* + * root hub support + */ + .hub_status_data = fotg210_hub_status_data, + .hub_control = fotg210_hub_control, + .bus_suspend = fotg210_bus_suspend, + .bus_resume = fotg210_bus_resume, + + .relinquish_port = fotg210_relinquish_port, + .port_handed_over = fotg210_port_handed_over, + + .clear_tt_buffer_complete = fotg210_clear_tt_buffer_complete, +}; + +static void fotg210_init(struct fotg210_hcd *fotg210) +{ + u32 value; + + iowrite32(GMIR_MDEV_INT | GMIR_MOTG_INT | GMIR_INT_POLARITY, + &fotg210->regs->gmir); + + value = ioread32(&fotg210->regs->otgcsr); + value &= ~OTGCSR_A_BUS_DROP; + value |= OTGCSR_A_BUS_REQ; + iowrite32(value, &fotg210->regs->otgcsr); +} + +/* + * fotg210_hcd_probe - initialize faraday FOTG210 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. + */ +int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg) +{ + struct device *dev = &pdev->dev; + struct usb_hcd *hcd; + int irq; + int retval; + struct fotg210_hcd *fotg210; + + if (usb_disabled()) + return -ENODEV; + + pdev->dev.power.power_state = PMSG_ON; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + hcd = usb_create_hcd(&fotg210_fotg210_hc_driver, dev, + dev_name(dev)); + if (!hcd) { + retval = dev_err_probe(dev, -ENOMEM, "failed to create hcd\n"); + goto fail_create_hcd; + } + + hcd->has_tt = 1; + + hcd->regs = fotg->base; + + hcd->rsrc_start = fotg->res->start; + hcd->rsrc_len = resource_size(fotg->res); + + fotg210 = hcd_to_fotg210(hcd); + + fotg210->fotg = fotg; + fotg210->caps = hcd->regs; + + retval = fotg210_setup(hcd); + if (retval) + goto failed_put_hcd; + + fotg210_init(fotg210); + + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval) { + dev_err_probe(dev, retval, "failed to add hcd\n"); + goto failed_put_hcd; + } + device_wakeup_enable(hcd->self.controller); + platform_set_drvdata(pdev, hcd); + + return retval; + +failed_put_hcd: + usb_put_hcd(hcd); +fail_create_hcd: + return dev_err_probe(dev, retval, "init %s fail\n", dev_name(dev)); +} + +/* + * fotg210_hcd_remove - shutdown processing for EHCI HCDs + * @dev: USB Host Controller being removed + * + */ +int fotg210_hcd_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + + return 0; +} + +int __init fotg210_hcd_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_debug("%s: block sizes: qh %zd qtd %zd itd %zd\n", + hcd_name, sizeof(struct fotg210_qh), + sizeof(struct fotg210_qtd), + sizeof(struct fotg210_itd)); + + fotg210_debug_root = debugfs_create_dir("fotg210", usb_debug_root); + + return 0; +} + +void __exit fotg210_hcd_cleanup(void) +{ + debugfs_remove(fotg210_debug_root); +} diff --git a/drivers/usb/fotg210/fotg210-hcd.h b/drivers/usb/fotg210/fotg210-hcd.h new file mode 100644 index 000000000..13c934298 --- /dev/null +++ b/drivers/usb/fotg210/fotg210-hcd.h @@ -0,0 +1,689 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LINUX_FOTG210_H +#define __LINUX_FOTG210_H + +#include + +/* definitions used for the EHCI driver */ + +/* + * __hc32 and __hc16 are "Host Controller" types, they may be equivalent to + * __leXX (normally) or __beXX (given FOTG210_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. + */ +#define __hc32 __le32 +#define __hc16 __le16 + +/* statistics can be kept for tuning/monitoring */ +struct fotg210_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; +}; + +/* fotg210_hcd->lock guards shared data against other CPUs: + * fotg210_hcd: async, unlink, periodic (and shadow), ... + * usb_host_endpoint: hcpriv + * fotg210_qh: qh_next, qtd_list + * fotg210_qtd: qtd_list + * + * Also, hold this lock when talking to HC registers or + * when updating hw_* fields in shared qh/qtd/... structures. + */ + +#define FOTG210_MAX_ROOT_PORTS 1 /* see HCS_N_PORTS */ + +/* + * fotg210_rh_state values of FOTG210_RH_RUNNING or above mean that the + * controller may be doing DMA. Lower values mean there's no DMA. + */ +enum fotg210_rh_state { + FOTG210_RH_HALTED, + FOTG210_RH_SUSPENDED, + FOTG210_RH_RUNNING, + FOTG210_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 fotg210_hrtimer_event { + FOTG210_HRTIMER_POLL_ASS, /* Poll for async schedule off */ + FOTG210_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */ + FOTG210_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */ + FOTG210_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */ + FOTG210_HRTIMER_FREE_ITDS, /* Wait for unused iTDs and siTDs */ + FOTG210_HRTIMER_ASYNC_UNLINKS, /* Unlink empty async QHs */ + FOTG210_HRTIMER_IAA_WATCHDOG, /* Handle lost IAA interrupts */ + FOTG210_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ + FOTG210_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ + FOTG210_HRTIMER_IO_WATCHDOG, /* Check for missing IRQs */ + FOTG210_HRTIMER_NUM_EVENTS /* Must come last */ +}; +#define FOTG210_HRTIMER_NO_EVENT 99 + +struct fotg210_hcd { /* one per controller */ + /* timing support */ + enum fotg210_hrtimer_event next_hrtimer_event; + unsigned enabled_hrtimer_events; + ktime_t hr_timeouts[FOTG210_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 fotg210_caps __iomem *caps; + struct fotg210_regs __iomem *regs; + struct ehci_dbg_port __iomem *debug; + + __u32 hcs_params; /* cached register copy */ + spinlock_t lock; + enum fotg210_rh_state rh_state; + + /* general schedule support */ + bool scanning:1; + bool need_rescan:1; + bool intr_unlinking:1; + bool async_unlinking:1; + bool shutdown:1; + struct fotg210_qh *qh_scan_next; + + /* async schedule support */ + struct fotg210_qh *async; + struct fotg210_qh *dummy; /* For AMD quirk use */ + struct fotg210_qh *async_unlink; + struct fotg210_qh *async_unlink_last; + struct fotg210_qh *async_iaa; + 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 fotg210_shadow *pshadow; /* mirror hw periodic table */ + struct fotg210_qh *intr_unlink; + struct fotg210_qh *intr_unlink_last; + unsigned intr_unlink_cycle; + unsigned now_frame; /* frame from HC hardware */ + unsigned next_frame; /* scan periodic, start here */ + unsigned intr_count; /* intr activity count */ + unsigned isoc_count; /* isoc activity count */ + unsigned periodic_count; /* periodic activity count */ + /* max periodic time per uframe */ + unsigned uframe_periodic_max; + + + /* list of itds completed while now_frame was still active */ + struct list_head cached_itd_list; + struct fotg210_itd *last_itd_to_free; + + /* per root hub port */ + unsigned long reset_done[FOTG210_MAX_ROOT_PORTS]; + + /* bit vectors (one bit per port) + * which ports were already suspended at the start of a bus suspend + */ + unsigned long bus_suspended; + + /* which ports are edicated to the companion controller */ + unsigned long companion_ports; + + /* which ports are owned by the companion during a bus suspend */ + unsigned long owned_ports; + + /* which ports have the change-suspend feature turned on */ + unsigned long port_c_suspend; + + /* which ports are suspended */ + unsigned long suspended_ports; + + /* which ports have started to resume */ + unsigned long resuming_ports; + + /* 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 */ + + unsigned random_frame; + unsigned long next_statechange; + ktime_t last_periodic_enable; + u32 command; + + /* SILICON QUIRKS */ + unsigned need_io_watchdog:1; + unsigned fs_i_thresh:1; /* Intel iso scheduling */ + + u8 sbrn; /* packed release number */ + + /* irq statistics */ +#ifdef FOTG210_STATS + struct fotg210_stats stats; +# define INCR(x) ((x)++) +#else +# define INCR(x) do {} while (0) +#endif + + struct fotg210 *fotg; /* Overarching FOTG210 device */ + /* silicon clock */ + struct clk *pclk; +}; + +/* convert between an HCD pointer and the corresponding FOTG210_HCD */ +static inline struct fotg210_hcd *hcd_to_fotg210(struct usb_hcd *hcd) +{ + return (struct fotg210_hcd *)(hcd->hcd_priv); +} +static inline struct usb_hcd *fotg210_to_hcd(struct fotg210_hcd *fotg210) +{ + return container_of((void *) fotg210, struct usb_hcd, hcd_priv); +} + +/*-------------------------------------------------------------------------*/ + +/* EHCI register interface, corresponds to EHCI Revision 0.95 specification */ + +/* Section 2.2 Host Controller Capability Registers */ +struct fotg210_caps { + /* these fields are specified as 8 and 16 bit registers, + * but some hosts can't perform 8 or 16 bit PCI accesses. + * some hosts treat caplength and hciversion as parts of a 32-bit + * register, others treat them as two separate registers, this + * affects the memory map for big endian controllers. + */ + u32 hc_capbase; +#define HC_LENGTH(fotg210, p) (0x00ff&((p) >> /* bits 7:0 / offset 00h */ \ + (fotg210_big_endian_capbase(fotg210) ? 24 : 0))) +#define HC_VERSION(fotg210, p) (0xffff&((p) >> /* bits 31:16 / offset 02h */ \ + (fotg210_big_endian_capbase(fotg210) ? 0 : 16))) + u32 hcs_params; /* HCSPARAMS - offset 0x4 */ +#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ + + u32 hcc_params; /* HCCPARAMS - offset 0x8 */ +#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*/ + u8 portroute[8]; /* nibbles for routing - offset 0xC */ +}; + + +/* Section 2.3 Host Controller Operational Registers */ +struct fotg210_regs { + + /* USBCMD: offset 0x00 */ + u32 command; + +/* EHCI 1.1 addendum */ +/* 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_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, ...) */ + + /* 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 reserved1; + /* PORTSC: offset 0x20 */ + u32 port_status; +/* 31:23 reserved */ +#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_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) + u32 reserved2[19]; + + /* OTGCSR: offet 0x70 */ + u32 otgcsr; +#define OTGCSR_HOST_SPD_TYP (3 << 22) +#define OTGCSR_A_BUS_DROP (1 << 5) +#define OTGCSR_A_BUS_REQ (1 << 4) + + /* OTGISR: offset 0x74 */ + u32 otgisr; +#define OTGISR_OVC (1 << 10) + + u32 reserved3[15]; + + /* GMIR: offset 0xB4 */ + u32 gmir; +#define GMIR_INT_POLARITY (1 << 3) /*Active High*/ +#define GMIR_MHC_INT (1 << 2) +#define GMIR_MOTG_INT (1 << 1) +#define GMIR_MDEV_INT (1 << 0) +}; + +/*-------------------------------------------------------------------------*/ + +#define QTD_NEXT(fotg210, dma) cpu_to_hc32(fotg210, (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 fotg210_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(fotg210) cpu_to_hc32(fotg210, QTD_STS_ACTIVE) +#define HALT_BIT(fotg210) cpu_to_hc32(fotg210, QTD_STS_HALT) +#define STATUS_BIT(fotg210) cpu_to_hc32(fotg210, 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 */ +} __aligned(32); + +/* mask NakCnt+T in qh->hw_alt_next */ +#define QTD_MASK(fotg210) cpu_to_hc32(fotg210, ~0x1f) + +#define IS_SHORT_READ(token) (QTD_LENGTH(token) != 0 && QTD_PID(token) == 1) + +/*-------------------------------------------------------------------------*/ + +/* type tag from {qh,itd,fstn}->hw_next */ +#define Q_NEXT_TYPE(fotg210, dma) ((dma) & cpu_to_hc32(fotg210, 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(fotg210, dma) \ + (cpu_to_hc32(fotg210, (((u32)dma)&~0x01f)|Q_TYPE_QH)) + +/* for periodic/async schedules and qtd lists, mark end of list */ +#define FOTG210_LIST_END(fotg210) \ + cpu_to_hc32(fotg210, 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 fotg210_shadow { + struct fotg210_qh *qh; /* Q_TYPE_QH */ + struct fotg210_itd *itd; /* Q_TYPE_ITD */ + struct fotg210_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 fotg210_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 fotg210_qtd) */ + __hc32 hw_qtd_next; + __hc32 hw_alt_next; + __hc32 hw_token; + __hc32 hw_buf[5]; + __hc32 hw_buf_hi[5]; +} __aligned(32); + +struct fotg210_qh { + struct fotg210_qh_hw *hw; /* Must come first */ + /* the rest is HCD-private */ + dma_addr_t qh_dma; /* address of qh */ + union fotg210_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 fotg210_qtd *dummy; + struct fotg210_qh *unlink_next; /* next on unlink list */ + + unsigned unlink_cycle; + + u8 needs_rescan; /* Dequeue during giveback */ + 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 */ +}; + +/*-------------------------------------------------------------------------*/ + +/* description of one iso transaction (up to 3 KB data if highspeed) */ +struct fotg210_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 fotg210_iso_sched { + struct list_head td_list; + unsigned span; + struct fotg210_iso_packet packet[]; +}; + +/* + * fotg210_iso_stream - groups all (s)itds for this endpoint. + * acts like a qh would, if EHCI had them for ISO. + */ +struct fotg210_iso_stream { + /* first field matches fotg210_hq, but is NULL */ + struct fotg210_qh_hw *hw; + + u8 bEndpointAddress; + u8 highspeed; + struct list_head td_list; /* queued itds */ + struct list_head free_list; /* list of unused itds */ + 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 fotg210_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 FOTG210_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ +#define FOTG210_ISOC_BUF_ERR (1<<30) /* Data buffer error */ +#define FOTG210_ISOC_BABBLE (1<<29) /* babble detected */ +#define FOTG210_ISOC_XACTERR (1<<28) /* XactErr - transaction error */ +#define FOTG210_ITD_LENGTH(tok) (((tok)>>16) & 0x0fff) +#define FOTG210_ITD_IOC (1 << 15) /* interrupt on complete */ + +#define ITD_ACTIVE(fotg210) cpu_to_hc32(fotg210, FOTG210_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 fotg210_shadow itd_next; /* ptr to periodic q entry */ + + struct urb *urb; + struct fotg210_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 */ +} __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 fotg210_fstn { + __hc32 hw_next; /* any periodic q entry */ + __hc32 hw_prev; /* qh or FOTG210_LIST_END */ + + /* the rest is HCD-private */ + dma_addr_t fstn_dma; + union fotg210_shadow fstn_next; /* ptr to periodic q entry */ +} __aligned(32); + +/*-------------------------------------------------------------------------*/ + +/* Prepare the PORTSC wakeup flags during controller suspend/resume */ + +#define fotg210_prepare_ports_for_controller_suspend(fotg210, do_wakeup) \ + fotg210_adjust_port_wakeup_flags(fotg210, true, do_wakeup) + +#define fotg210_prepare_ports_for_controller_resume(fotg210) \ + fotg210_adjust_port_wakeup_flags(fotg210, false, false) + +/*-------------------------------------------------------------------------*/ + +/* + * 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). + */ + +static inline unsigned int +fotg210_get_speed(struct fotg210_hcd *fotg210, unsigned int portsc) +{ + return (readl(&fotg210->regs->otgcsr) + & OTGCSR_HOST_SPD_TYP) >> 22; +} + +/* Returns the speed of a device attached to a port on the root hub. */ +static inline unsigned int +fotg210_port_speed(struct fotg210_hcd *fotg210, unsigned int portsc) +{ + switch (fotg210_get_speed(fotg210, portsc)) { + case 0: + return 0; + case 1: + return USB_PORT_STAT_LOW_SPEED; + case 2: + default: + return USB_PORT_STAT_HIGH_SPEED; + } +} + +/*-------------------------------------------------------------------------*/ + +#define fotg210_has_fsl_portno_bug(e) (0) + +/* + * 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. + * + */ + +#define fotg210_big_endian_mmio(e) 0 +#define fotg210_big_endian_capbase(e) 0 + +static inline unsigned int fotg210_readl(const struct fotg210_hcd *fotg210, + __u32 __iomem *regs) +{ + return readl(regs); +} + +static inline void fotg210_writel(const struct fotg210_hcd *fotg210, + const unsigned int val, __u32 __iomem *regs) +{ + writel(val, regs); +} + +/* cpu to fotg210 */ +static inline __hc32 cpu_to_hc32(const struct fotg210_hcd *fotg210, const u32 x) +{ + return cpu_to_le32(x); +} + +/* fotg210 to cpu */ +static inline u32 hc32_to_cpu(const struct fotg210_hcd *fotg210, const __hc32 x) +{ + return le32_to_cpu(x); +} + +static inline u32 hc32_to_cpup(const struct fotg210_hcd *fotg210, + const __hc32 *x) +{ + return le32_to_cpup(x); +} + +/*-------------------------------------------------------------------------*/ + +static inline unsigned fotg210_read_frame_index(struct fotg210_hcd *fotg210) +{ + return fotg210_readl(fotg210, &fotg210->regs->frame_index); +} + +/*-------------------------------------------------------------------------*/ + +#endif /* __LINUX_FOTG210_H */ diff --git a/drivers/usb/fotg210/fotg210-udc.c b/drivers/usb/fotg210/fotg210-udc.c new file mode 100644 index 000000000..d9e024873 --- /dev/null +++ b/drivers/usb/fotg210/fotg210-udc.c @@ -0,0 +1,1306 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FOTG210 UDC Driver supports Bulk transfer so far + * + * Copyright (C) 2013 Faraday Technology Corporation + * + * Author : Yuan-Hsin Chen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fotg210.h" +#include "fotg210-udc.h" + +#define DRIVER_DESC "FOTG210 USB Device Controller Driver" +#define DRIVER_VERSION "30-April-2013" + +static const char udc_name[] = "fotg210_udc"; +static const char * const fotg210_ep_name[] = { + "ep0", "ep1", "ep2", "ep3", "ep4"}; + +static void fotg210_ack_int(struct fotg210_udc *fotg210, u32 offset, u32 mask) +{ + u32 value = ioread32(fotg210->reg + offset); + + value &= ~mask; + iowrite32(value, fotg210->reg + offset); +} + +static void fotg210_disable_fifo_int(struct fotg210_ep *ep) +{ + u32 value = ioread32(ep->fotg210->reg + FOTG210_DMISGR1); + + if (ep->dir_in) + value |= DMISGR1_MF_IN_INT(ep->epnum - 1); + else + value |= DMISGR1_MF_OUTSPK_INT(ep->epnum - 1); + iowrite32(value, ep->fotg210->reg + FOTG210_DMISGR1); +} + +static void fotg210_enable_fifo_int(struct fotg210_ep *ep) +{ + u32 value = ioread32(ep->fotg210->reg + FOTG210_DMISGR1); + + if (ep->dir_in) + value &= ~DMISGR1_MF_IN_INT(ep->epnum - 1); + else + value &= ~DMISGR1_MF_OUTSPK_INT(ep->epnum - 1); + iowrite32(value, ep->fotg210->reg + FOTG210_DMISGR1); +} + +static void fotg210_set_cxdone(struct fotg210_udc *fotg210) +{ + u32 value = ioread32(fotg210->reg + FOTG210_DCFESR); + + value |= DCFESR_CX_DONE; + iowrite32(value, fotg210->reg + FOTG210_DCFESR); +} + +static void fotg210_done(struct fotg210_ep *ep, struct fotg210_request *req, + int status) +{ + list_del_init(&req->queue); + + /* don't modify queue heads during completion callback */ + if (ep->fotg210->gadget.speed == USB_SPEED_UNKNOWN) + req->req.status = -ESHUTDOWN; + else + req->req.status = status; + + spin_unlock(&ep->fotg210->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&ep->fotg210->lock); + + if (ep->epnum) { + if (list_empty(&ep->queue)) + fotg210_disable_fifo_int(ep); + } else { + fotg210_set_cxdone(ep->fotg210); + } +} + +static void fotg210_fifo_ep_mapping(struct fotg210_ep *ep, u32 epnum, + u32 dir_in) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + u32 val; + + /* Driver should map an ep to a fifo and then map the fifo + * to the ep. What a brain-damaged design! + */ + + /* map a fifo to an ep */ + val = ioread32(fotg210->reg + FOTG210_EPMAP); + val &= ~EPMAP_FIFONOMSK(epnum, dir_in); + val |= EPMAP_FIFONO(epnum, dir_in); + iowrite32(val, fotg210->reg + FOTG210_EPMAP); + + /* map the ep to the fifo */ + val = ioread32(fotg210->reg + FOTG210_FIFOMAP); + val &= ~FIFOMAP_EPNOMSK(epnum); + val |= FIFOMAP_EPNO(epnum); + iowrite32(val, fotg210->reg + FOTG210_FIFOMAP); + + /* enable fifo */ + val = ioread32(fotg210->reg + FOTG210_FIFOCF); + val |= FIFOCF_FIFO_EN(epnum - 1); + iowrite32(val, fotg210->reg + FOTG210_FIFOCF); +} + +static void fotg210_set_fifo_dir(struct fotg210_ep *ep, u32 epnum, u32 dir_in) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + u32 val; + + val = ioread32(fotg210->reg + FOTG210_FIFOMAP); + val |= (dir_in ? FIFOMAP_DIRIN(epnum - 1) : FIFOMAP_DIROUT(epnum - 1)); + iowrite32(val, fotg210->reg + FOTG210_FIFOMAP); +} + +static void fotg210_set_tfrtype(struct fotg210_ep *ep, u32 epnum, u32 type) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + u32 val; + + val = ioread32(fotg210->reg + FOTG210_FIFOCF); + val |= FIFOCF_TYPE(type, epnum - 1); + iowrite32(val, fotg210->reg + FOTG210_FIFOCF); +} + +static void fotg210_set_mps(struct fotg210_ep *ep, u32 epnum, u32 mps, + u32 dir_in) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + u32 val; + u32 offset = dir_in ? FOTG210_INEPMPSR(epnum) : + FOTG210_OUTEPMPSR(epnum); + + val = ioread32(fotg210->reg + offset); + val |= INOUTEPMPSR_MPS(mps); + iowrite32(val, fotg210->reg + offset); +} + +static int fotg210_config_ep(struct fotg210_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + + fotg210_set_fifo_dir(ep, ep->epnum, ep->dir_in); + fotg210_set_tfrtype(ep, ep->epnum, ep->type); + fotg210_set_mps(ep, ep->epnum, ep->ep.maxpacket, ep->dir_in); + fotg210_fifo_ep_mapping(ep, ep->epnum, ep->dir_in); + + fotg210->ep[ep->epnum] = ep; + + return 0; +} + +static int fotg210_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct fotg210_ep *ep; + + ep = container_of(_ep, struct fotg210_ep, ep); + + ep->desc = desc; + ep->epnum = usb_endpoint_num(desc); + ep->type = usb_endpoint_type(desc); + ep->dir_in = usb_endpoint_dir_in(desc); + ep->ep.maxpacket = usb_endpoint_maxp(desc); + + return fotg210_config_ep(ep, desc); +} + +static void fotg210_reset_tseq(struct fotg210_udc *fotg210, u8 epnum) +{ + struct fotg210_ep *ep = fotg210->ep[epnum]; + u32 value; + void __iomem *reg; + + reg = (ep->dir_in) ? + fotg210->reg + FOTG210_INEPMPSR(epnum) : + fotg210->reg + FOTG210_OUTEPMPSR(epnum); + + /* Note: Driver needs to set and clear INOUTEPMPSR_RESET_TSEQ + * bit. Controller wouldn't clear this bit. WTF!!! + */ + + value = ioread32(reg); + value |= INOUTEPMPSR_RESET_TSEQ; + iowrite32(value, reg); + + value = ioread32(reg); + value &= ~INOUTEPMPSR_RESET_TSEQ; + iowrite32(value, reg); +} + +static int fotg210_ep_release(struct fotg210_ep *ep) +{ + if (!ep->epnum) + return 0; + ep->epnum = 0; + ep->stall = 0; + ep->wedged = 0; + + fotg210_reset_tseq(ep->fotg210, ep->epnum); + + return 0; +} + +static int fotg210_ep_disable(struct usb_ep *_ep) +{ + struct fotg210_ep *ep; + struct fotg210_request *req; + unsigned long flags; + + BUG_ON(!_ep); + + ep = container_of(_ep, struct fotg210_ep, ep); + + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct fotg210_request, queue); + spin_lock_irqsave(&ep->fotg210->lock, flags); + fotg210_done(ep, req, -ECONNRESET); + spin_unlock_irqrestore(&ep->fotg210->lock, flags); + } + + return fotg210_ep_release(ep); +} + +static struct usb_request *fotg210_ep_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct fotg210_request *req; + + req = kzalloc_obj(struct fotg210_request, gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void fotg210_ep_free_request(struct usb_ep *_ep, + struct usb_request *_req) +{ + struct fotg210_request *req; + + req = container_of(_req, struct fotg210_request, req); + kfree(req); +} + +static void fotg210_enable_dma(struct fotg210_ep *ep, + dma_addr_t d, u32 len) +{ + u32 value; + struct fotg210_udc *fotg210 = ep->fotg210; + + /* set transfer length and direction */ + value = ioread32(fotg210->reg + FOTG210_DMACPSR1); + value &= ~(DMACPSR1_DMA_LEN(0xFFFF) | DMACPSR1_DMA_TYPE(1)); + value |= DMACPSR1_DMA_LEN(len) | DMACPSR1_DMA_TYPE(ep->dir_in); + iowrite32(value, fotg210->reg + FOTG210_DMACPSR1); + + /* set device DMA target FIFO number */ + value = ioread32(fotg210->reg + FOTG210_DMATFNR); + if (ep->epnum) + value |= DMATFNR_ACC_FN(ep->epnum - 1); + else + value |= DMATFNR_ACC_CXF; + iowrite32(value, fotg210->reg + FOTG210_DMATFNR); + + /* set DMA memory address */ + iowrite32(d, fotg210->reg + FOTG210_DMACPSR2); + + /* enable MDMA_EROR and MDMA_CMPLT interrupt */ + value = ioread32(fotg210->reg + FOTG210_DMISGR2); + value &= ~(DMISGR2_MDMA_CMPLT | DMISGR2_MDMA_ERROR); + iowrite32(value, fotg210->reg + FOTG210_DMISGR2); + + /* start DMA */ + value = ioread32(fotg210->reg + FOTG210_DMACPSR1); + value |= DMACPSR1_DMA_START; + iowrite32(value, fotg210->reg + FOTG210_DMACPSR1); +} + +static void fotg210_disable_dma(struct fotg210_ep *ep) +{ + iowrite32(DMATFNR_DISDMA, ep->fotg210->reg + FOTG210_DMATFNR); +} + +static void fotg210_wait_dma_done(struct fotg210_ep *ep) +{ + u32 value; + + do { + value = ioread32(ep->fotg210->reg + FOTG210_DISGR2); + if ((value & DISGR2_USBRST_INT) || + (value & DISGR2_DMA_ERROR)) + goto dma_reset; + } while (!(value & DISGR2_DMA_CMPLT)); + + fotg210_ack_int(ep->fotg210, FOTG210_DISGR2, DISGR2_DMA_CMPLT); + return; + +dma_reset: + value = ioread32(ep->fotg210->reg + FOTG210_DMACPSR1); + value |= DMACPSR1_DMA_ABORT; + iowrite32(value, ep->fotg210->reg + FOTG210_DMACPSR1); + + /* reset fifo */ + if (ep->epnum) { + value = ioread32(ep->fotg210->reg + + FOTG210_FIBCR(ep->epnum - 1)); + value |= FIBCR_FFRST; + iowrite32(value, ep->fotg210->reg + + FOTG210_FIBCR(ep->epnum - 1)); + } else { + value = ioread32(ep->fotg210->reg + FOTG210_DCFESR); + value |= DCFESR_CX_CLR; + iowrite32(value, ep->fotg210->reg + FOTG210_DCFESR); + } +} + +static void fotg210_start_dma(struct fotg210_ep *ep, + struct fotg210_request *req) +{ + struct device *dev = &ep->fotg210->gadget.dev; + dma_addr_t d; + u8 *buffer; + u32 length; + + if (ep->epnum) { + if (ep->dir_in) { + buffer = req->req.buf; + length = req->req.length; + } else { + buffer = req->req.buf + req->req.actual; + length = ioread32(ep->fotg210->reg + + FOTG210_FIBCR(ep->epnum - 1)) & FIBCR_BCFX; + if (length > req->req.length - req->req.actual) + length = req->req.length - req->req.actual; + } + } else { + buffer = req->req.buf + req->req.actual; + if (req->req.length - req->req.actual > ep->ep.maxpacket) + length = ep->ep.maxpacket; + else + length = req->req.length - req->req.actual; + } + + d = dma_map_single(dev, buffer, length, + ep->dir_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + + if (dma_mapping_error(dev, d)) { + pr_err("dma_mapping_error\n"); + return; + } + + fotg210_enable_dma(ep, d, length); + + /* check if dma is done */ + fotg210_wait_dma_done(ep); + + fotg210_disable_dma(ep); + + /* update actual transfer length */ + req->req.actual += length; + + dma_unmap_single(dev, d, length, DMA_TO_DEVICE); +} + +static void fotg210_ep0_queue(struct fotg210_ep *ep, + struct fotg210_request *req) +{ + if (!req->req.length) { + fotg210_done(ep, req, 0); + return; + } + if (ep->dir_in) { /* if IN */ + fotg210_start_dma(ep, req); + if (req->req.length == req->req.actual) + fotg210_done(ep, req, 0); + } else { /* OUT */ + u32 value = ioread32(ep->fotg210->reg + FOTG210_DMISGR0); + + value &= ~DMISGR0_MCX_OUT_INT; + iowrite32(value, ep->fotg210->reg + FOTG210_DMISGR0); + } +} + +static int fotg210_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct fotg210_ep *ep; + struct fotg210_request *req; + unsigned long flags; + int request = 0; + + ep = container_of(_ep, struct fotg210_ep, ep); + req = container_of(_req, struct fotg210_request, req); + + if (ep->fotg210->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + spin_lock_irqsave(&ep->fotg210->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->epnum) /* ep0 */ + fotg210_ep0_queue(ep, req); + else if (request && !ep->stall) + fotg210_enable_fifo_int(ep); + + spin_unlock_irqrestore(&ep->fotg210->lock, flags); + + return 0; +} + +static int fotg210_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct fotg210_ep *ep; + struct fotg210_request *req; + unsigned long flags; + + ep = container_of(_ep, struct fotg210_ep, ep); + req = container_of(_req, struct fotg210_request, req); + + spin_lock_irqsave(&ep->fotg210->lock, flags); + if (!list_empty(&ep->queue)) + fotg210_done(ep, req, -ECONNRESET); + spin_unlock_irqrestore(&ep->fotg210->lock, flags); + + return 0; +} + +static void fotg210_set_epnstall(struct fotg210_ep *ep) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + u32 value; + void __iomem *reg; + + /* check if IN FIFO is empty before stall */ + if (ep->dir_in) { + do { + value = ioread32(fotg210->reg + FOTG210_DCFESR); + } while (!(value & DCFESR_FIFO_EMPTY(ep->epnum - 1))); + } + + reg = (ep->dir_in) ? + fotg210->reg + FOTG210_INEPMPSR(ep->epnum) : + fotg210->reg + FOTG210_OUTEPMPSR(ep->epnum); + value = ioread32(reg); + value |= INOUTEPMPSR_STL_EP; + iowrite32(value, reg); +} + +static void fotg210_clear_epnstall(struct fotg210_ep *ep) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + u32 value; + void __iomem *reg; + + reg = (ep->dir_in) ? + fotg210->reg + FOTG210_INEPMPSR(ep->epnum) : + fotg210->reg + FOTG210_OUTEPMPSR(ep->epnum); + value = ioread32(reg); + value &= ~INOUTEPMPSR_STL_EP; + iowrite32(value, reg); +} + +static int fotg210_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedge) +{ + struct fotg210_ep *ep; + struct fotg210_udc *fotg210; + unsigned long flags; + + ep = container_of(_ep, struct fotg210_ep, ep); + + fotg210 = ep->fotg210; + + spin_lock_irqsave(&ep->fotg210->lock, flags); + + if (value) { + fotg210_set_epnstall(ep); + ep->stall = 1; + if (wedge) + ep->wedged = 1; + } else { + fotg210_reset_tseq(fotg210, ep->epnum); + fotg210_clear_epnstall(ep); + ep->stall = 0; + ep->wedged = 0; + if (!list_empty(&ep->queue)) + fotg210_enable_fifo_int(ep); + } + + spin_unlock_irqrestore(&ep->fotg210->lock, flags); + return 0; +} + +static int fotg210_ep_set_halt(struct usb_ep *_ep, int value) +{ + return fotg210_set_halt_and_wedge(_ep, value, 0); +} + +static int fotg210_ep_set_wedge(struct usb_ep *_ep) +{ + return fotg210_set_halt_and_wedge(_ep, 1, 1); +} + +static void fotg210_ep_fifo_flush(struct usb_ep *_ep) +{ +} + +static const struct usb_ep_ops fotg210_ep_ops = { + .enable = fotg210_ep_enable, + .disable = fotg210_ep_disable, + + .alloc_request = fotg210_ep_alloc_request, + .free_request = fotg210_ep_free_request, + + .queue = fotg210_ep_queue, + .dequeue = fotg210_ep_dequeue, + + .set_halt = fotg210_ep_set_halt, + .fifo_flush = fotg210_ep_fifo_flush, + .set_wedge = fotg210_ep_set_wedge, +}; + +static void fotg210_clear_tx0byte(struct fotg210_udc *fotg210) +{ + u32 value = ioread32(fotg210->reg + FOTG210_TX0BYTE); + + value &= ~(TX0BYTE_EP1 | TX0BYTE_EP2 | TX0BYTE_EP3 + | TX0BYTE_EP4); + iowrite32(value, fotg210->reg + FOTG210_TX0BYTE); +} + +static void fotg210_clear_rx0byte(struct fotg210_udc *fotg210) +{ + u32 value = ioread32(fotg210->reg + FOTG210_RX0BYTE); + + value &= ~(RX0BYTE_EP1 | RX0BYTE_EP2 | RX0BYTE_EP3 + | RX0BYTE_EP4); + iowrite32(value, fotg210->reg + FOTG210_RX0BYTE); +} + +/* read 8-byte setup packet only */ +static void fotg210_rdsetupp(struct fotg210_udc *fotg210, + u8 *buffer) +{ + int i = 0; + u8 *tmp = buffer; + u32 data; + u32 length = 8; + + iowrite32(DMATFNR_ACC_CXF, fotg210->reg + FOTG210_DMATFNR); + + for (i = (length >> 2); i > 0; i--) { + data = ioread32(fotg210->reg + FOTG210_CXPORT); + *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(fotg210->reg + FOTG210_CXPORT); + *tmp = data & 0xFF; + break; + case 2: + data = ioread32(fotg210->reg + FOTG210_CXPORT); + *tmp = data & 0xFF; + *(tmp + 1) = (data >> 8) & 0xFF; + break; + case 3: + data = ioread32(fotg210->reg + FOTG210_CXPORT); + *tmp = data & 0xFF; + *(tmp + 1) = (data >> 8) & 0xFF; + *(tmp + 2) = (data >> 16) & 0xFF; + break; + default: + break; + } + + iowrite32(DMATFNR_DISDMA, fotg210->reg + FOTG210_DMATFNR); +} + +static void fotg210_set_configuration(struct fotg210_udc *fotg210) +{ + u32 value = ioread32(fotg210->reg + FOTG210_DAR); + + value |= DAR_AFT_CONF; + iowrite32(value, fotg210->reg + FOTG210_DAR); +} + +static void fotg210_set_dev_addr(struct fotg210_udc *fotg210, u32 addr) +{ + u32 value = ioread32(fotg210->reg + FOTG210_DAR); + + value |= (addr & 0x7F); + iowrite32(value, fotg210->reg + FOTG210_DAR); +} + +static void fotg210_set_cxstall(struct fotg210_udc *fotg210) +{ + u32 value = ioread32(fotg210->reg + FOTG210_DCFESR); + + value |= DCFESR_CX_STL; + iowrite32(value, fotg210->reg + FOTG210_DCFESR); +} + +static void fotg210_request_error(struct fotg210_udc *fotg210) +{ + fotg210_set_cxstall(fotg210); + pr_err("request error!!\n"); +} + +static void fotg210_set_address(struct fotg210_udc *fotg210, + struct usb_ctrlrequest *ctrl) +{ + if (le16_to_cpu(ctrl->wValue) >= 0x0100) { + fotg210_request_error(fotg210); + } else { + fotg210_set_dev_addr(fotg210, le16_to_cpu(ctrl->wValue)); + fotg210_set_cxdone(fotg210); + } +} + +static void fotg210_set_feature(struct fotg210_udc *fotg210, + struct usb_ctrlrequest *ctrl) +{ + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + fotg210_set_cxdone(fotg210); + break; + case USB_RECIP_INTERFACE: + fotg210_set_cxdone(fotg210); + break; + case USB_RECIP_ENDPOINT: { + u8 epnum; + epnum = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; + if (epnum) + fotg210_set_epnstall(fotg210->ep[epnum]); + else + fotg210_set_cxstall(fotg210); + fotg210_set_cxdone(fotg210); + } + break; + default: + fotg210_request_error(fotg210); + break; + } +} + +static void fotg210_clear_feature(struct fotg210_udc *fotg210, + struct usb_ctrlrequest *ctrl) +{ + struct fotg210_ep *ep = + fotg210->ep[ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK]; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + fotg210_set_cxdone(fotg210); + break; + case USB_RECIP_INTERFACE: + fotg210_set_cxdone(fotg210); + break; + case USB_RECIP_ENDPOINT: + if (ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK) { + if (ep->wedged) { + fotg210_set_cxdone(fotg210); + break; + } + if (ep->stall) + fotg210_set_halt_and_wedge(&ep->ep, 0, 0); + } + fotg210_set_cxdone(fotg210); + break; + default: + fotg210_request_error(fotg210); + break; + } +} + +static int fotg210_is_epnstall(struct fotg210_ep *ep) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + u32 value; + void __iomem *reg; + + reg = (ep->dir_in) ? + fotg210->reg + FOTG210_INEPMPSR(ep->epnum) : + fotg210->reg + FOTG210_OUTEPMPSR(ep->epnum); + value = ioread32(reg); + return value & INOUTEPMPSR_STL_EP ? 1 : 0; +} + +/* For EP0 requests triggered by this driver (currently GET_STATUS response) */ +static void fotg210_ep0_complete(struct usb_ep *_ep, struct usb_request *req) +{ + struct fotg210_ep *ep; + struct fotg210_udc *fotg210; + + ep = container_of(_ep, struct fotg210_ep, ep); + fotg210 = ep->fotg210; + + if (req->status || req->actual != req->length) { + dev_warn(&fotg210->gadget.dev, "EP0 request failed: %d\n", req->status); + } +} + +static void fotg210_get_status(struct fotg210_udc *fotg210, + struct usb_ctrlrequest *ctrl) +{ + u8 epnum; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + fotg210->ep0_data = cpu_to_le16(1 << USB_DEVICE_SELF_POWERED); + break; + case USB_RECIP_INTERFACE: + fotg210->ep0_data = cpu_to_le16(0); + break; + case USB_RECIP_ENDPOINT: + epnum = ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK; + if (epnum) + fotg210->ep0_data = + cpu_to_le16(fotg210_is_epnstall(fotg210->ep[epnum]) + << USB_ENDPOINT_HALT); + else + fotg210_request_error(fotg210); + break; + + default: + fotg210_request_error(fotg210); + return; /* exit */ + } + + fotg210->ep0_req->buf = &fotg210->ep0_data; + fotg210->ep0_req->length = 2; + + spin_unlock(&fotg210->lock); + fotg210_ep_queue(fotg210->gadget.ep0, fotg210->ep0_req, GFP_ATOMIC); + spin_lock(&fotg210->lock); +} + +static int fotg210_setup_packet(struct fotg210_udc *fotg210, + struct usb_ctrlrequest *ctrl) +{ + u8 *p = (u8 *)ctrl; + u8 ret = 0; + + fotg210_rdsetupp(fotg210, p); + + fotg210->ep[0]->dir_in = ctrl->bRequestType & USB_DIR_IN; + + if (fotg210->gadget.speed == USB_SPEED_UNKNOWN) { + u32 value = ioread32(fotg210->reg + FOTG210_DMCR); + fotg210->gadget.speed = value & DMCR_HS_EN ? + USB_SPEED_HIGH : USB_SPEED_FULL; + } + + /* check request */ + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_GET_STATUS: + fotg210_get_status(fotg210, ctrl); + break; + case USB_REQ_CLEAR_FEATURE: + fotg210_clear_feature(fotg210, ctrl); + break; + case USB_REQ_SET_FEATURE: + fotg210_set_feature(fotg210, ctrl); + break; + case USB_REQ_SET_ADDRESS: + fotg210_set_address(fotg210, ctrl); + break; + case USB_REQ_SET_CONFIGURATION: + fotg210_set_configuration(fotg210); + ret = 1; + break; + default: + ret = 1; + break; + } + } else { + ret = 1; + } + + return ret; +} + +static void fotg210_ep0out(struct fotg210_udc *fotg210) +{ + struct fotg210_ep *ep = fotg210->ep[0]; + + if (!list_empty(&ep->queue) && !ep->dir_in) { + struct fotg210_request *req; + + req = list_first_entry(&ep->queue, + struct fotg210_request, queue); + + if (req->req.length) + fotg210_start_dma(ep, req); + + if ((req->req.length - req->req.actual) < ep->ep.maxpacket) + fotg210_done(ep, req, 0); + } else { + pr_err("%s : empty queue\n", __func__); + } +} + +static void fotg210_ep0in(struct fotg210_udc *fotg210) +{ + struct fotg210_ep *ep = fotg210->ep[0]; + + if ((!list_empty(&ep->queue)) && (ep->dir_in)) { + struct fotg210_request *req; + + req = list_entry(ep->queue.next, + struct fotg210_request, queue); + + if (req->req.length) + fotg210_start_dma(ep, req); + + if (req->req.actual == req->req.length) + fotg210_done(ep, req, 0); + } else { + fotg210_set_cxdone(fotg210); + } +} + +static void fotg210_in_fifo_handler(struct fotg210_ep *ep) +{ + struct fotg210_request *req = list_entry(ep->queue.next, + struct fotg210_request, queue); + + if (req->req.length) + fotg210_start_dma(ep, req); + fotg210_done(ep, req, 0); +} + +static void fotg210_out_fifo_handler(struct fotg210_ep *ep) +{ + struct fotg210_request *req = list_entry(ep->queue.next, + struct fotg210_request, queue); + int disgr1 = ioread32(ep->fotg210->reg + FOTG210_DISGR1); + + fotg210_start_dma(ep, req); + + /* Complete the request when it's full or a short packet arrived. + * Like other drivers, short_not_ok isn't handled. + */ + + if (req->req.length == req->req.actual || + (disgr1 & DISGR1_SPK_INT(ep->epnum - 1))) + fotg210_done(ep, req, 0); +} + +static irqreturn_t fotg210_irq(int irq, void *_fotg210) +{ + struct fotg210_udc *fotg210 = _fotg210; + u32 int_grp = ioread32(fotg210->reg + FOTG210_DIGR); + u32 int_msk = ioread32(fotg210->reg + FOTG210_DMIGR); + + int_grp &= ~int_msk; + + spin_lock(&fotg210->lock); + + if (int_grp & DIGR_INT_G2) { + void __iomem *reg = fotg210->reg + FOTG210_DISGR2; + u32 int_grp2 = ioread32(reg); + u32 int_msk2 = ioread32(fotg210->reg + FOTG210_DMISGR2); + + int_grp2 &= ~int_msk2; + + if (int_grp2 & DISGR2_USBRST_INT) { + usb_gadget_udc_reset(&fotg210->gadget, + fotg210->driver); + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_USBRST_INT); + pr_info("fotg210 udc reset\n"); + } + if (int_grp2 & DISGR2_SUSP_INT) { + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_SUSP_INT); + pr_info("fotg210 udc suspend\n"); + } + if (int_grp2 & DISGR2_RESM_INT) { + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_RESM_INT); + pr_info("fotg210 udc resume\n"); + } + if (int_grp2 & DISGR2_ISO_SEQ_ERR_INT) { + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_ISO_SEQ_ERR_INT); + pr_info("fotg210 iso sequence error\n"); + } + if (int_grp2 & DISGR2_ISO_SEQ_ABORT_INT) { + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_ISO_SEQ_ABORT_INT); + pr_info("fotg210 iso sequence abort\n"); + } + if (int_grp2 & DISGR2_TX0BYTE_INT) { + fotg210_clear_tx0byte(fotg210); + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_TX0BYTE_INT); + pr_info("fotg210 transferred 0 byte\n"); + } + if (int_grp2 & DISGR2_RX0BYTE_INT) { + fotg210_clear_rx0byte(fotg210); + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_RX0BYTE_INT); + pr_info("fotg210 received 0 byte\n"); + } + if (int_grp2 & DISGR2_DMA_ERROR) { + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_DMA_ERROR); + } + } + + if (int_grp & DIGR_INT_G0) { + void __iomem *reg = fotg210->reg + FOTG210_DISGR0; + u32 int_grp0 = ioread32(reg); + u32 int_msk0 = ioread32(fotg210->reg + FOTG210_DMISGR0); + struct usb_ctrlrequest ctrl; + + int_grp0 &= ~int_msk0; + + /* the highest priority in this source register */ + if (int_grp0 & DISGR0_CX_COMABT_INT) { + fotg210_ack_int(fotg210, FOTG210_DISGR0, DISGR0_CX_COMABT_INT); + pr_info("fotg210 CX command abort\n"); + } + + if (int_grp0 & DISGR0_CX_SETUP_INT) { + if (fotg210_setup_packet(fotg210, &ctrl)) { + spin_unlock(&fotg210->lock); + if (fotg210->driver->setup(&fotg210->gadget, + &ctrl) < 0) + fotg210_set_cxstall(fotg210); + spin_lock(&fotg210->lock); + } + } + if (int_grp0 & DISGR0_CX_COMEND_INT) + pr_info("fotg210 cmd end\n"); + + if (int_grp0 & DISGR0_CX_IN_INT) + fotg210_ep0in(fotg210); + + if (int_grp0 & DISGR0_CX_OUT_INT) + fotg210_ep0out(fotg210); + + if (int_grp0 & DISGR0_CX_COMFAIL_INT) { + fotg210_set_cxstall(fotg210); + pr_info("fotg210 ep0 fail\n"); + } + } + + if (int_grp & DIGR_INT_G1) { + void __iomem *reg = fotg210->reg + FOTG210_DISGR1; + u32 int_grp1 = ioread32(reg); + u32 int_msk1 = ioread32(fotg210->reg + FOTG210_DMISGR1); + int fifo; + + int_grp1 &= ~int_msk1; + + for (fifo = 0; fifo < FOTG210_MAX_FIFO_NUM; fifo++) { + if (int_grp1 & DISGR1_IN_INT(fifo)) + fotg210_in_fifo_handler(fotg210->ep[fifo + 1]); + + if ((int_grp1 & DISGR1_OUT_INT(fifo)) || + (int_grp1 & DISGR1_SPK_INT(fifo))) + fotg210_out_fifo_handler(fotg210->ep[fifo + 1]); + } + } + + spin_unlock(&fotg210->lock); + + return IRQ_HANDLED; +} + +static void fotg210_disable_unplug(struct fotg210_udc *fotg210) +{ + u32 reg = ioread32(fotg210->reg + FOTG210_PHYTMSR); + + reg &= ~PHYTMSR_UNPLUG; + iowrite32(reg, fotg210->reg + FOTG210_PHYTMSR); +} + +static int fotg210_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct fotg210_udc *fotg210 = gadget_to_fotg210(g); + u32 value; + int ret; + + /* hook up the driver */ + fotg210->driver = driver; + fotg210->gadget.dev.of_node = fotg210->dev->of_node; + fotg210->gadget.speed = USB_SPEED_UNKNOWN; + + dev_info(fotg210->dev, "bound driver %s\n", driver->driver.name); + + if (!IS_ERR_OR_NULL(fotg210->phy)) { + ret = otg_set_peripheral(fotg210->phy->otg, + &fotg210->gadget); + if (ret) + dev_err(fotg210->dev, "can't bind to phy\n"); + } + + /* chip enable */ + value = ioread32(fotg210->reg + FOTG210_DMCR); + value |= DMCR_CHIP_EN; + iowrite32(value, fotg210->reg + FOTG210_DMCR); + + /* enable device global interrupt */ + value = ioread32(fotg210->reg + FOTG210_DMCR); + value |= DMCR_GLINT_EN; + iowrite32(value, fotg210->reg + FOTG210_DMCR); + + return 0; +} + +static void fotg210_init(struct fotg210_udc *fotg210) +{ + u32 value; + + /* disable global interrupt and set int polarity to active high */ + iowrite32(GMIR_MHC_INT | GMIR_MOTG_INT | GMIR_INT_POLARITY, + fotg210->reg + FOTG210_GMIR); + + /* mask interrupts for groups other than 0-2 */ + iowrite32(~(DMIGR_MINT_G0 | DMIGR_MINT_G1 | DMIGR_MINT_G2), + fotg210->reg + FOTG210_DMIGR); + + /* udc software reset */ + iowrite32(DMCR_SFRST, fotg210->reg + FOTG210_DMCR); + /* Better wait a bit, but without a datasheet, no idea how long. */ + usleep_range(100, 200); + + /* disable device global interrupt */ + value = ioread32(fotg210->reg + FOTG210_DMCR); + value &= ~DMCR_GLINT_EN; + iowrite32(value, fotg210->reg + FOTG210_DMCR); + + /* enable only grp2 irqs we handle */ + iowrite32(~(DISGR2_DMA_ERROR | DISGR2_RX0BYTE_INT | DISGR2_TX0BYTE_INT + | DISGR2_ISO_SEQ_ABORT_INT | DISGR2_ISO_SEQ_ERR_INT + | DISGR2_RESM_INT | DISGR2_SUSP_INT | DISGR2_USBRST_INT), + fotg210->reg + FOTG210_DMISGR2); + + /* disable all fifo interrupt */ + iowrite32(~(u32)0, fotg210->reg + FOTG210_DMISGR1); + + /* disable cmd end */ + value = ioread32(fotg210->reg + FOTG210_DMISGR0); + value |= DMISGR0_MCX_COMEND; + iowrite32(value, fotg210->reg + FOTG210_DMISGR0); +} + +static int fotg210_udc_stop(struct usb_gadget *g) +{ + struct fotg210_udc *fotg210 = gadget_to_fotg210(g); + unsigned long flags; + + if (!IS_ERR_OR_NULL(fotg210->phy)) + return otg_set_peripheral(fotg210->phy->otg, NULL); + + spin_lock_irqsave(&fotg210->lock, flags); + + fotg210_init(fotg210); + fotg210->driver = NULL; + fotg210->gadget.speed = USB_SPEED_UNKNOWN; + + spin_unlock_irqrestore(&fotg210->lock, flags); + + return 0; +} + +/** + * fotg210_vbus_session - Called by external transceiver to enable/disable udc + * @g: usb gadget + * @is_active: 0 if should disable UDC VBUS, 1 if should enable + * + * Returns: %0 + */ +static int fotg210_vbus_session(struct usb_gadget *g, int is_active) +{ + struct fotg210_udc *fotg210 = gadget_to_fotg210(g); + + /* Call down to core integration layer to drive or disable VBUS */ + fotg210_vbus(fotg210->fotg, is_active); + return 0; +} + +static const struct usb_gadget_ops fotg210_gadget_ops = { + .udc_start = fotg210_udc_start, + .udc_stop = fotg210_udc_stop, + .vbus_session = fotg210_vbus_session, +}; + +/** + * fotg210_phy_event - Called by phy upon VBus event + * @nb: notifier block + * @action: phy action, is vbus connect or disconnect + * @data: the usb_gadget structure in fotg210 + * + * Called by the USB Phy when a cable connect or disconnect is sensed. + * + * Returns: NOTIFY_OK or NOTIFY_DONE + */ +static int fotg210_phy_event(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct usb_gadget *gadget = data; + + if (!gadget) + return NOTIFY_DONE; + + switch (action) { + case USB_EVENT_VBUS: + usb_gadget_vbus_connect(gadget); + return NOTIFY_OK; + case USB_EVENT_NONE: + usb_gadget_vbus_disconnect(gadget); + return NOTIFY_OK; + default: + return NOTIFY_DONE; + } +} + +static struct notifier_block fotg210_phy_notifier = { + .notifier_call = fotg210_phy_event, +}; + +int fotg210_udc_remove(struct platform_device *pdev) +{ + struct fotg210_udc *fotg210 = platform_get_drvdata(pdev); + int i; + + usb_del_gadget_udc(&fotg210->gadget); + if (!IS_ERR_OR_NULL(fotg210->phy)) { + usb_unregister_notifier(fotg210->phy, &fotg210_phy_notifier); + usb_put_phy(fotg210->phy); + } + iounmap(fotg210->reg); + free_irq(platform_get_irq(pdev, 0), fotg210); + + fotg210_ep_free_request(&fotg210->ep[0]->ep, fotg210->ep0_req); + for (i = 0; i < FOTG210_MAX_NUM_EP; i++) + kfree(fotg210->ep[i]); + + kfree(fotg210); + + return 0; +} + +int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg) +{ + struct fotg210_udc *fotg210 = NULL; + struct device *dev = &pdev->dev; + int irq; + int ret = 0; + int i; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + /* initialize udc */ + fotg210 = kzalloc_obj(struct fotg210_udc); + if (fotg210 == NULL) + return -ENOMEM; + + fotg210->dev = dev; + fotg210->fotg = fotg; + + fotg210->phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 0); + if (IS_ERR(fotg210->phy)) { + ret = PTR_ERR(fotg210->phy); + if (ret == -EPROBE_DEFER) + goto err_free; + dev_info(dev, "no PHY found\n"); + fotg210->phy = NULL; + } else { + ret = usb_phy_init(fotg210->phy); + if (ret) + goto err_free; + dev_info(dev, "found and initialized PHY\n"); + } + + ret = -ENOMEM; + + for (i = 0; i < FOTG210_MAX_NUM_EP; i++) { + fotg210->ep[i] = kzalloc_obj(struct fotg210_ep); + if (!fotg210->ep[i]) + goto err_alloc; + } + + fotg210->reg = fotg->base; + + spin_lock_init(&fotg210->lock); + + platform_set_drvdata(pdev, fotg210); + + fotg210->gadget.ops = &fotg210_gadget_ops; + + fotg210->gadget.max_speed = USB_SPEED_HIGH; + fotg210->gadget.dev.parent = dev; + fotg210->gadget.dev.dma_mask = dev->dma_mask; + fotg210->gadget.name = udc_name; + + INIT_LIST_HEAD(&fotg210->gadget.ep_list); + + for (i = 0; i < FOTG210_MAX_NUM_EP; i++) { + struct fotg210_ep *ep = fotg210->ep[i]; + + if (i) { + INIT_LIST_HEAD(&fotg210->ep[i]->ep.ep_list); + list_add_tail(&fotg210->ep[i]->ep.ep_list, + &fotg210->gadget.ep_list); + } + ep->fotg210 = fotg210; + INIT_LIST_HEAD(&ep->queue); + ep->ep.name = fotg210_ep_name[i]; + ep->ep.ops = &fotg210_ep_ops; + usb_ep_set_maxpacket_limit(&ep->ep, (unsigned short) ~0); + + if (i == 0) { + ep->ep.caps.type_control = true; + } else { + ep->ep.caps.type_iso = true; + ep->ep.caps.type_bulk = true; + ep->ep.caps.type_int = true; + } + + ep->ep.caps.dir_in = true; + ep->ep.caps.dir_out = true; + } + usb_ep_set_maxpacket_limit(&fotg210->ep[0]->ep, 0x40); + fotg210->gadget.ep0 = &fotg210->ep[0]->ep; + INIT_LIST_HEAD(&fotg210->gadget.ep0->ep_list); + + fotg210->ep0_req = fotg210_ep_alloc_request(&fotg210->ep[0]->ep, + GFP_KERNEL); + if (fotg210->ep0_req == NULL) + goto err_map; + + fotg210->ep0_req->complete = fotg210_ep0_complete; + + fotg210_init(fotg210); + + fotg210_disable_unplug(fotg210); + + ret = request_irq(irq, fotg210_irq, IRQF_SHARED, + udc_name, fotg210); + if (ret < 0) { + dev_err_probe(dev, ret, "request_irq error\n"); + goto err_req; + } + + if (!IS_ERR_OR_NULL(fotg210->phy)) + usb_register_notifier(fotg210->phy, &fotg210_phy_notifier); + + ret = usb_add_gadget_udc(dev, &fotg210->gadget); + if (ret) + goto err_add_udc; + + dev_info(dev, "version %s\n", DRIVER_VERSION); + + return 0; + +err_add_udc: + if (!IS_ERR_OR_NULL(fotg210->phy)) + usb_unregister_notifier(fotg210->phy, &fotg210_phy_notifier); + free_irq(irq, fotg210); + +err_req: + fotg210_ep_free_request(&fotg210->ep[0]->ep, fotg210->ep0_req); + +err_map: + iounmap(fotg210->reg); + +err_alloc: + for (i = 0; i < FOTG210_MAX_NUM_EP; i++) + kfree(fotg210->ep[i]); + +err_free: + kfree(fotg210); + return ret; +} diff --git a/drivers/usb/fotg210/fotg210-udc.h b/drivers/usb/fotg210/fotg210-udc.h new file mode 100644 index 000000000..252cb2b8e --- /dev/null +++ b/drivers/usb/fotg210/fotg210-udc.h @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Faraday FOTG210 USB OTG controller + * + * Copyright (C) 2013 Faraday Technology Corporation + * Author: Yuan-Hsin Chen + */ + +#include + +#define FOTG210_MAX_NUM_EP 5 /* ep0...ep4 */ +#define FOTG210_MAX_FIFO_NUM 4 /* fifo0...fifo4 */ + +/* Global Mask of HC/OTG/DEV interrupt Register(0xC4) */ +#define FOTG210_GMIR 0xC4 +#define GMIR_INT_POLARITY 0x8 /*Active High*/ +#define GMIR_MHC_INT 0x4 +#define GMIR_MOTG_INT 0x2 +#define GMIR_MDEV_INT 0x1 + +/* Device Main Control Register(0x100) */ +#define FOTG210_DMCR 0x100 +#define DMCR_HS_EN (1 << 6) +#define DMCR_CHIP_EN (1 << 5) +#define DMCR_SFRST (1 << 4) +#define DMCR_GOSUSP (1 << 3) +#define DMCR_GLINT_EN (1 << 2) +#define DMCR_HALF_SPEED (1 << 1) +#define DMCR_CAP_RMWAKUP (1 << 0) + +/* Device Address Register(0x104) */ +#define FOTG210_DAR 0x104 +#define DAR_AFT_CONF (1 << 7) + +/* Device Test Register(0x108) */ +#define FOTG210_DTR 0x108 +#define DTR_TST_CLRFF (1 << 0) + +/* PHY Test Mode Selector register(0x114) */ +#define FOTG210_PHYTMSR 0x114 +#define PHYTMSR_TST_PKT (1 << 4) +#define PHYTMSR_TST_SE0NAK (1 << 3) +#define PHYTMSR_TST_KSTA (1 << 2) +#define PHYTMSR_TST_JSTA (1 << 1) +#define PHYTMSR_UNPLUG (1 << 0) + +/* Cx configuration and FIFO Empty Status register(0x120) */ +#define FOTG210_DCFESR 0x120 +#define DCFESR_FIFO_EMPTY(fifo) (1 << 8 << (fifo)) +#define DCFESR_CX_EMP (1 << 5) +#define DCFESR_CX_CLR (1 << 3) +#define DCFESR_CX_STL (1 << 2) +#define DCFESR_TST_PKDONE (1 << 1) +#define DCFESR_CX_DONE (1 << 0) + +/* Device IDLE Counter Register(0x124) */ +#define FOTG210_DICR 0x124 + +/* Device Mask of Interrupt Group Register (0x130) */ +#define FOTG210_DMIGR 0x130 +#define DMIGR_MINT_G2 (1 << 2) +#define DMIGR_MINT_G1 (1 << 1) +#define DMIGR_MINT_G0 (1 << 0) + +/* Device Mask of Interrupt Source Group 0(0x134) */ +#define FOTG210_DMISGR0 0x134 +#define DMISGR0_MCX_COMEND (1 << 3) +#define DMISGR0_MCX_OUT_INT (1 << 2) +#define DMISGR0_MCX_IN_INT (1 << 1) +#define DMISGR0_MCX_SETUP_INT (1 << 0) + +/* Device Mask of Interrupt Source Group 1 Register(0x138)*/ +#define FOTG210_DMISGR1 0x138 +#define DMISGR1_MF3_IN_INT (1 << 19) +#define DMISGR1_MF2_IN_INT (1 << 18) +#define DMISGR1_MF1_IN_INT (1 << 17) +#define DMISGR1_MF0_IN_INT (1 << 16) +#define DMISGR1_MF_IN_INT(fifo) (1 << (16 + (fifo))) +#define DMISGR1_MF3_SPK_INT (1 << 7) +#define DMISGR1_MF3_OUT_INT (1 << 6) +#define DMISGR1_MF2_SPK_INT (1 << 5) +#define DMISGR1_MF2_OUT_INT (1 << 4) +#define DMISGR1_MF1_SPK_INT (1 << 3) +#define DMISGR1_MF1_OUT_INT (1 << 2) +#define DMISGR1_MF0_SPK_INT (1 << 1) +#define DMISGR1_MF0_OUT_INT (1 << 0) +#define DMISGR1_MF_OUTSPK_INT(fifo) (0x3 << (fifo) * 2) + +/* Device Mask of Interrupt Source Group 2 Register (0x13C) */ +#define FOTG210_DMISGR2 0x13C +#define DMISGR2_MDMA_ERROR (1 << 8) +#define DMISGR2_MDMA_CMPLT (1 << 7) + +/* Device Interrupt group Register (0x140) */ +#define FOTG210_DIGR 0x140 +#define DIGR_INT_G2 (1 << 2) +#define DIGR_INT_G1 (1 << 1) +#define DIGR_INT_G0 (1 << 0) + +/* Device Interrupt Source Group 0 Register (0x144) */ +#define FOTG210_DISGR0 0x144 +#define DISGR0_CX_COMABT_INT (1 << 5) +#define DISGR0_CX_COMFAIL_INT (1 << 4) +#define DISGR0_CX_COMEND_INT (1 << 3) +#define DISGR0_CX_OUT_INT (1 << 2) +#define DISGR0_CX_IN_INT (1 << 1) +#define DISGR0_CX_SETUP_INT (1 << 0) + +/* Device Interrupt Source Group 1 Register (0x148) */ +#define FOTG210_DISGR1 0x148 +#define DISGR1_OUT_INT(fifo) (1 << ((fifo) * 2)) +#define DISGR1_SPK_INT(fifo) (1 << 1 << ((fifo) * 2)) +#define DISGR1_IN_INT(fifo) (1 << 16 << (fifo)) + +/* Device Interrupt Source Group 2 Register (0x14C) */ +#define FOTG210_DISGR2 0x14C +#define DISGR2_DMA_ERROR (1 << 8) +#define DISGR2_DMA_CMPLT (1 << 7) +#define DISGR2_RX0BYTE_INT (1 << 6) +#define DISGR2_TX0BYTE_INT (1 << 5) +#define DISGR2_ISO_SEQ_ABORT_INT (1 << 4) +#define DISGR2_ISO_SEQ_ERR_INT (1 << 3) +#define DISGR2_RESM_INT (1 << 2) +#define DISGR2_SUSP_INT (1 << 1) +#define DISGR2_USBRST_INT (1 << 0) + +/* Device Receive Zero-Length Data Packet Register (0x150)*/ +#define FOTG210_RX0BYTE 0x150 +#define RX0BYTE_EP8 (1 << 7) +#define RX0BYTE_EP7 (1 << 6) +#define RX0BYTE_EP6 (1 << 5) +#define RX0BYTE_EP5 (1 << 4) +#define RX0BYTE_EP4 (1 << 3) +#define RX0BYTE_EP3 (1 << 2) +#define RX0BYTE_EP2 (1 << 1) +#define RX0BYTE_EP1 (1 << 0) + +/* Device Transfer Zero-Length Data Packet Register (0x154)*/ +#define FOTG210_TX0BYTE 0x154 +#define TX0BYTE_EP8 (1 << 7) +#define TX0BYTE_EP7 (1 << 6) +#define TX0BYTE_EP6 (1 << 5) +#define TX0BYTE_EP5 (1 << 4) +#define TX0BYTE_EP4 (1 << 3) +#define TX0BYTE_EP3 (1 << 2) +#define TX0BYTE_EP2 (1 << 1) +#define TX0BYTE_EP1 (1 << 0) + +/* Device IN Endpoint x MaxPacketSize Register(0x160+4*(x-1)) */ +#define FOTG210_INEPMPSR(ep) (0x160 + 4 * ((ep) - 1)) +#define INOUTEPMPSR_MPS(mps) ((mps) & 0x2FF) +#define INOUTEPMPSR_STL_EP (1 << 11) +#define INOUTEPMPSR_RESET_TSEQ (1 << 12) + +/* Device OUT Endpoint x MaxPacketSize Register(0x180+4*(x-1)) */ +#define FOTG210_OUTEPMPSR(ep) (0x180 + 4 * ((ep) - 1)) + +/* Device Endpoint 1~4 Map Register (0x1A0) */ +#define FOTG210_EPMAP 0x1A0 +#define EPMAP_FIFONO(ep, dir) \ + ((((ep) - 1) << ((ep) - 1) * 8) << ((dir) ? 0 : 4)) +#define EPMAP_FIFONOMSK(ep, dir) \ + ((3 << ((ep) - 1) * 8) << ((dir) ? 0 : 4)) + +/* Device FIFO Map Register (0x1A8) */ +#define FOTG210_FIFOMAP 0x1A8 +#define FIFOMAP_DIROUT(fifo) (0x0 << 4 << (fifo) * 8) +#define FIFOMAP_DIRIN(fifo) (0x1 << 4 << (fifo) * 8) +#define FIFOMAP_BIDIR(fifo) (0x2 << 4 << (fifo) * 8) +#define FIFOMAP_NA(fifo) (0x3 << 4 << (fifo) * 8) +#define FIFOMAP_EPNO(ep) ((ep) << ((ep) - 1) * 8) +#define FIFOMAP_EPNOMSK(ep) (0xF << ((ep) - 1) * 8) + +/* Device FIFO Confuguration Register (0x1AC) */ +#define FOTG210_FIFOCF 0x1AC +#define FIFOCF_TYPE(type, fifo) ((type) << (fifo) * 8) +#define FIFOCF_BLK_SIN(fifo) (0x0 << (fifo) * 8 << 2) +#define FIFOCF_BLK_DUB(fifo) (0x1 << (fifo) * 8 << 2) +#define FIFOCF_BLK_TRI(fifo) (0x2 << (fifo) * 8 << 2) +#define FIFOCF_BLKSZ_512(fifo) (0x0 << (fifo) * 8 << 4) +#define FIFOCF_BLKSZ_1024(fifo) (0x1 << (fifo) * 8 << 4) +#define FIFOCF_FIFO_EN(fifo) (0x1 << (fifo) * 8 << 5) + +/* Device FIFO n Instruction and Byte Count Register (0x1B0+4*n) */ +#define FOTG210_FIBCR(fifo) (0x1B0 + (fifo) * 4) +#define FIBCR_BCFX 0x7FF +#define FIBCR_FFRST (1 << 12) + +/* Device DMA Target FIFO Number Register (0x1C0) */ +#define FOTG210_DMATFNR 0x1C0 +#define DMATFNR_ACC_CXF (1 << 4) +#define DMATFNR_ACC_F3 (1 << 3) +#define DMATFNR_ACC_F2 (1 << 2) +#define DMATFNR_ACC_F1 (1 << 1) +#define DMATFNR_ACC_F0 (1 << 0) +#define DMATFNR_ACC_FN(fifo) (1 << (fifo)) +#define DMATFNR_DISDMA 0 + +/* Device DMA Controller Parameter setting 1 Register (0x1C8) */ +#define FOTG210_DMACPSR1 0x1C8 +#define DMACPSR1_DMA_LEN(len) (((len) & 0xFFFF) << 8) +#define DMACPSR1_DMA_ABORT (1 << 3) +#define DMACPSR1_DMA_TYPE(dir_in) (((dir_in) ? 1 : 0) << 1) +#define DMACPSR1_DMA_START (1 << 0) + +/* Device DMA Controller Parameter setting 2 Register (0x1CC) */ +#define FOTG210_DMACPSR2 0x1CC + +/* Device DMA Controller Parameter setting 3 Register (0x1CC) */ +#define FOTG210_CXPORT 0x1D0 + +struct fotg210_request { + struct usb_request req; + struct list_head queue; +}; + +struct fotg210_ep { + struct usb_ep ep; + struct fotg210_udc *fotg210; + + struct list_head queue; + unsigned stall:1; + unsigned wedged:1; + unsigned use_dma:1; + + unsigned char epnum; + unsigned char type; + unsigned char dir_in; + unsigned int maxp; + const struct usb_endpoint_descriptor *desc; +}; + +struct fotg210_udc { + spinlock_t lock; /* protect the struct */ + void __iomem *reg; + + unsigned long irq_trigger; + + struct device *dev; + struct fotg210 *fotg; + struct usb_phy *phy; + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + + struct fotg210_ep *ep[FOTG210_MAX_NUM_EP]; + + struct usb_request *ep0_req; /* for internal request */ + __le16 ep0_data; + u8 ep0_dir; /* 0/0x80 out/in */ + + u8 reenum; /* if re-enumeration */ +}; + +#define gadget_to_fotg210(g) container_of((g), struct fotg210_udc, gadget) diff --git a/drivers/usb/fotg210/fotg210.h b/drivers/usb/fotg210/fotg210.h new file mode 100644 index 000000000..c44c0afe2 --- /dev/null +++ b/drivers/usb/fotg210/fotg210.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __FOTG210_H +#define __FOTG210_H + +enum gemini_port { + GEMINI_PORT_NONE = 0, + GEMINI_PORT_0, + GEMINI_PORT_1, +}; + +struct fotg210 { + struct device *dev; + struct resource *res; + void __iomem *base; + struct clk *pclk; + struct regmap *map; + enum gemini_port port; +}; + +void fotg210_vbus(struct fotg210 *fotg, bool enable); + +#ifdef CONFIG_USB_FOTG210_HCD +int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg); +int fotg210_hcd_remove(struct platform_device *pdev); +int fotg210_hcd_init(void); +void fotg210_hcd_cleanup(void); +#else +static inline int fotg210_hcd_probe(struct platform_device *pdev, + struct fotg210 *fotg) +{ + return 0; +} +static inline int fotg210_hcd_remove(struct platform_device *pdev) +{ + return 0; +} +static inline int fotg210_hcd_init(void) +{ + return 0; +} +static inline void fotg210_hcd_cleanup(void) +{ +} +#endif + +#ifdef CONFIG_USB_FOTG210_UDC +int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg); +int fotg210_udc_remove(struct platform_device *pdev); +#else +static inline int fotg210_udc_probe(struct platform_device *pdev, + struct fotg210 *fotg) +{ + return 0; +} +static inline int fotg210_udc_remove(struct platform_device *pdev) +{ + return 0; +} +#endif + +#endif /* __FOTG210_H */ diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig new file mode 100644 index 000000000..76521555e --- /dev/null +++ b/drivers/usb/gadget/Kconfig @@ -0,0 +1,511 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# 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 USB_COMMON + select NLS + help + USB is a host/device protocol, organized with one 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 documentation for this API. + +if USB_GADGET + +config USB_GADGET_DEBUG + bool "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_VERBOSE + bool "Verbose debugging Messages (DEVELOPMENT)" + depends on USB_GADGET_DEBUG + help + Many controller and gadget drivers will print verbose 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 + bool "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 + bool "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 256 + 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. + +config U_SERIAL_CONSOLE + bool "Serial gadget console support" + depends on USB_U_SERIAL + help + It supports the serial gadget can be used as a console. + +source "drivers/usb/gadget/udc/Kconfig" + +# +# 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_U_ETHER + tristate + +config USB_U_AUDIO + tristate + +config USB_F_SERIAL + tristate + +config USB_F_OBEX + tristate + +config USB_F_NCM + tristate + +config USB_F_ECM + tristate + +config USB_F_PHONET + tristate + +config USB_F_EEM + tristate + +config USB_F_SUBSET + tristate + +config USB_F_RNDIS + tristate + +config USB_F_MASS_STORAGE + tristate + +config USB_F_FS + select DMA_SHARED_BUFFER + tristate + +config USB_F_UAC1 + tristate + +config USB_F_UAC1_LEGACY + tristate + +config USB_F_UAC2 + tristate + +config USB_F_UVC + tristate + select UVC_COMMON + +config USB_F_MIDI + tristate + +config USB_F_MIDI2 + tristate + select SND_UMP + select SND_UMP_LEGACY_RAWMIDI + +config USB_F_HID + tristate + +config USB_F_PRINTER + tristate + +config USB_F_TCM + tristate + +# this first set of drivers all depend on bulk-capable hardware. + +config USB_CONFIGFS + tristate "USB Gadget functions configurable through configfs" + select USB_LIBCOMPOSITE + help + A Linux USB "gadget" can be set up through configfs. + If this is the case, the USB functions (which from the host's + perspective are seen as interfaces) and configurations are + specified simply by creating appropriate directories in configfs. + Associating functions with configurations is done by creating + appropriate symbolic links. + For more information see Documentation/usb/gadget_configfs.rst. + +config USB_CONFIGFS_SERIAL + bool "Generic serial bulk in/out" + depends on USB_CONFIGFS + depends on TTY + select USB_U_SERIAL + select USB_F_SERIAL + help + The function talks to the Linux-USB generic serial driver. + +config USB_CONFIGFS_ACM + bool "Abstract Control Model (CDC ACM)" + depends on USB_CONFIGFS + depends on TTY + select USB_U_SERIAL + select USB_F_ACM + help + ACM serial link. This function can be used to interoperate with + MS-Windows hosts or with the Linux-USB "cdc-acm" driver. + +config USB_CONFIGFS_OBEX + bool "Object Exchange Model (CDC OBEX)" + depends on USB_CONFIGFS + depends on TTY + select USB_U_SERIAL + select USB_F_OBEX + help + You will need a user space OBEX server talking to /dev/ttyGS*, + since the kernel itself doesn't implement the OBEX protocol. + +config USB_CONFIGFS_NCM + bool "Network Control Model (CDC NCM)" + depends on USB_CONFIGFS + depends on NET + select USB_U_ETHER + select USB_F_NCM + select CRC32 + help + NCM is an advanced protocol for Ethernet encapsulation, allows + grouping of several ethernet frames into one USB transfer and + different alignment possibilities. + +config USB_CONFIGFS_ECM + bool "Ethernet Control Model (CDC ECM)" + depends on USB_CONFIGFS + depends on NET + select USB_U_ETHER + select USB_F_ECM + help + 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. + +config USB_CONFIGFS_ECM_SUBSET + bool "Ethernet Control Model (CDC ECM) subset" + depends on USB_CONFIGFS + depends on NET + select USB_U_ETHER + select USB_F_SUBSET + help + On hardware that can't implement the full protocol, + a simple CDC subset is used, placing fewer demands on USB. + +config USB_CONFIGFS_RNDIS + bool "RNDIS" + depends on USB_CONFIGFS + depends on NET + select USB_U_ETHER + select USB_F_RNDIS + help + Microsoft Windows XP bundles the "Remote NDIS" (RNDIS) protocol, + and Microsoft provides redistributable binary RNDIS drivers for + older versions of Windows. + + 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_CONFIGFS_EEM + bool "Ethernet Emulation Model (EEM)" + depends on USB_CONFIGFS + depends on NET + select USB_U_ETHER + select USB_F_EEM + select CRC32 + 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. + +config USB_CONFIGFS_PHONET + bool "Phonet protocol" + depends on USB_CONFIGFS + depends on NET + depends on PHONET + select USB_U_ETHER + select USB_F_PHONET + help + The Phonet protocol implementation for USB device. + +config USB_CONFIGFS_MASS_STORAGE + bool "Mass storage" + depends on USB_CONFIGFS + depends on BLOCK + select USB_F_MASS_STORAGE + 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. + +config USB_CONFIGFS_F_LB_SS + bool "Loopback and sourcesink function (for testing)" + depends on USB_CONFIGFS + select USB_F_SS_LB + help + Loopback function loops back a configurable number of transfers. + Sourcesink function either sinks and sources bulk data. + It also implements control requests, for "chapter 9" conformance. + 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. + +config USB_CONFIGFS_F_FS + bool "Function filesystem (FunctionFS)" + depends on USB_CONFIGFS + select USB_F_FS + 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. + +config USB_CONFIGFS_F_UAC1 + bool "Audio Class 1.0" + depends on USB_CONFIGFS + depends on SND + select USB_LIBCOMPOSITE + select SND_PCM + select USB_U_AUDIO + select USB_F_UAC1 + help + This Audio function implements 1 AudioControl interface, + 1 AudioStreaming Interface each for USB-OUT and USB-IN. + 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. + +config USB_CONFIGFS_F_UAC1_LEGACY + bool "Audio Class 1.0 (legacy implementation)" + depends on USB_CONFIGFS + depends on SND + select USB_LIBCOMPOSITE + select SND_PCM + select USB_F_UAC1_LEGACY + help + This Audio function implements 1 AudioControl interface, + 1 AudioStreaming Interface each for USB-OUT and USB-IN. + This is a legacy driver and requires a real Audio codec + to be present on the device. + +config USB_CONFIGFS_F_UAC2 + bool "Audio Class 2.0" + depends on USB_CONFIGFS + depends on SND + select USB_LIBCOMPOSITE + select SND_PCM + select USB_U_AUDIO + select USB_F_UAC2 + help + This Audio function is compatible with USB Audio Class + specification 2.0. It implements 1 AudioControl interface, + 1 AudioStreaming Interface each for USB-OUT and USB-IN. + 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. + +config USB_CONFIGFS_F_MIDI + bool "MIDI function" + depends on USB_CONFIGFS + depends on SND + select USB_LIBCOMPOSITE + select SND_RAWMIDI + select USB_F_MIDI + help + The MIDI Function 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. + +config USB_CONFIGFS_F_MIDI2 + bool "MIDI 2.0 function" + depends on USB_CONFIGFS + depends on SND + select USB_LIBCOMPOSITE + select USB_F_MIDI2 + help + The MIDI 2.0 function driver provides the generic emulated + USB MIDI 2.0 interface, looped back to ALSA UMP rawmidi + device on the gadget host. It supports UMP 1.1 spec and + responds UMP Stream messages for UMP Endpoint and Function + Block information / configuration. + +config USB_CONFIGFS_F_HID + bool "HID function" + depends on USB_CONFIGFS + select USB_F_HID + help + The HID function driver provides generic emulation of USB + Human Interface Devices (HID). + + For more information, see Documentation/usb/gadget_hid.rst. + +config USB_CONFIGFS_F_UVC + bool "USB Webcam function" + depends on USB_CONFIGFS + depends on VIDEO_DEV + depends on VIDEO_DEV + select VIDEOBUF2_DMA_SG + select VIDEOBUF2_VMALLOC + select USB_F_UVC + help + The Webcam function 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. + +config USB_CONFIGFS_F_PRINTER + bool "Printer function" + select USB_F_PRINTER + depends on USB_CONFIGFS + help + The Printer function 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. + + For more information, see Documentation/usb/gadget_printer.rst + which includes sample code for accessing the device file. + +config USB_CONFIGFS_F_TCM + bool "USB Gadget Target Fabric" + depends on TARGET_CORE + depends on USB_CONFIGFS + select USB_LIBCOMPOSITE + select USB_F_TCM + help + This fabric is a USB gadget component. 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. + +source "drivers/usb/gadget/legacy/Kconfig" + +endif # USB_GADGET diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile new file mode 100644 index 000000000..33f1ef91b --- /dev/null +++ b/drivers/usb/gadget/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# USB peripheral controller drivers +# +subdir-ccflags-$(CONFIG_USB_GADGET_DEBUG) := -DDEBUG +subdir-ccflags-$(CONFIG_USB_GADGET_VERBOSE) += -DVERBOSE_DEBUG + +obj-$(CONFIG_USB_LIBCOMPOSITE) += libcomposite.o +libcomposite-y := usbstring.o config.o epautoconf.o +libcomposite-y += composite.o functions.o configfs.o u_f.o + +obj-$(CONFIG_USB_GADGET) += udc/ function/ legacy/ diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c new file mode 100644 index 000000000..df39e3487 --- /dev/null +++ b/drivers/usb/gadget/composite.c @@ -0,0 +1,2806 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * composite.c - infrastructure for Composite USB Gadgets + * + * Copyright (C) 2006-2008 David Brownell + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "u_os_desc.h" + +/** + * struct usb_os_string - represents OS String to be reported by a gadget + * @bLength: total length of the entire descritor, always 0x12 + * @bDescriptorType: USB_DT_STRING + * @qwSignature: the OS String proper + * @bMS_VendorCode: code used by the host for subsequent requests + * @bPad: not used, must be zero + */ +struct usb_os_string { + __u8 bLength; + __u8 bDescriptorType; + __u8 qwSignature[OS_STRING_QW_SIGN_LEN]; + __u8 bMS_VendorCode; + __u8 bPad; +} __packed; + +/* + * 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; +} + +/** + * function_descriptors() - get function descriptors for speed + * @f: the function + * @speed: the speed + * + * Returns the descriptors or NULL if not set. + */ +static struct usb_descriptor_header ** +function_descriptors(struct usb_function *f, + enum usb_device_speed speed) +{ + struct usb_descriptor_header **descriptors; + + /* + * NOTE: we try to help gadget drivers which might not be setting + * max_speed appropriately. + */ + + switch (speed) { + case USB_SPEED_SUPER_PLUS: + descriptors = f->ssp_descriptors; + if (descriptors) + break; + fallthrough; + case USB_SPEED_SUPER: + descriptors = f->ss_descriptors; + if (descriptors) + break; + fallthrough; + case USB_SPEED_HIGH: + descriptors = f->hs_descriptors; + if (descriptors) + break; + fallthrough; + default: + descriptors = f->fs_descriptors; + } + + /* + * if we can't find any descriptors at all, then this gadget deserves to + * Oops with a NULL pointer dereference + */ + + return descriptors; +} + +/** + * next_desc() - advance to the next desc_type descriptor + * @t: currect pointer within descriptor array + * @desc_type: descriptor type + * + * Return: next desc_type descriptor or NULL + * + * Iterate over @t until either desc_type descriptor found or + * NULL (that indicates end of list) encountered + */ +static struct usb_descriptor_header** +next_desc(struct usb_descriptor_header **t, u8 desc_type) +{ + for (; *t; t++) { + if ((*t)->bDescriptorType == desc_type) + return t; + } + return NULL; +} + +/* + * for_each_desc() - iterate over desc_type descriptors in the + * descriptors list + * @start: pointer within descriptor array. + * @iter_desc: desc_type descriptor to use as the loop cursor + * @desc_type: wanted descriptr type + */ +#define for_each_desc(start, iter_desc, desc_type) \ + for (iter_desc = next_desc(start, desc_type); \ + iter_desc; iter_desc = next_desc(iter_desc + 1, desc_type)) + +/** + * config_ep_by_speed_and_alt() - configures the given endpoint + * according to gadget speed. + * @g: pointer to the gadget + * @f: usb function + * @_ep: the endpoint to configure + * @alt: alternate setting number + * + * 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_and_alt(struct usb_gadget *g, + struct usb_function *f, + struct usb_ep *_ep, + u8 alt) +{ + struct usb_endpoint_descriptor *chosen_desc = NULL; + struct usb_interface_descriptor *int_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 */ + struct usb_composite_dev *cdev; + bool incomplete_desc = false; + + if (!g || !f || !_ep) + return -EIO; + + /* select desired speed */ + switch (g->speed) { + case USB_SPEED_SUPER_PLUS: + if (f->ssp_descriptors) { + speed_desc = f->ssp_descriptors; + want_comp_desc = 1; + break; + } + incomplete_desc = true; + fallthrough; + case USB_SPEED_SUPER: + if (f->ss_descriptors) { + speed_desc = f->ss_descriptors; + want_comp_desc = 1; + break; + } + incomplete_desc = true; + fallthrough; + case USB_SPEED_HIGH: + if (f->hs_descriptors) { + speed_desc = f->hs_descriptors; + break; + } + incomplete_desc = true; + fallthrough; + default: + speed_desc = f->fs_descriptors; + } + + cdev = get_gadget_data(g); + if (incomplete_desc) + WARNING(cdev, + "%s doesn't hold the descriptors for current speed\n", + f->name); + + /* find correct alternate setting descriptor */ + for_each_desc(speed_desc, d_spd, USB_DT_INTERFACE) { + int_desc = (struct usb_interface_descriptor *)*d_spd; + + if (int_desc->bAlternateSetting == alt) { + speed_desc = d_spd; + goto intf_found; + } + } + return -EIO; + +intf_found: + /* find descriptors */ + for_each_desc(speed_desc, d_spd, USB_DT_ENDPOINT) { + 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 = 1; + + if (g->speed == USB_SPEED_HIGH && (usb_endpoint_xfer_isoc(_ep->desc) || + usb_endpoint_xfer_int(_ep->desc))) + _ep->mult = usb_endpoint_maxp_mult(_ep->desc); + + 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) + 1; + fallthrough; + 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_and_alt); + +/** + * 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) +{ + return config_ep_by_speed_and_alt(g, f, _ep, 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); + + if (function->bind_deactivated) { + value = usb_function_deactivate(function); + if (value) + goto done; + } + + /* 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; + if (!config->superspeed_plus && function->ssp_descriptors) + config->superspeed_plus = 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); + + if (f->bind_deactivated) + usb_function_activate(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) { + spin_unlock_irqrestore(&cdev->lock, flags); + status = usb_gadget_deactivate(cdev->gadget); + spin_lock_irqsave(&cdev->lock, flags); + } + 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) { + spin_unlock_irqrestore(&cdev->lock, flags); + status = usb_gadget_activate(cdev->gadget); + spin_lock_irqsave(&cdev->lock, flags); + } + } + + 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); + +/** + * usb_func_wakeup - sends function wake notification to the host. + * @func: function that sends the remote wakeup notification. + * + * Applicable to devices operating at enhanced superspeed when usb + * functions are put in function suspend state and armed for function + * remote wakeup. On completion, function wake notification is sent. If + * the device is in low power state it tries to bring the device to active + * state before sending the wake notification. Since it is a synchronous + * call, caller must take care of not calling it in interrupt context. + * For devices operating at lower speeds returns negative errno. + * + * Returns zero on success, else negative errno. + */ +int usb_func_wakeup(struct usb_function *func) +{ + struct usb_gadget *gadget = func->config->cdev->gadget; + int id; + + if (!gadget->ops->func_wakeup) + return -EOPNOTSUPP; + + if (!func->func_wakeup_armed) { + ERROR(func->config->cdev, "not armed for func remote wakeup\n"); + return -EINVAL; + } + + for (id = 0; id < MAX_CONFIG_INTERFACES; id++) + if (func->config->interface[id] == func) + break; + + if (id == MAX_CONFIG_INTERFACES) { + ERROR(func->config->cdev, "Invalid function\n"); + return -EINVAL; + } + + return gadget->ops->func_wakeup(gadget, id); +} +EXPORT_SYMBOL_GPL(usb_func_wakeup); + +static u8 encode_bMaxPower(enum usb_device_speed speed, + struct usb_configuration *c) +{ + unsigned val; + + if (c->MaxPower || (c->bmAttributes & USB_CONFIG_ATT_SELFPOWER)) + val = c->MaxPower; + else + val = CONFIG_USB_GADGET_VBUS_DRAW; + if (!val) + return 0; + if (speed < USB_SPEED_SUPER) + return min(val, 500U) / 2; + else + /* + * USB 3.x supports up to 900mA, but since 900 isn't divisible + * by 8 the integral division will effectively cap to 896mA. + */ + return min(val, 900U) / 8; +} + +void check_remote_wakeup_config(struct usb_gadget *g, + struct usb_configuration *c) +{ + if (USB_CONFIG_ATT_WAKEUP & c->bmAttributes) { + /* Reset the rw bit if gadget is not capable of it */ + if (!g->wakeup_capable && g->ops->set_remote_wakeup) { + WARN(c->cdev, "Clearing wakeup bit for config c.%d\n", + c->bConfigurationValue); + c->bmAttributes &= ~USB_CONFIG_ATT_WAKEUP; + } + } +} + +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; + + descriptors = function_descriptors(f, speed); + 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; + struct list_head *pos; + 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; + + pos = &cdev->configs; + c = cdev->os_desc_config; + if (c) + goto check_config; + + while ((pos = pos->next) != &cdev->configs) { + c = list_entry(pos, typeof(*c), list); + + /* skip OS Descriptors config which is handled separately */ + if (c == cdev->os_desc_config) + continue; + +check_config: + /* ignore configs that won't work at this speed */ + switch (speed) { + case USB_SPEED_SUPER_PLUS: + if (!c->superspeed_plus) + continue; + break; + 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; + int ssp = 0; + + if (gadget_is_dualspeed(gadget)) { + if (gadget->speed == USB_SPEED_HIGH) + hs = 1; + if (gadget->speed == USB_SPEED_SUPER) + ss = 1; + if (gadget->speed == USB_SPEED_SUPER_PLUS) + ssp = 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 (ssp) { + if (!c->superspeed_plus) + continue; + } else 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_dcd_config_params dcd_config_params; + struct usb_bos_descriptor *bos = cdev->req->buf; + unsigned int besl = 0; + + bos->bLength = USB_DT_BOS_SIZE; + bos->bDescriptorType = USB_DT_BOS; + + bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE); + bos->bNumDeviceCaps = 0; + + /* Get Controller configuration */ + if (cdev->gadget->ops->get_config_params) { + cdev->gadget->ops->get_config_params(cdev->gadget, + &dcd_config_params); + } else { + dcd_config_params.besl_baseline = + USB_DEFAULT_BESL_UNSPECIFIED; + dcd_config_params.besl_deep = + USB_DEFAULT_BESL_UNSPECIFIED; + dcd_config_params.bU1devExitLat = + USB_DEFAULT_U1_DEV_EXIT_LAT; + dcd_config_params.bU2DevExitLat = + cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT); + } + + if (dcd_config_params.besl_baseline != USB_DEFAULT_BESL_UNSPECIFIED) + besl = USB_BESL_BASELINE_VALID | + USB_SET_BESL_BASELINE(dcd_config_params.besl_baseline); + + if (dcd_config_params.besl_deep != USB_DEFAULT_BESL_UNSPECIFIED) + besl |= USB_BESL_DEEP_VALID | + USB_SET_BESL_DEEP(dcd_config_params.besl_deep); + + /* + * A SuperSpeed device shall include the USB2.0 extension descriptor + * and shall support LPM when operating in USB2.0 HS mode. + */ + if (cdev->gadget->lpm_capable) { + 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 | + USB_BESL_SUPPORT | besl); + } + + /* + * The Superspeed USB Capability descriptor shall be implemented by all + * SuperSpeed devices. + */ + if (gadget_is_superspeed(cdev->gadget)) { + struct usb_ss_cap_descriptor *ss_cap; + + 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; + ss_cap->bU1devExitLat = dcd_config_params.bU1devExitLat; + ss_cap->bU2DevExitLat = dcd_config_params.bU2DevExitLat; + } + + /* The SuperSpeedPlus USB Device Capability descriptor */ + if (gadget_is_superspeed_plus(cdev->gadget)) { + struct usb_ssp_cap_descriptor *ssp_cap; + u8 ssac = 1; + u8 ssic; + int i; + + if (cdev->gadget->max_ssp_rate == USB_SSP_GEN_2x2) + ssac = 3; + + /* + * Paired RX and TX sublink speed attributes share + * the same SSID. + */ + ssic = (ssac + 1) / 2 - 1; + + ssp_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SSP_CAP_SIZE(ssac)); + ssp_cap->bLength = USB_DT_USB_SSP_CAP_SIZE(ssac); + ssp_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + ssp_cap->bDevCapabilityType = USB_SSP_CAP_TYPE; + ssp_cap->bReserved = 0; + ssp_cap->wReserved = 0; + + ssp_cap->bmAttributes = + cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_ATTRIBS, ssac) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_IDS, ssic)); + + ssp_cap->wFunctionalitySupport = + cpu_to_le16(FIELD_PREP(USB_SSP_MIN_SUBLINK_SPEED_ATTRIBUTE_ID, 0) | + FIELD_PREP(USB_SSP_MIN_RX_LANE_COUNT, 1) | + FIELD_PREP(USB_SSP_MIN_TX_LANE_COUNT, 1)); + + /* + * Use 1 SSID if the gadget supports up to gen2x1 or not + * specified: + * - SSID 0 for symmetric RX/TX sublink speed of 10 Gbps. + * + * Use 1 SSID if the gadget supports up to gen1x2: + * - SSID 0 for symmetric RX/TX sublink speed of 5 Gbps. + * + * Use 2 SSIDs if the gadget supports up to gen2x2: + * - SSID 0 for symmetric RX/TX sublink speed of 5 Gbps. + * - SSID 1 for symmetric RX/TX sublink speed of 10 Gbps. + */ + for (i = 0; i < ssac + 1; i++) { + u8 ssid; + u8 mantissa; + u8 type; + + ssid = i >> 1; + + if (cdev->gadget->max_ssp_rate == USB_SSP_GEN_2x1 || + cdev->gadget->max_ssp_rate == USB_SSP_GEN_UNKNOWN) + mantissa = 10; + else + mantissa = 5 << ssid; + + if (i % 2) + type = USB_SSP_SUBLINK_SPEED_ST_SYM_TX; + else + type = USB_SSP_SUBLINK_SPEED_ST_SYM_RX; + + ssp_cap->bmSublinkSpeedAttr[i] = + cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_SSID, ssid) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSE, + USB_SSP_SUBLINK_SPEED_LSE_GBPS) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, type) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LP, + USB_SSP_SUBLINK_SPEED_LP_SSP) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSM, mantissa)); + } + } + + /* The WebUSB Platform Capability descriptor */ + if (cdev->use_webusb) { + struct usb_plat_dev_cap_descriptor *webusb_cap; + struct usb_webusb_cap_data *webusb_cap_data; + guid_t webusb_uuid = WEBUSB_UUID; + + webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + webusb_cap_data = (struct usb_webusb_cap_data *) webusb_cap->CapabilityData; + bos->bNumDeviceCaps++; + le16_add_cpu(&bos->wTotalLength, + USB_DT_USB_PLAT_DEV_CAP_SIZE(USB_WEBUSB_CAP_DATA_SIZE)); + + webusb_cap->bLength = USB_DT_USB_PLAT_DEV_CAP_SIZE(USB_WEBUSB_CAP_DATA_SIZE); + webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE; + webusb_cap->bReserved = 0; + export_guid(webusb_cap->UUID, &webusb_uuid); + + if (cdev->bcd_webusb_version != 0) + webusb_cap_data->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version); + else + webusb_cap_data->bcdVersion = WEBUSB_VERSION_1_00; + + webusb_cap_data->bVendorCode = cdev->b_webusb_vendor_code; + + if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0) + webusb_cap_data->iLandingPage = WEBUSB_LANDING_PAGE_PRESENT; + else + webusb_cap_data->iLandingPage = WEBUSB_LANDING_PAGE_NOT_PRESENT; + } + + 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); + + /* Section 9.1.1.6, disable remote wakeup when device is reset */ + f->func_wakeup_armed = false; + + 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, *iter; + int result = -EINVAL; + unsigned power = gadget_is_otg(gadget) ? 8 : 100; + int tmp; + + if (number) { + list_for_each_entry(iter, &cdev->configs, list) { + if (iter->bConfigurationValue != number) + continue; + /* + * We disable the FDs of the previous + * configuration only if the new configuration + * is a valid one + */ + if (cdev->config) + reset_config(cdev); + c = iter; + 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; + } + + DBG(cdev, "%s config #%d: %s\n", + usb_speed_string(gadget->speed), + number, c ? c->label : "unconfigured"); + + if (!c) + goto done; + + usb_gadget_set_state(gadget, USB_STATE_CONFIGURED); + 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. + */ + descriptors = function_descriptors(f, gadget->speed); + + 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) + | usb_endpoint_num(ep); + 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 */ + if (c->MaxPower || (c->bmAttributes & USB_CONFIG_ATT_SELFPOWER)) + power = c->MaxPower; + else + power = CONFIG_USB_GADGET_VBUS_DRAW; + + if (gadget->speed < USB_SPEED_SUPER) + power = min(power, 500U); + else + power = min(power, 900U); + + if (USB_CONFIG_ATT_WAKEUP & c->bmAttributes) + usb_gadget_set_remote_wakeup(gadget, 1); + else + usb_gadget_set_remote_wakeup(gadget, 0); +done: + if (power > USB_SELF_POWER_VBUS_MAX_DRAW || + (c && !(c->bmAttributes & USB_CONFIG_ATT_SELFPOWER))) + usb_gadget_clear_selfpowered(gadget); + else + usb_gadget_set_selfpowered(gadget); + + 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); + + status = usb_add_config_only(cdev, config); + if (status) + goto done; + + status = bind(config); + + if (status == 0) + status = usb_gadget_check_config(cdev->gadget); + + 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(&config->list); + config->cdev = NULL; + } else { + unsigned i; + + DBG(cdev, "cfg %d/%p speeds:%s%s%s%s\n", + config->bConfigurationValue, config, + config->superspeed_plus ? " superplus" : "", + 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->claimed 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 remove_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); + + usb_remove_function(config, f); + } + list_del(&config->list); + if (config->unbind) { + DBG(cdev, "unbind config '%s'/%p\n", config->label, config); + config->unbind(config); + /* may free memory for "c" */ + } +} + +/*-------------------------------------------------------------------------*/ + +/* 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[USB_MAX_STRING_LEN]; 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 <= USB_MAX_STRING_LEN && s->wData[len]; len++) + continue; + if (!len) + return -EINVAL; + + s->bLength = 2 * (len + 1); + return s->bLength; + } + + if (cdev->use_os_string && language == 0 && id == OS_STRING_IDX) { + struct usb_os_string *b = buf; + b->bLength = sizeof(*b); + b->bDescriptorType = USB_DT_STRING; + compiletime_assert( + sizeof(b->qwSignature) == sizeof(cdev->qw_sign), + "qwSignature size must be equal to qw_sign"); + memcpy(&b->qwSignature, cdev->qw_sign, sizeof(b->qwSignature)); + b->bMS_VendorCode = cdev->b_vendor_code; + b->bPad = 0; + return sizeof(*b); + } + + 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_tab() - 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 languages 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 contains 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_CAST(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) +{ + struct usb_composite_dev *cdev; + + 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); + + /* + * REVIST The same ep0 requests are shared with function drivers + * so they don't have to maintain the same ->complete() stubs. + * + * Because of that, we need to check for the validity of ->context + * here, even though we know we've set it to something useful. + */ + if (!req->context) + return; + + cdev = req->context; + + if (cdev->req == req) + cdev->setup_pending = false; + else if (cdev->os_desc_req == req) + cdev->os_desc_pending = false; + else + WARN(1, "unknown request %p\n", req); +} + +static int composite_ep0_queue(struct usb_composite_dev *cdev, + struct usb_request *req, gfp_t gfp_flags) +{ + int ret; + + ret = usb_ep_queue(cdev->gadget->ep0, req, gfp_flags); + if (ret == 0) { + if (cdev->req == req) + cdev->setup_pending = true; + else if (cdev->os_desc_req == req) + cdev->os_desc_pending = true; + else + WARN(1, "unknown request %p\n", req); + } + + return ret; +} + +static int count_ext_compat(struct usb_configuration *c) +{ + int i, res; + + res = 0; + for (i = 0; i < c->next_interface_id; ++i) { + struct usb_function *f; + int j; + + f = c->interface[i]; + for (j = 0; j < f->os_desc_n; ++j) { + struct usb_os_desc *d; + + if (i != f->os_desc_table[j].if_id) + continue; + d = f->os_desc_table[j].os_desc; + if (d && d->ext_compat_id) + ++res; + } + } + BUG_ON(res > 255); + return res; +} + +static int fill_ext_compat(struct usb_configuration *c, u8 *buf) +{ + int i, count; + + count = 16; + buf += 16; + for (i = 0; i < c->next_interface_id; ++i) { + struct usb_function *f; + int j; + + f = c->interface[i]; + for (j = 0; j < f->os_desc_n; ++j) { + struct usb_os_desc *d; + + if (i != f->os_desc_table[j].if_id) + continue; + d = f->os_desc_table[j].os_desc; + if (d && d->ext_compat_id) { + *buf++ = i; + *buf++ = 0x01; + memcpy(buf, d->ext_compat_id, 16); + buf += 22; + } else { + ++buf; + *buf = 0x01; + buf += 23; + } + count += 24; + if (count + 24 >= USB_COMP_EP0_OS_DESC_BUFSIZ) + return count; + } + } + + return count; +} + +static int count_ext_prop(struct usb_configuration *c, int interface) +{ + struct usb_function *f; + int j; + + f = c->interface[interface]; + for (j = 0; j < f->os_desc_n; ++j) { + struct usb_os_desc *d; + + if (interface != f->os_desc_table[j].if_id) + continue; + d = f->os_desc_table[j].os_desc; + if (d && d->ext_compat_id) + return d->ext_prop_count; + } + return 0; +} + +static int len_ext_prop(struct usb_configuration *c, int interface) +{ + struct usb_function *f; + struct usb_os_desc *d; + int j, res; + + res = 10; /* header length */ + f = c->interface[interface]; + for (j = 0; j < f->os_desc_n; ++j) { + if (interface != f->os_desc_table[j].if_id) + continue; + d = f->os_desc_table[j].os_desc; + if (d) + return min(res + d->ext_prop_len, 4096); + } + return res; +} + +static int fill_ext_prop(struct usb_configuration *c, int interface, u8 *buf) +{ + struct usb_function *f; + struct usb_os_desc *d; + struct usb_os_desc_ext_prop *ext_prop; + int j, count, n, ret; + + f = c->interface[interface]; + count = 10; /* header length */ + buf += 10; + for (j = 0; j < f->os_desc_n; ++j) { + if (interface != f->os_desc_table[j].if_id) + continue; + d = f->os_desc_table[j].os_desc; + if (d) + list_for_each_entry(ext_prop, &d->ext_prop, entry) { + n = ext_prop->data_len + + ext_prop->name_len + 14; + if (count + n >= USB_COMP_EP0_OS_DESC_BUFSIZ) + return count; + usb_ext_prop_put_size(buf, n); + usb_ext_prop_put_type(buf, ext_prop->type); + ret = usb_ext_prop_put_name(buf, ext_prop->name, + ext_prop->name_len); + if (ret < 0) + return ret; + switch (ext_prop->type) { + case USB_EXT_PROP_UNICODE: + case USB_EXT_PROP_UNICODE_ENV: + case USB_EXT_PROP_UNICODE_LINK: + usb_ext_prop_put_unicode(buf, ret, + ext_prop->data, + ext_prop->data_len); + break; + case USB_EXT_PROP_BINARY: + usb_ext_prop_put_binary(buf, ret, + ext_prop->data, + ext_prop->data_len); + break; + case USB_EXT_PROP_LE32: + /* not implemented */ + case USB_EXT_PROP_BE32: + /* not implemented */ + default: + return -EINVAL; + } + buf += n; + count += n; + } + } + + return count; +} + +/* + * 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; + struct usb_function *iter; + u8 endp; + + if (w_length > USB_COMP_EP0_BUFSIZ) { + if (ctrl->bRequestType & USB_DIR_IN) { + /* Cast away the const, we are going to overwrite on purpose. */ + __le16 *temp = (__le16 *)&ctrl->wLength; + + *temp = cpu_to_le16(USB_COMP_EP0_BUFSIZ); + w_length = USB_COMP_EP0_BUFSIZ; + } else { + goto done; + } + } + + /* 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->context = cdev; + req->complete = composite_setup_complete; + req->length = 0; + gadget->ep0->driver_data = cdev; + + /* + * Don't let non-standard requests match any of the cases below + * by accident. + */ + if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) + goto unknown; + + 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(0x0320); + cdev->desc.bMaxPacketSize0 = 9; + } else { + cdev->desc.bcdUSB = cpu_to_le16(0x0210); + } + } else { + if (gadget->lpm_capable || cdev->use_webusb) + cdev->desc.bcdUSB = cpu_to_le16(0x0201); + else + cdev->desc.bcdUSB = cpu_to_le16(0x0200); + } + + value = min_t(u16, w_length, 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_t(u16, w_length, value); + break; + case USB_DT_STRING: + value = get_string(cdev, req->buf, + w_index, w_value & 0xff); + if (value >= 0) + value = min_t(u16, w_length, value); + break; + case USB_DT_BOS: + if (gadget_is_superspeed(gadget) || + gadget->lpm_capable || cdev->use_webusb) { + value = bos_desc(cdev); + value = min_t(u16, w_length, value); + } + break; + case USB_DT_OTG: + if (gadget_is_otg(gadget)) { + struct usb_configuration *config; + int otg_desc_len = 0; + + if (cdev->config) + config = cdev->config; + else + config = list_first_entry_or_null( + &cdev->configs, + struct usb_configuration, + list); + if (!config) + goto done; + + if (gadget->otg_caps && + (gadget->otg_caps->otg_rev >= 0x0200)) + otg_desc_len += sizeof( + struct usb_otg20_descriptor); + else + otg_desc_len += sizeof( + struct usb_otg_descriptor); + + value = min_t(int, w_length, otg_desc_len); + memcpy(req->buf, config->descriptors[0], 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_t(u16, w_length, 1); + break; + + /* function drivers must handle get/set altsetting */ + 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 there's no get_alt() method, we know only altsetting zero + * works. There is no need to check if set_alt() is not NULL + * as we check this in usb_add_function(). + */ + if (w_value && !f->get_alt) + break; + + spin_lock(&cdev->lock); + 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); + } + spin_unlock(&cdev->lock); + 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_t(u16, w_length, 1); + break; + case USB_REQ_GET_STATUS: + if (gadget_is_otg(gadget) && gadget->hnp_polling_support && + (w_index == OTG_STS_SELECTOR)) { + if (ctrl->bRequestType != (USB_DIR_IN | + USB_RECIP_DEVICE)) + goto unknown; + *((u8 *)req->buf) = gadget->host_request_flag; + value = 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 + */ + 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; + + if (f->get_status) { + status = f->get_status(f); + + if (status < 0) + break; + + /* if D5 is not set, then device is not wakeup capable */ + if (!(f->config->bmAttributes & USB_CONFIG_ATT_WAKEUP)) + status &= ~(USB_INTRF_STAT_FUNC_RW_CAP | USB_INTRF_STAT_FUNC_RW); + } + + 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); + /* SetFeature(FUNCTION_SUSPEND) */ + } else if (ctrl->bRequest == USB_REQ_SET_FEATURE) { + if (!(f->config->bmAttributes & + USB_CONFIG_ATT_WAKEUP) && + (w_index & USB_INTRF_FUNC_SUSPEND_RW)) + break; + + f->func_wakeup_armed = !!(w_index & + USB_INTRF_FUNC_SUSPEND_RW); + + if (w_index & USB_INTRF_FUNC_SUSPEND_LP) { + if (f->suspend && !f->func_suspended) { + f->suspend(f); + f->func_suspended = true; + } + /* + * Handle cases where host sends function resume + * through SetFeature(FUNCTION_SUSPEND) but low power + * bit reset + */ + } else { + if (f->resume && f->func_suspended) { + f->resume(f); + f->func_suspended = false; + } + } + /* ClearFeature(FUNCTION_SUSPEND) */ + } else if (ctrl->bRequest == USB_REQ_CLEAR_FEATURE) { + f->func_wakeup_armed = false; + + if (f->resume && f->func_suspended) { + f->resume(f); + f->func_suspended = false; + } + } + + if (value < 0) { + ERROR(cdev, + "func_suspend() returned error %d\n", + value); + value = 0; + } + break; + } + break; + default: +unknown: + /* + * OS descriptors handling + */ + if (cdev->use_os_string && cdev->os_desc_config && + (ctrl->bRequestType & USB_TYPE_VENDOR) && + ctrl->bRequest == cdev->b_vendor_code) { + struct usb_configuration *os_desc_cfg; + u8 *buf; + int interface; + int count = 0; + + req = cdev->os_desc_req; + req->context = cdev; + req->complete = composite_setup_complete; + buf = req->buf; + os_desc_cfg = cdev->os_desc_config; + w_length = min_t(u16, w_length, USB_COMP_EP0_OS_DESC_BUFSIZ); + memset(buf, 0, w_length); + buf[5] = 0x01; + switch (ctrl->bRequestType & USB_RECIP_MASK) { + /* + * The Microsoft CompatID OS Descriptor Spec(w_index = 0x4) and + * Extended Prop OS Desc Spec(w_index = 0x5) state that the + * HighByte of wValue is the InterfaceNumber and the LowByte is + * the PageNumber. This high/low byte ordering is incorrectly + * documented in the Spec. USB analyzer output on the below + * request packets show the high/low byte inverted i.e LowByte + * is the InterfaceNumber and the HighByte is the PageNumber. + * Since we dont support >64KB CompatID/ExtendedProp descriptors, + * PageNumber is set to 0. Hence verify that the HighByte is 0 + * for below two cases. + */ + case USB_RECIP_DEVICE: + if (w_index != 0x4 || (w_value >> 8)) + break; + buf[6] = w_index; + /* Number of ext compat interfaces */ + count = count_ext_compat(os_desc_cfg); + buf[8] = count; + count *= 24; /* 24 B/ext compat desc */ + count += 16; /* header */ + put_unaligned_le32(count, buf); + value = w_length; + if (w_length > 0x10) { + value = fill_ext_compat(os_desc_cfg, buf); + value = min_t(u16, w_length, value); + } + break; + case USB_RECIP_INTERFACE: + if (w_index != 0x5 || (w_value >> 8)) + break; + interface = w_value & 0xFF; + if (interface >= MAX_CONFIG_INTERFACES || + !os_desc_cfg->interface[interface]) + break; + buf[6] = w_index; + count = count_ext_prop(os_desc_cfg, + interface); + put_unaligned_le16(count, buf + 8); + count = len_ext_prop(os_desc_cfg, + interface); + put_unaligned_le32(count, buf); + value = w_length; + if (w_length > 0x0A) { + value = fill_ext_prop(os_desc_cfg, + interface, buf); + if (value >= 0) + value = min_t(u16, w_length, value); + } + break; + } + + goto check_value; + } + + /* + * WebUSB URL descriptor handling, following: + * https://wicg.github.io/webusb/#device-requests + */ + if (cdev->use_webusb && + ctrl->bRequestType == (USB_DIR_IN | USB_TYPE_VENDOR) && + w_index == WEBUSB_GET_URL && + w_value == WEBUSB_LANDING_PAGE_PRESENT && + ctrl->bRequest == cdev->b_webusb_vendor_code) { + unsigned int landing_page_length; + unsigned int landing_page_offset; + struct webusb_url_descriptor *url_descriptor = + (struct webusb_url_descriptor *)cdev->req->buf; + + url_descriptor->bDescriptorType = WEBUSB_URL_DESCRIPTOR_TYPE; + + if (strncasecmp(cdev->landing_page, "https://", 8) == 0) { + landing_page_offset = 8; + url_descriptor->bScheme = WEBUSB_URL_SCHEME_HTTPS; + } else if (strncasecmp(cdev->landing_page, "http://", 7) == 0) { + landing_page_offset = 7; + url_descriptor->bScheme = WEBUSB_URL_SCHEME_HTTP; + } else { + landing_page_offset = 0; + url_descriptor->bScheme = WEBUSB_URL_SCHEME_NONE; + } + + landing_page_length = strnlen(cdev->landing_page, + sizeof(url_descriptor->URL) + - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_offset); + + if (w_length < WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH) + landing_page_length = landing_page_offset; + else if (w_length < + WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_length) + landing_page_length = w_length + - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_offset; + + memcpy(url_descriptor->URL, + cdev->landing_page + landing_page_offset, + landing_page_length - landing_page_offset); + url_descriptor->bLength = landing_page_length + - landing_page_offset + WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH; + + value = url_descriptor->bLength; + + goto check_value; + } + + 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. + */ + if (cdev->config) { + list_for_each_entry(f, &cdev->config->functions, list) + if (f->req_match && + f->req_match(f, ctrl, false)) + goto try_fun_setup; + } else { + struct usb_configuration *c; + list_for_each_entry(c, &cdev->configs, list) + list_for_each_entry(f, &c->functions, list) + if (f->req_match && + f->req_match(f, ctrl, true)) + goto try_fun_setup; + } + f = NULL; + + 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: + if (!cdev->config) + break; + endp = ((w_index & 0x80) >> 3) | (w_index & 0x0f); + list_for_each_entry(iter, &cdev->config->functions, list) { + if (test_bit(endp, iter->endpoints)) { + f = iter; + break; + } + } + break; + } +try_fun_setup: + if (f && f->setup) + value = f->setup(f, ctrl); + else { + struct usb_configuration *c; + + c = cdev->config; + if (!c) + goto done; + + /* try current config's setup */ + if (c->setup) { + value = c->setup(c, ctrl); + goto done; + } + + /* try the only function in the current config */ + if (!list_is_singular(&c->functions)) + goto done; + f = list_first_entry(&c->functions, struct usb_function, + list); + if (f->setup) + value = f->setup(f, ctrl); + } + + goto done; + } + +check_value: + /* respond with data transfer before status phase? */ + if (value >= 0 && value != USB_GADGET_DELAYED_STATUS) { + req->length = value; + req->context = cdev; + req->zero = value < w_length; + value = composite_ep0_queue(cdev, 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; +} + +static 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); + cdev->suspended = 0; + if (cdev->config) + reset_config(cdev); + if (cdev->driver->disconnect) + cdev->driver->disconnect(cdev); + spin_unlock_irqrestore(&cdev->lock, flags); +} + +void composite_disconnect(struct usb_gadget *gadget) +{ + usb_gadget_vbus_draw(gadget, 0); + __composite_disconnect(gadget); +} + +void composite_reset(struct usb_gadget *gadget) +{ + /* + * Section 1.4.13 Standard Downstream Port of the USB battery charging + * specification v1.2 states that a device connected on a SDP shall only + * draw at max 100mA while in a connected, but unconfigured state. + */ + usb_gadget_vbus_draw(gadget, 100); + __composite_disconnect(gadget); +} + +/*-------------------------------------------------------------------------*/ + +static ssize_t suspended_show(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_RO(suspended); + +static void __composite_unbind(struct usb_gadget *gadget, bool unbind_driver) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_gadget_strings *gstr = cdev->driver->strings[0]; + struct usb_string *dev_str = gstr->strings; + + /* 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); + remove_config(cdev, c); + } + if (cdev->driver->unbind && unbind_driver) + cdev->driver->unbind(cdev); + + composite_dev_cleanup(cdev); + + if (dev_str[USB_GADGET_MANUFACTURER_IDX].s == cdev->def_manufacturer) + dev_str[USB_GADGET_MANUFACTURER_IDX].s = ""; + + 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 = kzalloc(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; + cdev->req->context = cdev; + 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; +} + +int composite_os_desc_req_prepare(struct usb_composite_dev *cdev, + struct usb_ep *ep0) +{ + int ret = 0; + + cdev->os_desc_req = usb_ep_alloc_request(ep0, GFP_KERNEL); + if (!cdev->os_desc_req) { + ret = -ENOMEM; + goto end; + } + + cdev->os_desc_req->buf = kmalloc(USB_COMP_EP0_OS_DESC_BUFSIZ, + GFP_KERNEL); + if (!cdev->os_desc_req->buf) { + ret = -ENOMEM; + usb_ep_free_request(ep0, cdev->os_desc_req); + /* + * Set os_desc_req to NULL so that composite_dev_cleanup() + * will not try to free it again. + */ + cdev->os_desc_req = NULL; + goto end; + } + cdev->os_desc_req->context = cdev; + cdev->os_desc_req->complete = composite_setup_complete; +end: + return ret; +} + +void composite_dev_cleanup(struct usb_composite_dev *cdev) +{ + struct usb_gadget_string_container *uc, *tmp; + struct usb_ep *ep, *tmp_ep; + + list_for_each_entry_safe(uc, tmp, &cdev->gstrings, list) { + list_del(&uc->list); + kfree(uc); + } + if (cdev->os_desc_req) { + if (cdev->os_desc_pending) + usb_ep_dequeue(cdev->gadget->ep0, cdev->os_desc_req); + + kfree(cdev->os_desc_req->buf); + cdev->os_desc_req->buf = NULL; + usb_ep_free_request(cdev->gadget->ep0, cdev->os_desc_req); + cdev->os_desc_req = NULL; + } + if (cdev->req) { + if (cdev->setup_pending) + usb_ep_dequeue(cdev->gadget->ep0, cdev->req); + + kfree(cdev->req->buf); + cdev->req->buf = NULL; + usb_ep_free_request(cdev->gadget->ep0, cdev->req); + cdev->req = NULL; + } + cdev->next_string_id = 0; + device_remove_file(&cdev->gadget->dev, &dev_attr_suspended); + + /* + * Some UDC backends have a dynamic EP allocation scheme. + * + * In that case, the dispose() callback is used to notify the + * backend that the EPs are no longer in use. + * + * Note: The UDC backend can remove the EP from the ep_list as + * a result, so we need to use the _safe list iterator. + */ + list_for_each_entry_safe(ep, tmp_ep, + &cdev->gadget->ep_list, ep_list) { + if (ep->ops->dispose) + ep->ops->dispose(ep); + } +} + +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_obj(*cdev); + 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; + + if (cdev->use_os_string) { + status = composite_os_desc_req_prepare(cdev, gadget->ep0); + if (status) + 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; +} + +/*-------------------------------------------------------------------------*/ + +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; + + if (cdev->config && + cdev->config->bmAttributes & USB_CONFIG_ATT_SELFPOWER) + usb_gadget_set_selfpowered(gadget); + + usb_gadget_vbus_draw(gadget, 2); +} + +void composite_resume(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_function *f; + unsigned 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) { + /* + * Check for func_suspended flag to see if the function is + * in USB3 FUNCTION_SUSPEND state. In this case resume is + * done via FUNCTION_SUSPEND feature selector. + */ + if (f->resume && !f->func_suspended) + f->resume(f); + } + + maxpower = cdev->config->MaxPower ? + cdev->config->MaxPower : CONFIG_USB_GADGET_VBUS_DRAW; + if (gadget->speed < USB_SPEED_SUPER) + maxpower = min(maxpower, 500U); + else + maxpower = min(maxpower, 900U); + + if (maxpower > USB_SELF_POWER_VBUS_MAX_DRAW || + !(cdev->config->bmAttributes & USB_CONFIG_ATT_SELFPOWER)) + usb_gadget_clear_selfpowered(gadget); + else + usb_gadget_set_selfpowered(gadget); + + usb_gadget_vbus_draw(gadget, maxpower); + } else { + maxpower = CONFIG_USB_GADGET_VBUS_DRAW; + maxpower = min(maxpower, 100U); + usb_gadget_vbus_draw(gadget, maxpower); + } + + cdev->suspended = 0; +} + +/*-------------------------------------------------------------------------*/ + +static const struct usb_gadget_driver composite_driver_template = { + .bind = composite_bind, + .unbind = composite_unbind, + + .setup = composite_setup, + .reset = composite_reset, + .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_register_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; + req->context = cdev; + value = composite_ep0_queue(cdev, 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) +{ + return kasprintf(GFP_KERNEL, "%s %s with %s", init_utsname()->sysname, + init_utsname()->release, gadget->name); +} + +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_DESCRIPTION("infrastructure for Composite USB Gadgets"); +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 000000000..256364d4b --- /dev/null +++ b/drivers/usb/gadget/config.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * usb/gadget/config.c -- simplify building config descriptors + * + * Copyright (C) 2003 David Brownell + */ + +#include +#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_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_descriptor_header **ssp) +{ + /* super-speed-plus descriptor falls back to super-speed one, + * if such a descriptor was provided, thus avoiding a NULL + * pointer dereference if a 5gbps capable gadget is used with + * a 10gbps capable config (device port + cable + host port) + */ + if (!ssp) + ssp = ss; + + if (fs) { + f->fs_descriptors = usb_copy_descriptors(fs); + if (!f->fs_descriptors) + goto err; + } + if (hs) { + f->hs_descriptors = usb_copy_descriptors(hs); + if (!f->hs_descriptors) + goto err; + } + if (ss) { + f->ss_descriptors = usb_copy_descriptors(ss); + if (!f->ss_descriptors) + goto err; + } + if (ssp) { + f->ssp_descriptors = usb_copy_descriptors(ssp); + if (!f->ssp_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); + f->fs_descriptors = NULL; + usb_free_descriptors(f->hs_descriptors); + f->hs_descriptors = NULL; + usb_free_descriptors(f->ss_descriptors); + f->ss_descriptors = NULL; + usb_free_descriptors(f->ssp_descriptors); + f->ssp_descriptors = NULL; +} +EXPORT_SYMBOL_GPL(usb_free_all_descriptors); + +struct usb_descriptor_header *usb_otg_descriptor_alloc( + struct usb_gadget *gadget) +{ + struct usb_descriptor_header *otg_desc; + unsigned length = 0; + + if (gadget->otg_caps && (gadget->otg_caps->otg_rev >= 0x0200)) + length = sizeof(struct usb_otg20_descriptor); + else + length = sizeof(struct usb_otg_descriptor); + + otg_desc = kzalloc(length, GFP_KERNEL); + return otg_desc; +} +EXPORT_SYMBOL_GPL(usb_otg_descriptor_alloc); + +int usb_otg_descriptor_init(struct usb_gadget *gadget, + struct usb_descriptor_header *otg_desc) +{ + struct usb_otg_descriptor *otg1x_desc; + struct usb_otg20_descriptor *otg20_desc; + struct usb_otg_caps *otg_caps = gadget->otg_caps; + u8 otg_attributes = 0; + + if (!otg_desc) + return -EINVAL; + + if (otg_caps && otg_caps->otg_rev) { + if (otg_caps->hnp_support) + otg_attributes |= USB_OTG_HNP; + if (otg_caps->srp_support) + otg_attributes |= USB_OTG_SRP; + if (otg_caps->adp_support && (otg_caps->otg_rev >= 0x0200)) + otg_attributes |= USB_OTG_ADP; + } else { + otg_attributes = USB_OTG_SRP | USB_OTG_HNP; + } + + if (otg_caps && (otg_caps->otg_rev >= 0x0200)) { + otg20_desc = (struct usb_otg20_descriptor *)otg_desc; + otg20_desc->bLength = sizeof(struct usb_otg20_descriptor); + otg20_desc->bDescriptorType = USB_DT_OTG; + otg20_desc->bmAttributes = otg_attributes; + otg20_desc->bcdOTG = cpu_to_le16(otg_caps->otg_rev); + } else { + otg1x_desc = (struct usb_otg_descriptor *)otg_desc; + otg1x_desc->bLength = sizeof(struct usb_otg_descriptor); + otg1x_desc->bDescriptorType = USB_DT_OTG; + otg1x_desc->bmAttributes = otg_attributes; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_otg_descriptor_init); diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c new file mode 100644 index 000000000..51df6d1d1 --- /dev/null +++ b/drivers/usb/gadget/configfs.c @@ -0,0 +1,2108 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "configfs.h" +#include "u_os_desc.h" + +static int check_user_usb_string(const char *name, + struct usb_gadget_strings *stringtab_dev) +{ + u16 num; + int ret; + + ret = kstrtou16(name, 0, &num); + if (ret) + return ret; + + if (!usb_validate_langid(num)) + return -EINVAL; + + stringtab_dev->language = num; + return 0; +} + +#define MAX_NAME_LEN 40 +#define MAX_USB_STRING_LANGS 2 + +static const struct usb_descriptor_header *otg_desc[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 os_desc_group; + struct config_group webusb_group; + + struct mutex lock; + struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1]; + struct list_head string_list; + struct list_head available_func; + + struct usb_composite_driver composite; + struct usb_composite_dev cdev; + bool use_os_desc; + char b_vendor_code; + char qw_sign[OS_STRING_QW_SIGN_LEN]; + bool use_webusb; + u16 bcd_webusb_version; + u8 b_webusb_vendor_code; + char landing_page[WEBUSB_URL_RAW_MAX_LENGTH]; + + spinlock_t spinlock; + bool unbind; +}; + +static inline struct gadget_info *to_gadget_info(struct config_item *item) +{ + return container_of(to_config_group(item), struct gadget_info, group); +} + +struct config_usb_cfg { + struct config_group group; + struct config_group strings_group; + struct list_head string_list; + struct usb_configuration c; + struct list_head func_list; + struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1]; +}; + +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 gadget_info *cfg_to_gadget_info(struct config_usb_cfg *cfg) +{ + return container_of(cfg->c.cdev, struct gadget_info, cdev); +} + +struct gadget_language { + 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 list_head gadget_strings; + unsigned int nstrings; +}; + +struct gadget_config_name { + struct usb_gadget_strings stringtab_dev; + struct usb_string strings; + char *configuration; + + struct config_group group; + struct list_head list; +}; + +#define USB_MAX_STRING_WITH_NULL_LEN (USB_MAX_STRING_LEN+1) + +static int usb_string_copy(const char *s, char **s_copy) +{ + int ret; + char *str; + char *copy = *s_copy; + + ret = strlen(s); + if (ret > USB_MAX_STRING_LEN) + return -EOVERFLOW; + if (ret < 1) + return -EINVAL; + + if (copy) { + str = copy; + } else { + str = kzalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL); + if (!str) + return -ENOMEM; + } + memcpy(str, s, ret + 1); + if (str[ret - 1] == '\n') + str[ret - 1] = '\0'; + *s_copy = str; + return 0; +} + +#define GI_DEVICE_DESC_SIMPLE_R_u8(__name) \ +static ssize_t gadget_dev_desc_##__name##_show(struct config_item *item, \ + char *page) \ +{ \ + return sprintf(page, "0x%02x\n", \ + to_gadget_info(item)->cdev.desc.__name); \ +} + +#define GI_DEVICE_DESC_SIMPLE_R_u16(__name) \ +static ssize_t gadget_dev_desc_##__name##_show(struct config_item *item, \ + char *page) \ +{ \ + return sprintf(page, "0x%04x\n", \ + le16_to_cpup(&to_gadget_info(item)->cdev.desc.__name)); \ +} + + +#define GI_DEVICE_DESC_SIMPLE_W_u8(_name) \ +static ssize_t gadget_dev_desc_##_name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + u8 val; \ + int ret; \ + ret = kstrtou8(page, 0, &val); \ + if (ret) \ + return ret; \ + to_gadget_info(item)->cdev.desc._name = val; \ + return len; \ +} + +#define GI_DEVICE_DESC_SIMPLE_W_u16(_name) \ +static ssize_t gadget_dev_desc_##_name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + u16 val; \ + int ret; \ + ret = kstrtou16(page, 0, &val); \ + if (ret) \ + return ret; \ + to_gadget_info(item)->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 config_item *item, + 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; + + to_gadget_info(item)->cdev.desc.bcdDevice = cpu_to_le16(bcdDevice); + return len; +} + +static ssize_t gadget_dev_desc_bcdUSB_store(struct config_item *item, + 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; + + to_gadget_info(item)->cdev.desc.bcdUSB = cpu_to_le16(bcdUSB); + return len; +} + +static ssize_t gadget_dev_desc_UDC_show(struct config_item *item, char *page) +{ + struct gadget_info *gi = to_gadget_info(item); + char *udc_name; + int ret; + + mutex_lock(&gi->lock); + udc_name = gi->composite.gadget_driver.udc_name; + ret = sprintf(page, "%s\n", udc_name ?: ""); + mutex_unlock(&gi->lock); + + return ret; +} + +static int unregister_gadget(struct gadget_info *gi) +{ + int ret; + + if (!gi->composite.gadget_driver.udc_name) + return -ENODEV; + + ret = usb_gadget_unregister_driver(&gi->composite.gadget_driver); + if (ret) + return ret; + kfree(gi->composite.gadget_driver.udc_name); + gi->composite.gadget_driver.udc_name = NULL; + return 0; +} + +static ssize_t gadget_dev_desc_UDC_store(struct config_item *item, + const char *page, size_t len) +{ + struct gadget_info *gi = to_gadget_info(item); + char *name; + int ret; + + if (strlen(page) < len) + return -EOVERFLOW; + + 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; + kfree(name); + } else { + if (gi->composite.gadget_driver.udc_name) { + ret = -EBUSY; + goto err; + } + gi->composite.gadget_driver.udc_name = name; + ret = usb_gadget_register_driver(&gi->composite.gadget_driver); + if (ret) { + gi->composite.gadget_driver.udc_name = NULL; + goto err; + } + } + mutex_unlock(&gi->lock); + return len; +err: + kfree(name); + mutex_unlock(&gi->lock); + return ret; +} + +static ssize_t gadget_dev_desc_max_speed_show(struct config_item *item, + char *page) +{ + enum usb_device_speed speed = to_gadget_info(item)->composite.max_speed; + + return sprintf(page, "%s\n", usb_speed_string(speed)); +} + +static ssize_t gadget_dev_desc_max_speed_store(struct config_item *item, + const char *page, size_t len) +{ + struct gadget_info *gi = to_gadget_info(item); + + mutex_lock(&gi->lock); + + /* Prevent changing of max_speed after the driver is binded */ + if (gi->composite.gadget_driver.udc_name) + goto err; + + if (strncmp(page, "super-speed-plus", 16) == 0) + gi->composite.max_speed = USB_SPEED_SUPER_PLUS; + else if (strncmp(page, "super-speed", 11) == 0) + gi->composite.max_speed = USB_SPEED_SUPER; + else if (strncmp(page, "high-speed", 10) == 0) + gi->composite.max_speed = USB_SPEED_HIGH; + else if (strncmp(page, "full-speed", 10) == 0) + gi->composite.max_speed = USB_SPEED_FULL; + else if (strncmp(page, "low-speed", 9) == 0) + gi->composite.max_speed = USB_SPEED_LOW; + else + goto err; + + gi->composite.gadget_driver.max_speed = gi->composite.max_speed; + + mutex_unlock(&gi->lock); + return len; +err: + mutex_unlock(&gi->lock); + return -EINVAL; +} + +CONFIGFS_ATTR(gadget_dev_desc_, bDeviceClass); +CONFIGFS_ATTR(gadget_dev_desc_, bDeviceSubClass); +CONFIGFS_ATTR(gadget_dev_desc_, bDeviceProtocol); +CONFIGFS_ATTR(gadget_dev_desc_, bMaxPacketSize0); +CONFIGFS_ATTR(gadget_dev_desc_, idVendor); +CONFIGFS_ATTR(gadget_dev_desc_, idProduct); +CONFIGFS_ATTR(gadget_dev_desc_, bcdDevice); +CONFIGFS_ATTR(gadget_dev_desc_, bcdUSB); +CONFIGFS_ATTR(gadget_dev_desc_, UDC); +CONFIGFS_ATTR(gadget_dev_desc_, max_speed); + +static struct configfs_attribute *gadget_root_attrs[] = { + &gadget_dev_desc_attr_bDeviceClass, + &gadget_dev_desc_attr_bDeviceSubClass, + &gadget_dev_desc_attr_bDeviceProtocol, + &gadget_dev_desc_attr_bMaxPacketSize0, + &gadget_dev_desc_attr_idVendor, + &gadget_dev_desc_attr_idProduct, + &gadget_dev_desc_attr_bcdDevice, + &gadget_dev_desc_attr_bcdUSB, + &gadget_dev_desc_attr_UDC, + &gadget_dev_desc_attr_max_speed, + NULL, +}; + +static inline struct gadget_language *to_gadget_language(struct config_item *item) +{ + return container_of(to_config_group(item), struct gadget_language, + 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 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->composite.gadget_driver.driver.name); + kfree(gi); +} + +static const struct configfs_item_operations gadget_root_item_ops = { + .release = gadget_info_attr_release, +}; + +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 gadget_info *gi = cfg_to_gadget_info(cfg); + + struct usb_function_instance *fi = + to_usb_function_instance(usb_func_ci); + struct usb_function_instance *a_fi = NULL, *iter; + 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. + */ + + if (gi->composite.gadget_driver.udc_name) { + ret = -EINVAL; + goto out; + } + + list_for_each_entry(iter, &gi->available_func, cfs_list) { + if (iter != fi) + continue; + a_fi = iter; + break; + } + if (!a_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 void 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 gadget_info *gi = cfg_to_gadget_info(cfg); + + struct usb_function_instance *fi = + to_usb_function_instance(usb_func_ci); + 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->composite.gadget_driver.udc_name) + unregister_gadget(gi); + WARN_ON(gi->composite.gadget_driver.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; + } + } + mutex_unlock(&gi->lock); + WARN(1, "Unable to locate function to unbind\n"); +} + +static const struct configfs_item_operations gadget_config_item_ops = { + .release = gadget_config_attr_release, + .allow_link = config_usb_cfg_link, + .drop_link = config_usb_cfg_unlink, +}; + + +static ssize_t gadget_config_desc_MaxPower_show(struct config_item *item, + char *page) +{ + struct config_usb_cfg *cfg = to_config_usb_cfg(item); + + return sprintf(page, "%u\n", cfg->c.MaxPower); +} + +static ssize_t gadget_config_desc_MaxPower_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_usb_cfg *cfg = to_config_usb_cfg(item); + 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_item *item, + char *page) +{ + struct config_usb_cfg *cfg = to_config_usb_cfg(item); + + return sprintf(page, "0x%02x\n", cfg->c.bmAttributes); +} + +static ssize_t gadget_config_desc_bmAttributes_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_usb_cfg *cfg = to_config_usb_cfg(item); + 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; +} + +CONFIGFS_ATTR(gadget_config_desc_, MaxPower); +CONFIGFS_ATTR(gadget_config_desc_, bmAttributes); + +static struct configfs_attribute *gadget_config_attrs[] = { + &gadget_config_desc_attr_MaxPower, + &gadget_config_desc_attr_bmAttributes, + NULL, +}; + +static const struct config_item_type gadget_config_type = { + .ct_item_ops = &gadget_config_item_ops, + .ct_attrs = gadget_config_attrs, + .ct_owner = THIS_MODULE, +}; + +static const 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; + + if (strlen(name) >= MAX_NAME_LEN) + return ERR_PTR(-ENAMETOOLONG); + + scnprintf(buf, MAX_NAME_LEN, "%s", name); + + 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_CAST(fi); + + ret = config_item_set_name(&fi->group.cg_item, "%s", name); + if (ret) { + usb_put_function_instance(fi); + return ERR_PTR(ret); + } + if (fi->set_inst_name) { + ret = fi->set_inst_name(fi, instance_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 const struct configfs_group_operations functions_ops = { + .make_group = &function_make, + .drop_item = &function_drop, +}; + +static const struct config_item_type functions_type = { + .ct_group_ops = &functions_ops, + .ct_owner = THIS_MODULE, +}; + +GS_STRINGS_RW(gadget_config_name, configuration); + +static struct configfs_attribute *gadget_config_name_langid_attrs[] = { + &gadget_config_name_attr_configuration, + 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); + + if (strlen(name) >= MAX_NAME_LEN) + return ERR_PTR(-ENAMETOOLONG); + + scnprintf(buf, MAX_NAME_LEN, "%s", name); + + 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_obj(*cfg); + 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); + + 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); + configfs_add_default_group(&cfg->strings_group, &cfg->group); + + 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 const struct configfs_group_operations config_desc_ops = { + .make_group = &config_desc_make, + .drop_item = &config_desc_drop, +}; + +static const struct config_item_type config_desc_type = { + .ct_group_ops = &config_desc_ops, + .ct_owner = THIS_MODULE, +}; + +GS_STRINGS_RW(gadget_language, manufacturer); +GS_STRINGS_RW(gadget_language, product); +GS_STRINGS_RW(gadget_language, serialnumber); + +static struct configfs_attribute *gadget_language_langid_attrs[] = { + &gadget_language_attr_manufacturer, + &gadget_language_attr_product, + &gadget_language_attr_serialnumber, + NULL, +}; + +static void gadget_language_attr_release(struct config_item *item) +{ + struct gadget_language *gs = to_gadget_language(item); + + kfree(gs->manufacturer); + kfree(gs->product); + kfree(gs->serialnumber); + + list_del(&gs->list); + kfree(gs); +} + +static const struct configfs_item_operations gadget_language_langid_item_ops = { + .release = gadget_language_attr_release, +}; + +static ssize_t gadget_string_id_show(struct config_item *item, char *page) +{ + struct gadget_string *string = to_gadget_string(item); + int ret; + + ret = sprintf(page, "%u\n", string->usb_string.id); + return ret; +} +CONFIGFS_ATTR_RO(gadget_string_, id); + +static ssize_t gadget_string_s_show(struct config_item *item, char *page) +{ + struct gadget_string *string = to_gadget_string(item); + int ret; + + ret = sysfs_emit(page, "%s\n", string->string); + return ret; +} + +static ssize_t gadget_string_s_store(struct config_item *item, const char *page, + size_t len) +{ + struct gadget_string *string = to_gadget_string(item); + int size = min(sizeof(string->string), len + 1); + ssize_t cpy_len; + + if (len > USB_MAX_STRING_LEN) + return -EINVAL; + + cpy_len = strscpy(string->string, page, size); + if (cpy_len > 0 && string->string[cpy_len - 1] == '\n') + string->string[cpy_len - 1] = 0; + return len; +} +CONFIGFS_ATTR(gadget_string_, s); + +static struct configfs_attribute *gadget_string_attrs[] = { + &gadget_string_attr_id, + &gadget_string_attr_s, + NULL, +}; + +static void gadget_string_release(struct config_item *item) +{ + struct gadget_string *string = to_gadget_string(item); + + kfree(string); +} + +static const struct configfs_item_operations gadget_string_item_ops = { + .release = gadget_string_release, +}; + +static const struct config_item_type gadget_string_type = { + .ct_item_ops = &gadget_string_item_ops, + .ct_attrs = gadget_string_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *gadget_language_string_make(struct config_group *group, + const char *name) +{ + struct gadget_language *language; + struct gadget_string *string; + + language = to_gadget_language(&group->cg_item); + + string = kzalloc_obj(*string); + if (!string) + return ERR_PTR(-ENOMEM); + + string->usb_string.id = language->nstrings++; + string->usb_string.s = string->string; + list_add_tail(&string->list, &language->gadget_strings); + + config_item_init_type_name(&string->item, name, &gadget_string_type); + + return &string->item; +} + +static void gadget_language_string_drop(struct config_group *group, + struct config_item *item) +{ + struct gadget_language *language; + struct gadget_string *string; + unsigned int i = USB_GADGET_FIRST_AVAIL_IDX; + + language = to_gadget_language(&group->cg_item); + string = to_gadget_string(item); + + list_del(&string->list); + language->nstrings--; + + /* Reset the ids for the language's strings to guarantee a continuous set */ + list_for_each_entry(string, &language->gadget_strings, list) + string->usb_string.id = i++; +} + +static const struct configfs_group_operations gadget_language_langid_group_ops = { + .make_item = gadget_language_string_make, + .drop_item = gadget_language_string_drop, +}; + +static const struct config_item_type gadget_language_type = { + .ct_item_ops = &gadget_language_langid_item_ops, + .ct_group_ops = &gadget_language_langid_group_ops, + .ct_attrs = gadget_language_langid_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *gadget_language_make(struct config_group *group, + const char *name) +{ + struct gadget_info *gi; + struct gadget_language *gs; + struct gadget_language *new; + int langs = 0; + int ret; + + new = kzalloc_obj(*new); + if (!new) + return ERR_PTR(-ENOMEM); + + ret = check_user_usb_string(name, &new->stringtab_dev); + if (ret) + goto err; + config_group_init_type_name(&new->group, name, + &gadget_language_type); + + gi = container_of(group, struct gadget_info, strings_group); + ret = -EEXIST; + list_for_each_entry(gs, &gi->string_list, list) { + if (gs->stringtab_dev.language == new->stringtab_dev.language) + goto err; + langs++; + } + ret = -EOVERFLOW; + if (langs >= MAX_USB_STRING_LANGS) + goto err; + + list_add_tail(&new->list, &gi->string_list); + INIT_LIST_HEAD(&new->gadget_strings); + + /* We have the default manufacturer, product and serialnumber strings */ + new->nstrings = 3; + return &new->group; +err: + kfree(new); + return ERR_PTR(ret); +} + +static void gadget_language_drop(struct config_group *group, + struct config_item *item) +{ + config_item_put(item); +} + +static const struct configfs_group_operations gadget_language_group_ops = { + .make_group = &gadget_language_make, + .drop_item = &gadget_language_drop, +}; + +static const struct config_item_type gadget_language_strings_type = { + .ct_group_ops = &gadget_language_group_ops, + .ct_owner = THIS_MODULE, +}; + +static inline struct gadget_info *webusb_item_to_gadget_info( + struct config_item *item) +{ + return container_of(to_config_group(item), + struct gadget_info, webusb_group); +} + +static ssize_t webusb_use_show(struct config_item *item, char *page) +{ + return sysfs_emit(page, "%d\n", + webusb_item_to_gadget_info(item)->use_webusb); +} + +static ssize_t webusb_use_store(struct config_item *item, const char *page, + size_t len) +{ + struct gadget_info *gi = webusb_item_to_gadget_info(item); + int ret; + bool use; + + ret = kstrtobool(page, &use); + if (ret) + return ret; + + mutex_lock(&gi->lock); + gi->use_webusb = use; + mutex_unlock(&gi->lock); + + return len; +} + +static ssize_t webusb_bcdVersion_show(struct config_item *item, char *page) +{ + return sysfs_emit(page, "0x%04x\n", + webusb_item_to_gadget_info(item)->bcd_webusb_version); +} + +static ssize_t webusb_bcdVersion_store(struct config_item *item, + const char *page, size_t len) +{ + struct gadget_info *gi = webusb_item_to_gadget_info(item); + u16 bcdVersion; + int ret; + + ret = kstrtou16(page, 0, &bcdVersion); + if (ret) + return ret; + + ret = is_valid_bcd(bcdVersion); + if (ret) + return ret; + + mutex_lock(&gi->lock); + gi->bcd_webusb_version = bcdVersion; + mutex_unlock(&gi->lock); + + return len; +} + +static ssize_t webusb_bVendorCode_show(struct config_item *item, char *page) +{ + return sysfs_emit(page, "0x%02x\n", + webusb_item_to_gadget_info(item)->b_webusb_vendor_code); +} + +static ssize_t webusb_bVendorCode_store(struct config_item *item, + const char *page, size_t len) +{ + struct gadget_info *gi = webusb_item_to_gadget_info(item); + int ret; + u8 b_vendor_code; + + ret = kstrtou8(page, 0, &b_vendor_code); + if (ret) + return ret; + + mutex_lock(&gi->lock); + gi->b_webusb_vendor_code = b_vendor_code; + mutex_unlock(&gi->lock); + + return len; +} + +static ssize_t webusb_landingPage_show(struct config_item *item, char *page) +{ + return sysfs_emit(page, "%s\n", webusb_item_to_gadget_info(item)->landing_page); +} + +static ssize_t webusb_landingPage_store(struct config_item *item, const char *page, + size_t len) +{ + struct gadget_info *gi = webusb_item_to_gadget_info(item); + unsigned int bytes_to_strip = 0; + int l = len; + + if (!len) + return len; + if (page[l - 1] == '\n') { + --l; + ++bytes_to_strip; + } + + if (l > sizeof(gi->landing_page)) { + pr_err("webusb: landingPage URL too long\n"); + return -EINVAL; + } + + // validation + if (strncasecmp(page, "https://", 8) == 0) + bytes_to_strip = 8; + else if (strncasecmp(page, "http://", 7) == 0) + bytes_to_strip = 7; + else + bytes_to_strip = 0; + + if (l > U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + bytes_to_strip) { + pr_err("webusb: landingPage URL %d bytes too long for given URL scheme\n", + l - U8_MAX + WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH - bytes_to_strip); + return -EINVAL; + } + + mutex_lock(&gi->lock); + // ensure 0 bytes are set, in case the new landing page is shorter then the old one. + memcpy_and_pad(gi->landing_page, sizeof(gi->landing_page), page, l, 0); + mutex_unlock(&gi->lock); + + return len; +} + +CONFIGFS_ATTR(webusb_, use); +CONFIGFS_ATTR(webusb_, bVendorCode); +CONFIGFS_ATTR(webusb_, bcdVersion); +CONFIGFS_ATTR(webusb_, landingPage); + +static struct configfs_attribute *webusb_attrs[] = { + &webusb_attr_use, + &webusb_attr_bcdVersion, + &webusb_attr_bVendorCode, + &webusb_attr_landingPage, + NULL, +}; + +static const struct config_item_type webusb_type = { + .ct_attrs = webusb_attrs, + .ct_owner = THIS_MODULE, +}; + +static inline struct gadget_info *os_desc_item_to_gadget_info( + struct config_item *item) +{ + return container_of(to_config_group(item), + struct gadget_info, os_desc_group); +} + +static ssize_t os_desc_use_show(struct config_item *item, char *page) +{ + return sprintf(page, "%d\n", + os_desc_item_to_gadget_info(item)->use_os_desc); +} + +static ssize_t os_desc_use_store(struct config_item *item, const char *page, + size_t len) +{ + struct gadget_info *gi = os_desc_item_to_gadget_info(item); + int ret; + bool use; + + ret = kstrtobool(page, &use); + if (ret) + return ret; + + mutex_lock(&gi->lock); + gi->use_os_desc = use; + mutex_unlock(&gi->lock); + + return len; +} + +static ssize_t os_desc_b_vendor_code_show(struct config_item *item, char *page) +{ + return sprintf(page, "0x%02x\n", + os_desc_item_to_gadget_info(item)->b_vendor_code); +} + +static ssize_t os_desc_b_vendor_code_store(struct config_item *item, + const char *page, size_t len) +{ + struct gadget_info *gi = os_desc_item_to_gadget_info(item); + int ret; + u8 b_vendor_code; + + ret = kstrtou8(page, 0, &b_vendor_code); + if (ret) + return ret; + + mutex_lock(&gi->lock); + gi->b_vendor_code = b_vendor_code; + mutex_unlock(&gi->lock); + + return len; +} + +static ssize_t os_desc_qw_sign_show(struct config_item *item, char *page) +{ + struct gadget_info *gi = os_desc_item_to_gadget_info(item); + int res; + + res = utf16s_to_utf8s((wchar_t *) gi->qw_sign, OS_STRING_QW_SIGN_LEN / 2, + UTF16_LITTLE_ENDIAN, page, PAGE_SIZE - 1); + page[res++] = '\n'; + + return res; +} + +static ssize_t os_desc_qw_sign_store(struct config_item *item, const char *page, + size_t len) +{ + struct gadget_info *gi = os_desc_item_to_gadget_info(item); + int res, l; + + if (!len) + return len; + l = min_t(int, len, OS_STRING_QW_SIGN_LEN >> 1); + if (page[l - 1] == '\n') + --l; + + mutex_lock(&gi->lock); + res = utf8s_to_utf16s(page, l, + UTF16_LITTLE_ENDIAN, (wchar_t *) gi->qw_sign, + OS_STRING_QW_SIGN_LEN / 2); + if (res > 0) + res = len; + mutex_unlock(&gi->lock); + + return res; +} + +CONFIGFS_ATTR(os_desc_, use); +CONFIGFS_ATTR(os_desc_, b_vendor_code); +CONFIGFS_ATTR(os_desc_, qw_sign); + +static struct configfs_attribute *os_desc_attrs[] = { + &os_desc_attr_use, + &os_desc_attr_b_vendor_code, + &os_desc_attr_qw_sign, + NULL, +}; + +static int os_desc_link(struct config_item *os_desc_ci, + struct config_item *usb_cfg_ci) +{ + struct gadget_info *gi = os_desc_item_to_gadget_info(os_desc_ci); + struct usb_composite_dev *cdev = &gi->cdev; + struct config_usb_cfg *c_target = to_config_usb_cfg(usb_cfg_ci); + struct usb_configuration *c = NULL, *iter; + int ret; + + mutex_lock(&gi->lock); + list_for_each_entry(iter, &cdev->configs, list) { + if (iter != &c_target->c) + continue; + c = iter; + break; + } + if (!c) { + ret = -EINVAL; + goto out; + } + + if (cdev->os_desc_config) { + ret = -EBUSY; + goto out; + } + + cdev->os_desc_config = &c_target->c; + ret = 0; + +out: + mutex_unlock(&gi->lock); + return ret; +} + +static void os_desc_unlink(struct config_item *os_desc_ci, + struct config_item *usb_cfg_ci) +{ + struct gadget_info *gi = os_desc_item_to_gadget_info(os_desc_ci); + struct usb_composite_dev *cdev = &gi->cdev; + + mutex_lock(&gi->lock); + if (gi->composite.gadget_driver.udc_name) + unregister_gadget(gi); + cdev->os_desc_config = NULL; + WARN_ON(gi->composite.gadget_driver.udc_name); + mutex_unlock(&gi->lock); +} + +static const struct configfs_item_operations os_desc_ops = { + .allow_link = os_desc_link, + .drop_link = os_desc_unlink, +}; + +static const struct config_item_type os_desc_type = { + .ct_item_ops = &os_desc_ops, + .ct_attrs = os_desc_attrs, + .ct_owner = THIS_MODULE, +}; + +static inline struct usb_os_desc_ext_prop +*to_usb_os_desc_ext_prop(struct config_item *item) +{ + return container_of(item, struct usb_os_desc_ext_prop, item); +} + +static ssize_t ext_prop_type_show(struct config_item *item, char *page) +{ + return sprintf(page, "%d\n", to_usb_os_desc_ext_prop(item)->type); +} + +static ssize_t ext_prop_type_store(struct config_item *item, + const char *page, size_t len) +{ + struct usb_os_desc_ext_prop *ext_prop = to_usb_os_desc_ext_prop(item); + struct usb_os_desc *desc = to_usb_os_desc(ext_prop->item.ci_parent); + u8 type; + int ret; + + ret = kstrtou8(page, 0, &type); + if (ret) + return ret; + + if (type < USB_EXT_PROP_UNICODE || type > USB_EXT_PROP_UNICODE_MULTI) + return -EINVAL; + + if (desc->opts_mutex) + mutex_lock(desc->opts_mutex); + + if ((ext_prop->type == USB_EXT_PROP_BINARY || + ext_prop->type == USB_EXT_PROP_LE32 || + ext_prop->type == USB_EXT_PROP_BE32) && + (type == USB_EXT_PROP_UNICODE || + type == USB_EXT_PROP_UNICODE_ENV || + type == USB_EXT_PROP_UNICODE_LINK)) + ext_prop->data_len <<= 1; + else if ((ext_prop->type == USB_EXT_PROP_UNICODE || + ext_prop->type == USB_EXT_PROP_UNICODE_ENV || + ext_prop->type == USB_EXT_PROP_UNICODE_LINK) && + (type == USB_EXT_PROP_BINARY || + type == USB_EXT_PROP_LE32 || + type == USB_EXT_PROP_BE32)) + ext_prop->data_len >>= 1; + ext_prop->type = type; + + if (desc->opts_mutex) + mutex_unlock(desc->opts_mutex); + return len; +} + +static ssize_t ext_prop_data_show(struct config_item *item, char *page) +{ + struct usb_os_desc_ext_prop *ext_prop = to_usb_os_desc_ext_prop(item); + int len = ext_prop->data_len; + + if (ext_prop->type == USB_EXT_PROP_UNICODE || + ext_prop->type == USB_EXT_PROP_UNICODE_ENV || + ext_prop->type == USB_EXT_PROP_UNICODE_LINK) + len >>= 1; + memcpy(page, ext_prop->data, len); + + return len; +} + +static ssize_t ext_prop_data_store(struct config_item *item, + const char *page, size_t len) +{ + struct usb_os_desc_ext_prop *ext_prop = to_usb_os_desc_ext_prop(item); + struct usb_os_desc *desc = to_usb_os_desc(ext_prop->item.ci_parent); + char *new_data; + size_t ret_len = len; + + if (page[len - 1] == '\n' || page[len - 1] == '\0') + --len; + new_data = kmemdup(page, len, GFP_KERNEL); + if (!new_data) + return -ENOMEM; + + if (desc->opts_mutex) + mutex_lock(desc->opts_mutex); + kfree(ext_prop->data); + ext_prop->data = new_data; + desc->ext_prop_len -= ext_prop->data_len; + ext_prop->data_len = len; + desc->ext_prop_len += ext_prop->data_len; + if (ext_prop->type == USB_EXT_PROP_UNICODE || + ext_prop->type == USB_EXT_PROP_UNICODE_ENV || + ext_prop->type == USB_EXT_PROP_UNICODE_LINK) { + desc->ext_prop_len -= ext_prop->data_len; + ext_prop->data_len <<= 1; + ext_prop->data_len += 2; + desc->ext_prop_len += ext_prop->data_len; + } + if (desc->opts_mutex) + mutex_unlock(desc->opts_mutex); + return ret_len; +} + +CONFIGFS_ATTR(ext_prop_, type); +CONFIGFS_ATTR(ext_prop_, data); + +static struct configfs_attribute *ext_prop_attrs[] = { + &ext_prop_attr_type, + &ext_prop_attr_data, + NULL, +}; + +static void usb_os_desc_ext_prop_release(struct config_item *item) +{ + struct usb_os_desc_ext_prop *ext_prop = to_usb_os_desc_ext_prop(item); + + kfree(ext_prop); /* frees a whole chunk */ +} + +static const struct configfs_item_operations ext_prop_ops = { + .release = usb_os_desc_ext_prop_release, +}; + +static struct config_item *ext_prop_make( + struct config_group *group, + const char *name) +{ + struct usb_os_desc_ext_prop *ext_prop; + struct config_item_type *ext_prop_type; + struct usb_os_desc *desc; + char *vlabuf; + + vla_group(data_chunk); + vla_item(data_chunk, struct usb_os_desc_ext_prop, ext_prop, 1); + vla_item(data_chunk, struct config_item_type, ext_prop_type, 1); + + vlabuf = kzalloc(vla_group_size(data_chunk), GFP_KERNEL); + if (!vlabuf) + return ERR_PTR(-ENOMEM); + + ext_prop = vla_ptr(vlabuf, data_chunk, ext_prop); + ext_prop_type = vla_ptr(vlabuf, data_chunk, ext_prop_type); + + desc = container_of(group, struct usb_os_desc, group); + ext_prop_type->ct_item_ops = &ext_prop_ops; + ext_prop_type->ct_attrs = ext_prop_attrs; + ext_prop_type->ct_owner = desc->owner; + + config_item_init_type_name(&ext_prop->item, name, ext_prop_type); + + ext_prop->name = kstrdup(name, GFP_KERNEL); + if (!ext_prop->name) { + kfree(vlabuf); + return ERR_PTR(-ENOMEM); + } + desc->ext_prop_len += 14; + ext_prop->name_len = 2 * strlen(ext_prop->name) + 2; + if (desc->opts_mutex) + mutex_lock(desc->opts_mutex); + desc->ext_prop_len += ext_prop->name_len; + list_add_tail(&ext_prop->entry, &desc->ext_prop); + ++desc->ext_prop_count; + if (desc->opts_mutex) + mutex_unlock(desc->opts_mutex); + + return &ext_prop->item; +} + +static void ext_prop_drop(struct config_group *group, struct config_item *item) +{ + struct usb_os_desc_ext_prop *ext_prop = to_usb_os_desc_ext_prop(item); + struct usb_os_desc *desc = to_usb_os_desc(&group->cg_item); + + if (desc->opts_mutex) + mutex_lock(desc->opts_mutex); + list_del(&ext_prop->entry); + --desc->ext_prop_count; + kfree(ext_prop->name); + desc->ext_prop_len -= (ext_prop->name_len + ext_prop->data_len + 14); + if (desc->opts_mutex) + mutex_unlock(desc->opts_mutex); + config_item_put(item); +} + +static const struct configfs_group_operations interf_grp_ops = { + .make_item = &ext_prop_make, + .drop_item = &ext_prop_drop, +}; + +static ssize_t interf_grp_compatible_id_show(struct config_item *item, + char *page) +{ + memcpy(page, to_usb_os_desc(item)->ext_compat_id, 8); + return 8; +} + +static ssize_t interf_grp_compatible_id_store(struct config_item *item, + const char *page, size_t len) +{ + struct usb_os_desc *desc = to_usb_os_desc(item); + int l; + + l = min_t(int, 8, len); + if (page[l - 1] == '\n') + --l; + if (desc->opts_mutex) + mutex_lock(desc->opts_mutex); + memcpy(desc->ext_compat_id, page, l); + + if (desc->opts_mutex) + mutex_unlock(desc->opts_mutex); + + return len; +} + +static ssize_t interf_grp_sub_compatible_id_show(struct config_item *item, + char *page) +{ + memcpy(page, to_usb_os_desc(item)->ext_compat_id + 8, 8); + return 8; +} + +static ssize_t interf_grp_sub_compatible_id_store(struct config_item *item, + const char *page, size_t len) +{ + struct usb_os_desc *desc = to_usb_os_desc(item); + int l; + + l = min_t(int, 8, len); + if (page[l - 1] == '\n') + --l; + if (desc->opts_mutex) + mutex_lock(desc->opts_mutex); + memcpy(desc->ext_compat_id + 8, page, l); + + if (desc->opts_mutex) + mutex_unlock(desc->opts_mutex); + + return len; +} + +CONFIGFS_ATTR(interf_grp_, compatible_id); +CONFIGFS_ATTR(interf_grp_, sub_compatible_id); + +static struct configfs_attribute *interf_grp_attrs[] = { + &interf_grp_attr_compatible_id, + &interf_grp_attr_sub_compatible_id, + NULL +}; + +struct config_group *usb_os_desc_prepare_interf_dir( + struct config_group *parent, + int n_interf, + struct usb_os_desc **desc, + char **names, + struct module *owner) +{ + struct config_group *os_desc_group; + struct config_item_type *os_desc_type, *interface_type; + + vla_group(data_chunk); + vla_item(data_chunk, struct config_group, os_desc_group, 1); + vla_item(data_chunk, struct config_item_type, os_desc_type, 1); + vla_item(data_chunk, struct config_item_type, interface_type, 1); + + char *vlabuf = kzalloc(vla_group_size(data_chunk), GFP_KERNEL); + if (!vlabuf) + return ERR_PTR(-ENOMEM); + + os_desc_group = vla_ptr(vlabuf, data_chunk, os_desc_group); + os_desc_type = vla_ptr(vlabuf, data_chunk, os_desc_type); + interface_type = vla_ptr(vlabuf, data_chunk, interface_type); + + os_desc_type->ct_owner = owner; + config_group_init_type_name(os_desc_group, "os_desc", os_desc_type); + configfs_add_default_group(os_desc_group, parent); + + interface_type->ct_group_ops = &interf_grp_ops; + interface_type->ct_attrs = interf_grp_attrs; + interface_type->ct_owner = owner; + + while (n_interf--) { + struct usb_os_desc *d; + + d = desc[n_interf]; + d->owner = owner; + config_group_init_type_name(&d->group, "", interface_type); + config_item_set_name(&d->group.cg_item, "interface.%s", + names[n_interf]); + configfs_add_default_group(&d->group, os_desc_group); + } + + return os_desc_group; +} +EXPORT_SYMBOL(usb_os_desc_prepare_interf_dir); + +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); + +int composite_os_desc_req_prepare(struct usb_composite_dev *cdev, + struct usb_ep *ep0); + +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_reverse(f, tmp, &c->functions, list) { + + list_move(&f->list, &cfg->func_list); + if (f->unbind) { + dev_dbg(&gi->cdev.gadget->dev, + "unbind function '%s'/%p\n", + f->name, f); + f->unbind(c, f); + } + } + c->next_interface_id = 0; + memset(c->interface, 0, sizeof(c->interface)); + c->superspeed_plus = 0; + c->superspeed = 0; + c->highspeed = 0; + c->fullspeed = 0; + } +} + +static struct usb_string * +configfs_attach_gadget_strings(struct gadget_info *gi) +{ + struct usb_gadget_strings **gadget_strings; + struct gadget_language *language; + struct gadget_string *string; + unsigned int nlangs = 0; + struct list_head *iter; + struct usb_string *us; + unsigned int i = 0; + int nstrings = -1; + unsigned int j; + + list_for_each(iter, &gi->string_list) + nlangs++; + + /* Bail out early if no languages are configured */ + if (!nlangs) + return NULL; + + gadget_strings = kzalloc_objs(struct usb_gadget_strings *, nlangs + 1)/* including NULL terminator */; + if (!gadget_strings) + return ERR_PTR(-ENOMEM); + + list_for_each_entry(language, &gi->string_list, list) { + struct usb_string *stringtab; + + if (nstrings == -1) { + nstrings = language->nstrings; + } else if (nstrings != language->nstrings) { + pr_err("languages must contain the same number of strings\n"); + us = ERR_PTR(-EINVAL); + goto cleanup; + } + + stringtab = kzalloc_objs(struct usb_string, + language->nstrings + 1); + if (!stringtab) { + us = ERR_PTR(-ENOMEM); + goto cleanup; + } + + stringtab[USB_GADGET_MANUFACTURER_IDX].id = USB_GADGET_MANUFACTURER_IDX; + stringtab[USB_GADGET_MANUFACTURER_IDX].s = language->manufacturer; + stringtab[USB_GADGET_PRODUCT_IDX].id = USB_GADGET_PRODUCT_IDX; + stringtab[USB_GADGET_PRODUCT_IDX].s = language->product; + stringtab[USB_GADGET_SERIAL_IDX].id = USB_GADGET_SERIAL_IDX; + stringtab[USB_GADGET_SERIAL_IDX].s = language->serialnumber; + + j = USB_GADGET_FIRST_AVAIL_IDX; + list_for_each_entry(string, &language->gadget_strings, list) { + memcpy(&stringtab[j], &string->usb_string, sizeof(struct usb_string)); + j++; + } + + language->stringtab_dev.strings = stringtab; + gadget_strings[i] = &language->stringtab_dev; + i++; + } + + us = usb_gstrings_attach(&gi->cdev, gadget_strings, nstrings); + +cleanup: + list_for_each_entry(language, &gi->string_list, list) { + kfree(language->stringtab_dev.strings); + language->stringtab_dev.strings = NULL; + } + + kfree(gadget_strings); + + return us; +} + +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 */ + gi->unbind = 0; + 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 at least 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 at least one function.\n", + c->label, c->bConfigurationValue, + gi->composite.name); + goto err_comp_cleanup; + } + } + + /* init all strings */ + if (!list_empty(&gi->string_list)) { + s = configfs_attach_gadget_strings(gi); + 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; + + gi->cdev.usb_strings = s; + } + + if (gi->use_webusb) { + cdev->use_webusb = true; + cdev->bcd_webusb_version = gi->bcd_webusb_version; + cdev->b_webusb_vendor_code = gi->b_webusb_vendor_code; + memcpy(cdev->landing_page, gi->landing_page, WEBUSB_URL_RAW_MAX_LENGTH); + } + + if (gi->use_os_desc) { + cdev->use_os_string = true; + cdev->b_vendor_code = gi->b_vendor_code; + memcpy(cdev->qw_sign, gi->qw_sign, OS_STRING_QW_SIGN_LEN); + } else { + cdev->use_os_string = false; + } + + if (gadget_is_otg(gadget) && !otg_desc[0]) { + struct usb_descriptor_header *usb_desc; + + usb_desc = usb_otg_descriptor_alloc(gadget); + if (!usb_desc) { + ret = -ENOMEM; + goto err_comp_cleanup; + } + usb_otg_descriptor_init(gadget, usb_desc); + otg_desc[0] = usb_desc; + otg_desc[1] = NULL; + } + + /* 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; + + if (gadget_is_otg(gadget)) + c->descriptors = otg_desc; + + /* Properly configure the bmAttributes wakeup bit */ + check_remote_wakeup_config(gadget, c); + + 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) { + list_add(&f->list, &cfg->func_list); + goto err_purge_funcs; + } + } + ret = usb_gadget_check_config(cdev->gadget); + if (ret) + goto err_purge_funcs; + + usb_ep_autoconfig_reset(cdev->gadget); + } + if (cdev->use_os_string) { + ret = composite_os_desc_req_prepare(cdev, gadget->ep0); + if (ret) + goto err_purge_funcs; + } + + 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; + unsigned long flags; + + /* the gi->lock is hold by the caller */ + + cdev = get_gadget_data(gadget); + gi = container_of(cdev, struct gadget_info, cdev); + spin_lock_irqsave(&gi->spinlock, flags); + gi->unbind = 1; + spin_unlock_irqrestore(&gi->spinlock, flags); + + kfree(otg_desc[0]); + otg_desc[0] = NULL; + purge_configs_funcs(gi); + composite_dev_cleanup(cdev); + usb_ep_autoconfig_reset(cdev->gadget); + spin_lock_irqsave(&gi->spinlock, flags); + cdev->gadget = NULL; + cdev->deactivations = 0; + gadget->deactivated = false; + set_gadget_data(gadget, NULL); + spin_unlock_irqrestore(&gi->spinlock, flags); +} + +static int configfs_composite_setup(struct usb_gadget *gadget, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev; + struct gadget_info *gi; + unsigned long flags; + int ret; + + cdev = get_gadget_data(gadget); + if (!cdev) + return 0; + + gi = container_of(cdev, struct gadget_info, cdev); + spin_lock_irqsave(&gi->spinlock, flags); + cdev = get_gadget_data(gadget); + if (!cdev || gi->unbind) { + spin_unlock_irqrestore(&gi->spinlock, flags); + return 0; + } + + ret = composite_setup(gadget, ctrl); + spin_unlock_irqrestore(&gi->spinlock, flags); + return ret; +} + +static void configfs_composite_disconnect(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev; + struct gadget_info *gi; + unsigned long flags; + + cdev = get_gadget_data(gadget); + if (!cdev) + return; + + gi = container_of(cdev, struct gadget_info, cdev); + spin_lock_irqsave(&gi->spinlock, flags); + cdev = get_gadget_data(gadget); + if (!cdev || gi->unbind) { + spin_unlock_irqrestore(&gi->spinlock, flags); + return; + } + + composite_disconnect(gadget); + spin_unlock_irqrestore(&gi->spinlock, flags); +} + +static void configfs_composite_reset(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev; + struct gadget_info *gi; + unsigned long flags; + + cdev = get_gadget_data(gadget); + if (!cdev) + return; + + gi = container_of(cdev, struct gadget_info, cdev); + spin_lock_irqsave(&gi->spinlock, flags); + cdev = get_gadget_data(gadget); + if (!cdev || gi->unbind) { + spin_unlock_irqrestore(&gi->spinlock, flags); + return; + } + + composite_reset(gadget); + spin_unlock_irqrestore(&gi->spinlock, flags); +} + +static void configfs_composite_suspend(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev; + struct gadget_info *gi; + unsigned long flags; + + cdev = get_gadget_data(gadget); + if (!cdev) + return; + + gi = container_of(cdev, struct gadget_info, cdev); + spin_lock_irqsave(&gi->spinlock, flags); + cdev = get_gadget_data(gadget); + if (!cdev || gi->unbind) { + spin_unlock_irqrestore(&gi->spinlock, flags); + return; + } + + composite_suspend(gadget); + spin_unlock_irqrestore(&gi->spinlock, flags); +} + +static void configfs_composite_resume(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev; + struct gadget_info *gi; + unsigned long flags; + + cdev = get_gadget_data(gadget); + if (!cdev) + return; + + gi = container_of(cdev, struct gadget_info, cdev); + spin_lock_irqsave(&gi->spinlock, flags); + cdev = get_gadget_data(gadget); + if (!cdev || gi->unbind) { + spin_unlock_irqrestore(&gi->spinlock, flags); + return; + } + + composite_resume(gadget); + spin_unlock_irqrestore(&gi->spinlock, flags); +} + +static const struct usb_gadget_driver configfs_driver_template = { + .bind = configfs_composite_bind, + .unbind = configfs_composite_unbind, + + .setup = configfs_composite_setup, + .reset = configfs_composite_reset, + .disconnect = configfs_composite_disconnect, + + .suspend = configfs_composite_suspend, + .resume = configfs_composite_resume, + + .max_speed = USB_SPEED_SUPER_PLUS, + .driver = { + .owner = THIS_MODULE, + }, + .match_existing_only = 1, +}; + +static struct config_group *gadgets_make( + struct config_group *group, + const char *name) +{ + struct gadget_info *gi; + + gi = kzalloc_obj(*gi); + if (!gi) + return ERR_PTR(-ENOMEM); + + config_group_init_type_name(&gi->group, name, &gadget_root_type); + + config_group_init_type_name(&gi->functions_group, "functions", + &functions_type); + configfs_add_default_group(&gi->functions_group, &gi->group); + + config_group_init_type_name(&gi->configs_group, "configs", + &config_desc_type); + configfs_add_default_group(&gi->configs_group, &gi->group); + + config_group_init_type_name(&gi->strings_group, "strings", + &gadget_language_strings_type); + configfs_add_default_group(&gi->strings_group, &gi->group); + + config_group_init_type_name(&gi->os_desc_group, "os_desc", + &os_desc_type); + configfs_add_default_group(&gi->os_desc_group, &gi->group); + + config_group_init_type_name(&gi->webusb_group, "webusb", + &webusb_type); + configfs_add_default_group(&gi->webusb_group, &gi->group); + + 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_PLUS; + + spin_lock_init(&gi->spinlock); + 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.driver.name = kasprintf(GFP_KERNEL, + "configfs-gadget.%s", name); + if (!gi->composite.gadget_driver.driver.name) + goto err; + + gi->composite.gadget_driver.function = kstrdup(name, GFP_KERNEL); + gi->composite.name = gi->composite.gadget_driver.function; + + if (!gi->composite.gadget_driver.function) + goto out_free_driver_name; + + return &gi->group; + +out_free_driver_name: + kfree(gi->composite.gadget_driver.driver.name); +err: + kfree(gi); + return ERR_PTR(-ENOMEM); +} + +static void gadgets_drop(struct config_group *group, struct config_item *item) +{ + config_item_put(item); +} + +static const struct configfs_group_operations gadgets_ops = { + .make_group = &gadgets_make, + .drop_item = &gadgets_drop, +}; + +static const 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), +}; + +void unregister_gadget_item(struct config_item *item) +{ + struct gadget_info *gi = to_gadget_info(item); + + mutex_lock(&gi->lock); + unregister_gadget(gi); + mutex_unlock(&gi->lock); +} +EXPORT_SYMBOL_GPL(unregister_gadget_item); + +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/configfs.h b/drivers/usb/gadget/configfs.h new file mode 100644 index 000000000..3b6f5298b --- /dev/null +++ b/drivers/usb/gadget/configfs.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef USB__GADGET__CONFIGFS__H +#define USB__GADGET__CONFIGFS__H + +#include + +void unregister_gadget_item(struct config_item *item); + +struct config_group *usb_os_desc_prepare_interf_dir( + struct config_group *parent, + int n_interf, + struct usb_os_desc **desc, + char **names, + struct module *owner); + +static inline struct usb_os_desc *to_usb_os_desc(struct config_item *item) +{ + return container_of(to_config_group(item), struct usb_os_desc, group); +} + +#endif /* USB__GADGET__CONFIGFS__H */ diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c new file mode 100644 index 000000000..30016b805 --- /dev/null +++ b/drivers/usb/gadget/epautoconf.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * epautoconf.c -- endpoint autoconfiguration for usb gadget drivers + * + * Copyright (C) 2004 David Brownell + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include + +/** + * 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 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, claims it by + * assigning ep->claimed to true. + * + * 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; + + if (gadget->ops->match_ep) { + ep = gadget->ops->match_ep(gadget, desc, ep_comp); + if (ep) + goto found_ep; + } + + /* Second, look at endpoints until an unclaimed one looks usable */ + list_for_each_entry (ep, &gadget->ep_list, ep_list) { + if (usb_gadget_ep_match_desc(gadget, ep, desc, ep_comp)) + goto found_ep; + } + + /* Fail */ + return NULL; +found_ep: + + /* + * 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_limit); + + /* 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 NULL; + desc->bEndpointAddress = USB_DIR_IN | gadget->in_epnum; + } else { + if (++gadget->out_epnum > 15) + return NULL; + desc->bEndpointAddress |= gadget->out_epnum; + } + + ep->address = desc->bEndpointAddress; + ep->desc = NULL; + ep->comp_desc = NULL; + ep->claimed = true; + 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 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. Because of + * that the users must consider adjusting the autoconfigured descriptor. + * To prevent the endpoint from being returned by a later autoconfig call, + * claims it by assigning ep->claimed to true. + * + * On failure, this returns a null endpoint descriptor. + */ +struct usb_ep *usb_ep_autoconfig( + struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc +) +{ + struct usb_ep *ep; + u8 type; + + ep = usb_ep_autoconfig_ss(gadget, desc, NULL); + if (!ep) + return NULL; + + type = usb_endpoint_type(desc); + + /* report (variable) full speed bulk maxpacket */ + if (type == USB_ENDPOINT_XFER_BULK) { + int size = ep->maxpacket_limit; + + /* min() doesn't work on bitfields with gcc-3.5 */ + if (size > 64) + size = 64; + desc->wMaxPacketSize = cpu_to_le16(size); + } + + return ep; +} +EXPORT_SYMBOL_GPL(usb_ep_autoconfig); + +/** + * usb_ep_autoconfig_release - releases endpoint and set it to initial state + * @ep: endpoint which should be released + * + * This function can be used during function bind for endpoints obtained + * from usb_ep_autoconfig(). It unclaims endpoint claimed by + * usb_ep_autoconfig() to make it available for other functions. Endpoint + * which was released is no longer valid and shouldn't be used in + * context of function which released it. + */ +void usb_ep_autoconfig_release(struct usb_ep *ep) +{ + ep->claimed = false; + ep->driver_data = NULL; +} +EXPORT_SYMBOL_GPL(usb_ep_autoconfig_release); + +/** + * 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->claimed 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->claimed = false; + 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/function/Makefile b/drivers/usb/gadget/function/Makefile new file mode 100644 index 000000000..7ce163727 --- /dev/null +++ b/drivers/usb/gadget/function/Makefile @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# USB peripheral controller drivers +# + +ccflags-y := -I$(srctree)/drivers/usb/gadget/ +ccflags-y += -I$(srctree)/drivers/usb/gadget/udc/ + +# 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 +obj-$(CONFIG_USB_U_ETHER) += u_ether.o +usb_f_ncm-y := f_ncm.o +obj-$(CONFIG_USB_F_NCM) += usb_f_ncm.o +usb_f_ecm-y := f_ecm.o +obj-$(CONFIG_USB_F_ECM) += usb_f_ecm.o +usb_f_phonet-y := f_phonet.o +obj-$(CONFIG_USB_F_PHONET) += usb_f_phonet.o +usb_f_eem-y := f_eem.o +obj-$(CONFIG_USB_F_EEM) += usb_f_eem.o +usb_f_ecm_subset-y := f_subset.o +obj-$(CONFIG_USB_F_SUBSET) += usb_f_ecm_subset.o +usb_f_rndis-y := f_rndis.o rndis.o +obj-$(CONFIG_USB_F_RNDIS) += usb_f_rndis.o +usb_f_mass_storage-y := f_mass_storage.o storage_common.o +obj-$(CONFIG_USB_F_MASS_STORAGE)+= usb_f_mass_storage.o +usb_f_fs-y := f_fs.o +obj-$(CONFIG_USB_F_FS) += usb_f_fs.o +obj-$(CONFIG_USB_U_AUDIO) += u_audio.o +usb_f_uac1-y := f_uac1.o +obj-$(CONFIG_USB_F_UAC1) += usb_f_uac1.o +usb_f_uac1_legacy-y := f_uac1_legacy.o u_uac1_legacy.o +obj-$(CONFIG_USB_F_UAC1_LEGACY) += usb_f_uac1_legacy.o +usb_f_uac2-y := f_uac2.o +obj-$(CONFIG_USB_F_UAC2) += usb_f_uac2.o +usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_configfs.o +ifneq ($(CONFIG_TRACING),) + CFLAGS_uvc_trace.o := -I$(src) + usb_f_uvc-y += uvc_trace.o +endif +obj-$(CONFIG_USB_F_UVC) += usb_f_uvc.o +usb_f_midi-y := f_midi.o +obj-$(CONFIG_USB_F_MIDI) += usb_f_midi.o +usb_f_midi2-y := f_midi2.o +obj-$(CONFIG_USB_F_MIDI2) += usb_f_midi2.o +usb_f_hid-y := f_hid.o +obj-$(CONFIG_USB_F_HID) += usb_f_hid.o +usb_f_printer-y := f_printer.o +obj-$(CONFIG_USB_F_PRINTER) += usb_f_printer.o +usb_f_tcm-y := f_tcm.o +obj-$(CONFIG_USB_F_TCM) += usb_f_tcm.o diff --git a/drivers/usb/gadget/function/f_acm.c b/drivers/usb/gadget/function/f_acm.c new file mode 100644 index 000000000..c628d02bf --- /dev/null +++ b/drivers/usb/gadget/function/f_acm.c @@ -0,0 +1,902 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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) + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "u_serial.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 bInterfaceProtocol; + + 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; + /* SerialState notification -- CDC 1.1 section 6.3.5 (OUTPUT) */ + u16 serial_state; +}; + +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 = DYNAMIC */ + /* .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 = DYNAMIC */ + /* .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) { + dev_dbg(&cdev->gadget->dev, "acm ttyGS%d completion, err %d\n", + acm->port_num, req->status); + return; + } + + /* normal completion */ + if (req->actual != sizeof(acm->port_line_coding)) { + dev_dbg(&cdev->gadget->dev, "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_send_break(struct gserial *port, int duration); + +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 USB_CDC_CTRL_DTR bit; and when it clears + * that bit, we should return to that no-flow state. + */ + acm->port_handshake_bits = w_value; + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SEND_BREAK: + if (w_index != acm->ctrl_id) + goto invalid; + + acm_send_break(&acm->port, w_value); + break; + + default: +invalid: + dev_vdbg(&cdev->gadget->dev, + "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) { + dev_dbg(&cdev->gadget->dev, + "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->enabled) { + dev_vdbg(&cdev->gadget->dev, + "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); + + } else if (intf == acm->data_id) { + if (acm->notify->enabled) { + dev_dbg(&cdev->gadget->dev, + "reset acm ttyGS%d\n", acm->port_num); + gserial_disconnect(&acm->port); + } + if (!acm->port.in->desc || !acm->port.out->desc) { + dev_dbg(&cdev->gadget->dev, + "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; + + dev_dbg(&cdev->gadget->dev, "acm ttyGS%d deactivated\n", acm->port_num); + gserial_disconnect(&acm->port); + usb_ep_disable(acm->notify); +} + +/*-------------------------------------------------------------------------*/ + +/** + * 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; + __le16 serial_state; + + spin_lock(&acm->lock); + if (acm->notify_req) { + dev_dbg(&cdev->gadget->dev, "acm ttyGS%d serial state %04x\n", + acm->port_num, acm->serial_state); + serial_state = cpu_to_le16(acm->serial_state); + status = acm_cdc_notify(acm, USB_CDC_NOTIFY_SERIAL_STATE, + 0, &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 |= USB_CDC_SERIAL_STATE_DSR | USB_CDC_SERIAL_STATE_DCD; + acm_notify_serial_state(acm); +} + +static void acm_disconnect(struct gserial *port) +{ + struct f_acm *acm = port_to_acm(port); + + acm->serial_state &= ~(USB_CDC_SERIAL_STATE_DSR | USB_CDC_SERIAL_STATE_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 &= ~USB_CDC_SERIAL_STATE_BREAK; + if (duration) + state |= USB_CDC_SERIAL_STATE_BREAK; + + 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; + struct usb_request *request __free(free_usb_request) = NULL; + + /* 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) + return status; + 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) + return status; + acm->data_id = status; + + acm_data_interface_desc.bInterfaceNumber = status; + acm_union_desc.bSlaveInterface0 = status; + acm_call_mgmt_descriptor.bDataInterface = status; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc); + if (!ep) + return -ENODEV; + acm->port.in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc); + if (!ep) + return -ENODEV; + acm->port.out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc); + if (!ep) + return -ENODEV; + acm->notify = ep; + + acm_iad_descriptor.bFunctionProtocol = acm->bInterfaceProtocol; + acm_control_interface_desc.bInterfaceProtocol = acm->bInterfaceProtocol; + + /* allocate notification */ + request = gs_alloc_req(ep, + sizeof(struct usb_cdc_notification) + 2, + GFP_KERNEL); + if (!request) + return -ENODEV; + + request->complete = acm_cdc_notify_complete; + request->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, acm_ss_function); + if (status) + return status; + + acm->notify_req = no_free_ptr(request); + + dev_dbg(&cdev->gadget->dev, + "acm ttyGS%d: IN/%s OUT/%s NOTIFY/%s\n", + acm->port_num, + acm->port.in->name, acm->port.out->name, + acm->notify->name); + return 0; +} + +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); + struct f_serial_opts *opts; + + opts = container_of(f->fi, struct f_serial_opts, func_inst); + + kfree(acm); + mutex_lock(&opts->lock); + opts->instances--; + mutex_unlock(&opts->lock); +} + +static void acm_resume(struct usb_function *f) +{ + struct f_acm *acm = func_to_acm(f); + + gserial_resume(&acm->port); +} + +static void acm_suspend(struct usb_function *f) +{ + struct f_acm *acm = func_to_acm(f); + + gserial_suspend(&acm->port); +} + +static struct usb_function *acm_alloc_func(struct usb_function_instance *fi) +{ + struct f_serial_opts *opts; + struct f_acm *acm; + + acm = kzalloc_obj(*acm); + 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); + mutex_lock(&opts->lock); + acm->port_num = opts->port_num; + acm->bInterfaceProtocol = opts->protocol; + opts->instances++; + mutex_unlock(&opts->lock); + acm->port.func.unbind = acm_unbind; + acm->port.func.free_func = acm_free_func; + acm->port.func.resume = acm_resume; + acm->port.func.suspend = acm_suspend; + + 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); +} + +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 const struct configfs_item_operations acm_item_ops = { + .release = acm_attr_release, +}; + +#ifdef CONFIG_U_SERIAL_CONSOLE + +static ssize_t f_acm_console_store(struct config_item *item, + const char *page, size_t count) +{ + return gserial_set_console(to_f_serial_opts(item)->port_num, + page, count); +} + +static ssize_t f_acm_console_show(struct config_item *item, char *page) +{ + return gserial_get_console(to_f_serial_opts(item)->port_num, page); +} + +CONFIGFS_ATTR(f_acm_, console); + +#endif /* CONFIG_U_SERIAL_CONSOLE */ + +static ssize_t f_acm_port_num_show(struct config_item *item, char *page) +{ + return sprintf(page, "%u\n", to_f_serial_opts(item)->port_num); +} + +CONFIGFS_ATTR_RO(f_acm_, port_num); + +static ssize_t f_acm_protocol_show(struct config_item *item, char *page) +{ + return sprintf(page, "%u\n", to_f_serial_opts(item)->protocol); +} + +static ssize_t f_acm_protocol_store(struct config_item *item, + const char *page, size_t count) +{ + struct f_serial_opts *opts = to_f_serial_opts(item); + int ret; + + mutex_lock(&opts->lock); + + if (opts->instances) { + ret = -EBUSY; + goto out; + } + + ret = kstrtou8(page, 0, &opts->protocol); + if (ret) + goto out; + ret = count; + +out: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_acm_, protocol); + +static struct configfs_attribute *acm_attrs[] = { +#ifdef CONFIG_U_SERIAL_CONSOLE + &f_acm_attr_console, +#endif + &f_acm_attr_port_num, + &f_acm_attr_protocol, + NULL, +}; + +static const 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); + mutex_destroy(&opts->lock); + kfree(opts); +} + +static struct usb_function_instance *acm_alloc_instance(void) +{ + struct f_serial_opts *opts; + int ret; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + opts->protocol = USB_CDC_ACM_PROTO_AT_V25TER; + opts->func_inst.free_func_inst = acm_free_instance; + mutex_init(&opts->lock); + 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_DESCRIPTION("USB CDC serial (ACM) function driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_ecm.c b/drivers/usb/gadget/function/f_ecm.c new file mode 100644 index 000000000..e495bac4e --- /dev/null +++ b/drivers/usb/gadget/function/f_ecm.c @@ -0,0 +1,981 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_ecm.c -- USB CDC Ethernet (ECM) link function driver + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2008 Nokia Corporation + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "u_ether.h" +#include "u_ether_configfs.h" +#include "u_ecm.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; + atomic_t notify_count; + 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); +} + +/*-------------------------------------------------------------------------*/ + +/* + * 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 (atomic_read(&ecm->notify_count)) + 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", str_true_false(ecm->is_open)); + 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(gether_bitrate(cdev->gadget)); + data[1] = data[0]; + + DBG(cdev, "notify speed %d\n", gether_bitrate(cdev->gadget)); + ecm->notify_state = ECM_NOTIFY_NONE; + break; + } + event->bmRequestType = 0xA1; + event->wIndex = cpu_to_le16(ecm->ctrl_id); + + atomic_inc(&ecm->notify_count); + status = usb_ep_queue(ecm->notify, req, GFP_ATOMIC); + if (status < 0) { + atomic_dec(&ecm->notify_count); + 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 */ + atomic_dec(&ecm->notify_count); + break; + case -ECONNRESET: + case -ESHUTDOWN: + atomic_set(&ecm->notify_count, 0); + ecm->notify_state = ECM_NOTIFY_NONE; + break; + default: + DBG(cdev, "event %02x --> %d\n", + event->bNotificationType, req->status); + atomic_dec(&ecm->notify_count); + break; + } + 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; + + 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); + + /* Data interface has two altsettings, 0 and 1 */ + } else if (intf == ecm->data_id) { + if (alt > 1) + goto fail; + + if (ecm->port.in_ep->enabled) { + 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_zlp_supported(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->enabled ? 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->enabled) { + gether_disconnect(&ecm->port); + } else { + ecm->port.in_ep->desc = NULL; + ecm->port.out_ep->desc = NULL; + } + + usb_ep_disable(ecm->notify); + 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); + struct usb_string *us; + int status = 0; + struct usb_ep *ep; + + struct f_ecm_opts *ecm_opts; + struct net_device *net __free(detach_gadget) = NULL; + struct usb_request *request __free(free_usb_request) = NULL; + + if (!can_support_ecm(cdev->gadget)) + return -EINVAL; + + ecm_opts = container_of(f->fi, struct f_ecm_opts, func_inst); + + scoped_guard(mutex, &ecm_opts->lock) + if (ecm_opts->bind_count == 0 && !ecm_opts->bound) { + if (!device_is_registered(&ecm_opts->net->dev)) { + gether_set_gadget(ecm_opts->net, cdev->gadget); + status = gether_register_netdev(ecm_opts->net); + } else + status = gether_attach_gadget(ecm_opts->net, cdev->gadget); + + if (status) + return status; + net = ecm_opts->net; + } + + ecm_string_defs[1].s = ecm->ethaddr; + + us = usb_gstrings_attach(cdev, ecm_strings, + ARRAY_SIZE(ecm_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + ecm_control_intf.iInterface = us[0].id; + ecm_data_intf.iInterface = us[2].id; + ecm_desc.iMACAddress = us[1].id; + ecm_iad_descriptor.iFunction = us[3].id; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + return status; + 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) + return status; + ecm->data_id = status; + + ecm_data_nop_intf.bInterfaceNumber = status; + ecm_data_intf.bInterfaceNumber = status; + ecm_union_desc.bSlaveInterface0 = status; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_in_desc); + if (!ep) + return -ENODEV; + ecm->port.in_ep = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_out_desc); + if (!ep) + return -ENODEV; + ecm->port.out_ep = ep; + + /* 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) + return -ENODEV; + ecm->notify = ep; + + /* allocate notification request and buffer */ + request = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!request) + return -ENOMEM; + request->buf = kmalloc(ECM_STATUS_BYTECOUNT, GFP_KERNEL); + if (!request->buf) + return -ENOMEM; + request->context = ecm; + request->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, ecm_ss_function); + if (status) + return status; + + /* 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; + + ecm->notify_req = no_free_ptr(request); + + ecm_opts->bind_count++; + retain_and_null_ptr(net); + + DBG(cdev, "CDC Ethernet: IN/%s OUT/%s NOTIFY/%s\n", + ecm->port.in_ep->name, ecm->port.out_ep->name, + ecm->notify->name); + return 0; +} + +static inline struct f_ecm_opts *to_f_ecm_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_ecm_opts, + func_inst.group); +} + +/* f_ecm_item_ops */ +USB_ETHERNET_CONFIGFS_ITEM(ecm); + +/* f_ecm_opts_dev_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(ecm); + +/* f_ecm_opts_host_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(ecm); + +/* f_ecm_opts_qmult */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(ecm); + +/* f_ecm_opts_ifname */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(ecm); + +static struct configfs_attribute *ecm_attrs[] = { + &ecm_opts_attr_dev_addr, + &ecm_opts_attr_host_addr, + &ecm_opts_attr_qmult, + &ecm_opts_attr_ifname, + NULL, +}; + +static const struct config_item_type ecm_func_type = { + .ct_item_ops = &ecm_item_ops, + .ct_attrs = ecm_attrs, + .ct_owner = THIS_MODULE, +}; + +static void ecm_free_inst(struct usb_function_instance *f) +{ + struct f_ecm_opts *opts; + + opts = container_of(f, struct f_ecm_opts, func_inst); + if (device_is_registered(&opts->net->dev)) + gether_cleanup(netdev_priv(opts->net)); + else + free_netdev(opts->net); + kfree(opts); +} + +static struct usb_function_instance *ecm_alloc_inst(void) +{ + struct f_ecm_opts *opts; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = ecm_free_inst; + opts->net = gether_setup_default(); + if (IS_ERR(opts->net)) { + struct net_device *net = opts->net; + kfree(opts); + return ERR_CAST(net); + } + + config_group_init_type_name(&opts->func_inst.group, "", &ecm_func_type); + + return &opts->func_inst; +} + +static void ecm_suspend(struct usb_function *f) +{ + struct f_ecm *ecm = func_to_ecm(f); + struct usb_composite_dev *cdev = ecm->port.func.config->cdev; + + DBG(cdev, "ECM Suspend\n"); + + gether_suspend(&ecm->port); +} + +static void ecm_resume(struct usb_function *f) +{ + struct f_ecm *ecm = func_to_ecm(f); + struct usb_composite_dev *cdev = ecm->port.func.config->cdev; + + DBG(cdev, "ECM Resume\n"); + + gether_resume(&ecm->port); +} + +static int ecm_get_status(struct usb_function *f) +{ + return (f->func_wakeup_armed ? USB_INTRF_STAT_FUNC_RW : 0) | + USB_INTRF_STAT_FUNC_RW_CAP; +} + +static void ecm_free(struct usb_function *f) +{ + struct f_ecm *ecm; + struct f_ecm_opts *opts; + + ecm = func_to_ecm(f); + opts = container_of(f->fi, struct f_ecm_opts, func_inst); + kfree(ecm); + mutex_lock(&opts->lock); + opts->refcnt--; + mutex_unlock(&opts->lock); +} + +static void ecm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_ecm *ecm = func_to_ecm(f); + struct f_ecm_opts *ecm_opts; + + DBG(c->cdev, "ecm unbind\n"); + + ecm_opts = container_of(f->fi, struct f_ecm_opts, func_inst); + + usb_free_all_descriptors(f); + + if (atomic_read(&ecm->notify_count)) { + usb_ep_dequeue(ecm->notify, ecm->notify_req); + atomic_set(&ecm->notify_count, 0); + } + + kfree(ecm->notify_req->buf); + usb_ep_free_request(ecm->notify, ecm->notify_req); + + ecm_opts->bind_count--; + if (ecm_opts->bind_count == 0 && !ecm_opts->bound) + gether_detach_gadget(ecm_opts->net); +} + +static struct usb_function *ecm_alloc(struct usb_function_instance *fi) +{ + struct f_ecm *ecm; + struct f_ecm_opts *opts; + int status; + + /* allocate and initialize one new instance */ + ecm = kzalloc_obj(*ecm); + if (!ecm) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_ecm_opts, func_inst); + mutex_lock(&opts->lock); + opts->refcnt++; + + /* export host's Ethernet address in CDC format */ + status = gether_get_host_addr_cdc(opts->net, ecm->ethaddr, + sizeof(ecm->ethaddr)); + if (status < 12) { + kfree(ecm); + mutex_unlock(&opts->lock); + return ERR_PTR(-EINVAL); + } + + ecm->port.ioport = netdev_priv(opts->net); + mutex_unlock(&opts->lock); + ecm->port.cdc_filter = DEFAULT_FILTER; + + ecm->port.func.name = "cdc_ethernet"; + /* 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; + ecm->port.func.free_func = ecm_free; + ecm->port.func.suspend = ecm_suspend; + ecm->port.func.get_status = ecm_get_status; + ecm->port.func.resume = ecm_resume; + + return &ecm->port.func; +} + +DECLARE_USB_FUNCTION_INIT(ecm, ecm_alloc_inst, ecm_alloc); +MODULE_DESCRIPTION("USB CDC Ethernet (ECM) link function driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/function/f_eem.c b/drivers/usb/gadget/function/f_eem.c new file mode 100644 index 000000000..ac37d7c1d --- /dev/null +++ b/drivers/usb/gadget/function/f_eem.c @@ -0,0 +1,687 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "u_ether.h" +#include "u_ether_configfs.h" +#include "u_eem.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; +}; + +struct in_context { + struct sk_buff *skb; + struct usb_ep *ep; +}; + +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 = { + .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 = { + .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 = { + .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[] = { + /* 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 = { + .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 = { + .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[] = { + /* 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 = { + .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 = { + .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 = { + .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[] = { + /* 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; + 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 -EOPNOTSUPP; +} + + +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) { + 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->enabled) + gether_disconnect(&eem->port); +} + +/*-------------------------------------------------------------------------*/ + +/* EEM function driver setup/binding */ + +static int 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); + struct usb_string *us; + int status; + struct usb_ep *ep; + + struct f_eem_opts *eem_opts; + struct net_device *net __free(detach_gadget) = NULL; + + eem_opts = container_of(f->fi, struct f_eem_opts, func_inst); + + scoped_guard(mutex, &eem_opts->lock) + if (eem_opts->bind_count == 0 && !eem_opts->bound) { + if (!device_is_registered(&eem_opts->net->dev)) { + gether_set_gadget(eem_opts->net, cdev->gadget); + status = gether_register_netdev(eem_opts->net); + } else + status = gether_attach_gadget(eem_opts->net, cdev->gadget); + + if (status) + return status; + net = eem_opts->net; + } + + us = usb_gstrings_attach(cdev, eem_strings, + ARRAY_SIZE(eem_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + eem_intf.iInterface = us[0].id; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + return status; + eem->ctrl_id = status; + eem_intf.bInterfaceNumber = status; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_in_desc); + if (!ep) + return -ENODEV; + eem->port.in_ep = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_out_desc); + if (!ep) + return -ENODEV; + eem->port.out_ep = ep; + + /* 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, eem_ss_function); + if (status) + return status; + + eem_opts->bind_count++; + retain_and_null_ptr(net); + + DBG(cdev, "CDC Ethernet (EEM): IN/%s OUT/%s\n", + eem->port.in_ep->name, eem->port.out_ep->name); + return 0; +} + +static void eem_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct in_context *ctx = req->context; + + dev_kfree_skb_any(ctx->skb); + kfree(req->buf); + usb_ep_free_request(ctx->ep, req); + kfree(ctx); +} + +/* + * 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 headroom, tailroom, padlen = 0; + u16 len; + + if (!skb) + return NULL; + + len = skb->len; + headroom = skb_headroom(skb); + 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) && !skb_cloned(skb)) + 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; + struct in_context *ctx; + struct usb_ep *ep; + 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)); + + ep = port->in_ep; + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (!req) { + dev_kfree_skb_any(skb2); + goto next; + } + + req->buf = kmalloc(skb2->len, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + dev_kfree_skb_any(skb2); + goto next; + } + + ctx = kmalloc_obj(*ctx); + if (!ctx) { + kfree(req->buf); + usb_ep_free_request(ep, req); + dev_kfree_skb_any(skb2); + goto next; + } + ctx->skb = skb2; + ctx->ep = ep; + + skb_copy_bits(skb2, 0, req->buf, skb2->len); + req->length = skb2->len; + req->complete = eem_cmd_complete; + req->zero = 1; + req->context = ctx; + if (usb_ep_queue(port->in_ep, req, GFP_ATOMIC)) { + DBG(cdev, "echo response queue fail\n"); + kfree(ctx); + kfree(req->buf); + usb_ep_free_request(ep, req); + dev_kfree_skb_any(skb2); + } + 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"); + goto next; + } + skb_trim(skb2, len - ETH_FCS_LEN); + + skb3 = skb_copy_expand(skb2, + NET_IP_ALIGN, + 0, + GFP_ATOMIC); + if (unlikely(!skb3)) { + dev_kfree_skb_any(skb2); + goto next; + } + 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; +} + +static inline struct f_eem_opts *to_f_eem_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_eem_opts, + func_inst.group); +} + +/* f_eem_item_ops */ +USB_ETHERNET_CONFIGFS_ITEM(eem); + +/* f_eem_opts_dev_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(eem); + +/* f_eem_opts_host_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(eem); + +/* f_eem_opts_qmult */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(eem); + +/* f_eem_opts_ifname */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(eem); + +static struct configfs_attribute *eem_attrs[] = { + &eem_opts_attr_dev_addr, + &eem_opts_attr_host_addr, + &eem_opts_attr_qmult, + &eem_opts_attr_ifname, + NULL, +}; + +static const struct config_item_type eem_func_type = { + .ct_item_ops = &eem_item_ops, + .ct_attrs = eem_attrs, + .ct_owner = THIS_MODULE, +}; + +static void eem_free_inst(struct usb_function_instance *f) +{ + struct f_eem_opts *opts; + + opts = container_of(f, struct f_eem_opts, func_inst); + if (device_is_registered(&opts->net->dev)) + gether_cleanup(netdev_priv(opts->net)); + else + free_netdev(opts->net); + kfree(opts); +} + +static struct usb_function_instance *eem_alloc_inst(void) +{ + struct f_eem_opts *opts; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = eem_free_inst; + opts->net = gether_setup_default(); + if (IS_ERR(opts->net)) { + struct net_device *net = opts->net; + kfree(opts); + return ERR_CAST(net); + } + + config_group_init_type_name(&opts->func_inst.group, "", &eem_func_type); + + return &opts->func_inst; +} + +static void eem_free(struct usb_function *f) +{ + struct f_eem *eem; + struct f_eem_opts *opts; + + eem = func_to_eem(f); + opts = container_of(f->fi, struct f_eem_opts, func_inst); + kfree(eem); + mutex_lock(&opts->lock); + opts->refcnt--; + mutex_unlock(&opts->lock); +} + +static void eem_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_eem_opts *opts; + + DBG(c->cdev, "eem unbind\n"); + + opts = container_of(f->fi, struct f_eem_opts, func_inst); + + usb_free_all_descriptors(f); + + opts->bind_count--; + if (opts->bind_count == 0 && !opts->bound) + gether_detach_gadget(opts->net); +} + +static struct usb_function *eem_alloc(struct usb_function_instance *fi) +{ + struct f_eem *eem; + struct f_eem_opts *opts; + + /* allocate and initialize one new instance */ + eem = kzalloc_obj(*eem); + if (!eem) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_eem_opts, func_inst); + mutex_lock(&opts->lock); + opts->refcnt++; + + eem->port.ioport = netdev_priv(opts->net); + mutex_unlock(&opts->lock); + eem->port.cdc_filter = DEFAULT_FILTER; + + eem->port.func.name = "cdc_eem"; + /* 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.func.free_func = eem_free; + eem->port.wrap = eem_wrap; + eem->port.unwrap = eem_unwrap; + eem->port.header_len = EEM_HLEN; + + return &eem->port.func; +} + +DECLARE_USB_FUNCTION_INIT(eem, eem_alloc_inst, eem_alloc); +MODULE_DESCRIPTION("USB CDC Ethernet (EEM) link function driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c new file mode 100644 index 000000000..43962e05e --- /dev/null +++ b/drivers/usb/gadget/function/f_fs.c @@ -0,0 +1,4470 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + + +/* #define DEBUG */ +/* #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 "u_fs.h" +#include "u_os_desc.h" +#include "configfs.h" + +#define FUNCTIONFS_MAGIC 0xa647361 /* Chosen by a honest dice roll ;) */ +#define MAX_ALT_SETTINGS 2 /* Allow up to 2 alt settings to be set. */ + +#define DMABUF_ENQUEUE_TIMEOUT_MS 5000 + +MODULE_IMPORT_NS("DMA_BUF"); + +/* 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(const char *dev_name) + __attribute__((malloc)); + +/* Opened counter handling. */ +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; + int cur_alt[MAX_CONFIG_INTERFACES]; +}; + + +static struct ffs_function *ffs_func_from_usb(struct usb_function *f) +{ + return container_of(f, struct ffs_function, function); +} + + +static inline enum ffs_setup_state +ffs_setup_state_clear_cancelled(struct ffs_data *ffs) +{ + return (enum ffs_setup_state) + cmpxchg(&ffs->setup_state, FFS_SETUP_CANCELLED, FFS_NO_SETUP); +} + + +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 int ffs_func_set_alt(struct usb_function *, unsigned, unsigned); +static int ffs_func_get_alt(struct usb_function *f, unsigned int intf); +static void ffs_func_disable(struct usb_function *); +static int ffs_func_setup(struct usb_function *, + const struct usb_ctrlrequest *); +static bool ffs_func_req_match(struct usb_function *, + const struct usb_ctrlrequest *, + bool config0); +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 *************************************************/ + +struct ffs_ep { + struct usb_ep *ep; /* P: ffs->eps_lock */ + struct usb_request *req; /* P: epfile->mutex */ + + /* [0]: full speed, [1]: high speed, [2]: super speed */ + struct usb_endpoint_descriptor *descs[3]; + + u8 num; +}; + +struct ffs_dmabuf_priv { + struct list_head entry; + struct kref ref; + struct ffs_data *ffs; + struct dma_buf_attachment *attach; + struct sg_table *sgt; + enum dma_data_direction dir; + spinlock_t lock; + u64 context; + struct usb_request *req; /* P: ffs->eps_lock */ + struct usb_ep *ep; /* P: ffs->eps_lock */ +}; + +struct ffs_dma_fence { + struct dma_fence base; + struct ffs_dmabuf_priv *priv; + struct work_struct work; + struct usb_ep *ep; + struct usb_request *req; +}; + +struct ffs_epfile { + /* Protects ep->ep and ep->req. */ + struct mutex mutex; + + struct ffs_data *ffs; + struct ffs_ep *ep; /* P: ffs->eps_lock */ + struct ffs_epfile *epfile_in; /* P: ffs->eps_lock */ + struct ffs_epfile *epfile_out; /* P: ffs->eps_lock */ + + /* + * Buffer for holding data from partial reads which may happen since + * we’re rounding user read requests to a multiple of a max packet size. + * + * The pointer is initialised with NULL value and may be set by + * __ffs_epfile_read_data function to point to a temporary buffer. + * + * In normal operation, calls to __ffs_epfile_read_buffered will consume + * data from said buffer and eventually free it. Importantly, while the + * function is using the buffer, it sets the pointer to NULL. This is + * all right since __ffs_epfile_read_data and __ffs_epfile_read_buffered + * can never run concurrently (they are synchronised by epfile->mutex) + * so the latter will not assign a new value to the pointer. + * + * Meanwhile ffs_func_eps_disable frees the buffer (if the pointer is + * valid) and sets the pointer to READ_BUFFER_DROP value. This special + * value is crux of the synchronisation between ffs_func_eps_disable and + * __ffs_epfile_read_data. + * + * Once __ffs_epfile_read_data is about to finish it will try to set the + * pointer back to its old value (as described above), but seeing as the + * pointer is not-NULL (namely READ_BUFFER_DROP) it will instead free + * the buffer. + * + * == State transitions == + * + * • ptr == NULL: (initial state) + * ◦ __ffs_epfile_read_buffer_free: go to ptr == DROP + * ◦ __ffs_epfile_read_buffered: nop + * ◦ __ffs_epfile_read_data allocates temp buffer: go to ptr == buf + * ◦ reading finishes: n/a, not in ‘and reading’ state + * • ptr == DROP: + * ◦ __ffs_epfile_read_buffer_free: nop + * ◦ __ffs_epfile_read_buffered: go to ptr == NULL + * ◦ __ffs_epfile_read_data allocates temp buffer: free buf, nop + * ◦ reading finishes: n/a, not in ‘and reading’ state + * • ptr == buf: + * ◦ __ffs_epfile_read_buffer_free: free buf, go to ptr == DROP + * ◦ __ffs_epfile_read_buffered: go to ptr == NULL and reading + * ◦ __ffs_epfile_read_data: n/a, __ffs_epfile_read_buffered + * is always called first + * ◦ reading finishes: n/a, not in ‘and reading’ state + * • ptr == NULL and reading: + * ◦ __ffs_epfile_read_buffer_free: go to ptr == DROP and reading + * ◦ __ffs_epfile_read_buffered: n/a, mutex is held + * ◦ __ffs_epfile_read_data: n/a, mutex is held + * ◦ reading finishes and … + * … all data read: free buf, go to ptr == NULL + * … otherwise: go to ptr == buf and reading + * • ptr == DROP and reading: + * ◦ __ffs_epfile_read_buffer_free: nop + * ◦ __ffs_epfile_read_buffered: n/a, mutex is held + * ◦ __ffs_epfile_read_data: n/a, mutex is held + * ◦ reading finishes: free buf, go to ptr == DROP + */ + struct ffs_buffer *read_buffer; +#define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN)) + + char name[8]; + + unsigned char in; /* P: ffs->eps_lock */ + unsigned char isoc; /* P: ffs->eps_lock */ + + u8 zlp_enabled; /* P: ffs->eps_lock */ + bool is_rw_proxy; + + /* Protects dmabufs */ + struct mutex dmabufs_mutex; + struct list_head dmabufs; /* P: dmabufs_mutex */ + atomic_t seqno; +}; + +struct ffs_buffer { + size_t length; + char *data; + char storage[] __counted_by(length); +}; + +/* ffs_io_data structure ***************************************************/ + +struct ffs_io_data { + bool aio; + bool read; + + struct kiocb *kiocb; + struct iov_iter data; + const void *to_free; + char *buf; + + struct mm_struct *mm; + struct work_struct work; + + struct usb_ep *ep; + struct usb_request *req; + struct sg_table sgt; + bool use_sg; + + struct ffs_data *ffs; + + int status; + struct completion done; +}; + +struct ffs_desc_helper { + struct ffs_data *ffs; + unsigned interfaces_count; + unsigned eps_count; +}; + +static int __must_check ffs_epfiles_create(struct ffs_data *ffs); +static void ffs_epfiles_destroy(struct super_block *sb, + struct ffs_epfile *epfiles, unsigned count); + +static int ffs_sb_create_file(struct super_block *sb, const char *name, + void *data, const struct file_operations *fops); + +/* Devices management *******************************************************/ + +DEFINE_MUTEX(ffs_lock); +EXPORT_SYMBOL_GPL(ffs_lock); + +static struct ffs_dev *_ffs_find_dev(const char *name); +static struct ffs_dev *_ffs_alloc_dev(void); +static void _ffs_free_dev(struct ffs_dev *dev); +static int ffs_acquire_dev(const char *dev_name, struct ffs_data *ffs_data); +static void ffs_release_dev(struct ffs_dev *ffs_dev); +static int ffs_ready(struct ffs_data *ffs); +static void ffs_closed(struct ffs_data *ffs); +static void ffs_reset_work(struct work_struct *work); + +/* 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(&ffs->ep0req_completion); +} + +static int __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data, size_t len) + __releases(&ffs->ev.waitq.lock) +{ + struct usb_request *req = ffs->ep0req; + int ret; + + if (!req) { + spin_unlock_irq(&ffs->ev.waitq.lock); + return -EINVAL; + } + + 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; + + reinit_completion(&ffs->ep0req_completion); + + ret = usb_ep_queue(ffs->gadget->ep0, req, GFP_ATOMIC); + if (ret < 0) + return ret; + + ret = wait_for_completion_interruptible(&ffs->ep0req_completion); + if (ret) { + usb_ep_dequeue(ffs->gadget->ep0, req); + return -EINTR; + } + + ffs->setup_state = FFS_NO_SETUP; + return req->status ? req->status : req->actual; +} + +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; + + /* Fast check if setup was canceled */ + if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED) + return -EIDRM; + + /* Acquire mutex */ + ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); + if (ret < 0) + return ret; + + /* Check state */ + switch (ffs->state) { + case FFS_READ_DESCRIPTORS: + case FFS_READ_STRINGS: + /* Copy data */ + if (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 (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 (ret < 0) + break; + + ret = ffs_epfiles_create(ffs); + if (ret) { + ffs->state = FFS_CLOSING; + break; + } + + ffs->state = FFS_ACTIVE; + mutex_unlock(&ffs->mutex); + + ret = ffs_ready(ffs); + if (ret < 0) { + ffs->state = FFS_CLOSING; + return ret; + } + + 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_clear_cancelled(ffs)) { + case FFS_SETUP_CANCELLED: + 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_t(size_t, len, 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_CANCELLED 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_clear_cancelled(ffs) == + FFS_SETUP_CANCELLED) { + 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; +} + +/* Called with ffs->ev.waitq.lock and ffs->mutex held, both released on exit. */ +static ssize_t __ffs_ep0_read_events(struct ffs_data *ffs, char __user *buf, + size_t n) + __releases(&ffs->ev.waitq.lock) +{ + /* + * n cannot be bigger than ffs->ev.count, which cannot be bigger than + * size of ffs->ev.types array (which is four) so that's how much space + * we reserve. + */ + struct usb_functionfs_event events[ARRAY_SIZE(ffs->ev.types)]; + const size_t size = n * sizeof *events; + unsigned i = 0; + + memset(events, 0, size); + + 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); + + ffs->ev.count -= n; + if (ffs->ev.count) + memmove(ffs->ev.types, ffs->ev.types + n, + ffs->ev.count * sizeof *ffs->ev.types); + + spin_unlock_irq(&ffs->ev.waitq.lock); + mutex_unlock(&ffs->mutex); + + return copy_to_user(buf, events, size) ? -EFAULT : size; +} + +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; + + /* Fast check if setup was canceled */ + if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED) + return -EIDRM; + +retry: + /* Acquire mutex */ + ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); + if (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_clear_cancelled(ffs)) { + case FFS_SETUP_CANCELLED: + ret = -EIDRM; + break; + + case FFS_NO_SETUP: + n = len / sizeof(struct usb_functionfs_event); + if (!n) { + ret = -EINVAL; + break; + } + + if ((file->f_flags & O_NONBLOCK) && !ffs->ev.count) { + ret = -EAGAIN; + break; + } + + if (!ffs->ev.count) { + spin_unlock_irq(&ffs->ev.waitq.lock); + mutex_unlock(&ffs->mutex); + + if (wait_event_interruptible_exclusive(ffs->ev.waitq, + ffs->ev.count)) + return -EINTR; + + goto retry; + } + + /* unlocks spinlock */ + return __ffs_ep0_read_events(ffs, buf, + min_t(size_t, n, 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_t(size_t, len, le16_to_cpu(ffs->ev.setup.wLength)); + + spin_unlock_irq(&ffs->ev.waitq.lock); + + if (len) { + data = kmalloc(len, GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto done_mutex; + } + } + + spin_lock_irq(&ffs->ev.waitq.lock); + + /* See ffs_ep0_write() */ + if (ffs_setup_state_clear_cancelled(ffs) == + FFS_SETUP_CANCELLED) { + ret = -EIDRM; + break; + } + + /* unlocks spinlock */ + ret = __ffs_ep0_queue_wait(ffs, data, len); + if ((ret > 0) && (copy_to_user(buf, data, ret))) + 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 void ffs_data_reset(struct ffs_data *ffs); + +static int ffs_ep0_open(struct inode *inode, struct file *file) +{ + struct ffs_data *ffs = inode->i_sb->s_fs_info; + + spin_lock_irq(&ffs->eps_lock); + if (ffs->state == FFS_CLOSING) { + spin_unlock_irq(&ffs->eps_lock); + return -EBUSY; + } + if (!ffs->opened++ && ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; + spin_unlock_irq(&ffs->eps_lock); + ffs_data_reset(ffs); + } else { + spin_unlock_irq(&ffs->eps_lock); + } + file->private_data = ffs; + + return stream_open(inode, file); +} + +static int ffs_ep0_release(struct inode *inode, struct file *file) +{ + struct ffs_data *ffs = file->private_data; + + 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; + + 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 __poll_t ffs_ep0_poll(struct file *file, poll_table *wait) +{ + struct ffs_data *ffs = file->private_data; + __poll_t mask = EPOLLWRNORM; + int ret; + + poll_wait(file, &ffs->ev.waitq, wait); + + ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); + if (ret < 0) + return mask; + + switch (ffs->state) { + case FFS_READ_DESCRIPTORS: + case FFS_READ_STRINGS: + mask |= EPOLLOUT; + break; + + case FFS_ACTIVE: + switch (ffs->setup_state) { + case FFS_NO_SETUP: + if (ffs->ev.count) + mask |= EPOLLIN; + break; + + case FFS_SETUP_PENDING: + case FFS_SETUP_CANCELLED: + mask |= (EPOLLIN | EPOLLOUT); + break; + } + break; + + case FFS_CLOSING: + break; + case FFS_DEACTIVATED: + break; + } + + mutex_unlock(&ffs->mutex); + + return mask; +} + +static const struct file_operations ffs_ep0_operations = { + + .open = ffs_ep0_open, + .write = ffs_ep0_write, + .read = ffs_ep0_read, + .release = ffs_ep0_release, + .unlocked_ioctl = ffs_ep0_ioctl, + .poll = ffs_ep0_poll, +}; + + +/* "Normal" endpoints operations ********************************************/ + +static void ffs_epfile_io_complete(struct usb_ep *_ep, struct usb_request *req) +{ + struct ffs_io_data *io_data = req->context; + + if (req->status) + io_data->status = req->status; + else + io_data->status = req->actual; + + complete(&io_data->done); +} + +static ssize_t ffs_copy_to_iter(void *data, int data_len, struct iov_iter *iter) +{ + ssize_t ret = copy_to_iter(data, data_len, iter); + if (ret == data_len) + return ret; + + if (iov_iter_count(iter)) + return -EFAULT; + + /* + * Dear user space developer! + * + * TL;DR: To stop getting below error message in your kernel log, change + * user space code using functionfs to align read buffers to a max + * packet size. + * + * Some UDCs (e.g. dwc3) require request sizes to be a multiple of a max + * packet size. When unaligned buffer is passed to functionfs, it + * internally uses a larger, aligned buffer so that such UDCs are happy. + * + * Unfortunately, this means that host may send more data than was + * requested in read(2) system call. f_fs doesn’t know what to do with + * that excess data so it simply drops it. + * + * Was the buffer aligned in the first place, no such problem would + * happen. + * + * Data may be dropped only in AIO reads. Synchronous reads are handled + * by splitting a request into multiple parts. This splitting may still + * be a problem though so it’s likely best to align the buffer + * regardless of it being AIO or not.. + * + * This only affects OUT endpoints, i.e. reading data with a read(2), + * aio_read(2) etc. system calls. Writing data to an IN endpoint is not + * affected. + */ + pr_err("functionfs read size %d > requested size %zd, dropping excess data. " + "Align read buffer size to max packet size to avoid the problem.\n", + data_len, ret); + + return ret; +} + +/* + * allocate a virtually contiguous buffer and create a scatterlist describing it + * @sg_table - pointer to a place to be filled with sg_table contents + * @size - required buffer size + */ +static void *ffs_build_sg_list(struct sg_table *sgt, size_t sz) +{ + struct page **pages; + void *vaddr, *ptr; + unsigned int n_pages; + int i; + + vaddr = vmalloc(sz); + if (!vaddr) + return NULL; + + n_pages = PAGE_ALIGN(sz) >> PAGE_SHIFT; + pages = kvmalloc_objs(struct page *, n_pages); + if (!pages) { + vfree(vaddr); + + return NULL; + } + for (i = 0, ptr = vaddr; i < n_pages; ++i, ptr += PAGE_SIZE) + pages[i] = vmalloc_to_page(ptr); + + if (sg_alloc_table_from_pages(sgt, pages, n_pages, 0, sz, GFP_KERNEL)) { + kvfree(pages); + vfree(vaddr); + + return NULL; + } + kvfree(pages); + + return vaddr; +} + +static inline void *ffs_alloc_buffer(struct ffs_io_data *io_data, + size_t data_len) +{ + if (io_data->use_sg) + return ffs_build_sg_list(&io_data->sgt, data_len); + + return kmalloc(data_len, GFP_KERNEL); +} + +static inline void ffs_free_buffer(struct ffs_io_data *io_data) +{ + if (!io_data->buf) + return; + + if (io_data->use_sg) { + sg_free_table(&io_data->sgt); + vfree(io_data->buf); + } else { + kfree(io_data->buf); + } +} + +static void ffs_user_copy_worker(struct work_struct *work) +{ + struct ffs_io_data *io_data = container_of(work, struct ffs_io_data, + work); + int ret = io_data->status; + bool kiocb_has_eventfd = io_data->kiocb->ki_flags & IOCB_EVENTFD; + + if (io_data->read && ret > 0) { + if (mmget_not_zero(io_data->mm)) { + kthread_use_mm(io_data->mm); + ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data); + kthread_unuse_mm(io_data->mm); + mmput(io_data->mm); + } else { + ret = -EFAULT; + } + mmdrop(io_data->mm); + } + + io_data->kiocb->ki_complete(io_data->kiocb, ret); + + if (io_data->ffs->ffs_eventfd && !kiocb_has_eventfd) + eventfd_signal(io_data->ffs->ffs_eventfd); + + usb_ep_free_request(io_data->ep, io_data->req); + + if (io_data->read) + kfree(io_data->to_free); + ffs_free_buffer(io_data); + kfree(io_data); +} + +static void ffs_epfile_async_io_complete(struct usb_ep *_ep, + struct usb_request *req) +{ + struct ffs_io_data *io_data = req->context; + struct ffs_data *ffs = io_data->ffs; + + io_data->status = req->status ? req->status : req->actual; + + INIT_WORK(&io_data->work, ffs_user_copy_worker); + queue_work(ffs->io_completion_wq, &io_data->work); +} + +static void __ffs_epfile_read_buffer_free(struct ffs_epfile *epfile) +{ + /* + * See comment in struct ffs_epfile for full read_buffer pointer + * synchronisation story. + */ + struct ffs_buffer *buf = xchg(&epfile->read_buffer, READ_BUFFER_DROP); + if (buf && buf != READ_BUFFER_DROP) + kfree(buf); +} + +/* Assumes epfile->mutex is held. */ +static ssize_t __ffs_epfile_read_buffered(struct ffs_epfile *epfile, + struct iov_iter *iter) +{ + /* + * Null out epfile->read_buffer so ffs_func_eps_disable does not free + * the buffer while we are using it. See comment in struct ffs_epfile + * for full read_buffer pointer synchronisation story. + */ + struct ffs_buffer *buf = xchg(&epfile->read_buffer, NULL); + ssize_t ret; + if (!buf || buf == READ_BUFFER_DROP) + return 0; + + ret = copy_to_iter(buf->data, buf->length, iter); + if (buf->length == ret) { + kfree(buf); + return ret; + } + + if (iov_iter_count(iter)) { + ret = -EFAULT; + } else { + buf->length -= ret; + buf->data += ret; + } + + if (cmpxchg(&epfile->read_buffer, NULL, buf)) + kfree(buf); + + return ret; +} + +/* Assumes epfile->mutex is held. */ +static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile, + void *data, int data_len, + struct iov_iter *iter) +{ + struct ffs_buffer *buf; + + ssize_t ret = copy_to_iter(data, data_len, iter); + if (data_len == ret) + return ret; + + if (iov_iter_count(iter)) + return -EFAULT; + + /* See ffs_copy_to_iter for more context. */ + pr_warn("functionfs read size %d > requested size %zd, splitting request into multiple reads.", + data_len, ret); + + data_len -= ret; + buf = kmalloc_flex(*buf, storage, data_len); + if (!buf) + return -ENOMEM; + buf->length = data_len; + buf->data = buf->storage; + memcpy(buf->storage, data + ret, flex_array_size(buf, storage, data_len)); + + /* + * At this point read_buffer is NULL or READ_BUFFER_DROP (if + * ffs_func_eps_disable has been called in the meanwhile). See comment + * in struct ffs_epfile for full read_buffer pointer synchronisation + * story. + */ + if (cmpxchg(&epfile->read_buffer, NULL, buf)) + kfree(buf); + + return ret; +} + +static struct ffs_ep *ffs_epfile_wait_ep(struct ffs_epfile *epfile, struct file *file) +{ + struct ffs_ep *ep; + int ret; + + /* Wait for endpoint to be enabled */ + ep = epfile->ep; + if (!ep) { + if (file->f_flags & O_NONBLOCK) + return ERR_PTR(-EAGAIN); + + ret = wait_event_interruptible( + epfile->ffs->wait, (ep = epfile->ep)); + if (ret) + return ERR_PTR(-EINTR); + } + + return ep; +} + +static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) +{ + struct ffs_epfile *epfile = file->private_data; + struct usb_request *req; + struct ffs_ep *ep; + char *data = NULL; + ssize_t ret, data_len = -EINVAL; + int halt; + bool is_rw_proxy = epfile->is_rw_proxy; + + /* Are we still active? */ + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + + /* Proxy to base endpoint if rw_proxy */ + if (is_rw_proxy) + epfile = io_data->read ? epfile->epfile_out : epfile->epfile_in; + + ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) + return PTR_ERR(ep); + + /* Do we halt? */ + halt = is_rw_proxy ? 0 : (!io_data->read == !epfile->in); + if (halt && epfile->isoc) + return -EINVAL; + + /* We will be using request and read_buffer */ + ret = ffs_mutex_lock(&epfile->mutex, file->f_flags & O_NONBLOCK); + if (ret) + goto error; + + /* Allocate & copy */ + if (!halt) { + struct usb_gadget *gadget; + + /* + * Do we have buffered data from previous partial read? Check + * that for synchronous case only because we do not have + * facility to ‘wake up’ a pending asynchronous read and push + * buffered data to it which we would need to make things behave + * consistently. + */ + if (!io_data->aio && io_data->read) { + ret = __ffs_epfile_read_buffered(epfile, &io_data->data); + if (ret) + goto error_mutex; + } + + /* + * if we _do_ wait above, the epfile->ffs->gadget might be NULL + * before the waiting completes, so do not assign to 'gadget' + * earlier + */ + gadget = epfile->ffs->gadget; + + spin_lock_irq(&epfile->ffs->eps_lock); + /* In the meantime, endpoint got disabled or changed. */ + if (epfile->ep != ep) { + ret = -ESHUTDOWN; + goto error_lock; + } + data_len = iov_iter_count(&io_data->data); + /* + * Controller may require buffer size to be aligned to + * maxpacketsize of an out endpoint. + */ + if (io_data->read) + data_len = usb_ep_align_maybe(gadget, ep->ep, data_len); + + io_data->use_sg = gadget->sg_supported && data_len > PAGE_SIZE; + spin_unlock_irq(&epfile->ffs->eps_lock); + + data = ffs_alloc_buffer(io_data, data_len); + if (!data) { + ret = -ENOMEM; + goto error_mutex; + } + if (!io_data->read && + !copy_from_iter_full(data, data_len, &io_data->data)) { + ret = -EFAULT; + goto error_mutex; + } + } + + spin_lock_irq(&epfile->ffs->eps_lock); + + if (epfile->ep != ep) { + /* In the meantime, endpoint got disabled or changed. */ + ret = -ESHUTDOWN; + } else if (halt) { + ret = usb_ep_set_halt(ep->ep); + if (!ret) + ret = -EBADMSG; + } else if (data_len == -EINVAL) { + /* + * Sanity Check: even though data_len can't be used + * uninitialized at the time I write this comment, some + * compilers complain about this situation. + * In order to keep the code clean from warnings, data_len is + * being initialized to -EINVAL during its declaration, which + * means we can't rely on compiler anymore to warn no future + * changes won't result in data_len being used uninitialized. + * For such reason, we're adding this redundant sanity check + * here. + */ + WARN(1, "%s: data_len == -EINVAL\n", __func__); + ret = -EINVAL; + } else if (!io_data->aio) { + bool interrupted = false; + + req = ep->req; + if (io_data->use_sg) { + req->buf = NULL; + req->sg = io_data->sgt.sgl; + req->num_sgs = io_data->sgt.nents; + } else { + req->buf = data; + req->num_sgs = 0; + } + + req->zero = !io_data->read ? epfile->zlp_enabled : 0; + req->length = data_len; + + io_data->buf = data; + + init_completion(&io_data->done); + req->context = io_data; + req->complete = ffs_epfile_io_complete; + + ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC); + if (ret < 0) + goto error_lock; + + spin_unlock_irq(&epfile->ffs->eps_lock); + + if (wait_for_completion_interruptible(&io_data->done)) { + spin_lock_irq(&epfile->ffs->eps_lock); + if (epfile->ep != ep) { + ret = -ESHUTDOWN; + goto error_lock; + } + /* + * To avoid race condition with ffs_epfile_io_complete, + * dequeue the request first then check + * status. usb_ep_dequeue API should guarantee no race + * condition with req->complete callback. + */ + usb_ep_dequeue(ep->ep, req); + spin_unlock_irq(&epfile->ffs->eps_lock); + wait_for_completion(&io_data->done); + interrupted = io_data->status < 0; + } + + if (interrupted) + ret = -EINTR; + else if (io_data->read && io_data->status > 0) + ret = __ffs_epfile_read_data(epfile, data, io_data->status, + &io_data->data); + else + ret = io_data->status; + goto error_mutex; + } else if (!(req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC))) { + ret = -ENOMEM; + } else { + if (io_data->use_sg) { + req->buf = NULL; + req->sg = io_data->sgt.sgl; + req->num_sgs = io_data->sgt.nents; + } else { + req->buf = data; + req->num_sgs = 0; + } + + req->zero = !io_data->read ? epfile->zlp_enabled : 0; + req->length = data_len; + + io_data->buf = data; + io_data->ep = ep->ep; + io_data->req = req; + io_data->ffs = epfile->ffs; + + req->context = io_data; + req->complete = ffs_epfile_async_io_complete; + + ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC); + if (ret) { + io_data->req = NULL; + usb_ep_free_request(ep->ep, req); + goto error_lock; + } + + ret = -EIOCBQUEUED; + /* + * Do not kfree the buffer in this function. It will be freed + * by ffs_user_copy_worker. + */ + data = NULL; + } + +error_lock: + spin_unlock_irq(&epfile->ffs->eps_lock); +error_mutex: + mutex_unlock(&epfile->mutex); +error: + if (ret != -EIOCBQUEUED) /* don't free if there is iocb queued */ + ffs_free_buffer(io_data); + return ret; +} + +static int +ffs_epfile_open(struct inode *inode, struct file *file) +{ + struct ffs_data *ffs = inode->i_sb->s_fs_info; + struct ffs_epfile *epfile; + + spin_lock_irq(&ffs->eps_lock); + if (!ffs->opened) { + spin_unlock_irq(&ffs->eps_lock); + return -ENODEV; + } + /* + * we want the state to be FFS_ACTIVE; FFS_ACTIVE alone is + * not enough, though - we might have been through FFS_CLOSING + * and back to FFS_ACTIVE, with our file already removed. + */ + epfile = smp_load_acquire(&inode->i_private); + if (unlikely(ffs->state != FFS_ACTIVE || !epfile)) { + spin_unlock_irq(&ffs->eps_lock); + return -ENODEV; + } + ffs->opened++; + spin_unlock_irq(&ffs->eps_lock); + + file->private_data = epfile; + return stream_open(inode, file); +} + +static int ffs_aio_cancel(struct kiocb *kiocb) +{ + struct ffs_io_data *io_data = kiocb->private; + int value; + + if (io_data && io_data->ep && io_data->req) + value = usb_ep_dequeue(io_data->ep, io_data->req); + else + value = -EINVAL; + + return value; +} + +static ssize_t ffs_epfile_write_iter(struct kiocb *kiocb, struct iov_iter *from) +{ + struct ffs_io_data io_data, *p = &io_data; + ssize_t res; + + if (!is_sync_kiocb(kiocb)) { + p = kzalloc_obj(io_data); + if (!p) + return -ENOMEM; + p->aio = true; + } else { + memset(p, 0, sizeof(*p)); + p->aio = false; + } + + p->read = false; + p->kiocb = kiocb; + p->data = *from; + p->mm = current->mm; + + kiocb->private = p; + + if (p->aio) { + mmgrab(p->mm); + kiocb_set_cancel_fn(kiocb, ffs_aio_cancel); + } + + res = ffs_epfile_io(kiocb->ki_filp, p); + if (res == -EIOCBQUEUED) + return res; + if (p->aio) { + kiocb->ki_complete(kiocb, res); + mmdrop(p->mm); + kfree(p); + return -EIOCBQUEUED; + } else { + *from = p->data; + } + return res; +} + +static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to) +{ + struct ffs_io_data io_data, *p = &io_data; + ssize_t res; + + if (!is_sync_kiocb(kiocb)) { + p = kzalloc_obj(io_data); + if (!p) + return -ENOMEM; + p->aio = true; + } else { + memset(p, 0, sizeof(*p)); + p->aio = false; + } + + p->read = true; + p->kiocb = kiocb; + if (p->aio) { + p->to_free = dup_iter(&p->data, to, GFP_KERNEL); + if (!iter_is_ubuf(&p->data) && !p->to_free) { + kfree(p); + return -ENOMEM; + } + } else { + p->data = *to; + p->to_free = NULL; + } + p->mm = current->mm; + + kiocb->private = p; + + if (p->aio) { + mmgrab(p->mm); + kiocb_set_cancel_fn(kiocb, ffs_aio_cancel); + } + + res = ffs_epfile_io(kiocb->ki_filp, p); + if (res == -EIOCBQUEUED) + return res; + + if (p->aio) { + kiocb->ki_complete(kiocb, res); + mmdrop(p->mm); + kfree(p->to_free); + kfree(p); + return -EIOCBQUEUED; + } else { + *to = p->data; + } + return res; +} + +static void ffs_dmabuf_release(struct kref *ref) +{ + struct ffs_dmabuf_priv *priv = container_of(ref, struct ffs_dmabuf_priv, ref); + struct dma_buf_attachment *attach = priv->attach; + struct dma_buf *dmabuf = attach->dmabuf; + + pr_vdebug("FFS DMABUF release\n"); + dma_buf_unmap_attachment_unlocked(attach, priv->sgt, priv->dir); + + dma_buf_detach(attach->dmabuf, attach); + dma_buf_put(dmabuf); + kfree(priv); +} + +static void ffs_dmabuf_get(struct dma_buf_attachment *attach) +{ + struct ffs_dmabuf_priv *priv = attach->importer_priv; + + kref_get(&priv->ref); +} + +static void ffs_dmabuf_put(struct dma_buf_attachment *attach) +{ + struct ffs_dmabuf_priv *priv = attach->importer_priv; + + kref_put(&priv->ref, ffs_dmabuf_release); +} + +static int +ffs_epfile_release(struct inode *inode, struct file *file) +{ + struct ffs_epfile *epfile = file->private_data; + struct ffs_dmabuf_priv *priv, *tmp; + struct ffs_data *ffs = epfile->ffs; + + mutex_lock(&epfile->dmabufs_mutex); + + /* Close all attached DMABUFs */ + list_for_each_entry_safe(priv, tmp, &epfile->dmabufs, entry) { + /* Cancel any pending transfer */ + spin_lock_irq(&ffs->eps_lock); + if (priv->ep && priv->req) + usb_ep_dequeue(priv->ep, priv->req); + spin_unlock_irq(&ffs->eps_lock); + + list_del(&priv->entry); + ffs_dmabuf_put(priv->attach); + } + + mutex_unlock(&epfile->dmabufs_mutex); + + ffs_data_closed(epfile->ffs); + + return 0; +} + +static void ffs_dmabuf_cleanup(struct work_struct *work) +{ + struct ffs_dma_fence *dma_fence = + container_of(work, struct ffs_dma_fence, work); + struct ffs_dmabuf_priv *priv = dma_fence->priv; + struct dma_buf_attachment *attach = priv->attach; + struct dma_fence *fence = &dma_fence->base; + struct usb_request *req = dma_fence->req; + struct usb_ep *ep = dma_fence->ep; + + /* + * eps_lock pairs with the cancel paths so they cannot pass a freed + * req to usb_ep_dequeue(). Only clear if priv->req still names ours; + * a re-queue on the same attachment may have taken that slot. + */ + spin_lock_irq(&priv->ffs->eps_lock); + if (priv->req == req) + priv->req = NULL; + spin_unlock_irq(&priv->ffs->eps_lock); + + if (ep && req) + usb_ep_free_request(ep, req); + + ffs_dmabuf_put(attach); + dma_fence_put(fence); +} + +static void ffs_dmabuf_signal_done(struct ffs_dma_fence *dma_fence, int ret) +{ + struct ffs_dmabuf_priv *priv = dma_fence->priv; + struct dma_fence *fence = &dma_fence->base; + bool cookie = dma_fence_begin_signalling(); + + dma_fence_get(fence); + fence->error = ret; + dma_fence_signal(fence); + dma_fence_end_signalling(cookie); + + /* + * The fence will be unref'd in ffs_dmabuf_cleanup. + * It can't be done here, as the unref functions might try to lock + * the resv object, which would deadlock. + */ + INIT_WORK(&dma_fence->work, ffs_dmabuf_cleanup); + queue_work(priv->ffs->io_completion_wq, &dma_fence->work); +} + +static void ffs_epfile_dmabuf_io_complete(struct usb_ep *ep, + struct usb_request *req) +{ + pr_vdebug("FFS: DMABUF transfer complete, status=%d\n", req->status); + /* req is freed by ffs_dmabuf_cleanup() under eps_lock. */ + ffs_dmabuf_signal_done(req->context, req->status); +} + +static const char *ffs_dmabuf_get_driver_name(struct dma_fence *fence) +{ + return "functionfs"; +} + +static const char *ffs_dmabuf_get_timeline_name(struct dma_fence *fence) +{ + return ""; +} + +static void ffs_dmabuf_fence_release(struct dma_fence *fence) +{ + struct ffs_dma_fence *dma_fence = + container_of(fence, struct ffs_dma_fence, base); + + kfree(dma_fence); +} + +static const struct dma_fence_ops ffs_dmabuf_fence_ops = { + .get_driver_name = ffs_dmabuf_get_driver_name, + .get_timeline_name = ffs_dmabuf_get_timeline_name, + .release = ffs_dmabuf_fence_release, +}; + +static int ffs_dma_resv_lock(struct dma_buf *dmabuf, bool nonblock) +{ + if (!nonblock) + return dma_resv_lock_interruptible(dmabuf->resv, NULL); + + if (!dma_resv_trylock(dmabuf->resv)) + return -EBUSY; + + return 0; +} + +static struct dma_buf_attachment * +ffs_dmabuf_find_attachment(struct ffs_epfile *epfile, struct dma_buf *dmabuf) +{ + struct device *dev = epfile->ffs->gadget->dev.parent; + struct dma_buf_attachment *attach = NULL; + struct ffs_dmabuf_priv *priv; + + mutex_lock(&epfile->dmabufs_mutex); + + list_for_each_entry(priv, &epfile->dmabufs, entry) { + if (priv->attach->dev == dev + && priv->attach->dmabuf == dmabuf) { + attach = priv->attach; + break; + } + } + + if (attach) + ffs_dmabuf_get(attach); + + mutex_unlock(&epfile->dmabufs_mutex); + + return attach ?: ERR_PTR(-EPERM); +} + +static int ffs_dmabuf_attach(struct file *file, int fd) +{ + bool nonblock = file->f_flags & O_NONBLOCK; + struct ffs_epfile *epfile = file->private_data; + struct usb_gadget *gadget = epfile->ffs->gadget; + struct dma_buf_attachment *attach; + struct ffs_dmabuf_priv *priv; + enum dma_data_direction dir; + struct sg_table *sg_table; + struct dma_buf *dmabuf; + int err; + + if (!gadget || !gadget->sg_supported) + return -EPERM; + + dmabuf = dma_buf_get(fd); + if (IS_ERR(dmabuf)) + return PTR_ERR(dmabuf); + + attach = dma_buf_attach(dmabuf, gadget->dev.parent); + if (IS_ERR(attach)) { + err = PTR_ERR(attach); + goto err_dmabuf_put; + } + + priv = kzalloc_obj(*priv); + if (!priv) { + err = -ENOMEM; + goto err_dmabuf_detach; + } + + dir = epfile->in ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + + err = ffs_dma_resv_lock(dmabuf, nonblock); + if (err) + goto err_free_priv; + + sg_table = dma_buf_map_attachment(attach, dir); + dma_resv_unlock(dmabuf->resv); + + if (IS_ERR(sg_table)) { + err = PTR_ERR(sg_table); + goto err_free_priv; + } + + attach->importer_priv = priv; + + priv->sgt = sg_table; + priv->dir = dir; + priv->ffs = epfile->ffs; + priv->attach = attach; + spin_lock_init(&priv->lock); + kref_init(&priv->ref); + priv->context = dma_fence_context_alloc(1); + + mutex_lock(&epfile->dmabufs_mutex); + list_add(&priv->entry, &epfile->dmabufs); + mutex_unlock(&epfile->dmabufs_mutex); + + return 0; + +err_free_priv: + kfree(priv); +err_dmabuf_detach: + dma_buf_detach(dmabuf, attach); +err_dmabuf_put: + dma_buf_put(dmabuf); + + return err; +} + +static int ffs_dmabuf_detach(struct file *file, int fd) +{ + struct ffs_epfile *epfile = file->private_data; + struct ffs_data *ffs = epfile->ffs; + struct device *dev = ffs->gadget->dev.parent; + struct ffs_dmabuf_priv *priv, *tmp; + struct dma_buf *dmabuf; + int ret = -EPERM; + + dmabuf = dma_buf_get(fd); + if (IS_ERR(dmabuf)) + return PTR_ERR(dmabuf); + + mutex_lock(&epfile->dmabufs_mutex); + + list_for_each_entry_safe(priv, tmp, &epfile->dmabufs, entry) { + if (priv->attach->dev == dev + && priv->attach->dmabuf == dmabuf) { + /* Cancel any pending transfer */ + spin_lock_irq(&ffs->eps_lock); + if (priv->ep && priv->req) + usb_ep_dequeue(priv->ep, priv->req); + spin_unlock_irq(&ffs->eps_lock); + + list_del(&priv->entry); + + /* Unref the reference from ffs_dmabuf_attach() */ + ffs_dmabuf_put(priv->attach); + ret = 0; + break; + } + } + + mutex_unlock(&epfile->dmabufs_mutex); + dma_buf_put(dmabuf); + + return ret; +} + +static int ffs_dmabuf_transfer(struct file *file, + const struct usb_ffs_dmabuf_transfer_req *req) +{ + bool nonblock = file->f_flags & O_NONBLOCK; + struct ffs_epfile *epfile = file->private_data; + struct dma_buf_attachment *attach; + struct ffs_dmabuf_priv *priv; + struct ffs_dma_fence *fence; + struct usb_request *usb_req; + enum dma_resv_usage resv_dir; + struct dma_buf *dmabuf; + unsigned long timeout; + struct ffs_ep *ep; + bool cookie; + u32 seqno; + long retl; + int ret; + + if (req->flags & ~USB_FFS_DMABUF_TRANSFER_MASK) + return -EINVAL; + + dmabuf = dma_buf_get(req->fd); + if (IS_ERR(dmabuf)) + return PTR_ERR(dmabuf); + + if (req->length > dmabuf->size || req->length == 0) { + ret = -EINVAL; + goto err_dmabuf_put; + } + + attach = ffs_dmabuf_find_attachment(epfile, dmabuf); + if (IS_ERR(attach)) { + ret = PTR_ERR(attach); + goto err_dmabuf_put; + } + + priv = attach->importer_priv; + + ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) { + ret = PTR_ERR(ep); + goto err_attachment_put; + } + + ret = ffs_dma_resv_lock(dmabuf, nonblock); + if (ret) + goto err_attachment_put; + + /* Make sure we don't have writers */ + timeout = nonblock ? 0 : msecs_to_jiffies(DMABUF_ENQUEUE_TIMEOUT_MS); + retl = dma_resv_wait_timeout(dmabuf->resv, + dma_resv_usage_rw(!epfile->in), + true, timeout); + if (retl == 0) + retl = -EBUSY; + if (retl < 0) { + ret = (int)retl; + goto err_resv_unlock; + } + + ret = dma_resv_reserve_fences(dmabuf->resv, 1); + if (ret) + goto err_resv_unlock; + + fence = kmalloc_obj(*fence); + if (!fence) { + ret = -ENOMEM; + goto err_resv_unlock; + } + + fence->priv = priv; + + spin_lock_irq(&epfile->ffs->eps_lock); + + /* In the meantime, endpoint got disabled or changed. */ + if (epfile->ep != ep) { + ret = -ESHUTDOWN; + goto err_fence_free; + } + + usb_req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC); + if (!usb_req) { + ret = -ENOMEM; + goto err_fence_free; + } + + /* + * usb_ep_queue() guarantees that all transfers are processed in the + * order they are enqueued, so we can use a simple incrementing + * sequence number for the dma_fence. + */ + seqno = atomic_add_return(1, &epfile->seqno); + + dma_fence_init(&fence->base, &ffs_dmabuf_fence_ops, + &priv->lock, priv->context, seqno); + + resv_dir = epfile->in ? DMA_RESV_USAGE_READ : DMA_RESV_USAGE_WRITE; + + dma_resv_add_fence(dmabuf->resv, &fence->base, resv_dir); + dma_fence_put(&fence->base); + dma_resv_unlock(dmabuf->resv); + + /* Now that the dma_fence is in place, queue the transfer. */ + + usb_req->zero = epfile->zlp_enabled; + usb_req->length = req->length; + usb_req->buf = NULL; + usb_req->sg = priv->sgt->sgl; + usb_req->num_sgs = sg_nents_for_len(priv->sgt->sgl, req->length); + usb_req->sg_was_mapped = true; + usb_req->context = fence; + usb_req->complete = ffs_epfile_dmabuf_io_complete; + + /* ffs_dmabuf_cleanup() frees usb_req via these two fields. */ + fence->req = usb_req; + fence->ep = ep->ep; + + cookie = dma_fence_begin_signalling(); + ret = usb_ep_queue(ep->ep, usb_req, GFP_ATOMIC); + dma_fence_end_signalling(cookie); + if (!ret) { + priv->req = usb_req; + priv->ep = ep->ep; + } else { + pr_warn("FFS: Failed to queue DMABUF: %d\n", ret); + ffs_dmabuf_signal_done(fence, ret); + } + + spin_unlock_irq(&epfile->ffs->eps_lock); + dma_buf_put(dmabuf); + + return ret; + +err_fence_free: + spin_unlock_irq(&epfile->ffs->eps_lock); + kfree(fence); +err_resv_unlock: + dma_resv_unlock(dmabuf->resv); +err_attachment_put: + ffs_dmabuf_put(attach); +err_dmabuf_put: + dma_buf_put(dmabuf); + + return ret; +} + +static long ffs_epfile_ioctl(struct file *file, unsigned code, + unsigned long value) +{ + struct ffs_epfile *epfile = file->private_data; + struct ffs_ep *ep; + int ret; + __u32 enable_zlp = 0; + + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + + if (epfile->is_rw_proxy) + return -ENOTTY; + + switch (code) { + case FUNCTIONFS_DMABUF_ATTACH: + { + int fd; + + if (copy_from_user(&fd, (void __user *)value, sizeof(fd))) + return -EFAULT; + + return ffs_dmabuf_attach(file, fd); + } + case FUNCTIONFS_DMABUF_DETACH: + { + int fd; + + if (copy_from_user(&fd, (void __user *)value, sizeof(fd))) + return -EFAULT; + + return ffs_dmabuf_detach(file, fd); + } + case FUNCTIONFS_DMABUF_TRANSFER: + { + struct usb_ffs_dmabuf_transfer_req req; + + if (copy_from_user(&req, (void __user *)value, sizeof(req))) + return -EFAULT; + + return ffs_dmabuf_transfer(file, &req); + } + /* + * We handle this IOCTL before ffs_epfile_wait_ep() to allow userspace + * to configure ZLP behavior immediately without blocking indefinitely + * while waiting for the USB host to connect and enable the endpoint. + */ + case FUNCTIONFS_ENDPOINT_ENABLE_ZLP: + if (!epfile->in) + return -EINVAL; + + if (copy_from_user(&enable_zlp, (void __user *)value, sizeof(enable_zlp))) + return -EFAULT; + + spin_lock_irq(&epfile->ffs->eps_lock); + epfile->zlp_enabled = !!enable_zlp; + spin_unlock_irq(&epfile->ffs->eps_lock); + + return 0; + default: + break; + } + + /* Wait for endpoint to be enabled */ + ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) + return PTR_ERR(ep); + + spin_lock_irq(&epfile->ffs->eps_lock); + + /* In the meantime, endpoint got disabled or changed. */ + if (epfile->ep != ep) { + spin_unlock_irq(&epfile->ffs->eps_lock); + return -ESHUTDOWN; + } + + 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; + case FUNCTIONFS_ENDPOINT_DESC: + { + int desc_idx; + struct usb_endpoint_descriptor desc1, *desc; + + switch (epfile->ffs->gadget->speed) { + case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + desc_idx = 2; + break; + case USB_SPEED_HIGH: + desc_idx = 1; + break; + default: + desc_idx = 0; + } + + desc = epfile->ep->descs[desc_idx]; + memcpy(&desc1, desc, desc->bLength); + + spin_unlock_irq(&epfile->ffs->eps_lock); + ret = copy_to_user((void __user *)value, &desc1, desc1.bLength); + if (ret) + ret = -EFAULT; + return ret; + } + default: + ret = -ENOTTY; + } + spin_unlock_irq(&epfile->ffs->eps_lock); + + return ret; +} + +static const struct file_operations ffs_epfile_operations = { + + .open = ffs_epfile_open, + .write_iter = ffs_epfile_write_iter, + .read_iter = ffs_epfile_read_iter, + .release = ffs_epfile_release, + .unlocked_ioctl = ffs_epfile_ioctl, + .compat_ioctl = compat_ptr_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; + + inode = new_inode(sb); + + if (inode) { + struct timespec64 ts = inode_set_ctime_current(inode); + + inode->i_ino = get_next_ino(); + inode->i_mode = perms->mode; + inode->i_uid = perms->uid; + inode->i_gid = perms->gid; + inode_set_atime_to_ts(inode, ts); + inode_set_mtime_to_ts(inode, ts); + inode->i_private = data; + if (fops) + inode->i_fop = fops; + if (iops) + inode->i_op = iops; + } + + return inode; +} + +/* Create "regular" file */ +static int ffs_sb_create_file(struct super_block *sb, const char *name, + void *data, const struct file_operations *fops) +{ + struct ffs_data *ffs = sb->s_fs_info; + struct dentry *dentry; + struct inode *inode; + + inode = ffs_sb_make_inode(sb, data, fops, NULL, &ffs->file_perms); + if (!inode) + return -ENOMEM; + dentry = simple_start_creating(sb->s_root, name); + if (IS_ERR(dentry)) { + iput(inode); + return PTR_ERR(dentry); + } + + d_make_persistent(dentry, inode); + + simple_done_creating(dentry); + return 0; +} + +/* Super block */ +static const struct super_operations ffs_sb_operations = { + .statfs = simple_statfs, + .drop_inode = inode_just_drop, +}; + +struct ffs_sb_fill_data { + struct ffs_file_perms perms; + umode_t root_mode; + const char *dev_name; + bool no_disconnect; + struct ffs_data *ffs_data; +}; + +static int ffs_sb_fill(struct super_block *sb, struct fs_context *fc) +{ + struct ffs_sb_fill_data *data = fc->fs_private; + struct inode *inode; + struct ffs_data *ffs = data->ffs_data; + + ffs->sb = sb; + data->ffs_data = NULL; + sb->s_fs_info = ffs; + sb->s_blocksize = PAGE_SIZE; + sb->s_blocksize_bits = PAGE_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 (!sb->s_root) + return -ENOMEM; + + /* EP0 file */ + return ffs_sb_create_file(sb, "ep0", ffs, &ffs_ep0_operations); +} + +enum { + Opt_no_disconnect, + Opt_rmode, + Opt_fmode, + Opt_mode, + Opt_uid, + Opt_gid, +}; + +static const struct fs_parameter_spec ffs_fs_fs_parameters[] = { + fsparam_bool ("no_disconnect", Opt_no_disconnect), + fsparam_u32 ("rmode", Opt_rmode), + fsparam_u32 ("fmode", Opt_fmode), + fsparam_u32 ("mode", Opt_mode), + fsparam_u32 ("uid", Opt_uid), + fsparam_u32 ("gid", Opt_gid), + {} +}; + +static int ffs_fs_parse_param(struct fs_context *fc, struct fs_parameter *param) +{ + struct ffs_sb_fill_data *data = fc->fs_private; + struct fs_parse_result result; + int opt; + + opt = fs_parse(fc, ffs_fs_fs_parameters, param, &result); + if (opt < 0) + return opt; + + switch (opt) { + case Opt_no_disconnect: + data->no_disconnect = result.boolean; + break; + case Opt_rmode: + data->root_mode = (result.uint_32 & 0555) | S_IFDIR; + break; + case Opt_fmode: + data->perms.mode = (result.uint_32 & 0666) | S_IFREG; + break; + case Opt_mode: + data->root_mode = (result.uint_32 & 0555) | S_IFDIR; + data->perms.mode = (result.uint_32 & 0666) | S_IFREG; + break; + + case Opt_uid: + data->perms.uid = make_kuid(current_user_ns(), result.uint_32); + if (!uid_valid(data->perms.uid)) + goto unmapped_value; + break; + case Opt_gid: + data->perms.gid = make_kgid(current_user_ns(), result.uint_32); + if (!gid_valid(data->perms.gid)) + goto unmapped_value; + break; + + default: + return -ENOPARAM; + } + + return 0; + +unmapped_value: + return invalf(fc, "%s: unmapped value: %u", param->key, result.uint_32); +} + +/* + * Set up the superblock for a mount. + */ +static int ffs_fs_get_tree(struct fs_context *fc) +{ + struct ffs_sb_fill_data *ctx = fc->fs_private; + struct ffs_data *ffs; + int ret; + + if (!fc->source) + return invalf(fc, "No source specified"); + + ffs = ffs_data_new(fc->source); + if (!ffs) + return -ENOMEM; + ffs->file_perms = ctx->perms; + ffs->no_disconnect = ctx->no_disconnect; + + ffs->dev_name = kstrdup(fc->source, GFP_KERNEL); + if (!ffs->dev_name) { + ffs_data_put(ffs); + return -ENOMEM; + } + + ret = ffs_acquire_dev(ffs->dev_name, ffs); + if (ret) { + ffs_data_put(ffs); + return ret; + } + + ctx->ffs_data = ffs; + return get_tree_nodev(fc, ffs_sb_fill); +} + +static void ffs_fs_free_fc(struct fs_context *fc) +{ + struct ffs_sb_fill_data *ctx = fc->fs_private; + + if (ctx) { + if (ctx->ffs_data) { + ffs_data_put(ctx->ffs_data); + } + + kfree(ctx); + } +} + +static const struct fs_context_operations ffs_fs_context_ops = { + .free = ffs_fs_free_fc, + .parse_param = ffs_fs_parse_param, + .get_tree = ffs_fs_get_tree, +}; + +static int ffs_fs_init_fs_context(struct fs_context *fc) +{ + struct ffs_sb_fill_data *ctx; + + ctx = kzalloc_obj(struct ffs_sb_fill_data); + if (!ctx) + return -ENOMEM; + + ctx->perms.mode = S_IFREG | 0600; + ctx->perms.uid = GLOBAL_ROOT_UID; + ctx->perms.gid = GLOBAL_ROOT_GID; + ctx->root_mode = S_IFDIR | 0500; + ctx->no_disconnect = false; + + fc->fs_private = ctx; + fc->ops = &ffs_fs_context_ops; + return 0; +} + +static void +ffs_fs_kill_sb(struct super_block *sb) +{ + kill_anon_super(sb); + if (sb->s_fs_info) { + struct ffs_data *ffs = sb->s_fs_info; + ffs->state = FFS_CLOSING; + ffs_data_reset(ffs); + // no configfs accesses from that point on, + // so no further schedule_work() is possible + cancel_work_sync(&ffs->reset_work); + ffs_data_put(ffs); + } +} + +static struct file_system_type ffs_fs_type = { + .owner = THIS_MODULE, + .name = "functionfs", + .init_fs_context = ffs_fs_init_fs_context, + .parameters = ffs_fs_fs_parameters, + .kill_sb = ffs_fs_kill_sb, +}; +MODULE_ALIAS_FS("functionfs"); + + +/* Driver's main init/cleanup functions *************************************/ + +static int functionfs_init(void) +{ + int ret; + + ret = register_filesystem(&ffs_fs_type); + if (!ret) + pr_info("file system registered\n"); + else + pr_err("failed registering file system (%d)\n", ret); + + return ret; +} + +static void functionfs_cleanup(void) +{ + 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_get(struct ffs_data *ffs) +{ + refcount_inc(&ffs->ref); +} + +static void ffs_data_put(struct ffs_data *ffs) +{ + if (refcount_dec_and_test(&ffs->ref)) { + pr_info("%s(): freeing\n", __func__); + ffs_data_clear(ffs); + ffs_release_dev(ffs->private_data); + BUG_ON(waitqueue_active(&ffs->ev.waitq) || + swait_active(&ffs->ep0req_completion.wait) || + waitqueue_active(&ffs->wait)); + destroy_workqueue(ffs->io_completion_wq); + kfree(ffs->dev_name); + kfree(ffs); + } +} + +static void ffs_data_closed(struct ffs_data *ffs) +{ + spin_lock_irq(&ffs->eps_lock); + if (--ffs->opened) { // not the last opener? + spin_unlock_irq(&ffs->eps_lock); + return; + } + if (ffs->no_disconnect) { + struct ffs_epfile *epfiles; + + ffs->state = FFS_DEACTIVATED; + epfiles = ffs->epfiles; + ffs->epfiles = NULL; + spin_unlock_irq(&ffs->eps_lock); + + if (epfiles) + ffs_epfiles_destroy(ffs->sb, epfiles, + ffs->epfiles_count); + + if (ffs->setup_state == FFS_SETUP_PENDING) + __ffs_ep0_stall(ffs); + } else { + ffs->state = FFS_CLOSING; + spin_unlock_irq(&ffs->eps_lock); + ffs_data_reset(ffs); + } +} + +static struct ffs_data *ffs_data_new(const char *dev_name) +{ + struct ffs_data *ffs = kzalloc_obj(*ffs); + if (!ffs) + return NULL; + + ffs->io_completion_wq = alloc_ordered_workqueue("%s", 0, dev_name); + if (!ffs->io_completion_wq) { + kfree(ffs); + return NULL; + } + + refcount_set(&ffs->ref, 1); + 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_waitqueue_head(&ffs->wait); + init_completion(&ffs->ep0req_completion); + INIT_WORK(&ffs->reset_work, ffs_reset_work); + + /* 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) +{ + struct ffs_epfile *epfiles; + unsigned long flags; + + ffs_closed(ffs); + + BUG_ON(ffs->gadget); + + spin_lock_irqsave(&ffs->eps_lock, flags); + epfiles = ffs->epfiles; + ffs->epfiles = NULL; + spin_unlock_irqrestore(&ffs->eps_lock, flags); + + /* + * potential race possible between ffs_func_eps_disable + * & ffs_epfile_release therefore maintaining a local + * copy of epfile will save us from use-after-free. + */ + if (epfiles) { + ffs_epfiles_destroy(ffs->sb, epfiles, ffs->epfiles_count); + ffs->epfiles = NULL; + } + + if (ffs->ffs_eventfd) { + eventfd_ctx_put(ffs->ffs_eventfd); + ffs->ffs_eventfd = NULL; + } + + kfree(ffs->raw_descs_data); + kfree(ffs->raw_strings); + kfree(ffs->stringtabs); +} + +static void ffs_data_reset(struct ffs_data *ffs) +{ + ffs_data_clear(ffs); + + spin_lock_irq(&ffs->eps_lock); + ffs->raw_descs_data = NULL; + ffs->raw_descs = NULL; + ffs->raw_strings = NULL; + ffs->stringtabs = NULL; + + ffs->raw_descs_length = 0; + ffs->fs_descs_count = 0; + ffs->hs_descs_count = 0; + ffs->ss_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; + + ffs->ms_os_descs_ext_prop_count = 0; + ffs->ms_os_descs_ext_prop_name_len = 0; + ffs->ms_os_descs_ext_prop_data_len = 0; + spin_unlock_irq(&ffs->eps_lock); +} + + +static int functionfs_bind(struct ffs_data *ffs, struct usb_composite_dev *cdev) +{ + struct usb_gadget_strings **lang; + int first_id; + + if ((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 (first_id < 0) + return first_id; + + ffs->ep0req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL); + if (!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) +{ + if (!WARN_ON(!ffs->gadget)) { + /* dequeue before freeing ep0req */ + usb_ep_dequeue(ffs->gadget->ep0, ffs->ep0req); + mutex_lock(&ffs->mutex); + usb_ep_free_request(ffs->gadget->ep0, ffs->ep0req); + ffs->ep0req = NULL; + ffs->gadget = NULL; + clear_bit(FFS_FL_BOUND, &ffs->flags); + mutex_unlock(&ffs->mutex); + ffs_data_put(ffs); + } +} + +static int ffs_epfiles_create(struct ffs_data *ffs) +{ + struct ffs_epfile *epfile, *epfiles; + unsigned int i, count, epfiles_count; + int err; + + count = ffs->eps_count; + epfiles_count = count; + if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) + epfiles_count += count / 2; + ffs->epfiles_count = epfiles_count; + + epfiles = kzalloc_objs(*epfiles, epfiles_count); + if (!epfiles) + return -ENOMEM; + + epfile = epfiles; + for (i = 1; i <= count; ++i, ++epfile) { + epfile->ffs = ffs; + mutex_init(&epfile->mutex); + mutex_init(&epfile->dmabufs_mutex); + INIT_LIST_HEAD(&epfile->dmabufs); + if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR) + sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]); + else + sprintf(epfile->name, "ep%u", i); + epfile->in = (ffs->eps_addrmap[i] & USB_ENDPOINT_DIR_MASK) ? 1 : 0; + err = ffs_sb_create_file(ffs->sb, epfile->name, + epfile, &ffs_epfile_operations); + if (err) { + ffs_epfiles_destroy(ffs->sb, epfiles, i - 1); + return err; + } + } + + if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { + struct ffs_epfile *comp = epfiles + count; + + for (i = 0; i < count; i += 2, ++comp) { + struct ffs_epfile *ep1 = &epfiles[i]; + struct ffs_epfile *ep2 = &epfiles[i + 1]; + bool ep1_in = ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK; + + comp->ffs = ffs; + comp->is_rw_proxy = true; + comp->epfile_in = ep1_in ? ep1 : ep2; + comp->epfile_out = ep1_in ? ep2 : ep1; + mutex_init(&comp->mutex); + mutex_init(&comp->dmabufs_mutex); + INIT_LIST_HEAD(&comp->dmabufs); + snprintf(comp->name, sizeof(comp->name), "%s_rw", + epfiles[i].name); + err = ffs_sb_create_file(ffs->sb, comp->name, + comp, &ffs_epfile_operations); + if (err) { + ffs_epfiles_destroy(ffs->sb, epfiles, count + (i / 2)); + return err; + } + } + } + + ffs->epfiles = epfiles; + return 0; +} + +static void clear_one(struct dentry *dentry) +{ + smp_store_release(&dentry->d_inode->i_private, NULL); +} + +static void ffs_epfiles_destroy(struct super_block *sb, + struct ffs_epfile *epfiles, unsigned count) +{ + struct ffs_epfile *epfile = epfiles; + struct dentry *root = sb->s_root; + + for (; count; --count, ++epfile) { + BUG_ON(mutex_is_locked(&epfile->mutex)); + __ffs_epfile_read_buffer_free(epfile); + simple_remove_by_name(root, epfile->name, clear_one); + } + + kfree(epfiles); +} + +static void ffs_func_eps_disable(struct ffs_function *func) +{ + struct ffs_ep *ep; + struct ffs_epfile *epfile; + unsigned short count; + unsigned long flags; + + spin_lock_irqsave(&func->ffs->eps_lock, flags); + count = func->ffs->eps_count; + epfile = func->ffs->epfiles; + ep = func->eps; + while (count--) { + /* pending requests get nuked */ + if (ep->ep) + usb_ep_disable(ep->ep); + ++ep; + + if (epfile) { + epfile->ep = NULL; + __ffs_epfile_read_buffer_free(epfile); + ++epfile; + } + } + spin_unlock_irqrestore(&func->ffs->eps_lock, flags); +} + +static int ffs_func_eps_enable(struct ffs_function *func) +{ + struct ffs_data *ffs; + struct ffs_ep *ep; + struct ffs_epfile *epfile; + unsigned short count; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&func->ffs->eps_lock, flags); + ffs = func->ffs; + ep = func->eps; + epfile = ffs->epfiles; + count = ffs->eps_count; + if (!epfile) { + ret = -ENOMEM; + goto done; + } + + while (count--) { + ep->ep->driver_data = ep; + + ret = config_ep_by_speed(func->gadget, &func->function, ep->ep); + if (ret) { + pr_err("%s: config_ep_by_speed(%s) returned %d\n", + __func__, ep->ep->name, ret); + break; + } + + ret = usb_ep_enable(ep->ep); + if (!ret) { + epfile->ep = ep; + epfile->isoc = usb_endpoint_xfer_isoc(ep->ep->desc); + } else { + break; + } + + ++ep; + ++epfile; + } + + wake_up_interruptible(&ffs->wait); +done: + 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 +}; + +enum ffs_os_desc_type { + FFS_OS_DESC, FFS_OS_DESC_EXT_COMPAT, FFS_OS_DESC_EXT_PROP +}; + +typedef int (*ffs_entity_callback)(enum ffs_entity_type entity, + u8 *valuep, + struct usb_descriptor_header *desc, + void *priv); + +typedef int (*ffs_os_desc_callback)(enum ffs_os_desc_type entity, + struct usb_os_desc_header *h, void *data, + unsigned len, void *priv); + +static int __must_check ffs_do_single_desc(char *data, unsigned len, + ffs_entity_callback entity, + void *priv, int *current_class, int *current_subclass) +{ + struct usb_descriptor_header *_ds = (void *)data; + u8 length; + int ret; + + /* 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 (!__entity_check_ ##type(val)) { \ + pr_vdebug("invalid entity's value\n"); \ + return -EINVAL; \ + } \ + ret = entity(FFS_ ##type, &val, _ds, priv); \ + if (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); + *current_class = ds->bInterfaceClass; + *current_subclass = ds->bInterfaceSubClass; + } + 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 USB_TYPE_CLASS | 0x01: + if (*current_class == USB_INTERFACE_CLASS_HID) { + pr_vdebug("hid descriptor\n"); + if (length != sizeof(struct hid_descriptor)) + goto inv_length; + break; + } else if (*current_class == USB_INTERFACE_CLASS_CCID) { + pr_vdebug("ccid descriptor\n"); + if (length != sizeof(struct ccid_descriptor)) + goto inv_length; + break; + } else if (*current_class == USB_CLASS_APP_SPEC && + *current_subclass == USB_SUBCLASS_DFU) { + pr_vdebug("dfu functional descriptor\n"); + if (length != sizeof(struct usb_dfu_functional_descriptor)) + goto inv_length; + break; + } else { + pr_vdebug("unknown descriptor: %d for class %d\n", + _ds->bDescriptorType, *current_class); + return -EINVAL; + } + + 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_SS_ENDPOINT_COMP: + pr_vdebug("EP SS companion descriptor\n"); + if (length != sizeof(struct usb_ss_ep_comp_descriptor)) + goto inv_length; + 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; + int current_class = -1; + int current_subclass = -1; + + for (;;) { + int ret; + + if (num == count) + data = NULL; + + /* Record "descriptor" entity */ + ret = entity(FFS_DESCRIPTOR, (u8 *)num, (void *)data, priv); + if (ret < 0) { + pr_debug("entity DESCRIPTOR(%02lx); ret = %d\n", + num, ret); + return ret; + } + + if (!data) + return _len - len; + + ret = ffs_do_single_desc(data, len, entity, priv, + ¤t_class, ¤t_subclass); + if (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_desc_helper *helper = priv; + struct usb_endpoint_descriptor *d; + + 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 >= helper->interfaces_count) + helper->interfaces_count = *valuep + 1; + break; + + case FFS_STRING: + /* + * Strings are indexed from 1 (0 is reserved + * for languages list) + */ + if (*valuep > helper->ffs->strings_count) + helper->ffs->strings_count = *valuep; + break; + + case FFS_ENDPOINT: + d = (void *)desc; + helper->eps_count++; + if (helper->eps_count >= FFS_MAX_EPS_COUNT) + return -EINVAL; + /* Check if descriptors for any speed were already parsed */ + if (!helper->ffs->eps_count && !helper->ffs->interfaces_count) + helper->ffs->eps_addrmap[helper->eps_count] = + d->bEndpointAddress; + else if (helper->ffs->eps_addrmap[helper->eps_count] != + d->bEndpointAddress) + return -EINVAL; + break; + } + + return 0; +} + +static int __ffs_do_os_desc_header(enum ffs_os_desc_type *next_type, + struct usb_os_desc_header *desc) +{ + u16 bcd_version = le16_to_cpu(desc->bcdVersion); + u16 w_index = le16_to_cpu(desc->wIndex); + + if (bcd_version == 0x1) { + pr_warn("bcdVersion must be 0x0100, stored in Little Endian order. " + "Userspace driver should be fixed, accepting 0x0001 for compatibility.\n"); + } else if (bcd_version != 0x100) { + pr_vdebug("unsupported os descriptors version: 0x%x\n", + bcd_version); + return -EINVAL; + } + switch (w_index) { + case 0x4: + *next_type = FFS_OS_DESC_EXT_COMPAT; + break; + case 0x5: + *next_type = FFS_OS_DESC_EXT_PROP; + break; + default: + pr_vdebug("unsupported os descriptor type: %d", w_index); + return -EINVAL; + } + + return sizeof(*desc); +} + +/* + * Process all extended compatibility/extended property descriptors + * of a feature descriptor + */ +static int __must_check ffs_do_single_os_desc(char *data, unsigned len, + enum ffs_os_desc_type type, + u16 feature_count, + ffs_os_desc_callback entity, + void *priv, + struct usb_os_desc_header *h) +{ + int ret; + const unsigned _len = len; + + /* loop over all ext compat/ext prop descriptors */ + while (feature_count--) { + ret = entity(type, h, data, len, priv); + if (ret < 0) { + pr_debug("bad OS descriptor, type: %d\n", type); + return ret; + } + data += ret; + len -= ret; + } + return _len - len; +} + +/* Process a number of complete Feature Descriptors (Ext Compat or Ext Prop) */ +static int __must_check ffs_do_os_descs(unsigned count, + char *data, unsigned len, + ffs_os_desc_callback entity, void *priv) +{ + const unsigned _len = len; + unsigned long num = 0; + + for (num = 0; num < count; ++num) { + int ret; + enum ffs_os_desc_type type; + u16 feature_count; + struct usb_os_desc_header *desc = (void *)data; + + if (len < sizeof(*desc)) + return -EINVAL; + + /* + * Record "descriptor" entity. + * Process dwLength, bcdVersion, wIndex, get b/wCount. + * Move the data pointer to the beginning of extended + * compatibilities proper or extended properties proper + * portions of the data + */ + if (le32_to_cpu(desc->dwLength) > len) + return -EINVAL; + + ret = __ffs_do_os_desc_header(&type, desc); + if (ret < 0) { + pr_debug("entity OS_DESCRIPTOR(%02lx); ret = %d\n", + num, ret); + return ret; + } + /* + * 16-bit hex "?? 00" Little Endian looks like 8-bit hex "??" + */ + feature_count = le16_to_cpu(desc->wCount); + if (type == FFS_OS_DESC_EXT_COMPAT && + (feature_count > 255 || desc->Reserved)) + return -EINVAL; + len -= ret; + data += ret; + + /* + * Process all function/property descriptors + * of this Feature Descriptor + */ + ret = ffs_do_single_os_desc(data, len, type, + feature_count, entity, priv, desc); + if (ret < 0) { + pr_debug("%s returns %d\n", __func__, ret); + return ret; + } + + len -= ret; + data += ret; + } + return _len - len; +} + +/* + * Validate contents of the buffer from userspace related to OS descriptors. + */ +static int __ffs_data_do_os_desc(enum ffs_os_desc_type type, + struct usb_os_desc_header *h, void *data, + unsigned len, void *priv) +{ + struct ffs_data *ffs = priv; + u8 length; + + switch (type) { + case FFS_OS_DESC_EXT_COMPAT: { + struct usb_ext_compat_desc *d = data; + int i; + + if (len < sizeof(*d) || + d->bFirstInterfaceNumber >= ffs->interfaces_count) + return -EINVAL; + if (d->Reserved1 != 1) { + /* + * According to the spec, Reserved1 must be set to 1 + * but older kernels incorrectly rejected non-zero + * values. We fix it here to avoid returning EINVAL + * in response to values we used to accept. + */ + pr_debug("usb_ext_compat_desc::Reserved1 forced to 1\n"); + d->Reserved1 = 1; + } + for (i = 0; i < ARRAY_SIZE(d->Reserved2); ++i) + if (d->Reserved2[i]) + return -EINVAL; + + length = sizeof(struct usb_ext_compat_desc); + } + break; + case FFS_OS_DESC_EXT_PROP: { + struct usb_ext_prop_desc *d = data; + u32 type, pdl; + u16 pnl; + + if (len < sizeof(*d) || h->interface >= ffs->interfaces_count) + return -EINVAL; + length = le32_to_cpu(d->dwSize); + if (len < length) + return -EINVAL; + type = le32_to_cpu(d->dwPropertyDataType); + if (type < USB_EXT_PROP_UNICODE || + type > USB_EXT_PROP_UNICODE_MULTI) { + pr_vdebug("unsupported os descriptor property type: %d", + type); + return -EINVAL; + } + pnl = le16_to_cpu(d->wPropertyNameLength); + if (length < 14 + pnl) { + pr_vdebug("invalid os descriptor length: %d pnl:%d (descriptor %d)\n", + length, pnl, type); + return -EINVAL; + } + pdl = le32_to_cpu(*(__le32 *)((u8 *)data + 10 + pnl)); + if (length != 14 + pnl + pdl) { + pr_vdebug("invalid os descriptor length: %d pnl:%d pdl:%d (descriptor %d)\n", + length, pnl, pdl, type); + return -EINVAL; + } + ++ffs->ms_os_descs_ext_prop_count; + /* property name reported to the host as "WCHAR"s */ + ffs->ms_os_descs_ext_prop_name_len += pnl * 2; + ffs->ms_os_descs_ext_prop_data_len += pdl; + } + break; + default: + pr_vdebug("unknown descriptor: %d\n", type); + return -EINVAL; + } + return length; +} + +static int __ffs_data_got_descs(struct ffs_data *ffs, + char *const _data, size_t len) +{ + char *data = _data, *raw_descs; + unsigned os_descs_count = 0, counts[3], flags; + int ret = -EINVAL, i; + struct ffs_desc_helper helper; + + if (get_unaligned_le32(data + 4) != len) + goto error; + + switch (get_unaligned_le32(data)) { + case FUNCTIONFS_DESCRIPTORS_MAGIC: + flags = FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC; + data += 8; + len -= 8; + break; + case FUNCTIONFS_DESCRIPTORS_MAGIC_V2: + flags = get_unaligned_le32(data + 8); + ffs->user_flags = flags; + if (flags & ~(FUNCTIONFS_HAS_FS_DESC | + FUNCTIONFS_HAS_HS_DESC | + FUNCTIONFS_HAS_SS_DESC | + FUNCTIONFS_HAS_MS_OS_DESC | + FUNCTIONFS_VIRTUAL_ADDR | + FUNCTIONFS_EVENTFD | + FUNCTIONFS_ALL_CTRL_RECIP | + FUNCTIONFS_CONFIG0_SETUP | + FUNCTIONFS_RW_PROXY_EPS)) { + ret = -ENOSYS; + goto error; + } + data += 12; + len -= 12; + break; + default: + goto error; + } + + if (flags & FUNCTIONFS_EVENTFD) { + if (len < 4) + goto error; + ffs->ffs_eventfd = + eventfd_ctx_fdget((int)get_unaligned_le32(data)); + if (IS_ERR(ffs->ffs_eventfd)) { + ret = PTR_ERR(ffs->ffs_eventfd); + ffs->ffs_eventfd = NULL; + goto error; + } + data += 4; + len -= 4; + } + + /* Read fs_count, hs_count and ss_count (if present) */ + for (i = 0; i < 3; ++i) { + if (!(flags & (1 << i))) { + counts[i] = 0; + } else if (len < 4) { + goto error; + } else { + counts[i] = get_unaligned_le32(data); + data += 4; + len -= 4; + } + } + if (flags & (1 << i)) { + if (len < 4) { + goto error; + } + os_descs_count = get_unaligned_le32(data); + data += 4; + len -= 4; + } + + /* Read descriptors */ + raw_descs = data; + helper.ffs = ffs; + for (i = 0; i < 3; ++i) { + if (!counts[i]) + continue; + helper.interfaces_count = 0; + helper.eps_count = 0; + ret = ffs_do_descs(counts[i], data, len, + __ffs_data_do_entity, &helper); + if (ret < 0) + goto error; + if (!ffs->eps_count && !ffs->interfaces_count) { + ffs->eps_count = helper.eps_count; + ffs->interfaces_count = helper.interfaces_count; + } else { + if (ffs->eps_count != helper.eps_count) { + ret = -EINVAL; + goto error; + } + if (ffs->interfaces_count != helper.interfaces_count) { + ret = -EINVAL; + goto error; + } + } + data += ret; + len -= ret; + } + if (os_descs_count) { + ret = ffs_do_os_descs(os_descs_count, data, len, + __ffs_data_do_os_desc, ffs); + if (ret < 0) + goto error; + data += ret; + len -= ret; + } + + if (raw_descs == data || len) { + ret = -EINVAL; + goto error; + } + + if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { + if (ffs->eps_count % 2) { + ret = -EINVAL; + goto error; + } + + for (i = 1; i < ffs->eps_count; i += 2) { + if ((ffs->eps_addrmap[i] & USB_ENDPOINT_DIR_MASK) == + (ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK)) { + ret = -EINVAL; + goto error; + } + } + } + + ffs->raw_descs_data = _data; + ffs->raw_descs = raw_descs; + ffs->raw_descs_length = data - raw_descs; + ffs->fs_descs_count = counts[0]; + ffs->hs_descs_count = counts[1]; + ffs->ss_descs_count = counts[2]; + ffs->ms_os_descs_count = os_descs_count; + + return 0; + +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; + const char *data = _data; + struct usb_string *s; + + if (len < 16 || + 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 (!str_count != !lang_count) + goto error; + + /* Do we have at least as many strings as descriptors need? */ + needed_count = ffs->strings_count; + if (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. */ + { + unsigned i = 0; + vla_group(d); + vla_item(d, struct usb_gadget_strings *, stringtabs, + size_add(lang_count, 1)); + vla_item(d, struct usb_gadget_strings, stringtab, lang_count); + vla_item(d, struct usb_string, strings, + size_mul(lang_count, (needed_count + 1))); + + char *vlabuf = kmalloc(vla_group_size(d), GFP_KERNEL); + + if (!vlabuf) { + kfree(_data); + return -ENOMEM; + } + + /* Initialize the VLA pointers */ + stringtabs = vla_ptr(vlabuf, d, stringtabs); + t = vla_ptr(vlabuf, d, stringtab); + i = lang_count; + do { + *stringtabs++ = t++; + } while (--i); + *stringtabs = NULL; + + /* stringtabs = vlabuf = d_stringtabs for later kfree */ + stringtabs = vla_ptr(vlabuf, d, stringtabs); + t = vla_ptr(vlabuf, d, stringtab); + s = vla_ptr(vlabuf, d, strings); + } + + /* For each language */ + data += 16; + len -= 16; + + do { /* lang_count > 0 so we can use do-while */ + unsigned needed = needed_count; + u32 str_per_lang = str_count; + + if (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 (length == len) + goto error_free; + + /* + * User may provide more strings then we need, + * if that's the case we simply ignore the + * rest + */ + if (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_per_lang); + + s->id = 0; /* terminator */ + s->s = NULL; + ++s; + + } while (--lang_count); + + /* Some garbage left? */ + if (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_CANCELLED; + + /* + * Logic of this function guarantees that there are at most four pending + * evens on ffs->ev.types queue. This is important because the queue + * has space for four elements only and __ffs_ep0_read_events function + * depends on that limit as well. If more event types are added, those + * limits have to be revisited or guaranteed to still hold. + */ + switch (type) { + case FUNCTIONFS_RESUME: + rem_type2 = FUNCTIONFS_SUSPEND; + fallthrough; + 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: + WARN(1, "%d: unknown event, this should not happen\n", type); + return; + } + + { + 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); + if (ffs->ffs_eventfd) + eventfd_signal(ffs->ffs_eventfd); +} + +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_ep_addr2idx(struct ffs_data *ffs, u8 endpoint_address) +{ + int i; + + for (i = 1; i < ARRAY_SIZE(ffs->eps_addrmap); ++i) + if (ffs->eps_addrmap[i] == endpoint_address) + return i; + return -ENOENT; +} + +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; + unsigned ep_desc_id; + int idx; + static const char *speed_names[] = { "full", "high", "super" }; + + if (type != FFS_DESCRIPTOR) + return 0; + + /* + * If ss_descriptors is not NULL, we are reading super speed + * descriptors; if hs_descriptors is not NULL, we are reading high + * speed descriptors; otherwise, we are reading full speed + * descriptors. + */ + if (func->function.ss_descriptors) { + ep_desc_id = 2; + func->function.ss_descriptors[(long)valuep] = desc; + } else if (func->function.hs_descriptors) { + ep_desc_id = 1; + func->function.hs_descriptors[(long)valuep] = desc; + } else { + ep_desc_id = 0; + func->function.fs_descriptors[(long)valuep] = desc; + } + + if (!desc || desc->bDescriptorType != USB_DT_ENDPOINT) + return 0; + + idx = ffs_ep_addr2idx(func->ffs, ds->bEndpointAddress) - 1; + if (idx < 0) + return idx; + + ffs_ep = func->eps + idx; + + if (ffs_ep->descs[ep_desc_id]) { + pr_err("two %sspeed descriptors for EP %d\n", + speed_names[ep_desc_id], + usb_endpoint_num(ds)); + return -EINVAL; + } + ffs_ep->descs[ep_desc_id] = 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; + u8 bEndpointAddress; + __le16 wMaxPacketSize; + + /* + * We back up bEndpointAddress because autoconfig overwrites + * it with physical endpoint address. + */ + bEndpointAddress = ds->bEndpointAddress; + /* + * We back up wMaxPacketSize because autoconfig treats + * endpoint descriptors as if they were full speed. + */ + wMaxPacketSize = ds->wMaxPacketSize; + pr_vdebug("autoconfig\n"); + ep = usb_ep_autoconfig(func->gadget, ds); + if (!ep) + return -ENOTSUPP; + ep->driver_data = func->eps + idx; + + req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return -ENOMEM; + + ffs_ep->ep = ep; + ffs_ep->req = req; + func->eps_revmap[ds->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK] = idx + 1; + /* + * If we use virtual address mapping, we restore + * original bEndpointAddress value. + */ + if (func->ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR) + ds->bEndpointAddress = bEndpointAddress; + /* + * Restore wMaxPacketSize which was potentially + * overwritten by autoconfig. + */ + ds->wMaxPacketSize = wMaxPacketSize; + } + 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 (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 (!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_do_os_desc(enum ffs_os_desc_type type, + struct usb_os_desc_header *h, void *data, + unsigned len, void *priv) +{ + struct ffs_function *func = priv; + u8 length = 0; + + switch (type) { + case FFS_OS_DESC_EXT_COMPAT: { + struct usb_ext_compat_desc *desc = data; + struct usb_os_desc_table *t; + + t = &func->function.os_desc_table[desc->bFirstInterfaceNumber]; + t->if_id = func->interfaces_nums[desc->bFirstInterfaceNumber]; + memcpy(t->os_desc->ext_compat_id, &desc->IDs, + sizeof_field(struct usb_ext_compat_desc, IDs)); + length = sizeof(*desc); + } + break; + case FFS_OS_DESC_EXT_PROP: { + struct usb_ext_prop_desc *desc = data; + struct usb_os_desc_table *t; + struct usb_os_desc_ext_prop *ext_prop; + char *ext_prop_name; + char *ext_prop_data; + + t = &func->function.os_desc_table[h->interface]; + t->if_id = func->interfaces_nums[h->interface]; + + ext_prop = func->ffs->ms_os_descs_ext_prop_avail; + func->ffs->ms_os_descs_ext_prop_avail += sizeof(*ext_prop); + + ext_prop->type = le32_to_cpu(desc->dwPropertyDataType); + ext_prop->name_len = le16_to_cpu(desc->wPropertyNameLength); + ext_prop->data_len = le32_to_cpu(*(__le32 *) + usb_ext_prop_data_len_ptr(data, ext_prop->name_len)); + length = ext_prop->name_len + ext_prop->data_len + 14; + + ext_prop_name = func->ffs->ms_os_descs_ext_prop_name_avail; + func->ffs->ms_os_descs_ext_prop_name_avail += + ext_prop->name_len; + + ext_prop_data = func->ffs->ms_os_descs_ext_prop_data_avail; + func->ffs->ms_os_descs_ext_prop_data_avail += + ext_prop->data_len; + memcpy(ext_prop_data, + usb_ext_prop_data_ptr(data, ext_prop->name_len), + ext_prop->data_len); + /* unicode data reported to the host as "WCHAR"s */ + switch (ext_prop->type) { + case USB_EXT_PROP_UNICODE: + case USB_EXT_PROP_UNICODE_ENV: + case USB_EXT_PROP_UNICODE_LINK: + case USB_EXT_PROP_UNICODE_MULTI: + ext_prop->data_len *= 2; + break; + } + ext_prop->data = ext_prop_data; + + memcpy(ext_prop_name, usb_ext_prop_name_ptr(data), + ext_prop->name_len); + /* property name reported to the host as "WCHAR"s */ + ext_prop->name_len *= 2; + ext_prop->name = ext_prop_name; + + t->os_desc->ext_prop_len += + ext_prop->name_len + ext_prop->data_len + 14; + ++t->os_desc->ext_prop_count; + list_add_tail(&ext_prop->entry, &t->os_desc->ext_prop); + } + break; + default: + pr_vdebug("unknown descriptor: %d\n", type); + } + + return length; +} + +static inline struct f_fs_opts *ffs_do_functionfs_bind(struct usb_function *f, + struct usb_configuration *c) +{ + struct ffs_function *func = ffs_func_from_usb(f); + struct f_fs_opts *ffs_opts = + container_of(f->fi, struct f_fs_opts, func_inst); + struct ffs_data *ffs_data; + int ret; + + /* + * Legacy gadget triggers binding in functionfs_ready_callback, + * which already uses locking; taking the same lock here would + * cause a deadlock. + * + * Configfs-enabled gadgets however do need ffs_dev_lock. + */ + if (!ffs_opts->no_configfs) + ffs_dev_lock(); + ret = ffs_opts->dev->desc_ready ? 0 : -ENODEV; + ffs_data = ffs_opts->dev->ffs_data; + if (!ffs_opts->no_configfs) + ffs_dev_unlock(); + if (ret) + return ERR_PTR(ret); + + func->ffs = ffs_data; + func->conf = c; + func->gadget = c->cdev->gadget; + + /* + * in drivers/usb/gadget/configfs.c:configfs_composite_bind() + * configurations are bound in sequence with list_for_each_entry, + * in each configuration its functions are bound in sequence + * with list_for_each_entry, so we assume no race condition + * with regard to ffs_opts->bound access + */ + if (!ffs_opts->refcnt) { + ret = functionfs_bind(func->ffs, c->cdev); + if (ret) + return ERR_PTR(ret); + } + ffs_opts->refcnt++; + func->function.strings = func->ffs->stringtabs; + + return ffs_opts; +} + +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 = !!func->ffs->hs_descs_count; + const int super = !!func->ffs->ss_descs_count; + + int fs_len, hs_len, ss_len, ret, i; + struct ffs_ep *eps_ptr; + + /* Make it a single chunk, less management later on */ + vla_group(d); + vla_item_with_sz(d, struct ffs_ep, eps, ffs->eps_count); + vla_item_with_sz(d, struct usb_descriptor_header *, fs_descs, + full ? ffs->fs_descs_count + 1 : 0); + vla_item_with_sz(d, struct usb_descriptor_header *, hs_descs, + high ? ffs->hs_descs_count + 1 : 0); + vla_item_with_sz(d, struct usb_descriptor_header *, ss_descs, + super ? ffs->ss_descs_count + 1 : 0); + vla_item_with_sz(d, short, inums, ffs->interfaces_count); + vla_item_with_sz(d, struct usb_os_desc_table, os_desc_table, + c->cdev->use_os_string ? ffs->interfaces_count : 0); + vla_item_with_sz(d, char[16], ext_compat, + c->cdev->use_os_string ? ffs->interfaces_count : 0); + vla_item_with_sz(d, struct usb_os_desc, os_desc, + c->cdev->use_os_string ? ffs->interfaces_count : 0); + vla_item_with_sz(d, struct usb_os_desc_ext_prop, ext_prop, + ffs->ms_os_descs_ext_prop_count); + vla_item_with_sz(d, char, ext_prop_name, + ffs->ms_os_descs_ext_prop_name_len); + vla_item_with_sz(d, char, ext_prop_data, + ffs->ms_os_descs_ext_prop_data_len); + vla_item_with_sz(d, char, raw_descs, ffs->raw_descs_length); + char *vlabuf; + + /* Has descriptors only for speeds gadget does not support */ + if (!(full | high | super)) + return -ENOTSUPP; + + /* Allocate a single chunk, less management later on */ + vlabuf = kzalloc(vla_group_size(d), GFP_KERNEL); + if (!vlabuf) + return -ENOMEM; + + ffs->ms_os_descs_ext_prop_avail = vla_ptr(vlabuf, d, ext_prop); + ffs->ms_os_descs_ext_prop_name_avail = + vla_ptr(vlabuf, d, ext_prop_name); + ffs->ms_os_descs_ext_prop_data_avail = + vla_ptr(vlabuf, d, ext_prop_data); + + /* Copy descriptors */ + memcpy(vla_ptr(vlabuf, d, raw_descs), ffs->raw_descs, + ffs->raw_descs_length); + + memset(vla_ptr(vlabuf, d, inums), 0xff, d_inums__sz); + eps_ptr = vla_ptr(vlabuf, d, eps); + for (i = 0; i < ffs->eps_count; i++) + eps_ptr[i].num = -1; + + /* Save pointers + * d_eps == vlabuf, func->eps used to kfree vlabuf later + */ + func->eps = vla_ptr(vlabuf, d, eps); + func->interfaces_nums = vla_ptr(vlabuf, d, 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 (full) { + func->function.fs_descriptors = vla_ptr(vlabuf, d, fs_descs); + fs_len = ffs_do_descs(ffs->fs_descs_count, + vla_ptr(vlabuf, d, raw_descs), + d_raw_descs__sz, + __ffs_func_bind_do_descs, func); + if (fs_len < 0) { + ret = fs_len; + goto error; + } + } else { + fs_len = 0; + } + + if (high) { + func->function.hs_descriptors = vla_ptr(vlabuf, d, hs_descs); + hs_len = ffs_do_descs(ffs->hs_descs_count, + vla_ptr(vlabuf, d, raw_descs) + fs_len, + d_raw_descs__sz - fs_len, + __ffs_func_bind_do_descs, func); + if (hs_len < 0) { + ret = hs_len; + goto error; + } + } else { + hs_len = 0; + } + + if (super) { + func->function.ss_descriptors = func->function.ssp_descriptors = + vla_ptr(vlabuf, d, ss_descs); + ss_len = ffs_do_descs(ffs->ss_descs_count, + vla_ptr(vlabuf, d, raw_descs) + fs_len + hs_len, + d_raw_descs__sz - fs_len - hs_len, + __ffs_func_bind_do_descs, func); + if (ss_len < 0) { + ret = ss_len; + goto error; + } + } else { + ss_len = 0; + } + + /* + * 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) + + (super ? ffs->ss_descs_count : 0), + vla_ptr(vlabuf, d, raw_descs), d_raw_descs__sz, + __ffs_func_bind_do_nums, func); + if (ret < 0) + goto error; + + func->function.os_desc_table = vla_ptr(vlabuf, d, os_desc_table); + if (c->cdev->use_os_string) { + for (i = 0; i < ffs->interfaces_count; ++i) { + struct usb_os_desc *desc; + + desc = func->function.os_desc_table[i].os_desc = + vla_ptr(vlabuf, d, os_desc) + + i * sizeof(struct usb_os_desc); + desc->ext_compat_id = + vla_ptr(vlabuf, d, ext_compat) + i * 16; + INIT_LIST_HEAD(&desc->ext_prop); + } + ret = ffs_do_os_descs(ffs->ms_os_descs_count, + vla_ptr(vlabuf, d, raw_descs) + + fs_len + hs_len + ss_len, + d_raw_descs__sz - fs_len - hs_len - + ss_len, + __ffs_func_bind_do_os_desc, func); + if (ret < 0) + goto error; + } + func->function.os_desc_n = + c->cdev->use_os_string ? ffs->interfaces_count : 0; + + /* And we're done */ + ffs_event_add(ffs, FUNCTIONFS_BIND); + return 0; + +error: + /* XXX Do we need to release all claimed endpoints here? */ + return ret; +} + +static int ffs_func_bind(struct usb_configuration *c, + struct usb_function *f) +{ + struct f_fs_opts *ffs_opts = ffs_do_functionfs_bind(f, c); + struct ffs_function *func = ffs_func_from_usb(f); + int ret; + + if (IS_ERR(ffs_opts)) + return PTR_ERR(ffs_opts); + + ret = _ffs_func_bind(c, f); + if (ret && !--ffs_opts->refcnt) + functionfs_unbind(func->ffs); + + return ret; +} + + +/* Other USB function hooks *************************************************/ + +static void ffs_reset_work(struct work_struct *work) +{ + struct ffs_data *ffs = container_of(work, + struct ffs_data, reset_work); + ffs_data_reset(ffs); +} + +static int ffs_func_get_alt(struct usb_function *f, + unsigned int interface) +{ + struct ffs_function *func = ffs_func_from_usb(f); + int intf = ffs_func_revmap_intf(func, interface); + + return (intf < 0) ? intf : func->cur_alt[interface]; +} + +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; + unsigned long flags; + int ret = 0, intf; + + if (alt > MAX_ALT_SETTINGS) + return -EINVAL; + + intf = ffs_func_revmap_intf(func, interface); + if (intf < 0) + return intf; + + if (ffs->func) + ffs_func_eps_disable(ffs->func); + + spin_lock_irqsave(&ffs->eps_lock, flags); + if (ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; + spin_unlock_irqrestore(&ffs->eps_lock, flags); + schedule_work(&ffs->reset_work); + return -ENODEV; + } + spin_unlock_irqrestore(&ffs->eps_lock, flags); + + if (ffs->state != FFS_ACTIVE) + return -ENODEV; + + ffs->func = func; + ret = ffs_func_eps_enable(func); + if (ret >= 0) { + ffs_event_add(ffs, FUNCTIONFS_ENABLE); + func->cur_alt[interface] = alt; + } + return ret; +} + +static void ffs_func_disable(struct usb_function *f) +{ + struct ffs_function *func = ffs_func_from_usb(f); + struct ffs_data *ffs = func->ffs; + unsigned long flags; + + if (ffs->func) + ffs_func_eps_disable(ffs->func); + + spin_lock_irqsave(&ffs->eps_lock, flags); + if (ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; + spin_unlock_irqrestore(&ffs->eps_lock, flags); + schedule_work(&ffs->reset_work); + return; + } + spin_unlock_irqrestore(&ffs->eps_lock, flags); + + if (ffs->state == FFS_ACTIVE) { + ffs->func = NULL; + ffs_event_add(ffs, FUNCTIONFS_DISABLE); + } +} + +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; + + 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). Other request recipient + * types are only handled when the user flag FUNCTIONFS_ALL_CTRL_RECIP + * is being used. + */ + 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 (ret < 0) + return ret; + break; + + case USB_RECIP_ENDPOINT: + ret = ffs_func_revmap_ep(func, le16_to_cpu(creq->wIndex)); + if (ret < 0) + return ret; + if (func->ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR) + ret = func->ffs->eps_addrmap[ret]; + break; + + default: + if (func->ffs->user_flags & FUNCTIONFS_ALL_CTRL_RECIP) + ret = le16_to_cpu(creq->wIndex); + else + 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 ffs->ev.setup.wLength == 0 ? USB_GADGET_DELAYED_STATUS : 0; +} + +static bool ffs_func_req_match(struct usb_function *f, + const struct usb_ctrlrequest *creq, + bool config0) +{ + struct ffs_function *func = ffs_func_from_usb(f); + + if (config0 && !(func->ffs->user_flags & FUNCTIONFS_CONFIG0_SETUP)) + return false; + + switch (creq->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + return (ffs_func_revmap_intf(func, + le16_to_cpu(creq->wIndex)) >= 0); + case USB_RECIP_ENDPOINT: + return (ffs_func_revmap_ep(func, + le16_to_cpu(creq->wIndex)) >= 0); + default: + return (bool) (func->ffs->user_flags & + FUNCTIONFS_ALL_CTRL_RECIP); + } +} + +static void ffs_func_suspend(struct usb_function *f) +{ + ffs_event_add(ffs_func_from_usb(f)->ffs, FUNCTIONFS_SUSPEND); +} + +static void ffs_func_resume(struct usb_function *f) +{ + 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; +} + + +/* Devices management *******************************************************/ + +static LIST_HEAD(ffs_devices); + +static struct ffs_dev *_ffs_do_find_dev(const char *name) +{ + struct ffs_dev *dev; + + if (!name) + return NULL; + + list_for_each_entry(dev, &ffs_devices, entry) { + if (strcmp(dev->name, name) == 0) + return dev; + } + + return NULL; +} + +/* + * ffs_lock must be taken by the caller of this function + */ +static struct ffs_dev *_ffs_get_single_dev(void) +{ + struct ffs_dev *dev; + + if (list_is_singular(&ffs_devices)) { + dev = list_first_entry(&ffs_devices, struct ffs_dev, entry); + if (dev->single) + return dev; + } + + return NULL; +} + +/* + * ffs_lock must be taken by the caller of this function + */ +static struct ffs_dev *_ffs_find_dev(const char *name) +{ + struct ffs_dev *dev; + + dev = _ffs_get_single_dev(); + if (dev) + return dev; + + return _ffs_do_find_dev(name); +} + +/* Configfs support *********************************************************/ + +static inline struct f_fs_opts *to_ffs_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_fs_opts, + func_inst.group); +} + +static ssize_t f_fs_opts_ready_show(struct config_item *item, char *page) +{ + struct f_fs_opts *opts = to_ffs_opts(item); + int ready; + + ffs_dev_lock(); + ready = opts->dev->desc_ready; + ffs_dev_unlock(); + + return sprintf(page, "%d\n", ready); +} + +CONFIGFS_ATTR_RO(f_fs_opts_, ready); + +static struct configfs_attribute *ffs_attrs[] = { + &f_fs_opts_attr_ready, + NULL, +}; + +static void ffs_attr_release(struct config_item *item) +{ + struct f_fs_opts *opts = to_ffs_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static const struct configfs_item_operations ffs_item_ops = { + .release = ffs_attr_release, +}; + +static const struct config_item_type ffs_func_type = { + .ct_item_ops = &ffs_item_ops, + .ct_attrs = ffs_attrs, + .ct_owner = THIS_MODULE, +}; + + +/* Function registration interface ******************************************/ + +static void ffs_free_inst(struct usb_function_instance *f) +{ + struct f_fs_opts *opts; + + opts = to_f_fs_opts(f); + ffs_release_dev(opts->dev); + ffs_dev_lock(); + _ffs_free_dev(opts->dev); + ffs_dev_unlock(); + kfree(opts); +} + +static int ffs_set_inst_name(struct usb_function_instance *fi, const char *name) +{ + if (strlen(name) >= sizeof_field(struct ffs_dev, name)) + return -ENAMETOOLONG; + return ffs_name_dev(to_f_fs_opts(fi)->dev, name); +} + +static struct usb_function_instance *ffs_alloc_inst(void) +{ + struct f_fs_opts *opts; + struct ffs_dev *dev; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + + opts->func_inst.set_inst_name = ffs_set_inst_name; + opts->func_inst.free_func_inst = ffs_free_inst; + ffs_dev_lock(); + dev = _ffs_alloc_dev(); + ffs_dev_unlock(); + if (IS_ERR(dev)) { + kfree(opts); + return ERR_CAST(dev); + } + opts->dev = dev; + dev->opts = opts; + + config_group_init_type_name(&opts->func_inst.group, "", + &ffs_func_type); + return &opts->func_inst; +} + +static void ffs_free(struct usb_function *f) +{ + kfree(ffs_func_from_usb(f)); +} + +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; + struct f_fs_opts *opts = + container_of(f->fi, struct f_fs_opts, func_inst); + struct ffs_ep *ep = func->eps; + unsigned count = ffs->eps_count; + unsigned long flags; + + if (ffs->func == func) { + ffs_func_eps_disable(func); + ffs->func = NULL; + } + + /* Drain any pending AIO completions */ + drain_workqueue(ffs->io_completion_wq); + + ffs_event_add(ffs, FUNCTIONFS_UNBIND); + if (!--opts->refcnt) + functionfs_unbind(ffs); + + /* cleanup after autoconfig */ + spin_lock_irqsave(&func->ffs->eps_lock, flags); + while (count--) { + if (ep->ep && ep->req) + usb_ep_free_request(ep->ep, ep->req); + ep->req = NULL; + ++ep; + } + spin_unlock_irqrestore(&func->ffs->eps_lock, flags); + kfree(func->eps); + func->eps = NULL; + /* + * eps, descriptors and interfaces_nums are allocated in the + * same chunk so only one free is required. + */ + func->function.fs_descriptors = NULL; + func->function.hs_descriptors = NULL; + func->function.ss_descriptors = NULL; + func->function.ssp_descriptors = NULL; + func->interfaces_nums = NULL; + +} + +static struct usb_function *ffs_alloc(struct usb_function_instance *fi) +{ + struct ffs_function *func; + + func = kzalloc_obj(*func); + if (!func) + return ERR_PTR(-ENOMEM); + + func->function.name = "Function FS Gadget"; + + func->function.bind = ffs_func_bind; + func->function.unbind = ffs_func_unbind; + func->function.set_alt = ffs_func_set_alt; + func->function.get_alt = ffs_func_get_alt; + func->function.disable = ffs_func_disable; + func->function.setup = ffs_func_setup; + func->function.req_match = ffs_func_req_match; + func->function.suspend = ffs_func_suspend; + func->function.resume = ffs_func_resume; + func->function.free_func = ffs_free; + + return &func->function; +} + +/* + * ffs_lock must be taken by the caller of this function + */ +static struct ffs_dev *_ffs_alloc_dev(void) +{ + struct ffs_dev *dev; + int ret; + + if (_ffs_get_single_dev()) + return ERR_PTR(-EBUSY); + + dev = kzalloc_obj(*dev); + if (!dev) + return ERR_PTR(-ENOMEM); + + if (list_empty(&ffs_devices)) { + ret = functionfs_init(); + if (ret) { + kfree(dev); + return ERR_PTR(ret); + } + } + + list_add(&dev->entry, &ffs_devices); + + return dev; +} + +int ffs_name_dev(struct ffs_dev *dev, const char *name) +{ + struct ffs_dev *existing; + int ret = 0; + + ffs_dev_lock(); + + existing = _ffs_do_find_dev(name); + if (!existing) + strscpy(dev->name, name, ARRAY_SIZE(dev->name)); + else if (existing != dev) + ret = -EBUSY; + + ffs_dev_unlock(); + + return ret; +} +EXPORT_SYMBOL_GPL(ffs_name_dev); + +int ffs_single_dev(struct ffs_dev *dev) +{ + int ret; + + ret = 0; + ffs_dev_lock(); + + if (!list_is_singular(&ffs_devices)) + ret = -EBUSY; + else + dev->single = true; + + ffs_dev_unlock(); + return ret; +} +EXPORT_SYMBOL_GPL(ffs_single_dev); + +/* + * ffs_lock must be taken by the caller of this function + */ +static void _ffs_free_dev(struct ffs_dev *dev) +{ + list_del(&dev->entry); + + kfree(dev); + if (list_empty(&ffs_devices)) + functionfs_cleanup(); +} + +static int ffs_acquire_dev(const char *dev_name, struct ffs_data *ffs_data) +{ + int ret = 0; + struct ffs_dev *ffs_dev; + + ffs_dev_lock(); + + ffs_dev = _ffs_find_dev(dev_name); + if (!ffs_dev) { + ret = -ENOENT; + } else if (ffs_dev->mounted) { + ret = -EBUSY; + } else if (ffs_dev->ffs_acquire_dev_callback && + ffs_dev->ffs_acquire_dev_callback(ffs_dev)) { + ret = -ENOENT; + } else { + ffs_dev->mounted = true; + ffs_dev->ffs_data = ffs_data; + ffs_data->private_data = ffs_dev; + } + + ffs_dev_unlock(); + return ret; +} + +static void ffs_release_dev(struct ffs_dev *ffs_dev) +{ + ffs_dev_lock(); + + if (ffs_dev && ffs_dev->mounted) { + ffs_dev->mounted = false; + if (ffs_dev->ffs_data) { + ffs_dev->ffs_data->private_data = NULL; + ffs_dev->ffs_data = NULL; + } + + if (ffs_dev->ffs_release_dev_callback) + ffs_dev->ffs_release_dev_callback(ffs_dev); + } + + ffs_dev_unlock(); +} + +static int ffs_ready(struct ffs_data *ffs) +{ + struct ffs_dev *ffs_obj; + int ret = 0; + + ffs_dev_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; + + if (ffs_obj->ffs_ready_callback) { + ret = ffs_obj->ffs_ready_callback(ffs); + if (ret) + goto done; + } + + set_bit(FFS_FL_CALL_CLOSED_CALLBACK, &ffs->flags); +done: + ffs_dev_unlock(); + return ret; +} + +static void ffs_closed(struct ffs_data *ffs) +{ + struct ffs_dev *ffs_obj; + struct f_fs_opts *opts; + struct config_item *ci; + + ffs_dev_lock(); + + ffs_obj = ffs->private_data; + if (!ffs_obj) + goto done; + + ffs_obj->desc_ready = false; + + if (test_and_clear_bit(FFS_FL_CALL_CLOSED_CALLBACK, &ffs->flags) && + ffs_obj->ffs_closed_callback) + ffs_obj->ffs_closed_callback(ffs); + + if (ffs_obj->opts) + opts = ffs_obj->opts; + else + goto done; + + if (opts->no_configfs || !opts->func_inst.group.cg_item.ci_parent + || !kref_read(&opts->func_inst.group.cg_item.ci_kref)) + goto done; + + ci = opts->func_inst.group.cg_item.ci_parent->ci_parent; + ffs_dev_unlock(); + + if (test_bit(FFS_FL_BOUND, &ffs->flags)) + unregister_gadget_item(ci); + return; +done: + ffs_dev_unlock(); +} + +/* Misc helper functions ****************************************************/ + +static int ffs_mutex_lock(struct mutex *mutex, unsigned nonblock) +{ + return nonblock + ? mutex_trylock(mutex) ? 0 : -EAGAIN + : mutex_lock_interruptible(mutex); +} + +static char *ffs_prepare_buffer(const char __user *buf, size_t len) +{ + char *data; + + if (!len) + return NULL; + + data = memdup_user(buf, len); + if (IS_ERR(data)) + return data; + + pr_vdebug("Buffer from user space:\n"); + ffs_dump_mem("", data, len); + + return data; +} + +DECLARE_USB_FUNCTION_INIT(ffs, ffs_alloc_inst, ffs_alloc); +MODULE_DESCRIPTION("user mode file system API for USB composite function controllers"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michal Nazarewicz"); diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c new file mode 100644 index 000000000..3c6b43d06 --- /dev/null +++ b/drivers/usb/gadget/function/f_hid.c @@ -0,0 +1,1700 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_hid.c -- USB HID function driver + * + * Copyright (C) 2010 Fabien Chouteau + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "u_hid.h" + +#define HIDG_MINORS 4 + +/* + * Most operating systems seem to allow for 5000ms timeout, we will allow + * userspace half that time to respond before we return an empty report. + */ +#define GET_REPORT_TIMEOUT_MS 2500 + +static int major, minors; + +static const struct class hidg_class = { + .name = "hidg", +}; + +static DEFINE_IDA(hidg_ida); +static DEFINE_MUTEX(hidg_ida_lock); /* protects access to hidg_ida */ + +struct report_entry { + struct usb_hidg_report report_data; + struct list_head node; +}; + +/*-------------------------------------------------------------------------*/ +/* 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 char protocol; + unsigned char idle; + unsigned short report_desc_length; + char *report_desc; + unsigned short report_length; + unsigned char interval; + bool interval_user_set; + + /* + * use_out_ep - if true, the OUT Endpoint (interrupt out method) + * will be used to receive reports from the host + * using functions with the "intout" suffix. + * Otherwise, the OUT Endpoint will not be configured + * and the SETUP/SET_REPORT method ("ssreport" suffix) + * will be used to receive reports. + */ + bool use_out_ep; + + /* recv report */ + spinlock_t read_spinlock; + wait_queue_head_t read_queue; + bool disabled; + /* recv report - interrupt out only (use_out_ep == 1) */ + struct list_head completed_out_req; + unsigned int qlen; + /* recv report - setup set_report only (use_out_ep == 0) */ + char *set_report_buf; + unsigned int set_report_length; + + /* send report */ + spinlock_t write_spinlock; + bool write_pending; + wait_queue_head_t write_queue; + struct usb_request *req; + + /* get report */ + struct usb_request *get_req; + struct usb_hidg_report get_report; + bool get_report_returned; + int get_report_req_report_id; + int get_report_req_report_length; + spinlock_t get_report_spinlock; + wait_queue_head_t get_queue; /* Waiting for userspace response */ + wait_queue_head_t get_id_queue; /* Get ID came in */ + struct work_struct work; + struct workqueue_struct *workqueue; + struct list_head report_list; + + struct device dev; + 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 void hidg_release(struct device *dev) +{ + struct f_hidg *hidg = container_of(dev, struct f_hidg, dev); + + kfree(hidg->report_desc); + kfree(hidg->set_report_buf); + kfree(hidg); +} + +/*-------------------------------------------------------------------------*/ +/* Static descriptors */ + +static struct usb_interface_descriptor hidg_interface_desc = { + .bLength = sizeof hidg_interface_desc, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bAlternateSetting = 0, + /* .bNumEndpoints = DYNAMIC (depends on use_out_ep) */ + .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 = cpu_to_le16(0x0101), + .bCountryCode = 0x00, + .bNumDescriptors = 0x1, + /*.rpt_desc.bDescriptorType = DYNAMIC */ + /*.rpt_desc.wDescriptorLength = DYNAMIC */ +}; + +/* Super-Speed Support */ + +static struct usb_endpoint_descriptor hidg_ss_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + /*.wMaxPacketSize = DYNAMIC */ + /*.bInterval = DYNAMIC */ +}; + +static struct usb_ss_ep_comp_descriptor hidg_ss_in_comp_desc = { + .bLength = sizeof(hidg_ss_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + /* .wBytesPerInterval = DYNAMIC */ +}; + +static struct usb_endpoint_descriptor hidg_ss_out_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_INT, + /*.wMaxPacketSize = DYNAMIC */ + /*.bInterval = DYNAMIC */ +}; + +static struct usb_ss_ep_comp_descriptor hidg_ss_out_comp_desc = { + .bLength = sizeof(hidg_ss_out_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + /* .wBytesPerInterval = DYNAMIC */ +}; + +static struct usb_descriptor_header *hidg_ss_descriptors_intout[] = { + (struct usb_descriptor_header *)&hidg_interface_desc, + (struct usb_descriptor_header *)&hidg_desc, + (struct usb_descriptor_header *)&hidg_ss_in_ep_desc, + (struct usb_descriptor_header *)&hidg_ss_in_comp_desc, + (struct usb_descriptor_header *)&hidg_ss_out_ep_desc, + (struct usb_descriptor_header *)&hidg_ss_out_comp_desc, + NULL, +}; + +static struct usb_descriptor_header *hidg_ss_descriptors_ssreport[] = { + (struct usb_descriptor_header *)&hidg_interface_desc, + (struct usb_descriptor_header *)&hidg_desc, + (struct usb_descriptor_header *)&hidg_ss_in_ep_desc, + (struct usb_descriptor_header *)&hidg_ss_in_comp_desc, + NULL, +}; + +/* 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 = DYNAMIC */ +}; + +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 = DYNAMIC */ +}; + +static struct usb_descriptor_header *hidg_hs_descriptors_intout[] = { + (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, +}; + +static struct usb_descriptor_header *hidg_hs_descriptors_ssreport[] = { + (struct usb_descriptor_header *)&hidg_interface_desc, + (struct usb_descriptor_header *)&hidg_desc, + (struct usb_descriptor_header *)&hidg_hs_in_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 = DYNAMIC */ +}; + +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 = DYNAMIC */ +}; + +static struct usb_descriptor_header *hidg_fs_descriptors_intout[] = { + (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, +}; + +static struct usb_descriptor_header *hidg_fs_descriptors_ssreport[] = { + (struct usb_descriptor_header *)&hidg_interface_desc, + (struct usb_descriptor_header *)&hidg_desc, + (struct usb_descriptor_header *)&hidg_fs_in_ep_desc, + NULL, +}; + +/*-------------------------------------------------------------------------*/ +/* 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, +}; + +/*-------------------------------------------------------------------------*/ +/* Char Device */ + +static ssize_t f_hidg_intout_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; + + spin_lock_irqsave(&hidg->read_spinlock, flags); + +#define READ_COND_INTOUT (!list_empty(&hidg->completed_out_req) || hidg->disabled) + + /* wait for at least one buffer to complete */ + while (!READ_COND_INTOUT) { + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible(hidg->read_queue, READ_COND_INTOUT)) + return -ERESTARTSYS; + + spin_lock_irqsave(&hidg->read_spinlock, flags); + } + + if (hidg->disabled) { + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + return -ESHUTDOWN; + } + + /* pick the first one */ + list = list_first_entry(&hidg->completed_out_req, + struct f_hidg_req_list, list); + + /* + * Remove this from list to protect it from beign free() + * while host disables our function + */ + list_del(&list->list); + + req = list->req; + count = min_t(unsigned int, count, req->actual - list->pos); + spin_unlock_irqrestore(&hidg->read_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) { + kfree(list); + + req->length = hidg->report_length; + ret = usb_ep_queue(hidg->out_ep, req, GFP_KERNEL); + if (ret < 0) { + free_ep_req(hidg->out_ep, req); + return ret; + } + } else { + spin_lock_irqsave(&hidg->read_spinlock, flags); + list_add(&list->list, &hidg->completed_out_req); + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + + wake_up(&hidg->read_queue); + } + + return count; +} + +#define READ_COND_SSREPORT (hidg->set_report_buf != NULL || hidg->disabled) + +static ssize_t f_hidg_ssreport_read(struct file *file, char __user *buffer, + size_t count, loff_t *ptr) +{ + struct f_hidg *hidg = file->private_data; + char *tmp_buf = NULL; + unsigned long flags; + + if (!count) + return 0; + + spin_lock_irqsave(&hidg->read_spinlock, flags); + + while (!READ_COND_SSREPORT) { + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible(hidg->read_queue, READ_COND_SSREPORT)) + return -ERESTARTSYS; + + spin_lock_irqsave(&hidg->read_spinlock, flags); + } + + count = min_t(unsigned int, count, hidg->set_report_length); + tmp_buf = hidg->set_report_buf; + hidg->set_report_buf = NULL; + + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + + if (tmp_buf != NULL) { + count -= copy_to_user(buffer, tmp_buf, count); + kfree(tmp_buf); + } else { + count = -ENOMEM; + } + + wake_up(&hidg->read_queue); + + return count; +} + +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; + + if (hidg->use_out_ep) + return f_hidg_intout_read(file, buffer, count, ptr); + else + return f_hidg_ssreport_read(file, buffer, count, ptr); +} + +static void f_hidg_req_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_hidg *hidg = (struct f_hidg *)ep->driver_data; + unsigned long flags; + + if (req->status != 0) { + ERROR(hidg->func.config->cdev, + "End Point Request ERROR: %d\n", req->status); + } + + spin_lock_irqsave(&hidg->write_spinlock, flags); + hidg->write_pending = 0; + spin_unlock_irqrestore(&hidg->write_spinlock, flags); + 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; + struct usb_request *req; + unsigned long flags; + ssize_t status = -ENOMEM; + + spin_lock_irqsave(&hidg->write_spinlock, flags); + + if (!hidg->req) { + spin_unlock_irqrestore(&hidg->write_spinlock, flags); + return -ESHUTDOWN; + } + +#define WRITE_COND (!hidg->write_pending) +try_again: + /* write queue */ + while (!WRITE_COND) { + spin_unlock_irqrestore(&hidg->write_spinlock, flags); + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible_exclusive( + hidg->write_queue, WRITE_COND)) + return -ERESTARTSYS; + + spin_lock_irqsave(&hidg->write_spinlock, flags); + } + + hidg->write_pending = 1; + req = hidg->req; + count = min_t(unsigned, count, hidg->report_length); + + spin_unlock_irqrestore(&hidg->write_spinlock, flags); + + if (!req) { + ERROR(hidg->func.config->cdev, "hidg->req is NULL\n"); + status = -ESHUTDOWN; + goto release_write_pending; + } + + status = copy_from_user(req->buf, buffer, count); + if (status != 0) { + ERROR(hidg->func.config->cdev, + "copy_from_user error\n"); + status = -EINVAL; + goto release_write_pending; + } + + spin_lock_irqsave(&hidg->write_spinlock, flags); + + /* when our function has been disabled by host */ + if (!hidg->req) { + free_ep_req(hidg->in_ep, req); + /* + * TODO + * Should we fail with error here? + */ + goto try_again; + } + + req->status = 0; + req->zero = 1; + req->length = count; + req->complete = f_hidg_req_complete; + req->context = hidg; + + spin_unlock_irqrestore(&hidg->write_spinlock, flags); + + if (!hidg->in_ep->enabled) { + ERROR(hidg->func.config->cdev, "in_ep is disabled\n"); + status = -ESHUTDOWN; + goto release_write_pending; + } + + status = usb_ep_queue(hidg->in_ep, req, GFP_ATOMIC); + if (status < 0) + goto release_write_pending; + else + status = count; + + return status; +release_write_pending: + spin_lock_irqsave(&hidg->write_spinlock, flags); + hidg->write_pending = 0; + spin_unlock_irqrestore(&hidg->write_spinlock, flags); + + wake_up(&hidg->write_queue); + + return status; +} + +static struct report_entry *f_hidg_search_for_report(struct f_hidg *hidg, u8 report_id) +{ + struct list_head *ptr; + struct report_entry *entry; + + list_for_each(ptr, &hidg->report_list) { + entry = list_entry(ptr, struct report_entry, node); + if (entry->report_data.report_id == report_id) + return entry; + } + + return NULL; +} + +static void get_report_workqueue_handler(struct work_struct *work) +{ + struct f_hidg *hidg = container_of(work, struct f_hidg, work); + struct usb_composite_dev *cdev = hidg->func.config->cdev; + struct usb_request *req; + struct report_entry *ptr; + unsigned long flags; + + int status = 0; + + spin_lock_irqsave(&hidg->get_report_spinlock, flags); + req = hidg->get_req; + if (!req) { + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + return; + } + + req->zero = 0; + req->length = min_t(unsigned int, min_t(unsigned int, hidg->get_report_req_report_length, + hidg->report_length), + MAX_REPORT_LENGTH); + + /* Check if there is a response available for immediate response */ + ptr = f_hidg_search_for_report(hidg, hidg->get_report_req_report_id); + if (ptr && !ptr->report_data.userspace_req) { + /* Report exists in list and it is to be used for immediate response */ + req->buf = ptr->report_data.data; + status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + hidg->get_report_returned = true; + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + } else { + /* + * Report does not exist in list or should not be immediately sent + * i.e. give userspace time to respond + */ + hidg->get_report_returned = false; + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + wake_up(&hidg->get_id_queue); +#define GET_REPORT_COND (!hidg->get_report_returned) + /* Wait until userspace has responded or timeout */ + status = wait_event_interruptible_timeout(hidg->get_queue, !GET_REPORT_COND, + msecs_to_jiffies(GET_REPORT_TIMEOUT_MS)); + spin_lock_irqsave(&hidg->get_report_spinlock, flags); + req = hidg->get_req; + if (!req) { + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + return; + } + if (status == 0 && !hidg->get_report_returned) { + /* GET_REPORT request was not serviced by userspace within timeout period */ + VDBG(cdev, "get_report : userspace timeout.\n"); + hidg->get_report_returned = true; + } + + /* Search again for report ID in list and respond to GET_REPORT request */ + ptr = f_hidg_search_for_report(hidg, hidg->get_report_req_report_id); + if (ptr) { + /* + * Either get an updated response just serviced by userspace + * or send the latest response in the list + */ + req->buf = ptr->report_data.data; + } else { + /* If there are no prevoiusly sent reports send empty report */ + req->buf = hidg->get_report.data; + memset(req->buf, 0x0, req->length); + } + + status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + } + + if (status < 0) + VDBG(cdev, "usb_ep_queue error on ep0 responding to GET_REPORT\n"); +} + +static int f_hidg_get_report_id(struct file *file, __u8 __user *buffer) +{ + struct f_hidg *hidg = file->private_data; + int ret = 0; + + ret = put_user(hidg->get_report_req_report_id, buffer); + + return ret; +} + +static int f_hidg_get_report(struct file *file, struct usb_hidg_report __user *buffer) +{ + struct f_hidg *hidg = file->private_data; + struct usb_composite_dev *cdev = hidg->func.config->cdev; + unsigned long flags; + struct report_entry *entry; + struct report_entry *ptr; + __u8 report_id; + + entry = kmalloc_obj(*entry); + if (!entry) + return -ENOMEM; + + if (copy_from_user(&entry->report_data, buffer, + sizeof(struct usb_hidg_report))) { + ERROR(cdev, "copy_from_user error\n"); + kfree(entry); + return -EINVAL; + } + + report_id = entry->report_data.report_id; + + spin_lock_irqsave(&hidg->get_report_spinlock, flags); + ptr = f_hidg_search_for_report(hidg, report_id); + + if (ptr) { + /* Report already exists in list - update it */ + if (copy_from_user(&ptr->report_data, buffer, + sizeof(struct usb_hidg_report))) { + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + ERROR(cdev, "copy_from_user error\n"); + kfree(entry); + return -EINVAL; + } + kfree(entry); + } else { + /* Report does not exist in list - add it */ + list_add_tail(&entry->node, &hidg->report_list); + } + + /* If there is no response pending then do nothing further */ + if (hidg->get_report_returned) { + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + return 0; + } + + /* If this userspace response serves the current pending report */ + if (hidg->get_report_req_report_id == report_id) { + hidg->get_report_returned = true; + wake_up(&hidg->get_queue); + } + + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + return 0; +} + +static long f_hidg_ioctl(struct file *file, unsigned int code, unsigned long arg) +{ + switch (code) { + case GADGET_HID_READ_GET_REPORT_ID: + return f_hidg_get_report_id(file, (__u8 __user *)arg); + case GADGET_HID_WRITE_GET_REPORT: + return f_hidg_get_report(file, (struct usb_hidg_report __user *)arg); + default: + return -ENOTTY; + } +} + +static __poll_t f_hidg_poll(struct file *file, poll_table *wait) +{ + struct f_hidg *hidg = file->private_data; + __poll_t ret = 0; + + poll_wait(file, &hidg->read_queue, wait); + poll_wait(file, &hidg->write_queue, wait); + poll_wait(file, &hidg->get_queue, wait); + poll_wait(file, &hidg->get_id_queue, wait); + + if (WRITE_COND) + ret |= EPOLLOUT | EPOLLWRNORM; + + if (hidg->use_out_ep) { + if (READ_COND_INTOUT) + ret |= EPOLLIN | EPOLLRDNORM; + } else { + if (READ_COND_SSREPORT) + ret |= EPOLLIN | EPOLLRDNORM; + } + + if (GET_REPORT_COND) + ret |= EPOLLPRI; + + return ret; +} + +#undef WRITE_COND +#undef READ_COND_SSREPORT +#undef READ_COND_INTOUT +#undef GET_REPORT_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 kobject *parent = inode->i_cdev->kobj.parent; + struct f_hidg *hidg = + container_of(parent, struct f_hidg, dev.kobj); + + fd->private_data = hidg; + + return 0; +} + +/*-------------------------------------------------------------------------*/ +/* usb_function */ + +static inline struct usb_request *hidg_alloc_ep_req(struct usb_ep *ep, + unsigned length) +{ + return alloc_ep_req(ep, length); +} + +static void hidg_intout_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_hidg *hidg = (struct f_hidg *) req->context; + struct usb_composite_dev *cdev = hidg->func.config->cdev; + struct f_hidg_req_list *req_list; + unsigned long flags; + + switch (req->status) { + case 0: + req_list = kzalloc_obj(*req_list, GFP_ATOMIC); + if (!req_list) { + ERROR(cdev, "Unable to allocate mem for req_list\n"); + goto free_req; + } + + req_list->req = req; + + spin_lock_irqsave(&hidg->read_spinlock, flags); + list_add_tail(&req_list->list, &hidg->completed_out_req); + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + + wake_up(&hidg->read_queue); + break; + default: + ERROR(cdev, "Set report failed %d\n", req->status); + fallthrough; + case -ECONNABORTED: /* hardware forced ep reset */ + case -ECONNRESET: /* request dequeued */ + case -ESHUTDOWN: /* disconnect from host */ +free_req: + free_ep_req(ep, req); + return; + } +} + +static void hidg_ssreport_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_hidg *hidg = (struct f_hidg *)req->context; + struct usb_composite_dev *cdev = hidg->func.config->cdev; + char *new_buf = NULL; + unsigned long flags; + + if (req->status != 0 || req->buf == NULL || req->actual == 0) { + ERROR(cdev, + "%s FAILED: status=%d, buf=%p, actual=%d\n", + __func__, req->status, req->buf, req->actual); + return; + } + + spin_lock_irqsave(&hidg->read_spinlock, flags); + + new_buf = krealloc(hidg->set_report_buf, req->actual, GFP_ATOMIC); + if (new_buf == NULL) { + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + return; + } + hidg->set_report_buf = new_buf; + + hidg->set_report_length = req->actual; + memcpy(hidg->set_report_buf, req->buf, req->actual); + + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + + wake_up(&hidg->read_queue); +} + +static void hidg_get_report_complete(struct usb_ep *ep, struct usb_request *req) +{ +} + +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; + unsigned long flags; + + value = __le16_to_cpu(ctrl->wValue); + length = __le16_to_cpu(ctrl->wLength); + + VDBG(cdev, + "%s crtl_request : bRequestType:0x%x bRequest:0x%x Value:0x%x\n", + __func__, 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 | wLength=%d\n", ctrl->wLength); + + /* + * Update GET_REPORT ID so that an ioctl can be used to determine what + * GET_REPORT the request was actually for. + */ + spin_lock_irqsave(&hidg->get_report_spinlock, flags); + hidg->get_report_req_report_id = value & 0xff; + hidg->get_report_req_report_length = length; + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + + queue_work(hidg->workqueue, &hidg->work); + + return status; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_GET_PROTOCOL): + VDBG(cdev, "get_protocol\n"); + length = min_t(unsigned int, length, 1); + ((u8 *) req->buf)[0] = hidg->protocol; + goto respond; + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_GET_IDLE): + VDBG(cdev, "get_idle\n"); + length = min_t(unsigned int, length, 1); + ((u8 *) req->buf)[0] = hidg->idle; + goto respond; + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_SET_REPORT): + VDBG(cdev, "set_report | wLength=%d\n", ctrl->wLength); + if (hidg->use_out_ep) + goto stall; + req->complete = hidg_ssreport_complete; + req->context = hidg; + goto respond; + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_SET_PROTOCOL): + VDBG(cdev, "set_protocol\n"); + if (value > HID_REPORT_PROTOCOL) + goto stall; + length = 0; + /* + * We assume that programs implementing the Boot protocol + * are also compatible with the Report Protocol + */ + if (hidg->bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT) { + hidg->protocol = value; + goto respond; + } + goto stall; + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_SET_IDLE): + VDBG(cdev, "set_idle\n"); + length = 0; + hidg->idle = value >> 8; + goto respond; + break; + + case ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8 + | USB_REQ_GET_DESCRIPTOR): + switch (value >> 8) { + case HID_DT_HID: + { + struct hid_descriptor hidg_desc_copy = hidg_desc; + + VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: HID\n"); + hidg_desc_copy.rpt_desc.bDescriptorType = HID_DT_REPORT; + hidg_desc_copy.rpt_desc.wDescriptorLength = + cpu_to_le16(hidg->report_desc_length); + + length = min_t(unsigned short, length, + hidg_desc_copy.bLength); + memcpy(req->buf, &hidg_desc_copy, 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 = 1; + 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; + unsigned long flags; + + usb_ep_disable(hidg->in_ep); + + if (hidg->out_ep) { + usb_ep_disable(hidg->out_ep); + + spin_lock_irqsave(&hidg->read_spinlock, flags); + list_for_each_entry_safe(list, next, &hidg->completed_out_req, list) { + free_ep_req(hidg->out_ep, list->req); + list_del(&list->list); + kfree(list); + } + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + } + + spin_lock_irqsave(&hidg->get_report_spinlock, flags); + if (!hidg->get_report_returned) { + usb_ep_free_request(f->config->cdev->gadget->ep0, hidg->get_req); + hidg->get_req = NULL; + hidg->get_report_returned = true; + } + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + + spin_lock_irqsave(&hidg->read_spinlock, flags); + hidg->disabled = true; + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + wake_up(&hidg->read_queue); + + spin_lock_irqsave(&hidg->write_spinlock, flags); + if (!hidg->write_pending) { + free_ep_req(hidg->in_ep, hidg->req); + hidg->write_pending = 1; + } + + hidg->req = NULL; + spin_unlock_irqrestore(&hidg->write_spinlock, flags); +} + +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); + struct usb_request *req_in = NULL; + unsigned long flags; + int i, status = 0; + + VDBG(cdev, "hidg_set_alt intf:%d alt:%d\n", intf, alt); + + if (hidg->in_ep != NULL) { + /* restart endpoint */ + 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; + + req_in = hidg_alloc_ep_req(hidg->in_ep, hidg->report_length); + if (!req_in) { + status = -ENOMEM; + goto disable_ep_in; + } + } + + if (hidg->use_out_ep && hidg->out_ep != NULL) { + /* restart endpoint */ + 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 free_req_in; + } + status = usb_ep_enable(hidg->out_ep); + if (status < 0) { + ERROR(cdev, "Enable OUT endpoint FAILED!\n"); + goto free_req_in; + } + 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_intout_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); + free_ep_req(hidg->out_ep, req); + } + } else { + status = -ENOMEM; + goto disable_out_ep; + } + } + } + + spin_lock_irqsave(&hidg->read_spinlock, flags); + hidg->disabled = false; + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + + if (hidg->in_ep != NULL) { + spin_lock_irqsave(&hidg->write_spinlock, flags); + hidg->req = req_in; + hidg->write_pending = 0; + spin_unlock_irqrestore(&hidg->write_spinlock, flags); + + wake_up(&hidg->write_queue); + } + return 0; +disable_out_ep: + if (hidg->out_ep) + usb_ep_disable(hidg->out_ep); +free_req_in: + if (req_in) + free_ep_req(hidg->in_ep, req_in); + +disable_ep_in: + if (hidg->in_ep) + usb_ep_disable(hidg->in_ep); + +fail: + return status; +} + +#ifdef CONFIG_COMPAT +static long f_hidg_compat_ioctl(struct file *file, unsigned int code, + unsigned long value) +{ + return f_hidg_ioctl(file, code, value); +} +#endif + +static 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, + .unlocked_ioctl = f_hidg_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = f_hidg_compat_ioctl, +#endif + .llseek = noop_llseek, +}; + +static int hidg_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_ep *ep; + struct f_hidg *hidg = func_to_hidg(f); + struct usb_string *us; + int status; + + hidg->get_req = usb_ep_alloc_request(c->cdev->gadget->ep0, GFP_ATOMIC); + if (!hidg->get_req) + return -ENOMEM; + + hidg->get_req->zero = 0; + hidg->get_req->complete = hidg_get_report_complete; + hidg->get_req->context = hidg; + hidg->get_report_returned = true; + + /* maybe allocate device-global string IDs, and patch descriptors */ + us = usb_gstrings_attach(c->cdev, ct_func_strings, + ARRAY_SIZE(ct_func_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + hidg_interface_desc.iInterface = us[CT_FUNC_HID_IDX].id; + + /* 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; + hidg->in_ep = ep; + + hidg->out_ep = NULL; + if (hidg->use_out_ep) { + ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_out_ep_desc); + if (!ep) + goto fail; + hidg->out_ep = ep; + } + + /* used only if use_out_ep == 1 */ + hidg->set_report_buf = NULL; + + /* set descriptor dynamic values */ + hidg_interface_desc.bInterfaceSubClass = hidg->bInterfaceSubClass; + hidg_interface_desc.bInterfaceProtocol = hidg->bInterfaceProtocol; + hidg_interface_desc.bNumEndpoints = hidg->use_out_ep ? 2 : 1; + hidg->protocol = HID_REPORT_PROTOCOL; + hidg->idle = 1; + hidg_ss_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + hidg_ss_in_comp_desc.wBytesPerInterval = + cpu_to_le16(hidg->report_length); + 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_ss_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + + /* IN endpoints: FS default=10ms, HS default=4µ-frame; user override if set */ + if (!hidg->interval_user_set) { + hidg_fs_in_ep_desc.bInterval = 10; + hidg_hs_in_ep_desc.bInterval = 4; + hidg_ss_in_ep_desc.bInterval = 4; + } else { + hidg_fs_in_ep_desc.bInterval = hidg->interval; + hidg_hs_in_ep_desc.bInterval = hidg->interval; + hidg_ss_in_ep_desc.bInterval = hidg->interval; + } + + hidg_ss_out_comp_desc.wBytesPerInterval = + 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); + /* + * We can use hidg_desc struct here but we should not relay + * that its content won't change after returning from this function. + */ + hidg_desc.rpt_desc.bDescriptorType = HID_DT_REPORT; + hidg_desc.rpt_desc.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; + + hidg_ss_in_ep_desc.bEndpointAddress = + hidg_fs_in_ep_desc.bEndpointAddress; + hidg_ss_out_ep_desc.bEndpointAddress = + hidg_fs_out_ep_desc.bEndpointAddress; + + if (hidg->use_out_ep) { + /* OUT endpoints: same defaults (FS=10, HS=4) unless user set */ + if (!hidg->interval_user_set) { + hidg_fs_out_ep_desc.bInterval = 10; + hidg_hs_out_ep_desc.bInterval = 4; + hidg_ss_out_ep_desc.bInterval = 4; + } else { + hidg_fs_out_ep_desc.bInterval = hidg->interval; + hidg_hs_out_ep_desc.bInterval = hidg->interval; + hidg_ss_out_ep_desc.bInterval = hidg->interval; + } + status = usb_assign_descriptors(f, + hidg_fs_descriptors_intout, + hidg_hs_descriptors_intout, + hidg_ss_descriptors_intout, + hidg_ss_descriptors_intout); + } else { + status = usb_assign_descriptors(f, + hidg_fs_descriptors_ssreport, + hidg_hs_descriptors_ssreport, + hidg_ss_descriptors_ssreport, + hidg_ss_descriptors_ssreport); + } + if (status) + goto fail; + + hidg->write_pending = 1; + hidg->req = NULL; + + INIT_WORK(&hidg->work, get_report_workqueue_handler); + hidg->workqueue = alloc_workqueue("report_work", + WQ_FREEZABLE | WQ_MEM_RECLAIM | WQ_PERCPU, + 1); + + if (!hidg->workqueue) { + status = -ENOMEM; + goto fail_free_descs; + } + + /* create char device */ + hidg->cdev = cdev_alloc(); + if (!hidg->cdev) { + status = -ENOMEM; + goto fail_free_all; + } + hidg->cdev->ops = &f_hidg_fops; + + status = cdev_device_add(hidg->cdev, &hidg->dev); + if (status) + goto fail_free_all; + + return 0; +fail_free_all: + destroy_workqueue(hidg->workqueue); +fail_free_descs: + usb_free_all_descriptors(f); +fail: + ERROR(f->config->cdev, "hidg_bind FAILED\n"); + if (hidg->req != NULL) + free_ep_req(hidg->in_ep, hidg->req); + + usb_ep_free_request(c->cdev->gadget->ep0, hidg->get_req); + hidg->get_req = NULL; + + return status; +} + +static inline int hidg_get_minor(void) +{ + int ret; + + ret = ida_alloc(&hidg_ida, GFP_KERNEL); + if (ret >= HIDG_MINORS) { + ida_free(&hidg_ida, ret); + ret = -ENODEV; + } + + return ret; +} + +static inline struct f_hid_opts *to_f_hid_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_hid_opts, + func_inst.group); +} + +static void hid_attr_release(struct config_item *item) +{ + struct f_hid_opts *opts = to_f_hid_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static const struct configfs_item_operations hidg_item_ops = { + .release = hid_attr_release, +}; + +#define F_HID_OPT(name, prec, limit) \ +static ssize_t f_hid_opts_##name##_show(struct config_item *item, char *page)\ +{ \ + struct f_hid_opts *opts = to_f_hid_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_hid_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_hid_opts *opts = to_f_hid_opts(item); \ + int ret; \ + u##prec num; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou##prec(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > limit) { \ + ret = -EINVAL; \ + goto end; \ + } \ + opts->name = num; \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_hid_opts_, name) + +F_HID_OPT(subclass, 8, 255); +F_HID_OPT(protocol, 8, 255); +F_HID_OPT(no_out_endpoint, 8, 1); +F_HID_OPT(report_length, 16, 65535); + +static ssize_t f_hid_opts_report_desc_show(struct config_item *item, char *page) +{ + struct f_hid_opts *opts = to_f_hid_opts(item); + int result; + + mutex_lock(&opts->lock); + result = opts->report_desc_length; + memcpy(page, opts->report_desc, opts->report_desc_length); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_hid_opts_report_desc_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_hid_opts *opts = to_f_hid_opts(item); + int ret = -EBUSY; + char *d; + + mutex_lock(&opts->lock); + + if (opts->refcnt) + goto end; + if (len > PAGE_SIZE) { + ret = -ENOSPC; + goto end; + } + d = kmemdup(page, len, GFP_KERNEL); + if (!d) { + ret = -ENOMEM; + goto end; + } + kfree(opts->report_desc); + opts->report_desc = d; + opts->report_desc_length = len; + opts->report_desc_alloc = true; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_hid_opts_, report_desc); + +static ssize_t f_hid_opts_interval_show(struct config_item *item, char *page) +{ + struct f_hid_opts *opts = to_f_hid_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d\n", opts->interval); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_hid_opts_interval_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_hid_opts *opts = to_f_hid_opts(item); + int ret; + unsigned int tmp; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + /* parse into a wider type first */ + ret = kstrtouint(page, 0, &tmp); + if (ret) + goto end; + + /* range-check against unsigned char max */ + if (tmp > 255) { + ret = -EINVAL; + goto end; + } + + opts->interval = (unsigned char)tmp; + opts->interval_user_set = true; + ret = len; + +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_hid_opts_, interval); + +static ssize_t f_hid_opts_dev_show(struct config_item *item, char *page) +{ + struct f_hid_opts *opts = to_f_hid_opts(item); + + return sprintf(page, "%d:%d\n", major, opts->minor); +} + +CONFIGFS_ATTR_RO(f_hid_opts_, dev); + +static struct configfs_attribute *hid_attrs[] = { + &f_hid_opts_attr_subclass, + &f_hid_opts_attr_protocol, + &f_hid_opts_attr_no_out_endpoint, + &f_hid_opts_attr_report_length, + &f_hid_opts_attr_interval, + &f_hid_opts_attr_report_desc, + &f_hid_opts_attr_dev, + NULL, +}; + +static const struct config_item_type hid_func_type = { + .ct_item_ops = &hidg_item_ops, + .ct_attrs = hid_attrs, + .ct_owner = THIS_MODULE, +}; + +static inline void hidg_put_minor(int minor) +{ + ida_free(&hidg_ida, minor); +} + +static void hidg_free_inst(struct usb_function_instance *f) +{ + struct f_hid_opts *opts; + + opts = container_of(f, struct f_hid_opts, func_inst); + + mutex_lock(&hidg_ida_lock); + + hidg_put_minor(opts->minor); + if (ida_is_empty(&hidg_ida)) + ghid_cleanup(); + + mutex_unlock(&hidg_ida_lock); + + if (opts->report_desc_alloc) + kfree(opts->report_desc); + + kfree(opts); +} + +static struct usb_function_instance *hidg_alloc_inst(void) +{ + struct f_hid_opts *opts; + struct usb_function_instance *ret; + int status = 0; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + mutex_init(&opts->lock); + + opts->interval = 4; + opts->interval_user_set = false; + + opts->func_inst.free_func_inst = hidg_free_inst; + ret = &opts->func_inst; + + mutex_lock(&hidg_ida_lock); + + if (ida_is_empty(&hidg_ida)) { + status = ghid_setup(NULL, HIDG_MINORS); + if (status) { + ret = ERR_PTR(status); + kfree(opts); + goto unlock; + } + } + + opts->minor = hidg_get_minor(); + if (opts->minor < 0) { + ret = ERR_PTR(opts->minor); + kfree(opts); + if (ida_is_empty(&hidg_ida)) + ghid_cleanup(); + goto unlock; + } + config_group_init_type_name(&opts->func_inst.group, "", &hid_func_type); + +unlock: + mutex_unlock(&hidg_ida_lock); + return ret; +} + +static void hidg_free(struct usb_function *f) +{ + struct f_hidg *hidg; + struct f_hid_opts *opts; + + hidg = func_to_hidg(f); + opts = container_of(f->fi, struct f_hid_opts, func_inst); + put_device(&hidg->dev); + mutex_lock(&opts->lock); + --opts->refcnt; + mutex_unlock(&opts->lock); +} + +static void hidg_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_hidg *hidg = func_to_hidg(f); + + cdev_device_del(hidg->cdev, &hidg->dev); + destroy_workqueue(hidg->workqueue); + usb_free_all_descriptors(f); +} + +static struct usb_function *hidg_alloc(struct usb_function_instance *fi) +{ + struct f_hidg *hidg; + struct f_hid_opts *opts; + int ret; + + /* allocate and initialize one new instance */ + hidg = kzalloc_obj(*hidg); + if (!hidg) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_hid_opts, func_inst); + + mutex_lock(&opts->lock); + + spin_lock_init(&hidg->write_spinlock); + spin_lock_init(&hidg->read_spinlock); + spin_lock_init(&hidg->get_report_spinlock); + init_waitqueue_head(&hidg->write_queue); + init_waitqueue_head(&hidg->read_queue); + init_waitqueue_head(&hidg->get_queue); + init_waitqueue_head(&hidg->get_id_queue); + INIT_LIST_HEAD(&hidg->completed_out_req); + INIT_LIST_HEAD(&hidg->report_list); + + device_initialize(&hidg->dev); + hidg->dev.release = hidg_release; + hidg->dev.class = &hidg_class; + hidg->dev.devt = MKDEV(major, opts->minor); + ret = dev_set_name(&hidg->dev, "hidg%d", opts->minor); + if (ret) + goto err_put_device; + + hidg->bInterfaceSubClass = opts->subclass; + hidg->bInterfaceProtocol = opts->protocol; + hidg->report_length = opts->report_length; + hidg->report_desc_length = opts->report_desc_length; + hidg->interval = opts->interval; + hidg->interval_user_set = opts->interval_user_set; + if (opts->report_desc) { + hidg->report_desc = kmemdup(opts->report_desc, + opts->report_desc_length, + GFP_KERNEL); + if (!hidg->report_desc) { + ret = -ENOMEM; + goto err_put_device; + } + } + hidg->use_out_ep = !opts->no_out_endpoint; + + ++opts->refcnt; + mutex_unlock(&opts->lock); + + hidg->func.name = "hid"; + 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; + hidg->func.free_func = hidg_free; + + /* this could be made configurable at some point */ + hidg->qlen = 4; + + return &hidg->func; + +err_put_device: + put_device(&hidg->dev); + mutex_unlock(&opts->lock); + return ERR_PTR(ret); +} + +DECLARE_USB_FUNCTION_INIT(hid, hidg_alloc_inst, hidg_alloc); +MODULE_DESCRIPTION("USB HID function driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Fabien Chouteau"); + +int ghid_setup(struct usb_gadget *g, int count) +{ + int status; + dev_t dev; + + status = class_register(&hidg_class); + if (status) + return status; + + status = alloc_chrdev_region(&dev, 0, count, "hidg"); + if (status) { + class_unregister(&hidg_class); + return status; + } + + major = MAJOR(dev); + minors = count; + + return 0; +} + +void ghid_cleanup(void) +{ + if (major) { + unregister_chrdev_region(MKDEV(major, 0), minors); + major = minors = 0; + } + + class_unregister(&hidg_class); +} diff --git a/drivers/usb/gadget/function/f_loopback.c b/drivers/usb/gadget/function/f_loopback.c new file mode 100644 index 000000000..d2d07fb49 --- /dev/null +++ b/drivers/usb/gadget/function/f_loopback.c @@ -0,0 +1,597 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_loopback.c - USB peripheral loopback configuration driver + * + * Copyright (C) 2003-2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + */ + +/* #define VERBOSE_DEBUG */ + +#include +#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. + */ +struct f_loopback { + struct usb_function function; + + struct usb_ep *in_ep; + struct usb_ep *out_ep; + + unsigned qlen; + unsigned buflen; +}; + +static inline struct f_loopback *func_to_loop(struct usb_function *f) +{ + return container_of(f, struct f_loopback, function); +} + +/*-------------------------------------------------------------------------*/ + +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), +}; + +static 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), +}; + +static 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->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc); + if (!loop->out_ep) + goto autoconf_fail; + + /* 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, ss_loopback_descs); + if (ret) + return ret; + + DBG(cdev, "%s: IN/%s, OUT/%s\n", + f->name, loop->in_ep->name, loop->out_ep->name); + return 0; +} + +static void lb_free_func(struct usb_function *f) +{ + struct f_lb_opts *opts; + + opts = container_of(f->fi, struct f_lb_opts, func_inst); + + mutex_lock(&opts->lock); + opts->refcnt--; + mutex_unlock(&opts->lock); + + 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) { + /* + * We received some data from the host so let's + * queue it so host can read the from our in ep + */ + struct usb_request *in_req = req->context; + + in_req->zero = (req->actual < req->length); + in_req->length = req->actual; + ep = loop->in_ep; + req = in_req; + } else { + /* + * We have just looped back a bunch of data + * to host. Now let's wait for some more data. + */ + req = req->context; + ep = loop->out_ep; + } + + /* queue the buffer back to host or for next bunch of data */ + status = usb_ep_queue(ep, req, GFP_ATOMIC); + if (status == 0) { + return; + } else { + ERROR(cdev, "Unable to loop back buffer to %s: %d\n", + ep->name, status); + goto free_req; + } + + /* "should never get here" */ + 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_req: + usb_ep_free_request(ep == loop->in_ep ? + loop->out_ep : loop->in_ep, + req->context); + 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 inline struct usb_request *lb_alloc_ep_req(struct usb_ep *ep, int len) +{ + return alloc_ep_req(ep, len); +} + +static int alloc_requests(struct usb_composite_dev *cdev, + struct f_loopback *loop) +{ + struct usb_request *in_req, *out_req; + int i; + int result = 0; + + /* + * allocate a bunch of read buffers and queue them all at once. + * we buffer at most 'qlen' transfers; We allocate buffers only + * for out transfer and reuse them in IN transfers to implement + * our loopback functionality + */ + for (i = 0; i < loop->qlen && result == 0; i++) { + result = -ENOMEM; + + in_req = usb_ep_alloc_request(loop->in_ep, GFP_ATOMIC); + if (!in_req) + goto fail; + + out_req = lb_alloc_ep_req(loop->out_ep, loop->buflen); + if (!out_req) + goto fail_in; + + in_req->complete = loopback_complete; + out_req->complete = loopback_complete; + + in_req->buf = out_req->buf; + /* length will be set in complete routine */ + in_req->context = out_req; + out_req->context = in_req; + + result = usb_ep_queue(loop->out_ep, out_req, GFP_ATOMIC); + if (result) { + ERROR(cdev, "%s queue req --> %d\n", + loop->out_ep->name, result); + goto fail_out; + } + } + + return 0; + +fail_out: + free_ep_req(loop->out_ep, out_req); +fail_in: + usb_ep_free_request(loop->in_ep, in_req); +fail: + return result; +} + +static int enable_endpoint(struct usb_composite_dev *cdev, + struct f_loopback *loop, struct usb_ep *ep) +{ + int result; + + result = config_ep_by_speed(cdev->gadget, &(loop->function), ep); + if (result) + goto out; + + result = usb_ep_enable(ep); + if (result < 0) + goto out; + ep->driver_data = loop; + result = 0; + +out: + return result; +} + +static int +enable_loopback(struct usb_composite_dev *cdev, struct f_loopback *loop) +{ + int result = 0; + + result = enable_endpoint(cdev, loop, loop->in_ep); + if (result) + goto out; + + result = enable_endpoint(cdev, loop, loop->out_ep); + if (result) + goto disable_in; + + result = alloc_requests(cdev, loop); + if (result) + goto disable_out; + + DBG(cdev, "%s enabled\n", loop->function.name); + return 0; + +disable_out: + usb_ep_disable(loop->out_ep); +disable_in: + usb_ep_disable(loop->in_ep); +out: + 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 */ + 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_obj(*loop); + if (!loop) + return ERR_PTR(-ENOMEM); + + lb_opts = container_of(fi, struct f_lb_opts, func_inst); + + mutex_lock(&lb_opts->lock); + lb_opts->refcnt++; + mutex_unlock(&lb_opts->lock); + + loop->buflen = lb_opts->bulk_buflen; + loop->qlen = lb_opts->qlen; + if (!loop->qlen) + loop->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 inline struct f_lb_opts *to_f_lb_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_lb_opts, + func_inst.group); +} + +static void lb_attr_release(struct config_item *item) +{ + struct f_lb_opts *lb_opts = to_f_lb_opts(item); + + usb_put_function_instance(&lb_opts->func_inst); +} + +static const struct configfs_item_operations lb_item_ops = { + .release = lb_attr_release, +}; + +static ssize_t f_lb_opts_qlen_show(struct config_item *item, char *page) +{ + struct f_lb_opts *opts = to_f_lb_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d\n", opts->qlen); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_lb_opts_qlen_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_lb_opts *opts = to_f_lb_opts(item); + int ret; + u32 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou32(page, 0, &num); + if (ret) + goto end; + + opts->qlen = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_lb_opts_, qlen); + +static ssize_t f_lb_opts_bulk_buflen_show(struct config_item *item, char *page) +{ + struct f_lb_opts *opts = to_f_lb_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d\n", opts->bulk_buflen); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_lb_opts_bulk_buflen_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_lb_opts *opts = to_f_lb_opts(item); + int ret; + u32 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou32(page, 0, &num); + if (ret) + goto end; + + opts->bulk_buflen = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_lb_opts_, bulk_buflen); + +static struct configfs_attribute *lb_attrs[] = { + &f_lb_opts_attr_qlen, + &f_lb_opts_attr_bulk_buflen, + NULL, +}; + +static const struct config_item_type lb_func_type = { + .ct_item_ops = &lb_item_ops, + .ct_attrs = lb_attrs, + .ct_owner = THIS_MODULE, +}; + +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_obj(*lb_opts); + if (!lb_opts) + return ERR_PTR(-ENOMEM); + mutex_init(&lb_opts->lock); + lb_opts->func_inst.free_func_inst = lb_free_instance; + lb_opts->bulk_buflen = GZERO_BULK_BUFLEN; + lb_opts->qlen = GZERO_QLEN; + + config_group_init_type_name(&lb_opts->func_inst.group, "", + &lb_func_type); + + return &lb_opts->func_inst; +} +DECLARE_USB_FUNCTION(Loopback, loopback_alloc_instance, loopback_alloc); + +int __init lb_modinit(void) +{ + return usb_function_register(&Loopbackusb_func); +} + +void __exit lb_modexit(void) +{ + usb_function_unregister(&Loopbackusb_func); +} + +MODULE_DESCRIPTION("USB peripheral loopback configuration driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c new file mode 100644 index 000000000..a50743caf --- /dev/null +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -0,0 +1,3623 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * 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. + */ + +/* + * 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). + * 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 +#include +#include + +#include +#include +#include + +#include + +#include "configfs.h" + + +/*------------------------------------------------------------------------*/ + +#define FSG_DRIVER_DESC "Mass Storage Function" + +static const char fsg_string_interface[] = "Mass Storage"; + +#include "storage_common.h" +#include "f_mass_storage.h" + +/* 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, +}; + +static struct usb_gadget_strings *fsg_strings_array[] = { + &fsg_stringtab, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +struct fsg_dev; +struct fsg_common; + +/* Data shared by all the FSG instances. */ +struct fsg_common { + struct usb_gadget *gadget; + struct usb_composite_dev *cdev; + struct fsg_dev *fsg; + wait_queue_head_t io_wait; + wait_queue_head_t fsg_wait; + + /* filesem protects: backing files in use */ + struct rw_semaphore filesem; + + /* lock protects: state and thread_task */ + 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; + unsigned int fsg_num_buffers; + + int cmnd_size; + u8 cmnd[MAX_COMMAND_SIZE]; + + unsigned int lun; + struct fsg_lun *luns[FSG_MAX_LUNS]; + struct fsg_lun *curlun; + + unsigned int bulk_out_maxpacket; + enum fsg_state state; /* For exception handling */ + unsigned int exception_req_tag; + void *exception_arg; + + 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; + unsigned int sysfs:1; + + struct completion thread_notifier; + struct task_struct *thread_task; + + /* Gadget's private data. */ + void *private_data; + + char inquiry_string[INQUIRY_STRING_LEN]; +}; + +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); +} + +static int exception_in_progress(struct fsg_common *common) +{ + return common->state > FSG_STATE_NORMAL; +} + +/* 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 */ + +static void __raise_exception(struct fsg_common *common, enum fsg_state new_state, + void *arg) +{ + 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. + */ + spin_lock_irqsave(&common->lock, flags); + if (common->state <= new_state) { + common->exception_req_tag = common->ep0_req_tag; + common->state = new_state; + common->exception_arg = arg; + if (common->thread_task) + send_sig_info(SIGUSR1, SEND_SIG_PRIV, + common->thread_task); + } + spin_unlock_irqrestore(&common->lock, flags); +} + +static void raise_exception(struct fsg_common *common, enum fsg_state new_state) +{ + __raise_exception(common, new_state, NULL); +} + +/*-------------------------------------------------------------------------*/ + +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); + + /* Synchronize with the smp_load_acquire() in sleep_thread() */ + smp_store_release(&bh->state, BUF_STATE_EMPTY); + wake_up(&common->io_wait); +} + +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); + + /* Synchronize with the smp_load_acquire() in sleep_thread() */ + smp_store_release(&bh->state, BUF_STATE_FULL); + wake_up(&common->io_wait); +} + +static int _fsg_common_get_max_lun(struct fsg_common *common) +{ + int i = ARRAY_SIZE(common->luns) - 1; + + while (i >= 0 && !common->luns[i]) + --i; + + return i; +} + +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_PROTOCOL_RESET); + return USB_GADGET_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_get_max_lun(fsg->common); + + /* Respond with data/status */ + req->length = min_t(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 int start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, + struct usb_request *req) +{ + int rc; + + if (ep == fsg->bulk_in) + dump_msg(fsg, "bulk-in", req->buf, req->length); + + rc = usb_ep_queue(ep, req, GFP_KERNEL); + if (rc) { + + /* We can't do much more than wait for a reset */ + req->status = rc; + + /* + * 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); + } + return rc; +} + +static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh) +{ + int rc; + + if (!fsg_is_set(common)) + return false; + bh->state = BUF_STATE_SENDING; + rc = start_transfer(common->fsg, common->fsg->bulk_in, bh->inreq); + if (rc) { + bh->state = BUF_STATE_EMPTY; + if (rc == -ESHUTDOWN) { + common->running = 0; + return false; + } + } + return true; +} + +static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh) +{ + int rc; + + if (!fsg_is_set(common)) + return false; + bh->state = BUF_STATE_RECEIVING; + rc = start_transfer(common->fsg, common->fsg->bulk_out, bh->outreq); + if (rc) { + bh->state = BUF_STATE_FULL; + if (rc == -ESHUTDOWN) { + common->running = 0; + return false; + } + } + return true; +} + +static int sleep_thread(struct fsg_common *common, bool can_freeze, + struct fsg_buffhd *bh) +{ + int rc; + + /* Wait until a signal arrives or bh is no longer busy */ + if (can_freeze) + /* + * synchronize with the smp_store_release(&bh->state) in + * bulk_in_complete() or bulk_out_complete() + */ + rc = wait_event_freezable(common->io_wait, + bh && smp_load_acquire(&bh->state) >= + BUF_STATE_EMPTY); + else + rc = wait_event_interruptible(common->io_wait, + bh && smp_load_acquire(&bh->state) >= + BUF_STATE_EMPTY); + return rc ? -EINTR : 0; +} + + +/*-------------------------------------------------------------------------*/ + +static int do_read(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + u64 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 { + if (common->cmnd[0] == READ_16) + lba = get_unaligned_be64(&common->cmnd[2]); + else /* READ_10 or READ_12 */ + 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_t(loff_t, amount, + curlun->file_length - file_offset); + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + rc = sleep_thread(common, false, bh); + 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 = kernel_read(curlun->filp, 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; + u64 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 { + if (common->cmnd[0] == WRITE_16) + lba = get_unaligned_be64(&common->cmnd[2]); + else /* WRITE_10 or WRITE_12 */ + 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 */ + + /* Wait for the data to be received */ + rc = sleep_thread(common, false, bh); + if (rc) + return rc; + + 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 = kernel_write(curlun->filp, 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; + } + } + + 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 __maybe_unused 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_t(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 = kernel_read(curlun->filp, 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] = TYPE_NO_LUN; /* 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; + if (curlun->inquiry_string[0]) + memcpy(buf + 8, curlun->inquiry_string, + sizeof(curlun->inquiry_string)); + else + 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; + u32 max_lba; + + /* Check the PMI and LBA fields */ + if (pmi > 1 || (pmi == 0 && lba != 0)) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + if (curlun->num_sectors < 0x100000000ULL) + max_lba = curlun->num_sectors - 1; + else + max_lba = 0xffffffff; + put_unaligned_be32(max_lba, &buf[0]); /* Max logical block */ + put_unaligned_be32(curlun->blksize, &buf[4]); /* Block length */ + return 8; +} + +static int do_read_capacity_16(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + u64 lba = get_unaligned_be64(&common->cmnd[2]); + int pmi = common->cmnd[14]; + 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_be64(curlun->num_sectors - 1, &buf[0]); + /* Max logical block */ + put_unaligned_be32(curlun->blksize, &buf[8]); /* Block length */ + + /* It is safe to keep other fields zeroed */ + memset(&buf[12], 0, 32 - 12); + return 32; +} + +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; + u8 format; + int i, len; + + format = common->cmnd[2] & 0xf; + + if ((common->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */ + (start_track > 1 && format != 0x1)) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + /* + * Check if CDB is old style SFF-8020i + * i.e. format is in 2 MSBs of byte 9 + * Mac OS-X host sends us this. + */ + if (format == 0) + format = (common->cmnd[9] >> 6) & 0x3; + + switch (format) { + case 0: /* Formatted TOC */ + case 1: /* Multi-session info */ + len = 4 + 2*8; /* 4 byte header + 2 descriptors */ + memset(buf, 0, len); + buf[1] = len - 2; /* TOC Length excludes length field */ + 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 len; + + case 2: + /* Raw TOC */ + len = 4 + 3*11; /* 4 byte header + 3 descriptors */ + memset(buf, 0, len); /* Header + A0, A1 & A2 descriptors */ + buf[1] = len - 2; /* TOC Length excludes length field */ + buf[2] = 1; /* First complete session */ + buf[3] = 1; /* Last complete session */ + + buf += 4; + /* fill in A0, A1 and A2 points */ + for (i = 0; i < 3; i++) { + buf[0] = 1; /* Session number */ + buf[1] = 0x16; /* Data track, copying allowed */ + /* 2 - Track number 0 -> TOC */ + buf[3] = 0xA0 + i; /* A0, A1, A2 point */ + /* 4, 5, 6 - Min, sec, frame is zero */ + buf[8] = 1; /* Pmin: last track number */ + buf += 11; /* go to next track descriptor */ + } + buf -= 11; /* go back to A2 descriptor */ + + /* For A2, 7, 8, 9, 10 - zero, Pmin, Psec, Pframe of Lead out */ + store_cdrom_address(&buf[7], msf, curlun->num_sectors); + return len; + + default: + /* PMA, ATIP, CD-TEXT not supported/required */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } +} + +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->prevent_medium_removal && !prevent) + fsg_lun_fsync_sub(curlun); + curlun->prevent_medium_removal = prevent; + return 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, *bh2; + 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) { + + /* Try to submit another request if we need one */ + bh2 = common->next_buffhd_to_fill; + if (bh2->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, bh2, amount); + if (!start_out_transfer(common, bh2)) + /* Dunno what to do if common->fsg is NULL */ + return -EIO; + common->next_buffhd_to_fill = bh2->next; + common->usb_amount_left -= amount; + continue; + } + + /* Wait for the data to be received */ + rc = sleep_thread(common, false, bh); + if (rc) + return rc; + + /* Throw away the data in a filled buffer */ + 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; + } + } + 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; + 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 void 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; + rc = sleep_thread(common, false, bh); + if (rc) + return; + + 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; + + common->next_buffhd_to_fill = bh->next; + return; +} + + +/*-------------------------------------------------------------------------*/ + +/* + * 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) { + if (check_shl_overflow(common->data_size_from_cmnd, + common->curlun->blkbits, + &common->data_size_from_cmnd)) { + common->phase_error = 1; + return -EINVAL; + } + } + + 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; + rc = sleep_thread(common, false, bh); + 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_16: + common->data_size_from_cmnd = + get_unaligned_be32(&common->cmnd[10]); + reply = check_command_size_in_blocks(common, 16, + DATA_DIR_TO_HOST, + (1<<1) | (0xff<<2) | (0xf<<10), 1, + "READ(16)"); + 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 SERVICE_ACTION_IN_16: + switch (common->cmnd[1] & 0x1f) { + + case SAI_READ_CAPACITY_16: + common->data_size_from_cmnd = + get_unaligned_be32(&common->cmnd[10]); + reply = check_command(common, 16, DATA_DIR_TO_HOST, + (1<<1) | (0xff<<2) | (0xf<<10) | + (1<<14), 1, + "READ CAPACITY(16)"); + if (reply == 0) + reply = do_read_capacity_16(common, bh); + break; + + default: + goto unknown_cmnd; + } + 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; + + case WRITE_16: + common->data_size_from_cmnd = + get_unaligned_be32(&common->cmnd[10]); + reply = check_command_size_in_blocks(common, 16, + DATA_DIR_FROM_HOST, + (1<<1) | (0xff<<2) | (0xf<<10), 1, + "WRITE(16)"); + 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_6: + case RESERVE_6: + case SEND_DIAGNOSTIC: + + 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_t(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 >= ARRAY_SIZE(common->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 < ARRAY_SIZE(common->luns)) + 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; + rc = sleep_thread(common, true, bh); + 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 */ + rc = sleep_thread(common, true, bh); + if (rc) + return rc; + + 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 < common->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; + + 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 < common->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 < ARRAY_SIZE(common->luns); ++i) + if (common->luns[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); + + __raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE, fsg); + return USB_GADGET_DELAYED_STATUS; +} + +static void fsg_disable(struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + + /* 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; + } + + __raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE, NULL); +} + + +/*-------------------------------------------------------------------------*/ + +static void handle_exception(struct fsg_common *common) +{ + int i; + struct fsg_buffhd *bh; + enum fsg_state old_state; + struct fsg_lun *curlun; + unsigned int exception_req_tag; + struct fsg_dev *new_fsg; + + /* + * Clear the existing signals. Anything but SIGUSR1 is converted + * into a high-priority EXIT exception. + */ + for (;;) { + int sig = kernel_dequeue_signal(); + if (!sig) + break; + if (sig != SIGUSR1) { + spin_lock_irq(&common->lock); + if (common->state < FSG_STATE_EXIT) + DBG(common, "Main thread exiting on signal\n"); + common->state = FSG_STATE_EXIT; + spin_unlock_irq(&common->lock); + } + } + + /* Cancel all the pending transfers */ + if (likely(common->fsg)) { + for (i = 0; i < common->fsg_num_buffers; ++i) { + bh = &common->buffhds[i]; + if (bh->state == BUF_STATE_SENDING) + usb_ep_dequeue(common->fsg->bulk_in, bh->inreq); + if (bh->state == BUF_STATE_RECEIVING) + usb_ep_dequeue(common->fsg->bulk_out, + bh->outreq); + + /* Wait for a transfer to become idle */ + if (sleep_thread(common, false, bh)) + 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 < common->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; + new_fsg = common->exception_arg; + old_state = common->state; + common->state = FSG_STATE_NORMAL; + + if (old_state != FSG_STATE_ABORT_BULK_OUT) { + for (i = 0; i < ARRAY_SIZE(common->luns); ++i) { + curlun = common->luns[i]; + if (!curlun) + continue; + 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; + } + } + spin_unlock_irq(&common->lock); + + /* Carry out any extra actions required for the exception */ + switch (old_state) { + case FSG_STATE_NORMAL: + break; + + case FSG_STATE_ABORT_BULK_OUT: + send_status(common); + break; + + case FSG_STATE_PROTOCOL_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->ARRAY_SIZE(common->luns); ++i) */ + /* if (common->luns[i]) */ + /* common->luns[i]->unit_attention_data = */ + /* SS_RESET_OCCURRED; */ + break; + + case FSG_STATE_CONFIG_CHANGE: + do_set_interface(common, new_fsg); + if (new_fsg) + usb_composite_setup_continue(common->cdev); + break; + + case FSG_STATE_EXIT: + 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_TERMINATED: + break; + } +} + + +/*-------------------------------------------------------------------------*/ + +static int fsg_main_thread(void *common_) +{ + struct fsg_common *common = common_; + int i; + + /* + * 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(); + + /* 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, true, NULL); + continue; + } + + if (get_next_command(common) || exception_in_progress(common)) + continue; + if (do_scsi_command(common) || exception_in_progress(common)) + continue; + if (finish_reply(common) || exception_in_progress(common)) + continue; + send_status(common); + } + + spin_lock_irq(&common->lock); + common->thread_task = NULL; + spin_unlock_irq(&common->lock); + + /* Eject media from all LUNs */ + + down_write(&common->filesem); + for (i = 0; i < ARRAY_SIZE(common->luns); i++) { + struct fsg_lun *curlun = common->luns[i]; + + if (curlun && fsg_lun_is_open(curlun)) + fsg_lun_close(curlun); + } + up_write(&common->filesem); + + /* Let fsg_unbind() know the thread has exited */ + kthread_complete_and_exit(&common->thread_notifier, 0); +} + + +/*************************** DEVICE ATTRIBUTES ***************************/ + +static ssize_t ro_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + + return fsg_show_ro(curlun, buf); +} + +static ssize_t nofua_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + + return fsg_show_nofua(curlun, buf); +} + +static ssize_t file_show(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); + + return fsg_show_file(curlun, filesem, buf); +} + +static ssize_t ro_store(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); + + return fsg_store_ro(curlun, filesem, buf, count); +} + +static ssize_t nofua_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + + return fsg_store_nofua(curlun, buf, count); +} + +static ssize_t file_store(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); + + return fsg_store_file(curlun, filesem, buf, count); +} + +static ssize_t forced_eject_store(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); + + return fsg_store_forced_eject(curlun, filesem, buf, count); +} + +static DEVICE_ATTR_RW(nofua); +static DEVICE_ATTR_WO(forced_eject); + +/* + * Mode of the ro and file attribute files will be overridden in + * fsg_lun_dev_is_visible() depending on if this is a cdrom, or if it is a + * removable device. + */ +static DEVICE_ATTR_RW(ro); +static DEVICE_ATTR_RW(file); + +/****************************** FSG COMMON ******************************/ + +static void fsg_lun_release(struct device *dev) +{ + /* Nothing needs to be done */ +} + +static struct fsg_common *fsg_common_setup(struct fsg_common *common) +{ + if (!common) { + common = kzalloc_obj(*common); + if (!common) + return ERR_PTR(-ENOMEM); + common->free_storage_on_release = 1; + } else { + common->free_storage_on_release = 0; + } + init_rwsem(&common->filesem); + spin_lock_init(&common->lock); + init_completion(&common->thread_notifier); + init_waitqueue_head(&common->io_wait); + init_waitqueue_head(&common->fsg_wait); + common->state = FSG_STATE_TERMINATED; + memset(common->luns, 0, sizeof(common->luns)); + + return common; +} + +void fsg_common_set_sysfs(struct fsg_common *common, bool sysfs) +{ + common->sysfs = sysfs; +} +EXPORT_SYMBOL_GPL(fsg_common_set_sysfs); + +static void _fsg_common_free_buffers(struct fsg_buffhd *buffhds, unsigned n) +{ + if (buffhds) { + struct fsg_buffhd *bh = buffhds; + while (n--) { + kfree(bh->buf); + ++bh; + } + kfree(buffhds); + } +} + +int fsg_common_set_num_buffers(struct fsg_common *common, unsigned int n) +{ + struct fsg_buffhd *bh, *buffhds; + int i; + + buffhds = kzalloc_objs(*buffhds, n); + if (!buffhds) + return -ENOMEM; + + /* Data buffers cyclic list */ + bh = buffhds; + i = n; + goto buffhds_first_it; + do { + bh->next = bh + 1; + ++bh; +buffhds_first_it: + bh->buf = kmalloc(FSG_BUFLEN, GFP_KERNEL); + if (unlikely(!bh->buf)) + goto error_release; + } while (--i); + bh->next = buffhds; + + _fsg_common_free_buffers(common->buffhds, common->fsg_num_buffers); + common->fsg_num_buffers = n; + common->buffhds = buffhds; + + return 0; + +error_release: + /* + * "buf"s pointed to by heads after n - i are NULL + * so releasing them won't hurt + */ + _fsg_common_free_buffers(buffhds, n); + + return -ENOMEM; +} +EXPORT_SYMBOL_GPL(fsg_common_set_num_buffers); + +void fsg_common_remove_lun(struct fsg_lun *lun) +{ + if (device_is_registered(&lun->dev)) + device_unregister(&lun->dev); + fsg_lun_close(lun); + kfree(lun); +} +EXPORT_SYMBOL_GPL(fsg_common_remove_lun); + +static void _fsg_common_remove_luns(struct fsg_common *common, int n) +{ + int i; + + for (i = 0; i < n; ++i) + if (common->luns[i]) { + fsg_common_remove_lun(common->luns[i]); + common->luns[i] = NULL; + } +} + +void fsg_common_remove_luns(struct fsg_common *common) +{ + _fsg_common_remove_luns(common, ARRAY_SIZE(common->luns)); +} +EXPORT_SYMBOL_GPL(fsg_common_remove_luns); + +void fsg_common_free_buffers(struct fsg_common *common) +{ + _fsg_common_free_buffers(common->buffhds, common->fsg_num_buffers); + common->buffhds = NULL; +} +EXPORT_SYMBOL_GPL(fsg_common_free_buffers); + +int fsg_common_set_cdev(struct fsg_common *common, + struct usb_composite_dev *cdev, bool can_stall) +{ + struct usb_string *us; + + common->gadget = cdev->gadget; + common->ep0 = cdev->gadget->ep0; + common->ep0req = cdev->req; + common->cdev = cdev; + + us = usb_gstrings_attach(cdev, fsg_strings_array, + ARRAY_SIZE(fsg_strings)); + if (IS_ERR(us)) + return PTR_ERR(us); + + fsg_intf_desc.iInterface = us[FSG_STRING_INTERFACE].id; + + /* + * 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 = can_stall && + gadget_is_stall_supported(common->gadget); + + return 0; +} +EXPORT_SYMBOL_GPL(fsg_common_set_cdev); + +static struct attribute *fsg_lun_dev_attrs[] = { + &dev_attr_ro.attr, + &dev_attr_file.attr, + &dev_attr_nofua.attr, + &dev_attr_forced_eject.attr, + NULL +}; + +static umode_t fsg_lun_dev_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + struct device *dev = kobj_to_dev(kobj); + struct fsg_lun *lun = fsg_lun_from_dev(dev); + + if (attr == &dev_attr_ro.attr) + return lun->cdrom ? S_IRUGO : (S_IWUSR | S_IRUGO); + if (attr == &dev_attr_file.attr) + return lun->removable ? (S_IWUSR | S_IRUGO) : S_IRUGO; + return attr->mode; +} + +static const struct attribute_group fsg_lun_dev_group = { + .attrs = fsg_lun_dev_attrs, + .is_visible = fsg_lun_dev_is_visible, +}; + +static const struct attribute_group *fsg_lun_dev_groups[] = { + &fsg_lun_dev_group, + NULL +}; + +int fsg_common_create_lun(struct fsg_common *common, struct fsg_lun_config *cfg, + unsigned int id, const char *name, + const char **name_pfx) +{ + struct fsg_lun *lun; + char *pathbuf = NULL, *p = "(no medium)"; + int rc = -ENOMEM; + + if (id >= ARRAY_SIZE(common->luns)) + return -ENODEV; + + if (common->luns[id]) + return -EBUSY; + + if (!cfg->filename && !cfg->removable) { + pr_err("no file given for LUN%d\n", id); + return -EINVAL; + } + + lun = kzalloc_obj(*lun); + if (!lun) + return -ENOMEM; + + lun->name_pfx = name_pfx; + + lun->cdrom = !!cfg->cdrom; + lun->ro = cfg->cdrom || cfg->ro; + lun->initially_ro = lun->ro; + lun->removable = !!cfg->removable; + + if (!common->sysfs) { + /* we DON'T own the name!*/ + lun->name = name; + } else { + lun->dev.release = fsg_lun_release; + lun->dev.parent = &common->gadget->dev; + lun->dev.groups = fsg_lun_dev_groups; + dev_set_drvdata(&lun->dev, &common->filesem); + dev_set_name(&lun->dev, "%s", name); + lun->name = dev_name(&lun->dev); + + rc = device_register(&lun->dev); + if (rc) { + pr_info("failed to register LUN%d: %d\n", id, rc); + put_device(&lun->dev); + goto error_sysfs; + } + } + + common->luns[id] = lun; + + if (cfg->filename) { + rc = fsg_lun_open(lun, cfg->filename); + if (rc) + goto error_lun; + + p = "(error)"; + pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); + if (pathbuf) { + p = file_path(lun->filp, pathbuf, PATH_MAX); + if (IS_ERR(p)) + p = "(error)"; + } + } + pr_info("LUN: %s%s%sfile: %s\n", + lun->removable ? "removable " : "", + lun->ro ? "read only " : "", + lun->cdrom ? "CD-ROM " : "", + p); + kfree(pathbuf); + + return 0; + +error_lun: + if (device_is_registered(&lun->dev)) + device_unregister(&lun->dev); + common->luns[id] = NULL; +error_sysfs: + kfree(lun); + return rc; +} +EXPORT_SYMBOL_GPL(fsg_common_create_lun); + +int fsg_common_create_luns(struct fsg_common *common, struct fsg_config *cfg) +{ + char buf[8]; /* enough for 100000000 different numbers, decimal */ + int i, rc; + + fsg_common_remove_luns(common); + + for (i = 0; i < cfg->nluns; ++i) { + snprintf(buf, sizeof(buf), "lun%d", i); + rc = fsg_common_create_lun(common, &cfg->luns[i], i, buf, NULL); + if (rc) + goto fail; + } + + pr_info("Number of LUNs=%d\n", cfg->nluns); + + return 0; + +fail: + _fsg_common_remove_luns(common, i); + return rc; +} +EXPORT_SYMBOL_GPL(fsg_common_create_luns); + +void fsg_common_set_inquiry_string(struct fsg_common *common, const char *vn, + const char *pn) +{ + int i; + + /* Prepare inquiryString */ + i = get_default_bcdDevice(); + snprintf(common->inquiry_string, sizeof(common->inquiry_string), + "%-8s%-16s%04x", vn ?: "Linux", + /* Assume product name dependent on the first LUN */ + pn ?: ((*common->luns)->cdrom + ? "File-CD Gadget" + : "File-Stor Gadget"), + i); +} +EXPORT_SYMBOL_GPL(fsg_common_set_inquiry_string); + +static void fsg_common_release(struct fsg_common *common) +{ + int i; + + /* 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); + } + + for (i = 0; i < ARRAY_SIZE(common->luns); ++i) { + struct fsg_lun *lun = common->luns[i]; + if (!lun) + continue; + fsg_lun_close(lun); + if (device_is_registered(&lun->dev)) + device_unregister(&lun->dev); + kfree(lun); + } + + _fsg_common_free_buffers(common->buffhds, common->fsg_num_buffers); + if (common->free_storage_on_release) + kfree(common); +} + + +/*-------------------------------------------------------------------------*/ + +static int fsg_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + struct fsg_common *common = fsg->common; + struct usb_gadget *gadget = c->cdev->gadget; + int i; + struct usb_ep *ep; + unsigned max_burst; + int ret; + struct fsg_opts *opts; + + /* Don't allow to bind if we don't have at least one LUN */ + ret = _fsg_common_get_max_lun(common); + if (ret < 0) { + pr_err("There should be at least one LUN.\n"); + return -EINVAL; + } + + opts = fsg_opts_from_func_inst(f->fi); + if (!opts->no_configfs) { + ret = fsg_common_set_cdev(fsg->common, c->cdev, + fsg->common->can_stall); + if (ret) + return ret; + fsg_common_set_inquiry_string(fsg->common, NULL, NULL); + } + + if (!common->thread_task) { + common->state = FSG_STATE_NORMAL; + common->thread_task = + kthread_run(fsg_main_thread, common, "file-storage"); + if (IS_ERR(common->thread_task)) { + ret = PTR_ERR(common->thread_task); + common->thread_task = NULL; + common->state = FSG_STATE_TERMINATED; + return ret; + } + DBG(common, "I/O thread pid: %d\n", + task_pid_nr(common->thread_task)); + } + + fsg->gadget = gadget; + + /* New interface */ + i = usb_interface_id(c, f); + if (i < 0) + goto fail; + 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; + fsg->bulk_in = ep; + + ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc); + if (!ep) + goto autoconf_fail; + 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, fsg_ss_function); + if (ret) + goto autoconf_fail; + + return 0; + +autoconf_fail: + ERROR(fsg, "unable to autoconfigure all endpoints\n"); + i = -ENOTSUPP; +fail: + /* terminate the thread */ + if (fsg->common->state != FSG_STATE_TERMINATED) { + raise_exception(fsg->common, FSG_STATE_EXIT); + wait_for_completion(&fsg->common->thread_notifier); + } + return i; +} + +/****************************** ALLOCATE FUNCTION *************************/ + +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) { + __raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE, NULL); + /* FIXME: make interruptible or killable somehow? */ + wait_event(common->fsg_wait, common->fsg != fsg); + } + + usb_free_all_descriptors(&fsg->function); +} + +static inline struct fsg_lun_opts *to_fsg_lun_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct fsg_lun_opts, group); +} + +static inline struct fsg_opts *to_fsg_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct fsg_opts, + func_inst.group); +} + +static void fsg_lun_attr_release(struct config_item *item) +{ + struct fsg_lun_opts *lun_opts; + + lun_opts = to_fsg_lun_opts(item); + kfree(lun_opts); +} + +static const struct configfs_item_operations fsg_lun_item_ops = { + .release = fsg_lun_attr_release, +}; + +static ssize_t fsg_lun_opts_file_show(struct config_item *item, char *page) +{ + struct fsg_lun_opts *opts = to_fsg_lun_opts(item); + struct fsg_opts *fsg_opts = to_fsg_opts(opts->group.cg_item.ci_parent); + + return fsg_show_file(opts->lun, &fsg_opts->common->filesem, page); +} + +static ssize_t fsg_lun_opts_file_store(struct config_item *item, + const char *page, size_t len) +{ + struct fsg_lun_opts *opts = to_fsg_lun_opts(item); + struct fsg_opts *fsg_opts = to_fsg_opts(opts->group.cg_item.ci_parent); + + return fsg_store_file(opts->lun, &fsg_opts->common->filesem, page, len); +} + +CONFIGFS_ATTR(fsg_lun_opts_, file); + +static ssize_t fsg_lun_opts_ro_show(struct config_item *item, char *page) +{ + return fsg_show_ro(to_fsg_lun_opts(item)->lun, page); +} + +static ssize_t fsg_lun_opts_ro_store(struct config_item *item, + const char *page, size_t len) +{ + struct fsg_lun_opts *opts = to_fsg_lun_opts(item); + struct fsg_opts *fsg_opts = to_fsg_opts(opts->group.cg_item.ci_parent); + + return fsg_store_ro(opts->lun, &fsg_opts->common->filesem, page, len); +} + +CONFIGFS_ATTR(fsg_lun_opts_, ro); + +static ssize_t fsg_lun_opts_removable_show(struct config_item *item, + char *page) +{ + return fsg_show_removable(to_fsg_lun_opts(item)->lun, page); +} + +static ssize_t fsg_lun_opts_removable_store(struct config_item *item, + const char *page, size_t len) +{ + return fsg_store_removable(to_fsg_lun_opts(item)->lun, page, len); +} + +CONFIGFS_ATTR(fsg_lun_opts_, removable); + +static ssize_t fsg_lun_opts_cdrom_show(struct config_item *item, char *page) +{ + return fsg_show_cdrom(to_fsg_lun_opts(item)->lun, page); +} + +static ssize_t fsg_lun_opts_cdrom_store(struct config_item *item, + const char *page, size_t len) +{ + struct fsg_lun_opts *opts = to_fsg_lun_opts(item); + struct fsg_opts *fsg_opts = to_fsg_opts(opts->group.cg_item.ci_parent); + + return fsg_store_cdrom(opts->lun, &fsg_opts->common->filesem, page, + len); +} + +CONFIGFS_ATTR(fsg_lun_opts_, cdrom); + +static ssize_t fsg_lun_opts_nofua_show(struct config_item *item, char *page) +{ + return fsg_show_nofua(to_fsg_lun_opts(item)->lun, page); +} + +static ssize_t fsg_lun_opts_nofua_store(struct config_item *item, + const char *page, size_t len) +{ + return fsg_store_nofua(to_fsg_lun_opts(item)->lun, page, len); +} + +CONFIGFS_ATTR(fsg_lun_opts_, nofua); + +static ssize_t fsg_lun_opts_inquiry_string_show(struct config_item *item, + char *page) +{ + return fsg_show_inquiry_string(to_fsg_lun_opts(item)->lun, page); +} + +static ssize_t fsg_lun_opts_inquiry_string_store(struct config_item *item, + const char *page, size_t len) +{ + return fsg_store_inquiry_string(to_fsg_lun_opts(item)->lun, page, len); +} + +CONFIGFS_ATTR(fsg_lun_opts_, inquiry_string); + +static ssize_t fsg_lun_opts_forced_eject_store(struct config_item *item, + const char *page, size_t len) +{ + struct fsg_lun_opts *opts = to_fsg_lun_opts(item); + struct fsg_opts *fsg_opts = to_fsg_opts(opts->group.cg_item.ci_parent); + + return fsg_store_forced_eject(opts->lun, &fsg_opts->common->filesem, + page, len); +} + +CONFIGFS_ATTR_WO(fsg_lun_opts_, forced_eject); + +static struct configfs_attribute *fsg_lun_attrs[] = { + &fsg_lun_opts_attr_file, + &fsg_lun_opts_attr_ro, + &fsg_lun_opts_attr_removable, + &fsg_lun_opts_attr_cdrom, + &fsg_lun_opts_attr_nofua, + &fsg_lun_opts_attr_inquiry_string, + &fsg_lun_opts_attr_forced_eject, + NULL, +}; + +static const struct config_item_type fsg_lun_type = { + .ct_item_ops = &fsg_lun_item_ops, + .ct_attrs = fsg_lun_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *fsg_lun_make(struct config_group *group, + const char *name) +{ + struct fsg_lun_opts *opts; + struct fsg_opts *fsg_opts; + struct fsg_lun_config config; + char *num_str; + u8 num; + int ret; + + num_str = strchr(name, '.'); + if (!num_str) { + pr_err("Unable to locate . in LUN.NUMBER\n"); + return ERR_PTR(-EINVAL); + } + num_str++; + + ret = kstrtou8(num_str, 0, &num); + if (ret) + return ERR_PTR(ret); + + fsg_opts = to_fsg_opts(&group->cg_item); + if (num >= FSG_MAX_LUNS) + return ERR_PTR(-ERANGE); + num = array_index_nospec(num, FSG_MAX_LUNS); + + mutex_lock(&fsg_opts->lock); + if (fsg_opts->refcnt || fsg_opts->common->luns[num]) { + ret = -EBUSY; + goto out; + } + + opts = kzalloc_obj(*opts); + if (!opts) { + ret = -ENOMEM; + goto out; + } + + memset(&config, 0, sizeof(config)); + config.removable = true; + + ret = fsg_common_create_lun(fsg_opts->common, &config, num, name, + (const char **)&group->cg_item.ci_name); + if (ret) { + kfree(opts); + goto out; + } + opts->lun = fsg_opts->common->luns[num]; + opts->lun_id = num; + mutex_unlock(&fsg_opts->lock); + + config_group_init_type_name(&opts->group, name, &fsg_lun_type); + + return &opts->group; +out: + mutex_unlock(&fsg_opts->lock); + return ERR_PTR(ret); +} + +static void fsg_lun_drop(struct config_group *group, struct config_item *item) +{ + struct fsg_lun_opts *lun_opts; + struct fsg_opts *fsg_opts; + + lun_opts = to_fsg_lun_opts(item); + fsg_opts = to_fsg_opts(&group->cg_item); + + mutex_lock(&fsg_opts->lock); + if (fsg_opts->refcnt) { + struct config_item *gadget; + + gadget = group->cg_item.ci_parent->ci_parent; + unregister_gadget_item(gadget); + } + + fsg_common_remove_lun(lun_opts->lun); + fsg_opts->common->luns[lun_opts->lun_id] = NULL; + lun_opts->lun_id = 0; + mutex_unlock(&fsg_opts->lock); + + config_item_put(item); +} + +static void fsg_attr_release(struct config_item *item) +{ + struct fsg_opts *opts = to_fsg_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static const struct configfs_item_operations fsg_item_ops = { + .release = fsg_attr_release, +}; + +static ssize_t fsg_opts_stall_show(struct config_item *item, char *page) +{ + struct fsg_opts *opts = to_fsg_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d", opts->common->can_stall); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t fsg_opts_stall_store(struct config_item *item, const char *page, + size_t len) +{ + struct fsg_opts *opts = to_fsg_opts(item); + int ret; + bool stall; + + mutex_lock(&opts->lock); + + if (opts->refcnt) { + mutex_unlock(&opts->lock); + return -EBUSY; + } + + ret = kstrtobool(page, &stall); + if (!ret) { + opts->common->can_stall = stall; + ret = len; + } + + mutex_unlock(&opts->lock); + + return ret; +} + +CONFIGFS_ATTR(fsg_opts_, stall); + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES +static ssize_t fsg_opts_num_buffers_show(struct config_item *item, char *page) +{ + struct fsg_opts *opts = to_fsg_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d", opts->common->fsg_num_buffers); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t fsg_opts_num_buffers_store(struct config_item *item, + const char *page, size_t len) +{ + struct fsg_opts *opts = to_fsg_opts(item); + int ret; + u8 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + ret = kstrtou8(page, 0, &num); + if (ret) + goto end; + + ret = fsg_common_set_num_buffers(opts->common, num); + if (ret) + goto end; + ret = len; + +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(fsg_opts_, num_buffers); +#endif + +static struct configfs_attribute *fsg_attrs[] = { + &fsg_opts_attr_stall, +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + &fsg_opts_attr_num_buffers, +#endif + NULL, +}; + +static const struct configfs_group_operations fsg_group_ops = { + .make_group = fsg_lun_make, + .drop_item = fsg_lun_drop, +}; + +static const struct config_item_type fsg_func_type = { + .ct_item_ops = &fsg_item_ops, + .ct_group_ops = &fsg_group_ops, + .ct_attrs = fsg_attrs, + .ct_owner = THIS_MODULE, +}; + +static void fsg_free_inst(struct usb_function_instance *fi) +{ + struct fsg_opts *opts; + + opts = fsg_opts_from_func_inst(fi); + fsg_common_release(opts->common); + kfree(opts); +} + +static struct usb_function_instance *fsg_alloc_inst(void) +{ + struct fsg_opts *opts; + struct fsg_lun_config config; + int rc; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = fsg_free_inst; + opts->common = fsg_common_setup(opts->common); + if (IS_ERR(opts->common)) { + rc = PTR_ERR(opts->common); + goto release_opts; + } + + rc = fsg_common_set_num_buffers(opts->common, + CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS); + if (rc) + goto release_common; + + memset(&config, 0, sizeof(config)); + config.removable = true; + rc = fsg_common_create_lun(opts->common, &config, 0, "lun.0", + (const char **)&opts->func_inst.group.cg_item.ci_name); + if (rc) + goto release_buffers; + + opts->lun0.lun = opts->common->luns[0]; + opts->lun0.lun_id = 0; + + config_group_init_type_name(&opts->func_inst.group, "", &fsg_func_type); + + config_group_init_type_name(&opts->lun0.group, "lun.0", &fsg_lun_type); + configfs_add_default_group(&opts->lun0.group, &opts->func_inst.group); + + return &opts->func_inst; + +release_buffers: + fsg_common_free_buffers(opts->common); +release_common: + kfree(opts->common); +release_opts: + kfree(opts); + return ERR_PTR(rc); +} + +static void fsg_free(struct usb_function *f) +{ + struct fsg_dev *fsg; + struct fsg_opts *opts; + + fsg = container_of(f, struct fsg_dev, function); + opts = container_of(f->fi, struct fsg_opts, func_inst); + + mutex_lock(&opts->lock); + opts->refcnt--; + mutex_unlock(&opts->lock); + + kfree(fsg); +} + +static struct usb_function *fsg_alloc(struct usb_function_instance *fi) +{ + struct fsg_opts *opts = fsg_opts_from_func_inst(fi); + struct fsg_common *common = opts->common; + struct fsg_dev *fsg; + + fsg = kzalloc_obj(*fsg); + if (unlikely(!fsg)) + return ERR_PTR(-ENOMEM); + + mutex_lock(&opts->lock); + opts->refcnt++; + mutex_unlock(&opts->lock); + + fsg->function.name = FSG_DRIVER_DESC; + 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->function.free_func = fsg_free; + + fsg->common = common; + + return &fsg->function; +} + +DECLARE_USB_FUNCTION_INIT(mass_storage, fsg_alloc_inst, fsg_alloc); +MODULE_DESCRIPTION("Mass Storage USB Composite Function"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michal Nazarewicz"); + +/************************* Module parameters *************************/ + + +void fsg_config_from_params(struct fsg_config *cfg, + const struct fsg_module_parameters *params, + unsigned int fsg_num_buffers) +{ + 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] + : NULL; + } + + /* Let MSF use defaults */ + cfg->vendor_name = NULL; + cfg->product_name = NULL; + + cfg->ops = NULL; + cfg->private_data = NULL; + + /* Finalise */ + cfg->can_stall = params->stall; + cfg->fsg_num_buffers = fsg_num_buffers; +} +EXPORT_SYMBOL_GPL(fsg_config_from_params); diff --git a/drivers/usb/gadget/function/f_mass_storage.h b/drivers/usb/gadget/function/f_mass_storage.h new file mode 100644 index 000000000..82ecd3fed --- /dev/null +++ b/drivers/usb/gadget/function/f_mass_storage.h @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef USB_F_MASS_STORAGE_H +#define USB_F_MASS_STORAGE_H + +#include +#include "storage_common.h" + +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") + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +#define FSG_MODULE_PARAMETERS(prefix, params) \ + __FSG_MODULE_PARAMETERS(prefix, params); \ + module_param_named(num_buffers, fsg_num_buffers, uint, S_IRUGO);\ + MODULE_PARM_DESC(num_buffers, "Number of pipeline buffers") +#else + +#define FSG_MODULE_PARAMETERS(prefix, params) \ + __FSG_MODULE_PARAMETERS(prefix, params) + +#endif + +struct fsg_common; + +/* FSF callback functions */ +struct fsg_lun_opts { + struct config_group group; + struct fsg_lun *lun; + int lun_id; +}; + +struct fsg_opts { + struct fsg_common *common; + struct usb_function_instance func_inst; + struct fsg_lun_opts lun0; + struct config_group *default_groups[2]; + bool no_configfs; /* for legacy gadgets */ + + /* + * Read/write access to configfs attributes is handled by configfs. + * + * This is to protect the data from concurrent access by read/write + * and create symlink/remove symlink. + */ + struct mutex lock; + int refcnt; +}; + +struct fsg_lun_config { + const char *filename; + char ro; + char removable; + char cdrom; + char nofua; + char inquiry_string[INQUIRY_STRING_LEN]; +}; + +struct fsg_config { + unsigned nluns; + struct fsg_lun_config 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; + unsigned int fsg_num_buffers; +}; + +static inline struct fsg_opts * +fsg_opts_from_func_inst(struct usb_function_instance *fi) +{ + return container_of(fi, struct fsg_opts, func_inst); +} + +void fsg_common_set_sysfs(struct fsg_common *common, bool sysfs); + +int fsg_common_set_num_buffers(struct fsg_common *common, unsigned int n); + +void fsg_common_free_buffers(struct fsg_common *common); + +int fsg_common_set_cdev(struct fsg_common *common, + struct usb_composite_dev *cdev, bool can_stall); + +void fsg_common_remove_lun(struct fsg_lun *lun); + +void fsg_common_remove_luns(struct fsg_common *common); + +int fsg_common_create_lun(struct fsg_common *common, struct fsg_lun_config *cfg, + unsigned int id, const char *name, + const char **name_pfx); + +int fsg_common_create_luns(struct fsg_common *common, struct fsg_config *cfg); + +void fsg_common_set_inquiry_string(struct fsg_common *common, const char *vn, + const char *pn); + +void fsg_config_from_params(struct fsg_config *cfg, + const struct fsg_module_parameters *params, + unsigned int fsg_num_buffers); + +#endif /* USB_F_MASS_STORAGE_H */ diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c new file mode 100644 index 000000000..fba8cf787 --- /dev/null +++ b/drivers/usb/gadget/function/f_midi.c @@ -0,0 +1,1415 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "u_midi.h" + +MODULE_AUTHOR("Ben Williamson"); +MODULE_DESCRIPTION("USB MIDI class function driver"); +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 + +/* MIDI message states */ +enum { + STATE_INITIAL = 0, /* pseudo state */ + STATE_1PARAM, + STATE_2PARAM_1, + STATE_2PARAM_2, + STATE_SYSEX_0, + STATE_SYSEX_1, + STATE_SYSEX_2, + STATE_REAL_TIME, + STATE_FINISHED, /* pseudo state */ +}; + +/* + * 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 snd_rawmidi_substream *substream; + int active; + uint8_t cable; + uint8_t state; + 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; + u8 ms_id; + + struct snd_rawmidi_substream *out_substream[MAX_PORTS]; + + unsigned long out_triggered; + struct work_struct work; + unsigned int in_ports; + unsigned int out_ports; + int index; + char *id; + unsigned int buflen, qlen; + /* This fifo is used as a buffer ring for pre-allocated IN usb_requests */ + DECLARE_KFIFO_PTR(in_req_fifo, struct usb_request *); + spinlock_t transmit_lock; + unsigned int in_last_port; + unsigned char free_ref; + + struct gmidi_in_port in_ports_array[] __counted_by(in_ports); +}; + +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); +static void f_midi_rmidi_free(struct snd_rawmidi *rmidi); +static void f_midi_free_inst(struct usb_function_instance *f); + +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 = { + .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 = { + .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 = { + .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 = { + .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, +}; + +static struct usb_ss_ep_comp_descriptor bulk_out_ss_comp_desc = { + .bLength = sizeof(bulk_out_ss_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +/* 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, +}; + +static struct usb_ss_ep_comp_descriptor bulk_in_ss_comp_desc = { + .bLength = sizeof(bulk_in_ss_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +/* 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 inline struct usb_request *midi_alloc_ep_req(struct usb_ep *ep, + unsigned length) +{ + return alloc_ep_req(ep, length); +} + +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. */ + req->length = 0; + queue_work(system_highpri_wq, &midi->work); + 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); + /* We don't need to free IN requests because it's handled + * by the midi->in_req_fifo. */ + 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 void f_midi_drop_out_substreams(struct f_midi *midi) +{ + unsigned int i; + + for (i = 0; i < midi->in_ports; i++) { + struct gmidi_in_port *port = midi->in_ports_array + i; + struct snd_rawmidi_substream *substream = port->substream; + + if (port->active && substream) + snd_rawmidi_drop_output(substream); + } +} + +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; + + 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); + unsigned i; + int err; + + /* we only set alt for MIDIStreaming interface */ + if (intf != midi->ms_id) + return 0; + + 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; + + /* pre-allocate write usb requests to use on f_midi_transmit. */ + while (kfifo_avail(&midi->in_req_fifo)) { + struct usb_request *req = + midi_alloc_ep_req(midi->in_ep, midi->buflen); + + if (req == NULL) + return -ENOMEM; + + req->length = 0; + req->complete = f_midi_complete; + + kfifo_put(&midi->in_req_fifo, req); + } + + /* 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 = + midi_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: couldn't enqueue request: %d\n", + midi->out_ep->name, err); + if (req->buf != NULL) + free_ep_req(midi->out_ep, req); + return 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; + struct usb_request *req = NULL; + + 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); + + /* release IN requests */ + while (kfifo_get(&midi->in_req_fifo, &req)) + free_ep_req(midi->in_ep, req); + + f_midi_drop_out_substreams(midi); +} + +static int f_midi_snd_free(struct snd_device *device) +{ + return 0; +} + +/* + * 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 p[4] = { port->cable << 4, 0, 0, 0 }; + uint8_t next_state = STATE_INITIAL; + + switch (b) { + case 0xf8 ... 0xff: + /* System Real-Time Messages */ + p[0] |= 0x0f; + p[1] = b; + next_state = port->state; + port->state = STATE_REAL_TIME; + break; + + case 0xf7: + /* End of SysEx */ + switch (port->state) { + case STATE_SYSEX_0: + p[0] |= 0x05; + p[1] = 0xf7; + next_state = STATE_FINISHED; + break; + case STATE_SYSEX_1: + p[0] |= 0x06; + p[1] = port->data[0]; + p[2] = 0xf7; + next_state = STATE_FINISHED; + break; + case STATE_SYSEX_2: + p[0] |= 0x07; + p[1] = port->data[0]; + p[2] = port->data[1]; + p[3] = 0xf7; + next_state = STATE_FINISHED; + break; + default: + /* Ignore byte */ + next_state = port->state; + port->state = STATE_INITIAL; + } + break; + + case 0xf0 ... 0xf6: + /* System Common Messages */ + port->data[0] = port->data[1] = 0; + port->state = STATE_INITIAL; + switch (b) { + case 0xf0: + port->data[0] = b; + port->data[1] = 0; + next_state = STATE_SYSEX_1; + break; + case 0xf1: + case 0xf3: + port->data[0] = b; + next_state = STATE_1PARAM; + break; + case 0xf2: + port->data[0] = b; + next_state = STATE_2PARAM_1; + break; + case 0xf4: + case 0xf5: + next_state = STATE_INITIAL; + break; + case 0xf6: + p[0] |= 0x05; + p[1] = 0xf6; + next_state = STATE_FINISHED; + break; + } + break; + + case 0x80 ... 0xef: + /* + * Channel Voice Messages, Channel Mode Messages + * and Control Change Messages. + */ + port->data[0] = b; + port->data[1] = 0; + port->state = STATE_INITIAL; + if (b >= 0xc0 && b <= 0xdf) + next_state = STATE_1PARAM; + else + next_state = STATE_2PARAM_1; + break; + + case 0x00 ... 0x7f: + /* Message parameters */ + switch (port->state) { + case STATE_1PARAM: + if (port->data[0] < 0xf0) + p[0] |= port->data[0] >> 4; + else + p[0] |= 0x02; + + p[1] = port->data[0]; + p[2] = b; + /* This is to allow Running State Messages */ + next_state = STATE_1PARAM; + break; + case STATE_2PARAM_1: + port->data[1] = b; + next_state = STATE_2PARAM_2; + break; + case STATE_2PARAM_2: + if (port->data[0] < 0xf0) + p[0] |= port->data[0] >> 4; + else + p[0] |= 0x03; + + p[1] = port->data[0]; + p[2] = port->data[1]; + p[3] = b; + /* This is to allow Running State Messages */ + next_state = STATE_2PARAM_1; + break; + case STATE_SYSEX_0: + port->data[0] = b; + next_state = STATE_SYSEX_1; + break; + case STATE_SYSEX_1: + port->data[1] = b; + next_state = STATE_SYSEX_2; + break; + case STATE_SYSEX_2: + p[0] |= 0x04; + p[1] = port->data[0]; + p[2] = port->data[1]; + p[3] = b; + next_state = STATE_SYSEX_0; + break; + } + break; + } + + /* States where we have to write into the USB request */ + if (next_state == STATE_FINISHED || + port->state == STATE_SYSEX_2 || + port->state == STATE_1PARAM || + port->state == STATE_2PARAM_2 || + port->state == STATE_REAL_TIME) { + + unsigned int length = req->length; + u8 *buf = (u8 *)req->buf + length; + + memcpy(buf, p, sizeof(p)); + req->length = length + sizeof(p); + + if (next_state == STATE_FINISHED) { + next_state = STATE_INITIAL; + port->data[0] = port->data[1] = 0; + } + } + + port->state = next_state; +} + +static int f_midi_do_transmit(struct f_midi *midi, struct usb_ep *ep) +{ + struct usb_request *req = NULL; + unsigned int len, i; + bool active = false; + int err; + + /* + * We peek the request in order to reuse it if it fails to enqueue on + * its endpoint + */ + len = kfifo_peek(&midi->in_req_fifo, &req); + if (len != 1) { + ERROR(midi, "%s: Couldn't get usb request\n", __func__); + return -1; + } + + /* + * If buffer overrun, then we ignore this transmission. + * IMPORTANT: This will cause the user-space rawmidi device to block + * until a) usb requests have been completed or b) snd_rawmidi_write() + * times out. + */ + if (req->length > 0) + return 0; + + for (i = midi->in_last_port; i < midi->in_ports; ++i) { + struct gmidi_in_port *port = midi->in_ports_array + i; + struct snd_rawmidi_substream *substream = port->substream; + + if (!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); + } + + active = !!port->active; + if (active) + break; + } + midi->in_last_port = active ? i : 0; + + if (req->length <= 0) + goto done; + + err = usb_ep_queue(ep, req, GFP_ATOMIC); + if (err < 0) { + ERROR(midi, "%s failed to queue req: %d\n", + midi->in_ep->name, err); + req->length = 0; /* Re-use request next time. */ + } else { + /* Upon success, put request at the back of the queue. */ + kfifo_skip(&midi->in_req_fifo); + kfifo_put(&midi->in_req_fifo, req); + } + +done: + return active; +} + +static void f_midi_transmit(struct f_midi *midi) +{ + struct usb_ep *ep = midi->in_ep; + int ret; + unsigned long flags; + + /* We only care about USB requests if IN endpoint is enabled */ + if (!ep || !ep->enabled) + goto drop_out; + + spin_lock_irqsave(&midi->transmit_lock, flags); + + do { + ret = f_midi_do_transmit(midi, ep); + if (ret < 0) { + spin_unlock_irqrestore(&midi->transmit_lock, flags); + goto drop_out; + } + } while (ret); + + spin_unlock_irqrestore(&midi->transmit_lock, flags); + + return; + +drop_out: + f_midi_drop_out_substreams(midi); +} + +static void f_midi_in_work(struct work_struct *work) +{ + struct f_midi *midi; + + midi = container_of(work, struct f_midi, work); + f_midi_transmit(midi); +} + +static int f_midi_in_open(struct snd_rawmidi_substream *substream) +{ + struct f_midi *midi = substream->rmidi->private_data; + struct gmidi_in_port *port; + + if (substream->number >= midi->in_ports) + return -EINVAL; + + VDBG(midi, "%s()\n", __func__); + port = midi->in_ports_array + substream->number; + port->substream = substream; + port->state = STATE_INITIAL; + 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 (substream->number >= midi->in_ports) + return; + + VDBG(midi, "%s() %d\n", __func__, up); + midi->in_ports_array[substream->number].active = up; + if (up) + queue_work(system_highpri_wq, &midi->work); +} + +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 const struct snd_rawmidi_ops gmidi_in_ops = { + .open = f_midi_in_open, + .close = f_midi_in_close, + .trigger = f_midi_in_trigger, +}; + +static const struct snd_rawmidi_ops gmidi_out_ops = { + .open = f_midi_out_open, + .close = f_midi_out_close, + .trigger = f_midi_out_trigger +}; + +static inline void f_midi_unregister_card(struct f_midi *midi) +{ + if (midi->card) { + snd_card_free(midi->card); + midi->card = NULL; + } +} + +/* 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_new(&midi->gadget->dev, midi->index, midi->id, + THIS_MODULE, 0, &card); + if (err < 0) { + ERROR(midi, "snd_card_new() 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; + } + + strscpy(card->driver, f_midi_longname); + strscpy(card->longname, f_midi_longname); + strscpy(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; + midi->in_last_port = 0; + strscpy(rmidi->name, card->shortname); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = midi; + rmidi->private_free = f_midi_rmidi_free; + midi->free_ref++; + + /* + * 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); + + /* 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: + f_midi_unregister_card(midi); + return err; +} + +/* MIDI function driver setup/binding */ + +static int 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); + struct usb_string *us; + struct f_midi_opts *opts; + int status, n, jack = 1, i = 0, endpoint_descriptor_index = 0; + + midi->gadget = cdev->gadget; + INIT_WORK(&midi->work, f_midi_in_work); + status = f_midi_register_card(midi); + if (status < 0) + goto fail_register; + + opts = container_of(f->fi, struct f_midi_opts, func_inst); + if (opts->interface_string) + midi_string_defs[STRING_FUNC_IDX].s = opts->interface_string; + + /* maybe allocate device-global string ID */ + us = usb_gstrings_attach(c->cdev, midi_strings, + ARRAY_SIZE(midi_string_defs)); + if (IS_ERR(us)) { + status = PTR_ERR(us); + goto fail; + } + ac_interface_desc.iInterface = us[STRING_FUNC_IDX].id; + + /* 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; + midi->ms_id = status; + + status = -ENODEV; + + /* + * Reset wMaxPacketSize with maximum packet size of FS bulk transfer before + * endpoint claim. This ensures that the wMaxPacketSize does not exceed the + * limit during bind retries where configured dwc3 TX/RX FIFO's maxpacket + * size of 512 bytes for IN/OUT endpoints in support HS speed only. + */ + bulk_in_desc.wMaxPacketSize = cpu_to_le16(64); + bulk_out_desc.wMaxPacketSize = cpu_to_le16(64); + + /* allocate instance-specific endpoints */ + midi->in_ep = usb_ep_autoconfig(cdev->gadget, &bulk_in_desc); + if (!midi->in_ep) + goto fail; + + midi->out_ep = usb_ep_autoconfig(cdev->gadget, &bulk_out_desc); + if (!midi->out_ep) + goto fail; + + /* allocate temporary function list */ + midi_function = kzalloc_objs(*midi_function, (MAX_PORTS * 4) + 11); + 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->out_ports); + ms_out_desc.bNumEmbMIDIJack = midi->out_ports; + + ms_in_desc.bLength = USB_DT_MS_ENDPOINT_SIZE(midi->in_ports); + ms_in_desc.bNumEmbMIDIJack = midi->in_ports; + + /* ... and add them to the list */ + endpoint_descriptor_index = i; + 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; + + 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; + + bulk_in_desc.wMaxPacketSize = cpu_to_le16(1024); + bulk_out_desc.wMaxPacketSize = cpu_to_le16(1024); + i = endpoint_descriptor_index; + midi_function[i++] = (struct usb_descriptor_header *) + &bulk_out_desc; + midi_function[i++] = (struct usb_descriptor_header *) + &bulk_out_ss_comp_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 *) + &bulk_in_ss_comp_desc; + midi_function[i++] = (struct usb_descriptor_header *) + &ms_in_desc; + f->ss_descriptors = usb_copy_descriptors(midi_function); + if (!f->ss_descriptors) + goto fail_f_midi; + + kfree(midi_function); + + return 0; + +fail_f_midi: + kfree(midi_function); + usb_free_all_descriptors(f); +fail: + f_midi_unregister_card(midi); +fail_register: + ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); + + return status; +} + +static inline struct f_midi_opts *to_f_midi_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_midi_opts, + func_inst.group); +} + +static void midi_attr_release(struct config_item *item) +{ + struct f_midi_opts *opts = to_f_midi_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static const struct configfs_item_operations midi_item_ops = { + .release = midi_attr_release, +}; + +#define F_MIDI_OPT(name, test_limit, limit) \ +static ssize_t f_midi_opts_##name##_show(struct config_item *item, char *page) \ +{ \ + struct f_midi_opts *opts = to_f_midi_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_midi_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_midi_opts *opts = to_f_midi_opts(item); \ + int ret; \ + u32 num; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt > 1) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou32(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (test_limit && num > limit) { \ + ret = -EINVAL; \ + goto end; \ + } \ + opts->name = num; \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_midi_opts_, name); + +#define F_MIDI_OPT_SIGNED(name, test_limit, limit) \ +static ssize_t f_midi_opts_##name##_show(struct config_item *item, char *page) \ +{ \ + struct f_midi_opts *opts = to_f_midi_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_midi_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_midi_opts *opts = to_f_midi_opts(item); \ + int ret; \ + s32 num; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt > 1) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtos32(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (test_limit && num > limit) { \ + ret = -EINVAL; \ + goto end; \ + } \ + opts->name = num; \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_midi_opts_, name); + +#define F_MIDI_OPT_STRING(name) \ +static ssize_t f_midi_opts_##name##_show(struct config_item *item, char *page) \ +{ \ + struct f_midi_opts *opts = to_f_midi_opts(item); \ + ssize_t result; \ + \ + mutex_lock(&opts->lock); \ + if (opts->name) { \ + result = strscpy(page, opts->name, PAGE_SIZE); \ + } else { \ + page[0] = 0; \ + result = 0; \ + } \ + \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_midi_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_midi_opts *opts = to_f_midi_opts(item); \ + int ret; \ + char *c; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt > 1) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + c = kstrndup(page, len, GFP_KERNEL); \ + if (!c) { \ + ret = -ENOMEM; \ + goto end; \ + } \ + kfree(opts->name); \ + opts->name = c; \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_midi_opts_, name) + +F_MIDI_OPT_SIGNED(index, true, SNDRV_CARDS); +F_MIDI_OPT(buflen, false, 0); +F_MIDI_OPT(qlen, false, 0); +F_MIDI_OPT(in_ports, true, MAX_PORTS); +F_MIDI_OPT(out_ports, true, MAX_PORTS); +F_MIDI_OPT_STRING(id); +F_MIDI_OPT_STRING(interface_string); + +static struct configfs_attribute *midi_attrs[] = { + &f_midi_opts_attr_index, + &f_midi_opts_attr_buflen, + &f_midi_opts_attr_qlen, + &f_midi_opts_attr_in_ports, + &f_midi_opts_attr_out_ports, + &f_midi_opts_attr_id, + &f_midi_opts_attr_interface_string, + NULL, +}; + +static const struct config_item_type midi_func_type = { + .ct_item_ops = &midi_item_ops, + .ct_attrs = midi_attrs, + .ct_owner = THIS_MODULE, +}; + +static void f_midi_free_inst(struct usb_function_instance *f) +{ + struct f_midi_opts *opts; + bool free = false; + + opts = container_of(f, struct f_midi_opts, func_inst); + + mutex_lock(&opts->lock); + if (!--opts->refcnt) { + free = true; + } + mutex_unlock(&opts->lock); + + if (free) { + kfree(opts->id); + kfree(opts->interface_string); + kfree(opts); + } +} + +static struct usb_function_instance *f_midi_alloc_inst(void) +{ + struct f_midi_opts *opts; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = f_midi_free_inst; + opts->index = SNDRV_DEFAULT_IDX1; + opts->id = NULL; + opts->interface_string = NULL; + opts->buflen = 512; + opts->qlen = 32; + opts->in_ports = 1; + opts->out_ports = 1; + opts->refcnt = 1; + + config_group_init_type_name(&opts->func_inst.group, "", + &midi_func_type); + + return &opts->func_inst; +} + +static void f_midi_free(struct usb_function *f) +{ + struct f_midi *midi; + struct f_midi_opts *opts; + bool free = false; + + midi = func_to_midi(f); + opts = container_of(f->fi, struct f_midi_opts, func_inst); + mutex_lock(&opts->lock); + if (!--midi->free_ref) { + cancel_work_sync(&midi->work); + kfree(midi->id); + kfifo_free(&midi->in_req_fifo); + kfree(midi); + free = true; + } + mutex_unlock(&opts->lock); + + if (free) + f_midi_free_inst(&opts->func_inst); +} + +static void f_midi_rmidi_free(struct snd_rawmidi *rmidi) +{ + f_midi_free(rmidi->private_data); +} + +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_when_closed(card); + + usb_free_all_descriptors(f); +} + +static struct usb_function *f_midi_alloc(struct usb_function_instance *fi) +{ + struct f_midi *midi = NULL; + struct f_midi_opts *opts; + int status, i; + + opts = container_of(fi, struct f_midi_opts, func_inst); + + mutex_lock(&opts->lock); + /* sanity check */ + if (opts->in_ports > MAX_PORTS || opts->out_ports > MAX_PORTS) { + status = -EINVAL; + goto setup_fail; + } + + /* allocate and initialize one new instance */ + midi = kzalloc_flex(*midi, in_ports_array, opts->in_ports); + if (!midi) { + status = -ENOMEM; + goto setup_fail; + } + midi->in_ports = opts->in_ports; + + for (i = 0; i < opts->in_ports; i++) + midi->in_ports_array[i].cable = i; + + /* set up ALSA midi devices */ + midi->id = kstrdup(opts->id, GFP_KERNEL); + if (opts->id && !midi->id) { + status = -ENOMEM; + goto midi_free; + } + midi->out_ports = opts->out_ports; + midi->index = opts->index; + midi->buflen = opts->buflen; + midi->qlen = opts->qlen; + midi->in_last_port = 0; + midi->free_ref = 1; + + status = kfifo_alloc(&midi->in_req_fifo, midi->qlen, GFP_KERNEL); + if (status) + goto midi_free; + + spin_lock_init(&midi->transmit_lock); + + ++opts->refcnt; + mutex_unlock(&opts->lock); + + midi->func.name = "gmidi function"; + 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->func.free_func = f_midi_free; + + return &midi->func; + +midi_free: + if (midi) + kfree(midi->id); + kfree(midi); +setup_fail: + mutex_unlock(&opts->lock); + + return ERR_PTR(status); +} + +DECLARE_USB_FUNCTION_INIT(midi, f_midi_alloc_inst, f_midi_alloc); diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c new file mode 100644 index 000000000..a4b72a6fa --- /dev/null +++ b/drivers/usb/gadget/function/f_midi2.c @@ -0,0 +1,2894 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_midi2.c -- USB MIDI 2.0 class function driver + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "u_midi2.h" + +struct f_midi2; +struct f_midi2_ep; +struct f_midi2_usb_ep; + +/* Context for each USB request */ +struct f_midi2_req_ctx { + struct f_midi2_usb_ep *usb_ep; /* belonging USB EP */ + unsigned int index; /* array index: 0-31 */ + struct usb_request *req; /* assigned request */ +}; + +/* Resources for a USB Endpoint */ +struct f_midi2_usb_ep { + struct f_midi2 *card; /* belonging card */ + struct f_midi2_ep *ep; /* belonging UMP EP (optional) */ + struct usb_ep *usb_ep; /* assigned USB EP */ + void (*complete)(struct usb_ep *usb_ep, struct usb_request *req); + unsigned long free_reqs; /* bitmap for unused requests */ + unsigned int num_reqs; /* number of allocated requests */ + struct f_midi2_req_ctx *reqs; /* request context array */ +}; + +/* Resources for UMP Function Block (and USB Group Terminal Block) */ +struct f_midi2_block { + struct f_midi2_block_info info; /* FB info, copied from configfs */ + struct snd_ump_block *fb; /* assigned FB */ + unsigned int gtb_id; /* assigned GTB id */ + unsigned int string_id; /* assigned string id */ +}; + +/* Temporary buffer for altset 0 MIDI 1.0 handling */ +struct f_midi2_midi1_port { + unsigned int pending; /* pending bytes on the input buffer */ + u8 buf[32]; /* raw MIDI 1.0 byte input */ + u8 state; /* running status */ + u8 data[2]; /* rendered USB MIDI 1.0 packet data */ +}; + +/* MIDI 1.0 message states */ +enum { + STATE_INITIAL = 0, /* pseudo state */ + STATE_1PARAM, + STATE_2PARAM_1, + STATE_2PARAM_2, + STATE_SYSEX_0, + STATE_SYSEX_1, + STATE_SYSEX_2, + STATE_REAL_TIME, + STATE_FINISHED, /* pseudo state */ +}; + +/* Resources for UMP Endpoint */ +struct f_midi2_ep { + struct snd_ump_endpoint *ump; /* assigned UMP EP */ + struct f_midi2 *card; /* belonging MIDI 2.0 device */ + + struct f_midi2_ep_info info; /* UMP EP info, copied from configfs */ + unsigned int num_blks; /* number of FBs */ + struct f_midi2_block blks[SNDRV_UMP_MAX_BLOCKS]; /* UMP FBs */ + + struct f_midi2_usb_ep ep_in; /* USB MIDI EP-in */ + struct f_midi2_usb_ep ep_out; /* USB MIDI EP-out */ + + u8 in_group_to_cable[SNDRV_UMP_MAX_GROUPS]; /* map to cable; 1-based! */ +}; + +/* indices for USB strings */ +enum { + STR_IFACE = 0, + STR_GTB1 = 1, +}; + +/* 1-based GTB id to string id */ +#define gtb_to_str_id(id) (STR_GTB1 + (id) - 1) + +/* mapping from MIDI 1.0 cable to UMP group */ +struct midi1_cable_mapping { + struct f_midi2_ep *ep; + unsigned char block; + unsigned char group; +}; + +/* operation mode */ +enum { + MIDI_OP_MODE_UNSET, /* no altset set yet */ + MIDI_OP_MODE_MIDI1, /* MIDI 1.0 (altset 0) is used */ + MIDI_OP_MODE_MIDI2, /* MIDI 2.0 (altset 1) is used */ +}; + +/* Resources for MIDI 2.0 Device */ +struct f_midi2 { + struct usb_function func; + struct usb_gadget *gadget; + struct snd_card *card; + + /* MIDI 1.0 in/out USB EPs */ + struct f_midi2_usb_ep midi1_ep_in; + struct f_midi2_usb_ep midi1_ep_out; + + /* number of MIDI 1.0 I/O cables */ + unsigned int num_midi1_in; + unsigned int num_midi1_out; + + /* conversion for MIDI 1.0 EP-in */ + struct f_midi2_midi1_port midi1_port[MAX_CABLES]; + /* conversion for MIDI 1.0 EP-out */ + struct ump_cvt_to_ump midi1_ump_cvt; + /* mapping between cables and UMP groups */ + struct midi1_cable_mapping in_cable_mapping[MAX_CABLES]; + struct midi1_cable_mapping out_cable_mapping[MAX_CABLES]; + + int midi_if; /* USB MIDI interface number */ + int operation_mode; /* current operation mode */ + + spinlock_t queue_lock; + + struct f_midi2_card_info info; /* card info, copied from configfs */ + + unsigned int num_eps; + struct f_midi2_ep midi2_eps[MAX_UMP_EPS]; + + unsigned int total_blocks; /* total number of blocks of all EPs */ + struct usb_string *string_defs; + struct usb_string *strings; +}; + +#define func_to_midi2(f) container_of(f, struct f_midi2, func) + +/* convert from MIDI protocol number (1 or 2) to SNDRV_UMP_EP_INFO_PROTO_* */ +#define to_ump_protocol(v) (((v) & 3) << 8) + +/* get EP name string */ +static const char *ump_ep_name(const struct f_midi2_ep *ep) +{ + return ep->info.ep_name ? ep->info.ep_name : "MIDI 2.0 Gadget"; +} + +/* get EP product ID string */ +static const char *ump_product_id(const struct f_midi2_ep *ep) +{ + return ep->info.product_id ? ep->info.product_id : "Unique Product ID"; +} + +/* get FB name string */ +static const char *ump_fb_name(const struct f_midi2_block_info *info) +{ + return info->name ? info->name : "MIDI 2.0 Gadget I/O"; +} + +/* + * USB Descriptor Definitions + */ +/* GTB header descriptor */ +static struct usb_ms20_gr_trm_block_header_descriptor gtb_header_desc = { + .bLength = sizeof(gtb_header_desc), + .bDescriptorType = USB_DT_CS_GR_TRM_BLOCK, + .bDescriptorSubtype = USB_MS_GR_TRM_BLOCK_HEADER, + .wTotalLength = __cpu_to_le16(0x12), // to be filled +}; + +/* GTB descriptor template: most items are replaced dynamically */ +static struct usb_ms20_gr_trm_block_descriptor gtb_desc = { + .bLength = sizeof(gtb_desc), + .bDescriptorType = USB_DT_CS_GR_TRM_BLOCK, + .bDescriptorSubtype = USB_MS_GR_TRM_BLOCK, + .bGrpTrmBlkID = 0x01, + .bGrpTrmBlkType = USB_MS_GR_TRM_BLOCK_TYPE_BIDIRECTIONAL, + .nGroupTrm = 0x00, + .nNumGroupTrm = 1, + .iBlockItem = 0, + .bMIDIProtocol = USB_MS_MIDI_PROTO_1_0_64, + .wMaxInputBandwidth = 0, + .wMaxOutputBandwidth = 0, +}; + +DECLARE_USB_MIDI_OUT_JACK_DESCRIPTOR(1); +DECLARE_USB_MS_ENDPOINT_DESCRIPTOR(16); +DECLARE_UAC_AC_HEADER_DESCRIPTOR(1); +DECLARE_USB_MS20_ENDPOINT_DESCRIPTOR(32); + +#define EP_MAX_PACKET_INT 8 + +/* Audio Control Interface */ +static struct usb_interface_descriptor midi2_audio_if_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, // to be filled + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .bInterfaceProtocol = 0, + .iInterface = 0, +}; + +static struct uac1_ac_header_descriptor_1 midi2_audio_class_desc = { + .bLength = 0x09, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = 0x01, + .bcdADC = __cpu_to_le16(0x0100), + .wTotalLength = __cpu_to_le16(0x0009), + .bInCollection = 0x01, + .baInterfaceNr = { 0x01 }, // to be filled +}; + +/* MIDI 1.0 Streaming Interface (altset 0) */ +static struct usb_interface_descriptor midi2_midi1_if_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, // to be filled + .bAlternateSetting = 0, + .bNumEndpoints = 2, // to be filled + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING, + .bInterfaceProtocol = 0, + .iInterface = 0, // to be filled +}; + +static struct usb_ms_header_descriptor midi2_midi1_class_desc = { + .bLength = 0x07, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MS_HEADER, + .bcdMSC = __cpu_to_le16(0x0100), + .wTotalLength = __cpu_to_le16(0x41), // to be calculated +}; + +/* MIDI 1.0 EP OUT */ +static struct usb_endpoint_descriptor midi2_midi1_ep_out_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT | 0, // set up dynamically + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_ss_ep_comp_descriptor midi2_midi1_ep_out_ss_comp_desc = { + .bLength = sizeof(midi2_midi1_ep_out_ss_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_ms_endpoint_descriptor_16 midi2_midi1_ep_out_class_desc = { + .bLength = 0x05, // to be filled + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = USB_MS_GENERAL, + .bNumEmbMIDIJack = 1, + .baAssocJackID = { 0x01 }, +}; + +/* MIDI 1.0 EP IN */ +static struct usb_endpoint_descriptor midi2_midi1_ep_in_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN | 0, // set up dynamically + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_ss_ep_comp_descriptor midi2_midi1_ep_in_ss_comp_desc = { + .bLength = sizeof(midi2_midi1_ep_in_ss_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_ms_endpoint_descriptor_16 midi2_midi1_ep_in_class_desc = { + .bLength = 0x05, // to be filled + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = USB_MS_GENERAL, + .bNumEmbMIDIJack = 1, + .baAssocJackID = { 0x03 }, +}; + +/* MIDI 2.0 Streaming Interface (altset 1) */ +static struct usb_interface_descriptor midi2_midi2_if_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, // to be filled + .bAlternateSetting = 1, + .bNumEndpoints = 2, // to be filled + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING, + .bInterfaceProtocol = 0, + .iInterface = 0, // to be filled +}; + +static struct usb_ms_header_descriptor midi2_midi2_class_desc = { + .bLength = 0x07, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MS_HEADER, + .bcdMSC = __cpu_to_le16(0x0200), + .wTotalLength = __cpu_to_le16(0x07), +}; + +/* MIDI 2.0 EP OUT */ +static struct usb_endpoint_descriptor midi2_midi2_ep_out_desc[MAX_UMP_EPS]; + +static struct usb_ss_ep_comp_descriptor midi2_midi2_ep_out_ss_comp_desc = { + .bLength = sizeof(midi2_midi1_ep_out_ss_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_ms20_endpoint_descriptor_32 midi2_midi2_ep_out_class_desc[MAX_UMP_EPS]; + +/* MIDI 2.0 EP IN */ +static struct usb_endpoint_descriptor midi2_midi2_ep_in_desc[MAX_UMP_EPS]; + +static struct usb_ss_ep_comp_descriptor midi2_midi2_ep_in_ss_comp_desc = { + .bLength = sizeof(midi2_midi2_ep_in_ss_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_ms20_endpoint_descriptor_32 midi2_midi2_ep_in_class_desc[MAX_UMP_EPS]; + +/* Arrays of descriptors to be created */ +static void *midi2_audio_descs[] = { + &midi2_audio_if_desc, + &midi2_audio_class_desc, + NULL +}; + +static void *midi2_midi1_descs[] = { + &midi2_midi1_if_desc, + &midi2_midi1_class_desc, + NULL +}; + +static void *midi2_midi1_ep_out_descs[] = { + &midi2_midi1_ep_out_desc, + &midi2_midi1_ep_out_class_desc, + NULL +}; + +static void *midi2_midi1_ep_in_descs[] = { + &midi2_midi1_ep_in_desc, + &midi2_midi1_ep_in_class_desc, + NULL +}; + +static void *midi2_midi1_ep_out_ss_descs[] = { + &midi2_midi1_ep_out_desc, + &midi2_midi1_ep_out_ss_comp_desc, + &midi2_midi1_ep_out_class_desc, + NULL +}; + +static void *midi2_midi1_ep_in_ss_descs[] = { + &midi2_midi1_ep_in_desc, + &midi2_midi1_ep_in_ss_comp_desc, + &midi2_midi1_ep_in_class_desc, + NULL +}; + +static void *midi2_midi2_descs[] = { + &midi2_midi2_if_desc, + &midi2_midi2_class_desc, + NULL +}; + +/* + * USB request handling + */ + +/* get an empty request for the given EP */ +static struct usb_request *get_empty_request(struct f_midi2_usb_ep *usb_ep) +{ + struct usb_request *req = NULL; + unsigned long flags; + int index; + + spin_lock_irqsave(&usb_ep->card->queue_lock, flags); + if (!usb_ep->free_reqs) + goto unlock; + index = find_first_bit(&usb_ep->free_reqs, usb_ep->num_reqs); + if (index >= usb_ep->num_reqs) + goto unlock; + req = usb_ep->reqs[index].req; + if (!req) + goto unlock; + clear_bit(index, &usb_ep->free_reqs); + req->length = 0; + unlock: + spin_unlock_irqrestore(&usb_ep->card->queue_lock, flags); + return req; +} + +/* put the empty request back */ +static void put_empty_request(struct usb_request *req) +{ + struct f_midi2_req_ctx *ctx = req->context; + unsigned long flags; + + spin_lock_irqsave(&ctx->usb_ep->card->queue_lock, flags); + set_bit(ctx->index, &ctx->usb_ep->free_reqs); + spin_unlock_irqrestore(&ctx->usb_ep->card->queue_lock, flags); +} + +/* + * UMP v1.1 Stream message handling + */ + +/* queue a request to UMP EP; request is either queued or freed after this */ +static int queue_request_ep_raw(struct usb_request *req) +{ + struct f_midi2_req_ctx *ctx = req->context; + int err; + + req->complete = ctx->usb_ep->complete; + err = usb_ep_queue(ctx->usb_ep->usb_ep, req, GFP_ATOMIC); + if (err) { + put_empty_request(req); + return err; + } + return 0; +} + +/* queue a request with endianness conversion */ +static int queue_request_ep_in(struct usb_request *req) +{ + /* UMP packets have to be converted to little-endian */ + cpu_to_le32_array((u32 *)req->buf, req->length >> 2); + return queue_request_ep_raw(req); +} + +/* reply a UMP packet via EP-in */ +static int reply_ep_in(struct f_midi2_ep *ep, const void *buf, int len) +{ + struct f_midi2_usb_ep *usb_ep = &ep->ep_in; + struct usb_request *req; + + req = get_empty_request(usb_ep); + if (!req) + return -ENOSPC; + + req->length = len; + memcpy(req->buf, buf, len); + return queue_request_ep_in(req); +} + +/* reply a UMP stream EP info */ +static void reply_ump_stream_ep_info(struct f_midi2_ep *ep) +{ + struct snd_ump_stream_msg_ep_info rep = { + .type = UMP_MSG_TYPE_STREAM, + .status = UMP_STREAM_MSG_STATUS_EP_INFO, + .ump_version_major = 0x01, + .ump_version_minor = 0x01, + .num_function_blocks = ep->num_blks, + .static_function_block = !!ep->card->info.static_block, + .protocol = (UMP_STREAM_MSG_EP_INFO_CAP_MIDI1 | + UMP_STREAM_MSG_EP_INFO_CAP_MIDI2) >> 8, + }; + + reply_ep_in(ep, &rep, sizeof(rep)); +} + +/* reply a UMP EP device info */ +static void reply_ump_stream_ep_device(struct f_midi2_ep *ep) +{ + struct snd_ump_stream_msg_device_info rep = { + .type = UMP_MSG_TYPE_STREAM, + .status = UMP_STREAM_MSG_STATUS_DEVICE_INFO, + .manufacture_id = ep->info.manufacturer, + .family_lsb = ep->info.family & 0xff, + .family_msb = (ep->info.family >> 8) & 0xff, + .model_lsb = ep->info.model & 0xff, + .model_msb = (ep->info.model >> 8) & 0xff, + .sw_revision = ep->info.sw_revision, + }; + + reply_ep_in(ep, &rep, sizeof(rep)); +} + +#define UMP_STREAM_PKT_BYTES 16 /* UMP stream packet size = 16 bytes*/ +#define UMP_STREAM_EP_STR_OFF 2 /* offset of name string for EP info */ +#define UMP_STREAM_FB_STR_OFF 3 /* offset of name string for FB info */ + +/* Helper to replay a string */ +static void reply_ump_stream_string(struct f_midi2_ep *ep, const u8 *name, + unsigned int type, unsigned int extra, + unsigned int start_ofs) +{ + struct f_midi2_usb_ep *usb_ep = &ep->ep_in; + struct f_midi2 *midi2 = ep->card; + struct usb_request *req; + unsigned int pos; + u32 *buf; + + if (!*name) + return; + req = get_empty_request(usb_ep); + if (!req) + return; + + buf = (u32 *)req->buf; + pos = start_ofs; + for (;;) { + if (pos == start_ofs) { + memset(buf, 0, UMP_STREAM_PKT_BYTES); + buf[0] = ump_stream_compose(type, 0) | extra; + } + buf[pos / 4] |= *name++ << ((3 - (pos % 4)) * 8); + if (!*name) { + if (req->length) + buf[0] |= UMP_STREAM_MSG_FORMAT_END << 26; + req->length += UMP_STREAM_PKT_BYTES; + break; + } + if (++pos == UMP_STREAM_PKT_BYTES) { + if (!req->length) + buf[0] |= UMP_STREAM_MSG_FORMAT_START << 26; + else + buf[0] |= UMP_STREAM_MSG_FORMAT_CONTINUE << 26; + req->length += UMP_STREAM_PKT_BYTES; + if (midi2->info.req_buf_size - req->length < UMP_STREAM_PKT_BYTES) + break; + buf += 4; + pos = start_ofs; + } + } + + if (req->length) + queue_request_ep_in(req); + else + put_empty_request(req); +} + +/* Reply a UMP EP name string */ +static void reply_ump_stream_ep_name(struct f_midi2_ep *ep) +{ + reply_ump_stream_string(ep, ump_ep_name(ep), + UMP_STREAM_MSG_STATUS_EP_NAME, 0, + UMP_STREAM_EP_STR_OFF); +} + +/* Reply a UMP EP product ID string */ +static void reply_ump_stream_ep_pid(struct f_midi2_ep *ep) +{ + reply_ump_stream_string(ep, ump_product_id(ep), + UMP_STREAM_MSG_STATUS_PRODUCT_ID, 0, + UMP_STREAM_EP_STR_OFF); +} + +/* Reply a UMP EP stream config */ +static void reply_ump_stream_ep_config(struct f_midi2_ep *ep) +{ + struct snd_ump_stream_msg_stream_cfg rep = { + .type = UMP_MSG_TYPE_STREAM, + .status = UMP_STREAM_MSG_STATUS_STREAM_CFG, + }; + + if (ep->info.protocol == 2) + rep.protocol = UMP_STREAM_MSG_EP_INFO_CAP_MIDI2 >> 8; + else + rep.protocol = UMP_STREAM_MSG_EP_INFO_CAP_MIDI1 >> 8; + + reply_ep_in(ep, &rep, sizeof(rep)); +} + +/* Reply a UMP FB info */ +static void reply_ump_stream_fb_info(struct f_midi2_ep *ep, int blk) +{ + struct f_midi2_block_info *b = &ep->blks[blk].info; + struct snd_ump_stream_msg_fb_info rep = { + .type = UMP_MSG_TYPE_STREAM, + .status = UMP_STREAM_MSG_STATUS_FB_INFO, + .active = !!b->active, + .function_block_id = blk, + .ui_hint = b->ui_hint, + .midi_10 = b->is_midi1, + .direction = b->direction, + .first_group = b->first_group, + .num_groups = b->num_groups, + .midi_ci_version = b->midi_ci_version, + .sysex8_streams = b->sysex8_streams, + }; + + reply_ep_in(ep, &rep, sizeof(rep)); +} + +/* Reply a FB name string */ +static void reply_ump_stream_fb_name(struct f_midi2_ep *ep, unsigned int blk) +{ + reply_ump_stream_string(ep, ump_fb_name(&ep->blks[blk].info), + UMP_STREAM_MSG_STATUS_FB_NAME, blk << 8, + UMP_STREAM_FB_STR_OFF); +} + +/* Process a UMP Stream message */ +static void process_ump_stream_msg(struct f_midi2_ep *ep, const u32 *data) +{ + struct f_midi2 *midi2 = ep->card; + unsigned int format, status, blk; + + format = ump_stream_message_format(*data); + status = ump_stream_message_status(*data); + switch (status) { + case UMP_STREAM_MSG_STATUS_EP_DISCOVERY: + if (format) + return; // invalid + if (data[1] & UMP_STREAM_MSG_REQUEST_EP_INFO) + reply_ump_stream_ep_info(ep); + if (data[1] & UMP_STREAM_MSG_REQUEST_DEVICE_INFO) + reply_ump_stream_ep_device(ep); + if (data[1] & UMP_STREAM_MSG_REQUEST_EP_NAME) + reply_ump_stream_ep_name(ep); + if (data[1] & UMP_STREAM_MSG_REQUEST_PRODUCT_ID) + reply_ump_stream_ep_pid(ep); + if (data[1] & UMP_STREAM_MSG_REQUEST_STREAM_CFG) + reply_ump_stream_ep_config(ep); + return; + case UMP_STREAM_MSG_STATUS_STREAM_CFG_REQUEST: + if (*data & UMP_STREAM_MSG_EP_INFO_CAP_MIDI2) { + ep->info.protocol = 2; + DBG(midi2, "Switching Protocol to MIDI2\n"); + } else { + ep->info.protocol = 1; + DBG(midi2, "Switching Protocol to MIDI1\n"); + } + snd_ump_switch_protocol(ep->ump, to_ump_protocol(ep->info.protocol)); + reply_ump_stream_ep_config(ep); + return; + case UMP_STREAM_MSG_STATUS_FB_DISCOVERY: + if (format) + return; // invalid + blk = (*data >> 8) & 0xff; + if (blk == 0xff) { + /* inquiry for all blocks */ + for (blk = 0; blk < ep->num_blks; blk++) { + if (*data & UMP_STREAM_MSG_REQUEST_FB_INFO) + reply_ump_stream_fb_info(ep, blk); + if (*data & UMP_STREAM_MSG_REQUEST_FB_NAME) + reply_ump_stream_fb_name(ep, blk); + } + } else if (blk < ep->num_blks) { + /* only the specified block */ + if (*data & UMP_STREAM_MSG_REQUEST_FB_INFO) + reply_ump_stream_fb_info(ep, blk); + if (*data & UMP_STREAM_MSG_REQUEST_FB_NAME) + reply_ump_stream_fb_name(ep, blk); + } + return; + } +} + +/* Process UMP messages included in a USB request */ +static void process_ump(struct f_midi2_ep *ep, const struct usb_request *req) +{ + const u32 *data = (u32 *)req->buf; + int len = req->actual >> 2; + const u32 *in_buf = ep->ump->input_buf; + + for (; len > 0; len--, data++) { + if (snd_ump_receive_ump_val(ep->ump, *data) <= 0) + continue; + if (ump_message_type(*in_buf) == UMP_MSG_TYPE_STREAM) + process_ump_stream_msg(ep, in_buf); + } +} + +/* + * MIDI 2.0 UMP USB request handling + */ + +/* complete handler for UMP EP-out requests */ +static void f_midi2_ep_out_complete(struct usb_ep *usb_ep, + struct usb_request *req) +{ + struct f_midi2_req_ctx *ctx = req->context; + struct f_midi2_ep *ep = ctx->usb_ep->ep; + struct f_midi2 *midi2 = ep->card; + int status = req->status; + + if (status) { + DBG(midi2, "%s complete error %d: %d/%d\n", + usb_ep->name, status, req->actual, req->length); + goto error; + } + + /* convert to UMP packet in native endianness */ + le32_to_cpu_array((u32 *)req->buf, req->actual >> 2); + + if (midi2->info.process_ump) + process_ump(ep, req); + + snd_ump_receive(ep->ump, req->buf, req->actual & ~3); + + if (midi2->operation_mode != MIDI_OP_MODE_MIDI2) + goto error; + + if (queue_request_ep_raw(req)) + goto error; + return; + + error: + put_empty_request(req); +} + +/* Transmit UMP packets received from user-space to the gadget */ +static void process_ump_transmit(struct f_midi2_ep *ep) +{ + struct f_midi2_usb_ep *usb_ep = &ep->ep_in; + struct f_midi2 *midi2 = ep->card; + struct usb_request *req; + int len; + + if (!usb_ep->usb_ep->enabled) + return; + + for (;;) { + req = get_empty_request(usb_ep); + if (!req) + break; + len = snd_ump_transmit(ep->ump, (u32 *)req->buf, + midi2->info.req_buf_size); + if (len <= 0) { + put_empty_request(req); + break; + } + + req->length = len; + if (queue_request_ep_in(req) < 0) + break; + } +} + +/* Complete handler for UMP EP-in requests */ +static void f_midi2_ep_in_complete(struct usb_ep *usb_ep, + struct usb_request *req) +{ + struct f_midi2_req_ctx *ctx = req->context; + struct f_midi2_ep *ep = ctx->usb_ep->ep; + struct f_midi2 *midi2 = ep->card; + int status = req->status; + + put_empty_request(req); + + if (status) { + DBG(midi2, "%s complete error %d: %d/%d\n", + usb_ep->name, status, req->actual, req->length); + return; + } + + process_ump_transmit(ep); +} + +/* + * MIDI1 (altset 0) USB request handling + */ + +/* process one MIDI byte -- copied from f_midi.c + * + * fill the packet or request if needed + * returns true if the request became empty (queued) + */ +static bool process_midi1_byte(struct f_midi2 *midi2, u8 cable, u8 b, + struct usb_request **req_p) +{ + struct f_midi2_midi1_port *port = &midi2->midi1_port[cable]; + u8 p[4] = { cable << 4, 0, 0, 0 }; + int next_state = STATE_INITIAL; + struct usb_request *req = *req_p; + + switch (b) { + case 0xf8 ... 0xff: + /* System Real-Time Messages */ + p[0] |= 0x0f; + p[1] = b; + next_state = port->state; + port->state = STATE_REAL_TIME; + break; + + case 0xf7: + /* End of SysEx */ + switch (port->state) { + case STATE_SYSEX_0: + p[0] |= 0x05; + p[1] = 0xf7; + next_state = STATE_FINISHED; + break; + case STATE_SYSEX_1: + p[0] |= 0x06; + p[1] = port->data[0]; + p[2] = 0xf7; + next_state = STATE_FINISHED; + break; + case STATE_SYSEX_2: + p[0] |= 0x07; + p[1] = port->data[0]; + p[2] = port->data[1]; + p[3] = 0xf7; + next_state = STATE_FINISHED; + break; + default: + /* Ignore byte */ + next_state = port->state; + port->state = STATE_INITIAL; + } + break; + + case 0xf0 ... 0xf6: + /* System Common Messages */ + port->data[0] = port->data[1] = 0; + port->state = STATE_INITIAL; + switch (b) { + case 0xf0: + port->data[0] = b; + port->data[1] = 0; + next_state = STATE_SYSEX_1; + break; + case 0xf1: + case 0xf3: + port->data[0] = b; + next_state = STATE_1PARAM; + break; + case 0xf2: + port->data[0] = b; + next_state = STATE_2PARAM_1; + break; + case 0xf4: + case 0xf5: + next_state = STATE_INITIAL; + break; + case 0xf6: + p[0] |= 0x05; + p[1] = 0xf6; + next_state = STATE_FINISHED; + break; + } + break; + + case 0x80 ... 0xef: + /* + * Channel Voice Messages, Channel Mode Messages + * and Control Change Messages. + */ + port->data[0] = b; + port->data[1] = 0; + port->state = STATE_INITIAL; + if (b >= 0xc0 && b <= 0xdf) + next_state = STATE_1PARAM; + else + next_state = STATE_2PARAM_1; + break; + + case 0x00 ... 0x7f: + /* Message parameters */ + switch (port->state) { + case STATE_1PARAM: + if (port->data[0] < 0xf0) + p[0] |= port->data[0] >> 4; + else + p[0] |= 0x02; + + p[1] = port->data[0]; + p[2] = b; + /* This is to allow Running State Messages */ + next_state = STATE_1PARAM; + break; + case STATE_2PARAM_1: + port->data[1] = b; + next_state = STATE_2PARAM_2; + break; + case STATE_2PARAM_2: + if (port->data[0] < 0xf0) + p[0] |= port->data[0] >> 4; + else + p[0] |= 0x03; + + p[1] = port->data[0]; + p[2] = port->data[1]; + p[3] = b; + /* This is to allow Running State Messages */ + next_state = STATE_2PARAM_1; + break; + case STATE_SYSEX_0: + port->data[0] = b; + next_state = STATE_SYSEX_1; + break; + case STATE_SYSEX_1: + port->data[1] = b; + next_state = STATE_SYSEX_2; + break; + case STATE_SYSEX_2: + p[0] |= 0x04; + p[1] = port->data[0]; + p[2] = port->data[1]; + p[3] = b; + next_state = STATE_SYSEX_0; + break; + } + break; + } + + /* States where we have to write into the USB request */ + if (next_state == STATE_FINISHED || + port->state == STATE_SYSEX_2 || + port->state == STATE_1PARAM || + port->state == STATE_2PARAM_2 || + port->state == STATE_REAL_TIME) { + memcpy(req->buf + req->length, p, sizeof(p)); + req->length += sizeof(p); + + if (next_state == STATE_FINISHED) { + next_state = STATE_INITIAL; + port->data[0] = port->data[1] = 0; + } + + if (midi2->info.req_buf_size - req->length <= 4) { + queue_request_ep_raw(req); + *req_p = NULL; + return true; + } + } + + port->state = next_state; + return false; +} + +/* process all pending MIDI bytes in the internal buffer; + * returns true if the request gets empty + * returns false if all have been processed + */ +static bool process_midi1_pending_buf(struct f_midi2 *midi2, + struct usb_request **req_p) +{ + unsigned int cable, c; + + for (cable = 0; cable < midi2->num_midi1_in; cable++) { + struct f_midi2_midi1_port *port = &midi2->midi1_port[cable]; + + if (!port->pending) + continue; + for (c = 0; c < port->pending; c++) { + if (process_midi1_byte(midi2, cable, port->buf[c], + req_p)) { + port->pending -= c; + if (port->pending) + memmove(port->buf, port->buf + c, + port->pending); + return true; + } + } + port->pending = 0; + } + + return false; +} + +/* fill the MIDI bytes onto the temporary buffer + */ +static void fill_midi1_pending_buf(struct f_midi2 *midi2, u8 cable, u8 *buf, + unsigned int size) +{ + struct f_midi2_midi1_port *port = &midi2->midi1_port[cable]; + + if (port->pending + size > sizeof(port->buf)) + return; + memcpy(port->buf + port->pending, buf, size); + port->pending += size; +} + +/* try to process data given from the associated UMP stream */ +static void process_midi1_transmit(struct f_midi2 *midi2) +{ + struct f_midi2_usb_ep *usb_ep = &midi2->midi1_ep_in; + struct f_midi2_ep *ep = &midi2->midi2_eps[0]; + struct usb_request *req = NULL; + /* 12 is the largest outcome (4 MIDI1 cmds) for a single UMP packet */ + unsigned char outbuf[12]; + unsigned char group, cable; + int len, size; + u32 ump; + + if (!usb_ep->usb_ep || !usb_ep->usb_ep->enabled) + return; + + for (;;) { + if (!req) { + req = get_empty_request(usb_ep); + if (!req) + break; + } + + if (process_midi1_pending_buf(midi2, &req)) + continue; + + len = snd_ump_transmit(ep->ump, &ump, 4); + if (len <= 0) + break; + if (snd_ump_receive_ump_val(ep->ump, ump) <= 0) + continue; + size = snd_ump_convert_from_ump(ep->ump->input_buf, outbuf, + &group); + if (size <= 0) + continue; + cable = ep->in_group_to_cable[group]; + if (!cable) + continue; + cable--; /* to 0-base */ + fill_midi1_pending_buf(midi2, cable, outbuf, size); + } + + if (req) { + if (req->length) + queue_request_ep_raw(req); + else + put_empty_request(req); + } +} + +/* complete handler for MIDI1 EP-in requests */ +static void f_midi2_midi1_ep_in_complete(struct usb_ep *usb_ep, + struct usb_request *req) +{ + struct f_midi2_req_ctx *ctx = req->context; + struct f_midi2 *midi2 = ctx->usb_ep->card; + int status = req->status; + + put_empty_request(req); + + if (status) { + DBG(midi2, "%s complete error %d: %d/%d\n", + usb_ep->name, status, req->actual, req->length); + return; + } + + process_midi1_transmit(midi2); +} + +/* complete handler for MIDI1 EP-out requests */ +static void f_midi2_midi1_ep_out_complete(struct usb_ep *usb_ep, + struct usb_request *req) +{ + struct f_midi2_req_ctx *ctx = req->context; + struct f_midi2 *midi2 = ctx->usb_ep->card; + struct f_midi2_ep *ep; + struct ump_cvt_to_ump *cvt = &midi2->midi1_ump_cvt; + static const u8 midi1_packet_bytes[16] = { + 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1 + }; + unsigned int group, cable, bytes, c, len; + int status = req->status; + const u8 *buf = req->buf; + + if (status) { + DBG(midi2, "%s complete error %d: %d/%d\n", + usb_ep->name, status, req->actual, req->length); + goto error; + } + + len = req->actual >> 2; + for (; len; len--, buf += 4) { + cable = *buf >> 4; + ep = midi2->out_cable_mapping[cable].ep; + if (!ep) + continue; + group = midi2->out_cable_mapping[cable].group; + bytes = midi1_packet_bytes[*buf & 0x0f]; + for (c = 0; c < bytes; c++) { + snd_ump_convert_to_ump(cvt, group, + to_ump_protocol(ep->info.protocol), + buf[c + 1]); + if (cvt->ump_bytes) { + snd_ump_receive(ep->ump, cvt->ump, + cvt->ump_bytes); + cvt->ump_bytes = 0; + } + } + } + + if (midi2->operation_mode != MIDI_OP_MODE_MIDI1) + goto error; + + if (queue_request_ep_raw(req)) + goto error; + return; + + error: + put_empty_request(req); +} + +/* + * Common EP handling helpers + */ + +/* Start MIDI EP */ +static int f_midi2_start_ep(struct f_midi2_usb_ep *usb_ep, + struct usb_function *fn) +{ + int err; + + if (!usb_ep->usb_ep) + return 0; + + usb_ep_disable(usb_ep->usb_ep); + err = config_ep_by_speed(usb_ep->card->gadget, fn, usb_ep->usb_ep); + if (err) + return err; + return usb_ep_enable(usb_ep->usb_ep); +} + +/* Drop pending requests */ +static void f_midi2_drop_reqs(struct f_midi2_usb_ep *usb_ep) +{ + int i; + + if (!usb_ep->usb_ep || !usb_ep->num_reqs) + return; + + for (i = 0; i < usb_ep->num_reqs; i++) { + if (!test_bit(i, &usb_ep->free_reqs) && usb_ep->reqs[i].req) { + usb_ep_dequeue(usb_ep->usb_ep, usb_ep->reqs[i].req); + set_bit(i, &usb_ep->free_reqs); + } + } +} + +/* Allocate requests for the given EP */ +static int f_midi2_alloc_ep_reqs(struct f_midi2_usb_ep *usb_ep) +{ + struct f_midi2 *midi2 = usb_ep->card; + int i; + + if (!usb_ep->usb_ep) + return 0; + if (!usb_ep->reqs) + return -EINVAL; + + for (i = 0; i < midi2->info.num_reqs; i++) { + if (usb_ep->reqs[i].req) + continue; + usb_ep->reqs[i].req = alloc_ep_req(usb_ep->usb_ep, + midi2->info.req_buf_size); + if (!usb_ep->reqs[i].req) + return -ENOMEM; + usb_ep->reqs[i].req->context = &usb_ep->reqs[i]; + } + return 0; +} + +/* Free allocated requests */ +static void f_midi2_free_ep_reqs(struct f_midi2_usb_ep *usb_ep) +{ + struct f_midi2 *midi2 = usb_ep->card; + int i; + + for (i = 0; i < midi2->info.num_reqs; i++) { + if (!usb_ep->reqs[i].req) + continue; + free_ep_req(usb_ep->usb_ep, usb_ep->reqs[i].req); + usb_ep->reqs[i].req = NULL; + } +} + +/* Initialize EP */ +static int f_midi2_init_ep(struct f_midi2 *midi2, struct f_midi2_ep *ep, + struct f_midi2_usb_ep *usb_ep, + void *desc, + void (*complete)(struct usb_ep *usb_ep, + struct usb_request *req)) +{ + int i; + + usb_ep->card = midi2; + usb_ep->ep = ep; + usb_ep->usb_ep = usb_ep_autoconfig(midi2->gadget, desc); + if (!usb_ep->usb_ep) + return -ENODEV; + usb_ep->complete = complete; + + usb_ep->reqs = kzalloc_objs(*usb_ep->reqs, midi2->info.num_reqs); + if (!usb_ep->reqs) + return -ENOMEM; + for (i = 0; i < midi2->info.num_reqs; i++) { + usb_ep->reqs[i].index = i; + usb_ep->reqs[i].usb_ep = usb_ep; + set_bit(i, &usb_ep->free_reqs); + usb_ep->num_reqs++; + } + + return 0; +} + +/* Free EP */ +static void f_midi2_free_ep(struct f_midi2_usb_ep *usb_ep) +{ + f_midi2_drop_reqs(usb_ep); + + f_midi2_free_ep_reqs(usb_ep); + + kfree(usb_ep->reqs); + usb_ep->num_reqs = 0; + usb_ep->free_reqs = 0; + usb_ep->reqs = NULL; +} + +/* Queue requests for EP-out at start */ +static void f_midi2_queue_out_reqs(struct f_midi2_usb_ep *usb_ep) +{ + int i, err; + + if (!usb_ep->usb_ep) + return; + + for (i = 0; i < usb_ep->num_reqs; i++) { + if (!test_bit(i, &usb_ep->free_reqs) || !usb_ep->reqs[i].req) + continue; + usb_ep->reqs[i].req->complete = usb_ep->complete; + err = usb_ep_queue(usb_ep->usb_ep, usb_ep->reqs[i].req, + GFP_ATOMIC); + if (!err) + clear_bit(i, &usb_ep->free_reqs); + } +} + +/* + * Gadget Function callbacks + */ + +/* stop both IN and OUT EPs */ +static void f_midi2_stop_eps(struct f_midi2_usb_ep *ep_in, + struct f_midi2_usb_ep *ep_out) +{ + f_midi2_drop_reqs(ep_in); + f_midi2_drop_reqs(ep_out); + f_midi2_free_ep_reqs(ep_in); + f_midi2_free_ep_reqs(ep_out); +} + +/* start/queue both IN and OUT EPs */ +static int f_midi2_start_eps(struct f_midi2_usb_ep *ep_in, + struct f_midi2_usb_ep *ep_out, + struct usb_function *fn) +{ + int err; + + err = f_midi2_start_ep(ep_in, fn); + if (err) + return err; + err = f_midi2_start_ep(ep_out, fn); + if (err) + return err; + + err = f_midi2_alloc_ep_reqs(ep_in); + if (err) + return err; + err = f_midi2_alloc_ep_reqs(ep_out); + if (err) + return err; + + f_midi2_queue_out_reqs(ep_out); + return 0; +} + +/* gadget function set_alt callback */ +static int f_midi2_set_alt(struct usb_function *fn, unsigned int intf, + unsigned int alt) +{ + struct f_midi2 *midi2 = func_to_midi2(fn); + struct f_midi2_ep *ep; + int i, op_mode, err; + + if (intf != midi2->midi_if || alt > 1) + return 0; + + if (alt == 0) + op_mode = MIDI_OP_MODE_MIDI1; + else + op_mode = MIDI_OP_MODE_MIDI2; + + if (midi2->operation_mode == op_mode) + return 0; + + midi2->operation_mode = op_mode; + + if (op_mode != MIDI_OP_MODE_MIDI1) + f_midi2_stop_eps(&midi2->midi1_ep_in, &midi2->midi1_ep_out); + + if (op_mode != MIDI_OP_MODE_MIDI2) { + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; + f_midi2_stop_eps(&ep->ep_in, &ep->ep_out); + } + } + + if (op_mode == MIDI_OP_MODE_MIDI1) + return f_midi2_start_eps(&midi2->midi1_ep_in, + &midi2->midi1_ep_out, fn); + + if (op_mode == MIDI_OP_MODE_MIDI2) { + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; + + err = f_midi2_start_eps(&ep->ep_in, &ep->ep_out, fn); + if (err) + return err; + } + } + + return 0; +} + +/* gadget function get_alt callback */ +static int f_midi2_get_alt(struct usb_function *fn, unsigned int intf) +{ + struct f_midi2 *midi2 = func_to_midi2(fn); + + if (intf == midi2->midi_if && + midi2->operation_mode == MIDI_OP_MODE_MIDI2) + return 1; + return 0; +} + +/* convert UMP direction to USB MIDI 2.0 direction */ +static unsigned int ump_to_usb_dir(unsigned int ump_dir) +{ + switch (ump_dir) { + case SNDRV_UMP_DIR_INPUT: + return USB_MS_GR_TRM_BLOCK_TYPE_INPUT_ONLY; + case SNDRV_UMP_DIR_OUTPUT: + return USB_MS_GR_TRM_BLOCK_TYPE_OUTPUT_ONLY; + default: + return USB_MS_GR_TRM_BLOCK_TYPE_BIDIRECTIONAL; + } +} + +/* assign GTB descriptors (for the given request) */ +static void assign_block_descriptors(struct f_midi2 *midi2, + struct usb_request *req, + int max_len) +{ + struct usb_ms20_gr_trm_block_header_descriptor header; + struct usb_ms20_gr_trm_block_descriptor *desc; + struct f_midi2_block_info *b; + struct f_midi2_ep *ep; + int i, blk, len; + char *data; + + len = sizeof(gtb_header_desc) + sizeof(gtb_desc) * midi2->total_blocks; + if (WARN_ON(len > midi2->info.req_buf_size)) + return; + + header = gtb_header_desc; + header.wTotalLength = cpu_to_le16(len); + if (max_len < len) { + len = min_t(int, len, sizeof(header)); + memcpy(req->buf, &header, len); + req->length = len; + req->zero = len < max_len; + return; + } + + memcpy(req->buf, &header, sizeof(header)); + data = req->buf + sizeof(header); + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; + for (blk = 0; blk < ep->num_blks; blk++) { + b = &ep->blks[blk].info; + desc = (struct usb_ms20_gr_trm_block_descriptor *)data; + + *desc = gtb_desc; + desc->bGrpTrmBlkID = ep->blks[blk].gtb_id; + desc->bGrpTrmBlkType = ump_to_usb_dir(b->direction); + desc->nGroupTrm = b->first_group; + desc->nNumGroupTrm = b->num_groups; + desc->iBlockItem = ep->blks[blk].string_id; + + if (ep->info.protocol == 2) + desc->bMIDIProtocol = USB_MS_MIDI_PROTO_2_0; + else + desc->bMIDIProtocol = USB_MS_MIDI_PROTO_1_0_128; + + if (b->is_midi1 == 2) { + desc->wMaxInputBandwidth = cpu_to_le16(1); + desc->wMaxOutputBandwidth = cpu_to_le16(1); + } + + data += sizeof(*desc); + } + } + + req->length = len; + req->zero = len < max_len; +} + +/* gadget function setup callback: handle GTB requests */ +static int f_midi2_setup(struct usb_function *fn, + const struct usb_ctrlrequest *ctrl) +{ + struct f_midi2 *midi2 = func_to_midi2(fn); + struct usb_composite_dev *cdev = fn->config->cdev; + struct usb_request *req = cdev->req; + u16 value, length; + + if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD || + ctrl->bRequest != USB_REQ_GET_DESCRIPTOR) + return -EOPNOTSUPP; + + value = le16_to_cpu(ctrl->wValue); + length = le16_to_cpu(ctrl->wLength); + + if ((value >> 8) != USB_DT_CS_GR_TRM_BLOCK) + return -EOPNOTSUPP; + + /* handle only altset 1 */ + if ((value & 0xff) != 1) + return -EOPNOTSUPP; + + assign_block_descriptors(midi2, req, length); + return usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); +} + +/* gadget function disable callback */ +static void f_midi2_disable(struct usb_function *fn) +{ + struct f_midi2 *midi2 = func_to_midi2(fn); + + midi2->operation_mode = MIDI_OP_MODE_UNSET; +} + +/* + * ALSA UMP ops: most of them are NOPs, only trigger for write is needed + */ +static int f_midi2_ump_open(struct snd_ump_endpoint *ump, int dir) +{ + return 0; +} + +static void f_midi2_ump_close(struct snd_ump_endpoint *ump, int dir) +{ +} + +static void f_midi2_ump_trigger(struct snd_ump_endpoint *ump, int dir, int up) +{ + struct f_midi2_ep *ep = ump->private_data; + struct f_midi2 *midi2 = ep->card; + + if (up && dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + switch (midi2->operation_mode) { + case MIDI_OP_MODE_MIDI1: + process_midi1_transmit(midi2); + break; + case MIDI_OP_MODE_MIDI2: + process_ump_transmit(ep); + break; + } + } +} + +static void f_midi2_ump_drain(struct snd_ump_endpoint *ump, int dir) +{ +} + +static const struct snd_ump_ops f_midi2_ump_ops = { + .open = f_midi2_ump_open, + .close = f_midi2_ump_close, + .trigger = f_midi2_ump_trigger, + .drain = f_midi2_ump_drain, +}; + +/* + * "Operation Mode" control element + */ +static int f_midi2_operation_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = MIDI_OP_MODE_UNSET; + uinfo->value.integer.max = MIDI_OP_MODE_MIDI2; + return 0; +} + +static int f_midi2_operation_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct f_midi2 *midi2 = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = midi2->operation_mode; + return 0; +} + +static const struct snd_kcontrol_new operation_mode_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI, + .name = "Operation Mode", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = f_midi2_operation_mode_info, + .get = f_midi2_operation_mode_get, +}; + +/* + * ALSA UMP instance creation / deletion + */ +static void f_midi2_free_card(struct f_midi2 *midi2) +{ + if (midi2->card) { + snd_card_free_when_closed(midi2->card); + midi2->card = NULL; + } +} + +/* use a reverse direction for the gadget host */ +static int reverse_dir(int dir) +{ + if (!dir || dir == SNDRV_UMP_DIR_BIDIRECTION) + return dir; + return (dir == SNDRV_UMP_DIR_OUTPUT) ? + SNDRV_UMP_DIR_INPUT : SNDRV_UMP_DIR_OUTPUT; +} + +static int f_midi2_create_card(struct f_midi2 *midi2) +{ + struct snd_card *card; + struct snd_ump_endpoint *ump; + struct f_midi2_ep *ep; + int i, id, blk, err; + __be32 sw; + + err = snd_card_new(&midi2->gadget->dev, -1, NULL, THIS_MODULE, 0, + &card); + if (err < 0) + return err; + midi2->card = card; + + strscpy(card->driver, "f_midi2"); + strscpy(card->shortname, "MIDI 2.0 Gadget"); + strscpy(card->longname, "MIDI 2.0 Gadget"); + + id = 0; + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; + err = snd_ump_endpoint_new(card, "MIDI 2.0 Gadget", id, + 1, 1, &ump); + if (err < 0) + goto error; + id++; + + ep->ump = ump; + ump->no_process_stream = true; + ump->private_data = ep; + ump->ops = &f_midi2_ump_ops; + if (midi2->info.static_block) + ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS; + ump->info.protocol_caps = (ep->info.protocol_caps & 3) << 8; + ump->info.protocol = to_ump_protocol(ep->info.protocol); + ump->info.version = 0x0101; + ump->info.family_id = ep->info.family; + ump->info.model_id = ep->info.model; + ump->info.manufacturer_id = ep->info.manufacturer & 0xffffff; + sw = cpu_to_be32(ep->info.sw_revision); + memcpy(ump->info.sw_revision, &sw, 4); + + strscpy(ump->info.name, ump_ep_name(ep), + sizeof(ump->info.name)); + strscpy(ump->info.product_id, ump_product_id(ep), + sizeof(ump->info.product_id)); + strscpy(ump->core.name, ump->info.name, sizeof(ump->core.name)); + + for (blk = 0; blk < ep->num_blks; blk++) { + const struct f_midi2_block_info *b = &ep->blks[blk].info; + struct snd_ump_block *fb; + + err = snd_ump_block_new(ump, blk, + reverse_dir(b->direction), + b->first_group, b->num_groups, + &ep->blks[blk].fb); + if (err < 0) + goto error; + fb = ep->blks[blk].fb; + fb->info.active = !!b->active; + fb->info.midi_ci_version = b->midi_ci_version; + fb->info.ui_hint = reverse_dir(b->ui_hint); + fb->info.sysex8_streams = b->sysex8_streams; + if (b->is_midi1 < 2) + fb->info.flags |= b->is_midi1; + else + fb->info.flags |= SNDRV_UMP_BLOCK_IS_MIDI1 | + SNDRV_UMP_BLOCK_IS_LOWSPEED; + strscpy(fb->info.name, ump_fb_name(b), + sizeof(fb->info.name)); + } + snd_ump_update_group_attrs(ump); + } + + for (i = 0; i < midi2->num_eps; i++) { + err = snd_ump_attach_legacy_rawmidi(midi2->midi2_eps[i].ump, + "Legacy MIDI", id); + if (err < 0) + goto error; + id++; + } + + err = snd_ctl_add(card, snd_ctl_new1(&operation_mode_ctl, midi2)); + if (err < 0) + goto error; + + err = snd_card_register(card); + if (err < 0) + goto error; + + return 0; + + error: + f_midi2_free_card(midi2); + return err; +} + +/* + * Creation of USB descriptors + */ +struct f_midi2_usb_config { + struct usb_descriptor_header **list; + unsigned int size; + unsigned int alloc; + + /* MIDI 1.0 jacks */ + unsigned char jack_in, jack_out, jack_id; + struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES]; + struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES]; +}; + +static int append_config(struct f_midi2_usb_config *config, void *d) +{ + unsigned int size; + void *buf; + + if (config->size + 2 >= config->alloc) { + size = config->size + 16; + buf = krealloc(config->list, size * sizeof(void *), GFP_KERNEL); + if (!buf) + return -ENOMEM; + config->list = buf; + config->alloc = size; + } + + config->list[config->size] = d; + config->size++; + config->list[config->size] = NULL; + return 0; +} + +static int append_configs(struct f_midi2_usb_config *config, void **d) +{ + int err; + + for (; *d; d++) { + err = append_config(config, *d); + if (err) + return err; + } + return 0; +} + +static int append_midi1_in_jack(struct f_midi2 *midi2, + struct f_midi2_usb_config *config, + struct midi1_cable_mapping *map, + unsigned int type) +{ + struct usb_midi_in_jack_descriptor *jack = + &config->jack_ins[config->jack_in++]; + int id = ++config->jack_id; + int err; + + jack->bLength = 0x06; + jack->bDescriptorType = USB_DT_CS_INTERFACE; + jack->bDescriptorSubtype = USB_MS_MIDI_IN_JACK; + jack->bJackType = type; + jack->bJackID = id; + /* use the corresponding block name as jack name */ + if (map->ep) + jack->iJack = map->ep->blks[map->block].string_id; + + err = append_config(config, jack); + if (err < 0) + return err; + return id; +} + +static int append_midi1_out_jack(struct f_midi2 *midi2, + struct f_midi2_usb_config *config, + struct midi1_cable_mapping *map, + unsigned int type, unsigned int source) +{ + struct usb_midi_out_jack_descriptor_1 *jack = + &config->jack_outs[config->jack_out++]; + int id = ++config->jack_id; + int err; + + jack->bLength = 0x09; + jack->bDescriptorType = USB_DT_CS_INTERFACE; + jack->bDescriptorSubtype = USB_MS_MIDI_OUT_JACK; + jack->bJackType = type; + jack->bJackID = id; + jack->bNrInputPins = 1; + jack->pins[0].baSourceID = source; + jack->pins[0].baSourcePin = 0x01; + /* use the corresponding block name as jack name */ + if (map->ep) + jack->iJack = map->ep->blks[map->block].string_id; + + err = append_config(config, jack); + if (err < 0) + return err; + return id; +} + +static int f_midi2_create_usb_configs(struct f_midi2 *midi2, + struct f_midi2_usb_config *config, + int speed) +{ + void **midi1_in_eps, **midi1_out_eps; + int i, jack, total; + int err; + + switch (speed) { + default: + case USB_SPEED_HIGH: + midi2_midi1_ep_out_desc.wMaxPacketSize = cpu_to_le16(512); + midi2_midi1_ep_in_desc.wMaxPacketSize = cpu_to_le16(512); + for (i = 0; i < midi2->num_eps; i++) { + midi2_midi2_ep_out_desc[i].wMaxPacketSize = + cpu_to_le16(512); + midi2_midi2_ep_in_desc[i].wMaxPacketSize = + cpu_to_le16(512); + } + fallthrough; + case USB_SPEED_FULL: + midi1_in_eps = midi2_midi1_ep_in_descs; + midi1_out_eps = midi2_midi1_ep_out_descs; + break; + case USB_SPEED_SUPER: + midi2_midi1_ep_out_desc.wMaxPacketSize = cpu_to_le16(1024); + midi2_midi1_ep_in_desc.wMaxPacketSize = cpu_to_le16(1024); + for (i = 0; i < midi2->num_eps; i++) { + midi2_midi2_ep_out_desc[i].wMaxPacketSize = + cpu_to_le16(1024); + midi2_midi2_ep_in_desc[i].wMaxPacketSize = + cpu_to_le16(1024); + } + midi1_in_eps = midi2_midi1_ep_in_ss_descs; + midi1_out_eps = midi2_midi1_ep_out_ss_descs; + break; + } + + err = append_configs(config, midi2_audio_descs); + if (err < 0) + return err; + + if (midi2->num_midi1_in && midi2->num_midi1_out) + midi2_midi1_if_desc.bNumEndpoints = 2; + else + midi2_midi1_if_desc.bNumEndpoints = 1; + + err = append_configs(config, midi2_midi1_descs); + if (err < 0) + return err; + + total = USB_DT_MS_HEADER_SIZE; + if (midi2->num_midi1_out) { + midi2_midi1_ep_out_class_desc.bLength = + USB_DT_MS_ENDPOINT_SIZE(midi2->num_midi1_out); + total += midi2_midi1_ep_out_class_desc.bLength; + midi2_midi1_ep_out_class_desc.bNumEmbMIDIJack = + midi2->num_midi1_out; + total += midi2->num_midi1_out * + (USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1)); + for (i = 0; i < midi2->num_midi1_out; i++) { + jack = append_midi1_in_jack(midi2, config, + &midi2->in_cable_mapping[i], + USB_MS_EMBEDDED); + if (jack < 0) + return jack; + midi2_midi1_ep_out_class_desc.baAssocJackID[i] = jack; + jack = append_midi1_out_jack(midi2, config, + &midi2->in_cable_mapping[i], + USB_MS_EXTERNAL, jack); + if (jack < 0) + return jack; + } + } + + if (midi2->num_midi1_in) { + midi2_midi1_ep_in_class_desc.bLength = + USB_DT_MS_ENDPOINT_SIZE(midi2->num_midi1_in); + total += midi2_midi1_ep_in_class_desc.bLength; + midi2_midi1_ep_in_class_desc.bNumEmbMIDIJack = + midi2->num_midi1_in; + total += midi2->num_midi1_in * + (USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1)); + for (i = 0; i < midi2->num_midi1_in; i++) { + jack = append_midi1_in_jack(midi2, config, + &midi2->out_cable_mapping[i], + USB_MS_EXTERNAL); + if (jack < 0) + return jack; + jack = append_midi1_out_jack(midi2, config, + &midi2->out_cable_mapping[i], + USB_MS_EMBEDDED, jack); + if (jack < 0) + return jack; + midi2_midi1_ep_in_class_desc.baAssocJackID[i] = jack; + } + } + + midi2_midi1_class_desc.wTotalLength = cpu_to_le16(total); + + if (midi2->num_midi1_out) { + err = append_configs(config, midi1_out_eps); + if (err < 0) + return err; + } + if (midi2->num_midi1_in) { + err = append_configs(config, midi1_in_eps); + if (err < 0) + return err; + } + + err = append_configs(config, midi2_midi2_descs); + if (err < 0) + return err; + + for (i = 0; i < midi2->num_eps; i++) { + err = append_config(config, &midi2_midi2_ep_out_desc[i]); + if (err < 0) + return err; + if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS) { + err = append_config(config, &midi2_midi2_ep_out_ss_comp_desc); + if (err < 0) + return err; + } + err = append_config(config, &midi2_midi2_ep_out_class_desc[i]); + if (err < 0) + return err; + err = append_config(config, &midi2_midi2_ep_in_desc[i]); + if (err < 0) + return err; + if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS) { + err = append_config(config, &midi2_midi2_ep_in_ss_comp_desc); + if (err < 0) + return err; + } + err = append_config(config, &midi2_midi2_ep_in_class_desc[i]); + if (err < 0) + return err; + } + + return 0; +} + +static void f_midi2_free_usb_configs(struct f_midi2_usb_config *config) +{ + kfree(config->list); + memset(config, 0, sizeof(*config)); +} + +/* as we use the static descriptors for simplicity, serialize bind call */ +static DEFINE_MUTEX(f_midi2_desc_mutex); + +/* fill MIDI2 EP class-specific descriptor */ +static void fill_midi2_class_desc(struct f_midi2_ep *ep, + struct usb_ms20_endpoint_descriptor_32 *cdesc) +{ + int blk; + + cdesc->bLength = USB_DT_MS20_ENDPOINT_SIZE(ep->num_blks); + cdesc->bDescriptorType = USB_DT_CS_ENDPOINT; + cdesc->bDescriptorSubtype = USB_MS_GENERAL_2_0; + cdesc->bNumGrpTrmBlock = ep->num_blks; + for (blk = 0; blk < ep->num_blks; blk++) + cdesc->baAssoGrpTrmBlkID[blk] = ep->blks[blk].gtb_id; +} + +/* initialize MIDI2 EP-in */ +static int f_midi2_init_midi2_ep_in(struct f_midi2 *midi2, int index) +{ + struct f_midi2_ep *ep = &midi2->midi2_eps[index]; + struct usb_endpoint_descriptor *desc = &midi2_midi2_ep_in_desc[index]; + + desc->bLength = USB_DT_ENDPOINT_SIZE; + desc->bDescriptorType = USB_DT_ENDPOINT; + desc->bEndpointAddress = USB_DIR_IN; + desc->bmAttributes = USB_ENDPOINT_XFER_INT; + desc->wMaxPacketSize = cpu_to_le16(EP_MAX_PACKET_INT); + desc->bInterval = 1; + + fill_midi2_class_desc(ep, &midi2_midi2_ep_in_class_desc[index]); + + return f_midi2_init_ep(midi2, ep, &ep->ep_in, desc, + f_midi2_ep_in_complete); +} + +/* initialize MIDI2 EP-out */ +static int f_midi2_init_midi2_ep_out(struct f_midi2 *midi2, int index) +{ + struct f_midi2_ep *ep = &midi2->midi2_eps[index]; + struct usb_endpoint_descriptor *desc = &midi2_midi2_ep_out_desc[index]; + + desc->bLength = USB_DT_ENDPOINT_SIZE; + desc->bDescriptorType = USB_DT_ENDPOINT; + desc->bEndpointAddress = USB_DIR_OUT; + desc->bmAttributes = USB_ENDPOINT_XFER_BULK; + + fill_midi2_class_desc(ep, &midi2_midi2_ep_out_class_desc[index]); + + return f_midi2_init_ep(midi2, ep, &ep->ep_out, desc, + f_midi2_ep_out_complete); +} + +/* gadget function bind callback */ +static int f_midi2_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_midi2 *midi2 = func_to_midi2(f); + struct f_midi2_ep *ep; + struct f_midi2_usb_config config = {}; + struct usb_gadget_strings string_fn = { + .language = 0x0409, /* en-us */ + .strings = midi2->string_defs, + }; + struct usb_gadget_strings *strings[] = { + &string_fn, + NULL, + }; + int i, blk, status; + + midi2->gadget = cdev->gadget; + midi2->operation_mode = MIDI_OP_MODE_UNSET; + + status = f_midi2_create_card(midi2); + if (status < 0) + goto fail_register; + + /* maybe allocate device-global string ID */ + midi2->strings = usb_gstrings_attach(c->cdev, strings, + midi2->total_blocks + 1); + if (IS_ERR(midi2->strings)) { + status = PTR_ERR(midi2->strings); + goto fail_string; + } + + mutex_lock(&f_midi2_desc_mutex); + midi2_midi1_if_desc.iInterface = midi2->strings[STR_IFACE].id; + midi2_midi2_if_desc.iInterface = midi2->strings[STR_IFACE].id; + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; + for (blk = 0; blk < ep->num_blks; blk++) + ep->blks[blk].string_id = + midi2->strings[gtb_to_str_id(ep->blks[blk].gtb_id)].id; + } + + midi2_midi2_if_desc.bNumEndpoints = midi2->num_eps * 2; + + /* audio interface */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + midi2_audio_if_desc.bInterfaceNumber = status; + + /* MIDI streaming */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + midi2->midi_if = status; + midi2_midi1_if_desc.bInterfaceNumber = status; + midi2_midi2_if_desc.bInterfaceNumber = status; + midi2_audio_class_desc.baInterfaceNr[0] = status; + + /* allocate instance-specific endpoints */ + if (midi2->midi2_eps[0].blks[0].info.direction != SNDRV_UMP_DIR_OUTPUT) { + status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_in, + &midi2_midi1_ep_in_desc, + f_midi2_midi1_ep_in_complete); + if (status) + goto fail; + } + + if (midi2->midi2_eps[0].blks[0].info.direction != SNDRV_UMP_DIR_INPUT) { + status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_out, + &midi2_midi1_ep_out_desc, + f_midi2_midi1_ep_out_complete); + if (status) + goto fail; + } + + for (i = 0; i < midi2->num_eps; i++) { + status = f_midi2_init_midi2_ep_in(midi2, i); + if (status) + goto fail; + status = f_midi2_init_midi2_ep_out(midi2, i); + if (status) + goto fail; + } + + status = f_midi2_create_usb_configs(midi2, &config, USB_SPEED_FULL); + if (status < 0) + goto fail; + f->fs_descriptors = usb_copy_descriptors(config.list); + if (!f->fs_descriptors) { + status = -ENOMEM; + goto fail; + } + f_midi2_free_usb_configs(&config); + + status = f_midi2_create_usb_configs(midi2, &config, USB_SPEED_HIGH); + if (status < 0) + goto fail; + f->hs_descriptors = usb_copy_descriptors(config.list); + if (!f->hs_descriptors) { + status = -ENOMEM; + goto fail; + } + f_midi2_free_usb_configs(&config); + + status = f_midi2_create_usb_configs(midi2, &config, USB_SPEED_SUPER); + if (status < 0) + goto fail; + f->ss_descriptors = usb_copy_descriptors(config.list); + if (!f->ss_descriptors) { + status = -ENOMEM; + goto fail; + } + f_midi2_free_usb_configs(&config); + + mutex_unlock(&f_midi2_desc_mutex); + return 0; + +fail: + f_midi2_free_usb_configs(&config); + mutex_unlock(&f_midi2_desc_mutex); + usb_free_all_descriptors(f); +fail_string: + f_midi2_free_card(midi2); +fail_register: + ERROR(midi2, "%s: can't bind, err %d\n", f->name, status); + return status; +} + +/* gadget function unbind callback */ +static void f_midi2_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_midi2 *midi2 = func_to_midi2(f); + int i; + + f_midi2_free_card(midi2); + + f_midi2_free_ep(&midi2->midi1_ep_in); + f_midi2_free_ep(&midi2->midi1_ep_out); + for (i = 0; i < midi2->num_eps; i++) { + f_midi2_free_ep(&midi2->midi2_eps[i].ep_in); + f_midi2_free_ep(&midi2->midi2_eps[i].ep_out); + } + + usb_free_all_descriptors(f); +} + +/* + * ConfigFS interface + */ + +/* type conversion helpers */ +static inline struct f_midi2_opts *to_f_midi2_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_midi2_opts, + func_inst.group); +} + +static inline struct f_midi2_ep_opts * +to_f_midi2_ep_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_midi2_ep_opts, + group); +} + +static inline struct f_midi2_block_opts * +to_f_midi2_block_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_midi2_block_opts, + group); +} + +/* trim the string to be usable for EP and FB name strings */ +static void make_name_string(char *s) +{ + char *p; + + p = strchr(s, '\n'); + if (p) + *p = 0; + + p = s + strlen(s); + for (; p > s && isspace(*p); p--) + *p = 0; +} + +/* configfs helpers: generic show/store for unisnged int */ +static ssize_t f_midi2_opts_uint_show(struct f_midi2_opts *opts, + u32 val, const char *format, char *page) +{ + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, format, val); + mutex_unlock(&opts->lock); + return result; +} + +static ssize_t f_midi2_opts_uint_store(struct f_midi2_opts *opts, + u32 *valp, u32 minval, u32 maxval, + const char *page, size_t len) +{ + int ret; + u32 val; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou32(page, 0, &val); + if (ret) + goto end; + if (val < minval || val > maxval) { + ret = -EINVAL; + goto end; + } + + *valp = val; + ret = len; + +end: + mutex_unlock(&opts->lock); + return ret; +} + +/* generic store for bool */ +static ssize_t f_midi2_opts_bool_store(struct f_midi2_opts *opts, + bool *valp, const char *page, size_t len) +{ + int ret; + bool val; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtobool(page, &val); + if (ret) + goto end; + *valp = val; + ret = len; + +end: + mutex_unlock(&opts->lock); + return ret; +} + +/* generic show/store for string */ +static ssize_t f_midi2_opts_str_show(struct f_midi2_opts *opts, + const char *str, char *page) +{ + int result = 0; + + mutex_lock(&opts->lock); + if (str) + result = scnprintf(page, PAGE_SIZE, "%s\n", str); + mutex_unlock(&opts->lock); + return result; +} + +static ssize_t f_midi2_opts_str_store(struct f_midi2_opts *opts, + const char **strp, size_t maxlen, + const char *page, size_t len) +{ + char *c; + int ret; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + c = kstrndup(page, min(len, maxlen), GFP_KERNEL); + if (!c) { + ret = -ENOMEM; + goto end; + } + + kfree(*strp); + make_name_string(c); + *strp = c; + ret = len; + +end: + mutex_unlock(&opts->lock); + return ret; +} + +/* + * Definitions for UMP Block config + */ + +/* define an uint option for block */ +#define F_MIDI2_BLOCK_OPT(name, format, minval, maxval) \ +static ssize_t f_midi2_block_opts_##name##_show(struct config_item *item,\ + char *page) \ +{ \ + struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); \ + return f_midi2_opts_uint_show(opts->ep->opts, opts->info.name, \ + format "\n", page); \ +} \ + \ +static ssize_t f_midi2_block_opts_##name##_store(struct config_item *item,\ + const char *page, size_t len) \ +{ \ + struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); \ + return f_midi2_opts_uint_store(opts->ep->opts, &opts->info.name,\ + minval, maxval, page, len); \ +} \ + \ +CONFIGFS_ATTR(f_midi2_block_opts_, name) + +/* define a boolean option for block */ +#define F_MIDI2_BLOCK_BOOL_OPT(name) \ +static ssize_t f_midi2_block_opts_##name##_show(struct config_item *item,\ + char *page) \ +{ \ + struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); \ + return f_midi2_opts_uint_show(opts->ep->opts, opts->info.name, \ + "%u\n", page); \ +} \ + \ +static ssize_t f_midi2_block_opts_##name##_store(struct config_item *item,\ + const char *page, size_t len) \ +{ \ + struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); \ + return f_midi2_opts_bool_store(opts->ep->opts, &opts->info.name,\ + page, len); \ +} \ + \ +CONFIGFS_ATTR(f_midi2_block_opts_, name) + +F_MIDI2_BLOCK_OPT(direction, "0x%x", 1, 3); +F_MIDI2_BLOCK_OPT(first_group, "0x%x", 0, 15); +F_MIDI2_BLOCK_OPT(num_groups, "0x%x", 1, 16); +F_MIDI2_BLOCK_OPT(midi1_first_group, "0x%x", 0, 15); +F_MIDI2_BLOCK_OPT(midi1_num_groups, "0x%x", 0, 16); +F_MIDI2_BLOCK_OPT(ui_hint, "0x%x", 0, 3); +F_MIDI2_BLOCK_OPT(midi_ci_version, "%u", 0, 1); +F_MIDI2_BLOCK_OPT(sysex8_streams, "%u", 0, 255); +F_MIDI2_BLOCK_OPT(is_midi1, "%u", 0, 2); +F_MIDI2_BLOCK_BOOL_OPT(active); + +static ssize_t f_midi2_block_opts_name_show(struct config_item *item, + char *page) +{ + struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); + + return f_midi2_opts_str_show(opts->ep->opts, opts->info.name, page); +} + +static ssize_t f_midi2_block_opts_name_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); + + return f_midi2_opts_str_store(opts->ep->opts, &opts->info.name, 128, + page, len); +} + +CONFIGFS_ATTR(f_midi2_block_opts_, name); + +static struct configfs_attribute *f_midi2_block_attrs[] = { + &f_midi2_block_opts_attr_direction, + &f_midi2_block_opts_attr_first_group, + &f_midi2_block_opts_attr_num_groups, + &f_midi2_block_opts_attr_midi1_first_group, + &f_midi2_block_opts_attr_midi1_num_groups, + &f_midi2_block_opts_attr_ui_hint, + &f_midi2_block_opts_attr_midi_ci_version, + &f_midi2_block_opts_attr_sysex8_streams, + &f_midi2_block_opts_attr_is_midi1, + &f_midi2_block_opts_attr_active, + &f_midi2_block_opts_attr_name, + NULL, +}; + +static void f_midi2_block_opts_release(struct config_item *item) +{ + struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); + + kfree(opts->info.name); + kfree(opts); +} + +static const struct configfs_item_operations f_midi2_block_item_ops = { + .release = f_midi2_block_opts_release, +}; + +static const struct config_item_type f_midi2_block_type = { + .ct_item_ops = &f_midi2_block_item_ops, + .ct_attrs = f_midi2_block_attrs, + .ct_owner = THIS_MODULE, +}; + +/* create a f_midi2_block_opts instance for the given block number */ +static int f_midi2_block_opts_create(struct f_midi2_ep_opts *ep_opts, + unsigned int blk, + struct f_midi2_block_opts **block_p) +{ + struct f_midi2_block_opts *block_opts; + int ret = 0; + + mutex_lock(&ep_opts->opts->lock); + if (ep_opts->opts->refcnt || ep_opts->blks[blk]) { + ret = -EBUSY; + goto out; + } + + block_opts = kzalloc_obj(*block_opts); + if (!block_opts) { + ret = -ENOMEM; + goto out; + } + + block_opts->ep = ep_opts; + block_opts->id = blk; + + /* set up the default values */ + block_opts->info.direction = SNDRV_UMP_DIR_BIDIRECTION; + block_opts->info.first_group = 0; + block_opts->info.num_groups = 1; + block_opts->info.ui_hint = SNDRV_UMP_BLOCK_UI_HINT_BOTH; + block_opts->info.active = 1; + + ep_opts->blks[blk] = block_opts; + *block_p = block_opts; + + out: + mutex_unlock(&ep_opts->opts->lock); + return ret; +} + +/* make_group callback for a block */ +static struct config_group * +f_midi2_opts_block_make(struct config_group *group, const char *name) +{ + struct f_midi2_ep_opts *ep_opts; + struct f_midi2_block_opts *block_opts; + unsigned int blk; + int ret; + + if (strncmp(name, "block.", 6)) + return ERR_PTR(-EINVAL); + ret = kstrtouint(name + 6, 10, &blk); + if (ret) + return ERR_PTR(ret); + + ep_opts = to_f_midi2_ep_opts(&group->cg_item); + + if (blk >= SNDRV_UMP_MAX_BLOCKS) + return ERR_PTR(-EINVAL); + if (ep_opts->blks[blk]) + return ERR_PTR(-EBUSY); + ret = f_midi2_block_opts_create(ep_opts, blk, &block_opts); + if (ret) + return ERR_PTR(ret); + + config_group_init_type_name(&block_opts->group, name, + &f_midi2_block_type); + return &block_opts->group; +} + +/* drop_item callback for a block */ +static void +f_midi2_opts_block_drop(struct config_group *group, struct config_item *item) +{ + struct f_midi2_block_opts *block_opts = to_f_midi2_block_opts(item); + + mutex_lock(&block_opts->ep->opts->lock); + block_opts->ep->blks[block_opts->id] = NULL; + mutex_unlock(&block_opts->ep->opts->lock); + config_item_put(item); +} + +/* + * Definitions for UMP Endpoint config + */ + +/* define an uint option for EP */ +#define F_MIDI2_EP_OPT(name, format, minval, maxval) \ +static ssize_t f_midi2_ep_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); \ + return f_midi2_opts_uint_show(opts->opts, opts->info.name, \ + format "\n", page); \ +} \ + \ +static ssize_t f_midi2_ep_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len)\ +{ \ + struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); \ + return f_midi2_opts_uint_store(opts->opts, &opts->info.name, \ + minval, maxval, page, len); \ +} \ + \ +CONFIGFS_ATTR(f_midi2_ep_opts_, name) + +/* define a string option for EP */ +#define F_MIDI2_EP_STR_OPT(name, maxlen) \ +static ssize_t f_midi2_ep_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); \ + return f_midi2_opts_str_show(opts->opts, opts->info.name, page);\ +} \ + \ +static ssize_t f_midi2_ep_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); \ + return f_midi2_opts_str_store(opts->opts, &opts->info.name, maxlen,\ + page, len); \ +} \ + \ +CONFIGFS_ATTR(f_midi2_ep_opts_, name) + +F_MIDI2_EP_OPT(protocol, "0x%x", 1, 2); +F_MIDI2_EP_OPT(protocol_caps, "0x%x", 1, 3); +F_MIDI2_EP_OPT(manufacturer, "0x%x", 0, 0xffffff); +F_MIDI2_EP_OPT(family, "0x%x", 0, 0xffff); +F_MIDI2_EP_OPT(model, "0x%x", 0, 0xffff); +F_MIDI2_EP_OPT(sw_revision, "0x%x", 0, 0xffffffff); +F_MIDI2_EP_STR_OPT(ep_name, 128); +F_MIDI2_EP_STR_OPT(product_id, 128); + +static struct configfs_attribute *f_midi2_ep_attrs[] = { + &f_midi2_ep_opts_attr_protocol, + &f_midi2_ep_opts_attr_protocol_caps, + &f_midi2_ep_opts_attr_ep_name, + &f_midi2_ep_opts_attr_product_id, + &f_midi2_ep_opts_attr_manufacturer, + &f_midi2_ep_opts_attr_family, + &f_midi2_ep_opts_attr_model, + &f_midi2_ep_opts_attr_sw_revision, + NULL, +}; + +static void f_midi2_ep_opts_release(struct config_item *item) +{ + struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); + + configfs_remove_default_groups(&opts->group); + kfree(opts->info.ep_name); + kfree(opts->info.product_id); + kfree(opts); +} + +static const struct configfs_item_operations f_midi2_ep_item_ops = { + .release = f_midi2_ep_opts_release, +}; + +static const struct configfs_group_operations f_midi2_ep_group_ops = { + .make_group = f_midi2_opts_block_make, + .drop_item = f_midi2_opts_block_drop, +}; + +static const struct config_item_type f_midi2_ep_type = { + .ct_item_ops = &f_midi2_ep_item_ops, + .ct_group_ops = &f_midi2_ep_group_ops, + .ct_attrs = f_midi2_ep_attrs, + .ct_owner = THIS_MODULE, +}; + +/* create a f_midi2_ep_opts instance */ +static int f_midi2_ep_opts_create(struct f_midi2_opts *opts, + unsigned int index, + struct f_midi2_ep_opts **ep_p) +{ + struct f_midi2_ep_opts *ep_opts; + + ep_opts = kzalloc_obj(*ep_opts); + if (!ep_opts) + return -ENOMEM; + + ep_opts->opts = opts; + ep_opts->index = index; + + /* set up the default values */ + ep_opts->info.protocol = 2; + ep_opts->info.protocol_caps = 3; + + opts->eps[index] = ep_opts; + *ep_p = ep_opts; + return 0; +} + +/* make_group callback for an EP */ +static struct config_group * +f_midi2_opts_ep_make(struct config_group *group, const char *name) +{ + struct f_midi2_opts *opts; + struct f_midi2_ep_opts *ep_opts; + unsigned int index; + int ret; + + if (strncmp(name, "ep.", 3)) + return ERR_PTR(-EINVAL); + ret = kstrtouint(name + 3, 10, &index); + if (ret) + return ERR_PTR(ret); + + opts = to_f_midi2_opts(&group->cg_item); + if (index >= MAX_UMP_EPS) + return ERR_PTR(-EINVAL); + if (opts->eps[index]) + return ERR_PTR(-EBUSY); + ret = f_midi2_ep_opts_create(opts, index, &ep_opts); + if (ret) + return ERR_PTR(ret); + + config_group_init_type_name(&ep_opts->group, name, &f_midi2_ep_type); + return &ep_opts->group; +} + +/* drop_item callback for an EP */ +static void +f_midi2_opts_ep_drop(struct config_group *group, struct config_item *item) +{ + struct f_midi2_ep_opts *ep_opts = to_f_midi2_ep_opts(item); + + mutex_lock(&ep_opts->opts->lock); + ep_opts->opts->eps[ep_opts->index] = NULL; + mutex_unlock(&ep_opts->opts->lock); + config_item_put(item); +} + +/* + * Definitions for card config + */ + +/* define a bool option for card */ +#define F_MIDI2_BOOL_OPT(name) \ +static ssize_t f_midi2_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_midi2_opts *opts = to_f_midi2_opts(item); \ + return f_midi2_opts_uint_show(opts, opts->info.name, \ + "%u\n", page); \ +} \ + \ +static ssize_t f_midi2_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_midi2_opts *opts = to_f_midi2_opts(item); \ + return f_midi2_opts_bool_store(opts, &opts->info.name, \ + page, len); \ +} \ + \ +CONFIGFS_ATTR(f_midi2_opts_, name) + +F_MIDI2_BOOL_OPT(process_ump); +F_MIDI2_BOOL_OPT(static_block); + +static ssize_t f_midi2_opts_iface_name_show(struct config_item *item, + char *page) +{ + struct f_midi2_opts *opts = to_f_midi2_opts(item); + + return f_midi2_opts_str_show(opts, opts->info.iface_name, page); +} + +static ssize_t f_midi2_opts_iface_name_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_midi2_opts *opts = to_f_midi2_opts(item); + + return f_midi2_opts_str_store(opts, &opts->info.iface_name, 128, + page, len); +} + +CONFIGFS_ATTR(f_midi2_opts_, iface_name); + +static struct configfs_attribute *f_midi2_attrs[] = { + &f_midi2_opts_attr_process_ump, + &f_midi2_opts_attr_static_block, + &f_midi2_opts_attr_iface_name, + NULL +}; + +static void f_midi2_opts_release(struct config_item *item) +{ + struct f_midi2_opts *opts = to_f_midi2_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static const struct configfs_item_operations f_midi2_item_ops = { + .release = f_midi2_opts_release, +}; + +static const struct configfs_group_operations f_midi2_group_ops = { + .make_group = f_midi2_opts_ep_make, + .drop_item = f_midi2_opts_ep_drop, +}; + +static const struct config_item_type f_midi2_func_type = { + .ct_item_ops = &f_midi2_item_ops, + .ct_group_ops = &f_midi2_group_ops, + .ct_attrs = f_midi2_attrs, + .ct_owner = THIS_MODULE, +}; + +static void f_midi2_free_inst(struct usb_function_instance *f) +{ + struct f_midi2_opts *opts; + + opts = container_of(f, struct f_midi2_opts, func_inst); + + configfs_remove_default_groups(&opts->func_inst.group); + kfree(opts->info.iface_name); + kfree(opts); +} + +/* gadget alloc_inst */ +static struct usb_function_instance *f_midi2_alloc_inst(void) +{ + struct f_midi2_opts *opts; + struct f_midi2_ep_opts *ep_opts; + struct f_midi2_block_opts *block_opts; + int ret; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = f_midi2_free_inst; + opts->info.process_ump = true; + opts->info.static_block = true; + opts->info.num_reqs = 32; + opts->info.req_buf_size = 512; + + /* create the default ep */ + ret = f_midi2_ep_opts_create(opts, 0, &ep_opts); + if (ret) { + kfree(opts); + return ERR_PTR(ret); + } + + /* create the default block */ + ret = f_midi2_block_opts_create(ep_opts, 0, &block_opts); + if (ret) { + kfree(ep_opts); + kfree(opts); + return ERR_PTR(ret); + } + + /* set up the default MIDI1 (that is mandatory) */ + block_opts->info.midi1_num_groups = 1; + + config_group_init_type_name(&opts->func_inst.group, "", + &f_midi2_func_type); + + config_group_init_type_name(&ep_opts->group, "ep.0", + &f_midi2_ep_type); + configfs_add_default_group(&ep_opts->group, &opts->func_inst.group); + + config_group_init_type_name(&block_opts->group, "block.0", + &f_midi2_block_type); + configfs_add_default_group(&block_opts->group, &ep_opts->group); + + return &opts->func_inst; +} + +static void do_f_midi2_free(struct f_midi2 *midi2, struct f_midi2_opts *opts) +{ + mutex_lock(&opts->lock); + --opts->refcnt; + mutex_unlock(&opts->lock); + kfree(midi2->string_defs); + kfree(midi2); +} + +static void f_midi2_free(struct usb_function *f) +{ + do_f_midi2_free(func_to_midi2(f), + container_of(f->fi, struct f_midi2_opts, func_inst)); +} + +/* verify the parameters set up via configfs; + * return the number of EPs or a negative error + */ +static int verify_parameters(struct f_midi2_opts *opts) +{ + int i, j, num_eps, num_blks; + struct f_midi2_ep_info *ep; + struct f_midi2_block_info *bp; + + for (num_eps = 0; num_eps < MAX_UMP_EPS && opts->eps[num_eps]; + num_eps++) + ; + if (!num_eps) { + pr_err("f_midi2: No EP is defined\n"); + return -EINVAL; + } + + num_blks = 0; + for (i = 0; i < num_eps; i++) { + ep = &opts->eps[i]->info; + if (!(ep->protocol_caps & ep->protocol)) { + pr_err("f_midi2: Invalid protocol 0x%x (caps 0x%x) for EP %d\n", + ep->protocol, ep->protocol_caps, i); + return -EINVAL; + } + + for (j = 0; j < SNDRV_UMP_MAX_BLOCKS && opts->eps[i]->blks[j]; + j++, num_blks++) { + bp = &opts->eps[i]->blks[j]->info; + if (bp->first_group + bp->num_groups > SNDRV_UMP_MAX_GROUPS) { + pr_err("f_midi2: Invalid group definitions for block %d:%d\n", + i, j); + return -EINVAL; + } + + if (bp->midi1_num_groups) { + if (bp->midi1_first_group < bp->first_group || + bp->midi1_first_group + bp->midi1_num_groups > + bp->first_group + bp->num_groups) { + pr_err("f_midi2: Invalid MIDI1 group definitions for block %d:%d\n", + i, j); + return -EINVAL; + } + } + } + } + if (!num_blks) { + pr_err("f_midi2: No block is defined\n"); + return -EINVAL; + } + + return num_eps; +} + +/* fill mapping between MIDI 1.0 cable and UMP EP/group */ +static void fill_midi1_cable_mapping(struct f_midi2 *midi2, + struct f_midi2_ep *ep, + int blk) +{ + const struct f_midi2_block_info *binfo = &ep->blks[blk].info; + struct midi1_cable_mapping *map; + int i, group; + + if (!binfo->midi1_num_groups) + return; + if (binfo->direction != SNDRV_UMP_DIR_OUTPUT) { + group = binfo->midi1_first_group; + map = midi2->in_cable_mapping + midi2->num_midi1_in; + for (i = 0; i < binfo->midi1_num_groups; i++, group++, map++) { + if (midi2->num_midi1_in >= MAX_CABLES) + break; + map->ep = ep; + map->block = blk; + map->group = group; + midi2->num_midi1_in++; + /* store 1-based cable number */ + ep->in_group_to_cable[group] = midi2->num_midi1_in; + } + } + + if (binfo->direction != SNDRV_UMP_DIR_INPUT) { + group = binfo->midi1_first_group; + map = midi2->out_cable_mapping + midi2->num_midi1_out; + for (i = 0; i < binfo->midi1_num_groups; i++, group++, map++) { + if (midi2->num_midi1_out >= MAX_CABLES) + break; + map->ep = ep; + map->block = blk; + map->group = group; + midi2->num_midi1_out++; + } + } +} + +/* gadget alloc callback */ +static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi) +{ + struct f_midi2 *midi2; + struct f_midi2_opts *opts; + struct f_midi2_ep *ep; + struct f_midi2_block *bp; + int i, num_eps, blk; + + midi2 = kzalloc_obj(*midi2); + if (!midi2) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_midi2_opts, func_inst); + mutex_lock(&opts->lock); + num_eps = verify_parameters(opts); + if (num_eps < 0) { + mutex_unlock(&opts->lock); + kfree(midi2); + return ERR_PTR(num_eps); + } + ++opts->refcnt; + mutex_unlock(&opts->lock); + + spin_lock_init(&midi2->queue_lock); + + midi2->func.name = "midi2_func"; + midi2->func.bind = f_midi2_bind; + midi2->func.unbind = f_midi2_unbind; + midi2->func.get_alt = f_midi2_get_alt; + midi2->func.set_alt = f_midi2_set_alt; + midi2->func.setup = f_midi2_setup; + midi2->func.disable = f_midi2_disable; + midi2->func.free_func = f_midi2_free; + + midi2->info = opts->info; + midi2->num_eps = num_eps; + + for (i = 0; i < num_eps; i++) { + ep = &midi2->midi2_eps[i]; + ep->info = opts->eps[i]->info; + ep->card = midi2; + for (blk = 0; blk < SNDRV_UMP_MAX_BLOCKS && + opts->eps[i]->blks[blk]; blk++) { + bp = &ep->blks[blk]; + ep->num_blks++; + bp->info = opts->eps[i]->blks[blk]->info; + bp->gtb_id = ++midi2->total_blocks; + } + } + + midi2->string_defs = kzalloc_objs(*midi2->string_defs, + midi2->total_blocks + 1); + if (!midi2->string_defs) { + do_f_midi2_free(midi2, opts); + return ERR_PTR(-ENOMEM); + } + + if (opts->info.iface_name && *opts->info.iface_name) + midi2->string_defs[STR_IFACE].s = opts->info.iface_name; + else + midi2->string_defs[STR_IFACE].s = ump_ep_name(&midi2->midi2_eps[0]); + + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; + for (blk = 0; blk < ep->num_blks; blk++) { + bp = &ep->blks[blk]; + midi2->string_defs[gtb_to_str_id(bp->gtb_id)].s = + ump_fb_name(&bp->info); + + fill_midi1_cable_mapping(midi2, ep, blk); + } + } + + if (!midi2->num_midi1_in && !midi2->num_midi1_out) { + pr_err("f_midi2: MIDI1 definition is missing\n"); + do_f_midi2_free(midi2, opts); + return ERR_PTR(-EINVAL); + } + + return &midi2->func; +} + +DECLARE_USB_FUNCTION_INIT(midi2, f_midi2_alloc_inst, f_midi2_alloc); + +MODULE_DESCRIPTION("USB MIDI 2.0 class function driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c new file mode 100644 index 000000000..bf02545b3 --- /dev/null +++ b/drivers/usb/gadget/function/f_ncm.c @@ -0,0 +1,1812 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "u_ether.h" +#include "u_ether_configfs.h" +#include "u_ncm.h" +#include "configfs.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 0x01000000 + +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; + atomic_t notify_count; + 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; + + struct net_device *netdev; + + /* For multi-frame NDP TX */ + struct sk_buff *skb_tx_data; + struct sk_buff *skb_tx_ndp; + u16 ndp_dgram_count; + struct hrtimer task_timer; +}; + +static inline struct f_ncm *func_to_ncm(struct usb_function *f) +{ + return container_of(f, struct f_ncm, port.func); +} + +/*-------------------------------------------------------------------------*/ + +/* + * 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 16384 +#define NTB_OUT_SIZE 16384 + +/* Allocation for storing the NDP, 32 should suffice for a + * 16k packet. This allows a maximum of 32 * 507 Byte packets to + * be transmitted in a single 16kB skb, though when sending full size + * packets this limit will be plenty. + * Smaller packets are not likely to be trying to maximize the + * throughput and will be mstly sending smaller infrequent frames. + */ +#define TX_MAX_NUM_DPE 32 + +/* Delay for the transmit to wait before sending an unfilled NTB frame. */ +#define TX_TIMEOUT_NSECS 300000 + +/* + * Although max mtu as dictated by u_ether is 15412 bytes, setting + * max_segment_size to 15426 would not be efficient. If user chooses segment + * size to be (>= 8192), then we can't aggregate more than one buffer in each + * NTB (assuming each packet coming from network layer is >= 8192 bytes) as ep + * maxpacket limit is 16384. So let max_segment_size be limited to 8000 to allow + * at least 2 packets to be aggregated reducing wastage of NTB buffer space + */ +#define MAX_DATAGRAM_SIZE 8000 + +#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 = { + .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 = { + .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 = { + .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 = { + .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 = { + .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 */ + .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 = { + .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 = { + .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 = { + .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 = { + .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 = { + .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 = { + .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[] = { + (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 = { + .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 = { + .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 = { + .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[] = { + (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, +}; + + +/* super speed support: */ + +static struct usb_endpoint_descriptor ss_ncm_notify_desc = { + .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_ss_ep_comp_descriptor ss_ncm_notify_comp_desc = { + .bLength = sizeof(ss_ncm_notify_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(NCM_STATUS_BYTECOUNT), +}; + +static struct usb_endpoint_descriptor ss_ncm_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_ncm_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_ncm_bulk_comp_desc = { + .bLength = sizeof(ss_ncm_bulk_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + .bMaxBurst = 15, + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *ncm_ss_function[] = { + (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 *) &ss_ncm_notify_desc, + (struct usb_descriptor_header *) &ss_ncm_notify_comp_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 *) &ss_ncm_in_desc, + (struct usb_descriptor_header *) &ss_ncm_bulk_comp_desc, + (struct usb_descriptor_header *) &ss_ncm_out_desc, + (struct usb_descriptor_header *) &ss_ncm_bulk_comp_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 dpe_size; + unsigned ndplen_align; + /* sizes in u16 units */ + unsigned dgram_item_len; /* index or length */ + unsigned block_length; + unsigned ndp_index; + unsigned reserved1; + unsigned reserved2; + unsigned next_ndp_index; +}; + +static const struct ndp_parser_opts 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), + .dpe_size = sizeof(struct usb_cdc_ncm_dpe16), + .ndplen_align = 4, + .dgram_item_len = 1, + .block_length = 1, + .ndp_index = 1, + .reserved1 = 0, + .reserved2 = 0, + .next_ndp_index = 1, +}; + +static const struct ndp_parser_opts 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), + .dpe_size = sizeof(struct usb_cdc_ncm_dpe32), + .ndplen_align = 8, + .dgram_item_len = 2, + .block_length = 2, + .ndp_index = 2, + .reserved1 = 1, + .reserved2 = 2, + .next_ndp_index = 2, +}; + +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->ndp_sign = ncm->parser_opts->ndp_sign; + 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 (atomic_read(&ncm->notify_count)) + 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", + str_true_false(ncm->is_open)); + 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(gether_bitrate(cdev->gadget)); + data[1] = data[0]; + + DBG(cdev, "notify speed %u\n", gether_bitrate(cdev->gadget)); + ncm->notify_state = NCM_NOTIFY_CONNECT; + break; + } + event->bmRequestType = 0xA1; + event->wIndex = cpu_to_le16(ncm->ctrl_id); + + atomic_inc(&ncm->notify_count); + + /* + * 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) { + atomic_dec(&ncm->notify_count); + 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); + atomic_dec(&ncm->notify_count); + break; + case -ECONNRESET: + case -ESHUTDOWN: + atomic_set(&ncm->notify_count, 0); + ncm->notify_state = NCM_NOTIFY_NONE; + break; + default: + DBG(cdev, "event %02x --> %d\n", + event->bNotificationType, req->status); + atomic_dec(&ncm->notify_count); + break; + } + 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 = f->config->cdev; + + 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: + { + if (w_length != 0 || w_index != ncm->ctrl_id) + goto invalid; + switch (w_value) { + case 0x0000: + ncm->is_crc = false; + DBG(cdev, "non-CRC mode selected\n"); + break; + case 0x0001: + ncm->is_crc = true; + DBG(cdev, "CRC mode selected\n"); + break; + default: + goto invalid; + } + 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); + } + ncm->ndp_sign = ncm->parser_opts->ndp_sign | + (ncm->is_crc ? NCM_NDP_HDR_CRC : 0); + + /* 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; + + 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); + + /* Data interface has two altsettings, 0 and 1 */ + } else if (intf == ncm->data_id) { + if (alt > 1) + goto fail; + + if (ncm->netdev) { + DBG(cdev, "reset ncm\n"); + ncm->netdev = NULL; + 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_zlp_supported(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); + ncm->netdev = 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->enabled ? 1 : 0; +} + +static struct sk_buff *package_for_tx(struct f_ncm *ncm) +{ + __le16 *ntb_iter; + struct sk_buff *skb2 = NULL; + unsigned ndp_pad; + unsigned ndp_index; + unsigned new_len; + + const struct ndp_parser_opts *opts = ncm->parser_opts; + const int ndp_align = le16_to_cpu(ntb_parameters.wNdpInAlignment); + const int dgram_idx_len = 2 * 2 * opts->dgram_item_len; + + /* Stop the timer */ + hrtimer_try_to_cancel(&ncm->task_timer); + + ndp_pad = ALIGN(ncm->skb_tx_data->len, ndp_align) - + ncm->skb_tx_data->len; + ndp_index = ncm->skb_tx_data->len + ndp_pad; + new_len = ndp_index + dgram_idx_len + ncm->skb_tx_ndp->len; + + /* Set the final BlockLength and wNdpIndex */ + ntb_iter = (void *) ncm->skb_tx_data->data; + /* Increment pointer to BlockLength */ + ntb_iter += 2 + 1 + 1; + put_ncm(&ntb_iter, opts->block_length, new_len); + put_ncm(&ntb_iter, opts->ndp_index, ndp_index); + + /* Set the final NDP wLength */ + new_len = opts->ndp_size + + (ncm->ndp_dgram_count * dgram_idx_len); + ncm->ndp_dgram_count = 0; + /* Increment from start to wLength */ + ntb_iter = (void *) ncm->skb_tx_ndp->data; + ntb_iter += 2; + put_unaligned_le16(new_len, ntb_iter); + + /* Merge the skbs */ + swap(skb2, ncm->skb_tx_data); + if (ncm->skb_tx_data) { + dev_consume_skb_any(ncm->skb_tx_data); + ncm->skb_tx_data = NULL; + } + + /* Insert NDP alignment. */ + skb_put_zero(skb2, ndp_pad); + + /* Copy NTB across. */ + skb_put_data(skb2, ncm->skb_tx_ndp->data, ncm->skb_tx_ndp->len); + dev_consume_skb_any(ncm->skb_tx_ndp); + ncm->skb_tx_ndp = NULL; + + /* Insert zero'd datagram. */ + skb_put_zero(skb2, dgram_idx_len); + + return skb2; +} + +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 = NULL; + + if (skb) { + int ncb_len = 0; + __le16 *ntb_data; + __le16 *ntb_ndp; + int dgram_pad; + + unsigned max_size = ncm->port.fixed_in_len; + const struct ndp_parser_opts *opts = ncm->parser_opts; + const int ndp_align = le16_to_cpu(ntb_parameters.wNdpInAlignment); + const int div = le16_to_cpu(ntb_parameters.wNdpInDivisor); + const int rem = le16_to_cpu(ntb_parameters.wNdpInPayloadRemainder); + const int dgram_idx_len = 2 * 2 * opts->dgram_item_len; + + /* Add the CRC if required up front */ + if (ncm->is_crc) { + uint32_t crc; + __le16 *crc_pos; + + crc = ~crc32_le(~0, + skb->data, + skb->len); + crc_pos = skb_put(skb, sizeof(uint32_t)); + put_unaligned_le32(crc, crc_pos); + } + + /* If the new skb is too big for the current NCM NTB then + * set the current stored skb to be sent now and clear it + * ready for new data. + * NOTE: Assume maximum align for speed of calculation. + */ + if (ncm->skb_tx_data + && (ncm->ndp_dgram_count >= TX_MAX_NUM_DPE + || (ncm->skb_tx_data->len + + div + rem + skb->len + + ncm->skb_tx_ndp->len + ndp_align + (2 * dgram_idx_len)) + > max_size)) { + skb2 = package_for_tx(ncm); + if (!skb2) + goto err; + } + + if (!ncm->skb_tx_data) { + ncb_len = opts->nth_size; + dgram_pad = ALIGN(ncb_len, div) + rem - ncb_len; + ncb_len += dgram_pad; + + /* Create a new skb for the NTH and datagrams. */ + ncm->skb_tx_data = alloc_skb(max_size, GFP_ATOMIC); + if (!ncm->skb_tx_data) + goto err; + + ncm->skb_tx_data->dev = ncm->netdev; + ntb_data = skb_put_zero(ncm->skb_tx_data, ncb_len); + /* dwSignature */ + put_unaligned_le32(opts->nth_sign, ntb_data); + ntb_data += 2; + /* wHeaderLength */ + put_unaligned_le16(opts->nth_size, ntb_data++); + + /* Allocate an skb for storing the NDP, + * TX_MAX_NUM_DPE should easily suffice for a + * 16k packet. + */ + ncm->skb_tx_ndp = alloc_skb((int)(opts->ndp_size + + opts->dpe_size + * TX_MAX_NUM_DPE), + GFP_ATOMIC); + if (!ncm->skb_tx_ndp) + goto err; + + ncm->skb_tx_ndp->dev = ncm->netdev; + ntb_ndp = skb_put(ncm->skb_tx_ndp, opts->ndp_size); + memset(ntb_ndp, 0, ncb_len); + /* dwSignature */ + put_unaligned_le32(ncm->ndp_sign, ntb_ndp); + ntb_ndp += 2; + + /* There is always a zeroed entry */ + ncm->ndp_dgram_count = 1; + + /* Note: we skip opts->next_ndp_index */ + + /* Start the timer. */ + hrtimer_start(&ncm->task_timer, TX_TIMEOUT_NSECS, + HRTIMER_MODE_REL_SOFT); + } + + /* Add the datagram position entries */ + ntb_ndp = skb_put_zero(ncm->skb_tx_ndp, dgram_idx_len); + + ncb_len = ncm->skb_tx_data->len; + dgram_pad = ALIGN(ncb_len, div) + rem - ncb_len; + ncb_len += dgram_pad; + + /* (d)wDatagramIndex */ + put_ncm(&ntb_ndp, opts->dgram_item_len, ncb_len); + /* (d)wDatagramLength */ + put_ncm(&ntb_ndp, opts->dgram_item_len, skb->len); + ncm->ndp_dgram_count++; + + /* Add the new data to the skb */ + skb_put_zero(ncm->skb_tx_data, dgram_pad); + skb_put_data(ncm->skb_tx_data, skb->data, skb->len); + dev_consume_skb_any(skb); + skb = NULL; + + } else if (ncm->skb_tx_data) { + /* If we get here ncm_wrap_ntb() was called with NULL skb, + * because eth_start_xmit() was called with NULL skb by + * ncm_tx_timeout() - hence, this is our signal to flush/send. + */ + skb2 = package_for_tx(ncm); + if (!skb2) + goto err; + } + + return skb2; + +err: + ncm->netdev->stats.tx_dropped++; + + if (skb) + dev_kfree_skb_any(skb); + if (ncm->skb_tx_data) + dev_kfree_skb_any(ncm->skb_tx_data); + if (ncm->skb_tx_ndp) + dev_kfree_skb_any(ncm->skb_tx_ndp); + + return NULL; +} + +/* + * The transmit should only be run if no skb data has been sent + * for a certain duration. + */ +static enum hrtimer_restart ncm_tx_timeout(struct hrtimer *data) +{ + struct f_ncm *ncm = container_of(data, struct f_ncm, task_timer); + struct net_device *netdev = READ_ONCE(ncm->netdev); + + if (netdev) { + /* XXX This allowance of a NULL skb argument to ndo_start_xmit + * XXX is not sane. The gadget layer should be redesigned so + * XXX that the dev->wrap() invocations to build SKBs is transparent + * XXX and performed in some way outside of the ndo_start_xmit + * XXX interface. + * + * This will call directly into u_ether's eth_start_xmit() + */ + netdev->netdev_ops->ndo_start_xmit(NULL, netdev); + } + return HRTIMER_NORESTART; +} + +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); + unsigned char *ntb_ptr = skb->data; + __le16 *tmp; + unsigned index, index2; + unsigned int ndp_index; + unsigned dg_len, dg_len2; + unsigned ndp_len; + unsigned block_len; + struct sk_buff *skb2; + int ret = -EINVAL; + unsigned ntb_max = le32_to_cpu(ntb_parameters.dwNtbOutMaxSize); + unsigned frame_max; + const struct ndp_parser_opts *opts = ncm->parser_opts; + unsigned crc_len = ncm->is_crc ? sizeof(uint32_t) : 0; + int dgram_counter; + int to_process = skb->len; + struct f_ncm_opts *ncm_opts; + + ncm_opts = container_of(port->func.fi, struct f_ncm_opts, func_inst); + frame_max = ncm_opts->max_segment_size; + +parse_ntb: + if (to_process < (int)opts->nth_size) { + INFO(port->func.config->cdev, "Packet too small for headers\n"); + goto err; + } + tmp = (__le16 *)ntb_ptr; + + /* 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 */ + + block_len = get_ncm(&tmp, opts->block_length); + if (block_len == 0) + block_len = to_process; + + /* (d)wBlockLength */ + if ((block_len < opts->nth_size + opts->ndp_size) || (block_len > ntb_max) || + (block_len > to_process)) { + INFO(port->func.config->cdev, "Bad block length: %#X\n", block_len); + goto err; + } + + ndp_index = get_ncm(&tmp, opts->ndp_index); + + /* Run through all the NDP's in the NTB */ + do { + /* + * NCM 3.2 + * dwNdpIndex + */ + if (((ndp_index % 4) != 0) || + (ndp_index < opts->nth_size) || + (ndp_index > (block_len - + opts->ndp_size))) { + INFO(port->func.config->cdev, "Bad index: %#X\n", + ndp_index); + goto err; + } + + /* + * walk through NDP + * dwSignature + */ + tmp = (__le16 *)(ntb_ptr + ndp_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 + * wLength + * 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 + * Each entry is a dgram index and a dgram length. + */ + 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; + /* Check for another NDP (d)wNextNdpIndex */ + ndp_index = get_ncm(&tmp, opts->next_ndp_index); + 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; + /* wDatagramIndex[0] */ + if ((index < opts->nth_size) || + (index > block_len)) { + INFO(port->func.config->cdev, + "Bad index: %#X\n", index); + goto err; + } + + dg_len = dg_len2; + /* + * wDatagramLength[0] + * ethernet hdr + crc or larger than max frame size + */ + if ((dg_len < 14 + crc_len) || + (dg_len > frame_max) || + (dg_len > block_len - index)) { + 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(ntb_ptr + + index + dg_len - + crc_len); + crc2 = ~crc32_le(~0, + ntb_ptr + 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); + + /* wDatagramIndex[1] */ + if (index2 > block_len) { + INFO(port->func.config->cdev, + "Bad index: %#X\n", index2); + goto err; + } + + /* + * Copy the data into a new skb. + * This ensures the truesize is correct + */ + skb2 = netdev_alloc_skb_ip_align(ncm->netdev, + dg_len - crc_len); + if (skb2 == NULL) + goto err; + skb_put_data(skb2, ntb_ptr + index, + 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)); + } while (ndp_index); + + VDBG(port->func.config->cdev, + "Parsed NTB with %d frames\n", dgram_counter); + + to_process -= block_len; + + /* + * Windows NCM driver avoids USB ZLPs by adding a 1-byte + * zero pad as needed. + */ + if (to_process == 1 && + (*(unsigned char *)(ntb_ptr + block_len) == 0x00)) { + to_process--; + } else if ((to_process > 0) && (block_len != 0)) { + ntb_ptr = (unsigned char *)(ntb_ptr + block_len); + goto parse_ntb; + } + + dev_consume_skb_any(skb); + + 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->netdev) { + ncm->netdev = NULL; + gether_disconnect(&ncm->port); + } + + if (ncm->notify->enabled) { + usb_ep_disable(ncm->notify); + 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 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); + struct usb_string *us; + int status = 0; + struct usb_ep *ep; + struct f_ncm_opts *ncm_opts; + + struct usb_os_desc_table *os_desc_table __free(kfree) = NULL; + struct net_device *net __free(detach_gadget) = NULL; + struct usb_request *request __free(free_usb_request) = NULL; + + if (!can_support_ecm(cdev->gadget)) + return -EINVAL; + + ncm_opts = container_of(f->fi, struct f_ncm_opts, func_inst); + + if (cdev->use_os_string) { + os_desc_table = kzalloc(sizeof(*os_desc_table), GFP_KERNEL); + if (!os_desc_table) + return -ENOMEM; + } + + scoped_guard(mutex, &ncm_opts->lock) + if (ncm_opts->bind_count == 0) { + if (!device_is_registered(&ncm_opts->net->dev)) { + ncm_opts->net->mtu = (ncm_opts->max_segment_size - ETH_HLEN); + gether_set_gadget(ncm_opts->net, cdev->gadget); + status = gether_register_netdev(ncm_opts->net); + } else + status = gether_attach_gadget(ncm_opts->net, cdev->gadget); + + if (status) + return status; + net = ncm_opts->net; + } + + ncm_string_defs[1].s = ncm->ethaddr; + + us = usb_gstrings_attach(cdev, ncm_strings, + ARRAY_SIZE(ncm_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + + ncm_control_intf.iInterface = us[STRING_CTRL_IDX].id; + ncm_data_nop_intf.iInterface = us[STRING_DATA_IDX].id; + ncm_data_intf.iInterface = us[STRING_DATA_IDX].id; + ecm_desc.iMACAddress = us[STRING_MAC_IDX].id; + ncm_iad_desc.iFunction = us[STRING_IAD_IDX].id; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + return status; + 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) + return status; + ncm->data_id = status; + + ncm_data_nop_intf.bInterfaceNumber = status; + ncm_data_intf.bInterfaceNumber = status; + ncm_union_desc.bSlaveInterface0 = status; + + ecm_desc.wMaxSegmentSize = cpu_to_le16(ncm_opts->max_segment_size); + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_in_desc); + if (!ep) + return -ENODEV; + ncm->port.in_ep = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_out_desc); + if (!ep) + return -ENODEV; + ncm->port.out_ep = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_notify_desc); + if (!ep) + return -ENODEV; + ncm->notify = ep; + + /* allocate notification request and buffer */ + request = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!request) + return -ENOMEM; + request->buf = kmalloc(NCM_STATUS_BYTECOUNT, GFP_KERNEL); + if (!request->buf) + return -ENOMEM; + request->context = ncm; + request->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; + + ss_ncm_in_desc.bEndpointAddress = fs_ncm_in_desc.bEndpointAddress; + ss_ncm_out_desc.bEndpointAddress = fs_ncm_out_desc.bEndpointAddress; + ss_ncm_notify_desc.bEndpointAddress = + fs_ncm_notify_desc.bEndpointAddress; + + status = usb_assign_descriptors(f, ncm_fs_function, ncm_hs_function, + ncm_ss_function, ncm_ss_function); + if (status) + return status; + + /* + * 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; + + hrtimer_setup(&ncm->task_timer, ncm_tx_timeout, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + + if (cdev->use_os_string) { + os_desc_table[0].os_desc = &ncm_opts->ncm_os_desc; + os_desc_table[0].if_id = ncm_iad_desc.bFirstInterface; + f->os_desc_table = no_free_ptr(os_desc_table); + f->os_desc_n = 1; + } + ncm->notify_req = no_free_ptr(request); + + ncm_opts->bind_count++; + retain_and_null_ptr(net); + + DBG(cdev, "CDC Network: IN/%s OUT/%s NOTIFY/%s\n", + ncm->port.in_ep->name, ncm->port.out_ep->name, + ncm->notify->name); + return 0; +} + +static inline struct f_ncm_opts *to_f_ncm_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_ncm_opts, + func_inst.group); +} + +/* f_ncm_item_ops */ +USB_ETHERNET_CONFIGFS_ITEM(ncm); + +/* f_ncm_opts_dev_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(ncm); + +/* f_ncm_opts_host_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(ncm); + +/* f_ncm_opts_qmult */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(ncm); + +/* f_ncm_opts_ifname */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(ncm); + +static ssize_t ncm_opts_max_segment_size_show(struct config_item *item, + char *page) +{ + struct f_ncm_opts *opts = to_f_ncm_opts(item); + u16 segment_size; + + mutex_lock(&opts->lock); + segment_size = opts->max_segment_size; + mutex_unlock(&opts->lock); + + return sysfs_emit(page, "%u\n", segment_size); +} + +static ssize_t ncm_opts_max_segment_size_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ncm_opts *opts = to_f_ncm_opts(item); + u16 segment_size; + int ret; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto out; + } + + ret = kstrtou16(page, 0, &segment_size); + if (ret) + goto out; + + if (segment_size > MAX_DATAGRAM_SIZE) { + ret = -EINVAL; + goto out; + } + + opts->max_segment_size = segment_size; + ret = len; +out: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(ncm_opts_, max_segment_size); + +static struct configfs_attribute *ncm_attrs[] = { + &ncm_opts_attr_dev_addr, + &ncm_opts_attr_host_addr, + &ncm_opts_attr_qmult, + &ncm_opts_attr_ifname, + &ncm_opts_attr_max_segment_size, + NULL, +}; + +static const struct config_item_type ncm_func_type = { + .ct_item_ops = &ncm_item_ops, + .ct_attrs = ncm_attrs, + .ct_owner = THIS_MODULE, +}; + +static void ncm_free_inst(struct usb_function_instance *f) +{ + struct f_ncm_opts *opts; + + opts = container_of(f, struct f_ncm_opts, func_inst); + if (device_is_registered(&opts->net->dev)) + gether_cleanup(netdev_priv(opts->net)); + else + free_netdev(opts->net); + kfree(opts->ncm_interf_group); + kfree(opts); +} + +static struct usb_function_instance *ncm_alloc_inst(void) +{ + struct f_ncm_opts *opts; + struct usb_os_desc *descs[1]; + char *names[1]; + struct config_group *ncm_interf_group; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + opts->ncm_os_desc.ext_compat_id = opts->ncm_ext_compat_id; + + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = ncm_free_inst; + opts->net = gether_setup_default(); + if (IS_ERR(opts->net)) { + struct net_device *net = opts->net; + kfree(opts); + return ERR_CAST(net); + } + opts->max_segment_size = ETH_FRAME_LEN; + INIT_LIST_HEAD(&opts->ncm_os_desc.ext_prop); + + descs[0] = &opts->ncm_os_desc; + names[0] = "ncm"; + + config_group_init_type_name(&opts->func_inst.group, "", &ncm_func_type); + ncm_interf_group = + usb_os_desc_prepare_interf_dir(&opts->func_inst.group, 1, descs, + names, THIS_MODULE); + if (IS_ERR(ncm_interf_group)) { + ncm_free_inst(&opts->func_inst); + return ERR_CAST(ncm_interf_group); + } + opts->ncm_interf_group = ncm_interf_group; + + return &opts->func_inst; +} + +static void ncm_free(struct usb_function *f) +{ + struct f_ncm *ncm; + struct f_ncm_opts *opts; + + ncm = func_to_ncm(f); + opts = container_of(f->fi, struct f_ncm_opts, func_inst); + kfree(ncm); + mutex_lock(&opts->lock); + opts->refcnt--; + mutex_unlock(&opts->lock); +} + +static void ncm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_ncm *ncm = func_to_ncm(f); + struct f_ncm_opts *ncm_opts; + + DBG(c->cdev, "ncm unbind\n"); + + ncm_opts = container_of(f->fi, struct f_ncm_opts, func_inst); + + hrtimer_cancel(&ncm->task_timer); + + kfree(f->os_desc_table); + f->os_desc_n = 0; + + ncm_string_defs[0].id = 0; + usb_free_all_descriptors(f); + + if (atomic_read(&ncm->notify_count)) { + usb_ep_dequeue(ncm->notify, ncm->notify_req); + atomic_set(&ncm->notify_count, 0); + } + + kfree(ncm->notify_req->buf); + usb_ep_free_request(ncm->notify, ncm->notify_req); + + ncm_opts->bind_count--; + if (ncm_opts->bind_count == 0) + gether_detach_gadget(ncm_opts->net); +} + +static struct usb_function *ncm_alloc(struct usb_function_instance *fi) +{ + struct f_ncm *ncm; + struct f_ncm_opts *opts; + int status; + + /* allocate and initialize one new instance */ + ncm = kzalloc(sizeof(*ncm), GFP_KERNEL); + if (!ncm) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_ncm_opts, func_inst); + mutex_lock(&opts->lock); + opts->refcnt++; + + /* export host's Ethernet address in CDC format */ + status = gether_get_host_addr_cdc(opts->net, ncm->ethaddr, + sizeof(ncm->ethaddr)); + if (status < 12) { /* strlen("01234567890a") */ + kfree(ncm); + mutex_unlock(&opts->lock); + return ERR_PTR(-EINVAL); + } + + spin_lock_init(&ncm->lock); + ncm_reset_values(ncm); + ncm->port.ioport = netdev_priv(opts->net); + mutex_unlock(&opts->lock); + ncm->port.is_fixed = true; + ncm->port.supports_multi_frame = true; + + ncm->port.func.name = "cdc_network"; + /* 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.func.free_func = ncm_free; + + ncm->port.wrap = ncm_wrap_ntb; + ncm->port.unwrap = ncm_unwrap_ntb; + + return &ncm->port.func; +} + +DECLARE_USB_FUNCTION_INIT(ncm, ncm_alloc_inst, ncm_alloc); +MODULE_DESCRIPTION("USB CDC Network (NCM) link function driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yauheni Kaliuta"); diff --git a/drivers/usb/gadget/function/f_obex.c b/drivers/usb/gadget/function/f_obex.c new file mode 100644 index 000000000..715bb0dec --- /dev/null +++ b/drivers/usb/gadget/function/f_obex.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include + +#include "u_serial.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 cur_alt; + u8 port_num; +}; + +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 */ + dev_dbg(&cdev->gadget->dev, + "reset obex ttyGS%d control\n", obex->port_num); + + } else if (intf == obex->data_id) { + if (alt > 1) + goto fail; + + if (obex->port.in->enabled) { + dev_dbg(&cdev->gadget->dev, + "reset obex ttyGS%d\n", obex->port_num); + gserial_disconnect(&obex->port); + } + + if (!obex->port.in->desc || !obex->port.out->desc) { + dev_dbg(&cdev->gadget->dev, + "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) { + dev_dbg(&cdev->gadget->dev, + "activate obex ttyGS%d\n", obex->port_num); + gserial_connect(&obex->port, obex->port_num); + } + + } else + goto fail; + + obex->cur_alt = alt; + + return 0; + +fail: + return -EINVAL; +} + +static int obex_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_obex *obex = func_to_obex(f); + + return obex->cur_alt; +} + +static void obex_disable(struct usb_function *f) +{ + struct f_obex *obex = func_to_obex(f); + struct usb_composite_dev *cdev = f->config->cdev; + + dev_dbg(&cdev->gadget->dev, "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; + + status = usb_function_activate(&g->func); + if (status) + dev_dbg(&cdev->gadget->dev, + "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; + + status = usb_function_deactivate(&g->func); + if (status) + dev_dbg(&cdev->gadget->dev, + "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_is_altset_supported(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); + struct usb_string *us; + int status; + struct usb_ep *ep; + + if (!can_support_obex(c)) + return -EINVAL; + + us = usb_gstrings_attach(cdev, obex_strings, + ARRAY_SIZE(obex_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + obex_control_intf.iInterface = us[OBEX_CTRL_IDX].id; + obex_data_nop_intf.iInterface = us[OBEX_DATA_IDX].id; + obex_data_intf.iInterface = us[OBEX_DATA_IDX].id; + + /* 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 = usb_ep_autoconfig(cdev->gadget, &obex_fs_ep_out_desc); + if (!ep) + goto fail; + obex->port.out = ep; + + /* 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, + NULL); + if (status) + goto fail; + + dev_dbg(&cdev->gadget->dev, "obex ttyGS%d: IN/%s OUT/%s\n", + obex->port_num, + obex->port.in->name, obex->port.out->name); + + return 0; + +fail: + ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, 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); +} + +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 const struct configfs_item_operations obex_item_ops = { + .release = obex_attr_release, +}; + +static ssize_t f_obex_port_num_show(struct config_item *item, char *page) +{ + return sprintf(page, "%u\n", to_f_serial_opts(item)->port_num); +} + +CONFIGFS_ATTR_RO(f_obex_, port_num); + +static struct configfs_attribute *acm_attrs[] = { + &f_obex_attr_port_num, + NULL, +}; + +static const 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_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + + opts->func_inst.free_func_inst = obex_free_inst; + ret = gserial_alloc_line_no_console(&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) +{ + usb_free_all_descriptors(f); +} + +static 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_obj(*obex); + 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"; + /* 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; + obex->port.func.bind_deactivated = true; + + return &obex->port.func; +} + +DECLARE_USB_FUNCTION_INIT(obex, obex_alloc_inst, obex_alloc); +MODULE_AUTHOR("Felipe Balbi"); +MODULE_DESCRIPTION("USB CDC OBEX function driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_phonet.c b/drivers/usb/gadget/function/f_phonet.c new file mode 100644 index 000000000..b1ee9a7c2 --- /dev/null +++ b/drivers/usb/gadget/function/f_phonet.c @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * f_phonet.c -- USB CDC Phonet function + * + * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved. + * + * Author: Rémi Denis-Courmont + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "u_phonet.h" +#include "u_ether.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[]; +}; + +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++; + fallthrough; + default: + dev->stats.tx_errors++; + } + + dev_kfree_skb_any(skb); + netif_wake_queue(dev); +} + +static netdev_tx_t 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 const struct net_device_ops pn_netdev_ops = { + .ndo_open = pn_net_open, + .ndo_stop = pn_net_close, + .ndo_start_xmit = pn_net_xmit, +}; + +static void pn_net_setup(struct net_device *dev) +{ + const u8 addr = PN_MEDIA_USB; + + dev->features = 0; + dev->type = ARPHRD_PHONET; + dev->flags = IFF_POINTOPOINT | IFF_NOARP; + dev->mtu = PHONET_DEV_MTU; + dev->min_mtu = PHONET_MIN_MTU; + dev->max_mtu = PHONET_MAX_MTU; + dev->hard_header_len = 1; + dev->addr_len = 1; + dev_addr_set(dev, &addr); + + dev->tx_queue_len = 1; + + dev->netdev_ops = &pn_netdev_ops; + dev->needs_free_netdev = true; + 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 = __dev_alloc_page(gfp_flags | __GFP_NOMEMALLOC); + 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 (unlikely(skb_shinfo(skb)->nr_frags >= MAX_SKB_FRAGS)) { + /* Frame count from host exceeds frags[] capacity */ + dev_kfree_skb_any(skb); + if (fp->rx.skb == skb) + fp->rx.skb = NULL; + dev->stats.rx_length_errors++; + 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 */ + skb_put_data(skb, 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++; + fallthrough; + default: + dev->stats.rx_errors++; + break; + } + + if (page) + put_page(page); + if (req) + pn_rx_submit(fp, req, GFP_ATOMIC); +} + +/*-------------------------------------------------------------------------*/ + +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); + + if (fp->in_ep->enabled) + __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); + } + 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 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; + + struct f_phonet_opts *phonet_opts; + + phonet_opts = container_of(f->fi, struct f_phonet_opts, func_inst); + + /* + * in drivers/usb/gadget/configfs.c:configfs_composite_bind() + * configurations are bound in sequence with list_for_each_entry, + * in each configuration its functions are bound in sequence + * with list_for_each_entry, so we assume no race condition + * with regard to phonet_opts->bound access + */ + if (!phonet_opts->bound) { + gphonet_set_gadget(phonet_opts->net, gadget); + status = gphonet_register_netdev(phonet_opts->net); + if (status) + return status; + phonet_opts->bound = true; + } + + /* 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 = usb_ep_autoconfig(gadget, &pn_fs_source_desc); + if (!ep) + goto err; + fp->in_ep = ep; + + 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, 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]); + usb_free_all_descriptors(f); +err: + ERROR(cdev, "USB CDC Phonet: cannot autoconfigure\n"); + return status; +} + +static inline struct f_phonet_opts *to_f_phonet_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_phonet_opts, + func_inst.group); +} + +static void phonet_attr_release(struct config_item *item) +{ + struct f_phonet_opts *opts = to_f_phonet_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static const struct configfs_item_operations phonet_item_ops = { + .release = phonet_attr_release, +}; + +static ssize_t f_phonet_ifname_show(struct config_item *item, char *page) +{ + return gether_get_ifname(to_f_phonet_opts(item)->net, page, PAGE_SIZE); +} + +CONFIGFS_ATTR_RO(f_phonet_, ifname); + +static struct configfs_attribute *phonet_attrs[] = { + &f_phonet_attr_ifname, + NULL, +}; + +static const struct config_item_type phonet_func_type = { + .ct_item_ops = &phonet_item_ops, + .ct_attrs = phonet_attrs, + .ct_owner = THIS_MODULE, +}; + +static void phonet_free_inst(struct usb_function_instance *f) +{ + struct f_phonet_opts *opts; + + opts = container_of(f, struct f_phonet_opts, func_inst); + if (opts->bound) + gphonet_cleanup(opts->net); + else + free_netdev(opts->net); + kfree(opts); +} + +static struct usb_function_instance *phonet_alloc_inst(void) +{ + struct f_phonet_opts *opts; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + + opts->func_inst.free_func_inst = phonet_free_inst; + opts->net = gphonet_setup_default(); + if (IS_ERR(opts->net)) { + struct net_device *net = opts->net; + kfree(opts); + return ERR_CAST(net); + } + + config_group_init_type_name(&opts->func_inst.group, "", + &phonet_func_type); + + return &opts->func_inst; +} + +static void phonet_free(struct usb_function *f) +{ + struct f_phonet *phonet; + + phonet = func_to_pn(f); + kfree(phonet); +} + +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); +} + +static struct usb_function *phonet_alloc(struct usb_function_instance *fi) +{ + struct f_phonet *fp; + struct f_phonet_opts *opts; + + fp = kzalloc_flex(*fp, out_reqv, phonet_rxq_size); + if (!fp) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_phonet_opts, func_inst); + + fp->dev = opts->net; + 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; + fp->function.free_func = phonet_free; + spin_lock_init(&fp->rx.lock); + + return &fp->function; +} + +struct net_device *gphonet_setup_default(void) +{ + struct net_device *dev; + struct phonet_port *port; + + /* Create net device */ + dev = alloc_netdev(sizeof(*port), "upnlink%d", NET_NAME_UNKNOWN, + pn_net_setup); + if (!dev) + return ERR_PTR(-ENOMEM); + + port = netdev_priv(dev); + spin_lock_init(&port->lock); + netif_carrier_off(dev); + + return dev; +} + +void gphonet_set_gadget(struct net_device *net, struct usb_gadget *g) +{ + SET_NETDEV_DEV(net, &g->dev); +} + +int gphonet_register_netdev(struct net_device *net) +{ + int status; + + status = register_netdev(net); + if (status) + free_netdev(net); + + return status; +} + +void gphonet_cleanup(struct net_device *dev) +{ + unregister_netdev(dev); +} + +DECLARE_USB_FUNCTION_INIT(phonet, phonet_alloc_inst, phonet_alloc); +MODULE_AUTHOR("Rémi Denis-Courmont"); +MODULE_DESCRIPTION("USB CDC Phonet function"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_printer.c b/drivers/usb/gadget/function/f_printer.c new file mode 100644 index 000000000..1857d7861 --- /dev/null +++ b/drivers/usb/gadget/function/f_printer.c @@ -0,0 +1,1575 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_printer.c - USB printer function driver + * + * Copied from drivers/usb/gadget/legacy/printer.c, + * which was: + * + * printer.c -- Printer gadget driver + * + * Copyright (C) 2003-2005 David Brownell + * Copyright (C) 2006 Craig W. Nadler + */ + +#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 "u_printer.h" + +#define PRINTER_MINORS 4 +#define GET_DEVICE_ID 0 +#define GET_PORT_STATUS 1 +#define SOFT_RESET 2 + +#define DEFAULT_Q_LEN 10 /* same as legacy g_printer gadget */ + +static int major, minors; +static const struct class usb_gadget_class = { + .name = "usb_printer_gadget", +}; + +static DEFINE_IDA(printer_ida); +static DEFINE_MUTEX(printer_ida_lock); /* protects access do printer_ida */ + +/*-------------------------------------------------------------------------*/ + +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 kref kref; + 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; + int minor; + struct cdev printer_cdev; + u8 printer_cdev_open; + wait_queue_head_t wait; + unsigned q_len; + char **pnp_string; /* We don't own memory! */ + struct usb_function function; +}; + +static inline struct printer_dev *func_to_printer(struct usb_function *f) +{ + return container_of(f, struct printer_dev, function); +} + +/*-------------------------------------------------------------------------*/ + +/* + * 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_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_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 +}; + +/* + * Added endpoint descriptors for 3.0 devices + */ + +static struct usb_endpoint_descriptor ss_ep_in_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_ep_in_comp_desc = { + .bLength = sizeof(ss_ep_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_endpoint_descriptor ss_ep_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_ep_out_comp_desc = { + .bLength = sizeof(ss_ep_out_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_descriptor_header *ss_printer_function[] = { + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &ss_ep_in_desc, + (struct usb_descriptor_header *) &ss_ep_in_comp_desc, + (struct usb_descriptor_header *) &ss_ep_out_desc, + (struct usb_descriptor_header *) &ss_ep_out_comp_desc, + NULL +}; + +/* maxpacket and other transfer characteristics vary by speed. */ +static inline struct usb_endpoint_descriptor *ep_desc(struct usb_gadget *gadget, + struct usb_endpoint_descriptor *fs, + struct usb_endpoint_descriptor *hs, + struct usb_endpoint_descriptor *ss) +{ + switch (gadget->speed) { + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER: + return ss; + case USB_SPEED_HIGH: + return hs; + default: + return fs; + } +} + +/*-------------------------------------------------------------------------*/ + +static void printer_dev_free(struct kref *kref) +{ + struct printer_dev *dev = container_of(kref, struct printer_dev, kref); + + kfree(dev); +} + +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; + + dev = container_of(inode->i_cdev, struct printer_dev, printer_cdev); + + spin_lock_irqsave(&dev->lock, flags); + + if (dev->interface < 0) { + spin_unlock_irqrestore(&dev->lock, flags); + return -ENODEV; + } + + 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; + kref_get(&dev->kref); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + 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); + + kref_put(&dev->kref, printer_dev_free); + + 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; + + /* here, we unlock, and only unlock, to avoid deadlock. */ + spin_unlock(&dev->lock); + error = usb_ep_queue(dev->out_ep, req, GFP_ATOMIC); + spin_lock(&dev->lock); + if (error) { + DBG(dev, "rx submit --> %d\n", error); + list_add(&req->list, &dev->rx_reqs); + break; + } + /* if the req is empty, then add it into dev->rx_reqs_active. */ + else if (list_empty(&req->list)) + 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, not_copied, copied; + 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); + + if (dev->interface < 0) + goto out_disabled; + + /* 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); + /* this dropped the lock - need to retest */ + if (dev->interface < 0) + goto out_disabled; + + 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); + if (dev->interface < 0) + goto out_disabled; + } + + /* 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; + + not_copied = copy_to_user(buf, current_rx_buf, size); + copied = size - not_copied; + + bytes_copied += copied; + len -= copied; + buf += copied; + + 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 (dev->interface < 0) + goto out_disabled; + + if (!copied) { + 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); + return bytes_copied ? bytes_copied : -EFAULT; + } + + size = copied; + + /* 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; + +out_disabled: + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->lock_printer_io); + return -ENODEV; +} + +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; + int value; + + 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); + + if (dev->interface < 0) + goto out_disabled; + + /* 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); + if (dev->interface < 0) + goto out_disabled; + } + + 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 multiple 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 (dev->interface < 0) + goto out_disabled; + + list_add(&req->list, &dev->tx_reqs_active); + + /* here, we unlock, and only unlock, to avoid deadlock. */ + spin_unlock(&dev->lock); + value = usb_ep_queue(dev->in_ep, req, GFP_ATOMIC); + spin_lock(&dev->lock); + if (value) { + list_move(&req->list, &dev->tx_reqs); + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->lock_printer_io); + return -EAGAIN; + } + if (dev->interface < 0) + goto out_disabled; + } + + 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; + +out_disabled: + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->lock_printer_io); + return -ENODEV; +} + +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; + + inode_lock(inode); + spin_lock_irqsave(&dev->lock, flags); + + if (dev->interface < 0) { + spin_unlock_irqrestore(&dev->lock, flags); + inode_unlock(inode); + return -ENODEV; + } + + 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)))); + } + inode_unlock(inode); + + return 0; +} + +static __poll_t +printer_poll(struct file *fd, poll_table *wait) +{ + struct printer_dev *dev = fd->private_data; + unsigned long flags; + __poll_t status = 0; + + mutex_lock(&dev->lock_printer_io); + spin_lock_irqsave(&dev->lock, flags); + + if (dev->interface < 0) { + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->lock_printer_io); + return EPOLLERR | EPOLLHUP; + } + + 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 |= EPOLLOUT | EPOLLWRNORM; + + if (likely(dev->current_rx_bytes) || + likely(!list_empty(&dev->rx_buffers))) + status |= EPOLLIN | EPOLLRDNORM; + + 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); + + if (dev->interface < 0) { + spin_unlock_irqrestore(&dev->lock, flags); + return -ENODEV; + } + + 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, &fs_ep_in_desc, &hs_ep_in_desc, + &ss_ep_in_desc); + dev->in_ep->driver_data = dev; + + dev->out_ep->desc = ep_desc(dev->gadget, &fs_ep_out_desc, + &hs_ep_out_desc, &ss_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->out_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) +{ + unsigned long flags; + + if (dev->interface < 0) + return; + + if (dev->in_ep->desc) + usb_ep_disable(dev->in_ep); + + if (dev->out_ep->desc) + usb_ep_disable(dev->out_ep); + + spin_lock_irqsave(&dev->lock, flags); + dev->in_ep->desc = NULL; + dev->out_ep->desc = NULL; + dev->interface = -1; + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* 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; + + 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); +} + +/*-------------------------------------------------------------------------*/ + +static bool gprinter_req_match(struct usb_function *f, + const struct usb_ctrlrequest *ctrl, + bool config0) +{ + struct printer_dev *dev = func_to_printer(f); + 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 (config0) + return false; + + if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE || + (ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS) + return false; + + switch (ctrl->bRequest) { + case GET_DEVICE_ID: + w_index >>= 8; + if (USB_DIR_IN & ctrl->bRequestType) + break; + return false; + case GET_PORT_STATUS: + if (!w_value && w_length == 1 && + (USB_DIR_IN & ctrl->bRequestType)) + break; + return false; + case SOFT_RESET: + if (!w_value && !w_length && + !(USB_DIR_IN & ctrl->bRequestType)) + break; + fallthrough; + default: + return false; + } + return w_index == dev->interface; +} + +/* + * 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 = func_to_printer(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + u8 *buf = req->buf; + 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 GET_DEVICE_ID: /* Get the IEEE-1284 PNP String */ + /* Only one printer interface is supported. */ + if ((wIndex>>8) != dev->interface) + break; + + if (!*dev->pnp_string) { + value = 0; + break; + } + value = strlen(*dev->pnp_string); + buf[0] = (value >> 8) & 0xFF; + buf[1] = value & 0xFF; + memcpy(buf + 2, *dev->pnp_string, value); + DBG(dev, "1284 PNP String: %x %s\n", value, + *dev->pnp_string); + break; + + case GET_PORT_STATUS: /* Get Port Status */ + /* Only one printer interface is supported. */ + if (wIndex != dev->interface) + break; + + buf[0] = dev->printer_status; + value = min_t(u16, wLength, 1); + break; + + case SOFT_RESET: /* 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 */ + if (value >= 0) { + req->length = value; + req->zero = value < wLength; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) { + ERROR(dev, "%s:%d Error!\n", __func__, __LINE__); + req->status = 0; + } + } + return value; +} + +static int printer_func_bind(struct usb_configuration *c, + struct usb_function *f) +{ + struct usb_gadget *gadget = c->cdev->gadget; + struct printer_dev *dev = func_to_printer(f); + struct device *pdev; + struct usb_composite_dev *cdev = c->cdev; + struct usb_ep *in_ep; + struct usb_ep *out_ep = NULL; + struct usb_request *req; + dev_t devt; + int id; + int ret; + u32 i; + + id = usb_interface_id(c, f); + if (id < 0) + return id; + intf_desc.bInterfaceNumber = id; + + /* finish hookup to lower layer ... */ + dev->gadget = gadget; + + /* 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; + } + + out_ep = usb_ep_autoconfig(cdev->gadget, &fs_ep_out_desc); + if (!out_ep) + goto autoconf_fail; + + /* 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; + ss_ep_in_desc.bEndpointAddress = fs_ep_in_desc.bEndpointAddress; + ss_ep_out_desc.bEndpointAddress = fs_ep_out_desc.bEndpointAddress; + + ret = usb_assign_descriptors(f, fs_printer_function, + hs_printer_function, ss_printer_function, + ss_printer_function); + if (ret) + return ret; + + dev->in_ep = in_ep; + dev->out_ep = out_ep; + + ret = -ENOMEM; + for (i = 0; i < dev->q_len; i++) { + req = printer_req_alloc(dev->in_ep, USB_BUFSIZE, GFP_KERNEL); + if (!req) + goto fail_tx_reqs; + list_add(&req->list, &dev->tx_reqs); + } + + for (i = 0; i < dev->q_len; i++) { + req = printer_req_alloc(dev->out_ep, USB_BUFSIZE, GFP_KERNEL); + if (!req) + goto fail_rx_reqs; + list_add(&req->list, &dev->rx_reqs); + } + + /* Setup the sysfs files for the printer gadget. */ + devt = MKDEV(major, dev->minor); + pdev = device_create(&usb_gadget_class, NULL, devt, + NULL, "g_printer%d", dev->minor); + if (IS_ERR(pdev)) { + ERROR(dev, "Failed to create device: g_printer\n"); + ret = PTR_ERR(pdev); + goto fail_rx_reqs; + } + + /* + * 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; + ret = cdev_add(&dev->printer_cdev, devt, 1); + if (ret) { + ERROR(dev, "Failed to open char device\n"); + goto fail_cdev_add; + } + + return 0; + +fail_cdev_add: + device_destroy(&usb_gadget_class, devt); + +fail_rx_reqs: + 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); + } + +fail_tx_reqs: + 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); + } + + usb_free_all_descriptors(f); + return ret; + +} + +static int printer_func_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct printer_dev *dev = func_to_printer(f); + 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 = func_to_printer(f); + + printer_reset_interface(dev); +} + +static inline struct f_printer_opts +*to_f_printer_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_printer_opts, + func_inst.group); +} + +static void printer_attr_release(struct config_item *item) +{ + struct f_printer_opts *opts = to_f_printer_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static const struct configfs_item_operations printer_item_ops = { + .release = printer_attr_release, +}; + +static ssize_t f_printer_opts_pnp_string_show(struct config_item *item, + char *page) +{ + struct f_printer_opts *opts = to_f_printer_opts(item); + int result = 0; + + mutex_lock(&opts->lock); + if (!opts->pnp_string) + goto unlock; + + result = strscpy(page, opts->pnp_string, PAGE_SIZE); + if (result < 1) { + result = PAGE_SIZE; + } else if (page[result - 1] != '\n' && result + 1 < PAGE_SIZE) { + page[result++] = '\n'; + page[result] = '\0'; + } + +unlock: + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_printer_opts_pnp_string_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_printer_opts *opts = to_f_printer_opts(item); + char *new_pnp; + int result; + + mutex_lock(&opts->lock); + + new_pnp = kstrndup(page, len, GFP_KERNEL); + if (!new_pnp) { + result = -ENOMEM; + goto unlock; + } + + if (opts->pnp_string_allocated) + kfree(opts->pnp_string); + + opts->pnp_string_allocated = true; + opts->pnp_string = new_pnp; + result = len; +unlock: + mutex_unlock(&opts->lock); + + return result; +} + +CONFIGFS_ATTR(f_printer_opts_, pnp_string); + +static ssize_t f_printer_opts_q_len_show(struct config_item *item, + char *page) +{ + struct f_printer_opts *opts = to_f_printer_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d\n", opts->q_len); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_printer_opts_q_len_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_printer_opts *opts = to_f_printer_opts(item); + int ret; + u16 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou16(page, 0, &num); + if (ret) + goto end; + + opts->q_len = (unsigned)num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_printer_opts_, q_len); + +static struct configfs_attribute *printer_attrs[] = { + &f_printer_opts_attr_pnp_string, + &f_printer_opts_attr_q_len, + NULL, +}; + +static const struct config_item_type printer_func_type = { + .ct_item_ops = &printer_item_ops, + .ct_attrs = printer_attrs, + .ct_owner = THIS_MODULE, +}; + +static inline int gprinter_get_minor(void) +{ + int ret; + + ret = ida_alloc(&printer_ida, GFP_KERNEL); + if (ret >= PRINTER_MINORS) { + ida_free(&printer_ida, ret); + ret = -ENODEV; + } + + return ret; +} + +static inline void gprinter_put_minor(int minor) +{ + ida_free(&printer_ida, minor); +} + +static int gprinter_setup(int); +static void gprinter_cleanup(void); + +static void gprinter_free_inst(struct usb_function_instance *f) +{ + struct f_printer_opts *opts; + + opts = container_of(f, struct f_printer_opts, func_inst); + + mutex_lock(&printer_ida_lock); + + gprinter_put_minor(opts->minor); + if (ida_is_empty(&printer_ida)) + gprinter_cleanup(); + + mutex_unlock(&printer_ida_lock); + + if (opts->pnp_string_allocated) + kfree(opts->pnp_string); + kfree(opts); +} + +static struct usb_function_instance *gprinter_alloc_inst(void) +{ + struct f_printer_opts *opts; + struct usb_function_instance *ret; + int status = 0; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = gprinter_free_inst; + ret = &opts->func_inst; + + /* Make sure q_len is initialized, otherwise the bound device can't support read/write! */ + opts->q_len = DEFAULT_Q_LEN; + + mutex_lock(&printer_ida_lock); + + if (ida_is_empty(&printer_ida)) { + status = gprinter_setup(PRINTER_MINORS); + if (status) { + ret = ERR_PTR(status); + kfree(opts); + goto unlock; + } + } + + opts->minor = gprinter_get_minor(); + if (opts->minor < 0) { + ret = ERR_PTR(opts->minor); + kfree(opts); + if (ida_is_empty(&printer_ida)) + gprinter_cleanup(); + goto unlock; + } + config_group_init_type_name(&opts->func_inst.group, "", + &printer_func_type); + +unlock: + mutex_unlock(&printer_ida_lock); + return ret; +} + +static void gprinter_free(struct usb_function *f) +{ + struct printer_dev *dev = func_to_printer(f); + struct f_printer_opts *opts; + + opts = container_of(f->fi, struct f_printer_opts, func_inst); + + kref_put(&dev->kref, printer_dev_free); + mutex_lock(&opts->lock); + --opts->refcnt; + mutex_unlock(&opts->lock); +} + +static void printer_func_unbind(struct usb_configuration *c, + struct usb_function *f) +{ + struct printer_dev *dev; + struct usb_request *req; + + dev = func_to_printer(f); + + device_destroy(&usb_gadget_class, MKDEV(major, dev->minor)); + + /* 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); + } + usb_free_all_descriptors(f); +} + +static struct usb_function *gprinter_alloc(struct usb_function_instance *fi) +{ + struct printer_dev *dev; + struct f_printer_opts *opts; + + opts = container_of(fi, struct f_printer_opts, func_inst); + + mutex_lock(&opts->lock); + if (opts->minor >= minors) { + mutex_unlock(&opts->lock); + return ERR_PTR(-ENOENT); + } + + dev = kzalloc_obj(*dev); + if (!dev) { + mutex_unlock(&opts->lock); + return ERR_PTR(-ENOMEM); + } + + kref_init(&dev->kref); + ++opts->refcnt; + dev->minor = opts->minor; + dev->pnp_string = &opts->pnp_string; + dev->q_len = opts->q_len; + mutex_unlock(&opts->lock); + + dev->function.name = "printer"; + 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; + dev->function.req_match = gprinter_req_match; + dev->function.free_func = gprinter_free; + + INIT_LIST_HEAD(&dev->tx_reqs); + INIT_LIST_HEAD(&dev->rx_reqs); + INIT_LIST_HEAD(&dev->rx_buffers); + INIT_LIST_HEAD(&dev->tx_reqs_active); + INIT_LIST_HEAD(&dev->rx_reqs_active); + + spin_lock_init(&dev->lock); + mutex_init(&dev->lock_printer_io); + 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; + + return &dev->function; +} + +DECLARE_USB_FUNCTION_INIT(printer, gprinter_alloc_inst, gprinter_alloc); +MODULE_DESCRIPTION("USB printer function driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Craig Nadler"); + +static int gprinter_setup(int count) +{ + int status; + dev_t devt; + + status = class_register(&usb_gadget_class); + if (status) + return status; + + status = alloc_chrdev_region(&devt, 0, count, "USB printer gadget"); + if (status) { + pr_err("alloc_chrdev_region %d\n", status); + class_unregister(&usb_gadget_class); + return status; + } + + major = MAJOR(devt); + minors = count; + + return status; +} + +static void gprinter_cleanup(void) +{ + if (major) { + unregister_chrdev_region(MKDEV(major, 0), minors); + major = minors = 0; + } + class_unregister(&usb_gadget_class); +} diff --git a/drivers/usb/gadget/function/f_rndis.c b/drivers/usb/gadget/function/f_rndis.c new file mode 100644 index 000000000..7de1c5f8e --- /dev/null +++ b/drivers/usb/gadget/function/f_rndis.c @@ -0,0 +1,1014 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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) + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "u_ether.h" +#include "u_ether_configfs.h" +#include "u_rndis.h" +#include "rndis.h" +#include "configfs.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; + struct rndis_params *params; + + struct usb_ep *notify; + struct usb_request *notify_req; + atomic_t notify_count; +}; + +static inline struct f_rndis *func_to_rndis(struct usb_function *f) +{ + return container_of(f, struct f_rndis, port.func); +} + +/*-------------------------------------------------------------------------*/ + +/* + */ + +#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; + + if (!skb) + return NULL; + + skb2 = skb_realloc_headroom(skb, sizeof(struct rndis_packet_msg_type)); + rndis_add_hdr(skb2); + + dev_kfree_skb(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 = rndis->port.func.config->cdev; + int status = req->status; + + /* 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; + + /* received RNDIS command from USB_CDC_SEND_ENCAPSULATED_COMMAND */ +// spin_lock(&dev->lock); + status = rndis_msg_parser(rndis->params, (u8 *) req->buf); + if (status < 0) + pr_err("RNDIS command error %d, %d/%d\n", + status, req->actual, req->length); +// spin_unlock(&dev->lock); +} + +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->params, &n); + if (buf) { + memcpy(req->buf, buf, n); + req->complete = rndis_response_complete; + req->context = rndis; + rndis_free_response(rndis->params, 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) { + 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); + + } else if (intf == rndis->data_id) { + struct net_device *net; + + if (rndis->port.in_ep->enabled) { + 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->params, 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->enabled) + return; + + DBG(cdev, "rndis deactivated\n"); + + rndis_uninit(rndis->params); + gether_disconnect(&rndis->port); + + usb_ep_disable(rndis->notify); + rndis->notify->desc = 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->params, RNDIS_MEDIUM_802_3, + gether_bitrate(cdev->gadget) / 100); + rndis_signal_connect(rndis->params); +} + +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->params, RNDIS_MEDIUM_802_3, 0); + rndis_signal_disconnect(rndis->params); +} + +/*-------------------------------------------------------------------------*/ + +/* Some controllers can't support RNDIS ... */ +static inline bool can_support_rndis(struct usb_configuration *c) +{ + /* everything else is *presumably* fine */ + return true; +} + +/* 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); + struct usb_string *us; + int status; + struct usb_ep *ep; + + struct f_rndis_opts *rndis_opts; + struct usb_os_desc_table *os_desc_table __free(kfree) = NULL; + struct net_device *net __free(detach_gadget) = NULL; + struct usb_request *request __free(free_usb_request) = NULL; + + if (!can_support_rndis(c)) + return -EINVAL; + + rndis_opts = container_of(f->fi, struct f_rndis_opts, func_inst); + + if (cdev->use_os_string) { + os_desc_table = kzalloc_obj(*os_desc_table); + if (!os_desc_table) + return -ENOMEM; + } + + scoped_guard(mutex, &rndis_opts->lock) { + rndis_iad_descriptor.bFunctionClass = rndis_opts->class; + rndis_iad_descriptor.bFunctionSubClass = rndis_opts->subclass; + rndis_iad_descriptor.bFunctionProtocol = rndis_opts->protocol; + + if (rndis_opts->bind_count == 0 && !rndis_opts->borrowed_net) { + if (!device_is_registered(&rndis_opts->net->dev)) { + gether_set_gadget(rndis_opts->net, cdev->gadget); + status = gether_register_netdev(rndis_opts->net); + } else + status = gether_attach_gadget(rndis_opts->net, cdev->gadget); + + if (status) + return status; + net = rndis_opts->net; + } + } + + us = usb_gstrings_attach(cdev, rndis_strings, + ARRAY_SIZE(rndis_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + rndis_control_intf.iInterface = us[0].id; + rndis_data_intf.iInterface = us[1].id; + rndis_iad_descriptor.iFunction = us[2].id; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + return status; + 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) + return status; + rndis->data_id = status; + + rndis_data_intf.bInterfaceNumber = status; + rndis_union_desc.bSlaveInterface0 = status; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc); + if (!ep) + return -ENODEV; + rndis->port.in_ep = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc); + if (!ep) + return -ENODEV; + rndis->port.out_ep = ep; + + /* 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) + return -ENODEV; + rndis->notify = ep; + + /* allocate notification request and buffer */ + request = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!request) + return -ENOMEM; + request->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL); + if (!request->buf) + return -ENOMEM; + request->length = STATUS_BYTECOUNT; + request->context = rndis; + request->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, eth_ss_function); + if (status) + return status; + + rndis->port.open = rndis_open; + rndis->port.close = rndis_close; + + rndis_set_param_medium(rndis->params, RNDIS_MEDIUM_802_3, 0); + rndis_set_host_mac(rndis->params, rndis->ethaddr); + + if (rndis->manufacturer && rndis->vendorID && + rndis_set_param_vendor(rndis->params, rndis->vendorID, + rndis->manufacturer)) { + usb_free_all_descriptors(f); + return -EINVAL; + } + + if (cdev->use_os_string) { + os_desc_table[0].os_desc = &rndis_opts->rndis_os_desc; + os_desc_table[0].if_id = rndis_iad_descriptor.bFirstInterface; + f->os_desc_table = no_free_ptr(os_desc_table); + f->os_desc_n = 1; + + } + rndis->notify_req = no_free_ptr(request); + + rndis_opts->bind_count++; + retain_and_null_ptr(net); + + /* 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: IN/%s OUT/%s NOTIFY/%s\n", + rndis->port.in_ep->name, rndis->port.out_ep->name, + rndis->notify->name); + return 0; +} + +void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net) +{ + struct f_rndis_opts *opts; + + opts = container_of(f, struct f_rndis_opts, func_inst); + if (device_is_registered(&opts->net->dev)) + gether_cleanup(netdev_priv(opts->net)); + else + free_netdev(opts->net); + opts->borrowed_net = true; + opts->net = net; +} +EXPORT_SYMBOL_GPL(rndis_borrow_net); + +static inline struct f_rndis_opts *to_f_rndis_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_rndis_opts, + func_inst.group); +} + +/* f_rndis_item_ops */ +USB_ETHERNET_CONFIGFS_ITEM(rndis); + +/* f_rndis_opts_dev_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(rndis); + +/* f_rndis_opts_host_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(rndis); + +/* f_rndis_opts_qmult */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(rndis); + +/* f_rndis_opts_ifname */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(rndis); + +/* f_rndis_opts_class */ +USB_ETHER_CONFIGFS_ITEM_ATTR_U8_RW(rndis, class); + +/* f_rndis_opts_subclass */ +USB_ETHER_CONFIGFS_ITEM_ATTR_U8_RW(rndis, subclass); + +/* f_rndis_opts_protocol */ +USB_ETHER_CONFIGFS_ITEM_ATTR_U8_RW(rndis, protocol); + +static struct configfs_attribute *rndis_attrs[] = { + &rndis_opts_attr_dev_addr, + &rndis_opts_attr_host_addr, + &rndis_opts_attr_qmult, + &rndis_opts_attr_ifname, + &rndis_opts_attr_class, + &rndis_opts_attr_subclass, + &rndis_opts_attr_protocol, + NULL, +}; + +static const struct config_item_type rndis_func_type = { + .ct_item_ops = &rndis_item_ops, + .ct_attrs = rndis_attrs, + .ct_owner = THIS_MODULE, +}; + +static void rndis_free_inst(struct usb_function_instance *f) +{ + struct f_rndis_opts *opts; + + opts = container_of(f, struct f_rndis_opts, func_inst); + if (!opts->borrowed_net) { + if (device_is_registered(&opts->net->dev)) + gether_cleanup(netdev_priv(opts->net)); + else + free_netdev(opts->net); + } + + kfree(opts->rndis_interf_group); /* single VLA chunk */ + kfree(opts); +} + +static struct usb_function_instance *rndis_alloc_inst(void) +{ + struct f_rndis_opts *opts; + struct usb_os_desc *descs[1]; + char *names[1]; + struct config_group *rndis_interf_group; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + opts->rndis_os_desc.ext_compat_id = opts->rndis_ext_compat_id; + + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = rndis_free_inst; + opts->net = gether_setup_default(); + if (IS_ERR(opts->net)) { + struct net_device *net = opts->net; + kfree(opts); + return ERR_CAST(net); + } + INIT_LIST_HEAD(&opts->rndis_os_desc.ext_prop); + + opts->class = rndis_iad_descriptor.bFunctionClass; + opts->subclass = rndis_iad_descriptor.bFunctionSubClass; + opts->protocol = rndis_iad_descriptor.bFunctionProtocol; + + descs[0] = &opts->rndis_os_desc; + names[0] = "rndis"; + config_group_init_type_name(&opts->func_inst.group, "", + &rndis_func_type); + rndis_interf_group = + usb_os_desc_prepare_interf_dir(&opts->func_inst.group, 1, descs, + names, THIS_MODULE); + if (IS_ERR(rndis_interf_group)) { + rndis_free_inst(&opts->func_inst); + return ERR_CAST(rndis_interf_group); + } + opts->rndis_interf_group = rndis_interf_group; + + return &opts->func_inst; +} + +static void rndis_free(struct usb_function *f) +{ + struct f_rndis *rndis; + struct f_rndis_opts *opts; + + rndis = func_to_rndis(f); + rndis_deregister(rndis->params); + opts = container_of(f->fi, struct f_rndis_opts, func_inst); + kfree(rndis); + mutex_lock(&opts->lock); + opts->refcnt--; + mutex_unlock(&opts->lock); +} + +static void rndis_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_rndis *rndis = func_to_rndis(f); + struct f_rndis_opts *rndis_opts; + + rndis_opts = container_of(f->fi, struct f_rndis_opts, func_inst); + + kfree(f->os_desc_table); + f->os_desc_n = 0; + usb_free_all_descriptors(f); + + kfree(rndis->notify_req->buf); + usb_ep_free_request(rndis->notify, rndis->notify_req); + + rndis_opts->bind_count--; + if (rndis_opts->bind_count == 0 && !rndis_opts->borrowed_net) + gether_detach_gadget(rndis_opts->net); +} + +static struct usb_function *rndis_alloc(struct usb_function_instance *fi) +{ + struct f_rndis *rndis; + struct f_rndis_opts *opts; + struct rndis_params *params; + + /* allocate and initialize one new instance */ + rndis = kzalloc_obj(*rndis); + if (!rndis) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_rndis_opts, func_inst); + mutex_lock(&opts->lock); + opts->refcnt++; + + gether_get_host_addr_u8(opts->net, rndis->ethaddr); + rndis->vendorID = opts->vendor_id; + rndis->manufacturer = opts->manufacturer; + + rndis->port.ioport = netdev_priv(opts->net); + mutex_unlock(&opts->lock); + /* 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"; + /* 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; + rndis->port.func.free_func = rndis_free; + + params = rndis_register(rndis_response_available, rndis); + if (IS_ERR(params)) { + kfree(rndis); + return ERR_CAST(params); + } + rndis->params = params; + + return &rndis->port.func; +} + +DECLARE_USB_FUNCTION_INIT(rndis, rndis_alloc_inst, rndis_alloc); +MODULE_DESCRIPTION("RNDIS link function driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/function/f_serial.c b/drivers/usb/gadget/function/f_serial.c new file mode 100644 index 000000000..ae8813c67 --- /dev/null +++ b/drivers/usb/gadget/function/f_serial.c @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include +#include +#include +#include + +#include "u_serial.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->enabled) { + dev_dbg(&cdev->gadget->dev, + "reset generic ttyGS%d\n", gser->port_num); + gserial_disconnect(&gser->port); + } + if (!gser->port.in->desc || !gser->port.out->desc) { + dev_dbg(&cdev->gadget->dev, + "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; + + dev_dbg(&cdev->gadget->dev, + "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 = usb_ep_autoconfig(cdev->gadget, &gser_fs_out_desc); + if (!ep) + goto fail; + gser->port.out = ep; + + /* 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, gser_ss_function); + if (status) + goto fail; + dev_dbg(&cdev->gadget->dev, "generic ttyGS%d: IN/%s OUT/%s\n", + gser->port_num, + gser->port.in->name, gser->port.out->name); + return 0; + +fail: + 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); +} + +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 const struct configfs_item_operations serial_item_ops = { + .release = serial_attr_release, +}; + +#ifdef CONFIG_U_SERIAL_CONSOLE + +static ssize_t f_serial_console_store(struct config_item *item, + const char *page, size_t count) +{ + return gserial_set_console(to_f_serial_opts(item)->port_num, + page, count); +} + +static ssize_t f_serial_console_show(struct config_item *item, char *page) +{ + return gserial_get_console(to_f_serial_opts(item)->port_num, page); +} + +CONFIGFS_ATTR(f_serial_, console); + +#endif /* CONFIG_U_SERIAL_CONSOLE */ + +static ssize_t f_serial_port_num_show(struct config_item *item, char *page) +{ + return sprintf(page, "%u\n", to_f_serial_opts(item)->port_num); +} + +CONFIGFS_ATTR_RO(f_serial_, port_num); + +static struct configfs_attribute *acm_attrs[] = { +#ifdef CONFIG_U_SERIAL_CONSOLE + &f_serial_attr_console, +#endif + &f_serial_attr_port_num, + NULL, +}; + +static const 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_obj(*opts); + 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) +{ + struct f_gser *gser = func_to_gser(f); + + /* Ensure port is disconnected before unbinding */ + gserial_disconnect(&gser->port); + usb_free_all_descriptors(f); +} + +static void gser_resume(struct usb_function *f) +{ + struct f_gser *gser = func_to_gser(f); + + gserial_resume(&gser->port); +} + +static void gser_suspend(struct usb_function *f) +{ + struct f_gser *gser = func_to_gser(f); + + gserial_suspend(&gser->port); +} + +static int gser_get_status(struct usb_function *f) +{ + return (f->func_wakeup_armed ? USB_INTRF_STAT_FUNC_RW : 0) | + USB_INTRF_STAT_FUNC_RW_CAP; +} + +static 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_obj(*gser); + 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; + gser->port.func.resume = gser_resume; + gser->port.func.suspend = gser_suspend; + gser->port.func.get_status = gser_get_status; + + return &gser->port.func; +} + +DECLARE_USB_FUNCTION_INIT(gser, gser_alloc_inst, gser_alloc); +MODULE_DESCRIPTION("generic USB serial function driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Al Borchers"); +MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/function/f_sourcesink.c b/drivers/usb/gadget/function/f_sourcesink.c new file mode 100644 index 000000000..d2b707e41 --- /dev/null +++ b/drivers/usb/gadget/function/f_sourcesink.c @@ -0,0 +1,1340 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_sourcesink.c - USB peripheral source/sink configuration driver + * + * Copyright (C) 2003-2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include +#include + +#include "g_zero.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. + */ +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; + + unsigned pattern; + unsigned isoc_interval; + unsigned isoc_maxpacket; + unsigned isoc_mult; + unsigned isoc_maxburst; + unsigned buflen; + unsigned bulk_maxburst; + unsigned bulk_qlen; + unsigned iso_qlen; +}; + +static inline struct f_sourcesink *func_to_ss(struct usb_function *f) +{ + return container_of(f, struct f_sourcesink, function); +} + +/*-------------------------------------------------------------------------*/ + +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), +}; + +static 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), +}; + +static 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, +}; + +static 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, +}; + +static 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, +}; + +/*-------------------------------------------------------------------------*/ + +static inline struct usb_request *ss_alloc_ep_req(struct usb_ep *ep, int len) +{ + return alloc_ep_req(ep, len); +} + +static void disable_ep(struct usb_composite_dev *cdev, struct usb_ep *ep) +{ + int value; + + value = usb_ep_disable(ep); + if (value < 0) + DBG(cdev, "disable %s --> %d\n", ep->name, value); +} + +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; + + if (ss->bulk_maxburst > 15) + ss->bulk_maxburst = 15; + + ss_source_comp_desc.bMaxBurst = ss->bulk_maxburst; + ss_sink_comp_desc.bMaxBurst = ss->bulk_maxburst; + + /* 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->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_sink_desc); + if (!ss->out_ep) + goto autoconf_fail; + + /* sanity check the isoc module parameters */ + if (ss->isoc_interval < 1) + ss->isoc_interval = 1; + if (ss->isoc_interval > 16) + ss->isoc_interval = 16; + if (ss->isoc_mult > 2) + ss->isoc_mult = 2; + if (ss->isoc_maxburst > 15) + ss->isoc_maxburst = 15; + + /* fill in the FS isoc descriptors from the module parameters */ + fs_iso_source_desc.wMaxPacketSize = ss->isoc_maxpacket > 1023 ? + 1023 : ss->isoc_maxpacket; + fs_iso_source_desc.bInterval = ss->isoc_interval; + fs_iso_sink_desc.wMaxPacketSize = ss->isoc_maxpacket > 1023 ? + 1023 : ss->isoc_maxpacket; + fs_iso_sink_desc.bInterval = ss->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_out_ep = usb_ep_autoconfig(cdev->gadget, &fs_iso_sink_desc); + if (!ss->iso_out_ep) { + usb_ep_autoconfig_release(ss->iso_in_ep); + 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 (ss->isoc_maxpacket > 1024) + ss->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 = ss->isoc_maxpacket; + hs_iso_source_desc.wMaxPacketSize |= ss->isoc_mult << 11; + hs_iso_source_desc.bInterval = ss->isoc_interval; + hs_iso_source_desc.bEndpointAddress = + fs_iso_source_desc.bEndpointAddress; + + hs_iso_sink_desc.wMaxPacketSize = ss->isoc_maxpacket; + hs_iso_sink_desc.wMaxPacketSize |= ss->isoc_mult << 11; + hs_iso_sink_desc.bInterval = ss->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 = ss->isoc_maxpacket; + ss_iso_source_desc.bInterval = ss->isoc_interval; + ss_iso_source_comp_desc.bmAttributes = ss->isoc_mult; + ss_iso_source_comp_desc.bMaxBurst = ss->isoc_maxburst; + ss_iso_source_comp_desc.wBytesPerInterval = ss->isoc_maxpacket * + (ss->isoc_mult + 1) * (ss->isoc_maxburst + 1); + ss_iso_source_desc.bEndpointAddress = + fs_iso_source_desc.bEndpointAddress; + + ss_iso_sink_desc.wMaxPacketSize = ss->isoc_maxpacket; + ss_iso_sink_desc.bInterval = ss->isoc_interval; + ss_iso_sink_comp_desc.bmAttributes = ss->isoc_mult; + ss_iso_sink_comp_desc.bMaxBurst = ss->isoc_maxburst; + ss_iso_sink_comp_desc.wBytesPerInterval = ss->isoc_maxpacket * + (ss->isoc_mult + 1) * (ss->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, + ss_source_sink_descs); + if (ret) + return ret; + + DBG(cdev, "%s: IN/%s, OUT/%s, ISO-IN/%s, ISO-OUT/%s\n", + 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) +{ + struct f_ss_opts *opts; + + opts = container_of(f->fi, struct f_ss_opts, func_inst); + + mutex_lock(&opts->lock); + opts->refcnt--; + mutex_unlock(&opts->lock); + + 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; + int max_packet_size = le16_to_cpu(ss->out_ep->desc->wMaxPacketSize); + + if (ss->pattern == 2) + return 0; + + for (i = 0; i < req->actual; i++, buf++) { + switch (ss->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 % max_packet_size) % 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; + int max_packet_size = le16_to_cpu(ep->desc->wMaxPacketSize); + struct f_sourcesink *ss = ep->driver_data; + + switch (ss->pattern) { + case 0: + memset(req->buf, 0, req->length); + break; + case 1: + for (i = 0; i < req->length; i++) + *buf++ = (u8) ((i % max_packet_size) % 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 (ss->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); + break; +#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, qlen, status = 0; + + if (is_iso) { + switch (speed) { + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER: + size = ss->isoc_maxpacket * + (ss->isoc_mult + 1) * + (ss->isoc_maxburst + 1); + break; + case USB_SPEED_HIGH: + size = ss->isoc_maxpacket * (ss->isoc_mult + 1); + break; + default: + size = ss->isoc_maxpacket > 1023 ? + 1023 : ss->isoc_maxpacket; + break; + } + ep = is_in ? ss->iso_in_ep : ss->iso_out_ep; + qlen = ss->iso_qlen; + } else { + ep = is_in ? ss->in_ep : ss->out_ep; + qlen = ss->bulk_qlen; + size = ss->buflen; + } + + for (i = 0; i < qlen; i++) { + req = ss_alloc_ep_req(ep, size); + if (!req) + return -ENOMEM; + + req->complete = source_sink_complete; + if (is_in) + reinit_write_data(ep, req); + else if (ss->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); + return status; + } + } + + 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); + 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); + 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); + 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); + 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; + + 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_obj(*ss); + if (!ss) + return ERR_PTR(-ENOMEM); + + ss_opts = container_of(fi, struct f_ss_opts, func_inst); + + mutex_lock(&ss_opts->lock); + ss_opts->refcnt++; + mutex_unlock(&ss_opts->lock); + + ss->pattern = ss_opts->pattern; + ss->isoc_interval = ss_opts->isoc_interval; + ss->isoc_maxpacket = ss_opts->isoc_maxpacket; + ss->isoc_mult = ss_opts->isoc_mult; + ss->isoc_maxburst = ss_opts->isoc_maxburst; + ss->buflen = ss_opts->bulk_buflen; + ss->bulk_maxburst = ss_opts->bulk_maxburst; + ss->bulk_qlen = ss_opts->bulk_qlen; + ss->iso_qlen = ss_opts->iso_qlen; + + 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 inline struct f_ss_opts *to_f_ss_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_ss_opts, + func_inst.group); +} + +static void ss_attr_release(struct config_item *item) +{ + struct f_ss_opts *ss_opts = to_f_ss_opts(item); + + usb_put_function_instance(&ss_opts->func_inst); +} + +static const struct configfs_item_operations ss_item_ops = { + .release = ss_attr_release, +}; + +static ssize_t f_ss_opts_pattern_show(struct config_item *item, char *page) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", opts->pattern); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_pattern_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int ret; + u8 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou8(page, 0, &num); + if (ret) + goto end; + + if (num != 0 && num != 1 && num != 2) { + ret = -EINVAL; + goto end; + } + + opts->pattern = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_ss_opts_, pattern); + +static ssize_t f_ss_opts_isoc_interval_show(struct config_item *item, char *page) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", opts->isoc_interval); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_isoc_interval_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int ret; + u8 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou8(page, 0, &num); + if (ret) + goto end; + + if (num > 16) { + ret = -EINVAL; + goto end; + } + + opts->isoc_interval = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_ss_opts_, isoc_interval); + +static ssize_t f_ss_opts_isoc_maxpacket_show(struct config_item *item, char *page) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", opts->isoc_maxpacket); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_isoc_maxpacket_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int ret; + u16 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou16(page, 0, &num); + if (ret) + goto end; + + if (num > 1024) { + ret = -EINVAL; + goto end; + } + + opts->isoc_maxpacket = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_ss_opts_, isoc_maxpacket); + +static ssize_t f_ss_opts_isoc_mult_show(struct config_item *item, char *page) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", opts->isoc_mult); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_isoc_mult_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int ret; + u8 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou8(page, 0, &num); + if (ret) + goto end; + + if (num > 2) { + ret = -EINVAL; + goto end; + } + + opts->isoc_mult = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_ss_opts_, isoc_mult); + +static ssize_t f_ss_opts_isoc_maxburst_show(struct config_item *item, char *page) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", opts->isoc_maxburst); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_isoc_maxburst_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int ret; + u8 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou8(page, 0, &num); + if (ret) + goto end; + + if (num > 15) { + ret = -EINVAL; + goto end; + } + + opts->isoc_maxburst = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_ss_opts_, isoc_maxburst); + +static ssize_t f_ss_opts_bulk_maxburst_show(struct config_item *item, char *page) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sysfs_emit(page, "%u\n", opts->bulk_maxburst); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_bulk_maxburst_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int ret; + u8 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou8(page, 0, &num); + if (ret) + goto end; + + if (num > 15) { + ret = -EINVAL; + goto end; + } + + opts->bulk_maxburst = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_ss_opts_, bulk_maxburst); + +static ssize_t f_ss_opts_bulk_buflen_show(struct config_item *item, char *page) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", opts->bulk_buflen); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_bulk_buflen_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int ret; + u32 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou32(page, 0, &num); + if (ret) + goto end; + + opts->bulk_buflen = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_ss_opts_, bulk_buflen); + +static ssize_t f_ss_opts_bulk_qlen_show(struct config_item *item, char *page) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", opts->bulk_qlen); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_bulk_qlen_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int ret; + u32 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou32(page, 0, &num); + if (ret) + goto end; + + opts->bulk_qlen = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_ss_opts_, bulk_qlen); + +static ssize_t f_ss_opts_iso_qlen_show(struct config_item *item, char *page) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", opts->iso_qlen); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_iso_qlen_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ss_opts *opts = to_f_ss_opts(item); + int ret; + u32 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou32(page, 0, &num); + if (ret) + goto end; + + opts->iso_qlen = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_ss_opts_, iso_qlen); + +static struct configfs_attribute *ss_attrs[] = { + &f_ss_opts_attr_pattern, + &f_ss_opts_attr_isoc_interval, + &f_ss_opts_attr_isoc_maxpacket, + &f_ss_opts_attr_isoc_mult, + &f_ss_opts_attr_isoc_maxburst, + &f_ss_opts_attr_bulk_buflen, + &f_ss_opts_attr_bulk_maxburst, + &f_ss_opts_attr_bulk_qlen, + &f_ss_opts_attr_iso_qlen, + NULL, +}; + +static const struct config_item_type ss_func_type = { + .ct_item_ops = &ss_item_ops, + .ct_attrs = ss_attrs, + .ct_owner = THIS_MODULE, +}; + +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_obj(*ss_opts); + if (!ss_opts) + return ERR_PTR(-ENOMEM); + mutex_init(&ss_opts->lock); + ss_opts->func_inst.free_func_inst = source_sink_free_instance; + ss_opts->isoc_interval = GZERO_ISOC_INTERVAL; + ss_opts->isoc_maxpacket = GZERO_ISOC_MAXPACKET; + ss_opts->bulk_buflen = GZERO_BULK_BUFLEN; + ss_opts->bulk_qlen = GZERO_SS_BULK_QLEN; + ss_opts->iso_qlen = GZERO_SS_ISO_QLEN; + + config_group_init_type_name(&ss_opts->func_inst.group, "", + &ss_func_type); + + 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_DESCRIPTION("USB peripheral source/sink configuration driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_subset.c b/drivers/usb/gadget/function/f_subset.c new file mode 100644 index 000000000..6e3265b8a --- /dev/null +++ b/drivers/usb/gadget/function/f_subset.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_subset.c -- "CDC Subset" Ethernet link function driver + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2008 Nokia Corporation + */ + +#include +#include +#include +#include +#include +#include + +#include "u_ether.h" +#include "u_ether_configfs.h" +#include "u_gether.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->enabled) { + 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 PTR_ERR_OR_ZERO(net); +} + +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); + struct usb_string *us; + int status; + struct usb_ep *ep; + + struct f_gether_opts *gether_opts; + struct net_device *net __free(detach_gadget) = NULL; + + gether_opts = container_of(f->fi, struct f_gether_opts, func_inst); + + scoped_guard(mutex, &gether_opts->lock) + if (gether_opts->bind_count == 0 && !gether_opts->bound) { + if (!device_is_registered(&gether_opts->net->dev)) { + gether_set_gadget(gether_opts->net, cdev->gadget); + status = gether_register_netdev(gether_opts->net); + } else + status = gether_attach_gadget(gether_opts->net, cdev->gadget); + + if (status) + return status; + net = gether_opts->net; + } + + us = usb_gstrings_attach(cdev, geth_strings, + ARRAY_SIZE(geth_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + + subset_data_intf.iInterface = us[0].id; + ether_desc.iMACAddress = us[1].id; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + return status; + subset_data_intf.bInterfaceNumber = status; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_in_desc); + if (!ep) + return -ENODEV; + geth->port.in_ep = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_out_desc); + if (!ep) + return -ENODEV; + geth->port.out_ep = ep; + + /* 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, ss_eth_function); + if (status) + return status; + + /* 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(). + */ + + gether_opts->bind_count++; + retain_and_null_ptr(net); + + DBG(cdev, "CDC Subset: IN/%s OUT/%s\n", + geth->port.in_ep->name, geth->port.out_ep->name); + return 0; +} + +static inline struct f_gether_opts *to_f_gether_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_gether_opts, + func_inst.group); +} + +/* f_gether_item_ops */ +USB_ETHERNET_CONFIGFS_ITEM(gether); + +/* f_gether_opts_dev_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(gether); + +/* f_gether_opts_host_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(gether); + +/* f_gether_opts_qmult */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(gether); + +/* f_gether_opts_ifname */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(gether); + +static struct configfs_attribute *gether_attrs[] = { + &gether_opts_attr_dev_addr, + &gether_opts_attr_host_addr, + &gether_opts_attr_qmult, + &gether_opts_attr_ifname, + NULL, +}; + +static const struct config_item_type gether_func_type = { + .ct_item_ops = &gether_item_ops, + .ct_attrs = gether_attrs, + .ct_owner = THIS_MODULE, +}; + +static void geth_free_inst(struct usb_function_instance *f) +{ + struct f_gether_opts *opts; + + opts = container_of(f, struct f_gether_opts, func_inst); + if (device_is_registered(&opts->net->dev)) + gether_cleanup(netdev_priv(opts->net)); + else + free_netdev(opts->net); + kfree(opts); +} + +static struct usb_function_instance *geth_alloc_inst(void) +{ + struct f_gether_opts *opts; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = geth_free_inst; + opts->net = gether_setup_default(); + if (IS_ERR(opts->net)) { + struct net_device *net = opts->net; + kfree(opts); + return ERR_CAST(net); + } + + config_group_init_type_name(&opts->func_inst.group, "", + &gether_func_type); + + return &opts->func_inst; +} + +static void geth_free(struct usb_function *f) +{ + struct f_gether *eth; + struct f_gether_opts *opts; + + opts = container_of(f->fi, struct f_gether_opts, func_inst); + + eth = func_to_geth(f); + scoped_guard(mutex, &opts->lock) + opts->refcnt--; + kfree(eth); +} + +static void geth_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_gether_opts *opts; + + opts = container_of(f->fi, struct f_gether_opts, func_inst); + + geth_string_defs[0].id = 0; + usb_free_all_descriptors(f); + + opts->bind_count--; + if (opts->bind_count == 0 && !opts->bound) + gether_detach_gadget(opts->net); +} + +static struct usb_function *geth_alloc(struct usb_function_instance *fi) +{ + struct f_gether *geth; + struct f_gether_opts *opts; + int status; + + /* allocate and initialize one new instance */ + geth = kzalloc_obj(*geth); + if (!geth) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_gether_opts, func_inst); + + mutex_lock(&opts->lock); + opts->refcnt++; + /* export host's Ethernet address in CDC format */ + status = gether_get_host_addr_cdc(opts->net, geth->ethaddr, + sizeof(geth->ethaddr)); + if (status < 12) { + kfree(geth); + mutex_unlock(&opts->lock); + return ERR_PTR(-EINVAL); + } + geth_string_defs[1].s = geth->ethaddr; + + geth->port.ioport = netdev_priv(opts->net); + mutex_unlock(&opts->lock); + geth->port.cdc_filter = DEFAULT_FILTER; + + geth->port.func.name = "cdc_subset"; + 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; + geth->port.func.free_func = geth_free; + + return &geth->port.func; +} + +DECLARE_USB_FUNCTION_INIT(geth, geth_alloc_inst, geth_alloc); +MODULE_DESCRIPTION("\"CDC Subset\" Ethernet link function driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c new file mode 100644 index 000000000..9e6d4f399 --- /dev/null +++ b/drivers/usb/gadget/function/f_tcm.c @@ -0,0 +1,2794 @@ +// SPDX-License-Identifier: GPL-2.0 +/* 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tcm.h" +#include "u_tcm.h" +#include "configfs.h" + +#define TPG_INSTANCES 1 + +struct tpg_instance { + struct usb_function_instance *func_inst; + struct usbg_tpg *tpg; +}; + +static struct tpg_instance tpg_instances[TPG_INSTANCES]; + +static DEFINE_MUTEX(tpg_instances_lock); + +static inline struct f_uas *to_f_uas(struct usb_function *f) +{ + return container_of(f, struct f_uas, function); +} + +/* 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[0].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; + + transport_generic_free_cmd(&cmd->se_cmd, 0); + if (req->status == -ESHUTDOWN) + return; + + if (req->status < 0) + pr_err("ERR %s(%d)\n", __func__, __LINE__); + + /* 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; + unsigned int csw_stat; + + csw_stat = cmd->csw_code; + 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[0].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__); + + if (req->status == -ESHUTDOWN) { + transport_generic_free_cmd(&cmd->se_cmd, 0); + return; + } + + 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; + int ret; + + cmd->fu = fu; + + if (!cmd->data_len) { + cmd->csw_code = US_BULK_STAT_PHASE; + return -EINVAL; + } + + 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__); + +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; + + if (req->status == -ESHUTDOWN) + return; + + fu->flags &= ~USBG_BOT_CMD_PEND; + + if (req->status < 0) { + struct usb_gadget *gadget = fuas_to_gadget(fu); + + dev_err(&gadget->dev, "BOT command req err (%d)\n", req->status); + bot_enqueue_cmd_cbw(fu); + return; + } + + ret = bot_submit_command(fu, req->buf, req->actual); + if (ret) { + pr_err("%s(%d): %d\n", __func__, __LINE__, ret); + if (!(fu->flags & USBG_BOT_WEDGED)) + usb_ep_set_wedge(fu->ep_in); + + fu->flags |= USBG_BOT_WEDGED; + bot_enqueue_cmd_cbw(fu); + } else if (fu->flags & USBG_BOT_WEDGED) { + fu->flags &= ~USBG_BOT_WEDGED; + usb_ep_clear_halt(fu->ep_in); + } +} + +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[0].req = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL); + if (!fu->cmd[0].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[0].buf = kmalloc(fu->ep_out->maxpacket, GFP_KERNEL); + if (!fu->cmd[0].buf) + goto err_buf; + + fu->cmd[0].req->complete = bot_cmd_complete; + fu->cmd[0].req->buf = fu->cmd[0].buf; + fu->cmd[0].req->length = fu->ep_out->maxpacket; + fu->cmd[0].req->context = fu; + + ret = bot_enqueue_cmd_cbw(fu); + if (ret) + goto err_queue; + return 0; +err_queue: + kfree(fu->cmd[0].buf); + fu->cmd[0].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[0].req); + fu->cmd[0].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; +} + +static 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[0].req); + usb_ep_free_request(fu->ep_in, fu->bot_status.req); + + kfree(fu->cmd[0].buf); + + fu->bot_req_in = NULL; + fu->bot_req_out = NULL; + fu->cmd[0].req = NULL; + fu->bot_status.req = NULL; + fu->cmd[0].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_and_alt(gadget, f, fu->ep_in, USB_G_ALT_INT_BBB); + ret = usb_ep_enable(fu->ep_in); + if (ret) + goto err_b_in; + + config_ep_by_speed_and_alt(gadget, f, fu->ep_out, USB_G_ALT_INT_BBB); + 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; + } + luns--; + if (luns > US_BULK_MAX_LUN_LIMIT) { + pr_info_once("Limiting the number of luns to 16\n"); + luns = US_BULK_MAX_LUN_LIMIT; + } + ret_lun = cdev->req->buf; + *ret_lun = luns; + cdev->req->length = 1; + return usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + + case US_BULK_RESET_REQUEST: + /* XXX maybe we should remove previous requests for IN + OUT */ + if (fu->flags & USBG_BOT_WEDGED) { + fu->flags &= ~USBG_BOT_WEDGED; + usb_ep_clear_halt(fu->ep_in); + } + + bot_enqueue_cmd_cbw(fu); + return 0; + } + return -ENOTSUPP; +} + +/* Start uas.c code */ + +static int tcm_to_uasp_response(enum tcm_tmrsp_table code) +{ + switch (code) { + case TMR_FUNCTION_FAILED: + return RC_TMF_FAILED; + case TMR_FUNCTION_COMPLETE: + case TMR_TASK_DOES_NOT_EXIST: + return RC_TMF_COMPLETE; + case TMR_LUN_DOES_NOT_EXIST: + return RC_INCORRECT_LUN; + case TMR_FUNCTION_REJECTED: + case TMR_TASK_MGMT_FUNCTION_NOT_SUPPORTED: + default: + return RC_TMF_NOT_SUPPORTED; + } +} + +static unsigned char uasp_to_tcm_func(int code) +{ + switch (code) { + case TMF_ABORT_TASK: + return TMR_ABORT_TASK; + case TMF_ABORT_TASK_SET: + return TMR_ABORT_TASK_SET; + case TMF_CLEAR_TASK_SET: + return TMR_CLEAR_TASK_SET; + case TMF_LOGICAL_UNIT_RESET: + return TMR_LUN_RESET; + case TMF_CLEAR_ACA: + return TMR_CLEAR_ACA; + case TMF_I_T_NEXUS_RESET: + case TMF_QUERY_TASK: + case TMF_QUERY_TASK_SET: + case TMF_QUERY_ASYNC_EVENT: + default: + return TMR_UNKNOWN; + } +} + +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) +{ + int i; + + for (i = 0; i < USBG_NUM_CMDS; i++) { + usb_ep_free_request(fu->ep_cmd, fu->cmd[i].req); + kfree(fu->cmd[i].buf); + fu->cmd[i].req = NULL; + fu->cmd[i].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 < USBG_NUM_CMDS; 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 = &fu->stream[se_cmd->map_tag]; + + 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->is_last = 1; + stream->req_in->stream_id = cmd->tag; + 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->fu->stream[se_cmd->map_tag]; + + 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->is_last = 1; + stream->req_status->stream_id = cmd->tag; + 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_prepare_response(struct usbg_cmd *cmd) +{ + struct se_cmd *se_cmd = &cmd->se_cmd; + struct response_iu *rsp_iu = &cmd->response_iu; + struct uas_stream *stream = &cmd->fu->stream[se_cmd->map_tag]; + + cmd->state = UASP_QUEUE_COMMAND; + rsp_iu->iu_id = IU_ID_RESPONSE; + rsp_iu->tag = cpu_to_be16(cmd->tag); + + if (cmd->tmr_rsp != RC_RESPONSE_UNKNOWN) + rsp_iu->response_code = cmd->tmr_rsp; + else + rsp_iu->response_code = + tcm_to_uasp_response(se_cmd->se_tmr_req->response); + + /* + * The UASP driver must support all the task management functions listed + * in Table 20 of UAS-r04. To remain compliant while indicate that the + * TMR did not go through, report RC_TMF_FAILED instead of + * RC_TMF_NOT_SUPPORTED and print a warning to the user. + */ + switch (cmd->tmr_func) { + case TMF_ABORT_TASK: + case TMF_ABORT_TASK_SET: + case TMF_CLEAR_TASK_SET: + case TMF_LOGICAL_UNIT_RESET: + case TMF_CLEAR_ACA: + case TMF_I_T_NEXUS_RESET: + case TMF_QUERY_TASK: + case TMF_QUERY_TASK_SET: + case TMF_QUERY_ASYNC_EVENT: + if (rsp_iu->response_code == RC_TMF_NOT_SUPPORTED) { + struct usb_gadget *gadget = fuas_to_gadget(cmd->fu); + + dev_warn(&gadget->dev, "TMF function %d not supported\n", + cmd->tmr_func); + rsp_iu->response_code = RC_TMF_FAILED; + } + break; + default: + break; + } + + stream->req_status->is_last = 1; + stream->req_status->stream_id = cmd->tag; + stream->req_status->context = cmd; + stream->req_status->length = sizeof(struct response_iu); + stream->req_status->buf = rsp_iu; + stream->req_status->complete = uasp_status_data_cmpl; +} + +static void usbg_release_cmd(struct se_cmd *se_cmd); +static int uasp_send_tm_response(struct usbg_cmd *cmd); + +static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req) +{ + struct usbg_cmd *cmd = req->context; + struct f_uas *fu = cmd->fu; + struct uas_stream *stream = &fu->stream[cmd->se_cmd.map_tag]; + int ret; + + if (req->status == -ESHUTDOWN) + 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: + /* + * Overlapped command detected and cancelled. + * So send overlapped attempted status. + */ + if (cmd->tmr_rsp == RC_OVERLAPPED_TAG && + req->status == -ECONNRESET) { + uasp_send_tm_response(cmd); + return; + } + + hash_del(&stream->node); + + /* + * If no command submitted to target core here, just free the + * bitmap index. This is for the cases where f_tcm handles + * status response instead of the target core. + */ + if (cmd->tmr_rsp != RC_OVERLAPPED_TAG && + cmd->tmr_rsp != RC_RESPONSE_UNKNOWN) { + struct se_session *se_sess; + + se_sess = fu->tpg->tpg_nexus->tvn_se_sess; + sbitmap_queue_clear(&se_sess->sess_tag_pool, + cmd->se_cmd.map_tag, + cmd->se_cmd.map_cpu); + } else { + transport_generic_free_cmd(&cmd->se_cmd, 0); + } + + usb_ep_queue(fu->ep_cmd, cmd->req, GFP_ATOMIC); + complete(&stream->cmd_completion); + break; + + default: + BUG(); + } + return; + +cleanup: + hash_del(&stream->node); + transport_generic_free_cmd(&cmd->se_cmd, 0); +} + +static int uasp_send_status_response(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct uas_stream *stream = &fu->stream[cmd->se_cmd.map_tag]; + struct sense_iu *iu = &cmd->sense_iu; + + iu->tag = cpu_to_be16(cmd->tag); + cmd->fu = fu; + uasp_prepare_status(cmd); + return usb_ep_queue(fu->ep_status, stream->req_status, GFP_ATOMIC); +} + +static int uasp_send_tm_response(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct uas_stream *stream = &fu->stream[cmd->se_cmd.map_tag]; + struct response_iu *iu = &cmd->response_iu; + + iu->tag = cpu_to_be16(cmd->tag); + cmd->fu = fu; + uasp_prepare_response(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 = &fu->stream[cmd->se_cmd.map_tag]; + 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 = &fu->stream[se_cmd->map_tag]; + 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 = 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__); + } + +cleanup: + return ret; +} + +static int usbg_submit_command(struct f_uas *, struct usb_request *); + +static void uasp_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_uas *fu = req->context; + + if (req->status == -ESHUTDOWN) + return; + + if (req->status < 0) { + usb_ep_queue(fu->ep_cmd, req, GFP_ATOMIC); + return; + } + + usbg_submit_command(fu, req); +} + +static int uasp_alloc_stream_res(struct f_uas *fu, struct uas_stream *stream) +{ + init_completion(&stream->cmd_completion); + + 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_out, stream->req_out); + stream->req_out = NULL; +err_out: + usb_ep_free_request(fu->ep_in, stream->req_in); + stream->req_in = NULL; +out: + return -ENOMEM; +} + +static int uasp_alloc_cmd(struct f_uas *fu, int i) +{ + fu->cmd[i].req = usb_ep_alloc_request(fu->ep_cmd, GFP_KERNEL); + if (!fu->cmd[i].req) + goto err; + + fu->cmd[i].buf = kmalloc(fu->ep_cmd->maxpacket, GFP_KERNEL); + if (!fu->cmd[i].buf) + goto err_buf; + + fu->cmd[i].req->complete = uasp_cmd_complete; + fu->cmd[i].req->buf = fu->cmd[i].buf; + fu->cmd[i].req->length = fu->ep_cmd->maxpacket; + fu->cmd[i].req->context = fu; + return 0; + +err_buf: + usb_ep_free_request(fu->ep_cmd, fu->cmd[i].req); +err: + return -ENOMEM; +} + +static int uasp_prepare_reqs(struct f_uas *fu) +{ + int ret; + int i; + + for (i = 0; i < USBG_NUM_CMDS; i++) { + ret = uasp_alloc_stream_res(fu, &fu->stream[i]); + if (ret) + goto err_cleanup; + } + + for (i = 0; i < USBG_NUM_CMDS; i++) { + ret = uasp_alloc_cmd(fu, i); + if (ret) + goto err_free_stream; + + ret = usb_ep_queue(fu->ep_cmd, fu->cmd[i].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_and_alt(gadget, f, fu->ep_in, USB_G_ALT_INT_UAS); + ret = usb_ep_enable(fu->ep_in); + if (ret) + goto err_b_in; + + config_ep_by_speed_and_alt(gadget, f, fu->ep_out, USB_G_ALT_INT_UAS); + ret = usb_ep_enable(fu->ep_out); + if (ret) + goto err_b_out; + + config_ep_by_speed_and_alt(gadget, f, fu->ep_cmd, USB_G_ALT_INT_UAS); + ret = usb_ep_enable(fu->ep_cmd); + if (ret) + goto err_cmd; + config_ep_by_speed_and_alt(gadget, f, fu->ep_status, USB_G_ALT_INT_UAS); + 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_16: + 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: + case ATA_12: + case ATA_16: + 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: +#define CMD_DIR_MSG "target: Unknown data direction for SCSI Opcode 0x%02x\n" + pr_warn(CMD_DIR_MSG, cdb[0]); +#undef CMD_DIR_MSG + 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; + + cmd->state = UASP_QUEUE_COMMAND; + + if (req->status == -ESHUTDOWN) { + struct uas_stream *stream = &cmd->fu->stream[se_cmd->map_tag]; + + hash_del(&stream->node); + target_put_sess_cmd(se_cmd); + transport_generic_free_cmd(&cmd->se_cmd, 0); + return; + } + + if (req->status) { + 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); + } + + cmd->flags |= USBG_CMD_PENDING_DATA_WRITE; + queue_work(cmd->fu->tpg->workqueue, &cmd->work); + return; + +cleanup: + target_put_sess_cmd(se_cmd); + + /* Command was aborted due to overlapped tag */ + if (cmd->state == UASP_QUEUE_COMMAND && + cmd->tmr_rsp == RC_OVERLAPPED_TAG) { + uasp_send_tm_response(cmd); + return; + } + + transport_send_check_condition_and_sense(se_cmd, + TCM_CHECK_CONDITION_ABORT_CMD, 0); +} + +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->is_last = 1; + req->stream_id = cmd->tag; + req->complete = usbg_data_write_cmpl; + req->length = se_cmd->data_length; + req->context = cmd; + + cmd->state = UASP_SEND_STATUS; + 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_aborted_task(struct se_cmd *se_cmd); + +static void usbg_submit_tmr(struct usbg_cmd *cmd) +{ + struct se_session *se_sess; + struct se_cmd *se_cmd; + int flags = TARGET_SCF_ACK_KREF; + + se_cmd = &cmd->se_cmd; + se_sess = cmd->fu->tpg->tpg_nexus->tvn_se_sess; + + target_submit_tmr(se_cmd, se_sess, + cmd->response_iu.add_response_info, + cmd->unpacked_lun, NULL, uasp_to_tcm_func(cmd->tmr_func), + GFP_ATOMIC, cmd->tag, flags); +} + +static void usbg_submit_cmd(struct usbg_cmd *cmd) +{ + struct se_cmd *se_cmd; + struct tcm_usbg_nexus *tv_nexus; + struct usbg_tpg *tpg; + int dir, flags = (TARGET_SCF_UNKNOWN_SIZE | TARGET_SCF_ACK_KREF); + + /* + * Note: each command will spawn its own process, and each stage of the + * command is processed sequentially. Should this no longer be the case, + * locking is needed. + */ + if (cmd->flags & USBG_CMD_PENDING_DATA_WRITE) { + target_execute_cmd(&cmd->se_cmd); + cmd->flags &= ~USBG_CMD_PENDING_DATA_WRITE; + return; + } + + se_cmd = &cmd->se_cmd; + tpg = cmd->fu->tpg; + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) { + struct usb_gadget *gadget = fuas_to_gadget(cmd->fu); + + dev_err(&gadget->dev, "Missing nexus, ignoring command\n"); + return; + } + + dir = get_cmd_dir(cmd->cmd_buf); + if (dir < 0) + goto out; + + 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, flags); + + return; + +out: + __target_init_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, + cmd->unpacked_lun, NULL); + transport_send_check_condition_and_sense(se_cmd, + TCM_UNSUPPORTED_SCSI_OPCODE, 0); +} + +static void usbg_cmd_work(struct work_struct *work) +{ + struct usbg_cmd *cmd = container_of(work, struct usbg_cmd, work); + + /* + * Failure is detected by f_tcm here. Skip submitting the command to the + * target core if we already know the failing response and send the usb + * response to the host directly. + */ + if (cmd->tmr_rsp != RC_RESPONSE_UNKNOWN) + goto skip; + + if (cmd->tmr_func) + usbg_submit_tmr(cmd); + else + usbg_submit_cmd(cmd); + + return; + +skip: + if (cmd->tmr_rsp == RC_OVERLAPPED_TAG) { + struct f_uas *fu = cmd->fu; + struct se_session *se_sess; + struct uas_stream *stream = NULL; + struct hlist_node *tmp; + struct usbg_cmd *active_cmd = NULL; + + se_sess = cmd->fu->tpg->tpg_nexus->tvn_se_sess; + + hash_for_each_possible_safe(fu->stream_hash, stream, tmp, node, cmd->tag) { + int i = stream - &fu->stream[0]; + + active_cmd = &((struct usbg_cmd *)se_sess->sess_cmd_map)[i]; + if (active_cmd->tag == cmd->tag) + break; + } + + /* Sanity check */ + if (!stream || (active_cmd && active_cmd->tag != cmd->tag)) { + usbg_submit_command(cmd->fu, cmd->req); + return; + } + + reinit_completion(&stream->cmd_completion); + + /* + * A UASP command consists of the command, data, and status + * stages, each operating sequentially from different endpoints. + * + * Each USB endpoint operates independently, and depending on + * hardware implementation, a completion callback for a transfer + * from one endpoint may not reflect the order of completion on + * the wire. This is particularly true for devices with + * endpoints that have independent interrupts and event buffers. + * + * The driver must still detect misbehaving hosts and respond + * with an overlap status. To reduce false overlap failures, + * allow the active and matching stream ID a brief 1ms to + * complete before responding with an overlap command failure. + * Overlap failure should be rare. + */ + wait_for_completion_timeout(&stream->cmd_completion, msecs_to_jiffies(1)); + + /* If the previous stream is completed, retry the command. */ + if (!hash_hashed(&stream->node)) { + usbg_submit_command(cmd->fu, cmd->req); + return; + } + + /* + * The command isn't submitted to the target core, so we're safe + * to remove the bitmap index from the session tag pool. + */ + sbitmap_queue_clear(&se_sess->sess_tag_pool, + cmd->se_cmd.map_tag, + cmd->se_cmd.map_cpu); + + /* + * Overlap command tag detected. Cancel any pending transfer of + * the command submitted to target core. + */ + active_cmd->tmr_rsp = RC_OVERLAPPED_TAG; + usbg_aborted_task(&active_cmd->se_cmd); + + /* Send the response after the transfer is aborted. */ + return; + } + + uasp_send_tm_response(cmd); +} + +static struct usbg_cmd *usbg_get_cmd(struct f_uas *fu, + struct tcm_usbg_nexus *tv_nexus, u32 scsi_tag) +{ + struct se_session *se_sess = tv_nexus->tvn_se_sess; + struct usbg_cmd *cmd; + int tag, cpu; + + tag = sbitmap_queue_get(&se_sess->sess_tag_pool, &cpu); + if (tag < 0) + return ERR_PTR(-ENOMEM); + + cmd = &((struct usbg_cmd *)se_sess->sess_cmd_map)[tag]; + memset(cmd, 0, sizeof(*cmd)); + cmd->se_cmd.map_tag = tag; + cmd->se_cmd.map_cpu = cpu; + cmd->se_cmd.cpuid = cpu; + cmd->se_cmd.tag = cmd->tag = scsi_tag; + cmd->fu = fu; + + return cmd; +} + +static void usbg_release_cmd(struct se_cmd *); + +static int usbg_submit_command(struct f_uas *fu, struct usb_request *req) +{ + struct iu *iu = req->buf; + struct usbg_cmd *cmd; + struct usbg_tpg *tpg = fu->tpg; + struct tcm_usbg_nexus *tv_nexus; + struct uas_stream *stream; + struct hlist_node *tmp; + struct command_iu *cmd_iu; + u32 cmd_len; + u16 scsi_tag; + + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) { + pr_err("Missing nexus, ignoring command\n"); + return -EINVAL; + } + + scsi_tag = be16_to_cpup(&iu->tag); + cmd = usbg_get_cmd(fu, tv_nexus, scsi_tag); + if (IS_ERR(cmd)) { + pr_err("usbg_get_cmd failed\n"); + return -ENOMEM; + } + + cmd->req = req; + cmd->fu = fu; + cmd->tag = scsi_tag; + cmd->se_cmd.tag = scsi_tag; + cmd->tmr_func = 0; + cmd->tmr_rsp = RC_RESPONSE_UNKNOWN; + cmd->flags = 0; + cmd->data_len = 0; + + cmd_iu = (struct command_iu *)iu; + + /* Command and Task Management IUs share the same LUN offset */ + cmd->unpacked_lun = scsilun_to_int(&cmd_iu->lun); + + if (iu->iu_id != IU_ID_COMMAND && iu->iu_id != IU_ID_TASK_MGMT) { + cmd->tmr_rsp = RC_INVALID_INFO_UNIT; + goto skip; + } + + hash_for_each_possible_safe(fu->stream_hash, stream, tmp, node, scsi_tag) { + struct usbg_cmd *active_cmd; + struct se_session *se_sess; + int i = stream - &fu->stream[0]; + + se_sess = cmd->fu->tpg->tpg_nexus->tvn_se_sess; + active_cmd = &((struct usbg_cmd *)se_sess->sess_cmd_map)[i]; + + if (active_cmd->tag == scsi_tag) { + cmd->tmr_rsp = RC_OVERLAPPED_TAG; + goto skip; + } + } + + stream = &fu->stream[cmd->se_cmd.map_tag]; + hash_add(fu->stream_hash, &stream->node, scsi_tag); + + if (iu->iu_id == IU_ID_TASK_MGMT) { + struct task_mgmt_iu *tm_iu; + + tm_iu = (struct task_mgmt_iu *)iu; + cmd->tmr_func = tm_iu->function; + goto skip; + } + + cmd_len = (cmd_iu->len & ~0x3) + 16; + if (cmd_len > USBG_MAX_CMD) { + target_free_tag(tv_nexus->tvn_se_sess, &cmd->se_cmd); + hash_del(&stream->node); + return -EINVAL; + } + memcpy(cmd->cmd_buf, cmd_iu->cdb, cmd_len); + + switch (cmd_iu->prio_attr & 0x7) { + case UAS_HEAD_TAG: + cmd->prio_attr = TCM_HEAD_TAG; + break; + case UAS_ORDERED_TAG: + cmd->prio_attr = TCM_ORDERED_TAG; + break; + case UAS_ACA: + cmd->prio_attr = TCM_ACA_TAG; + break; + default: + pr_debug_once("Unsupported prio_attr: %02x.\n", + cmd_iu->prio_attr); + fallthrough; + case UAS_SIMPLE_TAG: + cmd->prio_attr = TCM_SIMPLE_TAG; + break; + } + +skip: + INIT_WORK(&cmd->work, usbg_cmd_work); + queue_work(tpg->workqueue, &cmd->work); + + return 0; +} + +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 flags = TARGET_SCF_ACK_KREF; + int dir; + + /* + * Note: each command will spawn its own process, and each stage of the + * command is processed sequentially. Should this no longer be the case, + * locking is needed. + */ + if (cmd->flags & USBG_CMD_PENDING_DATA_WRITE) { + target_execute_cmd(&cmd->se_cmd); + cmd->flags &= ~USBG_CMD_PENDING_DATA_WRITE; + return; + } + + se_cmd = &cmd->se_cmd; + tpg = cmd->fu->tpg; + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) { + struct usb_gadget *gadget = fuas_to_gadget(cmd->fu); + + dev_err(&gadget->dev, "Missing nexus, ignoring command\n"); + return; + } + + dir = get_cmd_dir(cmd->cmd_buf); + if (dir < 0) + goto out; + + 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, flags); + return; + +out: + __target_init_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, + cmd->unpacked_lun, NULL); + transport_send_check_condition_and_sense(se_cmd, + TCM_UNSUPPORTED_SCSI_OPCODE, 0); +} + +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 = fu->tpg; + struct tcm_usbg_nexus *tv_nexus; + u32 cmd_len; + + 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; + + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) { + pr_err("Missing nexus, ignoring command\n"); + return -ENODEV; + } + + cmd = usbg_get_cmd(fu, tv_nexus, cbw->Tag); + if (IS_ERR(cmd)) { + pr_err("usbg_get_cmd failed\n"); + return -ENOMEM; + } + memcpy(cmd->cmd_buf, cbw->CDB, cmd_len); + + cmd->bot_tag = cbw->Tag; + cmd->prio_attr = TCM_SIMPLE_TAG; + cmd->unpacked_lun = cbw->Lun; + cmd->is_read = cbw->Flags & US_BULK_FLAG_IN ? 1 : 0; + cmd->data_len = le32_to_cpu(cbw->DataTransferLength); + cmd->se_cmd.tag = le32_to_cpu(cmd->bot_tag); + cmd->flags = 0; + + INIT_WORK(&cmd->work, bot_cmd_work); + queue_work(tpg->workqueue, &cmd->work); + + return 0; +} + +/* Start fabric.c code */ + +static int usbg_check_true(struct se_portal_group *se_tpg) +{ + return 1; +} + +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 void usbg_release_cmd(struct se_cmd *se_cmd) +{ + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, + se_cmd); + struct se_session *se_sess = se_cmd->se_sess; + + cmd->tag = 0; + kfree(cmd->data_buf); + target_free_tag(se_sess, se_cmd); +} + +static void usbg_queue_tm_rsp(struct se_cmd *se_cmd) +{ + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, se_cmd); + + uasp_send_tm_response(cmd); +} + +static void usbg_aborted_task(struct se_cmd *se_cmd) +{ + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, se_cmd); + struct f_uas *fu = cmd->fu; + struct usb_gadget *gadget = fuas_to_gadget(fu); + struct uas_stream *stream = &fu->stream[se_cmd->map_tag]; + int ret = 0; + + if (stream->req_out->status == -EINPROGRESS) + ret = usb_ep_dequeue(fu->ep_out, stream->req_out); + else if (stream->req_in->status == -EINPROGRESS) + ret = usb_ep_dequeue(fu->ep_in, stream->req_in); + else if (stream->req_status->status == -EINPROGRESS) + ret = usb_ep_dequeue(fu->ep_status, stream->req_status); + + if (ret) + dev_err(&gadget->dev, "Failed to abort cmd tag %d, (%d)\n", + cmd->tag, ret); + + cmd->state = UASP_QUEUE_COMMAND; +} + +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 int usbg_init_nodeacl(struct se_node_acl *se_nacl, const char *name) +{ + if (!usbg_check_wwn(name)) + return -EINVAL; + return 0; +} + +static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn, + const char *name) +{ + struct usbg_tport *tport = container_of(wwn, struct usbg_tport, + tport_wwn); + struct usbg_tpg *tpg; + u16 tpgt; + int ret; + struct f_tcm_opts *opts; + unsigned i; + + if (strstr(name, "tpgt_") != name) + return ERR_PTR(-EINVAL); + if (kstrtou16(name + 5, 0, &tpgt)) + return ERR_PTR(-EINVAL); + ret = -ENODEV; + mutex_lock(&tpg_instances_lock); + for (i = 0; i < TPG_INSTANCES; ++i) + if (tpg_instances[i].func_inst && !tpg_instances[i].tpg) + break; + if (i == TPG_INSTANCES) + goto unlock_inst; + + opts = container_of(tpg_instances[i].func_inst, struct f_tcm_opts, + func_inst); + if (!READ_ONCE(opts->ready)) + goto unlock_inst; + + if (opts->has_dep) { + if (!try_module_get(opts->dependent)) + goto unlock_inst; + } else { + /* + * configfs_depend_item_unlocked() may acquire the configfs + * root inode lock when the target belongs to a different + * subsystem. Calling it under dep_lock would create a + * circular dependency: + * dep_lock -> configfs inode lock -> su_mutex -> dep_lock + */ + ret = configfs_depend_item_unlocked( + wwn->wwn_group.cg_subsys, + &opts->func_inst.group.cg_item); + if (ret) + goto unlock_inst; + } + + tpg = kzalloc_obj(struct usbg_tpg); + ret = -ENOMEM; + if (!tpg) + goto unref_dep; + mutex_init(&tpg->tpg_mutex); + atomic_set(&tpg->tpg_port_count, 0); + tpg->workqueue = alloc_workqueue("tcm_usb_gadget", + WQ_UNBOUND, WQ_UNBOUND_MAX_ACTIVE); + if (!tpg->workqueue) + goto free_tpg; + + tpg->tport = tport; + tpg->tport_tpgt = tpgt; + + /* + * SPC doesn't assign a protocol identifier for USB-SCSI, so we + * pretend to be SAS.. + */ + ret = core_tpg_register(wwn, &tpg->se_tpg, SCSI_PROTOCOL_SAS); + if (ret < 0) + goto free_workqueue; + + tpg_instances[i].tpg = tpg; + tpg->fi = tpg_instances[i].func_inst; + mutex_unlock(&tpg_instances_lock); + return &tpg->se_tpg; + +free_workqueue: + destroy_workqueue(tpg->workqueue); +free_tpg: + kfree(tpg); +unref_dep: + if (opts->has_dep) + module_put(opts->dependent); + else + configfs_undepend_item_unlocked(&opts->func_inst.group.cg_item); +unlock_inst: + mutex_unlock(&tpg_instances_lock); + + return ERR_PTR(ret); +} + +static int tcm_usbg_drop_nexus(struct usbg_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); + unsigned i; + struct f_tcm_opts *opts; + + tcm_usbg_drop_nexus(tpg); + core_tpg_deregister(se_tpg); + destroy_workqueue(tpg->workqueue); + + mutex_lock(&tpg_instances_lock); + for (i = 0; i < TPG_INSTANCES; ++i) + if (tpg_instances[i].tpg == tpg) + break; + if (i < TPG_INSTANCES) { + tpg_instances[i].tpg = NULL; + opts = container_of(tpg_instances[i].func_inst, + struct f_tcm_opts, func_inst); + mutex_lock(&opts->dep_lock); + if (opts->has_dep) + module_put(opts->dependent); + else + configfs_undepend_item_unlocked( + &opts->func_inst.group.cg_item); + mutex_unlock(&opts->dep_lock); + } + mutex_unlock(&tpg_instances_lock); + + kfree(tpg); +} + +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_obj(struct usbg_tport); + if (!(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_version_show(struct config_item *item, char *page) +{ + return sprintf(page, "usb-gadget fabric module\n"); +} + +CONFIGFS_ATTR_RO(usbg_wwn_, version); + +static struct configfs_attribute *usbg_wwn_attrs[] = { + &usbg_wwn_attr_version, + NULL, +}; + +static int usbg_attach(struct usbg_tpg *); +static void usbg_detach(struct usbg_tpg *); + +static int usbg_enable_tpg(struct se_portal_group *se_tpg, bool enable) +{ + struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); + int ret = 0; + + if (enable) + ret = usbg_attach(tpg); + else + usbg_detach(tpg); + if (ret) + return ret; + + tpg->gadget_connect = enable; + + return 0; +} + +static ssize_t tcm_usbg_tpg_nexus_show(struct config_item *item, char *page) +{ + struct se_portal_group *se_tpg = to_tpg(item); + 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 = sysfs_emit(page, "%s\n", + tv_nexus->tvn_se_sess->se_node_acl->initiatorname); +out: + mutex_unlock(&tpg->tpg_mutex); + return ret; +} + +static int usbg_alloc_sess_cb(struct se_portal_group *se_tpg, + struct se_session *se_sess, void *p) +{ + struct usbg_tpg *tpg = container_of(se_tpg, + struct usbg_tpg, se_tpg); + + tpg->tpg_nexus = p; + return 0; +} + +static int tcm_usbg_make_nexus(struct usbg_tpg *tpg, char *name) +{ + struct tcm_usbg_nexus *tv_nexus; + int ret = 0; + + mutex_lock(&tpg->tpg_mutex); + if (tpg->tpg_nexus) { + ret = -EEXIST; + pr_debug("tpg->tpg_nexus already exists\n"); + goto out_unlock; + } + + tv_nexus = kzalloc_obj(*tv_nexus); + if (!tv_nexus) { + ret = -ENOMEM; + goto out_unlock; + } + + tv_nexus->tvn_se_sess = target_setup_session(&tpg->se_tpg, + USB_G_DEFAULT_SESSION_TAGS, + sizeof(struct usbg_cmd), + TARGET_PROT_NORMAL, name, + tv_nexus, usbg_alloc_sess_cb); + if (IS_ERR(tv_nexus->tvn_se_sess)) { +#define MAKE_NEXUS_MSG "core_tpg_check_initiator_node_acl() failed for %s\n" + pr_debug(MAKE_NEXUS_MSG, name); +#undef MAKE_NEXUS_MSG + ret = PTR_ERR(tv_nexus->tvn_se_sess); + kfree(tv_nexus); + } + +out_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; +#define MSG "Unable to remove Host I_T Nexus with active TPG port count: %d\n" + pr_err(MSG, atomic_read(&tpg->tpg_port_count)); +#undef MSG + 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 + */ + target_remove_session(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_nexus_store(struct config_item *item, + const char *page, size_t count) +{ + struct se_portal_group *se_tpg = to_tpg(item); + 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) { + +#define NEXUS_STORE_MSG "Emulated NAA Sas Address: %s, exceeds max: %d\n" + pr_err(NEXUS_STORE_MSG, page, USBG_NAMELEN); +#undef NEXUS_STORE_MSG + 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[0]); + if (ret < 0) + return ret; + return count; +} + +CONFIGFS_ATTR(tcm_usbg_tpg_, nexus); + +static struct configfs_attribute *usbg_base_attrs[] = { + &tcm_usbg_tpg_attr_nexus, + 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(); + 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(); +} + +static int usbg_check_stop_free(struct se_cmd *se_cmd) +{ + return target_put_sess_cmd(se_cmd); +} + +static const struct target_core_fabric_ops usbg_ops = { + .module = THIS_MODULE, + .fabric_name = "usb_gadget", + .tpg_get_wwn = usbg_get_fabric_wwn, + .tpg_get_tag = usbg_get_tag, + .tpg_check_demo_mode = usbg_check_true, + .release_cmd = usbg_release_cmd, + .sess_get_initiator_sid = NULL, + .write_pending = usbg_send_write_request, + .queue_data_in = usbg_send_read_response, + .queue_status = usbg_send_status_response, + .queue_tm_rsp = usbg_queue_tm_rsp, + .aborted_task = usbg_aborted_task, + .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_enable_tpg = usbg_enable_tpg, + .fabric_drop_tpg = usbg_drop_tpg, + .fabric_post_link = usbg_port_link, + .fabric_post_unlink = usbg_port_unlink, + .fabric_init_nodeacl = usbg_init_nodeacl, + + .tfc_wwn_attrs = usbg_wwn_attrs, + .tfc_tpg_base_attrs = usbg_base_attrs, + + .default_compl_type = TARGET_QUEUE_COMPL, + .default_submit_type = TARGET_DIRECT_SUBMIT, + .direct_submit_supp = 1, +}; + +/* 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 = 15, + .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 = 15, +}; + +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, + .bMaxBurst = 15, + .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, + .bMaxBurst = 15, +}; + +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, +}; + +static struct usb_string tcm_us_strings[] = { + [USB_G_STR_INT_UAS].s = "USB Attached SCSI", + [USB_G_STR_INT_BBB].s = "Bulk Only Transport", + { }, +}; + +static struct usb_gadget_strings tcm_stringtab = { + .language = 0x0409, + .strings = tcm_us_strings, +}; + +static struct usb_gadget_strings *tcm_strings[] = { + &tcm_stringtab, + NULL, +}; + +static int tcm_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_uas *fu = to_f_uas(f); + struct usb_string *us; + struct usb_gadget *gadget = c->cdev->gadget; + struct usb_ep *ep; + struct f_tcm_opts *opts; + int iface; + int ret; + + opts = container_of(f->fi, struct f_tcm_opts, func_inst); + + mutex_lock(&opts->dep_lock); + if (!opts->can_attach) { + mutex_unlock(&opts->dep_lock); + return -ENODEV; + } + mutex_unlock(&opts->dep_lock); + us = usb_gstrings_attach(c->cdev, tcm_strings, + ARRAY_SIZE(tcm_us_strings)); + if (IS_ERR(us)) + return PTR_ERR(us); + bot_intf_desc.iInterface = us[USB_G_STR_INT_BBB].id; + uasp_intf_desc.iInterface = us[USB_G_STR_INT_UAS].id; + + 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(gadget, &uasp_fs_bi_desc); + if (!ep) + goto ep_fail; + + fu->ep_in = ep; + + ep = usb_ep_autoconfig(gadget, &uasp_fs_bo_desc); + if (!ep) + goto ep_fail; + fu->ep_out = ep; + + ep = usb_ep_autoconfig(gadget, &uasp_fs_status_desc); + if (!ep) + goto ep_fail; + fu->ep_status = ep; + + ep = usb_ep_autoconfig(gadget, &uasp_fs_cmd_desc); + if (!ep) + goto ep_fail; + fu->ep_cmd = ep; + + /* Assume endpoint addresses are the same for both speeds */ + uasp_bi_desc.bEndpointAddress = uasp_fs_bi_desc.bEndpointAddress; + uasp_bo_desc.bEndpointAddress = uasp_fs_bo_desc.bEndpointAddress; + uasp_status_desc.bEndpointAddress = + uasp_fs_status_desc.bEndpointAddress; + uasp_cmd_desc.bEndpointAddress = uasp_fs_cmd_desc.bEndpointAddress; + + uasp_ss_bi_desc.bEndpointAddress = uasp_fs_bi_desc.bEndpointAddress; + uasp_ss_bo_desc.bEndpointAddress = uasp_fs_bo_desc.bEndpointAddress; + uasp_ss_status_desc.bEndpointAddress = + uasp_fs_status_desc.bEndpointAddress; + uasp_ss_cmd_desc.bEndpointAddress = uasp_fs_cmd_desc.bEndpointAddress; + + ret = usb_assign_descriptors(f, uasp_fs_function_desc, + uasp_hs_function_desc, uasp_ss_function_desc, + uasp_ss_function_desc); + if (ret) + goto ep_fail; + + return 0; +ep_fail: + pr_err("Can't claim all required eps\n"); + + return -ENOTSUPP; +} + +static void tcm_cleanup_old_alt(struct f_uas *fu) +{ + 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 void tcm_delayed_set_alt_done(struct f_uas *fu) +{ + unsigned long flags; + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_IDLE; + fu->delayed_set_alt_cancel = false; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); +} + +static bool tcm_delayed_set_alt_cancelled(struct f_uas *fu) +{ + bool cancelled; + unsigned long flags; + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + cancelled = fu->delayed_set_alt_cancel; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + + return cancelled; +} + +static bool tcm_complete_delayed_status(struct f_uas *fu) +{ + struct usb_composite_dev *cdev = fu->function.config->cdev; + struct usb_request *req = cdev->req; + unsigned long cdev_flags; + bool cancelled; + int ret; + + spin_lock_irqsave(&cdev->lock, cdev_flags); + spin_lock(&fu->delayed_set_alt_lock); + cancelled = fu->delayed_set_alt_cancel; + if (!cancelled) { + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_IDLE; + fu->delayed_set_alt_cancel = false; + } + spin_unlock(&fu->delayed_set_alt_lock); + + if (cancelled) { + spin_unlock_irqrestore(&cdev->lock, cdev_flags); + return false; + } + + if (cdev->delayed_status == 0) { + WARN(cdev, "%s: Unexpected call\n", __func__); + } else if (--cdev->delayed_status == 0) { + req->length = 0; + req->context = cdev; + ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (ret == 0) { + cdev->setup_pending = true; + } else { + req->status = 0; + req->complete(cdev->gadget->ep0, req); + } + } + + spin_unlock_irqrestore(&cdev->lock, cdev_flags); + + return true; +} + +static bool tcm_cancel_delayed_set_alt(struct f_uas *fu) +{ + bool cleanup = false; + bool cancel = false; + unsigned long flags; + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + switch (fu->delayed_set_alt_state) { + case USBG_DELAYED_SET_ALT_IDLE: + cleanup = true; + break; + case USBG_DELAYED_SET_ALT_QUEUED: + case USBG_DELAYED_SET_ALT_RUNNING: + fu->delayed_set_alt_cancel = true; + cancel = true; + break; + } + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + + if (cancel && cancel_work(&fu->delayed_set_alt)) { + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + if (fu->delayed_set_alt_state == USBG_DELAYED_SET_ALT_QUEUED) { + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_IDLE; + fu->delayed_set_alt_cancel = false; + cleanup = true; + } + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + } + + return cleanup; +} + +static void tcm_cancel_delayed_set_alt_sync(struct f_uas *fu) +{ + unsigned long flags; + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + if (fu->delayed_set_alt_state != USBG_DELAYED_SET_ALT_IDLE) + fu->delayed_set_alt_cancel = true; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + + cancel_work_sync(&fu->delayed_set_alt); + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_IDLE; + fu->delayed_set_alt_cancel = false; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); +} + +static void tcm_delayed_set_alt(struct work_struct *wq) +{ + struct f_uas *fu = container_of(wq, struct f_uas, delayed_set_alt); + unsigned long flags; + unsigned int alt; + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + if (fu->delayed_set_alt_state != USBG_DELAYED_SET_ALT_QUEUED) { + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + return; + } + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_RUNNING; + alt = fu->delayed_alt; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + + tcm_cleanup_old_alt(fu); + + if (tcm_delayed_set_alt_cancelled(fu)) + goto out_done; + + if (alt == USB_G_ALT_INT_BBB) + bot_set_alt(fu); + else if (alt == USB_G_ALT_INT_UAS) + uasp_set_alt(fu); + + if (tcm_complete_delayed_status(fu)) + return; + + tcm_cleanup_old_alt(fu); +out_done: + tcm_delayed_set_alt_done(fu); +} + +static int tcm_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_uas *fu = to_f_uas(f); + + if (fu->iface != intf) + return -EOPNOTSUPP; + + if (fu->flags & USBG_IS_BOT) + return USB_G_ALT_INT_BBB; + else if (fu->flags & USBG_IS_UAS) + return USB_G_ALT_INT_UAS; + + return -EOPNOTSUPP; +} + +static int tcm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_uas *fu = to_f_uas(f); + + if (fu->iface != intf) + return -EOPNOTSUPP; + + if ((alt == USB_G_ALT_INT_BBB) || (alt == USB_G_ALT_INT_UAS)) { + unsigned long flags; + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + if (fu->delayed_set_alt_state != USBG_DELAYED_SET_ALT_IDLE) { + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, + flags); + return -EBUSY; + } + fu->delayed_alt = alt; + fu->delayed_set_alt_cancel = false; + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_QUEUED; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + + schedule_work(&fu->delayed_set_alt); + return USB_GADGET_DELAYED_STATUS; + } + return -EOPNOTSUPP; +} + +static void tcm_disable(struct usb_function *f) +{ + struct f_uas *fu = to_f_uas(f); + + if (tcm_cancel_delayed_set_alt(fu)) + tcm_cleanup_old_alt(fu); +} + +static int tcm_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 inline struct f_tcm_opts *to_f_tcm_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_tcm_opts, + func_inst.group); +} + +static void tcm_attr_release(struct config_item *item) +{ + struct f_tcm_opts *opts = to_f_tcm_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static const struct configfs_item_operations tcm_item_ops = { + .release = tcm_attr_release, +}; + +static const struct config_item_type tcm_func_type = { + .ct_item_ops = &tcm_item_ops, + .ct_owner = THIS_MODULE, +}; + +static void tcm_free_inst(struct usb_function_instance *f) +{ + struct f_tcm_opts *opts; + unsigned i; + + opts = container_of(f, struct f_tcm_opts, func_inst); + + mutex_lock(&tpg_instances_lock); + for (i = 0; i < TPG_INSTANCES; ++i) + if (tpg_instances[i].func_inst == f) + break; + if (i < TPG_INSTANCES) + tpg_instances[i].func_inst = NULL; + mutex_unlock(&tpg_instances_lock); + + kfree(opts); +} + +static int tcm_register_callback(struct usb_function_instance *f) +{ + struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst); + + mutex_lock(&opts->dep_lock); + opts->can_attach = true; + mutex_unlock(&opts->dep_lock); + + return 0; +} + +static void tcm_unregister_callback(struct usb_function_instance *f) +{ + struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst); + + mutex_lock(&opts->dep_lock); + unregister_gadget_item(opts-> + func_inst.group.cg_item.ci_parent->ci_parent); + opts->can_attach = false; + mutex_unlock(&opts->dep_lock); +} + +static int usbg_attach(struct usbg_tpg *tpg) +{ + struct usb_function_instance *f = tpg->fi; + struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst); + + if (opts->tcm_register_callback) + return opts->tcm_register_callback(f); + + return 0; +} + +static void usbg_detach(struct usbg_tpg *tpg) +{ + struct usb_function_instance *f = tpg->fi; + struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst); + + if (opts->tcm_unregister_callback) + opts->tcm_unregister_callback(f); +} + +static int tcm_set_name(struct usb_function_instance *f, const char *name) +{ + struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst); + + pr_debug("tcm: Activating %s\n", name); + + WRITE_ONCE(opts->ready, true); + + return 0; +} + +static struct usb_function_instance *tcm_alloc_inst(void) +{ + struct f_tcm_opts *opts; + int i; + + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + + mutex_lock(&tpg_instances_lock); + for (i = 0; i < TPG_INSTANCES; ++i) + if (!tpg_instances[i].func_inst) + break; + + if (i == TPG_INSTANCES) { + mutex_unlock(&tpg_instances_lock); + kfree(opts); + return ERR_PTR(-EBUSY); + } + tpg_instances[i].func_inst = &opts->func_inst; + mutex_unlock(&tpg_instances_lock); + + mutex_init(&opts->dep_lock); + opts->func_inst.set_inst_name = tcm_set_name; + opts->func_inst.free_func_inst = tcm_free_inst; + opts->tcm_register_callback = tcm_register_callback; + opts->tcm_unregister_callback = tcm_unregister_callback; + + config_group_init_type_name(&opts->func_inst.group, "", + &tcm_func_type); + + return &opts->func_inst; +} + +static void tcm_free(struct usb_function *f) +{ + struct f_uas *tcm = to_f_uas(f); + + tcm_cancel_delayed_set_alt_sync(tcm); + kfree(tcm); +} + +static void tcm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_uas *fu = to_f_uas(f); + + tcm_cancel_delayed_set_alt_sync(fu); + tcm_cleanup_old_alt(fu); + usb_free_all_descriptors(f); +} + +static struct usb_function *tcm_alloc(struct usb_function_instance *fi) +{ + struct f_uas *fu; + unsigned i; + + mutex_lock(&tpg_instances_lock); + for (i = 0; i < TPG_INSTANCES; ++i) + if (tpg_instances[i].func_inst == fi) + break; + if (i == TPG_INSTANCES) { + mutex_unlock(&tpg_instances_lock); + return ERR_PTR(-ENODEV); + } + + fu = kzalloc_obj(*fu); + if (!fu) { + mutex_unlock(&tpg_instances_lock); + return ERR_PTR(-ENOMEM); + } + + fu->function.name = "Target Function"; + fu->function.bind = tcm_bind; + fu->function.unbind = tcm_unbind; + fu->function.set_alt = tcm_set_alt; + fu->function.get_alt = tcm_get_alt; + fu->function.setup = tcm_setup; + fu->function.disable = tcm_disable; + fu->function.free_func = tcm_free; + fu->tpg = tpg_instances[i].tpg; + INIT_WORK(&fu->delayed_set_alt, tcm_delayed_set_alt); + spin_lock_init(&fu->delayed_set_alt_lock); + + hash_init(fu->stream_hash); + mutex_unlock(&tpg_instances_lock); + + return &fu->function; +} + +DECLARE_USB_FUNCTION(tcm, tcm_alloc_inst, tcm_alloc); + +static int __init tcm_init(void) +{ + int ret; + + ret = usb_function_register(&tcmusb_func); + if (ret) + return ret; + + ret = target_register_template(&usbg_ops); + if (ret) + usb_function_unregister(&tcmusb_func); + + return ret; +} +module_init(tcm_init); + +static void __exit tcm_exit(void) +{ + target_unregister_template(&usbg_ops); + usb_function_unregister(&tcmusb_func); +} +module_exit(tcm_exit); + +MODULE_DESCRIPTION("Target based USB-Gadget"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sebastian Andrzej Siewior"); diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c new file mode 100644 index 000000000..7a81cd176 --- /dev/null +++ b/drivers/usb/gadget/function/f_uac1.c @@ -0,0 +1,1864 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_uac1.c -- USB Audio Class 1.0 Function (using u_audio API) + * + * Copyright (C) 2016 Ruslan Bilovol + * Copyright (C) 2021 Julian Scheel + * + * 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. + * + * This file is based on f_uac1.c which is + * Copyright (C) 2008 Bryan Wu + * Copyright (C) 2008 Analog Devices, Inc + */ + +#include +#include + +#include "u_audio.h" +#include "u_uac1.h" + +/* UAC1 spec: 3.7.2.3 Audio Channel Cluster Format */ +#define UAC1_CHANNEL_MASK 0x0FFF + +#define USB_OUT_FU_ID (out_feature_unit_desc->bUnitID) +#define USB_IN_FU_ID (in_feature_unit_desc->bUnitID) + +#define EPIN_EN(_opts) ((_opts)->p_chmask != 0) +#define EPOUT_EN(_opts) ((_opts)->c_chmask != 0) +#define FUIN_EN(_opts) ((_opts)->p_mute_present \ + || (_opts)->p_volume_present) +#define FUOUT_EN(_opts) ((_opts)->c_mute_present \ + || (_opts)->c_volume_present) + +struct f_uac1 { + struct g_audio g_audio; + u8 ac_intf, as_in_intf, as_out_intf; + u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */ + + struct usb_ctrlrequest setup_cr; /* will be used in data stage */ + + /* Interrupt IN endpoint of AC interface */ + struct usb_ep *int_ep; + atomic_t int_count; + int ctl_id; /* EP id */ + int c_srate; /* current capture srate */ + int p_srate; /* current playback prate */ +}; + +static inline struct f_uac1 *func_to_uac1(struct usb_function *f) +{ + return container_of(f, struct f_uac1, g_audio.func); +} + +static inline struct f_uac1_opts *g_audio_to_uac1_opts(struct g_audio *audio) +{ + return container_of(audio->func.fi, struct f_uac1_opts, func_inst); +} + +/* + * DESCRIPTORS ... most are static, but strings and full + * configuration descriptors are built on demand. + */ + +/* + * We have three interfaces - one AudioControl and two AudioStreaming + * + * The driver implements a simple UAC_1 topology. + * USB-OUT -> IT_1 -> OT_2 -> ALSA_Capture + * ALSA_Playback -> IT_3 -> OT_4 -> USB-IN + */ + +/* 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 = DYNAMIC */ + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, +}; + +/* B.3.2 Class-Specific AC Interface Descriptor */ +static struct uac1_ac_header_descriptor *ac_header_desc; + +static struct uac_input_terminal_descriptor usb_out_it_desc = { + .bLength = UAC_DT_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + /* .bTerminalID = DYNAMIC */ + .wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING), + .bAssocTerminal = 0, + .wChannelConfig = cpu_to_le16(0x3), +}; + +static struct uac1_output_terminal_descriptor io_out_ot_desc = { + .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + /* .bTerminalID = DYNAMIC */ + .wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_SPEAKER), + .bAssocTerminal = 0, + /* .bSourceID = DYNAMIC */ +}; + +static struct uac_input_terminal_descriptor io_in_it_desc = { + .bLength = UAC_DT_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + /* .bTerminalID = DYNAMIC */ + .wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_MICROPHONE), + .bAssocTerminal = 0, + .wChannelConfig = cpu_to_le16(0x3), +}; + +static struct uac1_output_terminal_descriptor usb_in_ot_desc = { + .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + /* .bTerminalID = DYNAMIC */ + .wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING), + .bAssocTerminal = 0, + /* .bSourceID = DYNAMIC */ +}; + +static struct uac_feature_unit_descriptor *in_feature_unit_desc; +static struct uac_feature_unit_descriptor *out_feature_unit_desc; + +/* AC IN Interrupt Endpoint */ +static struct usb_endpoint_descriptor ac_int_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(2), + .bInterval = 4, +}; + +/* B.4.1 Standard AS Interface Descriptor */ +static struct usb_interface_descriptor as_out_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_out_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, +}; + +static struct usb_interface_descriptor as_in_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_in_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_out_header_desc = { + .bLength = UAC_DT_AS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_AS_GENERAL, + /* .bTerminalLink = DYNAMIC */ + .bDelay = 1, + .wFormatTag = cpu_to_le16(UAC_FORMAT_TYPE_I_PCM), +}; + +static struct uac1_as_header_descriptor as_in_header_desc = { + .bLength = UAC_DT_AS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_AS_GENERAL, + /* .bTerminalLink = DYNAMIC */ + .bDelay = 1, + .wFormatTag = cpu_to_le16(UAC_FORMAT_TYPE_I_PCM), +}; + +DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(UAC_MAX_RATES); +#define uac_format_type_i_discrete_descriptor \ + uac_format_type_i_discrete_descriptor_##UAC_MAX_RATES + +static struct uac_format_type_i_discrete_descriptor as_out_type_i_desc = { + .bLength = 0, /* filled on rate setup */ + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FORMAT_TYPE, + .bFormatType = UAC_FORMAT_TYPE_I, + .bSubframeSize = 2, + .bBitResolution = 16, + .bSamFreqType = 0, /* filled on rate setup */ +}; + +/* 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 = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct uac_iso_endpoint_descriptor as_iso_out_desc = { + .bLength = UAC_ISO_ENDPOINT_DESC_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = UAC_EP_GENERAL, + .bmAttributes = 1, + .bLockDelayUnits = 1, + .wLockDelay = cpu_to_le16(1), +}; + +static struct uac_format_type_i_discrete_descriptor as_in_type_i_desc = { + .bLength = 0, /* filled on rate setup */ + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FORMAT_TYPE, + .bFormatType = UAC_FORMAT_TYPE_I, + .bSubframeSize = 2, + .bBitResolution = 16, + .bSamFreqType = 0, /* filled on rate setup */ +}; + +/* Standard ISO OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor as_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_ASYNC + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +/* 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 = 0, + .wLockDelay = 0, +}; + +static struct usb_descriptor_header *f_audio_desc[] = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&usb_out_it_desc, + (struct usb_descriptor_header *)&io_out_ot_desc, + (struct usb_descriptor_header *)&out_feature_unit_desc, + + (struct usb_descriptor_header *)&io_in_it_desc, + (struct usb_descriptor_header *)&usb_in_ot_desc, + (struct usb_descriptor_header *)&in_feature_unit_desc, + + (struct usb_descriptor_header *)&ac_int_ep_desc, + + (struct usb_descriptor_header *)&as_out_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_out_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_out_header_desc, + + (struct usb_descriptor_header *)&as_out_type_i_desc, + + (struct usb_descriptor_header *)&as_out_ep_desc, + (struct usb_descriptor_header *)&as_iso_out_desc, + + (struct usb_descriptor_header *)&as_in_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_in_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_in_header_desc, + + (struct usb_descriptor_header *)&as_in_type_i_desc, + + (struct usb_descriptor_header *)&as_in_ep_desc, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + +/* Standard ISO OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor ss_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 = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +static struct usb_ss_ep_comp_descriptor ss_as_out_ep_desc_comp = { + .bLength = sizeof(ss_as_out_ep_desc_comp), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = 0, + /* wBytesPerInterval = DYNAMIC */ +}; + +/* Standard ISO OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor ss_as_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_ASYNC + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +static struct usb_ss_ep_comp_descriptor ss_as_in_ep_desc_comp = { + .bLength = sizeof(ss_as_in_ep_desc_comp), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = 0, + /* wBytesPerInterval = DYNAMIC */ +}; + +static struct usb_descriptor_header *f_audio_ss_desc[] = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&usb_out_it_desc, + (struct usb_descriptor_header *)&io_out_ot_desc, + (struct usb_descriptor_header *)&io_in_it_desc, + (struct usb_descriptor_header *)&usb_in_ot_desc, + + (struct usb_descriptor_header *)&as_out_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_out_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_out_header_desc, + + (struct usb_descriptor_header *)&as_out_type_i_desc, + + //(struct usb_descriptor_header *)&as_out_ep_desc, + (struct usb_descriptor_header *)&ss_as_out_ep_desc, + (struct usb_descriptor_header *)&ss_as_out_ep_desc_comp, + (struct usb_descriptor_header *)&as_iso_out_desc, + + (struct usb_descriptor_header *)&as_in_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_in_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_in_header_desc, + + (struct usb_descriptor_header *)&as_in_type_i_desc, + + //(struct usb_descriptor_header *)&as_in_ep_desc, + (struct usb_descriptor_header *)&ss_as_in_ep_desc, + (struct usb_descriptor_header *)&ss_as_in_ep_desc_comp, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + +enum { + STR_AC_IF, + STR_USB_OUT_IT, + STR_USB_OUT_IT_CH_NAMES, + STR_IO_OUT_OT, + STR_IO_IN_IT, + STR_IO_IN_IT_CH_NAMES, + STR_USB_IN_OT, + STR_FU_IN, + STR_FU_OUT, + STR_AS_OUT_IF_ALT0, + STR_AS_OUT_IF_ALT1, + STR_AS_IN_IF_ALT0, + STR_AS_IN_IF_ALT1, + NUM_STR_DESCRIPTORS, +}; + +static struct usb_string strings_uac1[NUM_STR_DESCRIPTORS + 1] = {}; + +static struct usb_gadget_strings str_uac1 = { + .language = 0x0409, /* en-us */ + .strings = strings_uac1, +}; + +static struct usb_gadget_strings *uac1_strings[] = { + &str_uac1, + NULL, +}; + +/* + * This function is an ALSA sound card following USB Audio Class Spec 1.0. + */ + +static void uac_cs_attr_sample_rate(struct usb_ep *ep, struct usb_request *req) +{ + struct usb_function *fn = ep->driver_data; + struct usb_composite_dev *cdev = fn->config->cdev; + struct g_audio *agdev = func_to_g_audio(fn); + struct f_uac1 *uac1 = func_to_uac1(fn); + u8 *buf = (u8 *)req->buf; + u32 val = 0; + + if (req->actual != 3) { + WARN(cdev, "Invalid data size for UAC_EP_CS_ATTR_SAMPLE_RATE.\n"); + return; + } + + val = buf[0] | (buf[1] << 8) | (buf[2] << 16); + if (uac1->ctl_id == (USB_DIR_IN | 2)) { + uac1->p_srate = val; + u_audio_set_playback_srate(agdev, uac1->p_srate); + } else if (uac1->ctl_id == (USB_DIR_OUT | 1)) { + uac1->c_srate = val; + u_audio_set_capture_srate(agdev, uac1->c_srate); + } +} + +static void audio_notify_complete(struct usb_ep *_ep, struct usb_request *req) +{ + struct g_audio *audio = req->context; + struct f_uac1 *uac1 = func_to_uac1(&audio->func); + + atomic_dec(&uac1->int_count); + kfree(req->buf); + usb_ep_free_request(_ep, req); +} + +static int audio_notify(struct g_audio *audio, int unit_id, int cs) +{ + struct f_uac1 *uac1 = func_to_uac1(&audio->func); + struct usb_request *req; + struct uac1_status_word *msg; + int ret; + + if (!uac1->int_ep->enabled) + return 0; + + if (atomic_inc_return(&uac1->int_count) > UAC1_DEF_INT_REQ_NUM) { + atomic_dec(&uac1->int_count); + return 0; + } + + req = usb_ep_alloc_request(uac1->int_ep, GFP_ATOMIC); + if (req == NULL) { + ret = -ENOMEM; + goto err_dec_int_count; + } + + msg = kmalloc_obj(*msg, GFP_ATOMIC); + if (msg == NULL) { + ret = -ENOMEM; + goto err_free_request; + } + + msg->bStatusType = UAC1_STATUS_TYPE_IRQ_PENDING + | UAC1_STATUS_TYPE_ORIG_AUDIO_CONTROL_IF; + msg->bOriginator = unit_id; + + req->length = sizeof(*msg); + req->buf = msg; + req->context = audio; + req->complete = audio_notify_complete; + + ret = usb_ep_queue(uac1->int_ep, req, GFP_ATOMIC); + + if (ret) + goto err_free_msg; + + return 0; + +err_free_msg: + kfree(msg); +err_free_request: + usb_ep_free_request(uac1->int_ep, req); +err_dec_int_count: + atomic_dec(&uac1->int_count); + + return ret; +} + +static int +in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + 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 ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_MUTE) { + unsigned int mute; + + u_audio_get_mute(audio, is_playback, &mute); + + *(u8 *)req->buf = mute; + value = min_t(unsigned int, w_length, 1); + } else if (control_selector == UAC_FU_VOLUME) { + __le16 c; + s16 volume; + + u_audio_get_volume(audio, is_playback, &volume); + + c = cpu_to_le16(volume); + + value = min_t(unsigned int, w_length, sizeof(c)); + memcpy(req->buf, &c, value); + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + + return value; +} + +static int +in_rq_min(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + 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 ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_VOLUME) { + __le16 r; + s16 min_db; + + if (is_playback) + min_db = opts->p_volume_min; + else + min_db = opts->c_volume_min; + + r = cpu_to_le16(min_db); + + value = min_t(unsigned int, w_length, sizeof(r)); + memcpy(req->buf, &r, value); + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + + return value; +} + +static int +in_rq_max(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + 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 ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_VOLUME) { + __le16 r; + s16 max_db; + + if (is_playback) + max_db = opts->p_volume_max; + else + max_db = opts->c_volume_max; + + r = cpu_to_le16(max_db); + + value = min_t(unsigned int, w_length, sizeof(r)); + memcpy(req->buf, &r, value); + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + + return value; +} + +static int +in_rq_res(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + 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 ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_VOLUME) { + __le16 r; + s16 res_db; + + if (is_playback) + res_db = opts->p_volume_res; + else + res_db = opts->c_volume_res; + + r = cpu_to_le16(res_db); + + value = min_t(unsigned int, w_length, sizeof(r)); + memcpy(req->buf, &r, value); + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + + return value; +} + +static void +out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct g_audio *audio = req->context; + struct usb_composite_dev *cdev = audio->func.config->cdev; + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + struct f_uac1 *uac1 = func_to_uac1(&audio->func); + struct usb_ctrlrequest *cr = &uac1->setup_cr; + 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; + + if (req->status != 0) { + dev_dbg(&cdev->gadget->dev, "completion err %d\n", req->status); + return; + } + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_MUTE) { + u8 mute = *(u8 *)req->buf; + + u_audio_set_mute(audio, is_playback, mute); + + return; + } else if (control_selector == UAC_FU_VOLUME) { + __le16 *c = req->buf; + s16 volume; + + volume = le16_to_cpu(*c); + u_audio_set_volume(audio, is_playback, volume); + + return; + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + usb_ep_set_halt(ep); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + usb_ep_set_halt(ep); + + } +} + +static int +out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + struct f_uac1 *uac1 = func_to_uac1(&audio->func); + 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; + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + memcpy(&uac1->setup_cr, cr, sizeof(*cr)); + req->context = audio; + req->complete = out_rq_cur_complete; + + return w_length; + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + return -EOPNOTSUPP; +} + +static int ac_rq_in(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: + return in_rq_cur(f, ctrl); + case UAC_GET_MIN: + return in_rq_min(f, ctrl); + case UAC_GET_MAX: + return in_rq_max(f, ctrl); + case UAC_GET_RES: + return in_rq_res(f, ctrl); + case UAC_GET_MEM: + break; + case UAC_GET_STAT: + value = len; + break; + default: + break; + } + + return value; +} + +static int audio_set_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = f->config->cdev->req; + struct f_uac1 *uac1 = func_to_uac1(f); + 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); + u8 cs = w_value >> 8; + + 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: { + if (cs == UAC_EP_CS_ATTR_SAMPLE_RATE) { + cdev->gadget->ep0->driver_data = f; + uac1->ctl_id = ep; + req->complete = uac_cs_attr_sample_rate; + } + 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; + struct usb_request *req = f->config->cdev->req; + struct f_uac1 *uac1 = func_to_uac1(f); + u8 *buf = (u8 *)req->buf; + int value = -EOPNOTSUPP; + u8 ep = le16_to_cpu(ctrl->wIndex); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 cs = w_value >> 8; + u32 val = 0; + + 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: { + if (cs == UAC_EP_CS_ATTR_SAMPLE_RATE) { + if (ep == (USB_DIR_IN | 2)) + val = uac1->p_srate; + else if (ep == (USB_DIR_OUT | 1)) + val = uac1->c_srate; + buf[2] = (val >> 16) & 0xff; + buf[1] = (val >> 8) & 0xff; + buf[0] = val & 0xff; + } + value = len; + break; + } + 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_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; + case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE: + if (ctrl->bRequest == UAC_SET_CUR) + value = out_rq_cur(f, ctrl); + break; + case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE: + value = ac_rq_in(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 usb_composite_dev *cdev = f->config->cdev; + struct usb_gadget *gadget = cdev->gadget; + struct device *dev = &gadget->dev; + struct g_audio *audio = func_to_g_audio(f); + struct f_uac1 *uac1 = func_to_uac1(f); + int ret = 0; + + /* No i/f has more than 2 alt settings */ + if (alt > 1) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + if (intf == uac1->ac_intf) { + /* Control I/f has only 1 AltSetting - 0 */ + if (alt) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + /* restart interrupt endpoint */ + if (uac1->int_ep) { + usb_ep_disable(uac1->int_ep); + config_ep_by_speed(gadget, &audio->func, uac1->int_ep); + usb_ep_enable(uac1->int_ep); + } + + return 0; + } + + if (intf == uac1->as_out_intf) { + uac1->as_out_alt = alt; + + if (alt) + ret = u_audio_start_capture(&uac1->g_audio); + else + u_audio_stop_capture(&uac1->g_audio); + } else if (intf == uac1->as_in_intf) { + uac1->as_in_alt = alt; + + if (alt) + ret = u_audio_start_playback(&uac1->g_audio); + else + u_audio_stop_playback(&uac1->g_audio); + } else { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + return ret; +} + +static int f_audio_get_alt(struct usb_function *f, unsigned intf) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_gadget *gadget = cdev->gadget; + struct device *dev = &gadget->dev; + struct f_uac1 *uac1 = func_to_uac1(f); + + if (intf == uac1->ac_intf) + return uac1->ac_alt; + else if (intf == uac1->as_out_intf) + return uac1->as_out_alt; + else if (intf == uac1->as_in_intf) + return uac1->as_in_alt; + else + dev_err(dev, "%s:%d Invalid Interface %d!\n", + __func__, __LINE__, intf); + + return -EINVAL; +} + + +static void f_audio_disable(struct usb_function *f) +{ + struct f_uac1 *uac1 = func_to_uac1(f); + + uac1->as_out_alt = 0; + uac1->as_in_alt = 0; + + u_audio_stop_playback(&uac1->g_audio); + u_audio_stop_capture(&uac1->g_audio); + if (uac1->int_ep) + usb_ep_disable(uac1->int_ep); +} + +static void +f_audio_suspend(struct usb_function *f) +{ + struct f_uac1 *uac1 = func_to_uac1(f); + + u_audio_suspend(&uac1->g_audio); +} + +/*-------------------------------------------------------------------------*/ +static struct uac_feature_unit_descriptor *build_fu_desc(int chmask) +{ + struct uac_feature_unit_descriptor *fu_desc; + int channels = num_channels(chmask); + int fu_desc_size = UAC_DT_FEATURE_UNIT_SIZE(channels); + + fu_desc = kzalloc(fu_desc_size, GFP_KERNEL); + if (!fu_desc) + return NULL; + + fu_desc->bLength = fu_desc_size; + fu_desc->bDescriptorType = USB_DT_CS_INTERFACE; + + fu_desc->bDescriptorSubtype = UAC_FEATURE_UNIT; + fu_desc->bControlSize = 2; + + /* bUnitID, bSourceID and bmaControls will be defined later */ + + return fu_desc; +} + +/* B.3.2 Class-Specific AC Interface Descriptor */ +static struct +uac1_ac_header_descriptor *build_ac_header_desc(struct f_uac1_opts *opts) +{ + struct uac1_ac_header_descriptor *ac_desc; + int ac_header_desc_size; + int num_ifaces = 0; + + if (EPOUT_EN(opts)) + num_ifaces++; + if (EPIN_EN(opts)) + num_ifaces++; + + ac_header_desc_size = UAC_DT_AC_HEADER_SIZE(num_ifaces); + + ac_desc = kzalloc(ac_header_desc_size, GFP_KERNEL); + if (!ac_desc) + return NULL; + + ac_desc->bLength = ac_header_desc_size; + ac_desc->bDescriptorType = USB_DT_CS_INTERFACE; + ac_desc->bDescriptorSubtype = UAC_HEADER; + ac_desc->bcdADC = cpu_to_le16(0x0100); + ac_desc->bInCollection = num_ifaces; + + /* wTotalLength and baInterfaceNr will be defined later */ + + return ac_desc; +} + +/* Use macro to overcome line length limitation */ +#define USBDHDR(p) (struct usb_descriptor_header *)(p) + +static void setup_descriptor(struct f_uac1_opts *opts) +{ + /* patch descriptors */ + int i = 1; /* ID's start with 1 */ + + if (EPOUT_EN(opts)) + usb_out_it_desc.bTerminalID = i++; + if (EPIN_EN(opts)) + io_in_it_desc.bTerminalID = i++; + if (EPOUT_EN(opts)) + io_out_ot_desc.bTerminalID = i++; + if (EPIN_EN(opts)) + usb_in_ot_desc.bTerminalID = i++; + if (FUOUT_EN(opts)) + out_feature_unit_desc->bUnitID = i++; + if (FUIN_EN(opts)) + in_feature_unit_desc->bUnitID = i++; + + if (FUIN_EN(opts)) { + usb_in_ot_desc.bSourceID = in_feature_unit_desc->bUnitID; + in_feature_unit_desc->bSourceID = io_in_it_desc.bTerminalID; + } else { + usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID; + } + if (FUOUT_EN(opts)) { + io_out_ot_desc.bSourceID = out_feature_unit_desc->bUnitID; + out_feature_unit_desc->bSourceID = usb_out_it_desc.bTerminalID; + } else { + io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID; + } + + as_out_header_desc.bTerminalLink = usb_out_it_desc.bTerminalID; + as_in_header_desc.bTerminalLink = usb_in_ot_desc.bTerminalID; + + ac_header_desc->wTotalLength = cpu_to_le16(ac_header_desc->bLength); + + if (EPIN_EN(opts)) { + u16 len = le16_to_cpu(ac_header_desc->wTotalLength); + + len += sizeof(usb_in_ot_desc); + len += sizeof(io_in_it_desc); + if (FUIN_EN(opts)) + len += in_feature_unit_desc->bLength; + ac_header_desc->wTotalLength = cpu_to_le16(len); + } + if (EPOUT_EN(opts)) { + u16 len = le16_to_cpu(ac_header_desc->wTotalLength); + + len += sizeof(usb_out_it_desc); + len += sizeof(io_out_ot_desc); + if (FUOUT_EN(opts)) + len += out_feature_unit_desc->bLength; + ac_header_desc->wTotalLength = cpu_to_le16(len); + } + + i = 0; + f_audio_desc[i++] = USBDHDR(&ac_interface_desc); + f_audio_desc[i++] = USBDHDR(ac_header_desc); + + if (EPOUT_EN(opts)) { + f_audio_desc[i++] = USBDHDR(&usb_out_it_desc); + f_audio_desc[i++] = USBDHDR(&io_out_ot_desc); + if (FUOUT_EN(opts)) + f_audio_desc[i++] = USBDHDR(out_feature_unit_desc); + } + + if (EPIN_EN(opts)) { + f_audio_desc[i++] = USBDHDR(&io_in_it_desc); + f_audio_desc[i++] = USBDHDR(&usb_in_ot_desc); + if (FUIN_EN(opts)) + f_audio_desc[i++] = USBDHDR(in_feature_unit_desc); + } + + if (FUOUT_EN(opts) || FUIN_EN(opts)) + f_audio_desc[i++] = USBDHDR(&ac_int_ep_desc); + + if (EPOUT_EN(opts)) { + f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_0_desc); + f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_1_desc); + f_audio_desc[i++] = USBDHDR(&as_out_header_desc); + f_audio_desc[i++] = USBDHDR(&as_out_type_i_desc); + f_audio_desc[i++] = USBDHDR(&as_out_ep_desc); + f_audio_desc[i++] = USBDHDR(&as_iso_out_desc); + } + if (EPIN_EN(opts)) { + f_audio_desc[i++] = USBDHDR(&as_in_interface_alt_0_desc); + f_audio_desc[i++] = USBDHDR(&as_in_interface_alt_1_desc); + f_audio_desc[i++] = USBDHDR(&as_in_header_desc); + f_audio_desc[i++] = USBDHDR(&as_in_type_i_desc); + f_audio_desc[i++] = USBDHDR(&as_in_ep_desc); + f_audio_desc[i++] = USBDHDR(&as_iso_in_desc); + } + f_audio_desc[i] = NULL; +} + +static int f_audio_validate_opts(struct g_audio *audio, struct device *dev) +{ + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + + if (!opts->p_chmask && !opts->c_chmask) { + dev_err(dev, "Error: no playback and capture channels\n"); + return -EINVAL; + } else if (opts->p_chmask & ~UAC1_CHANNEL_MASK) { + dev_err(dev, "Error: unsupported playback channels mask\n"); + return -EINVAL; + } else if (opts->c_chmask & ~UAC1_CHANNEL_MASK) { + dev_err(dev, "Error: unsupported capture channels mask\n"); + return -EINVAL; + } else if ((opts->p_ssize < 1) || (opts->p_ssize > 4)) { + dev_err(dev, "Error: incorrect playback sample size\n"); + return -EINVAL; + } else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) { + dev_err(dev, "Error: incorrect capture sample size\n"); + return -EINVAL; + } else if (!opts->p_srates[0]) { + dev_err(dev, "Error: incorrect playback sampling rate\n"); + return -EINVAL; + } else if (!opts->c_srates[0]) { + dev_err(dev, "Error: incorrect capture sampling rate\n"); + return -EINVAL; + } + + if (opts->p_volume_max <= opts->p_volume_min) { + dev_err(dev, "Error: incorrect playback volume max/min\n"); + return -EINVAL; + } else if (opts->c_volume_max <= opts->c_volume_min) { + dev_err(dev, "Error: incorrect capture volume max/min\n"); + return -EINVAL; + } else if (opts->p_volume_res <= 0) { + dev_err(dev, "Error: negative/zero playback volume resolution\n"); + return -EINVAL; + } else if (opts->c_volume_res <= 0) { + dev_err(dev, "Error: negative/zero capture volume resolution\n"); + return -EINVAL; + } + + if ((opts->p_volume_max - opts->p_volume_min) % opts->p_volume_res) { + dev_err(dev, "Error: incorrect playback volume resolution\n"); + return -EINVAL; + } else if ((opts->c_volume_max - opts->c_volume_min) % opts->c_volume_res) { + dev_err(dev, "Error: incorrect capture volume resolution\n"); + return -EINVAL; + } + + return 0; +} + +/* audio function driver setup/binding */ +static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct usb_gadget *gadget = cdev->gadget; + struct device *dev = &gadget->dev; + struct f_uac1 *uac1 = func_to_uac1(f); + struct g_audio *audio = func_to_g_audio(f); + struct f_uac1_opts *audio_opts; + struct usb_ep *ep = NULL; + struct usb_string *us; + int ba_iface_id; + int status; + int idx, i; + + status = f_audio_validate_opts(audio, dev); + if (status) + return status; + + audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst); + + strings_uac1[STR_AC_IF].s = audio_opts->function_name; + + strings_uac1[STR_USB_OUT_IT].s = audio_opts->c_it_name; + strings_uac1[STR_USB_OUT_IT_CH_NAMES].s = audio_opts->c_it_ch_name; + strings_uac1[STR_IO_OUT_OT].s = audio_opts->c_ot_name; + strings_uac1[STR_FU_OUT].s = audio_opts->c_fu_vol_name; + strings_uac1[STR_AS_OUT_IF_ALT0].s = "Playback Inactive"; + strings_uac1[STR_AS_OUT_IF_ALT1].s = "Playback Active"; + + strings_uac1[STR_IO_IN_IT].s = audio_opts->p_it_name; + strings_uac1[STR_IO_IN_IT_CH_NAMES].s = audio_opts->p_it_ch_name; + strings_uac1[STR_USB_IN_OT].s = audio_opts->p_ot_name; + strings_uac1[STR_FU_IN].s = audio_opts->p_fu_vol_name; + strings_uac1[STR_AS_IN_IF_ALT0].s = "Capture Inactive"; + strings_uac1[STR_AS_IN_IF_ALT1].s = "Capture Active"; + + us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1)); + if (IS_ERR(us)) + return PTR_ERR(us); + + ac_header_desc = build_ac_header_desc(audio_opts); + if (!ac_header_desc) + return -ENOMEM; + + if (FUOUT_EN(audio_opts)) { + out_feature_unit_desc = build_fu_desc(audio_opts->c_chmask); + if (!out_feature_unit_desc) { + status = -ENOMEM; + goto fail; + } + } + if (FUIN_EN(audio_opts)) { + in_feature_unit_desc = build_fu_desc(audio_opts->p_chmask); + if (!in_feature_unit_desc) { + status = -ENOMEM; + goto err_free_fu; + } + } + + ac_interface_desc.iInterface = us[STR_AC_IF].id; + usb_out_it_desc.iTerminal = us[STR_USB_OUT_IT].id; + usb_out_it_desc.iChannelNames = us[STR_USB_OUT_IT_CH_NAMES].id; + io_out_ot_desc.iTerminal = us[STR_IO_OUT_OT].id; + as_out_interface_alt_0_desc.iInterface = us[STR_AS_OUT_IF_ALT0].id; + as_out_interface_alt_1_desc.iInterface = us[STR_AS_OUT_IF_ALT1].id; + io_in_it_desc.iTerminal = us[STR_IO_IN_IT].id; + io_in_it_desc.iChannelNames = us[STR_IO_IN_IT_CH_NAMES].id; + usb_in_ot_desc.iTerminal = us[STR_USB_IN_OT].id; + as_in_interface_alt_0_desc.iInterface = us[STR_AS_IN_IF_ALT0].id; + as_in_interface_alt_1_desc.iInterface = us[STR_AS_IN_IF_ALT1].id; + + if (FUOUT_EN(audio_opts)) { + u8 *i_feature; + + i_feature = (u8 *)out_feature_unit_desc + + out_feature_unit_desc->bLength - 1; + *i_feature = us[STR_FU_OUT].id; + } + if (FUIN_EN(audio_opts)) { + u8 *i_feature; + + i_feature = (u8 *)in_feature_unit_desc + + in_feature_unit_desc->bLength - 1; + *i_feature = us[STR_FU_IN].id; + } + + /* Set channel numbers */ + usb_out_it_desc.bNrChannels = num_channels(audio_opts->c_chmask); + usb_out_it_desc.wChannelConfig = cpu_to_le16(audio_opts->c_chmask); + as_out_type_i_desc.bNrChannels = num_channels(audio_opts->c_chmask); + as_out_type_i_desc.bSubframeSize = audio_opts->c_ssize; + as_out_type_i_desc.bBitResolution = audio_opts->c_ssize * 8; + io_in_it_desc.bNrChannels = num_channels(audio_opts->p_chmask); + io_in_it_desc.wChannelConfig = cpu_to_le16(audio_opts->p_chmask); + as_in_type_i_desc.bNrChannels = num_channels(audio_opts->p_chmask); + as_in_type_i_desc.bSubframeSize = audio_opts->p_ssize; + as_in_type_i_desc.bBitResolution = audio_opts->p_ssize * 8; + + if (FUOUT_EN(audio_opts)) { + __le16 *bma = (__le16 *)&out_feature_unit_desc->bmaControls[0]; + u32 control = 0; + + if (audio_opts->c_mute_present) + control |= UAC_FU_MUTE; + if (audio_opts->c_volume_present) + control |= UAC_FU_VOLUME; + *bma = cpu_to_le16(control); + } + if (FUIN_EN(audio_opts)) { + __le16 *bma = (__le16 *)&in_feature_unit_desc->bmaControls[0]; + u32 control = 0; + + if (audio_opts->p_mute_present) + control |= UAC_FU_MUTE; + if (audio_opts->p_volume_present) + control |= UAC_FU_VOLUME; + *bma = cpu_to_le16(control); + } + + /* Set sample rates */ + for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) { + if (audio_opts->c_srates[i] == 0) + break; + memcpy(as_out_type_i_desc.tSamFreq[idx++], + &audio_opts->c_srates[i], 3); + } + as_out_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx); + as_out_type_i_desc.bSamFreqType = idx; + + for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) { + if (audio_opts->p_srates[i] == 0) + break; + memcpy(as_in_type_i_desc.tSamFreq[idx++], + &audio_opts->p_srates[i], 3); + } + as_in_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx); + as_in_type_i_desc.bSamFreqType = idx; + uac1->p_srate = audio_opts->p_srates[0]; + uac1->c_srate = audio_opts->c_srates[0]; + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto err_free_fu; + ac_interface_desc.bInterfaceNumber = status; + uac1->ac_intf = status; + uac1->ac_alt = 0; + + ba_iface_id = 0; + + if (EPOUT_EN(audio_opts)) { + status = usb_interface_id(c, f); + if (status < 0) + goto err_free_fu; + as_out_interface_alt_0_desc.bInterfaceNumber = status; + as_out_interface_alt_1_desc.bInterfaceNumber = status; + ac_header_desc->baInterfaceNr[ba_iface_id++] = status; + uac1->as_out_intf = status; + uac1->as_out_alt = 0; + } + + if (EPIN_EN(audio_opts)) { + status = usb_interface_id(c, f); + if (status < 0) + goto err_free_fu; + as_in_interface_alt_0_desc.bInterfaceNumber = status; + as_in_interface_alt_1_desc.bInterfaceNumber = status; + ac_header_desc->baInterfaceNr[ba_iface_id++] = status; + uac1->as_in_intf = status; + uac1->as_in_alt = 0; + } + + audio->gadget = gadget; + + status = -ENODEV; + + ac_interface_desc.bNumEndpoints = 0; + + /* allocate AC interrupt endpoint */ + if (FUOUT_EN(audio_opts) || FUIN_EN(audio_opts)) { + ep = usb_ep_autoconfig(cdev->gadget, &ac_int_ep_desc); + if (!ep) + goto err_free_fu; + uac1->int_ep = ep; + uac1->int_ep->desc = &ac_int_ep_desc; + + ac_interface_desc.bNumEndpoints = 1; + } + + /* allocate instance-specific endpoints */ + if (EPOUT_EN(audio_opts)) { + ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc); + if (!ep) + goto err_free_fu; + ss_as_out_ep_desc.bEndpointAddress = as_out_ep_desc.bEndpointAddress; + audio->out_ep = ep; + audio->out_ep->desc = &as_out_ep_desc; + } + + if (EPIN_EN(audio_opts)) { + ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc); + if (!ep) + goto err_free_fu; + ss_as_in_ep_desc.bEndpointAddress = as_in_ep_desc.bEndpointAddress; + audio->in_ep = ep; + audio->in_ep->desc = &as_in_ep_desc; + } + + setup_descriptor(audio_opts); + + /* copy descriptors, and track endpoint copies */ + status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, f_audio_ss_desc, + f_audio_ss_desc); + if (status) + goto err_free_fu; + + audio->out_ep_maxpsize = le16_to_cpu(as_out_ep_desc.wMaxPacketSize); + audio->in_ep_maxpsize = le16_to_cpu(as_in_ep_desc.wMaxPacketSize); + audio->params.c_chmask = audio_opts->c_chmask; + memcpy(audio->params.c_srates, audio_opts->c_srates, + sizeof(audio->params.c_srates)); + audio->params.c_ssize = audio_opts->c_ssize; + if (FUIN_EN(audio_opts)) { + audio->params.p_fu.id = USB_IN_FU_ID; + audio->params.p_fu.mute_present = audio_opts->p_mute_present; + audio->params.p_fu.volume_present = + audio_opts->p_volume_present; + audio->params.p_fu.volume_min = audio_opts->p_volume_min; + audio->params.p_fu.volume_max = audio_opts->p_volume_max; + audio->params.p_fu.volume_res = audio_opts->p_volume_res; + } + audio->params.p_chmask = audio_opts->p_chmask; + memcpy(audio->params.p_srates, audio_opts->p_srates, + sizeof(audio->params.p_srates)); + audio->params.p_ssize = audio_opts->p_ssize; + if (FUOUT_EN(audio_opts)) { + audio->params.c_fu.id = USB_OUT_FU_ID; + audio->params.c_fu.mute_present = audio_opts->c_mute_present; + audio->params.c_fu.volume_present = + audio_opts->c_volume_present; + audio->params.c_fu.volume_min = audio_opts->c_volume_min; + audio->params.c_fu.volume_max = audio_opts->c_volume_max; + audio->params.c_fu.volume_res = audio_opts->c_volume_res; + } + audio->params.req_number = audio_opts->req_number; + audio->params.fb_max = FBACK_FAST_MAX; + if (FUOUT_EN(audio_opts) || FUIN_EN(audio_opts)) + audio->notify = audio_notify; + + status = g_audio_setup(audio, "UAC1_PCM", "UAC1_Gadget"); + if (status) + goto err_card_register; + + return 0; + +err_card_register: + usb_free_all_descriptors(f); +err_free_fu: + kfree(out_feature_unit_desc); + out_feature_unit_desc = NULL; + kfree(in_feature_unit_desc); + in_feature_unit_desc = NULL; +fail: + kfree(ac_header_desc); + ac_header_desc = NULL; + return status; +} + +/*-------------------------------------------------------------------------*/ + +static inline struct f_uac1_opts *to_f_uac1_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_uac1_opts, + func_inst.group); +} + +static void f_uac1_attr_release(struct config_item *item) +{ + struct f_uac1_opts *opts = to_f_uac1_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static const struct configfs_item_operations f_uac1_item_ops = { + .release = f_uac1_attr_release, +}; + +#define uac1_kstrtou32 kstrtou32 +#define uac1_kstrtos16 kstrtos16 +#define uac1_kstrtobool(s, base, res) kstrtobool((s), (res)) + +static const char *u32_fmt = "%u\n"; +static const char *s16_fmt = "%hd\n"; +static const char *bool_fmt = "%u\n"; + +#define UAC1_ATTRIBUTE(type, name) \ +static ssize_t f_uac1_opts_##name##_show( \ + struct config_item *item, \ + char *page) \ +{ \ + struct f_uac1_opts *opts = to_f_uac1_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, type##_fmt, opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac1_opts_##name##_store( \ + struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac1_opts *opts = to_f_uac1_opts(item); \ + int ret; \ + type num; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = uac1_kstrto##type(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + opts->name = num; \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac1_opts_, name) + +#define UAC1_RATE_ATTRIBUTE(name) \ +static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac1_opts *opts = to_f_uac1_opts(item); \ + int result = 0; \ + int i; \ + \ + mutex_lock(&opts->lock); \ + page[0] = '\0'; \ + for (i = 0; i < UAC_MAX_RATES; i++) { \ + if (opts->name##s[i] == 0) \ + break; \ + result += sprintf(page + strlen(page), "%u,", \ + opts->name##s[i]); \ + } \ + if (strlen(page) > 0) \ + page[strlen(page) - 1] = '\n'; \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac1_opts *opts = to_f_uac1_opts(item); \ + char *buf = NULL; \ + char *split_page; \ + int ret = -EINVAL; \ + char *token; \ + u32 num; \ + int i; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + i = 0; \ + memset(opts->name##s, 0x00, sizeof(opts->name##s)); \ + buf = kstrdup(page, GFP_KERNEL); \ + split_page = buf; \ + while ((token = strsep(&split_page, ",")) != NULL) { \ + ret = kstrtou32(token, 0, &num); \ + if (ret) \ + goto end; \ + if (i >= UAC_MAX_RATES) { \ + ret = -EINVAL; \ + goto end; \ + } \ + opts->name##s[i++] = num; \ + ret = len; \ + }; \ + \ +end: \ + kfree(buf); \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac1_opts_, name) + +#define UAC1_ATTRIBUTE_STRING(name) \ +static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac1_opts *opts = to_f_uac1_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sysfs_emit(page, "%s", opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac1_opts *opts = to_f_uac1_opts(item); \ + int ret = 0; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = scnprintf(opts->name, min(sizeof(opts->name), len), \ + "%s", page); \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac1_opts_, name) + +UAC1_ATTRIBUTE(u32, c_chmask); +UAC1_RATE_ATTRIBUTE(c_srate); +UAC1_ATTRIBUTE(u32, c_ssize); +UAC1_ATTRIBUTE(u32, p_chmask); +UAC1_RATE_ATTRIBUTE(p_srate); +UAC1_ATTRIBUTE(u32, p_ssize); +UAC1_ATTRIBUTE(u32, req_number); + +UAC1_ATTRIBUTE(bool, p_mute_present); +UAC1_ATTRIBUTE(bool, p_volume_present); +UAC1_ATTRIBUTE(s16, p_volume_min); +UAC1_ATTRIBUTE(s16, p_volume_max); +UAC1_ATTRIBUTE(s16, p_volume_res); + +UAC1_ATTRIBUTE(bool, c_mute_present); +UAC1_ATTRIBUTE(bool, c_volume_present); +UAC1_ATTRIBUTE(s16, c_volume_min); +UAC1_ATTRIBUTE(s16, c_volume_max); +UAC1_ATTRIBUTE(s16, c_volume_res); + +UAC1_ATTRIBUTE_STRING(function_name); + +UAC1_ATTRIBUTE_STRING(p_it_name); +UAC1_ATTRIBUTE_STRING(p_it_ch_name); +UAC1_ATTRIBUTE_STRING(p_ot_name); +UAC1_ATTRIBUTE_STRING(p_fu_vol_name); + +UAC1_ATTRIBUTE_STRING(c_it_name); +UAC1_ATTRIBUTE_STRING(c_it_ch_name); +UAC1_ATTRIBUTE_STRING(c_ot_name); +UAC1_ATTRIBUTE_STRING(c_fu_vol_name); + +static struct configfs_attribute *f_uac1_attrs[] = { + &f_uac1_opts_attr_c_chmask, + &f_uac1_opts_attr_c_srate, + &f_uac1_opts_attr_c_ssize, + &f_uac1_opts_attr_p_chmask, + &f_uac1_opts_attr_p_srate, + &f_uac1_opts_attr_p_ssize, + &f_uac1_opts_attr_req_number, + + &f_uac1_opts_attr_p_mute_present, + &f_uac1_opts_attr_p_volume_present, + &f_uac1_opts_attr_p_volume_min, + &f_uac1_opts_attr_p_volume_max, + &f_uac1_opts_attr_p_volume_res, + + &f_uac1_opts_attr_c_mute_present, + &f_uac1_opts_attr_c_volume_present, + &f_uac1_opts_attr_c_volume_min, + &f_uac1_opts_attr_c_volume_max, + &f_uac1_opts_attr_c_volume_res, + + &f_uac1_opts_attr_function_name, + + &f_uac1_opts_attr_p_it_name, + &f_uac1_opts_attr_p_it_ch_name, + &f_uac1_opts_attr_p_ot_name, + &f_uac1_opts_attr_p_fu_vol_name, + + &f_uac1_opts_attr_c_it_name, + &f_uac1_opts_attr_c_it_ch_name, + &f_uac1_opts_attr_c_ot_name, + &f_uac1_opts_attr_c_fu_vol_name, + + NULL, +}; + +static const struct config_item_type f_uac1_func_type = { + .ct_item_ops = &f_uac1_item_ops, + .ct_attrs = f_uac1_attrs, + .ct_owner = THIS_MODULE, +}; + +static void f_audio_free_inst(struct usb_function_instance *f) +{ + struct f_uac1_opts *opts; + + opts = container_of(f, struct f_uac1_opts, func_inst); + kfree(opts); +} + +static struct usb_function_instance *f_audio_alloc_inst(void) +{ + struct f_uac1_opts *opts; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = f_audio_free_inst; + + config_group_init_type_name(&opts->func_inst.group, "", + &f_uac1_func_type); + + opts->c_chmask = UAC1_DEF_CCHMASK; + opts->c_srates[0] = UAC1_DEF_CSRATE; + opts->c_ssize = UAC1_DEF_CSSIZE; + opts->p_chmask = UAC1_DEF_PCHMASK; + opts->p_srates[0] = UAC1_DEF_PSRATE; + opts->p_ssize = UAC1_DEF_PSSIZE; + + opts->p_mute_present = UAC1_DEF_MUTE_PRESENT; + opts->p_volume_present = UAC1_DEF_VOLUME_PRESENT; + opts->p_volume_min = UAC1_DEF_MIN_DB; + opts->p_volume_max = UAC1_DEF_MAX_DB; + opts->p_volume_res = UAC1_DEF_RES_DB; + + opts->c_mute_present = UAC1_DEF_MUTE_PRESENT; + opts->c_volume_present = UAC1_DEF_VOLUME_PRESENT; + opts->c_volume_min = UAC1_DEF_MIN_DB; + opts->c_volume_max = UAC1_DEF_MAX_DB; + opts->c_volume_res = UAC1_DEF_RES_DB; + + opts->req_number = UAC1_DEF_REQ_NUM; + + scnprintf(opts->function_name, sizeof(opts->function_name), "AC Interface"); + + scnprintf(opts->p_it_name, sizeof(opts->p_it_name), "Capture Input terminal"); + scnprintf(opts->p_it_ch_name, sizeof(opts->p_it_ch_name), "Capture Channels"); + scnprintf(opts->p_ot_name, sizeof(opts->p_ot_name), "Capture Output terminal"); + scnprintf(opts->p_fu_vol_name, sizeof(opts->p_fu_vol_name), "Capture Volume"); + + scnprintf(opts->c_it_name, sizeof(opts->c_it_name), "Playback Input terminal"); + scnprintf(opts->c_it_ch_name, sizeof(opts->c_it_ch_name), "Playback Channels"); + scnprintf(opts->c_ot_name, sizeof(opts->c_ot_name), "Playback Output terminal"); + scnprintf(opts->c_fu_vol_name, sizeof(opts->c_fu_vol_name), "Playback Volume"); + + return &opts->func_inst; +} + +static void f_audio_free(struct usb_function *f) +{ + struct g_audio *audio; + struct f_uac1_opts *opts; + + audio = func_to_g_audio(f); + opts = container_of(f->fi, struct f_uac1_opts, func_inst); + kfree(audio); + mutex_lock(&opts->lock); + --opts->refcnt; + mutex_unlock(&opts->lock); +} + +static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct g_audio *audio = func_to_g_audio(f); + + g_audio_cleanup(audio); + usb_free_all_descriptors(f); + + kfree(out_feature_unit_desc); + out_feature_unit_desc = NULL; + kfree(in_feature_unit_desc); + in_feature_unit_desc = NULL; + + kfree(ac_header_desc); + ac_header_desc = NULL; + + audio->gadget = NULL; +} + +static struct usb_function *f_audio_alloc(struct usb_function_instance *fi) +{ + struct f_uac1 *uac1; + struct f_uac1_opts *opts; + + /* allocate and initialize one new instance */ + uac1 = kzalloc_obj(*uac1); + if (!uac1) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_uac1_opts, func_inst); + mutex_lock(&opts->lock); + ++opts->refcnt; + mutex_unlock(&opts->lock); + + uac1->g_audio.func.name = "uac1_func"; + uac1->g_audio.func.bind = f_audio_bind; + uac1->g_audio.func.unbind = f_audio_unbind; + uac1->g_audio.func.set_alt = f_audio_set_alt; + uac1->g_audio.func.get_alt = f_audio_get_alt; + uac1->g_audio.func.setup = f_audio_setup; + uac1->g_audio.func.disable = f_audio_disable; + uac1->g_audio.func.suspend = f_audio_suspend; + uac1->g_audio.func.free_func = f_audio_free; + + return &uac1->g_audio.func; +} + +DECLARE_USB_FUNCTION_INIT(uac1, f_audio_alloc_inst, f_audio_alloc); +MODULE_DESCRIPTION("USB Audio Class 1.0 Function (using u_audio API)"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ruslan Bilovol"); diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c new file mode 100644 index 000000000..3f52099a4 --- /dev/null +++ b/drivers/usb/gadget/function/f_uac1_legacy.c @@ -0,0 +1,990 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_audio.c -- USB Audio class function driver + * + * Copyright (C) 2008 Bryan Wu + * Copyright (C) 2008 Analog Devices, Inc + */ + +#include +#include +#include +#include +#include + +#include "u_uac1_legacy.h" + +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 1 + +/* 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, +}; + +/* + * The number of AudioStreaming and MIDIStreaming interfaces + * in the Audio Interface Collection + */ +DECLARE_UAC_AC_HEADER_DESCRIPTOR(1); + +#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_1 ac_header_desc = { + .bLength = UAC_DT_AC_HEADER_LENGTH, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_HEADER, + .bcdADC = cpu_to_le16(0x0100), + .wTotalLength = cpu_to_le16(UAC_DT_TOTAL_LENGTH), + .bInCollection = F_AUDIO_NUM_INTERFACES, + .baInterfaceNr = { + /* Interface number of the first AudioStream interface */ + [0] = 1, + } +}; + +#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 = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct uac_iso_endpoint_descriptor as_iso_out_desc = { + .bLength = UAC_ISO_ENDPOINT_DESC_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = UAC_EP_GENERAL, + .bmAttributes = 1, + .bLockDelayUnits = 1, + .wLockDelay = cpu_to_le16(1), +}; + +static struct usb_descriptor_header *f_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 *)&as_out_ep_desc, + (struct usb_descriptor_header *)&as_iso_out_desc, + NULL, +}; + +enum { + STR_AC_IF, + STR_INPUT_TERMINAL, + STR_INPUT_TERMINAL_CH_NAMES, + STR_FEAT_DESC_0, + STR_OUTPUT_TERMINAL, + STR_AS_IF_ALT0, + STR_AS_IF_ALT1, +}; + +static struct usb_string strings_uac1[] = { + [STR_AC_IF].s = "AC Interface", + [STR_INPUT_TERMINAL].s = "Input terminal", + [STR_INPUT_TERMINAL_CH_NAMES].s = "Channels", + [STR_FEAT_DESC_0].s = "Volume control & mute", + [STR_OUTPUT_TERMINAL].s = "Output terminal", + [STR_AS_IF_ALT0].s = "AS Interface", + [STR_AS_IF_ALT1].s = "AS Interface", + { }, +}; + +static struct usb_gadget_strings str_uac1 = { + .language = 0x0409, /* en-us */ + .strings = strings_uac1, +}; + +static struct usb_gadget_strings *uac1_strings[] = { + &str_uac1, + 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_obj(*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; + + u8 ac_intf, ac_alt; + u8 as_intf, as_alt; + + /* 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; + struct f_uac1_legacy_opts *opts; + int audio_buf_size; + int err; + + opts = container_of(audio->card.func.fi, struct f_uac1_legacy_opts, + func_inst); + audio_buf_size = opts->audio_buf_size; + + if (!copy_buf) + return -EINVAL; + + /* Copy buffer is full, add it to the play_queue */ + if (audio_buf_size - copy_buf->actual < req->actual) { + spin_lock_irq(&audio->lock); + list_add_tail(©_buf->list, &audio->play_queue); + spin_unlock_irq(&audio->lock); + 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; + struct usb_ep *out_ep = audio->out_ep; + + switch (req->status) { + case 0: + if (ep == out_ep) { + f_audio_out_ep_complete(ep, req); + } else if (audio->set_con) { + struct usb_audio_control *con = audio->set_con; + u8 type = con->type; + u32 data; + bool valid_request = false; + + switch (type) { + case UAC_FU_MUTE: { + u8 value; + + if (req->actual == sizeof(value)) { + memcpy(&value, req->buf, sizeof(value)); + data = value; + valid_request = true; + } + break; + } + case UAC_FU_VOLUME: { + __le16 value; + + if (req->actual == sizeof(value)) { + memcpy(&value, req->buf, sizeof(value)); + data = le16_to_cpu(value); + valid_request = true; + } + break; + } + } + + if (valid_request) + con->set(con, audio->set_cmd, data); + else + usb_ep_set_halt(ep); + + 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; + struct f_uac1_legacy_opts *opts; + int req_buf_size, req_count, audio_buf_size; + int i = 0, err = 0; + + DBG(cdev, "intf %d, alt %d\n", intf, alt); + + opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst); + req_buf_size = opts->req_buf_size; + req_count = opts->req_count; + audio_buf_size = opts->audio_buf_size; + + /* No i/f has more than 2 alt settings */ + if (alt > 1) { + ERROR(cdev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + if (intf == audio->ac_intf) { + /* Control I/f has only 1 AltSetting - 0 */ + if (alt) { + ERROR(cdev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + return 0; + } else if (intf == audio->as_intf) { + if (alt == 1) { + err = config_ep_by_speed(cdev->gadget, f, out_ep); + if (err) + return err; + + usb_ep_enable(out_ep); + 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); + } + } + audio->as_alt = alt; + } + + return err; +} + +static int f_audio_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + + if (intf == audio->ac_intf) + return audio->ac_alt; + else if (intf == audio->as_intf) + return audio->as_alt; + else + ERROR(cdev, "%s:%d Invalid Interface %d!\n", + __func__, __LINE__, intf); + + return -EINVAL; +} + +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 +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); + struct usb_string *us; + int status; + struct usb_ep *ep = NULL; + struct f_uac1_legacy_opts *audio_opts; + + audio_opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst); + audio->card.gadget = c->cdev->gadget; + /* set up ASLA audio devices */ + if (!audio_opts->bound) { + status = gaudio_setup(&audio->card); + if (status < 0) + return status; + audio_opts->bound = true; + } + us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1)); + if (IS_ERR(us)) + return PTR_ERR(us); + ac_interface_desc.iInterface = us[STR_AC_IF].id; + input_terminal_desc.iTerminal = us[STR_INPUT_TERMINAL].id; + input_terminal_desc.iChannelNames = us[STR_INPUT_TERMINAL_CH_NAMES].id; + feature_unit_desc.iFeature = us[STR_FEAT_DESC_0].id; + output_terminal_desc.iTerminal = us[STR_OUTPUT_TERMINAL].id; + as_interface_alt_0_desc.iInterface = us[STR_AS_IF_ALT0].id; + as_interface_alt_1_desc.iInterface = us[STR_AS_IF_ALT1].id; + + + 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; + audio->ac_intf = status; + audio->ac_alt = 0; + + 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_intf = status; + audio->as_alt = 0; + + 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; + + /* copy descriptors, and track endpoint copies */ + status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL, + NULL); + if (status) + goto fail; + return 0; + +fail: + gaudio_cleanup(&audio->card); + return status; +} + +/*-------------------------------------------------------------------------*/ + +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 */ +static int 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; +} + +static inline +struct f_uac1_legacy_opts *to_f_uac1_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_uac1_legacy_opts, + func_inst.group); +} + +static void f_uac1_attr_release(struct config_item *item) +{ + struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static const struct configfs_item_operations f_uac1_item_ops = { + .release = f_uac1_attr_release, +}; + +#define UAC1_INT_ATTRIBUTE(name) \ +static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \ + int ret; \ + u32 num; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou32(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + opts->name = num; \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac1_opts_, name) + +UAC1_INT_ATTRIBUTE(req_buf_size); +UAC1_INT_ATTRIBUTE(req_count); +UAC1_INT_ATTRIBUTE(audio_buf_size); + +static struct configfs_attribute *f_uac1_attrs[] = { + &f_uac1_opts_attr_req_buf_size, + &f_uac1_opts_attr_req_count, + &f_uac1_opts_attr_audio_buf_size, + NULL, +}; + +static const struct config_item_type f_uac1_func_type = { + .ct_item_ops = &f_uac1_item_ops, + .ct_attrs = f_uac1_attrs, + .ct_owner = THIS_MODULE, +}; + +static void f_audio_free_inst(struct usb_function_instance *f) +{ + struct f_uac1_legacy_opts *opts; + + opts = container_of(f, struct f_uac1_legacy_opts, func_inst); + kfree(opts); +} + +static struct usb_function_instance *f_audio_alloc_inst(void) +{ + struct f_uac1_legacy_opts *opts; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = f_audio_free_inst; + + config_group_init_type_name(&opts->func_inst.group, "", + &f_uac1_func_type); + + opts->req_buf_size = UAC1_OUT_EP_MAX_PACKET_SIZE; + opts->req_count = UAC1_REQ_COUNT; + opts->audio_buf_size = UAC1_AUDIO_BUF_SIZE; + opts->fn_play = FILE_PCM_PLAYBACK; + opts->fn_cap = FILE_PCM_CAPTURE; + opts->fn_cntl = FILE_CONTROL; + return &opts->func_inst; +} + +static void f_audio_free(struct usb_function *f) +{ + struct f_audio *audio = func_to_audio(f); + struct f_uac1_legacy_opts *opts; + + gaudio_cleanup(&audio->card); + opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst); + kfree(audio); + mutex_lock(&opts->lock); + --opts->refcnt; + mutex_unlock(&opts->lock); +} + +static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f) +{ + usb_free_all_descriptors(f); +} + +static struct usb_function *f_audio_alloc(struct usb_function_instance *fi) +{ + struct f_audio *audio; + struct f_uac1_legacy_opts *opts; + + /* allocate and initialize one new instance */ + audio = kzalloc_obj(*audio); + if (!audio) + return ERR_PTR(-ENOMEM); + + audio->card.func.name = "g_audio"; + + opts = container_of(fi, struct f_uac1_legacy_opts, func_inst); + mutex_lock(&opts->lock); + ++opts->refcnt; + mutex_unlock(&opts->lock); + INIT_LIST_HEAD(&audio->play_queue); + spin_lock_init(&audio->lock); + + 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.get_alt = f_audio_get_alt; + audio->card.func.setup = f_audio_setup; + audio->card.func.disable = f_audio_disable; + audio->card.func.free_func = f_audio_free; + + control_selector_init(audio); + + INIT_WORK(&audio->playback_work, f_audio_playback_work); + + return &audio->card.func; +} + +DECLARE_USB_FUNCTION_INIT(uac1_legacy, f_audio_alloc_inst, f_audio_alloc); +MODULE_DESCRIPTION("USB Audio class function driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Bryan Wu"); diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c new file mode 100644 index 000000000..d8cf71008 --- /dev/null +++ b/drivers/usb/gadget/function/f_uac2.c @@ -0,0 +1,2313 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_uac2.c -- USB Audio Class 2.0 Function + * + * Copyright (C) 2011 + * Yadwinder Singh (yadi.brar01@gmail.com) + * Jaswinder Singh (jaswinder.singh@linaro.org) + * + * Copyright (C) 2020 + * Ruslan Bilovol (ruslan.bilovol@gmail.com) + */ + +#include +#include +#include + +#include "u_audio.h" + +#include "u_uac2.h" + +/* UAC2 spec: 4.1 Audio Channel Cluster Descriptor */ +#define UAC2_CHANNEL_MASK 0x07FFFFFF + +/* + * The driver implements a simple UAC_2 topology. + * USB-OUT -> IT_1 -> FU -> OT_3 -> ALSA_Capture + * ALSA_Playback -> IT_2 -> FU -> 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_CLK_ID (out_clk_src_desc.bClockID) +#define USB_IN_CLK_ID (in_clk_src_desc.bClockID) +#define USB_OUT_FU_ID (out_feature_unit_desc->bUnitID) +#define USB_IN_FU_ID (in_feature_unit_desc->bUnitID) + +#define CONTROL_ABSENT 0 +#define CONTROL_RDONLY 1 +#define CONTROL_RDWR 3 + +#define CLK_FREQ_CTRL 0 +#define CLK_VLD_CTRL 2 +#define FU_MUTE_CTRL 0 +#define FU_VOL_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 + +#define EPIN_EN(_opts) ((_opts)->p_chmask != 0) +#define EPOUT_EN(_opts) ((_opts)->c_chmask != 0) +#define FUIN_EN(_opts) (EPIN_EN(_opts) \ + && ((_opts)->p_mute_present \ + || (_opts)->p_volume_present)) +#define FUOUT_EN(_opts) (EPOUT_EN(_opts) \ + && ((_opts)->c_mute_present \ + || (_opts)->c_volume_present)) +#define EPOUT_FBACK_IN_EN(_opts) ((_opts)->c_sync == USB_ENDPOINT_SYNC_ASYNC) + +struct f_uac2 { + struct g_audio g_audio; + u8 ac_intf, as_in_intf, as_out_intf; + u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */ + + struct usb_ctrlrequest setup_cr; /* will be used in data stage */ + + /* Interrupt IN endpoint of AC interface */ + struct usb_ep *int_ep; + atomic_t int_count; + /* transient state, only valid during handling of a single control request */ + int clock_id; +}; + +static inline struct f_uac2 *func_to_uac2(struct usb_function *f) +{ + return container_of(f, struct f_uac2, g_audio.func); +} + +static inline +struct f_uac2_opts *g_audio_to_uac2_opts(struct g_audio *agdev) +{ + return container_of(agdev->func.fi, struct f_uac2_opts, func_inst); +} + +static int afunc_notify(struct g_audio *agdev, int unit_id, int cs); + +/* --------- USB Function Interface ------------- */ + +enum { + STR_ASSOC, + STR_IF_CTRL, + STR_CLKSRC_IN, + STR_CLKSRC_OUT, + STR_USB_IT, + STR_USB_IT_CH, + STR_IO_IT, + STR_IO_IT_CH, + STR_USB_OT, + STR_IO_OT, + STR_FU_IN, + STR_FU_OUT, + STR_AS_OUT_ALT0, + STR_AS_OUT_ALT1, + STR_AS_IN_ALT0, + STR_AS_IN_ALT1, + NUM_STR_DESCRIPTORS, +}; + +static struct usb_string strings_fn[NUM_STR_DESCRIPTORS + 1] = {}; + +static const char *const speed_names[] = { + [USB_SPEED_UNKNOWN] = "UNKNOWN", + [USB_SPEED_LOW] = "LS", + [USB_SPEED_FULL] = "FS", + [USB_SPEED_HIGH] = "HS", + [USB_SPEED_WIRELESS] = "W", + [USB_SPEED_SUPER] = "SS", + [USB_SPEED_SUPER_PLUS] = "SS+", +}; + +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_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 = DYNAMIC */ + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .bInterfaceProtocol = UAC_VERSION_2, +}; + +/* Clock source for IN traffic */ +static 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 = DYNAMIC */ + .bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED, + .bmControls = (CONTROL_RDWR << CLK_FREQ_CTRL), + .bAssocTerminal = 0, +}; + +/* Clock source for OUT traffic */ +static 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 = DYNAMIC */ + .bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED, + .bmControls = (CONTROL_RDWR << CLK_FREQ_CTRL), + .bAssocTerminal = 0, +}; + +/* Input Terminal for USB_OUT */ +static 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 = DYNAMIC */ + .wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING), + .bAssocTerminal = 0, + /* .bCSourceID = DYNAMIC */ + .iChannelNames = 0, + .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL), +}; + +/* Input Terminal for I/O-In */ +static 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 = DYNAMIC */ + /* .wTerminalType = DYNAMIC */ + .bAssocTerminal = 0, + /* .bCSourceID = DYNAMIC */ + .iChannelNames = 0, + .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL), +}; + +/* Ouput Terminal for USB_IN */ +static 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 = DYNAMIC */ + .wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING), + .bAssocTerminal = 0, + /* .bSourceID = DYNAMIC */ + /* .bCSourceID = DYNAMIC */ + .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL), +}; + +/* Ouput Terminal for I/O-Out */ +static 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 = DYNAMIC */ + /* .wTerminalType = DYNAMIC */ + .bAssocTerminal = 0, + /* .bSourceID = DYNAMIC */ + /* .bCSourceID = DYNAMIC */ + .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL), +}; + +static struct uac2_feature_unit_descriptor *in_feature_unit_desc; +static struct uac2_feature_unit_descriptor *out_feature_unit_desc; + +static 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 = DYNAMIC */ + .bmControls = 0, +}; + +/* AC IN Interrupt Endpoint */ +static struct usb_endpoint_descriptor fs_ep_int_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(6), + .bInterval = 1, +}; + +static struct usb_endpoint_descriptor hs_ep_int_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(6), + .bInterval = 4, +}; + +static struct usb_endpoint_descriptor ss_ep_int_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(6), + .bInterval = 4, +}; + +static struct usb_ss_ep_comp_descriptor ss_ep_int_desc_comp = { + .bLength = sizeof(ss_ep_int_desc_comp), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .wBytesPerInterval = cpu_to_le16(6), +}; + +/* 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 */ +static 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 = DYNAMIC */ + .bmControls = 0, + .bFormatType = UAC_FORMAT_TYPE_I, + .bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM), + .iChannelNames = 0, +}; + +/* Audio USB_OUT Format */ +static 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 */ +static struct usb_endpoint_descriptor fs_epout_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + /* .bmAttributes = DYNAMIC */ + /* .wMaxPacketSize = DYNAMIC */ + .bInterval = 1, +}; + +static struct usb_endpoint_descriptor hs_epout_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* .bmAttributes = DYNAMIC */ + /* .wMaxPacketSize = DYNAMIC */ + /* .bInterval = DYNAMIC */ +}; + +static struct usb_endpoint_descriptor ss_epout_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + /* .bmAttributes = DYNAMIC */ + /* .wMaxPacketSize = DYNAMIC */ + /* .bInterval = DYNAMIC */ +}; + +static struct usb_ss_ep_comp_descriptor ss_epout_desc_comp = { + .bLength = sizeof(ss_epout_desc_comp), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = 0, + /* wBytesPerInterval = DYNAMIC */ +}; + +/* 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, +}; + +/* STD AS ISO IN Feedback Endpoint */ +static struct usb_endpoint_descriptor fs_epin_fback_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_USAGE_FEEDBACK, + .wMaxPacketSize = cpu_to_le16(3), + .bInterval = 1, +}; + +static struct usb_endpoint_descriptor hs_epin_fback_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_USAGE_FEEDBACK, + .wMaxPacketSize = cpu_to_le16(4), + .bInterval = 4, +}; + +static struct usb_endpoint_descriptor ss_epin_fback_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_USAGE_FEEDBACK, + .wMaxPacketSize = cpu_to_le16(4), + .bInterval = 4, +}; + +static struct usb_ss_ep_comp_descriptor ss_epin_fback_desc_comp = { + .bLength = sizeof(ss_epin_fback_desc_comp), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = 0, + .wBytesPerInterval = cpu_to_le16(4), +}; + + +/* 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 */ +static 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 = DYNAMIC */ + .bmControls = 0, + .bFormatType = UAC_FORMAT_TYPE_I, + .bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM), + .iChannelNames = 0, +}; + +/* Audio USB_IN Format */ +static 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 */ +static 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, + /* .wMaxPacketSize = DYNAMIC */ + .bInterval = 1, +}; + +static 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, + /* .wMaxPacketSize = DYNAMIC */ + /* .bInterval = DYNAMIC */ +}; + +static struct usb_endpoint_descriptor ss_epin_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, + /* .wMaxPacketSize = DYNAMIC */ + /* .bInterval = DYNAMIC */ +}; + +static struct usb_ss_ep_comp_descriptor ss_epin_desc_comp = { + .bLength = sizeof(ss_epin_desc_comp), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = 0, + /* wBytesPerInterval = DYNAMIC */ +}; + +/* 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 *)&out_feature_unit_desc, + (struct usb_descriptor_header *)&io_in_it_desc, + (struct usb_descriptor_header *)&usb_in_ot_desc, + (struct usb_descriptor_header *)&in_feature_unit_desc, + (struct usb_descriptor_header *)&io_out_ot_desc, + + (struct usb_descriptor_header *)&fs_ep_int_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 *)&fs_epin_fback_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 *)&out_feature_unit_desc, + (struct usb_descriptor_header *)&io_in_it_desc, + (struct usb_descriptor_header *)&usb_in_ot_desc, + (struct usb_descriptor_header *)&in_feature_unit_desc, + (struct usb_descriptor_header *)&io_out_ot_desc, + + (struct usb_descriptor_header *)&hs_ep_int_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 *)&hs_epin_fback_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, +}; + +static struct usb_descriptor_header *ss_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 *)&out_feature_unit_desc, + (struct usb_descriptor_header *)&io_in_it_desc, + (struct usb_descriptor_header *)&usb_in_ot_desc, + (struct usb_descriptor_header *)&in_feature_unit_desc, + (struct usb_descriptor_header *)&io_out_ot_desc, + + (struct usb_descriptor_header *)&ss_ep_int_desc, + (struct usb_descriptor_header *)&ss_ep_int_desc_comp, + + (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 *)&ss_epout_desc, + (struct usb_descriptor_header *)&ss_epout_desc_comp, + (struct usb_descriptor_header *)&as_iso_out_desc, + (struct usb_descriptor_header *)&ss_epin_fback_desc, + (struct usb_descriptor_header *)&ss_epin_fback_desc_comp, + + (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 *)&ss_epin_desc, + (struct usb_descriptor_header *)&ss_epin_desc_comp, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + +struct cntrl_cur_lay2 { + __le16 wCUR; +}; + +struct cntrl_range_lay2 { + __le16 wNumSubRanges; + __le16 wMIN; + __le16 wMAX; + __le16 wRES; +} __packed; + +struct cntrl_cur_lay3 { + __le32 dCUR; +}; + +struct cntrl_subrange_lay3 { + __le32 dMIN; + __le32 dMAX; + __le32 dRES; +} __packed; + +#define ranges_lay3_size(c) (sizeof(c.wNumSubRanges) \ + + le16_to_cpu(c.wNumSubRanges) \ + * sizeof(struct cntrl_subrange_lay3)) + +#define DECLARE_UAC2_CNTRL_RANGES_LAY3(k, n) \ + struct cntrl_ranges_lay3_##k { \ + __le16 wNumSubRanges; \ + struct cntrl_subrange_lay3 r[n]; \ +} __packed + +DECLARE_UAC2_CNTRL_RANGES_LAY3(srates, UAC_MAX_RATES); + +static int get_max_srate(const int *srates) +{ + int i, max_srate = 0; + + for (i = 0; i < UAC_MAX_RATES; i++) { + if (srates[i] == 0) + break; + if (srates[i] > max_srate) + max_srate = srates[i]; + } + return max_srate; +} + +static int get_max_bw_for_bint(const struct f_uac2_opts *uac2_opts, + u8 bint, unsigned int factor, bool is_playback) +{ + int chmask, srate, ssize; + u16 max_size_bw; + + if (is_playback) { + chmask = uac2_opts->p_chmask; + srate = get_max_srate(uac2_opts->p_srates); + ssize = uac2_opts->p_ssize; + } else { + chmask = uac2_opts->c_chmask; + srate = get_max_srate(uac2_opts->c_srates); + ssize = uac2_opts->c_ssize; + } + + if (is_playback || (uac2_opts->c_sync == USB_ENDPOINT_SYNC_ASYNC)) { + // playback is always async, capture only when configured + // Win10 requires max packet size + 1 frame + srate = srate * (1000 + uac2_opts->fb_max) / 1000; + // updated srate is always bigger, therefore DIV_ROUND_UP always yields +1 + max_size_bw = num_channels(chmask) * ssize * + (DIV_ROUND_UP(srate, factor / (1 << (bint - 1)))); + } else { + // adding 1 frame provision for Win10 + max_size_bw = num_channels(chmask) * ssize * + (DIV_ROUND_UP(srate, factor / (1 << (bint - 1))) + 1); + } + return max_size_bw; +} + +static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_opts *uac2_opts, + struct usb_endpoint_descriptor *ep_desc, + enum usb_device_speed speed, bool is_playback) +{ + u16 max_size_bw, max_size_ep; + u8 bint, opts_bint; + char *dir; + + switch (speed) { + case USB_SPEED_FULL: + max_size_ep = 1023; + // fixed + bint = ep_desc->bInterval; + max_size_bw = get_max_bw_for_bint(uac2_opts, bint, 1000, is_playback); + break; + + case USB_SPEED_HIGH: + case USB_SPEED_SUPER: + max_size_ep = 1024; + if (is_playback) + opts_bint = uac2_opts->p_hs_bint; + else + opts_bint = uac2_opts->c_hs_bint; + + if (opts_bint > 0) { + /* fixed bint */ + bint = opts_bint; + max_size_bw = get_max_bw_for_bint(uac2_opts, bint, 8000, is_playback); + } else { + /* checking bInterval from 4 to 1 whether the required bandwidth fits */ + for (bint = 4; bint > 0; --bint) { + max_size_bw = get_max_bw_for_bint( + uac2_opts, bint, 8000, is_playback); + if (max_size_bw <= max_size_ep) + break; + } + } + break; + + default: + return -EINVAL; + } + + if (is_playback) + dir = "Playback"; + else + dir = "Capture"; + + if (max_size_bw <= max_size_ep) + dev_dbg(dev, + "%s %s: Would use wMaxPacketSize %d and bInterval %d\n", + speed_names[speed], dir, max_size_bw, bint); + else { + dev_warn(dev, + "%s %s: Req. wMaxPacketSize %d at bInterval %d > max ISOC %d, may drop data!\n", + speed_names[speed], dir, max_size_bw, bint, max_size_ep); + max_size_bw = max_size_ep; + } + + ep_desc->wMaxPacketSize = cpu_to_le16(max_size_bw); + ep_desc->bInterval = bint; + + return 0; +} + +static struct uac2_feature_unit_descriptor *build_fu_desc(int chmask) +{ + struct uac2_feature_unit_descriptor *fu_desc; + int channels = num_channels(chmask); + int fu_desc_size = UAC2_DT_FEATURE_UNIT_SIZE(channels); + + fu_desc = kzalloc(fu_desc_size, GFP_KERNEL); + if (!fu_desc) + return NULL; + + fu_desc->bLength = fu_desc_size; + fu_desc->bDescriptorType = USB_DT_CS_INTERFACE; + + fu_desc->bDescriptorSubtype = UAC_FEATURE_UNIT; + + /* bUnitID, bSourceID and bmaControls will be defined later */ + + return fu_desc; +} + +/* Use macro to overcome line length limitation */ +#define USBDHDR(p) (struct usb_descriptor_header *)(p) + +static void setup_headers(struct f_uac2_opts *opts, + struct usb_descriptor_header **headers, + enum usb_device_speed speed) +{ + struct usb_ss_ep_comp_descriptor *epout_desc_comp = NULL; + struct usb_ss_ep_comp_descriptor *epin_desc_comp = NULL; + struct usb_ss_ep_comp_descriptor *epin_fback_desc_comp = NULL; + struct usb_ss_ep_comp_descriptor *ep_int_desc_comp = NULL; + struct usb_endpoint_descriptor *epout_desc; + struct usb_endpoint_descriptor *epin_desc; + struct usb_endpoint_descriptor *epin_fback_desc; + struct usb_endpoint_descriptor *ep_int_desc; + int i; + + switch (speed) { + case USB_SPEED_FULL: + epout_desc = &fs_epout_desc; + epin_desc = &fs_epin_desc; + epin_fback_desc = &fs_epin_fback_desc; + ep_int_desc = &fs_ep_int_desc; + break; + case USB_SPEED_HIGH: + epout_desc = &hs_epout_desc; + epin_desc = &hs_epin_desc; + epin_fback_desc = &hs_epin_fback_desc; + ep_int_desc = &hs_ep_int_desc; + break; + default: + epout_desc = &ss_epout_desc; + epin_desc = &ss_epin_desc; + epout_desc_comp = &ss_epout_desc_comp; + epin_desc_comp = &ss_epin_desc_comp; + epin_fback_desc = &ss_epin_fback_desc; + epin_fback_desc_comp = &ss_epin_fback_desc_comp; + ep_int_desc = &ss_ep_int_desc; + ep_int_desc_comp = &ss_ep_int_desc_comp; + } + + i = 0; + headers[i++] = USBDHDR(&iad_desc); + headers[i++] = USBDHDR(&std_ac_if_desc); + headers[i++] = USBDHDR(&ac_hdr_desc); + if (EPIN_EN(opts)) + headers[i++] = USBDHDR(&in_clk_src_desc); + if (EPOUT_EN(opts)) { + headers[i++] = USBDHDR(&out_clk_src_desc); + headers[i++] = USBDHDR(&usb_out_it_desc); + + if (FUOUT_EN(opts)) + headers[i++] = USBDHDR(out_feature_unit_desc); + } + + if (EPIN_EN(opts)) { + headers[i++] = USBDHDR(&io_in_it_desc); + + if (FUIN_EN(opts)) + headers[i++] = USBDHDR(in_feature_unit_desc); + + headers[i++] = USBDHDR(&usb_in_ot_desc); + } + + if (EPOUT_EN(opts)) + headers[i++] = USBDHDR(&io_out_ot_desc); + + if (FUOUT_EN(opts) || FUIN_EN(opts)) { + headers[i++] = USBDHDR(ep_int_desc); + if (ep_int_desc_comp) + headers[i++] = USBDHDR(ep_int_desc_comp); + } + + if (EPOUT_EN(opts)) { + headers[i++] = USBDHDR(&std_as_out_if0_desc); + headers[i++] = USBDHDR(&std_as_out_if1_desc); + headers[i++] = USBDHDR(&as_out_hdr_desc); + headers[i++] = USBDHDR(&as_out_fmt1_desc); + headers[i++] = USBDHDR(epout_desc); + if (epout_desc_comp) + headers[i++] = USBDHDR(epout_desc_comp); + + headers[i++] = USBDHDR(&as_iso_out_desc); + + if (EPOUT_FBACK_IN_EN(opts)) { + headers[i++] = USBDHDR(epin_fback_desc); + if (epin_fback_desc_comp) + headers[i++] = USBDHDR(epin_fback_desc_comp); + } + } + + if (EPIN_EN(opts)) { + headers[i++] = USBDHDR(&std_as_in_if0_desc); + headers[i++] = USBDHDR(&std_as_in_if1_desc); + headers[i++] = USBDHDR(&as_in_hdr_desc); + headers[i++] = USBDHDR(&as_in_fmt1_desc); + headers[i++] = USBDHDR(epin_desc); + if (epin_desc_comp) + headers[i++] = USBDHDR(epin_desc_comp); + + headers[i++] = USBDHDR(&as_iso_in_desc); + } + headers[i] = NULL; +} + +static void setup_descriptor(struct f_uac2_opts *opts) +{ + /* patch descriptors */ + int i = 1; /* ID's start with 1 */ + + if (EPOUT_EN(opts)) + usb_out_it_desc.bTerminalID = i++; + if (EPIN_EN(opts)) + io_in_it_desc.bTerminalID = i++; + if (EPOUT_EN(opts)) + io_out_ot_desc.bTerminalID = i++; + if (EPIN_EN(opts)) + usb_in_ot_desc.bTerminalID = i++; + if (FUOUT_EN(opts)) + out_feature_unit_desc->bUnitID = i++; + if (FUIN_EN(opts)) + in_feature_unit_desc->bUnitID = i++; + if (EPOUT_EN(opts)) + out_clk_src_desc.bClockID = i++; + if (EPIN_EN(opts)) + in_clk_src_desc.bClockID = i++; + + usb_out_it_desc.bCSourceID = out_clk_src_desc.bClockID; + + if (FUIN_EN(opts)) { + usb_in_ot_desc.bSourceID = in_feature_unit_desc->bUnitID; + in_feature_unit_desc->bSourceID = io_in_it_desc.bTerminalID; + } else { + usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID; + } + + usb_in_ot_desc.bCSourceID = in_clk_src_desc.bClockID; + io_in_it_desc.bCSourceID = in_clk_src_desc.bClockID; + io_out_ot_desc.bCSourceID = out_clk_src_desc.bClockID; + + if (FUOUT_EN(opts)) { + io_out_ot_desc.bSourceID = out_feature_unit_desc->bUnitID; + out_feature_unit_desc->bSourceID = usb_out_it_desc.bTerminalID; + } else { + io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID; + } + + as_out_hdr_desc.bTerminalLink = usb_out_it_desc.bTerminalID; + as_in_hdr_desc.bTerminalLink = usb_in_ot_desc.bTerminalID; + + iad_desc.bInterfaceCount = 1; + ac_hdr_desc.wTotalLength = cpu_to_le16(sizeof(ac_hdr_desc)); + + if (EPIN_EN(opts)) { + u16 len = le16_to_cpu(ac_hdr_desc.wTotalLength); + + len += sizeof(in_clk_src_desc); + len += sizeof(usb_in_ot_desc); + + if (FUIN_EN(opts)) + len += in_feature_unit_desc->bLength; + + len += sizeof(io_in_it_desc); + ac_hdr_desc.wTotalLength = cpu_to_le16(len); + iad_desc.bInterfaceCount++; + } + if (EPOUT_EN(opts)) { + u16 len = le16_to_cpu(ac_hdr_desc.wTotalLength); + + len += sizeof(out_clk_src_desc); + len += sizeof(usb_out_it_desc); + + if (FUOUT_EN(opts)) + len += out_feature_unit_desc->bLength; + + len += sizeof(io_out_ot_desc); + ac_hdr_desc.wTotalLength = cpu_to_le16(len); + iad_desc.bInterfaceCount++; + } + + io_in_it_desc.wTerminalType = cpu_to_le16(opts->c_terminal_type); + io_out_ot_desc.wTerminalType = cpu_to_le16(opts->p_terminal_type); + + setup_headers(opts, fs_audio_desc, USB_SPEED_FULL); + setup_headers(opts, hs_audio_desc, USB_SPEED_HIGH); + setup_headers(opts, ss_audio_desc, USB_SPEED_SUPER); +} + +static int afunc_validate_opts(struct g_audio *agdev, struct device *dev) +{ + struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); + const char *msg = NULL; + + if (!opts->p_chmask && !opts->c_chmask) + msg = "no playback and capture channels"; + else if (opts->p_chmask & ~UAC2_CHANNEL_MASK) + msg = "unsupported playback channels mask"; + else if (opts->c_chmask & ~UAC2_CHANNEL_MASK) + msg = "unsupported capture channels mask"; + else if ((opts->p_ssize < 1) || (opts->p_ssize > 4)) + msg = "incorrect playback sample size"; + else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) + msg = "incorrect capture sample size"; + else if (!opts->p_srates[0]) + msg = "incorrect playback sampling rate"; + else if (!opts->c_srates[0]) + msg = "incorrect capture sampling rate"; + + else if (opts->p_volume_max <= opts->p_volume_min) + msg = "incorrect playback volume max/min"; + else if (opts->c_volume_max <= opts->c_volume_min) + msg = "incorrect capture volume max/min"; + else if (opts->p_volume_res <= 0) + msg = "negative/zero playback volume resolution"; + else if (opts->c_volume_res <= 0) + msg = "negative/zero capture volume resolution"; + + else if ((opts->p_volume_max - opts->p_volume_min) % opts->p_volume_res) + msg = "incorrect playback volume resolution"; + else if ((opts->c_volume_max - opts->c_volume_min) % opts->c_volume_res) + msg = "incorrect capture volume resolution"; + + else if ((opts->p_hs_bint < 0) || (opts->p_hs_bint > 4)) + msg = "incorrect playback HS/SS bInterval (1-4: fixed, 0: auto)"; + else if ((opts->c_hs_bint < 0) || (opts->c_hs_bint > 4)) + msg = "incorrect capture HS/SS bInterval (1-4: fixed, 0: auto)"; + + if (msg) { + dev_err(dev, "Error: %s\n", msg); + return -EINVAL; + } + + return 0; +} + +static int +afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) +{ + struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); + struct usb_composite_dev *cdev = cfg->cdev; + struct usb_gadget *gadget = cdev->gadget; + struct device *dev = &gadget->dev; + struct f_uac2_opts *uac2_opts = g_audio_to_uac2_opts(agdev); + struct usb_string *us; + int ret; + + ret = afunc_validate_opts(agdev, dev); + if (ret) + return ret; + + strings_fn[STR_ASSOC].s = uac2_opts->function_name; + strings_fn[STR_IF_CTRL].s = uac2_opts->if_ctrl_name; + strings_fn[STR_CLKSRC_IN].s = uac2_opts->clksrc_in_name; + strings_fn[STR_CLKSRC_OUT].s = uac2_opts->clksrc_out_name; + + strings_fn[STR_USB_IT].s = uac2_opts->c_it_name; + strings_fn[STR_USB_IT_CH].s = uac2_opts->c_it_ch_name; + strings_fn[STR_IO_OT].s = uac2_opts->c_ot_name; + strings_fn[STR_FU_OUT].s = uac2_opts->c_fu_vol_name; + strings_fn[STR_AS_OUT_ALT0].s = "Playback Inactive"; + strings_fn[STR_AS_OUT_ALT1].s = "Playback Active"; + + strings_fn[STR_IO_IT].s = uac2_opts->p_it_name; + strings_fn[STR_IO_IT_CH].s = uac2_opts->p_it_ch_name; + strings_fn[STR_USB_OT].s = uac2_opts->p_ot_name; + strings_fn[STR_FU_IN].s = uac2_opts->p_fu_vol_name; + strings_fn[STR_AS_IN_ALT0].s = "Capture Inactive"; + strings_fn[STR_AS_IN_ALT1].s = "Capture Active"; + + us = usb_gstrings_attach(cdev, fn_strings, ARRAY_SIZE(strings_fn)); + if (IS_ERR(us)) + return PTR_ERR(us); + + if (FUOUT_EN(uac2_opts)) { + out_feature_unit_desc = build_fu_desc(uac2_opts->c_chmask); + if (!out_feature_unit_desc) + return -ENOMEM; + } + if (FUIN_EN(uac2_opts)) { + in_feature_unit_desc = build_fu_desc(uac2_opts->p_chmask); + if (!in_feature_unit_desc) { + ret = -ENOMEM; + goto err_free_fu; + } + } + + iad_desc.iFunction = us[STR_ASSOC].id; + std_ac_if_desc.iInterface = us[STR_IF_CTRL].id; + in_clk_src_desc.iClockSource = us[STR_CLKSRC_IN].id; + out_clk_src_desc.iClockSource = us[STR_CLKSRC_OUT].id; + usb_out_it_desc.iTerminal = us[STR_USB_IT].id; + usb_out_it_desc.iChannelNames = us[STR_USB_IT_CH].id; + io_in_it_desc.iTerminal = us[STR_IO_IT].id; + io_in_it_desc.iChannelNames = us[STR_IO_IT_CH].id; + usb_in_ot_desc.iTerminal = us[STR_USB_OT].id; + io_out_ot_desc.iTerminal = us[STR_IO_OT].id; + std_as_out_if0_desc.iInterface = us[STR_AS_OUT_ALT0].id; + std_as_out_if1_desc.iInterface = us[STR_AS_OUT_ALT1].id; + std_as_in_if0_desc.iInterface = us[STR_AS_IN_ALT0].id; + std_as_in_if1_desc.iInterface = us[STR_AS_IN_ALT1].id; + + if (FUOUT_EN(uac2_opts)) { + u8 *i_feature = (u8 *)out_feature_unit_desc + + out_feature_unit_desc->bLength - 1; + *i_feature = us[STR_FU_OUT].id; + } + if (FUIN_EN(uac2_opts)) { + u8 *i_feature = (u8 *)in_feature_unit_desc + + in_feature_unit_desc->bLength - 1; + *i_feature = us[STR_FU_IN].id; + } + + + /* Initialize the configurable parameters */ + usb_out_it_desc.bNrChannels = num_channels(uac2_opts->c_chmask); + usb_out_it_desc.bmChannelConfig = cpu_to_le32(uac2_opts->c_chmask); + io_in_it_desc.bNrChannels = num_channels(uac2_opts->p_chmask); + io_in_it_desc.bmChannelConfig = cpu_to_le32(uac2_opts->p_chmask); + as_out_hdr_desc.bNrChannels = num_channels(uac2_opts->c_chmask); + as_out_hdr_desc.bmChannelConfig = cpu_to_le32(uac2_opts->c_chmask); + as_in_hdr_desc.bNrChannels = num_channels(uac2_opts->p_chmask); + as_in_hdr_desc.bmChannelConfig = cpu_to_le32(uac2_opts->p_chmask); + as_out_fmt1_desc.bSubslotSize = uac2_opts->c_ssize; + as_out_fmt1_desc.bBitResolution = uac2_opts->c_ssize * 8; + as_in_fmt1_desc.bSubslotSize = uac2_opts->p_ssize; + as_in_fmt1_desc.bBitResolution = uac2_opts->p_ssize * 8; + if (FUOUT_EN(uac2_opts)) { + __le32 *bma = (__le32 *)&out_feature_unit_desc->bmaControls[0]; + u32 control = 0; + + if (uac2_opts->c_mute_present) + control |= CONTROL_RDWR << FU_MUTE_CTRL; + if (uac2_opts->c_volume_present) + control |= CONTROL_RDWR << FU_VOL_CTRL; + *bma = cpu_to_le32(control); + } + if (FUIN_EN(uac2_opts)) { + __le32 *bma = (__le32 *)&in_feature_unit_desc->bmaControls[0]; + u32 control = 0; + + if (uac2_opts->p_mute_present) + control |= CONTROL_RDWR << FU_MUTE_CTRL; + if (uac2_opts->p_volume_present) + control |= CONTROL_RDWR << FU_VOL_CTRL; + *bma = cpu_to_le32(control); + } + + ret = usb_interface_id(cfg, fn); + if (ret < 0) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + goto err_free_fu; + } + iad_desc.bFirstInterface = ret; + + std_ac_if_desc.bInterfaceNumber = ret; + uac2->ac_intf = ret; + uac2->ac_alt = 0; + + if (EPOUT_EN(uac2_opts)) { + ret = usb_interface_id(cfg, fn); + if (ret < 0) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + goto err_free_fu; + } + std_as_out_if0_desc.bInterfaceNumber = ret; + std_as_out_if1_desc.bInterfaceNumber = ret; + std_as_out_if1_desc.bNumEndpoints = 1; + uac2->as_out_intf = ret; + uac2->as_out_alt = 0; + + if (EPOUT_FBACK_IN_EN(uac2_opts)) { + fs_epout_desc.bmAttributes = + USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC; + hs_epout_desc.bmAttributes = + USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC; + ss_epout_desc.bmAttributes = + USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC; + std_as_out_if1_desc.bNumEndpoints++; + } else { + fs_epout_desc.bmAttributes = + USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE; + hs_epout_desc.bmAttributes = + USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE; + ss_epout_desc.bmAttributes = + USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE; + } + } + + if (EPIN_EN(uac2_opts)) { + ret = usb_interface_id(cfg, fn); + if (ret < 0) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + goto err_free_fu; + } + std_as_in_if0_desc.bInterfaceNumber = ret; + std_as_in_if1_desc.bInterfaceNumber = ret; + uac2->as_in_intf = ret; + uac2->as_in_alt = 0; + } + + std_ac_if_desc.bNumEndpoints = 0; + if (FUOUT_EN(uac2_opts) || FUIN_EN(uac2_opts)) { + uac2->int_ep = usb_ep_autoconfig(gadget, &fs_ep_int_desc); + if (!uac2->int_ep) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + ret = -ENODEV; + goto err_free_fu; + } + + std_ac_if_desc.bNumEndpoints = 1; + } + + hs_epin_desc.bInterval = uac2_opts->p_hs_bint; + ss_epin_desc.bInterval = uac2_opts->p_hs_bint; + hs_epout_desc.bInterval = uac2_opts->c_hs_bint; + ss_epout_desc.bInterval = uac2_opts->c_hs_bint; + + /* Calculate wMaxPacketSize according to audio bandwidth */ + ret = set_ep_max_packet_size_bint(dev, uac2_opts, &fs_epin_desc, + USB_SPEED_FULL, true); + if (ret < 0) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return ret; + } + + ret = set_ep_max_packet_size_bint(dev, uac2_opts, &fs_epout_desc, + USB_SPEED_FULL, false); + if (ret < 0) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return ret; + } + + ret = set_ep_max_packet_size_bint(dev, uac2_opts, &hs_epin_desc, + USB_SPEED_HIGH, true); + if (ret < 0) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return ret; + } + + ret = set_ep_max_packet_size_bint(dev, uac2_opts, &hs_epout_desc, + USB_SPEED_HIGH, false); + if (ret < 0) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return ret; + } + + ret = set_ep_max_packet_size_bint(dev, uac2_opts, &ss_epin_desc, + USB_SPEED_SUPER, true); + if (ret < 0) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return ret; + } + + ret = set_ep_max_packet_size_bint(dev, uac2_opts, &ss_epout_desc, + USB_SPEED_SUPER, false); + if (ret < 0) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return ret; + } + + if (EPOUT_EN(uac2_opts)) { + agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc); + if (!agdev->out_ep) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + ret = -ENODEV; + goto err_free_fu; + } + if (EPOUT_FBACK_IN_EN(uac2_opts)) { + agdev->in_ep_fback = usb_ep_autoconfig(gadget, + &fs_epin_fback_desc); + if (!agdev->in_ep_fback) { + dev_err(dev, "%s:%d Error!\n", + __func__, __LINE__); + ret = -ENODEV; + goto err_free_fu; + } + } + } + + if (EPIN_EN(uac2_opts)) { + agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc); + if (!agdev->in_ep) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + ret = -ENODEV; + goto err_free_fu; + } + } + + agdev->in_ep_maxpsize = max_t(u16, + le16_to_cpu(fs_epin_desc.wMaxPacketSize), + le16_to_cpu(hs_epin_desc.wMaxPacketSize)); + agdev->out_ep_maxpsize = max_t(u16, + le16_to_cpu(fs_epout_desc.wMaxPacketSize), + le16_to_cpu(hs_epout_desc.wMaxPacketSize)); + + agdev->in_ep_maxpsize = max_t(u16, agdev->in_ep_maxpsize, + le16_to_cpu(ss_epin_desc.wMaxPacketSize)); + agdev->out_ep_maxpsize = max_t(u16, agdev->out_ep_maxpsize, + le16_to_cpu(ss_epout_desc.wMaxPacketSize)); + + ss_epin_desc_comp.wBytesPerInterval = ss_epin_desc.wMaxPacketSize; + ss_epout_desc_comp.wBytesPerInterval = ss_epout_desc.wMaxPacketSize; + + // HS and SS endpoint addresses are copied from autoconfigured FS descriptors + hs_ep_int_desc.bEndpointAddress = fs_ep_int_desc.bEndpointAddress; + hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress; + hs_epin_fback_desc.bEndpointAddress = fs_epin_fback_desc.bEndpointAddress; + hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress; + ss_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress; + ss_epin_fback_desc.bEndpointAddress = fs_epin_fback_desc.bEndpointAddress; + ss_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress; + ss_ep_int_desc.bEndpointAddress = fs_ep_int_desc.bEndpointAddress; + + setup_descriptor(uac2_opts); + + ret = usb_assign_descriptors(fn, fs_audio_desc, hs_audio_desc, ss_audio_desc, + ss_audio_desc); + if (ret) + goto err_free_fu; + + agdev->gadget = gadget; + + agdev->params.p_chmask = uac2_opts->p_chmask; + memcpy(agdev->params.p_srates, uac2_opts->p_srates, + sizeof(agdev->params.p_srates)); + agdev->params.p_ssize = uac2_opts->p_ssize; + if (FUIN_EN(uac2_opts)) { + agdev->params.p_fu.id = USB_IN_FU_ID; + agdev->params.p_fu.mute_present = uac2_opts->p_mute_present; + agdev->params.p_fu.volume_present = uac2_opts->p_volume_present; + agdev->params.p_fu.volume_min = uac2_opts->p_volume_min; + agdev->params.p_fu.volume_max = uac2_opts->p_volume_max; + agdev->params.p_fu.volume_res = uac2_opts->p_volume_res; + } + agdev->params.c_chmask = uac2_opts->c_chmask; + memcpy(agdev->params.c_srates, uac2_opts->c_srates, + sizeof(agdev->params.c_srates)); + agdev->params.c_ssize = uac2_opts->c_ssize; + if (FUOUT_EN(uac2_opts)) { + agdev->params.c_fu.id = USB_OUT_FU_ID; + agdev->params.c_fu.mute_present = uac2_opts->c_mute_present; + agdev->params.c_fu.volume_present = uac2_opts->c_volume_present; + agdev->params.c_fu.volume_min = uac2_opts->c_volume_min; + agdev->params.c_fu.volume_max = uac2_opts->c_volume_max; + agdev->params.c_fu.volume_res = uac2_opts->c_volume_res; + } + agdev->params.req_number = uac2_opts->req_number; + agdev->params.fb_max = uac2_opts->fb_max; + + if (FUOUT_EN(uac2_opts) || FUIN_EN(uac2_opts)) + agdev->notify = afunc_notify; + + ret = g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget"); + if (ret) + goto err_free_descs; + + return 0; + +err_free_descs: + usb_free_all_descriptors(fn); + agdev->gadget = NULL; +err_free_fu: + kfree(out_feature_unit_desc); + out_feature_unit_desc = NULL; + kfree(in_feature_unit_desc); + in_feature_unit_desc = NULL; + return ret; +} + +static void +afunc_notify_complete(struct usb_ep *_ep, struct usb_request *req) +{ + struct g_audio *agdev = req->context; + struct f_uac2 *uac2 = func_to_uac2(&agdev->func); + + atomic_dec(&uac2->int_count); + kfree(req->buf); + usb_ep_free_request(_ep, req); +} + +static int +afunc_notify(struct g_audio *agdev, int unit_id, int cs) +{ + struct f_uac2 *uac2 = func_to_uac2(&agdev->func); + struct usb_request *req; + struct uac2_interrupt_data_msg *msg; + u16 w_index, w_value; + int ret; + + if (!uac2->int_ep->enabled) + return 0; + + if (atomic_inc_return(&uac2->int_count) > UAC2_DEF_INT_REQ_NUM) { + atomic_dec(&uac2->int_count); + return 0; + } + + req = usb_ep_alloc_request(uac2->int_ep, GFP_ATOMIC); + if (req == NULL) { + ret = -ENOMEM; + goto err_dec_int_count; + } + + msg = kzalloc_obj(*msg, GFP_ATOMIC); + if (msg == NULL) { + ret = -ENOMEM; + goto err_free_request; + } + + w_index = unit_id << 8 | uac2->ac_intf; + w_value = cs << 8; + + msg->bInfo = 0; /* Non-vendor, interface interrupt */ + msg->bAttribute = UAC2_CS_CUR; + msg->wIndex = cpu_to_le16(w_index); + msg->wValue = cpu_to_le16(w_value); + + req->length = sizeof(*msg); + req->buf = msg; + req->context = agdev; + req->complete = afunc_notify_complete; + + ret = usb_ep_queue(uac2->int_ep, req, GFP_ATOMIC); + + if (ret) + goto err_free_msg; + + return 0; + +err_free_msg: + kfree(msg); +err_free_request: + usb_ep_free_request(uac2->int_ep, req); +err_dec_int_count: + atomic_dec(&uac2->int_count); + + return ret; +} + +static int +afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) +{ + struct usb_composite_dev *cdev = fn->config->cdev; + struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); + struct usb_gadget *gadget = cdev->gadget; + struct device *dev = &gadget->dev; + int ret = 0; + + /* No i/f has more than 2 alt settings */ + if (alt > 1) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + if (intf == uac2->ac_intf) { + /* Control I/f has only 1 AltSetting - 0 */ + if (alt) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + /* restart interrupt endpoint */ + if (uac2->int_ep) { + usb_ep_disable(uac2->int_ep); + config_ep_by_speed(gadget, &agdev->func, uac2->int_ep); + usb_ep_enable(uac2->int_ep); + } + + return 0; + } + + if (intf == uac2->as_out_intf) { + uac2->as_out_alt = alt; + + if (alt) + ret = u_audio_start_capture(&uac2->g_audio); + else + u_audio_stop_capture(&uac2->g_audio); + } else if (intf == uac2->as_in_intf) { + uac2->as_in_alt = alt; + + if (alt) + ret = u_audio_start_playback(&uac2->g_audio); + else + u_audio_stop_playback(&uac2->g_audio); + } else { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + return ret; +} + +static int +afunc_get_alt(struct usb_function *fn, unsigned intf) +{ + struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); + + if (intf == uac2->ac_intf) + return uac2->ac_alt; + else if (intf == uac2->as_out_intf) + return uac2->as_out_alt; + else if (intf == uac2->as_in_intf) + return uac2->as_in_alt; + else + dev_err(&agdev->gadget->dev, + "%s:%d Invalid Interface %d!\n", + __func__, __LINE__, intf); + + return -EINVAL; +} + +static void +afunc_disable(struct usb_function *fn) +{ + struct f_uac2 *uac2 = func_to_uac2(fn); + + uac2->as_in_alt = 0; + uac2->as_out_alt = 0; + u_audio_stop_capture(&uac2->g_audio); + u_audio_stop_playback(&uac2->g_audio); + if (uac2->int_ep) + usb_ep_disable(uac2->int_ep); +} + +static void +afunc_suspend(struct usb_function *fn) +{ + struct f_uac2 *uac2 = func_to_uac2(fn); + + u_audio_suspend(&uac2->g_audio); +} + +static int +in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *agdev = func_to_g_audio(fn); + struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); + 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; + u32 p_srate, c_srate; + + u_audio_get_playback_srate(agdev, &p_srate); + u_audio_get_capture_srate(agdev, &c_srate); + + if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) { + if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { + struct cntrl_cur_lay3 c; + + memset(&c, 0, sizeof(struct cntrl_cur_lay3)); + + if (entity_id == USB_IN_CLK_ID) + c.dCUR = cpu_to_le32(p_srate); + else if (entity_id == USB_OUT_CLK_ID) + c.dCUR = cpu_to_le32(c_srate); + + value = min_t(unsigned int, 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 int, w_length, 1); + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_MUTE) { + unsigned int mute; + + u_audio_get_mute(agdev, is_playback, &mute); + + *(u8 *)req->buf = mute; + value = min_t(unsigned int, w_length, 1); + } else if (control_selector == UAC_FU_VOLUME) { + struct cntrl_cur_lay2 c; + s16 volume; + + memset(&c, 0, sizeof(struct cntrl_cur_lay2)); + + u_audio_get_volume(agdev, is_playback, &volume); + c.wCUR = cpu_to_le16(volume); + + value = min_t(unsigned int, w_length, sizeof(c)); + memcpy(req->buf, &c, value); + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else { + dev_err(&agdev->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, 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 g_audio *agdev = func_to_g_audio(fn); + struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); + 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 ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) { + if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { + struct cntrl_ranges_lay3_srates rs; + int i; + int wNumSubRanges = 0; + int srate; + int *srates; + + if (entity_id == USB_IN_CLK_ID) + srates = opts->p_srates; + else if (entity_id == USB_OUT_CLK_ID) + srates = opts->c_srates; + else + return -EOPNOTSUPP; + for (i = 0; i < UAC_MAX_RATES; i++) { + srate = srates[i]; + if (srate == 0) + break; + + rs.r[wNumSubRanges].dMIN = cpu_to_le32(srate); + rs.r[wNumSubRanges].dMAX = cpu_to_le32(srate); + rs.r[wNumSubRanges].dRES = 0; + wNumSubRanges++; + dev_dbg(&agdev->gadget->dev, + "%s(): clk %d: rate ID %d: %d\n", + __func__, entity_id, wNumSubRanges, srate); + } + rs.wNumSubRanges = cpu_to_le16(wNumSubRanges); + value = min_t(unsigned int, w_length, ranges_lay3_size(rs)); + dev_dbg(&agdev->gadget->dev, "%s(): sending %d rates, size %d\n", + __func__, rs.wNumSubRanges, value); + memcpy(req->buf, &rs, value); + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_VOLUME) { + struct cntrl_range_lay2 r; + s16 max_db, min_db, res_db; + + if (is_playback) { + max_db = opts->p_volume_max; + min_db = opts->p_volume_min; + res_db = opts->p_volume_res; + } else { + max_db = opts->c_volume_max; + min_db = opts->c_volume_min; + res_db = opts->c_volume_res; + } + + r.wMAX = cpu_to_le16(max_db); + r.wMIN = cpu_to_le16(min_db); + r.wRES = cpu_to_le16(res_db); + r.wNumSubRanges = cpu_to_le16(1); + + value = min_t(unsigned int, w_length, sizeof(r)); + memcpy(req->buf, &r, value); + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else { + dev_err(&agdev->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, 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 void uac2_cs_control_sam_freq(struct usb_ep *ep, struct usb_request *req) +{ + struct usb_function *fn = ep->driver_data; + struct g_audio *agdev = func_to_g_audio(fn); + struct f_uac2 *uac2 = func_to_uac2(fn); + u32 val; + + if (req->actual != 4) + return; + + val = le32_to_cpu(*((__le32 *)req->buf)); + dev_dbg(&agdev->gadget->dev, "%s val: %d.\n", __func__, val); + if (uac2->clock_id == USB_IN_CLK_ID) { + u_audio_set_playback_srate(agdev, val); + } else if (uac2->clock_id == USB_OUT_CLK_ID) { + u_audio_set_capture_srate(agdev, val); + } +} + +static void +out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct g_audio *agdev = req->context; + struct usb_composite_dev *cdev = agdev->func.config->cdev; + struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); + struct f_uac2 *uac2 = func_to_uac2(&agdev->func); + struct usb_ctrlrequest *cr = &uac2->setup_cr; + 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; + + if (req->status != 0) { + dev_dbg(&cdev->gadget->dev, "completion err %d\n", req->status); + return; + } + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_MUTE) { + u8 mute = *(u8 *)req->buf; + + u_audio_set_mute(agdev, is_playback, mute); + + return; + } else if (control_selector == UAC_FU_VOLUME) { + struct cntrl_cur_lay2 *c = req->buf; + s16 volume; + + volume = le16_to_cpu(c->wCUR); + u_audio_set_volume(agdev, is_playback, volume); + + return; + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + usb_ep_set_halt(ep); + } + } +} + +static int +out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_composite_dev *cdev = fn->config->cdev; + struct usb_request *req = fn->config->cdev->req; + struct g_audio *agdev = func_to_g_audio(fn); + struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); + struct f_uac2 *uac2 = func_to_uac2(fn); + 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; + u8 clock_id = w_index >> 8; + + if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) { + if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { + dev_dbg(&agdev->gadget->dev, + "control_selector UAC2_CS_CONTROL_SAM_FREQ, clock: %d\n", clock_id); + cdev->gadget->ep0->driver_data = fn; + uac2->clock_id = clock_id; + req->complete = uac2_cs_control_sam_freq; + return w_length; + } + } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + memcpy(&uac2->setup_cr, cr, sizeof(*cr)); + req->context = agdev; + req->complete = out_rq_cur_complete; + + return w_length; + } else { + dev_err(&agdev->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + return -EOPNOTSUPP; +} + +static int +setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); + u16 w_index = le16_to_cpu(cr->wIndex); + u8 intf = w_index & 0xff; + + if (intf != uac2->ac_intf) { + dev_err(&agdev->gadget->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 g_audio *agdev = func_to_g_audio(fn); + 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(&agdev->gadget->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(&agdev->gadget->dev, + "%s:%d Error!\n", __func__, __LINE__); + req->status = 0; + } + } + + return value; +} + +static inline struct f_uac2_opts *to_f_uac2_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_uac2_opts, + func_inst.group); +} + +static void f_uac2_attr_release(struct config_item *item) +{ + struct f_uac2_opts *opts = to_f_uac2_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static const struct configfs_item_operations f_uac2_item_ops = { + .release = f_uac2_attr_release, +}; + +#define uac2_kstrtou8 kstrtou8 +#define uac2_kstrtou32 kstrtou32 +#define uac2_kstrtos16 kstrtos16 +#define uac2_kstrtobool(s, base, res) kstrtobool((s), (res)) + +static const char *u8_fmt = "%u\n"; +static const char *u32_fmt = "%u\n"; +static const char *s16_fmt = "%hd\n"; +static const char *bool_fmt = "%u\n"; + +#define UAC2_ATTRIBUTE(type, name) \ +static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac2_opts *opts = to_f_uac2_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, type##_fmt, opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac2_opts *opts = to_f_uac2_opts(item); \ + int ret; \ + type num; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = uac2_kstrto##type(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + opts->name = num; \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac2_opts_, name) + +#define UAC2_ATTRIBUTE_SYNC(name) \ +static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac2_opts *opts = to_f_uac2_opts(item); \ + int result; \ + char *str; \ + \ + mutex_lock(&opts->lock); \ + switch (opts->name) { \ + case USB_ENDPOINT_SYNC_ASYNC: \ + str = "async"; \ + break; \ + case USB_ENDPOINT_SYNC_ADAPTIVE: \ + str = "adaptive"; \ + break; \ + default: \ + str = "unknown"; \ + break; \ + } \ + result = sprintf(page, "%s\n", str); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac2_opts *opts = to_f_uac2_opts(item); \ + int ret = 0; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + if (!strncmp(page, "async", 5)) \ + opts->name = USB_ENDPOINT_SYNC_ASYNC; \ + else if (!strncmp(page, "adaptive", 8)) \ + opts->name = USB_ENDPOINT_SYNC_ADAPTIVE; \ + else { \ + ret = -EINVAL; \ + goto end; \ + } \ + \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac2_opts_, name) + +#define UAC2_RATE_ATTRIBUTE(name) \ +static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac2_opts *opts = to_f_uac2_opts(item); \ + int result = 0; \ + int i; \ + \ + mutex_lock(&opts->lock); \ + page[0] = '\0'; \ + for (i = 0; i < UAC_MAX_RATES; i++) { \ + if (opts->name##s[i] == 0) \ + break; \ + result += sprintf(page + strlen(page), "%u,", \ + opts->name##s[i]); \ + } \ + if (strlen(page) > 0) \ + page[strlen(page) - 1] = '\n'; \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac2_opts *opts = to_f_uac2_opts(item); \ + char *buf = NULL; \ + char *split_page; \ + int ret = -EINVAL; \ + char *token; \ + u32 num; \ + int i; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + i = 0; \ + memset(opts->name##s, 0x00, sizeof(opts->name##s)); \ + buf = kstrdup(page, GFP_KERNEL); \ + split_page = buf; \ + while ((token = strsep(&split_page, ",")) != NULL) { \ + ret = kstrtou32(token, 0, &num); \ + if (ret) \ + goto end; \ + if (i >= UAC_MAX_RATES) { \ + ret = -EINVAL; \ + goto end; \ + } \ + opts->name##s[i++] = num; \ + ret = len; \ + }; \ + \ +end: \ + kfree(buf); \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac2_opts_, name) + +#define UAC2_ATTRIBUTE_STRING(name) \ +static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac2_opts *opts = to_f_uac2_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sysfs_emit(page, "%s", opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac2_opts *opts = to_f_uac2_opts(item); \ + int ret = len; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + if (len && page[len - 1] == '\n') \ + len--; \ + \ + scnprintf(opts->name, min(sizeof(opts->name), len + 1), \ + "%s", page); \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac2_opts_, name) + +UAC2_ATTRIBUTE(u32, p_chmask); +UAC2_RATE_ATTRIBUTE(p_srate); +UAC2_ATTRIBUTE(u32, p_ssize); +UAC2_ATTRIBUTE(u8, p_hs_bint); +UAC2_ATTRIBUTE(u32, c_chmask); +UAC2_RATE_ATTRIBUTE(c_srate); +UAC2_ATTRIBUTE_SYNC(c_sync); +UAC2_ATTRIBUTE(u32, c_ssize); +UAC2_ATTRIBUTE(u8, c_hs_bint); +UAC2_ATTRIBUTE(u32, req_number); + +UAC2_ATTRIBUTE(bool, p_mute_present); +UAC2_ATTRIBUTE(bool, p_volume_present); +UAC2_ATTRIBUTE(s16, p_volume_min); +UAC2_ATTRIBUTE(s16, p_volume_max); +UAC2_ATTRIBUTE(s16, p_volume_res); + +UAC2_ATTRIBUTE(bool, c_mute_present); +UAC2_ATTRIBUTE(bool, c_volume_present); +UAC2_ATTRIBUTE(s16, c_volume_min); +UAC2_ATTRIBUTE(s16, c_volume_max); +UAC2_ATTRIBUTE(s16, c_volume_res); +UAC2_ATTRIBUTE(u32, fb_max); +UAC2_ATTRIBUTE_STRING(function_name); +UAC2_ATTRIBUTE_STRING(if_ctrl_name); +UAC2_ATTRIBUTE_STRING(clksrc_in_name); +UAC2_ATTRIBUTE_STRING(clksrc_out_name); + +UAC2_ATTRIBUTE_STRING(p_it_name); +UAC2_ATTRIBUTE_STRING(p_it_ch_name); +UAC2_ATTRIBUTE_STRING(p_ot_name); +UAC2_ATTRIBUTE_STRING(p_fu_vol_name); + +UAC2_ATTRIBUTE_STRING(c_it_name); +UAC2_ATTRIBUTE_STRING(c_it_ch_name); +UAC2_ATTRIBUTE_STRING(c_ot_name); +UAC2_ATTRIBUTE_STRING(c_fu_vol_name); + +UAC2_ATTRIBUTE(s16, p_terminal_type); +UAC2_ATTRIBUTE(s16, c_terminal_type); + + +static struct configfs_attribute *f_uac2_attrs[] = { + &f_uac2_opts_attr_p_chmask, + &f_uac2_opts_attr_p_srate, + &f_uac2_opts_attr_p_ssize, + &f_uac2_opts_attr_p_hs_bint, + &f_uac2_opts_attr_c_chmask, + &f_uac2_opts_attr_c_srate, + &f_uac2_opts_attr_c_ssize, + &f_uac2_opts_attr_c_hs_bint, + &f_uac2_opts_attr_c_sync, + &f_uac2_opts_attr_req_number, + &f_uac2_opts_attr_fb_max, + + &f_uac2_opts_attr_p_mute_present, + &f_uac2_opts_attr_p_volume_present, + &f_uac2_opts_attr_p_volume_min, + &f_uac2_opts_attr_p_volume_max, + &f_uac2_opts_attr_p_volume_res, + + &f_uac2_opts_attr_c_mute_present, + &f_uac2_opts_attr_c_volume_present, + &f_uac2_opts_attr_c_volume_min, + &f_uac2_opts_attr_c_volume_max, + &f_uac2_opts_attr_c_volume_res, + + &f_uac2_opts_attr_function_name, + &f_uac2_opts_attr_if_ctrl_name, + &f_uac2_opts_attr_clksrc_in_name, + &f_uac2_opts_attr_clksrc_out_name, + + &f_uac2_opts_attr_p_it_name, + &f_uac2_opts_attr_p_it_ch_name, + &f_uac2_opts_attr_p_ot_name, + &f_uac2_opts_attr_p_fu_vol_name, + + &f_uac2_opts_attr_c_it_name, + &f_uac2_opts_attr_c_it_ch_name, + &f_uac2_opts_attr_c_ot_name, + &f_uac2_opts_attr_c_fu_vol_name, + + &f_uac2_opts_attr_p_terminal_type, + &f_uac2_opts_attr_c_terminal_type, + + NULL, +}; + +static const struct config_item_type f_uac2_func_type = { + .ct_item_ops = &f_uac2_item_ops, + .ct_attrs = f_uac2_attrs, + .ct_owner = THIS_MODULE, +}; + +static void afunc_free_inst(struct usb_function_instance *f) +{ + struct f_uac2_opts *opts; + + opts = container_of(f, struct f_uac2_opts, func_inst); + kfree(opts); +} + +static struct usb_function_instance *afunc_alloc_inst(void) +{ + struct f_uac2_opts *opts; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = afunc_free_inst; + + config_group_init_type_name(&opts->func_inst.group, "", + &f_uac2_func_type); + + opts->p_chmask = UAC2_DEF_PCHMASK; + opts->p_srates[0] = UAC2_DEF_PSRATE; + opts->p_ssize = UAC2_DEF_PSSIZE; + opts->p_hs_bint = UAC2_DEF_PHSBINT; + opts->c_chmask = UAC2_DEF_CCHMASK; + opts->c_srates[0] = UAC2_DEF_CSRATE; + opts->c_ssize = UAC2_DEF_CSSIZE; + opts->c_hs_bint = UAC2_DEF_CHSBINT; + opts->c_sync = UAC2_DEF_CSYNC; + + opts->p_mute_present = UAC2_DEF_MUTE_PRESENT; + opts->p_volume_present = UAC2_DEF_VOLUME_PRESENT; + opts->p_volume_min = UAC2_DEF_MIN_DB; + opts->p_volume_max = UAC2_DEF_MAX_DB; + opts->p_volume_res = UAC2_DEF_RES_DB; + + opts->c_mute_present = UAC2_DEF_MUTE_PRESENT; + opts->c_volume_present = UAC2_DEF_VOLUME_PRESENT; + opts->c_volume_min = UAC2_DEF_MIN_DB; + opts->c_volume_max = UAC2_DEF_MAX_DB; + opts->c_volume_res = UAC2_DEF_RES_DB; + + opts->req_number = UAC2_DEF_REQ_NUM; + opts->fb_max = FBACK_FAST_MAX; + + scnprintf(opts->function_name, sizeof(opts->function_name), "Source/Sink"); + scnprintf(opts->if_ctrl_name, sizeof(opts->if_ctrl_name), "Topology Control"); + scnprintf(opts->clksrc_in_name, sizeof(opts->clksrc_in_name), "Input Clock"); + scnprintf(opts->clksrc_out_name, sizeof(opts->clksrc_out_name), "Output Clock"); + + scnprintf(opts->p_it_name, sizeof(opts->p_it_name), "USBD Out"); + scnprintf(opts->p_it_ch_name, sizeof(opts->p_it_ch_name), "Capture Channels"); + scnprintf(opts->p_ot_name, sizeof(opts->p_ot_name), "USBH In"); + scnprintf(opts->p_fu_vol_name, sizeof(opts->p_fu_vol_name), "Capture Volume"); + + scnprintf(opts->c_it_name, sizeof(opts->c_it_name), "USBH Out"); + scnprintf(opts->c_it_ch_name, sizeof(opts->c_it_ch_name), "Playback Channels"); + scnprintf(opts->c_ot_name, sizeof(opts->c_ot_name), "USBD In"); + scnprintf(opts->c_fu_vol_name, sizeof(opts->c_fu_vol_name), "Playback Volume"); + + opts->p_terminal_type = UAC2_DEF_P_TERM_TYPE; + opts->c_terminal_type = UAC2_DEF_C_TERM_TYPE; + + return &opts->func_inst; +} + +static void afunc_free(struct usb_function *f) +{ + struct g_audio *agdev; + struct f_uac2_opts *opts; + + agdev = func_to_g_audio(f); + opts = container_of(f->fi, struct f_uac2_opts, func_inst); + kfree(agdev); + mutex_lock(&opts->lock); + --opts->refcnt; + mutex_unlock(&opts->lock); +} + +static void afunc_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct g_audio *agdev = func_to_g_audio(f); + + g_audio_cleanup(agdev); + usb_free_all_descriptors(f); + + agdev->gadget = NULL; + + kfree(out_feature_unit_desc); + out_feature_unit_desc = NULL; + kfree(in_feature_unit_desc); + in_feature_unit_desc = NULL; +} + +static struct usb_function *afunc_alloc(struct usb_function_instance *fi) +{ + struct f_uac2 *uac2; + struct f_uac2_opts *opts; + + uac2 = kzalloc_obj(*uac2); + if (uac2 == NULL) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_uac2_opts, func_inst); + mutex_lock(&opts->lock); + ++opts->refcnt; + mutex_unlock(&opts->lock); + + uac2->g_audio.func.name = "uac2_func"; + uac2->g_audio.func.bind = afunc_bind; + uac2->g_audio.func.unbind = afunc_unbind; + uac2->g_audio.func.set_alt = afunc_set_alt; + uac2->g_audio.func.get_alt = afunc_get_alt; + uac2->g_audio.func.disable = afunc_disable; + uac2->g_audio.func.suspend = afunc_suspend; + uac2->g_audio.func.setup = afunc_setup; + uac2->g_audio.func.free_func = afunc_free; + + return &uac2->g_audio.func; +} + +DECLARE_USB_FUNCTION_INIT(uac2, afunc_alloc_inst, afunc_alloc); +MODULE_DESCRIPTION("USB Audio Class 2.0 Function"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yadwinder Singh"); +MODULE_AUTHOR("Jaswinder Singh"); +MODULE_AUTHOR("Ruslan Bilovol"); diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c new file mode 100644 index 000000000..d1bf3ea75 --- /dev/null +++ b/drivers/usb/gadget/function/f_uvc.c @@ -0,0 +1,1181 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * uvc_gadget.c -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "uvc.h" +#include "uvc_configfs.h" +#include "uvc_v4l2.h" +#include "uvc_video.h" + +unsigned int uvc_gadget_trace_param; +module_param_named(trace, uvc_gadget_trace_param, uint, 0644); +MODULE_PARM_DESC(trace, "Trace level bitmask"); + +/* -------------------------------------------------------------------------- + * Function descriptors + */ + +/* string IDs are assigned dynamically */ + +static struct usb_string uvc_en_us_strings[] = { + /* [UVC_STRING_CONTROL_IDX].s = DYNAMIC, */ + [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 = { + .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 = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = UVC_INTF_VIDEO_CONTROL, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = UVC_SC_VIDEOCONTROL, + .bInterfaceProtocol = 0x00, + .iInterface = 0, +}; + +static struct usb_endpoint_descriptor uvc_interrupt_ep = { + .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_interrupt_comp = { + .bLength = sizeof(uvc_ss_interrupt_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_interrupt_cs_ep = { + .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 = { + .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 = { + .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 = { + .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. + */ +}; + +static struct usb_endpoint_descriptor uvc_hs_streaming_ep = { + .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. + */ +}; + +static struct usb_endpoint_descriptor uvc_ss_streaming_ep = { + .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. + */ +}; + +static struct usb_ss_ep_comp_descriptor uvc_ss_streaming_comp = { + .bLength = sizeof(uvc_ss_streaming_comp), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + /* + * The bMaxBurst, bmAttributes and wBytesPerInterval values will be + * initialized from module parameters. + */ +}; + +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 = min_t(unsigned int, req->actual, + sizeof(uvc_event->data.data)); + memcpy(&uvc_event->data.data, req->buf, uvc_event->data.length); + 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; + unsigned int interface = le16_to_cpu(ctrl->wIndex) & 0xff; + struct usb_ctrlrequest *mctrl; + + if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS) { + uvcg_info(f, "invalid request type\n"); + return -EINVAL; + } + + /* Stall too big requests. */ + if (le16_to_cpu(ctrl->wLength) > UVC_MAX_REQUEST_SIZE) + return -EINVAL; + + /* + * Tell the complete callback to generate an event for the next request + * that will be enqueued by UVCIOC_SEND_RESPONSE. + */ + uvc->event_setup_out = !(ctrl->bRequestType & USB_DIR_IN); + uvc->event_length = le16_to_cpu(ctrl->wLength); + + memset(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_SETUP; + memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req)); + + /* check for the interface number, fixup the interface number in + * the ctrl request so the userspace doesn't have to bother with + * offset and configfs parsing + */ + mctrl = &uvc_event->req; + mctrl->wIndex &= ~cpu_to_le16(0xff); + if (interface == uvc->streaming_intf) + mctrl->wIndex = cpu_to_le16(UVC_STRING_STREAMING_IDX); + + v4l2_event_queue(&uvc->vdev, &v4l2_event); + + return 0; +} + +void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep) +{ + struct usb_composite_dev *cdev = uvc->func.config->cdev; + + if (disable_ep && uvc->video.ep) + usb_ep_disable(uvc->video.ep); + + usb_composite_setup_continue(cdev); +} + +static int +uvc_function_get_alt(struct usb_function *f, unsigned interface) +{ + struct uvc_device *uvc = to_uvc(f); + + uvcg_info(f, "%s(%u)\n", __func__, interface); + + if (interface == uvc->control_intf) + return 0; + else if (interface != uvc->streaming_intf) + return -EINVAL; + else + return uvc->video.ep->enabled ? 1 : 0; +} + +static int +uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt) +{ + struct uvc_device *uvc = to_uvc(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct v4l2_event v4l2_event; + struct uvc_event *uvc_event = (void *)&v4l2_event.u.data; + int ret; + + uvcg_info(f, "%s(%u, %u)\n", __func__, interface, alt); + + if (interface == uvc->control_intf) { + if (alt) + return -EINVAL; + + if (uvc->enable_interrupt_ep) { + uvcg_info(f, "reset UVC interrupt endpoint\n"); + usb_ep_disable(uvc->interrupt_ep); + + if (!uvc->interrupt_ep->desc) + if (config_ep_by_speed(cdev->gadget, f, + uvc->interrupt_ep)) + return -EINVAL; + + usb_ep_enable(uvc->interrupt_ep); + } + + if (uvc->state == UVC_STATE_DISCONNECTED) { + memset(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_CONNECT; + uvc_event->speed = 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; + + memset(&v4l2_event, 0, sizeof(v4l2_event)); + v4l2_event.type = UVC_EVENT_STREAMOFF; + v4l2_event_queue(&uvc->vdev, &v4l2_event); + + return USB_GADGET_DELAYED_STATUS; + + case 1: + if (uvc->state != UVC_STATE_CONNECTED) + return 0; + + if (!uvc->video.ep) + return -EINVAL; + + uvcg_info(f, "reset UVC\n"); + usb_ep_disable(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); + + uvc->video.max_req_size = uvc->video.ep->maxpacket + * max_t(unsigned int, uvc->video.ep->maxburst, 1) + * (uvc->video.ep->mult); + + 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; + + uvcg_info(f, "%s()\n", __func__); + + 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; + + usb_ep_disable(uvc->video.ep); + if (uvc->enable_interrupt_ep) + usb_ep_disable(uvc->interrupt_ep); +} + +/* -------------------------------------------------------------------------- + * Connection / disconnection + */ + +void +uvc_function_connect(struct uvc_device *uvc) +{ + int ret; + + if ((ret = usb_function_activate(&uvc->func)) < 0) + uvcg_info(&uvc->func, "UVC connect failed with %d\n", ret); +} + +void +uvc_function_disconnect(struct uvc_device *uvc) +{ + int ret; + + guard(mutex)(&uvc->lock); + if (uvc->func_unbound) { + dev_dbg(&uvc->vdev.dev, "skipping function deactivate (unbound)\n"); + return; + } + + if ((ret = usb_function_deactivate(&uvc->func)) < 0) + uvcg_info(&uvc->func, "UVC disconnect failed with %d\n", ret); +} + +/* -------------------------------------------------------------------------- + * USB probe and disconnect + */ + +static ssize_t function_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uvc_device *uvc = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", uvc->func.fi->group.cg_item.ci_name); +} + +static DEVICE_ATTR_RO(function_name); + +static void uvc_vdev_release(struct video_device *vdev) +{ + struct uvc_device *uvc = video_get_drvdata(vdev); + + /* Signal uvc_function_unbind() that the video device has been released */ + if (uvc->vdev_release_done) + complete(uvc->vdev_release_done); +} + +static int +uvc_register_video(struct uvc_device *uvc) +{ + struct usb_composite_dev *cdev = uvc->func.config->cdev; + int ret; + + /* TODO reference counting. */ + memset(&uvc->vdev, 0, sizeof(uvc->vdev)); + uvc->vdev.v4l2_dev = &uvc->v4l2_dev; + uvc->vdev.v4l2_dev->dev = &cdev->gadget->dev; + uvc->vdev.fops = &uvc_v4l2_fops; + uvc->vdev.ioctl_ops = &uvc_v4l2_ioctl_ops; + uvc->vdev.release = uvc_vdev_release; + uvc->vdev.vfl_dir = VFL_DIR_TX; + uvc->vdev.lock = &uvc->video.mutex; + uvc->vdev.device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + strscpy(uvc->vdev.name, cdev->gadget->name, sizeof(uvc->vdev.name)); + + video_set_drvdata(&uvc->vdev, uvc); + + ret = video_register_device(&uvc->vdev, VFL_TYPE_VIDEO, -1); + if (ret < 0) + return ret; + + ret = device_create_file(&uvc->vdev.dev, &dev_attr_function_name); + if (ret < 0) { + video_unregister_device(&uvc->vdev); + return ret; + } + + return 0; +} + +#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) + +#define UVC_COPY_XU_DESCRIPTOR(mem, dst, desc) \ + do { \ + *(dst)++ = mem; \ + memcpy(mem, desc, 22); /* bLength to bNrInPins */ \ + mem += 22; \ + \ + memcpy(mem, (desc)->baSourceID, (desc)->bNrInPins); \ + mem += (desc)->bNrInPins; \ + \ + memcpy(mem, &(desc)->bControlSize, 1); \ + mem++; \ + \ + memcpy(mem, (desc)->bmControls, (desc)->bControlSize); \ + mem += (desc)->bControlSize; \ + \ + memcpy(mem, &(desc)->iExtension, 1); \ + mem++; \ + } while (0) + +static struct usb_descriptor_header ** +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; + struct uvcg_extension *xu; + unsigned int control_size; + unsigned int streaming_size; + unsigned int n_desc; + unsigned int bytes; + void *mem; + + switch (speed) { + case USB_SPEED_SUPER_PLUS: + 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; + } + + if (!uvc_control_desc || !uvc_streaming_cls) + return ERR_PTR(-ENODEV); + + /* + * Descriptors layout + * + * uvc_iad + * uvc_control_intf + * Class-specific UVC control descriptors + * uvc_interrupt_ep + * uvc_interrupt_cs_ep + * uvc_ss_interrupt_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_streaming_intf_alt0.bLength; + + n_desc = 3; + if (uvc->enable_interrupt_ep) { + bytes += uvc_interrupt_ep.bLength + uvc_interrupt_cs_ep.bLength; + n_desc += 2; + + if (speed == USB_SPEED_SUPER || + speed == USB_SPEED_SUPER_PLUS) { + bytes += uvc_ss_interrupt_comp.bLength; + n_desc += 1; + } + } + + for (src = (const struct usb_descriptor_header **)uvc_control_desc; + *src; ++src) { + control_size += (*src)->bLength; + bytes += (*src)->bLength; + n_desc++; + } + + list_for_each_entry(xu, uvc->desc.extension_units, list) { + control_size += xu->desc.bLength; + bytes += xu->desc.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); + + list_for_each_entry(xu, uvc->desc.extension_units, list) + UVC_COPY_XU_DESCRIPTOR(mem, dst, &xu->desc); + + uvc_control_header->wTotalLength = cpu_to_le16(control_size); + uvc_control_header->bInCollection = 1; + uvc_control_header->baInterfaceNr[0] = uvc->streaming_intf; + + if (uvc->enable_interrupt_ep) { + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_interrupt_ep); + if (speed == USB_SPEED_SUPER || + speed == USB_SPEED_SUPER_PLUS) + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_ss_interrupt_comp); + + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_interrupt_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 int +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); + struct uvcg_extension *xu; + struct usb_string *us; + unsigned int max_packet_mult; + unsigned int max_packet_size; + struct usb_ep *ep; + struct f_uvc_opts *opts; + int ret = -EINVAL; + + uvcg_info(f, "%s()\n", __func__); + scoped_guard(mutex, &uvc->lock) + uvc->func_unbound = false; + + opts = fi_to_f_uvc_opts(f->fi); + /* Sanity check the streaming endpoint module parameters. */ + opts->streaming_interval = clamp(opts->streaming_interval, 1U, 16U); + opts->streaming_maxpacket = clamp(opts->streaming_maxpacket, 1U, 3072U); + opts->streaming_maxburst = min(opts->streaming_maxburst, 15U); + + /* For SS, wMaxPacketSize has to be 1024 if bMaxBurst is not 0 */ + if (opts->streaming_maxburst && + (opts->streaming_maxpacket % 1024) != 0) { + opts->streaming_maxpacket = roundup(opts->streaming_maxpacket, 1024); + uvcg_info(f, "overriding streaming_maxpacket to %d\n", + opts->streaming_maxpacket); + } + + /* + * 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 (opts->streaming_maxpacket <= 1024) { + max_packet_mult = 1; + max_packet_size = opts->streaming_maxpacket; + } else if (opts->streaming_maxpacket <= 2048) { + max_packet_mult = 2; + max_packet_size = opts->streaming_maxpacket / 2; + } else { + max_packet_mult = 3; + max_packet_size = opts->streaming_maxpacket / 3; + } + + uvc_fs_streaming_ep.wMaxPacketSize = + cpu_to_le16(min(opts->streaming_maxpacket, 1023U)); + uvc_fs_streaming_ep.bInterval = opts->streaming_interval; + + uvc_hs_streaming_ep.wMaxPacketSize = + cpu_to_le16(max_packet_size | ((max_packet_mult - 1) << 11)); + + /* A high-bandwidth endpoint must specify a bInterval value of 1 */ + if (max_packet_mult > 1) + uvc_hs_streaming_ep.bInterval = 1; + else + uvc_hs_streaming_ep.bInterval = opts->streaming_interval; + + uvc_ss_streaming_ep.wMaxPacketSize = cpu_to_le16(max_packet_size); + uvc_ss_streaming_ep.bInterval = opts->streaming_interval; + uvc_ss_streaming_comp.bmAttributes = max_packet_mult - 1; + uvc_ss_streaming_comp.bMaxBurst = opts->streaming_maxburst; + uvc_ss_streaming_comp.wBytesPerInterval = + cpu_to_le16(max_packet_size * max_packet_mult * + (opts->streaming_maxburst + 1)); + + /* Allocate endpoints. */ + if (opts->enable_interrupt_ep) { + ep = usb_ep_autoconfig(cdev->gadget, &uvc_interrupt_ep); + if (!ep) { + uvcg_info(f, "Unable to allocate interrupt EP\n"); + goto error; + } + uvc->interrupt_ep = ep; + uvc_control_intf.bNumEndpoints = 1; + } + uvc->enable_interrupt_ep = opts->enable_interrupt_ep; + + /* + * gadget_is_{super|dual}speed() API check UDC controller capitblity. It should pass down + * highest speed endpoint descriptor to UDC controller. So UDC controller driver can reserve + * enough resource at check_config(), especially mult and maxburst. So UDC driver (such as + * cdns3) can know need at least (mult + 1) * (maxburst + 1) * wMaxPacketSize internal + * memory for this uvc functions. This is the only straightforward method to resolve the UDC + * resource allocation issue in the current gadget framework. + */ + 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) { + uvcg_info(f, "Unable to allocate streaming EP\n"); + goto error; + } + uvc->video.ep = ep; + + 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; + + /* + * Hold opts->lock across both the XU string-descriptor fixup below and + * the descriptor-copy block further down. Without this, configfs + * uvcg_extension_drop() (which takes opts->lock) can race with the + * list_for_each_entry() walks here and inside uvc_copy_descriptors(), + * leading to a UAF on a freed struct uvcg_extension. See + * drivers/usb/gadget/function/uvc_configfs.c::uvcg_extension_drop(). + */ + mutex_lock(&opts->lock); + + /* + * XUs can have an arbitrary string descriptor describing them. If they + * have one pick up the ID. + */ + list_for_each_entry(xu, &opts->extension_units, list) + if (xu->string_descriptor_index) + xu->desc.iExtension = cdev->usb_strings[xu->string_descriptor_index].id; + + /* + * We attach the hard-coded defaults incase the user does not provide + * any more appropriate strings through configfs. + */ + uvc_en_us_strings[UVC_STRING_CONTROL_IDX].s = opts->function_name; + us = usb_gstrings_attach(cdev, uvc_function_strings, + ARRAY_SIZE(uvc_en_us_strings)); + if (IS_ERR(us)) { + ret = PTR_ERR(us); + goto error_unlock; + } + + uvc_iad.iFunction = opts->iad_index ? cdev->usb_strings[opts->iad_index].id : + us[UVC_STRING_CONTROL_IDX].id; + uvc_streaming_intf_alt0.iInterface = opts->vs0_index ? + cdev->usb_strings[opts->vs0_index].id : + us[UVC_STRING_STREAMING_IDX].id; + uvc_streaming_intf_alt1.iInterface = opts->vs1_index ? + cdev->usb_strings[opts->vs1_index].id : + us[UVC_STRING_STREAMING_IDX].id; + + /* Allocate interface IDs. */ + if ((ret = usb_interface_id(c, f)) < 0) + goto error_unlock; + uvc_iad.bFirstInterface = ret; + uvc_control_intf.bInterfaceNumber = ret; + uvc->control_intf = ret; + opts->control_interface = ret; + + if ((ret = usb_interface_id(c, f)) < 0) + goto error_unlock; + uvc_streaming_intf_alt0.bInterfaceNumber = ret; + uvc_streaming_intf_alt1.bInterfaceNumber = ret; + uvc->streaming_intf = ret; + opts->streaming_interface = ret; + + /* Copy descriptors */ + f->fs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_FULL); + if (IS_ERR(f->fs_descriptors)) { + ret = PTR_ERR(f->fs_descriptors); + f->fs_descriptors = NULL; + goto error_unlock; + } + + f->hs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_HIGH); + if (IS_ERR(f->hs_descriptors)) { + ret = PTR_ERR(f->hs_descriptors); + f->hs_descriptors = NULL; + goto error_unlock; + } + + f->ss_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER); + if (IS_ERR(f->ss_descriptors)) { + ret = PTR_ERR(f->ss_descriptors); + f->ss_descriptors = NULL; + goto error_unlock; + } + + f->ssp_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER_PLUS); + if (IS_ERR(f->ssp_descriptors)) { + ret = PTR_ERR(f->ssp_descriptors); + f->ssp_descriptors = NULL; + goto error_unlock; + } + + mutex_unlock(&opts->lock); + + /* 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; + + if (v4l2_device_register(&cdev->gadget->dev, &uvc->v4l2_dev)) { + uvcg_err(f, "failed to register V4L2 device\n"); + goto error; + } + + /* Initialise video. */ + ret = uvcg_video_init(&uvc->video, uvc); + if (ret < 0) + goto v4l2_error; + + /* Register a V4L2 device. */ + ret = uvc_register_video(uvc); + if (ret < 0) { + uvcg_err(f, "failed to register video device\n"); + goto v4l2_error; + } + + return 0; + +error_unlock: + mutex_unlock(&opts->lock); +v4l2_error: + v4l2_device_unregister(&uvc->v4l2_dev); +error: + if (uvc->control_req) { + usb_ep_free_request(cdev->gadget->ep0, uvc->control_req); + uvc->control_req = NULL; + } + kfree(uvc->control_buf); + uvc->control_buf = NULL; + + usb_free_all_descriptors(f); + return ret; +} + +/* -------------------------------------------------------------------------- + * USB gadget function + */ + +static void uvc_free_inst(struct usb_function_instance *f) +{ + struct f_uvc_opts *opts = fi_to_f_uvc_opts(f); + + mutex_destroy(&opts->lock); + kfree(opts); +} + +static struct usb_function_instance *uvc_alloc_inst(void) +{ + struct f_uvc_opts *opts; + struct uvc_camera_terminal_descriptor *cd; + struct uvc_processing_unit_descriptor *pd; + struct uvc_output_terminal_descriptor *od; + struct uvc_descriptor_header **ctl_cls; + int ret; + + opts = kzalloc_obj(*opts); + if (!opts) + return ERR_PTR(-ENOMEM); + opts->func_inst.free_func_inst = uvc_free_inst; + mutex_init(&opts->lock); + + cd = &opts->uvc_camera_terminal; + cd->bLength = UVC_DT_CAMERA_TERMINAL_SIZE(3); + cd->bDescriptorType = USB_DT_CS_INTERFACE; + cd->bDescriptorSubType = UVC_VC_INPUT_TERMINAL; + cd->bTerminalID = 1; + cd->wTerminalType = cpu_to_le16(0x0201); + cd->bAssocTerminal = 0; + cd->iTerminal = 0; + cd->wObjectiveFocalLengthMin = cpu_to_le16(0); + cd->wObjectiveFocalLengthMax = cpu_to_le16(0); + cd->wOcularFocalLength = cpu_to_le16(0); + cd->bControlSize = 3; + cd->bmControls[0] = 2; + cd->bmControls[1] = 0; + cd->bmControls[2] = 0; + + pd = &opts->uvc_processing; + pd->bLength = UVC_DT_PROCESSING_UNIT_SIZE(2); + pd->bDescriptorType = USB_DT_CS_INTERFACE; + pd->bDescriptorSubType = UVC_VC_PROCESSING_UNIT; + pd->bUnitID = 2; + pd->bSourceID = 1; + pd->wMaxMultiplier = cpu_to_le16(16*1024); + pd->bControlSize = 2; + pd->bmControls[0] = 1; + pd->bmControls[1] = 0; + pd->iProcessing = 0; + pd->bmVideoStandards = 0; + + od = &opts->uvc_output_terminal; + od->bLength = UVC_DT_OUTPUT_TERMINAL_SIZE; + od->bDescriptorType = USB_DT_CS_INTERFACE; + od->bDescriptorSubType = UVC_VC_OUTPUT_TERMINAL; + od->bTerminalID = 3; + od->wTerminalType = cpu_to_le16(0x0101); + od->bAssocTerminal = 0; + od->bSourceID = 2; + od->iTerminal = 0; + + /* + * With the ability to add XUs to the UVC function graph, we need to be + * able to allocate unique unit IDs to them. The IDs are 1-based, with + * the CT, PU and OT above consuming the first 3. + */ + opts->last_unit_id = 3; + + /* Prepare fs control class descriptors for configfs-based gadgets */ + ctl_cls = opts->uvc_fs_control_cls; + ctl_cls[0] = NULL; /* assigned elsewhere by configfs */ + ctl_cls[1] = (struct uvc_descriptor_header *)cd; + ctl_cls[2] = (struct uvc_descriptor_header *)pd; + ctl_cls[3] = (struct uvc_descriptor_header *)od; + ctl_cls[4] = NULL; /* NULL-terminate */ + opts->fs_control = + (const struct uvc_descriptor_header * const *)ctl_cls; + + /* Prepare hs control class descriptors for configfs-based gadgets */ + ctl_cls = opts->uvc_ss_control_cls; + ctl_cls[0] = NULL; /* assigned elsewhere by configfs */ + ctl_cls[1] = (struct uvc_descriptor_header *)cd; + ctl_cls[2] = (struct uvc_descriptor_header *)pd; + ctl_cls[3] = (struct uvc_descriptor_header *)od; + ctl_cls[4] = NULL; /* NULL-terminate */ + opts->ss_control = + (const struct uvc_descriptor_header * const *)ctl_cls; + + INIT_LIST_HEAD(&opts->extension_units); + + opts->streaming_interval = 1; + opts->streaming_maxpacket = 1024; + snprintf(opts->function_name, sizeof(opts->function_name), "UVC Camera"); + + ret = uvcg_attach_configfs(opts); + if (ret < 0) { + kfree(opts); + return ERR_PTR(ret); + } + + return &opts->func_inst; +} + +static void uvc_free(struct usb_function *f) +{ + struct uvc_device *uvc = to_uvc(f); + struct f_uvc_opts *opts = container_of(f->fi, struct f_uvc_opts, + func_inst); + if (!opts->header) + config_item_put(&uvc->header->item); + --opts->refcnt; + kfree(uvc); +} + +static void uvc_function_unbind(struct usb_configuration *c, + struct usb_function *f) +{ + DECLARE_COMPLETION_ONSTACK(vdev_release_done); + struct usb_composite_dev *cdev = c->cdev; + struct uvc_device *uvc = to_uvc(f); + struct uvc_video *video = &uvc->video; + long wait_ret = 1; + bool connected; + + uvcg_info(f, "%s()\n", __func__); + scoped_guard(mutex, &uvc->lock) { + uvc->func_unbound = true; + uvc->vdev_release_done = &vdev_release_done; + connected = uvc->func_connected; + } + + kthread_cancel_work_sync(&video->hw_submit); + + if (video->async_wq) + destroy_workqueue(video->async_wq); + + /* + * If we know we're connected via v4l2, then there should be a cleanup + * of the device from userspace either via UVC_EVENT_DISCONNECT or + * though the video device removal uevent. Allow some time for the + * application to close out before things get deleted. + */ + if (connected) { + uvcg_dbg(f, "waiting for clean disconnect\n"); + wait_ret = wait_event_interruptible_timeout(uvc->func_connected_queue, + uvc->func_connected == false, msecs_to_jiffies(500)); + uvcg_dbg(f, "done waiting with ret: %ld\n", wait_ret); + } + + device_remove_file(&uvc->vdev.dev, &dev_attr_function_name); + video_unregister_device(&uvc->vdev); + v4l2_device_unregister(&uvc->v4l2_dev); + + scoped_guard(mutex, &uvc->lock) + connected = uvc->func_connected; + + if (connected) { + /* + * Wait for the release to occur to ensure there are no longer any + * pending operations that may cause panics when resources are cleaned + * up. + */ + uvcg_warn(f, "%s no clean disconnect, wait for release\n", __func__); + wait_ret = wait_event_interruptible_timeout(uvc->func_connected_queue, + uvc->func_connected == false, msecs_to_jiffies(1000)); + uvcg_dbg(f, "done waiting for release with ret: %ld\n", wait_ret); + } + + /* Wait for the video device to be released */ + wait_for_completion(&vdev_release_done); + uvc->vdev_release_done = NULL; + + usb_ep_free_request(cdev->gadget->ep0, uvc->control_req); + uvc->control_req = NULL; + kfree(uvc->control_buf); + uvc->control_buf = NULL; + + usb_free_all_descriptors(f); +} + +static struct usb_function *uvc_alloc(struct usb_function_instance *fi) +{ + struct uvc_device *uvc; + struct f_uvc_opts *opts; + struct uvc_descriptor_header **strm_cls; + struct config_item *streaming, *header, *h; + + uvc = kzalloc_obj(*uvc); + if (uvc == NULL) + return ERR_PTR(-ENOMEM); + + mutex_init(&uvc->video.mutex); + mutex_init(&uvc->lock); + uvc->func_unbound = true; + uvc->state = UVC_STATE_DISCONNECTED; + init_waitqueue_head(&uvc->func_connected_queue); + opts = fi_to_f_uvc_opts(fi); + + mutex_lock(&opts->lock); + if (opts->uvc_fs_streaming_cls) { + strm_cls = opts->uvc_fs_streaming_cls; + opts->fs_streaming = + (const struct uvc_descriptor_header * const *)strm_cls; + } + if (opts->uvc_hs_streaming_cls) { + strm_cls = opts->uvc_hs_streaming_cls; + opts->hs_streaming = + (const struct uvc_descriptor_header * const *)strm_cls; + } + if (opts->uvc_ss_streaming_cls) { + strm_cls = opts->uvc_ss_streaming_cls; + opts->ss_streaming = + (const struct uvc_descriptor_header * const *)strm_cls; + } + + uvc->desc.fs_control = opts->fs_control; + uvc->desc.ss_control = opts->ss_control; + uvc->desc.fs_streaming = opts->fs_streaming; + uvc->desc.hs_streaming = opts->hs_streaming; + uvc->desc.ss_streaming = opts->ss_streaming; + + if (opts->header) { + uvc->header = opts->header; + } else { + streaming = config_group_find_item(&opts->func_inst.group, "streaming"); + if (!streaming) + goto err_config; + + header = config_group_find_item(to_config_group(streaming), "header"); + config_item_put(streaming); + if (!header) + goto err_config; + + h = config_group_find_item(to_config_group(header), "h"); + config_item_put(header); + if (!h) + goto err_config; + + uvc->header = to_uvcg_streaming_header(h); + if (!uvc->header->linked) { + mutex_unlock(&opts->lock); + kfree(uvc); + return ERR_PTR(-EBUSY); + } + } + + uvc->desc.extension_units = &opts->extension_units; + + ++opts->refcnt; + mutex_unlock(&opts->lock); + + /* Register the function. */ + uvc->func.name = "uvc"; + 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; + uvc->func.free_func = uvc_free; + uvc->func.bind_deactivated = true; + + return &uvc->func; + +err_config: + mutex_unlock(&opts->lock); + kfree(uvc); + return ERR_PTR(-ENOENT); +} + +DECLARE_USB_FUNCTION_INIT(uvc, uvc_alloc_inst, uvc_alloc); +MODULE_DESCRIPTION("USB Video Class Gadget driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Laurent Pinchart"); diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h new file mode 100644 index 000000000..083aef0c6 --- /dev/null +++ b/drivers/usb/gadget/function/f_uvc.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * f_uvc.h -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef _F_UVC_H_ +#define _F_UVC_H_ + +struct uvc_device; + +void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep); + +void uvc_function_connect(struct uvc_device *uvc); + +void uvc_function_disconnect(struct uvc_device *uvc); + +#endif /* _F_UVC_H_ */ diff --git a/drivers/usb/gadget/function/g_zero.h b/drivers/usb/gadget/function/g_zero.h new file mode 100644 index 000000000..7bd660048 --- /dev/null +++ b/drivers/usb/gadget/function/g_zero.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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 + +#define GZERO_BULK_BUFLEN 4096 +#define GZERO_QLEN 32 +#define GZERO_ISOC_INTERVAL 4 +#define GZERO_ISOC_MAXPACKET 1024 +#define GZERO_SS_BULK_QLEN 1 +#define GZERO_SS_ISO_QLEN 8 + +struct usb_zero_options { + unsigned pattern; + unsigned isoc_interval; + unsigned isoc_maxpacket; + unsigned isoc_mult; + unsigned isoc_maxburst; + unsigned bulk_buflen; + unsigned qlen; + unsigned ss_bulk_qlen; + unsigned ss_iso_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; + unsigned bulk_maxburst; + unsigned bulk_qlen; + unsigned iso_qlen; + + /* + * Read/write access to configfs attributes is handled by configfs. + * + * This is to protect the data from concurrent access by read/write + * and create symlink/remove symlink. + */ + struct mutex lock; + int refcnt; +}; + +struct f_lb_opts { + struct usb_function_instance func_inst; + unsigned bulk_buflen; + unsigned qlen; + + /* + * Read/write access to configfs attributes is handled by configfs. + * + * This is to protect the data from concurrent access by read/write + * and create symlink/remove symlink. + */ + struct mutex lock; + int refcnt; +}; + +void lb_modexit(void); +int lb_modinit(void); + +/* common utilities */ +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/function/ndis.h b/drivers/usb/gadget/function/ndis.h new file mode 100644 index 000000000..a19f72dec --- /dev/null +++ b/drivers/usb/gadget/function/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/function/rndis.c b/drivers/usb/gadget/function/rndis.c new file mode 100644 index 000000000..a2fd239b7 --- /dev/null +++ b/drivers/usb/gadget/function/rndis.c @@ -0,0 +1,1201 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * RNDIS MSG parser + * + * Authors: Benedikt Spranger, Pengutronix + * Robert Schwebel, Pengutronix + * + * 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 + +#include "u_rndis.h" + +#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 + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +#define NAME_TEMPLATE "driver/rndis-%03d" + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + +static DEFINE_IDA(rndis_ida); + +/* Driver Version */ +static const __le32 rndis_driver_version = cpu_to_le32(1); + +/* Function Prototypes */ +static rndis_resp_t *rndis_add_response(struct rndis_params *params, + u32 length); + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +static const struct proc_ops rndis_proc_ops; + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + +/* 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(struct rndis_params *params, 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 = params->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(params->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(params->medium); + retval = 0; + break; + + /* mandatory */ + case RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE: + pr_debug("%s: RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE\n", __func__); + if (params->dev) { + *outbuf = cpu_to_le32(params->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 (params->media_state == RNDIS_MEDIA_STATE_DISCONNECTED) + *outbuf = cpu_to_le32(0); + else + *outbuf = cpu_to_le32(params->speed); + retval = 0; + break; + + /* mandatory */ + case RNDIS_OID_GEN_TRANSMIT_BLOCK_SIZE: + pr_debug("%s: RNDIS_OID_GEN_TRANSMIT_BLOCK_SIZE\n", __func__); + if (params->dev) { + *outbuf = cpu_to_le32(params->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 (params->dev) { + *outbuf = cpu_to_le32(params->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(params->vendorID); + retval = 0; + break; + + /* mandatory */ + case RNDIS_OID_GEN_VENDOR_DESCRIPTION: + pr_debug("%s: RNDIS_OID_GEN_VENDOR_DESCRIPTION\n", __func__); + if (params->vendorDescr) { + length = strlen(params->vendorDescr); + memcpy(outbuf, params->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(*params->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(params->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 (params->dev) { + length = ETH_ALEN; + memcpy(outbuf, params->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 (params->dev) { + length = ETH_ALEN; + memcpy(outbuf, params->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_warn("%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(struct rndis_params *params, u32 OID, + u8 *buf, u32 buf_len, rndis_resp_t *r) +{ + rndis_set_cmplt_type *resp; + int i, retval = -ENOTSUPP; + + 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])); + } + } + + 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_warn("%s: set unknown OID 0x%08X, size %d\n", + __func__, OID, buf_len); + } + + return retval; +} + +/* + * Response Functions + */ + +static int rndis_init_response(struct rndis_params *params, + rndis_init_msg_type *buf) +{ + rndis_init_cmplt_type *resp; + rndis_resp_t *r; + + if (!params->dev) + return -ENOTSUPP; + + r = rndis_add_response(params, 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(1); + resp->MaxTransferSize = cpu_to_le32( + params->dev->mtu + + sizeof(struct ethhdr) + + sizeof(struct rndis_packet_msg_type) + + 22); + 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(struct rndis_params *params, + rndis_query_msg_type *buf) +{ + u32 BufLength, BufOffset; + rndis_query_cmplt_type *resp; + rndis_resp_t *r; + + /* pr_debug("%s: OID = %08X\n", __func__, cpu_to_le32(buf->OID)); */ + if (!params->dev) + return -ENOTSUPP; + + BufLength = le32_to_cpu(buf->InformationBufferLength); + BufOffset = le32_to_cpu(buf->InformationBufferOffset); + if ((BufLength > RNDIS_MAX_TOTAL_SIZE) || + (BufOffset > RNDIS_MAX_TOTAL_SIZE) || + (BufOffset + 8 >= RNDIS_MAX_TOTAL_SIZE)) + return -EINVAL; + + /* + * 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(params, + 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(params, le32_to_cpu(buf->OID), + BufOffset + 8 + (u8 *)buf, + BufLength, 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(struct rndis_params *params, + rndis_set_msg_type *buf) +{ + u32 BufLength, BufOffset; + rndis_set_cmplt_type *resp; + rndis_resp_t *r; + + BufLength = le32_to_cpu(buf->InformationBufferLength); + BufOffset = le32_to_cpu(buf->InformationBufferOffset); + if ((BufLength > RNDIS_MAX_TOTAL_SIZE) || + (BufOffset > RNDIS_MAX_TOTAL_SIZE) || + (BufOffset + 8 >= RNDIS_MAX_TOTAL_SIZE)) + return -EINVAL; + + r = rndis_add_response(params, sizeof(rndis_set_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_set_cmplt_type *)r->buf; + +#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(params, 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(struct rndis_params *params, + rndis_reset_msg_type *buf) +{ + rndis_reset_cmplt_type *resp; + rndis_resp_t *r; + u8 *xbuf; + u32 length; + + /* drain the response queue */ + while ((xbuf = rndis_get_next_response(params, &length))) + rndis_free_response(params, xbuf); + + r = rndis_add_response(params, 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(struct rndis_params *params, + rndis_keepalive_msg_type *buf) +{ + rndis_keepalive_cmplt_type *resp; + rndis_resp_t *r; + + /* host "should" check only in RNDIS_DATA_INITIALIZED state */ + + r = rndis_add_response(params, 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(struct rndis_params *params, u32 status) +{ + rndis_indicate_status_msg_type *resp; + rndis_resp_t *r; + + if (params->state == RNDIS_UNINITIALIZED) + return -ENOTSUPP; + + r = rndis_add_response(params, 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(struct rndis_params *params) +{ + params->media_state = RNDIS_MEDIA_STATE_CONNECTED; + return rndis_indicate_status_msg(params, RNDIS_STATUS_MEDIA_CONNECT); +} +EXPORT_SYMBOL_GPL(rndis_signal_connect); + +int rndis_signal_disconnect(struct rndis_params *params) +{ + params->media_state = RNDIS_MEDIA_STATE_DISCONNECTED; + return rndis_indicate_status_msg(params, RNDIS_STATUS_MEDIA_DISCONNECT); +} +EXPORT_SYMBOL_GPL(rndis_signal_disconnect); + +void rndis_uninit(struct rndis_params *params) +{ + u8 *buf; + u32 length; + + if (!params) + return; + params->state = RNDIS_UNINITIALIZED; + + /* drain the response queue */ + while ((buf = rndis_get_next_response(params, &length))) + rndis_free_response(params, buf); +} +EXPORT_SYMBOL_GPL(rndis_uninit); + +void rndis_set_host_mac(struct rndis_params *params, const u8 *addr) +{ + params->host_mac = addr; +} +EXPORT_SYMBOL_GPL(rndis_set_host_mac); + +/* + * Message Parser + */ +int rndis_msg_parser(struct rndis_params *params, u8 *buf) +{ + u32 MsgType, MsgLength; + __le32 *tmp; + + if (!buf) + return -ENOMEM; + + tmp = (__le32 *)buf; + MsgType = get_unaligned_le32(tmp++); + MsgLength = get_unaligned_le32(tmp++); + + if (!params) + return -ENOTSUPP; + + /* 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(params, (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(params, + (rndis_query_msg_type *)buf); + + case RNDIS_MSG_SET: + return rndis_set_response(params, (rndis_set_msg_type *)buf); + + case RNDIS_MSG_RESET: + pr_debug("%s: RNDIS_MSG_RESET\n", + __func__); + return rndis_reset_response(params, + (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(params, + (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_warn("%s: unknown RNDIS message 0x%08X len %d\n", + __func__, MsgType, MsgLength); + /* Garbled message can be huge, so limit what we display */ + if (MsgLength > 16) + MsgLength = 16; + print_hex_dump_bytes(__func__, DUMP_PREFIX_OFFSET, + buf, MsgLength); + break; + } + + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(rndis_msg_parser); + +static inline int rndis_get_nr(void) +{ + return ida_alloc_max(&rndis_ida, 999, GFP_KERNEL); +} + +static inline void rndis_put_nr(int nr) +{ + ida_free(&rndis_ida, nr); +} + +struct rndis_params *rndis_register(void (*resp_avail)(void *v), void *v) +{ + struct rndis_params *params; + int i; + + if (!resp_avail) + return ERR_PTR(-EINVAL); + + i = rndis_get_nr(); + if (i < 0) { + pr_debug("failed\n"); + + return ERR_PTR(-ENODEV); + } + + params = kzalloc_obj(*params); + if (!params) { + rndis_put_nr(i); + + return ERR_PTR(-ENOMEM); + } + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + { + struct proc_dir_entry *proc_entry; + char name[20]; + + sprintf(name, NAME_TEMPLATE, i); + proc_entry = proc_create_data(name, 0660, NULL, + &rndis_proc_ops, params); + if (!proc_entry) { + kfree(params); + rndis_put_nr(i); + + return ERR_PTR(-EIO); + } + } +#endif + + params->confignr = i; + params->used = 1; + params->state = RNDIS_UNINITIALIZED; + params->media_state = RNDIS_MEDIA_STATE_DISCONNECTED; + params->resp_avail = resp_avail; + params->v = v; + INIT_LIST_HEAD(¶ms->resp_queue); + spin_lock_init(¶ms->resp_lock); + pr_debug("%s: configNr = %d\n", __func__, i); + + return params; +} +EXPORT_SYMBOL_GPL(rndis_register); + +void rndis_deregister(struct rndis_params *params) +{ + int i; + + pr_debug("%s:\n", __func__); + + if (!params) + return; + + i = params->confignr; + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + { + char name[20]; + + sprintf(name, NAME_TEMPLATE, i); + remove_proc_entry(name, NULL); + } +#endif + + kfree(params); + rndis_put_nr(i); +} +EXPORT_SYMBOL_GPL(rndis_deregister); +int rndis_set_param_dev(struct rndis_params *params, struct net_device *dev, + u16 *cdc_filter) +{ + pr_debug("%s:\n", __func__); + if (!dev) + return -EINVAL; + if (!params) + return -1; + + params->dev = dev; + params->filter = cdc_filter; + + return 0; +} +EXPORT_SYMBOL_GPL(rndis_set_param_dev); + +int rndis_set_param_vendor(struct rndis_params *params, u32 vendorID, + const char *vendorDescr) +{ + pr_debug("%s:\n", __func__); + if (!vendorDescr) return -1; + if (!params) + return -1; + + params->vendorID = vendorID; + params->vendorDescr = vendorDescr; + + return 0; +} +EXPORT_SYMBOL_GPL(rndis_set_param_vendor); + +int rndis_set_param_medium(struct rndis_params *params, u32 medium, u32 speed) +{ + pr_debug("%s: %u %u\n", __func__, medium, speed); + if (!params) + return -1; + + params->medium = medium; + params->speed = speed; + + return 0; +} +EXPORT_SYMBOL_GPL(rndis_set_param_medium); + +void rndis_add_hdr(struct sk_buff *skb) +{ + struct rndis_packet_msg_type *header; + + if (!skb) + return; + header = 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)); +} +EXPORT_SYMBOL_GPL(rndis_add_hdr); + +void rndis_free_response(struct rndis_params *params, u8 *buf) +{ + rndis_resp_t *r, *n; + + spin_lock(¶ms->resp_lock); + list_for_each_entry_safe(r, n, ¶ms->resp_queue, list) { + if (r->buf == buf) { + list_del(&r->list); + kfree(r); + } + } + spin_unlock(¶ms->resp_lock); +} +EXPORT_SYMBOL_GPL(rndis_free_response); + +u8 *rndis_get_next_response(struct rndis_params *params, u32 *length) +{ + rndis_resp_t *r, *n; + + if (!length) return NULL; + + spin_lock(¶ms->resp_lock); + list_for_each_entry_safe(r, n, ¶ms->resp_queue, list) { + if (!r->send) { + r->send = 1; + *length = r->length; + spin_unlock(¶ms->resp_lock); + return r->buf; + } + } + + spin_unlock(¶ms->resp_lock); + return NULL; +} +EXPORT_SYMBOL_GPL(rndis_get_next_response); + +static rndis_resp_t *rndis_add_response(struct rndis_params *params, 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; + + spin_lock(¶ms->resp_lock); + list_add_tail(&r->list, ¶ms->resp_queue); + spin_unlock(¶ms->resp_lock); + return r; +} + +int rndis_rm_hdr(struct gether *port, + struct sk_buff *skb, + struct sk_buff_head *list) +{ + /* tmp points to a struct rndis_packet_msg_type */ + __le32 *tmp = (void *)skb->data; + + /* Need at least MessageType, MessageLength, DataOffset, DataLength */ + if (skb->len < 16) { + dev_kfree_skb_any(skb); + return -EINVAL; + } + + /* MessageType, MessageLength */ + if (cpu_to_le32(RNDIS_MSG_PACKET) + != get_unaligned(tmp++)) { + dev_kfree_skb_any(skb); + return -EINVAL; + } + tmp++; + + /* DataOffset, DataLength */ + if (!skb_pull(skb, get_unaligned_le32(tmp++) + 8)) { + dev_kfree_skb_any(skb); + return -EOVERFLOW; + } + skb_trim(skb, get_unaligned_le32(tmp++)); + + skb_queue_tail(list, skb); + return 0; +} +EXPORT_SYMBOL_GPL(rndis_rm_hdr); + +#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 : %u\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); + break; + case 'D': + case 'd': + rndis_signal_disconnect(p); + 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 proc_ops rndis_proc_ops = { + .proc_open = rndis_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, + .proc_write = rndis_proc_write, +}; + +#define NAME_TEMPLATE "driver/rndis-%03d" + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ diff --git a/drivers/usb/gadget/function/rndis.h b/drivers/usb/gadget/function/rndis.h new file mode 100644 index 000000000..6206b8b74 --- /dev/null +++ b/drivers/usb/gadget/function/rndis.h @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * RNDIS Definitions for Remote NDIS + * + * Authors: Benedikt Spranger, Pengutronix + * Robert Schwebel, Pengutronix + * + * 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 "u_ether.h" +#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 { + int 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; + spinlock_t resp_lock; +} rndis_params; + +/* RNDIS Message parser and other useless functions */ +int rndis_msg_parser(struct rndis_params *params, u8 *buf); +struct rndis_params *rndis_register(void (*resp_avail)(void *v), void *v); +void rndis_deregister(struct rndis_params *params); +int rndis_set_param_dev(struct rndis_params *params, struct net_device *dev, + u16 *cdc_filter); +int rndis_set_param_vendor(struct rndis_params *params, u32 vendorID, + const char *vendorDescr); +int rndis_set_param_medium(struct rndis_params *params, 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(struct rndis_params *params, u32 *length); +void rndis_free_response(struct rndis_params *params, u8 *buf); + +void rndis_uninit(struct rndis_params *params); +int rndis_signal_connect(struct rndis_params *params); +int rndis_signal_disconnect(struct rndis_params *params); +int rndis_state(struct rndis_params *params); +extern void rndis_set_host_mac(struct rndis_params *params, const u8 *addr); + +#endif /* _LINUX_RNDIS_H */ diff --git a/drivers/usb/gadget/function/storage_common.c b/drivers/usb/gadget/function/storage_common.c new file mode 100644 index 000000000..75831f2c7 --- /dev/null +++ b/drivers/usb/gadget/function/storage_common.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 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 +#include +#include +#include + +#include "storage_common.h" + +/* There is only one interface. */ + +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, +}; +EXPORT_SYMBOL_GPL(fsg_intf_desc); + +/* + * Three full-speed endpoint descriptors: bulk-in, bulk-out, and + * interrupt-in. + */ + +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 */ +}; +EXPORT_SYMBOL_GPL(fsg_fs_bulk_in_desc); + +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 */ +}; +EXPORT_SYMBOL_GPL(fsg_fs_bulk_out_desc); + +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, +}; +EXPORT_SYMBOL_GPL(fsg_fs_function); + + +/* + * 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). + */ +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), +}; +EXPORT_SYMBOL_GPL(fsg_hs_bulk_in_desc); + +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 */ +}; +EXPORT_SYMBOL_GPL(fsg_hs_bulk_out_desc); + + +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, +}; +EXPORT_SYMBOL_GPL(fsg_hs_function); + +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), +}; +EXPORT_SYMBOL_GPL(fsg_ss_bulk_in_desc); + +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, */ +}; +EXPORT_SYMBOL_GPL(fsg_ss_bulk_in_comp_desc); + +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), +}; +EXPORT_SYMBOL_GPL(fsg_ss_bulk_out_desc); + +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, */ +}; +EXPORT_SYMBOL_GPL(fsg_ss_bulk_out_comp_desc); + +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, +}; +EXPORT_SYMBOL_GPL(fsg_ss_function); + + + /*-------------------------------------------------------------------------*/ + +/* + * If the next two routines are called while the gadget is registered, + * the caller must own fsg->filesem for writing. + */ + +void fsg_lun_close(struct fsg_lun *curlun) +{ + if (curlun->filp) { + LDBG(curlun, "close backing file\n"); + fput(curlun->filp); + curlun->filp = NULL; + } +} +EXPORT_SYMBOL_GPL(fsg_lun_close); + +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 = filp->f_mapping->host; + 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_mode & FMODE_CAN_READ)) { + LINFO(curlun, "file not readable: %s\n", filename); + goto out; + } + if (!(filp->f_mode & FMODE_CAN_WRITE)) + ro = 1; + + size = i_size_read(inode); + if (size < 0) { + LINFO(curlun, "unable to find file size: %s\n", filename); + rc = (int) size; + goto out; + } + + if (curlun->cdrom) { + blksize = 2048; + blkbits = 11; + } else if (S_ISBLK(inode->i_mode)) { + blksize = bdev_logical_block_size(I_BDEV(inode)); + blkbits = blksize_bits(blksize); + } else { + blksize = 512; + blkbits = 9; + } + + num_sectors = size >> blkbits; /* File size in logic-block-size blocks */ + min_sectors = 1; + if (curlun->cdrom) { + min_sectors = 300; /* Smallest track is 300 frames */ + if (num_sectors >= 256*60*75) { + num_sectors = 256*60*75 - 1; + 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; + LDBG(curlun, "open backing file: %s\n", filename); + return 0; + +out: + fput(filp); + return rc; +} +EXPORT_SYMBOL_GPL(fsg_lun_open); + + +/*-------------------------------------------------------------------------*/ + +/* + * Sync the file data, don't bother with the metadata. + * This code was copied from fs/buffer.c:sys_fdatasync(). + */ +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); +} +EXPORT_SYMBOL_GPL(fsg_lun_fsync_sub); + +void store_cdrom_address(u8 *dest, int msf, u32 addr) +{ + if (msf) { + /* + * Convert to Minutes-Seconds-Frames. + * Sector size is already set to 2048 bytes. + */ + 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); + } +} +EXPORT_SYMBOL_GPL(store_cdrom_address); + +/*-------------------------------------------------------------------------*/ + + +ssize_t fsg_show_ro(struct fsg_lun *curlun, char *buf) +{ + return sprintf(buf, "%d\n", fsg_lun_is_open(curlun) + ? curlun->ro + : curlun->initially_ro); +} +EXPORT_SYMBOL_GPL(fsg_show_ro); + +ssize_t fsg_show_nofua(struct fsg_lun *curlun, char *buf) +{ + return sprintf(buf, "%u\n", curlun->nofua); +} +EXPORT_SYMBOL_GPL(fsg_show_nofua); + +ssize_t fsg_show_file(struct fsg_lun *curlun, struct rw_semaphore *filesem, + char *buf) +{ + char *p; + ssize_t rc; + + down_read(filesem); + if (fsg_lun_is_open(curlun)) { /* Get the complete pathname */ + p = file_path(curlun->filp, 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; +} +EXPORT_SYMBOL_GPL(fsg_show_file); + +ssize_t fsg_show_cdrom(struct fsg_lun *curlun, char *buf) +{ + return sprintf(buf, "%u\n", curlun->cdrom); +} +EXPORT_SYMBOL_GPL(fsg_show_cdrom); + +ssize_t fsg_show_removable(struct fsg_lun *curlun, char *buf) +{ + return sprintf(buf, "%u\n", curlun->removable); +} +EXPORT_SYMBOL_GPL(fsg_show_removable); + +ssize_t fsg_show_inquiry_string(struct fsg_lun *curlun, char *buf) +{ + return sprintf(buf, "%s\n", curlun->inquiry_string); +} +EXPORT_SYMBOL_GPL(fsg_show_inquiry_string); + +/* + * The caller must hold fsg->filesem for reading when calling this function. + */ +static ssize_t _fsg_store_ro(struct fsg_lun *curlun, bool ro) +{ + if (fsg_lun_is_open(curlun)) { + LDBG(curlun, "read-only status change prevented\n"); + return -EBUSY; + } + + curlun->ro = ro; + curlun->initially_ro = ro; + LDBG(curlun, "read-only status set to %d\n", curlun->ro); + + return 0; +} + +ssize_t fsg_store_ro(struct fsg_lun *curlun, struct rw_semaphore *filesem, + const char *buf, size_t count) +{ + ssize_t rc; + bool ro; + + rc = kstrtobool(buf, &ro); + if (rc) + return rc; + + /* + * Allow the write-enable status to change only while the + * backing file is closed. + */ + down_read(filesem); + rc = _fsg_store_ro(curlun, ro); + if (!rc) + rc = count; + up_read(filesem); + + return rc; +} +EXPORT_SYMBOL_GPL(fsg_store_ro); + +ssize_t fsg_store_nofua(struct fsg_lun *curlun, const char *buf, size_t count) +{ + bool nofua; + int ret; + + ret = kstrtobool(buf, &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; +} +EXPORT_SYMBOL_GPL(fsg_store_nofua); + +ssize_t fsg_store_file(struct fsg_lun *curlun, struct rw_semaphore *filesem, + const char *buf, size_t count) +{ + int rc = 0; + + if (curlun->prevent_medium_removal && fsg_lun_is_open(curlun)) { + LDBG(curlun, "eject attempt prevented\n"); + return -EBUSY; /* "Door is locked" */ + } + + /* Remove a trailing newline */ + if (count > 0 && buf[count-1] == '\n') + ((char *) buf)[count-1] = 0; /* Ugh! */ + + /* Load new medium */ + down_write(filesem); + 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; + } else if (fsg_lun_is_open(curlun)) { + fsg_lun_close(curlun); + curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT; + } + up_write(filesem); + return (rc < 0 ? rc : count); +} +EXPORT_SYMBOL_GPL(fsg_store_file); + +ssize_t fsg_store_cdrom(struct fsg_lun *curlun, struct rw_semaphore *filesem, + const char *buf, size_t count) +{ + bool cdrom; + int ret; + + ret = kstrtobool(buf, &cdrom); + if (ret) + return ret; + + down_read(filesem); + ret = cdrom ? _fsg_store_ro(curlun, true) : 0; + + if (!ret) { + curlun->cdrom = cdrom; + ret = count; + } + up_read(filesem); + + return ret; +} +EXPORT_SYMBOL_GPL(fsg_store_cdrom); + +ssize_t fsg_store_removable(struct fsg_lun *curlun, const char *buf, + size_t count) +{ + bool removable; + int ret; + + ret = kstrtobool(buf, &removable); + if (ret) + return ret; + + curlun->removable = removable; + + return count; +} +EXPORT_SYMBOL_GPL(fsg_store_removable); + +ssize_t fsg_store_inquiry_string(struct fsg_lun *curlun, const char *buf, + size_t count) +{ + const size_t len = min(count, sizeof(curlun->inquiry_string)); + + if (len == 0 || buf[0] == '\n') { + curlun->inquiry_string[0] = 0; + } else { + snprintf(curlun->inquiry_string, + sizeof(curlun->inquiry_string), "%-28s", buf); + if (curlun->inquiry_string[len-1] == '\n') + curlun->inquiry_string[len-1] = ' '; + } + + return count; +} +EXPORT_SYMBOL_GPL(fsg_store_inquiry_string); + +ssize_t fsg_store_forced_eject(struct fsg_lun *curlun, struct rw_semaphore *filesem, + const char *buf, size_t count) +{ + int ret; + + /* + * Forcibly detach the backing file from the LUN + * regardless of whether the host has allowed it. + */ + curlun->prevent_medium_removal = 0; + ret = fsg_store_file(curlun, filesem, "", 0); + return ret < 0 ? ret : count; +} +EXPORT_SYMBOL_GPL(fsg_store_forced_eject); + +MODULE_DESCRIPTION("Common definitions for mass storage functionality"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/storage_common.h b/drivers/usb/gadget/function/storage_common.h new file mode 100644 index 000000000..11ac785d5 --- /dev/null +++ b/drivers/usb/gadget/function/storage_common.h @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef USB_STORAGE_COMMON_H +#define USB_STORAGE_COMMON_H + +#include +#include +#include +#include + +#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 _LMSG(func, lun, fmt, args...) \ + do { \ + if ((lun)->name_pfx && *(lun)->name_pfx) \ + func("%s/%s: " fmt, *(lun)->name_pfx, \ + (lun)->name, ## args); \ + else \ + func("%s: " fmt, (lun)->name, ## args); \ + } while (0) + +#define LDBG(lun, fmt, args...) _LMSG(pr_debug, lun, fmt, ## args) +#define LERROR(lun, fmt, args...) _LMSG(pr_err, lun, fmt, ## args) +#define LWARN(lun, fmt, args...) _LMSG(pr_warn, lun, fmt, ## args) +#define LINFO(lun, fmt, args...) _LMSG(pr_info, lun, 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)) + +/* + * Vendor (8 chars), product (16 chars), release (4 hexadecimal digits) and NUL + * byte + */ +#define INQUIRY_STRING_LEN ((size_t) (8 + 16 + 4 + 1)) + +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; + const char *name; /* "lun.name" */ + const char **name_pfx; /* "function.name" */ + char inquiry_string[INQUIRY_STRING_LEN]; +}; + +static inline bool fsg_lun_is_open(struct fsg_lun *curlun) +{ + return curlun->filp != NULL; +} + +/* Default size of buffer length. */ +#define FSG_BUFLEN ((u32)16384) + +/* Maximal number of LUNs supported in mass storage function */ +#define FSG_MAX_LUNS (US_BULK_MAX_LUN_LIMIT + 1) + +enum fsg_buffer_state { + BUF_STATE_SENDING = -2, + BUF_STATE_RECEIVING, + BUF_STATE_EMPTY = 0, + BUF_STATE_FULL +}; + +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; + struct usb_request *outreq; +}; + +enum fsg_state { + FSG_STATE_NORMAL, + FSG_STATE_ABORT_BULK_OUT, + FSG_STATE_PROTOCOL_RESET, + FSG_STATE_CONFIG_CHANGE, + 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 struct fsg_lun *fsg_lun_from_dev(struct device *dev) +{ + return container_of(dev, struct fsg_lun, dev); +} + +enum { + FSG_STRING_INTERFACE +}; + +extern struct usb_interface_descriptor fsg_intf_desc; + +extern struct usb_endpoint_descriptor fsg_fs_bulk_in_desc; +extern struct usb_endpoint_descriptor fsg_fs_bulk_out_desc; +extern struct usb_descriptor_header *fsg_fs_function[]; + +extern struct usb_endpoint_descriptor fsg_hs_bulk_in_desc; +extern struct usb_endpoint_descriptor fsg_hs_bulk_out_desc; +extern struct usb_descriptor_header *fsg_hs_function[]; + +extern struct usb_endpoint_descriptor fsg_ss_bulk_in_desc; +extern struct usb_ss_ep_comp_descriptor fsg_ss_bulk_in_comp_desc; +extern struct usb_endpoint_descriptor fsg_ss_bulk_out_desc; +extern struct usb_ss_ep_comp_descriptor fsg_ss_bulk_out_comp_desc; +extern struct usb_descriptor_header *fsg_ss_function[]; + +void fsg_lun_close(struct fsg_lun *curlun); +int fsg_lun_open(struct fsg_lun *curlun, const char *filename); +int fsg_lun_fsync_sub(struct fsg_lun *curlun); +void store_cdrom_address(u8 *dest, int msf, u32 addr); +ssize_t fsg_show_ro(struct fsg_lun *curlun, char *buf); +ssize_t fsg_show_nofua(struct fsg_lun *curlun, char *buf); +ssize_t fsg_show_file(struct fsg_lun *curlun, struct rw_semaphore *filesem, + char *buf); +ssize_t fsg_show_inquiry_string(struct fsg_lun *curlun, char *buf); +ssize_t fsg_show_cdrom(struct fsg_lun *curlun, char *buf); +ssize_t fsg_show_removable(struct fsg_lun *curlun, char *buf); +ssize_t fsg_store_ro(struct fsg_lun *curlun, struct rw_semaphore *filesem, + const char *buf, size_t count); +ssize_t fsg_store_nofua(struct fsg_lun *curlun, const char *buf, size_t count); +ssize_t fsg_store_file(struct fsg_lun *curlun, struct rw_semaphore *filesem, + const char *buf, size_t count); +ssize_t fsg_store_cdrom(struct fsg_lun *curlun, struct rw_semaphore *filesem, + const char *buf, size_t count); +ssize_t fsg_store_removable(struct fsg_lun *curlun, const char *buf, + size_t count); +ssize_t fsg_store_inquiry_string(struct fsg_lun *curlun, const char *buf, + size_t count); +ssize_t fsg_store_forced_eject(struct fsg_lun *curlun, struct rw_semaphore *filesem, + const char *buf, size_t count); + +#endif /* USB_STORAGE_COMMON_H */ diff --git a/drivers/usb/gadget/function/tcm.h b/drivers/usb/gadget/function/tcm.h new file mode 100644 index 000000000..e1d5a9391 --- /dev/null +++ b/drivers/usb/gadget/function/tcm.h @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __TARGET_USB_GADGET_H__ +#define __TARGET_USB_GADGET_H__ + +#include +#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 5 +#define UASP_SS_EP_COMP_NUM_STREAMS (1 << UASP_SS_EP_COMP_LOG_STREAMS) + +#define USBG_NUM_CMDS (UASP_SS_EP_COMP_NUM_STREAMS + 1) + +enum { + USB_G_STR_INT_UAS = 0, + USB_G_STR_INT_BBB, +}; + +#define USB_G_ALT_INT_BBB 0 +#define USB_G_ALT_INT_UAS 1 + +#define USB_G_DEFAULT_SESSION_TAGS USBG_NUM_CMDS + +enum { + USBG_DELAYED_SET_ALT_IDLE = 0, + USBG_DELAYED_SET_ALT_QUEUED, + USBG_DELAYED_SET_ALT_RUNNING, +}; + +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 usb_function_instance *fi; +}; + +struct usbg_tport { + /* 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 kref ref; + + struct usb_request *req; + + u32 flags; +#define USBG_CMD_PENDING_DATA_WRITE BIT(0) + + /* UAS only */ + u16 tag; + u16 prio_attr; + struct sense_iu sense_iu; + struct response_iu response_iu; + enum uas_state state; + + int tmr_func; + int tmr_rsp; +#define RC_RESPONSE_UNKNOWN 0xff + + /* 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 completion cmd_completion; + struct hlist_node node; +}; + +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) +#define USBG_BOT_WEDGED (1 << 5) + + struct work_struct delayed_set_alt; + spinlock_t delayed_set_alt_lock; /* protects delayed_set_alt_* */ + unsigned int delayed_alt; + unsigned int delayed_set_alt_state; + bool delayed_set_alt_cancel; + + struct usbg_cdb cmd[USBG_NUM_CMDS]; + 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[USBG_NUM_CMDS]; + DECLARE_HASHTABLE(stream_hash, UASP_SS_EP_COMP_LOG_STREAMS); + + /* BOT */ + struct bot_status bot_status; + struct usb_request *bot_req_in; + struct usb_request *bot_req_out; +}; + +#endif /* __TARGET_USB_GADGET_H__ */ diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c new file mode 100644 index 000000000..f0ee83abc --- /dev/null +++ b/drivers/usb/gadget/function/u_audio.c @@ -0,0 +1,1481 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * u_audio.c -- interface to USB gadget "ALSA sound card" utilities + * + * Copyright (C) 2016 + * Author: Ruslan Bilovol + * + * Sound card implementation was cut-and-pasted with changes + * from f_uac2.c and has: + * Copyright (C) 2011 + * Yadwinder Singh (yadi.brar01@gmail.com) + * Jaswinder Singh (jaswinder.singh@linaro.org) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "u_audio.h" + +#define BUFF_SIZE_MAX (PAGE_SIZE * 16) +#define PRD_SIZE_MAX PAGE_SIZE +#define MIN_PERIODS 4 + +enum { + UAC_FBACK_CTRL, + UAC_P_PITCH_CTRL, + UAC_MUTE_CTRL, + UAC_VOLUME_CTRL, + UAC_RATE_CTRL, +}; + +/* Runtime data params for one stream */ +struct uac_rtd_params { + struct snd_uac_chip *uac; /* parent chip */ + bool ep_enabled; /* if the ep is enabled */ + + struct snd_pcm_substream *ss; + + /* Ring buffer */ + ssize_t hw_ptr; + + void *rbuf; + + unsigned int pitch; /* Stream pitch ratio to 1000000 */ + unsigned int max_psize; /* MaxPacketSize of endpoint */ + + struct usb_request **reqs; + + struct usb_request *req_fback; /* Feedback endpoint request */ + bool fb_ep_enabled; /* if the ep is enabled */ + + /* Volume/Mute controls and their state */ + int fu_id; /* Feature Unit ID */ + struct snd_ctl_elem_id snd_kctl_volume_id; + struct snd_ctl_elem_id snd_kctl_mute_id; + s16 volume_min, volume_max, volume_res; + s16 volume; + int mute; + + struct snd_ctl_elem_id snd_kctl_rate_id; /* read-only current rate */ + int srate; /* selected samplerate */ + int active; /* playback/capture running */ + + spinlock_t lock; /* lock for control transfers */ + +}; + +struct snd_uac_chip { + struct g_audio *audio_dev; + + struct uac_rtd_params p_prm; + struct uac_rtd_params c_prm; + + struct snd_card *card; + struct snd_pcm *pcm; + + /* pre-calculated values for playback iso completion */ + unsigned long long p_residue_mil; + unsigned int p_interval; + unsigned int p_framesize; +}; + +static const struct snd_pcm_hardware uac_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, +}; + +static void u_audio_set_fback_frequency(enum usb_device_speed speed, + struct usb_ep *out_ep, + unsigned long long freq, + unsigned int pitch, + void *buf) +{ + u32 ff = 0; + const struct usb_endpoint_descriptor *ep_desc; + + /* + * Because the pitch base is 1000000, the final divider here + * will be 1000 * 1000000 = 1953125 << 9 + * + * Instead of dealing with big numbers lets fold this 9 left shift + */ + + if (speed == USB_SPEED_FULL) { + /* + * Full-speed feedback endpoints report frequency + * in samples/frame + * Format is encoded in Q10.10 left-justified in the 24 bits, + * so that it has a Q10.14 format. + * + * ff = (freq << 14) / 1000 + */ + freq <<= 5; + } else { + /* + * High-speed feedback endpoints report frequency + * in samples/microframe. + * Format is encoded in Q12.13 fitted into four bytes so that + * the binary point is located between the second and the third + * byte fromat (that is Q16.16) + * + * ff = (freq << 16) / 8000 + * + * Win10 and OSX UAC2 drivers require number of samples per packet + * in order to honor the feedback value. + * Linux snd-usb-audio detects the applied bit-shift automatically. + */ + ep_desc = out_ep->desc; + freq <<= 4 + (ep_desc->bInterval - 1); + } + + ff = DIV_ROUND_CLOSEST_ULL((freq * pitch), 1953125); + + *(__le32 *)buf = cpu_to_le32(ff); +} + +static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) +{ + unsigned int pending; + unsigned int hw_ptr; + int status = req->status; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + struct uac_rtd_params *prm = req->context; + struct snd_uac_chip *uac = prm->uac; + unsigned int frames, p_pktsize; + unsigned long long pitched_rate_mil, p_pktsize_residue_mil, + residue_frames_mil, div_result; + + /* i/f shutting down */ + if (!prm->ep_enabled) { + usb_ep_free_request(ep, req); + return; + } + + if (req->status == -ESHUTDOWN) + 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; + + snd_pcm_stream_lock(substream); + + runtime = substream->runtime; + if (!runtime || !snd_pcm_running(substream)) { + snd_pcm_stream_unlock(substream); + goto exit; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * For each IN packet, take the quotient of the current data + * rate and the endpoint's interval as the base packet size. + * If there is a residue from this division, add it to the + * residue accumulator. + */ + unsigned long long p_interval_mil = uac->p_interval * 1000000ULL; + + pitched_rate_mil = (unsigned long long) prm->srate * prm->pitch; + div_result = pitched_rate_mil; + do_div(div_result, uac->p_interval); + do_div(div_result, 1000000); + frames = (unsigned int) div_result; + + pr_debug("p_srate %d, pitch %d, interval_mil %llu, frames %d\n", + prm->srate, prm->pitch, p_interval_mil, frames); + + p_pktsize = min_t(unsigned int, + uac->p_framesize * frames, + ep->maxpacket); + + if (p_pktsize < ep->maxpacket) { + residue_frames_mil = pitched_rate_mil - frames * p_interval_mil; + p_pktsize_residue_mil = uac->p_framesize * residue_frames_mil; + } else + p_pktsize_residue_mil = 0; + + req->length = p_pktsize; + uac->p_residue_mil += p_pktsize_residue_mil; + + /* + * Whenever there are more bytes in the accumulator p_residue_mil than we + * need to add one more sample frame, increase this packet's + * size and decrease the accumulator. + */ + div_result = uac->p_residue_mil; + do_div(div_result, uac->p_interval); + do_div(div_result, 1000000); + if ((unsigned int) div_result >= uac->p_framesize) { + req->length += uac->p_framesize; + uac->p_residue_mil -= uac->p_framesize * p_interval_mil; + pr_debug("increased req length to %d\n", req->length); + } + pr_debug("remains uac->p_residue_mil %llu\n", uac->p_residue_mil); + + req->actual = req->length; + } + + hw_ptr = prm->hw_ptr; + + /* Pack USB load in ALSA ring buffer */ + pending = runtime->dma_bytes - hw_ptr; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (unlikely(pending < req->actual)) { + memcpy(req->buf, runtime->dma_area + hw_ptr, pending); + memcpy(req->buf + pending, runtime->dma_area, + req->actual - pending); + } else { + memcpy(req->buf, runtime->dma_area + hw_ptr, + req->actual); + } + } else { + if (unlikely(pending < req->actual)) { + memcpy(runtime->dma_area + hw_ptr, req->buf, pending); + memcpy(runtime->dma_area, req->buf + pending, + req->actual - pending); + } else { + memcpy(runtime->dma_area + hw_ptr, req->buf, + req->actual); + } + } + + /* update hw_ptr after data is copied to memory */ + prm->hw_ptr = (hw_ptr + req->actual) % runtime->dma_bytes; + hw_ptr = prm->hw_ptr; + snd_pcm_stream_unlock(substream); + + if ((hw_ptr % snd_pcm_lib_period_bytes(substream)) < req->actual) + snd_pcm_period_elapsed(substream); + +exit: + if (usb_ep_queue(ep, req, GFP_ATOMIC)) + dev_err(uac->card->dev, "%d Error!\n", __LINE__); +} + +static void u_audio_iso_fback_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct uac_rtd_params *prm = req->context; + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + int status = req->status; + + /* i/f shutting down */ + if (!prm->fb_ep_enabled) { + kfree(req->buf); + usb_ep_free_request(ep, req); + return; + } + + if (req->status == -ESHUTDOWN) + 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); + + u_audio_set_fback_frequency(audio_dev->gadget->speed, audio_dev->out_ep, + prm->srate, prm->pitch, + req->buf); + + if (usb_ep_queue(ep, req, GFP_ATOMIC)) + dev_err(uac->card->dev, "%d Error!\n", __LINE__); +} + +static int uac_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); + struct uac_rtd_params *prm; + struct g_audio *audio_dev; + struct uac_params *params; + int err = 0; + + audio_dev = uac->audio_dev; + params = &audio_dev->params; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + /* 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; + } + + /* Clear buffer after Play stops */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss) + memset(prm->rbuf, 0, prm->max_psize * params->req_number); + + return err; +} + +static snd_pcm_uframes_t uac_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); + struct uac_rtd_params *prm; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + return bytes_to_frames(substream->runtime, prm->hw_ptr); +} + +static u64 uac_ssize_to_fmt(int ssize) +{ + u64 ret; + + switch (ssize) { + case 3: + ret = SNDRV_PCM_FMTBIT_S24_3LE; + break; + case 4: + ret = SNDRV_PCM_FMTBIT_S32_LE; + break; + default: + ret = SNDRV_PCM_FMTBIT_S16_LE; + break; + } + + return ret; +} + +static int uac_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct g_audio *audio_dev; + struct uac_params *params; + struct uac_rtd_params *prm; + int p_ssize, c_ssize; + int p_chmask, c_chmask; + + audio_dev = uac->audio_dev; + params = &audio_dev->params; + p_ssize = params->p_ssize; + c_ssize = params->c_ssize; + p_chmask = params->p_chmask; + c_chmask = params->c_chmask; + uac->p_residue_mil = 0; + + runtime->hw = uac_pcm_hardware; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw.formats = uac_ssize_to_fmt(p_ssize); + runtime->hw.channels_min = num_channels(p_chmask); + prm = &uac->p_prm; + } else { + runtime->hw.formats = uac_ssize_to_fmt(c_ssize); + runtime->hw.channels_min = num_channels(c_chmask); + prm = &uac->c_prm; + } + + runtime->hw.period_bytes_min = 2 * prm->max_psize + / runtime->hw.periods_min; + runtime->hw.rate_min = prm->srate; + 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 uac_pcm_null(struct snd_pcm_substream *substream) +{ + return 0; +} + +static const struct snd_pcm_ops uac_pcm_ops = { + .open = uac_pcm_open, + .close = uac_pcm_null, + .trigger = uac_pcm_trigger, + .pointer = uac_pcm_pointer, + .prepare = uac_pcm_null, +}; + +static inline void free_ep(struct uac_rtd_params *prm, struct usb_ep *ep) +{ + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev; + struct uac_params *params; + int i; + + if (!prm->ep_enabled) + return; + + audio_dev = uac->audio_dev; + params = &audio_dev->params; + + for (i = 0; i < params->req_number; i++) { + if (prm->reqs[i]) { + if (usb_ep_dequeue(ep, prm->reqs[i])) + usb_ep_free_request(ep, prm->reqs[i]); + /* + * If usb_ep_dequeue() cannot successfully dequeue the + * request, the request will be freed by the completion + * callback. + */ + + prm->reqs[i] = NULL; + } + } + + prm->ep_enabled = false; + + if (usb_ep_disable(ep)) + dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__); +} + +static inline void free_ep_fback(struct uac_rtd_params *prm, struct usb_ep *ep) +{ + struct snd_uac_chip *uac = prm->uac; + + if (!prm->fb_ep_enabled) + return; + + if (prm->req_fback) { + if (usb_ep_dequeue(ep, prm->req_fback)) { + kfree(prm->req_fback->buf); + usb_ep_free_request(ep, prm->req_fback); + } + prm->req_fback = NULL; + } + + prm->fb_ep_enabled = false; + + if (usb_ep_disable(ep)) + dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__); +} + +static void set_active(struct uac_rtd_params *prm, bool active) +{ + // notifying through the Rate ctrl + unsigned long flags; + + spin_lock_irqsave(&prm->lock, flags); + if (prm->active != active) { + prm->active = active; + snd_ctl_notify(prm->uac->card, SNDRV_CTL_EVENT_MASK_VALUE, + &prm->snd_kctl_rate_id); + } + spin_unlock_irqrestore(&prm->lock, flags); +} + +int u_audio_set_capture_srate(struct g_audio *audio_dev, int srate) +{ + struct uac_params *params = &audio_dev->params; + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + int i; + unsigned long flags; + + dev_dbg(&audio_dev->gadget->dev, "%s: srate %d\n", __func__, srate); + prm = &uac->c_prm; + for (i = 0; i < UAC_MAX_RATES; i++) { + if (params->c_srates[i] == srate) { + spin_lock_irqsave(&prm->lock, flags); + prm->srate = srate; + spin_unlock_irqrestore(&prm->lock, flags); + return 0; + } + if (params->c_srates[i] == 0) + break; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(u_audio_set_capture_srate); + +int u_audio_get_capture_srate(struct g_audio *audio_dev, u32 *val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + + prm = &uac->c_prm; + spin_lock_irqsave(&prm->lock, flags); + *val = prm->srate; + spin_unlock_irqrestore(&prm->lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_get_capture_srate); + +int u_audio_set_playback_srate(struct g_audio *audio_dev, int srate) +{ + struct uac_params *params = &audio_dev->params; + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + int i; + unsigned long flags; + + dev_dbg(&audio_dev->gadget->dev, "%s: srate %d\n", __func__, srate); + prm = &uac->p_prm; + for (i = 0; i < UAC_MAX_RATES; i++) { + if (params->p_srates[i] == srate) { + spin_lock_irqsave(&prm->lock, flags); + prm->srate = srate; + spin_unlock_irqrestore(&prm->lock, flags); + return 0; + } + if (params->p_srates[i] == 0) + break; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(u_audio_set_playback_srate); + +int u_audio_get_playback_srate(struct g_audio *audio_dev, u32 *val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + + prm = &uac->p_prm; + spin_lock_irqsave(&prm->lock, flags); + *val = prm->srate; + spin_unlock_irqrestore(&prm->lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_get_playback_srate); + +int u_audio_start_capture(struct g_audio *audio_dev) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct usb_gadget *gadget = audio_dev->gadget; + struct device *dev = &gadget->dev; + struct usb_request *req, *req_fback; + struct usb_ep *ep, *ep_fback; + struct uac_rtd_params *prm; + struct uac_params *params = &audio_dev->params; + int req_len, i, ret; + + prm = &uac->c_prm; + dev_dbg(dev, "start capture with rate %d\n", prm->srate); + ep = audio_dev->out_ep; + ret = config_ep_by_speed(gadget, &audio_dev->func, ep); + if (ret < 0) { + dev_err(dev, "config_ep_by_speed for out_ep failed (%d)\n", ret); + return ret; + } + + req_len = ep->maxpacket; + + prm->ep_enabled = true; + ret = usb_ep_enable(ep); + if (ret < 0) { + dev_err(dev, "usb_ep_enable failed for out_ep (%d)\n", ret); + return ret; + } + + for (i = 0; i < params->req_number; i++) { + if (!prm->reqs[i]) { + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (req == NULL) + return -ENOMEM; + + prm->reqs[i] = req; + + req->zero = 0; + req->context = prm; + req->length = req_len; + req->complete = u_audio_iso_complete; + req->buf = prm->rbuf + i * ep->maxpacket; + } + + if (usb_ep_queue(ep, prm->reqs[i], GFP_ATOMIC)) + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + } + + set_active(&uac->c_prm, true); + + ep_fback = audio_dev->in_ep_fback; + if (!ep_fback) + return 0; + + /* Setup feedback endpoint */ + ret = config_ep_by_speed(gadget, &audio_dev->func, ep_fback); + if (ret < 0) { + dev_err(dev, "config_ep_by_speed in_ep_fback failed (%d)\n", ret); + goto err_out_ep; + } + + ret = usb_ep_enable(ep_fback); + if (ret < 0) { + dev_err(dev, "usb_ep_enable failed for in_ep_fback (%d)\n", ret); + goto err_out_ep; + } + prm->fb_ep_enabled = true; + req_len = ep_fback->maxpacket; + + req_fback = usb_ep_alloc_request(ep_fback, GFP_ATOMIC); + if (req_fback == NULL) + return -ENOMEM; + + prm->req_fback = req_fback; + req_fback->zero = 0; + req_fback->context = prm; + req_fback->length = req_len; + req_fback->complete = u_audio_iso_fback_complete; + + req_fback->buf = kzalloc(req_len, GFP_ATOMIC); + if (!req_fback->buf) + return -ENOMEM; + + /* + * Configure the feedback endpoint's reported frequency. + * Always start with original frequency since its deviation can't + * be meauserd at start of playback + */ + prm->pitch = 1000000; + u_audio_set_fback_frequency(audio_dev->gadget->speed, ep, + prm->srate, prm->pitch, + req_fback->buf); + + if (usb_ep_queue(ep_fback, req_fback, GFP_ATOMIC)) + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + + return 0; + +err_out_ep: + set_active(prm, false); + free_ep(prm, ep); + + return ret; +} +EXPORT_SYMBOL_GPL(u_audio_start_capture); + +void u_audio_stop_capture(struct g_audio *audio_dev) +{ + struct snd_uac_chip *uac = audio_dev->uac; + + set_active(&uac->c_prm, false); + if (audio_dev->in_ep_fback) + free_ep_fback(&uac->c_prm, audio_dev->in_ep_fback); + free_ep(&uac->c_prm, audio_dev->out_ep); +} +EXPORT_SYMBOL_GPL(u_audio_stop_capture); + +int u_audio_start_playback(struct g_audio *audio_dev) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct usb_gadget *gadget = audio_dev->gadget; + struct device *dev = &gadget->dev; + struct usb_request *req; + struct usb_ep *ep; + struct uac_rtd_params *prm; + struct uac_params *params = &audio_dev->params; + unsigned int factor; + const struct usb_endpoint_descriptor *ep_desc; + int req_len, i, ret; + unsigned int p_pktsize; + + prm = &uac->p_prm; + dev_dbg(dev, "start playback with rate %d\n", prm->srate); + ep = audio_dev->in_ep; + ret = config_ep_by_speed(gadget, &audio_dev->func, ep); + if (ret < 0) { + dev_err(dev, "config_ep_by_speed for in_ep failed (%d)\n", ret); + return ret; + } + + ep_desc = ep->desc; + /* + * Always start with original frequency + */ + prm->pitch = 1000000; + + /* pre-calculate the playback endpoint's interval */ + if (gadget->speed == USB_SPEED_FULL) + factor = 1000; + else + factor = 8000; + + /* pre-compute some values for iso_complete() */ + uac->p_framesize = params->p_ssize * + num_channels(params->p_chmask); + uac->p_interval = factor / (1 << (ep_desc->bInterval - 1)); + p_pktsize = min_t(unsigned int, + uac->p_framesize * + (prm->srate / uac->p_interval), + ep->maxpacket); + + req_len = p_pktsize; + uac->p_residue_mil = 0; + + prm->ep_enabled = true; + ret = usb_ep_enable(ep); + if (ret < 0) { + dev_err(dev, "usb_ep_enable failed for in_ep (%d)\n", ret); + return ret; + } + + for (i = 0; i < params->req_number; i++) { + if (!prm->reqs[i]) { + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (req == NULL) + return -ENOMEM; + + prm->reqs[i] = req; + + req->zero = 0; + req->context = prm; + req->length = req_len; + req->complete = u_audio_iso_complete; + req->buf = prm->rbuf + i * ep->maxpacket; + } + + if (usb_ep_queue(ep, prm->reqs[i], GFP_ATOMIC)) + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + } + + set_active(&uac->p_prm, true); + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_start_playback); + +void u_audio_stop_playback(struct g_audio *audio_dev) +{ + struct snd_uac_chip *uac = audio_dev->uac; + + set_active(&uac->p_prm, false); + free_ep(&uac->p_prm, audio_dev->in_ep); +} +EXPORT_SYMBOL_GPL(u_audio_stop_playback); + +void u_audio_suspend(struct g_audio *audio_dev) +{ + struct snd_uac_chip *uac = audio_dev->uac; + + set_active(&uac->p_prm, false); + set_active(&uac->c_prm, false); +} +EXPORT_SYMBOL_GPL(u_audio_suspend); + +int u_audio_get_volume(struct g_audio *audio_dev, int playback, s16 *val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + + if (playback) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + spin_lock_irqsave(&prm->lock, flags); + *val = prm->volume; + spin_unlock_irqrestore(&prm->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_get_volume); + +int u_audio_set_volume(struct g_audio *audio_dev, int playback, s16 val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + int change = 0; + + if (playback) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + spin_lock_irqsave(&prm->lock, flags); + val = clamp(val, prm->volume_min, prm->volume_max); + if (prm->volume != val) { + prm->volume = val; + change = 1; + } + spin_unlock_irqrestore(&prm->lock, flags); + + if (change) + snd_ctl_notify(uac->card, SNDRV_CTL_EVENT_MASK_VALUE, + &prm->snd_kctl_volume_id); + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_set_volume); + +int u_audio_get_mute(struct g_audio *audio_dev, int playback, int *val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + + if (playback) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + spin_lock_irqsave(&prm->lock, flags); + *val = prm->mute; + spin_unlock_irqrestore(&prm->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_get_mute); + +int u_audio_set_mute(struct g_audio *audio_dev, int playback, int val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + int change = 0; + int mute; + + if (playback) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + mute = val ? 1 : 0; + + spin_lock_irqsave(&prm->lock, flags); + if (prm->mute != mute) { + prm->mute = mute; + change = 1; + } + spin_unlock_irqrestore(&prm->lock, flags); + + if (change) + snd_ctl_notify(uac->card, SNDRV_CTL_EVENT_MASK_VALUE, + &prm->snd_kctl_mute_id); + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_set_mute); + + +static int u_audio_pitch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + struct uac_params *params = &audio_dev->params; + unsigned int pitch_min, pitch_max; + + pitch_min = (1000 - FBACK_SLOW_MAX) * 1000; + pitch_max = (1000 + params->fb_max) * 1000; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = pitch_min; + uinfo->value.integer.max = pitch_max; + uinfo->value.integer.step = 1; + return 0; +} + +static int u_audio_pitch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = prm->pitch; + + return 0; +} + +static int u_audio_pitch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + struct uac_params *params = &audio_dev->params; + unsigned int val; + unsigned int pitch_min, pitch_max; + int change = 0; + + pitch_min = (1000 - FBACK_SLOW_MAX) * 1000; + pitch_max = (1000 + params->fb_max) * 1000; + + val = ucontrol->value.integer.value[0]; + + if (val < pitch_min) + val = pitch_min; + if (val > pitch_max) + val = pitch_max; + + if (prm->pitch != val) { + prm->pitch = val; + change = 1; + } + + return change; +} + +static int u_audio_mute_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + uinfo->value.integer.step = 1; + + return 0; +} + +static int u_audio_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&prm->lock, flags); + ucontrol->value.integer.value[0] = !prm->mute; + spin_unlock_irqrestore(&prm->lock, flags); + + return 0; +} + +static int u_audio_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + unsigned int val; + unsigned long flags; + int change = 0; + + val = !ucontrol->value.integer.value[0]; + + spin_lock_irqsave(&prm->lock, flags); + if (val != prm->mute) { + prm->mute = val; + change = 1; + } + spin_unlock_irqrestore(&prm->lock, flags); + + if (change && audio_dev->notify) + audio_dev->notify(audio_dev, prm->fu_id, UAC_FU_MUTE); + + return change; +} + +/* + * TLV callback for mixer volume controls + */ +static int u_audio_volume_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *_tlv) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + DECLARE_TLV_DB_MINMAX(scale, 0, 0); + + if (size < sizeof(scale)) + return -ENOMEM; + + /* UAC volume resolution is 1/256 dB, TLV is 1/100 dB */ + scale[2] = (prm->volume_min * 100) / 256; + scale[3] = (prm->volume_max * 100) / 256; + if (copy_to_user(_tlv, scale, sizeof(scale))) + return -EFAULT; + + return 0; +} + +static int u_audio_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = + (prm->volume_max - prm->volume_min + prm->volume_res - 1) + / prm->volume_res; + uinfo->value.integer.step = 1; + + return 0; +} + +static int u_audio_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&prm->lock, flags); + ucontrol->value.integer.value[0] = + (prm->volume - prm->volume_min) / prm->volume_res; + spin_unlock_irqrestore(&prm->lock, flags); + + return 0; +} + +static int u_audio_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + unsigned int val; + s16 volume; + unsigned long flags; + int change = 0; + + val = ucontrol->value.integer.value[0]; + + spin_lock_irqsave(&prm->lock, flags); + volume = (val * prm->volume_res) + prm->volume_min; + volume = clamp(volume, prm->volume_min, prm->volume_max); + if (volume != prm->volume) { + prm->volume = volume; + change = 1; + } + spin_unlock_irqrestore(&prm->lock, flags); + + if (change && audio_dev->notify) + audio_dev->notify(audio_dev, prm->fu_id, UAC_FU_VOLUME); + + return change; +} + +static int get_max_srate(const int *srates) +{ + int i, max_srate = 0; + + for (i = 0; i < UAC_MAX_RATES; i++) { + if (srates[i] == 0) + break; + if (srates[i] > max_srate) + max_srate = srates[i]; + } + return max_srate; +} + +static int get_min_srate(const int *srates) +{ + int i, min_srate = INT_MAX; + + for (i = 0; i < UAC_MAX_RATES; i++) { + if (srates[i] == 0) + break; + if (srates[i] < min_srate) + min_srate = srates[i]; + } + return min_srate; +} + +static int u_audio_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + const int *srates; + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + struct uac_params *params = &audio_dev->params; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + + if (prm == &uac->c_prm) + srates = params->c_srates; + else + srates = params->p_srates; + uinfo->value.integer.min = get_min_srate(srates); + uinfo->value.integer.max = get_max_srate(srates); + return 0; +} + +static int u_audio_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&prm->lock, flags); + if (prm->active) + ucontrol->value.integer.value[0] = prm->srate; + else + /* not active: reporting zero rate */ + ucontrol->value.integer.value[0] = 0; + spin_unlock_irqrestore(&prm->lock, flags); + return 0; +} + +static struct snd_kcontrol_new u_audio_controls[] = { + [UAC_FBACK_CTRL] = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Capture Pitch 1000000", + .info = u_audio_pitch_info, + .get = u_audio_pitch_get, + .put = u_audio_pitch_put, + }, + [UAC_P_PITCH_CTRL] = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Playback Pitch 1000000", + .info = u_audio_pitch_info, + .get = u_audio_pitch_get, + .put = u_audio_pitch_put, + }, + [UAC_MUTE_CTRL] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", /* will be filled later */ + .info = u_audio_mute_info, + .get = u_audio_mute_get, + .put = u_audio_mute_put, + }, + [UAC_VOLUME_CTRL] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", /* will be filled later */ + .info = u_audio_volume_info, + .get = u_audio_volume_get, + .put = u_audio_volume_put, + }, + [UAC_RATE_CTRL] = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "", /* will be filled later */ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = u_audio_rate_info, + .get = u_audio_rate_get, + }, +}; + +static void u_audio_card_free(struct snd_card *card) +{ + struct snd_uac_chip *uac = card->private_data; + + if (!uac) + return; + + kfree(uac->p_prm.reqs); + kfree(uac->c_prm.reqs); + kfree(uac->p_prm.rbuf); + kfree(uac->c_prm.rbuf); + kfree(uac); +} + +int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, + const char *card_name) +{ + struct snd_uac_chip *uac; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_kcontrol *kctl; + struct uac_params *params; + int p_chmask, c_chmask; + int i, err; + + if (!g_audio) + return -EINVAL; + + uac = kzalloc_obj(*uac); + if (!uac) + return -ENOMEM; + g_audio->uac = uac; + uac->audio_dev = g_audio; + + params = &g_audio->params; + p_chmask = params->p_chmask; + c_chmask = params->c_chmask; + + if (c_chmask) { + struct uac_rtd_params *prm = &uac->c_prm; + + spin_lock_init(&prm->lock); + uac->c_prm.uac = uac; + prm->max_psize = g_audio->out_ep_maxpsize; + prm->srate = params->c_srates[0]; + + prm->reqs = kzalloc_objs(struct usb_request *, + params->req_number); + if (!prm->reqs) { + err = -ENOMEM; + goto fail; + } + + prm->rbuf = kcalloc(params->req_number, prm->max_psize, + GFP_KERNEL); + if (!prm->rbuf) { + prm->max_psize = 0; + err = -ENOMEM; + goto fail; + } + } + + if (p_chmask) { + struct uac_rtd_params *prm = &uac->p_prm; + + spin_lock_init(&prm->lock); + uac->p_prm.uac = uac; + prm->max_psize = g_audio->in_ep_maxpsize; + prm->srate = params->p_srates[0]; + + prm->reqs = kzalloc_objs(struct usb_request *, + params->req_number); + if (!prm->reqs) { + err = -ENOMEM; + goto fail; + } + + prm->rbuf = kcalloc(params->req_number, prm->max_psize, + GFP_KERNEL); + if (!prm->rbuf) { + prm->max_psize = 0; + err = -ENOMEM; + goto fail; + } + } + + /* Choose any slot, with no id */ + err = snd_card_new(&g_audio->gadget->dev, + -1, NULL, THIS_MODULE, 0, &card); + if (err < 0) + goto fail; + + uac->card = card; + card->private_data = uac; + card->private_free = u_audio_card_free; + + /* + * Create first PCM device + * Create a substream only for non-zero channel streams + */ + err = snd_pcm_new(uac->card, pcm_name, 0, + p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm); + if (err < 0) + goto snd_fail; + + strscpy(pcm->name, pcm_name); + pcm->private_data = uac; + uac->pcm = pcm; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac_pcm_ops); + + /* + * Create mixer and controls + * Create only if it's required on USB side + */ + if ((c_chmask && g_audio->in_ep_fback) + || (p_chmask && params->p_fu.id) + || (c_chmask && params->c_fu.id)) + strscpy(card->mixername, card_name); + + if (c_chmask && g_audio->in_ep_fback) { + kctl = snd_ctl_new1(&u_audio_controls[UAC_FBACK_CTRL], + &uac->c_prm); + if (!kctl) { + err = -ENOMEM; + goto snd_fail; + } + + kctl->id.device = pcm->device; + kctl->id.subdevice = 0; + + err = snd_ctl_add(card, kctl); + if (err < 0) + goto snd_fail; + } + + if (p_chmask) { + kctl = snd_ctl_new1(&u_audio_controls[UAC_P_PITCH_CTRL], + &uac->p_prm); + if (!kctl) { + err = -ENOMEM; + goto snd_fail; + } + + kctl->id.device = pcm->device; + kctl->id.subdevice = 0; + + err = snd_ctl_add(card, kctl); + if (err < 0) + goto snd_fail; + } + + for (i = 0; i <= SNDRV_PCM_STREAM_LAST; i++) { + struct uac_rtd_params *prm; + struct uac_fu_params *fu; + char ctrl_name[24]; + char *direction; + + if (!pcm->streams[i].substream_count) + continue; + + if (i == SNDRV_PCM_STREAM_PLAYBACK) { + prm = &uac->p_prm; + fu = ¶ms->p_fu; + direction = "Playback"; + } else { + prm = &uac->c_prm; + fu = ¶ms->c_fu; + direction = "Capture"; + } + + prm->fu_id = fu->id; + + if (fu->mute_present) { + snprintf(ctrl_name, sizeof(ctrl_name), + "PCM %s Switch", direction); + + u_audio_controls[UAC_MUTE_CTRL].name = ctrl_name; + + kctl = snd_ctl_new1(&u_audio_controls[UAC_MUTE_CTRL], + prm); + if (!kctl) { + err = -ENOMEM; + goto snd_fail; + } + + kctl->id.device = pcm->device; + kctl->id.subdevice = 0; + + err = snd_ctl_add(card, kctl); + if (err < 0) + goto snd_fail; + prm->snd_kctl_mute_id = kctl->id; + prm->mute = 0; + } + + if (fu->volume_present) { + snprintf(ctrl_name, sizeof(ctrl_name), + "PCM %s Volume", direction); + + u_audio_controls[UAC_VOLUME_CTRL].name = ctrl_name; + + kctl = snd_ctl_new1(&u_audio_controls[UAC_VOLUME_CTRL], + prm); + if (!kctl) { + err = -ENOMEM; + goto snd_fail; + } + + kctl->id.device = pcm->device; + kctl->id.subdevice = 0; + + + kctl->tlv.c = u_audio_volume_tlv; + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + + err = snd_ctl_add(card, kctl); + if (err < 0) + goto snd_fail; + prm->snd_kctl_volume_id = kctl->id; + prm->volume = fu->volume_max; + prm->volume_max = fu->volume_max; + prm->volume_min = fu->volume_min; + prm->volume_res = fu->volume_res; + } + + /* Add rate control */ + snprintf(ctrl_name, sizeof(ctrl_name), + "%s Rate", direction); + u_audio_controls[UAC_RATE_CTRL].name = ctrl_name; + + kctl = snd_ctl_new1(&u_audio_controls[UAC_RATE_CTRL], prm); + if (!kctl) { + err = -ENOMEM; + goto snd_fail; + } + + kctl->id.device = pcm->device; + kctl->id.subdevice = 0; + + err = snd_ctl_add(card, kctl); + if (err < 0) + goto snd_fail; + prm->snd_kctl_rate_id = kctl->id; + } + + strscpy(card->driver, card_name); + strscpy(card->shortname, card_name); + snprintf(card->longname, sizeof(card->longname), "%s %i", + card_name, card->dev->id); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + NULL, 0, BUFF_SIZE_MAX); + + err = snd_card_register(card); + + if (!err) + return 0; + +snd_fail: + snd_card_free(card); + return err; + +fail: + kfree(uac->p_prm.reqs); + kfree(uac->c_prm.reqs); + kfree(uac->p_prm.rbuf); + kfree(uac->c_prm.rbuf); + kfree(uac); + + return err; +} +EXPORT_SYMBOL_GPL(g_audio_setup); + +void g_audio_cleanup(struct g_audio *g_audio) +{ + struct snd_uac_chip *uac; + struct snd_card *card; + + if (!g_audio || !g_audio->uac) + return; + + uac = g_audio->uac; + g_audio->uac = NULL; + + card = uac->card; + if (card) + snd_card_free_when_closed(card); +} +EXPORT_SYMBOL_GPL(g_audio_cleanup); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("USB gadget \"ALSA sound card\" utilities"); +MODULE_AUTHOR("Ruslan Bilovol"); diff --git a/drivers/usb/gadget/function/u_audio.h b/drivers/usb/gadget/function/u_audio.h new file mode 100644 index 000000000..9512b8fcc --- /dev/null +++ b/drivers/usb/gadget/function/u_audio.h @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * u_audio.h -- interface to USB gadget "ALSA sound card" utilities + * + * Copyright (C) 2016 + * Author: Ruslan Bilovol + */ + +#ifndef __U_AUDIO_H +#define __U_AUDIO_H + +#include +#include "uac_common.h" + +/* + * Same maximum frequency deviation on the slower side as in + * sound/usb/endpoint.c. Value is expressed in per-mil deviation. + */ +#define FBACK_SLOW_MAX 250 + +/* + * Maximum frequency deviation on the faster side, default value for UAC1/2. + * Value is expressed in per-mil deviation. + * UAC2 provides the value as a parameter as it impacts the endpoint required + * bandwidth. + */ +#define FBACK_FAST_MAX 5 + +/* Feature Unit parameters */ +struct uac_fu_params { + int id; /* Feature Unit ID */ + + bool mute_present; /* mute control enable */ + + bool volume_present; /* volume control enable */ + s16 volume_min; /* min volume in 1/256 dB */ + s16 volume_max; /* max volume in 1/256 dB */ + s16 volume_res; /* volume resolution in 1/256 dB */ +}; + +struct uac_params { + /* playback */ + int p_chmask; /* channel mask */ + int p_srates[UAC_MAX_RATES]; /* available rates in Hz (0 terminated list) */ + int p_ssize; /* sample size */ + struct uac_fu_params p_fu; /* Feature Unit parameters */ + + /* capture */ + int c_chmask; /* channel mask */ + int c_srates[UAC_MAX_RATES]; /* available rates in Hz (0 terminated list) */ + int c_ssize; /* sample size */ + struct uac_fu_params c_fu; /* Feature Unit parameters */ + + /* rates are dynamic, in uac_rtd_params */ + + int req_number; /* number of preallocated requests */ + int fb_max; /* upper frequency drift feedback limit per-mil */ +}; + +struct g_audio { + struct usb_function func; + struct usb_gadget *gadget; + + struct usb_ep *in_ep; + + struct usb_ep *out_ep; + /* feedback IN endpoint corresponding to out_ep */ + struct usb_ep *in_ep_fback; + + /* Max packet size for all in_ep possible speeds */ + unsigned int in_ep_maxpsize; + /* Max packet size for all out_ep possible speeds */ + unsigned int out_ep_maxpsize; + + /* Notify UAC driver about control change */ + int (*notify)(struct g_audio *g_audio, int unit_id, int cs); + + /* The ALSA Sound Card it represents on the USB-Client side */ + struct snd_uac_chip *uac; + + struct uac_params params; +}; + +static inline struct g_audio *func_to_g_audio(struct usb_function *f) +{ + return container_of(f, struct g_audio, func); +} + +static inline uint num_channels(uint chanmask) +{ + uint num = 0; + + while (chanmask) { + num += (chanmask & 1); + chanmask >>= 1; + } + + return num; +} + +/* + * g_audio_setup - initialize one virtual ALSA sound card + * @g_audio: struct with filled params, in_ep_maxpsize, out_ep_maxpsize + * @pcm_name: the id string for a PCM instance of this sound card + * @card_name: name of this soundcard + * + * This sets up the single virtual ALSA sound card that may be exported by a + * gadget driver using this framework. + * + * Context: may sleep + * + * Returns zero on success, or a negative error on failure. + */ +int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, + const char *card_name); +void g_audio_cleanup(struct g_audio *g_audio); + +int u_audio_start_capture(struct g_audio *g_audio); +void u_audio_stop_capture(struct g_audio *g_audio); +int u_audio_start_playback(struct g_audio *g_audio); +void u_audio_stop_playback(struct g_audio *g_audio); + +int u_audio_get_capture_srate(struct g_audio *audio_dev, u32 *val); +int u_audio_set_capture_srate(struct g_audio *audio_dev, int srate); +int u_audio_get_playback_srate(struct g_audio *audio_dev, u32 *val); +int u_audio_set_playback_srate(struct g_audio *audio_dev, int srate); + +int u_audio_get_volume(struct g_audio *g_audio, int playback, s16 *val); +int u_audio_set_volume(struct g_audio *g_audio, int playback, s16 val); +int u_audio_get_mute(struct g_audio *g_audio, int playback, int *val); +int u_audio_set_mute(struct g_audio *g_audio, int playback, int val); + +void u_audio_suspend(struct g_audio *g_audio); + +#endif /* __U_AUDIO_H */ diff --git a/drivers/usb/gadget/function/u_ecm.h b/drivers/usb/gadget/function/u_ecm.h new file mode 100644 index 000000000..7f666b9de --- /dev/null +++ b/drivers/usb/gadget/function/u_ecm.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * u_ecm.h + * + * Utility definitions for the ecm function + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef U_ECM_H +#define U_ECM_H + +#include + +/** + * struct f_ecm_opts - ECM function options + * @func_inst: USB function instance. + * @net: The net_device associated with the ECM function. + * @bound: True if the net_device is shared and pre-registered during the + * legacy composite driver's bind phase (e.g., multi.c). If false, + * the ECM function will register the net_device during its own + * bind phase. + * @bind_count: Tracks the number of configurations the ECM function is + * bound to, preventing double-registration of the @net device. + * @lock: Protects the data from concurrent access by configfs read/write + * and create symlink/remove symlink operations. + * @refcnt: Reference counter for the function instance. + */ +struct f_ecm_opts { + struct usb_function_instance func_inst; + struct net_device *net; + bool bound; + int bind_count; + + struct mutex lock; + int refcnt; +}; + +#endif /* U_ECM_H */ diff --git a/drivers/usb/gadget/function/u_eem.h b/drivers/usb/gadget/function/u_eem.h new file mode 100644 index 000000000..78ef55815 --- /dev/null +++ b/drivers/usb/gadget/function/u_eem.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * u_eem.h + * + * Utility definitions for the eem function + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef U_EEM_H +#define U_EEM_H + +#include + +/** + * struct f_eem_opts - EEM function options + * @func_inst: USB function instance. + * @net: The net_device associated with the EEM function. + * @bound: True if the net_device is shared and pre-registered during the + * legacy composite driver's bind phase (e.g., multi.c). If false, + * the EEM function will register the net_device during its own + * bind phase. + * @bind_count: Tracks the number of configurations the EEM function is + * bound to, preventing double-registration of the @net device. + * @lock: Protects the data from concurrent access by configfs read/write + * and create symlink/remove symlink operations. + * @refcnt: Reference counter for the function instance. + */ +struct f_eem_opts { + struct usb_function_instance func_inst; + struct net_device *net; + bool bound; + int bind_count; + + struct mutex lock; + int refcnt; +}; + +#endif /* U_EEM_H */ diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c new file mode 100644 index 000000000..59d85d6a8 --- /dev/null +++ b/drivers/usb/gadget/function/u_ether.c @@ -0,0 +1,1275 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#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" + +/* Experiments show that both Linux and Windows hosts allow up to 16k + * frame sizes. Set the max MTU size to 15k+52 to prevent allocating 32k + * blocks and still have efficient handling. */ +#define GETHER_MAX_MTU_SIZE 15412 +#define GETHER_MAX_ETH_FRAME_LEN (GETHER_MAX_MTU_SIZE + ETH_HLEN) + +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 qmult; + + 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; + bool no_skb_reserve; + bool ifname_set; + u8 host_mac[ETH_ALEN]; + u8 dev_mac[ETH_ALEN]; +}; + +/*-------------------------------------------------------------------------*/ + +#define RX_EXTRA 20 /* bytes guarding against rx overflows */ + +#define DEFAULT_QLEN 2 /* double buffering by default */ + +/* use deeper queues at high/super speed */ +static inline int qlen(struct usb_gadget *gadget, unsigned qmult) +{ + if (gadget->speed == USB_SPEED_HIGH || gadget->speed >= USB_SPEED_SUPER) + return qmult * DEFAULT_QLEN; + else + return DEFAULT_QLEN; +} + +/*-------------------------------------------------------------------------*/ + +/* NETWORK DRIVER HOOKUP (to the layer above this driver) */ + +static void eth_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *p) +{ + struct eth_dev *dev = netdev_priv(net); + + strscpy(p->driver, "g_ether", sizeof(p->driver)); + strscpy(p->version, UETH__VERSION, sizeof(p->version)); + if (dev->gadget) { + strscpy(p->fw_version, dev->gadget->name, sizeof(p->fw_version)); + strscpy(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 int +rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags) +{ + struct usb_gadget *g = dev->gadget; + 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; + + if (!out) + { + spin_unlock_irqrestore(&dev->lock, flags); + 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; + + if (g->quirk_ep_out_aligned_size) { + 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); + spin_unlock_irqrestore(&dev->lock, flags); + + skb = __netdev_alloc_skb(dev->net, 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. + */ + if (likely(!dev->no_skb_reserve)) + 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 (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 > GETHER_MAX_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) +{ + unsigned i; + struct usb_request *req; + + if (!n) + return -ENOMEM; + + /* queue/recycle up to N requests */ + i = n; + list_for_each_entry(req, list, list) { + if (i-- == 0) + goto extra; + } + while (i--) { + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (!req) + return list_empty(list) ? -ENOMEM : 0; + list_add(&req->list, list); + } + return 0; + +extra: + /* free extras */ + for (;;) { + struct list_head *next; + + next = req->list.next; + list_del(&req->list); + 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); + if (status < 0) + goto fail; + status = prealloc(&dev->rx_reqs, link->out_ep, n); + if (status < 0) + 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 = list_first_entry(&dev->rx_reqs, 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 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 */ + dev_kfree_skb_any(skb); + break; + case 0: + dev->net->stats.tx_bytes += skb->len; + dev_consume_skb_any(skb); + } + dev->net->stats.tx_packets++; + + spin_lock(&dev->req_lock); + list_add(&req->list, &dev->tx_reqs); + spin_unlock(&dev->req_lock); + + atomic_dec(&dev->tx_qlen); + if (netif_carrier_ok(dev->net)) + netif_wake_queue(dev->net); +} + +static inline int is_promisc(u16 cdc_filter) +{ + return cdc_filter & USB_CDC_PACKET_TYPE_PROMISCUOUS; +} + +static int ether_wakeup_host(struct gether *port) +{ + int ret; + struct usb_function *func = &port->func; + struct usb_gadget *gadget = func->config->cdev->gadget; + + if (func->func_suspended) + ret = usb_func_wakeup(func); + else + ret = usb_gadget_wakeup(gadget); + + return ret; +} + +static netdev_tx_t eth_start_xmit(struct sk_buff *skb, + struct net_device *net) +{ + struct eth_dev *dev = netdev_priv(net); + int length = 0; + 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; + } + + if (dev->port_usb && dev->port_usb->is_suspend) { + DBG(dev, "Port suspended. Triggering wakeup\n"); + netif_stop_queue(net); + spin_unlock_irqrestore(&dev->lock, flags); + ether_wakeup_host(dev->port_usb); + return NETDEV_TX_BUSY; + } + + spin_unlock_irqrestore(&dev->lock, flags); + + if (!in) { + if (skb) + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + /* apply outgoing CDC or RNDIS filters */ + if (skb && !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 = list_first_entry(&dev->tx_reqs, 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) { + /* Multi frame CDC protocols may store the frame for + * later which is not a dropped frame. + */ + if (dev->port_usb && + dev->port_usb->supports_multi_frame) + goto multiframe; + goto drop; + } + } + + length = 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 && + 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; + + retval = usb_ep_queue(in, req, GFP_ATOMIC); + switch (retval) { + default: + DBG(dev, "tx queue err %d\n", retval); + break; + case 0: + netif_trans_update(net); + atomic_inc(&dev->tx_qlen); + } + + if (retval) { + dev_kfree_skb_any(skb); +drop: + dev->net->stats.tx_dropped++; +multiframe: + 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; +} + +/*-------------------------------------------------------------------------*/ + +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); +} + +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; +} + +/*-------------------------------------------------------------------------*/ + +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 int get_ether_addr_str(u8 dev_addr[ETH_ALEN], char *str, int len) +{ + if (len < 18) + return -EINVAL; + + snprintf(str, len, "%pM", dev_addr); + return 18; +} + +static const struct net_device_ops eth_netdev_ops = { + .ndo_open = eth_open, + .ndo_stop = eth_stop, + .ndo_start_xmit = eth_start_xmit, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static const 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 an eth_dev pointer on success, or an ERR_PTR on failure. + */ +struct eth_dev *gether_setup_name(struct usb_gadget *g, + const char *dev_addr, const char *host_addr, + u8 ethaddr[ETH_ALEN], unsigned qmult, const char *netname) +{ + struct eth_dev *dev; + struct net_device *net; + int status; + u8 addr[ETH_ALEN]; + + 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; + dev->qmult = qmult; + snprintf(net->name, sizeof(net->name), "%s%%d", netname); + + if (get_ether_addr(dev_addr, addr)) { + net->addr_assign_type = NET_ADDR_RANDOM; + dev_warn(&g->dev, + "using random %s ethernet address\n", "self"); + } else { + net->addr_assign_type = NET_ADDR_SET; + } + eth_hw_addr_set(net, addr); + 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; + + net->ethtool_ops = &ops; + + /* MTU range: 14 - 15412 */ + net->min_mtu = ETH_HLEN; + net->max_mtu = GETHER_MAX_MTU_SIZE; + + 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; +} +EXPORT_SYMBOL_GPL(gether_setup_name); + +struct net_device *gether_setup_name_default(const char *netname) +{ + struct net_device *net; + struct eth_dev *dev; + + 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; + dev->qmult = QMULT_DEFAULT; + snprintf(net->name, sizeof(net->name), "%s%%d", netname); + + eth_random_addr(dev->dev_mac); + + /* by default we always have a random MAC address */ + net->addr_assign_type = NET_ADDR_RANDOM; + + eth_random_addr(dev->host_mac); + + net->netdev_ops = ð_netdev_ops; + + net->ethtool_ops = &ops; + SET_NETDEV_DEVTYPE(net, &gadget_type); + + /* MTU range: 14 - 15412 */ + net->min_mtu = ETH_HLEN; + net->max_mtu = GETHER_MAX_MTU_SIZE; + + return net; +} +EXPORT_SYMBOL_GPL(gether_setup_name_default); + +int gether_register_netdev(struct net_device *net) +{ + struct eth_dev *dev; + struct usb_gadget *g; + int status; + + if (!net->dev.parent) + return -EINVAL; + dev = netdev_priv(net); + g = dev->gadget; + + eth_hw_addr_set(net, dev->dev_mac); + + status = register_netdev(net); + if (status < 0) { + dev_dbg(&g->dev, "register_netdev failed, %d\n", status); + return status; + } else { + INFO(dev, "HOST MAC %pM\n", dev->host_mac); + INFO(dev, "MAC %pM\n", dev->dev_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 status; +} +EXPORT_SYMBOL_GPL(gether_register_netdev); + +void gether_set_gadget(struct net_device *net, struct usb_gadget *g) +{ + struct eth_dev *dev; + + dev = netdev_priv(net); + dev->gadget = g; + SET_NETDEV_DEV(net, &g->dev); +} +EXPORT_SYMBOL_GPL(gether_set_gadget); + +int gether_attach_gadget(struct net_device *net, struct usb_gadget *g) +{ + int ret; + + ret = device_move(&net->dev, &g->dev, DPM_ORDER_DEV_AFTER_PARENT); + if (ret) + return ret; + + gether_set_gadget(net, g); + return 0; +} +EXPORT_SYMBOL_GPL(gether_attach_gadget); + +void gether_detach_gadget(struct net_device *net) +{ + struct eth_dev *dev = netdev_priv(net); + + device_move(&net->dev, NULL, DPM_ORDER_NONE); + dev->gadget = NULL; +} +EXPORT_SYMBOL_GPL(gether_detach_gadget); + +int gether_set_dev_addr(struct net_device *net, const char *dev_addr) +{ + struct eth_dev *dev; + u8 new_addr[ETH_ALEN]; + + dev = netdev_priv(net); + if (get_ether_addr(dev_addr, new_addr)) + return -EINVAL; + memcpy(dev->dev_mac, new_addr, ETH_ALEN); + net->addr_assign_type = NET_ADDR_SET; + return 0; +} +EXPORT_SYMBOL_GPL(gether_set_dev_addr); + +int gether_get_dev_addr(struct net_device *net, char *dev_addr, int len) +{ + struct eth_dev *dev; + int ret; + + dev = netdev_priv(net); + ret = get_ether_addr_str(dev->dev_mac, dev_addr, len); + if (ret + 1 < len) { + dev_addr[ret++] = '\n'; + dev_addr[ret] = '\0'; + } + + return ret; +} +EXPORT_SYMBOL_GPL(gether_get_dev_addr); + +int gether_set_host_addr(struct net_device *net, const char *host_addr) +{ + struct eth_dev *dev; + u8 new_addr[ETH_ALEN]; + + dev = netdev_priv(net); + if (get_ether_addr(host_addr, new_addr)) + return -EINVAL; + memcpy(dev->host_mac, new_addr, ETH_ALEN); + return 0; +} +EXPORT_SYMBOL_GPL(gether_set_host_addr); + +int gether_get_host_addr(struct net_device *net, char *host_addr, int len) +{ + struct eth_dev *dev; + int ret; + + dev = netdev_priv(net); + ret = get_ether_addr_str(dev->host_mac, host_addr, len); + if (ret + 1 < len) { + host_addr[ret++] = '\n'; + host_addr[ret] = '\0'; + } + + return ret; +} +EXPORT_SYMBOL_GPL(gether_get_host_addr); + +int gether_get_host_addr_cdc(struct net_device *net, char *host_addr, int len) +{ + struct eth_dev *dev; + + if (len < 13) + return -EINVAL; + + dev = netdev_priv(net); + snprintf(host_addr, len, "%pm", dev->host_mac); + + string_upper(host_addr, host_addr); + + return strlen(host_addr); +} +EXPORT_SYMBOL_GPL(gether_get_host_addr_cdc); + +void gether_get_host_addr_u8(struct net_device *net, u8 host_mac[ETH_ALEN]) +{ + struct eth_dev *dev; + + dev = netdev_priv(net); + memcpy(host_mac, dev->host_mac, ETH_ALEN); +} +EXPORT_SYMBOL_GPL(gether_get_host_addr_u8); + +void gether_set_qmult(struct net_device *net, unsigned qmult) +{ + struct eth_dev *dev; + + dev = netdev_priv(net); + dev->qmult = qmult; +} +EXPORT_SYMBOL_GPL(gether_set_qmult); + +unsigned gether_get_qmult(struct net_device *net) +{ + struct eth_dev *dev; + + dev = netdev_priv(net); + return dev->qmult; +} +EXPORT_SYMBOL_GPL(gether_get_qmult); + +int gether_get_ifname(struct net_device *net, char *name, int len) +{ + struct eth_dev *dev = netdev_priv(net); + int ret; + + rtnl_lock(); + ret = scnprintf(name, len, "%s\n", + dev->ifname_set ? net->name : netdev_name(net)); + rtnl_unlock(); + return ret; +} +EXPORT_SYMBOL_GPL(gether_get_ifname); + +int gether_set_ifname(struct net_device *net, const char *name, int len) +{ + struct eth_dev *dev = netdev_priv(net); + char tmp[IFNAMSIZ]; + const char *p; + + if (name[len - 1] == '\n') + len--; + + if (len >= sizeof(tmp)) + return -E2BIG; + + strscpy(tmp, name, len + 1); + if (!dev_valid_name(tmp)) + return -EINVAL; + + /* Require exactly one %d, so binding will not fail with EEXIST. */ + p = strchr(name, '%'); + if (!p || p[1] != 'd' || strchr(p + 2, '%')) + return -EINVAL; + + strscpy(net->name, tmp); + dev->ifname_set = true; + + return 0; +} +EXPORT_SYMBOL_GPL(gether_set_ifname); + +void gether_suspend(struct gether *link) +{ + struct eth_dev *dev = link->ioport; + unsigned long flags; + + if (!dev) + return; + + if (atomic_read(&dev->tx_qlen)) { + /* + * There is a transfer in progress. So we trigger a remote + * wakeup to inform the host. + */ + if (!ether_wakeup_host(dev->port_usb)) + return; + } + spin_lock_irqsave(&dev->lock, flags); + link->is_suspend = true; + spin_unlock_irqrestore(&dev->lock, flags); +} +EXPORT_SYMBOL_GPL(gether_suspend); + +void gether_resume(struct gether *link) +{ + struct eth_dev *dev = link->ioport; + unsigned long flags; + + if (!dev) + return; + + if (netif_queue_stopped(dev->net)) + netif_start_queue(dev->net); + + spin_lock_irqsave(&dev->lock, flags); + link->is_suspend = false; + spin_unlock_irqrestore(&dev->lock, flags); +} +EXPORT_SYMBOL_GPL(gether_resume); + +/* + * 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); +} +EXPORT_SYMBOL_GPL(gether_cleanup); + +/** + * 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, + dev->qmult)); + + if (result == 0) { + dev->zlp = link->is_zlp_ok; + dev->no_skb_reserve = gadget_avoids_skb_reserve(dev->gadget); + DBG(dev, "qlen %d\n", qlen(dev->gadget, dev->qmult)); + + 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; +} +EXPORT_SYMBOL_GPL(gether_connect); + +/** + * 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__); + + spin_lock(&dev->lock); + dev->port_usb = NULL; + link->is_suspend = false; + spin_unlock(&dev->lock); + + 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 = list_first_entry(&dev->tx_reqs, struct usb_request, list); + list_del(&req->list); + + spin_unlock(&dev->req_lock); + usb_ep_free_request(link->in_ep, req); + spin_lock(&dev->req_lock); + } + spin_unlock(&dev->req_lock); + link->in_ep->desc = NULL; + + usb_ep_disable(link->out_ep); + spin_lock(&dev->req_lock); + while (!list_empty(&dev->rx_reqs)) { + req = list_first_entry(&dev->rx_reqs, 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->desc = NULL; + + /* finish forgetting about this USB link episode */ + dev->header_len = 0; + dev->unwrap = NULL; + dev->wrap = NULL; +} +EXPORT_SYMBOL_GPL(gether_disconnect); + +MODULE_DESCRIPTION("Ethernet-over-USB link layer utilities for Gadget stack"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/function/u_ether.h b/drivers/usb/gadget/function/u_ether.h new file mode 100644 index 000000000..c85a1cf3c --- /dev/null +++ b/drivers/usb/gadget/function/u_ether.h @@ -0,0 +1,321 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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 + */ + +#ifndef __U_ETHER_H +#define __U_ETHER_H + +#include +#include +#include +#include +#include + +#define QMULT_DEFAULT 5 + +/* + * dev_addr: initial value + * changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx" + * host_addr: this address is invisible to ifconfig + */ +#define USB_ETHERNET_MODULE_PARAMETERS() \ + static unsigned qmult = QMULT_DEFAULT; \ + module_param(qmult, uint, S_IRUGO|S_IWUSR); \ + MODULE_PARM_DESC(qmult, "queue length multiplier at high/super speed");\ + \ + static char *dev_addr; \ + module_param(dev_addr, charp, S_IRUGO); \ + MODULE_PARM_DESC(dev_addr, "Device Ethernet Address"); \ + \ + static char *host_addr; \ + module_param(host_addr, charp, S_IRUGO); \ + MODULE_PARM_DESC(host_addr, "Host Ethernet Address") + +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 supports_multi_frame; + 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 *); + bool is_suspend; +}; + +#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, + const char *dev_addr, const char *host_addr, + u8 ethaddr[ETH_ALEN], unsigned qmult, 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 a eth_dev pointer on success, or an ERR_PTR on failure + */ +static inline struct eth_dev *gether_setup(struct usb_gadget *g, + const char *dev_addr, const char *host_addr, + u8 ethaddr[ETH_ALEN], unsigned qmult) +{ + return gether_setup_name(g, dev_addr, host_addr, ethaddr, qmult, "usb"); +} + +/* + * variant of gether_setup_default that allows customizing + * network device name + */ +struct net_device *gether_setup_name_default(const char *netname); + +/* + * gether_register_netdev - register the net device + * @net: net device to register + * + * Registers the net device associated with this ethernet-over-usb link + * + */ +int gether_register_netdev(struct net_device *net); + +/* gether_setup_default - initialize one ethernet-over-usb link + * 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 to random values. + * + * Returns negative errno, or zero on success + */ +static inline struct net_device *gether_setup_default(void) +{ + return gether_setup_name_default("usb"); +} + +/** + * gether_set_gadget - initialize one ethernet-over-usb link with a gadget + * @net: device representing this link + * @g: the gadget to initialize with + * + * This associates one ethernet-over-usb link with a gadget. + */ +void gether_set_gadget(struct net_device *net, struct usb_gadget *g); + +/** + * gether_attach_gadget - Reparent net_device to the gadget device. + * @net: The network device to reparent. + * @g: The target USB gadget device to parent to. + * + * This function moves the network device to be a child of the USB gadget + * device in the device hierarchy. This is typically done when the function + * is bound to a configuration. + * + * Returns 0 on success, or a negative error code on failure. + */ +int gether_attach_gadget(struct net_device *net, struct usb_gadget *g); + +/** + * gether_detach_gadget - Detach net_device from its gadget parent. + * @net: The network device to detach. + * + * This function moves the network device to be a child of the virtual + * devices parent, effectively detaching it from the USB gadget device + * hierarchy. This is typically done when the function is unbound + * from a configuration but the instance is not yet freed. + */ +void gether_detach_gadget(struct net_device *net); + +DEFINE_FREE(detach_gadget, struct net_device *, if (_T) gether_detach_gadget(_T)) + +/** + * gether_set_dev_addr - initialize an ethernet-over-usb link with eth address + * @net: device representing this link + * @dev_addr: eth address of this device + * + * This sets the device-side Ethernet address of this ethernet-over-usb link + * if dev_addr is correct. + * Returns negative errno if the new address is incorrect. + */ +int gether_set_dev_addr(struct net_device *net, const char *dev_addr); + +/** + * gether_get_dev_addr - get an ethernet-over-usb link eth address + * @net: device representing this link + * @dev_addr: place to store device's eth address + * @len: length of the @dev_addr buffer + * + * This gets the device-side Ethernet address of this ethernet-over-usb link. + * Returns zero on success, else negative errno. + */ +int gether_get_dev_addr(struct net_device *net, char *dev_addr, int len); + +/** + * gether_set_host_addr - initialize an ethernet-over-usb link with host address + * @net: device representing this link + * @host_addr: eth address of the host + * + * This sets the host-side Ethernet address of this ethernet-over-usb link + * if host_addr is correct. + * Returns negative errno if the new address is incorrect. + */ +int gether_set_host_addr(struct net_device *net, const char *host_addr); + +/** + * gether_get_host_addr - get an ethernet-over-usb link host address + * @net: device representing this link + * @host_addr: place to store eth address of the host + * @len: length of the @host_addr buffer + * + * This gets the host-side Ethernet address of this ethernet-over-usb link. + * Returns zero on success, else negative errno. + */ +int gether_get_host_addr(struct net_device *net, char *host_addr, int len); + +/** + * gether_get_host_addr_cdc - get an ethernet-over-usb link host address + * @net: device representing this link + * @host_addr: place to store eth address of the host + * @len: length of the @host_addr buffer + * + * This gets the CDC formatted host-side Ethernet address of this + * ethernet-over-usb link. + * Returns zero on success, else negative errno. + */ +int gether_get_host_addr_cdc(struct net_device *net, char *host_addr, int len); + +/** + * gether_get_host_addr_u8 - get an ethernet-over-usb link host address + * @net: device representing this link + * @host_mac: place to store the eth address of the host + * + * This gets the binary formatted host-side Ethernet address of this + * ethernet-over-usb link. + */ +void gether_get_host_addr_u8(struct net_device *net, u8 host_mac[ETH_ALEN]); + +/** + * gether_set_qmult - initialize an ethernet-over-usb link with a multiplier + * @net: device representing this link + * @qmult: queue multiplier + * + * This sets the queue length multiplier of this ethernet-over-usb link. + * For higher speeds use longer queues. + */ +void gether_set_qmult(struct net_device *net, unsigned qmult); + +/** + * gether_get_qmult - get an ethernet-over-usb link multiplier + * @net: device representing this link + * + * This gets the queue length multiplier of this ethernet-over-usb link. + */ +unsigned gether_get_qmult(struct net_device *net); + +/** + * gether_get_ifname - get an ethernet-over-usb link interface name + * @net: device representing this link + * @name: place to store the interface name + * @len: length of the @name buffer + * + * This gets the interface name of this ethernet-over-usb link. + * Returns zero on success, else negative errno. + */ +int gether_get_ifname(struct net_device *net, char *name, int len); + +/** + * gether_set_ifname - set an ethernet-over-usb link interface name + * @net: device representing this link + * @name: new interface name + * @len: length of @name + * + * This sets the interface name of this ethernet-over-usb link. + * A single terminating newline, if any, is ignored. + * Returns zero on success, else negative errno. + */ +int gether_set_ifname(struct net_device *net, const char *name, int len); + +void gether_cleanup(struct eth_dev *dev); + +void gether_suspend(struct gether *link); +void gether_resume(struct gether *link); + +/* 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_is_altset_supported(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; +} + +/* peak (theoretical) bulk transfer rate in bits-per-second */ +static inline unsigned int gether_bitrate(struct usb_gadget *g) +{ + if (g->speed >= USB_SPEED_SUPER_PLUS) + return 4250000000U; + if (g->speed == USB_SPEED_SUPER) + return 3750000000U; + else if (g->speed == USB_SPEED_HIGH) + return 13 * 512 * 8 * 1000 * 8; + else + return 19 * 64 * 1 * 1000 * 8; +} + +#endif /* __U_ETHER_H */ diff --git a/drivers/usb/gadget/function/u_ether_configfs.h b/drivers/usb/gadget/function/u_ether_configfs.h new file mode 100644 index 000000000..51f0d79e5 --- /dev/null +++ b/drivers/usb/gadget/function/u_ether_configfs.h @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * u_ether_configfs.h + * + * Utility definitions for configfs support in USB Ethernet functions + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef __U_ETHER_CONFIGFS_H +#define __U_ETHER_CONFIGFS_H + +#define USB_ETHERNET_CONFIGFS_ITEM(_f_) \ + static void _f_##_attr_release(struct config_item *item) \ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + \ + usb_put_function_instance(&opts->func_inst); \ + } \ + \ + static const struct configfs_item_operations _f_##_item_ops = { \ + .release = _f_##_attr_release, \ + } + +#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(_f_) \ + static ssize_t _f_##_opts_dev_addr_show(struct config_item *item, \ + char *page) \ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = gether_get_dev_addr(opts->net, page, PAGE_SIZE); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ + } \ + \ + static ssize_t _f_##_opts_dev_addr_store(struct config_item *item, \ + const char *page, size_t len)\ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + int ret; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + mutex_unlock(&opts->lock); \ + return -EBUSY; \ + } \ + \ + ret = gether_set_dev_addr(opts->net, page); \ + mutex_unlock(&opts->lock); \ + if (!ret) \ + ret = len; \ + return ret; \ + } \ + \ + CONFIGFS_ATTR(_f_##_opts_, dev_addr) + +#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(_f_) \ + static ssize_t _f_##_opts_host_addr_show(struct config_item *item, \ + char *page) \ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = gether_get_host_addr(opts->net, page, PAGE_SIZE); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ + } \ + \ + static ssize_t _f_##_opts_host_addr_store(struct config_item *item, \ + const char *page, size_t len)\ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + int ret; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + mutex_unlock(&opts->lock); \ + return -EBUSY; \ + } \ + \ + ret = gether_set_host_addr(opts->net, page); \ + mutex_unlock(&opts->lock); \ + if (!ret) \ + ret = len; \ + return ret; \ + } \ + \ + CONFIGFS_ATTR(_f_##_opts_, host_addr) + +#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(_f_) \ + static ssize_t _f_##_opts_qmult_show(struct config_item *item, \ + char *page) \ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + unsigned qmult; \ + \ + mutex_lock(&opts->lock); \ + qmult = gether_get_qmult(opts->net); \ + mutex_unlock(&opts->lock); \ + return sprintf(page, "%d\n", qmult); \ + } \ + \ + static ssize_t _f_##_opts_qmult_store(struct config_item *item, \ + const char *page, size_t len)\ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + u8 val; \ + int ret; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto out; \ + } \ + \ + ret = kstrtou8(page, 0, &val); \ + if (ret) \ + goto out; \ + \ + gether_set_qmult(opts->net, val); \ + ret = len; \ +out: \ + mutex_unlock(&opts->lock); \ + return ret; \ + } \ + \ + CONFIGFS_ATTR(_f_##_opts_, qmult) + +#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(_f_) \ + static ssize_t _f_##_opts_ifname_show(struct config_item *item, \ + char *page) \ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + int ret; \ + \ + mutex_lock(&opts->lock); \ + ret = gether_get_ifname(opts->net, page, PAGE_SIZE); \ + mutex_unlock(&opts->lock); \ + \ + return ret; \ + } \ + \ + static ssize_t _f_##_opts_ifname_store(struct config_item *item, \ + const char *page, size_t len)\ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + int ret = -EBUSY; \ + \ + mutex_lock(&opts->lock); \ + if (!opts->refcnt) \ + ret = gether_set_ifname(opts->net, page, len); \ + mutex_unlock(&opts->lock); \ + return ret ?: len; \ + } \ + \ + CONFIGFS_ATTR(_f_##_opts_, ifname) + +#define USB_ETHER_CONFIGFS_ITEM_ATTR_U8_RW(_f_, _n_) \ + static ssize_t _f_##_opts_##_n_##_show(struct config_item *item,\ + char *page) \ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + int ret; \ + \ + mutex_lock(&opts->lock); \ + ret = sprintf(page, "%02x\n", opts->_n_); \ + mutex_unlock(&opts->lock); \ + \ + return ret; \ + } \ + \ + static ssize_t _f_##_opts_##_n_##_store(struct config_item *item,\ + const char *page, \ + size_t len) \ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + int ret = -EINVAL; \ + u8 val; \ + \ + mutex_lock(&opts->lock); \ + if (sscanf(page, "%02hhx", &val) > 0) { \ + opts->_n_ = val; \ + ret = len; \ + } \ + mutex_unlock(&opts->lock); \ + \ + return ret; \ + } \ + \ + CONFIGFS_ATTR(_f_##_opts_, _n_) + +#endif /* __U_ETHER_CONFIGFS_H */ diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h new file mode 100644 index 000000000..c280c495f --- /dev/null +++ b/drivers/usb/gadget/function/u_fs.h @@ -0,0 +1,307 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * u_fs.h + * + * Utility definitions for the FunctionFS + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef U_FFS_H +#define U_FFS_H + +#include +#include +#include +#include +#include + +#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 */ + +struct f_fs_opts; + +struct ffs_dev { + struct ffs_data *ffs_data; + struct f_fs_opts *opts; + struct list_head entry; + + char name[41]; + + bool mounted; + bool desc_ready; + bool single; + + int (*ffs_ready_callback)(struct ffs_data *ffs); + void (*ffs_closed_callback)(struct ffs_data *ffs); + void *(*ffs_acquire_dev_callback)(struct ffs_dev *dev); + void (*ffs_release_dev_callback)(struct ffs_dev *dev); +}; + +extern struct mutex ffs_lock; + +static inline void ffs_dev_lock(void) +{ + mutex_lock(&ffs_lock); +} + +static inline void ffs_dev_unlock(void) +{ + mutex_unlock(&ffs_lock); +} + +int ffs_name_dev(struct ffs_dev *dev, const char *name); +int ffs_single_dev(struct ffs_dev *dev); + +struct ffs_epfile; +struct ffs_function; + +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, + + /* + * Function is visible to host, but it's not functional. All + * setup requests are stalled and transfers on another endpoints + * are refused. All epfiles, except ep0, are deleted so there + * is no way to perform any operations on them. + * + * This state is set after closing all functionfs files, when + * mount parameter "no_disconnect=1" has been set. Function will + * remain in deactivated state until filesystem is umounted or + * ep0 is opened again. In the second case functionfs state will + * be reset, and it will be ready for descriptors and strings + * writing. + * + * This is useful only when functionfs is composed to gadget + * with another function which can perform some critical + * operations, and it's strongly desired to have this operations + * completed, even after functionfs files closure. + */ + FFS_DEACTIVATED, + + /* + * 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). + */ + 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_CANCELLED +}; + +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 */ + + /* reference counter */ + refcount_t ref; + /* how many files are opened (EP0 and others) */ + int 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_CANCELLED -- P: ev.waitq.lock + * + FFS_SETUP_CANCELLED -> FFS_NO_SETUP -- cmpxchg + * + * This field should never be accessed directly and instead + * ffs_setup_state_clear_cancelled function should be used. + */ + enum ffs_setup_state setup_state; + + /* 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 + + /* For waking up blocked threads when function is enabled. */ + wait_queue_head_t wait; + + /* 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() */ + /* + * raw_descs is what you kfree, real_descs points inside of raw_descs, + * where full speed, high speed and super speed descriptors start. + * real_descs_length is the length of all those descriptors. + */ + const void *raw_descs_data; + const void *raw_descs; + unsigned raw_descs_length; + unsigned fs_descs_count; + unsigned hs_descs_count; + unsigned ss_descs_count; + unsigned ms_os_descs_count; + unsigned ms_os_descs_ext_prop_count; + unsigned ms_os_descs_ext_prop_name_len; + unsigned ms_os_descs_ext_prop_data_len; + void *ms_os_descs_ext_prop_avail; + void *ms_os_descs_ext_prop_name_avail; + void *ms_os_descs_ext_prop_data_avail; + + unsigned user_flags; + +#define FFS_MAX_EPS_COUNT 31 + u8 eps_addrmap[FFS_MAX_EPS_COUNT]; + + unsigned short strings_count; + unsigned short interfaces_count; + + /* + * eps_count tracks the number of underlying hardware endpoints. + * epfiles_count tracks the total number of VFS endpoint files. + * When companion endpoints are active, epfiles_count > eps_count. + */ + unsigned short eps_count; + unsigned short epfiles_count; + + /* 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; + + struct eventfd_ctx *ffs_eventfd; + struct workqueue_struct *io_completion_wq; + bool no_disconnect; + struct work_struct reset_work; + + /* + * The endpoint files, filled by ffs_epfiles_create(), + * destroyed by ffs_epfiles_destroy(). + */ + struct ffs_epfile *epfiles; +}; + + +struct f_fs_opts { + struct usb_function_instance func_inst; + struct ffs_dev *dev; + unsigned refcnt; + bool no_configfs; +}; + +static inline struct f_fs_opts *to_f_fs_opts(struct usb_function_instance *fi) +{ + return container_of(fi, struct f_fs_opts, func_inst); +} + +#endif /* U_FFS_H */ diff --git a/drivers/usb/gadget/function/u_gether.h b/drivers/usb/gadget/function/u_gether.h new file mode 100644 index 000000000..e7b6b51f6 --- /dev/null +++ b/drivers/usb/gadget/function/u_gether.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * u_gether.h + * + * Utility definitions for the subset function + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef U_GETHER_H +#define U_GETHER_H + +#include + +/** + * struct f_gether_opts - subset function options + * @func_inst: USB function instance. + * @net: The net_device associated with the subset function. + * @bound: True if the net_device is shared and pre-registered during the + * legacy composite driver's bind phase (e.g., multi.c). If false, + * the subset function will register the net_device during its own + * bind phase. + * @bind_count: Tracks the number of configurations the subset function is + * bound to, preventing double-registration of the @net device. + * @lock: Protects the data from concurrent access by configfs read/write + * and create symlink/remove symlink operations. + * @refcnt: Reference counter for the function instance. + */ +struct f_gether_opts { + struct usb_function_instance func_inst; + struct net_device *net; + bool bound; + int bind_count; + struct mutex lock; + int refcnt; +}; + +#endif /* U_GETHER_H */ diff --git a/drivers/usb/gadget/function/u_hid.h b/drivers/usb/gadget/function/u_hid.h new file mode 100644 index 000000000..a9ed9720c --- /dev/null +++ b/drivers/usb/gadget/function/u_hid.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * u_hid.h + * + * Utility definitions for the hid function + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef U_HID_H +#define U_HID_H + +#include + +struct f_hid_opts { + struct usb_function_instance func_inst; + int minor; + unsigned char subclass; + unsigned char protocol; + unsigned char no_out_endpoint; + unsigned short report_length; + unsigned short report_desc_length; + unsigned char *report_desc; + bool report_desc_alloc; + unsigned char interval; + bool interval_user_set; + + /* + * Protect the data form concurrent access by read/write + * and create symlink/remove symlink. + */ + struct mutex lock; + int refcnt; +}; + +int ghid_setup(struct usb_gadget *g, int count); +void ghid_cleanup(void); + +#endif /* U_HID_H */ diff --git a/drivers/usb/gadget/function/u_midi.h b/drivers/usb/gadget/function/u_midi.h new file mode 100644 index 000000000..41cb8aa73 --- /dev/null +++ b/drivers/usb/gadget/function/u_midi.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * u_midi.h + * + * Utility definitions for the midi function + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef U_MIDI_H +#define U_MIDI_H + +#include + +struct f_midi_opts { + struct usb_function_instance func_inst; + int index; + char *id; + char *interface_string; + unsigned int in_ports; + unsigned int out_ports; + unsigned int buflen; + unsigned int qlen; + + /* + * Protect the data form concurrent access by read/write + * and create symlink/remove symlink. + */ + struct mutex lock; + int refcnt; +}; + +#endif /* U_MIDI_H */ + diff --git a/drivers/usb/gadget/function/u_midi2.h b/drivers/usb/gadget/function/u_midi2.h new file mode 100644 index 000000000..4e7adb41d --- /dev/null +++ b/drivers/usb/gadget/function/u_midi2.h @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Utility definitions for MIDI 2.0 function + */ + +#ifndef U_MIDI2_H +#define U_MIDI2_H + +#include +#include + +struct f_midi2_opts; +struct f_midi2_ep_opts; +struct f_midi2_block_opts; + +/* UMP Function Block info */ +struct f_midi2_block_info { + unsigned int direction; /* FB direction: 1-3 */ + unsigned int first_group; /* first UMP group: 0-15 */ + unsigned int num_groups; /* number of UMP groups: 1-16 */ + unsigned int midi1_first_group; /* first UMP group for MIDI 1.0 */ + unsigned int midi1_num_groups; /* number of UMP groups for MIDI 1.0 */ + unsigned int ui_hint; /* UI-hint: 0-3 */ + unsigned int midi_ci_version; /* MIDI-CI version: 0-255 */ + unsigned int sysex8_streams; /* number of sysex8 streams: 0-255 */ + unsigned int is_midi1; /* MIDI 1.0 port: 0-2 */ + bool active; /* FB active flag: bool */ + const char *name; /* FB name */ +}; + +/* UMP Endpoint info */ +struct f_midi2_ep_info { + unsigned int protocol_caps; /* protocol capabilities: 1-3 */ + unsigned int protocol; /* default protocol: 1-2 */ + unsigned int manufacturer; /* manufacturer id: 0-0xffffff */ + unsigned int family; /* device family id: 0-0xffff */ + unsigned int model; /* device model id: 0x-0xffff */ + unsigned int sw_revision; /* software revision: 32bit */ + + const char *ep_name; /* Endpoint name */ + const char *product_id; /* Product ID */ +}; + +struct f_midi2_card_info { + bool process_ump; /* process UMP stream: bool */ + bool static_block; /* static FBs: bool */ + unsigned int req_buf_size; /* request buffer size */ + unsigned int num_reqs; /* number of requests */ + const char *iface_name; /* interface name */ +}; + +struct f_midi2_block_opts { + struct config_group group; + unsigned int id; + struct f_midi2_block_info info; + struct f_midi2_ep_opts *ep; +}; + +struct f_midi2_ep_opts { + struct config_group group; + unsigned int index; + struct f_midi2_ep_info info; + struct f_midi2_block_opts *blks[SNDRV_UMP_MAX_BLOCKS]; + struct f_midi2_opts *opts; +}; + +#define MAX_UMP_EPS 4 +#define MAX_CABLES 16 + +struct f_midi2_opts { + struct usb_function_instance func_inst; + struct mutex lock; + int refcnt; + + struct f_midi2_card_info info; + + unsigned int num_eps; + struct f_midi2_ep_opts *eps[MAX_UMP_EPS]; +}; + +#endif /* U_MIDI2_H */ diff --git a/drivers/usb/gadget/function/u_ncm.h b/drivers/usb/gadget/function/u_ncm.h new file mode 100644 index 000000000..ce2f63586 --- /dev/null +++ b/drivers/usb/gadget/function/u_ncm.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * u_ncm.h + * + * Utility definitions for the ncm function + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef U_NCM_H +#define U_NCM_H + +#include + +/** + * struct f_ncm_opts - NCM function options + * @func_inst: USB function instance. + * @net: The net_device associated with the NCM function. + * @bind_count: Tracks the number of configurations the NCM function is + * bound to, preventing double-registration of the @net device. + * @ncm_interf_group: ConfigFS group for NCM interface. + * @ncm_os_desc: USB OS descriptor for NCM. + * @ncm_ext_compat_id: Extended compatibility ID. + * @lock: Protects the data from concurrent access by configfs read/write + * and create symlink/remove symlink operations. + * @refcnt: Reference counter for the function instance. + * @max_segment_size: Maximum segment size. + */ +struct f_ncm_opts { + struct usb_function_instance func_inst; + struct net_device *net; + int bind_count; + + struct config_group *ncm_interf_group; + struct usb_os_desc ncm_os_desc; + char ncm_ext_compat_id[16]; + + struct mutex lock; + int refcnt; + + u16 max_segment_size; +}; + +#endif /* U_NCM_H */ diff --git a/drivers/usb/gadget/function/u_phonet.h b/drivers/usb/gadget/function/u_phonet.h new file mode 100644 index 000000000..ff62ca22c --- /dev/null +++ b/drivers/usb/gadget/function/u_phonet.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * u_phonet.h - interface to Phonet + * + * Copyright (C) 2007-2008 by Nokia Corporation + */ + +#ifndef __U_PHONET_H +#define __U_PHONET_H + +#include +#include + +struct f_phonet_opts { + struct usb_function_instance func_inst; + bool bound; + struct net_device *net; +}; + +struct net_device *gphonet_setup_default(void); +void gphonet_set_gadget(struct net_device *net, struct usb_gadget *g); +int gphonet_register_netdev(struct net_device *net); +void gphonet_cleanup(struct net_device *dev); + +#endif /* __U_PHONET_H */ diff --git a/drivers/usb/gadget/function/u_printer.h b/drivers/usb/gadget/function/u_printer.h new file mode 100644 index 000000000..318205fb7 --- /dev/null +++ b/drivers/usb/gadget/function/u_printer.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * u_printer.h + * + * Utility definitions for the printer function + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef U_PRINTER_H +#define U_PRINTER_H + +#include + +struct f_printer_opts { + struct usb_function_instance func_inst; + int minor; + char *pnp_string; + bool pnp_string_allocated; + unsigned q_len; + + /* + * Protect the data from concurrent access by read/write + * and create symlink/remove symlink + */ + struct mutex lock; + int refcnt; +}; + +#endif /* U_PRINTER_H */ diff --git a/drivers/usb/gadget/function/u_rndis.h b/drivers/usb/gadget/function/u_rndis.h new file mode 100644 index 000000000..4e6461971 --- /dev/null +++ b/drivers/usb/gadget/function/u_rndis.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * u_rndis.h + * + * Utility definitions for the subset function + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef U_RNDIS_H +#define U_RNDIS_H + +#include + +/** + * struct f_rndis_opts - RNDIS function options + * @func_inst: USB function instance. + * @vendor_id: Vendor ID. + * @manufacturer: Manufacturer string. + * @net: The net_device associated with the RNDIS function. + * @bind_count: Tracks the number of configurations the RNDIS function is + * bound to, preventing double-registration of the @net device. + * @borrowed_net: True if the net_device is shared and pre-registered during + * the legacy composite driver's bind phase (e.g., multi.c). + * If false, the RNDIS function will register the net_device + * during its own bind phase. + * @rndis_interf_group: ConfigFS group for RNDIS interface. + * @rndis_os_desc: USB OS descriptor for RNDIS. + * @rndis_ext_compat_id: Extended compatibility ID. + * @class: USB class. + * @subclass: USB subclass. + * @protocol: USB protocol. + * @lock: Protects the data from concurrent access by configfs read/write + * and create symlink/remove symlink operations. + * @refcnt: Reference counter for the function instance. + */ +struct f_rndis_opts { + struct usb_function_instance func_inst; + u32 vendor_id; + const char *manufacturer; + struct net_device *net; + int bind_count; + bool borrowed_net; + + struct config_group *rndis_interf_group; + struct usb_os_desc rndis_os_desc; + char rndis_ext_compat_id[16]; + + u8 class; + u8 subclass; + u8 protocol; + struct mutex lock; + int refcnt; +}; + +void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net); + +#endif /* U_RNDIS_H */ diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c new file mode 100644 index 000000000..cdd1dfc66 --- /dev/null +++ b/drivers/usb/gadget/function/u_serial.c @@ -0,0 +1,1607 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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) + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include +#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. + * + * + * 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 16 +#define WRITE_BUF_SIZE 8192 /* TX only */ +#define GS_CONSOLE_BUF_SIZE 8192 + +/* Prevents race conditions while accessing gser->ioport */ +static DEFINE_SPINLOCK(serial_port_lock); + +/* console info */ +struct gs_console { + struct console console; + struct work_struct work; + spinlock_t lock; + struct usb_request *req; + struct kfifo buf; + size_t missed; +}; + +/* + * 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; +#ifdef CONFIG_U_SERIAL_CONSOLE + struct gs_console *console; +#endif + + u8 port_num; + + struct list_head read_pool; + int read_started; + int read_allocated; + struct list_head read_queue; + unsigned n_read; + struct delayed_work push; + + struct list_head write_pool; + int write_started; + int write_allocated; + struct kfifo port_write_buf; + wait_queue_head_t drain_wait; /* wait while writes drain */ + bool write_busy; + wait_queue_head_t close_wait; + bool suspended; /* port suspended */ + bool start_delayed; /* delay start when suspended */ + struct async_icount icount; + + /* 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 + +/*-------------------------------------------------------------------------*/ + +/* 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 = kfifo_len(&port->port_write_buf); + if (len < size) + size = len; + if (size != 0) + size = kfifo_out(&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; + int status = 0; + bool do_tty_wake = false; + + if (!port->port_usb) + return status; + + in = port->port_usb->in; + + while (!port->write_busy && !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, in->maxpacket); + if (len == 0) { + wake_up_interruptible(&port->drain_wait); + break; + } + do_tty_wake = true; + port->icount.tx += len; + + req->length = len; + list_del(&req->list); + req->zero = kfifo_is_empty(&port->port_write_buf); + + pr_vdebug("ttyGS%d: tx len=%d, %3ph ...\n", port->port_num, len, req->buf); + + /* 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). + */ + port->write_busy = true; + spin_unlock(&port->port_lock); + status = usb_ep_queue(in, req, GFP_ATOMIC); + spin_lock(&port->port_lock); + port->write_busy = false; + + 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) + tty_port_tty_wakeup(&port->port); + 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 work 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(struct work_struct *work) +{ + struct delayed_work *w = to_delayed_work(work); + struct gs_port *port = container_of(w, struct gs_port, push); + 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 && tty_throttled(tty)) + break; + + switch (req->status) { + case -ESHUTDOWN: + disconnect = true; + pr_vdebug("ttyGS%d: shutdown\n", port->port_num); + break; + + default: + /* presumably a transient fault */ + pr_warn("ttyGS%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 && tty) { + 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; + } + + port->icount.rx += size; + 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("ttyGS%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; 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, RX may be starved, so wait until next jiffy. + * + * We may leave non-empty queue only when there is a tty, and + * either it is throttled or there is no more room in flip buffer. + */ + if (!list_empty(queue) && !tty_throttled(tty)) + schedule_delayed_work(&port->push, 1); + + /* 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); + schedule_delayed_work(&port->push, 0); + spin_unlock(&port->port_lock); +} + +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_warn("%s: unexpected %s status %d\n", + __func__, ep->name, req->status); + fallthrough; + 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, ep->maxpacket, 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 + * @port: port 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); + + if (started) { + gs_start_tx(port); + /* Unblock any pending writes into our circular buffer, in case + * we didn't in gs_start_tx() */ + tty_port_tty_wakeup(&port->port); + } else { + /* Free reqs only if we are still connected */ + if (port->port_usb) { + 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; +} + +static int gserial_wakeup_host(struct gserial *gser) +{ + struct usb_function *func = &gser->func; + struct usb_gadget *gadget = func->config->cdev->gadget; + + if (func->func_suspended) + return usb_func_wakeup(func); + else + return usb_gadget_wakeup(gadget); +} + +/*-------------------------------------------------------------------------*/ + +/* 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 = 0; + + mutex_lock(&ports[port_num].lock); + port = ports[port_num].port; + if (!port) { + status = -ENODEV; + goto out; + } + + spin_lock_irq(&port->port_lock); + + /* allocate circular buffer on first open */ + if (!kfifo_initialized(&port->port_write_buf)) { + + spin_unlock_irq(&port->port_lock); + + /* + * portmaster's mutex still protects from simultaneous open(), + * and close() can't happen, yet. + */ + + status = kfifo_alloc(&port->port_write_buf, + WRITE_BUF_SIZE, GFP_KERNEL); + if (status) { + pr_debug("gs_open: ttyGS%d (%p,%p) no buffer\n", + port_num, tty, file); + goto out; + } + + spin_lock_irq(&port->port_lock); + } + + /* already open? Great. */ + if (port->port.count++) + goto exit_unlock_port; + + tty->driver_data = port; + port->port.tty = tty; + + /* if connected, start the I/O stream */ + if (port->port_usb) { + /* if port is suspended, wait resume to start I/0 stream */ + if (!port->suspended) { + 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); + } else { + pr_debug("delay start of ttyGS%d\n", port->port_num); + port->start_delayed = true; + } + } + + pr_debug("gs_open: ttyGS%d (%p,%p)\n", port->port_num, tty, file); + +exit_unlock_port: + spin_unlock_irq(&port->port_lock); +out: + mutex_unlock(&ports[port_num].lock); + return status; +} + +static int gs_close_flush_done(struct gs_port *p) +{ + int cond; + + /* return true on disconnect or empty buffer or if raced with open() */ + spin_lock_irq(&p->port_lock); + cond = p->port_usb == NULL || !kfifo_len(&p->port_write_buf) || + p->port.count > 1; + 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) { +raced_with_open: + 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); + + gser = port->port_usb; + if (gser && !port->suspended && 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 (kfifo_len(&port->port_write_buf) > 0 && gser) { + spin_unlock_irq(&port->port_lock); + wait_event_interruptible_timeout(port->drain_wait, + gs_close_flush_done(port), + GS_CLOSE_TIMEOUT * HZ); + spin_lock_irq(&port->port_lock); + + if (port->port.count != 1) + goto raced_with_open; + + 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 async work fire again until we're re-opened. + */ + if (gser == NULL) + kfifo_free(&port->port_write_buf); + else + kfifo_reset(&port->port_write_buf); + + port->start_delayed = false; + port->port.count = 0; + port->port.tty = NULL; + + pr_debug("gs_close: ttyGS%d (%p,%p) done!\n", + port->port_num, tty, file); + + wake_up(&port->close_wait); +exit: + spin_unlock_irq(&port->port_lock); +} + +static ssize_t gs_write(struct tty_struct *tty, const u8 *buf, size_t count) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + int ret = 0; + struct gserial *gser = port->port_usb; + + pr_vdebug("gs_write: ttyGS%d (%p) writing %zu bytes\n", + port->port_num, tty, count); + + spin_lock_irqsave(&port->port_lock, flags); + if (count) + count = kfifo_in(&port->port_write_buf, buf, count); + + if (port->suspended) { + spin_unlock_irqrestore(&port->port_lock, flags); + ret = gserial_wakeup_host(gser); + if (ret) { + pr_debug("ttyGS%d: Remote wakeup failed:%d\n", port->port_num, ret); + return count; + } + spin_lock_irqsave(&port->port_lock, flags); + } + + /* treat count == 0 as flush_chars() */ + if (port->port_usb) + gs_start_tx(port); + spin_unlock_irqrestore(&port->port_lock, flags); + + return count; +} + +static int gs_put_char(struct tty_struct *tty, u8 ch) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + int status; + + pr_vdebug("gs_put_char: (%d,%p) char=0x%x, called from %ps\n", + port->port_num, tty, ch, __builtin_return_address(0)); + + spin_lock_irqsave(&port->port_lock, flags); + status = kfifo_put(&port->port_write_buf, ch); + 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; + int ret = 0; + struct gserial *gser = port->port_usb; + + pr_vdebug("gs_flush_chars: (%d,%p)\n", port->port_num, tty); + + spin_lock_irqsave(&port->port_lock, flags); + if (port->suspended) { + spin_unlock_irqrestore(&port->port_lock, flags); + ret = gserial_wakeup_host(gser); + if (ret) { + pr_debug("ttyGS%d: Remote wakeup failed:%d\n", port->port_num, ret); + return; + } + spin_lock_irqsave(&port->port_lock, flags); + } + + if (port->port_usb) + gs_start_tx(port); + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static unsigned int gs_write_room(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + unsigned int room = 0; + + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb) + room = kfifo_avail(&port->port_write_buf); + spin_unlock_irqrestore(&port->port_lock, flags); + + pr_vdebug("gs_write_room: (%d,%p) room=%u\n", + port->port_num, tty, room); + + return room; +} + +static unsigned int gs_chars_in_buffer(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + unsigned int chars; + + spin_lock_irqsave(&port->port_lock, flags); + chars = kfifo_len(&port->port_write_buf); + spin_unlock_irqrestore(&port->port_lock, flags); + + pr_vdebug("gs_chars_in_buffer: (%d,%p) chars=%u\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; + + 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. + */ + pr_vdebug("ttyGS%d: unthrottle\n", port->port_num); + schedule_delayed_work(&port->push, 0); + } + 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; + + 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 int gs_get_icount(struct tty_struct *tty, + struct serial_icounter_struct *icount) +{ + struct gs_port *port = tty->driver_data; + struct async_icount cnow; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + cnow = port->icount; + spin_unlock_irqrestore(&port->port_lock, flags); + + icount->rx = cnow.rx; + icount->tx = cnow.tx; + + return 0; +} + +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, + .get_icount = gs_get_icount, +}; + +/*-------------------------------------------------------------------------*/ + +static struct tty_driver *gs_tty_driver; + +#ifdef CONFIG_U_SERIAL_CONSOLE + +static void gs_console_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct gs_console *cons = req->context; + + switch (req->status) { + default: + pr_warn("%s: unexpected %s status %d\n", + __func__, ep->name, req->status); + fallthrough; + case 0: + /* normal completion */ + spin_lock(&cons->lock); + req->length = 0; + schedule_work(&cons->work); + spin_unlock(&cons->lock); + break; + case -ECONNRESET: + case -ESHUTDOWN: + /* disconnect */ + pr_vdebug("%s: %s shutdown\n", __func__, ep->name); + break; + } +} + +static void __gs_console_push(struct gs_console *cons) +{ + struct usb_request *req = cons->req; + struct usb_ep *ep; + size_t size; + + if (!req) + return; /* disconnected */ + + if (req->length) + return; /* busy */ + + ep = cons->console.data; + size = kfifo_out(&cons->buf, req->buf, ep->maxpacket); + if (!size) + return; + + if (cons->missed && ep->maxpacket >= 64) { + char buf[64]; + size_t len; + + len = sprintf(buf, "\n[missed %zu bytes]\n", cons->missed); + kfifo_in(&cons->buf, buf, len); + cons->missed = 0; + } + + req->length = size; + + spin_unlock_irq(&cons->lock); + if (usb_ep_queue(ep, req, GFP_ATOMIC)) + req->length = 0; + spin_lock_irq(&cons->lock); +} + +static void gs_console_work(struct work_struct *work) +{ + struct gs_console *cons = container_of(work, struct gs_console, work); + + spin_lock_irq(&cons->lock); + + __gs_console_push(cons); + + spin_unlock_irq(&cons->lock); +} + +static void gs_console_write(struct console *co, + const char *buf, unsigned count) +{ + struct gs_console *cons = container_of(co, struct gs_console, console); + unsigned long flags; + size_t n; + + spin_lock_irqsave(&cons->lock, flags); + + n = kfifo_in(&cons->buf, buf, count); + if (n < count) + cons->missed += count - n; + + if (cons->req && !cons->req->length) + schedule_work(&cons->work); + + spin_unlock_irqrestore(&cons->lock, flags); +} + +static struct tty_driver *gs_console_device(struct console *co, int *index) +{ + *index = co->index; + return gs_tty_driver; +} + +static int gs_console_connect(struct gs_port *port) +{ + struct gs_console *cons = port->console; + struct usb_request *req; + struct usb_ep *ep; + + if (!cons) + return 0; + + ep = port->port_usb->in; + req = gs_alloc_req(ep, ep->maxpacket, GFP_ATOMIC); + if (!req) + return -ENOMEM; + req->complete = gs_console_complete_out; + req->context = cons; + req->length = 0; + + spin_lock(&cons->lock); + cons->req = req; + cons->console.data = ep; + spin_unlock(&cons->lock); + + pr_debug("ttyGS%d: console connected!\n", port->port_num); + + schedule_work(&cons->work); + + return 0; +} + +static void gs_console_disconnect(struct gs_port *port) +{ + struct gs_console *cons = port->console; + struct usb_request *req; + struct usb_ep *ep; + + if (!cons) + return; + + spin_lock(&cons->lock); + + req = cons->req; + ep = cons->console.data; + cons->req = NULL; + + spin_unlock(&cons->lock); + + if (!req) + return; + + usb_ep_dequeue(ep, req); + gs_free_req(ep, req); +} + +static int gs_console_init(struct gs_port *port) +{ + struct gs_console *cons; + int err; + + if (port->console) + return 0; + + cons = kzalloc_obj(*port->console); + if (!cons) + return -ENOMEM; + + strscpy(cons->console.name, "ttyGS"); + cons->console.write = gs_console_write; + cons->console.device = gs_console_device; + cons->console.flags = CON_PRINTBUFFER; + cons->console.index = port->port_num; + + INIT_WORK(&cons->work, gs_console_work); + spin_lock_init(&cons->lock); + + err = kfifo_alloc(&cons->buf, GS_CONSOLE_BUF_SIZE, GFP_KERNEL); + if (err) { + pr_err("ttyGS%d: allocate console buffer failed\n", port->port_num); + kfree(cons); + return err; + } + + port->console = cons; + register_console(&cons->console); + + spin_lock_irq(&port->port_lock); + if (port->port_usb) + gs_console_connect(port); + spin_unlock_irq(&port->port_lock); + + return 0; +} + +static void gs_console_exit(struct gs_port *port) +{ + struct gs_console *cons = port->console; + + if (!cons) + return; + + unregister_console(&cons->console); + + spin_lock_irq(&port->port_lock); + if (cons->req) + gs_console_disconnect(port); + spin_unlock_irq(&port->port_lock); + + cancel_work_sync(&cons->work); + kfifo_free(&cons->buf); + kfree(cons); + port->console = NULL; +} + +ssize_t gserial_set_console(unsigned char port_num, const char *page, size_t count) +{ + struct gs_port *port; + bool enable; + int ret; + + ret = kstrtobool(page, &enable); + if (ret) + return ret; + + mutex_lock(&ports[port_num].lock); + port = ports[port_num].port; + + if (WARN_ON(port == NULL)) { + ret = -ENXIO; + goto out; + } + + if (enable) + ret = gs_console_init(port); + else + gs_console_exit(port); +out: + mutex_unlock(&ports[port_num].lock); + + return ret < 0 ? ret : count; +} +EXPORT_SYMBOL_GPL(gserial_set_console); + +ssize_t gserial_get_console(unsigned char port_num, char *page) +{ + struct gs_port *port; + ssize_t ret; + + mutex_lock(&ports[port_num].lock); + port = ports[port_num].port; + + if (WARN_ON(port == NULL)) + ret = -ENXIO; + else + ret = sprintf(page, "%u\n", !!port->console); + + mutex_unlock(&ports[port_num].lock); + + return ret; +} +EXPORT_SYMBOL_GPL(gserial_get_console); + +#else + +static int gs_console_connect(struct gs_port *port) +{ + return 0; +} + +static void gs_console_disconnect(struct gs_port *port) +{ +} + +static int gs_console_init(struct gs_port *port) +{ + return -ENOSYS; +} + +static void gs_console_exit(struct gs_port *port) +{ +} + +#endif + +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_obj(struct gs_port); + if (port == NULL) { + ret = -ENOMEM; + goto out; + } + + tty_port_init(&port->port); + spin_lock_init(&port->port_lock); + init_waitqueue_head(&port->drain_wait); + init_waitqueue_head(&port->close_wait); + + INIT_DELAYED_WORK(&port->push, gs_rx_push); + + 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; + spin_unlock_irq(&port->port_lock); + + return cond; +} + +static void gserial_free_port(struct gs_port *port) +{ + cancel_delayed_work_sync(&port->push); + /* wait for old opens to finish */ + wait_event(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 (!ports[port_num].port) { + mutex_unlock(&ports[port_num].lock); + return; + } + port = ports[port_num].port; + gs_console_exit(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_no_console(unsigned char *line_num) +{ + struct usb_cdc_line_coding coding; + struct gs_port *port; + 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* */ + + port = ports[port_num].port; + tty_dev = tty_port_register_device(&port->port, + gs_tty_driver, port_num, NULL); + if (IS_ERR(tty_dev)) { + 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); + mutex_lock(&ports[port_num].lock); + ports[port_num].port = NULL; + mutex_unlock(&ports[port_num].lock); + gserial_free_port(port); + goto err; + } + *line_num = port_num; +err: + return ret; +} +EXPORT_SYMBOL_GPL(gserial_alloc_line_no_console); + +int gserial_alloc_line(unsigned char *line_num) +{ + int ret = gserial_alloc_line_no_console(line_num); + + if (!ret && !*line_num) + gs_console_init(ports[*line_num].port); + + 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); + } + + status = gs_console_connect(port); + spin_unlock_irqrestore(&port->port_lock, flags); + + return status; + +fail_out: + usb_ep_disable(gser->in); + 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; + + spin_lock_irqsave(&serial_port_lock, flags); + + /* tell the TTY glue not to do I/O here any more */ + spin_lock(&port->port_lock); + + gs_console_disconnect(port); + + /* 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) { + wake_up_interruptible(&port->drain_wait); + if (port->port.tty) + tty_hangup(port->port.tty); + } + port->suspended = false; + spin_unlock(&port->port_lock); + spin_unlock_irqrestore(&serial_port_lock, flags); + + /* disable endpoints, aborting down any active I/O */ + usb_ep_disable(gser->out); + usb_ep_disable(gser->in); + + /* finally, free any unused/unusable I/O buffers */ + spin_lock_irqsave(&port->port_lock, flags); + if (port->port.count == 0) + kfifo_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); + +void gserial_suspend(struct gserial *gser) +{ + struct gs_port *port; + unsigned long flags; + + spin_lock_irqsave(&serial_port_lock, flags); + port = gser->ioport; + + if (!port) { + spin_unlock_irqrestore(&serial_port_lock, flags); + return; + } + + if (port->write_busy || port->write_started) { + /* Wakeup to host if there are ongoing transfers */ + spin_unlock_irqrestore(&serial_port_lock, flags); + if (!gserial_wakeup_host(gser)) + return; + spin_lock_irqsave(&serial_port_lock, flags); + } + + spin_lock(&port->port_lock); + spin_unlock(&serial_port_lock); + port->suspended = true; + port->start_delayed = true; + spin_unlock_irqrestore(&port->port_lock, flags); +} +EXPORT_SYMBOL_GPL(gserial_suspend); + +void gserial_resume(struct gserial *gser) +{ + struct gs_port *port; + unsigned long flags; + + spin_lock_irqsave(&serial_port_lock, flags); + port = gser->ioport; + + if (!port) { + spin_unlock_irqrestore(&serial_port_lock, flags); + return; + } + + spin_lock(&port->port_lock); + spin_unlock(&serial_port_lock); + port->suspended = false; + if (!port->start_delayed) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + pr_debug("delayed start ttyGS%d\n", port->port_num); + gs_start_io(port); + if (gser->connect) + gser->connect(gser); + port->start_delayed = false; + spin_unlock_irqrestore(&port->port_lock, flags); +} +EXPORT_SYMBOL_GPL(gserial_resume); + +static int __init userial_init(void) +{ + struct tty_driver *driver; + unsigned i; + int status; + + driver = tty_alloc_driver(MAX_U_SERIAL_PORTS, TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV); + if (IS_ERR(driver)) + return PTR_ERR(driver); + + driver->driver_name = "g_serial"; + driver->name = "ttyGS"; + /* uses dynamically assigned dev_t values */ + + driver->type = TTY_DRIVER_TYPE_SERIAL; + driver->subtype = SERIAL_TYPE_NORMAL; + 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. + */ + driver->init_termios.c_cflag = + B9600 | CS8 | CREAD | HUPCL | CLOCAL; + driver->init_termios.c_ispeed = 9600; + driver->init_termios.c_ospeed = 9600; + + tty_set_operations(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(driver); + if (status) { + pr_err("%s: cannot register, err %d\n", + __func__, status); + goto fail; + } + + gs_tty_driver = driver; + + pr_debug("%s: registered %d ttyGS* device%s\n", __func__, + MAX_U_SERIAL_PORTS, + str_plural(MAX_U_SERIAL_PORTS)); + + return status; +fail: + tty_driver_kref_put(driver); + return status; +} +module_init(userial_init); + +static void __exit userial_cleanup(void) +{ + tty_unregister_driver(gs_tty_driver); + tty_driver_kref_put(gs_tty_driver); + gs_tty_driver = NULL; +} +module_exit(userial_cleanup); + +MODULE_DESCRIPTION("utilities for USB gadget \"serial port\"/TTY support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/u_serial.h b/drivers/usb/gadget/function/u_serial.h new file mode 100644 index 000000000..e1274338e --- /dev/null +++ b/drivers/usb/gadget/function/u_serial.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * u_serial.h - interface to USB gadget "serial port"/TTY utilities + * + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + */ + +#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; + u8 protocol; + + struct mutex lock; /* protect instances */ + int instances; +}; + +/* + * 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_no_console(unsigned char *port_line); +int gserial_alloc_line(unsigned char *port_line); +void gserial_free_line(unsigned char port_line); + +#ifdef CONFIG_U_SERIAL_CONSOLE + +ssize_t gserial_set_console(unsigned char port_num, const char *page, size_t count); +ssize_t gserial_get_console(unsigned char port_num, char *page); + +#endif /* CONFIG_U_SERIAL_CONSOLE */ + +/* connect/disconnect is handled by individual functions */ +int gserial_connect(struct gserial *, u8 port_num); +void gserial_disconnect(struct gserial *); +void gserial_suspend(struct gserial *p); +void gserial_resume(struct gserial *p); + +#endif /* __U_SERIAL_H */ diff --git a/drivers/usb/gadget/function/u_tcm.h b/drivers/usb/gadget/function/u_tcm.h new file mode 100644 index 000000000..2cd15d9a1 --- /dev/null +++ b/drivers/usb/gadget/function/u_tcm.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * u_tcm.h + * + * Utility definitions for the tcm function + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef U_TCM_H +#define U_TCM_H + +#include + +/** + * @dependent: optional dependent module. Meant for legacy gadget. + * If non-null its refcount will be increased when a tpg is created and + * decreased when tpg is dropped. + * @dep_lock: lock for dependent module operations. + * @ready: true if the dependent module information is set. + * @can_attach: true a function can be bound to gadget + * @has_dep: true if there is a dependent module + * + */ +struct f_tcm_opts { + struct usb_function_instance func_inst; + struct module *dependent; + struct mutex dep_lock; + bool ready; + bool can_attach; + bool has_dep; + + /* + * Callbacks to be removed when legacy tcm gadget disappears. + * + * If you use the new function registration interface + * programmatically, you MUST set these callbacks to + * something sensible (e.g. probe/remove the composite). + */ + int (*tcm_register_callback)(struct usb_function_instance *); + void (*tcm_unregister_callback)(struct usb_function_instance *); +}; + +#endif /* U_TCM_H */ diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h new file mode 100644 index 000000000..feb6eb764 --- /dev/null +++ b/drivers/usb/gadget/function/u_uac1.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * u_uac1.h - Utility definitions for UAC1 function + * + * Copyright (C) 2016 Ruslan Bilovol + */ + +#ifndef __U_UAC1_H +#define __U_UAC1_H + +#include +#include "uac_common.h" + +#define UAC1_OUT_EP_MAX_PACKET_SIZE 200 +#define UAC1_DEF_CCHMASK 0x3 +#define UAC1_DEF_CSRATE 48000 +#define UAC1_DEF_CSSIZE 2 +#define UAC1_DEF_PCHMASK 0x3 +#define UAC1_DEF_PSRATE 48000 +#define UAC1_DEF_PSSIZE 2 +#define UAC1_DEF_REQ_NUM 2 +#define UAC1_DEF_INT_REQ_NUM 10 + +#define UAC1_DEF_MUTE_PRESENT 1 +#define UAC1_DEF_VOLUME_PRESENT 1 +#define UAC1_DEF_MIN_DB (-100*256) /* -100 dB */ +#define UAC1_DEF_MAX_DB 0 /* 0 dB */ +#define UAC1_DEF_RES_DB (1*256) /* 1 dB */ + + +struct f_uac1_opts { + struct usb_function_instance func_inst; + int c_chmask; + int c_srates[UAC_MAX_RATES]; + int c_ssize; + int p_chmask; + int p_srates[UAC_MAX_RATES]; + int p_ssize; + + bool p_mute_present; + bool p_volume_present; + s16 p_volume_min; + s16 p_volume_max; + s16 p_volume_res; + + bool c_mute_present; + bool c_volume_present; + s16 c_volume_min; + s16 c_volume_max; + s16 c_volume_res; + + int req_number; + unsigned bound:1; + + char function_name[USB_MAX_STRING_LEN]; + + char p_it_name[USB_MAX_STRING_LEN]; + char p_it_ch_name[USB_MAX_STRING_LEN]; + char p_ot_name[USB_MAX_STRING_LEN]; + char p_fu_vol_name[USB_MAX_STRING_LEN]; + + char c_it_name[USB_MAX_STRING_LEN]; + char c_it_ch_name[USB_MAX_STRING_LEN]; + char c_ot_name[USB_MAX_STRING_LEN]; + char c_fu_vol_name[USB_MAX_STRING_LEN]; + + struct mutex lock; + int refcnt; +}; + +#endif /* __U_UAC1_H */ diff --git a/drivers/usb/gadget/function/u_uac1_legacy.c b/drivers/usb/gadget/function/u_uac1_legacy.c new file mode 100644 index 000000000..01016102f --- /dev/null +++ b/drivers/usb/gadget/function/u_uac1_legacy.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * u_uac1.c -- ALSA audio utilities for Gadget stack + * + * Copyright (C) 2008 Bryan Wu + * Copyright (C) 2008 Analog Devices, Inc + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "u_uac1_legacy.h" + +/* + * This component encapsulates the ALSA devices for USB audio gadget + */ + +/*-------------------------------------------------------------------------*/ + +/* + * 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_obj(*params); + 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 + */ +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; + ssize_t result; + snd_pcm_sframes_t frames; + +try_again: + if (runtime->state == SNDRV_PCM_STATE_XRUN || + runtime->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); + result = snd_pcm_kernel_write(snd->substream, buf, frames); + if (result != frames) { + ERROR(card, "Playback error: %d\n", (int)result); + goto try_again; + } + + return 0; +} + +int u_audio_get_playback_channels(struct gaudio *card) +{ + return card->playback.channels; +} + +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; + struct f_uac1_legacy_opts *opts; + char *fn_play, *fn_cap, *fn_cntl; + + opts = container_of(card->func.fi, struct f_uac1_legacy_opts, + func_inst); + fn_play = opts->fn_play; + fn_cap = opts->fn_cap; + fn_cntl = opts->fn_cntl; + + /* 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; +} + +/* + * 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 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"); + + return ret; + +} + +/* + * gaudio_cleanup - remove ALSA device interface + * + * This is called to free all resources allocated by @gaudio_setup(). + */ +void gaudio_cleanup(struct gaudio *the_card) +{ + if (the_card) + gaudio_close_snd_dev(the_card); +} + diff --git a/drivers/usb/gadget/function/u_uac1_legacy.h b/drivers/usb/gadget/function/u_uac1_legacy.h new file mode 100644 index 000000000..b9ddae550 --- /dev/null +++ b/drivers/usb/gadget/function/u_uac1_legacy.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * u_uac1.h -- interface to USB gadget "ALSA AUDIO" utilities + * + * Copyright (C) 2008 Bryan Wu + * Copyright (C) 2008 Analog Devices, Inc + */ + +#ifndef __U_UAC1_LEGACY_H +#define __U_UAC1_LEGACY_H + +#include +#include +#include +#include + +#include +#include +#include + +#define FILE_PCM_PLAYBACK "/dev/snd/pcmC0D0p" +#define FILE_PCM_CAPTURE "/dev/snd/pcmC0D0c" +#define FILE_CONTROL "/dev/snd/controlC0" + +#define UAC1_OUT_EP_MAX_PACKET_SIZE 200 +#define UAC1_REQ_COUNT 256 +#define UAC1_AUDIO_BUF_SIZE 48000 + +/* + * 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 */ +}; + +struct f_uac1_legacy_opts { + struct usb_function_instance func_inst; + int req_buf_size; + int req_count; + int audio_buf_size; + char *fn_play; + char *fn_cap; + char *fn_cntl; + unsigned bound:1; + struct mutex lock; + int refcnt; +}; + +int gaudio_setup(struct gaudio *card); +void gaudio_cleanup(struct gaudio *the_card); + +size_t u_audio_playback(struct gaudio *card, void *buf, size_t count); +int u_audio_get_playback_channels(struct gaudio *card); +int u_audio_get_playback_rate(struct gaudio *card); + +#endif /* __U_UAC1_LEGACY_H */ diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h new file mode 100644 index 000000000..0df808289 --- /dev/null +++ b/drivers/usb/gadget/function/u_uac2.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * u_uac2.h + * + * Utility definitions for UAC2 function + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef U_UAC2_H +#define U_UAC2_H + +#include +#include "uac_common.h" + +#define UAC2_DEF_PCHMASK 0x3 +#define UAC2_DEF_PSRATE 48000 +#define UAC2_DEF_PSSIZE 2 +#define UAC2_DEF_PHSBINT 0 +#define UAC2_DEF_CCHMASK 0x3 +#define UAC2_DEF_CSRATE 64000 +#define UAC2_DEF_CSSIZE 2 +#define UAC2_DEF_CHSBINT 0 +#define UAC2_DEF_CSYNC USB_ENDPOINT_SYNC_ASYNC + +#define UAC2_DEF_MUTE_PRESENT 1 +#define UAC2_DEF_VOLUME_PRESENT 1 +#define UAC2_DEF_MIN_DB (-100*256) /* -100 dB */ +#define UAC2_DEF_MAX_DB 0 /* 0 dB */ +#define UAC2_DEF_RES_DB (1*256) /* 1 dB */ + +#define UAC2_DEF_REQ_NUM 2 +#define UAC2_DEF_INT_REQ_NUM 10 + +#define UAC2_DEF_P_TERM_TYPE 0x301 + /* UAC_OUTPUT_TERMINAL_SPEAKER */ +#define UAC2_DEF_C_TERM_TYPE 0x201 + /* UAC_INPUT_TERMINAL_MICROPHONE*/ + +struct f_uac2_opts { + struct usb_function_instance func_inst; + int p_chmask; + int p_srates[UAC_MAX_RATES]; + int p_ssize; + u8 p_hs_bint; + int c_chmask; + int c_srates[UAC_MAX_RATES]; + int c_ssize; + int c_sync; + u8 c_hs_bint; + + bool p_mute_present; + bool p_volume_present; + s16 p_volume_min; + s16 p_volume_max; + s16 p_volume_res; + + bool c_mute_present; + bool c_volume_present; + s16 c_volume_min; + s16 c_volume_max; + s16 c_volume_res; + + int req_number; + int fb_max; + bool bound; + + char function_name[USB_MAX_STRING_LEN]; + char if_ctrl_name[USB_MAX_STRING_LEN]; + char clksrc_in_name[USB_MAX_STRING_LEN]; + char clksrc_out_name[USB_MAX_STRING_LEN]; + + char p_it_name[USB_MAX_STRING_LEN]; + char p_it_ch_name[USB_MAX_STRING_LEN]; + char p_ot_name[USB_MAX_STRING_LEN]; + char p_fu_vol_name[USB_MAX_STRING_LEN]; + + char c_it_name[USB_MAX_STRING_LEN]; + char c_it_ch_name[USB_MAX_STRING_LEN]; + char c_ot_name[USB_MAX_STRING_LEN]; + char c_fu_vol_name[USB_MAX_STRING_LEN]; + + s16 p_terminal_type; + s16 c_terminal_type; + + struct mutex lock; + int refcnt; +}; + +#endif diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h new file mode 100644 index 000000000..3ac392cbb --- /dev/null +++ b/drivers/usb/gadget/function/u_uvc.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * u_uvc.h + * + * Utility definitions for the uvc function + * + * Copyright (c) 2013-2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef U_UVC_H +#define U_UVC_H + +#include +#include +#include + +#define fi_to_f_uvc_opts(f) container_of(f, struct f_uvc_opts, func_inst) + +struct f_uvc_opts { + struct usb_function_instance func_inst; + unsigned int streaming_interval; + unsigned int streaming_maxpacket; + unsigned int streaming_maxburst; + + unsigned int control_interface; + unsigned int streaming_interface; + char function_name[32]; + unsigned int last_unit_id; + + bool enable_interrupt_ep; + + /* + * Control descriptors array pointers for full-/high-speed and + * super-speed. They point by default to the uvc_fs_control_cls and + * uvc_ss_control_cls arrays respectively. Legacy gadgets must + * override them in their gadget bind callback. + */ + const struct uvc_descriptor_header * const *fs_control; + const struct uvc_descriptor_header * const *ss_control; + + /* + * Streaming descriptors array pointers for full-speed, high-speed and + * super-speed. They will point to the uvc_[fhs]s_streaming_cls arrays + * for configfs-based gadgets. Legacy gadgets must initialize them in + * their gadget bind callback. + */ + const struct uvc_descriptor_header * const *fs_streaming; + const struct uvc_descriptor_header * const *hs_streaming; + const struct uvc_descriptor_header * const *ss_streaming; + + /* Default control descriptors for configfs-based gadgets. */ + struct uvc_camera_terminal_descriptor uvc_camera_terminal; + struct uvc_processing_unit_descriptor uvc_processing; + struct uvc_output_terminal_descriptor uvc_output_terminal; + + /* + * Control descriptors pointers arrays for full-/high-speed and + * super-speed. The first element is a configurable control header + * descriptor, the other elements point to the fixed default control + * descriptors. Used by configfs only, must not be touched by legacy + * gadgets. + */ + struct uvc_descriptor_header *uvc_fs_control_cls[5]; + struct uvc_descriptor_header *uvc_ss_control_cls[5]; + + /* + * Control descriptors for extension units. There could be any number + * of these, including none at all. + */ + struct list_head extension_units; + + /* + * Streaming descriptors for full-speed, high-speed and super-speed. + * Used by configfs only, must not be touched by legacy gadgets. The + * arrays are allocated at runtime as the number of descriptors isn't + * known in advance. + */ + struct uvc_descriptor_header **uvc_fs_streaming_cls; + struct uvc_descriptor_header **uvc_hs_streaming_cls; + struct uvc_descriptor_header **uvc_ss_streaming_cls; + + /* + * Indexes into the function's string descriptors allowing users to set + * custom descriptions rather than the hard-coded defaults. + */ + u8 iad_index; + u8 vs0_index; + u8 vs1_index; + + /* + * Read/write access to configfs attributes is handled by configfs. + * + * This lock protects the descriptors from concurrent access by + * read/write and symlink creation/removal. + */ + struct mutex lock; + int refcnt; + + /* + * Only for legacy gadget. Shall be NULL for configfs-composed gadgets, + * which is guaranteed by alloc_inst implementation of f_uvc doing kzalloc. + */ + struct uvcg_streaming_header *header; +}; + +#endif /* U_UVC_H */ diff --git a/drivers/usb/gadget/function/uac_common.h b/drivers/usb/gadget/function/uac_common.h new file mode 100644 index 000000000..3ecf89d6e --- /dev/null +++ b/drivers/usb/gadget/function/uac_common.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + */ + +#ifndef UAC_COMMON_H +#define UAC_COMMON_H + +#define UAC_MAX_RATES 10 /* maximum number of rates configurable by f_uac1/2 */ +#endif diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h new file mode 100644 index 000000000..7abfdd5e1 --- /dev/null +++ b/drivers/usb/gadget/function/uvc.h @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * uvc_gadget.h -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef _UVC_GADGET_H_ +#define _UVC_GADGET_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "uvc_queue.h" + +struct usb_ep; +struct usb_request; +struct uvc_descriptor_header; +struct uvc_device; + +/* ------------------------------------------------------------------------ + * Debugging, printing and logging + */ + +#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 uvcg_dbg(f, fmt, args...) \ + dev_dbg(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args) +#define uvcg_info(f, fmt, args...) \ + dev_info(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args) +#define uvcg_warn(f, fmt, args...) \ + dev_warn(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args) +#define uvcg_err(f, fmt, args...) \ + dev_err(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args) + +/* ------------------------------------------------------------------------ + * Driver specific constants + */ + +#define UVC_MAX_REQUEST_SIZE 64 +#define UVC_MAX_EVENTS 4 + +#define UVCG_REQUEST_HEADER_LEN 12 + +#define UVCG_REQ_MAX_INT_COUNT 16 +#define UVCG_REQ_MAX_ZERO_COUNT (2 * UVCG_REQ_MAX_INT_COUNT) + +#define UVCG_STREAMING_MIN_BUFFERS 2 + +/* ------------------------------------------------------------------------ + * Structures + */ +struct uvc_request { + struct usb_request *req; + u8 *req_buffer; + struct uvc_video *video; + struct sg_table sgt; + u8 header[UVCG_REQUEST_HEADER_LEN]; + struct uvc_buffer *last_buf; + struct list_head list; +}; + +struct uvc_video { + struct uvc_device *uvc; + struct usb_ep *ep; + + struct work_struct pump; + struct workqueue_struct *async_wq; + + struct kthread_worker *kworker; + struct kthread_work hw_submit; + + atomic_t queued; + + /* Frame parameters */ + u8 bpp; + u32 fcc; + unsigned int width; + unsigned int height; + unsigned int imagesize; + unsigned int interval; /* in 100ns units */ + struct mutex mutex; /* protects frame parameters */ + + unsigned int uvc_num_requests; + + unsigned int reqs_per_frame; + + /* Requests */ + bool is_enabled; /* tracks whether video stream is enabled */ + unsigned int req_size; + unsigned int max_req_size; + struct list_head ureqs; /* all uvc_requests allocated by uvc_video */ + + /* USB requests that the video pump thread can encode into */ + struct list_head req_free; + + /* + * USB requests video pump thread has already encoded into. These are + * ready to be queued to the endpoint. + */ + struct list_head req_ready; + spinlock_t req_lock; + + unsigned int req_int_count; + + 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; + struct v4l2_device v4l2_dev; + enum uvc_state state; + struct usb_function func; + struct uvc_video video; + struct completion *vdev_release_done; + struct mutex lock; /* protects func_unbound and func_connected */ + bool func_unbound; + bool func_connected; + wait_queue_head_t func_connected_queue; + + struct uvcg_streaming_header *header; + + /* 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; + struct list_head *extension_units; + } desc; + + unsigned int control_intf; + struct usb_ep *interrupt_ep; + struct usb_request *control_req; + void *control_buf; + bool enable_interrupt_ep; + + 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; + bool is_uvc_app_handle; +}; + +#define to_uvc_file_handle(handle) \ + container_of(handle, struct uvc_file_handle, vfh) + +static inline struct uvc_file_handle *file_to_uvc_file_handle(struct file *filp) +{ + return container_of(file_to_v4l2_fh(filp), struct uvc_file_handle, vfh); +} + +/* ------------------------------------------------------------------------ + * Functions + */ + +extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep); +extern void uvc_function_connect(struct uvc_device *uvc); +extern void uvc_function_disconnect(struct uvc_device *uvc); + +#endif /* _UVC_GADGET_H_ */ diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c new file mode 100644 index 000000000..70a1415ea --- /dev/null +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -0,0 +1,3835 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * uvc_configfs.c + * + * Configfs support for the uvc function. + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#include "uvc_configfs.h" + +#include +#include +#include +#include + +/* ----------------------------------------------------------------------------- + * Global Utility Structures and Macros + */ + +#define UVC_ATTR(prefix, cname, aname) \ +static struct configfs_attribute prefix##attr_##cname = { \ + .ca_name = __stringify(aname), \ + .ca_mode = S_IRUGO | S_IWUGO, \ + .ca_owner = THIS_MODULE, \ + .show = prefix##cname##_show, \ + .store = prefix##cname##_store, \ +} + +#define UVC_ATTR_RO(prefix, cname, aname) \ +static struct configfs_attribute prefix##attr_##cname = { \ + .ca_name = __stringify(aname), \ + .ca_mode = S_IRUGO, \ + .ca_owner = THIS_MODULE, \ + .show = prefix##cname##_show, \ +} + +#define le8_to_cpu(x) (x) +#define cpu_to_le8(x) (x) + +static int uvcg_config_compare_u32(const void *l, const void *r) +{ + u32 li = *(const u32 *)l; + u32 ri = *(const u32 *)r; + + return li < ri ? -1 : li == ri ? 0 : 1; +} + +static inline int __uvcg_count_item_entries(char *buf, void *priv, unsigned int size) +{ + ++*((int *)priv); + return 0; +} + +static inline int __uvcg_fill_item_entries(char *buf, void *priv, unsigned int size) +{ + unsigned int num; + u8 **values; + int ret; + + ret = kstrtouint(buf, 0, &num); + if (ret) + return ret; + + if (num != (num & GENMASK((size * 8) - 1, 0))) + return -ERANGE; + + values = priv; + memcpy(*values, &num, size); + *values += size; + + return 0; +} + +static int __uvcg_iter_item_entries(const char *page, size_t len, + int (*fun)(char *, void *, unsigned int), + void *priv, unsigned int size) +{ + /* sign, base 2 representation, newline, terminator */ + unsigned int bufsize = 1 + size * 8 + 1 + 1; + const char *pg = page; + int i, ret = 0; + char *buf; + + if (!fun) + return -EINVAL; + + buf = kzalloc(bufsize, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + while (pg - page < len) { + i = 0; + while (i < bufsize && (pg - page < len) && + *pg != '\0' && *pg != '\n') + buf[i++] = *pg++; + if (i == bufsize) { + ret = -EINVAL; + goto out_free_buf; + } + while ((pg - page < len) && (*pg == '\0' || *pg == '\n')) + ++pg; + buf[i] = '\0'; + ret = fun(buf, priv, size); + if (ret) + goto out_free_buf; + } + +out_free_buf: + kfree(buf); + return ret; +} + +struct uvcg_config_group_type { + struct config_item_type type; + const char *name; + const struct uvcg_config_group_type **children; + int (*create_children)(struct config_group *group); +}; + +static void uvcg_config_item_release(struct config_item *item) +{ + struct config_group *group = to_config_group(item); + + kfree(group); +} + +static const struct configfs_item_operations uvcg_config_item_ops = { + .release = uvcg_config_item_release, +}; + +static int uvcg_config_create_group(struct config_group *parent, + const struct uvcg_config_group_type *type); + +static int uvcg_config_create_children(struct config_group *group, + const struct uvcg_config_group_type *type) +{ + const struct uvcg_config_group_type **child; + int ret; + + if (type->create_children) + return type->create_children(group); + + for (child = type->children; child && *child; ++child) { + ret = uvcg_config_create_group(group, *child); + if (ret < 0) + return ret; + } + + return 0; +} + +static int uvcg_config_create_group(struct config_group *parent, + const struct uvcg_config_group_type *type) +{ + struct config_group *group; + + group = kzalloc_obj(*group); + if (!group) + return -ENOMEM; + + config_group_init_type_name(group, type->name, &type->type); + configfs_add_default_group(group, parent); + + return uvcg_config_create_children(group, type); +} + +static void uvcg_config_remove_children(struct config_group *group) +{ + struct config_group *child, *n; + + list_for_each_entry_safe(child, n, &group->default_groups, group_entry) { + list_del(&child->group_entry); + uvcg_config_remove_children(child); + config_item_put(&child->cg_item); + } +} + +/* ----------------------------------------------------------------------------- + * control/header/ + * control/header + */ + +#define UVCG_CTRL_HDR_ATTR(cname, aname, bits, limit) \ +static ssize_t uvcg_control_header_##cname##_show( \ + struct config_item *item, char *page) \ +{ \ + struct uvcg_control_header *ch = to_uvcg_control_header(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;\ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = ch->item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(ch->desc.aname));\ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t \ +uvcg_control_header_##cname##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct uvcg_control_header *ch = to_uvcg_control_header(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;\ + int ret; \ + u##bits num; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = ch->item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + if (ch->linked || opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou##bits(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > limit) { \ + ret = -EINVAL; \ + goto end; \ + } \ + ch->desc.aname = cpu_to_le##bits(num); \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(su_mutex); \ + return ret; \ +} \ + \ +UVC_ATTR(uvcg_control_header_, cname, aname) + +UVCG_CTRL_HDR_ATTR(bcd_uvc, bcdUVC, 16, 0xffff); + +UVCG_CTRL_HDR_ATTR(dw_clock_frequency, dwClockFrequency, 32, 0x7fffffff); + +#undef UVCG_CTRL_HDR_ATTR + +static struct configfs_attribute *uvcg_control_header_attrs[] = { + &uvcg_control_header_attr_bcd_uvc, + &uvcg_control_header_attr_dw_clock_frequency, + NULL, +}; + +static const struct config_item_type uvcg_control_header_type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_control_header_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *uvcg_control_header_make(struct config_group *group, + const char *name) +{ + struct uvcg_control_header *h; + + h = kzalloc_obj(*h); + if (!h) + return ERR_PTR(-ENOMEM); + + h->desc.bLength = UVC_DT_HEADER_SIZE(1); + h->desc.bDescriptorType = USB_DT_CS_INTERFACE; + h->desc.bDescriptorSubType = UVC_VC_HEADER; + h->desc.bcdUVC = cpu_to_le16(0x0110); + h->desc.dwClockFrequency = cpu_to_le32(48000000); + + config_item_init_type_name(&h->item, name, &uvcg_control_header_type); + + return &h->item; +} + +static const struct configfs_group_operations uvcg_control_header_grp_ops = { + .make_item = uvcg_control_header_make, +}; + +static const struct uvcg_config_group_type uvcg_control_header_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_control_header_grp_ops, + .ct_owner = THIS_MODULE, + }, + .name = "header", +}; + +/* ----------------------------------------------------------------------------- + * control/processing/default + */ + +#define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, bits) \ +static ssize_t uvcg_default_processing_##cname##_show( \ + struct config_item *item, char *page) \ +{ \ + struct config_group *group = to_config_group(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ + struct uvc_processing_unit_descriptor *pd; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + pd = &opts->uvc_processing; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(pd->aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +UVC_ATTR_RO(uvcg_default_processing_, cname, aname) + +UVCG_DEFAULT_PROCESSING_ATTR(b_unit_id, bUnitID, 8); +UVCG_DEFAULT_PROCESSING_ATTR(b_source_id, bSourceID, 8); +UVCG_DEFAULT_PROCESSING_ATTR(w_max_multiplier, wMaxMultiplier, 16); +UVCG_DEFAULT_PROCESSING_ATTR(i_processing, iProcessing, 8); + +#undef UVCG_DEFAULT_PROCESSING_ATTR + +static ssize_t uvcg_default_processing_bm_controls_store( + struct config_item *item, const char *page, size_t len) +{ + struct config_group *group = to_config_group(item); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvc_processing_unit_descriptor *pd; + struct config_item *opts_item; + struct f_uvc_opts *opts; + u8 *bm_controls, *tmp; + unsigned int i; + int ret, n = 0; + + mutex_lock(su_mutex); + + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + pd = &opts->uvc_processing; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto unlock; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n, + sizeof(u8)); + if (ret) + goto unlock; + + if (n > pd->bControlSize) { + ret = -EINVAL; + goto unlock; + } + + tmp = bm_controls = kcalloc(n, sizeof(u8), GFP_KERNEL); + if (!bm_controls) { + ret = -ENOMEM; + goto unlock; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &tmp, + sizeof(u8)); + if (ret) + goto free_mem; + + for (i = 0; i < n; i++) + pd->bmControls[i] = bm_controls[i]; + + ret = len; + +free_mem: + kfree(bm_controls); +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +static ssize_t uvcg_default_processing_bm_controls_show( + struct config_item *item, char *page) +{ + struct config_group *group = to_config_group(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvc_processing_unit_descriptor *pd; + int result, i; + char *pg = page; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + pd = &opts->uvc_processing; + + mutex_lock(&opts->lock); + for (result = 0, i = 0; i < pd->bControlSize; ++i) { + result += sprintf(pg, "%u\n", pd->bmControls[i]); + pg = page + result; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return result; +} + +UVC_ATTR(uvcg_default_processing_, bm_controls, bmControls); + +static struct configfs_attribute *uvcg_default_processing_attrs[] = { + &uvcg_default_processing_attr_b_unit_id, + &uvcg_default_processing_attr_b_source_id, + &uvcg_default_processing_attr_w_max_multiplier, + &uvcg_default_processing_attr_bm_controls, + &uvcg_default_processing_attr_i_processing, + NULL, +}; + +static const struct uvcg_config_group_type uvcg_default_processing_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_default_processing_attrs, + .ct_owner = THIS_MODULE, + }, + .name = "default", +}; + +/* ----------------------------------------------------------------------------- + * control/processing + */ + +static const struct uvcg_config_group_type uvcg_processing_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "processing", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_default_processing_type, + NULL, + }, +}; + +/* ----------------------------------------------------------------------------- + * control/terminal/camera/default + */ + +#define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, bits) \ +static ssize_t uvcg_default_camera_##cname##_show( \ + struct config_item *item, char *page) \ +{ \ + struct config_group *group = to_config_group(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ + struct uvc_camera_terminal_descriptor *cd; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent-> \ + ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + cd = &opts->uvc_camera_terminal; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(cd->aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + \ + return result; \ +} \ + \ +UVC_ATTR_RO(uvcg_default_camera_, cname, aname) + +UVCG_DEFAULT_CAMERA_ATTR(b_terminal_id, bTerminalID, 8); +UVCG_DEFAULT_CAMERA_ATTR(w_terminal_type, wTerminalType, 16); +UVCG_DEFAULT_CAMERA_ATTR(b_assoc_terminal, bAssocTerminal, 8); +UVCG_DEFAULT_CAMERA_ATTR(i_terminal, iTerminal, 8); +UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_min, wObjectiveFocalLengthMin, + 16); +UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_max, wObjectiveFocalLengthMax, + 16); +UVCG_DEFAULT_CAMERA_ATTR(w_ocular_focal_length, wOcularFocalLength, + 16); + +#undef UVCG_DEFAULT_CAMERA_ATTR + +static ssize_t uvcg_default_camera_bm_controls_store( + struct config_item *item, const char *page, size_t len) +{ + struct config_group *group = to_config_group(item); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvc_camera_terminal_descriptor *cd; + struct config_item *opts_item; + struct f_uvc_opts *opts; + u8 *bm_controls, *tmp; + unsigned int i; + int ret, n = 0; + + mutex_lock(su_mutex); + + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent-> + ci_parent; + opts = to_f_uvc_opts(opts_item); + cd = &opts->uvc_camera_terminal; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto unlock; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n, + sizeof(u8)); + if (ret) + goto unlock; + + if (n > cd->bControlSize) { + ret = -EINVAL; + goto unlock; + } + + tmp = bm_controls = kcalloc(n, sizeof(u8), GFP_KERNEL); + if (!bm_controls) { + ret = -ENOMEM; + goto unlock; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &tmp, + sizeof(u8)); + if (ret) + goto free_mem; + + for (i = 0; i < n; i++) + cd->bmControls[i] = bm_controls[i]; + + ret = len; + +free_mem: + kfree(bm_controls); +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +static ssize_t uvcg_default_camera_bm_controls_show( + struct config_item *item, char *page) +{ + struct config_group *group = to_config_group(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvc_camera_terminal_descriptor *cd; + int result, i; + char *pg = page; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent-> + ci_parent; + opts = to_f_uvc_opts(opts_item); + cd = &opts->uvc_camera_terminal; + + mutex_lock(&opts->lock); + for (result = 0, i = 0; i < cd->bControlSize; ++i) { + result += sprintf(pg, "%u\n", cd->bmControls[i]); + pg = page + result; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + return result; +} + +UVC_ATTR(uvcg_default_camera_, bm_controls, bmControls); + +static struct configfs_attribute *uvcg_default_camera_attrs[] = { + &uvcg_default_camera_attr_b_terminal_id, + &uvcg_default_camera_attr_w_terminal_type, + &uvcg_default_camera_attr_b_assoc_terminal, + &uvcg_default_camera_attr_i_terminal, + &uvcg_default_camera_attr_w_objective_focal_length_min, + &uvcg_default_camera_attr_w_objective_focal_length_max, + &uvcg_default_camera_attr_w_ocular_focal_length, + &uvcg_default_camera_attr_bm_controls, + NULL, +}; + +static const struct uvcg_config_group_type uvcg_default_camera_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_default_camera_attrs, + .ct_owner = THIS_MODULE, + }, + .name = "default", +}; + +/* ----------------------------------------------------------------------------- + * control/terminal/camera + */ + +static const struct uvcg_config_group_type uvcg_camera_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "camera", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_default_camera_type, + NULL, + }, +}; + +/* ----------------------------------------------------------------------------- + * control/terminal/output/default + */ + +#define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, bits) \ +static ssize_t uvcg_default_output_##cname##_show( \ + struct config_item *item, char *page) \ +{ \ + struct config_group *group = to_config_group(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ + struct uvc_output_terminal_descriptor *cd; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = group->cg_item.ci_parent->ci_parent-> \ + ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + cd = &opts->uvc_output_terminal; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(cd->aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + \ + return result; \ +} \ + \ +UVC_ATTR_RO(uvcg_default_output_, cname, aname) + +UVCG_DEFAULT_OUTPUT_ATTR(b_terminal_id, bTerminalID, 8); +UVCG_DEFAULT_OUTPUT_ATTR(w_terminal_type, wTerminalType, 16); +UVCG_DEFAULT_OUTPUT_ATTR(b_assoc_terminal, bAssocTerminal, 8); +UVCG_DEFAULT_OUTPUT_ATTR(i_terminal, iTerminal, 8); + +#undef UVCG_DEFAULT_OUTPUT_ATTR + +static ssize_t uvcg_default_output_b_source_id_show(struct config_item *item, + char *page) +{ + struct config_group *group = to_config_group(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvc_output_terminal_descriptor *cd; + int result; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = group->cg_item.ci_parent->ci_parent-> + ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + cd = &opts->uvc_output_terminal; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", le8_to_cpu(cd->bSourceID)); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return result; +} + +static ssize_t uvcg_default_output_b_source_id_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvc_output_terminal_descriptor *cd; + int result; + u8 num; + + result = kstrtou8(page, 0, &num); + if (result) + return result; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = group->cg_item.ci_parent->ci_parent-> + ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + cd = &opts->uvc_output_terminal; + + mutex_lock(&opts->lock); + cd->bSourceID = num; + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return len; +} +UVC_ATTR(uvcg_default_output_, b_source_id, bSourceID); + +static struct configfs_attribute *uvcg_default_output_attrs[] = { + &uvcg_default_output_attr_b_terminal_id, + &uvcg_default_output_attr_w_terminal_type, + &uvcg_default_output_attr_b_assoc_terminal, + &uvcg_default_output_attr_b_source_id, + &uvcg_default_output_attr_i_terminal, + NULL, +}; + +static const struct uvcg_config_group_type uvcg_default_output_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_default_output_attrs, + .ct_owner = THIS_MODULE, + }, + .name = "default", +}; + +/* ----------------------------------------------------------------------------- + * control/terminal/output + */ + +static const struct uvcg_config_group_type uvcg_output_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "output", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_default_output_type, + NULL, + }, +}; + +/* ----------------------------------------------------------------------------- + * control/terminal + */ + +static const struct uvcg_config_group_type uvcg_terminal_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "terminal", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_camera_grp_type, + &uvcg_output_grp_type, + NULL, + }, +}; + +/* ----------------------------------------------------------------------------- + * control/extensions + */ + +#define UVCG_EXTENSION_ATTR(cname, aname, ro...) \ +static ssize_t uvcg_extension_##cname##_show(struct config_item *item, \ + char *page) \ +{ \ + struct config_group *group = to_config_group(item->ci_parent); \ + struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ + struct uvcg_extension *xu = to_uvcg_extension(item); \ + struct config_item *opts_item; \ + struct f_uvc_opts *opts; \ + int ret; \ + \ + mutex_lock(su_mutex); \ + \ + opts_item = item->ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + ret = sprintf(page, "%u\n", xu->desc.aname); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + \ + return ret; \ +} \ +UVC_ATTR##ro(uvcg_extension_, cname, aname) + +UVCG_EXTENSION_ATTR(b_length, bLength, _RO); +UVCG_EXTENSION_ATTR(b_unit_id, bUnitID, _RO); +UVCG_EXTENSION_ATTR(i_extension, iExtension, _RO); + +static ssize_t uvcg_extension_b_num_controls_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + int ret; + u8 num; + + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + xu->desc.bNumControls = num; + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return len; +} +UVCG_EXTENSION_ATTR(b_num_controls, bNumControls); + +/* + * In addition to storing bNrInPins, this function needs to realloc the + * memory for the baSourceID array and additionally expand bLength. + */ +static ssize_t uvcg_extension_b_nr_in_pins_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + void *tmp_buf; + int ret; + u8 num; + + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + if (num == xu->desc.bNrInPins) { + ret = len; + goto unlock; + } + + tmp_buf = krealloc_array(xu->desc.baSourceID, num, sizeof(u8), + GFP_KERNEL | __GFP_ZERO); + if (!tmp_buf) { + ret = -ENOMEM; + goto unlock; + } + + xu->desc.baSourceID = tmp_buf; + xu->desc.bNrInPins = num; + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins, + xu->desc.bControlSize); + + ret = len; + +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} +UVCG_EXTENSION_ATTR(b_nr_in_pins, bNrInPins); + +/* + * In addition to storing bControlSize, this function needs to realloc the + * memory for the bmControls array and additionally expand bLength. + */ +static ssize_t uvcg_extension_b_control_size_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + void *tmp_buf; + int ret; + u8 num; + + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + if (num == xu->desc.bControlSize) { + ret = len; + goto unlock; + } + + tmp_buf = krealloc_array(xu->desc.bmControls, num, sizeof(u8), + GFP_KERNEL | __GFP_ZERO); + if (!tmp_buf) { + ret = -ENOMEM; + goto unlock; + } + + xu->desc.bmControls = tmp_buf; + xu->desc.bControlSize = num; + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins, + xu->desc.bControlSize); + + ret = len; + +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +UVCG_EXTENSION_ATTR(b_control_size, bControlSize); + +static ssize_t uvcg_extension_guid_extension_code_show(struct config_item *item, + char *page) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + memcpy(page, xu->desc.guidExtensionCode, sizeof(xu->desc.guidExtensionCode)); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return sizeof(xu->desc.guidExtensionCode); +} + +static ssize_t uvcg_extension_guid_extension_code_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + int ret; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + memcpy(xu->desc.guidExtensionCode, page, + min(sizeof(xu->desc.guidExtensionCode), len)); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + ret = sizeof(xu->desc.guidExtensionCode); + + return ret; +} + +UVC_ATTR(uvcg_extension_, guid_extension_code, guidExtensionCode); + +static ssize_t uvcg_extension_ba_source_id_show(struct config_item *item, + char *page) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + char *pg = page; + int ret, i; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + for (ret = 0, i = 0; i < xu->desc.bNrInPins; ++i) { + ret += sprintf(pg, "%u\n", xu->desc.baSourceID[i]); + pg = page + ret; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return ret; +} + +static ssize_t uvcg_extension_ba_source_id_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + u8 *source_ids, *iter; + int ret, n = 0; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n, + sizeof(u8)); + if (ret) + goto unlock; + + iter = source_ids = kcalloc(n, sizeof(u8), GFP_KERNEL); + if (!source_ids) { + ret = -ENOMEM; + goto unlock; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &iter, + sizeof(u8)); + if (ret) { + kfree(source_ids); + goto unlock; + } + + kfree(xu->desc.baSourceID); + xu->desc.baSourceID = source_ids; + xu->desc.bNrInPins = n; + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins, + xu->desc.bControlSize); + + ret = len; + +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} +UVC_ATTR(uvcg_extension_, ba_source_id, baSourceID); + +static ssize_t uvcg_extension_bm_controls_show(struct config_item *item, + char *page) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + char *pg = page; + int ret, i; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + for (ret = 0, i = 0; i < xu->desc.bControlSize; ++i) { + ret += sprintf(pg, "0x%02x\n", xu->desc.bmControls[i]); + pg = page + ret; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return ret; +} + +static ssize_t uvcg_extension_bm_controls_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + u8 *bm_controls, *iter; + int ret, n = 0; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n, + sizeof(u8)); + if (ret) + goto unlock; + + iter = bm_controls = kcalloc(n, sizeof(u8), GFP_KERNEL); + if (!bm_controls) { + ret = -ENOMEM; + goto unlock; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &iter, + sizeof(u8)); + if (ret) { + kfree(bm_controls); + goto unlock; + } + + kfree(xu->desc.bmControls); + xu->desc.bmControls = bm_controls; + xu->desc.bControlSize = n; + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins, + xu->desc.bControlSize); + + ret = len; + +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +UVC_ATTR(uvcg_extension_, bm_controls, bmControls); + +static struct configfs_attribute *uvcg_extension_attrs[] = { + &uvcg_extension_attr_b_length, + &uvcg_extension_attr_b_unit_id, + &uvcg_extension_attr_b_num_controls, + &uvcg_extension_attr_b_nr_in_pins, + &uvcg_extension_attr_b_control_size, + &uvcg_extension_attr_guid_extension_code, + &uvcg_extension_attr_ba_source_id, + &uvcg_extension_attr_bm_controls, + &uvcg_extension_attr_i_extension, + NULL, +}; + +static void uvcg_extension_release(struct config_item *item) +{ + struct uvcg_extension *xu = container_of(item, struct uvcg_extension, item); + + kfree(xu); +} + +static int uvcg_extension_allow_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(src); + struct config_item *gadget_item; + struct gadget_string *string; + struct config_item *strings; + int ret = 0; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + /* Validate that the target of the link is an entry in strings/ */ + gadget_item = src->ci_parent->ci_parent->ci_parent->ci_parent->ci_parent; + strings = config_group_find_item(to_config_group(gadget_item), "strings"); + if (!strings || tgt->ci_parent->ci_parent != strings) { + ret = -EINVAL; + goto put_strings; + } + + string = to_gadget_string(tgt); + xu->string_descriptor_index = string->usb_string.id; + +put_strings: + config_item_put(strings); + mutex_unlock(su_mutex); + + return ret; +} + +static void uvcg_extension_drop_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(src); + struct config_item *opts_item; + struct f_uvc_opts *opts; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = src->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + xu->string_descriptor_index = 0; + + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); +} + +static const struct configfs_item_operations uvcg_extension_item_ops = { + .release = uvcg_extension_release, + .allow_link = uvcg_extension_allow_link, + .drop_link = uvcg_extension_drop_link, +}; + +static const struct config_item_type uvcg_extension_type = { + .ct_item_ops = &uvcg_extension_item_ops, + .ct_attrs = uvcg_extension_attrs, + .ct_owner = THIS_MODULE, +}; + +static void uvcg_extension_drop(struct config_group *group, struct config_item *item) +{ + struct uvcg_extension *xu = container_of(item, struct uvcg_extension, item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + + opts_item = group->cg_item.ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + config_item_put(item); + list_del(&xu->list); + kfree(xu->desc.baSourceID); + kfree(xu->desc.bmControls); + + mutex_unlock(&opts->lock); +} + +static struct config_item *uvcg_extension_make(struct config_group *group, const char *name) +{ + struct config_item *opts_item; + struct uvcg_extension *xu; + struct f_uvc_opts *opts; + + opts_item = group->cg_item.ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + xu = kzalloc_obj(*xu); + if (!xu) + return ERR_PTR(-ENOMEM); + + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(0, 0); + xu->desc.bDescriptorType = USB_DT_CS_INTERFACE; + xu->desc.bDescriptorSubType = UVC_VC_EXTENSION_UNIT; + xu->desc.bNumControls = 0; + xu->desc.bNrInPins = 0; + xu->desc.baSourceID = NULL; + xu->desc.bControlSize = 0; + xu->desc.bmControls = NULL; + + mutex_lock(&opts->lock); + + xu->desc.bUnitID = ++opts->last_unit_id; + + config_item_init_type_name(&xu->item, name, &uvcg_extension_type); + list_add_tail(&xu->list, &opts->extension_units); + + mutex_unlock(&opts->lock); + + return &xu->item; +} + +static const struct configfs_group_operations uvcg_extensions_grp_ops = { + .make_item = uvcg_extension_make, + .drop_item = uvcg_extension_drop, +}; + +static const struct uvcg_config_group_type uvcg_extensions_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_extensions_grp_ops, + .ct_owner = THIS_MODULE, + }, + .name = "extensions", +}; + +/* ----------------------------------------------------------------------------- + * control/class/{fs|ss} + */ + +struct uvcg_control_class_group { + struct config_group group; + const char *name; +}; + +static inline struct uvc_descriptor_header +**uvcg_get_ctl_class_arr(struct config_item *i, struct f_uvc_opts *o) +{ + struct uvcg_control_class_group *group = + container_of(i, struct uvcg_control_class_group, + group.cg_item); + + if (!strcmp(group->name, "fs")) + return o->uvc_fs_control_cls; + + if (!strcmp(group->name, "ss")) + return o->uvc_ss_control_cls; + + return NULL; +} + +static int uvcg_control_class_allow_link(struct config_item *src, + struct config_item *target) +{ + struct config_item *control, *header; + struct f_uvc_opts *opts; + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvc_descriptor_header **class_array; + struct uvcg_control_header *target_hdr; + int ret = -EINVAL; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + control = src->ci_parent->ci_parent; + header = config_group_find_item(to_config_group(control), "header"); + if (!header || target->ci_parent != header) + goto out; + + opts = to_f_uvc_opts(control->ci_parent); + + mutex_lock(&opts->lock); + + class_array = uvcg_get_ctl_class_arr(src, opts); + if (!class_array) + goto unlock; + if (opts->refcnt || class_array[0]) { + ret = -EBUSY; + goto unlock; + } + + target_hdr = to_uvcg_control_header(target); + ++target_hdr->linked; + class_array[0] = (struct uvc_descriptor_header *)&target_hdr->desc; + ret = 0; + +unlock: + mutex_unlock(&opts->lock); +out: + config_item_put(header); + mutex_unlock(su_mutex); + return ret; +} + +static void uvcg_control_class_drop_link(struct config_item *src, + struct config_item *target) +{ + struct config_item *control, *header; + struct f_uvc_opts *opts; + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvc_descriptor_header **class_array; + struct uvcg_control_header *target_hdr; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + control = src->ci_parent->ci_parent; + header = config_group_find_item(to_config_group(control), "header"); + if (!header || target->ci_parent != header) + goto out; + + opts = to_f_uvc_opts(control->ci_parent); + + mutex_lock(&opts->lock); + + class_array = uvcg_get_ctl_class_arr(src, opts); + if (!class_array || opts->refcnt) + goto unlock; + + target_hdr = to_uvcg_control_header(target); + --target_hdr->linked; + class_array[0] = NULL; + +unlock: + mutex_unlock(&opts->lock); +out: + config_item_put(header); + mutex_unlock(su_mutex); +} + +static const struct configfs_item_operations uvcg_control_class_item_ops = { + .release = uvcg_config_item_release, + .allow_link = uvcg_control_class_allow_link, + .drop_link = uvcg_control_class_drop_link, +}; + +static const struct config_item_type uvcg_control_class_type = { + .ct_item_ops = &uvcg_control_class_item_ops, + .ct_owner = THIS_MODULE, +}; + +/* ----------------------------------------------------------------------------- + * control/class + */ + +static int uvcg_control_class_create_children(struct config_group *parent) +{ + static const char * const names[] = { "fs", "ss" }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(names); ++i) { + struct uvcg_control_class_group *group; + + group = kzalloc_obj(*group); + if (!group) + return -ENOMEM; + + group->name = names[i]; + + config_group_init_type_name(&group->group, group->name, + &uvcg_control_class_type); + configfs_add_default_group(&group->group, parent); + } + + return 0; +} + +static const struct uvcg_config_group_type uvcg_control_class_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "class", + .create_children = uvcg_control_class_create_children, +}; + +/* ----------------------------------------------------------------------------- + * control + */ + +static ssize_t uvcg_default_control_b_interface_number_show( + struct config_item *item, char *page) +{ + struct config_group *group = to_config_group(item); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + int result = 0; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = item->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + result += sprintf(page, "%u\n", opts->control_interface); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return result; +} + +UVC_ATTR_RO(uvcg_default_control_, b_interface_number, bInterfaceNumber); + +static ssize_t uvcg_default_control_enable_interrupt_ep_show( + struct config_item *item, char *page) +{ + struct config_group *group = to_config_group(item); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + int result = 0; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = item->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + result += sprintf(page, "%u\n", opts->enable_interrupt_ep); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return result; +} + +static ssize_t uvcg_default_control_enable_interrupt_ep_store( + struct config_item *item, const char *page, size_t len) +{ + struct config_group *group = to_config_group(item); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + ssize_t ret; + u8 num; + + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = item->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + opts->enable_interrupt_ep = num; + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return len; +} +UVC_ATTR(uvcg_default_control_, enable_interrupt_ep, enable_interrupt_ep); + +static struct configfs_attribute *uvcg_default_control_attrs[] = { + &uvcg_default_control_attr_b_interface_number, + &uvcg_default_control_attr_enable_interrupt_ep, + NULL, +}; + +static const struct uvcg_config_group_type uvcg_control_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_default_control_attrs, + .ct_owner = THIS_MODULE, + }, + .name = "control", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_control_header_grp_type, + &uvcg_processing_grp_type, + &uvcg_terminal_grp_type, + &uvcg_control_class_grp_type, + &uvcg_extensions_grp_type, + NULL, + }, +}; + +/* ----------------------------------------------------------------------------- + * streaming/uncompressed + * streaming/mjpeg + * streaming/framebased + */ + +static const char * const uvcg_format_names[] = { + "uncompressed", + "mjpeg", + "framebased", +}; + +static struct uvcg_color_matching * +uvcg_format_get_default_color_match(struct config_item *streaming) +{ + struct config_item *color_matching_item, *cm_default; + struct uvcg_color_matching *color_match; + + color_matching_item = config_group_find_item(to_config_group(streaming), + "color_matching"); + if (!color_matching_item) + return NULL; + + cm_default = config_group_find_item(to_config_group(color_matching_item), + "default"); + config_item_put(color_matching_item); + if (!cm_default) + return NULL; + + color_match = to_uvcg_color_matching(to_config_group(cm_default)); + config_item_put(cm_default); + + return color_match; +} + +static int uvcg_format_allow_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvcg_color_matching *color_matching_desc; + struct config_item *streaming, *color_matching; + struct uvcg_format *fmt; + int ret = 0; + + mutex_lock(su_mutex); + + streaming = src->ci_parent->ci_parent; + color_matching = config_group_find_item(to_config_group(streaming), "color_matching"); + if (!color_matching || color_matching != tgt->ci_parent) { + ret = -EINVAL; + goto out_put_cm; + } + + fmt = to_uvcg_format(src); + + /* + * There's always a color matching descriptor associated with the format + * but without a symlink it should only ever be the default one. If it's + * not the default, there's already a symlink and we should bail out. + */ + color_matching_desc = uvcg_format_get_default_color_match(streaming); + if (fmt->color_matching != color_matching_desc) { + ret = -EBUSY; + goto out_put_cm; + } + + color_matching_desc->refcnt--; + + color_matching_desc = to_uvcg_color_matching(to_config_group(tgt)); + fmt->color_matching = color_matching_desc; + color_matching_desc->refcnt++; + +out_put_cm: + config_item_put(color_matching); + mutex_unlock(su_mutex); + + return ret; +} + +static void uvcg_format_drop_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvcg_color_matching *color_matching_desc; + struct config_item *streaming; + struct uvcg_format *fmt; + + mutex_lock(su_mutex); + + color_matching_desc = to_uvcg_color_matching(to_config_group(tgt)); + color_matching_desc->refcnt--; + + streaming = src->ci_parent->ci_parent; + color_matching_desc = uvcg_format_get_default_color_match(streaming); + + fmt = to_uvcg_format(src); + fmt->color_matching = color_matching_desc; + color_matching_desc->refcnt++; + + mutex_unlock(su_mutex); +} + +static const struct configfs_item_operations uvcg_format_item_operations = { + .release = uvcg_config_item_release, + .allow_link = uvcg_format_allow_link, + .drop_link = uvcg_format_drop_link, +}; + +static ssize_t uvcg_format_bma_controls_show(struct uvcg_format *f, char *page) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &f->group.cg_subsys->su_mutex; + int result, i; + char *pg = page; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = f->group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + result = sprintf(pg, "0x"); + pg += result; + for (i = 0; i < UVCG_STREAMING_CONTROL_SIZE; ++i) { + result += sprintf(pg, "%x\n", f->bmaControls[i]); + pg = page + result; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + return result; +} + +static ssize_t uvcg_format_bma_controls_store(struct uvcg_format *ch, + const char *page, size_t len) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &ch->group.cg_subsys->su_mutex; + int ret = -EINVAL; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + if (ch->linked || opts->refcnt) { + ret = -EBUSY; + goto end; + } + + if (len < 4 || *page != '0' || + (*(page + 1) != 'x' && *(page + 1) != 'X')) + goto end; + ret = hex2bin(ch->bmaControls, page + 2, 1); + if (ret < 0) + goto end; + ret = len; +end: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +/* ----------------------------------------------------------------------------- + * streaming/header/ + * streaming/header + */ + +static void uvcg_format_set_indices(struct config_group *fmt); + +static int uvcg_streaming_header_allow_link(struct config_item *src, + struct config_item *target) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + struct uvcg_streaming_header *src_hdr; + struct uvcg_format *target_fmt = NULL; + struct uvcg_format_ptr *format_ptr; + int i, ret = -EINVAL; + + src_hdr = to_uvcg_streaming_header(src); + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = src->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + if (src_hdr->linked) { + ret = -EBUSY; + goto out; + } + + /* + * Linking is only allowed to direct children of the format nodes + * (streaming/uncompressed or streaming/mjpeg nodes). First check that + * the grand-parent of the target matches the grand-parent of the source + * (the streaming node), and then verify that the target parent is a + * format node. + */ + if (src->ci_parent->ci_parent != target->ci_parent->ci_parent) + goto out; + + for (i = 0; i < ARRAY_SIZE(uvcg_format_names); ++i) { + if (!strcmp(target->ci_parent->ci_name, uvcg_format_names[i])) + break; + } + + if (i == ARRAY_SIZE(uvcg_format_names)) + goto out; + + target_fmt = container_of(to_config_group(target), struct uvcg_format, + group); + + if (!target_fmt) + goto out; + + uvcg_format_set_indices(to_config_group(target)); + + format_ptr = kzalloc_obj(*format_ptr); + if (!format_ptr) { + ret = -ENOMEM; + goto out; + } + ret = 0; + format_ptr->fmt = target_fmt; + list_add_tail(&format_ptr->entry, &src_hdr->formats); + ++src_hdr->num_fmt; + ++target_fmt->linked; + +out: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +static void uvcg_streaming_header_drop_link(struct config_item *src, + struct config_item *target) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + struct uvcg_streaming_header *src_hdr; + struct uvcg_format *target_fmt = NULL; + struct uvcg_format_ptr *format_ptr, *tmp; + + src_hdr = to_uvcg_streaming_header(src); + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = src->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + target_fmt = container_of(to_config_group(target), struct uvcg_format, + group); + + if (!target_fmt) + goto out; + + list_for_each_entry_safe(format_ptr, tmp, &src_hdr->formats, entry) + if (format_ptr->fmt == target_fmt) { + list_del(&format_ptr->entry); + kfree(format_ptr); + --src_hdr->num_fmt; + break; + } + + --target_fmt->linked; + +out: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); +} + +static const struct configfs_item_operations uvcg_streaming_header_item_ops = { + .release = uvcg_config_item_release, + .allow_link = uvcg_streaming_header_allow_link, + .drop_link = uvcg_streaming_header_drop_link, +}; + +#define UVCG_STREAMING_HEADER_ATTR(cname, aname, bits) \ +static ssize_t uvcg_streaming_header_##cname##_show( \ + struct config_item *item, char *page) \ +{ \ + struct uvcg_streaming_header *sh = to_uvcg_streaming_header(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &sh->item.ci_group->cg_subsys->su_mutex;\ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = sh->item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(sh->desc.aname));\ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +UVC_ATTR_RO(uvcg_streaming_header_, cname, aname) + +UVCG_STREAMING_HEADER_ATTR(bm_info, bmInfo, 8); +UVCG_STREAMING_HEADER_ATTR(b_terminal_link, bTerminalLink, 8); +UVCG_STREAMING_HEADER_ATTR(b_still_capture_method, bStillCaptureMethod, 8); +UVCG_STREAMING_HEADER_ATTR(b_trigger_support, bTriggerSupport, 8); +UVCG_STREAMING_HEADER_ATTR(b_trigger_usage, bTriggerUsage, 8); + +#undef UVCG_STREAMING_HEADER_ATTR + +static struct configfs_attribute *uvcg_streaming_header_attrs[] = { + &uvcg_streaming_header_attr_bm_info, + &uvcg_streaming_header_attr_b_terminal_link, + &uvcg_streaming_header_attr_b_still_capture_method, + &uvcg_streaming_header_attr_b_trigger_support, + &uvcg_streaming_header_attr_b_trigger_usage, + NULL, +}; + +static const struct config_item_type uvcg_streaming_header_type = { + .ct_item_ops = &uvcg_streaming_header_item_ops, + .ct_attrs = uvcg_streaming_header_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item +*uvcg_streaming_header_make(struct config_group *group, const char *name) +{ + struct uvcg_streaming_header *h; + + h = kzalloc_obj(*h); + if (!h) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&h->formats); + h->desc.bDescriptorType = USB_DT_CS_INTERFACE; + h->desc.bDescriptorSubType = UVC_VS_INPUT_HEADER; + h->desc.bTerminalLink = 3; + h->desc.bControlSize = UVCG_STREAMING_CONTROL_SIZE; + + config_item_init_type_name(&h->item, name, &uvcg_streaming_header_type); + + return &h->item; +} + +static const struct configfs_group_operations uvcg_streaming_header_grp_ops = { + .make_item = uvcg_streaming_header_make, +}; + +static const struct uvcg_config_group_type uvcg_streaming_header_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_streaming_header_grp_ops, + .ct_owner = THIS_MODULE, + }, + .name = "header", +}; + +/* ----------------------------------------------------------------------------- + * streaming/// + */ + +#define UVCG_FRAME_ATTR(cname, aname, bits) \ +static ssize_t uvcg_frame_##cname##_show(struct config_item *item, char *page)\ +{ \ + struct uvcg_frame *f = to_uvcg_frame(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = f->item.ci_parent->ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", f->frame.cname); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t uvcg_frame_##cname##_store(struct config_item *item, \ + const char *page, size_t len)\ +{ \ + struct uvcg_frame *f = to_uvcg_frame(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct uvcg_format *fmt; \ + struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\ + typeof(f->frame.cname) num; \ + int ret; \ + \ + ret = kstrtou##bits(page, 0, &num); \ + if (ret) \ + return ret; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = f->item.ci_parent->ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + fmt = to_uvcg_format(f->item.ci_parent); \ + \ + mutex_lock(&opts->lock); \ + if (fmt->linked || opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + f->frame.cname = num; \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(su_mutex); \ + return ret; \ +} \ + \ +UVC_ATTR(uvcg_frame_, cname, aname); + +static ssize_t uvcg_frame_b_frame_index_show(struct config_item *item, + char *page) +{ + struct uvcg_frame *f = to_uvcg_frame(item); + struct uvcg_format *fmt; + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct config_item *fmt_item; + struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex; + int result; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + fmt_item = f->item.ci_parent; + fmt = to_uvcg_format(fmt_item); + + if (!fmt->linked) { + result = -EBUSY; + goto out; + } + + opts_item = fmt_item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", f->frame.b_frame_index); + mutex_unlock(&opts->lock); + +out: + mutex_unlock(su_mutex); + return result; +} + +UVC_ATTR_RO(uvcg_frame_, b_frame_index, bFrameIndex); + +UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, 8); +UVCG_FRAME_ATTR(w_width, wWidth, 16); +UVCG_FRAME_ATTR(w_height, wHeight, 16); +UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, 32); +UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, 32); +UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize, 32); +UVCG_FRAME_ATTR(dw_default_frame_interval, dwDefaultFrameInterval, 32); +UVCG_FRAME_ATTR(dw_bytes_perline, dwBytesPerLine, 32); + +#undef UVCG_FRAME_ATTR + +static ssize_t uvcg_frame_dw_frame_interval_show(struct config_item *item, + char *page) +{ + struct uvcg_frame *frm = to_uvcg_frame(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &frm->item.ci_group->cg_subsys->su_mutex; + int result, i; + char *pg = page; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = frm->item.ci_parent->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + for (result = 0, i = 0; i < frm->frame.b_frame_interval_type; ++i) { + result += sprintf(pg, "%u\n", frm->dw_frame_interval[i]); + pg = page + result; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + return result; +} + +static ssize_t uvcg_frame_dw_frame_interval_store(struct config_item *item, + const char *page, size_t len) +{ + struct uvcg_frame *ch = to_uvcg_frame(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct uvcg_format *fmt; + struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex; + int ret = 0, n = 0; + u32 *frm_intrv, *tmp; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->item.ci_parent->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + fmt = to_uvcg_format(ch->item.ci_parent); + + mutex_lock(&opts->lock); + if (fmt->linked || opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n, sizeof(u32)); + if (ret) + goto end; + + tmp = frm_intrv = kcalloc(n, sizeof(u32), GFP_KERNEL); + if (!frm_intrv) { + ret = -ENOMEM; + goto end; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &tmp, sizeof(u32)); + if (ret) { + kfree(frm_intrv); + goto end; + } + + kfree(ch->dw_frame_interval); + ch->dw_frame_interval = frm_intrv; + ch->frame.b_frame_interval_type = n; + sort(ch->dw_frame_interval, n, sizeof(*ch->dw_frame_interval), + uvcg_config_compare_u32, NULL); + ret = len; + +end: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +UVC_ATTR(uvcg_frame_, dw_frame_interval, dwFrameInterval); + +static struct configfs_attribute *uvcg_frame_attrs1[] = { + &uvcg_frame_attr_b_frame_index, + &uvcg_frame_attr_bm_capabilities, + &uvcg_frame_attr_w_width, + &uvcg_frame_attr_w_height, + &uvcg_frame_attr_dw_min_bit_rate, + &uvcg_frame_attr_dw_max_bit_rate, + &uvcg_frame_attr_dw_max_video_frame_buffer_size, + &uvcg_frame_attr_dw_default_frame_interval, + &uvcg_frame_attr_dw_frame_interval, + NULL, +}; + +static struct configfs_attribute *uvcg_frame_attrs2[] = { + &uvcg_frame_attr_b_frame_index, + &uvcg_frame_attr_bm_capabilities, + &uvcg_frame_attr_w_width, + &uvcg_frame_attr_w_height, + &uvcg_frame_attr_dw_min_bit_rate, + &uvcg_frame_attr_dw_max_bit_rate, + &uvcg_frame_attr_dw_default_frame_interval, + &uvcg_frame_attr_dw_frame_interval, + &uvcg_frame_attr_dw_bytes_perline, + NULL, +}; + +static const struct config_item_type uvcg_frame_type1 = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_frame_attrs1, + .ct_owner = THIS_MODULE, +}; + +static const struct config_item_type uvcg_frame_type2 = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_frame_attrs2, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *uvcg_frame_make(struct config_group *group, + const char *name) +{ + struct uvcg_frame *h; + struct uvcg_format *fmt; + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct uvcg_frame_ptr *frame_ptr; + + h = kzalloc_obj(*h); + if (!h) + return ERR_PTR(-ENOMEM); + + h->frame.b_descriptor_type = USB_DT_CS_INTERFACE; + h->frame.b_frame_index = 1; + h->frame.w_width = 640; + h->frame.w_height = 360; + h->frame.dw_min_bit_rate = 18432000; + h->frame.dw_max_bit_rate = 55296000; + h->frame.dw_max_video_frame_buffer_size = 460800; + h->frame.dw_default_frame_interval = 666666; + h->frame.dw_bytes_perline = 0; + + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + fmt = to_uvcg_format(&group->cg_item); + if (fmt->type == UVCG_UNCOMPRESSED) { + h->frame.b_descriptor_subtype = UVC_VS_FRAME_UNCOMPRESSED; + h->fmt_type = UVCG_UNCOMPRESSED; + } else if (fmt->type == UVCG_MJPEG) { + h->frame.b_descriptor_subtype = UVC_VS_FRAME_MJPEG; + h->fmt_type = UVCG_MJPEG; + } else if (fmt->type == UVCG_FRAMEBASED) { + h->frame.b_descriptor_subtype = UVC_VS_FRAME_FRAME_BASED; + h->fmt_type = UVCG_FRAMEBASED; + } else { + mutex_unlock(&opts->lock); + kfree(h); + return ERR_PTR(-EINVAL); + } + + frame_ptr = kzalloc_obj(*frame_ptr); + if (!frame_ptr) { + mutex_unlock(&opts->lock); + kfree(h); + return ERR_PTR(-ENOMEM); + } + + frame_ptr->frm = h; + list_add_tail(&frame_ptr->entry, &fmt->frames); + ++fmt->num_frames; + mutex_unlock(&opts->lock); + + if (fmt->type == UVCG_FRAMEBASED) + config_item_init_type_name(&h->item, name, &uvcg_frame_type2); + else + config_item_init_type_name(&h->item, name, &uvcg_frame_type1); + + return &h->item; +} + +static void uvcg_frame_drop(struct config_group *group, struct config_item *item) +{ + struct uvcg_format *fmt; + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct uvcg_frame *target_frm = NULL; + struct uvcg_frame_ptr *frame_ptr, *tmp; + + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + target_frm = container_of(item, struct uvcg_frame, item); + fmt = to_uvcg_format(&group->cg_item); + + list_for_each_entry_safe(frame_ptr, tmp, &fmt->frames, entry) + if (frame_ptr->frm == target_frm) { + list_del(&frame_ptr->entry); + kfree(frame_ptr); + --fmt->num_frames; + break; + } + mutex_unlock(&opts->lock); + + config_item_put(item); +} + +static void uvcg_format_set_indices(struct config_group *fmt) +{ + struct config_item *ci; + unsigned int i = 1; + + list_for_each_entry(ci, &fmt->cg_children, ci_entry) { + struct uvcg_frame *frm; + + frm = to_uvcg_frame(ci); + frm->frame.b_frame_index = i++; + } +} + +/* ----------------------------------------------------------------------------- + * streaming/uncompressed/ + */ + +static const struct configfs_group_operations uvcg_uncompressed_group_ops = { + .make_item = uvcg_frame_make, + .drop_item = uvcg_frame_drop, +}; + +static ssize_t uvcg_uncompressed_guid_format_show(struct config_item *item, + char *page) +{ + struct uvcg_uncompressed *ch = to_uvcg_uncompressed(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + memcpy(page, ch->desc.guidFormat, sizeof(ch->desc.guidFormat)); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return sizeof(ch->desc.guidFormat); +} + +static ssize_t uvcg_uncompressed_guid_format_store(struct config_item *item, + const char *page, size_t len) +{ + struct uvcg_uncompressed *ch = to_uvcg_uncompressed(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex; + const struct uvc_format_desc *format; + u8 tmpguidFormat[sizeof(ch->desc.guidFormat)]; + int ret; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + if (ch->fmt.linked || opts->refcnt) { + ret = -EBUSY; + goto end; + } + + memcpy(tmpguidFormat, page, + min(sizeof(tmpguidFormat), len)); + + format = uvc_format_by_guid(tmpguidFormat); + if (!format) { + ret = -EINVAL; + goto end; + } + + memcpy(ch->desc.guidFormat, tmpguidFormat, + min(sizeof(ch->desc.guidFormat), len)); + ret = sizeof(ch->desc.guidFormat); + +end: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +UVC_ATTR(uvcg_uncompressed_, guid_format, guidFormat); + +#define UVCG_UNCOMPRESSED_ATTR_RO(cname, aname, bits) \ +static ssize_t uvcg_uncompressed_##cname##_show( \ + struct config_item *item, char *page) \ +{ \ + struct uvcg_uncompressed *u = to_uvcg_uncompressed(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +UVC_ATTR_RO(uvcg_uncompressed_, cname, aname); + +#define UVCG_UNCOMPRESSED_ATTR(cname, aname, bits) \ +static ssize_t uvcg_uncompressed_##cname##_show( \ + struct config_item *item, char *page) \ +{ \ + struct uvcg_uncompressed *u = to_uvcg_uncompressed(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t \ +uvcg_uncompressed_##cname##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct uvcg_uncompressed *u = to_uvcg_uncompressed(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int ret; \ + u8 num; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + if (u->fmt.linked || opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou8(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + /* index values in uvc are never 0 */ \ + if (!num) { \ + ret = -EINVAL; \ + goto end; \ + } \ + \ + u->desc.aname = num; \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(su_mutex); \ + return ret; \ +} \ + \ +UVC_ATTR(uvcg_uncompressed_, cname, aname); + +UVCG_UNCOMPRESSED_ATTR_RO(b_format_index, bFormatIndex, 8); +UVCG_UNCOMPRESSED_ATTR(b_bits_per_pixel, bBitsPerPixel, 8); +UVCG_UNCOMPRESSED_ATTR(b_default_frame_index, bDefaultFrameIndex, 8); +UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, 8); +UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, 8); +UVCG_UNCOMPRESSED_ATTR_RO(bm_interlace_flags, bmInterlaceFlags, 8); + +#undef UVCG_UNCOMPRESSED_ATTR +#undef UVCG_UNCOMPRESSED_ATTR_RO + +static inline ssize_t +uvcg_uncompressed_bma_controls_show(struct config_item *item, char *page) +{ + struct uvcg_uncompressed *unc = to_uvcg_uncompressed(item); + return uvcg_format_bma_controls_show(&unc->fmt, page); +} + +static inline ssize_t +uvcg_uncompressed_bma_controls_store(struct config_item *item, + const char *page, size_t len) +{ + struct uvcg_uncompressed *unc = to_uvcg_uncompressed(item); + return uvcg_format_bma_controls_store(&unc->fmt, page, len); +} + +UVC_ATTR(uvcg_uncompressed_, bma_controls, bmaControls); + +static struct configfs_attribute *uvcg_uncompressed_attrs[] = { + &uvcg_uncompressed_attr_b_format_index, + &uvcg_uncompressed_attr_guid_format, + &uvcg_uncompressed_attr_b_bits_per_pixel, + &uvcg_uncompressed_attr_b_default_frame_index, + &uvcg_uncompressed_attr_b_aspect_ratio_x, + &uvcg_uncompressed_attr_b_aspect_ratio_y, + &uvcg_uncompressed_attr_bm_interlace_flags, + &uvcg_uncompressed_attr_bma_controls, + NULL, +}; + +static const struct config_item_type uvcg_uncompressed_type = { + .ct_item_ops = &uvcg_format_item_operations, + .ct_group_ops = &uvcg_uncompressed_group_ops, + .ct_attrs = uvcg_uncompressed_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_uncompressed_make(struct config_group *group, + const char *name) +{ + static char guid[] = { + 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 + }; + struct uvcg_color_matching *color_match; + struct config_item *streaming; + struct uvcg_uncompressed *h; + + streaming = group->cg_item.ci_parent; + color_match = uvcg_format_get_default_color_match(streaming); + if (!color_match) + return ERR_PTR(-EINVAL); + + h = kzalloc_obj(*h); + if (!h) + return ERR_PTR(-ENOMEM); + + h->desc.bLength = UVC_DT_FORMAT_UNCOMPRESSED_SIZE; + h->desc.bDescriptorType = USB_DT_CS_INTERFACE; + h->desc.bDescriptorSubType = UVC_VS_FORMAT_UNCOMPRESSED; + memcpy(h->desc.guidFormat, guid, sizeof(guid)); + h->desc.bBitsPerPixel = 16; + h->desc.bDefaultFrameIndex = 1; + h->desc.bAspectRatioX = 0; + h->desc.bAspectRatioY = 0; + h->desc.bmInterlaceFlags = 0; + h->desc.bCopyProtect = 0; + + INIT_LIST_HEAD(&h->fmt.frames); + h->fmt.type = UVCG_UNCOMPRESSED; + h->fmt.color_matching = color_match; + color_match->refcnt++; + config_group_init_type_name(&h->fmt.group, name, + &uvcg_uncompressed_type); + + return &h->fmt.group; +} + +static const struct configfs_group_operations uvcg_uncompressed_grp_ops = { + .make_group = uvcg_uncompressed_make, +}; + +static const struct uvcg_config_group_type uvcg_uncompressed_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_uncompressed_grp_ops, + .ct_owner = THIS_MODULE, + }, + .name = "uncompressed", +}; + +/* ----------------------------------------------------------------------------- + * streaming/mjpeg/ + */ + +static const struct configfs_group_operations uvcg_mjpeg_group_ops = { + .make_item = uvcg_frame_make, + .drop_item = uvcg_frame_drop, +}; + +#define UVCG_MJPEG_ATTR_RO(cname, aname, bits) \ +static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\ +{ \ + struct uvcg_mjpeg *u = to_uvcg_mjpeg(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +UVC_ATTR_RO(uvcg_mjpeg_, cname, aname) + +#define UVCG_MJPEG_ATTR(cname, aname, bits) \ +static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\ +{ \ + struct uvcg_mjpeg *u = to_uvcg_mjpeg(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t \ +uvcg_mjpeg_##cname##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct uvcg_mjpeg *u = to_uvcg_mjpeg(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int ret; \ + u8 num; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + if (u->fmt.linked || opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou8(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + /* index values in uvc are never 0 */ \ + if (!num) { \ + ret = -EINVAL; \ + goto end; \ + } \ + \ + u->desc.aname = num; \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(su_mutex); \ + return ret; \ +} \ + \ +UVC_ATTR(uvcg_mjpeg_, cname, aname) + +UVCG_MJPEG_ATTR_RO(b_format_index, bFormatIndex, 8); +UVCG_MJPEG_ATTR(b_default_frame_index, bDefaultFrameIndex, 8); +UVCG_MJPEG_ATTR_RO(bm_flags, bmFlags, 8); +UVCG_MJPEG_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, 8); +UVCG_MJPEG_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, 8); +UVCG_MJPEG_ATTR_RO(bm_interlace_flags, bmInterlaceFlags, 8); + +#undef UVCG_MJPEG_ATTR +#undef UVCG_MJPEG_ATTR_RO + +static inline ssize_t +uvcg_mjpeg_bma_controls_show(struct config_item *item, char *page) +{ + struct uvcg_mjpeg *u = to_uvcg_mjpeg(item); + return uvcg_format_bma_controls_show(&u->fmt, page); +} + +static inline ssize_t +uvcg_mjpeg_bma_controls_store(struct config_item *item, + const char *page, size_t len) +{ + struct uvcg_mjpeg *u = to_uvcg_mjpeg(item); + return uvcg_format_bma_controls_store(&u->fmt, page, len); +} + +UVC_ATTR(uvcg_mjpeg_, bma_controls, bmaControls); + +static struct configfs_attribute *uvcg_mjpeg_attrs[] = { + &uvcg_mjpeg_attr_b_format_index, + &uvcg_mjpeg_attr_b_default_frame_index, + &uvcg_mjpeg_attr_bm_flags, + &uvcg_mjpeg_attr_b_aspect_ratio_x, + &uvcg_mjpeg_attr_b_aspect_ratio_y, + &uvcg_mjpeg_attr_bm_interlace_flags, + &uvcg_mjpeg_attr_bma_controls, + NULL, +}; + +static const struct config_item_type uvcg_mjpeg_type = { + .ct_item_ops = &uvcg_format_item_operations, + .ct_group_ops = &uvcg_mjpeg_group_ops, + .ct_attrs = uvcg_mjpeg_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_mjpeg_make(struct config_group *group, + const char *name) +{ + struct uvcg_color_matching *color_match; + struct config_item *streaming; + struct uvcg_mjpeg *h; + + streaming = group->cg_item.ci_parent; + color_match = uvcg_format_get_default_color_match(streaming); + if (!color_match) + return ERR_PTR(-EINVAL); + + h = kzalloc_obj(*h); + if (!h) + return ERR_PTR(-ENOMEM); + + h->desc.bLength = UVC_DT_FORMAT_MJPEG_SIZE; + h->desc.bDescriptorType = USB_DT_CS_INTERFACE; + h->desc.bDescriptorSubType = UVC_VS_FORMAT_MJPEG; + h->desc.bDefaultFrameIndex = 1; + h->desc.bAspectRatioX = 0; + h->desc.bAspectRatioY = 0; + h->desc.bmInterlaceFlags = 0; + h->desc.bCopyProtect = 0; + + INIT_LIST_HEAD(&h->fmt.frames); + h->fmt.type = UVCG_MJPEG; + h->fmt.color_matching = color_match; + color_match->refcnt++; + config_group_init_type_name(&h->fmt.group, name, + &uvcg_mjpeg_type); + + return &h->fmt.group; +} + +static const struct configfs_group_operations uvcg_mjpeg_grp_ops = { + .make_group = uvcg_mjpeg_make, +}; + +static const struct uvcg_config_group_type uvcg_mjpeg_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_mjpeg_grp_ops, + .ct_owner = THIS_MODULE, + }, + .name = "mjpeg", +}; + +/* ----------------------------------------------------------------------------- + * streaming/framebased/ + */ + +static const struct configfs_group_operations uvcg_framebased_group_ops = { + .make_item = uvcg_frame_make, + .drop_item = uvcg_frame_drop, +}; + +#define UVCG_FRAMEBASED_ATTR_RO(cname, aname, bits) \ +static ssize_t uvcg_framebased_##cname##_show(struct config_item *item, \ + char *page) \ +{ \ + struct uvcg_framebased *u = to_uvcg_framebased(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +UVC_ATTR_RO(uvcg_framebased_, cname, aname) + +#define UVCG_FRAMEBASED_ATTR(cname, aname, bits) \ +static ssize_t uvcg_framebased_##cname##_show(struct config_item *item, \ + char *page) \ +{ \ + struct uvcg_framebased *u = to_uvcg_framebased(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t \ +uvcg_framebased_##cname##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct uvcg_framebased *u = to_uvcg_framebased(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int ret; \ + u8 num; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + if (u->fmt.linked || opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou8(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > 255) { \ + ret = -EINVAL; \ + goto end; \ + } \ + u->desc.aname = num; \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(su_mutex); \ + return ret; \ +} \ + \ +UVC_ATTR(uvcg_framebased_, cname, aname) + +UVCG_FRAMEBASED_ATTR_RO(b_format_index, bFormatIndex, 8); +UVCG_FRAMEBASED_ATTR_RO(b_bits_per_pixel, bBitsPerPixel, 8); +UVCG_FRAMEBASED_ATTR(b_default_frame_index, bDefaultFrameIndex, 8); +UVCG_FRAMEBASED_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, 8); +UVCG_FRAMEBASED_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, 8); +UVCG_FRAMEBASED_ATTR_RO(bm_interface_flags, bmInterfaceFlags, 8); + +#undef UVCG_FRAMEBASED_ATTR +#undef UVCG_FRAMEBASED_ATTR_RO + +static ssize_t uvcg_framebased_guid_format_show(struct config_item *item, + char *page) +{ + struct uvcg_framebased *ch = to_uvcg_framebased(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + memcpy(page, ch->desc.guidFormat, sizeof(ch->desc.guidFormat)); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return sizeof(ch->desc.guidFormat); +} + +static ssize_t uvcg_framebased_guid_format_store(struct config_item *item, + const char *page, size_t len) +{ + struct uvcg_framebased *ch = to_uvcg_framebased(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex; + int ret; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + if (ch->fmt.linked || opts->refcnt) { + ret = -EBUSY; + goto end; + } + + memcpy(ch->desc.guidFormat, page, + min(sizeof(ch->desc.guidFormat), len)); + ret = sizeof(ch->desc.guidFormat); + +end: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +UVC_ATTR(uvcg_framebased_, guid_format, guidFormat); + +static inline ssize_t +uvcg_framebased_bma_controls_show(struct config_item *item, char *page) +{ + struct uvcg_framebased *u = to_uvcg_framebased(item); + + return uvcg_format_bma_controls_show(&u->fmt, page); +} + +static inline ssize_t +uvcg_framebased_bma_controls_store(struct config_item *item, + const char *page, size_t len) +{ + struct uvcg_framebased *u = to_uvcg_framebased(item); + + return uvcg_format_bma_controls_store(&u->fmt, page, len); +} + +UVC_ATTR(uvcg_framebased_, bma_controls, bmaControls); + +static struct configfs_attribute *uvcg_framebased_attrs[] = { + &uvcg_framebased_attr_b_format_index, + &uvcg_framebased_attr_b_default_frame_index, + &uvcg_framebased_attr_b_bits_per_pixel, + &uvcg_framebased_attr_b_aspect_ratio_x, + &uvcg_framebased_attr_b_aspect_ratio_y, + &uvcg_framebased_attr_bm_interface_flags, + &uvcg_framebased_attr_bma_controls, + &uvcg_framebased_attr_guid_format, + NULL, +}; + +static const struct config_item_type uvcg_framebased_type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_framebased_group_ops, + .ct_attrs = uvcg_framebased_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_framebased_make(struct config_group *group, + const char *name) +{ + static char guid[] = { /*Declear frame based as H264 format*/ + 'H', '2', '6', '4', 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 + }; + struct uvcg_color_matching *color_match; + struct config_item *streaming; + struct uvcg_framebased *h; + + streaming = group->cg_item.ci_parent; + color_match = uvcg_format_get_default_color_match(streaming); + if (!color_match) + return ERR_PTR(-EINVAL); + + h = kzalloc_obj(*h); + if (!h) + return ERR_PTR(-ENOMEM); + + h->desc.bLength = UVC_DT_FORMAT_FRAMEBASED_SIZE; + h->desc.bDescriptorType = USB_DT_CS_INTERFACE; + h->desc.bDescriptorSubType = UVC_VS_FORMAT_FRAME_BASED; + memcpy(h->desc.guidFormat, guid, sizeof(guid)); + h->desc.bBitsPerPixel = 0; + h->desc.bDefaultFrameIndex = 1; + h->desc.bAspectRatioX = 0; + h->desc.bAspectRatioY = 0; + h->desc.bmInterfaceFlags = 0; + h->desc.bCopyProtect = 0; + h->desc.bVariableSize = 1; + + INIT_LIST_HEAD(&h->fmt.frames); + h->fmt.type = UVCG_FRAMEBASED; + + h->fmt.color_matching = color_match; + color_match->refcnt++; + config_group_init_type_name(&h->fmt.group, name, + &uvcg_framebased_type); + + return &h->fmt.group; +} + +static const struct configfs_group_operations uvcg_framebased_grp_ops = { + .make_group = uvcg_framebased_make, +}; + +static const struct uvcg_config_group_type uvcg_framebased_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_framebased_grp_ops, + .ct_owner = THIS_MODULE, + }, + .name = "framebased", +}; + +/* ----------------------------------------------------------------------------- + * streaming/color_matching/default + */ + +#define UVCG_COLOR_MATCHING_ATTR(cname, aname, bits) \ +static ssize_t uvcg_color_matching_##cname##_show( \ + struct config_item *item, char *page) \ +{ \ + struct config_group *group = to_config_group(item); \ + struct uvcg_color_matching *color_match = \ + to_uvcg_color_matching(group); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", \ + le##bits##_to_cpu(color_match->desc.aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t uvcg_color_matching_##cname##_store( \ + struct config_item *item, const char *page, size_t len) \ +{ \ + struct config_group *group = to_config_group(item); \ + struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ + struct uvcg_color_matching *color_match = \ + to_uvcg_color_matching(group); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + int ret; \ + u##bits num; \ + \ + ret = kstrtou##bits(page, 0, &num); \ + if (ret) \ + return ret; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + if (color_match->refcnt) { \ + ret = -EBUSY; \ + goto unlock_su; \ + } \ + \ + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + \ + color_match->desc.aname = num; \ + ret = len; \ + \ + mutex_unlock(&opts->lock); \ +unlock_su: \ + mutex_unlock(su_mutex); \ + \ + return ret; \ +} \ +UVC_ATTR(uvcg_color_matching_, cname, aname) + +UVCG_COLOR_MATCHING_ATTR(b_color_primaries, bColorPrimaries, 8); +UVCG_COLOR_MATCHING_ATTR(b_transfer_characteristics, bTransferCharacteristics, 8); +UVCG_COLOR_MATCHING_ATTR(b_matrix_coefficients, bMatrixCoefficients, 8); + +#undef UVCG_COLOR_MATCHING_ATTR + +static struct configfs_attribute *uvcg_color_matching_attrs[] = { + &uvcg_color_matching_attr_b_color_primaries, + &uvcg_color_matching_attr_b_transfer_characteristics, + &uvcg_color_matching_attr_b_matrix_coefficients, + NULL, +}; + +static void uvcg_color_matching_release(struct config_item *item) +{ + struct uvcg_color_matching *color_match = + to_uvcg_color_matching(to_config_group(item)); + + kfree(color_match); +} + +static const struct configfs_item_operations uvcg_color_matching_item_ops = { + .release = uvcg_color_matching_release, +}; + +static const struct config_item_type uvcg_color_matching_type = { + .ct_item_ops = &uvcg_color_matching_item_ops, + .ct_attrs = uvcg_color_matching_attrs, + .ct_owner = THIS_MODULE, +}; + +/* ----------------------------------------------------------------------------- + * streaming/color_matching + */ + +static struct config_group *uvcg_color_matching_make(struct config_group *group, + const char *name) +{ + struct uvcg_color_matching *color_match; + + color_match = kzalloc_obj(*color_match); + if (!color_match) + return ERR_PTR(-ENOMEM); + + color_match->desc.bLength = UVC_DT_COLOR_MATCHING_SIZE; + color_match->desc.bDescriptorType = USB_DT_CS_INTERFACE; + color_match->desc.bDescriptorSubType = UVC_VS_COLORFORMAT; + + config_group_init_type_name(&color_match->group, name, + &uvcg_color_matching_type); + + return &color_match->group; +} + +static const struct configfs_group_operations uvcg_color_matching_grp_group_ops = { + .make_group = uvcg_color_matching_make, +}; + +static int uvcg_color_matching_create_children(struct config_group *parent) +{ + struct uvcg_color_matching *color_match; + + color_match = kzalloc_obj(*color_match); + if (!color_match) + return -ENOMEM; + + color_match->desc.bLength = UVC_DT_COLOR_MATCHING_SIZE; + color_match->desc.bDescriptorType = USB_DT_CS_INTERFACE; + color_match->desc.bDescriptorSubType = UVC_VS_COLORFORMAT; + color_match->desc.bColorPrimaries = UVC_COLOR_PRIMARIES_BT_709_SRGB; + color_match->desc.bTransferCharacteristics = UVC_TRANSFER_CHARACTERISTICS_BT_709; + color_match->desc.bMatrixCoefficients = UVC_MATRIX_COEFFICIENTS_SMPTE_170M; + + config_group_init_type_name(&color_match->group, "default", + &uvcg_color_matching_type); + configfs_add_default_group(&color_match->group, parent); + + return 0; +} + +static const struct uvcg_config_group_type uvcg_color_matching_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_color_matching_grp_group_ops, + .ct_owner = THIS_MODULE, + }, + .name = "color_matching", + .create_children = uvcg_color_matching_create_children, +}; + +/* ----------------------------------------------------------------------------- + * streaming/class/{fs|hs|ss} + */ + +struct uvcg_streaming_class_group { + struct config_group group; + const char *name; +}; + +static inline struct uvc_descriptor_header +***__uvcg_get_stream_class_arr(struct config_item *i, struct f_uvc_opts *o) +{ + struct uvcg_streaming_class_group *group = + container_of(i, struct uvcg_streaming_class_group, + group.cg_item); + + if (!strcmp(group->name, "fs")) + return &o->uvc_fs_streaming_cls; + + if (!strcmp(group->name, "hs")) + return &o->uvc_hs_streaming_cls; + + if (!strcmp(group->name, "ss")) + return &o->uvc_ss_streaming_cls; + + return NULL; +} + +enum uvcg_strm_type { + UVCG_HEADER = 0, + UVCG_FORMAT, + UVCG_FRAME, + UVCG_COLOR_MATCHING, +}; + +/* + * Iterate over a hierarchy of streaming descriptors' config items. + * The items are created by the user with configfs. + * + * It "processes" the header pointed to by @priv1, then for each format + * that follows the header "processes" the format itself and then for + * each frame inside a format "processes" the frame. + * + * As a "processing" function the @fun is used. + * + * __uvcg_iter_strm_cls() is used in two context: first, to calculate + * the amount of memory needed for an array of streaming descriptors + * and second, to actually fill the array. + * + * @h: streaming header pointer + * @priv2: an "inout" parameter (the caller might want to see the changes to it) + * @priv3: an "inout" parameter (the caller might want to see the changes to it) + * @fun: callback function for processing each level of the hierarchy + */ +static int __uvcg_iter_strm_cls(struct uvcg_streaming_header *h, + void *priv2, void *priv3, + int (*fun)(void *, void *, void *, int, enum uvcg_strm_type type)) +{ + struct uvcg_format_ptr *f; + struct config_group *grp; + struct config_item *item; + struct uvcg_frame *frm; + int ret, i, j; + + if (!fun) + return -EINVAL; + + i = j = 0; + ret = fun(h, priv2, priv3, 0, UVCG_HEADER); + if (ret) + return ret; + list_for_each_entry(f, &h->formats, entry) { + ret = fun(f->fmt, priv2, priv3, i++, UVCG_FORMAT); + if (ret) + return ret; + grp = &f->fmt->group; + j = 0; + list_for_each_entry(item, &grp->cg_children, ci_entry) { + frm = to_uvcg_frame(item); + ret = fun(frm, priv2, priv3, j++, UVCG_FRAME); + if (ret) + return ret; + } + + ret = fun(f->fmt->color_matching, priv2, priv3, 0, + UVCG_COLOR_MATCHING); + if (ret) + return ret; + } + + return ret; +} + +/* + * Count how many bytes are needed for an array of streaming descriptors. + * + * @priv1: pointer to a header, format or frame + * @priv2: inout parameter, accumulated size of the array + * @priv3: inout parameter, accumulated number of the array elements + * @n: unused, this function's prototype must match @fun in __uvcg_iter_strm_cls + */ +static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n, + enum uvcg_strm_type type) +{ + size_t *size = priv2; + size_t *count = priv3; + + switch (type) { + case UVCG_HEADER: { + struct uvcg_streaming_header *h = priv1; + + *size += sizeof(h->desc); + /* bmaControls */ + *size += h->num_fmt * UVCG_STREAMING_CONTROL_SIZE; + } + break; + case UVCG_FORMAT: { + struct uvcg_format *fmt = priv1; + + if (fmt->type == UVCG_UNCOMPRESSED) { + struct uvcg_uncompressed *u = + container_of(fmt, struct uvcg_uncompressed, + fmt); + + *size += sizeof(u->desc); + } else if (fmt->type == UVCG_MJPEG) { + struct uvcg_mjpeg *m = + container_of(fmt, struct uvcg_mjpeg, fmt); + + *size += sizeof(m->desc); + } else if (fmt->type == UVCG_FRAMEBASED) { + struct uvcg_framebased *f = + container_of(fmt, struct uvcg_framebased, fmt); + + *size += sizeof(f->desc); + } else { + return -EINVAL; + } + } + break; + case UVCG_FRAME: { + struct uvcg_frame *frm = priv1; + int sz = sizeof(frm->dw_frame_interval); + + *size += sizeof(frm->frame); + /* + * framebased has duplicate member with uncompressed and + * mjpeg, so minus it + */ + *size -= sizeof(u32); + *size += frm->frame.b_frame_interval_type * sz; + } + break; + case UVCG_COLOR_MATCHING: { + struct uvcg_color_matching *color_match = priv1; + + *size += sizeof(color_match->desc); + } + break; + } + + ++*count; + + return 0; +} + +static int __uvcg_copy_framebased_desc(void *dest, struct uvcg_frame *frm, + int sz) +{ + struct uvc_frame_framebased *desc = dest; + + desc->bLength = frm->frame.b_length; + desc->bDescriptorType = frm->frame.b_descriptor_type; + desc->bDescriptorSubType = frm->frame.b_descriptor_subtype; + desc->bFrameIndex = frm->frame.b_frame_index; + desc->bmCapabilities = frm->frame.bm_capabilities; + desc->wWidth = frm->frame.w_width; + desc->wHeight = frm->frame.w_height; + desc->dwMinBitRate = frm->frame.dw_min_bit_rate; + desc->dwMaxBitRate = frm->frame.dw_max_bit_rate; + desc->dwDefaultFrameInterval = frm->frame.dw_default_frame_interval; + desc->bFrameIntervalType = frm->frame.b_frame_interval_type; + desc->dwBytesPerLine = frm->frame.dw_bytes_perline; + + return 0; +} + +/* + * Fill an array of streaming descriptors. + * + * @priv1: pointer to a header, format or frame + * @priv2: inout parameter, pointer into a block of memory + * @priv3: inout parameter, pointer to a 2-dimensional array + */ +static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n, + enum uvcg_strm_type type) +{ + void **dest = priv2; + struct uvc_descriptor_header ***array = priv3; + size_t sz; + + **array = *dest; + ++*array; + + switch (type) { + case UVCG_HEADER: { + struct uvc_input_header_descriptor *ihdr = *dest; + struct uvcg_streaming_header *h = priv1; + struct uvcg_format_ptr *f; + + memcpy(*dest, &h->desc, sizeof(h->desc)); + *dest += sizeof(h->desc); + sz = UVCG_STREAMING_CONTROL_SIZE; + list_for_each_entry(f, &h->formats, entry) { + memcpy(*dest, f->fmt->bmaControls, sz); + *dest += sz; + } + ihdr->bLength = sizeof(h->desc) + h->num_fmt * sz; + ihdr->bNumFormats = h->num_fmt; + } + break; + case UVCG_FORMAT: { + struct uvcg_format *fmt = priv1; + + if (fmt->type == UVCG_UNCOMPRESSED) { + struct uvcg_uncompressed *u = + container_of(fmt, struct uvcg_uncompressed, + fmt); + + u->desc.bFormatIndex = n + 1; + u->desc.bNumFrameDescriptors = fmt->num_frames; + memcpy(*dest, &u->desc, sizeof(u->desc)); + *dest += sizeof(u->desc); + } else if (fmt->type == UVCG_MJPEG) { + struct uvcg_mjpeg *m = + container_of(fmt, struct uvcg_mjpeg, fmt); + + m->desc.bFormatIndex = n + 1; + m->desc.bNumFrameDescriptors = fmt->num_frames; + memcpy(*dest, &m->desc, sizeof(m->desc)); + *dest += sizeof(m->desc); + } else if (fmt->type == UVCG_FRAMEBASED) { + struct uvcg_framebased *f = + container_of(fmt, struct uvcg_framebased, + fmt); + + f->desc.bFormatIndex = n + 1; + f->desc.bNumFrameDescriptors = fmt->num_frames; + memcpy(*dest, &f->desc, sizeof(f->desc)); + *dest += sizeof(f->desc); + } else { + return -EINVAL; + } + } + break; + case UVCG_FRAME: { + struct uvcg_frame *frm = priv1; + struct uvc_descriptor_header *h = *dest; + + sz = sizeof(frm->frame) - 4; + if (frm->fmt_type != UVCG_FRAMEBASED) + memcpy(*dest, &frm->frame, sz); + else + __uvcg_copy_framebased_desc(*dest, frm, sz); + *dest += sz; + sz = frm->frame.b_frame_interval_type * + sizeof(*frm->dw_frame_interval); + memcpy(*dest, frm->dw_frame_interval, sz); + *dest += sz; + if (frm->fmt_type == UVCG_UNCOMPRESSED) + h->bLength = UVC_DT_FRAME_UNCOMPRESSED_SIZE( + frm->frame.b_frame_interval_type); + else if (frm->fmt_type == UVCG_MJPEG) + h->bLength = UVC_DT_FRAME_MJPEG_SIZE( + frm->frame.b_frame_interval_type); + else if (frm->fmt_type == UVCG_FRAMEBASED) + h->bLength = UVC_DT_FRAME_FRAMEBASED_SIZE( + frm->frame.b_frame_interval_type); + } + break; + case UVCG_COLOR_MATCHING: { + struct uvcg_color_matching *color_match = priv1; + + memcpy(*dest, &color_match->desc, sizeof(color_match->desc)); + *dest += sizeof(color_match->desc); + } + break; + } + + return 0; +} + +static int uvcg_streaming_class_allow_link(struct config_item *src, + struct config_item *target) +{ + struct config_item *streaming, *header; + struct f_uvc_opts *opts; + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvc_descriptor_header ***class_array, **cl_arr; + struct uvcg_streaming_header *target_hdr; + void *data, *data_save; + size_t size = 0, count = 0; + int ret = -EINVAL; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + streaming = src->ci_parent->ci_parent; + header = config_group_find_item(to_config_group(streaming), "header"); + if (!header || target->ci_parent != header) + goto out; + + opts = to_f_uvc_opts(streaming->ci_parent); + + mutex_lock(&opts->lock); + + class_array = __uvcg_get_stream_class_arr(src, opts); + if (!class_array || *class_array || opts->refcnt) { + ret = -EBUSY; + goto unlock; + } + + target_hdr = to_uvcg_streaming_header(target); + ret = __uvcg_iter_strm_cls(target_hdr, &size, &count, __uvcg_cnt_strm); + if (ret) + goto unlock; + + count += 1; /* NULL */ + *class_array = kcalloc(count, sizeof(void *), GFP_KERNEL); + if (!*class_array) { + ret = -ENOMEM; + goto unlock; + } + + data = data_save = kzalloc(size, GFP_KERNEL); + if (!data) { + kfree(*class_array); + *class_array = NULL; + ret = -ENOMEM; + goto unlock; + } + cl_arr = *class_array; + ret = __uvcg_iter_strm_cls(target_hdr, &data, &cl_arr, + __uvcg_fill_strm); + if (ret) { + kfree(*class_array); + *class_array = NULL; + /* + * __uvcg_fill_strm() called from __uvcg_iter_stream_cls() + * might have advanced the "data", so use a backup copy + */ + kfree(data_save); + goto unlock; + } + + ++target_hdr->linked; + ret = 0; + +unlock: + mutex_unlock(&opts->lock); +out: + config_item_put(header); + mutex_unlock(su_mutex); + return ret; +} + +static void uvcg_streaming_class_drop_link(struct config_item *src, + struct config_item *target) +{ + struct config_item *streaming, *header; + struct f_uvc_opts *opts; + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvc_descriptor_header ***class_array; + struct uvcg_streaming_header *target_hdr; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + streaming = src->ci_parent->ci_parent; + header = config_group_find_item(to_config_group(streaming), "header"); + if (!header || target->ci_parent != header) + goto out; + + opts = to_f_uvc_opts(streaming->ci_parent); + + mutex_lock(&opts->lock); + + class_array = __uvcg_get_stream_class_arr(src, opts); + if (!class_array || !*class_array) + goto unlock; + + if (opts->refcnt) + goto unlock; + + target_hdr = to_uvcg_streaming_header(target); + --target_hdr->linked; + kfree(**class_array); + kfree(*class_array); + *class_array = NULL; + +unlock: + mutex_unlock(&opts->lock); +out: + config_item_put(header); + mutex_unlock(su_mutex); +} + +static const struct configfs_item_operations uvcg_streaming_class_item_ops = { + .release = uvcg_config_item_release, + .allow_link = uvcg_streaming_class_allow_link, + .drop_link = uvcg_streaming_class_drop_link, +}; + +static const struct config_item_type uvcg_streaming_class_type = { + .ct_item_ops = &uvcg_streaming_class_item_ops, + .ct_owner = THIS_MODULE, +}; + +/* ----------------------------------------------------------------------------- + * streaming/class + */ + +static int uvcg_streaming_class_create_children(struct config_group *parent) +{ + static const char * const names[] = { "fs", "hs", "ss" }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(names); ++i) { + struct uvcg_streaming_class_group *group; + + group = kzalloc_obj(*group); + if (!group) + return -ENOMEM; + + group->name = names[i]; + + config_group_init_type_name(&group->group, group->name, + &uvcg_streaming_class_type); + configfs_add_default_group(&group->group, parent); + } + + return 0; +} + +static const struct uvcg_config_group_type uvcg_streaming_class_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "class", + .create_children = uvcg_streaming_class_create_children, +}; + +/* ----------------------------------------------------------------------------- + * streaming + */ + +static ssize_t uvcg_default_streaming_b_interface_number_show( + struct config_item *item, char *page) +{ + struct config_group *group = to_config_group(item); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + int result = 0; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = item->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + result += sprintf(page, "%u\n", opts->streaming_interface); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return result; +} + +UVC_ATTR_RO(uvcg_default_streaming_, b_interface_number, bInterfaceNumber); + +static struct configfs_attribute *uvcg_default_streaming_attrs[] = { + &uvcg_default_streaming_attr_b_interface_number, + NULL, +}; + +static const struct uvcg_config_group_type uvcg_streaming_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_default_streaming_attrs, + .ct_owner = THIS_MODULE, + }, + .name = "streaming", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_streaming_header_grp_type, + &uvcg_uncompressed_grp_type, + &uvcg_mjpeg_grp_type, + &uvcg_framebased_grp_type, + &uvcg_color_matching_grp_type, + &uvcg_streaming_class_grp_type, + NULL, + }, +}; + +/* ----------------------------------------------------------------------------- + * UVC function + */ + +static void uvc_func_item_release(struct config_item *item) +{ + struct f_uvc_opts *opts = to_f_uvc_opts(item); + + uvcg_config_remove_children(to_config_group(item)); + usb_put_function_instance(&opts->func_inst); +} + +static int uvc_func_allow_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct gadget_string *string; + struct config_item *strings; + struct f_uvc_opts *opts; + int ret = 0; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + /* Validate that the target is an entry in strings/ */ + strings = config_group_find_item(to_config_group(src->ci_parent->ci_parent), + "strings"); + if (!strings || tgt->ci_parent->ci_parent != strings) { + ret = -EINVAL; + goto put_strings; + } + + string = to_gadget_string(tgt); + + opts = to_f_uvc_opts(src); + mutex_lock(&opts->lock); + + if (!strcmp(tgt->ci_name, "iad_desc")) + opts->iad_index = string->usb_string.id; + else if (!strcmp(tgt->ci_name, "vs0_desc")) + opts->vs0_index = string->usb_string.id; + else if (!strcmp(tgt->ci_name, "vs1_desc")) + opts->vs1_index = string->usb_string.id; + else + ret = -EINVAL; + + mutex_unlock(&opts->lock); + +put_strings: + config_item_put(strings); + mutex_unlock(su_mutex); + + return ret; +} + +static void uvc_func_drop_link(struct config_item *src, struct config_item *tgt) +{ + struct f_uvc_opts *opts; + + opts = to_f_uvc_opts(src); + mutex_lock(&opts->lock); + + if (!strcmp(tgt->ci_name, "iad_desc")) + opts->iad_index = 0; + else if (!strcmp(tgt->ci_name, "vs0_desc")) + opts->vs0_index = 0; + else if (!strcmp(tgt->ci_name, "vs1_desc")) + opts->vs1_index = 0; + + mutex_unlock(&opts->lock); +} + +static const struct configfs_item_operations uvc_func_item_ops = { + .release = uvc_func_item_release, + .allow_link = uvc_func_allow_link, + .drop_link = uvc_func_drop_link, +}; + +#define UVCG_OPTS_ATTR(cname, aname, limit) \ +static ssize_t f_uvc_opts_##cname##_show( \ + struct config_item *item, char *page) \ +{ \ + struct f_uvc_opts *opts = to_f_uvc_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", opts->cname); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t \ +f_uvc_opts_##cname##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uvc_opts *opts = to_f_uvc_opts(item); \ + unsigned int num; \ + int ret; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtouint(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > limit) { \ + ret = -EINVAL; \ + goto end; \ + } \ + opts->cname = num; \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +UVC_ATTR(f_uvc_opts_, cname, cname) + +UVCG_OPTS_ATTR(streaming_interval, streaming_interval, 16); +UVCG_OPTS_ATTR(streaming_maxpacket, streaming_maxpacket, 3072); +UVCG_OPTS_ATTR(streaming_maxburst, streaming_maxburst, 15); + +#undef UVCG_OPTS_ATTR + +#define UVCG_OPTS_STRING_ATTR(cname, aname) \ +static ssize_t f_uvc_opts_string_##cname##_show(struct config_item *item,\ + char *page) \ +{ \ + struct f_uvc_opts *opts = to_f_uvc_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = scnprintf(page, sizeof(opts->aname), "%s", opts->aname);\ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uvc_opts_string_##cname##_store(struct config_item *item,\ + const char *page, size_t len) \ +{ \ + struct f_uvc_opts *opts = to_f_uvc_opts(item); \ + int size = min(sizeof(opts->aname), len + 1); \ + int ret = 0; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = strscpy(opts->aname, page, size); \ + if (ret == -E2BIG) \ + ret = size - 1; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +UVC_ATTR(f_uvc_opts_string_, cname, aname) + +UVCG_OPTS_STRING_ATTR(function_name, function_name); + +#undef UVCG_OPTS_STRING_ATTR + +static struct configfs_attribute *uvc_attrs[] = { + &f_uvc_opts_attr_streaming_interval, + &f_uvc_opts_attr_streaming_maxpacket, + &f_uvc_opts_attr_streaming_maxburst, + &f_uvc_opts_string_attr_function_name, + NULL, +}; + +static const struct uvcg_config_group_type uvc_func_type = { + .type = { + .ct_item_ops = &uvc_func_item_ops, + .ct_attrs = uvc_attrs, + .ct_owner = THIS_MODULE, + }, + .name = "", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_control_grp_type, + &uvcg_streaming_grp_type, + NULL, + }, +}; + +int uvcg_attach_configfs(struct f_uvc_opts *opts) +{ + int ret; + + config_group_init_type_name(&opts->func_inst.group, uvc_func_type.name, + &uvc_func_type.type); + + ret = uvcg_config_create_children(&opts->func_inst.group, + &uvc_func_type); + if (ret < 0) + config_group_put(&opts->func_inst.group); + + return ret; +} diff --git a/drivers/usb/gadget/function/uvc_configfs.h b/drivers/usb/gadget/function/uvc_configfs.h new file mode 100644 index 000000000..939161413 --- /dev/null +++ b/drivers/usb/gadget/function/uvc_configfs.h @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * uvc_configfs.h + * + * Configfs support for the uvc function. + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ +#ifndef UVC_CONFIGFS_H +#define UVC_CONFIGFS_H + +#include + +#include "u_uvc.h" + +static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_uvc_opts, + func_inst.group); +} + +#define UVCG_STREAMING_CONTROL_SIZE 1 + +DECLARE_UVC_HEADER_DESCRIPTOR(1); + +struct uvcg_control_header { + struct config_item item; + struct UVC_HEADER_DESCRIPTOR(1) desc; + unsigned linked; +}; + +static inline struct uvcg_control_header *to_uvcg_control_header(struct config_item *item) +{ + return container_of(item, struct uvcg_control_header, item); +} + +struct uvcg_color_matching { + struct config_group group; + struct uvc_color_matching_descriptor desc; + unsigned int refcnt; +}; + +#define to_uvcg_color_matching(group_ptr) \ +container_of(group_ptr, struct uvcg_color_matching, group) + +enum uvcg_format_type { + UVCG_UNCOMPRESSED = 0, + UVCG_MJPEG, + UVCG_FRAMEBASED, +}; + +struct uvcg_format { + struct config_group group; + enum uvcg_format_type type; + unsigned linked; + struct list_head frames; + unsigned num_frames; + __u8 bmaControls[UVCG_STREAMING_CONTROL_SIZE]; + struct uvcg_color_matching *color_matching; +}; + +struct uvcg_format_ptr { + struct uvcg_format *fmt; + struct list_head entry; +}; + +static inline struct uvcg_format *to_uvcg_format(struct config_item *item) +{ + return container_of(to_config_group(item), struct uvcg_format, group); +} + +struct uvcg_streaming_header { + struct config_item item; + unsigned linked; + struct list_head formats; + unsigned num_fmt; + + /* Must be last --ends in a flexible-array member. */ + struct uvc_input_header_descriptor desc; +}; + +static inline struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item) +{ + return container_of(item, struct uvcg_streaming_header, item); +} + +struct uvcg_frame_ptr { + struct uvcg_frame *frm; + struct list_head entry; +}; + +struct uvcg_frame { + struct config_item item; + enum uvcg_format_type fmt_type; + struct { + u8 b_length; + u8 b_descriptor_type; + u8 b_descriptor_subtype; + u8 b_frame_index; + u8 bm_capabilities; + u16 w_width; + u16 w_height; + u32 dw_min_bit_rate; + u32 dw_max_bit_rate; + u32 dw_max_video_frame_buffer_size; + u32 dw_default_frame_interval; + u8 b_frame_interval_type; + u32 dw_bytes_perline; + } __attribute__((packed)) frame; + u32 *dw_frame_interval; +}; + +static inline struct uvcg_frame *to_uvcg_frame(struct config_item *item) +{ + return container_of(item, struct uvcg_frame, item); +} + +/* ----------------------------------------------------------------------------- + * streaming/uncompressed/ + */ + +struct uvcg_uncompressed { + struct uvcg_format fmt; + struct uvc_format_uncompressed desc; +}; + +static inline struct uvcg_uncompressed *to_uvcg_uncompressed(struct config_item *item) +{ + return container_of(to_uvcg_format(item), struct uvcg_uncompressed, fmt); +} + +/* ----------------------------------------------------------------------------- + * streaming/mjpeg/ + */ + +struct uvcg_mjpeg { + struct uvcg_format fmt; + struct uvc_format_mjpeg desc; +}; + +static inline struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item) +{ + return container_of(to_uvcg_format(item), struct uvcg_mjpeg, fmt); +} + +/* ----------------------------------------------------------------------------- + * streaming/framebased/ + */ + +struct uvcg_framebased { + struct uvcg_format fmt; + struct uvc_format_framebased desc; +}; + +static inline struct uvcg_framebased *to_uvcg_framebased(struct config_item *item) +{ + return container_of(to_uvcg_format(item), struct uvcg_framebased, fmt); +} + +/* ----------------------------------------------------------------------------- + * control/extensions/ + */ + +struct uvcg_extension_unit_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 bUnitID; + u8 guidExtensionCode[16]; + u8 bNumControls; + u8 bNrInPins; + u8 *baSourceID; + u8 bControlSize; + u8 *bmControls; + u8 iExtension; +} __packed; + +struct uvcg_extension { + struct config_item item; + struct list_head list; + u8 string_descriptor_index; + struct uvcg_extension_unit_descriptor desc; +}; + +static inline struct uvcg_extension *to_uvcg_extension(struct config_item *item) +{ + return container_of(item, struct uvcg_extension, item); +} + +int uvcg_attach_configfs(struct f_uvc_opts *opts); + +#endif /* UVC_CONFIGFS_H */ diff --git a/drivers/usb/gadget/function/uvc_queue.c b/drivers/usb/gadget/function/uvc_queue.c new file mode 100644 index 000000000..586e5524c --- /dev/null +++ b/drivers/usb/gadget/function/uvc_queue.c @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * uvc_queue.c -- USB Video Class driver - Buffers management + * + * Copyright (C) 2005-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "uvc.h" +#include "uvc_video.h" + +/* ------------------------------------------------------------------------ + * Video buffers queue management. + * + * Video queues is initialized by uvcg_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, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + 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; + if (*nbuffers < UVCG_STREAMING_MIN_BUFFERS) + *nbuffers = UVCG_STREAMING_MIN_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_video *video = container_of(queue, struct uvc_video, queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct uvc_buffer *buf = container_of(vbuf, struct uvc_buffer, buf); + + if (vb->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; + if (queue->use_sg) { + buf->sgt = vb2_dma_sg_plane_desc(vb, 0); + buf->sg = buf->sgt->sgl; + } else { + buf->mem = vb2_plane_vaddr(vb, 0); + } + buf->length = vb2_plane_size(vb, 0); + if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + buf->bytesused = 0; + } else { + buf->bytesused = vb2_get_plane_payload(vb, 0); + + if (video->reqs_per_frame != 0) { + buf->req_payload_size = + DIV_ROUND_UP(buf->bytesused + + (video->reqs_per_frame * UVCG_REQUEST_HEADER_LEN), + video->reqs_per_frame); + if (buf->req_payload_size > video->req_size) + buf->req_payload_size = video->req_size; + } else { + buf->req_payload_size = video->max_req_size; + } + } + + return 0; +} + +static void uvc_buffer_queue(struct vb2_buffer *vb) +{ + struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct uvc_buffer *buf = container_of(vbuf, 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(vb, VB2_BUF_STATE_ERROR); + } + + spin_unlock_irqrestore(&queue->irqlock, flags); +} + +static const struct vb2_ops uvc_queue_qops = { + .queue_setup = uvc_queue_setup, + .buf_prepare = uvc_buffer_prepare, + .buf_queue = uvc_buffer_queue, +}; + +int uvcg_queue_init(struct uvc_video_queue *queue, struct device *dev, enum v4l2_buf_type type, + struct mutex *lock) +{ + struct uvc_video *video = container_of(queue, struct uvc_video, queue); + struct usb_composite_dev *cdev = video->uvc->func.config->cdev; + int ret; + + queue->queue.type = type; + queue->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + queue->queue.drv_priv = queue; + queue->queue.buf_struct_size = sizeof(struct uvc_buffer); + queue->queue.ops = &uvc_queue_qops; + queue->queue.lock = lock; + if (cdev->gadget->sg_supported) { + queue->queue.mem_ops = &vb2_dma_sg_memops; + queue->use_sg = 1; + } else { + queue->queue.mem_ops = &vb2_vmalloc_memops; + } + + queue->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY + | V4L2_BUF_FLAG_TSTAMP_SRC_EOF; + queue->queue.dev = dev; + + ret = vb2_queue_init(&queue->queue); + if (ret) + return ret; + + spin_lock_init(&queue->irqlock); + INIT_LIST_HEAD(&queue->irqqueue); + queue->flags = 0; + + return 0; +} + +/* + * Free the video buffers. + */ +void uvcg_free_buffers(struct uvc_video_queue *queue) +{ + vb2_queue_release(&queue->queue); +} + +/* + * Allocate the video buffers. + */ +int uvcg_alloc_buffers(struct uvc_video_queue *queue, + struct v4l2_requestbuffers *rb) +{ + int ret; + +retry: + ret = vb2_reqbufs(&queue->queue, rb); + if (ret < 0 && queue->use_sg) { + uvc_trace(UVC_TRACE_IOCTL, + "failed to alloc buffer with sg enabled, try non-sg mode\n"); + queue->use_sg = 0; + queue->queue.mem_ops = &vb2_vmalloc_memops; + goto retry; + } + + return ret ? ret : rb->count; +} + +int uvcg_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf) +{ + return vb2_querybuf(&queue->queue, buf); +} + +int uvcg_queue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf) +{ + return vb2_qbuf(&queue->queue, NULL, buf); +} + +/* + * Dequeue a video buffer. If nonblocking is false, block until a buffer is + * available. + */ +int uvcg_dequeue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf, + int nonblocking) +{ + return vb2_dqbuf(&queue->queue, buf, nonblocking); +} + +/* + * Poll the video queue. + * + * This function implements video queue polling and is intended to be used by + * the device poll handler. + */ +__poll_t uvcg_queue_poll(struct uvc_video_queue *queue, struct file *file, + poll_table *wait) +{ + return vb2_poll(&queue->queue, file, wait); +} + +int uvcg_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma) +{ + return vb2_mmap(&queue->queue, vma); +} + +#ifndef CONFIG_MMU +/* + * Get unmapped area. + * + * NO-MMU arch need this function to make mmap() work correctly. + */ +unsigned long uvcg_queue_get_unmapped_area(struct uvc_video_queue *queue, + unsigned long pgoff) +{ + return vb2_get_unmapped_area(&queue->queue, 0, 0, pgoff, 0); +} +#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. + */ +void uvcg_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, VB2_BUF_STATE_ERROR); + } + queue->buf_used = 0; + + /* + * 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 + * uvcg_queue_cancel() instead. + */ +int uvcg_queue_enable(struct uvc_video_queue *queue, int enable) +{ + unsigned long flags; + int ret = 0; + + if (enable) { + ret = vb2_streamon(&queue->queue, queue->queue.type); + if (ret < 0) + return ret; + + queue->sequence = 0; + queue->buf_used = 0; + queue->flags &= ~UVC_QUEUE_DROP_INCOMPLETE; + } else { + ret = vb2_streamoff(&queue->queue, queue->queue.type); + if (ret < 0) + return ret; + + 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); + } + + return ret; +} + +/* called with &queue_irqlock held.. */ +void uvcg_complete_buffer(struct uvc_video_queue *queue, + struct uvc_buffer *buf) +{ + if (queue->flags & UVC_QUEUE_DROP_INCOMPLETE) { + queue->flags &= ~UVC_QUEUE_DROP_INCOMPLETE; + buf->state = UVC_BUF_STATE_ERROR; + vb2_set_plane_payload(&buf->buf.vb2_buf, 0, 0); + vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_ERROR); + return; + } + + buf->buf.field = V4L2_FIELD_NONE; + buf->buf.sequence = queue->sequence++; + buf->buf.vb2_buf.timestamp = ktime_get_ns(); + + vb2_set_plane_payload(&buf->buf.vb2_buf, 0, buf->bytesused); + vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_DONE); +} + +struct uvc_buffer *uvcg_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); + + return buf; +} + diff --git a/drivers/usb/gadget/function/uvc_queue.h b/drivers/usb/gadget/function/uvc_queue.h new file mode 100644 index 000000000..b54becc57 --- /dev/null +++ b/drivers/usb/gadget/function/uvc_queue.h @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _UVC_QUEUE_H_ +#define _UVC_QUEUE_H_ + +#include +#include +#include + +#include + +struct file; +struct mutex; + +/* 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_v4l2_buffer buf; + struct list_head queue; + + enum uvc_buffer_state state; + void *mem; + struct sg_table *sgt; + struct scatterlist *sg; + unsigned int offset; + unsigned int length; + unsigned int bytesused; + /* req_payload_size: only used with isoc */ + unsigned int req_payload_size; +}; + +#define UVC_QUEUE_DISCONNECTED (1 << 0) +#define UVC_QUEUE_DROP_INCOMPLETE (1 << 1) + +struct uvc_video_queue { + struct vb2_queue queue; + + unsigned int flags; + __u32 sequence; + + unsigned int buf_used; + + bool use_sg; + + 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); +} + +int uvcg_queue_init(struct uvc_video_queue *queue, struct device *dev, enum v4l2_buf_type type, + struct mutex *lock); + +void uvcg_free_buffers(struct uvc_video_queue *queue); + +int uvcg_alloc_buffers(struct uvc_video_queue *queue, + struct v4l2_requestbuffers *rb); + +int uvcg_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf); + +int uvcg_queue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf); + +int uvcg_dequeue_buffer(struct uvc_video_queue *queue, + struct v4l2_buffer *buf, int nonblocking); + +__poll_t uvcg_queue_poll(struct uvc_video_queue *queue, + struct file *file, poll_table *wait); + +int uvcg_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma); + +#ifndef CONFIG_MMU +unsigned long uvcg_queue_get_unmapped_area(struct uvc_video_queue *queue, + unsigned long pgoff); +#endif /* CONFIG_MMU */ + +void uvcg_queue_cancel(struct uvc_video_queue *queue, int disconnect); + +int uvcg_queue_enable(struct uvc_video_queue *queue, int enable); + +void uvcg_complete_buffer(struct uvc_video_queue *queue, + struct uvc_buffer *buf); + +struct uvc_buffer *uvcg_queue_head(struct uvc_video_queue *queue); + +#endif /* _UVC_QUEUE_H_ */ + diff --git a/drivers/usb/gadget/function/uvc_trace.c b/drivers/usb/gadget/function/uvc_trace.c new file mode 100644 index 000000000..d384f6d82 --- /dev/null +++ b/drivers/usb/gadget/function/uvc_trace.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * trace.c - USB UVC Gadget Trace Support + * + * Copyright (C) 2024 Pengutronix e.K. + * + * Author: Michael Grzeschik + */ + +#define CREATE_TRACE_POINTS +#include "uvc_trace.h" diff --git a/drivers/usb/gadget/function/uvc_trace.h b/drivers/usb/gadget/function/uvc_trace.h new file mode 100644 index 000000000..04c33cf43 --- /dev/null +++ b/drivers/usb/gadget/function/uvc_trace.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * trace.h - USB UVC Gadget Trace Support + * + * Copyright (C) 2024 Pengutronix e.K. + * + * Author: Michael Grzeschik + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM uvcg + +#if !defined(__UVCG_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __UVCG_TRACE_H + +#include +#include +#include +#include + +DECLARE_EVENT_CLASS(uvcg_video_req, + TP_PROTO(struct usb_request *req, u32 queued), + TP_ARGS(req, queued), + TP_STRUCT__entry( + __field(struct usb_request *, req) + __field(u32, length) + __field(u32, queued) + ), + TP_fast_assign( + __entry->req = req; + __entry->length = req->length; + __entry->queued = queued; + ), + TP_printk("req %p length %u queued %u", + __entry->req, + __entry->length, + __entry->queued) +); + +DEFINE_EVENT(uvcg_video_req, uvcg_video_complete, + TP_PROTO(struct usb_request *req, u32 queued), + TP_ARGS(req, queued) +); + +DEFINE_EVENT(uvcg_video_req, uvcg_video_queue, + TP_PROTO(struct usb_request *req, u32 queued), + TP_ARGS(req, queued) +); + +#endif /* __UVCG_TRACE_H */ + +/* this part has to be here */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE uvc_trace + +#include diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c new file mode 100644 index 000000000..dfa0521a2 --- /dev/null +++ b/drivers/usb/gadget/function/uvc_v4l2.c @@ -0,0 +1,747 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * uvc_v4l2.c -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "f_uvc.h" +#include "uvc.h" +#include "uvc_queue.h" +#include "uvc_video.h" +#include "uvc_v4l2.h" +#include "uvc_configfs.h" + +static const struct uvc_format_desc *to_uvc_format(struct uvcg_format *uformat) +{ + char guid[16] = UVC_GUID_FORMAT_MJPEG; + const struct uvc_format_desc *format; + + if (uformat->type == UVCG_UNCOMPRESSED) { + struct uvcg_uncompressed *unc; + + unc = to_uvcg_uncompressed(&uformat->group.cg_item); + if (!unc) + return ERR_PTR(-EINVAL); + + memcpy(guid, unc->desc.guidFormat, sizeof(guid)); + } else if (uformat->type == UVCG_FRAMEBASED) { + struct uvcg_framebased *unc; + + unc = to_uvcg_framebased(&uformat->group.cg_item); + if (!unc) + return ERR_PTR(-EINVAL); + + memcpy(guid, unc->desc.guidFormat, sizeof(guid)); + } + + format = uvc_format_by_guid(guid); + if (!format) + return ERR_PTR(-EINVAL); + + return format; +} + +static int uvc_v4l2_get_bytesperline(struct uvcg_format *uformat, + struct uvcg_frame *uframe) +{ + struct uvcg_uncompressed *u; + + if (uformat->type == UVCG_UNCOMPRESSED) { + u = to_uvcg_uncompressed(&uformat->group.cg_item); + if (!u) + return 0; + + return u->desc.bBitsPerPixel * uframe->frame.w_width / 8; + } + + return 0; +} + +static int uvc_get_frame_size(struct uvcg_format *uformat, + struct uvcg_frame *uframe) +{ + unsigned int bpl = uvc_v4l2_get_bytesperline(uformat, uframe); + + return bpl ? bpl * uframe->frame.w_height : + uframe->frame.dw_max_video_frame_buffer_size; +} + +static struct uvcg_format *find_format_by_index(struct uvc_device *uvc, int index) +{ + struct uvcg_format_ptr *format; + struct uvcg_format *uformat = NULL; + int i = 1; + + list_for_each_entry(format, &uvc->header->formats, entry) { + if (index == i) { + uformat = format->fmt; + break; + } + i++; + } + + return uformat; +} + +static struct uvcg_frame *find_frame_by_index(struct uvc_device *uvc, + struct uvcg_format *uformat, + int index) +{ + struct uvcg_format_ptr *format; + struct uvcg_frame_ptr *frame; + struct uvcg_frame *uframe = NULL; + + list_for_each_entry(format, &uvc->header->formats, entry) { + if (format->fmt->type != uformat->type) + continue; + list_for_each_entry(frame, &format->fmt->frames, entry) { + if (index == frame->frm->frame.b_frame_index) { + uframe = frame->frm; + break; + } + } + } + + return uframe; +} + +static struct uvcg_format *find_format_by_pix(struct uvc_device *uvc, + u32 pixelformat) +{ + struct uvcg_format_ptr *format; + struct uvcg_format *uformat = NULL; + + list_for_each_entry(format, &uvc->header->formats, entry) { + const struct uvc_format_desc *fmtdesc = to_uvc_format(format->fmt); + + if (IS_ERR(fmtdesc)) + continue; + + if (fmtdesc->fcc == pixelformat) { + uformat = format->fmt; + break; + } + } + + return uformat; +} + +static struct uvcg_frame *find_closest_frame_by_size(struct uvc_device *uvc, + struct uvcg_format *uformat, + u16 rw, u16 rh) +{ + struct uvc_video *video = &uvc->video; + struct uvcg_format_ptr *format; + struct uvcg_frame_ptr *frame; + struct uvcg_frame *uframe = NULL; + unsigned int d, maxd; + + /* Find the closest image size. The distance between image sizes is + * the size in pixels of the non-overlapping regions between the + * requested size and the frame-specified size. + */ + maxd = (unsigned int)-1; + + list_for_each_entry(format, &uvc->header->formats, entry) { + if (format->fmt->type != uformat->type) + continue; + + list_for_each_entry(frame, &format->fmt->frames, entry) { + u16 w, h; + + w = frame->frm->frame.w_width; + h = frame->frm->frame.w_height; + + d = min(w, rw) * min(h, rh); + d = w*h + rw*rh - 2*d; + if (d < maxd) { + maxd = d; + uframe = frame->frm; + } + + if (maxd == 0) + break; + } + } + + if (!uframe) + uvcg_dbg(&video->uvc->func, "Unsupported size %ux%u\n", rw, rh); + + return uframe; +} + +/* -------------------------------------------------------------------------- + * 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); + if (req->length > sizeof(data->data)) + req->length = sizeof(data->data); + 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 ioctls + */ + +static int +uvc_v4l2_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct usb_composite_dev *cdev = uvc->func.config->cdev; + + strscpy(cap->driver, "g_uvc", sizeof(cap->driver)); + strscpy(cap->card, cdev->gadget->name, sizeof(cap->card)); + strscpy(cap->bus_info, dev_name(&cdev->gadget->dev), + sizeof(cap->bus_info)); + return 0; +} + +static int +uvc_v4l2_get_format(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_video *video = &uvc->video; + + 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_try_format(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_video *video = &uvc->video; + struct uvcg_format *uformat; + struct uvcg_frame *uframe; + const struct uvc_format_desc *fmtdesc; + u8 *fcc; + + if (fmt->type != video->queue.queue.type) + return -EINVAL; + + fcc = (u8 *)&fmt->fmt.pix.pixelformat; + uvcg_dbg(&uvc->func, "Trying format 0x%08x (%c%c%c%c): %ux%u\n", + fmt->fmt.pix.pixelformat, + fcc[0], fcc[1], fcc[2], fcc[3], + fmt->fmt.pix.width, fmt->fmt.pix.height); + + uformat = find_format_by_pix(uvc, fmt->fmt.pix.pixelformat); + if (!uformat) + return -EINVAL; + + uframe = find_closest_frame_by_size(uvc, uformat, + fmt->fmt.pix.width, fmt->fmt.pix.height); + if (!uframe) + return -EINVAL; + + if (uformat->type == UVCG_UNCOMPRESSED) { + struct uvcg_uncompressed *u = + to_uvcg_uncompressed(&uformat->group.cg_item); + if (!u) + return 0; + + v4l2_fill_pixfmt(&fmt->fmt.pix, fmt->fmt.pix.pixelformat, + uframe->frame.w_width, uframe->frame.w_height); + + if (fmt->fmt.pix.sizeimage != (uvc_v4l2_get_bytesperline(uformat, uframe) * + uframe->frame.w_height)) + return -EINVAL; + } else { + fmt->fmt.pix.width = uframe->frame.w_width; + fmt->fmt.pix.height = uframe->frame.w_height; + fmt->fmt.pix.bytesperline = uvc_v4l2_get_bytesperline(uformat, uframe); + fmt->fmt.pix.sizeimage = uvc_get_frame_size(uformat, uframe); + fmtdesc = to_uvc_format(uformat); + if (IS_ERR(fmtdesc)) + return PTR_ERR(fmtdesc); + fmt->fmt.pix.pixelformat = fmtdesc->fcc; + } + fmt->fmt.pix.field = V4L2_FIELD_NONE; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + fmt->fmt.pix.priv = 0; + + return 0; +} + +static int +uvc_v4l2_set_format(struct file *file, void *fh, struct v4l2_format *fmt) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_video *video = &uvc->video; + int ret; + + ret = uvc_v4l2_try_format(file, fh, fmt); + if (ret) + return ret; + + video->fcc = fmt->fmt.pix.pixelformat; + video->bpp = fmt->fmt.pix.bytesperline * 8 / video->width; + video->width = fmt->fmt.pix.width; + video->height = fmt->fmt.pix.height; + video->imagesize = fmt->fmt.pix.sizeimage; + + return ret; +} + +static int uvc_v4l2_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *parm) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_video *video = &uvc->video; + struct v4l2_fract timeperframe; + + if (!V4L2_TYPE_IS_OUTPUT(parm->type)) + return -EINVAL; + + /* Return the actual frame period. */ + timeperframe.numerator = video->interval; + timeperframe.denominator = 10000000; + v4l2_simplify_fraction(&timeperframe.numerator, + &timeperframe.denominator, 8, 333); + + uvcg_dbg(&uvc->func, "Getting frame interval of %u/%u (%u)\n", + timeperframe.numerator, timeperframe.denominator, + video->interval); + + parm->parm.output.timeperframe = timeperframe; + parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + + return 0; +} + +static int uvc_v4l2_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *parm) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_video *video = &uvc->video; + struct v4l2_fract timeperframe; + + if (!V4L2_TYPE_IS_OUTPUT(parm->type)) + return -EINVAL; + + timeperframe = parm->parm.output.timeperframe; + + video->interval = v4l2_fraction_to_interval(timeperframe.numerator, + timeperframe.denominator); + + uvcg_dbg(&uvc->func, "Setting frame interval to %u/%u (%u)\n", + timeperframe.numerator, timeperframe.denominator, + video->interval); + + return 0; +} + +static int +uvc_v4l2_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fival) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvcg_format *uformat = NULL; + struct uvcg_frame *uframe = NULL; + struct uvcg_frame_ptr *frame; + + uformat = find_format_by_pix(uvc, fival->pixel_format); + if (!uformat) + return -EINVAL; + + list_for_each_entry(frame, &uformat->frames, entry) { + if (frame->frm->frame.w_width == fival->width && + frame->frm->frame.w_height == fival->height) { + uframe = frame->frm; + break; + } + } + if (!uframe) + return -EINVAL; + + if (fival->index >= uframe->frame.b_frame_interval_type) + return -EINVAL; + + fival->discrete.numerator = + uframe->dw_frame_interval[fival->index]; + + /* TODO: handle V4L2_FRMIVAL_TYPE_STEPWISE */ + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete.denominator = 10000000; + v4l2_simplify_fraction(&fival->discrete.numerator, + &fival->discrete.denominator, 8, 333); + + return 0; +} + +static int +uvc_v4l2_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvcg_format *uformat = NULL; + struct uvcg_frame *uframe = NULL; + + uformat = find_format_by_pix(uvc, fsize->pixel_format); + if (!uformat) + return -EINVAL; + + if (fsize->index >= uformat->num_frames) + return -EINVAL; + + uframe = find_frame_by_index(uvc, uformat, fsize->index + 1); + if (!uframe) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = uframe->frame.w_width; + fsize->discrete.height = uframe->frame.w_height; + + return 0; +} + +static int +uvc_v4l2_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + const struct uvc_format_desc *fmtdesc; + struct uvcg_format *uformat; + + if (f->index >= uvc->header->num_fmt) + return -EINVAL; + + uformat = find_format_by_index(uvc, f->index + 1); + if (!uformat) + return -EINVAL; + + fmtdesc = to_uvc_format(uformat); + if (IS_ERR(fmtdesc)) + return PTR_ERR(fmtdesc); + + f->pixelformat = fmtdesc->fcc; + + return 0; +} + +static int +uvc_v4l2_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *b) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_video *video = &uvc->video; + + if (b->type != video->queue.queue.type) + return -EINVAL; + + return uvcg_alloc_buffers(&video->queue, b); +} + +static int +uvc_v4l2_querybuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_video *video = &uvc->video; + + return uvcg_query_buffer(&video->queue, b); +} + +static int +uvc_v4l2_qbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_video *video = &uvc->video; + int ret; + + ret = uvcg_queue_buffer(&video->queue, b); + if (ret < 0) + return ret; + + if (uvc->state == UVC_STATE_STREAMING) + queue_work(video->async_wq, &video->pump); + + return ret; +} + +static int +uvc_v4l2_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_video *video = &uvc->video; + + return uvcg_dequeue_buffer(&video->queue, b, file->f_flags & O_NONBLOCK); +} + +static int +uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_video *video = &uvc->video; + int ret; + + if (type != video->queue.queue.type) + return -EINVAL; + + /* Enable UVC video. */ + ret = uvcg_video_enable(video); + 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, 0); + uvc->state = UVC_STATE_STREAMING; + + return 0; +} + +static int +uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_video *video = &uvc->video; + int ret = 0; + + if (type != video->queue.queue.type) + return -EINVAL; + + ret = uvcg_video_disable(video); + if (ret < 0) + return ret; + + if (uvc->state != UVC_STATE_STREAMING) + return 0; + + uvc->state = UVC_STATE_CONNECTED; + uvc_function_setup_continue(uvc, 1); + return 0; +} + +static int +uvc_v4l2_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + struct uvc_device *uvc = video_get_drvdata(fh->vdev); + struct uvc_file_handle *handle = to_uvc_file_handle(fh); + int ret; + + if (sub->type < UVC_EVENT_FIRST || sub->type > UVC_EVENT_LAST) + return -EINVAL; + + guard(mutex)(&uvc->lock); + + if (sub->type == UVC_EVENT_SETUP && uvc->func_connected) + return -EBUSY; + + ret = v4l2_event_subscribe(fh, sub, 2, NULL); + if (ret < 0) + return ret; + + if (sub->type == UVC_EVENT_SETUP) { + uvc->func_connected = true; + handle->is_uvc_app_handle = true; + uvc_function_connect(uvc); + } + + return 0; +} + +static void uvc_v4l2_disable(struct uvc_device *uvc) +{ + uvc_function_disconnect(uvc); + uvcg_video_disable(&uvc->video); + uvcg_free_buffers(&uvc->video.queue); + scoped_guard(mutex, &uvc->lock) + uvc->func_connected = false; + wake_up_interruptible(&uvc->func_connected_queue); +} + +static int +uvc_v4l2_unsubscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + struct uvc_device *uvc = video_get_drvdata(fh->vdev); + struct uvc_file_handle *handle = to_uvc_file_handle(fh); + int ret; + + ret = v4l2_event_unsubscribe(fh, sub); + if (ret < 0) + return ret; + + if (sub->type == UVC_EVENT_SETUP && handle->is_uvc_app_handle) { + uvc_v4l2_disable(uvc); + handle->is_uvc_app_handle = false; + } + + return 0; +} + +static long +uvc_v4l2_ioctl_default(struct file *file, void *fh, bool valid_prio, + unsigned int cmd, void *arg) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + + switch (cmd) { + case UVCIOC_SEND_RESPONSE: + return uvc_send_response(uvc, arg); + + default: + return -ENOIOCTLCMD; + } +} + +const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops = { + .vidioc_querycap = uvc_v4l2_querycap, + .vidioc_try_fmt_vid_out = uvc_v4l2_try_format, + .vidioc_g_fmt_vid_out = uvc_v4l2_get_format, + .vidioc_s_fmt_vid_out = uvc_v4l2_set_format, + .vidioc_enum_frameintervals = uvc_v4l2_enum_frameintervals, + .vidioc_enum_framesizes = uvc_v4l2_enum_framesizes, + .vidioc_enum_fmt_vid_out = uvc_v4l2_enum_format, + .vidioc_reqbufs = uvc_v4l2_reqbufs, + .vidioc_querybuf = uvc_v4l2_querybuf, + .vidioc_qbuf = uvc_v4l2_qbuf, + .vidioc_dqbuf = uvc_v4l2_dqbuf, + .vidioc_streamon = uvc_v4l2_streamon, + .vidioc_streamoff = uvc_v4l2_streamoff, + .vidioc_s_parm = uvc_v4l2_s_parm, + .vidioc_g_parm = uvc_v4l2_g_parm, + .vidioc_subscribe_event = uvc_v4l2_subscribe_event, + .vidioc_unsubscribe_event = uvc_v4l2_unsubscribe_event, + .vidioc_default = uvc_v4l2_ioctl_default, +}; + +/* -------------------------------------------------------------------------- + * V4L2 + */ + +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_obj(*handle); + if (handle == NULL) + return -ENOMEM; + + v4l2_fh_init(&handle->vfh, vdev); + v4l2_fh_add(&handle->vfh, file); + + handle->device = &uvc->video; + + 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 = file_to_uvc_file_handle(file); + struct uvc_video *video = handle->device; + + mutex_lock(&video->mutex); + if (handle->is_uvc_app_handle) + uvc_v4l2_disable(uvc); + mutex_unlock(&video->mutex); + + v4l2_fh_del(&handle->vfh, file); + v4l2_fh_exit(&handle->vfh); + kfree(handle); + + return 0; +} + +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 uvcg_queue_mmap(&uvc->video.queue, vma); +} + +static __poll_t +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 uvcg_queue_poll(&uvc->video.queue, file, wait); +} + +#ifndef CONFIG_MMU +static unsigned long uvcg_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 uvcg_queue_get_unmapped_area(&uvc->video.queue, pgoff); +} +#endif + +const struct v4l2_file_operations uvc_v4l2_fops = { + .owner = THIS_MODULE, + .open = uvc_v4l2_open, + .release = uvc_v4l2_release, + .unlocked_ioctl = video_ioctl2, + .mmap = uvc_v4l2_mmap, + .poll = uvc_v4l2_poll, +#ifndef CONFIG_MMU + .get_unmapped_area = uvcg_v4l2_get_unmapped_area, +#endif +}; diff --git a/drivers/usb/gadget/function/uvc_v4l2.h b/drivers/usb/gadget/function/uvc_v4l2.h new file mode 100644 index 000000000..1576005b6 --- /dev/null +++ b/drivers/usb/gadget/function/uvc_v4l2.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * uvc_v4l2.h -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * Author: Andrzej Pietrasiewicz + */ + +#ifndef __UVC_V4L2_H__ +#define __UVC_V4L2_H__ + +extern const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops; +extern const struct v4l2_file_operations uvc_v4l2_fops; + +#endif /* __UVC_V4L2_H__ */ diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c new file mode 100644 index 000000000..9ba09118b --- /dev/null +++ b/drivers/usb/gadget/function/uvc_video.c @@ -0,0 +1,843 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * uvc_video.c -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "uvc.h" +#include "uvc_queue.h" +#include "uvc_video.h" +#include "uvc_trace.h" + +/* -------------------------------------------------------------------------- + * Video codecs + */ + +static int +uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf, + u8 *data, int len) +{ + struct uvc_device *uvc = container_of(video, struct uvc_device, video); + struct usb_composite_dev *cdev = uvc->func.config->cdev; + struct timespec64 ts = ns_to_timespec64(buf->buf.vb2_buf.timestamp); + int pos = 2; + + data[1] = UVC_STREAM_EOH | video->fid; + + if (video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE) + data[1] |= UVC_STREAM_ERR; + + if (video->queue.buf_used == 0 && ts.tv_sec) { + /* dwClockFrequency is 48 MHz */ + u32 pts = ((u64)ts.tv_sec * USEC_PER_SEC + ts.tv_nsec / NSEC_PER_USEC) * 48; + + data[1] |= UVC_STREAM_PTS; + put_unaligned_le32(pts, &data[pos]); + pos += 4; + } + + if (cdev->gadget->ops->get_frame) { + u32 sof, stc; + + sof = usb_gadget_frame_number(cdev->gadget); + ktime_get_ts64(&ts); + stc = ((u64)ts.tv_sec * USEC_PER_SEC + ts.tv_nsec / NSEC_PER_USEC) * 48; + + data[1] |= UVC_STREAM_SCR; + put_unaligned_le32(stc, &data[pos]); + put_unaligned_le16(sof, &data[pos+4]); + pos += 6; + } + + data[0] = pos; + + if (buf->bytesused - video->queue.buf_used <= len - pos) + data[1] |= UVC_STREAM_EOF; + + return pos; +} + +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_t(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; + struct uvc_request *ureq = req->context; + 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_t(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; + list_del(&buf->queue); + video->fid ^= UVC_STREAM_FID; + ureq->last_buf = buf; + + video->payload_size = 0; + } + + if (video->payload_size == video->max_payload_size || + video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE || + buf->bytesused == video->queue.buf_used) + video->payload_size = 0; +} + +static void +uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video, + struct uvc_buffer *buf) +{ + unsigned int pending = buf->bytesused - video->queue.buf_used; + struct uvc_request *ureq = req->context; + struct scatterlist *sg, *iter; + unsigned int len = buf->req_payload_size; + unsigned int sg_left, part = 0; + unsigned int i; + int header_len; + + sg = ureq->sgt.sgl; + sg_init_table(sg, ureq->sgt.nents); + + /* Init the header. */ + header_len = uvc_video_encode_header(video, buf, ureq->header, + buf->req_payload_size); + sg_set_buf(sg, ureq->header, header_len); + len -= header_len; + + if (pending <= len) + len = pending; + + req->length = (len == pending) ? len + header_len : + buf->req_payload_size; + + /* Init the pending sgs with payload */ + sg = sg_next(sg); + + for_each_sg(sg, iter, ureq->sgt.nents - 1, i) { + if (!len || !buf->sg || !buf->sg->length) + break; + + sg_left = buf->sg->length - buf->offset; + part = min_t(unsigned int, len, sg_left); + + sg_set_page(iter, sg_page(buf->sg), part, buf->offset); + + if (part == sg_left) { + buf->offset = 0; + buf->sg = sg_next(buf->sg); + } else { + buf->offset += part; + } + len -= part; + } + + /* Assign the video data with header. */ + req->buf = NULL; + req->sg = ureq->sgt.sgl; + req->num_sgs = i + 1; + + req->length -= len; + video->queue.buf_used += req->length - header_len; + + if (buf->bytesused == video->queue.buf_used || !buf->sg || + video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE) { + video->queue.buf_used = 0; + buf->state = UVC_BUF_STATE_DONE; + buf->offset = 0; + list_del(&buf->queue); + video->fid ^= UVC_STREAM_FID; + ureq->last_buf = buf; + } +} + +static void +uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video, + struct uvc_buffer *buf) +{ + void *mem = req->buf; + struct uvc_request *ureq = req->context; + int len = buf->req_payload_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 = buf->req_payload_size - len; + + if (buf->bytesused == video->queue.buf_used || + video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE) { + video->queue.buf_used = 0; + buf->state = UVC_BUF_STATE_DONE; + list_del(&buf->queue); + video->fid ^= UVC_STREAM_FID; + ureq->last_buf = buf; + } +} + +/* -------------------------------------------------------------------------- + * Request handling + */ + +/* + * Callers must take care to hold req_lock when this function may be called + * from multiple threads. For example, when frames are streaming to the host. + */ +static void +uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep) +{ + sg_free_table(&ureq->sgt); + if (ureq->req && ep) { + usb_ep_free_request(ep, ureq->req); + ureq->req = NULL; + } + + kfree(ureq->req_buffer); + ureq->req_buffer = NULL; + + if (!list_empty(&ureq->list)) + list_del_init(&ureq->list); + + kfree(ureq); +} + +static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req) +{ + int ret; + + ret = usb_ep_queue(video->ep, req, GFP_ATOMIC); + if (ret < 0) { + uvcg_err(&video->uvc->func, "Failed to queue request (%d).\n", + ret); + + /* If the endpoint is disabled the descriptor may be NULL. */ + if (video->ep->desc) { + /* Isochronous endpoints can't be halted. */ + if (usb_endpoint_xfer_bulk(video->ep->desc)) + usb_ep_set_halt(video->ep); + } + } + + atomic_inc(&video->queued); + + trace_uvcg_video_queue(req, atomic_read(&video->queued)); + + return ret; +} + +/* This function must be called with video->req_lock held. */ +static int uvcg_video_usb_req_queue(struct uvc_video *video, + struct usb_request *req, bool queue_to_ep) +{ + bool is_bulk = video->max_payload_size; + struct list_head *list = NULL; + + if (!video->is_enabled) + return -ENODEV; + + if (queue_to_ep) { + struct uvc_request *ureq = req->context; + /* + * With USB3 handling more requests at a higher speed, we can't + * afford to generate an interrupt for every request. Decide to + * interrupt: + * + * - When no more requests are available in the free queue, as + * this may be our last chance to refill the endpoint's + * request queue. + * + * - When this is request is the last request for the video + * buffer, as we want to start sending the next video buffer + * ASAP in case it doesn't get started already in the next + * iteration of this loop. + * + * - Four times over the length of the requests queue (as + * indicated by video->uvc_num_requests), as a trade-off + * between latency and interrupt load. + */ + if (list_empty(&video->req_free) || ureq->last_buf || + !(video->req_int_count % + min(DIV_ROUND_UP(video->uvc_num_requests, 4), UVCG_REQ_MAX_INT_COUNT))) { + video->req_int_count = 0; + req->no_interrupt = 0; + } else { + req->no_interrupt = 1; + } + video->req_int_count++; + return uvcg_video_ep_queue(video, req); + } + /* + * If we're not queuing to the ep, for isoc we're queuing + * to the req_ready list, otherwise req_free. + */ + list = is_bulk ? &video->req_free : &video->req_ready; + list_add_tail(&req->list, list); + return 0; +} + +static void +uvc_video_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct uvc_request *ureq = req->context; + struct uvc_video *video = ureq->video; + struct uvc_video_queue *queue = &video->queue; + struct uvc_buffer *last_buf; + unsigned long flags; + + spin_lock_irqsave(&video->req_lock, flags); + atomic_dec(&video->queued); + if (!video->is_enabled) { + /* + * When is_enabled is false, uvcg_video_disable() ensures + * that in-flight uvc_buffers are returned, so we can + * safely call free_request without worrying about + * last_buf. + */ + uvc_video_free_request(ureq, ep); + spin_unlock_irqrestore(&video->req_lock, flags); + return; + } + + last_buf = ureq->last_buf; + ureq->last_buf = NULL; + spin_unlock_irqrestore(&video->req_lock, flags); + + switch (req->status) { + case 0: + break; + + case -EXDEV: + uvcg_dbg(&video->uvc->func, "VS request missed xfer.\n"); + if (req->length != 0) + queue->flags |= UVC_QUEUE_DROP_INCOMPLETE; + break; + + case -ESHUTDOWN: /* disconnect from host. */ + uvcg_dbg(&video->uvc->func, "VS request cancelled.\n"); + uvcg_queue_cancel(queue, 1); + break; + + default: + uvcg_warn(&video->uvc->func, + "VS request completed with status %d.\n", + req->status); + uvcg_queue_cancel(queue, 0); + } + + if (last_buf) { + spin_lock_irqsave(&queue->irqlock, flags); + uvcg_complete_buffer(queue, last_buf); + spin_unlock_irqrestore(&queue->irqlock, flags); + } + + spin_lock_irqsave(&video->req_lock, flags); + /* + * Video stream might have been disabled while we were + * processing the current usb_request. So make sure + * we're still streaming before queueing the usb_request + * back to req_free + */ + if (!video->is_enabled) { + uvc_video_free_request(ureq, ep); + spin_unlock_irqrestore(&video->req_lock, flags); + uvcg_queue_cancel(queue, 0); + + return; + } + + list_add_tail(&req->list, &video->req_free); + /* + * Queue work to the wq as well since it is possible that a + * buffer may not have been completely encoded with the set of + * in-flight usb requests for whih the complete callbacks are + * firing. + * In that case, if we do not queue work to the worker thread, + * the buffer will never be marked as complete - and therefore + * not be returned to userpsace. As a result, + * dequeue -> queue -> dequeue flow of uvc buffers will not + * happen. Since there are is a new free request wake up the pump. + */ + queue_work(video->async_wq, &video->pump); + + trace_uvcg_video_complete(req, atomic_read(&video->queued)); + + spin_unlock_irqrestore(&video->req_lock, flags); + + kthread_queue_work(video->kworker, &video->hw_submit); +} + +static void uvcg_video_hw_submit(struct kthread_work *work) +{ + struct uvc_video *video = container_of(work, struct uvc_video, hw_submit); + bool is_bulk = video->max_payload_size; + unsigned long flags; + struct usb_request *req; + int ret = 0; + + while (true) { + if (!video->ep->enabled) + return; + spin_lock_irqsave(&video->req_lock, flags); + /* + * Here we check whether any request is available in the ready + * list. If it is, queue it to the ep and add the current + * usb_request to the req_free list - for video_pump to fill in. + * Otherwise, just use the current usb_request to queue a 0 + * length request to the ep. Since we always add to the req_free + * list if we dequeue from the ready list, there will never + * be a situation where the req_free list is completely out of + * requests and cannot recover. + */ + if (!list_empty(&video->req_ready)) { + req = list_first_entry(&video->req_ready, + struct usb_request, list); + } else { + if (list_empty(&video->req_free) || + (atomic_read(&video->queued) > UVCG_REQ_MAX_ZERO_COUNT)) { + spin_unlock_irqrestore(&video->req_lock, flags); + + return; + } + req = list_first_entry(&video->req_free, struct usb_request, + list); + req->length = 0; + } + list_del(&req->list); + + /* + * Queue to the endpoint. The actual queueing to ep will + * only happen on one thread - the async_wq for bulk endpoints + * and this thread for isoc endpoints. + */ + ret = uvcg_video_usb_req_queue(video, req, !is_bulk); + if (ret < 0) { + /* + * Endpoint error, but the stream is still enabled. + * Put request back in req_free for it to be cleaned + * up later. + */ + list_add_tail(&req->list, &video->req_free); + /* + * There is a new free request - wake up the pump. + */ + queue_work(video->async_wq, &video->pump); + + } + + spin_unlock_irqrestore(&video->req_lock, flags); + } +} + +static int +uvc_video_free_requests(struct uvc_video *video) +{ + struct uvc_request *ureq, *temp; + + list_for_each_entry_safe(ureq, temp, &video->ureqs, list) + uvc_video_free_request(ureq, video->ep); + + INIT_LIST_HEAD(&video->ureqs); + INIT_LIST_HEAD(&video->req_free); + INIT_LIST_HEAD(&video->req_ready); + return 0; +} + +static void +uvc_video_prep_requests(struct uvc_video *video) +{ + struct uvc_device *uvc = container_of(video, struct uvc_device, video); + struct usb_composite_dev *cdev = uvc->func.config->cdev; + unsigned int interval_duration; + unsigned int max_req_size, req_size, header_size; + unsigned int nreq; + + max_req_size = video->max_req_size; + + if (!usb_endpoint_xfer_isoc(video->ep->desc)) { + video->req_size = max_req_size; + video->reqs_per_frame = video->uvc_num_requests = + DIV_ROUND_UP(video->imagesize, max_req_size); + + return; + } + + interval_duration = 1 << (video->ep->desc->bInterval - 1); + if (cdev->gadget->speed < USB_SPEED_HIGH) + interval_duration *= 10000; + else + interval_duration *= 1250; + + nreq = DIV_ROUND_UP(video->interval, interval_duration); + + header_size = nreq * UVCG_REQUEST_HEADER_LEN; + + req_size = DIV_ROUND_UP(video->imagesize + header_size, nreq); + + if (req_size > max_req_size) { + /* The prepared interval length and expected buffer size + * is not possible to stream with the currently configured + * isoc bandwidth. Fallback to the maximum. + */ + req_size = max_req_size; + } + video->req_size = req_size; + + /* We need to compensate the amount of requests to be + * allocated with the maximum amount of zero length requests. + * Since it is possible that hw_submit will initially + * enqueue some zero length requests and we then will not be + * able to fully encode one frame. + */ + video->uvc_num_requests = nreq + UVCG_REQ_MAX_ZERO_COUNT; + video->reqs_per_frame = nreq; +} + +static int +uvc_video_alloc_requests(struct uvc_video *video) +{ + struct uvc_request *ureq; + unsigned int i; + int ret = -ENOMEM; + + /* + * calculate in uvc_video_prep_requests + * - video->uvc_num_requests + * - video->req_size + */ + uvc_video_prep_requests(video); + + for (i = 0; i < video->uvc_num_requests; i++) { + ureq = kzalloc_obj(struct uvc_request); + if (ureq == NULL) + goto error; + + INIT_LIST_HEAD(&ureq->list); + + list_add_tail(&ureq->list, &video->ureqs); + + ureq->req_buffer = kmalloc(video->req_size, GFP_KERNEL); + if (ureq->req_buffer == NULL) + goto error; + + ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL); + if (ureq->req == NULL) + goto error; + + ureq->req->buf = ureq->req_buffer; + ureq->req->length = 0; + ureq->req->complete = uvc_video_complete; + ureq->req->context = ureq; + ureq->video = video; + ureq->last_buf = NULL; + + list_add_tail(&ureq->req->list, &video->req_free); + /* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */ + sg_alloc_table(&ureq->sgt, + DIV_ROUND_UP(video->req_size - UVCG_REQUEST_HEADER_LEN, + PAGE_SIZE) + 2, GFP_KERNEL); + } + + return 0; + +error: + uvc_video_free_requests(video); + return ret; +} + +/* -------------------------------------------------------------------------- + * Video streaming + */ + +/* + * uvcg_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 void uvcg_video_pump(struct work_struct *work) +{ + struct uvc_video *video = container_of(work, struct uvc_video, pump); + struct uvc_video_queue *queue = &video->queue; + /* video->max_payload_size is only set when using bulk transfer */ + bool is_bulk = video->max_payload_size; + struct usb_request *req = NULL; + struct uvc_buffer *buf; + unsigned long flags; + int ret = 0; + + while (true) { + if (!video->ep->enabled) + return; + + /* + * Check is_enabled and retrieve the first available USB + * request, protected by the request lock. + */ + spin_lock_irqsave(&video->req_lock, flags); + if (!video->is_enabled || list_empty(&video->req_free)) { + spin_unlock_irqrestore(&video->req_lock, flags); + return; + } + 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(&queue->irqlock, flags); + buf = uvcg_queue_head(queue); + if (!buf) { + /* + * Either the queue has been disconnected or no video buffer + * available for bulk transfer. Either way, stop processing + * further. + */ + spin_unlock_irqrestore(&queue->irqlock, flags); + break; + } + + video->encode(req, video, buf); + + spin_unlock_irqrestore(&queue->irqlock, flags); + + spin_lock_irqsave(&video->req_lock, flags); + /* For bulk end points we queue from the worker thread + * since we would preferably not want to wait on requests + * to be ready, in the uvcg_video_complete() handler. + * For isoc endpoints we add the request to the ready list + * and only queue it to the endpoint from the complete handler. + */ + ret = uvcg_video_usb_req_queue(video, req, is_bulk); + spin_unlock_irqrestore(&video->req_lock, flags); + + if (ret < 0) { + uvcg_queue_cancel(queue, 0); + break; + } + } + spin_lock_irqsave(&video->req_lock, flags); + if (video->is_enabled) + list_add_tail(&req->list, &video->req_free); + else + uvc_video_free_request(req->context, video->ep); + spin_unlock_irqrestore(&video->req_lock, flags); +} + +/* + * Disable the video stream + */ +int +uvcg_video_disable(struct uvc_video *video) +{ + unsigned long flags; + struct list_head inflight_bufs; + struct usb_request *req, *temp; + struct uvc_buffer *buf, *btemp; + struct uvc_request *ureq, *utemp; + + if (video->ep == NULL) { + uvcg_info(&video->uvc->func, + "Video disable failed, device is uninitialized.\n"); + return -ENODEV; + } + + INIT_LIST_HEAD(&inflight_bufs); + spin_lock_irqsave(&video->req_lock, flags); + video->is_enabled = false; + + /* + * Remove any in-flight buffers from the uvc_requests + * because we want to return them before cancelling the + * queue. This ensures that we aren't stuck waiting for + * all complete callbacks to come through before disabling + * vb2 queue. + */ + list_for_each_entry(ureq, &video->ureqs, list) { + if (ureq->last_buf) { + list_add_tail(&ureq->last_buf->queue, &inflight_bufs); + ureq->last_buf = NULL; + } + } + spin_unlock_irqrestore(&video->req_lock, flags); + + cancel_work_sync(&video->pump); + uvcg_queue_cancel(&video->queue, 0); + + spin_lock_irqsave(&video->req_lock, flags); + /* + * Remove all uvc_requests from ureqs with list_del_init + * This lets uvc_video_free_request correctly identify + * if the uvc_request is attached to a list or not when freeing + * memory. + */ + list_for_each_entry_safe(ureq, utemp, &video->ureqs, list) + list_del_init(&ureq->list); + + list_for_each_entry_safe(req, temp, &video->req_free, list) { + list_del(&req->list); + uvc_video_free_request(req->context, video->ep); + } + + list_for_each_entry_safe(req, temp, &video->req_ready, list) { + list_del(&req->list); + uvc_video_free_request(req->context, video->ep); + } + + INIT_LIST_HEAD(&video->ureqs); + INIT_LIST_HEAD(&video->req_free); + INIT_LIST_HEAD(&video->req_ready); + spin_unlock_irqrestore(&video->req_lock, flags); + + /* + * Return all the video buffers before disabling the queue. + */ + spin_lock_irqsave(&video->queue.irqlock, flags); + list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) { + list_del(&buf->queue); + uvcg_complete_buffer(&video->queue, buf); + } + spin_unlock_irqrestore(&video->queue.irqlock, flags); + + uvcg_queue_enable(&video->queue, 0); + return 0; +} + +/* + * Enable the video stream. + */ +int uvcg_video_enable(struct uvc_video *video) +{ + int ret; + + if (video->ep == NULL) { + uvcg_info(&video->uvc->func, + "Video enable failed, device is uninitialized.\n"); + return -ENODEV; + } + + /* + * Safe to access request related fields without req_lock because + * this is the only thread currently active, and no other + * request handling thread will become active until this function + * returns. + */ + video->is_enabled = true; + + if ((ret = uvcg_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 = video->queue.use_sg ? + uvc_video_encode_isoc_sg : uvc_video_encode_isoc; + + video->req_int_count = 0; + + atomic_set(&video->queued, 0); + + kthread_queue_work(video->kworker, &video->hw_submit); + queue_work(video->async_wq, &video->pump); + + return ret; +} + +/* + * Initialize the UVC video stream. + */ +int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc) +{ + video->is_enabled = false; + INIT_LIST_HEAD(&video->ureqs); + INIT_LIST_HEAD(&video->req_free); + INIT_LIST_HEAD(&video->req_ready); + spin_lock_init(&video->req_lock); + INIT_WORK(&video->pump, uvcg_video_pump); + + /* Allocate a work queue for asynchronous video pump handler. */ + video->async_wq = alloc_workqueue("uvcgadget", WQ_UNBOUND | WQ_HIGHPRI, 0); + if (!video->async_wq) + return -EINVAL; + + /* Allocate a kthread for asynchronous hw submit handler. */ + video->kworker = kthread_run_worker(0, "UVCG"); + if (IS_ERR(video->kworker)) { + uvcg_err(&uvc->func, "failed to create UVCG kworker\n"); + return PTR_ERR(video->kworker); + } + + kthread_init_work(&video->hw_submit, uvcg_video_hw_submit); + + sched_set_fifo(video->kworker->task); + + video->uvc = uvc; + video->fcc = V4L2_PIX_FMT_YUYV; + video->bpp = 16; + video->width = 320; + video->height = 240; + video->imagesize = 320 * 240 * 2; + video->interval = 666666; + + /* Initialize the video buffers queue. */ + return uvcg_queue_init(&video->queue, uvc->v4l2_dev.dev->parent, + V4L2_BUF_TYPE_VIDEO_OUTPUT, &video->mutex); +} diff --git a/drivers/usb/gadget/function/uvc_video.h b/drivers/usb/gadget/function/uvc_video.h new file mode 100644 index 000000000..8ef625974 --- /dev/null +++ b/drivers/usb/gadget/function/uvc_video.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * uvc_video.h -- USB Video Class Gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * Author: Andrzej Pietrasiewicz + */ +#ifndef __UVC_VIDEO_H__ +#define __UVC_VIDEO_H__ + +struct uvc_video; + +int uvcg_video_enable(struct uvc_video *video); +int uvcg_video_disable(struct uvc_video *video); + +int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc); + +#endif /* __UVC_VIDEO_H__ */ diff --git a/drivers/usb/gadget/functions.c b/drivers/usb/gadget/functions.c new file mode 100644 index 000000000..203361a64 --- /dev/null +++ b/drivers/usb/gadget/functions.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 +#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/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig new file mode 100644 index 000000000..0a7b382fb --- /dev/null +++ b/drivers/usb/gadget/legacy/Kconfig @@ -0,0 +1,532 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# 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). +# +# 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. +# +# The available choices each represent a single precomposed USB +# gadget configuration. In the device model, each option contains +# both the device instantiation as a child for a USB gadget +# controller, and the relevant drivers for each function declared +# by the device. + +menu "USB Gadget precomposed configurations" + +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 + bool "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 + select USB_F_UAC1 if (GADGET_UAC1 && !GADGET_UAC1_LEGACY) + select USB_F_UAC1_LEGACY if (GADGET_UAC1 && GADGET_UAC1_LEGACY) + select USB_F_UAC2 if !GADGET_UAC1 + select USB_U_AUDIO if (USB_F_UAC2 || USB_F_UAC1) + 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" + depends on USB_AUDIO + help + If you instead want older USB Audio Class specification 1.0 support + with similar driver capabilities. + +config GADGET_UAC1_LEGACY + bool "UAC 1.0 (Legacy)" + depends on GADGET_UAC1 + help + If you instead want legacy 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 USB_U_ETHER + select USB_F_ECM + select USB_F_SUBSET + 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 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 + select USB_F_RNDIS + 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 + select USB_F_EEM + 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 USB_U_ETHER + select USB_F_NCM + 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_F_FS + 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 + select USB_U_ETHER + select USB_F_ECM + select USB_F_SUBSET + 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 + select USB_U_ETHER + select USB_F_RNDIS + 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 + select USB_F_MASS_STORAGE + 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 + select USB_F_TCM + 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.rst + 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 + select USB_F_MIDI + 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 + select USB_F_PRINTER + 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.rst + which includes sample code for accessing the device file. + +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_U_ETHER + select USB_F_ACM + select USB_F_ECM + 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 + depends on BLOCK + select USB_LIBCOMPOSITE + select USB_U_SERIAL + select USB_U_ETHER + select USB_F_ACM + select USB_F_OBEX + select USB_F_PHONET + select USB_F_ECM + select USB_F_MASS_STORAGE + 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 + select USB_F_MASS_STORAGE + 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_U_ETHER + select USB_F_ACM + select USB_F_MASS_STORAGE + 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 + select USB_F_RNDIS + 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 + select USB_F_ECM + 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 + select USB_F_HID + help + The HID gadget driver provides generic emulation of USB + Human Interface Devices (HID). + + For more information, see Documentation/usb/gadget_hid.rst 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_DMA_SG + select VIDEOBUF2_VMALLOC + select USB_F_UVC + 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". + +config USB_RAW_GADGET + tristate "USB Raw Gadget" + help + USB Raw Gadget is a gadget driver that gives userspace low-level + control over the gadget's communication process. + + Like any other gadget driver, Raw Gadget implements USB devices via + the USB gadget API. Unlike most gadget drivers, Raw Gadget does not + implement any concrete USB functions itself but requires userspace + to do that. + + See Documentation/usb/raw-gadget.rst for details. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "raw_gadget". + +endmenu diff --git a/drivers/usb/gadget/legacy/Makefile b/drivers/usb/gadget/legacy/Makefile new file mode 100644 index 000000000..4d864bf82 --- /dev/null +++ b/drivers/usb/gadget/legacy/Makefile @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# USB gadget drivers +# + +ccflags-y := -I$(srctree)/drivers/usb/gadget/ +ccflags-y += -I$(srctree)/drivers/usb/gadget/udc/ +ccflags-y += -I$(srctree)/drivers/usb/gadget/function/ + +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_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_GADGET_TARGET) += tcm_usb_gadget.o +obj-$(CONFIG_USB_RAW_GADGET) += raw_gadget.o diff --git a/drivers/usb/gadget/legacy/acm_ms.c b/drivers/usb/gadget/legacy/acm_ms.c new file mode 100644 index 000000000..e8033e5f0 --- /dev/null +++ b/drivers/usb/gadget/legacy/acm_ms.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#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*/ + +#include "f_mass_storage.h" + +/*-------------------------------------------------------------------------*/ +USB_GADGET_COMPOSITE_OPTIONS(); + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + /* .bcdUSB = DYNAMIC */ + + .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 const struct usb_descriptor_header *otg_desc[2]; + +/* 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 }; +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_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_GADGET_DEBUG_FILES */ + +FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data); + +/*-------------------------------------------------------------------------*/ +static struct usb_function *f_acm; +static struct usb_function_instance *f_acm_inst; + +static struct usb_function_instance *fi_msg; +static struct usb_function *f_msg; + +/* + * We _always_ have both ACM and mass storage functions. + */ +static int 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 = usb_get_function(f_acm_inst); + if (IS_ERR(f_acm)) + return PTR_ERR(f_acm); + + f_msg = usb_get_function(fi_msg); + if (IS_ERR(f_msg)) { + status = PTR_ERR(f_msg); + goto put_acm; + } + + status = usb_add_function(c, f_acm); + if (status < 0) + goto put_msg; + + status = usb_add_function(c, f_msg); + if (status) + goto remove_acm; + + return 0; +remove_acm: + usb_remove_function(c, f_acm); +put_msg: + usb_put_function(f_msg); +put_acm: + usb_put_function(f_acm); + return status; +} + +static struct usb_configuration acm_ms_config_driver = { + .label = DRIVER_DESC, + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/*-------------------------------------------------------------------------*/ + +static int acm_ms_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + struct fsg_opts *opts; + struct fsg_config config; + int status; + + f_acm_inst = usb_get_function_instance("acm"); + if (IS_ERR(f_acm_inst)) + return PTR_ERR(f_acm_inst); + + fi_msg = usb_get_function_instance("mass_storage"); + if (IS_ERR(fi_msg)) { + status = PTR_ERR(fi_msg); + goto fail_get_msg; + } + + /* set up mass storage function */ + fsg_config_from_params(&config, &fsg_mod_data, fsg_num_buffers); + opts = fsg_opts_from_func_inst(fi_msg); + + opts->no_configfs = true; + status = fsg_common_set_num_buffers(opts->common, fsg_num_buffers); + if (status) + goto fail; + + status = fsg_common_set_cdev(opts->common, cdev, config.can_stall); + if (status) + goto fail_set_cdev; + + fsg_common_set_sysfs(opts->common, true); + status = fsg_common_create_luns(opts->common, &config); + if (status) + goto fail_set_cdev; + + fsg_common_set_inquiry_string(opts->common, config.vendor_name, + config.product_name); + /* + * 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_string_ids; + device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + + if (gadget_is_otg(gadget) && !otg_desc[0]) { + struct usb_descriptor_header *usb_desc; + + usb_desc = usb_otg_descriptor_alloc(gadget); + if (!usb_desc) { + status = -ENOMEM; + goto fail_string_ids; + } + usb_otg_descriptor_init(gadget, usb_desc); + otg_desc[0] = usb_desc; + otg_desc[1] = NULL; + } + + /* register our configuration */ + status = usb_add_config(cdev, &acm_ms_config_driver, acm_ms_do_config); + if (status < 0) + goto fail_otg_desc; + + usb_composite_overwrite_options(cdev, &coverwrite); + dev_info(&gadget->dev, "%s, version: " DRIVER_VERSION "\n", + DRIVER_DESC); + return 0; + + /* error recovery */ +fail_otg_desc: + kfree(otg_desc[0]); + otg_desc[0] = NULL; +fail_string_ids: + fsg_common_remove_luns(opts->common); +fail_set_cdev: + fsg_common_free_buffers(opts->common); +fail: + usb_put_function_instance(fi_msg); +fail_get_msg: + usb_put_function_instance(f_acm_inst); + return status; +} + +static int acm_ms_unbind(struct usb_composite_dev *cdev) +{ + usb_put_function(f_msg); + usb_put_function_instance(fi_msg); + usb_put_function(f_acm); + usb_put_function_instance(f_acm_inst); + kfree(otg_desc[0]); + otg_desc[0] = NULL; + + return 0; +} + +static 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 = acm_ms_unbind, +}; + +module_usb_composite_driver(acm_ms_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Klaus Schwarzkopf "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/gadget/legacy/audio.c b/drivers/usb/gadget/legacy/audio.c new file mode 100644 index 000000000..76ea6decf --- /dev/null +++ b/drivers/usb/gadget/legacy/audio.c @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * audio.c -- Audio gadget driver + * + * Copyright (C) 2008 Bryan Wu + * Copyright (C) 2008 Analog Devices, Inc + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include + +#define DRIVER_DESC "Linux USB Audio Gadget" +#define DRIVER_VERSION "Feb 2, 2012" + +USB_GADGET_COMPOSITE_OPTIONS(); + +#ifndef CONFIG_GADGET_UAC1 +#include "u_uac2.h" + +/* Playback(USB-IN) Default Stereo - Fl/Fr */ +static int p_chmask = UAC2_DEF_PCHMASK; +module_param(p_chmask, uint, 0444); +MODULE_PARM_DESC(p_chmask, "Playback Channel Mask"); + +/* Playback Default 48 KHz */ +static int p_srates[UAC_MAX_RATES] = {UAC2_DEF_PSRATE}; +static int p_srates_cnt = 1; +module_param_array_named(p_srate, p_srates, uint, &p_srates_cnt, 0444); +MODULE_PARM_DESC(p_srate, "Playback Sampling Rates (array)"); + +/* Playback Default 16bits/sample */ +static int p_ssize = UAC2_DEF_PSSIZE; +module_param(p_ssize, uint, 0444); +MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)"); + +/* Playback bInterval for HS/SS (1-4: fixed, 0: auto) */ +static u8 p_hs_bint = UAC2_DEF_PHSBINT; +module_param(p_hs_bint, byte, 0444); +MODULE_PARM_DESC(p_hs_bint, + "Playback bInterval for HS/SS (1-4: fixed, 0: auto)"); + +/* Capture(USB-OUT) Default Stereo - Fl/Fr */ +static int c_chmask = UAC2_DEF_CCHMASK; +module_param(c_chmask, uint, 0444); +MODULE_PARM_DESC(c_chmask, "Capture Channel Mask"); + +/* Capture Default 64 KHz */ +static int c_srates[UAC_MAX_RATES] = {UAC2_DEF_CSRATE}; +static int c_srates_cnt = 1; +module_param_array_named(c_srate, c_srates, uint, &c_srates_cnt, 0444); +MODULE_PARM_DESC(c_srate, "Capture Sampling Rates (array)"); + +/* Capture Default 16bits/sample */ +static int c_ssize = UAC2_DEF_CSSIZE; +module_param(c_ssize, uint, 0444); +MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)"); + +/* capture bInterval for HS/SS (1-4: fixed, 0: auto) */ +static u8 c_hs_bint = UAC2_DEF_CHSBINT; +module_param(c_hs_bint, byte, 0444); +MODULE_PARM_DESC(c_hs_bint, + "Capture bInterval for HS/SS (1-4: fixed, 0: auto)"); + +#else +#ifndef CONFIG_GADGET_UAC1_LEGACY +#include "u_uac1.h" + +/* Playback(USB-IN) Default Stereo - Fl/Fr */ +static int p_chmask = UAC1_DEF_PCHMASK; +module_param(p_chmask, uint, 0444); +MODULE_PARM_DESC(p_chmask, "Playback Channel Mask"); + +/* Playback Default 48 KHz */ +static int p_srates[UAC_MAX_RATES] = {UAC1_DEF_PSRATE}; +static int p_srates_cnt = 1; +module_param_array_named(p_srate, p_srates, uint, &p_srates_cnt, 0444); +MODULE_PARM_DESC(p_srate, "Playback Sampling Rates (array)"); + +/* Playback Default 16bits/sample */ +static int p_ssize = UAC1_DEF_PSSIZE; +module_param(p_ssize, uint, 0444); +MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)"); + +/* Capture(USB-OUT) Default Stereo - Fl/Fr */ +static int c_chmask = UAC1_DEF_CCHMASK; +module_param(c_chmask, uint, 0444); +MODULE_PARM_DESC(c_chmask, "Capture Channel Mask"); + +/* Capture Default 48 KHz */ +static int c_srates[UAC_MAX_RATES] = {UAC1_DEF_CSRATE}; +static int c_srates_cnt = 1; +module_param_array_named(c_srate, c_srates, uint, &c_srates_cnt, 0444); +MODULE_PARM_DESC(c_srate, "Capture Sampling Rates (array)"); + +/* Capture Default 16bits/sample */ +static int c_ssize = UAC1_DEF_CSSIZE; +module_param(c_ssize, uint, 0444); +MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)"); +#else /* CONFIG_GADGET_UAC1_LEGACY */ +#include "u_uac1_legacy.h" + +static char *fn_play = FILE_PCM_PLAYBACK; +module_param(fn_play, charp, 0444); +MODULE_PARM_DESC(fn_play, "Playback PCM device file name"); + +static char *fn_cap = FILE_PCM_CAPTURE; +module_param(fn_cap, charp, 0444); +MODULE_PARM_DESC(fn_cap, "Capture PCM device file name"); + +static char *fn_cntl = FILE_CONTROL; +module_param(fn_cntl, charp, 0444); +MODULE_PARM_DESC(fn_cntl, "Control device file name"); + +static int req_buf_size = UAC1_OUT_EP_MAX_PACKET_SIZE; +module_param(req_buf_size, int, 0444); +MODULE_PARM_DESC(req_buf_size, "ISO OUT endpoint request buffer size"); + +static int req_count = UAC1_REQ_COUNT; +module_param(req_count, int, 0444); +MODULE_PARM_DESC(req_count, "ISO OUT endpoint request count"); + +static int audio_buf_size = UAC1_AUDIO_BUF_SIZE; +module_param(audio_buf_size, int, 0444); +MODULE_PARM_DESC(audio_buf_size, "Audio buffer size"); +#endif /* CONFIG_GADGET_UAC1_LEGACY */ +#endif + +/* 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, +}; + +#ifndef CONFIG_GADGET_UAC1 +static struct usb_function_instance *fi_uac2; +static struct usb_function *f_uac2; +#else +static struct usb_function_instance *fi_uac1; +static struct usb_function *f_uac1; +#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 = DYNAMIC */ + +#ifdef CONFIG_GADGET_UAC1_LEGACY + .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 = cpu_to_le16(AUDIO_VENDOR_NUM), + .idProduct = cpu_to_le16(AUDIO_PRODUCT_NUM), + /* .bcdDevice = f(hardware) */ + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + /* NO SERIAL NUMBER */ + .bNumConfigurations = 1, +}; + +static const struct usb_descriptor_header *otg_desc[2]; + +/*-------------------------------------------------------------------------*/ + +static int audio_do_config(struct usb_configuration *c) +{ + int status; + + /* 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; + } + +#ifdef CONFIG_GADGET_UAC1 + f_uac1 = usb_get_function(fi_uac1); + if (IS_ERR(f_uac1)) { + status = PTR_ERR(f_uac1); + return status; + } + + status = usb_add_function(c, f_uac1); + if (status < 0) { + usb_put_function(f_uac1); + return status; + } +#else + f_uac2 = usb_get_function(fi_uac2); + if (IS_ERR(f_uac2)) { + status = PTR_ERR(f_uac2); + return status; + } + + status = usb_add_function(c, f_uac2); + if (status < 0) { + usb_put_function(f_uac2); + return status; + } +#endif + + return 0; +} + +static struct usb_configuration audio_config_driver = { + .label = DRIVER_DESC, + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/*-------------------------------------------------------------------------*/ + +static int audio_bind(struct usb_composite_dev *cdev) +{ +#ifndef CONFIG_GADGET_UAC1 + struct f_uac2_opts *uac2_opts; + int i; +#else +#ifndef CONFIG_GADGET_UAC1_LEGACY + struct f_uac1_opts *uac1_opts; + int i; +#else + struct f_uac1_legacy_opts *uac1_opts; +#endif +#endif + int status; + +#ifndef CONFIG_GADGET_UAC1 + fi_uac2 = usb_get_function_instance("uac2"); + if (IS_ERR(fi_uac2)) + return PTR_ERR(fi_uac2); +#else +#ifndef CONFIG_GADGET_UAC1_LEGACY + fi_uac1 = usb_get_function_instance("uac1"); +#else + fi_uac1 = usb_get_function_instance("uac1_legacy"); +#endif + if (IS_ERR(fi_uac1)) + return PTR_ERR(fi_uac1); +#endif + +#ifndef CONFIG_GADGET_UAC1 + uac2_opts = container_of(fi_uac2, struct f_uac2_opts, func_inst); + uac2_opts->p_chmask = p_chmask; + + for (i = 0; i < p_srates_cnt; ++i) + uac2_opts->p_srates[i] = p_srates[i]; + + uac2_opts->p_ssize = p_ssize; + uac2_opts->p_hs_bint = p_hs_bint; + uac2_opts->c_chmask = c_chmask; + + for (i = 0; i < c_srates_cnt; ++i) + uac2_opts->c_srates[i] = c_srates[i]; + + uac2_opts->c_ssize = c_ssize; + uac2_opts->c_hs_bint = c_hs_bint; + uac2_opts->req_number = UAC2_DEF_REQ_NUM; +#else +#ifndef CONFIG_GADGET_UAC1_LEGACY + uac1_opts = container_of(fi_uac1, struct f_uac1_opts, func_inst); + uac1_opts->p_chmask = p_chmask; + + for (i = 0; i < p_srates_cnt; ++i) + uac1_opts->p_srates[i] = p_srates[i]; + + uac1_opts->p_ssize = p_ssize; + uac1_opts->c_chmask = c_chmask; + + for (i = 0; i < c_srates_cnt; ++i) + uac1_opts->c_srates[i] = c_srates[i]; + + uac1_opts->c_ssize = c_ssize; + uac1_opts->req_number = UAC1_DEF_REQ_NUM; +#else /* CONFIG_GADGET_UAC1_LEGACY */ + uac1_opts = container_of(fi_uac1, struct f_uac1_legacy_opts, func_inst); + uac1_opts->fn_play = fn_play; + uac1_opts->fn_cap = fn_cap; + uac1_opts->fn_cntl = fn_cntl; + uac1_opts->req_buf_size = req_buf_size; + uac1_opts->req_count = req_count; + uac1_opts->audio_buf_size = audio_buf_size; +#endif /* CONFIG_GADGET_UAC1_LEGACY */ +#endif + + 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; + + if (gadget_is_otg(cdev->gadget) && !otg_desc[0]) { + struct usb_descriptor_header *usb_desc; + + usb_desc = usb_otg_descriptor_alloc(cdev->gadget); + if (!usb_desc) { + status = -ENOMEM; + goto fail; + } + usb_otg_descriptor_init(cdev->gadget, usb_desc); + otg_desc[0] = usb_desc; + otg_desc[1] = NULL; + } + + status = usb_add_config(cdev, &audio_config_driver, audio_do_config); + if (status < 0) + goto fail_otg_desc; + usb_composite_overwrite_options(cdev, &coverwrite); + + INFO(cdev, "%s, version: %s\n", DRIVER_DESC, DRIVER_VERSION); + return 0; + +fail_otg_desc: + kfree(otg_desc[0]); + otg_desc[0] = NULL; +fail: +#ifndef CONFIG_GADGET_UAC1 + usb_put_function_instance(fi_uac2); +#else + usb_put_function_instance(fi_uac1); +#endif + return status; +} + +static int audio_unbind(struct usb_composite_dev *cdev) +{ +#ifdef CONFIG_GADGET_UAC1 + if (!IS_ERR_OR_NULL(f_uac1)) + usb_put_function(f_uac1); + if (!IS_ERR_OR_NULL(fi_uac1)) + usb_put_function_instance(fi_uac1); +#else + if (!IS_ERR_OR_NULL(f_uac2)) + usb_put_function(f_uac2); + if (!IS_ERR_OR_NULL(fi_uac2)) + usb_put_function_instance(fi_uac2); +#endif + kfree(otg_desc[0]); + otg_desc[0] = NULL; + + return 0; +} + +static struct usb_composite_driver audio_driver = { + .name = "g_audio", + .dev = &device_desc, + .strings = audio_strings, + .max_speed = USB_SPEED_HIGH, + .bind = audio_bind, + .unbind = audio_unbind, +}; + +module_usb_composite_driver(audio_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Bryan Wu "); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/gadget/legacy/cdc2.c b/drivers/usb/gadget/legacy/cdc2.c new file mode 100644 index 000000000..563363aba --- /dev/null +++ b/drivers/usb/gadget/legacy/cdc2.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * cdc2.c -- CDC Composite driver, with ECM and ACM support + * + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 Nokia Corporation + */ + +#include +#include + +#include "u_ether.h" +#include "u_serial.h" +#include "u_ecm.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(); + +USB_ETHERNET_MODULE_PARAMETERS(); + +/*-------------------------------------------------------------------------*/ + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + /* .bcdUSB = DYNAMIC */ + + .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 const struct usb_descriptor_header *otg_desc[2]; + +/* 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 struct usb_function *f_acm; +static struct usb_function_instance *fi_serial; + +static struct usb_function *f_ecm; +static struct usb_function_instance *fi_ecm; + +/* + * We _always_ have both CDC ECM and CDC ACM functions. + */ +static int 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; + } + + f_ecm = usb_get_function(fi_ecm); + if (IS_ERR(f_ecm)) { + status = PTR_ERR(f_ecm); + goto err_get_ecm; + } + + status = usb_add_function(c, f_ecm); + if (status) + goto err_add_ecm; + + f_acm = usb_get_function(fi_serial); + if (IS_ERR(f_acm)) { + status = PTR_ERR(f_acm); + goto err_get_acm; + } + + status = usb_add_function(c, f_acm); + if (status) + goto err_add_acm; + return 0; + +err_add_acm: + usb_put_function(f_acm); +err_get_acm: + usb_remove_function(c, f_ecm); +err_add_ecm: + usb_put_function(f_ecm); +err_get_ecm: + 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 cdc_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + struct f_ecm_opts *ecm_opts; + int status; + + if (!can_support_ecm(cdev->gadget)) { + dev_err(&gadget->dev, "controller '%s' not usable\n", + gadget->name); + return -EINVAL; + } + + fi_ecm = usb_get_function_instance("ecm"); + if (IS_ERR(fi_ecm)) + return PTR_ERR(fi_ecm); + + ecm_opts = container_of(fi_ecm, struct f_ecm_opts, func_inst); + + gether_set_qmult(ecm_opts->net, qmult); + if (!gether_set_host_addr(ecm_opts->net, host_addr)) + pr_info("using host ethernet address: %s", host_addr); + if (!gether_set_dev_addr(ecm_opts->net, dev_addr)) + pr_info("using self ethernet address: %s", dev_addr); + + fi_serial = usb_get_function_instance("acm"); + if (IS_ERR(fi_serial)) { + status = PTR_ERR(fi_serial); + goto fail; + } + + /* 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; + + if (gadget_is_otg(gadget) && !otg_desc[0]) { + struct usb_descriptor_header *usb_desc; + + usb_desc = usb_otg_descriptor_alloc(gadget); + if (!usb_desc) { + status = -ENOMEM; + goto fail1; + } + usb_otg_descriptor_init(gadget, usb_desc); + otg_desc[0] = usb_desc; + otg_desc[1] = NULL; + } + + /* register our configuration */ + status = usb_add_config(cdev, &cdc_config_driver, cdc_do_config); + if (status < 0) + goto fail2; + + usb_composite_overwrite_options(cdev, &coverwrite); + dev_info(&gadget->dev, "%s, version: " DRIVER_VERSION "\n", + DRIVER_DESC); + + return 0; + +fail2: + kfree(otg_desc[0]); + otg_desc[0] = NULL; +fail1: + usb_put_function_instance(fi_serial); +fail: + usb_put_function_instance(fi_ecm); + return status; +} + +static int cdc_unbind(struct usb_composite_dev *cdev) +{ + usb_put_function(f_acm); + usb_put_function_instance(fi_serial); + if (!IS_ERR_OR_NULL(f_ecm)) + usb_put_function(f_ecm); + if (!IS_ERR_OR_NULL(fi_ecm)) + usb_put_function_instance(fi_ecm); + kfree(otg_desc[0]); + otg_desc[0] = NULL; + + return 0; +} + +static struct usb_composite_driver cdc_driver = { + .name = "g_cdc", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_SUPER, + .bind = cdc_bind, + .unbind = cdc_unbind, +}; + +module_usb_composite_driver(cdc_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("David Brownell"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/legacy/dbgp.c b/drivers/usb/gadget/legacy/dbgp.c new file mode 100644 index 000000000..03032ea6f --- /dev/null +++ b/drivers/usb/gadget/legacy/dbgp.c @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_VENDOR_SPEC, + .idVendor = cpu_to_le16(DRIVER_VENDOR_ID), + .idProduct = 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) +{ + usb_ep_disable(ep); +} + +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 = kzalloc(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); + 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); + dbgp.serial = NULL; +#endif + if (dbgp.req) { + kfree(dbgp.req->buf); + usb_ep_free_request(gadget->ep0, dbgp.req); + dbgp.req = NULL; + } +} + +#ifdef CONFIG_USB_G_DBGP_SERIAL +static unsigned char tty_line; +#endif + +static int 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; + } + + i_desc.wMaxPacketSize = + cpu_to_le16(USB_DEBUG_MAX_PACKET_SIZE); + + dbgp.o_ep = usb_ep_autoconfig(gadget, &o_desc); + if (!dbgp.o_ep) { + stp = 2; + goto fail_1; + } + + o_desc.wMaxPacketSize = + 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; +#endif + + return 0; + +fail_1: + dev_dbg(&dbgp.gadget->dev, "ep config: failure (%d)\n", stp); + return -ENODEV; +} + +static int 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; + +#ifdef CONFIG_USB_G_DBGP_SERIAL + dbgp.serial = kzalloc_obj(struct gserial); + if (!dbgp.serial) { + stp = 3; + err = -ENOMEM; + goto fail; + } + + if (gserial_alloc_line(&tty_line)) { + stp = 4; + err = -ENODEV; + goto fail; + } +#endif + + err = dbgp_configure_endpoints(gadget); + if (err < 0) { + stp = 5; + 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; + + if (length > DBGP_REQ_LEN) { + if (ctrl->bRequestType & USB_DIR_IN) { + /* Cast away the const, we are going to overwrite on purpose. */ + __le16 *temp = (__le16 *)&ctrl->wLength; + + *temp = cpu_to_le16(DBGP_REQ_LEN); + length = DBGP_REQ_LEN; + } else { + return err; + } + } + + + 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 = dbgp_configure_endpoints(gadget); + if (err < 0) { + goto fail; + } + 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 struct usb_gadget_driver dbgp_driver = { + .function = "dbgp", + .max_speed = USB_SPEED_HIGH, + .bind = dbgp_bind, + .unbind = dbgp_unbind, + .setup = dbgp_setup, + .reset = dbgp_disconnect, + .disconnect = dbgp_disconnect, + .driver = { + .owner = THIS_MODULE, + .name = "dbgp" + }, +}; + +static int __init dbgp_init(void) +{ + return usb_gadget_register_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_DESCRIPTION("EHCI Debug Port device gadget"); +MODULE_LICENSE("GPL"); +module_init(dbgp_init); +module_exit(dbgp_exit); diff --git a/drivers/usb/gadget/legacy/ether.c b/drivers/usb/gadget/legacy/ether.c new file mode 100644 index 000000000..99c7fc0d1 --- /dev/null +++ b/drivers/usb/gadget/legacy/ether.c @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +/* #define VERBOSE_DEBUG */ + +#include +#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 +} + +#include + +#include "u_ecm.h" +#include "u_gether.h" +#ifdef USB_ETH_RNDIS +#include "u_rndis.h" +#include "rndis.h" +#else +#define rndis_borrow_net(...) do {} while (0) +#endif +#include "u_eem.h" + +/*-------------------------------------------------------------------------*/ +USB_GADGET_COMPOSITE_OPTIONS(); + +USB_ETHERNET_MODULE_PARAMETERS(); + +/* 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 = DYNAMIC */ + + .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 const struct usb_descriptor_header *otg_desc[2]; + +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 struct usb_function_instance *fi_ecm; +static struct usb_function *f_ecm; + +static struct usb_function_instance *fi_eem; +static struct usb_function *f_eem; + +static struct usb_function_instance *fi_geth; +static struct usb_function *f_geth; + +static struct usb_function_instance *fi_rndis; +static struct usb_function *f_rndis; + +/*-------------------------------------------------------------------------*/ + +/* + * 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 rndis_do_config(struct usb_configuration *c) +{ + int status; + + /* 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; + } + + f_rndis = usb_get_function(fi_rndis); + if (IS_ERR(f_rndis)) + return PTR_ERR(f_rndis); + + status = usb_add_function(c, f_rndis); + if (status < 0) + usb_put_function(f_rndis); + + return status; +} + +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 eth_do_config(struct usb_configuration *c) +{ + int status = 0; + + /* 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) { + f_eem = usb_get_function(fi_eem); + if (IS_ERR(f_eem)) + return PTR_ERR(f_eem); + + status = usb_add_function(c, f_eem); + if (status < 0) + usb_put_function(f_eem); + + return status; + } else if (can_support_ecm(c->cdev->gadget)) { + f_ecm = usb_get_function(fi_ecm); + if (IS_ERR(f_ecm)) + return PTR_ERR(f_ecm); + + status = usb_add_function(c, f_ecm); + if (status < 0) + usb_put_function(f_ecm); + + return status; + } else { + f_geth = usb_get_function(fi_geth); + if (IS_ERR(f_geth)) + return PTR_ERR(f_geth); + + status = usb_add_function(c, f_geth); + if (status < 0) + usb_put_function(f_geth); + + return status; + } + +} + +static struct usb_configuration eth_config_driver = { + /* .label = f(hardware) */ + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/*-------------------------------------------------------------------------*/ + +static int eth_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + struct f_eem_opts *eem_opts = NULL; + struct f_ecm_opts *ecm_opts = NULL; + struct f_gether_opts *geth_opts = NULL; + struct net_device *net; + int status; + + /* set up main config label and device descriptor */ + if (use_eem) { + /* EEM */ + fi_eem = usb_get_function_instance("eem"); + if (IS_ERR(fi_eem)) + return PTR_ERR(fi_eem); + + eem_opts = container_of(fi_eem, struct f_eem_opts, func_inst); + + net = eem_opts->net; + + 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(gadget)) { + /* ECM */ + + fi_ecm = usb_get_function_instance("ecm"); + if (IS_ERR(fi_ecm)) + return PTR_ERR(fi_ecm); + + ecm_opts = container_of(fi_ecm, struct f_ecm_opts, func_inst); + + net = ecm_opts->net; + + eth_config_driver.label = "CDC Ethernet (ECM)"; + } else { + /* CDC Subset */ + + fi_geth = usb_get_function_instance("geth"); + if (IS_ERR(fi_geth)) + return PTR_ERR(fi_geth); + + geth_opts = container_of(fi_geth, struct f_gether_opts, + func_inst); + + net = geth_opts->net; + + 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; + } + + gether_set_qmult(net, qmult); + if (!gether_set_host_addr(net, host_addr)) + pr_info("using host ethernet address: %s", host_addr); + if (!gether_set_dev_addr(net, dev_addr)) + pr_info("using self ethernet address: %s", dev_addr); + + if (has_rndis()) { + /* RNDIS plus ECM-or-Subset */ + gether_set_gadget(net, cdev->gadget); + status = gether_register_netdev(net); + if (status) + goto fail; + + if (use_eem) + eem_opts->bound = true; + else if (can_support_ecm(gadget)) + ecm_opts->bound = true; + else + geth_opts->bound = true; + + fi_rndis = usb_get_function_instance("rndis"); + if (IS_ERR(fi_rndis)) { + status = PTR_ERR(fi_rndis); + goto fail; + } + + rndis_borrow_net(fi_rndis, net); + + 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 fail1; + device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + + if (gadget_is_otg(gadget) && !otg_desc[0]) { + struct usb_descriptor_header *usb_desc; + + usb_desc = usb_otg_descriptor_alloc(gadget); + if (!usb_desc) { + status = -ENOMEM; + goto fail1; + } + usb_otg_descriptor_init(gadget, usb_desc); + otg_desc[0] = usb_desc; + otg_desc[1] = NULL; + } + + /* 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 fail2; + } + + status = usb_add_config(cdev, ð_config_driver, eth_do_config); + if (status < 0) + goto fail2; + + usb_composite_overwrite_options(cdev, &coverwrite); + dev_info(&gadget->dev, "%s, version: " DRIVER_VERSION "\n", + DRIVER_DESC); + + return 0; + +fail2: + kfree(otg_desc[0]); + otg_desc[0] = NULL; +fail1: + if (has_rndis()) + usb_put_function_instance(fi_rndis); +fail: + if (use_eem) + usb_put_function_instance(fi_eem); + else if (can_support_ecm(gadget)) + usb_put_function_instance(fi_ecm); + else + usb_put_function_instance(fi_geth); + return status; +} + +static int eth_unbind(struct usb_composite_dev *cdev) +{ + if (has_rndis()) { + usb_put_function(f_rndis); + usb_put_function_instance(fi_rndis); + } + if (use_eem) { + usb_put_function(f_eem); + usb_put_function_instance(fi_eem); + } else if (can_support_ecm(cdev->gadget)) { + usb_put_function(f_ecm); + usb_put_function_instance(fi_ecm); + } else { + usb_put_function(f_geth); + usb_put_function_instance(fi_geth); + } + kfree(otg_desc[0]); + otg_desc[0] = NULL; + + return 0; +} + +static struct usb_composite_driver eth_driver = { + .name = "g_ether", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_SUPER, + .bind = eth_bind, + .unbind = eth_unbind, +}; + +module_usb_composite_driver(eth_driver); + +MODULE_DESCRIPTION(PREFIX DRIVER_DESC); +MODULE_AUTHOR("David Brownell, Benedikt Spanger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/legacy/g_ffs.c b/drivers/usb/gadget/legacy/g_ffs.c new file mode 100644 index 000000000..dc59e625a --- /dev/null +++ b/drivers/usb/gadget/legacy/g_ffs.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * g_ffs.c -- user mode file system API for USB composite function controllers + * + * Copyright (C) 2010 Samsung Electronics + * Author: Michal Nazarewicz + */ + +#define pr_fmt(fmt) "g_ffs: " fmt + +#include + +#if defined CONFIG_USB_FUNCTIONFS_ETH || defined CONFIG_USB_FUNCTIONFS_RNDIS +#include + +# if defined USB_ETH_RNDIS +# undef USB_ETH_RNDIS +# endif +# ifdef CONFIG_USB_FUNCTIONFS_RNDIS +# define USB_ETH_RNDIS y +# endif + +# include "u_ecm.h" +# include "u_gether.h" +# ifdef USB_ETH_RNDIS +# include "u_rndis.h" +# include "rndis.h" +# endif +# include "u_ether.h" + +USB_ETHERNET_MODULE_PARAMETERS(); + +# ifdef CONFIG_USB_FUNCTIONFS_ETH +static int eth_bind_config(struct usb_configuration *c); +static struct usb_function_instance *fi_ecm; +static struct usb_function *f_ecm; +static struct usb_function_instance *fi_geth; +static struct usb_function *f_geth; +# endif +# ifdef CONFIG_USB_FUNCTIONFS_RNDIS +static int bind_rndis_config(struct usb_configuration *c); +static struct usb_function_instance *fi_rndis; +static struct usb_function *f_rndis; +# endif +#endif + +#include "u_fs.h" + +#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 + +USB_GADGET_COMPOSITE_OPTIONS(); + +static struct usb_device_descriptor gfs_dev_desc = { + .bLength = sizeof gfs_dev_desc, + .bDescriptorType = USB_DT_DEVICE, + + /* .bcdUSB = DYNAMIC */ + .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[2]; + +/* 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); + int num; +}; + +static struct gfs_configuration gfs_configurations[] = { +#ifdef CONFIG_USB_FUNCTIONFS_RNDIS + { + .eth = bind_rndis_config, + }, +#endif + +#ifdef CONFIG_USB_FUNCTIONFS_ETH + { + .eth = eth_bind_config, + }, +#endif + +#ifdef CONFIG_USB_FUNCTIONFS_GENERIC + { + }, +#endif +}; + +static void *functionfs_acquire_dev(struct ffs_dev *dev); +static void functionfs_release_dev(struct ffs_dev *dev); +static int functionfs_ready_callback(struct ffs_data *ffs); +static void functionfs_closed_callback(struct ffs_data *ffs); +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 struct usb_composite_driver gfs_driver = { + .name = DRIVER_NAME, + .dev = &gfs_dev_desc, + .strings = gfs_dev_strings, + .max_speed = USB_SPEED_SUPER, + .bind = gfs_bind, + .unbind = gfs_unbind, +}; + +static unsigned int missing_funcs; +static bool gfs_registered; +static bool gfs_single_func; +static struct usb_function_instance **fi_ffs; +static struct usb_function **f_ffs[] = { +#ifdef CONFIG_USB_FUNCTIONFS_RNDIS + NULL, +#endif + +#ifdef CONFIG_USB_FUNCTIONFS_ETH + NULL, +#endif + +#ifdef CONFIG_USB_FUNCTIONFS_GENERIC + NULL, +#endif +}; + +#define N_CONF ARRAY_SIZE(f_ffs) + +static int __init gfs_init(void) +{ + struct f_fs_opts *opts; + int i; + int ret = 0; + + if (func_num < 2) { + gfs_single_func = true; + func_num = 1; + } + + /* + * Allocate in one chunk for easier maintenance + */ + f_ffs[0] = kzalloc_objs(*f_ffs[0], func_num * N_CONF); + if (!f_ffs[0]) { + ret = -ENOMEM; + goto no_func; + } + for (i = 1; i < N_CONF; ++i) + f_ffs[i] = f_ffs[0] + i * func_num; + + fi_ffs = kzalloc_objs(*fi_ffs, func_num); + if (!fi_ffs) { + ret = -ENOMEM; + goto no_func; + } + + for (i = 0; i < func_num; i++) { + fi_ffs[i] = usb_get_function_instance("ffs"); + if (IS_ERR(fi_ffs[i])) { + ret = PTR_ERR(fi_ffs[i]); + --i; + goto no_dev; + } + opts = to_f_fs_opts(fi_ffs[i]); + if (gfs_single_func) + ret = ffs_single_dev(opts->dev); + else + ret = ffs_name_dev(opts->dev, func_names[i]); + if (ret) + goto no_dev; + opts->dev->ffs_ready_callback = functionfs_ready_callback; + opts->dev->ffs_closed_callback = functionfs_closed_callback; + opts->dev->ffs_acquire_dev_callback = functionfs_acquire_dev; + opts->dev->ffs_release_dev_callback = functionfs_release_dev; + opts->no_configfs = true; + } + + missing_funcs = func_num; + + return 0; +no_dev: + while (i >= 0) + usb_put_function_instance(fi_ffs[i--]); + kfree(fi_ffs); +no_func: + kfree(f_ffs[0]); + return ret; +} +module_init(gfs_init); + +static void __exit gfs_exit(void) +{ + int i; + + if (gfs_registered) + usb_composite_unregister(&gfs_driver); + gfs_registered = false; + + kfree(f_ffs[0]); + + for (i = 0; i < func_num; i++) + usb_put_function_instance(fi_ffs[i]); + + kfree(fi_ffs); +} +module_exit(gfs_exit); + +static void *functionfs_acquire_dev(struct ffs_dev *dev) +{ + if (!try_module_get(THIS_MODULE)) + return ERR_PTR(-ENOENT); + + return NULL; +} + +static void functionfs_release_dev(struct ffs_dev *dev) +{ + module_put(THIS_MODULE); +} + +/* + * The caller of this function takes ffs_lock + */ +static int functionfs_ready_callback(struct ffs_data *ffs) +{ + int ret = 0; + + if (--missing_funcs) + return 0; + + if (gfs_registered) + return -EBUSY; + + gfs_registered = true; + + ret = usb_composite_probe(&gfs_driver); + if (unlikely(ret < 0)) { + ++missing_funcs; + gfs_registered = false; + } + + return ret; +} + +/* + * The caller of this function takes ffs_lock + */ +static void functionfs_closed_callback(struct ffs_data *ffs) +{ + missing_funcs++; + + if (gfs_registered) + usb_composite_unregister(&gfs_driver); + gfs_registered = false; +} + +/* + * It is assumed that gfs_bind is called from a context where ffs_lock is held + */ +static int gfs_bind(struct usb_composite_dev *cdev) +{ +#if defined CONFIG_USB_FUNCTIONFS_ETH || defined CONFIG_USB_FUNCTIONFS_RNDIS + struct net_device *net; +#endif + int ret, i; + + if (missing_funcs) + return -ENODEV; +#if defined CONFIG_USB_FUNCTIONFS_ETH + if (can_support_ecm(cdev->gadget)) { + struct f_ecm_opts *ecm_opts; + + fi_ecm = usb_get_function_instance("ecm"); + if (IS_ERR(fi_ecm)) + return PTR_ERR(fi_ecm); + ecm_opts = container_of(fi_ecm, struct f_ecm_opts, func_inst); + net = ecm_opts->net; + } else { + struct f_gether_opts *geth_opts; + + fi_geth = usb_get_function_instance("geth"); + if (IS_ERR(fi_geth)) + return PTR_ERR(fi_geth); + geth_opts = container_of(fi_geth, struct f_gether_opts, + func_inst); + net = geth_opts->net; + } +#endif + +#ifdef CONFIG_USB_FUNCTIONFS_RNDIS + { + fi_rndis = usb_get_function_instance("rndis"); + if (IS_ERR(fi_rndis)) { + ret = PTR_ERR(fi_rndis); + goto error; + } +#ifndef CONFIG_USB_FUNCTIONFS_ETH + net = container_of(fi_rndis, struct f_rndis_opts, + func_inst)->net; +#endif + } +#endif + +#if defined CONFIG_USB_FUNCTIONFS_ETH || defined CONFIG_USB_FUNCTIONFS_RNDIS + gether_set_qmult(net, qmult); + if (!gether_set_host_addr(net, host_addr)) + pr_info("using host ethernet address: %s", host_addr); + if (!gether_set_dev_addr(net, dev_addr)) + pr_info("using self ethernet address: %s", dev_addr); +#endif + +#if defined CONFIG_USB_FUNCTIONFS_RNDIS && defined CONFIG_USB_FUNCTIONFS_ETH + gether_set_gadget(net, cdev->gadget); + ret = gether_register_netdev(net); + if (ret) + goto error_rndis; + + if (can_support_ecm(cdev->gadget)) { + struct f_ecm_opts *ecm_opts; + + ecm_opts = container_of(fi_ecm, struct f_ecm_opts, func_inst); + ecm_opts->bound = true; + } else { + struct f_gether_opts *geth_opts; + + geth_opts = container_of(fi_geth, struct f_gether_opts, + func_inst); + geth_opts->bound = true; + } + + rndis_borrow_net(fi_rndis, net); +#endif + + /* TODO: gstrings_attach? */ + ret = usb_string_ids_tab(cdev, gfs_strings); + if (unlikely(ret < 0)) + goto error_rndis; + gfs_dev_desc.iProduct = gfs_strings[USB_GADGET_PRODUCT_IDX].id; + + if (gadget_is_otg(cdev->gadget) && !gfs_otg_desc[0]) { + struct usb_descriptor_header *usb_desc; + + usb_desc = usb_otg_descriptor_alloc(cdev->gadget); + if (!usb_desc) { + ret = -ENOMEM; + goto error_rndis; + } + usb_otg_descriptor_init(cdev->gadget, usb_desc); + gfs_otg_desc[0] = usb_desc; + gfs_otg_desc[1] = NULL; + } + + 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; + + c->num = i; + + 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; + +/* TODO */ +error_unbind: + kfree(gfs_otg_desc[0]); + gfs_otg_desc[0] = NULL; +error_rndis: +#ifdef CONFIG_USB_FUNCTIONFS_RNDIS + usb_put_function_instance(fi_rndis); +error: +#endif +#if defined CONFIG_USB_FUNCTIONFS_ETH + if (can_support_ecm(cdev->gadget)) + usb_put_function_instance(fi_ecm); + else + usb_put_function_instance(fi_geth); +#endif + return ret; +} + +/* + * It is assumed that gfs_unbind is called from a context where ffs_lock is held + */ +static int gfs_unbind(struct usb_composite_dev *cdev) +{ + int i; + +#ifdef CONFIG_USB_FUNCTIONFS_RNDIS + usb_put_function(f_rndis); + usb_put_function_instance(fi_rndis); +#endif + +#if defined CONFIG_USB_FUNCTIONFS_ETH + if (can_support_ecm(cdev->gadget)) { + usb_put_function(f_ecm); + usb_put_function_instance(fi_ecm); + } else { + usb_put_function(f_geth); + usb_put_function_instance(fi_geth); + } +#endif + for (i = 0; i < N_CONF * func_num; ++i) + usb_put_function(*(f_ffs[0] + i)); + + kfree(gfs_otg_desc[0]); + gfs_otg_desc[0] = NULL; + + return 0; +} + +/* + * It is assumed that gfs_do_config is called from a context where + * ffs_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); + if (unlikely(ret < 0)) + return ret; + } + + for (i = 0; i < func_num; i++) { + f_ffs[gc->num][i] = usb_get_function(fi_ffs[i]); + if (IS_ERR(f_ffs[gc->num][i])) { + ret = PTR_ERR(f_ffs[gc->num][i]); + goto error; + } + ret = usb_add_function(c, f_ffs[gc->num][i]); + if (ret < 0) { + usb_put_function(f_ffs[gc->num][i]); + goto error; + } + } + + /* + * 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; +error: + while (--i >= 0) { + if (!IS_ERR(f_ffs[gc->num][i])) + usb_remove_function(c, f_ffs[gc->num][i]); + usb_put_function(f_ffs[gc->num][i]); + } + return ret; +} + +#ifdef CONFIG_USB_FUNCTIONFS_ETH + +static int eth_bind_config(struct usb_configuration *c) +{ + int status = 0; + + if (can_support_ecm(c->cdev->gadget)) { + f_ecm = usb_get_function(fi_ecm); + if (IS_ERR(f_ecm)) + return PTR_ERR(f_ecm); + + status = usb_add_function(c, f_ecm); + if (status < 0) + usb_put_function(f_ecm); + + } else { + f_geth = usb_get_function(fi_geth); + if (IS_ERR(f_geth)) + return PTR_ERR(f_geth); + + status = usb_add_function(c, f_geth); + if (status < 0) + usb_put_function(f_geth); + } + return status; +} + +#endif + +#ifdef CONFIG_USB_FUNCTIONFS_RNDIS + +static int bind_rndis_config(struct usb_configuration *c) +{ + int status = 0; + + f_rndis = usb_get_function(fi_rndis); + if (IS_ERR(f_rndis)) + return PTR_ERR(f_rndis); + + status = usb_add_function(c, f_rndis); + if (status < 0) + usb_put_function(f_rndis); + + return status; +} + +#endif diff --git a/drivers/usb/gadget/legacy/gmidi.c b/drivers/usb/gadget/legacy/gmidi.c new file mode 100644 index 000000000..e4a419b19 --- /dev/null +++ b/drivers/usb/gadget/legacy/gmidi.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * gmidi.c -- USB MIDI Gadget Driver + * + * Copyright (C) 2006 Thumtronics Pty Ltd. + * Developed for Thumtronics by Grey Innovation + * Ben Williamson + * + * 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 "u_midi.h" + +/*-------------------------------------------------------------------------*/ + +MODULE_AUTHOR("Ben Williamson"); +MODULE_DESCRIPTION("USB MIDI Gadget Driver"); +MODULE_LICENSE("GPL v2"); + +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 = 512; +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 and write 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 = DYNAMIC */ + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .idVendor = cpu_to_le16(DRIVER_VENDOR_NUM), + .idProduct = 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 struct usb_function_instance *fi_midi; +static struct usb_function *f_midi; + +static int midi_unbind(struct usb_composite_dev *dev) +{ + usb_put_function(f_midi); + usb_put_function_instance(fi_midi); + 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 midi_bind_config(struct usb_configuration *c) +{ + int status; + + f_midi = usb_get_function(fi_midi); + if (IS_ERR(f_midi)) + return PTR_ERR(f_midi); + + status = usb_add_function(c, f_midi); + if (status < 0) { + usb_put_function(f_midi); + return status; + } + + return 0; +} + +static int midi_bind(struct usb_composite_dev *cdev) +{ + struct f_midi_opts *midi_opts; + int status; + + fi_midi = usb_get_function_instance("midi"); + if (IS_ERR(fi_midi)) + return PTR_ERR(fi_midi); + + midi_opts = container_of(fi_midi, struct f_midi_opts, func_inst); + midi_opts->index = index; + midi_opts->id = id; + midi_opts->in_ports = in_ports; + midi_opts->out_ports = out_ports; + midi_opts->buflen = buflen; + midi_opts->qlen = qlen; + + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + goto put; + 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) + goto put; + usb_composite_overwrite_options(cdev, &coverwrite); + pr_info("%s\n", longname); + return 0; +put: + usb_put_function_instance(fi_midi); + return status; +} + +static struct usb_composite_driver midi_driver = { + .name = longname, + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_HIGH, + .bind = midi_bind, + .unbind = midi_unbind, +}; + +module_usb_composite_driver(midi_driver); diff --git a/drivers/usb/gadget/legacy/hid.c b/drivers/usb/gadget/legacy/hid.c new file mode 100644 index 000000000..89722413e --- /dev/null +++ b/drivers/usb/gadget/legacy/hid.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * hid.c -- HID Composite driver + * + * Based on multi.c + * + * Copyright (C) 2010 Fabien Chouteau + */ + + +#include +#include +#include +#include +#include +#include + +#define DRIVER_DESC "HID Gadget" +#define DRIVER_VERSION "2010/03/16" + +#include "u_hid.h" + +/*-------------------------------------------------------------------------*/ + +#define HIDG_VENDOR_NUM 0x0525 /* XXX NetChip */ +#define HIDG_PRODUCT_NUM 0xa4ac /* Linux-USB HID gadget */ + +/*-------------------------------------------------------------------------*/ + +struct hidg_func_node { + struct usb_function_instance *fi; + struct usb_function *f; + 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 = DYNAMIC */ + + /* .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 const struct usb_descriptor_header *otg_desc[2]; + +/* 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 do_config(struct usb_configuration *c) +{ + struct hidg_func_node *e, *n; + int 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) { + e->f = usb_get_function(e->fi); + if (IS_ERR(e->f)) { + status = PTR_ERR(e->f); + goto put; + } + status = usb_add_function(c, e->f); + if (status < 0) { + usb_put_function(e->f); + goto put; + } + } + + return 0; +put: + list_for_each_entry(n, &hidg_func_list, node) { + if (n == e) + break; + usb_remove_function(c, n->f); + usb_put_function(n->f); + } + return status; +} + +static struct usb_configuration config_driver = { + .label = "HID Gadget", + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/****************************** Gadget Bind ******************************/ + +static int hid_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + struct hidg_func_node *n = NULL, *m, *iter_n; + struct f_hid_opts *hid_opts; + int status, funcs; + + funcs = list_count_nodes(&hidg_func_list); + if (!funcs) + return -ENODEV; + + list_for_each_entry(iter_n, &hidg_func_list, node) { + iter_n->fi = usb_get_function_instance("hid"); + if (IS_ERR(iter_n->fi)) { + status = PTR_ERR(iter_n->fi); + n = iter_n; + goto put; + } + hid_opts = container_of(iter_n->fi, struct f_hid_opts, func_inst); + hid_opts->subclass = iter_n->func->subclass; + hid_opts->protocol = iter_n->func->protocol; + hid_opts->report_length = iter_n->func->report_length; + hid_opts->report_desc_length = iter_n->func->report_desc_length; + hid_opts->report_desc = iter_n->func->report_desc; + } + + + /* 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 put; + device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + + if (gadget_is_otg(gadget) && !otg_desc[0]) { + struct usb_descriptor_header *usb_desc; + + usb_desc = usb_otg_descriptor_alloc(gadget); + if (!usb_desc) { + status = -ENOMEM; + goto put; + } + usb_otg_descriptor_init(gadget, usb_desc); + otg_desc[0] = usb_desc; + otg_desc[1] = NULL; + } + + /* register our configuration */ + status = usb_add_config(cdev, &config_driver, do_config); + if (status < 0) + goto free_otg_desc; + + usb_composite_overwrite_options(cdev, &coverwrite); + dev_info(&gadget->dev, DRIVER_DESC ", version: " DRIVER_VERSION "\n"); + + return 0; + +free_otg_desc: + kfree(otg_desc[0]); + otg_desc[0] = NULL; +put: + list_for_each_entry(m, &hidg_func_list, node) { + if (m == n) + break; + usb_put_function_instance(m->fi); + } + return status; +} + +static int hid_unbind(struct usb_composite_dev *cdev) +{ + struct hidg_func_node *n; + + list_for_each_entry(n, &hidg_func_list, node) { + usb_put_function(n->f); + usb_put_function_instance(n->fi); + } + + kfree(otg_desc[0]); + otg_desc[0] = NULL; + + return 0; +} + +static int hidg_plat_driver_probe(struct platform_device *pdev) +{ + struct hidg_func_descriptor *func = dev_get_platdata(&pdev->dev); + struct hidg_func_node *entry; + + if (!func) { + dev_err(&pdev->dev, "Platform data missing\n"); + return -ENODEV; + } + + entry = kzalloc_obj(*entry); + if (!entry) + return -ENOMEM; + + entry->func = func; + list_add_tail(&entry->node, &hidg_func_list); + + return 0; +} + +static void 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); + } +} + + +/****************************** Some noise ******************************/ + + +static struct usb_composite_driver hidg_driver = { + .name = "g_hid", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_HIGH, + .bind = hid_bind, + .unbind = hid_unbind, +}; + +static struct platform_driver hidg_plat_driver = { + .remove = hidg_plat_driver_remove, + .driver = { + .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) +{ + usb_composite_unregister(&hidg_driver); + platform_driver_unregister(&hidg_plat_driver); +} +module_exit(hidg_cleanup); diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c new file mode 100644 index 000000000..db961aaa3 --- /dev/null +++ b/drivers/usb/gadget/legacy/inode.c @@ -0,0 +1,2151 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * inode.c -- user mode filesystem api for usb gadget controllers + * + * Copyright (C) 2003-2004 David Brownell + * Copyright (C) 2003 Agilent Technologies + */ + + +/* #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 /* for USB_GADGET_DELAYED_STATUS */ + +/* Undef helpers from linux/usb/composite.h as gadgetfs redefines them */ +#undef DBG +#undef ERROR +#undef INFO + + +/* + * 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"); + +static int ep_open(struct inode *, struct file *); + + +/*----------------------------------------------------------------------*/ + +#define GADGETFS_MAGIC 0xaee71ee7 + +/* /dev/gadget/$CHIP represents ep0 and the whole device */ +enum ep0_state { + /* DISABLED 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 + +#define RBUF_SIZE 256 + +struct dev_data { + spinlock_t lock; + refcount_t count; + int udc_usage; + 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, + gadget_registered : 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; + + /* except this scratch i/o buffer for ep0 */ + u8 rbuf[RBUF_SIZE]; +}; + +static inline void get_dev (struct dev_data *data) +{ + refcount_inc (&data->count); +} + +static void put_dev (struct dev_data *data) +{ + if (likely (!refcount_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_obj(*dev); + if (!dev) + return NULL; + dev->state = STATE_DEV_DISABLED; + refcount_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; + refcount_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; +}; + +static inline void get_ep (struct ep_data *data) +{ + refcount_inc (&data->count); +} + +static void put_ep (struct ep_data *data) +{ + if (likely (!refcount_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; +static DEFINE_MUTEX(sb_mutex); /* Serialize superblock operations */ + +/*----------------------------------------------------------------------*/ + +/* 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, bool is_write) +{ + int val; + + if (f_flags & O_NONBLOCK) { + if (!mutex_trylock(&epdata->lock)) + goto nonblock; + if (epdata->state != STATE_EP_ENABLED && + (!is_write || epdata->state != STATE_EP_READY)) { + 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: + return 0; + case STATE_EP_READY: /* not configured yet */ + if (is_write) + return 0; + fallthrough; + case STATE_EP_UNBOUND: /* clean disconnect */ + break; + // case STATE_EP_DISABLED: /* "can't happen" */ + default: /* error! */ + pr_debug ("%s: ep %p not available, state %d\n", + shortname, epdata, epdata->state); + } + mutex_unlock(&epdata->lock); + return -ENODEV; +} + +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_for_completion_interruptible(&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_for_completion(&done); + if (epdata->status == -ECONNRESET) + epdata->status = -EINTR; + } else { + spin_unlock_irq (&epdata->dev->lock); + + DBG (epdata->dev, "endpoint gone\n"); + wait_for_completion(&done); + epdata->status = -ENODEV; + } + } + return epdata->status; + } + 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, false)) < 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; + struct iov_iter to; + const void *to_free; + unsigned actual; +}; + +static int ep_aio_cancel(struct kiocb *iocb) +{ + 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(); + + return value; +} + +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; + + if (mmget_not_zero(mm)) { + kthread_use_mm(mm); + ret = copy_to_iter(priv->buf, priv->actual, &priv->to); + kthread_unuse_mm(mm); + mmput(mm); + if (!ret) + ret = -EFAULT; + } else { + ret = -EFAULT; + } + mmdrop(mm); + + /* completing the iocb can drop the ctx and mm, don't touch mm after */ + iocb->ki_complete(iocb, ret); + + kfree(priv->buf); + kfree(priv->to_free); + 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->to_free == NULL || unlikely(req->actual == 0)) { + mmdrop(priv->mm); + kfree(req->buf); + kfree(priv->to_free); + kfree(priv); + iocb->private = NULL; + iocb->ki_complete(iocb, + req->actual ? req->actual : (long)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; + INIT_WORK(&priv->work, ep_user_copy_worker); + schedule_work(&priv->work); + } + + usb_ep_free_request(ep, req); + spin_unlock(&epdata->dev->lock); + put_ep(epdata); +} + +static ssize_t ep_aio(struct kiocb *iocb, + struct kiocb_priv *priv, + struct ep_data *epdata, + char *buf, + size_t len) +{ + struct usb_request *req; + ssize_t value; + + iocb->private = priv; + priv->iocb = iocb; + + 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() */ + mmgrab(priv->mm); + + /* 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); + value = -ENODEV; + if (unlikely(epdata->ep == NULL)) + goto fail; + + req = usb_ep_alloc_request(epdata->ep, GFP_ATOMIC); + value = -ENOMEM; + if (unlikely(!req)) + goto fail; + + 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); + goto fail; + } + spin_unlock_irq(&epdata->dev->lock); + return -EIOCBQUEUED; + +fail: + spin_unlock_irq(&epdata->dev->lock); + mmdrop(priv->mm); + kfree(priv->to_free); + kfree(priv); + put_ep(epdata); + return value; +} + +static ssize_t +ep_read_iter(struct kiocb *iocb, struct iov_iter *to) +{ + struct file *file = iocb->ki_filp; + struct ep_data *epdata = file->private_data; + size_t len = iov_iter_count(to); + ssize_t value; + char *buf; + + if ((value = get_ready_ep(file->f_flags, epdata, false)) < 0) + return value; + + /* halt any endpoint by doing a "wrong direction" i/o call */ + if (usb_endpoint_dir_in(&epdata->desc)) { + if (usb_endpoint_xfer_isoc(&epdata->desc) || + !is_sync_kiocb(iocb)) { + mutex_unlock(&epdata->lock); + return -EINVAL; + } + DBG (epdata->dev, "%s halt\n", epdata->name); + spin_lock_irq(&epdata->dev->lock); + if (likely(epdata->ep != NULL)) + usb_ep_set_halt(epdata->ep); + spin_unlock_irq(&epdata->dev->lock); + mutex_unlock(&epdata->lock); + return -EBADMSG; + } + + buf = kmalloc(len, GFP_KERNEL); + if (unlikely(!buf)) { + mutex_unlock(&epdata->lock); + return -ENOMEM; + } + if (is_sync_kiocb(iocb)) { + value = ep_io(epdata, buf, len); + if (value >= 0 && (copy_to_iter(buf, value, to) != value)) + value = -EFAULT; + } else { + struct kiocb_priv *priv = kzalloc_obj(*priv); + value = -ENOMEM; + if (!priv) + goto fail; + priv->to_free = dup_iter(&priv->to, to, GFP_KERNEL); + if (!iter_is_ubuf(&priv->to) && !priv->to_free) { + kfree(priv); + goto fail; + } + value = ep_aio(iocb, priv, epdata, buf, len); + if (value == -EIOCBQUEUED) + buf = NULL; + } +fail: + kfree(buf); + mutex_unlock(&epdata->lock); + return value; +} + +static ssize_t ep_config(struct ep_data *, const char *, size_t); + +static ssize_t +ep_write_iter(struct kiocb *iocb, struct iov_iter *from) +{ + struct file *file = iocb->ki_filp; + struct ep_data *epdata = file->private_data; + size_t len = iov_iter_count(from); + bool configured; + ssize_t value; + char *buf; + + if ((value = get_ready_ep(file->f_flags, epdata, true)) < 0) + return value; + + configured = epdata->state == STATE_EP_ENABLED; + + /* halt any endpoint by doing a "wrong direction" i/o call */ + if (configured && !usb_endpoint_dir_in(&epdata->desc)) { + if (usb_endpoint_xfer_isoc(&epdata->desc) || + !is_sync_kiocb(iocb)) { + mutex_unlock(&epdata->lock); + return -EINVAL; + } + DBG (epdata->dev, "%s halt\n", epdata->name); + spin_lock_irq(&epdata->dev->lock); + if (likely(epdata->ep != NULL)) + usb_ep_set_halt(epdata->ep); + spin_unlock_irq(&epdata->dev->lock); + mutex_unlock(&epdata->lock); + return -EBADMSG; + } + + buf = kmalloc(len, GFP_KERNEL); + if (unlikely(!buf)) { + mutex_unlock(&epdata->lock); + return -ENOMEM; + } + + if (unlikely(!copy_from_iter_full(buf, len, from))) { + value = -EFAULT; + goto out; + } + + if (unlikely(!configured)) { + value = ep_config(epdata, buf, len); + } else if (is_sync_kiocb(iocb)) { + value = ep_io(epdata, buf, len); + } else { + struct kiocb_priv *priv = kzalloc_obj(*priv); + value = -ENOMEM; + if (priv) { + value = ep_aio(iocb, priv, epdata, buf, len); + if (value == -EIOCBQUEUED) + buf = NULL; + } + } +out: + kfree(buf); + mutex_unlock(&epdata->lock); + return value; +} + +/*----------------------------------------------------------------------*/ + +/* used after endpoint configuration */ +static const struct file_operations ep_io_operations = { + .owner = THIS_MODULE, + + .open = ep_open, + .release = ep_release, + .unlocked_ioctl = ep_ioctl, + .read_iter = ep_read_iter, + .write_iter = ep_write_iter, +}; + +/* 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 ep_data *data, const char *buf, size_t len) +{ + struct usb_ep *ep; + u32 tag; + int value, length = len; + + 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 */ + memcpy(&tag, buf, 4); + 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 */ + memcpy(&data->desc, buf, USB_DT_ENDPOINT_SIZE); + 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; + memcpy(&data->hs_desc, buf + USB_DT_ENDPOINT_SIZE, + USB_DT_ENDPOINT_SIZE); + 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 { + ep = data->ep; + if (ep == NULL) { + value = -ENODEV; + goto gone; + } + } + switch (data->dev->gadget->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + ep->desc = &data->desc; + break; + case USB_SPEED_HIGH: + /* fails if caller didn't provide that descriptor... */ + ep->desc = &data->hs_desc; + break; + default: + DBG(data->dev, "unconnected, %s init abandoned\n", + data->name); + value = -EINVAL; + goto gone; + } + value = usb_ep_enable(ep); + if (value == 0) { + data->state = STATE_EP_ENABLED; + value = length; + } +gone: + spin_unlock_irq (&data->dev->lock); + if (value < 0) { +fail: + data->desc.bDescriptorType = 0; + data->hs_desc.bDescriptorType = 0; + } + return value; +fail0: + value = -EINVAL; + 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; +} + +/*----------------------------------------------------------------------*/ + +/* 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); + if (dev->state <= STATE_DEV_OPENED) { + retval = -EINVAL; + goto done; + } + + /* 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) { + ++dev->udc_usage; + spin_unlock_irq (&dev->lock); + retval = usb_ep_queue (ep, req, GFP_KERNEL); + spin_lock_irq (&dev->lock); + --dev->udc_usage; + } + 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); + ++dev->udc_usage; + spin_unlock_irq(&dev->lock); + if (copy_to_user (buf, dev->req->buf, len)) + retval = -EFAULT; + else + retval = len; + spin_lock_irq(&dev->lock); + --dev->udc_usage; + 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; + fallthrough; + 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; + + /* 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) { + + len = min_t(size_t, len, dev->setup_wLength); + if (dev->setup_in) { + retval = setup_req (dev->gadget->ep0, dev->req, len); + if (retval == 0) { + dev->state = STATE_DEV_CONNECTED; + ++dev->udc_usage; + 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); + } + spin_lock_irq(&dev->lock); + --dev->udc_usage; + if (retval < 0) { + clean_req (dev->gadget->ep0, dev->req); + } 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); + + 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__, str_on_off(on)); + 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 */ + + if (dev->gadget_registered) { + usb_gadget_unregister_driver (&gadgetfs_driver); + dev->gadget_registered = false; + } + + /* 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; + + /* other endpoints were all decoupled from this device */ + spin_lock_irq(&dev->lock); + dev->state = STATE_DEV_DISABLED; + spin_unlock_irq(&dev->lock); + + put_dev (dev); + return 0; +} + +static __poll_t +ep0_poll (struct file *fd, poll_table *wait) +{ + struct dev_data *dev = fd->private_data; + __poll_t mask = 0; + + if (dev->state <= STATE_DEV_OPENED) + return DEFAULT_POLLMASK; + + 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 = EPOLLHUP; + goto out; + } + + if (dev->state == STATE_DEV_SETUP) { + if (dev->setup_in || dev->setup_can_stall) + mask = EPOLLOUT; + } else { + if (dev->ev_next != 0) + mask = EPOLLIN; + } +out: + spin_unlock_irq(&dev->lock); + return mask; +} + +static long gadget_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; + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_DEV_OPENED || + dev->state == STATE_DEV_UNBOUND) { + /* Not bound to a UDC */ + } else if (gadget->ops->ioctl) { + ++dev->udc_usage; + spin_unlock_irq(&dev->lock); + + ret = gadget->ops->ioctl (gadget, code, value); + + spin_lock_irq(&dev->lock); + --dev->udc_usage; + } + spin_unlock_irq(&dev->lock); + + return ret; +} + +/*----------------------------------------------------------------------*/ + +/* 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); + + if (w_length > RBUF_SIZE) { + if (ctrl->bRequestType & USB_DIR_IN) { + /* Cast away the const, we are going to overwrite on purpose. */ + __le16 *temp = (__le16 *)&ctrl->wLength; + + *temp = cpu_to_le16(RBUF_SIZE); + w_length = RBUF_SIZE; + } else { + return value; + } + } + + 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; + 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: + 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); + usb_gadget_set_state(gadget, USB_STATE_CONFIGURED); + 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; + + /* 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; + + ++dev->udc_usage; + spin_unlock (&dev->lock); + value = usb_ep_queue (gadget->ep0, dev->req, + GFP_KERNEL); + spin_lock (&dev->lock); + --dev->udc_usage; + 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 USB_GADGET_DELAYED_STATUS as a workaround to + * stop some UDC drivers (e.g. dwc3) from automatically + * proceeding with the status stage for 0-length + * transfers. + * Should be removed once all UDC drivers are fixed to + * always delay the status stage until a response is + * queued to EP0. + */ + return w_length == 0 ? USB_GADGET_DELAYED_STATUS : 0; + } + } + + /* proceed with data transfer and status phases? */ + if (value >= 0 && dev->state != STATE_DEV_SETUP) { + req->length = value; + req->zero = value < w_length; + + ++dev->udc_usage; + spin_unlock (&dev->lock); + value = usb_ep_queue (gadget->ep0, req, GFP_KERNEL); + spin_lock(&dev->lock); + --dev->udc_usage; + spin_unlock(&dev->lock); + if (value < 0) { + DBG (dev, "ep_queue --> %d\n", value); + req->status = 0; + } + return value; + } + + /* 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; + + /* break link to FS */ + ep = list_first_entry (&dev->epfiles, struct ep_data, epfiles); + list_del_init (&ep->epfiles); + spin_unlock_irq (&dev->lock); + + /* break link to controller */ + mutex_lock(&ep->lock); + 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; + mutex_unlock(&ep->lock); + + wake_up (&ep->wait); + + /* break link to dcache */ + simple_remove_by_name(dev->sb->s_root, ep->name, NULL); + + put_ep (ep); + + spin_lock_irq (&dev->lock); + } + spin_unlock_irq (&dev->lock); +} + + +static int gadgetfs_create_file (struct super_block *sb, char const *name, + void *data, const struct file_operations *fops); + +static int activate_ep_files (struct dev_data *dev) +{ + struct usb_ep *ep; + struct ep_data *data; + int err; + + gadget_for_each_ep (ep, dev->gadget) { + + data = kzalloc_obj(*data); + if (!data) + goto enomem0; + data->state = STATE_EP_DISABLED; + mutex_init(&data->lock); + init_waitqueue_head (&data->wait); + + strscpy(data->name, ep->name); + refcount_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; + + err = gadgetfs_create_file (dev->sb, data->name, + data, &ep_io_operations); + if (err) + 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; + while (dev->udc_usage > 0) { + spin_unlock_irq(&dev->lock); + usleep_range(1000, 2000); + spin_lock_irq(&dev->lock); + } + 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); + unsigned long flags; + + INFO (dev, "suspended from state %d\n", dev->state); + spin_lock_irqsave(&dev->lock, flags); + 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_irqrestore(&dev->lock, flags); +} + +static struct usb_gadget_driver gadgetfs_driver = { + .function = (char *) driver_desc, + .bind = gadgetfs_bind, + .unbind = gadgetfs_unbind, + .setup = gadgetfs_setup, + .reset = gadgetfs_disconnect, + .disconnect = gadgetfs_disconnect, + .suspend = gadgetfs_suspend, + + .driver = { + .name = shortname, + }, +}; + +/*----------------------------------------------------------------------*/ +/* 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, + unsigned int total) +{ + return config->bDescriptorType == USB_DT_CONFIG + && config->bLength == USB_DT_CONFIG_SIZE + && total >= 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, length = len; + unsigned total; + u32 tag; + char *kbuf; + + spin_lock_irq(&dev->lock); + if (dev->state > STATE_DEV_OPENED) { + value = ep0_write(fd, buf, len, ptr); + spin_unlock_irq(&dev->lock); + return value; + } + spin_unlock_irq(&dev->lock); + + if ((len < (USB_DT_CONFIG_SIZE + USB_DT_DEVICE_SIZE + 4)) || + (len > PAGE_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) { + spin_unlock_irq(&dev->lock); + kfree(kbuf); + return value; + } + 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) || + total > length - USB_DT_DEVICE_SIZE) + 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) || + total > length - USB_DT_DEVICE_SIZE) + goto fail; + kbuf += total; + length -= total; + } else { + dev->hs_config = NULL; + } + + /* 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->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_register_driver(&gadgetfs_driver); + if (value != 0) { + spin_lock_irq(&dev->lock); + goto fail; + } 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. + */ + value = len; + dev->gadget_registered = true; + } + return value; + +fail: + dev->config = NULL; + dev->hs_config = NULL; + dev->dev = NULL; + 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 +gadget_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 ep0_operations = { + + .open = gadget_dev_open, + .read = ep0_read, + .write = dev_config, + .fasync = ep0_fasync, + .poll = ep0_poll, + .unlocked_ioctl = gadget_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); + simple_inode_init_ts(inode); + 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 int gadgetfs_create_file (struct super_block *sb, char const *name, + void *data, const struct file_operations *fops) +{ + struct dentry *dentry; + struct inode *inode; + + inode = gadgetfs_make_inode (sb, data, fops, + S_IFREG | (default_perm & S_IRWXUGO)); + if (!inode) + return -ENOMEM; + + dentry = simple_start_creating(sb->s_root, name); + if (IS_ERR(dentry)) { + iput(inode); + return PTR_ERR(dentry); + } + + d_make_persistent(dentry, inode); + + simple_done_creating(dentry); + return 0; +} + +static const struct super_operations gadget_fs_operations = { + .statfs = simple_statfs, + .drop_inode = inode_just_drop, +}; + +static int +gadgetfs_fill_super (struct super_block *sb, struct fs_context *fc) +{ + struct inode *inode; + struct dev_data *dev; + int rc; + + mutex_lock(&sb_mutex); + + if (the_device) { + rc = -ESRCH; + goto Done; + } + + CHIP = usb_get_gadget_udc_name(); + if (!CHIP) { + rc = -ENODEV; + goto Done; + } + + /* superblock */ + sb->s_blocksize = PAGE_SIZE; + sb->s_blocksize_bits = PAGE_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; + rc = gadgetfs_create_file(sb, CHIP, dev, &ep0_operations); + if (rc) { + put_dev(dev); + goto Enomem; + } + + /* other endpoint files are available after hardware setup, + * from binding to a controller. + */ + the_device = dev; + rc = 0; + goto Done; + + Enomem: + kfree(CHIP); + CHIP = NULL; + rc = -ENOMEM; + + Done: + mutex_unlock(&sb_mutex); + return rc; +} + +/* "mount -t gadgetfs path /dev/gadget" ends up here */ +static int gadgetfs_get_tree(struct fs_context *fc) +{ + return get_tree_single(fc, gadgetfs_fill_super); +} + +static const struct fs_context_operations gadgetfs_context_ops = { + .get_tree = gadgetfs_get_tree, +}; + +static int gadgetfs_init_fs_context(struct fs_context *fc) +{ + fc->ops = &gadgetfs_context_ops; + return 0; +} + +static void +gadgetfs_kill_sb (struct super_block *sb) +{ + mutex_lock(&sb_mutex); + kill_anon_super (sb); + if (the_device) { + put_dev (the_device); + the_device = NULL; + } + kfree(CHIP); + CHIP = NULL; + mutex_unlock(&sb_mutex); +} + +/*----------------------------------------------------------------------*/ + +static struct file_system_type gadgetfs_type = { + .owner = THIS_MODULE, + .name = shortname, + .init_fs_context = gadgetfs_init_fs_context, + .kill_sb = gadgetfs_kill_sb, +}; +MODULE_ALIAS_FS("gadgetfs"); + +/*----------------------------------------------------------------------*/ + +static int __init gadgetfs_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 (gadgetfs_init); + +static void __exit gadgetfs_cleanup (void) +{ + pr_debug ("unregister %s\n", shortname); + unregister_filesystem (&gadgetfs_type); +} +module_exit (gadgetfs_cleanup); + diff --git a/drivers/usb/gadget/legacy/mass_storage.c b/drivers/usb/gadget/legacy/mass_storage.c new file mode 100644 index 000000000..ac1741126 --- /dev/null +++ b/drivers/usb/gadget/legacy/mass_storage.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * mass_storage.c -- Mass Storage USB Gadget + * + * Copyright (C) 2003-2008 Alan Stern + * Copyright (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz + * All rights reserved. + */ + + +/* + * 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" + +/* + * 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 */ + +#include "f_mass_storage.h" + +/*-------------------------------------------------------------------------*/ +USB_GADGET_COMPOSITE_OPTIONS(); + +static struct usb_device_descriptor msg_device_desc = { + .bLength = sizeof msg_device_desc, + .bDescriptorType = USB_DT_DEVICE, + + /* .bcdUSB = DYNAMIC */ + .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 const struct usb_descriptor_header *otg_desc[2]; + +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 struct usb_function_instance *fi_msg; +static struct usb_function *f_msg; + +/****************************** Configurations ******************************/ + +static struct fsg_module_parameters mod_data = { + .stall = 1 +}; +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_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_GADGET_DEBUG_FILES */ + +FSG_MODULE_PARAMETERS(/* no prefix */, mod_data); + +static int msg_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; + } + + f_msg = usb_get_function(fi_msg); + if (IS_ERR(f_msg)) + return PTR_ERR(f_msg); + + ret = usb_add_function(c, f_msg); + if (ret) + goto put_func; + + return 0; + +put_func: + usb_put_function(f_msg); + 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 msg_bind(struct usb_composite_dev *cdev) +{ + struct fsg_opts *opts; + struct fsg_config config; + int status; + + fi_msg = usb_get_function_instance("mass_storage"); + if (IS_ERR(fi_msg)) + return PTR_ERR(fi_msg); + + fsg_config_from_params(&config, &mod_data, fsg_num_buffers); + opts = fsg_opts_from_func_inst(fi_msg); + + opts->no_configfs = true; + status = fsg_common_set_num_buffers(opts->common, fsg_num_buffers); + if (status) + goto fail; + + status = fsg_common_set_cdev(opts->common, cdev, config.can_stall); + if (status) + goto fail_set_cdev; + + fsg_common_set_sysfs(opts->common, true); + status = fsg_common_create_luns(opts->common, &config); + if (status) + goto fail_set_cdev; + + fsg_common_set_inquiry_string(opts->common, config.vendor_name, + config.product_name); + + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + goto fail_string_ids; + msg_device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + + if (gadget_is_otg(cdev->gadget) && !otg_desc[0]) { + struct usb_descriptor_header *usb_desc; + + usb_desc = usb_otg_descriptor_alloc(cdev->gadget); + if (!usb_desc) { + status = -ENOMEM; + goto fail_string_ids; + } + usb_otg_descriptor_init(cdev->gadget, usb_desc); + otg_desc[0] = usb_desc; + otg_desc[1] = NULL; + } + + status = usb_add_config(cdev, &msg_config_driver, msg_do_config); + if (status < 0) + goto fail_otg_desc; + + usb_composite_overwrite_options(cdev, &coverwrite); + dev_info(&cdev->gadget->dev, + DRIVER_DESC ", version: " DRIVER_VERSION "\n"); + return 0; + +fail_otg_desc: + kfree(otg_desc[0]); + otg_desc[0] = NULL; +fail_string_ids: + fsg_common_remove_luns(opts->common); +fail_set_cdev: + fsg_common_free_buffers(opts->common); +fail: + usb_put_function_instance(fi_msg); + return status; +} + +static int msg_unbind(struct usb_composite_dev *cdev) +{ + if (!IS_ERR(f_msg)) + usb_put_function(f_msg); + + if (!IS_ERR(fi_msg)) + usb_put_function_instance(fi_msg); + + kfree(otg_desc[0]); + otg_desc[0] = NULL; + + return 0; +} + +/****************************** Some noise ******************************/ + +static struct usb_composite_driver msg_driver = { + .name = "g_mass_storage", + .dev = &msg_device_desc, + .max_speed = USB_SPEED_SUPER_PLUS, + .needs_serial = 1, + .strings = dev_strings, + .bind = msg_bind, + .unbind = msg_unbind, +}; + +module_usb_composite_driver(msg_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Michal Nazarewicz"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/legacy/multi.c b/drivers/usb/gadget/legacy/multi.c new file mode 100644 index 000000000..8db5c91ae --- /dev/null +++ b/drivers/usb/gadget/legacy/multi.c @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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) + */ + + +#include +#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"); + + +#include "f_mass_storage.h" + +#include "u_ecm.h" +#ifdef USB_ETH_RNDIS +# include "u_rndis.h" +# include "rndis.h" +#endif +#include "u_ether.h" + +USB_GADGET_COMPOSITE_OPTIONS(); + +USB_ETHERNET_MODULE_PARAMETERS(); + +/***************************** 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 = DYNAMIC */ + + .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[2]; + +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 }; +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_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_GADGET_DEBUG_FILES */ + +FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data); + +static struct usb_function_instance *fi_acm; +static struct usb_function_instance *fi_msg; + +/********** RNDIS **********/ + +#ifdef USB_ETH_RNDIS +static struct usb_function_instance *fi_rndis; +static struct usb_function *f_acm_rndis; +static struct usb_function *f_rndis; +static struct usb_function *f_msg_rndis; + +static 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; + } + + f_rndis = usb_get_function(fi_rndis); + if (IS_ERR(f_rndis)) + return PTR_ERR(f_rndis); + + ret = usb_add_function(c, f_rndis); + if (ret < 0) + goto err_func_rndis; + + 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; + + f_msg_rndis = usb_get_function(fi_msg); + if (IS_ERR(f_msg_rndis)) { + ret = PTR_ERR(f_msg_rndis); + goto err_fsg; + } + + ret = usb_add_function(c, f_msg_rndis); + if (ret) + goto err_run; + + return 0; +err_run: + usb_put_function(f_msg_rndis); +err_fsg: + usb_remove_function(c, f_acm_rndis); +err_conf: + usb_put_function(f_acm_rndis); +err_func_acm: + usb_remove_function(c, f_rndis); +err_func_rndis: + usb_put_function(f_rndis); + 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_instance *fi_ecm; +static struct usb_function *f_acm_multi; +static struct usb_function *f_ecm; +static struct usb_function *f_msg_multi; + +static 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; + } + + f_ecm = usb_get_function(fi_ecm); + if (IS_ERR(f_ecm)) + return PTR_ERR(f_ecm); + + ret = usb_add_function(c, f_ecm); + if (ret < 0) + goto err_func_ecm; + + /* implicit port_num is zero */ + f_acm_multi = usb_get_function(fi_acm); + if (IS_ERR(f_acm_multi)) { + ret = PTR_ERR(f_acm_multi); + goto err_func_acm; + } + + ret = usb_add_function(c, f_acm_multi); + if (ret) + goto err_conf; + + f_msg_multi = usb_get_function(fi_msg); + if (IS_ERR(f_msg_multi)) { + ret = PTR_ERR(f_msg_multi); + goto err_fsg; + } + + ret = usb_add_function(c, f_msg_multi); + if (ret) + goto err_run; + + return 0; +err_run: + usb_put_function(f_msg_multi); +err_fsg: + usb_remove_function(c, f_acm_multi); +err_conf: + usb_put_function(f_acm_multi); +err_func_acm: + usb_remove_function(c, f_ecm); +err_func_ecm: + usb_put_function(f_ecm); + 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 multi_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; +#ifdef CONFIG_USB_G_MULTI_CDC + struct f_ecm_opts *ecm_opts; +#endif +#ifdef USB_ETH_RNDIS + struct f_rndis_opts *rndis_opts; +#endif + struct fsg_opts *fsg_opts; + struct fsg_config config; + int status; + + if (!can_support_ecm(cdev->gadget)) { + dev_err(&gadget->dev, "controller '%s' not usable\n", + gadget->name); + return -EINVAL; + } + +#ifdef CONFIG_USB_G_MULTI_CDC + fi_ecm = usb_get_function_instance("ecm"); + if (IS_ERR(fi_ecm)) + return PTR_ERR(fi_ecm); + + ecm_opts = container_of(fi_ecm, struct f_ecm_opts, func_inst); + + gether_set_qmult(ecm_opts->net, qmult); + if (!gether_set_host_addr(ecm_opts->net, host_addr)) + pr_info("using host ethernet address: %s", host_addr); + if (!gether_set_dev_addr(ecm_opts->net, dev_addr)) + pr_info("using self ethernet address: %s", dev_addr); +#endif + +#ifdef USB_ETH_RNDIS + fi_rndis = usb_get_function_instance("rndis"); + if (IS_ERR(fi_rndis)) { + status = PTR_ERR(fi_rndis); + goto fail; + } + + rndis_opts = container_of(fi_rndis, struct f_rndis_opts, func_inst); + + gether_set_qmult(rndis_opts->net, qmult); + if (!gether_set_host_addr(rndis_opts->net, host_addr)) + pr_info("using host ethernet address: %s", host_addr); + if (!gether_set_dev_addr(rndis_opts->net, dev_addr)) + pr_info("using self ethernet address: %s", dev_addr); +#endif + +#if (defined CONFIG_USB_G_MULTI_CDC && defined USB_ETH_RNDIS) + /* + * If both ecm and rndis are selected then: + * 1) rndis borrows the net interface from ecm + * 2) since the interface is shared it must not be bound + * twice - in ecm's _and_ rndis' binds, so do it here. + */ + gether_set_gadget(ecm_opts->net, cdev->gadget); + status = gether_register_netdev(ecm_opts->net); + if (status) + goto fail0; + + rndis_borrow_net(fi_rndis, ecm_opts->net); + ecm_opts->bound = true; +#endif + + /* 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 */ + fi_msg = usb_get_function_instance("mass_storage"); + if (IS_ERR(fi_msg)) { + status = PTR_ERR(fi_msg); + goto fail1; + } + fsg_config_from_params(&config, &fsg_mod_data, fsg_num_buffers); + fsg_opts = fsg_opts_from_func_inst(fi_msg); + + fsg_opts->no_configfs = true; + status = fsg_common_set_num_buffers(fsg_opts->common, fsg_num_buffers); + if (status) + goto fail2; + + status = fsg_common_set_cdev(fsg_opts->common, cdev, config.can_stall); + if (status) + goto fail_set_cdev; + + fsg_common_set_sysfs(fsg_opts->common, true); + status = fsg_common_create_luns(fsg_opts->common, &config); + if (status) + goto fail_set_cdev; + + fsg_common_set_inquiry_string(fsg_opts->common, config.vendor_name, + config.product_name); + + /* allocate string IDs */ + status = usb_string_ids_tab(cdev, strings_dev); + if (unlikely(status < 0)) + goto fail_string_ids; + device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; + + if (gadget_is_otg(gadget) && !otg_desc[0]) { + struct usb_descriptor_header *usb_desc; + + usb_desc = usb_otg_descriptor_alloc(gadget); + if (!usb_desc) { + status = -ENOMEM; + goto fail_string_ids; + } + usb_otg_descriptor_init(gadget, usb_desc); + otg_desc[0] = usb_desc; + otg_desc[1] = NULL; + } + + /* register configurations */ + status = rndis_config_register(cdev); + if (unlikely(status < 0)) + goto fail_otg_desc; + + status = cdc_config_register(cdev); + if (unlikely(status < 0)) + goto fail_otg_desc; + usb_composite_overwrite_options(cdev, &coverwrite); + + /* we're done */ + dev_info(&gadget->dev, DRIVER_DESC "\n"); + return 0; + + + /* error recovery */ +fail_otg_desc: + kfree(otg_desc[0]); + otg_desc[0] = NULL; +fail_string_ids: + fsg_common_remove_luns(fsg_opts->common); +fail_set_cdev: + fsg_common_free_buffers(fsg_opts->common); +fail2: + usb_put_function_instance(fi_msg); +fail1: + usb_put_function_instance(fi_acm); +fail0: +#ifdef USB_ETH_RNDIS + usb_put_function_instance(fi_rndis); +fail: +#endif +#ifdef CONFIG_USB_G_MULTI_CDC + usb_put_function_instance(fi_ecm); +#endif + return status; +} + +static int multi_unbind(struct usb_composite_dev *cdev) +{ +#ifdef CONFIG_USB_G_MULTI_CDC + usb_put_function(f_msg_multi); +#endif +#ifdef USB_ETH_RNDIS + usb_put_function(f_msg_rndis); +#endif + usb_put_function_instance(fi_msg); +#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); +#ifdef USB_ETH_RNDIS + usb_put_function(f_rndis); + usb_put_function_instance(fi_rndis); +#endif +#ifdef CONFIG_USB_G_MULTI_CDC + usb_put_function(f_ecm); + usb_put_function_instance(fi_ecm); +#endif + kfree(otg_desc[0]); + otg_desc[0] = NULL; + + return 0; +} + + +/****************************** Some noise ******************************/ + + +static struct usb_composite_driver multi_driver = { + .name = "g_multi", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_SUPER, + .bind = multi_bind, + .unbind = multi_unbind, + .needs_serial = 1, +}; + +module_usb_composite_driver(multi_driver); diff --git a/drivers/usb/gadget/legacy/ncm.c b/drivers/usb/gadget/legacy/ncm.c new file mode 100644 index 000000000..0f1b45e3a --- /dev/null +++ b/drivers/usb/gadget/legacy/ncm.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include +#include +#include + +#include "u_ether.h" +#include "u_ncm.h" + +#define DRIVER_DESC "NCM 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. + * 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(); + +USB_ETHERNET_MODULE_PARAMETERS(); + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + /* .bcdUSB = DYNAMIC */ + + .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 const struct usb_descriptor_header *otg_desc[2]; + +/* 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 struct usb_function_instance *f_ncm_inst; +static struct usb_function *f_ncm; + +/*-------------------------------------------------------------------------*/ + +static int ncm_do_config(struct usb_configuration *c) +{ + int status; + + /* 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; + } + + f_ncm = usb_get_function(f_ncm_inst); + if (IS_ERR(f_ncm)) + return PTR_ERR(f_ncm); + + status = usb_add_function(c, f_ncm); + if (status < 0) { + usb_put_function(f_ncm); + return status; + } + + return 0; +} + +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 gncm_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + struct f_ncm_opts *ncm_opts; + int status; + + f_ncm_inst = usb_get_function_instance("ncm"); + if (IS_ERR(f_ncm_inst)) + return PTR_ERR(f_ncm_inst); + + ncm_opts = container_of(f_ncm_inst, struct f_ncm_opts, func_inst); + + gether_set_qmult(ncm_opts->net, qmult); + if (!gether_set_host_addr(ncm_opts->net, host_addr)) + pr_info("using host ethernet address: %s", host_addr); + if (!gether_set_dev_addr(ncm_opts->net, dev_addr)) + pr_info("using self ethernet address: %s", dev_addr); + + /* 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; + + if (gadget_is_otg(gadget) && !otg_desc[0]) { + struct usb_descriptor_header *usb_desc; + + usb_desc = usb_otg_descriptor_alloc(gadget); + if (!usb_desc) { + status = -ENOMEM; + goto fail; + } + usb_otg_descriptor_init(gadget, usb_desc); + otg_desc[0] = usb_desc; + otg_desc[1] = NULL; + } + + status = usb_add_config(cdev, &ncm_config_driver, + ncm_do_config); + if (status < 0) + goto fail1; + + usb_composite_overwrite_options(cdev, &coverwrite); + dev_info(&gadget->dev, "%s\n", DRIVER_DESC); + + return 0; + +fail1: + kfree(otg_desc[0]); + otg_desc[0] = NULL; +fail: + usb_put_function_instance(f_ncm_inst); + return status; +} + +static int gncm_unbind(struct usb_composite_dev *cdev) +{ + if (!IS_ERR_OR_NULL(f_ncm)) + usb_put_function(f_ncm); + if (!IS_ERR_OR_NULL(f_ncm_inst)) + usb_put_function_instance(f_ncm_inst); + kfree(otg_desc[0]); + otg_desc[0] = NULL; + + return 0; +} + +static struct usb_composite_driver ncm_driver = { + .name = "g_ncm", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_SUPER, + .bind = gncm_bind, + .unbind = gncm_unbind, +}; + +module_usb_composite_driver(ncm_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Yauheni Kaliuta"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/legacy/nokia.c b/drivers/usb/gadget/legacy/nokia.c new file mode 100644 index 000000000..396605a84 --- /dev/null +++ b/drivers/usb/gadget/legacy/nokia.c @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + */ + +#include +#include +#include + +#include "u_serial.h" +#include "u_ether.h" +#include "u_phonet.h" +#include "u_ecm.h" +#include "f_mass_storage.h" + +/* Defines */ + +#define NOKIA_VERSION_NUM 0x0211 +#define NOKIA_LONG_NAME "N900 (PC-Suite Mode)" + +USB_GADGET_COMPOSITE_OPTIONS(); + +USB_ETHERNET_MODULE_PARAMETERS(); + +static struct fsg_module_parameters fsg_mod_data = { + .stall = 0, + .luns = 2, + .removable_count = 2, + .removable = { 1, 1, }, +}; + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_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_GADGET_DEBUG_FILES */ + +FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data); + +#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 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 = DYNAMIC */ + .bDeviceClass = USB_CLASS_COMM, + .idVendor = cpu_to_le16(NOKIA_VENDOR_ID), + .idProduct = 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 struct usb_function *f_ecm_cfg1; +static struct usb_function *f_ecm_cfg2; +static struct usb_function *f_obex1_cfg1; +static struct usb_function *f_obex2_cfg1; +static struct usb_function *f_obex1_cfg2; +static struct usb_function *f_obex2_cfg2; +static struct usb_function *f_phonet_cfg1; +static struct usb_function *f_phonet_cfg2; +static struct usb_function *f_msg_cfg1; +static struct usb_function *f_msg_cfg2; + + +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 struct usb_function_instance *fi_ecm; +static struct usb_function_instance *fi_obex1; +static struct usb_function_instance *fi_obex2; +static struct usb_function_instance *fi_phonet; +static struct usb_function_instance *fi_msg; + +static int nokia_bind_config(struct usb_configuration *c) +{ + struct usb_function *f_acm; + struct usb_function *f_phonet = NULL; + struct usb_function *f_obex1 = NULL; + struct usb_function *f_ecm; + struct usb_function *f_obex2 = NULL; + struct usb_function *f_msg; + int status = 0; + int obex1_stat = -1; + int obex2_stat = -1; + int phonet_stat = -1; + + if (!IS_ERR(fi_phonet)) { + f_phonet = usb_get_function(fi_phonet); + if (IS_ERR(f_phonet)) + pr_debug("could not get phonet function\n"); + } + + if (!IS_ERR(fi_obex1)) { + f_obex1 = usb_get_function(fi_obex1); + if (IS_ERR(f_obex1)) + pr_debug("could not get obex function 0\n"); + } + + if (!IS_ERR(fi_obex2)) { + f_obex2 = usb_get_function(fi_obex2); + if (IS_ERR(f_obex2)) + pr_debug("could not get obex function 1\n"); + } + + f_acm = usb_get_function(fi_acm); + if (IS_ERR(f_acm)) { + status = PTR_ERR(f_acm); + goto err_get_acm; + } + + f_ecm = usb_get_function(fi_ecm); + if (IS_ERR(f_ecm)) { + status = PTR_ERR(f_ecm); + goto err_get_ecm; + } + + f_msg = usb_get_function(fi_msg); + if (IS_ERR(f_msg)) { + status = PTR_ERR(f_msg); + goto err_get_msg; + } + + if (!IS_ERR_OR_NULL(f_phonet)) { + phonet_stat = usb_add_function(c, f_phonet); + if (phonet_stat) + pr_debug("could not add phonet function\n"); + } + + if (!IS_ERR_OR_NULL(f_obex1)) { + obex1_stat = usb_add_function(c, f_obex1); + if (obex1_stat) + pr_debug("could not add obex function 0\n"); + } + + if (!IS_ERR_OR_NULL(f_obex2)) { + obex2_stat = usb_add_function(c, f_obex2); + if (obex2_stat) + pr_debug("could not add obex function 1\n"); + } + + status = usb_add_function(c, f_acm); + if (status) + goto err_conf; + + status = usb_add_function(c, f_ecm); + if (status) { + pr_debug("could not bind ecm config %d\n", status); + goto err_ecm; + } + + status = usb_add_function(c, f_msg); + if (status) + goto err_msg; + + if (c == &nokia_config_500ma_driver) { + f_acm_cfg1 = f_acm; + f_ecm_cfg1 = f_ecm; + f_phonet_cfg1 = f_phonet; + f_obex1_cfg1 = f_obex1; + f_obex2_cfg1 = f_obex2; + f_msg_cfg1 = f_msg; + } else { + f_acm_cfg2 = f_acm; + f_ecm_cfg2 = f_ecm; + f_phonet_cfg2 = f_phonet; + f_obex1_cfg2 = f_obex1; + f_obex2_cfg2 = f_obex2; + f_msg_cfg2 = f_msg; + } + + return status; +err_msg: + usb_remove_function(c, f_ecm); +err_ecm: + usb_remove_function(c, f_acm); +err_conf: + if (!obex2_stat) + usb_remove_function(c, f_obex2); + if (!obex1_stat) + usb_remove_function(c, f_obex1); + if (!phonet_stat) + usb_remove_function(c, f_phonet); + usb_put_function(f_msg); +err_get_msg: + usb_put_function(f_ecm); +err_get_ecm: + usb_put_function(f_acm); +err_get_acm: + if (!IS_ERR_OR_NULL(f_obex2)) + usb_put_function(f_obex2); + if (!IS_ERR_OR_NULL(f_obex1)) + usb_put_function(f_obex1); + if (!IS_ERR_OR_NULL(f_phonet)) + usb_put_function(f_phonet); + return status; +} + +static int nokia_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + struct fsg_opts *fsg_opts; + struct fsg_config fsg_config; + int status; + + 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_is_altset_supported(gadget)) { + status = -ENODEV; + goto err_usb; + } + + fi_phonet = usb_get_function_instance("phonet"); + if (IS_ERR(fi_phonet)) + pr_debug("could not find phonet function\n"); + + fi_obex1 = usb_get_function_instance("obex"); + if (IS_ERR(fi_obex1)) + pr_debug("could not find obex function 1\n"); + + fi_obex2 = usb_get_function_instance("obex"); + if (IS_ERR(fi_obex2)) + pr_debug("could not find obex function 2\n"); + + fi_acm = usb_get_function_instance("acm"); + if (IS_ERR(fi_acm)) { + status = PTR_ERR(fi_acm); + goto err_obex2_inst; + } + + fi_ecm = usb_get_function_instance("ecm"); + if (IS_ERR(fi_ecm)) { + status = PTR_ERR(fi_ecm); + goto err_acm_inst; + } + + fi_msg = usb_get_function_instance("mass_storage"); + if (IS_ERR(fi_msg)) { + status = PTR_ERR(fi_msg); + goto err_ecm_inst; + } + + /* set up mass storage function */ + fsg_config_from_params(&fsg_config, &fsg_mod_data, fsg_num_buffers); + fsg_config.vendor_name = "Nokia"; + fsg_config.product_name = "N900"; + + fsg_opts = fsg_opts_from_func_inst(fi_msg); + fsg_opts->no_configfs = true; + + status = fsg_common_set_num_buffers(fsg_opts->common, fsg_num_buffers); + if (status) + goto err_msg_inst; + + status = fsg_common_set_cdev(fsg_opts->common, cdev, fsg_config.can_stall); + if (status) + goto err_msg_buf; + + fsg_common_set_sysfs(fsg_opts->common, true); + + status = fsg_common_create_luns(fsg_opts->common, &fsg_config); + if (status) + goto err_msg_buf; + + fsg_common_set_inquiry_string(fsg_opts->common, fsg_config.vendor_name, + fsg_config.product_name); + + /* finally register the configuration */ + status = usb_add_config(cdev, &nokia_config_500ma_driver, + nokia_bind_config); + if (status < 0) + goto err_msg_luns; + + 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); + if (!IS_ERR_OR_NULL(f_obex1_cfg1)) + usb_put_function(f_obex1_cfg1); + if (!IS_ERR_OR_NULL(f_obex2_cfg1)) + usb_put_function(f_obex2_cfg1); + if (!IS_ERR_OR_NULL(f_phonet_cfg1)) + usb_put_function(f_phonet_cfg1); + usb_put_function(f_ecm_cfg1); +err_msg_luns: + fsg_common_remove_luns(fsg_opts->common); +err_msg_buf: + fsg_common_free_buffers(fsg_opts->common); +err_msg_inst: + usb_put_function_instance(fi_msg); +err_ecm_inst: + usb_put_function_instance(fi_ecm); +err_acm_inst: + usb_put_function_instance(fi_acm); +err_obex2_inst: + if (!IS_ERR(fi_obex2)) + usb_put_function_instance(fi_obex2); + if (!IS_ERR(fi_obex1)) + usb_put_function_instance(fi_obex1); + if (!IS_ERR(fi_phonet)) + usb_put_function_instance(fi_phonet); +err_usb: + return status; +} + +static int nokia_unbind(struct usb_composite_dev *cdev) +{ + if (!IS_ERR_OR_NULL(f_obex1_cfg2)) + usb_put_function(f_obex1_cfg2); + if (!IS_ERR_OR_NULL(f_obex2_cfg2)) + usb_put_function(f_obex2_cfg2); + if (!IS_ERR_OR_NULL(f_obex1_cfg1)) + usb_put_function(f_obex1_cfg1); + if (!IS_ERR_OR_NULL(f_obex2_cfg1)) + usb_put_function(f_obex2_cfg1); + if (!IS_ERR_OR_NULL(f_phonet_cfg1)) + usb_put_function(f_phonet_cfg1); + if (!IS_ERR_OR_NULL(f_phonet_cfg2)) + usb_put_function(f_phonet_cfg2); + usb_put_function(f_acm_cfg1); + usb_put_function(f_acm_cfg2); + usb_put_function(f_ecm_cfg1); + usb_put_function(f_ecm_cfg2); + usb_put_function(f_msg_cfg1); + usb_put_function(f_msg_cfg2); + + usb_put_function_instance(fi_msg); + usb_put_function_instance(fi_ecm); + if (!IS_ERR(fi_obex2)) + usb_put_function_instance(fi_obex2); + if (!IS_ERR(fi_obex1)) + usb_put_function_instance(fi_obex1); + if (!IS_ERR(fi_phonet)) + usb_put_function_instance(fi_phonet); + usb_put_function_instance(fi_acm); + + return 0; +} + +static struct usb_composite_driver nokia_driver = { + .name = "g_nokia", + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_HIGH, + .bind = nokia_bind, + .unbind = nokia_unbind, +}; + +module_usb_composite_driver(nokia_driver); diff --git a/drivers/usb/gadget/legacy/printer.c b/drivers/usb/gadget/legacy/printer.c new file mode 100644 index 000000000..ed762ba9b --- /dev/null +++ b/drivers/usb/gadget/legacy/printer.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * printer.c -- Printer gadget driver + * + * Copyright (C) 2003-2005 David Brownell + * Copyright (C) 2006 Craig W. Nadler + */ + +#include +#include +#include + +#include +#include +#include +#include + +USB_GADGET_COMPOSITE_OPTIONS(); + +#define DRIVER_DESC "Printer Gadget" +#define DRIVER_VERSION "2015 FEB 17" + +static const char shortname [] = "printer"; + +#include "u_printer.h" + +/*-------------------------------------------------------------------------*/ + +/* 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); +MODULE_PARM_DESC(qlen, "The number of 8k buffers to use per endpoint"); + +#define QLEN qlen + +static struct usb_function_instance *fi_printer; +static struct usb_function *f_printer; + +/*-------------------------------------------------------------------------*/ + +/* + * DESCRIPTORS ... most are static, but strings and (full) configuration + * descriptors are built on demand. + */ + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + /* .bcdUSB = DYNAMIC */ + .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 const struct usb_descriptor_header *otg_desc[2]; + +/*-------------------------------------------------------------------------*/ + +/* descriptors that are built on-demand */ + +static char product_desc [40] = DRIVER_DESC; +static char serial_num [40] = "1"; +static char *pnp_string = + "MFG: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_configuration printer_cfg_driver = { + .label = "printer", + .bConfigurationValue = 1, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, +}; + +static int printer_do_config(struct usb_configuration *c) +{ + struct usb_gadget *gadget = c->cdev->gadget; + int status = 0; + + usb_ep_autoconfig_reset(gadget); + + usb_gadget_set_selfpowered(gadget); + + if (gadget_is_otg(gadget)) { + printer_cfg_driver.descriptors = otg_desc; + printer_cfg_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + f_printer = usb_get_function(fi_printer); + if (IS_ERR(f_printer)) + return PTR_ERR(f_printer); + + status = usb_add_function(c, f_printer); + if (status < 0) + usb_put_function(f_printer); + + return status; +} + +static int printer_bind(struct usb_composite_dev *cdev) +{ + struct f_printer_opts *opts; + int ret; + + fi_printer = usb_get_function_instance("printer"); + if (IS_ERR(fi_printer)) + return PTR_ERR(fi_printer); + + opts = container_of(fi_printer, struct f_printer_opts, func_inst); + opts->minor = 0; + opts->q_len = QLEN; + if (iPNPstring) { + opts->pnp_string = kstrdup(iPNPstring, GFP_KERNEL); + if (!opts->pnp_string) { + ret = -ENOMEM; + goto fail_put_func_inst; + } + opts->pnp_string_allocated = true; + /* + * we don't free this memory in case of error + * as printer cleanup func will do this for us + */ + } else { + opts->pnp_string = pnp_string; + } + + ret = usb_string_ids_tab(cdev, strings); + if (ret < 0) + goto fail_put_func_inst; + + 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; + + if (gadget_is_otg(cdev->gadget) && !otg_desc[0]) { + struct usb_descriptor_header *usb_desc; + + usb_desc = usb_otg_descriptor_alloc(cdev->gadget); + if (!usb_desc) { + ret = -ENOMEM; + goto fail_put_func_inst; + } + usb_otg_descriptor_init(cdev->gadget, usb_desc); + otg_desc[0] = usb_desc; + otg_desc[1] = NULL; + } + + ret = usb_add_config(cdev, &printer_cfg_driver, printer_do_config); + if (ret) + goto fail_free_otg_desc; + + usb_composite_overwrite_options(cdev, &coverwrite); + return ret; + +fail_free_otg_desc: + kfree(otg_desc[0]); + otg_desc[0] = NULL; +fail_put_func_inst: + usb_put_function_instance(fi_printer); + return ret; +} + +static int printer_unbind(struct usb_composite_dev *cdev) +{ + usb_put_function(f_printer); + usb_put_function_instance(fi_printer); + + kfree(otg_desc[0]); + otg_desc[0] = NULL; + + return 0; +} + +static struct usb_composite_driver printer_driver = { + .name = shortname, + .dev = &device_desc, + .strings = dev_strings, + .max_speed = USB_SPEED_SUPER, + .bind = printer_bind, + .unbind = printer_unbind, +}; + +module_usb_composite_driver(printer_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Craig Nadler"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/legacy/raw_gadget.c b/drivers/usb/gadget/legacy/raw_gadget.c new file mode 100644 index 000000000..4febf8dac --- /dev/null +++ b/drivers/usb/gadget/legacy/raw_gadget.c @@ -0,0 +1,1376 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Raw Gadget driver. + * See Documentation/usb/raw-gadget.rst for more details. + * + * Copyright (c) 2020 Google, Inc. + * Author: Andrey Konovalov + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define DRIVER_DESC "USB Raw Gadget" +#define DRIVER_NAME "raw-gadget" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Andrey Konovalov"); +MODULE_LICENSE("GPL"); + +/*----------------------------------------------------------------------*/ + +static DEFINE_IDA(driver_id_numbers); +#define DRIVER_DRIVER_NAME_LENGTH_MAX 32 +#define USB_RAW_IO_LENGTH_MAX KMALLOC_MAX_SIZE + +#define RAW_EVENT_QUEUE_SIZE 16 + +struct raw_event_queue { + /* See the comment in raw_event_queue_fetch() for locking details. */ + spinlock_t lock; + struct semaphore sema; + struct usb_raw_event *events[RAW_EVENT_QUEUE_SIZE]; + int size; +}; + +static void raw_event_queue_init(struct raw_event_queue *queue) +{ + spin_lock_init(&queue->lock); + sema_init(&queue->sema, 0); + queue->size = 0; +} + +static int raw_event_queue_add(struct raw_event_queue *queue, + enum usb_raw_event_type type, size_t length, const void *data) +{ + unsigned long flags; + struct usb_raw_event *event; + + spin_lock_irqsave(&queue->lock, flags); + if (queue->size >= RAW_EVENT_QUEUE_SIZE) { + spin_unlock_irqrestore(&queue->lock, flags); + return -ENOMEM; + } + event = kmalloc(sizeof(*event) + length, GFP_ATOMIC); + if (!event) { + spin_unlock_irqrestore(&queue->lock, flags); + return -ENOMEM; + } + event->type = type; + event->length = length; + if (event->length) + memcpy(&event->data[0], data, length); + queue->events[queue->size] = event; + queue->size++; + up(&queue->sema); + spin_unlock_irqrestore(&queue->lock, flags); + return 0; +} + +static struct usb_raw_event *raw_event_queue_fetch( + struct raw_event_queue *queue) +{ + int ret; + unsigned long flags; + struct usb_raw_event *event; + + /* + * This function can be called concurrently. We first check that + * there's at least one event queued by decrementing the semaphore, + * and then take the lock to protect queue struct fields. + */ + ret = down_interruptible(&queue->sema); + if (ret) + return ERR_PTR(ret); + spin_lock_irqsave(&queue->lock, flags); + /* + * queue->size must have the same value as queue->sema counter (before + * the down_interruptible() call above), so this check is a fail-safe. + */ + if (WARN_ON(!queue->size)) { + spin_unlock_irqrestore(&queue->lock, flags); + return ERR_PTR(-ENODEV); + } + event = queue->events[0]; + queue->size--; + memmove(&queue->events[0], &queue->events[1], + queue->size * sizeof(queue->events[0])); + spin_unlock_irqrestore(&queue->lock, flags); + return event; +} + +static void raw_event_queue_destroy(struct raw_event_queue *queue) +{ + int i; + + for (i = 0; i < queue->size; i++) + kfree(queue->events[i]); + queue->size = 0; +} + +/*----------------------------------------------------------------------*/ + +struct raw_dev; + +enum ep_state { + STATE_EP_DISABLED, + STATE_EP_ENABLED, +}; + +struct raw_ep { + struct raw_dev *dev; + enum ep_state state; + struct usb_ep *ep; + u8 addr; + struct usb_request *req; + bool urb_queued; + bool disabling; + ssize_t status; +}; + +enum dev_state { + STATE_DEV_INVALID = 0, + STATE_DEV_OPENED, + STATE_DEV_INITIALIZED, + STATE_DEV_REGISTERING, + STATE_DEV_RUNNING, + STATE_DEV_CLOSED, + STATE_DEV_FAILED +}; + +struct raw_dev { + struct kref count; + spinlock_t lock; + + const char *udc_name; + struct usb_gadget_driver driver; + + /* Reference to misc device: */ + struct device *dev; + + /* Make driver names unique */ + int driver_id_number; + + /* Protected by lock: */ + enum dev_state state; + bool gadget_registered; + struct usb_gadget *gadget; + struct usb_request *req; + bool ep0_in_pending; + bool ep0_out_pending; + bool ep0_urb_queued; + ssize_t ep0_status; + struct raw_ep eps[USB_RAW_EPS_NUM_MAX]; + int eps_num; + + struct completion ep0_done; + struct raw_event_queue queue; +}; + +static struct raw_dev *dev_new(void) +{ + struct raw_dev *dev; + + dev = kzalloc_obj(*dev); + if (!dev) + return NULL; + /* Matches kref_put() in raw_release(). */ + kref_init(&dev->count); + spin_lock_init(&dev->lock); + init_completion(&dev->ep0_done); + raw_event_queue_init(&dev->queue); + dev->driver_id_number = -1; + return dev; +} + +static void dev_free(struct kref *kref) +{ + struct raw_dev *dev = container_of(kref, struct raw_dev, count); + int i; + + kfree(dev->udc_name); + kfree(dev->driver.udc_name); + kfree(dev->driver.driver.name); + if (dev->driver_id_number >= 0) + ida_free(&driver_id_numbers, dev->driver_id_number); + if (dev->req) { + if (dev->ep0_urb_queued) + usb_ep_dequeue(dev->gadget->ep0, dev->req); + usb_ep_free_request(dev->gadget->ep0, dev->req); + } + raw_event_queue_destroy(&dev->queue); + for (i = 0; i < dev->eps_num; i++) { + if (dev->eps[i].state == STATE_EP_DISABLED) + continue; + usb_ep_disable(dev->eps[i].ep); + usb_ep_free_request(dev->eps[i].ep, dev->eps[i].req); + kfree(dev->eps[i].ep->desc); + dev->eps[i].state = STATE_EP_DISABLED; + } + kfree(dev); +} + +/*----------------------------------------------------------------------*/ + +static int raw_queue_event(struct raw_dev *dev, + enum usb_raw_event_type type, size_t length, const void *data) +{ + int ret = 0; + unsigned long flags; + + ret = raw_event_queue_add(&dev->queue, type, length, data); + if (ret < 0) { + spin_lock_irqsave(&dev->lock, flags); + dev->state = STATE_DEV_FAILED; + spin_unlock_irqrestore(&dev->lock, flags); + } + return ret; +} + +static void gadget_ep0_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct raw_dev *dev = req->context; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (req->status) + dev->ep0_status = req->status; + else + dev->ep0_status = req->actual; + if (dev->ep0_in_pending) + dev->ep0_in_pending = false; + else + dev->ep0_out_pending = false; + spin_unlock_irqrestore(&dev->lock, flags); + + complete(&dev->ep0_done); +} + +static u8 get_ep_addr(const char *name) +{ + /* If the endpoint has fixed function (named as e.g. "ep12out-bulk"), + * parse the endpoint address from its name. We deliberately use + * deprecated simple_strtoul() function here, as the number isn't + * followed by '\0' nor '\n'. + */ + if (isdigit(name[2])) + return simple_strtoul(&name[2], NULL, 10); + /* Otherwise the endpoint is configurable (named as e.g. "ep-a"). */ + return USB_RAW_EP_ADDR_ANY; +} + +static int gadget_bind(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + int ret = 0, i = 0; + struct raw_dev *dev = container_of(driver, struct raw_dev, driver); + struct usb_request *req; + struct usb_ep *ep; + unsigned long flags; + + if (strcmp(gadget->name, dev->udc_name) != 0) + return -ENODEV; + + set_gadget_data(gadget, dev); + req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL); + if (!req) { + dev_err(&gadget->dev, "usb_ep_alloc_request failed\n"); + set_gadget_data(gadget, NULL); + return -ENOMEM; + } + + spin_lock_irqsave(&dev->lock, flags); + dev->req = req; + dev->req->context = dev; + dev->req->complete = gadget_ep0_complete; + dev->gadget = gadget; + gadget_for_each_ep(ep, dev->gadget) { + dev->eps[i].ep = ep; + dev->eps[i].addr = get_ep_addr(ep->name); + dev->eps[i].state = STATE_EP_DISABLED; + i++; + } + dev->eps_num = i; + spin_unlock_irqrestore(&dev->lock, flags); + + dev_dbg(&gadget->dev, "gadget connected\n"); + ret = raw_queue_event(dev, USB_RAW_EVENT_CONNECT, 0, NULL); + if (ret < 0) { + dev_err(&gadget->dev, "failed to queue connect event\n"); + set_gadget_data(gadget, NULL); + return ret; + } + + /* Matches kref_put() in gadget_unbind(). */ + kref_get(&dev->count); + return ret; +} + +static void gadget_unbind(struct usb_gadget *gadget) +{ + struct raw_dev *dev = get_gadget_data(gadget); + + set_gadget_data(gadget, NULL); + /* Matches kref_get() in gadget_bind(). */ + kref_put(&dev->count, dev_free); +} + +static int gadget_setup(struct usb_gadget *gadget, + const struct usb_ctrlrequest *ctrl) +{ + int ret = 0; + struct raw_dev *dev = get_gadget_data(gadget); + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_err(&gadget->dev, "ignoring, device is not running\n"); + ret = -ENODEV; + goto out_unlock; + } + if (dev->ep0_in_pending || dev->ep0_out_pending) { + dev_dbg(&gadget->dev, "stalling, request already pending\n"); + ret = -EBUSY; + goto out_unlock; + } + if ((ctrl->bRequestType & USB_DIR_IN) && ctrl->wLength) + dev->ep0_in_pending = true; + else + dev->ep0_out_pending = true; + spin_unlock_irqrestore(&dev->lock, flags); + + ret = raw_queue_event(dev, USB_RAW_EVENT_CONTROL, sizeof(*ctrl), ctrl); + if (ret < 0) + dev_err(&gadget->dev, "failed to queue control event\n"); + goto out; + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); +out: + if (ret == 0 && ctrl->wLength == 0) { + /* + * Return USB_GADGET_DELAYED_STATUS as a workaround to stop + * some UDC drivers (e.g. dwc3) from automatically proceeding + * with the status stage for 0-length transfers. + * Should be removed once all UDC drivers are fixed to always + * delay the status stage until a response is queued to EP0. + */ + return USB_GADGET_DELAYED_STATUS; + } + return ret; +} + +static void gadget_disconnect(struct usb_gadget *gadget) +{ + struct raw_dev *dev = get_gadget_data(gadget); + int ret; + + dev_dbg(&gadget->dev, "gadget disconnected\n"); + ret = raw_queue_event(dev, USB_RAW_EVENT_DISCONNECT, 0, NULL); + if (ret < 0) + dev_err(&gadget->dev, "failed to queue disconnect event\n"); +} +static void gadget_suspend(struct usb_gadget *gadget) +{ + struct raw_dev *dev = get_gadget_data(gadget); + int ret; + + dev_dbg(&gadget->dev, "gadget suspended\n"); + ret = raw_queue_event(dev, USB_RAW_EVENT_SUSPEND, 0, NULL); + if (ret < 0) + dev_err(&gadget->dev, "failed to queue suspend event\n"); +} +static void gadget_resume(struct usb_gadget *gadget) +{ + struct raw_dev *dev = get_gadget_data(gadget); + int ret; + + dev_dbg(&gadget->dev, "gadget resumed\n"); + ret = raw_queue_event(dev, USB_RAW_EVENT_RESUME, 0, NULL); + if (ret < 0) + dev_err(&gadget->dev, "failed to queue resume event\n"); +} +static void gadget_reset(struct usb_gadget *gadget) +{ + struct raw_dev *dev = get_gadget_data(gadget); + int ret; + + dev_dbg(&gadget->dev, "gadget reset\n"); + ret = raw_queue_event(dev, USB_RAW_EVENT_RESET, 0, NULL); + if (ret < 0) + dev_err(&gadget->dev, "failed to queue reset event\n"); +} + +/*----------------------------------------------------------------------*/ + +static struct miscdevice raw_misc_device; + +static int raw_open(struct inode *inode, struct file *fd) +{ + struct raw_dev *dev; + + /* Nonblocking I/O is not supported yet. */ + if (fd->f_flags & O_NONBLOCK) + return -EINVAL; + + dev = dev_new(); + if (!dev) + return -ENOMEM; + fd->private_data = dev; + dev->state = STATE_DEV_OPENED; + dev->dev = raw_misc_device.this_device; + return 0; +} + +static int raw_release(struct inode *inode, struct file *fd) +{ + int ret = 0; + struct raw_dev *dev = fd->private_data; + unsigned long flags; + bool unregister = false; + + spin_lock_irqsave(&dev->lock, flags); + dev->state = STATE_DEV_CLOSED; + if (!dev->gadget) { + spin_unlock_irqrestore(&dev->lock, flags); + goto out_put; + } + if (dev->gadget_registered) + unregister = true; + dev->gadget_registered = false; + spin_unlock_irqrestore(&dev->lock, flags); + + if (unregister) { + ret = usb_gadget_unregister_driver(&dev->driver); + if (ret != 0) + dev_err(dev->dev, + "usb_gadget_unregister_driver() failed with %d\n", + ret); + /* Matches kref_get() in raw_ioctl_run(). */ + kref_put(&dev->count, dev_free); + } + +out_put: + /* Matches dev_new() in raw_open(). */ + kref_put(&dev->count, dev_free); + return ret; +} + +/*----------------------------------------------------------------------*/ + +static int raw_ioctl_init(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + int driver_id_number; + struct usb_raw_init arg; + char *udc_driver_name; + char *udc_device_name; + char *driver_driver_name; + unsigned long flags; + + if (copy_from_user(&arg, (void __user *)value, sizeof(arg))) + return -EFAULT; + + switch (arg.speed) { + case USB_SPEED_UNKNOWN: + arg.speed = USB_SPEED_HIGH; + break; + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + case USB_SPEED_SUPER: + break; + default: + return -EINVAL; + } + + driver_id_number = ida_alloc(&driver_id_numbers, GFP_KERNEL); + if (driver_id_number < 0) + return driver_id_number; + + driver_driver_name = kmalloc(DRIVER_DRIVER_NAME_LENGTH_MAX, GFP_KERNEL); + if (!driver_driver_name) { + ret = -ENOMEM; + goto out_free_driver_id_number; + } + snprintf(driver_driver_name, DRIVER_DRIVER_NAME_LENGTH_MAX, + DRIVER_NAME ".%d", driver_id_number); + + udc_driver_name = kmalloc(UDC_NAME_LENGTH_MAX, GFP_KERNEL); + if (!udc_driver_name) { + ret = -ENOMEM; + goto out_free_driver_driver_name; + } + ret = strscpy(udc_driver_name, &arg.driver_name[0], + UDC_NAME_LENGTH_MAX); + if (ret < 0) + goto out_free_udc_driver_name; + ret = 0; + + udc_device_name = kmalloc(UDC_NAME_LENGTH_MAX, GFP_KERNEL); + if (!udc_device_name) { + ret = -ENOMEM; + goto out_free_udc_driver_name; + } + ret = strscpy(udc_device_name, &arg.device_name[0], + UDC_NAME_LENGTH_MAX); + if (ret < 0) + goto out_free_udc_device_name; + ret = 0; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_OPENED) { + dev_dbg(dev->dev, "fail, device is not opened\n"); + ret = -EINVAL; + goto out_unlock; + } + dev->udc_name = udc_driver_name; + + dev->driver.function = DRIVER_DESC; + dev->driver.max_speed = arg.speed; + dev->driver.setup = gadget_setup; + dev->driver.disconnect = gadget_disconnect; + dev->driver.bind = gadget_bind; + dev->driver.unbind = gadget_unbind; + dev->driver.suspend = gadget_suspend; + dev->driver.resume = gadget_resume; + dev->driver.reset = gadget_reset; + dev->driver.driver.name = driver_driver_name; + dev->driver.udc_name = udc_device_name; + dev->driver.match_existing_only = 1; + dev->driver_id_number = driver_id_number; + + dev->state = STATE_DEV_INITIALIZED; + spin_unlock_irqrestore(&dev->lock, flags); + return ret; + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); +out_free_udc_device_name: + kfree(udc_device_name); +out_free_udc_driver_name: + kfree(udc_driver_name); +out_free_driver_driver_name: + kfree(driver_driver_name); +out_free_driver_id_number: + ida_free(&driver_id_numbers, driver_id_number); + return ret; +} + +static int raw_ioctl_run(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + unsigned long flags; + + if (value) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_INITIALIZED) { + dev_dbg(dev->dev, "fail, device is not initialized\n"); + ret = -EINVAL; + goto out_unlock; + } + dev->state = STATE_DEV_REGISTERING; + spin_unlock_irqrestore(&dev->lock, flags); + + ret = usb_gadget_register_driver(&dev->driver); + + spin_lock_irqsave(&dev->lock, flags); + if (ret) { + dev_err(dev->dev, + "fail, usb_gadget_register_driver returned %d\n", ret); + dev->state = STATE_DEV_FAILED; + goto out_unlock; + } + dev->gadget_registered = true; + dev->state = STATE_DEV_RUNNING; + /* Matches kref_put() in raw_release(). */ + kref_get(&dev->count); + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_event_fetch(struct raw_dev *dev, unsigned long value) +{ + struct usb_raw_event arg; + unsigned long flags; + struct usb_raw_event *event; + uint32_t length; + + if (copy_from_user(&arg, (void __user *)value, sizeof(arg))) + return -EFAULT; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + spin_unlock_irqrestore(&dev->lock, flags); + return -EINVAL; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + spin_unlock_irqrestore(&dev->lock, flags); + return -EBUSY; + } + spin_unlock_irqrestore(&dev->lock, flags); + + event = raw_event_queue_fetch(&dev->queue); + if (PTR_ERR(event) == -EINTR) { + dev_dbg(&dev->gadget->dev, "event fetching interrupted\n"); + return -EINTR; + } + if (IS_ERR(event)) { + dev_err(&dev->gadget->dev, "failed to fetch event\n"); + spin_lock_irqsave(&dev->lock, flags); + dev->state = STATE_DEV_FAILED; + spin_unlock_irqrestore(&dev->lock, flags); + return -ENODEV; + } + length = min(arg.length, event->length); + if (copy_to_user((void __user *)value, event, sizeof(*event) + length)) { + kfree(event); + return -EFAULT; + } + + kfree(event); + return 0; +} + +static void *raw_alloc_io_data(struct usb_raw_ep_io *io, void __user *ptr, + bool get_from_user) +{ + void *data; + + if (copy_from_user(io, ptr, sizeof(*io))) + return ERR_PTR(-EFAULT); + if (io->ep >= USB_RAW_EPS_NUM_MAX) + return ERR_PTR(-EINVAL); + if (!usb_raw_io_flags_valid(io->flags)) + return ERR_PTR(-EINVAL); + if (io->length > USB_RAW_IO_LENGTH_MAX) + return ERR_PTR(-EINVAL); + if (get_from_user) + data = memdup_user(ptr + sizeof(*io), io->length); + else { + data = kmalloc(io->length, GFP_KERNEL); + if (!data) + data = ERR_PTR(-ENOMEM); + } + return data; +} + +static int raw_process_ep0_io(struct raw_dev *dev, struct usb_raw_ep_io *io, + void *data, bool in) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + if (dev->ep0_urb_queued) { + dev_dbg(&dev->gadget->dev, "fail, urb already queued\n"); + ret = -EBUSY; + goto out_unlock; + } + if ((in && !dev->ep0_in_pending) || + (!in && !dev->ep0_out_pending)) { + dev_dbg(&dev->gadget->dev, "fail, wrong direction\n"); + ret = -EBUSY; + goto out_unlock; + } + if (WARN_ON(in && dev->ep0_out_pending)) { + ret = -ENODEV; + dev->state = STATE_DEV_FAILED; + goto out_unlock; + } + if (WARN_ON(!in && dev->ep0_in_pending)) { + ret = -ENODEV; + dev->state = STATE_DEV_FAILED; + goto out_unlock; + } + + dev->req->buf = data; + dev->req->length = io->length; + dev->req->zero = usb_raw_io_flags_zero(io->flags); + dev->ep0_urb_queued = true; + spin_unlock_irqrestore(&dev->lock, flags); + + ret = usb_ep_queue(dev->gadget->ep0, dev->req, GFP_KERNEL); + if (ret) { + dev_err(&dev->gadget->dev, + "fail, usb_ep_queue returned %d\n", ret); + spin_lock_irqsave(&dev->lock, flags); + goto out_queue_failed; + } + + ret = wait_for_completion_interruptible(&dev->ep0_done); + if (ret) { + dev_dbg(&dev->gadget->dev, "wait interrupted\n"); + usb_ep_dequeue(dev->gadget->ep0, dev->req); + wait_for_completion(&dev->ep0_done); + spin_lock_irqsave(&dev->lock, flags); + if (dev->ep0_status == -ECONNRESET) + dev->ep0_status = -EINTR; + goto out_interrupted; + } + + spin_lock_irqsave(&dev->lock, flags); + +out_interrupted: + ret = dev->ep0_status; +out_queue_failed: + dev->ep0_urb_queued = false; +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_ep0_write(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + void *data; + struct usb_raw_ep_io io; + + data = raw_alloc_io_data(&io, (void __user *)value, true); + if (IS_ERR(data)) + return PTR_ERR(data); + ret = raw_process_ep0_io(dev, &io, data, true); + kfree(data); + return ret; +} + +static int raw_ioctl_ep0_read(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + void *data; + struct usb_raw_ep_io io; + unsigned int length; + + data = raw_alloc_io_data(&io, (void __user *)value, false); + if (IS_ERR(data)) + return PTR_ERR(data); + ret = raw_process_ep0_io(dev, &io, data, false); + if (ret < 0) + goto free; + + length = min_t(unsigned int, io.length, ret); + if (copy_to_user((void __user *)(value + sizeof(io)), data, length)) + ret = -EFAULT; + else + ret = length; +free: + kfree(data); + return ret; +} + +static int raw_ioctl_ep0_stall(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + unsigned long flags; + + if (value) + return -EINVAL; + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + if (dev->ep0_urb_queued) { + dev_dbg(&dev->gadget->dev, "fail, urb already queued\n"); + ret = -EBUSY; + goto out_unlock; + } + if (!dev->ep0_in_pending && !dev->ep0_out_pending) { + dev_dbg(&dev->gadget->dev, "fail, no request pending\n"); + ret = -EBUSY; + goto out_unlock; + } + + ret = usb_ep_set_halt(dev->gadget->ep0); + if (ret < 0) + dev_err(&dev->gadget->dev, + "fail, usb_ep_set_halt returned %d\n", ret); + + if (dev->ep0_in_pending) + dev->ep0_in_pending = false; + else + dev->ep0_out_pending = false; + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_ep_enable(struct raw_dev *dev, unsigned long value) +{ + int ret = 0, i; + unsigned long flags; + struct usb_endpoint_descriptor *desc; + struct raw_ep *ep; + bool ep_props_matched = false; + + desc = memdup_user((void __user *)value, sizeof(*desc)); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + /* + * Endpoints with a maxpacket length of 0 can cause crashes in UDC + * drivers. + */ + if (usb_endpoint_maxp(desc) == 0) { + dev_dbg(dev->dev, "fail, bad endpoint maxpacket\n"); + kfree(desc); + return -EINVAL; + } + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_free; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_free; + } + + for (i = 0; i < dev->eps_num; i++) { + ep = &dev->eps[i]; + if (ep->addr != usb_endpoint_num(desc) && + ep->addr != USB_RAW_EP_ADDR_ANY) + continue; + if (!usb_gadget_ep_match_desc(dev->gadget, ep->ep, desc, NULL)) + continue; + ep_props_matched = true; + if (ep->state != STATE_EP_DISABLED) + continue; + ep->ep->desc = desc; + ret = usb_ep_enable(ep->ep); + if (ret < 0) { + dev_err(&dev->gadget->dev, + "fail, usb_ep_enable returned %d\n", ret); + goto out_free; + } + ep->req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC); + if (!ep->req) { + dev_err(&dev->gadget->dev, + "fail, usb_ep_alloc_request failed\n"); + usb_ep_disable(ep->ep); + ret = -ENOMEM; + goto out_free; + } + ep->state = STATE_EP_ENABLED; + ep->ep->driver_data = ep; + ret = i; + goto out_unlock; + } + + if (!ep_props_matched) { + dev_dbg(&dev->gadget->dev, "fail, bad endpoint descriptor\n"); + ret = -EINVAL; + } else { + dev_dbg(&dev->gadget->dev, "fail, no endpoints available\n"); + ret = -EBUSY; + } + +out_free: + kfree(desc); +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_ep_disable(struct raw_dev *dev, unsigned long value) +{ + int ret = 0, i = value; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + if (i < 0 || i >= dev->eps_num) { + dev_dbg(dev->dev, "fail, invalid endpoint\n"); + ret = -EBUSY; + goto out_unlock; + } + if (dev->eps[i].state == STATE_EP_DISABLED) { + dev_dbg(&dev->gadget->dev, "fail, endpoint is not enabled\n"); + ret = -EINVAL; + goto out_unlock; + } + if (dev->eps[i].disabling) { + dev_dbg(&dev->gadget->dev, + "fail, disable already in progress\n"); + ret = -EINVAL; + goto out_unlock; + } + if (dev->eps[i].urb_queued) { + dev_dbg(&dev->gadget->dev, + "fail, waiting for urb completion\n"); + ret = -EINVAL; + goto out_unlock; + } + dev->eps[i].disabling = true; + spin_unlock_irqrestore(&dev->lock, flags); + + usb_ep_disable(dev->eps[i].ep); + + spin_lock_irqsave(&dev->lock, flags); + usb_ep_free_request(dev->eps[i].ep, dev->eps[i].req); + kfree(dev->eps[i].ep->desc); + dev->eps[i].state = STATE_EP_DISABLED; + dev->eps[i].disabling = false; + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_ep_set_clear_halt_wedge(struct raw_dev *dev, + unsigned long value, bool set, bool halt) +{ + int ret = 0, i = value; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + if (i < 0 || i >= dev->eps_num) { + dev_dbg(dev->dev, "fail, invalid endpoint\n"); + ret = -EBUSY; + goto out_unlock; + } + if (dev->eps[i].state == STATE_EP_DISABLED) { + dev_dbg(&dev->gadget->dev, "fail, endpoint is not enabled\n"); + ret = -EINVAL; + goto out_unlock; + } + if (dev->eps[i].disabling) { + dev_dbg(&dev->gadget->dev, + "fail, disable is in progress\n"); + ret = -EINVAL; + goto out_unlock; + } + if (dev->eps[i].urb_queued) { + dev_dbg(&dev->gadget->dev, + "fail, waiting for urb completion\n"); + ret = -EINVAL; + goto out_unlock; + } + if (usb_endpoint_xfer_isoc(dev->eps[i].ep->desc)) { + dev_dbg(&dev->gadget->dev, + "fail, can't halt/wedge ISO endpoint\n"); + ret = -EINVAL; + goto out_unlock; + } + + if (set && halt) { + ret = usb_ep_set_halt(dev->eps[i].ep); + if (ret < 0) + dev_err(&dev->gadget->dev, + "fail, usb_ep_set_halt returned %d\n", ret); + } else if (!set && halt) { + ret = usb_ep_clear_halt(dev->eps[i].ep); + if (ret < 0) + dev_err(&dev->gadget->dev, + "fail, usb_ep_clear_halt returned %d\n", ret); + } else if (set && !halt) { + ret = usb_ep_set_wedge(dev->eps[i].ep); + if (ret < 0) + dev_err(&dev->gadget->dev, + "fail, usb_ep_set_wedge returned %d\n", ret); + } + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static void gadget_ep_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct raw_ep *r_ep = (struct raw_ep *)ep->driver_data; + struct raw_dev *dev = r_ep->dev; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (req->status) + r_ep->status = req->status; + else + r_ep->status = req->actual; + spin_unlock_irqrestore(&dev->lock, flags); + + complete((struct completion *)req->context); +} + +static int raw_process_ep_io(struct raw_dev *dev, struct usb_raw_ep_io *io, + void *data, bool in) +{ + int ret = 0; + unsigned long flags; + struct raw_ep *ep; + DECLARE_COMPLETION_ONSTACK(done); + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + if (io->ep >= dev->eps_num) { + dev_dbg(&dev->gadget->dev, "fail, invalid endpoint\n"); + ret = -EINVAL; + goto out_unlock; + } + ep = &dev->eps[io->ep]; + if (ep->state != STATE_EP_ENABLED) { + dev_dbg(&dev->gadget->dev, "fail, endpoint is not enabled\n"); + ret = -EBUSY; + goto out_unlock; + } + if (ep->disabling) { + dev_dbg(&dev->gadget->dev, + "fail, endpoint is already being disabled\n"); + ret = -EBUSY; + goto out_unlock; + } + if (ep->urb_queued) { + dev_dbg(&dev->gadget->dev, "fail, urb already queued\n"); + ret = -EBUSY; + goto out_unlock; + } + if (in != usb_endpoint_dir_in(ep->ep->desc)) { + dev_dbg(&dev->gadget->dev, "fail, wrong direction\n"); + ret = -EINVAL; + goto out_unlock; + } + + ep->dev = dev; + ep->req->context = &done; + ep->req->complete = gadget_ep_complete; + ep->req->buf = data; + ep->req->length = io->length; + ep->req->zero = usb_raw_io_flags_zero(io->flags); + ep->urb_queued = true; + spin_unlock_irqrestore(&dev->lock, flags); + + ret = usb_ep_queue(ep->ep, ep->req, GFP_KERNEL); + if (ret) { + dev_err(&dev->gadget->dev, + "fail, usb_ep_queue returned %d\n", ret); + spin_lock_irqsave(&dev->lock, flags); + goto out_queue_failed; + } + + ret = wait_for_completion_interruptible(&done); + if (ret) { + dev_dbg(&dev->gadget->dev, "wait interrupted\n"); + usb_ep_dequeue(ep->ep, ep->req); + wait_for_completion(&done); + spin_lock_irqsave(&dev->lock, flags); + if (ep->status == -ECONNRESET) + ep->status = -EINTR; + goto out_interrupted; + } + + spin_lock_irqsave(&dev->lock, flags); + +out_interrupted: + ret = ep->status; +out_queue_failed: + ep->urb_queued = false; +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_ep_write(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + char *data; + struct usb_raw_ep_io io; + + data = raw_alloc_io_data(&io, (void __user *)value, true); + if (IS_ERR(data)) + return PTR_ERR(data); + ret = raw_process_ep_io(dev, &io, data, true); + kfree(data); + return ret; +} + +static int raw_ioctl_ep_read(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + char *data; + struct usb_raw_ep_io io; + unsigned int length; + + data = raw_alloc_io_data(&io, (void __user *)value, false); + if (IS_ERR(data)) + return PTR_ERR(data); + ret = raw_process_ep_io(dev, &io, data, false); + if (ret < 0) + goto free; + + length = min_t(unsigned int, io.length, ret); + if (copy_to_user((void __user *)(value + sizeof(io)), data, length)) + ret = -EFAULT; + else + ret = length; +free: + kfree(data); + return ret; +} + +static int raw_ioctl_configure(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + unsigned long flags; + + if (value) + return -EINVAL; + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + usb_gadget_set_state(dev->gadget, USB_STATE_CONFIGURED); + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static int raw_ioctl_vbus_draw(struct raw_dev *dev, unsigned long value) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + goto out_unlock; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + goto out_unlock; + } + usb_gadget_vbus_draw(dev->gadget, 2 * value); + +out_unlock: + spin_unlock_irqrestore(&dev->lock, flags); + return ret; +} + +static void fill_ep_caps(struct usb_ep_caps *caps, + struct usb_raw_ep_caps *raw_caps) +{ + raw_caps->type_control = caps->type_control; + raw_caps->type_iso = caps->type_iso; + raw_caps->type_bulk = caps->type_bulk; + raw_caps->type_int = caps->type_int; + raw_caps->dir_in = caps->dir_in; + raw_caps->dir_out = caps->dir_out; +} + +static void fill_ep_limits(struct usb_ep *ep, struct usb_raw_ep_limits *limits) +{ + limits->maxpacket_limit = ep->maxpacket_limit; + limits->max_streams = ep->max_streams; +} + +static int raw_ioctl_eps_info(struct raw_dev *dev, unsigned long value) +{ + int ret = 0, i; + unsigned long flags; + struct usb_raw_eps_info *info; + struct raw_ep *ep; + + info = kzalloc_obj(*info); + if (!info) { + ret = -ENOMEM; + goto out; + } + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state != STATE_DEV_RUNNING) { + dev_dbg(dev->dev, "fail, device is not running\n"); + ret = -EINVAL; + spin_unlock_irqrestore(&dev->lock, flags); + goto out_free; + } + if (!dev->gadget) { + dev_dbg(dev->dev, "fail, gadget is not bound\n"); + ret = -EBUSY; + spin_unlock_irqrestore(&dev->lock, flags); + goto out_free; + } + + for (i = 0; i < dev->eps_num; i++) { + ep = &dev->eps[i]; + strscpy(&info->eps[i].name[0], ep->ep->name, + USB_RAW_EP_NAME_MAX); + info->eps[i].addr = ep->addr; + fill_ep_caps(&ep->ep->caps, &info->eps[i].caps); + fill_ep_limits(ep->ep, &info->eps[i].limits); + } + ret = dev->eps_num; + spin_unlock_irqrestore(&dev->lock, flags); + + if (copy_to_user((void __user *)value, info, sizeof(*info))) + ret = -EFAULT; + +out_free: + kfree(info); +out: + return ret; +} + +static long raw_ioctl(struct file *fd, unsigned int cmd, unsigned long value) +{ + struct raw_dev *dev = fd->private_data; + int ret = 0; + + if (!dev) + return -EBUSY; + + switch (cmd) { + case USB_RAW_IOCTL_INIT: + ret = raw_ioctl_init(dev, value); + break; + case USB_RAW_IOCTL_RUN: + ret = raw_ioctl_run(dev, value); + break; + case USB_RAW_IOCTL_EVENT_FETCH: + ret = raw_ioctl_event_fetch(dev, value); + break; + case USB_RAW_IOCTL_EP0_WRITE: + ret = raw_ioctl_ep0_write(dev, value); + break; + case USB_RAW_IOCTL_EP0_READ: + ret = raw_ioctl_ep0_read(dev, value); + break; + case USB_RAW_IOCTL_EP_ENABLE: + ret = raw_ioctl_ep_enable(dev, value); + break; + case USB_RAW_IOCTL_EP_DISABLE: + ret = raw_ioctl_ep_disable(dev, value); + break; + case USB_RAW_IOCTL_EP_WRITE: + ret = raw_ioctl_ep_write(dev, value); + break; + case USB_RAW_IOCTL_EP_READ: + ret = raw_ioctl_ep_read(dev, value); + break; + case USB_RAW_IOCTL_CONFIGURE: + ret = raw_ioctl_configure(dev, value); + break; + case USB_RAW_IOCTL_VBUS_DRAW: + ret = raw_ioctl_vbus_draw(dev, value); + break; + case USB_RAW_IOCTL_EPS_INFO: + ret = raw_ioctl_eps_info(dev, value); + break; + case USB_RAW_IOCTL_EP0_STALL: + ret = raw_ioctl_ep0_stall(dev, value); + break; + case USB_RAW_IOCTL_EP_SET_HALT: + ret = raw_ioctl_ep_set_clear_halt_wedge( + dev, value, true, true); + break; + case USB_RAW_IOCTL_EP_CLEAR_HALT: + ret = raw_ioctl_ep_set_clear_halt_wedge( + dev, value, false, true); + break; + case USB_RAW_IOCTL_EP_SET_WEDGE: + ret = raw_ioctl_ep_set_clear_halt_wedge( + dev, value, true, false); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/*----------------------------------------------------------------------*/ + +static const struct file_operations raw_fops = { + .open = raw_open, + .unlocked_ioctl = raw_ioctl, + .compat_ioctl = raw_ioctl, + .release = raw_release, +}; + +static struct miscdevice raw_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = DRIVER_NAME, + .fops = &raw_fops, +}; + +module_misc_device(raw_misc_device); diff --git a/drivers/usb/gadget/legacy/serial.c b/drivers/usb/gadget/legacy/serial.c new file mode 100644 index 000000000..4974bee60 --- /dev/null +++ b/drivers/usb/gadget/legacy/serial.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include +#include +#include +#include +#include +#include + +#include "u_serial.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 = DYNAMIC */ + /* .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 const struct usb_descriptor_header *otg_desc[2]; + +/*-------------------------------------------------------------------------*/ + +/* 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 bool enable = true; + +static int switch_gserial_enable(bool do_enable); + +static int enable_set(const char *s, const struct kernel_param *kp) +{ + bool do_enable; + int ret; + + if (!s) /* called for no-arg enable == default */ + return 0; + + ret = kstrtobool(s, &do_enable); + if (ret || enable == do_enable) + return ret; + + ret = switch_gserial_enable(do_enable); + if (!ret) + enable = do_enable; + + return ret; +} + +static const struct kernel_param_ops enable_ops = { + .set = enable_set, + .get = param_get_bool, +}; + +module_param_cb(enable, &enable_ops, &enable, 0644); + +/*-------------------------------------------------------------------------*/ + +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 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)) { + if (!otg_desc[0]) { + struct usb_descriptor_header *usb_desc; + + usb_desc = usb_otg_descriptor_alloc(cdev->gadget); + if (!usb_desc) { + status = -ENOMEM; + goto fail; + } + usb_otg_descriptor_init(cdev->gadget, usb_desc); + otg_desc[0] = usb_desc; + otg_desc[1] = NULL; + } + 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 fail1; + + usb_composite_overwrite_options(cdev, &coverwrite); + INFO(cdev, "%s\n", GS_VERSION_NAME); + + return 0; +fail1: + kfree(otg_desc[0]); + otg_desc[0] = NULL; +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]); + } + + kfree(otg_desc[0]); + otg_desc[0] = NULL; + + return 0; +} + +static 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 switch_gserial_enable(bool do_enable) +{ + if (!serial_config_driver.label) + /* gserial_init() was not called, yet */ + return 0; + + if (do_enable) + return usb_composite_probe(&gserial_driver); + + usb_composite_unregister(&gserial_driver); + return 0; +} + +static int __init gserial_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; + + if (!enable) + return 0; + + return usb_composite_probe(&gserial_driver); +} +module_init(gserial_init); + +static void __exit gserial_cleanup(void) +{ + if (enable) + usb_composite_unregister(&gserial_driver); +} +module_exit(gserial_cleanup); diff --git a/drivers/usb/gadget/legacy/tcm_usb_gadget.c b/drivers/usb/gadget/legacy/tcm_usb_gadget.c new file mode 100644 index 000000000..fc1e06246 --- /dev/null +++ b/drivers/usb/gadget/legacy/tcm_usb_gadget.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0 +/* 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "u_tcm.h" + +USB_GADGET_COMPOSITE_OPTIONS(); + +#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 = DYNAMIC */ + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .idVendor = cpu_to_le16(UAS_VENDOR_ID), + .idProduct = cpu_to_le16(UAS_PRODUCT_ID), + .bNumConfigurations = 1, +}; + +#define USB_G_STR_CONFIG USB_GADGET_FIRST_AVAIL_IDX + +static struct usb_string usbg_us_strings[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "Target Manufacturer", + [USB_GADGET_PRODUCT_IDX].s = "Target Product", + [USB_GADGET_SERIAL_IDX].s = "000000000001", + [USB_G_STR_CONFIG].s = "default config", + { }, +}; + +static struct usb_gadget_strings usbg_stringtab = { + .language = 0x0409, + .strings = usbg_us_strings, +}; + +static struct usb_gadget_strings *usbg_strings[] = { + &usbg_stringtab, + NULL, +}; + +static struct usb_function_instance *fi_tcm; +static struct usb_function *f_tcm; + +static int guas_unbind(struct usb_composite_dev *cdev) +{ + if (!IS_ERR_OR_NULL(f_tcm)) + usb_put_function(f_tcm); + + return 0; +} + +static int tcm_do_config(struct usb_configuration *c) +{ + int status; + + f_tcm = usb_get_function(fi_tcm); + if (IS_ERR(f_tcm)) + return PTR_ERR(f_tcm); + + status = usb_add_function(c, f_tcm); + if (status < 0) { + usb_put_function(f_tcm); + return status; + } + + return 0; +} + +static struct usb_configuration usbg_config_driver = { + .label = "Linux Target", + .bConfigurationValue = 1, + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +static int usbg_attach(struct usb_function_instance *f); +static void usbg_detach(struct usb_function_instance *f); + +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, tcm_do_config); + if (ret) + return ret; + usb_composite_overwrite_options(cdev, &coverwrite); + return 0; +} + +static 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 usb_function_instance *f) +{ + return usb_composite_probe(&usbg_driver); +} + +static void usbg_detach(struct usb_function_instance *f) +{ + usb_composite_unregister(&usbg_driver); +} + +static int __init usb_target_gadget_init(void) +{ + struct f_tcm_opts *tcm_opts; + + fi_tcm = usb_get_function_instance("tcm"); + if (IS_ERR(fi_tcm)) + return PTR_ERR(fi_tcm); + + tcm_opts = container_of(fi_tcm, struct f_tcm_opts, func_inst); + mutex_lock(&tcm_opts->dep_lock); + tcm_opts->tcm_register_callback = usbg_attach; + tcm_opts->tcm_unregister_callback = usbg_detach; + tcm_opts->dependent = THIS_MODULE; + tcm_opts->can_attach = true; + tcm_opts->has_dep = true; + mutex_unlock(&tcm_opts->dep_lock); + + fi_tcm->set_inst_name(fi_tcm, "tcm-legacy"); + + return 0; +} +module_init(usb_target_gadget_init); + +static void __exit usb_target_gadget_exit(void) +{ + if (!IS_ERR_OR_NULL(fi_tcm)) + usb_put_function_instance(fi_tcm); + +} +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/legacy/webcam.c b/drivers/usb/gadget/legacy/webcam.c new file mode 100644 index 000000000..c395438d3 --- /dev/null +++ b/drivers/usb/gadget/legacy/webcam.c @@ -0,0 +1,608 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * webcam.c -- USB webcam gadget driver + * + * Copyright (C) 2009-2010 + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include +#include +#include + +#include "u_uvc.h" +#include "uvc_configfs.h" + +USB_GADGET_COMPOSITE_OPTIONS(); + +/*-------------------------------------------------------------------------*/ + +/* 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)"); + +/* -------------------------------------------------------------------------- + * 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_function_instance *fi_uvc; +static struct usb_function *f_uvc; + +static struct usb_device_descriptor webcam_device_descriptor = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + /* .bcdUSB = DYNAMIC */ + .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 */ +}; + +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(0x0110), + .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, + .bmVideoStandards = 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 uvcg_color_matching uvcg_color_matching = { + .desc = { + .bLength = UVC_DT_COLOR_MATCHING_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = UVC_VS_COLORFORMAT, + .bColorPrimaries = 1, + .bTransferCharacteristics = 1, + .bMatrixCoefficients = 4, + }, +}; + +static struct uvcg_uncompressed uvcg_format_yuv = { + .fmt = { + .type = UVCG_UNCOMPRESSED, + /* add to .frames and fill .num_frames at runtime */ + .color_matching = (struct uvcg_color_matching *)&uvcg_color_matching, + }, + .desc = { + .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, + .bmInterlaceFlags = 0, + .bCopyProtect = 0, + }, +}; + +static struct uvcg_format_ptr uvcg_format_ptr_yuv = { + .fmt = &uvcg_format_yuv.fmt, +}; + +DECLARE_UVC_FRAME_UNCOMPRESSED(1); +DECLARE_UVC_FRAME_UNCOMPRESSED(3); + +#define UVCG_WIDTH_360P 640 +#define UVCG_HEIGHT_360P 360 +#define UVCG_MIN_BITRATE_360P 18432000 +#define UVCG_MAX_BITRATE_360P 55296000 +#define UVCG_MAX_VIDEO_FB_SZ_360P 460800 +#define UVCG_FRM_INTERV_0_360P 666666 +#define UVCG_FRM_INTERV_1_360P 1000000 +#define UVCG_FRM_INTERV_2_360P 5000000 +#define UVCG_DEFAULT_FRM_INTERV_360P UVCG_FRM_INTERV_0_360P + +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(UVCG_WIDTH_360P), + .wHeight = cpu_to_le16(UVCG_HEIGHT_360P), + .dwMinBitRate = cpu_to_le32(UVCG_MIN_BITRATE_360P), + .dwMaxBitRate = cpu_to_le32(UVCG_MAX_BITRATE_360P), + .dwMaxVideoFrameBufferSize = cpu_to_le32(UVCG_MAX_VIDEO_FB_SZ_360P), + .dwDefaultFrameInterval = cpu_to_le32(UVCG_DEFAULT_FRM_INTERV_360P), + .bFrameIntervalType = 3, + .dwFrameInterval[0] = cpu_to_le32(UVCG_FRM_INTERV_0_360P), + .dwFrameInterval[1] = cpu_to_le32(UVCG_FRM_INTERV_1_360P), + .dwFrameInterval[2] = cpu_to_le32(UVCG_FRM_INTERV_2_360P), +}; + +static u32 uvcg_frame_yuv_360p_dw_frame_interval[] = { + [0] = UVCG_FRM_INTERV_0_360P, + [1] = UVCG_FRM_INTERV_1_360P, + [2] = UVCG_FRM_INTERV_2_360P, +}; + +static const struct uvcg_frame uvcg_frame_yuv_360p = { + .fmt_type = UVCG_UNCOMPRESSED, + .frame = { + .b_length = UVC_DT_FRAME_UNCOMPRESSED_SIZE(3), + .b_descriptor_type = USB_DT_CS_INTERFACE, + .b_descriptor_subtype = UVC_VS_FRAME_UNCOMPRESSED, + .b_frame_index = 1, + .bm_capabilities = 0, + .w_width = UVCG_WIDTH_360P, + .w_height = UVCG_HEIGHT_360P, + .dw_min_bit_rate = UVCG_MIN_BITRATE_360P, + .dw_max_bit_rate = UVCG_MAX_BITRATE_360P, + .dw_max_video_frame_buffer_size = UVCG_MAX_VIDEO_FB_SZ_360P, + .dw_default_frame_interval = UVCG_DEFAULT_FRM_INTERV_360P, + .b_frame_interval_type = 3, + }, + .dw_frame_interval = uvcg_frame_yuv_360p_dw_frame_interval, +}; + +static struct uvcg_frame_ptr uvcg_frame_ptr_yuv_360p = { + .frm = (struct uvcg_frame *)&uvcg_frame_yuv_360p, +}; +#define UVCG_WIDTH_720P 1280 +#define UVCG_HEIGHT_720P 720 +#define UVCG_MIN_BITRATE_720P 29491200 +#define UVCG_MAX_BITRATE_720P 29491200 +#define UVCG_MAX_VIDEO_FB_SZ_720P 1843200 +#define UVCG_FRM_INTERV_0_720P 5000000 +#define UVCG_DEFAULT_FRM_INTERV_720P UVCG_FRM_INTERV_0_720P + +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(UVCG_WIDTH_720P), + .wHeight = cpu_to_le16(UVCG_HEIGHT_720P), + .dwMinBitRate = cpu_to_le32(UVCG_MIN_BITRATE_720P), + .dwMaxBitRate = cpu_to_le32(UVCG_MAX_BITRATE_720P), + .dwMaxVideoFrameBufferSize = cpu_to_le32(UVCG_MAX_VIDEO_FB_SZ_720P), + .dwDefaultFrameInterval = cpu_to_le32(UVCG_DEFAULT_FRM_INTERV_720P), + .bFrameIntervalType = 1, + .dwFrameInterval[0] = cpu_to_le32(UVCG_FRM_INTERV_0_720P), +}; + +static u32 uvcg_frame_yuv_720p_dw_frame_interval[] = { + [0] = UVCG_FRM_INTERV_0_720P, +}; + +static const struct uvcg_frame uvcg_frame_yuv_720p = { + .fmt_type = UVCG_UNCOMPRESSED, + .frame = { + .b_length = UVC_DT_FRAME_UNCOMPRESSED_SIZE(1), + .b_descriptor_type = USB_DT_CS_INTERFACE, + .b_descriptor_subtype = UVC_VS_FRAME_UNCOMPRESSED, + .b_frame_index = 2, + .bm_capabilities = 0, + .w_width = UVCG_WIDTH_720P, + .w_height = UVCG_HEIGHT_720P, + .dw_min_bit_rate = UVCG_MIN_BITRATE_720P, + .dw_max_bit_rate = UVCG_MAX_BITRATE_720P, + .dw_max_video_frame_buffer_size = UVCG_MAX_VIDEO_FB_SZ_720P, + .dw_default_frame_interval = UVCG_DEFAULT_FRM_INTERV_720P, + .b_frame_interval_type = 1, + }, + .dw_frame_interval = uvcg_frame_yuv_720p_dw_frame_interval, +}; + +static struct uvcg_frame_ptr uvcg_frame_ptr_yuv_720p = { + .frm = (struct uvcg_frame *)&uvcg_frame_yuv_720p, +}; + +static struct uvcg_mjpeg uvcg_format_mjpeg = { + .fmt = { + .type = UVCG_MJPEG, + /* add to .frames and fill .num_frames at runtime */ + .color_matching = (struct uvcg_color_matching *)&uvcg_color_matching, + }, + .desc = { + .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, + .bmInterlaceFlags = 0, + .bCopyProtect = 0, + }, +}; + +static struct uvcg_format_ptr uvcg_format_ptr_mjpeg = { + .fmt = &uvcg_format_mjpeg.fmt, +}; + +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(UVCG_WIDTH_360P), + .wHeight = cpu_to_le16(UVCG_HEIGHT_360P), + .dwMinBitRate = cpu_to_le32(UVCG_MIN_BITRATE_360P), + .dwMaxBitRate = cpu_to_le32(UVCG_MAX_BITRATE_360P), + .dwMaxVideoFrameBufferSize = cpu_to_le32(UVCG_MAX_VIDEO_FB_SZ_360P), + .dwDefaultFrameInterval = cpu_to_le32(UVCG_DEFAULT_FRM_INTERV_360P), + .bFrameIntervalType = 3, + .dwFrameInterval[0] = cpu_to_le32(UVCG_FRM_INTERV_0_360P), + .dwFrameInterval[1] = cpu_to_le32(UVCG_FRM_INTERV_1_360P), + .dwFrameInterval[2] = cpu_to_le32(UVCG_FRM_INTERV_2_360P), +}; + +static u32 uvcg_frame_mjpeg_360p_dw_frame_interval[] = { + [0] = UVCG_FRM_INTERV_0_360P, + [1] = UVCG_FRM_INTERV_1_360P, + [2] = UVCG_FRM_INTERV_2_360P, +}; + +static const struct uvcg_frame uvcg_frame_mjpeg_360p = { + .fmt_type = UVCG_MJPEG, + .frame = { + .b_length = UVC_DT_FRAME_MJPEG_SIZE(3), + .b_descriptor_type = USB_DT_CS_INTERFACE, + .b_descriptor_subtype = UVC_VS_FRAME_MJPEG, + .b_frame_index = 1, + .bm_capabilities = 0, + .w_width = UVCG_WIDTH_360P, + .w_height = UVCG_HEIGHT_360P, + .dw_min_bit_rate = UVCG_MIN_BITRATE_360P, + .dw_max_bit_rate = UVCG_MAX_BITRATE_360P, + .dw_max_video_frame_buffer_size = UVCG_MAX_VIDEO_FB_SZ_360P, + .dw_default_frame_interval = UVCG_DEFAULT_FRM_INTERV_360P, + .b_frame_interval_type = 3, + }, + .dw_frame_interval = uvcg_frame_mjpeg_360p_dw_frame_interval, +}; + +static struct uvcg_frame_ptr uvcg_frame_ptr_mjpeg_360p = { + .frm = (struct uvcg_frame *)&uvcg_frame_mjpeg_360p, +}; + +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(UVCG_WIDTH_720P), + .wHeight = cpu_to_le16(UVCG_HEIGHT_720P), + .dwMinBitRate = cpu_to_le32(UVCG_MIN_BITRATE_720P), + .dwMaxBitRate = cpu_to_le32(UVCG_MAX_BITRATE_720P), + .dwMaxVideoFrameBufferSize = cpu_to_le32(UVCG_MAX_VIDEO_FB_SZ_720P), + .dwDefaultFrameInterval = cpu_to_le32(UVCG_DEFAULT_FRM_INTERV_720P), + .bFrameIntervalType = 1, + .dwFrameInterval[0] = cpu_to_le32(UVCG_FRM_INTERV_0_720P), +}; + +static u32 uvcg_frame_mjpeg_720p_dw_frame_interval[] = { + [0] = UVCG_FRM_INTERV_0_720P, +}; + +static const struct uvcg_frame uvcg_frame_mjpeg_720p = { + .fmt_type = UVCG_MJPEG, + .frame = { + .b_length = UVC_DT_FRAME_MJPEG_SIZE(1), + .b_descriptor_type = USB_DT_CS_INTERFACE, + .b_descriptor_subtype = UVC_VS_FRAME_MJPEG, + .b_frame_index = 2, + .bm_capabilities = 0, + .w_width = UVCG_WIDTH_720P, + .w_height = UVCG_HEIGHT_720P, + .dw_min_bit_rate = UVCG_MIN_BITRATE_720P, + .dw_max_bit_rate = UVCG_MAX_BITRATE_720P, + .dw_max_video_frame_buffer_size = UVCG_MAX_VIDEO_FB_SZ_720P, + .dw_default_frame_interval = UVCG_DEFAULT_FRM_INTERV_720P, + .b_frame_interval_type = 1, + }, + .dw_frame_interval = uvcg_frame_mjpeg_720p_dw_frame_interval, +}; + +static struct uvcg_frame_ptr uvcg_frame_ptr_mjpeg_720p = { + .frm = (struct uvcg_frame *)&uvcg_frame_mjpeg_720p, +}; + +static struct uvcg_streaming_header uvcg_streaming_header = { +}; + +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 *) &uvcg_format_yuv.desc, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_360p, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_720p, + (const struct uvc_descriptor_header *) &uvcg_color_matching.desc, + (const struct uvc_descriptor_header *) &uvcg_format_mjpeg.desc, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_360p, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_720p, + (const struct uvc_descriptor_header *) &uvcg_color_matching.desc, + 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 *) &uvcg_format_yuv.desc, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_360p, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_720p, + (const struct uvc_descriptor_header *) &uvcg_color_matching.desc, + (const struct uvc_descriptor_header *) &uvcg_format_mjpeg.desc, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_360p, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_720p, + (const struct uvc_descriptor_header *) &uvcg_color_matching.desc, + 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 *) &uvcg_format_yuv.desc, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_360p, + (const struct uvc_descriptor_header *) &uvc_frame_yuv_720p, + (const struct uvc_descriptor_header *) &uvcg_color_matching.desc, + (const struct uvc_descriptor_header *) &uvcg_format_mjpeg.desc, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_360p, + (const struct uvc_descriptor_header *) &uvc_frame_mjpg_720p, + (const struct uvc_descriptor_header *) &uvcg_color_matching.desc, + NULL, +}; + +/* -------------------------------------------------------------------------- + * USB configuration + */ + +static int +webcam_config_bind(struct usb_configuration *c) +{ + int status = 0; + + f_uvc = usb_get_function(fi_uvc); + if (IS_ERR(f_uvc)) + return PTR_ERR(f_uvc); + + status = usb_add_function(c, f_uvc); + if (status < 0) + usb_put_function(f_uvc); + + return status; +} + +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 +webcam_unbind(struct usb_composite_dev *cdev) +{ + if (!IS_ERR_OR_NULL(f_uvc)) + usb_put_function(f_uvc); + if (!IS_ERR_OR_NULL(fi_uvc)) + usb_put_function_instance(fi_uvc); + return 0; +} + +static int +webcam_bind(struct usb_composite_dev *cdev) +{ + struct f_uvc_opts *uvc_opts; + int ret; + + fi_uvc = usb_get_function_instance("uvc"); + if (IS_ERR(fi_uvc)) + return PTR_ERR(fi_uvc); + + uvc_opts = container_of(fi_uvc, struct f_uvc_opts, func_inst); + + uvc_opts->streaming_interval = streaming_interval; + uvc_opts->streaming_maxpacket = streaming_maxpacket; + uvc_opts->streaming_maxburst = streaming_maxburst; + + uvc_opts->fs_control = uvc_fs_control_cls; + uvc_opts->ss_control = uvc_ss_control_cls; + uvc_opts->fs_streaming = uvc_fs_streaming_cls; + uvc_opts->hs_streaming = uvc_hs_streaming_cls; + uvc_opts->ss_streaming = uvc_ss_streaming_cls; + + INIT_LIST_HEAD(&uvcg_format_yuv.fmt.frames); + list_add_tail(&uvcg_frame_ptr_yuv_360p.entry, &uvcg_format_yuv.fmt.frames); + list_add_tail(&uvcg_frame_ptr_yuv_720p.entry, &uvcg_format_yuv.fmt.frames); + uvcg_format_yuv.fmt.num_frames = 2; + + INIT_LIST_HEAD(&uvcg_format_mjpeg.fmt.frames); + list_add_tail(&uvcg_frame_ptr_mjpeg_360p.entry, &uvcg_format_mjpeg.fmt.frames); + list_add_tail(&uvcg_frame_ptr_mjpeg_720p.entry, &uvcg_format_mjpeg.fmt.frames); + uvcg_format_mjpeg.fmt.num_frames = 2; + + INIT_LIST_HEAD(&uvcg_streaming_header.formats); + list_add_tail(&uvcg_format_ptr_yuv.entry, &uvcg_streaming_header.formats); + list_add_tail(&uvcg_format_ptr_mjpeg.entry, &uvcg_streaming_header.formats); + uvcg_streaming_header.num_fmt = 2; + + uvc_opts->header = &uvcg_streaming_header; + + /* 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: + usb_put_function_instance(fi_uvc); + return ret; +} + +/* -------------------------------------------------------------------------- + * Driver + */ + +static 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, +}; + +module_usb_composite_driver(webcam_driver); + +MODULE_AUTHOR("Laurent Pinchart"); +MODULE_DESCRIPTION("Webcam Video Gadget"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/gadget/legacy/zero.c b/drivers/usb/gadget/legacy/zero.c new file mode 100644 index 000000000..08a21bd0c --- /dev/null +++ b/drivers/usb/gadget/legacy/zero.c @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * zero.c -- Gadget Zero, for USB development + * + * Copyright (C) 2003-2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + */ + +/* + * 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 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 infrastructure 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 = GZERO_ISOC_INTERVAL, + .isoc_maxpacket = GZERO_ISOC_MAXPACKET, + .bulk_buflen = GZERO_BULK_BUFLEN, + .qlen = GZERO_QLEN, + .ss_bulk_qlen = GZERO_SS_BULK_QLEN, + .ss_iso_qlen = GZERO_SS_ISO_QLEN, +}; + +/*-------------------------------------------------------------------------*/ + +/* 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. + */ +static unsigned autoresume = DEFAULT_AUTORESUME; +module_param(autoresume, uint, S_IRUGO); +MODULE_PARM_DESC(autoresume, "zero, or seconds before remote wakeup"); + +/* Maximum Autoresume time */ +static unsigned max_autoresume; +module_param(max_autoresume, uint, S_IRUGO); +MODULE_PARM_DESC(max_autoresume, "maximum seconds before remote wakeup"); + +/* Interval between two remote wakeups */ +static unsigned autoresume_interval_ms; +module_param(autoresume_interval_ms, uint, S_IRUGO); +MODULE_PARM_DESC(autoresume_interval_ms, + "milliseconds to increase successive wakeup delays"); + +static unsigned autoresume_step_ms; +/*-------------------------------------------------------------------------*/ + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + /* .bcdUSB = DYNAMIC */ + .bDeviceClass = USB_CLASS_VENDOR_SPEC, + + .idVendor = cpu_to_le16(DRIVER_VENDOR_NUM), + .idProduct = cpu_to_le16(DRIVER_PRODUCT_NUM), + .bNumConfigurations = 2, +}; + +static const struct usb_descriptor_header *otg_desc[2]; + +/* 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 usb_function *func_lb; +static struct usb_function_instance *func_inst_lb; + +static struct usb_function *func_ss; +static struct usb_function_instance *func_inst_ss; + +/*-------------------------------------------------------------------------*/ + +static struct timer_list autoresume_timer; +static struct usb_composite_dev *autoresume_cdev; + +static void zero_autoresume(struct timer_list *unused) +{ + struct usb_composite_dev *cdev = autoresume_cdev; + struct usb_gadget *g = cdev->gadget; + int status; + + /* 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) + return; + + if (g->speed >= USB_SPEED_SUPER) { + if (loopdefault) + status = usb_func_wakeup(func_lb); + else + status = usb_func_wakeup(func_ss); + } else { + 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) { + if (max_autoresume && + (autoresume_step_ms > max_autoresume * 1000)) + autoresume_step_ms = autoresume * 1000; + + mod_timer(&autoresume_timer, jiffies + + msecs_to_jiffies(autoresume_step_ms)); + DBG(cdev, "suspend, wakeup in %d milliseconds\n", + autoresume_step_ms); + + autoresume_step_ms += autoresume_interval_ms; + } else + DBG(cdev, "%s\n", __func__); +} + +static void zero_resume(struct usb_composite_dev *cdev) +{ + DBG(cdev, "%s\n", __func__); + timer_delete(&autoresume_timer); +} + +/*-------------------------------------------------------------------------*/ + +static struct usb_configuration loopback_driver = { + .label = "loopback", + .bConfigurationValue = 2, + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, + /* .iConfiguration = DYNAMIC */ +}; + +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)"); + +module_param_named(qlen, gzero_options.qlen, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(qlen, "depth of loopback queue"); + +module_param_named(ss_bulk_qlen, gzero_options.ss_bulk_qlen, uint, + S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(bulk_qlen, "depth of sourcesink queue for bulk transfer"); + +module_param_named(ss_iso_qlen, gzero_options.ss_iso_qlen, uint, + S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(iso_qlen, "depth of sourcesink queue for iso transfer"); + +static int 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; + + autoresume_cdev = cdev; + timer_setup(&autoresume_timer, zero_autoresume, 0); + + 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; + ss_opts->bulk_qlen = gzero_options.ss_bulk_qlen; + ss_opts->iso_qlen = gzero_options.ss_iso_qlen; + + 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; + autoresume_step_ms = autoresume * 1000; + } + + /* support OTG systems */ + if (gadget_is_otg(cdev->gadget)) { + if (!otg_desc[0]) { + struct usb_descriptor_header *usb_desc; + + usb_desc = usb_otg_descriptor_alloc(cdev->gadget); + if (!usb_desc) { + status = -ENOMEM; + goto err_conf_flb; + } + usb_otg_descriptor_init(cdev->gadget, usb_desc); + otg_desc[0] = usb_desc; + otg_desc[1] = NULL; + } + 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_free_otg_desc; + + usb_ep_autoconfig_reset(cdev->gadget); + status = usb_add_function(&loopback_driver, func_lb); + if (status) + goto err_free_otg_desc; + + usb_ep_autoconfig_reset(cdev->gadget); + usb_composite_overwrite_options(cdev, &coverwrite); + + INFO(cdev, "%s, version: " DRIVER_VERSION "\n", longname); + + return 0; + +err_free_otg_desc: + kfree(otg_desc[0]); + otg_desc[0] = NULL; +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) +{ + timer_delete_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); + kfree(otg_desc[0]); + otg_desc[0] = NULL; + + return 0; +} + +static 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_usb_composite_driver(zero_driver); + +MODULE_AUTHOR("David Brownell"); +MODULE_DESCRIPTION("Gadget Zero, for USB development"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/u_f.c b/drivers/usb/gadget/u_f.c new file mode 100644 index 000000000..115d219c9 --- /dev/null +++ b/drivers/usb/gadget/u_f.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * u_f.c -- USB function utilities for Gadget stack + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#include +#include + +struct usb_request *alloc_ep_req(struct usb_ep *ep, size_t len) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (req) { + req->length = usb_endpoint_dir_out(ep->desc) ? + usb_ep_align(ep, len) : len; + req->buf = kmalloc(req->length, GFP_ATOMIC); + if (!req->buf) { + usb_ep_free_request(ep, req); + req = NULL; + } + } + return req; +} +EXPORT_SYMBOL_GPL(alloc_ep_req); diff --git a/drivers/usb/gadget/u_os_desc.h b/drivers/usb/gadget/u_os_desc.h new file mode 100644 index 000000000..f8b9f0faa --- /dev/null +++ b/drivers/usb/gadget/u_os_desc.h @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * u_os_desc.h + * + * Utility definitions for "OS Descriptors" support + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef __U_OS_DESC_H__ +#define __U_OS_DESC_H__ + +#include +#include + +#define USB_EXT_PROP_DW_SIZE 0 +#define USB_EXT_PROP_DW_PROPERTY_DATA_TYPE 4 +#define USB_EXT_PROP_W_PROPERTY_NAME_LENGTH 8 +#define USB_EXT_PROP_B_PROPERTY_NAME 10 +#define USB_EXT_PROP_DW_PROPERTY_DATA_LENGTH 10 +#define USB_EXT_PROP_B_PROPERTY_DATA 14 + +#define USB_EXT_PROP_RESERVED 0 +#define USB_EXT_PROP_UNICODE 1 +#define USB_EXT_PROP_UNICODE_ENV 2 +#define USB_EXT_PROP_BINARY 3 +#define USB_EXT_PROP_LE32 4 +#define USB_EXT_PROP_BE32 5 +#define USB_EXT_PROP_UNICODE_LINK 6 +#define USB_EXT_PROP_UNICODE_MULTI 7 + +static inline u8 *__usb_ext_prop_ptr(u8 *buf, size_t offset) +{ + return buf + offset; +} + +static inline u8 *usb_ext_prop_size_ptr(u8 *buf) +{ + return __usb_ext_prop_ptr(buf, USB_EXT_PROP_DW_SIZE); +} + +static inline u8 *usb_ext_prop_type_ptr(u8 *buf) +{ + return __usb_ext_prop_ptr(buf, USB_EXT_PROP_DW_PROPERTY_DATA_TYPE); +} + +static inline u8 *usb_ext_prop_name_len_ptr(u8 *buf) +{ + return __usb_ext_prop_ptr(buf, USB_EXT_PROP_W_PROPERTY_NAME_LENGTH); +} + +static inline u8 *usb_ext_prop_name_ptr(u8 *buf) +{ + return __usb_ext_prop_ptr(buf, USB_EXT_PROP_B_PROPERTY_NAME); +} + +static inline u8 *usb_ext_prop_data_len_ptr(u8 *buf, size_t off) +{ + return __usb_ext_prop_ptr(buf, + USB_EXT_PROP_DW_PROPERTY_DATA_LENGTH + off); +} + +static inline u8 *usb_ext_prop_data_ptr(u8 *buf, size_t off) +{ + return __usb_ext_prop_ptr(buf, USB_EXT_PROP_B_PROPERTY_DATA + off); +} + +static inline void usb_ext_prop_put_size(u8 *buf, int dw_size) +{ + put_unaligned_le32(dw_size, usb_ext_prop_size_ptr(buf)); +} + +static inline void usb_ext_prop_put_type(u8 *buf, int type) +{ + put_unaligned_le32(type, usb_ext_prop_type_ptr(buf)); +} + +static inline int usb_ext_prop_put_name(u8 *buf, const char *name, int pnl) +{ + int result; + + put_unaligned_le16(pnl, usb_ext_prop_name_len_ptr(buf)); + result = utf8s_to_utf16s(name, strlen(name), UTF16_LITTLE_ENDIAN, + (wchar_t *) usb_ext_prop_name_ptr(buf), pnl - 2); + if (result < 0) + return result; + + put_unaligned_le16(0, &buf[USB_EXT_PROP_B_PROPERTY_NAME + pnl - 2]); + + return pnl; +} + +static inline void usb_ext_prop_put_binary(u8 *buf, int pnl, const u8 *data, + int data_len) +{ + put_unaligned_le32(data_len, usb_ext_prop_data_len_ptr(buf, pnl)); + memcpy(usb_ext_prop_data_ptr(buf, pnl), data, data_len); +} + +static inline int usb_ext_prop_put_unicode(u8 *buf, int pnl, const char *string, + int data_len) +{ + int result; + put_unaligned_le32(data_len, usb_ext_prop_data_len_ptr(buf, pnl)); + result = utf8s_to_utf16s(string, data_len >> 1, UTF16_LITTLE_ENDIAN, + (wchar_t *) usb_ext_prop_data_ptr(buf, pnl), + data_len - 2); + if (result < 0) + return result; + + put_unaligned_le16(0, + &buf[USB_EXT_PROP_B_PROPERTY_DATA + pnl + data_len - 2]); + + return data_len; +} + +#endif /* __U_OS_DESC_H__ */ diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig new file mode 100644 index 000000000..7ddbc2606 --- /dev/null +++ b/drivers/usb/gadget/udc/Kconfig @@ -0,0 +1,453 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# 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). +# + +# +# 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 + depends on OF + 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 || COMPILE_TEST + depends on I2C + 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 ARCH_AT91 + help + USBA is the integrated high-speed USB Device controller on some + AT91SAM9 and AT91CAP9 processors from Atmel. + + The fifo_mode parameter is used to select endpoint allocation mode. + fifo_mode = 0 is used to let the driver autoconfigure the endpoints. + In this case, for ep1 2 banks are allocated if it works in isochronous + mode and only 1 bank otherwise. For the rest of the endpoints + only 1 bank is allocated. + + fifo_mode = 1 is a generic maximum fifo size (1024 bytes) configuration + allowing the usage of ep1 - ep6 + + fifo_mode = 2 is a generic performance maximum fifo size (1024 bytes) + configuration allowing the usage of ep1 - ep3 + + fifo_mode = 3 is a balanced performance configuration allowing the + the usage of ep1 - ep8 + +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 || COMPILE_TEST + 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_GR_UDC + tristate "Aeroflex Gaisler GRUSBDC USB Peripheral Controller Driver" + depends on HAS_DMA + help + Select this to support Aeroflex Gaisler GRUSBDC cores from the GRLIB + VHDL IP core library. + +config USB_OMAP + tristate "OMAP USB Device Controller" + depends on ARCH_OMAP1 + 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 + depends on HAS_IOMEM + 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" + depends on HAS_DMA + 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_RZV2M_USB3DRD + tristate 'Renesas USB3.1 DRD controller' + depends on ARCH_R9A09G011 || COMPILE_TEST + help + Renesas USB3.1 DRD controller is a USB DRD controller + that supports both host and device switching. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "rzv2m_usb3drd". + +config USB_RENESAS_USB3 + tristate 'Renesas USB3.0 Peripheral controller' + depends on ARCH_RENESAS || COMPILE_TEST + depends on USB_RZV2M_USB3DRD || !USB_RZV2M_USB3DRD + depends on EXTCON + select USB_ROLE_SWITCH + help + Renesas USB3.0 Peripheral controller is a USB peripheral controller + that supports super, high, and full speed USB 3.0 data transfers. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "renesas_usb3" and force all + gadget drivers to also be dynamically linked. + +config USB_RENESAS_USBF + tristate 'Renesas USB Function controller' + depends on ARCH_RENESAS || COMPILE_TEST + help + Renesas USB Function controller is a USB peripheral controller + available on RZ/N1 Renesas SoCs. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "renesas_usbf" and force all + gadget drivers to also be dynamically linked. + +config USB_PXA27X + tristate "PXA 27x" + depends on HAS_IOMEM + 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_SNP_CORE + depends on (USB_AMD5536UDC || USB_SNP_UDC_PLAT) + depends on HAS_DMA + tristate + help + This enables core driver support for Synopsys USB 2.0 Device + controller. + + This will be enabled when PCI or Platform driver for this UDC is + selected. Currently, this will be enabled by USB_SNP_UDC_PLAT or + USB_AMD5536UDC options. + + This IP is different to the High Speed OTG IP that can be enabled + by selecting USB_DWC2 or USB_DWC3 options. + +config USB_SNP_UDC_PLAT + tristate "Synopsys USB 2.0 Device controller" + depends on USB_GADGET && OF && HAS_DMA + depends on EXTCON || EXTCON=n + select USB_SNP_CORE + default ARCH_BCM_IPROC + help + This adds Platform Device support for Synopsys Designware core + AHB subsystem USB2.0 Device Controller (UDC). + + This driver works with UDCs integrated into Broadcom's Northstar2 + and Cygnus SoCs. + + If unsure, say N. +# +# Controllers available in both integrated and discrete versions +# + +config USB_M66592 + tristate "Renesas M66592 USB Peripheral Controller" + depends on HAS_IOMEM + 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. + +source "drivers/usb/gadget/udc/bdc/Kconfig" + +# +# Controllers available only in discrete form (and all PCI controllers) +# + +config USB_AMD5536UDC + tristate "AMD5536 UDC" + depends on USB_PCI && HAS_DMA + select USB_SNP_CORE + 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. + + This UDC is based on Synopsys USB device controller IP and selects + CONFIG_USB_SNP_CORE option to build the core driver. + + 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) + depends on !64BIT || BROKEN + 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_NET2280 + tristate "NetChip NET228x / PLX USB3x8x" + depends on USB_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. + + PLX 2380 is a PCIe version of the PLX 2380. + + PLX 3380 / 3382 is a PCIe based USB peripheral controller which + supports full, high speed USB 2.0 and super speed USB 3.0 + data transfers. + + It has eight 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 USB_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 QUARK X1000/EG20T PCH/LAPIS Semiconductor IOH(ML7213/ML7831) UDC" + depends on USB_PCI + 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. + + This driver can be used with Intel's Quark X1000 SOC platform + +config USB_GADGET_XILINX + tristate "Xilinx USB Driver" + depends on HAS_DMA + depends on OF + help + USB peripheral controller driver for Xilinx USB2 device. + Xilinx USB2 device is a soft IP which supports both full + and high speed USB 2.0 data transfers. It has seven configurable + endpoints(bulk or interrupt or isochronous), as well as + endpoint zero(for control transfers). + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "udc-xilinx" and force all + gadget drivers to also be dynamically linked. + +config USB_MAX3420_UDC + tristate "MAX3420 (USB-over-SPI) support" + depends on SPI + help + The Maxim MAX3420 chip supports USB2.0 full-speed peripheral mode. + The MAX3420 is run by SPI interface, and hence the dependency. + + To compile this driver as a module, choose M here: the module will + be called max3420_udc + +config USB_TEGRA_XUDC + tristate "NVIDIA Tegra Superspeed USB 3.0 Device Controller" + depends on ARCH_TEGRA || COMPILE_TEST + depends on PHY_TEGRA_XUSB + help + Enables NVIDIA Tegra USB 3.0 device mode controller driver. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "tegra_xudc" and force all + gadget drivers to also be dynamically linked. + +config USB_ASPEED_UDC + tristate "Aspeed UDC driver support" + depends on ARCH_ASPEED || COMPILE_TEST + depends on USB_LIBCOMPOSITE + help + Enables Aspeed USB2.0 Device Controller driver for AST260x + family SoCs. The controller supports 1 control endpoint and + 4 programmable endpoints. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "aspeed_udc" and force all + gadget drivers to also be dynamically linked. + +source "drivers/usb/gadget/udc/aspeed-vhub/Kconfig" + +source "drivers/usb/gadget/udc/cdns2/Kconfig" + +# +# 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 controller; the gadget side is the device. 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 diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile new file mode 100644 index 000000000..1b9b1a4f9 --- /dev/null +++ b/drivers/usb/gadget/udc/Makefile @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-2.0 +# define_trace.h needs to know how to find our header +CFLAGS_trace.o := -I$(src) + +udc-core-y := core.o trace.o + +# +# USB peripheral controller drivers +# +obj-$(CONFIG_USB_GADGET) += udc-core.o +obj-$(CONFIG_USB_DUMMY_HCD) += dummy_hcd.o +obj-$(CONFIG_USB_NET2280) += net2280.o +obj-$(CONFIG_USB_SNP_CORE) += snps_udc_core.o +obj-$(CONFIG_USB_AMD5536UDC) += amd5536udc_pci.o +obj-$(CONFIG_USB_PXA25X) += pxa25x_udc.o +obj-$(CONFIG_USB_PXA27X) += pxa27x_udc.o +obj-$(CONFIG_USB_GOKU) += goku_udc.o +obj-$(CONFIG_USB_OMAP) += omap_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 +obj-$(CONFIG_USB_TEGRA_XUDC) += tegra-xudc.o +obj-$(CONFIG_USB_M66592) += m66592-udc.o +obj-$(CONFIG_USB_R8A66597) += r8a66597-udc.o +obj-$(CONFIG_USB_RENESAS_USB3) += renesas_usb3.o +obj-$(CONFIG_USB_RZV2M_USB3DRD) += rzv2m_usb3drd.o +obj-$(CONFIG_USB_RENESAS_USBF) += renesas_usbf.o +obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o +obj-$(CONFIG_USB_LPC32XX) += lpc32xx_udc.o +obj-$(CONFIG_USB_EG20T) += pch_udc.o +obj-$(CONFIG_USB_GR_UDC) += gr_udc.o +obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o +obj-$(CONFIG_USB_SNP_UDC_PLAT) += snps_udc_plat.o +obj-$(CONFIG_USB_ASPEED_VHUB) += aspeed-vhub/ +obj-$(CONFIG_USB_ASPEED_UDC) += aspeed_udc.o +obj-$(CONFIG_USB_BDC_UDC) += bdc/ +obj-$(CONFIG_USB_MAX3420_UDC) += max3420_udc.o +obj-$(CONFIG_USB_CDNS2_UDC) += cdns2/ diff --git a/drivers/usb/gadget/udc/amd5536udc.h b/drivers/usb/gadget/udc/amd5536udc.h new file mode 100644 index 000000000..055436016 --- /dev/null +++ b/drivers/usb/gadget/udc/amd5536udc.h @@ -0,0 +1,661 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * amd5536.h -- header for AMD 5536 UDC high/full speed USB device controller + * + * Copyright (C) 2007 AMD (https://www.amd.com) + * Author: Thomas Dahlmann + */ + +#ifndef AMD5536UDC_H +#define AMD5536UDC_H + +/* debug control */ +/* #define UDC_VERBOSE */ + +#include +#include +#include + +/* 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 + +/* Broadcom chip rev. */ +#define UDC_BCM_REV 10 + +/* + * 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_SRX_FLUSH 14 +#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; + + /* reserved */ + 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 stall_ep0in : 1, + waiting_zlp_ack_ep0in : 1, + set_cfg_not_acked : 1, + data_ep_enabled : 1, + data_ep_queued : 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 dma_pool *data_requests; + struct dma_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; + + /* for platform device and extcon support */ + struct device *dev; + struct phy *udc_phy; + struct extcon_dev *edev; + struct extcon_specific_cable_nb extcon_nb; + struct notifier_block nb; + struct delayed_work drd_work; + u32 conn_type; +}; + +#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; +}; + +/* Function declarations */ +int udc_enable_dev_setup_interrupts(struct udc *dev); +int udc_mask_unused_interrupts(struct udc *dev); +irqreturn_t udc_irq(int irq, void *pdev); +void gadget_release(struct device *pdev); +void empty_req_queue(struct udc_ep *ep); +void udc_basic_init(struct udc *dev); +void free_dma_pools(struct udc *dev); +int init_dma_pools(struct udc *dev); +void udc_remove(struct udc *dev); +int udc_probe(struct udc *dev); + +/* 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; +/* full speed only mode */ +static bool use_fullspeed; + +/* 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"); +/* + *--------------------------------------------------------------------------- + * 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->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/udc/amd5536udc_pci.c b/drivers/usb/gadget/udc/amd5536udc_pci.c new file mode 100644 index 000000000..cd767b1e3 --- /dev/null +++ b/drivers/usb/gadget/udc/amd5536udc_pci.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * amd5536udc_pci.c -- AMD 5536 UDC high/full speed USB device controller + * + * Copyright (C) 2005-2007 AMD (https://www.amd.com) + * Author: Thomas Dahlmann + */ + +/* + * 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. + * + * This file does pci device registration, and the core driver implementation + * is done in amd5536udc.c + * + * The driver is split so as to use the core UDC driver which is based on + * Synopsys device controller IP (different than HS OTG IP) in UDCs + * integrated to SoC platforms. + * + */ + +/* Driver strings */ +#define UDC_MOD_DESCRIPTION "AMD 5536 UDC - USB Device Controller" + +/* system */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* udc specific */ +#include "amd5536udc.h" + +/* pointer to device object */ +static struct udc *udc; + +/* description */ +static const char name[] = "amd5536udc-pci"; + +/* 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 */ + if (WARN_ON(dev->driver)) + return; + + /* dma pool cleanup */ + free_dma_pools(dev); + + /* reset controller */ + writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg); + free_irq(pdev->irq, dev); + iounmap(dev->virt_addr); + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + pci_disable_device(pdev); + + udc_remove(dev); +} + +/* 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_obj(struct udc); + if (!dev) + return -ENOMEM; + + /* pci setup */ + if (pci_enable_device(pdev) < 0) { + retval = -ENODEV; + goto err_pcidev; + } + + /* 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"); + retval = -EBUSY; + goto err_memreg; + } + + dev->virt_addr = ioremap(resource, len); + if (!dev->virt_addr) { + dev_dbg(&pdev->dev, "start address cannot be mapped\n"); + retval = -EFAULT; + goto err_ioremap; + } + + if (!pdev->irq) { + dev_err(&pdev->dev, "irq not set\n"); + retval = -ENODEV; + goto err_irq; + } + + 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); + retval = -EBUSY; + goto err_irq; + } + + pci_set_drvdata(pdev, dev); + + /* chip revision for Hs AMD5536 */ + dev->chiprev = pdev->revision; + + pci_set_master(pdev); + pci_try_set_mwi(pdev); + + dev->phys_addr = resource; + dev->irq = pdev->irq; + dev->pdev = pdev; + dev->dev = &pdev->dev; + + /* init dma pools */ + if (use_dma) { + retval = init_dma_pools(dev); + if (retval != 0) + goto err_dma; + } + + /* general probing */ + if (udc_probe(dev)) { + retval = -ENODEV; + goto err_probe; + } + + udc = dev; + + return 0; + +err_probe: + if (use_dma) + free_dma_pools(dev); +err_dma: + free_irq(pdev->irq, dev); +err_irq: + iounmap(dev->virt_addr); +err_ioremap: + release_mem_region(resource, len); +err_memreg: + pci_disable_device(pdev); +err_pcidev: + kfree(dev); + return retval; +} + +/* PCI device parameters */ +static const struct pci_device_id pci_id[] = { + { + PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x2096), + .class = PCI_CLASS_SERIAL_USB_DEVICE, + .class_mask = 0xffffffff, + }, + {}, +}; +MODULE_DEVICE_TABLE(pci, pci_id); + +/* PCI functions */ +static struct pci_driver udc_pci_driver = { + .name = 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/udc/aspeed-vhub/Kconfig b/drivers/usb/gadget/udc/aspeed-vhub/Kconfig new file mode 100644 index 000000000..605500b19 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/Kconfig @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0+ +config USB_ASPEED_VHUB + tristate "Aspeed vHub UDC driver" + depends on ARCH_ASPEED || COMPILE_TEST + depends on USB_LIBCOMPOSITE + help + USB peripheral controller for the Aspeed AST2400, AST2500 and + AST2600 family SoCs supporting the "vHub" functionality and USB2.0 diff --git a/drivers/usb/gadget/udc/aspeed-vhub/Makefile b/drivers/usb/gadget/udc/aspeed-vhub/Makefile new file mode 100644 index 000000000..9f3add605 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0+ +obj-$(CONFIG_USB_ASPEED_VHUB) += aspeed-vhub.o +aspeed-vhub-y := core.o ep0.o epn.o dev.o hub.o + diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c new file mode 100644 index 000000000..4a8b9ff83 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c @@ -0,0 +1,471 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget + * + * core.c - Top level support + * + * Copyright 2017 IBM Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vhub.h" + +void ast_vhub_done(struct ast_vhub_ep *ep, struct ast_vhub_req *req, + int status) +{ + bool internal = req->internal; + struct ast_vhub *vhub = ep->vhub; + + EPVDBG(ep, "completing request @%p, status %d\n", req, status); + + list_del_init(&req->queue); + + if ((req->req.status == -EINPROGRESS) || (status == -EOVERFLOW)) + req->req.status = status; + + if (req->req.dma) { + if (!WARN_ON(!ep->dev)) + usb_gadget_unmap_request_by_dev(&vhub->pdev->dev, + &req->req, ep->epn.is_in); + req->req.dma = 0; + } + + /* + * If this isn't an internal EP0 request, call the core + * to call the gadget completion. + */ + if (!internal) { + spin_unlock(&ep->vhub->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&ep->vhub->lock); + } +} + +void ast_vhub_nuke(struct ast_vhub_ep *ep, int status) +{ + struct ast_vhub_req *req; + int count = 0; + + /* Beware, lock will be dropped & req-acquired by done() */ + while (!list_empty(&ep->queue)) { + req = list_first_entry(&ep->queue, struct ast_vhub_req, queue); + ast_vhub_done(ep, req, status); + count++; + } + if (count) + EPDBG(ep, "Nuked %d request(s)\n", count); +} + +struct usb_request *ast_vhub_alloc_request(struct usb_ep *u_ep, + gfp_t gfp_flags) +{ + struct ast_vhub_req *req; + + req = kzalloc_obj(*req, gfp_flags); + if (!req) + return NULL; + return &req->req; +} + +void ast_vhub_free_request(struct usb_ep *u_ep, struct usb_request *u_req) +{ + struct ast_vhub_req *req = to_ast_req(u_req); + + kfree(req); +} + +static irqreturn_t ast_vhub_irq(int irq, void *data) +{ + struct ast_vhub *vhub = data; + irqreturn_t iret = IRQ_NONE; + u32 i, istat; + + /* Stale interrupt while tearing down */ + if (!vhub->ep0_bufs) + return IRQ_NONE; + + spin_lock(&vhub->lock); + + /* Read and ACK interrupts */ + istat = readl(vhub->regs + AST_VHUB_ISR); + if (!istat) + goto bail; + writel(istat, vhub->regs + AST_VHUB_ISR); + iret = IRQ_HANDLED; + + UDCVDBG(vhub, "irq status=%08x, ep_acks=%08x ep_nacks=%08x\n", + istat, + readl(vhub->regs + AST_VHUB_EP_ACK_ISR), + readl(vhub->regs + AST_VHUB_EP_NACK_ISR)); + + /* Handle generic EPs first */ + if (istat & VHUB_IRQ_EP_POOL_ACK_STALL) { + u32 ep_acks = readl(vhub->regs + AST_VHUB_EP_ACK_ISR); + writel(ep_acks, vhub->regs + AST_VHUB_EP_ACK_ISR); + + for (i = 0; ep_acks && i < vhub->max_epns; i++) { + u32 mask = VHUB_EP_IRQ(i); + if (ep_acks & mask) { + ast_vhub_epn_ack_irq(&vhub->epns[i]); + ep_acks &= ~mask; + } + } + } + + /* Handle device interrupts */ + if (istat & vhub->port_irq_mask) { + for (i = 0; i < vhub->max_ports; i++) { + if (istat & VHUB_DEV_IRQ(i)) + ast_vhub_dev_irq(&vhub->ports[i].dev); + } + } + + /* Handle top-level vHub EP0 interrupts */ + if (istat & (VHUB_IRQ_HUB_EP0_OUT_ACK_STALL | + VHUB_IRQ_HUB_EP0_IN_ACK_STALL | + VHUB_IRQ_HUB_EP0_SETUP)) { + if (istat & VHUB_IRQ_HUB_EP0_IN_ACK_STALL) + ast_vhub_ep0_handle_ack(&vhub->ep0, true); + if (istat & VHUB_IRQ_HUB_EP0_OUT_ACK_STALL) + ast_vhub_ep0_handle_ack(&vhub->ep0, false); + if (istat & VHUB_IRQ_HUB_EP0_SETUP) + ast_vhub_ep0_handle_setup(&vhub->ep0); + } + + /* Various top level bus events */ + if (istat & (VHUB_IRQ_BUS_RESUME | + VHUB_IRQ_BUS_SUSPEND | + VHUB_IRQ_BUS_RESET)) { + if (istat & VHUB_IRQ_BUS_RESUME) + ast_vhub_hub_resume(vhub); + if (istat & VHUB_IRQ_BUS_SUSPEND) + ast_vhub_hub_suspend(vhub); + if (istat & VHUB_IRQ_BUS_RESET) + ast_vhub_hub_reset(vhub); + } + + bail: + spin_unlock(&vhub->lock); + return iret; +} + +void ast_vhub_init_hw(struct ast_vhub *vhub) +{ + u32 ctrl, port_mask, epn_mask; + + UDCDBG(vhub,"(Re)Starting HW ...\n"); + + /* Enable PHY */ + ctrl = VHUB_CTRL_PHY_CLK | + VHUB_CTRL_PHY_RESET_DIS; + + /* + * We do *NOT* set the VHUB_CTRL_CLK_STOP_SUSPEND bit + * to stop the logic clock during suspend because + * it causes the registers to become inaccessible and + * we haven't yet figured out a good wayt to bring the + * controller back into life to issue a wakeup. + */ + + /* + * Set some ISO & split control bits according to Aspeed + * recommendation + * + * VHUB_CTRL_ISO_RSP_CTRL: When set tells the HW to respond + * with 0 bytes data packet to ISO IN endpoints when no data + * is available. + * + * VHUB_CTRL_SPLIT_IN: This makes a SOF complete a split IN + * transaction. + */ + ctrl |= VHUB_CTRL_ISO_RSP_CTRL | VHUB_CTRL_SPLIT_IN; + writel(ctrl, vhub->regs + AST_VHUB_CTRL); + udelay(1); + + /* Set descriptor ring size */ + if (AST_VHUB_DESCS_COUNT == 256) { + ctrl |= VHUB_CTRL_LONG_DESC; + writel(ctrl, vhub->regs + AST_VHUB_CTRL); + } else { + BUILD_BUG_ON(AST_VHUB_DESCS_COUNT != 32); + } + + /* Reset all devices */ + port_mask = GENMASK(vhub->max_ports, 1); + writel(VHUB_SW_RESET_ROOT_HUB | + VHUB_SW_RESET_DMA_CONTROLLER | + VHUB_SW_RESET_EP_POOL | + port_mask, vhub->regs + AST_VHUB_SW_RESET); + udelay(1); + writel(0, vhub->regs + AST_VHUB_SW_RESET); + + /* Disable and cleanup EP ACK/NACK interrupts */ + epn_mask = GENMASK(vhub->max_epns - 1, 0); + writel(0, vhub->regs + AST_VHUB_EP_ACK_IER); + writel(0, vhub->regs + AST_VHUB_EP_NACK_IER); + writel(epn_mask, vhub->regs + AST_VHUB_EP_ACK_ISR); + writel(epn_mask, vhub->regs + AST_VHUB_EP_NACK_ISR); + + /* Default settings for EP0, enable HW hub EP1 */ + writel(0, vhub->regs + AST_VHUB_EP0_CTRL); + writel(VHUB_EP1_CTRL_RESET_TOGGLE | + VHUB_EP1_CTRL_ENABLE, + vhub->regs + AST_VHUB_EP1_CTRL); + writel(0, vhub->regs + AST_VHUB_EP1_STS_CHG); + + /* Configure EP0 DMA buffer */ + writel(vhub->ep0.buf_dma, vhub->regs + AST_VHUB_EP0_DATA); + + /* Clear address */ + writel(0, vhub->regs + AST_VHUB_CONF); + + /* Pullup hub (activate on host) */ + if (vhub->force_usb1) + ctrl |= VHUB_CTRL_FULL_SPEED_ONLY; + + ctrl |= VHUB_CTRL_UPSTREAM_CONNECT; + writel(ctrl, vhub->regs + AST_VHUB_CTRL); + + /* Enable some interrupts */ + writel(VHUB_IRQ_HUB_EP0_IN_ACK_STALL | + VHUB_IRQ_HUB_EP0_OUT_ACK_STALL | + VHUB_IRQ_HUB_EP0_SETUP | + VHUB_IRQ_EP_POOL_ACK_STALL | + VHUB_IRQ_BUS_RESUME | + VHUB_IRQ_BUS_SUSPEND | + VHUB_IRQ_BUS_RESET, + vhub->regs + AST_VHUB_IER); +} + +static void ast_vhub_remove(struct platform_device *pdev) +{ + struct ast_vhub *vhub = platform_get_drvdata(pdev); + unsigned long flags; + int i; + + if (!vhub || !vhub->regs) + return; + + /* Remove devices */ + for (i = 0; i < vhub->max_ports; i++) + ast_vhub_del_dev(&vhub->ports[i].dev); + + spin_lock_irqsave(&vhub->lock, flags); + + /* Mask & ack all interrupts */ + writel(0, vhub->regs + AST_VHUB_IER); + writel(VHUB_IRQ_ACK_ALL, vhub->regs + AST_VHUB_ISR); + + /* Pull device, leave PHY enabled */ + writel(VHUB_CTRL_PHY_CLK | + VHUB_CTRL_PHY_RESET_DIS, + vhub->regs + AST_VHUB_CTRL); + + if (vhub->clk) + clk_disable_unprepare(vhub->clk); + + reset_control_assert(vhub->rst); + + spin_unlock_irqrestore(&vhub->lock, flags); + + if (vhub->ep0_bufs) + dma_free_coherent(&pdev->dev, + AST_VHUB_EP0_MAX_PACKET * + (vhub->max_ports + 1), + vhub->ep0_bufs, + vhub->ep0_bufs_dma); + vhub->ep0_bufs = NULL; +} + +static int ast_vhub_probe(struct platform_device *pdev) +{ + enum usb_device_speed max_speed; + const u64 *dma_mask_ptr; + struct ast_vhub *vhub; + struct resource *res; + int i, rc = 0; + const struct device_node *np = pdev->dev.of_node; + + vhub = devm_kzalloc(&pdev->dev, sizeof(*vhub), GFP_KERNEL); + if (!vhub) + return -ENOMEM; + + rc = of_property_read_u32(np, "aspeed,vhub-downstream-ports", + &vhub->max_ports); + if (rc < 0) + vhub->max_ports = AST_VHUB_NUM_PORTS; + + vhub->ports = devm_kcalloc(&pdev->dev, vhub->max_ports, + sizeof(*vhub->ports), GFP_KERNEL); + if (!vhub->ports) + return -ENOMEM; + + rc = of_property_read_u32(np, "aspeed,vhub-generic-endpoints", + &vhub->max_epns); + if (rc < 0) + vhub->max_epns = AST_VHUB_NUM_GEN_EPs; + + vhub->epns = devm_kcalloc(&pdev->dev, vhub->max_epns, + sizeof(*vhub->epns), GFP_KERNEL); + if (!vhub->epns) + return -ENOMEM; + + spin_lock_init(&vhub->lock); + vhub->pdev = pdev; + vhub->port_irq_mask = GENMASK(VHUB_IRQ_DEV1_BIT + vhub->max_ports - 1, + VHUB_IRQ_DEV1_BIT); + + vhub->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(vhub->regs)) { + dev_err(&pdev->dev, "Failed to map resources\n"); + return PTR_ERR(vhub->regs); + } + UDCDBG(vhub, "vHub@%pR mapped @%p\n", res, vhub->regs); + + platform_set_drvdata(pdev, vhub); + + vhub->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(vhub->clk)) { + rc = PTR_ERR(vhub->clk); + goto err; + } + rc = clk_prepare_enable(vhub->clk); + if (rc) { + dev_err(&pdev->dev, "Error couldn't enable clock (%d)\n", rc); + goto err; + } + + vhub->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL); + if (IS_ERR(vhub->rst)) { + rc = PTR_ERR(vhub->rst); + goto err; + } + + rc = reset_control_deassert(vhub->rst); + if (rc) + goto err; + + /* Check if we need to limit the HW to USB1 */ + max_speed = usb_get_maximum_speed(&pdev->dev); + if (max_speed != USB_SPEED_UNKNOWN && max_speed < USB_SPEED_HIGH) + vhub->force_usb1 = true; + + /* Mask & ack all interrupts before installing the handler */ + writel(0, vhub->regs + AST_VHUB_IER); + writel(VHUB_IRQ_ACK_ALL, vhub->regs + AST_VHUB_ISR); + + /* Find interrupt and install handler */ + vhub->irq = platform_get_irq(pdev, 0); + if (vhub->irq < 0) { + rc = vhub->irq; + goto err; + } + rc = devm_request_irq(&pdev->dev, vhub->irq, ast_vhub_irq, 0, + KBUILD_MODNAME, vhub); + if (rc) { + dev_err(&pdev->dev, "Failed to request interrupt\n"); + goto err; + } + + dma_mask_ptr = (u64 *)of_device_get_match_data(&pdev->dev); + if (dma_mask_ptr) { + rc = dma_coerce_mask_and_coherent(&pdev->dev, *dma_mask_ptr); + if (rc) + goto err; + } + /* + * Allocate DMA buffers for all EP0s in one chunk, + * one per port and one for the vHub itself + */ + vhub->ep0_bufs = dma_alloc_coherent(&pdev->dev, + AST_VHUB_EP0_MAX_PACKET * + (vhub->max_ports + 1), + &vhub->ep0_bufs_dma, GFP_KERNEL); + if (!vhub->ep0_bufs) { + dev_err(&pdev->dev, "Failed to allocate EP0 DMA buffers\n"); + rc = -ENOMEM; + goto err; + } + UDCVDBG(vhub, "EP0 DMA buffers @%p (DMA 0x%08x)\n", + vhub->ep0_bufs, (u32)vhub->ep0_bufs_dma); + + /* Init vHub EP0 */ + ast_vhub_init_ep0(vhub, &vhub->ep0, NULL); + + /* Init devices */ + for (i = 0; i < vhub->max_ports && rc == 0; i++) + rc = ast_vhub_init_dev(vhub, i); + if (rc) + goto err; + + /* Init hub emulation */ + rc = ast_vhub_init_hub(vhub); + if (rc) + goto err; + + /* Initialize HW */ + ast_vhub_init_hw(vhub); + + dev_info(&pdev->dev, "Initialized virtual hub in USB%d mode\n", + vhub->force_usb1 ? 1 : 2); + + return 0; + err: + ast_vhub_remove(pdev); + return rc; +} + +static const u64 dma_mask_32 = DMA_BIT_MASK(32); +static const u64 dma_mask_64 = DMA_BIT_MASK(64); + +static const struct of_device_id ast_vhub_dt_ids[] = { + { + .compatible = "aspeed,ast2400-usb-vhub", + .data = &dma_mask_32, + }, + { + .compatible = "aspeed,ast2500-usb-vhub", + .data = &dma_mask_32, + }, + { + .compatible = "aspeed,ast2600-usb-vhub", + .data = &dma_mask_32, + }, + { + .compatible = "aspeed,ast2700-usb-vhub", + .data = &dma_mask_64, + }, + { } +}; +MODULE_DEVICE_TABLE(of, ast_vhub_dt_ids); + +static struct platform_driver ast_vhub_driver = { + .probe = ast_vhub_probe, + .remove = ast_vhub_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ast_vhub_dt_ids, + }, +}; +module_platform_driver(ast_vhub_driver); + +MODULE_DESCRIPTION("Aspeed vHub udc driver"); +MODULE_AUTHOR("Benjamin Herrenschmidt "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c new file mode 100644 index 000000000..8b9449d16 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c @@ -0,0 +1,612 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget + * + * dev.c - Individual device/gadget management (ie, a port = a gadget) + * + * Copyright 2017 IBM Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vhub.h" + +void ast_vhub_dev_irq(struct ast_vhub_dev *d) +{ + u32 istat = readl(d->regs + AST_VHUB_DEV_ISR); + + writel(istat, d->regs + AST_VHUB_DEV_ISR); + + if (istat & VHUV_DEV_IRQ_EP0_IN_ACK_STALL) + ast_vhub_ep0_handle_ack(&d->ep0, true); + if (istat & VHUV_DEV_IRQ_EP0_OUT_ACK_STALL) + ast_vhub_ep0_handle_ack(&d->ep0, false); + if (istat & VHUV_DEV_IRQ_EP0_SETUP) + ast_vhub_ep0_handle_setup(&d->ep0); +} + +static void ast_vhub_dev_enable(struct ast_vhub_dev *d) +{ + u32 reg, hmsk, i; + + if (d->enabled) + return; + + /* Cleanup EP0 state */ + ast_vhub_reset_ep0(d); + + /* Enable device and its EP0 interrupts */ + reg = VHUB_DEV_EN_ENABLE_PORT | + VHUB_DEV_EN_EP0_IN_ACK_IRQEN | + VHUB_DEV_EN_EP0_OUT_ACK_IRQEN | + VHUB_DEV_EN_EP0_SETUP_IRQEN; + if (d->gadget.speed == USB_SPEED_HIGH) + reg |= VHUB_DEV_EN_SPEED_SEL_HIGH; + writel(reg, d->regs + AST_VHUB_DEV_EN_CTRL); + + /* Enable device interrupt in the hub as well */ + hmsk = VHUB_IRQ_DEVICE1 << d->index; + reg = readl(d->vhub->regs + AST_VHUB_IER); + reg |= hmsk; + writel(reg, d->vhub->regs + AST_VHUB_IER); + + /* Set EP0 DMA buffer address */ + writel(d->ep0.buf_dma, d->regs + AST_VHUB_DEV_EP0_DATA); + + /* Clear stall on all EPs */ + for (i = 0; i < d->max_epns; i++) { + struct ast_vhub_ep *ep = d->epns[i]; + + if (ep && (ep->epn.stalled || ep->epn.wedged)) { + ep->epn.stalled = false; + ep->epn.wedged = false; + ast_vhub_update_epn_stall(ep); + } + } + + /* Additional cleanups */ + d->wakeup_en = false; + d->enabled = true; +} + +static void ast_vhub_dev_disable(struct ast_vhub_dev *d) +{ + u32 reg, hmsk; + + if (!d->enabled) + return; + + /* Disable device interrupt in the hub */ + hmsk = VHUB_IRQ_DEVICE1 << d->index; + reg = readl(d->vhub->regs + AST_VHUB_IER); + reg &= ~hmsk; + writel(reg, d->vhub->regs + AST_VHUB_IER); + + /* Then disable device */ + writel(0, d->regs + AST_VHUB_DEV_EN_CTRL); + d->gadget.speed = USB_SPEED_UNKNOWN; + d->enabled = false; +} + +static int ast_vhub_dev_feature(struct ast_vhub_dev *d, + u16 wIndex, u16 wValue, + bool is_set) +{ + u32 val; + + DDBG(d, "%s_FEATURE(dev val=%02x)\n", + is_set ? "SET" : "CLEAR", wValue); + + if (wValue == USB_DEVICE_REMOTE_WAKEUP) { + d->wakeup_en = is_set; + return std_req_complete; + } + + if (wValue == USB_DEVICE_TEST_MODE) { + val = readl(d->vhub->regs + AST_VHUB_CTRL); + val &= ~GENMASK(10, 8); + val |= VHUB_CTRL_SET_TEST_MODE((wIndex >> 8) & 0x7); + writel(val, d->vhub->regs + AST_VHUB_CTRL); + + return std_req_complete; + } + + return std_req_driver; +} + +static int ast_vhub_ep_feature(struct ast_vhub_dev *d, + u16 wIndex, u16 wValue, bool is_set) +{ + struct ast_vhub_ep *ep; + int ep_num; + + ep_num = wIndex & USB_ENDPOINT_NUMBER_MASK; + DDBG(d, "%s_FEATURE(ep%d val=%02x)\n", + is_set ? "SET" : "CLEAR", ep_num, wValue); + if (ep_num == 0) + return std_req_complete; + if (ep_num >= d->max_epns || !d->epns[ep_num - 1]) + return std_req_stall; + if (wValue != USB_ENDPOINT_HALT) + return std_req_driver; + + ep = d->epns[ep_num - 1]; + if (WARN_ON(!ep)) + return std_req_stall; + + if (!ep->epn.enabled || !ep->ep.desc || ep->epn.is_iso || + ep->epn.is_in != !!(wIndex & USB_DIR_IN)) + return std_req_stall; + + DDBG(d, "%s stall on EP %d\n", + is_set ? "setting" : "clearing", ep_num); + ep->epn.stalled = is_set; + ast_vhub_update_epn_stall(ep); + + return std_req_complete; +} + +static int ast_vhub_dev_status(struct ast_vhub_dev *d, + u16 wIndex, u16 wValue) +{ + u8 st0; + + DDBG(d, "GET_STATUS(dev)\n"); + + st0 = d->gadget.is_selfpowered << USB_DEVICE_SELF_POWERED; + if (d->wakeup_en) + st0 |= 1 << USB_DEVICE_REMOTE_WAKEUP; + + return ast_vhub_simple_reply(&d->ep0, st0, 0); +} + +static int ast_vhub_ep_status(struct ast_vhub_dev *d, + u16 wIndex, u16 wValue) +{ + int ep_num = wIndex & USB_ENDPOINT_NUMBER_MASK; + struct ast_vhub_ep *ep; + u8 st0 = 0; + + DDBG(d, "GET_STATUS(ep%d)\n", ep_num); + + if (ep_num >= d->max_epns) + return std_req_stall; + if (ep_num != 0) { + ep = d->epns[ep_num - 1]; + if (!ep) + return std_req_stall; + if (!ep->epn.enabled || !ep->ep.desc || ep->epn.is_iso || + ep->epn.is_in != !!(wIndex & USB_DIR_IN)) + return std_req_stall; + if (ep->epn.stalled) + st0 |= 1 << USB_ENDPOINT_HALT; + } + + return ast_vhub_simple_reply(&d->ep0, st0, 0); +} + +static void ast_vhub_dev_set_address(struct ast_vhub_dev *d, u8 addr) +{ + u32 reg; + + DDBG(d, "SET_ADDRESS: Got address %x\n", addr); + + reg = readl(d->regs + AST_VHUB_DEV_EN_CTRL); + reg &= ~VHUB_DEV_EN_ADDR_MASK; + reg |= VHUB_DEV_EN_SET_ADDR(addr); + writel(reg, d->regs + AST_VHUB_DEV_EN_CTRL); +} + +int ast_vhub_std_dev_request(struct ast_vhub_ep *ep, + struct usb_ctrlrequest *crq) +{ + struct ast_vhub_dev *d = ep->dev; + u16 wValue, wIndex; + + /* No driver, we shouldn't be enabled ... */ + if (!d->driver || !d->enabled) { + EPDBG(ep, + "Device is wrong state driver=%p enabled=%d\n", + d->driver, d->enabled); + return std_req_stall; + } + + /* + * Note: we used to reject/stall requests while suspended, + * we don't do that anymore as we seem to have cases of + * mass storage getting very upset. + */ + + /* First packet, grab speed */ + if (d->gadget.speed == USB_SPEED_UNKNOWN) { + d->gadget.speed = ep->vhub->speed; + if (d->gadget.speed > d->driver->max_speed) + d->gadget.speed = d->driver->max_speed; + DDBG(d, "fist packet, captured speed %d\n", + d->gadget.speed); + } + + wValue = le16_to_cpu(crq->wValue); + wIndex = le16_to_cpu(crq->wIndex); + + switch ((crq->bRequestType << 8) | crq->bRequest) { + /* SET_ADDRESS */ + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + ast_vhub_dev_set_address(d, wValue); + return std_req_complete; + + /* GET_STATUS */ + case DeviceRequest | USB_REQ_GET_STATUS: + return ast_vhub_dev_status(d, wIndex, wValue); + case InterfaceRequest | USB_REQ_GET_STATUS: + return ast_vhub_simple_reply(ep, 0, 0); + case EndpointRequest | USB_REQ_GET_STATUS: + return ast_vhub_ep_status(d, wIndex, wValue); + + /* SET/CLEAR_FEATURE */ + case DeviceOutRequest | USB_REQ_SET_FEATURE: + return ast_vhub_dev_feature(d, wIndex, wValue, true); + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + return ast_vhub_dev_feature(d, wIndex, wValue, false); + case EndpointOutRequest | USB_REQ_SET_FEATURE: + return ast_vhub_ep_feature(d, wIndex, wValue, true); + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + return ast_vhub_ep_feature(d, wIndex, wValue, false); + } + return std_req_driver; +} + +static int ast_vhub_udc_wakeup(struct usb_gadget* gadget) +{ + struct ast_vhub_dev *d = to_ast_dev(gadget); + unsigned long flags; + int rc = -EINVAL; + + spin_lock_irqsave(&d->vhub->lock, flags); + if (!d->wakeup_en) + goto err; + + DDBG(d, "Device initiated wakeup\n"); + + /* Wakeup the host */ + ast_vhub_hub_wake_all(d->vhub); + rc = 0; + err: + spin_unlock_irqrestore(&d->vhub->lock, flags); + return rc; +} + +static int ast_vhub_udc_get_frame(struct usb_gadget* gadget) +{ + struct ast_vhub_dev *d = to_ast_dev(gadget); + + return (readl(d->vhub->regs + AST_VHUB_USBSTS) >> 16) & 0x7ff; +} + +static void ast_vhub_dev_nuke(struct ast_vhub_dev *d) +{ + unsigned int i; + + for (i = 0; i < d->max_epns; i++) { + if (!d->epns[i]) + continue; + ast_vhub_nuke(d->epns[i], -ESHUTDOWN); + } +} + +static int ast_vhub_udc_pullup(struct usb_gadget* gadget, int on) +{ + struct ast_vhub_dev *d = to_ast_dev(gadget); + unsigned long flags; + + spin_lock_irqsave(&d->vhub->lock, flags); + + DDBG(d, "pullup(%d)\n", on); + + /* Mark disconnected in the hub */ + ast_vhub_device_connect(d->vhub, d->index, on); + + /* + * If enabled, nuke all requests if any (there shouldn't be) + * and disable the port. This will clear the address too. + */ + if (d->enabled) { + ast_vhub_dev_nuke(d); + ast_vhub_dev_disable(d); + } + + spin_unlock_irqrestore(&d->vhub->lock, flags); + + return 0; +} + +static int ast_vhub_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct ast_vhub_dev *d = to_ast_dev(gadget); + unsigned long flags; + + spin_lock_irqsave(&d->vhub->lock, flags); + + DDBG(d, "start\n"); + + /* We don't do much more until the hub enables us */ + d->driver = driver; + d->gadget.is_selfpowered = 1; + + spin_unlock_irqrestore(&d->vhub->lock, flags); + + return 0; +} + +static struct usb_ep *ast_vhub_udc_match_ep(struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *ss) +{ + struct ast_vhub_dev *d = to_ast_dev(gadget); + struct ast_vhub_ep *ep; + struct usb_ep *u_ep; + unsigned int max, addr, i; + + DDBG(d, "Match EP type %d\n", usb_endpoint_type(desc)); + + /* + * First we need to look for an existing unclaimed EP as another + * configuration may have already associated a bunch of EPs with + * this gadget. This duplicates the code in usb_ep_autoconfig_ss() + * unfortunately. + */ + list_for_each_entry(u_ep, &gadget->ep_list, ep_list) { + if (usb_gadget_ep_match_desc(gadget, u_ep, desc, ss)) { + DDBG(d, " -> using existing EP%d\n", + to_ast_ep(u_ep)->d_idx); + return u_ep; + } + } + + /* + * We didn't find one, we need to grab one from the pool. + * + * First let's do some sanity checking + */ + switch(usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_CONTROL: + /* Only EP0 can be a control endpoint */ + return NULL; + case USB_ENDPOINT_XFER_ISOC: + /* ISO: limit 1023 bytes full speed, 1024 high/super speed */ + if (gadget_is_dualspeed(gadget)) + max = 1024; + else + max = 1023; + break; + case USB_ENDPOINT_XFER_BULK: + if (gadget_is_dualspeed(gadget)) + max = 512; + else + max = 64; + break; + case USB_ENDPOINT_XFER_INT: + if (gadget_is_dualspeed(gadget)) + max = 1024; + else + max = 64; + break; + } + if (usb_endpoint_maxp(desc) > max) + return NULL; + + /* + * Find a free EP address for that device. We can't + * let the generic code assign these as it would + * create overlapping numbers for IN and OUT which + * we don't support, so also create a suitable name + * that will allow the generic code to use our + * assigned address. + */ + for (i = 0; i < d->max_epns; i++) + if (d->epns[i] == NULL) + break; + if (i >= d->max_epns) + return NULL; + addr = i + 1; + + /* + * Now grab an EP from the shared pool and associate + * it with our device + */ + ep = ast_vhub_alloc_epn(d, addr); + if (!ep) + return NULL; + DDBG(d, "Allocated epn#%d for port EP%d\n", + ep->epn.g_idx, addr); + + return &ep->ep; +} + +static int ast_vhub_udc_stop(struct usb_gadget *gadget) +{ + struct ast_vhub_dev *d = to_ast_dev(gadget); + unsigned long flags; + + spin_lock_irqsave(&d->vhub->lock, flags); + + DDBG(d, "stop\n"); + + d->driver = NULL; + d->gadget.speed = USB_SPEED_UNKNOWN; + + ast_vhub_dev_nuke(d); + + if (d->enabled) + ast_vhub_dev_disable(d); + + spin_unlock_irqrestore(&d->vhub->lock, flags); + + return 0; +} + +static const struct usb_gadget_ops ast_vhub_udc_ops = { + .get_frame = ast_vhub_udc_get_frame, + .wakeup = ast_vhub_udc_wakeup, + .pullup = ast_vhub_udc_pullup, + .udc_start = ast_vhub_udc_start, + .udc_stop = ast_vhub_udc_stop, + .match_ep = ast_vhub_udc_match_ep, +}; + +void ast_vhub_dev_suspend(struct ast_vhub_dev *d) +{ + if (d->driver && d->driver->suspend) { + spin_unlock(&d->vhub->lock); + d->driver->suspend(&d->gadget); + spin_lock(&d->vhub->lock); + } +} + +void ast_vhub_dev_resume(struct ast_vhub_dev *d) +{ + if (d->driver && d->driver->resume) { + spin_unlock(&d->vhub->lock); + d->driver->resume(&d->gadget); + spin_lock(&d->vhub->lock); + } +} + +void ast_vhub_dev_reset(struct ast_vhub_dev *d) +{ + /* No driver, just disable the device and return */ + if (!d->driver) { + ast_vhub_dev_disable(d); + return; + } + + /* If the port isn't enabled, just enable it */ + if (!d->enabled) { + DDBG(d, "Reset of disabled device, enabling...\n"); + ast_vhub_dev_enable(d); + } else { + DDBG(d, "Reset of enabled device, resetting...\n"); + spin_unlock(&d->vhub->lock); + usb_gadget_udc_reset(&d->gadget, d->driver); + spin_lock(&d->vhub->lock); + + /* + * Disable and maybe re-enable HW, this will clear the address + * and speed setting. + */ + ast_vhub_dev_disable(d); + ast_vhub_dev_enable(d); + } +} + +void ast_vhub_del_dev(struct ast_vhub_dev *d) +{ + unsigned long flags; + + spin_lock_irqsave(&d->vhub->lock, flags); + if (!d->registered) { + spin_unlock_irqrestore(&d->vhub->lock, flags); + return; + } + d->registered = false; + spin_unlock_irqrestore(&d->vhub->lock, flags); + + usb_del_gadget_udc(&d->gadget); + device_unregister(d->port_dev); + kfree(d->epns); +} + +static void ast_vhub_dev_release(struct device *dev) +{ + kfree(dev); +} + +int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx) +{ + struct ast_vhub_dev *d = &vhub->ports[idx].dev; + struct device *parent = &vhub->pdev->dev; + int rc; + + d->vhub = vhub; + d->index = idx; + d->name = devm_kasprintf(parent, GFP_KERNEL, "port%d", idx+1); + if (!d->name) + return -ENOMEM; + + d->regs = vhub->regs + 0x100 + 0x10 * idx; + + ast_vhub_init_ep0(vhub, &d->ep0, d); + + /* + * A USB device can have up to 30 endpoints besides control + * endpoint 0. + */ + d->max_epns = min_t(u32, vhub->max_epns, 30); + d->epns = kzalloc_objs(*d->epns, d->max_epns); + if (!d->epns) + return -ENOMEM; + + /* + * The UDC core really needs us to have separate and uniquely + * named "parent" devices for each port so we create a sub device + * here for that purpose + */ + d->port_dev = kzalloc_obj(struct device); + if (!d->port_dev) { + rc = -ENOMEM; + goto fail_alloc; + } + device_initialize(d->port_dev); + d->port_dev->release = ast_vhub_dev_release; + d->port_dev->parent = parent; + dev_set_name(d->port_dev, "%s:p%d", dev_name(parent), idx + 1); + rc = device_add(d->port_dev); + if (rc) + goto fail_add; + + /* Populate gadget */ + INIT_LIST_HEAD(&d->gadget.ep_list); + d->gadget.ops = &ast_vhub_udc_ops; + d->gadget.ep0 = &d->ep0.ep; + d->gadget.name = KBUILD_MODNAME; + if (vhub->force_usb1) + d->gadget.max_speed = USB_SPEED_FULL; + else + d->gadget.max_speed = USB_SPEED_HIGH; + d->gadget.speed = USB_SPEED_UNKNOWN; + d->gadget.dev.of_node = vhub->pdev->dev.of_node; + dev_set_of_node_reused(&d->gadget.dev); + + rc = usb_add_gadget_udc(d->port_dev, &d->gadget); + if (rc != 0) + goto fail_udc; + d->registered = true; + + return 0; + fail_udc: + device_del(d->port_dev); + fail_add: + put_device(d->port_dev); + fail_alloc: + kfree(d->epns); + + return rc; +} diff --git a/drivers/usb/gadget/udc/aspeed-vhub/ep0.c b/drivers/usb/gadget/udc/aspeed-vhub/ep0.c new file mode 100644 index 000000000..e9aa74231 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/ep0.c @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget + * + * ep0.c - Endpoint 0 handling + * + * Copyright 2017 IBM Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vhub.h" + +int ast_vhub_reply(struct ast_vhub_ep *ep, char *ptr, int len) +{ + struct usb_request *req = &ep->ep0.req.req; + int rc; + + if (WARN_ON(ep->d_idx != 0)) + return std_req_stall; + if (WARN_ON(!ep->ep0.dir_in)) + return std_req_stall; + if (WARN_ON(len > AST_VHUB_EP0_MAX_PACKET)) + return std_req_stall; + if (WARN_ON(req->status == -EINPROGRESS)) + return std_req_stall; + + req->buf = ptr; + req->length = len; + req->complete = NULL; + req->zero = true; + + /* + * Call internal queue directly after dropping the lock. This is + * safe to do as the reply is always the last thing done when + * processing a SETUP packet, usually as a tail call + */ + spin_unlock(&ep->vhub->lock); + if (ep->ep.ops->queue(&ep->ep, req, GFP_ATOMIC)) + rc = std_req_stall; + else + rc = std_req_data; + spin_lock(&ep->vhub->lock); + return rc; +} + +int __ast_vhub_simple_reply(struct ast_vhub_ep *ep, int len, ...) +{ + u8 *buffer = ep->buf; + unsigned int i; + va_list args; + + va_start(args, len); + + /* Copy data directly into EP buffer */ + for (i = 0; i < len; i++) + buffer[i] = va_arg(args, int); + va_end(args); + + /* req->buf NULL means data is already there */ + return ast_vhub_reply(ep, NULL, len); +} + +void ast_vhub_ep0_handle_setup(struct ast_vhub_ep *ep) +{ + struct usb_ctrlrequest crq; + enum std_req_rc std_req_rc; + int rc = -ENODEV; + + if (WARN_ON(ep->d_idx != 0)) + return; + + /* + * Grab the setup packet from the chip and byteswap + * interesting fields + */ + memcpy_fromio(&crq, ep->ep0.setup, sizeof(crq)); + + EPDBG(ep, "SETUP packet %02x/%02x/%04x/%04x/%04x [%s] st=%d\n", + crq.bRequestType, crq.bRequest, + le16_to_cpu(crq.wValue), + le16_to_cpu(crq.wIndex), + le16_to_cpu(crq.wLength), + (crq.bRequestType & USB_DIR_IN) ? "in" : "out", + ep->ep0.state); + + /* + * Check our state, cancel pending requests if needed + * + * Note: Under some circumstances, we can get a new setup + * packet while waiting for the stall ack, just accept it. + * + * In any case, a SETUP packet in wrong state should have + * reset the HW state machine, so let's just log, nuke + * requests, move on. + */ + if (ep->ep0.state != ep0_state_token && + ep->ep0.state != ep0_state_stall) { + EPDBG(ep, "wrong state\n"); + ast_vhub_nuke(ep, -EIO); + } + + /* Calculate next state for EP0 */ + ep->ep0.state = ep0_state_data; + ep->ep0.dir_in = !!(crq.bRequestType & USB_DIR_IN); + + /* If this is the vHub, we handle requests differently */ + std_req_rc = std_req_driver; + if (ep->dev == NULL) { + if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + std_req_rc = ast_vhub_std_hub_request(ep, &crq); + else if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) + std_req_rc = ast_vhub_class_hub_request(ep, &crq); + else + std_req_rc = std_req_stall; + } else if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + std_req_rc = ast_vhub_std_dev_request(ep, &crq); + + /* Act upon result */ + switch(std_req_rc) { + case std_req_complete: + goto complete; + case std_req_stall: + goto stall; + case std_req_driver: + break; + case std_req_data: + return; + } + + /* Pass request up to the gadget driver */ + if (WARN_ON(!ep->dev)) + goto stall; + if (ep->dev->driver) { + EPDBG(ep, "forwarding to gadget...\n"); + spin_unlock(&ep->vhub->lock); + rc = ep->dev->driver->setup(&ep->dev->gadget, &crq); + spin_lock(&ep->vhub->lock); + EPDBG(ep, "driver returned %d\n", rc); + } else { + EPDBG(ep, "no gadget for request !\n"); + } + if (rc >= 0) + return; + + stall: + EPDBG(ep, "stalling\n"); + writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat); + ep->ep0.state = ep0_state_stall; + ep->ep0.dir_in = false; + return; + + complete: + EPVDBG(ep, "sending [in] status with no data\n"); + writel(VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat); + ep->ep0.state = ep0_state_status; + ep->ep0.dir_in = false; +} + + +static void ast_vhub_ep0_do_send(struct ast_vhub_ep *ep, + struct ast_vhub_req *req) +{ + unsigned int chunk; + u32 reg; + + /* If this is a 0-length request, it's the gadget trying to + * send a status on our behalf. We take it from here. + */ + if (req->req.length == 0) + req->last_desc = 1; + + /* Are we done ? Complete request, otherwise wait for next interrupt */ + if (req->last_desc >= 0) { + EPVDBG(ep, "complete send %d/%d\n", + req->req.actual, req->req.length); + ep->ep0.state = ep0_state_status; + writel(VHUB_EP0_RX_BUFF_RDY, ep->ep0.ctlstat); + ast_vhub_done(ep, req, 0); + return; + } + + /* + * Next chunk cropped to max packet size. Also check if this + * is the last packet + */ + chunk = req->req.length - req->req.actual; + if (chunk > ep->ep.maxpacket) + chunk = ep->ep.maxpacket; + else if ((chunk < ep->ep.maxpacket) || !req->req.zero) + req->last_desc = 1; + + EPVDBG(ep, "send chunk=%d last=%d, req->act=%d mp=%d\n", + chunk, req->last_desc, req->req.actual, ep->ep.maxpacket); + + /* + * Copy data if any (internal requests already have data + * in the EP buffer) + */ + if (chunk && req->req.buf) + memcpy(ep->buf, req->req.buf + req->req.actual, chunk); + + vhub_dma_workaround(ep->buf); + + /* Remember chunk size and trigger send */ + reg = VHUB_EP0_SET_TX_LEN(chunk); + writel(reg, ep->ep0.ctlstat); + writel(reg | VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat); + req->req.actual += chunk; +} + +static void ast_vhub_ep0_rx_prime(struct ast_vhub_ep *ep) +{ + EPVDBG(ep, "rx prime\n"); + + /* Prime endpoint for receiving data */ + writel(VHUB_EP0_RX_BUFF_RDY, ep->ep0.ctlstat); +} + +static void ast_vhub_ep0_do_receive(struct ast_vhub_ep *ep, struct ast_vhub_req *req, + unsigned int len) +{ + unsigned int remain; + int rc = 0; + + /* We are receiving... grab request */ + remain = req->req.length - req->req.actual; + + EPVDBG(ep, "receive got=%d remain=%d\n", len, remain); + + /* Are we getting more than asked ? */ + if (len > remain) { + EPDBG(ep, "receiving too much (ovf: %d) !\n", + len - remain); + len = remain; + rc = -EOVERFLOW; + } + + /* Hardware return wrong data len */ + if (len < ep->ep.maxpacket && len != remain) { + EPDBG(ep, "using expected data len instead\n"); + len = remain; + } + + if (len && req->req.buf) + memcpy(req->req.buf + req->req.actual, ep->buf, len); + req->req.actual += len; + + /* Done ? */ + if (len < ep->ep.maxpacket || len == remain) { + ep->ep0.state = ep0_state_status; + writel(VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat); + ast_vhub_done(ep, req, rc); + } else + ast_vhub_ep0_rx_prime(ep); +} + +void ast_vhub_ep0_handle_ack(struct ast_vhub_ep *ep, bool in_ack) +{ + struct ast_vhub_req *req; + struct ast_vhub *vhub = ep->vhub; + struct device *dev = &vhub->pdev->dev; + bool stall = false; + u32 stat; + + /* Read EP0 status */ + stat = readl(ep->ep0.ctlstat); + + /* Grab current request if any */ + req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue); + + EPVDBG(ep, "ACK status=%08x,state=%d is_in=%d in_ack=%d req=%p\n", + stat, ep->ep0.state, ep->ep0.dir_in, in_ack, req); + + switch(ep->ep0.state) { + case ep0_state_token: + /* There should be no request queued in that state... */ + if (req) { + dev_warn(dev, "request present while in TOKEN state\n"); + ast_vhub_nuke(ep, -EINVAL); + } + dev_warn(dev, "ack while in TOKEN state\n"); + stall = true; + break; + case ep0_state_data: + /* Check the state bits corresponding to our direction */ + if ((ep->ep0.dir_in && (stat & VHUB_EP0_TX_BUFF_RDY)) || + (!ep->ep0.dir_in && (stat & VHUB_EP0_RX_BUFF_RDY)) || + (ep->ep0.dir_in != in_ack)) { + /* In that case, ignore interrupt */ + dev_warn(dev, "irq state mismatch"); + break; + } + /* + * We are in data phase and there's no request, something is + * wrong, stall + */ + if (!req) { + dev_warn(dev, "data phase, no request\n"); + stall = true; + break; + } + + /* We have a request, handle data transfers */ + if (ep->ep0.dir_in) + ast_vhub_ep0_do_send(ep, req); + else + ast_vhub_ep0_do_receive(ep, req, VHUB_EP0_RX_LEN(stat)); + return; + case ep0_state_status: + /* Nuke stale requests */ + if (req) { + dev_warn(dev, "request present while in STATUS state\n"); + ast_vhub_nuke(ep, -EINVAL); + } + + /* + * If the status phase completes with the wrong ack, stall + * the endpoint just in case, to abort whatever the host + * was doing. + */ + if (ep->ep0.dir_in == in_ack) { + dev_warn(dev, "status direction mismatch\n"); + stall = true; + } + break; + case ep0_state_stall: + /* + * There shouldn't be any request left, but nuke just in case + * otherwise the stale request will block subsequent ones + */ + ast_vhub_nuke(ep, -EIO); + break; + } + + /* Reset to token state or stall */ + if (stall) { + writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat); + ep->ep0.state = ep0_state_stall; + } else + ep->ep0.state = ep0_state_token; +} + +static int ast_vhub_ep0_queue(struct usb_ep* u_ep, struct usb_request *u_req, + gfp_t gfp_flags) +{ + struct ast_vhub_req *req = to_ast_req(u_req); + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + struct ast_vhub *vhub = ep->vhub; + struct device *dev = &vhub->pdev->dev; + unsigned long flags; + + /* Paranoid cheks */ + if (!u_req || (!u_req->complete && !req->internal)) { + dev_warn(dev, "Bogus EP0 request ! u_req=%p\n", u_req); + if (u_req) { + dev_warn(dev, "complete=%p internal=%d\n", + u_req->complete, req->internal); + } + return -EINVAL; + } + + /* Not endpoint 0 ? */ + if (WARN_ON(ep->d_idx != 0)) + return -EINVAL; + + /* Disabled device */ + if (ep->dev && !ep->dev->enabled) + return -ESHUTDOWN; + + /* Data, no buffer and not internal ? */ + if (u_req->length && !u_req->buf && !req->internal) { + dev_warn(dev, "Request with no buffer !\n"); + return -EINVAL; + } + + EPVDBG(ep, "enqueue req @%p\n", req); + EPVDBG(ep, " l=%d zero=%d noshort=%d is_in=%d\n", + u_req->length, u_req->zero, + u_req->short_not_ok, ep->ep0.dir_in); + + /* Initialize request progress fields */ + u_req->status = -EINPROGRESS; + u_req->actual = 0; + req->last_desc = -1; + req->active = false; + + spin_lock_irqsave(&vhub->lock, flags); + + /* EP0 can only support a single request at a time */ + if (!list_empty(&ep->queue) || + ep->ep0.state == ep0_state_token || + ep->ep0.state == ep0_state_stall) { + dev_warn(dev, "EP0: Request in wrong state\n"); + EPVDBG(ep, "EP0: list_empty=%d state=%d\n", + list_empty(&ep->queue), ep->ep0.state); + spin_unlock_irqrestore(&vhub->lock, flags); + return -EBUSY; + } + + /* Add request to list and kick processing if empty */ + list_add_tail(&req->queue, &ep->queue); + + if (ep->ep0.dir_in) { + /* IN request, send data */ + ast_vhub_ep0_do_send(ep, req); + } else if (u_req->length == 0) { + /* 0-len request, send completion as rx */ + EPVDBG(ep, "0-length rx completion\n"); + ep->ep0.state = ep0_state_status; + writel(VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat); + ast_vhub_done(ep, req, 0); + } else { + /* OUT request, start receiver */ + ast_vhub_ep0_rx_prime(ep); + } + + spin_unlock_irqrestore(&vhub->lock, flags); + + return 0; +} + +static int ast_vhub_ep0_dequeue(struct usb_ep* u_ep, struct usb_request *u_req) +{ + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + struct ast_vhub *vhub = ep->vhub; + struct ast_vhub_req *req; + unsigned long flags; + int rc = -EINVAL; + + spin_lock_irqsave(&vhub->lock, flags); + + /* Only one request can be in the queue */ + req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue); + + /* Is it ours ? */ + if (req && u_req == &req->req) { + EPVDBG(ep, "dequeue req @%p\n", req); + + /* + * We don't have to deal with "active" as all + * DMAs go to the EP buffers, not the request. + */ + ast_vhub_done(ep, req, -ECONNRESET); + + /* We do stall the EP to clean things up in HW */ + writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat); + ep->ep0.state = ep0_state_status; + ep->ep0.dir_in = false; + rc = 0; + } + spin_unlock_irqrestore(&vhub->lock, flags); + return rc; +} + + +static const struct usb_ep_ops ast_vhub_ep0_ops = { + .queue = ast_vhub_ep0_queue, + .dequeue = ast_vhub_ep0_dequeue, + .alloc_request = ast_vhub_alloc_request, + .free_request = ast_vhub_free_request, +}; + +void ast_vhub_reset_ep0(struct ast_vhub_dev *dev) +{ + struct ast_vhub_ep *ep = &dev->ep0; + + ast_vhub_nuke(ep, -EIO); + ep->ep0.state = ep0_state_token; +} + + +void ast_vhub_init_ep0(struct ast_vhub *vhub, struct ast_vhub_ep *ep, + struct ast_vhub_dev *dev) +{ + memset(ep, 0, sizeof(*ep)); + + INIT_LIST_HEAD(&ep->ep.ep_list); + INIT_LIST_HEAD(&ep->queue); + ep->ep.ops = &ast_vhub_ep0_ops; + ep->ep.name = "ep0"; + ep->ep.caps.type_control = true; + usb_ep_set_maxpacket_limit(&ep->ep, AST_VHUB_EP0_MAX_PACKET); + ep->d_idx = 0; + ep->dev = dev; + ep->vhub = vhub; + ep->ep0.state = ep0_state_token; + INIT_LIST_HEAD(&ep->ep0.req.queue); + ep->ep0.req.internal = true; + + /* Small difference between vHub and devices */ + if (dev) { + ep->ep0.ctlstat = dev->regs + AST_VHUB_DEV_EP0_CTRL; + ep->ep0.setup = vhub->regs + + AST_VHUB_SETUP0 + 8 * (dev->index + 1); + ep->buf = vhub->ep0_bufs + + AST_VHUB_EP0_MAX_PACKET * (dev->index + 1); + ep->buf_dma = vhub->ep0_bufs_dma + + AST_VHUB_EP0_MAX_PACKET * (dev->index + 1); + } else { + ep->ep0.ctlstat = vhub->regs + AST_VHUB_EP0_CTRL; + ep->ep0.setup = vhub->regs + AST_VHUB_SETUP0; + ep->buf = vhub->ep0_bufs; + ep->buf_dma = vhub->ep0_bufs_dma; + } +} diff --git a/drivers/usb/gadget/udc/aspeed-vhub/epn.c b/drivers/usb/gadget/udc/aspeed-vhub/epn.c new file mode 100644 index 000000000..148d7ec3e --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/epn.c @@ -0,0 +1,854 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget + * + * epn.c - Generic endpoints management + * + * Copyright 2017 IBM Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vhub.h" + +#define EXTRA_CHECKS + +#ifdef EXTRA_CHECKS +#define CHECK(ep, expr, fmt...) \ + do { \ + if (!(expr)) EPDBG(ep, "CHECK:" fmt); \ + } while(0) +#else +#define CHECK(ep, expr, fmt...) do { } while(0) +#endif + +static void ast_vhub_epn_kick(struct ast_vhub_ep *ep, struct ast_vhub_req *req) +{ + unsigned int act = req->req.actual; + unsigned int len = req->req.length; + unsigned int chunk; + + /* There should be no DMA ongoing */ + WARN_ON(req->active); + + /* Calculate next chunk size */ + chunk = len - act; + if (chunk > ep->ep.maxpacket) + chunk = ep->ep.maxpacket; + else if ((chunk < ep->ep.maxpacket) || !req->req.zero) + req->last_desc = 1; + + EPVDBG(ep, "kick req %p act=%d/%d chunk=%d last=%d\n", + req, act, len, chunk, req->last_desc); + + /* If DMA unavailable, using staging EP buffer */ + if (!req->req.dma) { + + /* For IN transfers, copy data over first */ + if (ep->epn.is_in) { + memcpy(ep->buf, req->req.buf + act, chunk); + vhub_dma_workaround(ep->buf); + } + writel(ep->buf_dma, ep->epn.regs + AST_VHUB_EP_DESC_BASE); + } else { + if (ep->epn.is_in) + vhub_dma_workaround(req->req.buf); + writel(req->req.dma + act, ep->epn.regs + AST_VHUB_EP_DESC_BASE); + } + + /* Start DMA */ + req->active = true; + writel(VHUB_EP_DMA_SET_TX_SIZE(chunk), + ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + writel(VHUB_EP_DMA_SET_TX_SIZE(chunk) | VHUB_EP_DMA_SINGLE_KICK, + ep->epn.regs + AST_VHUB_EP_DESC_STATUS); +} + +static void ast_vhub_epn_handle_ack(struct ast_vhub_ep *ep) +{ + struct ast_vhub_req *req; + unsigned int len; + int status = 0; + u32 stat; + + /* Read EP status */ + stat = readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + + /* Grab current request if any */ + req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue); + + EPVDBG(ep, "ACK status=%08x is_in=%d, req=%p (active=%d)\n", + stat, ep->epn.is_in, req, req ? req->active : 0); + + /* In absence of a request, bail out, must have been dequeued */ + if (!req) + return; + + /* + * Request not active, move on to processing queue, active request + * was probably dequeued + */ + if (!req->active) + goto next_chunk; + + /* Check if HW has moved on */ + if (VHUB_EP_DMA_RPTR(stat) != 0) { + EPDBG(ep, "DMA read pointer not 0 !\n"); + return; + } + + /* No current DMA ongoing */ + req->active = false; + + /* Grab length out of HW */ + len = VHUB_EP_DMA_TX_SIZE(stat); + + /* If not using DMA, copy data out if needed */ + if (!req->req.dma && !ep->epn.is_in && len) { + if (req->req.actual + len > req->req.length) { + req->last_desc = 1; + status = -EOVERFLOW; + goto done; + } else { + memcpy(req->req.buf + req->req.actual, ep->buf, len); + } + } + /* Adjust size */ + req->req.actual += len; + + /* Check for short packet */ + if (len < ep->ep.maxpacket) + req->last_desc = 1; + +done: + /* That's it ? complete the request and pick a new one */ + if (req->last_desc >= 0) { + ast_vhub_done(ep, req, status); + req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, + queue); + + /* + * Due to lock dropping inside "done" the next request could + * already be active, so check for that and bail if needed. + */ + if (!req || req->active) + return; + } + + next_chunk: + ast_vhub_epn_kick(ep, req); +} + +static inline unsigned int ast_vhub_count_free_descs(struct ast_vhub_ep *ep) +{ + /* + * d_next == d_last means descriptor list empty to HW, + * thus we can only have AST_VHUB_DESCS_COUNT-1 descriptors + * in the list + */ + return (ep->epn.d_last + AST_VHUB_DESCS_COUNT - ep->epn.d_next - 1) & + (AST_VHUB_DESCS_COUNT - 1); +} + +static void ast_vhub_epn_kick_desc(struct ast_vhub_ep *ep, + struct ast_vhub_req *req) +{ + struct ast_vhub_desc *desc = NULL; + unsigned int act = req->act_count; + unsigned int len = req->req.length; + unsigned int chunk; + + /* Mark request active if not already */ + req->active = true; + + /* If the request was already completely written, do nothing */ + if (req->last_desc >= 0) + return; + + EPVDBG(ep, "kick act=%d/%d chunk_max=%d free_descs=%d\n", + act, len, ep->epn.chunk_max, ast_vhub_count_free_descs(ep)); + + /* While we can create descriptors */ + while (ast_vhub_count_free_descs(ep) && req->last_desc < 0) { + unsigned int d_num; + + /* Grab next free descriptor */ + d_num = ep->epn.d_next; + desc = &ep->epn.descs[d_num]; + ep->epn.d_next = (d_num + 1) & (AST_VHUB_DESCS_COUNT - 1); + + /* Calculate next chunk size */ + chunk = len - act; + if (chunk <= ep->epn.chunk_max) { + /* + * Is this the last packet ? Because of having up to 8 + * packets in a descriptor we can't just compare "chunk" + * with ep.maxpacket. We have to see if it's a multiple + * of it to know if we have to send a zero packet. + * Sadly that involves a modulo which is a bit expensive + * but probably still better than not doing it. + */ + if (!chunk || !req->req.zero || (chunk % ep->ep.maxpacket) != 0) + req->last_desc = d_num; + } else { + chunk = ep->epn.chunk_max; + } + + EPVDBG(ep, " chunk: act=%d/%d chunk=%d last=%d desc=%d free=%d\n", + act, len, chunk, req->last_desc, d_num, + ast_vhub_count_free_descs(ep)); + + /* Populate descriptor */ + desc->w0 = cpu_to_le32(req->req.dma + act); + + /* Interrupt if end of request or no more descriptors */ + + /* + * TODO: Be smarter about it, if we don't have enough + * descriptors request an interrupt before queue empty + * or so in order to be able to populate more before + * the HW runs out. This isn't a problem at the moment + * as we use 256 descriptors and only put at most one + * request in the ring. + */ + desc->w1 = cpu_to_le32(VHUB_DSC1_IN_SET_LEN(chunk)); + if (req->last_desc >= 0 || !ast_vhub_count_free_descs(ep)) + desc->w1 |= cpu_to_le32(VHUB_DSC1_IN_INTERRUPT); + + /* Account packet */ + req->act_count = act = act + chunk; + } + + if (likely(desc)) + vhub_dma_workaround(desc); + + /* Tell HW about new descriptors */ + writel(VHUB_EP_DMA_SET_CPU_WPTR(ep->epn.d_next), + ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + + EPVDBG(ep, "HW kicked, d_next=%d dstat=%08x\n", + ep->epn.d_next, readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS)); +} + +static void ast_vhub_epn_handle_ack_desc(struct ast_vhub_ep *ep) +{ + struct ast_vhub_req *req; + unsigned int len, d_last; + u32 stat, stat1; + + /* Read EP status, workaround HW race */ + do { + stat = readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + stat1 = readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + } while(stat != stat1); + + /* Extract RPTR */ + d_last = VHUB_EP_DMA_RPTR(stat); + + /* Grab current request if any */ + req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue); + + EPVDBG(ep, "ACK status=%08x is_in=%d ep->d_last=%d..%d\n", + stat, ep->epn.is_in, ep->epn.d_last, d_last); + + /* Check all completed descriptors */ + while (ep->epn.d_last != d_last) { + struct ast_vhub_desc *desc; + unsigned int d_num; + bool is_last_desc; + + /* Grab next completed descriptor */ + d_num = ep->epn.d_last; + desc = &ep->epn.descs[d_num]; + ep->epn.d_last = (d_num + 1) & (AST_VHUB_DESCS_COUNT - 1); + + /* Grab len out of descriptor */ + len = VHUB_DSC1_IN_LEN(le32_to_cpu(desc->w1)); + + EPVDBG(ep, " desc %d len=%d req=%p (act=%d)\n", + d_num, len, req, req ? req->active : 0); + + /* If no active request pending, move on */ + if (!req || !req->active) + continue; + + /* Adjust size */ + req->req.actual += len; + + /* Is that the last chunk ? */ + is_last_desc = req->last_desc == d_num; + CHECK(ep, is_last_desc == (len < ep->ep.maxpacket || + (req->req.actual >= req->req.length && + !req->req.zero)), + "Last packet discrepancy: last_desc=%d len=%d r.act=%d " + "r.len=%d r.zero=%d mp=%d\n", + is_last_desc, len, req->req.actual, req->req.length, + req->req.zero, ep->ep.maxpacket); + + if (is_last_desc) { + /* + * Because we can only have one request at a time + * in our descriptor list in this implementation, + * d_last and ep->d_last should now be equal + */ + CHECK(ep, d_last == ep->epn.d_last, + "DMA read ptr mismatch %d vs %d\n", + d_last, ep->epn.d_last); + + /* Note: done will drop and re-acquire the lock */ + ast_vhub_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, + struct ast_vhub_req, + queue); + break; + } + } + + /* More work ? */ + if (req) + ast_vhub_epn_kick_desc(ep, req); +} + +void ast_vhub_epn_ack_irq(struct ast_vhub_ep *ep) +{ + if (ep->epn.desc_mode) + ast_vhub_epn_handle_ack_desc(ep); + else + ast_vhub_epn_handle_ack(ep); +} + +static int ast_vhub_epn_queue(struct usb_ep* u_ep, struct usb_request *u_req, + gfp_t gfp_flags) +{ + struct ast_vhub_req *req = to_ast_req(u_req); + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + struct ast_vhub *vhub = ep->vhub; + unsigned long flags; + bool empty; + int rc; + + /* Paranoid checks */ + if (!u_req || !u_req->complete || !u_req->buf) { + dev_warn(&vhub->pdev->dev, "Bogus EPn request ! u_req=%p\n", u_req); + if (u_req) { + dev_warn(&vhub->pdev->dev, "complete=%p internal=%d\n", + u_req->complete, req->internal); + } + return -EINVAL; + } + + /* Endpoint enabled ? */ + if (!ep->epn.enabled || !u_ep->desc || !ep->dev || !ep->d_idx || + !ep->dev->enabled) { + EPDBG(ep, "Enqueuing request on wrong or disabled EP\n"); + return -ESHUTDOWN; + } + + /* Map request for DMA if possible. For now, the rule for DMA is + * that: + * + * * For single stage mode (no descriptors): + * + * - The buffer is aligned to a 8 bytes boundary (HW requirement) + * - For a OUT endpoint, the request size is a multiple of the EP + * packet size (otherwise the controller will DMA past the end + * of the buffer if the host is sending a too long packet). + * + * * For descriptor mode (tx only for now), always. + * + * We could relax the latter by making the decision to use the bounce + * buffer based on the size of a given *segment* of the request rather + * than the whole request. + */ + if (ep->epn.desc_mode || + ((((unsigned long)u_req->buf & 7) == 0) && + (ep->epn.is_in || !(u_req->length & (u_ep->maxpacket - 1))))) { + rc = usb_gadget_map_request_by_dev(&vhub->pdev->dev, u_req, + ep->epn.is_in); + if (rc) { + dev_warn(&vhub->pdev->dev, + "Request mapping failure %d\n", rc); + return rc; + } + } else + u_req->dma = 0; + + EPVDBG(ep, "enqueue req @%p\n", req); + EPVDBG(ep, " l=%d dma=0x%x zero=%d noshort=%d noirq=%d is_in=%d\n", + u_req->length, (u32)u_req->dma, u_req->zero, + u_req->short_not_ok, u_req->no_interrupt, + ep->epn.is_in); + + /* Initialize request progress fields */ + u_req->status = -EINPROGRESS; + u_req->actual = 0; + req->act_count = 0; + req->active = false; + req->last_desc = -1; + spin_lock_irqsave(&vhub->lock, flags); + empty = list_empty(&ep->queue); + + /* Add request to list and kick processing if empty */ + list_add_tail(&req->queue, &ep->queue); + if (empty) { + if (ep->epn.desc_mode) + ast_vhub_epn_kick_desc(ep, req); + else + ast_vhub_epn_kick(ep, req); + } + spin_unlock_irqrestore(&vhub->lock, flags); + + return 0; +} + +static void ast_vhub_stop_active_req(struct ast_vhub_ep *ep, + bool restart_ep) +{ + u32 state, reg, loops; + + /* Stop DMA activity */ + if (ep->epn.desc_mode) + writel(VHUB_EP_DMA_CTRL_RESET, ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + else + writel(0, ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + + /* Wait for it to complete */ + for (loops = 0; loops < 1000; loops++) { + state = readl(ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + state = VHUB_EP_DMA_PROC_STATUS(state); + if (state == EP_DMA_PROC_RX_IDLE || + state == EP_DMA_PROC_TX_IDLE) + break; + udelay(1); + } + if (loops >= 1000) + dev_warn(&ep->vhub->pdev->dev, "Timeout waiting for DMA\n"); + + /* If we don't have to restart the endpoint, that's it */ + if (!restart_ep) + return; + + /* Restart the endpoint */ + if (ep->epn.desc_mode) { + /* + * Take out descriptors by resetting the DMA read + * pointer to be equal to the CPU write pointer. + * + * Note: If we ever support creating descriptors for + * requests that aren't the head of the queue, we + * may have to do something more complex here, + * especially if the request being taken out is + * not the current head descriptors. + */ + reg = VHUB_EP_DMA_SET_RPTR(ep->epn.d_next) | + VHUB_EP_DMA_SET_CPU_WPTR(ep->epn.d_next); + writel(reg, ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + + /* Then turn it back on */ + writel(ep->epn.dma_conf, + ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + } else { + /* Single mode: just turn it back on */ + writel(ep->epn.dma_conf, + ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + } +} + +static int ast_vhub_epn_dequeue(struct usb_ep* u_ep, struct usb_request *u_req) +{ + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + struct ast_vhub *vhub = ep->vhub; + struct ast_vhub_req *req = NULL, *iter; + unsigned long flags; + int rc = -EINVAL; + + spin_lock_irqsave(&vhub->lock, flags); + + /* Make sure it's actually queued on this endpoint */ + list_for_each_entry(iter, &ep->queue, queue) { + if (&iter->req != u_req) + continue; + req = iter; + break; + } + + if (req) { + EPVDBG(ep, "dequeue req @%p active=%d\n", + req, req->active); + if (req->active) + ast_vhub_stop_active_req(ep, true); + ast_vhub_done(ep, req, -ECONNRESET); + rc = 0; + } + + spin_unlock_irqrestore(&vhub->lock, flags); + return rc; +} + +void ast_vhub_update_epn_stall(struct ast_vhub_ep *ep) +{ + u32 reg; + + if (WARN_ON(ep->d_idx == 0)) + return; + reg = readl(ep->epn.regs + AST_VHUB_EP_CONFIG); + if (ep->epn.stalled || ep->epn.wedged) + reg |= VHUB_EP_CFG_STALL_CTRL; + else + reg &= ~VHUB_EP_CFG_STALL_CTRL; + writel(reg, ep->epn.regs + AST_VHUB_EP_CONFIG); + + if (!ep->epn.stalled && !ep->epn.wedged) + writel(VHUB_EP_TOGGLE_SET_EPNUM(ep->epn.g_idx), + ep->vhub->regs + AST_VHUB_EP_TOGGLE); +} + +static int ast_vhub_set_halt_and_wedge(struct usb_ep* u_ep, bool halt, + bool wedge) +{ + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + struct ast_vhub *vhub = ep->vhub; + unsigned long flags; + + EPDBG(ep, "Set halt (%d) & wedge (%d)\n", halt, wedge); + + if (!u_ep || !u_ep->desc) + return -EINVAL; + if (ep->d_idx == 0) + return 0; + if (ep->epn.is_iso) + return -EOPNOTSUPP; + + spin_lock_irqsave(&vhub->lock, flags); + + /* Fail with still-busy IN endpoints */ + if (halt && ep->epn.is_in && !list_empty(&ep->queue)) { + spin_unlock_irqrestore(&vhub->lock, flags); + return -EAGAIN; + } + ep->epn.stalled = halt; + ep->epn.wedged = wedge; + ast_vhub_update_epn_stall(ep); + + spin_unlock_irqrestore(&vhub->lock, flags); + + return 0; +} + +static int ast_vhub_epn_set_halt(struct usb_ep *u_ep, int value) +{ + return ast_vhub_set_halt_and_wedge(u_ep, value != 0, false); +} + +static int ast_vhub_epn_set_wedge(struct usb_ep *u_ep) +{ + return ast_vhub_set_halt_and_wedge(u_ep, true, true); +} + +static int ast_vhub_epn_disable(struct usb_ep* u_ep) +{ + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + struct ast_vhub *vhub = ep->vhub; + unsigned long flags; + u32 imask, ep_ier; + + EPDBG(ep, "Disabling !\n"); + + spin_lock_irqsave(&vhub->lock, flags); + + ep->epn.enabled = false; + + /* Stop active DMA if any */ + ast_vhub_stop_active_req(ep, false); + + /* Disable endpoint */ + writel(0, ep->epn.regs + AST_VHUB_EP_CONFIG); + + /* Disable ACK interrupt */ + imask = VHUB_EP_IRQ(ep->epn.g_idx); + ep_ier = readl(vhub->regs + AST_VHUB_EP_ACK_IER); + ep_ier &= ~imask; + writel(ep_ier, vhub->regs + AST_VHUB_EP_ACK_IER); + writel(imask, vhub->regs + AST_VHUB_EP_ACK_ISR); + + /* Nuke all pending requests */ + ast_vhub_nuke(ep, -ESHUTDOWN); + + /* No more descriptor associated with request */ + ep->ep.desc = NULL; + + spin_unlock_irqrestore(&vhub->lock, flags); + + return 0; +} + +static int ast_vhub_epn_enable(struct usb_ep* u_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + struct ast_vhub_dev *dev; + struct ast_vhub *vhub; + u16 maxpacket, type; + unsigned long flags; + u32 ep_conf, ep_ier, imask; + + /* Check arguments */ + if (!u_ep || !desc) + return -EINVAL; + + maxpacket = usb_endpoint_maxp(desc); + if (!ep->d_idx || !ep->dev || + desc->bDescriptorType != USB_DT_ENDPOINT || + maxpacket == 0 || maxpacket > ep->ep.maxpacket) { + EPDBG(ep, "Invalid EP enable,d_idx=%d,dev=%p,type=%d,mp=%d/%d\n", + ep->d_idx, ep->dev, desc->bDescriptorType, + maxpacket, ep->ep.maxpacket); + return -EINVAL; + } + if (ep->d_idx != usb_endpoint_num(desc)) { + EPDBG(ep, "EP number mismatch !\n"); + return -EINVAL; + } + + if (ep->epn.enabled) { + EPDBG(ep, "Already enabled\n"); + return -EBUSY; + } + dev = ep->dev; + vhub = ep->vhub; + + /* Check device state */ + if (!dev->driver) { + EPDBG(ep, "Bogus device state: driver=%p speed=%d\n", + dev->driver, dev->gadget.speed); + return -ESHUTDOWN; + } + + /* Grab some info from the descriptor */ + ep->epn.is_in = usb_endpoint_dir_in(desc); + ep->ep.maxpacket = maxpacket; + type = usb_endpoint_type(desc); + ep->epn.d_next = ep->epn.d_last = 0; + ep->epn.is_iso = false; + ep->epn.stalled = false; + ep->epn.wedged = false; + + EPDBG(ep, "Enabling [%s] %s num %d maxpacket=%d\n", + ep->epn.is_in ? "in" : "out", usb_ep_type_string(type), + usb_endpoint_num(desc), maxpacket); + + /* Can we use DMA descriptor mode ? */ + ep->epn.desc_mode = ep->epn.descs && ep->epn.is_in; + if (ep->epn.desc_mode) + memset(ep->epn.descs, 0, 8 * AST_VHUB_DESCS_COUNT); + + /* + * Large send function can send up to 8 packets from + * one descriptor with a limit of 4095 bytes. + */ + ep->epn.chunk_max = ep->ep.maxpacket; + if (ep->epn.is_in) { + ep->epn.chunk_max <<= 3; + while (ep->epn.chunk_max > 4095) + ep->epn.chunk_max -= ep->ep.maxpacket; + } + + switch(type) { + case USB_ENDPOINT_XFER_CONTROL: + EPDBG(ep, "Only one control endpoint\n"); + return -EINVAL; + case USB_ENDPOINT_XFER_INT: + ep_conf = VHUB_EP_CFG_SET_TYPE(EP_TYPE_INT); + break; + case USB_ENDPOINT_XFER_BULK: + ep_conf = VHUB_EP_CFG_SET_TYPE(EP_TYPE_BULK); + break; + case USB_ENDPOINT_XFER_ISOC: + ep_conf = VHUB_EP_CFG_SET_TYPE(EP_TYPE_ISO); + ep->epn.is_iso = true; + break; + default: + return -EINVAL; + } + + /* Encode the rest of the EP config register */ + if (maxpacket < 1024) + ep_conf |= VHUB_EP_CFG_SET_MAX_PKT(maxpacket); + if (!ep->epn.is_in) + ep_conf |= VHUB_EP_CFG_DIR_OUT; + ep_conf |= VHUB_EP_CFG_SET_EP_NUM(usb_endpoint_num(desc)); + ep_conf |= VHUB_EP_CFG_ENABLE; + ep_conf |= VHUB_EP_CFG_SET_DEV(dev->index + 1); + EPVDBG(ep, "config=%08x\n", ep_conf); + + spin_lock_irqsave(&vhub->lock, flags); + + /* Disable HW and reset DMA */ + writel(0, ep->epn.regs + AST_VHUB_EP_CONFIG); + writel(VHUB_EP_DMA_CTRL_RESET, + ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + + /* Configure and enable */ + writel(ep_conf, ep->epn.regs + AST_VHUB_EP_CONFIG); + + if (ep->epn.desc_mode) { + /* Clear DMA status, including the DMA read ptr */ + writel(0, ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + + /* Set descriptor base */ + writel(ep->epn.descs_dma, + ep->epn.regs + AST_VHUB_EP_DESC_BASE); + + /* Set base DMA config value */ + ep->epn.dma_conf = VHUB_EP_DMA_DESC_MODE; + if (ep->epn.is_in) + ep->epn.dma_conf |= VHUB_EP_DMA_IN_LONG_MODE; + + /* First reset and disable all operations */ + writel(ep->epn.dma_conf | VHUB_EP_DMA_CTRL_RESET, + ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + + /* Enable descriptor mode */ + writel(ep->epn.dma_conf, + ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + } else { + /* Set base DMA config value */ + ep->epn.dma_conf = VHUB_EP_DMA_SINGLE_STAGE; + + /* Reset and switch to single stage mode */ + writel(ep->epn.dma_conf | VHUB_EP_DMA_CTRL_RESET, + ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + writel(ep->epn.dma_conf, + ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + writel(0, ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + } + + /* Cleanup data toggle just in case */ + writel(VHUB_EP_TOGGLE_SET_EPNUM(ep->epn.g_idx), + vhub->regs + AST_VHUB_EP_TOGGLE); + + /* Cleanup and enable ACK interrupt */ + imask = VHUB_EP_IRQ(ep->epn.g_idx); + writel(imask, vhub->regs + AST_VHUB_EP_ACK_ISR); + ep_ier = readl(vhub->regs + AST_VHUB_EP_ACK_IER); + ep_ier |= imask; + writel(ep_ier, vhub->regs + AST_VHUB_EP_ACK_IER); + + /* Woot, we are online ! */ + ep->epn.enabled = true; + + spin_unlock_irqrestore(&vhub->lock, flags); + + return 0; +} + +static void ast_vhub_epn_dispose(struct usb_ep *u_ep) +{ + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + + if (WARN_ON(!ep->dev || !ep->d_idx)) + return; + + EPDBG(ep, "Releasing endpoint\n"); + + /* Take it out of the EP list */ + list_del_init(&ep->ep.ep_list); + + /* Mark the address free in the device */ + ep->dev->epns[ep->d_idx - 1] = NULL; + + /* Free name & DMA buffers */ + kfree(ep->ep.name); + ep->ep.name = NULL; + dma_free_coherent(&ep->vhub->pdev->dev, + AST_VHUB_EPn_MAX_PACKET + + 8 * AST_VHUB_DESCS_COUNT, + ep->buf, ep->buf_dma); + ep->buf = NULL; + ep->epn.descs = NULL; + + /* Mark free */ + ep->dev = NULL; +} + +static const struct usb_ep_ops ast_vhub_epn_ops = { + .enable = ast_vhub_epn_enable, + .disable = ast_vhub_epn_disable, + .dispose = ast_vhub_epn_dispose, + .queue = ast_vhub_epn_queue, + .dequeue = ast_vhub_epn_dequeue, + .set_halt = ast_vhub_epn_set_halt, + .set_wedge = ast_vhub_epn_set_wedge, + .alloc_request = ast_vhub_alloc_request, + .free_request = ast_vhub_free_request, +}; + +struct ast_vhub_ep *ast_vhub_alloc_epn(struct ast_vhub_dev *d, u8 addr) +{ + struct ast_vhub *vhub = d->vhub; + struct ast_vhub_ep *ep; + unsigned long flags; + int i; + + /* Find a free one (no device) */ + spin_lock_irqsave(&vhub->lock, flags); + for (i = 0; i < vhub->max_epns; i++) + if (vhub->epns[i].dev == NULL) + break; + if (i >= vhub->max_epns) { + spin_unlock_irqrestore(&vhub->lock, flags); + return NULL; + } + + /* Set it up */ + ep = &vhub->epns[i]; + ep->dev = d; + spin_unlock_irqrestore(&vhub->lock, flags); + + DDBG(d, "Allocating gen EP %d for addr %d\n", i, addr); + INIT_LIST_HEAD(&ep->queue); + ep->d_idx = addr; + ep->vhub = vhub; + ep->ep.ops = &ast_vhub_epn_ops; + ep->ep.name = kasprintf(GFP_KERNEL, "ep%d", addr); + d->epns[addr-1] = ep; + ep->epn.g_idx = i; + ep->epn.regs = vhub->regs + 0x200 + (i * 0x10); + + ep->buf = dma_alloc_coherent(&vhub->pdev->dev, + AST_VHUB_EPn_MAX_PACKET + + 8 * AST_VHUB_DESCS_COUNT, + &ep->buf_dma, GFP_KERNEL); + if (!ep->buf) { + kfree(ep->ep.name); + ep->ep.name = NULL; + return NULL; + } + ep->epn.descs = ep->buf + AST_VHUB_EPn_MAX_PACKET; + ep->epn.descs_dma = ep->buf_dma + AST_VHUB_EPn_MAX_PACKET; + + usb_ep_set_maxpacket_limit(&ep->ep, AST_VHUB_EPn_MAX_PACKET); + list_add_tail(&ep->ep.ep_list, &d->gadget.ep_list); + ep->ep.caps.type_iso = true; + ep->ep.caps.type_bulk = true; + ep->ep.caps.type_int = true; + ep->ep.caps.dir_in = true; + ep->ep.caps.dir_out = true; + + return ep; +} diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c new file mode 100644 index 000000000..02fe1a08d --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c @@ -0,0 +1,1082 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget + * + * hub.c - virtual hub handling + * + * Copyright 2017 IBM Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vhub.h" + +/* usb 2.0 hub device descriptor + * + * A few things we may want to improve here: + * + * - We may need to indicate TT support + * - We may need a device qualifier descriptor + * as devices can pretend to be usb1 or 2 + * - Make vid/did overridable + * - make it look like usb1 if usb1 mode forced + */ +#define KERNEL_REL bin2bcd(LINUX_VERSION_MAJOR) +#define KERNEL_VER bin2bcd(LINUX_VERSION_PATCHLEVEL) + +enum { + AST_VHUB_STR_INDEX_MAX = 4, + AST_VHUB_STR_MANUF = 3, + AST_VHUB_STR_PRODUCT = 2, + AST_VHUB_STR_SERIAL = 1, +}; + +static const struct usb_device_descriptor ast_vhub_dev_desc = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_HUB, + .bDeviceSubClass = 0, + .bDeviceProtocol = 1, + .bMaxPacketSize0 = 64, + .idVendor = cpu_to_le16(0x1d6b), + .idProduct = cpu_to_le16(0x0107), + .bcdDevice = cpu_to_le16(0x0100), + .iManufacturer = AST_VHUB_STR_MANUF, + .iProduct = AST_VHUB_STR_PRODUCT, + .iSerialNumber = AST_VHUB_STR_SERIAL, + .bNumConfigurations = 1, +}; + +static const struct usb_qualifier_descriptor ast_vhub_qual_desc = { + .bLength = 0xA, + .bDescriptorType = USB_DT_DEVICE_QUALIFIER, + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_HUB, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .bRESERVED = 0, +}; + +/* + * Configuration descriptor: same comments as above + * regarding handling USB1 mode. + */ + +/* + * We don't use sizeof() as Linux definition of + * struct usb_endpoint_descriptor contains 2 + * extra bytes + */ +#define AST_VHUB_CONF_DESC_SIZE (USB_DT_CONFIG_SIZE + \ + USB_DT_INTERFACE_SIZE + \ + USB_DT_ENDPOINT_SIZE) + +static const struct ast_vhub_full_cdesc ast_vhub_conf_desc = { + .cfg = { + .bLength = USB_DT_CONFIG_SIZE, + .bDescriptorType = USB_DT_CONFIG, + .wTotalLength = cpu_to_le16(AST_VHUB_CONF_DESC_SIZE), + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = USB_CONFIG_ATT_ONE | + USB_CONFIG_ATT_SELFPOWER | + USB_CONFIG_ATT_WAKEUP, + .bMaxPower = 0, + }, + .intf = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HUB, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = 0, + }, + .ep = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x81, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(1), + .bInterval = 0x0c, + }, +}; + +#define AST_VHUB_HUB_DESC_SIZE (USB_DT_HUB_NONVAR_SIZE + 2) + +static const struct usb_hub_descriptor ast_vhub_hub_desc = { + .bDescLength = AST_VHUB_HUB_DESC_SIZE, + .bDescriptorType = USB_DT_HUB, + .bNbrPorts = AST_VHUB_NUM_PORTS, + .wHubCharacteristics = cpu_to_le16(HUB_CHAR_NO_LPSM), + .bPwrOn2PwrGood = 10, + .bHubContrCurrent = 0, + .u.hs.DeviceRemovable[0] = 0, + .u.hs.DeviceRemovable[1] = 0xff, +}; + +/* + * These strings converted to UTF-16 must be smaller than + * our EP0 buffer. + */ +static const struct usb_string ast_vhub_str_array[] = { + { + .id = AST_VHUB_STR_SERIAL, + .s = "00000000" + }, + { + .id = AST_VHUB_STR_PRODUCT, + .s = "USB Virtual Hub" + }, + { + .id = AST_VHUB_STR_MANUF, + .s = "Aspeed" + }, + { } +}; + +static const struct usb_gadget_strings ast_vhub_strings = { + .language = 0x0409, + .strings = (struct usb_string *)ast_vhub_str_array +}; + +static int ast_vhub_hub_dev_status(struct ast_vhub_ep *ep, + u16 wIndex, u16 wValue) +{ + u8 st0; + + EPDBG(ep, "GET_STATUS(dev)\n"); + + /* + * Mark it as self-powered, I doubt the BMC is powered off + * the USB bus ... + */ + st0 = 1 << USB_DEVICE_SELF_POWERED; + + /* + * Need to double check how remote wakeup actually works + * on that chip and what triggers it. + */ + if (ep->vhub->wakeup_en) + st0 |= 1 << USB_DEVICE_REMOTE_WAKEUP; + + return ast_vhub_simple_reply(ep, st0, 0); +} + +static int ast_vhub_hub_ep_status(struct ast_vhub_ep *ep, + u16 wIndex, u16 wValue) +{ + int ep_num; + u8 st0 = 0; + + ep_num = wIndex & USB_ENDPOINT_NUMBER_MASK; + EPDBG(ep, "GET_STATUS(ep%d)\n", ep_num); + + /* On the hub we have only EP 0 and 1 */ + if (ep_num == 1) { + if (ep->vhub->ep1_stalled) + st0 |= 1 << USB_ENDPOINT_HALT; + } else if (ep_num != 0) + return std_req_stall; + + return ast_vhub_simple_reply(ep, st0, 0); +} + +static int ast_vhub_hub_dev_feature(struct ast_vhub_ep *ep, + u16 wIndex, u16 wValue, + bool is_set) +{ + u32 val; + + EPDBG(ep, "%s_FEATURE(dev val=%02x)\n", + is_set ? "SET" : "CLEAR", wValue); + + if (wValue == USB_DEVICE_REMOTE_WAKEUP) { + ep->vhub->wakeup_en = is_set; + EPDBG(ep, "Hub remote wakeup %s\n", + str_enabled_disabled(is_set)); + return std_req_complete; + } + + if (wValue == USB_DEVICE_TEST_MODE) { + val = readl(ep->vhub->regs + AST_VHUB_CTRL); + val &= ~GENMASK(10, 8); + val |= VHUB_CTRL_SET_TEST_MODE((wIndex >> 8) & 0x7); + writel(val, ep->vhub->regs + AST_VHUB_CTRL); + + return std_req_complete; + } + + return std_req_stall; +} + +static int ast_vhub_hub_ep_feature(struct ast_vhub_ep *ep, + u16 wIndex, u16 wValue, + bool is_set) +{ + int ep_num; + u32 reg; + + ep_num = wIndex & USB_ENDPOINT_NUMBER_MASK; + EPDBG(ep, "%s_FEATURE(ep%d val=%02x)\n", + is_set ? "SET" : "CLEAR", ep_num, wValue); + + if (ep_num > 1) + return std_req_stall; + if (wValue != USB_ENDPOINT_HALT) + return std_req_stall; + if (ep_num == 0) + return std_req_complete; + + EPDBG(ep, "%s stall on EP 1\n", + is_set ? "setting" : "clearing"); + + ep->vhub->ep1_stalled = is_set; + reg = readl(ep->vhub->regs + AST_VHUB_EP1_CTRL); + if (is_set) { + reg |= VHUB_EP1_CTRL_STALL; + } else { + reg &= ~VHUB_EP1_CTRL_STALL; + reg |= VHUB_EP1_CTRL_RESET_TOGGLE; + } + writel(reg, ep->vhub->regs + AST_VHUB_EP1_CTRL); + + return std_req_complete; +} + +static int ast_vhub_rep_desc(struct ast_vhub_ep *ep, + u8 desc_type, u16 len) +{ + size_t dsize; + struct ast_vhub *vhub = ep->vhub; + + EPDBG(ep, "GET_DESCRIPTOR(type:%d)\n", desc_type); + + /* + * Copy first to EP buffer and send from there, so + * we can do some in-place patching if needed. We know + * the EP buffer is big enough but ensure that doesn't + * change. We do that now rather than later after we + * have checked sizes etc... to avoid a gcc bug where + * it thinks len is constant and barfs about read + * overflows in memcpy. + */ + switch(desc_type) { + case USB_DT_DEVICE: + dsize = USB_DT_DEVICE_SIZE; + memcpy(ep->buf, &vhub->vhub_dev_desc, dsize); + BUILD_BUG_ON(dsize > sizeof(vhub->vhub_dev_desc)); + BUILD_BUG_ON(USB_DT_DEVICE_SIZE >= AST_VHUB_EP0_MAX_PACKET); + break; + case USB_DT_OTHER_SPEED_CONFIG: + case USB_DT_CONFIG: + dsize = AST_VHUB_CONF_DESC_SIZE; + memcpy(ep->buf, &vhub->vhub_conf_desc, dsize); + ((u8 *)ep->buf)[1] = desc_type; + BUILD_BUG_ON(dsize > sizeof(vhub->vhub_conf_desc)); + BUILD_BUG_ON(AST_VHUB_CONF_DESC_SIZE >= AST_VHUB_EP0_MAX_PACKET); + break; + case USB_DT_HUB: + dsize = AST_VHUB_HUB_DESC_SIZE; + memcpy(ep->buf, &vhub->vhub_hub_desc, dsize); + BUILD_BUG_ON(dsize > sizeof(vhub->vhub_hub_desc)); + BUILD_BUG_ON(AST_VHUB_HUB_DESC_SIZE >= AST_VHUB_EP0_MAX_PACKET); + break; + case USB_DT_DEVICE_QUALIFIER: + dsize = sizeof(vhub->vhub_qual_desc); + memcpy(ep->buf, &vhub->vhub_qual_desc, dsize); + break; + default: + return std_req_stall; + } + + /* Crop requested length */ + if (len > dsize) + len = dsize; + + /* Shoot it from the EP buffer */ + return ast_vhub_reply(ep, NULL, len); +} + +static struct usb_gadget_strings* +ast_vhub_str_of_container(struct usb_gadget_string_container *container) +{ + return (struct usb_gadget_strings *)container->stash; +} + +static int ast_vhub_collect_languages(struct ast_vhub *vhub, void *buf, + size_t size) +{ + int rc, hdr_len, nlangs, max_langs; + struct usb_gadget_strings *lang_str; + struct usb_gadget_string_container *container; + struct usb_string_descriptor *sdesc = buf; + + nlangs = 0; + hdr_len = sizeof(struct usb_descriptor_header); + max_langs = (size - hdr_len) / sizeof(sdesc->wData[0]); + list_for_each_entry(container, &vhub->vhub_str_desc, list) { + if (nlangs >= max_langs) + break; + + lang_str = ast_vhub_str_of_container(container); + sdesc->wData[nlangs++] = cpu_to_le16(lang_str->language); + } + + rc = hdr_len + nlangs * sizeof(sdesc->wData[0]); + sdesc->bLength = rc; + sdesc->bDescriptorType = USB_DT_STRING; + + return rc; +} + +static struct usb_gadget_strings *ast_vhub_lookup_string(struct ast_vhub *vhub, + u16 lang_id) +{ + struct usb_gadget_strings *lang_str; + struct usb_gadget_string_container *container; + + list_for_each_entry(container, &vhub->vhub_str_desc, list) { + lang_str = ast_vhub_str_of_container(container); + if (lang_str->language == lang_id) + return lang_str; + } + + return NULL; +} + +static int ast_vhub_rep_string(struct ast_vhub_ep *ep, + u8 string_id, u16 lang_id, + u16 len) +{ + int rc; + u8 buf[256]; + struct ast_vhub *vhub = ep->vhub; + struct usb_gadget_strings *lang_str; + + if (string_id == 0) { + rc = ast_vhub_collect_languages(vhub, buf, sizeof(buf)); + } else { + lang_str = ast_vhub_lookup_string(vhub, lang_id); + if (!lang_str) + return std_req_stall; + + rc = usb_gadget_get_string(lang_str, string_id, buf); + } + + if (rc < 0 || rc >= AST_VHUB_EP0_MAX_PACKET) + return std_req_stall; + + /* Shoot it from the EP buffer */ + memcpy(ep->buf, buf, rc); + return ast_vhub_reply(ep, NULL, min_t(u16, rc, len)); +} + +enum std_req_rc ast_vhub_std_hub_request(struct ast_vhub_ep *ep, + struct usb_ctrlrequest *crq) +{ + struct ast_vhub *vhub = ep->vhub; + u16 wValue, wIndex, wLength; + + wValue = le16_to_cpu(crq->wValue); + wIndex = le16_to_cpu(crq->wIndex); + wLength = le16_to_cpu(crq->wLength); + + /* First packet, grab speed */ + if (vhub->speed == USB_SPEED_UNKNOWN) { + u32 ustat = readl(vhub->regs + AST_VHUB_USBSTS); + if (ustat & VHUB_USBSTS_HISPEED) + vhub->speed = USB_SPEED_HIGH; + else + vhub->speed = USB_SPEED_FULL; + UDCDBG(vhub, "USB status=%08x speed=%s\n", ustat, + vhub->speed == USB_SPEED_HIGH ? "high" : "full"); + } + + switch ((crq->bRequestType << 8) | crq->bRequest) { + /* SET_ADDRESS */ + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + EPDBG(ep, "SET_ADDRESS: Got address %x\n", wValue); + writel(wValue, vhub->regs + AST_VHUB_CONF); + return std_req_complete; + + /* GET_STATUS */ + case DeviceRequest | USB_REQ_GET_STATUS: + return ast_vhub_hub_dev_status(ep, wIndex, wValue); + case InterfaceRequest | USB_REQ_GET_STATUS: + return ast_vhub_simple_reply(ep, 0, 0); + case EndpointRequest | USB_REQ_GET_STATUS: + return ast_vhub_hub_ep_status(ep, wIndex, wValue); + + /* SET/CLEAR_FEATURE */ + case DeviceOutRequest | USB_REQ_SET_FEATURE: + return ast_vhub_hub_dev_feature(ep, wIndex, wValue, true); + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + return ast_vhub_hub_dev_feature(ep, wIndex, wValue, false); + case EndpointOutRequest | USB_REQ_SET_FEATURE: + return ast_vhub_hub_ep_feature(ep, wIndex, wValue, true); + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + return ast_vhub_hub_ep_feature(ep, wIndex, wValue, false); + + /* GET/SET_CONFIGURATION */ + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + return ast_vhub_simple_reply(ep, 1); + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + if (wValue != 1) + return std_req_stall; + return std_req_complete; + + /* GET_DESCRIPTOR */ + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch (wValue >> 8) { + case USB_DT_DEVICE: + case USB_DT_CONFIG: + case USB_DT_DEVICE_QUALIFIER: + case USB_DT_OTHER_SPEED_CONFIG: + return ast_vhub_rep_desc(ep, wValue >> 8, + wLength); + case USB_DT_STRING: + return ast_vhub_rep_string(ep, wValue & 0xff, + wIndex, wLength); + } + return std_req_stall; + + /* GET/SET_INTERFACE */ + case DeviceRequest | USB_REQ_GET_INTERFACE: + return ast_vhub_simple_reply(ep, 0); + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + if (wValue != 0 || wIndex != 0) + return std_req_stall; + return std_req_complete; + } + return std_req_stall; +} + +static void ast_vhub_update_hub_ep1(struct ast_vhub *vhub, + unsigned int port) +{ + /* Update HW EP1 response */ + u32 reg = readl(vhub->regs + AST_VHUB_EP1_STS_CHG); + u32 pmask = (1 << (port + 1)); + if (vhub->ports[port].change) + reg |= pmask; + else + reg &= ~pmask; + writel(reg, vhub->regs + AST_VHUB_EP1_STS_CHG); +} + +static void ast_vhub_change_port_stat(struct ast_vhub *vhub, + unsigned int port, + u16 clr_flags, + u16 set_flags, + bool set_c) +{ + struct ast_vhub_port *p = &vhub->ports[port]; + u16 prev; + + /* Update port status */ + prev = p->status; + p->status = (prev & ~clr_flags) | set_flags; + DDBG(&p->dev, "port %d status %04x -> %04x (C=%d)\n", + port + 1, prev, p->status, set_c); + + /* Update change bits if needed */ + if (set_c) { + u16 chg = p->status ^ prev; + + /* Only these are relevant for change */ + chg &= 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 | + USB_PORT_STAT_C_L1; + + /* + * We only set USB_PORT_STAT_C_ENABLE if we are disabling + * the port as per USB spec, otherwise MacOS gets upset + */ + if (p->status & USB_PORT_STAT_ENABLE) + chg &= ~USB_PORT_STAT_C_ENABLE; + + p->change = chg; + ast_vhub_update_hub_ep1(vhub, port); + } +} + +static void ast_vhub_send_host_wakeup(struct ast_vhub *vhub) +{ + u32 reg = readl(vhub->regs + AST_VHUB_CTRL); + UDCDBG(vhub, "Waking up host !\n"); + reg |= VHUB_CTRL_MANUAL_REMOTE_WAKEUP; + writel(reg, vhub->regs + AST_VHUB_CTRL); +} + +void ast_vhub_device_connect(struct ast_vhub *vhub, + unsigned int port, bool on) +{ + if (on) + ast_vhub_change_port_stat(vhub, port, 0, + USB_PORT_STAT_CONNECTION, true); + else + ast_vhub_change_port_stat(vhub, port, + USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_ENABLE, + 0, true); + + /* + * If the hub is set to wakup the host on connection events + * then send a wakeup. + */ + if (vhub->wakeup_en) + ast_vhub_send_host_wakeup(vhub); +} + +static void ast_vhub_wake_work(struct work_struct *work) +{ + struct ast_vhub *vhub = container_of(work, + struct ast_vhub, + wake_work); + unsigned long flags; + unsigned int i; + + /* + * Wake all sleeping ports. If a port is suspended by + * the host suspend (without explicit state suspend), + * we let the normal host wake path deal with it later. + */ + spin_lock_irqsave(&vhub->lock, flags); + for (i = 0; i < vhub->max_ports; i++) { + struct ast_vhub_port *p = &vhub->ports[i]; + + if (!(p->status & USB_PORT_STAT_SUSPEND)) + continue; + ast_vhub_change_port_stat(vhub, i, + USB_PORT_STAT_SUSPEND, + 0, true); + ast_vhub_dev_resume(&p->dev); + } + ast_vhub_send_host_wakeup(vhub); + spin_unlock_irqrestore(&vhub->lock, flags); +} + +void ast_vhub_hub_wake_all(struct ast_vhub *vhub) +{ + /* + * A device is trying to wake the world, because this + * can recurse into the device, we break the call chain + * using a work queue + */ + schedule_work(&vhub->wake_work); +} + +static void ast_vhub_port_reset(struct ast_vhub *vhub, u8 port) +{ + struct ast_vhub_port *p = &vhub->ports[port]; + u16 set, clr, speed; + + /* First mark disabled */ + ast_vhub_change_port_stat(vhub, port, + USB_PORT_STAT_ENABLE | + USB_PORT_STAT_SUSPEND, + USB_PORT_STAT_RESET, + false); + + if (!p->dev.driver) + return; + + /* + * This will either "start" the port or reset the + * device if already started... + */ + ast_vhub_dev_reset(&p->dev); + + /* Grab the right speed */ + speed = p->dev.driver->max_speed; + if (speed == USB_SPEED_UNKNOWN || speed > vhub->speed) + speed = vhub->speed; + + switch (speed) { + case USB_SPEED_LOW: + set = USB_PORT_STAT_LOW_SPEED; + clr = USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_FULL: + set = 0; + clr = USB_PORT_STAT_LOW_SPEED | + USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_HIGH: + set = USB_PORT_STAT_HIGH_SPEED; + clr = USB_PORT_STAT_LOW_SPEED; + break; + default: + UDCDBG(vhub, "Unsupported speed %d when" + " connecting device\n", + speed); + return; + } + clr |= USB_PORT_STAT_RESET; + set |= USB_PORT_STAT_ENABLE; + + /* This should ideally be delayed ... */ + ast_vhub_change_port_stat(vhub, port, clr, set, true); +} + +static enum std_req_rc ast_vhub_set_port_feature(struct ast_vhub_ep *ep, + u8 port, u16 feat) +{ + struct ast_vhub *vhub = ep->vhub; + struct ast_vhub_port *p; + + if (port == 0 || port > vhub->max_ports) + return std_req_stall; + port--; + p = &vhub->ports[port]; + + switch(feat) { + case USB_PORT_FEAT_SUSPEND: + if (!(p->status & USB_PORT_STAT_ENABLE)) + return std_req_complete; + ast_vhub_change_port_stat(vhub, port, + 0, USB_PORT_STAT_SUSPEND, + false); + ast_vhub_dev_suspend(&p->dev); + return std_req_complete; + case USB_PORT_FEAT_RESET: + EPDBG(ep, "Port reset !\n"); + ast_vhub_port_reset(vhub, port); + return std_req_complete; + case USB_PORT_FEAT_POWER: + /* + * On Power-on, we mark the connected flag changed, + * if there's a connected device, some hosts will + * otherwise fail to detect it. + */ + if (p->status & USB_PORT_STAT_CONNECTION) { + p->change |= USB_PORT_STAT_C_CONNECTION; + ast_vhub_update_hub_ep1(vhub, port); + } + return std_req_complete; + case USB_PORT_FEAT_TEST: + case USB_PORT_FEAT_INDICATOR: + /* We don't do anything with these */ + return std_req_complete; + } + return std_req_stall; +} + +static enum std_req_rc ast_vhub_clr_port_feature(struct ast_vhub_ep *ep, + u8 port, u16 feat) +{ + struct ast_vhub *vhub = ep->vhub; + struct ast_vhub_port *p; + + if (port == 0 || port > vhub->max_ports) + return std_req_stall; + port--; + p = &vhub->ports[port]; + + switch(feat) { + case USB_PORT_FEAT_ENABLE: + ast_vhub_change_port_stat(vhub, port, + USB_PORT_STAT_ENABLE | + USB_PORT_STAT_SUSPEND, 0, + false); + ast_vhub_dev_suspend(&p->dev); + return std_req_complete; + case USB_PORT_FEAT_SUSPEND: + if (!(p->status & USB_PORT_STAT_SUSPEND)) + return std_req_complete; + ast_vhub_change_port_stat(vhub, port, + USB_PORT_STAT_SUSPEND, 0, + false); + ast_vhub_dev_resume(&p->dev); + return std_req_complete; + case USB_PORT_FEAT_POWER: + /* We don't do power control */ + return std_req_complete; + case USB_PORT_FEAT_INDICATOR: + /* We don't have indicators */ + return std_req_complete; + case USB_PORT_FEAT_C_CONNECTION: + case USB_PORT_FEAT_C_ENABLE: + case USB_PORT_FEAT_C_SUSPEND: + case USB_PORT_FEAT_C_OVER_CURRENT: + case USB_PORT_FEAT_C_RESET: + /* Clear state-change feature */ + p->change &= ~(1u << (feat - 16)); + ast_vhub_update_hub_ep1(vhub, port); + return std_req_complete; + } + return std_req_stall; +} + +static enum std_req_rc ast_vhub_get_port_stat(struct ast_vhub_ep *ep, + u8 port) +{ + struct ast_vhub *vhub = ep->vhub; + u16 stat, chg; + + if (port == 0 || port > vhub->max_ports) + return std_req_stall; + port--; + + stat = vhub->ports[port].status; + chg = vhub->ports[port].change; + + /* We always have power */ + stat |= USB_PORT_STAT_POWER; + + EPDBG(ep, " port status=%04x change=%04x\n", stat, chg); + + return ast_vhub_simple_reply(ep, + stat & 0xff, + stat >> 8, + chg & 0xff, + chg >> 8); +} + +enum std_req_rc ast_vhub_class_hub_request(struct ast_vhub_ep *ep, + struct usb_ctrlrequest *crq) +{ + u16 wValue, wIndex, wLength; + + wValue = le16_to_cpu(crq->wValue); + wIndex = le16_to_cpu(crq->wIndex); + wLength = le16_to_cpu(crq->wLength); + + switch ((crq->bRequestType << 8) | crq->bRequest) { + case GetHubStatus: + EPDBG(ep, "GetHubStatus\n"); + return ast_vhub_simple_reply(ep, 0, 0, 0, 0); + case GetPortStatus: + EPDBG(ep, "GetPortStatus(%d)\n", wIndex & 0xff); + return ast_vhub_get_port_stat(ep, wIndex & 0xf); + case GetHubDescriptor: + if (wValue != (USB_DT_HUB << 8)) + return std_req_stall; + EPDBG(ep, "GetHubDescriptor(%d)\n", wIndex & 0xff); + return ast_vhub_rep_desc(ep, USB_DT_HUB, wLength); + case SetHubFeature: + case ClearHubFeature: + EPDBG(ep, "Get/SetHubFeature(%d)\n", wValue); + /* No feature, just complete the requests */ + if (wValue == C_HUB_LOCAL_POWER || + wValue == C_HUB_OVER_CURRENT) + return std_req_complete; + return std_req_stall; + case SetPortFeature: + EPDBG(ep, "SetPortFeature(%d,%d)\n", wIndex & 0xf, wValue); + return ast_vhub_set_port_feature(ep, wIndex & 0xf, wValue); + case ClearPortFeature: + EPDBG(ep, "ClearPortFeature(%d,%d)\n", wIndex & 0xf, wValue); + return ast_vhub_clr_port_feature(ep, wIndex & 0xf, wValue); + case ClearTTBuffer: + case ResetTT: + case StopTT: + return std_req_complete; + case GetTTState: + return ast_vhub_simple_reply(ep, 0, 0, 0, 0); + default: + EPDBG(ep, "Unknown class request\n"); + } + return std_req_stall; +} + +void ast_vhub_hub_suspend(struct ast_vhub *vhub) +{ + unsigned int i; + + UDCDBG(vhub, "USB bus suspend\n"); + + if (vhub->suspended) + return; + + vhub->suspended = true; + + /* + * Forward to unsuspended ports without changing + * their connection status. + */ + for (i = 0; i < vhub->max_ports; i++) { + struct ast_vhub_port *p = &vhub->ports[i]; + + if (!(p->status & USB_PORT_STAT_SUSPEND)) + ast_vhub_dev_suspend(&p->dev); + } +} + +void ast_vhub_hub_resume(struct ast_vhub *vhub) +{ + unsigned int i; + + UDCDBG(vhub, "USB bus resume\n"); + + if (!vhub->suspended) + return; + + vhub->suspended = false; + + /* + * Forward to unsuspended ports without changing + * their connection status. + */ + for (i = 0; i < vhub->max_ports; i++) { + struct ast_vhub_port *p = &vhub->ports[i]; + + if (!(p->status & USB_PORT_STAT_SUSPEND)) + ast_vhub_dev_resume(&p->dev); + } +} + +void ast_vhub_hub_reset(struct ast_vhub *vhub) +{ + unsigned int i; + + UDCDBG(vhub, "USB bus reset\n"); + + /* + * Is the speed known ? If not we don't care, we aren't + * initialized yet and ports haven't been enabled. + */ + if (vhub->speed == USB_SPEED_UNKNOWN) + return; + + /* We aren't suspended anymore obviously */ + vhub->suspended = false; + + /* No speed set */ + vhub->speed = USB_SPEED_UNKNOWN; + + /* Wakeup not enabled anymore */ + vhub->wakeup_en = false; + + /* + * Clear all port status, disable gadgets and "suspend" + * them. They will be woken up by a port reset. + */ + for (i = 0; i < vhub->max_ports; i++) { + struct ast_vhub_port *p = &vhub->ports[i]; + + /* Only keep the connected flag */ + p->status &= USB_PORT_STAT_CONNECTION; + p->change = 0; + + /* Suspend the gadget if any */ + ast_vhub_dev_suspend(&p->dev); + } + + /* Cleanup HW */ + writel(0, vhub->regs + AST_VHUB_CONF); + writel(0, vhub->regs + AST_VHUB_EP0_CTRL); + writel(VHUB_EP1_CTRL_RESET_TOGGLE | + VHUB_EP1_CTRL_ENABLE, + vhub->regs + AST_VHUB_EP1_CTRL); + writel(0, vhub->regs + AST_VHUB_EP1_STS_CHG); +} + +static void ast_vhub_of_parse_dev_desc(struct ast_vhub *vhub, + const struct device_node *vhub_np) +{ + u16 id; + u32 data; + + if (!of_property_read_u32(vhub_np, "vhub-vendor-id", &data)) { + id = (u16)data; + vhub->vhub_dev_desc.idVendor = cpu_to_le16(id); + } + if (!of_property_read_u32(vhub_np, "vhub-product-id", &data)) { + id = (u16)data; + vhub->vhub_dev_desc.idProduct = cpu_to_le16(id); + } + if (!of_property_read_u32(vhub_np, "vhub-device-revision", &data)) { + id = (u16)data; + vhub->vhub_dev_desc.bcdDevice = cpu_to_le16(id); + } +} + +static void ast_vhub_fixup_usb1_dev_desc(struct ast_vhub *vhub) +{ + vhub->vhub_dev_desc.bcdUSB = cpu_to_le16(0x0100); + vhub->vhub_dev_desc.bDeviceProtocol = 0; +} + +static struct usb_gadget_string_container* +ast_vhub_str_container_alloc(struct ast_vhub *vhub) +{ + unsigned int size; + struct usb_string *str_array; + struct usb_gadget_strings *lang_str; + struct usb_gadget_string_container *container; + + size = sizeof(*container); + size += sizeof(struct usb_gadget_strings); + size += sizeof(struct usb_string) * AST_VHUB_STR_INDEX_MAX; + container = devm_kzalloc(&vhub->pdev->dev, size, GFP_KERNEL); + if (!container) + return ERR_PTR(-ENOMEM); + + lang_str = ast_vhub_str_of_container(container); + str_array = (struct usb_string *)(lang_str + 1); + lang_str->strings = str_array; + return container; +} + +static void ast_vhub_str_deep_copy(struct usb_gadget_strings *dest, + const struct usb_gadget_strings *src) +{ + struct usb_string *src_array = src->strings; + struct usb_string *dest_array = dest->strings; + + dest->language = src->language; + if (src_array && dest_array) { + do { + *dest_array = *src_array; + dest_array++; + src_array++; + } while (src_array->s); + } +} + +static int ast_vhub_str_alloc_add(struct ast_vhub *vhub, + const struct usb_gadget_strings *src_str) +{ + struct usb_gadget_strings *dest_str; + struct usb_gadget_string_container *container; + + container = ast_vhub_str_container_alloc(vhub); + if (IS_ERR(container)) + return PTR_ERR(container); + + dest_str = ast_vhub_str_of_container(container); + ast_vhub_str_deep_copy(dest_str, src_str); + list_add_tail(&container->list, &vhub->vhub_str_desc); + + return 0; +} + +static const struct { + const char *name; + u8 id; +} str_id_map[] = { + {"manufacturer", AST_VHUB_STR_MANUF}, + {"product", AST_VHUB_STR_PRODUCT}, + {"serial-number", AST_VHUB_STR_SERIAL}, + {}, +}; + +static int ast_vhub_of_parse_str_desc(struct ast_vhub *vhub, + const struct device_node *desc_np) +{ + u32 langid; + int ret = 0; + int i, offset; + const char *str; + struct device_node *child; + struct usb_string str_array[AST_VHUB_STR_INDEX_MAX]; + struct usb_gadget_strings lang_str = { + .strings = (struct usb_string *)str_array, + }; + + for_each_child_of_node(desc_np, child) { + if (of_property_read_u32(child, "reg", &langid)) + continue; /* no language identifier specified */ + + if (!usb_validate_langid(langid)) + continue; /* invalid language identifier */ + + lang_str.language = langid; + for (i = offset = 0; str_id_map[i].name; i++) { + str = of_get_property(child, str_id_map[i].name, NULL); + if (str) { + str_array[offset].s = str; + str_array[offset].id = str_id_map[i].id; + offset++; + } + } + str_array[offset].id = 0; + str_array[offset].s = NULL; + + ret = ast_vhub_str_alloc_add(vhub, &lang_str); + if (ret) { + of_node_put(child); + break; + } + } + + return ret; +} + +static int ast_vhub_init_desc(struct ast_vhub *vhub) +{ + int ret; + struct device_node *desc_np; + const struct device_node *vhub_np = vhub->pdev->dev.of_node; + + /* Initialize vhub Device Descriptor. */ + memcpy(&vhub->vhub_dev_desc, &ast_vhub_dev_desc, + sizeof(vhub->vhub_dev_desc)); + ast_vhub_of_parse_dev_desc(vhub, vhub_np); + if (vhub->force_usb1) + ast_vhub_fixup_usb1_dev_desc(vhub); + + /* Initialize vhub Configuration Descriptor. */ + memcpy(&vhub->vhub_conf_desc, &ast_vhub_conf_desc, + sizeof(vhub->vhub_conf_desc)); + + /* Initialize vhub Hub Descriptor. */ + memcpy(&vhub->vhub_hub_desc, &ast_vhub_hub_desc, + sizeof(vhub->vhub_hub_desc)); + vhub->vhub_hub_desc.bNbrPorts = vhub->max_ports; + + /* Initialize vhub String Descriptors. */ + INIT_LIST_HEAD(&vhub->vhub_str_desc); + desc_np = of_get_child_by_name(vhub_np, "vhub-strings"); + if (desc_np) { + ret = ast_vhub_of_parse_str_desc(vhub, desc_np); + of_node_put(desc_np); + } + else + ret = ast_vhub_str_alloc_add(vhub, &ast_vhub_strings); + + /* Initialize vhub Qualifier Descriptor. */ + memcpy(&vhub->vhub_qual_desc, &ast_vhub_qual_desc, + sizeof(vhub->vhub_qual_desc)); + + return ret; +} + +int ast_vhub_init_hub(struct ast_vhub *vhub) +{ + vhub->speed = USB_SPEED_UNKNOWN; + INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); + + return ast_vhub_init_desc(vhub); +} diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h new file mode 100644 index 000000000..aca2050e2 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h @@ -0,0 +1,566 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#ifndef __ASPEED_VHUB_H +#define __ASPEED_VHUB_H + +#include +#include + +/***************************** + * * + * VHUB register definitions * + * * + *****************************/ + +#define AST_VHUB_CTRL 0x00 /* Root Function Control & Status Register */ +#define AST_VHUB_CONF 0x04 /* Root Configuration Setting Register */ +#define AST_VHUB_IER 0x08 /* Interrupt Ctrl Register */ +#define AST_VHUB_ISR 0x0C /* Interrupt Status Register */ +#define AST_VHUB_EP_ACK_IER 0x10 /* Programmable Endpoint Pool ACK Interrupt Enable Register */ +#define AST_VHUB_EP_NACK_IER 0x14 /* Programmable Endpoint Pool NACK Interrupt Enable Register */ +#define AST_VHUB_EP_ACK_ISR 0x18 /* Programmable Endpoint Pool ACK Interrupt Status Register */ +#define AST_VHUB_EP_NACK_ISR 0x1C /* Programmable Endpoint Pool NACK Interrupt Status Register */ +#define AST_VHUB_SW_RESET 0x20 /* Device Controller Soft Reset Enable Register */ +#define AST_VHUB_USBSTS 0x24 /* USB Status Register */ +#define AST_VHUB_EP_TOGGLE 0x28 /* Programmable Endpoint Pool Data Toggle Value Set */ +#define AST_VHUB_ISO_FAIL_ACC 0x2C /* Isochronous Transaction Fail Accumulator */ +#define AST_VHUB_EP0_CTRL 0x30 /* Endpoint 0 Contrl/Status Register */ +#define AST_VHUB_EP0_DATA 0x34 /* Base Address of Endpoint 0 In/OUT Data Buffer Register */ +#define AST_VHUB_EP1_CTRL 0x38 /* Endpoint 1 Contrl/Status Register */ +#define AST_VHUB_EP1_STS_CHG 0x3C /* Endpoint 1 Status Change Bitmap Data */ +#define AST_VHUB_SETUP0 0x80 /* Root Device Setup Data Buffer0 */ +#define AST_VHUB_SETUP1 0x84 /* Root Device Setup Data Buffer1 */ + +/* Main control reg */ +#define VHUB_CTRL_PHY_CLK (1 << 31) +#define VHUB_CTRL_PHY_LOOP_TEST (1 << 25) +#define VHUB_CTRL_DN_PWN (1 << 24) +#define VHUB_CTRL_DP_PWN (1 << 23) +#define VHUB_CTRL_LONG_DESC (1 << 18) +#define VHUB_CTRL_ISO_RSP_CTRL (1 << 17) +#define VHUB_CTRL_SPLIT_IN (1 << 16) +#define VHUB_CTRL_LOOP_T_RESULT (1 << 15) +#define VHUB_CTRL_LOOP_T_STS (1 << 14) +#define VHUB_CTRL_PHY_BIST_RESULT (1 << 13) +#define VHUB_CTRL_PHY_BIST_CTRL (1 << 12) +#define VHUB_CTRL_PHY_RESET_DIS (1 << 11) +#define VHUB_CTRL_SET_TEST_MODE(x) ((x) << 8) +#define VHUB_CTRL_MANUAL_REMOTE_WAKEUP (1 << 4) +#define VHUB_CTRL_AUTO_REMOTE_WAKEUP (1 << 3) +#define VHUB_CTRL_CLK_STOP_SUSPEND (1 << 2) +#define VHUB_CTRL_FULL_SPEED_ONLY (1 << 1) +#define VHUB_CTRL_UPSTREAM_CONNECT (1 << 0) + +/* IER & ISR */ +#define VHUB_IRQ_DEV1_BIT 9 +#define VHUB_IRQ_USB_CMD_DEADLOCK (1 << 18) +#define VHUB_IRQ_EP_POOL_NAK (1 << 17) +#define VHUB_IRQ_EP_POOL_ACK_STALL (1 << 16) +#define VHUB_IRQ_DEVICE1 (1 << (VHUB_IRQ_DEV1_BIT)) +#define VHUB_IRQ_BUS_RESUME (1 << 8) +#define VHUB_IRQ_BUS_SUSPEND (1 << 7) +#define VHUB_IRQ_BUS_RESET (1 << 6) +#define VHUB_IRQ_HUB_EP1_IN_DATA_ACK (1 << 5) +#define VHUB_IRQ_HUB_EP0_IN_DATA_NAK (1 << 4) +#define VHUB_IRQ_HUB_EP0_IN_ACK_STALL (1 << 3) +#define VHUB_IRQ_HUB_EP0_OUT_NAK (1 << 2) +#define VHUB_IRQ_HUB_EP0_OUT_ACK_STALL (1 << 1) +#define VHUB_IRQ_HUB_EP0_SETUP (1 << 0) +#define VHUB_IRQ_ACK_ALL 0x1ff + +/* Downstream device IRQ mask. */ +#define VHUB_DEV_IRQ(n) (VHUB_IRQ_DEVICE1 << (n)) + +/* SW reset reg */ +#define VHUB_SW_RESET_EP_POOL (1 << 9) +#define VHUB_SW_RESET_DMA_CONTROLLER (1 << 8) +#define VHUB_SW_RESET_DEVICE5 (1 << 5) +#define VHUB_SW_RESET_DEVICE4 (1 << 4) +#define VHUB_SW_RESET_DEVICE3 (1 << 3) +#define VHUB_SW_RESET_DEVICE2 (1 << 2) +#define VHUB_SW_RESET_DEVICE1 (1 << 1) +#define VHUB_SW_RESET_ROOT_HUB (1 << 0) + +/* EP ACK/NACK IRQ masks */ +#define VHUB_EP_IRQ(n) (1 << (n)) + +/* USB status reg */ +#define VHUB_USBSTS_HISPEED (1 << 27) + +/* EP toggle */ +#define VHUB_EP_TOGGLE_VALUE (1 << 8) +#define VHUB_EP_TOGGLE_SET_EPNUM(x) ((x) & 0x1f) + +/* HUB EP0 control */ +#define VHUB_EP0_CTRL_STALL (1 << 0) +#define VHUB_EP0_TX_BUFF_RDY (1 << 1) +#define VHUB_EP0_RX_BUFF_RDY (1 << 2) +#define VHUB_EP0_RX_LEN(x) (((x) >> 16) & 0x7f) +#define VHUB_EP0_SET_TX_LEN(x) (((x) & 0x7f) << 8) + +/* HUB EP1 control */ +#define VHUB_EP1_CTRL_RESET_TOGGLE (1 << 2) +#define VHUB_EP1_CTRL_STALL (1 << 1) +#define VHUB_EP1_CTRL_ENABLE (1 << 0) + +/*********************************** + * * + * per-device register definitions * + * * + ***********************************/ +#define AST_VHUB_DEV_EN_CTRL 0x00 +#define AST_VHUB_DEV_ISR 0x04 +#define AST_VHUB_DEV_EP0_CTRL 0x08 +#define AST_VHUB_DEV_EP0_DATA 0x0c + +/* Device enable control */ +#define VHUB_DEV_EN_SET_ADDR(x) ((x) << 8) +#define VHUB_DEV_EN_ADDR_MASK ((0xff) << 8) +#define VHUB_DEV_EN_EP0_NAK_IRQEN (1 << 6) +#define VHUB_DEV_EN_EP0_IN_ACK_IRQEN (1 << 5) +#define VHUB_DEV_EN_EP0_OUT_NAK_IRQEN (1 << 4) +#define VHUB_DEV_EN_EP0_OUT_ACK_IRQEN (1 << 3) +#define VHUB_DEV_EN_EP0_SETUP_IRQEN (1 << 2) +#define VHUB_DEV_EN_SPEED_SEL_HIGH (1 << 1) +#define VHUB_DEV_EN_ENABLE_PORT (1 << 0) + +/* Interrupt status */ +#define VHUV_DEV_IRQ_EP0_IN_DATA_NACK (1 << 4) +#define VHUV_DEV_IRQ_EP0_IN_ACK_STALL (1 << 3) +#define VHUV_DEV_IRQ_EP0_OUT_DATA_NACK (1 << 2) +#define VHUV_DEV_IRQ_EP0_OUT_ACK_STALL (1 << 1) +#define VHUV_DEV_IRQ_EP0_SETUP (1 << 0) + +/* Control bits. + * + * Note: The driver relies on the bulk of those bits + * matching corresponding vHub EP0 control bits + */ +#define VHUB_DEV_EP0_CTRL_STALL VHUB_EP0_CTRL_STALL +#define VHUB_DEV_EP0_TX_BUFF_RDY VHUB_EP0_TX_BUFF_RDY +#define VHUB_DEV_EP0_RX_BUFF_RDY VHUB_EP0_RX_BUFF_RDY +#define VHUB_DEV_EP0_RX_LEN(x) VHUB_EP0_RX_LEN(x) +#define VHUB_DEV_EP0_SET_TX_LEN(x) VHUB_EP0_SET_TX_LEN(x) + +/************************************* + * * + * per-endpoint register definitions * + * * + *************************************/ + +#define AST_VHUB_EP_CONFIG 0x00 +#define AST_VHUB_EP_DMA_CTLSTAT 0x04 +#define AST_VHUB_EP_DESC_BASE 0x08 +#define AST_VHUB_EP_DESC_STATUS 0x0C + +/* EP config reg */ +#define VHUB_EP_CFG_SET_MAX_PKT(x) (((x) & 0x3ff) << 16) +#define VHUB_EP_CFG_AUTO_DATA_DISABLE (1 << 13) +#define VHUB_EP_CFG_STALL_CTRL (1 << 12) +#define VHUB_EP_CFG_SET_EP_NUM(x) (((x) & 0xf) << 8) +#define VHUB_EP_CFG_SET_TYPE(x) ((x) << 5) +#define EP_TYPE_OFF 0 +#define EP_TYPE_BULK 1 +#define EP_TYPE_INT 2 +#define EP_TYPE_ISO 3 +#define VHUB_EP_CFG_DIR_OUT (1 << 4) +#define VHUB_EP_CFG_SET_DEV(x) ((x) << 1) +#define VHUB_EP_CFG_ENABLE (1 << 0) + +/* EP DMA control */ +#define VHUB_EP_DMA_PROC_STATUS(x) (((x) >> 4) & 0xf) +#define EP_DMA_PROC_RX_IDLE 0 +#define EP_DMA_PROC_TX_IDLE 8 +#define VHUB_EP_DMA_IN_LONG_MODE (1 << 3) +#define VHUB_EP_DMA_OUT_CONTIG_MODE (1 << 3) +#define VHUB_EP_DMA_CTRL_RESET (1 << 2) +#define VHUB_EP_DMA_SINGLE_STAGE (1 << 1) +#define VHUB_EP_DMA_DESC_MODE (1 << 0) + +/* EP DMA status */ +#define VHUB_EP_DMA_SET_TX_SIZE(x) ((x) << 16) +#define VHUB_EP_DMA_TX_SIZE(x) (((x) >> 16) & 0x7ff) +#define VHUB_EP_DMA_RPTR(x) (((x) >> 8) & 0xff) +#define VHUB_EP_DMA_SET_RPTR(x) (((x) & 0xff) << 8) +#define VHUB_EP_DMA_SET_CPU_WPTR(x) (x) +#define VHUB_EP_DMA_SINGLE_KICK (1 << 0) /* WPTR = 1 for single mode */ + +/******************************* + * * + * DMA descriptors definitions * + * * + *******************************/ + +/* Desc W1 IN */ +#define VHUB_DSC1_IN_INTERRUPT (1 << 31) +#define VHUB_DSC1_IN_SPID_DATA0 (0 << 14) +#define VHUB_DSC1_IN_SPID_DATA2 (1 << 14) +#define VHUB_DSC1_IN_SPID_DATA1 (2 << 14) +#define VHUB_DSC1_IN_SPID_MDATA (3 << 14) +#define VHUB_DSC1_IN_SET_LEN(x) ((x) & 0xfff) +#define VHUB_DSC1_IN_LEN(x) ((x) & 0xfff) + +/**************************************** + * * + * Data structures and misc definitions * + * * + ****************************************/ + +/* + * AST_VHUB_NUM_GEN_EPs and AST_VHUB_NUM_PORTS are kept to avoid breaking + * existing AST2400/AST2500 platforms. AST2600 and future vhub revisions + * should define number of downstream ports and endpoints in device tree. + */ +#define AST_VHUB_NUM_GEN_EPs 15 /* Generic non-0 EPs */ +#define AST_VHUB_NUM_PORTS 5 /* vHub ports */ +#define AST_VHUB_EP0_MAX_PACKET 64 /* EP0's max packet size */ +#define AST_VHUB_EPn_MAX_PACKET 1024 /* Generic EPs max packet size */ +#define AST_VHUB_DESCS_COUNT 256 /* Use 256 descriptor mode (valid + * values are 256 and 32) + */ + +struct ast_vhub; +struct ast_vhub_dev; + +/* + * DMA descriptor (generic EPs only, currently only used + * for IN endpoints + */ +struct ast_vhub_desc { + __le32 w0; + __le32 w1; +}; + +/* A transfer request, either core-originated or internal */ +struct ast_vhub_req { + struct usb_request req; + struct list_head queue; + + /* Actual count written to descriptors (desc mode only) */ + unsigned int act_count; + + /* + * Desc number of the final packet or -1. For non-desc + * mode (or ep0), any >= 0 value means "last packet" + */ + int last_desc; + + /* Request active (pending DMAs) */ + bool active : 1; + + /* Internal request (don't call back core) */ + bool internal : 1; +}; +#define to_ast_req(__ureq) container_of(__ureq, struct ast_vhub_req, req) + +/* Current state of an EP0 */ +enum ep0_state { + ep0_state_token, + ep0_state_data, + ep0_state_status, + ep0_state_stall, +}; + +/* + * An endpoint, either generic, ep0, actual gadget EP + * or internal use vhub EP0. vhub EP1 doesn't have an + * associated structure as it's mostly HW managed. + */ +struct ast_vhub_ep { + struct usb_ep ep; + + /* Request queue */ + struct list_head queue; + + /* EP index in the device, 0 means this is an EP0 */ + unsigned int d_idx; + + /* Dev pointer or NULL for vHub EP0 */ + struct ast_vhub_dev *dev; + + /* vHub itself */ + struct ast_vhub *vhub; + + /* + * DMA buffer for EP0, fallback DMA buffer for misaligned + * OUT transfers for generic EPs + */ + void *buf; + dma_addr_t buf_dma; + + /* The rest depends on the EP type */ + union { + /* EP0 (either device or vhub) */ + struct { + /* + * EP0 registers are "similar" for + * vHub and devices but located in + * different places. + */ + void __iomem *ctlstat; + void __iomem *setup; + + /* Current state & direction */ + enum ep0_state state; + bool dir_in; + + /* Internal use request */ + struct ast_vhub_req req; + } ep0; + + /* Generic endpoint (aka EPn) */ + struct { + /* Registers */ + void __iomem *regs; + + /* Index in global pool (zero-based) */ + unsigned int g_idx; + + /* DMA Descriptors */ + struct ast_vhub_desc *descs; + dma_addr_t descs_dma; + unsigned int d_next; + unsigned int d_last; + unsigned int dma_conf; + + /* Max chunk size for IN EPs */ + unsigned int chunk_max; + + /* State flags */ + bool is_in : 1; + bool is_iso : 1; + bool stalled : 1; + bool wedged : 1; + bool enabled : 1; + bool desc_mode : 1; + } epn; + }; +}; +#define to_ast_ep(__uep) container_of(__uep, struct ast_vhub_ep, ep) + +/* A device attached to a vHub port */ +struct ast_vhub_dev { + struct ast_vhub *vhub; + void __iomem *regs; + + /* Device index (zero-based) and name string */ + unsigned int index; + const char *name; + + /* sysfs enclosure for the gadget gunk */ + struct device *port_dev; + + /* Link to gadget core */ + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + bool registered : 1; + bool wakeup_en : 1; + bool enabled : 1; + + /* Endpoint structures */ + struct ast_vhub_ep ep0; + struct ast_vhub_ep **epns; + u32 max_epns; + +}; +#define to_ast_dev(__g) container_of(__g, struct ast_vhub_dev, gadget) + +/* Per vhub port stateinfo structure */ +struct ast_vhub_port { + /* Port status & status change registers */ + u16 status; + u16 change; + + /* Associated device slot */ + struct ast_vhub_dev dev; +}; + +struct ast_vhub_full_cdesc { + struct usb_config_descriptor cfg; + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor ep; +} __packed; + +/* Global vhub structure */ +struct ast_vhub { + struct platform_device *pdev; + void __iomem *regs; + int irq; + spinlock_t lock; + struct work_struct wake_work; + struct clk *clk; + struct reset_control *rst; + + /* EP0 DMA buffers allocated in one chunk */ + void *ep0_bufs; + dma_addr_t ep0_bufs_dma; + + /* EP0 of the vhub itself */ + struct ast_vhub_ep ep0; + + /* State of vhub ep1 */ + bool ep1_stalled : 1; + + /* Per-port info */ + struct ast_vhub_port *ports; + u32 max_ports; + u32 port_irq_mask; + + /* Generic EP data structures */ + struct ast_vhub_ep *epns; + u32 max_epns; + + /* Upstream bus is suspended ? */ + bool suspended : 1; + + /* Hub itself can signal remote wakeup */ + bool wakeup_en : 1; + + /* Force full speed only */ + bool force_usb1 : 1; + + /* Upstream bus speed captured at bus reset */ + unsigned int speed; + + /* Standard USB Descriptors of the vhub. */ + struct usb_device_descriptor vhub_dev_desc; + struct ast_vhub_full_cdesc vhub_conf_desc; + struct usb_hub_descriptor vhub_hub_desc; + struct list_head vhub_str_desc; + struct usb_qualifier_descriptor vhub_qual_desc; +}; + +/* Standard request handlers result codes */ +enum std_req_rc { + std_req_stall = -1, /* Stall requested */ + std_req_complete = 0, /* Request completed with no data */ + std_req_data = 1, /* Request completed with data */ + std_req_driver = 2, /* Pass to driver pls */ +}; + +#ifdef CONFIG_USB_GADGET_VERBOSE +#define UDCVDBG(u, fmt...) dev_dbg(&(u)->pdev->dev, fmt) + +#define EPVDBG(ep, fmt, ...) do { \ + dev_dbg(&(ep)->vhub->pdev->dev, \ + "%s:EP%d " fmt, \ + (ep)->dev ? (ep)->dev->name : "hub", \ + (ep)->d_idx, ##__VA_ARGS__); \ + } while(0) + +#define DVDBG(d, fmt, ...) do { \ + dev_dbg(&(d)->vhub->pdev->dev, \ + "%s " fmt, (d)->name, \ + ##__VA_ARGS__); \ + } while(0) + +#else +#define UDCVDBG(u, fmt...) do { } while(0) +#define EPVDBG(ep, fmt, ...) do { } while(0) +#define DVDBG(d, fmt, ...) do { } while(0) +#endif + +#ifdef CONFIG_USB_GADGET_DEBUG +#define UDCDBG(u, fmt...) dev_dbg(&(u)->pdev->dev, fmt) + +#define EPDBG(ep, fmt, ...) do { \ + dev_dbg(&(ep)->vhub->pdev->dev, \ + "%s:EP%d " fmt, \ + (ep)->dev ? (ep)->dev->name : "hub", \ + (ep)->d_idx, ##__VA_ARGS__); \ + } while(0) + +#define DDBG(d, fmt, ...) do { \ + dev_dbg(&(d)->vhub->pdev->dev, \ + "%s " fmt, (d)->name, \ + ##__VA_ARGS__); \ + } while(0) +#else +#define UDCDBG(u, fmt...) do { } while(0) +#define EPDBG(ep, fmt, ...) do { } while(0) +#define DDBG(d, fmt, ...) do { } while(0) +#endif + +static inline void vhub_dma_workaround(void *addr) +{ + /* + * This works around a confirmed HW issue with the Aspeed chip. + * + * The core uses a different bus to memory than the AHB going to + * the USB device controller. Due to the latter having a higher + * priority than the core for arbitration on that bus, it's + * possible for an MMIO to the device, followed by a DMA by the + * device from memory to all be performed and services before + * a previous store to memory gets completed. + * + * This the following scenario can happen: + * + * - Driver writes to a DMA descriptor (Mbus) + * - Driver writes to the MMIO register to start the DMA (AHB) + * - The gadget sees the second write and sends a read of the + * descriptor to the memory controller (Mbus) + * - The gadget hits memory before the descriptor write + * causing it to read an obsolete value. + * + * Thankfully the problem is limited to the USB gadget device, other + * masters in the SoC all have a lower priority than the core, thus + * ensuring that the store by the core arrives first. + * + * The workaround consists of using a dummy read of the memory before + * doing the MMIO writes. This will ensure that the previous writes + * have been "pushed out". + */ + mb(); + (void)__raw_readl((void __iomem *)addr); +} + +/* core.c */ +void ast_vhub_done(struct ast_vhub_ep *ep, struct ast_vhub_req *req, + int status); +void ast_vhub_nuke(struct ast_vhub_ep *ep, int status); +struct usb_request *ast_vhub_alloc_request(struct usb_ep *u_ep, + gfp_t gfp_flags); +void ast_vhub_free_request(struct usb_ep *u_ep, struct usb_request *u_req); +void ast_vhub_init_hw(struct ast_vhub *vhub); + +/* ep0.c */ +void ast_vhub_ep0_handle_ack(struct ast_vhub_ep *ep, bool in_ack); +void ast_vhub_ep0_handle_setup(struct ast_vhub_ep *ep); +void ast_vhub_reset_ep0(struct ast_vhub_dev *dev); +void ast_vhub_init_ep0(struct ast_vhub *vhub, struct ast_vhub_ep *ep, + struct ast_vhub_dev *dev); +int ast_vhub_reply(struct ast_vhub_ep *ep, char *ptr, int len); +int __ast_vhub_simple_reply(struct ast_vhub_ep *ep, int len, ...); +#define ast_vhub_simple_reply(udc, ...) \ + __ast_vhub_simple_reply((udc), \ + sizeof((u8[]) { __VA_ARGS__ })/sizeof(u8), \ + __VA_ARGS__) + +/* hub.c */ +int ast_vhub_init_hub(struct ast_vhub *vhub); +enum std_req_rc ast_vhub_std_hub_request(struct ast_vhub_ep *ep, + struct usb_ctrlrequest *crq); +enum std_req_rc ast_vhub_class_hub_request(struct ast_vhub_ep *ep, + struct usb_ctrlrequest *crq); +void ast_vhub_device_connect(struct ast_vhub *vhub, unsigned int port, + bool on); +void ast_vhub_hub_suspend(struct ast_vhub *vhub); +void ast_vhub_hub_resume(struct ast_vhub *vhub); +void ast_vhub_hub_reset(struct ast_vhub *vhub); +void ast_vhub_hub_wake_all(struct ast_vhub *vhub); + +/* dev.c */ +int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx); +void ast_vhub_del_dev(struct ast_vhub_dev *d); +void ast_vhub_dev_irq(struct ast_vhub_dev *d); +int ast_vhub_std_dev_request(struct ast_vhub_ep *ep, + struct usb_ctrlrequest *crq); + +/* epn.c */ +void ast_vhub_epn_ack_irq(struct ast_vhub_ep *ep); +void ast_vhub_update_epn_stall(struct ast_vhub_ep *ep); +struct ast_vhub_ep *ast_vhub_alloc_epn(struct ast_vhub_dev *d, u8 addr); +void ast_vhub_dev_suspend(struct ast_vhub_dev *d); +void ast_vhub_dev_resume(struct ast_vhub_dev *d); +void ast_vhub_dev_reset(struct ast_vhub_dev *d); + +#endif /* __ASPEED_VHUB_H */ diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c new file mode 100644 index 000000000..54f81e668 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed_udc.c @@ -0,0 +1,1615 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2021 Aspeed Technology Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AST_UDC_NUM_ENDPOINTS (1 + 4) +#define AST_UDC_EP0_MAX_PACKET 64 /* EP0's max packet size */ +#define AST_UDC_EPn_MAX_PACKET 1024 /* Generic EPs max packet size */ +#define AST_UDC_DESCS_COUNT 256 /* Use 256 stages descriptor mode (32/256) */ +#define AST_UDC_DESC_MODE 1 /* Single/Multiple Stage(s) Descriptor Mode */ + +#define AST_UDC_EP_DMA_SIZE (AST_UDC_EPn_MAX_PACKET + 8 * AST_UDC_DESCS_COUNT) + +/***************************** + * * + * UDC register definitions * + * * + *****************************/ + +#define AST_UDC_FUNC_CTRL 0x00 /* Root Function Control & Status Register */ +#define AST_UDC_CONFIG 0x04 /* Root Configuration Setting Register */ +#define AST_UDC_IER 0x08 /* Interrupt Control Register */ +#define AST_UDC_ISR 0x0C /* Interrupt Status Register */ +#define AST_UDC_EP_ACK_IER 0x10 /* Programmable ep Pool ACK Interrupt Enable Reg */ +#define AST_UDC_EP_NAK_IER 0x14 /* Programmable ep Pool NAK Interrupt Enable Reg */ +#define AST_UDC_EP_ACK_ISR 0x18 /* Programmable ep Pool ACK Interrupt Status Reg */ +#define AST_UDC_EP_NAK_ISR 0x1C /* Programmable ep Pool NAK Interrupt Status Reg */ +#define AST_UDC_DEV_RESET 0x20 /* Device Controller Soft Reset Enable Register */ +#define AST_UDC_STS 0x24 /* USB Status Register */ +#define AST_VHUB_EP_DATA 0x28 /* Programmable ep Pool Data Toggle Value Set */ +#define AST_VHUB_ISO_TX_FAIL 0x2C /* Isochronous Transaction Fail Accumulator */ +#define AST_UDC_EP0_CTRL 0x30 /* Endpoint 0 Control/Status Register */ +#define AST_UDC_EP0_DATA_BUFF 0x34 /* Base Address of ep0 IN/OUT Data Buffer Reg */ +#define AST_UDC_SETUP0 0x80 /* Root Device Setup Data Buffer0 */ +#define AST_UDC_SETUP1 0x84 /* Root Device Setup Data Buffer1 */ + + +/* Main control reg */ +#define USB_PHY_CLK_EN BIT(31) +#define USB_FIFO_DYN_PWRD_EN BIT(19) +#define USB_EP_LONG_DESC BIT(18) +#define USB_BIST_TEST_PASS BIT(13) +#define USB_BIST_TURN_ON BIT(12) +#define USB_PHY_RESET_DIS BIT(11) +#define USB_TEST_MODE(x) ((x) << 8) +#define USB_FORCE_TIMER_HS BIT(7) +#define USB_FORCE_HS BIT(6) +#define USB_REMOTE_WAKEUP_12MS BIT(5) +#define USB_REMOTE_WAKEUP_EN BIT(4) +#define USB_AUTO_REMOTE_WAKEUP_EN BIT(3) +#define USB_STOP_CLK_IN_SUPEND BIT(2) +#define USB_UPSTREAM_FS BIT(1) +#define USB_UPSTREAM_EN BIT(0) + +/* Main config reg */ +#define UDC_CFG_SET_ADDR(x) ((x) & UDC_CFG_ADDR_MASK) +#define UDC_CFG_ADDR_MASK GENMASK(6, 0) + +/* Interrupt ctrl & status reg */ +#define UDC_IRQ_EP_POOL_NAK BIT(17) +#define UDC_IRQ_EP_POOL_ACK_STALL BIT(16) +#define UDC_IRQ_BUS_RESUME BIT(8) +#define UDC_IRQ_BUS_SUSPEND BIT(7) +#define UDC_IRQ_BUS_RESET BIT(6) +#define UDC_IRQ_EP0_IN_DATA_NAK BIT(4) +#define UDC_IRQ_EP0_IN_ACK_STALL BIT(3) +#define UDC_IRQ_EP0_OUT_NAK BIT(2) +#define UDC_IRQ_EP0_OUT_ACK_STALL BIT(1) +#define UDC_IRQ_EP0_SETUP BIT(0) +#define UDC_IRQ_ACK_ALL (0x1ff) + +/* EP isr reg */ +#define USB_EP3_ISR BIT(3) +#define USB_EP2_ISR BIT(2) +#define USB_EP1_ISR BIT(1) +#define USB_EP0_ISR BIT(0) +#define UDC_IRQ_EP_ACK_ALL (0xf) + +/*Soft reset reg */ +#define ROOT_UDC_SOFT_RESET BIT(0) + +/* USB status reg */ +#define UDC_STS_HIGHSPEED BIT(27) + +/* Programmable EP data toggle */ +#define EP_TOGGLE_SET_EPNUM(x) ((x) & 0x3) + +/* EP0 ctrl reg */ +#define EP0_GET_RX_LEN(x) ((x >> 16) & 0x7f) +#define EP0_TX_LEN(x) ((x & 0x7f) << 8) +#define EP0_RX_BUFF_RDY BIT(2) +#define EP0_TX_BUFF_RDY BIT(1) +#define EP0_STALL BIT(0) + +/************************************* + * * + * per-endpoint register definitions * + * * + *************************************/ + +#define AST_UDC_EP_CONFIG 0x00 /* Endpoint Configuration Register */ +#define AST_UDC_EP_DMA_CTRL 0x04 /* DMA Descriptor List Control/Status Register */ +#define AST_UDC_EP_DMA_BUFF 0x08 /* DMA Descriptor/Buffer Base Address */ +#define AST_UDC_EP_DMA_STS 0x0C /* DMA Descriptor List R/W Pointer and Status */ + +#define AST_UDC_EP_BASE 0x200 +#define AST_UDC_EP_OFFSET 0x10 + +/* EP config reg */ +#define EP_SET_MAX_PKT(x) ((x & 0x3ff) << 16) +#define EP_DATA_FETCH_CTRL(x) ((x & 0x3) << 14) +#define EP_AUTO_DATA_DISABLE (0x1 << 13) +#define EP_SET_EP_STALL (0x1 << 12) +#define EP_SET_EP_NUM(x) ((x & 0xf) << 8) +#define EP_SET_TYPE_MASK(x) ((x) << 5) +#define EP_TYPE_BULK (0x1) +#define EP_TYPE_INT (0x2) +#define EP_TYPE_ISO (0x3) +#define EP_DIR_OUT (0x1 << 4) +#define EP_ALLOCATED_MASK (0x7 << 1) +#define EP_ENABLE BIT(0) + +/* EP DMA ctrl reg */ +#define EP_DMA_CTRL_GET_PROC_STS(x) ((x >> 4) & 0xf) +#define EP_DMA_CTRL_STS_RX_IDLE 0x0 +#define EP_DMA_CTRL_STS_TX_IDLE 0x8 +#define EP_DMA_CTRL_IN_LONG_MODE (0x1 << 3) +#define EP_DMA_CTRL_RESET (0x1 << 2) +#define EP_DMA_SINGLE_STAGE (0x1 << 1) +#define EP_DMA_DESC_MODE (0x1 << 0) + +/* EP DMA status reg */ +#define EP_DMA_SET_TX_SIZE(x) ((x & 0x7ff) << 16) +#define EP_DMA_GET_TX_SIZE(x) (((x) >> 16) & 0x7ff) +#define EP_DMA_GET_RPTR(x) (((x) >> 8) & 0xff) +#define EP_DMA_GET_WPTR(x) ((x) & 0xff) +#define EP_DMA_SINGLE_KICK (1 << 0) /* WPTR = 1 for single mode */ + +/* EP desc reg */ +#define AST_EP_DMA_DESC_INTR_ENABLE BIT(31) +#define AST_EP_DMA_DESC_PID_DATA0 (0 << 14) +#define AST_EP_DMA_DESC_PID_DATA2 BIT(14) +#define AST_EP_DMA_DESC_PID_DATA1 (2 << 14) +#define AST_EP_DMA_DESC_PID_MDATA (3 << 14) +#define EP_DESC1_IN_LEN(x) ((x) & 0x1fff) +#define AST_EP_DMA_DESC_MAX_LEN (7680) /* Max packet length for transmit in 1 desc */ + +struct ast_udc_request { + struct usb_request req; + struct list_head queue; + unsigned mapped:1; + unsigned int actual_dma_length; + u32 saved_dma_wptr; +}; + +#define to_ast_req(__req) container_of(__req, struct ast_udc_request, req) + +struct ast_dma_desc { + u32 des_0; + u32 des_1; +}; + +struct ast_udc_ep { + struct usb_ep ep; + + /* Request queue */ + struct list_head queue; + + struct ast_udc_dev *udc; + void __iomem *ep_reg; + void *epn_buf; + dma_addr_t epn_buf_dma; + const struct usb_endpoint_descriptor *desc; + + /* DMA Descriptors */ + struct ast_dma_desc *descs; + dma_addr_t descs_dma; + u32 descs_wptr; + u32 chunk_max; + + bool dir_in:1; + unsigned stopped:1; + bool desc_mode:1; +}; + +#define to_ast_ep(__ep) container_of(__ep, struct ast_udc_ep, ep) + +struct ast_udc_dev { + struct platform_device *pdev; + void __iomem *reg; + int irq; + spinlock_t lock; + struct clk *clk; + struct work_struct wake_work; + + /* EP0 DMA buffers allocated in one chunk */ + void *ep0_buf; + dma_addr_t ep0_buf_dma; + struct ast_udc_ep ep[AST_UDC_NUM_ENDPOINTS]; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + void __iomem *creq; + enum usb_device_state suspended_from; + int desc_mode; + + /* Force full speed only */ + bool force_usb1:1; + unsigned is_control_tx:1; + bool wakeup_en:1; +}; + +#define to_ast_dev(__g) container_of(__g, struct ast_udc_dev, gadget) + +static const char * const ast_ep_name[] = { + "ep0", "ep1", "ep2", "ep3", "ep4" +}; + +#ifdef AST_UDC_DEBUG_ALL +#define AST_UDC_DEBUG +#define AST_SETUP_DEBUG +#define AST_EP_DEBUG +#define AST_ISR_DEBUG +#endif + +#ifdef AST_SETUP_DEBUG +#define SETUP_DBG(u, fmt, ...) \ + dev_dbg(&(u)->pdev->dev, "%s() " fmt, __func__, ##__VA_ARGS__) +#else +#define SETUP_DBG(u, fmt, ...) +#endif + +#ifdef AST_EP_DEBUG +#define EP_DBG(e, fmt, ...) \ + dev_dbg(&(e)->udc->pdev->dev, "%s():%s " fmt, __func__, \ + (e)->ep.name, ##__VA_ARGS__) +#else +#define EP_DBG(ep, fmt, ...) ((void)(ep)) +#endif + +#ifdef AST_UDC_DEBUG +#define UDC_DBG(u, fmt, ...) \ + dev_dbg(&(u)->pdev->dev, "%s() " fmt, __func__, ##__VA_ARGS__) +#else +#define UDC_DBG(u, fmt, ...) +#endif + +#ifdef AST_ISR_DEBUG +#define ISR_DBG(u, fmt, ...) \ + dev_dbg(&(u)->pdev->dev, "%s() " fmt, __func__, ##__VA_ARGS__) +#else +#define ISR_DBG(u, fmt, ...) +#endif + +/*-------------------------------------------------------------------------*/ +#define ast_udc_read(udc, offset) \ + readl((udc)->reg + (offset)) +#define ast_udc_write(udc, val, offset) \ + writel((val), (udc)->reg + (offset)) + +#define ast_ep_read(ep, reg) \ + readl((ep)->ep_reg + (reg)) +#define ast_ep_write(ep, val, reg) \ + writel((val), (ep)->ep_reg + (reg)) + +/*-------------------------------------------------------------------------*/ + +static void ast_udc_done(struct ast_udc_ep *ep, struct ast_udc_request *req, + int status) +{ + struct ast_udc_dev *udc = ep->udc; + + EP_DBG(ep, "req @%p, len (%d/%d), buf:0x%x, dir:0x%x\n", + req, req->req.actual, req->req.length, + (u32)req->req.buf, ep->dir_in); + + list_del(&req->queue); + + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + if (status && status != -ESHUTDOWN) + EP_DBG(ep, "done req:%p, status:%d\n", req, status); + + spin_unlock(&udc->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&udc->lock); +} + +static void ast_udc_nuke(struct ast_udc_ep *ep, int status) +{ + int count = 0; + + while (!list_empty(&ep->queue)) { + struct ast_udc_request *req; + + req = list_entry(ep->queue.next, struct ast_udc_request, + queue); + ast_udc_done(ep, req, status); + count++; + } + + if (count) + EP_DBG(ep, "Nuked %d request(s)\n", count); +} + +/* + * Stop activity on all endpoints. + * 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 ast_udc_stop_activity(struct ast_udc_dev *udc) +{ + struct ast_udc_ep *ep; + int i; + + for (i = 0; i < AST_UDC_NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + ep->stopped = 1; + ast_udc_nuke(ep, -ESHUTDOWN); + } +} + +static int ast_udc_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + u16 maxpacket = usb_endpoint_maxp(desc); + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + u8 epnum = usb_endpoint_num(desc); + unsigned long flags; + u32 ep_conf = 0; + u8 dir_in; + u8 type; + + if (!_ep || !ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT || + maxpacket == 0 || maxpacket > ep->ep.maxpacket) { + EP_DBG(ep, "Failed, invalid EP enable param\n"); + return -EINVAL; + } + + if (!udc->driver) { + EP_DBG(ep, "bogus device state\n"); + return -ESHUTDOWN; + } + + EP_DBG(ep, "maxpacket:0x%x\n", maxpacket); + + spin_lock_irqsave(&udc->lock, flags); + + ep->desc = desc; + ep->stopped = 0; + ep->ep.maxpacket = maxpacket; + ep->chunk_max = AST_EP_DMA_DESC_MAX_LEN; + + if (maxpacket < AST_UDC_EPn_MAX_PACKET) + ep_conf = EP_SET_MAX_PKT(maxpacket); + + ep_conf |= EP_SET_EP_NUM(epnum); + + type = usb_endpoint_type(desc); + dir_in = usb_endpoint_dir_in(desc); + ep->dir_in = dir_in; + if (!ep->dir_in) + ep_conf |= EP_DIR_OUT; + + EP_DBG(ep, "type %d, dir_in %d\n", type, dir_in); + switch (type) { + case USB_ENDPOINT_XFER_ISOC: + ep_conf |= EP_SET_TYPE_MASK(EP_TYPE_ISO); + break; + + case USB_ENDPOINT_XFER_BULK: + ep_conf |= EP_SET_TYPE_MASK(EP_TYPE_BULK); + break; + + case USB_ENDPOINT_XFER_INT: + ep_conf |= EP_SET_TYPE_MASK(EP_TYPE_INT); + break; + } + + ep->desc_mode = udc->desc_mode && ep->descs_dma && ep->dir_in; + if (ep->desc_mode) { + ast_ep_write(ep, EP_DMA_CTRL_RESET, AST_UDC_EP_DMA_CTRL); + ast_ep_write(ep, 0, AST_UDC_EP_DMA_STS); + ast_ep_write(ep, ep->descs_dma, AST_UDC_EP_DMA_BUFF); + + /* Enable Long Descriptor Mode */ + ast_ep_write(ep, EP_DMA_CTRL_IN_LONG_MODE | EP_DMA_DESC_MODE, + AST_UDC_EP_DMA_CTRL); + + ep->descs_wptr = 0; + + } else { + ast_ep_write(ep, EP_DMA_CTRL_RESET, AST_UDC_EP_DMA_CTRL); + ast_ep_write(ep, EP_DMA_SINGLE_STAGE, AST_UDC_EP_DMA_CTRL); + ast_ep_write(ep, 0, AST_UDC_EP_DMA_STS); + } + + /* Cleanup data toggle just in case */ + ast_udc_write(udc, EP_TOGGLE_SET_EPNUM(epnum), AST_VHUB_EP_DATA); + + /* Enable EP */ + ast_ep_write(ep, ep_conf | EP_ENABLE, AST_UDC_EP_CONFIG); + + EP_DBG(ep, "ep_config: 0x%x\n", ast_ep_read(ep, AST_UDC_EP_CONFIG)); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int ast_udc_ep_disable(struct usb_ep *_ep) +{ + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + ep->ep.desc = NULL; + ep->stopped = 1; + + ast_udc_nuke(ep, -ESHUTDOWN); + ast_ep_write(ep, 0, AST_UDC_EP_CONFIG); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static struct usb_request *ast_udc_ep_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_request *req; + + req = kzalloc_obj(struct ast_udc_request, gfp_flags); + if (!req) { + EP_DBG(ep, "request allocation failed\n"); + return NULL; + } + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void ast_udc_ep_free_request(struct usb_ep *_ep, + struct usb_request *_req) +{ + struct ast_udc_request *req = to_ast_req(_req); + + kfree(req); +} + +static int ast_dma_descriptor_setup(struct ast_udc_ep *ep, u32 dma_buf, + u16 tx_len, struct ast_udc_request *req) +{ + struct ast_udc_dev *udc = ep->udc; + struct device *dev = &udc->pdev->dev; + bool last = false; + int chunk, count; + u32 offset; + + if (!ep->descs) { + dev_warn(dev, "%s: Empty DMA descs list failure\n", + ep->ep.name); + return -EINVAL; + } + + chunk = tx_len; + offset = count = 0; + + EP_DBG(ep, "req @%p, %s:%d, %s:0x%x, %s:0x%x\n", req, + "wptr", ep->descs_wptr, "dma_buf", dma_buf, + "tx_len", tx_len); + + /* Create Descriptor Lists */ + while (chunk >= 0 && !last && count < AST_UDC_DESCS_COUNT) { + + ep->descs[ep->descs_wptr].des_0 = dma_buf + offset; + + if (chunk > ep->chunk_max) { + ep->descs[ep->descs_wptr].des_1 = ep->chunk_max; + } else { + ep->descs[ep->descs_wptr].des_1 = chunk; + last = true; + } + + chunk -= ep->chunk_max; + + EP_DBG(ep, "descs[%d]: 0x%x 0x%x\n", + ep->descs_wptr, + ep->descs[ep->descs_wptr].des_0, + ep->descs[ep->descs_wptr].des_1); + + if (count == 0) + req->saved_dma_wptr = ep->descs_wptr; + + ep->descs_wptr++; + count++; + + if (ep->descs_wptr >= AST_UDC_DESCS_COUNT) + ep->descs_wptr = 0; + + offset = ep->chunk_max * count; + } + + return 0; +} + +static void ast_udc_epn_kick(struct ast_udc_ep *ep, struct ast_udc_request *req) +{ + u32 tx_len; + u32 last; + + last = req->req.length - req->req.actual; + tx_len = last > ep->ep.maxpacket ? ep->ep.maxpacket : last; + + EP_DBG(ep, "kick req @%p, len:%d, dir:%d\n", + req, tx_len, ep->dir_in); + + ast_ep_write(ep, req->req.dma + req->req.actual, AST_UDC_EP_DMA_BUFF); + + /* Start DMA */ + ast_ep_write(ep, EP_DMA_SET_TX_SIZE(tx_len), AST_UDC_EP_DMA_STS); + ast_ep_write(ep, EP_DMA_SET_TX_SIZE(tx_len) | EP_DMA_SINGLE_KICK, + AST_UDC_EP_DMA_STS); +} + +static void ast_udc_epn_kick_desc(struct ast_udc_ep *ep, + struct ast_udc_request *req) +{ + u32 descs_max_size; + u32 tx_len; + u32 last; + + descs_max_size = AST_EP_DMA_DESC_MAX_LEN * AST_UDC_DESCS_COUNT; + + last = req->req.length - req->req.actual; + tx_len = last > descs_max_size ? descs_max_size : last; + + EP_DBG(ep, "kick req @%p, %s:%d, %s:0x%x, %s:0x%x (%d/%d), %s:0x%x\n", + req, "tx_len", tx_len, "dir_in", ep->dir_in, + "dma", req->req.dma + req->req.actual, + req->req.actual, req->req.length, + "descs_max_size", descs_max_size); + + if (!ast_dma_descriptor_setup(ep, req->req.dma + req->req.actual, + tx_len, req)) + req->actual_dma_length += tx_len; + + /* make sure CPU done everything before triggering DMA */ + mb(); + + ast_ep_write(ep, ep->descs_wptr, AST_UDC_EP_DMA_STS); + + EP_DBG(ep, "descs_wptr:%d, dstat:0x%x, dctrl:0x%x\n", + ep->descs_wptr, + ast_ep_read(ep, AST_UDC_EP_DMA_STS), + ast_ep_read(ep, AST_UDC_EP_DMA_CTRL)); +} + +static void ast_udc_ep0_queue(struct ast_udc_ep *ep, + struct ast_udc_request *req) +{ + struct ast_udc_dev *udc = ep->udc; + u32 tx_len; + u32 last; + + last = req->req.length - req->req.actual; + tx_len = last > ep->ep.maxpacket ? ep->ep.maxpacket : last; + + ast_udc_write(udc, req->req.dma + req->req.actual, + AST_UDC_EP0_DATA_BUFF); + + if (ep->dir_in) { + /* IN requests, send data */ + SETUP_DBG(udc, "IN: %s:0x%x, %s:0x%x, %s:%d (%d/%d), %s:%d\n", + "buf", (u32)req->req.buf, + "dma", req->req.dma + req->req.actual, + "tx_len", tx_len, + req->req.actual, req->req.length, + "dir_in", ep->dir_in); + + req->req.actual += tx_len; + ast_udc_write(udc, EP0_TX_LEN(tx_len), AST_UDC_EP0_CTRL); + ast_udc_write(udc, EP0_TX_LEN(tx_len) | EP0_TX_BUFF_RDY, + AST_UDC_EP0_CTRL); + + } else { + /* OUT requests, receive data */ + SETUP_DBG(udc, "OUT: %s:%x, %s:%x, %s:(%d/%d), %s:%d\n", + "buf", (u32)req->req.buf, + "dma", req->req.dma + req->req.actual, + "len", req->req.actual, req->req.length, + "dir_in", ep->dir_in); + + if (!req->req.length) { + /* 0 len request, send tx as completion */ + ast_udc_write(udc, EP0_TX_BUFF_RDY, AST_UDC_EP0_CTRL); + ep->dir_in = 0x1; + } else + ast_udc_write(udc, EP0_RX_BUFF_RDY, AST_UDC_EP0_CTRL); + } +} + +static int ast_udc_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct ast_udc_request *req = to_ast_req(_req); + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + struct device *dev = &udc->pdev->dev; + unsigned long flags; + int rc; + + if (unlikely(!_req || !_req->complete || !_req->buf || !_ep)) { + dev_warn(dev, "Invalid EP request !\n"); + return -EINVAL; + } + + if (ep->stopped) { + dev_warn(dev, "%s is already stopped !\n", _ep->name); + return -ESHUTDOWN; + } + + spin_lock_irqsave(&udc->lock, flags); + + list_add_tail(&req->queue, &ep->queue); + + req->req.actual = 0; + req->req.status = -EINPROGRESS; + req->actual_dma_length = 0; + + rc = usb_gadget_map_request(&udc->gadget, &req->req, ep->dir_in); + if (rc) { + EP_DBG(ep, "Request mapping failure %d\n", rc); + dev_warn(dev, "Request mapping failure %d\n", rc); + goto end; + } + + EP_DBG(ep, "enqueue req @%p\n", req); + EP_DBG(ep, "l=%d, dma:0x%x, zero:%d, is_in:%d\n", + _req->length, _req->dma, _req->zero, ep->dir_in); + + /* EP0 request enqueue */ + if (ep->ep.desc == NULL) { + if ((req->req.dma % 4) != 0) { + dev_warn(dev, "EP0 req dma alignment error\n"); + rc = -ESHUTDOWN; + goto end; + } + + ast_udc_ep0_queue(ep, req); + goto end; + } + + /* EPn request enqueue */ + if (list_is_singular(&ep->queue)) { + if (ep->desc_mode) + ast_udc_epn_kick_desc(ep, req); + else + ast_udc_epn_kick(ep, req); + } + +end: + spin_unlock_irqrestore(&udc->lock, flags); + + return rc; +} + +static int ast_udc_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + struct ast_udc_request *req; + unsigned long flags; + int rc = -EINVAL; + + 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) { + list_del_init(&req->queue); + ast_udc_done(ep, req, -ESHUTDOWN); + _req->status = -ECONNRESET; + rc = 0; + break; + } + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return rc; +} + +static int ast_udc_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct ast_udc_ep *ep = to_ast_ep(_ep); + struct ast_udc_dev *udc = ep->udc; + unsigned long flags; + int epnum; + u32 ctrl; + + EP_DBG(ep, "val:%d\n", value); + + spin_lock_irqsave(&udc->lock, flags); + + epnum = usb_endpoint_num(ep->desc); + + /* EP0 */ + if (epnum == 0) { + ctrl = ast_udc_read(udc, AST_UDC_EP0_CTRL); + if (value) + ctrl |= EP0_STALL; + else + ctrl &= ~EP0_STALL; + + ast_udc_write(udc, ctrl, AST_UDC_EP0_CTRL); + + } else { + /* EPn */ + ctrl = ast_udc_read(udc, AST_UDC_EP_CONFIG); + if (value) + ctrl |= EP_SET_EP_STALL; + else + ctrl &= ~EP_SET_EP_STALL; + + ast_ep_write(ep, ctrl, AST_UDC_EP_CONFIG); + + /* only epn is stopped and waits for clear */ + ep->stopped = value ? 1 : 0; + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static const struct usb_ep_ops ast_udc_ep_ops = { + .enable = ast_udc_ep_enable, + .disable = ast_udc_ep_disable, + .alloc_request = ast_udc_ep_alloc_request, + .free_request = ast_udc_ep_free_request, + .queue = ast_udc_ep_queue, + .dequeue = ast_udc_ep_dequeue, + .set_halt = ast_udc_ep_set_halt, + /* there's only imprecise fifo status reporting */ +}; + +static void ast_udc_ep0_rx(struct ast_udc_dev *udc) +{ + ast_udc_write(udc, udc->ep0_buf_dma, AST_UDC_EP0_DATA_BUFF); + ast_udc_write(udc, EP0_RX_BUFF_RDY, AST_UDC_EP0_CTRL); +} + +static void ast_udc_ep0_tx(struct ast_udc_dev *udc) +{ + ast_udc_write(udc, udc->ep0_buf_dma, AST_UDC_EP0_DATA_BUFF); + ast_udc_write(udc, EP0_TX_BUFF_RDY, AST_UDC_EP0_CTRL); +} + +static void ast_udc_ep0_out(struct ast_udc_dev *udc) +{ + struct device *dev = &udc->pdev->dev; + struct ast_udc_ep *ep = &udc->ep[0]; + struct ast_udc_request *req; + u16 rx_len; + + if (list_empty(&ep->queue)) + return; + + req = list_entry(ep->queue.next, struct ast_udc_request, queue); + + rx_len = EP0_GET_RX_LEN(ast_udc_read(udc, AST_UDC_EP0_CTRL)); + req->req.actual += rx_len; + + SETUP_DBG(udc, "req %p (%d/%d)\n", req, + req->req.actual, req->req.length); + + if ((rx_len < ep->ep.maxpacket) || + (req->req.actual == req->req.length)) { + ast_udc_ep0_tx(udc); + if (!ep->dir_in) + ast_udc_done(ep, req, 0); + + } else { + if (rx_len > req->req.length) { + // Issue Fix + dev_warn(dev, "Something wrong (%d/%d)\n", + req->req.actual, req->req.length); + ast_udc_ep0_tx(udc); + ast_udc_done(ep, req, 0); + return; + } + + ep->dir_in = 0; + + /* More works */ + ast_udc_ep0_queue(ep, req); + } +} + +static void ast_udc_ep0_in(struct ast_udc_dev *udc) +{ + struct ast_udc_ep *ep = &udc->ep[0]; + struct ast_udc_request *req; + + if (list_empty(&ep->queue)) { + if (udc->is_control_tx) { + ast_udc_ep0_rx(udc); + udc->is_control_tx = 0; + } + + return; + } + + req = list_entry(ep->queue.next, struct ast_udc_request, queue); + + SETUP_DBG(udc, "req %p (%d/%d)\n", req, + req->req.actual, req->req.length); + + if (req->req.length == req->req.actual) { + if (req->req.length) + ast_udc_ep0_rx(udc); + + if (ep->dir_in) + ast_udc_done(ep, req, 0); + + } else { + /* More works */ + ast_udc_ep0_queue(ep, req); + } +} + +static void ast_udc_epn_handle(struct ast_udc_dev *udc, u16 ep_num) +{ + struct ast_udc_ep *ep = &udc->ep[ep_num]; + struct ast_udc_request *req; + u16 len = 0; + + if (list_empty(&ep->queue)) + return; + + req = list_first_entry(&ep->queue, struct ast_udc_request, queue); + + len = EP_DMA_GET_TX_SIZE(ast_ep_read(ep, AST_UDC_EP_DMA_STS)); + req->req.actual += len; + + EP_DBG(ep, "req @%p, length:(%d/%d), %s:0x%x\n", req, + req->req.actual, req->req.length, "len", len); + + /* Done this request */ + if (req->req.length == req->req.actual) { + ast_udc_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, + struct ast_udc_request, + queue); + + } else { + /* Check for short packet */ + if (len < ep->ep.maxpacket) { + ast_udc_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, + struct ast_udc_request, + queue); + } + } + + /* More requests */ + if (req) + ast_udc_epn_kick(ep, req); +} + +static void ast_udc_epn_handle_desc(struct ast_udc_dev *udc, u16 ep_num) +{ + struct ast_udc_ep *ep = &udc->ep[ep_num]; + struct device *dev = &udc->pdev->dev; + struct ast_udc_request *req; + u32 proc_sts, wr_ptr, rd_ptr; + u32 len_in_desc, ctrl; + u16 total_len = 0; + int i; + + if (list_empty(&ep->queue)) { + dev_warn(dev, "%s request queue empty!\n", ep->ep.name); + return; + } + + req = list_first_entry(&ep->queue, struct ast_udc_request, queue); + + ctrl = ast_ep_read(ep, AST_UDC_EP_DMA_CTRL); + proc_sts = EP_DMA_CTRL_GET_PROC_STS(ctrl); + + /* Check processing status is idle */ + if (proc_sts != EP_DMA_CTRL_STS_RX_IDLE && + proc_sts != EP_DMA_CTRL_STS_TX_IDLE) { + dev_warn(dev, "EP DMA CTRL: 0x%x, PS:0x%x\n", + ast_ep_read(ep, AST_UDC_EP_DMA_CTRL), + proc_sts); + return; + } + + ctrl = ast_ep_read(ep, AST_UDC_EP_DMA_STS); + rd_ptr = EP_DMA_GET_RPTR(ctrl); + wr_ptr = EP_DMA_GET_WPTR(ctrl); + + if (rd_ptr != wr_ptr) { + dev_warn(dev, "desc list is not empty ! %s:%d, %s:%d\n", + "rptr", rd_ptr, "wptr", wr_ptr); + return; + } + + EP_DBG(ep, "rd_ptr:%d, wr_ptr:%d\n", rd_ptr, wr_ptr); + i = req->saved_dma_wptr; + + do { + len_in_desc = EP_DESC1_IN_LEN(ep->descs[i].des_1); + EP_DBG(ep, "desc[%d] len: %d\n", i, len_in_desc); + total_len += len_in_desc; + i++; + if (i >= AST_UDC_DESCS_COUNT) + i = 0; + + } while (i != wr_ptr); + + req->req.actual += total_len; + + EP_DBG(ep, "req @%p, length:(%d/%d), %s:0x%x\n", req, + req->req.actual, req->req.length, "len", total_len); + + /* Done this request */ + if (req->req.length == req->req.actual) { + ast_udc_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, + struct ast_udc_request, + queue); + + } else { + /* Check for short packet */ + if (total_len < ep->ep.maxpacket) { + ast_udc_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, + struct ast_udc_request, + queue); + } + } + + /* More requests & dma descs not setup yet */ + if (req && (req->actual_dma_length == req->req.actual)) { + EP_DBG(ep, "More requests\n"); + ast_udc_epn_kick_desc(ep, req); + } +} + +static void ast_udc_ep0_data_tx(struct ast_udc_dev *udc, u8 *tx_data, u32 len) +{ + if (len) { + memcpy(udc->ep0_buf, tx_data, len); + + ast_udc_write(udc, udc->ep0_buf_dma, AST_UDC_EP0_DATA_BUFF); + ast_udc_write(udc, EP0_TX_LEN(len), AST_UDC_EP0_CTRL); + ast_udc_write(udc, EP0_TX_LEN(len) | EP0_TX_BUFF_RDY, + AST_UDC_EP0_CTRL); + udc->is_control_tx = 1; + + } else + ast_udc_write(udc, EP0_TX_BUFF_RDY, AST_UDC_EP0_CTRL); +} + +static void ast_udc_getstatus(struct ast_udc_dev *udc) +{ + struct usb_ctrlrequest crq; + struct ast_udc_ep *ep; + u16 status = 0; + u16 epnum = 0; + + memcpy_fromio(&crq, udc->creq, sizeof(crq)); + + switch (crq.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + /* Get device status */ + status = 1 << USB_DEVICE_SELF_POWERED; + break; + case USB_RECIP_INTERFACE: + break; + case USB_RECIP_ENDPOINT: + epnum = crq.wIndex & USB_ENDPOINT_NUMBER_MASK; + if (epnum >= AST_UDC_NUM_ENDPOINTS) + goto stall; + status = udc->ep[epnum].stopped; + break; + default: + goto stall; + } + + ep = &udc->ep[epnum]; + EP_DBG(ep, "status: 0x%x\n", status); + ast_udc_ep0_data_tx(udc, (u8 *)&status, sizeof(status)); + + return; + +stall: + EP_DBG(ep, "Can't respond request\n"); + ast_udc_write(udc, ast_udc_read(udc, AST_UDC_EP0_CTRL) | EP0_STALL, + AST_UDC_EP0_CTRL); +} + +static void ast_udc_ep0_handle_setup(struct ast_udc_dev *udc) +{ + struct ast_udc_ep *ep = &udc->ep[0]; + struct ast_udc_request *req; + struct usb_ctrlrequest crq; + int req_num = 0; + int rc = 0; + u32 reg; + + memcpy_fromio(&crq, udc->creq, sizeof(crq)); + + SETUP_DBG(udc, "SETUP packet: %02x/%02x/%04x/%04x/%04x\n", + crq.bRequestType, crq.bRequest, le16_to_cpu(crq.wValue), + le16_to_cpu(crq.wIndex), le16_to_cpu(crq.wLength)); + + /* + * Cleanup ep0 request(s) in queue because + * there is a new control setup comes. + */ + list_for_each_entry(req, &udc->ep[0].queue, queue) { + req_num++; + EP_DBG(ep, "there is req %p in ep0 queue !\n", req); + } + + if (req_num) + ast_udc_nuke(&udc->ep[0], -ETIMEDOUT); + + udc->ep[0].dir_in = crq.bRequestType & USB_DIR_IN; + + if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (crq.bRequest) { + case USB_REQ_SET_ADDRESS: + if (ast_udc_read(udc, AST_UDC_STS) & UDC_STS_HIGHSPEED) + udc->gadget.speed = USB_SPEED_HIGH; + else + udc->gadget.speed = USB_SPEED_FULL; + + SETUP_DBG(udc, "set addr: 0x%x\n", crq.wValue); + reg = ast_udc_read(udc, AST_UDC_CONFIG); + reg &= ~UDC_CFG_ADDR_MASK; + reg |= UDC_CFG_SET_ADDR(crq.wValue); + ast_udc_write(udc, reg, AST_UDC_CONFIG); + goto req_complete; + + case USB_REQ_CLEAR_FEATURE: + SETUP_DBG(udc, "ep0: CLEAR FEATURE\n"); + goto req_driver; + + case USB_REQ_SET_FEATURE: + SETUP_DBG(udc, "ep0: SET FEATURE\n"); + goto req_driver; + + case USB_REQ_GET_STATUS: + ast_udc_getstatus(udc); + return; + + default: + goto req_driver; + } + + } + +req_driver: + if (udc->driver) { + SETUP_DBG(udc, "Forwarding %s to gadget...\n", + udc->gadget.name); + + spin_unlock(&udc->lock); + rc = udc->driver->setup(&udc->gadget, &crq); + spin_lock(&udc->lock); + + } else { + SETUP_DBG(udc, "No gadget for request !\n"); + } + + if (rc >= 0) + return; + + /* Stall if gadget failed */ + SETUP_DBG(udc, "Stalling, rc:0x%x\n", rc); + ast_udc_write(udc, ast_udc_read(udc, AST_UDC_EP0_CTRL) | EP0_STALL, + AST_UDC_EP0_CTRL); + return; + +req_complete: + SETUP_DBG(udc, "ep0: Sending IN status without data\n"); + ast_udc_write(udc, EP0_TX_BUFF_RDY, AST_UDC_EP0_CTRL); +} + +static irqreturn_t ast_udc_isr(int irq, void *data) +{ + struct ast_udc_dev *udc = (struct ast_udc_dev *)data; + struct ast_udc_ep *ep; + u32 isr, ep_isr; + int i; + + spin_lock(&udc->lock); + + isr = ast_udc_read(udc, AST_UDC_ISR); + if (!isr) + goto done; + + /* Ack interrupts */ + ast_udc_write(udc, isr, AST_UDC_ISR); + + if (isr & UDC_IRQ_BUS_RESET) { + ISR_DBG(udc, "UDC_IRQ_BUS_RESET\n"); + udc->gadget.speed = USB_SPEED_UNKNOWN; + + ep = &udc->ep[1]; + EP_DBG(ep, "dctrl:0x%x\n", + ast_ep_read(ep, AST_UDC_EP_DMA_CTRL)); + + if (udc->driver && udc->driver->reset) { + spin_unlock(&udc->lock); + udc->driver->reset(&udc->gadget); + spin_lock(&udc->lock); + } + } + + if (isr & UDC_IRQ_BUS_SUSPEND) { + ISR_DBG(udc, "UDC_IRQ_BUS_SUSPEND\n"); + udc->suspended_from = udc->gadget.state; + usb_gadget_set_state(&udc->gadget, USB_STATE_SUSPENDED); + + if (udc->driver && udc->driver->suspend) { + spin_unlock(&udc->lock); + udc->driver->suspend(&udc->gadget); + spin_lock(&udc->lock); + } + } + + if (isr & UDC_IRQ_BUS_RESUME) { + ISR_DBG(udc, "UDC_IRQ_BUS_RESUME\n"); + usb_gadget_set_state(&udc->gadget, udc->suspended_from); + + if (udc->driver && udc->driver->resume) { + spin_unlock(&udc->lock); + udc->driver->resume(&udc->gadget); + spin_lock(&udc->lock); + } + } + + if (isr & UDC_IRQ_EP0_IN_ACK_STALL) { + ISR_DBG(udc, "UDC_IRQ_EP0_IN_ACK_STALL\n"); + ast_udc_ep0_in(udc); + } + + if (isr & UDC_IRQ_EP0_OUT_ACK_STALL) { + ISR_DBG(udc, "UDC_IRQ_EP0_OUT_ACK_STALL\n"); + ast_udc_ep0_out(udc); + } + + if (isr & UDC_IRQ_EP0_SETUP) { + ISR_DBG(udc, "UDC_IRQ_EP0_SETUP\n"); + ast_udc_ep0_handle_setup(udc); + } + + if (isr & UDC_IRQ_EP_POOL_ACK_STALL) { + ISR_DBG(udc, "UDC_IRQ_EP_POOL_ACK_STALL\n"); + ep_isr = ast_udc_read(udc, AST_UDC_EP_ACK_ISR); + + /* Ack EP interrupts */ + ast_udc_write(udc, ep_isr, AST_UDC_EP_ACK_ISR); + + /* Handle each EP */ + for (i = 0; i < AST_UDC_NUM_ENDPOINTS - 1; i++) { + if (ep_isr & (0x1 << i)) { + ep = &udc->ep[i + 1]; + if (ep->desc_mode) + ast_udc_epn_handle_desc(udc, i + 1); + else + ast_udc_epn_handle(udc, i + 1); + } + } + } + +done: + spin_unlock(&udc->lock); + return IRQ_HANDLED; +} + +static int ast_udc_gadget_getframe(struct usb_gadget *gadget) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + + return (ast_udc_read(udc, AST_UDC_STS) >> 16) & 0x7ff; +} + +static void ast_udc_wake_work(struct work_struct *work) +{ + struct ast_udc_dev *udc = container_of(work, struct ast_udc_dev, + wake_work); + unsigned long flags; + u32 ctrl; + + spin_lock_irqsave(&udc->lock, flags); + + UDC_DBG(udc, "Wakeup Host !\n"); + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL); + ast_udc_write(udc, ctrl | USB_REMOTE_WAKEUP_EN, AST_UDC_FUNC_CTRL); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void ast_udc_wakeup_all(struct ast_udc_dev *udc) +{ + /* + * A device is trying to wake the world, because this + * can recurse into the device, we break the call chain + * using a work queue + */ + schedule_work(&udc->wake_work); +} + +static int ast_udc_wakeup(struct usb_gadget *gadget) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&udc->lock, flags); + + if (!udc->wakeup_en) { + UDC_DBG(udc, "Remote Wakeup is disabled\n"); + rc = -EINVAL; + goto err; + } + + UDC_DBG(udc, "Device initiated wakeup\n"); + ast_udc_wakeup_all(udc); + +err: + spin_unlock_irqrestore(&udc->lock, flags); + return rc; +} + +/* + * Activate/Deactivate link with host + */ +static int ast_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + unsigned long flags; + u32 ctrl; + + spin_lock_irqsave(&udc->lock, flags); + + UDC_DBG(udc, "is_on: %d\n", is_on); + if (is_on) + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL) | USB_UPSTREAM_EN; + else + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL) & ~USB_UPSTREAM_EN; + + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int ast_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + struct ast_udc_ep *ep; + unsigned long flags; + int i; + + spin_lock_irqsave(&udc->lock, flags); + + UDC_DBG(udc, "\n"); + udc->driver = driver; + udc->gadget.dev.of_node = udc->pdev->dev.of_node; + + for (i = 0; i < AST_UDC_NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + ep->stopped = 0; + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int ast_udc_stop(struct usb_gadget *gadget) +{ + struct ast_udc_dev *udc = to_ast_dev(gadget); + unsigned long flags; + u32 ctrl; + + spin_lock_irqsave(&udc->lock, flags); + + UDC_DBG(udc, "\n"); + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL) & ~USB_UPSTREAM_EN; + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->driver = NULL; + + ast_udc_stop_activity(udc); + usb_gadget_set_state(&udc->gadget, USB_STATE_NOTATTACHED); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static const struct usb_gadget_ops ast_udc_ops = { + .get_frame = ast_udc_gadget_getframe, + .wakeup = ast_udc_wakeup, + .pullup = ast_udc_pullup, + .udc_start = ast_udc_start, + .udc_stop = ast_udc_stop, +}; + +/* + * Support 1 Control Endpoint. + * Support multiple programmable endpoints that can be configured to + * Bulk IN/OUT, Interrupt IN/OUT, and Isochronous IN/OUT type endpoint. + */ +static void ast_udc_init_ep(struct ast_udc_dev *udc) +{ + struct ast_udc_ep *ep; + int i; + + for (i = 0; i < AST_UDC_NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + ep->ep.name = ast_ep_name[i]; + if (i == 0) { + ep->ep.caps.type_control = true; + } else { + ep->ep.caps.type_iso = true; + ep->ep.caps.type_bulk = true; + ep->ep.caps.type_int = true; + } + ep->ep.caps.dir_in = true; + ep->ep.caps.dir_out = true; + + ep->ep.ops = &ast_udc_ep_ops; + ep->udc = udc; + + INIT_LIST_HEAD(&ep->queue); + + if (i == 0) { + usb_ep_set_maxpacket_limit(&ep->ep, + AST_UDC_EP0_MAX_PACKET); + continue; + } + + ep->ep_reg = udc->reg + AST_UDC_EP_BASE + + (AST_UDC_EP_OFFSET * (i - 1)); + + ep->epn_buf = udc->ep0_buf + (i * AST_UDC_EP_DMA_SIZE); + ep->epn_buf_dma = udc->ep0_buf_dma + (i * AST_UDC_EP_DMA_SIZE); + usb_ep_set_maxpacket_limit(&ep->ep, AST_UDC_EPn_MAX_PACKET); + + ep->descs = ep->epn_buf + AST_UDC_EPn_MAX_PACKET; + ep->descs_dma = ep->epn_buf_dma + AST_UDC_EPn_MAX_PACKET; + ep->descs_wptr = 0; + + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + } +} + +static void ast_udc_init_dev(struct ast_udc_dev *udc) +{ + INIT_WORK(&udc->wake_work, ast_udc_wake_work); +} + +static void ast_udc_init_hw(struct ast_udc_dev *udc) +{ + u32 ctrl; + + /* Enable PHY */ + ctrl = USB_PHY_CLK_EN | USB_PHY_RESET_DIS; + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + + udelay(1); + ast_udc_write(udc, 0, AST_UDC_DEV_RESET); + + /* Set descriptor ring size */ + if (AST_UDC_DESCS_COUNT == 256) { + ctrl |= USB_EP_LONG_DESC; + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + } + + /* Mask & ack all interrupts before installing the handler */ + ast_udc_write(udc, 0, AST_UDC_IER); + ast_udc_write(udc, UDC_IRQ_ACK_ALL, AST_UDC_ISR); + + /* Enable some interrupts */ + ctrl = UDC_IRQ_EP_POOL_ACK_STALL | UDC_IRQ_BUS_RESUME | + UDC_IRQ_BUS_SUSPEND | UDC_IRQ_BUS_RESET | + UDC_IRQ_EP0_IN_ACK_STALL | UDC_IRQ_EP0_OUT_ACK_STALL | + UDC_IRQ_EP0_SETUP; + ast_udc_write(udc, ctrl, AST_UDC_IER); + + /* Cleanup and enable ep ACK interrupts */ + ast_udc_write(udc, UDC_IRQ_EP_ACK_ALL, AST_UDC_EP_ACK_IER); + ast_udc_write(udc, UDC_IRQ_EP_ACK_ALL, AST_UDC_EP_ACK_ISR); + + ast_udc_write(udc, 0, AST_UDC_EP0_CTRL); +} + +static void ast_udc_cleanup(struct platform_device *pdev) +{ + struct ast_udc_dev *udc = platform_get_drvdata(pdev); + unsigned long flags; + u32 ctrl; + + spin_lock_irqsave(&udc->lock, flags); + + /* Disable upstream port connection */ + ctrl = ast_udc_read(udc, AST_UDC_FUNC_CTRL) & ~USB_UPSTREAM_EN; + ast_udc_write(udc, ctrl, AST_UDC_FUNC_CTRL); + + clk_disable_unprepare(udc->clk); + + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->ep0_buf) + dma_free_coherent(&pdev->dev, + AST_UDC_EP_DMA_SIZE * AST_UDC_NUM_ENDPOINTS, + udc->ep0_buf, + udc->ep0_buf_dma); + + udc->ep0_buf = NULL; +} + +static void ast_udc_remove(struct platform_device *pdev) +{ + struct ast_udc_dev *udc = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&udc->gadget); + if (udc->driver) { + /* + * This is broken as only some cleanup is skipped, *udev is + * freed and the register mapping goes away. Any further usage + * probably crashes. Also the device is unbound, so the skipped + * cleanup is never catched up later. + */ + dev_alert(&pdev->dev, + "Driver is busy and still going away. Fasten your seat belts!\n"); + return; + } + + ast_udc_cleanup(pdev); +} + +static int ast_udc_probe(struct platform_device *pdev) +{ + enum usb_device_speed max_speed; + struct device *dev = &pdev->dev; + struct ast_udc_dev *udc; + int rc; + + udc = devm_kzalloc(&pdev->dev, sizeof(struct ast_udc_dev), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + udc->gadget.dev.parent = dev; + udc->pdev = pdev; + spin_lock_init(&udc->lock); + + udc->gadget.ops = &ast_udc_ops; + udc->gadget.ep0 = &udc->ep[0].ep; + udc->gadget.name = "aspeed-udc"; + udc->gadget.dev.init_name = "gadget"; + + udc->reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(udc->reg)) { + dev_err(&pdev->dev, "Failed to map resources\n"); + return PTR_ERR(udc->reg); + } + + platform_set_drvdata(pdev, udc); + + udc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(udc->clk)) { + rc = PTR_ERR(udc->clk); + goto err; + } + rc = clk_prepare_enable(udc->clk); + if (rc) { + dev_err(&pdev->dev, "Failed to enable clock (0x%x)\n", rc); + goto err; + } + + /* Check if we need to limit the HW to USB1 */ + max_speed = usb_get_maximum_speed(&pdev->dev); + if (max_speed != USB_SPEED_UNKNOWN && max_speed < USB_SPEED_HIGH) + udc->force_usb1 = true; + + /* + * Allocate DMA buffers for all EPs in one chunk + */ + udc->ep0_buf = dma_alloc_coherent(&pdev->dev, + AST_UDC_EP_DMA_SIZE * + AST_UDC_NUM_ENDPOINTS, + &udc->ep0_buf_dma, GFP_KERNEL); + + if (!udc->ep0_buf) { + clk_disable_unprepare(udc->clk); + rc = -ENOMEM; + goto err; + } + + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->creq = udc->reg + AST_UDC_SETUP0; + + /* + * Support single stage mode or 32/256 stages descriptor mode. + * Set default as Descriptor Mode. + */ + udc->desc_mode = AST_UDC_DESC_MODE; + + dev_info(&pdev->dev, "DMA %s\n", udc->desc_mode ? + "descriptor mode" : "single mode"); + + INIT_LIST_HEAD(&udc->gadget.ep_list); + INIT_LIST_HEAD(&udc->gadget.ep0->ep_list); + + /* Initialized udc ep */ + ast_udc_init_ep(udc); + + /* Initialized udc device */ + ast_udc_init_dev(udc); + + /* Initialized udc hardware */ + ast_udc_init_hw(udc); + + /* Find interrupt and install handler */ + udc->irq = platform_get_irq(pdev, 0); + if (udc->irq < 0) { + rc = udc->irq; + goto err_cleanup; + } + + rc = devm_request_irq(&pdev->dev, udc->irq, ast_udc_isr, 0, + KBUILD_MODNAME, udc); + if (rc) { + dev_err(&pdev->dev, "Failed to request interrupt\n"); + goto err_cleanup; + } + + rc = usb_add_gadget_udc(&pdev->dev, &udc->gadget); + if (rc) { + dev_err(&pdev->dev, "Failed to add gadget udc\n"); + goto err_cleanup; + } + + dev_info(&pdev->dev, "Initialized udc in USB%s mode\n", + udc->force_usb1 ? "1" : "2"); + + return 0; + +err_cleanup: + ast_udc_cleanup(pdev); +err: + dev_err(&pdev->dev, "Failed to udc probe, rc:0x%x\n", rc); + + return rc; +} + +static const struct of_device_id ast_udc_of_dt_ids[] = { + { .compatible = "aspeed,ast2600-udc", }, + {} +}; + +MODULE_DEVICE_TABLE(of, ast_udc_of_dt_ids); + +static struct platform_driver ast_udc_driver = { + .probe = ast_udc_probe, + .remove = ast_udc_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ast_udc_of_dt_ids, + }, +}; + +module_platform_driver(ast_udc_driver); + +MODULE_DESCRIPTION("ASPEED UDC driver"); +MODULE_AUTHOR("Neal Liu "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/at91_udc.c b/drivers/usb/gadget/udc/at91_udc.c new file mode 100644 index 000000000..8e5d7fb71 --- /dev/null +++ b/drivers/usb/gadget/udc/at91_udc.c @@ -0,0 +1,2039 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#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 "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 struct { + const char *name; + const struct usb_ep_caps caps; +} ep_info[] = { +#define EP_INFO(_name, _caps) \ + { \ + .name = _name, \ + .caps = _caps, \ + } + + EP_INFO("ep0", + USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep1", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep2", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep3-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep4", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep5", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_ALL)), + +#undef EP_INFO +}; + +#define ep0name ep_info[0].name + +#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, + str_enabled_disabled(csr & (1 << 15)), + (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->gadget.is_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 void create_debug_file(struct at91_udc *udc) +{ + udc->pde = proc_create_single_data(debug_filename, 0, NULL, + proc_udc_show, 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); + usb_gadget_giveback_request(&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_obj(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 = NULL, *iter; + 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(iter, &ep->queue, queue) { + if (&iter->req != _req) + continue; + req = iter; + break; + } + if (!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; + 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 0; +} + +/* 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); + udc->gadget.quirk_stall_not_supp = 1; + + 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; + usb_ep_set_maxpacket_limit(&ep->ep, 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 reset_gadget(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); + usb_gadget_udc_reset(&udc->gadget, driver); + spin_lock(&udc->lock); + } + + udc_reinit(udc); +} + +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) +{ + 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); + } 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); + clk_off(udc); + } + + if (udc->caps && udc->caps->pullup) + udc->caps->pullup(udc, is_on); +} + +/* 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); + gadget->is_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); + +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 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->gadget.is_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; + reset_gadget(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 at91_vbus_update(struct at91_udc *udc, unsigned value) +{ + 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, gpiod_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, gpiod_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(struct timer_list *t) +{ + struct at91_udc *udc = timer_container_of(udc, t, vbus_timer); + + /* + * 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->gadget.is_selfpowered = 1; + + return 0; +} + +static int at91_stop(struct usb_gadget *gadget) +{ + 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; + + 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 int at91rm9200_udc_init(struct at91_udc *udc) +{ + struct at91_ep *ep; + int i; + + for (i = 0; i < NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + + switch (i) { + case 0: + case 3: + ep->maxpacket = 8; + break; + case 1 ... 2: + ep->maxpacket = 64; + break; + case 4 ... 5: + ep->maxpacket = 256; + break; + } + } + + if (!udc->board.pullup_pin) { + DBG("no D+ pullup?\n"); + return -ENODEV; + } + + gpiod_direction_output(udc->board.pullup_pin, + gpiod_is_active_low(udc->board.pullup_pin)); + + return 0; +} + +static void at91rm9200_udc_pullup(struct at91_udc *udc, int is_on) +{ + gpiod_set_value(udc->board.pullup_pin, is_on); +} + +static const struct at91_udc_caps at91rm9200_udc_caps = { + .init = at91rm9200_udc_init, + .pullup = at91rm9200_udc_pullup, +}; + +static int at91sam9260_udc_init(struct at91_udc *udc) +{ + struct at91_ep *ep; + int i; + + for (i = 0; i < NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + + switch (i) { + case 0 ... 3: + ep->maxpacket = 64; + break; + case 4 ... 5: + ep->maxpacket = 512; + break; + } + } + + return 0; +} + +static void at91sam9260_udc_pullup(struct at91_udc *udc, int is_on) +{ + u32 txvc = at91_udp_read(udc, AT91_UDP_TXVC); + + if (is_on) + txvc |= AT91_UDP_TXVC_PUON; + else + txvc &= ~AT91_UDP_TXVC_PUON; + + at91_udp_write(udc, AT91_UDP_TXVC, txvc); +} + +static const struct at91_udc_caps at91sam9260_udc_caps = { + .init = at91sam9260_udc_init, + .pullup = at91sam9260_udc_pullup, +}; + +static int at91sam9261_udc_init(struct at91_udc *udc) +{ + struct at91_ep *ep; + int i; + + for (i = 0; i < NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + + switch (i) { + case 0: + ep->maxpacket = 8; + break; + case 1 ... 3: + ep->maxpacket = 64; + break; + case 4 ... 5: + ep->maxpacket = 256; + break; + } + } + + udc->matrix = syscon_regmap_lookup_by_phandle(udc->pdev->dev.of_node, + "atmel,matrix"); + return PTR_ERR_OR_ZERO(udc->matrix); +} + +static void at91sam9261_udc_pullup(struct at91_udc *udc, int is_on) +{ + u32 usbpucr = 0; + + if (is_on) + usbpucr = AT91_MATRIX_USBPUCR_PUON; + + regmap_update_bits(udc->matrix, AT91SAM9261_MATRIX_USBPUCR, + AT91_MATRIX_USBPUCR_PUON, usbpucr); +} + +static const struct at91_udc_caps at91sam9261_udc_caps = { + .init = at91sam9261_udc_init, + .pullup = at91sam9261_udc_pullup, +}; + +static int at91sam9263_udc_init(struct at91_udc *udc) +{ + struct at91_ep *ep; + int i; + + for (i = 0; i < NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + + switch (i) { + case 0: + case 1: + case 2: + case 3: + ep->maxpacket = 64; + break; + case 4: + case 5: + ep->maxpacket = 256; + break; + } + } + + return 0; +} + +static const struct at91_udc_caps at91sam9263_udc_caps = { + .init = at91sam9263_udc_init, + .pullup = at91sam9260_udc_pullup, +}; + +static const struct of_device_id at91_udc_dt_ids[] = { + { + .compatible = "atmel,at91rm9200-udc", + .data = &at91rm9200_udc_caps, + }, + { + .compatible = "atmel,at91sam9260-udc", + .data = &at91sam9260_udc_caps, + }, + { + .compatible = "atmel,at91sam9261-udc", + .data = &at91sam9261_udc_caps, + }, + { + .compatible = "atmel,at91sam9263-udc", + .data = &at91sam9263_udc_caps, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, at91_udc_dt_ids); + +static void at91udc_of_init(struct at91_udc *udc, struct device_node *np) +{ + struct at91_udc_data *board = &udc->board; + const struct of_device_id *match; + u32 val; + + if (of_property_read_u32(np, "atmel,vbus-polled", &val) == 0) + board->vbus_polled = 1; + + board->vbus_pin = fwnode_gpiod_get_index(of_fwnode_handle(np), + "atmel,vbus", 0, GPIOD_IN, + "udc_vbus"); + if (IS_ERR(board->vbus_pin)) + board->vbus_pin = NULL; + + board->pullup_pin = fwnode_gpiod_get_index(of_fwnode_handle(np), + "atmel,pullup", 0, + GPIOD_ASIS, "udc_pullup"); + if (IS_ERR(board->pullup_pin)) + board->pullup_pin = NULL; + + match = of_match_node(at91_udc_dt_ids, np); + if (match) + udc->caps = match->data; +} + +/* + * The work handler re-arms this timer, so shut the timer down before + * draining the work; otherwise it restarts the polling cycle. + */ +static void at91_udc_shutdown_vbus_timer(struct at91_udc *udc) +{ + if (!(udc->board.vbus_pin && udc->board.vbus_polled)) + return; + + timer_shutdown_sync(&udc->vbus_timer); + cancel_work_sync(&udc->vbus_timer_work); +} + +static int at91udc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct at91_udc *udc; + int retval; + struct at91_ep *ep; + int i; + + udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + /* init software state */ + udc->gadget.dev.parent = dev; + at91udc_of_init(udc, pdev->dev.of_node); + udc->pdev = pdev; + udc->enabled = 0; + spin_lock_init(&udc->lock); + + udc->gadget.ops = &at91_udc_ops; + udc->gadget.ep0 = &udc->ep[0].ep; + udc->gadget.name = driver_name; + udc->gadget.dev.init_name = "gadget"; + + for (i = 0; i < NUM_ENDPOINTS; i++) { + ep = &udc->ep[i]; + ep->ep.name = ep_info[i].name; + ep->ep.caps = ep_info[i].caps; + ep->ep.ops = &at91_ep_ops; + ep->udc = udc; + ep->int_mask = BIT(i); + if (i != 0 && i != 3) + ep->is_pingpong = 1; + } + + udc->udp_baseaddr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(udc->udp_baseaddr)) + return PTR_ERR(udc->udp_baseaddr); + + if (udc->caps && udc->caps->init) { + retval = udc->caps->init(udc); + if (retval) + return retval; + } + + udc_reinit(udc); + + /* get interface and function clocks */ + udc->iclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(udc->iclk)) + return PTR_ERR(udc->iclk); + + udc->fclk = devm_clk_get(dev, "hclk"); + if (IS_ERR(udc->fclk)) + return PTR_ERR(udc->fclk); + + /* don't do anything until we have both gadget driver and VBUS */ + clk_set_rate(udc->fclk, 48000000); + retval = clk_prepare(udc->fclk); + if (retval) + return retval; + + retval = clk_prepare_enable(udc->iclk); + if (retval) + goto err_unprepare_fclk; + + 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 = retval = platform_get_irq(pdev, 0); + if (retval < 0) + goto err_unprepare_iclk; + retval = devm_request_irq(dev, udc->udp_irq, at91_udc_irq, 0, + driver_name, udc); + if (retval) { + DBG("request irq %d failed\n", udc->udp_irq); + goto err_unprepare_iclk; + } + + if (udc->board.vbus_pin) { + gpiod_direction_input(udc->board.vbus_pin); + + /* + * Get the initial state of VBUS - we cannot expect + * a pending interrupt. + */ + udc->vbus = gpiod_get_value_cansleep(udc->board.vbus_pin); + + if (udc->board.vbus_polled) { + INIT_WORK(&udc->vbus_timer_work, at91_vbus_timer_work); + timer_setup(&udc->vbus_timer, at91_vbus_timer, 0); + mod_timer(&udc->vbus_timer, + jiffies + VBUS_POLL_TIMEOUT); + } else { + retval = devm_request_irq(dev, + gpiod_to_irq(udc->board.vbus_pin), + at91_vbus_irq, 0, driver_name, udc); + if (retval) { + DBG("request vbus irq %d failed\n", + desc_to_gpio(udc->board.vbus_pin)); + goto err_unprepare_iclk; + } + } + } else { + DBG("no VBUS detection, assuming always-on\n"); + udc->vbus = 1; + } + retval = usb_add_gadget_udc(dev, &udc->gadget); + if (retval) + goto err_shutdown_vbus; + 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; + +err_shutdown_vbus: + at91_udc_shutdown_vbus_timer(udc); +err_unprepare_iclk: + clk_unprepare(udc->iclk); +err_unprepare_fclk: + clk_unprepare(udc->fclk); + + DBG("%s probe failed, %d\n", driver_name, retval); + + return retval; +} + +static void at91udc_remove(struct platform_device *pdev) +{ + struct at91_udc *udc = platform_get_drvdata(pdev); + unsigned long flags; + + DBG("remove\n"); + + usb_del_gadget_udc(&udc->gadget); + + at91_udc_shutdown_vbus_timer(udc); + + if (udc->driver) { + dev_err(&pdev->dev, + "Driver still in use but removing anyhow\n"); + return; + } + + 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); + clk_unprepare(udc->fclk); + clk_unprepare(udc->iclk); +} + +#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 (udc->board.vbus_pin && !udc->board.vbus_polled && wake) + enable_irq_wake(gpiod_to_irq(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 (udc->board.vbus_pin && !udc->board.vbus_polled && + udc->active_suspend) + disable_irq_wake(gpiod_to_irq(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 + +static struct platform_driver at91_udc_driver = { + .probe = at91udc_probe, + .remove = at91udc_remove, + .shutdown = at91udc_shutdown, + .suspend = at91udc_suspend, + .resume = at91udc_resume, + .driver = { + .name = driver_name, + .of_match_table = at91_udc_dt_ids, + }, +}; + +module_platform_driver(at91_udc_driver); + +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/udc/at91_udc.h b/drivers/usb/gadget/udc/at91_udc.h new file mode 100644 index 000000000..28c1042f8 --- /dev/null +++ b/drivers/usb/gadget/udc/at91_udc.h @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2004 by Thomas Rathbone, HP Labs + * Copyright (C) 2005 by Ivan Kokshaysky + * Copyright (C) 2006 by SAN People + */ + +#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; +}; + +struct at91_udc_caps { + int (*init)(struct at91_udc *udc); + void (*pullup)(struct at91_udc *udc, int is_on); +}; + +struct at91_udc_data { + struct gpio_desc *vbus_pin; /* high == host powering us */ + u8 vbus_polled; /* Use polling, not interrupt */ + struct gpio_desc *pullup_pin; /* active == D+ pulled up */ +}; + +/* + * 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; + const struct at91_udc_caps *caps; + 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 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; + struct regmap *matrix; +}; + +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_warn("udc: " stuff) +#define INFO(stuff...) pr_info("udc: " stuff) +#define DBG(stuff...) pr_debug("udc: " stuff) + +#endif + diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c new file mode 100644 index 000000000..72a2f95ff --- /dev/null +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -0,0 +1,2459 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Atmel USBA high speed USB device controller + * + * Copyright (C) 2005-2007 Atmel Corporation + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atmel_usba_udc.h" +#define USBA_VBUS_IRQFLAGS (IRQF_ONESHOT \ + | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING) + +#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_obj(*queue_data); + 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(buf, nbytes)) + return -EFAULT; + + inode_lock(file_inode(file)); + list_for_each_entry_safe(req, tmp_req, queue, queue) { + len = scnprintf(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'); + 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; + } + inode_unlock(file_inode(file)); + + 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; + + inode_lock(inode); + 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] = readl_relaxed(udc->regs + i * 4); + spin_unlock_irq(&udc->lock); + + file->private_data = data; + ret = 0; + +out: + inode_unlock(inode); + + 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; + + inode_lock(inode); + ret = simple_read_from_buffer(buf, nbytes, ppos, + file->private_data, + file_inode(file)->i_size); + inode_unlock(inode); + + return ret; +} + +static int regs_dbg_release(struct inode *inode, struct file *file) +{ + kfree(file->private_data); + return 0; +} + +static const struct file_operations queue_dbg_fops = { + .owner = THIS_MODULE, + .open = queue_dbg_open, + .read = queue_dbg_read, + .release = queue_dbg_release, +}; + +static 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); + ep->debugfs_dir = ep_root; + + debugfs_create_file("queue", 0400, ep_root, ep, &queue_dbg_fops); + if (ep->can_dma) + debugfs_create_u32("dma_status", 0400, ep_root, + &ep->last_dma_status); + if (ep_is_control(ep)) + debugfs_create_u32("state", 0400, ep_root, &ep->state); +} + +static void usba_ep_cleanup_debugfs(struct usba_ep *ep) +{ + debugfs_remove_recursive(ep->debugfs_dir); +} + +static void usba_init_debugfs(struct usba_udc *udc) +{ + struct dentry *root; + struct resource *regs_resource; + + root = debugfs_create_dir(udc->gadget.name, usb_debug_root); + udc->debugfs_root = root; + + regs_resource = platform_get_resource(udc->pdev, IORESOURCE_MEM, + CTRL_IOMEM_ID); + + if (regs_resource) { + debugfs_create_file_size("regs", 0400, root, udc, + ®s_dbg_fops, + resource_size(regs_resource)); + } + + usba_ep_init_debugfs(udc, to_usba_ep(udc->gadget.ep0)); +} + +static void usba_cleanup_debugfs(struct usba_udc *udc) +{ + usba_ep_cleanup_debugfs(to_usba_ep(udc->gadget.ep0)); + debugfs_remove_recursive(udc->debugfs_root); +} +#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 ushort fifo_mode; + +module_param(fifo_mode, ushort, 0x0); +MODULE_PARM_DESC(fifo_mode, "Endpoint configuration mode"); + +/* mode 0 - uses autoconfig */ + +/* mode 1 - fits in 8KB, generic max fifo configuration */ +static struct usba_fifo_cfg mode_1_cfg[] = { +{ .hw_ep_num = 0, .fifo_size = 64, .nr_banks = 1, }, +{ .hw_ep_num = 1, .fifo_size = 1024, .nr_banks = 2, }, +{ .hw_ep_num = 2, .fifo_size = 1024, .nr_banks = 1, }, +{ .hw_ep_num = 3, .fifo_size = 1024, .nr_banks = 1, }, +{ .hw_ep_num = 4, .fifo_size = 1024, .nr_banks = 1, }, +{ .hw_ep_num = 5, .fifo_size = 1024, .nr_banks = 1, }, +{ .hw_ep_num = 6, .fifo_size = 1024, .nr_banks = 1, }, +}; + +/* mode 2 - fits in 8KB, performance max fifo configuration */ +static struct usba_fifo_cfg mode_2_cfg[] = { +{ .hw_ep_num = 0, .fifo_size = 64, .nr_banks = 1, }, +{ .hw_ep_num = 1, .fifo_size = 1024, .nr_banks = 3, }, +{ .hw_ep_num = 2, .fifo_size = 1024, .nr_banks = 2, }, +{ .hw_ep_num = 3, .fifo_size = 1024, .nr_banks = 2, }, +}; + +/* mode 3 - fits in 8KB, mixed fifo configuration */ +static struct usba_fifo_cfg mode_3_cfg[] = { +{ .hw_ep_num = 0, .fifo_size = 64, .nr_banks = 1, }, +{ .hw_ep_num = 1, .fifo_size = 1024, .nr_banks = 2, }, +{ .hw_ep_num = 2, .fifo_size = 512, .nr_banks = 2, }, +{ .hw_ep_num = 3, .fifo_size = 512, .nr_banks = 2, }, +{ .hw_ep_num = 4, .fifo_size = 512, .nr_banks = 2, }, +{ .hw_ep_num = 5, .fifo_size = 512, .nr_banks = 2, }, +{ .hw_ep_num = 6, .fifo_size = 512, .nr_banks = 2, }, +}; + +/* mode 4 - fits in 8KB, custom fifo configuration */ +static struct usba_fifo_cfg mode_4_cfg[] = { +{ .hw_ep_num = 0, .fifo_size = 64, .nr_banks = 1, }, +{ .hw_ep_num = 1, .fifo_size = 512, .nr_banks = 2, }, +{ .hw_ep_num = 2, .fifo_size = 512, .nr_banks = 2, }, +{ .hw_ep_num = 3, .fifo_size = 8, .nr_banks = 2, }, +{ .hw_ep_num = 4, .fifo_size = 512, .nr_banks = 2, }, +{ .hw_ep_num = 5, .fifo_size = 512, .nr_banks = 2, }, +{ .hw_ep_num = 6, .fifo_size = 16, .nr_banks = 2, }, +{ .hw_ep_num = 7, .fifo_size = 8, .nr_banks = 2, }, +{ .hw_ep_num = 8, .fifo_size = 8, .nr_banks = 2, }, +}; +/* Add additional configurations here */ + +static int usba_config_fifo_table(struct usba_udc *udc) +{ + int n; + + switch (fifo_mode) { + default: + fifo_mode = 0; + fallthrough; + case 0: + udc->fifo_cfg = NULL; + n = 0; + break; + case 1: + udc->fifo_cfg = mode_1_cfg; + n = ARRAY_SIZE(mode_1_cfg); + break; + case 2: + udc->fifo_cfg = mode_2_cfg; + n = ARRAY_SIZE(mode_2_cfg); + break; + case 3: + udc->fifo_cfg = mode_3_cfg; + n = ARRAY_SIZE(mode_3_cfg); + break; + case 4: + udc->fifo_cfg = mode_4_cfg; + n = ARRAY_SIZE(mode_4_cfg); + break; + } + DBG(DBG_HW, "Setup fifo_mode %d\n", fifo_mode); + + return n; +} + +static inline u32 usba_int_enb_get(struct usba_udc *udc) +{ + return udc->int_enb_cache; +} + +static inline void usba_int_enb_set(struct usba_udc *udc, u32 mask) +{ + u32 val; + + val = udc->int_enb_cache | mask; + usba_writel(udc, INT_ENB, val); + udc->int_enb_cache = val; +} + +static inline void usba_int_enb_clear(struct usba_udc *udc, u32 mask) +{ + u32 val; + + val = udc->int_enb_cache & ~mask; + usba_writel(udc, INT_ENB, val); + udc->int_enb_cache = val; +} + +static int vbus_is_present(struct usba_udc *udc) +{ + if (udc->vbus_pin) + return gpiod_get_value(udc->vbus_pin); + + /* No Vbus detection: Assume always present */ + return 1; +} + +static void toggle_bias(struct usba_udc *udc, int is_on) +{ + if (udc->errata && udc->errata->toggle_bias) + udc->errata->toggle_bias(udc, is_on); +} + +static void generate_bias_pulse(struct usba_udc *udc) +{ + if (!udc->bias_pulse_needed) + return; + + if (udc->errata && udc->errata->pulse_bias) + udc->errata->pulse_bias(udc); + + udc->bias_pulse_needed = false; +} + +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); + if (ep_is_control(ep)) + usba_ep_writel(ep, CTL_ENB, USBA_TX_COMPLETE); + } else { + if (ep_is_control(ep)) + 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); + usb_gadget_giveback_request(&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); + usb_gadget_giveback_request(&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, maxpacket; + unsigned int nr_trans; + + DBG(DBG_GADGET, "%s: ep_enable: desc=%p\n", ep->ep.name, desc); + + maxpacket = usb_endpoint_maxp(desc); + + 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; + + DBG(DBG_ERR, "%s: EPT_CFG = 0x%lx (maxpacket = %lu)\n", + ep->ep.name, ep->ept_cfg, maxpacket); + + if (usb_endpoint_dir_in(desc)) { + ep->is_in = 1; + ep->ept_cfg |= USBA_EPT_DIR_IN; + } + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_CONTROL: + ep->ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_CONTROL); + 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_mult(desc); + if (nr_trans > 3) + return -EINVAL; + + ep->is_isoc = 1; + ep->ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_ISO); + ep->ept_cfg |= USBA_BF(NB_TRANS, nr_trans); + + break; + case USB_ENDPOINT_XFER_BULK: + ep->ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_BULK); + break; + case USB_ENDPOINT_XFER_INT: + ep->ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_INT); + break; + } + + spin_lock_irqsave(&ep->udc->lock, flags); + + ep->ep.desc = desc; + ep->ep.maxpacket = maxpacket; + + usba_ep_writel(ep, CFG, ep->ept_cfg); + usba_ep_writel(ep, CTL_ENB, USBA_EPT_ENABLE); + + if (ep->can_dma) { + u32 ctrl; + + usba_int_enb_set(udc, 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_int_enb_set(udc, 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_int_enb_get(udc)); + + 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); + 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_int_enb_clear(udc, 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_obj(*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/%pad %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_BUF_EN; + + if (!ep->is_in) + req->ctrl |= USBA_DMA_END_TR_EN | USBA_DMA_END_TR_IE; + + /* + * 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 = NULL; + struct usba_request *iter; + 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); + + list_for_each_entry(iter, &ep->queue, queue) { + if (&iter->req != _req) + continue; + req = iter; + break; + } + + if (!req) { + spin_unlock_irqrestore(&udc->lock, flags); + return -EINVAL; + } + + 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; + + gadget->is_selfpowered = (is_selfpowered != 0); + 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_pullup(struct usb_gadget *gadget, int is_on); +static int atmel_usba_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static int atmel_usba_stop(struct usb_gadget *gadget); + +static struct usb_ep *atmel_usba_match_ep(struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *ep_comp) +{ + struct usb_ep *_ep; + struct usba_ep *ep; + + /* Look at endpoints until an unclaimed one looks usable */ + list_for_each_entry(_ep, &gadget->ep_list, ep_list) { + if (usb_gadget_ep_match_desc(gadget, _ep, desc, ep_comp)) + goto found_ep; + } + /* Fail */ + return NULL; + +found_ep: + + if (fifo_mode == 0) { + /* Optimize hw fifo size based on ep type and other info */ + ep = to_usba_ep(_ep); + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_CONTROL: + ep->nr_banks = 1; + break; + + case USB_ENDPOINT_XFER_ISOC: + ep->fifo_size = 1024; + if (ep->udc->ep_prealloc) + ep->nr_banks = 2; + break; + + case USB_ENDPOINT_XFER_BULK: + ep->fifo_size = 512; + if (ep->udc->ep_prealloc) + ep->nr_banks = 1; + break; + + case USB_ENDPOINT_XFER_INT: + if (desc->wMaxPacketSize == 0) + ep->fifo_size = + roundup_pow_of_two(_ep->maxpacket_limit); + else + ep->fifo_size = + roundup_pow_of_two(le16_to_cpu(desc->wMaxPacketSize)); + if (ep->udc->ep_prealloc) + ep->nr_banks = 1; + break; + } + + /* It might be a little bit late to set this */ + usb_ep_set_maxpacket_limit(&ep->ep, ep->fifo_size); + + /* Generate ept_cfg basd on FIFO size and number of banks */ + if (ep->fifo_size <= 8) + ep->ept_cfg = USBA_BF(EPT_SIZE, USBA_EPT_SIZE_8); + else + /* LSB is bit 1, not 0 */ + ep->ept_cfg = + USBA_BF(EPT_SIZE, fls(ep->fifo_size - 1) - 3); + + ep->ept_cfg |= USBA_BF(BK_NUMBER, ep->nr_banks); + } + + return _ep; +} + +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, + .pullup = atmel_usba_pullup, + .udc_start = atmel_usba_start, + .udc_stop = atmel_usba_stop, + .match_ep = atmel_usba_match_ep, +}; + +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 const struct usb_gadget usba_gadget_template = { + .ops = &usba_udc_ops, + .max_speed = USB_SPEED_HIGH, + .name = "atmel_usba_udc", +}; + +/* + * 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); + } +} + +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 = &udc->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 = &udc->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; + writew_relaxed(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_warn("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); + } +} + +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 int start_clock(struct usba_udc *udc); +static void stop_clock(struct usba_udc *udc); + +static irqreturn_t usba_udc_irq(int irq, void *devid) +{ + struct usba_udc *udc = devid; + u32 status, int_enb; + u32 dma_status; + u32 ep_status; + + spin_lock(&udc->lock); + + int_enb = usba_int_enb_get(udc); + status = usba_readl(udc, INT_STA) & (int_enb | USBA_HIGH_SPEED); + DBG(DBG_INT, "irq, status=%#08x\n", status); + + if (status & USBA_DET_SUSPEND) { + usba_writel(udc, INT_CLR, USBA_DET_SUSPEND|USBA_WAKE_UP); + usba_int_enb_set(udc, USBA_WAKE_UP); + usba_int_enb_clear(udc, USBA_DET_SUSPEND); + udc->suspended = true; + toggle_bias(udc, 0); + udc->bias_pulse_needed = true; + stop_clock(udc); + 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) { + start_clock(udc); + toggle_bias(udc, 1); + usba_writel(udc, INT_CLR, USBA_WAKE_UP); + DBG(DBG_BUS, "Wake Up CPU detected\n"); + } + + if (status & USBA_END_OF_RESUME) { + udc->suspended = false; + usba_writel(udc, INT_CLR, USBA_END_OF_RESUME); + usba_int_enb_clear(udc, USBA_WAKE_UP); + usba_int_enb_set(udc, USBA_DET_SUSPEND); + generate_bias_pulse(udc); + 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; + + usba_int_enb_set(udc, USBA_DET_SUSPEND); + + for (i = 1; i <= USBA_NR_DMAS; i++) + if (dma_status & (1 << i)) + usba_dma_irq(udc, &udc->usba_ep[i]); + } + + ep_status = USBA_BFEXT(EPT_INT, status); + if (ep_status) { + int i; + + usba_int_enb_set(udc, USBA_DET_SUSPEND); + + for (i = 0; i < udc->num_ep; i++) + if (ep_status & (1 << i)) { + if (ep_is_control(&udc->usba_ep[i])) + usba_control_irq(udc, &udc->usba_ep[i]); + else + usba_ep_irq(udc, &udc->usba_ep[i]); + } + } + + if (status & USBA_END_OF_RESET) { + struct usba_ep *ep0, *ep; + int i; + + usba_writel(udc, INT_CLR, + USBA_END_OF_RESET|USBA_END_OF_RESUME + |USBA_DET_SUSPEND|USBA_WAKE_UP); + generate_bias_pulse(udc); + reset_all_endpoints(udc); + + if (udc->gadget.speed != USB_SPEED_UNKNOWN && udc->driver) { + udc->gadget.speed = USB_SPEED_UNKNOWN; + spin_unlock(&udc->lock); + usb_gadget_udc_reset(&udc->gadget, udc->driver); + 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 = &udc->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); + + /* If we get reset while suspended... */ + udc->suspended = false; + usba_int_enb_clear(udc, USBA_WAKE_UP); + + usba_int_enb_set(udc, 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_err(&udc->pdev->dev, + "ODD: EP0 configuration is invalid!\n"); + + /* Preallocate other endpoints */ + for (i = 1; i < udc->num_ep; i++) { + ep = &udc->usba_ep[i]; + if (ep->ep.claimed) { + usba_ep_writel(ep, CFG, ep->ept_cfg); + if (!(usba_ep_readl(ep, CFG) & USBA_EPT_MAPPED)) + dev_err(&udc->pdev->dev, + "ODD: EP%d configuration is invalid!\n", i); + } + } + } + + spin_unlock(&udc->lock); + + return IRQ_HANDLED; +} + +static int start_clock(struct usba_udc *udc) +{ + int ret; + + if (udc->clocked) + return 0; + + pm_stay_awake(&udc->pdev->dev); + + ret = clk_prepare_enable(udc->pclk); + if (ret) + return ret; + ret = clk_prepare_enable(udc->hclk); + if (ret) { + clk_disable_unprepare(udc->pclk); + return ret; + } + + udc->clocked = true; + return 0; +} + +static void stop_clock(struct usba_udc *udc) +{ + if (!udc->clocked) + return; + + clk_disable_unprepare(udc->hclk); + clk_disable_unprepare(udc->pclk); + + udc->clocked = false; + + pm_relax(&udc->pdev->dev); +} + +static int usba_start(struct usba_udc *udc) +{ + unsigned long flags; + int ret; + + ret = start_clock(udc); + if (ret) + return ret; + + if (udc->suspended) + return 0; + + spin_lock_irqsave(&udc->lock, flags); + toggle_bias(udc, 1); + usba_writel(udc, CTRL, USBA_ENABLE_MASK); + /* Clear all requested and pending interrupts... */ + usba_writel(udc, INT_ENB, 0); + udc->int_enb_cache = 0; + usba_writel(udc, INT_CLR, + USBA_END_OF_RESET|USBA_END_OF_RESUME + |USBA_DET_SUSPEND|USBA_WAKE_UP); + /* ...and enable just 'reset' IRQ to get us started */ + usba_int_enb_set(udc, USBA_END_OF_RESET); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static void usba_stop(struct usba_udc *udc) +{ + unsigned long flags; + + if (udc->suspended) + return; + + spin_lock_irqsave(&udc->lock, flags); + udc->gadget.speed = USB_SPEED_UNKNOWN; + reset_all_endpoints(udc); + + /* This will also disable the DP pullup */ + toggle_bias(udc, 0); + usba_writel(udc, CTRL, USBA_DISABLE_MASK); + spin_unlock_irqrestore(&udc->lock, flags); + + stop_clock(udc); +} + +static irqreturn_t usba_vbus_irq_thread(int irq, void *devid) +{ + struct usba_udc *udc = devid; + int vbus; + + /* debounce */ + udelay(10); + + mutex_lock(&udc->vbus_mutex); + + vbus = vbus_is_present(udc); + if (vbus != udc->vbus_prev) { + if (vbus) { + usba_start(udc); + } else { + udc->suspended = false; + if (udc->driver->disconnect) + udc->driver->disconnect(&udc->gadget); + + usba_stop(udc); + } + udc->vbus_prev = vbus; + } + + mutex_unlock(&udc->vbus_mutex); + return IRQ_HANDLED; +} + +static int atmel_usba_pullup(struct usb_gadget *gadget, int is_on) +{ + struct usba_udc *udc = container_of(gadget, struct usba_udc, gadget); + unsigned long flags; + u32 ctrl; + + spin_lock_irqsave(&udc->lock, flags); + ctrl = usba_readl(udc, CTRL); + if (is_on) + ctrl &= ~USBA_DETACH; + else + ctrl |= USBA_DETACH; + usba_writel(udc, CTRL, ctrl); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int atmel_usba_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + int ret; + 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); + + mutex_lock(&udc->vbus_mutex); + + if (udc->vbus_pin) + enable_irq(gpiod_to_irq(udc->vbus_pin)); + + /* If Vbus is present, enable the controller and wait for reset */ + udc->vbus_prev = vbus_is_present(udc); + if (udc->vbus_prev) { + ret = usba_start(udc); + if (ret) + goto err; + } + + mutex_unlock(&udc->vbus_mutex); + return 0; + +err: + if (udc->vbus_pin) + disable_irq(gpiod_to_irq(udc->vbus_pin)); + + mutex_unlock(&udc->vbus_mutex); + + spin_lock_irqsave(&udc->lock, flags); + udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + udc->driver = NULL; + spin_unlock_irqrestore(&udc->lock, flags); + return ret; +} + +static int atmel_usba_stop(struct usb_gadget *gadget) +{ + struct usba_udc *udc = container_of(gadget, struct usba_udc, gadget); + + if (udc->vbus_pin) + disable_irq(gpiod_to_irq(udc->vbus_pin)); + + udc->suspended = false; + usba_stop(udc); + + udc->driver = NULL; + + return 0; +} + +static void at91sam9rl_toggle_bias(struct usba_udc *udc, int is_on) +{ + regmap_update_bits(udc->pmc, AT91_CKGR_UCKR, AT91_PMC_BIASEN, + is_on ? AT91_PMC_BIASEN : 0); +} + +static void at91sam9g45_pulse_bias(struct usba_udc *udc) +{ + regmap_update_bits(udc->pmc, AT91_CKGR_UCKR, AT91_PMC_BIASEN, 0); + regmap_update_bits(udc->pmc, AT91_CKGR_UCKR, AT91_PMC_BIASEN, + AT91_PMC_BIASEN); +} + +static const struct usba_udc_errata at91sam9rl_errata = { + .toggle_bias = at91sam9rl_toggle_bias, +}; + +static const struct usba_udc_errata at91sam9g45_errata = { + .pulse_bias = at91sam9g45_pulse_bias, +}; + +static const struct usba_ep_config ep_config_sam9[] = { + { .nr_banks = 1 }, /* ep 0 */ + { .nr_banks = 2, .can_dma = 1, .can_isoc = 1 }, /* ep 1 */ + { .nr_banks = 2, .can_dma = 1, .can_isoc = 1 }, /* ep 2 */ + { .nr_banks = 3, .can_dma = 1 }, /* ep 3 */ + { .nr_banks = 3, .can_dma = 1 }, /* ep 4 */ + { .nr_banks = 3, .can_dma = 1, .can_isoc = 1 }, /* ep 5 */ + { .nr_banks = 3, .can_dma = 1, .can_isoc = 1 }, /* ep 6 */ +}; + +static const struct usba_ep_config ep_config_sama5[] = { + { .nr_banks = 1 }, /* ep 0 */ + { .nr_banks = 3, .can_dma = 1, .can_isoc = 1 }, /* ep 1 */ + { .nr_banks = 3, .can_dma = 1, .can_isoc = 1 }, /* ep 2 */ + { .nr_banks = 2, .can_dma = 1, .can_isoc = 1 }, /* ep 3 */ + { .nr_banks = 2, .can_dma = 1, .can_isoc = 1 }, /* ep 4 */ + { .nr_banks = 2, .can_dma = 1, .can_isoc = 1 }, /* ep 5 */ + { .nr_banks = 2, .can_dma = 1, .can_isoc = 1 }, /* ep 6 */ + { .nr_banks = 2, .can_dma = 1, .can_isoc = 1 }, /* ep 7 */ + { .nr_banks = 2, .can_isoc = 1 }, /* ep 8 */ + { .nr_banks = 2, .can_isoc = 1 }, /* ep 9 */ + { .nr_banks = 2, .can_isoc = 1 }, /* ep 10 */ + { .nr_banks = 2, .can_isoc = 1 }, /* ep 11 */ + { .nr_banks = 2, .can_isoc = 1 }, /* ep 12 */ + { .nr_banks = 2, .can_isoc = 1 }, /* ep 13 */ + { .nr_banks = 2, .can_isoc = 1 }, /* ep 14 */ + { .nr_banks = 2, .can_isoc = 1 }, /* ep 15 */ +}; + +static const struct usba_udc_config udc_at91sam9rl_cfg = { + .errata = &at91sam9rl_errata, + .config = ep_config_sam9, + .num_ep = ARRAY_SIZE(ep_config_sam9), + .ep_prealloc = true, +}; + +static const struct usba_udc_config udc_at91sam9g45_cfg = { + .errata = &at91sam9g45_errata, + .config = ep_config_sam9, + .num_ep = ARRAY_SIZE(ep_config_sam9), + .ep_prealloc = true, +}; + +static const struct usba_udc_config udc_sama5d3_cfg = { + .config = ep_config_sama5, + .num_ep = ARRAY_SIZE(ep_config_sama5), + .ep_prealloc = true, +}; + +static const struct usba_udc_config udc_sam9x60_cfg = { + .num_ep = ARRAY_SIZE(ep_config_sam9), + .config = ep_config_sam9, + .ep_prealloc = false, +}; + +static const struct of_device_id atmel_udc_dt_ids[] = { + { .compatible = "atmel,at91sam9rl-udc", .data = &udc_at91sam9rl_cfg }, + { .compatible = "atmel,at91sam9g45-udc", .data = &udc_at91sam9g45_cfg }, + { .compatible = "atmel,sama5d3-udc", .data = &udc_sama5d3_cfg }, + { .compatible = "microchip,sam9x60-udc", .data = &udc_sam9x60_cfg }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmel_udc_dt_ids); + +static const struct of_device_id atmel_pmc_dt_ids[] = { + { .compatible = "atmel,at91sam9g45-pmc" }, + { .compatible = "atmel,at91sam9rl-pmc" }, + { .compatible = "atmel,at91sam9x5-pmc" }, + { /* sentinel */ } +}; + +static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, + struct usba_udc *udc) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match; + struct device_node *pp; + int i, ret; + struct usba_ep *eps, *ep; + const struct usba_udc_config *udc_config; + + match = of_match_node(atmel_udc_dt_ids, np); + if (!match) + return ERR_PTR(-EINVAL); + + udc_config = match->data; + udc->ep_prealloc = udc_config->ep_prealloc; + udc->errata = udc_config->errata; + if (udc->errata) { + pp = of_find_matching_node_and_match(NULL, atmel_pmc_dt_ids, + NULL); + if (!pp) + return ERR_PTR(-ENODEV); + + udc->pmc = syscon_node_to_regmap(pp); + of_node_put(pp); + if (IS_ERR(udc->pmc)) + return ERR_CAST(udc->pmc); + } + + udc->num_ep = 0; + + udc->vbus_pin = devm_gpiod_get_optional(&pdev->dev, "atmel,vbus", + GPIOD_IN); + if (IS_ERR(udc->vbus_pin)) + return ERR_CAST(udc->vbus_pin); + + if (fifo_mode == 0) { + udc->num_ep = udc_config->num_ep; + } else { + udc->num_ep = usba_config_fifo_table(udc); + } + + eps = devm_kcalloc(&pdev->dev, udc->num_ep, sizeof(struct usba_ep), + GFP_KERNEL); + if (!eps) + return ERR_PTR(-ENOMEM); + + udc->gadget.ep0 = &eps[0].ep; + + INIT_LIST_HEAD(&eps[0].ep.ep_list); + + i = 0; + while (i < udc->num_ep) { + const struct usba_ep_config *ep_cfg = &udc_config->config[i]; + + ep = &eps[i]; + + ep->index = fifo_mode ? udc->fifo_cfg[i].hw_ep_num : i; + + /* Only the first EP is 64 bytes */ + if (ep->index == 0) + ep->fifo_size = 64; + else + ep->fifo_size = 1024; + + if (fifo_mode) { + if (ep->fifo_size < udc->fifo_cfg[i].fifo_size) + dev_warn(&pdev->dev, + "Using default max fifo-size value\n"); + else + ep->fifo_size = udc->fifo_cfg[i].fifo_size; + } + + ep->nr_banks = ep_cfg->nr_banks; + if (fifo_mode) { + if (ep->nr_banks < udc->fifo_cfg[i].nr_banks) + dev_warn(&pdev->dev, + "Using default max nb-banks value\n"); + else + ep->nr_banks = udc->fifo_cfg[i].nr_banks; + } + + ep->can_dma = ep_cfg->can_dma; + ep->can_isoc = ep_cfg->can_isoc; + + sprintf(ep->name, "ep%d", ep->index); + ep->ep.name = ep->name; + + 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; + usb_ep_set_maxpacket_limit(&ep->ep, ep->fifo_size); + ep->udc = udc; + INIT_LIST_HEAD(&ep->queue); + + if (ep->index == 0) { + ep->ep.caps.type_control = true; + } else { + ep->ep.caps.type_iso = ep->can_isoc; + ep->ep.caps.type_bulk = true; + ep->ep.caps.type_int = true; + } + + ep->ep.caps.dir_in = true; + ep->ep.caps.dir_out = true; + + if (fifo_mode != 0) { + /* + * Generate ept_cfg based on FIFO size and + * banks number + */ + if (ep->fifo_size <= 8) + ep->ept_cfg = USBA_BF(EPT_SIZE, USBA_EPT_SIZE_8); + else + /* LSB is bit 1, not 0 */ + ep->ept_cfg = + USBA_BF(EPT_SIZE, fls(ep->fifo_size - 1) - 3); + + ep->ept_cfg |= USBA_BF(BK_NUMBER, ep->nr_banks); + } + + if (i) + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + + i++; + } + + if (i == 0) { + dev_err(&pdev->dev, "of_probe: no endpoint specified\n"); + ret = -EINVAL; + goto err; + } + + return eps; +err: + return ERR_PTR(ret); +} + +static int usba_udc_probe(struct platform_device *pdev) +{ + struct resource *res; + struct clk *pclk, *hclk; + struct usba_udc *udc; + int irq, ret, i; + + udc = devm_kzalloc(&pdev->dev, sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + udc->gadget = usba_gadget_template; + INIT_LIST_HEAD(&udc->gadget.ep_list); + + udc->regs = devm_platform_get_and_ioremap_resource(pdev, CTRL_IOMEM_ID, &res); + if (IS_ERR(udc->regs)) + return PTR_ERR(udc->regs); + dev_info(&pdev->dev, "MMIO registers at %pR mapped at %p\n", + res, udc->regs); + + udc->fifo = devm_platform_get_and_ioremap_resource(pdev, FIFO_IOMEM_ID, &res); + if (IS_ERR(udc->fifo)) + return PTR_ERR(udc->fifo); + dev_info(&pdev->dev, "FIFO at %pR mapped at %p\n", res, udc->fifo); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(pclk)) + return PTR_ERR(pclk); + hclk = devm_clk_get(&pdev->dev, "hclk"); + if (IS_ERR(hclk)) + return PTR_ERR(hclk); + + spin_lock_init(&udc->lock); + mutex_init(&udc->vbus_mutex); + udc->pdev = pdev; + udc->pclk = pclk; + udc->hclk = hclk; + + platform_set_drvdata(pdev, udc); + + /* Make sure we start from a clean slate */ + ret = clk_prepare_enable(pclk); + if (ret) { + dev_err(&pdev->dev, "Unable to enable pclk, aborting.\n"); + return ret; + } + + usba_writel(udc, CTRL, USBA_DISABLE_MASK); + clk_disable_unprepare(pclk); + + udc->usba_ep = atmel_udc_of_init(pdev, udc); + + toggle_bias(udc, 0); + + if (IS_ERR(udc->usba_ep)) + return PTR_ERR(udc->usba_ep); + + ret = devm_request_irq(&pdev->dev, irq, usba_udc_irq, 0, + "atmel_usba_udc", udc); + if (ret) { + dev_err(&pdev->dev, "Cannot request irq %d (error %d)\n", + irq, ret); + return ret; + } + udc->irq = irq; + + if (udc->vbus_pin) { + irq_set_status_flags(gpiod_to_irq(udc->vbus_pin), IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(&pdev->dev, + gpiod_to_irq(udc->vbus_pin), NULL, + usba_vbus_irq_thread, USBA_VBUS_IRQFLAGS, + "atmel_usba_udc", udc); + if (ret) { + udc->vbus_pin = NULL; + dev_warn(&udc->pdev->dev, + "failed to request vbus irq; " + "assuming always on\n"); + } + } + + ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget); + if (ret) + return ret; + device_init_wakeup(&pdev->dev, 1); + + usba_init_debugfs(udc); + for (i = 1; i < udc->num_ep; i++) + usba_ep_init_debugfs(udc, &udc->usba_ep[i]); + + return 0; +} + +static void usba_udc_remove(struct platform_device *pdev) +{ + struct usba_udc *udc; + int i; + + udc = platform_get_drvdata(pdev); + + device_init_wakeup(&pdev->dev, 0); + usb_del_gadget_udc(&udc->gadget); + + for (i = 1; i < udc->num_ep; i++) + usba_ep_cleanup_debugfs(&udc->usba_ep[i]); + usba_cleanup_debugfs(udc); +} + +#ifdef CONFIG_PM_SLEEP +static int usba_udc_suspend(struct device *dev) +{ + struct usba_udc *udc = dev_get_drvdata(dev); + + /* Not started */ + if (!udc->driver) + return 0; + + mutex_lock(&udc->vbus_mutex); + + if (!device_may_wakeup(dev)) { + udc->suspended = false; + usba_stop(udc); + goto out; + } + + /* + * Device may wake up. We stay clocked if we failed + * to request vbus irq, assuming always on. + */ + if (udc->vbus_pin) { + /* FIXME: right to stop here...??? */ + usba_stop(udc); + enable_irq_wake(gpiod_to_irq(udc->vbus_pin)); + } + + enable_irq_wake(udc->irq); + +out: + mutex_unlock(&udc->vbus_mutex); + return 0; +} + +static int usba_udc_resume(struct device *dev) +{ + struct usba_udc *udc = dev_get_drvdata(dev); + + /* Not started */ + if (!udc->driver) + return 0; + + if (device_may_wakeup(dev)) { + if (udc->vbus_pin) + disable_irq_wake(gpiod_to_irq(udc->vbus_pin)); + + disable_irq_wake(udc->irq); + } + + /* If Vbus is present, enable the controller and wait for reset */ + mutex_lock(&udc->vbus_mutex); + udc->vbus_prev = vbus_is_present(udc); + if (udc->vbus_prev) + usba_start(udc); + mutex_unlock(&udc->vbus_mutex); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(usba_udc_pm_ops, usba_udc_suspend, usba_udc_resume); + +static struct platform_driver udc_driver = { + .probe = usba_udc_probe, + .remove = usba_udc_remove, + .driver = { + .name = "atmel_usba_udc", + .pm = &usba_udc_pm_ops, + .of_match_table = atmel_udc_dt_ids, + }, +}; +module_platform_driver(udc_driver); + +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/udc/atmel_usba_udc.h b/drivers/usb/gadget/udc/atmel_usba_udc.h new file mode 100644 index 000000000..620472f21 --- /dev/null +++ b/drivers/usb/gadget/udc/atmel_usba_udc.h @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Atmel USBA high speed USB device controller + * + * Copyright (C) 2005-2007 Atmel Corporation + */ +#ifndef __LINUX_USB_GADGET_USBA_UDC_H__ +#define __LINUX_USB_GADGET_USBA_UDC_H__ + +#include + +/* 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) + +#define USBA_ENABLE_MASK (USBA_EN_USBA | USBA_PULLD_DIS) +#define USBA_DISABLE_MASK USBA_DETACH + +/* 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) \ + readl_relaxed((udc)->regs + USBA_##reg) +#define usba_writel(udc, reg, value) \ + writel_relaxed((value), (udc)->regs + USBA_##reg) +#define usba_ep_readl(ep, reg) \ + readl_relaxed((ep)->ep_regs + USBA_EPT_##reg) +#define usba_ep_writel(ep, reg, value) \ + writel_relaxed((value), (ep)->ep_regs + USBA_EPT_##reg) +#define usba_dma_readl(ep, reg) \ + readl_relaxed((ep)->dma_regs + USBA_DMA_##reg) +#define usba_dma_writel(ep, reg, value) \ + writel_relaxed((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_DMAS 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_fifo_cfg { + u8 hw_ep_num; + u16 fifo_size; + u8 nr_banks; +}; + +struct usba_ep { + int state; + void __iomem *ep_regs; + void __iomem *dma_regs; + void __iomem *fifo; + char name[8]; + 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; + unsigned long ept_cfg; +#ifdef CONFIG_USB_GADGET_DEBUG_FS + u32 last_dma_status; + struct dentry *debugfs_dir; +#endif +}; + +struct usba_ep_config { + u8 nr_banks; + unsigned int can_dma:1; + unsigned int can_isoc:1; +}; + +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_errata { + void (*toggle_bias)(struct usba_udc *udc, int is_on); + void (*pulse_bias)(struct usba_udc *udc); +}; + +struct usba_udc_config { + const struct usba_udc_errata *errata; + const struct usba_ep_config *config; + const int num_ep; + const bool ep_prealloc; +}; + +struct usba_udc { + /* Protect hw registers from concurrent modifications */ + spinlock_t lock; + + /* Mutex to prevent concurrent start or stop */ + struct mutex vbus_mutex; + + void __iomem *regs; + void __iomem *fifo; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct platform_device *pdev; + const struct usba_udc_errata *errata; + int irq; + struct gpio_desc *vbus_pin; + int num_ep; + struct usba_fifo_cfg *fifo_cfg; + struct clk *pclk; + struct clk *hclk; + struct usba_ep *usba_ep; + bool bias_pulse_needed; + bool clocked; + bool suspended; + bool ep_prealloc; + + u16 devstatus; + + u16 test_mode; + int vbus_prev; + + u32 int_enb_cache; + +#ifdef CONFIG_USB_GADGET_DEBUG_FS + struct dentry *debugfs_root; +#endif + + struct regmap *pmc; +}; + +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/udc/bcm63xx_udc.c b/drivers/usb/gadget/udc/bcm63xx_udc.c new file mode 100644 index 000000000..c4f9ea45b --- /dev/null +++ b/drivers/usb/gadget/udc/bcm63xx_udc.c @@ -0,0 +1,2380 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * bcm63xx_udc.c -- BCM63xx UDC high/full speed USB device controller + * + * Copyright (C) 2012 Kevin Cernekee + * Copyright (C) 2012 Broadcom Corporation + */ + +#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 struct { + const char *name; + const struct usb_ep_caps caps; +} bcm63xx_ep_info[] = { +#define EP_INFO(_name, _caps) \ + { \ + .name = _name, \ + .caps = _caps, \ + } + + EP_INFO(bcm63xx_ep0name, + USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep1in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep2out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep3in-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep4out-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, USB_EP_CAPS_DIR_OUT)), + +#undef EP_INFO +}; + +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 device. + * @driver: Driver for USB device. + * @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. + */ +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; +}; + +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; + usb_ep_set_maxpacket_limit(&udc->bep[idx].ep, 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_info[i].name; + bep->ep.caps = bcm63xx_ep_info[i].caps; + 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; + usb_ep_set_maxpacket_limit(&bep->ep, 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 bcm63xx_req *breq, *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_entry_safe(breq, n, &bep->queue, 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); + usb_gadget_giveback_request(&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_obj(*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 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 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 device. + * @driver: Driver for USB device. + */ +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; + 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 device. + * @driver: Driver for USB device. + */ +static int bcm63xx_udc_stop(struct usb_gadget *gadget) +{ + 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, bus_reset = 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); + bus_reset = 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); + else if (bus_reset && udc->driver) + usb_gadget_udc_reset(&udc->gadget, udc->driver); + + 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; +} +DEFINE_SHOW_ATTRIBUTE(bcm63xx_usbd_dbg); + +/* + * 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]; + + 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) + seq_printf(s, "; %zu queued\n", list_count_nodes(&iudma->bep->queue)); + 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, " <gadget.name, usb_debug_root); + debugfs_create_file("usbd", 0400, root, udc, &bcm63xx_usbd_dbg_fops); + debugfs_create_file("iudma", 0400, root, udc, &bcm63xx_iudma_dbg_fops); +} + +/** + * 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_lookup_and_remove(udc->gadget.name, usb_debug_root); +} + +/*********************************************************************** + * 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_get_platdata(dev); + struct bcm63xx_udc *udc; + int rc = -ENOMEM, i, irq; + + udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + platform_set_drvdata(pdev, udc); + udc->dev = dev; + udc->pd = pd; + + if (!pd) { + dev_err(dev, "missing platform data\n"); + return -EINVAL; + } + + udc->usbd_regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(udc->usbd_regs)) + return PTR_ERR(udc->usbd_regs); + + udc->iudma_regs = devm_platform_ioremap_resource(pdev, 1); + 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) { + rc = irq; + goto out_uninit; + } + if (devm_request_irq(dev, irq, &bcm63xx_udc_ctrl_isr, 0, + dev_name(dev), udc) < 0) + goto report_request_failure; + + /* 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) { + rc = irq; + goto out_uninit; + } + if (devm_request_irq(dev, irq, &bcm63xx_udc_data_isr, 0, + dev_name(dev), &udc->iudma[i]) < 0) + goto report_request_failure; + } + + 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; + +report_request_failure: + dev_err(dev, "error requesting IRQ #%d\n", irq); + goto out_uninit; +} + +/** + * bcm63xx_udc_remove - Remove the device from the system. + * @pdev: Platform device struct from the bcm63xx BSP code. + */ +static void 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); +} + +static struct platform_driver bcm63xx_udc_driver = { + .probe = bcm63xx_udc_probe, + .remove = bcm63xx_udc_remove, + .driver = { + .name = DRV_MODULE_NAME, + }, +}; +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/udc/bdc/Kconfig b/drivers/usb/gadget/udc/bdc/Kconfig new file mode 100644 index 000000000..8bedb7f64 --- /dev/null +++ b/drivers/usb/gadget/udc/bdc/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 + +config USB_BDC_UDC + tristate "Broadcom USB3.0 device controller IP driver(BDC)" + depends on USB_GADGET && HAS_DMA + default ARCH_BRCMSTB + + help + BDC is Broadcom's USB3.0 device controller IP. If your SOC has a BDC IP + then select this driver. + + Say "y" here to link the driver statically, or "m" to build a dynamically + linked module called "bdc". diff --git a/drivers/usb/gadget/udc/bdc/Makefile b/drivers/usb/gadget/udc/bdc/Makefile new file mode 100644 index 000000000..1b21c9518 --- /dev/null +++ b/drivers/usb/gadget/udc/bdc/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_USB_BDC_UDC) += bdc.o +bdc-y := bdc_core.o bdc_cmd.o bdc_ep.o bdc_udc.o + +ifneq ($(CONFIG_USB_GADGET_VERBOSE),) + bdc-y += bdc_dbg.o +endif diff --git a/drivers/usb/gadget/udc/bdc/bdc.h b/drivers/usb/gadget/udc/bdc/bdc.h new file mode 100644 index 000000000..2f4abf6f8 --- /dev/null +++ b/drivers/usb/gadget/udc/bdc/bdc.h @@ -0,0 +1,489 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * bdc.h - header for the BRCM BDC USB3.0 device controller + * + * Copyright (C) 2014 Broadcom Corporation + * + * Author: Ashwini Pahuja + */ + +#ifndef __LINUX_BDC_H__ +#define __LINUX_BDC_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BRCM_BDC_NAME "bdc" +#define BRCM_BDC_DESC "Broadcom USB Device Controller driver" + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +/* BDC command operation timeout in usec*/ +#define BDC_CMD_TIMEOUT 1000 +/* BDC controller operation timeout in usec*/ +#define BDC_COP_TIMEOUT 500 + +/* + * Maximum size of ep0 response buffer for ch9 requests, + * the set_sel request uses 6 so far, the max. + */ +#define EP0_RESPONSE_BUFF 6 +/* Start with SS as default */ +#define EP0_MAX_PKT_SIZE 512 + +/* 64 entries in a SRR */ +#define NUM_SR_ENTRIES 64 + +/* Num of bds per table */ +#define NUM_BDS_PER_TABLE 64 + +/* Num of tables in bd list for control,bulk and Int ep */ +#define NUM_TABLES 2 + +/* Num of tables in bd list for Isoch ep */ +#define NUM_TABLES_ISOCH 6 + +/* U1 Timeout default: 248usec */ +#define U1_TIMEOUT 0xf8 + +/* Interrupt coalescence in usec */ +#define INT_CLS 500 + +/* Register offsets */ +/* Configuration and Capability registers */ +#define BDC_BDCCFG0 0x00 +#define BDC_BDCCFG1 0x04 +#define BDC_BDCCAP0 0x08 +#define BDC_BDCCAP1 0x0c +#define BDC_CMDPAR0 0x10 +#define BDC_CMDPAR1 0x14 +#define BDC_CMDPAR2 0x18 +#define BDC_CMDSC 0x1c +#define BDC_USPC 0x20 +#define BDC_USPPMS 0x28 +#define BDC_USPPM2 0x2c +#define BDC_SPBBAL 0x38 +#define BDC_SPBBAH 0x3c +#define BDC_BDCSC 0x40 +#define BDC_XSFNTF 0x4c + +#define BDC_DVCSA 0x50 +#define BDC_DVCSB 0x54 +#define BDC_EPSTS0 0x60 +#define BDC_EPSTS1 0x64 +#define BDC_EPSTS2 0x68 +#define BDC_EPSTS3 0x6c +#define BDC_EPSTS4 0x70 +#define BDC_EPSTS5 0x74 +#define BDC_EPSTS6 0x78 +#define BDC_EPSTS7 0x7c +#define BDC_SRRBAL(n) (0x200 + ((n) * 0x10)) +#define BDC_SRRBAH(n) (0x204 + ((n) * 0x10)) +#define BDC_SRRINT(n) (0x208 + ((n) * 0x10)) +#define BDC_INTCTLS(n) (0x20c + ((n) * 0x10)) + +/* Extended capability regs */ +#define BDC_FSCNOC 0xcd4 +#define BDC_FSCNIC 0xce4 +#define NUM_NCS(p) ((p) >> 28) + +/* Register bit fields and Masks */ +/* BDC Configuration 0 */ +#define BDC_PGS(p) (((p) & (0x7 << 8)) >> 8) +#define BDC_SPB(p) ((p) & 0x7) + +/* BDC Capability1 */ +#define BDC_P64 BIT(0) + +/* BDC Command register */ +#define BDC_CMD_FH 0xe +#define BDC_CMD_DNC 0x6 +#define BDC_CMD_EPO 0x4 +#define BDC_CMD_BLA 0x3 +#define BDC_CMD_EPC 0x2 +#define BDC_CMD_DVC 0x1 +#define BDC_CMD_CWS BIT(5) +#define BDC_CMD_CST(p) (((p) & (0xf << 6))>>6) +#define BDC_CMD_EPN(p) (((p) & 0x1f) << 10) +#define BDC_SUB_CMD_ADD (0x1 << 17) +#define BDC_SUB_CMD_FWK (0x4 << 17) +/* Reset sequence number */ +#define BDC_CMD_EPO_RST_SN (0x1 << 16) +#define BDC_CMD_EP0_XSD (0x1 << 16) +#define BDC_SUB_CMD_ADD_EP (0x1 << 17) +#define BDC_SUB_CMD_DRP_EP (0x2 << 17) +#define BDC_SUB_CMD_EP_STP (0x2 << 17) +#define BDC_SUB_CMD_EP_STL (0x4 << 17) +#define BDC_SUB_CMD_EP_RST (0x1 << 17) +#define BDC_CMD_SRD BIT(27) + +/* CMD completion status */ +#define BDC_CMDS_SUCC 0x1 +#define BDC_CMDS_PARA 0x3 +#define BDC_CMDS_STAT 0x4 +#define BDC_CMDS_FAIL 0x5 +#define BDC_CMDS_INTL 0x6 +#define BDC_CMDS_BUSY 0xf + +/* CMDSC Param 2 shifts */ +#define EPT_SHIFT 22 +#define MP_SHIFT 10 +#define MB_SHIFT 6 +#define EPM_SHIFT 4 + +/* BDC USPSC */ +#define BDC_VBC BIT(31) +#define BDC_PRC BIT(30) +#define BDC_PCE BIT(29) +#define BDC_CFC BIT(28) +#define BDC_PCC BIT(27) +#define BDC_PSC BIT(26) +#define BDC_VBS BIT(25) +#define BDC_PRS BIT(24) +#define BDC_PCS BIT(23) +#define BDC_PSP(p) (((p) & (0x7 << 20))>>20) +#define BDC_SCN BIT(8) +#define BDC_SDC BIT(7) +#define BDC_SWS BIT(4) + +#define BDC_USPSC_RW (BDC_SCN|BDC_SDC|BDC_SWS|0xf) +#define BDC_PSP(p) (((p) & (0x7 << 20))>>20) + +#define BDC_SPEED_FS 0x1 +#define BDC_SPEED_LS 0x2 +#define BDC_SPEED_HS 0x3 +#define BDC_SPEED_SS 0x4 + +#define BDC_PST(p) ((p) & 0xf) +#define BDC_PST_MASK 0xf + +/* USPPMS */ +#define BDC_U2E BIT(31) +#define BDC_U1E BIT(30) +#define BDC_U2A BIT(29) +#define BDC_PORT_W1S BIT(17) +#define BDC_U1T(p) ((p) & 0xff) +#define BDC_U2T(p) (((p) & 0xff) << 8) +#define BDC_U1T_MASK 0xff + +/* USBPM2 */ +/* Hardware LPM Enable */ +#define BDC_HLE BIT(16) + +/* BDC Status and Control */ +#define BDC_COP_RST (1 << 29) +#define BDC_COP_RUN (2 << 29) +#define BDC_COP_STP (4 << 29) + +#define BDC_COP_MASK (BDC_COP_RST|BDC_COP_RUN|BDC_COP_STP) + +#define BDC_COS BIT(28) +#define BDC_CSTS(p) (((p) & (0x7 << 20)) >> 20) +#define BDC_MASK_MCW BIT(7) +#define BDC_GIE BIT(1) +#define BDC_GIP BIT(0) + +#define BDC_HLT 1 +#define BDC_NOR 2 +#define BDC_OIP 7 + +/* Buffer descriptor and Status report bit fields and masks */ +#define BD_TYPE_BITMASK (0xf) +#define BD_CHAIN 0xf + +#define BD_TFS_SHIFT 4 +#define BD_SOT BIT(26) +#define BD_EOT BIT(27) +#define BD_ISP BIT(29) +#define BD_IOC BIT(30) +#define BD_SBF BIT(31) + +#define BD_INTR_TARGET(p) (((p) & 0x1f) << 27) + +#define BDC_SRR_RWS BIT(4) +#define BDC_SRR_RST BIT(3) +#define BDC_SRR_ISR BIT(2) +#define BDC_SRR_IE BIT(1) +#define BDC_SRR_IP BIT(0) +#define BDC_SRR_EPI(p) (((p) & (0xff << 24)) >> 24) +#define BDC_SRR_DPI(p) (((p) & (0xff << 16)) >> 16) +#define BDC_SRR_DPI_MASK 0x00ff0000 + +#define MARK_CHAIN_BD (BD_CHAIN|BD_EOT|BD_SOT) + +/* Control transfer BD specific fields */ +#define BD_DIR_IN BIT(25) + +#define BDC_PTC_MASK 0xf0000000 + +/* status report defines */ +#define SR_XSF 0 +#define SR_USPC 4 +#define SR_BD_LEN(p) ((p) & 0xffffff) + +#define XSF_SUCC 0x1 +#define XSF_SHORT 0x3 +#define XSF_BABB 0x4 +#define XSF_SETUP_RECV 0x6 +#define XSF_DATA_START 0x7 +#define XSF_STATUS_START 0x8 + +#define XSF_STS(p) (((p) >> 28) & 0xf) + +/* Transfer BD fields */ +#define BD_LEN(p) ((p) & 0x1ffff) +#define BD_LTF BIT(25) +#define BD_TYPE_DS 0x1 +#define BD_TYPE_SS 0x2 + +#define BDC_EP_ENABLED BIT(0) +#define BDC_EP_STALL BIT(1) +#define BDC_EP_STOP BIT(2) + +/* One BD can transfer max 65536 bytes */ +#define BD_MAX_BUFF_SIZE (1 << 16) +/* Maximum bytes in one XFR, Refer to BDC spec */ +#define MAX_XFR_LEN 16777215 + +/* defines for Force Header command */ +#define DEV_NOTF_TYPE 6 +#define FWK_SUBTYPE 1 +#define TRA_PACKET 4 + +#define to_bdc_ep(e) container_of(e, struct bdc_ep, usb_ep) +#define to_bdc_req(r) container_of(r, struct bdc_req, usb_req) +#define gadget_to_bdc(g) container_of(g, struct bdc, gadget) + +/* FUNCTION WAKE DEV NOTIFICATION interval, USB3 spec table 8.13 */ +#define BDC_TNOTIFY 2500 /*in ms*/ +/* Devstatus bitfields */ +#define REMOTE_WAKEUP_ISSUED BIT(16) +#define DEVICE_SUSPENDED BIT(17) +#define FUNC_WAKE_ISSUED BIT(18) +#define REMOTE_WAKE_ENABLE (1 << USB_DEVICE_REMOTE_WAKEUP) + +/* On disconnect, preserve these bits and clear rest */ +#define DEVSTATUS_CLEAR (1 << USB_DEVICE_SELF_POWERED) +/* Hardware and software Data structures */ + +/* Endpoint bd: buffer descriptor */ +struct bdc_bd { + __le32 offset[4]; +}; + +/* Status report in Status report ring(srr) */ +struct bdc_sr { + __le32 offset[4]; +}; + +/* bd_table: contiguous bd's in a table */ +struct bd_table { + struct bdc_bd *start_bd; + /* dma address of start bd of table*/ + dma_addr_t dma; +}; + +/* + * Each endpoint has a bdl(buffer descriptor list), bdl consists of 1 or more bd + * table's chained to each other through a chain bd, every table has equal + * number of bds. the software uses bdi(bd index) to refer to particular bd in + * the list. + */ +struct bd_list { + /* Array of bd table pointers*/ + struct bd_table **bd_table_array; + /* How many tables chained to each other */ + int num_tabs; + /* Max_bdi = num_tabs * num_bds_table - 1 */ + int max_bdi; + /* current enq bdi from sw point of view */ + int eqp_bdi; + /* current deq bdi from sw point of view */ + int hwd_bdi; + /* numbers of bds per table */ + int num_bds_table; +}; + +struct bdc_req; + +/* Representation of a transfer, one transfer can have multiple bd's */ +struct bd_transfer { + struct bdc_req *req; + /* start bd index */ + int start_bdi; + /* this will be the next hw dqp when this transfer completes */ + int next_hwd_bdi; + /* number of bds in this transfer */ + int num_bds; +}; + +/* + * Representation of a gadget request, every gadget request is contained + * by 1 bd_transfer. + */ +struct bdc_req { + struct usb_request usb_req; + struct list_head queue; + struct bdc_ep *ep; + /* only one Transfer per request */ + struct bd_transfer bd_xfr; + int epnum; +}; + +/* scratchpad buffer needed by bdc hardware */ +struct bdc_scratchpad { + dma_addr_t sp_dma; + void *buff; + u32 size; +}; + +/* endpoint representation */ +struct bdc_ep { + struct usb_ep usb_ep; + struct list_head queue; + struct bdc *bdc; + u8 ep_type; + u8 dir; + u8 ep_num; + const struct usb_ss_ep_comp_descriptor *comp_desc; + const struct usb_endpoint_descriptor *desc; + unsigned int flags; + char name[20]; + /* endpoint bd list*/ + struct bd_list bd_list; + /* + * HW generates extra event for multi bd tranfers, this flag helps in + * ignoring the extra event + */ + bool ignore_next_sr; +}; + +/* bdc cmmand parameter structure */ +struct bdc_cmd_params { + u32 param2; + u32 param1; + u32 param0; +}; + +/* status report ring(srr), currently one srr is supported for entire system */ +struct srr { + struct bdc_sr *sr_bds; + u16 eqp_index; + u16 dqp_index; + dma_addr_t dma_addr; +}; + +/* EP0 states */ +enum bdc_ep0_state { + WAIT_FOR_SETUP = 0, + WAIT_FOR_DATA_START, + WAIT_FOR_DATA_XMIT, + WAIT_FOR_STATUS_START, + WAIT_FOR_STATUS_XMIT, + STATUS_PENDING +}; + +/* Link states */ +enum bdc_link_state { + BDC_LINK_STATE_U0 = 0x00, + BDC_LINK_STATE_U3 = 0x03, + BDC_LINK_STATE_RX_DET = 0x05, + BDC_LINK_STATE_RESUME = 0x0f +}; + +/* representation of bdc */ +struct bdc { + struct usb_gadget gadget; + struct usb_gadget_driver *gadget_driver; + struct device *dev; + /* device lock */ + spinlock_t lock; + + /* generic phy */ + struct phy **phys; + int num_phys; + /* num of endpoints for a particular instantiation of IP */ + unsigned int num_eps; + /* + * Array of ep's, it uses the same index covention as bdc hw i.e. + * 1 for ep0, 2 for 1out,3 for 1in .... + */ + struct bdc_ep **bdc_ep_array; + void __iomem *regs; + struct bdc_scratchpad scratchpad; + u32 sp_buff_size; + /* current driver supports 1 status ring */ + struct srr srr; + /* Last received setup packet */ + struct usb_ctrlrequest setup_pkt; + struct bdc_req ep0_req; + struct bdc_req status_req; + enum bdc_ep0_state ep0_state; + bool delayed_status; + bool zlp_needed; + bool reinit; + bool pullup; + /* Bits 0-15 are standard and 16-31 for proprietary information */ + u32 devstatus; + int irq; + void *mem; + u32 dev_addr; + /* DMA pools */ + struct dma_pool *bd_table_pool; + u8 test_mode; + /* array of callbacks for various status report handlers */ + void (*sr_handler[2])(struct bdc *, struct bdc_sr *); + /* ep0 callback handlers */ + void (*sr_xsf_ep0[3])(struct bdc *, struct bdc_sr *); + /* ep0 response buffer for ch9 requests like GET_STATUS and SET_SEL */ + unsigned char ep0_response_buff[EP0_RESPONSE_BUFF]; + /* + * Timer to check if host resumed transfer after bdc sent Func wake + * notification packet after a remote wakeup. if not, then resend the + * Func Wake packet every 2.5 secs. Refer to USB3 spec section 8.5.6.4 + */ + struct delayed_work func_wake_notify; + struct clk *clk; +}; + +static inline u32 bdc_readl(void __iomem *base, u32 offset) +{ + return readl(base + offset); +} + +static inline void bdc_writel(void __iomem *base, u32 offset, u32 value) +{ + writel(value, base + offset); +} + +/* Buffer descriptor list operations */ +void bdc_notify_xfr(struct bdc *bdc, u32 epnum); +void bdc_softconn(struct bdc *bdc); +void bdc_softdisconn(struct bdc *bdc); +int bdc_run(struct bdc *bdc); +int bdc_stop(struct bdc *bdc); +int bdc_reset(struct bdc *bdc); +int bdc_udc_init(struct bdc *bdc); +void bdc_udc_exit(struct bdc *bdc); +int bdc_reinit(struct bdc *bdc); + +/* Status report handlers */ +/* Upstream port status change sr */ +void bdc_sr_uspc(struct bdc *bdc, struct bdc_sr *sreport); +/* transfer sr */ +void bdc_sr_xsf(struct bdc *bdc, struct bdc_sr *sreport); +/* EP0 XSF handlers */ +void bdc_xsf_ep0_setup_recv(struct bdc *bdc, struct bdc_sr *sreport); +void bdc_xsf_ep0_data_start(struct bdc *bdc, struct bdc_sr *sreport); +void bdc_xsf_ep0_status_start(struct bdc *bdc, struct bdc_sr *sreport); + +#endif /* __LINUX_BDC_H__ */ diff --git a/drivers/usb/gadget/udc/bdc/bdc_cmd.c b/drivers/usb/gadget/udc/bdc/bdc_cmd.c new file mode 100644 index 000000000..1848ced07 --- /dev/null +++ b/drivers/usb/gadget/udc/bdc/bdc_cmd.c @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * bdc_cmd.c - BRCM BDC USB3.0 device controller + * + * Copyright (C) 2014 Broadcom Corporation + * + * Author: Ashwini Pahuja + */ +#include +#include + +#include "bdc.h" +#include "bdc_cmd.h" +#include "bdc_dbg.h" + +/* Issues a cmd to cmd processor and waits for cmd completion */ +static int bdc_issue_cmd(struct bdc *bdc, u32 cmd_sc, u32 param0, + u32 param1, u32 param2) +{ + u32 timeout = BDC_CMD_TIMEOUT; + u32 cmd_status; + u32 temp; + + bdc_writel(bdc->regs, BDC_CMDPAR0, param0); + bdc_writel(bdc->regs, BDC_CMDPAR1, param1); + bdc_writel(bdc->regs, BDC_CMDPAR2, param2); + + /* Issue the cmd */ + /* Make sure the cmd params are written before asking HW to exec cmd */ + wmb(); + bdc_writel(bdc->regs, BDC_CMDSC, cmd_sc | BDC_CMD_CWS | BDC_CMD_SRD); + do { + temp = bdc_readl(bdc->regs, BDC_CMDSC); + dev_dbg_ratelimited(bdc->dev, "cmdsc=%x", temp); + cmd_status = BDC_CMD_CST(temp); + if (cmd_status != BDC_CMDS_BUSY) { + dev_dbg(bdc->dev, + "command completed cmd_sts:%x\n", cmd_status); + return cmd_status; + } + udelay(1); + } while (timeout--); + + dev_err(bdc->dev, + "command operation timedout cmd_status=%d\n", cmd_status); + + return cmd_status; +} + +/* Submits cmd and analyze the return value of bdc_issue_cmd */ +static int bdc_submit_cmd(struct bdc *bdc, u32 cmd_sc, + u32 param0, u32 param1, u32 param2) +{ + u32 temp, cmd_status; + int ret; + + temp = bdc_readl(bdc->regs, BDC_CMDSC); + dev_dbg(bdc->dev, + "%s:CMDSC:%08x cmdsc:%08x param0=%08x param1=%08x param2=%08x\n", + __func__, temp, cmd_sc, param0, param1, param2); + + cmd_status = BDC_CMD_CST(temp); + if (cmd_status == BDC_CMDS_BUSY) { + dev_err(bdc->dev, "command processor busy: %x\n", cmd_status); + return -EBUSY; + } + ret = bdc_issue_cmd(bdc, cmd_sc, param0, param1, param2); + switch (ret) { + case BDC_CMDS_SUCC: + dev_dbg(bdc->dev, "command completed successfully\n"); + ret = 0; + break; + + case BDC_CMDS_PARA: + dev_err(bdc->dev, "command parameter error\n"); + ret = -EINVAL; + break; + + case BDC_CMDS_STAT: + dev_err(bdc->dev, "Invalid device/ep state\n"); + ret = -EINVAL; + break; + + case BDC_CMDS_FAIL: + dev_err(bdc->dev, "Command failed?\n"); + ret = -EAGAIN; + break; + + case BDC_CMDS_INTL: + dev_err(bdc->dev, "BDC Internal error\n"); + ret = -ECONNRESET; + break; + + case BDC_CMDS_BUSY: + dev_err(bdc->dev, + "command timedout waited for %dusec\n", + BDC_CMD_TIMEOUT); + ret = -ECONNRESET; + break; + default: + dev_dbg(bdc->dev, "Unknown command completion code:%x\n", ret); + } + + return ret; +} + +/* Deconfigure the endpoint from HW */ +int bdc_dconfig_ep(struct bdc *bdc, struct bdc_ep *ep) +{ + u32 cmd_sc; + + cmd_sc = BDC_SUB_CMD_DRP_EP|BDC_CMD_EPN(ep->ep_num)|BDC_CMD_EPC; + dev_dbg(bdc->dev, "%s ep->ep_num =%d cmd_sc=%x\n", __func__, + ep->ep_num, cmd_sc); + + return bdc_submit_cmd(bdc, cmd_sc, 0, 0, 0); +} + +/* Reinitalize the bdlist after config ep command */ +static void ep_bd_list_reinit(struct bdc_ep *ep) +{ + struct bdc *bdc = ep->bdc; + struct bdc_bd *bd; + + ep->bd_list.eqp_bdi = 0; + ep->bd_list.hwd_bdi = 0; + bd = ep->bd_list.bd_table_array[0]->start_bd; + dev_dbg(bdc->dev, "%s ep:%p bd:%p\n", __func__, ep, bd); + memset(bd, 0, sizeof(struct bdc_bd)); + bd->offset[3] |= cpu_to_le32(BD_SBF); +} + +/* Configure an endpoint */ +int bdc_config_ep(struct bdc *bdc, struct bdc_ep *ep) +{ + const struct usb_ss_ep_comp_descriptor *comp_desc; + const struct usb_endpoint_descriptor *desc; + u32 param0, param1, param2, cmd_sc; + u32 mps, mbs, mul, si; + int ret; + + desc = ep->desc; + comp_desc = ep->comp_desc; + cmd_sc = mul = mbs = param2 = 0; + param0 = lower_32_bits(ep->bd_list.bd_table_array[0]->dma); + param1 = upper_32_bits(ep->bd_list.bd_table_array[0]->dma); + cpu_to_le32s(¶m0); + cpu_to_le32s(¶m1); + + dev_dbg(bdc->dev, "%s: param0=%08x param1=%08x", + __func__, param0, param1); + si = desc->bInterval; + si = clamp_val(si, 1, 16) - 1; + + mps = usb_endpoint_maxp(desc); + param2 |= mps << MP_SHIFT; + param2 |= usb_endpoint_type(desc) << EPT_SHIFT; + + switch (bdc->gadget.speed) { + case USB_SPEED_SUPER: + if (usb_endpoint_xfer_int(desc) || + usb_endpoint_xfer_isoc(desc)) { + param2 |= si; + if (usb_endpoint_xfer_isoc(desc) && comp_desc) + mul = comp_desc->bmAttributes; + + } + param2 |= mul << EPM_SHIFT; + if (comp_desc) + mbs = comp_desc->bMaxBurst; + param2 |= mbs << MB_SHIFT; + break; + + case USB_SPEED_HIGH: + if (usb_endpoint_xfer_isoc(desc) || + usb_endpoint_xfer_int(desc)) { + param2 |= si; + + mbs = usb_endpoint_maxp_mult(desc); + param2 |= mbs << MB_SHIFT; + } + break; + + case USB_SPEED_FULL: + case USB_SPEED_LOW: + /* the hardware accepts SI in 125usec range */ + if (usb_endpoint_xfer_isoc(desc)) + si += 3; + + /* + * FS Int endpoints can have si of 1-255ms but the controller + * accepts 2^bInterval*125usec, so convert ms to nearest power + * of 2 + */ + if (usb_endpoint_xfer_int(desc)) + si = fls(desc->bInterval * 8) - 1; + + param2 |= si; + break; + default: + dev_err(bdc->dev, "UNKNOWN speed ERR\n"); + return -EINVAL; + } + + cmd_sc |= BDC_CMD_EPC|BDC_CMD_EPN(ep->ep_num)|BDC_SUB_CMD_ADD_EP; + + dev_dbg(bdc->dev, "cmd_sc=%x param2=%08x\n", cmd_sc, param2); + ret = bdc_submit_cmd(bdc, cmd_sc, param0, param1, param2); + if (ret) { + dev_err(bdc->dev, "command failed :%x\n", ret); + return ret; + } + ep_bd_list_reinit(ep); + + return ret; +} + +/* + * Change the HW deq pointer, if this command is successful, HW will start + * fetching the next bd from address dma_addr. + */ +int bdc_ep_bla(struct bdc *bdc, struct bdc_ep *ep, dma_addr_t dma_addr) +{ + u32 param0, param1; + u32 cmd_sc = 0; + + dev_dbg(bdc->dev, "%s: add=%08llx\n", __func__, + (unsigned long long)(dma_addr)); + param0 = lower_32_bits(dma_addr); + param1 = upper_32_bits(dma_addr); + cpu_to_le32s(¶m0); + cpu_to_le32s(¶m1); + + cmd_sc |= BDC_CMD_EPN(ep->ep_num)|BDC_CMD_BLA; + dev_dbg(bdc->dev, "cmd_sc=%x\n", cmd_sc); + + return bdc_submit_cmd(bdc, cmd_sc, param0, param1, 0); +} + +/* Set the address sent bu Host in SET_ADD request */ +int bdc_address_device(struct bdc *bdc, u32 add) +{ + u32 cmd_sc = 0; + u32 param2; + + dev_dbg(bdc->dev, "%s: add=%d\n", __func__, add); + cmd_sc |= BDC_SUB_CMD_ADD|BDC_CMD_DVC; + param2 = add & 0x7f; + + return bdc_submit_cmd(bdc, cmd_sc, 0, 0, param2); +} + +/* Send a Function Wake notification packet using FH command */ +int bdc_function_wake_fh(struct bdc *bdc, u8 intf) +{ + u32 param0, param1; + u32 cmd_sc = 0; + + param0 = param1 = 0; + dev_dbg(bdc->dev, "%s intf=%d\n", __func__, intf); + cmd_sc |= BDC_CMD_FH; + param0 |= TRA_PACKET; + param0 |= (bdc->dev_addr << 25); + param1 |= DEV_NOTF_TYPE; + param1 |= (FWK_SUBTYPE<<4); + dev_dbg(bdc->dev, "param0=%08x param1=%08x\n", param0, param1); + + return bdc_submit_cmd(bdc, cmd_sc, param0, param1, 0); +} + +/* Send a Function Wake notification packet using DNC command */ +int bdc_function_wake(struct bdc *bdc, u8 intf) +{ + u32 cmd_sc = 0; + u32 param2 = 0; + + dev_dbg(bdc->dev, "%s intf=%d", __func__, intf); + param2 |= intf; + cmd_sc |= BDC_SUB_CMD_FWK|BDC_CMD_DNC; + + return bdc_submit_cmd(bdc, cmd_sc, 0, 0, param2); +} + +/* Stall the endpoint */ +int bdc_ep_set_stall(struct bdc *bdc, int epnum) +{ + u32 cmd_sc = 0; + + dev_dbg(bdc->dev, "%s epnum=%d\n", __func__, epnum); + /* issue a stall endpoint command */ + cmd_sc |= BDC_SUB_CMD_EP_STL | BDC_CMD_EPN(epnum) | BDC_CMD_EPO; + + return bdc_submit_cmd(bdc, cmd_sc, 0, 0, 0); +} + +/* resets the endpoint, called when host sends CLEAR_FEATURE(HALT) */ +int bdc_ep_clear_stall(struct bdc *bdc, int epnum) +{ + struct bdc_ep *ep; + u32 cmd_sc = 0; + int ret; + + dev_dbg(bdc->dev, "%s: epnum=%d\n", __func__, epnum); + ep = bdc->bdc_ep_array[epnum]; + /* + * If we are not in stalled then stall Endpoint and issue clear stall, + * his will reset the seq number for non EP0. + */ + if (epnum != 1) { + /* if the endpoint it not stalled */ + if (!(ep->flags & BDC_EP_STALL)) { + ret = bdc_ep_set_stall(bdc, epnum); + if (ret) + return ret; + } + } + /* Preserve the seq number for ep0 only */ + if (epnum != 1) + cmd_sc |= BDC_CMD_EPO_RST_SN; + + /* issue a reset endpoint command */ + cmd_sc |= BDC_SUB_CMD_EP_RST | BDC_CMD_EPN(epnum) | BDC_CMD_EPO; + + ret = bdc_submit_cmd(bdc, cmd_sc, 0, 0, 0); + if (ret) { + dev_err(bdc->dev, "command failed:%x\n", ret); + return ret; + } + bdc_notify_xfr(bdc, epnum); + + return ret; +} + +/* Stop the endpoint, called when software wants to dequeue some request */ +int bdc_stop_ep(struct bdc *bdc, int epnum) +{ + struct bdc_ep *ep; + u32 cmd_sc = 0; + int ret; + + ep = bdc->bdc_ep_array[epnum]; + dev_dbg(bdc->dev, "%s: ep:%s ep->flags:%08x\n", __func__, + ep->name, ep->flags); + /* Endpoint has to be in running state to execute stop ep command */ + if (!(ep->flags & BDC_EP_ENABLED)) { + dev_err(bdc->dev, "stop endpoint called for disabled ep\n"); + return -EINVAL; + } + if ((ep->flags & BDC_EP_STALL) || (ep->flags & BDC_EP_STOP)) + return 0; + + /* issue a stop endpoint command */ + cmd_sc |= BDC_CMD_EP0_XSD | BDC_SUB_CMD_EP_STP + | BDC_CMD_EPN(epnum) | BDC_CMD_EPO; + + ret = bdc_submit_cmd(bdc, cmd_sc, 0, 0, 0); + if (ret) { + dev_err(bdc->dev, + "stop endpoint command didn't complete:%d ep:%s\n", + ret, ep->name); + return ret; + } + ep->flags |= BDC_EP_STOP; + bdc_dump_epsts(bdc); + + return ret; +} diff --git a/drivers/usb/gadget/udc/bdc/bdc_cmd.h b/drivers/usb/gadget/udc/bdc/bdc_cmd.h new file mode 100644 index 000000000..533ad52fa --- /dev/null +++ b/drivers/usb/gadget/udc/bdc/bdc_cmd.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * bdc_cmd.h - header for the BDC debug functions + * + * Copyright (C) 2014 Broadcom Corporation + * + * Author: Ashwini Pahuja + */ +#ifndef __LINUX_BDC_CMD_H__ +#define __LINUX_BDC_CMD_H__ + +/* Command operations */ +int bdc_address_device(struct bdc *bdc, u32 add); +int bdc_config_ep(struct bdc *bdc, struct bdc_ep *ep); +int bdc_dconfig_ep(struct bdc *bdc, struct bdc_ep *ep); +int bdc_stop_ep(struct bdc *bdc, int epnum); +int bdc_ep_set_stall(struct bdc *bdc, int epnum); +int bdc_ep_clear_stall(struct bdc *bdc, int epnum); +int bdc_ep_bla(struct bdc *bdc, struct bdc_ep *ep, dma_addr_t dma_addr); +int bdc_function_wake(struct bdc *bdc, u8 intf); +int bdc_function_wake_fh(struct bdc *bdc, u8 intf); + +#endif /* __LINUX_BDC_CMD_H__ */ diff --git a/drivers/usb/gadget/udc/bdc/bdc_core.c b/drivers/usb/gadget/udc/bdc/bdc_core.c new file mode 100644 index 000000000..a8dbaef54 --- /dev/null +++ b/drivers/usb/gadget/udc/bdc/bdc_core.c @@ -0,0 +1,676 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * bdc_core.c - BRCM BDC USB3.0 device controller core operations + * + * Copyright (C) 2014 Broadcom Corporation + * + * Author: Ashwini Pahuja + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bdc.h" +#include "bdc_dbg.h" + +/* Poll till controller status is not OIP */ +static int poll_oip(struct bdc *bdc, u32 usec) +{ + u32 status; + int ret; + + ret = readl_poll_timeout_atomic(bdc->regs + BDC_BDCSC, status, + (BDC_CSTS(status) != BDC_OIP), 10, usec); + if (ret) + dev_err(bdc->dev, "operation timedout BDCSC: 0x%08x\n", status); + else + dev_dbg(bdc->dev, "%s complete status=%d", __func__, BDC_CSTS(status)); + + return ret; +} + +/* Stop the BDC controller */ +int bdc_stop(struct bdc *bdc) +{ + int ret; + u32 temp; + + dev_dbg(bdc->dev, "%s ()\n\n", __func__); + temp = bdc_readl(bdc->regs, BDC_BDCSC); + /* Check if BDC is already halted */ + if (BDC_CSTS(temp) == BDC_HLT) { + dev_vdbg(bdc->dev, "BDC already halted\n"); + return 0; + } + temp &= ~BDC_COP_MASK; + temp |= BDC_COS|BDC_COP_STP; + bdc_writel(bdc->regs, BDC_BDCSC, temp); + + ret = poll_oip(bdc, BDC_COP_TIMEOUT); + if (ret) + dev_err(bdc->dev, "bdc stop operation failed"); + + return ret; +} + +/* Issue a reset to BDC controller */ +int bdc_reset(struct bdc *bdc) +{ + u32 temp; + int ret; + + dev_dbg(bdc->dev, "%s ()\n", __func__); + /* First halt the controller */ + ret = bdc_stop(bdc); + if (ret) + return ret; + + temp = bdc_readl(bdc->regs, BDC_BDCSC); + temp &= ~BDC_COP_MASK; + temp |= BDC_COS|BDC_COP_RST; + bdc_writel(bdc->regs, BDC_BDCSC, temp); + ret = poll_oip(bdc, BDC_COP_TIMEOUT); + if (ret) + dev_err(bdc->dev, "bdc reset operation failed"); + + return ret; +} + +/* Run the BDC controller */ +int bdc_run(struct bdc *bdc) +{ + u32 temp; + int ret; + + dev_dbg(bdc->dev, "%s ()\n", __func__); + temp = bdc_readl(bdc->regs, BDC_BDCSC); + /* if BDC is already in running state then do not do anything */ + if (BDC_CSTS(temp) == BDC_NOR) { + dev_warn(bdc->dev, "bdc is already in running state\n"); + return 0; + } + temp &= ~BDC_COP_MASK; + temp |= BDC_COP_RUN; + temp |= BDC_COS; + bdc_writel(bdc->regs, BDC_BDCSC, temp); + ret = poll_oip(bdc, BDC_COP_TIMEOUT); + if (ret) { + dev_err(bdc->dev, "bdc run operation failed:%d", ret); + return ret; + } + temp = bdc_readl(bdc->regs, BDC_BDCSC); + if (BDC_CSTS(temp) != BDC_NOR) { + dev_err(bdc->dev, "bdc not in normal mode after RUN op :%d\n", + BDC_CSTS(temp)); + return -ESHUTDOWN; + } + + return 0; +} + +/* + * Present the termination to the host, typically called from upstream port + * event with Vbus present =1 + */ +void bdc_softconn(struct bdc *bdc) +{ + u32 uspc; + + uspc = bdc_readl(bdc->regs, BDC_USPC); + uspc &= ~BDC_PST_MASK; + uspc |= BDC_LINK_STATE_RX_DET; + uspc |= BDC_SWS; + dev_dbg(bdc->dev, "%s () uspc=%08x\n", __func__, uspc); + bdc_writel(bdc->regs, BDC_USPC, uspc); +} + +/* Remove the termination */ +void bdc_softdisconn(struct bdc *bdc) +{ + u32 uspc; + + uspc = bdc_readl(bdc->regs, BDC_USPC); + uspc |= BDC_SDC; + uspc &= ~BDC_SCN; + dev_dbg(bdc->dev, "%s () uspc=%x\n", __func__, uspc); + bdc_writel(bdc->regs, BDC_USPC, uspc); +} + +/* Set up the scratchpad buffer array and scratchpad buffers, if needed. */ +static int scratchpad_setup(struct bdc *bdc) +{ + int sp_buff_size; + u32 low32; + u32 upp32; + + sp_buff_size = BDC_SPB(bdc_readl(bdc->regs, BDC_BDCCFG0)); + dev_dbg(bdc->dev, "%s() sp_buff_size=%d\n", __func__, sp_buff_size); + if (!sp_buff_size) { + dev_dbg(bdc->dev, "Scratchpad buffer not needed\n"); + return 0; + } + /* Refer to BDC spec, Table 4 for description of SPB */ + sp_buff_size = 1 << (sp_buff_size + 5); + dev_dbg(bdc->dev, "Allocating %d bytes for scratchpad\n", sp_buff_size); + bdc->scratchpad.buff = dma_alloc_coherent(bdc->dev, sp_buff_size, + &bdc->scratchpad.sp_dma, + GFP_KERNEL); + + if (!bdc->scratchpad.buff) + goto fail; + + bdc->sp_buff_size = sp_buff_size; + bdc->scratchpad.size = sp_buff_size; + low32 = lower_32_bits(bdc->scratchpad.sp_dma); + upp32 = upper_32_bits(bdc->scratchpad.sp_dma); + cpu_to_le32s(&low32); + cpu_to_le32s(&upp32); + bdc_writel(bdc->regs, BDC_SPBBAL, low32); + bdc_writel(bdc->regs, BDC_SPBBAH, upp32); + return 0; + +fail: + bdc->scratchpad.buff = NULL; + + return -ENOMEM; +} + +/* Allocate the status report ring */ +static int setup_srr(struct bdc *bdc, int interrupter) +{ + dev_dbg(bdc->dev, "%s() NUM_SR_ENTRIES:%d\n", __func__, NUM_SR_ENTRIES); + /* Reset the SRR */ + bdc_writel(bdc->regs, BDC_SRRINT(0), BDC_SRR_RWS | BDC_SRR_RST); + bdc->srr.dqp_index = 0; + /* allocate the status report descriptors */ + bdc->srr.sr_bds = dma_alloc_coherent(bdc->dev, + NUM_SR_ENTRIES * sizeof(struct bdc_bd), + &bdc->srr.dma_addr, GFP_KERNEL); + if (!bdc->srr.sr_bds) + return -ENOMEM; + + return 0; +} + +/* Initialize the HW regs and internal data structures */ +static void bdc_mem_init(struct bdc *bdc, bool reinit) +{ + u8 size = 0; + u32 usb2_pm; + u32 low32; + u32 upp32; + u32 temp; + + dev_dbg(bdc->dev, "%s ()\n", __func__); + bdc->ep0_state = WAIT_FOR_SETUP; + bdc->dev_addr = 0; + bdc->srr.eqp_index = 0; + bdc->srr.dqp_index = 0; + bdc->zlp_needed = false; + bdc->delayed_status = false; + + bdc_writel(bdc->regs, BDC_SPBBAL, bdc->scratchpad.sp_dma); + /* Init the SRR */ + temp = BDC_SRR_RWS | BDC_SRR_RST; + /* Reset the SRR */ + bdc_writel(bdc->regs, BDC_SRRINT(0), temp); + dev_dbg(bdc->dev, "bdc->srr.sr_bds =%p\n", bdc->srr.sr_bds); + temp = lower_32_bits(bdc->srr.dma_addr); + size = fls(NUM_SR_ENTRIES) - 2; + temp |= size; + dev_dbg(bdc->dev, "SRRBAL[0]=%08x NUM_SR_ENTRIES:%d size:%d\n", + temp, NUM_SR_ENTRIES, size); + + low32 = lower_32_bits(temp); + upp32 = upper_32_bits(bdc->srr.dma_addr); + cpu_to_le32s(&low32); + cpu_to_le32s(&upp32); + + /* Write the dma addresses into regs*/ + bdc_writel(bdc->regs, BDC_SRRBAL(0), low32); + bdc_writel(bdc->regs, BDC_SRRBAH(0), upp32); + + temp = bdc_readl(bdc->regs, BDC_SRRINT(0)); + temp |= BDC_SRR_IE; + temp &= ~(BDC_SRR_RST | BDC_SRR_RWS); + bdc_writel(bdc->regs, BDC_SRRINT(0), temp); + + /* Set the Interrupt Coalescence ~500 usec */ + temp = bdc_readl(bdc->regs, BDC_INTCTLS(0)); + temp &= ~0xffff; + temp |= INT_CLS; + bdc_writel(bdc->regs, BDC_INTCTLS(0), temp); + + usb2_pm = bdc_readl(bdc->regs, BDC_USPPM2); + dev_dbg(bdc->dev, "usb2_pm=%08x", usb2_pm); + /* Enable hardware LPM Enable */ + usb2_pm |= BDC_HLE; + bdc_writel(bdc->regs, BDC_USPPM2, usb2_pm); + + /* readback for debug */ + usb2_pm = bdc_readl(bdc->regs, BDC_USPPM2); + dev_dbg(bdc->dev, "usb2_pm=%08x\n", usb2_pm); + + /* Disable any unwanted SR's on SRR */ + temp = bdc_readl(bdc->regs, BDC_BDCSC); + /* We don't want Microframe counter wrap SR */ + temp |= BDC_MASK_MCW; + bdc_writel(bdc->regs, BDC_BDCSC, temp); + + /* + * In some error cases, driver has to reset the entire BDC controller + * in that case reinit is passed as 1 + */ + if (reinit) { + int i; + /* Enable interrupts */ + temp = bdc_readl(bdc->regs, BDC_BDCSC); + temp |= BDC_GIE; + bdc_writel(bdc->regs, BDC_BDCSC, temp); + /* Init scratchpad to 0 */ + memset(bdc->scratchpad.buff, 0, bdc->sp_buff_size); + /* Initialize SRR to 0 */ + memset(bdc->srr.sr_bds, 0, + NUM_SR_ENTRIES * sizeof(struct bdc_bd)); + /* + * clear ep flags to avoid post disconnect stops/deconfigs but + * not during S2 exit + */ + if (!bdc->gadget.speed) + for (i = 1; i < bdc->num_eps; ++i) + bdc->bdc_ep_array[i]->flags = 0; + } else { + /* One time initiaization only */ + /* Enable status report function pointers */ + bdc->sr_handler[0] = bdc_sr_xsf; + bdc->sr_handler[1] = bdc_sr_uspc; + + /* EP0 status report function pointers */ + bdc->sr_xsf_ep0[0] = bdc_xsf_ep0_setup_recv; + bdc->sr_xsf_ep0[1] = bdc_xsf_ep0_data_start; + bdc->sr_xsf_ep0[2] = bdc_xsf_ep0_status_start; + } +} + +/* Free the dynamic memory */ +static void bdc_mem_free(struct bdc *bdc) +{ + dev_dbg(bdc->dev, "%s\n", __func__); + /* Free SRR */ + if (bdc->srr.sr_bds) + dma_free_coherent(bdc->dev, + NUM_SR_ENTRIES * sizeof(struct bdc_bd), + bdc->srr.sr_bds, bdc->srr.dma_addr); + + /* Free scratchpad */ + if (bdc->scratchpad.buff) + dma_free_coherent(bdc->dev, bdc->sp_buff_size, + bdc->scratchpad.buff, bdc->scratchpad.sp_dma); + + /* Destroy the dma pools */ + dma_pool_destroy(bdc->bd_table_pool); + + /* Free the bdc_ep array */ + kfree(bdc->bdc_ep_array); + + bdc->srr.sr_bds = NULL; + bdc->scratchpad.buff = NULL; + bdc->bd_table_pool = NULL; + bdc->bdc_ep_array = NULL; +} + +/* + * bdc reinit gives a controller reset and reinitialize the registers, + * called from disconnect/bus reset scenario's, to ensure proper HW cleanup + */ +int bdc_reinit(struct bdc *bdc) +{ + int ret; + + dev_dbg(bdc->dev, "%s\n", __func__); + ret = bdc_stop(bdc); + if (ret) + goto out; + + ret = bdc_reset(bdc); + if (ret) + goto out; + + /* the reinit flag is 1 */ + bdc_mem_init(bdc, true); + ret = bdc_run(bdc); +out: + bdc->reinit = false; + + return ret; +} + +/* Allocate all the dyanmic memory */ +static int bdc_mem_alloc(struct bdc *bdc) +{ + u32 page_size; + unsigned int num_ieps, num_oeps; + + dev_dbg(bdc->dev, + "%s() NUM_BDS_PER_TABLE:%d\n", __func__, + NUM_BDS_PER_TABLE); + page_size = BDC_PGS(bdc_readl(bdc->regs, BDC_BDCCFG0)); + /* page size is 2^pgs KB */ + page_size = 1 << page_size; + /* KB */ + page_size <<= 10; + dev_dbg(bdc->dev, "page_size=%d\n", page_size); + + /* Create a pool of bd tables */ + bdc->bd_table_pool = + dma_pool_create("BDC BD tables", bdc->dev, NUM_BDS_PER_TABLE * 16, + 16, page_size); + + if (!bdc->bd_table_pool) + goto fail; + + if (scratchpad_setup(bdc)) + goto fail; + + /* read from regs */ + num_ieps = NUM_NCS(bdc_readl(bdc->regs, BDC_FSCNIC)); + num_oeps = NUM_NCS(bdc_readl(bdc->regs, BDC_FSCNOC)); + /* +2: 1 for ep0 and the other is rsvd i.e. bdc_ep[0] is rsvd */ + bdc->num_eps = num_ieps + num_oeps + 2; + dev_dbg(bdc->dev, + "ieps:%d eops:%d num_eps:%d\n", + num_ieps, num_oeps, bdc->num_eps); + /* allocate array of ep pointers */ + bdc->bdc_ep_array = kzalloc_objs(struct bdc_ep *, bdc->num_eps); + if (!bdc->bdc_ep_array) + goto fail; + + dev_dbg(bdc->dev, "Allocating sr report0\n"); + if (setup_srr(bdc, 0)) + goto fail; + + return 0; +fail: + dev_warn(bdc->dev, "Couldn't initialize memory\n"); + bdc_mem_free(bdc); + + return -ENOMEM; +} + +/* opposite to bdc_hw_init */ +static void bdc_hw_exit(struct bdc *bdc) +{ + dev_dbg(bdc->dev, "%s ()\n", __func__); + bdc_mem_free(bdc); +} + +/* Initialize the bdc HW and memory */ +static int bdc_hw_init(struct bdc *bdc) +{ + int ret; + + dev_dbg(bdc->dev, "%s ()\n", __func__); + ret = bdc_reset(bdc); + if (ret) { + dev_err(bdc->dev, "err resetting bdc abort bdc init%d\n", ret); + return ret; + } + ret = bdc_mem_alloc(bdc); + if (ret) { + dev_err(bdc->dev, "Mem alloc failed, aborting\n"); + return -ENOMEM; + } + bdc_mem_init(bdc, 0); + bdc_dbg_regs(bdc); + dev_dbg(bdc->dev, "HW Init done\n"); + + return 0; +} + +static int bdc_phy_init(struct bdc *bdc) +{ + int phy_num; + int ret; + + for (phy_num = 0; phy_num < bdc->num_phys; phy_num++) { + ret = phy_init(bdc->phys[phy_num]); + if (ret) + goto err_exit_phy; + ret = phy_power_on(bdc->phys[phy_num]); + if (ret) { + phy_exit(bdc->phys[phy_num]); + goto err_exit_phy; + } + } + + return 0; + +err_exit_phy: + while (--phy_num >= 0) { + phy_power_off(bdc->phys[phy_num]); + phy_exit(bdc->phys[phy_num]); + } + + return ret; +} + +static void bdc_phy_exit(struct bdc *bdc) +{ + int phy_num; + + for (phy_num = 0; phy_num < bdc->num_phys; phy_num++) { + phy_power_off(bdc->phys[phy_num]); + phy_exit(bdc->phys[phy_num]); + } +} + +static int bdc_probe(struct platform_device *pdev) +{ + struct bdc *bdc; + int ret; + int irq; + u32 temp; + struct device *dev = &pdev->dev; + int phy_num; + + dev_dbg(dev, "%s()\n", __func__); + + bdc = devm_kzalloc(dev, sizeof(*bdc), GFP_KERNEL); + if (!bdc) + return -ENOMEM; + + bdc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(bdc->regs)) + return PTR_ERR(bdc->regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + spin_lock_init(&bdc->lock); + platform_set_drvdata(pdev, bdc); + bdc->irq = irq; + bdc->dev = dev; + dev_dbg(dev, "bdc->regs: %p irq=%d\n", bdc->regs, bdc->irq); + + bdc->num_phys = of_count_phandle_with_args(dev->of_node, + "phys", "#phy-cells"); + if (bdc->num_phys > 0) { + bdc->phys = devm_kcalloc(dev, bdc->num_phys, + sizeof(struct phy *), GFP_KERNEL); + if (!bdc->phys) + return -ENOMEM; + } else { + bdc->num_phys = 0; + } + dev_info(dev, "Using %d phy(s)\n", bdc->num_phys); + + for (phy_num = 0; phy_num < bdc->num_phys; phy_num++) { + bdc->phys[phy_num] = devm_of_phy_get_by_index( + dev, dev->of_node, phy_num); + if (IS_ERR(bdc->phys[phy_num])) { + ret = PTR_ERR(bdc->phys[phy_num]); + dev_err(bdc->dev, + "BDC phy specified but not found:%d\n", ret); + return ret; + } + } + + bdc->clk = devm_clk_get_optional(dev, "sw_usbd"); + if (IS_ERR(bdc->clk)) + return PTR_ERR(bdc->clk); + + ret = clk_prepare_enable(bdc->clk); + if (ret) { + dev_err(dev, "could not enable clock\n"); + return ret; + } + + ret = bdc_phy_init(bdc); + if (ret) { + dev_err(bdc->dev, "BDC phy init failure:%d\n", ret); + goto disable_clk; + } + + temp = bdc_readl(bdc->regs, BDC_BDCCAP1); + if ((temp & BDC_P64) && + !dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) { + dev_dbg(dev, "Using 64-bit address\n"); + } else { + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(dev, + "No suitable DMA config available, abort\n"); + ret = -ENOTSUPP; + goto phycleanup; + } + dev_dbg(dev, "Using 32-bit address\n"); + } + ret = bdc_hw_init(bdc); + if (ret) { + dev_err(dev, "BDC init failure:%d\n", ret); + goto phycleanup; + } + ret = bdc_udc_init(bdc); + if (ret) { + dev_err(dev, "BDC Gadget init failure:%d\n", ret); + goto cleanup; + } + return 0; + +cleanup: + bdc_hw_exit(bdc); +phycleanup: + bdc_phy_exit(bdc); +disable_clk: + clk_disable_unprepare(bdc->clk); + return ret; +} + +static void bdc_remove(struct platform_device *pdev) +{ + struct bdc *bdc; + unsigned long flags; + u32 temp; + + bdc = platform_get_drvdata(pdev); + dev_dbg(bdc->dev, "%s ()\n", __func__); + /* + * Disable the device interrupt source before freeing the IRQ: + * clear BDC_GIE so the controller stops asserting interrupts, + * then free_irq drains any in-flight handler. + */ + spin_lock_irqsave(&bdc->lock, flags); + temp = bdc_readl(bdc->regs, BDC_BDCSC); + temp &= ~BDC_GIE; + bdc_writel(bdc->regs, BDC_BDCSC, temp); + spin_unlock_irqrestore(&bdc->lock, flags); + free_irq(bdc->irq, bdc); + /* + * Drain func_wake_notify after free_irq: the IRQ handler arms this + * delayed_work via bdc_sr_uspc -> handle_link_state_change -> + * schedule_delayed_work (self-rearmed in bdc_func_wake_timer), so + * the IRQ must be released first to prevent re-arm after cancel. + */ + cancel_delayed_work_sync(&bdc->func_wake_notify); + bdc_udc_exit(bdc); + bdc_hw_exit(bdc); + bdc_phy_exit(bdc); + clk_disable_unprepare(bdc->clk); +} + +#ifdef CONFIG_PM_SLEEP +static int bdc_suspend(struct device *dev) +{ + struct bdc *bdc = dev_get_drvdata(dev); + int ret; + + /* Halt the controller */ + ret = bdc_stop(bdc); + if (!ret) + clk_disable_unprepare(bdc->clk); + + return ret; +} + +static int bdc_resume(struct device *dev) +{ + struct bdc *bdc = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(bdc->clk); + if (ret) { + dev_err(bdc->dev, "err enabling the clock\n"); + return ret; + } + ret = bdc_reinit(bdc); + if (ret) { + dev_err(bdc->dev, "err in bdc reinit\n"); + clk_disable_unprepare(bdc->clk); + return ret; + } + + return 0; +} + +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(bdc_pm_ops, bdc_suspend, + bdc_resume); + +static const struct of_device_id bdc_of_match[] = { + { .compatible = "brcm,bdc-udc-v2" }, + { .compatible = "brcm,bdc" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, bdc_of_match); + +static struct platform_driver bdc_driver = { + .driver = { + .name = BRCM_BDC_NAME, + .pm = &bdc_pm_ops, + .of_match_table = bdc_of_match, + }, + .probe = bdc_probe, + .remove = bdc_remove, +}; + +module_platform_driver(bdc_driver); +MODULE_AUTHOR("Ashwini Pahuja "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION(BRCM_BDC_DESC); diff --git a/drivers/usb/gadget/udc/bdc/bdc_dbg.c b/drivers/usb/gadget/udc/bdc/bdc_dbg.c new file mode 100644 index 000000000..9c03e1330 --- /dev/null +++ b/drivers/usb/gadget/udc/bdc/bdc_dbg.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * bdc_dbg.c - BRCM BDC USB3.0 device controller debug functions + * + * Copyright (C) 2014 Broadcom Corporation + * + * Author: Ashwini Pahuja + */ + +#include "bdc.h" +#include "bdc_dbg.h" + +void bdc_dbg_regs(struct bdc *bdc) +{ + u32 temp; + + dev_vdbg(bdc->dev, "bdc->regs:%p\n", bdc->regs); + temp = bdc_readl(bdc->regs, BDC_BDCCFG0); + dev_vdbg(bdc->dev, "bdccfg0:0x%08x\n", temp); + temp = bdc_readl(bdc->regs, BDC_BDCCFG1); + dev_vdbg(bdc->dev, "bdccfg1:0x%08x\n", temp); + temp = bdc_readl(bdc->regs, BDC_BDCCAP0); + dev_vdbg(bdc->dev, "bdccap0:0x%08x\n", temp); + temp = bdc_readl(bdc->regs, BDC_BDCCAP1); + dev_vdbg(bdc->dev, "bdccap1:0x%08x\n", temp); + temp = bdc_readl(bdc->regs, BDC_USPC); + dev_vdbg(bdc->dev, "uspc:0x%08x\n", temp); + temp = bdc_readl(bdc->regs, BDC_DVCSA); + dev_vdbg(bdc->dev, "dvcsa:0x%08x\n", temp); + temp = bdc_readl(bdc->regs, BDC_DVCSB); + dev_vdbg(bdc->dev, "dvcsb:0x%x08\n", temp); +} + +void bdc_dump_epsts(struct bdc *bdc) +{ + u32 temp; + + temp = bdc_readl(bdc->regs, BDC_EPSTS0); + dev_vdbg(bdc->dev, "BDC_EPSTS0:0x%08x\n", temp); + + temp = bdc_readl(bdc->regs, BDC_EPSTS1); + dev_vdbg(bdc->dev, "BDC_EPSTS1:0x%x\n", temp); + + temp = bdc_readl(bdc->regs, BDC_EPSTS2); + dev_vdbg(bdc->dev, "BDC_EPSTS2:0x%08x\n", temp); + + temp = bdc_readl(bdc->regs, BDC_EPSTS3); + dev_vdbg(bdc->dev, "BDC_EPSTS3:0x%08x\n", temp); + + temp = bdc_readl(bdc->regs, BDC_EPSTS4); + dev_vdbg(bdc->dev, "BDC_EPSTS4:0x%08x\n", temp); + + temp = bdc_readl(bdc->regs, BDC_EPSTS5); + dev_vdbg(bdc->dev, "BDC_EPSTS5:0x%08x\n", temp); + + temp = bdc_readl(bdc->regs, BDC_EPSTS6); + dev_vdbg(bdc->dev, "BDC_EPSTS6:0x%08x\n", temp); + + temp = bdc_readl(bdc->regs, BDC_EPSTS7); + dev_vdbg(bdc->dev, "BDC_EPSTS7:0x%08x\n", temp); +} + +void bdc_dbg_srr(struct bdc *bdc, u32 srr_num) +{ + struct bdc_sr *sr; + dma_addr_t addr; + int i; + + sr = bdc->srr.sr_bds; + addr = bdc->srr.dma_addr; + dev_vdbg(bdc->dev, "%s sr:%p dqp_index:%d\n", __func__, + sr, bdc->srr.dqp_index); + for (i = 0; i < NUM_SR_ENTRIES; i++) { + sr = &bdc->srr.sr_bds[i]; + dev_vdbg(bdc->dev, "%llx %08x %08x %08x %08x\n", + (unsigned long long)addr, + le32_to_cpu(sr->offset[0]), + le32_to_cpu(sr->offset[1]), + le32_to_cpu(sr->offset[2]), + le32_to_cpu(sr->offset[3])); + addr += sizeof(*sr); + } +} + +void bdc_dbg_bd_list(struct bdc *bdc, struct bdc_ep *ep) +{ + struct bd_list *bd_list = &ep->bd_list; + struct bd_table *bd_table; + struct bdc_bd *bd; + int tbi, bdi, gbdi; + dma_addr_t dma; + + gbdi = 0; + dev_vdbg(bdc->dev, + "Dump bd list for %s epnum:%d\n", + ep->name, ep->ep_num); + + dev_vdbg(bdc->dev, + "tabs:%d max_bdi:%d eqp_bdi:%d hwd_bdi:%d num_bds_table:%d\n", + bd_list->num_tabs, bd_list->max_bdi, bd_list->eqp_bdi, + bd_list->hwd_bdi, bd_list->num_bds_table); + + for (tbi = 0; tbi < bd_list->num_tabs; tbi++) { + bd_table = bd_list->bd_table_array[tbi]; + for (bdi = 0; bdi < bd_list->num_bds_table; bdi++) { + bd = bd_table->start_bd + bdi; + dma = bd_table->dma + (sizeof(struct bdc_bd) * bdi); + dev_vdbg(bdc->dev, + "tbi:%2d bdi:%2d gbdi:%2d virt:%p phys:%llx %08x %08x %08x %08x\n", + tbi, bdi, gbdi++, bd, (unsigned long long)dma, + le32_to_cpu(bd->offset[0]), + le32_to_cpu(bd->offset[1]), + le32_to_cpu(bd->offset[2]), + le32_to_cpu(bd->offset[3])); + } + dev_vdbg(bdc->dev, "\n\n"); + } +} diff --git a/drivers/usb/gadget/udc/bdc/bdc_dbg.h b/drivers/usb/gadget/udc/bdc/bdc_dbg.h new file mode 100644 index 000000000..acd8332f8 --- /dev/null +++ b/drivers/usb/gadget/udc/bdc/bdc_dbg.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * bdc_dbg.h - header for the BDC debug functions + * + * Copyright (C) 2014 Broadcom Corporation + * + * Author: Ashwini Pahuja + */ +#ifndef __LINUX_BDC_DBG_H__ +#define __LINUX_BDC_DBG_H__ + +#include "bdc.h" + +#ifdef CONFIG_USB_GADGET_VERBOSE +void bdc_dbg_bd_list(struct bdc *bdc, struct bdc_ep *ep); +void bdc_dbg_srr(struct bdc *bdc, u32 srr_num); +void bdc_dbg_regs(struct bdc *bdc); +void bdc_dump_epsts(struct bdc *bdc); +#else +static inline void bdc_dbg_regs(struct bdc *bdc) +{ } + +static inline void bdc_dbg_srr(struct bdc *bdc, u32 srr_num) +{ } + +static inline void bdc_dbg_bd_list(struct bdc *bdc, struct bdc_ep *ep) +{ } + +static inline void bdc_dump_epsts(struct bdc *bdc) +{ } +#endif /* CONFIG_USB_GADGET_VERBOSE */ +#endif /* __LINUX_BDC_DBG_H__ */ diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.c b/drivers/usb/gadget/udc/bdc/bdc_ep.c new file mode 100644 index 000000000..a7a22e5ec --- /dev/null +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.c @@ -0,0 +1,2038 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * bdc_ep.c - BRCM BDC USB3.0 device controller endpoint related functions + * + * Copyright (C) 2014 Broadcom Corporation + * + * Author: Ashwini Pahuja + * + * Based on drivers under drivers/usb/ + */ +#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 "bdc.h" +#include "bdc_ep.h" +#include "bdc_cmd.h" +#include "bdc_dbg.h" + +static const char * const ep0_state_string[] = { + "WAIT_FOR_SETUP", + "WAIT_FOR_DATA_START", + "WAIT_FOR_DATA_XMIT", + "WAIT_FOR_STATUS_START", + "WAIT_FOR_STATUS_XMIT", + "STATUS_PENDING" +}; + +/* Free the bdl during ep disable */ +static void ep_bd_list_free(struct bdc_ep *ep, u32 num_tabs) +{ + struct bd_list *bd_list = &ep->bd_list; + struct bdc *bdc = ep->bdc; + struct bd_table *bd_table; + int index; + + dev_dbg(bdc->dev, "%s ep:%s num_tabs:%d\n", + __func__, ep->name, num_tabs); + + if (!bd_list->bd_table_array) { + dev_dbg(bdc->dev, "%s already freed\n", ep->name); + return; + } + for (index = 0; index < num_tabs; index++) { + /* + * check if the bd_table struct is allocated ? + * if yes, then check if bd memory has been allocated, then + * free the dma_pool and also the bd_table struct memory + */ + bd_table = bd_list->bd_table_array[index]; + dev_dbg(bdc->dev, "bd_table:%p index:%d\n", bd_table, index); + if (!bd_table) { + dev_dbg(bdc->dev, "bd_table not allocated\n"); + continue; + } + if (!bd_table->start_bd) { + dev_dbg(bdc->dev, "bd dma pool not allocated\n"); + continue; + } + + dev_dbg(bdc->dev, + "Free dma pool start_bd:%p dma:%llx\n", + bd_table->start_bd, + (unsigned long long)bd_table->dma); + + dma_pool_free(bdc->bd_table_pool, + bd_table->start_bd, + bd_table->dma); + /* Free the bd_table structure */ + kfree(bd_table); + } + /* Free the bd table array */ + kfree(ep->bd_list.bd_table_array); +} + +/* + * chain the tables, by insteting a chain bd at the end of prev_table, pointing + * to next_table + */ +static inline void chain_table(struct bd_table *prev_table, + struct bd_table *next_table, + u32 bd_p_tab) +{ + /* Chain the prev table to next table */ + prev_table->start_bd[bd_p_tab-1].offset[0] = + cpu_to_le32(lower_32_bits(next_table->dma)); + + prev_table->start_bd[bd_p_tab-1].offset[1] = + cpu_to_le32(upper_32_bits(next_table->dma)); + + prev_table->start_bd[bd_p_tab-1].offset[2] = + 0x0; + + prev_table->start_bd[bd_p_tab-1].offset[3] = + cpu_to_le32(MARK_CHAIN_BD); +} + +/* Allocate the bdl for ep, during config ep */ +static int ep_bd_list_alloc(struct bdc_ep *ep) +{ + struct bd_table *prev_table = NULL; + int index, num_tabs, bd_p_tab; + struct bdc *bdc = ep->bdc; + struct bd_table *bd_table; + dma_addr_t dma; + + if (usb_endpoint_xfer_isoc(ep->desc)) + num_tabs = NUM_TABLES_ISOCH; + else + num_tabs = NUM_TABLES; + + bd_p_tab = NUM_BDS_PER_TABLE; + /* if there is only 1 table in bd list then loop chain to self */ + dev_dbg(bdc->dev, + "%s ep:%p num_tabs:%d\n", + __func__, ep, num_tabs); + + /* Allocate memory for table array */ + ep->bd_list.bd_table_array = kzalloc_objs(struct bd_table *, num_tabs, + GFP_ATOMIC); + if (!ep->bd_list.bd_table_array) + return -ENOMEM; + + /* Allocate memory for each table */ + for (index = 0; index < num_tabs; index++) { + /* Allocate memory for bd_table structure */ + bd_table = kzalloc_obj(*bd_table, GFP_ATOMIC); + if (!bd_table) + goto fail; + + bd_table->start_bd = dma_pool_zalloc(bdc->bd_table_pool, + GFP_ATOMIC, + &dma); + if (!bd_table->start_bd) { + kfree(bd_table); + goto fail; + } + + bd_table->dma = dma; + + dev_dbg(bdc->dev, + "index:%d start_bd:%p dma=%08llx prev_table:%p\n", + index, bd_table->start_bd, + (unsigned long long)bd_table->dma, prev_table); + + ep->bd_list.bd_table_array[index] = bd_table; + if (prev_table) + chain_table(prev_table, bd_table, bd_p_tab); + + prev_table = bd_table; + } + chain_table(prev_table, ep->bd_list.bd_table_array[0], bd_p_tab); + /* Memory allocation is successful, now init the internal fields */ + ep->bd_list.num_tabs = num_tabs; + ep->bd_list.max_bdi = (num_tabs * bd_p_tab) - 1; + ep->bd_list.num_tabs = num_tabs; + ep->bd_list.num_bds_table = bd_p_tab; + ep->bd_list.eqp_bdi = 0; + ep->bd_list.hwd_bdi = 0; + + return 0; +fail: + /* Free the bd_table_array, bd_table struct, bd's */ + ep_bd_list_free(ep, num_tabs); + + return -ENOMEM; +} + +/* returns how many bd's are need for this transfer */ +static inline int bd_needed_req(struct bdc_req *req) +{ + int bd_needed = 0; + int remaining; + + /* 1 bd needed for 0 byte transfer */ + if (req->usb_req.length == 0) + return 1; + + /* remaining bytes after tranfering all max BD size BD's */ + remaining = req->usb_req.length % BD_MAX_BUFF_SIZE; + if (remaining) + bd_needed++; + + /* How many maximum BUFF size BD's ? */ + remaining = req->usb_req.length / BD_MAX_BUFF_SIZE; + bd_needed += remaining; + + return bd_needed; +} + +/* returns the bd index(bdi) corresponding to bd dma address */ +static int bd_add_to_bdi(struct bdc_ep *ep, dma_addr_t bd_dma_addr) +{ + struct bd_list *bd_list = &ep->bd_list; + dma_addr_t dma_first_bd, dma_last_bd; + struct bdc *bdc = ep->bdc; + struct bd_table *bd_table; + bool found = false; + int tbi, bdi; + + dma_first_bd = dma_last_bd = 0; + dev_dbg(bdc->dev, "%s %llx\n", + __func__, (unsigned long long)bd_dma_addr); + /* + * Find in which table this bd_dma_addr belongs?, go through the table + * array and compare addresses of first and last address of bd of each + * table + */ + for (tbi = 0; tbi < bd_list->num_tabs; tbi++) { + bd_table = bd_list->bd_table_array[tbi]; + dma_first_bd = bd_table->dma; + dma_last_bd = bd_table->dma + + (sizeof(struct bdc_bd) * + (bd_list->num_bds_table - 1)); + dev_dbg(bdc->dev, "dma_first_bd:%llx dma_last_bd:%llx\n", + (unsigned long long)dma_first_bd, + (unsigned long long)dma_last_bd); + if (bd_dma_addr >= dma_first_bd && bd_dma_addr <= dma_last_bd) { + found = true; + break; + } + } + if (unlikely(!found)) { + dev_err(bdc->dev, "%s FATAL err, bd not found\n", __func__); + return -EINVAL; + } + /* Now we know the table, find the bdi */ + bdi = (bd_dma_addr - dma_first_bd) / sizeof(struct bdc_bd); + + /* return the global bdi, to compare with ep eqp_bdi */ + return (bdi + (tbi * bd_list->num_bds_table)); +} + +/* returns the table index(tbi) of the given bdi */ +static int bdi_to_tbi(struct bdc_ep *ep, int bdi) +{ + int tbi; + + tbi = bdi / ep->bd_list.num_bds_table; + dev_vdbg(ep->bdc->dev, + "bdi:%d num_bds_table:%d tbi:%d\n", + bdi, ep->bd_list.num_bds_table, tbi); + + return tbi; +} + +/* Find the bdi last bd in the transfer */ +static inline int find_end_bdi(struct bdc_ep *ep, int next_hwd_bdi) +{ + int end_bdi; + + end_bdi = next_hwd_bdi - 1; + if (end_bdi < 0) + end_bdi = ep->bd_list.max_bdi - 1; + else if ((end_bdi % (ep->bd_list.num_bds_table-1)) == 0) + end_bdi--; + + return end_bdi; +} + +/* + * How many transfer bd's are available on this ep bdl, chain bds are not + * counted in available bds + */ +static int bd_available_ep(struct bdc_ep *ep) +{ + struct bd_list *bd_list = &ep->bd_list; + int available1, available2; + struct bdc *bdc = ep->bdc; + int chain_bd1, chain_bd2; + int available_bd = 0; + + available1 = available2 = chain_bd1 = chain_bd2 = 0; + /* if empty then we have all bd's available - number of chain bd's */ + if (bd_list->eqp_bdi == bd_list->hwd_bdi) + return bd_list->max_bdi - bd_list->num_tabs; + + /* + * Depending upon where eqp and dqp pointers are, caculate number + * of avaialble bd's + */ + if (bd_list->hwd_bdi < bd_list->eqp_bdi) { + /* available bd's are from eqp..max_bds + 0..dqp - chain_bds */ + available1 = bd_list->max_bdi - bd_list->eqp_bdi; + available2 = bd_list->hwd_bdi; + chain_bd1 = available1 / bd_list->num_bds_table; + chain_bd2 = available2 / bd_list->num_bds_table; + dev_vdbg(bdc->dev, "chain_bd1:%d chain_bd2:%d\n", + chain_bd1, chain_bd2); + available_bd = available1 + available2 - chain_bd1 - chain_bd2; + } else { + /* available bd's are from eqp..dqp - number of chain bd's */ + available1 = bd_list->hwd_bdi - bd_list->eqp_bdi; + /* if gap between eqp and dqp is less than NUM_BDS_PER_TABLE */ + if ((bd_list->hwd_bdi - bd_list->eqp_bdi) + <= bd_list->num_bds_table) { + /* If there any chain bd in between */ + if (!(bdi_to_tbi(ep, bd_list->hwd_bdi) + == bdi_to_tbi(ep, bd_list->eqp_bdi))) { + available_bd = available1 - 1; + } + } else { + chain_bd1 = available1 / bd_list->num_bds_table; + available_bd = available1 - chain_bd1; + } + } + /* + * we need to keep one extra bd to check if ring is full or empty so + * reduce by 1 + */ + available_bd--; + dev_vdbg(bdc->dev, "available_bd:%d\n", available_bd); + + return available_bd; +} + +/* Notify the hardware after queueing the bd to bdl */ +void bdc_notify_xfr(struct bdc *bdc, u32 epnum) +{ + struct bdc_ep *ep = bdc->bdc_ep_array[epnum]; + + dev_vdbg(bdc->dev, "%s epnum:%d\n", __func__, epnum); + /* + * We don't have anyway to check if ep state is running, + * except the software flags. + */ + if (unlikely(ep->flags & BDC_EP_STOP)) + ep->flags &= ~BDC_EP_STOP; + + bdc_writel(bdc->regs, BDC_XSFNTF, epnum); +} + +/* returns the bd corresponding to bdi */ +static struct bdc_bd *bdi_to_bd(struct bdc_ep *ep, int bdi) +{ + int tbi = bdi_to_tbi(ep, bdi); + int local_bdi = 0; + + local_bdi = bdi - (tbi * ep->bd_list.num_bds_table); + dev_vdbg(ep->bdc->dev, + "%s bdi:%d local_bdi:%d\n", + __func__, bdi, local_bdi); + + return (ep->bd_list.bd_table_array[tbi]->start_bd + local_bdi); +} + +/* Advance the enqueue pointer */ +static void ep_bdlist_eqp_adv(struct bdc_ep *ep) +{ + ep->bd_list.eqp_bdi++; + /* if it's chain bd, then move to next */ + if (((ep->bd_list.eqp_bdi + 1) % ep->bd_list.num_bds_table) == 0) + ep->bd_list.eqp_bdi++; + + /* if the eqp is pointing to last + 1 then move back to 0 */ + if (ep->bd_list.eqp_bdi == (ep->bd_list.max_bdi + 1)) + ep->bd_list.eqp_bdi = 0; +} + +/* Setup the first bd for ep0 transfer */ +static int setup_first_bd_ep0(struct bdc *bdc, struct bdc_req *req, u32 *dword3) +{ + u16 wValue; + u32 req_len; + + req->ep->dir = 0; + req_len = req->usb_req.length; + switch (bdc->ep0_state) { + case WAIT_FOR_DATA_START: + *dword3 |= BD_TYPE_DS; + if (bdc->setup_pkt.bRequestType & USB_DIR_IN) + *dword3 |= BD_DIR_IN; + + /* check if zlp will be needed */ + wValue = le16_to_cpu(bdc->setup_pkt.wValue); + if ((wValue > req_len) && + (req_len % bdc->gadget.ep0->maxpacket == 0)) { + dev_dbg(bdc->dev, "ZLP needed wVal:%d len:%d MaxP:%d\n", + wValue, req_len, + bdc->gadget.ep0->maxpacket); + bdc->zlp_needed = true; + } + break; + + case WAIT_FOR_STATUS_START: + *dword3 |= BD_TYPE_SS; + if (!le16_to_cpu(bdc->setup_pkt.wLength) || + !(bdc->setup_pkt.bRequestType & USB_DIR_IN)) + *dword3 |= BD_DIR_IN; + break; + default: + dev_err(bdc->dev, + "Unknown ep0 state for queueing bd ep0_state:%s\n", + ep0_state_string[bdc->ep0_state]); + return -EINVAL; + } + + return 0; +} + +/* Setup the bd dma descriptor for a given request */ +static int setup_bd_list_xfr(struct bdc *bdc, struct bdc_req *req, int num_bds) +{ + dma_addr_t buf_add = req->usb_req.dma; + u32 maxp, tfs, dword2, dword3; + struct bd_transfer *bd_xfr; + struct bd_list *bd_list; + struct bdc_ep *ep; + struct bdc_bd *bd; + int ret, bdnum; + u32 req_len; + + ep = req->ep; + bd_list = &ep->bd_list; + bd_xfr = &req->bd_xfr; + bd_xfr->req = req; + bd_xfr->start_bdi = bd_list->eqp_bdi; + bd = bdi_to_bd(ep, bd_list->eqp_bdi); + req_len = req->usb_req.length; + maxp = usb_endpoint_maxp(ep->desc); + tfs = roundup(req->usb_req.length, maxp); + tfs = tfs/maxp; + dev_vdbg(bdc->dev, "%s ep:%s num_bds:%d tfs:%d r_len:%d bd:%p\n", + __func__, ep->name, num_bds, tfs, req_len, bd); + + for (bdnum = 0; bdnum < num_bds; bdnum++) { + dword2 = dword3 = 0; + /* First bd */ + if (!bdnum) { + dword3 |= BD_SOT|BD_SBF|(tfs<ep_num == 1) { + ret = setup_first_bd_ep0(bdc, req, &dword3); + if (ret) + return ret; + } + } + if (!req->ep->dir) + dword3 |= BD_ISP; + + if (req_len > BD_MAX_BUFF_SIZE) { + dword2 |= BD_MAX_BUFF_SIZE; + req_len -= BD_MAX_BUFF_SIZE; + } else { + /* this should be the last bd */ + dword2 |= req_len; + dword3 |= BD_IOC; + dword3 |= BD_EOT; + } + /* Currently only 1 INT target is supported */ + dword2 |= BD_INTR_TARGET(0); + bd = bdi_to_bd(ep, ep->bd_list.eqp_bdi); + if (unlikely(!bd)) { + dev_err(bdc->dev, "Err bd pointing to wrong addr\n"); + return -EINVAL; + } + /* write bd */ + bd->offset[0] = cpu_to_le32(lower_32_bits(buf_add)); + bd->offset[1] = cpu_to_le32(upper_32_bits(buf_add)); + bd->offset[2] = cpu_to_le32(dword2); + bd->offset[3] = cpu_to_le32(dword3); + /* advance eqp pointer */ + ep_bdlist_eqp_adv(ep); + /* advance the buff pointer */ + buf_add += BD_MAX_BUFF_SIZE; + dev_vdbg(bdc->dev, "buf_add:%08llx req_len:%d bd:%p eqp:%d\n", + (unsigned long long)buf_add, req_len, bd, + ep->bd_list.eqp_bdi); + bd = bdi_to_bd(ep, ep->bd_list.eqp_bdi); + bd->offset[3] = cpu_to_le32(BD_SBF); + } + /* clear the STOP BD fetch bit from the first bd of this xfr */ + bd = bdi_to_bd(ep, bd_xfr->start_bdi); + bd->offset[3] &= cpu_to_le32(~BD_SBF); + /* the new eqp will be next hw dqp */ + bd_xfr->num_bds = num_bds; + bd_xfr->next_hwd_bdi = ep->bd_list.eqp_bdi; + /* everything is written correctly before notifying the HW */ + wmb(); + + return 0; +} + +/* Queue the xfr */ +static int bdc_queue_xfr(struct bdc *bdc, struct bdc_req *req) +{ + int num_bds, bd_available; + struct bdc_ep *ep; + int ret; + + ep = req->ep; + dev_dbg(bdc->dev, "%s req:%p\n", __func__, req); + dev_dbg(bdc->dev, "eqp_bdi:%d hwd_bdi:%d\n", + ep->bd_list.eqp_bdi, ep->bd_list.hwd_bdi); + + num_bds = bd_needed_req(req); + bd_available = bd_available_ep(ep); + + /* how many bd's are avaialble on ep */ + if (num_bds > bd_available) + return -ENOMEM; + + ret = setup_bd_list_xfr(bdc, req, num_bds); + if (ret) + return ret; + list_add_tail(&req->queue, &ep->queue); + bdc_dbg_bd_list(bdc, ep); + bdc_notify_xfr(bdc, ep->ep_num); + + return 0; +} + +/* callback to gadget layer when xfr completes */ +static void bdc_req_complete(struct bdc_ep *ep, struct bdc_req *req, + int status) +{ + struct bdc *bdc = ep->bdc; + + if (req == NULL) + return; + + dev_dbg(bdc->dev, "%s ep:%s status:%d\n", __func__, ep->name, status); + list_del(&req->queue); + req->usb_req.status = status; + usb_gadget_unmap_request(&bdc->gadget, &req->usb_req, ep->dir); + if (req->usb_req.complete) { + spin_unlock(&bdc->lock); + usb_gadget_giveback_request(&ep->usb_ep, &req->usb_req); + spin_lock(&bdc->lock); + } +} + +/* Disable the endpoint */ +int bdc_ep_disable(struct bdc_ep *ep) +{ + struct bdc_req *req; + struct bdc *bdc; + int ret; + + ret = 0; + bdc = ep->bdc; + dev_dbg(bdc->dev, "%s() ep->ep_num=%d\n", __func__, ep->ep_num); + /* Stop the endpoint */ + ret = bdc_stop_ep(bdc, ep->ep_num); + + /* + * Intentionally don't check the ret value of stop, it can fail in + * disconnect scenarios, continue with dconfig + */ + /* de-queue any pending requests */ + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct bdc_req, + queue); + bdc_req_complete(ep, req, -ESHUTDOWN); + } + /* deconfigure the endpoint */ + ret = bdc_dconfig_ep(bdc, ep); + if (ret) + dev_warn(bdc->dev, + "dconfig fail but continue with memory free"); + + ep->flags = 0; + /* ep0 memory is not freed, but reused on next connect sr */ + if (ep->ep_num == 1) + return 0; + + /* Free the bdl memory */ + ep_bd_list_free(ep, ep->bd_list.num_tabs); + ep->desc = NULL; + ep->comp_desc = NULL; + ep->usb_ep.desc = NULL; + ep->ep_type = 0; + + return ret; +} + +/* Enable the ep */ +int bdc_ep_enable(struct bdc_ep *ep) +{ + struct bdc *bdc; + int ret = 0; + + bdc = ep->bdc; + dev_dbg(bdc->dev, "%s NUM_TABLES:%d %d\n", + __func__, NUM_TABLES, NUM_TABLES_ISOCH); + + ret = ep_bd_list_alloc(ep); + if (ret) { + dev_err(bdc->dev, "ep bd list allocation failed:%d\n", ret); + return -ENOMEM; + } + bdc_dbg_bd_list(bdc, ep); + /* only for ep0: config ep is called for ep0 from connect event */ + if (ep->ep_num == 1) + return ret; + + /* Issue a configure endpoint command */ + ret = bdc_config_ep(bdc, ep); + if (ret) + return ret; + + ep->usb_ep.maxpacket = usb_endpoint_maxp(ep->desc); + ep->usb_ep.desc = ep->desc; + ep->usb_ep.comp_desc = ep->comp_desc; + ep->ep_type = usb_endpoint_type(ep->desc); + ep->flags |= BDC_EP_ENABLED; + + return 0; +} + +/* EP0 related code */ + +/* Queue a status stage BD */ +static int ep0_queue_status_stage(struct bdc *bdc) +{ + struct bdc_req *status_req; + struct bdc_ep *ep; + + status_req = &bdc->status_req; + ep = bdc->bdc_ep_array[1]; + status_req->ep = ep; + status_req->usb_req.length = 0; + status_req->usb_req.status = -EINPROGRESS; + status_req->usb_req.actual = 0; + status_req->usb_req.complete = NULL; + bdc_queue_xfr(bdc, status_req); + + return 0; +} + +/* Queue xfr on ep0 */ +static int ep0_queue(struct bdc_ep *ep, struct bdc_req *req) +{ + struct bdc *bdc; + int ret; + + bdc = ep->bdc; + dev_dbg(bdc->dev, "%s()\n", __func__); + req->usb_req.actual = 0; + req->usb_req.status = -EINPROGRESS; + req->epnum = ep->ep_num; + + if (bdc->delayed_status) { + bdc->delayed_status = false; + /* if status stage was delayed? */ + if (bdc->ep0_state == WAIT_FOR_STATUS_START) { + /* Queue a status stage BD */ + ep0_queue_status_stage(bdc); + bdc->ep0_state = WAIT_FOR_STATUS_XMIT; + return 0; + } + } else { + /* + * if delayed status is false and 0 length transfer is requested + * i.e. for status stage of some setup request, then just + * return from here the status stage is queued independently + */ + if (req->usb_req.length == 0) + return 0; + + } + ret = usb_gadget_map_request(&bdc->gadget, &req->usb_req, ep->dir); + if (ret) { + dev_err(bdc->dev, "dma mapping failed %s\n", ep->name); + return ret; + } + + return bdc_queue_xfr(bdc, req); +} + +/* Queue data stage */ +static int ep0_queue_data_stage(struct bdc *bdc) +{ + struct bdc_ep *ep; + + dev_dbg(bdc->dev, "%s\n", __func__); + ep = bdc->bdc_ep_array[1]; + bdc->ep0_req.ep = ep; + bdc->ep0_req.usb_req.complete = NULL; + + return ep0_queue(ep, &bdc->ep0_req); +} + +/* Queue req on ep */ +static int ep_queue(struct bdc_ep *ep, struct bdc_req *req) +{ + struct bdc *bdc; + int ret = 0; + + if (!req || !ep->usb_ep.desc) + return -EINVAL; + + bdc = ep->bdc; + + req->usb_req.actual = 0; + req->usb_req.status = -EINPROGRESS; + req->epnum = ep->ep_num; + + ret = usb_gadget_map_request(&bdc->gadget, &req->usb_req, ep->dir); + if (ret) { + dev_err(bdc->dev, "dma mapping failed\n"); + return ret; + } + + return bdc_queue_xfr(bdc, req); +} + +/* Dequeue a request from ep */ +static int ep_dequeue(struct bdc_ep *ep, struct bdc_req *req) +{ + int start_bdi, end_bdi, tbi, eqp_bdi, curr_hw_dqpi; + bool start_pending, end_pending; + bool first_remove = false; + struct bdc_req *first_req; + struct bdc_bd *bd_start; + struct bd_table *table; + dma_addr_t next_bd_dma; + u64 deq_ptr_64 = 0; + struct bdc *bdc; + u32 tmp_32; + int ret; + + bdc = ep->bdc; + start_pending = end_pending = false; + eqp_bdi = ep->bd_list.eqp_bdi - 1; + + if (eqp_bdi < 0) + eqp_bdi = ep->bd_list.max_bdi; + + start_bdi = req->bd_xfr.start_bdi; + end_bdi = find_end_bdi(ep, req->bd_xfr.next_hwd_bdi); + + dev_dbg(bdc->dev, "%s ep:%s start:%d end:%d\n", + __func__, ep->name, start_bdi, end_bdi); + dev_dbg(bdc->dev, "%s ep=%p ep->desc=%p\n", __func__, + ep, (void *)ep->usb_ep.desc); + /* if still connected, stop the ep to see where the HW is ? */ + if (!(bdc_readl(bdc->regs, BDC_USPC) & BDC_PST_MASK)) { + ret = bdc_stop_ep(bdc, ep->ep_num); + /* if there is an issue, then no need to go further */ + if (ret) + return 0; + } else + return 0; + + /* + * After endpoint is stopped, there can be 3 cases, the request + * is processed, pending or in the middle of processing + */ + + /* The current hw dequeue pointer */ + tmp_32 = bdc_readl(bdc->regs, BDC_EPSTS0); + deq_ptr_64 = tmp_32; + tmp_32 = bdc_readl(bdc->regs, BDC_EPSTS1); + deq_ptr_64 |= ((u64)tmp_32 << 32); + + /* we have the dma addr of next bd that will be fetched by hardware */ + curr_hw_dqpi = bd_add_to_bdi(ep, deq_ptr_64); + if (curr_hw_dqpi < 0) + return curr_hw_dqpi; + + /* + * curr_hw_dqpi points to actual dqp of HW and HW owns bd's from + * curr_hw_dqbdi..eqp_bdi. + */ + + /* Check if start_bdi and end_bdi are in range of HW owned BD's */ + if (curr_hw_dqpi > eqp_bdi) { + /* there is a wrap from last to 0 */ + if (start_bdi >= curr_hw_dqpi || start_bdi <= eqp_bdi) { + start_pending = true; + end_pending = true; + } else if (end_bdi >= curr_hw_dqpi || end_bdi <= eqp_bdi) { + end_pending = true; + } + } else { + if (start_bdi >= curr_hw_dqpi) { + start_pending = true; + end_pending = true; + } else if (end_bdi >= curr_hw_dqpi) { + end_pending = true; + } + } + dev_dbg(bdc->dev, + "start_pending:%d end_pending:%d speed:%d\n", + start_pending, end_pending, bdc->gadget.speed); + + /* If both start till end are processes, we cannot deq req */ + if (!start_pending && !end_pending) + return -EINVAL; + + /* + * if ep_dequeue is called after disconnect then just return + * success from here + */ + if (bdc->gadget.speed == USB_SPEED_UNKNOWN) + return 0; + tbi = bdi_to_tbi(ep, req->bd_xfr.next_hwd_bdi); + table = ep->bd_list.bd_table_array[tbi]; + next_bd_dma = table->dma + + sizeof(struct bdc_bd)*(req->bd_xfr.next_hwd_bdi - + tbi * ep->bd_list.num_bds_table); + + first_req = list_first_entry(&ep->queue, struct bdc_req, + queue); + + if (req == first_req) + first_remove = true; + + /* + * Due to HW limitation we need to bypadd chain bd's and issue ep_bla, + * incase if start is pending this is the first request in the list + * then issue ep_bla instead of marking as chain bd + */ + if (start_pending && !first_remove) { + /* + * Mark the start bd as Chain bd, and point the chain + * bd to next_bd_dma + */ + bd_start = bdi_to_bd(ep, start_bdi); + bd_start->offset[0] = cpu_to_le32(lower_32_bits(next_bd_dma)); + bd_start->offset[1] = cpu_to_le32(upper_32_bits(next_bd_dma)); + bd_start->offset[2] = 0x0; + bd_start->offset[3] = cpu_to_le32(MARK_CHAIN_BD); + bdc_dbg_bd_list(bdc, ep); + } else if (end_pending) { + /* + * The transfer is stopped in the middle, move the + * HW deq pointer to next_bd_dma + */ + ret = bdc_ep_bla(bdc, ep, next_bd_dma); + if (ret) { + dev_err(bdc->dev, "error in ep_bla:%d\n", ret); + return ret; + } + } + + return 0; +} + +/* Halt/Clear the ep based on value */ +static int ep_set_halt(struct bdc_ep *ep, u32 value) +{ + struct bdc *bdc; + int ret; + + bdc = ep->bdc; + dev_dbg(bdc->dev, "%s ep:%s value=%d\n", __func__, ep->name, value); + + if (value) { + dev_dbg(bdc->dev, "Halt\n"); + if (ep->ep_num == 1) + bdc->ep0_state = WAIT_FOR_SETUP; + + ret = bdc_ep_set_stall(bdc, ep->ep_num); + if (ret) + dev_err(bdc->dev, "failed to set STALL on %s\n", + ep->name); + else + ep->flags |= BDC_EP_STALL; + } else { + /* Clear */ + dev_dbg(bdc->dev, "Before Clear\n"); + ret = bdc_ep_clear_stall(bdc, ep->ep_num); + if (ret) + dev_err(bdc->dev, "failed to clear STALL on %s\n", + ep->name); + else + ep->flags &= ~BDC_EP_STALL; + dev_dbg(bdc->dev, "After Clear\n"); + } + + return ret; +} + +/* Free all the ep */ +void bdc_free_ep(struct bdc *bdc) +{ + struct bdc_ep *ep; + u8 epnum; + + dev_dbg(bdc->dev, "%s\n", __func__); + for (epnum = 1; epnum < bdc->num_eps; epnum++) { + ep = bdc->bdc_ep_array[epnum]; + if (!ep) + continue; + + if (ep->flags & BDC_EP_ENABLED) + ep_bd_list_free(ep, ep->bd_list.num_tabs); + + /* ep0 is not in this gadget list */ + if (epnum != 1) + list_del(&ep->usb_ep.ep_list); + + kfree(ep); + } +} + +/* USB2 spec, section 7.1.20 */ +static int bdc_set_test_mode(struct bdc *bdc) +{ + u32 usb2_pm; + + usb2_pm = bdc_readl(bdc->regs, BDC_USPPM2); + usb2_pm &= ~BDC_PTC_MASK; + dev_dbg(bdc->dev, "%s\n", __func__); + switch (bdc->test_mode) { + case USB_TEST_J: + case USB_TEST_K: + case USB_TEST_SE0_NAK: + case USB_TEST_PACKET: + case USB_TEST_FORCE_ENABLE: + usb2_pm |= bdc->test_mode << 28; + break; + default: + return -EINVAL; + } + dev_dbg(bdc->dev, "usb2_pm=%08x", usb2_pm); + bdc_writel(bdc->regs, BDC_USPPM2, usb2_pm); + + return 0; +} + +/* + * Helper function to handle Transfer status report with status as either + * success or short + */ +static void handle_xsr_succ_status(struct bdc *bdc, struct bdc_ep *ep, + struct bdc_sr *sreport) +{ + int short_bdi, start_bdi, end_bdi, max_len_bds, chain_bds; + struct bd_list *bd_list = &ep->bd_list; + int actual_length, length_short; + struct bd_transfer *bd_xfr; + struct bdc_bd *short_bd; + struct bdc_req *req; + u64 deq_ptr_64 = 0; + int status = 0; + int sr_status; + u32 tmp_32; + + dev_dbg(bdc->dev, "%s ep:%p\n", __func__, ep); + bdc_dbg_srr(bdc, 0); + /* do not process thie sr if ignore flag is set */ + if (ep->ignore_next_sr) { + ep->ignore_next_sr = false; + return; + } + + if (unlikely(list_empty(&ep->queue))) { + dev_warn(bdc->dev, "xfr srr with no BD's queued\n"); + return; + } + req = list_entry(ep->queue.next, struct bdc_req, + queue); + + bd_xfr = &req->bd_xfr; + sr_status = XSF_STS(le32_to_cpu(sreport->offset[3])); + + /* + * sr_status is short and this transfer has more than 1 bd then it needs + * special handling, this is only applicable for bulk and ctrl + */ + if (sr_status == XSF_SHORT && bd_xfr->num_bds > 1) { + /* + * This is multi bd xfr, lets see which bd + * caused short transfer and how many bytes have been + * transferred so far. + */ + tmp_32 = le32_to_cpu(sreport->offset[0]); + deq_ptr_64 = tmp_32; + tmp_32 = le32_to_cpu(sreport->offset[1]); + deq_ptr_64 |= ((u64)tmp_32 << 32); + short_bdi = bd_add_to_bdi(ep, deq_ptr_64); + if (unlikely(short_bdi < 0)) + dev_warn(bdc->dev, "bd doesn't exist?\n"); + + start_bdi = bd_xfr->start_bdi; + /* + * We know the start_bdi and short_bdi, how many xfr + * bds in between + */ + if (start_bdi <= short_bdi) { + max_len_bds = short_bdi - start_bdi; + if (max_len_bds <= bd_list->num_bds_table) { + if (!(bdi_to_tbi(ep, start_bdi) == + bdi_to_tbi(ep, short_bdi))) + max_len_bds--; + } else { + chain_bds = max_len_bds/bd_list->num_bds_table; + max_len_bds -= chain_bds; + } + } else { + /* there is a wrap in the ring within a xfr */ + chain_bds = (bd_list->max_bdi - start_bdi)/ + bd_list->num_bds_table; + chain_bds += short_bdi/bd_list->num_bds_table; + max_len_bds = bd_list->max_bdi - start_bdi; + max_len_bds += short_bdi; + max_len_bds -= chain_bds; + } + /* max_len_bds is the number of full length bds */ + end_bdi = find_end_bdi(ep, bd_xfr->next_hwd_bdi); + if (!(end_bdi == short_bdi)) + ep->ignore_next_sr = true; + + actual_length = max_len_bds * BD_MAX_BUFF_SIZE; + short_bd = bdi_to_bd(ep, short_bdi); + /* length queued */ + length_short = le32_to_cpu(short_bd->offset[2]) & 0x1FFFFF; + /* actual length trensfered */ + length_short -= SR_BD_LEN(le32_to_cpu(sreport->offset[2])); + actual_length += length_short; + req->usb_req.actual = actual_length; + } else { + req->usb_req.actual = req->usb_req.length - + SR_BD_LEN(le32_to_cpu(sreport->offset[2])); + dev_dbg(bdc->dev, + "len=%d actual=%d bd_xfr->next_hwd_bdi:%d\n", + req->usb_req.length, req->usb_req.actual, + bd_xfr->next_hwd_bdi); + } + + /* Update the dequeue pointer */ + ep->bd_list.hwd_bdi = bd_xfr->next_hwd_bdi; + if (req->usb_req.actual < req->usb_req.length) { + dev_dbg(bdc->dev, "short xfr on %d\n", ep->ep_num); + if (req->usb_req.short_not_ok) + status = -EREMOTEIO; + } + bdc_req_complete(ep, bd_xfr->req, status); +} + +/* EP0 setup related packet handlers */ + +/* + * Setup packet received, just store the packet and process on next DS or SS + * started SR + */ +void bdc_xsf_ep0_setup_recv(struct bdc *bdc, struct bdc_sr *sreport) +{ + struct usb_ctrlrequest *setup_pkt; + u32 len; + + dev_dbg(bdc->dev, + "%s ep0_state:%s\n", + __func__, ep0_state_string[bdc->ep0_state]); + /* Store received setup packet */ + setup_pkt = &bdc->setup_pkt; + memcpy(setup_pkt, &sreport->offset[0], sizeof(*setup_pkt)); + len = le16_to_cpu(setup_pkt->wLength); + if (!len) + bdc->ep0_state = WAIT_FOR_STATUS_START; + else + bdc->ep0_state = WAIT_FOR_DATA_START; + + + dev_dbg(bdc->dev, + "%s exit ep0_state:%s\n", + __func__, ep0_state_string[bdc->ep0_state]); +} + +/* Stall ep0 */ +static void ep0_stall(struct bdc *bdc) +{ + struct bdc_ep *ep = bdc->bdc_ep_array[1]; + struct bdc_req *req; + + dev_dbg(bdc->dev, "%s\n", __func__); + bdc->delayed_status = false; + ep_set_halt(ep, 1); + + /* de-queue any pendig requests */ + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct bdc_req, + queue); + bdc_req_complete(ep, req, -ESHUTDOWN); + } +} + +/* SET_ADD handlers */ +static int ep0_set_address(struct bdc *bdc, struct usb_ctrlrequest *ctrl) +{ + enum usb_device_state state = bdc->gadget.state; + int ret = 0; + u32 addr; + + addr = le16_to_cpu(ctrl->wValue); + dev_dbg(bdc->dev, + "%s addr:%d dev state:%d\n", + __func__, addr, state); + + if (addr > 127) + return -EINVAL; + + switch (state) { + case USB_STATE_DEFAULT: + case USB_STATE_ADDRESS: + /* Issue Address device command */ + ret = bdc_address_device(bdc, addr); + if (ret) + return ret; + + if (addr) + usb_gadget_set_state(&bdc->gadget, USB_STATE_ADDRESS); + else + usb_gadget_set_state(&bdc->gadget, USB_STATE_DEFAULT); + + bdc->dev_addr = addr; + break; + default: + dev_warn(bdc->dev, + "SET Address in wrong device state %d\n", + state); + ret = -EINVAL; + } + + return ret; +} + +/* Handler for SET/CLEAR FEATURE requests for device */ +static int ep0_handle_feature_dev(struct bdc *bdc, u16 wValue, + u16 wIndex, bool set) +{ + enum usb_device_state state = bdc->gadget.state; + u32 usppms = 0; + + dev_dbg(bdc->dev, "%s set:%d dev state:%d\n", + __func__, set, state); + switch (wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + dev_dbg(bdc->dev, "USB_DEVICE_REMOTE_WAKEUP\n"); + if (set) + bdc->devstatus |= REMOTE_WAKE_ENABLE; + else + bdc->devstatus &= ~REMOTE_WAKE_ENABLE; + break; + + case USB_DEVICE_TEST_MODE: + dev_dbg(bdc->dev, "USB_DEVICE_TEST_MODE\n"); + if ((wIndex & 0xFF) || + (bdc->gadget.speed != USB_SPEED_HIGH) || !set) + return -EINVAL; + + bdc->test_mode = wIndex >> 8; + break; + + case USB_DEVICE_U1_ENABLE: + dev_dbg(bdc->dev, "USB_DEVICE_U1_ENABLE\n"); + + if (bdc->gadget.speed != USB_SPEED_SUPER || + state != USB_STATE_CONFIGURED) + return -EINVAL; + + usppms = bdc_readl(bdc->regs, BDC_USPPMS); + if (set) { + /* clear previous u1t */ + usppms &= ~BDC_U1T(BDC_U1T_MASK); + usppms |= BDC_U1T(U1_TIMEOUT); + usppms |= BDC_U1E | BDC_PORT_W1S; + bdc->devstatus |= (1 << USB_DEV_STAT_U1_ENABLED); + } else { + usppms &= ~BDC_U1E; + usppms |= BDC_PORT_W1S; + bdc->devstatus &= ~(1 << USB_DEV_STAT_U1_ENABLED); + } + bdc_writel(bdc->regs, BDC_USPPMS, usppms); + break; + + case USB_DEVICE_U2_ENABLE: + dev_dbg(bdc->dev, "USB_DEVICE_U2_ENABLE\n"); + + if (bdc->gadget.speed != USB_SPEED_SUPER || + state != USB_STATE_CONFIGURED) + return -EINVAL; + + usppms = bdc_readl(bdc->regs, BDC_USPPMS); + if (set) { + usppms |= BDC_U2E; + usppms |= BDC_U2A; + bdc->devstatus |= (1 << USB_DEV_STAT_U2_ENABLED); + } else { + usppms &= ~BDC_U2E; + usppms &= ~BDC_U2A; + bdc->devstatus &= ~(1 << USB_DEV_STAT_U2_ENABLED); + } + bdc_writel(bdc->regs, BDC_USPPMS, usppms); + break; + + case USB_DEVICE_LTM_ENABLE: + dev_dbg(bdc->dev, "USB_DEVICE_LTM_ENABLE?\n"); + if (bdc->gadget.speed != USB_SPEED_SUPER || + state != USB_STATE_CONFIGURED) + return -EINVAL; + break; + default: + dev_err(bdc->dev, "Unknown wValue:%d\n", wValue); + return -EOPNOTSUPP; + } /* USB_RECIP_DEVICE end */ + + return 0; +} + +/* SET/CLEAR FEATURE handler */ +static int ep0_handle_feature(struct bdc *bdc, + struct usb_ctrlrequest *setup_pkt, bool set) +{ + enum usb_device_state state = bdc->gadget.state; + struct bdc_ep *ep; + u16 wValue; + u16 wIndex; + int epnum; + + wValue = le16_to_cpu(setup_pkt->wValue); + wIndex = le16_to_cpu(setup_pkt->wIndex); + + dev_dbg(bdc->dev, + "%s wValue=%d wIndex=%d devstate=%08x speed=%d set=%d", + __func__, wValue, wIndex, state, + bdc->gadget.speed, set); + + switch (setup_pkt->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + return ep0_handle_feature_dev(bdc, wValue, wIndex, set); + case USB_RECIP_INTERFACE: + dev_dbg(bdc->dev, "USB_RECIP_INTERFACE\n"); + /* USB3 spec, sec 9.4.9 */ + if (wValue != USB_INTRF_FUNC_SUSPEND) + return -EINVAL; + /* USB3 spec, Table 9-8 */ + if (set) { + if (wIndex & USB_INTRF_FUNC_SUSPEND_RW) { + dev_dbg(bdc->dev, "SET REMOTE_WAKEUP\n"); + bdc->devstatus |= REMOTE_WAKE_ENABLE; + } else { + dev_dbg(bdc->dev, "CLEAR REMOTE_WAKEUP\n"); + bdc->devstatus &= ~REMOTE_WAKE_ENABLE; + } + } + break; + + case USB_RECIP_ENDPOINT: + dev_dbg(bdc->dev, "USB_RECIP_ENDPOINT\n"); + if (wValue != USB_ENDPOINT_HALT) + return -EINVAL; + + epnum = wIndex & USB_ENDPOINT_NUMBER_MASK; + if (epnum) { + if ((wIndex & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) + epnum = epnum * 2 + 1; + else + epnum *= 2; + } else { + epnum = 1; /*EP0*/ + } + /* + * If CLEAR_FEATURE on ep0 then don't do anything as the stall + * condition on ep0 has already been cleared when SETUP packet + * was received. + */ + if (epnum == 1 && !set) { + dev_dbg(bdc->dev, "ep0 stall already cleared\n"); + return 0; + } + dev_dbg(bdc->dev, "epnum=%d\n", epnum); + ep = bdc->bdc_ep_array[epnum]; + if (!ep) + return -EINVAL; + + return ep_set_halt(ep, set); + default: + dev_err(bdc->dev, "Unknown recipient\n"); + return -EINVAL; + } + + return 0; +} + +/* GET_STATUS request handler */ +static int ep0_handle_status(struct bdc *bdc, + struct usb_ctrlrequest *setup_pkt) +{ + enum usb_device_state state = bdc->gadget.state; + struct bdc_ep *ep; + u16 usb_status = 0; + u32 epnum; + u16 wIndex; + + /* USB2.0 spec sec 9.4.5 */ + if (state == USB_STATE_DEFAULT) + return -EINVAL; + wIndex = le16_to_cpu(setup_pkt->wIndex); + dev_dbg(bdc->dev, "%s\n", __func__); + usb_status = bdc->devstatus; + switch (setup_pkt->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + dev_dbg(bdc->dev, + "USB_RECIP_DEVICE devstatus:%08x\n", + bdc->devstatus); + /* USB3 spec, sec 9.4.5 */ + if (bdc->gadget.speed == USB_SPEED_SUPER) + usb_status &= ~REMOTE_WAKE_ENABLE; + break; + + case USB_RECIP_INTERFACE: + dev_dbg(bdc->dev, "USB_RECIP_INTERFACE\n"); + if (bdc->gadget.speed == USB_SPEED_SUPER) { + /* + * This should come from func for Func remote wkup + * usb_status |=1; + */ + if (bdc->devstatus & REMOTE_WAKE_ENABLE) + usb_status |= REMOTE_WAKE_ENABLE; + } else { + usb_status = 0; + } + + break; + + case USB_RECIP_ENDPOINT: + dev_dbg(bdc->dev, "USB_RECIP_ENDPOINT\n"); + epnum = wIndex & USB_ENDPOINT_NUMBER_MASK; + if (epnum) { + if ((wIndex & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) + epnum = epnum*2 + 1; + else + epnum *= 2; + } else { + epnum = 1; /* EP0 */ + } + + ep = bdc->bdc_ep_array[epnum]; + if (!ep) { + dev_err(bdc->dev, "ISSUE, GET_STATUS for invalid EP ?"); + return -EINVAL; + } + if (ep->flags & BDC_EP_STALL) + usb_status |= 1 << USB_ENDPOINT_HALT; + + break; + default: + dev_err(bdc->dev, "Unknown recipient for get_status\n"); + return -EINVAL; + } + /* prepare a data stage for GET_STATUS */ + dev_dbg(bdc->dev, "usb_status=%08x\n", usb_status); + *(__le16 *)bdc->ep0_response_buff = cpu_to_le16(usb_status); + bdc->ep0_req.usb_req.length = 2; + bdc->ep0_req.usb_req.buf = &bdc->ep0_response_buff; + ep0_queue_data_stage(bdc); + + return 0; +} + +static void ep0_set_sel_cmpl(struct usb_ep *_ep, struct usb_request *_req) +{ + /* ep0_set_sel_cmpl */ +} + +/* Queue data stage to handle 6 byte SET_SEL request */ +static int ep0_set_sel(struct bdc *bdc, + struct usb_ctrlrequest *setup_pkt) +{ + struct bdc_ep *ep; + u16 wLength; + + dev_dbg(bdc->dev, "%s\n", __func__); + wLength = le16_to_cpu(setup_pkt->wLength); + if (unlikely(wLength != 6)) { + dev_err(bdc->dev, "%s Wrong wLength:%d\n", __func__, wLength); + return -EINVAL; + } + ep = bdc->bdc_ep_array[1]; + bdc->ep0_req.ep = ep; + bdc->ep0_req.usb_req.length = 6; + bdc->ep0_req.usb_req.buf = bdc->ep0_response_buff; + bdc->ep0_req.usb_req.complete = ep0_set_sel_cmpl; + ep0_queue_data_stage(bdc); + + return 0; +} + +/* + * Queue a 0 byte bd only if wLength is more than the length and length is + * a multiple of MaxPacket then queue 0 byte BD + */ +static int ep0_queue_zlp(struct bdc *bdc) +{ + int ret; + + dev_dbg(bdc->dev, "%s\n", __func__); + bdc->ep0_req.ep = bdc->bdc_ep_array[1]; + bdc->ep0_req.usb_req.length = 0; + bdc->ep0_req.usb_req.complete = NULL; + bdc->ep0_state = WAIT_FOR_DATA_START; + ret = bdc_queue_xfr(bdc, &bdc->ep0_req); + if (ret) { + dev_err(bdc->dev, "err queueing zlp :%d\n", ret); + return ret; + } + bdc->ep0_state = WAIT_FOR_DATA_XMIT; + + return 0; +} + +/* Control request handler */ +static int handle_control_request(struct bdc *bdc) +{ + enum usb_device_state state = bdc->gadget.state; + struct usb_ctrlrequest *setup_pkt; + int delegate_setup = 0; + int ret = 0; + int config = 0; + + setup_pkt = &bdc->setup_pkt; + dev_dbg(bdc->dev, "%s\n", __func__); + if ((setup_pkt->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (setup_pkt->bRequest) { + case USB_REQ_SET_ADDRESS: + dev_dbg(bdc->dev, "USB_REQ_SET_ADDRESS\n"); + ret = ep0_set_address(bdc, setup_pkt); + bdc->devstatus &= DEVSTATUS_CLEAR; + break; + + case USB_REQ_SET_CONFIGURATION: + dev_dbg(bdc->dev, "USB_REQ_SET_CONFIGURATION\n"); + if (state == USB_STATE_ADDRESS) { + usb_gadget_set_state(&bdc->gadget, + USB_STATE_CONFIGURED); + } else if (state == USB_STATE_CONFIGURED) { + /* + * USB2 spec sec 9.4.7, if wValue is 0 then dev + * is moved to addressed state + */ + config = le16_to_cpu(setup_pkt->wValue); + if (!config) + usb_gadget_set_state( + &bdc->gadget, + USB_STATE_ADDRESS); + } + delegate_setup = 1; + break; + + case USB_REQ_SET_FEATURE: + dev_dbg(bdc->dev, "USB_REQ_SET_FEATURE\n"); + ret = ep0_handle_feature(bdc, setup_pkt, 1); + break; + + case USB_REQ_CLEAR_FEATURE: + dev_dbg(bdc->dev, "USB_REQ_CLEAR_FEATURE\n"); + ret = ep0_handle_feature(bdc, setup_pkt, 0); + break; + + case USB_REQ_GET_STATUS: + dev_dbg(bdc->dev, "USB_REQ_GET_STATUS\n"); + ret = ep0_handle_status(bdc, setup_pkt); + break; + + case USB_REQ_SET_SEL: + dev_dbg(bdc->dev, "USB_REQ_SET_SEL\n"); + ret = ep0_set_sel(bdc, setup_pkt); + break; + + case USB_REQ_SET_ISOCH_DELAY: + dev_warn(bdc->dev, + "USB_REQ_SET_ISOCH_DELAY not handled\n"); + ret = 0; + break; + default: + delegate_setup = 1; + } + } else { + delegate_setup = 1; + } + + if (delegate_setup) { + spin_unlock(&bdc->lock); + ret = bdc->gadget_driver->setup(&bdc->gadget, setup_pkt); + spin_lock(&bdc->lock); + } + + return ret; +} + +/* EP0: Data stage started */ +void bdc_xsf_ep0_data_start(struct bdc *bdc, struct bdc_sr *sreport) +{ + struct bdc_ep *ep; + int ret = 0; + + dev_dbg(bdc->dev, "%s\n", __func__); + ep = bdc->bdc_ep_array[1]; + /* If ep0 was stalled, the clear it first */ + if (ep->flags & BDC_EP_STALL) { + ret = ep_set_halt(ep, 0); + if (ret) + goto err; + } + if (bdc->ep0_state != WAIT_FOR_DATA_START) + dev_warn(bdc->dev, + "Data stage not expected ep0_state:%s\n", + ep0_state_string[bdc->ep0_state]); + + ret = handle_control_request(bdc); + if (ret == USB_GADGET_DELAYED_STATUS) { + /* + * The ep0 state will remain WAIT_FOR_DATA_START till + * we received ep_queue on ep0 + */ + bdc->delayed_status = true; + return; + } + if (!ret) { + bdc->ep0_state = WAIT_FOR_DATA_XMIT; + dev_dbg(bdc->dev, + "ep0_state:%s", ep0_state_string[bdc->ep0_state]); + return; + } +err: + ep0_stall(bdc); +} + +/* EP0: status stage started */ +void bdc_xsf_ep0_status_start(struct bdc *bdc, struct bdc_sr *sreport) +{ + struct usb_ctrlrequest *setup_pkt; + struct bdc_ep *ep; + int ret = 0; + + dev_dbg(bdc->dev, + "%s ep0_state:%s", + __func__, ep0_state_string[bdc->ep0_state]); + ep = bdc->bdc_ep_array[1]; + + /* check if ZLP was queued? */ + if (bdc->zlp_needed) + bdc->zlp_needed = false; + + if (ep->flags & BDC_EP_STALL) { + ret = ep_set_halt(ep, 0); + if (ret) + goto err; + } + + if ((bdc->ep0_state != WAIT_FOR_STATUS_START) && + (bdc->ep0_state != WAIT_FOR_DATA_XMIT)) + dev_err(bdc->dev, + "Status stage recv but ep0_state:%s\n", + ep0_state_string[bdc->ep0_state]); + + /* check if data stage is in progress ? */ + if (bdc->ep0_state == WAIT_FOR_DATA_XMIT) { + bdc->ep0_state = STATUS_PENDING; + /* Status stage will be queued upon Data stage transmit event */ + dev_dbg(bdc->dev, + "status started but data not transmitted yet\n"); + return; + } + setup_pkt = &bdc->setup_pkt; + + /* + * 2 stage setup then only process the setup, for 3 stage setup the date + * stage is already handled + */ + if (!le16_to_cpu(setup_pkt->wLength)) { + ret = handle_control_request(bdc); + if (ret == USB_GADGET_DELAYED_STATUS) { + bdc->delayed_status = true; + /* ep0_state will remain WAIT_FOR_STATUS_START */ + return; + } + } + if (!ret) { + /* Queue a status stage BD */ + ep0_queue_status_stage(bdc); + bdc->ep0_state = WAIT_FOR_STATUS_XMIT; + dev_dbg(bdc->dev, + "ep0_state:%s", ep0_state_string[bdc->ep0_state]); + return; + } +err: + ep0_stall(bdc); +} + +/* Helper function to update ep0 upon SR with xsf_succ or xsf_short */ +static void ep0_xsf_complete(struct bdc *bdc, struct bdc_sr *sreport) +{ + dev_dbg(bdc->dev, "%s\n", __func__); + switch (bdc->ep0_state) { + case WAIT_FOR_DATA_XMIT: + bdc->ep0_state = WAIT_FOR_STATUS_START; + break; + case WAIT_FOR_STATUS_XMIT: + bdc->ep0_state = WAIT_FOR_SETUP; + if (bdc->test_mode) { + int ret; + + dev_dbg(bdc->dev, "test_mode:%d\n", bdc->test_mode); + ret = bdc_set_test_mode(bdc); + if (ret < 0) { + dev_err(bdc->dev, "Err in setting Test mode\n"); + return; + } + bdc->test_mode = 0; + } + break; + case STATUS_PENDING: + bdc_xsf_ep0_status_start(bdc, sreport); + break; + + default: + dev_err(bdc->dev, + "Unknown ep0_state:%s\n", + ep0_state_string[bdc->ep0_state]); + + } +} + +/* xfr completion status report handler */ +void bdc_sr_xsf(struct bdc *bdc, struct bdc_sr *sreport) +{ + struct bdc_ep *ep; + u32 sr_status; + u8 ep_num; + + ep_num = (le32_to_cpu(sreport->offset[3])>>4) & 0x1f; + if (ep_num >= bdc->num_eps) { + dev_err(bdc->dev, "xsf for invalid ep %u\n", ep_num); + return; + } + ep = bdc->bdc_ep_array[ep_num]; + if (!ep || !(ep->flags & BDC_EP_ENABLED)) { + dev_err(bdc->dev, "xsf for ep not enabled\n"); + return; + } + /* + * check if this transfer is after link went from U3->U0 due + * to remote wakeup + */ + if (bdc->devstatus & FUNC_WAKE_ISSUED) { + bdc->devstatus &= ~(FUNC_WAKE_ISSUED); + dev_dbg(bdc->dev, "%s clearing FUNC_WAKE_ISSUED flag\n", + __func__); + } + sr_status = XSF_STS(le32_to_cpu(sreport->offset[3])); + dev_dbg_ratelimited(bdc->dev, "%s sr_status=%d ep:%s\n", + __func__, sr_status, ep->name); + + switch (sr_status) { + case XSF_SUCC: + case XSF_SHORT: + handle_xsr_succ_status(bdc, ep, sreport); + if (ep_num == 1) + ep0_xsf_complete(bdc, sreport); + break; + + case XSF_SETUP_RECV: + case XSF_DATA_START: + case XSF_STATUS_START: + if (ep_num != 1) { + dev_err(bdc->dev, + "ep0 related packets on non ep0 endpoint"); + return; + } + bdc->sr_xsf_ep0[sr_status - XSF_SETUP_RECV](bdc, sreport); + break; + + case XSF_BABB: + if (ep_num == 1) { + dev_dbg(bdc->dev, "Babble on ep0 zlp_need:%d\n", + bdc->zlp_needed); + /* + * If the last completed transfer had wLength >Data Len, + * and Len is multiple of MaxPacket,then queue ZLP + */ + if (bdc->zlp_needed) { + /* queue 0 length bd */ + ep0_queue_zlp(bdc); + return; + } + } + dev_warn(bdc->dev, "Babble on ep not handled\n"); + break; + default: + dev_warn(bdc->dev, "sr status not handled:%x\n", sr_status); + break; + } +} + +static int bdc_gadget_ep_queue(struct usb_ep *_ep, + struct usb_request *_req, gfp_t gfp_flags) +{ + struct bdc_req *req; + unsigned long flags; + struct bdc_ep *ep; + struct bdc *bdc; + int ret; + + if (!_ep || !_ep->desc) + return -ESHUTDOWN; + + if (!_req || !_req->complete || !_req->buf) + return -EINVAL; + + ep = to_bdc_ep(_ep); + req = to_bdc_req(_req); + bdc = ep->bdc; + dev_dbg(bdc->dev, "%s ep:%p req:%p\n", __func__, ep, req); + dev_dbg(bdc->dev, "queuing request %p to %s length %d zero:%d\n", + _req, ep->name, _req->length, _req->zero); + + if (!ep->usb_ep.desc) { + dev_warn(bdc->dev, + "trying to queue req %p to disabled %s\n", + _req, ep->name); + return -ESHUTDOWN; + } + + if (_req->length > MAX_XFR_LEN) { + dev_warn(bdc->dev, + "req length > supported MAX:%d requested:%d\n", + MAX_XFR_LEN, _req->length); + return -EOPNOTSUPP; + } + spin_lock_irqsave(&bdc->lock, flags); + if (ep == bdc->bdc_ep_array[1]) + ret = ep0_queue(ep, req); + else + ret = ep_queue(ep, req); + + spin_unlock_irqrestore(&bdc->lock, flags); + + return ret; +} + +static int bdc_gadget_ep_dequeue(struct usb_ep *_ep, + struct usb_request *_req) +{ + struct bdc_req *req; + struct bdc_req *iter; + unsigned long flags; + struct bdc_ep *ep; + struct bdc *bdc; + int ret; + + if (!_ep || !_req) + return -EINVAL; + + ep = to_bdc_ep(_ep); + req = to_bdc_req(_req); + bdc = ep->bdc; + dev_dbg(bdc->dev, "%s ep:%s req:%p\n", __func__, ep->name, req); + bdc_dbg_bd_list(bdc, ep); + spin_lock_irqsave(&bdc->lock, flags); + + req = NULL; + /* make sure it's still queued on this endpoint */ + list_for_each_entry(iter, &ep->queue, queue) { + if (&iter->usb_req != _req) + continue; + req = iter; + break; + } + if (!req) { + spin_unlock_irqrestore(&bdc->lock, flags); + dev_err(bdc->dev, "usb_req !=req n"); + return -EINVAL; + } + ret = ep_dequeue(ep, req); + if (ret) { + ret = -EOPNOTSUPP; + goto err; + } + bdc_req_complete(ep, req, -ECONNRESET); + +err: + bdc_dbg_bd_list(bdc, ep); + spin_unlock_irqrestore(&bdc->lock, flags); + + return ret; +} + +static int bdc_gadget_ep_set_halt(struct usb_ep *_ep, int value) +{ + unsigned long flags; + struct bdc_ep *ep; + struct bdc *bdc; + int ret; + + ep = to_bdc_ep(_ep); + bdc = ep->bdc; + dev_dbg(bdc->dev, "%s ep:%s value=%d\n", __func__, ep->name, value); + spin_lock_irqsave(&bdc->lock, flags); + if (usb_endpoint_xfer_isoc(ep->usb_ep.desc)) + ret = -EINVAL; + else if (!list_empty(&ep->queue)) + ret = -EAGAIN; + else + ret = ep_set_halt(ep, value); + + spin_unlock_irqrestore(&bdc->lock, flags); + + return ret; +} + +static struct usb_request *bdc_gadget_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct bdc_req *req; + struct bdc_ep *ep; + + req = kzalloc_obj(*req, gfp_flags); + if (!req) + return NULL; + + ep = to_bdc_ep(_ep); + req->ep = ep; + req->epnum = ep->ep_num; + req->usb_req.dma = DMA_ADDR_INVALID; + dev_dbg(ep->bdc->dev, "%s ep:%s req:%p\n", __func__, ep->name, req); + + return &req->usb_req; +} + +static void bdc_gadget_free_request(struct usb_ep *_ep, + struct usb_request *_req) +{ + struct bdc_req *req; + + req = to_bdc_req(_req); + kfree(req); +} + +/* endpoint operations */ + +/* configure endpoint and also allocate resources */ +static int bdc_gadget_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + unsigned long flags; + struct bdc_ep *ep; + struct bdc *bdc; + int ret; + + if (!_ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) { + pr_debug("%s invalid parameters\n", __func__); + return -EINVAL; + } + + if (!desc->wMaxPacketSize) { + pr_debug("%s missing wMaxPacketSize\n", __func__); + return -EINVAL; + } + + ep = to_bdc_ep(_ep); + bdc = ep->bdc; + + /* Sanity check, upper layer will not send enable for ep0 */ + if (ep == bdc->bdc_ep_array[1]) + return -EINVAL; + + if (!bdc->gadget_driver + || bdc->gadget.speed == USB_SPEED_UNKNOWN) { + return -ESHUTDOWN; + } + + dev_dbg(bdc->dev, "%s Enabling %s\n", __func__, ep->name); + spin_lock_irqsave(&bdc->lock, flags); + ep->desc = desc; + ep->comp_desc = _ep->comp_desc; + ret = bdc_ep_enable(ep); + spin_unlock_irqrestore(&bdc->lock, flags); + + return ret; +} + +static int bdc_gadget_ep_disable(struct usb_ep *_ep) +{ + unsigned long flags; + struct bdc_ep *ep; + struct bdc *bdc; + int ret; + + if (!_ep) { + pr_debug("bdc: invalid parameters\n"); + return -EINVAL; + } + ep = to_bdc_ep(_ep); + bdc = ep->bdc; + + /* Upper layer will not call this for ep0, but do a sanity check */ + if (ep == bdc->bdc_ep_array[1]) { + dev_warn(bdc->dev, "%s called for ep0\n", __func__); + return -EINVAL; + } + dev_dbg(bdc->dev, + "%s() ep:%s ep->flags:%08x\n", + __func__, ep->name, ep->flags); + + if (!(ep->flags & BDC_EP_ENABLED)) { + if (bdc->gadget.speed != USB_SPEED_UNKNOWN) + dev_warn(bdc->dev, "%s is already disabled\n", + ep->name); + return 0; + } + spin_lock_irqsave(&bdc->lock, flags); + ret = bdc_ep_disable(ep); + spin_unlock_irqrestore(&bdc->lock, flags); + + return ret; +} + +static const struct usb_ep_ops bdc_gadget_ep_ops = { + .enable = bdc_gadget_ep_enable, + .disable = bdc_gadget_ep_disable, + .alloc_request = bdc_gadget_alloc_request, + .free_request = bdc_gadget_free_request, + .queue = bdc_gadget_ep_queue, + .dequeue = bdc_gadget_ep_dequeue, + .set_halt = bdc_gadget_ep_set_halt +}; + +/* dir = 1 is IN */ +static int init_ep(struct bdc *bdc, u32 epnum, u32 dir) +{ + struct bdc_ep *ep; + + dev_dbg(bdc->dev, "%s epnum=%d dir=%d\n", __func__, epnum, dir); + ep = kzalloc_obj(*ep); + if (!ep) + return -ENOMEM; + + ep->bdc = bdc; + ep->dir = dir; + + if (dir) + ep->usb_ep.caps.dir_in = true; + else + ep->usb_ep.caps.dir_out = true; + + /* ep->ep_num is the index inside bdc_ep */ + if (epnum == 1) { + ep->ep_num = 1; + bdc->bdc_ep_array[ep->ep_num] = ep; + snprintf(ep->name, sizeof(ep->name), "ep%d", epnum - 1); + usb_ep_set_maxpacket_limit(&ep->usb_ep, EP0_MAX_PKT_SIZE); + ep->usb_ep.caps.type_control = true; + ep->comp_desc = NULL; + bdc->gadget.ep0 = &ep->usb_ep; + } else { + if (dir) + ep->ep_num = epnum * 2 - 1; + else + ep->ep_num = epnum * 2 - 2; + + bdc->bdc_ep_array[ep->ep_num] = ep; + snprintf(ep->name, sizeof(ep->name), "ep%d%s", epnum - 1, + dir & 1 ? "in" : "out"); + + usb_ep_set_maxpacket_limit(&ep->usb_ep, 1024); + ep->usb_ep.caps.type_iso = true; + ep->usb_ep.caps.type_bulk = true; + ep->usb_ep.caps.type_int = true; + ep->usb_ep.max_streams = 0; + list_add_tail(&ep->usb_ep.ep_list, &bdc->gadget.ep_list); + } + ep->usb_ep.ops = &bdc_gadget_ep_ops; + ep->usb_ep.name = ep->name; + ep->flags = 0; + ep->ignore_next_sr = false; + dev_dbg(bdc->dev, "ep=%p ep->usb_ep.name=%s epnum=%d ep->epnum=%d\n", + ep, ep->usb_ep.name, epnum, ep->ep_num); + + INIT_LIST_HEAD(&ep->queue); + + return 0; +} + +/* Init all ep */ +int bdc_init_ep(struct bdc *bdc) +{ + u8 epnum; + int ret; + + dev_dbg(bdc->dev, "%s()\n", __func__); + INIT_LIST_HEAD(&bdc->gadget.ep_list); + /* init ep0 */ + ret = init_ep(bdc, 1, 0); + if (ret) { + dev_err(bdc->dev, "init ep ep0 fail %d\n", ret); + return ret; + } + + for (epnum = 2; epnum <= bdc->num_eps / 2; epnum++) { + /* OUT */ + ret = init_ep(bdc, epnum, 0); + if (ret) { + dev_err(bdc->dev, + "init ep failed for:%d error: %d\n", + epnum, ret); + return ret; + } + + /* IN */ + ret = init_ep(bdc, epnum, 1); + if (ret) { + dev_err(bdc->dev, + "init ep failed for:%d error: %d\n", + epnum, ret); + return ret; + } + } + + return 0; +} diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.h b/drivers/usb/gadget/udc/bdc/bdc_ep.h new file mode 100644 index 000000000..4d3affd07 --- /dev/null +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * bdc_ep.h - header for the BDC debug functions + * + * Copyright (C) 2014 Broadcom Corporation + * + * Author: Ashwini Pahuja + */ +#ifndef __LINUX_BDC_EP_H__ +#define __LINUX_BDC_EP_H__ + +int bdc_init_ep(struct bdc *bdc); +int bdc_ep_disable(struct bdc_ep *ep); +int bdc_ep_enable(struct bdc_ep *ep); +void bdc_free_ep(struct bdc *bdc); + +#endif /* __LINUX_BDC_EP_H__ */ diff --git a/drivers/usb/gadget/udc/bdc/bdc_udc.c b/drivers/usb/gadget/udc/bdc/bdc_udc.c new file mode 100644 index 000000000..7a12219ed --- /dev/null +++ b/drivers/usb/gadget/udc/bdc/bdc_udc.c @@ -0,0 +1,591 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * bdc_udc.c - BRCM BDC USB3.0 device controller gagdet ops + * + * Copyright (C) 2014 Broadcom Corporation + * + * Author: Ashwini Pahuja + * + * Based on drivers under drivers/usb/gadget/udc/ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bdc.h" +#include "bdc_ep.h" +#include "bdc_cmd.h" +#include "bdc_dbg.h" + +static const struct usb_gadget_ops bdc_gadget_ops; + +static const char * const conn_speed_str[] = { + "Not connected", + "Full Speed", + "Low Speed", + "High Speed", + "Super Speed", +}; + +/* EP0 initial descripror */ +static struct usb_endpoint_descriptor bdc_gadget_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .bEndpointAddress = 0, + .wMaxPacketSize = cpu_to_le16(EP0_MAX_PKT_SIZE), +}; + +/* Advance the srr dqp maintained by SW */ +static void srr_dqp_index_advc(struct bdc *bdc, u32 srr_num) +{ + struct srr *srr; + + srr = &bdc->srr; + dev_dbg_ratelimited(bdc->dev, "srr->dqp_index:%d\n", srr->dqp_index); + srr->dqp_index++; + /* rollback to 0 if we are past the last */ + if (srr->dqp_index == NUM_SR_ENTRIES) + srr->dqp_index = 0; +} + +/* connect sr */ +static void bdc_uspc_connected(struct bdc *bdc) +{ + u32 speed, temp; + u32 usppms; + int ret; + + temp = bdc_readl(bdc->regs, BDC_USPC); + speed = BDC_PSP(temp); + dev_dbg(bdc->dev, "%s speed=%x\n", __func__, speed); + switch (speed) { + case BDC_SPEED_SS: + bdc_gadget_ep0_desc.wMaxPacketSize = + cpu_to_le16(EP0_MAX_PKT_SIZE); + bdc->gadget.ep0->maxpacket = EP0_MAX_PKT_SIZE; + bdc->gadget.speed = USB_SPEED_SUPER; + /* Enable U1T in SS mode */ + usppms = bdc_readl(bdc->regs, BDC_USPPMS); + usppms &= ~BDC_U1T(0xff); + usppms |= BDC_U1T(U1_TIMEOUT); + usppms |= BDC_PORT_W1S; + bdc_writel(bdc->regs, BDC_USPPMS, usppms); + break; + + case BDC_SPEED_HS: + bdc_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + bdc->gadget.ep0->maxpacket = 64; + bdc->gadget.speed = USB_SPEED_HIGH; + break; + + case BDC_SPEED_FS: + bdc_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + bdc->gadget.ep0->maxpacket = 64; + bdc->gadget.speed = USB_SPEED_FULL; + break; + + case BDC_SPEED_LS: + bdc_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(8); + bdc->gadget.ep0->maxpacket = 8; + bdc->gadget.speed = USB_SPEED_LOW; + break; + default: + dev_err(bdc->dev, "UNDEFINED SPEED\n"); + return; + } + dev_dbg(bdc->dev, "connected at %s\n", conn_speed_str[speed]); + /* Now we know the speed, configure ep0 */ + bdc->bdc_ep_array[1]->desc = &bdc_gadget_ep0_desc; + ret = bdc_config_ep(bdc, bdc->bdc_ep_array[1]); + if (ret) + dev_err(bdc->dev, "EP0 config failed\n"); + bdc->bdc_ep_array[1]->usb_ep.desc = &bdc_gadget_ep0_desc; + bdc->bdc_ep_array[1]->flags |= BDC_EP_ENABLED; + usb_gadget_set_state(&bdc->gadget, USB_STATE_DEFAULT); +} + +/* device got disconnected */ +static void bdc_uspc_disconnected(struct bdc *bdc, bool reinit) +{ + struct bdc_ep *ep; + + dev_dbg(bdc->dev, "%s\n", __func__); + /* + * Only stop ep0 from here, rest of the endpoints will be disabled + * from gadget_disconnect + */ + ep = bdc->bdc_ep_array[1]; + if (ep && (ep->flags & BDC_EP_ENABLED)) + /* if enabled then stop and remove requests */ + bdc_ep_disable(ep); + + if (bdc->gadget_driver && bdc->gadget_driver->disconnect) { + spin_unlock(&bdc->lock); + bdc->gadget_driver->disconnect(&bdc->gadget); + spin_lock(&bdc->lock); + } + /* Set Unknown speed */ + bdc->gadget.speed = USB_SPEED_UNKNOWN; + bdc->devstatus &= DEVSTATUS_CLEAR; + bdc->delayed_status = false; + bdc->reinit = reinit; + bdc->test_mode = false; + usb_gadget_set_state(&bdc->gadget, USB_STATE_NOTATTACHED); +} + +/* TNotify wkaeup timer */ +static void bdc_func_wake_timer(struct work_struct *work) +{ + struct bdc *bdc = container_of(work, struct bdc, func_wake_notify.work); + unsigned long flags; + + dev_dbg(bdc->dev, "%s\n", __func__); + spin_lock_irqsave(&bdc->lock, flags); + /* + * Check if host has started transferring on endpoints + * FUNC_WAKE_ISSUED is cleared when transfer has started after resume + */ + if (bdc->devstatus & FUNC_WAKE_ISSUED) { + dev_dbg(bdc->dev, "FUNC_WAKE_ISSUED FLAG IS STILL SET\n"); + /* flag is still set, so again send func wake */ + bdc_function_wake_fh(bdc, 0); + schedule_delayed_work(&bdc->func_wake_notify, + msecs_to_jiffies(BDC_TNOTIFY)); + } + spin_unlock_irqrestore(&bdc->lock, flags); +} + +/* handler for Link state change condition */ +static void handle_link_state_change(struct bdc *bdc, u32 uspc) +{ + u32 link_state; + + dev_dbg(bdc->dev, "Link state change"); + link_state = BDC_PST(uspc); + switch (link_state) { + case BDC_LINK_STATE_U3: + if ((bdc->gadget.speed != USB_SPEED_UNKNOWN) && + bdc->gadget_driver->suspend) { + dev_dbg(bdc->dev, "Entered Suspend mode\n"); + spin_unlock(&bdc->lock); + bdc->devstatus |= DEVICE_SUSPENDED; + bdc->gadget_driver->suspend(&bdc->gadget); + spin_lock(&bdc->lock); + } + break; + case BDC_LINK_STATE_U0: + if (bdc->devstatus & REMOTE_WAKEUP_ISSUED) { + bdc->devstatus &= ~REMOTE_WAKEUP_ISSUED; + if (bdc->gadget.speed == USB_SPEED_SUPER) { + bdc_function_wake_fh(bdc, 0); + bdc->devstatus |= FUNC_WAKE_ISSUED; + /* + * Start a Notification timer and check if the + * Host transferred anything on any of the EPs, + * if not then send function wake again every + * TNotification secs until host initiates + * transfer to BDC, USB3 spec Table 8.13 + */ + schedule_delayed_work( + &bdc->func_wake_notify, + msecs_to_jiffies(BDC_TNOTIFY)); + dev_dbg(bdc->dev, "sched func_wake_notify\n"); + } + } + break; + + case BDC_LINK_STATE_RESUME: + dev_dbg(bdc->dev, "Resumed from Suspend\n"); + if (bdc->devstatus & DEVICE_SUSPENDED) { + bdc->gadget_driver->resume(&bdc->gadget); + bdc->devstatus &= ~DEVICE_SUSPENDED; + } + break; + default: + dev_dbg(bdc->dev, "link state:%d\n", link_state); + } +} + +/* something changes on upstream port, handle it here */ +void bdc_sr_uspc(struct bdc *bdc, struct bdc_sr *sreport) +{ + u32 clear_flags = 0; + u32 uspc; + bool connected = false; + bool disconn = false; + + uspc = bdc_readl(bdc->regs, BDC_USPC); + dev_dbg(bdc->dev, "%s uspc=0x%08x\n", __func__, uspc); + + /* Port connect changed */ + if (uspc & BDC_PCC) { + /* Vbus not present, and not connected to Downstream port */ + if ((uspc & BDC_VBC) && !(uspc & BDC_VBS) && !(uspc & BDC_PCS)) + disconn = true; + else if ((uspc & BDC_PCS) && !BDC_PST(uspc)) + connected = true; + clear_flags |= BDC_PCC; + } + + /* Change in VBus and VBus is present */ + if ((uspc & BDC_VBC) && (uspc & BDC_VBS)) { + if (bdc->pullup) { + dev_dbg(bdc->dev, "Do a softconnect\n"); + /* Attached state, do a softconnect */ + bdc_softconn(bdc); + usb_gadget_set_state(&bdc->gadget, USB_STATE_POWERED); + } + clear_flags |= BDC_VBC; + } else if ((uspc & BDC_PRS) || (uspc & BDC_PRC) || disconn) { + /* Hot reset, warm reset, 2.0 bus reset or disconn */ + dev_dbg(bdc->dev, "Port reset or disconn\n"); + bdc_uspc_disconnected(bdc, disconn); + clear_flags |= BDC_PRC; + } else if ((uspc & BDC_PSC) && (uspc & BDC_PCS)) { + /* Change in Link state */ + handle_link_state_change(bdc, uspc); + clear_flags |= BDC_PSC; + } + + /* + * In SS we might not have PRC bit set before connection, but in 2.0 + * the PRC bit is set before connection, so moving this condition out + * of bus reset to handle both SS/2.0 speeds. + */ + if (connected) { + /* This is the connect event for U0/L0 */ + dev_dbg(bdc->dev, "Connected\n"); + bdc_uspc_connected(bdc); + bdc->devstatus &= ~(DEVICE_SUSPENDED); + } + uspc = bdc_readl(bdc->regs, BDC_USPC); + uspc &= (~BDC_USPSC_RW); + dev_dbg(bdc->dev, "uspc=%x\n", uspc); + bdc_writel(bdc->regs, BDC_USPC, clear_flags); +} + +/* Main interrupt handler for bdc */ +static irqreturn_t bdc_udc_interrupt(int irq, void *_bdc) +{ + u32 eqp_index, dqp_index, sr_type, srr_int; + struct bdc_sr *sreport; + struct bdc *bdc = _bdc; + u32 status; + int ret; + + spin_lock(&bdc->lock); + status = bdc_readl(bdc->regs, BDC_BDCSC); + if (!(status & BDC_GIP)) { + spin_unlock(&bdc->lock); + return IRQ_NONE; + } + srr_int = bdc_readl(bdc->regs, BDC_SRRINT(0)); + /* Check if the SRR IP bit it set? */ + if (!(srr_int & BDC_SRR_IP)) { + dev_warn(bdc->dev, "Global irq pending but SRR IP is 0\n"); + spin_unlock(&bdc->lock); + return IRQ_NONE; + } + eqp_index = BDC_SRR_EPI(srr_int); + dqp_index = BDC_SRR_DPI(srr_int); + dev_dbg(bdc->dev, + "%s eqp_index=%d dqp_index=%d srr.dqp_index=%d\n\n", + __func__, eqp_index, dqp_index, bdc->srr.dqp_index); + + /* check for ring empty condition */ + if (eqp_index == dqp_index) { + dev_dbg(bdc->dev, "SRR empty?\n"); + spin_unlock(&bdc->lock); + return IRQ_HANDLED; + } + + while (bdc->srr.dqp_index != eqp_index) { + sreport = &bdc->srr.sr_bds[bdc->srr.dqp_index]; + /* sreport is read before using it */ + rmb(); + sr_type = le32_to_cpu(sreport->offset[3]) & BD_TYPE_BITMASK; + dev_dbg_ratelimited(bdc->dev, "sr_type=%d\n", sr_type); + switch (sr_type) { + case SR_XSF: + bdc->sr_handler[0](bdc, sreport); + break; + + case SR_USPC: + bdc->sr_handler[1](bdc, sreport); + break; + default: + dev_warn(bdc->dev, "SR:%d not handled\n", sr_type); + } + /* Advance the srr dqp index */ + srr_dqp_index_advc(bdc, 0); + } + /* update the hw dequeue pointer */ + srr_int = bdc_readl(bdc->regs, BDC_SRRINT(0)); + srr_int &= ~BDC_SRR_DPI_MASK; + srr_int &= ~(BDC_SRR_RWS|BDC_SRR_RST|BDC_SRR_ISR); + srr_int |= ((bdc->srr.dqp_index) << 16); + srr_int |= BDC_SRR_IP; + bdc_writel(bdc->regs, BDC_SRRINT(0), srr_int); + srr_int = bdc_readl(bdc->regs, BDC_SRRINT(0)); + if (bdc->reinit) { + ret = bdc_reinit(bdc); + if (ret) + dev_err(bdc->dev, "err in bdc reinit\n"); + } + + spin_unlock(&bdc->lock); + + return IRQ_HANDLED; +} + +/* Gadget ops */ +static int bdc_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct bdc *bdc = gadget_to_bdc(gadget); + unsigned long flags; + int ret = 0; + + dev_dbg(bdc->dev, "%s()\n", __func__); + spin_lock_irqsave(&bdc->lock, flags); + if (bdc->gadget_driver) { + dev_err(bdc->dev, "%s is already bound to %s\n", + bdc->gadget.name, + bdc->gadget_driver->driver.name); + ret = -EBUSY; + goto err; + } + /* + * Run the controller from here and when BDC is connected to + * Host then driver will receive a USPC SR with VBUS present + * and then driver will do a softconnect. + */ + ret = bdc_run(bdc); + if (ret) { + dev_err(bdc->dev, "%s bdc run fail\n", __func__); + goto err; + } + bdc->gadget_driver = driver; + bdc->gadget.dev.driver = &driver->driver; +err: + spin_unlock_irqrestore(&bdc->lock, flags); + + return ret; +} + +static int bdc_udc_stop(struct usb_gadget *gadget) +{ + struct bdc *bdc = gadget_to_bdc(gadget); + unsigned long flags; + + dev_dbg(bdc->dev, "%s()\n", __func__); + spin_lock_irqsave(&bdc->lock, flags); + bdc_stop(bdc); + bdc->gadget_driver = NULL; + bdc->gadget.dev.driver = NULL; + spin_unlock_irqrestore(&bdc->lock, flags); + + return 0; +} + +static int bdc_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct bdc *bdc = gadget_to_bdc(gadget); + unsigned long flags; + u32 uspc; + + dev_dbg(bdc->dev, "%s() is_on:%d\n", __func__, is_on); + if (!gadget) + return -EINVAL; + + spin_lock_irqsave(&bdc->lock, flags); + if (!is_on) { + bdc_softdisconn(bdc); + bdc->pullup = false; + } else { + /* + * For a self powered device, we need to wait till we receive + * a VBUS change and Vbus present event, then if pullup flag + * is set, then only we present the Termintation. + */ + bdc->pullup = true; + /* + * Check if BDC is already connected to Host i.e Vbus=1, + * if yes, then present TERM now, this is typical for bus + * powered devices. + */ + uspc = bdc_readl(bdc->regs, BDC_USPC); + if (uspc & BDC_VBS) + bdc_softconn(bdc); + } + spin_unlock_irqrestore(&bdc->lock, flags); + + return 0; +} + +static int bdc_udc_set_selfpowered(struct usb_gadget *gadget, + int is_self) +{ + struct bdc *bdc = gadget_to_bdc(gadget); + unsigned long flags; + + dev_dbg(bdc->dev, "%s()\n", __func__); + gadget->is_selfpowered = (is_self != 0); + spin_lock_irqsave(&bdc->lock, flags); + if (!is_self) + bdc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; + else + bdc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + + spin_unlock_irqrestore(&bdc->lock, flags); + + return 0; +} + +static int bdc_udc_wakeup(struct usb_gadget *gadget) +{ + struct bdc *bdc = gadget_to_bdc(gadget); + unsigned long flags; + u8 link_state; + u32 uspc; + int ret = 0; + + dev_dbg(bdc->dev, + "%s() bdc->devstatus=%08x\n", + __func__, bdc->devstatus); + + if (!(bdc->devstatus & REMOTE_WAKE_ENABLE)) + return -EOPNOTSUPP; + + spin_lock_irqsave(&bdc->lock, flags); + uspc = bdc_readl(bdc->regs, BDC_USPC); + link_state = BDC_PST(uspc); + dev_dbg(bdc->dev, "link_state =%d portsc=%x", link_state, uspc); + if (link_state != BDC_LINK_STATE_U3) { + dev_warn(bdc->dev, + "can't wakeup from link state %d\n", + link_state); + ret = -EINVAL; + goto out; + } + if (bdc->gadget.speed == USB_SPEED_SUPER) + bdc->devstatus |= REMOTE_WAKEUP_ISSUED; + + uspc &= ~BDC_PST_MASK; + uspc &= (~BDC_USPSC_RW); + uspc |= BDC_PST(BDC_LINK_STATE_U0); + uspc |= BDC_SWS; + bdc_writel(bdc->regs, BDC_USPC, uspc); + uspc = bdc_readl(bdc->regs, BDC_USPC); + link_state = BDC_PST(uspc); + dev_dbg(bdc->dev, "link_state =%d portsc=%x", link_state, uspc); +out: + spin_unlock_irqrestore(&bdc->lock, flags); + + return ret; +} + +static const struct usb_gadget_ops bdc_gadget_ops = { + .wakeup = bdc_udc_wakeup, + .set_selfpowered = bdc_udc_set_selfpowered, + .pullup = bdc_udc_pullup, + .udc_start = bdc_udc_start, + .udc_stop = bdc_udc_stop, +}; + +/* Init the gadget interface and register the udc */ +int bdc_udc_init(struct bdc *bdc) +{ + u32 temp; + int ret; + + dev_dbg(bdc->dev, "%s()\n", __func__); + bdc->gadget.ops = &bdc_gadget_ops; + bdc->gadget.max_speed = USB_SPEED_SUPER; + bdc->gadget.speed = USB_SPEED_UNKNOWN; + bdc->gadget.dev.parent = bdc->dev; + + bdc->gadget.sg_supported = false; + + + bdc->gadget.name = BRCM_BDC_NAME; + ret = request_irq(bdc->irq, bdc_udc_interrupt, IRQF_SHARED, + BRCM_BDC_NAME, bdc); + if (ret) { + dev_err(bdc->dev, + "failed to request irq #%d %d\n", + bdc->irq, ret); + return ret; + } + + ret = bdc_init_ep(bdc); + if (ret) { + dev_err(bdc->dev, "bdc init ep fail: %d\n", ret); + goto err0; + } + + ret = usb_add_gadget_udc(bdc->dev, &bdc->gadget); + if (ret) { + dev_err(bdc->dev, "failed to register udc\n"); + goto err0; + } + usb_gadget_set_state(&bdc->gadget, USB_STATE_NOTATTACHED); + bdc->bdc_ep_array[1]->desc = &bdc_gadget_ep0_desc; + /* + * Allocate bd list for ep0 only, ep0 will be enabled on connect + * status report when the speed is known + */ + ret = bdc_ep_enable(bdc->bdc_ep_array[1]); + if (ret) { + dev_err(bdc->dev, "fail to enable %s\n", + bdc->bdc_ep_array[1]->name); + goto err1; + } + INIT_DELAYED_WORK(&bdc->func_wake_notify, bdc_func_wake_timer); + /* Enable Interrupts */ + temp = bdc_readl(bdc->regs, BDC_BDCSC); + temp |= BDC_GIE; + bdc_writel(bdc->regs, BDC_BDCSC, temp); + return 0; +err1: + usb_del_gadget_udc(&bdc->gadget); +err0: + free_irq(bdc->irq, bdc); + bdc_free_ep(bdc); + + return ret; +} + +void bdc_udc_exit(struct bdc *bdc) +{ + unsigned long flags; + + dev_dbg(bdc->dev, "%s()\n", __func__); + spin_lock_irqsave(&bdc->lock, flags); + bdc_ep_disable(bdc->bdc_ep_array[1]); + spin_unlock_irqrestore(&bdc->lock, flags); + + usb_del_gadget_udc(&bdc->gadget); + bdc_free_ep(bdc); +} diff --git a/drivers/usb/gadget/udc/cdns2/Kconfig b/drivers/usb/gadget/udc/cdns2/Kconfig new file mode 100644 index 000000000..c07d35390 --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/Kconfig @@ -0,0 +1,11 @@ +config USB_CDNS2_UDC + tristate "Cadence USBHS Device Controller" + depends on USB_PCI && ACPI && HAS_DMA + help + Cadence USBHS Device controller is a PCI based USB peripheral + controller which supports both full and high speed USB 2.0 + data transfers. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "cdns2-udc-pci.ko" and to + force all gadget drivers to also be dynamically linked. diff --git a/drivers/usb/gadget/udc/cdns2/Makefile b/drivers/usb/gadget/udc/cdns2/Makefile new file mode 100644 index 000000000..a1ffbbe2e --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# define_trace.h needs to know how to find our header +CFLAGS_cdns2-trace.o := -I$(src) + +obj-$(CONFIG_USB_CDNS2_UDC) += cdns2-udc-pci.o +cdns2-udc-pci-$(CONFIG_USB_CDNS2_UDC) += cdns2-pci.o cdns2-gadget.o cdns2-ep0.o +cdns2-udc-pci-$(CONFIG_TRACING) += cdns2-trace.o diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-debug.h b/drivers/usb/gadget/udc/cdns2/cdns2-debug.h new file mode 100644 index 000000000..f5f330004 --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/cdns2-debug.h @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Cadence USBHS-DEV Driver. + * Debug header file. + * + * Copyright (C) 2023 Cadence. + * + * Author: Pawel Laszczak + */ + +#ifndef __LINUX_CDNS2_DEBUG +#define __LINUX_CDNS2_DEBUG + +static inline const char *cdns2_decode_usb_irq(char *str, size_t size, + u8 usb_irq, u8 ext_irq) +{ + int ret; + + ret = scnprintf(str, size, "usbirq: 0x%02x - ", usb_irq); + + if (usb_irq & USBIRQ_SOF) + ret += scnprintf(str + ret, size - ret, "SOF "); + if (usb_irq & USBIRQ_SUTOK) + ret += scnprintf(str + ret, size - ret, "SUTOK "); + if (usb_irq & USBIRQ_SUDAV) + ret += scnprintf(str + ret, size - ret, "SETUP "); + if (usb_irq & USBIRQ_SUSPEND) + ret += scnprintf(str + ret, size - ret, "Suspend "); + if (usb_irq & USBIRQ_URESET) + ret += scnprintf(str + ret, size - ret, "Reset "); + if (usb_irq & USBIRQ_HSPEED) + ret += scnprintf(str + ret, size - ret, "HS "); + if (usb_irq & USBIRQ_LPM) + ret += scnprintf(str + ret, size - ret, "LPM "); + + ret += scnprintf(str + ret, size - ret, ", EXT: 0x%02x - ", ext_irq); + + if (ext_irq & EXTIRQ_WAKEUP) + ret += scnprintf(str + ret, size - ret, "Wakeup "); + if (ext_irq & EXTIRQ_VBUSFAULT_FALL) + ret += scnprintf(str + ret, size - ret, "VBUS_FALL "); + if (ext_irq & EXTIRQ_VBUSFAULT_RISE) + ret += scnprintf(str + ret, size - ret, "VBUS_RISE "); + + if (ret == size - 1) + pr_info("CDNS2: buffer may be truncated.\n"); + + return str; +} + +static inline const char *cdns2_decode_dma_irq(char *str, size_t size, + u32 ep_ists, u32 ep_sts, + const char *ep_name) +{ + int ret; + + ret = scnprintf(str, size, "ISTS: %08x, %s: %08x ", + ep_ists, ep_name, ep_sts); + + if (ep_sts & DMA_EP_STS_IOC) + ret += scnprintf(str + ret, size - ret, "IOC "); + if (ep_sts & DMA_EP_STS_ISP) + ret += scnprintf(str + ret, size - ret, "ISP "); + if (ep_sts & DMA_EP_STS_DESCMIS) + ret += scnprintf(str + ret, size - ret, "DESCMIS "); + if (ep_sts & DMA_EP_STS_TRBERR) + ret += scnprintf(str + ret, size - ret, "TRBERR "); + if (ep_sts & DMA_EP_STS_OUTSMM) + ret += scnprintf(str + ret, size - ret, "OUTSMM "); + if (ep_sts & DMA_EP_STS_ISOERR) + ret += scnprintf(str + ret, size - ret, "ISOERR "); + if (ep_sts & DMA_EP_STS_DBUSY) + ret += scnprintf(str + ret, size - ret, "DBUSY "); + if (DMA_EP_STS_CCS(ep_sts)) + ret += scnprintf(str + ret, size - ret, "CCS "); + + if (ret == size - 1) + pr_info("CDNS2: buffer may be truncated.\n"); + + return str; +} + +static inline const char *cdns2_decode_epx_irq(char *str, size_t size, + char *ep_name, u32 ep_ists, + u32 ep_sts) +{ + return cdns2_decode_dma_irq(str, size, ep_ists, ep_sts, ep_name); +} + +static inline const char *cdns2_decode_ep0_irq(char *str, size_t size, + u32 ep_ists, u32 ep_sts, + int dir) +{ + return cdns2_decode_dma_irq(str, size, ep_ists, ep_sts, + dir ? "ep0IN" : "ep0OUT"); +} + +static inline const char *cdns2_raw_ring(struct cdns2_endpoint *pep, + struct cdns2_trb *trbs, + char *str, size_t size) +{ + struct cdns2_ring *ring = &pep->ring; + struct cdns2_trb *trb; + dma_addr_t dma; + int ret; + int i; + + ret = scnprintf(str, size, "\n\t\tTR for %s:", pep->name); + + trb = &trbs[ring->dequeue]; + dma = cdns2_trb_virt_to_dma(pep, trb); + ret += scnprintf(str + ret, size - ret, + "\n\t\tRing deq index: %d, trb: V=%p, P=0x%pad\n", + ring->dequeue, trb, &dma); + + trb = &trbs[ring->enqueue]; + dma = cdns2_trb_virt_to_dma(pep, trb); + ret += scnprintf(str + ret, size - ret, + "\t\tRing enq index: %d, trb: V=%p, P=0x%pad\n", + ring->enqueue, trb, &dma); + + ret += scnprintf(str + ret, size - ret, + "\t\tfree trbs: %d, CCS=%d, PCS=%d\n", + ring->free_trbs, ring->ccs, ring->pcs); + + if (TRBS_PER_SEGMENT > 40) { + ret += scnprintf(str + ret, size - ret, + "\t\tTransfer ring %d too big\n", TRBS_PER_SEGMENT); + return str; + } + + dma = ring->dma; + for (i = 0; i < TRBS_PER_SEGMENT; ++i) { + trb = &trbs[i]; + ret += scnprintf(str + ret, size - ret, + "\t\t@%pad %08x %08x %08x\n", &dma, + le32_to_cpu(trb->buffer), + le32_to_cpu(trb->length), + le32_to_cpu(trb->control)); + dma += sizeof(*trb); + } + + if (ret == size - 1) + pr_info("CDNS2: buffer may be truncated.\n"); + + return str; +} + +static inline const char *cdns2_trb_type_string(u8 type) +{ + switch (type) { + case TRB_NORMAL: + return "Normal"; + case TRB_LINK: + return "Link"; + default: + return "UNKNOWN"; + } +} + +static inline const char *cdns2_decode_trb(char *str, size_t size, u32 flags, + u32 length, u32 buffer) +{ + int type = TRB_FIELD_TO_TYPE(flags); + int ret; + + switch (type) { + case TRB_LINK: + ret = scnprintf(str, size, + "LINK %08x type '%s' flags %c:%c:%c%c:%c", + buffer, cdns2_trb_type_string(type), + flags & TRB_CYCLE ? 'C' : 'c', + flags & TRB_TOGGLE ? 'T' : 't', + flags & TRB_CHAIN ? 'C' : 'c', + flags & TRB_CHAIN ? 'H' : 'h', + flags & TRB_IOC ? 'I' : 'i'); + break; + case TRB_NORMAL: + ret = scnprintf(str, size, + "type: '%s', Buffer: %08x, length: %ld, burst len: %ld, " + "flags %c:%c:%c%c:%c", + cdns2_trb_type_string(type), + buffer, TRB_LEN(length), + TRB_FIELD_TO_BURST(length), + flags & TRB_CYCLE ? 'C' : 'c', + flags & TRB_ISP ? 'I' : 'i', + flags & TRB_CHAIN ? 'C' : 'c', + flags & TRB_CHAIN ? 'H' : 'h', + flags & TRB_IOC ? 'I' : 'i'); + break; + default: + ret = scnprintf(str, size, "type '%s' -> raw %08x %08x %08x", + cdns2_trb_type_string(type), + buffer, length, flags); + } + + if (ret == size - 1) + pr_info("CDNS2: buffer may be truncated.\n"); + + return str; +} + +#endif /*__LINUX_CDNS2_DEBUG*/ diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-ep0.c b/drivers/usb/gadget/udc/cdns2/cdns2-ep0.c new file mode 100644 index 000000000..a5a9d395f --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/cdns2-ep0.c @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBHS-DEV driver. + * + * Copyright (C) 2023 Cadence Design Systems. + * + * Authors: Pawel Laszczak + */ + +#include +#include + +#include "cdns2-gadget.h" +#include "cdns2-trace.h" + +static struct usb_endpoint_descriptor cdns2_gadget_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(64) +}; + +static int cdns2_w_index_to_ep_index(u16 wIndex) +{ + if (!(wIndex & USB_ENDPOINT_NUMBER_MASK)) + return 0; + + return ((wIndex & USB_ENDPOINT_NUMBER_MASK) * 2) + + (wIndex & USB_ENDPOINT_DIR_MASK ? 1 : 0) - 1; +} + +static bool cdns2_check_new_setup(struct cdns2_device *pdev) +{ + u8 reg; + + reg = readb(&pdev->ep0_regs->cs); + + return !!(reg & EP0CS_CHGSET); +} + +static void cdns2_ep0_enqueue(struct cdns2_device *pdev, dma_addr_t dma_addr, + unsigned int length, int zlp) +{ + struct cdns2_adma_regs __iomem *regs = pdev->adma_regs; + struct cdns2_endpoint *pep = &pdev->eps[0]; + struct cdns2_ring *ring = &pep->ring; + + ring->trbs[0].buffer = cpu_to_le32(TRB_BUFFER(dma_addr)); + ring->trbs[0].length = cpu_to_le32(TRB_LEN(length)); + + if (zlp) { + ring->trbs[0].control = cpu_to_le32(TRB_CYCLE | + TRB_TYPE(TRB_NORMAL)); + ring->trbs[1].buffer = cpu_to_le32(TRB_BUFFER(dma_addr)); + ring->trbs[1].length = cpu_to_le32(TRB_LEN(0)); + ring->trbs[1].control = cpu_to_le32(TRB_CYCLE | TRB_IOC | + TRB_TYPE(TRB_NORMAL)); + } else { + ring->trbs[0].control = cpu_to_le32(TRB_CYCLE | TRB_IOC | + TRB_TYPE(TRB_NORMAL)); + ring->trbs[1].control = 0; + } + + trace_cdns2_queue_trb(pep, ring->trbs); + + if (!pep->dir) + writel(0, &pdev->ep0_regs->rxbc); + + cdns2_select_ep(pdev, pep->dir); + + writel(DMA_EP_STS_TRBERR, ®s->ep_sts); + writel(pep->ring.dma, ®s->ep_traddr); + + trace_cdns2_doorbell_ep0(pep, readl(®s->ep_traddr)); + + writel(DMA_EP_CMD_DRDY, ®s->ep_cmd); +} + +static int cdns2_ep0_delegate_req(struct cdns2_device *pdev) +{ + int ret; + + spin_unlock(&pdev->lock); + ret = pdev->gadget_driver->setup(&pdev->gadget, &pdev->setup); + spin_lock(&pdev->lock); + + return ret; +} + +static void cdns2_ep0_stall(struct cdns2_device *pdev) +{ + struct cdns2_endpoint *pep = &pdev->eps[0]; + struct cdns2_request *preq; + + preq = cdns2_next_preq(&pep->pending_list); + set_reg_bit_8(&pdev->ep0_regs->cs, EP0CS_DSTALL); + + if (pdev->ep0_stage == CDNS2_DATA_STAGE && preq) + cdns2_gadget_giveback(pep, preq, -ECONNRESET); + else if (preq) + list_del_init(&preq->list); + + pdev->ep0_stage = CDNS2_SETUP_STAGE; + pep->ep_state |= EP_STALLED; +} + +static void cdns2_status_stage(struct cdns2_device *pdev) +{ + struct cdns2_endpoint *pep = &pdev->eps[0]; + struct cdns2_request *preq; + + preq = cdns2_next_preq(&pep->pending_list); + if (preq) + list_del_init(&preq->list); + + pdev->ep0_stage = CDNS2_SETUP_STAGE; + writeb(EP0CS_HSNAK, &pdev->ep0_regs->cs); +} + +static int cdns2_req_ep0_set_configuration(struct cdns2_device *pdev, + struct usb_ctrlrequest *ctrl_req) +{ + enum usb_device_state state = pdev->gadget.state; + u32 config = le16_to_cpu(ctrl_req->wValue); + int ret; + + if (state < USB_STATE_ADDRESS) { + dev_err(pdev->dev, "Set Configuration - bad device state\n"); + return -EINVAL; + } + + ret = cdns2_ep0_delegate_req(pdev); + if (ret) + return ret; + + trace_cdns2_device_state(config ? "configured" : "addressed"); + + if (!config) + usb_gadget_set_state(&pdev->gadget, USB_STATE_ADDRESS); + + return 0; +} + +static int cdns2_req_ep0_set_address(struct cdns2_device *pdev, u32 addr) +{ + enum usb_device_state device_state = pdev->gadget.state; + u8 reg; + + if (addr > USB_DEVICE_MAX_ADDRESS) { + dev_err(pdev->dev, + "Device address (%d) cannot be greater than %d\n", + addr, USB_DEVICE_MAX_ADDRESS); + return -EINVAL; + } + + if (device_state == USB_STATE_CONFIGURED) { + dev_err(pdev->dev, + "can't set_address from configured state\n"); + return -EINVAL; + } + + reg = readb(&pdev->usb_regs->fnaddr); + pdev->dev_address = reg; + + usb_gadget_set_state(&pdev->gadget, + (addr ? USB_STATE_ADDRESS : USB_STATE_DEFAULT)); + + trace_cdns2_device_state(addr ? "addressed" : "default"); + + return 0; +} + +static int cdns2_req_ep0_handle_status(struct cdns2_device *pdev, + struct usb_ctrlrequest *ctrl) +{ + struct cdns2_endpoint *pep; + __le16 *response_pkt; + u16 status = 0; + int ep_sts; + u32 recip; + + recip = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recip) { + case USB_RECIP_DEVICE: + status = pdev->gadget.is_selfpowered; + status |= pdev->may_wakeup << USB_DEVICE_REMOTE_WAKEUP; + break; + case USB_RECIP_INTERFACE: + return cdns2_ep0_delegate_req(pdev); + case USB_RECIP_ENDPOINT: + ep_sts = cdns2_w_index_to_ep_index(le16_to_cpu(ctrl->wIndex)); + pep = &pdev->eps[ep_sts]; + + if (pep->ep_state & EP_STALLED) + status = BIT(USB_ENDPOINT_HALT); + break; + default: + return -EINVAL; + } + + put_unaligned_le16(status, (__le16 *)pdev->ep0_preq.request.buf); + + cdns2_ep0_enqueue(pdev, pdev->ep0_preq.request.dma, + sizeof(*response_pkt), 0); + + return 0; +} + +static int cdns2_ep0_handle_feature_device(struct cdns2_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + enum usb_device_state state; + enum usb_device_speed speed; + int ret = 0; + u32 wValue; + u16 tmode; + + wValue = le16_to_cpu(ctrl->wValue); + state = pdev->gadget.state; + speed = pdev->gadget.speed; + + switch (wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + pdev->may_wakeup = !!set; + break; + case USB_DEVICE_TEST_MODE: + if (state != USB_STATE_CONFIGURED || speed > USB_SPEED_HIGH) + return -EINVAL; + + tmode = le16_to_cpu(ctrl->wIndex); + + if (!set || (tmode & 0xff) != 0) + return -EINVAL; + + tmode >>= 8; + switch (tmode) { + case USB_TEST_J: + case USB_TEST_K: + case USB_TEST_SE0_NAK: + case USB_TEST_PACKET: + /* + * The USBHS controller automatically handles the + * Set_Feature(testmode) request. Standard test modes + * that use values of test mode selector from + * 01h to 04h (Test_J, Test_K, Test_SE0_NAK, + * Test_Packet) are supported by the + * controller(HS - ack, FS - stall). + */ + break; + default: + ret = -EINVAL; + } + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int cdns2_ep0_handle_feature_intf(struct cdns2_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + int ret = 0; + u32 wValue; + + wValue = le16_to_cpu(ctrl->wValue); + + switch (wValue) { + case USB_INTRF_FUNC_SUSPEND: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int cdns2_ep0_handle_feature_endpoint(struct cdns2_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + struct cdns2_endpoint *pep; + u8 wValue; + + wValue = le16_to_cpu(ctrl->wValue); + pep = &pdev->eps[cdns2_w_index_to_ep_index(le16_to_cpu(ctrl->wIndex))]; + + if (wValue != USB_ENDPOINT_HALT) + return -EINVAL; + + if (!(le16_to_cpu(ctrl->wIndex) & ~USB_DIR_IN)) + return 0; + + switch (wValue) { + case USB_ENDPOINT_HALT: + if (set || !(pep->ep_state & EP_WEDGE)) + return cdns2_halt_endpoint(pdev, pep, set); + break; + default: + dev_warn(pdev->dev, "WARN Incorrect wValue %04x\n", wValue); + return -EINVAL; + } + + return 0; +} + +static int cdns2_req_ep0_handle_feature(struct cdns2_device *pdev, + struct usb_ctrlrequest *ctrl, + int set) +{ + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + return cdns2_ep0_handle_feature_device(pdev, ctrl, set); + case USB_RECIP_INTERFACE: + return cdns2_ep0_handle_feature_intf(pdev, ctrl, set); + case USB_RECIP_ENDPOINT: + return cdns2_ep0_handle_feature_endpoint(pdev, ctrl, set); + default: + return -EINVAL; + } +} + +static int cdns2_ep0_std_request(struct cdns2_device *pdev) +{ + struct usb_ctrlrequest *ctrl = &pdev->setup; + int ret; + + switch (ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + ret = cdns2_req_ep0_set_address(pdev, + le16_to_cpu(ctrl->wValue)); + break; + case USB_REQ_SET_CONFIGURATION: + ret = cdns2_req_ep0_set_configuration(pdev, ctrl); + break; + case USB_REQ_GET_STATUS: + ret = cdns2_req_ep0_handle_status(pdev, ctrl); + break; + case USB_REQ_CLEAR_FEATURE: + ret = cdns2_req_ep0_handle_feature(pdev, ctrl, 0); + break; + case USB_REQ_SET_FEATURE: + ret = cdns2_req_ep0_handle_feature(pdev, ctrl, 1); + break; + default: + ret = cdns2_ep0_delegate_req(pdev); + break; + } + + return ret; +} + +static void __pending_setup_status_handler(struct cdns2_device *pdev) +{ + struct usb_request *request = pdev->pending_status_request; + + if (pdev->status_completion_no_call && request && request->complete) { + request->complete(&pdev->eps[0].endpoint, request); + pdev->status_completion_no_call = 0; + } +} + +void cdns2_pending_setup_status_handler(struct work_struct *work) +{ + struct cdns2_device *pdev = container_of(work, struct cdns2_device, + pending_status_wq); + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + __pending_setup_status_handler(pdev); + spin_unlock_irqrestore(&pdev->lock, flags); +} + +void cdns2_handle_setup_packet(struct cdns2_device *pdev) +{ + struct usb_ctrlrequest *ctrl = &pdev->setup; + struct cdns2_endpoint *pep = &pdev->eps[0]; + struct cdns2_request *preq; + int ret = 0; + u16 len; + u8 reg; + int i; + + writeb(EP0CS_CHGSET, &pdev->ep0_regs->cs); + + for (i = 0; i < 8; i++) + ((u8 *)&pdev->setup)[i] = readb(&pdev->ep0_regs->setupdat[i]); + + /* + * If SETUP packet was modified while reading just simple ignore it. + * The new one will be handled latter. + */ + if (cdns2_check_new_setup(pdev)) { + trace_cdns2_ep0_setup("overridden"); + return; + } + + trace_cdns2_ctrl_req(ctrl); + + if (!pdev->gadget_driver) + goto out; + + if (pdev->gadget.state == USB_STATE_NOTATTACHED) { + dev_err(pdev->dev, "ERR: Setup detected in unattached state\n"); + ret = -EINVAL; + goto out; + } + + pep = &pdev->eps[0]; + + /* Halt for Ep0 is cleared automatically when SETUP packet arrives. */ + pep->ep_state &= ~EP_STALLED; + + if (!list_empty(&pep->pending_list)) { + preq = cdns2_next_preq(&pep->pending_list); + cdns2_gadget_giveback(pep, preq, -ECONNRESET); + } + + len = le16_to_cpu(ctrl->wLength); + if (len) + pdev->ep0_stage = CDNS2_DATA_STAGE; + else + pdev->ep0_stage = CDNS2_STATUS_STAGE; + + pep->dir = ctrl->bRequestType & USB_DIR_IN; + + /* + * SET_ADDRESS request is acknowledged automatically by controller and + * in the worse case driver may not notice this request. To check + * whether this request has been processed driver can use + * fnaddr register. + */ + reg = readb(&pdev->usb_regs->fnaddr); + if (pdev->setup.bRequest != USB_REQ_SET_ADDRESS && + pdev->dev_address != reg) + cdns2_req_ep0_set_address(pdev, reg); + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + ret = cdns2_ep0_std_request(pdev); + else + ret = cdns2_ep0_delegate_req(pdev); + + if (ret == USB_GADGET_DELAYED_STATUS) { + trace_cdns2_ep0_status_stage("delayed"); + return; + } + +out: + if (ret < 0) + cdns2_ep0_stall(pdev); + else if (pdev->ep0_stage == CDNS2_STATUS_STAGE) + cdns2_status_stage(pdev); +} + +static void cdns2_transfer_completed(struct cdns2_device *pdev) +{ + struct cdns2_endpoint *pep = &pdev->eps[0]; + + if (!list_empty(&pep->pending_list)) { + struct cdns2_request *preq; + + trace_cdns2_complete_trb(pep, pep->ring.trbs); + preq = cdns2_next_preq(&pep->pending_list); + + preq->request.actual = + TRB_LEN(le32_to_cpu(pep->ring.trbs->length)); + cdns2_gadget_giveback(pep, preq, 0); + } + + cdns2_status_stage(pdev); +} + +void cdns2_handle_ep0_interrupt(struct cdns2_device *pdev, int dir) +{ + u32 ep_sts_reg; + + cdns2_select_ep(pdev, dir); + + trace_cdns2_ep0_irq(pdev); + + ep_sts_reg = readl(&pdev->adma_regs->ep_sts); + writel(ep_sts_reg, &pdev->adma_regs->ep_sts); + + __pending_setup_status_handler(pdev); + + if ((ep_sts_reg & DMA_EP_STS_IOC) || (ep_sts_reg & DMA_EP_STS_ISP)) { + pdev->eps[0].dir = dir; + cdns2_transfer_completed(pdev); + } +} + +/* + * Function shouldn't be called by gadget driver, + * endpoint 0 is allways active. + */ +static int cdns2_gadget_ep0_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + return -EINVAL; +} + +/* + * Function shouldn't be called by gadget driver, + * endpoint 0 is allways active. + */ +static int cdns2_gadget_ep0_disable(struct usb_ep *ep) +{ + return -EINVAL; +} + +static int cdns2_gadget_ep0_set_halt(struct usb_ep *ep, int value) +{ + struct cdns2_endpoint *pep = ep_to_cdns2_ep(ep); + struct cdns2_device *pdev = pep->pdev; + unsigned long flags; + + if (!value) + return 0; + + spin_lock_irqsave(&pdev->lock, flags); + cdns2_ep0_stall(pdev); + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdns2_gadget_ep0_set_wedge(struct usb_ep *ep) +{ + return cdns2_gadget_ep0_set_halt(ep, 1); +} + +static int cdns2_gadget_ep0_queue(struct usb_ep *ep, + struct usb_request *request, + gfp_t gfp_flags) +{ + struct cdns2_endpoint *pep = ep_to_cdns2_ep(ep); + struct cdns2_device *pdev = pep->pdev; + struct cdns2_request *preq; + unsigned long flags; + u8 zlp = 0; + int ret; + + spin_lock_irqsave(&pdev->lock, flags); + + preq = to_cdns2_request(request); + + trace_cdns2_request_enqueue(preq); + + /* Cancel the request if controller receive new SETUP packet. */ + if (cdns2_check_new_setup(pdev)) { + trace_cdns2_ep0_setup("overridden"); + spin_unlock_irqrestore(&pdev->lock, flags); + return -ECONNRESET; + } + + /* Send STATUS stage. Should be called only for SET_CONFIGURATION. */ + if (pdev->ep0_stage == CDNS2_STATUS_STAGE) { + cdns2_status_stage(pdev); + + request->actual = 0; + pdev->status_completion_no_call = true; + pdev->pending_status_request = request; + usb_gadget_set_state(&pdev->gadget, USB_STATE_CONFIGURED); + spin_unlock_irqrestore(&pdev->lock, flags); + + /* + * Since there is no completion interrupt for status stage, + * it needs to call ->completion in software after + * cdns2_gadget_ep0_queue is back. + */ + queue_work(system_freezable_wq, &pdev->pending_status_wq); + return 0; + } + + if (!list_empty(&pep->pending_list)) { + trace_cdns2_ep0_setup("pending"); + dev_err(pdev->dev, + "can't handle multiple requests for ep0\n"); + spin_unlock_irqrestore(&pdev->lock, flags); + return -EBUSY; + } + + ret = usb_gadget_map_request_by_dev(pdev->dev, request, pep->dir); + if (ret) { + spin_unlock_irqrestore(&pdev->lock, flags); + dev_err(pdev->dev, "failed to map request\n"); + return -EINVAL; + } + + request->status = -EINPROGRESS; + list_add_tail(&preq->list, &pep->pending_list); + + if (request->zero && request->length && + (request->length % ep->maxpacket == 0)) + zlp = 1; + + cdns2_ep0_enqueue(pdev, request->dma, request->length, zlp); + + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static const struct usb_ep_ops cdns2_gadget_ep0_ops = { + .enable = cdns2_gadget_ep0_enable, + .disable = cdns2_gadget_ep0_disable, + .alloc_request = cdns2_gadget_ep_alloc_request, + .free_request = cdns2_gadget_ep_free_request, + .queue = cdns2_gadget_ep0_queue, + .dequeue = cdns2_gadget_ep_dequeue, + .set_halt = cdns2_gadget_ep0_set_halt, + .set_wedge = cdns2_gadget_ep0_set_wedge, +}; + +void cdns2_ep0_config(struct cdns2_device *pdev) +{ + struct cdns2_endpoint *pep; + + pep = &pdev->eps[0]; + + if (!list_empty(&pep->pending_list)) { + struct cdns2_request *preq; + + preq = cdns2_next_preq(&pep->pending_list); + list_del_init(&preq->list); + } + + writeb(EP0_FIFO_AUTO, &pdev->ep0_regs->fifo); + cdns2_select_ep(pdev, USB_DIR_OUT); + writel(DMA_EP_CFG_ENABLE, &pdev->adma_regs->ep_cfg); + + writeb(EP0_FIFO_IO_TX | EP0_FIFO_AUTO, &pdev->ep0_regs->fifo); + cdns2_select_ep(pdev, USB_DIR_IN); + writel(DMA_EP_CFG_ENABLE, &pdev->adma_regs->ep_cfg); + + writeb(pdev->gadget.ep0->maxpacket, &pdev->ep0_regs->maxpack); + writel(DMA_EP_IEN_EP_OUT0 | DMA_EP_IEN_EP_IN0, + &pdev->adma_regs->ep_ien); +} + +void cdns2_init_ep0(struct cdns2_device *pdev, + struct cdns2_endpoint *pep) +{ + u16 maxpacket = le16_to_cpu(cdns2_gadget_ep0_desc.wMaxPacketSize); + + usb_ep_set_maxpacket_limit(&pep->endpoint, maxpacket); + + pep->endpoint.ops = &cdns2_gadget_ep0_ops; + pep->endpoint.desc = &cdns2_gadget_ep0_desc; + pep->endpoint.caps.type_control = true; + pep->endpoint.caps.dir_in = true; + pep->endpoint.caps.dir_out = true; + + pdev->gadget.ep0 = &pep->endpoint; +} diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-gadget.c b/drivers/usb/gadget/udc/cdns2/cdns2-gadget.c new file mode 100644 index 000000000..308d3c468 --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/cdns2-gadget.c @@ -0,0 +1,2468 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBHS-DEV Driver - gadget side. + * + * Copyright (C) 2023 Cadence Design Systems. + * + * Authors: Pawel Laszczak + */ + +/* + * Work around 1: + * At some situations, the controller may get stale data address in TRB + * at below sequences: + * 1. Controller read TRB includes data address + * 2. Software updates TRBs includes data address and Cycle bit + * 3. Controller read TRB which includes Cycle bit + * 4. DMA run with stale data address + * + * To fix this problem, driver needs to make the first TRB in TD as invalid. + * After preparing all TRBs driver needs to check the position of DMA and + * if the DMA point to the first just added TRB and doorbell is 1, + * then driver must defer making this TRB as valid. This TRB will be make + * as valid during adding next TRB only if DMA is stopped or at TRBERR + * interrupt. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "cdns2-gadget.h" +#include "cdns2-trace.h" + +/** + * set_reg_bit_32 - set bit in given 32 bits register. + * @ptr: register address. + * @mask: bits to set. + */ +static void set_reg_bit_32(void __iomem *ptr, u32 mask) +{ + mask = readl(ptr) | mask; + writel(mask, ptr); +} + +/* + * clear_reg_bit_32 - clear bit in given 32 bits register. + * @ptr: register address. + * @mask: bits to clear. + */ +static void clear_reg_bit_32(void __iomem *ptr, u32 mask) +{ + mask = readl(ptr) & ~mask; + writel(mask, ptr); +} + +/* Clear bit in given 8 bits register. */ +static void clear_reg_bit_8(void __iomem *ptr, u8 mask) +{ + mask = readb(ptr) & ~mask; + writeb(mask, ptr); +} + +/* Set bit in given 16 bits register. */ +void set_reg_bit_8(void __iomem *ptr, u8 mask) +{ + mask = readb(ptr) | mask; + writeb(mask, ptr); +} + +static int cdns2_get_dma_pos(struct cdns2_device *pdev, + struct cdns2_endpoint *pep) +{ + int dma_index; + + dma_index = readl(&pdev->adma_regs->ep_traddr) - pep->ring.dma; + + return dma_index / TRB_SIZE; +} + +/* Get next private request from list. */ +struct cdns2_request *cdns2_next_preq(struct list_head *list) +{ + return list_first_entry_or_null(list, struct cdns2_request, list); +} + +void cdns2_select_ep(struct cdns2_device *pdev, u32 ep) +{ + if (pdev->selected_ep == ep) + return; + + pdev->selected_ep = ep; + writel(ep, &pdev->adma_regs->ep_sel); +} + +dma_addr_t cdns2_trb_virt_to_dma(struct cdns2_endpoint *pep, + struct cdns2_trb *trb) +{ + u32 offset = (char *)trb - (char *)pep->ring.trbs; + + return pep->ring.dma + offset; +} + +static void cdns2_free_tr_segment(struct cdns2_endpoint *pep) +{ + struct cdns2_device *pdev = pep->pdev; + struct cdns2_ring *ring = &pep->ring; + + if (pep->ring.trbs) { + dma_pool_free(pdev->eps_dma_pool, ring->trbs, ring->dma); + memset(ring, 0, sizeof(*ring)); + } +} + +/* Allocates Transfer Ring segment. */ +static int cdns2_alloc_tr_segment(struct cdns2_endpoint *pep) +{ + struct cdns2_device *pdev = pep->pdev; + struct cdns2_trb *link_trb; + struct cdns2_ring *ring; + + ring = &pep->ring; + + if (!ring->trbs) { + ring->trbs = dma_pool_alloc(pdev->eps_dma_pool, + GFP_DMA32 | GFP_ATOMIC, + &ring->dma); + if (!ring->trbs) + return -ENOMEM; + } + + memset(ring->trbs, 0, TR_SEG_SIZE); + + if (!pep->num) + return 0; + + /* Initialize the last TRB as Link TRB */ + link_trb = (ring->trbs + (TRBS_PER_SEGMENT - 1)); + link_trb->buffer = cpu_to_le32(TRB_BUFFER(ring->dma)); + link_trb->control = cpu_to_le32(TRB_CYCLE | TRB_TYPE(TRB_LINK) | + TRB_TOGGLE); + + return 0; +} + +/* + * Stalls and flushes selected endpoint. + * Endpoint must be selected before invoking this function. + */ +static void cdns2_ep_stall_flush(struct cdns2_endpoint *pep) +{ + struct cdns2_device *pdev = pep->pdev; + int val; + + trace_cdns2_ep_halt(pep, 1, 1); + + writel(DMA_EP_CMD_DFLUSH, &pdev->adma_regs->ep_cmd); + + /* Wait for DFLUSH cleared. */ + readl_poll_timeout_atomic(&pdev->adma_regs->ep_cmd, val, + !(val & DMA_EP_CMD_DFLUSH), 1, 1000); + pep->ep_state |= EP_STALLED; + pep->ep_state &= ~EP_STALL_PENDING; +} + +/* + * Increment a trb index. + * + * The index should never point to the last link TRB in TR. After incrementing, + * if it point to the link TRB, wrap around to the beginning and revert + * cycle state bit. The link TRB is always at the last TRB entry. + */ +static void cdns2_ep_inc_trb(int *index, u8 *cs, int trb_in_seg) +{ + (*index)++; + if (*index == (trb_in_seg - 1)) { + *index = 0; + *cs ^= 1; + } +} + +static void cdns2_ep_inc_enq(struct cdns2_ring *ring) +{ + ring->free_trbs--; + cdns2_ep_inc_trb(&ring->enqueue, &ring->pcs, TRBS_PER_SEGMENT); +} + +static void cdns2_ep_inc_deq(struct cdns2_ring *ring) +{ + ring->free_trbs++; + cdns2_ep_inc_trb(&ring->dequeue, &ring->ccs, TRBS_PER_SEGMENT); +} + +/* + * Enable/disable LPM. + * + * If bit USBCS_LPMNYET is not set and device receive Extended Token packet, + * then controller answer with ACK handshake. + * If bit USBCS_LPMNYET is set and device receive Extended Token packet, + * then controller answer with NYET handshake. + */ +static void cdns2_enable_l1(struct cdns2_device *pdev, int enable) +{ + if (enable) { + clear_reg_bit_8(&pdev->usb_regs->usbcs, USBCS_LPMNYET); + writeb(LPMCLOCK_SLEEP_ENTRY, &pdev->usb_regs->lpmclock); + } else { + set_reg_bit_8(&pdev->usb_regs->usbcs, USBCS_LPMNYET); + } +} + +static enum usb_device_speed cdns2_get_speed(struct cdns2_device *pdev) +{ + u8 speed = readb(&pdev->usb_regs->speedctrl); + + if (speed & SPEEDCTRL_HS) + return USB_SPEED_HIGH; + else if (speed & SPEEDCTRL_FS) + return USB_SPEED_FULL; + + return USB_SPEED_UNKNOWN; +} + +static struct cdns2_trb *cdns2_next_trb(struct cdns2_endpoint *pep, + struct cdns2_trb *trb) +{ + if (trb == (pep->ring.trbs + (TRBS_PER_SEGMENT - 1))) + return pep->ring.trbs; + else + return ++trb; +} + +void cdns2_gadget_giveback(struct cdns2_endpoint *pep, + struct cdns2_request *preq, + int status) +{ + struct usb_request *request = &preq->request; + struct cdns2_device *pdev = pep->pdev; + + list_del_init(&preq->list); + + if (request->status == -EINPROGRESS) + request->status = status; + + usb_gadget_unmap_request_by_dev(pdev->dev, request, pep->dir); + + /* All TRBs have finished, clear the counter. */ + preq->finished_trb = 0; + + trace_cdns2_request_giveback(preq); + + if (request->complete) { + spin_unlock(&pdev->lock); + usb_gadget_giveback_request(&pep->endpoint, request); + spin_lock(&pdev->lock); + } + + if (request->buf == pdev->zlp_buf) + cdns2_gadget_ep_free_request(&pep->endpoint, request); +} + +static void cdns2_wa1_restore_cycle_bit(struct cdns2_endpoint *pep) +{ + /* Work around for stale data address in TRB. */ + if (pep->wa1_set) { + trace_cdns2_wa1(pep, "restore cycle bit"); + + pep->wa1_set = 0; + pep->wa1_trb_index = 0xFFFF; + if (pep->wa1_cycle_bit) + pep->wa1_trb->control |= cpu_to_le32(0x1); + else + pep->wa1_trb->control &= cpu_to_le32(~0x1); + } +} + +static int cdns2_wa1_update_guard(struct cdns2_endpoint *pep, + struct cdns2_trb *trb) +{ + struct cdns2_device *pdev = pep->pdev; + + if (!pep->wa1_set) { + u32 doorbell; + + doorbell = !!(readl(&pdev->adma_regs->ep_cmd) & DMA_EP_CMD_DRDY); + + if (doorbell) { + pep->wa1_cycle_bit = pep->ring.pcs ? TRB_CYCLE : 0; + pep->wa1_set = 1; + pep->wa1_trb = trb; + pep->wa1_trb_index = pep->ring.enqueue; + trace_cdns2_wa1(pep, "set guard"); + return 0; + } + } + return 1; +} + +static void cdns2_wa1_tray_restore_cycle_bit(struct cdns2_device *pdev, + struct cdns2_endpoint *pep) +{ + int dma_index; + u32 doorbell; + + doorbell = !!(readl(&pdev->adma_regs->ep_cmd) & DMA_EP_CMD_DRDY); + dma_index = cdns2_get_dma_pos(pdev, pep); + + if (!doorbell || dma_index != pep->wa1_trb_index) + cdns2_wa1_restore_cycle_bit(pep); +} + +static int cdns2_prepare_ring(struct cdns2_device *pdev, + struct cdns2_endpoint *pep, + int num_trbs) +{ + struct cdns2_trb *link_trb = NULL; + int doorbell, dma_index; + struct cdns2_ring *ring; + u32 ch_bit = 0; + + ring = &pep->ring; + + if (num_trbs > ring->free_trbs) { + pep->ep_state |= EP_RING_FULL; + trace_cdns2_no_room_on_ring("Ring full\n"); + return -ENOBUFS; + } + + if ((ring->enqueue + num_trbs) >= (TRBS_PER_SEGMENT - 1)) { + doorbell = !!(readl(&pdev->adma_regs->ep_cmd) & DMA_EP_CMD_DRDY); + dma_index = cdns2_get_dma_pos(pdev, pep); + + /* Driver can't update LINK TRB if it is current processed. */ + if (doorbell && dma_index == TRBS_PER_SEGMENT - 1) { + pep->ep_state |= EP_DEFERRED_DRDY; + return -ENOBUFS; + } + + /* Update C bt in Link TRB before starting DMA. */ + link_trb = ring->trbs + (TRBS_PER_SEGMENT - 1); + + /* + * For TRs size equal 2 enabling TRB_CHAIN for epXin causes + * that DMA stuck at the LINK TRB. + * On the other hand, removing TRB_CHAIN for longer TRs for + * epXout cause that DMA stuck after handling LINK TRB. + * To eliminate this strange behavioral driver set TRB_CHAIN + * bit only for TR size > 2. + */ + if (pep->type == USB_ENDPOINT_XFER_ISOC || TRBS_PER_SEGMENT > 2) + ch_bit = TRB_CHAIN; + + link_trb->control = cpu_to_le32(((ring->pcs) ? TRB_CYCLE : 0) | + TRB_TYPE(TRB_LINK) | TRB_TOGGLE | ch_bit); + } + + return 0; +} + +static void cdns2_dbg_request_trbs(struct cdns2_endpoint *pep, + struct cdns2_request *preq) +{ + struct cdns2_trb *link_trb = pep->ring.trbs + (TRBS_PER_SEGMENT - 1); + struct cdns2_trb *trb = preq->trb; + int num_trbs = preq->num_of_trb; + int i = 0; + + while (i < num_trbs) { + trace_cdns2_queue_trb(pep, trb + i); + if (trb + i == link_trb) { + trb = pep->ring.trbs; + num_trbs = num_trbs - i; + i = 0; + } else { + i++; + } + } +} + +static unsigned int cdns2_count_trbs(struct cdns2_endpoint *pep, + u64 addr, u64 len) +{ + unsigned int num_trbs = 1; + + if (pep->type == USB_ENDPOINT_XFER_ISOC) { + /* + * To speed up DMA performance address should not exceed 4KB. + * for high bandwidth transfer and driver will split + * such buffer into two TRBs. + */ + num_trbs = DIV_ROUND_UP(len + + (addr & (TRB_MAX_ISO_BUFF_SIZE - 1)), + TRB_MAX_ISO_BUFF_SIZE); + + if (pep->interval > 1) + num_trbs = pep->dir ? num_trbs * pep->interval : 1; + } else if (pep->dir) { + /* + * One extra link trb for IN direction. + * Sometimes DMA doesn't want advance to next TD and transfer + * hangs. This extra Link TRB force DMA to advance to next TD. + */ + num_trbs++; + } + + return num_trbs; +} + +static unsigned int cdns2_count_sg_trbs(struct cdns2_endpoint *pep, + struct usb_request *req) +{ + unsigned int i, len, full_len, num_trbs = 0; + struct scatterlist *sg; + int trb_len = 0; + + full_len = req->length; + + for_each_sg(req->sg, sg, req->num_sgs, i) { + len = sg_dma_len(sg); + num_trbs += cdns2_count_trbs(pep, sg_dma_address(sg), len); + len = min(len, full_len); + + /* + * For HS ISO transfer TRBs should not exceed max packet size. + * When DMA is working, and data exceed max packet size then + * some data will be read in single mode instead burst mode. + * This behavior will drastically reduce the copying speed. + * To avoid this we need one or two extra TRBs. + * This issue occurs for UVC class with sg_supported = 1 + * because buffers addresses are not aligned to 1024. + */ + if (pep->type == USB_ENDPOINT_XFER_ISOC) { + u8 temp; + + trb_len += len; + temp = trb_len >> 10; + + if (temp) { + if (trb_len % 1024) + num_trbs = num_trbs + temp; + else + num_trbs = num_trbs + temp - 1; + + trb_len = trb_len - (temp << 10); + } + } + + full_len -= len; + if (full_len == 0) + break; + } + + return num_trbs; +} + +/* + * Function prepares the array with optimized AXI burst value for different + * transfer lengths. Controller handles the final data which are less + * then AXI burst size as single byte transactions. + * e.g.: + * Let's assume that driver prepares trb with trb->length 700 and burst size + * will be set to 128. In this case the controller will handle a first 512 as + * single AXI transaction but the next 188 bytes will be handled + * as 47 separate AXI transaction. + * The better solution is to use the burst size equal 16 and then we will + * have only 25 AXI transaction (10 * 64 + 15 *4). + */ +static void cdsn2_isoc_burst_opt(struct cdns2_device *pdev) +{ + int axi_burst_option[] = {1, 2, 4, 8, 16, 32, 64, 128}; + int best_burst; + int array_size; + int opt_burst; + int trb_size; + int i, j; + + array_size = ARRAY_SIZE(axi_burst_option); + + for (i = 0; i <= MAX_ISO_SIZE; i++) { + trb_size = i / 4; + best_burst = trb_size ? trb_size : 1; + + for (j = 0; j < array_size; j++) { + opt_burst = trb_size / axi_burst_option[j]; + opt_burst += trb_size % axi_burst_option[j]; + + if (opt_burst < best_burst) { + best_burst = opt_burst; + pdev->burst_opt[i] = axi_burst_option[j]; + } + } + } +} + +static void cdns2_ep_tx_isoc(struct cdns2_endpoint *pep, + struct cdns2_request *preq, + int num_trbs) +{ + struct scatterlist *sg = NULL; + u32 remaining_packet_size = 0; + struct cdns2_trb *trb; + bool first_trb = true; + dma_addr_t trb_dma; + u32 trb_buff_len; + u32 block_length; + int td_idx = 0; + int split_size; + u32 full_len; + int enqd_len; + int sent_len; + int sg_iter; + u32 control; + int num_tds; + u32 length; + + /* + * For OUT direction 1 TD per interval is enough + * because TRBs are not dumped by controller. + */ + num_tds = pep->dir ? pep->interval : 1; + split_size = preq->request.num_sgs ? 1024 : 3072; + + for (td_idx = 0; td_idx < num_tds; td_idx++) { + if (preq->request.num_sgs) { + sg = preq->request.sg; + trb_dma = sg_dma_address(sg); + block_length = sg_dma_len(sg); + } else { + trb_dma = preq->request.dma; + block_length = preq->request.length; + } + + full_len = preq->request.length; + sg_iter = preq->request.num_sgs ? preq->request.num_sgs : 1; + remaining_packet_size = split_size; + + for (enqd_len = 0; enqd_len < full_len; + enqd_len += trb_buff_len) { + if (remaining_packet_size == 0) + remaining_packet_size = split_size; + + /* + * Calculate TRB length.- buffer can't across 4KB + * and max packet size. + */ + trb_buff_len = TRB_BUFF_LEN_UP_TO_BOUNDARY(trb_dma); + trb_buff_len = min(trb_buff_len, remaining_packet_size); + trb_buff_len = min(trb_buff_len, block_length); + + if (trb_buff_len > full_len - enqd_len) + trb_buff_len = full_len - enqd_len; + + control = TRB_TYPE(TRB_NORMAL); + + /* + * For IN direction driver has to set the IOC for + * last TRB in last TD. + * For OUT direction driver must set IOC and ISP + * only for last TRB in each TDs. + */ + if (enqd_len + trb_buff_len >= full_len || !pep->dir) + control |= TRB_IOC | TRB_ISP; + + /* + * Don't give the first TRB to the hardware (by toggling + * the cycle bit) until we've finished creating all the + * other TRBs. + */ + if (first_trb) { + first_trb = false; + if (pep->ring.pcs == 0) + control |= TRB_CYCLE; + } else { + control |= pep->ring.pcs; + } + + if (enqd_len + trb_buff_len < full_len) + control |= TRB_CHAIN; + + length = TRB_LEN(trb_buff_len) | + TRB_BURST(pep->pdev->burst_opt[trb_buff_len]); + + trb = pep->ring.trbs + pep->ring.enqueue; + trb->buffer = cpu_to_le32(TRB_BUFFER(trb_dma)); + trb->length = cpu_to_le32(length); + trb->control = cpu_to_le32(control); + + trb_dma += trb_buff_len; + sent_len = trb_buff_len; + + if (sg && sent_len >= block_length) { + /* New sg entry */ + --sg_iter; + sent_len -= block_length; + if (sg_iter != 0) { + sg = sg_next(sg); + trb_dma = sg_dma_address(sg); + block_length = sg_dma_len(sg); + } + } + + remaining_packet_size -= trb_buff_len; + block_length -= sent_len; + preq->end_trb = pep->ring.enqueue; + + cdns2_ep_inc_enq(&pep->ring); + } + } +} + +static void cdns2_ep_tx_bulk(struct cdns2_endpoint *pep, + struct cdns2_request *preq, + int trbs_per_td) +{ + struct scatterlist *sg = NULL; + struct cdns2_ring *ring; + struct cdns2_trb *trb; + dma_addr_t trb_dma; + int sg_iter = 0; + u32 control; + u32 length; + + if (preq->request.num_sgs) { + sg = preq->request.sg; + trb_dma = sg_dma_address(sg); + length = sg_dma_len(sg); + } else { + trb_dma = preq->request.dma; + length = preq->request.length; + } + + ring = &pep->ring; + + for (sg_iter = 0; sg_iter < trbs_per_td; sg_iter++) { + control = TRB_TYPE(TRB_NORMAL) | ring->pcs | TRB_ISP; + trb = pep->ring.trbs + ring->enqueue; + + if (pep->dir && sg_iter == trbs_per_td - 1) { + preq->end_trb = ring->enqueue; + control = ring->pcs | TRB_TYPE(TRB_LINK) | TRB_CHAIN + | TRB_IOC; + cdns2_ep_inc_enq(&pep->ring); + + if (ring->enqueue == 0) + control |= TRB_TOGGLE; + + /* Point to next bad TRB. */ + trb->buffer = cpu_to_le32(pep->ring.dma + + (ring->enqueue * TRB_SIZE)); + trb->length = 0; + trb->control = cpu_to_le32(control); + break; + } + + /* + * Don't give the first TRB to the hardware (by toggling + * the cycle bit) until we've finished creating all the + * other TRBs. + */ + if (sg_iter == 0) + control = control ^ TRB_CYCLE; + + /* For last TRB in TD. */ + if (sg_iter == (trbs_per_td - (pep->dir ? 2 : 1))) + control |= TRB_IOC; + else + control |= TRB_CHAIN; + + trb->buffer = cpu_to_le32(trb_dma); + trb->length = cpu_to_le32(TRB_BURST(pep->trb_burst_size) | + TRB_LEN(length)); + trb->control = cpu_to_le32(control); + + if (sg && sg_iter < (trbs_per_td - 1)) { + sg = sg_next(sg); + trb_dma = sg_dma_address(sg); + length = sg_dma_len(sg); + } + + preq->end_trb = ring->enqueue; + cdns2_ep_inc_enq(&pep->ring); + } +} + +static void cdns2_set_drdy(struct cdns2_device *pdev, + struct cdns2_endpoint *pep) +{ + trace_cdns2_ring(pep); + + /* + * Memory barrier - Cycle Bit must be set before doorbell. + */ + dma_wmb(); + + /* Clearing TRBERR and DESCMIS before setting DRDY. */ + writel(DMA_EP_STS_TRBERR | DMA_EP_STS_DESCMIS, + &pdev->adma_regs->ep_sts); + writel(DMA_EP_CMD_DRDY, &pdev->adma_regs->ep_cmd); + + if (readl(&pdev->adma_regs->ep_sts) & DMA_EP_STS_TRBERR) { + writel(DMA_EP_STS_TRBERR, &pdev->adma_regs->ep_sts); + writel(DMA_EP_CMD_DRDY, &pdev->adma_regs->ep_cmd); + } + + trace_cdns2_doorbell_epx(pep, readl(&pdev->adma_regs->ep_traddr)); +} + +static int cdns2_prepare_first_isoc_transfer(struct cdns2_device *pdev, + struct cdns2_endpoint *pep) +{ + struct cdns2_trb *trb; + u32 buffer; + u8 hw_ccs; + + if ((readl(&pdev->adma_regs->ep_cmd) & DMA_EP_CMD_DRDY)) + return -EBUSY; + + if (!pep->dir) { + set_reg_bit_32(&pdev->adma_regs->ep_cfg, DMA_EP_CFG_ENABLE); + writel(pep->ring.dma + pep->ring.dequeue, + &pdev->adma_regs->ep_traddr); + return 0; + } + + /* + * The first packet after doorbell can be corrupted so, + * driver prepares 0 length packet as first packet. + */ + buffer = pep->ring.dma + pep->ring.dequeue * TRB_SIZE; + hw_ccs = !!DMA_EP_STS_CCS(readl(&pdev->adma_regs->ep_sts)); + + trb = &pep->ring.trbs[TRBS_PER_SEGMENT]; + trb->length = 0; + trb->buffer = cpu_to_le32(TRB_BUFFER(buffer)); + trb->control = cpu_to_le32((hw_ccs ? TRB_CYCLE : 0) | TRB_TYPE(TRB_NORMAL)); + + /* + * LINK TRB is used to force updating cycle bit in controller and + * move to correct place in transfer ring. + */ + trb++; + trb->length = 0; + trb->buffer = cpu_to_le32(TRB_BUFFER(buffer)); + trb->control = cpu_to_le32((hw_ccs ? TRB_CYCLE : 0) | + TRB_TYPE(TRB_LINK) | TRB_CHAIN); + + if (hw_ccs != pep->ring.ccs) + trb->control |= cpu_to_le32(TRB_TOGGLE); + + set_reg_bit_32(&pdev->adma_regs->ep_cfg, DMA_EP_CFG_ENABLE); + writel(pep->ring.dma + (TRBS_PER_SEGMENT * TRB_SIZE), + &pdev->adma_regs->ep_traddr); + + return 0; +} + +/* Prepare and start transfer on no-default endpoint. */ +static int cdns2_ep_run_transfer(struct cdns2_endpoint *pep, + struct cdns2_request *preq) +{ + struct cdns2_device *pdev = pep->pdev; + struct cdns2_ring *ring; + u32 togle_pcs = 1; + int num_trbs; + int ret; + + cdns2_select_ep(pdev, pep->endpoint.address); + + if (preq->request.sg) + num_trbs = cdns2_count_sg_trbs(pep, &preq->request); + else + num_trbs = cdns2_count_trbs(pep, preq->request.dma, + preq->request.length); + + ret = cdns2_prepare_ring(pdev, pep, num_trbs); + if (ret) + return ret; + + ring = &pep->ring; + preq->start_trb = ring->enqueue; + preq->trb = ring->trbs + ring->enqueue; + + if (usb_endpoint_xfer_isoc(pep->endpoint.desc)) { + cdns2_ep_tx_isoc(pep, preq, num_trbs); + } else { + togle_pcs = cdns2_wa1_update_guard(pep, ring->trbs + ring->enqueue); + cdns2_ep_tx_bulk(pep, preq, num_trbs); + } + + preq->num_of_trb = num_trbs; + + /* + * Memory barrier - cycle bit must be set as the last operation. + */ + dma_wmb(); + + /* Give the TD to the consumer. */ + if (togle_pcs) + preq->trb->control = preq->trb->control ^ cpu_to_le32(1); + + cdns2_wa1_tray_restore_cycle_bit(pdev, pep); + cdns2_dbg_request_trbs(pep, preq); + + if (!pep->wa1_set && !(pep->ep_state & EP_STALLED) && !pep->skip) { + if (pep->type == USB_ENDPOINT_XFER_ISOC) { + ret = cdns2_prepare_first_isoc_transfer(pdev, pep); + if (ret) + return 0; + } + + cdns2_set_drdy(pdev, pep); + } + + return 0; +} + +/* Prepare and start transfer for all not started requests. */ +static int cdns2_start_all_request(struct cdns2_device *pdev, + struct cdns2_endpoint *pep) +{ + struct cdns2_request *preq; + int ret; + + while (!list_empty(&pep->deferred_list)) { + preq = cdns2_next_preq(&pep->deferred_list); + + ret = cdns2_ep_run_transfer(pep, preq); + if (ret) + return ret; + + list_move_tail(&preq->list, &pep->pending_list); + } + + pep->ep_state &= ~EP_RING_FULL; + + return 0; +} + +/* + * Check whether trb has been handled by DMA. + * + * Endpoint must be selected before invoking this function. + * + * Returns false if request has not been handled by DMA, else returns true. + * + * SR - start ring + * ER - end ring + * DQ = ring->dequeue - dequeue position + * EQ = ring->enqueue - enqueue position + * ST = preq->start_trb - index of first TRB in transfer ring + * ET = preq->end_trb - index of last TRB in transfer ring + * CI = current_index - index of processed TRB by DMA. + * + * As first step, we check if the TRB between the ST and ET. + * Then, we check if cycle bit for index pep->dequeue + * is correct. + * + * some rules: + * 1. ring->dequeue never equals to current_index. + * 2 ring->enqueue never exceed ring->dequeue + * 3. exception: ring->enqueue == ring->dequeue + * and ring->free_trbs is zero. + * This case indicate that TR is full. + * + * At below two cases, the request have been handled. + * Case 1 - ring->dequeue < current_index + * SR ... EQ ... DQ ... CI ... ER + * SR ... DQ ... CI ... EQ ... ER + * + * Case 2 - ring->dequeue > current_index + * This situation takes place when CI go through the LINK TRB at the end of + * transfer ring. + * SR ... CI ... EQ ... DQ ... ER + */ +static bool cdns2_trb_handled(struct cdns2_endpoint *pep, + struct cdns2_request *preq) +{ + struct cdns2_device *pdev = pep->pdev; + struct cdns2_ring *ring; + struct cdns2_trb *trb; + int current_index = 0; + int handled = 0; + int doorbell; + + ring = &pep->ring; + current_index = cdns2_get_dma_pos(pdev, pep); + doorbell = !!(readl(&pdev->adma_regs->ep_cmd) & DMA_EP_CMD_DRDY); + + /* + * Only ISO transfer can use 2 entries outside the standard + * Transfer Ring. First of them is used as zero length packet and the + * second as LINK TRB. + */ + if (current_index >= TRBS_PER_SEGMENT) + goto finish; + + /* Current trb doesn't belong to this request. */ + if (preq->start_trb < preq->end_trb) { + if (ring->dequeue > preq->end_trb) + goto finish; + + if (ring->dequeue < preq->start_trb) + goto finish; + } + + if (preq->start_trb > preq->end_trb && ring->dequeue > preq->end_trb && + ring->dequeue < preq->start_trb) + goto finish; + + if (preq->start_trb == preq->end_trb && ring->dequeue != preq->end_trb) + goto finish; + + trb = &ring->trbs[ring->dequeue]; + + if ((le32_to_cpu(trb->control) & TRB_CYCLE) != ring->ccs) + goto finish; + + if (doorbell == 1 && current_index == ring->dequeue) + goto finish; + + /* The corner case for TRBS_PER_SEGMENT equal 2). */ + if (TRBS_PER_SEGMENT == 2 && pep->type != USB_ENDPOINT_XFER_ISOC) { + handled = 1; + goto finish; + } + + if (ring->enqueue == ring->dequeue && + ring->free_trbs == 0) { + handled = 1; + } else if (ring->dequeue < current_index) { + if ((current_index == (TRBS_PER_SEGMENT - 1)) && + !ring->dequeue) + goto finish; + + handled = 1; + } else if (ring->dequeue > current_index) { + handled = 1; + } + +finish: + trace_cdns2_request_handled(preq, current_index, handled); + + return handled; +} + +static void cdns2_skip_isoc_td(struct cdns2_device *pdev, + struct cdns2_endpoint *pep, + struct cdns2_request *preq) +{ + struct cdns2_trb *trb; + int i; + + trb = pep->ring.trbs + pep->ring.dequeue; + + for (i = preq->finished_trb ; i < preq->num_of_trb; i++) { + preq->finished_trb++; + trace_cdns2_complete_trb(pep, trb); + cdns2_ep_inc_deq(&pep->ring); + trb = cdns2_next_trb(pep, trb); + } + + cdns2_gadget_giveback(pep, preq, 0); + cdns2_prepare_first_isoc_transfer(pdev, pep); + pep->skip = false; + cdns2_set_drdy(pdev, pep); +} + +static void cdns2_transfer_completed(struct cdns2_device *pdev, + struct cdns2_endpoint *pep) +{ + struct cdns2_request *preq = NULL; + bool request_handled = false; + struct cdns2_trb *trb; + + while (!list_empty(&pep->pending_list)) { + preq = cdns2_next_preq(&pep->pending_list); + trb = pep->ring.trbs + pep->ring.dequeue; + + /* + * The TRB was changed as link TRB, and the request + * was handled at ep_dequeue. + */ + while (TRB_FIELD_TO_TYPE(le32_to_cpu(trb->control)) == TRB_LINK && + le32_to_cpu(trb->length)) { + trace_cdns2_complete_trb(pep, trb); + cdns2_ep_inc_deq(&pep->ring); + trb = pep->ring.trbs + pep->ring.dequeue; + } + + /* + * Re-select endpoint. It could be changed by other CPU + * during handling usb_gadget_giveback_request. + */ + cdns2_select_ep(pdev, pep->endpoint.address); + + while (cdns2_trb_handled(pep, preq)) { + preq->finished_trb++; + + if (preq->finished_trb >= preq->num_of_trb) + request_handled = true; + + trb = pep->ring.trbs + pep->ring.dequeue; + trace_cdns2_complete_trb(pep, trb); + + if (pep->dir && pep->type == USB_ENDPOINT_XFER_ISOC) + /* + * For ISOC IN controller doens't update the + * trb->length. + */ + preq->request.actual = preq->request.length; + else + preq->request.actual += + TRB_LEN(le32_to_cpu(trb->length)); + + cdns2_ep_inc_deq(&pep->ring); + } + + if (request_handled) { + cdns2_gadget_giveback(pep, preq, 0); + request_handled = false; + } else { + goto prepare_next_td; + } + + if (pep->type != USB_ENDPOINT_XFER_ISOC && + TRBS_PER_SEGMENT == 2) + break; + } + +prepare_next_td: + if (pep->skip && preq) + cdns2_skip_isoc_td(pdev, pep, preq); + + if (!(pep->ep_state & EP_STALLED) && + !(pep->ep_state & EP_STALL_PENDING)) + cdns2_start_all_request(pdev, pep); +} + +static void cdns2_wakeup(struct cdns2_device *pdev) +{ + if (!pdev->may_wakeup) + return; + + /* Start driving resume signaling to indicate remote wakeup. */ + set_reg_bit_8(&pdev->usb_regs->usbcs, USBCS_SIGRSUME); +} + +static void cdns2_rearm_transfer(struct cdns2_endpoint *pep, u8 rearm) +{ + struct cdns2_device *pdev = pep->pdev; + + cdns2_wa1_restore_cycle_bit(pep); + + if (rearm) { + trace_cdns2_ring(pep); + + /* Cycle Bit must be updated before arming DMA. */ + dma_wmb(); + + writel(DMA_EP_CMD_DRDY, &pdev->adma_regs->ep_cmd); + + cdns2_wakeup(pdev); + + trace_cdns2_doorbell_epx(pep, + readl(&pdev->adma_regs->ep_traddr)); + } +} + +static void cdns2_handle_epx_interrupt(struct cdns2_endpoint *pep) +{ + struct cdns2_device *pdev = pep->pdev; + u8 isoerror = 0; + u32 ep_sts_reg; + u32 val; + + cdns2_select_ep(pdev, pep->endpoint.address); + + trace_cdns2_epx_irq(pdev, pep); + + ep_sts_reg = readl(&pdev->adma_regs->ep_sts); + writel(ep_sts_reg, &pdev->adma_regs->ep_sts); + + if (pep->type == USB_ENDPOINT_XFER_ISOC) { + u8 mult; + u8 cs; + + mult = USB_EP_MAXP_MULT(pep->endpoint.desc->wMaxPacketSize); + cs = pep->dir ? readb(&pdev->epx_regs->ep[pep->num - 1].txcs) : + readb(&pdev->epx_regs->ep[pep->num - 1].rxcs); + if (mult > 0) + isoerror = EPX_CS_ERR(cs); + } + + /* + * Sometimes ISO Error for mult=1 or mult=2 is not propagated on time + * from USB module to DMA module. To protect against this driver + * checks also the txcs/rxcs registers. + */ + if ((ep_sts_reg & DMA_EP_STS_ISOERR) || isoerror) { + clear_reg_bit_32(&pdev->adma_regs->ep_cfg, DMA_EP_CFG_ENABLE); + + /* Wait for DBUSY cleared. */ + readl_poll_timeout_atomic(&pdev->adma_regs->ep_sts, val, + !(val & DMA_EP_STS_DBUSY), 1, 125); + + writel(DMA_EP_CMD_DFLUSH, &pep->pdev->adma_regs->ep_cmd); + + /* Wait for DFLUSH cleared. */ + readl_poll_timeout_atomic(&pep->pdev->adma_regs->ep_cmd, val, + !(val & DMA_EP_CMD_DFLUSH), 1, 10); + + pep->skip = true; + } + + if (ep_sts_reg & DMA_EP_STS_TRBERR || pep->skip) { + if (pep->ep_state & EP_STALL_PENDING && + !(ep_sts_reg & DMA_EP_STS_DESCMIS)) + cdns2_ep_stall_flush(pep); + + /* + * For isochronous transfer driver completes request on + * IOC or on TRBERR. IOC appears only when device receive + * OUT data packet. If host disable stream or lost some packet + * then the only way to finish all queued transfer is to do it + * on TRBERR event. + */ + if (pep->type == USB_ENDPOINT_XFER_ISOC && !pep->wa1_set) { + if (!pep->dir) + clear_reg_bit_32(&pdev->adma_regs->ep_cfg, + DMA_EP_CFG_ENABLE); + + cdns2_transfer_completed(pdev, pep); + if (pep->ep_state & EP_DEFERRED_DRDY) { + pep->ep_state &= ~EP_DEFERRED_DRDY; + cdns2_set_drdy(pdev, pep); + } + + return; + } + + cdns2_transfer_completed(pdev, pep); + + if (!(pep->ep_state & EP_STALLED) && + !(pep->ep_state & EP_STALL_PENDING)) { + if (pep->ep_state & EP_DEFERRED_DRDY) { + pep->ep_state &= ~EP_DEFERRED_DRDY; + cdns2_start_all_request(pdev, pep); + } else { + cdns2_rearm_transfer(pep, pep->wa1_set); + } + } + + return; + } + + if ((ep_sts_reg & DMA_EP_STS_IOC) || (ep_sts_reg & DMA_EP_STS_ISP)) + cdns2_transfer_completed(pdev, pep); +} + +static void cdns2_disconnect_gadget(struct cdns2_device *pdev) +{ + if (pdev->gadget_driver && pdev->gadget_driver->disconnect) + pdev->gadget_driver->disconnect(&pdev->gadget); +} + +static irqreturn_t cdns2_usb_irq_handler(int irq, void *data) +{ + struct cdns2_device *pdev = data; + unsigned long reg_ep_ists; + u8 reg_usb_irq_m; + u8 reg_ext_irq_m; + u8 reg_usb_irq; + u8 reg_ext_irq; + + if (pdev->in_lpm) + return IRQ_NONE; + + reg_usb_irq_m = readb(&pdev->interrupt_regs->usbien); + reg_ext_irq_m = readb(&pdev->interrupt_regs->extien); + + /* Mask all sources of interrupt. */ + writeb(0, &pdev->interrupt_regs->usbien); + writeb(0, &pdev->interrupt_regs->extien); + writel(0, &pdev->adma_regs->ep_ien); + + /* Clear interrupt sources. */ + writel(0, &pdev->adma_regs->ep_sts); + writeb(0, &pdev->interrupt_regs->usbirq); + writeb(0, &pdev->interrupt_regs->extirq); + + reg_ep_ists = readl(&pdev->adma_regs->ep_ists); + reg_usb_irq = readb(&pdev->interrupt_regs->usbirq); + reg_ext_irq = readb(&pdev->interrupt_regs->extirq); + + if (reg_ep_ists || (reg_usb_irq & reg_usb_irq_m) || + (reg_ext_irq & reg_ext_irq_m)) + return IRQ_WAKE_THREAD; + + writeb(USB_IEN_INIT, &pdev->interrupt_regs->usbien); + writeb(EXTIRQ_WAKEUP, &pdev->interrupt_regs->extien); + writel(~0, &pdev->adma_regs->ep_ien); + + return IRQ_NONE; +} + +static irqreturn_t cdns2_thread_usb_irq_handler(struct cdns2_device *pdev) +{ + u8 usb_irq, ext_irq; + int speed; + int i; + + ext_irq = readb(&pdev->interrupt_regs->extirq) & EXTIRQ_WAKEUP; + writeb(ext_irq, &pdev->interrupt_regs->extirq); + + usb_irq = readb(&pdev->interrupt_regs->usbirq) & USB_IEN_INIT; + writeb(usb_irq, &pdev->interrupt_regs->usbirq); + + if (!ext_irq && !usb_irq) + return IRQ_NONE; + + trace_cdns2_usb_irq(usb_irq, ext_irq); + + if (ext_irq & EXTIRQ_WAKEUP) { + if (pdev->gadget_driver && pdev->gadget_driver->resume) { + spin_unlock(&pdev->lock); + pdev->gadget_driver->resume(&pdev->gadget); + spin_lock(&pdev->lock); + } + } + + if (usb_irq & USBIRQ_LPM) { + u8 reg = readb(&pdev->usb_regs->lpmctrl); + + /* LPM1 enter */ + if (!(reg & LPMCTRLLH_LPMNYET)) + writeb(0, &pdev->usb_regs->sleep_clkgate); + } + + if (usb_irq & USBIRQ_SUSPEND) { + if (pdev->gadget_driver && pdev->gadget_driver->suspend) { + spin_unlock(&pdev->lock); + pdev->gadget_driver->suspend(&pdev->gadget); + spin_lock(&pdev->lock); + } + } + + if (usb_irq & USBIRQ_URESET) { + if (pdev->gadget_driver) { + pdev->dev_address = 0; + + spin_unlock(&pdev->lock); + usb_gadget_udc_reset(&pdev->gadget, + pdev->gadget_driver); + spin_lock(&pdev->lock); + + /* + * The USBIRQ_URESET is reported at the beginning of + * reset signal. 100ms is enough time to finish reset + * process. For high-speed reset procedure is completed + * when controller detect HS mode. + */ + for (i = 0; i < 100; i++) { + mdelay(1); + speed = cdns2_get_speed(pdev); + if (speed == USB_SPEED_HIGH) + break; + } + + pdev->gadget.speed = speed; + cdns2_enable_l1(pdev, 0); + cdns2_ep0_config(pdev); + pdev->may_wakeup = 0; + } + } + + if (usb_irq & USBIRQ_SUDAV) { + pdev->ep0_stage = CDNS2_SETUP_STAGE; + cdns2_handle_setup_packet(pdev); + } + + return IRQ_HANDLED; +} + +/* Deferred USB interrupt handler. */ +static irqreturn_t cdns2_thread_irq_handler(int irq, void *data) +{ + struct cdns2_device *pdev = data; + unsigned long dma_ep_ists; + unsigned long flags; + unsigned int bit; + + local_bh_disable(); + spin_lock_irqsave(&pdev->lock, flags); + + cdns2_thread_usb_irq_handler(pdev); + + dma_ep_ists = readl(&pdev->adma_regs->ep_ists); + if (!dma_ep_ists) + goto unlock; + + trace_cdns2_dma_ep_ists(dma_ep_ists); + + /* Handle default endpoint OUT. */ + if (dma_ep_ists & DMA_EP_ISTS_EP_OUT0) + cdns2_handle_ep0_interrupt(pdev, USB_DIR_OUT); + + /* Handle default endpoint IN. */ + if (dma_ep_ists & DMA_EP_ISTS_EP_IN0) + cdns2_handle_ep0_interrupt(pdev, USB_DIR_IN); + + dma_ep_ists &= ~(DMA_EP_ISTS_EP_OUT0 | DMA_EP_ISTS_EP_IN0); + + for_each_set_bit(bit, &dma_ep_ists, sizeof(u32) * BITS_PER_BYTE) { + u8 ep_idx = bit > 16 ? (bit - 16) * 2 : (bit * 2) - 1; + + /* + * Endpoints in pdev->eps[] are held in order: + * ep0, ep1out, ep1in, ep2out, ep2in... ep15out, ep15in. + * but in dma_ep_ists in order: + * ep0 ep1out ep2out ... ep15out ep0in ep1in .. ep15in + */ + cdns2_handle_epx_interrupt(&pdev->eps[ep_idx]); + } + +unlock: + writel(~0, &pdev->adma_regs->ep_ien); + writeb(USB_IEN_INIT, &pdev->interrupt_regs->usbien); + writeb(EXTIRQ_WAKEUP, &pdev->interrupt_regs->extien); + + spin_unlock_irqrestore(&pdev->lock, flags); + local_bh_enable(); + + return IRQ_HANDLED; +} + +/* Calculates and assigns onchip memory for endpoints. */ +static void cdns2_eps_onchip_buffer_init(struct cdns2_device *pdev) +{ + struct cdns2_endpoint *pep; + int min_buf_tx = 0; + int min_buf_rx = 0; + u16 tx_offset = 0; + u16 rx_offset = 0; + int free; + int i; + + for (i = 0; i < CDNS2_ENDPOINTS_NUM; i++) { + pep = &pdev->eps[i]; + + if (!(pep->ep_state & EP_CLAIMED)) + continue; + + if (pep->dir) + min_buf_tx += pep->buffering; + else + min_buf_rx += pep->buffering; + } + + for (i = 0; i < CDNS2_ENDPOINTS_NUM; i++) { + pep = &pdev->eps[i]; + + if (!(pep->ep_state & EP_CLAIMED)) + continue; + + if (pep->dir) { + free = pdev->onchip_tx_buf - min_buf_tx; + + if (free + pep->buffering >= 4) + free = 4; + else + free = free + pep->buffering; + + min_buf_tx = min_buf_tx - pep->buffering + free; + + pep->buffering = free; + + writel(tx_offset, + &pdev->epx_regs->txstaddr[pep->num - 1]); + pdev->epx_regs->txstaddr[pep->num - 1] = tx_offset; + + dev_dbg(pdev->dev, "%s onchip address %04x, buffering: %d\n", + pep->name, tx_offset, pep->buffering); + + tx_offset += pep->buffering * 1024; + } else { + free = pdev->onchip_rx_buf - min_buf_rx; + + if (free + pep->buffering >= 4) + free = 4; + else + free = free + pep->buffering; + + min_buf_rx = min_buf_rx - pep->buffering + free; + + pep->buffering = free; + writel(rx_offset, + &pdev->epx_regs->rxstaddr[pep->num - 1]); + + dev_dbg(pdev->dev, "%s onchip address %04x, buffering: %d\n", + pep->name, rx_offset, pep->buffering); + + rx_offset += pep->buffering * 1024; + } + } +} + +/* Configure hardware endpoint. */ +static int cdns2_ep_config(struct cdns2_endpoint *pep, bool enable) +{ + bool is_iso_ep = (pep->type == USB_ENDPOINT_XFER_ISOC); + struct cdns2_device *pdev = pep->pdev; + u32 max_packet_size; + u8 dir = 0; + u8 ep_cfg; + u8 mult; + u32 val; + int ret; + + switch (pep->type) { + case USB_ENDPOINT_XFER_INT: + ep_cfg = EPX_CON_TYPE_INT; + break; + case USB_ENDPOINT_XFER_BULK: + ep_cfg = EPX_CON_TYPE_BULK; + break; + default: + mult = USB_EP_MAXP_MULT(pep->endpoint.desc->wMaxPacketSize); + ep_cfg = mult << EPX_CON_ISOD_SHIFT; + ep_cfg |= EPX_CON_TYPE_ISOC; + + if (pep->dir) { + set_reg_bit_8(&pdev->epx_regs->isoautoarm, BIT(pep->num)); + set_reg_bit_8(&pdev->epx_regs->isoautodump, BIT(pep->num)); + set_reg_bit_8(&pdev->epx_regs->isodctrl, BIT(pep->num)); + } + } + + switch (pdev->gadget.speed) { + case USB_SPEED_FULL: + max_packet_size = is_iso_ep ? 1023 : 64; + break; + case USB_SPEED_HIGH: + max_packet_size = is_iso_ep ? 1024 : 512; + break; + default: + /* All other speed are not supported. */ + return -EINVAL; + } + + ep_cfg |= (EPX_CON_VAL | (pep->buffering - 1)); + + if (pep->dir) { + dir = FIFOCTRL_IO_TX; + writew(max_packet_size, &pdev->epx_regs->txmaxpack[pep->num - 1]); + writeb(ep_cfg, &pdev->epx_regs->ep[pep->num - 1].txcon); + } else { + writew(max_packet_size, &pdev->epx_regs->rxmaxpack[pep->num - 1]); + writeb(ep_cfg, &pdev->epx_regs->ep[pep->num - 1].rxcon); + } + + writeb(pep->num | dir | FIFOCTRL_FIFOAUTO, + &pdev->usb_regs->fifoctrl); + writeb(pep->num | dir, &pdev->epx_regs->endprst); + writeb(pep->num | ENDPRST_FIFORST | ENDPRST_TOGRST | dir, + &pdev->epx_regs->endprst); + + if (max_packet_size == 1024) + pep->trb_burst_size = 128; + else if (max_packet_size >= 512) + pep->trb_burst_size = 64; + else + pep->trb_burst_size = 16; + + cdns2_select_ep(pdev, pep->num | pep->dir); + writel(DMA_EP_CMD_EPRST | DMA_EP_CMD_DFLUSH, &pdev->adma_regs->ep_cmd); + + ret = readl_poll_timeout_atomic(&pdev->adma_regs->ep_cmd, val, + !(val & (DMA_EP_CMD_DFLUSH | + DMA_EP_CMD_EPRST)), + 1, 1000); + + if (ret) + return ret; + + writel(DMA_EP_STS_TRBERR | DMA_EP_STS_ISOERR, &pdev->adma_regs->ep_sts_en); + + if (enable) + writel(DMA_EP_CFG_ENABLE, &pdev->adma_regs->ep_cfg); + + trace_cdns2_epx_hw_cfg(pdev, pep); + + dev_dbg(pdev->dev, "Configure %s: with MPS: %08x, ep con: %02x\n", + pep->name, max_packet_size, ep_cfg); + + return 0; +} + +struct usb_request *cdns2_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct cdns2_endpoint *pep = ep_to_cdns2_ep(ep); + struct cdns2_request *preq; + + preq = kzalloc_obj(*preq, gfp_flags); + if (!preq) + return NULL; + + preq->pep = pep; + + trace_cdns2_alloc_request(preq); + + return &preq->request; +} + +void cdns2_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request) +{ + struct cdns2_request *preq = to_cdns2_request(request); + + trace_cdns2_free_request(preq); + kfree(preq); +} + +static int cdns2_gadget_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + u32 reg = DMA_EP_STS_EN_TRBERREN; + struct cdns2_endpoint *pep; + struct cdns2_device *pdev; + unsigned long flags; + int enable = 1; + int ret = 0; + + if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT || + !desc->wMaxPacketSize) { + return -EINVAL; + } + + pep = ep_to_cdns2_ep(ep); + pdev = pep->pdev; + + if (dev_WARN_ONCE(pdev->dev, pep->ep_state & EP_ENABLED, + "%s is already enabled\n", pep->name)) + return 0; + + spin_lock_irqsave(&pdev->lock, flags); + + pep->type = usb_endpoint_type(desc); + pep->interval = desc->bInterval ? BIT(desc->bInterval - 1) : 0; + + if (pdev->gadget.speed == USB_SPEED_FULL) + if (pep->type == USB_ENDPOINT_XFER_INT) + pep->interval = desc->bInterval; + + if (pep->interval > ISO_MAX_INTERVAL && + pep->type == USB_ENDPOINT_XFER_ISOC) { + dev_err(pdev->dev, "ISO period is limited to %d (current: %d)\n", + ISO_MAX_INTERVAL, pep->interval); + + ret = -EINVAL; + goto exit; + } + + /* + * During ISO OUT traffic DMA reads Transfer Ring for the EP which has + * never got doorbell. + * This issue was detected only on simulation, but to avoid this issue + * driver add protection against it. To fix it driver enable ISO OUT + * endpoint before setting DRBL. This special treatment of ISO OUT + * endpoints are recommended by controller specification. + */ + if (pep->type == USB_ENDPOINT_XFER_ISOC && !pep->dir) + enable = 0; + + ret = cdns2_alloc_tr_segment(pep); + if (ret) + goto exit; + + ret = cdns2_ep_config(pep, enable); + if (ret) { + cdns2_free_tr_segment(pep); + ret = -EINVAL; + goto exit; + } + + trace_cdns2_gadget_ep_enable(pep); + + pep->ep_state &= ~(EP_STALLED | EP_STALL_PENDING); + pep->ep_state |= EP_ENABLED; + pep->wa1_set = 0; + pep->ring.enqueue = 0; + pep->ring.dequeue = 0; + reg = readl(&pdev->adma_regs->ep_sts); + pep->ring.pcs = !!DMA_EP_STS_CCS(reg); + pep->ring.ccs = !!DMA_EP_STS_CCS(reg); + + writel(pep->ring.dma, &pdev->adma_regs->ep_traddr); + + /* one TRB is reserved for link TRB used in DMULT mode*/ + pep->ring.free_trbs = TRBS_PER_SEGMENT - 1; + +exit: + spin_unlock_irqrestore(&pdev->lock, flags); + + return ret; +} + +static int cdns2_gadget_ep_disable(struct usb_ep *ep) +{ + struct cdns2_endpoint *pep; + struct cdns2_request *preq; + struct cdns2_device *pdev; + unsigned long flags; + int val; + + if (!ep) + return -EINVAL; + + pep = ep_to_cdns2_ep(ep); + pdev = pep->pdev; + + if (dev_WARN_ONCE(pdev->dev, !(pep->ep_state & EP_ENABLED), + "%s is already disabled\n", pep->name)) + return 0; + + spin_lock_irqsave(&pdev->lock, flags); + + trace_cdns2_gadget_ep_disable(pep); + + cdns2_select_ep(pdev, ep->desc->bEndpointAddress); + + clear_reg_bit_32(&pdev->adma_regs->ep_cfg, DMA_EP_CFG_ENABLE); + + /* + * Driver needs some time before resetting endpoint. + * It need waits for clearing DBUSY bit or for timeout expired. + * 10us is enough time for controller to stop transfer. + */ + readl_poll_timeout_atomic(&pdev->adma_regs->ep_sts, val, + !(val & DMA_EP_STS_DBUSY), 1, 10); + writel(DMA_EP_CMD_EPRST, &pdev->adma_regs->ep_cmd); + + readl_poll_timeout_atomic(&pdev->adma_regs->ep_cmd, val, + !(val & (DMA_EP_CMD_DFLUSH | DMA_EP_CMD_EPRST)), + 1, 1000); + + while (!list_empty(&pep->pending_list)) { + preq = cdns2_next_preq(&pep->pending_list); + cdns2_gadget_giveback(pep, preq, -ESHUTDOWN); + } + + while (!list_empty(&pep->deferred_list)) { + preq = cdns2_next_preq(&pep->deferred_list); + cdns2_gadget_giveback(pep, preq, -ESHUTDOWN); + } + + ep->desc = NULL; + pep->ep_state &= ~EP_ENABLED; + + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdns2_ep_enqueue(struct cdns2_endpoint *pep, + struct cdns2_request *preq, + gfp_t gfp_flags) +{ + struct cdns2_device *pdev = pep->pdev; + struct usb_request *request; + int ret; + + request = &preq->request; + request->actual = 0; + request->status = -EINPROGRESS; + + ret = usb_gadget_map_request_by_dev(pdev->dev, request, pep->dir); + if (ret) { + trace_cdns2_request_enqueue_error(preq); + return ret; + } + + list_add_tail(&preq->list, &pep->deferred_list); + trace_cdns2_request_enqueue(preq); + + if (!(pep->ep_state & EP_STALLED) && !(pep->ep_state & EP_STALL_PENDING)) + cdns2_start_all_request(pdev, pep); + + return 0; +} + +static int cdns2_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, + gfp_t gfp_flags) +{ + struct usb_request *zlp_request; + struct cdns2_request *preq; + struct cdns2_endpoint *pep; + struct cdns2_device *pdev; + unsigned long flags; + int ret; + + if (!request || !ep) + return -EINVAL; + + pep = ep_to_cdns2_ep(ep); + pdev = pep->pdev; + + if (!(pep->ep_state & EP_ENABLED)) { + dev_err(pdev->dev, "%s: can't queue to disabled endpoint\n", + pep->name); + return -EINVAL; + } + + spin_lock_irqsave(&pdev->lock, flags); + + preq = to_cdns2_request(request); + ret = cdns2_ep_enqueue(pep, preq, gfp_flags); + + if (ret == 0 && request->zero && request->length && + (request->length % ep->maxpacket == 0)) { + struct cdns2_request *preq; + + zlp_request = cdns2_gadget_ep_alloc_request(ep, GFP_ATOMIC); + zlp_request->buf = pdev->zlp_buf; + zlp_request->length = 0; + + preq = to_cdns2_request(zlp_request); + ret = cdns2_ep_enqueue(pep, preq, gfp_flags); + } + + spin_unlock_irqrestore(&pdev->lock, flags); + return ret; +} + +int cdns2_gadget_ep_dequeue(struct usb_ep *ep, + struct usb_request *request) +{ + struct cdns2_request *preq, *preq_temp, *cur_preq; + struct cdns2_endpoint *pep; + struct cdns2_trb *link_trb; + u8 req_on_hw_ring = 0; + unsigned long flags; + u32 buffer; + int val, i; + + if (!ep || !request || !ep->desc) + return -EINVAL; + + pep = ep_to_cdns2_ep(ep); + if (!pep->endpoint.desc) { + dev_err(pep->pdev->dev, "%s: can't dequeue to disabled endpoint\n", + pep->name); + return -ESHUTDOWN; + } + + /* Requests has been dequeued during disabling endpoint. */ + if (!(pep->ep_state & EP_ENABLED)) + return 0; + + spin_lock_irqsave(&pep->pdev->lock, flags); + + cur_preq = to_cdns2_request(request); + trace_cdns2_request_dequeue(cur_preq); + + list_for_each_entry_safe(preq, preq_temp, &pep->pending_list, list) { + if (cur_preq == preq) { + req_on_hw_ring = 1; + goto found; + } + } + + list_for_each_entry_safe(preq, preq_temp, &pep->deferred_list, list) { + if (cur_preq == preq) + goto found; + } + + goto not_found; + +found: + link_trb = preq->trb; + + /* Update ring only if removed request is on pending_req_list list. */ + if (req_on_hw_ring && link_trb) { + /* Stop DMA */ + writel(DMA_EP_CMD_DFLUSH, &pep->pdev->adma_regs->ep_cmd); + + /* Wait for DFLUSH cleared. */ + readl_poll_timeout_atomic(&pep->pdev->adma_regs->ep_cmd, val, + !(val & DMA_EP_CMD_DFLUSH), 1, 1000); + + buffer = cpu_to_le32(TRB_BUFFER(pep->ring.dma + + ((preq->end_trb + 1) * TRB_SIZE))); + + for (i = 0; i < preq->num_of_trb; i++) { + link_trb->buffer = buffer; + link_trb->control = cpu_to_le32((le32_to_cpu(link_trb->control) + & TRB_CYCLE) | TRB_CHAIN | + TRB_TYPE(TRB_LINK)); + + trace_cdns2_queue_trb(pep, link_trb); + link_trb = cdns2_next_trb(pep, link_trb); + } + + if (pep->wa1_trb == preq->trb) + cdns2_wa1_restore_cycle_bit(pep); + } + + cdns2_gadget_giveback(pep, cur_preq, -ECONNRESET); + + preq = cdns2_next_preq(&pep->pending_list); + if (preq) + cdns2_rearm_transfer(pep, 1); + +not_found: + spin_unlock_irqrestore(&pep->pdev->lock, flags); + return 0; +} + +int cdns2_halt_endpoint(struct cdns2_device *pdev, + struct cdns2_endpoint *pep, + int value) +{ + u8 __iomem *conf; + int dir = 0; + + if (!(pep->ep_state & EP_ENABLED)) + return -EPERM; + + if (pep->dir) { + dir = ENDPRST_IO_TX; + conf = &pdev->epx_regs->ep[pep->num - 1].txcon; + } else { + conf = &pdev->epx_regs->ep[pep->num - 1].rxcon; + } + + if (!value) { + struct cdns2_trb *trb = NULL; + struct cdns2_request *preq; + struct cdns2_trb trb_tmp; + + preq = cdns2_next_preq(&pep->pending_list); + if (preq) { + trb = preq->trb; + if (trb) { + trb_tmp = *trb; + trb->control = trb->control ^ cpu_to_le32(TRB_CYCLE); + } + } + + trace_cdns2_ep_halt(pep, 0, 0); + + /* Resets Sequence Number */ + writeb(dir | pep->num, &pdev->epx_regs->endprst); + writeb(dir | ENDPRST_TOGRST | pep->num, + &pdev->epx_regs->endprst); + + clear_reg_bit_8(conf, EPX_CON_STALL); + + pep->ep_state &= ~(EP_STALLED | EP_STALL_PENDING); + + if (preq) { + if (trb) + *trb = trb_tmp; + + cdns2_rearm_transfer(pep, 1); + } + + cdns2_start_all_request(pdev, pep); + } else { + trace_cdns2_ep_halt(pep, 1, 0); + set_reg_bit_8(conf, EPX_CON_STALL); + writeb(dir | pep->num, &pdev->epx_regs->endprst); + writeb(dir | ENDPRST_FIFORST | pep->num, + &pdev->epx_regs->endprst); + pep->ep_state |= EP_STALLED; + } + + return 0; +} + +/* Sets/clears stall on selected endpoint. */ +static int cdns2_gadget_ep_set_halt(struct usb_ep *ep, int value) +{ + struct cdns2_endpoint *pep = ep_to_cdns2_ep(ep); + struct cdns2_device *pdev = pep->pdev; + struct cdns2_request *preq; + unsigned long flags = 0; + int ret; + + spin_lock_irqsave(&pdev->lock, flags); + + preq = cdns2_next_preq(&pep->pending_list); + if (value && preq) { + trace_cdns2_ep_busy_try_halt_again(pep); + ret = -EAGAIN; + goto done; + } + + if (!value) + pep->ep_state &= ~EP_WEDGE; + + ret = cdns2_halt_endpoint(pdev, pep, value); + +done: + spin_unlock_irqrestore(&pdev->lock, flags); + return ret; +} + +static int cdns2_gadget_ep_set_wedge(struct usb_ep *ep) +{ + struct cdns2_endpoint *pep = ep_to_cdns2_ep(ep); + + cdns2_gadget_ep_set_halt(ep, 1); + pep->ep_state |= EP_WEDGE; + + return 0; +} + +static struct +cdns2_endpoint *cdns2_find_available_ep(struct cdns2_device *pdev, + struct usb_endpoint_descriptor *desc) +{ + struct cdns2_endpoint *pep; + struct usb_ep *ep; + int ep_correct; + + list_for_each_entry(ep, &pdev->gadget.ep_list, ep_list) { + unsigned long num; + int ret; + /* ep name pattern likes epXin or epXout. */ + char c[2] = {ep->name[2], '\0'}; + + ret = kstrtoul(c, 10, &num); + if (ret) + return ERR_PTR(ret); + pep = ep_to_cdns2_ep(ep); + + if (pep->num != num) + continue; + + ep_correct = (pep->endpoint.caps.dir_in && + usb_endpoint_dir_in(desc)) || + (pep->endpoint.caps.dir_out && + usb_endpoint_dir_out(desc)); + + if (ep_correct && !(pep->ep_state & EP_CLAIMED)) + return pep; + } + + return ERR_PTR(-ENOENT); +} + +/* + * Function used to recognize which endpoints will be used to optimize + * on-chip memory usage. + */ +static struct +usb_ep *cdns2_gadget_match_ep(struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *comp_desc) +{ + struct cdns2_device *pdev = gadget_to_cdns2_device(gadget); + struct cdns2_endpoint *pep; + unsigned long flags; + + pep = cdns2_find_available_ep(pdev, desc); + if (IS_ERR(pep)) { + dev_err(pdev->dev, "no available ep\n"); + return NULL; + } + + spin_lock_irqsave(&pdev->lock, flags); + + if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_ISOC) + pep->buffering = 4; + else + pep->buffering = 1; + + pep->ep_state |= EP_CLAIMED; + spin_unlock_irqrestore(&pdev->lock, flags); + + return &pep->endpoint; +} + +static const struct usb_ep_ops cdns2_gadget_ep_ops = { + .enable = cdns2_gadget_ep_enable, + .disable = cdns2_gadget_ep_disable, + .alloc_request = cdns2_gadget_ep_alloc_request, + .free_request = cdns2_gadget_ep_free_request, + .queue = cdns2_gadget_ep_queue, + .dequeue = cdns2_gadget_ep_dequeue, + .set_halt = cdns2_gadget_ep_set_halt, + .set_wedge = cdns2_gadget_ep_set_wedge, +}; + +static int cdns2_gadget_get_frame(struct usb_gadget *gadget) +{ + struct cdns2_device *pdev = gadget_to_cdns2_device(gadget); + + return readw(&pdev->usb_regs->frmnr); +} + +static int cdns2_gadget_wakeup(struct usb_gadget *gadget) +{ + struct cdns2_device *pdev = gadget_to_cdns2_device(gadget); + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + cdns2_wakeup(pdev); + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdns2_gadget_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct cdns2_device *pdev = gadget_to_cdns2_device(gadget); + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + pdev->is_selfpowered = !!is_selfpowered; + spin_unlock_irqrestore(&pdev->lock, flags); + return 0; +} + +/* Disable interrupts and begin the controller halting process. */ +static void cdns2_quiesce(struct cdns2_device *pdev) +{ + set_reg_bit_8(&pdev->usb_regs->usbcs, USBCS_DISCON); + + /* Disable interrupt. */ + writeb(0, &pdev->interrupt_regs->extien); + writeb(0, &pdev->interrupt_regs->usbien); + writew(0, &pdev->adma_regs->ep_ien); + + /* Clear interrupt line. */ + writeb(0x0, &pdev->interrupt_regs->usbirq); +} + +static void cdns2_gadget_config(struct cdns2_device *pdev) +{ + cdns2_ep0_config(pdev); + + /* Enable DMA interrupts for all endpoints. */ + writel(~0x0, &pdev->adma_regs->ep_ien); + cdns2_enable_l1(pdev, 0); + writeb(USB_IEN_INIT, &pdev->interrupt_regs->usbien); + writeb(EXTIRQ_WAKEUP, &pdev->interrupt_regs->extien); + writel(DMA_CONF_DMULT, &pdev->adma_regs->conf); +} + +static int cdns2_gadget_pullup(struct usb_gadget *gadget, int is_on) +{ + struct cdns2_device *pdev = gadget_to_cdns2_device(gadget); + unsigned long flags; + + trace_cdns2_pullup(is_on); + + /* + * Disable events handling while controller is being + * enabled/disabled. + */ + disable_irq(pdev->irq); + spin_lock_irqsave(&pdev->lock, flags); + + if (is_on) { + cdns2_gadget_config(pdev); + clear_reg_bit_8(&pdev->usb_regs->usbcs, USBCS_DISCON); + } else { + cdns2_quiesce(pdev); + } + + spin_unlock_irqrestore(&pdev->lock, flags); + enable_irq(pdev->irq); + + return 0; +} + +static int cdns2_gadget_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct cdns2_device *pdev = gadget_to_cdns2_device(gadget); + enum usb_device_speed max_speed = driver->max_speed; + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + pdev->gadget_driver = driver; + + /* Limit speed if necessary. */ + max_speed = min(driver->max_speed, gadget->max_speed); + + switch (max_speed) { + case USB_SPEED_FULL: + writeb(SPEEDCTRL_HSDISABLE, &pdev->usb_regs->speedctrl); + break; + case USB_SPEED_HIGH: + writeb(0, &pdev->usb_regs->speedctrl); + break; + default: + dev_err(pdev->dev, "invalid maximum_speed parameter %d\n", + max_speed); + fallthrough; + case USB_SPEED_UNKNOWN: + /* Default to highspeed. */ + max_speed = USB_SPEED_HIGH; + break; + } + + /* Reset all USB endpoints. */ + writeb(ENDPRST_IO_TX, &pdev->usb_regs->endprst); + writeb(ENDPRST_FIFORST | ENDPRST_TOGRST | ENDPRST_IO_TX, + &pdev->usb_regs->endprst); + writeb(ENDPRST_FIFORST | ENDPRST_TOGRST, &pdev->usb_regs->endprst); + + cdns2_eps_onchip_buffer_init(pdev); + + cdns2_gadget_config(pdev); + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +static int cdns2_gadget_udc_stop(struct usb_gadget *gadget) +{ + struct cdns2_device *pdev = gadget_to_cdns2_device(gadget); + struct cdns2_endpoint *pep; + u32 bEndpointAddress; + struct usb_ep *ep; + int val; + + pdev->gadget_driver = NULL; + pdev->gadget.speed = USB_SPEED_UNKNOWN; + + list_for_each_entry(ep, &pdev->gadget.ep_list, ep_list) { + pep = ep_to_cdns2_ep(ep); + bEndpointAddress = pep->num | pep->dir; + cdns2_select_ep(pdev, bEndpointAddress); + writel(DMA_EP_CMD_EPRST, &pdev->adma_regs->ep_cmd); + readl_poll_timeout_atomic(&pdev->adma_regs->ep_cmd, val, + !(val & DMA_EP_CMD_EPRST), 1, 100); + } + + cdns2_quiesce(pdev); + + writeb(ENDPRST_IO_TX, &pdev->usb_regs->endprst); + writeb(ENDPRST_FIFORST | ENDPRST_TOGRST | ENDPRST_IO_TX, + &pdev->epx_regs->endprst); + writeb(ENDPRST_FIFORST | ENDPRST_TOGRST, &pdev->epx_regs->endprst); + + return 0; +} + +static const struct usb_gadget_ops cdns2_gadget_ops = { + .get_frame = cdns2_gadget_get_frame, + .wakeup = cdns2_gadget_wakeup, + .set_selfpowered = cdns2_gadget_set_selfpowered, + .pullup = cdns2_gadget_pullup, + .udc_start = cdns2_gadget_udc_start, + .udc_stop = cdns2_gadget_udc_stop, + .match_ep = cdns2_gadget_match_ep, +}; + +static void cdns2_free_all_eps(struct cdns2_device *pdev) +{ + int i; + + for (i = 0; i < CDNS2_ENDPOINTS_NUM; i++) + cdns2_free_tr_segment(&pdev->eps[i]); +} + +/* Initializes software endpoints of gadget. */ +static int cdns2_init_eps(struct cdns2_device *pdev) +{ + struct cdns2_endpoint *pep; + int i; + + for (i = 0; i < CDNS2_ENDPOINTS_NUM; i++) { + bool direction = !(i & 1); /* Start from OUT endpoint. */ + u8 epnum = ((i + 1) >> 1); + + /* + * Endpoints are being held in pdev->eps[] in form: + * ep0, ep1out, ep1in ... ep15out, ep15in. + */ + if (!CDNS2_IF_EP_EXIST(pdev, epnum, direction)) + continue; + + pep = &pdev->eps[i]; + pep->pdev = pdev; + pep->num = epnum; + /* 0 for OUT, 1 for IN. */ + pep->dir = direction ? USB_DIR_IN : USB_DIR_OUT; + pep->idx = i; + + /* Ep0in and ep0out are represented by pdev->eps[0]. */ + if (!epnum) { + int ret; + + snprintf(pep->name, sizeof(pep->name), "ep%d%s", + epnum, "BiDir"); + + cdns2_init_ep0(pdev, pep); + + ret = cdns2_alloc_tr_segment(pep); + if (ret) { + dev_err(pdev->dev, "Failed to init ep0\n"); + return ret; + } + } else { + snprintf(pep->name, sizeof(pep->name), "ep%d%s", + epnum, !!direction ? "in" : "out"); + pep->endpoint.name = pep->name; + + usb_ep_set_maxpacket_limit(&pep->endpoint, 1024); + pep->endpoint.ops = &cdns2_gadget_ep_ops; + list_add_tail(&pep->endpoint.ep_list, &pdev->gadget.ep_list); + + pep->endpoint.caps.dir_in = direction; + pep->endpoint.caps.dir_out = !direction; + + pep->endpoint.caps.type_iso = 1; + pep->endpoint.caps.type_bulk = 1; + pep->endpoint.caps.type_int = 1; + } + + pep->endpoint.name = pep->name; + pep->ep_state = 0; + + dev_dbg(pdev->dev, "Init %s, SupType: CTRL: %s, INT: %s, " + "BULK: %s, ISOC %s, SupDir IN: %s, OUT: %s\n", + pep->name, + str_yes_no(pep->endpoint.caps.type_control), + str_yes_no(pep->endpoint.caps.type_int), + str_yes_no(pep->endpoint.caps.type_bulk), + str_yes_no(pep->endpoint.caps.type_iso), + str_yes_no(pep->endpoint.caps.dir_in), + str_yes_no(pep->endpoint.caps.dir_out)); + + INIT_LIST_HEAD(&pep->pending_list); + INIT_LIST_HEAD(&pep->deferred_list); + } + + return 0; +} + +static int cdns2_gadget_start(struct cdns2_device *pdev) +{ + u32 max_speed; + void *buf; + int ret; + + pdev->usb_regs = pdev->regs; + pdev->ep0_regs = pdev->regs; + pdev->epx_regs = pdev->regs; + pdev->interrupt_regs = pdev->regs; + pdev->adma_regs = pdev->regs + CDNS2_ADMA_REGS_OFFSET; + + /* Reset controller. */ + writeb(CPUCTRL_SW_RST | CPUCTRL_UPCLK | CPUCTRL_WUEN, + &pdev->usb_regs->cpuctrl); + usleep_range(5, 10); + + usb_initialize_gadget(pdev->dev, &pdev->gadget, NULL); + + device_property_read_u16(pdev->dev, "cdns,on-chip-tx-buff-size", + &pdev->onchip_tx_buf); + device_property_read_u16(pdev->dev, "cdns,on-chip-rx-buff-size", + &pdev->onchip_rx_buf); + device_property_read_u32(pdev->dev, "cdns,avail-endpoints", + &pdev->eps_supported); + + /* + * Driver assumes that each USBHS controller has at least + * one IN and one OUT non control endpoint. + */ + if (!pdev->onchip_tx_buf && !pdev->onchip_rx_buf) { + ret = -EINVAL; + dev_err(pdev->dev, "Invalid on-chip memory configuration\n"); + goto put_gadget; + } + + if (!(pdev->eps_supported & ~0x00010001)) { + ret = -EINVAL; + dev_err(pdev->dev, "No hardware endpoints available\n"); + goto put_gadget; + } + + max_speed = usb_get_maximum_speed(pdev->dev); + + switch (max_speed) { + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + break; + default: + dev_err(pdev->dev, "invalid maximum_speed parameter %d\n", + max_speed); + fallthrough; + case USB_SPEED_UNKNOWN: + max_speed = USB_SPEED_HIGH; + break; + } + + pdev->gadget.max_speed = max_speed; + pdev->gadget.speed = USB_SPEED_UNKNOWN; + pdev->gadget.ops = &cdns2_gadget_ops; + pdev->gadget.name = "usbhs-gadget"; + pdev->gadget.quirk_avoids_skb_reserve = 1; + pdev->gadget.irq = pdev->irq; + + spin_lock_init(&pdev->lock); + INIT_WORK(&pdev->pending_status_wq, cdns2_pending_setup_status_handler); + + /* Initialize endpoint container. */ + INIT_LIST_HEAD(&pdev->gadget.ep_list); + pdev->eps_dma_pool = dma_pool_create("cdns2_eps_dma_pool", pdev->dev, + TR_SEG_SIZE, 8, 0); + if (!pdev->eps_dma_pool) { + dev_err(pdev->dev, "Failed to create TRB dma pool\n"); + ret = -ENOMEM; + goto put_gadget; + } + + ret = cdns2_init_eps(pdev); + if (ret) { + dev_err(pdev->dev, "Failed to create endpoints\n"); + goto destroy_dma_pool; + } + + pdev->gadget.sg_supported = 1; + + pdev->zlp_buf = kzalloc(CDNS2_EP_ZLP_BUF_SIZE, GFP_KERNEL); + if (!pdev->zlp_buf) { + ret = -ENOMEM; + goto destroy_dma_pool; + } + + /* Allocate memory for setup packet buffer. */ + buf = dma_alloc_coherent(pdev->dev, 8, &pdev->ep0_preq.request.dma, + GFP_DMA); + pdev->ep0_preq.request.buf = buf; + + if (!pdev->ep0_preq.request.buf) { + ret = -ENOMEM; + goto free_zlp_buf; + } + + /* Add USB gadget device. */ + ret = usb_add_gadget(&pdev->gadget); + if (ret < 0) { + dev_err(pdev->dev, "Failed to add gadget\n"); + goto free_ep0_buf; + } + + return 0; + +free_ep0_buf: + dma_free_coherent(pdev->dev, 8, pdev->ep0_preq.request.buf, + pdev->ep0_preq.request.dma); +free_zlp_buf: + kfree(pdev->zlp_buf); +destroy_dma_pool: + dma_pool_destroy(pdev->eps_dma_pool); +put_gadget: + usb_put_gadget(&pdev->gadget); + + return ret; +} + +int cdns2_gadget_suspend(struct cdns2_device *pdev) +{ + unsigned long flags; + + cdns2_disconnect_gadget(pdev); + + spin_lock_irqsave(&pdev->lock, flags); + pdev->gadget.speed = USB_SPEED_UNKNOWN; + + trace_cdns2_device_state("notattached"); + usb_gadget_set_state(&pdev->gadget, USB_STATE_NOTATTACHED); + cdns2_enable_l1(pdev, 0); + + /* Disable interrupt for device. */ + writeb(0, &pdev->interrupt_regs->usbien); + writel(0, &pdev->adma_regs->ep_ien); + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +int cdns2_gadget_resume(struct cdns2_device *pdev, bool hibernated) +{ + unsigned long flags; + + spin_lock_irqsave(&pdev->lock, flags); + + if (!pdev->gadget_driver) { + spin_unlock_irqrestore(&pdev->lock, flags); + return 0; + } + + cdns2_gadget_config(pdev); + + if (hibernated) + clear_reg_bit_8(&pdev->usb_regs->usbcs, USBCS_DISCON); + + spin_unlock_irqrestore(&pdev->lock, flags); + + return 0; +} + +void cdns2_gadget_remove(struct cdns2_device *pdev) +{ + pm_runtime_put_autosuspend(pdev->dev); + + usb_del_gadget(&pdev->gadget); + cdns2_free_all_eps(pdev); + + dma_pool_destroy(pdev->eps_dma_pool); + kfree(pdev->zlp_buf); + usb_put_gadget(&pdev->gadget); +} + +int cdns2_gadget_init(struct cdns2_device *pdev) +{ + int ret; + + /* Ensure 32-bit DMA Mask. */ + ret = dma_set_mask_and_coherent(pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(pdev->dev, "Failed to set dma mask: %d\n", ret); + return ret; + } + + pm_runtime_get_sync(pdev->dev); + + cdsn2_isoc_burst_opt(pdev); + + ret = cdns2_gadget_start(pdev); + if (ret) { + pm_runtime_put_sync(pdev->dev); + return ret; + } + + /* + * Because interrupt line can be shared with other components in + * driver it can't use IRQF_ONESHOT flag here. + */ + ret = devm_request_threaded_irq(pdev->dev, pdev->irq, + cdns2_usb_irq_handler, + cdns2_thread_irq_handler, + IRQF_SHARED, + dev_name(pdev->dev), + pdev); + if (ret) + goto err0; + + return 0; + +err0: + cdns2_gadget_remove(pdev); + + return ret; +} diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-gadget.h b/drivers/usb/gadget/udc/cdns2/cdns2-gadget.h new file mode 100644 index 000000000..b5d5ec12e --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/cdns2-gadget.h @@ -0,0 +1,716 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * USBHS-DEV device controller driver header file + * + * Copyright (C) 2023 Cadence. + * + * Author: Pawel Laszczak + */ + +#ifndef __LINUX_CDNS2_GADGET +#define __LINUX_CDNS2_GADGET + +#include +#include + +/* + * USBHS register interface. + * This corresponds to the USBHS Device Controller Interface. + */ + +/** + * struct cdns2_ep0_regs - endpoint 0 related registers. + * @rxbc: receive (OUT) 0 endpoint byte count register. + * @txbc: transmit (IN) 0 endpoint byte count register. + * @cs: 0 endpoint control and status register. + * @reserved1: reserved. + * @fifo: 0 endpoint fifo register. + * @reserved2: reserved. + * @setupdat: SETUP data register. + * @reserved4: reserved. + * @maxpack: 0 endpoint max packet size. + */ +struct cdns2_ep0_regs { + __u8 rxbc; + __u8 txbc; + __u8 cs; + __u8 reserved1[4]; + __u8 fifo; + __le32 reserved2[94]; + __u8 setupdat[8]; + __u8 reserved4[88]; + __u8 maxpack; +} __packed __aligned(4); + +/* EP0CS - bitmasks. */ +/* Endpoint 0 stall bit for status stage. */ +#define EP0CS_STALL BIT(0) +/* HSNAK bit. */ +#define EP0CS_HSNAK BIT(1) +/* IN 0 endpoint busy bit. */ +#define EP0CS_TXBSY_MSK BIT(2) +/* OUT 0 endpoint busy bit. */ +#define EP0CS_RXBSY_MSK BIT(3) +/* Send STALL in the data stage phase. */ +#define EP0CS_DSTALL BIT(4) +/* SETUP buffer content was changed. */ +#define EP0CS_CHGSET BIT(7) + +/* EP0FIFO - bitmasks. */ +/* Direction. */ +#define EP0_FIFO_IO_TX BIT(4) +/* FIFO auto bit. */ +#define EP0_FIFO_AUTO BIT(5) +/* FIFO commit bit. */ +#define EP0_FIFO_COMMIT BIT(6) +/* FIFO access bit. */ +#define EP0_FIFO_ACCES BIT(7) + +/** + * struct cdns2_epx_base - base endpoint registers. + * @rxbc: OUT endpoint byte count register. + * @rxcon: OUT endpoint control register. + * @rxcs: OUT endpoint control and status register. + * @txbc: IN endpoint byte count register. + * @txcon: IN endpoint control register. + * @txcs: IN endpoint control and status register. + */ +struct cdns2_epx_base { + __le16 rxbc; + __u8 rxcon; + __u8 rxcs; + __le16 txbc; + __u8 txcon; + __u8 txcs; +} __packed __aligned(4); + +/* rxcon/txcon - endpoint control register bitmasks. */ +/* Endpoint buffering: 0 - single buffering ... 3 - quad buffering. */ +#define EPX_CON_BUF GENMASK(1, 0) +/* Endpoint type. */ +#define EPX_CON_TYPE GENMASK(3, 2) +/* Endpoint type: isochronous. */ +#define EPX_CON_TYPE_ISOC 0x4 +/* Endpoint type: bulk. */ +#define EPX_CON_TYPE_BULK 0x8 +/* Endpoint type: interrupt. */ +#define EPX_CON_TYPE_INT 0xC +/* Number of packets per microframe. */ +#define EPX_CON_ISOD GENMASK(5, 4) +#define EPX_CON_ISOD_SHIFT 0x4 +/* Endpoint stall bit. */ +#define EPX_CON_STALL BIT(6) +/* Endpoint enable bit.*/ +#define EPX_CON_VAL BIT(7) + +/* rxcs/txcs - endpoint control and status bitmasks. */ +/* Data sequence error for the ISO endpoint. */ +#define EPX_CS_ERR(p) ((p) & BIT(0)) + +/** + * struct cdns2_epx_regs - endpoint 1..15 related registers. + * @reserved: reserved. + * @ep: none control endpoints array. + * @reserved2: reserved. + * @endprst: endpoint reset register. + * @reserved3: reserved. + * @isoautoarm: ISO auto-arm register. + * @reserved4: reserved. + * @isodctrl: ISO control register. + * @reserved5: reserved. + * @isoautodump: ISO auto dump enable register. + * @reserved6: reserved. + * @rxmaxpack: receive (OUT) Max packet size register. + * @reserved7: reserved. + * @rxstaddr: receive (OUT) start address endpoint buffer register. + * @reserved8: reserved. + * @txstaddr: transmit (IN) start address endpoint buffer register. + * @reserved9: reserved. + * @txmaxpack: transmit (IN) Max packet size register. + */ +struct cdns2_epx_regs { + __le32 reserved[2]; + struct cdns2_epx_base ep[15]; + __u8 reserved2[290]; + __u8 endprst; + __u8 reserved3[41]; + __le16 isoautoarm; + __u8 reserved4[10]; + __le16 isodctrl; + __le16 reserved5; + __le16 isoautodump; + __le32 reserved6; + __le16 rxmaxpack[15]; + __le32 reserved7[65]; + __le32 rxstaddr[15]; + __u8 reserved8[4]; + __le32 txstaddr[15]; + __u8 reserved9[98]; + __le16 txmaxpack[15]; +} __packed __aligned(4); + +/* ENDPRST - bitmasks. */ +/* Endpoint number. */ +#define ENDPRST_EP GENMASK(3, 0) +/* IN direction bit. */ +#define ENDPRST_IO_TX BIT(4) +/* Toggle reset bit. */ +#define ENDPRST_TOGRST BIT(5) +/* FIFO reset bit. */ +#define ENDPRST_FIFORST BIT(6) +/* Toggle status and reset bit. */ +#define ENDPRST_TOGSETQ BIT(7) + +/** + * struct cdns2_interrupt_regs - USB interrupt related registers. + * @reserved: reserved. + * @usbirq: USB interrupt request register. + * @extirq: external interrupt request register. + * @rxpngirq: external interrupt request register. + * @reserved1: reserved. + * @usbien: USB interrupt enable register. + * @extien: external interrupt enable register. + * @reserved2: reserved. + * @usbivect: USB interrupt vector register. + */ +struct cdns2_interrupt_regs { + __u8 reserved[396]; + __u8 usbirq; + __u8 extirq; + __le16 rxpngirq; + __le16 reserved1[4]; + __u8 usbien; + __u8 extien; + __le16 reserved2[3]; + __u8 usbivect; +} __packed __aligned(4); + +/* EXTIRQ and EXTIEN - bitmasks. */ +/* VBUS fault fall interrupt. */ +#define EXTIRQ_VBUSFAULT_FALL BIT(0) +/* VBUS fault fall interrupt. */ +#define EXTIRQ_VBUSFAULT_RISE BIT(1) +/* Wake up interrupt bit. */ +#define EXTIRQ_WAKEUP BIT(7) + +/* USBIEN and USBIRQ - bitmasks. */ +/* SETUP data valid interrupt bit.*/ +#define USBIRQ_SUDAV BIT(0) +/* Start-of-frame interrupt bit. */ +#define USBIRQ_SOF BIT(1) +/* SETUP token interrupt bit. */ +#define USBIRQ_SUTOK BIT(2) +/* USB suspend interrupt bit. */ +#define USBIRQ_SUSPEND BIT(3) +/* USB reset interrupt bit. */ +#define USBIRQ_URESET BIT(4) +/* USB high-speed mode interrupt bit. */ +#define USBIRQ_HSPEED BIT(5) +/* Link Power Management interrupt bit. */ +#define USBIRQ_LPM BIT(7) + +#define USB_IEN_INIT (USBIRQ_SUDAV | USBIRQ_SUSPEND | USBIRQ_URESET \ + | USBIRQ_HSPEED | USBIRQ_LPM) +/** + * struct cdns2_usb_regs - USB controller registers. + * @reserved: reserved. + * @lpmctrl: LPM control register. + * @lpmclock: LPM clock register. + * @reserved2: reserved. + * @endprst: endpoint reset register. + * @usbcs: USB control and status register. + * @frmnr: USB frame counter register. + * @fnaddr: function Address register. + * @clkgate: clock gate register. + * @fifoctrl: FIFO control register. + * @speedctrl: speed Control register. + * @sleep_clkgate: sleep Clock Gate register. + * @reserved3: reserved. + * @cpuctrl: microprocessor control register. + */ +struct cdns2_usb_regs { + __u8 reserved[4]; + __u16 lpmctrl; + __u8 lpmclock; + __u8 reserved2[411]; + __u8 endprst; + __u8 usbcs; + __le16 frmnr; + __u8 fnaddr; + __u8 clkgate; + __u8 fifoctrl; + __u8 speedctrl; + __u8 sleep_clkgate; + __u8 reserved3[533]; + __u8 cpuctrl; +} __packed __aligned(4); + +/* LPMCTRL - bitmasks. */ +/* BESL (Best Effort Service Latency). */ +#define LPMCTRLLL_HIRD GENMASK(7, 4) +/* Last received Remote Wakeup field from LPM Extended Token packet. */ +#define LPMCTRLLH_BREMOTEWAKEUP BIT(8) +/* Reflects value of the lpmnyet bit located in the usbcs[1] register. */ +#define LPMCTRLLH_LPMNYET BIT(16) + +/* LPMCLOCK - bitmasks. */ +/* + * If bit is 1 the controller automatically turns off clock + * (utmisleepm goes to low), else the microprocessor should use + * sleep clock gate register to turn off clock. + */ +#define LPMCLOCK_SLEEP_ENTRY BIT(7) + +/* USBCS - bitmasks. */ +/* Send NYET handshake for the LPM transaction. */ +#define USBCS_LPMNYET BIT(2) +/* Remote wake-up bit. */ +#define USBCS_SIGRSUME BIT(5) +/* Software disconnect bit. */ +#define USBCS_DISCON BIT(6) +/* Indicates that a wakeup pin resumed the controller. */ +#define USBCS_WAKESRC BIT(7) + +/* FIFOCTRL - bitmasks. */ +/* Endpoint number. */ +#define FIFOCTRL_EP GENMASK(3, 0) +/* Direction bit. */ +#define FIFOCTRL_IO_TX BIT(4) +/* FIFO auto bit. */ +#define FIFOCTRL_FIFOAUTO BIT(5) +/* FIFO commit bit. */ +#define FIFOCTRL_FIFOCMIT BIT(6) +/* FIFO access bit. */ +#define FIFOCTRL_FIFOACC BIT(7) + +/* SPEEDCTRL - bitmasks. */ +/* Device works in Full Speed. */ +#define SPEEDCTRL_FS BIT(1) +/* Device works in High Speed. */ +#define SPEEDCTRL_HS BIT(2) +/* Force FS mode. */ +#define SPEEDCTRL_HSDISABLE BIT(7) + +/* CPUCTRL- bitmasks. */ +/* UP clock enable */ +#define CPUCTRL_UPCLK BIT(0) +/* Controller reset bit. */ +#define CPUCTRL_SW_RST BIT(1) +/** + * If the wuen bit is ‘1’, the upclken is automatically set to ‘1’ after + * detecting rising edge of wuintereq interrupt. If the wuen bit is ‘0’, + * the wuintereq interrupt is ignored. + */ +#define CPUCTRL_WUEN BIT(7) + + +/** + * struct cdns2_adma_regs - ADMA controller registers. + * @conf: DMA global configuration register. + * @sts: DMA global Status register. + * @reserved1: reserved. + * @ep_sel: DMA endpoint select register. + * @ep_traddr: DMA endpoint transfer ring address register. + * @ep_cfg: DMA endpoint configuration register. + * @ep_cmd: DMA endpoint command register. + * @ep_sts: DMA endpoint status register. + * @reserved2: reserved. + * @ep_sts_en: DMA endpoint status enable register. + * @drbl: DMA doorbell register. + * @ep_ien: DMA endpoint interrupt enable register. + * @ep_ists: DMA endpoint interrupt status register. + * @axim_ctrl: AXI Master Control register. + * @axim_id: AXI Master ID register. + * @reserved3: reserved. + * @axim_cap: AXI Master Wrapper Extended Capability. + * @reserved4: reserved. + * @axim_ctrl0: AXI Master Wrapper Extended Capability Control Register 0. + * @axim_ctrl1: AXI Master Wrapper Extended Capability Control Register 1. + */ +struct cdns2_adma_regs { + __le32 conf; + __le32 sts; + __le32 reserved1[5]; + __le32 ep_sel; + __le32 ep_traddr; + __le32 ep_cfg; + __le32 ep_cmd; + __le32 ep_sts; + __le32 reserved2; + __le32 ep_sts_en; + __le32 drbl; + __le32 ep_ien; + __le32 ep_ists; + __le32 axim_ctrl; + __le32 axim_id; + __le32 reserved3; + __le32 axim_cap; + __le32 reserved4; + __le32 axim_ctrl0; + __le32 axim_ctrl1; +}; + +#define CDNS2_ADMA_REGS_OFFSET 0x400 + +/* DMA_CONF - bitmasks. */ +/* Reset USB device configuration. */ +#define DMA_CONF_CFGRST BIT(0) +/* Singular DMA transfer mode.*/ +#define DMA_CONF_DSING BIT(8) +/* Multiple DMA transfers mode.*/ +#define DMA_CONF_DMULT BIT(9) + +/* DMA_EP_CFG - bitmasks. */ +/* Endpoint enable. */ +#define DMA_EP_CFG_ENABLE BIT(0) + +/* DMA_EP_CMD - bitmasks. */ +/* Endpoint reset. */ +#define DMA_EP_CMD_EPRST BIT(0) +/* Transfer descriptor ready. */ +#define DMA_EP_CMD_DRDY BIT(6) +/* Data flush. */ +#define DMA_EP_CMD_DFLUSH BIT(7) + +/* DMA_EP_STS - bitmasks. */ +/* Interrupt On Complete. */ +#define DMA_EP_STS_IOC BIT(2) +/* Interrupt on Short Packet. */ +#define DMA_EP_STS_ISP BIT(3) +/* Transfer descriptor missing. */ +#define DMA_EP_STS_DESCMIS BIT(4) +/* TRB error. */ +#define DMA_EP_STS_TRBERR BIT(7) +/* DMA busy bit. */ +#define DMA_EP_STS_DBUSY BIT(9) +/* Current Cycle Status. */ +#define DMA_EP_STS_CCS(p) ((p) & BIT(11)) +/* OUT size mismatch. */ +#define DMA_EP_STS_OUTSMM BIT(14) +/* ISO transmission error. */ +#define DMA_EP_STS_ISOERR BIT(15) + +/* DMA_EP_STS_EN - bitmasks. */ +/* OUT transfer missing descriptor enable. */ +#define DMA_EP_STS_EN_DESCMISEN BIT(4) +/* TRB enable. */ +#define DMA_EP_STS_EN_TRBERREN BIT(7) +/* OUT size mismatch enable. */ +#define DMA_EP_STS_EN_OUTSMMEN BIT(14) +/* ISO transmission error enable. */ +#define DMA_EP_STS_EN_ISOERREN BIT(15) + +/* DMA_EP_IEN - bitmasks. */ +#define DMA_EP_IEN(index) (1 << (index)) +#define DMA_EP_IEN_EP_OUT0 BIT(0) +#define DMA_EP_IEN_EP_IN0 BIT(16) + +/* DMA_EP_ISTS - bitmasks. */ +#define DMA_EP_ISTS(index) (1 << (index)) +#define DMA_EP_ISTS_EP_OUT0 BIT(0) +#define DMA_EP_ISTS_EP_IN0 BIT(16) + +#define gadget_to_cdns2_device(g) (container_of(g, struct cdns2_device, gadget)) +#define ep_to_cdns2_ep(ep) (container_of(ep, struct cdns2_endpoint, endpoint)) + +/*-------------------------------------------------------------------------*/ +#define TRBS_PER_SEGMENT 600 +#define ISO_MAX_INTERVAL 8 +#define MAX_TRB_LENGTH BIT(16) +#define MAX_ISO_SIZE 3076 +/* + * To improve performance the TRB buffer pointers can't cross + * 4KB boundaries. + */ +#define TRB_MAX_ISO_BUFF_SHIFT 12 +#define TRB_MAX_ISO_BUFF_SIZE BIT(TRB_MAX_ISO_BUFF_SHIFT) +/* How much data is left before the 4KB boundary? */ +#define TRB_BUFF_LEN_UP_TO_BOUNDARY(addr) (TRB_MAX_ISO_BUFF_SIZE - \ + ((addr) & (TRB_MAX_ISO_BUFF_SIZE - 1))) + +#if TRBS_PER_SEGMENT < 2 +#error "Incorrect TRBS_PER_SEGMENT. Minimal Transfer Ring size is 2." +#endif + +/** + * struct cdns2_trb - represent Transfer Descriptor block. + * @buffer: pointer to buffer data. + * @length: length of data. + * @control: control flags. + * + * This structure describes transfer block handled by DMA module. + */ +struct cdns2_trb { + __le32 buffer; + __le32 length; + __le32 control; +}; + +#define TRB_SIZE (sizeof(struct cdns2_trb)) +/* + * These two extra TRBs are reserved for isochronous transfer + * to inject 0 length packet and extra LINK TRB to synchronize the ISO transfer. + */ +#define TRB_ISO_RESERVED 2 +#define TR_SEG_SIZE (TRB_SIZE * (TRBS_PER_SEGMENT + TRB_ISO_RESERVED)) + +/* TRB bit mask. */ +#define TRB_TYPE_BITMASK GENMASK(15, 10) +#define TRB_TYPE(p) ((p) << 10) +#define TRB_FIELD_TO_TYPE(p) (((p) & TRB_TYPE_BITMASK) >> 10) + +/* TRB type IDs. */ +/* Used for Bulk, Interrupt, ISOC, and control data stage. */ +#define TRB_NORMAL 1 +/* TRB for linking ring segments. */ +#define TRB_LINK 6 + +/* Cycle bit - indicates TRB ownership by driver or hw. */ +#define TRB_CYCLE BIT(0) +/* + * When set to '1', the device will toggle its interpretation of the Cycle bit. + */ +#define TRB_TOGGLE BIT(1) +/* Interrupt on short packet. */ +#define TRB_ISP BIT(2) +/* Chain bit associate this TRB with next one TRB. */ +#define TRB_CHAIN BIT(4) +/* Interrupt on completion. */ +#define TRB_IOC BIT(5) + +/* Transfer_len bitmasks. */ +#define TRB_LEN(p) ((p) & GENMASK(16, 0)) +#define TRB_BURST(p) (((p) << 24) & GENMASK(31, 24)) +#define TRB_FIELD_TO_BURST(p) (((p) & GENMASK(31, 24)) >> 24) + +/* Data buffer pointer bitmasks. */ +#define TRB_BUFFER(p) ((p) & GENMASK(31, 0)) + +/*-------------------------------------------------------------------------*/ +/* Driver numeric constants. */ + +/* Maximum address that can be assigned to device. */ +#define USB_DEVICE_MAX_ADDRESS 127 + +/* One control and 15 IN and 15 OUT endpoints. */ +#define CDNS2_ENDPOINTS_NUM 31 + +#define CDNS2_EP_ZLP_BUF_SIZE 512 + +/*-------------------------------------------------------------------------*/ +/* Used structures. */ + +struct cdns2_device; + +/** + * struct cdns2_ring - transfer ring representation. + * @trbs: pointer to transfer ring. + * @dma: dma address of transfer ring. + * @free_trbs: number of free TRBs in transfer ring. + * @pcs: producer cycle state. + * @ccs: consumer cycle state. + * @enqueue: enqueue index in transfer ring. + * @dequeue: dequeue index in transfer ring. + */ +struct cdns2_ring { + struct cdns2_trb *trbs; + dma_addr_t dma; + int free_trbs; + u8 pcs; + u8 ccs; + int enqueue; + int dequeue; +}; + +/** + * struct cdns2_endpoint - extended device side representation of USB endpoint. + * @endpoint: usb endpoint. + * @pending_list: list of requests queuing on transfer ring. + * @deferred_list: list of requests waiting for queuing on transfer ring. + * @pdev: device associated with endpoint. + * @name: a human readable name e.g. ep1out. + * @ring: transfer ring associated with endpoint. + * @ep_state: state of endpoint. + * @idx: index of endpoint in pdev->eps table. + * @dir: endpoint direction. + * @num: endpoint number (1 - 15). + * @type: set to bmAttributes & USB_ENDPOINT_XFERTYPE_MASK. + * @interval: interval between packets used for ISOC and Interrupt endpoint. + * @buffering: on-chip buffers assigned to endpoint. + * @trb_burst_size: number of burst used in TRB. + * @skip: Sometimes the controller cannot process isochronous endpoint ring + * quickly enough and it will miss some isoc tds on the ring and + * generate ISO transmition error. + * Driver sets skip flag when receive a ISO transmition error and + * process the missed TDs on the endpoint ring. + * @wa1_set: use WA1. + * @wa1_trb: TRB assigned to WA1. + * @wa1_trb_index: TRB index for WA1. + * @wa1_cycle_bit: correct cycle bit for WA1. + */ +struct cdns2_endpoint { + struct usb_ep endpoint; + struct list_head pending_list; + struct list_head deferred_list; + + struct cdns2_device *pdev; + char name[20]; + + struct cdns2_ring ring; + +#define EP_ENABLED BIT(0) +#define EP_STALLED BIT(1) +#define EP_STALL_PENDING BIT(2) +#define EP_WEDGE BIT(3) +#define EP_CLAIMED BIT(4) +#define EP_RING_FULL BIT(5) +#define EP_DEFERRED_DRDY BIT(6) + + u32 ep_state; + + u8 idx; + u8 dir; + u8 num; + u8 type; + int interval; + u8 buffering; + u8 trb_burst_size; + bool skip; + + unsigned int wa1_set:1; + struct cdns2_trb *wa1_trb; + unsigned int wa1_trb_index; + unsigned int wa1_cycle_bit:1; +}; + +/** + * struct cdns2_request - extended device side representation of usb_request + * object. + * @request: generic usb_request object describing single I/O request. + * @pep: extended representation of usb_ep object. + * @trb: the first TRB association with this request. + * @start_trb: number of the first TRB in transfer ring. + * @end_trb: number of the last TRB in transfer ring. + * @list: used for queuing request in lists. + * @finished_trb: number of trb has already finished per request. + * @num_of_trb: how many trbs are associated with request. + */ +struct cdns2_request { + struct usb_request request; + struct cdns2_endpoint *pep; + struct cdns2_trb *trb; + int start_trb; + int end_trb; + struct list_head list; + int finished_trb; + int num_of_trb; +}; + +#define to_cdns2_request(r) (container_of(r, struct cdns2_request, request)) + +/* Stages used during enumeration process.*/ +#define CDNS2_SETUP_STAGE 0x0 +#define CDNS2_DATA_STAGE 0x1 +#define CDNS2_STATUS_STAGE 0x2 + +/** + * struct cdns2_device - represent USB device. + * @dev: pointer to device structure associated whit this controller. + * @gadget: device side representation of the peripheral controller. + * @gadget_driver: pointer to the gadget driver. + * @lock: for synchronizing. + * @irq: interrupt line number. + * @regs: base address for registers + * @usb_regs: base address for common USB registers. + * @ep0_regs: base address for endpoint 0 related registers. + * @epx_regs: base address for all none control endpoint registers. + * @interrupt_regs: base address for interrupt handling related registers. + * @adma_regs: base address for ADMA registers. + * @eps_dma_pool: endpoint Transfer Ring pool. + * @setup: used while processing usb control requests. + * @ep0_preq: private request used while handling EP0. + * @ep0_stage: ep0 stage during enumeration process. + * @zlp_buf: zlp buffer. + * @dev_address: device address assigned by host. + * @eps: array of objects describing endpoints. + * @selected_ep: actually selected endpoint. It's used only to improve + * performance by limiting access to dma_ep_sel register. + * @is_selfpowered: device is self powered. + * @may_wakeup: allows device to remote wakeup the host. + * @status_completion_no_call: indicate that driver is waiting for status + * stage completion. It's used in deferred SET_CONFIGURATION request. + * @in_lpm: indicate the controller is in low power mode. + * @pending_status_wq: workqueue handling status stage for deferred requests. + * @pending_status_request: request for which status stage was deferred. + * @eps_supported: endpoints supported by controller in form: + * bit: 0 - ep0, 1 - epOut1, 2 - epIn1, 3 - epOut2 ... + * @burst_opt: array with the best burst size value for different TRB size. + * @onchip_tx_buf: size of transmit on-chip buffer in KB. + * @onchip_rx_buf: size of receive on-chip buffer in KB. + */ +struct cdns2_device { + struct device *dev; + struct usb_gadget gadget; + struct usb_gadget_driver *gadget_driver; + + /* generic spin-lock for drivers */ + spinlock_t lock; + int irq; + void __iomem *regs; + struct cdns2_usb_regs __iomem *usb_regs; + struct cdns2_ep0_regs __iomem *ep0_regs; + struct cdns2_epx_regs __iomem *epx_regs; + struct cdns2_interrupt_regs __iomem *interrupt_regs; + struct cdns2_adma_regs __iomem *adma_regs; + struct dma_pool *eps_dma_pool; + struct usb_ctrlrequest setup; + struct cdns2_request ep0_preq; + u8 ep0_stage; + void *zlp_buf; + u8 dev_address; + struct cdns2_endpoint eps[CDNS2_ENDPOINTS_NUM]; + u32 selected_ep; + bool is_selfpowered; + bool may_wakeup; + bool status_completion_no_call; + bool in_lpm; + struct work_struct pending_status_wq; + struct usb_request *pending_status_request; + u32 eps_supported; + u8 burst_opt[MAX_ISO_SIZE + 1]; + + /*in KB */ + u16 onchip_tx_buf; + u16 onchip_rx_buf; +}; + +#define CDNS2_IF_EP_EXIST(pdev, ep_num, dir) \ + ((pdev)->eps_supported & \ + (BIT(ep_num) << ((dir) ? 0 : 16))) + +dma_addr_t cdns2_trb_virt_to_dma(struct cdns2_endpoint *pep, + struct cdns2_trb *trb); +void cdns2_pending_setup_status_handler(struct work_struct *work); +void cdns2_select_ep(struct cdns2_device *pdev, u32 ep); +struct cdns2_request *cdns2_next_preq(struct list_head *list); +struct usb_request *cdns2_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags); +void cdns2_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request); +int cdns2_gadget_ep_dequeue(struct usb_ep *ep, struct usb_request *request); +void cdns2_gadget_giveback(struct cdns2_endpoint *pep, + struct cdns2_request *priv_req, + int status); +void cdns2_init_ep0(struct cdns2_device *pdev, struct cdns2_endpoint *pep); +void cdns2_ep0_config(struct cdns2_device *pdev); +void cdns2_handle_ep0_interrupt(struct cdns2_device *pdev, int dir); +void cdns2_handle_setup_packet(struct cdns2_device *pdev); +int cdns2_gadget_resume(struct cdns2_device *pdev, bool hibernated); +int cdns2_gadget_suspend(struct cdns2_device *pdev); +void cdns2_gadget_remove(struct cdns2_device *pdev); +int cdns2_gadget_init(struct cdns2_device *pdev); +void set_reg_bit_8(void __iomem *ptr, u8 mask); +int cdns2_halt_endpoint(struct cdns2_device *pdev, struct cdns2_endpoint *pep, + int value); + +#endif /* __LINUX_CDNS2_GADGET */ diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-pci.c b/drivers/usb/gadget/udc/cdns2/cdns2-pci.c new file mode 100644 index 000000000..e589593b4 --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/cdns2-pci.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence USBHS-DEV controller - PCI Glue driver. + * + * Copyright (C) 2023 Cadence. + * + * Author: Pawel Laszczak + * + */ + +#include +#include +#include + +#include "cdns2-gadget.h" + +#define PCI_DRIVER_NAME "cdns-pci-usbhs" +#define PCI_BAR_DEV 0 +#define PCI_DEV_FN_DEVICE 0 + +static int cdns2_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + resource_size_t rsrc_start, rsrc_len; + struct device *dev = &pdev->dev; + struct cdns2_device *priv_dev; + struct resource *res; + int ret; + + /* For GADGET PCI (devfn) function number is 0. */ + if (!id || pdev->devfn != PCI_DEV_FN_DEVICE || + pdev->class != PCI_CLASS_SERIAL_USB_DEVICE) + return -EINVAL; + + ret = pcim_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "Enabling PCI device has failed %d\n", ret); + return ret; + } + + pci_set_master(pdev); + + priv_dev = devm_kzalloc(&pdev->dev, sizeof(*priv_dev), GFP_KERNEL); + if (!priv_dev) + return -ENOMEM; + + dev_dbg(dev, "Initialize resources\n"); + rsrc_start = pci_resource_start(pdev, PCI_BAR_DEV); + rsrc_len = pci_resource_len(pdev, PCI_BAR_DEV); + + res = devm_request_mem_region(dev, rsrc_start, rsrc_len, "dev"); + if (!res) { + dev_dbg(dev, "controller already in use\n"); + return -EBUSY; + } + + priv_dev->regs = devm_ioremap(dev, rsrc_start, rsrc_len); + if (!priv_dev->regs) { + dev_dbg(dev, "error mapping memory\n"); + return -EFAULT; + } + + priv_dev->irq = pdev->irq; + dev_dbg(dev, "USBSS-DEV physical base addr: %pa\n", + &rsrc_start); + + priv_dev->dev = dev; + + priv_dev->eps_supported = 0x000f000f; + priv_dev->onchip_tx_buf = 16; + priv_dev->onchip_rx_buf = 16; + + ret = cdns2_gadget_init(priv_dev); + if (ret) + return ret; + + pci_set_drvdata(pdev, priv_dev); + + device_wakeup_enable(&pdev->dev); + if (pci_dev_run_wake(pdev)) + pm_runtime_put_noidle(&pdev->dev); + + return 0; +} + +static void cdns2_pci_remove(struct pci_dev *pdev) +{ + struct cdns2_device *priv_dev = pci_get_drvdata(pdev); + + if (pci_dev_run_wake(pdev)) + pm_runtime_get_noresume(&pdev->dev); + + cdns2_gadget_remove(priv_dev); +} + +static int cdns2_pci_suspend(struct device *dev) +{ + struct cdns2_device *priv_dev = dev_get_drvdata(dev); + + return cdns2_gadget_suspend(priv_dev); +} + +static int cdns2_pci_resume(struct device *dev) +{ + struct cdns2_device *priv_dev = dev_get_drvdata(dev); + + return cdns2_gadget_resume(priv_dev, 1); +} + +static const struct dev_pm_ops cdns2_pci_pm_ops = { + SYSTEM_SLEEP_PM_OPS(cdns2_pci_suspend, cdns2_pci_resume) +}; + +static const struct pci_device_id cdns2_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USB), + .class = PCI_CLASS_SERIAL_USB_DEVICE }, + { 0, } +}; + +static struct pci_driver cdns2_pci_driver = { + .name = "cdns2-pci", + .id_table = cdns2_pci_ids, + .probe = cdns2_pci_probe, + .remove = cdns2_pci_remove, + .driver = { + .pm = pm_ptr(&cdns2_pci_pm_ops), + } +}; + +module_pci_driver(cdns2_pci_driver); +MODULE_DEVICE_TABLE(pci, cdns2_pci_ids); + +MODULE_ALIAS("pci:cdns2"); +MODULE_AUTHOR("Pawel Laszczak "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Cadence CDNS2 PCI driver"); diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-trace.c b/drivers/usb/gadget/udc/cdns2/cdns2-trace.c new file mode 100644 index 000000000..de6b8cc3d --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/cdns2-trace.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USBHS device controller driver Trace Support + * + * Copyright (C) 2023 Cadence. + * + * Author: Pawel Laszczak + */ + +#define CREATE_TRACE_POINTS +#include "cdns2-trace.h" diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-trace.h b/drivers/usb/gadget/udc/cdns2/cdns2-trace.h new file mode 100644 index 000000000..f4df0e2ff --- /dev/null +++ b/drivers/usb/gadget/udc/cdns2/cdns2-trace.h @@ -0,0 +1,536 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * USBHS-DEV device controller driver. + * Trace support header file. + * + * Copyright (C) 2023 Cadence. + * + * Author: Pawel Laszczak + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM cdns2-dev + +/* + * The TRACE_SYSTEM_VAR defaults to TRACE_SYSTEM, but must be a + * legitimate C variable. It is not exported to user space. + */ +#undef TRACE_SYSTEM_VAR +#define TRACE_SYSTEM_VAR cdns2_dev + +#if !defined(__LINUX_CDNS2_TRACE) || defined(TRACE_HEADER_MULTI_READ) +#define __LINUX_CDNS2_TRACE + +#include +#include +#include +#include +#include "cdns2-gadget.h" +#include "cdns2-debug.h" + +#define CDNS2_MSG_MAX 500 + +DECLARE_EVENT_CLASS(cdns2_log_enable_disable, + TP_PROTO(int set), + TP_ARGS(set), + TP_STRUCT__entry( + __field(int, set) + ), + TP_fast_assign( + __entry->set = set; + ), + TP_printk("%s", __entry->set ? "enabled" : "disabled") +); + +DEFINE_EVENT(cdns2_log_enable_disable, cdns2_pullup, + TP_PROTO(int set), + TP_ARGS(set) +); + +DECLARE_EVENT_CLASS(cdns2_log_simple, + TP_PROTO(char *msg), + TP_ARGS(msg), + TP_STRUCT__entry( + __string(text, msg) + ), + TP_fast_assign( + __assign_str(text); + ), + TP_printk("%s", __get_str(text)) +); + +DEFINE_EVENT(cdns2_log_simple, cdns2_no_room_on_ring, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdns2_log_simple, cdns2_ep0_status_stage, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdns2_log_simple, cdns2_ep0_setup, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +DEFINE_EVENT(cdns2_log_simple, cdns2_device_state, + TP_PROTO(char *msg), + TP_ARGS(msg) +); + +TRACE_EVENT(cdns2_ep_halt, + TP_PROTO(struct cdns2_endpoint *ep_priv, u8 halt, u8 flush), + TP_ARGS(ep_priv, halt, flush), + TP_STRUCT__entry( + __string(name, ep_priv->name) + __field(u8, halt) + __field(u8, flush) + ), + TP_fast_assign( + __assign_str(name); + __entry->halt = halt; + __entry->flush = flush; + ), + TP_printk("Halt %s for %s: %s", __entry->flush ? " and flush" : "", + __get_str(name), __entry->halt ? "set" : "cleared") +); + +TRACE_EVENT(cdns2_wa1, + TP_PROTO(struct cdns2_endpoint *ep_priv, char *msg), + TP_ARGS(ep_priv, msg), + TP_STRUCT__entry( + __string(ep_name, ep_priv->name) + __string(msg, msg) + ), + TP_fast_assign( + __assign_str(ep_name); + __assign_str(msg); + ), + TP_printk("WA1: %s %s", __get_str(ep_name), __get_str(msg)) +); + +DECLARE_EVENT_CLASS(cdns2_log_doorbell, + TP_PROTO(struct cdns2_endpoint *pep, u32 ep_trbaddr), + TP_ARGS(pep, ep_trbaddr), + TP_STRUCT__entry( + __string(name, pep->num ? pep->name : + (pep->dir ? "ep0in" : "ep0out")) + __field(u32, ep_trbaddr) + ), + TP_fast_assign( + __assign_str(name); + __entry->ep_trbaddr = ep_trbaddr; + ), + TP_printk("%s, ep_trbaddr %08x", __get_str(name), + __entry->ep_trbaddr) +); + +DEFINE_EVENT(cdns2_log_doorbell, cdns2_doorbell_ep0, + TP_PROTO(struct cdns2_endpoint *pep, u32 ep_trbaddr), + TP_ARGS(pep, ep_trbaddr) +); + +DEFINE_EVENT(cdns2_log_doorbell, cdns2_doorbell_epx, + TP_PROTO(struct cdns2_endpoint *pep, u32 ep_trbaddr), + TP_ARGS(pep, ep_trbaddr) +); + +DECLARE_EVENT_CLASS(cdns2_log_usb_irq, + TP_PROTO(u32 usb_irq, u32 ext_irq), + TP_ARGS(usb_irq, ext_irq), + TP_STRUCT__entry( + __field(u32, usb_irq) + __field(u32, ext_irq) + ), + TP_fast_assign( + __entry->usb_irq = usb_irq; + __entry->ext_irq = ext_irq; + ), + TP_printk("%s", cdns2_decode_usb_irq(__get_buf(CDNS2_MSG_MAX), + CDNS2_MSG_MAX, + __entry->usb_irq, + __entry->ext_irq)) +); + +DEFINE_EVENT(cdns2_log_usb_irq, cdns2_usb_irq, + TP_PROTO(u32 usb_irq, u32 ext_irq), + TP_ARGS(usb_irq, ext_irq) +); + +TRACE_EVENT(cdns2_dma_ep_ists, + TP_PROTO(u32 dma_ep_ists), + TP_ARGS(dma_ep_ists), + TP_STRUCT__entry( + __field(u32, dma_ep_ists) + ), + TP_fast_assign( + __entry->dma_ep_ists = dma_ep_ists; + ), + TP_printk("OUT: 0x%04x, IN: 0x%04x", (u16)__entry->dma_ep_ists, + __entry->dma_ep_ists >> 16) +); + +DECLARE_EVENT_CLASS(cdns2_log_epx_irq, + TP_PROTO(struct cdns2_device *pdev, struct cdns2_endpoint *pep), + TP_ARGS(pdev, pep), + TP_STRUCT__entry( + __string(ep_name, pep->name) + __field(u32, ep_sts) + __field(u32, ep_ists) + __field(u32, ep_traddr) + ), + TP_fast_assign( + __assign_str(ep_name); + __entry->ep_sts = readl(&pdev->adma_regs->ep_sts); + __entry->ep_ists = readl(&pdev->adma_regs->ep_ists); + __entry->ep_traddr = readl(&pdev->adma_regs->ep_traddr); + ), + TP_printk("%s, ep_traddr: %08x", + cdns2_decode_epx_irq(__get_buf(CDNS2_MSG_MAX), CDNS2_MSG_MAX, + __get_str(ep_name), + __entry->ep_ists, __entry->ep_sts), + __entry->ep_traddr) +); + +DEFINE_EVENT(cdns2_log_epx_irq, cdns2_epx_irq, + TP_PROTO(struct cdns2_device *pdev, struct cdns2_endpoint *pep), + TP_ARGS(pdev, pep) +); + +DECLARE_EVENT_CLASS(cdns2_log_ep0_irq, + TP_PROTO(struct cdns2_device *pdev), + TP_ARGS(pdev), + TP_STRUCT__entry( + __field(int, ep_dir) + __field(u32, ep_ists) + __field(u32, ep_sts) + ), + TP_fast_assign( + __entry->ep_dir = pdev->selected_ep; + __entry->ep_ists = readl(&pdev->adma_regs->ep_ists); + __entry->ep_sts = readl(&pdev->adma_regs->ep_sts); + ), + TP_printk("%s", cdns2_decode_ep0_irq(__get_buf(CDNS2_MSG_MAX), + CDNS2_MSG_MAX, + __entry->ep_ists, __entry->ep_sts, + __entry->ep_dir)) +); + +DEFINE_EVENT(cdns2_log_ep0_irq, cdns2_ep0_irq, + TP_PROTO(struct cdns2_device *pdev), + TP_ARGS(pdev) +); + +DECLARE_EVENT_CLASS(cdns2_log_ctrl, + TP_PROTO(struct usb_ctrlrequest *ctrl), + TP_ARGS(ctrl), + TP_STRUCT__entry( + __field(u8, bRequestType) + __field(u8, bRequest) + __field(u16, wValue) + __field(u16, wIndex) + __field(u16, wLength) + ), + TP_fast_assign( + __entry->bRequestType = ctrl->bRequestType; + __entry->bRequest = ctrl->bRequest; + __entry->wValue = le16_to_cpu(ctrl->wValue); + __entry->wIndex = le16_to_cpu(ctrl->wIndex); + __entry->wLength = le16_to_cpu(ctrl->wLength); + ), + TP_printk("%s", usb_decode_ctrl(__get_buf(CDNS2_MSG_MAX), CDNS2_MSG_MAX, + __entry->bRequestType, + __entry->bRequest, __entry->wValue, + __entry->wIndex, __entry->wLength) + ) +); + +DEFINE_EVENT(cdns2_log_ctrl, cdns2_ctrl_req, + TP_PROTO(struct usb_ctrlrequest *ctrl), + TP_ARGS(ctrl) +); + +DECLARE_EVENT_CLASS(cdns2_log_request, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq), + TP_STRUCT__entry( + __string(name, preq->pep->name) + __field(struct usb_request *, request) + __field(struct cdns2_request *, preq) + __field(void *, buf) + __field(unsigned int, actual) + __field(unsigned int, length) + __field(int, status) + __field(dma_addr_t, dma) + __field(int, zero) + __field(int, short_not_ok) + __field(int, no_interrupt) + __field(struct scatterlist*, sg) + __field(unsigned int, num_sgs) + __field(unsigned int, num_mapped_sgs) + __field(int, start_trb) + __field(int, end_trb) + ), + TP_fast_assign( + __assign_str(name); + __entry->request = &preq->request; + __entry->preq = preq; + __entry->buf = preq->request.buf; + __entry->actual = preq->request.actual; + __entry->length = preq->request.length; + __entry->status = preq->request.status; + __entry->dma = preq->request.dma; + __entry->zero = preq->request.zero; + __entry->short_not_ok = preq->request.short_not_ok; + __entry->no_interrupt = preq->request.no_interrupt; + __entry->sg = preq->request.sg; + __entry->num_sgs = preq->request.num_sgs; + __entry->num_mapped_sgs = preq->request.num_mapped_sgs; + __entry->start_trb = preq->start_trb; + __entry->end_trb = preq->end_trb; + ), + TP_printk("%s: req: %p, preq: %p, req buf: %p, length: %u/%u, status: %d," + "buf dma: (%pad), %s%s%s, sg: %p, num_sgs: %d, num_m_sgs: %d," + "trb: [start: %d, end: %d]", + __get_str(name), __entry->request, __entry->preq, + __entry->buf, __entry->actual, __entry->length, + __entry->status, &__entry->dma, + __entry->zero ? "Z" : "z", + __entry->short_not_ok ? "S" : "s", + __entry->no_interrupt ? "I" : "i", + __entry->sg, __entry->num_sgs, __entry->num_mapped_sgs, + __entry->start_trb, + __entry->end_trb + ) +); + +DEFINE_EVENT(cdns2_log_request, cdns2_request_enqueue, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq) +); + +DEFINE_EVENT(cdns2_log_request, cdns2_request_enqueue_error, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq) +); + +DEFINE_EVENT(cdns2_log_request, cdns2_alloc_request, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq) +); + +DEFINE_EVENT(cdns2_log_request, cdns2_free_request, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq) +); + +DEFINE_EVENT(cdns2_log_request, cdns2_request_dequeue, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq) +); + +DEFINE_EVENT(cdns2_log_request, cdns2_request_giveback, + TP_PROTO(struct cdns2_request *preq), + TP_ARGS(preq) +); + +DECLARE_EVENT_CLASS(cdns2_log_trb, + TP_PROTO(struct cdns2_endpoint *pep, struct cdns2_trb *trb), + TP_ARGS(pep, trb), + TP_STRUCT__entry( + __string(name, pep->name) + __field(struct cdns2_trb *, trb) + __field(u32, buffer) + __field(u32, length) + __field(u32, control) + __field(u32, type) + ), + TP_fast_assign( + __assign_str(name); + __entry->trb = trb; + __entry->buffer = le32_to_cpu(trb->buffer); + __entry->length = le32_to_cpu(trb->length); + __entry->control = le32_to_cpu(trb->control); + __entry->type = usb_endpoint_type(pep->endpoint.desc); + ), + TP_printk("%s: trb V: %p, dma buf: P: 0x%08x, %s", + __get_str(name), __entry->trb, __entry->buffer, + cdns2_decode_trb(__get_buf(CDNS2_MSG_MAX), CDNS2_MSG_MAX, + __entry->control, __entry->length, + __entry->buffer)) +); + +DEFINE_EVENT(cdns2_log_trb, cdns2_queue_trb, + TP_PROTO(struct cdns2_endpoint *pep, struct cdns2_trb *trb), + TP_ARGS(pep, trb) +); + +DEFINE_EVENT(cdns2_log_trb, cdns2_complete_trb, + TP_PROTO(struct cdns2_endpoint *pep, struct cdns2_trb *trb), + TP_ARGS(pep, trb) +); + +DECLARE_EVENT_CLASS(cdns2_log_ring, + TP_PROTO(struct cdns2_endpoint *pep), + TP_ARGS(pep), + TP_STRUCT__entry( + __dynamic_array(u8, tr_seg, TR_SEG_SIZE) + __dynamic_array(u8, pep, sizeof(struct cdns2_endpoint)) + __dynamic_array(char, buffer, + (TRBS_PER_SEGMENT * 65) + CDNS2_MSG_MAX) + ), + TP_fast_assign( + memcpy(__get_dynamic_array(pep), pep, + sizeof(struct cdns2_endpoint)); + memcpy(__get_dynamic_array(tr_seg), pep->ring.trbs, + TR_SEG_SIZE); + ), + + TP_printk("%s", + cdns2_raw_ring((struct cdns2_endpoint *)__get_str(pep), + (struct cdns2_trb *)__get_str(tr_seg), + __get_str(buffer), + (TRBS_PER_SEGMENT * 65) + CDNS2_MSG_MAX)) +); + +DEFINE_EVENT(cdns2_log_ring, cdns2_ring, + TP_PROTO(struct cdns2_endpoint *pep), + TP_ARGS(pep) +); + +DECLARE_EVENT_CLASS(cdns2_log_ep, + TP_PROTO(struct cdns2_endpoint *pep), + TP_ARGS(pep), + TP_STRUCT__entry( + __string(name, pep->name) + __field(unsigned int, maxpacket) + __field(unsigned int, maxpacket_limit) + __field(unsigned int, flags) + __field(unsigned int, dir) + __field(u8, enqueue) + __field(u8, dequeue) + ), + TP_fast_assign( + __assign_str(name); + __entry->maxpacket = pep->endpoint.maxpacket; + __entry->maxpacket_limit = pep->endpoint.maxpacket_limit; + __entry->flags = pep->ep_state; + __entry->dir = pep->dir; + __entry->enqueue = pep->ring.enqueue; + __entry->dequeue = pep->ring.dequeue; + ), + TP_printk("%s: mps: %d/%d, enq idx: %d, deq idx: %d, " + "flags: %s%s%s%s, dir: %s", + __get_str(name), __entry->maxpacket, + __entry->maxpacket_limit, __entry->enqueue, + __entry->dequeue, + __entry->flags & EP_ENABLED ? "EN | " : "", + __entry->flags & EP_STALLED ? "STALLED | " : "", + __entry->flags & EP_WEDGE ? "WEDGE | " : "", + __entry->flags & EP_RING_FULL ? "RING FULL |" : "", + __entry->dir ? "IN" : "OUT" + ) +); + +DEFINE_EVENT(cdns2_log_ep, cdns2_gadget_ep_enable, + TP_PROTO(struct cdns2_endpoint *pep), + TP_ARGS(pep) +); + +DEFINE_EVENT(cdns2_log_ep, cdns2_gadget_ep_disable, + TP_PROTO(struct cdns2_endpoint *pep), + TP_ARGS(pep) +); + +DEFINE_EVENT(cdns2_log_ep, cdns2_ep_busy_try_halt_again, + TP_PROTO(struct cdns2_endpoint *pep), + TP_ARGS(pep) +); + +DECLARE_EVENT_CLASS(cdns2_log_request_handled, + TP_PROTO(struct cdns2_request *priv_req, int current_index, + int handled), + TP_ARGS(priv_req, current_index, handled), + TP_STRUCT__entry( + __field(struct cdns2_request *, priv_req) + __field(unsigned int, dma_position) + __field(unsigned int, handled) + __field(unsigned int, dequeue_idx) + __field(unsigned int, enqueue_idx) + __field(unsigned int, start_trb) + __field(unsigned int, end_trb) + ), + TP_fast_assign( + __entry->priv_req = priv_req; + __entry->dma_position = current_index; + __entry->handled = handled; + __entry->dequeue_idx = priv_req->pep->ring.dequeue; + __entry->enqueue_idx = priv_req->pep->ring.enqueue; + __entry->start_trb = priv_req->start_trb; + __entry->end_trb = priv_req->end_trb; + ), + TP_printk("Req: %p %s, DMA pos: %d, ep deq: %d, ep enq: %d," + " start trb: %d, end trb: %d", + __entry->priv_req, + __entry->handled ? "handled" : "not handled", + __entry->dma_position, __entry->dequeue_idx, + __entry->enqueue_idx, __entry->start_trb, + __entry->end_trb + ) +); + +DEFINE_EVENT(cdns2_log_request_handled, cdns2_request_handled, + TP_PROTO(struct cdns2_request *priv_req, int current_index, + int handled), + TP_ARGS(priv_req, current_index, handled) +); + +DECLARE_EVENT_CLASS(cdns2_log_epx_reg_config, + TP_PROTO(struct cdns2_device *pdev, struct cdns2_endpoint *pep), + TP_ARGS(pdev, pep), + TP_STRUCT__entry( + __string(ep_name, pep->name) + __field(u8, burst_size) + __field(__le16, maxpack_reg) + __field(__u8, con_reg) + __field(u32, ep_sel_reg) + __field(u32, ep_sts_en_reg) + __field(u32, ep_cfg_reg) + ), + TP_fast_assign( + __assign_str(ep_name); + __entry->burst_size = pep->trb_burst_size; + __entry->maxpack_reg = pep->dir ? readw(&pdev->epx_regs->txmaxpack[pep->num - 1]) : + readw(&pdev->epx_regs->rxmaxpack[pep->num - 1]); + __entry->con_reg = pep->dir ? readb(&pdev->epx_regs->ep[pep->num - 1].txcon) : + readb(&pdev->epx_regs->ep[pep->num - 1].rxcon); + __entry->ep_sel_reg = readl(&pdev->adma_regs->ep_sel); + __entry->ep_sts_en_reg = readl(&pdev->adma_regs->ep_sts_en); + __entry->ep_cfg_reg = readl(&pdev->adma_regs->ep_cfg); + ), + + TP_printk("%s, maxpack: %d, con: %02x, dma_ep_sel: %08x, dma_ep_sts_en: %08x" + " dma_ep_cfg %08x", + __get_str(ep_name), __entry->maxpack_reg, __entry->con_reg, + __entry->ep_sel_reg, __entry->ep_sts_en_reg, + __entry->ep_cfg_reg + ) +); + +DEFINE_EVENT(cdns2_log_epx_reg_config, cdns2_epx_hw_cfg, + TP_PROTO(struct cdns2_device *pdev, struct cdns2_endpoint *pep), + TP_ARGS(pdev, pep) +); + +#endif /* __LINUX_CDNS2_TRACE */ + +/* This part must be outside header guard. */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE cdns2-trace + +#include diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c new file mode 100644 index 000000000..f6da12b55 --- /dev/null +++ b/drivers/usb/gadget/udc/core.c @@ -0,0 +1,1982 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * udc.c - Core UDC Framework + * + * Copyright (C) 2010 Texas Instruments + * Author: Felipe Balbi + */ + +#define pr_fmt(fmt) "UDC core: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "trace.h" + +static DEFINE_IDA(gadget_id_numbers); + +static const struct bus_type gadget_bus_type; + +/** + * struct usb_udc - describes one usb device controller + * @driver: the gadget driver pointer. For use by the class code + * @gadget: the gadget. For use by the class code + * @gadget_release: the gadget's release routine + * @dev: the child device to the actual controller + * @list: for use by the udc class driver + * @vbus: for udcs who care about vbus status, this value is real vbus status; + * for udcs who do not care about vbus status, this value is always true + * @started: the UDC's started state. True if the UDC had started. + * @allow_connect: Indicates whether UDC is allowed to be pulled up. + * Set/cleared by gadget_(un)bind_driver() after gadget driver is bound or + * unbound. + * @vbus_work: work routine to handle VBUS status change notifications. + * @connect_lock: protects udc->started, gadget->connect, + * gadget->allow_connect and gadget->deactivate. The routines + * usb_gadget_connect_locked(), usb_gadget_disconnect_locked(), + * usb_udc_connect_control_locked(), usb_gadget_udc_start_locked() and + * usb_gadget_udc_stop_locked() are called with this lock held. + * + * 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; + void (*gadget_release)(struct device *dev); + struct device dev; + struct list_head list; + bool vbus; + bool started; + bool allow_connect; + struct work_struct vbus_work; + struct mutex connect_lock; +}; + +static const struct class udc_class; +static LIST_HEAD(udc_list); + +/* Protects udc_list, udc->driver, driver->is_bound, and related calls */ +static DEFINE_MUTEX(udc_lock); + +/* ------------------------------------------------------------------------- */ + +/** + * usb_ep_set_maxpacket_limit - set maximum packet size limit for endpoint + * @ep:the endpoint being configured + * @maxpacket_limit:value of maximum packet size limit + * + * This function should be used only in UDC drivers to initialize endpoint + * (usually in probe function). + */ +void usb_ep_set_maxpacket_limit(struct usb_ep *ep, + unsigned maxpacket_limit) +{ + ep->maxpacket_limit = maxpacket_limit; + ep->maxpacket = maxpacket_limit; + + trace_usb_ep_set_maxpacket_limit(ep, 0); +} +EXPORT_SYMBOL_GPL(usb_ep_set_maxpacket_limit); + +/** + * usb_ep_enable - configure endpoint, making it usable + * @ep:the endpoint being configured. may not be the endpoint named "ep0". + * drivers discover endpoints through the ep_list of a usb_gadget. + * + * When configurations are set, or when interface settings change, the driver + * will enable or disable the relevant endpoints. while it is enabled, an + * endpoint may be used for i/o until the driver receives a disconnect() from + * the host or until the endpoint is disabled. + * + * the ep0 implementation (which calls this routine) must ensure that the + * hardware capabilities of each endpoint match the descriptor provided + * for it. for example, an endpoint named "ep2in-bulk" would be usable + * for interrupt transfers as well as bulk, but it likely couldn't be used + * for iso transfers or for endpoint 14. some endpoints are fully + * configurable, with more generic names like "ep-a". (remember that for + * USB, "in" means "towards the USB host".) + * + * This routine may be called in an atomic (interrupt) context. + * + * returns zero, or a negative error code. + */ +int usb_ep_enable(struct usb_ep *ep) +{ + int ret = 0; + + if (ep->enabled) + goto out; + + /* UDC drivers can't handle endpoints with maxpacket size 0 */ + if (!ep->desc || usb_endpoint_maxp(ep->desc) == 0) { + WARN_ONCE(1, "%s: ep%d (%s) has %s\n", __func__, ep->address, ep->name, + (!ep->desc) ? "NULL descriptor" : "maxpacket 0"); + + ret = -EINVAL; + goto out; + } + + ret = ep->ops->enable(ep, ep->desc); + if (ret) + goto out; + + ep->enabled = true; + +out: + trace_usb_ep_enable(ep, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_ep_enable); + +/** + * usb_ep_disable - endpoint is no longer usable + * @ep:the endpoint being unconfigured. may not be the endpoint named "ep0". + * + * no other task may be using this endpoint when this is called. + * any pending and uncompleted requests will complete with status + * indicating disconnect (-ESHUTDOWN) before this call returns. + * gadget drivers must call usb_ep_enable() again before queueing + * requests to the endpoint. + * + * This routine may be called in an atomic (interrupt) context. + * + * returns zero, or a negative error code. + */ +int usb_ep_disable(struct usb_ep *ep) +{ + int ret = 0; + + if (!ep->enabled) + goto out; + + ret = ep->ops->disable(ep); + if (ret) + goto out; + + ep->enabled = false; + +out: + trace_usb_ep_disable(ep, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_ep_disable); + +/** + * usb_ep_alloc_request - allocate a request object to use with this endpoint + * @ep:the endpoint to be used with with the request + * @gfp_flags:GFP_* flags to use + * + * Request objects must be allocated with this call, since they normally + * need controller-specific setup and may even need endpoint-specific + * resources such as allocation of DMA descriptors. + * Requests may be submitted with usb_ep_queue(), and receive a single + * completion callback. Free requests with usb_ep_free_request(), when + * they are no longer needed. + * + * Returns the request, or null if one could not be allocated. + */ +struct usb_request *usb_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct usb_request *req = NULL; + + req = ep->ops->alloc_request(ep, gfp_flags); + + if (req) + req->ep = ep; + + trace_usb_ep_alloc_request(ep, req, req ? 0 : -ENOMEM); + + return req; +} +EXPORT_SYMBOL_GPL(usb_ep_alloc_request); + +/** + * usb_ep_free_request - frees a request object + * @ep:the endpoint associated with the request + * @req:the request being freed + * + * Reverses the effect of usb_ep_alloc_request(). + * Caller guarantees the request is not queued, and that it will + * no longer be requeued (or otherwise used). + */ +void usb_ep_free_request(struct usb_ep *ep, + struct usb_request *req) +{ + trace_usb_ep_free_request(ep, req, 0); + ep->ops->free_request(ep, req); +} +EXPORT_SYMBOL_GPL(usb_ep_free_request); + +/** + * usb_ep_queue - queues (submits) an I/O request to an endpoint. + * @ep:the endpoint associated with the request + * @req:the request being submitted + * @gfp_flags: GFP_* flags to use in case the lower level driver couldn't + * pre-allocate all necessary memory with the request. + * + * This tells the device controller to perform the specified request through + * that endpoint (reading or writing a buffer). When the request completes, + * including being canceled by usb_ep_dequeue(), the request's completion + * routine is called to return the request to the driver. Any endpoint + * (except control endpoints like ep0) may have more than one transfer + * request queued; they complete in FIFO order. Once a gadget driver + * submits a request, that request may not be examined or modified until it + * is given back to that driver through the completion callback. + * + * Each request is turned into one or more packets. The controller driver + * never merges adjacent requests into the same packet. OUT transfers + * will sometimes use data that's already buffered in the hardware. + * Drivers can rely on the fact that the first byte of the request's buffer + * always corresponds to the first byte of some USB packet, for both + * IN and OUT transfers. + * + * Bulk endpoints can queue any amount of data; the transfer is packetized + * automatically. The last packet will be short if the request doesn't fill it + * out completely. Zero length packets (ZLPs) should be avoided in portable + * protocols since not all usb hardware can successfully handle zero length + * packets. (ZLPs may be explicitly written, and may be implicitly written if + * the request 'zero' flag is set.) Bulk endpoints may also be used + * for interrupt transfers; but the reverse is not true, and some endpoints + * won't support every interrupt transfer. (Such as 768 byte packets.) + * + * Interrupt-only endpoints are less functional than bulk endpoints, for + * example by not supporting queueing or not handling buffers that are + * larger than the endpoint's maxpacket size. They may also treat data + * toggle differently. + * + * Control endpoints ... after getting a setup() callback, the driver queues + * one response (even if it would be zero length). That enables the + * status ack, after transferring data as specified in the response. Setup + * functions may return negative error codes to generate protocol stalls. + * (Note that some USB device controllers disallow protocol stall responses + * in some cases.) When control responses are deferred (the response is + * written after the setup callback returns), then usb_ep_set_halt() may be + * used on ep0 to trigger protocol stalls. Depending on the controller, + * it may not be possible to trigger a status-stage protocol stall when the + * data stage is over, that is, from within the response's completion + * routine. + * + * For periodic endpoints, like interrupt or isochronous ones, the usb host + * arranges to poll once per interval, and the gadget driver usually will + * have queued some data to transfer at that time. + * + * Note that @req's ->complete() callback must never be called from + * within usb_ep_queue() as that can create deadlock situations. + * + * This routine may be called in interrupt context. + * + * Returns zero, or a negative error code. Endpoints that are not enabled + * report errors; errors will also be + * reported when the usb peripheral is disconnected. + * + * If and only if @req is successfully queued (the return value is zero), + * @req->complete() will be called exactly once, when the Gadget core and + * UDC are finished with the request. When the completion function is called, + * control of the request is returned to the device driver which submitted it. + * The completion handler may then immediately free or reuse @req. + */ +int usb_ep_queue(struct usb_ep *ep, + struct usb_request *req, gfp_t gfp_flags) +{ + int ret = 0; + + if (!ep->enabled && ep->address) { + pr_debug("USB gadget: queue request to disabled ep 0x%x (%s)\n", + ep->address, ep->name); + ret = -ESHUTDOWN; + goto out; + } + + ret = ep->ops->queue(ep, req, gfp_flags); + +out: + trace_usb_ep_queue(ep, req, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_ep_queue); + +/** + * usb_ep_dequeue - dequeues (cancels, unlinks) an I/O request from an endpoint + * @ep:the endpoint associated with the request + * @req:the request being canceled + * + * If the request is still active on the endpoint, it is dequeued and + * eventually its completion routine is called (with status -ECONNRESET); + * else a negative error code is returned. This routine is asynchronous, + * that is, it may return before the completion routine runs. + * + * Note that some hardware can't clear out write fifos (to unlink the request + * at the head of the queue) except as part of disconnecting from usb. Such + * restrictions prevent drivers from supporting configuration changes, + * even to configuration zero (a "chapter 9" requirement). + * + * This routine may be called in interrupt context. + */ +int usb_ep_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + int ret; + + ret = ep->ops->dequeue(ep, req); + trace_usb_ep_dequeue(ep, req, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_ep_dequeue); + +/** + * usb_ep_set_halt - sets the endpoint halt feature. + * @ep: the non-isochronous endpoint being stalled + * + * Use this to stall an endpoint, perhaps as an error report. + * Except for control endpoints, + * the endpoint stays halted (will not stream any data) until the host + * clears this feature; drivers may need to empty the endpoint's request + * queue first, to make sure no inappropriate transfers happen. + * + * Note that while an endpoint CLEAR_FEATURE will be invisible to the + * gadget driver, a SET_INTERFACE will not be. To reset endpoints for the + * current altsetting, see usb_ep_clear_halt(). When switching altsettings, + * it's simplest to use usb_ep_enable() or usb_ep_disable() for the endpoints. + * + * This routine may be called in interrupt context. + * + * Returns zero, or a negative error code. On success, this call sets + * underlying hardware state that blocks data transfers. + * Attempts to halt IN endpoints will fail (returning -EAGAIN) if any + * transfer requests are still queued, or if the controller hardware + * (usually a FIFO) still holds bytes that the host hasn't collected. + */ +int usb_ep_set_halt(struct usb_ep *ep) +{ + int ret; + + ret = ep->ops->set_halt(ep, 1); + trace_usb_ep_set_halt(ep, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_ep_set_halt); + +/** + * usb_ep_clear_halt - clears endpoint halt, and resets toggle + * @ep:the bulk or interrupt endpoint being reset + * + * Use this when responding to the standard usb "set interface" request, + * for endpoints that aren't reconfigured, after clearing any other state + * in the endpoint's i/o queue. + * + * This routine may be called in interrupt context. + * + * Returns zero, or a negative error code. On success, this call clears + * the underlying hardware state reflecting endpoint halt and data toggle. + * Note that some hardware can't support this request (like pxa2xx_udc), + * and accordingly can't correctly implement interface altsettings. + */ +int usb_ep_clear_halt(struct usb_ep *ep) +{ + int ret; + + ret = ep->ops->set_halt(ep, 0); + trace_usb_ep_clear_halt(ep, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_ep_clear_halt); + +/** + * usb_ep_set_wedge - sets the halt feature and ignores clear requests + * @ep: the endpoint being wedged + * + * Use this to stall an endpoint and ignore CLEAR_FEATURE(HALT_ENDPOINT) + * requests. If the gadget driver clears the halt status, it will + * automatically unwedge the endpoint. + * + * This routine may be called in interrupt context. + * + * Returns zero on success, else negative errno. + */ +int usb_ep_set_wedge(struct usb_ep *ep) +{ + int ret; + + if (ep->ops->set_wedge) + ret = ep->ops->set_wedge(ep); + else + ret = ep->ops->set_halt(ep, 1); + + trace_usb_ep_set_wedge(ep, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_ep_set_wedge); + +/** + * usb_ep_fifo_status - returns number of bytes in fifo, or error + * @ep: the endpoint whose fifo status is being checked. + * + * FIFO endpoints may have "unclaimed data" in them in certain cases, + * such as after aborted transfers. Hosts may not have collected all + * the IN data written by the gadget driver (and reported by a request + * completion). The gadget driver may not have collected all the data + * written OUT to it by the host. Drivers that need precise handling for + * fault reporting or recovery may need to use this call. + * + * This routine may be called in interrupt context. + * + * This returns the number of such bytes in the fifo, or a negative + * errno if the endpoint doesn't use a FIFO or doesn't support such + * precise handling. + */ +int usb_ep_fifo_status(struct usb_ep *ep) +{ + int ret; + + if (ep->ops->fifo_status) + ret = ep->ops->fifo_status(ep); + else + ret = -EOPNOTSUPP; + + trace_usb_ep_fifo_status(ep, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_ep_fifo_status); + +/** + * usb_ep_fifo_flush - flushes contents of a fifo + * @ep: the endpoint whose fifo is being flushed. + * + * This call may be used to flush the "unclaimed data" that may exist in + * an endpoint fifo after abnormal transaction terminations. The call + * must never be used except when endpoint is not being used for any + * protocol translation. + * + * This routine may be called in interrupt context. + */ +void usb_ep_fifo_flush(struct usb_ep *ep) +{ + if (ep->ops->fifo_flush) + ep->ops->fifo_flush(ep); + + trace_usb_ep_fifo_flush(ep, 0); +} +EXPORT_SYMBOL_GPL(usb_ep_fifo_flush); + +/* ------------------------------------------------------------------------- */ + +/** + * usb_gadget_frame_number - returns the current frame number + * @gadget: controller that reports the frame number + * + * Returns the usb frame number, normally eleven bits from a SOF packet, + * or negative errno if this device doesn't support this capability. + */ +int usb_gadget_frame_number(struct usb_gadget *gadget) +{ + int ret; + + ret = gadget->ops->get_frame(gadget); + + trace_usb_gadget_frame_number(gadget, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_frame_number); + +/** + * usb_gadget_wakeup - tries to wake up the host connected to this gadget + * @gadget: controller used to wake up the host + * + * Returns zero on success, else negative error code if the hardware + * doesn't support such attempts, or its support has not been enabled + * by the usb host. Drivers must return device descriptors that report + * their ability to support this, or hosts won't enable it. + * + * This may also try to use SRP to wake the host and start enumeration, + * even if OTG isn't otherwise in use. OTG devices may also start + * remote wakeup even when hosts don't explicitly enable it. + */ +int usb_gadget_wakeup(struct usb_gadget *gadget) +{ + int ret = 0; + + if (!gadget->ops->wakeup) { + ret = -EOPNOTSUPP; + goto out; + } + + ret = gadget->ops->wakeup(gadget); + +out: + trace_usb_gadget_wakeup(gadget, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_wakeup); + +/** + * usb_gadget_set_remote_wakeup - configures the device remote wakeup feature. + * @gadget:the device being configured for remote wakeup + * @set:value to be configured. + * + * set to one to enable remote wakeup feature and zero to disable it. + * + * returns zero on success, else negative errno. + */ +int usb_gadget_set_remote_wakeup(struct usb_gadget *gadget, int set) +{ + int ret = 0; + + if (!gadget->ops->set_remote_wakeup) { + ret = -EOPNOTSUPP; + goto out; + } + + ret = gadget->ops->set_remote_wakeup(gadget, set); + +out: + trace_usb_gadget_set_remote_wakeup(gadget, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_set_remote_wakeup); + +/** + * usb_gadget_set_selfpowered - sets the device selfpowered feature. + * @gadget:the device being declared as self-powered + * + * this affects the device status reported by the hardware driver + * to reflect that it now has a local power supply. + * + * returns zero on success, else negative errno. + */ +int usb_gadget_set_selfpowered(struct usb_gadget *gadget) +{ + int ret = 0; + + if (!gadget->ops->set_selfpowered) { + ret = -EOPNOTSUPP; + goto out; + } + + ret = gadget->ops->set_selfpowered(gadget, 1); + +out: + trace_usb_gadget_set_selfpowered(gadget, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_set_selfpowered); + +/** + * usb_gadget_clear_selfpowered - clear the device selfpowered feature. + * @gadget:the device being declared as bus-powered + * + * this affects the device status reported by the hardware driver. + * some hardware may not support bus-powered operation, in which + * case this feature's value can never change. + * + * returns zero on success, else negative errno. + */ +int usb_gadget_clear_selfpowered(struct usb_gadget *gadget) +{ + int ret = 0; + + if (!gadget->ops->set_selfpowered) { + ret = -EOPNOTSUPP; + goto out; + } + + ret = gadget->ops->set_selfpowered(gadget, 0); + +out: + trace_usb_gadget_clear_selfpowered(gadget, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_clear_selfpowered); + +/** + * usb_gadget_vbus_connect - Notify controller that VBUS is powered + * @gadget:The device which now has VBUS power. + * Context: can sleep + * + * This call is used by a driver for an external transceiver (or GPIO) + * that detects a VBUS power session starting. Common responses include + * resuming the controller, activating the D+ (or D-) pullup to let the + * host detect that a USB device is attached, and starting to draw power + * (8mA or possibly more, especially after SET_CONFIGURATION). + * + * Returns zero on success, else negative errno. + */ +int usb_gadget_vbus_connect(struct usb_gadget *gadget) +{ + int ret = 0; + + if (!gadget->ops->vbus_session) { + ret = -EOPNOTSUPP; + goto out; + } + + ret = gadget->ops->vbus_session(gadget, 1); + +out: + trace_usb_gadget_vbus_connect(gadget, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_vbus_connect); + +/** + * usb_gadget_vbus_draw - constrain controller's VBUS power usage + * @gadget:The device whose VBUS usage is being described + * @mA:How much current to draw, in milliAmperes. This should be twice + * the value listed in the configuration descriptor bMaxPower field. + * + * 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. + */ +int usb_gadget_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + int ret = 0; + + if (!gadget->ops->vbus_draw) { + ret = -EOPNOTSUPP; + goto out; + } + + ret = gadget->ops->vbus_draw(gadget, mA); + if (!ret) + gadget->mA = mA; + +out: + trace_usb_gadget_vbus_draw(gadget, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_vbus_draw); + +/** + * usb_gadget_vbus_disconnect - notify controller about VBUS session end + * @gadget:the device whose VBUS supply is being described + * Context: can sleep + * + * This call is used by a driver for an external transceiver (or GPIO) + * that detects a VBUS power session ending. Common responses include + * reversing everything done in usb_gadget_vbus_connect(). + * + * Returns zero on success, else negative errno. + */ +int usb_gadget_vbus_disconnect(struct usb_gadget *gadget) +{ + int ret = 0; + + if (!gadget->ops->vbus_session) { + ret = -EOPNOTSUPP; + goto out; + } + + ret = gadget->ops->vbus_session(gadget, 0); + +out: + trace_usb_gadget_vbus_disconnect(gadget, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_vbus_disconnect); + +static int usb_gadget_connect_locked(struct usb_gadget *gadget) + __must_hold(&gadget->udc->connect_lock) +{ + int ret = 0; + + if (!gadget->ops->pullup) { + ret = -EOPNOTSUPP; + goto out; + } + + if (gadget->connected) + goto out; + + if (gadget->deactivated || !gadget->udc->allow_connect || !gadget->udc->started) { + /* + * If the gadget isn't usable (because it is deactivated, + * unbound, or not yet started), we only save the new state. + * The gadget will be connected automatically when it is + * activated/bound/started. + */ + gadget->connected = true; + goto out; + } + + ret = gadget->ops->pullup(gadget, 1); + if (!ret) + gadget->connected = 1; + +out: + trace_usb_gadget_connect(gadget, ret); + + return ret; +} + +/** + * usb_gadget_connect - software-controlled connect to USB host + * @gadget:the peripheral being connected + * + * Enables the D+ (or potentially D-) pullup. The host will start + * enumerating this gadget when the pullup is active and a VBUS session + * is active (the link is powered). + * + * Returns zero on success, else negative errno. + */ +int usb_gadget_connect(struct usb_gadget *gadget) +{ + int ret; + + mutex_lock(&gadget->udc->connect_lock); + ret = usb_gadget_connect_locked(gadget); + mutex_unlock(&gadget->udc->connect_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_connect); + +static int usb_gadget_disconnect_locked(struct usb_gadget *gadget) + __must_hold(&gadget->udc->connect_lock) +{ + int ret = 0; + + if (!gadget->ops->pullup) { + ret = -EOPNOTSUPP; + goto out; + } + + if (!gadget->connected) + goto out; + + if (gadget->deactivated || !gadget->udc->started) { + /* + * If gadget is deactivated we only save new state. + * Gadget will stay disconnected after activation. + */ + gadget->connected = false; + goto out; + } + + ret = gadget->ops->pullup(gadget, 0); + if (!ret) + gadget->connected = 0; + + mutex_lock(&udc_lock); + if (gadget->udc->driver) + gadget->udc->driver->disconnect(gadget); + mutex_unlock(&udc_lock); + +out: + trace_usb_gadget_disconnect(gadget, ret); + + return ret; +} + +/** + * usb_gadget_disconnect - software-controlled disconnect from USB host + * @gadget:the peripheral being disconnected + * + * Disables the D+ (or potentially D-) pullup, which the host may see + * as a disconnect (when a VBUS session is active). Not all systems + * support software pullup controls. + * + * Following a successful disconnect, invoke the ->disconnect() callback + * for the current gadget driver so that UDC drivers don't need to. + * + * Returns zero on success, else negative errno. + */ +int usb_gadget_disconnect(struct usb_gadget *gadget) +{ + int ret; + + mutex_lock(&gadget->udc->connect_lock); + ret = usb_gadget_disconnect_locked(gadget); + mutex_unlock(&gadget->udc->connect_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_disconnect); + +/** + * usb_gadget_deactivate - deactivate function which is not ready to work + * @gadget: the peripheral being deactivated + * + * This routine may be used during the gadget driver bind() call to prevent + * the peripheral from ever being visible to the USB host, unless later + * usb_gadget_activate() is called. For example, user mode components may + * need to be activated before the system can talk to hosts. + * + * This routine may sleep; it must not be called in interrupt context + * (such as from within a gadget driver's disconnect() callback). + * + * Returns zero on success, else negative errno. + */ +int usb_gadget_deactivate(struct usb_gadget *gadget) +{ + int ret = 0; + + mutex_lock(&gadget->udc->connect_lock); + if (gadget->deactivated) + goto unlock; + + if (gadget->connected) { + ret = usb_gadget_disconnect_locked(gadget); + if (ret) + goto unlock; + + /* + * If gadget was being connected before deactivation, we want + * to reconnect it in usb_gadget_activate(). + */ + gadget->connected = true; + } + gadget->deactivated = true; + +unlock: + mutex_unlock(&gadget->udc->connect_lock); + trace_usb_gadget_deactivate(gadget, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_deactivate); + +/** + * usb_gadget_activate - activate function which is not ready to work + * @gadget: the peripheral being activated + * + * This routine activates gadget which was previously deactivated with + * usb_gadget_deactivate() call. It calls usb_gadget_connect() if needed. + * + * This routine may sleep; it must not be called in interrupt context. + * + * Returns zero on success, else negative errno. + */ +int usb_gadget_activate(struct usb_gadget *gadget) +{ + int ret = 0; + + mutex_lock(&gadget->udc->connect_lock); + if (!gadget->deactivated) + goto unlock; + + gadget->deactivated = false; + + /* + * If gadget has been connected before deactivation, or became connected + * while it was being deactivated, we call usb_gadget_connect(). + */ + if (gadget->connected) { + gadget->connected = false; + ret = usb_gadget_connect_locked(gadget); + } + +unlock: + mutex_unlock(&gadget->udc->connect_lock); + trace_usb_gadget_activate(gadget, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_activate); + +/* ------------------------------------------------------------------------- */ + +#ifdef CONFIG_HAS_DMA + +int usb_gadget_map_request_by_dev(struct device *dev, + struct usb_request *req, int is_in) +{ + if (req->length == 0) + return 0; + + if (req->sg_was_mapped) { + req->num_mapped_sgs = req->num_sgs; + return 0; + } + + if (req->num_sgs) { + int mapped; + + mapped = dma_map_sg(dev, req->sg, req->num_sgs, + is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + if (mapped == 0) { + dev_err(dev, "failed to map SGs\n"); + return -EFAULT; + } + + req->num_mapped_sgs = mapped; + } else { + if (is_vmalloc_addr(req->buf)) { + dev_err(dev, "buffer is not dma capable\n"); + return -EFAULT; + } else if (object_is_on_stack(req->buf)) { + dev_err(dev, "buffer is on stack\n"); + return -EFAULT; + } + + req->dma = dma_map_single(dev, req->buf, req->length, + is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + + if (dma_mapping_error(dev, req->dma)) { + dev_err(dev, "failed to map buffer\n"); + return -EFAULT; + } + + req->dma_mapped = 1; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_gadget_map_request_by_dev); + +int usb_gadget_map_request(struct usb_gadget *gadget, + struct usb_request *req, int is_in) +{ + return usb_gadget_map_request_by_dev(gadget->dev.parent, req, is_in); +} +EXPORT_SYMBOL_GPL(usb_gadget_map_request); + +void usb_gadget_unmap_request_by_dev(struct device *dev, + struct usb_request *req, int is_in) +{ + if (req->length == 0 || req->sg_was_mapped) + return; + + if (req->num_mapped_sgs) { + dma_unmap_sg(dev, req->sg, req->num_sgs, + is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + + req->num_mapped_sgs = 0; + } else if (req->dma_mapped) { + dma_unmap_single(dev, req->dma, req->length, + is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + req->dma_mapped = 0; + } +} +EXPORT_SYMBOL_GPL(usb_gadget_unmap_request_by_dev); + +void usb_gadget_unmap_request(struct usb_gadget *gadget, + struct usb_request *req, int is_in) +{ + usb_gadget_unmap_request_by_dev(gadget->dev.parent, req, is_in); +} +EXPORT_SYMBOL_GPL(usb_gadget_unmap_request); + +#endif /* CONFIG_HAS_DMA */ + +/* ------------------------------------------------------------------------- */ + +/** + * usb_gadget_giveback_request - give the request back to the gadget layer + * @ep: the endpoint to be used with with the request + * @req: the request being given back + * + * This is called by device controller drivers in order to return the + * completed request back to the gadget layer. + */ +void usb_gadget_giveback_request(struct usb_ep *ep, + struct usb_request *req) +{ + if (likely(req->status == 0)) + usb_led_activity(USB_LED_EVENT_GADGET); + + trace_usb_gadget_giveback_request(ep, req, 0); + + req->complete(ep, req); +} +EXPORT_SYMBOL_GPL(usb_gadget_giveback_request); + +/* ------------------------------------------------------------------------- */ + +/** + * gadget_find_ep_by_name - returns ep whose name is the same as sting passed + * in second parameter or NULL if searched endpoint not found + * @g: controller to check for quirk + * @name: name of searched endpoint + */ +struct usb_ep *gadget_find_ep_by_name(struct usb_gadget *g, const char *name) +{ + struct usb_ep *ep; + + gadget_for_each_ep(ep, g) { + if (!strcmp(ep->name, name)) + return ep; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(gadget_find_ep_by_name); + +/* ------------------------------------------------------------------------- */ + +int usb_gadget_ep_match_desc(struct usb_gadget *gadget, + struct usb_ep *ep, struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *ep_comp) +{ + u8 type; + u16 max; + int num_req_streams = 0; + + /* endpoint already claimed? */ + if (ep->claimed) + return 0; + + type = usb_endpoint_type(desc); + max = usb_endpoint_maxp(desc); + + if (usb_endpoint_dir_in(desc) && !ep->caps.dir_in) + return 0; + if (usb_endpoint_dir_out(desc) && !ep->caps.dir_out) + return 0; + + if (max > ep->maxpacket_limit) + return 0; + + /* "high bandwidth" works only at high speed */ + if (!gadget_is_dualspeed(gadget) && usb_endpoint_maxp_mult(desc) > 1) + return 0; + + switch (type) { + case USB_ENDPOINT_XFER_CONTROL: + /* only support ep0 for portable CONTROL traffic */ + return 0; + case USB_ENDPOINT_XFER_ISOC: + if (!ep->caps.type_iso) + return 0; + /* ISO: limit 1023 bytes full speed, 1024 high/super speed */ + if (!gadget_is_dualspeed(gadget) && max > 1023) + return 0; + break; + case USB_ENDPOINT_XFER_BULK: + if (!ep->caps.type_bulk) + return 0; + if (ep_comp && gadget_is_superspeed(gadget)) { + /* Get the number of required streams from the + * EP companion descriptor and see if the EP + * matches it + */ + num_req_streams = ep_comp->bmAttributes & 0x1f; + if (num_req_streams > ep->max_streams) + return 0; + } + break; + case USB_ENDPOINT_XFER_INT: + /* Bulk endpoints handle interrupt transfers, + * except the toggle-quirky iso-synch kind + */ + if (!ep->caps.type_int && !ep->caps.type_bulk) + return 0; + /* INT: limit 64 bytes full speed, 1024 high/super speed */ + if (!gadget_is_dualspeed(gadget) && max > 64) + return 0; + break; + } + + return 1; +} +EXPORT_SYMBOL_GPL(usb_gadget_ep_match_desc); + +/** + * usb_gadget_check_config - checks if the UDC can support the binded + * configuration + * @gadget: controller to check the USB configuration + * + * Ensure that a UDC is able to support the requested resources by a + * configuration, and that there are no resource limitations, such as + * internal memory allocated to all requested endpoints. + * + * Returns zero on success, else a negative errno. + */ +int usb_gadget_check_config(struct usb_gadget *gadget) +{ + if (gadget->ops->check_config) + return gadget->ops->check_config(gadget); + return 0; +} +EXPORT_SYMBOL_GPL(usb_gadget_check_config); + +/* ------------------------------------------------------------------------- */ + +static void usb_gadget_state_work(struct work_struct *work) +{ + struct usb_gadget *gadget = work_to_gadget(work); + struct usb_udc *udc = gadget->udc; + + if (udc) + sysfs_notify(&udc->dev.kobj, NULL, "state"); +} + +void usb_gadget_set_state(struct usb_gadget *gadget, + enum usb_device_state state) +{ + unsigned long flags; + + spin_lock_irqsave(&gadget->state_lock, flags); + gadget->state = state; + if (!gadget->teardown) + schedule_work(&gadget->work); + spin_unlock_irqrestore(&gadget->state_lock, flags); + trace_usb_gadget_set_state(gadget, 0); +} +EXPORT_SYMBOL_GPL(usb_gadget_set_state); + +/* ------------------------------------------------------------------------- */ + +/* Acquire connect_lock before calling this function. */ +static int usb_udc_connect_control_locked(struct usb_udc *udc) __must_hold(&udc->connect_lock) +{ + if (udc->vbus) + return usb_gadget_connect_locked(udc->gadget); + else + return usb_gadget_disconnect_locked(udc->gadget); +} + +static void vbus_event_work(struct work_struct *work) +{ + struct usb_udc *udc = container_of(work, struct usb_udc, vbus_work); + + mutex_lock(&udc->connect_lock); + usb_udc_connect_control_locked(udc); + mutex_unlock(&udc->connect_lock); +} + +/** + * usb_udc_vbus_handler - updates the udc core vbus status, and try to + * connect or disconnect gadget + * @gadget: The gadget which vbus change occurs + * @status: The vbus status + * + * The udc driver calls it when it wants to connect or disconnect gadget + * according to vbus status. + * + * This function can be invoked from interrupt context by irq handlers of + * the gadget drivers, however, usb_udc_connect_control() has to run in + * non-atomic context due to the following: + * a. Some of the gadget driver implementations expect the ->pullup + * callback to be invoked in non-atomic context. + * b. usb_gadget_disconnect() acquires udc_lock which is a mutex. + * Hence offload invocation of usb_udc_connect_control() to workqueue. + */ +void usb_udc_vbus_handler(struct usb_gadget *gadget, bool status) +{ + struct usb_udc *udc = gadget->udc; + + if (udc) { + udc->vbus = status; + schedule_work(&udc->vbus_work); + } +} +EXPORT_SYMBOL_GPL(usb_udc_vbus_handler); + +/** + * usb_gadget_udc_reset - notifies the udc core that bus reset occurs + * @gadget: The gadget which bus reset occurs + * @driver: The gadget driver we want to notify + * + * If the udc driver has bus reset handler, it needs to call this when the bus + * reset occurs, it notifies the gadget driver that the bus reset occurs as + * well as updates gadget state. + */ +void usb_gadget_udc_reset(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + driver->reset(gadget); + usb_gadget_set_state(gadget, USB_STATE_DEFAULT); +} +EXPORT_SYMBOL_GPL(usb_gadget_udc_reset); + +/** + * usb_gadget_udc_start_locked - tells usb device controller to start up + * @udc: The UDC to be started + * + * 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. + * + * Caller should acquire connect_lock before invoking this function. + */ +static inline int usb_gadget_udc_start_locked(struct usb_udc *udc) + __must_hold(&udc->connect_lock) +{ + int ret; + + if (udc->started) { + dev_err(&udc->dev, "UDC had already started\n"); + return -EBUSY; + } + + ret = udc->gadget->ops->udc_start(udc->gadget, udc->driver); + if (!ret) + udc->started = true; + + return ret; +} + +/** + * usb_gadget_udc_stop_locked - tells usb device controller we don't need it anymore + * @udc: The UDC to be stopped + * + * 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. + * + * Caller should acquire connect lock before invoking this function. + */ +static inline void usb_gadget_udc_stop_locked(struct usb_udc *udc) + __must_hold(&udc->connect_lock) +{ + if (!udc->started) { + dev_err(&udc->dev, "UDC had already stopped\n"); + return; + } + + udc->gadget->ops->udc_stop(udc->gadget); + udc->started = false; +} + +/** + * usb_gadget_udc_set_speed - tells usb device controller speed supported by + * current driver + * @udc: The device we want to set maximum speed + * @speed: The maximum speed to allowed to run + * + * This call is issued by the UDC Class driver before calling + * usb_gadget_udc_start_locked() in order to make sure that + * we don't try to connect on speeds the gadget driver + * doesn't support. + */ +static inline void usb_gadget_udc_set_speed(struct usb_udc *udc, + enum usb_device_speed speed) +{ + struct usb_gadget *gadget = udc->gadget; + enum usb_device_speed s; + + if (speed == USB_SPEED_UNKNOWN) + s = gadget->max_speed; + else + s = min(speed, gadget->max_speed); + + if (s == USB_SPEED_SUPER_PLUS && gadget->ops->udc_set_ssp_rate) + gadget->ops->udc_set_ssp_rate(gadget, gadget->max_ssp_rate); + else if (gadget->ops->udc_set_speed) + gadget->ops->udc_set_speed(gadget, s); +} + +/** + * usb_gadget_enable_async_callbacks - tell usb device controller to enable asynchronous callbacks + * @udc: The UDC which should enable async callbacks + * + * This routine is used when binding gadget drivers. It undoes the effect + * of usb_gadget_disable_async_callbacks(); the UDC driver should enable IRQs + * (if necessary) and resume issuing callbacks. + * + * This routine will always be called in process context. + */ +static inline void usb_gadget_enable_async_callbacks(struct usb_udc *udc) +{ + struct usb_gadget *gadget = udc->gadget; + + if (gadget->ops->udc_async_callbacks) + gadget->ops->udc_async_callbacks(gadget, true); +} + +/** + * usb_gadget_disable_async_callbacks - tell usb device controller to disable asynchronous callbacks + * @udc: The UDC which should disable async callbacks + * + * This routine is used when unbinding gadget drivers. It prevents a race: + * The UDC driver doesn't know when the gadget driver's ->unbind callback + * runs, so unless it is told to disable asynchronous callbacks, it might + * issue a callback (such as ->disconnect) after the unbind has completed. + * + * After this function runs, the UDC driver must suppress all ->suspend, + * ->resume, ->disconnect, ->reset, and ->setup callbacks to the gadget driver + * until async callbacks are again enabled. A simple-minded but effective + * way to accomplish this is to tell the UDC hardware not to generate any + * more IRQs. + * + * Request completion callbacks must still be issued. However, it's okay + * to defer them until the request is cancelled, since the pull-up will be + * turned off during the time period when async callbacks are disabled. + * + * This routine will always be called in process context. + */ +static inline void usb_gadget_disable_async_callbacks(struct usb_udc *udc) +{ + struct usb_gadget *gadget = udc->gadget; + + if (gadget->ops->udc_async_callbacks) + gadget->ops->udc_async_callbacks(gadget, false); +} + +/** + * 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__); +} + +static void usb_gadget_release(struct device *dev) +{ + struct usb_gadget *gadget = dev_to_usb_gadget(dev); + struct usb_udc *udc = gadget->udc; + /* Cache the gadget's release routine to prevent UAF */ + void (*release)(struct device *dev) = udc->gadget_release; + + put_device(&udc->dev); + release(dev); +} + +/** + * usb_initialize_gadget - initialize a gadget and its embedded struct device + * @parent: the parent device to this udc. Usually the controller driver's + * device. + * @gadget: the gadget to be initialized. + * @release: a gadget release function. + */ +void usb_initialize_gadget(struct device *parent, struct usb_gadget *gadget, + void (*release)(struct device *dev)) +{ + spin_lock_init(&gadget->state_lock); + gadget->teardown = false; + INIT_WORK(&gadget->work, usb_gadget_state_work); + gadget->dev.parent = parent; + + if (release) + gadget->dev.release = release; + else + gadget->dev.release = usb_udc_nop_release; + + device_initialize(&gadget->dev); + gadget->dev.bus = &gadget_bus_type; +} +EXPORT_SYMBOL_GPL(usb_initialize_gadget); + +/** + * usb_add_gadget - adds a new gadget to the udc class driver list + * @gadget: the gadget to be added to the list. + * + * Returns zero on success, negative errno otherwise. + * Does not do a final usb_put_gadget() if an error occurs. + */ +int usb_add_gadget(struct usb_gadget *gadget) +{ + struct usb_udc *udc; + int ret = -ENOMEM; + + udc = kzalloc_obj(*udc); + if (!udc) + goto error; + + 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 = gadget->dev.parent; + ret = dev_set_name(&udc->dev, "%s", + kobject_name(&gadget->dev.parent->kobj)); + if (ret) + goto err_put_udc; + + udc->gadget = gadget; + gadget->udc = udc; + mutex_init(&udc->connect_lock); + + udc->started = false; + /* + * Align decoupled lifecycles: take a UDC reference to ensure it + * remains allocated until the gadget is released, requiring an + * override of the gadget's release routine to drop it. + */ + udc->gadget_release = gadget->dev.release; + gadget->dev.release = usb_gadget_release; + get_device(&udc->dev); + + mutex_lock(&udc_lock); + list_add_tail(&udc->list, &udc_list); + mutex_unlock(&udc_lock); + INIT_WORK(&udc->vbus_work, vbus_event_work); + + ret = device_add(&udc->dev); + if (ret) + goto err_unlist_udc; + + usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED); + udc->vbus = true; + + ret = ida_alloc(&gadget_id_numbers, GFP_KERNEL); + if (ret < 0) + goto err_del_udc; + gadget->id_number = ret; + dev_set_name(&gadget->dev, "gadget.%d", ret); + + ret = device_add(&gadget->dev); + if (ret) + goto err_free_id; + + ret = sysfs_create_link(&udc->dev.kobj, + &gadget->dev.kobj, "gadget"); + if (ret) + goto err_del_gadget; + + return 0; + + err_del_gadget: + device_del(&gadget->dev); + + err_free_id: + ida_free(&gadget_id_numbers, gadget->id_number); + + err_del_udc: + flush_work(&gadget->work); + device_del(&udc->dev); + + err_unlist_udc: + mutex_lock(&udc_lock); + list_del(&udc->list); + mutex_unlock(&udc_lock); + /* + * Revert the override and drop the UDC reference to prevent + * leaking the UDC if the gadget was statically allocated. + */ + gadget->dev.release = udc->gadget_release; + put_device(&udc->dev); + + err_put_udc: + put_device(&udc->dev); + + error: + return ret; +} +EXPORT_SYMBOL_GPL(usb_add_gadget); + +/** + * 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. + * Calls the gadget release function in the latter case. + */ +int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, + void (*release)(struct device *dev)) +{ + int ret; + + usb_initialize_gadget(parent, gadget, release); + ret = usb_add_gadget(gadget); + if (ret) + usb_put_gadget(gadget); + return ret; +} +EXPORT_SYMBOL_GPL(usb_add_gadget_udc_release); + +/** + * usb_get_gadget_udc_name - get the name of the first UDC controller + * This functions returns the name of the first UDC controller in the system. + * Please note that this interface is usefull only for legacy drivers which + * assume that there is only one UDC controller in the system and they need to + * get its name before initialization. There is no guarantee that the UDC + * of the returned name will be still available, when gadget driver registers + * itself. + * + * Returns pointer to string with UDC controller name on success, NULL + * otherwise. Caller should kfree() returned string. + */ +char *usb_get_gadget_udc_name(void) +{ + struct usb_udc *udc; + char *name = NULL; + + /* For now we take the first available UDC */ + mutex_lock(&udc_lock); + list_for_each_entry(udc, &udc_list, list) { + if (!udc->driver) { + name = kstrdup(udc->gadget->name, GFP_KERNEL); + break; + } + } + mutex_unlock(&udc_lock); + return name; +} +EXPORT_SYMBOL_GPL(usb_get_gadget_udc_name); + +/** + * 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); + +/** + * usb_del_gadget - deletes a gadget and unregisters its udc + * @gadget: the gadget to be deleted. + * + * This will unbind @gadget, if it is bound. + * It will not do a final usb_put_gadget(). + */ +void usb_del_gadget(struct usb_gadget *gadget) +{ + struct usb_udc *udc = gadget->udc; + unsigned long flags; + + if (!udc) + return; + + dev_vdbg(gadget->dev.parent, "unregistering gadget\n"); + + mutex_lock(&udc_lock); + list_del(&udc->list); + mutex_unlock(&udc_lock); + + kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE); + sysfs_remove_link(&udc->dev.kobj, "gadget"); + device_del(&gadget->dev); + /* + * Set the teardown flag before flushing the work to prevent new work + * from being scheduled while we are cleaning up. + */ + spin_lock_irqsave(&gadget->state_lock, flags); + gadget->teardown = true; + spin_unlock_irqrestore(&gadget->state_lock, flags); + flush_work(&gadget->work); + ida_free(&gadget_id_numbers, gadget->id_number); + cancel_work_sync(&udc->vbus_work); + device_unregister(&udc->dev); +} +EXPORT_SYMBOL_GPL(usb_del_gadget); + +/** + * usb_del_gadget_udc - unregisters a gadget + * @gadget: the gadget to be unregistered. + * + * Calls usb_del_gadget() and does a final usb_put_gadget(). + */ +void usb_del_gadget_udc(struct usb_gadget *gadget) +{ + usb_del_gadget(gadget); + usb_put_gadget(gadget); +} +EXPORT_SYMBOL_GPL(usb_del_gadget_udc); + +/* ------------------------------------------------------------------------- */ + +static int gadget_match_driver(struct device *dev, const struct device_driver *drv) +{ + struct usb_gadget *gadget = dev_to_usb_gadget(dev); + struct usb_udc *udc = gadget->udc; + const struct usb_gadget_driver *driver = container_of(drv, + struct usb_gadget_driver, driver); + + /* If the driver specifies a udc_name, it must match the UDC's name */ + if (driver->udc_name && + strcmp(driver->udc_name, dev_name(&udc->dev)) != 0) + return 0; + + /* If the driver is already bound to a gadget, it doesn't match */ + if (driver->is_bound) + return 0; + + /* Otherwise any gadget driver matches any UDC */ + return 1; +} + +static int gadget_bind_driver(struct device *dev) +{ + struct usb_gadget *gadget = dev_to_usb_gadget(dev); + struct usb_udc *udc = gadget->udc; + struct usb_gadget_driver *driver = container_of(dev->driver, + struct usb_gadget_driver, driver); + int ret = 0; + + mutex_lock(&udc_lock); + if (driver->is_bound) { + mutex_unlock(&udc_lock); + return -ENXIO; /* Driver binds to only one gadget */ + } + driver->is_bound = true; + udc->driver = driver; + mutex_unlock(&udc_lock); + + dev_dbg(&udc->dev, "binding gadget driver [%s]\n", driver->function); + + usb_gadget_udc_set_speed(udc, driver->max_speed); + + ret = driver->bind(udc->gadget, driver); + if (ret) + goto err_bind; + + mutex_lock(&udc->connect_lock); + ret = usb_gadget_udc_start_locked(udc); + if (ret) { + mutex_unlock(&udc->connect_lock); + goto err_start; + } + usb_gadget_enable_async_callbacks(udc); + udc->allow_connect = true; + ret = usb_udc_connect_control_locked(udc); + if (ret) + goto err_connect_control; + + mutex_unlock(&udc->connect_lock); + + kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); + return 0; + + err_connect_control: + udc->allow_connect = false; + usb_gadget_disable_async_callbacks(udc); + if (gadget->irq) + synchronize_irq(gadget->irq); + usb_gadget_udc_stop_locked(udc); + mutex_unlock(&udc->connect_lock); + + err_start: + driver->unbind(udc->gadget); + + err_bind: + if (ret != -EISNAM) + dev_err(&udc->dev, "failed to start %s: %d\n", + driver->function, ret); + + mutex_lock(&udc_lock); + udc->driver = NULL; + driver->is_bound = false; + mutex_unlock(&udc_lock); + + return ret; +} + +static void gadget_unbind_driver(struct device *dev) +{ + struct usb_gadget *gadget = dev_to_usb_gadget(dev); + struct usb_udc *udc = gadget->udc; + struct usb_gadget_driver *driver = udc->driver; + + dev_dbg(&udc->dev, "unbinding gadget driver [%s]\n", driver->function); + + udc->allow_connect = false; + cancel_work_sync(&udc->vbus_work); + mutex_lock(&udc->connect_lock); + usb_gadget_disconnect_locked(gadget); + usb_gadget_disable_async_callbacks(udc); + if (gadget->irq) + synchronize_irq(gadget->irq); + mutex_unlock(&udc->connect_lock); + + udc->driver->unbind(gadget); + + mutex_lock(&udc->connect_lock); + usb_gadget_udc_stop_locked(udc); + mutex_unlock(&udc->connect_lock); + + mutex_lock(&udc_lock); + driver->is_bound = false; + udc->driver = NULL; + mutex_unlock(&udc_lock); + + kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); +} + +/* ------------------------------------------------------------------------- */ + +int usb_gadget_register_driver_owner(struct usb_gadget_driver *driver, + struct module *owner, const char *mod_name) +{ + int ret; + + if (!driver || !driver->bind || !driver->setup) + return -EINVAL; + + driver->driver.bus = &gadget_bus_type; + driver->driver.owner = owner; + driver->driver.mod_name = mod_name; + driver->driver.probe_type = PROBE_FORCE_SYNCHRONOUS; + ret = driver_register(&driver->driver); + if (ret) { + pr_warn("%s: driver registration failed: %d\n", + driver->function, ret); + return ret; + } + + mutex_lock(&udc_lock); + if (!driver->is_bound) { + if (driver->match_existing_only) { + pr_warn("%s: couldn't find an available UDC or it's busy\n", + driver->function); + ret = -EBUSY; + } else { + pr_info("%s: couldn't find an available UDC\n", + driver->function); + ret = 0; + } + } + mutex_unlock(&udc_lock); + + if (ret) + driver_unregister(&driver->driver); + return ret; +} +EXPORT_SYMBOL_GPL(usb_gadget_register_driver_owner); + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + if (!driver || !driver->unbind) + return -EINVAL; + + driver_unregister(&driver->driver); + return 0; +} +EXPORT_SYMBOL_GPL(usb_gadget_unregister_driver); + +/* ------------------------------------------------------------------------- */ + +static ssize_t 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_WO(srp); + +static ssize_t soft_connect_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); + ssize_t ret; + + device_lock(&udc->gadget->dev); + if (!udc->driver) { + dev_err(dev, "soft-connect without a gadget driver\n"); + ret = -EOPNOTSUPP; + goto out; + } + + if (sysfs_streq(buf, "connect")) { + mutex_lock(&udc->connect_lock); + usb_gadget_udc_start_locked(udc); + usb_gadget_connect_locked(udc->gadget); + mutex_unlock(&udc->connect_lock); + } else if (sysfs_streq(buf, "disconnect")) { + mutex_lock(&udc->connect_lock); + usb_gadget_disconnect_locked(udc->gadget); + usb_gadget_udc_stop_locked(udc); + mutex_unlock(&udc->connect_lock); + } else { + dev_err(dev, "unsupported command '%s'\n", buf); + ret = -EINVAL; + goto out; + } + + ret = n; +out: + device_unlock(&udc->gadget->dev); + return ret; +} +static DEVICE_ATTR_WO(soft_connect); + +static ssize_t 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_RO(state); + +static ssize_t function_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_udc *udc = container_of(dev, struct usb_udc, dev); + struct usb_gadget_driver *drv; + int rc = 0; + + mutex_lock(&udc_lock); + drv = udc->driver; + if (drv && drv->function) + rc = scnprintf(buf, PAGE_SIZE, "%s\n", drv->function); + mutex_unlock(&udc_lock); + return rc; +} +static DEVICE_ATTR_RO(function); + +#define USB_UDC_SPEED_ATTR(name, param) \ +ssize_t name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_udc *udc = container_of(dev, struct usb_udc, dev); \ + return scnprintf(buf, PAGE_SIZE, "%s\n", \ + usb_speed_string(udc->gadget->param)); \ +} \ +static DEVICE_ATTR_RO(name) + +static USB_UDC_SPEED_ATTR(current_speed, speed); +static USB_UDC_SPEED_ATTR(maximum_speed, max_speed); + +#define USB_UDC_ATTR(name) \ +ssize_t 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 scnprintf(buf, PAGE_SIZE, "%d\n", gadget->name); \ +} \ +static DEVICE_ATTR_RO(name) + +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 USB_UDC_ATTR(is_selfpowered); + +static struct attribute *usb_udc_attrs[] = { + &dev_attr_srp.attr, + &dev_attr_soft_connect.attr, + &dev_attr_state.attr, + &dev_attr_function.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, + &dev_attr_is_selfpowered.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(const struct device *dev, struct kobj_uevent_env *env) +{ + const 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; + } + + mutex_lock(&udc_lock); + if (udc->driver) + ret = add_uevent_var(env, "USB_UDC_DRIVER=%s", + udc->driver->function); + mutex_unlock(&udc_lock); + if (ret) { + dev_err(dev, "failed to add uevent USB_UDC_DRIVER\n"); + return ret; + } + + return 0; +} + +static const struct class udc_class = { + .name = "udc", + .dev_uevent = usb_udc_uevent, +}; + +static const struct bus_type gadget_bus_type = { + .name = "gadget", + .probe = gadget_bind_driver, + .remove = gadget_unbind_driver, + .match = gadget_match_driver, +}; + +static int __init usb_udc_init(void) +{ + int rc; + + rc = class_register(&udc_class); + if (rc) + return rc; + + rc = bus_register(&gadget_bus_type); + if (rc) + class_unregister(&udc_class); + return rc; +} +subsys_initcall(usb_udc_init); + +static void __exit usb_udc_exit(void) +{ + bus_unregister(&gadget_bus_type); + class_unregister(&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/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c new file mode 100644 index 000000000..c0e40fa6d --- /dev/null +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -0,0 +1,2953 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 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 (device); + * - 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. + * + * Note: The emulation does not include isochronous transfers! + */ + +#include +#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 */ +#define POWER_BUDGET_3 900 /* in mA */ + +#define DUMMY_TIMER_INT_NSECS 125000 /* 1 microframe */ + +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 structures */ +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 struct { + const char *name; + const struct usb_ep_caps caps; +} ep_info[] = { +#define EP_INFO(_name, _caps) \ + { \ + .name = _name, \ + .caps = _caps, \ + } + +/* we don't provide isochronous endpoints since we don't support them */ +#define TYPE_BULK_OR_INT (USB_EP_CAPS_TYPE_BULK | USB_EP_CAPS_TYPE_INT) + + /* everyone has ep0 */ + EP_INFO(ep0name, + USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, USB_EP_CAPS_DIR_ALL)), + /* act like a pxa250: fifteen fixed function endpoints */ + EP_INFO("ep1in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep2out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), +/* + EP_INFO("ep3in-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep4out-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, USB_EP_CAPS_DIR_OUT)), +*/ + EP_INFO("ep5in-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep6in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep7out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), +/* + EP_INFO("ep8in-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep9out-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, USB_EP_CAPS_DIR_OUT)), +*/ + EP_INFO("ep10in-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep11in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep12out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), +/* + EP_INFO("ep13in-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep14out-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, USB_EP_CAPS_DIR_OUT)), +*/ + EP_INFO("ep15in-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, USB_EP_CAPS_DIR_IN)), + + /* or like sa1100: two fixed function endpoints */ + EP_INFO("ep1out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep2in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + + /* and now some generic EPs so we have enough in multi config */ + EP_INFO("ep-aout", + USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep-bin", + USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep-cout", + USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep-dout", + USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep-ein", + USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep-fout", + USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep-gin", + USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep-hout", + USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep-iout", + USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep-jin", + USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep-kout", + USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep-lin", + USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep-mout", + USB_EP_CAPS(TYPE_BULK_OR_INT, USB_EP_CAPS_DIR_OUT)), + +#undef EP_INFO +}; + +#define DUMMY_ENDPOINTS ARRAY_SIZE(ep_info) + +/*-------------------------------------------------------------------------*/ + +#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 hrtimer timer; + u32 port_status; + u32 old_status; + unsigned long re_timeout; + + struct usb_device *udev; + struct list_head urbp_list; + struct urbp *next_frame_urbp; + + u32 stream_en_ep; + u8 num_stream[30 / 2]; + + unsigned timer_pending:1; + unsigned active:1; + unsigned old_active:1; + unsigned resuming:1; +}; + +struct dummy { + spinlock_t lock; + + /* + * DEVICE/GADGET side support + */ + struct dummy_ep ep[DUMMY_ENDPOINTS]; + int address; + int callback_usage; + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct dummy_request fifo_req; + u8 fifo_buf[FIFO_SIZE]; + u16 devstatus; + unsigned ints_enabled:1; + unsigned udc_suspended:1; + unsigned pullup:1; + unsigned fifo_req_busy:1; + + /* + * 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); +} + +/*-------------------------------------------------------------------------*/ + +/* DEVICE/GADGET SIDE UTILITY ROUTINES */ + +/* + * Give back a gadget request with dum->lock dropped around the callback. + * If @req is the shared fifo_req, clear fifo_req_busy afterward: the flag + * was set in dummy_queue() when the shared request was taken and must stay + * set until its completion callback has returned; list_del_init() alone + * makes the request look idle while the callback is still running. + * Caller holds dum->lock and has already done list_del_init() + status. + */ +static void dummy_giveback(struct dummy *dum, struct usb_ep *_ep, + struct dummy_request *req) +{ + bool fifo = req == &dum->fifo_req; + + spin_unlock(&dum->lock); + usb_gadget_giveback_request(_ep, &req->req); + spin_lock(&dum->lock); + if (fifo) + dum->fifo_req_busy = 0; +} + +/* 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; + + dummy_giveback(dum, &ep->ep, req); + } +} + +/* caller must hold lock */ +static void stop_activity(struct dummy *dum) +{ + int i; + + /* 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 */ + for (i = 0; i < DUMMY_ENDPOINTS; ++i) + nuke(dum, &dum->ep[i]); + + /* 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) && + (dum_hcd->port_status & + USB_PORT_STAT_LINK_STATE) == USB_SS_PORT_LS_U0 && + 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) + __must_hold(&dum->lock) +{ + struct dummy *dum = dum_hcd->dum; + unsigned int power_bit; + + 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); + power_bit = (dummy_hcd_to_hcd(dum_hcd)->speed == HCD_USB3 ? + USB_SS_PORT_STAT_POWER : USB_PORT_STAT_POWER); + + if ((dum_hcd->port_status & USB_PORT_STAT_ENABLE) == 0 || + dum_hcd->active) + dum_hcd->resuming = 0; + + /* Currently !connected or in reset */ + if ((dum_hcd->port_status & power_bit) == 0 || + (dum_hcd->port_status & USB_PORT_STAT_RESET) != 0) { + unsigned int disconnect = power_bit & + dum_hcd->old_status & (~dum_hcd->port_status); + unsigned int reset = USB_PORT_STAT_RESET & + (~dum_hcd->old_status) & dum_hcd->port_status; + + /* Report reset and disconnect events to the driver */ + if (dum->ints_enabled && (disconnect || reset)) { + ++dum->callback_usage; + /* + * stop_activity() can drop dum->lock, so it must + * not come between the dum->ints_enabled test + * and the ++dum->callback_usage. + */ + stop_activity(dum); + spin_unlock(&dum->lock); + if (reset) + usb_gadget_udc_reset(&dum->gadget, dum->driver); + else + dum->driver->disconnect(&dum->gadget); + spin_lock(&dum->lock); + --dum->callback_usage; + } + } else if (dum_hcd->active != dum_hcd->old_active && + dum->ints_enabled) { + ++dum->callback_usage; + spin_unlock(&dum->lock); + if (dum_hcd->old_active && dum->driver->suspend) + dum->driver->suspend(&dum->gadget); + else if (!dum_hcd->old_active && dum->driver->resume) + dum->driver->resume(&dum->gadget); + spin_lock(&dum->lock); + --dum->callback_usage; + } + + dum_hcd->old_status = dum_hcd->port_status; + dum_hcd->old_active = dum_hcd->active; +} + +/*-------------------------------------------------------------------------*/ + +/* DEVICE/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); + + /* 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 */ + fallthrough; + 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 */ + fallthrough; + case USB_SPEED_FULL: + if (max <= 64) + break; + /* save a return statement */ + fallthrough; + 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 */ + fallthrough; + case USB_SPEED_FULL: + if (max <= 1023) + break; + /* save a return statement */ + fallthrough; + 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, + usb_endpoint_num(desc), + (desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out", + usb_ep_type_string(usb_endpoint_type(desc)), + max, str_enabled_disabled(ep->stream_en)); + + /* 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; + + 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; + nuke(dum, ep); + spin_unlock_irqrestore(&dum->lock, flags); + + dev_dbg(udc_dev(dum), "disabled %s\n", _ep->name); + return 0; +} + +static struct usb_request *dummy_alloc_request(struct usb_ep *_ep, + gfp_t mem_flags) +{ + struct dummy_request *req; + + if (!_ep) + return NULL; + + req = kzalloc_obj(*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) && + !dum->fifo_req_busy && + list_empty(&ep->queue) && + _req->length <= FIFO_SIZE) { + req = &dum->fifo_req; + dum->fifo_req_busy = 1; + 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; + usb_gadget_giveback_request(_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, *iter; + + if (!_ep || !_req) + return retval; + ep = usb_ep_to_dummy_ep(_ep); + dum = ep_to_dummy(ep); + + if (!dum->driver) + return -ESHUTDOWN; + + spin_lock_irqsave(&dum->lock, flags); + list_for_each_entry(iter, &ep->queue, queue) { + if (&iter->req != _req) + continue; + list_del_init(&iter->queue); + _req->status = -ECONNRESET; + req = iter; + retval = 0; + break; + } + + 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); + dummy_giveback(dum, _ep, req); + } + spin_unlock_irqrestore(&dum->lock, 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 timespec64 ts64; + + ktime_get_ts64(&ts64); + return ts64.tv_nsec / NSEC_PER_MSEC; +} + +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; + + _gadget->is_selfpowered = (value != 0); + 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); + 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 void dummy_udc_set_speed(struct usb_gadget *_gadget, + enum usb_device_speed speed) +{ + struct dummy *dum; + + dum = gadget_dev_to_dummy(&_gadget->dev); + dum->gadget.speed = speed; + dummy_udc_update_ep0(dum); +} + +static void dummy_udc_async_callbacks(struct usb_gadget *_gadget, bool enable) +{ + struct dummy *dum = gadget_dev_to_dummy(&_gadget->dev); + + spin_lock_irq(&dum->lock); + dum->ints_enabled = enable; + if (!enable) { + /* + * Emulate synchronize_irq(): wait for callbacks to finish. + * This has to happen after emulated interrupts are disabled + * (dum->ints_enabled is clear) and before the unbind callback, + * just like the call to synchronize_irq() in + * gadget/udc/core:gadget_unbind_driver(). + */ + while (dum->callback_usage > 0) { + spin_unlock_irq(&dum->lock); + usleep_range(1000, 2000); + spin_lock_irq(&dum->lock); + } + } + spin_unlock_irq(&dum->lock); +} + +static int dummy_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static int dummy_udc_stop(struct usb_gadget *g); + +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, + .udc_set_speed = dummy_udc_set_speed, + .udc_async_callbacks = dummy_udc_async_callbacks, +}; + +/*-------------------------------------------------------------------------*/ + +/* "function" sysfs attribute */ +static ssize_t function_show(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_RO(function); + +/*-------------------------------------------------------------------------*/ + +/* + * 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 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; + + switch (g->speed) { + /* All the speeds we support */ + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + case USB_SPEED_SUPER: + break; + default: + dev_err(dummy_dev(dum_hcd), "Unsupported driver max speed %d\n", + driver->max_speed); + return -EINVAL; + } + + /* + * DEVICE side init ... the layer above hardware, which + * can't enumerate without help from the driver we're binding. + */ + + spin_lock_irq(&dum->lock); + dum->devstatus = 0; + dum->driver = driver; + spin_unlock_irq(&dum->lock); + + return 0; +} + +static int dummy_udc_stop(struct usb_gadget *g) +{ + struct dummy_hcd *dum_hcd = gadget_to_dummy_hcd(g); + struct dummy *dum = dum_hcd->dum; + + spin_lock_irq(&dum->lock); + dum->ints_enabled = 0; + stop_activity(dum); + dum->driver = NULL; + spin_unlock_irq(&dum->lock); + + 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_info[i].name) + break; + ep->ep.name = ep_info[i].name; + ep->ep.caps = ep_info[i].caps; + 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; + usb_ep_set_maxpacket_limit(&ep->ep, ~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)); + /* Clear usb_gadget region for new registration to udc-core */ + memzero_explicit(&dum->gadget, sizeof(struct usb_gadget)); + dum->gadget.name = gadget_name; + dum->gadget.ops = &dummy_ops; + if (mod_data.is_super_speed) + dum->gadget.max_speed = USB_SPEED_SUPER; + else if (mod_data.is_high_speed) + dum->gadget.max_speed = USB_SPEED_HIGH; + else + dum->gadget.max_speed = USB_SPEED_FULL; + + 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 void 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); +} + +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 = gadget_name, + }, +}; + +/*-------------------------------------------------------------------------*/ + +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; +} + +/* 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_obj(*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 (!dum_hcd->next_frame_urbp) + dum_hcd->next_frame_urbp = 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 (!dum_hcd->timer_pending) { + dum_hcd->timer_pending = 1; + hrtimer_start(&dum_hcd->timer, ns_to_ktime(DUMMY_TIMER_INT_NSECS), + HRTIMER_MODE_REL_SOFT); + } + + 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 == 0 && !dum_hcd->timer_pending) { + dum_hcd->timer_pending = 1; + hrtimer_start(&dum_hcd->timer, ns_to_ktime(0), HRTIMER_MODE_REL_SOFT); + } + + 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_urb_dir_in(urb); + 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; + int sent = 0; + +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_urb_dir_in(urb); + 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; + + /* send multiple of maxpacket first, then remainder */ + if (len >= ep->ep.maxpacket) { + is_short = 0; + if (len % ep->ep.maxpacket) + rescan = 1; + len -= len % ep->ep.maxpacket; + } else { + is_short = 1; + } + + len = dummy_perform_transfer(urb, req, len); + + ep->last_io = jiffies; + if ((int)len < 0) { + req->req.status = len; + } else { + limit -= len; + sent += 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 { + *status = 0; + if (host_len > dev_len) + req->req.status = -EOVERFLOW; + else + req->req.status = 0; + } + + /* + * many requests terminate without a short packet. + * send a zlp if demanded by flags. + */ + } else { + if (req->req.length == req->req.actual) { + if (req->req.zero && to_host) + rescan = 1; + else + req->req.status = 0; + } + if (urb->transfer_buffer_length == urb->actual_length) { + if (urb->transfer_flags & URB_ZERO_PACKET && + !to_host) + rescan = 1; + else + *status = 0; + } + } + + /* device side completion --> continuable */ + if (req->req.status != -EINPROGRESS) { + list_del_init(&req->queue); + + dummy_giveback(dum, &ep->ep, req); + + /* 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; + + /* request not fully transferred; stop iterating to + * preserve data ordering across queued requests. + */ + if (req->req.actual < req->req.length) + break; + } + return sent; +} + +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_mult(ep->desc); + 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 (!dum->ints_enabled) + 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_hcd: 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 that the callbacks are invoked from soft interrupt + * context. + */ +static enum hrtimer_restart dummy_timer(struct hrtimer *t) +{ + struct dummy_hcd *dum_hcd = timer_container_of(dum_hcd, t, + timer); + 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 */ + /* FIXME: account for transaction and packet overhead */ + 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: /* Can't happen */ + dev_err(dummy_dev(dum_hcd), "bogus device speed\n"); + total = 0; + break; + } + + /* look at each urb queued by the host side driver */ + spin_lock_irqsave(&dum->lock, flags); + dum_hcd->timer_pending = 0; + + if (!dum_hcd->udev) { + dev_err(dummy_dev(dum_hcd), + "timer fired with no URBs pending?\n"); + spin_unlock_irqrestore(&dum->lock, flags); + return HRTIMER_NORESTART; + } + dum_hcd->next_frame_urbp = NULL; + + for (i = 0; i < DUMMY_ENDPOINTS; i++) { + if (!ep_info[i].name) + 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 status = -EINPROGRESS; + + /* stop when we reach URBs queued after the timer interrupt */ + if (urbp == dum_hcd->next_frame_urbp) + break; + + urb = urbp->urb; + if (urb->unlinked) + goto return_urb; + else if (dum_hcd->rh_state != DUMMY_RH_RUNNING) + continue; + + /* Used up this frame's bandwidth? */ + if (total <= 0) + continue; + + /* find the gadget's ep for this request (if configured) */ + address = usb_pipeendpoint (urb->pipe); + if (usb_urb_dir_in(urb)) + 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; + + 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); + + dummy_giveback(dum, &ep->ep, req); + 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) { + ++dum->callback_usage; + spin_unlock(&dum->lock); + value = dum->driver->setup(&dum->gadget, + &setup); + spin_lock(&dum->lock); + --dum->callback_usage; + + 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: + /* + * We don't support isochronous. But if we did, + * here are some of the issues we'd have to face: + * + * 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 = -EINVAL; /* fail all xfers */ + 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->timer_pending && + dum_hcd->rh_state == DUMMY_RH_RUNNING) { + /* want a 1 msec delay here */ + dum_hcd->timer_pending = 1; + hrtimer_start(&dum_hcd->timer, ns_to_ktime(DUMMY_TIMER_INT_NSECS), + HRTIMER_MODE_REL_SOFT); + } + + spin_unlock_irqrestore(&dum->lock, flags); + + return HRTIMER_NORESTART; +} + +/*-------------------------------------------------------------------------*/ + +#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 = USB_DT_SS_HUB; + desc->bDescLength = 12; + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | + HUB_CHAR_COMMON_OCPM); + desc->bNbrPorts = 1; + desc->u.ss.bHubHdrDecLat = 0x04; /* Worst case: 0.4 micro sec*/ + desc->u.ss.DeviceRemovable = 0; +} + +static inline void hub_descriptor(struct usb_hub_descriptor *desc) +{ + memset(desc, 0, sizeof *desc); + desc->bDescriptorType = USB_DT_HUB; + desc->bDescLength = 9; + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | + HUB_CHAR_COMMON_OCPM); + desc->bNbrPorts = 1; + desc->u.hs.DeviceRemovable[0] = 0; + desc->u.hs.DeviceRemovable[1] = 0xff; /* PortPwrCtrlMask */ +} + +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: + if (wIndex != 1) + goto error; + 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: + dev_dbg(dummy_dev(dum_hcd), "power-off\n"); + 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_ENABLE: + case USB_PORT_FEAT_C_ENABLE: + case USB_PORT_FEAT_C_SUSPEND: + /* Not allowed for USB-3 */ + if (hcd->speed == HCD_USB3) + goto error; + fallthrough; + case USB_PORT_FEAT_C_CONNECTION: + case USB_PORT_FEAT_C_RESET: + dum_hcd->port_status &= ~(1 << wValue); + set_link_state(dum_hcd); + break; + default: + /* Disallow INDICATOR and C_OVER_CURRENT */ + goto error; + } + 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: + 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: + if (wIndex != 1) + goto error; + 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; + } + fallthrough; + case USB_PORT_FEAT_RESET: + if (!(dum_hcd->port_status & USB_PORT_STAT_CONNECTION)) + break; + /* if it's already enabled, disable */ + if (hcd->speed == HCD_USB3) { + 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); + dum_hcd->port_status |= USB_PORT_STAT_RESET; + } + /* + * 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); + set_link_state(dum_hcd); + break; + case USB_PORT_FEAT_C_CONNECTION: + case USB_PORT_FEAT_C_RESET: + case USB_PORT_FEAT_C_ENABLE: + case USB_PORT_FEAT_C_SUSPEND: + /* Not allowed for USB-3, and ignored for USB-2 */ + if (hcd->speed == HCD_USB3) + goto error; + break; + default: + /* Disallow TEST, INDICATOR, and C_OVER_CURRENT */ + goto error; + } + 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)) { + dum_hcd->timer_pending = 1; + hrtimer_start(&dum_hcd->timer, ns_to_ktime(0), HRTIMER_MODE_REL_SOFT); + } + 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 scnprintf(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_urb_dir_in(urb) ? "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 urbs_show(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_RO(urbs); + +static int dummy_start_ss(struct dummy_hcd *dum_hcd) +{ + hrtimer_setup(&dum_hcd->timer, dummy_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + 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_3; + 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); + + /* + * HOST side init ... we emulate a root hub that'll only ever + * talk to one device (the gadget 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); + hrtimer_setup(&dum_hcd->timer, dummy_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + 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_hcd *dum_hcd = hcd_to_dummy_hcd(hcd); + + hrtimer_cancel(&dum_hcd->timer); + dum_hcd->timer_pending = 0; + device_remove_file(dummy_dev(dum_hcd), &dev_attr_urbs); + dev_info(dummy_dev(dum_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), + + .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_USB3 | HCD_SHARED; + else if (mod_data.is_high_speed) + dummy_hcd.flags = HCD_USB2; + else + dummy_hcd.flags = HCD_USB11; + 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 void 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; +} + +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 = driver_name, + }, +}; + +/*-------------------------------------------------------------------------*/ +#define MAX_NUM_UDC 32 +static struct platform_device *the_udc_pdev[MAX_NUM_UDC]; +static struct platform_device *the_hcd_pdev[MAX_NUM_UDC]; + +static int __init dummy_hcd_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_obj(struct dummy); + 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(dummy_hcd_init); + +static void __exit dummy_hcd_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(dummy_hcd_cleanup); diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.c b/drivers/usb/gadget/udc/fsl_qe_udc.c new file mode 100644 index 000000000..603c77ff1 --- /dev/null +++ b/drivers/usb/gadget/udc/fsl_qe_udc.c @@ -0,0 +1,2722 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#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 +#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 const 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); + + usb_gadget_giveback_request(&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_obj(*ep->rxframe, GFP_ATOMIC); + if (!ep->rxframe) + 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) { + 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 divisible 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; + fallthrough; + 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; + fallthrough; + case USB_SPEED_FULL: + if (max <= 64) + break; + fallthrough; + 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; + fallthrough; + case USB_SPEED_FULL: + if (max <= 1023) + break; + fallthrough; + 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; + } + fallthrough; + 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_obj(*ep->txframe, GFP_ATOMIC); + if (!ep->txframe) + 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(struct tasklet_struct *t) +{ + struct qe_udc *udc = from_tasklet(udc, t, rx_tasklet); + 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 transmitted 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_obj(*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 = NULL; + struct qe_req *iter; + 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(iter, &ep->queue, queue) { + if (&iter->req != _req) + continue; + req = iter; + break; + } + + if (!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 const 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) + return tmp & 0x07ff; + return -EINVAL; +} + +static int fsl_qe_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static int fsl_qe_stop(struct usb_gadget *gadget); + +/* 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); + usb_gadget_udc_reset(&udc->gadget, udc->driver); + 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 usb_request *usb_req; + 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; + if (pipe >= USB_MAX_ENDPOINTS) + goto stall; + 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; + } + } + + usb_req = qe_alloc_request(&ep->ep, GFP_KERNEL); + if (!usb_req) + goto stall; + + req = container_of(usb_req, 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 packet'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); + + /* 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); + + return 0; +} + +static int fsl_qe_stop(struct usb_gadget *gadget) +{ + 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; + + 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 long tmp_addr = 0; + struct usb_device_para __iomem *usbpram; + unsigned int i; + u64 size; + u32 offset; + + udc = kzalloc_obj(*udc); + if (!udc) + 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]; + + if (pipe_num == 0) { + ep->ep.caps.type_control = true; + } else { + ep->ep.caps.type_iso = true; + ep->ep.caps.type_bulk = true; + ep->ep.caps.type_int = true; + } + + ep->ep.caps.dir_in = true; + ep->ep.caps.dir_out = true; + + ep->ep.ops = &qe_ep_ops; + ep->stopped = 1; + usb_ep_set_maxpacket_limit(&ep->ep, (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; + struct device_node *np = ofdev->dev.of_node; + struct qe_ep *ep; + unsigned int ret = 0; + unsigned int i; + const void *prop; + + 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)device_get_match_data(&ofdev->dev); + 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 = devm_kzalloc(&ofdev->dev, 256, GFP_KERNEL); + if (udc->nullbuf == NULL) { + ret = -ENOMEM; + goto err3; + } + + /* buffer for data of get_status request */ + udc->statusbuf = devm_kzalloc(&ofdev->dev, 2, GFP_KERNEL); + if (udc->statusbuf == NULL) { + ret = -ENOMEM; + goto err3; + } + + 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_setup(&udc->rx_tasklet, ep_rx_tasklet); + /* 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 err4; + } + + ret = usb_add_gadget_udc_release(&ofdev->dev, &udc->gadget, + qe_udc_release); + if (ret) + goto err5; + + platform_set_drvdata(ofdev, udc); + dev_info(udc->dev, + "%s USB controller initialized as device\n", + (udc->soc_type == PORT_QE) ? "QE" : "CPM"); + return 0; + +err5: + free_irq(udc->usb_irq, udc); +err4: + 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); + } +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 void qe_udc_remove(struct platform_device *ofdev) +{ + struct qe_udc *udc = platform_get_drvdata(ofdev); + struct qe_ep *ep; + unsigned int size; + DECLARE_COMPLETION_ONSTACK(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); + } + + 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); +} + +/*-------------------------------------------------------------------------*/ +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 = driver_name, + .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/udc/fsl_qe_udc.h b/drivers/usb/gadget/udc/fsl_qe_udc.h new file mode 100644 index 000000000..53ca0ff7c --- /dev/null +++ b/drivers/usb/gadget/udc/fsl_qe_udc.h @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#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; /* Endpoint zero state */ + u32 ep0_dir; /* Endpoint 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/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c new file mode 100644 index 000000000..20392409a --- /dev/null +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -0,0 +1,2693 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#define pr_fmt(x) "udc: " x + +#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 struct usb_dr_device __iomem *dr_regs; + +static struct usb_sys_interface __iomem *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) +__releases(ep->udc->lock) +__acquires(ep->udc->lock) +{ + 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)) + 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); + + ep->stopped = 1; + + spin_unlock(&ep->udc->lock); + + usb_gadget_giveback_request(&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; + fallthrough; + case FSL_USB2_PHY_UTMI: + case FSL_USB2_PHY_UTMI_DUAL: + 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)) { + dev_err(udc->dev, "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); + + dev_vdbg(udc->dev, + "vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x\n", + 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 */ + if (udc->pdata->have_sysif_regs) { + ctrl = __raw_readl(&usb_sys_regs->control); + ctrl |= USB_CTRL_IOENB; + __raw_writel(ctrl, &usb_sys_regs->control); + } + +#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: + dev_vdbg(udc->dev, "error ep type is %d\n", 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 initialization 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; + + 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 = usb_endpoint_maxp_mult(desc); + /* 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; + + dev_vdbg(udc->dev, "enabled %s (ep%d%s) maxpacket %d\n", + 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; + u32 epctrl; + int ep_num; + + ep = container_of(_ep, struct fsl_ep, ep); + if (!_ep || !ep->ep.desc) + 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); + + dev_vdbg(udc->dev, "disabled %s OK\n", _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; + + req = kzalloc_obj(*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; + + 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) + dev_vdbg(udc_controller->dev, "multi-dtd request!\n"); + /* 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(); + + dev_vdbg(udc_controller->dev, "length = %d address= 0x%x\n", *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 = ep->udc; + unsigned long flags; + int ret; + + /* catch various bogus parameters */ + if (!_req || !req->req.complete || !req->req.buf + || !list_empty(&req->queue)) { + dev_vdbg(udc->dev, "%s, bad params\n", __func__); + return -EINVAL; + } + if (unlikely(!ep->ep.desc)) { + dev_vdbg(udc->dev, "%s, bad ep\n", __func__); + return -EINVAL; + } + if (usb_endpoint_xfer_isoc(ep->ep.desc)) { + if (req->req.length > ep->ep.maxpacket) + return -EMSGSIZE; + } + + 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 = NULL; + struct fsl_req *iter; + 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(iter, &ep->queue, queue) { + if (&iter->req != _req) + continue; + req = iter; + break; + } + if (!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; + 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: + dev_vdbg(udc->dev, "%s %s halt stat %d\n", 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; + + if (!_ep || !_ep->desc || !(_ep->desc->bEndpointAddress&0xF)) + return -ENODEV; + + ep = container_of(_ep, struct fsl_ep, ep); + + 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)) { + dev_err(udc_controller->dev, + "ep flush timeout\n"); + return; + } + cpu_relax(); + } + /* See if we need to flush again */ + } while (fsl_readl(&dr_regs->endptstatus) & bits); +} + +static const 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); + dev_vdbg(udc->dev, "VBUS %s\n", str_on_off(is_active)); + 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/disconnect + */ +static int fsl_pullup(struct usb_gadget *gadget, int is_on) +{ + struct fsl_udc *udc; + + udc = container_of(gadget, struct fsl_udc, gadget); + + if (!udc->vbus_active) + return -EOPNOTSUPP; + + 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); + +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, +}; + +/* + * Empty complete function used by this driver to fill in the req->complete + * field when creating a request since the complete field is mandatory. + */ +static void fsl_noop_complete(struct usb_ep *ep, struct usb_request *req) { } + +/* 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 = fsl_noop_complete; + 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->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 = udc->gadget.is_selfpowered; + 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 request 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 = fsl_noop_complete; + 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) +__releases(udc->lock) +__acquires(udc->lock) +{ + 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: + dev_err(udc->dev, "Unexpected 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 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; + 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) { + dev_err(udc->dev, "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) { + dev_vdbg(udc->dev, "Transfer overflow\n"); + status = -EPROTO; + break; + } else if (errors & DTD_STATUS_TRANSACTION_ERR) { + dev_vdbg(udc->dev, "ISO error\n"); + status = -EILSEQ; + break; + } else + dev_err(udc->dev, + "Unknown error has occurred (0x%x)!\n", + errors); + + } else if (hc32_to_cpu(curr_td->size_ioc_sts) + & DTD_STATUS_ACTIVE) { + dev_vdbg(udc->dev, "Request not complete\n"); + status = REQ_UNCOMPLETE; + return status; + } else if (remaining_length) { + if (direction) { + dev_vdbg(udc->dev, + "Transmit dTD remaining length not zero\n"); + status = -EPROTO; + break; + } else { + break; + } + } else { + dev_vdbg(udc->dev, "dTD transmitted successful\n"); + } + + 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->ep.name) { + dev_warn(udc->dev, "Invalid EP?\n"); + 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); + + dev_vdbg(udc->dev, + "status of process_ep_req= %d, ep = %d\n", + 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, bool bus_reset) +{ + 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); + if (bus_reset) + usb_gadget_udc_reset(&udc->gadget, udc->driver); + else + 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)) { + dev_err(udc->dev, "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) { + dev_vdbg(udc->dev, "Bus reset\n"); + /* Bus is reseting */ + udc->bus_reset = 1; + /* Reset all the queues, include XD, dTD, EP queue + * head and TR Queue */ + reset_queues(udc, true); + udc->usb_state = USB_STATE_DEFAULT; + } else { + dev_vdbg(udc->dev, "Controller reset\n"); + /* 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, false); + + 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); + + /* dev_vdbg(udc->dev, "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) { + dev_vdbg(udc->dev, "Packet int\n"); + /* 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) { + dev_vdbg(udc->dev, "reset int\n"); + 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)) { + dev_vdbg(udc->dev, "Error IRQ %x\n", 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; + + /* lock is needed but whether should use this lock or another */ + spin_lock_irqsave(&udc_controller->lock, flags); + + /* hook up the driver */ + udc_controller->driver = driver; + spin_unlock_irqrestore(&udc_controller->lock, flags); + g->is_selfpowered = 1; + + 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) { + dev_err(udc_controller->dev, "can't bind to transceiver\n"); + udc_controller->driver = NULL; + 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 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 Detected 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); + + 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); + } + + /* ------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; +} + +#define create_proc_file() \ + proc_create_single(proc_filename, 0, NULL, fsl_proc_read) +#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 global controller called by fsl_udc_probe() + * On success the udc handle is initialized, on failure it is + * unchanged (reset). + * Return 0 on success and -1 on allocation failure + ------------------------------------------------------------------*/ +static int struct_udc_setup(struct fsl_udc *udc, + struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + size_t size; + + pdata = dev_get_platdata(&pdev->dev); + udc->phy_mode = pdata->phy_mode; + + udc->eps = kzalloc_objs(struct fsl_ep, udc->max_ep); + if (!udc->eps) { + dev_err(udc->dev, "kmalloc udc endpoint status failed\n"); + goto eps_alloc_failed; + } + + /* 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) { + dev_err(udc->dev, "malloc QHs for udc failed\n"); + goto ep_queue_alloc_failed; + } + + 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); + if (!udc->status_req) { + dev_err(udc->dev, "kzalloc for udc status request failed\n"); + goto udc_status_alloc_failed; + } + + /* allocate a small amount of memory to get valid address */ + udc->status_req->req.buf = kmalloc(8, GFP_KERNEL); + if (!udc->status_req->req.buf) { + dev_err(udc->dev, "kzalloc for udc request buffer failed\n"); + goto udc_req_buf_alloc_failed; + } + + 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; + +udc_req_buf_alloc_failed: + kfree(udc->status_req); +udc_status_alloc_failed: + kfree(udc->ep_qh); + udc->ep_qh_size = 0; +ep_queue_alloc_failed: + kfree(udc->eps); +eps_alloc_failed: + udc->phy_mode = 0; + return -1; + +} + +/*---------------------------------------------------------------- + * 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 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; + + if (index == 0) { + ep->ep.caps.type_control = true; + } else { + ep->ep.caps.type_iso = true; + ep->ep.caps.type_bulk = true; + ep->ep.caps.type_int = true; + } + + if (index & 1) + ep->ep.caps.dir_in = true; + else + ep->ep.caps.dir_out = true; + + /* for ep0: maxP defined in desc + * for other eps, maxP is set by epautoconfig() called by gadget layer + */ + usb_ep_set_maxpacket_limit(&ep->ep, (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 initialization operations implemented here except enabling usb_intr reg + * board setup should have been done in the platform code + */ +static int 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_obj(struct fsl_udc); + if (udc_controller == NULL) + return -ENOMEM; + + udc_controller->dev = &pdev->dev; + pdata = dev_get_platdata(&pdev->dev); + 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)) { + dev_err(&pdev->dev, "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)) { + dev_err(&pdev->dev, "failed to request mem region\n"); + 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 __iomem *)dr_regs; + + /* + * do platform specific init: check the clock, grab/config pins, etc. + */ + if (pdata->init && pdata->init(pdev)) { + ret = -ENODEV; + goto err_iounmap; + } + + /* Set accessors only after pdata->init() ! */ + fsl_set_accessors(pdata); + + if (pdata->have_sysif_regs) + usb_sys_regs = (void *)dr_regs + USB_DR_SYS_OFFSET; + + /* Read Device Controller Capability Parameters register */ + dccparams = fsl_readl(&dr_regs->dccparams); + if (!(dccparams & DCCPARAMS_DC)) { + dev_err(&pdev->dev, "This SOC doesn't support device role\n"); + ret = -ENODEV; + goto err_exit; + } + /* Get max device endpoints */ + /* DEN is bidirectional ep number, max_ep doubles the number */ + udc_controller->max_ep = (dccparams & DCCPARAMS_DEN_MASK) * 2; + + ret = platform_get_irq(pdev, 0); + if (ret <= 0) { + ret = ret ? : -ENODEV; + goto err_exit; + } + udc_controller->irq = ret; + + ret = request_irq(udc_controller->irq, fsl_udc_irq, IRQF_SHARED, + driver_name, udc_controller); + if (ret != 0) { + dev_err(&pdev->dev, "cannot request irq %d err %d\n", + udc_controller->irq, ret); + goto err_exit; + } + + /* Initialize the udc structure including QH member and other member */ + if (struct_udc_setup(udc_controller, pdev)) { + dev_err(&pdev->dev, "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); + } + + /* 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 */ + 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; + usb_ep_set_maxpacket_limit(&udc_controller->eps[0].ep, + 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[16]; + + 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_exit: + if (pdata->exit) + pdata->exit(pdev); +err_iounmap: + 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 void fsl_udc_remove(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct fsl_usb2_platform_data *pdata = dev_get_platdata(&pdev->dev); + + DECLARE_COMPLETION_ONSTACK(done); + + udc_controller->done = &done; + usb_del_gadget_udc(&udc_controller->gadget); + + /* 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 (res && (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); +} + +/*----------------------------------------------------------------- + * 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 = "fsl-usb2-udc", + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, fsl_udc_devtype); + +static const struct of_device_id fsl_udc_dt_ids[] = { + { .compatible = "fsl-usb2-dr" }, + { .compatible = "fsl-usb2-mph" }, + { .compatible = "fsl,mpc5121-usb2-dr" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_udc_dt_ids); + +static struct platform_driver udc_driver = { + .probe = fsl_udc_probe, + .remove = fsl_udc_remove, + .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 = driver_name, + .of_match_table = fsl_udc_dt_ids, + /* udc suspend/resume called from OTG driver */ + .suspend = fsl_udc_otg_suspend, + .resume = fsl_udc_otg_resume, + }, +}; + +module_platform_driver(udc_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:fsl-usb2-udc"); diff --git a/drivers/usb/gadget/udc/fsl_usb2_udc.h b/drivers/usb/gadget/udc/fsl_usb2_udc.h new file mode 100644 index 000000000..53922bc58 --- /dev/null +++ b/drivers/usb/gadget/udc/fsl_usb2_udc.h @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2004,2012 Freescale Semiconductor, Inc + * All rights reserved. + * + * Freescale USB device/endpoint management registers + */ +#ifndef __FSL_USB2_UDC_H +#define __FSL_USB2_UDC_H + +#include +#include + +/* ### 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 device *dev; + 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 */ +}; + +/*-------------------------------------------------------------------------*/ + +/* ### 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]; +} + +#endif diff --git a/drivers/usb/gadget/udc/goku_udc.c b/drivers/usb/gadget/udc/goku_udc.c new file mode 100644 index 000000000..ac2a984c2 --- /dev/null +++ b/drivers/usb/gadget/udc/goku_udc.c @@ -0,0 +1,1861 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 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 "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++; + fallthrough; + case 32: + mode++; + fallthrough; + case 16: + mode++; + fallthrough; + 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); + } + } + + usb_ep_set_maxpacket_limit(&ep->ep, 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_obj(*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); + usb_gadget_giveback_request(&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 != NULL)) + 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 = NULL, *iter; + 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(iter, &ep->queue, queue) { + if (&iter->req != _req) + continue; + req = iter; + break; + } + if (!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 const 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 struct usb_ep *goku_match_ep(struct usb_gadget *g, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *ep_comp) +{ + struct goku_udc *dev = to_goku_udc(g); + struct usb_ep *ep; + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_INT: + /* single buffering is enough */ + ep = &dev->ep[3].ep; + if (usb_gadget_ep_match_desc(g, ep, desc, ep_comp)) + return ep; + break; + case USB_ENDPOINT_XFER_BULK: + if (usb_endpoint_dir_in(desc)) { + /* DMA may be available */ + ep = &dev->ep[2].ep; + if (usb_gadget_ep_match_desc(g, ep, desc, ep_comp)) + return ep; + } + break; + default: + /* nothing */ ; + } + + return NULL; +} + +static int goku_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static int goku_udc_stop(struct usb_gadget *g); + +static const struct usb_gadget_ops goku_ops = { + .get_frame = goku_get_frame, + .udc_start = goku_udc_start, + .udc_stop = goku_udc_stop, + .match_ep = goku_match_ep, + // 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 const char *udc_ep_state(enum ep0state state) +{ + switch (state) { + case EP0_DISCONNECT: + return "ep0_disconnect"; + case EP0_IDLE: + return "ep0_idle"; + case EP0_IN: + return "ep0_in"; + case EP0_OUT: + return "ep0_out"; + case EP0_STATUS: + return "ep0_status"; + case EP0_STALL: + return "ep0_stall"; + case EP0_SUSPEND: + return "ep0_suspend"; + } + + return "ep0_?"; +} + +static const char *udc_ep_status(u32 status) +{ + switch (status & EPxSTATUS_EP_MASK) { + case EPxSTATUS_EP_READY: + return "ready"; + case EPxSTATUS_EP_DATAIN: + return "packet"; + case EPxSTATUS_EP_FULL: + return "full"; + case EPxSTATUS_EP_TX_ERR: /* host will retry */ + return "tx_err"; + case EPxSTATUS_EP_RX_ERR: + return "rx_err"; + case EPxSTATUS_EP_BUSY: /* ep0 only */ + return "busy"; + case EPxSTATUS_EP_STALL: + return "stall"; + case EPxSTATUS_EP_INVALID: /* these "can't happen" */ + return "invalid"; + } + + return "?"; +} + +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", + udc_ep_state(dev->ep0state)); + + 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 */ + 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)); + if (seq_has_overflowed(m)) + goto done; + + tmp = readl(®s->dma_master); + 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"); + if (seq_has_overflowed(m)) + 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); + 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, udc_ep_status(tmp), + (tmp & EPxSTATUS_TOGGLE) ? "data1" : "data0", + (tmp & EPxSTATUS_SUSPEND) ? " suspend" : "", + (tmp & EPxSTATUS_FIFO_DISABLE) ? " disable" : "", + (tmp & EPxSTATUS_STAGE_ERROR) ? " ep0stat" : ""); + if (seq_has_overflowed(m)) + goto done; + + if (list_empty(&ep->queue)) { + seq_puts(m, "\t(nothing queued)\n"); + if (seq_has_overflowed(m)) + 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; + + seq_printf(m, "\treq %p len %u/%u buf %p\n", + &req->req, tmp, req->req.length, + req->req.buf); + if (seq_has_overflowed(m)) + goto done; + } + } + +done: + local_irq_restore(flags); + return 0; +} +#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); + + if (i == 0) + ep->ep.caps.type_control = true; + else + ep->ep.caps.type_bulk = true; + + ep->ep.caps.dir_in = true; + ep->ep.caps.dir_out = true; + } + + dev->ep[0].reg_mode = NULL; + usb_ep_set_maxpacket_limit(&dev->ep[0].ep, 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 */ + 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) +{ + unsigned i; + + DBG (dev, "%s\n", __func__); + + /* 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 goku_udc *dev = to_goku_udc(g); + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + dev->driver = NULL; + stop_activity(dev); + 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); + 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->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 ? 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); + + 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_obj(*dev); + if (!dev) { + retval = -ENOMEM; + goto err; + } + + pci_set_drvdata(pdev, dev); + 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(resource, len); + if (base == NULL) { + DBG(dev, "can't map memory\n"); + retval = -EFAULT; + goto err; + } + dev->regs = (struct goku_udc_regs __iomem *) base; + + 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_single_data(proc_node_name, 0, NULL, udc_proc_read, 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); + /* gadget_release is not registered yet, kfree explicitly */ + kfree(dev); + return retval; +} + + +/*-------------------------------------------------------------------------*/ + +static const struct pci_device_id pci_ids[] = { { + .class = PCI_CLASS_SERIAL_USB_DEVICE, + .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 = 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/udc/goku_udc.h b/drivers/usb/gadget/udc/goku_udc.h new file mode 100644 index 000000000..70023d401 --- /dev/null +++ b/drivers/usb/gadget/udc/goku_udc.h @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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) + */ + +/* + * 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/udc/gr_udc.c b/drivers/usb/gadget/udc/gr_udc.c new file mode 100644 index 000000000..cdcbfae14 --- /dev/null +++ b/drivers/usb/gadget/udc/gr_udc.c @@ -0,0 +1,2258 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * USB Peripheral Controller driver for Aeroflex Gaisler GRUSBDC. + * + * 2013 (c) Aeroflex Gaisler AB + * + * This driver supports GRUSBDC USB Device Controller cores available in the + * GRLIB VHDL IP core library. + * + * Full documentation of the GRUSBDC core can be found here: + * https://www.gaisler.com/products/grlib/grip.pdf + * + * Contributors: + * - Andreas Larsson + * - Marko Isomaki + */ + +/* + * A GRUSBDC core can have up to 16 IN endpoints and 16 OUT endpoints each + * individually configurable to any of the four USB transfer types. This driver + * only supports cores in DMA mode. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gr_udc.h" + +#define DRIVER_NAME "gr_udc" +#define DRIVER_DESC "Aeroflex Gaisler GRUSBDC USB Peripheral Controller" + +static const char driver_name[] = DRIVER_NAME; + +#define gr_read32(x) (ioread32be((x))) +#define gr_write32(x, v) (iowrite32be((v), (x))) + +/* USB speed and corresponding string calculated from status register value */ +#define GR_SPEED(status) \ + ((status & GR_STATUS_SP) ? USB_SPEED_FULL : USB_SPEED_HIGH) +#define GR_SPEED_STR(status) usb_speed_string(GR_SPEED(status)) + +/* Size of hardware buffer calculated from epctrl register value */ +#define GR_BUFFER_SIZE(epctrl) \ + ((((epctrl) & GR_EPCTRL_BUFSZ_MASK) >> GR_EPCTRL_BUFSZ_POS) * \ + GR_EPCTRL_BUFSZ_SCALER) + +/* ---------------------------------------------------------------------- */ +/* Debug printout functionality */ + +static const char * const gr_modestring[] = {"control", "iso", "bulk", "int"}; + +static const char *gr_ep0state_string(enum gr_ep0state state) +{ + static const char *const names[] = { + [GR_EP0_DISCONNECT] = "disconnect", + [GR_EP0_SETUP] = "setup", + [GR_EP0_IDATA] = "idata", + [GR_EP0_ODATA] = "odata", + [GR_EP0_ISTATUS] = "istatus", + [GR_EP0_OSTATUS] = "ostatus", + [GR_EP0_STALL] = "stall", + [GR_EP0_SUSPEND] = "suspend", + }; + + if (state < 0 || state >= ARRAY_SIZE(names)) + return "UNKNOWN"; + + return names[state]; +} + +#ifdef VERBOSE_DEBUG + +static void gr_dbgprint_request(const char *str, struct gr_ep *ep, + struct gr_request *req) +{ + int buflen = ep->is_in ? req->req.length : req->req.actual; + int rowlen = 32; + int plen = min(rowlen, buflen); + + dev_dbg(ep->dev->dev, "%s: 0x%p, %d bytes data%s:\n", str, req, buflen, + (buflen > plen ? " (truncated)" : "")); + print_hex_dump_debug(" ", DUMP_PREFIX_NONE, + rowlen, 4, req->req.buf, plen, false); +} + +static void gr_dbgprint_devreq(struct gr_udc *dev, u8 type, u8 request, + u16 value, u16 index, u16 length) +{ + dev_vdbg(dev->dev, "REQ: %02x.%02x v%04x i%04x l%04x\n", + type, request, value, index, length); +} +#else /* !VERBOSE_DEBUG */ + +static void gr_dbgprint_request(const char *str, struct gr_ep *ep, + struct gr_request *req) {} + +static void gr_dbgprint_devreq(struct gr_udc *dev, u8 type, u8 request, + u16 value, u16 index, u16 length) {} + +#endif /* VERBOSE_DEBUG */ + +/* ---------------------------------------------------------------------- */ +/* Debugfs functionality */ + +#ifdef CONFIG_USB_GADGET_DEBUG_FS + +static void gr_seq_ep_show(struct seq_file *seq, struct gr_ep *ep) +{ + u32 epctrl = gr_read32(&ep->regs->epctrl); + u32 epstat = gr_read32(&ep->regs->epstat); + int mode = (epctrl & GR_EPCTRL_TT_MASK) >> GR_EPCTRL_TT_POS; + struct gr_request *req; + + seq_printf(seq, "%s:\n", ep->ep.name); + seq_printf(seq, " mode = %s\n", gr_modestring[mode]); + seq_printf(seq, " halted: %d\n", !!(epctrl & GR_EPCTRL_EH)); + seq_printf(seq, " disabled: %d\n", !!(epctrl & GR_EPCTRL_ED)); + seq_printf(seq, " valid: %d\n", !!(epctrl & GR_EPCTRL_EV)); + seq_printf(seq, " dma_start = %d\n", ep->dma_start); + seq_printf(seq, " stopped = %d\n", ep->stopped); + seq_printf(seq, " wedged = %d\n", ep->wedged); + seq_printf(seq, " callback = %d\n", ep->callback); + seq_printf(seq, " maxpacket = %d\n", ep->ep.maxpacket); + seq_printf(seq, " maxpacket_limit = %d\n", ep->ep.maxpacket_limit); + seq_printf(seq, " bytes_per_buffer = %d\n", ep->bytes_per_buffer); + if (mode == 1 || mode == 3) + seq_printf(seq, " nt = %d\n", + (epctrl & GR_EPCTRL_NT_MASK) >> GR_EPCTRL_NT_POS); + + seq_printf(seq, " Buffer 0: %s %s%d\n", + epstat & GR_EPSTAT_B0 ? "valid" : "invalid", + epstat & GR_EPSTAT_BS ? " " : "selected ", + (epstat & GR_EPSTAT_B0CNT_MASK) >> GR_EPSTAT_B0CNT_POS); + seq_printf(seq, " Buffer 1: %s %s%d\n", + epstat & GR_EPSTAT_B1 ? "valid" : "invalid", + epstat & GR_EPSTAT_BS ? "selected " : " ", + (epstat & GR_EPSTAT_B1CNT_MASK) >> GR_EPSTAT_B1CNT_POS); + + if (list_empty(&ep->queue)) { + seq_puts(seq, " Queue: empty\n\n"); + return; + } + + seq_puts(seq, " Queue:\n"); + list_for_each_entry(req, &ep->queue, queue) { + struct gr_dma_desc *desc; + struct gr_dma_desc *next; + + seq_printf(seq, " 0x%p: 0x%p %d %d\n", req, + &req->req.buf, req->req.actual, req->req.length); + + next = req->first_desc; + do { + desc = next; + next = desc->next_desc; + seq_printf(seq, " %c 0x%p (0x%08x): 0x%05x 0x%08x\n", + desc == req->curr_desc ? 'c' : ' ', + desc, desc->paddr, desc->ctrl, desc->data); + } while (desc != req->last_desc); + } + seq_puts(seq, "\n"); +} + +static int gr_dfs_show(struct seq_file *seq, void *v) +{ + struct gr_udc *dev = seq->private; + u32 control = gr_read32(&dev->regs->control); + u32 status = gr_read32(&dev->regs->status); + struct gr_ep *ep; + + seq_printf(seq, "usb state = %s\n", + usb_state_string(dev->gadget.state)); + seq_printf(seq, "address = %d\n", + (control & GR_CONTROL_UA_MASK) >> GR_CONTROL_UA_POS); + seq_printf(seq, "speed = %s\n", GR_SPEED_STR(status)); + seq_printf(seq, "ep0state = %s\n", gr_ep0state_string(dev->ep0state)); + seq_printf(seq, "irq_enabled = %d\n", dev->irq_enabled); + seq_printf(seq, "remote_wakeup = %d\n", dev->remote_wakeup); + seq_printf(seq, "test_mode = %d\n", dev->test_mode); + seq_puts(seq, "\n"); + + list_for_each_entry(ep, &dev->ep_list, ep_list) + gr_seq_ep_show(seq, ep); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(gr_dfs); + +static void gr_dfs_create(struct gr_udc *dev) +{ + const char *name = "gr_udc_state"; + struct dentry *root; + + root = debugfs_create_dir(dev_name(dev->dev), usb_debug_root); + debugfs_create_file(name, 0444, root, dev, &gr_dfs_fops); +} + +static void gr_dfs_delete(struct gr_udc *dev) +{ + debugfs_lookup_and_remove(dev_name(dev->dev), usb_debug_root); +} + +#else /* !CONFIG_USB_GADGET_DEBUG_FS */ + +static void gr_dfs_create(struct gr_udc *dev) {} +static void gr_dfs_delete(struct gr_udc *dev) {} + +#endif /* CONFIG_USB_GADGET_DEBUG_FS */ + +/* ---------------------------------------------------------------------- */ +/* DMA and request handling */ + +/* Allocates a new struct gr_dma_desc, sets paddr and zeroes the rest */ +static struct gr_dma_desc *gr_alloc_dma_desc(struct gr_ep *ep, gfp_t gfp_flags) +{ + dma_addr_t paddr; + struct gr_dma_desc *dma_desc; + + dma_desc = dma_pool_zalloc(ep->dev->desc_pool, gfp_flags, &paddr); + if (!dma_desc) { + dev_err(ep->dev->dev, "Could not allocate from DMA pool\n"); + return NULL; + } + + dma_desc->paddr = paddr; + + return dma_desc; +} + +static inline void gr_free_dma_desc(struct gr_udc *dev, + struct gr_dma_desc *desc) +{ + dma_pool_free(dev->desc_pool, desc, (dma_addr_t)desc->paddr); +} + +/* Frees the chain of struct gr_dma_desc for the given request */ +static void gr_free_dma_desc_chain(struct gr_udc *dev, struct gr_request *req) +{ + struct gr_dma_desc *desc; + struct gr_dma_desc *next; + + next = req->first_desc; + if (!next) + return; + + do { + desc = next; + next = desc->next_desc; + gr_free_dma_desc(dev, desc); + } while (desc != req->last_desc); + + req->first_desc = NULL; + req->curr_desc = NULL; + req->last_desc = NULL; +} + +static void gr_ep0_setup(struct gr_udc *dev, struct gr_request *req); + +/* + * Frees allocated resources and calls the appropriate completion function/setup + * package handler for a finished request. + * + * Must be called with dev->lock held and irqs disabled. + */ +static void gr_finish_request(struct gr_ep *ep, struct gr_request *req, + int status) + __releases(&dev->lock) + __acquires(&dev->lock) +{ + struct gr_udc *dev; + + list_del_init(&req->queue); + + if (likely(req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + dev = ep->dev; + usb_gadget_unmap_request(&dev->gadget, &req->req, ep->is_in); + gr_free_dma_desc_chain(dev, req); + + if (ep->is_in) { /* For OUT, req->req.actual gets updated bit by bit */ + req->req.actual = req->req.length; + } else if (req->oddlen && req->req.actual > req->evenlen) { + /* + * Copy to user buffer in this case where length was not evenly + * divisible by ep->ep.maxpacket and the last descriptor was + * actually used. + */ + char *buftail = ((char *)req->req.buf + req->evenlen); + + memcpy(buftail, ep->tailbuf, req->oddlen); + + if (req->req.actual > req->req.length) { + /* We got more data than was requested */ + dev_dbg(ep->dev->dev, "Overflow for ep %s\n", + ep->ep.name); + gr_dbgprint_request("OVFL", ep, req); + req->req.status = -EOVERFLOW; + } + } + + if (!status) { + if (ep->is_in) + gr_dbgprint_request("SENT", ep, req); + else + gr_dbgprint_request("RECV", ep, req); + } + + /* Prevent changes to ep->queue during callback */ + ep->callback = 1; + if (req == dev->ep0reqo && !status) { + if (req->setup) + gr_ep0_setup(dev, req); + else + dev_err(dev->dev, + "Unexpected non setup packet on ep0in\n"); + } else if (req->req.complete) { + spin_unlock(&dev->lock); + + usb_gadget_giveback_request(&ep->ep, &req->req); + + spin_lock(&dev->lock); + } + ep->callback = 0; +} + +static struct usb_request *gr_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct gr_request *req; + + req = kzalloc_obj(*req, gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +/* + * Starts DMA for endpoint ep if there are requests in the queue. + * + * Must be called with dev->lock held and with !ep->stopped. + */ +static void gr_start_dma(struct gr_ep *ep) +{ + struct gr_request *req; + u32 dmactrl; + + if (list_empty(&ep->queue)) { + ep->dma_start = 0; + return; + } + + req = list_first_entry(&ep->queue, struct gr_request, queue); + + /* A descriptor should already have been allocated */ + BUG_ON(!req->curr_desc); + + /* + * The DMA controller can not handle smaller OUT buffers than + * ep->ep.maxpacket. It could lead to buffer overruns if an unexpectedly + * long packet are received. Therefore an internal bounce buffer gets + * used when such a request gets enabled. + */ + if (!ep->is_in && req->oddlen) + req->last_desc->data = ep->tailbuf_paddr; + + wmb(); /* Make sure all is settled before handing it over to DMA */ + + /* Set the descriptor pointer in the hardware */ + gr_write32(&ep->regs->dmaaddr, req->curr_desc->paddr); + + /* Announce available descriptors */ + dmactrl = gr_read32(&ep->regs->dmactrl); + gr_write32(&ep->regs->dmactrl, dmactrl | GR_DMACTRL_DA); + + ep->dma_start = 1; +} + +/* + * Finishes the first request in the ep's queue and, if available, starts the + * next request in queue. + * + * Must be called with dev->lock held, irqs disabled and with !ep->stopped. + */ +static void gr_dma_advance(struct gr_ep *ep, int status) +{ + struct gr_request *req; + + req = list_first_entry(&ep->queue, struct gr_request, queue); + gr_finish_request(ep, req, status); + gr_start_dma(ep); /* Regardless of ep->dma_start */ +} + +/* + * Abort DMA for an endpoint. Sets the abort DMA bit which causes an ongoing DMA + * transfer to be canceled and clears GR_DMACTRL_DA. + * + * Must be called with dev->lock held. + */ +static void gr_abort_dma(struct gr_ep *ep) +{ + u32 dmactrl; + + dmactrl = gr_read32(&ep->regs->dmactrl); + gr_write32(&ep->regs->dmactrl, dmactrl | GR_DMACTRL_AD); +} + +/* + * Allocates and sets up a struct gr_dma_desc and putting it on the descriptor + * chain. + * + * Size is not used for OUT endpoints. Hardware can not be instructed to handle + * smaller buffer than MAXPL in the OUT direction. + */ +static int gr_add_dma_desc(struct gr_ep *ep, struct gr_request *req, + dma_addr_t data, unsigned size, gfp_t gfp_flags) +{ + struct gr_dma_desc *desc; + + desc = gr_alloc_dma_desc(ep, gfp_flags); + if (!desc) + return -ENOMEM; + + desc->data = data; + if (ep->is_in) + desc->ctrl = + (GR_DESC_IN_CTRL_LEN_MASK & size) | GR_DESC_IN_CTRL_EN; + else + desc->ctrl = GR_DESC_OUT_CTRL_IE; + + if (!req->first_desc) { + req->first_desc = desc; + req->curr_desc = desc; + } else { + req->last_desc->next_desc = desc; + req->last_desc->next = desc->paddr; + req->last_desc->ctrl |= GR_DESC_OUT_CTRL_NX; + } + req->last_desc = desc; + + return 0; +} + +/* + * Sets up a chain of struct gr_dma_descriptors pointing to buffers that + * together covers req->req.length bytes of the buffer at DMA address + * req->req.dma for the OUT direction. + * + * The first descriptor in the chain is enabled, the rest disabled. The + * interrupt handler will later enable them one by one when needed so we can + * find out when the transfer is finished. For OUT endpoints, all descriptors + * therefore generate interrutps. + */ +static int gr_setup_out_desc_list(struct gr_ep *ep, struct gr_request *req, + gfp_t gfp_flags) +{ + u16 bytes_left; /* Bytes left to provide descriptors for */ + u16 bytes_used; /* Bytes accommodated for */ + int ret = 0; + + req->first_desc = NULL; /* Signals that no allocation is done yet */ + bytes_left = req->req.length; + bytes_used = 0; + while (bytes_left > 0) { + dma_addr_t start = req->req.dma + bytes_used; + u16 size = min(bytes_left, ep->bytes_per_buffer); + + if (size < ep->bytes_per_buffer) { + /* Prepare using bounce buffer */ + req->evenlen = req->req.length - bytes_left; + req->oddlen = size; + } + + ret = gr_add_dma_desc(ep, req, start, size, gfp_flags); + if (ret) + goto alloc_err; + + bytes_left -= size; + bytes_used += size; + } + + req->first_desc->ctrl |= GR_DESC_OUT_CTRL_EN; + + return 0; + +alloc_err: + gr_free_dma_desc_chain(ep->dev, req); + + return ret; +} + +/* + * Sets up a chain of struct gr_dma_descriptors pointing to buffers that + * together covers req->req.length bytes of the buffer at DMA address + * req->req.dma for the IN direction. + * + * When more data is provided than the maximum payload size, the hardware splits + * this up into several payloads automatically. Moreover, ep->bytes_per_buffer + * is always set to a multiple of the maximum payload (restricted to the valid + * number of maximum payloads during high bandwidth isochronous or interrupt + * transfers) + * + * All descriptors are enabled from the beginning and we only generate an + * interrupt for the last one indicating that the entire request has been pushed + * to hardware. + */ +static int gr_setup_in_desc_list(struct gr_ep *ep, struct gr_request *req, + gfp_t gfp_flags) +{ + u16 bytes_left; /* Bytes left in req to provide descriptors for */ + u16 bytes_used; /* Bytes in req accommodated for */ + int ret = 0; + + req->first_desc = NULL; /* Signals that no allocation is done yet */ + bytes_left = req->req.length; + bytes_used = 0; + do { /* Allow for zero length packets */ + dma_addr_t start = req->req.dma + bytes_used; + u16 size = min(bytes_left, ep->bytes_per_buffer); + + ret = gr_add_dma_desc(ep, req, start, size, gfp_flags); + if (ret) + goto alloc_err; + + bytes_left -= size; + bytes_used += size; + } while (bytes_left > 0); + + /* + * Send an extra zero length packet to indicate that no more data is + * available when req->req.zero is set and the data length is even + * multiples of ep->ep.maxpacket. + */ + if (req->req.zero && (req->req.length % ep->ep.maxpacket == 0)) { + ret = gr_add_dma_desc(ep, req, 0, 0, gfp_flags); + if (ret) + goto alloc_err; + } + + /* + * For IN packets we only want to know when the last packet has been + * transmitted (not just put into internal buffers). + */ + req->last_desc->ctrl |= GR_DESC_IN_CTRL_PI; + + return 0; + +alloc_err: + gr_free_dma_desc_chain(ep->dev, req); + + return ret; +} + +/* Must be called with dev->lock held */ +static int gr_queue(struct gr_ep *ep, struct gr_request *req, gfp_t gfp_flags) +{ + struct gr_udc *dev = ep->dev; + int ret; + + if (unlikely(!ep->ep.desc && ep->num != 0)) { + dev_err(dev->dev, "No ep descriptor for %s\n", ep->ep.name); + return -EINVAL; + } + + if (unlikely(!req->req.buf || !list_empty(&req->queue))) { + dev_err(dev->dev, + "Invalid request for %s: buf=%p list_empty=%d\n", + ep->ep.name, req->req.buf, list_empty(&req->queue)); + return -EINVAL; + } + + if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) { + dev_err(dev->dev, "-ESHUTDOWN"); + return -ESHUTDOWN; + } + + /* Can't touch registers when suspended */ + if (dev->ep0state == GR_EP0_SUSPEND) { + dev_err(dev->dev, "-EBUSY"); + return -EBUSY; + } + + /* Set up DMA mapping in case the caller didn't */ + ret = usb_gadget_map_request(&dev->gadget, &req->req, ep->is_in); + if (ret) { + dev_err(dev->dev, "usb_gadget_map_request"); + return ret; + } + + if (ep->is_in) + ret = gr_setup_in_desc_list(ep, req, gfp_flags); + else + ret = gr_setup_out_desc_list(ep, req, gfp_flags); + if (ret) + return ret; + + req->req.status = -EINPROGRESS; + req->req.actual = 0; + list_add_tail(&req->queue, &ep->queue); + + /* Start DMA if not started, otherwise interrupt handler handles it */ + if (!ep->dma_start && likely(!ep->stopped)) + gr_start_dma(ep); + + return 0; +} + +/* + * Queue a request from within the driver. + * + * Must be called with dev->lock held. + */ +static inline int gr_queue_int(struct gr_ep *ep, struct gr_request *req, + gfp_t gfp_flags) +{ + if (ep->is_in) + gr_dbgprint_request("RESP", ep, req); + + return gr_queue(ep, req, gfp_flags); +} + +/* ---------------------------------------------------------------------- */ +/* General helper functions */ + +/* + * Dequeue ALL requests. + * + * Must be called with dev->lock held and irqs disabled. + */ +static void gr_ep_nuke(struct gr_ep *ep) +{ + struct gr_request *req; + + ep->stopped = 1; + ep->dma_start = 0; + gr_abort_dma(ep); + + while (!list_empty(&ep->queue)) { + req = list_first_entry(&ep->queue, struct gr_request, queue); + gr_finish_request(ep, req, -ESHUTDOWN); + } +} + +/* + * Reset the hardware state of this endpoint. + * + * Must be called with dev->lock held. + */ +static void gr_ep_reset(struct gr_ep *ep) +{ + gr_write32(&ep->regs->epctrl, 0); + gr_write32(&ep->regs->dmactrl, 0); + + ep->ep.maxpacket = MAX_CTRL_PL_SIZE; + ep->ep.desc = NULL; + ep->stopped = 1; + ep->dma_start = 0; +} + +/* + * Generate STALL on ep0in/out. + * + * Must be called with dev->lock held. + */ +static void gr_control_stall(struct gr_udc *dev) +{ + u32 epctrl; + + epctrl = gr_read32(&dev->epo[0].regs->epctrl); + gr_write32(&dev->epo[0].regs->epctrl, epctrl | GR_EPCTRL_CS); + epctrl = gr_read32(&dev->epi[0].regs->epctrl); + gr_write32(&dev->epi[0].regs->epctrl, epctrl | GR_EPCTRL_CS); + + dev->ep0state = GR_EP0_STALL; +} + +/* + * Halts, halts and wedges, or clears halt for an endpoint. + * + * Must be called with dev->lock held. + */ +static int gr_ep_halt_wedge(struct gr_ep *ep, int halt, int wedge, int fromhost) +{ + u32 epctrl; + int retval = 0; + + if (ep->num && !ep->ep.desc) + return -EINVAL; + + if (ep->num && ep->ep.desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) + return -EOPNOTSUPP; + + /* Never actually halt ep0, and therefore never clear halt for ep0 */ + if (!ep->num) { + if (halt && !fromhost) { + /* ep0 halt from gadget - generate protocol stall */ + gr_control_stall(ep->dev); + dev_dbg(ep->dev->dev, "EP: stall ep0\n"); + return 0; + } + return -EINVAL; + } + + dev_dbg(ep->dev->dev, "EP: %s halt %s\n", + (halt ? (wedge ? "wedge" : "set") : "clear"), ep->ep.name); + + epctrl = gr_read32(&ep->regs->epctrl); + if (halt) { + /* Set HALT */ + gr_write32(&ep->regs->epctrl, epctrl | GR_EPCTRL_EH); + ep->stopped = 1; + if (wedge) + ep->wedged = 1; + } else { + gr_write32(&ep->regs->epctrl, epctrl & ~GR_EPCTRL_EH); + ep->stopped = 0; + ep->wedged = 0; + + /* Things might have been queued up in the meantime */ + if (!ep->dma_start) + gr_start_dma(ep); + } + + return retval; +} + +/* Must be called with dev->lock held */ +static inline void gr_set_ep0state(struct gr_udc *dev, enum gr_ep0state value) +{ + if (dev->ep0state != value) + dev_vdbg(dev->dev, "STATE: ep0state=%s\n", + gr_ep0state_string(value)); + dev->ep0state = value; +} + +/* + * Should only be called when endpoints can not generate interrupts. + * + * Must be called with dev->lock held. + */ +static void gr_disable_interrupts_and_pullup(struct gr_udc *dev) +{ + gr_write32(&dev->regs->control, 0); + wmb(); /* Make sure that we do not deny one of our interrupts */ + dev->irq_enabled = 0; +} + +/* + * Stop all device activity and disable data line pullup. + * + * Must be called with dev->lock held and irqs disabled. + */ +static void gr_stop_activity(struct gr_udc *dev) +{ + struct gr_ep *ep; + + list_for_each_entry(ep, &dev->ep_list, ep_list) + gr_ep_nuke(ep); + + gr_disable_interrupts_and_pullup(dev); + + gr_set_ep0state(dev, GR_EP0_DISCONNECT); + usb_gadget_set_state(&dev->gadget, USB_STATE_NOTATTACHED); +} + +/* ---------------------------------------------------------------------- */ +/* ep0 setup packet handling */ + +static void gr_ep0_testmode_complete(struct usb_ep *_ep, + struct usb_request *_req) +{ + struct gr_ep *ep; + struct gr_udc *dev; + u32 control; + + ep = container_of(_ep, struct gr_ep, ep); + dev = ep->dev; + + spin_lock(&dev->lock); + + control = gr_read32(&dev->regs->control); + control |= GR_CONTROL_TM | (dev->test_mode << GR_CONTROL_TS_POS); + gr_write32(&dev->regs->control, control); + + spin_unlock(&dev->lock); +} + +static void gr_ep0_dummy_complete(struct usb_ep *_ep, struct usb_request *_req) +{ + /* Nothing needs to be done here */ +} + +/* + * Queue a response on ep0in. + * + * Must be called with dev->lock held. + */ +static int gr_ep0_respond(struct gr_udc *dev, u8 *buf, int length, + void (*complete)(struct usb_ep *ep, + struct usb_request *req)) +{ + u8 *reqbuf = dev->ep0reqi->req.buf; + int status; + int i; + + for (i = 0; i < length; i++) + reqbuf[i] = buf[i]; + dev->ep0reqi->req.length = length; + dev->ep0reqi->req.complete = complete; + + status = gr_queue_int(&dev->epi[0], dev->ep0reqi, GFP_ATOMIC); + if (status < 0) + dev_err(dev->dev, + "Could not queue ep0in setup response: %d\n", status); + + return status; +} + +/* + * Queue a 2 byte response on ep0in. + * + * Must be called with dev->lock held. + */ +static inline int gr_ep0_respond_u16(struct gr_udc *dev, u16 response) +{ + __le16 le_response = cpu_to_le16(response); + + return gr_ep0_respond(dev, (u8 *)&le_response, 2, + gr_ep0_dummy_complete); +} + +/* + * Queue a ZLP response on ep0in. + * + * Must be called with dev->lock held. + */ +static inline int gr_ep0_respond_empty(struct gr_udc *dev) +{ + return gr_ep0_respond(dev, NULL, 0, gr_ep0_dummy_complete); +} + +/* + * This is run when a SET_ADDRESS request is received. First writes + * the new address to the control register which is updated internally + * when the next IN packet is ACKED. + * + * Must be called with dev->lock held. + */ +static void gr_set_address(struct gr_udc *dev, u8 address) +{ + u32 control; + + control = gr_read32(&dev->regs->control) & ~GR_CONTROL_UA_MASK; + control |= (address << GR_CONTROL_UA_POS) & GR_CONTROL_UA_MASK; + control |= GR_CONTROL_SU; + gr_write32(&dev->regs->control, control); +} + +/* + * Returns negative for STALL, 0 for successful handling and positive for + * delegation. + * + * Must be called with dev->lock held. + */ +static int gr_device_request(struct gr_udc *dev, u8 type, u8 request, + u16 value, u16 index) +{ + u16 response; + u8 test; + + switch (request) { + case USB_REQ_SET_ADDRESS: + dev_dbg(dev->dev, "STATUS: address %d\n", value & 0xff); + gr_set_address(dev, value & 0xff); + if (value) + usb_gadget_set_state(&dev->gadget, USB_STATE_ADDRESS); + else + usb_gadget_set_state(&dev->gadget, USB_STATE_DEFAULT); + return gr_ep0_respond_empty(dev); + + case USB_REQ_GET_STATUS: + /* Self powered | remote wakeup */ + response = 0x0001 | (dev->remote_wakeup ? 0x0002 : 0); + return gr_ep0_respond_u16(dev, response); + + case USB_REQ_SET_FEATURE: + switch (value) { + case USB_DEVICE_REMOTE_WAKEUP: + /* Allow remote wakeup */ + dev->remote_wakeup = 1; + return gr_ep0_respond_empty(dev); + + case USB_DEVICE_TEST_MODE: + /* The hardware does not support USB_TEST_FORCE_ENABLE */ + test = index >> 8; + if (test >= USB_TEST_J && test <= USB_TEST_PACKET) { + dev->test_mode = test; + return gr_ep0_respond(dev, NULL, 0, + gr_ep0_testmode_complete); + } + } + break; + + case USB_REQ_CLEAR_FEATURE: + switch (value) { + case USB_DEVICE_REMOTE_WAKEUP: + /* Disallow remote wakeup */ + dev->remote_wakeup = 0; + return gr_ep0_respond_empty(dev); + } + break; + } + + return 1; /* Delegate the rest */ +} + +/* + * Returns negative for STALL, 0 for successful handling and positive for + * delegation. + * + * Must be called with dev->lock held. + */ +static int gr_interface_request(struct gr_udc *dev, u8 type, u8 request, + u16 value, u16 index) +{ + if (dev->gadget.state != USB_STATE_CONFIGURED) + return -1; + + /* + * Should return STALL for invalid interfaces, but udc driver does not + * know anything about that. However, many gadget drivers do not handle + * GET_STATUS so we need to take care of that. + */ + + switch (request) { + case USB_REQ_GET_STATUS: + return gr_ep0_respond_u16(dev, 0x0000); + + case USB_REQ_SET_FEATURE: + case USB_REQ_CLEAR_FEATURE: + /* + * No possible valid standard requests. Still let gadget drivers + * have a go at it. + */ + break; + } + + return 1; /* Delegate the rest */ +} + +/* + * Returns negative for STALL, 0 for successful handling and positive for + * delegation. + * + * Must be called with dev->lock held. + */ +static int gr_endpoint_request(struct gr_udc *dev, u8 type, u8 request, + u16 value, u16 index) +{ + struct gr_ep *ep; + int status; + int halted; + u8 epnum = index & USB_ENDPOINT_NUMBER_MASK; + u8 is_in = index & USB_ENDPOINT_DIR_MASK; + + if ((is_in && epnum >= dev->nepi) || (!is_in && epnum >= dev->nepo)) + return -1; + + if (dev->gadget.state != USB_STATE_CONFIGURED && epnum != 0) + return -1; + + ep = (is_in ? &dev->epi[epnum] : &dev->epo[epnum]); + + switch (request) { + case USB_REQ_GET_STATUS: + halted = gr_read32(&ep->regs->epctrl) & GR_EPCTRL_EH; + return gr_ep0_respond_u16(dev, halted ? 0x0001 : 0); + + case USB_REQ_SET_FEATURE: + switch (value) { + case USB_ENDPOINT_HALT: + status = gr_ep_halt_wedge(ep, 1, 0, 1); + if (status >= 0) + status = gr_ep0_respond_empty(dev); + return status; + } + break; + + case USB_REQ_CLEAR_FEATURE: + switch (value) { + case USB_ENDPOINT_HALT: + if (ep->wedged) + return -1; + status = gr_ep_halt_wedge(ep, 0, 0, 1); + if (status >= 0) + status = gr_ep0_respond_empty(dev); + return status; + } + break; + } + + return 1; /* Delegate the rest */ +} + +/* Must be called with dev->lock held */ +static void gr_ep0out_requeue(struct gr_udc *dev) +{ + int ret = gr_queue_int(&dev->epo[0], dev->ep0reqo, GFP_ATOMIC); + + if (ret) + dev_err(dev->dev, "Could not queue ep0out setup request: %d\n", + ret); +} + +/* + * The main function dealing with setup requests on ep0. + * + * Must be called with dev->lock held and irqs disabled + */ +static void gr_ep0_setup(struct gr_udc *dev, struct gr_request *req) + __releases(&dev->lock) + __acquires(&dev->lock) +{ + union { + struct usb_ctrlrequest ctrl; + u8 raw[8]; + u32 word[2]; + } u; + u8 type; + u8 request; + u16 value; + u16 index; + u16 length; + int i; + int status; + + /* Restore from ep0 halt */ + if (dev->ep0state == GR_EP0_STALL) { + gr_set_ep0state(dev, GR_EP0_SETUP); + if (!req->req.actual) + goto out; + } + + if (dev->ep0state == GR_EP0_ISTATUS) { + gr_set_ep0state(dev, GR_EP0_SETUP); + if (req->req.actual > 0) + dev_dbg(dev->dev, + "Unexpected setup packet at state %s\n", + gr_ep0state_string(GR_EP0_ISTATUS)); + else + goto out; /* Got expected ZLP */ + } else if (dev->ep0state != GR_EP0_SETUP) { + dev_info(dev->dev, + "Unexpected ep0out request at state %s - stalling\n", + gr_ep0state_string(dev->ep0state)); + gr_control_stall(dev); + gr_set_ep0state(dev, GR_EP0_SETUP); + goto out; + } else if (!req->req.actual) { + dev_dbg(dev->dev, "Unexpected ZLP at state %s\n", + gr_ep0state_string(dev->ep0state)); + goto out; + } + + /* Handle SETUP packet */ + for (i = 0; i < req->req.actual; i++) + u.raw[i] = ((u8 *)req->req.buf)[i]; + + type = u.ctrl.bRequestType; + request = u.ctrl.bRequest; + value = le16_to_cpu(u.ctrl.wValue); + index = le16_to_cpu(u.ctrl.wIndex); + length = le16_to_cpu(u.ctrl.wLength); + + gr_dbgprint_devreq(dev, type, request, value, index, length); + + /* Check for data stage */ + if (length) { + if (type & USB_DIR_IN) + gr_set_ep0state(dev, GR_EP0_IDATA); + else + gr_set_ep0state(dev, GR_EP0_ODATA); + } + + status = 1; /* Positive status flags delegation */ + if ((type & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (type & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + status = gr_device_request(dev, type, request, + value, index); + break; + case USB_RECIP_ENDPOINT: + status = gr_endpoint_request(dev, type, request, + value, index); + break; + case USB_RECIP_INTERFACE: + status = gr_interface_request(dev, type, request, + value, index); + break; + } + } + + if (status > 0) { + spin_unlock(&dev->lock); + + dev_vdbg(dev->dev, "DELEGATE\n"); + status = dev->driver->setup(&dev->gadget, &u.ctrl); + + spin_lock(&dev->lock); + } + + /* Generate STALL on both ep0out and ep0in if requested */ + if (unlikely(status < 0)) { + dev_vdbg(dev->dev, "STALL\n"); + gr_control_stall(dev); + } + + if ((type & USB_TYPE_MASK) == USB_TYPE_STANDARD && + request == USB_REQ_SET_CONFIGURATION) { + if (!value) { + dev_dbg(dev->dev, "STATUS: deconfigured\n"); + usb_gadget_set_state(&dev->gadget, USB_STATE_ADDRESS); + } else if (status >= 0) { + /* Not configured unless gadget OK:s it */ + dev_dbg(dev->dev, "STATUS: configured: %d\n", value); + usb_gadget_set_state(&dev->gadget, + USB_STATE_CONFIGURED); + } + } + + /* Get ready for next stage */ + if (dev->ep0state == GR_EP0_ODATA) + gr_set_ep0state(dev, GR_EP0_OSTATUS); + else if (dev->ep0state == GR_EP0_IDATA) + gr_set_ep0state(dev, GR_EP0_ISTATUS); + else + gr_set_ep0state(dev, GR_EP0_SETUP); + +out: + gr_ep0out_requeue(dev); +} + +/* ---------------------------------------------------------------------- */ +/* VBUS and USB reset handling */ + +/* Must be called with dev->lock held and irqs disabled */ +static void gr_vbus_connected(struct gr_udc *dev, u32 status) +{ + u32 control; + + dev->gadget.speed = GR_SPEED(status); + usb_gadget_set_state(&dev->gadget, USB_STATE_POWERED); + + /* Turn on full interrupts and pullup */ + control = (GR_CONTROL_SI | GR_CONTROL_UI | GR_CONTROL_VI | + GR_CONTROL_SP | GR_CONTROL_EP); + gr_write32(&dev->regs->control, control); +} + +/* Must be called with dev->lock held */ +static void gr_enable_vbus_detect(struct gr_udc *dev) +{ + u32 status; + + dev->irq_enabled = 1; + wmb(); /* Make sure we do not ignore an interrupt */ + gr_write32(&dev->regs->control, GR_CONTROL_VI); + + /* Take care of the case we are already plugged in at this point */ + status = gr_read32(&dev->regs->status); + if (status & GR_STATUS_VB) + gr_vbus_connected(dev, status); +} + +/* Must be called with dev->lock held and irqs disabled */ +static void gr_vbus_disconnected(struct gr_udc *dev) +{ + gr_stop_activity(dev); + + /* Report disconnect */ + if (dev->driver && dev->driver->disconnect) { + spin_unlock(&dev->lock); + + dev->driver->disconnect(&dev->gadget); + + spin_lock(&dev->lock); + } + + gr_enable_vbus_detect(dev); +} + +/* Must be called with dev->lock held and irqs disabled */ +static void gr_udc_usbreset(struct gr_udc *dev, u32 status) +{ + gr_set_address(dev, 0); + gr_set_ep0state(dev, GR_EP0_SETUP); + usb_gadget_set_state(&dev->gadget, USB_STATE_DEFAULT); + dev->gadget.speed = GR_SPEED(status); + + gr_ep_nuke(&dev->epo[0]); + gr_ep_nuke(&dev->epi[0]); + dev->epo[0].stopped = 0; + dev->epi[0].stopped = 0; + gr_ep0out_requeue(dev); +} + +/* ---------------------------------------------------------------------- */ +/* Irq handling */ + +/* + * Handles interrupts from in endpoints. Returns whether something was handled. + * + * Must be called with dev->lock held, irqs disabled and with !ep->stopped. + */ +static int gr_handle_in_ep(struct gr_ep *ep) +{ + struct gr_request *req; + + req = list_first_entry(&ep->queue, struct gr_request, queue); + if (!req->last_desc) + return 0; + + if (READ_ONCE(req->last_desc->ctrl) & GR_DESC_IN_CTRL_EN) + return 0; /* Not put in hardware buffers yet */ + + if (gr_read32(&ep->regs->epstat) & (GR_EPSTAT_B1 | GR_EPSTAT_B0)) + return 0; /* Not transmitted yet, still in hardware buffers */ + + /* Write complete */ + gr_dma_advance(ep, 0); + + return 1; +} + +/* + * Handles interrupts from out endpoints. Returns whether something was handled. + * + * Must be called with dev->lock held, irqs disabled and with !ep->stopped. + */ +static int gr_handle_out_ep(struct gr_ep *ep) +{ + u32 ep_dmactrl; + u32 ctrl; + u16 len; + struct gr_request *req; + struct gr_udc *dev = ep->dev; + + req = list_first_entry(&ep->queue, struct gr_request, queue); + if (!req->curr_desc) + return 0; + + ctrl = READ_ONCE(req->curr_desc->ctrl); + if (ctrl & GR_DESC_OUT_CTRL_EN) + return 0; /* Not received yet */ + + /* Read complete */ + len = ctrl & GR_DESC_OUT_CTRL_LEN_MASK; + req->req.actual += len; + if (ctrl & GR_DESC_OUT_CTRL_SE) + req->setup = 1; + + if (len < ep->ep.maxpacket || req->req.actual >= req->req.length) { + /* Short packet or >= expected size - we are done */ + + if ((ep == &dev->epo[0]) && (dev->ep0state == GR_EP0_OSTATUS)) { + /* + * Send a status stage ZLP to ack the DATA stage in the + * OUT direction. This needs to be done before + * gr_dma_advance as that can lead to a call to + * ep0_setup that can change dev->ep0state. + */ + gr_ep0_respond_empty(dev); + gr_set_ep0state(dev, GR_EP0_SETUP); + } + + gr_dma_advance(ep, 0); + } else { + /* Not done yet. Enable the next descriptor to receive more. */ + req->curr_desc = req->curr_desc->next_desc; + req->curr_desc->ctrl |= GR_DESC_OUT_CTRL_EN; + + ep_dmactrl = gr_read32(&ep->regs->dmactrl); + gr_write32(&ep->regs->dmactrl, ep_dmactrl | GR_DMACTRL_DA); + } + + return 1; +} + +/* + * Handle state changes. Returns whether something was handled. + * + * Must be called with dev->lock held and irqs disabled. + */ +static int gr_handle_state_changes(struct gr_udc *dev) +{ + u32 status = gr_read32(&dev->regs->status); + int handled = 0; + int powstate = !(dev->gadget.state == USB_STATE_NOTATTACHED || + dev->gadget.state == USB_STATE_ATTACHED); + + /* VBUS valid detected */ + if (!powstate && (status & GR_STATUS_VB)) { + dev_dbg(dev->dev, "STATUS: vbus valid detected\n"); + gr_vbus_connected(dev, status); + handled = 1; + } + + /* Disconnect */ + if (powstate && !(status & GR_STATUS_VB)) { + dev_dbg(dev->dev, "STATUS: vbus invalid detected\n"); + gr_vbus_disconnected(dev); + handled = 1; + } + + /* USB reset detected */ + if (status & GR_STATUS_UR) { + dev_dbg(dev->dev, "STATUS: USB reset - speed is %s\n", + GR_SPEED_STR(status)); + gr_write32(&dev->regs->status, GR_STATUS_UR); + gr_udc_usbreset(dev, status); + handled = 1; + } + + /* Speed change */ + if (dev->gadget.speed != GR_SPEED(status)) { + dev_dbg(dev->dev, "STATUS: USB Speed change to %s\n", + GR_SPEED_STR(status)); + dev->gadget.speed = GR_SPEED(status); + handled = 1; + } + + /* Going into suspend */ + if ((dev->ep0state != GR_EP0_SUSPEND) && !(status & GR_STATUS_SU)) { + dev_dbg(dev->dev, "STATUS: USB suspend\n"); + gr_set_ep0state(dev, GR_EP0_SUSPEND); + dev->suspended_from = dev->gadget.state; + usb_gadget_set_state(&dev->gadget, USB_STATE_SUSPENDED); + + 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); + } + handled = 1; + } + + /* Coming out of suspend */ + if ((dev->ep0state == GR_EP0_SUSPEND) && (status & GR_STATUS_SU)) { + dev_dbg(dev->dev, "STATUS: USB resume\n"); + if (dev->suspended_from == USB_STATE_POWERED) + gr_set_ep0state(dev, GR_EP0_DISCONNECT); + else + gr_set_ep0state(dev, GR_EP0_SETUP); + usb_gadget_set_state(&dev->gadget, dev->suspended_from); + + 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); + } + handled = 1; + } + + return handled; +} + +/* Non-interrupt context irq handler */ +static irqreturn_t gr_irq_handler(int irq, void *_dev) +{ + struct gr_udc *dev = _dev; + struct gr_ep *ep; + int handled = 0; + int i; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + if (!dev->irq_enabled) + goto out; + + /* + * Check IN ep interrupts. We check these before the OUT eps because + * some gadgets reuse the request that might already be currently + * outstanding and needs to be completed (mainly setup requests). + */ + for (i = 0; i < dev->nepi; i++) { + ep = &dev->epi[i]; + if (!ep->stopped && !ep->callback && !list_empty(&ep->queue)) + handled = gr_handle_in_ep(ep) || handled; + } + + /* Check OUT ep interrupts */ + for (i = 0; i < dev->nepo; i++) { + ep = &dev->epo[i]; + if (!ep->stopped && !ep->callback && !list_empty(&ep->queue)) + handled = gr_handle_out_ep(ep) || handled; + } + + /* Check status interrupts */ + handled = gr_handle_state_changes(dev) || handled; + + /* + * Check AMBA DMA errors. Only check if we didn't find anything else to + * handle because this shouldn't happen if we did everything right. + */ + if (!handled) { + list_for_each_entry(ep, &dev->ep_list, ep_list) { + if (gr_read32(&ep->regs->dmactrl) & GR_DMACTRL_AE) { + dev_err(dev->dev, + "AMBA Error occurred for %s\n", + ep->ep.name); + handled = 1; + } + } + } + +out: + spin_unlock_irqrestore(&dev->lock, flags); + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +/* Interrupt context irq handler */ +static irqreturn_t gr_irq(int irq, void *_dev) +{ + struct gr_udc *dev = _dev; + + if (!dev->irq_enabled) + return IRQ_NONE; + + return IRQ_WAKE_THREAD; +} + +/* ---------------------------------------------------------------------- */ +/* USB ep ops */ + +/* Enable endpoint. Not for ep0in and ep0out that are handled separately. */ +static int gr_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct gr_udc *dev; + struct gr_ep *ep; + u8 mode; + u8 nt; + u16 max; + u16 buffer_size = 0; + u32 epctrl; + + ep = container_of(_ep, struct gr_ep, ep); + if (!_ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + + dev = ep->dev; + + /* 'ep0' IN and OUT are reserved */ + if (ep == &dev->epo[0] || ep == &dev->epi[0]) + return -EINVAL; + + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + /* Make sure we are clear for enabling */ + epctrl = gr_read32(&ep->regs->epctrl); + if (epctrl & GR_EPCTRL_EV) + return -EBUSY; + + /* Check that directions match */ + if (!ep->is_in != !usb_endpoint_dir_in(desc)) + return -EINVAL; + + /* Check ep num */ + if ((!ep->is_in && ep->num >= dev->nepo) || + (ep->is_in && ep->num >= dev->nepi)) + return -EINVAL; + + if (usb_endpoint_xfer_control(desc)) { + mode = 0; + } else if (usb_endpoint_xfer_isoc(desc)) { + mode = 1; + } else if (usb_endpoint_xfer_bulk(desc)) { + mode = 2; + } else if (usb_endpoint_xfer_int(desc)) { + mode = 3; + } else { + dev_err(dev->dev, "Unknown transfer type for %s\n", + ep->ep.name); + return -EINVAL; + } + + /* + * Bits 10-0 set the max payload. 12-11 set the number of + * additional transactions. + */ + max = usb_endpoint_maxp(desc); + nt = usb_endpoint_maxp_mult(desc) - 1; + buffer_size = GR_BUFFER_SIZE(epctrl); + if (nt && (mode == 0 || mode == 2)) { + dev_err(dev->dev, + "%s mode: multiple trans./microframe not valid\n", + (mode == 2 ? "Bulk" : "Control")); + return -EINVAL; + } else if (nt == 0x3) { + dev_err(dev->dev, + "Invalid value 0x3 for additional trans./microframe\n"); + return -EINVAL; + } else if ((nt + 1) * max > buffer_size) { + dev_err(dev->dev, "Hw buffer size %d < max payload %d * %d\n", + buffer_size, (nt + 1), max); + return -EINVAL; + } else if (max == 0) { + dev_err(dev->dev, "Max payload cannot be set to 0\n"); + return -EINVAL; + } else if (max > ep->ep.maxpacket_limit) { + dev_err(dev->dev, "Requested max payload %d > limit %d\n", + max, ep->ep.maxpacket_limit); + return -EINVAL; + } + + spin_lock(&ep->dev->lock); + + if (!ep->stopped) { + spin_unlock(&ep->dev->lock); + return -EBUSY; + } + + ep->stopped = 0; + ep->wedged = 0; + ep->ep.desc = desc; + ep->ep.maxpacket = max; + ep->dma_start = 0; + + + if (nt) { + /* + * Maximum possible size of all payloads in one microframe + * regardless of direction when using high-bandwidth mode. + */ + ep->bytes_per_buffer = (nt + 1) * max; + } else if (ep->is_in) { + /* + * The biggest multiple of maximum packet size that fits into + * the buffer. The hardware will split up into many packets in + * the IN direction. + */ + ep->bytes_per_buffer = (buffer_size / max) * max; + } else { + /* + * Only single packets will be placed the buffers in the OUT + * direction. + */ + ep->bytes_per_buffer = max; + } + + epctrl = (max << GR_EPCTRL_MAXPL_POS) + | (nt << GR_EPCTRL_NT_POS) + | (mode << GR_EPCTRL_TT_POS) + | GR_EPCTRL_EV; + if (ep->is_in) + epctrl |= GR_EPCTRL_PI; + gr_write32(&ep->regs->epctrl, epctrl); + + gr_write32(&ep->regs->dmactrl, GR_DMACTRL_IE | GR_DMACTRL_AI); + + spin_unlock(&ep->dev->lock); + + dev_dbg(ep->dev->dev, "EP: %s enabled - %s with %d bytes/buffer\n", + ep->ep.name, gr_modestring[mode], ep->bytes_per_buffer); + return 0; +} + +/* Disable endpoint. Not for ep0in and ep0out that are handled separately. */ +static int gr_ep_disable(struct usb_ep *_ep) +{ + struct gr_ep *ep; + struct gr_udc *dev; + unsigned long flags; + + ep = container_of(_ep, struct gr_ep, ep); + if (!_ep || !ep->ep.desc) + return -ENODEV; + + dev = ep->dev; + + /* 'ep0' IN and OUT are reserved */ + if (ep == &dev->epo[0] || ep == &dev->epi[0]) + return -EINVAL; + + if (dev->ep0state == GR_EP0_SUSPEND) + return -EBUSY; + + dev_dbg(ep->dev->dev, "EP: disable %s\n", ep->ep.name); + + spin_lock_irqsave(&dev->lock, flags); + + gr_ep_nuke(ep); + gr_ep_reset(ep); + ep->ep.desc = NULL; + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +/* + * Frees a request, but not any DMA buffers associated with it + * (gr_finish_request should already have taken care of that). + */ +static void gr_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct gr_request *req; + + if (!_ep || !_req) + return; + req = container_of(_req, struct gr_request, req); + + /* Leads to memory leak */ + WARN(!list_empty(&req->queue), + "request not dequeued properly before freeing\n"); + + kfree(req); +} + +/* Queue a request from the gadget */ +static int gr_queue_ext(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct gr_ep *ep; + struct gr_request *req; + struct gr_udc *dev; + int ret; + + if (unlikely(!_ep || !_req)) + return -EINVAL; + + ep = container_of(_ep, struct gr_ep, ep); + req = container_of(_req, struct gr_request, req); + dev = ep->dev; + + spin_lock(&ep->dev->lock); + + /* + * The ep0 pointer in the gadget struct is used both for ep0in and + * ep0out. In a data stage in the out direction ep0out needs to be used + * instead of the default ep0in. Completion functions might use + * driver_data, so that needs to be copied as well. + */ + if ((ep == &dev->epi[0]) && (dev->ep0state == GR_EP0_ODATA)) { + ep = &dev->epo[0]; + ep->ep.driver_data = dev->epi[0].ep.driver_data; + } + + if (ep->is_in) + gr_dbgprint_request("EXTERN", ep, req); + + ret = gr_queue(ep, req, GFP_ATOMIC); + + spin_unlock(&ep->dev->lock); + + return ret; +} + +/* Dequeue JUST ONE request */ +static int gr_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct gr_request *req = NULL, *iter; + struct gr_ep *ep; + struct gr_udc *dev; + int ret = 0; + unsigned long flags; + + ep = container_of(_ep, struct gr_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 == GR_EP0_SUSPEND) + return -EBUSY; + + spin_lock_irqsave(&dev->lock, flags); + + /* Make sure it's actually queued on this endpoint */ + list_for_each_entry(iter, &ep->queue, queue) { + if (&iter->req != _req) + continue; + req = iter; + break; + } + if (!req) { + ret = -EINVAL; + goto out; + } + + if (list_first_entry(&ep->queue, struct gr_request, queue) == req) { + /* This request is currently being processed */ + gr_abort_dma(ep); + if (ep->stopped) + gr_finish_request(ep, req, -ECONNRESET); + else + gr_dma_advance(ep, -ECONNRESET); + } else if (!list_empty(&req->queue)) { + /* Not being processed - gr_finish_request dequeues it */ + gr_finish_request(ep, req, -ECONNRESET); + } else { + ret = -EOPNOTSUPP; + } + +out: + spin_unlock_irqrestore(&dev->lock, flags); + + return ret; +} + +/* Helper for gr_set_halt and gr_set_wedge */ +static int gr_set_halt_wedge(struct usb_ep *_ep, int halt, int wedge) +{ + int ret; + struct gr_ep *ep; + + if (!_ep) + return -ENODEV; + ep = container_of(_ep, struct gr_ep, ep); + + spin_lock(&ep->dev->lock); + + /* Halting an IN endpoint should fail if queue is not empty */ + if (halt && ep->is_in && !list_empty(&ep->queue)) { + ret = -EAGAIN; + goto out; + } + + ret = gr_ep_halt_wedge(ep, halt, wedge, 0); + +out: + spin_unlock(&ep->dev->lock); + + return ret; +} + +/* Halt endpoint */ +static int gr_set_halt(struct usb_ep *_ep, int halt) +{ + return gr_set_halt_wedge(_ep, halt, 0); +} + +/* Halt and wedge endpoint */ +static int gr_set_wedge(struct usb_ep *_ep) +{ + return gr_set_halt_wedge(_ep, 1, 1); +} + +/* + * Return the total number of bytes currently stored in the internal buffers of + * the endpoint. + */ +static int gr_fifo_status(struct usb_ep *_ep) +{ + struct gr_ep *ep; + u32 epstat; + u32 bytes = 0; + + if (!_ep) + return -ENODEV; + ep = container_of(_ep, struct gr_ep, ep); + + epstat = gr_read32(&ep->regs->epstat); + + if (epstat & GR_EPSTAT_B0) + bytes += (epstat & GR_EPSTAT_B0CNT_MASK) >> GR_EPSTAT_B0CNT_POS; + if (epstat & GR_EPSTAT_B1) + bytes += (epstat & GR_EPSTAT_B1CNT_MASK) >> GR_EPSTAT_B1CNT_POS; + + return bytes; +} + + +/* Empty data from internal buffers of an endpoint. */ +static void gr_fifo_flush(struct usb_ep *_ep) +{ + struct gr_ep *ep; + u32 epctrl; + + if (!_ep) + return; + ep = container_of(_ep, struct gr_ep, ep); + dev_vdbg(ep->dev->dev, "EP: flush fifo %s\n", ep->ep.name); + + spin_lock(&ep->dev->lock); + + epctrl = gr_read32(&ep->regs->epctrl); + epctrl |= GR_EPCTRL_CB; + gr_write32(&ep->regs->epctrl, epctrl); + + spin_unlock(&ep->dev->lock); +} + +static const struct usb_ep_ops gr_ep_ops = { + .enable = gr_ep_enable, + .disable = gr_ep_disable, + + .alloc_request = gr_alloc_request, + .free_request = gr_free_request, + + .queue = gr_queue_ext, + .dequeue = gr_dequeue, + + .set_halt = gr_set_halt, + .set_wedge = gr_set_wedge, + .fifo_status = gr_fifo_status, + .fifo_flush = gr_fifo_flush, +}; + +/* ---------------------------------------------------------------------- */ +/* USB Gadget ops */ + +static int gr_get_frame(struct usb_gadget *_gadget) +{ + struct gr_udc *dev; + + if (!_gadget) + return -ENODEV; + dev = container_of(_gadget, struct gr_udc, gadget); + return gr_read32(&dev->regs->status) & GR_STATUS_FN_MASK; +} + +static int gr_wakeup(struct usb_gadget *_gadget) +{ + struct gr_udc *dev; + + if (!_gadget) + return -ENODEV; + dev = container_of(_gadget, struct gr_udc, gadget); + + /* Remote wakeup feature not enabled by host*/ + if (!dev->remote_wakeup) + return -EINVAL; + + spin_lock(&dev->lock); + + gr_write32(&dev->regs->control, + gr_read32(&dev->regs->control) | GR_CONTROL_RW); + + spin_unlock(&dev->lock); + + return 0; +} + +static int gr_pullup(struct usb_gadget *_gadget, int is_on) +{ + struct gr_udc *dev; + u32 control; + + if (!_gadget) + return -ENODEV; + dev = container_of(_gadget, struct gr_udc, gadget); + + spin_lock(&dev->lock); + + control = gr_read32(&dev->regs->control); + if (is_on) + control |= GR_CONTROL_EP; + else + control &= ~GR_CONTROL_EP; + gr_write32(&dev->regs->control, control); + + spin_unlock(&dev->lock); + + return 0; +} + +static int gr_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct gr_udc *dev = to_gr_udc(gadget); + + spin_lock(&dev->lock); + + /* Hook up the driver */ + dev->driver = driver; + + /* Get ready for host detection */ + gr_enable_vbus_detect(dev); + + spin_unlock(&dev->lock); + + return 0; +} + +static int gr_udc_stop(struct usb_gadget *gadget) +{ + struct gr_udc *dev = to_gr_udc(gadget); + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + dev->driver = NULL; + gr_stop_activity(dev); + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +static const struct usb_gadget_ops gr_ops = { + .get_frame = gr_get_frame, + .wakeup = gr_wakeup, + .pullup = gr_pullup, + .udc_start = gr_udc_start, + .udc_stop = gr_udc_stop, + /* Other operations not supported */ +}; + +/* ---------------------------------------------------------------------- */ +/* Module probe, removal and of-matching */ + +static const char * const onames[] = { + "ep0out", "ep1out", "ep2out", "ep3out", "ep4out", "ep5out", + "ep6out", "ep7out", "ep8out", "ep9out", "ep10out", "ep11out", + "ep12out", "ep13out", "ep14out", "ep15out" +}; + +static const char * const inames[] = { + "ep0in", "ep1in", "ep2in", "ep3in", "ep4in", "ep5in", + "ep6in", "ep7in", "ep8in", "ep9in", "ep10in", "ep11in", + "ep12in", "ep13in", "ep14in", "ep15in" +}; + +/* Must be called with dev->lock held */ +static int gr_ep_init(struct gr_udc *dev, int num, int is_in, u32 maxplimit) +{ + struct gr_ep *ep; + struct gr_request *req; + struct usb_request *_req; + void *buf; + + if (is_in) { + ep = &dev->epi[num]; + ep->ep.name = inames[num]; + ep->regs = &dev->regs->epi[num]; + } else { + ep = &dev->epo[num]; + ep->ep.name = onames[num]; + ep->regs = &dev->regs->epo[num]; + } + + gr_ep_reset(ep); + ep->num = num; + ep->is_in = is_in; + ep->dev = dev; + ep->ep.ops = &gr_ep_ops; + INIT_LIST_HEAD(&ep->queue); + + if (num == 0) { + _req = gr_alloc_request(&ep->ep, GFP_ATOMIC); + if (!_req) + return -ENOMEM; + + buf = devm_kzalloc(dev->dev, PAGE_SIZE, GFP_DMA | GFP_ATOMIC); + if (!buf) { + gr_free_request(&ep->ep, _req); + return -ENOMEM; + } + + req = container_of(_req, struct gr_request, req); + req->req.buf = buf; + req->req.length = MAX_CTRL_PL_SIZE; + + if (is_in) + dev->ep0reqi = req; /* Complete gets set as used */ + else + dev->ep0reqo = req; /* Completion treated separately */ + + usb_ep_set_maxpacket_limit(&ep->ep, MAX_CTRL_PL_SIZE); + ep->bytes_per_buffer = MAX_CTRL_PL_SIZE; + + ep->ep.caps.type_control = true; + } else { + usb_ep_set_maxpacket_limit(&ep->ep, (u16)maxplimit); + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + + ep->ep.caps.type_iso = true; + ep->ep.caps.type_bulk = true; + ep->ep.caps.type_int = true; + } + list_add_tail(&ep->ep_list, &dev->ep_list); + + if (is_in) + ep->ep.caps.dir_in = true; + else + ep->ep.caps.dir_out = true; + + ep->tailbuf = dma_alloc_coherent(dev->dev, ep->ep.maxpacket_limit, + &ep->tailbuf_paddr, GFP_ATOMIC); + if (!ep->tailbuf) + return -ENOMEM; + + return 0; +} + +/* Must be called with dev->lock held */ +static int gr_udc_init(struct gr_udc *dev) +{ + struct device_node *np = dev->dev->of_node; + u32 epctrl_val; + u32 dmactrl_val; + int i; + int ret = 0; + u32 bufsize; + + gr_set_address(dev, 0); + + INIT_LIST_HEAD(&dev->gadget.ep_list); + dev->gadget.speed = USB_SPEED_UNKNOWN; + dev->gadget.ep0 = &dev->epi[0].ep; + + INIT_LIST_HEAD(&dev->ep_list); + gr_set_ep0state(dev, GR_EP0_DISCONNECT); + + for (i = 0; i < dev->nepo; i++) { + if (of_property_read_u32_index(np, "epobufsizes", i, &bufsize)) + bufsize = 1024; + ret = gr_ep_init(dev, i, 0, bufsize); + if (ret) + return ret; + } + + for (i = 0; i < dev->nepi; i++) { + if (of_property_read_u32_index(np, "epibufsizes", i, &bufsize)) + bufsize = 1024; + ret = gr_ep_init(dev, i, 1, bufsize); + if (ret) + return ret; + } + + /* Must be disabled by default */ + dev->remote_wakeup = 0; + + /* Enable ep0out and ep0in */ + epctrl_val = (MAX_CTRL_PL_SIZE << GR_EPCTRL_MAXPL_POS) | GR_EPCTRL_EV; + dmactrl_val = GR_DMACTRL_IE | GR_DMACTRL_AI; + gr_write32(&dev->epo[0].regs->epctrl, epctrl_val); + gr_write32(&dev->epi[0].regs->epctrl, epctrl_val | GR_EPCTRL_PI); + gr_write32(&dev->epo[0].regs->dmactrl, dmactrl_val); + gr_write32(&dev->epi[0].regs->dmactrl, dmactrl_val); + + return 0; +} + +static void gr_ep_remove(struct gr_udc *dev, int num, int is_in) +{ + struct gr_ep *ep; + + if (is_in) + ep = &dev->epi[num]; + else + ep = &dev->epo[num]; + + if (ep->tailbuf) + dma_free_coherent(dev->dev, ep->ep.maxpacket_limit, + ep->tailbuf, ep->tailbuf_paddr); +} + +static void gr_remove(struct platform_device *pdev) +{ + struct gr_udc *dev = platform_get_drvdata(pdev); + int i; + + if (dev->added) + usb_del_gadget_udc(&dev->gadget); /* Shuts everything down */ + if (dev->driver) { + dev_err(&pdev->dev, + "Driver still in use but removing anyhow\n"); + return; + } + + gr_dfs_delete(dev); + dma_pool_destroy(dev->desc_pool); + platform_set_drvdata(pdev, NULL); + + gr_free_request(&dev->epi[0].ep, &dev->ep0reqi->req); + gr_free_request(&dev->epo[0].ep, &dev->ep0reqo->req); + + for (i = 0; i < dev->nepo; i++) + gr_ep_remove(dev, i, 0); + for (i = 0; i < dev->nepi; i++) + gr_ep_remove(dev, i, 1); +} +static int gr_request_irq(struct gr_udc *dev, int irq) +{ + return devm_request_threaded_irq(dev->dev, irq, gr_irq, gr_irq_handler, + IRQF_SHARED, driver_name, dev); +} + +static int gr_probe(struct platform_device *pdev) +{ + struct gr_udc *dev; + struct gr_regs __iomem *regs; + int retval; + u32 status; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + dev->dev = &pdev->dev; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + dev->irq = platform_get_irq(pdev, 0); + if (dev->irq < 0) + return dev->irq; + + /* Some core configurations has separate irqs for IN and OUT events */ + dev->irqi = platform_get_irq(pdev, 1); + if (dev->irqi > 0) { + dev->irqo = platform_get_irq(pdev, 2); + if (dev->irqo < 0) + return dev->irqo; + } else { + dev->irqi = 0; + } + + dev->gadget.name = driver_name; + dev->gadget.max_speed = USB_SPEED_HIGH; + dev->gadget.ops = &gr_ops; + + spin_lock_init(&dev->lock); + dev->regs = regs; + + platform_set_drvdata(pdev, dev); + + /* Determine number of endpoints and data interface mode */ + status = gr_read32(&dev->regs->status); + dev->nepi = ((status & GR_STATUS_NEPI_MASK) >> GR_STATUS_NEPI_POS) + 1; + dev->nepo = ((status & GR_STATUS_NEPO_MASK) >> GR_STATUS_NEPO_POS) + 1; + + if (!(status & GR_STATUS_DM)) { + dev_err(dev->dev, "Slave mode cores are not supported\n"); + return -ENODEV; + } + + /* --- Effects of the following calls might need explicit cleanup --- */ + + /* Create DMA pool for descriptors */ + dev->desc_pool = dma_pool_create("desc_pool", dev->dev, + sizeof(struct gr_dma_desc), 4, 0); + if (!dev->desc_pool) { + dev_err(dev->dev, "Could not allocate DMA pool"); + return -ENOMEM; + } + + /* Inside lock so that no gadget can use this udc until probe is done */ + retval = usb_add_gadget_udc(dev->dev, &dev->gadget); + if (retval) { + dev_err(dev->dev, "Could not add gadget udc"); + goto out; + } + dev->added = 1; + + spin_lock(&dev->lock); + + retval = gr_udc_init(dev); + if (retval) { + spin_unlock(&dev->lock); + goto out; + } + + /* Clear all interrupt enables that might be left on since last boot */ + gr_disable_interrupts_and_pullup(dev); + + spin_unlock(&dev->lock); + + gr_dfs_create(dev); + + retval = gr_request_irq(dev, dev->irq); + if (retval) { + dev_err(dev->dev, "Failed to request irq %d\n", dev->irq); + goto out; + } + + if (dev->irqi) { + retval = gr_request_irq(dev, dev->irqi); + if (retval) { + dev_err(dev->dev, "Failed to request irqi %d\n", + dev->irqi); + goto out; + } + retval = gr_request_irq(dev, dev->irqo); + if (retval) { + dev_err(dev->dev, "Failed to request irqo %d\n", + dev->irqo); + goto out; + } + } + + if (dev->irqi) + dev_info(dev->dev, "regs: %p, irqs %d, %d, %d\n", dev->regs, + dev->irq, dev->irqi, dev->irqo); + else + dev_info(dev->dev, "regs: %p, irq %d\n", dev->regs, dev->irq); + +out: + if (retval) + gr_remove(pdev); + + return retval; +} + +static const struct of_device_id gr_match[] = { + {.name = "GAISLER_USBDC"}, + {.name = "01_021"}, + {}, +}; +MODULE_DEVICE_TABLE(of, gr_match); + +static struct platform_driver gr_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = gr_match, + }, + .probe = gr_probe, + .remove = gr_remove, +}; +module_platform_driver(gr_driver); + +MODULE_AUTHOR("Aeroflex Gaisler AB."); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/gr_udc.h b/drivers/usb/gadget/udc/gr_udc.h new file mode 100644 index 000000000..701342391 --- /dev/null +++ b/drivers/usb/gadget/udc/gr_udc.h @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * USB Peripheral Controller driver for Aeroflex Gaisler GRUSBDC. + * + * 2013 (c) Aeroflex Gaisler AB + * + * This driver supports GRUSBDC USB Device Controller cores available in the + * GRLIB VHDL IP core library. + * + * Full documentation of the GRUSBDC core can be found here: + * https://www.gaisler.com/products/grlib/grip.pdf + * + * Contributors: + * - Andreas Larsson + * - Marko Isomaki + */ + +/* Control registers on the AMBA bus */ + +#define GR_MAXEP 16 /* Max # endpoints for *each* direction */ + +struct gr_epregs { + u32 epctrl; + union { + struct { /* Slave mode*/ + u32 slvctrl; + u32 slvdata; + }; + struct { /* DMA mode*/ + u32 dmactrl; + u32 dmaaddr; + }; + }; + u32 epstat; +}; + +struct gr_regs { + struct gr_epregs epo[GR_MAXEP]; /* 0x000 - 0x0fc */ + struct gr_epregs epi[GR_MAXEP]; /* 0x100 - 0x1fc */ + u32 control; /* 0x200 */ + u32 status; /* 0x204 */ +}; + +#define GR_EPCTRL_BUFSZ_SCALER 8 +#define GR_EPCTRL_BUFSZ_MASK 0xffe00000 +#define GR_EPCTRL_BUFSZ_POS 21 +#define GR_EPCTRL_PI BIT(20) +#define GR_EPCTRL_CB BIT(19) +#define GR_EPCTRL_CS BIT(18) +#define GR_EPCTRL_MAXPL_MASK 0x0003ff80 +#define GR_EPCTRL_MAXPL_POS 7 +#define GR_EPCTRL_NT_MASK 0x00000060 +#define GR_EPCTRL_NT_POS 5 +#define GR_EPCTRL_TT_MASK 0x00000018 +#define GR_EPCTRL_TT_POS 3 +#define GR_EPCTRL_EH BIT(2) +#define GR_EPCTRL_ED BIT(1) +#define GR_EPCTRL_EV BIT(0) + +#define GR_DMACTRL_AE BIT(10) +#define GR_DMACTRL_AD BIT(3) +#define GR_DMACTRL_AI BIT(2) +#define GR_DMACTRL_IE BIT(1) +#define GR_DMACTRL_DA BIT(0) + +#define GR_EPSTAT_PT BIT(29) +#define GR_EPSTAT_PR BIT(29) +#define GR_EPSTAT_B1CNT_MASK 0x1fff0000 +#define GR_EPSTAT_B1CNT_POS 16 +#define GR_EPSTAT_B0CNT_MASK 0x0000fff8 +#define GR_EPSTAT_B0CNT_POS 3 +#define GR_EPSTAT_B1 BIT(2) +#define GR_EPSTAT_B0 BIT(1) +#define GR_EPSTAT_BS BIT(0) + +#define GR_CONTROL_SI BIT(31) +#define GR_CONTROL_UI BIT(30) +#define GR_CONTROL_VI BIT(29) +#define GR_CONTROL_SP BIT(28) +#define GR_CONTROL_FI BIT(27) +#define GR_CONTROL_EP BIT(14) +#define GR_CONTROL_DH BIT(13) +#define GR_CONTROL_RW BIT(12) +#define GR_CONTROL_TS_MASK 0x00000e00 +#define GR_CONTROL_TS_POS 9 +#define GR_CONTROL_TM BIT(8) +#define GR_CONTROL_UA_MASK 0x000000fe +#define GR_CONTROL_UA_POS 1 +#define GR_CONTROL_SU BIT(0) + +#define GR_STATUS_NEPI_MASK 0xf0000000 +#define GR_STATUS_NEPI_POS 28 +#define GR_STATUS_NEPO_MASK 0x0f000000 +#define GR_STATUS_NEPO_POS 24 +#define GR_STATUS_DM BIT(23) +#define GR_STATUS_SU BIT(17) +#define GR_STATUS_UR BIT(16) +#define GR_STATUS_VB BIT(15) +#define GR_STATUS_SP BIT(14) +#define GR_STATUS_AF_MASK 0x00003800 +#define GR_STATUS_AF_POS 11 +#define GR_STATUS_FN_MASK 0x000007ff +#define GR_STATUS_FN_POS 0 + + +#define MAX_CTRL_PL_SIZE 64 /* As per USB standard for full and high speed */ + +/*-------------------------------------------------------------------------*/ + +/* Driver data structures and utilities */ + +struct gr_dma_desc { + u32 ctrl; + u32 data; + u32 next; + + /* These must be last because hw uses the previous three */ + u32 paddr; + struct gr_dma_desc *next_desc; +}; + +#define GR_DESC_OUT_CTRL_SE BIT(17) +#define GR_DESC_OUT_CTRL_IE BIT(15) +#define GR_DESC_OUT_CTRL_NX BIT(14) +#define GR_DESC_OUT_CTRL_EN BIT(13) +#define GR_DESC_OUT_CTRL_LEN_MASK 0x00001fff + +#define GR_DESC_IN_CTRL_MO BIT(18) +#define GR_DESC_IN_CTRL_PI BIT(17) +#define GR_DESC_IN_CTRL_ML BIT(16) +#define GR_DESC_IN_CTRL_IE BIT(15) +#define GR_DESC_IN_CTRL_NX BIT(14) +#define GR_DESC_IN_CTRL_EN BIT(13) +#define GR_DESC_IN_CTRL_LEN_MASK 0x00001fff + +#define GR_DESC_DMAADDR_MASK 0xfffffffc + +struct gr_ep { + struct usb_ep ep; + struct gr_udc *dev; + u16 bytes_per_buffer; + unsigned int dma_start; + struct gr_epregs __iomem *regs; + + unsigned num:8; + unsigned is_in:1; + unsigned stopped:1; + unsigned wedged:1; + unsigned callback:1; + + /* analogous to a host-side qh */ + struct list_head queue; + + struct list_head ep_list; + + /* Bounce buffer for end of "odd" sized OUT requests */ + void *tailbuf; + dma_addr_t tailbuf_paddr; +}; + +struct gr_request { + struct usb_request req; + struct list_head queue; + + /* Chain of dma descriptors */ + struct gr_dma_desc *first_desc; /* First in the chain */ + struct gr_dma_desc *curr_desc; /* Current descriptor */ + struct gr_dma_desc *last_desc; /* Last in the chain */ + + u16 evenlen; /* Size of even length head (if oddlen != 0) */ + u16 oddlen; /* Size of odd length tail if buffer length is "odd" */ + + u8 setup; /* Setup packet */ +}; + +enum gr_ep0state { + GR_EP0_DISCONNECT = 0, /* No host */ + GR_EP0_SETUP, /* Between STATUS ack and SETUP report */ + GR_EP0_IDATA, /* IN data stage */ + GR_EP0_ODATA, /* OUT data stage */ + GR_EP0_ISTATUS, /* Status stage after IN data stage */ + GR_EP0_OSTATUS, /* Status stage after OUT data stage */ + GR_EP0_STALL, /* Data or status stages */ + GR_EP0_SUSPEND, /* USB suspend */ +}; + +struct gr_udc { + struct usb_gadget gadget; + struct gr_ep epi[GR_MAXEP]; + struct gr_ep epo[GR_MAXEP]; + struct usb_gadget_driver *driver; + struct dma_pool *desc_pool; + struct device *dev; + + enum gr_ep0state ep0state; + struct gr_request *ep0reqo; + struct gr_request *ep0reqi; + + struct gr_regs __iomem *regs; + int irq; + int irqi; + int irqo; + + unsigned added:1; + unsigned irq_enabled:1; + unsigned remote_wakeup:1; + + u8 test_mode; + + enum usb_device_state suspended_from; + + unsigned int nepi; + unsigned int nepo; + + struct list_head ep_list; + + spinlock_t lock; /* General lock, a.k.a. "dev->lock" in comments */ +}; + +#define to_gr_udc(gadget) (container_of((gadget), struct gr_udc, gadget)) diff --git a/drivers/usb/gadget/udc/lpc32xx_udc.c b/drivers/usb/gadget/udc/lpc32xx_udc.c new file mode 100644 index 000000000..044c31869 --- /dev/null +++ b/drivers/usb/gadget/udc/lpc32xx_udc.c @@ -0,0 +1,3276 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#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; +}; + +enum atx_type { + ISP1301, + STOTG04, +}; + +/* + * Common UDC structure + */ +struct lpc32xx_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct platform_device *pdev; + struct device *dev; + spinlock_t lock; + struct i2c_client *isp1301_i2c_client; + + /* Board and device specific */ + struct lpc32xx_usbd_cfg *board; + void __iomem *udp_baseaddr; + int udp_irq[4]; + struct clk *usb_slv_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; + enum atx_type atx; + + /* Work queues related to I2C support */ + struct work_struct pullup_job; + struct work_struct power_job; + + /* USB device peripheral - various */ + struct lpc32xx_ep ep[NUM_ENDPOINTS]; + bool enabled; + bool clocked; + bool suspended; + 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) + +/********************************************************************** + * 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 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->gadget.is_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; +} + +DEFINE_SHOW_ATTRIBUTE(udc); + +static void create_debug_file(struct lpc32xx_udc *udc) +{ + debugfs_create_file(debug_filename, 0, NULL, udc, &udc_fops); +} + +static void remove_debug_file(struct lpc32xx_udc *udc) +{ + debugfs_lookup_and_remove(debug_filename, NULL); +} + +#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) +{ + u8 value; + s32 vendor, product; + + vendor = i2c_smbus_read_word_data(udc->isp1301_i2c_client, 0x00); + product = i2c_smbus_read_word_data(udc->isp1301_i2c_client, 0x02); + + if (vendor == 0x0483 && product == 0xa0c4) + udc->atx = STOTG04; + + /* 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); + + value = MC2_BI_DI; + if (udc->atx != STOTG04) + value |= MC2_SPD_SUSP_CTRL; + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_MODE_CONTROL_2, value); + + /* 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); + + 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_RISING | ISP1301_I2C_REG_CLEAR_ADDR, ~0); + + dev_info(udc->dev, "ISP1301 Vendor ID : 0x%04x\n", vendor); + dev_info(udc->dev, "ISP1301 Product ID : 0x%04x\n", product); + 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) +{ + /* There is no "global power down" register for stotg04 */ + if (udc->atx == STOTG04) + return; + + 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) +{ + 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); + + 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 = dma_pool_alloc(udc->dd_cache, GFP_ATOMIC | 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; + clk_prepare_enable(udc->usb_slv_clk); + } else { + if (!udc->clocked) + return; + + udc->clocked = 0; + clk_disable_unprepare(udc->usb_slv_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 (((uintptr_t) 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 > 4) + bl = 4; + + for (i = 0; i < bl; i++) + data[n + i] = (u8) ((tmp >> (i * 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 (((uintptr_t) 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); + usb_ep_set_maxpacket_limit(&ep->ep, 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); + usb_gadget_giveback_request(&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->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; + u16 maxpacket; + u32 tmp; + unsigned long flags; + + /* Verify EP data */ + if ((!_ep) || (!ep) || (!desc) || + (desc->bDescriptorType != USB_DT_ENDPOINT)) + return -EINVAL; + + udc = ep->udc; + 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 = usb_endpoint_type(desc); + 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_obj(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 (!_ep || !_req || !_req->complete || !_req->buf || + !list_empty(&req->queue)) + return -EINVAL; + + udc = ep->udc; + + if (udc->gadget.speed == USB_SPEED_UNKNOWN) + return -EPIPE; + + 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 = NULL, *iter; + 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(iter, &ep->queue, queue) { + if (&iter->req != _req) + continue; + req = iter; + break; + } + if (!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; + unsigned long flags; + + if ((!ep) || (ep->hwep_num <= 1)) + return -EINVAL; + + /* Don't halt an IN EP */ + if (ep->is_in) + return -EAGAIN; + + udc = ep->udc; + 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 */ +static 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. + */ +static 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); + + 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; + 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); + 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); + 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->gadget.is_selfpowered; + 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, 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); + 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; + } + 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) +{ + gadget->is_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 *); + +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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + }, + .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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + }, + .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 lpc32xx_udc *udc) +{ + u8 value; + + 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); + } + } +} + +static irqreturn_t lpc32xx_usb_vbus_irq(int irq, void *_udc) +{ + struct lpc32xx_udc *udc = _udc; + + vbus_work(udc); + + return IRQ_HANDLED; +} + +static int lpc32xx_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct lpc32xx_udc *udc = to_udc(gadget); + + 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->gadget.is_selfpowered = 1; + udc->vbus = 0; + + /* Force VBUS process once to check for cable insertion */ + udc->last_vbus = udc->vbus = 0; + vbus_work(udc); + + /* enable interrupts */ + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_INTERRUPT_FALLING, INT_SESS_VLD | INT_VBUS_VLD); + i2c_smbus_write_byte_data(udc->isp1301_i2c_client, + ISP1301_I2C_INTERRUPT_RISING, INT_SESS_VLD | INT_VBUS_VLD); + + return 0; +} + +static int lpc32xx_stop(struct usb_gadget *gadget) +{ + struct lpc32xx_udc *udc = to_udc(gadget); + + 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_RISING | ISP1301_I2C_REG_CLEAR_ADDR, ~0); + + 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 */ +} + +static 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 lpc32xx_udc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct lpc32xx_udc *udc; + int retval, i; + dma_addr_t dma_handle; + struct device_node *isp1301_node; + + udc = devm_kmemdup(dev, &controller_template, sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + 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); + of_node_put(isp1301_node); + if (!udc->isp1301_i2c_client) { + return -EPROBE_DEFER; + } + + dev_info(udc->dev, "ISP1301 I2C device at address 0x%x\n", + udc->isp1301_i2c_client->addr); + + pdev->dev.dma_mask = &lpc32xx_usbd_dmamask; + retval = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (retval) + goto err_put_client; + + 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 + */ + + 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) { + retval = udc->udp_irq[i]; + goto err_put_client; + } + } + + udc->udp_baseaddr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(udc->udp_baseaddr)) { + dev_err(udc->dev, "IO map failure\n"); + retval = PTR_ERR(udc->udp_baseaddr); + goto err_put_client; + } + + /* Get USB device clock */ + udc->usb_slv_clk = devm_clk_get(&pdev->dev, NULL); + 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 err_put_client; + } + + /* Enable USB device clock */ + retval = clk_prepare_enable(udc->usb_slv_clk); + if (retval < 0) { + dev_err(udc->dev, "failed to start USB device clock\n"); + goto err_put_client; + } + + /* Setup deferred workqueue data */ + udc->poweron = udc->pullup = 0; + INIT_WORK(&udc->pullup_job, pullup_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 err_disable_clk; + } + 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 err_free_dma; + } + + /* 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 = devm_request_irq(dev, 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 err_destroy_pool; + } + retval = devm_request_irq(dev, 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 err_destroy_pool; + } + + retval = devm_request_irq(dev, 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 err_destroy_pool; + } + + /* The transceiver interrupt is used for VBUS detection and will + kick off the VBUS handler function */ + retval = devm_request_threaded_irq(dev, udc->udp_irq[IRQ_USB_ATX], NULL, + lpc32xx_usb_vbus_irq, IRQF_ONESHOT, + "udc_otg", udc); + if (retval < 0) { + dev_err(udc->dev, "VBUS request irq %d failed\n", + udc->udp_irq[IRQ_USB_ATX]); + goto err_destroy_pool; + } + + /* Initialize wait queue */ + init_waitqueue_head(&udc->ep_disable_wait_queue); + atomic_set(&udc->enabled_ep_cnt, 0); + + retval = usb_add_gadget_udc(dev, &udc->gadget); + if (retval < 0) + goto err_destroy_pool; + + 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; + +err_destroy_pool: + dma_pool_destroy(udc->dd_cache); +err_free_dma: + dma_free_coherent(&pdev->dev, UDCA_BUFF_SIZE, + udc->udca_v_base, udc->udca_p_base); +err_disable_clk: + clk_disable_unprepare(udc->usb_slv_clk); +err_put_client: + put_device(&udc->isp1301_i2c_client->dev); + + dev_err(udc->dev, "%s probe failed, %d\n", driver_name, retval); + + return retval; +} + +static void lpc32xx_udc_remove(struct platform_device *pdev) +{ + struct lpc32xx_udc *udc = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&udc->gadget); + if (udc->driver) { + dev_err(&pdev->dev, + "Driver still in use but removing anyhow\n"); + return; + } + + udc_clk_set(udc, 1); + udc_disable(udc); + pullup(udc, 0); + + 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); + + clk_disable_unprepare(udc->usb_slv_clk); + + put_device(&udc->isp1301_i2c_client->dev); +} + +#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_unprepare(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_prepare_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 const 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 = { + .probe = lpc32xx_udc_probe, + .remove = lpc32xx_udc_remove, + .shutdown = lpc32xx_udc_shutdown, + .suspend = lpc32xx_udc_suspend, + .resume = lpc32xx_udc_resume, + .driver = { + .name = driver_name, + .of_match_table = of_match_ptr(lpc32xx_udc_of_match), + }, +}; + +module_platform_driver(lpc32xx_udc_driver); + +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/udc/m66592-udc.c b/drivers/usb/gadget/udc/m66592-udc.c new file mode 100644 index 000000000..d77c11c4e --- /dev/null +++ b/drivers/usb/gadget/udc/m66592-udc.c @@ -0,0 +1,1697 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * M66592 UDC (USB gadget) + * + * Copyright (C) 2006-2007 Renesas Solutions Corp. + * + * Author : Yoshihiro Shimoda + */ + +#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[usb_endpoint_num(desc)] = 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 (usb_endpoint_type(desc)) { + 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 = usb_endpoint_num(desc); + 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_warn("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_warn("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); + usb_gadget_giveback_request(&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 */ + usb_gadget_udc_reset(&m66592->gadget, m66592->driver); + 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 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) { + u16 brdysts = m66592_read(m66592, M66592_BRDYSTS); + u16 bempsts = m66592_read(m66592, M66592_BEMPSTS); + u16 brdyenb = m66592_read(m66592, M66592_BRDYENB); + u16 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(struct timer_list *t) +{ + struct m66592 *m66592 = timer_container_of(m66592, t, timer); + 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_obj(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 = container_of(_ep, struct m66592_ep, ep); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&ep->m66592->lock, flags); + if (!list_empty(&ep->queue)) { + ret = -EAGAIN; + } else if (value) { + ep->busy = 1; + pipe_stall(ep->m66592, ep->pipenum); + } else { + ep->busy = 0; + pipe_stop(ep->m66592, ep->pipenum); + } + 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 const 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 */ + 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 m66592 *m66592 = to_m66592(g); + + m66592_bclr(m66592, M66592_VBSE | M66592_URST, M66592_INTENB0); + + 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 void m66592_remove(struct platform_device *pdev) +{ + struct m66592 *m66592 = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&m66592->gadget); + + timer_shutdown_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); +} + +static void nop_completion(struct usb_ep *ep, struct usb_request *r) +{ +} + +static int 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 (dev_get_platdata(&pdev->dev) == NULL) { + dev_err(&pdev->dev, "no platform data\n"); + ret = -ENODEV; + goto clean_up; + } + + /* initialize ucd */ + m66592 = kzalloc_obj(struct m66592); + if (m66592 == NULL) { + ret = -ENOMEM; + goto clean_up; + } + + m66592->pdata = dev_get_platdata(&pdev->dev); + m66592->irq_trigger = ires->flags & IRQF_TRIGGER_MASK; + + spin_lock_init(&m66592->lock); + platform_set_drvdata(pdev, m66592); + + m66592->gadget.ops = &m66592_gadget_ops; + m66592->gadget.max_speed = USB_SPEED_HIGH; + m66592->gadget.name = udc_name; + + timer_setup(&m66592->timer, m66592_timer, 0); + 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; + usb_ep_set_maxpacket_limit(&ep->ep, 512); + + if (i == 0) { + ep->ep.caps.type_control = true; + } else { + ep->ep.caps.type_iso = true; + ep->ep.caps.type_bulk = true; + ep->ep.caps.type_int = true; + } + + ep->ep.caps.dir_in = true; + ep->ep.caps.dir_out = true; + } + usb_ep_set_maxpacket_limit(&m66592->ep[0].ep, 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); + m66592->ep0_req = NULL; +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 = { + .probe = m66592_probe, + .remove = m66592_remove, + .driver = { + .name = udc_name, + }, +}; + +module_platform_driver(m66592_driver); diff --git a/drivers/usb/gadget/udc/m66592-udc.h b/drivers/usb/gadget/udc/m66592-udc.h new file mode 100644 index 000000000..01a64685b --- /dev/null +++ b/drivers/usb/gadget/udc/m66592-udc.h @@ -0,0 +1,603 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * M66592 UDC (USB gadget) + * + * Copyright (C) 2006-2007 Renesas Solutions Corp. + * + * Author : Yoshihiro Shimoda + */ + +#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/udc/max3420_udc.c b/drivers/usb/gadget/udc/max3420_udc.c new file mode 100644 index 000000000..11fd61cba --- /dev/null +++ b/drivers/usb/gadget/udc/max3420_udc.c @@ -0,0 +1,1330 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MAX3420 Device Controller driver for USB. + * + * Author: Jaswinder Singh Brar + * (C) Copyright 2019-2020 Linaro Ltd + * + * Based on: + * o MAX3420E datasheet + * https://datasheets.maximintegrated.com/en/ds/MAX3420E.pdf + * o MAX342{0,1}E Programming Guides + * https://pdfserv.maximintegrated.com/en/an/AN3598.pdf + * https://pdfserv.maximintegrated.com/en/an/AN3785.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX3420_MAX_EPS 4 +#define MAX3420_EP_MAX_PACKET 64 /* Same for all Endpoints */ +#define MAX3420_EPNAME_SIZE 16 /* Buffer size for endpoint name */ + +#define MAX3420_ACKSTAT BIT(0) + +#define MAX3420_SPI_DIR_RD 0 /* read register from MAX3420 */ +#define MAX3420_SPI_DIR_WR 1 /* write register to MAX3420 */ + +/* SPI commands: */ +#define MAX3420_SPI_DIR_SHIFT 1 +#define MAX3420_SPI_REG_SHIFT 3 + +#define MAX3420_REG_EP0FIFO 0 +#define MAX3420_REG_EP1FIFO 1 +#define MAX3420_REG_EP2FIFO 2 +#define MAX3420_REG_EP3FIFO 3 +#define MAX3420_REG_SUDFIFO 4 +#define MAX3420_REG_EP0BC 5 +#define MAX3420_REG_EP1BC 6 +#define MAX3420_REG_EP2BC 7 +#define MAX3420_REG_EP3BC 8 + +#define MAX3420_REG_EPSTALLS 9 + #define ACKSTAT BIT(6) + #define STLSTAT BIT(5) + #define STLEP3IN BIT(4) + #define STLEP2IN BIT(3) + #define STLEP1OUT BIT(2) + #define STLEP0OUT BIT(1) + #define STLEP0IN BIT(0) + +#define MAX3420_REG_CLRTOGS 10 + #define EP3DISAB BIT(7) + #define EP2DISAB BIT(6) + #define EP1DISAB BIT(5) + #define CTGEP3IN BIT(4) + #define CTGEP2IN BIT(3) + #define CTGEP1OUT BIT(2) + +#define MAX3420_REG_EPIRQ 11 +#define MAX3420_REG_EPIEN 12 + #define SUDAVIRQ BIT(5) + #define IN3BAVIRQ BIT(4) + #define IN2BAVIRQ BIT(3) + #define OUT1DAVIRQ BIT(2) + #define OUT0DAVIRQ BIT(1) + #define IN0BAVIRQ BIT(0) + +#define MAX3420_REG_USBIRQ 13 +#define MAX3420_REG_USBIEN 14 + #define OSCOKIRQ BIT(0) + #define RWUDNIRQ BIT(1) + #define BUSACTIRQ BIT(2) + #define URESIRQ BIT(3) + #define SUSPIRQ BIT(4) + #define NOVBUSIRQ BIT(5) + #define VBUSIRQ BIT(6) + #define URESDNIRQ BIT(7) + +#define MAX3420_REG_USBCTL 15 + #define HOSCSTEN BIT(7) + #define VBGATE BIT(6) + #define CHIPRES BIT(5) + #define PWRDOWN BIT(4) + #define CONNECT BIT(3) + #define SIGRWU BIT(2) + +#define MAX3420_REG_CPUCTL 16 + #define IE BIT(0) + +#define MAX3420_REG_PINCTL 17 + #define EP3INAK BIT(7) + #define EP2INAK BIT(6) + #define EP0INAK BIT(5) + #define FDUPSPI BIT(4) + #define INTLEVEL BIT(3) + #define POSINT BIT(2) + #define GPXB BIT(1) + #define GPXA BIT(0) + +#define MAX3420_REG_REVISION 18 + +#define MAX3420_REG_FNADDR 19 + #define FNADDR_MASK 0x7f + +#define MAX3420_REG_IOPINS 20 +#define MAX3420_REG_IOPINS2 21 +#define MAX3420_REG_GPINIRQ 22 +#define MAX3420_REG_GPINIEN 23 +#define MAX3420_REG_GPINPOL 24 +#define MAX3420_REG_HIRQ 25 +#define MAX3420_REG_HIEN 26 +#define MAX3420_REG_MODE 27 +#define MAX3420_REG_PERADDR 28 +#define MAX3420_REG_HCTL 29 +#define MAX3420_REG_HXFR 30 +#define MAX3420_REG_HRSL 31 + +#define ENABLE_IRQ BIT(0) +#define IOPIN_UPDATE BIT(1) +#define REMOTE_WAKEUP BIT(2) +#define CONNECT_HOST GENMASK(4, 3) +#define HCONNECT (1 << 3) +#define HDISCONNECT (3 << 3) +#define UDC_START GENMASK(6, 5) +#define START (1 << 5) +#define STOP (3 << 5) +#define ENABLE_EP GENMASK(8, 7) +#define ENABLE (1 << 7) +#define DISABLE (3 << 7) +#define STALL_EP GENMASK(10, 9) +#define STALL (1 << 9) +#define UNSTALL (3 << 9) + +#define MAX3420_CMD(c) FIELD_PREP(GENMASK(7, 3), c) +#define MAX3420_SPI_CMD_RD(c) (MAX3420_CMD(c) | (0 << 1)) +#define MAX3420_SPI_CMD_WR(c) (MAX3420_CMD(c) | (1 << 1)) + +struct max3420_req { + struct usb_request usb_req; + struct list_head queue; + struct max3420_ep *ep; +}; + +struct max3420_ep { + struct usb_ep ep_usb; + struct max3420_udc *udc; + struct list_head queue; + char name[MAX3420_EPNAME_SIZE]; + unsigned int maxpacket; + spinlock_t lock; + int halted; + u32 todo; + int id; +}; + +struct max3420_udc { + struct usb_gadget gadget; + struct max3420_ep ep[MAX3420_MAX_EPS]; + struct usb_gadget_driver *driver; + struct task_struct *thread_task; + int remote_wkp, is_selfpowered; + bool vbus_active, softconnect; + struct usb_ctrlrequest setup; + struct mutex spi_bus_mutex; + struct max3420_req ep0req; + struct spi_device *spi; + struct device *dev; + spinlock_t lock; + bool suspended; + u8 ep0buf[64]; + u32 todo; +}; + +#define to_max3420_req(r) container_of((r), struct max3420_req, usb_req) +#define to_max3420_ep(e) container_of((e), struct max3420_ep, ep_usb) +#define to_udc(g) container_of((g), struct max3420_udc, gadget) + +#define DRIVER_DESC "MAX3420 USB Device-Mode Driver" +static const char driver_name[] = "max3420-udc"; + +/* Control endpoint configuration.*/ +static const struct usb_endpoint_descriptor ep0_desc = { + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(MAX3420_EP_MAX_PACKET), +}; + +static void spi_ack_ctrl(struct max3420_udc *udc) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 txdata[1]; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + txdata[0] = MAX3420_ACKSTAT; + transfer.tx_buf = txdata; + transfer.len = 1; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); +} + +static u8 spi_rd8_ack(struct max3420_udc *udc, u8 reg, int actstat) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 txdata[2], rxdata[2]; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + txdata[0] = MAX3420_SPI_CMD_RD(reg) | (actstat ? MAX3420_ACKSTAT : 0); + transfer.tx_buf = txdata; + transfer.rx_buf = rxdata; + transfer.len = 2; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); + + return rxdata[1]; +} + +static u8 spi_rd8(struct max3420_udc *udc, u8 reg) +{ + return spi_rd8_ack(udc, reg, 0); +} + +static void spi_wr8_ack(struct max3420_udc *udc, u8 reg, u8 val, int actstat) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 txdata[2]; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + txdata[0] = MAX3420_SPI_CMD_WR(reg) | (actstat ? MAX3420_ACKSTAT : 0); + txdata[1] = val; + + transfer.tx_buf = txdata; + transfer.len = 2; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); +} + +static void spi_wr8(struct max3420_udc *udc, u8 reg, u8 val) +{ + spi_wr8_ack(udc, reg, val, 0); +} + +static void spi_rd_buf(struct max3420_udc *udc, u8 reg, void *buf, u8 len) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 local_buf[MAX3420_EP_MAX_PACKET + 1] = {}; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + local_buf[0] = MAX3420_SPI_CMD_RD(reg); + transfer.tx_buf = &local_buf[0]; + transfer.rx_buf = &local_buf[0]; + transfer.len = len + 1; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); + + memcpy(buf, &local_buf[1], len); +} + +static void spi_wr_buf(struct max3420_udc *udc, u8 reg, void *buf, u8 len) +{ + struct spi_device *spi = udc->spi; + struct spi_transfer transfer; + struct spi_message msg; + u8 local_buf[MAX3420_EP_MAX_PACKET + 1] = {}; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + local_buf[0] = MAX3420_SPI_CMD_WR(reg); + memcpy(&local_buf[1], buf, len); + + transfer.tx_buf = local_buf; + transfer.len = len + 1; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); +} + +static int spi_max3420_enable(struct max3420_ep *ep) +{ + struct max3420_udc *udc = ep->udc; + unsigned long flags; + u8 epdis, epien; + int todo; + + spin_lock_irqsave(&ep->lock, flags); + todo = ep->todo & ENABLE_EP; + ep->todo &= ~ENABLE_EP; + spin_unlock_irqrestore(&ep->lock, flags); + + if (!todo || ep->id == 0) + return false; + + epien = spi_rd8(udc, MAX3420_REG_EPIEN); + epdis = spi_rd8(udc, MAX3420_REG_CLRTOGS); + + if (todo == ENABLE) { + epdis &= ~BIT(ep->id + 4); + epien |= BIT(ep->id + 1); + } else { + epdis |= BIT(ep->id + 4); + epien &= ~BIT(ep->id + 1); + } + + spi_wr8(udc, MAX3420_REG_CLRTOGS, epdis); + spi_wr8(udc, MAX3420_REG_EPIEN, epien); + + return true; +} + +static int spi_max3420_stall(struct max3420_ep *ep) +{ + struct max3420_udc *udc = ep->udc; + unsigned long flags; + u8 epstalls; + int todo; + + spin_lock_irqsave(&ep->lock, flags); + todo = ep->todo & STALL_EP; + ep->todo &= ~STALL_EP; + spin_unlock_irqrestore(&ep->lock, flags); + + if (!todo || ep->id == 0) + return false; + + epstalls = spi_rd8(udc, MAX3420_REG_EPSTALLS); + if (todo == STALL) { + ep->halted = 1; + epstalls |= BIT(ep->id + 1); + } else { + u8 clrtogs; + + ep->halted = 0; + epstalls &= ~BIT(ep->id + 1); + clrtogs = spi_rd8(udc, MAX3420_REG_CLRTOGS); + clrtogs |= BIT(ep->id + 1); + spi_wr8(udc, MAX3420_REG_CLRTOGS, clrtogs); + } + spi_wr8(udc, MAX3420_REG_EPSTALLS, epstalls | ACKSTAT); + + return true; +} + +static int spi_max3420_rwkup(struct max3420_udc *udc) +{ + unsigned long flags; + int wake_remote; + u8 usbctl; + + spin_lock_irqsave(&udc->lock, flags); + wake_remote = udc->todo & REMOTE_WAKEUP; + udc->todo &= ~REMOTE_WAKEUP; + spin_unlock_irqrestore(&udc->lock, flags); + + if (!wake_remote || !udc->suspended) + return false; + + /* Set Remote-WkUp Signal*/ + usbctl = spi_rd8(udc, MAX3420_REG_USBCTL); + usbctl |= SIGRWU; + spi_wr8(udc, MAX3420_REG_USBCTL, usbctl); + + msleep_interruptible(5); + + /* Clear Remote-WkUp Signal*/ + usbctl = spi_rd8(udc, MAX3420_REG_USBCTL); + usbctl &= ~SIGRWU; + spi_wr8(udc, MAX3420_REG_USBCTL, usbctl); + + udc->suspended = false; + + return true; +} + +static void max3420_nuke(struct max3420_ep *ep, int status); +static void __max3420_stop(struct max3420_udc *udc) +{ + u8 val; + int i; + + /* clear all pending requests */ + for (i = 1; i < MAX3420_MAX_EPS; i++) + max3420_nuke(&udc->ep[i], -ECONNRESET); + + /* Disable IRQ to CPU */ + spi_wr8(udc, MAX3420_REG_CPUCTL, 0); + + val = spi_rd8(udc, MAX3420_REG_USBCTL); + val |= PWRDOWN; + if (udc->is_selfpowered) + val &= ~HOSCSTEN; + else + val |= HOSCSTEN; + spi_wr8(udc, MAX3420_REG_USBCTL, val); +} + +static void __max3420_start(struct max3420_udc *udc) +{ + u8 val; + + /* Need this delay if bus-powered, + * but even for self-powered it helps stability + */ + msleep_interruptible(250); + + /* configure SPI */ + spi_wr8(udc, MAX3420_REG_PINCTL, FDUPSPI); + + /* Chip Reset */ + spi_wr8(udc, MAX3420_REG_USBCTL, CHIPRES); + msleep_interruptible(5); + spi_wr8(udc, MAX3420_REG_USBCTL, 0); + + /* Poll for OSC to stabilize */ + while (1) { + val = spi_rd8(udc, MAX3420_REG_USBIRQ); + if (val & OSCOKIRQ) + break; + cond_resched(); + } + + /* Enable PULL-UP only when Vbus detected */ + val = spi_rd8(udc, MAX3420_REG_USBCTL); + val |= VBGATE | CONNECT; + spi_wr8(udc, MAX3420_REG_USBCTL, val); + + val = URESDNIRQ | URESIRQ; + if (udc->is_selfpowered) + val |= NOVBUSIRQ; + spi_wr8(udc, MAX3420_REG_USBIEN, val); + + /* Enable only EP0 interrupts */ + val = IN0BAVIRQ | OUT0DAVIRQ | SUDAVIRQ; + spi_wr8(udc, MAX3420_REG_EPIEN, val); + + /* Enable IRQ to CPU */ + spi_wr8(udc, MAX3420_REG_CPUCTL, IE); +} + +static int max3420_start(struct max3420_udc *udc) +{ + unsigned long flags; + int todo; + + spin_lock_irqsave(&udc->lock, flags); + todo = udc->todo & UDC_START; + udc->todo &= ~UDC_START; + spin_unlock_irqrestore(&udc->lock, flags); + + if (!todo) + return false; + + if (udc->vbus_active && udc->softconnect) + __max3420_start(udc); + else + __max3420_stop(udc); + + return true; +} + +static irqreturn_t max3420_vbus_handler(int irq, void *dev_id) +{ + struct max3420_udc *udc = dev_id; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + /* its a vbus change interrupt */ + udc->vbus_active = !udc->vbus_active; + udc->todo |= UDC_START; + usb_udc_vbus_handler(&udc->gadget, udc->vbus_active); + usb_gadget_set_state(&udc->gadget, udc->vbus_active + ? USB_STATE_POWERED : USB_STATE_NOTATTACHED); + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task) + wake_up_process(udc->thread_task); + + return IRQ_HANDLED; +} + +static irqreturn_t max3420_irq_handler(int irq, void *dev_id) +{ + struct max3420_udc *udc = dev_id; + struct spi_device *spi = udc->spi; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + if ((udc->todo & ENABLE_IRQ) == 0) { + disable_irq_nosync(spi->irq); + udc->todo |= ENABLE_IRQ; + } + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task) + wake_up_process(udc->thread_task); + + return IRQ_HANDLED; +} + +static void max3420_getstatus(struct max3420_udc *udc) +{ + struct max3420_ep *ep; + u16 status = 0; + + switch (udc->setup.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + /* Get device status */ + status = udc->gadget.is_selfpowered << USB_DEVICE_SELF_POWERED; + status |= (udc->remote_wkp << USB_DEVICE_REMOTE_WAKEUP); + break; + case USB_RECIP_INTERFACE: + if (udc->driver->setup(&udc->gadget, &udc->setup) < 0) + goto stall; + break; + case USB_RECIP_ENDPOINT: + ep = &udc->ep[udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK]; + if (udc->setup.wIndex & USB_DIR_IN) { + if (!ep->ep_usb.caps.dir_in) + goto stall; + } else { + if (!ep->ep_usb.caps.dir_out) + goto stall; + } + if (ep->halted) + status = 1 << USB_ENDPOINT_HALT; + break; + default: + goto stall; + } + + status = cpu_to_le16(status); + spi_wr_buf(udc, MAX3420_REG_EP0FIFO, &status, 2); + spi_wr8_ack(udc, MAX3420_REG_EP0BC, 2, 1); + return; +stall: + dev_err(udc->dev, "Can't respond to getstatus request\n"); + spi_wr8(udc, MAX3420_REG_EPSTALLS, STLEP0IN | STLEP0OUT | STLSTAT); +} + +static void max3420_set_clear_feature(struct max3420_udc *udc) +{ + struct max3420_ep *ep; + int set = udc->setup.bRequest == USB_REQ_SET_FEATURE; + unsigned long flags; + int id; + + switch (udc->setup.bRequestType) { + case USB_RECIP_DEVICE: + if (udc->setup.wValue != USB_DEVICE_REMOTE_WAKEUP) + break; + + if (udc->setup.bRequest == USB_REQ_SET_FEATURE) + udc->remote_wkp = 1; + else + udc->remote_wkp = 0; + + return spi_ack_ctrl(udc); + + case USB_RECIP_ENDPOINT: + if (udc->setup.wValue != USB_ENDPOINT_HALT) + break; + + id = udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK; + ep = &udc->ep[id]; + + spin_lock_irqsave(&ep->lock, flags); + ep->todo &= ~STALL_EP; + if (set) + ep->todo |= STALL; + else + ep->todo |= UNSTALL; + spin_unlock_irqrestore(&ep->lock, flags); + + spi_max3420_stall(ep); + return; + default: + break; + } + + dev_err(udc->dev, "Can't respond to SET/CLEAR FEATURE\n"); + spi_wr8(udc, MAX3420_REG_EPSTALLS, STLEP0IN | STLEP0OUT | STLSTAT); +} + +static void max3420_handle_setup(struct max3420_udc *udc) +{ + struct usb_ctrlrequest setup; + + spi_rd_buf(udc, MAX3420_REG_SUDFIFO, (void *)&setup, 8); + + udc->setup = setup; + udc->setup.wValue = cpu_to_le16(setup.wValue); + udc->setup.wIndex = cpu_to_le16(setup.wIndex); + udc->setup.wLength = cpu_to_le16(setup.wLength); + + switch (udc->setup.bRequest) { + case USB_REQ_GET_STATUS: + /* Data+Status phase form udc */ + if ((udc->setup.bRequestType & + (USB_DIR_IN | USB_TYPE_MASK)) != + (USB_DIR_IN | USB_TYPE_STANDARD)) { + break; + } + return max3420_getstatus(udc); + case USB_REQ_SET_ADDRESS: + /* Status phase from udc */ + if (udc->setup.bRequestType != (USB_DIR_OUT | + USB_TYPE_STANDARD | USB_RECIP_DEVICE)) { + break; + } + spi_rd8_ack(udc, MAX3420_REG_FNADDR, 1); + dev_dbg(udc->dev, "Assigned Address=%d\n", udc->setup.wValue); + return; + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + /* Requests with no data phase, status phase from udc */ + if ((udc->setup.bRequestType & USB_TYPE_MASK) + != USB_TYPE_STANDARD) + break; + return max3420_set_clear_feature(udc); + default: + break; + } + + if (udc->driver->setup(&udc->gadget, &setup) < 0) { + /* Stall EP0 */ + spi_wr8(udc, MAX3420_REG_EPSTALLS, + STLEP0IN | STLEP0OUT | STLSTAT); + } +} + +static void max3420_req_done(struct max3420_req *req, int status) +{ + struct max3420_ep *ep = req->ep; + struct max3420_udc *udc = ep->udc; + + if (req->usb_req.status == -EINPROGRESS) + req->usb_req.status = status; + else + status = req->usb_req.status; + + if (status && status != -ESHUTDOWN) + dev_err(udc->dev, "%s done %p, status %d\n", + ep->ep_usb.name, req, status); + + if (req->usb_req.complete) + req->usb_req.complete(&ep->ep_usb, &req->usb_req); +} + +static int max3420_do_data(struct max3420_udc *udc, int ep_id, int in) +{ + struct max3420_ep *ep = &udc->ep[ep_id]; + struct max3420_req *req; + int done, length, psz; + void *buf; + + if (list_empty(&ep->queue)) + return false; + + req = list_first_entry(&ep->queue, struct max3420_req, queue); + buf = req->usb_req.buf + req->usb_req.actual; + + psz = ep->ep_usb.maxpacket; + length = req->usb_req.length - req->usb_req.actual; + length = min(length, psz); + + if (length == 0) { + done = 1; + goto xfer_done; + } + + done = 0; + if (in) { + prefetch(buf); + spi_wr_buf(udc, MAX3420_REG_EP0FIFO + ep_id, buf, length); + spi_wr8(udc, MAX3420_REG_EP0BC + ep_id, length); + if (length < psz) + done = 1; + } else { + psz = spi_rd8(udc, MAX3420_REG_EP0BC + ep_id); + length = min(length, psz); + prefetchw(buf); + spi_rd_buf(udc, MAX3420_REG_EP0FIFO + ep_id, buf, length); + if (length < ep->ep_usb.maxpacket) + done = 1; + } + + req->usb_req.actual += length; + + if (req->usb_req.actual == req->usb_req.length) + done = 1; + +xfer_done: + if (done) { + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + list_del_init(&req->queue); + spin_unlock_irqrestore(&ep->lock, flags); + + if (ep_id == 0) + spi_ack_ctrl(udc); + + max3420_req_done(req, 0); + } + + return true; +} + +static int max3420_handle_irqs(struct max3420_udc *udc) +{ + u8 epien, epirq, usbirq, usbien, reg[4]; + bool ret = false; + + spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 4); + epirq = reg[0]; + epien = reg[1]; + usbirq = reg[2]; + usbien = reg[3]; + + usbirq &= usbien; + epirq &= epien; + + if (epirq & SUDAVIRQ) { + spi_wr8(udc, MAX3420_REG_EPIRQ, SUDAVIRQ); + max3420_handle_setup(udc); + return true; + } + + if (usbirq & VBUSIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, VBUSIRQ); + dev_dbg(udc->dev, "Cable plugged in\n"); + return true; + } + + if (usbirq & NOVBUSIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, NOVBUSIRQ); + dev_dbg(udc->dev, "Cable pulled out\n"); + return true; + } + + if (usbirq & URESIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, URESIRQ); + dev_dbg(udc->dev, "USB Reset - Start\n"); + return true; + } + + if (usbirq & URESDNIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, URESDNIRQ); + dev_dbg(udc->dev, "USB Reset - END\n"); + spi_wr8(udc, MAX3420_REG_USBIEN, URESDNIRQ | URESIRQ); + spi_wr8(udc, MAX3420_REG_EPIEN, SUDAVIRQ | IN0BAVIRQ + | OUT0DAVIRQ); + return true; + } + + if (usbirq & SUSPIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, SUSPIRQ); + dev_dbg(udc->dev, "USB Suspend - Enter\n"); + udc->suspended = true; + return true; + } + + if (usbirq & BUSACTIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, BUSACTIRQ); + dev_dbg(udc->dev, "USB Suspend - Exit\n"); + udc->suspended = false; + return true; + } + + if (usbirq & RWUDNIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, RWUDNIRQ); + dev_dbg(udc->dev, "Asked Host to wakeup\n"); + return true; + } + + if (usbirq & OSCOKIRQ) { + spi_wr8(udc, MAX3420_REG_USBIRQ, OSCOKIRQ); + dev_dbg(udc->dev, "Osc stabilized, start work\n"); + return true; + } + + if (epirq & OUT0DAVIRQ && max3420_do_data(udc, 0, 0)) { + spi_wr8_ack(udc, MAX3420_REG_EPIRQ, OUT0DAVIRQ, 1); + ret = true; + } + + if (epirq & IN0BAVIRQ && max3420_do_data(udc, 0, 1)) + ret = true; + + if (epirq & OUT1DAVIRQ && max3420_do_data(udc, 1, 0)) { + spi_wr8_ack(udc, MAX3420_REG_EPIRQ, OUT1DAVIRQ, 1); + ret = true; + } + + if (epirq & IN2BAVIRQ && max3420_do_data(udc, 2, 1)) + ret = true; + + if (epirq & IN3BAVIRQ && max3420_do_data(udc, 3, 1)) + ret = true; + + return ret; +} + +static int max3420_thread(void *dev_id) +{ + struct max3420_udc *udc = dev_id; + struct spi_device *spi = udc->spi; + int i, loop_again = 1; + unsigned long flags; + + while (!kthread_should_stop()) { + if (!loop_again) { + ktime_t kt = ns_to_ktime(1000 * 1000 * 250); /* 250ms */ + + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&udc->lock, flags); + if (udc->todo & ENABLE_IRQ) { + enable_irq(spi->irq); + udc->todo &= ~ENABLE_IRQ; + } + spin_unlock_irqrestore(&udc->lock, flags); + + schedule_hrtimeout(&kt, HRTIMER_MODE_REL); + } + loop_again = 0; + + mutex_lock(&udc->spi_bus_mutex); + + /* If bus-vbus_active and disconnected */ + if (!udc->vbus_active || !udc->softconnect) + goto loop; + + if (max3420_start(udc)) { + loop_again = 1; + goto loop; + } + + if (max3420_handle_irqs(udc)) { + loop_again = 1; + goto loop; + } + + if (spi_max3420_rwkup(udc)) { + loop_again = 1; + goto loop; + } + + max3420_do_data(udc, 0, 1); /* get done with the EP0 ZLP */ + + for (i = 1; i < MAX3420_MAX_EPS; i++) { + struct max3420_ep *ep = &udc->ep[i]; + + if (spi_max3420_enable(ep)) + loop_again = 1; + if (spi_max3420_stall(ep)) + loop_again = 1; + } +loop: + mutex_unlock(&udc->spi_bus_mutex); + } + + set_current_state(TASK_RUNNING); + dev_info(udc->dev, "SPI thread exiting\n"); + return 0; +} + +static int max3420_ep_set_halt(struct usb_ep *_ep, int stall) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_udc *udc = ep->udc; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + ep->todo &= ~STALL_EP; + if (stall) + ep->todo |= STALL; + else + ep->todo |= UNSTALL; + + spin_unlock_irqrestore(&ep->lock, flags); + + wake_up_process(udc->thread_task); + + dev_dbg(udc->dev, "%sStall %s\n", stall ? "" : "Un", ep->name); + return 0; +} + +static int __max3420_ep_enable(struct max3420_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + unsigned int maxp = usb_endpoint_maxp(desc); + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + ep->ep_usb.desc = desc; + ep->ep_usb.maxpacket = maxp; + + ep->todo &= ~ENABLE_EP; + ep->todo |= ENABLE; + spin_unlock_irqrestore(&ep->lock, flags); + + return 0; +} + +static int max3420_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_udc *udc = ep->udc; + + __max3420_ep_enable(ep, desc); + + wake_up_process(udc->thread_task); + + return 0; +} + +static void max3420_nuke(struct max3420_ep *ep, int status) +{ + struct max3420_req *req, *r; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + list_for_each_entry_safe(req, r, &ep->queue, queue) { + list_del_init(&req->queue); + + spin_unlock_irqrestore(&ep->lock, flags); + max3420_req_done(req, status); + spin_lock_irqsave(&ep->lock, flags); + } + + spin_unlock_irqrestore(&ep->lock, flags); +} + +static void __max3420_ep_disable(struct max3420_ep *ep) +{ + struct max3420_udc *udc = ep->udc; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + ep->ep_usb.desc = NULL; + + ep->todo &= ~ENABLE_EP; + ep->todo |= DISABLE; + + spin_unlock_irqrestore(&ep->lock, flags); + + dev_dbg(udc->dev, "Disabled %s\n", ep->name); +} + +static int max3420_ep_disable(struct usb_ep *_ep) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_udc *udc = ep->udc; + + max3420_nuke(ep, -ESHUTDOWN); + + __max3420_ep_disable(ep); + + wake_up_process(udc->thread_task); + + return 0; +} + +static struct usb_request *max3420_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_req *req; + + req = kzalloc_obj(*req, gfp_flags); + if (!req) + return NULL; + + req->ep = ep; + + return &req->usb_req; +} + +static void max3420_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + kfree(to_max3420_req(_req)); +} + +static int max3420_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t ignored) +{ + struct max3420_req *req = to_max3420_req(_req); + struct max3420_ep *ep = to_max3420_ep(_ep); + struct max3420_udc *udc = ep->udc; + unsigned long flags; + + _req->status = -EINPROGRESS; + _req->actual = 0; + + spin_lock_irqsave(&ep->lock, flags); + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&ep->lock, flags); + + wake_up_process(udc->thread_task); + return 0; +} + +static int max3420_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct max3420_req *t = NULL; + struct max3420_req *req = to_max3420_req(_req); + struct max3420_req *iter; + struct max3420_ep *ep = to_max3420_ep(_ep); + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + + /* Pluck the descriptor from queue */ + list_for_each_entry(iter, &ep->queue, queue) { + if (iter != req) + continue; + list_del_init(&req->queue); + t = iter; + break; + } + + spin_unlock_irqrestore(&ep->lock, flags); + + if (t) + max3420_req_done(req, -ECONNRESET); + + return 0; +} + +static const struct usb_ep_ops max3420_ep_ops = { + .enable = max3420_ep_enable, + .disable = max3420_ep_disable, + .alloc_request = max3420_alloc_request, + .free_request = max3420_free_request, + .queue = max3420_ep_queue, + .dequeue = max3420_ep_dequeue, + .set_halt = max3420_ep_set_halt, +}; + +static int max3420_wakeup(struct usb_gadget *gadget) +{ + struct max3420_udc *udc = to_udc(gadget); + unsigned long flags; + int ret = -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + + /* Only if wakeup allowed by host */ + if (udc->remote_wkp) { + udc->todo |= REMOTE_WAKEUP; + ret = 0; + } + + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task) + wake_up_process(udc->thread_task); + return ret; +} + +static int max3420_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct max3420_udc *udc = to_udc(gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + /* hook up the driver */ + udc->driver = driver; + udc->gadget.speed = USB_SPEED_FULL; + + udc->gadget.is_selfpowered = udc->is_selfpowered; + udc->remote_wkp = 0; + udc->softconnect = true; + udc->todo |= UDC_START; + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task) + wake_up_process(udc->thread_task); + + return 0; +} + +static int max3420_udc_stop(struct usb_gadget *gadget) +{ + struct max3420_udc *udc = to_udc(gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + udc->is_selfpowered = udc->gadget.is_selfpowered; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->driver = NULL; + udc->softconnect = false; + udc->todo |= UDC_START; + spin_unlock_irqrestore(&udc->lock, flags); + + if (udc->thread_task) + wake_up_process(udc->thread_task); + + return 0; +} + +static const struct usb_gadget_ops max3420_udc_ops = { + .udc_start = max3420_udc_start, + .udc_stop = max3420_udc_stop, + .wakeup = max3420_wakeup, +}; + +static void max3420_eps_init(struct max3420_udc *udc) +{ + int idx; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + for (idx = 0; idx < MAX3420_MAX_EPS; idx++) { + struct max3420_ep *ep = &udc->ep[idx]; + + spin_lock_init(&ep->lock); + INIT_LIST_HEAD(&ep->queue); + + ep->udc = udc; + ep->id = idx; + ep->halted = 0; + ep->maxpacket = 0; + ep->ep_usb.name = ep->name; + ep->ep_usb.ops = &max3420_ep_ops; + usb_ep_set_maxpacket_limit(&ep->ep_usb, MAX3420_EP_MAX_PACKET); + + if (idx == 0) { /* For EP0 */ + ep->ep_usb.desc = &ep0_desc; + ep->ep_usb.maxpacket = usb_endpoint_maxp(&ep0_desc); + ep->ep_usb.caps.type_control = true; + ep->ep_usb.caps.dir_in = true; + ep->ep_usb.caps.dir_out = true; + snprintf(ep->name, MAX3420_EPNAME_SIZE, "ep0"); + continue; + } + + if (idx == 1) { /* EP1 is OUT */ + ep->ep_usb.caps.dir_in = false; + ep->ep_usb.caps.dir_out = true; + snprintf(ep->name, MAX3420_EPNAME_SIZE, "ep1-bulk-out"); + } else { /* EP2 & EP3 are IN */ + ep->ep_usb.caps.dir_in = true; + ep->ep_usb.caps.dir_out = false; + snprintf(ep->name, MAX3420_EPNAME_SIZE, + "ep%d-bulk-in", idx); + } + ep->ep_usb.caps.type_iso = false; + ep->ep_usb.caps.type_int = false; + ep->ep_usb.caps.type_bulk = true; + + list_add_tail(&ep->ep_usb.ep_list, + &udc->gadget.ep_list); + } +} + +static int max3420_probe(struct spi_device *spi) +{ + struct max3420_udc *udc; + int err, irq; + u8 reg[8]; + + if (spi->controller->flags & SPI_CONTROLLER_HALF_DUPLEX) { + dev_err(&spi->dev, "UDC needs full duplex to work\n"); + return -EINVAL; + } + + spi->mode = SPI_MODE_3; + spi->bits_per_word = 8; + + err = spi_setup(spi); + if (err) { + dev_err(&spi->dev, "Unable to setup SPI bus\n"); + return -EFAULT; + } + + udc = devm_kzalloc(&spi->dev, sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + udc->spi = spi; + + udc->remote_wkp = 0; + + /* Setup gadget structure */ + udc->gadget.ops = &max3420_udc_ops; + udc->gadget.max_speed = USB_SPEED_FULL; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.ep0 = &udc->ep[0].ep_usb; + udc->gadget.name = driver_name; + + spin_lock_init(&udc->lock); + mutex_init(&udc->spi_bus_mutex); + + udc->ep0req.ep = &udc->ep[0]; + udc->ep0req.usb_req.buf = udc->ep0buf; + INIT_LIST_HEAD(&udc->ep0req.queue); + + /* setup Endpoints */ + max3420_eps_init(udc); + + /* configure SPI */ + spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 8); + spi_wr8(udc, MAX3420_REG_PINCTL, FDUPSPI); + + err = usb_add_gadget_udc(&spi->dev, &udc->gadget); + if (err) + return err; + + udc->dev = &udc->gadget.dev; + + spi_set_drvdata(spi, udc); + + irq = of_irq_get_byname(spi->dev.of_node, "udc"); + err = devm_request_irq(&spi->dev, irq, max3420_irq_handler, 0, + "max3420", udc); + if (err < 0) + goto del_gadget; + + udc->thread_task = kthread_create(max3420_thread, udc, + "max3420-thread"); + if (IS_ERR(udc->thread_task)) { + err = PTR_ERR(udc->thread_task); + goto del_gadget; + } + + irq = of_irq_get_byname(spi->dev.of_node, "vbus"); + if (irq <= 0) { /* no vbus irq implies self-powered design */ + udc->is_selfpowered = 1; + udc->vbus_active = true; + udc->todo |= UDC_START; + usb_udc_vbus_handler(&udc->gadget, udc->vbus_active); + usb_gadget_set_state(&udc->gadget, USB_STATE_POWERED); + max3420_start(udc); + } else { + udc->is_selfpowered = 0; + /* Detect current vbus status */ + spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 8); + if (reg[7] != 0xff) + udc->vbus_active = true; + + err = devm_request_irq(&spi->dev, irq, + max3420_vbus_handler, 0, "vbus", udc); + if (err < 0) + goto del_gadget; + } + + return 0; + +del_gadget: + usb_del_gadget_udc(&udc->gadget); + return err; +} + +static void max3420_remove(struct spi_device *spi) +{ + struct max3420_udc *udc = spi_get_drvdata(spi); + unsigned long flags; + + usb_del_gadget_udc(&udc->gadget); + + spin_lock_irqsave(&udc->lock, flags); + + kthread_stop(udc->thread_task); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static const struct of_device_id max3420_udc_of_match[] = { + { .compatible = "maxim,max3420-udc"}, + { .compatible = "maxim,max3421-udc"}, + {}, +}; +MODULE_DEVICE_TABLE(of, max3420_udc_of_match); + +static struct spi_driver max3420_driver = { + .driver = { + .name = "max3420-udc", + .of_match_table = max3420_udc_of_match, + }, + .probe = max3420_probe, + .remove = max3420_remove, +}; + +module_spi_driver(max3420_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Jassi Brar "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c new file mode 100644 index 000000000..7c5f30cfd --- /dev/null +++ b/drivers/usb/gadget/udc/net2280.c @@ -0,0 +1,3882 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * MSI is enabled by default. The legacy IRQ is used if MSI couldn't + * be 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. + * Copyright (C) 2014 Ricardo Ribalda - Qtechnology/AS + * + * Modified Seth Levy 2005 PLX Technology, Inc. to provide compatibility + * with 2282 chip + * + * Modified Ricardo Ribalda Qtechnology AS to provide compatibility + * with usb 338x chip. Based on PLX driver + */ + +#include +#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/USB338x USB Peripheral Controller" +#define DRIVER_VERSION "2005 Sept 27/v3.0" + +#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 u32 ep_bit[9] = { 0, 17, 2, 19, 4, 1, 18, 3, 20 }; +static const char ep0name[] = "ep0"; + +#define EP_INFO(_name, _caps) \ + { \ + .name = _name, \ + .caps = _caps, \ + } + +static const struct { + const char *name; + const struct usb_ep_caps caps; +} ep_info_dft[] = { /* Default endpoint configuration */ + EP_INFO(ep0name, + USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep-a", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep-b", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep-c", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep-d", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep-e", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep-f", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep-g", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep-h", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_ALL)), +}, ep_info_adv[] = { /* Endpoints for usb3380 advance mode */ + EP_INFO(ep0name, + USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, USB_EP_CAPS_DIR_ALL)), + EP_INFO("ep1in", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep2out", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep3in", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep4out", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep1out", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep2in", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep3out", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep4in", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ALL, USB_EP_CAPS_DIR_IN)), +}; + +#undef EP_INFO + +/* 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; + +/* "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; + +/* "modprobe net2280 enable_suspend=1" etc */ +module_param(enable_suspend, bool, 0444); + +#define DIR_STRING(bAddress) (((bAddress) & USB_DIR_IN) ? "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"; + } + return "control"; +} + +#include "net2280.h" + +#define valid_bit cpu_to_le32(BIT(VALID_BIT)) +#define dma_done_ie cpu_to_le32(BIT(DMA_DONE_INTERRUPT_ENABLE)) + +static void ep_clear_seqnum(struct net2280_ep *ep); +static void stop_activity(struct net2280 *dev, + struct usb_gadget_driver *driver); +static void ep0_start(struct net2280 *dev); + +/*-------------------------------------------------------------------------*/ +static inline void enable_pciirqenb(struct net2280_ep *ep) +{ + u32 tmp = readl(&ep->dev->regs->pciirqenb0); + + if (ep->dev->quirks & PLX_LEGACY) + tmp |= BIT(ep->num); + else + tmp |= BIT(ep_bit[ep->num]); + writel(tmp, &ep->dev->regs->pciirqenb0); + + return; +} + +static int +net2280_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) +{ + struct net2280 *dev; + struct net2280_ep *ep; + u32 max; + u32 tmp = 0; + u32 type; + unsigned long flags; + static const u32 ep_key[9] = { 1, 0, 1, 0, 1, 1, 0, 1, 0 }; + int ret = 0; + + ep = container_of(_ep, struct net2280_ep, ep); + if (!_ep || !desc || ep->desc || _ep->name == ep0name || + desc->bDescriptorType != USB_DT_ENDPOINT) { + pr_err("%s: failed at line=%d\n", __func__, __LINE__); + return -EINVAL; + } + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { + ret = -ESHUTDOWN; + goto print_err; + } + + /* erratum 0119 workaround ties up an endpoint number */ + if (usb_endpoint_num(desc) == EP_DONTUSE) { + ret = -EDOM; + goto print_err; + } + + if (dev->quirks & PLX_PCIE) { + if (usb_endpoint_num(desc) >= 0x0c) { + ret = -EDOM; + goto print_err; + } + ep->is_in = !!usb_endpoint_dir_in(desc); + if (dev->enhanced_mode && ep->is_in && ep_key[ep->num]) { + ret = -EINVAL; + goto print_err; + } + } + + /* sanity check ep-e/ep-f since their fifos are small */ + max = usb_endpoint_maxp(desc); + if (ep->num > 4 && max > 64 && (dev->quirks & PLX_LEGACY)) { + ret = -ERANGE; + goto print_err; + } + + spin_lock_irqsave(&dev->lock, flags); + _ep->maxpacket = max; + 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_max_speed(ep, max); + + /* set type, direction, address; reset fifo counters */ + writel(BIT(FIFO_FLUSH), &ep->regs->ep_stat); + + if ((dev->quirks & PLX_PCIE) && dev->enhanced_mode) { + tmp = readl(&ep->cfg->ep_cfg); + /* If USB ep number doesn't match hardware ep number */ + if ((tmp & 0xf) != usb_endpoint_num(desc)) { + ret = -EINVAL; + spin_unlock_irqrestore(&dev->lock, flags); + goto print_err; + } + if (ep->is_in) + tmp &= ~USB3380_EP_CFG_MASK_IN; + else + tmp &= ~USB3380_EP_CFG_MASK_OUT; + } + type = usb_endpoint_type(desc); + if (type == 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(BIT(CLEAR_NAK_OUT_PACKETS_MODE), + &ep->regs->ep_rsp); + } else if (type == USB_ENDPOINT_XFER_BULK) { + /* catch some particularly blatant driver bugs */ + if ((dev->gadget.speed == USB_SPEED_SUPER && max != 1024) || + (dev->gadget.speed == USB_SPEED_HIGH && max != 512) || + (dev->gadget.speed == USB_SPEED_FULL && max > 64)) { + spin_unlock_irqrestore(&dev->lock, flags); + ret = -ERANGE; + goto print_err; + } + } + ep->is_iso = (type == USB_ENDPOINT_XFER_ISOC); + /* Enable this endpoint */ + if (dev->quirks & PLX_LEGACY) { + tmp |= type << ENDPOINT_TYPE; + tmp |= desc->bEndpointAddress; + /* default full fifo lines */ + tmp |= (4 << ENDPOINT_BYTE_COUNT); + tmp |= BIT(ENDPOINT_ENABLE); + ep->is_in = (tmp & USB_DIR_IN) != 0; + } else { + /* In Legacy mode, only OUT endpoints are used */ + if (dev->enhanced_mode && ep->is_in) { + tmp |= type << IN_ENDPOINT_TYPE; + tmp |= BIT(IN_ENDPOINT_ENABLE); + } else { + tmp |= type << OUT_ENDPOINT_TYPE; + tmp |= BIT(OUT_ENDPOINT_ENABLE); + tmp |= (ep->is_in << ENDPOINT_DIRECTION); + } + + tmp |= (4 << ENDPOINT_BYTE_COUNT); + if (!dev->enhanced_mode) + tmp |= usb_endpoint_num(desc); + tmp |= (ep->ep.maxburst << MAX_BURST_SIZE); + } + + /* Make sure all the registers are written before ep_rsp*/ + wmb(); + + /* for OUT transfers, block the rx fifo until a read is posted */ + if (!ep->is_in) + writel(BIT(SET_NAK_OUT_PACKETS), &ep->regs->ep_rsp); + else if (!(dev->quirks & PLX_2280)) { + /* Added for 2282, Don't use nak packets on an in endpoint, + * this was ignored on 2280 + */ + writel(BIT(CLEAR_NAK_OUT_PACKETS) | + BIT(CLEAR_NAK_OUT_PACKETS_MODE), &ep->regs->ep_rsp); + } + + if (dev->quirks & PLX_PCIE) + ep_clear_seqnum(ep); + writel(tmp, &ep->cfg->ep_cfg); + + /* enable irqs */ + if (!ep->dma) { /* pio, per-packet */ + enable_pciirqenb(ep); + + tmp = BIT(DATA_PACKET_RECEIVED_INTERRUPT_ENABLE) | + BIT(DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE); + if (dev->quirks & PLX_2280) + tmp |= readl(&ep->regs->ep_irqenb); + writel(tmp, &ep->regs->ep_irqenb); + } else { /* dma, per-request */ + tmp = BIT((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 = BIT(SHORT_PACKET_TRANSFERRED_INTERRUPT_ENABLE); + writel(tmp, &ep->regs->ep_irqenb); + + enable_pciirqenb(ep); + } + } + + tmp = desc->bEndpointAddress; + ep_dbg(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 ret; + +print_err: + dev_err(&ep->dev->pdev->dev, "%s: error=%d\n", __func__, ret); + return ret; +} + +static int handshake(u32 __iomem *ptr, u32 mask, u32 done, int usec) +{ + u32 result; + int ret; + + ret = readl_poll_timeout_atomic(ptr, result, + ((result & mask) == done || + result == U32_MAX), + 1, usec); + if (result == U32_MAX) /* device unplugged */ + return -ENODEV; + + return ret; +} + +static const struct usb_ep_ops net2280_ep_ops; + +static void ep_reset_228x(struct net2280_regs __iomem *regs, + struct net2280_ep *ep) +{ + u32 tmp; + + ep->desc = NULL; + INIT_LIST_HEAD(&ep->queue); + + usb_ep_set_maxpacket_limit(&ep->ep, ~0); + ep->ep.ops = &net2280_ep_ops; + + /* disable the dma, irqs, endpoint... */ + if (ep->dma) { + writel(0, &ep->dma->dmactl); + writel(BIT(DMA_SCATTER_GATHER_DONE_INTERRUPT) | + BIT(DMA_TRANSACTION_DONE_INTERRUPT) | + BIT(DMA_ABORT), + &ep->dma->dmastat); + + tmp = readl(®s->pciirqenb0); + tmp &= ~BIT(ep->num); + writel(tmp, ®s->pciirqenb0); + } else { + tmp = readl(®s->pciirqenb1); + tmp &= ~BIT((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->quirks & PLX_2280)) { + tmp = BIT(SET_NAK_OUT_PACKETS_MODE) | + BIT(SET_NAK_OUT_PACKETS) | + BIT(CLEAR_EP_HIDE_STATUS_PHASE) | + BIT(CLEAR_INTERRUPT_MODE); + } else { + /* added for 2282 */ + tmp = BIT(CLEAR_NAK_OUT_PACKETS_MODE) | + BIT(CLEAR_NAK_OUT_PACKETS) | + BIT(CLEAR_EP_HIDE_STATUS_PHASE) | + BIT(CLEAR_INTERRUPT_MODE); + } + + if (ep->num != 0) { + tmp |= BIT(CLEAR_ENDPOINT_TOGGLE) | + BIT(CLEAR_ENDPOINT_HALT); + } + writel(tmp, &ep->regs->ep_rsp); + + /* scrub most status bits, and flush any fifo state */ + if (ep->dev->quirks & PLX_2280) + tmp = BIT(FIFO_OVERFLOW) | + BIT(FIFO_UNDERFLOW); + else + tmp = 0; + + writel(tmp | BIT(TIMEOUT) | + BIT(USB_STALL_SENT) | + BIT(USB_IN_NAK_SENT) | + BIT(USB_IN_ACK_RCVD) | + BIT(USB_OUT_PING_NAK_SENT) | + BIT(USB_OUT_ACK_SENT) | + BIT(FIFO_FLUSH) | + BIT(SHORT_PACKET_OUT_DONE_INTERRUPT) | + BIT(SHORT_PACKET_TRANSFERRED_INTERRUPT) | + BIT(DATA_PACKET_RECEIVED_INTERRUPT) | + BIT(DATA_PACKET_TRANSMITTED_INTERRUPT) | + BIT(DATA_OUT_PING_TOKEN_INTERRUPT) | + BIT(DATA_IN_TOKEN_INTERRUPT), + &ep->regs->ep_stat); + + /* fifo size is handled separately */ +} + +static void ep_reset_338x(struct net2280_regs __iomem *regs, + struct net2280_ep *ep) +{ + u32 tmp, dmastat; + + ep->desc = NULL; + INIT_LIST_HEAD(&ep->queue); + + usb_ep_set_maxpacket_limit(&ep->ep, ~0); + ep->ep.ops = &net2280_ep_ops; + + /* disable the dma, irqs, endpoint... */ + if (ep->dma) { + writel(0, &ep->dma->dmactl); + writel(BIT(DMA_ABORT_DONE_INTERRUPT) | + BIT(DMA_PAUSE_DONE_INTERRUPT) | + BIT(DMA_SCATTER_GATHER_DONE_INTERRUPT) | + BIT(DMA_TRANSACTION_DONE_INTERRUPT), + /* | BIT(DMA_ABORT), */ + &ep->dma->dmastat); + + dmastat = readl(&ep->dma->dmastat); + if (dmastat == 0x5002) { + ep_warn(ep->dev, "The dmastat return = %x!!\n", + dmastat); + writel(0x5a, &ep->dma->dmastat); + } + + tmp = readl(®s->pciirqenb0); + tmp &= ~BIT(ep_bit[ep->num]); + writel(tmp, ®s->pciirqenb0); + } else { + if (ep->num < 5) { + tmp = readl(®s->pciirqenb1); + tmp &= ~BIT((8 + ep->num)); /* completion */ + writel(tmp, ®s->pciirqenb1); + } + } + writel(0, &ep->regs->ep_irqenb); + + writel(BIT(SHORT_PACKET_OUT_DONE_INTERRUPT) | + BIT(SHORT_PACKET_TRANSFERRED_INTERRUPT) | + BIT(FIFO_OVERFLOW) | + BIT(DATA_PACKET_RECEIVED_INTERRUPT) | + BIT(DATA_PACKET_TRANSMITTED_INTERRUPT) | + BIT(DATA_OUT_PING_TOKEN_INTERRUPT) | + BIT(DATA_IN_TOKEN_INTERRUPT), &ep->regs->ep_stat); + + tmp = readl(&ep->cfg->ep_cfg); + if (ep->is_in) + tmp &= ~USB3380_EP_CFG_MASK_IN; + else + tmp &= ~USB3380_EP_CFG_MASK_OUT; + writel(tmp, &ep->cfg->ep_cfg); +} + +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->name == ep0name) { + pr_err("%s: Invalid ep=%p\n", __func__, _ep); + return -EINVAL; + } + spin_lock_irqsave(&ep->dev->lock, flags); + nuke(ep); + + if (ep->dev->quirks & PLX_PCIE) + ep_reset_338x(ep->dev->regs, ep); + else + ep_reset_228x(ep->dev->regs, ep); + + ep_vdbg(ep->dev, "disabled %s %s\n", + ep->dma ? "dma" : "pio", _ep->name); + + /* synch memory views with the device */ + (void)readl(&ep->cfg->ep_cfg); + + if (!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) { + pr_err("%s: Invalid ep\n", __func__); + return NULL; + } + ep = container_of(_ep, struct net2280_ep, ep); + + req = kzalloc_obj(*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 = dma_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) { + dev_err(&ep->dev->pdev->dev, "%s: Invalid ep=%p or req=%p\n", + __func__, _ep, _req); + return; + } + + req = container_of(_req, struct net2280_request, req); + WARN_ON(!list_empty(&req->queue)); + if (req->td) + dma_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; + + ep_vdbg(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; + + statp = &ep->regs->ep_stat; + + tmp = readl(statp); + if (tmp & BIT(NAK_OUT_PACKETS)) { + ep_dbg(ep->dev, "%s %s %08x !NAK\n", + ep->ep.name, __func__, tmp); + writel(BIT(SET_NAK_OUT_PACKETS), &ep->regs->ep_rsp); + } + + writel(BIT(DATA_OUT_PING_TOKEN_INTERRUPT) | + BIT(DATA_PACKET_RECEIVED_INTERRUPT), + statp); + writel(BIT(FIFO_FLUSH), statp); + /* Make sure that stap is written */ + mb(); + tmp = readl(statp); + if (tmp & BIT(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, BIT(USB_OUT_PING_NAK_SENT), + BIT(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 & BIT(NAK_OUT_PACKETS))) + cleanup = 1; + else if ((tmp & BIT(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 & BIT(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) { + ep_err(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); + + ep_vdbg(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(BIT(CLEAR_NAK_OUT_PACKETS), &ep->regs->ep_rsp); + (void) readl(&ep->regs->ep_rsp); + } + + return is_short || req->req.actual == req->req.length; +} + +/* 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 |= BIT(DMA_DIRECTION); + if ((!ep->is_in && (dmacount % ep->ep.maxpacket) != 0) || + !(ep->dev->quirks & PLX_2280)) + dmacount |= BIT(END_OF_CHAIN); + + req->valid = valid; + if (valid) + dmacount |= BIT(VALID_BIT); + dmacount |= BIT(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 = + BIT(DMA_SCATTER_GATHER_DONE_INTERRUPT) | + BIT(DMA_CLEAR_COUNT_ENABLE) | + /* erratum 0116 workaround part 1 (use POLLING) */ + (POLL_100_USEC << DESCRIPTOR_POLLING_RATE) | + BIT(DMA_VALID_BIT_POLLING_ENABLE) | + BIT(DMA_VALID_BIT_ENABLE) | + BIT(DMA_SCATTER_GATHER_ENABLE) | + /* erratum 0116 workaround part 2 (no AUTOSTART) */ + BIT(DMA_ENABLE); + +static inline void spin_stop_dma(struct net2280_dma_regs __iomem *dma) +{ + handshake(&dma->dmactl, BIT(DMA_ENABLE), 0, 50); +} + +static inline void stop_dma(struct net2280_dma_regs __iomem *dma) +{ + writel(readl(&dma->dmactl) & ~BIT(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 = BIT(VALID_BIT) | (ep->is_in << DMA_DIRECTION); + + if (!(ep->dev->quirks & PLX_2280)) + tmp |= BIT(END_OF_CHAIN); + + writel(tmp, &dma->dmacount); + writel(readl(&dma->dmastat), &dma->dmastat); + + writel(td_dma, &dma->dmadesc); + if (ep->dev->quirks & PLX_PCIE) + dmactl |= BIT(DMA_REQUEST_OUTSTANDING); + writel(dmactl, &dma->dmactl); + + /* erratum 0116 workaround part 3: pci arbiter away from net2280 */ + (void) readl(&ep->dev->pci->pcimstctl); + + writel(BIT(DMA_START), &dma->dmastat); +} + +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) & BIT(DMA_ENABLE)); + writel(0, &ep->dma->dmactl); + + /* previous OUT packet might have been short */ + if (!ep->is_in && (readl(&ep->regs->ep_stat) & + BIT(NAK_OUT_PACKETS))) { + writel(BIT(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(BIT(DMA_DONE_INTERRUPT_ENABLE) | tmp, + &dma->dmacount); + req->td->dmadesc = 0; + req->valid = 1; + + writel(BIT(DMA_ENABLE), &dma->dmactl); + writel(BIT(DMA_START), &dma->dmastat); + return; + } + stop_out_naking(ep); + } + + 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) || + req->req.zero)){ + tmp |= BIT(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); + + req->td->dmacount |= cpu_to_le32(BIT(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) +{ + /* swap new dummy for old, link; fill and maybe activate */ + swap(ep->dummy, req->td); + swap(ep->td_dma, req->td_dma); + + req->td->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) + ep_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); + usb_gadget_giveback_request(&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; + int ret = 0; + + /* we always require a cpu-view buffer, so that we can + * always use pio (as fallback or whatever). + */ + ep = container_of(_ep, struct net2280_ep, ep); + if (!_ep || (!ep->desc && ep->num != 0)) { + pr_err("%s: Invalid ep=%p or ep->desc\n", __func__, _ep); + return -EINVAL; + } + req = container_of(_req, struct net2280_request, req); + if (!_req || !_req->complete || !_req->buf || + !list_empty(&req->queue)) { + ret = -EINVAL; + goto print_err; + } + if (_req->length > (~0 & DMA_BYTE_COUNT_MASK)) { + ret = -EDOM; + goto print_err; + } + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { + ret = -ESHUTDOWN; + goto print_err; + } + + /* FIXME implement PIO fallback for ZLPs with DMA */ + if (ep->dma && _req->length == 0) { + ret = -EOPNOTSUPP; + goto print_err; + } + + /* set up dma mapping in case the caller didn't */ + if (ep->dma) { + ret = usb_gadget_map_request(&dev->gadget, _req, + ep->is_in); + if (ret) + goto print_err; + } + + ep_vdbg(dev, "%s queue req %p, len %d buf %p\n", + _ep->name, _req, _req->length, _req->buf); + + spin_lock_irqsave(&dev->lock, flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* kickstart this i/o queue? */ + if (list_empty(&ep->queue) && !ep->stopped && + !((dev->quirks & PLX_PCIE) && ep->dma && + (readl(&ep->regs->ep_rsp) & BIT(CLEAR_ENDPOINT_HALT)))) { + + /* 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); + ep_vdbg(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 { + u32 s; + + /* OUT FIFO might have packet(s) buffered */ + s = readl(&ep->regs->ep_stat); + if ((s & BIT(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) && + ep->num == 0) { + done(ep, req, 0); + allow_status(ep); + /* don't queue it */ + req = NULL; + } else if (read_fifo(ep, req) && + ep->num != 0) { + done(ep, req, 0); + req = NULL; + } else + s = readl(&ep->regs->ep_stat); + } + + /* don't NAK, let the fifo fill */ + if (req && (s & BIT(NAK_OUT_PACKETS))) + writel(BIT(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)); + 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 ret; + +print_err: + dev_err(&ep->dev->pdev->dev, "%s: error=%d\n", __func__, ret); + return ret; +} + +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 int scan_dma_completions(struct net2280_ep *ep) +{ + int num_completed = 0; + + /* 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 req_dma_count; + + req = list_entry(ep->queue.next, + struct net2280_request, queue); + if (!req->valid) + break; + rmb(); + req_dma_count = le32_to_cpup(&req->td->dmacount); + if ((req_dma_count & BIT(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 */ + u32 const ep_dmacount = readl(&ep->dma->dmacount); + + if (ep_dmacount & DMA_BYTE_COUNT_MASK) + break; + /* single transfer mode */ + dma_done(ep, req, req_dma_count, 0); + num_completed++; + break; + } else if (!ep->is_in && + (req->req.length % ep->ep.maxpacket) && + !(ep->dev->quirks & PLX_PCIE)) { + + u32 const ep_stat = 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 ((ep_stat & BIT(NAK_OUT_PACKETS)) == 0) { + ep_warn(ep->dev, "%s lost packet sync!\n", + ep->ep.name); + req->req.status = -EOVERFLOW; + } else { + u32 const ep_avail = readl(&ep->regs->ep_avail); + if (ep_avail) { + /* fifo gets flushed later */ + ep->out_overflow = 1; + ep_dbg(ep->dev, + "%s dma, discard %d len %d\n", + ep->ep.name, ep_avail, + req->req.length); + req->req.status = -EOVERFLOW; + } + } + } + dma_done(ep, req, req_dma_count, 0); + num_completed++; + } + + return num_completed; +} + +static void restart_dma(struct net2280_ep *ep) +{ + struct net2280_request *req; + + if (ep->stopped) + return; + req = list_entry(ep->queue.next, struct net2280_request, queue); + + start_dma(ep, req); +} + +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(BIT(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 = NULL; + struct net2280_request *iter; + unsigned long flags; + u32 dmactl; + int stopped; + + ep = container_of(_ep, struct net2280_ep, ep); + if (!_ep || (!ep->desc && ep->num != 0) || !_req) { + pr_err("%s: Invalid ep=%p or ep->desc or req=%p\n", + __func__, _ep, _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(iter, &ep->queue, queue) { + if (&iter->req != _req) + continue; + req = iter; + break; + } + if (!req) { + ep->stopped = stopped; + spin_unlock_irqrestore(&ep->dev->lock, flags); + ep_dbg(ep->dev, "%s: Request mismatch\n", __func__); + return -EINVAL; + } + + /* queue head may be partially complete. */ + if (ep->queue.next == &req->queue) { + if (ep->dma) { + ep_dbg(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 { + ep_dbg(ep->dev, "unlink (%s) pio\n", _ep->name); + done(ep, req, -ECONNRESET); + } + req = NULL; + } + + 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)) { + pr_err("%s: Invalid ep=%p or ep->desc\n", __func__, _ep); + return -EINVAL; + } + if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) { + retval = -ESHUTDOWN; + goto print_err; + } + if (ep->desc /* not ep0 */ && usb_endpoint_type(ep->desc) + == USB_ENDPOINT_XFER_ISOC) { + retval = -EINVAL; + goto print_err; + } + + spin_lock_irqsave(&ep->dev->lock, flags); + if (!list_empty(&ep->queue)) { + retval = -EAGAIN; + goto print_unlock; + } else if (ep->is_in && value && net2280_fifo_status(_ep) != 0) { + retval = -EAGAIN; + goto print_unlock; + } else { + ep_vdbg(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); + if (ep->dev->quirks & PLX_PCIE && + !list_empty(&ep->queue) && ep->td_dma) + restart_dma(ep); + ep->wedged = 0; + } + (void) readl(&ep->regs->ep_rsp); + } + spin_unlock_irqrestore(&ep->dev->lock, flags); + + return retval; + +print_unlock: + spin_unlock_irqrestore(&ep->dev->lock, flags); +print_err: + dev_err(&ep->dev->pdev->dev, "%s: error=%d\n", __func__, retval); + 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) { + pr_err("%s: Invalid ep=%p or ep0\n", __func__, _ep); + 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)) { + pr_err("%s: Invalid ep=%p or ep->desc\n", __func__, _ep); + return -ENODEV; + } + if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) { + dev_err(&ep->dev->pdev->dev, + "%s: Invalid driver=%p or speed=%d\n", + __func__, ep->dev->driver, ep->dev->gadget.speed); + return -ESHUTDOWN; + } + + avail = readl(&ep->regs->ep_avail) & (BIT(12) - 1); + if (avail > ep->fifo_size) { + dev_err(&ep->dev->pdev->dev, "%s: Fifo overflow\n", __func__); + 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)) { + pr_err("%s: Invalid ep=%p or ep->desc\n", __func__, _ep); + return; + } + if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) { + dev_err(&ep->dev->pdev->dev, + "%s: Invalid driver=%p or speed=%d\n", + __func__, ep->dev->driver, ep->dev->gadget.speed); + return; + } + + writel(BIT(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 & BIT(DEVICE_REMOTE_WAKEUP_ENABLE)) + writel(BIT(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 |= BIT(SELF_POWERED_STATUS); + _gadget->is_selfpowered = 1; + } else { + tmp &= ~BIT(SELF_POWERED_STATUS); + _gadget->is_selfpowered = 0; + } + 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) { + ep0_start(dev); + writel(tmp | BIT(USB_DETECT_ENABLE), &dev->usb->usbctl); + } else { + writel(tmp & ~BIT(USB_DETECT_ENABLE), &dev->usb->usbctl); + stop_activity(dev, NULL); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +static struct usb_ep *net2280_match_ep(struct usb_gadget *_gadget, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *ep_comp) +{ + char name[8]; + struct usb_ep *ep; + + if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_INT) { + /* ep-e, ep-f are PIO with only 64 byte fifos */ + ep = gadget_find_ep_by_name(_gadget, "ep-e"); + if (ep && usb_gadget_ep_match_desc(_gadget, ep, desc, ep_comp)) + return ep; + ep = gadget_find_ep_by_name(_gadget, "ep-f"); + if (ep && usb_gadget_ep_match_desc(_gadget, ep, desc, ep_comp)) + return ep; + } + + /* USB3380: Only first four endpoints have DMA channels. Allocate + * slower interrupt endpoints from PIO hw endpoints, to allow bulk/isoc + * endpoints use DMA hw endpoints. + */ + if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_INT && + usb_endpoint_dir_in(desc)) { + ep = gadget_find_ep_by_name(_gadget, "ep2in"); + if (ep && usb_gadget_ep_match_desc(_gadget, ep, desc, ep_comp)) + return ep; + ep = gadget_find_ep_by_name(_gadget, "ep4in"); + if (ep && usb_gadget_ep_match_desc(_gadget, ep, desc, ep_comp)) + return ep; + } else if (usb_endpoint_type(desc) == USB_ENDPOINT_XFER_INT && + !usb_endpoint_dir_in(desc)) { + ep = gadget_find_ep_by_name(_gadget, "ep1out"); + if (ep && usb_gadget_ep_match_desc(_gadget, ep, desc, ep_comp)) + return ep; + ep = gadget_find_ep_by_name(_gadget, "ep3out"); + if (ep && usb_gadget_ep_match_desc(_gadget, ep, desc, ep_comp)) + return ep; + } else if (usb_endpoint_type(desc) != USB_ENDPOINT_XFER_BULK && + usb_endpoint_dir_in(desc)) { + ep = gadget_find_ep_by_name(_gadget, "ep1in"); + if (ep && usb_gadget_ep_match_desc(_gadget, ep, desc, ep_comp)) + return ep; + ep = gadget_find_ep_by_name(_gadget, "ep3in"); + if (ep && usb_gadget_ep_match_desc(_gadget, ep, desc, ep_comp)) + return ep; + } else if (usb_endpoint_type(desc) != USB_ENDPOINT_XFER_BULK && + !usb_endpoint_dir_in(desc)) { + ep = gadget_find_ep_by_name(_gadget, "ep2out"); + if (ep && usb_gadget_ep_match_desc(_gadget, ep, desc, ep_comp)) + return ep; + ep = gadget_find_ep_by_name(_gadget, "ep4out"); + if (ep && usb_gadget_ep_match_desc(_gadget, ep, desc, ep_comp)) + return ep; + } + + /* USB3380: use same address for usb and hardware endpoints */ + snprintf(name, sizeof(name), "ep%d%s", usb_endpoint_num(desc), + usb_endpoint_dir_in(desc) ? "in" : "out"); + ep = gadget_find_ep_by_name(_gadget, name); + if (ep && usb_gadget_ep_match_desc(_gadget, ep, desc, ep_comp)) + return ep; + + return NULL; +} + +static int net2280_start(struct usb_gadget *_gadget, + struct usb_gadget_driver *driver); +static int net2280_stop(struct usb_gadget *_gadget); +static void net2280_async_callbacks(struct usb_gadget *_gadget, bool enable); + +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, + .udc_async_callbacks = net2280_async_callbacks, + .match_ep = net2280_match_ep, +}; + +/*-------------------------------------------------------------------------*/ + +#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 function_show(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_RO(function); + +static ssize_t registers_show(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\n\n" + "devinit %03x fifoctl %08x gadget '%s'\n" + "pci irqenb0 %02x irqenb1 %08x " + "irqstat0 %04x irqstat1 %08x\n", + driver_name, dev->chiprev, + 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 & BIT(VBUS_PIN)) { + if (t2 & BIT(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 < dev->n_ep; i++) { + struct net2280_ep *ep; + + ep = &dev->ep[i]; + if (i && !ep->desc) + continue; + + t1 = readl(&ep->cfg->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 & BIT(CLEAR_NAK_OUT_PACKETS)) + ? "NAK " : "", + (t2 & BIT(CLEAR_EP_HIDE_STATUS_PHASE)) + ? "hide " : "", + (t2 & BIT(CLEAR_EP_FORCE_CRC_ERROR)) + ? "CRC " : "", + (t2 & BIT(CLEAR_INTERRUPT_MODE)) + ? "interrupt " : "", + (t2 & BIT(CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE)) + ? "status " : "", + (t2 & BIT(CLEAR_NAK_OUT_PACKETS_MODE)) + ? "NAKmode " : "", + (t2 & BIT(CLEAR_ENDPOINT_TOGGLE)) + ? "DATA1 " : "DATA0 ", + (t2 & BIT(CLEAR_ENDPOINT_HALT)) + ? "HALT " : "", + readl(&ep->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 < dev->n_ep; 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_RO(registers); + +static ssize_t queues_show(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 < dev->n_ep; 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", + type_string(d->bmAttributes), + usb_endpoint_maxp(d), + 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_RO(queues); + + +#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); +} + +static void defect7374_disable_data_eps(struct net2280 *dev) +{ + /* + * For Defect 7374, disable data EPs (and more): + * - This phase undoes the earlier phase of the Defect 7374 workaround, + * returing ep regs back to normal. + */ + struct net2280_ep *ep; + int i; + unsigned char ep_sel; + u32 tmp_reg; + + for (i = 1; i < 5; i++) { + ep = &dev->ep[i]; + writel(i, &ep->cfg->ep_cfg); + } + + /* CSROUT, CSRIN, PCIOUT, PCIIN, STATIN, RCIN */ + for (i = 0; i < 6; i++) + writel(0, &dev->dep[i].dep_cfg); + + for (ep_sel = 0; ep_sel <= 21; ep_sel++) { + /* Select an endpoint for subsequent operations: */ + tmp_reg = readl(&dev->plregs->pl_ep_ctrl); + writel(((tmp_reg & ~0x1f) | ep_sel), &dev->plregs->pl_ep_ctrl); + + if (ep_sel < 2 || (ep_sel > 9 && ep_sel < 14) || + ep_sel == 18 || ep_sel == 20) + continue; + + /* Change settings on some selected endpoints */ + tmp_reg = readl(&dev->plregs->pl_ep_cfg_4); + tmp_reg &= ~BIT(NON_CTRL_IN_TOLERATE_BAD_DIR); + writel(tmp_reg, &dev->plregs->pl_ep_cfg_4); + tmp_reg = readl(&dev->plregs->pl_ep_ctrl); + tmp_reg |= BIT(EP_INITIALIZED); + writel(tmp_reg, &dev->plregs->pl_ep_ctrl); + } +} + +static void defect7374_enable_data_eps_zero(struct net2280 *dev) +{ + u32 tmp = 0, tmp_reg; + u32 scratch; + int i; + unsigned char ep_sel; + + scratch = get_idx_reg(dev->regs, SCRATCH); + + WARN_ON((scratch & (0xf << DEFECT7374_FSM_FIELD)) + == DEFECT7374_FSM_SS_CONTROL_READ); + + scratch &= ~(0xf << DEFECT7374_FSM_FIELD); + + ep_warn(dev, "Operate Defect 7374 workaround soft this time"); + ep_warn(dev, "It will operate on cold-reboot and SS connect"); + + /*GPEPs:*/ + tmp = ((0 << ENDPOINT_NUMBER) | BIT(ENDPOINT_DIRECTION) | + (2 << OUT_ENDPOINT_TYPE) | (2 << IN_ENDPOINT_TYPE) | + ((dev->enhanced_mode) ? + BIT(OUT_ENDPOINT_ENABLE) | BIT(IN_ENDPOINT_ENABLE) : + BIT(ENDPOINT_ENABLE))); + + for (i = 1; i < 5; i++) + writel(tmp, &dev->ep[i].cfg->ep_cfg); + + /* CSRIN, PCIIN, STATIN, RCIN*/ + tmp = ((0 << ENDPOINT_NUMBER) | BIT(ENDPOINT_ENABLE)); + writel(tmp, &dev->dep[1].dep_cfg); + writel(tmp, &dev->dep[3].dep_cfg); + writel(tmp, &dev->dep[4].dep_cfg); + writel(tmp, &dev->dep[5].dep_cfg); + + /*Implemented for development and debug. + * Can be refined/tuned later.*/ + for (ep_sel = 0; ep_sel <= 21; ep_sel++) { + /* Select an endpoint for subsequent operations: */ + tmp_reg = readl(&dev->plregs->pl_ep_ctrl); + writel(((tmp_reg & ~0x1f) | ep_sel), + &dev->plregs->pl_ep_ctrl); + + if (ep_sel == 1) { + tmp = + (readl(&dev->plregs->pl_ep_ctrl) | + BIT(CLEAR_ACK_ERROR_CODE) | 0); + writel(tmp, &dev->plregs->pl_ep_ctrl); + continue; + } + + if (ep_sel == 0 || (ep_sel > 9 && ep_sel < 14) || + ep_sel == 18 || ep_sel == 20) + continue; + + tmp = (readl(&dev->plregs->pl_ep_cfg_4) | + BIT(NON_CTRL_IN_TOLERATE_BAD_DIR) | 0); + writel(tmp, &dev->plregs->pl_ep_cfg_4); + + tmp = readl(&dev->plregs->pl_ep_ctrl) & + ~BIT(EP_INITIALIZED); + writel(tmp, &dev->plregs->pl_ep_ctrl); + + } + + /* Set FSM to focus on the first Control Read: + * - Tip: Connection speed is known upon the first + * setup request.*/ + scratch |= DEFECT7374_FSM_WAITING_FOR_CONTROL_READ; + set_idx_reg(dev->regs, SCRATCH, scratch); + +} + +/* 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_228x(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(~(u32)BIT(SUSPEND_REQUEST_INTERRUPT), &dev->regs->irqstat1), + + /* reset, and enable pci */ + tmp = readl(&dev->regs->devinit) | + BIT(PCI_ENABLE) | + BIT(FIFO_SOFT_RESET) | + BIT(USB_SOFT_RESET) | + BIT(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_reset_338x(struct net2280 *dev) +{ + u32 tmp; + + dev->gadget.speed = USB_SPEED_UNKNOWN; + (void)readl(&dev->usb->usbctl); + + net2280_led_init(dev); + + if (dev->bug7734_patched) { + /* 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]; + struct net2280_dma_regs __iomem *dma; + + if (ep->dma) { + abort_dma(ep); + } else { + dma = &dev->dma[tmp]; + writel(BIT(DMA_ABORT), &dma->dmastat); + writel(0, &dma->dmactl); + } + } + + writel(~0, &dev->regs->irqstat0), writel(~0, &dev->regs->irqstat1); + + if (dev->bug7734_patched) { + /* reset, and enable pci */ + tmp = readl(&dev->regs->devinit) | + BIT(PCI_ENABLE) | + BIT(FIFO_SOFT_RESET) | + BIT(USB_SOFT_RESET) | + BIT(M8051_RESET); + + writel(tmp, &dev->regs->devinit); + } + + /* always ep-{1,2,3,4} ... maybe not ep-3 or ep-4 */ + INIT_LIST_HEAD(&dev->gadget.ep_list); + + for (tmp = 1; tmp < dev->n_ep; tmp++) + list_add_tail(&dev->ep[tmp].ep.ep_list, &dev->gadget.ep_list); + +} + +static void usb_reset(struct net2280 *dev) +{ + if (dev->quirks & PLX_LEGACY) + return usb_reset_228x(dev); + return usb_reset_338x(dev); +} + +static void usb_reinit_228x(struct net2280 *dev) +{ + u32 tmp; + + /* basic endpoint init */ + for (tmp = 0; tmp < 7; tmp++) { + struct net2280_ep *ep = &dev->ep[tmp]; + + ep->ep.name = ep_info_dft[tmp].name; + ep->ep.caps = ep_info_dft[tmp].caps; + ep->dev = dev; + ep->num = tmp; + + if (tmp > 0 && tmp <= 4) { + ep->fifo_size = 1024; + ep->dma = &dev->dma[tmp - 1]; + } else + ep->fifo_size = 64; + ep->regs = &dev->epregs[tmp]; + ep->cfg = &dev->epregs[tmp]; + ep_reset_228x(dev->regs, ep); + } + usb_ep_set_maxpacket_limit(&dev->ep[0].ep, 64); + usb_ep_set_maxpacket_limit(&dev->ep[5].ep, 64); + usb_ep_set_maxpacket_limit(&dev->ep[6].ep, 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 usb_reinit_338x(struct net2280 *dev) +{ + int i; + u32 tmp, val; + static const u32 ne[9] = { 0, 1, 2, 3, 4, 1, 2, 3, 4 }; + static const u32 ep_reg_addr[9] = { 0x00, 0xC0, 0x00, 0xC0, 0x00, + 0x00, 0xC0, 0x00, 0xC0 }; + + /* basic endpoint init */ + for (i = 0; i < dev->n_ep; i++) { + struct net2280_ep *ep = &dev->ep[i]; + + ep->ep.name = dev->enhanced_mode ? ep_info_adv[i].name : + ep_info_dft[i].name; + ep->ep.caps = dev->enhanced_mode ? ep_info_adv[i].caps : + ep_info_dft[i].caps; + ep->dev = dev; + ep->num = i; + + if (i > 0 && i <= 4) + ep->dma = &dev->dma[i - 1]; + + if (dev->enhanced_mode) { + ep->cfg = &dev->epregs[ne[i]]; + /* + * Set USB endpoint number, hardware allows same number + * in both directions. + */ + if (i > 0 && i < 5) + writel(ne[i], &ep->cfg->ep_cfg); + ep->regs = (struct net2280_ep_regs __iomem *) + (((void __iomem *)&dev->epregs[ne[i]]) + + ep_reg_addr[i]); + } else { + ep->cfg = &dev->epregs[i]; + ep->regs = &dev->epregs[i]; + } + + ep->fifo_size = (i != 0) ? 2048 : 512; + + ep_reset_338x(dev->regs, ep); + } + usb_ep_set_maxpacket_limit(&dev->ep[0].ep, 512); + + dev->gadget.ep0 = &dev->ep[0].ep; + dev->ep[0].stopped = 0; + + /* Link layer set up */ + if (dev->bug7734_patched) { + tmp = readl(&dev->usb_ext->usbctl2) & + ~(BIT(U1_ENABLE) | BIT(U2_ENABLE) | BIT(LTM_ENABLE)); + writel(tmp, &dev->usb_ext->usbctl2); + } + + /* Hardware Defect and Workaround */ + val = readl(&dev->llregs->ll_lfps_5); + val &= ~(0xf << TIMER_LFPS_6US); + val |= 0x5 << TIMER_LFPS_6US; + writel(val, &dev->llregs->ll_lfps_5); + + val = readl(&dev->llregs->ll_lfps_6); + val &= ~(0xffff << TIMER_LFPS_80US); + val |= 0x0100 << TIMER_LFPS_80US; + writel(val, &dev->llregs->ll_lfps_6); + + /* + * AA_AB Errata. Issue 4. Workaround for SuperSpeed USB + * Hot Reset Exit Handshake may Fail in Specific Case using + * Default Register Settings. Workaround for Enumeration test. + */ + val = readl(&dev->llregs->ll_tsn_counters_2); + val &= ~(0x1f << HOT_TX_NORESET_TS2); + val |= 0x10 << HOT_TX_NORESET_TS2; + writel(val, &dev->llregs->ll_tsn_counters_2); + + val = readl(&dev->llregs->ll_tsn_counters_3); + val &= ~(0x1f << HOT_RX_RESET_TS2); + val |= 0x3 << HOT_RX_RESET_TS2; + writel(val, &dev->llregs->ll_tsn_counters_3); + + /* + * AB errata. Errata 11. Workaround for Default Duration of LFPS + * Handshake Signaling for Device-Initiated U1 Exit is too short. + * Without this, various enumeration failures observed with + * modern superspeed hosts. + */ + val = readl(&dev->llregs->ll_lfps_timers_2); + writel((val & 0xffff0000) | LFPS_TIMERS_2_WORKAROUND_VALUE, + &dev->llregs->ll_lfps_timers_2); + + /* + * Set Recovery Idle to Recover bit: + * - On SS connections, setting Recovery Idle to Recover Fmw improves + * link robustness with various hosts and hubs. + * - It is safe to set for all connection speeds; all chip revisions. + * - R-M-W to leave other bits undisturbed. + * - Reference PLX TT-7372 + */ + val = readl(&dev->llregs->ll_tsn_chicken_bit); + val |= BIT(RECOVERY_IDLE_TO_RECOVER_FMW); + writel(val, &dev->llregs->ll_tsn_chicken_bit); + + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); + + /* disable dedicated endpoints */ + writel(0x0D, &dev->dep[0].dep_cfg); + writel(0x0D, &dev->dep[1].dep_cfg); + writel(0x0E, &dev->dep[2].dep_cfg); + writel(0x0E, &dev->dep[3].dep_cfg); + writel(0x0F, &dev->dep[4].dep_cfg); + writel(0x0C, &dev->dep[5].dep_cfg); +} + +static void usb_reinit(struct net2280 *dev) +{ + if (dev->quirks & PLX_LEGACY) + return usb_reinit_228x(dev); + return usb_reinit_338x(dev); +} + +static void ep0_start_228x(struct net2280 *dev) +{ + writel(BIT(CLEAR_EP_HIDE_STATUS_PHASE) | + BIT(CLEAR_NAK_OUT_PACKETS) | + BIT(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(BIT(SET_TEST_MODE) | + BIT(SET_ADDRESS) | + BIT(DEVICE_SET_CLEAR_DEVICE_REMOTE_WAKEUP) | + BIT(GET_DEVICE_STATUS) | + BIT(GET_INTERFACE_STATUS), + &dev->usb->stdrsp); + writel(BIT(USB_ROOT_PORT_WAKEUP_ENABLE) | + BIT(SELF_POWERED_USB_DEVICE) | + BIT(REMOTE_WAKEUP_SUPPORT) | + (dev->softconnect << USB_DETECT_ENABLE) | + BIT(SELF_POWERED_STATUS), + &dev->usb->usbctl); + + /* enable irqs so we can see ep0 and general operation */ + writel(BIT(SETUP_PACKET_INTERRUPT_ENABLE) | + BIT(ENDPOINT_0_INTERRUPT_ENABLE), + &dev->regs->pciirqenb0); + writel(BIT(PCI_INTERRUPT_ENABLE) | + BIT(PCI_MASTER_ABORT_RECEIVED_INTERRUPT_ENABLE) | + BIT(PCI_TARGET_ABORT_RECEIVED_INTERRUPT_ENABLE) | + BIT(PCI_RETRY_ABORT_INTERRUPT_ENABLE) | + BIT(VBUS_INTERRUPT_ENABLE) | + BIT(ROOT_PORT_RESET_INTERRUPT_ENABLE) | + BIT(SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE), + &dev->regs->pciirqenb1); + + /* don't leave any writes posted */ + (void) readl(&dev->usb->usbctl); +} + +static void ep0_start_338x(struct net2280 *dev) +{ + + if (dev->bug7734_patched) + writel(BIT(CLEAR_NAK_OUT_PACKETS_MODE) | + BIT(SET_EP_HIDE_STATUS_PHASE), + &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(BIT(SET_ISOCHRONOUS_DELAY) | + BIT(SET_SEL) | + BIT(SET_TEST_MODE) | + BIT(SET_ADDRESS) | + BIT(GET_INTERFACE_STATUS) | + BIT(GET_DEVICE_STATUS), + &dev->usb->stdrsp); + dev->wakeup_enable = 1; + writel(BIT(USB_ROOT_PORT_WAKEUP_ENABLE) | + (dev->softconnect << USB_DETECT_ENABLE) | + BIT(DEVICE_REMOTE_WAKEUP_ENABLE), + &dev->usb->usbctl); + + /* enable irqs so we can see ep0 and general operation */ + writel(BIT(SETUP_PACKET_INTERRUPT_ENABLE) | + BIT(ENDPOINT_0_INTERRUPT_ENABLE), + &dev->regs->pciirqenb0); + writel(BIT(PCI_INTERRUPT_ENABLE) | + BIT(ROOT_PORT_RESET_INTERRUPT_ENABLE) | + BIT(SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE) | + BIT(VBUS_INTERRUPT_ENABLE), + &dev->regs->pciirqenb1); + + /* don't leave any writes posted */ + (void)readl(&dev->usb->usbctl); +} + +static void ep0_start(struct net2280 *dev) +{ + if (dev->quirks & PLX_LEGACY) + return ep0_start_228x(dev); + return ep0_start_338x(dev); +} + +/* 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 < dev->n_ep; i++) + dev->ep[i].irqs = 0; + + /* hook up the driver ... */ + 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 host detection and ep0; and we're ready + * for set_configuration as well as eventual disconnect. + */ + net2280_led_active(dev, 1); + + if ((dev->quirks & PLX_PCIE) && !dev->bug7734_patched) + defect7374_enable_data_eps_zero(dev); + + ep0_start(dev); + + /* 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 < dev->n_ep; i++) + nuke(&dev->ep[i]); + + /* report disconnect; the driver is already quiesced */ + if (dev->async_callbacks && 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 net2280 *dev; + unsigned long flags; + + dev = container_of(_gadget, struct net2280, gadget); + + spin_lock_irqsave(&dev->lock, flags); + stop_activity(dev, NULL); + spin_unlock_irqrestore(&dev->lock, flags); + + net2280_led_active(dev, 0); + + device_remove_file(&dev->pdev->dev, &dev_attr_function); + device_remove_file(&dev->pdev->dev, &dev_attr_queues); + + dev->driver = NULL; + + return 0; +} + +static void net2280_async_callbacks(struct usb_gadget *_gadget, bool enable) +{ + struct net2280 *dev = container_of(_gadget, struct net2280, gadget); + + spin_lock_irq(&dev->lock); + dev->async_callbacks = enable; + spin_unlock_irq(&dev->lock); +} + +/*-------------------------------------------------------------------------*/ + +/* 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++; + + ep_vdbg(ep->dev, "%s ack ep_stat %08x, req %p\n", + ep->ep.name, t, req ? &req->req : NULL); + + if (!ep->is_in || (ep->dev->quirks & PLX_2280)) + writel(t & ~BIT(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 & BIT(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 & BIT(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 & BIT(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 & BIT(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 & BIT(SHORT_PACKET_TRANSFERRED_INTERRUPT)) { + struct net2280_request *stuck_req = NULL; + int stopped = ep->stopped; + int num_completed; + int stuck = 0; + u32 count; + + /* 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 + */ + num_completed = 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 & BIT(FIFO_EMPTY))) { + count = readl(&ep->dma->dmacount); + count &= DMA_BYTE_COUNT_MASK; + if (readl(&ep->dma->dmadesc) + != req->td_dma) + req = NULL; + break; + } + + /* Escape loop if no dma transfers completed + * after few retries. + */ + if (num_completed == 0) { + if (stuck_req == req && + readl(&ep->dma->dmadesc) != + req->td_dma && stuck++ > 5) { + count = readl( + &ep->dma->dmacount); + count &= DMA_BYTE_COUNT_MASK; + req = NULL; + ep_dbg(ep->dev, "%s escape stuck %d, count %u\n", + ep->ep.name, stuck, + count); + break; + } else if (stuck_req != req) { + stuck_req = req; + stuck = 0; + } + } else { + stuck_req = NULL; + stuck = 0; + } + + udelay(1); + } + + /* stop DMA, leave ep NAKing */ + writel(BIT(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 + ep_dbg(ep->dev, "%s dma ep_stat %08x ??\n", + ep->ep.name, t); + return; + + /* data packet(s) received (in the fifo, OUT) */ + } else if (t & BIT(DATA_PACKET_RECEIVED_INTERRUPT)) { + if (read_fifo(ep, req) && ep->num != 0) + mode = 2; + + /* data packet(s) transmitted (IN) */ + } else if (t & BIT(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 */ + /* send zlps until the status stage */ + if ((req->req.actual == req->req.length) && + (!req->req.zero || len != ep->ep.maxpacket) && ep->num) + 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 & BIT(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 defect7374_workaround(struct net2280 *dev, struct usb_ctrlrequest r) +{ + u32 scratch, fsmvalue; + u32 ack_wait_timeout, state; + + /* Workaround for Defect 7374 (U1/U2 erroneously rejected): */ + scratch = get_idx_reg(dev->regs, SCRATCH); + fsmvalue = scratch & (0xf << DEFECT7374_FSM_FIELD); + scratch &= ~(0xf << DEFECT7374_FSM_FIELD); + + if (!((fsmvalue == DEFECT7374_FSM_WAITING_FOR_CONTROL_READ) && + (r.bRequestType & USB_DIR_IN))) + return; + + /* This is the first Control Read for this connection: */ + if (!(readl(&dev->usb->usbstat) & BIT(SUPER_SPEED_MODE))) { + /* + * Connection is NOT SS: + * - Connection must be FS or HS. + * - This FSM state should allow workaround software to + * run after the next USB connection. + */ + scratch |= DEFECT7374_FSM_NON_SS_CONTROL_READ; + dev->bug7734_patched = 1; + goto restore_data_eps; + } + + /* Connection is SS: */ + for (ack_wait_timeout = 0; + ack_wait_timeout < DEFECT_7374_NUMBEROF_MAX_WAIT_LOOPS; + ack_wait_timeout++) { + + state = readl(&dev->plregs->pl_ep_status_1) + & (0xff << STATE); + if ((state >= (ACK_GOOD_NORMAL << STATE)) && + (state <= (ACK_GOOD_MORE_ACKS_TO_COME << STATE))) { + scratch |= DEFECT7374_FSM_SS_CONTROL_READ; + dev->bug7734_patched = 1; + break; + } + + /* + * We have not yet received host's Data Phase ACK + * - Wait and try again. + */ + udelay(DEFECT_7374_PROCESSOR_WAIT_TIME); + } + + + if (ack_wait_timeout >= DEFECT_7374_NUMBEROF_MAX_WAIT_LOOPS) { + ep_err(dev, "FAIL: Defect 7374 workaround waited but failed " + "to detect SS host's data phase ACK."); + ep_err(dev, "PL_EP_STATUS_1(23:16):.Expected from 0x11 to 0x16" + "got 0x%2.2x.\n", state >> STATE); + } else { + ep_warn(dev, "INFO: Defect 7374 workaround waited about\n" + "%duSec for Control Read Data Phase ACK\n", + DEFECT_7374_PROCESSOR_WAIT_TIME * ack_wait_timeout); + } + +restore_data_eps: + /* + * Restore data EPs to their pre-workaround settings (disabled, + * initialized, and other details). + */ + defect7374_disable_data_eps(dev); + + set_idx_reg(dev->regs, SCRATCH, scratch); + + return; +} + +static void ep_clear_seqnum(struct net2280_ep *ep) +{ + struct net2280 *dev = ep->dev; + u32 val; + static const u32 ep_pl[9] = { 0, 3, 4, 7, 8, 2, 5, 6, 9 }; + + val = readl(&dev->plregs->pl_ep_ctrl) & ~0x1f; + val |= ep_pl[ep->num]; + writel(val, &dev->plregs->pl_ep_ctrl); + val |= BIT(SEQUENCE_NUMBER_RESET); + writel(val, &dev->plregs->pl_ep_ctrl); + + return; +} + +static void handle_stat0_irqs_superspeed(struct net2280 *dev, + struct net2280_ep *ep, struct usb_ctrlrequest r) +{ + struct net2280_ep *e; + u16 status; + int tmp = 0; + +#define w_value le16_to_cpu(r.wValue) +#define w_index le16_to_cpu(r.wIndex) +#define w_length le16_to_cpu(r.wLength) + + switch (r.bRequest) { + case USB_REQ_SET_CONFIGURATION: + dev->addressed_state = !w_value; + goto usb3_delegate; + + case USB_REQ_GET_STATUS: + switch (r.bRequestType) { + case (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE): + status = dev->wakeup_enable ? 0x02 : 0x00; + if (dev->gadget.is_selfpowered) + status |= BIT(0); + status |= (dev->u1_enable << 2 | dev->u2_enable << 3 | + dev->ltm_enable << 4); + writel(0, &dev->epregs[0].ep_irqenb); + set_fifo_bytecount(ep, sizeof(status)); + writel((__force u32) status, &dev->epregs[0].ep_data); + allow_status_338x(ep); + break; + + case (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT): + e = get_ep_by_addr(dev, w_index); + if (!e) + goto do_stall3; + status = readl(&e->regs->ep_rsp) & + BIT(CLEAR_ENDPOINT_HALT); + writel(0, &dev->epregs[0].ep_irqenb); + set_fifo_bytecount(ep, sizeof(status)); + writel((__force u32) status, &dev->epregs[0].ep_data); + allow_status_338x(ep); + break; + + default: + goto usb3_delegate; + } + break; + + case USB_REQ_CLEAR_FEATURE: + switch (r.bRequestType) { + case (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE): + if (!dev->addressed_state) { + switch (w_value) { + case USB_DEVICE_U1_ENABLE: + dev->u1_enable = 0; + writel(readl(&dev->usb_ext->usbctl2) & + ~BIT(U1_ENABLE), + &dev->usb_ext->usbctl2); + allow_status_338x(ep); + goto next_endpoints3; + + case USB_DEVICE_U2_ENABLE: + dev->u2_enable = 0; + writel(readl(&dev->usb_ext->usbctl2) & + ~BIT(U2_ENABLE), + &dev->usb_ext->usbctl2); + allow_status_338x(ep); + goto next_endpoints3; + + case USB_DEVICE_LTM_ENABLE: + dev->ltm_enable = 0; + writel(readl(&dev->usb_ext->usbctl2) & + ~BIT(LTM_ENABLE), + &dev->usb_ext->usbctl2); + allow_status_338x(ep); + goto next_endpoints3; + + default: + break; + } + } + if (w_value == USB_DEVICE_REMOTE_WAKEUP) { + dev->wakeup_enable = 0; + writel(readl(&dev->usb->usbctl) & + ~BIT(DEVICE_REMOTE_WAKEUP_ENABLE), + &dev->usb->usbctl); + allow_status_338x(ep); + break; + } + goto usb3_delegate; + + case (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT): + e = get_ep_by_addr(dev, w_index); + if (!e) + goto do_stall3; + if (w_value != USB_ENDPOINT_HALT) + goto do_stall3; + ep_vdbg(dev, "%s clear halt\n", e->ep.name); + /* + * Workaround for SS SeqNum not cleared via + * Endpoint Halt (Clear) bit. select endpoint + */ + ep_clear_seqnum(e); + clear_halt(e); + if (!list_empty(&e->queue) && e->td_dma) + restart_dma(e); + allow_status(ep); + ep->stopped = 1; + break; + + default: + goto usb3_delegate; + } + break; + case USB_REQ_SET_FEATURE: + switch (r.bRequestType) { + case (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE): + if (!dev->addressed_state) { + switch (w_value) { + case USB_DEVICE_U1_ENABLE: + dev->u1_enable = 1; + writel(readl(&dev->usb_ext->usbctl2) | + BIT(U1_ENABLE), + &dev->usb_ext->usbctl2); + allow_status_338x(ep); + goto next_endpoints3; + + case USB_DEVICE_U2_ENABLE: + dev->u2_enable = 1; + writel(readl(&dev->usb_ext->usbctl2) | + BIT(U2_ENABLE), + &dev->usb_ext->usbctl2); + allow_status_338x(ep); + goto next_endpoints3; + + case USB_DEVICE_LTM_ENABLE: + dev->ltm_enable = 1; + writel(readl(&dev->usb_ext->usbctl2) | + BIT(LTM_ENABLE), + &dev->usb_ext->usbctl2); + allow_status_338x(ep); + goto next_endpoints3; + default: + break; + } + } + + if (w_value == USB_DEVICE_REMOTE_WAKEUP) { + dev->wakeup_enable = 1; + writel(readl(&dev->usb->usbctl) | + BIT(DEVICE_REMOTE_WAKEUP_ENABLE), + &dev->usb->usbctl); + allow_status_338x(ep); + break; + } + goto usb3_delegate; + + case (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT): + e = get_ep_by_addr(dev, w_index); + if (!e || (w_value != USB_ENDPOINT_HALT)) + goto do_stall3; + ep->stopped = 1; + if (ep->num == 0) + ep->dev->protocol_stall = 1; + else { + if (ep->dma) + abort_dma(ep); + set_halt(ep); + } + allow_status_338x(ep); + break; + + default: + goto usb3_delegate; + } + + break; + default: + +usb3_delegate: + ep_vdbg(dev, "setup %02x.%02x v%04x i%04x l%04x ep_cfg %08x\n", + r.bRequestType, r.bRequest, + w_value, w_index, w_length, + readl(&ep->cfg->ep_cfg)); + + ep->responded = 0; + if (dev->async_callbacks) { + spin_unlock(&dev->lock); + tmp = dev->driver->setup(&dev->gadget, &r); + spin_lock(&dev->lock); + } + } +do_stall3: + if (tmp < 0) { + ep_vdbg(dev, "req %02x.%02x protocol STALL; stat %d\n", + r.bRequestType, r.bRequest, tmp); + dev->protocol_stall = 1; + /* TD 9.9 Halt Endpoint test. TD 9.22 Set feature test */ + set_halt(ep); + } + +next_endpoints3: + +#undef w_value +#undef w_index +#undef w_length + + return; +} + +static void usb338x_handle_ep_intr(struct net2280 *dev, u32 stat0) +{ + u32 index; + u32 bit; + + for (index = 0; index < ARRAY_SIZE(ep_bit); index++) { + bit = BIT(ep_bit[index]); + + if (!stat0) + break; + + if (!(stat0 & bit)) + continue; + + stat0 &= ~bit; + + handle_ep_small(&dev->ep[index]); + } +} + +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 &= ~BIT(INTA_ASSERTED); + if (!stat) + return; + /* ep_dbg(dev, "irqstat0 %04x\n", stat); */ + + /* starting a control request? */ + if (unlikely(stat & BIT(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) { + u32 val = readl(&dev->usb->usbstat); + if (val & BIT(SUPER_SPEED)) { + dev->gadget.speed = USB_SPEED_SUPER; + usb_ep_set_maxpacket_limit(&dev->ep[0].ep, + EP0_SS_MAX_PACKET_SIZE); + } else if (val & BIT(HIGH_SPEED)) { + dev->gadget.speed = USB_SPEED_HIGH; + usb_ep_set_maxpacket_limit(&dev->ep[0].ep, + EP0_HS_MAX_PACKET_SIZE); + } else { + dev->gadget.speed = USB_SPEED_FULL; + usb_ep_set_maxpacket_limit(&dev->ep[0].ep, + EP0_HS_MAX_PACKET_SIZE); + } + net2280_led_speed(dev, dev->gadget.speed); + ep_dbg(dev, "%s\n", + usb_speed_string(dev->gadget.speed)); + } + + ep = &dev->ep[0]; + ep->irqs++; + + /* make sure any leftover request state is cleared */ + stat &= ~BIT(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 (!(dev->quirks & PLX_PCIE)) { + if (ep->dev->quirks & PLX_2280) + tmp = BIT(FIFO_OVERFLOW) | + BIT(FIFO_UNDERFLOW); + else + tmp = 0; + + writel(tmp | BIT(TIMEOUT) | + BIT(USB_STALL_SENT) | + BIT(USB_IN_NAK_SENT) | + BIT(USB_IN_ACK_RCVD) | + BIT(USB_OUT_PING_NAK_SENT) | + BIT(USB_OUT_ACK_SENT) | + BIT(SHORT_PACKET_OUT_DONE_INTERRUPT) | + BIT(SHORT_PACKET_TRANSFERRED_INTERRUPT) | + BIT(DATA_PACKET_RECEIVED_INTERRUPT) | + BIT(DATA_PACKET_TRANSMITTED_INTERRUPT) | + BIT(DATA_OUT_PING_TOKEN_INTERRUPT) | + BIT(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]); + + if ((dev->quirks & PLX_PCIE) && !dev->bug7734_patched) + defect7374_workaround(dev, u.r); + + 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(BIT(SETUP_PACKET_INTERRUPT), &dev->regs->irqstat0); + stat ^= BIT(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 = BIT(DATA_PACKET_TRANSMITTED_INTERRUPT) | + BIT(DATA_OUT_PING_TOKEN_INTERRUPT) | + BIT(DATA_IN_TOKEN_INTERRUPT); + stop_out_naking(ep); + } else + scratch = BIT(DATA_PACKET_RECEIVED_INTERRUPT) | + BIT(DATA_OUT_PING_TOKEN_INTERRUPT) | + BIT(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; + + if (dev->gadget.speed == USB_SPEED_SUPER) { + handle_stat0_irqs_superspeed(dev, ep, u.r); + goto next_endpoints; + } + + 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; + e = get_ep_by_addr(dev, w_index); + if (!e || w_length > 2) + goto do_stall; + + if (readl(&e->regs->ep_rsp) & BIT(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); + ep_vdbg(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; + e = get_ep_by_addr(dev, w_index); + if (!e) + goto do_stall; + if (e->wedged) { + ep_vdbg(dev, "%s wedged, halt not cleared\n", + ep->ep.name); + } else { + ep_vdbg(dev, "%s clear halt\n", e->ep.name); + clear_halt(e); + if ((ep->dev->quirks & PLX_PCIE) && + !list_empty(&e->queue) && e->td_dma) + restart_dma(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; + e = get_ep_by_addr(dev, w_index); + if (!e) + goto do_stall; + if (e->ep.name == ep0name) + goto do_stall; + set_halt(e); + if ((dev->quirks & PLX_PCIE) && e->dma) + abort_dma(e); + allow_status(ep); + ep_vdbg(dev, "%s set halt\n", ep->ep.name); + goto next_endpoints; + } + break; + default: +delegate: + ep_vdbg(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->cfg->ep_cfg)); + ep->responded = 0; + if (dev->async_callbacks) { + 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: + ep_vdbg(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: + if ((dev->quirks & PLX_PCIE) && dev->enhanced_mode) { + u32 mask = (BIT(ENDPOINT_0_INTERRUPT) | + USB3380_IRQSTAT0_EP_INTR_MASK_IN | + USB3380_IRQSTAT0_EP_INTR_MASK_OUT); + + if (stat & mask) { + usb338x_handle_ep_intr(dev, stat & mask); + stat &= ~mask; + } + } else { + /* 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 = BIT(num); + if ((scratch & t) == 0) + continue; + scratch ^= t; + + ep = &dev->ep[num]; + handle_ep_small(ep); + } + } + + if (stat) + ep_dbg(dev, "unhandled irqstat0 %08x\n", stat); +} + +#define DMA_INTERRUPTS (BIT(DMA_D_INTERRUPT) | \ + BIT(DMA_C_INTERRUPT) | \ + BIT(DMA_B_INTERRUPT) | \ + BIT(DMA_A_INTERRUPT)) +#define PCI_ERROR_INTERRUPTS ( \ + BIT(PCI_MASTER_ABORT_RECEIVED_INTERRUPT) | \ + BIT(PCI_TARGET_ABORT_RECEIVED_INTERRUPT) | \ + BIT(PCI_RETRY_ABORT_INTERRUPT)) + +static void handle_stat1_irqs(struct net2280 *dev, u32 stat) +__releases(dev->lock) +__acquires(dev->lock) +{ + struct net2280_ep *ep; + u32 tmp, num, mask, scratch; + + /* after disconnect there's nothing else to do! */ + tmp = BIT(VBUS_INTERRUPT) | BIT(ROOT_PORT_RESET_INTERRUPT); + mask = BIT(SUPER_SPEED) | BIT(HIGH_SPEED) | BIT(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) { + bool reset = false; + bool disconnect = false; + + /* + * Ignore disconnects and resets if the speed hasn't been set. + * VBUS can bounce and there's always an initial reset. + */ + writel(tmp, &dev->regs->irqstat1); + if (dev->gadget.speed != USB_SPEED_UNKNOWN) { + if ((stat & BIT(VBUS_INTERRUPT)) && + (readl(&dev->usb->usbctl) & + BIT(VBUS_PIN)) == 0) { + disconnect = true; + ep_dbg(dev, "disconnect %s\n", + dev->driver->driver.name); + } else if ((stat & BIT(ROOT_PORT_RESET_INTERRUPT)) && + (readl(&dev->usb->usbstat) & mask) + == 0) { + reset = true; + ep_dbg(dev, "reset %s\n", + dev->driver->driver.name); + } + + if (disconnect || reset) { + stop_activity(dev, dev->driver); + ep0_start(dev); + if (dev->async_callbacks) { + spin_unlock(&dev->lock); + if (reset) + usb_gadget_udc_reset(&dev->gadget, dev->driver); + else + (dev->driver->disconnect)(&dev->gadget); + spin_lock(&dev->lock); + } + 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 = BIT(SUSPEND_REQUEST_CHANGE_INTERRUPT); + if (stat & tmp) { + writel(tmp, &dev->regs->irqstat1); + spin_unlock(&dev->lock); + if (stat & BIT(SUSPEND_REQUEST_INTERRUPT)) { + if (dev->async_callbacks && dev->driver->suspend) + dev->driver->suspend(&dev->gadget); + if (!enable_suspend) + stat &= ~BIT(SUSPEND_REQUEST_INTERRUPT); + } else { + if (dev->async_callbacks && dev->driver->resume) + dev->driver->resume(&dev->gadget); + /* at high speed, note erratum 0133 */ + } + spin_lock(&dev->lock); + stat &= ~tmp; + } + + /* clear any other status/irqs */ + if (stat) + writel(stat, &dev->regs->irqstat1); + + /* some status we can just ignore */ + if (dev->quirks & PLX_2280) + stat &= ~(BIT(CONTROL_STATUS_INTERRUPT) | + BIT(SUSPEND_REQUEST_INTERRUPT) | + BIT(RESUME_INTERRUPT) | + BIT(SOF_INTERRUPT)); + else + stat &= ~(BIT(CONTROL_STATUS_INTERRUPT) | + BIT(RESUME_INTERRUPT) | + BIT(SOF_DOWN_INTERRUPT) | + BIT(SOF_INTERRUPT)); + + if (!stat) + return; + /* ep_dbg(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 = BIT(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); + + /* dma sync*/ + if (dev->quirks & PLX_PCIE) { + u32 r_dmacount = readl(&dma->dmacount); + if (!ep->is_in && (r_dmacount & 0x00FFFFFF) && + (tmp & BIT(DMA_TRANSACTION_DONE_INTERRUPT))) + continue; + } + + if (!(tmp & BIT(DMA_TRANSACTION_DONE_INTERRUPT))) { + ep_dbg(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)) { + tmp = readl(&dma->dmactl); + 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) { + ep_err(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) + ep_dbg(dev, "unhandled irqstat1 %08x\n", stat); +} + +static irqreturn_t net2280_irq(int irq, void *_dev) +{ + struct net2280 *dev = _dev; + + /* shared interrupt, not ours */ + if ((dev->quirks & PLX_LEGACY) && + (!(readl(&dev->regs->irqstat0) & BIT(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)); + + if (dev->quirks & PLX_PCIE) { + /* re-enable interrupt to trigger any possible new interrupt */ + u32 pciirqenb1 = readl(&dev->regs->pciirqenb1); + writel(pciirqenb1 & 0x7FFFFFFF, &dev->regs->pciirqenb1); + writel(pciirqenb1, &dev->regs->pciirqenb1); + } + + spin_unlock(&dev->lock); + + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +static void gadget_release(struct device *_dev) +{ + struct net2280 *dev = container_of(_dev, struct net2280, gadget.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); + + if (dev->added) + usb_del_gadget(&dev->gadget); + + BUG_ON(dev->driver); + + /* then clean up the resources we allocated during probe() */ + if (dev->requests) { + int i; + for (i = 1; i < 5; i++) { + if (!dev->ep[i].dummy) + continue; + dma_pool_free(dev->requests, dev->ep[i].dummy, + dev->ep[i].td_dma); + } + dma_pool_destroy(dev->requests); + } + if (dev->got_irq) + free_irq(pdev->irq, dev); + if (dev->quirks & PLX_PCIE) + pci_disable_msi(pdev); + if (dev->regs) { + net2280_led_shutdown(dev); + 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); + + ep_info(dev, "unbind\n"); + usb_put_gadget(&dev->gadget); +} + +/* 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_obj(*dev); + if (dev == NULL) { + retval = -ENOMEM; + goto done; + } + + pci_set_drvdata(pdev, dev); + usb_initialize_gadget(&pdev->dev, &dev->gadget, gadget_release); + spin_lock_init(&dev->lock); + dev->quirks = id->driver_data; + dev->pdev = pdev; + dev->gadget.ops = &net2280_ops; + dev->gadget.max_speed = (dev->quirks & PLX_SUPERSPEED) ? + USB_SPEED_SUPER : 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)) { + ep_dbg(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(resource, len); + if (base == NULL) { + ep_dbg(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); + + if (dev->quirks & PLX_PCIE) { + u32 fsmvalue; + u32 usbstat; + dev->usb_ext = (struct usb338x_usb_ext_regs __iomem *) + (base + 0x00b4); + dev->llregs = (struct usb338x_ll_regs __iomem *) + (base + 0x0700); + dev->plregs = (struct usb338x_pl_regs __iomem *) + (base + 0x0800); + usbstat = readl(&dev->usb->usbstat); + dev->enhanced_mode = !!(usbstat & BIT(11)); + dev->n_ep = (dev->enhanced_mode) ? 9 : 5; + /* put into initial config, link up all endpoints */ + fsmvalue = get_idx_reg(dev->regs, SCRATCH) & + (0xf << DEFECT7374_FSM_FIELD); + /* See if firmware needs to set up for workaround: */ + if (fsmvalue == DEFECT7374_FSM_SS_CONTROL_READ) { + dev->bug7734_patched = 1; + writel(0, &dev->usb->usbctl); + } else + dev->bug7734_patched = 0; + } else { + dev->enhanced_mode = 0; + dev->n_ep = 7; + /* 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) { + ep_err(dev, "No IRQ. Check PCI setup!\n"); + retval = -ENODEV; + goto done; + } + + if (dev->quirks & PLX_PCIE) + if (pci_enable_msi(pdev)) + ep_err(dev, "Failed to enable MSI mode\n"); + + if (request_irq(pdev->irq, net2280_irq, IRQF_SHARED, + driver_name, dev)) { + ep_err(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 = dma_pool_create("requests", &pdev->dev, + sizeof(struct net2280_dma), + 0 /* no alignment requirements */, + 0 /* or page-crossing issues */); + if (!dev->requests) { + ep_dbg(dev, "can't get request pool\n"); + retval = -ENOMEM; + goto done; + } + for (i = 1; i < 5; i++) { + struct net2280_dma *td; + + td = dma_pool_alloc(dev->requests, GFP_KERNEL, + &dev->ep[i].td_dma); + if (!td) { + ep_dbg(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 */ + if (dev->quirks & PLX_LEGACY) + writel(BIT(DMA_MEMORY_WRITE_AND_INVALIDATE_ENABLE) | + /* + * 256 write retries may not be enough... + BIT(PCI_RETRY_ABORT_ENABLE) | + */ + BIT(DMA_READ_MULTIPLE_ENABLE) | + BIT(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 */ + ep_info(dev, "%s\n", driver_desc); + ep_info(dev, "irq %d, pci mem %p, chip rev %04x\n", + pdev->irq, base, dev->chiprev); + ep_info(dev, "version: " DRIVER_VERSION "; %s\n", + dev->enhanced_mode ? "enhanced mode" : "legacy mode"); + retval = device_create_file(&pdev->dev, &dev_attr_registers); + if (retval) + goto done; + + retval = usb_add_gadget(&dev->gadget); + if (retval) + goto done; + dev->added = 1; + 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); + +} + + +/*-------------------------------------------------------------------------*/ + +static const struct pci_device_id pci_ids[] = { { + .class = PCI_CLASS_SERIAL_USB_DEVICE, + .class_mask = ~0, + .vendor = PCI_VENDOR_ID_PLX_LEGACY, + .device = 0x2280, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = PLX_LEGACY | PLX_2280, + }, { + .class = PCI_CLASS_SERIAL_USB_DEVICE, + .class_mask = ~0, + .vendor = PCI_VENDOR_ID_PLX_LEGACY, + .device = 0x2282, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = PLX_LEGACY, + }, + { + .class = PCI_CLASS_SERIAL_USB_DEVICE, + .class_mask = ~0, + .vendor = PCI_VENDOR_ID_PLX, + .device = 0x2380, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = PLX_PCIE, + }, + { + .class = ((PCI_CLASS_SERIAL_USB << 8) | 0xfe), + .class_mask = ~0, + .vendor = PCI_VENDOR_ID_PLX, + .device = 0x3380, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = PLX_PCIE | PLX_SUPERSPEED, + }, + { + .class = PCI_CLASS_SERIAL_USB_DEVICE, + .class_mask = ~0, + .vendor = PCI_VENDOR_ID_PLX, + .device = 0x3382, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = PLX_PCIE | PLX_SUPERSPEED, + }, +{ /* 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 = driver_name, + .id_table = pci_ids, + + .probe = net2280_probe, + .remove = net2280_remove, + .shutdown = net2280_shutdown, + + /* FIXME add power management support */ +}; + +module_pci_driver(net2280_pci_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("David Brownell"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h new file mode 100644 index 000000000..34716a9f4 --- /dev/null +++ b/drivers/usb/gadget/udc/net2280.h @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + * Copyright (C) 2014 Ricardo Ribalda - Qtechnology/AS + */ + +#include +#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 PCI_VENDOR_ID_PLX_LEGACY 0x17cc + +#define PLX_LEGACY BIT(0) +#define PLX_2280 BIT(1) +#define PLX_SUPERSPEED BIT(2) +#define PLX_PCIE BIT(3) + +#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 + +/* DEFECT 7374 */ +#define DEFECT_7374_NUMBEROF_MAX_WAIT_LOOPS 200 +#define DEFECT_7374_PROCESSOR_WAIT_TIME 10 + +/* ep0 max packet size */ +#define EP0_SS_MAX_PACKET_SIZE 0x200 +#define EP0_HS_MAX_PACKET_SIZE 0x40 +#ifdef __KERNEL__ + +/*-------------------------------------------------------------------------*/ + +/* [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; +} __aligned(16); + +/*-------------------------------------------------------------------------*/ + +/* DRIVER DATA STRUCTURES and UTILITIES */ + +struct net2280_ep { + struct usb_ep ep; + struct net2280_ep_regs __iomem *cfg; + 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(BIT(CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE) | + BIT(CLEAR_NAK_OUT_PACKETS) | + BIT(CLEAR_NAK_OUT_PACKETS_MODE), + &ep->regs->ep_rsp); + ep->stopped = 1; +} + +static inline void allow_status_338x(struct net2280_ep *ep) +{ + /* + * Control Status Phase Handshake was set by the chip when the setup + * packet arrived. While set, the chip automatically NAKs the host's + * Status Phase tokens. + */ + writel(BIT(CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE), &ep->regs->ep_rsp); + + ep->stopped = 1; + + /* TD 9.9 Halt Endpoint test. TD 9.22 set feature test. */ + ep->responded = 0; +} + +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[9]; + struct usb_gadget_driver *driver; + unsigned enabled : 1, + protocol_stall : 1, + softconnect : 1, + got_irq : 1, + region:1, + added:1, + u1_enable:1, + u2_enable:1, + ltm_enable:1, + wakeup_enable:1, + addressed_state:1, + async_callbacks:1, + bug7734_patched:1; + u16 chiprev; + int enhanced_mode; + int n_ep; + kernel_ulong_t quirks; + + + /* pci state used to access those endpoints */ + struct pci_dev *pdev; + struct net2280_regs __iomem *regs; + struct net2280_usb_regs __iomem *usb; + struct usb338x_usb_ext_regs __iomem *usb_ext; + 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 usb338x_ll_regs __iomem *llregs; + struct usb338x_pl_regs __iomem *plregs; + + struct dma_pool *requests; + /* statistics...*/ +}; + +static inline void set_halt(struct net2280_ep *ep) +{ + /* ep0 and bulk/intr endpoints */ + writel(BIT(CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE) | + /* set NAK_OUT for erratum 0114 */ + ((ep->dev->chiprev == CHIPREV_1) << SET_NAK_OUT_PACKETS) | + BIT(SET_ENDPOINT_HALT), + &ep->regs->ep_rsp); +} + +static inline void clear_halt(struct net2280_ep *ep) +{ + /* ep0 and bulk/intr endpoints */ + writel(BIT(CLEAR_ENDPOINT_HALT) | + BIT(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); +} + +/* + * FSM value for Defect 7374 (U1U2 Test) is managed in + * chip's SCRATCH register: + */ +#define DEFECT7374_FSM_FIELD 28 + +/* Waiting for Control Read: + * - A transition to this state indicates a fresh USB connection, + * before the first Setup Packet. The connection speed is not + * known. Firmware is waiting for the first Control Read. + * - Starting state: This state can be thought of as the FSM's typical + * starting state. + * - Tip: Upon the first SS Control Read the FSM never + * returns to this state. + */ +#define DEFECT7374_FSM_WAITING_FOR_CONTROL_READ BIT(DEFECT7374_FSM_FIELD) + +/* Non-SS Control Read: + * - A transition to this state indicates detection of the first HS + * or FS Control Read. + * - Tip: Upon the first SS Control Read the FSM never + * returns to this state. + */ +#define DEFECT7374_FSM_NON_SS_CONTROL_READ (2 << DEFECT7374_FSM_FIELD) + +/* SS Control Read: + * - A transition to this state indicates detection of the + * first SS Control Read. + * - This state indicates workaround completion. Workarounds no longer + * need to be applied (as long as the chip remains powered up). + * - Tip: Once in this state the FSM state does not change (until + * the chip's power is lost and restored). + * - This can be thought of as the final state of the FSM; + * the FSM 'locks-up' in this state until the chip loses power. + */ +#define DEFECT7374_FSM_SS_CONTROL_READ (3 << DEFECT7374_FSM_FIELD) + +#ifdef USE_RDK_LEDS + +static inline void net2280_led_init(struct net2280 *dev) +{ + /* LED3 (green) is on during USB activity. note erratum 0113. */ + writel(BIT(GPIO3_LED_SELECT) | + BIT(GPIO3_OUTPUT_ENABLE) | + BIT(GPIO2_OUTPUT_ENABLE) | + BIT(GPIO1_OUTPUT_ENABLE) | + BIT(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_SUPER: /* green + red */ + val |= BIT(GPIO0_DATA) | BIT(GPIO1_DATA); + break; + case USB_SPEED_HIGH: /* green */ + val &= ~BIT(GPIO0_DATA); + val |= BIT(GPIO1_DATA); + break; + case USB_SPEED_FULL: /* red */ + val &= ~BIT(GPIO1_DATA); + val |= BIT(GPIO0_DATA); + break; + default: /* (off/black) */ + val &= ~(BIT(GPIO1_DATA) | BIT(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 ep_dbg(ndev, fmt, args...) \ + dev_dbg((&((ndev)->pdev->dev)), fmt, ##args) + +#define ep_vdbg(ndev, fmt, args...) \ + dev_vdbg((&((ndev)->pdev->dev)), fmt, ##args) + +#define ep_info(ndev, fmt, args...) \ + dev_info((&((ndev)->pdev->dev)), fmt, ##args) + +#define ep_warn(ndev, fmt, args...) \ + dev_warn((&((ndev)->pdev->dev)), fmt, ##args) + +#define ep_err(ndev, fmt, args...) \ + dev_err((&((ndev)->pdev->dev)), fmt, ##args) + +/*-------------------------------------------------------------------------*/ + +static inline void set_fifo_bytecount(struct net2280_ep *ep, unsigned count) +{ + if (ep->dev->pdev->vendor == 0x17cc) + writeb(count, 2 + (u8 __iomem *) &ep->regs->ep_cfg); + else{ + u32 tmp = readl(&ep->cfg->ep_cfg) & + (~(0x07 << EP_FIFO_BYTE_COUNT)); + writel(tmp | (count << EP_FIFO_BYTE_COUNT), &ep->cfg->ep_cfg); + } +} + +static inline void start_out_naking(struct net2280_ep *ep) +{ + /* NOTE: hardware races lurk here, and PING protocol issues */ + writel(BIT(SET_NAK_OUT_PACKETS), &ep->regs->ep_rsp); + /* synch with device */ + readl(&ep->regs->ep_rsp); +} + +static inline void stop_out_naking(struct net2280_ep *ep) +{ + u32 tmp; + + tmp = readl(&ep->regs->ep_stat); + if ((tmp & BIT(NAK_OUT_PACKETS)) != 0) + writel(BIT(CLEAR_NAK_OUT_PACKETS), &ep->regs->ep_rsp); +} + + +static inline void set_max_speed(struct net2280_ep *ep, u32 max) +{ + u32 reg; + static const u32 ep_enhanced[9] = { 0x10, 0x60, 0x30, 0x80, + 0x50, 0x20, 0x70, 0x40, 0x90 }; + + if (ep->dev->enhanced_mode) { + reg = ep_enhanced[ep->num]; + switch (ep->dev->gadget.speed) { + case USB_SPEED_SUPER: + reg += 2; + break; + case USB_SPEED_FULL: + reg += 1; + break; + case USB_SPEED_HIGH: + default: + break; + } + } else { + reg = (ep->num + 1) * 0x10; + if (ep->dev->gadget.speed != USB_SPEED_HIGH) + reg += 1; + } + + set_idx_reg(ep->dev->regs, reg, max); +} + +#endif /* __KERNEL__ */ diff --git a/drivers/usb/gadget/udc/omap_udc.c b/drivers/usb/gadget/udc/omap_udc.c new file mode 100644 index 000000000..f3ca79cec --- /dev/null +++ b/drivers/usb/gadget/udc/omap_udc.c @@ -0,0 +1,2992 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#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 +#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_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"; + +/*-------------------------------------------------------------------------*/ + +/* 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); + timer_delete(&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_obj(*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); + usb_gadget_giveback_request(&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 int 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_t(unsigned int, packets, 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_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_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 = NULL, *iter; + 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(iter, &ep->queue, queue) { + if (&iter->req != _req) + continue; + req = iter; + break; + } + if (!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 const 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; + + gadget->is_selfpowered = (is_selfpowered != 0); + 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", str_on_off(is_active)); + 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); + +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 + * reactivating 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(struct timer_list *t) +{ + struct omap_ep *ep = timer_container_of(ep, t, timer); + 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_osk() || machine_is_omap_palmte() || + machine_is_sx1(); +} + +static int omap_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + int status; + 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 */ + 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"); + udc->driver = NULL; + goto done; + } + } else { + status = 0; + 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) +{ + unsigned long flags; + + 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 0; +} + +/*-------------------------------------------------------------------------*/ + +#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 = "transceiver_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, "OMAP UDC driver, version: " DRIVER_VERSION +#ifdef USE_ISO + " (iso)" +#endif + "%s\n", 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 void create_proc_file(void) +{ + proc_create_single(proc_filename, 0, NULL, proc_udc_show); +} + +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; + timer_setup(&ep->timer, pio_out_timer, 0); + } + 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); + strscpy(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; + + switch (type) { + case USB_ENDPOINT_XFER_CONTROL: + ep->ep.caps.type_control = true; + ep->ep.caps.dir_in = true; + ep->ep.caps.dir_out = true; + break; + case USB_ENDPOINT_XFER_ISOC: + ep->ep.caps.type_iso = true; + break; + case USB_ENDPOINT_XFER_BULK: + ep->ep.caps.type_bulk = true; + break; + case USB_ENDPOINT_XFER_INT: + ep->ep.caps.type_int = true; + break; + } + + if (addr & USB_DIR_IN) + ep->ep.caps.dir_in = true; + else + ep->ep.caps.dir_out = true; + + ep->ep.name = ep->name; + ep->ep.ops = &omap_ep_ops; + ep->maxpacket = maxp; + usb_ep_set_maxpacket_limit(&ep->ep, ep->maxpacket); + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + + return buf; +} + +static void omap_udc_release(struct device *dev) +{ + 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(); + if (udc->dc_clk) { + if (udc->clk_requested) + omap_udc_enable_clock(0); + clk_unprepare(udc->hhc_clk); + clk_unprepare(udc->dc_clk); + clk_put(udc->hhc_clk); + clk_put(udc->dc_clk); + } + if (udc->done) + complete(udc->done); + kfree(udc); +} + +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_obj(*udc); + 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->gadget.quirk_ep_out_aligned_size = 1; + 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 = dev_get_platdata(&pdev->dev); + struct clk *dc_clk = NULL; + struct clk *hhc_clk = NULL; + + /* NOTE: "knows" the order of the resources! */ + if (!request_mem_region(pdev->resource[0].start, + resource_size(&pdev->resource[0]), + 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_prepare_enable(dc_clk); + clk_prepare_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; + } + fallthrough; + 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; + fallthrough; + 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 IS_ENABLED(CONFIG_USB_OHCI_HCD) + 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 = devm_request_irq(&pdev->dev, 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 = devm_request_irq(&pdev->dev, 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 cleanup1; + } +#ifdef USE_ISO + status = devm_request_irq(&pdev->dev, 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 cleanup1; + } +#endif + if (cpu_is_omap16xx()) { + udc->dc_clk = dc_clk; + udc->hhc_clk = hhc_clk; + clk_disable(hhc_clk); + clk_disable(dc_clk); + } + + create_proc_file(); + return usb_add_gadget_udc_release(&pdev->dev, &udc->gadget, + omap_udc_release); + +cleanup1: + kfree(udc); + udc = NULL; + +cleanup0: + if (!IS_ERR_OR_NULL(xceiv)) + usb_put_phy(xceiv); + + if (cpu_is_omap16xx()) { + clk_disable_unprepare(hhc_clk); + clk_disable_unprepare(dc_clk); + clk_put(hhc_clk); + clk_put(dc_clk); + } + + release_mem_region(pdev->resource[0].start, + resource_size(&pdev->resource[0])); + + return status; +} + +static void omap_udc_remove(struct platform_device *pdev) +{ + DECLARE_COMPLETION_ONSTACK(done); + + udc->done = &done; + + usb_del_gadget_udc(&udc->gadget); + + wait_for_completion(&done); + + release_mem_region(pdev->resource[0].start, + resource_size(&pdev->resource[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 = { + .name = driver_name, + }, +}; + +module_platform_driver(udc_driver); + +MODULE_DESCRIPTION("OMAP UDC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:omap_udc"); diff --git a/drivers/usb/gadget/udc/omap_udc.h b/drivers/usb/gadget/udc/omap_udc.h new file mode 100644 index 000000000..00f9e608e --- /dev/null +++ b/drivers/usb/gadget/udc/omap_udc.h @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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_warn("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/udc/pch_udc.c b/drivers/usb/gadget/udc/pch_udc.c new file mode 100644 index 000000000..99b3ce282 --- /dev/null +++ b/drivers/usb/gadget/udc/pch_udc.c @@ -0,0 +1,3161 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2011 LAPIS Semiconductor Co., Ltd. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * @request: Control Request + */ +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 + * @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 descriptor for the VBUS GPIO + * @intr: gpio interrupt number + * @irq_work_fall: Structure for WorkQueue + * @irq_work_rise: Structure for WorkQueue + */ +struct pch_vbus_gpio_data { + struct gpio_desc *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 + * @stall: stall requested + * @prot_stall: protcol stall requested + * @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 + * @setup_data: Received setup data + * @base_addr: for mapped device memory + * @bar: PCI BAR used for mapped device memory + * @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 + stall:1, + prot_stall:1, + suspended:1, + connected:1, + vbus_session:1, + set_cfg_not_acked:1, + waiting_zlp_ack:1; + struct dma_pool *data_requests; + struct dma_pool *stp_requests; + dma_addr_t dma_addr; + struct usb_ctrlrequest setup_data; + void __iomem *base_addr; + unsigned short bar; + 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_QUARK_X1000 0 +#define PCH_UDC_PCI_BAR 1 + +#define PCI_DEVICE_ID_INTEL_QUARK_X1000_UDC 0x0939 +#define PCI_DEVICE_ID_INTEL_EG20T_UDC 0x8808 + +#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_done: DMA completed for request + * @chain_len: chain length + */ +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_done:1; + unsigned chain_len; +}; + +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 + * @ep: end-point number + */ +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 + * @ep: end-point number + * + * 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); +} + +static void pch_udc_init(struct pch_udc_dev *dev); + +/** + * 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_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) +{ + unsigned long iflags; + + spin_lock_irqsave(&dev->lock, iflags); + if (is_active) { + pch_udc_reconnect(dev); + dev->vbus_session = 1; + } else { + if (dev->driver && dev->driver->disconnect) { + spin_unlock_irqrestore(&dev->lock, iflags); + dev->driver->disconnect(&dev->gadget); + spin_lock_irqsave(&dev->lock, iflags); + } + pch_udc_set_disconnect(dev); + dev->vbus_session = 0; + } + spin_unlock_irqrestore(&dev->lock, iflags); +} + +/** + * 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 + * @ep_in: EP is IN + */ +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 + * @ep: reference to structure of type pch_udc_ep_regs + * @cfg: current configuration information + * @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 | + (usb_endpoint_type(desc) << + 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 + * @ep: reference to structure of type 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. + * @ep: reference to structure of type pch_udc_ep_regs + */ +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; + gadget->is_selfpowered = (value != 0); + 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; + unsigned long iflags; + + if (!gadget) + return -EINVAL; + + dev = container_of(gadget, struct pch_udc_dev, gadget); + + spin_lock_irqsave(&dev->lock, iflags); + if (is_on) { + pch_udc_reconnect(dev); + } else { + if (dev->driver && dev->driver->disconnect) { + spin_unlock_irqrestore(&dev->lock, iflags); + dev->driver->disconnect(&dev->gadget); + spin_lock_irqsave(&dev->lock, iflags); + } + pch_udc_set_disconnect(dev); + } + spin_unlock_irqrestore(&dev->lock, iflags); + + 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); + +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 = gpiod_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 interrupt for changing VBUS + * @irq: Interrupt request number + * @data: 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 + * + * 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) +{ + struct device *d = &dev->pdev->dev; + int err; + int irq_num = 0; + struct gpio_desc *gpiod; + + dev->vbus_gpio.port = NULL; + dev->vbus_gpio.intr = 0; + + /* Retrieve the GPIO line from the USB gadget device */ + gpiod = devm_gpiod_get_optional(d, NULL, GPIOD_IN); + if (IS_ERR(gpiod)) + return PTR_ERR(gpiod); + gpiod_set_consumer_name(gpiod, "pch_vbus"); + + dev->vbus_gpio.port = gpiod; + INIT_WORK(&dev->vbus_gpio.irq_work_fall, pch_vbus_gpio_work_fall); + + irq_num = gpiod_to_irq(gpiod); + 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); +} + +/** + * 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; + usb_gadget_unmap_request(&dev->gadget, &req->req, ep->in); + ep->halted = 1; + spin_unlock(&dev->lock); + if (!ep->in) + pch_udc_ep_clear_rrdy(ep); + usb_gadget_giveback_request(&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; + dma_pool_free(dev->data_requests, td, addr); + 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: dma_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); + + td->dataptr = req->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 = dma_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; + unsigned long iflags; + + if (!usbep) + return -EINVAL; + + ep = container_of(usbep, struct pch_udc_ep, ep); + 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; + + if (!usbep) + return NULL; + ep = container_of(usbep, struct pch_udc_ep, ep); + req = kzalloc_obj(*req, gfp); + if (!req) + return NULL; + req->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 = dma_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 = lower_32_bits(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); + dma_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 */ + retval = usb_gadget_map_request(&dev->gadget, usbreq, ep->in); + if (retval) + goto probe_end; + 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; + unsigned long flags; + int ret = -EINVAL; + + ep = container_of(usbep, struct pch_udc_ep, ep); + 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; + unsigned long iflags; + int ret; + + if (!usbep) + return -EINVAL; + ep = container_of(usbep, struct pch_udc_ep, ep); + 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 + * + * Return codes: + * 0: Success + * linux error number: Failure + */ +static int pch_udc_pcd_set_wedge(struct usb_ep *usbep) +{ + struct pch_udc_ep *ep; + unsigned long iflags; + int ret; + + if (!usbep) + return -EINVAL; + ep = container_of(usbep, struct pch_udc_ep, ep); + 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); +} + +static int pch_udc_gadget_setup(struct pch_udc_dev *dev) + __must_hold(&dev->lock) +{ + int rc; + + /* In some cases we can get an interrupt before driver gets setup */ + if (!dev->driver) + return -ESHUTDOWN; + + spin_unlock(&dev->lock); + rc = dev->driver->setup(&dev->gadget, &dev->setup_data); + spin_lock(&dev->lock); + return rc; +} + +/** + * 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; + /* 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 = pch_udc_gadget_setup(dev); + + 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 = &dev->ep[UDC_EPIN_IDX(ep_num)]; + if (list_empty(&ep->queue)) + return; + 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) { + spin_unlock(&dev->lock); + usb_gadget_udc_reset(&dev->gadget, dev->driver); + 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; + + 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; + pch_udc_gadget_setup(dev); +} + +/** + * 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; + 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 */ + pch_udc_gadget_setup(dev); +} + +/** + * 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 + * @pdev: 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); +} + +/** + * 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; + ep->ep.caps.dir_in = true; + } else { + ep->offset_addr = (UDC_EPINT_OUT_SHIFT + ep->num) * + UDC_EP_REG_SHIFT; + ep->ep.caps.dir_out = true; + } + if (i == UDC_EP0IN_IDX || i == UDC_EP0OUT_IDX) { + ep->ep.caps.type_control = true; + } else { + ep->ep.caps.type_iso = true; + ep->ep.caps.type_bulk = true; + ep->ep.caps.type_int = true; + } + /* need to set ep->ep.maxpacket and set Default Configuration?*/ + usb_ep_set_maxpacket_limit(&ep->ep, UDC_BULK_MAX_PKT_SIZE); + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + INIT_LIST_HEAD(&ep->queue); + } + usb_ep_set_maxpacket_limit(&dev->ep[UDC_EP0IN_IDX].ep, UDC_EP0IN_MAX_PKT_SIZE); + usb_ep_set_maxpacket_limit(&dev->ep[UDC_EP0OUT_IDX].ep, 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 + * -ERRNO: All kind of errors when retrieving VBUS GPIO + */ +static int pch_udc_pcd_init(struct pch_udc_dev *dev) +{ + int ret; + + pch_udc_init(dev); + pch_udc_pcd_reinit(dev); + + ret = pch_vbus_gpio_init(dev); + if (ret) + pch_udc_exit(dev); + return ret; +} + +/** + * init_dma_pools() - create dma pools during initialization + * @dev: 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; + void *ep0out_buf; + + /* DMA setup */ + dev->data_requests = dma_pool_create("data_requests", &dev->pdev->dev, + 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 = dma_pool_create("setup requests", &dev->pdev->dev, + 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 = dma_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 = dma_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; + + ep0out_buf = devm_kzalloc(&dev->pdev->dev, UDC_EP0OUT_BUFF_SIZE * 4, + GFP_KERNEL); + if (!ep0out_buf) + return -ENOMEM; + dev->dma_addr = dma_map_single(&dev->pdev->dev, ep0out_buf, + UDC_EP0OUT_BUFF_SIZE * 4, + DMA_FROM_DEVICE); + return dma_mapping_error(&dev->pdev->dev, dev->dma_addr); +} + +static int pch_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct pch_udc_dev *dev = to_pch_udc(g); + + 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 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_vbus_gpio_remove_table(void *table) +{ + gpiod_remove_lookup_table(table); +} + +static int pch_vbus_gpio_add_table(struct device *d, void *table) +{ + gpiod_add_lookup_table(table); + return devm_add_action_or_reset(d, pch_vbus_gpio_remove_table, table); +} + +static struct gpiod_lookup_table pch_udc_minnow_vbus_gpio_table = { + .dev_id = "0000:02:02.4", + .table = { + GPIO_LOOKUP("sch_gpio.33158", 12, NULL, GPIO_ACTIVE_HIGH), + {} + }, +}; + +static int pch_udc_minnow_platform_init(struct device *d) +{ + return pch_vbus_gpio_add_table(d, &pch_udc_minnow_vbus_gpio_table); +} + +static int pch_udc_quark_platform_init(struct device *d) +{ + struct pch_udc_dev *dev = dev_get_drvdata(d); + + dev->bar = PCH_UDC_PCI_BAR_QUARK_X1000; + 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 */ + dma_pool_destroy(dev->data_requests); + + if (dev->stp_requests) { + /* cleanup DMA desc's for ep0in */ + if (dev->ep[UDC_EP0OUT_IDX].td_stp) { + dma_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) { + dma_pool_free(dev->stp_requests, + dev->ep[UDC_EP0OUT_IDX].td_data, + dev->ep[UDC_EP0OUT_IDX].td_data_phys); + } + dma_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); + + pch_vbus_gpio_free(dev); + + pch_udc_exit(dev); +} + +static int __maybe_unused pch_udc_suspend(struct device *d) +{ + struct pch_udc_dev *dev = dev_get_drvdata(d); + + pch_udc_disable_interrupts(dev, UDC_DEVINT_MSK); + pch_udc_disable_ep_interrupts(dev, UDC_EPINT_MSK_DISABLE_ALL); + + return 0; +} + +static int __maybe_unused pch_udc_resume(struct device *d) +{ + return 0; +} + +static SIMPLE_DEV_PM_OPS(pch_udc_pm, pch_udc_suspend, pch_udc_resume); + +typedef int (*platform_init_fn)(struct device *); + +static int pch_udc_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + platform_init_fn platform_init = (platform_init_fn)id->driver_data; + int retval; + struct pch_udc_dev *dev; + + /* init */ + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* pci setup */ + retval = pcim_enable_device(pdev); + if (retval) + return retval; + + dev->bar = PCH_UDC_PCI_BAR; + dev->pdev = pdev; + pci_set_drvdata(pdev, dev); + + /* Platform specific hook */ + if (platform_init) { + retval = platform_init(&pdev->dev); + if (retval) + return retval; + } + + /* PCI resource allocation */ + retval = pcim_iomap_regions(pdev, BIT(dev->bar), pci_name(pdev)); + if (retval) + return retval; + + dev->base_addr = pcim_iomap_table(pdev)[dev->bar]; + + /* initialize the hardware */ + retval = pch_udc_pcd_init(dev); + if (retval) + return retval; + + pci_enable_msi(pdev); + + retval = devm_request_irq(&pdev->dev, pdev->irq, pch_udc_isr, + IRQF_SHARED, KBUILD_MODNAME, dev); + if (retval) { + dev_err(&pdev->dev, "%s: request_irq(%d) fail\n", __func__, + pdev->irq); + goto finished; + } + + pci_set_master(pdev); + pci_try_set_mwi(pdev); + + /* device struct setup */ + spin_lock_init(&dev->lock); + 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(&pdev->dev, &dev->gadget); + if (retval) + goto finished; + return 0; + +finished: + pch_udc_remove(pdev); + return retval; +} + +static const struct pci_device_id pch_udc_pcidev_id[] = { + { + PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_QUARK_X1000_UDC), + .class = PCI_CLASS_SERIAL_USB_DEVICE, + .class_mask = 0xffffffff, + .driver_data = (kernel_ulong_t)&pch_udc_quark_platform_init, + }, + { + PCI_DEVICE_SUB(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_EG20T_UDC, + PCI_VENDOR_ID_CIRCUITCO, PCI_SUBSYSTEM_ID_CIRCUITCO_MINNOWBOARD), + .class = PCI_CLASS_SERIAL_USB_DEVICE, + .class_mask = 0xffffffff, + .driver_data = (kernel_ulong_t)&pch_udc_minnow_platform_init, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_EG20T_UDC), + .class = PCI_CLASS_SERIAL_USB_DEVICE, + .class_mask = 0xffffffff, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_ROHM, PCI_DEVICE_ID_ML7213_IOH_UDC), + .class = PCI_CLASS_SERIAL_USB_DEVICE, + .class_mask = 0xffffffff, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_ROHM, PCI_DEVICE_ID_ML7831_IOH_UDC), + .class = PCI_CLASS_SERIAL_USB_DEVICE, + .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, + .shutdown = pch_udc_shutdown, + .driver = { + .pm = &pch_udc_pm, + }, +}; + +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/udc/pxa25x_udc.c b/drivers/usb/gadget/udc/pxa25x_udc.c new file mode 100644 index 000000000..594d67193 --- /dev/null +++ b/drivers/usb/gadget/udc/pxa25x_udc.c @@ -0,0 +1,2474 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +/* #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 + +#define UDCCR 0x0000 /* UDC Control Register */ +#define UDC_RES1 0x0004 /* UDC Undocumented - Reserved1 */ +#define UDC_RES2 0x0008 /* UDC Undocumented - Reserved2 */ +#define UDC_RES3 0x000C /* UDC Undocumented - Reserved3 */ +#define UDCCS0 0x0010 /* UDC Endpoint 0 Control/Status Register */ +#define UDCCS1 0x0014 /* UDC Endpoint 1 (IN) Control/Status Register */ +#define UDCCS2 0x0018 /* UDC Endpoint 2 (OUT) Control/Status Register */ +#define UDCCS3 0x001C /* UDC Endpoint 3 (IN) Control/Status Register */ +#define UDCCS4 0x0020 /* UDC Endpoint 4 (OUT) Control/Status Register */ +#define UDCCS5 0x0024 /* UDC Endpoint 5 (Interrupt) Control/Status Register */ +#define UDCCS6 0x0028 /* UDC Endpoint 6 (IN) Control/Status Register */ +#define UDCCS7 0x002C /* UDC Endpoint 7 (OUT) Control/Status Register */ +#define UDCCS8 0x0030 /* UDC Endpoint 8 (IN) Control/Status Register */ +#define UDCCS9 0x0034 /* UDC Endpoint 9 (OUT) Control/Status Register */ +#define UDCCS10 0x0038 /* UDC Endpoint 10 (Interrupt) Control/Status Register */ +#define UDCCS11 0x003C /* UDC Endpoint 11 (IN) Control/Status Register */ +#define UDCCS12 0x0040 /* UDC Endpoint 12 (OUT) Control/Status Register */ +#define UDCCS13 0x0044 /* UDC Endpoint 13 (IN) Control/Status Register */ +#define UDCCS14 0x0048 /* UDC Endpoint 14 (OUT) Control/Status Register */ +#define UDCCS15 0x004C /* UDC Endpoint 15 (Interrupt) Control/Status Register */ +#define UFNRH 0x0060 /* UDC Frame Number Register High */ +#define UFNRL 0x0064 /* UDC Frame Number Register Low */ +#define UBCR2 0x0068 /* UDC Byte Count Reg 2 */ +#define UBCR4 0x006c /* UDC Byte Count Reg 4 */ +#define UBCR7 0x0070 /* UDC Byte Count Reg 7 */ +#define UBCR9 0x0074 /* UDC Byte Count Reg 9 */ +#define UBCR12 0x0078 /* UDC Byte Count Reg 12 */ +#define UBCR14 0x007c /* UDC Byte Count Reg 14 */ +#define UDDR0 0x0080 /* UDC Endpoint 0 Data Register */ +#define UDDR1 0x0100 /* UDC Endpoint 1 Data Register */ +#define UDDR2 0x0180 /* UDC Endpoint 2 Data Register */ +#define UDDR3 0x0200 /* UDC Endpoint 3 Data Register */ +#define UDDR4 0x0400 /* UDC Endpoint 4 Data Register */ +#define UDDR5 0x00A0 /* UDC Endpoint 5 Data Register */ +#define UDDR6 0x0600 /* UDC Endpoint 6 Data Register */ +#define UDDR7 0x0680 /* UDC Endpoint 7 Data Register */ +#define UDDR8 0x0700 /* UDC Endpoint 8 Data Register */ +#define UDDR9 0x0900 /* UDC Endpoint 9 Data Register */ +#define UDDR10 0x00C0 /* UDC Endpoint 10 Data Register */ +#define UDDR11 0x0B00 /* UDC Endpoint 11 Data Register */ +#define UDDR12 0x0B80 /* UDC Endpoint 12 Data Register */ +#define UDDR13 0x0C00 /* UDC Endpoint 13 Data Register */ +#define UDDR14 0x0E00 /* UDC Endpoint 14 Data Register */ +#define UDDR15 0x00E0 /* UDC Endpoint 15 Data Register */ + +#define UICR0 0x0050 /* UDC Interrupt Control Register 0 */ +#define UICR1 0x0054 /* UDC Interrupt Control Register 1 */ + +#define USIR0 0x0058 /* UDC Status Interrupt Register 0 */ +#define USIR1 0x005C /* UDC Status Interrupt Register 1 */ + +#define UDCCR_UDE (1 << 0) /* UDC enable */ +#define UDCCR_UDA (1 << 1) /* UDC active */ +#define UDCCR_RSM (1 << 2) /* Device resume */ +#define UDCCR_RESIR (1 << 3) /* Resume interrupt request */ +#define UDCCR_SUSIR (1 << 4) /* Suspend interrupt request */ +#define UDCCR_SRM (1 << 5) /* Suspend/resume interrupt mask */ +#define UDCCR_RSTIR (1 << 6) /* Reset interrupt request */ +#define UDCCR_REM (1 << 7) /* Reset interrupt mask */ + +#define UDCCS0_OPR (1 << 0) /* OUT packet ready */ +#define UDCCS0_IPR (1 << 1) /* IN packet ready */ +#define UDCCS0_FTF (1 << 2) /* Flush Tx FIFO */ +#define UDCCS0_DRWF (1 << 3) /* Device remote wakeup feature */ +#define UDCCS0_SST (1 << 4) /* Sent stall */ +#define UDCCS0_FST (1 << 5) /* Force stall */ +#define UDCCS0_RNE (1 << 6) /* Receive FIFO no empty */ +#define UDCCS0_SA (1 << 7) /* Setup active */ + +#define UDCCS_BI_TFS (1 << 0) /* Transmit FIFO service */ +#define UDCCS_BI_TPC (1 << 1) /* Transmit packet complete */ +#define UDCCS_BI_FTF (1 << 2) /* Flush Tx FIFO */ +#define UDCCS_BI_TUR (1 << 3) /* Transmit FIFO underrun */ +#define UDCCS_BI_SST (1 << 4) /* Sent stall */ +#define UDCCS_BI_FST (1 << 5) /* Force stall */ +#define UDCCS_BI_TSP (1 << 7) /* Transmit short packet */ + +#define UDCCS_BO_RFS (1 << 0) /* Receive FIFO service */ +#define UDCCS_BO_RPC (1 << 1) /* Receive packet complete */ +#define UDCCS_BO_DME (1 << 3) /* DMA enable */ +#define UDCCS_BO_SST (1 << 4) /* Sent stall */ +#define UDCCS_BO_FST (1 << 5) /* Force stall */ +#define UDCCS_BO_RNE (1 << 6) /* Receive FIFO not empty */ +#define UDCCS_BO_RSP (1 << 7) /* Receive short packet */ + +#define UDCCS_II_TFS (1 << 0) /* Transmit FIFO service */ +#define UDCCS_II_TPC (1 << 1) /* Transmit packet complete */ +#define UDCCS_II_FTF (1 << 2) /* Flush Tx FIFO */ +#define UDCCS_II_TUR (1 << 3) /* Transmit FIFO underrun */ +#define UDCCS_II_TSP (1 << 7) /* Transmit short packet */ + +#define UDCCS_IO_RFS (1 << 0) /* Receive FIFO service */ +#define UDCCS_IO_RPC (1 << 1) /* Receive packet complete */ +#ifdef CONFIG_ARCH_IXP4XX /* FIXME: is this right?, datasheed says '2' */ +#define UDCCS_IO_ROF (1 << 3) /* Receive overflow */ +#endif +#ifdef CONFIG_ARCH_PXA +#define UDCCS_IO_ROF (1 << 2) /* Receive overflow */ +#endif +#define UDCCS_IO_DME (1 << 3) /* DMA enable */ +#define UDCCS_IO_RNE (1 << 6) /* Receive FIFO not empty */ +#define UDCCS_IO_RSP (1 << 7) /* Receive short packet */ + +#define UDCCS_INT_TFS (1 << 0) /* Transmit FIFO service */ +#define UDCCS_INT_TPC (1 << 1) /* Transmit packet complete */ +#define UDCCS_INT_FTF (1 << 2) /* Flush Tx FIFO */ +#define UDCCS_INT_TUR (1 << 3) /* Transmit FIFO underrun */ +#define UDCCS_INT_SST (1 << 4) /* Sent stall */ +#define UDCCS_INT_FST (1 << 5) /* Force stall */ +#define UDCCS_INT_TSP (1 << 7) /* Transmit short packet */ + +#define UICR0_IM0 (1 << 0) /* Interrupt mask ep 0 */ +#define UICR0_IM1 (1 << 1) /* Interrupt mask ep 1 */ +#define UICR0_IM2 (1 << 2) /* Interrupt mask ep 2 */ +#define UICR0_IM3 (1 << 3) /* Interrupt mask ep 3 */ +#define UICR0_IM4 (1 << 4) /* Interrupt mask ep 4 */ +#define UICR0_IM5 (1 << 5) /* Interrupt mask ep 5 */ +#define UICR0_IM6 (1 << 6) /* Interrupt mask ep 6 */ +#define UICR0_IM7 (1 << 7) /* Interrupt mask ep 7 */ + +#define UICR1_IM8 (1 << 0) /* Interrupt mask ep 8 */ +#define UICR1_IM9 (1 << 1) /* Interrupt mask ep 9 */ +#define UICR1_IM10 (1 << 2) /* Interrupt mask ep 10 */ +#define UICR1_IM11 (1 << 3) /* Interrupt mask ep 11 */ +#define UICR1_IM12 (1 << 4) /* Interrupt mask ep 12 */ +#define UICR1_IM13 (1 << 5) /* Interrupt mask ep 13 */ +#define UICR1_IM14 (1 << 6) /* Interrupt mask ep 14 */ +#define UICR1_IM15 (1 << 7) /* Interrupt mask ep 15 */ + +#define USIR0_IR0 (1 << 0) /* Interrupt request ep 0 */ +#define USIR0_IR1 (1 << 1) /* Interrupt request ep 1 */ +#define USIR0_IR2 (1 << 2) /* Interrupt request ep 2 */ +#define USIR0_IR3 (1 << 3) /* Interrupt request ep 3 */ +#define USIR0_IR4 (1 << 4) /* Interrupt request ep 4 */ +#define USIR0_IR5 (1 << 5) /* Interrupt request ep 5 */ +#define USIR0_IR6 (1 << 6) /* Interrupt request ep 6 */ +#define USIR0_IR7 (1 << 7) /* Interrupt request ep 7 */ + +#define USIR1_IR8 (1 << 0) /* Interrupt request ep 8 */ +#define USIR1_IR9 (1 << 1) /* Interrupt request ep 9 */ +#define USIR1_IR10 (1 << 2) /* Interrupt request ep 10 */ +#define USIR1_IR11 (1 << 3) /* Interrupt request ep 11 */ +#define USIR1_IR12 (1 << 4) /* Interrupt request ep 12 */ +#define USIR1_IR13 (1 << 5) /* Interrupt request ep 13 */ +#define USIR1_IR14 (1 << 6) /* Interrupt request ep 14 */ +#define USIR1_IR15 (1 << 7) /* Interrupt request ep 15 */ + +/* + * 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) +{ + gpiod_set_value(the_controller->pullup_gpio, 0); +} + +static void pullup_on(void) +{ + gpiod_set_value(the_controller->pullup_gpio, 1); +} + +#if defined(CONFIG_CPU_BIG_ENDIAN) +/* + * IXP4xx has its buses wired up in a way that relies on never doing any + * byte swaps, independent of whether it runs in big-endian or little-endian + * mode, as explained by Krzysztof Hałasa. + * + * We only support pxa25x in little-endian mode, but it is very likely + * that it works the same way. + */ +static inline void udc_set_reg(struct pxa25x_udc *dev, u32 reg, u32 val) +{ + iowrite32be(val, dev->regs + reg); +} + +static inline u32 udc_get_reg(struct pxa25x_udc *dev, u32 reg) +{ + return ioread32be(dev->regs + reg); +} +#else +static inline void udc_set_reg(struct pxa25x_udc *dev, u32 reg, u32 val) +{ + writel(val, dev->regs + reg); +} + +static inline u32 udc_get_reg(struct pxa25x_udc *dev, u32 reg) +{ + return readl(dev->regs + reg); +} +#endif + +static void pio_irq_enable(struct pxa25x_ep *ep) +{ + u32 bEndpointAddress = ep->bEndpointAddress & 0xf; + + if (bEndpointAddress < 8) + udc_set_reg(ep->dev, UICR0, udc_get_reg(ep->dev, UICR0) & + ~(1 << bEndpointAddress)); + else { + bEndpointAddress -= 8; + udc_set_reg(ep->dev, UICR1, udc_get_reg(ep->dev, UICR1) & + ~(1 << bEndpointAddress)); + } +} + +static void pio_irq_disable(struct pxa25x_ep *ep) +{ + u32 bEndpointAddress = ep->bEndpointAddress & 0xf; + + if (bEndpointAddress < 8) + udc_set_reg(ep->dev, UICR0, udc_get_reg(ep->dev, UICR0) | + (1 << bEndpointAddress)); + else { + bEndpointAddress -= 8; + udc_set_reg(ep->dev, UICR1, udc_get_reg(ep->dev, 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(struct pxa25x_udc *dev, int mask) +{ + u32 udccr = udc_get_reg(dev, UDCCR); + + udc_set_reg(dev, (udccr & UDCCR_MASK_BITS) | (mask & UDCCR_MASK_BITS), UDCCR); +} + +static inline void udc_clear_mask_UDCCR(struct pxa25x_udc *dev, int mask) +{ + u32 udccr = udc_get_reg(dev, UDCCR); + + udc_set_reg(dev, (udccr & UDCCR_MASK_BITS) & ~(mask & UDCCR_MASK_BITS), UDCCR); +} + +static inline void udc_ack_int_UDCCR(struct pxa25x_udc *dev, int mask) +{ + /* udccr contains the bits we dont want to change */ + u32 udccr = udc_get_reg(dev, UDCCR) & UDCCR_MASK_BITS; + + udc_set_reg(dev, udccr | (mask & ~UDCCR_MASK_BITS), UDCCR); +} + +static inline u32 udc_ep_get_UDCCS(struct pxa25x_ep *ep) +{ + return udc_get_reg(ep->dev, ep->regoff_udccs); +} + +static inline void udc_ep_set_UDCCS(struct pxa25x_ep *ep, u32 data) +{ + udc_set_reg(ep->dev, data, ep->regoff_udccs); +} + +static inline u32 udc_ep0_get_UDCCS(struct pxa25x_udc *dev) +{ + return udc_get_reg(dev, UDCCS0); +} + +static inline void udc_ep0_set_UDCCS(struct pxa25x_udc *dev, u32 data) +{ + udc_set_reg(dev, data, UDCCS0); +} + +static inline u32 udc_ep_get_UDDR(struct pxa25x_ep *ep) +{ + return udc_get_reg(ep->dev, ep->regoff_uddr); +} + +static inline void udc_ep_set_UDDR(struct pxa25x_ep *ep, u32 data) +{ + udc_set_reg(ep->dev, data, ep->regoff_uddr); +} + +static inline u32 udc_ep_get_UBCR(struct pxa25x_ep *ep) +{ + return udc_get_reg(ep->dev, ep->regoff_ubcr); +} + +/* + * 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_obj(*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; + usb_gadget_giveback_request(&ep->ep, &req->req); + ep->stopped = stopped; +} + + +static inline void ep0_idle (struct pxa25x_udc *dev) +{ + dev->ep0state = EP0_IDLE; +} + +static int +write_packet(struct pxa25x_ep *ep, 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--)) + udc_ep_set_UDDR(ep, *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, 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. + */ + udc_ep_set_UDCCS(ep, UDCCS_BI_TPC); + if (is_short) + udc_ep_set_UDCCS(ep, 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); + 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 (udc_ep_get_UDCCS(ep) & 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) +{ + udc_ep0_set_UDCCS(dev, flags|UDCCS0_SA|UDCCS0_OPR); + udc_set_reg(dev, USIR0, USIR0_IR0); + dev->req_pending = 0; + DBG(DBG_VERY_NOISY, "%s %s, %02x/%02x\n", + __func__, tag, udc_ep0_get_UDCCS(dev), flags); +} + +static int +write_ep0_fifo (struct pxa25x_ep *ep, struct pxa25x_request *req) +{ + struct pxa25x_udc *dev = ep->dev; + unsigned count; + int is_short; + + count = write_packet(&dev->ep[0], 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 + udc_ep0_set_UDCCS(dev, 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 ((udc_ep0_get_UDCCS(dev) & UDCCS0_OPR) != 0) { + /* clear OPR, generate ack */ + udc_ep0_set_UDCCS(dev, 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 = udc_ep_get_UDCCS(ep); + 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 & udc_ep_get_UBCR(ep)); + 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) udc_ep_get_UDDR(ep); + + 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--; + } + } + udc_ep_set_UDCCS(ep, 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); + 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 (udc_ep_get_UDCCS(ep) & 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--; + } + } + + udc_ep_set_UDCCS(ep, 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) + udc_set_reg(dev, 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 || ((udc_ep0_get_UDCCS(dev) & 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 ((udc_ep_get_UDCCS(ep) & UDCCS_BI_TFS) != 0 + && write_fifo(ep, req)) + req = NULL; + } else if ((udc_ep_get_UDCCS(ep) & UDCCS_BO_RFS) != 0 + && read_fifo(ep, req)) { + req = NULL; + } + + if (likely(req && ep->ep.desc)) + pio_irq_enable(ep); + } + + /* 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); +} + + +/* 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 = NULL; + struct pxa25x_request *iter; + 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(iter, &ep->queue, queue) { + if (&iter->req != _req) + continue; + req = iter; + break; + } + if (!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 + && ((udc_ep_get_UDCCS(ep) & 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 */ + udc_ep_set_UDCCS(ep, 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 (udc_ep_get_UDCCS(ep) & 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 + || (udc_ep_get_UDCCS(ep) & UDCCS_BO_RFS) == 0) + return 0; + else + return (udc_ep_get_UBCR(ep) & 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 (((udc_ep_get_UDCCS(ep)) & UDCCS_BO_RNE) != 0) + (void)udc_ep_get_UDDR(ep); + return; + } + + /* most IN status is the same, but ISO can't stall */ + udc_ep_set_UDCCS(ep, UDCCS_BI_TPC|UDCCS_BI_FTF|UDCCS_BI_TUR + | (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC + ? 0 : UDCCS_BI_SST)); +} + + +static const 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) +{ + struct pxa25x_udc *dev; + + dev = container_of(_gadget, struct pxa25x_udc, gadget); + return ((udc_get_reg(dev, UFNRH) & 0x07) << 8) | + (udc_get_reg(dev, UFNRL) & 0xff); +} + +static int pxa25x_udc_wakeup(struct usb_gadget *_gadget) +{ + struct pxa25x_udc *udc; + + udc = container_of(_gadget, struct pxa25x_udc, gadget); + + /* host may not have enabled remote wakeup */ + if ((udc_ep0_get_UDCCS(udc) & UDCCS0_DRWF) == 0) + return -EHOSTUNREACH; + udc_set_mask_UDCCR(udc, 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); + + if (!udc->pullup_gpio) + 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); + +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_debug_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", + udc_get_reg(dev, UICR1), udc_get_reg(dev, UICR0), + udc_get_reg(dev, USIR1), udc_get_reg(dev, USIR0), + udc_get_reg(dev, UFNRH), udc_get_reg(dev, UFNRL)); + + tmp = udc_get_reg(dev, 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 = udc_ep0_get_UDCCS(dev); + 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 = udc_get_reg(dev, 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 = udc_ep_get_UDCCS(&dev->ep[i]); + 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; +} +DEFINE_SHOW_ATTRIBUTE(udc_debug); + +#define create_debug_files(dev) \ + do { \ + debugfs_create_file(dev->gadget.name, \ + S_IRUGO, NULL, dev, &udc_debug_fops); \ + } while (0) +#define remove_debug_files(dev) debugfs_lookup_and_remove(dev->gadget.name, NULL) + +#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(dev, UDCCR_SRM|UDCCR_REM); + udc_set_reg(dev, UICR0, 0xff); + udc_set_reg(dev, UICR1, 0xff); + udc_set_reg(dev, UFNRH, UFNRH_SIM); + + /* if hardware supports it, disconnect from usb */ + pullup_off(); + + udc_clear_mask_UDCCR(dev, 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; + dev->gadget.quirk_altset_not_supp = 1; + + /* 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; + usb_ep_set_maxpacket_limit(&ep->ep, ep->ep.maxpacket); + } + + /* 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(dev, UDCCR_UDE); + + /* try to clear these bits before we enable the udc */ + udc_ack_int_UDCCR(dev, 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(dev, UDCCR_UDE); + if (!(udc_get_reg(dev, UDCCR) & UDCCR_UDA)) + udc_ack_int_UDCCR(dev, 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 + */ + udc_set_reg(dev, 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_set_reg(dev, UDC_RES1, 0x00); + udc_set_reg(dev, UDC_RES2, 0x00); + } + + /* enable suspend/resume and reset irqs */ + udc_clear_mask_UDCCR(dev, UDCCR_SRM | UDCCR_REM); + + /* enable ep0 irqs */ + udc_set_reg(dev, UICR0, udc_get_reg(dev, 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; + } + + dump_state(dev); + return 0; +bind_fail: + return retval; +} + +static void +reset_gadget(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); + } + timer_delete_sync(&dev->timer); + + /* report reset; the driver is already quiesced */ + if (driver) + usb_gadget_udc_reset(&dev->gadget, driver); + + /* re-init driver-visible data structures */ + udc_reinit(dev); +} + +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); + } + timer_delete_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 pxa25x_udc *dev = to_pxa25x(g); + + local_irq_disable(); + dev->pullup = 0; + stop_activity(dev, NULL); + 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; +} + +/*-------------------------------------------------------------------------*/ + +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(struct timer_list *t) +{ + struct pxa25x_udc *dev = timer_container_of(dev, t, timer); + + local_irq_disable(); + if (dev->ep0state == EP0_STALL + && (udc_ep0_get_UDCCS(dev) & UDCCS0_FST) == 0 + && (udc_ep0_get_UDCCS(dev) & UDCCS0_SST) == 0) { + udc_ep0_set_UDCCS(dev, 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 = udc_ep0_get_UDCCS(dev); + 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); + udc_ep0_set_UDCCS(dev, UDCCS0_SST); + timer_delete(&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); + timer_delete(&dev->timer); + ep0_idle(dev); + } + + switch (dev->ep0state) { + case EP0_IDLE: + /* late-breaking status? */ + udccs0 = udc_ep0_get_UDCCS(dev); + + /* 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(!(udc_ep0_get_UDCCS(dev) & UDCCS0_RNE))) { +bad_setup: + DMSG("SETUP %d!\n", i); + goto stall; + } + u.raw [i] = (u8) UDDR0; + } + if (unlikely((udc_ep0_get_UDCCS(dev) & 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", udc_ep0_get_UDCCS(dev), 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) + */ + udc_ep0_set_UDCCS(dev, udccs0 & (UDCCS0_SA|UDCCS0_OPR)); + } + break; + case EP0_IN_DATA_PHASE: /* GET_DESCRIPTOR etc */ + if (udccs0 & UDCCS0_OPR) { + udc_ep0_set_UDCCS(dev, 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) + udc_ep0_set_UDCCS(dev, UDCCS0_OPR); + ep0_idle(dev); + break; + case EP0_STALL: + udc_ep0_set_UDCCS(dev, UDCCS0_FST); + break; + } + udc_set_reg(dev, 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 = udc_ep_get_UDCCS(ep); + 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)) + udc_ep_set_UDCCS(ep, 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)) + udc_ep_set_UDCCS(ep, tmp); + + /* fifos can hold packets, ready for reading... */ + if (likely(req)) { + completed = read_fifo(ep, req); + } else + pio_irq_disable(ep); + } + 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 = udc_get_reg(dev, UDCCR); + + handled = 0; + + /* SUSpend Interrupt Request */ + if (unlikely(udccr & UDCCR_SUSIR)) { + udc_ack_int_UDCCR(dev, 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(dev, 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(dev, UDCCR_RSTIR); + handled = 1; + + if ((udc_get_reg(dev, UDCCR) & UDCCR_UDA) == 0) { + DBG(DBG_VERBOSE, "USB reset start\n"); + + /* reset driver and endpoints, + * in case that's not yet done + */ + reset_gadget(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 = udc_get_reg(dev, USIR0) & + ~udc_get_reg(dev, UICR0); + u32 usir1 = udc_get_reg(dev, USIR1) & + ~udc_get_reg(dev, 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]); + udc_set_reg(dev, USIR0, + udc_get_reg(dev, USIR0) | tmp); + handled = 1; + } +#ifndef CONFIG_USB_PXA25X_SMALL + if (usir1 & tmp) { + handle_ep(&dev->ep[i+8]); + udc_set_reg(dev, USIR1, + udc_get_reg(dev, 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, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, + USB_EP_CAPS_DIR_ALL), + }, + .dev = &memory, + .regoff_udccs = UDCCS0, + .regoff_uddr = UDDR0, + }, + + /* first group of endpoints */ + .ep[1] = { + .ep = { + .name = "ep1in-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_IN), + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 1, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .regoff_udccs = UDCCS1, + .regoff_uddr = UDDR1, + }, + .ep[2] = { + .ep = { + .name = "ep2out-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_OUT), + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = 2, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .regoff_udccs = UDCCS2, + .regoff_ubcr = UBCR2, + .regoff_uddr = UDDR2, + }, +#ifndef CONFIG_USB_PXA25X_SMALL + .ep[3] = { + .ep = { + .name = "ep3in-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_IN), + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 3, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .regoff_udccs = UDCCS3, + .regoff_uddr = UDDR3, + }, + .ep[4] = { + .ep = { + .name = "ep4out-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_OUT), + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = 4, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .regoff_udccs = UDCCS4, + .regoff_ubcr = UBCR4, + .regoff_uddr = UDDR4, + }, + .ep[5] = { + .ep = { + .name = "ep5in-int", + .ops = &pxa25x_ep_ops, + .maxpacket = INT_FIFO_SIZE, + .caps = USB_EP_CAPS(0, 0), + }, + .dev = &memory, + .fifo_size = INT_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 5, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .regoff_udccs = UDCCS5, + .regoff_uddr = UDDR5, + }, + + /* second group of endpoints */ + .ep[6] = { + .ep = { + .name = "ep6in-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_IN), + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 6, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .regoff_udccs = UDCCS6, + .regoff_uddr = UDDR6, + }, + .ep[7] = { + .ep = { + .name = "ep7out-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_OUT), + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = 7, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .regoff_udccs = UDCCS7, + .regoff_ubcr = UBCR7, + .regoff_uddr = UDDR7, + }, + .ep[8] = { + .ep = { + .name = "ep8in-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_IN), + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 8, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .regoff_udccs = UDCCS8, + .regoff_uddr = UDDR8, + }, + .ep[9] = { + .ep = { + .name = "ep9out-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_OUT), + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = 9, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .regoff_udccs = UDCCS9, + .regoff_ubcr = UBCR9, + .regoff_uddr = UDDR9, + }, + .ep[10] = { + .ep = { + .name = "ep10in-int", + .ops = &pxa25x_ep_ops, + .maxpacket = INT_FIFO_SIZE, + .caps = USB_EP_CAPS(0, 0), + }, + .dev = &memory, + .fifo_size = INT_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 10, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .regoff_udccs = UDCCS10, + .regoff_uddr = UDDR10, + }, + + /* third group of endpoints */ + .ep[11] = { + .ep = { + .name = "ep11in-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_IN), + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 11, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .regoff_udccs = UDCCS11, + .regoff_uddr = UDDR11, + }, + .ep[12] = { + .ep = { + .name = "ep12out-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_OUT), + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = 12, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .regoff_udccs = UDCCS12, + .regoff_ubcr = UBCR12, + .regoff_uddr = UDDR12, + }, + .ep[13] = { + .ep = { + .name = "ep13in-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_IN), + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 13, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .regoff_udccs = UDCCS13, + .regoff_uddr = UDDR13, + }, + .ep[14] = { + .ep = { + .name = "ep14out-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + .caps = USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_OUT), + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = 14, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .regoff_udccs = UDCCS14, + .regoff_ubcr = UBCR14, + .regoff_uddr = UDDR14, + }, + .ep[15] = { + .ep = { + .name = "ep15in-int", + .ops = &pxa25x_ep_ops, + .maxpacket = INT_FIFO_SIZE, + .caps = USB_EP_CAPS(0, 0), + }, + .dev = &memory, + .fifo_size = INT_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 15, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .regoff_udccs = UDCCS15, + .regoff_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 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 */ + fallthrough; + case PXA250_B2: case PXA210_B2: + case PXA250_B1: case PXA210_B1: + case PXA250_B0: case PXA210_B0: + /* OUT-DMA is broken ... */ + fallthrough; + 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 irq; + + dev->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dev->regs)) + return PTR_ERR(dev->regs); + + dev->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dev->clk)) + return PTR_ERR(dev->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->transceiver = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); + + dev->pullup_gpio = devm_gpiod_get_index_optional(&pdev->dev, "pullup", 0, + GPIOD_OUT_HIGH); + if (IS_ERR(dev->pullup_gpio)) { + dev_dbg(&pdev->dev, + "can't get pullup gpio err: %ld\n", + PTR_ERR(dev->pullup_gpio)); + retval = PTR_ERR(dev->pullup_gpio); + goto err; + } + + timer_setup(&dev->timer, udc_watchdog, 0); + + 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 = devm_request_irq(&pdev->dev, 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; + } + dev->got_irq = 1; + + create_debug_files(dev); + + retval = usb_add_gadget_udc(&pdev->dev, &dev->gadget); + if (!retval) + return retval; + + remove_debug_files(dev); + err: + if (!IS_ERR_OR_NULL(dev->transceiver)) + dev->transceiver = NULL; + return retval; +} + +static void pxa25x_udc_shutdown(struct platform_device *_dev) +{ + pullup_off(); +} + +static void pxa25x_udc_remove(struct platform_device *pdev) +{ + struct pxa25x_udc *dev = platform_get_drvdata(pdev); + + if (dev->driver) { + dev_err(&pdev->dev, + "Driver still in use but removing anyhow\n"); + return; + } + + usb_del_gadget_udc(&dev->gadget); + dev->pullup = 0; + pullup(dev); + + remove_debug_files(dev); + + if (!IS_ERR_OR_NULL(dev->transceiver)) + dev->transceiver = NULL; + + the_controller = NULL; +} + +/*-------------------------------------------------------------------------*/ + +#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 (!udc->pullup_gpio) + 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, + .probe = pxa25x_udc_probe, + .remove = pxa25x_udc_remove, + .suspend = pxa25x_udc_suspend, + .resume = pxa25x_udc_resume, + .driver = { + .name = "pxa25x-udc", + }, +}; + +module_platform_driver(udc_driver); + +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/udc/pxa25x_udc.h b/drivers/usb/gadget/udc/pxa25x_udc.h new file mode 100644 index 000000000..3452cf542 --- /dev/null +++ b/drivers/usb/gadget/udc/pxa25x_udc.h @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Intel PXA25x on-chip full speed USB device controller + * + * Copyright (C) 2003 Robert Schwebel , Pengutronix + * Copyright (C) 2003 David Brownell + */ + +#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 + */ + u32 regoff_udccs; + u32 regoff_ubcr; + u32 regoff_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 gpio_desc *pullup_gpio; + struct usb_phy *transceiver; + u64 dma_mask; + struct pxa25x_ep ep [PXA_UDC_NUM_ENDPOINTS]; + void __iomem *regs; + int usb_irq; + int usb_disc_irq; +}; +#define to_pxa25x(g) (container_of((g), struct pxa25x_udc, gadget)) + +/*-------------------------------------------------------------------------*/ + +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 inline u32 udc_ep_get_UDCCS(struct pxa25x_ep *); + +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, udc_ep_get_UDCCS(&dev->ep[i])); + } +} + +#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_warn("udc: " stuff) +#define INFO(stuff...) pr_info("udc: " stuff) + + +#endif /* __LINUX_USB_GADGET_PXA25X_H */ diff --git a/drivers/usb/gadget/udc/pxa27x_udc.c b/drivers/usb/gadget/udc/pxa27x_udc.c new file mode 100644 index 000000000..df5cca43a --- /dev/null +++ b/drivers/usb/gadget/udc/pxa27x_udc.c @@ -0,0 +1,2566 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Handles the Intel 27x USB Device Controller (UDC) + * + * Inspired by original driver by Frank Becker, David Brownell, and others. + * Copyright (C) 2008 Robert Jarzmik + */ +#include +#include +#include +#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; + u32 tmp; + + if (!udc->driver) + return -ENODEV; + + /* basic device status */ + seq_printf(s, DRIVER_DESC "\n" + "%s version: %s\n" + "Gadget driver: %s\n", + driver_name, DRIVER_VERSION, + udc->driver ? udc->driver->driver.name : "(none)"); + + tmp = udc_readl(udc, UDCCR); + 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 */ + seq_printf(s, "udcicr0=0x%08x udcicr1=0x%08x\n", + udc_readl(udc, UDCICR0), udc_readl(udc, UDCICR1)); + seq_printf(s, "udcisr0=0x%08x udcisr1=0x%08x\n", + udc_readl(udc, UDCISR0), udc_readl(udc, UDCISR1)); + seq_printf(s, "udcfnr=%d\n", udc_readl(udc, UDCFNR)); + 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); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(state_dbg); + +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 i, maxpkt; + + if (!udc->driver) + return -ENODEV; + + /* dump endpoint queues */ + for (i = 0; i < NR_PXA_ENDPOINTS; i++) { + ep = &udc->pxa_ep[i]; + maxpkt = ep->fifo_size; + seq_printf(s, "%-12s max_pkt=%d %s\n", + EPNAME(ep), maxpkt, "pio"); + + if (list_empty(&ep->queue)) { + seq_puts(s, "\t(nothing queued)\n"); + continue; + } + + list_for_each_entry(req, &ep->queue, queue) { + seq_printf(s, "\treq %p len %d/%d buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + } + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(queues_dbg); + +static int eps_dbg_show(struct seq_file *s, void *p) +{ + struct pxa_udc *udc = s->private; + struct pxa_ep *ep; + int i; + u32 tmp; + + if (!udc->driver) + return -ENODEV; + + ep = &udc->pxa_ep[0]; + tmp = udc_ep_readl(ep, UDCCSR); + 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); + 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)); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(eps_dbg); + +static void pxa_init_debugfs(struct pxa_udc *udc) +{ + struct dentry *root; + + root = debugfs_create_dir(udc->gadget.name, usb_debug_root); + debugfs_create_file("udcstate", 0400, root, udc, &state_dbg_fops); + debugfs_create_file("queues", 0400, root, udc, &queues_dbg_fops); + debugfs_create_file("epstate", 0400, root, udc, &eps_dbg_fops); +} + +static void pxa_cleanup_debugfs(struct pxa_udc *udc) +{ + debugfs_lookup_and_remove(udc->gadget.name, usb_debug_root); +} + +#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: interrupt handler + * + * 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 + * @ep: udc endpoint + * @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 + * @udc: 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 + * @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_obj(*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); + usb_gadget_giveback_request(&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: interrupt handler + * + * 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 { + 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: interrupt handler + * + * 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: thread context or from the interrupt handler 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; + + 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, str_yes_no(is_first_req), + _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 = NULL, *iter; + 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(iter, &ep->queue, queue) { + if (&iter->req != _req) + continue; + req = iter; + 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 const 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) +{ + gpiod_set_value(udc->gpiod, on); + 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 int 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: task context, might sleep + * + * 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); + int ret; + + if (!udc->gpiod) + return -EOPNOTSUPP; + + dplus_pullup(udc, is_active); + + if (should_enable_udc(udc)) { + ret = udc_enable(udc); + if (ret) { + dplus_pullup(udc, !is_active); + return ret; + } + } + if (should_disable_udc(udc)) + udc_disable(udc); + return 0; +} + +/** + * 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); + int ret; + + udc->vbus_sensed = is_active; + if (should_enable_udc(udc)) { + ret = udc_enable(udc); + if (ret) { + udc->vbus_sensed = !is_active; + return ret; + } + } + 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: task context, might sleep + * + * 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; +} + +/** + * pxa_udc_phy_event - Called by phy upon VBus event + * @nb: notifier block + * @action: phy action, is vbus connect or disconnect + * @data: the usb_gadget structure in pxa_udc + * + * Called by the USB Phy when a cable connect or disconnect is sensed. + * + * Returns 0 + */ +static int pxa_udc_phy_event(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct usb_gadget *gadget = data; + + switch (action) { + case USB_EVENT_VBUS: + usb_gadget_vbus_connect(gadget); + return NOTIFY_OK; + case USB_EVENT_NONE: + usb_gadget_vbus_disconnect(gadget); + return NOTIFY_OK; + default: + return NOTIFY_DONE; + } +} + +static struct notifier_block pxa27x_udc_phy = { + .notifier_call = pxa_udc_phy_event, +}; + +static int pxa27x_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static int pxa27x_udc_stop(struct usb_gadget *g); + +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); + + ep0_idle(udc); + udc->gadget.speed = USB_SPEED_UNKNOWN; + clk_disable(udc->clk); + + 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]; + dev->gadget.quirk_altset_not_supp = 1; + 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); + usb_ep_set_maxpacket_limit(&dev->udc_usb_ep[i].usb_ep, + dev->udc_usb_ep[i].usb_ep.maxpacket); + } +} + +/** + * udc_enable - Enables the udc device + * @udc: udc device + * + * Enables the udc device : enables clocks, udc interrupts, control endpoint + * interrupts, sets usb as UDC client and setups endpoints. + */ +static int udc_enable(struct pxa_udc *udc) +{ + int ret; + + if (udc->enabled) + return 0; + + ret = clk_enable(udc->clk); + if (ret) { + dev_err(udc->dev, "clk_enable failed: %d\n", ret); + return ret; + } + udc_writel(udc, UDCICR0, 0); + udc_writel(udc, UDCICR1, 0); + udc_clear_mask_UDCCR(udc, UDCCR_UDE); + + ep0_idle(udc); + udc->gadget.speed = USB_SPEED_FULL; + memset(&udc->stats, 0, sizeof(udc->stats)); + + pxa_eps_setup(udc); + 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; + + return 0; +} + +/** + * pxa27x_udc_start - Register gadget driver + * @g: gadget + * @driver: gadget driver + * + * 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; + + 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)) { + retval = udc_enable(udc); + if (retval) + goto fail_enable; + } + return 0; + +fail_enable: + if (!IS_ERR_OR_NULL(udc->transceiver)) + otg_set_peripheral(udc->transceiver->otg, NULL); +fail: + udc->driver = NULL; + return retval; +} + +/** + * stop_activity - Stops udc endpoints + * @udc: udc device + * + * Disables all udc endpoints (even control endpoint), report disconnect to + * the gadget user. + */ +static void stop_activity(struct pxa_udc *udc) +{ + int i; + + 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 + * @g: gadget + * + * Returns 0 if no error, -ENODEV, -EINVAL otherwise + */ +static int pxa27x_udc_stop(struct usb_gadget *g) +{ + struct pxa_udc *udc = to_pxa(g); + + stop_activity(udc); + udc_disable(udc); + + 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 : interrupt handler + * + * 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 from the interrupt handler. ep->lock must not be held. + */ +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->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), + } +}; + +#if defined(CONFIG_OF) +static const struct of_device_id udc_pxa_dt_ids[] = { + { .compatible = "marvell,pxa270-udc" }, + {} +}; +MODULE_DEVICE_TABLE(of, udc_pxa_dt_ids); +#endif + +/** + * pxa_udc_probe - probes the udc device + * @pdev: platform device + * + * Perform basic init : allocates udc clock, creates sysfs files, requests + * irq. + */ +static int pxa_udc_probe(struct platform_device *pdev) +{ + struct pxa_udc *udc = &memory; + int retval = 0; + + udc->gpiod = devm_gpiod_get_optional(&pdev->dev, NULL, GPIOD_ASIS); + if (IS_ERR(udc->gpiod)) + return dev_err_probe(&pdev->dev, PTR_ERR(udc->gpiod), + "Couldn't find or request D+ gpio\n"); + + udc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(udc->regs)) + return PTR_ERR(udc->regs); + udc->irq = platform_get_irq(pdev, 0); + if (udc->irq < 0) + return udc->irq; + + udc->dev = &pdev->dev; + if (of_have_populated_dt()) { + udc->transceiver = + devm_usb_get_phy_by_phandle(udc->dev, "phys", 0); + if (IS_ERR(udc->transceiver)) + return PTR_ERR(udc->transceiver); + } else { + udc->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); + } + + if (udc->gpiod) + gpiod_direction_output(udc->gpiod, 0); + + udc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(udc->clk)) + return PTR_ERR(udc->clk); + + retval = clk_prepare(udc->clk); + if (retval) + return retval; + + udc->vbus_sensed = 0; + + the_controller = udc; + platform_set_drvdata(pdev, udc); + udc_init_data(udc); + + /* irq setup after old hardware state is cleaned up */ + retval = devm_request_irq(&pdev->dev, 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; + } + + if (!IS_ERR_OR_NULL(udc->transceiver)) + usb_register_notifier(udc->transceiver, &pxa27x_udc_phy); + retval = usb_add_gadget_udc(&pdev->dev, &udc->gadget); + if (retval) + goto err_add_gadget; + + pxa_init_debugfs(udc); + if (should_enable_udc(udc)) { + retval = udc_enable(udc); + if (retval) + goto err_enable; + } + return 0; + +err_enable: + usb_del_gadget_udc(&udc->gadget); + pxa_cleanup_debugfs(udc); +err_add_gadget: + if (!IS_ERR_OR_NULL(udc->transceiver)) + usb_unregister_notifier(udc->transceiver, &pxa27x_udc_phy); +err: + clk_unprepare(udc->clk); + return retval; +} + +/** + * pxa_udc_remove - removes the udc device driver + * @_dev: platform device + */ +static void pxa_udc_remove(struct platform_device *_dev) +{ + struct pxa_udc *udc = platform_get_drvdata(_dev); + + usb_del_gadget_udc(&udc->gadget); + pxa_cleanup_debugfs(udc); + + if (!IS_ERR_OR_NULL(udc->transceiver)) { + usb_unregister_notifier(udc->transceiver, &pxa27x_udc_phy); + usb_put_phy(udc->transceiver); + } + + udc->transceiver = NULL; + the_controller = NULL; + clk_unprepare(udc->clk); +} + +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_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) +{ + struct pxa_udc *udc = platform_get_drvdata(_dev); + struct pxa_ep *ep; + + ep = &udc->pxa_ep[0]; + udc->udccsr0 = udc_ep_readl(ep, UDCCSR); + + udc_disable(udc); + udc->pullup_resume = udc->pullup_on; + dplus_pullup(udc, 0); + + if (udc->driver) + udc->driver->disconnect(&udc->gadget); + + 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) +{ + struct pxa_udc *udc = platform_get_drvdata(_dev); + struct pxa_ep *ep; + int ret; + + ep = &udc->pxa_ep[0]; + udc_ep_writel(ep, UDCCSR, udc->udccsr0 & (UDCCSR0_FST | UDCCSR0_DME)); + + dplus_pullup(udc, udc->pullup_resume); + if (should_enable_udc(udc)) { + ret = udc_enable(udc); + if (ret) { + dplus_pullup(udc, !udc->pullup_resume); + return ret; + } + } + /* + * 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", + .of_match_table = of_match_ptr(udc_pxa_dt_ids), + }, + .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/udc/pxa27x_udc.h b/drivers/usb/gadget/udc/pxa27x_udc.h new file mode 100644 index 000000000..2c28b6910 --- /dev/null +++ b/drivers/usb/gadget/udc/pxa27x_udc.h @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#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, ctype, cdir) \ +{ .usb_ep = { .name = bname, .ops = &pxa_ep_ops, .maxpacket = maxpkt, \ + .caps = USB_EP_CAPS(ctype, cdir), }, \ + .desc = { .bEndpointAddress = addr | (dir ? USB_DIR_IN : 0), \ + .bmAttributes = USB_ENDPOINT_XFER_ ## type, \ + .wMaxPacketSize = maxpkt, }, \ + .dev = &memory \ +} +#define USB_EP_BULK(addr, bname, dir, cdir) \ + USB_EP_DEF(addr, bname, dir, BULK, BULK_FIFO_SIZE, \ + USB_EP_CAPS_TYPE_BULK, cdir) +#define USB_EP_ISO(addr, bname, dir, cdir) \ + USB_EP_DEF(addr, bname, dir, ISOC, ISO_FIFO_SIZE, \ + USB_EP_CAPS_TYPE_ISO, cdir) +#define USB_EP_INT(addr, bname, dir, cdir) \ + USB_EP_DEF(addr, bname, dir, INT, INT_FIFO_SIZE, \ + USB_EP_CAPS_TYPE_INT, cdir) +#define USB_EP_IN_BULK(n) USB_EP_BULK(n, "ep" #n "in-bulk", 1, \ + USB_EP_CAPS_DIR_IN) +#define USB_EP_OUT_BULK(n) USB_EP_BULK(n, "ep" #n "out-bulk", 0, \ + USB_EP_CAPS_DIR_OUT) +#define USB_EP_IN_ISO(n) USB_EP_ISO(n, "ep" #n "in-iso", 1, \ + USB_EP_CAPS_DIR_IN) +#define USB_EP_OUT_ISO(n) USB_EP_ISO(n, "ep" #n "out-iso", 0, \ + USB_EP_CAPS_DIR_OUT) +#define USB_EP_IN_INT(n) USB_EP_INT(n, "ep" #n "in-int", 1, \ + USB_EP_CAPS_DIR_IN) +#define USB_EP_CTRL USB_EP_DEF(0, "ep0", 0, CONTROL, EP0_FIFO_SIZE, \ + USB_EP_CAPS_TYPE_CONTROL, USB_EP_CAPS_DIR_ALL) + +#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 endpoint 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 + * @gpiod: gpio descriptor of gpio for D+ pullup (or NULL if none) + * @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_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 gpio_desc *gpiod; + 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 +}; +#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/udc/r8a66597-udc.c b/drivers/usb/gadget/udc/r8a66597-udc.c new file mode 100644 index 000000000..d190e16d4 --- /dev/null +++ b/drivers/usb/gadget/udc/r8a66597-udc.c @@ -0,0 +1,1978 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * R8A66597 UDC (USB gadget) + * + * Copyright (C) 2006-2009 Renesas Solutions Corp. + * + * Author : Yoshihiro Shimoda + */ + +#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 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[usb_endpoint_num(desc)] + = 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 (usb_endpoint_type(desc)) { + 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 = usb_endpoint_num(desc); + 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); + + mdelay(3); + + r8a66597_bset(r8a66597, PLLC, SYSCFG0); + + mdelay(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); + usb_gadget_giveback_request(&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_ATOMIC); + 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); + usb_gadget_udc_reset(&r8a66597->gadget, r8a66597->driver); + 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 savepipe; + u16 mask0; + + spin_lock(&r8a66597->lock); + + if (r8a66597_is_sudmac(r8a66597)) + r8a66597_sudmac_irq(r8a66597); + + intsts0 = r8a66597_read(r8a66597, INTSTS0); + intenb0 = r8a66597_read(r8a66597, INTENB0); + + savepipe = r8a66597_read(r8a66597, CFIFOSEL); + + mask0 = intsts0 & intenb0; + if (mask0) { + u16 brdysts = r8a66597_read(r8a66597, BRDYSTS); + u16 bempsts = r8a66597_read(r8a66597, BEMPSTS); + u16 brdyenb = r8a66597_read(r8a66597, BRDYENB); + u16 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(struct timer_list *t) +{ + struct r8a66597 *r8a66597 = timer_container_of(r8a66597, t, timer); + 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_obj(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 = container_of(_ep, struct r8a66597_ep, ep); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&ep->r8a66597->lock, flags); + if (!list_empty(&ep->queue)) { + ret = -EAGAIN; + } else if (value) { + ep->busy = 1; + pipe_stall(ep->r8a66597, ep->pipenum); + } else { + ep->busy = 0; + ep->wedge = 0; + pipe_stop(ep->r8a66597, ep->pipenum); + } + 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 const 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 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); + + gadget->is_selfpowered = (is_self != 0); + 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 void r8a66597_remove(struct platform_device *pdev) +{ + struct r8a66597 *r8a66597 = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&r8a66597->gadget); + timer_delete_sync(&r8a66597->timer); + r8a66597_free_request(&r8a66597->ep[0].ep, r8a66597->ep0_req); + + if (r8a66597->pdata->on_chip) { + clk_disable_unprepare(r8a66597->clk); + } +} + +static void nop_completion(struct usb_ep *ep, struct usb_request *r) +{ +} + +static int r8a66597_sudmac_ioremap(struct r8a66597 *r8a66597, + struct platform_device *pdev) +{ + r8a66597->sudmac_reg = + devm_platform_ioremap_resource_byname(pdev, "sudmac"); + return PTR_ERR_OR_ZERO(r8a66597->sudmac_reg); +} + +static int r8a66597_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + char clk_name[8]; + struct resource *ires; + int irq; + void __iomem *reg = NULL; + struct r8a66597 *r8a66597 = NULL; + int ret = 0; + int i; + unsigned long irq_trigger; + + reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(reg)) + return PTR_ERR(reg); + + ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!ires) + return -EINVAL; + irq = ires->start; + irq_trigger = ires->flags & IRQF_TRIGGER_MASK; + + if (irq < 0) { + dev_err(dev, "platform_get_irq error.\n"); + return -ENODEV; + } + + /* initialize ucd */ + r8a66597 = devm_kzalloc(dev, sizeof(struct r8a66597), GFP_KERNEL); + if (r8a66597 == NULL) + return -ENOMEM; + + spin_lock_init(&r8a66597->lock); + platform_set_drvdata(pdev, r8a66597); + r8a66597->pdata = dev_get_platdata(dev); + 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; + + timer_setup(&r8a66597->timer, r8a66597_timer, 0); + r8a66597->reg = reg; + + if (r8a66597->pdata->on_chip) { + snprintf(clk_name, sizeof(clk_name), "usb%d", pdev->id); + r8a66597->clk = devm_clk_get(dev, clk_name); + if (IS_ERR(r8a66597->clk)) { + dev_err(dev, "cannot get clock \"%s\"\n", clk_name); + return PTR_ERR(r8a66597->clk); + } + clk_prepare_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 = devm_request_irq(dev, irq, r8a66597_irq, IRQF_SHARED, + udc_name, r8a66597); + if (ret < 0) { + dev_err(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; + usb_ep_set_maxpacket_limit(&ep->ep, 512); + + if (i == 0) { + ep->ep.caps.type_control = true; + } else { + ep->ep.caps.type_iso = true; + ep->ep.caps.type_bulk = true; + ep->ep.caps.type_int = true; + } + ep->ep.caps.dir_in = true; + ep->ep.caps.dir_out = true; + } + usb_ep_set_maxpacket_limit(&r8a66597->ep[0].ep, 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_up2; + } + r8a66597->ep0_req->complete = nop_completion; + + ret = usb_add_gadget_udc(dev, &r8a66597->gadget); + if (ret) + goto err_add_udc; + + dev_info(dev, "version %s\n", DRIVER_VERSION); + return 0; + +err_add_udc: +clean_up2: + if (r8a66597->pdata->on_chip) + clk_disable_unprepare(r8a66597->clk); + + if (r8a66597->ep0_req) + r8a66597_free_request(&r8a66597->ep[0].ep, r8a66597->ep0_req); + + return ret; +} + +/*-------------------------------------------------------------------------*/ +static struct platform_driver r8a66597_driver = { + .probe = r8a66597_probe, + .remove = r8a66597_remove, + .driver = { + .name = udc_name, + }, +}; + +module_platform_driver(r8a66597_driver); + +MODULE_DESCRIPTION("R8A66597 USB gadget driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yoshihiro Shimoda"); +MODULE_ALIAS("platform:r8a66597_udc"); diff --git a/drivers/usb/gadget/udc/r8a66597-udc.h b/drivers/usb/gadget/udc/r8a66597-udc.h new file mode 100644 index 000000000..9a115caba --- /dev/null +++ b/drivers/usb/gadget/udc/r8a66597-udc.h @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * R8A66597 UDC + * + * Copyright (C) 2007-2009 Renesas Solutions Corp. + * + * Author : Yoshihiro Shimoda + */ + +#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/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c new file mode 100644 index 000000000..2c9c3e935 --- /dev/null +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -0,0 +1,3029 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas USB3.0 Peripheral driver (USB gadget) + * + * Copyright (C) 2015-2017 Renesas Electronics Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* register definitions */ +#define USB3_AXI_INT_STA 0x008 +#define USB3_AXI_INT_ENA 0x00c +#define USB3_DMA_INT_STA 0x010 +#define USB3_DMA_INT_ENA 0x014 +#define USB3_DMA_CH0_CON(n) (0x030 + ((n) - 1) * 0x10) /* n = 1 to 4 */ +#define USB3_DMA_CH0_PRD_ADR(n) (0x034 + ((n) - 1) * 0x10) /* n = 1 to 4 */ +#define USB3_USB_COM_CON 0x200 +#define USB3_USB20_CON 0x204 +#define USB3_USB30_CON 0x208 +#define USB3_USB_STA 0x210 +#define USB3_DRD_CON(p) ((p)->is_rzv2m ? 0x400 : 0x218) +#define USB3_USB_INT_STA_1 0x220 +#define USB3_USB_INT_STA_2 0x224 +#define USB3_USB_INT_ENA_1 0x228 +#define USB3_USB_INT_ENA_2 0x22c +#define USB3_STUP_DAT_0 0x230 +#define USB3_STUP_DAT_1 0x234 +#define USB3_USB_OTG_STA(p) ((p)->is_rzv2m ? 0x410 : 0x268) +#define USB3_USB_OTG_INT_STA(p) ((p)->is_rzv2m ? 0x414 : 0x26c) +#define USB3_USB_OTG_INT_ENA(p) ((p)->is_rzv2m ? 0x418 : 0x270) +#define USB3_P0_MOD 0x280 +#define USB3_P0_CON 0x288 +#define USB3_P0_STA 0x28c +#define USB3_P0_INT_STA 0x290 +#define USB3_P0_INT_ENA 0x294 +#define USB3_P0_LNG 0x2a0 +#define USB3_P0_READ 0x2a4 +#define USB3_P0_WRITE 0x2a8 +#define USB3_PIPE_COM 0x2b0 +#define USB3_PN_MOD 0x2c0 +#define USB3_PN_RAMMAP 0x2c4 +#define USB3_PN_CON 0x2c8 +#define USB3_PN_STA 0x2cc +#define USB3_PN_INT_STA 0x2d0 +#define USB3_PN_INT_ENA 0x2d4 +#define USB3_PN_LNG 0x2e0 +#define USB3_PN_READ 0x2e4 +#define USB3_PN_WRITE 0x2e8 +#define USB3_SSIFCMD 0x340 + +/* AXI_INT_ENA and AXI_INT_STA */ +#define AXI_INT_DMAINT BIT(31) +#define AXI_INT_EPCINT BIT(30) +/* PRD's n = from 1 to 4 */ +#define AXI_INT_PRDEN_CLR_STA_SHIFT(n) (16 + (n) - 1) +#define AXI_INT_PRDERR_STA_SHIFT(n) (0 + (n) - 1) +#define AXI_INT_PRDEN_CLR_STA(n) (1 << AXI_INT_PRDEN_CLR_STA_SHIFT(n)) +#define AXI_INT_PRDERR_STA(n) (1 << AXI_INT_PRDERR_STA_SHIFT(n)) + +/* DMA_INT_ENA and DMA_INT_STA */ +#define DMA_INT(n) BIT(n) + +/* DMA_CH0_CONn */ +#define DMA_CON_PIPE_DIR BIT(15) /* 1: In Transfer */ +#define DMA_CON_PIPE_NO_SHIFT 8 +#define DMA_CON_PIPE_NO_MASK GENMASK(12, DMA_CON_PIPE_NO_SHIFT) +#define DMA_COM_PIPE_NO(n) (((n) << DMA_CON_PIPE_NO_SHIFT) & \ + DMA_CON_PIPE_NO_MASK) +#define DMA_CON_PRD_EN BIT(0) + +/* LCLKSEL */ +#define LCLKSEL_LSEL BIT(18) + +/* USB_COM_CON */ +#define USB_COM_CON_CONF BIT(24) +#define USB_COM_CON_PN_WDATAIF_NL BIT(23) +#define USB_COM_CON_PN_RDATAIF_NL BIT(22) +#define USB_COM_CON_PN_LSTTR_PP BIT(21) +#define USB_COM_CON_SPD_MODE BIT(17) +#define USB_COM_CON_EP0_EN BIT(16) +#define USB_COM_CON_DEV_ADDR_SHIFT 8 +#define USB_COM_CON_DEV_ADDR_MASK GENMASK(14, USB_COM_CON_DEV_ADDR_SHIFT) +#define USB_COM_CON_DEV_ADDR(n) (((n) << USB_COM_CON_DEV_ADDR_SHIFT) & \ + USB_COM_CON_DEV_ADDR_MASK) +#define USB_COM_CON_RX_DETECTION BIT(1) +#define USB_COM_CON_PIPE_CLR BIT(0) + +/* USB20_CON */ +#define USB20_CON_B2_PUE BIT(31) +#define USB20_CON_B2_SUSPEND BIT(24) +#define USB20_CON_B2_CONNECT BIT(17) +#define USB20_CON_B2_TSTMOD_SHIFT 8 +#define USB20_CON_B2_TSTMOD_MASK GENMASK(10, USB20_CON_B2_TSTMOD_SHIFT) +#define USB20_CON_B2_TSTMOD(n) (((n) << USB20_CON_B2_TSTMOD_SHIFT) & \ + USB20_CON_B2_TSTMOD_MASK) +#define USB20_CON_B2_TSTMOD_EN BIT(0) + +/* USB30_CON */ +#define USB30_CON_POW_SEL_SHIFT 24 +#define USB30_CON_POW_SEL_MASK GENMASK(26, USB30_CON_POW_SEL_SHIFT) +#define USB30_CON_POW_SEL_IN_U3 BIT(26) +#define USB30_CON_POW_SEL_IN_DISCON 0 +#define USB30_CON_POW_SEL_P2_TO_P0 BIT(25) +#define USB30_CON_POW_SEL_P0_TO_P3 BIT(24) +#define USB30_CON_POW_SEL_P0_TO_P2 0 +#define USB30_CON_B3_PLLWAKE BIT(23) +#define USB30_CON_B3_CONNECT BIT(17) +#define USB30_CON_B3_HOTRST_CMP BIT(1) + +/* USB_STA */ +#define USB_STA_SPEED_MASK (BIT(2) | BIT(1)) +#define USB_STA_SPEED_HS BIT(2) +#define USB_STA_SPEED_FS BIT(1) +#define USB_STA_SPEED_SS 0 +#define USB_STA_VBUS_STA BIT(0) + +/* DRD_CON */ +#define DRD_CON_PERI_RST BIT(31) /* rzv2m only */ +#define DRD_CON_HOST_RST BIT(30) /* rzv2m only */ +#define DRD_CON_PERI_CON BIT(24) +#define DRD_CON_VBOUT BIT(0) + +/* USB_INT_ENA_1 and USB_INT_STA_1 */ +#define USB_INT_1_B3_PLLWKUP BIT(31) +#define USB_INT_1_B3_LUPSUCS BIT(30) +#define USB_INT_1_B3_DISABLE BIT(27) +#define USB_INT_1_B3_WRMRST BIT(21) +#define USB_INT_1_B3_HOTRST BIT(20) +#define USB_INT_1_B2_USBRST BIT(12) +#define USB_INT_1_B2_L1SPND BIT(11) +#define USB_INT_1_B2_SPND BIT(9) +#define USB_INT_1_B2_RSUM BIT(8) +#define USB_INT_1_SPEED BIT(1) +#define USB_INT_1_VBUS_CNG BIT(0) + +/* USB_INT_ENA_2 and USB_INT_STA_2 */ +#define USB_INT_2_PIPE(n) BIT(n) + +/* USB_OTG_STA, USB_OTG_INT_STA and USB_OTG_INT_ENA */ +#define USB_OTG_IDMON(p) ((p)->is_rzv2m ? BIT(0) : BIT(4)) + +/* P0_MOD */ +#define P0_MOD_DIR BIT(6) + +/* P0_CON and PN_CON */ +#define PX_CON_BYTE_EN_MASK (BIT(10) | BIT(9)) +#define PX_CON_BYTE_EN_SHIFT 9 +#define PX_CON_BYTE_EN_BYTES(n) (((n) << PX_CON_BYTE_EN_SHIFT) & \ + PX_CON_BYTE_EN_MASK) +#define PX_CON_SEND BIT(8) + +/* P0_CON */ +#define P0_CON_ST_RES_MASK (BIT(27) | BIT(26)) +#define P0_CON_ST_RES_FORCE_STALL BIT(27) +#define P0_CON_ST_RES_NORMAL BIT(26) +#define P0_CON_ST_RES_FORCE_NRDY 0 +#define P0_CON_OT_RES_MASK (BIT(25) | BIT(24)) +#define P0_CON_OT_RES_FORCE_STALL BIT(25) +#define P0_CON_OT_RES_NORMAL BIT(24) +#define P0_CON_OT_RES_FORCE_NRDY 0 +#define P0_CON_IN_RES_MASK (BIT(17) | BIT(16)) +#define P0_CON_IN_RES_FORCE_STALL BIT(17) +#define P0_CON_IN_RES_NORMAL BIT(16) +#define P0_CON_IN_RES_FORCE_NRDY 0 +#define P0_CON_RES_WEN BIT(7) +#define P0_CON_BCLR BIT(1) + +/* P0_STA and PN_STA */ +#define PX_STA_BUFSTS BIT(0) + +/* P0_INT_ENA and P0_INT_STA */ +#define P0_INT_STSED BIT(18) +#define P0_INT_STSST BIT(17) +#define P0_INT_SETUP BIT(16) +#define P0_INT_RCVNL BIT(8) +#define P0_INT_ERDY BIT(7) +#define P0_INT_FLOW BIT(6) +#define P0_INT_STALL BIT(2) +#define P0_INT_NRDY BIT(1) +#define P0_INT_BFRDY BIT(0) +#define P0_INT_ALL_BITS (P0_INT_STSED | P0_INT_SETUP | P0_INT_BFRDY) + +/* PN_MOD */ +#define PN_MOD_DIR BIT(6) +#define PN_MOD_TYPE_SHIFT 4 +#define PN_MOD_TYPE_MASK GENMASK(5, PN_MOD_TYPE_SHIFT) +#define PN_MOD_TYPE(n) (((n) << PN_MOD_TYPE_SHIFT) & \ + PN_MOD_TYPE_MASK) +#define PN_MOD_EPNUM_MASK GENMASK(3, 0) +#define PN_MOD_EPNUM(n) ((n) & PN_MOD_EPNUM_MASK) + +/* PN_RAMMAP */ +#define PN_RAMMAP_RAMAREA_SHIFT 29 +#define PN_RAMMAP_RAMAREA_MASK GENMASK(31, PN_RAMMAP_RAMAREA_SHIFT) +#define PN_RAMMAP_RAMAREA_16KB BIT(31) +#define PN_RAMMAP_RAMAREA_8KB (BIT(30) | BIT(29)) +#define PN_RAMMAP_RAMAREA_4KB BIT(30) +#define PN_RAMMAP_RAMAREA_2KB BIT(29) +#define PN_RAMMAP_RAMAREA_1KB 0 +#define PN_RAMMAP_MPKT_SHIFT 16 +#define PN_RAMMAP_MPKT_MASK GENMASK(26, PN_RAMMAP_MPKT_SHIFT) +#define PN_RAMMAP_MPKT(n) (((n) << PN_RAMMAP_MPKT_SHIFT) & \ + PN_RAMMAP_MPKT_MASK) +#define PN_RAMMAP_RAMIF_SHIFT 14 +#define PN_RAMMAP_RAMIF_MASK GENMASK(15, PN_RAMMAP_RAMIF_SHIFT) +#define PN_RAMMAP_RAMIF(n) (((n) << PN_RAMMAP_RAMIF_SHIFT) & \ + PN_RAMMAP_RAMIF_MASK) +#define PN_RAMMAP_BASEAD_MASK GENMASK(13, 0) +#define PN_RAMMAP_BASEAD(offs) (((offs) >> 3) & PN_RAMMAP_BASEAD_MASK) +#define PN_RAMMAP_DATA(area, ramif, basead) ((PN_RAMMAP_##area) | \ + (PN_RAMMAP_RAMIF(ramif)) | \ + (PN_RAMMAP_BASEAD(basead))) + +/* PN_CON */ +#define PN_CON_EN BIT(31) +#define PN_CON_DATAIF_EN BIT(30) +#define PN_CON_RES_MASK (BIT(17) | BIT(16)) +#define PN_CON_RES_FORCE_STALL BIT(17) +#define PN_CON_RES_NORMAL BIT(16) +#define PN_CON_RES_FORCE_NRDY 0 +#define PN_CON_LAST BIT(11) +#define PN_CON_RES_WEN BIT(7) +#define PN_CON_CLR BIT(0) + +/* PN_INT_STA and PN_INT_ENA */ +#define PN_INT_LSTTR BIT(4) +#define PN_INT_BFRDY BIT(0) + +/* USB3_SSIFCMD */ +#define SSIFCMD_URES_U2 BIT(9) +#define SSIFCMD_URES_U1 BIT(8) +#define SSIFCMD_UDIR_U2 BIT(7) +#define SSIFCMD_UDIR_U1 BIT(6) +#define SSIFCMD_UREQ_U2 BIT(5) +#define SSIFCMD_UREQ_U1 BIT(4) + +#define USB3_EP0_SS_MAX_PACKET_SIZE 512 +#define USB3_EP0_HSFS_MAX_PACKET_SIZE 64 +#define USB3_EP0_BUF_SIZE 8 +#define USB3_MAX_NUM_PIPES(p) ((p)->is_rzv2m ? 16 : 6) /* This includes PIPE 0 */ +#define USB3_WAIT_US 3 +#define USB3_DMA_NUM_SETTING_AREA 4 +/* + * To avoid double-meaning of "0" (xferred 65536 bytes or received zlp if + * buffer size is 65536), this driver uses the maximum size per a entry is + * 32768 bytes. + */ +#define USB3_DMA_MAX_XFER_SIZE 32768 +#define USB3_DMA_PRD_SIZE 4096 + +struct renesas_usb3; + +/* Physical Region Descriptor Table */ +struct renesas_usb3_prd { + u32 word1; +#define USB3_PRD1_E BIT(30) /* the end of chain */ +#define USB3_PRD1_U BIT(29) /* completion of transfer */ +#define USB3_PRD1_D BIT(28) /* Error occurred */ +#define USB3_PRD1_INT BIT(27) /* Interrupt occurred */ +#define USB3_PRD1_LST BIT(26) /* Last Packet */ +#define USB3_PRD1_B_INC BIT(24) +#define USB3_PRD1_MPS_8 0 +#define USB3_PRD1_MPS_16 BIT(21) +#define USB3_PRD1_MPS_32 BIT(22) +#define USB3_PRD1_MPS_64 (BIT(22) | BIT(21)) +#define USB3_PRD1_MPS_512 BIT(23) +#define USB3_PRD1_MPS_1024 (BIT(23) | BIT(21)) +#define USB3_PRD1_MPS_RESERVED (BIT(23) | BIT(22) | BIT(21)) +#define USB3_PRD1_SIZE_MASK GENMASK(15, 0) + + u32 bap; +}; +#define USB3_DMA_NUM_PRD_ENTRIES (USB3_DMA_PRD_SIZE / \ + sizeof(struct renesas_usb3_prd)) +#define USB3_DMA_MAX_XFER_SIZE_ALL_PRDS (USB3_DMA_PRD_SIZE / \ + sizeof(struct renesas_usb3_prd) * \ + USB3_DMA_MAX_XFER_SIZE) + +struct renesas_usb3_dma { + struct renesas_usb3_prd *prd; + dma_addr_t prd_dma; + int num; /* Setting area number (from 1 to 4) */ + bool used; +}; + +struct renesas_usb3_request { + struct usb_request req; + struct list_head queue; +}; + +#define USB3_EP_NAME_SIZE 16 +struct renesas_usb3_ep { + struct usb_ep ep; + struct renesas_usb3 *usb3; + struct renesas_usb3_dma *dma; + int num; + char ep_name[USB3_EP_NAME_SIZE]; + struct list_head queue; + u32 rammap_val; + bool dir_in; + bool halt; + bool wedge; + bool started; +}; + +struct renesas_usb3_priv { + int ramsize_per_ramif; /* unit = bytes */ + int num_ramif; + int ramsize_per_pipe; /* unit = bytes */ + bool workaround_for_vbus; /* if true, don't check vbus signal */ + bool is_rzv2m; /* if true, RZ/V2M SoC */ +}; + +struct renesas_usb3 { + void __iomem *reg; + void __iomem *drd_reg; + struct reset_control *usbp_rstc; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct extcon_dev *extcon; + struct work_struct extcon_work; + struct phy *phy; + struct dentry *dentry; + + struct usb_role_switch *role_sw; + struct device *host_dev; + struct work_struct role_work; + enum usb_role role; + + struct renesas_usb3_ep *usb3_ep; + int num_usb3_eps; + + struct renesas_usb3_dma dma[USB3_DMA_NUM_SETTING_AREA]; + + spinlock_t lock; + int disabled_count; + + struct usb_request *ep0_req; + + enum usb_role connection_state; + u16 test_mode; + u8 ep0_buf[USB3_EP0_BUF_SIZE]; + bool softconnect; + bool workaround_for_vbus; + bool extcon_host; /* check id and set EXTCON_USB_HOST */ + bool extcon_usb; /* check vbus and set EXTCON_USB */ + bool forced_b_device; + bool start_to_connect; + bool role_sw_by_connector; + bool is_rzv2m; +}; + +#define gadget_to_renesas_usb3(_gadget) \ + container_of(_gadget, struct renesas_usb3, gadget) +#define renesas_usb3_to_gadget(renesas_usb3) (&renesas_usb3->gadget) +#define usb3_to_dev(_usb3) (_usb3->gadget.dev.parent) + +#define usb_ep_to_usb3_ep(_ep) container_of(_ep, struct renesas_usb3_ep, ep) +#define usb3_ep_to_usb3(_usb3_ep) (_usb3_ep->usb3) +#define usb_req_to_usb3_req(_req) container_of(_req, \ + struct renesas_usb3_request, req) + +#define usb3_get_ep(usb3, n) ((usb3)->usb3_ep + (n)) +#define usb3_for_each_ep(usb3_ep, usb3, i) \ + for ((i) = 0, usb3_ep = usb3_get_ep(usb3, (i)); \ + (i) < (usb3)->num_usb3_eps; \ + (i)++, usb3_ep = usb3_get_ep(usb3, (i))) + +#define usb3_get_dma(usb3, i) (&(usb3)->dma[i]) +#define usb3_for_each_dma(usb3, dma, i) \ + for ((i) = 0, dma = usb3_get_dma((usb3), (i)); \ + (i) < USB3_DMA_NUM_SETTING_AREA; \ + (i)++, dma = usb3_get_dma((usb3), (i))) + +static const char udc_name[] = "renesas_usb3"; + +static bool use_dma = 1; +module_param(use_dma, bool, 0644); +MODULE_PARM_DESC(use_dma, "use dedicated DMAC"); + +static void usb3_write(struct renesas_usb3 *usb3, u32 data, u32 offs) +{ + iowrite32(data, usb3->reg + offs); +} + +static u32 usb3_read(struct renesas_usb3 *usb3, u32 offs) +{ + return ioread32(usb3->reg + offs); +} + +static void usb3_set_bit(struct renesas_usb3 *usb3, u32 bits, u32 offs) +{ + u32 val = usb3_read(usb3, offs); + + val |= bits; + usb3_write(usb3, val, offs); +} + +static void usb3_clear_bit(struct renesas_usb3 *usb3, u32 bits, u32 offs) +{ + u32 val = usb3_read(usb3, offs); + + val &= ~bits; + usb3_write(usb3, val, offs); +} + +static void usb3_drd_write(struct renesas_usb3 *usb3, u32 data, u32 offs) +{ + void __iomem *reg; + + if (usb3->is_rzv2m) + reg = usb3->drd_reg + offs - USB3_DRD_CON(usb3); + else + reg = usb3->reg + offs; + + iowrite32(data, reg); +} + +static u32 usb3_drd_read(struct renesas_usb3 *usb3, u32 offs) +{ + void __iomem *reg; + + if (usb3->is_rzv2m) + reg = usb3->drd_reg + offs - USB3_DRD_CON(usb3); + else + reg = usb3->reg + offs; + + return ioread32(reg); +} + +static void usb3_drd_set_bit(struct renesas_usb3 *usb3, u32 bits, u32 offs) +{ + u32 val = usb3_drd_read(usb3, offs); + + val |= bits; + usb3_drd_write(usb3, val, offs); +} + +static void usb3_drd_clear_bit(struct renesas_usb3 *usb3, u32 bits, u32 offs) +{ + u32 val = usb3_drd_read(usb3, offs); + + val &= ~bits; + usb3_drd_write(usb3, val, offs); +} + +static int usb3_wait(struct renesas_usb3 *usb3, u32 reg, u32 mask, + u32 expected) +{ + int i; + + for (i = 0; i < USB3_WAIT_US; i++) { + if ((usb3_read(usb3, reg) & mask) == expected) + return 0; + udelay(1); + } + + dev_dbg(usb3_to_dev(usb3), "%s: timed out (%8x, %08x, %08x)\n", + __func__, reg, mask, expected); + + return -EBUSY; +} + +static void renesas_usb3_extcon_work(struct work_struct *work) +{ + struct renesas_usb3 *usb3 = container_of(work, struct renesas_usb3, + extcon_work); + + extcon_set_state_sync(usb3->extcon, EXTCON_USB_HOST, usb3->extcon_host); + extcon_set_state_sync(usb3->extcon, EXTCON_USB, usb3->extcon_usb); +} + +static void usb3_enable_irq_1(struct renesas_usb3 *usb3, u32 bits) +{ + usb3_set_bit(usb3, bits, USB3_USB_INT_ENA_1); +} + +static void usb3_disable_irq_1(struct renesas_usb3 *usb3, u32 bits) +{ + usb3_clear_bit(usb3, bits, USB3_USB_INT_ENA_1); +} + +static void usb3_enable_pipe_irq(struct renesas_usb3 *usb3, int num) +{ + usb3_set_bit(usb3, USB_INT_2_PIPE(num), USB3_USB_INT_ENA_2); +} + +static void usb3_disable_pipe_irq(struct renesas_usb3 *usb3, int num) +{ + usb3_clear_bit(usb3, USB_INT_2_PIPE(num), USB3_USB_INT_ENA_2); +} + +static bool usb3_is_host(struct renesas_usb3 *usb3) +{ + return !(usb3_drd_read(usb3, USB3_DRD_CON(usb3)) & DRD_CON_PERI_CON); +} + +static void usb3_init_axi_bridge(struct renesas_usb3 *usb3) +{ + /* Set AXI_INT */ + usb3_write(usb3, ~0, USB3_DMA_INT_STA); + usb3_write(usb3, 0, USB3_DMA_INT_ENA); + usb3_set_bit(usb3, AXI_INT_DMAINT | AXI_INT_EPCINT, USB3_AXI_INT_ENA); +} + +static void usb3_init_epc_registers(struct renesas_usb3 *usb3) +{ + usb3_write(usb3, ~0, USB3_USB_INT_STA_1); + if (!usb3->workaround_for_vbus) + usb3_enable_irq_1(usb3, USB_INT_1_VBUS_CNG); +} + +static bool usb3_wakeup_usb2_phy(struct renesas_usb3 *usb3) +{ + if (!(usb3_read(usb3, USB3_USB20_CON) & USB20_CON_B2_SUSPEND)) + return true; /* already waked it up */ + + usb3_clear_bit(usb3, USB20_CON_B2_SUSPEND, USB3_USB20_CON); + usb3_enable_irq_1(usb3, USB_INT_1_B2_RSUM); + + return false; +} + +static void usb3_usb2_pullup(struct renesas_usb3 *usb3, int pullup) +{ + u32 bits = USB20_CON_B2_PUE | USB20_CON_B2_CONNECT; + + if (usb3->softconnect && pullup) + usb3_set_bit(usb3, bits, USB3_USB20_CON); + else + usb3_clear_bit(usb3, bits, USB3_USB20_CON); +} + +static void usb3_set_test_mode(struct renesas_usb3 *usb3) +{ + u32 val = usb3_read(usb3, USB3_USB20_CON); + + val &= ~USB20_CON_B2_TSTMOD_MASK; + val |= USB20_CON_B2_TSTMOD(usb3->test_mode); + usb3_write(usb3, val | USB20_CON_B2_TSTMOD_EN, USB3_USB20_CON); + if (!usb3->test_mode) + usb3_clear_bit(usb3, USB20_CON_B2_TSTMOD_EN, USB3_USB20_CON); +} + +static void usb3_start_usb2_connection(struct renesas_usb3 *usb3) +{ + usb3->disabled_count++; + usb3_set_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + usb3_set_bit(usb3, USB_COM_CON_SPD_MODE, USB3_USB_COM_CON); + usb3_usb2_pullup(usb3, 1); +} + +static int usb3_is_usb3_phy_in_u3(struct renesas_usb3 *usb3) +{ + return usb3_read(usb3, USB3_USB30_CON) & USB30_CON_POW_SEL_IN_U3; +} + +static bool usb3_wakeup_usb3_phy(struct renesas_usb3 *usb3) +{ + if (!usb3_is_usb3_phy_in_u3(usb3)) + return true; /* already waked it up */ + + usb3_set_bit(usb3, USB30_CON_B3_PLLWAKE, USB3_USB30_CON); + usb3_enable_irq_1(usb3, USB_INT_1_B3_PLLWKUP); + + return false; +} + +static u16 usb3_feature_get_un_enabled(struct renesas_usb3 *usb3) +{ + u32 mask_u2 = SSIFCMD_UDIR_U2 | SSIFCMD_UREQ_U2; + u32 mask_u1 = SSIFCMD_UDIR_U1 | SSIFCMD_UREQ_U1; + u32 val = usb3_read(usb3, USB3_SSIFCMD); + u16 ret = 0; + + /* Enables {U2,U1} if the bits of UDIR and UREQ are set to 0 */ + if (!(val & mask_u2)) + ret |= 1 << USB_DEV_STAT_U2_ENABLED; + if (!(val & mask_u1)) + ret |= 1 << USB_DEV_STAT_U1_ENABLED; + + return ret; +} + +static void usb3_feature_u2_enable(struct renesas_usb3 *usb3, bool enable) +{ + u32 bits = SSIFCMD_UDIR_U2 | SSIFCMD_UREQ_U2; + + /* Enables U2 if the bits of UDIR and UREQ are set to 0 */ + if (enable) + usb3_clear_bit(usb3, bits, USB3_SSIFCMD); + else + usb3_set_bit(usb3, bits, USB3_SSIFCMD); +} + +static void usb3_feature_u1_enable(struct renesas_usb3 *usb3, bool enable) +{ + u32 bits = SSIFCMD_UDIR_U1 | SSIFCMD_UREQ_U1; + + /* Enables U1 if the bits of UDIR and UREQ are set to 0 */ + if (enable) + usb3_clear_bit(usb3, bits, USB3_SSIFCMD); + else + usb3_set_bit(usb3, bits, USB3_SSIFCMD); +} + +static void usb3_start_operation_for_usb3(struct renesas_usb3 *usb3) +{ + usb3_set_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + usb3_clear_bit(usb3, USB_COM_CON_SPD_MODE, USB3_USB_COM_CON); + usb3_set_bit(usb3, USB30_CON_B3_CONNECT, USB3_USB30_CON); +} + +static void usb3_start_usb3_connection(struct renesas_usb3 *usb3) +{ + usb3_start_operation_for_usb3(usb3); + usb3_set_bit(usb3, USB_COM_CON_RX_DETECTION, USB3_USB_COM_CON); + + usb3_enable_irq_1(usb3, USB_INT_1_B3_LUPSUCS | USB_INT_1_B3_DISABLE | + USB_INT_1_SPEED); +} + +static void usb3_stop_usb3_connection(struct renesas_usb3 *usb3) +{ + usb3_clear_bit(usb3, USB30_CON_B3_CONNECT, USB3_USB30_CON); +} + +static void usb3_transition_to_default_state(struct renesas_usb3 *usb3, + bool is_usb3) +{ + usb3_set_bit(usb3, USB_INT_2_PIPE(0), USB3_USB_INT_ENA_2); + usb3_write(usb3, P0_INT_ALL_BITS, USB3_P0_INT_STA); + usb3_set_bit(usb3, P0_INT_ALL_BITS, USB3_P0_INT_ENA); + + if (is_usb3) + usb3_enable_irq_1(usb3, USB_INT_1_B3_WRMRST | + USB_INT_1_B3_HOTRST); + else + usb3_enable_irq_1(usb3, USB_INT_1_B2_SPND | + USB_INT_1_B2_L1SPND | USB_INT_1_B2_USBRST); +} + +static void usb3_connect(struct renesas_usb3 *usb3) +{ + if (usb3_wakeup_usb3_phy(usb3)) + usb3_start_usb3_connection(usb3); +} + +static void usb3_reset_epc(struct renesas_usb3 *usb3) +{ + usb3_clear_bit(usb3, USB_COM_CON_CONF, USB3_USB_COM_CON); + usb3_clear_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + usb3_set_bit(usb3, USB_COM_CON_PIPE_CLR, USB3_USB_COM_CON); + usb3->test_mode = 0; + usb3_set_test_mode(usb3); +} + +static void usb3_disconnect(struct renesas_usb3 *usb3) +{ + usb3->disabled_count = 0; + usb3_usb2_pullup(usb3, 0); + usb3_clear_bit(usb3, USB30_CON_B3_CONNECT, USB3_USB30_CON); + usb3_reset_epc(usb3); + usb3_disable_irq_1(usb3, USB_INT_1_B2_RSUM | USB_INT_1_B3_PLLWKUP | + USB_INT_1_B3_LUPSUCS | USB_INT_1_B3_DISABLE | + USB_INT_1_SPEED | USB_INT_1_B3_WRMRST | + USB_INT_1_B3_HOTRST | USB_INT_1_B2_SPND | + USB_INT_1_B2_L1SPND | USB_INT_1_B2_USBRST); + usb3_clear_bit(usb3, USB_COM_CON_SPD_MODE, USB3_USB_COM_CON); + usb3_init_epc_registers(usb3); + + if (usb3->driver) + usb3->driver->disconnect(&usb3->gadget); +} + +static void usb3_check_vbus(struct renesas_usb3 *usb3) +{ + if (usb3->workaround_for_vbus) { + usb3_connect(usb3); + } else { + usb3->extcon_usb = !!(usb3_read(usb3, USB3_USB_STA) & + USB_STA_VBUS_STA); + if (usb3->extcon_usb) + usb3_connect(usb3); + else + usb3_disconnect(usb3); + + schedule_work(&usb3->extcon_work); + } +} + +static void renesas_usb3_role_work(struct work_struct *work) +{ + struct renesas_usb3 *usb3 = + container_of(work, struct renesas_usb3, role_work); + + usb_role_switch_set_role(usb3->role_sw, usb3->role); +} + +static void usb3_set_mode(struct renesas_usb3 *usb3, bool host) +{ + if (usb3->is_rzv2m) { + if (host) { + usb3_drd_set_bit(usb3, DRD_CON_PERI_RST, USB3_DRD_CON(usb3)); + usb3_drd_clear_bit(usb3, DRD_CON_HOST_RST, USB3_DRD_CON(usb3)); + } else { + usb3_drd_set_bit(usb3, DRD_CON_HOST_RST, USB3_DRD_CON(usb3)); + usb3_drd_clear_bit(usb3, DRD_CON_PERI_RST, USB3_DRD_CON(usb3)); + } + } + + if (host) + usb3_drd_clear_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON(usb3)); + else + usb3_drd_set_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON(usb3)); +} + +static void usb3_set_mode_by_role_sw(struct renesas_usb3 *usb3, bool host) +{ + if (usb3->role_sw) { + usb3->role = host ? USB_ROLE_HOST : USB_ROLE_DEVICE; + schedule_work(&usb3->role_work); + } else { + usb3_set_mode(usb3, host); + } +} + +static void usb3_vbus_out(struct renesas_usb3 *usb3, bool enable) +{ + if (enable) + usb3_drd_set_bit(usb3, DRD_CON_VBOUT, USB3_DRD_CON(usb3)); + else + usb3_drd_clear_bit(usb3, DRD_CON_VBOUT, USB3_DRD_CON(usb3)); +} + +static void usb3_mode_config(struct renesas_usb3 *usb3, bool host, bool a_dev) +{ + unsigned long flags; + + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3->role_sw_by_connector || + usb3->connection_state != USB_ROLE_NONE) { + usb3_set_mode_by_role_sw(usb3, host); + usb3_vbus_out(usb3, a_dev); + } + /* for A-Peripheral or forced B-device mode */ + if ((!host && a_dev) || usb3->start_to_connect) + usb3_connect(usb3); + spin_unlock_irqrestore(&usb3->lock, flags); +} + +static bool usb3_is_a_device(struct renesas_usb3 *usb3) +{ + return !(usb3_drd_read(usb3, USB3_USB_OTG_STA(usb3)) & USB_OTG_IDMON(usb3)); +} + +static void usb3_check_id(struct renesas_usb3 *usb3) +{ + usb3->extcon_host = usb3_is_a_device(usb3); + + if ((!usb3->role_sw_by_connector && usb3->extcon_host && + !usb3->forced_b_device) || usb3->connection_state == USB_ROLE_HOST) + usb3_mode_config(usb3, true, true); + else + usb3_mode_config(usb3, false, false); + + schedule_work(&usb3->extcon_work); +} + +static void renesas_usb3_init_controller(struct renesas_usb3 *usb3) +{ + usb3_init_axi_bridge(usb3); + usb3_init_epc_registers(usb3); + usb3_set_bit(usb3, USB_COM_CON_PN_WDATAIF_NL | + USB_COM_CON_PN_RDATAIF_NL | USB_COM_CON_PN_LSTTR_PP, + USB3_USB_COM_CON); + usb3_drd_write(usb3, USB_OTG_IDMON(usb3), USB3_USB_OTG_INT_STA(usb3)); + usb3_drd_write(usb3, USB_OTG_IDMON(usb3), USB3_USB_OTG_INT_ENA(usb3)); + + usb3_check_id(usb3); + usb3_check_vbus(usb3); +} + +static void renesas_usb3_stop_controller(struct renesas_usb3 *usb3) +{ + usb3_disconnect(usb3); + usb3_write(usb3, 0, USB3_P0_INT_ENA); + usb3_drd_write(usb3, 0, USB3_USB_OTG_INT_ENA(usb3)); + usb3_write(usb3, 0, USB3_USB_INT_ENA_1); + usb3_write(usb3, 0, USB3_USB_INT_ENA_2); + usb3_write(usb3, 0, USB3_AXI_INT_ENA); +} + +static void usb3_irq_epc_int_1_pll_wakeup(struct renesas_usb3 *usb3) +{ + usb3_disable_irq_1(usb3, USB_INT_1_B3_PLLWKUP); + usb3_clear_bit(usb3, USB30_CON_B3_PLLWAKE, USB3_USB30_CON); + usb3_start_usb3_connection(usb3); +} + +static void usb3_irq_epc_int_1_linkup_success(struct renesas_usb3 *usb3) +{ + usb3_transition_to_default_state(usb3, true); +} + +static void usb3_irq_epc_int_1_resume(struct renesas_usb3 *usb3) +{ + usb3_disable_irq_1(usb3, USB_INT_1_B2_RSUM); + usb3_start_usb2_connection(usb3); + usb3_transition_to_default_state(usb3, false); +} + +static void usb3_irq_epc_int_1_suspend(struct renesas_usb3 *usb3) +{ + usb3_disable_irq_1(usb3, USB_INT_1_B2_SPND); + + if (usb3->gadget.speed != USB_SPEED_UNKNOWN && + usb3->gadget.state != USB_STATE_NOTATTACHED) { + if (usb3->driver && usb3->driver->suspend) + usb3->driver->suspend(&usb3->gadget); + usb_gadget_set_state(&usb3->gadget, USB_STATE_SUSPENDED); + } +} + +static void usb3_irq_epc_int_1_disable(struct renesas_usb3 *usb3) +{ + usb3_stop_usb3_connection(usb3); + if (usb3_wakeup_usb2_phy(usb3)) + usb3_irq_epc_int_1_resume(usb3); +} + +static void usb3_irq_epc_int_1_bus_reset(struct renesas_usb3 *usb3) +{ + usb3_reset_epc(usb3); + if (usb3->disabled_count < 3) + usb3_start_usb3_connection(usb3); + else + usb3_start_usb2_connection(usb3); +} + +static void usb3_irq_epc_int_1_vbus_change(struct renesas_usb3 *usb3) +{ + usb3_check_vbus(usb3); +} + +static void usb3_irq_epc_int_1_hot_reset(struct renesas_usb3 *usb3) +{ + usb3_reset_epc(usb3); + usb3_set_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + + /* This bit shall be set within 12ms from the start of HotReset */ + usb3_set_bit(usb3, USB30_CON_B3_HOTRST_CMP, USB3_USB30_CON); +} + +static void usb3_irq_epc_int_1_warm_reset(struct renesas_usb3 *usb3) +{ + usb3_reset_epc(usb3); + usb3_set_bit(usb3, USB_COM_CON_EP0_EN, USB3_USB_COM_CON); + + usb3_start_operation_for_usb3(usb3); + usb3_enable_irq_1(usb3, USB_INT_1_SPEED); +} + +static void usb3_irq_epc_int_1_speed(struct renesas_usb3 *usb3) +{ + u32 speed = usb3_read(usb3, USB3_USB_STA) & USB_STA_SPEED_MASK; + + switch (speed) { + case USB_STA_SPEED_SS: + usb3->gadget.speed = USB_SPEED_SUPER; + usb3->gadget.ep0->maxpacket = USB3_EP0_SS_MAX_PACKET_SIZE; + break; + case USB_STA_SPEED_HS: + usb3->gadget.speed = USB_SPEED_HIGH; + usb3->gadget.ep0->maxpacket = USB3_EP0_HSFS_MAX_PACKET_SIZE; + break; + case USB_STA_SPEED_FS: + usb3->gadget.speed = USB_SPEED_FULL; + usb3->gadget.ep0->maxpacket = USB3_EP0_HSFS_MAX_PACKET_SIZE; + break; + default: + usb3->gadget.speed = USB_SPEED_UNKNOWN; + break; + } +} + +static void usb3_irq_epc_int_1(struct renesas_usb3 *usb3, u32 int_sta_1) +{ + if (int_sta_1 & USB_INT_1_B3_PLLWKUP) + usb3_irq_epc_int_1_pll_wakeup(usb3); + + if (int_sta_1 & USB_INT_1_B3_LUPSUCS) + usb3_irq_epc_int_1_linkup_success(usb3); + + if (int_sta_1 & USB_INT_1_B3_HOTRST) + usb3_irq_epc_int_1_hot_reset(usb3); + + if (int_sta_1 & USB_INT_1_B3_WRMRST) + usb3_irq_epc_int_1_warm_reset(usb3); + + if (int_sta_1 & USB_INT_1_B3_DISABLE) + usb3_irq_epc_int_1_disable(usb3); + + if (int_sta_1 & USB_INT_1_B2_USBRST) + usb3_irq_epc_int_1_bus_reset(usb3); + + if (int_sta_1 & USB_INT_1_B2_RSUM) + usb3_irq_epc_int_1_resume(usb3); + + if (int_sta_1 & USB_INT_1_B2_SPND) + usb3_irq_epc_int_1_suspend(usb3); + + if (int_sta_1 & USB_INT_1_SPEED) + usb3_irq_epc_int_1_speed(usb3); + + if (int_sta_1 & USB_INT_1_VBUS_CNG) + usb3_irq_epc_int_1_vbus_change(usb3); +} + +static struct renesas_usb3_request *__usb3_get_request(struct renesas_usb3_ep + *usb3_ep) +{ + return list_first_entry_or_null(&usb3_ep->queue, + struct renesas_usb3_request, queue); +} + +static struct renesas_usb3_request *usb3_get_request(struct renesas_usb3_ep + *usb3_ep) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + struct renesas_usb3_request *usb3_req; + unsigned long flags; + + spin_lock_irqsave(&usb3->lock, flags); + usb3_req = __usb3_get_request(usb3_ep); + spin_unlock_irqrestore(&usb3->lock, flags); + + return usb3_req; +} + +static void __usb3_request_done(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req, + int status) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + dev_dbg(usb3_to_dev(usb3), "giveback: ep%2d, %u, %u, %d\n", + usb3_ep->num, usb3_req->req.length, usb3_req->req.actual, + status); + usb3_req->req.status = status; + usb3_ep->started = false; + list_del_init(&usb3_req->queue); + spin_unlock(&usb3->lock); + usb_gadget_giveback_request(&usb3_ep->ep, &usb3_req->req); + spin_lock(&usb3->lock); +} + +static void usb3_request_done(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req, int status) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + spin_lock_irqsave(&usb3->lock, flags); + __usb3_request_done(usb3_ep, usb3_req, status); + spin_unlock_irqrestore(&usb3->lock, flags); +} + +static void usb3_irq_epc_pipe0_status_end(struct renesas_usb3 *usb3) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + struct renesas_usb3_request *usb3_req = usb3_get_request(usb3_ep); + + if (usb3_req) + usb3_request_done(usb3_ep, usb3_req, 0); + if (usb3->test_mode) + usb3_set_test_mode(usb3); +} + +static void usb3_get_setup_data(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + u32 *data = (u32 *)ctrl; + + *data++ = usb3_read(usb3, USB3_STUP_DAT_0); + *data = usb3_read(usb3, USB3_STUP_DAT_1); + + /* update this driver's flag */ + usb3_ep->dir_in = !!(ctrl->bRequestType & USB_DIR_IN); +} + +static void usb3_set_p0_con_update_res(struct renesas_usb3 *usb3, u32 res) +{ + u32 val = usb3_read(usb3, USB3_P0_CON); + + val &= ~(P0_CON_ST_RES_MASK | P0_CON_OT_RES_MASK | P0_CON_IN_RES_MASK); + val |= res | P0_CON_RES_WEN; + usb3_write(usb3, val, USB3_P0_CON); +} + +static void usb3_set_p0_con_for_ctrl_read_data(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_FORCE_NRDY | + P0_CON_OT_RES_FORCE_STALL | + P0_CON_IN_RES_NORMAL); +} + +static void usb3_set_p0_con_for_ctrl_read_status(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_NORMAL | + P0_CON_OT_RES_FORCE_STALL | + P0_CON_IN_RES_NORMAL); +} + +static void usb3_set_p0_con_for_ctrl_write_data(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_FORCE_NRDY | + P0_CON_OT_RES_NORMAL | + P0_CON_IN_RES_FORCE_STALL); +} + +static void usb3_set_p0_con_for_ctrl_write_status(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_NORMAL | + P0_CON_OT_RES_NORMAL | + P0_CON_IN_RES_FORCE_STALL); +} + +static void usb3_set_p0_con_for_no_data(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_NORMAL | + P0_CON_OT_RES_FORCE_STALL | + P0_CON_IN_RES_FORCE_STALL); +} + +static void usb3_set_p0_con_stall(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_FORCE_STALL | + P0_CON_OT_RES_FORCE_STALL | + P0_CON_IN_RES_FORCE_STALL); +} + +static void usb3_set_p0_con_stop(struct renesas_usb3 *usb3) +{ + usb3_set_p0_con_update_res(usb3, P0_CON_ST_RES_FORCE_NRDY | + P0_CON_OT_RES_FORCE_NRDY | + P0_CON_IN_RES_FORCE_NRDY); +} + +static int usb3_pn_change(struct renesas_usb3 *usb3, int num) +{ + if (num == 0 || num > usb3->num_usb3_eps) + return -ENXIO; + + usb3_write(usb3, num, USB3_PIPE_COM); + + return 0; +} + +static void usb3_set_pn_con_update_res(struct renesas_usb3 *usb3, u32 res) +{ + u32 val = usb3_read(usb3, USB3_PN_CON); + + val &= ~PN_CON_RES_MASK; + val |= res & PN_CON_RES_MASK; + val |= PN_CON_RES_WEN; + usb3_write(usb3, val, USB3_PN_CON); +} + +static void usb3_pn_start(struct renesas_usb3 *usb3) +{ + usb3_set_pn_con_update_res(usb3, PN_CON_RES_NORMAL); +} + +static void usb3_pn_stop(struct renesas_usb3 *usb3) +{ + usb3_set_pn_con_update_res(usb3, PN_CON_RES_FORCE_NRDY); +} + +static void usb3_pn_stall(struct renesas_usb3 *usb3) +{ + usb3_set_pn_con_update_res(usb3, PN_CON_RES_FORCE_STALL); +} + +static int usb3_pn_con_clear(struct renesas_usb3 *usb3) +{ + usb3_set_bit(usb3, PN_CON_CLR, USB3_PN_CON); + + return usb3_wait(usb3, USB3_PN_CON, PN_CON_CLR, 0); +} + +static bool usb3_is_transfer_complete(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct usb_request *req = &usb3_req->req; + + if ((!req->zero && req->actual == req->length) || + (req->actual % usb3_ep->ep.maxpacket) || (req->length == 0)) + return true; + else + return false; +} + +static int usb3_wait_pipe_status(struct renesas_usb3_ep *usb3_ep, u32 mask) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 sta_reg = usb3_ep->num ? USB3_PN_STA : USB3_P0_STA; + + return usb3_wait(usb3, sta_reg, mask, mask); +} + +static void usb3_set_px_con_send(struct renesas_usb3_ep *usb3_ep, int bytes, + bool last) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 con_reg = usb3_ep->num ? USB3_PN_CON : USB3_P0_CON; + u32 val = usb3_read(usb3, con_reg); + + val |= PX_CON_SEND | PX_CON_BYTE_EN_BYTES(bytes); + val |= (usb3_ep->num && last) ? PN_CON_LAST : 0; + usb3_write(usb3, val, con_reg); +} + +static int usb3_write_pipe(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req, + u32 fifo_reg) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + int i; + int len = min_t(unsigned, usb3_req->req.length - usb3_req->req.actual, + usb3_ep->ep.maxpacket); + u8 *buf = usb3_req->req.buf + usb3_req->req.actual; + u32 tmp = 0; + bool is_last = !len ? true : false; + + if (usb3_wait_pipe_status(usb3_ep, PX_STA_BUFSTS) < 0) + return -EBUSY; + + /* Update gadget driver parameter */ + usb3_req->req.actual += len; + + /* Write data to the register */ + if (len >= 4) { + iowrite32_rep(usb3->reg + fifo_reg, buf, len / 4); + buf += (len / 4) * 4; + len %= 4; /* update len to use usb3_set_pX_con_send() */ + } + + if (len) { + for (i = 0; i < len; i++) + tmp |= buf[i] << (8 * i); + usb3_write(usb3, tmp, fifo_reg); + } + + if (!is_last) + is_last = usb3_is_transfer_complete(usb3_ep, usb3_req); + /* Send the data */ + usb3_set_px_con_send(usb3_ep, len, is_last); + + return is_last ? 0 : -EAGAIN; +} + +static u32 usb3_get_received_length(struct renesas_usb3_ep *usb3_ep) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 lng_reg = usb3_ep->num ? USB3_PN_LNG : USB3_P0_LNG; + + return usb3_read(usb3, lng_reg); +} + +static int usb3_read_pipe(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req, u32 fifo_reg) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + int i; + int len = min_t(unsigned, usb3_req->req.length - usb3_req->req.actual, + usb3_get_received_length(usb3_ep)); + u8 *buf = usb3_req->req.buf + usb3_req->req.actual; + u32 tmp = 0; + + if (!len) + return 0; + + /* Update gadget driver parameter */ + usb3_req->req.actual += len; + + /* Read data from the register */ + if (len >= 4) { + ioread32_rep(usb3->reg + fifo_reg, buf, len / 4); + buf += (len / 4) * 4; + len %= 4; + } + + if (len) { + tmp = usb3_read(usb3, fifo_reg); + for (i = 0; i < len; i++) + buf[i] = (tmp >> (8 * i)) & 0xff; + } + + return usb3_is_transfer_complete(usb3_ep, usb3_req) ? 0 : -EAGAIN; +} + +static void usb3_set_status_stage(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + if (usb3_ep->dir_in) { + usb3_set_p0_con_for_ctrl_read_status(usb3); + } else { + if (!usb3_req->req.length) + usb3_set_p0_con_for_no_data(usb3); + else + usb3_set_p0_con_for_ctrl_write_status(usb3); + } +} + +static void usb3_p0_xfer(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + int ret; + + if (usb3_ep->dir_in) + ret = usb3_write_pipe(usb3_ep, usb3_req, USB3_P0_WRITE); + else + ret = usb3_read_pipe(usb3_ep, usb3_req, USB3_P0_READ); + + if (!ret) + usb3_set_status_stage(usb3_ep, usb3_req); +} + +static void usb3_start_pipe0(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + if (usb3_ep->started) + return; + + usb3_ep->started = true; + + if (usb3_ep->dir_in) { + usb3_set_bit(usb3, P0_MOD_DIR, USB3_P0_MOD); + usb3_set_p0_con_for_ctrl_read_data(usb3); + } else { + usb3_clear_bit(usb3, P0_MOD_DIR, USB3_P0_MOD); + if (usb3_req->req.length) + usb3_set_p0_con_for_ctrl_write_data(usb3); + } + + usb3_p0_xfer(usb3_ep, usb3_req); +} + +static void usb3_enable_dma_pipen(struct renesas_usb3 *usb3) +{ + usb3_set_bit(usb3, PN_CON_DATAIF_EN, USB3_PN_CON); +} + +static void usb3_disable_dma_pipen(struct renesas_usb3 *usb3) +{ + usb3_clear_bit(usb3, PN_CON_DATAIF_EN, USB3_PN_CON); +} + +static void usb3_enable_dma_irq(struct renesas_usb3 *usb3, int num) +{ + usb3_set_bit(usb3, DMA_INT(num), USB3_DMA_INT_ENA); +} + +static void usb3_disable_dma_irq(struct renesas_usb3 *usb3, int num) +{ + usb3_clear_bit(usb3, DMA_INT(num), USB3_DMA_INT_ENA); +} + +static u32 usb3_dma_mps_to_prd_word1(struct renesas_usb3_ep *usb3_ep) +{ + switch (usb3_ep->ep.maxpacket) { + case 8: + return USB3_PRD1_MPS_8; + case 16: + return USB3_PRD1_MPS_16; + case 32: + return USB3_PRD1_MPS_32; + case 64: + return USB3_PRD1_MPS_64; + case 512: + return USB3_PRD1_MPS_512; + case 1024: + return USB3_PRD1_MPS_1024; + default: + return USB3_PRD1_MPS_RESERVED; + } +} + +static bool usb3_dma_get_setting_area(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + struct renesas_usb3_dma *dma; + int i; + bool ret = false; + + if (usb3_req->req.length > USB3_DMA_MAX_XFER_SIZE_ALL_PRDS) { + dev_dbg(usb3_to_dev(usb3), "%s: the length is too big (%d)\n", + __func__, usb3_req->req.length); + return false; + } + + /* The driver doesn't handle zero-length packet via dmac */ + if (!usb3_req->req.length) + return false; + + if (usb3_dma_mps_to_prd_word1(usb3_ep) == USB3_PRD1_MPS_RESERVED) + return false; + + usb3_for_each_dma(usb3, dma, i) { + if (dma->used) + continue; + + if (usb_gadget_map_request(&usb3->gadget, &usb3_req->req, + usb3_ep->dir_in) < 0) + break; + + dma->used = true; + usb3_ep->dma = dma; + ret = true; + break; + } + + return ret; +} + +static void usb3_dma_put_setting_area(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + int i; + struct renesas_usb3_dma *dma; + + usb3_for_each_dma(usb3, dma, i) { + if (usb3_ep->dma == dma) { + usb_gadget_unmap_request(&usb3->gadget, &usb3_req->req, + usb3_ep->dir_in); + dma->used = false; + usb3_ep->dma = NULL; + break; + } + } +} + +static void usb3_dma_fill_prd(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3_prd *cur_prd = usb3_ep->dma->prd; + u32 remain = usb3_req->req.length; + u32 dma = usb3_req->req.dma; + u32 len; + int i = 0; + + do { + len = min_t(u32, remain, USB3_DMA_MAX_XFER_SIZE) & + USB3_PRD1_SIZE_MASK; + cur_prd->word1 = usb3_dma_mps_to_prd_word1(usb3_ep) | + USB3_PRD1_B_INC | len; + cur_prd->bap = dma; + remain -= len; + dma += len; + if (!remain || (i + 1) < USB3_DMA_NUM_PRD_ENTRIES) + break; + + cur_prd++; + i++; + } while (1); + + cur_prd->word1 |= USB3_PRD1_E | USB3_PRD1_INT; + if (usb3_ep->dir_in) + cur_prd->word1 |= USB3_PRD1_LST; +} + +static void usb3_dma_kick_prd(struct renesas_usb3_ep *usb3_ep) +{ + struct renesas_usb3_dma *dma = usb3_ep->dma; + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 dma_con = DMA_COM_PIPE_NO(usb3_ep->num) | DMA_CON_PRD_EN; + + if (usb3_ep->dir_in) + dma_con |= DMA_CON_PIPE_DIR; + + wmb(); /* prd entries should be in system memory here */ + + usb3_write(usb3, 1 << usb3_ep->num, USB3_DMA_INT_STA); + usb3_write(usb3, AXI_INT_PRDEN_CLR_STA(dma->num) | + AXI_INT_PRDERR_STA(dma->num), USB3_AXI_INT_STA); + + usb3_write(usb3, dma->prd_dma, USB3_DMA_CH0_PRD_ADR(dma->num)); + usb3_write(usb3, dma_con, USB3_DMA_CH0_CON(dma->num)); + usb3_enable_dma_irq(usb3, usb3_ep->num); +} + +static void usb3_dma_stop_prd(struct renesas_usb3_ep *usb3_ep) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + struct renesas_usb3_dma *dma = usb3_ep->dma; + + usb3_disable_dma_irq(usb3, usb3_ep->num); + usb3_write(usb3, 0, USB3_DMA_CH0_CON(dma->num)); +} + +static int usb3_dma_update_status(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3_prd *cur_prd = usb3_ep->dma->prd; + struct usb_request *req = &usb3_req->req; + u32 remain, len; + int i = 0; + int status = 0; + + rmb(); /* The controller updated prd entries */ + + do { + if (cur_prd->word1 & USB3_PRD1_D) + status = -EIO; + if (cur_prd->word1 & USB3_PRD1_E) + len = req->length % USB3_DMA_MAX_XFER_SIZE; + else + len = USB3_DMA_MAX_XFER_SIZE; + remain = cur_prd->word1 & USB3_PRD1_SIZE_MASK; + req->actual += len - remain; + + if (cur_prd->word1 & USB3_PRD1_E || + (i + 1) < USB3_DMA_NUM_PRD_ENTRIES) + break; + + cur_prd++; + i++; + } while (1); + + return status; +} + +static bool usb3_dma_try_start(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + if (!use_dma) + return false; + + if (usb3_dma_get_setting_area(usb3_ep, usb3_req)) { + usb3_pn_stop(usb3); + usb3_enable_dma_pipen(usb3); + usb3_dma_fill_prd(usb3_ep, usb3_req); + usb3_dma_kick_prd(usb3_ep); + usb3_pn_start(usb3); + return true; + } + + return false; +} + +static int usb3_dma_try_stop(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + int status = 0; + + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_ep->dma) + goto out; + + if (!usb3_pn_change(usb3, usb3_ep->num)) + usb3_disable_dma_pipen(usb3); + usb3_dma_stop_prd(usb3_ep); + status = usb3_dma_update_status(usb3_ep, usb3_req); + usb3_dma_put_setting_area(usb3_ep, usb3_req); + +out: + spin_unlock_irqrestore(&usb3->lock, flags); + return status; +} + +static int renesas_usb3_dma_free_prd(struct renesas_usb3 *usb3, + struct device *dev) +{ + int i; + struct renesas_usb3_dma *dma; + + usb3_for_each_dma(usb3, dma, i) { + if (dma->prd) { + dma_free_coherent(dev, USB3_DMA_PRD_SIZE, + dma->prd, dma->prd_dma); + dma->prd = NULL; + } + } + + return 0; +} + +static int renesas_usb3_dma_alloc_prd(struct renesas_usb3 *usb3, + struct device *dev) +{ + int i; + struct renesas_usb3_dma *dma; + + if (!use_dma) + return 0; + + usb3_for_each_dma(usb3, dma, i) { + dma->prd = dma_alloc_coherent(dev, USB3_DMA_PRD_SIZE, + &dma->prd_dma, GFP_KERNEL); + if (!dma->prd) { + renesas_usb3_dma_free_prd(usb3, dev); + return -ENOMEM; + } + dma->num = i + 1; + } + + return 0; +} + +static void usb3_start_pipen(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + struct renesas_usb3_request *usb3_req_first; + unsigned long flags; + int ret = -EAGAIN; + u32 enable_bits = 0; + + spin_lock_irqsave(&usb3->lock, flags); + if (usb3_ep->halt || usb3_ep->started) + goto out; + usb3_req_first = __usb3_get_request(usb3_ep); + if (!usb3_req_first || usb3_req != usb3_req_first) + goto out; + + if (usb3_pn_change(usb3, usb3_ep->num) < 0) + goto out; + + usb3_ep->started = true; + + if (usb3_dma_try_start(usb3_ep, usb3_req)) + goto out; + + usb3_pn_start(usb3); + + if (usb3_ep->dir_in) { + ret = usb3_write_pipe(usb3_ep, usb3_req, USB3_PN_WRITE); + enable_bits |= PN_INT_LSTTR; + } + + if (ret < 0) + enable_bits |= PN_INT_BFRDY; + + if (enable_bits) { + usb3_set_bit(usb3, enable_bits, USB3_PN_INT_ENA); + usb3_enable_pipe_irq(usb3, usb3_ep->num); + } +out: + spin_unlock_irqrestore(&usb3->lock, flags); +} + +static int renesas_usb3_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + struct renesas_usb3_request *usb3_req = usb_req_to_usb3_req(_req); + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + dev_dbg(usb3_to_dev(usb3), "ep_queue: ep%2d, %u\n", usb3_ep->num, + _req->length); + + _req->status = -EINPROGRESS; + _req->actual = 0; + spin_lock_irqsave(&usb3->lock, flags); + list_add_tail(&usb3_req->queue, &usb3_ep->queue); + spin_unlock_irqrestore(&usb3->lock, flags); + + if (!usb3_ep->num) + usb3_start_pipe0(usb3_ep, usb3_req); + else + usb3_start_pipen(usb3_ep, usb3_req); + + return 0; +} + +static void usb3_set_device_address(struct renesas_usb3 *usb3, u16 addr) +{ + /* DEV_ADDR bit field is cleared by WarmReset, HotReset and BusReset */ + usb3_set_bit(usb3, USB_COM_CON_DEV_ADDR(addr), USB3_USB_COM_CON); +} + +static bool usb3_std_req_set_address(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + if (le16_to_cpu(ctrl->wValue) >= 128) + return true; /* stall */ + + usb3_set_device_address(usb3, le16_to_cpu(ctrl->wValue)); + usb3_set_p0_con_for_no_data(usb3); + + return false; +} + +static void usb3_pipe0_internal_xfer(struct renesas_usb3 *usb3, + void *tx_data, size_t len, + void (*complete)(struct usb_ep *ep, + struct usb_request *req)) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + + if (tx_data) + memcpy(usb3->ep0_buf, tx_data, + min_t(size_t, len, USB3_EP0_BUF_SIZE)); + + usb3->ep0_req->buf = &usb3->ep0_buf; + usb3->ep0_req->length = len; + usb3->ep0_req->complete = complete; + renesas_usb3_ep_queue(&usb3_ep->ep, usb3->ep0_req, GFP_ATOMIC); +} + +static void usb3_pipe0_get_status_completion(struct usb_ep *ep, + struct usb_request *req) +{ +} + +static bool usb3_std_req_get_status(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + bool stall = false; + struct renesas_usb3_ep *usb3_ep; + int num; + u16 status = 0; + __le16 tx_data; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + if (usb3->gadget.is_selfpowered) + status |= 1 << USB_DEVICE_SELF_POWERED; + if (usb3->gadget.speed == USB_SPEED_SUPER) + status |= usb3_feature_get_un_enabled(usb3); + break; + case USB_RECIP_INTERFACE: + break; + case USB_RECIP_ENDPOINT: + num = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; + if (num >= usb3->num_usb3_eps) { + stall = true; + break; + } + usb3_ep = usb3_get_ep(usb3, num); + if (usb3_ep->halt) + status |= 1 << USB_ENDPOINT_HALT; + break; + default: + stall = true; + break; + } + + if (!stall) { + tx_data = cpu_to_le16(status); + dev_dbg(usb3_to_dev(usb3), "get_status: req = %p\n", + usb_req_to_usb3_req(usb3->ep0_req)); + usb3_pipe0_internal_xfer(usb3, &tx_data, sizeof(tx_data), + usb3_pipe0_get_status_completion); + } + + return stall; +} + +static bool usb3_std_req_feature_device(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl, bool set) +{ + bool stall = true; + u16 w_value = le16_to_cpu(ctrl->wValue); + + switch (w_value) { + case USB_DEVICE_TEST_MODE: + if (!set) + break; + usb3->test_mode = le16_to_cpu(ctrl->wIndex) >> 8; + stall = false; + break; + case USB_DEVICE_U1_ENABLE: + case USB_DEVICE_U2_ENABLE: + if (usb3->gadget.speed != USB_SPEED_SUPER) + break; + if (w_value == USB_DEVICE_U1_ENABLE) + usb3_feature_u1_enable(usb3, set); + if (w_value == USB_DEVICE_U2_ENABLE) + usb3_feature_u2_enable(usb3, set); + stall = false; + break; + default: + break; + } + + return stall; +} + +static int usb3_set_halt_p0(struct renesas_usb3_ep *usb3_ep, bool halt) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + if (unlikely(usb3_ep->num)) + return -EINVAL; + + usb3_ep->halt = halt; + if (halt) + usb3_set_p0_con_stall(usb3); + else + usb3_set_p0_con_stop(usb3); + + return 0; +} + +static int usb3_set_halt_pn(struct renesas_usb3_ep *usb3_ep, bool halt, + bool is_clear_feature) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_pn_change(usb3, usb3_ep->num)) { + usb3_ep->halt = halt; + if (halt) { + usb3_pn_stall(usb3); + } else if (!is_clear_feature || !usb3_ep->wedge) { + usb3_pn_con_clear(usb3); + usb3_set_bit(usb3, PN_CON_EN, USB3_PN_CON); + usb3_pn_stop(usb3); + } + } + spin_unlock_irqrestore(&usb3->lock, flags); + + return 0; +} + +static int usb3_set_halt(struct renesas_usb3_ep *usb3_ep, bool halt, + bool is_clear_feature) +{ + int ret = 0; + + if (halt && usb3_ep->started) + return -EAGAIN; + + if (usb3_ep->num) + ret = usb3_set_halt_pn(usb3_ep, halt, is_clear_feature); + else + ret = usb3_set_halt_p0(usb3_ep, halt); + + return ret; +} + +static bool usb3_std_req_feature_endpoint(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl, + bool set) +{ + int num = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; + struct renesas_usb3_ep *usb3_ep; + struct renesas_usb3_request *usb3_req; + + if ((le16_to_cpu(ctrl->wValue) != USB_ENDPOINT_HALT) || + (num >= usb3->num_usb3_eps)) + return true; /* stall */ + + usb3_ep = usb3_get_ep(usb3, num); + usb3_set_halt(usb3_ep, set, true); + + /* Restarts a queue if clear feature */ + if (!set) { + usb3_ep->started = false; + usb3_req = usb3_get_request(usb3_ep); + if (usb3_req) + usb3_start_pipen(usb3_ep, usb3_req); + } + + return false; +} + +static bool usb3_std_req_feature(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl, bool set) +{ + bool stall = false; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + stall = usb3_std_req_feature_device(usb3, ctrl, set); + break; + case USB_RECIP_INTERFACE: + break; + case USB_RECIP_ENDPOINT: + stall = usb3_std_req_feature_endpoint(usb3, ctrl, set); + break; + default: + stall = true; + break; + } + + if (!stall) + usb3_set_p0_con_for_no_data(usb3); + + return stall; +} + +static void usb3_pipe0_set_sel_completion(struct usb_ep *ep, + struct usb_request *req) +{ + /* TODO */ +} + +static bool usb3_std_req_set_sel(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + u16 w_length = le16_to_cpu(ctrl->wLength); + + if (w_length != 6) + return true; /* stall */ + + dev_dbg(usb3_to_dev(usb3), "set_sel: req = %p\n", + usb_req_to_usb3_req(usb3->ep0_req)); + usb3_pipe0_internal_xfer(usb3, NULL, 6, usb3_pipe0_set_sel_completion); + + return false; +} + +static bool usb3_std_req_set_configuration(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + if (le16_to_cpu(ctrl->wValue) > 0) + usb3_set_bit(usb3, USB_COM_CON_CONF, USB3_USB_COM_CON); + else + usb3_clear_bit(usb3, USB_COM_CON_CONF, USB3_USB_COM_CON); + + return false; +} + +/** + * usb3_handle_standard_request - handle some standard requests + * @usb3: the renesas_usb3 pointer + * @ctrl: a pointer of setup data + * + * Returns true if this function handled a standard request + */ +static bool usb3_handle_standard_request(struct renesas_usb3 *usb3, + struct usb_ctrlrequest *ctrl) +{ + bool ret = false; + bool stall = false; + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + stall = usb3_std_req_set_address(usb3, ctrl); + ret = true; + break; + case USB_REQ_GET_STATUS: + stall = usb3_std_req_get_status(usb3, ctrl); + ret = true; + break; + case USB_REQ_CLEAR_FEATURE: + stall = usb3_std_req_feature(usb3, ctrl, false); + ret = true; + break; + case USB_REQ_SET_FEATURE: + stall = usb3_std_req_feature(usb3, ctrl, true); + ret = true; + break; + case USB_REQ_SET_SEL: + stall = usb3_std_req_set_sel(usb3, ctrl); + ret = true; + break; + case USB_REQ_SET_ISOCH_DELAY: + /* This hardware doesn't support Isochronous xfer */ + stall = true; + ret = true; + break; + case USB_REQ_SET_CONFIGURATION: + usb3_std_req_set_configuration(usb3, ctrl); + break; + default: + break; + } + } + + if (stall) + usb3_set_p0_con_stall(usb3); + + return ret; +} + +static int usb3_p0_con_clear_buffer(struct renesas_usb3 *usb3) +{ + usb3_set_bit(usb3, P0_CON_BCLR, USB3_P0_CON); + + return usb3_wait(usb3, USB3_P0_CON, P0_CON_BCLR, 0); +} + +static void usb3_irq_epc_pipe0_setup(struct renesas_usb3 *usb3) +{ + struct usb_ctrlrequest ctrl; + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + + /* Call giveback function if previous transfer is not completed */ + if (usb3_ep->started) + usb3_request_done(usb3_ep, usb3_get_request(usb3_ep), + -ECONNRESET); + + usb3_p0_con_clear_buffer(usb3); + usb3_get_setup_data(usb3, &ctrl); + if (!usb3_handle_standard_request(usb3, &ctrl)) + if (usb3->driver->setup(&usb3->gadget, &ctrl) < 0) + usb3_set_p0_con_stall(usb3); +} + +static void usb3_irq_epc_pipe0_bfrdy(struct renesas_usb3 *usb3) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, 0); + struct renesas_usb3_request *usb3_req = usb3_get_request(usb3_ep); + + if (!usb3_req) + return; + + usb3_p0_xfer(usb3_ep, usb3_req); +} + +static void usb3_irq_epc_pipe0(struct renesas_usb3 *usb3) +{ + u32 p0_int_sta = usb3_read(usb3, USB3_P0_INT_STA); + + p0_int_sta &= usb3_read(usb3, USB3_P0_INT_ENA); + usb3_write(usb3, p0_int_sta, USB3_P0_INT_STA); + if (p0_int_sta & P0_INT_STSED) + usb3_irq_epc_pipe0_status_end(usb3); + if (p0_int_sta & P0_INT_SETUP) + usb3_irq_epc_pipe0_setup(usb3); + if (p0_int_sta & P0_INT_BFRDY) + usb3_irq_epc_pipe0_bfrdy(usb3); +} + +static void usb3_request_done_pipen(struct renesas_usb3 *usb3, + struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req, + int status) +{ + unsigned long flags; + + spin_lock_irqsave(&usb3->lock, flags); + if (usb3_pn_change(usb3, usb3_ep->num)) + usb3_pn_stop(usb3); + spin_unlock_irqrestore(&usb3->lock, flags); + + usb3_disable_pipe_irq(usb3, usb3_ep->num); + usb3_request_done(usb3_ep, usb3_req, status); + + /* get next usb3_req */ + usb3_req = usb3_get_request(usb3_ep); + if (usb3_req) + usb3_start_pipen(usb3_ep, usb3_req); +} + +static void usb3_irq_epc_pipen_lsttr(struct renesas_usb3 *usb3, int num) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, num); + struct renesas_usb3_request *usb3_req = usb3_get_request(usb3_ep); + + if (!usb3_req) + return; + + if (usb3_ep->dir_in) { + dev_dbg(usb3_to_dev(usb3), "%s: len = %u, actual = %u\n", + __func__, usb3_req->req.length, usb3_req->req.actual); + usb3_request_done_pipen(usb3, usb3_ep, usb3_req, 0); + } +} + +static void usb3_irq_epc_pipen_bfrdy(struct renesas_usb3 *usb3, int num) +{ + struct renesas_usb3_ep *usb3_ep = usb3_get_ep(usb3, num); + struct renesas_usb3_request *usb3_req = usb3_get_request(usb3_ep); + bool done = false; + + if (!usb3_req) + return; + + spin_lock(&usb3->lock); + if (usb3_pn_change(usb3, num)) + goto out; + + if (usb3_ep->dir_in) { + /* Do not stop the IN pipe here to detect LSTTR interrupt */ + if (!usb3_write_pipe(usb3_ep, usb3_req, USB3_PN_WRITE)) + usb3_clear_bit(usb3, PN_INT_BFRDY, USB3_PN_INT_ENA); + } else { + if (!usb3_read_pipe(usb3_ep, usb3_req, USB3_PN_READ)) + done = true; + } + +out: + /* need to unlock because usb3_request_done_pipen() locks it */ + spin_unlock(&usb3->lock); + + if (done) + usb3_request_done_pipen(usb3, usb3_ep, usb3_req, 0); +} + +static void usb3_irq_epc_pipen(struct renesas_usb3 *usb3, int num) +{ + u32 pn_int_sta; + + spin_lock(&usb3->lock); + if (usb3_pn_change(usb3, num) < 0) { + spin_unlock(&usb3->lock); + return; + } + + pn_int_sta = usb3_read(usb3, USB3_PN_INT_STA); + pn_int_sta &= usb3_read(usb3, USB3_PN_INT_ENA); + usb3_write(usb3, pn_int_sta, USB3_PN_INT_STA); + spin_unlock(&usb3->lock); + if (pn_int_sta & PN_INT_LSTTR) + usb3_irq_epc_pipen_lsttr(usb3, num); + if (pn_int_sta & PN_INT_BFRDY) + usb3_irq_epc_pipen_bfrdy(usb3, num); +} + +static void usb3_irq_epc_int_2(struct renesas_usb3 *usb3, u32 int_sta_2) +{ + int i; + + for (i = 0; i < usb3->num_usb3_eps; i++) { + if (int_sta_2 & USB_INT_2_PIPE(i)) { + if (!i) + usb3_irq_epc_pipe0(usb3); + else + usb3_irq_epc_pipen(usb3, i); + } + } +} + +static void usb3_irq_idmon_change(struct renesas_usb3 *usb3) +{ + usb3_check_id(usb3); +} + +static void usb3_irq_otg_int(struct renesas_usb3 *usb3) +{ + u32 otg_int_sta = usb3_drd_read(usb3, USB3_USB_OTG_INT_STA(usb3)); + + otg_int_sta &= usb3_drd_read(usb3, USB3_USB_OTG_INT_ENA(usb3)); + if (otg_int_sta) + usb3_drd_write(usb3, otg_int_sta, USB3_USB_OTG_INT_STA(usb3)); + + if (otg_int_sta & USB_OTG_IDMON(usb3)) + usb3_irq_idmon_change(usb3); +} + +static void usb3_irq_epc(struct renesas_usb3 *usb3) +{ + u32 int_sta_1 = usb3_read(usb3, USB3_USB_INT_STA_1); + u32 int_sta_2 = usb3_read(usb3, USB3_USB_INT_STA_2); + + int_sta_1 &= usb3_read(usb3, USB3_USB_INT_ENA_1); + if (int_sta_1) { + usb3_write(usb3, int_sta_1, USB3_USB_INT_STA_1); + usb3_irq_epc_int_1(usb3, int_sta_1); + } + + int_sta_2 &= usb3_read(usb3, USB3_USB_INT_ENA_2); + if (int_sta_2) + usb3_irq_epc_int_2(usb3, int_sta_2); + + if (!usb3->is_rzv2m) + usb3_irq_otg_int(usb3); +} + +static void usb3_irq_dma_int(struct renesas_usb3 *usb3, u32 dma_sta) +{ + struct renesas_usb3_ep *usb3_ep; + struct renesas_usb3_request *usb3_req; + int i, status; + + for (i = 0; i < usb3->num_usb3_eps; i++) { + if (!(dma_sta & DMA_INT(i))) + continue; + + usb3_ep = usb3_get_ep(usb3, i); + if (!(usb3_read(usb3, USB3_AXI_INT_STA) & + AXI_INT_PRDEN_CLR_STA(usb3_ep->dma->num))) + continue; + + usb3_req = usb3_get_request(usb3_ep); + status = usb3_dma_try_stop(usb3_ep, usb3_req); + usb3_request_done_pipen(usb3, usb3_ep, usb3_req, status); + } +} + +static void usb3_irq_dma(struct renesas_usb3 *usb3) +{ + u32 dma_sta = usb3_read(usb3, USB3_DMA_INT_STA); + + dma_sta &= usb3_read(usb3, USB3_DMA_INT_ENA); + if (dma_sta) { + usb3_write(usb3, dma_sta, USB3_DMA_INT_STA); + usb3_irq_dma_int(usb3, dma_sta); + } +} + +static irqreturn_t renesas_usb3_irq(int irq, void *_usb3) +{ + struct renesas_usb3 *usb3 = _usb3; + irqreturn_t ret = IRQ_NONE; + u32 axi_int_sta = usb3_read(usb3, USB3_AXI_INT_STA); + + if (axi_int_sta & AXI_INT_DMAINT) { + usb3_irq_dma(usb3); + ret = IRQ_HANDLED; + } + + if (axi_int_sta & AXI_INT_EPCINT) { + usb3_irq_epc(usb3); + ret = IRQ_HANDLED; + } + + return ret; +} + +static irqreturn_t renesas_usb3_otg_irq(int irq, void *_usb3) +{ + struct renesas_usb3 *usb3 = _usb3; + + usb3_irq_otg_int(usb3); + + return IRQ_HANDLED; +} + +static void usb3_write_pn_mod(struct renesas_usb3_ep *usb3_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 val = 0; + + val |= usb3_ep->dir_in ? PN_MOD_DIR : 0; + val |= PN_MOD_TYPE(usb_endpoint_type(desc)); + val |= PN_MOD_EPNUM(usb_endpoint_num(desc)); + usb3_write(usb3, val, USB3_PN_MOD); +} + +static u32 usb3_calc_ramarea(int ram_size) +{ + WARN_ON(ram_size > SZ_16K); + + if (ram_size <= SZ_1K) + return PN_RAMMAP_RAMAREA_1KB; + else if (ram_size <= SZ_2K) + return PN_RAMMAP_RAMAREA_2KB; + else if (ram_size <= SZ_4K) + return PN_RAMMAP_RAMAREA_4KB; + else if (ram_size <= SZ_8K) + return PN_RAMMAP_RAMAREA_8KB; + else + return PN_RAMMAP_RAMAREA_16KB; +} + +static u32 usb3_calc_rammap_val(struct renesas_usb3_ep *usb3_ep, + const struct usb_endpoint_descriptor *desc) +{ + int i; + static const u32 max_packet_array[] = {8, 16, 32, 64, 512}; + u32 mpkt = PN_RAMMAP_MPKT(1024); + + for (i = 0; i < ARRAY_SIZE(max_packet_array); i++) { + if (usb_endpoint_maxp(desc) <= max_packet_array[i]) + mpkt = PN_RAMMAP_MPKT(max_packet_array[i]); + } + + return usb3_ep->rammap_val | mpkt; +} + +static int usb3_enable_pipe_n(struct renesas_usb3_ep *usb3_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + usb3_ep->dir_in = usb_endpoint_dir_in(desc); + + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_pn_change(usb3, usb3_ep->num)) { + usb3_write_pn_mod(usb3_ep, desc); + usb3_write(usb3, usb3_calc_rammap_val(usb3_ep, desc), + USB3_PN_RAMMAP); + usb3_pn_con_clear(usb3); + usb3_set_bit(usb3, PN_CON_EN, USB3_PN_CON); + } + spin_unlock_irqrestore(&usb3->lock, flags); + + return 0; +} + +static int usb3_disable_pipe_n(struct renesas_usb3_ep *usb3_ep) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + usb3_ep->halt = false; + + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_pn_change(usb3, usb3_ep->num)) { + usb3_write(usb3, 0, USB3_PN_INT_ENA); + usb3_write(usb3, 0, USB3_PN_RAMMAP); + usb3_clear_bit(usb3, PN_CON_EN, USB3_PN_CON); + } + spin_unlock_irqrestore(&usb3->lock, flags); + + return 0; +} + +/*------- usb_ep_ops -----------------------------------------------------*/ +static int renesas_usb3_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + + return usb3_enable_pipe_n(usb3_ep, desc); +} + +static int renesas_usb3_ep_disable(struct usb_ep *_ep) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + struct renesas_usb3_request *usb3_req; + + do { + usb3_req = usb3_get_request(usb3_ep); + if (!usb3_req) + break; + usb3_dma_try_stop(usb3_ep, usb3_req); + usb3_request_done(usb3_ep, usb3_req, -ESHUTDOWN); + } while (1); + + return usb3_disable_pipe_n(usb3_ep); +} + +static struct usb_request *__renesas_usb3_ep_alloc_request(gfp_t gfp_flags) +{ + struct renesas_usb3_request *usb3_req; + + usb3_req = kzalloc_obj(struct renesas_usb3_request, gfp_flags); + if (!usb3_req) + return NULL; + + INIT_LIST_HEAD(&usb3_req->queue); + + return &usb3_req->req; +} + +static void __renesas_usb3_ep_free_request(struct usb_request *_req) +{ + struct renesas_usb3_request *usb3_req = usb_req_to_usb3_req(_req); + + kfree(usb3_req); +} + +static struct usb_request *renesas_usb3_ep_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + return __renesas_usb3_ep_alloc_request(gfp_flags); +} + +static void renesas_usb3_ep_free_request(struct usb_ep *_ep, + struct usb_request *_req) +{ + __renesas_usb3_ep_free_request(_req); +} + +static int renesas_usb3_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + struct renesas_usb3_request *usb3_req = usb_req_to_usb3_req(_req); + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + dev_dbg(usb3_to_dev(usb3), "ep_dequeue: ep%2d, %u\n", usb3_ep->num, + _req->length); + + usb3_dma_try_stop(usb3_ep, usb3_req); + usb3_request_done_pipen(usb3, usb3_ep, usb3_req, -ECONNRESET); + + return 0; +} + +static int renesas_usb3_ep_set_halt(struct usb_ep *_ep, int value) +{ + return usb3_set_halt(usb_ep_to_usb3_ep(_ep), !!value, false); +} + +static int renesas_usb3_ep_set_wedge(struct usb_ep *_ep) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + + usb3_ep->wedge = true; + return usb3_set_halt(usb3_ep, true, false); +} + +static void renesas_usb3_ep_fifo_flush(struct usb_ep *_ep) +{ + struct renesas_usb3_ep *usb3_ep = usb_ep_to_usb3_ep(_ep); + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + + if (usb3_ep->num) { + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_pn_change(usb3, usb3_ep->num)) { + usb3_pn_con_clear(usb3); + usb3_set_bit(usb3, PN_CON_EN, USB3_PN_CON); + } + spin_unlock_irqrestore(&usb3->lock, flags); + } else { + usb3_p0_con_clear_buffer(usb3); + } +} + +static const struct usb_ep_ops renesas_usb3_ep_ops = { + .enable = renesas_usb3_ep_enable, + .disable = renesas_usb3_ep_disable, + + .alloc_request = renesas_usb3_ep_alloc_request, + .free_request = renesas_usb3_ep_free_request, + + .queue = renesas_usb3_ep_queue, + .dequeue = renesas_usb3_ep_dequeue, + + .set_halt = renesas_usb3_ep_set_halt, + .set_wedge = renesas_usb3_ep_set_wedge, + .fifo_flush = renesas_usb3_ep_fifo_flush, +}; + +/*------- usb_gadget_ops -------------------------------------------------*/ +static int renesas_usb3_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct renesas_usb3 *usb3; + + if (!driver || driver->max_speed < USB_SPEED_FULL || + !driver->setup) + return -EINVAL; + + usb3 = gadget_to_renesas_usb3(gadget); + + if (usb3->is_rzv2m && usb3_is_a_device(usb3)) + return -EBUSY; + + /* hook up the driver */ + usb3->driver = driver; + + if (usb3->phy) + phy_init(usb3->phy); + + pm_runtime_get_sync(usb3_to_dev(usb3)); + + /* Peripheral Reset */ + if (usb3->is_rzv2m) + rzv2m_usb3drd_reset(usb3_to_dev(usb3)->parent, false); + + renesas_usb3_init_controller(usb3); + + return 0; +} + +static int renesas_usb3_stop(struct usb_gadget *gadget) +{ + struct renesas_usb3 *usb3 = gadget_to_renesas_usb3(gadget); + + usb3->softconnect = false; + usb3->gadget.speed = USB_SPEED_UNKNOWN; + usb3->driver = NULL; + if (usb3->is_rzv2m) + rzv2m_usb3drd_reset(usb3_to_dev(usb3)->parent, false); + + renesas_usb3_stop_controller(usb3); + phy_exit(usb3->phy); + + pm_runtime_put(usb3_to_dev(usb3)); + + return 0; +} + +static int renesas_usb3_get_frame(struct usb_gadget *_gadget) +{ + return -EOPNOTSUPP; +} + +static int renesas_usb3_pullup(struct usb_gadget *gadget, int is_on) +{ + struct renesas_usb3 *usb3 = gadget_to_renesas_usb3(gadget); + + usb3->softconnect = !!is_on; + + return 0; +} + +static int renesas_usb3_set_selfpowered(struct usb_gadget *gadget, int is_self) +{ + gadget->is_selfpowered = !!is_self; + + return 0; +} + +static const struct usb_gadget_ops renesas_usb3_gadget_ops = { + .get_frame = renesas_usb3_get_frame, + .udc_start = renesas_usb3_start, + .udc_stop = renesas_usb3_stop, + .pullup = renesas_usb3_pullup, + .set_selfpowered = renesas_usb3_set_selfpowered, +}; + +static enum usb_role renesas_usb3_role_switch_get(struct usb_role_switch *sw) +{ + struct renesas_usb3 *usb3 = usb_role_switch_get_drvdata(sw); + enum usb_role cur_role; + + pm_runtime_get_sync(usb3_to_dev(usb3)); + cur_role = usb3_is_host(usb3) ? USB_ROLE_HOST : USB_ROLE_DEVICE; + pm_runtime_put(usb3_to_dev(usb3)); + + return cur_role; +} + +static void handle_ext_role_switch_states(struct device *dev, + enum usb_role role) +{ + struct renesas_usb3 *usb3 = dev_get_drvdata(dev); + struct device *host = usb3->host_dev; + enum usb_role cur_role = renesas_usb3_role_switch_get(usb3->role_sw); + + switch (role) { + case USB_ROLE_NONE: + usb3->connection_state = USB_ROLE_NONE; + if (!usb3->is_rzv2m && cur_role == USB_ROLE_HOST) + device_release_driver(host); + if (usb3->driver) { + if (usb3->is_rzv2m) + rzv2m_usb3drd_reset(dev->parent, false); + usb3_disconnect(usb3); + } + usb3_vbus_out(usb3, false); + + if (usb3->is_rzv2m) { + rzv2m_usb3drd_reset(dev->parent, true); + device_release_driver(host); + } + break; + case USB_ROLE_DEVICE: + if (usb3->connection_state == USB_ROLE_NONE) { + usb3->connection_state = USB_ROLE_DEVICE; + usb3_set_mode(usb3, false); + if (usb3->driver) { + if (usb3->is_rzv2m) + renesas_usb3_init_controller(usb3); + usb3_connect(usb3); + } + } else if (cur_role == USB_ROLE_HOST) { + device_release_driver(host); + usb3_set_mode(usb3, false); + if (usb3->driver) + usb3_connect(usb3); + } + usb3_vbus_out(usb3, false); + break; + case USB_ROLE_HOST: + if (usb3->connection_state == USB_ROLE_NONE) { + if (usb3->driver) { + if (usb3->is_rzv2m) + rzv2m_usb3drd_reset(dev->parent, false); + usb3_disconnect(usb3); + } + + usb3->connection_state = USB_ROLE_HOST; + usb3_set_mode(usb3, true); + usb3_vbus_out(usb3, true); + if (device_attach(host) < 0) + dev_err(dev, "device_attach(host) failed\n"); + } else if (cur_role == USB_ROLE_DEVICE) { + usb3_disconnect(usb3); + /* Must set the mode before device_attach of the host */ + usb3_set_mode(usb3, true); + /* This device_attach() might sleep */ + if (device_attach(host) < 0) + dev_err(dev, "device_attach(host) failed\n"); + } + break; + default: + break; + } +} + +static void handle_role_switch_states(struct device *dev, + enum usb_role role) +{ + struct renesas_usb3 *usb3 = dev_get_drvdata(dev); + struct device *host = usb3->host_dev; + enum usb_role cur_role = renesas_usb3_role_switch_get(usb3->role_sw); + + if (cur_role == USB_ROLE_HOST && role == USB_ROLE_DEVICE) { + device_release_driver(host); + usb3_set_mode(usb3, false); + } else if (cur_role == USB_ROLE_DEVICE && role == USB_ROLE_HOST) { + /* Must set the mode before device_attach of the host */ + usb3_set_mode(usb3, true); + /* This device_attach() might sleep */ + if (device_attach(host) < 0) + dev_err(dev, "device_attach(host) failed\n"); + } +} + +static int renesas_usb3_role_switch_set(struct usb_role_switch *sw, + enum usb_role role) +{ + struct renesas_usb3 *usb3 = usb_role_switch_get_drvdata(sw); + + pm_runtime_get_sync(usb3_to_dev(usb3)); + + if (usb3->role_sw_by_connector) + handle_ext_role_switch_states(usb3_to_dev(usb3), role); + else + handle_role_switch_states(usb3_to_dev(usb3), role); + + pm_runtime_put(usb3_to_dev(usb3)); + + return 0; +} + +static ssize_t role_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct renesas_usb3 *usb3 = dev_get_drvdata(dev); + bool new_mode_is_host; + + if (!usb3->driver) + return -ENODEV; + + if (usb3->forced_b_device) + return -EBUSY; + + if (sysfs_streq(buf, "host")) + new_mode_is_host = true; + else if (sysfs_streq(buf, "peripheral")) + new_mode_is_host = false; + else + return -EINVAL; + + if (new_mode_is_host == usb3_is_host(usb3)) + return -EINVAL; + + usb3_mode_config(usb3, new_mode_is_host, usb3_is_a_device(usb3)); + + return count; +} + +static ssize_t role_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct renesas_usb3 *usb3 = dev_get_drvdata(dev); + + if (!usb3->driver) + return -ENODEV; + + return sprintf(buf, "%s\n", usb3_is_host(usb3) ? "host" : "peripheral"); +} +static DEVICE_ATTR_RW(role); + +static int renesas_usb3_b_device_show(struct seq_file *s, void *unused) +{ + struct renesas_usb3 *usb3 = s->private; + + seq_printf(s, "%d\n", usb3->forced_b_device); + + return 0; +} + +static int renesas_usb3_b_device_open(struct inode *inode, struct file *file) +{ + return single_open(file, renesas_usb3_b_device_show, inode->i_private); +} + +static ssize_t renesas_usb3_b_device_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct renesas_usb3 *usb3 = s->private; + char buf[32]; + + if (!usb3->driver) + return -ENODEV; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + usb3->start_to_connect = false; + if (usb3->workaround_for_vbus && usb3->forced_b_device && + !strncmp(buf, "2", 1)) + usb3->start_to_connect = true; + else if (!strncmp(buf, "1", 1)) + usb3->forced_b_device = true; + else + usb3->forced_b_device = false; + + if (usb3->workaround_for_vbus) + usb3_disconnect(usb3); + + /* Let this driver call usb3_connect() if needed */ + usb3_check_id(usb3); + + return count; +} + +static const struct file_operations renesas_usb3_b_device_fops = { + .open = renesas_usb3_b_device_open, + .write = renesas_usb3_b_device_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void renesas_usb3_debugfs_init(struct renesas_usb3 *usb3, + struct device *dev) +{ + usb3->dentry = debugfs_create_dir(dev_name(dev), usb_debug_root); + + debugfs_create_file("b_device", 0644, usb3->dentry, usb3, + &renesas_usb3_b_device_fops); +} + +/*------- platform_driver ------------------------------------------------*/ +static void renesas_usb3_remove(struct platform_device *pdev) +{ + struct renesas_usb3 *usb3 = platform_get_drvdata(pdev); + + debugfs_remove_recursive(usb3->dentry); + put_device(usb3->host_dev); + device_remove_file(&pdev->dev, &dev_attr_role); + + cancel_work_sync(&usb3->role_work); + usb_role_switch_unregister(usb3->role_sw); + + usb_del_gadget_udc(&usb3->gadget); + reset_control_assert(usb3->usbp_rstc); + renesas_usb3_dma_free_prd(usb3, &pdev->dev); + + __renesas_usb3_ep_free_request(usb3->ep0_req); + pm_runtime_disable(&pdev->dev); +} + +static int renesas_usb3_init_ep(struct renesas_usb3 *usb3, struct device *dev, + const struct renesas_usb3_priv *priv) +{ + struct renesas_usb3_ep *usb3_ep; + int i; + + /* calculate num_usb3_eps from renesas_usb3_priv */ + usb3->num_usb3_eps = priv->ramsize_per_ramif * priv->num_ramif * 2 / + priv->ramsize_per_pipe + 1; + + if (usb3->num_usb3_eps > USB3_MAX_NUM_PIPES(usb3)) + usb3->num_usb3_eps = USB3_MAX_NUM_PIPES(usb3); + + usb3->usb3_ep = devm_kcalloc(dev, + usb3->num_usb3_eps, sizeof(*usb3_ep), + GFP_KERNEL); + if (!usb3->usb3_ep) + return -ENOMEM; + + dev_dbg(dev, "%s: num_usb3_eps = %d\n", __func__, usb3->num_usb3_eps); + /* + * This driver prepares pipes as follows: + * - odd pipes = IN pipe + * - even pipes = OUT pipe (except pipe 0) + */ + usb3_for_each_ep(usb3_ep, usb3, i) { + snprintf(usb3_ep->ep_name, sizeof(usb3_ep->ep_name), "ep%d", i); + usb3_ep->usb3 = usb3; + usb3_ep->num = i; + usb3_ep->ep.name = usb3_ep->ep_name; + usb3_ep->ep.ops = &renesas_usb3_ep_ops; + INIT_LIST_HEAD(&usb3_ep->queue); + INIT_LIST_HEAD(&usb3_ep->ep.ep_list); + if (!i) { + /* for control pipe */ + usb3->gadget.ep0 = &usb3_ep->ep; + usb_ep_set_maxpacket_limit(&usb3_ep->ep, + USB3_EP0_SS_MAX_PACKET_SIZE); + usb3_ep->ep.caps.type_control = true; + usb3_ep->ep.caps.dir_in = true; + usb3_ep->ep.caps.dir_out = true; + continue; + } + + /* for bulk or interrupt pipe */ + usb_ep_set_maxpacket_limit(&usb3_ep->ep, ~0); + list_add_tail(&usb3_ep->ep.ep_list, &usb3->gadget.ep_list); + usb3_ep->ep.caps.type_bulk = true; + usb3_ep->ep.caps.type_int = true; + if (i & 1) + usb3_ep->ep.caps.dir_in = true; + else + usb3_ep->ep.caps.dir_out = true; + } + + return 0; +} + +static void renesas_usb3_init_ram(struct renesas_usb3 *usb3, struct device *dev, + const struct renesas_usb3_priv *priv) +{ + struct renesas_usb3_ep *usb3_ep; + int i; + u32 ramif[2], basead[2]; /* index 0 = for IN pipes */ + u32 *cur_ramif, *cur_basead; + u32 val; + + memset(ramif, 0, sizeof(ramif)); + memset(basead, 0, sizeof(basead)); + + /* + * This driver prepares pipes as follows: + * - all pipes = the same size as "ramsize_per_pipe" + * Please refer to the "Method of Specifying RAM Mapping" + */ + usb3_for_each_ep(usb3_ep, usb3, i) { + if (!i) + continue; /* out of scope if ep num = 0 */ + if (usb3_ep->ep.caps.dir_in) { + cur_ramif = &ramif[0]; + cur_basead = &basead[0]; + } else { + cur_ramif = &ramif[1]; + cur_basead = &basead[1]; + } + + if (*cur_basead > priv->ramsize_per_ramif) + continue; /* out of memory for IN or OUT pipe */ + + /* calculate rammap_val */ + val = PN_RAMMAP_RAMIF(*cur_ramif); + val |= usb3_calc_ramarea(priv->ramsize_per_pipe); + val |= PN_RAMMAP_BASEAD(*cur_basead); + usb3_ep->rammap_val = val; + + dev_dbg(dev, "ep%2d: val = %08x, ramif = %d, base = %x\n", + i, val, *cur_ramif, *cur_basead); + + /* update current ramif */ + if (*cur_ramif + 1 == priv->num_ramif) { + *cur_ramif = 0; + *cur_basead += priv->ramsize_per_pipe; + } else { + (*cur_ramif)++; + } + } +} + +static const struct renesas_usb3_priv renesas_usb3_priv_gen3 = { + .ramsize_per_ramif = SZ_16K, + .num_ramif = 4, + .ramsize_per_pipe = SZ_4K, +}; + +static const struct renesas_usb3_priv renesas_usb3_priv_r8a77990 = { + .ramsize_per_ramif = SZ_16K, + .num_ramif = 4, + .ramsize_per_pipe = SZ_4K, + .workaround_for_vbus = true, +}; + +static const struct renesas_usb3_priv renesas_usb3_priv_rzv2m = { + .ramsize_per_ramif = SZ_16K, + .num_ramif = 1, + .ramsize_per_pipe = SZ_4K, + .is_rzv2m = true, +}; + +static const struct of_device_id usb3_of_match[] = { + { + .compatible = "renesas,r8a774c0-usb3-peri", + .data = &renesas_usb3_priv_r8a77990, + }, { + .compatible = "renesas,r8a7795-usb3-peri", + .data = &renesas_usb3_priv_gen3, + }, { + .compatible = "renesas,r8a77990-usb3-peri", + .data = &renesas_usb3_priv_r8a77990, + }, { + .compatible = "renesas,rzv2m-usb3-peri", + .data = &renesas_usb3_priv_rzv2m, + }, { + .compatible = "renesas,rcar-gen3-usb3-peri", + .data = &renesas_usb3_priv_gen3, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, usb3_of_match); + +static const unsigned int renesas_usb3_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +static struct usb_role_switch_desc renesas_usb3_role_switch_desc = { + .set = renesas_usb3_role_switch_set, + .get = renesas_usb3_role_switch_get, + .allow_userspace_control = true, +}; + +static int renesas_usb3_probe(struct platform_device *pdev) +{ + struct renesas_usb3 *usb3; + int irq, ret; + const struct renesas_usb3_priv *priv; + + priv = of_device_get_match_data(&pdev->dev); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + usb3 = devm_kzalloc(&pdev->dev, sizeof(*usb3), GFP_KERNEL); + if (!usb3) + return -ENOMEM; + + usb3->is_rzv2m = priv->is_rzv2m; + + usb3->reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(usb3->reg)) + return PTR_ERR(usb3->reg); + + platform_set_drvdata(pdev, usb3); + spin_lock_init(&usb3->lock); + + usb3->gadget.ops = &renesas_usb3_gadget_ops; + usb3->gadget.name = udc_name; + usb3->gadget.max_speed = USB_SPEED_SUPER; + INIT_LIST_HEAD(&usb3->gadget.ep_list); + ret = renesas_usb3_init_ep(usb3, &pdev->dev, priv); + if (ret < 0) + return ret; + renesas_usb3_init_ram(usb3, &pdev->dev, priv); + + ret = devm_request_irq(&pdev->dev, irq, renesas_usb3_irq, 0, + dev_name(&pdev->dev), usb3); + if (ret < 0) + return ret; + + if (usb3->is_rzv2m) { + struct rzv2m_usb3drd *ddata = dev_get_drvdata(pdev->dev.parent); + + usb3->drd_reg = ddata->reg; + ret = devm_request_irq(&pdev->dev, ddata->drd_irq, + renesas_usb3_otg_irq, 0, + dev_name(&pdev->dev), usb3); + if (ret < 0) + return ret; + } + + INIT_WORK(&usb3->extcon_work, renesas_usb3_extcon_work); + usb3->extcon = devm_extcon_dev_allocate(&pdev->dev, renesas_usb3_cable); + if (IS_ERR(usb3->extcon)) + return PTR_ERR(usb3->extcon); + + ret = devm_extcon_dev_register(&pdev->dev, usb3->extcon); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register extcon\n"); + return ret; + } + + /* for ep0 handling */ + usb3->ep0_req = __renesas_usb3_ep_alloc_request(GFP_KERNEL); + if (!usb3->ep0_req) + return -ENOMEM; + + ret = renesas_usb3_dma_alloc_prd(usb3, &pdev->dev); + if (ret < 0) + goto err_alloc_prd; + + /* + * This is optional. So, if this driver cannot get a phy, + * this driver will not handle a phy anymore. + */ + usb3->phy = devm_phy_optional_get(&pdev->dev, "usb"); + if (IS_ERR(usb3->phy)) { + ret = PTR_ERR(usb3->phy); + goto err_add_udc; + } + + usb3->usbp_rstc = devm_reset_control_get_optional_shared(&pdev->dev, + NULL); + if (IS_ERR(usb3->usbp_rstc)) { + ret = PTR_ERR(usb3->usbp_rstc); + goto err_add_udc; + } + + reset_control_deassert(usb3->usbp_rstc); + + pm_runtime_enable(&pdev->dev); + ret = usb_add_gadget_udc(&pdev->dev, &usb3->gadget); + if (ret < 0) + goto err_reset; + + ret = device_create_file(&pdev->dev, &dev_attr_role); + if (ret < 0) + goto err_dev_create; + + if (device_property_read_bool(&pdev->dev, "usb-role-switch")) { + usb3->role_sw_by_connector = true; + renesas_usb3_role_switch_desc.fwnode = dev_fwnode(&pdev->dev); + } + + renesas_usb3_role_switch_desc.driver_data = usb3; + + INIT_WORK(&usb3->role_work, renesas_usb3_role_work); + usb3->role_sw = usb_role_switch_register(&pdev->dev, + &renesas_usb3_role_switch_desc); + if (!IS_ERR(usb3->role_sw)) { + usb3->host_dev = usb_of_get_companion_dev(&pdev->dev); + if (!usb3->host_dev) { + /* If not found, this driver will not use a role sw */ + usb_role_switch_unregister(usb3->role_sw); + usb3->role_sw = NULL; + } + } else { + usb3->role_sw = NULL; + } + + usb3->workaround_for_vbus = priv->workaround_for_vbus; + + renesas_usb3_debugfs_init(usb3, &pdev->dev); + + dev_info(&pdev->dev, "probed%s\n", usb3->phy ? " with phy" : ""); + + return 0; + +err_dev_create: + usb_del_gadget_udc(&usb3->gadget); + +err_reset: + reset_control_assert(usb3->usbp_rstc); + +err_add_udc: + renesas_usb3_dma_free_prd(usb3, &pdev->dev); + +err_alloc_prd: + __renesas_usb3_ep_free_request(usb3->ep0_req); + + return ret; +} + +static int renesas_usb3_suspend(struct device *dev) +{ + struct renesas_usb3 *usb3 = dev_get_drvdata(dev); + + /* Not started */ + if (!usb3->driver) + return 0; + + renesas_usb3_stop_controller(usb3); + phy_exit(usb3->phy); + pm_runtime_put(dev); + + return 0; +} + +static int renesas_usb3_resume(struct device *dev) +{ + struct renesas_usb3 *usb3 = dev_get_drvdata(dev); + + /* Not started */ + if (!usb3->driver) + return 0; + + if (usb3->phy) + phy_init(usb3->phy); + pm_runtime_get_sync(dev); + renesas_usb3_init_controller(usb3); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(renesas_usb3_pm_ops, renesas_usb3_suspend, + renesas_usb3_resume); + +static struct platform_driver renesas_usb3_driver = { + .probe = renesas_usb3_probe, + .remove = renesas_usb3_remove, + .driver = { + .name = udc_name, + .pm = pm_sleep_ptr(&renesas_usb3_pm_ops), + .of_match_table = usb3_of_match, + }, +}; +module_platform_driver(renesas_usb3_driver); + +MODULE_DESCRIPTION("Renesas USB3.0 Peripheral driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Yoshihiro Shimoda "); diff --git a/drivers/usb/gadget/udc/renesas_usbf.c b/drivers/usb/gadget/udc/renesas_usbf.c new file mode 100644 index 000000000..d67002ea0 --- /dev/null +++ b/drivers/usb/gadget/udc/renesas_usbf.c @@ -0,0 +1,3392 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas USBF USB Function driver + * + * Copyright 2022 Schneider Electric + * Author: Herve Codina + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USBF_NUM_ENDPOINTS 16 +#define USBF_EP0_MAX_PCKT_SIZE 64 + +/* EPC registers */ +#define USBF_REG_USB_CONTROL 0x000 +#define USBF_USB_PUE2 BIT(2) +#define USBF_USB_CONNECTB BIT(3) +#define USBF_USB_DEFAULT BIT(4) +#define USBF_USB_CONF BIT(5) +#define USBF_USB_SUSPEND BIT(6) +#define USBF_USB_RSUM_IN BIT(7) +#define USBF_USB_SOF_RCV BIT(8) +#define USBF_USB_FORCEFS BIT(9) +#define USBF_USB_INT_SEL BIT(10) +#define USBF_USB_SOF_CLK_MODE BIT(11) + +#define USBF_REG_USB_STATUS 0x004 +#define USBF_USB_RSUM_OUT BIT(1) +#define USBF_USB_SPND_OUT BIT(2) +#define USBF_USB_USB_RST BIT(3) +#define USBF_USB_DEFAULT_ST BIT(4) +#define USBF_USB_CONF_ST BIT(5) +#define USBF_USB_SPEED_MODE BIT(6) +#define USBF_USB_SOF_DELAY_STATUS BIT(31) + +#define USBF_REG_USB_ADDRESS 0x008 +#define USBF_USB_SOF_STATUS BIT(15) +#define USBF_USB_SET_USB_ADDR(_a) ((_a) << 16) +#define USBF_USB_GET_FRAME(_r) ((_r) & 0x7FF) + +#define USBF_REG_SETUP_DATA0 0x018 +#define USBF_REG_SETUP_DATA1 0x01C +#define USBF_REG_USB_INT_STA 0x020 +#define USBF_USB_RSUM_INT BIT(1) +#define USBF_USB_SPND_INT BIT(2) +#define USBF_USB_USB_RST_INT BIT(3) +#define USBF_USB_SOF_INT BIT(4) +#define USBF_USB_SOF_ERROR_INT BIT(5) +#define USBF_USB_SPEED_MODE_INT BIT(6) +#define USBF_USB_EPN_INT(_n) (BIT(8) << (_n)) /* n=0..15 */ + +#define USBF_REG_USB_INT_ENA 0x024 +#define USBF_USB_RSUM_EN BIT(1) +#define USBF_USB_SPND_EN BIT(2) +#define USBF_USB_USB_RST_EN BIT(3) +#define USBF_USB_SOF_EN BIT(4) +#define USBF_USB_SOF_ERROR_EN BIT(5) +#define USBF_USB_SPEED_MODE_EN BIT(6) +#define USBF_USB_EPN_EN(_n) (BIT(8) << (_n)) /* n=0..15 */ + +#define USBF_BASE_EP0 0x028 +/* EP0 registers offsets from Base + USBF_BASE_EP0 (EP0 regs area) */ +#define USBF_REG_EP0_CONTROL 0x00 +#define USBF_EP0_ONAK BIT(0) +#define USBF_EP0_INAK BIT(1) +#define USBF_EP0_STL BIT(2) +#define USBF_EP0_PERR_NAK_CLR BIT(3) +#define USBF_EP0_INAK_EN BIT(4) +#define USBF_EP0_DW_MASK (0x3 << 5) +#define USBF_EP0_DW(_s) ((_s) << 5) +#define USBF_EP0_DEND BIT(7) +#define USBF_EP0_BCLR BIT(8) +#define USBF_EP0_PIDCLR BIT(9) +#define USBF_EP0_AUTO BIT(16) +#define USBF_EP0_OVERSEL BIT(17) +#define USBF_EP0_STGSEL BIT(18) + +#define USBF_REG_EP0_STATUS 0x04 +#define USBF_EP0_SETUP_INT BIT(0) +#define USBF_EP0_STG_START_INT BIT(1) +#define USBF_EP0_STG_END_INT BIT(2) +#define USBF_EP0_STALL_INT BIT(3) +#define USBF_EP0_IN_INT BIT(4) +#define USBF_EP0_OUT_INT BIT(5) +#define USBF_EP0_OUT_OR_INT BIT(6) +#define USBF_EP0_OUT_NULL_INT BIT(7) +#define USBF_EP0_IN_EMPTY BIT(8) +#define USBF_EP0_IN_FULL BIT(9) +#define USBF_EP0_IN_DATA BIT(10) +#define USBF_EP0_IN_NAK_INT BIT(11) +#define USBF_EP0_OUT_EMPTY BIT(12) +#define USBF_EP0_OUT_FULL BIT(13) +#define USBF_EP0_OUT_NULL BIT(14) +#define USBF_EP0_OUT_NAK_INT BIT(15) +#define USBF_EP0_PERR_NAK_INT BIT(16) +#define USBF_EP0_PERR_NAK BIT(17) +#define USBF_EP0_PID BIT(18) + +#define USBF_REG_EP0_INT_ENA 0x08 +#define USBF_EP0_SETUP_EN BIT(0) +#define USBF_EP0_STG_START_EN BIT(1) +#define USBF_EP0_STG_END_EN BIT(2) +#define USBF_EP0_STALL_EN BIT(3) +#define USBF_EP0_IN_EN BIT(4) +#define USBF_EP0_OUT_EN BIT(5) +#define USBF_EP0_OUT_OR_EN BIT(6) +#define USBF_EP0_OUT_NULL_EN BIT(7) +#define USBF_EP0_IN_NAK_EN BIT(11) +#define USBF_EP0_OUT_NAK_EN BIT(15) +#define USBF_EP0_PERR_NAK_EN BIT(16) + +#define USBF_REG_EP0_LENGTH 0x0C +#define USBF_EP0_LDATA (0x7FF << 0) +#define USBF_REG_EP0_READ 0x10 +#define USBF_REG_EP0_WRITE 0x14 + +#define USBF_BASE_EPN(_n) (0x040 + (_n) * 0x020) +/* EPn registers offsets from Base + USBF_BASE_EPN(n-1). n=1..15 */ +#define USBF_REG_EPN_CONTROL 0x000 +#define USBF_EPN_ONAK BIT(0) +#define USBF_EPN_OSTL BIT(2) +#define USBF_EPN_ISTL BIT(3) +#define USBF_EPN_OSTL_EN BIT(4) +#define USBF_EPN_DW_MASK (0x3 << 5) +#define USBF_EPN_DW(_s) ((_s) << 5) +#define USBF_EPN_DEND BIT(7) +#define USBF_EPN_CBCLR BIT(8) +#define USBF_EPN_BCLR BIT(9) +#define USBF_EPN_OPIDCLR BIT(10) +#define USBF_EPN_IPIDCLR BIT(11) +#define USBF_EPN_AUTO BIT(16) +#define USBF_EPN_OVERSEL BIT(17) +#define USBF_EPN_MODE_MASK (0x3 << 24) +#define USBF_EPN_MODE_BULK (0x0 << 24) +#define USBF_EPN_MODE_INTR (0x1 << 24) +#define USBF_EPN_MODE_ISO (0x2 << 24) +#define USBF_EPN_DIR0 BIT(26) +#define USBF_EPN_BUF_TYPE_DOUBLE BIT(30) +#define USBF_EPN_EN BIT(31) + +#define USBF_REG_EPN_STATUS 0x004 +#define USBF_EPN_IN_EMPTY BIT(0) +#define USBF_EPN_IN_FULL BIT(1) +#define USBF_EPN_IN_DATA BIT(2) +#define USBF_EPN_IN_INT BIT(3) +#define USBF_EPN_IN_STALL_INT BIT(4) +#define USBF_EPN_IN_NAK_ERR_INT BIT(5) +#define USBF_EPN_IN_END_INT BIT(7) +#define USBF_EPN_IPID BIT(10) +#define USBF_EPN_OUT_EMPTY BIT(16) +#define USBF_EPN_OUT_FULL BIT(17) +#define USBF_EPN_OUT_NULL_INT BIT(18) +#define USBF_EPN_OUT_INT BIT(19) +#define USBF_EPN_OUT_STALL_INT BIT(20) +#define USBF_EPN_OUT_NAK_ERR_INT BIT(21) +#define USBF_EPN_OUT_OR_INT BIT(22) +#define USBF_EPN_OUT_END_INT BIT(23) +#define USBF_EPN_ISO_CRC BIT(24) +#define USBF_EPN_ISO_OR BIT(26) +#define USBF_EPN_OUT_NOTKN BIT(27) +#define USBF_EPN_ISO_OPID BIT(28) +#define USBF_EPN_ISO_PIDERR BIT(29) + +#define USBF_REG_EPN_INT_ENA 0x008 +#define USBF_EPN_IN_EN BIT(3) +#define USBF_EPN_IN_STALL_EN BIT(4) +#define USBF_EPN_IN_NAK_ERR_EN BIT(5) +#define USBF_EPN_IN_END_EN BIT(7) +#define USBF_EPN_OUT_NULL_EN BIT(18) +#define USBF_EPN_OUT_EN BIT(19) +#define USBF_EPN_OUT_STALL_EN BIT(20) +#define USBF_EPN_OUT_NAK_ERR_EN BIT(21) +#define USBF_EPN_OUT_OR_EN BIT(22) +#define USBF_EPN_OUT_END_EN BIT(23) + +#define USBF_REG_EPN_DMA_CTRL 0x00C +#define USBF_EPN_DMAMODE0 BIT(0) +#define USBF_EPN_DMA_EN BIT(4) +#define USBF_EPN_STOP_SET BIT(8) +#define USBF_EPN_BURST_SET BIT(9) +#define USBF_EPN_DEND_SET BIT(10) +#define USBF_EPN_STOP_MODE BIT(11) + +#define USBF_REG_EPN_PCKT_ADRS 0x010 +#define USBF_EPN_MPKT(_l) ((_l) << 0) +#define USBF_EPN_BASEAD(_a) ((_a) << 16) + +#define USBF_REG_EPN_LEN_DCNT 0x014 +#define USBF_EPN_GET_LDATA(_r) ((_r) & 0x7FF) +#define USBF_EPN_SET_DMACNT(_c) ((_c) << 16) +#define USBF_EPN_GET_DMACNT(_r) (((_r) >> 16) & 0x1ff) + +#define USBF_REG_EPN_READ 0x018 +#define USBF_REG_EPN_WRITE 0x01C + +/* AHB-EPC Bridge registers */ +#define USBF_REG_AHBSCTR 0x1000 +#define USBF_REG_AHBMCTR 0x1004 +#define USBF_SYS_WBURST_TYPE BIT(2) +#define USBF_SYS_ARBITER_CTR BIT(31) + +#define USBF_REG_AHBBINT 0x1008 +#define USBF_SYS_ERR_MASTER (0x0F << 0) +#define USBF_SYS_SBUS_ERRINT0 BIT(4) +#define USBF_SYS_SBUS_ERRINT1 BIT(5) +#define USBF_SYS_MBUS_ERRINT BIT(6) +#define USBF_SYS_VBUS_INT BIT(13) +#define USBF_SYS_DMA_ENDINT_EPN(_n) (BIT(16) << (_n)) /* _n=1..15 */ + +#define USBF_REG_AHBBINTEN 0x100C +#define USBF_SYS_SBUS_ERRINT0EN BIT(4) +#define USBF_SYS_SBUS_ERRINT1EN BIT(5) +#define USBF_SYS_MBUS_ERRINTEN BIT(6) +#define USBF_SYS_VBUS_INTEN BIT(13) +#define USBF_SYS_DMA_ENDINTEN_EPN(_n) (BIT(16) << (_n)) /* _n=1..15 */ + +#define USBF_REG_EPCTR 0x1010 +#define USBF_SYS_EPC_RST BIT(0) +#define USBF_SYS_PLL_RST BIT(2) +#define USBF_SYS_PLL_LOCK BIT(4) +#define USBF_SYS_PLL_RESUME BIT(5) +#define USBF_SYS_VBUS_LEVEL BIT(8) +#define USBF_SYS_DIRPD BIT(12) + +#define USBF_REG_USBSSVER 0x1020 +#define USBF_REG_USBSSCONF 0x1024 +#define USBF_SYS_DMA_AVAILABLE(_n) (BIT(0) << (_n)) /* _n=0..15 */ +#define USBF_SYS_EP_AVAILABLE(_n) (BIT(16) << (_n)) /* _n=0..15 */ + +#define USBF_BASE_DMA_EPN(_n) (0x1110 + (_n) * 0x010) +/* EPn DMA registers offsets from Base USBF_BASE_DMA_EPN(n-1). n=1..15*/ +#define USBF_REG_DMA_EPN_DCR1 0x00 +#define USBF_SYS_EPN_REQEN BIT(0) +#define USBF_SYS_EPN_DIR0 BIT(1) +#define USBF_SYS_EPN_SET_DMACNT(_c) ((_c) << 16) +#define USBF_SYS_EPN_GET_DMACNT(_r) (((_r) >> 16) & 0x0FF) + +#define USBF_REG_DMA_EPN_DCR2 0x04 +#define USBF_SYS_EPN_MPKT(_s) ((_s) << 0) +#define USBF_SYS_EPN_LMPKT(_l) ((_l) << 16) + +#define USBF_REG_DMA_EPN_TADR 0x08 + +/* USB request */ +struct usbf_req { + struct usb_request req; + struct list_head queue; + unsigned int is_zero_sent : 1; + unsigned int is_mapped : 1; + enum { + USBF_XFER_START, + USBF_XFER_WAIT_DMA, + USBF_XFER_SEND_NULL, + USBF_XFER_WAIT_END, + USBF_XFER_WAIT_DMA_SHORT, + USBF_XFER_WAIT_BRIDGE, + } xfer_step; + size_t dma_size; +}; + +/* USB Endpoint */ +struct usbf_ep { + struct usb_ep ep; + char name[32]; + struct list_head queue; + unsigned int is_processing : 1; + unsigned int is_in : 1; + struct usbf_udc *udc; + void __iomem *regs; + void __iomem *dma_regs; + unsigned int id : 8; + unsigned int disabled : 1; + unsigned int is_wedged : 1; + unsigned int delayed_status : 1; + u32 status; + void (*bridge_on_dma_end)(struct usbf_ep *ep); +}; + +enum usbf_ep0state { + EP0_IDLE, + EP0_IN_DATA_PHASE, + EP0_OUT_DATA_PHASE, + EP0_OUT_STATUS_START_PHASE, + EP0_OUT_STATUS_PHASE, + EP0_OUT_STATUS_END_PHASE, + EP0_IN_STATUS_START_PHASE, + EP0_IN_STATUS_PHASE, + EP0_IN_STATUS_END_PHASE, +}; + +struct usbf_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct device *dev; + void __iomem *regs; + spinlock_t lock; + bool is_remote_wakeup; + bool is_usb_suspended; + struct usbf_ep ep[USBF_NUM_ENDPOINTS]; + /* for EP0 control messages */ + enum usbf_ep0state ep0state; + struct usbf_req setup_reply; + u8 ep0_buf[USBF_EP0_MAX_PCKT_SIZE]; +}; + +struct usbf_ep_info { + const char *name; + struct usb_ep_caps caps; + u16 base_addr; + unsigned int is_double : 1; + u16 maxpacket_limit; +}; + +#define USBF_SINGLE_BUFFER 0 +#define USBF_DOUBLE_BUFFER 1 +#define USBF_EP_INFO(_name, _caps, _base_addr, _is_double, _maxpacket_limit) \ + { \ + .name = _name, \ + .caps = _caps, \ + .base_addr = _base_addr, \ + .is_double = _is_double, \ + .maxpacket_limit = _maxpacket_limit, \ + } + +/* This table is computed from the recommended values provided in the SOC + * datasheet. The buffer type (single/double) and the endpoint type cannot + * be changed. The mapping in internal RAM (base_addr and number of words) + * for each endpoints depends on the max packet size and the buffer type. + */ +static const struct usbf_ep_info usbf_ep_info[USBF_NUM_ENDPOINTS] = { + /* ep0: buf @0x0000 64 bytes, fixed 32 words */ + [0] = USBF_EP_INFO("ep0-ctrl", + USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, + USB_EP_CAPS_DIR_ALL), + 0x0000, USBF_SINGLE_BUFFER, USBF_EP0_MAX_PCKT_SIZE), + /* ep1: buf @0x0020, 2 buffers 512 bytes -> (512 * 2 / 4) words */ + [1] = USBF_EP_INFO("ep1-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + 0x0020, USBF_DOUBLE_BUFFER, 512), + /* ep2: buf @0x0120, 2 buffers 512 bytes -> (512 * 2 / 4) words */ + [2] = USBF_EP_INFO("ep2-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + 0x0120, USBF_DOUBLE_BUFFER, 512), + /* ep3: buf @0x0220, 1 buffer 512 bytes -> (512 * 2 / 4) words */ + [3] = USBF_EP_INFO("ep3-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + 0x0220, USBF_SINGLE_BUFFER, 512), + /* ep4: buf @0x02A0, 1 buffer 512 bytes -> (512 * 1 / 4) words */ + [4] = USBF_EP_INFO("ep4-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + 0x02A0, USBF_SINGLE_BUFFER, 512), + /* ep5: buf @0x0320, 1 buffer 512 bytes -> (512 * 2 / 4) words */ + [5] = USBF_EP_INFO("ep5-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + 0x0320, USBF_SINGLE_BUFFER, 512), + /* ep6: buf @0x03A0, 1 buffer 1024 bytes -> (1024 * 1 / 4) words */ + [6] = USBF_EP_INFO("ep6-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, + USB_EP_CAPS_DIR_ALL), + 0x03A0, USBF_SINGLE_BUFFER, 1024), + /* ep7: buf @0x04A0, 1 buffer 1024 bytes -> (1024 * 1 / 4) words */ + [7] = USBF_EP_INFO("ep7-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, + USB_EP_CAPS_DIR_ALL), + 0x04A0, USBF_SINGLE_BUFFER, 1024), + /* ep8: buf @0x0520, 1 buffer 1024 bytes -> (1024 * 1 / 4) words */ + [8] = USBF_EP_INFO("ep8-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, + USB_EP_CAPS_DIR_ALL), + 0x0520, USBF_SINGLE_BUFFER, 1024), + /* ep9: buf @0x0620, 1 buffer 1024 bytes -> (1024 * 1 / 4) words */ + [9] = USBF_EP_INFO("ep9-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, + USB_EP_CAPS_DIR_ALL), + 0x0620, USBF_SINGLE_BUFFER, 1024), + /* ep10: buf @0x0720, 2 buffers 1024 bytes -> (1024 * 2 / 4) words */ + [10] = USBF_EP_INFO("ep10-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + 0x0720, USBF_DOUBLE_BUFFER, 1024), + /* ep11: buf @0x0920, 2 buffers 1024 bytes -> (1024 * 2 / 4) words */ + [11] = USBF_EP_INFO("ep11-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + 0x0920, USBF_DOUBLE_BUFFER, 1024), + /* ep12: buf @0x0B20, 2 buffers 1024 bytes -> (1024 * 2 / 4) words */ + [12] = USBF_EP_INFO("ep12-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + 0x0B20, USBF_DOUBLE_BUFFER, 1024), + /* ep13: buf @0x0D20, 2 buffers 1024 bytes -> (1024 * 2 / 4) words */ + [13] = USBF_EP_INFO("ep13-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + 0x0D20, USBF_DOUBLE_BUFFER, 1024), + /* ep14: buf @0x0F20, 2 buffers 1024 bytes -> (1024 * 2 / 4) words */ + [14] = USBF_EP_INFO("ep14-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + 0x0F20, USBF_DOUBLE_BUFFER, 1024), + /* ep15: buf @0x1120, 2 buffers 1024 bytes -> (1024 * 2 / 4) words */ + [15] = USBF_EP_INFO("ep15-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + 0x1120, USBF_DOUBLE_BUFFER, 1024), +}; + +static inline u32 usbf_reg_readl(struct usbf_udc *udc, uint offset) +{ + return readl(udc->regs + offset); +} + +static inline void usbf_reg_writel(struct usbf_udc *udc, uint offset, u32 val) +{ + writel(val, udc->regs + offset); +} + +static inline void usbf_reg_bitset(struct usbf_udc *udc, uint offset, u32 set) +{ + u32 tmp; + + tmp = usbf_reg_readl(udc, offset); + tmp |= set; + usbf_reg_writel(udc, offset, tmp); +} + +static inline void usbf_reg_bitclr(struct usbf_udc *udc, uint offset, u32 clr) +{ + u32 tmp; + + tmp = usbf_reg_readl(udc, offset); + tmp &= ~clr; + usbf_reg_writel(udc, offset, tmp); +} + +static inline void usbf_reg_clrset(struct usbf_udc *udc, uint offset, + u32 clr, u32 set) +{ + u32 tmp; + + tmp = usbf_reg_readl(udc, offset); + tmp &= ~clr; + tmp |= set; + usbf_reg_writel(udc, offset, tmp); +} + +static inline u32 usbf_ep_reg_readl(struct usbf_ep *ep, uint offset) +{ + return readl(ep->regs + offset); +} + +static inline void usbf_ep_reg_read_rep(struct usbf_ep *ep, uint offset, + void *dst, uint count) +{ + readsl(ep->regs + offset, dst, count); +} + +static inline void usbf_ep_reg_writel(struct usbf_ep *ep, uint offset, u32 val) +{ + writel(val, ep->regs + offset); +} + +static inline void usbf_ep_reg_write_rep(struct usbf_ep *ep, uint offset, + const void *src, uint count) +{ + writesl(ep->regs + offset, src, count); +} + +static inline void usbf_ep_reg_bitset(struct usbf_ep *ep, uint offset, u32 set) +{ + u32 tmp; + + tmp = usbf_ep_reg_readl(ep, offset); + tmp |= set; + usbf_ep_reg_writel(ep, offset, tmp); +} + +static inline void usbf_ep_reg_bitclr(struct usbf_ep *ep, uint offset, u32 clr) +{ + u32 tmp; + + tmp = usbf_ep_reg_readl(ep, offset); + tmp &= ~clr; + usbf_ep_reg_writel(ep, offset, tmp); +} + +static inline void usbf_ep_reg_clrset(struct usbf_ep *ep, uint offset, + u32 clr, u32 set) +{ + u32 tmp; + + tmp = usbf_ep_reg_readl(ep, offset); + tmp &= ~clr; + tmp |= set; + usbf_ep_reg_writel(ep, offset, tmp); +} + +static inline u32 usbf_ep_dma_reg_readl(struct usbf_ep *ep, uint offset) +{ + return readl(ep->dma_regs + offset); +} + +static inline void usbf_ep_dma_reg_writel(struct usbf_ep *ep, uint offset, + u32 val) +{ + writel(val, ep->dma_regs + offset); +} + +static inline void usbf_ep_dma_reg_bitset(struct usbf_ep *ep, uint offset, + u32 set) +{ + u32 tmp; + + tmp = usbf_ep_dma_reg_readl(ep, offset); + tmp |= set; + usbf_ep_dma_reg_writel(ep, offset, tmp); +} + +static inline void usbf_ep_dma_reg_bitclr(struct usbf_ep *ep, uint offset, + u32 clr) +{ + u32 tmp; + + tmp = usbf_ep_dma_reg_readl(ep, offset); + tmp &= ~clr; + usbf_ep_dma_reg_writel(ep, offset, tmp); +} + +static void usbf_ep0_send_null(struct usbf_ep *ep0, bool is_data1) +{ + u32 set; + + set = USBF_EP0_DEND; + if (is_data1) + set |= USBF_EP0_PIDCLR; + + usbf_ep_reg_bitset(ep0, USBF_REG_EP0_CONTROL, set); +} + +static int usbf_ep0_pio_in(struct usbf_ep *ep0, struct usbf_req *req) +{ + unsigned int left; + unsigned int nb; + const void *buf; + u32 ctrl; + u32 last; + + left = req->req.length - req->req.actual; + + if (left == 0) { + if (!req->is_zero_sent) { + if (req->req.length == 0) { + dev_dbg(ep0->udc->dev, "ep0 send null\n"); + usbf_ep0_send_null(ep0, false); + req->is_zero_sent = 1; + return -EINPROGRESS; + } + if ((req->req.actual % ep0->ep.maxpacket) == 0) { + if (req->req.zero) { + dev_dbg(ep0->udc->dev, "ep0 send null\n"); + usbf_ep0_send_null(ep0, false); + req->is_zero_sent = 1; + return -EINPROGRESS; + } + } + } + return 0; + } + + if (left > ep0->ep.maxpacket) + left = ep0->ep.maxpacket; + + buf = req->req.buf; + buf += req->req.actual; + + nb = left / sizeof(u32); + if (nb) { + usbf_ep_reg_write_rep(ep0, USBF_REG_EP0_WRITE, buf, nb); + buf += (nb * sizeof(u32)); + req->req.actual += (nb * sizeof(u32)); + left -= (nb * sizeof(u32)); + } + ctrl = usbf_ep_reg_readl(ep0, USBF_REG_EP0_CONTROL); + ctrl &= ~USBF_EP0_DW_MASK; + if (left) { + memcpy(&last, buf, left); + usbf_ep_reg_writel(ep0, USBF_REG_EP0_WRITE, last); + ctrl |= USBF_EP0_DW(left); + req->req.actual += left; + } + usbf_ep_reg_writel(ep0, USBF_REG_EP0_CONTROL, ctrl | USBF_EP0_DEND); + + dev_dbg(ep0->udc->dev, "ep0 send %u/%u\n", + req->req.actual, req->req.length); + + return -EINPROGRESS; +} + +static int usbf_ep0_pio_out(struct usbf_ep *ep0, struct usbf_req *req) +{ + int req_status = 0; + unsigned int count; + unsigned int recv; + unsigned int left; + unsigned int nb; + void *buf; + u32 last; + + if (ep0->status & USBF_EP0_OUT_INT) { + recv = usbf_ep_reg_readl(ep0, USBF_REG_EP0_LENGTH) & USBF_EP0_LDATA; + count = recv; + + buf = req->req.buf; + buf += req->req.actual; + + left = req->req.length - req->req.actual; + + dev_dbg(ep0->udc->dev, "ep0 recv %u, left %u\n", count, left); + + if (left > ep0->ep.maxpacket) + left = ep0->ep.maxpacket; + + if (count > left) { + req_status = -EOVERFLOW; + count = left; + } + + if (count) { + nb = count / sizeof(u32); + if (nb) { + usbf_ep_reg_read_rep(ep0, USBF_REG_EP0_READ, + buf, nb); + buf += (nb * sizeof(u32)); + req->req.actual += (nb * sizeof(u32)); + count -= (nb * sizeof(u32)); + } + if (count) { + last = usbf_ep_reg_readl(ep0, USBF_REG_EP0_READ); + memcpy(buf, &last, count); + req->req.actual += count; + } + } + dev_dbg(ep0->udc->dev, "ep0 recv %u/%u\n", + req->req.actual, req->req.length); + + if (req_status) { + dev_dbg(ep0->udc->dev, "ep0 req.status=%d\n", req_status); + req->req.status = req_status; + return 0; + } + + if (recv < ep0->ep.maxpacket) { + dev_dbg(ep0->udc->dev, "ep0 short packet\n"); + /* This is a short packet -> It is the end */ + req->req.status = 0; + return 0; + } + + /* The Data stage of a control transfer from an endpoint to the + * host is complete when the endpoint does one of the following: + * - Has transferred exactly the expected amount of data + * - Transfers a packet with a payload size less than + * wMaxPacketSize or transfers a zero-length packet + */ + if (req->req.actual == req->req.length) { + req->req.status = 0; + return 0; + } + } + + if (ep0->status & USBF_EP0_OUT_NULL_INT) { + /* NULL packet received */ + dev_dbg(ep0->udc->dev, "ep0 null packet\n"); + if (req->req.actual != req->req.length) { + req->req.status = req->req.short_not_ok ? + -EREMOTEIO : 0; + } else { + req->req.status = 0; + } + return 0; + } + + return -EINPROGRESS; +} + +static void usbf_ep0_fifo_flush(struct usbf_ep *ep0) +{ + u32 sts; + int ret; + + usbf_ep_reg_bitset(ep0, USBF_REG_EP0_CONTROL, USBF_EP0_BCLR); + + ret = readl_poll_timeout_atomic(ep0->regs + USBF_REG_EP0_STATUS, sts, + (sts & (USBF_EP0_IN_DATA | USBF_EP0_IN_EMPTY)) == USBF_EP0_IN_EMPTY, + 0, 10000); + if (ret) + dev_err(ep0->udc->dev, "ep0 flush fifo timed out\n"); + +} + +static void usbf_epn_send_null(struct usbf_ep *epn) +{ + usbf_ep_reg_bitset(epn, USBF_REG_EPN_CONTROL, USBF_EPN_DEND); +} + +static void usbf_epn_send_residue(struct usbf_ep *epn, const void *buf, + unsigned int size) +{ + u32 tmp; + + memcpy(&tmp, buf, size); + usbf_ep_reg_writel(epn, USBF_REG_EPN_WRITE, tmp); + + usbf_ep_reg_clrset(epn, USBF_REG_EPN_CONTROL, + USBF_EPN_DW_MASK, + USBF_EPN_DW(size) | USBF_EPN_DEND); +} + +static int usbf_epn_pio_in(struct usbf_ep *epn, struct usbf_req *req) +{ + unsigned int left; + unsigned int nb; + const void *buf; + + left = req->req.length - req->req.actual; + + if (left == 0) { + if (!req->is_zero_sent) { + if (req->req.length == 0) { + dev_dbg(epn->udc->dev, "ep%u send_null\n", epn->id); + usbf_epn_send_null(epn); + req->is_zero_sent = 1; + return -EINPROGRESS; + } + if ((req->req.actual % epn->ep.maxpacket) == 0) { + if (req->req.zero) { + dev_dbg(epn->udc->dev, "ep%u send_null\n", + epn->id); + usbf_epn_send_null(epn); + req->is_zero_sent = 1; + return -EINPROGRESS; + } + } + } + return 0; + } + + if (left > epn->ep.maxpacket) + left = epn->ep.maxpacket; + + buf = req->req.buf; + buf += req->req.actual; + + nb = left / sizeof(u32); + if (nb) { + usbf_ep_reg_write_rep(epn, USBF_REG_EPN_WRITE, buf, nb); + buf += (nb * sizeof(u32)); + req->req.actual += (nb * sizeof(u32)); + left -= (nb * sizeof(u32)); + } + + if (left) { + usbf_epn_send_residue(epn, buf, left); + req->req.actual += left; + } else { + usbf_ep_reg_clrset(epn, USBF_REG_EPN_CONTROL, + USBF_EPN_DW_MASK, + USBF_EPN_DEND); + } + + dev_dbg(epn->udc->dev, "ep%u send %u/%u\n", epn->id, req->req.actual, + req->req.length); + + return -EINPROGRESS; +} + +static void usbf_epn_enable_in_end_int(struct usbf_ep *epn) +{ + usbf_ep_reg_bitset(epn, USBF_REG_EPN_INT_ENA, USBF_EPN_IN_END_EN); +} + +static int usbf_epn_dma_in(struct usbf_ep *epn, struct usbf_req *req) +{ + unsigned int left; + u32 npkt; + u32 lastpkt; + int ret; + + if (!IS_ALIGNED((uintptr_t)req->req.buf, 4)) { + dev_dbg(epn->udc->dev, "ep%u buf unaligned -> fallback pio\n", + epn->id); + return usbf_epn_pio_in(epn, req); + } + + left = req->req.length - req->req.actual; + + switch (req->xfer_step) { + default: + case USBF_XFER_START: + if (left == 0) { + dev_dbg(epn->udc->dev, "ep%u send null\n", epn->id); + usbf_epn_send_null(epn); + req->xfer_step = USBF_XFER_WAIT_END; + break; + } + if (left < 4) { + dev_dbg(epn->udc->dev, "ep%u send residue %u\n", epn->id, + left); + usbf_epn_send_residue(epn, + req->req.buf + req->req.actual, left); + req->req.actual += left; + req->xfer_step = USBF_XFER_WAIT_END; + break; + } + + ret = usb_gadget_map_request(&epn->udc->gadget, &req->req, 1); + if (ret < 0) { + dev_err(epn->udc->dev, "usb_gadget_map_request failed (%d)\n", + ret); + return ret; + } + req->is_mapped = 1; + + npkt = DIV_ROUND_UP(left, epn->ep.maxpacket); + lastpkt = (left % epn->ep.maxpacket); + if (lastpkt == 0) + lastpkt = epn->ep.maxpacket; + lastpkt &= ~0x3; /* DMA is done on 32bit units */ + + usbf_ep_dma_reg_writel(epn, USBF_REG_DMA_EPN_DCR2, + USBF_SYS_EPN_MPKT(epn->ep.maxpacket) | USBF_SYS_EPN_LMPKT(lastpkt)); + usbf_ep_dma_reg_writel(epn, USBF_REG_DMA_EPN_TADR, + req->req.dma); + usbf_ep_dma_reg_writel(epn, USBF_REG_DMA_EPN_DCR1, + USBF_SYS_EPN_SET_DMACNT(npkt)); + usbf_ep_dma_reg_bitset(epn, USBF_REG_DMA_EPN_DCR1, + USBF_SYS_EPN_REQEN); + + usbf_ep_reg_writel(epn, USBF_REG_EPN_LEN_DCNT, USBF_EPN_SET_DMACNT(npkt)); + + usbf_ep_reg_bitset(epn, USBF_REG_EPN_CONTROL, USBF_EPN_AUTO); + + /* The end of DMA transfer at the USBF level needs to be handle + * after the detection of the end of DMA transfer at the brige + * level. + * To force this sequence, EPN_IN_END_EN will be set by the + * detection of the end of transfer at bridge level (ie. bridge + * interrupt). + */ + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_IN_EN | USBF_EPN_IN_END_EN); + epn->bridge_on_dma_end = usbf_epn_enable_in_end_int; + + /* Clear any pending IN_END interrupt */ + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, ~(u32)USBF_EPN_IN_END_INT); + + usbf_ep_reg_writel(epn, USBF_REG_EPN_DMA_CTRL, + USBF_EPN_BURST_SET | USBF_EPN_DMAMODE0); + usbf_ep_reg_bitset(epn, USBF_REG_EPN_DMA_CTRL, + USBF_EPN_DMA_EN); + + req->dma_size = (npkt - 1) * epn->ep.maxpacket + lastpkt; + + dev_dbg(epn->udc->dev, "ep%u dma xfer %zu\n", epn->id, + req->dma_size); + + req->xfer_step = USBF_XFER_WAIT_DMA; + break; + + case USBF_XFER_WAIT_DMA: + if (!(epn->status & USBF_EPN_IN_END_INT)) { + dev_dbg(epn->udc->dev, "ep%u dma not done\n", epn->id); + break; + } + dev_dbg(epn->udc->dev, "ep%u dma done\n", epn->id); + + usb_gadget_unmap_request(&epn->udc->gadget, &req->req, 1); + req->is_mapped = 0; + + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_CONTROL, USBF_EPN_AUTO); + + usbf_ep_reg_clrset(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_IN_END_EN, + USBF_EPN_IN_EN); + + req->req.actual += req->dma_size; + + left = req->req.length - req->req.actual; + if (left) { + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, ~(u32)USBF_EPN_IN_INT); + + dev_dbg(epn->udc->dev, "ep%u send residue %u\n", epn->id, + left); + usbf_epn_send_residue(epn, + req->req.buf + req->req.actual, left); + req->req.actual += left; + req->xfer_step = USBF_XFER_WAIT_END; + break; + } + + if (req->req.actual % epn->ep.maxpacket) { + /* last packet was a short packet. Tell the hardware to + * send it right now. + */ + dev_dbg(epn->udc->dev, "ep%u send short\n", epn->id); + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, + ~(u32)USBF_EPN_IN_INT); + usbf_ep_reg_bitset(epn, USBF_REG_EPN_CONTROL, + USBF_EPN_DEND); + + req->xfer_step = USBF_XFER_WAIT_END; + break; + } + + /* Last packet size was a maxpacket size + * Send null packet if needed + */ + if (req->req.zero) { + req->xfer_step = USBF_XFER_SEND_NULL; + break; + } + + /* No more action to do. Wait for the end of the USB transfer */ + req->xfer_step = USBF_XFER_WAIT_END; + break; + + case USBF_XFER_SEND_NULL: + dev_dbg(epn->udc->dev, "ep%u send null\n", epn->id); + usbf_epn_send_null(epn); + req->xfer_step = USBF_XFER_WAIT_END; + break; + + case USBF_XFER_WAIT_END: + if (!(epn->status & USBF_EPN_IN_INT)) { + dev_dbg(epn->udc->dev, "ep%u end not done\n", epn->id); + break; + } + dev_dbg(epn->udc->dev, "ep%u send done %u/%u\n", epn->id, + req->req.actual, req->req.length); + req->xfer_step = USBF_XFER_START; + return 0; + } + + return -EINPROGRESS; +} + +static void usbf_epn_recv_residue(struct usbf_ep *epn, void *buf, + unsigned int size) +{ + u32 last; + + last = usbf_ep_reg_readl(epn, USBF_REG_EPN_READ); + memcpy(buf, &last, size); +} + +static int usbf_epn_pio_out(struct usbf_ep *epn, struct usbf_req *req) +{ + int req_status = 0; + unsigned int count; + unsigned int recv; + unsigned int left; + unsigned int nb; + void *buf; + + if (epn->status & USBF_EPN_OUT_INT) { + recv = USBF_EPN_GET_LDATA( + usbf_ep_reg_readl(epn, USBF_REG_EPN_LEN_DCNT)); + count = recv; + + buf = req->req.buf; + buf += req->req.actual; + + left = req->req.length - req->req.actual; + + dev_dbg(epn->udc->dev, "ep%u recv %u, left %u, mpkt %u\n", epn->id, + recv, left, epn->ep.maxpacket); + + if (left > epn->ep.maxpacket) + left = epn->ep.maxpacket; + + if (count > left) { + req_status = -EOVERFLOW; + count = left; + } + + if (count) { + nb = count / sizeof(u32); + if (nb) { + usbf_ep_reg_read_rep(epn, USBF_REG_EPN_READ, + buf, nb); + buf += (nb * sizeof(u32)); + req->req.actual += (nb * sizeof(u32)); + count -= (nb * sizeof(u32)); + } + if (count) { + usbf_epn_recv_residue(epn, buf, count); + req->req.actual += count; + } + } + dev_dbg(epn->udc->dev, "ep%u recv %u/%u\n", epn->id, + req->req.actual, req->req.length); + + if (req_status) { + dev_dbg(epn->udc->dev, "ep%u req.status=%d\n", epn->id, + req_status); + req->req.status = req_status; + return 0; + } + + if (recv < epn->ep.maxpacket) { + dev_dbg(epn->udc->dev, "ep%u short packet\n", epn->id); + /* This is a short packet -> It is the end */ + req->req.status = 0; + return 0; + } + + /* Request full -> complete */ + if (req->req.actual == req->req.length) { + req->req.status = 0; + return 0; + } + } + + if (epn->status & USBF_EPN_OUT_NULL_INT) { + /* NULL packet received */ + dev_dbg(epn->udc->dev, "ep%u null packet\n", epn->id); + if (req->req.actual != req->req.length) { + req->req.status = req->req.short_not_ok ? + -EREMOTEIO : 0; + } else { + req->req.status = 0; + } + return 0; + } + + return -EINPROGRESS; +} + +static void usbf_epn_enable_out_end_int(struct usbf_ep *epn) +{ + usbf_ep_reg_bitset(epn, USBF_REG_EPN_INT_ENA, USBF_EPN_OUT_END_EN); +} + +static void usbf_epn_process_queue(struct usbf_ep *epn); + +static void usbf_epn_dma_out_send_dma(struct usbf_ep *epn, dma_addr_t addr, u32 npkt, bool is_short) +{ + usbf_ep_dma_reg_writel(epn, USBF_REG_DMA_EPN_DCR2, USBF_SYS_EPN_MPKT(epn->ep.maxpacket)); + usbf_ep_dma_reg_writel(epn, USBF_REG_DMA_EPN_TADR, addr); + + if (is_short) { + usbf_ep_dma_reg_writel(epn, USBF_REG_DMA_EPN_DCR1, + USBF_SYS_EPN_SET_DMACNT(1) | USBF_SYS_EPN_DIR0); + usbf_ep_dma_reg_bitset(epn, USBF_REG_DMA_EPN_DCR1, + USBF_SYS_EPN_REQEN); + + usbf_ep_reg_writel(epn, USBF_REG_EPN_LEN_DCNT, + USBF_EPN_SET_DMACNT(0)); + + /* The end of DMA transfer at the USBF level needs to be handled + * after the detection of the end of DMA transfer at the brige + * level. + * To force this sequence, enabling the OUT_END interrupt will + * be donee by the detection of the end of transfer at bridge + * level (ie. bridge interrupt). + */ + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_EN | USBF_EPN_OUT_NULL_EN | USBF_EPN_OUT_END_EN); + epn->bridge_on_dma_end = usbf_epn_enable_out_end_int; + + /* Clear any pending OUT_END interrupt */ + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, + ~(u32)USBF_EPN_OUT_END_INT); + + usbf_ep_reg_writel(epn, USBF_REG_EPN_DMA_CTRL, + USBF_EPN_STOP_MODE | USBF_EPN_STOP_SET | USBF_EPN_DMAMODE0); + usbf_ep_reg_bitset(epn, USBF_REG_EPN_DMA_CTRL, + USBF_EPN_DMA_EN); + return; + } + + usbf_ep_dma_reg_writel(epn, USBF_REG_DMA_EPN_DCR1, + USBF_SYS_EPN_SET_DMACNT(npkt) | USBF_SYS_EPN_DIR0); + usbf_ep_dma_reg_bitset(epn, USBF_REG_DMA_EPN_DCR1, + USBF_SYS_EPN_REQEN); + + usbf_ep_reg_writel(epn, USBF_REG_EPN_LEN_DCNT, + USBF_EPN_SET_DMACNT(npkt)); + + /* Here, the bridge may or may not generate an interrupt to signal the + * end of DMA transfer. + * Keep only OUT_END interrupt and let handle the bridge later during + * the OUT_END processing. + */ + usbf_ep_reg_clrset(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_EN | USBF_EPN_OUT_NULL_EN, + USBF_EPN_OUT_END_EN); + + /* Disable bridge interrupt. It will be renabled later */ + usbf_reg_bitclr(epn->udc, USBF_REG_AHBBINTEN, + USBF_SYS_DMA_ENDINTEN_EPN(epn->id)); + + /* Clear any pending DMA_END interrupt at bridge level */ + usbf_reg_writel(epn->udc, USBF_REG_AHBBINT, + USBF_SYS_DMA_ENDINT_EPN(epn->id)); + + /* Clear any pending OUT_END interrupt */ + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, + ~(u32)USBF_EPN_OUT_END_INT); + + usbf_ep_reg_writel(epn, USBF_REG_EPN_DMA_CTRL, + USBF_EPN_STOP_MODE | USBF_EPN_STOP_SET | USBF_EPN_DMAMODE0 | USBF_EPN_BURST_SET); + usbf_ep_reg_bitset(epn, USBF_REG_EPN_DMA_CTRL, + USBF_EPN_DMA_EN); +} + +static size_t usbf_epn_dma_out_complete_dma(struct usbf_ep *epn, bool is_short) +{ + u32 dmacnt; + u32 tmp; + int ret; + + /* Restore interrupt mask */ + usbf_ep_reg_clrset(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_END_EN, + USBF_EPN_OUT_EN | USBF_EPN_OUT_NULL_EN); + + if (is_short) { + /* Nothing more to do when the DMA was for a short packet */ + return 0; + } + + /* Enable the bridge interrupt */ + usbf_reg_bitset(epn->udc, USBF_REG_AHBBINTEN, + USBF_SYS_DMA_ENDINTEN_EPN(epn->id)); + + tmp = usbf_ep_reg_readl(epn, USBF_REG_EPN_LEN_DCNT); + dmacnt = USBF_EPN_GET_DMACNT(tmp); + + if (dmacnt) { + /* Some packet were not received (halted by a short or a null + * packet. + * The bridge never raises an interrupt in this case. + * Wait for the end of transfer at bridge level + */ + ret = readl_poll_timeout_atomic( + epn->dma_regs + USBF_REG_DMA_EPN_DCR1, + tmp, (USBF_SYS_EPN_GET_DMACNT(tmp) == dmacnt), + 0, 10000); + if (ret) { + dev_err(epn->udc->dev, "ep%u wait bridge timed out\n", + epn->id); + } + + usbf_ep_dma_reg_bitclr(epn, USBF_REG_DMA_EPN_DCR1, + USBF_SYS_EPN_REQEN); + + /* The dmacnt value tells how many packet were not transferred + * from the maximum number of packet we set for the DMA transfer. + * Compute the left DMA size based on this value. + */ + return dmacnt * epn->ep.maxpacket; + } + + return 0; +} + +static int usbf_epn_dma_out(struct usbf_ep *epn, struct usbf_req *req) +{ + unsigned int dma_left; + unsigned int count; + unsigned int recv; + unsigned int left; + u32 npkt; + int ret; + + if (!IS_ALIGNED((uintptr_t)req->req.buf, 4)) { + dev_dbg(epn->udc->dev, "ep%u buf unaligned -> fallback pio\n", + epn->id); + return usbf_epn_pio_out(epn, req); + } + + switch (req->xfer_step) { + default: + case USBF_XFER_START: + if (epn->status & USBF_EPN_OUT_NULL_INT) { + dev_dbg(epn->udc->dev, "ep%u null packet\n", epn->id); + if (req->req.actual != req->req.length) { + req->req.status = req->req.short_not_ok ? + -EREMOTEIO : 0; + } else { + req->req.status = 0; + } + return 0; + } + + if (!(epn->status & USBF_EPN_OUT_INT)) { + dev_dbg(epn->udc->dev, "ep%u OUT_INT not set -> spurious\n", + epn->id); + break; + } + + recv = USBF_EPN_GET_LDATA( + usbf_ep_reg_readl(epn, USBF_REG_EPN_LEN_DCNT)); + if (!recv) { + dev_dbg(epn->udc->dev, "ep%u recv = 0 -> spurious\n", + epn->id); + break; + } + + left = req->req.length - req->req.actual; + + dev_dbg(epn->udc->dev, "ep%u recv %u, left %u, mpkt %u\n", epn->id, + recv, left, epn->ep.maxpacket); + + if (recv > left) { + dev_err(epn->udc->dev, "ep%u overflow (%u/%u)\n", + epn->id, recv, left); + req->req.status = -EOVERFLOW; + return -EOVERFLOW; + } + + if (recv < epn->ep.maxpacket) { + /* Short packet received */ + dev_dbg(epn->udc->dev, "ep%u short packet\n", epn->id); + if (recv <= 3) { + usbf_epn_recv_residue(epn, + req->req.buf + req->req.actual, recv); + req->req.actual += recv; + + dev_dbg(epn->udc->dev, "ep%u recv done %u/%u\n", + epn->id, req->req.actual, req->req.length); + + req->xfer_step = USBF_XFER_START; + return 0; + } + + ret = usb_gadget_map_request(&epn->udc->gadget, &req->req, 0); + if (ret < 0) { + dev_err(epn->udc->dev, "map request failed (%d)\n", + ret); + return ret; + } + req->is_mapped = 1; + + usbf_epn_dma_out_send_dma(epn, + req->req.dma + req->req.actual, + 1, true); + req->dma_size = recv & ~0x3; + + dev_dbg(epn->udc->dev, "ep%u dma short xfer %zu\n", epn->id, + req->dma_size); + + req->xfer_step = USBF_XFER_WAIT_DMA_SHORT; + break; + } + + ret = usb_gadget_map_request(&epn->udc->gadget, &req->req, 0); + if (ret < 0) { + dev_err(epn->udc->dev, "map request failed (%d)\n", + ret); + return ret; + } + req->is_mapped = 1; + + /* Use the maximum DMA size according to the request buffer. + * We will adjust the received size later at the end of the DMA + * transfer with the left size computed from + * usbf_epn_dma_out_complete_dma(). + */ + npkt = left / epn->ep.maxpacket; + usbf_epn_dma_out_send_dma(epn, + req->req.dma + req->req.actual, + npkt, false); + req->dma_size = npkt * epn->ep.maxpacket; + + dev_dbg(epn->udc->dev, "ep%u dma xfer %zu (%u)\n", epn->id, + req->dma_size, npkt); + + req->xfer_step = USBF_XFER_WAIT_DMA; + break; + + case USBF_XFER_WAIT_DMA_SHORT: + if (!(epn->status & USBF_EPN_OUT_END_INT)) { + dev_dbg(epn->udc->dev, "ep%u dma short not done\n", epn->id); + break; + } + dev_dbg(epn->udc->dev, "ep%u dma short done\n", epn->id); + + usbf_epn_dma_out_complete_dma(epn, true); + + usb_gadget_unmap_request(&epn->udc->gadget, &req->req, 0); + req->is_mapped = 0; + + req->req.actual += req->dma_size; + + recv = USBF_EPN_GET_LDATA( + usbf_ep_reg_readl(epn, USBF_REG_EPN_LEN_DCNT)); + + count = recv & 0x3; + if (count) { + dev_dbg(epn->udc->dev, "ep%u recv residue %u\n", epn->id, + count); + usbf_epn_recv_residue(epn, + req->req.buf + req->req.actual, count); + req->req.actual += count; + } + + dev_dbg(epn->udc->dev, "ep%u recv done %u/%u\n", epn->id, + req->req.actual, req->req.length); + + req->xfer_step = USBF_XFER_START; + return 0; + + case USBF_XFER_WAIT_DMA: + if (!(epn->status & USBF_EPN_OUT_END_INT)) { + dev_dbg(epn->udc->dev, "ep%u dma not done\n", epn->id); + break; + } + dev_dbg(epn->udc->dev, "ep%u dma done\n", epn->id); + + dma_left = usbf_epn_dma_out_complete_dma(epn, false); + if (dma_left) { + /* Adjust the final DMA size with */ + count = req->dma_size - dma_left; + + dev_dbg(epn->udc->dev, "ep%u dma xfer done %u\n", epn->id, + count); + + req->req.actual += count; + + if (epn->status & USBF_EPN_OUT_NULL_INT) { + /* DMA was stopped by a null packet reception */ + dev_dbg(epn->udc->dev, "ep%u dma stopped by null pckt\n", + epn->id); + usb_gadget_unmap_request(&epn->udc->gadget, + &req->req, 0); + req->is_mapped = 0; + + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, + ~(u32)USBF_EPN_OUT_NULL_INT); + + if (req->req.actual != req->req.length) { + req->req.status = req->req.short_not_ok ? + -EREMOTEIO : 0; + } else { + req->req.status = 0; + } + dev_dbg(epn->udc->dev, "ep%u recv done %u/%u\n", + epn->id, req->req.actual, req->req.length); + req->xfer_step = USBF_XFER_START; + return 0; + } + + recv = USBF_EPN_GET_LDATA( + usbf_ep_reg_readl(epn, USBF_REG_EPN_LEN_DCNT)); + left = req->req.length - req->req.actual; + if (recv > left) { + dev_err(epn->udc->dev, + "ep%u overflow (%u/%u)\n", epn->id, + recv, left); + req->req.status = -EOVERFLOW; + usb_gadget_unmap_request(&epn->udc->gadget, + &req->req, 0); + req->is_mapped = 0; + + req->xfer_step = USBF_XFER_START; + return -EOVERFLOW; + } + + if (recv > 3) { + usbf_epn_dma_out_send_dma(epn, + req->req.dma + req->req.actual, + 1, true); + req->dma_size = recv & ~0x3; + + dev_dbg(epn->udc->dev, "ep%u dma short xfer %zu\n", + epn->id, req->dma_size); + + req->xfer_step = USBF_XFER_WAIT_DMA_SHORT; + break; + } + + usb_gadget_unmap_request(&epn->udc->gadget, &req->req, 0); + req->is_mapped = 0; + + count = recv & 0x3; + if (count) { + dev_dbg(epn->udc->dev, "ep%u recv residue %u\n", + epn->id, count); + usbf_epn_recv_residue(epn, + req->req.buf + req->req.actual, count); + req->req.actual += count; + } + + dev_dbg(epn->udc->dev, "ep%u recv done %u/%u\n", epn->id, + req->req.actual, req->req.length); + + req->xfer_step = USBF_XFER_START; + return 0; + } + + /* Process queue at bridge interrupt only */ + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_END_EN | USBF_EPN_OUT_EN | USBF_EPN_OUT_NULL_EN); + epn->status = 0; + epn->bridge_on_dma_end = usbf_epn_process_queue; + + req->xfer_step = USBF_XFER_WAIT_BRIDGE; + break; + + case USBF_XFER_WAIT_BRIDGE: + dev_dbg(epn->udc->dev, "ep%u bridge transfers done\n", epn->id); + + /* Restore interrupt mask */ + usbf_ep_reg_clrset(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_END_EN, + USBF_EPN_OUT_EN | USBF_EPN_OUT_NULL_EN); + + usb_gadget_unmap_request(&epn->udc->gadget, &req->req, 0); + req->is_mapped = 0; + + req->req.actual += req->dma_size; + + req->xfer_step = USBF_XFER_START; + left = req->req.length - req->req.actual; + if (!left) { + /* No more data can be added to the buffer */ + dev_dbg(epn->udc->dev, "ep%u recv done %u/%u\n", epn->id, + req->req.actual, req->req.length); + return 0; + } + dev_dbg(epn->udc->dev, "ep%u recv done %u/%u, wait more data\n", + epn->id, req->req.actual, req->req.length); + break; + } + + return -EINPROGRESS; +} + +static void usbf_epn_dma_stop(struct usbf_ep *epn) +{ + usbf_ep_dma_reg_bitclr(epn, USBF_REG_DMA_EPN_DCR1, USBF_SYS_EPN_REQEN); + + /* In the datasheet: + * If EP[m]_REQEN = 0b is set during DMA transfer, AHB-EPC stops DMA + * after 1 packet transfer completed. + * Therefore, wait sufficient time for ensuring DMA transfer + * completion. The WAIT time depends on the system, especially AHB + * bus activity + * So arbitrary 10ms would be sufficient. + */ + mdelay(10); + + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_DMA_CTRL, USBF_EPN_DMA_EN); +} + +static void usbf_epn_dma_abort(struct usbf_ep *epn, struct usbf_req *req) +{ + dev_dbg(epn->udc->dev, "ep%u %s dma abort\n", epn->id, + epn->is_in ? "in" : "out"); + + epn->bridge_on_dma_end = NULL; + + usbf_epn_dma_stop(epn); + + usb_gadget_unmap_request(&epn->udc->gadget, &req->req, + epn->is_in ? 1 : 0); + req->is_mapped = 0; + + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_CONTROL, USBF_EPN_AUTO); + + if (epn->is_in) { + usbf_ep_reg_clrset(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_IN_END_EN, + USBF_EPN_IN_EN); + } else { + usbf_ep_reg_clrset(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_END_EN, + USBF_EPN_OUT_EN | USBF_EPN_OUT_NULL_EN); + } + + /* As dma is stopped, be sure that no DMA interrupt are pending */ + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, + USBF_EPN_IN_END_INT | USBF_EPN_OUT_END_INT); + + usbf_reg_writel(epn->udc, USBF_REG_AHBBINT, USBF_SYS_DMA_ENDINT_EPN(epn->id)); + + /* Enable DMA interrupt the bridge level */ + usbf_reg_bitset(epn->udc, USBF_REG_AHBBINTEN, + USBF_SYS_DMA_ENDINTEN_EPN(epn->id)); + + /* Reset transfer step */ + req->xfer_step = USBF_XFER_START; +} + +static void usbf_epn_fifo_flush(struct usbf_ep *epn) +{ + u32 ctrl; + u32 sts; + int ret; + + dev_dbg(epn->udc->dev, "ep%u %s fifo flush\n", epn->id, + epn->is_in ? "in" : "out"); + + ctrl = usbf_ep_reg_readl(epn, USBF_REG_EPN_CONTROL); + usbf_ep_reg_writel(epn, USBF_REG_EPN_CONTROL, ctrl | USBF_EPN_BCLR); + + if (ctrl & USBF_EPN_DIR0) + return; + + ret = readl_poll_timeout_atomic(epn->regs + USBF_REG_EPN_STATUS, sts, + (sts & (USBF_EPN_IN_DATA | USBF_EPN_IN_EMPTY)) == USBF_EPN_IN_EMPTY, + 0, 10000); + if (ret) + dev_err(epn->udc->dev, "ep%u flush fifo timed out\n", epn->id); +} + +static void usbf_ep_req_done(struct usbf_ep *ep, struct usbf_req *req, + int status) +{ + list_del_init(&req->queue); + + if (status) { + req->req.status = status; + } else { + if (req->req.status == -EINPROGRESS) + req->req.status = status; + } + + dev_dbg(ep->udc->dev, "ep%u %s req done length %u/%u, status=%d\n", ep->id, + ep->is_in ? "in" : "out", + req->req.actual, req->req.length, req->req.status); + + if (req->is_mapped) + usbf_epn_dma_abort(ep, req); + + spin_unlock(&ep->udc->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&ep->udc->lock); +} + +static void usbf_ep_nuke(struct usbf_ep *ep, int status) +{ + struct usbf_req *req; + + dev_dbg(ep->udc->dev, "ep%u %s nuke status %d\n", ep->id, + ep->is_in ? "in" : "out", + status); + + while (!list_empty(&ep->queue)) { + req = list_first_entry(&ep->queue, struct usbf_req, queue); + usbf_ep_req_done(ep, req, status); + } + + if (ep->id == 0) + usbf_ep0_fifo_flush(ep); + else + usbf_epn_fifo_flush(ep); +} + +static bool usbf_ep_is_stalled(struct usbf_ep *ep) +{ + u32 ctrl; + + if (ep->id == 0) { + ctrl = usbf_ep_reg_readl(ep, USBF_REG_EP0_CONTROL); + return (ctrl & USBF_EP0_STL) ? true : false; + } + + ctrl = usbf_ep_reg_readl(ep, USBF_REG_EPN_CONTROL); + if (ep->is_in) + return (ctrl & USBF_EPN_ISTL) ? true : false; + + return (ctrl & USBF_EPN_OSTL) ? true : false; +} + +static int usbf_epn_start_queue(struct usbf_ep *epn) +{ + struct usbf_req *req; + int ret; + + if (usbf_ep_is_stalled(epn)) + return 0; + + req = list_first_entry_or_null(&epn->queue, struct usbf_req, queue); + + if (epn->is_in) { + if (req && !epn->is_processing) { + ret = epn->dma_regs ? + usbf_epn_dma_in(epn, req) : + usbf_epn_pio_in(epn, req); + if (ret != -EINPROGRESS) { + dev_err(epn->udc->dev, + "queued next request not in progress\n"); + /* The request cannot be completed (ie + * ret == 0) on the first call. + * stall and nuke the endpoint + */ + return ret ? ret : -EIO; + } + } + } else { + if (req) { + /* Clear ONAK to accept OUT tokens */ + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_CONTROL, + USBF_EPN_ONAK); + + /* Enable interrupts */ + usbf_ep_reg_bitset(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_INT | USBF_EPN_OUT_NULL_INT); + } else { + /* Disable incoming data and interrupt. + * They will be enable on next usb_eb_queue call + */ + usbf_ep_reg_bitset(epn, USBF_REG_EPN_CONTROL, + USBF_EPN_ONAK); + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_INT | USBF_EPN_OUT_NULL_INT); + } + } + return 0; +} + +static int usbf_ep_process_queue(struct usbf_ep *ep) +{ + int (*usbf_ep_xfer)(struct usbf_ep *ep, struct usbf_req *req); + struct usbf_req *req; + int is_processing; + int ret; + + if (ep->is_in) { + usbf_ep_xfer = usbf_ep0_pio_in; + if (ep->id) { + usbf_ep_xfer = ep->dma_regs ? + usbf_epn_dma_in : usbf_epn_pio_in; + } + } else { + usbf_ep_xfer = usbf_ep0_pio_out; + if (ep->id) { + usbf_ep_xfer = ep->dma_regs ? + usbf_epn_dma_out : usbf_epn_pio_out; + } + } + + req = list_first_entry_or_null(&ep->queue, struct usbf_req, queue); + if (!req) { + dev_err(ep->udc->dev, + "no request available for ep%u %s process\n", ep->id, + ep->is_in ? "in" : "out"); + return -ENOENT; + } + + do { + /* Were going to read the FIFO for this current request. + * NAK any other incoming data to avoid a race condition if no + * more request are available. + */ + if (!ep->is_in && ep->id != 0) { + usbf_ep_reg_bitset(ep, USBF_REG_EPN_CONTROL, + USBF_EPN_ONAK); + } + + ret = usbf_ep_xfer(ep, req); + if (ret == -EINPROGRESS) { + if (!ep->is_in && ep->id != 0) { + /* The current request needs more data. + * Allow incoming data + */ + usbf_ep_reg_bitclr(ep, USBF_REG_EPN_CONTROL, + USBF_EPN_ONAK); + } + return ret; + } + + is_processing = ep->is_processing; + ep->is_processing = 1; + usbf_ep_req_done(ep, req, ret); + ep->is_processing = is_processing; + + if (ret) { + /* An error was detected during the request transfer. + * Any pending DMA transfers were aborted by the + * usbf_ep_req_done() call. + * It's time to flush the fifo + */ + if (ep->id == 0) + usbf_ep0_fifo_flush(ep); + else + usbf_epn_fifo_flush(ep); + } + + req = list_first_entry_or_null(&ep->queue, struct usbf_req, + queue); + + if (ep->is_in) + continue; + + if (ep->id != 0) { + if (req) { + /* An other request is available. + * Allow incoming data + */ + usbf_ep_reg_bitclr(ep, USBF_REG_EPN_CONTROL, + USBF_EPN_ONAK); + } else { + /* No request queued. Disable interrupts. + * They will be enabled on usb_ep_queue + */ + usbf_ep_reg_bitclr(ep, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_INT | USBF_EPN_OUT_NULL_INT); + } + } + /* Do not recall usbf_ep_xfer() */ + return req ? -EINPROGRESS : 0; + + } while (req); + + return 0; +} + +static void usbf_ep_stall(struct usbf_ep *ep, bool stall) +{ + struct usbf_req *first; + + dev_dbg(ep->udc->dev, "ep%u %s %s\n", ep->id, + ep->is_in ? "in" : "out", + stall ? "stall" : "unstall"); + + if (ep->id == 0) { + if (stall) + usbf_ep_reg_bitset(ep, USBF_REG_EP0_CONTROL, USBF_EP0_STL); + else + usbf_ep_reg_bitclr(ep, USBF_REG_EP0_CONTROL, USBF_EP0_STL); + return; + } + + if (stall) { + if (ep->is_in) + usbf_ep_reg_bitset(ep, USBF_REG_EPN_CONTROL, + USBF_EPN_ISTL); + else + usbf_ep_reg_bitset(ep, USBF_REG_EPN_CONTROL, + USBF_EPN_OSTL | USBF_EPN_OSTL_EN); + } else { + first = list_first_entry_or_null(&ep->queue, struct usbf_req, queue); + if (first && first->is_mapped) { + /* This can appear if the host halts an endpoint using + * SET_FEATURE and then un-halts the endpoint + */ + usbf_epn_dma_abort(ep, first); + } + usbf_epn_fifo_flush(ep); + if (ep->is_in) { + usbf_ep_reg_clrset(ep, USBF_REG_EPN_CONTROL, + USBF_EPN_ISTL, + USBF_EPN_IPIDCLR); + } else { + usbf_ep_reg_clrset(ep, USBF_REG_EPN_CONTROL, + USBF_EPN_OSTL, + USBF_EPN_OSTL_EN | USBF_EPN_OPIDCLR); + } + usbf_epn_start_queue(ep); + } +} + +static void usbf_ep0_enable(struct usbf_ep *ep0) +{ + usbf_ep_reg_writel(ep0, USBF_REG_EP0_CONTROL, USBF_EP0_INAK_EN | USBF_EP0_BCLR); + + usbf_ep_reg_writel(ep0, USBF_REG_EP0_INT_ENA, + USBF_EP0_SETUP_EN | USBF_EP0_STG_START_EN | USBF_EP0_STG_END_EN | + USBF_EP0_OUT_EN | USBF_EP0_OUT_NULL_EN | USBF_EP0_IN_EN); + + ep0->udc->ep0state = EP0_IDLE; + ep0->disabled = 0; + + /* enable interrupts for the ep0 */ + usbf_reg_bitset(ep0->udc, USBF_REG_USB_INT_ENA, USBF_USB_EPN_EN(0)); +} + +static int usbf_epn_enable(struct usbf_ep *epn) +{ + u32 base_addr; + u32 ctrl; + + base_addr = usbf_ep_info[epn->id].base_addr; + usbf_ep_reg_writel(epn, USBF_REG_EPN_PCKT_ADRS, + USBF_EPN_BASEAD(base_addr) | USBF_EPN_MPKT(epn->ep.maxpacket)); + + /* OUT transfer interrupt are enabled during usb_ep_queue */ + if (epn->is_in) { + /* Will be changed in DMA processing */ + usbf_ep_reg_writel(epn, USBF_REG_EPN_INT_ENA, USBF_EPN_IN_EN); + } + + /* Clear, set endpoint direction, set IN/OUT STL, and enable + * Send NAK for Data out as request are not queued yet + */ + ctrl = USBF_EPN_EN | USBF_EPN_BCLR; + if (epn->is_in) + ctrl |= USBF_EPN_OSTL | USBF_EPN_OSTL_EN; + else + ctrl |= USBF_EPN_DIR0 | USBF_EPN_ISTL | USBF_EPN_OSTL_EN | USBF_EPN_ONAK; + usbf_ep_reg_writel(epn, USBF_REG_EPN_CONTROL, ctrl); + + return 0; +} + +static int usbf_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct usbf_ep *ep = container_of(_ep, struct usbf_ep, ep); + struct usbf_udc *udc = ep->udc; + unsigned long flags; + int ret; + + if (ep->id == 0) + return -EINVAL; + + if (!desc || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + + dev_dbg(ep->udc->dev, "ep%u %s mpkts %d\n", ep->id, + usb_endpoint_dir_in(desc) ? "in" : "out", + usb_endpoint_maxp(desc)); + + spin_lock_irqsave(&ep->udc->lock, flags); + ep->is_in = usb_endpoint_dir_in(desc); + ep->ep.maxpacket = usb_endpoint_maxp(desc); + + ret = usbf_epn_enable(ep); + if (ret) + goto end; + + ep->disabled = 0; + + /* enable interrupts for this endpoint */ + usbf_reg_bitset(udc, USBF_REG_USB_INT_ENA, USBF_USB_EPN_EN(ep->id)); + + /* enable DMA interrupt at bridge level if DMA is used */ + if (ep->dma_regs) { + ep->bridge_on_dma_end = NULL; + usbf_reg_bitset(udc, USBF_REG_AHBBINTEN, + USBF_SYS_DMA_ENDINTEN_EPN(ep->id)); + } + + ret = 0; +end: + spin_unlock_irqrestore(&ep->udc->lock, flags); + return ret; +} + +static int usbf_epn_disable(struct usbf_ep *epn) +{ + /* Disable interrupts */ + usbf_ep_reg_writel(epn, USBF_REG_EPN_INT_ENA, 0); + + /* Disable endpoint */ + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_CONTROL, USBF_EPN_EN); + + /* remove anything that was pending */ + usbf_ep_nuke(epn, -ESHUTDOWN); + + return 0; +} + +static int usbf_ep_disable(struct usb_ep *_ep) +{ + struct usbf_ep *ep = container_of(_ep, struct usbf_ep, ep); + struct usbf_udc *udc = ep->udc; + unsigned long flags; + int ret; + + if (ep->id == 0) + return -EINVAL; + + dev_dbg(ep->udc->dev, "ep%u %s mpkts %d\n", ep->id, + ep->is_in ? "in" : "out", ep->ep.maxpacket); + + spin_lock_irqsave(&ep->udc->lock, flags); + ep->disabled = 1; + /* Disable DMA interrupt */ + if (ep->dma_regs) { + usbf_reg_bitclr(udc, USBF_REG_AHBBINTEN, + USBF_SYS_DMA_ENDINTEN_EPN(ep->id)); + ep->bridge_on_dma_end = NULL; + } + /* disable interrupts for this endpoint */ + usbf_reg_bitclr(udc, USBF_REG_USB_INT_ENA, USBF_USB_EPN_EN(ep->id)); + /* and the endpoint itself */ + ret = usbf_epn_disable(ep); + spin_unlock_irqrestore(&ep->udc->lock, flags); + + return ret; +} + +static int usbf_ep0_queue(struct usbf_ep *ep0, struct usbf_req *req, + gfp_t gfp_flags) +{ + int ret; + + req->req.actual = 0; + req->req.status = -EINPROGRESS; + req->is_zero_sent = 0; + + list_add_tail(&req->queue, &ep0->queue); + + if (ep0->udc->ep0state == EP0_IN_STATUS_START_PHASE) + return 0; + + if (!ep0->is_in) + return 0; + + if (ep0->udc->ep0state == EP0_IN_STATUS_PHASE) { + if (req->req.length) { + dev_err(ep0->udc->dev, + "request lng %u for ep0 in status phase\n", + req->req.length); + return -EINVAL; + } + ep0->delayed_status = 0; + } + if (!ep0->is_processing) { + ret = usbf_ep0_pio_in(ep0, req); + if (ret != -EINPROGRESS) { + dev_err(ep0->udc->dev, + "queued request not in progress\n"); + /* The request cannot be completed (ie + * ret == 0) on the first call + */ + return ret ? ret : -EIO; + } + } + + return 0; +} + +static int usbf_epn_queue(struct usbf_ep *ep, struct usbf_req *req, + gfp_t gfp_flags) +{ + int was_empty; + int ret; + + if (ep->disabled) { + dev_err(ep->udc->dev, "ep%u request queue while disable\n", + ep->id); + return -ESHUTDOWN; + } + + req->req.actual = 0; + req->req.status = -EINPROGRESS; + req->is_zero_sent = 0; + req->xfer_step = USBF_XFER_START; + + was_empty = list_empty(&ep->queue); + list_add_tail(&req->queue, &ep->queue); + if (was_empty) { + ret = usbf_epn_start_queue(ep); + if (ret) + return ret; + } + return 0; +} + +static int usbf_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct usbf_req *req = container_of(_req, struct usbf_req, req); + struct usbf_ep *ep = container_of(_ep, struct usbf_ep, ep); + struct usbf_udc *udc = ep->udc; + unsigned long flags; + int ret; + + if (!_req || !_req->buf) + return -EINVAL; + + if (!udc || !udc->driver) + return -EINVAL; + + dev_dbg(ep->udc->dev, "ep%u %s req queue length %u, zero %u, short_not_ok %u\n", + ep->id, ep->is_in ? "in" : "out", + req->req.length, req->req.zero, req->req.short_not_ok); + + spin_lock_irqsave(&ep->udc->lock, flags); + if (ep->id == 0) + ret = usbf_ep0_queue(ep, req, gfp_flags); + else + ret = usbf_epn_queue(ep, req, gfp_flags); + spin_unlock_irqrestore(&ep->udc->lock, flags); + return ret; +} + +static int usbf_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct usbf_req *req = container_of(_req, struct usbf_req, req); + struct usbf_ep *ep = container_of(_ep, struct usbf_ep, ep); + unsigned long flags; + int is_processing; + int first; + int ret; + + spin_lock_irqsave(&ep->udc->lock, flags); + + dev_dbg(ep->udc->dev, "ep%u %s req dequeue length %u/%u\n", + ep->id, ep->is_in ? "in" : "out", + req->req.actual, req->req.length); + + first = list_is_first(&req->queue, &ep->queue); + + /* Complete the request but avoid any operation that could be done + * if a new request is queued during the request completion + */ + is_processing = ep->is_processing; + ep->is_processing = 1; + usbf_ep_req_done(ep, req, -ECONNRESET); + ep->is_processing = is_processing; + + if (first) { + /* The first item in the list was dequeued. + * This item could already be submitted to the hardware. + * So, flush the fifo + */ + if (ep->id) + usbf_epn_fifo_flush(ep); + else + usbf_ep0_fifo_flush(ep); + } + + if (ep->id == 0) { + /* We dequeue a request on ep0. On this endpoint, we can have + * 1 request related to the data stage and/or 1 request + * related to the status stage. + * We dequeue one of them and so the USB control transaction + * is no more coherent. The simple way to be consistent after + * dequeuing is to stall and nuke the endpoint and wait the + * next SETUP packet. + */ + usbf_ep_stall(ep, true); + usbf_ep_nuke(ep, -ECONNRESET); + ep->udc->ep0state = EP0_IDLE; + goto end; + } + + if (!first) + goto end; + + ret = usbf_epn_start_queue(ep); + if (ret) { + usbf_ep_stall(ep, true); + usbf_ep_nuke(ep, -EIO); + } +end: + spin_unlock_irqrestore(&ep->udc->lock, flags); + return 0; +} + +static struct usb_request *usbf_ep_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct usbf_req *req; + + if (!_ep) + return NULL; + + req = kzalloc_obj(*req, gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void usbf_ep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct usbf_req *req; + unsigned long flags; + struct usbf_ep *ep; + + if (!_ep || !_req) + return; + + req = container_of(_req, struct usbf_req, req); + ep = container_of(_ep, struct usbf_ep, ep); + + spin_lock_irqsave(&ep->udc->lock, flags); + list_del_init(&req->queue); + spin_unlock_irqrestore(&ep->udc->lock, flags); + kfree(req); +} + +static int usbf_ep_set_halt(struct usb_ep *_ep, int halt) +{ + struct usbf_ep *ep = container_of(_ep, struct usbf_ep, ep); + unsigned long flags; + int ret; + + if (ep->id == 0) + return -EINVAL; + + spin_lock_irqsave(&ep->udc->lock, flags); + + if (!list_empty(&ep->queue)) { + ret = -EAGAIN; + goto end; + } + + usbf_ep_stall(ep, halt); + if (!halt) + ep->is_wedged = 0; + + ret = 0; +end: + spin_unlock_irqrestore(&ep->udc->lock, flags); + + return ret; +} + +static int usbf_ep_set_wedge(struct usb_ep *_ep) +{ + struct usbf_ep *ep = container_of(_ep, struct usbf_ep, ep); + unsigned long flags; + int ret; + + if (ep->id == 0) + return -EINVAL; + + spin_lock_irqsave(&ep->udc->lock, flags); + if (!list_empty(&ep->queue)) { + ret = -EAGAIN; + goto end; + } + usbf_ep_stall(ep, 1); + ep->is_wedged = 1; + + ret = 0; +end: + spin_unlock_irqrestore(&ep->udc->lock, flags); + return ret; +} + +static struct usb_ep_ops usbf_ep_ops = { + .enable = usbf_ep_enable, + .disable = usbf_ep_disable, + .queue = usbf_ep_queue, + .dequeue = usbf_ep_dequeue, + .set_halt = usbf_ep_set_halt, + .set_wedge = usbf_ep_set_wedge, + .alloc_request = usbf_ep_alloc_request, + .free_request = usbf_ep_free_request, +}; + +static void usbf_ep0_req_complete(struct usb_ep *_ep, struct usb_request *_req) +{ +} + +static void usbf_ep0_fill_req(struct usbf_ep *ep0, struct usbf_req *req, + void *buf, unsigned int length, + void (*complete)(struct usb_ep *_ep, + struct usb_request *_req)) +{ + if (buf && length) + memcpy(ep0->udc->ep0_buf, buf, length); + + req->req.buf = ep0->udc->ep0_buf; + req->req.length = length; + req->req.dma = 0; + req->req.zero = true; + req->req.complete = complete ? complete : usbf_ep0_req_complete; + req->req.status = -EINPROGRESS; + req->req.context = NULL; + req->req.actual = 0; +} + +static struct usbf_ep *usbf_get_ep_by_addr(struct usbf_udc *udc, u8 address) +{ + struct usbf_ep *ep; + unsigned int i; + + if ((address & USB_ENDPOINT_NUMBER_MASK) == 0) + return &udc->ep[0]; + + for (i = 1; i < ARRAY_SIZE(udc->ep); i++) { + ep = &udc->ep[i]; + + if (!ep->ep.desc) + continue; + + if (ep->ep.desc->bEndpointAddress == address) + return ep; + } + + return NULL; +} + +static int usbf_req_delegate(struct usbf_udc *udc, + const struct usb_ctrlrequest *ctrlrequest) +{ + int ret; + + spin_unlock(&udc->lock); + ret = udc->driver->setup(&udc->gadget, ctrlrequest); + spin_lock(&udc->lock); + if (ret < 0) { + dev_dbg(udc->dev, "udc driver setup failed %d\n", ret); + return ret; + } + if (ret == USB_GADGET_DELAYED_STATUS) { + dev_dbg(udc->dev, "delayed status set\n"); + udc->ep[0].delayed_status = 1; + return 0; + } + return ret; +} + +static int usbf_req_get_status(struct usbf_udc *udc, + const struct usb_ctrlrequest *ctrlrequest) +{ + struct usbf_ep *ep; + u16 status_data; + u16 wLength; + u16 wValue; + u16 wIndex; + + wValue = le16_to_cpu(ctrlrequest->wValue); + wLength = le16_to_cpu(ctrlrequest->wLength); + wIndex = le16_to_cpu(ctrlrequest->wIndex); + + switch (ctrlrequest->bRequestType) { + case USB_DIR_IN | USB_RECIP_DEVICE | USB_TYPE_STANDARD: + if ((wValue != 0) || (wIndex != 0) || (wLength != 2)) + goto delegate; + + status_data = 0; + if (udc->gadget.is_selfpowered) + status_data |= BIT(USB_DEVICE_SELF_POWERED); + + if (udc->is_remote_wakeup) + status_data |= BIT(USB_DEVICE_REMOTE_WAKEUP); + + break; + + case USB_DIR_IN | USB_RECIP_ENDPOINT | USB_TYPE_STANDARD: + if ((wValue != 0) || (wLength != 2)) + goto delegate; + + ep = usbf_get_ep_by_addr(udc, wIndex); + if (!ep) + return -EINVAL; + + status_data = 0; + if (usbf_ep_is_stalled(ep)) + status_data |= cpu_to_le16(1); + break; + + case USB_DIR_IN | USB_RECIP_INTERFACE | USB_TYPE_STANDARD: + if ((wValue != 0) || (wLength != 2)) + goto delegate; + status_data = 0; + break; + + default: + goto delegate; + } + + usbf_ep0_fill_req(&udc->ep[0], &udc->setup_reply, &status_data, + sizeof(status_data), NULL); + usbf_ep0_queue(&udc->ep[0], &udc->setup_reply, GFP_ATOMIC); + + return 0; + +delegate: + return usbf_req_delegate(udc, ctrlrequest); +} + +static int usbf_req_clear_set_feature(struct usbf_udc *udc, + const struct usb_ctrlrequest *ctrlrequest, + bool is_set) +{ + struct usbf_ep *ep; + u16 wLength; + u16 wValue; + u16 wIndex; + + wValue = le16_to_cpu(ctrlrequest->wValue); + wLength = le16_to_cpu(ctrlrequest->wLength); + wIndex = le16_to_cpu(ctrlrequest->wIndex); + + switch (ctrlrequest->bRequestType) { + case USB_DIR_OUT | USB_RECIP_DEVICE: + if ((wIndex != 0) || (wLength != 0)) + goto delegate; + + if (wValue != cpu_to_le16(USB_DEVICE_REMOTE_WAKEUP)) + goto delegate; + + udc->is_remote_wakeup = is_set; + break; + + case USB_DIR_OUT | USB_RECIP_ENDPOINT: + if (wLength != 0) + goto delegate; + + ep = usbf_get_ep_by_addr(udc, wIndex); + if (!ep) + return -EINVAL; + + if ((ep->id == 0) && is_set) { + /* Endpoint 0 cannot be halted (stalled) + * Returning an error code leads to a STALL on this ep0 + * but keep the automate in a consistent state. + */ + return -EINVAL; + } + if (ep->is_wedged && !is_set) { + /* Ignore CLEAR_FEATURE(HALT ENDPOINT) when the + * endpoint is wedged + */ + break; + } + usbf_ep_stall(ep, is_set); + break; + + default: + goto delegate; + } + + return 0; + +delegate: + return usbf_req_delegate(udc, ctrlrequest); +} + +static void usbf_ep0_req_set_address_complete(struct usb_ep *_ep, + struct usb_request *_req) +{ + struct usbf_ep *ep = container_of(_ep, struct usbf_ep, ep); + + /* The status phase of the SET_ADDRESS request is completed ... */ + if (_req->status == 0) { + /* ... without any errors -> Signaled the state to the core. */ + usb_gadget_set_state(&ep->udc->gadget, USB_STATE_ADDRESS); + } + + /* In case of request failure, there is no need to revert the address + * value set to the hardware as the hardware will take care of the + * value only if the status stage is completed normally. + */ +} + +static int usbf_req_set_address(struct usbf_udc *udc, + const struct usb_ctrlrequest *ctrlrequest) +{ + u16 wLength; + u16 wValue; + u16 wIndex; + u32 addr; + + wValue = le16_to_cpu(ctrlrequest->wValue); + wLength = le16_to_cpu(ctrlrequest->wLength); + wIndex = le16_to_cpu(ctrlrequest->wIndex); + + if (ctrlrequest->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + goto delegate; + + if ((wIndex != 0) || (wLength != 0) || (wValue > 127)) + return -EINVAL; + + addr = wValue; + /* The hardware will take care of this USB address after the status + * stage of the SET_ADDRESS request is completed normally. + * It is safe to write it now + */ + usbf_reg_writel(udc, USBF_REG_USB_ADDRESS, USBF_USB_SET_USB_ADDR(addr)); + + /* Queued the status request */ + usbf_ep0_fill_req(&udc->ep[0], &udc->setup_reply, NULL, 0, + usbf_ep0_req_set_address_complete); + usbf_ep0_queue(&udc->ep[0], &udc->setup_reply, GFP_ATOMIC); + + return 0; + +delegate: + return usbf_req_delegate(udc, ctrlrequest); +} + +static int usbf_req_set_configuration(struct usbf_udc *udc, + const struct usb_ctrlrequest *ctrlrequest) +{ + u16 wLength; + u16 wValue; + u16 wIndex; + int ret; + + ret = usbf_req_delegate(udc, ctrlrequest); + if (ret) + return ret; + + wValue = le16_to_cpu(ctrlrequest->wValue); + wLength = le16_to_cpu(ctrlrequest->wLength); + wIndex = le16_to_cpu(ctrlrequest->wIndex); + + if ((ctrlrequest->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) || + (wIndex != 0) || (wLength != 0)) { + /* No error detected by driver->setup() but it is not an USB2.0 + * Ch9 SET_CONFIGURATION. + * Nothing more to do + */ + return 0; + } + + if (wValue & 0x00FF) { + usbf_reg_bitset(udc, USBF_REG_USB_CONTROL, USBF_USB_CONF); + } else { + usbf_reg_bitclr(udc, USBF_REG_USB_CONTROL, USBF_USB_CONF); + /* Go back to Address State */ + spin_unlock(&udc->lock); + usb_gadget_set_state(&udc->gadget, USB_STATE_ADDRESS); + spin_lock(&udc->lock); + } + + return 0; +} + +static int usbf_handle_ep0_setup(struct usbf_ep *ep0) +{ + union { + struct usb_ctrlrequest ctrlreq; + u32 raw[2]; + } crq; + struct usbf_udc *udc = ep0->udc; + int ret; + + /* Read setup data (ie the USB control request) */ + crq.raw[0] = usbf_reg_readl(udc, USBF_REG_SETUP_DATA0); + crq.raw[1] = usbf_reg_readl(udc, USBF_REG_SETUP_DATA1); + + dev_dbg(ep0->udc->dev, + "ep0 req%02x.%02x, wValue 0x%04x, wIndex 0x%04x, wLength 0x%04x\n", + crq.ctrlreq.bRequestType, crq.ctrlreq.bRequest, + crq.ctrlreq.wValue, crq.ctrlreq.wIndex, crq.ctrlreq.wLength); + + /* Set current EP0 state according to the received request */ + if (crq.ctrlreq.wLength) { + if (crq.ctrlreq.bRequestType & USB_DIR_IN) { + udc->ep0state = EP0_IN_DATA_PHASE; + usbf_ep_reg_clrset(ep0, USBF_REG_EP0_CONTROL, + USBF_EP0_INAK, + USBF_EP0_INAK_EN); + ep0->is_in = 1; + } else { + udc->ep0state = EP0_OUT_DATA_PHASE; + usbf_ep_reg_bitclr(ep0, USBF_REG_EP0_CONTROL, + USBF_EP0_ONAK); + ep0->is_in = 0; + } + } else { + udc->ep0state = EP0_IN_STATUS_START_PHASE; + ep0->is_in = 1; + } + + /* We starts a new control transfer -> Clear the delayed status flag */ + ep0->delayed_status = 0; + + if ((crq.ctrlreq.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) { + /* This is not a USB standard request -> delegate */ + goto delegate; + } + + switch (crq.ctrlreq.bRequest) { + case USB_REQ_GET_STATUS: + ret = usbf_req_get_status(udc, &crq.ctrlreq); + break; + + case USB_REQ_CLEAR_FEATURE: + ret = usbf_req_clear_set_feature(udc, &crq.ctrlreq, false); + break; + + case USB_REQ_SET_FEATURE: + ret = usbf_req_clear_set_feature(udc, &crq.ctrlreq, true); + break; + + case USB_REQ_SET_ADDRESS: + ret = usbf_req_set_address(udc, &crq.ctrlreq); + break; + + case USB_REQ_SET_CONFIGURATION: + ret = usbf_req_set_configuration(udc, &crq.ctrlreq); + break; + + default: + goto delegate; + } + + return ret; + +delegate: + return usbf_req_delegate(udc, &crq.ctrlreq); +} + +static int usbf_handle_ep0_data_status(struct usbf_ep *ep0, + const char *ep0state_name, + enum usbf_ep0state next_ep0state) +{ + struct usbf_udc *udc = ep0->udc; + int ret; + + ret = usbf_ep_process_queue(ep0); + switch (ret) { + case -ENOENT: + dev_err(udc->dev, + "no request available for ep0 %s phase\n", + ep0state_name); + break; + case -EINPROGRESS: + /* More data needs to be processed */ + ret = 0; + break; + case 0: + /* All requests in the queue are processed */ + udc->ep0state = next_ep0state; + break; + default: + dev_err(udc->dev, + "process queue failed for ep0 %s phase (%d)\n", + ep0state_name, ret); + break; + } + return ret; +} + +static int usbf_handle_ep0_out_status_start(struct usbf_ep *ep0) +{ + struct usbf_udc *udc = ep0->udc; + struct usbf_req *req; + + usbf_ep_reg_clrset(ep0, USBF_REG_EP0_CONTROL, + USBF_EP0_ONAK, + USBF_EP0_PIDCLR); + ep0->is_in = 0; + + req = list_first_entry_or_null(&ep0->queue, struct usbf_req, queue); + if (!req) { + usbf_ep0_fill_req(ep0, &udc->setup_reply, NULL, 0, NULL); + usbf_ep0_queue(ep0, &udc->setup_reply, GFP_ATOMIC); + } else { + if (req->req.length) { + dev_err(udc->dev, + "queued request length %u for ep0 out status phase\n", + req->req.length); + } + } + udc->ep0state = EP0_OUT_STATUS_PHASE; + return 0; +} + +static int usbf_handle_ep0_in_status_start(struct usbf_ep *ep0) +{ + struct usbf_udc *udc = ep0->udc; + struct usbf_req *req; + int ret; + + usbf_ep_reg_clrset(ep0, USBF_REG_EP0_CONTROL, + USBF_EP0_INAK, + USBF_EP0_INAK_EN | USBF_EP0_PIDCLR); + ep0->is_in = 1; + + /* Queue request for status if needed */ + req = list_first_entry_or_null(&ep0->queue, struct usbf_req, queue); + if (!req) { + if (ep0->delayed_status) { + dev_dbg(ep0->udc->dev, + "EP0_IN_STATUS_START_PHASE ep0->delayed_status set\n"); + udc->ep0state = EP0_IN_STATUS_PHASE; + return 0; + } + + usbf_ep0_fill_req(ep0, &udc->setup_reply, NULL, + 0, NULL); + usbf_ep0_queue(ep0, &udc->setup_reply, + GFP_ATOMIC); + + req = list_first_entry_or_null(&ep0->queue, struct usbf_req, queue); + } else { + if (req->req.length) { + dev_err(udc->dev, + "queued request length %u for ep0 in status phase\n", + req->req.length); + } + } + + ret = usbf_ep0_pio_in(ep0, req); + if (ret != -EINPROGRESS) { + usbf_ep_req_done(ep0, req, ret); + udc->ep0state = EP0_IN_STATUS_END_PHASE; + return 0; + } + + udc->ep0state = EP0_IN_STATUS_PHASE; + return 0; +} + +static void usbf_ep0_interrupt(struct usbf_ep *ep0) +{ + struct usbf_udc *udc = ep0->udc; + u32 sts, prev_sts; + int prev_ep0state; + int ret; + + ep0->status = usbf_ep_reg_readl(ep0, USBF_REG_EP0_STATUS); + usbf_ep_reg_writel(ep0, USBF_REG_EP0_STATUS, ~ep0->status); + + dev_dbg(ep0->udc->dev, "ep0 status=0x%08x, enable=%08x\n, ctrl=0x%08x\n", + ep0->status, + usbf_ep_reg_readl(ep0, USBF_REG_EP0_INT_ENA), + usbf_ep_reg_readl(ep0, USBF_REG_EP0_CONTROL)); + + sts = ep0->status & (USBF_EP0_SETUP_INT | USBF_EP0_IN_INT | USBF_EP0_OUT_INT | + USBF_EP0_OUT_NULL_INT | USBF_EP0_STG_START_INT | + USBF_EP0_STG_END_INT); + + ret = 0; + do { + dev_dbg(ep0->udc->dev, "udc->ep0state=%d\n", udc->ep0state); + + prev_sts = sts; + prev_ep0state = udc->ep0state; + switch (udc->ep0state) { + case EP0_IDLE: + if (!(sts & USBF_EP0_SETUP_INT)) + break; + + sts &= ~USBF_EP0_SETUP_INT; + dev_dbg(ep0->udc->dev, "ep0 handle setup\n"); + ret = usbf_handle_ep0_setup(ep0); + break; + + case EP0_IN_DATA_PHASE: + if (!(sts & USBF_EP0_IN_INT)) + break; + + sts &= ~USBF_EP0_IN_INT; + dev_dbg(ep0->udc->dev, "ep0 handle in data phase\n"); + ret = usbf_handle_ep0_data_status(ep0, + "in data", EP0_OUT_STATUS_START_PHASE); + break; + + case EP0_OUT_STATUS_START_PHASE: + if (!(sts & USBF_EP0_STG_START_INT)) + break; + + sts &= ~USBF_EP0_STG_START_INT; + dev_dbg(ep0->udc->dev, "ep0 handle out status start phase\n"); + ret = usbf_handle_ep0_out_status_start(ep0); + break; + + case EP0_OUT_STATUS_PHASE: + if (!(sts & (USBF_EP0_OUT_INT | USBF_EP0_OUT_NULL_INT))) + break; + + sts &= ~(USBF_EP0_OUT_INT | USBF_EP0_OUT_NULL_INT); + dev_dbg(ep0->udc->dev, "ep0 handle out status phase\n"); + ret = usbf_handle_ep0_data_status(ep0, + "out status", + EP0_OUT_STATUS_END_PHASE); + break; + + case EP0_OUT_STATUS_END_PHASE: + if (!(sts & (USBF_EP0_STG_END_INT | USBF_EP0_SETUP_INT))) + break; + + sts &= ~USBF_EP0_STG_END_INT; + dev_dbg(ep0->udc->dev, "ep0 handle out status end phase\n"); + udc->ep0state = EP0_IDLE; + break; + + case EP0_OUT_DATA_PHASE: + if (!(sts & (USBF_EP0_OUT_INT | USBF_EP0_OUT_NULL_INT))) + break; + + sts &= ~(USBF_EP0_OUT_INT | USBF_EP0_OUT_NULL_INT); + dev_dbg(ep0->udc->dev, "ep0 handle out data phase\n"); + ret = usbf_handle_ep0_data_status(ep0, + "out data", EP0_IN_STATUS_START_PHASE); + break; + + case EP0_IN_STATUS_START_PHASE: + if (!(sts & USBF_EP0_STG_START_INT)) + break; + + sts &= ~USBF_EP0_STG_START_INT; + dev_dbg(ep0->udc->dev, "ep0 handle in status start phase\n"); + ret = usbf_handle_ep0_in_status_start(ep0); + break; + + case EP0_IN_STATUS_PHASE: + if (!(sts & USBF_EP0_IN_INT)) + break; + + sts &= ~USBF_EP0_IN_INT; + dev_dbg(ep0->udc->dev, "ep0 handle in status phase\n"); + ret = usbf_handle_ep0_data_status(ep0, + "in status", EP0_IN_STATUS_END_PHASE); + break; + + case EP0_IN_STATUS_END_PHASE: + if (!(sts & (USBF_EP0_STG_END_INT | USBF_EP0_SETUP_INT))) + break; + + sts &= ~USBF_EP0_STG_END_INT; + dev_dbg(ep0->udc->dev, "ep0 handle in status end\n"); + udc->ep0state = EP0_IDLE; + break; + + default: + udc->ep0state = EP0_IDLE; + break; + } + + if (ret) { + dev_dbg(ep0->udc->dev, "ep0 failed (%d)\n", ret); + /* Failure -> stall. + * This stall state will be automatically cleared when + * the IP receives the next SETUP packet + */ + usbf_ep_stall(ep0, true); + + /* Remove anything that was pending */ + usbf_ep_nuke(ep0, -EPROTO); + + udc->ep0state = EP0_IDLE; + break; + } + + } while ((prev_ep0state != udc->ep0state) || (prev_sts != sts)); + + dev_dbg(ep0->udc->dev, "ep0 done udc->ep0state=%d, status=0x%08x. next=0x%08x\n", + udc->ep0state, sts, + usbf_ep_reg_readl(ep0, USBF_REG_EP0_STATUS)); +} + +static void usbf_epn_process_queue(struct usbf_ep *epn) +{ + int ret; + + ret = usbf_ep_process_queue(epn); + switch (ret) { + case -ENOENT: + dev_warn(epn->udc->dev, "ep%u %s, no request available\n", + epn->id, epn->is_in ? "in" : "out"); + break; + case -EINPROGRESS: + /* More data needs to be processed */ + ret = 0; + break; + case 0: + /* All requests in the queue are processed */ + break; + default: + dev_err(epn->udc->dev, "ep%u %s, process queue failed (%d)\n", + epn->id, epn->is_in ? "in" : "out", ret); + break; + } + + if (ret) { + dev_dbg(epn->udc->dev, "ep%u %s failed (%d)\n", epn->id, + epn->is_in ? "in" : "out", ret); + usbf_ep_stall(epn, true); + usbf_ep_nuke(epn, ret); + } +} + +static void usbf_epn_interrupt(struct usbf_ep *epn) +{ + u32 sts; + u32 ena; + + epn->status = usbf_ep_reg_readl(epn, USBF_REG_EPN_STATUS); + ena = usbf_ep_reg_readl(epn, USBF_REG_EPN_INT_ENA); + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, ~(epn->status & ena)); + + dev_dbg(epn->udc->dev, "ep%u %s status=0x%08x, enable=%08x\n, ctrl=0x%08x\n", + epn->id, epn->is_in ? "in" : "out", epn->status, ena, + usbf_ep_reg_readl(epn, USBF_REG_EPN_CONTROL)); + + if (epn->disabled) { + dev_warn(epn->udc->dev, "ep%u %s, interrupt while disabled\n", + epn->id, epn->is_in ? "in" : "out"); + return; + } + + sts = epn->status & ena; + + if (sts & (USBF_EPN_IN_END_INT | USBF_EPN_IN_INT)) { + sts &= ~(USBF_EPN_IN_END_INT | USBF_EPN_IN_INT); + dev_dbg(epn->udc->dev, "ep%u %s process queue (in interrupts)\n", + epn->id, epn->is_in ? "in" : "out"); + usbf_epn_process_queue(epn); + } + + if (sts & (USBF_EPN_OUT_END_INT | USBF_EPN_OUT_INT | USBF_EPN_OUT_NULL_INT)) { + sts &= ~(USBF_EPN_OUT_END_INT | USBF_EPN_OUT_INT | USBF_EPN_OUT_NULL_INT); + dev_dbg(epn->udc->dev, "ep%u %s process queue (out interrupts)\n", + epn->id, epn->is_in ? "in" : "out"); + usbf_epn_process_queue(epn); + } + + dev_dbg(epn->udc->dev, "ep%u %s done status=0x%08x. next=0x%08x\n", + epn->id, epn->is_in ? "in" : "out", + sts, usbf_ep_reg_readl(epn, USBF_REG_EPN_STATUS)); +} + +static void usbf_ep_reset(struct usbf_ep *ep) +{ + ep->status = 0; + /* Remove anything that was pending */ + usbf_ep_nuke(ep, -ESHUTDOWN); +} + +static void usbf_reset(struct usbf_udc *udc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(udc->ep); i++) { + if (udc->ep[i].disabled) + continue; + + usbf_ep_reset(&udc->ep[i]); + } + + if (usbf_reg_readl(udc, USBF_REG_USB_STATUS) & USBF_USB_SPEED_MODE) + udc->gadget.speed = USB_SPEED_HIGH; + else + udc->gadget.speed = USB_SPEED_FULL; + + /* Remote wakeup feature must be disabled on USB bus reset */ + udc->is_remote_wakeup = false; + + /* Enable endpoint zero */ + usbf_ep0_enable(&udc->ep[0]); + + if (udc->driver) { + /* Signal the reset */ + spin_unlock(&udc->lock); + usb_gadget_udc_reset(&udc->gadget, udc->driver); + spin_lock(&udc->lock); + } +} + +static void usbf_driver_suspend(struct usbf_udc *udc) +{ + if (udc->is_usb_suspended) { + dev_dbg(udc->dev, "already suspended\n"); + return; + } + + dev_dbg(udc->dev, "do usb suspend\n"); + udc->is_usb_suspended = true; + + if (udc->driver && udc->driver->suspend) { + spin_unlock(&udc->lock); + udc->driver->suspend(&udc->gadget); + spin_lock(&udc->lock); + + /* The datasheet tells to set the USB_CONTROL register SUSPEND + * bit when the USB bus suspend is detected. + * This bit stops the clocks (clocks for EPC, SIE, USBPHY) but + * these clocks seems not used only by the USB device. Some + * UARTs can be lost ... + * So, do not set the USB_CONTROL register SUSPEND bit. + */ + } +} + +static void usbf_driver_resume(struct usbf_udc *udc) +{ + if (!udc->is_usb_suspended) + return; + + dev_dbg(udc->dev, "do usb resume\n"); + udc->is_usb_suspended = false; + + if (udc->driver && udc->driver->resume) { + spin_unlock(&udc->lock); + udc->driver->resume(&udc->gadget); + spin_lock(&udc->lock); + } +} + +static irqreturn_t usbf_epc_irq(int irq, void *_udc) +{ + struct usbf_udc *udc = (struct usbf_udc *)_udc; + unsigned long flags; + struct usbf_ep *ep; + u32 int_sts; + u32 int_en; + int i; + + spin_lock_irqsave(&udc->lock, flags); + + int_en = usbf_reg_readl(udc, USBF_REG_USB_INT_ENA); + int_sts = usbf_reg_readl(udc, USBF_REG_USB_INT_STA) & int_en; + usbf_reg_writel(udc, USBF_REG_USB_INT_STA, ~int_sts); + + dev_dbg(udc->dev, "int_sts=0x%08x\n", int_sts); + + if (int_sts & USBF_USB_RSUM_INT) { + dev_dbg(udc->dev, "handle resume\n"); + usbf_driver_resume(udc); + } + + if (int_sts & USBF_USB_USB_RST_INT) { + dev_dbg(udc->dev, "handle bus reset\n"); + usbf_driver_resume(udc); + usbf_reset(udc); + } + + if (int_sts & USBF_USB_SPEED_MODE_INT) { + if (usbf_reg_readl(udc, USBF_REG_USB_STATUS) & USBF_USB_SPEED_MODE) + udc->gadget.speed = USB_SPEED_HIGH; + else + udc->gadget.speed = USB_SPEED_FULL; + dev_dbg(udc->dev, "handle speed change (%s)\n", + udc->gadget.speed == USB_SPEED_HIGH ? "High" : "Full"); + } + + if (int_sts & USBF_USB_EPN_INT(0)) { + usbf_driver_resume(udc); + usbf_ep0_interrupt(&udc->ep[0]); + } + + for (i = 1; i < ARRAY_SIZE(udc->ep); i++) { + ep = &udc->ep[i]; + + if (int_sts & USBF_USB_EPN_INT(i)) { + usbf_driver_resume(udc); + usbf_epn_interrupt(ep); + } + } + + if (int_sts & USBF_USB_SPND_INT) { + dev_dbg(udc->dev, "handle suspend\n"); + usbf_driver_suspend(udc); + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return IRQ_HANDLED; +} + +static irqreturn_t usbf_ahb_epc_irq(int irq, void *_udc) +{ + struct usbf_udc *udc = (struct usbf_udc *)_udc; + unsigned long flags; + struct usbf_ep *epn; + u32 sysbint; + void (*ep_action)(struct usbf_ep *epn); + int i; + + spin_lock_irqsave(&udc->lock, flags); + + /* Read and ack interrupts */ + sysbint = usbf_reg_readl(udc, USBF_REG_AHBBINT); + usbf_reg_writel(udc, USBF_REG_AHBBINT, sysbint); + + if ((sysbint & USBF_SYS_VBUS_INT) == USBF_SYS_VBUS_INT) { + if (usbf_reg_readl(udc, USBF_REG_EPCTR) & USBF_SYS_VBUS_LEVEL) { + dev_dbg(udc->dev, "handle vbus (1)\n"); + spin_unlock(&udc->lock); + usb_udc_vbus_handler(&udc->gadget, true); + usb_gadget_set_state(&udc->gadget, USB_STATE_POWERED); + spin_lock(&udc->lock); + } else { + dev_dbg(udc->dev, "handle vbus (0)\n"); + udc->is_usb_suspended = false; + spin_unlock(&udc->lock); + usb_udc_vbus_handler(&udc->gadget, false); + usb_gadget_set_state(&udc->gadget, + USB_STATE_NOTATTACHED); + spin_lock(&udc->lock); + } + } + + for (i = 1; i < ARRAY_SIZE(udc->ep); i++) { + if (sysbint & USBF_SYS_DMA_ENDINT_EPN(i)) { + epn = &udc->ep[i]; + dev_dbg(epn->udc->dev, + "ep%u handle DMA complete. action=%ps\n", + epn->id, epn->bridge_on_dma_end); + ep_action = epn->bridge_on_dma_end; + if (ep_action) { + epn->bridge_on_dma_end = NULL; + ep_action(epn); + } + } + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return IRQ_HANDLED; +} + +static int usbf_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct usbf_udc *udc = container_of(gadget, struct usbf_udc, gadget); + unsigned long flags; + + dev_info(udc->dev, "start (driver '%s')\n", driver->driver.name); + + spin_lock_irqsave(&udc->lock, flags); + + /* hook up the driver */ + udc->driver = driver; + + /* Enable VBUS interrupt */ + usbf_reg_writel(udc, USBF_REG_AHBBINTEN, USBF_SYS_VBUS_INTEN); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int usbf_udc_stop(struct usb_gadget *gadget) +{ + struct usbf_udc *udc = container_of(gadget, struct usbf_udc, gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + /* Disable VBUS interrupt */ + usbf_reg_writel(udc, USBF_REG_AHBBINTEN, 0); + + udc->driver = NULL; + + spin_unlock_irqrestore(&udc->lock, flags); + + dev_info(udc->dev, "stopped\n"); + + return 0; +} + +static int usbf_get_frame(struct usb_gadget *gadget) +{ + struct usbf_udc *udc = container_of(gadget, struct usbf_udc, gadget); + + return USBF_USB_GET_FRAME(usbf_reg_readl(udc, USBF_REG_USB_ADDRESS)); +} + +static void usbf_attach(struct usbf_udc *udc) +{ + /* Enable USB signal to Function PHY + * D+ signal Pull-up + * Disable endpoint 0, it will be automatically enable when a USB reset + * is received. + * Disable the other endpoints + */ + usbf_reg_clrset(udc, USBF_REG_USB_CONTROL, + USBF_USB_CONNECTB | USBF_USB_DEFAULT | USBF_USB_CONF, + USBF_USB_PUE2); + + /* Enable reset and mode change interrupts */ + usbf_reg_bitset(udc, USBF_REG_USB_INT_ENA, + USBF_USB_USB_RST_EN | USBF_USB_SPEED_MODE_EN | USBF_USB_RSUM_EN | USBF_USB_SPND_EN); +} + +static void usbf_detach(struct usbf_udc *udc) +{ + int i; + + /* Disable interrupts */ + usbf_reg_writel(udc, USBF_REG_USB_INT_ENA, 0); + + for (i = 0; i < ARRAY_SIZE(udc->ep); i++) { + if (udc->ep[i].disabled) + continue; + + usbf_ep_reset(&udc->ep[i]); + } + + /* Disable USB signal to Function PHY + * Do not Pull-up D+ signal + * Disable endpoint 0 + * Disable the other endpoints + */ + usbf_reg_clrset(udc, USBF_REG_USB_CONTROL, + USBF_USB_PUE2 | USBF_USB_DEFAULT | USBF_USB_CONF, + USBF_USB_CONNECTB); +} + +static int usbf_pullup(struct usb_gadget *gadget, int is_on) +{ + struct usbf_udc *udc = container_of(gadget, struct usbf_udc, gadget); + unsigned long flags; + + dev_dbg(udc->dev, "pullup %d\n", is_on); + + spin_lock_irqsave(&udc->lock, flags); + if (is_on) + usbf_attach(udc); + else + usbf_detach(udc); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int usbf_udc_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct usbf_udc *udc = container_of(gadget, struct usbf_udc, gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + gadget->is_selfpowered = (is_selfpowered != 0); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int usbf_udc_wakeup(struct usb_gadget *gadget) +{ + struct usbf_udc *udc = container_of(gadget, struct usbf_udc, gadget); + unsigned long flags; + int ret; + + spin_lock_irqsave(&udc->lock, flags); + + if (!udc->is_remote_wakeup) { + dev_dbg(udc->dev, "remote wakeup not allowed\n"); + ret = -EINVAL; + goto end; + } + + dev_dbg(udc->dev, "do wakeup\n"); + + /* Send the resume signal */ + usbf_reg_bitset(udc, USBF_REG_USB_CONTROL, USBF_USB_RSUM_IN); + usbf_reg_bitclr(udc, USBF_REG_USB_CONTROL, USBF_USB_RSUM_IN); + + ret = 0; +end: + spin_unlock_irqrestore(&udc->lock, flags); + return ret; +} + +static struct usb_gadget_ops usbf_gadget_ops = { + .get_frame = usbf_get_frame, + .pullup = usbf_pullup, + .udc_start = usbf_udc_start, + .udc_stop = usbf_udc_stop, + .set_selfpowered = usbf_udc_set_selfpowered, + .wakeup = usbf_udc_wakeup, +}; + +static int usbf_epn_check(struct usbf_ep *epn) +{ + const char *type_txt; + const char *buf_txt; + int ret = 0; + u32 ctrl; + + ctrl = usbf_ep_reg_readl(epn, USBF_REG_EPN_CONTROL); + + switch (ctrl & USBF_EPN_MODE_MASK) { + case USBF_EPN_MODE_BULK: + type_txt = "bulk"; + if (epn->ep.caps.type_control || epn->ep.caps.type_iso || + !epn->ep.caps.type_bulk || epn->ep.caps.type_int) { + dev_err(epn->udc->dev, + "ep%u caps mismatch, bulk expected\n", epn->id); + ret = -EINVAL; + } + break; + case USBF_EPN_MODE_INTR: + type_txt = "intr"; + if (epn->ep.caps.type_control || epn->ep.caps.type_iso || + epn->ep.caps.type_bulk || !epn->ep.caps.type_int) { + dev_err(epn->udc->dev, + "ep%u caps mismatch, int expected\n", epn->id); + ret = -EINVAL; + } + break; + case USBF_EPN_MODE_ISO: + type_txt = "iso"; + if (epn->ep.caps.type_control || !epn->ep.caps.type_iso || + epn->ep.caps.type_bulk || epn->ep.caps.type_int) { + dev_err(epn->udc->dev, + "ep%u caps mismatch, iso expected\n", epn->id); + ret = -EINVAL; + } + break; + default: + type_txt = "unknown"; + dev_err(epn->udc->dev, "ep%u unknown type\n", epn->id); + ret = -EINVAL; + break; + } + + if (ctrl & USBF_EPN_BUF_TYPE_DOUBLE) { + buf_txt = "double"; + if (!usbf_ep_info[epn->id].is_double) { + dev_err(epn->udc->dev, + "ep%u buffer mismatch, double expected\n", + epn->id); + ret = -EINVAL; + } + } else { + buf_txt = "single"; + if (usbf_ep_info[epn->id].is_double) { + dev_err(epn->udc->dev, + "ep%u buffer mismatch, single expected\n", + epn->id); + ret = -EINVAL; + } + } + + dev_dbg(epn->udc->dev, "ep%u (%s) %s, %s buffer %u, checked %s\n", + epn->id, epn->ep.name, type_txt, buf_txt, + epn->ep.maxpacket_limit, ret ? "failed" : "ok"); + + return ret; +} + +static int usbf_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usbf_udc *udc; + struct usbf_ep *ep; + unsigned int i; + int irq; + int ret; + + udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + platform_set_drvdata(pdev, udc); + + udc->dev = dev; + spin_lock_init(&udc->lock); + + udc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(udc->regs)) + return PTR_ERR(udc->regs); + + ret = devm_pm_runtime_enable(&pdev->dev); + if (ret) + return ret; + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret < 0) + return ret; + + dev_info(dev, "USBF version: %08x\n", + usbf_reg_readl(udc, USBF_REG_USBSSVER)); + + /* Resetting the PLL is handled via the clock driver as it has common + * registers with USB Host + */ + usbf_reg_bitclr(udc, USBF_REG_EPCTR, USBF_SYS_EPC_RST); + + /* modify in register gadget process */ + udc->gadget.speed = USB_SPEED_FULL; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->gadget.ops = &usbf_gadget_ops; + + udc->gadget.name = dev->driver->name; + udc->gadget.dev.parent = dev; + udc->gadget.ep0 = &udc->ep[0].ep; + + /* The hardware DMA controller needs dma addresses aligned on 32bit. + * A fallback to pio is done if DMA addresses are not aligned. + */ + udc->gadget.quirk_avoids_skb_reserve = 1; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + /* we have a canned request structure to allow sending packets as reply + * to get_status requests + */ + INIT_LIST_HEAD(&udc->setup_reply.queue); + + for (i = 0; i < ARRAY_SIZE(udc->ep); i++) { + ep = &udc->ep[i]; + + if (!(usbf_reg_readl(udc, USBF_REG_USBSSCONF) & + USBF_SYS_EP_AVAILABLE(i))) { + continue; + } + + INIT_LIST_HEAD(&ep->queue); + + ep->id = i; + ep->disabled = 1; + ep->udc = udc; + ep->ep.ops = &usbf_ep_ops; + ep->ep.name = usbf_ep_info[i].name; + ep->ep.caps = usbf_ep_info[i].caps; + usb_ep_set_maxpacket_limit(&ep->ep, + usbf_ep_info[i].maxpacket_limit); + + if (ep->id == 0) { + ep->regs = ep->udc->regs + USBF_BASE_EP0; + } else { + ep->regs = ep->udc->regs + USBF_BASE_EPN(ep->id - 1); + ret = usbf_epn_check(ep); + if (ret) + return ret; + if (usbf_reg_readl(udc, USBF_REG_USBSSCONF) & + USBF_SYS_DMA_AVAILABLE(i)) { + ep->dma_regs = ep->udc->regs + + USBF_BASE_DMA_EPN(ep->id - 1); + } + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + } + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + ret = devm_request_irq(dev, irq, usbf_epc_irq, 0, "usbf-epc", udc); + if (ret) { + dev_err(dev, "cannot request irq %d err %d\n", irq, ret); + return ret; + } + + irq = platform_get_irq(pdev, 1); + if (irq < 0) + return irq; + ret = devm_request_irq(dev, irq, usbf_ahb_epc_irq, 0, "usbf-ahb-epc", udc); + if (ret) { + dev_err(dev, "cannot request irq %d err %d\n", irq, ret); + return ret; + } + + usbf_reg_bitset(udc, USBF_REG_AHBMCTR, USBF_SYS_WBURST_TYPE); + + usbf_reg_bitset(udc, USBF_REG_USB_CONTROL, + USBF_USB_INT_SEL | USBF_USB_SOF_RCV | USBF_USB_SOF_CLK_MODE); + + ret = usb_add_gadget_udc(dev, &udc->gadget); + if (ret) + return ret; + + return 0; +} + +static void usbf_remove(struct platform_device *pdev) +{ + struct usbf_udc *udc = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&udc->gadget); + + pm_runtime_put(&pdev->dev); +} + +static const struct of_device_id usbf_match[] = { + { .compatible = "renesas,rzn1-usbf" }, + {} /* sentinel */ +}; +MODULE_DEVICE_TABLE(of, usbf_match); + +static struct platform_driver udc_driver = { + .driver = { + .name = "usbf_renesas", + .of_match_table = usbf_match, + }, + .probe = usbf_probe, + .remove = usbf_remove, +}; + +module_platform_driver(udc_driver); + +MODULE_AUTHOR("Herve Codina "); +MODULE_DESCRIPTION("Renesas R-Car Gen3 & RZ/N1 USB Function driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/rzv2m_usb3drd.c b/drivers/usb/gadget/udc/rzv2m_usb3drd.c new file mode 100644 index 000000000..4692eae89 --- /dev/null +++ b/drivers/usb/gadget/udc/rzv2m_usb3drd.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/V2M USB3DRD driver + * + * Copyright (C) 2022 Renesas Electronics Corporation + */ + +#include +#include +#include +#include +#include +#include + +#define USB_PERI_DRD_CON 0x000 + +#define USB_PERI_DRD_CON_PERI_RST BIT(31) +#define USB_PERI_DRD_CON_HOST_RST BIT(30) +#define USB_PERI_DRD_CON_PERI_CON BIT(24) + +static void rzv2m_usb3drd_set_bit(struct rzv2m_usb3drd *usb3, u32 bits, + u32 offs) +{ + u32 val = readl(usb3->reg + offs); + + val |= bits; + writel(val, usb3->reg + offs); +} + +static void rzv2m_usb3drd_clear_bit(struct rzv2m_usb3drd *usb3, u32 bits, + u32 offs) +{ + u32 val = readl(usb3->reg + offs); + + val &= ~bits; + writel(val, usb3->reg + offs); +} + +void rzv2m_usb3drd_reset(struct device *dev, bool host) +{ + struct rzv2m_usb3drd *usb3 = dev_get_drvdata(dev); + + if (host) { + rzv2m_usb3drd_clear_bit(usb3, USB_PERI_DRD_CON_PERI_CON, + USB_PERI_DRD_CON); + rzv2m_usb3drd_clear_bit(usb3, USB_PERI_DRD_CON_HOST_RST, + USB_PERI_DRD_CON); + rzv2m_usb3drd_set_bit(usb3, USB_PERI_DRD_CON_PERI_RST, + USB_PERI_DRD_CON); + } else { + rzv2m_usb3drd_set_bit(usb3, USB_PERI_DRD_CON_PERI_CON, + USB_PERI_DRD_CON); + rzv2m_usb3drd_set_bit(usb3, USB_PERI_DRD_CON_HOST_RST, + USB_PERI_DRD_CON); + rzv2m_usb3drd_clear_bit(usb3, USB_PERI_DRD_CON_PERI_RST, + USB_PERI_DRD_CON); + } +} +EXPORT_SYMBOL_GPL(rzv2m_usb3drd_reset); + +static void rzv2m_usb3drd_remove(struct platform_device *pdev) +{ + struct rzv2m_usb3drd *usb3 = platform_get_drvdata(pdev); + + of_platform_depopulate(usb3->dev); + pm_runtime_put(usb3->dev); + pm_runtime_disable(&pdev->dev); + reset_control_assert(usb3->drd_rstc); +} + +static int rzv2m_usb3drd_probe(struct platform_device *pdev) +{ + struct rzv2m_usb3drd *usb3; + int ret; + + usb3 = devm_kzalloc(&pdev->dev, sizeof(*usb3), GFP_KERNEL); + if (!usb3) + return -ENOMEM; + + usb3->dev = &pdev->dev; + + usb3->drd_irq = platform_get_irq_byname(pdev, "drd"); + if (usb3->drd_irq < 0) + return usb3->drd_irq; + + usb3->reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(usb3->reg)) + return PTR_ERR(usb3->reg); + + platform_set_drvdata(pdev, usb3); + + usb3->drd_rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(usb3->drd_rstc)) + return dev_err_probe(&pdev->dev, PTR_ERR(usb3->drd_rstc), + "failed to get drd reset"); + + reset_control_deassert(usb3->drd_rstc); + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_resume_and_get(usb3->dev); + if (ret) + goto err_rst; + + ret = of_platform_populate(usb3->dev->of_node, NULL, NULL, usb3->dev); + if (ret) + goto err_pm; + + return 0; + +err_pm: + pm_runtime_put(usb3->dev); + +err_rst: + pm_runtime_disable(&pdev->dev); + reset_control_assert(usb3->drd_rstc); + return ret; +} + +static const struct of_device_id rzv2m_usb3drd_of_match[] = { + { .compatible = "renesas,rzv2m-usb3drd", }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rzv2m_usb3drd_of_match); + +static struct platform_driver rzv2m_usb3drd_driver = { + .driver = { + .name = "rzv2m-usb3drd", + .of_match_table = rzv2m_usb3drd_of_match, + }, + .probe = rzv2m_usb3drd_probe, + .remove = rzv2m_usb3drd_remove, +}; +module_platform_driver(rzv2m_usb3drd_driver); + +MODULE_AUTHOR("Biju Das "); +MODULE_DESCRIPTION("Renesas RZ/V2M USB3DRD driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rzv2m_usb3drd"); diff --git a/drivers/usb/gadget/udc/snps_udc_core.c b/drivers/usb/gadget/udc/snps_udc_core.c new file mode 100644 index 000000000..d506f9d92 --- /dev/null +++ b/drivers/usb/gadget/udc/snps_udc_core.c @@ -0,0 +1,3191 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * amd5536.c -- AMD 5536 UDC high/full speed USB device controller + * + * Copyright (C) 2005-2007 AMD (https://www.amd.com) + * Author: Thomas Dahlmann + */ + +/* + * This file does the core driver implementation for the UDC that is based + * on Synopsys device controller IP (different than HS OTG IP) that is either + * connected through PCI bus or integrated to SoC platforms. + */ + +/* Driver strings */ +#define UDC_MOD_DESCRIPTION "Synopsys USB Device Controller" +#define UDC_DRIVER_VERSION_STRING "01.00.0206" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "amd5536udc.h" + +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); + +/* description */ +static const char mod_desc[] = UDC_MOD_DESCRIPTION; +static const char name[] = "udc"; + +/* 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 corresponding 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); + +/* endpoint names used for print */ +static const char ep0_string[] = "ep0in"; +static const struct { + const char *name; + const struct usb_ep_caps caps; +} ep_info[] = { +#define EP_INFO(_name, _caps) \ + { \ + .name = _name, \ + .caps = _caps, \ + } + + EP_INFO(ep0_string, + USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep1in-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep2in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep3in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep4in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep5in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep6in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep7in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep8in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep9in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep10in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep11in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep12in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep13in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep14in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep15in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep0out", + USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep1out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep2out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep3out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep4out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep5out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep6out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep7out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep8out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep9out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep10out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep11out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep12out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep13out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep14out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep15out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + +#undef EP_INFO +}; + +/* buffer fill mode */ +static int use_dma_bufferfill_mode; +/* tx buffer size for high speed */ +static unsigned long hs_tx_buf = UDC_EPIN_BUFF_SIZE; + +/*---------------------------------------------------------------------------*/ +/* 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->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->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->dev, "DMA mode (%s)\n", "BF"); + } + if (!use_dma) + dev_info(dev->dev, "FIFO mode\n"); + DBG(dev, "-------------------------------------------------------\n"); +} + +/* Masks unused interrupts */ +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; +} +EXPORT_SYMBOL_GPL(udc_mask_unused_interrupts); + +/* 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 */ +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; +} +EXPORT_SYMBOL_GPL(udc_enable_dev_setup_interrupts); + +/* 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); + + usb_ep_set_maxpacket_limit(&ep->ep,(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_obj(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 = dma_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 pci pool descriptors of a DMA chain */ +static void udc_free_dma_chain(struct udc *dev, struct udc_request *req) +{ + struct udc_data_dma *td = req->td_data; + unsigned int i; + + dma_addr_t addr_next = 0x00; + dma_addr_t addr = (dma_addr_t)td->next; + + DBG(dev, "free chain req = %p\n", req); + + /* do not free first desc., will be done by free for request */ + for (i = 1; i < req->chain_len; i++) { + td = phys_to_virt(addr); + addr_next = (dma_addr_t)td->next; + dma_pool_free(dev->data_requests, td, addr); + addr = addr_next; + } +} + +/* 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); + + dma_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; +} + +/* 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 = dma_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; +} + +/* 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); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&dev->lock); + ep->halted = halted; +} + +/* 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; + +} + +/* 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_ATOMIC); + 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_ATOMIC); + 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 */ +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); + } +} +EXPORT_SYMBOL_GPL(empty_req_queue); + +/* 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; +} + +/* 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; +} + +/* 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); + +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; +} + +/* Inits UDC context */ +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; +} +EXPORT_SYMBOL_GPL(udc_basic_init); + +/* 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; +} + +/* 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_info[tmp].name; + ep->ep.caps = ep_info[tmp].caps; + 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) { + usb_ep_set_maxpacket_limit(&dev->ep[UDC_EP0IN_IX].ep, + UDC_FS_EP0IN_MAX_PKT_SIZE); + usb_ep_set_maxpacket_limit(&dev->ep[UDC_EP0OUT_IX].ep, + UDC_FS_EP0OUT_MAX_PKT_SIZE); + } else if (dev->gadget.speed == USB_SPEED_HIGH) { + usb_ep_set_maxpacket_limit(&dev->ep[UDC_EP0IN_IX].ep, + UDC_EP0IN_MAX_PKT_SIZE); + usb_ep_set_maxpacket_limit(&dev->ep[UDC_EP0OUT_IX].ep, + 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) +{ + /* Return if already connected */ + if (dev->connected) + return; + + dev_info(dev->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) +{ + u32 tmp; + + /* Return if already disconnected */ + if (!dev->connected) + return; + + dev_info(dev->dev, "USB Disconnect\n"); + + dev->connected = 0; + + /* mask interrupts */ + udc_mask_unused_interrupts(dev); + + 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); + } +} + +/* 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); + + /* Don't do this for Broadcom UDC since this is a reserved + * bit. + */ + if (dev->chiprev != UDC_BCM_REV) { + 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(struct timer_list *unused) +{ + 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(struct timer_list *unused) +{ + 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; + + 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 udc *dev = to_amd5536_udc(g); + unsigned long flags; + u32 tmp; + + spin_lock_irqsave(&dev->lock, flags); + udc_mask_unused_interrupts(dev); + shutdown(dev, NULL); + 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->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) { + 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 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->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->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) + 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); + + /* length 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; + } + usb_gadget_udc_reset(&dev->gadget, dev->driver); + 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->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 */ +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; +} +EXPORT_SYMBOL_GPL(udc_irq); + +/* Tears down device */ +void gadget_release(struct device *pdev) +{ + struct amd5536udc *dev = dev_get_drvdata(pdev); + kfree(dev); +} +EXPORT_SYMBOL_GPL(gadget_release); + +/* Cleanup on device remove */ +void udc_remove(struct udc *dev) +{ + /* remove timer */ + stop_timer++; + if (timer_pending(&udc_timer)) + wait_for_completion(&on_exit); + timer_delete_sync(&udc_timer); + /* remove pollstall timer */ + stop_pollstall_timer++; + if (timer_pending(&udc_pollstall_timer)) + wait_for_completion(&on_pollstall_exit); + timer_delete_sync(&udc_pollstall_timer); + udc = NULL; +} +EXPORT_SYMBOL_GPL(udc_remove); + +/* free all the dma pools */ +void free_dma_pools(struct udc *dev) +{ + dma_pool_free(dev->stp_requests, dev->ep[UDC_EP0OUT_IX].td, + dev->ep[UDC_EP0OUT_IX].td_phys); + dma_pool_free(dev->stp_requests, dev->ep[UDC_EP0OUT_IX].td_stp, + dev->ep[UDC_EP0OUT_IX].td_stp_dma); + dma_pool_destroy(dev->stp_requests); + dma_pool_destroy(dev->data_requests); +} +EXPORT_SYMBOL_GPL(free_dma_pools); + +/* create dma pools on init */ +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", dev->dev, + sizeof(struct udc_data_dma), 0, 0); + if (!dev->data_requests) { + DBG(dev, "can't get request data pool\n"); + return -ENOMEM; + } + + /* 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", dev->dev, + sizeof(struct udc_stp_dma), 0, 0); + if (!dev->stp_requests) { + DBG(dev, "can't get stp request pool\n"); + retval = -ENOMEM; + goto err_create_dma_pool; + } + /* setup */ + td_stp = dma_pool_alloc(dev->stp_requests, GFP_KERNEL, + &dev->ep[UDC_EP0OUT_IX].td_stp_dma); + if (!td_stp) { + retval = -ENOMEM; + goto err_alloc_dma; + } + 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) { + retval = -ENOMEM; + goto err_alloc_phys; + } + dev->ep[UDC_EP0OUT_IX].td = td_data; + return 0; + +err_alloc_phys: + dma_pool_free(dev->stp_requests, dev->ep[UDC_EP0OUT_IX].td_stp, + dev->ep[UDC_EP0OUT_IX].td_stp_dma); +err_alloc_dma: + dma_pool_destroy(dev->stp_requests); + dev->stp_requests = NULL; +err_create_dma_pool: + dma_pool_destroy(dev->data_requests); + dev->data_requests = NULL; + return retval; +} +EXPORT_SYMBOL_GPL(init_dma_pools); + +/* general probe */ +int udc_probe(struct udc *dev) +{ + char tmp[128]; + u32 reg; + int retval; + + /* device struct setup */ + dev->gadget.ops = &udc_ops; + + dev->gadget.name = name; + dev->gadget.max_speed = USB_SPEED_HIGH; + + /* init registers, interrupts, ... */ + startup_registers(dev); + + dev_info(dev->dev, "%s\n", mod_desc); + + snprintf(tmp, sizeof(tmp), "%d", dev->irq); + + /* Print this device info for AMD chips only*/ + if (dev->chiprev == UDC_HSA0_REV || + dev->chiprev == UDC_HSB1_REV) { + dev_info(dev->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"); + strscpy(tmp, UDC_DRIVER_VERSION_STRING); + if (dev->chiprev == UDC_HSA0_REV) { + dev_err(dev->dev, "chip revision is A0; too old\n"); + retval = -ENODEV; + goto finished; + } + dev_info(dev->dev, + "driver version: %s(for Geode5536 B1)\n", tmp); + } + + udc = dev; + + retval = usb_add_gadget_udc_release(udc->dev, &dev->gadget, + gadget_release); + if (retval) + goto finished; + + /* timer init */ + timer_setup(&udc_timer, udc_timer_function, 0); + timer_setup(&udc_pollstall_timer, udc_pollstall_timer_function, 0); + + /* 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; +} +EXPORT_SYMBOL_GPL(udc_probe); + +MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION); +MODULE_AUTHOR("Thomas Dahlmann"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/snps_udc_plat.c b/drivers/usb/gadget/udc/snps_udc_plat.c new file mode 100644 index 000000000..0ee3ed185 --- /dev/null +++ b/drivers/usb/gadget/udc/snps_udc_plat.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * snps_udc_plat.c - Synopsys UDC Platform Driver + * + * Copyright (C) 2016 Broadcom + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "amd5536udc.h" + +/* description */ +#define UDC_MOD_DESCRIPTION "Synopsys UDC platform driver" + +static void start_udc(struct udc *udc) +{ + if (udc->driver) { + dev_info(udc->dev, "Connecting...\n"); + udc_enable_dev_setup_interrupts(udc); + udc_basic_init(udc); + udc->connected = 1; + } +} + +static void stop_udc(struct udc *udc) +{ + int tmp; + u32 reg; + + spin_lock(&udc->lock); + + /* Flush the receieve fifo */ + reg = readl(&udc->regs->ctl); + reg |= AMD_BIT(UDC_DEVCTL_SRX_FLUSH); + writel(reg, &udc->regs->ctl); + + reg = readl(&udc->regs->ctl); + reg &= ~(AMD_BIT(UDC_DEVCTL_SRX_FLUSH)); + writel(reg, &udc->regs->ctl); + dev_dbg(udc->dev, "ep rx queue flushed\n"); + + /* Mask interrupts. Required more so when the + * UDC is connected to a DRD phy. + */ + udc_mask_unused_interrupts(udc); + + /* Disconnect gadget driver */ + if (udc->driver) { + spin_unlock(&udc->lock); + udc->driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + + /* empty queues */ + for (tmp = 0; tmp < UDC_EP_NUM; tmp++) + empty_req_queue(&udc->ep[tmp]); + } + udc->connected = 0; + + spin_unlock(&udc->lock); + dev_info(udc->dev, "Device disconnected\n"); +} + +static void udc_drd_work(struct work_struct *work) +{ + struct udc *udc; + + udc = container_of(to_delayed_work(work), + struct udc, drd_work); + + if (udc->conn_type) { + dev_dbg(udc->dev, "idle -> device\n"); + start_udc(udc); + } else { + dev_dbg(udc->dev, "device -> idle\n"); + stop_udc(udc); + } +} + +static int usbd_connect_notify(struct notifier_block *self, + unsigned long event, void *ptr) +{ + struct udc *udc = container_of(self, struct udc, nb); + + dev_dbg(udc->dev, "%s: event: %lu\n", __func__, event); + + udc->conn_type = event; + + schedule_delayed_work(&udc->drd_work, 0); + + return NOTIFY_OK; +} + +static int udc_plat_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct udc *udc; + int ret; + + udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + spin_lock_init(&udc->lock); + udc->dev = dev; + + udc->virt_addr = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(udc->virt_addr)) + return PTR_ERR(udc->virt_addr); + + /* udc csr registers base */ + udc->csr = udc->virt_addr + UDC_CSR_ADDR; + + /* dev registers base */ + udc->regs = udc->virt_addr + UDC_DEVCFG_ADDR; + + /* ep registers base */ + udc->ep_regs = udc->virt_addr + UDC_EPREGS_ADDR; + + /* fifo's base */ + udc->rxfifo = (u32 __iomem *)(udc->virt_addr + UDC_RXFIFO_ADDR); + udc->txfifo = (u32 __iomem *)(udc->virt_addr + UDC_TXFIFO_ADDR); + + udc->phys_addr = (unsigned long)res->start; + + udc->irq = irq_of_parse_and_map(dev->of_node, 0); + if (udc->irq <= 0) { + dev_err(dev, "Can't parse and map interrupt\n"); + return -EINVAL; + } + + udc->udc_phy = devm_of_phy_get_by_index(dev, dev->of_node, 0); + if (IS_ERR(udc->udc_phy)) { + dev_err(dev, "Failed to obtain phy from device tree\n"); + return PTR_ERR(udc->udc_phy); + } + + ret = phy_init(udc->udc_phy); + if (ret) { + dev_err(dev, "UDC phy init failed"); + return ret; + } + + ret = phy_power_on(udc->udc_phy); + if (ret) { + dev_err(dev, "UDC phy power on failed"); + phy_exit(udc->udc_phy); + return ret; + } + + /* Register for extcon if supported */ + if (of_property_present(dev->of_node, "extcon")) { + udc->edev = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(udc->edev)) { + ret = PTR_ERR(udc->edev); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Invalid or missing extcon\n"); + goto exit_phy; + } + + udc->nb.notifier_call = usbd_connect_notify; + ret = extcon_register_notifier(udc->edev, EXTCON_USB, + &udc->nb); + if (ret < 0) { + dev_err(dev, "Can't register extcon device\n"); + goto exit_phy; + } + + ret = extcon_get_state(udc->edev, EXTCON_USB); + if (ret < 0) { + dev_err(dev, "Can't get cable state\n"); + goto exit_extcon; + } else if (ret) { + udc->conn_type = ret; + } + INIT_DELAYED_WORK(&udc->drd_work, udc_drd_work); + } + + /* init dma pools */ + if (use_dma) { + ret = init_dma_pools(udc); + if (ret != 0) + goto exit_extcon; + } + + ret = devm_request_irq(dev, udc->irq, udc_irq, IRQF_SHARED, + "snps-udc", udc); + if (ret < 0) { + dev_err(dev, "Request irq %d failed for UDC\n", udc->irq); + goto exit_dma; + } + + platform_set_drvdata(pdev, udc); + udc->chiprev = UDC_BCM_REV; + + if (udc_probe(udc)) { + ret = -ENODEV; + goto exit_dma; + } + dev_info(dev, "Synopsys UDC platform driver probe successful\n"); + + return 0; + +exit_dma: + if (use_dma) + free_dma_pools(udc); +exit_extcon: + if (udc->edev) + extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb); +exit_phy: + if (udc->udc_phy) { + phy_power_off(udc->udc_phy); + phy_exit(udc->udc_phy); + } + return ret; +} + +static void udc_plat_remove(struct platform_device *pdev) +{ + struct udc *dev; + + dev = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&dev->gadget); + /* gadget driver must not be registered */ + if (WARN_ON(dev->driver)) + return; + + /* dma pool cleanup */ + free_dma_pools(dev); + + udc_remove(dev); + + platform_set_drvdata(pdev, NULL); + + phy_power_off(dev->udc_phy); + phy_exit(dev->udc_phy); + extcon_unregister_notifier(dev->edev, EXTCON_USB, &dev->nb); + + dev_info(&pdev->dev, "Synopsys UDC platform driver removed\n"); +} + +#ifdef CONFIG_PM_SLEEP +static int udc_plat_suspend(struct device *dev) +{ + struct udc *udc; + + udc = dev_get_drvdata(dev); + stop_udc(udc); + + if (extcon_get_state(udc->edev, EXTCON_USB) > 0) { + dev_dbg(udc->dev, "device -> idle\n"); + stop_udc(udc); + } + phy_power_off(udc->udc_phy); + phy_exit(udc->udc_phy); + + return 0; +} + +static int udc_plat_resume(struct device *dev) +{ + struct udc *udc; + int ret; + + udc = dev_get_drvdata(dev); + + ret = phy_init(udc->udc_phy); + if (ret) { + dev_err(udc->dev, "UDC phy init failure"); + return ret; + } + + ret = phy_power_on(udc->udc_phy); + if (ret) { + dev_err(udc->dev, "UDC phy power on failure"); + phy_exit(udc->udc_phy); + return ret; + } + + if (extcon_get_state(udc->edev, EXTCON_USB) > 0) { + dev_dbg(udc->dev, "idle -> device\n"); + start_udc(udc); + } + + return 0; +} +static const struct dev_pm_ops udc_plat_pm_ops = { + .suspend = udc_plat_suspend, + .resume = udc_plat_resume, +}; +#endif + +static const struct of_device_id of_udc_match[] = { + { .compatible = "brcm,ns2-udc", }, + { .compatible = "brcm,cygnus-udc", }, + { .compatible = "brcm,iproc-udc", }, + { } +}; +MODULE_DEVICE_TABLE(of, of_udc_match); + +static struct platform_driver udc_plat_driver = { + .probe = udc_plat_probe, + .remove = udc_plat_remove, + .driver = { + .name = "snps-udc-plat", + .of_match_table = of_udc_match, +#ifdef CONFIG_PM_SLEEP + .pm = &udc_plat_pm_ops, +#endif + }, +}; +module_platform_driver(udc_plat_driver); + +MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION); +MODULE_AUTHOR("Broadcom"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c new file mode 100644 index 000000000..e9d33be02 --- /dev/null +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -0,0 +1,4095 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * NVIDIA Tegra XUSB device mode controller + * + * Copyright (c) 2013-2022, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2015, Google Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* XUSB_DEV registers */ +#define DB 0x004 +#define DB_TARGET_MASK GENMASK(15, 8) +#define DB_TARGET(x) (((x) << 8) & DB_TARGET_MASK) +#define DB_STREAMID_MASK GENMASK(31, 16) +#define DB_STREAMID(x) (((x) << 16) & DB_STREAMID_MASK) +#define ERSTSZ 0x008 +#define ERSTSZ_ERSTXSZ_SHIFT(x) ((x) * 16) +#define ERSTSZ_ERSTXSZ_MASK GENMASK(15, 0) +#define ERSTXBALO(x) (0x010 + 8 * (x)) +#define ERSTXBAHI(x) (0x014 + 8 * (x)) +#define ERDPLO 0x020 +#define ERDPLO_EHB BIT(3) +#define ERDPHI 0x024 +#define EREPLO 0x028 +#define EREPLO_ECS BIT(0) +#define EREPLO_SEGI BIT(1) +#define EREPHI 0x02c +#define CTRL 0x030 +#define CTRL_RUN BIT(0) +#define CTRL_LSE BIT(1) +#define CTRL_IE BIT(4) +#define CTRL_SMI_EVT BIT(5) +#define CTRL_SMI_DSE BIT(6) +#define CTRL_EWE BIT(7) +#define CTRL_DEVADDR_MASK GENMASK(30, 24) +#define CTRL_DEVADDR(x) (((x) << 24) & CTRL_DEVADDR_MASK) +#define CTRL_ENABLE BIT(31) +#define ST 0x034 +#define ST_RC BIT(0) +#define ST_IP BIT(4) +#define RT_IMOD 0x038 +#define RT_IMOD_IMODI_MASK GENMASK(15, 0) +#define RT_IMOD_IMODI(x) ((x) & RT_IMOD_IMODI_MASK) +#define RT_IMOD_IMODC_MASK GENMASK(31, 16) +#define RT_IMOD_IMODC(x) (((x) << 16) & RT_IMOD_IMODC_MASK) +#define PORTSC 0x03c +#define PORTSC_CCS BIT(0) +#define PORTSC_PED BIT(1) +#define PORTSC_PR BIT(4) +#define PORTSC_PLS_SHIFT 5 +#define PORTSC_PLS_MASK GENMASK(8, 5) +#define PORTSC_PLS_U0 0x0 +#define PORTSC_PLS_U2 0x2 +#define PORTSC_PLS_U3 0x3 +#define PORTSC_PLS_DISABLED 0x4 +#define PORTSC_PLS_RXDETECT 0x5 +#define PORTSC_PLS_INACTIVE 0x6 +#define PORTSC_PLS_RESUME 0xf +#define PORTSC_PLS(x) (((x) << PORTSC_PLS_SHIFT) & PORTSC_PLS_MASK) +#define PORTSC_PS_SHIFT 10 +#define PORTSC_PS_MASK GENMASK(13, 10) +#define PORTSC_PS_UNDEFINED 0x0 +#define PORTSC_PS_FS 0x1 +#define PORTSC_PS_LS 0x2 +#define PORTSC_PS_HS 0x3 +#define PORTSC_PS_SS 0x4 +#define PORTSC_LWS BIT(16) +#define PORTSC_CSC BIT(17) +#define PORTSC_WRC BIT(19) +#define PORTSC_PRC BIT(21) +#define PORTSC_PLC BIT(22) +#define PORTSC_CEC BIT(23) +#define PORTSC_WPR BIT(30) +#define PORTSC_CHANGE_MASK (PORTSC_CSC | PORTSC_WRC | PORTSC_PRC | \ + PORTSC_PLC | PORTSC_CEC) +#define ECPLO 0x040 +#define ECPHI 0x044 +#define MFINDEX 0x048 +#define MFINDEX_FRAME_SHIFT 3 +#define MFINDEX_FRAME_MASK GENMASK(13, 3) +#define PORTPM 0x04c +#define PORTPM_L1S_MASK GENMASK(1, 0) +#define PORTPM_L1S_DROP 0x0 +#define PORTPM_L1S_ACCEPT 0x1 +#define PORTPM_L1S_NYET 0x2 +#define PORTPM_L1S_STALL 0x3 +#define PORTPM_L1S(x) ((x) & PORTPM_L1S_MASK) +#define PORTPM_RWE BIT(3) +#define PORTPM_U2TIMEOUT_MASK GENMASK(15, 8) +#define PORTPM_U1TIMEOUT_MASK GENMASK(23, 16) +#define PORTPM_FLA BIT(24) +#define PORTPM_VBA BIT(25) +#define PORTPM_WOC BIT(26) +#define PORTPM_WOD BIT(27) +#define PORTPM_U1E BIT(28) +#define PORTPM_U2E BIT(29) +#define PORTPM_FRWE BIT(30) +#define PORTPM_PNG_CYA BIT(31) +#define EP_HALT 0x050 +#define EP_PAUSE 0x054 +#define EP_RELOAD 0x058 +#define EP_STCHG 0x05c +#define DEVNOTIF_LO 0x064 +#define DEVNOTIF_LO_TRIG BIT(0) +#define DEVNOTIF_LO_TYPE_MASK GENMASK(7, 4) +#define DEVNOTIF_LO_TYPE(x) (((x) << 4) & DEVNOTIF_LO_TYPE_MASK) +#define DEVNOTIF_LO_TYPE_FUNCTION_WAKE 0x1 +#define DEVNOTIF_HI 0x068 +#define PORTHALT 0x06c +#define PORTHALT_HALT_LTSSM BIT(0) +#define PORTHALT_HALT_REJECT BIT(1) +#define PORTHALT_STCHG_REQ BIT(20) +#define PORTHALT_STCHG_INTR_EN BIT(24) +#define PORT_TM 0x070 +#define EP_THREAD_ACTIVE 0x074 +#define EP_STOPPED 0x078 +#define HSFSPI_COUNT0 0x100 +#define HSFSPI_COUNT13 0x134 +#define HSFSPI_COUNT13_U2_RESUME_K_DURATION_MASK GENMASK(29, 0) +#define HSFSPI_COUNT13_U2_RESUME_K_DURATION(x) ((x) & \ + HSFSPI_COUNT13_U2_RESUME_K_DURATION_MASK) +#define BLCG 0x840 +#define SSPX_CORE_CNT0 0x610 +#define SSPX_CORE_CNT0_PING_TBURST_MASK GENMASK(7, 0) +#define SSPX_CORE_CNT0_PING_TBURST(x) ((x) & SSPX_CORE_CNT0_PING_TBURST_MASK) +#define SSPX_CORE_CNT30 0x688 +#define SSPX_CORE_CNT30_LMPITP_TIMER_MASK GENMASK(19, 0) +#define SSPX_CORE_CNT30_LMPITP_TIMER(x) ((x) & \ + SSPX_CORE_CNT30_LMPITP_TIMER_MASK) +#define SSPX_CORE_CNT32 0x690 +#define SSPX_CORE_CNT32_POLL_TBURST_MAX_MASK GENMASK(7, 0) +#define SSPX_CORE_CNT32_POLL_TBURST_MAX(x) ((x) & \ + SSPX_CORE_CNT32_POLL_TBURST_MAX_MASK) +#define SSPX_CORE_CNT56 0x6fc +#define SSPX_CORE_CNT56_SCD_BIT0_TRPT_MAX_MASK GENMASK(19, 0) +#define SSPX_CORE_CNT56_SCD_BIT0_TRPT_MAX(x) ((x) & \ + SSPX_CORE_CNT56_SCD_BIT0_TRPT_MAX_MASK) +#define SSPX_CORE_CNT57 0x700 +#define SSPX_CORE_CNT57_SCD_BIT1_TRPT_MAX_MASK GENMASK(19, 0) +#define SSPX_CORE_CNT57_SCD_BIT1_TRPT_MAX(x) ((x) & \ + SSPX_CORE_CNT57_SCD_BIT1_TRPT_MAX_MASK) +#define SSPX_CORE_CNT65 0x720 +#define SSPX_CORE_CNT65_TX_SCD_END_TRPT_MID_MASK GENMASK(19, 0) +#define SSPX_CORE_CNT65_TX_SCD_END_TRPT_MID(x) ((x) & \ + SSPX_CORE_CNT65_TX_SCD_END_TRPT_MID_MASK) +#define SSPX_CORE_CNT66 0x724 +#define SSPX_CORE_CNT66_TX_SCD_BIT0_TRPT_MID_MASK GENMASK(19, 0) +#define SSPX_CORE_CNT66_TX_SCD_BIT0_TRPT_MID(x) ((x) & \ + SSPX_CORE_CNT66_TX_SCD_BIT0_TRPT_MID_MASK) +#define SSPX_CORE_CNT67 0x728 +#define SSPX_CORE_CNT67_TX_SCD_BIT1_TRPT_MID_MASK GENMASK(19, 0) +#define SSPX_CORE_CNT67_TX_SCD_BIT1_TRPT_MID(x) ((x) & \ + SSPX_CORE_CNT67_TX_SCD_BIT1_TRPT_MID_MASK) +#define SSPX_CORE_CNT72 0x73c +#define SSPX_CORE_CNT72_SCD_LFPS_TIMEOUT_MASK GENMASK(19, 0) +#define SSPX_CORE_CNT72_SCD_LFPS_TIMEOUT(x) ((x) & \ + SSPX_CORE_CNT72_SCD_LFPS_TIMEOUT_MASK) +#define SSPX_CORE_PADCTL4 0x750 +#define SSPX_CORE_PADCTL4_RXDAT_VLD_TIMEOUT_U3_MASK GENMASK(19, 0) +#define SSPX_CORE_PADCTL4_RXDAT_VLD_TIMEOUT_U3(x) ((x) & \ + SSPX_CORE_PADCTL4_RXDAT_VLD_TIMEOUT_U3_MASK) +#define BLCG_DFPCI BIT(0) +#define BLCG_UFPCI BIT(1) +#define BLCG_FE BIT(2) +#define BLCG_COREPLL_PWRDN BIT(8) +#define BLCG_IOPLL_0_PWRDN BIT(9) +#define BLCG_IOPLL_1_PWRDN BIT(10) +#define BLCG_IOPLL_2_PWRDN BIT(11) +#define BLCG_ALL 0x1ff +#define CFG_DEV_SSPI_XFER 0x858 +#define CFG_DEV_SSPI_XFER_ACKTIMEOUT_MASK GENMASK(31, 0) +#define CFG_DEV_SSPI_XFER_ACKTIMEOUT(x) ((x) & \ + CFG_DEV_SSPI_XFER_ACKTIMEOUT_MASK) +#define CFG_DEV_FE 0x85c +#define CFG_DEV_FE_PORTREGSEL_MASK GENMASK(1, 0) +#define CFG_DEV_FE_PORTREGSEL_SS_PI 1 +#define CFG_DEV_FE_PORTREGSEL_HSFS_PI 2 +#define CFG_DEV_FE_PORTREGSEL(x) ((x) & CFG_DEV_FE_PORTREGSEL_MASK) +#define CFG_DEV_FE_INFINITE_SS_RETRY BIT(29) + +/* FPCI registers */ +#define XUSB_DEV_CFG_1 0x004 +#define XUSB_DEV_CFG_1_IO_SPACE_EN BIT(0) +#define XUSB_DEV_CFG_1_MEMORY_SPACE_EN BIT(1) +#define XUSB_DEV_CFG_1_BUS_MASTER_EN BIT(2) +#define XUSB_DEV_CFG_4 0x010 +#define XUSB_DEV_CFG_4_BASE_ADDR_MASK GENMASK(31, 15) +#define XUSB_DEV_CFG_5 0x014 + +/* IPFS registers */ +#define XUSB_DEV_CONFIGURATION_0 0x180 +#define XUSB_DEV_CONFIGURATION_0_EN_FPCI BIT(0) +#define XUSB_DEV_INTR_MASK_0 0x188 +#define XUSB_DEV_INTR_MASK_0_IP_INT_MASK BIT(16) + +struct tegra_xudc_ep_context { + __le32 info0; + __le32 info1; + __le32 deq_lo; + __le32 deq_hi; + __le32 tx_info; + __le32 rsvd[11]; +}; + +#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 + +#define EP_TYPE_INVALID 0 +#define EP_TYPE_ISOCH_OUT 1 +#define EP_TYPE_BULK_OUT 2 +#define EP_TYPE_INTERRUPT_OUT 3 +#define EP_TYPE_CONTROL 4 +#define EP_TYPE_ISCOH_IN 5 +#define EP_TYPE_BULK_IN 6 +#define EP_TYPE_INTERRUPT_IN 7 + +#define BUILD_EP_CONTEXT_RW(name, member, shift, mask) \ +static inline u32 ep_ctx_read_##name(struct tegra_xudc_ep_context *ctx) \ +{ \ + return (le32_to_cpu(ctx->member) >> (shift)) & (mask); \ +} \ +static inline void \ +ep_ctx_write_##name(struct tegra_xudc_ep_context *ctx, u32 val) \ +{ \ + u32 tmp; \ + \ + tmp = le32_to_cpu(ctx->member) & ~((mask) << (shift)); \ + tmp |= (val & (mask)) << (shift); \ + ctx->member = cpu_to_le32(tmp); \ +} + +BUILD_EP_CONTEXT_RW(state, info0, 0, 0x7) +BUILD_EP_CONTEXT_RW(mult, info0, 8, 0x3) +BUILD_EP_CONTEXT_RW(max_pstreams, info0, 10, 0x1f) +BUILD_EP_CONTEXT_RW(lsa, info0, 15, 0x1) +BUILD_EP_CONTEXT_RW(interval, info0, 16, 0xff) +BUILD_EP_CONTEXT_RW(cerr, info1, 1, 0x3) +BUILD_EP_CONTEXT_RW(type, info1, 3, 0x7) +BUILD_EP_CONTEXT_RW(hid, info1, 7, 0x1) +BUILD_EP_CONTEXT_RW(max_burst_size, info1, 8, 0xff) +BUILD_EP_CONTEXT_RW(max_packet_size, info1, 16, 0xffff) +BUILD_EP_CONTEXT_RW(dcs, deq_lo, 0, 0x1) +BUILD_EP_CONTEXT_RW(deq_lo, deq_lo, 4, 0xfffffff) +BUILD_EP_CONTEXT_RW(deq_hi, deq_hi, 0, 0xffffffff) +BUILD_EP_CONTEXT_RW(avg_trb_len, tx_info, 0, 0xffff) +BUILD_EP_CONTEXT_RW(max_esit_payload, tx_info, 16, 0xffff) +BUILD_EP_CONTEXT_RW(edtla, rsvd[0], 0, 0xffffff) +BUILD_EP_CONTEXT_RW(rsvd, rsvd[0], 24, 0x1) +BUILD_EP_CONTEXT_RW(partial_td, rsvd[0], 25, 0x1) +BUILD_EP_CONTEXT_RW(splitxstate, rsvd[0], 26, 0x1) +BUILD_EP_CONTEXT_RW(seq_num, rsvd[0], 27, 0x1f) +BUILD_EP_CONTEXT_RW(cerrcnt, rsvd[1], 18, 0x3) +BUILD_EP_CONTEXT_RW(data_offset, rsvd[2], 0, 0x1ffff) +BUILD_EP_CONTEXT_RW(numtrbs, rsvd[2], 22, 0x1f) +BUILD_EP_CONTEXT_RW(devaddr, rsvd[6], 0, 0x7f) + +static inline u64 ep_ctx_read_deq_ptr(struct tegra_xudc_ep_context *ctx) +{ + return ((u64)ep_ctx_read_deq_hi(ctx) << 32) | + (ep_ctx_read_deq_lo(ctx) << 4); +} + +static inline void +ep_ctx_write_deq_ptr(struct tegra_xudc_ep_context *ctx, u64 addr) +{ + ep_ctx_write_deq_lo(ctx, lower_32_bits(addr) >> 4); + ep_ctx_write_deq_hi(ctx, upper_32_bits(addr)); +} + +struct tegra_xudc_trb { + __le32 data_lo; + __le32 data_hi; + __le32 status; + __le32 control; +}; + +#define TRB_TYPE_RSVD 0 +#define TRB_TYPE_NORMAL 1 +#define TRB_TYPE_SETUP_STAGE 2 +#define TRB_TYPE_DATA_STAGE 3 +#define TRB_TYPE_STATUS_STAGE 4 +#define TRB_TYPE_ISOCH 5 +#define TRB_TYPE_LINK 6 +#define TRB_TYPE_TRANSFER_EVENT 32 +#define TRB_TYPE_PORT_STATUS_CHANGE_EVENT 34 +#define TRB_TYPE_STREAM 48 +#define TRB_TYPE_SETUP_PACKET_EVENT 63 + +#define TRB_CMPL_CODE_INVALID 0 +#define TRB_CMPL_CODE_SUCCESS 1 +#define TRB_CMPL_CODE_DATA_BUFFER_ERR 2 +#define TRB_CMPL_CODE_BABBLE_DETECTED_ERR 3 +#define TRB_CMPL_CODE_USB_TRANS_ERR 4 +#define TRB_CMPL_CODE_TRB_ERR 5 +#define TRB_CMPL_CODE_STALL 6 +#define TRB_CMPL_CODE_INVALID_STREAM_TYPE_ERR 10 +#define TRB_CMPL_CODE_SHORT_PACKET 13 +#define TRB_CMPL_CODE_RING_UNDERRUN 14 +#define TRB_CMPL_CODE_RING_OVERRUN 15 +#define TRB_CMPL_CODE_EVENT_RING_FULL_ERR 21 +#define TRB_CMPL_CODE_STOPPED 26 +#define TRB_CMPL_CODE_ISOCH_BUFFER_OVERRUN 31 +#define TRB_CMPL_CODE_STREAM_NUMP_ERROR 219 +#define TRB_CMPL_CODE_PRIME_PIPE_RECEIVED 220 +#define TRB_CMPL_CODE_HOST_REJECTED 221 +#define TRB_CMPL_CODE_CTRL_DIR_ERR 222 +#define TRB_CMPL_CODE_CTRL_SEQNUM_ERR 223 + +#define BUILD_TRB_RW(name, member, shift, mask) \ +static inline u32 trb_read_##name(struct tegra_xudc_trb *trb) \ +{ \ + return (le32_to_cpu(trb->member) >> (shift)) & (mask); \ +} \ +static inline void \ +trb_write_##name(struct tegra_xudc_trb *trb, u32 val) \ +{ \ + u32 tmp; \ + \ + tmp = le32_to_cpu(trb->member) & ~((mask) << (shift)); \ + tmp |= (val & (mask)) << (shift); \ + trb->member = cpu_to_le32(tmp); \ +} + +BUILD_TRB_RW(data_lo, data_lo, 0, 0xffffffff) +BUILD_TRB_RW(data_hi, data_hi, 0, 0xffffffff) +BUILD_TRB_RW(seq_num, status, 0, 0xffff) +BUILD_TRB_RW(transfer_len, status, 0, 0xffffff) +BUILD_TRB_RW(td_size, status, 17, 0x1f) +BUILD_TRB_RW(cmpl_code, status, 24, 0xff) +BUILD_TRB_RW(cycle, control, 0, 0x1) +BUILD_TRB_RW(toggle_cycle, control, 1, 0x1) +BUILD_TRB_RW(isp, control, 2, 0x1) +BUILD_TRB_RW(chain, control, 4, 0x1) +BUILD_TRB_RW(ioc, control, 5, 0x1) +BUILD_TRB_RW(type, control, 10, 0x3f) +BUILD_TRB_RW(stream_id, control, 16, 0xffff) +BUILD_TRB_RW(endpoint_id, control, 16, 0x1f) +BUILD_TRB_RW(tlbpc, control, 16, 0xf) +BUILD_TRB_RW(data_stage_dir, control, 16, 0x1) +BUILD_TRB_RW(frame_id, control, 20, 0x7ff) +BUILD_TRB_RW(sia, control, 31, 0x1) + +static inline u64 trb_read_data_ptr(struct tegra_xudc_trb *trb) +{ + return ((u64)trb_read_data_hi(trb) << 32) | + trb_read_data_lo(trb); +} + +static inline void trb_write_data_ptr(struct tegra_xudc_trb *trb, u64 addr) +{ + trb_write_data_lo(trb, lower_32_bits(addr)); + trb_write_data_hi(trb, upper_32_bits(addr)); +} + +struct tegra_xudc_request { + struct usb_request usb_req; + + size_t buf_queued; + unsigned int trbs_queued; + unsigned int trbs_needed; + bool need_zlp; + + struct tegra_xudc_trb *first_trb; + struct tegra_xudc_trb *last_trb; + + struct list_head list; +}; + +struct tegra_xudc_ep { + struct tegra_xudc *xudc; + struct usb_ep usb_ep; + unsigned int index; + char name[8]; + + struct tegra_xudc_ep_context *context; + +#define XUDC_TRANSFER_RING_SIZE 64 + struct tegra_xudc_trb *transfer_ring; + dma_addr_t transfer_ring_phys; + + unsigned int enq_ptr; + unsigned int deq_ptr; + bool pcs; + bool ring_full; + bool stream_rejected; + + struct list_head queue; + const struct usb_endpoint_descriptor *desc; + const struct usb_ss_ep_comp_descriptor *comp_desc; +}; + +struct tegra_xudc_sel_timing { + __u8 u1sel; + __u8 u1pel; + __le16 u2sel; + __le16 u2pel; +}; + +enum tegra_xudc_setup_state { + WAIT_FOR_SETUP, + DATA_STAGE_XFER, + DATA_STAGE_RECV, + STATUS_STAGE_XFER, + STATUS_STAGE_RECV, +}; + +struct tegra_xudc_setup_packet { + struct usb_ctrlrequest ctrl_req; + unsigned int seq_num; +}; + +struct tegra_xudc_save_regs { + u32 ctrl; + u32 portpm; +}; + +struct tegra_xudc { + struct device *dev; + const struct tegra_xudc_soc *soc; + struct tegra_xusb_padctl *padctl; + + spinlock_t lock; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + +#define XUDC_NR_EVENT_RINGS 2 +#define XUDC_EVENT_RING_SIZE 4096 + struct tegra_xudc_trb *event_ring[XUDC_NR_EVENT_RINGS]; + dma_addr_t event_ring_phys[XUDC_NR_EVENT_RINGS]; + unsigned int event_ring_index; + unsigned int event_ring_deq_ptr; + bool ccs; + +#define XUDC_NR_EPS 32 + struct tegra_xudc_ep ep[XUDC_NR_EPS]; + struct tegra_xudc_ep_context *ep_context; + dma_addr_t ep_context_phys; + + struct device *genpd_dev_device; + struct device *genpd_dev_ss; + struct device_link *genpd_dl_device; + struct device_link *genpd_dl_ss; + + struct dma_pool *transfer_ring_pool; + + bool queued_setup_packet; + struct tegra_xudc_setup_packet setup_packet; + enum tegra_xudc_setup_state setup_state; + u16 setup_seq_num; + + u16 dev_addr; + u16 isoch_delay; + struct tegra_xudc_sel_timing sel_timing; + u8 test_mode_pattern; + u16 status_buf; + struct tegra_xudc_request *ep0_req; + + bool pullup; + + unsigned int nr_enabled_eps; + unsigned int nr_isoch_eps; + + unsigned int device_state; + unsigned int resume_state; + + int irq; + + void __iomem *base; + resource_size_t phys_base; + void __iomem *ipfs; + void __iomem *fpci; + + struct regulator_bulk_data *supplies; + + struct clk_bulk_data *clks; + + bool device_mode; + bool current_device_mode; + struct work_struct usb_role_sw_work; + + struct phy **usb3_phy; + struct phy *curr_usb3_phy; + struct phy **utmi_phy; + struct phy *curr_utmi_phy; + + struct tegra_xudc_save_regs saved_regs; + bool suspended; + bool powergated; + + struct usb_phy **usbphy; + struct usb_phy *curr_usbphy; + struct notifier_block vbus_nb; + + struct completion disconnect_complete; + + bool selfpowered; + +#define TOGGLE_VBUS_WAIT_MS 100 + struct delayed_work plc_reset_work; + bool wait_csc; + + struct delayed_work port_reset_war_work; + bool wait_for_sec_prc; +}; + +#define XUDC_TRB_MAX_BUFFER_SIZE 65536 +#define XUDC_MAX_ISOCH_EPS 4 +#define XUDC_INTERRUPT_MODERATION_US 0 + +static struct usb_endpoint_descriptor tegra_xudc_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(64), +}; + +struct tegra_xudc_soc { + const char * const *supply_names; + unsigned int num_supplies; + const char * const *clock_names; + unsigned int num_clks; + unsigned int num_phys; + bool u1_enable; + bool u2_enable; + bool lpm_enable; + bool invalid_seq_num; + bool pls_quirk; + bool port_reset_quirk; + bool port_speed_quirk; + bool has_ipfs; +}; + +static inline u32 fpci_readl(struct tegra_xudc *xudc, unsigned int offset) +{ + return readl(xudc->fpci + offset); +} + +static inline void fpci_writel(struct tegra_xudc *xudc, u32 val, + unsigned int offset) +{ + writel(val, xudc->fpci + offset); +} + +static inline u32 ipfs_readl(struct tegra_xudc *xudc, unsigned int offset) +{ + return readl(xudc->ipfs + offset); +} + +static inline void ipfs_writel(struct tegra_xudc *xudc, u32 val, + unsigned int offset) +{ + writel(val, xudc->ipfs + offset); +} + +static inline u32 xudc_readl(struct tegra_xudc *xudc, unsigned int offset) +{ + return readl(xudc->base + offset); +} + +static inline void xudc_writel(struct tegra_xudc *xudc, u32 val, + unsigned int offset) +{ + writel(val, xudc->base + offset); +} + +static inline int xudc_readl_poll(struct tegra_xudc *xudc, + unsigned int offset, u32 mask, u32 val) +{ + u32 regval; + + return readl_poll_timeout_atomic(xudc->base + offset, regval, + (regval & mask) == val, 1, 100); +} + +static inline struct tegra_xudc *to_xudc(struct usb_gadget *gadget) +{ + return container_of(gadget, struct tegra_xudc, gadget); +} + +static inline struct tegra_xudc_ep *to_xudc_ep(struct usb_ep *ep) +{ + return container_of(ep, struct tegra_xudc_ep, usb_ep); +} + +static inline struct tegra_xudc_request *to_xudc_req(struct usb_request *req) +{ + return container_of(req, struct tegra_xudc_request, usb_req); +} + +static inline void dump_trb(struct tegra_xudc *xudc, const char *type, + struct tegra_xudc_trb *trb) +{ + dev_dbg(xudc->dev, + "%s: %p, lo = %#x, hi = %#x, status = %#x, control = %#x\n", + type, trb, trb->data_lo, trb->data_hi, trb->status, + trb->control); +} + +static void tegra_xudc_limit_port_speed(struct tegra_xudc *xudc) +{ + u32 val; + + /* limit port speed to gen 1 */ + val = xudc_readl(xudc, SSPX_CORE_CNT56); + val &= ~(SSPX_CORE_CNT56_SCD_BIT0_TRPT_MAX_MASK); + val |= SSPX_CORE_CNT56_SCD_BIT0_TRPT_MAX(0x260); + xudc_writel(xudc, val, SSPX_CORE_CNT56); + + val = xudc_readl(xudc, SSPX_CORE_CNT57); + val &= ~(SSPX_CORE_CNT57_SCD_BIT1_TRPT_MAX_MASK); + val |= SSPX_CORE_CNT57_SCD_BIT1_TRPT_MAX(0x6D6); + xudc_writel(xudc, val, SSPX_CORE_CNT57); + + val = xudc_readl(xudc, SSPX_CORE_CNT65); + val &= ~(SSPX_CORE_CNT65_TX_SCD_END_TRPT_MID_MASK); + val |= SSPX_CORE_CNT65_TX_SCD_END_TRPT_MID(0x4B0); + xudc_writel(xudc, val, SSPX_CORE_CNT66); + + val = xudc_readl(xudc, SSPX_CORE_CNT66); + val &= ~(SSPX_CORE_CNT66_TX_SCD_BIT0_TRPT_MID_MASK); + val |= SSPX_CORE_CNT66_TX_SCD_BIT0_TRPT_MID(0x4B0); + xudc_writel(xudc, val, SSPX_CORE_CNT66); + + val = xudc_readl(xudc, SSPX_CORE_CNT67); + val &= ~(SSPX_CORE_CNT67_TX_SCD_BIT1_TRPT_MID_MASK); + val |= SSPX_CORE_CNT67_TX_SCD_BIT1_TRPT_MID(0x4B0); + xudc_writel(xudc, val, SSPX_CORE_CNT67); + + val = xudc_readl(xudc, SSPX_CORE_CNT72); + val &= ~(SSPX_CORE_CNT72_SCD_LFPS_TIMEOUT_MASK); + val |= SSPX_CORE_CNT72_SCD_LFPS_TIMEOUT(0x10); + xudc_writel(xudc, val, SSPX_CORE_CNT72); +} + +static void tegra_xudc_restore_port_speed(struct tegra_xudc *xudc) +{ + u32 val; + + /* restore port speed to gen2 */ + val = xudc_readl(xudc, SSPX_CORE_CNT56); + val &= ~(SSPX_CORE_CNT56_SCD_BIT0_TRPT_MAX_MASK); + val |= SSPX_CORE_CNT56_SCD_BIT0_TRPT_MAX(0x438); + xudc_writel(xudc, val, SSPX_CORE_CNT56); + + val = xudc_readl(xudc, SSPX_CORE_CNT57); + val &= ~(SSPX_CORE_CNT57_SCD_BIT1_TRPT_MAX_MASK); + val |= SSPX_CORE_CNT57_SCD_BIT1_TRPT_MAX(0x528); + xudc_writel(xudc, val, SSPX_CORE_CNT57); + + val = xudc_readl(xudc, SSPX_CORE_CNT65); + val &= ~(SSPX_CORE_CNT65_TX_SCD_END_TRPT_MID_MASK); + val |= SSPX_CORE_CNT65_TX_SCD_END_TRPT_MID(0xE10); + xudc_writel(xudc, val, SSPX_CORE_CNT66); + + val = xudc_readl(xudc, SSPX_CORE_CNT66); + val &= ~(SSPX_CORE_CNT66_TX_SCD_BIT0_TRPT_MID_MASK); + val |= SSPX_CORE_CNT66_TX_SCD_BIT0_TRPT_MID(0x348); + xudc_writel(xudc, val, SSPX_CORE_CNT66); + + val = xudc_readl(xudc, SSPX_CORE_CNT67); + val &= ~(SSPX_CORE_CNT67_TX_SCD_BIT1_TRPT_MID_MASK); + val |= SSPX_CORE_CNT67_TX_SCD_BIT1_TRPT_MID(0x5a0); + xudc_writel(xudc, val, SSPX_CORE_CNT67); + + val = xudc_readl(xudc, SSPX_CORE_CNT72); + val &= ~(SSPX_CORE_CNT72_SCD_LFPS_TIMEOUT_MASK); + val |= SSPX_CORE_CNT72_SCD_LFPS_TIMEOUT(0x1c21); + xudc_writel(xudc, val, SSPX_CORE_CNT72); +} + +static void tegra_xudc_device_mode_on(struct tegra_xudc *xudc) +{ + int err; + + pm_runtime_get_sync(xudc->dev); + + tegra_phy_xusb_utmi_pad_power_on(xudc->curr_utmi_phy); + + err = phy_power_on(xudc->curr_utmi_phy); + if (err < 0) + dev_err(xudc->dev, "UTMI power on failed: %d\n", err); + + err = phy_power_on(xudc->curr_usb3_phy); + if (err < 0) + dev_err(xudc->dev, "USB3 PHY power on failed: %d\n", err); + + dev_dbg(xudc->dev, "device mode on\n"); + + phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG, + USB_ROLE_DEVICE); + + xudc->current_device_mode = true; +} + +static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc) +{ + bool connected = false; + u32 pls, val; + int err; + + dev_dbg(xudc->dev, "device mode off\n"); + + xudc->current_device_mode = false; + + connected = !!(xudc_readl(xudc, PORTSC) & PORTSC_CCS); + + reinit_completion(&xudc->disconnect_complete); + + if (xudc->soc->port_speed_quirk) + tegra_xudc_restore_port_speed(xudc); + + phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG, USB_ROLE_NONE); + + pls = (xudc_readl(xudc, PORTSC) & PORTSC_PLS_MASK) >> + PORTSC_PLS_SHIFT; + + /* Direct link to U0 if disconnected in RESUME or U2. */ + if (xudc->soc->pls_quirk && xudc->gadget.speed == USB_SPEED_SUPER && + (pls == PORTSC_PLS_RESUME || pls == PORTSC_PLS_U2)) { + val = xudc_readl(xudc, PORTPM); + val |= PORTPM_FRWE; + xudc_writel(xudc, val, PORTPM); + + val = xudc_readl(xudc, PORTSC); + val &= ~(PORTSC_CHANGE_MASK | PORTSC_PLS_MASK); + val |= PORTSC_LWS | PORTSC_PLS(PORTSC_PLS_U0); + xudc_writel(xudc, val, PORTSC); + } + + /* Wait for disconnect event. */ + if (connected) + wait_for_completion(&xudc->disconnect_complete); + + /* Make sure interrupt handler has completed before powergating. */ + synchronize_irq(xudc->irq); + + tegra_phy_xusb_utmi_pad_power_down(xudc->curr_utmi_phy); + + err = phy_power_off(xudc->curr_utmi_phy); + if (err < 0) + dev_err(xudc->dev, "UTMI PHY power off failed: %d\n", err); + + err = phy_power_off(xudc->curr_usb3_phy); + if (err < 0) + dev_err(xudc->dev, "USB3 PHY power off failed: %d\n", err); + + pm_runtime_put(xudc->dev); +} + +static void tegra_xudc_usb_role_sw_work(struct work_struct *work) +{ + struct tegra_xudc *xudc = container_of(work, struct tegra_xudc, + usb_role_sw_work); + + if (xudc->device_mode) + tegra_xudc_device_mode_on(xudc); + else + tegra_xudc_device_mode_off(xudc); +} + +static int tegra_xudc_get_phy_index(struct tegra_xudc *xudc, + struct usb_phy *usbphy) +{ + unsigned int i; + + for (i = 0; i < xudc->soc->num_phys; i++) { + if (xudc->usbphy[i] && usbphy == xudc->usbphy[i]) + return i; + } + + dev_info(xudc->dev, "phy index could not be found for shared USB PHY"); + return -1; +} + +static void tegra_xudc_update_data_role(struct tegra_xudc *xudc, + struct usb_phy *usbphy) +{ + int phy_index; + + if ((xudc->device_mode && usbphy->last_event == USB_EVENT_VBUS) || + (!xudc->device_mode && usbphy->last_event != USB_EVENT_VBUS)) { + dev_dbg(xudc->dev, "Same role(%d) received. Ignore", + xudc->device_mode); + return; + } + + xudc->device_mode = usbphy->last_event == USB_EVENT_VBUS; + + phy_index = tegra_xudc_get_phy_index(xudc, usbphy); + dev_dbg(xudc->dev, "%s(): current phy index is %d\n", __func__, + phy_index); + + if (!xudc->suspended && phy_index != -1) { + xudc->curr_utmi_phy = xudc->utmi_phy[phy_index]; + xudc->curr_usb3_phy = xudc->usb3_phy[phy_index]; + xudc->curr_usbphy = usbphy; + schedule_work(&xudc->usb_role_sw_work); + } +} + +static int tegra_xudc_vbus_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct tegra_xudc *xudc = container_of(nb, struct tegra_xudc, + vbus_nb); + struct usb_phy *usbphy = (struct usb_phy *)data; + + dev_dbg(xudc->dev, "%s(): event is %d\n", __func__, usbphy->last_event); + + tegra_xudc_update_data_role(xudc, usbphy); + + return NOTIFY_OK; +} + +static void tegra_xudc_plc_reset_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct tegra_xudc *xudc = container_of(dwork, struct tegra_xudc, + plc_reset_work); + unsigned long flags; + + spin_lock_irqsave(&xudc->lock, flags); + + if (xudc->wait_csc) { + u32 pls = (xudc_readl(xudc, PORTSC) & PORTSC_PLS_MASK) >> + PORTSC_PLS_SHIFT; + + if (pls == PORTSC_PLS_INACTIVE) { + dev_info(xudc->dev, "PLS = Inactive. Toggle VBUS\n"); + phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG, + USB_ROLE_NONE); + phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG, + USB_ROLE_DEVICE); + + xudc->wait_csc = false; + } + } + + spin_unlock_irqrestore(&xudc->lock, flags); +} + +static void tegra_xudc_port_reset_war_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct tegra_xudc *xudc = + container_of(dwork, struct tegra_xudc, port_reset_war_work); + unsigned long flags; + u32 pls; + int ret; + + spin_lock_irqsave(&xudc->lock, flags); + + if (xudc->device_mode && xudc->wait_for_sec_prc) { + pls = (xudc_readl(xudc, PORTSC) & PORTSC_PLS_MASK) >> + PORTSC_PLS_SHIFT; + dev_dbg(xudc->dev, "pls = %x\n", pls); + + if (pls == PORTSC_PLS_DISABLED) { + dev_dbg(xudc->dev, "toggle vbus\n"); + /* PRC doesn't complete in 100ms, toggle the vbus */ + ret = tegra_phy_xusb_utmi_port_reset( + xudc->curr_utmi_phy); + if (ret == 1) + xudc->wait_for_sec_prc = 0; + } + } + + spin_unlock_irqrestore(&xudc->lock, flags); +} + +static dma_addr_t trb_virt_to_phys(struct tegra_xudc_ep *ep, + struct tegra_xudc_trb *trb) +{ + unsigned int index; + + index = trb - ep->transfer_ring; + + if (WARN_ON(index >= XUDC_TRANSFER_RING_SIZE)) + return 0; + + return (ep->transfer_ring_phys + index * sizeof(*trb)); +} + +static struct tegra_xudc_trb *trb_phys_to_virt(struct tegra_xudc_ep *ep, + dma_addr_t addr) +{ + struct tegra_xudc_trb *trb; + unsigned int index; + + index = (addr - ep->transfer_ring_phys) / sizeof(*trb); + + if (WARN_ON(index >= XUDC_TRANSFER_RING_SIZE)) + return NULL; + + trb = &ep->transfer_ring[index]; + + return trb; +} + +static void ep_reload(struct tegra_xudc *xudc, unsigned int ep) +{ + xudc_writel(xudc, BIT(ep), EP_RELOAD); + xudc_readl_poll(xudc, EP_RELOAD, BIT(ep), 0); +} + +static void ep_pause(struct tegra_xudc *xudc, unsigned int ep) +{ + u32 val; + + val = xudc_readl(xudc, EP_PAUSE); + if (val & BIT(ep)) + return; + val |= BIT(ep); + + xudc_writel(xudc, val, EP_PAUSE); + + xudc_readl_poll(xudc, EP_STCHG, BIT(ep), BIT(ep)); + + xudc_writel(xudc, BIT(ep), EP_STCHG); +} + +static void ep_unpause(struct tegra_xudc *xudc, unsigned int ep) +{ + u32 val; + + val = xudc_readl(xudc, EP_PAUSE); + if (!(val & BIT(ep))) + return; + val &= ~BIT(ep); + + xudc_writel(xudc, val, EP_PAUSE); + + xudc_readl_poll(xudc, EP_STCHG, BIT(ep), BIT(ep)); + + xudc_writel(xudc, BIT(ep), EP_STCHG); +} + +static void ep_unpause_all(struct tegra_xudc *xudc) +{ + u32 val; + + val = xudc_readl(xudc, EP_PAUSE); + + xudc_writel(xudc, 0, EP_PAUSE); + + xudc_readl_poll(xudc, EP_STCHG, val, val); + + xudc_writel(xudc, val, EP_STCHG); +} + +static void ep_halt(struct tegra_xudc *xudc, unsigned int ep) +{ + u32 val; + + val = xudc_readl(xudc, EP_HALT); + if (val & BIT(ep)) + return; + val |= BIT(ep); + xudc_writel(xudc, val, EP_HALT); + + xudc_readl_poll(xudc, EP_STCHG, BIT(ep), BIT(ep)); + + xudc_writel(xudc, BIT(ep), EP_STCHG); +} + +static void ep_unhalt(struct tegra_xudc *xudc, unsigned int ep) +{ + u32 val; + + val = xudc_readl(xudc, EP_HALT); + if (!(val & BIT(ep))) + return; + val &= ~BIT(ep); + xudc_writel(xudc, val, EP_HALT); + + xudc_readl_poll(xudc, EP_STCHG, BIT(ep), BIT(ep)); + + xudc_writel(xudc, BIT(ep), EP_STCHG); +} + +static void ep_unhalt_all(struct tegra_xudc *xudc) +{ + u32 val; + + val = xudc_readl(xudc, EP_HALT); + if (!val) + return; + xudc_writel(xudc, 0, EP_HALT); + + xudc_readl_poll(xudc, EP_STCHG, val, val); + + xudc_writel(xudc, val, EP_STCHG); +} + +static void ep_wait_for_stopped(struct tegra_xudc *xudc, unsigned int ep) +{ + xudc_readl_poll(xudc, EP_STOPPED, BIT(ep), BIT(ep)); + xudc_writel(xudc, BIT(ep), EP_STOPPED); +} + +static void ep_wait_for_inactive(struct tegra_xudc *xudc, unsigned int ep) +{ + xudc_readl_poll(xudc, EP_THREAD_ACTIVE, BIT(ep), 0); +} + +static void tegra_xudc_req_done(struct tegra_xudc_ep *ep, + struct tegra_xudc_request *req, int status) +{ + struct tegra_xudc *xudc = ep->xudc; + + dev_dbg(xudc->dev, "completing request %p on EP %u with status %d\n", + req, ep->index, status); + + if (likely(req->usb_req.status == -EINPROGRESS)) + req->usb_req.status = status; + + list_del_init(&req->list); + + if (usb_endpoint_xfer_control(ep->desc)) { + usb_gadget_unmap_request(&xudc->gadget, &req->usb_req, + (xudc->setup_state == + DATA_STAGE_XFER)); + } else { + usb_gadget_unmap_request(&xudc->gadget, &req->usb_req, + usb_endpoint_dir_in(ep->desc)); + } + + spin_unlock(&xudc->lock); + usb_gadget_giveback_request(&ep->usb_ep, &req->usb_req); + spin_lock(&xudc->lock); +} + +static void tegra_xudc_ep_nuke(struct tegra_xudc_ep *ep, int status) +{ + struct tegra_xudc_request *req; + + while (!list_empty(&ep->queue)) { + req = list_first_entry(&ep->queue, struct tegra_xudc_request, + list); + tegra_xudc_req_done(ep, req, status); + } +} + +static unsigned int ep_available_trbs(struct tegra_xudc_ep *ep) +{ + if (ep->ring_full) + return 0; + + if (ep->deq_ptr > ep->enq_ptr) + return ep->deq_ptr - ep->enq_ptr - 1; + + return XUDC_TRANSFER_RING_SIZE - (ep->enq_ptr - ep->deq_ptr) - 2; +} + +static void tegra_xudc_queue_one_trb(struct tegra_xudc_ep *ep, + struct tegra_xudc_request *req, + struct tegra_xudc_trb *trb, + bool ioc) +{ + struct tegra_xudc *xudc = ep->xudc; + dma_addr_t buf_addr; + size_t len; + + len = min_t(size_t, XUDC_TRB_MAX_BUFFER_SIZE, req->usb_req.length - + req->buf_queued); + if (len > 0) + buf_addr = req->usb_req.dma + req->buf_queued; + else + buf_addr = 0; + + trb_write_data_ptr(trb, buf_addr); + + trb_write_transfer_len(trb, len); + trb_write_td_size(trb, req->trbs_needed - req->trbs_queued - 1); + + if (req->trbs_queued == req->trbs_needed - 1 || + (req->need_zlp && req->trbs_queued == req->trbs_needed - 2)) + trb_write_chain(trb, 0); + else + trb_write_chain(trb, 1); + + trb_write_ioc(trb, ioc); + + if (usb_endpoint_dir_out(ep->desc) || + (usb_endpoint_xfer_control(ep->desc) && + (xudc->setup_state == DATA_STAGE_RECV))) + trb_write_isp(trb, 1); + else + trb_write_isp(trb, 0); + + if (usb_endpoint_xfer_control(ep->desc)) { + if (xudc->setup_state == DATA_STAGE_XFER || + xudc->setup_state == DATA_STAGE_RECV) + trb_write_type(trb, TRB_TYPE_DATA_STAGE); + else + trb_write_type(trb, TRB_TYPE_STATUS_STAGE); + + if (xudc->setup_state == DATA_STAGE_XFER || + xudc->setup_state == STATUS_STAGE_XFER) + trb_write_data_stage_dir(trb, 1); + else + trb_write_data_stage_dir(trb, 0); + } else if (usb_endpoint_xfer_isoc(ep->desc)) { + trb_write_type(trb, TRB_TYPE_ISOCH); + trb_write_sia(trb, 1); + trb_write_frame_id(trb, 0); + trb_write_tlbpc(trb, 0); + } else if (usb_ss_max_streams(ep->comp_desc)) { + trb_write_type(trb, TRB_TYPE_STREAM); + trb_write_stream_id(trb, req->usb_req.stream_id); + } else { + trb_write_type(trb, TRB_TYPE_NORMAL); + trb_write_stream_id(trb, 0); + } + + trb_write_cycle(trb, ep->pcs); + + req->trbs_queued++; + req->buf_queued += len; + + dump_trb(xudc, "TRANSFER", trb); +} + +static unsigned int tegra_xudc_queue_trbs(struct tegra_xudc_ep *ep, + struct tegra_xudc_request *req) +{ + unsigned int i, count, available; + bool wait_td = false; + + available = ep_available_trbs(ep); + count = req->trbs_needed - req->trbs_queued; + if (available < count) { + count = available; + ep->ring_full = true; + } + + /* + * To generate zero-length packet on USB bus, SW needs schedule a + * standalone zero-length TD. According to HW's behavior, SW needs + * to schedule TDs in different ways for different endpoint types. + * + * For control endpoint: + * - Data stage TD (IOC = 1, CH = 0) + * - Ring doorbell and wait transfer event + * - Data stage TD for ZLP (IOC = 1, CH = 0) + * - Ring doorbell + * + * For bulk and interrupt endpoints: + * - Normal transfer TD (IOC = 0, CH = 0) + * - Normal transfer TD for ZLP (IOC = 1, CH = 0) + * - Ring doorbell + */ + + if (req->need_zlp && usb_endpoint_xfer_control(ep->desc) && count > 1) + wait_td = true; + + if (!req->first_trb) + req->first_trb = &ep->transfer_ring[ep->enq_ptr]; + + for (i = 0; i < count; i++) { + struct tegra_xudc_trb *trb = &ep->transfer_ring[ep->enq_ptr]; + bool ioc = false; + + if ((i == count - 1) || (wait_td && i == count - 2)) + ioc = true; + + tegra_xudc_queue_one_trb(ep, req, trb, ioc); + req->last_trb = trb; + + ep->enq_ptr++; + if (ep->enq_ptr == XUDC_TRANSFER_RING_SIZE - 1) { + trb = &ep->transfer_ring[ep->enq_ptr]; + trb_write_cycle(trb, ep->pcs); + ep->pcs = !ep->pcs; + ep->enq_ptr = 0; + } + + if (ioc) + break; + } + + return count; +} + +static void tegra_xudc_ep_ring_doorbell(struct tegra_xudc_ep *ep) +{ + struct tegra_xudc *xudc = ep->xudc; + u32 val; + + if (list_empty(&ep->queue)) + return; + + val = DB_TARGET(ep->index); + if (usb_endpoint_xfer_control(ep->desc)) { + val |= DB_STREAMID(xudc->setup_seq_num); + } else if (usb_ss_max_streams(ep->comp_desc) > 0) { + struct tegra_xudc_request *req; + + /* Don't ring doorbell if the stream has been rejected. */ + if (ep->stream_rejected) + return; + + req = list_first_entry(&ep->queue, struct tegra_xudc_request, + list); + val |= DB_STREAMID(req->usb_req.stream_id); + } + + dev_dbg(xudc->dev, "ring doorbell: %#x\n", val); + xudc_writel(xudc, val, DB); +} + +static void tegra_xudc_ep_kick_queue(struct tegra_xudc_ep *ep) +{ + struct tegra_xudc_request *req; + bool trbs_queued = false; + + list_for_each_entry(req, &ep->queue, list) { + if (ep->ring_full) + break; + + if (tegra_xudc_queue_trbs(ep, req) > 0) + trbs_queued = true; + } + + if (trbs_queued) + tegra_xudc_ep_ring_doorbell(ep); +} + +static int +__tegra_xudc_ep_queue(struct tegra_xudc_ep *ep, struct tegra_xudc_request *req) +{ + struct tegra_xudc *xudc = ep->xudc; + int err; + + if (usb_endpoint_xfer_control(ep->desc) && !list_empty(&ep->queue)) { + dev_err(xudc->dev, "control EP has pending transfers\n"); + return -EINVAL; + } + + if (usb_endpoint_xfer_control(ep->desc)) { + err = usb_gadget_map_request(&xudc->gadget, &req->usb_req, + (xudc->setup_state == + DATA_STAGE_XFER)); + } else { + err = usb_gadget_map_request(&xudc->gadget, &req->usb_req, + usb_endpoint_dir_in(ep->desc)); + } + + if (err < 0) { + dev_err(xudc->dev, "failed to map request: %d\n", err); + return err; + } + + req->first_trb = NULL; + req->last_trb = NULL; + req->buf_queued = 0; + req->trbs_queued = 0; + req->need_zlp = false; + req->trbs_needed = DIV_ROUND_UP(req->usb_req.length, + XUDC_TRB_MAX_BUFFER_SIZE); + if (req->usb_req.length == 0) + req->trbs_needed++; + + if (!usb_endpoint_xfer_isoc(ep->desc) && + req->usb_req.zero && req->usb_req.length && + ((req->usb_req.length % ep->usb_ep.maxpacket) == 0)) { + req->trbs_needed++; + req->need_zlp = true; + } + + req->usb_req.status = -EINPROGRESS; + req->usb_req.actual = 0; + + list_add_tail(&req->list, &ep->queue); + + tegra_xudc_ep_kick_queue(ep); + + return 0; +} + +static int +tegra_xudc_ep_queue(struct usb_ep *usb_ep, struct usb_request *usb_req, + gfp_t gfp) +{ + struct tegra_xudc_request *req; + struct tegra_xudc_ep *ep; + struct tegra_xudc *xudc; + unsigned long flags; + int ret; + + if (!usb_ep || !usb_req) + return -EINVAL; + + ep = to_xudc_ep(usb_ep); + req = to_xudc_req(usb_req); + xudc = ep->xudc; + + spin_lock_irqsave(&xudc->lock, flags); + if (xudc->powergated || !ep->desc) { + ret = -ESHUTDOWN; + goto unlock; + } + + ret = __tegra_xudc_ep_queue(ep, req); +unlock: + spin_unlock_irqrestore(&xudc->lock, flags); + + return ret; +} + +static void squeeze_transfer_ring(struct tegra_xudc_ep *ep, + struct tegra_xudc_request *req) +{ + struct tegra_xudc_trb *trb = req->first_trb; + bool pcs_enq = trb_read_cycle(trb); + bool pcs; + + /* + * Clear out all the TRBs part of or after the cancelled request, + * and must correct trb cycle bit to the last un-enqueued state. + */ + while (trb != &ep->transfer_ring[ep->enq_ptr]) { + pcs = trb_read_cycle(trb); + memset(trb, 0, sizeof(*trb)); + trb_write_cycle(trb, !pcs); + trb++; + + if (trb_read_type(trb) == TRB_TYPE_LINK) + trb = ep->transfer_ring; + } + + /* Requests will be re-queued at the start of the cancelled request. */ + ep->enq_ptr = req->first_trb - ep->transfer_ring; + /* + * Retrieve the correct cycle bit state from the first trb of + * the cancelled request. + */ + ep->pcs = pcs_enq; + ep->ring_full = false; + list_for_each_entry_continue(req, &ep->queue, list) { + req->usb_req.status = -EINPROGRESS; + req->usb_req.actual = 0; + + req->first_trb = NULL; + req->last_trb = NULL; + req->buf_queued = 0; + req->trbs_queued = 0; + } +} + +/* + * Determine if the given TRB is in the range [first trb, last trb] for the + * given request. + */ +static bool trb_in_request(struct tegra_xudc_ep *ep, + struct tegra_xudc_request *req, + struct tegra_xudc_trb *trb) +{ + dev_dbg(ep->xudc->dev, "%s: request %p -> %p; trb %p\n", __func__, + req->first_trb, req->last_trb, trb); + + if (trb >= req->first_trb && (trb <= req->last_trb || + req->last_trb < req->first_trb)) + return true; + + if (trb < req->first_trb && trb <= req->last_trb && + req->last_trb < req->first_trb) + return true; + + return false; +} + +/* + * Determine if the given TRB is in the range [EP enqueue pointer, first TRB) + * for the given endpoint and request. + */ +static bool trb_before_request(struct tegra_xudc_ep *ep, + struct tegra_xudc_request *req, + struct tegra_xudc_trb *trb) +{ + struct tegra_xudc_trb *enq_trb = &ep->transfer_ring[ep->enq_ptr]; + + dev_dbg(ep->xudc->dev, "%s: request %p -> %p; enq ptr: %p; trb %p\n", + __func__, req->first_trb, req->last_trb, enq_trb, trb); + + if (trb < req->first_trb && (enq_trb <= trb || + req->first_trb < enq_trb)) + return true; + + if (trb > req->first_trb && req->first_trb < enq_trb && enq_trb <= trb) + return true; + + return false; +} + +static int +__tegra_xudc_ep_dequeue(struct tegra_xudc_ep *ep, + struct tegra_xudc_request *req) +{ + struct tegra_xudc *xudc = ep->xudc; + struct tegra_xudc_request *r = NULL, *iter; + struct tegra_xudc_trb *deq_trb; + bool busy, kick_queue = false; + int ret = 0; + + /* Make sure the request is actually queued to this endpoint. */ + list_for_each_entry(iter, &ep->queue, list) { + if (iter != req) + continue; + r = iter; + break; + } + + if (!r) + return -EINVAL; + + /* Request hasn't been queued in the transfer ring yet. */ + if (!req->trbs_queued) { + tegra_xudc_req_done(ep, req, -ECONNRESET); + return 0; + } + + /* Halt DMA for this endpoint. */ + if (ep_ctx_read_state(ep->context) == EP_STATE_RUNNING) { + ep_pause(xudc, ep->index); + ep_wait_for_inactive(xudc, ep->index); + } + + deq_trb = trb_phys_to_virt(ep, ep_ctx_read_deq_ptr(ep->context)); + /* Is the hardware processing the TRB at the dequeue pointer? */ + busy = (trb_read_cycle(deq_trb) == ep_ctx_read_dcs(ep->context)); + + if (trb_in_request(ep, req, deq_trb) && busy) { + /* + * Request has been partially completed or it hasn't + * started processing yet. + */ + dma_addr_t deq_ptr; + + squeeze_transfer_ring(ep, req); + + req->usb_req.actual = ep_ctx_read_edtla(ep->context); + tegra_xudc_req_done(ep, req, -ECONNRESET); + kick_queue = true; + + /* EDTLA is > 0: request has been partially completed */ + if (req->usb_req.actual > 0) { + /* + * Abort the pending transfer and update the dequeue + * pointer + */ + ep_ctx_write_edtla(ep->context, 0); + ep_ctx_write_partial_td(ep->context, 0); + ep_ctx_write_data_offset(ep->context, 0); + + deq_ptr = trb_virt_to_phys(ep, + &ep->transfer_ring[ep->enq_ptr]); + + if (dma_mapping_error(xudc->dev, deq_ptr)) { + ret = -EINVAL; + } else { + ep_ctx_write_deq_ptr(ep->context, deq_ptr); + ep_ctx_write_dcs(ep->context, ep->pcs); + ep_reload(xudc, ep->index); + } + } + } else if (trb_before_request(ep, req, deq_trb) && busy) { + /* Request hasn't started processing yet. */ + squeeze_transfer_ring(ep, req); + + tegra_xudc_req_done(ep, req, -ECONNRESET); + kick_queue = true; + } else { + /* + * Request has completed, but we haven't processed the + * completion event yet. + */ + tegra_xudc_req_done(ep, req, -ECONNRESET); + ret = -EINVAL; + } + + /* Resume the endpoint. */ + ep_unpause(xudc, ep->index); + + if (kick_queue) + tegra_xudc_ep_kick_queue(ep); + + return ret; +} + +static int +tegra_xudc_ep_dequeue(struct usb_ep *usb_ep, struct usb_request *usb_req) +{ + struct tegra_xudc_request *req; + struct tegra_xudc_ep *ep; + struct tegra_xudc *xudc; + unsigned long flags; + int ret; + + if (!usb_ep || !usb_req) + return -EINVAL; + + ep = to_xudc_ep(usb_ep); + req = to_xudc_req(usb_req); + xudc = ep->xudc; + + spin_lock_irqsave(&xudc->lock, flags); + + if (xudc->powergated || !ep->desc) { + ret = -ESHUTDOWN; + goto unlock; + } + + ret = __tegra_xudc_ep_dequeue(ep, req); +unlock: + spin_unlock_irqrestore(&xudc->lock, flags); + + return ret; +} + +static int __tegra_xudc_ep_set_halt(struct tegra_xudc_ep *ep, bool halt) +{ + struct tegra_xudc *xudc = ep->xudc; + + if (!ep->desc) + return -EINVAL; + + if (usb_endpoint_xfer_isoc(ep->desc)) { + dev_err(xudc->dev, "can't halt isochronous EP\n"); + return -ENOTSUPP; + } + + if (halt) { + ep_halt(xudc, ep->index); + } else { + ep_ctx_write_state(ep->context, EP_STATE_DISABLED); + + ep_reload(xudc, ep->index); + + ep_ctx_write_state(ep->context, EP_STATE_RUNNING); + ep_ctx_write_rsvd(ep->context, 0); + ep_ctx_write_partial_td(ep->context, 0); + ep_ctx_write_splitxstate(ep->context, 0); + ep_ctx_write_seq_num(ep->context, 0); + + ep_reload(xudc, ep->index); + ep_unpause(xudc, ep->index); + ep_unhalt(xudc, ep->index); + + tegra_xudc_ep_ring_doorbell(ep); + } + + return 0; +} + +static int tegra_xudc_ep_set_halt(struct usb_ep *usb_ep, int value) +{ + struct tegra_xudc_ep *ep; + struct tegra_xudc *xudc; + unsigned long flags; + int ret; + + if (!usb_ep) + return -EINVAL; + + ep = to_xudc_ep(usb_ep); + xudc = ep->xudc; + + spin_lock_irqsave(&xudc->lock, flags); + if (xudc->powergated) { + ret = -ESHUTDOWN; + goto unlock; + } + + if (value && usb_endpoint_dir_in(ep->desc) && + !list_empty(&ep->queue)) { + dev_err(xudc->dev, "can't halt EP with requests pending\n"); + ret = -EAGAIN; + goto unlock; + } + + ret = __tegra_xudc_ep_set_halt(ep, value); +unlock: + spin_unlock_irqrestore(&xudc->lock, flags); + + return ret; +} + +static void tegra_xudc_ep_context_setup(struct tegra_xudc_ep *ep) +{ + const struct usb_endpoint_descriptor *desc = ep->desc; + const struct usb_ss_ep_comp_descriptor *comp_desc = ep->comp_desc; + struct tegra_xudc *xudc = ep->xudc; + u16 maxpacket, maxburst = 0, esit = 0; + u32 val; + + maxpacket = usb_endpoint_maxp(desc); + if (xudc->gadget.speed == USB_SPEED_SUPER) { + if (!usb_endpoint_xfer_control(desc)) + maxburst = comp_desc->bMaxBurst; + + if (usb_endpoint_xfer_int(desc) || usb_endpoint_xfer_isoc(desc)) + esit = le16_to_cpu(comp_desc->wBytesPerInterval); + } else if ((xudc->gadget.speed < USB_SPEED_SUPER) && + (usb_endpoint_xfer_int(desc) || + usb_endpoint_xfer_isoc(desc))) { + if (xudc->gadget.speed == USB_SPEED_HIGH) { + maxburst = usb_endpoint_maxp_mult(desc) - 1; + if (maxburst == 0x3) { + dev_warn(xudc->dev, + "invalid endpoint maxburst\n"); + maxburst = 0x2; + } + } + esit = maxpacket * (maxburst + 1); + } + + memset(ep->context, 0, sizeof(*ep->context)); + + ep_ctx_write_state(ep->context, EP_STATE_RUNNING); + ep_ctx_write_interval(ep->context, desc->bInterval); + if (xudc->gadget.speed == USB_SPEED_SUPER) { + if (usb_endpoint_xfer_isoc(desc)) { + ep_ctx_write_mult(ep->context, + comp_desc->bmAttributes & 0x3); + } + + if (usb_endpoint_xfer_bulk(desc)) { + ep_ctx_write_max_pstreams(ep->context, + comp_desc->bmAttributes & + 0x1f); + ep_ctx_write_lsa(ep->context, 1); + } + } + + if (!usb_endpoint_xfer_control(desc) && usb_endpoint_dir_out(desc)) + val = usb_endpoint_type(desc); + else + val = usb_endpoint_type(desc) + EP_TYPE_CONTROL; + + ep_ctx_write_type(ep->context, val); + ep_ctx_write_cerr(ep->context, 0x3); + ep_ctx_write_max_packet_size(ep->context, maxpacket); + ep_ctx_write_max_burst_size(ep->context, maxburst); + + ep_ctx_write_deq_ptr(ep->context, ep->transfer_ring_phys); + ep_ctx_write_dcs(ep->context, ep->pcs); + + /* Select a reasonable average TRB length based on endpoint type. */ + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_CONTROL: + val = 8; + break; + case USB_ENDPOINT_XFER_INT: + val = 1024; + break; + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_ISOC: + default: + val = 3072; + break; + } + + ep_ctx_write_avg_trb_len(ep->context, val); + ep_ctx_write_max_esit_payload(ep->context, esit); + + ep_ctx_write_cerrcnt(ep->context, 0x3); +} + +static void setup_link_trb(struct tegra_xudc_ep *ep, + struct tegra_xudc_trb *trb) +{ + trb_write_data_ptr(trb, ep->transfer_ring_phys); + trb_write_type(trb, TRB_TYPE_LINK); + trb_write_toggle_cycle(trb, 1); +} + +static int __tegra_xudc_ep_disable(struct tegra_xudc_ep *ep) +{ + struct tegra_xudc *xudc = ep->xudc; + + if (ep_ctx_read_state(ep->context) == EP_STATE_DISABLED) { + dev_err(xudc->dev, "endpoint %u already disabled\n", + ep->index); + return -EINVAL; + } + + ep_ctx_write_state(ep->context, EP_STATE_DISABLED); + + ep_reload(xudc, ep->index); + + tegra_xudc_ep_nuke(ep, -ESHUTDOWN); + + xudc->nr_enabled_eps--; + if (usb_endpoint_xfer_isoc(ep->desc)) + xudc->nr_isoch_eps--; + + ep->desc = NULL; + ep->comp_desc = NULL; + + memset(ep->context, 0, sizeof(*ep->context)); + + ep_unpause(xudc, ep->index); + ep_unhalt(xudc, ep->index); + if (xudc_readl(xudc, EP_STOPPED) & BIT(ep->index)) + xudc_writel(xudc, BIT(ep->index), EP_STOPPED); + + /* + * If this is the last endpoint disabled in a de-configure request, + * switch back to address state. + */ + if ((xudc->device_state == USB_STATE_CONFIGURED) && + (xudc->nr_enabled_eps == 1)) { + u32 val; + + xudc->device_state = USB_STATE_ADDRESS; + usb_gadget_set_state(&xudc->gadget, xudc->device_state); + + val = xudc_readl(xudc, CTRL); + val &= ~CTRL_RUN; + xudc_writel(xudc, val, CTRL); + + val = xudc_readl(xudc, ST); + if (val & ST_RC) + xudc_writel(xudc, ST_RC, ST); + } + + dev_info(xudc->dev, "ep %u disabled\n", ep->index); + + return 0; +} + +static int tegra_xudc_ep_disable(struct usb_ep *usb_ep) +{ + struct tegra_xudc_ep *ep; + struct tegra_xudc *xudc; + unsigned long flags; + int ret; + + if (!usb_ep) + return -EINVAL; + + ep = to_xudc_ep(usb_ep); + xudc = ep->xudc; + + spin_lock_irqsave(&xudc->lock, flags); + if (xudc->powergated) { + ret = -ESHUTDOWN; + goto unlock; + } + + ret = __tegra_xudc_ep_disable(ep); +unlock: + spin_unlock_irqrestore(&xudc->lock, flags); + + return ret; +} + +static int __tegra_xudc_ep_enable(struct tegra_xudc_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct tegra_xudc *xudc = ep->xudc; + unsigned int i; + u32 val; + + if (xudc->gadget.speed == USB_SPEED_SUPER && + !usb_endpoint_xfer_control(desc) && !ep->usb_ep.comp_desc) + return -EINVAL; + + /* Disable the EP if it is not disabled */ + if (ep_ctx_read_state(ep->context) != EP_STATE_DISABLED) + __tegra_xudc_ep_disable(ep); + + ep->desc = desc; + ep->comp_desc = ep->usb_ep.comp_desc; + + if (usb_endpoint_xfer_isoc(desc)) { + if (xudc->nr_isoch_eps > XUDC_MAX_ISOCH_EPS) { + dev_err(xudc->dev, "too many isochronous endpoints\n"); + return -EBUSY; + } + xudc->nr_isoch_eps++; + } + + memset(ep->transfer_ring, 0, XUDC_TRANSFER_RING_SIZE * + sizeof(*ep->transfer_ring)); + setup_link_trb(ep, &ep->transfer_ring[XUDC_TRANSFER_RING_SIZE - 1]); + + ep->enq_ptr = 0; + ep->deq_ptr = 0; + ep->pcs = true; + ep->ring_full = false; + xudc->nr_enabled_eps++; + + tegra_xudc_ep_context_setup(ep); + + /* + * No need to reload and un-halt EP0. This will be done automatically + * once a valid SETUP packet is received. + */ + if (usb_endpoint_xfer_control(desc)) + goto out; + + /* + * Transition to configured state once the first non-control + * endpoint is enabled. + */ + if (xudc->device_state == USB_STATE_ADDRESS) { + val = xudc_readl(xudc, CTRL); + val |= CTRL_RUN; + xudc_writel(xudc, val, CTRL); + + xudc->device_state = USB_STATE_CONFIGURED; + usb_gadget_set_state(&xudc->gadget, xudc->device_state); + } + + if (usb_endpoint_xfer_isoc(desc)) { + /* + * Pause all bulk endpoints when enabling an isoch endpoint + * to ensure the isoch endpoint is allocated enough bandwidth. + */ + for (i = 0; i < ARRAY_SIZE(xudc->ep); i++) { + if (xudc->ep[i].desc && + usb_endpoint_xfer_bulk(xudc->ep[i].desc)) + ep_pause(xudc, i); + } + } + + ep_reload(xudc, ep->index); + ep_unpause(xudc, ep->index); + ep_unhalt(xudc, ep->index); + + if (usb_endpoint_xfer_isoc(desc)) { + for (i = 0; i < ARRAY_SIZE(xudc->ep); i++) { + if (xudc->ep[i].desc && + usb_endpoint_xfer_bulk(xudc->ep[i].desc)) + ep_unpause(xudc, i); + } + } + +out: + dev_info(xudc->dev, "EP %u (type: %s, dir: %s) enabled\n", ep->index, + usb_ep_type_string(usb_endpoint_type(ep->desc)), + usb_endpoint_dir_in(ep->desc) ? "in" : "out"); + + return 0; +} + +static int tegra_xudc_ep_enable(struct usb_ep *usb_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct tegra_xudc_ep *ep; + struct tegra_xudc *xudc; + unsigned long flags; + int ret; + + if (!usb_ep || !desc || (desc->bDescriptorType != USB_DT_ENDPOINT)) + return -EINVAL; + + ep = to_xudc_ep(usb_ep); + xudc = ep->xudc; + + spin_lock_irqsave(&xudc->lock, flags); + if (xudc->powergated) { + ret = -ESHUTDOWN; + goto unlock; + } + + ret = __tegra_xudc_ep_enable(ep, desc); +unlock: + spin_unlock_irqrestore(&xudc->lock, flags); + + return ret; +} + +static struct usb_request * +tegra_xudc_ep_alloc_request(struct usb_ep *usb_ep, gfp_t gfp) +{ + struct tegra_xudc_request *req; + + req = kzalloc_obj(*req, gfp); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->list); + + return &req->usb_req; +} + +static void tegra_xudc_ep_free_request(struct usb_ep *usb_ep, + struct usb_request *usb_req) +{ + struct tegra_xudc_request *req = to_xudc_req(usb_req); + + kfree(req); +} + +static const struct usb_ep_ops tegra_xudc_ep_ops = { + .enable = tegra_xudc_ep_enable, + .disable = tegra_xudc_ep_disable, + .alloc_request = tegra_xudc_ep_alloc_request, + .free_request = tegra_xudc_ep_free_request, + .queue = tegra_xudc_ep_queue, + .dequeue = tegra_xudc_ep_dequeue, + .set_halt = tegra_xudc_ep_set_halt, +}; + +static int tegra_xudc_ep0_enable(struct usb_ep *usb_ep, + const struct usb_endpoint_descriptor *desc) +{ + return -EBUSY; +} + +static int tegra_xudc_ep0_disable(struct usb_ep *usb_ep) +{ + return -EBUSY; +} + +static const struct usb_ep_ops tegra_xudc_ep0_ops = { + .enable = tegra_xudc_ep0_enable, + .disable = tegra_xudc_ep0_disable, + .alloc_request = tegra_xudc_ep_alloc_request, + .free_request = tegra_xudc_ep_free_request, + .queue = tegra_xudc_ep_queue, + .dequeue = tegra_xudc_ep_dequeue, + .set_halt = tegra_xudc_ep_set_halt, +}; + +static int tegra_xudc_gadget_get_frame(struct usb_gadget *gadget) +{ + struct tegra_xudc *xudc = to_xudc(gadget); + unsigned long flags; + int ret; + + spin_lock_irqsave(&xudc->lock, flags); + if (xudc->powergated) { + ret = -ESHUTDOWN; + goto unlock; + } + + ret = (xudc_readl(xudc, MFINDEX) & MFINDEX_FRAME_MASK) >> + MFINDEX_FRAME_SHIFT; +unlock: + spin_unlock_irqrestore(&xudc->lock, flags); + + return ret; +} + +static void tegra_xudc_resume_device_state(struct tegra_xudc *xudc) +{ + unsigned int i; + u32 val; + + ep_unpause_all(xudc); + + /* Direct link to U0. */ + val = xudc_readl(xudc, PORTSC); + if (((val & PORTSC_PLS_MASK) >> PORTSC_PLS_SHIFT) != PORTSC_PLS_U0) { + val &= ~(PORTSC_CHANGE_MASK | PORTSC_PLS_MASK); + val |= PORTSC_LWS | PORTSC_PLS(PORTSC_PLS_U0); + xudc_writel(xudc, val, PORTSC); + } + + if (xudc->device_state == USB_STATE_SUSPENDED) { + xudc->device_state = xudc->resume_state; + usb_gadget_set_state(&xudc->gadget, xudc->device_state); + xudc->resume_state = 0; + } + + /* + * Doorbells may be dropped if they are sent too soon (< ~200ns) + * after unpausing the endpoint. Wait for 500ns just to be safe. + */ + ndelay(500); + for (i = 0; i < ARRAY_SIZE(xudc->ep); i++) + tegra_xudc_ep_ring_doorbell(&xudc->ep[i]); +} + +static int tegra_xudc_gadget_wakeup(struct usb_gadget *gadget) +{ + struct tegra_xudc *xudc = to_xudc(gadget); + unsigned long flags; + int ret = 0; + u32 val; + + spin_lock_irqsave(&xudc->lock, flags); + + if (xudc->powergated) { + ret = -ESHUTDOWN; + goto unlock; + } + val = xudc_readl(xudc, PORTPM); + dev_dbg(xudc->dev, "%s: PORTPM=%#x, speed=%x\n", __func__, + val, gadget->speed); + + if (((xudc->gadget.speed <= USB_SPEED_HIGH) && + (val & PORTPM_RWE)) || + ((xudc->gadget.speed == USB_SPEED_SUPER) && + (val & PORTPM_FRWE))) { + tegra_xudc_resume_device_state(xudc); + + /* Send Device Notification packet. */ + if (xudc->gadget.speed == USB_SPEED_SUPER) { + val = DEVNOTIF_LO_TYPE(DEVNOTIF_LO_TYPE_FUNCTION_WAKE) + | DEVNOTIF_LO_TRIG; + xudc_writel(xudc, 0, DEVNOTIF_HI); + xudc_writel(xudc, val, DEVNOTIF_LO); + } + } + +unlock: + dev_dbg(xudc->dev, "%s: ret value is %d", __func__, ret); + spin_unlock_irqrestore(&xudc->lock, flags); + + return ret; +} + +static int tegra_xudc_gadget_pullup(struct usb_gadget *gadget, int is_on) +{ + struct tegra_xudc *xudc = to_xudc(gadget); + unsigned long flags; + u32 val; + + pm_runtime_get_sync(xudc->dev); + + spin_lock_irqsave(&xudc->lock, flags); + + if (is_on != xudc->pullup) { + val = xudc_readl(xudc, CTRL); + if (is_on) + val |= CTRL_ENABLE; + else + val &= ~CTRL_ENABLE; + xudc_writel(xudc, val, CTRL); + } + + xudc->pullup = is_on; + dev_dbg(xudc->dev, "%s: pullup:%d", __func__, is_on); + + spin_unlock_irqrestore(&xudc->lock, flags); + + pm_runtime_put(xudc->dev); + + return 0; +} + +static int tegra_xudc_gadget_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct tegra_xudc *xudc = to_xudc(gadget); + unsigned long flags; + u32 val; + int ret; + unsigned int i; + + if (!driver) + return -EINVAL; + + pm_runtime_get_sync(xudc->dev); + + spin_lock_irqsave(&xudc->lock, flags); + + if (xudc->driver) { + ret = -EBUSY; + goto unlock; + } + + xudc->setup_state = WAIT_FOR_SETUP; + xudc->device_state = USB_STATE_DEFAULT; + usb_gadget_set_state(&xudc->gadget, xudc->device_state); + + ret = __tegra_xudc_ep_enable(&xudc->ep[0], &tegra_xudc_ep0_desc); + if (ret < 0) + goto unlock; + + val = xudc_readl(xudc, CTRL); + val |= CTRL_IE | CTRL_LSE; + xudc_writel(xudc, val, CTRL); + + val = xudc_readl(xudc, PORTHALT); + val |= PORTHALT_STCHG_INTR_EN; + xudc_writel(xudc, val, PORTHALT); + + if (xudc->pullup) { + val = xudc_readl(xudc, CTRL); + val |= CTRL_ENABLE; + xudc_writel(xudc, val, CTRL); + } + + for (i = 0; i < xudc->soc->num_phys; i++) + if (xudc->usbphy[i]) + otg_set_peripheral(xudc->usbphy[i]->otg, gadget); + + xudc->driver = driver; +unlock: + dev_dbg(xudc->dev, "%s: ret value is %d", __func__, ret); + spin_unlock_irqrestore(&xudc->lock, flags); + + pm_runtime_put(xudc->dev); + + return ret; +} + +static int tegra_xudc_gadget_stop(struct usb_gadget *gadget) +{ + struct tegra_xudc *xudc = to_xudc(gadget); + unsigned long flags; + u32 val; + unsigned int i; + + pm_runtime_get_sync(xudc->dev); + + spin_lock_irqsave(&xudc->lock, flags); + + for (i = 0; i < xudc->soc->num_phys; i++) + if (xudc->usbphy[i]) + otg_set_peripheral(xudc->usbphy[i]->otg, NULL); + + val = xudc_readl(xudc, CTRL); + val &= ~(CTRL_IE | CTRL_ENABLE); + xudc_writel(xudc, val, CTRL); + + __tegra_xudc_ep_disable(&xudc->ep[0]); + + xudc->driver = NULL; + dev_dbg(xudc->dev, "Gadget stopped"); + + spin_unlock_irqrestore(&xudc->lock, flags); + + pm_runtime_put(xudc->dev); + + return 0; +} + +static int tegra_xudc_gadget_vbus_draw(struct usb_gadget *gadget, + unsigned int m_a) +{ + struct tegra_xudc *xudc = to_xudc(gadget); + + dev_dbg(xudc->dev, "%s: %u mA\n", __func__, m_a); + + if (xudc->curr_usbphy && xudc->curr_usbphy->chg_type == SDP_TYPE) + return usb_phy_set_power(xudc->curr_usbphy, m_a); + + return 0; +} + +static int tegra_xudc_set_selfpowered(struct usb_gadget *gadget, int is_on) +{ + struct tegra_xudc *xudc = to_xudc(gadget); + + dev_dbg(xudc->dev, "%s: %d\n", __func__, is_on); + xudc->selfpowered = !!is_on; + + return 0; +} + +static const struct usb_gadget_ops tegra_xudc_gadget_ops = { + .get_frame = tegra_xudc_gadget_get_frame, + .wakeup = tegra_xudc_gadget_wakeup, + .pullup = tegra_xudc_gadget_pullup, + .udc_start = tegra_xudc_gadget_start, + .udc_stop = tegra_xudc_gadget_stop, + .vbus_draw = tegra_xudc_gadget_vbus_draw, + .set_selfpowered = tegra_xudc_set_selfpowered, +}; + +static void no_op_complete(struct usb_ep *ep, struct usb_request *req) +{ +} + +static int +tegra_xudc_ep0_queue_status(struct tegra_xudc *xudc, + void (*cmpl)(struct usb_ep *, struct usb_request *)) +{ + xudc->ep0_req->usb_req.buf = NULL; + xudc->ep0_req->usb_req.dma = 0; + xudc->ep0_req->usb_req.length = 0; + xudc->ep0_req->usb_req.complete = cmpl; + xudc->ep0_req->usb_req.context = xudc; + + return __tegra_xudc_ep_queue(&xudc->ep[0], xudc->ep0_req); +} + +static int +tegra_xudc_ep0_queue_data(struct tegra_xudc *xudc, void *buf, size_t len, + void (*cmpl)(struct usb_ep *, struct usb_request *)) +{ + xudc->ep0_req->usb_req.buf = buf; + xudc->ep0_req->usb_req.length = len; + xudc->ep0_req->usb_req.complete = cmpl; + xudc->ep0_req->usb_req.context = xudc; + + return __tegra_xudc_ep_queue(&xudc->ep[0], xudc->ep0_req); +} + +static void tegra_xudc_ep0_req_done(struct tegra_xudc *xudc) +{ + switch (xudc->setup_state) { + case DATA_STAGE_XFER: + xudc->setup_state = STATUS_STAGE_RECV; + tegra_xudc_ep0_queue_status(xudc, no_op_complete); + break; + case DATA_STAGE_RECV: + xudc->setup_state = STATUS_STAGE_XFER; + tegra_xudc_ep0_queue_status(xudc, no_op_complete); + break; + default: + xudc->setup_state = WAIT_FOR_SETUP; + break; + } +} + +static int tegra_xudc_ep0_delegate_req(struct tegra_xudc *xudc, + struct usb_ctrlrequest *ctrl) +{ + int ret; + + spin_unlock(&xudc->lock); + ret = xudc->driver->setup(&xudc->gadget, ctrl); + spin_lock(&xudc->lock); + + return ret; +} + +static void set_feature_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct tegra_xudc *xudc = req->context; + + if (xudc->test_mode_pattern) { + xudc_writel(xudc, xudc->test_mode_pattern, PORT_TM); + xudc->test_mode_pattern = 0; + } +} + +static int tegra_xudc_ep0_set_feature(struct tegra_xudc *xudc, + struct usb_ctrlrequest *ctrl) +{ + bool set = (ctrl->bRequest == USB_REQ_SET_FEATURE); + u32 feature = le16_to_cpu(ctrl->wValue); + u32 index = le16_to_cpu(ctrl->wIndex); + u32 val, ep; + int ret; + + if (le16_to_cpu(ctrl->wLength) != 0) + return -EINVAL; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (feature) { + case USB_DEVICE_REMOTE_WAKEUP: + if ((xudc->gadget.speed == USB_SPEED_SUPER) || + (xudc->device_state == USB_STATE_DEFAULT)) + return -EINVAL; + + val = xudc_readl(xudc, PORTPM); + if (set) + val |= PORTPM_RWE; + else + val &= ~PORTPM_RWE; + + xudc_writel(xudc, val, PORTPM); + break; + case USB_DEVICE_U1_ENABLE: + case USB_DEVICE_U2_ENABLE: + if ((xudc->device_state != USB_STATE_CONFIGURED) || + (xudc->gadget.speed != USB_SPEED_SUPER)) + return -EINVAL; + + val = xudc_readl(xudc, PORTPM); + if ((feature == USB_DEVICE_U1_ENABLE) && + xudc->soc->u1_enable) { + if (set) + val |= PORTPM_U1E; + else + val &= ~PORTPM_U1E; + } + + if ((feature == USB_DEVICE_U2_ENABLE) && + xudc->soc->u2_enable) { + if (set) + val |= PORTPM_U2E; + else + val &= ~PORTPM_U2E; + } + + xudc_writel(xudc, val, PORTPM); + break; + case USB_DEVICE_TEST_MODE: + if (xudc->gadget.speed != USB_SPEED_HIGH) + return -EINVAL; + + if (!set) + return -EINVAL; + + xudc->test_mode_pattern = index >> 8; + break; + default: + return -EINVAL; + } + + break; + case USB_RECIP_INTERFACE: + if (xudc->device_state != USB_STATE_CONFIGURED) + return -EINVAL; + + switch (feature) { + case USB_INTRF_FUNC_SUSPEND: + if (set) { + val = xudc_readl(xudc, PORTPM); + + if (index & USB_INTRF_FUNC_SUSPEND_RW) + val |= PORTPM_FRWE; + else + val &= ~PORTPM_FRWE; + + xudc_writel(xudc, val, PORTPM); + } + + return tegra_xudc_ep0_delegate_req(xudc, ctrl); + default: + return -EINVAL; + } + + break; + case USB_RECIP_ENDPOINT: + ep = (index & USB_ENDPOINT_NUMBER_MASK) * 2 + + ((index & USB_DIR_IN) ? 1 : 0); + + if ((xudc->device_state == USB_STATE_DEFAULT) || + ((xudc->device_state == USB_STATE_ADDRESS) && + (index != 0))) + return -EINVAL; + + ret = __tegra_xudc_ep_set_halt(&xudc->ep[ep], set); + if (ret < 0) + return ret; + break; + default: + return -EINVAL; + } + + return tegra_xudc_ep0_queue_status(xudc, set_feature_complete); +} + +static int tegra_xudc_ep0_get_status(struct tegra_xudc *xudc, + struct usb_ctrlrequest *ctrl) +{ + struct tegra_xudc_ep_context *ep_ctx; + u32 val, ep, index = le16_to_cpu(ctrl->wIndex); + u16 status = 0; + + if (!(ctrl->bRequestType & USB_DIR_IN)) + return -EINVAL; + + if ((le16_to_cpu(ctrl->wValue) != 0) || + (le16_to_cpu(ctrl->wLength) != 2)) + return -EINVAL; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + val = xudc_readl(xudc, PORTPM); + + if (xudc->selfpowered) + status |= BIT(USB_DEVICE_SELF_POWERED); + + if ((xudc->gadget.speed < USB_SPEED_SUPER) && + (val & PORTPM_RWE)) + status |= BIT(USB_DEVICE_REMOTE_WAKEUP); + + if (xudc->gadget.speed == USB_SPEED_SUPER) { + if (val & PORTPM_U1E) + status |= BIT(USB_DEV_STAT_U1_ENABLED); + if (val & PORTPM_U2E) + status |= BIT(USB_DEV_STAT_U2_ENABLED); + } + break; + case USB_RECIP_INTERFACE: + if (xudc->gadget.speed == USB_SPEED_SUPER) { + status |= USB_INTRF_STAT_FUNC_RW_CAP; + val = xudc_readl(xudc, PORTPM); + if (val & PORTPM_FRWE) + status |= USB_INTRF_STAT_FUNC_RW; + } + break; + case USB_RECIP_ENDPOINT: + ep = (index & USB_ENDPOINT_NUMBER_MASK) * 2 + + ((index & USB_DIR_IN) ? 1 : 0); + ep_ctx = &xudc->ep_context[ep]; + + if ((xudc->device_state != USB_STATE_CONFIGURED) && + ((xudc->device_state != USB_STATE_ADDRESS) || (ep != 0))) + return -EINVAL; + + if (ep_ctx_read_state(ep_ctx) == EP_STATE_DISABLED) + return -EINVAL; + + if (xudc_readl(xudc, EP_HALT) & BIT(ep)) + status |= BIT(USB_ENDPOINT_HALT); + break; + default: + return -EINVAL; + } + + xudc->status_buf = cpu_to_le16(status); + return tegra_xudc_ep0_queue_data(xudc, &xudc->status_buf, + sizeof(xudc->status_buf), + no_op_complete); +} + +static void set_sel_complete(struct usb_ep *ep, struct usb_request *req) +{ + /* Nothing to do with SEL values */ +} + +static int tegra_xudc_ep0_set_sel(struct tegra_xudc *xudc, + struct usb_ctrlrequest *ctrl) +{ + if (ctrl->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE | + USB_TYPE_STANDARD)) + return -EINVAL; + + if (xudc->device_state == USB_STATE_DEFAULT) + return -EINVAL; + + if ((le16_to_cpu(ctrl->wIndex) != 0) || + (le16_to_cpu(ctrl->wValue) != 0) || + (le16_to_cpu(ctrl->wLength) != 6)) + return -EINVAL; + + return tegra_xudc_ep0_queue_data(xudc, &xudc->sel_timing, + sizeof(xudc->sel_timing), + set_sel_complete); +} + +static void set_isoch_delay_complete(struct usb_ep *ep, struct usb_request *req) +{ + /* Nothing to do with isoch delay */ +} + +static int tegra_xudc_ep0_set_isoch_delay(struct tegra_xudc *xudc, + struct usb_ctrlrequest *ctrl) +{ + u32 delay = le16_to_cpu(ctrl->wValue); + + if (ctrl->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE | + USB_TYPE_STANDARD)) + return -EINVAL; + + if ((delay > 65535) || (le16_to_cpu(ctrl->wIndex) != 0) || + (le16_to_cpu(ctrl->wLength) != 0)) + return -EINVAL; + + xudc->isoch_delay = delay; + + return tegra_xudc_ep0_queue_status(xudc, set_isoch_delay_complete); +} + +static void set_address_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct tegra_xudc *xudc = req->context; + + if ((xudc->device_state == USB_STATE_DEFAULT) && + (xudc->dev_addr != 0)) { + xudc->device_state = USB_STATE_ADDRESS; + usb_gadget_set_state(&xudc->gadget, xudc->device_state); + } else if ((xudc->device_state == USB_STATE_ADDRESS) && + (xudc->dev_addr == 0)) { + xudc->device_state = USB_STATE_DEFAULT; + usb_gadget_set_state(&xudc->gadget, xudc->device_state); + } +} + +static int tegra_xudc_ep0_set_address(struct tegra_xudc *xudc, + struct usb_ctrlrequest *ctrl) +{ + struct tegra_xudc_ep *ep0 = &xudc->ep[0]; + u32 val, addr = le16_to_cpu(ctrl->wValue); + + if (ctrl->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE | + USB_TYPE_STANDARD)) + return -EINVAL; + + if ((addr > 127) || (le16_to_cpu(ctrl->wIndex) != 0) || + (le16_to_cpu(ctrl->wLength) != 0)) + return -EINVAL; + + if (xudc->device_state == USB_STATE_CONFIGURED) + return -EINVAL; + + dev_dbg(xudc->dev, "set address: %u\n", addr); + + xudc->dev_addr = addr; + val = xudc_readl(xudc, CTRL); + val &= ~(CTRL_DEVADDR_MASK); + val |= CTRL_DEVADDR(addr); + xudc_writel(xudc, val, CTRL); + + ep_ctx_write_devaddr(ep0->context, addr); + + return tegra_xudc_ep0_queue_status(xudc, set_address_complete); +} + +static int tegra_xudc_ep0_standard_req(struct tegra_xudc *xudc, + struct usb_ctrlrequest *ctrl) +{ + int ret; + + switch (ctrl->bRequest) { + case USB_REQ_GET_STATUS: + dev_dbg(xudc->dev, "USB_REQ_GET_STATUS\n"); + ret = tegra_xudc_ep0_get_status(xudc, ctrl); + break; + case USB_REQ_SET_ADDRESS: + dev_dbg(xudc->dev, "USB_REQ_SET_ADDRESS\n"); + ret = tegra_xudc_ep0_set_address(xudc, ctrl); + break; + case USB_REQ_SET_SEL: + dev_dbg(xudc->dev, "USB_REQ_SET_SEL\n"); + ret = tegra_xudc_ep0_set_sel(xudc, ctrl); + break; + case USB_REQ_SET_ISOCH_DELAY: + dev_dbg(xudc->dev, "USB_REQ_SET_ISOCH_DELAY\n"); + ret = tegra_xudc_ep0_set_isoch_delay(xudc, ctrl); + break; + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + dev_dbg(xudc->dev, "USB_REQ_CLEAR/SET_FEATURE\n"); + ret = tegra_xudc_ep0_set_feature(xudc, ctrl); + break; + case USB_REQ_SET_CONFIGURATION: + dev_dbg(xudc->dev, "USB_REQ_SET_CONFIGURATION\n"); + /* + * In theory we need to clear RUN bit before status stage of + * deconfig request sent, but this seems to be causing problems. + * Clear RUN once all endpoints are disabled instead. + */ + fallthrough; + default: + ret = tegra_xudc_ep0_delegate_req(xudc, ctrl); + break; + } + + return ret; +} + +static void tegra_xudc_handle_ep0_setup_packet(struct tegra_xudc *xudc, + struct usb_ctrlrequest *ctrl, + u16 seq_num) +{ + int ret; + + xudc->setup_seq_num = seq_num; + + /* Ensure EP0 is unhalted. */ + ep_unhalt(xudc, 0); + + /* + * On Tegra210, setup packets with sequence numbers 0xfffe or 0xffff + * are invalid. Halt EP0 until we get a valid packet. + */ + if (xudc->soc->invalid_seq_num && + (seq_num == 0xfffe || seq_num == 0xffff)) { + dev_warn(xudc->dev, "invalid sequence number detected\n"); + ep_halt(xudc, 0); + return; + } + + if (ctrl->wLength) + xudc->setup_state = (ctrl->bRequestType & USB_DIR_IN) ? + DATA_STAGE_XFER : DATA_STAGE_RECV; + else + xudc->setup_state = STATUS_STAGE_XFER; + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + ret = tegra_xudc_ep0_standard_req(xudc, ctrl); + else + ret = tegra_xudc_ep0_delegate_req(xudc, ctrl); + + if (ret < 0) { + dev_warn(xudc->dev, "setup request failed: %d\n", ret); + xudc->setup_state = WAIT_FOR_SETUP; + ep_halt(xudc, 0); + } +} + +static void tegra_xudc_handle_ep0_event(struct tegra_xudc *xudc, + struct tegra_xudc_trb *event) +{ + struct usb_ctrlrequest *ctrl = (struct usb_ctrlrequest *)event; + u16 seq_num = trb_read_seq_num(event); + + if (xudc->setup_state != WAIT_FOR_SETUP) { + /* + * The controller is in the process of handling another + * setup request. Queue subsequent requests and handle + * the last one once the controller reports a sequence + * number error. + */ + memcpy(&xudc->setup_packet.ctrl_req, ctrl, sizeof(*ctrl)); + xudc->setup_packet.seq_num = seq_num; + xudc->queued_setup_packet = true; + } else { + tegra_xudc_handle_ep0_setup_packet(xudc, ctrl, seq_num); + } +} + +static struct tegra_xudc_request * +trb_to_request(struct tegra_xudc_ep *ep, struct tegra_xudc_trb *trb) +{ + struct tegra_xudc_request *req; + + list_for_each_entry(req, &ep->queue, list) { + if (!req->trbs_queued) + break; + + if (trb_in_request(ep, req, trb)) + return req; + } + + return NULL; +} + +static void tegra_xudc_handle_transfer_completion(struct tegra_xudc *xudc, + struct tegra_xudc_ep *ep, + struct tegra_xudc_trb *event) +{ + struct tegra_xudc_request *req; + struct tegra_xudc_trb *trb; + bool short_packet; + + short_packet = (trb_read_cmpl_code(event) == + TRB_CMPL_CODE_SHORT_PACKET); + + trb = trb_phys_to_virt(ep, trb_read_data_ptr(event)); + req = trb_to_request(ep, trb); + + /* + * TDs are complete on short packet or when the completed TRB is the + * last TRB in the TD (the CHAIN bit is unset). + */ + if (req && (short_packet || (!trb_read_chain(trb) && + (req->trbs_needed == req->trbs_queued)))) { + struct tegra_xudc_trb *last = req->last_trb; + unsigned int residual; + + residual = trb_read_transfer_len(event); + req->usb_req.actual = req->usb_req.length - residual; + + dev_dbg(xudc->dev, "bytes transferred %u / %u\n", + req->usb_req.actual, req->usb_req.length); + + tegra_xudc_req_done(ep, req, 0); + + if (ep->desc && usb_endpoint_xfer_control(ep->desc)) + tegra_xudc_ep0_req_done(xudc); + + /* + * Advance the dequeue pointer past the end of the current TD + * on short packet completion. + */ + if (short_packet) { + ep->deq_ptr = (last - ep->transfer_ring) + 1; + if (ep->deq_ptr == XUDC_TRANSFER_RING_SIZE - 1) + ep->deq_ptr = 0; + } + } else if (!req) { + dev_warn(xudc->dev, "transfer event on dequeued request\n"); + } + + if (ep->desc) + tegra_xudc_ep_kick_queue(ep); +} + +static void tegra_xudc_handle_transfer_event(struct tegra_xudc *xudc, + struct tegra_xudc_trb *event) +{ + unsigned int ep_index = trb_read_endpoint_id(event); + struct tegra_xudc_ep *ep = &xudc->ep[ep_index]; + struct tegra_xudc_trb *trb; + u16 comp_code; + + if (ep_ctx_read_state(ep->context) == EP_STATE_DISABLED) { + dev_warn(xudc->dev, "transfer event on disabled EP %u\n", + ep_index); + return; + } + + /* Update transfer ring dequeue pointer. */ + trb = trb_phys_to_virt(ep, trb_read_data_ptr(event)); + comp_code = trb_read_cmpl_code(event); + if (comp_code != TRB_CMPL_CODE_BABBLE_DETECTED_ERR) { + ep->deq_ptr = (trb - ep->transfer_ring) + 1; + + if (ep->deq_ptr == XUDC_TRANSFER_RING_SIZE - 1) + ep->deq_ptr = 0; + ep->ring_full = false; + } + + switch (comp_code) { + case TRB_CMPL_CODE_SUCCESS: + case TRB_CMPL_CODE_SHORT_PACKET: + tegra_xudc_handle_transfer_completion(xudc, ep, event); + break; + case TRB_CMPL_CODE_HOST_REJECTED: + dev_info(xudc->dev, "stream rejected on EP %u\n", ep_index); + + ep->stream_rejected = true; + break; + case TRB_CMPL_CODE_PRIME_PIPE_RECEIVED: + dev_info(xudc->dev, "prime pipe received on EP %u\n", ep_index); + + if (ep->stream_rejected) { + ep->stream_rejected = false; + /* + * An EP is stopped when a stream is rejected. Wait + * for the EP to report that it is stopped and then + * un-stop it. + */ + ep_wait_for_stopped(xudc, ep_index); + } + tegra_xudc_ep_ring_doorbell(ep); + break; + case TRB_CMPL_CODE_BABBLE_DETECTED_ERR: + /* + * Wait for the EP to be stopped so the controller stops + * processing doorbells. + */ + ep_wait_for_stopped(xudc, ep_index); + ep->enq_ptr = ep->deq_ptr; + tegra_xudc_ep_nuke(ep, -EIO); + fallthrough; + case TRB_CMPL_CODE_STREAM_NUMP_ERROR: + case TRB_CMPL_CODE_CTRL_DIR_ERR: + case TRB_CMPL_CODE_INVALID_STREAM_TYPE_ERR: + case TRB_CMPL_CODE_RING_UNDERRUN: + case TRB_CMPL_CODE_RING_OVERRUN: + case TRB_CMPL_CODE_ISOCH_BUFFER_OVERRUN: + case TRB_CMPL_CODE_USB_TRANS_ERR: + case TRB_CMPL_CODE_TRB_ERR: + dev_err(xudc->dev, "completion error %#x on EP %u\n", + comp_code, ep_index); + + ep_halt(xudc, ep_index); + break; + case TRB_CMPL_CODE_CTRL_SEQNUM_ERR: + dev_info(xudc->dev, "sequence number error\n"); + + /* + * Kill any queued control request and skip to the last + * setup packet we received. + */ + tegra_xudc_ep_nuke(ep, -EINVAL); + xudc->setup_state = WAIT_FOR_SETUP; + if (!xudc->queued_setup_packet) + break; + + tegra_xudc_handle_ep0_setup_packet(xudc, + &xudc->setup_packet.ctrl_req, + xudc->setup_packet.seq_num); + xudc->queued_setup_packet = false; + break; + case TRB_CMPL_CODE_STOPPED: + dev_dbg(xudc->dev, "stop completion code on EP %u\n", + ep_index); + + /* Disconnected. */ + tegra_xudc_ep_nuke(ep, -ECONNREFUSED); + break; + default: + dev_dbg(xudc->dev, "completion event %#x on EP %u\n", + comp_code, ep_index); + break; + } +} + +static void tegra_xudc_reset(struct tegra_xudc *xudc) +{ + struct tegra_xudc_ep *ep0 = &xudc->ep[0]; + dma_addr_t deq_ptr; + unsigned int i; + + xudc->setup_state = WAIT_FOR_SETUP; + xudc->device_state = USB_STATE_DEFAULT; + usb_gadget_set_state(&xudc->gadget, xudc->device_state); + + ep_unpause_all(xudc); + + for (i = 0; i < ARRAY_SIZE(xudc->ep); i++) + tegra_xudc_ep_nuke(&xudc->ep[i], -ESHUTDOWN); + + /* + * Reset sequence number and dequeue pointer to flush the transfer + * ring. + */ + ep0->deq_ptr = ep0->enq_ptr; + ep0->ring_full = false; + + xudc->setup_seq_num = 0; + xudc->queued_setup_packet = false; + + ep_ctx_write_rsvd(ep0->context, 0); + ep_ctx_write_partial_td(ep0->context, 0); + ep_ctx_write_splitxstate(ep0->context, 0); + ep_ctx_write_seq_num(ep0->context, 0); + + deq_ptr = trb_virt_to_phys(ep0, &ep0->transfer_ring[ep0->deq_ptr]); + + if (!dma_mapping_error(xudc->dev, deq_ptr)) { + ep_ctx_write_deq_ptr(ep0->context, deq_ptr); + ep_ctx_write_dcs(ep0->context, ep0->pcs); + } + + ep_unhalt_all(xudc); + ep_reload(xudc, 0); + ep_unpause(xudc, 0); +} + +static void tegra_xudc_port_connect(struct tegra_xudc *xudc) +{ + struct tegra_xudc_ep *ep0 = &xudc->ep[0]; + u16 maxpacket; + u32 val; + + val = (xudc_readl(xudc, PORTSC) & PORTSC_PS_MASK) >> PORTSC_PS_SHIFT; + switch (val) { + case PORTSC_PS_LS: + xudc->gadget.speed = USB_SPEED_LOW; + break; + case PORTSC_PS_FS: + xudc->gadget.speed = USB_SPEED_FULL; + break; + case PORTSC_PS_HS: + xudc->gadget.speed = USB_SPEED_HIGH; + break; + case PORTSC_PS_SS: + xudc->gadget.speed = USB_SPEED_SUPER; + break; + default: + xudc->gadget.speed = USB_SPEED_UNKNOWN; + break; + } + + xudc->device_state = USB_STATE_DEFAULT; + usb_gadget_set_state(&xudc->gadget, xudc->device_state); + + xudc->setup_state = WAIT_FOR_SETUP; + + if (xudc->gadget.speed == USB_SPEED_SUPER) + maxpacket = 512; + else + maxpacket = 64; + + ep_ctx_write_max_packet_size(ep0->context, maxpacket); + tegra_xudc_ep0_desc.wMaxPacketSize = cpu_to_le16(maxpacket); + usb_ep_set_maxpacket_limit(&ep0->usb_ep, maxpacket); + + if (!xudc->soc->u1_enable) { + val = xudc_readl(xudc, PORTPM); + val &= ~(PORTPM_U1TIMEOUT_MASK); + xudc_writel(xudc, val, PORTPM); + } + + if (!xudc->soc->u2_enable) { + val = xudc_readl(xudc, PORTPM); + val &= ~(PORTPM_U2TIMEOUT_MASK); + xudc_writel(xudc, val, PORTPM); + } + + if (xudc->gadget.speed <= USB_SPEED_HIGH) { + val = xudc_readl(xudc, PORTPM); + val &= ~(PORTPM_L1S_MASK); + if (xudc->soc->lpm_enable) + val |= PORTPM_L1S(PORTPM_L1S_ACCEPT); + else + val |= PORTPM_L1S(PORTPM_L1S_NYET); + xudc_writel(xudc, val, PORTPM); + } + + val = xudc_readl(xudc, ST); + if (val & ST_RC) + xudc_writel(xudc, ST_RC, ST); +} + +static void tegra_xudc_port_disconnect(struct tegra_xudc *xudc) +{ + tegra_xudc_reset(xudc); + + if (xudc->driver && xudc->driver->disconnect) { + spin_unlock(&xudc->lock); + xudc->driver->disconnect(&xudc->gadget); + spin_lock(&xudc->lock); + } + + xudc->device_state = USB_STATE_NOTATTACHED; + usb_gadget_set_state(&xudc->gadget, xudc->device_state); + + complete(&xudc->disconnect_complete); +} + +static void tegra_xudc_port_reset(struct tegra_xudc *xudc) +{ + tegra_xudc_reset(xudc); + + if (xudc->driver) { + spin_unlock(&xudc->lock); + usb_gadget_udc_reset(&xudc->gadget, xudc->driver); + spin_lock(&xudc->lock); + } + + tegra_xudc_port_connect(xudc); +} + +static void tegra_xudc_port_suspend(struct tegra_xudc *xudc) +{ + dev_dbg(xudc->dev, "port suspend\n"); + + xudc->resume_state = xudc->device_state; + xudc->device_state = USB_STATE_SUSPENDED; + usb_gadget_set_state(&xudc->gadget, xudc->device_state); + + if (xudc->driver->suspend) { + spin_unlock(&xudc->lock); + xudc->driver->suspend(&xudc->gadget); + spin_lock(&xudc->lock); + } +} + +static void tegra_xudc_port_resume(struct tegra_xudc *xudc) +{ + dev_dbg(xudc->dev, "port resume\n"); + + tegra_xudc_resume_device_state(xudc); + + if (xudc->driver->resume) { + spin_unlock(&xudc->lock); + xudc->driver->resume(&xudc->gadget); + spin_lock(&xudc->lock); + } +} + +static inline void clear_port_change(struct tegra_xudc *xudc, u32 flag) +{ + u32 val; + + val = xudc_readl(xudc, PORTSC); + val &= ~PORTSC_CHANGE_MASK; + val |= flag; + xudc_writel(xudc, val, PORTSC); +} + +static void __tegra_xudc_handle_port_status(struct tegra_xudc *xudc) +{ + u32 portsc, porthalt; + + porthalt = xudc_readl(xudc, PORTHALT); + if ((porthalt & PORTHALT_STCHG_REQ) && + (porthalt & PORTHALT_HALT_LTSSM)) { + dev_dbg(xudc->dev, "STCHG_REQ, PORTHALT = %#x\n", porthalt); + porthalt &= ~PORTHALT_HALT_LTSSM; + xudc_writel(xudc, porthalt, PORTHALT); + } + + portsc = xudc_readl(xudc, PORTSC); + if ((portsc & PORTSC_PRC) && (portsc & PORTSC_PR)) { + dev_dbg(xudc->dev, "PRC, PR, PORTSC = %#x\n", portsc); + clear_port_change(xudc, PORTSC_PRC | PORTSC_PED); +#define TOGGLE_VBUS_WAIT_MS 100 + if (xudc->soc->port_reset_quirk) { + schedule_delayed_work(&xudc->port_reset_war_work, + msecs_to_jiffies(TOGGLE_VBUS_WAIT_MS)); + xudc->wait_for_sec_prc = 1; + } + } + + if ((portsc & PORTSC_PRC) && !(portsc & PORTSC_PR)) { + dev_dbg(xudc->dev, "PRC, Not PR, PORTSC = %#x\n", portsc); + clear_port_change(xudc, PORTSC_PRC | PORTSC_PED); + tegra_xudc_port_reset(xudc); + cancel_delayed_work(&xudc->port_reset_war_work); + xudc->wait_for_sec_prc = 0; + } + + portsc = xudc_readl(xudc, PORTSC); + if (portsc & PORTSC_WRC) { + dev_dbg(xudc->dev, "WRC, PORTSC = %#x\n", portsc); + clear_port_change(xudc, PORTSC_WRC | PORTSC_PED); + if (!(xudc_readl(xudc, PORTSC) & PORTSC_WPR)) + tegra_xudc_port_reset(xudc); + } + + portsc = xudc_readl(xudc, PORTSC); + if (portsc & PORTSC_CSC) { + dev_dbg(xudc->dev, "CSC, PORTSC = %#x\n", portsc); + clear_port_change(xudc, PORTSC_CSC); + + if (portsc & PORTSC_CCS) + tegra_xudc_port_connect(xudc); + else + tegra_xudc_port_disconnect(xudc); + + if (xudc->wait_csc) { + cancel_delayed_work(&xudc->plc_reset_work); + xudc->wait_csc = false; + } + } + + portsc = xudc_readl(xudc, PORTSC); + if (portsc & PORTSC_PLC) { + u32 pls = (portsc & PORTSC_PLS_MASK) >> PORTSC_PLS_SHIFT; + + dev_dbg(xudc->dev, "PLC, PORTSC = %#x\n", portsc); + clear_port_change(xudc, PORTSC_PLC); + switch (pls) { + case PORTSC_PLS_U3: + tegra_xudc_port_suspend(xudc); + break; + case PORTSC_PLS_U0: + if (xudc->gadget.speed < USB_SPEED_SUPER) + tegra_xudc_port_resume(xudc); + break; + case PORTSC_PLS_RESUME: + if (xudc->gadget.speed == USB_SPEED_SUPER) + tegra_xudc_port_resume(xudc); + break; + case PORTSC_PLS_INACTIVE: + schedule_delayed_work(&xudc->plc_reset_work, + msecs_to_jiffies(TOGGLE_VBUS_WAIT_MS)); + xudc->wait_csc = true; + break; + default: + break; + } + } + + if (portsc & PORTSC_CEC) { + dev_warn(xudc->dev, "CEC, PORTSC = %#x\n", portsc); + clear_port_change(xudc, PORTSC_CEC); + } + + dev_dbg(xudc->dev, "PORTSC = %#x\n", xudc_readl(xudc, PORTSC)); +} + +static void tegra_xudc_handle_port_status(struct tegra_xudc *xudc) +{ + while ((xudc_readl(xudc, PORTSC) & PORTSC_CHANGE_MASK) || + (xudc_readl(xudc, PORTHALT) & PORTHALT_STCHG_REQ)) + __tegra_xudc_handle_port_status(xudc); +} + +static void tegra_xudc_handle_event(struct tegra_xudc *xudc, + struct tegra_xudc_trb *event) +{ + u32 type = trb_read_type(event); + + dump_trb(xudc, "EVENT", event); + + switch (type) { + case TRB_TYPE_PORT_STATUS_CHANGE_EVENT: + tegra_xudc_handle_port_status(xudc); + break; + case TRB_TYPE_TRANSFER_EVENT: + tegra_xudc_handle_transfer_event(xudc, event); + break; + case TRB_TYPE_SETUP_PACKET_EVENT: + tegra_xudc_handle_ep0_event(xudc, event); + break; + default: + dev_info(xudc->dev, "Unrecognized TRB type = %#x\n", type); + break; + } +} + +static void tegra_xudc_process_event_ring(struct tegra_xudc *xudc) +{ + struct tegra_xudc_trb *event; + dma_addr_t erdp; + + while (true) { + event = xudc->event_ring[xudc->event_ring_index] + + xudc->event_ring_deq_ptr; + + if (trb_read_cycle(event) != xudc->ccs) + break; + + tegra_xudc_handle_event(xudc, event); + + xudc->event_ring_deq_ptr++; + if (xudc->event_ring_deq_ptr == XUDC_EVENT_RING_SIZE) { + xudc->event_ring_deq_ptr = 0; + xudc->event_ring_index++; + } + + if (xudc->event_ring_index == XUDC_NR_EVENT_RINGS) { + xudc->event_ring_index = 0; + xudc->ccs = !xudc->ccs; + } + } + + erdp = xudc->event_ring_phys[xudc->event_ring_index] + + xudc->event_ring_deq_ptr * sizeof(*event); + + xudc_writel(xudc, upper_32_bits(erdp), ERDPHI); + xudc_writel(xudc, lower_32_bits(erdp) | ERDPLO_EHB, ERDPLO); +} + +static irqreturn_t tegra_xudc_irq(int irq, void *data) +{ + struct tegra_xudc *xudc = data; + unsigned long flags; + u32 val; + + val = xudc_readl(xudc, ST); + if (!(val & ST_IP)) + return IRQ_NONE; + xudc_writel(xudc, ST_IP, ST); + + spin_lock_irqsave(&xudc->lock, flags); + tegra_xudc_process_event_ring(xudc); + spin_unlock_irqrestore(&xudc->lock, flags); + + return IRQ_HANDLED; +} + +static int tegra_xudc_alloc_ep(struct tegra_xudc *xudc, unsigned int index) +{ + struct tegra_xudc_ep *ep = &xudc->ep[index]; + + ep->xudc = xudc; + ep->index = index; + ep->context = &xudc->ep_context[index]; + INIT_LIST_HEAD(&ep->queue); + + /* + * EP1 would be the input endpoint corresponding to EP0, but since + * EP0 is bi-directional, EP1 is unused. + */ + if (index == 1) + return 0; + + ep->transfer_ring = dma_pool_alloc(xudc->transfer_ring_pool, + GFP_KERNEL, + &ep->transfer_ring_phys); + if (!ep->transfer_ring) + return -ENOMEM; + + if (index) { + snprintf(ep->name, sizeof(ep->name), "ep%u%s", index / 2, + (index % 2 == 0) ? "out" : "in"); + ep->usb_ep.name = ep->name; + usb_ep_set_maxpacket_limit(&ep->usb_ep, 1024); + ep->usb_ep.max_streams = 16; + ep->usb_ep.ops = &tegra_xudc_ep_ops; + ep->usb_ep.caps.type_bulk = true; + ep->usb_ep.caps.type_int = true; + if (index & 1) + ep->usb_ep.caps.dir_in = true; + else + ep->usb_ep.caps.dir_out = true; + list_add_tail(&ep->usb_ep.ep_list, &xudc->gadget.ep_list); + } else { + strscpy(ep->name, "ep0", 3); + ep->usb_ep.name = ep->name; + usb_ep_set_maxpacket_limit(&ep->usb_ep, 512); + ep->usb_ep.ops = &tegra_xudc_ep0_ops; + ep->usb_ep.caps.type_control = true; + ep->usb_ep.caps.dir_in = true; + ep->usb_ep.caps.dir_out = true; + } + + return 0; +} + +static void tegra_xudc_free_ep(struct tegra_xudc *xudc, unsigned int index) +{ + struct tegra_xudc_ep *ep = &xudc->ep[index]; + + /* + * EP1 would be the input endpoint corresponding to EP0, but since + * EP0 is bi-directional, EP1 is unused. + */ + if (index == 1) + return; + + dma_pool_free(xudc->transfer_ring_pool, ep->transfer_ring, + ep->transfer_ring_phys); +} + +static int tegra_xudc_alloc_eps(struct tegra_xudc *xudc) +{ + struct usb_request *req; + unsigned int i; + int err; + + xudc->ep_context = + dma_alloc_coherent(xudc->dev, XUDC_NR_EPS * + sizeof(*xudc->ep_context), + &xudc->ep_context_phys, GFP_KERNEL); + if (!xudc->ep_context) + return -ENOMEM; + + xudc->transfer_ring_pool = + dmam_pool_create(dev_name(xudc->dev), xudc->dev, + XUDC_TRANSFER_RING_SIZE * + sizeof(struct tegra_xudc_trb), + sizeof(struct tegra_xudc_trb), 0); + if (!xudc->transfer_ring_pool) { + err = -ENOMEM; + goto free_ep_context; + } + + INIT_LIST_HEAD(&xudc->gadget.ep_list); + for (i = 0; i < ARRAY_SIZE(xudc->ep); i++) { + err = tegra_xudc_alloc_ep(xudc, i); + if (err < 0) + goto free_eps; + } + + req = tegra_xudc_ep_alloc_request(&xudc->ep[0].usb_ep, GFP_KERNEL); + if (!req) { + err = -ENOMEM; + goto free_eps; + } + xudc->ep0_req = to_xudc_req(req); + + return 0; + +free_eps: + for (; i > 0; i--) + tegra_xudc_free_ep(xudc, i - 1); +free_ep_context: + dma_free_coherent(xudc->dev, XUDC_NR_EPS * sizeof(*xudc->ep_context), + xudc->ep_context, xudc->ep_context_phys); + return err; +} + +static void tegra_xudc_init_eps(struct tegra_xudc *xudc) +{ + xudc_writel(xudc, lower_32_bits(xudc->ep_context_phys), ECPLO); + xudc_writel(xudc, upper_32_bits(xudc->ep_context_phys), ECPHI); +} + +static void tegra_xudc_free_eps(struct tegra_xudc *xudc) +{ + unsigned int i; + + tegra_xudc_ep_free_request(&xudc->ep[0].usb_ep, + &xudc->ep0_req->usb_req); + + for (i = 0; i < ARRAY_SIZE(xudc->ep); i++) + tegra_xudc_free_ep(xudc, i); + + dma_free_coherent(xudc->dev, XUDC_NR_EPS * sizeof(*xudc->ep_context), + xudc->ep_context, xudc->ep_context_phys); +} + +static int tegra_xudc_alloc_event_ring(struct tegra_xudc *xudc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(xudc->event_ring); i++) { + xudc->event_ring[i] = + dma_alloc_coherent(xudc->dev, XUDC_EVENT_RING_SIZE * + sizeof(*xudc->event_ring[i]), + &xudc->event_ring_phys[i], + GFP_KERNEL); + if (!xudc->event_ring[i]) + goto free_dma; + } + + return 0; + +free_dma: + for (; i > 0; i--) { + dma_free_coherent(xudc->dev, XUDC_EVENT_RING_SIZE * + sizeof(*xudc->event_ring[i - 1]), + xudc->event_ring[i - 1], + xudc->event_ring_phys[i - 1]); + } + return -ENOMEM; +} + +static void tegra_xudc_init_event_ring(struct tegra_xudc *xudc) +{ + unsigned int i; + u32 val; + + for (i = 0; i < ARRAY_SIZE(xudc->event_ring); i++) { + memset(xudc->event_ring[i], 0, XUDC_EVENT_RING_SIZE * + sizeof(*xudc->event_ring[i])); + + val = xudc_readl(xudc, ERSTSZ); + val &= ~(ERSTSZ_ERSTXSZ_MASK << ERSTSZ_ERSTXSZ_SHIFT(i)); + val |= XUDC_EVENT_RING_SIZE << ERSTSZ_ERSTXSZ_SHIFT(i); + xudc_writel(xudc, val, ERSTSZ); + + xudc_writel(xudc, lower_32_bits(xudc->event_ring_phys[i]), + ERSTXBALO(i)); + xudc_writel(xudc, upper_32_bits(xudc->event_ring_phys[i]), + ERSTXBAHI(i)); + } + + val = lower_32_bits(xudc->event_ring_phys[0]); + xudc_writel(xudc, val, ERDPLO); + val |= EREPLO_ECS; + xudc_writel(xudc, val, EREPLO); + + val = upper_32_bits(xudc->event_ring_phys[0]); + xudc_writel(xudc, val, ERDPHI); + xudc_writel(xudc, val, EREPHI); + + xudc->ccs = true; + xudc->event_ring_index = 0; + xudc->event_ring_deq_ptr = 0; +} + +static void tegra_xudc_free_event_ring(struct tegra_xudc *xudc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(xudc->event_ring); i++) { + dma_free_coherent(xudc->dev, XUDC_EVENT_RING_SIZE * + sizeof(*xudc->event_ring[i]), + xudc->event_ring[i], + xudc->event_ring_phys[i]); + } +} + +static void tegra_xudc_fpci_ipfs_init(struct tegra_xudc *xudc) +{ + u32 val; + + if (xudc->soc->has_ipfs) { + val = ipfs_readl(xudc, XUSB_DEV_CONFIGURATION_0); + val |= XUSB_DEV_CONFIGURATION_0_EN_FPCI; + ipfs_writel(xudc, val, XUSB_DEV_CONFIGURATION_0); + usleep_range(10, 15); + } + + /* Enable bus master */ + val = XUSB_DEV_CFG_1_IO_SPACE_EN | XUSB_DEV_CFG_1_MEMORY_SPACE_EN | + XUSB_DEV_CFG_1_BUS_MASTER_EN; + fpci_writel(xudc, val, XUSB_DEV_CFG_1); + + /* Program BAR0 space */ + val = fpci_readl(xudc, XUSB_DEV_CFG_4); + val &= ~(XUSB_DEV_CFG_4_BASE_ADDR_MASK); + val |= xudc->phys_base & (XUSB_DEV_CFG_4_BASE_ADDR_MASK); + + fpci_writel(xudc, val, XUSB_DEV_CFG_4); + fpci_writel(xudc, upper_32_bits(xudc->phys_base), XUSB_DEV_CFG_5); + + usleep_range(100, 200); + + if (xudc->soc->has_ipfs) { + /* Enable interrupt assertion */ + val = ipfs_readl(xudc, XUSB_DEV_INTR_MASK_0); + val |= XUSB_DEV_INTR_MASK_0_IP_INT_MASK; + ipfs_writel(xudc, val, XUSB_DEV_INTR_MASK_0); + } +} + +static void tegra_xudc_device_params_init(struct tegra_xudc *xudc) +{ + u32 val, imod; + + val = xudc_readl(xudc, BLCG); + if (xudc->soc->has_ipfs) { + val |= BLCG_ALL; + val &= ~(BLCG_DFPCI | BLCG_UFPCI | BLCG_FE | + BLCG_COREPLL_PWRDN); + val |= BLCG_IOPLL_0_PWRDN; + val |= BLCG_IOPLL_1_PWRDN; + val |= BLCG_IOPLL_2_PWRDN; + } else { + val &= ~BLCG_COREPLL_PWRDN; + } + xudc_writel(xudc, val, BLCG); + + if (xudc->soc->port_speed_quirk) + tegra_xudc_limit_port_speed(xudc); + + /* Set a reasonable U3 exit timer value. */ + val = xudc_readl(xudc, SSPX_CORE_PADCTL4); + val &= ~(SSPX_CORE_PADCTL4_RXDAT_VLD_TIMEOUT_U3_MASK); + val |= SSPX_CORE_PADCTL4_RXDAT_VLD_TIMEOUT_U3(0x5dc0); + xudc_writel(xudc, val, SSPX_CORE_PADCTL4); + + /* Default ping LFPS tBurst is too large. */ + val = xudc_readl(xudc, SSPX_CORE_CNT0); + val &= ~(SSPX_CORE_CNT0_PING_TBURST_MASK); + val |= SSPX_CORE_CNT0_PING_TBURST(0xa); + xudc_writel(xudc, val, SSPX_CORE_CNT0); + + /* Default tPortConfiguration timeout is too small. */ + val = xudc_readl(xudc, SSPX_CORE_CNT30); + val &= ~(SSPX_CORE_CNT30_LMPITP_TIMER_MASK); + val |= SSPX_CORE_CNT30_LMPITP_TIMER(0x978); + xudc_writel(xudc, val, SSPX_CORE_CNT30); + + if (xudc->soc->lpm_enable) { + /* Set L1 resume duration to 95 us. */ + val = xudc_readl(xudc, HSFSPI_COUNT13); + val &= ~(HSFSPI_COUNT13_U2_RESUME_K_DURATION_MASK); + val |= HSFSPI_COUNT13_U2_RESUME_K_DURATION(0x2c88); + xudc_writel(xudc, val, HSFSPI_COUNT13); + } + + /* + * Compliance suite appears to be violating polling LFPS tBurst max + * of 1.4us. Send 1.45us instead. + */ + val = xudc_readl(xudc, SSPX_CORE_CNT32); + val &= ~(SSPX_CORE_CNT32_POLL_TBURST_MAX_MASK); + val |= SSPX_CORE_CNT32_POLL_TBURST_MAX(0xb0); + xudc_writel(xudc, val, SSPX_CORE_CNT32); + + /* Direct HS/FS port instance to RxDetect. */ + val = xudc_readl(xudc, CFG_DEV_FE); + val &= ~(CFG_DEV_FE_PORTREGSEL_MASK); + val |= CFG_DEV_FE_PORTREGSEL(CFG_DEV_FE_PORTREGSEL_HSFS_PI); + xudc_writel(xudc, val, CFG_DEV_FE); + + val = xudc_readl(xudc, PORTSC); + val &= ~(PORTSC_CHANGE_MASK | PORTSC_PLS_MASK); + val |= PORTSC_LWS | PORTSC_PLS(PORTSC_PLS_RXDETECT); + xudc_writel(xudc, val, PORTSC); + + /* Direct SS port instance to RxDetect. */ + val = xudc_readl(xudc, CFG_DEV_FE); + val &= ~(CFG_DEV_FE_PORTREGSEL_MASK); + val |= CFG_DEV_FE_PORTREGSEL_SS_PI & CFG_DEV_FE_PORTREGSEL_MASK; + xudc_writel(xudc, val, CFG_DEV_FE); + + val = xudc_readl(xudc, PORTSC); + val &= ~(PORTSC_CHANGE_MASK | PORTSC_PLS_MASK); + val |= PORTSC_LWS | PORTSC_PLS(PORTSC_PLS_RXDETECT); + xudc_writel(xudc, val, PORTSC); + + /* Restore port instance. */ + val = xudc_readl(xudc, CFG_DEV_FE); + val &= ~(CFG_DEV_FE_PORTREGSEL_MASK); + xudc_writel(xudc, val, CFG_DEV_FE); + + /* + * Enable INFINITE_SS_RETRY to prevent device from entering + * Disabled.Error when attached to buggy SuperSpeed hubs. + */ + val = xudc_readl(xudc, CFG_DEV_FE); + val |= CFG_DEV_FE_INFINITE_SS_RETRY; + xudc_writel(xudc, val, CFG_DEV_FE); + + /* Set interrupt moderation. */ + imod = XUDC_INTERRUPT_MODERATION_US * 4; + val = xudc_readl(xudc, RT_IMOD); + val &= ~((RT_IMOD_IMODI_MASK) | (RT_IMOD_IMODC_MASK)); + val |= (RT_IMOD_IMODI(imod) | RT_IMOD_IMODC(imod)); + xudc_writel(xudc, val, RT_IMOD); + + /* increase SSPI transaction timeout from 32us to 512us */ + val = xudc_readl(xudc, CFG_DEV_SSPI_XFER); + val &= ~(CFG_DEV_SSPI_XFER_ACKTIMEOUT_MASK); + val |= CFG_DEV_SSPI_XFER_ACKTIMEOUT(0xf000); + xudc_writel(xudc, val, CFG_DEV_SSPI_XFER); +} + +static int tegra_xudc_phy_get(struct tegra_xudc *xudc) +{ + int err = 0, usb3_companion_port; + unsigned int i, j; + + xudc->utmi_phy = devm_kcalloc(xudc->dev, xudc->soc->num_phys, + sizeof(*xudc->utmi_phy), GFP_KERNEL); + if (!xudc->utmi_phy) + return -ENOMEM; + + xudc->usb3_phy = devm_kcalloc(xudc->dev, xudc->soc->num_phys, + sizeof(*xudc->usb3_phy), GFP_KERNEL); + if (!xudc->usb3_phy) + return -ENOMEM; + + xudc->usbphy = devm_kcalloc(xudc->dev, xudc->soc->num_phys, + sizeof(*xudc->usbphy), GFP_KERNEL); + if (!xudc->usbphy) + return -ENOMEM; + + xudc->vbus_nb.notifier_call = tegra_xudc_vbus_notify; + + for (i = 0; i < xudc->soc->num_phys; i++) { + char phy_name[] = "usb.-."; + + /* Get USB2 phy */ + snprintf(phy_name, sizeof(phy_name), "usb2-%d", i); + xudc->utmi_phy[i] = devm_phy_optional_get(xudc->dev, phy_name); + if (IS_ERR(xudc->utmi_phy[i])) { + err = PTR_ERR(xudc->utmi_phy[i]); + dev_err_probe(xudc->dev, err, + "failed to get PHY for phy-name usb2-%d\n", i); + goto clean_up; + } else if (xudc->utmi_phy[i]) { + /* Get usb-phy, if utmi phy is available */ + xudc->usbphy[i] = devm_usb_get_phy_by_node(xudc->dev, + xudc->utmi_phy[i]->dev.of_node, + NULL); + if (IS_ERR(xudc->usbphy[i])) { + err = PTR_ERR(xudc->usbphy[i]); + dev_err_probe(xudc->dev, err, + "failed to get usbphy-%d\n", i); + goto clean_up; + } + } else if (!xudc->utmi_phy[i]) { + /* if utmi phy is not available, ignore USB3 phy get */ + continue; + } + + /* Get USB3 phy */ + usb3_companion_port = tegra_xusb_padctl_get_usb3_companion(xudc->padctl, i); + if (usb3_companion_port < 0) + continue; + + for (j = 0; j < xudc->soc->num_phys; j++) { + snprintf(phy_name, sizeof(phy_name), "usb3-%d", j); + xudc->usb3_phy[i] = devm_phy_optional_get(xudc->dev, phy_name); + if (IS_ERR(xudc->usb3_phy[i])) { + err = PTR_ERR(xudc->usb3_phy[i]); + dev_err_probe(xudc->dev, err, + "failed to get PHY for phy-name usb3-%d\n", j); + goto clean_up; + } else if (xudc->usb3_phy[i]) { + int usb2_port = + tegra_xusb_padctl_get_port_number(xudc->utmi_phy[i]); + int usb3_port = + tegra_xusb_padctl_get_port_number(xudc->usb3_phy[i]); + if (usb3_port == usb3_companion_port) { + dev_dbg(xudc->dev, "USB2 port %d is paired with USB3 port %d for device mode port %d\n", + usb2_port, usb3_port, i); + break; + } + } + } + } + + return err; + +clean_up: + for (i = 0; i < xudc->soc->num_phys; i++) { + xudc->usb3_phy[i] = NULL; + xudc->utmi_phy[i] = NULL; + xudc->usbphy[i] = NULL; + } + + return err; +} + +static void tegra_xudc_phy_exit(struct tegra_xudc *xudc) +{ + unsigned int i; + + for (i = 0; i < xudc->soc->num_phys; i++) { + phy_exit(xudc->usb3_phy[i]); + phy_exit(xudc->utmi_phy[i]); + } +} + +static int tegra_xudc_phy_init(struct tegra_xudc *xudc) +{ + int err; + unsigned int i; + + for (i = 0; i < xudc->soc->num_phys; i++) { + err = phy_init(xudc->utmi_phy[i]); + if (err < 0) { + dev_err(xudc->dev, "UTMI PHY #%u initialization failed: %d\n", i, err); + goto exit_phy; + } + + err = phy_init(xudc->usb3_phy[i]); + if (err < 0) { + dev_err(xudc->dev, "USB3 PHY #%u initialization failed: %d\n", i, err); + goto exit_phy; + } + } + return 0; + +exit_phy: + tegra_xudc_phy_exit(xudc); + return err; +} + +static const char * const tegra210_xudc_supply_names[] = { + "hvdd-usb", + "avddio-usb", +}; + +static const char * const tegra210_xudc_clock_names[] = { + "dev", + "ss", + "ss_src", + "hs_src", + "fs_src", +}; + +static const char * const tegra186_xudc_clock_names[] = { + "dev", + "ss", + "ss_src", + "fs_src", +}; + +static struct tegra_xudc_soc tegra210_xudc_soc_data = { + .supply_names = tegra210_xudc_supply_names, + .num_supplies = ARRAY_SIZE(tegra210_xudc_supply_names), + .clock_names = tegra210_xudc_clock_names, + .num_clks = ARRAY_SIZE(tegra210_xudc_clock_names), + .num_phys = 4, + .u1_enable = false, + .u2_enable = true, + .lpm_enable = false, + .invalid_seq_num = true, + .pls_quirk = true, + .port_reset_quirk = true, + .port_speed_quirk = false, + .has_ipfs = true, +}; + +static struct tegra_xudc_soc tegra186_xudc_soc_data = { + .clock_names = tegra186_xudc_clock_names, + .num_clks = ARRAY_SIZE(tegra186_xudc_clock_names), + .num_phys = 4, + .u1_enable = true, + .u2_enable = true, + .lpm_enable = false, + .invalid_seq_num = false, + .pls_quirk = false, + .port_reset_quirk = false, + .port_speed_quirk = false, + .has_ipfs = false, +}; + +static struct tegra_xudc_soc tegra194_xudc_soc_data = { + .clock_names = tegra186_xudc_clock_names, + .num_clks = ARRAY_SIZE(tegra186_xudc_clock_names), + .num_phys = 4, + .u1_enable = true, + .u2_enable = true, + .lpm_enable = true, + .invalid_seq_num = false, + .pls_quirk = false, + .port_reset_quirk = false, + .port_speed_quirk = true, + .has_ipfs = false, +}; + +static struct tegra_xudc_soc tegra234_xudc_soc_data = { + .clock_names = tegra186_xudc_clock_names, + .num_clks = ARRAY_SIZE(tegra186_xudc_clock_names), + .num_phys = 4, + .u1_enable = true, + .u2_enable = true, + .lpm_enable = true, + .invalid_seq_num = false, + .pls_quirk = false, + .port_reset_quirk = false, + .has_ipfs = false, +}; + +static const struct of_device_id tegra_xudc_of_match[] = { + { + .compatible = "nvidia,tegra210-xudc", + .data = &tegra210_xudc_soc_data + }, + { + .compatible = "nvidia,tegra186-xudc", + .data = &tegra186_xudc_soc_data + }, + { + .compatible = "nvidia,tegra194-xudc", + .data = &tegra194_xudc_soc_data + }, + { + .compatible = "nvidia,tegra234-xudc", + .data = &tegra234_xudc_soc_data + }, + { } +}; +MODULE_DEVICE_TABLE(of, tegra_xudc_of_match); + +static void tegra_xudc_powerdomain_remove(struct tegra_xudc *xudc) +{ + if (xudc->genpd_dl_ss) + device_link_del(xudc->genpd_dl_ss); + if (xudc->genpd_dl_device) + device_link_del(xudc->genpd_dl_device); + if (xudc->genpd_dev_ss) + dev_pm_domain_detach(xudc->genpd_dev_ss, true); + if (xudc->genpd_dev_device) + dev_pm_domain_detach(xudc->genpd_dev_device, true); +} + +static int tegra_xudc_powerdomain_init(struct tegra_xudc *xudc) +{ + struct device *dev = xudc->dev; + int err; + + xudc->genpd_dev_device = dev_pm_domain_attach_by_name(dev, "dev"); + if (IS_ERR(xudc->genpd_dev_device)) { + err = PTR_ERR(xudc->genpd_dev_device); + dev_err(dev, "failed to get device power domain: %d\n", err); + return err; + } + + xudc->genpd_dev_ss = dev_pm_domain_attach_by_name(dev, "ss"); + if (IS_ERR(xudc->genpd_dev_ss)) { + err = PTR_ERR(xudc->genpd_dev_ss); + dev_err(dev, "failed to get SuperSpeed power domain: %d\n", err); + return err; + } + + xudc->genpd_dl_device = device_link_add(dev, xudc->genpd_dev_device, + DL_FLAG_PM_RUNTIME | + DL_FLAG_STATELESS); + if (!xudc->genpd_dl_device) { + dev_err(dev, "failed to add USB device link\n"); + return -ENODEV; + } + + xudc->genpd_dl_ss = device_link_add(dev, xudc->genpd_dev_ss, + DL_FLAG_PM_RUNTIME | + DL_FLAG_STATELESS); + if (!xudc->genpd_dl_ss) { + dev_err(dev, "failed to add SuperSpeed device link\n"); + return -ENODEV; + } + + return 0; +} + +static int tegra_xudc_probe(struct platform_device *pdev) +{ + struct tegra_xudc *xudc; + struct resource *res; + unsigned int i; + int err; + + xudc = devm_kzalloc(&pdev->dev, sizeof(*xudc), GFP_KERNEL); + if (!xudc) + return -ENOMEM; + + xudc->dev = &pdev->dev; + platform_set_drvdata(pdev, xudc); + + xudc->soc = of_device_get_match_data(&pdev->dev); + if (!xudc->soc) + return -ENODEV; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "base"); + xudc->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(xudc->base)) + return PTR_ERR(xudc->base); + xudc->phys_base = res->start; + + xudc->fpci = devm_platform_ioremap_resource_byname(pdev, "fpci"); + if (IS_ERR(xudc->fpci)) + return PTR_ERR(xudc->fpci); + + if (xudc->soc->has_ipfs) { + xudc->ipfs = devm_platform_ioremap_resource_byname(pdev, "ipfs"); + if (IS_ERR(xudc->ipfs)) + return PTR_ERR(xudc->ipfs); + } + + xudc->irq = platform_get_irq(pdev, 0); + if (xudc->irq < 0) + return xudc->irq; + + err = devm_request_irq(&pdev->dev, xudc->irq, tegra_xudc_irq, 0, + dev_name(&pdev->dev), xudc); + if (err < 0) { + dev_err(xudc->dev, "failed to claim IRQ#%u: %d\n", xudc->irq, + err); + return err; + } + + xudc->clks = devm_kcalloc(&pdev->dev, xudc->soc->num_clks, sizeof(*xudc->clks), + GFP_KERNEL); + if (!xudc->clks) + return -ENOMEM; + + for (i = 0; i < xudc->soc->num_clks; i++) + xudc->clks[i].id = xudc->soc->clock_names[i]; + + err = devm_clk_bulk_get(&pdev->dev, xudc->soc->num_clks, xudc->clks); + if (err) { + dev_err_probe(xudc->dev, err, "failed to request clocks\n"); + return err; + } + + xudc->supplies = devm_kcalloc(&pdev->dev, xudc->soc->num_supplies, + sizeof(*xudc->supplies), GFP_KERNEL); + if (!xudc->supplies) + return -ENOMEM; + + for (i = 0; i < xudc->soc->num_supplies; i++) + xudc->supplies[i].supply = xudc->soc->supply_names[i]; + + err = devm_regulator_bulk_get(&pdev->dev, xudc->soc->num_supplies, + xudc->supplies); + if (err) { + dev_err_probe(xudc->dev, err, "failed to request regulators\n"); + return err; + } + + xudc->padctl = tegra_xusb_padctl_get(&pdev->dev); + if (IS_ERR(xudc->padctl)) + return PTR_ERR(xudc->padctl); + + err = regulator_bulk_enable(xudc->soc->num_supplies, xudc->supplies); + if (err) { + dev_err(xudc->dev, "failed to enable regulators: %d\n", err); + goto put_padctl; + } + + err = tegra_xudc_phy_get(xudc); + if (err) + goto disable_regulator; + + err = tegra_xudc_powerdomain_init(xudc); + if (err) + goto put_powerdomains; + + err = tegra_xudc_phy_init(xudc); + if (err) + goto put_powerdomains; + + err = tegra_xudc_alloc_event_ring(xudc); + if (err) + goto disable_phy; + + err = tegra_xudc_alloc_eps(xudc); + if (err) + goto free_event_ring; + + spin_lock_init(&xudc->lock); + + init_completion(&xudc->disconnect_complete); + + INIT_WORK(&xudc->usb_role_sw_work, tegra_xudc_usb_role_sw_work); + + INIT_DELAYED_WORK(&xudc->plc_reset_work, tegra_xudc_plc_reset_work); + + INIT_DELAYED_WORK(&xudc->port_reset_war_work, + tegra_xudc_port_reset_war_work); + + pm_runtime_enable(&pdev->dev); + + xudc->gadget.ops = &tegra_xudc_gadget_ops; + xudc->gadget.ep0 = &xudc->ep[0].usb_ep; + xudc->gadget.name = "tegra-xudc"; + xudc->gadget.max_speed = USB_SPEED_SUPER; + + err = usb_add_gadget_udc(&pdev->dev, &xudc->gadget); + if (err) { + dev_err(&pdev->dev, "failed to add USB gadget: %d\n", err); + goto free_eps; + } + + for (i = 0; i < xudc->soc->num_phys; i++) { + if (!xudc->usbphy[i]) + continue; + + usb_register_notifier(xudc->usbphy[i], &xudc->vbus_nb); + tegra_xudc_update_data_role(xudc, xudc->usbphy[i]); + } + + return 0; + +free_eps: + pm_runtime_disable(&pdev->dev); + tegra_xudc_free_eps(xudc); +free_event_ring: + tegra_xudc_free_event_ring(xudc); +disable_phy: + tegra_xudc_phy_exit(xudc); +put_powerdomains: + tegra_xudc_powerdomain_remove(xudc); +disable_regulator: + regulator_bulk_disable(xudc->soc->num_supplies, xudc->supplies); +put_padctl: + tegra_xusb_padctl_put(xudc->padctl); + + return err; +} + +static void tegra_xudc_remove(struct platform_device *pdev) +{ + struct tegra_xudc *xudc = platform_get_drvdata(pdev); + unsigned int i; + + pm_runtime_get_sync(xudc->dev); + + cancel_delayed_work_sync(&xudc->plc_reset_work); + cancel_work_sync(&xudc->usb_role_sw_work); + + usb_del_gadget_udc(&xudc->gadget); + + tegra_xudc_free_eps(xudc); + tegra_xudc_free_event_ring(xudc); + + tegra_xudc_powerdomain_remove(xudc); + + regulator_bulk_disable(xudc->soc->num_supplies, xudc->supplies); + + for (i = 0; i < xudc->soc->num_phys; i++) { + phy_power_off(xudc->utmi_phy[i]); + phy_power_off(xudc->usb3_phy[i]); + } + + tegra_xudc_phy_exit(xudc); + + pm_runtime_disable(xudc->dev); + pm_runtime_put(xudc->dev); + + tegra_xusb_padctl_put(xudc->padctl); +} + +static int __maybe_unused tegra_xudc_powergate(struct tegra_xudc *xudc) +{ + unsigned long flags; + u32 val; + + dev_dbg(xudc->dev, "entering ELPG\n"); + + spin_lock_irqsave(&xudc->lock, flags); + + xudc->powergated = true; + xudc->saved_regs.ctrl = xudc_readl(xudc, CTRL); + xudc->saved_regs.portpm = xudc_readl(xudc, PORTPM); + xudc_writel(xudc, 0, CTRL); + + spin_unlock_irqrestore(&xudc->lock, flags); + + val = xudc_readl(xudc, BLCG); + val |= BLCG_COREPLL_PWRDN; + xudc_writel(xudc, val, BLCG); + + clk_bulk_disable_unprepare(xudc->soc->num_clks, xudc->clks); + + regulator_bulk_disable(xudc->soc->num_supplies, xudc->supplies); + + dev_dbg(xudc->dev, "entering ELPG done\n"); + return 0; +} + +static int __maybe_unused tegra_xudc_unpowergate(struct tegra_xudc *xudc) +{ + unsigned long flags; + int err; + + dev_dbg(xudc->dev, "exiting ELPG\n"); + + err = regulator_bulk_enable(xudc->soc->num_supplies, + xudc->supplies); + if (err < 0) + return err; + + err = clk_bulk_prepare_enable(xudc->soc->num_clks, xudc->clks); + if (err < 0) + return err; + + tegra_xudc_fpci_ipfs_init(xudc); + + tegra_xudc_device_params_init(xudc); + + tegra_xudc_init_event_ring(xudc); + + tegra_xudc_init_eps(xudc); + + xudc_writel(xudc, xudc->saved_regs.portpm, PORTPM); + xudc_writel(xudc, xudc->saved_regs.ctrl, CTRL); + + spin_lock_irqsave(&xudc->lock, flags); + xudc->powergated = false; + spin_unlock_irqrestore(&xudc->lock, flags); + + dev_dbg(xudc->dev, "exiting ELPG done\n"); + return 0; +} + +static int __maybe_unused tegra_xudc_suspend(struct device *dev) +{ + struct tegra_xudc *xudc = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&xudc->lock, flags); + xudc->suspended = true; + spin_unlock_irqrestore(&xudc->lock, flags); + + flush_work(&xudc->usb_role_sw_work); + + if (!pm_runtime_status_suspended(dev)) { + /* Forcibly disconnect before powergating. */ + tegra_xudc_device_mode_off(xudc); + tegra_xudc_powergate(xudc); + } + + pm_runtime_disable(dev); + + return 0; +} + +static int __maybe_unused tegra_xudc_resume(struct device *dev) +{ + struct tegra_xudc *xudc = dev_get_drvdata(dev); + unsigned long flags; + int err; + + err = tegra_xudc_unpowergate(xudc); + if (err < 0) + return err; + + spin_lock_irqsave(&xudc->lock, flags); + xudc->suspended = false; + if (xudc->device_mode != xudc->current_device_mode) + schedule_work(&xudc->usb_role_sw_work); + spin_unlock_irqrestore(&xudc->lock, flags); + + pm_runtime_enable(dev); + + return 0; +} + +static int __maybe_unused tegra_xudc_runtime_suspend(struct device *dev) +{ + struct tegra_xudc *xudc = dev_get_drvdata(dev); + + return tegra_xudc_powergate(xudc); +} + +static int __maybe_unused tegra_xudc_runtime_resume(struct device *dev) +{ + struct tegra_xudc *xudc = dev_get_drvdata(dev); + + return tegra_xudc_unpowergate(xudc); +} + +static const struct dev_pm_ops tegra_xudc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tegra_xudc_suspend, tegra_xudc_resume) + SET_RUNTIME_PM_OPS(tegra_xudc_runtime_suspend, + tegra_xudc_runtime_resume, NULL) +}; + +static struct platform_driver tegra_xudc_driver = { + .probe = tegra_xudc_probe, + .remove = tegra_xudc_remove, + .driver = { + .name = "tegra-xudc", + .pm = &tegra_xudc_pm_ops, + .of_match_table = tegra_xudc_of_match, + }, +}; +module_platform_driver(tegra_xudc_driver); + +MODULE_DESCRIPTION("NVIDIA Tegra XUSB Device Controller"); +MODULE_AUTHOR("Andrew Bresticker "); +MODULE_AUTHOR("Hui Fu "); +MODULE_AUTHOR("Nagarjuna Kristam "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/gadget/udc/trace.c b/drivers/usb/gadget/udc/trace.c new file mode 100644 index 000000000..19e837de2 --- /dev/null +++ b/drivers/usb/gadget/udc/trace.c @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * trace.c - USB Gadget Framework Trace Support + * + * Copyright (C) 2016 Intel Corporation + * Author: Felipe Balbi + */ + +#define CREATE_TRACE_POINTS +#include "trace.h" diff --git a/drivers/usb/gadget/udc/trace.h b/drivers/usb/gadget/udc/trace.h new file mode 100644 index 000000000..fa3e6ddf0 --- /dev/null +++ b/drivers/usb/gadget/udc/trace.h @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * udc.c - Core UDC Framework + * + * Copyright (C) 2016 Intel Corporation + * Author: Felipe Balbi + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM gadget + +#if !defined(__UDC_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __UDC_TRACE_H + +#include +#include +#include +#include + +DECLARE_EVENT_CLASS(udc_log_gadget, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret), + TP_STRUCT__entry( + __field(enum usb_device_speed, speed) + __field(enum usb_device_speed, max_speed) + __field(enum usb_device_state, state) + __field(unsigned, mA) + __field(unsigned, sg_supported) + __field(unsigned, is_otg) + __field(unsigned, is_a_peripheral) + __field(unsigned, b_hnp_enable) + __field(unsigned, a_hnp_support) + __field(unsigned, hnp_polling_support) + __field(unsigned, host_request_flag) + __field(unsigned, quirk_ep_out_aligned_size) + __field(unsigned, quirk_altset_not_supp) + __field(unsigned, quirk_stall_not_supp) + __field(unsigned, quirk_zlp_not_supp) + __field(unsigned, is_selfpowered) + __field(unsigned, deactivated) + __field(unsigned, connected) + __field(int, ret) + ), + TP_fast_assign( + __entry->speed = g->speed; + __entry->max_speed = g->max_speed; + __entry->state = g->state; + __entry->mA = g->mA; + __entry->sg_supported = g->sg_supported; + __entry->is_otg = g->is_otg; + __entry->is_a_peripheral = g->is_a_peripheral; + __entry->b_hnp_enable = g->b_hnp_enable; + __entry->a_hnp_support = g->a_hnp_support; + __entry->hnp_polling_support = g->hnp_polling_support; + __entry->host_request_flag = g->host_request_flag; + __entry->quirk_ep_out_aligned_size = g->quirk_ep_out_aligned_size; + __entry->quirk_altset_not_supp = g->quirk_altset_not_supp; + __entry->quirk_stall_not_supp = g->quirk_stall_not_supp; + __entry->quirk_zlp_not_supp = g->quirk_zlp_not_supp; + __entry->is_selfpowered = g->is_selfpowered; + __entry->deactivated = g->deactivated; + __entry->connected = g->connected; + __entry->ret = ret; + ), + TP_printk("speed %d/%d state %d %dmA [%s%s%s%s%s%s%s%s%s%s%s%s%s%s] --> %d", + __entry->speed, __entry->max_speed, __entry->state, __entry->mA, + __entry->sg_supported ? "sg:" : "", + __entry->is_otg ? "OTG:" : "", + __entry->is_a_peripheral ? "a_peripheral:" : "", + __entry->b_hnp_enable ? "b_hnp:" : "", + __entry->a_hnp_support ? "a_hnp:" : "", + __entry->hnp_polling_support ? "hnp_poll:" : "", + __entry->host_request_flag ? "hostreq:" : "", + __entry->quirk_ep_out_aligned_size ? "out_aligned:" : "", + __entry->quirk_altset_not_supp ? "no_altset:" : "", + __entry->quirk_stall_not_supp ? "no_stall:" : "", + __entry->quirk_zlp_not_supp ? "no_zlp" : "", + __entry->is_selfpowered ? "self-powered:" : "bus-powered:", + __entry->deactivated ? "deactivated:" : "activated:", + __entry->connected ? "connected" : "disconnected", + __entry->ret) +); + +DEFINE_EVENT(udc_log_gadget, usb_gadget_set_state, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + +DEFINE_EVENT(udc_log_gadget, usb_gadget_frame_number, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + +DEFINE_EVENT(udc_log_gadget, usb_gadget_wakeup, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + +DEFINE_EVENT(udc_log_gadget, usb_gadget_set_remote_wakeup, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + +DEFINE_EVENT(udc_log_gadget, usb_gadget_set_selfpowered, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + +DEFINE_EVENT(udc_log_gadget, usb_gadget_clear_selfpowered, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + +DEFINE_EVENT(udc_log_gadget, usb_gadget_vbus_connect, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + +DEFINE_EVENT(udc_log_gadget, usb_gadget_vbus_draw, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + +DEFINE_EVENT(udc_log_gadget, usb_gadget_vbus_disconnect, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + +DEFINE_EVENT(udc_log_gadget, usb_gadget_connect, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + +DEFINE_EVENT(udc_log_gadget, usb_gadget_disconnect, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + +DEFINE_EVENT(udc_log_gadget, usb_gadget_deactivate, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + +DEFINE_EVENT(udc_log_gadget, usb_gadget_activate, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + +DECLARE_EVENT_CLASS(udc_log_ep, + TP_PROTO(struct usb_ep *ep, int ret), + TP_ARGS(ep, ret), + TP_STRUCT__entry( + __string(name, ep->name) + __field(unsigned, maxpacket) + __field(unsigned, maxpacket_limit) + __field(unsigned, max_streams) + __field(unsigned, mult) + __field(unsigned, maxburst) + __field(u8, address) + __field(bool, claimed) + __field(bool, enabled) + __field(int, ret) + ), + TP_fast_assign( + __assign_str(name); + __entry->maxpacket = ep->maxpacket; + __entry->maxpacket_limit = ep->maxpacket_limit; + __entry->max_streams = ep->max_streams; + __entry->mult = ep->mult; + __entry->maxburst = ep->maxburst; + __entry->address = ep->address, + __entry->claimed = ep->claimed; + __entry->enabled = ep->enabled; + __entry->ret = ret; + ), + TP_printk("%s: mps %d/%d streams %d mult %d burst %d addr %02x %s%s --> %d", + __get_str(name), __entry->maxpacket, __entry->maxpacket_limit, + __entry->max_streams, __entry->mult, __entry->maxburst, + __entry->address, __entry->claimed ? "claimed:" : "released:", + __entry->enabled ? "enabled" : "disabled", ret) +); + +DEFINE_EVENT(udc_log_ep, usb_ep_set_maxpacket_limit, + TP_PROTO(struct usb_ep *ep, int ret), + TP_ARGS(ep, ret) +); + +DEFINE_EVENT(udc_log_ep, usb_ep_enable, + TP_PROTO(struct usb_ep *ep, int ret), + TP_ARGS(ep, ret) +); + +DEFINE_EVENT(udc_log_ep, usb_ep_disable, + TP_PROTO(struct usb_ep *ep, int ret), + TP_ARGS(ep, ret) +); + +DEFINE_EVENT(udc_log_ep, usb_ep_set_halt, + TP_PROTO(struct usb_ep *ep, int ret), + TP_ARGS(ep, ret) +); + +DEFINE_EVENT(udc_log_ep, usb_ep_clear_halt, + TP_PROTO(struct usb_ep *ep, int ret), + TP_ARGS(ep, ret) +); + +DEFINE_EVENT(udc_log_ep, usb_ep_set_wedge, + TP_PROTO(struct usb_ep *ep, int ret), + TP_ARGS(ep, ret) +); + +DEFINE_EVENT(udc_log_ep, usb_ep_fifo_status, + TP_PROTO(struct usb_ep *ep, int ret), + TP_ARGS(ep, ret) +); + +DEFINE_EVENT(udc_log_ep, usb_ep_fifo_flush, + TP_PROTO(struct usb_ep *ep, int ret), + TP_ARGS(ep, ret) +); + +DECLARE_EVENT_CLASS(udc_log_req, + TP_PROTO(struct usb_ep *ep, struct usb_request *req, int ret), + TP_ARGS(ep, req, ret), + TP_STRUCT__entry( + __string(name, ep->name) + __field(unsigned, length) + __field(unsigned, actual) + __field(unsigned, num_sgs) + __field(unsigned, num_mapped_sgs) + __field(unsigned, stream_id) + __field(unsigned, no_interrupt) + __field(unsigned, zero) + __field(unsigned, short_not_ok) + __field(int, status) + __field(int, ret) + __field(struct usb_request *, req) + ), + TP_fast_assign( + __assign_str(name); + __entry->length = req->length; + __entry->actual = req->actual; + __entry->num_sgs = req->num_sgs; + __entry->num_mapped_sgs = req->num_mapped_sgs; + __entry->stream_id = req->stream_id; + __entry->no_interrupt = req->no_interrupt; + __entry->zero = req->zero; + __entry->short_not_ok = req->short_not_ok; + __entry->status = req->status; + __entry->ret = ret; + __entry->req = req; + ), + TP_printk("%s: req %p length %d/%d sgs %d/%d stream %d %s%s%s status %d --> %d", + __get_str(name),__entry->req, __entry->actual, __entry->length, + __entry->num_mapped_sgs, __entry->num_sgs, __entry->stream_id, + __entry->zero ? "Z" : "z", + __entry->short_not_ok ? "S" : "s", + __entry->no_interrupt ? "i" : "I", + __entry->status, __entry->ret + ) +); + +DEFINE_EVENT(udc_log_req, usb_ep_alloc_request, + TP_PROTO(struct usb_ep *ep, struct usb_request *req, int ret), + TP_ARGS(ep, req, ret) +); + +DEFINE_EVENT(udc_log_req, usb_ep_free_request, + TP_PROTO(struct usb_ep *ep, struct usb_request *req, int ret), + TP_ARGS(ep, req, ret) +); + +DEFINE_EVENT(udc_log_req, usb_ep_queue, + TP_PROTO(struct usb_ep *ep, struct usb_request *req, int ret), + TP_ARGS(ep, req, ret) +); + +DEFINE_EVENT(udc_log_req, usb_ep_dequeue, + TP_PROTO(struct usb_ep *ep, struct usb_request *req, int ret), + TP_ARGS(ep, req, ret) +); + +DEFINE_EVENT(udc_log_req, usb_gadget_giveback_request, + TP_PROTO(struct usb_ep *ep, struct usb_request *req, int ret), + TP_ARGS(ep, req, ret) +); + +#endif /* __UDC_TRACE_H */ + +/* this part has to be here */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#include diff --git a/drivers/usb/gadget/udc/udc-xilinx.c b/drivers/usb/gadget/udc/udc-xilinx.c new file mode 100644 index 000000000..bef06fe75 --- /dev/null +++ b/drivers/usb/gadget/udc/udc-xilinx.c @@ -0,0 +1,2266 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Xilinx USB peripheral controller driver + * + * Copyright (C) 2004 by Thomas Rathbone + * Copyright (C) 2005 by HP Labs + * Copyright (C) 2005 by David Brownell + * Copyright (C) 2010 - 2014 Xilinx, Inc. + * + * Some parts of this driver code is based on the driver for at91-series + * USB peripheral controller (at91_udc.c). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register offsets for the USB device.*/ +#define XUSB_EP0_CONFIG_OFFSET 0x0000 /* EP0 Config Reg Offset */ +#define XUSB_SETUP_PKT_ADDR_OFFSET 0x0080 /* Setup Packet Address */ +#define XUSB_ADDRESS_OFFSET 0x0100 /* Address Register */ +#define XUSB_CONTROL_OFFSET 0x0104 /* Control Register */ +#define XUSB_STATUS_OFFSET 0x0108 /* Status Register */ +#define XUSB_FRAMENUM_OFFSET 0x010C /* Frame Number Register */ +#define XUSB_IER_OFFSET 0x0110 /* Interrupt Enable Register */ +#define XUSB_BUFFREADY_OFFSET 0x0114 /* Buffer Ready Register */ +#define XUSB_TESTMODE_OFFSET 0x0118 /* Test Mode Register */ +#define XUSB_DMA_RESET_OFFSET 0x0200 /* DMA Soft Reset Register */ +#define XUSB_DMA_CONTROL_OFFSET 0x0204 /* DMA Control Register */ +#define XUSB_DMA_DSAR_ADDR_OFFSET 0x0208 /* DMA source Address Reg */ +#define XUSB_DMA_DDAR_ADDR_OFFSET 0x020C /* DMA destination Addr Reg */ +#define XUSB_DMA_LENGTH_OFFSET 0x0210 /* DMA Length Register */ +#define XUSB_DMA_STATUS_OFFSET 0x0214 /* DMA Status Register */ + +/* Endpoint Configuration Space offsets */ +#define XUSB_EP_CFGSTATUS_OFFSET 0x00 /* Endpoint Config Status */ +#define XUSB_EP_BUF0COUNT_OFFSET 0x08 /* Buffer 0 Count */ +#define XUSB_EP_BUF1COUNT_OFFSET 0x0C /* Buffer 1 Count */ + +#define XUSB_CONTROL_USB_READY_MASK 0x80000000 /* USB ready Mask */ +#define XUSB_CONTROL_USB_RMTWAKE_MASK 0x40000000 /* Remote wake up mask */ + +/* Interrupt register related masks.*/ +#define XUSB_STATUS_GLOBAL_INTR_MASK 0x80000000 /* Global Intr Enable */ +#define XUSB_STATUS_DMADONE_MASK 0x04000000 /* DMA done Mask */ +#define XUSB_STATUS_DMAERR_MASK 0x02000000 /* DMA Error Mask */ +#define XUSB_STATUS_DMABUSY_MASK 0x80000000 /* DMA Error Mask */ +#define XUSB_STATUS_RESUME_MASK 0x01000000 /* USB Resume Mask */ +#define XUSB_STATUS_RESET_MASK 0x00800000 /* USB Reset Mask */ +#define XUSB_STATUS_SUSPEND_MASK 0x00400000 /* USB Suspend Mask */ +#define XUSB_STATUS_DISCONNECT_MASK 0x00200000 /* USB Disconnect Mask */ +#define XUSB_STATUS_FIFO_BUFF_RDY_MASK 0x00100000 /* FIFO Buff Ready Mask */ +#define XUSB_STATUS_FIFO_BUFF_FREE_MASK 0x00080000 /* FIFO Buff Free Mask */ +#define XUSB_STATUS_SETUP_PACKET_MASK 0x00040000 /* Setup packet received */ +#define XUSB_STATUS_EP1_BUFF2_COMP_MASK 0x00000200 /* EP 1 Buff 2 Processed */ +#define XUSB_STATUS_EP1_BUFF1_COMP_MASK 0x00000002 /* EP 1 Buff 1 Processed */ +#define XUSB_STATUS_EP0_BUFF2_COMP_MASK 0x00000100 /* EP 0 Buff 2 Processed */ +#define XUSB_STATUS_EP0_BUFF1_COMP_MASK 0x00000001 /* EP 0 Buff 1 Processed */ +#define XUSB_STATUS_HIGH_SPEED_MASK 0x00010000 /* USB Speed Mask */ +/* Suspend,Reset,Suspend and Disconnect Mask */ +#define XUSB_STATUS_INTR_EVENT_MASK 0x01E00000 +/* Buffers completion Mask */ +#define XUSB_STATUS_INTR_BUFF_COMP_ALL_MASK 0x0000FEFF +/* Mask for buffer 0 and buffer 1 completion for all Endpoints */ +#define XUSB_STATUS_INTR_BUFF_COMP_SHIFT_MASK 0x00000101 +#define XUSB_STATUS_EP_BUFF2_SHIFT 8 /* EP buffer offset */ + +/* Endpoint Configuration Status Register */ +#define XUSB_EP_CFG_VALID_MASK 0x80000000 /* Endpoint Valid bit */ +#define XUSB_EP_CFG_STALL_MASK 0x40000000 /* Endpoint Stall bit */ +#define XUSB_EP_CFG_DATA_TOGGLE_MASK 0x08000000 /* Endpoint Data toggle */ + +/* USB device specific global configuration constants.*/ +#define XUSB_MAX_ENDPOINTS 8 /* Maximum End Points */ +#define XUSB_EP_NUMBER_ZERO 0 /* End point Zero */ +/* DPRAM is the source address for DMA transfer */ +#define XUSB_DMA_READ_FROM_DPRAM 0x80000000 +#define XUSB_DMA_DMASR_BUSY 0x80000000 /* DMA busy */ +#define XUSB_DMA_DMASR_ERROR 0x40000000 /* DMA Error */ +/* + * When this bit is set, the DMA buffer ready bit is set by hardware upon + * DMA transfer completion. + */ +#define XUSB_DMA_BRR_CTRL 0x40000000 /* DMA bufready ctrl bit */ +/* Phase States */ +#define SETUP_PHASE 0x0000 /* Setup Phase */ +#define DATA_PHASE 0x0001 /* Data Phase */ +#define STATUS_PHASE 0x0002 /* Status Phase */ + +#define EP0_MAX_PACKET 64 /* Endpoint 0 maximum packet length */ +#define STATUSBUFF_SIZE 2 /* Buffer size for GET_STATUS command */ +#define EPNAME_SIZE 4 /* Buffer size for endpoint name */ + +/* container_of helper macros */ +#define to_udc(g) container_of((g), struct xusb_udc, gadget) +#define to_xusb_ep(ep) container_of((ep), struct xusb_ep, ep_usb) +#define to_xusb_req(req) container_of((req), struct xusb_req, usb_req) + +/** + * struct xusb_req - Xilinx USB device request structure + * @usb_req: Linux usb request structure + * @queue: usb device request queue + * @ep: pointer to xusb_endpoint structure + */ +struct xusb_req { + struct usb_request usb_req; + struct list_head queue; + struct xusb_ep *ep; +}; + +/** + * struct xusb_ep - USB end point structure. + * @ep_usb: usb endpoint instance + * @queue: endpoint message queue + * @udc: xilinx usb peripheral driver instance pointer + * @desc: pointer to the usb endpoint descriptor + * @rambase: the endpoint buffer address + * @offset: the endpoint register offset value + * @name: name of the endpoint + * @epnumber: endpoint number + * @maxpacket: maximum packet size the endpoint can store + * @buffer0count: the size of the packet recieved in the first buffer + * @buffer1count: the size of the packet received in the second buffer + * @curbufnum: current buffer of endpoint that will be processed next + * @buffer0ready: the busy state of first buffer + * @buffer1ready: the busy state of second buffer + * @is_in: endpoint direction (IN or OUT) + * @is_iso: endpoint type(isochronous or non isochronous) + */ +struct xusb_ep { + struct usb_ep ep_usb; + struct list_head queue; + struct xusb_udc *udc; + const struct usb_endpoint_descriptor *desc; + u32 rambase; + u32 offset; + char name[4]; + u16 epnumber; + u16 maxpacket; + u16 buffer0count; + u16 buffer1count; + u8 curbufnum; + bool buffer0ready; + bool buffer1ready; + bool is_in; + bool is_iso; +}; + +/** + * struct xusb_udc - USB peripheral driver structure + * @gadget: USB gadget driver instance + * @ep: an array of endpoint structures + * @driver: pointer to the usb gadget driver instance + * @setup: usb_ctrlrequest structure for control requests + * @req: pointer to dummy request for get status command + * @dev: pointer to device structure in gadget + * @usb_state: device in suspended state or not + * @remote_wkp: remote wakeup enabled by host + * @setupseqtx: tx status + * @setupseqrx: rx status + * @addr: the usb device base address + * @lock: instance of spinlock + * @dma_enabled: flag indicating whether the dma is included in the system + * @clk: pointer to struct clk + * @read_fn: function pointer to read device registers + * @write_fn: function pointer to write to device registers + */ +struct xusb_udc { + struct usb_gadget gadget; + struct xusb_ep ep[8]; + struct usb_gadget_driver *driver; + struct usb_ctrlrequest setup; + struct xusb_req *req; + struct device *dev; + u32 usb_state; + u32 remote_wkp; + u32 setupseqtx; + u32 setupseqrx; + void __iomem *addr; + spinlock_t lock; + bool dma_enabled; + struct clk *clk; + + unsigned int (*read_fn)(void __iomem *reg); + void (*write_fn)(void __iomem *, u32, u32); +}; + +/* Endpoint buffer start addresses in the core */ +static u32 rambase[8] = { 0x22, 0x1000, 0x1100, 0x1200, 0x1300, 0x1400, 0x1500, + 0x1600 }; + +static const char driver_name[] = "xilinx-udc"; +static const char ep0name[] = "ep0"; + +/* Control endpoint configuration.*/ +static const struct usb_endpoint_descriptor config_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(EP0_MAX_PACKET), +}; + +/** + * xudc_write32 - little endian write to device registers + * @addr: base addr of device registers + * @offset: register offset + * @val: data to be written + */ +static void xudc_write32(void __iomem *addr, u32 offset, u32 val) +{ + iowrite32(val, addr + offset); +} + +/** + * xudc_read32 - little endian read from device registers + * @addr: addr of device register + * Return: value at addr + */ +static unsigned int xudc_read32(void __iomem *addr) +{ + return ioread32(addr); +} + +/** + * xudc_write32_be - big endian write to device registers + * @addr: base addr of device registers + * @offset: register offset + * @val: data to be written + */ +static void xudc_write32_be(void __iomem *addr, u32 offset, u32 val) +{ + iowrite32be(val, addr + offset); +} + +/** + * xudc_read32_be - big endian read from device registers + * @addr: addr of device register + * Return: value at addr + */ +static unsigned int xudc_read32_be(void __iomem *addr) +{ + return ioread32be(addr); +} + +/** + * xudc_wrstatus - Sets up the usb device status stages. + * @udc: pointer to the usb device controller structure. + */ +static void xudc_wrstatus(struct xusb_udc *udc) +{ + struct xusb_ep *ep0 = &udc->ep[XUSB_EP_NUMBER_ZERO]; + u32 epcfgreg; + + epcfgreg = udc->read_fn(udc->addr + ep0->offset)| + XUSB_EP_CFG_DATA_TOGGLE_MASK; + udc->write_fn(udc->addr, ep0->offset, epcfgreg); + udc->write_fn(udc->addr, ep0->offset + XUSB_EP_BUF0COUNT_OFFSET, 0); + udc->write_fn(udc->addr, XUSB_BUFFREADY_OFFSET, 1); +} + +/** + * xudc_epconfig - Configures the given endpoint. + * @ep: pointer to the usb device endpoint structure. + * @udc: pointer to the usb peripheral controller structure. + * + * This function configures a specific endpoint with the given configuration + * data. + */ +static void xudc_epconfig(struct xusb_ep *ep, struct xusb_udc *udc) +{ + u32 epcfgreg; + + /* + * Configure the end point direction, type, Max Packet Size and the + * EP buffer location. + */ + epcfgreg = ((ep->is_in << 29) | (ep->is_iso << 28) | + (ep->ep_usb.maxpacket << 15) | (ep->rambase)); + udc->write_fn(udc->addr, ep->offset, epcfgreg); + + /* Set the Buffer count and the Buffer ready bits.*/ + udc->write_fn(udc->addr, ep->offset + XUSB_EP_BUF0COUNT_OFFSET, + ep->buffer0count); + udc->write_fn(udc->addr, ep->offset + XUSB_EP_BUF1COUNT_OFFSET, + ep->buffer1count); + if (ep->buffer0ready) + udc->write_fn(udc->addr, XUSB_BUFFREADY_OFFSET, + 1 << ep->epnumber); + if (ep->buffer1ready) + udc->write_fn(udc->addr, XUSB_BUFFREADY_OFFSET, + 1 << (ep->epnumber + XUSB_STATUS_EP_BUFF2_SHIFT)); +} + +/** + * xudc_start_dma - Starts DMA transfer. + * @ep: pointer to the usb device endpoint structure. + * @src: DMA source address. + * @dst: DMA destination address. + * @length: number of bytes to transfer. + * + * Return: 0 on success, error code on failure + * + * This function starts DMA transfer by writing to DMA source, + * destination and lenth registers. + */ +static int xudc_start_dma(struct xusb_ep *ep, dma_addr_t src, + dma_addr_t dst, u32 length) +{ + struct xusb_udc *udc = ep->udc; + int rc = 0; + u32 timeout = 500; + u32 reg; + + /* + * Set the addresses in the DMA source and + * destination registers and then set the length + * into the DMA length register. + */ + udc->write_fn(udc->addr, XUSB_DMA_DSAR_ADDR_OFFSET, src); + udc->write_fn(udc->addr, XUSB_DMA_DDAR_ADDR_OFFSET, dst); + udc->write_fn(udc->addr, XUSB_DMA_LENGTH_OFFSET, length); + + /* + * Wait till DMA transaction is complete and + * check whether the DMA transaction was + * successful. + */ + do { + reg = udc->read_fn(udc->addr + XUSB_DMA_STATUS_OFFSET); + if (!(reg & XUSB_DMA_DMASR_BUSY)) + break; + + /* + * We can't sleep here, because it's also called from + * interrupt context. + */ + timeout--; + if (!timeout) { + dev_err(udc->dev, "DMA timeout\n"); + return -ETIMEDOUT; + } + udelay(1); + } while (1); + + if ((udc->read_fn(udc->addr + XUSB_DMA_STATUS_OFFSET) & + XUSB_DMA_DMASR_ERROR) == XUSB_DMA_DMASR_ERROR){ + dev_err(udc->dev, "DMA Error\n"); + rc = -EINVAL; + } + + return rc; +} + +/** + * xudc_dma_send - Sends IN data using DMA. + * @ep: pointer to the usb device endpoint structure. + * @req: pointer to the usb request structure. + * @buffer: pointer to data to be sent. + * @length: number of bytes to send. + * + * Return: 0 on success, -EAGAIN if no buffer is free and error + * code on failure. + * + * This function sends data using DMA. + */ +static int xudc_dma_send(struct xusb_ep *ep, struct xusb_req *req, + u8 *buffer, u32 length) +{ + u32 *eprambase; + dma_addr_t src; + dma_addr_t dst; + struct xusb_udc *udc = ep->udc; + + src = req->usb_req.dma + req->usb_req.actual; + if (req->usb_req.length) + dma_sync_single_for_device(udc->dev, src, + length, DMA_TO_DEVICE); + if (!ep->curbufnum && !ep->buffer0ready) { + /* Get the Buffer address and copy the transmit data.*/ + eprambase = (u32 __force *)(udc->addr + ep->rambase); + dst = virt_to_phys(eprambase); + udc->write_fn(udc->addr, ep->offset + + XUSB_EP_BUF0COUNT_OFFSET, length); + udc->write_fn(udc->addr, XUSB_DMA_CONTROL_OFFSET, + XUSB_DMA_BRR_CTRL | (1 << ep->epnumber)); + ep->buffer0ready = 1; + ep->curbufnum = 1; + } else if (ep->curbufnum && !ep->buffer1ready) { + /* Get the Buffer address and copy the transmit data.*/ + eprambase = (u32 __force *)(udc->addr + ep->rambase + + ep->ep_usb.maxpacket); + dst = virt_to_phys(eprambase); + udc->write_fn(udc->addr, ep->offset + + XUSB_EP_BUF1COUNT_OFFSET, length); + udc->write_fn(udc->addr, XUSB_DMA_CONTROL_OFFSET, + XUSB_DMA_BRR_CTRL | (1 << (ep->epnumber + + XUSB_STATUS_EP_BUFF2_SHIFT))); + ep->buffer1ready = 1; + ep->curbufnum = 0; + } else { + /* None of ping pong buffers are ready currently .*/ + return -EAGAIN; + } + + return xudc_start_dma(ep, src, dst, length); +} + +/** + * xudc_dma_receive - Receives OUT data using DMA. + * @ep: pointer to the usb device endpoint structure. + * @req: pointer to the usb request structure. + * @buffer: pointer to storage buffer of received data. + * @length: number of bytes to receive. + * + * Return: 0 on success, -EAGAIN if no buffer is free and error + * code on failure. + * + * This function receives data using DMA. + */ +static int xudc_dma_receive(struct xusb_ep *ep, struct xusb_req *req, + u8 *buffer, u32 length) +{ + u32 *eprambase; + dma_addr_t src; + dma_addr_t dst; + struct xusb_udc *udc = ep->udc; + + dst = req->usb_req.dma + req->usb_req.actual; + if (!ep->curbufnum && !ep->buffer0ready) { + /* Get the Buffer address and copy the transmit data */ + eprambase = (u32 __force *)(udc->addr + ep->rambase); + src = virt_to_phys(eprambase); + udc->write_fn(udc->addr, XUSB_DMA_CONTROL_OFFSET, + XUSB_DMA_BRR_CTRL | XUSB_DMA_READ_FROM_DPRAM | + (1 << ep->epnumber)); + ep->buffer0ready = 1; + ep->curbufnum = 1; + } else if (ep->curbufnum && !ep->buffer1ready) { + /* Get the Buffer address and copy the transmit data */ + eprambase = (u32 __force *)(udc->addr + + ep->rambase + ep->ep_usb.maxpacket); + src = virt_to_phys(eprambase); + udc->write_fn(udc->addr, XUSB_DMA_CONTROL_OFFSET, + XUSB_DMA_BRR_CTRL | XUSB_DMA_READ_FROM_DPRAM | + (1 << (ep->epnumber + + XUSB_STATUS_EP_BUFF2_SHIFT))); + ep->buffer1ready = 1; + ep->curbufnum = 0; + } else { + /* None of the ping-pong buffers are ready currently */ + return -EAGAIN; + } + + return xudc_start_dma(ep, src, dst, length); +} + +/** + * xudc_eptxrx - Transmits or receives data to or from an endpoint. + * @ep: pointer to the usb endpoint configuration structure. + * @req: pointer to the usb request structure. + * @bufferptr: pointer to buffer containing the data to be sent. + * @bufferlen: The number of data bytes to be sent. + * + * Return: 0 on success, -EAGAIN if no buffer is free. + * + * This function copies the transmit/receive data to/from the end point buffer + * and enables the buffer for transmission/reception. + */ +static int xudc_eptxrx(struct xusb_ep *ep, struct xusb_req *req, + u8 *bufferptr, u32 bufferlen) +{ + u32 *eprambase; + u32 bytestosend; + int rc = 0; + struct xusb_udc *udc = ep->udc; + + bytestosend = bufferlen; + if (udc->dma_enabled) { + if (ep->is_in) + rc = xudc_dma_send(ep, req, bufferptr, bufferlen); + else + rc = xudc_dma_receive(ep, req, bufferptr, bufferlen); + return rc; + } + /* Put the transmit buffer into the correct ping-pong buffer.*/ + if (!ep->curbufnum && !ep->buffer0ready) { + /* Get the Buffer address and copy the transmit data.*/ + eprambase = (u32 __force *)(udc->addr + ep->rambase); + if (ep->is_in) { + memcpy_toio((void __iomem *)eprambase, bufferptr, + bytestosend); + udc->write_fn(udc->addr, ep->offset + + XUSB_EP_BUF0COUNT_OFFSET, bufferlen); + } else { + memcpy_toio((void __iomem *)bufferptr, eprambase, + bytestosend); + } + /* + * Enable the buffer for transmission. + */ + udc->write_fn(udc->addr, XUSB_BUFFREADY_OFFSET, + 1 << ep->epnumber); + ep->buffer0ready = 1; + ep->curbufnum = 1; + } else if (ep->curbufnum && !ep->buffer1ready) { + /* Get the Buffer address and copy the transmit data.*/ + eprambase = (u32 __force *)(udc->addr + ep->rambase + + ep->ep_usb.maxpacket); + if (ep->is_in) { + memcpy_toio((void __iomem *)eprambase, bufferptr, + bytestosend); + udc->write_fn(udc->addr, ep->offset + + XUSB_EP_BUF1COUNT_OFFSET, bufferlen); + } else { + memcpy_toio((void __iomem *)bufferptr, eprambase, + bytestosend); + } + /* + * Enable the buffer for transmission. + */ + udc->write_fn(udc->addr, XUSB_BUFFREADY_OFFSET, + 1 << (ep->epnumber + XUSB_STATUS_EP_BUFF2_SHIFT)); + ep->buffer1ready = 1; + ep->curbufnum = 0; + } else { + /* None of the ping-pong buffers are ready currently */ + return -EAGAIN; + } + return rc; +} + +/** + * xudc_done - Exeutes the endpoint data transfer completion tasks. + * @ep: pointer to the usb device endpoint structure. + * @req: pointer to the usb request structure. + * @status: Status of the data transfer. + * + * Deletes the message from the queue and updates data transfer completion + * status. + */ +static void xudc_done(struct xusb_ep *ep, struct xusb_req *req, int status) +{ + struct xusb_udc *udc = ep->udc; + + list_del_init(&req->queue); + + if (req->usb_req.status == -EINPROGRESS) + req->usb_req.status = status; + else + status = req->usb_req.status; + + if (status && status != -ESHUTDOWN) + dev_dbg(udc->dev, "%s done %p, status %d\n", + ep->ep_usb.name, req, status); + /* unmap request if DMA is present*/ + if (udc->dma_enabled && ep->epnumber && req->usb_req.length) + usb_gadget_unmap_request(&udc->gadget, &req->usb_req, + ep->is_in); + + if (req->usb_req.complete) { + spin_unlock(&udc->lock); + req->usb_req.complete(&ep->ep_usb, &req->usb_req); + spin_lock(&udc->lock); + } +} + +/** + * xudc_read_fifo - Reads the data from the given endpoint buffer. + * @ep: pointer to the usb device endpoint structure. + * @req: pointer to the usb request structure. + * + * Return: 0 if request is completed and -EAGAIN if not completed. + * + * Pulls OUT packet data from the endpoint buffer. + */ +static int xudc_read_fifo(struct xusb_ep *ep, struct xusb_req *req) +{ + u8 *buf; + u32 is_short, count, bufferspace; + u8 bufoffset; + u8 two_pkts = 0; + int ret; + int retval = -EAGAIN; + struct xusb_udc *udc = ep->udc; + + if (ep->buffer0ready && ep->buffer1ready) { + dev_dbg(udc->dev, "Packet NOT ready!\n"); + return retval; + } +top: + if (ep->curbufnum) + bufoffset = XUSB_EP_BUF1COUNT_OFFSET; + else + bufoffset = XUSB_EP_BUF0COUNT_OFFSET; + + count = udc->read_fn(udc->addr + ep->offset + bufoffset); + + if (!ep->buffer0ready && !ep->buffer1ready) + two_pkts = 1; + + buf = req->usb_req.buf + req->usb_req.actual; + prefetchw(buf); + bufferspace = req->usb_req.length - req->usb_req.actual; + is_short = count < ep->ep_usb.maxpacket; + + if (unlikely(!bufferspace)) { + /* + * This happens when the driver's buffer + * is smaller than what the host sent. + * discard the extra data. + */ + if (req->usb_req.status != -EOVERFLOW) + dev_dbg(udc->dev, "%s overflow %d\n", + ep->ep_usb.name, count); + req->usb_req.status = -EOVERFLOW; + xudc_done(ep, req, -EOVERFLOW); + return 0; + } + + ret = xudc_eptxrx(ep, req, buf, count); + switch (ret) { + case 0: + req->usb_req.actual += min(count, bufferspace); + dev_dbg(udc->dev, "read %s, %d bytes%s req %p %d/%d\n", + ep->ep_usb.name, count, is_short ? "/S" : "", req, + req->usb_req.actual, req->usb_req.length); + + /* Completion */ + if ((req->usb_req.actual == req->usb_req.length) || is_short) { + if (udc->dma_enabled && req->usb_req.length) + dma_sync_single_for_cpu(udc->dev, + req->usb_req.dma, + req->usb_req.actual, + DMA_FROM_DEVICE); + xudc_done(ep, req, 0); + return 0; + } + if (two_pkts) { + two_pkts = 0; + goto top; + } + break; + case -EAGAIN: + dev_dbg(udc->dev, "receive busy\n"); + break; + case -EINVAL: + case -ETIMEDOUT: + /* DMA error, dequeue the request */ + xudc_done(ep, req, -ECONNRESET); + retval = 0; + break; + } + + return retval; +} + +/** + * xudc_write_fifo - Writes data into the given endpoint buffer. + * @ep: pointer to the usb device endpoint structure. + * @req: pointer to the usb request structure. + * + * Return: 0 if request is completed and -EAGAIN if not completed. + * + * Loads endpoint buffer for an IN packet. + */ +static int xudc_write_fifo(struct xusb_ep *ep, struct xusb_req *req) +{ + u32 max; + u32 length; + int ret; + int retval = -EAGAIN; + struct xusb_udc *udc = ep->udc; + int is_last, is_short = 0; + u8 *buf; + + max = le16_to_cpu(ep->desc->wMaxPacketSize); + buf = req->usb_req.buf + req->usb_req.actual; + prefetch(buf); + length = req->usb_req.length - req->usb_req.actual; + length = min(length, max); + + ret = xudc_eptxrx(ep, req, buf, length); + switch (ret) { + case 0: + req->usb_req.actual += length; + if (unlikely(length != max)) { + is_last = is_short = 1; + } else { + if (likely(req->usb_req.length != + req->usb_req.actual) || req->usb_req.zero) + is_last = 0; + else + is_last = 1; + } + dev_dbg(udc->dev, "%s: wrote %s %d bytes%s%s %d left %p\n", + __func__, ep->ep_usb.name, length, is_last ? "/L" : "", + is_short ? "/S" : "", + req->usb_req.length - req->usb_req.actual, req); + /* completion */ + if (is_last) { + xudc_done(ep, req, 0); + retval = 0; + } + break; + case -EAGAIN: + dev_dbg(udc->dev, "Send busy\n"); + break; + case -EINVAL: + case -ETIMEDOUT: + /* DMA error, dequeue the request */ + xudc_done(ep, req, -ECONNRESET); + retval = 0; + break; + } + + return retval; +} + +/** + * xudc_nuke - Cleans up the data transfer message list. + * @ep: pointer to the usb device endpoint structure. + * @status: Status of the data transfer. + */ +static void xudc_nuke(struct xusb_ep *ep, int status) +{ + struct xusb_req *req; + + while (!list_empty(&ep->queue)) { + req = list_first_entry(&ep->queue, struct xusb_req, queue); + xudc_done(ep, req, status); + } +} + +/** + * xudc_ep_set_halt - Stalls/unstalls the given endpoint. + * @_ep: pointer to the usb device endpoint structure. + * @value: value to indicate stall/unstall. + * + * Return: 0 for success and error value on failure + */ +static int xudc_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct xusb_ep *ep = to_xusb_ep(_ep); + struct xusb_udc *udc; + unsigned long flags; + u32 epcfgreg; + + if (!_ep || (!ep->desc && ep->epnumber)) { + pr_debug("%s: bad ep or descriptor\n", __func__); + return -EINVAL; + } + udc = ep->udc; + + if (ep->is_in && (!list_empty(&ep->queue)) && value) { + dev_dbg(udc->dev, "requests pending can't halt\n"); + return -EAGAIN; + } + + if (ep->buffer0ready || ep->buffer1ready) { + dev_dbg(udc->dev, "HW buffers busy can't halt\n"); + return -EAGAIN; + } + + spin_lock_irqsave(&udc->lock, flags); + + if (value) { + /* Stall the device.*/ + epcfgreg = udc->read_fn(udc->addr + ep->offset); + epcfgreg |= XUSB_EP_CFG_STALL_MASK; + udc->write_fn(udc->addr, ep->offset, epcfgreg); + } else { + /* Unstall the device.*/ + epcfgreg = udc->read_fn(udc->addr + ep->offset); + epcfgreg &= ~XUSB_EP_CFG_STALL_MASK; + udc->write_fn(udc->addr, ep->offset, epcfgreg); + if (ep->epnumber) { + /* Reset the toggle bit.*/ + epcfgreg = udc->read_fn(ep->udc->addr + ep->offset); + epcfgreg &= ~XUSB_EP_CFG_DATA_TOGGLE_MASK; + udc->write_fn(udc->addr, ep->offset, epcfgreg); + } + } + + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +/** + * __xudc_ep_enable - Enables the given endpoint. + * @ep: pointer to the xusb endpoint structure. + * @desc: pointer to usb endpoint descriptor. + * + * Return: 0 for success and error value on failure + */ +static int __xudc_ep_enable(struct xusb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct xusb_udc *udc = ep->udc; + u32 tmp; + u32 epcfg; + u32 ier; + u16 maxpacket; + + ep->is_in = ((desc->bEndpointAddress & USB_DIR_IN) != 0); + /* Bit 3...0:endpoint number */ + ep->epnumber = usb_endpoint_num(desc); + ep->desc = desc; + ep->ep_usb.desc = desc; + tmp = usb_endpoint_type(desc); + ep->ep_usb.maxpacket = maxpacket = le16_to_cpu(desc->wMaxPacketSize); + + switch (tmp) { + case USB_ENDPOINT_XFER_CONTROL: + dev_dbg(udc->dev, "only one control endpoint\n"); + /* NON- ISO */ + ep->is_iso = 0; + return -EINVAL; + case USB_ENDPOINT_XFER_INT: + /* NON- ISO */ + ep->is_iso = 0; + if (maxpacket > 64) { + dev_dbg(udc->dev, "bogus maxpacket %d\n", maxpacket); + return -EINVAL; + } + break; + case USB_ENDPOINT_XFER_BULK: + /* NON- ISO */ + ep->is_iso = 0; + if (!(is_power_of_2(maxpacket) && maxpacket >= 8 && + maxpacket <= 512)) { + dev_dbg(udc->dev, "bogus maxpacket %d\n", maxpacket); + return -EINVAL; + } + break; + case USB_ENDPOINT_XFER_ISOC: + /* ISO */ + ep->is_iso = 1; + break; + } + + ep->buffer0ready = false; + ep->buffer1ready = false; + ep->curbufnum = 0; + ep->rambase = rambase[ep->epnumber]; + xudc_epconfig(ep, udc); + + dev_dbg(udc->dev, "Enable Endpoint %d max pkt is %d\n", + ep->epnumber, maxpacket); + + /* Enable the End point.*/ + epcfg = udc->read_fn(udc->addr + ep->offset); + epcfg |= XUSB_EP_CFG_VALID_MASK; + udc->write_fn(udc->addr, ep->offset, epcfg); + if (ep->epnumber) + ep->rambase <<= 2; + + /* Enable buffer completion interrupts for endpoint */ + ier = udc->read_fn(udc->addr + XUSB_IER_OFFSET); + ier |= (XUSB_STATUS_INTR_BUFF_COMP_SHIFT_MASK << ep->epnumber); + udc->write_fn(udc->addr, XUSB_IER_OFFSET, ier); + + /* for OUT endpoint set buffers ready to receive */ + if (ep->epnumber && !ep->is_in) { + udc->write_fn(udc->addr, XUSB_BUFFREADY_OFFSET, + 1 << ep->epnumber); + ep->buffer0ready = true; + udc->write_fn(udc->addr, XUSB_BUFFREADY_OFFSET, + (1 << (ep->epnumber + + XUSB_STATUS_EP_BUFF2_SHIFT))); + ep->buffer1ready = true; + } + + return 0; +} + +/** + * xudc_ep_enable - Enables the given endpoint. + * @_ep: pointer to the usb endpoint structure. + * @desc: pointer to usb endpoint descriptor. + * + * Return: 0 for success and error value on failure + */ +static int xudc_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct xusb_ep *ep; + struct xusb_udc *udc; + unsigned long flags; + int ret; + + if (!_ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) { + pr_debug("%s: bad ep or descriptor\n", __func__); + return -EINVAL; + } + + ep = to_xusb_ep(_ep); + udc = ep->udc; + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) { + dev_dbg(udc->dev, "bogus device state\n"); + return -ESHUTDOWN; + } + + spin_lock_irqsave(&udc->lock, flags); + ret = __xudc_ep_enable(ep, desc); + spin_unlock_irqrestore(&udc->lock, flags); + + return ret; +} + +/** + * xudc_ep_disable - Disables the given endpoint. + * @_ep: pointer to the usb endpoint structure. + * + * Return: 0 for success and error value on failure + */ +static int xudc_ep_disable(struct usb_ep *_ep) +{ + struct xusb_ep *ep; + unsigned long flags; + u32 epcfg; + struct xusb_udc *udc; + + if (!_ep) { + pr_debug("%s: invalid ep\n", __func__); + return -EINVAL; + } + + ep = to_xusb_ep(_ep); + udc = ep->udc; + + spin_lock_irqsave(&udc->lock, flags); + + xudc_nuke(ep, -ESHUTDOWN); + + /* Restore the endpoint's pristine config */ + ep->desc = NULL; + ep->ep_usb.desc = NULL; + + dev_dbg(udc->dev, "USB Ep %d disable\n", ep->epnumber); + /* Disable the endpoint.*/ + epcfg = udc->read_fn(udc->addr + ep->offset); + epcfg &= ~XUSB_EP_CFG_VALID_MASK; + udc->write_fn(udc->addr, ep->offset, epcfg); + + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +/** + * xudc_ep_alloc_request - Initializes the request queue. + * @_ep: pointer to the usb endpoint structure. + * @gfp_flags: Flags related to the request call. + * + * Return: pointer to request structure on success and a NULL on failure. + */ +static struct usb_request *xudc_ep_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct xusb_ep *ep = to_xusb_ep(_ep); + struct xusb_req *req; + + req = kzalloc_obj(*req, gfp_flags); + if (!req) + return NULL; + + req->ep = ep; + INIT_LIST_HEAD(&req->queue); + return &req->usb_req; +} + +/** + * xudc_free_request - Releases the request from queue. + * @_ep: pointer to the usb device endpoint structure. + * @_req: pointer to the usb request structure. + */ +static void xudc_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct xusb_req *req = to_xusb_req(_req); + + kfree(req); +} + +/** + * __xudc_ep0_queue - Adds the request to endpoint 0 queue. + * @ep0: pointer to the xusb endpoint 0 structure. + * @req: pointer to the xusb request structure. + * + * Return: 0 for success and error value on failure + */ +static int __xudc_ep0_queue(struct xusb_ep *ep0, struct xusb_req *req) +{ + struct xusb_udc *udc = ep0->udc; + u32 length; + u8 *corebuf; + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) { + dev_dbg(udc->dev, "%s, bogus device state\n", __func__); + return -EINVAL; + } + if (!list_empty(&ep0->queue)) { + dev_dbg(udc->dev, "%s:ep0 busy\n", __func__); + return -EBUSY; + } + + req->usb_req.status = -EINPROGRESS; + req->usb_req.actual = 0; + + list_add_tail(&req->queue, &ep0->queue); + + if (udc->setup.bRequestType & USB_DIR_IN) { + prefetch(req->usb_req.buf); + length = req->usb_req.length; + corebuf = (void __force *) ((ep0->rambase << 2) + + udc->addr); + length = req->usb_req.actual = min_t(u32, length, + EP0_MAX_PACKET); + memcpy_toio((void __iomem *)corebuf, req->usb_req.buf, length); + udc->write_fn(udc->addr, XUSB_EP_BUF0COUNT_OFFSET, length); + udc->write_fn(udc->addr, XUSB_BUFFREADY_OFFSET, 1); + } else { + if (udc->setup.wLength) { + /* Enable EP0 buffer to receive data */ + udc->write_fn(udc->addr, XUSB_EP_BUF0COUNT_OFFSET, 0); + udc->write_fn(udc->addr, XUSB_BUFFREADY_OFFSET, 1); + } else { + xudc_wrstatus(udc); + } + } + + return 0; +} + +/** + * xudc_ep0_queue - Adds the request to endpoint 0 queue. + * @_ep: pointer to the usb endpoint 0 structure. + * @_req: pointer to the usb request structure. + * @gfp_flags: Flags related to the request call. + * + * Return: 0 for success and error value on failure + */ +static int xudc_ep0_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct xusb_req *req = to_xusb_req(_req); + struct xusb_ep *ep0 = to_xusb_ep(_ep); + struct xusb_udc *udc = ep0->udc; + unsigned long flags; + int ret; + + spin_lock_irqsave(&udc->lock, flags); + ret = __xudc_ep0_queue(ep0, req); + spin_unlock_irqrestore(&udc->lock, flags); + + return ret; +} + +/** + * xudc_ep_queue - Adds the request to endpoint queue. + * @_ep: pointer to the usb endpoint structure. + * @_req: pointer to the usb request structure. + * @gfp_flags: Flags related to the request call. + * + * Return: 0 for success and error value on failure + */ +static int xudc_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct xusb_req *req = to_xusb_req(_req); + struct xusb_ep *ep = to_xusb_ep(_ep); + struct xusb_udc *udc = ep->udc; + int ret; + unsigned long flags; + + if (!ep->desc) { + dev_dbg(udc->dev, "%s: queuing request to disabled %s\n", + __func__, ep->name); + return -ESHUTDOWN; + } + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) { + dev_dbg(udc->dev, "%s, bogus device state\n", __func__); + return -EINVAL; + } + + spin_lock_irqsave(&udc->lock, flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + if (udc->dma_enabled) { + ret = usb_gadget_map_request(&udc->gadget, &req->usb_req, + ep->is_in); + if (ret) { + dev_dbg(udc->dev, "gadget_map failed ep%d\n", + ep->epnumber); + spin_unlock_irqrestore(&udc->lock, flags); + return -EAGAIN; + } + } + + if (list_empty(&ep->queue)) { + if (ep->is_in) { + dev_dbg(udc->dev, "xudc_write_fifo from ep_queue\n"); + if (!xudc_write_fifo(ep, req)) + req = NULL; + } else { + dev_dbg(udc->dev, "xudc_read_fifo from ep_queue\n"); + if (!xudc_read_fifo(ep, req)) + req = NULL; + } + } + + if (req != NULL) + list_add_tail(&req->queue, &ep->queue); + + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +/** + * xudc_ep_dequeue - Removes the request from the queue. + * @_ep: pointer to the usb device endpoint structure. + * @_req: pointer to the usb request structure. + * + * Return: 0 for success and error value on failure + */ +static int xudc_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct xusb_ep *ep = to_xusb_ep(_ep); + struct xusb_req *req = NULL; + struct xusb_req *iter; + struct xusb_udc *udc = ep->udc; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + /* Make sure it's actually queued on this endpoint */ + list_for_each_entry(iter, &ep->queue, queue) { + if (&iter->usb_req != _req) + continue; + req = iter; + break; + } + if (!req) { + spin_unlock_irqrestore(&udc->lock, flags); + return -EINVAL; + } + xudc_done(ep, req, -ECONNRESET); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +/** + * xudc_ep0_enable - Enables the given endpoint. + * @ep: pointer to the usb endpoint structure. + * @desc: pointer to usb endpoint descriptor. + * + * Return: error always. + * + * endpoint 0 enable should not be called by gadget layer. + */ +static int xudc_ep0_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + return -EINVAL; +} + +/** + * xudc_ep0_disable - Disables the given endpoint. + * @ep: pointer to the usb endpoint structure. + * + * Return: error always. + * + * endpoint 0 disable should not be called by gadget layer. + */ +static int xudc_ep0_disable(struct usb_ep *ep) +{ + return -EINVAL; +} + +static const struct usb_ep_ops xusb_ep0_ops = { + .enable = xudc_ep0_enable, + .disable = xudc_ep0_disable, + .alloc_request = xudc_ep_alloc_request, + .free_request = xudc_free_request, + .queue = xudc_ep0_queue, + .dequeue = xudc_ep_dequeue, + .set_halt = xudc_ep_set_halt, +}; + +static const struct usb_ep_ops xusb_ep_ops = { + .enable = xudc_ep_enable, + .disable = xudc_ep_disable, + .alloc_request = xudc_ep_alloc_request, + .free_request = xudc_free_request, + .queue = xudc_ep_queue, + .dequeue = xudc_ep_dequeue, + .set_halt = xudc_ep_set_halt, +}; + +/** + * xudc_get_frame - Reads the current usb frame number. + * @gadget: pointer to the usb gadget structure. + * + * Return: current frame number for success and error value on failure. + */ +static int xudc_get_frame(struct usb_gadget *gadget) +{ + struct xusb_udc *udc; + int frame; + + if (!gadget) + return -ENODEV; + + udc = to_udc(gadget); + frame = udc->read_fn(udc->addr + XUSB_FRAMENUM_OFFSET); + return frame; +} + +/** + * xudc_wakeup - Send remote wakeup signal to host + * @gadget: pointer to the usb gadget structure. + * + * Return: 0 on success and error on failure + */ +static int xudc_wakeup(struct usb_gadget *gadget) +{ + struct xusb_udc *udc = to_udc(gadget); + u32 crtlreg; + int status = -EINVAL; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + /* Remote wake up not enabled by host */ + if (!udc->remote_wkp) + goto done; + + crtlreg = udc->read_fn(udc->addr + XUSB_CONTROL_OFFSET); + crtlreg |= XUSB_CONTROL_USB_RMTWAKE_MASK; + /* set remote wake up bit */ + udc->write_fn(udc->addr, XUSB_CONTROL_OFFSET, crtlreg); + /* + * wait for a while and reset remote wake up bit since this bit + * is not cleared by HW after sending remote wakeup to host. + */ + mdelay(2); + + crtlreg &= ~XUSB_CONTROL_USB_RMTWAKE_MASK; + udc->write_fn(udc->addr, XUSB_CONTROL_OFFSET, crtlreg); + status = 0; +done: + spin_unlock_irqrestore(&udc->lock, flags); + return status; +} + +/** + * xudc_pullup - start/stop USB traffic + * @gadget: pointer to the usb gadget structure. + * @is_on: flag to start or stop + * + * Return: 0 always + * + * This function starts/stops SIE engine of IP based on is_on. + */ +static int xudc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct xusb_udc *udc = to_udc(gadget); + unsigned long flags; + u32 crtlreg; + + spin_lock_irqsave(&udc->lock, flags); + + crtlreg = udc->read_fn(udc->addr + XUSB_CONTROL_OFFSET); + if (is_on) + crtlreg |= XUSB_CONTROL_USB_READY_MASK; + else + crtlreg &= ~XUSB_CONTROL_USB_READY_MASK; + + udc->write_fn(udc->addr, XUSB_CONTROL_OFFSET, crtlreg); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +/** + * xudc_eps_init - initialize endpoints. + * @udc: pointer to the usb device controller structure. + */ +static void xudc_eps_init(struct xusb_udc *udc) +{ + u32 ep_number; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + for (ep_number = 0; ep_number < XUSB_MAX_ENDPOINTS; ep_number++) { + struct xusb_ep *ep = &udc->ep[ep_number]; + + if (ep_number) { + list_add_tail(&ep->ep_usb.ep_list, + &udc->gadget.ep_list); + usb_ep_set_maxpacket_limit(&ep->ep_usb, + (unsigned short) ~0); + snprintf(ep->name, EPNAME_SIZE, "ep%d", ep_number); + ep->ep_usb.name = ep->name; + ep->ep_usb.ops = &xusb_ep_ops; + + ep->ep_usb.caps.type_iso = true; + ep->ep_usb.caps.type_bulk = true; + ep->ep_usb.caps.type_int = true; + } else { + ep->ep_usb.name = ep0name; + usb_ep_set_maxpacket_limit(&ep->ep_usb, EP0_MAX_PACKET); + ep->ep_usb.ops = &xusb_ep0_ops; + + ep->ep_usb.caps.type_control = true; + } + + ep->ep_usb.caps.dir_in = true; + ep->ep_usb.caps.dir_out = true; + + ep->udc = udc; + ep->epnumber = ep_number; + ep->desc = NULL; + /* + * The configuration register address offset between + * each endpoint is 0x10. + */ + ep->offset = XUSB_EP0_CONFIG_OFFSET + (ep_number * 0x10); + ep->is_in = 0; + ep->is_iso = 0; + ep->maxpacket = 0; + xudc_epconfig(ep, udc); + + /* Initialize one queue per endpoint */ + INIT_LIST_HEAD(&ep->queue); + } +} + +/** + * xudc_stop_activity - Stops any further activity on the device. + * @udc: pointer to the usb device controller structure. + */ +static void xudc_stop_activity(struct xusb_udc *udc) +{ + int i; + struct xusb_ep *ep; + + for (i = 0; i < XUSB_MAX_ENDPOINTS; i++) { + ep = &udc->ep[i]; + xudc_nuke(ep, -ESHUTDOWN); + } +} + +/** + * xudc_start - Starts the device. + * @gadget: pointer to the usb gadget structure + * @driver: pointer to gadget driver structure + * + * Return: zero on success and error on failure + */ +static int xudc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct xusb_udc *udc = to_udc(gadget); + struct xusb_ep *ep0 = &udc->ep[XUSB_EP_NUMBER_ZERO]; + const struct usb_endpoint_descriptor *desc = &config_bulk_out_desc; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&udc->lock, flags); + + if (udc->driver) { + dev_err(udc->dev, "%s is already bound to %s\n", + udc->gadget.name, udc->driver->driver.name); + ret = -EBUSY; + goto err; + } + + /* hook up the driver */ + udc->driver = driver; + udc->gadget.speed = driver->max_speed; + + /* Enable the control endpoint. */ + ret = __xudc_ep_enable(ep0, desc); + + /* Set device address and remote wakeup to 0 */ + udc->write_fn(udc->addr, XUSB_ADDRESS_OFFSET, 0); + udc->remote_wkp = 0; +err: + spin_unlock_irqrestore(&udc->lock, flags); + return ret; +} + +/** + * xudc_stop - stops the device. + * @gadget: pointer to the usb gadget structure + * + * Return: zero always + */ +static int xudc_stop(struct usb_gadget *gadget) +{ + struct xusb_udc *udc = to_udc(gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->driver = NULL; + + /* Set device address and remote wakeup to 0 */ + udc->write_fn(udc->addr, XUSB_ADDRESS_OFFSET, 0); + udc->remote_wkp = 0; + + xudc_stop_activity(udc); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static const struct usb_gadget_ops xusb_udc_ops = { + .get_frame = xudc_get_frame, + .wakeup = xudc_wakeup, + .pullup = xudc_pullup, + .udc_start = xudc_start, + .udc_stop = xudc_stop, +}; + +/** + * xudc_clear_stall_all_ep - clears stall of every endpoint. + * @udc: pointer to the udc structure. + */ +static void xudc_clear_stall_all_ep(struct xusb_udc *udc) +{ + struct xusb_ep *ep; + u32 epcfgreg; + int i; + + for (i = 0; i < XUSB_MAX_ENDPOINTS; i++) { + ep = &udc->ep[i]; + epcfgreg = udc->read_fn(udc->addr + ep->offset); + epcfgreg &= ~XUSB_EP_CFG_STALL_MASK; + udc->write_fn(udc->addr, ep->offset, epcfgreg); + if (ep->epnumber) { + /* Reset the toggle bit.*/ + epcfgreg = udc->read_fn(udc->addr + ep->offset); + epcfgreg &= ~XUSB_EP_CFG_DATA_TOGGLE_MASK; + udc->write_fn(udc->addr, ep->offset, epcfgreg); + } + } +} + +/** + * xudc_startup_handler - The usb device controller interrupt handler. + * @udc: pointer to the udc structure. + * @intrstatus: The mask value containing the interrupt sources. + * + * This function handles the RESET,SUSPEND,RESUME and DISCONNECT interrupts. + */ +static void xudc_startup_handler(struct xusb_udc *udc, u32 intrstatus) +{ + u32 intrreg; + + if (intrstatus & XUSB_STATUS_RESET_MASK) { + + dev_dbg(udc->dev, "Reset\n"); + + if (intrstatus & XUSB_STATUS_HIGH_SPEED_MASK) + udc->gadget.speed = USB_SPEED_HIGH; + else + udc->gadget.speed = USB_SPEED_FULL; + + xudc_stop_activity(udc); + xudc_clear_stall_all_ep(udc); + udc->write_fn(udc->addr, XUSB_TESTMODE_OFFSET, 0); + + /* Set device address and remote wakeup to 0 */ + udc->write_fn(udc->addr, XUSB_ADDRESS_OFFSET, 0); + udc->remote_wkp = 0; + + /* Enable the suspend, resume and disconnect */ + intrreg = udc->read_fn(udc->addr + XUSB_IER_OFFSET); + intrreg |= XUSB_STATUS_SUSPEND_MASK | XUSB_STATUS_RESUME_MASK | + XUSB_STATUS_DISCONNECT_MASK; + udc->write_fn(udc->addr, XUSB_IER_OFFSET, intrreg); + } + if (intrstatus & XUSB_STATUS_SUSPEND_MASK) { + + dev_dbg(udc->dev, "Suspend\n"); + + /* Enable the reset, resume and disconnect */ + intrreg = udc->read_fn(udc->addr + XUSB_IER_OFFSET); + intrreg |= XUSB_STATUS_RESET_MASK | XUSB_STATUS_RESUME_MASK | + XUSB_STATUS_DISCONNECT_MASK; + udc->write_fn(udc->addr, XUSB_IER_OFFSET, intrreg); + + udc->usb_state = USB_STATE_SUSPENDED; + + if (udc->driver->suspend) { + spin_unlock(&udc->lock); + udc->driver->suspend(&udc->gadget); + spin_lock(&udc->lock); + } + } + if (intrstatus & XUSB_STATUS_RESUME_MASK) { + bool condition = (udc->usb_state != USB_STATE_SUSPENDED); + + dev_WARN_ONCE(udc->dev, condition, + "Resume IRQ while not suspended\n"); + + dev_dbg(udc->dev, "Resume\n"); + + /* Enable the reset, suspend and disconnect */ + intrreg = udc->read_fn(udc->addr + XUSB_IER_OFFSET); + intrreg |= XUSB_STATUS_RESET_MASK | XUSB_STATUS_SUSPEND_MASK | + XUSB_STATUS_DISCONNECT_MASK; + udc->write_fn(udc->addr, XUSB_IER_OFFSET, intrreg); + + udc->usb_state = 0; + + if (udc->driver->resume) { + spin_unlock(&udc->lock); + udc->driver->resume(&udc->gadget); + spin_lock(&udc->lock); + } + } + if (intrstatus & XUSB_STATUS_DISCONNECT_MASK) { + + dev_dbg(udc->dev, "Disconnect\n"); + + /* Enable the reset, resume and suspend */ + intrreg = udc->read_fn(udc->addr + XUSB_IER_OFFSET); + intrreg |= XUSB_STATUS_RESET_MASK | XUSB_STATUS_RESUME_MASK | + XUSB_STATUS_SUSPEND_MASK; + udc->write_fn(udc->addr, XUSB_IER_OFFSET, intrreg); + + if (udc->driver && udc->driver->disconnect) { + spin_unlock(&udc->lock); + udc->driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + } + } +} + +/** + * xudc_ep0_stall - Stall endpoint zero. + * @udc: pointer to the udc structure. + * + * This function stalls endpoint zero. + */ +static void xudc_ep0_stall(struct xusb_udc *udc) +{ + u32 epcfgreg; + struct xusb_ep *ep0 = &udc->ep[XUSB_EP_NUMBER_ZERO]; + + epcfgreg = udc->read_fn(udc->addr + ep0->offset); + epcfgreg |= XUSB_EP_CFG_STALL_MASK; + udc->write_fn(udc->addr, ep0->offset, epcfgreg); +} + +/** + * xudc_setaddress - executes SET_ADDRESS command + * @udc: pointer to the udc structure. + * + * This function executes USB SET_ADDRESS command + */ +static void xudc_setaddress(struct xusb_udc *udc) +{ + struct xusb_ep *ep0 = &udc->ep[0]; + struct xusb_req *req = udc->req; + int ret; + + req->usb_req.length = 0; + ret = __xudc_ep0_queue(ep0, req); + if (ret == 0) + return; + + dev_err(udc->dev, "Can't respond to SET ADDRESS request\n"); + xudc_ep0_stall(udc); +} + +/** + * xudc_getstatus - executes GET_STATUS command + * @udc: pointer to the udc structure. + * + * This function executes USB GET_STATUS command + */ +static void xudc_getstatus(struct xusb_udc *udc) +{ + struct xusb_ep *ep0 = &udc->ep[0]; + struct xusb_req *req = udc->req; + struct xusb_ep *target_ep; + u16 status = 0; + u32 epcfgreg; + int epnum; + u32 halt; + int ret; + + switch (udc->setup.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + /* Get device status */ + status = 1 << USB_DEVICE_SELF_POWERED; + if (udc->remote_wkp) + status |= (1 << USB_DEVICE_REMOTE_WAKEUP); + break; + case USB_RECIP_INTERFACE: + break; + case USB_RECIP_ENDPOINT: + epnum = le16_to_cpu(udc->setup.wIndex) & USB_ENDPOINT_NUMBER_MASK; + if (epnum >= XUSB_MAX_ENDPOINTS) + goto stall; + target_ep = &udc->ep[epnum]; + epcfgreg = udc->read_fn(udc->addr + target_ep->offset); + halt = epcfgreg & XUSB_EP_CFG_STALL_MASK; + if (le16_to_cpu(udc->setup.wIndex) & USB_DIR_IN) { + if (!target_ep->is_in) + goto stall; + } else { + if (target_ep->is_in) + goto stall; + } + if (halt) + status = 1 << USB_ENDPOINT_HALT; + break; + default: + goto stall; + } + + req->usb_req.length = 2; + *(__le16 *)req->usb_req.buf = cpu_to_le16(status); + ret = __xudc_ep0_queue(ep0, req); + if (ret == 0) + return; +stall: + dev_err(udc->dev, "Can't respond to getstatus request\n"); + xudc_ep0_stall(udc); +} + +/** + * xudc_set_clear_feature - Executes the set feature and clear feature commands. + * @udc: pointer to the usb device controller structure. + * + * Processes the SET_FEATURE and CLEAR_FEATURE commands. + */ +static void xudc_set_clear_feature(struct xusb_udc *udc) +{ + struct xusb_ep *ep0 = &udc->ep[0]; + struct xusb_req *req = udc->req; + struct xusb_ep *target_ep; + u8 endpoint; + u8 outinbit; + u32 epcfgreg; + int flag = (udc->setup.bRequest == USB_REQ_SET_FEATURE ? 1 : 0); + int ret; + + switch (udc->setup.bRequestType) { + case USB_RECIP_DEVICE: + switch (le16_to_cpu(udc->setup.wValue)) { + case USB_DEVICE_TEST_MODE: + /* + * The Test Mode will be executed + * after the status phase. + */ + break; + case USB_DEVICE_REMOTE_WAKEUP: + if (flag) + udc->remote_wkp = 1; + else + udc->remote_wkp = 0; + break; + default: + xudc_ep0_stall(udc); + break; + } + break; + case USB_RECIP_ENDPOINT: + if (!udc->setup.wValue) { + endpoint = le16_to_cpu(udc->setup.wIndex) & + USB_ENDPOINT_NUMBER_MASK; + if (endpoint >= XUSB_MAX_ENDPOINTS) { + xudc_ep0_stall(udc); + return; + } + target_ep = &udc->ep[endpoint]; + outinbit = le16_to_cpu(udc->setup.wIndex) & + USB_ENDPOINT_DIR_MASK; + outinbit = outinbit >> 7; + + /* Make sure direction matches.*/ + if (outinbit != target_ep->is_in) { + xudc_ep0_stall(udc); + return; + } + epcfgreg = udc->read_fn(udc->addr + target_ep->offset); + if (!endpoint) { + /* Clear the stall.*/ + epcfgreg &= ~XUSB_EP_CFG_STALL_MASK; + udc->write_fn(udc->addr, + target_ep->offset, epcfgreg); + } else { + if (flag) { + epcfgreg |= XUSB_EP_CFG_STALL_MASK; + udc->write_fn(udc->addr, + target_ep->offset, + epcfgreg); + } else { + /* Unstall the endpoint.*/ + epcfgreg &= ~(XUSB_EP_CFG_STALL_MASK | + XUSB_EP_CFG_DATA_TOGGLE_MASK); + udc->write_fn(udc->addr, + target_ep->offset, + epcfgreg); + } + } + } + break; + default: + xudc_ep0_stall(udc); + return; + } + + req->usb_req.length = 0; + ret = __xudc_ep0_queue(ep0, req); + if (ret == 0) + return; + + dev_err(udc->dev, "Can't respond to SET/CLEAR FEATURE\n"); + xudc_ep0_stall(udc); +} + +/** + * xudc_handle_setup - Processes the setup packet. + * @udc: pointer to the usb device controller structure. + * + * Process setup packet and delegate to gadget layer. + */ +static void xudc_handle_setup(struct xusb_udc *udc) + __must_hold(&udc->lock) +{ + struct xusb_ep *ep0 = &udc->ep[0]; + struct usb_ctrlrequest setup; + u32 *ep0rambase; + + /* Load up the chapter 9 command buffer.*/ + ep0rambase = (u32 __force *) (udc->addr + XUSB_SETUP_PKT_ADDR_OFFSET); + memcpy_toio((void __iomem *)&setup, ep0rambase, 8); + + udc->setup = setup; + udc->setup.wValue = cpu_to_le16((u16 __force)setup.wValue); + udc->setup.wIndex = cpu_to_le16((u16 __force)setup.wIndex); + udc->setup.wLength = cpu_to_le16((u16 __force)setup.wLength); + + /* Clear previous requests */ + xudc_nuke(ep0, -ECONNRESET); + + if (udc->setup.bRequestType & USB_DIR_IN) { + /* Execute the get command.*/ + udc->setupseqrx = STATUS_PHASE; + udc->setupseqtx = DATA_PHASE; + } else { + /* Execute the put command.*/ + udc->setupseqrx = DATA_PHASE; + udc->setupseqtx = STATUS_PHASE; + } + + switch (udc->setup.bRequest) { + case USB_REQ_GET_STATUS: + /* Data+Status phase form udc */ + if ((udc->setup.bRequestType & + (USB_DIR_IN | USB_TYPE_MASK)) != + (USB_DIR_IN | USB_TYPE_STANDARD)) + break; + xudc_getstatus(udc); + return; + case USB_REQ_SET_ADDRESS: + /* Status phase from udc */ + if (udc->setup.bRequestType != (USB_DIR_OUT | + USB_TYPE_STANDARD | USB_RECIP_DEVICE)) + break; + xudc_setaddress(udc); + return; + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + /* Requests with no data phase, status phase from udc */ + if ((udc->setup.bRequestType & USB_TYPE_MASK) + != USB_TYPE_STANDARD) + break; + xudc_set_clear_feature(udc); + return; + default: + break; + } + + spin_unlock(&udc->lock); + if (udc->driver->setup(&udc->gadget, &setup) < 0) + xudc_ep0_stall(udc); + spin_lock(&udc->lock); +} + +/** + * xudc_ep0_out - Processes the endpoint 0 OUT token. + * @udc: pointer to the usb device controller structure. + */ +static void xudc_ep0_out(struct xusb_udc *udc) +{ + struct xusb_ep *ep0 = &udc->ep[0]; + struct xusb_req *req; + u8 *ep0rambase; + unsigned int bytes_to_rx; + void *buffer; + + req = list_first_entry(&ep0->queue, struct xusb_req, queue); + + switch (udc->setupseqrx) { + case STATUS_PHASE: + /* + * This resets both state machines for the next + * Setup packet. + */ + udc->setupseqrx = SETUP_PHASE; + udc->setupseqtx = SETUP_PHASE; + req->usb_req.actual = req->usb_req.length; + xudc_done(ep0, req, 0); + break; + case DATA_PHASE: + bytes_to_rx = udc->read_fn(udc->addr + + XUSB_EP_BUF0COUNT_OFFSET); + /* Copy the data to be received from the DPRAM. */ + ep0rambase = (u8 __force *) (udc->addr + + (ep0->rambase << 2)); + buffer = req->usb_req.buf + req->usb_req.actual; + req->usb_req.actual = req->usb_req.actual + bytes_to_rx; + memcpy_toio((void __iomem *)buffer, ep0rambase, bytes_to_rx); + + if (req->usb_req.length == req->usb_req.actual) { + /* Data transfer completed get ready for Status stage */ + xudc_wrstatus(udc); + } else { + /* Enable EP0 buffer to receive data */ + udc->write_fn(udc->addr, XUSB_EP_BUF0COUNT_OFFSET, 0); + udc->write_fn(udc->addr, XUSB_BUFFREADY_OFFSET, 1); + } + break; + default: + break; + } +} + +/** + * xudc_ep0_in - Processes the endpoint 0 IN token. + * @udc: pointer to the usb device controller structure. + */ +static void xudc_ep0_in(struct xusb_udc *udc) +{ + struct xusb_ep *ep0 = &udc->ep[0]; + struct xusb_req *req; + unsigned int bytes_to_tx; + void *buffer; + u32 epcfgreg; + u16 count = 0; + u16 length; + u8 *ep0rambase; + u8 test_mode = le16_to_cpu(udc->setup.wIndex) >> 8; + + req = list_first_entry(&ep0->queue, struct xusb_req, queue); + bytes_to_tx = req->usb_req.length - req->usb_req.actual; + + switch (udc->setupseqtx) { + case STATUS_PHASE: + switch (udc->setup.bRequest) { + case USB_REQ_SET_ADDRESS: + /* Set the address of the device.*/ + udc->write_fn(udc->addr, XUSB_ADDRESS_OFFSET, + le16_to_cpu(udc->setup.wValue)); + break; + case USB_REQ_SET_FEATURE: + if (udc->setup.bRequestType == + USB_RECIP_DEVICE) { + if (le16_to_cpu(udc->setup.wValue) == + USB_DEVICE_TEST_MODE) + udc->write_fn(udc->addr, + XUSB_TESTMODE_OFFSET, + test_mode); + } + break; + } + req->usb_req.actual = req->usb_req.length; + xudc_done(ep0, req, 0); + break; + case DATA_PHASE: + if (!bytes_to_tx) { + /* + * We're done with data transfer, next + * will be zero length OUT with data toggle of + * 1. Setup data_toggle. + */ + epcfgreg = udc->read_fn(udc->addr + ep0->offset); + epcfgreg |= XUSB_EP_CFG_DATA_TOGGLE_MASK; + udc->write_fn(udc->addr, ep0->offset, epcfgreg); + udc->setupseqtx = STATUS_PHASE; + } else { + length = count = min_t(u32, bytes_to_tx, + EP0_MAX_PACKET); + /* Copy the data to be transmitted into the DPRAM. */ + ep0rambase = (u8 __force *) (udc->addr + + (ep0->rambase << 2)); + buffer = req->usb_req.buf + req->usb_req.actual; + req->usb_req.actual = req->usb_req.actual + length; + memcpy_toio((void __iomem *)ep0rambase, buffer, length); + } + udc->write_fn(udc->addr, XUSB_EP_BUF0COUNT_OFFSET, count); + udc->write_fn(udc->addr, XUSB_BUFFREADY_OFFSET, 1); + break; + default: + break; + } +} + +/** + * xudc_ctrl_ep_handler - Endpoint 0 interrupt handler. + * @udc: pointer to the udc structure. + * @intrstatus: It's the mask value for the interrupt sources on endpoint 0. + * + * Processes the commands received during enumeration phase. + */ +static void xudc_ctrl_ep_handler(struct xusb_udc *udc, u32 intrstatus) +{ + + if (intrstatus & XUSB_STATUS_SETUP_PACKET_MASK) { + xudc_handle_setup(udc); + } else { + if (intrstatus & XUSB_STATUS_FIFO_BUFF_RDY_MASK) + xudc_ep0_out(udc); + else if (intrstatus & XUSB_STATUS_FIFO_BUFF_FREE_MASK) + xudc_ep0_in(udc); + } +} + +/** + * xudc_nonctrl_ep_handler - Non control endpoint interrupt handler. + * @udc: pointer to the udc structure. + * @epnum: End point number for which the interrupt is to be processed + * @intrstatus: mask value for interrupt sources of endpoints other + * than endpoint 0. + * + * Processes the buffer completion interrupts. + */ +static void xudc_nonctrl_ep_handler(struct xusb_udc *udc, u8 epnum, + u32 intrstatus) +{ + + struct xusb_req *req; + struct xusb_ep *ep; + + ep = &udc->ep[epnum]; + /* Process the End point interrupts.*/ + if (intrstatus & (XUSB_STATUS_EP0_BUFF1_COMP_MASK << epnum)) + ep->buffer0ready = 0; + if (intrstatus & (XUSB_STATUS_EP0_BUFF2_COMP_MASK << epnum)) + ep->buffer1ready = false; + + if (list_empty(&ep->queue)) + return; + + req = list_first_entry(&ep->queue, struct xusb_req, queue); + + if (ep->is_in) + xudc_write_fifo(ep, req); + else + xudc_read_fifo(ep, req); +} + +/** + * xudc_irq - The main interrupt handler. + * @irq: The interrupt number. + * @_udc: pointer to the usb device controller structure. + * + * Return: IRQ_HANDLED after the interrupt is handled. + */ +static irqreturn_t xudc_irq(int irq, void *_udc) +{ + struct xusb_udc *udc = _udc; + u32 intrstatus; + u32 ier; + u8 index; + u32 bufintr; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + /* + * Event interrupts are level sensitive hence first disable + * IER, read ISR and figure out active interrupts. + */ + ier = udc->read_fn(udc->addr + XUSB_IER_OFFSET); + ier &= ~XUSB_STATUS_INTR_EVENT_MASK; + udc->write_fn(udc->addr, XUSB_IER_OFFSET, ier); + + /* Read the Interrupt Status Register.*/ + intrstatus = udc->read_fn(udc->addr + XUSB_STATUS_OFFSET); + + /* Call the handler for the event interrupt.*/ + if (intrstatus & XUSB_STATUS_INTR_EVENT_MASK) { + /* + * Check if there is any action to be done for : + * - USB Reset received {XUSB_STATUS_RESET_MASK} + * - USB Suspend received {XUSB_STATUS_SUSPEND_MASK} + * - USB Resume received {XUSB_STATUS_RESUME_MASK} + * - USB Disconnect received {XUSB_STATUS_DISCONNECT_MASK} + */ + xudc_startup_handler(udc, intrstatus); + } + + /* Check the buffer completion interrupts */ + if (intrstatus & XUSB_STATUS_INTR_BUFF_COMP_ALL_MASK) { + /* Enable Reset, Suspend, Resume and Disconnect */ + ier = udc->read_fn(udc->addr + XUSB_IER_OFFSET); + ier |= XUSB_STATUS_INTR_EVENT_MASK; + udc->write_fn(udc->addr, XUSB_IER_OFFSET, ier); + + if (intrstatus & XUSB_STATUS_EP0_BUFF1_COMP_MASK) + xudc_ctrl_ep_handler(udc, intrstatus); + + for (index = 1; index < 8; index++) { + bufintr = ((intrstatus & + (XUSB_STATUS_EP1_BUFF1_COMP_MASK << + (index - 1))) || (intrstatus & + (XUSB_STATUS_EP1_BUFF2_COMP_MASK << + (index - 1)))); + if (bufintr) { + xudc_nonctrl_ep_handler(udc, index, + intrstatus); + } + } + } + + spin_unlock_irqrestore(&udc->lock, flags); + return IRQ_HANDLED; +} + +/** + * xudc_probe - The device probe function for driver initialization. + * @pdev: pointer to the platform device structure. + * + * Return: 0 for success and error value on failure + */ +static int xudc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct resource *res; + struct xusb_udc *udc; + int irq; + int ret; + u32 ier; + u8 *buff; + + udc = devm_kzalloc(&pdev->dev, sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + /* Create a dummy request for GET_STATUS, SET_ADDRESS */ + udc->req = devm_kzalloc(&pdev->dev, sizeof(struct xusb_req), + GFP_KERNEL); + if (!udc->req) + return -ENOMEM; + + buff = devm_kzalloc(&pdev->dev, STATUSBUFF_SIZE, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + udc->req->usb_req.buf = buff; + + /* Map the registers */ + udc->addr = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(udc->addr)) + return PTR_ERR(udc->addr); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + ret = devm_request_irq(&pdev->dev, irq, xudc_irq, 0, + dev_name(&pdev->dev), udc); + if (ret < 0) { + dev_dbg(&pdev->dev, "unable to request irq %d", irq); + goto fail; + } + + udc->dma_enabled = of_property_read_bool(np, "xlnx,has-builtin-dma"); + + /* Setup gadget structure */ + udc->gadget.ops = &xusb_udc_ops; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.ep0 = &udc->ep[XUSB_EP_NUMBER_ZERO].ep_usb; + udc->gadget.name = driver_name; + + udc->clk = devm_clk_get(&pdev->dev, "s_axi_aclk"); + if (IS_ERR(udc->clk)) { + if (PTR_ERR(udc->clk) != -ENOENT) { + ret = PTR_ERR(udc->clk); + goto fail; + } + + /* + * Clock framework support is optional, continue on, + * anyways if we don't find a matching clock + */ + dev_warn(&pdev->dev, "s_axi_aclk clock property is not found\n"); + udc->clk = NULL; + } + + ret = clk_prepare_enable(udc->clk); + if (ret) { + dev_err(&pdev->dev, "Unable to enable clock.\n"); + return ret; + } + + spin_lock_init(&udc->lock); + + /* Check for IP endianness */ + udc->write_fn = xudc_write32_be; + udc->read_fn = xudc_read32_be; + udc->write_fn(udc->addr, XUSB_TESTMODE_OFFSET, USB_TEST_J); + if ((udc->read_fn(udc->addr + XUSB_TESTMODE_OFFSET)) + != USB_TEST_J) { + udc->write_fn = xudc_write32; + udc->read_fn = xudc_read32; + } + udc->write_fn(udc->addr, XUSB_TESTMODE_OFFSET, 0); + + xudc_eps_init(udc); + + /* Set device address to 0.*/ + udc->write_fn(udc->addr, XUSB_ADDRESS_OFFSET, 0); + + ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget); + if (ret) + goto err_disable_unprepare_clk; + + udc->dev = &udc->gadget.dev; + + /* Enable the interrupts.*/ + ier = XUSB_STATUS_GLOBAL_INTR_MASK | XUSB_STATUS_INTR_EVENT_MASK | + XUSB_STATUS_FIFO_BUFF_RDY_MASK | XUSB_STATUS_FIFO_BUFF_FREE_MASK | + XUSB_STATUS_SETUP_PACKET_MASK | + XUSB_STATUS_INTR_BUFF_COMP_ALL_MASK; + + udc->write_fn(udc->addr, XUSB_IER_OFFSET, ier); + + platform_set_drvdata(pdev, udc); + + dev_vdbg(&pdev->dev, "%s at 0x%08X mapped to %p %s\n", + driver_name, (u32)res->start, udc->addr, + udc->dma_enabled ? "with DMA" : "without DMA"); + + return 0; + +err_disable_unprepare_clk: + clk_disable_unprepare(udc->clk); +fail: + dev_err(&pdev->dev, "probe failed, %d\n", ret); + return ret; +} + +/** + * xudc_remove - Releases the resources allocated during the initialization. + * @pdev: pointer to the platform device structure. + */ +static void xudc_remove(struct platform_device *pdev) +{ + struct xusb_udc *udc = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&udc->gadget); + clk_disable_unprepare(udc->clk); +} + +#ifdef CONFIG_PM_SLEEP +static int xudc_suspend(struct device *dev) +{ + struct xusb_udc *udc; + u32 crtlreg; + unsigned long flags; + + udc = dev_get_drvdata(dev); + + spin_lock_irqsave(&udc->lock, flags); + + crtlreg = udc->read_fn(udc->addr + XUSB_CONTROL_OFFSET); + crtlreg &= ~XUSB_CONTROL_USB_READY_MASK; + + udc->write_fn(udc->addr, XUSB_CONTROL_OFFSET, crtlreg); + + spin_unlock_irqrestore(&udc->lock, flags); + if (udc->driver && udc->driver->suspend) + udc->driver->suspend(&udc->gadget); + + clk_disable(udc->clk); + + return 0; +} + +static int xudc_resume(struct device *dev) +{ + struct xusb_udc *udc; + u32 crtlreg; + unsigned long flags; + int ret; + + udc = dev_get_drvdata(dev); + + ret = clk_enable(udc->clk); + if (ret < 0) + return ret; + + spin_lock_irqsave(&udc->lock, flags); + + crtlreg = udc->read_fn(udc->addr + XUSB_CONTROL_OFFSET); + crtlreg |= XUSB_CONTROL_USB_READY_MASK; + + udc->write_fn(udc->addr, XUSB_CONTROL_OFFSET, crtlreg); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops xudc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(xudc_suspend, xudc_resume) +}; + +/* Match table for of_platform binding */ +static const struct of_device_id usb_of_match[] = { + { .compatible = "xlnx,usb2-device-4.00.a", }, + { /* end of list */ }, +}; +MODULE_DEVICE_TABLE(of, usb_of_match); + +static struct platform_driver xudc_driver = { + .driver = { + .name = driver_name, + .of_match_table = usb_of_match, + .pm = &xudc_pm_ops, + }, + .probe = xudc_probe, + .remove = xudc_remove, +}; + +module_platform_driver(xudc_driver); + +MODULE_DESCRIPTION("Xilinx udc driver"); +MODULE_AUTHOR("Xilinx, Inc"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/usbstring.c b/drivers/usb/gadget/usbstring.c new file mode 100644 index 000000000..37a2f1b61 --- /dev/null +++ b/drivers/usb/gadget/usbstring.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2003 David Brownell + */ + +#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 (const 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_t(size_t, USB_MAX_STRING_LEN, strlen(s->s)); + len = utf8s_to_utf16s(s->s, len, UTF16_LITTLE_ENDIAN, + (wchar_t *) &buf[2], USB_MAX_STRING_LEN); + 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); + +/** + * usb_validate_langid - validate usb language identifiers + * @langid: usb language identifier + * + * Returns true for valid language identifier, otherwise false. + */ +bool usb_validate_langid(u16 langid) +{ + u16 primary_lang = langid & 0x3ff; /* bit [9:0] */ + u16 sub_lang = langid >> 10; /* bit [15:10] */ + + switch (primary_lang) { + case 0: + case 0x62 ... 0xfe: + case 0x100 ... 0x3ff: + return false; + } + if (!sub_lang) + return false; + + return true; +} +EXPORT_SYMBOL_GPL(usb_validate_langid); diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig new file mode 100644 index 000000000..10cff12a3 --- /dev/null +++ b/drivers/usb/host/Kconfig @@ -0,0 +1,735 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# USB Host Controller Drivers +# +comment "USB Host Controller Drivers" + +config USB_C67X00_HCD + tristate "Cypress C67x00 HCD support" + depends on HAS_IOMEM + 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" + depends on HAS_DMA && HAS_IOMEM + 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_DBGCAP + bool "xHCI support for debug capability" + depends on TTY + help + Say 'Y' to enable the support for the xHCI debug capability. Make + sure that your xHCI host supports the extended debug capability and + you want a TTY serial device based on the xHCI debug capability + before enabling this option. If unsure, say 'N'. + +config USB_XHCI_PCI + tristate + depends on USB_PCI + default y + +config USB_XHCI_PCI_PROM21 + tristate + depends on USB_XHCI_PCI + default USB_XHCI_PCI if SENSORS_PROM21_XHCI != n + select AUXILIARY_BUS + +config USB_XHCI_PCI_RENESAS + tristate "Support for additional Renesas xHCI controller with firmware" + depends on USB_XHCI_PCI + help + Say 'Y' to enable the support for the Renesas xHCI controller with + firmware. Make sure you have the firmware for the device and + installed on your system for this device to work. + If unsure, say 'N'. + +config USB_XHCI_PLATFORM + tristate "Generic xHCI driver for a platform device" + help + Adds an xHCI host driver for a generic platform device, which + provides a memory space and an irq. + It is also a prerequisite for platform specific drivers that + implement some extra quirks. + + If unsure, say N. + +config USB_XHCI_HISTB + tristate "xHCI support for HiSilicon STB SoCs" + depends on USB_XHCI_PLATFORM && (ARCH_HISI || COMPILE_TEST) + help + Say 'Y' to enable the support for the xHCI host controller + found in HiSilicon STB SoCs. + +config USB_XHCI_MTK + tristate "xHCI support for MediaTek SoCs" + select MFD_SYSCON + depends on (MIPS && SOC_MT7621) || ARCH_MEDIATEK || ARCH_AIROHA || COMPILE_TEST + help + Say 'Y' to enable the support for the xHCI host controller + found in MediaTek SoCs. + If unsure, say N. + +config USB_XHCI_MVEBU + tristate "xHCI support for Marvell Armada 375/38x/37xx" + select USB_XHCI_PLATFORM + depends on HAS_IOMEM + depends on ARCH_MVEBU || COMPILE_TEST + help + Say 'Y' to enable the support for the xHCI host controller + found in Marvell Armada 375/38x/37xx ARM SOCs. + +config USB_XHCI_RCAR + tristate "xHCI support for Renesas R-Car SoCs" + depends on USB_XHCI_PLATFORM + depends on ARCH_RENESAS || COMPILE_TEST + default ARCH_RENESAS + help + Say 'Y' to enable the support for the xHCI host controller + found in Renesas R-Car and RZ/G3E alike ARM SoCs. + +config USB_XHCI_RZV2M + bool "xHCI support for Renesas RZ/V2M SoC" + depends on USB_XHCI_RCAR + depends on ARCH_R9A09G011 || COMPILE_TEST + depends on USB_RZV2M_USB3DRD=y || (USB_RZV2M_USB3DRD=USB_XHCI_RCAR) + help + Say 'Y' to enable the support for the xHCI host controller + found in Renesas RZ/V2M SoC. + +config USB_XHCI_SIDEBAND + bool "xHCI support for sideband" + help + Say 'Y' to enable the support for the xHCI sideband capability. + Provide a mechanism for a sideband datapath for payload associated + with audio class endpoints. This allows for an audio DSP to use + xHCI USB endpoints directly, allowing CPU to sleep while playing + audio. + +config USB_XHCI_TEGRA + tristate "xHCI support for NVIDIA Tegra SoCs" + depends on PHY_TEGRA_XUSB + depends on RESET_CONTROLLER + select FW_LOADER + help + Say 'Y' to enable the support for the xHCI host controller + found in NVIDIA Tegra124 and later SoCs. + +endif # USB_XHCI_HCD + +config USB_EHCI_BRCMSTB + tristate + +config USB_BRCMSTB + tristate "Broadcom STB USB support" + depends on (ARCH_BRCMSTB && PHY_BRCM_USB) || COMPILE_TEST + select USB_OHCI_HCD_PLATFORM if USB_OHCI_HCD + select USB_EHCI_BRCMSTB if USB_EHCI_HCD + select USB_XHCI_PLATFORM if USB_XHCI_HCD + help + Enables support for XHCI, EHCI and OHCI host controllers + found in Broadcom STB SoC's. + + To compile these drivers as modules, choose M here: the + modules will be called ohci-platform.ko, ehci-brcm.ko and + xhci-plat-hcd.ko + + Disabling this will keep the controllers and corresponding + PHYs powered down. + +config USB_EHCI_HCD + tristate "EHCI HCD (USB 2.0) support" + depends on HAS_DMA && HAS_IOMEM + 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 + 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 + 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. + +if USB_EHCI_HCD + +config USB_EHCI_PCI + tristate + depends on USB_PCI + default y + +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 compliant 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 + tristate "Support for Freescale on-chip EHCI USB controller" + select USB_EHCI_ROOT_HUB_TT + help + Variation of ARC USB block used in some Freescale chips. + +config USB_EHCI_HCD_NPCM7XX + tristate "Support for Nuvoton NPCM on-chip EHCI USB controller" + depends on (USB_EHCI_HCD && ARCH_NPCM) || COMPILE_TEST + default y if (USB_EHCI_HCD && ARCH_NPCM) + help + Enables support for the on-chip EHCI controller on + Nuvoton NPCM chips. + +config USB_EHCI_HCD_OMAP + tristate "EHCI support for OMAP3 and later chips" + depends on ARCH_OMAP || COMPILE_TEST + depends on NOP_USB_XCEIV + default ARCH_OMAP + help + Enables support for the on-chip EHCI controller on + OMAP3 and later chips. + +config USB_EHCI_HCD_ORION + tristate "Support for Marvell EBU on-chip EHCI USB controller" + depends on USB_EHCI_HCD && (PLAT_ORION || ARCH_MVEBU || COMPILE_TEST) + default y if (PLAT_ORION || ARCH_MVEBU) + 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 || COMPILE_TEST) + default y if PLAT_SPEAR + help + Enables support for the on-chip EHCI controller on + ST SPEAr chips. + +config USB_EHCI_HCD_STI + tristate "Support for ST STiHxxx on-chip EHCI USB controller" + depends on (ARCH_STI || COMPILE_TEST) && OF + select GENERIC_PHY + select USB_EHCI_HCD_PLATFORM + help + Enable support for the on-chip EHCI controller found on + STMicroelectronics consumer electronics SoC's. + +config USB_EHCI_HCD_AT91 + tristate "Support for Atmel on-chip EHCI USB controller" + depends on USB_EHCI_HCD && (ARCH_AT91 || COMPILE_TEST) + default y if ARCH_AT91 + help + Enables support for the on-chip EHCI controller on + Atmel chips. + +config USB_EHCI_TEGRA + tristate "NVIDIA Tegra HCD support" + depends on ARCH_TEGRA + select USB_CHIPIDEA + select USB_CHIPIDEA_HOST + select USB_CHIPIDEA_TEGRA + select USB_GADGET + help + This option is deprecated now and the driver was removed, use + USB_CHIPIDEA_TEGRA instead. + + Enable 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 + 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 || COMPILE_TEST + 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_EXYNOS + tristate "EHCI support for Samsung S5P/Exynos SoC Series" + depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST + help + Enable support for the Samsung S5Pv210 and Exynos SOC's on-chip EHCI + controller. + +config USB_EHCI_MV + tristate "EHCI support for Marvell PXA/MMP USB controller" + depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST + 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_OCTEON_HCD + tristate "Cavium Networks Octeon USB support" + depends on CAVIUM_OCTEON_SOC && USB + help + This driver supports USB host controller on some Cavium + Networks' products in the Octeon family. + + To compile this driver as a module, choose M here. The module + will be called octeon-hcd. + +config USB_EHCI_HCD_PLATFORM + tristate "Generic EHCI driver for a platform device" + 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 (DEPRECATED)" + depends on CAVIUM_OCTEON_SOC + select USB_EHCI_BIG_ENDIAN_MMIO if CPU_BIG_ENDIAN + 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 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 HAS_IOMEM + 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" + depends on HAS_IOMEM + 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_MAX3421_HCD + tristate "MAX3421 HCD (USB-over-SPI) support" + depends on USB && SPI + help + The Maxim MAX3421E chip supports standard USB 2.0-compliant + full-speed devices either in host or peripheral mode. This + driver supports the host-mode of the MAX3421E only. + + To compile this driver as a module, choose M here: the module will + be called max3421-hcd. + +config USB_OHCI_HCD + tristate "OHCI HCD (USB 1.1) support" + depends on HAS_DMA && HAS_IOMEM + 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 + tristate "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_SPEAR + tristate "Support for ST SPEAr on-chip OHCI USB controller" + depends on USB_OHCI_HCD && (PLAT_SPEAR || COMPILE_TEST) + default y if PLAT_SPEAR + help + Enables support for the on-chip OHCI controller on + ST SPEAr chips. + +config USB_OHCI_HCD_STI + tristate "Support for ST STiHxxx on-chip OHCI USB controller" + depends on (ARCH_STI || COMPILE_TEST) && OF + select GENERIC_PHY + select USB_OHCI_HCD_PLATFORM + help + Enable support for the on-chip OHCI controller found on + STMicroelectronics consumer electronics SoC's. + +config USB_OHCI_HCD_S3C2410 + tristate "OHCI support for Samsung S3C64xx SoC series" + depends on USB_OHCI_HCD && (ARCH_S3C64XX || COMPILE_TEST) + default ARCH_S3C64XX + help + Enables support for the on-chip OHCI controller on + S3C64xx chips. + +config USB_OHCI_HCD_LPC32XX + tristate "Support for LPC on-chip OHCI USB controller" + depends on USB_OHCI_HCD + depends on ARCH_LPC32XX || COMPILE_TEST + depends on USB_ISP1301 + default y + help + Enables support for the on-chip OHCI controller on + NXP chips. + +config USB_OHCI_HCD_PXA27X + tristate "Support for PXA27X/PXA3XX on-chip OHCI USB controller" + depends on USB_OHCI_HCD && (PXA27x || PXA3xx) + default y + help + Enables support for the on-chip OHCI controller on + PXA27x/PXA3xx chips. + +config USB_OHCI_HCD_AT91 + tristate "Support for Atmel on-chip OHCI USB controller" + depends on USB_OHCI_HCD && (ARCH_AT91 || COMPILE_TEST) && OF + default y if ARCH_AT91 + help + Enables support for the on-chip OHCI controller on + Atmel chips. + +config USB_OHCI_HCD_OMAP3 + tristate "OHCI support for OMAP3 and later chips" + depends on ARCH_OMAP3 || ARCH_OMAP4 || SOC_OMAP5 || COMPILE_TEST + select USB_OHCI_HCD_PLATFORM + default y if ARCH_OMAP3 || ARCH_OMAP4 || SOC_OMAP5 + 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 + OMAP3 and later chips. + +config USB_OHCI_HCD_DAVINCI + tristate "OHCI support for TI DaVinci DA8xx" + depends on ARCH_DAVINCI_DA8XX || COMPILE_TEST + depends on USB_OHCI_HCD + select PHY_DA8XX_USB + default y if ARCH_DAVINCI_DA8XX + help + Enables support for the DaVinci DA8xx integrated OHCI + controller. This driver cannot currently be a loadable + module because it lacks a proper PHY abstraction. + +config USB_OHCI_HCD_PPC_OF_BE + bool "OHCI support for OF platform bus (big endian)" + depends on PPC + 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 + 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 + default USB_OHCI_HCD_PPC_OF_BE || USB_OHCI_HCD_PPC_OF_LE + +config USB_OHCI_HCD_PCI + tristate "OHCI support for PCI-bus USB controllers" + depends on USB_PCI + 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 + 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_EXYNOS + tristate "OHCI support for Samsung S5P/Exynos SoC Series" + depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST + help + Enable support for the Samsung S5Pv210 and Exynos SOC's on-chip OHCI + controller. + +config USB_OHCI_HCD_PLATFORM + tristate "Generic OHCI driver for a platform device" + 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 (DEPRECATED)" + depends on CAVIUM_OCTEON_SOC + default USB_OCTEON_EHCI + select USB_OHCI_BIG_ENDIAN_MMIO if CPU_BIG_ENDIAN + select USB_OHCI_LITTLE_ENDIAN + 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 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. + +endif # USB_OHCI_HCD + +config USB_UHCI_HCD + tristate "UHCI HCD (most Intel and VIA) support" + depends on (USB_PCI && HAS_IOPORT) || 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 || ARCH_ASPEED) + +config USB_UHCI_ASPEED + bool + default y if ARCH_ASPEED + +config USB_FHCI_HCD + tristate "Freescale QE USB Host Controller support" + depends on QE_GPIO && QUICC_ENGINE + depends on PPC + 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_SL811_HCD + tristate "SL811HS HCD support" + depends on HAS_IOMEM + 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" + depends on HAS_IOMEM + 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_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. + +config USB_HCD_TEST_MODE + bool "HCD test mode support" + help + Say 'Y' to enable additional software test modes that may be + supported by the host controller drivers. + + One such test mode is the Embedded High-speed Host Electrical Test + (EHSET) for EHCI host controller hardware, specifically the "Single + Step Set Feature" test. Typically this will be enabled for On-the-Go + or embedded hosts that need to undergo USB-IF compliance testing with + the aid of special testing hardware. In the future, this may expand + to include other tests that require support from a HCD driver. + + This option is of interest only to developers who need to validate + their USB hardware designs. It is not needed for normal use. If + unsure, say N. + +config USB_XEN_HCD + tristate "Xen usb virtual host driver" + depends on XEN + select XEN_XENBUS_FRONTEND + help + The Xen usb virtual host driver serves as a frontend driver enabling + a Xen guest system to access USB Devices passed through to the guest + by the Xen host (usually Dom0). + Only needed if the kernel is running in a Xen guest and generic + access to a USB device is needed. diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile new file mode 100644 index 000000000..174580c12 --- /dev/null +++ b/drivers/usb/host/Makefile @@ -0,0 +1,91 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for USB Host Controller Drivers +# + +# tell define_trace.h where to find the xhci trace header +CFLAGS_xhci-trace.o := -I$(src) + +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-ext-caps.o +xhci-hcd-y += xhci-ring.o xhci-hub.o xhci-dbg.o +xhci-hcd-y += xhci-trace.o + +ifneq ($(CONFIG_USB_XHCI_DBGCAP), ) + xhci-hcd-y += xhci-dbgcap.o xhci-dbgtty.o +endif + +xhci-mtk-hcd-y := xhci-mtk.o xhci-mtk-sch.o + +xhci-plat-hcd-y := xhci-plat.o +ifneq ($(CONFIG_USB_XHCI_MVEBU), ) + xhci-plat-hcd-y += xhci-mvebu.o +endif +ifneq ($(CONFIG_DEBUG_FS),) + xhci-hcd-y += xhci-debugfs.o +endif + +xhci-rcar-hcd-y += xhci-rcar.o +xhci-rcar-hcd-$(CONFIG_USB_XHCI_RZV2M) += xhci-rzv2m.o + +ifneq ($(CONFIG_USB_XHCI_SIDEBAND),) + xhci-hcd-y += xhci-sideband.o +endif + +obj-$(CONFIG_USB_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_HCD_NPCM7XX) += ehci-npcm7xx.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_HCD_STI) += ehci-st.o +obj-$(CONFIG_USB_EHCI_EXYNOS) += ehci-exynos.o +obj-$(CONFIG_USB_EHCI_HCD_AT91) += ehci-atmel.o +obj-$(CONFIG_USB_EHCI_BRCMSTB) += ehci-brcm.o + +obj-$(CONFIG_USB_OXU210HP_HCD) += oxu210hp-hcd.o +obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o + +obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o +obj-$(CONFIG_USB_OHCI_HCD_PCI) += ohci-pci.o +obj-$(CONFIG_USB_OHCI_HCD_PLATFORM) += ohci-platform.o +obj-$(CONFIG_USB_OHCI_EXYNOS) += ohci-exynos.o +obj-$(CONFIG_USB_OHCI_HCD_OMAP1) += ohci-omap.o +obj-$(CONFIG_USB_OHCI_HCD_SPEAR) += ohci-spear.o +obj-$(CONFIG_USB_OHCI_HCD_STI) += ohci-st.o +obj-$(CONFIG_USB_OHCI_HCD_AT91) += ohci-at91.o +obj-$(CONFIG_USB_OHCI_HCD_S3C2410) += ohci-s3c2410.o +obj-$(CONFIG_USB_OHCI_HCD_LPC32XX) += ohci-nxp.o +obj-$(CONFIG_USB_OHCI_HCD_PXA27X) += ohci-pxa27x.o +obj-$(CONFIG_USB_OHCI_HCD_DAVINCI) += ohci-da8xx.o +obj-$(CONFIG_USB_OCTEON_HCD) += octeon-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_XHCI_PCI) += xhci-pci.o +obj-$(CONFIG_USB_XHCI_PCI_PROM21) += xhci-pci-prom21.o +obj-$(CONFIG_USB_XHCI_PCI_RENESAS) += xhci-pci-renesas.o +obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o +obj-$(CONFIG_USB_XHCI_HISTB) += xhci-histb.o +obj-$(CONFIG_USB_XHCI_RCAR) += xhci-rcar-hcd.o +obj-$(CONFIG_USB_XHCI_MTK) += xhci-mtk-hcd.o +obj-$(CONFIG_USB_XHCI_TEGRA) += xhci-tegra.o +obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o +obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o +obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o +obj-$(CONFIG_USB_FSL_USB2) += fsl-mph-dr-of.o +obj-$(CONFIG_USB_EHCI_FSL) += fsl-mph-dr-of.o +obj-$(CONFIG_USB_EHCI_FSL) += ehci-fsl.o +obj-$(CONFIG_USB_EHCI_MV) += ehci-mv.o +obj-$(CONFIG_USB_HCD_BCMA) += bcma-hcd.o +obj-$(CONFIG_USB_HCD_SSB) += ssb-hcd.o +obj-$(CONFIG_USB_MAX3421_HCD) += max3421-hcd.o +obj-$(CONFIG_USB_XEN_HCD) += xen-hcd.o diff --git a/drivers/usb/host/bcma-hcd.c b/drivers/usb/host/bcma-hcd.c new file mode 100644 index 000000000..519386255 --- /dev/null +++ b/drivers/usb/host/bcma-hcd.c @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Broadcom specific Advanced Microcontroller Bus + * Broadcom USB-core driver (BCMA bus glue) + * + * Copyright 2011-2015 Hauke Mehrtens + * Copyright 2015 Felix Fietkau + * + * 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Hauke Mehrtens"); +MODULE_DESCRIPTION("Common USB driver for BCMA Bus"); +MODULE_LICENSE("GPL"); + +/* See BCMA_CLKCTLST_EXTRESREQ and BCMA_CLKCTLST_EXTRESST */ +#define USB_BCMA_CLKCTLST_USB_CLK_REQ 0x00000100 + +struct bcma_hcd_device { + struct bcma_device *core; + struct platform_device *ehci_dev; + struct platform_device *ohci_dev; + struct gpio_desc *gpio_desc; +}; + +/* 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_mips(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); + } +} + +/* + * bcma_hcd_usb20_old_arm_init - Initialize old USB 2.0 controller on ARM + * + * Old USB 2.0 core is identified as BCMA_CORE_USB20_HOST and was introduced + * long before Northstar devices. It seems some cheaper chipsets like BCM53573 + * still use it. + * Initialization of this old core differs between MIPS and ARM. + */ +static int bcma_hcd_usb20_old_arm_init(struct bcma_hcd_device *usb_dev) +{ + struct bcma_device *core = usb_dev->core; + struct device *dev = &core->dev; + struct bcma_device *pmu_core; + + usleep_range(10000, 20000); + if (core->id.rev < 5) + return 0; + + pmu_core = bcma_find_core(core->bus, BCMA_CORE_PMU); + if (!pmu_core) { + dev_err(dev, "Could not find PMU core\n"); + return -ENOENT; + } + + /* Take USB core out of reset */ + bcma_awrite32(core, BCMA_IOCTL, BCMA_IOCTL_CLK | BCMA_IOCTL_FGC); + usleep_range(100, 200); + bcma_awrite32(core, BCMA_RESET_CTL, BCMA_RESET_CTL_RESET); + usleep_range(100, 200); + bcma_awrite32(core, BCMA_RESET_CTL, 0); + usleep_range(100, 200); + bcma_awrite32(core, BCMA_IOCTL, BCMA_IOCTL_CLK); + usleep_range(100, 200); + + /* Enable Misc PLL */ + bcma_write32(core, BCMA_CLKCTLST, BCMA_CLKCTLST_FORCEHT | + BCMA_CLKCTLST_HQCLKREQ | + USB_BCMA_CLKCTLST_USB_CLK_REQ); + usleep_range(100, 200); + + bcma_write32(core, 0x510, 0xc7f85000); + bcma_write32(core, 0x510, 0xc7f85003); + usleep_range(300, 600); + + /* Program USB PHY PLL parameters */ + bcma_write32(pmu_core, BCMA_CC_PMU_PLLCTL_ADDR, 0x6); + bcma_write32(pmu_core, BCMA_CC_PMU_PLLCTL_DATA, 0x005360c1); + usleep_range(100, 200); + bcma_write32(pmu_core, BCMA_CC_PMU_PLLCTL_ADDR, 0x7); + bcma_write32(pmu_core, BCMA_CC_PMU_PLLCTL_DATA, 0x0); + usleep_range(100, 200); + bcma_set32(pmu_core, BCMA_CC_PMU_CTL, BCMA_CC_PMU_CTL_PLL_UPD); + usleep_range(100, 200); + + bcma_write32(core, 0x510, 0x7f8d007); + udelay(1000); + + /* Take controller out of reset */ + bcma_write32(core, 0x200, 0x4ff); + usleep_range(25, 50); + bcma_write32(core, 0x200, 0x6ff); + usleep_range(25, 50); + bcma_write32(core, 0x200, 0x7ff); + usleep_range(25, 50); + + of_platform_default_populate(dev->of_node, NULL, dev); + + return 0; +} + +static void bcma_hcd_usb20_ns_init_hc(struct bcma_device *dev) +{ + u32 val; + + /* Set packet buffer OUT threshold */ + val = bcma_read32(dev, 0x94); + val &= 0xffff; + val |= 0x80 << 16; + bcma_write32(dev, 0x94, val); + + /* Enable break memory transfer */ + val = bcma_read32(dev, 0x9c); + val |= 1; + bcma_write32(dev, 0x9c, val); + + /* + * Broadcom initializes PHY and then waits to ensure HC is ready to be + * configured. In our case the order is reversed. We just initialized + * controller and we let HCD initialize PHY, so let's wait (sleep) now. + */ + usleep_range(1000, 2000); +} + +/* + * bcma_hcd_usb20_ns_init - Initialize Northstar USB 2.0 controller + */ +static int bcma_hcd_usb20_ns_init(struct bcma_hcd_device *bcma_hcd) +{ + struct bcma_device *core = bcma_hcd->core; + struct bcma_chipinfo *ci = &core->bus->chipinfo; + struct device *dev = &core->dev; + + bcma_core_enable(core, 0); + + if (ci->id == BCMA_CHIP_ID_BCM4707 || + ci->id == BCMA_CHIP_ID_BCM53018) + bcma_hcd_usb20_ns_init_hc(core); + + of_platform_default_populate(dev->of_node, NULL, dev); + + return 0; +} + +static void bcma_hci_platform_power_gpio(struct bcma_device *dev, bool val) +{ + struct bcma_hcd_device *usb_dev = bcma_get_drvdata(dev); + + if (!usb_dev->gpio_desc) + return; + + gpiod_set_value(usb_dev->gpio_desc, val); +} + +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, + const char *name, u32 addr, + const void *data, + size_t size) +{ + struct platform_device *hci_dev; + struct resource hci_res[2]; + int ret; + + 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(name, 0); + if (!hci_dev) + return ERR_PTR(-ENOMEM); + + 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 (data) + ret = platform_device_add_data(hci_dev, data, size); + 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_usb20_init(struct bcma_hcd_device *usb_dev) +{ + struct bcma_device *dev = usb_dev->core; + struct bcma_chipinfo *chipinfo = &dev->bus->chipinfo; + u32 ohci_addr; + int err; + + if (dma_set_mask_and_coherent(dev->dma_dev, DMA_BIT_MASK(32))) + return -EOPNOTSUPP; + + bcma_hcd_init_chip_mips(dev); + + /* In AI chips EHCI is addrspace 0, OHCI is 1 */ + ohci_addr = dev->addr_s[0]; + if ((chipinfo->id == BCMA_CHIP_ID_BCM5357 || + chipinfo->id == BCMA_CHIP_ID_BCM4749) + && chipinfo->rev == 0) + ohci_addr = 0x18009000; + + usb_dev->ohci_dev = bcma_hcd_create_pdev(dev, "ohci-platform", + ohci_addr, &ohci_pdata, + sizeof(ohci_pdata)); + if (IS_ERR(usb_dev->ohci_dev)) + return PTR_ERR(usb_dev->ohci_dev); + + usb_dev->ehci_dev = bcma_hcd_create_pdev(dev, "ehci-platform", + dev->addr, &ehci_pdata, + sizeof(ehci_pdata)); + if (IS_ERR(usb_dev->ehci_dev)) { + err = PTR_ERR(usb_dev->ehci_dev); + goto err_unregister_ohci_dev; + } + + return 0; + +err_unregister_ohci_dev: + platform_device_unregister(usb_dev->ohci_dev); + return err; +} + +static int bcma_hcd_usb30_init(struct bcma_hcd_device *bcma_hcd) +{ + struct bcma_device *core = bcma_hcd->core; + struct device *dev = &core->dev; + + bcma_core_enable(core, 0); + + of_platform_default_populate(dev->of_node, NULL, dev); + + return 0; +} + +static int bcma_hcd_probe(struct bcma_device *core) +{ + int err; + struct bcma_hcd_device *usb_dev; + + /* TODO: Probably need checks here; is the core connected? */ + + usb_dev = devm_kzalloc(&core->dev, sizeof(struct bcma_hcd_device), + GFP_KERNEL); + if (!usb_dev) + return -ENOMEM; + usb_dev->core = core; + + usb_dev->gpio_desc = devm_gpiod_get_optional(&core->dev, "vcc", + GPIOD_OUT_HIGH); + if (IS_ERR(usb_dev->gpio_desc)) + return dev_err_probe(&core->dev, PTR_ERR(usb_dev->gpio_desc), + "error obtaining VCC GPIO"); + + switch (core->id.id) { + case BCMA_CORE_USB20_HOST: + if (IS_ENABLED(CONFIG_ARM)) + err = bcma_hcd_usb20_old_arm_init(usb_dev); + else if (IS_ENABLED(CONFIG_MIPS)) + err = bcma_hcd_usb20_init(usb_dev); + else + err = -ENOTSUPP; + break; + case BCMA_CORE_NS_USB20: + err = bcma_hcd_usb20_ns_init(usb_dev); + break; + case BCMA_CORE_NS_USB30: + err = bcma_hcd_usb30_init(usb_dev); + break; + default: + return -ENODEV; + } + if (err) + return err; + + bcma_set_drvdata(core, usb_dev); + return 0; +} + +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_hci_platform_power_gpio(dev, false); + bcma_core_disable(dev, 0); +} + +#ifdef CONFIG_PM + +static int bcma_hcd_suspend(struct bcma_device *dev) +{ + bcma_hci_platform_power_gpio(dev, false); + bcma_core_disable(dev, 0); + + return 0; +} + +static int bcma_hcd_resume(struct bcma_device *dev) +{ + bcma_hci_platform_power_gpio(dev, true); + 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_CORE(BCMA_MANUF_BCM, BCMA_CORE_NS_USB20, BCMA_ANY_REV, BCMA_ANY_CLASS), + BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_NS_USB30, BCMA_ANY_REV, BCMA_ANY_CLASS), + {}, +}; +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, +}; +module_bcma_driver(bcma_hcd_driver); diff --git a/drivers/usb/host/ehci-atmel.c b/drivers/usb/host/ehci-atmel.c new file mode 100644 index 000000000..65747270f --- /dev/null +++ b/drivers/usb/host/ehci-atmel.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for EHCI UHP on Atmel chips + * + * Copyright (C) 2009 Atmel Corporation, + * Nicolas Ferre + * + * Based on various ehci-*.c drivers + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define DRIVER_DESC "EHCI Atmel driver" + +#define EHCI_INSNREG(index) ((index) * 4 + 0x90) +#define EHCI_INSNREG08_HSIC_EN BIT(2) + +/* interface and function clocks */ +#define hcd_to_atmel_ehci_priv(h) \ + ((struct atmel_ehci_priv *)hcd_to_ehci(h)->priv) + +struct atmel_ehci_priv { + struct clk *iclk; + struct clk *uclk; + bool clocked; +}; + +static struct hc_driver __read_mostly ehci_atmel_hc_driver; + +static const struct ehci_driver_overrides ehci_atmel_drv_overrides __initconst = { + .extra_priv_size = sizeof(struct atmel_ehci_priv), +}; + +/*-------------------------------------------------------------------------*/ + +static void atmel_start_clock(struct atmel_ehci_priv *atmel_ehci) +{ + if (atmel_ehci->clocked) + return; + + clk_prepare_enable(atmel_ehci->uclk); + clk_prepare_enable(atmel_ehci->iclk); + atmel_ehci->clocked = true; +} + +static void atmel_stop_clock(struct atmel_ehci_priv *atmel_ehci) +{ + if (!atmel_ehci->clocked) + return; + + clk_disable_unprepare(atmel_ehci->iclk); + clk_disable_unprepare(atmel_ehci->uclk); + atmel_ehci->clocked = false; +} + +static void atmel_start_ehci(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct atmel_ehci_priv *atmel_ehci = hcd_to_atmel_ehci_priv(hcd); + + dev_dbg(&pdev->dev, "start\n"); + atmel_start_clock(atmel_ehci); +} + +static void atmel_stop_ehci(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct atmel_ehci_priv *atmel_ehci = hcd_to_atmel_ehci_priv(hcd); + + dev_dbg(&pdev->dev, "stop\n"); + atmel_stop_clock(atmel_ehci); +} + +/*-------------------------------------------------------------------------*/ + +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; + struct atmel_ehci_priv *atmel_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) { + retval = irq; + 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. + */ + retval = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (retval) + goto fail_create_hcd; + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + retval = -ENOMEM; + goto fail_create_hcd; + } + atmel_ehci = hcd_to_atmel_ehci_priv(hcd); + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(hcd->regs)) { + retval = PTR_ERR(hcd->regs); + goto fail_request_resource; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + atmel_ehci->iclk = devm_clk_get(&pdev->dev, "ehci_clk"); + if (IS_ERR(atmel_ehci->iclk)) { + dev_err(&pdev->dev, "Error getting interface clock\n"); + retval = -ENOENT; + goto fail_request_resource; + } + + atmel_ehci->uclk = devm_clk_get(&pdev->dev, "usb_clk"); + if (IS_ERR(atmel_ehci->uclk)) { + dev_err(&pdev->dev, "failed to get uclk\n"); + retval = PTR_ERR(atmel_ehci->uclk); + 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; + device_wakeup_enable(hcd->self.controller); + + if (of_usb_get_phy_mode(pdev->dev.of_node) == USBPHY_INTERFACE_MODE_HSIC) + writel(EHCI_INSNREG08_HSIC_EN, hcd->regs + EHCI_INSNREG(8)); + + 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 void 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); +} + +static int __maybe_unused ehci_atmel_drv_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct atmel_ehci_priv *atmel_ehci = hcd_to_atmel_ehci_priv(hcd); + int ret; + + ret = ehci_suspend(hcd, false); + if (ret) + return ret; + + atmel_stop_clock(atmel_ehci); + return 0; +} + +static int __maybe_unused ehci_atmel_drv_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct atmel_ehci_priv *atmel_ehci = hcd_to_atmel_ehci_priv(hcd); + + atmel_start_clock(atmel_ehci); + ehci_resume(hcd, false); + 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 SIMPLE_DEV_PM_OPS(ehci_atmel_pm_ops, ehci_atmel_drv_suspend, + ehci_atmel_drv_resume); + +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", + .pm = &ehci_atmel_pm_ops, + .of_match_table = of_match_ptr(atmel_ehci_dt_ids), + }, +}; + +static int __init ehci_atmel_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ehci_init_driver(&ehci_atmel_hc_driver, &ehci_atmel_drv_overrides); + 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-brcm.c b/drivers/usb/host/ehci-brcm.c new file mode 100644 index 000000000..5e3156f94 --- /dev/null +++ b/drivers/usb/host/ehci-brcm.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2020, Broadcom */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define hcd_to_ehci_priv(h) ((struct brcm_priv *)hcd_to_ehci(h)->priv) + +struct brcm_priv { + struct clk *clk; +}; + +/* + * ehci_brcm_wait_for_sof + * Wait for start of next microframe, then wait extra delay microseconds + */ +static inline void ehci_brcm_wait_for_sof(struct ehci_hcd *ehci, u32 delay) +{ + u32 frame_idx = ehci_readl(ehci, &ehci->regs->frame_index); + u32 val; + int res; + + /* Wait for next microframe (every 125 usecs) */ + res = readl_relaxed_poll_timeout_atomic(&ehci->regs->frame_index, + val, val != frame_idx, 1, 130); + if (res) + ehci_err(ehci, "Error waiting for SOF\n"); + udelay(delay); +} + +/* + * ehci_brcm_hub_control + * The EHCI controller has a bug where it can violate the SOF + * interval between the first two SOF's transmitted after resume + * if the resume occurs near the end of the microframe. This causees + * the controller to detect babble on the suspended port and + * will eventually cause the controller to reset the port. + * The fix is to Intercept the echi-hcd request to complete RESUME and + * align it to the start of the next microframe. + * See SWLINUX-1909 for more details + */ +static int ehci_brcm_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; + unsigned long flags; + int retval, irq_disabled = 0; + u32 temp; + + temp = (wIndex & 0xff) - 1; + if (temp >= HCS_N_PORTS_MAX) /* Avoid index-out-of-bounds warning */ + temp = 0; + status_reg = &ehci->regs->port_status[temp]; + + /* + * RESUME is cleared when GetPortStatus() is called 20ms after start + * of RESUME + */ + if ((typeReq == GetPortStatus) && + (wIndex && wIndex <= ports) && + ehci->reset_done[wIndex-1] && + time_after_eq(jiffies, ehci->reset_done[wIndex-1]) && + (ehci_readl(ehci, status_reg) & PORT_RESUME)) { + + /* + * to make sure we are not interrupted until RESUME bit + * is cleared, disable interrupts on current CPU + */ + ehci_dbg(ehci, "SOF alignment workaround\n"); + irq_disabled = 1; + local_irq_save(flags); + ehci_brcm_wait_for_sof(ehci, 5); + } + retval = ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); + if (irq_disabled) + local_irq_restore(flags); + return retval; +} + +static int ehci_brcm_reset(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int len; + + ehci->big_endian_mmio = 1; + + ehci->caps = (void __iomem *)hcd->regs; + len = HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase)); + ehci->regs = (void __iomem *)(hcd->regs + len); + + /* This fixes the lockup during reboot due to prior interrupts */ + ehci_writel(ehci, CMD_RESET, &ehci->regs->command); + mdelay(10); + + /* + * SWLINUX-1705: Avoid OUT packet underflows during high memory + * bus usage + */ + ehci_writel(ehci, 0x00800040, &ehci->regs->brcm_insnreg[1]); + ehci_writel(ehci, 0x00000001, &ehci->regs->brcm_insnreg[3]); + + return ehci_setup(hcd); +} + +static struct hc_driver __read_mostly ehci_brcm_hc_driver; + +static const struct ehci_driver_overrides brcm_overrides __initconst = { + .reset = ehci_brcm_reset, + .extra_priv_size = sizeof(struct brcm_priv), +}; + +static int ehci_brcm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res_mem; + struct brcm_priv *priv; + struct usb_hcd *hcd; + int irq; + int err; + + err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (err) + return err; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + /* Hook the hub control routine to work around a bug */ + ehci_brcm_hc_driver.hub_control = ehci_brcm_hub_control; + + /* initialize hcd */ + hcd = usb_create_hcd(&ehci_brcm_hc_driver, dev, dev_name(dev)); + if (!hcd) + return -ENOMEM; + + platform_set_drvdata(pdev, hcd); + priv = hcd_to_ehci_priv(hcd); + + priv->clk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(priv->clk)) { + err = PTR_ERR(priv->clk); + goto err_hcd; + } + + err = clk_prepare_enable(priv->clk); + if (err) + goto err_hcd; + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res_mem); + if (IS_ERR(hcd->regs)) { + err = PTR_ERR(hcd->regs); + goto err_clk; + } + hcd->rsrc_start = res_mem->start; + hcd->rsrc_len = resource_size(res_mem); + err = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (err) + goto err_clk; + + device_wakeup_enable(hcd->self.controller); + device_enable_async_suspend(hcd->self.controller); + + return 0; + +err_clk: + clk_disable_unprepare(priv->clk); +err_hcd: + usb_put_hcd(hcd); + + return err; +} + +static void ehci_brcm_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct brcm_priv *priv = hcd_to_ehci_priv(hcd); + + usb_remove_hcd(hcd); + clk_disable_unprepare(priv->clk); + usb_put_hcd(hcd); +} + +static int __maybe_unused ehci_brcm_suspend(struct device *dev) +{ + int ret; + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct brcm_priv *priv = hcd_to_ehci_priv(hcd); + bool do_wakeup = device_may_wakeup(dev); + + ret = ehci_suspend(hcd, do_wakeup); + if (ret) + return ret; + clk_disable_unprepare(priv->clk); + return 0; +} + +static int __maybe_unused ehci_brcm_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct brcm_priv *priv = hcd_to_ehci_priv(hcd); + int err; + + err = clk_prepare_enable(priv->clk); + if (err) + return err; + /* + * SWLINUX-1705: Avoid OUT packet underflows during high memory + * bus usage + */ + ehci_writel(ehci, 0x00800040, &ehci->regs->brcm_insnreg[1]); + ehci_writel(ehci, 0x00000001, &ehci->regs->brcm_insnreg[3]); + + ehci_resume(hcd, false); + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(ehci_brcm_pm_ops, ehci_brcm_suspend, + ehci_brcm_resume); + +static const struct of_device_id brcm_ehci_of_match[] = { + { .compatible = "brcm,ehci-brcm-v2", }, + { .compatible = "brcm,bcm7445-ehci", }, + {} +}; +MODULE_DEVICE_TABLE(of, brcm_ehci_of_match); + +static struct platform_driver ehci_brcm_driver = { + .probe = ehci_brcm_probe, + .remove = ehci_brcm_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "ehci-brcm", + .pm = &ehci_brcm_pm_ops, + .of_match_table = brcm_ehci_of_match, + } +}; + +static int __init ehci_brcm_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ehci_init_driver(&ehci_brcm_hc_driver, &brcm_overrides); + return platform_driver_register(&ehci_brcm_driver); +} +module_init(ehci_brcm_init); + +static void __exit ehci_brcm_exit(void) +{ + platform_driver_unregister(&ehci_brcm_driver); +} +module_exit(ehci_brcm_exit); + +MODULE_ALIAS("platform:ehci-brcm"); +MODULE_DESCRIPTION("EHCI Broadcom STB driver"); +MODULE_AUTHOR("Al Cooper"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c new file mode 100644 index 000000000..3cf743c81 --- /dev/null +++ b/drivers/usb/host/ehci-dbg.c @@ -0,0 +1,1081 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2001-2002 by David Brownell + */ + +/* this file is part of ehci-hcd.c */ + +#ifdef CONFIG_DYNAMIC_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); + } +} + +/* + * 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" : ""); + } +} + +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: /* low speed */ + sig = "k"; + break; + 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" : ""); +} + +static inline void +dbg_status(struct ehci_hcd *ehci, const char *label, u32 status) +{ + char buf[80]; + + dbg_status_buf(buf, sizeof(buf), label, status); + ehci_dbg(ehci, "%s\n", buf); +} + +static inline void +dbg_cmd(struct ehci_hcd *ehci, const char *label, u32 command) +{ + char buf[80]; + + dbg_command_buf(buf, sizeof(buf), label, command); + ehci_dbg(ehci, "%s\n", buf); +} + +static inline void +dbg_port(struct ehci_hcd *ehci, const char *label, int port, u32 status) +{ + char buf[80]; + + dbg_port_buf(buf, sizeof(buf), label, port, status); + ehci_dbg(ehci, "%s\n", buf); +} + +/*-------------------------------------------------------------------------*/ + +/* troubleshooting help: expose state in debugfs */ + +static int debug_async_open(struct inode *, struct file *); +static int debug_bandwidth_open(struct inode *, struct file *); +static int debug_periodic_open(struct inode *, struct file *); +static int debug_registers_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_bandwidth_fops = { + .owner = THIS_MODULE, + .open = debug_bandwidth_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; +}; + +static inline char speed_char(u32 info1) +{ + switch (info1 & (3 << 12)) { + case QH_FULL_SPEED: + return 'f'; + case QH_LOW_SPEED: + return 'l'; + case QH_HIGH_SPEED: + return 'h'; + default: + return '?'; + } +} + +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)" + " [cur %08x next %08x buf[0] %08x]", + 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, + hc32_to_cpup(ehci, &hw->hw_current), + hc32_to_cpup(ehci, &hw->hw_qtd_next), + hc32_to_cpup(ehci, &hw->hw_buf[0])); + size -= temp; + next += temp; + + /* hc may be modifying the list as we read it ... */ + list_for_each(entry, &qh->qtd_list) { + char *type; + + 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 = '/'; + } + switch ((scratch >> 8) & 0x03) { + case PID_CODE_OUT: + type = "out"; + break; + case PID_CODE_IN: + type = "in"; + break; + case PID_CODE_SETUP: + type = "setup"; + break; + default: + type = "?"; + break; + } + temp = scnprintf(next, size, + "\n\t%p%c%s len=%d %08x urb %p" + " [td %08x buf[0] %08x]", + td, mark, type, + (scratch >> 16) & 0x7fff, + scratch, + td->urb, + (u32) td->qtd_dma, + hc32_to_cpup(ehci, &td->hw_buf[0])); + size -= temp; + next += temp; + if (temp == size) + goto done; + } + + temp = scnprintf(next, size, "\n"); + 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); +} + +static ssize_t fill_bandwidth_buffer(struct debug_buffer *buf) +{ + struct ehci_hcd *ehci; + struct ehci_tt *tt; + struct ehci_per_sched *ps; + unsigned temp, size; + char *next; + unsigned i; + u8 *bw; + u16 *bf; + u8 budget[EHCI_BANDWIDTH_SIZE]; + + ehci = hcd_to_ehci(bus_to_hcd(buf->bus)); + next = buf->output_buf; + size = buf->alloc_size; + + *next = 0; + + spin_lock_irq(&ehci->lock); + + /* Dump the HS bandwidth table */ + temp = scnprintf(next, size, + "HS bandwidth allocation (us per microframe)\n"); + size -= temp; + next += temp; + for (i = 0; i < EHCI_BANDWIDTH_SIZE; i += 8) { + bw = &ehci->bandwidth[i]; + temp = scnprintf(next, size, + "%2u: %4u%4u%4u%4u%4u%4u%4u%4u\n", + i, bw[0], bw[1], bw[2], bw[3], + bw[4], bw[5], bw[6], bw[7]); + size -= temp; + next += temp; + } + + /* Dump all the FS/LS tables */ + list_for_each_entry(tt, &ehci->tt_list, tt_list) { + temp = scnprintf(next, size, + "\nTT %s port %d FS/LS bandwidth allocation (us per frame)\n", + dev_name(&tt->usb_tt->hub->dev), + tt->tt_port + !!tt->usb_tt->multi); + size -= temp; + next += temp; + + bf = tt->bandwidth; + temp = scnprintf(next, size, + " %5u%5u%5u%5u%5u%5u%5u%5u\n", + bf[0], bf[1], bf[2], bf[3], + bf[4], bf[5], bf[6], bf[7]); + size -= temp; + next += temp; + + temp = scnprintf(next, size, + "FS/LS budget (us per microframe)\n"); + size -= temp; + next += temp; + compute_tt_budget(budget, tt); + for (i = 0; i < EHCI_BANDWIDTH_SIZE; i += 8) { + bw = &budget[i]; + temp = scnprintf(next, size, + "%2u: %4u%4u%4u%4u%4u%4u%4u%4u\n", + i, bw[0], bw[1], bw[2], bw[3], + bw[4], bw[5], bw[6], bw[7]); + size -= temp; + next += temp; + } + list_for_each_entry(ps, &tt->ps_list, ps_list) { + temp = scnprintf(next, size, + "%s ep %02x: %4u @ %2u.%u+%u mask %04x\n", + dev_name(&ps->udev->dev), + ps->ep->desc.bEndpointAddress, + ps->tt_usecs, + ps->bw_phase, ps->phase_uf, + ps->bw_period, ps->cs_mask); + size -= temp; + next += temp; + } + } + spin_unlock_irq(&ehci->lock); + + return next - buf->output_buf; +} + +static unsigned output_buf_tds_dir(char *buf, struct ehci_hcd *ehci, + struct ehci_qh_hw *hw, struct ehci_qh *qh, unsigned size) +{ + u32 scratch = hc32_to_cpup(ehci, &hw->hw_info1); + struct ehci_qtd *qtd; + char *type = ""; + unsigned temp = 0; + + /* count tds, get ep direction */ + list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { + temp++; + switch ((hc32_to_cpu(ehci, qtd->hw_token) >> 8) & 0x03) { + case PID_CODE_OUT: + type = "out"; + continue; + case PID_CODE_IN: + type = "in"; + continue; + } + } + + return scnprintf(buf, size, " (%c%d ep%d%s [%d/%d] q%d p%d)", + speed_char(scratch), scratch & 0x007f, + (scratch >> 8) & 0x000f, type, qh->ps.usecs, + qh->ps.c_usecs, temp, 0x7ff & (scratch >> 16)); +} + +#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; + + seen = kmalloc_objs(*seen, DBG_SCHED_LIMIT, GFP_ATOMIC); + if (!seen) + 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->ps.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) { + temp = output_buf_tds_dir(next, ehci, + hw, p.qh, size); + + 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->ps.period, + 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_USB_PCI + /* EHCI 0.96 and later may have "extended capabilities" */ + if (dev_is_pci(hcd->self.controller)) { + 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; + } + offset = (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_obj(*buf); + + 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; + + 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; + + 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_bandwidth_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, + fill_bandwidth_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); + + debugfs_create_file("async", S_IRUGO, ehci->debug_dir, bus, + &debug_async_fops); + debugfs_create_file("bandwidth", S_IRUGO, ehci->debug_dir, bus, + &debug_bandwidth_fops); + debugfs_create_file("periodic", S_IRUGO, ehci->debug_dir, bus, + &debug_periodic_fops); + debugfs_create_file("registers", S_IRUGO, ehci->debug_dir, bus, + &debug_registers_fops); +} + +static inline void remove_debug_files(struct ehci_hcd *ehci) +{ + debugfs_remove_recursive(ehci->debug_dir); +} + +#else /* CONFIG_DYNAMIC_DEBUG */ + +static inline void dbg_hcs_params(struct ehci_hcd *ehci, char *label) { } +static inline void dbg_hcc_params(struct ehci_hcd *ehci, char *label) { } + +static inline void __maybe_unused dbg_qh(const char *label, + struct ehci_hcd *ehci, struct ehci_qh *qh) { } + +static inline int __maybe_unused dbg_status_buf(const char *buf, + unsigned int len, const char *label, u32 status) +{ return 0; } + +static inline int __maybe_unused dbg_command_buf(const char *buf, + unsigned int len, const char *label, u32 command) +{ return 0; } + +static inline int __maybe_unused dbg_intr_buf(const char *buf, + unsigned int len, const char *label, u32 enable) +{ return 0; } + +static inline int __maybe_unused dbg_port_buf(char *buf, + unsigned int len, const char *label, int port, u32 status) +{ return 0; } + +static inline void dbg_status(struct ehci_hcd *ehci, const char *label, + u32 status) { } +static inline void dbg_cmd(struct ehci_hcd *ehci, const char *label, + u32 command) { } +static inline void dbg_port(struct ehci_hcd *ehci, const char *label, + int port, u32 status) { } + +static inline void create_debug_files(struct ehci_hcd *bus) { } +static inline void remove_debug_files(struct ehci_hcd *bus) { } + +#endif /* CONFIG_DYNAMIC_DEBUG */ diff --git a/drivers/usb/host/ehci-exynos.c b/drivers/usb/host/ehci-exynos.c new file mode 100644 index 000000000..d2a5bedf7 --- /dev/null +++ b/drivers/usb/host/ehci-exynos.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Samsung Exynos USB HOST EHCI Controller + * + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * Author: Jingoo Han + * Author: Joonyoung Shim + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define DRIVER_DESC "EHCI Exynos 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 struct hc_driver __read_mostly exynos_ehci_hc_driver; + +#define PHY_NUMBER 3 + +struct exynos_ehci_hcd { + struct clk *clk; + struct device_node *of_node; + struct phy *phy[PHY_NUMBER]; + bool legacy_phy; +}; + +#define to_exynos_ehci(hcd) (struct exynos_ehci_hcd *)(hcd_to_ehci(hcd)->priv) + +static int exynos_ehci_get_phy(struct device *dev, + struct exynos_ehci_hcd *exynos_ehci) +{ + struct phy *phy; + int phy_number, num_phys; + int ret; + + /* Get PHYs for the controller */ + num_phys = of_count_phandle_with_args(dev->of_node, "phys", + "#phy-cells"); + for (phy_number = 0; phy_number < num_phys; phy_number++) { + phy = devm_of_phy_get_by_index(dev, dev->of_node, phy_number); + if (IS_ERR(phy)) + return PTR_ERR(phy); + exynos_ehci->phy[phy_number] = phy; + } + if (num_phys > 0) + return 0; + + /* Get PHYs using legacy bindings */ + for_each_available_child_of_node_scoped(dev->of_node, child) { + ret = of_property_read_u32(child, "reg", &phy_number); + if (ret) { + dev_err(dev, "Failed to parse device tree\n"); + return ret; + } + + if (phy_number >= PHY_NUMBER) { + dev_err(dev, "Invalid number of PHYs\n"); + return -EINVAL; + } + + phy = devm_of_phy_optional_get(dev, child, NULL); + exynos_ehci->phy[phy_number] = phy; + if (IS_ERR(phy)) + return PTR_ERR(phy); + } + + exynos_ehci->legacy_phy = true; + return 0; +} + +static int exynos_ehci_phy_enable(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct exynos_ehci_hcd *exynos_ehci = to_exynos_ehci(hcd); + int i; + int ret = 0; + + for (i = 0; ret == 0 && i < PHY_NUMBER; i++) + ret = phy_power_on(exynos_ehci->phy[i]); + if (ret) + for (i--; i >= 0; i--) + phy_power_off(exynos_ehci->phy[i]); + + return ret; +} + +static void exynos_ehci_phy_disable(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct exynos_ehci_hcd *exynos_ehci = to_exynos_ehci(hcd); + int i; + + for (i = 0; i < PHY_NUMBER; i++) + phy_power_off(exynos_ehci->phy[i]); +} + +static void exynos_setup_vbus_gpio(struct device *dev) +{ + struct gpio_desc *gpio; + int err; + + gpio = devm_gpiod_get_optional(dev, "samsung,vbus", GPIOD_OUT_HIGH); + err = PTR_ERR_OR_ZERO(gpio); + if (err) + dev_err(dev, "can't request ehci vbus gpio: %d\n", err); +} + +static int exynos_ehci_probe(struct platform_device *pdev) +{ + struct exynos_ehci_hcd *exynos_ehci; + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + struct resource *res; + 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. + */ + err = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (err) + return err; + + exynos_setup_vbus_gpio(&pdev->dev); + + hcd = usb_create_hcd(&exynos_ehci_hc_driver, + &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + dev_err(&pdev->dev, "Unable to create HCD\n"); + return -ENOMEM; + } + exynos_ehci = to_exynos_ehci(hcd); + + err = exynos_ehci_get_phy(&pdev->dev, exynos_ehci); + if (err) + goto fail_io; + + exynos_ehci->clk = devm_clk_get_enabled(&pdev->dev, "usbhost"); + + if (IS_ERR(exynos_ehci->clk)) { + dev_err(&pdev->dev, "Failed to get usbhost clock\n"); + err = PTR_ERR(exynos_ehci->clk); + goto fail_io; + } + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(hcd->regs)) { + err = PTR_ERR(hcd->regs); + goto fail_io; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + err = irq; + goto fail_io; + } + + err = exynos_ehci_phy_enable(&pdev->dev); + if (err) { + dev_err(&pdev->dev, "Failed to enable USB phy\n"); + goto fail_io; + } + + ehci = hcd_to_ehci(hcd); + ehci->caps = hcd->regs; + + /* + * Workaround: reset of_node pointer to avoid conflict between legacy + * Exynos EHCI port subnodes and generic USB device bindings + */ + exynos_ehci->of_node = pdev->dev.of_node; + if (exynos_ehci->legacy_phy) + pdev->dev.of_node = NULL; + + /* 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; + } + device_wakeup_enable(hcd->self.controller); + + platform_set_drvdata(pdev, hcd); + + return 0; + +fail_add_hcd: + exynos_ehci_phy_disable(&pdev->dev); + pdev->dev.of_node = exynos_ehci->of_node; +fail_io: + usb_put_hcd(hcd); + return err; +} + +static void exynos_ehci_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct exynos_ehci_hcd *exynos_ehci = to_exynos_ehci(hcd); + + pdev->dev.of_node = exynos_ehci->of_node; + + usb_remove_hcd(hcd); + + exynos_ehci_phy_disable(&pdev->dev); + + usb_put_hcd(hcd); +} + +static int exynos_ehci_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct exynos_ehci_hcd *exynos_ehci = to_exynos_ehci(hcd); + + bool do_wakeup = device_may_wakeup(dev); + int rc; + + rc = ehci_suspend(hcd, do_wakeup); + if (rc) + return rc; + + exynos_ehci_phy_disable(dev); + + clk_disable_unprepare(exynos_ehci->clk); + + return rc; +} + +static int exynos_ehci_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct exynos_ehci_hcd *exynos_ehci = to_exynos_ehci(hcd); + int ret; + + ret = clk_prepare_enable(exynos_ehci->clk); + if (ret) + return ret; + + ret = exynos_ehci_phy_enable(dev); + if (ret) { + dev_err(dev, "Failed to enable USB phy\n"); + clk_disable_unprepare(exynos_ehci->clk); + return ret; + } + + /* DMA burst Enable */ + writel(EHCI_INSNREG00_ENABLE_DMA_BURST, EHCI_INSNREG00(hcd->regs)); + + ehci_resume(hcd, false); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(exynos_ehci_pm_ops, + exynos_ehci_suspend, exynos_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 exynos_ehci_driver = { + .probe = exynos_ehci_probe, + .remove = exynos_ehci_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "exynos-ehci", + .pm = pm_ptr(&exynos_ehci_pm_ops), + .of_match_table = of_match_ptr(exynos_ehci_match), + } +}; +static const struct ehci_driver_overrides exynos_overrides __initconst = { + .extra_priv_size = sizeof(struct exynos_ehci_hcd), +}; + +static int __init ehci_exynos_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ehci_init_driver(&exynos_ehci_hc_driver, &exynos_overrides); + return platform_driver_register(&exynos_ehci_driver); +} +module_init(ehci_exynos_init); + +static void __exit ehci_exynos_cleanup(void) +{ + platform_driver_unregister(&exynos_ehci_driver); +} +module_exit(ehci_exynos_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_ALIAS("platform:exynos-ehci"); +MODULE_AUTHOR("Jingoo Han"); +MODULE_AUTHOR("Joonyoung Shim"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c new file mode 100644 index 000000000..6ed2fa541 --- /dev/null +++ b/drivers/usb/host/ehci-fsl.c @@ -0,0 +1,732 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2005-2009 MontaVista Software, Inc. + * Copyright 2008,2012,2015 Freescale Semiconductor, Inc. + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "ehci.h" +#include "ehci-fsl.h" + +#define DRIVER_DESC "Freescale EHCI Host controller driver" +#define DRV_NAME "fsl-ehci" + +static struct hc_driver __read_mostly fsl_ehci_hc_driver; + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + +/* + * fsl_ehci_drv_probe - initialize FSL-based HCDs + * @pdev: USB Host Controller being probed + * + * Context: task context, might sleep + * + * Allocates basic resources for this USB host controller. + */ +static int fsl_ehci_drv_probe(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + struct usb_hcd *hcd; + struct resource *res; + int irq; + int retval; + u32 tmp; + + pr_debug("initializing FSL-SOC USB Controller\n"); + + /* Need platform data for setup */ + pdata = dev_get_platdata(&pdev->dev); + 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; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + hcd = __usb_create_hcd(&fsl_ehci_hc_driver, pdev->dev.parent, + &pdev->dev, dev_name(&pdev->dev), NULL); + if (!hcd) { + retval = -ENOMEM; + goto err1; + } + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(hcd->regs)) { + retval = PTR_ERR(hcd->regs); + goto err2; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + 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 err2; + } + + /* Enable USB controller, 83xx or 8536 */ + if (pdata->have_sysif_regs && pdata->controller_ver < FSL_USB_VER_1_6) { + tmp = ioread32be(hcd->regs + FSL_SOC_USB_CTRL); + tmp &= ~CONTROL_REGISTER_W1C_MASK; + tmp |= 0x4; + iowrite32be(tmp, hcd->regs + FSL_SOC_USB_CTRL); + } + + /* Set USB_EN bit to select ULPI phy for USB controller version 2.5 */ + if (pdata->controller_ver == FSL_USB_VER_2_5 && + pdata->phy_mode == FSL_USB2_PHY_ULPI) + iowrite32be(USB_CTRL_USB_EN, hcd->regs + FSL_SOC_USB_CTRL); + + /* + * Enable UTMI phy and program PTS field in UTMI mode before asserting + * controller reset for USB Controller version 2.5 + */ + if (pdata->has_fsl_erratum_a007792) { + tmp = ioread32be(hcd->regs + FSL_SOC_USB_CTRL); + tmp &= ~CONTROL_REGISTER_W1C_MASK; + tmp |= CTRL_UTMI_PHY_EN; + iowrite32be(tmp, hcd->regs + FSL_SOC_USB_CTRL); + + writel(PORT_PTS_UTMI, hcd->regs + FSL_SOC_USB_PORTSC1); + } + + /* 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 err2; + device_wakeup_enable(hcd->self.controller); + +#ifdef CONFIG_USB_OTG + if (pdata->operating_mode == FSL_USB2_DR_OTG) { + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + hcd->usb_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->usb_phy); + + if (!IS_ERR_OR_NULL(hcd->usb_phy)) { + retval = otg_set_host(hcd->usb_phy->otg, + &ehci_to_hcd(ehci)->self); + if (retval) { + usb_put_phy(hcd->usb_phy); + goto err2; + } + } else { + dev_err(&pdev->dev, "can't find phy\n"); + retval = -ENODEV; + goto err2; + } + + hcd->skip_phy_initialization = 1; + } +#endif + return retval; + + 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; +} + +static bool usb_phy_clk_valid(struct usb_hcd *hcd) +{ + void __iomem *non_ehci = hcd->regs; + bool ret = true; + + if (!(ioread32be(non_ehci + FSL_SOC_USB_CTRL) & PHY_CLK_VALID)) + ret = false; + + return ret; +} + +static int ehci_fsl_setup_phy(struct usb_hcd *hcd, + enum fsl_usb2_phy_modes phy_mode, + unsigned int port_offset) +{ + u32 portsc, tmp; + 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_get_platdata(dev); + + 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 */ + /* turn off UTMI PHY first */ + tmp = ioread32be(non_ehci + FSL_SOC_USB_CTRL); + tmp &= ~(CONTROL_REGISTER_W1C_MASK | UTMI_PHY_EN); + iowrite32be(tmp, non_ehci + FSL_SOC_USB_CTRL); + + /* then turn on ULPI and enable USB controller */ + tmp = ioread32be(non_ehci + FSL_SOC_USB_CTRL); + tmp &= ~CONTROL_REGISTER_W1C_MASK; + tmp |= ULPI_PHY_CLK_SEL | USB_CTRL_USB_EN; + iowrite32be(tmp, non_ehci + FSL_SOC_USB_CTRL); + } + 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; + fallthrough; + case FSL_USB2_PHY_UTMI: + /* Presence of this node "has_fsl_erratum_a006918" + * in device-tree is used to stop USB controller + * initialization in Linux + */ + if (pdata->has_fsl_erratum_a006918) { + dev_warn(dev, "USB PHY clock invalid\n"); + return -EINVAL; + } + fallthrough; + case FSL_USB2_PHY_UTMI_DUAL: + /* PHY_CLK_VALID bit is de-featured from all controller + * versions below 2.4 and is to be checked only for + * internal UTMI phy + */ + if (pdata->controller_ver > FSL_USB_VER_2_4 && + pdata->have_sysif_regs && !usb_phy_clk_valid(hcd)) { + dev_err(dev, "USB PHY clock invalid\n"); + return -EINVAL; + } + + if (pdata->have_sysif_regs && pdata->controller_ver) { + /* controller version 1.6 or above */ + tmp = ioread32be(non_ehci + FSL_SOC_USB_CTRL); + tmp &= ~CONTROL_REGISTER_W1C_MASK; + tmp |= UTMI_PHY_EN; + iowrite32be(tmp, non_ehci + FSL_SOC_USB_CTRL); + + mdelay(FSL_UTMI_PHY_DLY); /* Delay for UTMI PHY CLK to + become stable - 10ms*/ + } + /* enable UTMI PHY */ + if (pdata->have_sysif_regs) { + tmp = ioread32be(non_ehci + FSL_SOC_USB_CTRL); + tmp &= ~CONTROL_REGISTER_W1C_MASK; + tmp |= CTRL_UTMI_PHY_EN; + iowrite32be(tmp, non_ehci + FSL_SOC_USB_CTRL); + } + portsc |= PORT_PTS_UTMI; + break; + case FSL_USB2_PHY_NONE: + break; + } + + if (pdata->have_sysif_regs && + pdata->controller_ver > FSL_USB_VER_1_6 && + !usb_phy_clk_valid(hcd)) { + dev_warn(hcd->self.controller, "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) { + tmp = ioread32be(non_ehci + FSL_SOC_USB_CTRL); + tmp &= ~CONTROL_REGISTER_W1C_MASK; + tmp |= USB_CTRL_USB_EN; + iowrite32be(tmp, non_ehci + FSL_SOC_USB_CTRL); + } + + 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 = dev_get_platdata(hcd->self.controller); + + 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 */ + iowrite32be(0x0 | SNOOP_SIZE_2GB, + non_ehci + FSL_SOC_USB_SNOOP1); + /* SNOOP2 starts from 0x80000000, size 2G */ + iowrite32be(0x80000000 | SNOOP_SIZE_2GB, + non_ehci + FSL_SOC_USB_SNOOP2); + } + + /* Deal with USB erratum A-005275 */ + if (pdata->has_fsl_erratum_a005275 == 1) + ehci->has_fsl_hs_errata = 1; + + if (pdata->has_fsl_erratum_a005697 == 1) + ehci->has_fsl_susp_errata = 1; + + 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) { + + /* Deal with USB Erratum #14 on MPC834x Rev 1.0 & 1.1 chips */ + if (pdata->has_fsl_erratum_14 == 1) + 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 + iowrite32be(0x00000008, non_ehci + FSL_SOC_USB_PRICTRL); + iowrite32be(0x00000080, non_ehci + FSL_SOC_USB_AGECNTTHRSH); +#else + iowrite32be(0x0000000c, non_ehci + FSL_SOC_USB_PRICTRL); + iowrite32be(0x00000040, non_ehci + FSL_SOC_USB_AGECNTTHRSH); +#endif + iowrite32be(0x00000001, non_ehci + FSL_SOC_USB_SICTRL); + } + + 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 = dev_get_platdata(hcd->self.controller); + 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; + +#if defined(CONFIG_PPC_83xx) || defined(CONFIG_PPC_85xx) + /* + * Deal with MPC834X/85XX 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_priv { + /* Saved USB PHY settings, need to restore after deep sleep. */ + u32 usb_ctrl; +}; + +#define hcd_to_ehci_fsl_priv(h) ((struct ehci_fsl_priv *) hcd_to_ehci(h)->priv) + +#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_get_platdata(dev); + u32 tmp; + +#ifdef CONFIG_DYNAMIC_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_get_platdata(dev); + 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 int ehci_fsl_drv_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct ehci_fsl_priv *priv = hcd_to_ehci_fsl_priv(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; + + priv->usb_ctrl = ioread32be(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_priv *priv = hcd_to_ehci_fsl_priv(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. */ + iowrite32be(priv->usb_ctrl, non_ehci + FSL_SOC_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 const 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; + + /* hub_wq 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 ehci_driver_overrides ehci_fsl_overrides __initconst = { + .extra_priv_size = sizeof(struct ehci_fsl_priv), + .reset = ehci_fsl_setup, +}; + +/** + * fsl_ehci_drv_remove - shutdown processing for FSL-based HCDs + * @pdev: USB Host Controller being removed + * + * Context: task context, might sleep + * + * Reverses the effect of usb_hcd_fsl_probe(). + */ +static void fsl_ehci_drv_remove(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + if (!IS_ERR_OR_NULL(hcd->usb_phy)) { + otg_set_host(hcd->usb_phy->otg, NULL); + usb_put_phy(hcd->usb_phy); + } + + usb_remove_hcd(hcd); + + /* + * do platform specific un-initialization: + * release iomux pins, disable clock, etc. + */ + if (pdata->exit) + pdata->exit(pdev); + usb_put_hcd(hcd); +} + +static struct platform_driver ehci_fsl_driver = { + .probe = fsl_ehci_drv_probe, + .remove = fsl_ehci_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = DRV_NAME, + .pm = EHCI_FSL_PM_OPS, + }, +}; + +static int __init ehci_fsl_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ehci_init_driver(&fsl_ehci_hc_driver, &ehci_fsl_overrides); + + fsl_ehci_hc_driver.product_desc = + "Freescale On-Chip EHCI Host Controller"; + fsl_ehci_hc_driver.start_port_reset = ehci_start_port_reset; + + + return platform_driver_register(&ehci_fsl_driver); +} +module_init(ehci_fsl_init); + +static void __exit ehci_fsl_cleanup(void) +{ + platform_driver_unregister(&ehci_fsl_driver); +} +module_exit(ehci_fsl_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/usb/host/ehci-fsl.h b/drivers/usb/host/ehci-fsl.h new file mode 100644 index 000000000..c95341d47 --- /dev/null +++ b/drivers/usb/host/ehci-fsl.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* Copyright (C) 2005-2010,2012 Freescale Semiconductor, Inc. + * Copyright (c) 2005 MontaVista Software + */ +#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 CONTROL_REGISTER_W1C_MASK 0x00020000 /* W1C: PHY_CLK_VALID */ +#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) + +/* Retry count for checking UTMI PHY CLK validity */ +#define UTMI_PHY_CLK_VALID_CHK_RETRY 5 +#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 000000000..bd9762eaa --- /dev/null +++ b/drivers/usb/host/ehci-grlib.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#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_DMA | HCD_USB2 | HCD_BH, + + /* + * 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) { + dev_err(&op->dev, "%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; + + device_wakeup_enable(hcd->self.controller); + return 0; + +err_ioremap: + irq_dispose_mapping(irq); +err_irq: + usb_put_hcd(hcd); + + return rv; +} + + +static void ehci_hcd_grlib_remove(struct platform_device *op) +{ + struct usb_hcd *hcd = platform_get_drvdata(op); + + dev_dbg(&op->dev, "stopping GRLIB GRUSBHC EHCI USB Controller\n"); + + usb_remove_hcd(hcd); + + irq_dispose_mapping(hcd->irq); + + usb_put_hcd(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 = usb_hcd_platform_shutdown, + .driver = { + .name = "grlib-ehci", + .of_match_table = ehci_hcd_grlib_of_match, + }, +}; diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c new file mode 100644 index 000000000..3c46bb18c --- /dev/null +++ b/drivers/usb/host/ehci-hcd.c @@ -0,0 +1,1401 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Enhanced Host Controller Interface (EHCI) driver for USB. + * + * Maintainer: Alan Stern + * + * Copyright (c) 2000-2004 by David Brownell + */ + +#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 + +#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 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 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"); + +#define INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT) + +/*-------------------------------------------------------------------------*/ + +#include "ehci.h" +#include "pci-quirks.h" + +static void compute_tt_budget(u8 budget_table[EHCI_BANDWIDTH_SIZE], + struct ehci_tt *tt); + +/* + * 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" + +/*-------------------------------------------------------------------------*/ + +/* + * ehci_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. + */ +int ehci_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; +} +EXPORT_SYMBOL_GPL(ehci_handshake); + +/* 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 ehci_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. + */ +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 = ehci_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; +} +EXPORT_SYMBOL_GPL(ehci_reset); + +/* + * 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); + ehci_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 ... */ + ehci_handshake(ehci, &ehci->regs->status, STS_ASS | STS_PSS, 0, + 16 * 125); +} + +/*-------------------------------------------------------------------------*/ + +static void end_iaa_cycle(struct ehci_hcd *ehci); +static void end_unlink_async(struct ehci_hcd *ehci); +static void unlink_empty_async(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); +static int ehci_port_power(struct ehci_hcd *ehci, int portnum, bool enable); + +#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--) { + spin_unlock_irq(&ehci->lock); + ehci_port_power(ehci, port, false); + spin_lock_irq(&ehci->lock); + 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); + + /** + * Protect the system from crashing at system shutdown in cases where + * usb host is not added yet from OTG controller driver. + * As ehci_setup() not done yet, so stop accessing registers or + * variables initialized in ehci_setup() + */ + if (!ehci->sbrn) + return; + + 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(); + + 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_setup(&ehci->hrtimer, ehci_hrtimer_func, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + 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_wait); + 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); + INIT_LIST_HEAD(&ehci->tt_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_t(unsigned int, park, 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->localmem_pool) + hcd->self.sg_tablesize = ~0; + + /* Prepare for unlinking active QHs */ + ehci->old_current = ~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; + int rc; + + 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. + * dma_pool consistent memory always uses segment zero. + * streaming mappings for I/O buffers, like dma_map_single(), + * can return segments above 4GB, if the device allows. + * + * NOTE: the dma mask is visible through dev->dma_mask, 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)) { + ehci_writel(ehci, 0, &ehci->regs->segment); +#if 0 +// this is deeply broken on almost all architectures + if (!dma_set_mask(hcd->self.controller, DMA_BIT_MASK(64))) + ehci_info(ehci, "enabled 64bit DMA\n"); +#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); + + /* Wait until HC become operational */ + ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ + msleep(5); + + /* For Aspeed, STS_HALT also depends on ASS/PSS status. + * Check CMD_RUN instead. + */ + if (ehci->is_aspeed) + rc = ehci_handshake(ehci, &ehci->regs->command, CMD_RUN, + 1, 100 * 1000); + else + rc = ehci_handshake(ehci, &ehci->regs->status, STS_HALT, + 0, 100 * 1000); + + up_write(&ehci_cf_port_reset_rwsem); + + if (rc) { + ehci_err(ehci, "USB %x.%x, controller refused to start: %d\n", + ((ehci->sbrn & 0xf0)>>4), (ehci->sbrn & 0x0f), rc); + return rc; + } + + 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 || ehci->spurious_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) { + ehci_mem_cleanup(ehci); + 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, current_status, masked_status, pcd_status = 0; + u32 cmd; + int bh; + + spin_lock(&ehci->lock); + + status = 0; + current_status = ehci_readl(ehci, &ehci->regs->status); +restart: + + /* e.g. cardbus physical eject */ + if (current_status == ~(u32) 0) { + ehci_dbg (ehci, "device removed\n"); + goto dead; + } + status |= current_status; + + /* + * 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 = current_status & (INTR_MASK | STS_FLR); + + /* Shared IRQ? */ + if (!masked_status || unlikely(ehci->rh_state == EHCI_RH_HALTED)) { + spin_unlock(&ehci->lock); + return IRQ_NONE; + } + + /* clear (just) interrupts */ + ehci_writel(ehci, masked_status, &ehci->regs->status); + + /* For edge interrupts, don't race with an interrupt bit being raised */ + current_status = ehci_readl(ehci, &ehci->regs->status); + if (current_status & INTR_MASK) + goto restart; + + cmd = ehci_readl(ehci, &ehci->regs->command); + bh = 0; + + /* 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)) { + INCR(ehci->stats.normal); + } else { + /* Force to check port status */ + if (ehci->has_ci_pec_bug) + status |= STS_PCD; + INCR(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) + INCR(ehci->stats.iaa); + end_iaa_cycle(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 USB_RESUME_TIMEOUT msec resume signaling from + * this port, and make hub_wq collect + * PORT_STAT_C_SUSPEND to stop that signaling. + */ + ehci->reset_done[i] = jiffies + + msecs_to_jiffies(USB_RESUME_TIMEOUT); + 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(&ehci->lock); + 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->unlink_reason |= QH_UNLINK_REQUESTED; + 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; + + /* 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)); */ + reserve_release_iso_bandwidth(ehci, stream, -1); + kfree(stream); + goto done; + } + + qh->unlink_reason |= QH_UNLINK_REQUESTED; + switch (qh->qh_state) { + case QH_STATE_LINKED: + if (list_empty(&qh->qtd_list)) + qh->unlink_reason |= QH_UNLINK_QUEUE_EMPTY; + else + WARN_ON(1); + if (usb_endpoint_type(&ep->desc) != USB_ENDPOINT_XFER_INT) + start_unlink_async(ehci, qh); + else + start_unlink_intr(ehci, qh); + fallthrough; + case QH_STATE_COMPLETING: /* already in unlinking */ + 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)) { + if (qh->ps.bw_uperiod) + reserve_release_intr_bandwidth(ehci, qh, -1); + qh_destroy(ehci, qh); + break; + } + fallthrough; + 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) { + 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(). + */ + usb_settoggle(qh->ps.udev, epnum, is_out, 0); + qh->unlink_reason |= QH_UNLINK_REQUESTED; + 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; +} + +/*-------------------------------------------------------------------------*/ + +/* Device addition and removal */ + +static void ehci_remove_device(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + spin_lock_irq(&ehci->lock); + drop_tt(udev); + spin_unlock_irq(&ehci->lock); +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM + +/* Clear wakeup signal locked in zhaoxin platform when device plug in. */ +static void ehci_zx_wakeup_clear(struct ehci_hcd *ehci) +{ + u32 __iomem *reg = &ehci->regs->port_status[4]; + u32 t1 = ehci_readl(ehci, reg); + + t1 &= (u32)~0xf0000; + t1 |= PORT_TEST_FORCE; + ehci_writel(ehci, t1, reg); + t1 = ehci_readl(ehci, reg); + msleep(1); + t1 &= (u32)~0xf0000; + ehci_writel(ehci, t1, reg); + ehci_readl(ehci, reg); + msleep(1); + t1 = ehci_readl(ehci, reg); + ehci_writel(ehci, t1 | PORT_CSC, reg); + ehci_readl(ehci, reg); +} + +/* 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); + + synchronize_irq(hcd->irq); + + /* Check for race with a wakeup request */ + if (do_wakeup && HCD_WAKEUP_PENDING(hcd)) { + ehci_resume(hcd, false); + return -EBUSY; + } + + 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 force_reset) +{ + 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 (ehci->zx_wakeup_clear_needed) + ehci_zx_wakeup_clear(ehci); + + /* + * If CF is still set and reset isn't forced + * then we maintained suspend power. + * Just undo the effect of ehci_suspend(). + */ + if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF && + !force_reset) { + 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_DMA | HCD_USB2 | HCD_BH, + + /* + * 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, + .get_resuming_ports = ehci_get_resuming_ports, + + /* + * device support + */ + .free_dev = ehci_remove_device, +#ifdef CONFIG_USB_HCD_TEST_MODE + /* EH SINGLE_STEP_SET_FEATURE test support */ + .submit_single_step_set_feature = ehci_submit_single_step_set_feature, +#endif +}; + +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; + if (over->port_power) + drv->port_power = over->port_power; + } +} +EXPORT_SYMBOL_GPL(ehci_init_driver); + +/*-------------------------------------------------------------------------*/ + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR (DRIVER_AUTHOR); +MODULE_LICENSE ("GPL"); + +#ifdef CONFIG_USB_EHCI_SH +#include "ehci-sh.c" +#endif + +#ifdef CONFIG_PPC_PS3 +#include "ehci-ps3.c" +#endif + +#ifdef CONFIG_USB_EHCI_HCD_PPC_OF +#include "ehci-ppc-of.c" +#endif + +#ifdef CONFIG_XPS_USB_HCD_XILINX +#include "ehci-xilinx-of.c" +#endif + +#ifdef CONFIG_SPARC_LEON +#include "ehci-grlib.c" +#endif + +static struct platform_driver * const platform_drivers[] = { +#ifdef CONFIG_USB_EHCI_SH + &ehci_hcd_sh_driver, +#endif +#ifdef CONFIG_USB_EHCI_HCD_PPC_OF + &ehci_hcd_ppc_of_driver, +#endif +#ifdef CONFIG_XPS_USB_HCD_XILINX + &ehci_hcd_xilinx_of_driver, +#endif +#ifdef CONFIG_SPARC_LEON + &ehci_grlib_driver, +#endif +}; + +static int __init ehci_hcd_init(void) +{ + int retval = 0; + + if (usb_disabled()) + return -ENODEV; + + 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 CONFIG_DYNAMIC_DEBUG + ehci_debug_root = debugfs_create_dir("ehci", usb_debug_root); +#endif + + retval = platform_register_drivers(platform_drivers, ARRAY_SIZE(platform_drivers)); + if (retval < 0) + goto clean0; + +#ifdef CONFIG_PPC_PS3 + retval = ps3_ehci_driver_register(&ps3_ehci_driver); + if (retval < 0) + goto clean1; +#endif + + return 0; + +#ifdef CONFIG_PPC_PS3 +clean1: +#endif + platform_unregister_drivers(platform_drivers, ARRAY_SIZE(platform_drivers)); +clean0: +#ifdef CONFIG_DYNAMIC_DEBUG + debugfs_remove(ehci_debug_root); + ehci_debug_root = NULL; +#endif + return retval; +} +module_init(ehci_hcd_init); + +static void __exit ehci_hcd_cleanup(void) +{ +#ifdef CONFIG_PPC_PS3 + ps3_ehci_driver_unregister(&ps3_ehci_driver); +#endif + platform_unregister_drivers(platform_drivers, ARRAY_SIZE(platform_drivers)); +#ifdef CONFIG_DYNAMIC_DEBUG + debugfs_remove(ehci_debug_root); +#endif +} +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 000000000..1aee392e8 --- /dev/null +++ b/drivers/usb/host/ehci-hub.c @@ -0,0 +1,1222 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2001-2004 by David Brownell + */ + +/* this file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Root Hub ... the nonsharable stuff + * + * Registers don't need cpu_to_le32, that happens transparently + */ + +/*-------------------------------------------------------------------------*/ + +#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) + +#ifdef CONFIG_PM + +static void unlink_empty_async_suspended(struct ehci_hcd *ehci); + +static int persist_enabled_on_companion(struct usb_device *udev, void *unused) +{ + return !udev->maxchild && udev->persist_enabled && + udev->bus->root_hub->speed < USB_SPEED_HIGH; +} + +/* 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; + + /* + * USB 1.1 devices are mostly HIDs, which don't need to persist across + * suspends. If we ensure that none of our companion's devices have + * persist_enabled (by looking through all USB 1.1 buses in the system), + * we can skip this and avoid slowing resume down. Devices without + * persist will just get reenumerated shortly after resume anyway. + */ + if (!usb_for_each_dev(NULL, persist_enabled_on_companion)) + 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)) + ehci_port_power(ehci, port, true); + } + } + + /* 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; +} + +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_tdi_phy_lpm) { + 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_writel(ehci, t2, reg); + } + + /* enter phy low-power mode again */ + if (ehci->has_tdi_phy_lpm) { + 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); +} +EXPORT_SYMBOL_GPL(ehci_adjust_port_wakeup_flags); + +static int ehci_bus_suspend (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + int port; + int mask; + int changed; + bool fs_idle_delay; + + 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; + fs_idle_delay = false; + 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) { + /* + * On some controllers, Wake-On-Disconnect will + * generate false wakeup signals until the bus + * switches over to full-speed idle. For their + * sake, add a delay if we need one. + */ + if ((t2 & PORT_WKDISC_E) && + ehci_port_speed(ehci, t2) == + USB_PORT_STAT_HIGH_SPEED) + fs_idle_delay = true; + ehci_writel(ehci, t2, reg); + changed = 1; + } + } + spin_unlock_irq(&ehci->lock); + + if (changed && ehci_has_fsl_susp_errata(ehci)) + /* + * Wait for at least 10 millisecondes to ensure the controller + * enter the suspend status before initiating a port resume + * using the Force Port Resume bit (Not-EHCI compatible). + */ + usleep_range(10000, 20000); + + if ((changed && ehci->has_tdi_phy_lpm) || fs_idle_delay) { + /* + * Wait for HCD to enter low-power mode or for the bus + * to switch to full-speed idle. + */ + usleep_range(5000, 5500); + } + + if (changed && ehci->has_tdi_phy_lpm) { + 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; + + unlink_empty_async_suspended(ehci); + + /* Some Synopsys controllers mistakenly leave IAA turned on */ + ehci_writel(ehci, STS_IAA, &ehci->regs->status); + + /* Any IAA cycle that started before the suspend is now invalid */ + end_iaa_cycle(ehci); + ehci_handle_start_intr_unlinks(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_tdi_phy_lpm) { + 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 USB_RESUME_TIMEOUT ms only if code is trying to resume + * port + */ + if (resume_needed) { + spin_unlock_irq(&ehci->lock); + msleep(USB_RESUME_TIMEOUT); + 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->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; +} + +static unsigned long ehci_get_resuming_ports(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + return ehci->resuming_ports; +} + +#else + +#define ehci_bus_suspend NULL +#define ehci_bus_resume NULL +#define ehci_get_resuming_ports 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 && !ehci->spurious_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 hub_wq 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])) + || ehci_has_ci_pec_bug(ehci, temp)) { + 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 = USB_DT_HUB; + 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 = HUB_CHAR_INDV_PORT_OCPM; /* per-port overcurrent reporting */ + if (HCS_PPC (ehci->hcs_params)) + temp |= HUB_CHAR_INDV_PORT_LPSM; /* per-port power control */ + else + temp |= HUB_CHAR_NO_LPSM; /* no power switching */ +#if 0 +// re-enable when we support USB_PORT_FEAT_INDICATOR below. + if (HCS_INDICATOR (ehci->hcs_params)) + temp |= HUB_CHAR_PORTIND; /* per-port indicators (LEDs) */ +#endif + desc->wHubCharacteristics = cpu_to_le16(temp); +} + +/*-------------------------------------------------------------------------*/ + +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, *hostpc_reg; + u32 temp, temp1, status; + unsigned long flags; + int retval = 0; + unsigned selector; + + /* + * Avoid out-of-bounds values while calculating the port index + * from wIndex. The compiler doesn't like pointers to invalid + * addresses, even if they are never used. + */ + temp = (wIndex - 1) & 0xff; + if (temp >= HCS_N_PORTS_MAX) + temp = 0; + status_reg = &ehci->regs->port_status[temp]; + hostpc_reg = &ehci->regs->hostpc[temp]; + + /* + * 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, hub_wq 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->usb_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_tdi_phy_lpm) { + 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(USB_RESUME_TIMEOUT); + set_bit(wIndex, &ehci->resuming_ports); + usb_hcd_start_port_resume(&hcd->self, wIndex); + 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)) { + spin_unlock_irqrestore(&ehci->lock, flags); + ehci_port_power(ehci, wIndex, false); + spin_lock_irqsave(&ehci->lock, flags); + } + 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 (ehci_has_ci_pec_bug(ehci, temp)) { + status |= USB_PORT_STAT_C_ENABLE << 16; + ehci_info(ehci, + "PE is cleared by HW port:%d PORTSC:%08x\n", + wIndex + 1, temp); + } + + if ((temp & PORT_OCC) && (!ignore_oc && !ehci->spurious_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. hub_wq will turn the power back on. + */ + if (((temp & PORT_OC) || (ehci->need_oc_pp_cycle)) + && HCS_PPC(ehci->hcs_params)) { + spin_unlock_irqrestore(&ehci->lock, flags); + ehci_port_power(ehci, wIndex, false); + spin_lock_irqsave(&ehci->lock, flags); + temp = ehci_readl(ehci, status_reg); + } + } + + /* no reset or resume pending */ + if (!ehci->reset_done[wIndex]) { + + /* Remote Wakeup received? */ + if (temp & PORT_RESUME) { + /* 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]); + } + + /* reset or resume not yet complete */ + } else if (!time_after_eq(jiffies, ehci->reset_done[wIndex])) { + ; /* wait until it is complete */ + + /* resume completed */ + } else if (test_bit(wIndex, &ehci->resuming_ports)) { + 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 = ehci_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!! */ + } else { + status |= USB_PORT_STAT_C_RESET << 16; + ehci->reset_done [wIndex] = 0; + + /* 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 = ehci_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)); + } + + /* 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 hub_wq + * 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; + + if (status & ~0xffff) /* only if wPortChange is interesting */ + 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 tdi_phy_lpm feature + */ + temp &= ~PORT_WKCONN_E; + temp |= PORT_WKDISC_E | PORT_WKOC_E; + ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); + if (ehci->has_tdi_phy_lpm) { + 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"); + } + if (ehci_has_fsl_susp_errata(ehci)) { + /* 10ms for HCD enter suspend */ + spin_unlock_irqrestore(&ehci->lock, flags); + usleep_range(10000, 20000); + spin_lock_irqsave(&ehci->lock, flags); + } + set_bit(wIndex, &ehci->suspended_ports); + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(ehci->hcs_params)) { + spin_unlock_irqrestore(&ehci->lock, flags); + ehci_port_power(ehci, wIndex, true); + spin_lock_irqsave(&ehci->lock, flags); + } + break; + case USB_PORT_FEAT_RESET: + if (temp & (PORT_SUSPEND|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 { + 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); + + /* + * Force full-speed connect for FSL high-speed + * erratum; disable HS Chirp by setting PFSC bit + */ + if (ehci_has_fsl_hs_errata(ehci)) + temp |= (1 << PORTSC_FSL_PFSC); + } + 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: +#ifdef CONFIG_USB_HCD_TEST_MODE + if (selector == EHSET_TEST_SINGLE_STEP_SET_FEATURE) { + spin_unlock_irqrestore(&ehci->lock, flags); + retval = ehset_single_step_set_feature(hcd, + wIndex + 1); + spin_lock_irqsave(&ehci->lock, flags); + break; + } +#endif + 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; +} +EXPORT_SYMBOL_GPL(ehci_hub_control); + +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; +} + +static int ehci_port_power(struct ehci_hcd *ehci, int portnum, bool enable) +{ + struct usb_hcd *hcd = ehci_to_hcd(ehci); + u32 __iomem *status_reg = &ehci->regs->port_status[portnum]; + u32 temp = ehci_readl(ehci, status_reg) & ~PORT_RWC_BITS; + + if (enable) + ehci_writel(ehci, temp | PORT_POWER, status_reg); + else + ehci_writel(ehci, temp & ~PORT_POWER, status_reg); + + if (hcd->driver->port_power) + hcd->driver->port_power(hcd, portnum, enable); + + return 0; +} diff --git a/drivers/usb/host/ehci-mem.c b/drivers/usb/host/ehci-mem.c new file mode 100644 index 000000000..a2d4499e6 --- /dev/null +++ b/drivers/usb/host/ehci-mem.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2001 by David Brownell + */ + +/* 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_obj(*qh, GFP_ATOMIC); + if (!qh) + goto done; + qh->hw = (struct ehci_qh_hw *) + dma_pool_zalloc(ehci->qh_pool, flags, &dma); + if (!qh->hw) + goto fail; + qh->qh_dma = dma; + // INIT_LIST_HEAD (&qh->qh_list); + INIT_LIST_HEAD (&qh->qtd_list); + INIT_LIST_HEAD(&qh->unlink_node); + + /* 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 */ + dma_pool_destroy(ehci->qtd_pool); + ehci->qtd_pool = NULL; + dma_pool_destroy(ehci->qh_pool); + ehci->qh_pool = NULL; + dma_pool_destroy(ehci->itd_pool); + ehci->itd_pool = NULL; + dma_pool_destroy(ehci->sitd_pool); + ehci->sitd_pool = NULL; + + if (ehci->periodic) + dma_free_coherent(ehci_to_hcd(ehci)->self.sysdev, + 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.sysdev, + 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.sysdev, + 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.sysdev, + 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.sysdev, + 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.sysdev, + ehci->periodic_size * sizeof(__le32), + &ehci->periodic_dma, flags); + 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); + ehci->dummy->hw = hw; + + for (i = 0; i < ehci->periodic_size; i++) + ehci->periodic[i] = cpu_to_hc32(ehci, + 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-mv.c b/drivers/usb/host/ehci-mv.c new file mode 100644 index 000000000..cbabbe107 --- /dev/null +++ b/drivers/usb/host/ehci-mv.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * Author: Chao Xie + * Neil Zhang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ehci.h" + +/* registers */ +#define U2x_CAPREGS_OFFSET 0x100 + +#define CAPLENGTH_MASK (0xff) + +#define hcd_to_ehci_hcd_mv(h) ((struct ehci_hcd_mv *)hcd_to_ehci(h)->priv) + +struct ehci_hcd_mv { + /* Which mode does this ehci running OTG/Host ? */ + int mode; + + void __iomem *base; + void __iomem *cap_regs; + void __iomem *op_regs; + + struct usb_phy *otg; + struct clk *clk; + + struct phy *phy; + + int (*set_vbus)(unsigned int vbus); +}; + +static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv) +{ + int retval; + + retval = clk_prepare_enable(ehci_mv->clk); + if (retval) + return retval; + + retval = phy_init(ehci_mv->phy); + if (retval) + clk_disable_unprepare(ehci_mv->clk); + + return retval; +} + +static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv) +{ + phy_exit(ehci_mv->phy); + clk_disable_unprepare(ehci_mv->clk); +} + +static int mv_ehci_reset(struct usb_hcd *hcd) +{ + struct device *dev = hcd->self.controller; + struct ehci_hcd_mv *ehci_mv = hcd_to_ehci_hcd_mv(hcd); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + u32 status; + 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); + + if (of_usb_get_phy_mode(dev->of_node) == USBPHY_INTERFACE_MODE_HSIC) { + status = ehci_readl(ehci, &ehci->regs->port_status[0]); + status |= PORT_TEST_FORCE; + ehci_writel(ehci, status, &ehci->regs->port_status[0]); + status &= ~PORT_TEST_FORCE; + ehci_writel(ehci, status, &ehci->regs->port_status[0]); + } + + return retval; +} + +static struct hc_driver __read_mostly ehci_platform_hc_driver; + +static const struct ehci_driver_overrides platform_overrides __initconst = { + .reset = mv_ehci_reset, + .extra_priv_size = sizeof(struct ehci_hcd_mv), +}; + +static int mv_ehci_probe(struct platform_device *pdev) +{ + struct mv_usb_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + struct ehci_hcd_mv *ehci_mv; + struct resource *r; + int retval; + u32 offset; + u32 status; + + if (usb_disabled()) + return -ENODEV; + + hcd = usb_create_hcd(&ehci_platform_hc_driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) + return -ENOMEM; + + platform_set_drvdata(pdev, hcd); + ehci_mv = hcd_to_ehci_hcd_mv(hcd); + + ehci_mv->mode = MV_USB_MODE_HOST; + if (pdata) { + ehci_mv->mode = pdata->mode; + ehci_mv->set_vbus = pdata->set_vbus; + } + + ehci_mv->phy = devm_phy_optional_get(&pdev->dev, "usb"); + if (IS_ERR(ehci_mv->phy)) { + retval = PTR_ERR(ehci_mv->phy); + if (retval != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to get phy.\n"); + goto err_put_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_put_hcd; + } + + ehci_mv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &r); + if (IS_ERR(ehci_mv->base)) { + retval = PTR_ERR(ehci_mv->base); + goto err_put_hcd; + } + + retval = mv_ehci_enable(ehci_mv); + if (retval) { + dev_err(&pdev->dev, "init phy error %d\n", retval); + goto err_put_hcd; + } + + ehci_mv->cap_regs = + (void __iomem *) ((unsigned long) ehci_mv->base + U2x_CAPREGS_OFFSET); + 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; + + retval = platform_get_irq(pdev, 0); + if (retval < 0) + goto err_disable_clk; + hcd->irq = retval; + + ehci = hcd_to_ehci(hcd); + ehci->caps = (struct ehci_caps __iomem *) ehci_mv->cap_regs; + + 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 (ehci_mv->set_vbus) + ehci_mv->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; + } + device_wakeup_enable(hcd->self.controller); + } + + if (of_usb_get_phy_mode(pdev->dev.of_node) == USBPHY_INTERFACE_MODE_HSIC) { + status = ehci_readl(ehci, &ehci->regs->port_status[0]); + /* These "reserved" bits actually enable HSIC mode. */ + status |= BIT(25); + status &= ~GENMASK(31, 30); + ehci_writel(ehci, status, &ehci->regs->port_status[0]); + } + + 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 (ehci_mv->set_vbus) + ehci_mv->set_vbus(0); +err_disable_clk: + mv_ehci_disable(ehci_mv); +err_put_hcd: + usb_put_hcd(hcd); + + return retval; +} + +static void mv_ehci_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ehci_hcd_mv *ehci_mv = hcd_to_ehci_hcd_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->set_vbus) + ehci_mv->set_vbus(0); + + mv_ehci_disable(ehci_mv); + } + + usb_put_hcd(hcd); +} + +static const struct platform_device_id ehci_id_table[] = { + {"pxa-u2oehci", 0}, + {"pxa-sph", 0}, + {}, +}; + +static void mv_ehci_shutdown(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + if (!hcd->rh_registered) + return; + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + +static const struct of_device_id ehci_mv_dt_ids[] = { + { .compatible = "marvell,pxau2o-ehci", }, + {}, +}; + +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, + .of_match_table = ehci_mv_dt_ids, + }, + .id_table = ehci_id_table, +}; + +static int __init ehci_platform_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ehci_init_driver(&ehci_platform_hc_driver, &platform_overrides); + return platform_driver_register(&ehci_mv_driver); +} +module_init(ehci_platform_init); + +static void __exit ehci_platform_cleanup(void) +{ + platform_driver_unregister(&ehci_mv_driver); +} +module_exit(ehci_platform_cleanup); + +MODULE_DESCRIPTION("Marvell EHCI driver"); +MODULE_AUTHOR("Chao Xie "); +MODULE_AUTHOR("Neil Zhang "); +MODULE_ALIAS("mv-ehci"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(of, ehci_mv_dt_ids); diff --git a/drivers/usb/host/ehci-npcm7xx.c b/drivers/usb/host/ehci-npcm7xx.c new file mode 100644 index 000000000..f1c7034c1 --- /dev/null +++ b/drivers/usb/host/ehci-npcm7xx.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NPCM7xx driver for EHCI HCD + * + * Copyright (C) 2018 Nuvoton Technologies, + * Avi Fishman + * Tomer Maimon + * + * Based on various ehci-spear.c driver + */ + + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define DRIVER_DESC "EHCI npcm7xx driver" + +static struct hc_driver __read_mostly ehci_npcm7xx_hc_driver; + +static int __maybe_unused ehci_npcm7xx_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 __maybe_unused ehci_npcm7xx_drv_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + + ehci_resume(hcd, false); + return 0; +} + +static SIMPLE_DEV_PM_OPS(ehci_npcm7xx_pm_ops, ehci_npcm7xx_drv_suspend, + ehci_npcm7xx_drv_resume); + +static int npcm7xx_ehci_hcd_drv_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct resource *res; + const struct hc_driver *driver = &ehci_npcm7xx_hc_driver; + int irq; + int retval; + + dev_dbg(&pdev->dev, "initializing npcm7xx ehci USB Controller\n"); + + 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. + */ + retval = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (retval) + goto fail; + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + retval = -ENOMEM; + goto fail; + } + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(hcd->regs)) { + retval = PTR_ERR(hcd->regs); + goto err_put_hcd; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + /* registers start at offset 0x0 */ + hcd_to_ehci(hcd)->caps = hcd->regs; + + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval) + goto err_put_hcd; + + device_wakeup_enable(hcd->self.controller); + return retval; + +err_put_hcd: + usb_put_hcd(hcd); +fail: + dev_err(&pdev->dev, "init fail, %d\n", retval); + + return retval; +} + +static void npcm7xx_ehci_hcd_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); + + usb_put_hcd(hcd); +} + +static const struct of_device_id npcm7xx_ehci_id_table[] = { + { .compatible = "nuvoton,npcm750-ehci" }, + { }, +}; +MODULE_DEVICE_TABLE(of, npcm7xx_ehci_id_table); + +static struct platform_driver npcm7xx_ehci_hcd_driver = { + .probe = npcm7xx_ehci_hcd_drv_probe, + .remove = npcm7xx_ehci_hcd_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "npcm7xx-ehci", + .bus = &platform_bus_type, + .pm = pm_ptr(&ehci_npcm7xx_pm_ops), + .of_match_table = npcm7xx_ehci_id_table, + } +}; + +static int __init ehci_npcm7xx_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ehci_init_driver(&ehci_npcm7xx_hc_driver, NULL); + return platform_driver_register(&npcm7xx_ehci_hcd_driver); +} +module_init(ehci_npcm7xx_init); + +static void __exit ehci_npcm7xx_cleanup(void) +{ + platform_driver_unregister(&npcm7xx_ehci_hcd_driver); +} +module_exit(ehci_npcm7xx_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_ALIAS("platform:npcm7xx-ehci"); +MODULE_AUTHOR("Avi Fishman"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c new file mode 100644 index 000000000..db4a1acb2 --- /dev/null +++ b/drivers/usb/host/ehci-omap.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#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); +} + +/* 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 __initconst = { + .extra_priv_size = sizeof(struct omap_hcd), +}; + +/** + * ehci_hcd_omap_probe - initialize TI-based HCDs + * @pdev: Pointer to this platform device's information + * + * 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_get_platdata(dev); + struct resource *res; + struct usb_hcd *hcd; + void __iomem *regs; + int ret; + 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_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) + return irq; + + regs = devm_platform_get_and_ioremap_resource(pdev, 0, &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_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 */ + phy = devm_usb_get_phy_by_phandle(dev, "phys", i); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + if (ret == -ENODEV) { /* no PHY */ + phy = NULL; + continue; + } + + if (ret != -EPROBE_DEFER) + dev_err(dev, "Can't get PHY 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; + } + 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 < 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); + pm_runtime_disable(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 void 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); +} + +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 = usb_hcd_platform_shutdown, + /*.suspend = ehci_hcd_omap_suspend, */ + /*.resume = ehci_hcd_omap_resume, */ + .driver = { + .name = hcd_name, + .of_match_table = omap_ehci_dt_ids, + } +}; + +/*-------------------------------------------------------------------------*/ + +static int __init ehci_omap_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + 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 000000000..eaaa49712 --- /dev/null +++ b/drivers/usb/host/ehci-orion.c @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drivers/usb/host/ehci-orion.c + * + * Tzachi Perelstein + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define rdl(off) readl_relaxed(hcd->regs + (off)) +#define wrl(off, val) writel_relaxed((val), hcd->regs + (off)) + +#define USB_CMD 0x140 +#define USB_CMD_RUN BIT(0) +#define USB_CMD_RESET BIT(1) +#define USB_MODE 0x1a8 +#define USB_MODE_MASK GENMASK(1, 0) +#define USB_MODE_DEVICE 0x2 +#define USB_MODE_HOST 0x3 +#define USB_MODE_SDIS BIT(4) +#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 USB_SBUSCFG 0x90 + +/* BAWR = BARD = 3 : Align read/write bursts packets larger than 128 bytes */ +#define USB_SBUSCFG_BAWR_ALIGN_128B (0x3 << 6) +#define USB_SBUSCFG_BARD_ALIGN_128B (0x3 << 3) +/* AHBBRST = 3 : Align AHB Burst to INCR16 (64 bytes) */ +#define USB_SBUSCFG_AHBBRST_INCR16 (0x3 << 0) + +#define USB_SBUSCFG_DEF_VAL (USB_SBUSCFG_BAWR_ALIGN_128B \ + | USB_SBUSCFG_BARD_ALIGN_128B \ + | USB_SBUSCFG_AHBBRST_INCR16) + +#define DRIVER_DESC "EHCI orion driver" + +#define hcd_to_orion_priv(h) ((struct orion_ehci_hcd *)hcd_to_ehci(h)->priv) + +struct orion_ehci_hcd { + struct clk *clk; +}; + +static struct hc_driver __read_mostly ehci_orion_hc_driver; + +/* + * Legacy DMA mask is 32 bit. + * AC5 has the DDR starting at 8GB, hence it requires + * a larger (34-bit) DMA mask, in order for DMA allocations + * to succeed: + */ +static const u64 dma_mask_orion = DMA_BIT_MASK(32); +static const u64 dma_mask_ac5 = DMA_BIT_MASK(34); + +/* + * 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) | USB_CMD_RESET); + while (rdl(USB_CMD) & USB_CMD_RESET); + + /* + * 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) & ~USB_CMD_RUN); + wrl(USB_CMD, rdl(USB_CMD) | USB_CMD_RESET); + while (rdl(USB_CMD) & USB_CMD_RESET); + + /* + * 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, USB_MODE_SDIS | USB_MODE_HOST); +} + +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_reset(struct usb_hcd *hcd) +{ + struct device *dev = hcd->self.controller; + int ret; + + ret = ehci_setup(hcd); + if (ret) + return ret; + + /* + * For SoC without hlock, need to program sbuscfg value to guarantee + * AHB master's burst would not overrun or underrun FIFO. + * + * sbuscfg reg has to be set after usb controller reset, otherwise + * the value would be override to 0. + */ + if (of_device_is_compatible(dev->of_node, "marvell,armada-3700-ehci")) + wrl(USB_SBUSCFG, USB_SBUSCFG_DEF_VAL); + + return ret; +} + +static int __maybe_unused ehci_orion_drv_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + + return ehci_suspend(hcd, device_may_wakeup(dev)); +} + +static int __maybe_unused ehci_orion_drv_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + + return ehci_resume(hcd, false); +} + +static SIMPLE_DEV_PM_OPS(ehci_orion_pm_ops, ehci_orion_drv_suspend, + ehci_orion_drv_resume); + +static const struct ehci_driver_overrides orion_overrides __initconst = { + .extra_priv_size = sizeof(struct orion_ehci_hcd), + .reset = ehci_orion_drv_reset, +}; + +static int ehci_orion_drv_probe(struct platform_device *pdev) +{ + struct orion_ehci_data *pd = dev_get_platdata(&pdev->dev); + const struct mbus_dram_target_info *dram; + struct resource *res; + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + void __iomem *regs; + int irq, err; + enum orion_ehci_phy_ver phy_version; + struct orion_ehci_hcd *priv; + u64 *dma_mask_ptr; + + if (usb_disabled()) + return -ENODEV; + + pr_debug("Initializing Orion-SoC USB Host Controller\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + err = irq; + goto 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 have dma capability bindings this can go away. + */ + dma_mask_ptr = (u64 *)of_device_get_match_data(&pdev->dev); + err = dma_coerce_mask_and_coherent(&pdev->dev, *dma_mask_ptr); + if (err) + goto err; + + regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(regs)) { + err = PTR_ERR(regs); + goto err; + } + + hcd = usb_create_hcd(&ehci_orion_hc_driver, + &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + err = -ENOMEM; + goto err; + } + + 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; + + priv = hcd_to_orion_priv(hcd); + /* + * Not all platforms can gate the clock, so it is not an error if + * the clock does not exists. + */ + priv->clk = devm_clk_get(&pdev->dev, NULL); + if (!IS_ERR(priv->clk)) { + err = clk_prepare_enable(priv->clk); + if (err) + goto err_put_hcd; + } + + /* + * (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: + dev_warn(&pdev->dev, "USB phy version isn't supported.\n"); + } + + err = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (err) + goto err_dis_clk; + + device_wakeup_enable(hcd->self.controller); + return 0; + +err_dis_clk: + if (!IS_ERR(priv->clk)) + clk_disable_unprepare(priv->clk); +err_put_hcd: + usb_put_hcd(hcd); +err: + dev_err(&pdev->dev, "init %s fail, %d\n", + dev_name(&pdev->dev), err); + + return err; +} + +static void ehci_orion_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct orion_ehci_hcd *priv = hcd_to_orion_priv(hcd); + + usb_remove_hcd(hcd); + + if (!IS_ERR(priv->clk)) + clk_disable_unprepare(priv->clk); + + usb_put_hcd(hcd); +} + +static const struct of_device_id ehci_orion_dt_ids[] = { + { .compatible = "marvell,orion-ehci", .data = &dma_mask_orion}, + { .compatible = "marvell,armada-3700-ehci", .data = &dma_mask_orion}, + { .compatible = "marvell,ac5-ehci", .data = &dma_mask_ac5}, + {}, +}; +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", + .of_match_table = ehci_orion_dt_ids, + .pm = &ehci_orion_pm_ops, + }, +}; + +static int __init ehci_orion_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ehci_init_driver(&ehci_orion_hc_driver, &orion_overrides); + 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 000000000..bd3a63555 --- /dev/null +++ b/drivers/usb/host/ehci-pci.c @@ -0,0 +1,446 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EHCI HCD (Host Controller Driver) PCI Bus Glue. + * + * Copyright (c) 2000-2004 by David Brownell + */ + +#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_ASPEED_EHCI 0x2603 + +/*-------------------------------------------------------------------------*/ +#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; +} + +/* + * This is the list of PCI IDs for the devices that have EHCI USB class and + * specific drivers for that. One of the example is a ChipIdea device installed + * on some Intel MID platforms. + */ +static const struct pci_device_id bypass_pci_id_table[] = { + /* ChipIdea on Intel MID platform */ + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0811), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0829), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xe006), }, + {} +}; + +static inline bool is_bypassed_id(struct pci_dev *pdev) +{ + return !!pci_match_id(bypass_pci_id_table, pdev); +} + +/* + * 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); + 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 (dma_set_coherent_mask(&pdev->dev, 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_quirk_pll_check()) + 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_quirk_pll_check()) + 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) && + usb_amd_hang_symptom_quirk()) { + 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)); + } + 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; + case PCI_VENDOR_ID_HUAWEI: + /* Synopsys HC bug */ + if (pdev->device == 0xa239) { + ehci_info(ehci, "applying Synopsys HC workaround\n"); + ehci->has_synopsys_hc_bug = 1; + } + break; + case PCI_VENDOR_ID_ASPEED: + if (pdev->device == PCI_DEVICE_ID_ASPEED_EHCI) { + ehci_info(ehci, "applying Aspeed HC workaround\n"); + ehci->is_aspeed = 1; + } + break; + case PCI_VENDOR_ID_ZHAOXIN: + if (pdev->device == 0x3104 && (pdev->revision & 0xf0) == 0x90) + ehci->zx_wakeup_clear_needed = 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: + case PCI_VENDOR_ID_INTEL: + case PCI_VENDOR_ID_AMD: + 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 if (pdev->vendor == PCI_VENDOR_ID_HUAWEI + && pdev->device == 0xa239) + ; /* HUAWEI Kunpeng920 USB EHCI 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 + 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 int ehci_pci_resume(struct usb_hcd *hcd, pm_message_t msg) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + bool hibernated = (msg.event == PM_EVENT_RESTORE); + + 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, +}; + +/*-------------------------------------------------------------------------*/ + +static int ehci_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + if (is_bypassed_id(pdev)) + return -ENODEV; + return usb_hcd_pci_probe(pdev, &ehci_pci_hc_driver); +} + +static void ehci_pci_remove(struct pci_dev *pdev) +{ + pci_clear_mwi(pdev); + usb_hcd_pci_remove(pdev); +} + +/* 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), + }, { + PCI_VDEVICE(STMICRO, PCI_DEVICE_ID_STMICRO_USB_HOST), + }, + { /* 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 = hcd_name, + .id_table = pci_ids, + + .probe = ehci_pci_probe, + .remove = ehci_pci_remove, + .shutdown = usb_hcd_pci_shutdown, + + .driver = { +#ifdef CONFIG_PM + .pm = &usb_hcd_pci_pm_ops, +#endif + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; + +static int __init ehci_pci_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + 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 000000000..f61f095ce --- /dev/null +++ b/drivers/usb/host/ehci-platform.c @@ -0,0 +1,570 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic platform ehci driver + * + * Copyright 2007 Steven Brown + * Copyright 2010-2012 Hauke Mehrtens + * Copyright 2014 Hans de Goede + * + * 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define DRIVER_DESC "EHCI generic platform driver" +#define EHCI_MAX_CLKS 4 +#define hcd_to_ehci_priv(h) ((struct ehci_platform_priv *)hcd_to_ehci(h)->priv) + +#define BCM_USB_FIFO_THRESHOLD 0x00800040 + +struct ehci_platform_priv { + struct clk *clks[EHCI_MAX_CLKS]; + struct reset_control *rsts; + bool reset_on_resume; + bool quirk_poll; + struct timer_list poll_timer; + struct delayed_work poll_work; +}; + +static int ehci_platform_reset(struct usb_hcd *hcd) +{ + struct platform_device *pdev = to_platform_device(hcd->self.controller); + struct usb_ehci_pdata *pdata = dev_get_platdata(&pdev->dev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int retval; + + ehci->has_synopsys_hc_bug = pdata->has_synopsys_hc_bug; + + if (pdata->pre_setup) { + retval = pdata->pre_setup(hcd); + if (retval < 0) + return retval; + } + + 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; + + if (of_device_is_compatible(pdev->dev.of_node, "brcm,xgs-iproc-ehci")) + ehci_writel(ehci, BCM_USB_FIFO_THRESHOLD, + &ehci->regs->brcm_insnreg[1]); + + return 0; +} + +static int ehci_platform_power_on(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); + int clk, ret; + + for (clk = 0; clk < EHCI_MAX_CLKS && priv->clks[clk]; clk++) { + ret = clk_prepare_enable(priv->clks[clk]); + if (ret) + goto err_disable_clks; + } + + return 0; + +err_disable_clks: + while (--clk >= 0) + clk_disable_unprepare(priv->clks[clk]); + + return ret; +} + +static void ehci_platform_power_off(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); + int clk; + + for (clk = EHCI_MAX_CLKS - 1; clk >= 0; clk--) + clk_disable_unprepare(priv->clks[clk]); +} + +static struct hc_driver __read_mostly ehci_platform_hc_driver; + +static const struct ehci_driver_overrides platform_overrides __initconst = { + .reset = ehci_platform_reset, + .extra_priv_size = sizeof(struct ehci_platform_priv), +}; + +static struct usb_ehci_pdata ehci_platform_defaults = { + .power_on = ehci_platform_power_on, + .power_suspend = ehci_platform_power_off, + .power_off = ehci_platform_power_off, +}; + +/** + * quirk_poll_check_port_status - Poll port_status if the device sticks + * @ehci: the ehci hcd pointer + * + * Since EHCI/OHCI controllers on R-Car Gen3 SoCs are possible to be getting + * stuck very rarely after a full/low usb device was disconnected. To + * detect such a situation, the controllers require a special way which poll + * the EHCI PORTSC register. + * + * Return: true if the controller's port_status indicated getting stuck + */ +static bool quirk_poll_check_port_status(struct ehci_hcd *ehci) +{ + u32 port_status = ehci_readl(ehci, &ehci->regs->port_status[0]); + + if (!(port_status & PORT_OWNER) && + (port_status & PORT_POWER) && + !(port_status & PORT_CONNECT) && + (port_status & PORT_LS_MASK)) + return true; + + return false; +} + +/** + * quirk_poll_rebind_companion - rebind comanion device to recover + * @ehci: the ehci hcd pointer + * + * Since EHCI/OHCI controllers on R-Car Gen3 SoCs are possible to be getting + * stuck very rarely after a full/low usb device was disconnected. To + * recover from such a situation, the controllers require changing the OHCI + * functional state. + */ +static void quirk_poll_rebind_companion(struct ehci_hcd *ehci) +{ + struct device *companion_dev; + struct usb_hcd *hcd = ehci_to_hcd(ehci); + + companion_dev = usb_of_get_companion_dev(hcd->self.controller); + if (!companion_dev) + return; + + device_release_driver(companion_dev); + if (device_attach(companion_dev) < 0) + ehci_err(ehci, "%s: failed\n", __func__); + + put_device(companion_dev); +} + +static void quirk_poll_work(struct work_struct *work) +{ + struct ehci_platform_priv *priv = + container_of(to_delayed_work(work), struct ehci_platform_priv, + poll_work); + struct ehci_hcd *ehci = container_of((void *)priv, struct ehci_hcd, + priv); + + /* check the status twice to reduce misdetection rate */ + if (!quirk_poll_check_port_status(ehci)) + return; + udelay(10); + if (!quirk_poll_check_port_status(ehci)) + return; + + ehci_dbg(ehci, "%s: detected getting stuck. rebind now!\n", __func__); + quirk_poll_rebind_companion(ehci); +} + +static void quirk_poll_timer(struct timer_list *t) +{ + struct ehci_platform_priv *priv = timer_container_of(priv, t, + poll_timer); + struct ehci_hcd *ehci = container_of((void *)priv, struct ehci_hcd, + priv); + + if (quirk_poll_check_port_status(ehci)) { + /* + * Now scheduling the work for testing the port more. Note that + * updating the status is possible to be delayed when + * reconnection. So, this uses delayed work with 5 ms delay + * to avoid misdetection. + */ + schedule_delayed_work(&priv->poll_work, msecs_to_jiffies(5)); + } + + mod_timer(&priv->poll_timer, jiffies + HZ); +} + +static void quirk_poll_init(struct ehci_platform_priv *priv) +{ + INIT_DELAYED_WORK(&priv->poll_work, quirk_poll_work); + timer_setup(&priv->poll_timer, quirk_poll_timer, 0); + mod_timer(&priv->poll_timer, jiffies + HZ); +} + +static void quirk_poll_end(struct ehci_platform_priv *priv) +{ + timer_delete_sync(&priv->poll_timer); + cancel_delayed_work(&priv->poll_work); +} + +static const struct soc_device_attribute quirk_poll_match[] = { + { .family = "R-Car Gen3" }, + { /* sentinel*/ } +}; + +static int ehci_platform_probe(struct platform_device *dev) +{ + struct usb_hcd *hcd; + struct resource *res_mem; + struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); + const struct of_device_id *match; + struct ehci_platform_priv *priv; + struct ehci_hcd *ehci; + int err, irq, clk = 0; + bool dma_mask_64; + + if (usb_disabled()) + return -ENODEV; + + /* + * Use reasonable defaults so platforms don't have to provide these + * with DT probing on ARM. + */ + if (!pdata) + pdata = &ehci_platform_defaults; + + dma_mask_64 = pdata->dma_mask_64; + match = of_match_device(dev->dev.driver->of_match_table, &dev->dev); + if (match && match->data) + dma_mask_64 = true; + + err = dma_coerce_mask_and_coherent(&dev->dev, + dma_mask_64 ? DMA_BIT_MASK(64) : DMA_BIT_MASK(32)); + if (err) { + dev_err(&dev->dev, "Error: DMA mask configuration failed\n"); + return err; + } + + irq = platform_get_irq(dev, 0); + if (irq < 0) + return irq; + + hcd = usb_create_hcd(&ehci_platform_hc_driver, &dev->dev, + dev_name(&dev->dev)); + if (!hcd) + return -ENOMEM; + + platform_set_drvdata(dev, hcd); + dev->dev.platform_data = pdata; + priv = hcd_to_ehci_priv(hcd); + ehci = hcd_to_ehci(hcd); + + if (pdata == &ehci_platform_defaults && dev->dev.of_node) { + if (of_property_read_bool(dev->dev.of_node, "big-endian-regs")) + ehci->big_endian_mmio = 1; + + if (of_property_read_bool(dev->dev.of_node, "big-endian-desc")) + ehci->big_endian_desc = 1; + + if (of_property_read_bool(dev->dev.of_node, "big-endian")) + ehci->big_endian_mmio = ehci->big_endian_desc = 1; + + if (of_property_read_bool(dev->dev.of_node, "spurious-oc")) + ehci->spurious_oc = 1; + + if (of_property_read_bool(dev->dev.of_node, + "needs-reset-on-resume")) + priv->reset_on_resume = true; + + if (of_property_read_bool(dev->dev.of_node, + "has-transaction-translator")) + hcd->has_tt = 1; + + if (of_device_is_compatible(dev->dev.of_node, + "aspeed,ast2500-ehci") || + of_device_is_compatible(dev->dev.of_node, + "aspeed,ast2600-ehci") || + of_device_is_compatible(dev->dev.of_node, + "aspeed,ast2700-ehci")) + ehci->is_aspeed = 1; + + if (soc_device_match(quirk_poll_match)) + priv->quirk_poll = true; + + for (clk = 0; clk < EHCI_MAX_CLKS; clk++) { + priv->clks[clk] = of_clk_get(dev->dev.of_node, clk); + if (IS_ERR(priv->clks[clk])) { + err = PTR_ERR(priv->clks[clk]); + if (err == -EPROBE_DEFER) + goto err_put_clks; + priv->clks[clk] = NULL; + break; + } + } + } + + priv->rsts = devm_reset_control_array_get_optional_shared(&dev->dev); + if (IS_ERR(priv->rsts)) { + err = PTR_ERR(priv->rsts); + goto err_put_clks; + } + + err = reset_control_deassert(priv->rsts); + if (err) + goto err_put_clks; + + if (pdata->big_endian_desc) + ehci->big_endian_desc = 1; + if (pdata->big_endian_mmio) + ehci->big_endian_mmio = 1; + if (pdata->has_tt) + hcd->has_tt = 1; + if (pdata->reset_on_resume) + priv->reset_on_resume = true; + if (pdata->spurious_oc) + ehci->spurious_oc = 1; + +#ifndef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO + if (ehci->big_endian_mmio) { + dev_err(&dev->dev, + "Error: CONFIG_USB_EHCI_BIG_ENDIAN_MMIO not set\n"); + err = -EINVAL; + goto err_reset; + } +#endif +#ifndef CONFIG_USB_EHCI_BIG_ENDIAN_DESC + if (ehci->big_endian_desc) { + dev_err(&dev->dev, + "Error: CONFIG_USB_EHCI_BIG_ENDIAN_DESC not set\n"); + err = -EINVAL; + goto err_reset; + } +#endif + + if (pdata->power_on) { + err = pdata->power_on(dev); + if (err < 0) + goto err_reset; + } + + hcd->regs = devm_platform_get_and_ioremap_resource(dev, 0, &res_mem); + if (IS_ERR(hcd->regs)) { + err = PTR_ERR(hcd->regs); + goto err_power; + } + hcd->rsrc_start = res_mem->start; + hcd->rsrc_len = resource_size(res_mem); + + hcd->tpl_support = of_usb_host_tpl_support(dev->dev.of_node); + + err = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (err) + goto err_power; + + device_wakeup_enable(hcd->self.controller); + device_enable_async_suspend(hcd->self.controller); + platform_set_drvdata(dev, hcd); + + if (priv->quirk_poll) + quirk_poll_init(priv); + + return err; + +err_power: + if (pdata->power_off) + pdata->power_off(dev); +err_reset: + reset_control_assert(priv->rsts); +err_put_clks: + while (--clk >= 0) + clk_put(priv->clks[clk]); + + if (pdata == &ehci_platform_defaults) + dev->dev.platform_data = NULL; + + usb_put_hcd(hcd); + + return err; +} + +static void ehci_platform_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); + struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); + int clk; + + if (priv->quirk_poll) + quirk_poll_end(priv); + + usb_remove_hcd(hcd); + + if (pdata->power_off) + pdata->power_off(dev); + + reset_control_assert(priv->rsts); + + for (clk = 0; clk < EHCI_MAX_CLKS && priv->clks[clk]; clk++) + clk_put(priv->clks[clk]); + + usb_put_hcd(hcd); + + if (pdata == &ehci_platform_defaults) + dev->dev.platform_data = NULL; +} + +static int __maybe_unused ehci_platform_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct usb_ehci_pdata *pdata = dev_get_platdata(dev); + struct platform_device *pdev = to_platform_device(dev); + struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); + bool do_wakeup = device_may_wakeup(dev); + int ret; + + if (priv->quirk_poll) + quirk_poll_end(priv); + + ret = ehci_suspend(hcd, do_wakeup); + if (ret) + return ret; + + if (pdata->power_suspend) + pdata->power_suspend(pdev); + + ret = reset_control_assert(priv->rsts); + if (ret) { + if (pdata->power_on) + pdata->power_on(pdev); + + ehci_resume(hcd, false); + + if (priv->quirk_poll) + quirk_poll_init(priv); + } + + return ret; +} + +static int __maybe_unused ehci_platform_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct usb_ehci_pdata *pdata = dev_get_platdata(dev); + struct platform_device *pdev = to_platform_device(dev); + struct ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); + struct device *companion_dev; + int err; + + err = reset_control_deassert(priv->rsts); + if (err) + return err; + + if (pdata->power_on) { + err = pdata->power_on(pdev); + if (err < 0) { + reset_control_assert(priv->rsts); + return err; + } + } + + companion_dev = usb_of_get_companion_dev(hcd->self.controller); + if (companion_dev) { + device_pm_wait_for_dev(hcd->self.controller, companion_dev); + put_device(companion_dev); + } + + ehci_resume(hcd, priv->reset_on_resume); + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + if (priv->quirk_poll) + quirk_poll_init(priv); + + return 0; +} + +static const struct of_device_id vt8500_ehci_ids[] = { + { .compatible = "via,vt8500-ehci", }, + { .compatible = "wm,prizm-ehci", }, + { .compatible = "generic-ehci", }, + { .compatible = "cavium,octeon-6335-ehci", }, + { .compatible = "aspeed,ast2700-ehci", .data = (void *)1 }, + {} +}; +MODULE_DEVICE_TABLE(of, vt8500_ehci_ids); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id ehci_acpi_match[] = { + { "PNP0D20", 0 }, /* EHCI controller without debug */ + { } +}; +MODULE_DEVICE_TABLE(acpi, ehci_acpi_match); +#endif + +static const struct platform_device_id ehci_platform_table[] = { + { "ehci-platform", 0 }, + { } +}; +MODULE_DEVICE_TABLE(platform, ehci_platform_table); + +static SIMPLE_DEV_PM_OPS(ehci_platform_pm_ops, ehci_platform_suspend, + 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 = { + .name = "ehci-platform", + .pm = pm_ptr(&ehci_platform_pm_ops), + .of_match_table = vt8500_ehci_ids, + .acpi_match_table = ACPI_PTR(ehci_acpi_match), + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + } +}; + +static int __init ehci_platform_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + 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-ppc-of.c b/drivers/usb/host/ehci-ppc-of.c new file mode 100644 index 000000000..8063b9d3a --- /dev/null +++ b/drivers/usb/host/ehci-ppc-of.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * 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 +#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_DMA | HCD_USB2 | HCD_BH, + + /* + * 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) { + dev_err(&op->dev, "%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; + } + of_node_put(np); + } + + if (of_property_read_bool(dn, "big-endian")) { + ehci->big_endian_mmio = 1; + ehci->big_endian_desc = 1; + } + if (of_property_read_bool(dn, "big-endian-regs")) + ehci->big_endian_mmio = 1; + if (of_property_read_bool(dn, "big-endian-desc")) + 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; + + device_wakeup_enable(hcd->self.controller); + return 0; + +err_ioremap: + irq_dispose_mapping(irq); +err_irq: + usb_put_hcd(hcd); + + return rv; +} + + +static void ehci_hcd_ppc_of_remove(struct platform_device *op) +{ + struct usb_hcd *hcd = platform_get_drvdata(op); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + struct device_node *np; + struct resource res; + + 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); +} + + +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 = usb_hcd_platform_shutdown, + .driver = { + .name = "ppc-of-ehci", + .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 000000000..98568b046 --- /dev/null +++ b/drivers/usb/host/ehci-ps3.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PS3 EHCI Host Controller driver + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + */ + +#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_DMA | HCD_USB2 | HCD_BH, + .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; + + 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; + } + + dummy_mask = DMA_BIT_MASK(32); + dev->core.dma_mask = &dummy_mask; + dma_set_coherent_mask(&dev->core, dummy_mask); + + 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; + } + + device_wakeup_enable(hcd->self.controller); + 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 void 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); +} + +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 000000000..ba37a9fca --- /dev/null +++ b/drivers/usb/host/ehci-q.c @@ -0,0 +1,1526 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2001-2004 by David Brownell + */ + +/* 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 unsigned int +qtd_fill(struct ehci_hcd *ehci, struct ehci_qtd *qtd, dma_addr_t buf, + size_t len, int token, int maxpacket) +{ + unsigned int count; + u64 addr = buf; + int i; + + /* 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->ps.udev, epnum, is_out))) { + hw->hw_token &= ~cpu_to_hc32(ehci, QTD_TOGGLE); + usb_settoggle(qh->ps.udev, 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; + if (qh->should_be_inactive) + ehci_warn(ehci, "qh %p should be inactive!\n", qh); + } else { + qh_update(ehci, qh, qtd); + } + qh->should_be_inactive = 0; +} + +/*-------------------------------------------------------------------------*/ + +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 CONFIG_DYNAMIC_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 /* CONFIG_DYNAMIC_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) != PID_CODE_SETUP)) + 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; + /* + * When MMF is active and PID Code is IN, queue is halted. + * EHCI Specification, Table 4-13. + */ + } else if ((token & QTD_STS_MMF) && + (QTD_PID(token) == PID_CODE_IN)) { + status = -EPROTO; + /* 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) == PID_CODE_IN) /* 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; + } + } + + return status; +} + +static void +ehci_urb_done(struct ehci_hcd *ehci, struct urb *urb, int status) +{ + if (usb_pipetype(urb->pipe) == PIPE_INTERRUPT) { + /* ... update hc-wide periodic stats */ + ehci_to_hcd(ehci)->self.bandwidth_int_reqs--; + } + + if (unlikely(urb->unlinked)) { + INCR(ehci->stats.unlink); + } else { + /* report non-error and short read status as zero */ + if (status == -EINPROGRESS || status == -EREMOTEIO) + status = 0; + INCR(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 + + usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb); + usb_hcd_giveback_urb(ehci_to_hcd(ehci), urb, status); +} + +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; + qh->unlink_reason |= QH_UNLINK_HALTED; + + /* 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; + qh->unlink_reason |= QH_UNLINK_SHORT_READ; + } + + /* 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; + qh->unlink_reason |= QH_UNLINK_SHUTDOWN; + } + + /* 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); + qh->should_be_inactive = 1; + + /* 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->unlink_reason |= QH_UNLINK_DUMMY_OVERLAY; + + /* Let the caller know if the QH needs to be unlinked. */ + return qh->unlink_reason; +} + +/*-------------------------------------------------------------------------*/ + +/* + * 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 | (PID_CODE_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 |= (PID_CODE_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 |= (PID_CODE_IN << 8); + /* else it's already initted to "out" pid (0 << 8) */ + + maxpacket = usb_endpoint_maxp(&urb->ep->desc); + + /* + * 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 (;;) { + unsigned 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 ^= (PID_CODE_IN << 8); /* "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); + struct usb_host_endpoint *ep; + u32 info1 = 0, info2 = 0; + int is_input, type; + int maxp = 0; + int mult; + 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); + ep = usb_pipe_endpoint (urb->dev, urb->pipe); + maxp = usb_endpoint_maxp (&ep->desc); + mult = usb_endpoint_maxp_mult (&ep->desc); + + /* 1024 byte maxpacket is a hardware ceiling. High bandwidth + * acts like up to 3KB, but is built from smaller packets. + */ + if (maxp > 1024) { + ehci_dbg(ehci, "bogus qh maxpacket %d\n", 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) { + unsigned tmp; + + qh->ps.usecs = NS_TO_US(usb_calc_bus_time(USB_SPEED_HIGH, + is_input, 0, mult * maxp)); + qh->ps.phase = NO_FRAME; + + if (urb->dev->speed == USB_SPEED_HIGH) { + qh->ps.c_usecs = 0; + qh->gap_uf = 0; + + if (urb->interval > 1 && urb->interval < 8) { + /* NOTE interval 2 or 4 uframes could work. + * But interval 1 scheduling is simpler, and + * includes high bandwidth. + */ + urb->interval = 1; + } else if (urb->interval > ehci->periodic_size << 3) { + urb->interval = ehci->periodic_size << 3; + } + qh->ps.period = urb->interval >> 3; + + /* period for bandwidth allocation */ + tmp = min_t(unsigned, EHCI_BANDWIDTH_SIZE, + 1 << (urb->ep->desc.bInterval - 1)); + + /* Allow urb->interval to override */ + qh->ps.bw_uperiod = min_t(unsigned, tmp, urb->interval); + qh->ps.bw_period = qh->ps.bw_uperiod >> 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->ps.c_usecs = qh->ps.usecs + HS_USECS(0); + qh->ps.usecs = HS_USECS(1); + } else { // SPLIT+DATA, gap, CSPLIT + qh->ps.usecs += HS_USECS(1); + qh->ps.c_usecs = HS_USECS(0); + } + + think_time = tt ? tt->think_time : 0; + qh->ps.tt_usecs = NS_TO_US(think_time + + usb_calc_bus_time (urb->dev->speed, + is_input, 0, maxp)); + if (urb->interval > ehci->periodic_size) + urb->interval = ehci->periodic_size; + qh->ps.period = urb->interval; + + /* period for bandwidth allocation */ + tmp = min_t(unsigned, EHCI_BANDWIDTH_FRAMES, + urb->ep->desc.bInterval); + tmp = rounddown_pow_of_two(tmp); + + /* Allow urb->interval to override */ + qh->ps.bw_period = min_t(unsigned, tmp, urb->interval); + qh->ps.bw_uperiod = qh->ps.bw_period << 3; + } + } + + /* support for tt scheduling, and access to toggles */ + qh->ps.udev = urb->dev; + qh->ps.ep = urb->ep; + + /* using TT? */ + switch (urb->dev->speed) { + case USB_SPEED_LOW: + info1 |= QH_LOW_SPEED; + fallthrough; + + 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 |= maxp << 16; + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else { /* PIPE_INTERRUPT */ + info1 |= maxp << 16; + info2 |= mult << 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->unlink_reason = 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; +} + +/*-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_HCD_TEST_MODE +/* + * This function creates the qtds and submits them for the + * SINGLE_STEP_SET_FEATURE Test. + * This is done in two parts: first SETUP req for GetDesc is sent then + * 15 seconds later, the IN stage for GetDesc starts to req data from dev + * + * is_setup : i/p argument decides which of the two stage needs to be + * performed; TRUE - SETUP and FALSE - IN+STATUS + * Returns 0 if success + */ +static int ehci_submit_single_step_set_feature( + struct usb_hcd *hcd, + struct urb *urb, + int is_setup +) { + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct list_head qtd_list; + struct list_head *head; + + struct ehci_qtd *qtd, *qtd_prev; + dma_addr_t buf; + int len, maxpacket; + u32 token; + + INIT_LIST_HEAD(&qtd_list); + head = &qtd_list; + + /* URBs map to sequences of QTDs: one logical transaction */ + qtd = ehci_qtd_alloc(ehci, GFP_KERNEL); + if (unlikely(!qtd)) + return -1; + list_add_tail(&qtd->qtd_list, head); + qtd->urb = urb; + + token = QTD_STS_ACTIVE; + token |= (EHCI_TUNE_CERR << 10); + + len = urb->transfer_buffer_length; + /* + * Check if the request is to perform just the SETUP stage (getDesc) + * as in SINGLE_STEP_SET_FEATURE test, DATA stage (IN) happens + * 15 secs after the setup + */ + if (is_setup) { + /* SETUP pid, and interrupt after SETUP completion */ + qtd_fill(ehci, qtd, urb->setup_dma, + sizeof(struct usb_ctrlrequest), + QTD_IOC | token | (PID_CODE_SETUP << 8), 8); + + submit_async(ehci, urb, &qtd_list, GFP_ATOMIC); + return 0; /*Return now; we shall come back after 15 seconds*/ + } + + /* + * IN: data transfer stage: buffer setup : start the IN txn phase for + * the get_Desc SETUP which was sent 15seconds back + */ + token ^= QTD_TOGGLE; /*We need to start IN with DATA-1 Pid-sequence*/ + buf = urb->transfer_dma; + + token |= (PID_CODE_IN << 8); /*This is IN stage*/ + + maxpacket = usb_endpoint_maxp(&urb->ep->desc); + + qtd_fill(ehci, qtd, buf, len, token, maxpacket); + + /* + * Our IN phase shall always be a short read; so keep the queue running + * and let it advance to the next qtd which zero length OUT status + */ + qtd->hw_alt_next = EHCI_LIST_END(ehci); + + /* STATUS stage for GetDesc control request */ + token ^= (PID_CODE_IN << 8); /* "in" <--> "out" */ + token |= QTD_TOGGLE; /* force DATA1 */ + + qtd_prev = qtd; + qtd = ehci_qtd_alloc(ehci, GFP_ATOMIC); + 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); + + /* Interrupt after STATUS completion */ + qtd_fill(ehci, qtd, 0, 0, token | QTD_IOC, 0); + + submit_async(ehci, urb, &qtd_list, GFP_KERNEL); + + return 0; + +cleanup: + qtd_list_free(ehci, urb, head); + return -1; +} +#endif /* CONFIG_USB_HCD_TEST_MODE */ + +/*-------------------------------------------------------------------------*/ + +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) +{ + /* 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 if one isn't already running */ + } else if (ehci->rh_state == EHCI_RH_RUNNING && + !ehci->iaa_in_progress) { + + /* 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->iaa_in_progress = true; + ehci_enable_event(ehci, EHCI_HRTIMER_IAA_WATCHDOG, true); + } +} + +static void end_iaa_cycle(struct ehci_hcd *ehci) +{ + 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; + + end_unlink_async(ehci); +} + +/* See if 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 (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) { + /* + * Second IAA cycle has finished. Process only the first + * waiting QH (NVIDIA (?) bug). + */ + list_move_tail(&qh->unlink_node, &ehci->async_idle); + } + + /* + * AMD/ATI (?) bug: The HC can continue to use an active QH long + * after the IAA interrupt occurs. To prevent problems, QHs that + * may still be active will wait until 2 ms have passed with no + * change to the hw_current and hw_token fields (this delay occurs + * between the two IAA cycles). + * + * The EHCI spec (4.8.2) says that active QHs must not be removed + * from the async schedule and recommends waiting until the QH + * goes inactive. This is ridiculous because the QH will _never_ + * become inactive if the endpoint NAKs indefinitely. + */ + + /* Some reasons for unlinking guarantee the QH can't be active */ + else if (qh->unlink_reason & (QH_UNLINK_HALTED | + QH_UNLINK_SHORT_READ | QH_UNLINK_DUMMY_OVERLAY)) + goto DelayDone; + + /* The QH can't be active if the queue was and still is empty... */ + else if ((qh->unlink_reason & QH_UNLINK_QUEUE_EMPTY) && + list_empty(&qh->qtd_list)) + goto DelayDone; + + /* ... or if the QH has halted */ + else if (qh->hw->hw_token & cpu_to_hc32(ehci, QTD_STS_HALT)) + goto DelayDone; + + /* Otherwise we have to wait until the QH stops changing */ + else { + __hc32 qh_current, qh_token; + + qh_current = qh->hw->hw_current; + qh_token = qh->hw->hw_token; + if (qh_current != ehci->old_current || + qh_token != ehci->old_token) { + ehci->old_current = qh_current; + ehci->old_token = qh_token; + ehci_enable_event(ehci, + EHCI_HRTIMER_ACTIVE_UNLINK, true); + return; + } + DelayDone: + qh->qh_state = QH_STATE_UNLINK; + early_exit = true; + } + ehci->old_current = ~0; /* Prepare for next QH */ + + /* 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) { + qh_to_unlink->unlink_reason |= QH_UNLINK_QUEUE_EMPTY; + 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; + } +} + +#ifdef CONFIG_PM + +/* The root hub is suspended; unlink all the async QHs */ +static void 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); + } +} + +#endif + +/* 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-sched.c b/drivers/usb/host/ehci-sched.c new file mode 100644 index 000000000..57d07d1c2 --- /dev/null +++ b/drivers/usb/host/ehci-sched.c @@ -0,0 +1,2497 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2001-2004 by David Brownell + * Copyright (c) 2003 Michal Sojka, for high-speed iso transfers + */ + +/* 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 = cpu_to_hc32(ehci, ehci->dummy->qh_dma); +} + +/*-------------------------------------------------------------------------*/ + +/* Bandwidth and TT management */ + +/* Find the TT data structure for this device; create it if necessary */ +static struct ehci_tt *find_tt(struct usb_device *udev) +{ + struct usb_tt *utt = udev->tt; + struct ehci_tt *tt, **tt_index, **ptt; + unsigned port; + bool allocated_index = false; + + if (!utt) + return NULL; /* Not below a TT */ + + /* + * Find/create our data structure. + * For hubs with a single TT, we get it directly. + * For hubs with multiple TTs, there's an extra level of pointers. + */ + tt_index = NULL; + if (utt->multi) { + tt_index = utt->hcpriv; + if (!tt_index) { /* Create the index array */ + tt_index = kzalloc_objs(*tt_index, utt->hub->maxchild, + GFP_ATOMIC); + if (!tt_index) + return ERR_PTR(-ENOMEM); + utt->hcpriv = tt_index; + allocated_index = true; + } + port = udev->ttport - 1; + ptt = &tt_index[port]; + } else { + port = 0; + ptt = (struct ehci_tt **) &utt->hcpriv; + } + + tt = *ptt; + if (!tt) { /* Create the ehci_tt */ + struct ehci_hcd *ehci = + hcd_to_ehci(bus_to_hcd(udev->bus)); + + tt = kzalloc_obj(*tt, GFP_ATOMIC); + if (!tt) { + if (allocated_index) { + utt->hcpriv = NULL; + kfree(tt_index); + } + return ERR_PTR(-ENOMEM); + } + list_add_tail(&tt->tt_list, &ehci->tt_list); + INIT_LIST_HEAD(&tt->ps_list); + tt->usb_tt = utt; + tt->tt_port = port; + *ptt = tt; + } + + return tt; +} + +/* Release the TT above udev, if it's not in use */ +static void drop_tt(struct usb_device *udev) +{ + struct usb_tt *utt = udev->tt; + struct ehci_tt *tt, **tt_index, **ptt; + int cnt, i; + + if (!utt || !utt->hcpriv) + return; /* Not below a TT, or never allocated */ + + cnt = 0; + if (utt->multi) { + tt_index = utt->hcpriv; + ptt = &tt_index[udev->ttport - 1]; + + /* How many entries are left in tt_index? */ + for (i = 0; i < utt->hub->maxchild; ++i) + cnt += !!tt_index[i]; + } else { + tt_index = NULL; + ptt = (struct ehci_tt **) &utt->hcpriv; + } + + tt = *ptt; + if (!tt || !list_empty(&tt->ps_list)) + return; /* never allocated, or still in use */ + + list_del(&tt->tt_list); + *ptt = NULL; + kfree(tt); + if (cnt == 1) { + utt->hcpriv = NULL; + kfree(tt_index); + } +} + +static void bandwidth_dbg(struct ehci_hcd *ehci, int sign, char *type, + struct ehci_per_sched *ps) +{ + dev_dbg(&ps->udev->dev, + "ep %02x: %s %s @ %u+%u (%u.%u+%u) [%u/%u us] mask %04x\n", + ps->ep->desc.bEndpointAddress, + (sign >= 0 ? "reserve" : "release"), type, + (ps->bw_phase << 3) + ps->phase_uf, ps->bw_uperiod, + ps->phase, ps->phase_uf, ps->period, + ps->usecs, ps->c_usecs, ps->cs_mask); +} + +static void reserve_release_intr_bandwidth(struct ehci_hcd *ehci, + struct ehci_qh *qh, int sign) +{ + unsigned start_uf; + unsigned i, j, m; + int usecs = qh->ps.usecs; + int c_usecs = qh->ps.c_usecs; + int tt_usecs = qh->ps.tt_usecs; + struct ehci_tt *tt; + + if (qh->ps.phase == NO_FRAME) /* Bandwidth wasn't reserved */ + return; + start_uf = qh->ps.bw_phase << 3; + + bandwidth_dbg(ehci, sign, "intr", &qh->ps); + + if (sign < 0) { /* Release bandwidth */ + usecs = -usecs; + c_usecs = -c_usecs; + tt_usecs = -tt_usecs; + } + + /* Entire transaction (high speed) or start-split (full/low speed) */ + for (i = start_uf + qh->ps.phase_uf; i < EHCI_BANDWIDTH_SIZE; + i += qh->ps.bw_uperiod) + ehci->bandwidth[i] += usecs; + + /* Complete-split (full/low speed) */ + if (qh->ps.c_usecs) { + /* NOTE: adjustments needed for FSTN */ + for (i = start_uf; i < EHCI_BANDWIDTH_SIZE; + i += qh->ps.bw_uperiod) { + for ((j = 2, m = 1 << (j+8)); j < 8; (++j, m <<= 1)) { + if (qh->ps.cs_mask & m) + ehci->bandwidth[i+j] += c_usecs; + } + } + } + + /* FS/LS bus bandwidth */ + if (tt_usecs) { + /* + * find_tt() will not return any error here as we have + * already called find_tt() before calling this function + * and checked for any error return. The previous call + * would have created the data structure. + */ + tt = find_tt(qh->ps.udev); + if (sign > 0) + list_add_tail(&qh->ps.ps_list, &tt->ps_list); + else + list_del(&qh->ps.ps_list); + + for (i = start_uf >> 3; i < EHCI_BANDWIDTH_FRAMES; + i += qh->ps.bw_period) + tt->bandwidth[i] += tt_usecs; + } +} + +/*-------------------------------------------------------------------------*/ + +static void compute_tt_budget(u8 budget_table[EHCI_BANDWIDTH_SIZE], + struct ehci_tt *tt) +{ + struct ehci_per_sched *ps; + unsigned uframe, uf, x; + u8 *budget_line; + + if (!tt) + return; + memset(budget_table, 0, EHCI_BANDWIDTH_SIZE); + + /* Add up the contributions from all the endpoints using this TT */ + list_for_each_entry(ps, &tt->ps_list, ps_list) { + for (uframe = ps->bw_phase << 3; uframe < EHCI_BANDWIDTH_SIZE; + uframe += ps->bw_uperiod) { + budget_line = &budget_table[uframe]; + x = ps->tt_usecs; + + /* propagate the time forward */ + for (uf = ps->phase_uf; uf < 8; ++uf) { + x += budget_line[uf]; + + /* Each microframe lasts 125 us */ + if (x <= 125) { + budget_line[uf] = x; + break; + } + budget_line[uf] = 125; + x -= 125; + } + } + } +} + +static int __maybe_unused 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 + +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]; + } + } +} + +/* + * 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, + struct ehci_per_sched *ps, + struct ehci_tt *tt, + unsigned frame, + unsigned uframe +) +{ + unsigned period = ps->bw_period; + unsigned usecs = ps->tt_usecs; + + if ((period == 0) || (uframe >= 7)) /* error */ + return 0; + + for (frame &= period - 1; frame < EHCI_BANDWIDTH_FRAMES; + frame += period) { + unsigned i, uf; + unsigned short tt_usecs[8]; + + if (tt->bandwidth[frame] + usecs > 900) + return 0; + + uf = frame << 3; + for (i = 0; i < 8; (++i, ++uf)) + tt_usecs[i] = ehci->tt_budget[uf]; + + if (max_tt_usecs[uframe] <= tt_usecs[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 (usecs > 125) { + int ufs = (usecs / 125); + + for (i = uframe; i < (uframe + ufs) && i < 8; i++) + if (tt_usecs[i] > 0) + 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]) + 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->ps.udev)) { + 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++) + goto out; + + /* 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); +out: + 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->ps.period; + + dev_dbg(&qh->ps.udev->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->ps.phase, qh->ps.usecs, qh->ps.c_usecs); + + /* high bandwidth, or otherwise every microframe */ + if (period == 0) + period = 1; + + for (i = qh->ps.phase; 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->ps.period > here.qh->ps.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->unlink_reason = 0; + + /* update per-qh bandwidth for debugfs */ + ehci_to_hcd(ehci)->self.bandwidth_allocated += qh->ps.bw_period + ? ((qh->ps.usecs + qh->ps.c_usecs) / qh->ps.bw_period) + : (qh->ps.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 */ + period = qh->ps.period ? : 1; + + for (i = qh->ps.phase; i < ehci->periodic_size; i += period) + periodic_unlink(ehci, i, qh); + + /* update per-qh bandwidth for debugfs */ + ehci_to_hcd(ehci)->self.bandwidth_allocated -= qh->ps.bw_period + ? ((qh->ps.usecs + qh->ps.c_usecs) / qh->ps.bw_period) + : (qh->ps.usecs * 8); + + dev_dbg(&qh->ps.udev->dev, + "unlink qh%d-%04x/%p start %d [%d/%d us]\n", + qh->ps.period, + hc32_to_cpup(ehci, &qh->hw->hw_info2) & (QH_CMASK | QH_SMASK), + qh, qh->ps.phase, qh->ps.usecs, qh->ps.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 cancel_unlink_wait_intr(struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + if (qh->qh_state != QH_STATE_LINKED || + list_empty(&qh->unlink_node)) + return; + + list_del_init(&qh->unlink_node); + + /* + * TODO: disable the event of EHCI_HRTIMER_START_UNLINK_INTR for + * avoiding unnecessary CPU wakeup + */ +} + +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; + + /* if the qh is waiting for unlink, cancel it now */ + cancel_unlink_wait_intr(ehci, qh); + + 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; + } +} + +/* + * It is common only one intr URB is scheduled on one qh, and + * given complete() is run in tasklet context, introduce a bit + * delay to avoid unlink qh too early. + */ +static void start_unlink_intr_wait(struct ehci_hcd *ehci, + struct ehci_qh *qh) +{ + qh->unlink_cycle = ehci->intr_unlink_wait_cycle; + + /* New entries go at the end of the intr_unlink_wait list */ + list_add_tail(&qh->unlink_node, &ehci->intr_unlink_wait); + + if (ehci->rh_state < EHCI_RH_RUNNING) + ehci_handle_start_intr_unlinks(ehci); + else if (ehci->intr_unlink_wait.next == &qh->unlink_node) { + ehci_enable_event(ehci, EHCI_HRTIMER_START_UNLINK_INTR, true); + ++ehci->intr_unlink_wait_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 uperiod, + unsigned usecs +) { + /* 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; + + for (uframe += frame << 3; uframe < EHCI_BANDWIDTH_SIZE; + uframe += uperiod) { + if (ehci->bandwidth[uframe] > usecs) + return 0; + } + + /* success! */ + return 1; +} + +static int check_intr_schedule( + struct ehci_hcd *ehci, + unsigned frame, + unsigned uframe, + struct ehci_qh *qh, + unsigned *c_maskp, + struct ehci_tt *tt +) +{ + int retval = -ENOSPC; + u8 mask = 0; + + if (qh->ps.c_usecs && uframe >= 6) /* FSTN territory? */ + goto done; + + if (!check_period(ehci, frame, uframe, qh->ps.bw_uperiod, qh->ps.usecs)) + goto done; + if (!qh->ps.c_usecs) { + retval = 0; + *c_maskp = 0; + goto done; + } + +#ifdef CONFIG_USB_EHCI_TT_NEWSCHED + if (tt_available(ehci, &qh->ps, tt, frame, uframe)) { + unsigned i; + + /* TODO : this may need FSTN for SSPLIT in uframe 5. */ + for (i = uframe+2; i < 8 && i <= uframe+4; i++) + if (!check_period(ehci, frame, i, + qh->ps.bw_uperiod, qh->ps.c_usecs)) + goto done; + else + mask |= 1 << i; + + retval = 0; + + *c_maskp = mask; + } +#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 = mask; + + mask |= 1 << uframe; + if (tt_no_collision(ehci, qh->ps.bw_period, qh->ps.udev, frame, mask)) { + if (!check_period(ehci, frame, uframe + qh->gap_uf + 1, + qh->ps.bw_uperiod, qh->ps.c_usecs)) + goto done; + if (!check_period(ehci, frame, uframe + qh->gap_uf, + qh->ps.bw_uperiod, qh->ps.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 = 0; + unsigned uframe; + unsigned c_mask; + struct ehci_qh_hw *hw = qh->hw; + struct ehci_tt *tt; + + hw->hw_next = EHCI_LIST_END(ehci); + + /* reuse the previous schedule slots, if we can */ + if (qh->ps.phase != NO_FRAME) { + ehci_dbg(ehci, "reused qh %p schedule\n", qh); + return 0; + } + + uframe = 0; + c_mask = 0; + tt = find_tt(qh->ps.udev); + if (IS_ERR(tt)) { + status = PTR_ERR(tt); + goto done; + } + compute_tt_budget(ehci->tt_budget, tt); + + /* else scan the schedule to find a group of slots such that all + * uframes have enough periodic bandwidth available. + */ + /* "normal" case, uframing flexible except with splits */ + if (qh->ps.bw_period) { + int i; + unsigned frame; + + for (i = qh->ps.bw_period; i > 0; --i) { + frame = ++ehci->random_frame & (qh->ps.bw_period - 1); + for (uframe = 0; uframe < 8; uframe++) { + status = check_intr_schedule(ehci, + frame, uframe, qh, &c_mask, tt); + if (status == 0) + goto got_it; + } + } + + /* qh->ps.bw_period == 0 means every uframe */ + } else { + status = check_intr_schedule(ehci, 0, 0, qh, &c_mask, tt); + } + if (status) + goto done; + + got_it: + qh->ps.phase = (qh->ps.period ? ehci->random_frame & + (qh->ps.period - 1) : 0); + qh->ps.bw_phase = qh->ps.phase & (qh->ps.bw_period - 1); + qh->ps.phase_uf = uframe; + qh->ps.cs_mask = qh->ps.period ? + (c_mask << 8) | (1 << uframe) : + QH_SMASK; + + /* reset S-frame and (maybe) C-frame masks */ + hw->hw_info2 &= cpu_to_hc32(ehci, ~(QH_CMASK | QH_SMASK)); + hw->hw_info2 |= cpu_to_hc32(ehci, qh->ps.cs_mask); + reserve_release_intr_bandwidth(ehci, qh, 1); + +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) { + status = qh_schedule(ehci, qh); + if (status) + 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); + } else { + /* cancel unlink wait for the qh */ + cancel_unlink_wait_intr(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)) + start_unlink_intr(ehci, qh); + else if (unlikely(list_empty(&qh->qtd_list) && + qh->qh_state == QH_STATE_LINKED)) + start_unlink_intr_wait(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_obj(*stream, mem_flags); + if (likely(stream != NULL)) { + INIT_LIST_HEAD(&stream->td_list); + INIT_LIST_HEAD(&stream->free_list); + stream->next_uframe = NO_FRAME; + stream->ps.phase = NO_FRAME; + } + return stream; +} + +static void +iso_stream_init( + struct ehci_hcd *ehci, + struct ehci_iso_stream *stream, + struct urb *urb +) +{ + static const u8 smask_out[] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f }; + + struct usb_device *dev = urb->dev; + u32 buf1; + unsigned epnum, maxp; + int is_input; + unsigned tmp; + + /* + * this might be a "high bandwidth" highspeed endpoint, + * as encoded in the ep descriptor's wMaxPacket field + */ + epnum = usb_pipeendpoint(urb->pipe); + is_input = usb_pipein(urb->pipe) ? USB_DIR_IN : 0; + maxp = usb_endpoint_maxp(&urb->ep->desc); + buf1 = is_input ? 1 << 11 : 0; + + /* knows about ITD vs SITD */ + if (dev->speed == USB_SPEED_HIGH) { + unsigned multi = usb_endpoint_maxp_mult(&urb->ep->desc); + + stream->highspeed = 1; + + 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->ps.usecs = HS_USECS_ISO(maxp); + + /* period for bandwidth allocation */ + tmp = min_t(unsigned, EHCI_BANDWIDTH_SIZE, + 1 << (urb->ep->desc.bInterval - 1)); + + /* Allow urb->interval to override */ + stream->ps.bw_uperiod = min_t(unsigned, tmp, urb->interval); + + stream->uperiod = urb->interval; + stream->ps.period = urb->interval >> 3; + stream->bandwidth = stream->ps.usecs * 8 / + stream->ps.bw_uperiod; + + } 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->ps.usecs = HS_USECS_ISO(maxp); + think_time = dev->tt->think_time; + stream->ps.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->ps.c_usecs = stream->ps.usecs; + stream->ps.usecs = HS_USECS_ISO(1); + stream->ps.cs_mask = 1; + + /* c-mask as specified in USB 2.0 11.18.4 3.c */ + tmp = (1 << (hs_transfers + 2)) - 1; + stream->ps.cs_mask |= tmp << (8 + 2); + } else + stream->ps.cs_mask = smask_out[hs_transfers - 1]; + + /* period for bandwidth allocation */ + tmp = min_t(unsigned, EHCI_BANDWIDTH_FRAMES, + 1 << (urb->ep->desc.bInterval - 1)); + + /* Allow urb->interval to override */ + stream->ps.bw_period = min_t(unsigned, tmp, urb->interval); + stream->ps.bw_uperiod = stream->ps.bw_period << 3; + + stream->ps.period = urb->interval; + stream->uperiod = urb->interval << 3; + stream->bandwidth = (stream->ps.usecs + stream->ps.c_usecs) / + stream->ps.bw_period; + + /* stream->splits gets created from cs_mask later */ + stream->address = cpu_to_hc32(ehci, addr); + } + + stream->ps.udev = dev; + stream->ps.ep = urb->ep; + + stream->bEndpointAddress = is_input | epnum; + 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; + iso_stream_init(ehci, stream, urb); + } + + /* 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; + + iso_sched = kzalloc_flex(*iso_sched, packet, packets, 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->uperiod; + + /* 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 = NO_FRAME; + 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 void reserve_release_iso_bandwidth(struct ehci_hcd *ehci, + struct ehci_iso_stream *stream, int sign) +{ + unsigned uframe; + unsigned i, j; + unsigned s_mask, c_mask, m; + int usecs = stream->ps.usecs; + int c_usecs = stream->ps.c_usecs; + int tt_usecs = stream->ps.tt_usecs; + struct ehci_tt *tt; + + if (stream->ps.phase == NO_FRAME) /* Bandwidth wasn't reserved */ + return; + uframe = stream->ps.bw_phase << 3; + + bandwidth_dbg(ehci, sign, "iso", &stream->ps); + + if (sign < 0) { /* Release bandwidth */ + usecs = -usecs; + c_usecs = -c_usecs; + tt_usecs = -tt_usecs; + } + + if (!stream->splits) { /* High speed */ + for (i = uframe + stream->ps.phase_uf; i < EHCI_BANDWIDTH_SIZE; + i += stream->ps.bw_uperiod) + ehci->bandwidth[i] += usecs; + + } else { /* Full speed */ + s_mask = stream->ps.cs_mask; + c_mask = s_mask >> 8; + + /* NOTE: adjustment needed for frame overflow */ + for (i = uframe; i < EHCI_BANDWIDTH_SIZE; + i += stream->ps.bw_uperiod) { + for ((j = stream->ps.phase_uf, m = 1 << j); j < 8; + (++j, m <<= 1)) { + if (s_mask & m) + ehci->bandwidth[i+j] += usecs; + else if (c_mask & m) + ehci->bandwidth[i+j] += c_usecs; + } + } + + /* + * find_tt() will not return any error here as we have + * already called find_tt() before calling this function + * and checked for any error return. The previous call + * would have created the data structure. + */ + tt = find_tt(stream->ps.udev); + if (sign > 0) + list_add_tail(&stream->ps.ps_list, &tt->ps_list); + else + list_del(&stream->ps.ps_list); + + for (i = uframe >> 3; i < EHCI_BANDWIDTH_FRAMES; + i += stream->ps.bw_period) + tt->bandwidth[i] += tt_usecs; + } +} + +static inline int +itd_slot_ok( + struct ehci_hcd *ehci, + struct ehci_iso_stream *stream, + unsigned uframe +) +{ + unsigned usecs; + + /* convert "usecs we need" to "max already claimed" */ + usecs = ehci->uframe_periodic_max - stream->ps.usecs; + + for (uframe &= stream->ps.bw_uperiod - 1; uframe < EHCI_BANDWIDTH_SIZE; + uframe += stream->ps.bw_uperiod) { + if (ehci->bandwidth[uframe] > usecs) + return 0; + } + return 1; +} + +static inline int +sitd_slot_ok( + struct ehci_hcd *ehci, + struct ehci_iso_stream *stream, + unsigned uframe, + struct ehci_iso_sched *sched, + struct ehci_tt *tt +) +{ + unsigned mask, tmp; + unsigned frame, uf; + + mask = stream->ps.cs_mask << (uframe & 7); + + /* for OUT, don't wrap SSPLIT into H-microframe 7 */ + if (((stream->ps.cs_mask & 0xff) << (uframe & 7)) >= (1 << 7)) + return 0; + + /* for IN, don't wrap CSPLIT into the next frame */ + if (mask & ~0xffff) + return 0; + + /* check bandwidth */ + uframe &= stream->ps.bw_uperiod - 1; + 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, &stream->ps, tt, frame, uf)) + 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, stream->ps.bw_period, + stream->ps.udev, frame, mask)) + return 0; +#endif + + do { + unsigned max_used; + unsigned i; + + /* check starts (OUT uses more than one) */ + uf = uframe; + max_used = ehci->uframe_periodic_max - stream->ps.usecs; + for (tmp = stream->ps.cs_mask & 0xff; tmp; tmp >>= 1, uf++) { + if (ehci->bandwidth[uf] > max_used) + return 0; + } + + /* for IN, check CSPLIT */ + if (stream->ps.c_usecs) { + max_used = ehci->uframe_periodic_max - + stream->ps.c_usecs; + uf = uframe & ~7; + tmp = 1 << (2+8); + for (i = (uframe & 7) + 2; i < 8; (++i, tmp <<= 1)) { + if ((stream->ps.cs_mask & tmp) == 0) + continue; + if (ehci->bandwidth[uf+i] > max_used) + return 0; + } + } + + uframe += stream->ps.bw_uperiod; + } while (uframe < EHCI_BANDWIDTH_SIZE); + + stream->ps.cs_mask <<= uframe & 7; + stream->splits = cpu_to_hc32(ehci, stream->ps.cs_mask); + 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! + */ + +static int +iso_stream_schedule( + struct ehci_hcd *ehci, + struct urb *urb, + struct ehci_iso_stream *stream +) +{ + u32 now, base, next, start, period, span, now2; + u32 wrap = 0, skip = 0; + int status = 0; + unsigned mod = ehci->periodic_size << 3; + struct ehci_iso_sched *sched = urb->hcpriv; + bool empty = list_empty(&stream->td_list); + bool new_stream = false; + + period = stream->uperiod; + span = sched->span; + if (!stream->highspeed) + span <<= 3; + + /* Start a new isochronous stream? */ + if (unlikely(empty && !hcd_periodic_completion_in_progress( + ehci_to_hcd(ehci), urb->ep))) { + + /* Schedule the endpoint */ + if (stream->ps.phase == NO_FRAME) { + int done = 0; + struct ehci_tt *tt = find_tt(stream->ps.udev); + + if (IS_ERR(tt)) { + status = PTR_ERR(tt); + goto fail; + } + compute_tt_budget(ehci->tt_budget, tt); + + start = ((-(++ehci->random_frame)) << 3) & (period - 1); + + /* 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, stream, start)) + done = 1; + } else { + if ((start % 8) >= 6) + continue; + if (sitd_slot_ok(ehci, stream, start, + sched, tt)) + 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; + } + stream->ps.phase = (start >> 3) & + (stream->ps.period - 1); + stream->ps.bw_phase = stream->ps.phase & + (stream->ps.bw_period - 1); + stream->ps.phase_uf = start & 7; + reserve_release_iso_bandwidth(ehci, stream, 1); + } + + /* New stream is already scheduled; use the upcoming slot */ + else { + start = (stream->ps.phase << 3) + stream->ps.phase_uf; + } + + stream->next_uframe = start; + new_stream = true; + } + + now = ehci_read_frame_index(ehci) & (mod - 1); + + /* 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 */ + + /* If needed, initialize last_iso_frame so that this URB will be seen */ + if (ehci->isoc_count == 0) + ehci->last_iso_frame = now >> 3; + + /* + * 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); + + if (unlikely(new_stream)) + goto do_ASAP; + + /* + * Typical case: reuse current schedule, stream may still be 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. + */ + now2 = (now - base) & (mod - 1); + + /* Is the schedule about to wrap around? */ + if (unlikely(!empty && start < period)) { + ehci_dbg(ehci, "request %p would overflow (%u-%u < %u mod %u)\n", + urb, stream->next_uframe, base, period, mod); + status = -EFBIG; + goto fail; + } + + /* Is the next packet scheduled after the base time? */ + if (likely(!empty || start <= now2 + period)) { + + /* URB_ISO_ASAP: make sure that start >= next */ + if (unlikely(start < next && + (urb->transfer_flags & URB_ISO_ASAP))) + goto do_ASAP; + + /* Otherwise use start, if it's not in the past */ + if (likely(start >= now2)) + goto use_start; + + /* Otherwise we got an underrun while the queue was empty */ + } else { + if (urb->transfer_flags & URB_ISO_ASAP) + goto do_ASAP; + wrap = mod; + now2 += mod; + } + + /* How many uframes and packets do we need to skip? */ + skip = (now2 - start + period - 1) & -period; + if (skip >= span) { /* Entirely in the past? */ + ehci_dbg(ehci, "iso underrun %p (%u+%u < %u) [%u]\n", + urb, start + base, span - period, now2 + base, + base); + + /* Try to keep the last TD intact for scanning later */ + skip = span - period; + + /* Will it come before the current scan position? */ + if (empty) { + skip = span; /* Skip the entire URB */ + status = 1; /* and give it back immediately */ + iso_sched_free(stream, sched); + sched = NULL; + urb->hcpriv = NULL; + } + } + urb->error_count = skip / period; + if (sched) + sched->first_packet = urb->error_count; + goto use_start; + + do_ASAP: + /* Use the first slot after "next" */ + start = next + ((start - next) & (period - 1)); + + use_start: + /* Tried to schedule too far into the future? */ + if (unlikely(start + span - period >= mod + wrap)) { + ehci_dbg(ehci, "request %p would overflow (%u+%u >= %u)\n", + urb, start, span - period, mod + wrap); + status = -EFBIG; + goto fail; + } + + start += base; + stream->next_uframe = (start + skip) & (mod - 1); + + /* report high speed start in uframes; full speed, in frames */ + urb->start_frame = start & (mod - 1); + if (!stream->highspeed) + urb->start_frame >>= 3; + return status; + + fail: + 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; + + 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 = iso_sched->first_packet, 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->uperiod; + 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; + 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 */ + 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; + +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->uperiod)) { + ehci_dbg(ehci, "can't change iso interval %d --> %d\n", + stream->uperiod, 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 if (status > 0) { + status = 0; + ehci_urb_done(ehci, urb, 0); + } else { + usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb); + } + done_not_linked: + if (status < 0) { + iso_sched_free(stream, urb->hcpriv); + urb->hcpriv = NULL; + } + 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->ps.period; + + /* 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 = NO_FRAME; + 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; + + 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; + + 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 = sched->first_packet, 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->uperiod; + } + 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; + struct ehci_iso_stream *stream = sitd->stream; + 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 */ + 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; + +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->ps.period) { + ehci_dbg(ehci, "can't change iso interval %d --> %d\n", + stream->ps.period, 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 (likely(status == 0)) { + sitd_link_urb(ehci, urb, ehci->periodic_size << 3, stream); + } else if (status > 0) { + status = 0; + ehci_urb_done(ehci, urb, 0); + } else { + usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb); + } + done_not_linked: + if (status < 0) { + iso_sched_free(stream, urb->hcpriv); + urb->hcpriv = NULL; + } + 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; + union ehci_shadow q, *q_p; + __hc32 type, *hw_p; + + /* + * 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; + +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 = cpu_to_hc32(ehci, 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 = cpu_to_hc32(ehci, 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(); */ + fallthrough; + 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) + return; + + /* The last frame may still have active siTDs */ + ehci->last_iso_frame = frame; + frame = (frame + 1) & fmask; + + goto restart; +} diff --git a/drivers/usb/host/ehci-sh.c b/drivers/usb/host/ehci-sh.c new file mode 100644 index 000000000..2d23690d7 --- /dev/null +++ b/drivers/usb/host/ehci-sh.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SuperH EHCI host controller driver + * + * Copyright (C) 2010 Paul Mundt + * + * Based on ohci-sh.c and ehci-atmel.c. + */ +#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_DMA | HCD_MEMORY | HCD_BH, + + /* + * 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 usb_hcd *hcd; + int irq, ret; + + if (usb_disabled()) + return -ENODEV; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto fail_create_hcd; + } + + /* 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->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(hcd->regs)) { + ret = PTR_ERR(hcd->regs); + goto fail_request_resource; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + priv = devm_kzalloc(&pdev->dev, sizeof(struct ehci_sh_priv), + GFP_KERNEL); + if (!priv) { + 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; + + ret = clk_enable(priv->fclk); + if (ret) + goto fail_request_resource; + ret = clk_enable(priv->iclk); + if (ret) + goto fail_iclk; + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to add hcd"); + goto fail_add_hcd; + } + device_wakeup_enable(hcd->self.controller); + + priv->hcd = hcd; + platform_set_drvdata(pdev, priv); + + return ret; + +fail_add_hcd: + clk_disable(priv->iclk); +fail_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 void 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); + + clk_disable(priv->fclk); + clk_disable(priv->iclk); +} + +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", + }, +}; + +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 000000000..e96710192 --- /dev/null +++ b/drivers/usb/host/ehci-spear.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* Driver for EHCI HCD on SPEAr SOC +* +* Copyright (C) 2010 ST Micro Electronics, +* Deepak Sikri +* +* Based on various ehci-*.c drivers +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define DRIVER_DESC "EHCI SPEAr driver" + +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; + +static int __maybe_unused 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 __maybe_unused ehci_spear_drv_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + + ehci_resume(hcd, false); + return 0; +} + +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. + */ + retval = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (retval) + goto fail; + + 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; + } + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(hcd->regs)) { + retval = PTR_ERR(hcd->regs); + goto err_put_hcd; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + sehci = to_spear_ehci(hcd); + sehci->clk = usbh_clk; + + /* registers start at offset 0x0 */ + hcd_to_ehci(hcd)->caps = hcd->regs; + + retval = clk_prepare_enable(sehci->clk); + if (retval) + goto err_put_hcd; + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval) + goto err_stop_ehci; + + device_wakeup_enable(hcd->self.controller); + 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 void 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); + + usb_remove_hcd(hcd); + + clk_disable_unprepare(sehci->clk); + usb_put_hcd(hcd); +} + +static const struct of_device_id spear_ehci_id_table[] = { + { .compatible = "st,spear600-ehci", }, + { }, +}; +MODULE_DEVICE_TABLE(of, spear_ehci_id_table); + +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 = pm_ptr(&ehci_spear_pm_ops), + .of_match_table = spear_ehci_id_table, + } +}; + +static const struct ehci_driver_overrides spear_overrides __initconst = { + .extra_priv_size = sizeof(struct spear_ehci), +}; + +static int __init ehci_spear_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + 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-st.c b/drivers/usb/host/ehci-st.c new file mode 100644 index 000000000..58867d816 --- /dev/null +++ b/drivers/usb/host/ehci-st.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ST EHCI driver + * + * Copyright (C) 2014 STMicroelectronics – All Rights Reserved + * + * Author: Peter Griffin + * + * Derived from ehci-platform.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ehci.h" + +#define USB_MAX_CLKS 3 + +struct st_ehci_platform_priv { + struct clk *clks[USB_MAX_CLKS]; + struct clk *clk48; + struct reset_control *rst; + struct reset_control *pwr; + struct phy *phy; +}; + +#define DRIVER_DESC "EHCI STMicroelectronics driver" + +#define hcd_to_ehci_priv(h) \ + ((struct st_ehci_platform_priv *)hcd_to_ehci(h)->priv) + +#define EHCI_CAPS_SIZE 0x10 +#define AHB2STBUS_INSREG01 (EHCI_CAPS_SIZE + 0x84) + +static int st_ehci_platform_reset(struct usb_hcd *hcd) +{ + struct platform_device *pdev = to_platform_device(hcd->self.controller); + struct usb_ehci_pdata *pdata = dev_get_platdata(&pdev->dev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + u32 threshold; + + /* Set EHCI packet buffer IN/OUT threshold to 128 bytes */ + threshold = 128 | (128 << 16); + writel(threshold, hcd->regs + AHB2STBUS_INSREG01); + + ehci->caps = hcd->regs + pdata->caps_offset; + return ehci_setup(hcd); +} + +static int st_ehci_platform_power_on(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct st_ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); + int clk, ret; + + ret = reset_control_deassert(priv->pwr); + if (ret) + return ret; + + ret = reset_control_deassert(priv->rst); + if (ret) + goto err_assert_power; + + /* some SoCs don't have a dedicated 48Mhz clock, but those that do + need the rate to be explicitly set */ + if (priv->clk48) { + ret = clk_set_rate(priv->clk48, 48000000); + if (ret) + goto err_assert_reset; + } + + for (clk = 0; clk < USB_MAX_CLKS && priv->clks[clk]; clk++) { + ret = clk_prepare_enable(priv->clks[clk]); + if (ret) + goto err_disable_clks; + } + + ret = phy_init(priv->phy); + if (ret) + goto err_disable_clks; + + ret = phy_power_on(priv->phy); + if (ret) + goto err_exit_phy; + + return 0; + +err_exit_phy: + phy_exit(priv->phy); +err_disable_clks: + while (--clk >= 0) + clk_disable_unprepare(priv->clks[clk]); +err_assert_reset: + reset_control_assert(priv->rst); +err_assert_power: + reset_control_assert(priv->pwr); + + return ret; +} + +static void st_ehci_platform_power_off(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct st_ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); + int clk; + + reset_control_assert(priv->pwr); + + reset_control_assert(priv->rst); + + phy_power_off(priv->phy); + + phy_exit(priv->phy); + + for (clk = USB_MAX_CLKS - 1; clk >= 0; clk--) + if (priv->clks[clk]) + clk_disable_unprepare(priv->clks[clk]); + +} + +static struct hc_driver __read_mostly ehci_platform_hc_driver; + +static const struct ehci_driver_overrides platform_overrides __initconst = { + .reset = st_ehci_platform_reset, + .extra_priv_size = sizeof(struct st_ehci_platform_priv), +}; + +static struct usb_ehci_pdata ehci_platform_defaults = { + .power_on = st_ehci_platform_power_on, + .power_suspend = st_ehci_platform_power_off, + .power_off = st_ehci_platform_power_off, +}; + +static int st_ehci_platform_probe(struct platform_device *dev) +{ + struct usb_hcd *hcd; + struct resource *res_mem; + struct usb_ehci_pdata *pdata = &ehci_platform_defaults; + struct st_ehci_platform_priv *priv; + int err, irq, clk = 0; + + if (usb_disabled()) + return -ENODEV; + + irq = platform_get_irq(dev, 0); + if (irq < 0) + return irq; + + hcd = usb_create_hcd(&ehci_platform_hc_driver, &dev->dev, + dev_name(&dev->dev)); + if (!hcd) + return -ENOMEM; + + platform_set_drvdata(dev, hcd); + dev->dev.platform_data = pdata; + priv = hcd_to_ehci_priv(hcd); + + priv->phy = devm_phy_get(&dev->dev, "usb"); + if (IS_ERR(priv->phy)) { + err = PTR_ERR(priv->phy); + goto err_put_hcd; + } + + for (clk = 0; clk < USB_MAX_CLKS; clk++) { + priv->clks[clk] = of_clk_get(dev->dev.of_node, clk); + if (IS_ERR(priv->clks[clk])) { + err = PTR_ERR(priv->clks[clk]); + if (err == -EPROBE_DEFER) + goto err_put_clks; + priv->clks[clk] = NULL; + break; + } + } + + /* some SoCs don't have a dedicated 48Mhz clock, but those that + do need the rate to be explicitly set */ + priv->clk48 = devm_clk_get(&dev->dev, "clk48"); + if (IS_ERR(priv->clk48)) { + dev_info(&dev->dev, "48MHz clk not found\n"); + priv->clk48 = NULL; + } + + priv->pwr = + devm_reset_control_get_optional_shared(&dev->dev, "power"); + if (IS_ERR(priv->pwr)) { + err = PTR_ERR(priv->pwr); + if (err == -EPROBE_DEFER) + goto err_put_clks; + priv->pwr = NULL; + } + + priv->rst = + devm_reset_control_get_optional_shared(&dev->dev, "softreset"); + if (IS_ERR(priv->rst)) { + err = PTR_ERR(priv->rst); + if (err == -EPROBE_DEFER) + goto err_put_clks; + priv->rst = NULL; + } + + if (pdata->power_on) { + err = pdata->power_on(dev); + if (err < 0) + goto err_put_clks; + } + + hcd->regs = devm_platform_get_and_ioremap_resource(dev, 0, &res_mem); + if (IS_ERR(hcd->regs)) { + err = PTR_ERR(hcd->regs); + goto err_put_clks; + } + hcd->rsrc_start = res_mem->start; + hcd->rsrc_len = resource_size(res_mem); + + err = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (err) + goto err_put_clks; + + device_wakeup_enable(hcd->self.controller); + platform_set_drvdata(dev, hcd); + + return err; + +err_put_clks: + while (--clk >= 0) + clk_put(priv->clks[clk]); +err_put_hcd: + if (pdata == &ehci_platform_defaults) + dev->dev.platform_data = NULL; + + usb_put_hcd(hcd); + + return err; +} + +static void st_ehci_platform_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); + struct st_ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); + int clk; + + usb_remove_hcd(hcd); + + if (pdata->power_off) + pdata->power_off(dev); + + for (clk = 0; clk < USB_MAX_CLKS && priv->clks[clk]; clk++) + clk_put(priv->clks[clk]); + + usb_put_hcd(hcd); + + if (pdata == &ehci_platform_defaults) + dev->dev.platform_data = NULL; +} + +#ifdef CONFIG_PM_SLEEP + +static int st_ehci_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct usb_ehci_pdata *pdata = dev_get_platdata(dev); + struct platform_device *pdev = to_platform_device(dev); + bool do_wakeup = device_may_wakeup(dev); + int ret; + + ret = ehci_suspend(hcd, do_wakeup); + if (ret) + return ret; + + if (pdata->power_suspend) + pdata->power_suspend(pdev); + + pinctrl_pm_select_sleep_state(dev); + + return ret; +} + +static int st_ehci_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct usb_ehci_pdata *pdata = dev_get_platdata(dev); + struct platform_device *pdev = to_platform_device(dev); + int err; + + pinctrl_pm_select_default_state(dev); + + if (pdata->power_on) { + err = pdata->power_on(pdev); + if (err < 0) + return err; + } + + ehci_resume(hcd, false); + return 0; +} + +static SIMPLE_DEV_PM_OPS(st_ehci_pm_ops, st_ehci_suspend, st_ehci_resume); + +#endif /* CONFIG_PM_SLEEP */ + +static const struct of_device_id st_ehci_ids[] = { + { .compatible = "st,st-ehci-300x", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, st_ehci_ids); + +static struct platform_driver ehci_platform_driver = { + .probe = st_ehci_platform_probe, + .remove = st_ehci_platform_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "st-ehci", +#ifdef CONFIG_PM_SLEEP + .pm = &st_ehci_pm_ops, +#endif + .of_match_table = st_ehci_ids, + } +}; + +static int __init ehci_platform_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + 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("Peter Griffin "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ehci-sysfs.c b/drivers/usb/host/ehci-sysfs.c new file mode 100644 index 000000000..5e6b545c3 --- /dev/null +++ b/drivers/usb/host/ehci-sysfs.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2007 by Alan Stern + */ + +/* this file is part of ehci-hcd.c */ + + +/* Display the ports dedicated to the companion controller */ +static ssize_t companion_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ehci_hcd *ehci; + int nports, index; + int len = 0; + + ehci = hcd_to_ehci(dev_get_drvdata(dev)); + nports = HCS_N_PORTS(ehci->hcs_params); + + for (index = 0; index < nports; ++index) { + if (test_bit(index, &ehci->companion_ports)) + len += sysfs_emit_at(buf, len, "%d\n", index + 1); + } + return len; +} + +/* + * 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 companion_store(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(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_RW(companion); + + +/* + * Display / Set uframe_periodic_max + */ +static ssize_t uframe_periodic_max_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ehci_hcd *ehci; + + ehci = hcd_to_ehci(dev_get_drvdata(dev)); + return sysfs_emit(buf, "%d\n", ehci->uframe_periodic_max); +} + + +static ssize_t uframe_periodic_max_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ehci_hcd *ehci; + unsigned uframe_periodic_max; + unsigned uframe; + unsigned long flags; + ssize_t ret; + + ehci = hcd_to_ehci(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 + * to see whether the decrease is possible. + */ + if (uframe_periodic_max < ehci->uframe_periodic_max) { + u8 allocated_max = 0; + + for (uframe = 0; uframe < EHCI_BANDWIDTH_SIZE; ++uframe) + allocated_max = max(allocated_max, + ehci->bandwidth[uframe]); + + if (allocated_max > uframe_periodic_max) { + ehci_info(ehci, + "cannot decrease uframe_periodic_max because " + "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_RW(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-timer.c b/drivers/usb/host/ehci-timer.c new file mode 100644 index 000000000..a79c8ac0a --- /dev/null +++ b/drivers/usb/host/ehci-timer.c @@ -0,0 +1,426 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2012 by Alan Stern + */ + +/* 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 */ + 2 * NSEC_PER_MSEC, /* EHCI_HRTIMER_ACTIVE_UNLINK */ + 5 * NSEC_PER_MSEC, /* EHCI_HRTIMER_START_UNLINK_INTR */ + 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(), 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 */ +} + +/* start to unlink interrupt QHs */ +static void ehci_handle_start_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. + */ + while (!list_empty(&ehci->intr_unlink_wait)) { + struct ehci_qh *qh; + + qh = list_first_entry(&ehci->intr_unlink_wait, + struct ehci_qh, unlink_node); + if (!stopped && (qh->unlink_cycle == + ehci->intr_unlink_wait_cycle)) + break; + list_del_init(&qh->unlink_node); + qh->unlink_reason |= QH_UNLINK_QUEUE_EMPTY; + start_unlink_intr(ehci, qh); + } + + /* Handle remaining entries later */ + if (!list_empty(&ehci->intr_unlink_wait)) { + ehci_enable_event(ehci, EHCI_HRTIMER_START_UNLINK_INTR, true); + ++ehci->intr_unlink_wait_cycle; + } +} + +/* 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_init(&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)) { + INCR(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_iaa_cycle(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 */ + end_unlink_async, /* EHCI_HRTIMER_ACTIVE_UNLINK */ + ehci_handle_start_intr_unlinks, /* EHCI_HRTIMER_START_UNLINK_INTR */ + 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 (ktime_compare(now, ehci->hr_timeouts[e]) >= 0) + 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-xilinx-of.c b/drivers/usb/host/ehci-xilinx-of.c new file mode 100644 index 000000000..1d16cfefa --- /dev/null +++ b/drivers/usb/host/ehci-xilinx-of.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include +#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. + * + * Return: Always return 0 + */ +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_DMA | HCD_USB2 | HCD_BH, + + /* + * 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. + * + * Return: zero on success, negative error code otherwise + */ +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) { + dev_err(&op->dev, "%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) { + device_wakeup_enable(hcd->self.controller); + 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. + * + * Return: Always return 0 + */ +static void ehci_hcd_xilinx_of_remove(struct platform_device *op) +{ + struct usb_hcd *hcd = platform_get_drvdata(op); + + dev_dbg(&op->dev, "stopping XILINX-OF USB Controller\n"); + + usb_remove_hcd(hcd); + + usb_put_hcd(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 = usb_hcd_platform_shutdown, + .driver = { + .name = "xilinx-of-ehci", + .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 000000000..d7a3c8d13 --- /dev/null +++ b/drivers/usb/host/ehci.h @@ -0,0 +1,913 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2001-2002 by David Brownell + */ + +#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 CONFIG_DYNAMIC_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; +}; + +/* + * Scheduling and budgeting information for periodic transfers, for both + * high-speed devices and full/low-speed devices lying behind a TT. + */ +struct ehci_per_sched { + struct usb_device *udev; /* access to the TT */ + struct usb_host_endpoint *ep; + struct list_head ps_list; /* node on ehci_tt's ps_list */ + u16 tt_usecs; /* time on the FS/LS bus */ + u16 cs_mask; /* C-mask and S-mask bytes */ + u16 period; /* actual period in frames */ + u16 phase; /* actual phase, frame part */ + u8 bw_phase; /* same, for bandwidth + reservation */ + u8 phase_uf; /* uframe part of the phase */ + u8 usecs, c_usecs; /* times on the HS bus */ + u8 bw_uperiod; /* period in microframes, for + bandwidth reservation */ + u8 bw_period; /* same, in frames */ +}; +#define NO_FRAME 29999 /* frame not assigned yet */ + +/* 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_ACTIVE_UNLINK, /* Wait while unlinking an active QH */ + EHCI_HRTIMER_START_UNLINK_INTR, /* Unlink empty interrupt QHs */ + 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 */ + __hc32 old_current; /* Test for QH becoming */ + __hc32 old_token; /* inactive during unlink */ + + /* 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_wait; + struct list_head intr_unlink; + unsigned intr_unlink_wait_cycle; + 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 has_fsl_hs_errata:1; /* Freescale HS quirk */ + unsigned has_fsl_susp_errata:1; /* NXP SUSP quirk */ + unsigned has_ci_pec_bug:1; /* ChipIdea PEC bug */ + 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 */ + unsigned spurious_oc:1; + unsigned is_aspeed:1; + unsigned zx_wakeup_clear_needed:1; + + /* 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_tdi_phy_lpm: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 INCR(x) ((x)++) +#else +# define INCR(x) do {} while (0) +#endif + + /* debug files */ +#ifdef CONFIG_DYNAMIC_DEBUG + struct dentry *debug_dir; +#endif + + /* bandwidth usage */ +#define EHCI_BANDWIDTH_SIZE 64 +#define EHCI_BANDWIDTH_FRAMES (EHCI_BANDWIDTH_SIZE >> 3) + u8 bandwidth[EHCI_BANDWIDTH_SIZE]; + /* us allocated per uframe */ + u8 tt_budget[EHCI_BANDWIDTH_SIZE]; + /* us budgeted per uframe */ + struct list_head tt_list; + + /* platform-specific data -- must come last */ + unsigned long priv[] __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 */ +} __aligned(32); + +/* PID Codes that are used here, from EHCI specification, Table 3-16. */ +#define PID_CODE_OUT 0 +#define PID_CODE_IN 1 +#define PID_CODE_SETUP 2 + +/* 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) == PID_CODE_IN) + +/*-------------------------------------------------------------------------*/ + +/* 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]; +} __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; + struct ehci_per_sched ps; /* scheduling info */ + + 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 */ + + u8 unlink_reason; +#define QH_UNLINK_HALTED 0x01 /* Halt flag is set */ +#define QH_UNLINK_SHORT_READ 0x02 /* Recover from a short read */ +#define QH_UNLINK_DUMMY_OVERLAY 0x04 /* QH overlayed the dummy TD */ +#define QH_UNLINK_SHUTDOWN 0x08 /* The HC isn't running */ +#define QH_UNLINK_QUEUE_EMPTY 0x10 /* Reached end of the queue */ +#define QH_UNLINK_REQUESTED 0x20 /* Disable, reset, or dequeue */ + + u8 gap_uf; /* uframes split/csplit gap */ + + unsigned is_out:1; /* bulk or intr OUT */ + unsigned clearing_tt:1; /* Clear-TT-Buf in progress */ + unsigned dequeue_during_giveback:1; + unsigned should_be_inactive:1; +}; + +/*-------------------------------------------------------------------------*/ + +/* 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; + unsigned first_packet; + struct ehci_iso_packet packet[]; +}; + +/* + * 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_qh, 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 */ + + /* output of (re)scheduling */ + struct ehci_per_sched ps; /* scheduling info */ + unsigned next_uframe; + __hc32 splits; + + /* the rest is derived from the endpoint descriptor, + * including the extra info for hw_bufp[0..2] + */ + u16 uperiod; /* period in uframes */ + u16 maxp; + 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 */ +} __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) (((x) >> 16) & 0x3ff) +#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; +} __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 */ +} __aligned(32); + +/*-------------------------------------------------------------------------*/ + +/* + * USB-2.0 Specification Sections 11.14 and 11.18 + * Scheduling and budgeting split transactions using TTs + * + * A hub can have a single TT for all its ports, or multiple TTs (one for each + * port). The bandwidth and budgeting information for the full/low-speed bus + * below each TT is self-contained and independent of the other TTs or the + * high-speed bus. + * + * "Bandwidth" refers to the number of microseconds on the FS/LS bus allocated + * to an interrupt or isochronous endpoint for each frame. "Budget" refers to + * the best-case estimate of the number of full-speed bytes allocated to an + * endpoint for each microframe within an allocated frame. + * + * Removal of an endpoint invalidates a TT's budget. Instead of trying to + * keep an up-to-date record, we recompute the budget when it is needed. + */ + +struct ehci_tt { + u16 bandwidth[EHCI_BANDWIDTH_FRAMES]; + + struct list_head tt_list; /* List of all ehci_tt's */ + struct list_head ps_list; /* Items using this TT */ + struct usb_tt *usb_tt; + int tt_port; /* TT port number */ +}; + +/*-------------------------------------------------------------------------*/ + +/* 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 + +#define PORTSC_FSL_PFSC 24 /* Port Force Full-Speed Connect */ + +#if defined(CONFIG_PPC_85xx) +/* Some Freescale processors have an erratum (USB A-005275) in which + * incoming packets get corrupted in HS mode + */ +#define ehci_has_fsl_hs_errata(e) ((e)->has_fsl_hs_errata) +#else +#define ehci_has_fsl_hs_errata(e) (0) +#endif + +/* + * Some Freescale/NXP processors have an erratum (USB A-005697) + * in which we need to wait for 10ms for bus to enter suspend mode + * after setting SUSP bit. + */ +#define ehci_has_fsl_susp_errata(e) ((e)->has_fsl_susp_errata) + +/* + * Some Freescale/NXP processors using ChipIdea IP have a bug in which + * disabling the port (PE is cleared) does not cause PEC to be asserted + * when frame babble is detected. + */ +#define ehci_has_ci_pec_bug(e, portsc) \ + ((e)->has_ci_pec_bug && ((e)->command & CMD_PSE) \ + && !(portsc & PORT_PEC) && !(portsc & PORT_PE)) + +/* + * 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) + +/*-------------------------------------------------------------------------*/ + +/* 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); + int (*port_power)(struct usb_hcd *hcd, + int portnum, bool enable); +}; + +extern void ehci_init_driver(struct hc_driver *drv, + const struct ehci_driver_overrides *over); +extern int ehci_setup(struct usb_hcd *hcd); +extern int ehci_handshake(struct ehci_hcd *ehci, void __iomem *ptr, + u32 mask, u32 done, int usec); +extern int ehci_reset(struct ehci_hcd *ehci); + +extern int ehci_suspend(struct usb_hcd *hcd, bool do_wakeup); +extern int ehci_resume(struct usb_hcd *hcd, bool force_reset); +extern void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, + bool suspending, bool do_wakeup); + +extern int ehci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength); + +#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 000000000..100048b3b --- /dev/null +++ b/drivers/usb/host/fhci-dbg.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#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; +} +DEFINE_SHOW_ATTRIBUTE(fhci_dfs_regs); + +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; +} +DEFINE_SHOW_ATTRIBUTE(fhci_dfs_irq_stat); + +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); + + debugfs_create_file("regs", S_IFREG | S_IRUGO, fhci->dfs_root, fhci, + &fhci_dfs_regs_fops); + debugfs_create_file("irq_stat", S_IFREG | S_IRUGO, fhci->dfs_root, fhci, + &fhci_dfs_irq_stat_fops); +} + +void fhci_dfs_destroy(struct fhci_hcd *fhci) +{ + debugfs_remove_recursive(fhci->dfs_root); +} diff --git a/drivers/usb/host/fhci-hcd.c b/drivers/usb/host/fhci-hcd.c new file mode 100644 index 000000000..71e785f44 --- /dev/null +++ b/drivers/usb/host/fhci-hcd.c @@ -0,0 +1,794 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include +#include +#include +#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 (!gpiod_get_value(fhci->gpiods[GPIO_USBOE])) + return -1; + + /* check USBRP */ + if (gpiod_get_value(fhci->gpiods[GPIO_USBRP])) + bits |= 0x2; + + /* check USBRN */ + if (gpiod_get_value(fhci->gpiods[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_obj(*fhci->hc_list); + 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_obj(*fhci->vroot_hub); + 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_obj(*td); + if (!td) + goto err; + fhci_recycle_empty_td(fhci, td); + } + for (i = 0; i < MAX_EDS; i++) { + struct ed *ed; + + ed = kmalloc_obj(*ed); + 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_obj(*usb->actual_frame); + 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_obj(*usb); + if (!usb) + 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, hub_wq concurrently accesses the root + * hub; drivers will be talking to enumerated devices. + * (On restart paths, hub_wq 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, hub_wq + * 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; + fallthrough; + 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)) != 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_flex(*urb_priv, tds, size, mem_flags); + if (!urb_priv) + return -ENOMEM; + + urb_priv->num_of_tds = size; + + 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->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); + 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_DMA | 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++) { + if (i < GPIO_SPEED) + fhci->gpiods[i] = devm_gpiod_get_index(dev, + NULL, i, GPIOD_IN); + + else + fhci->gpiods[i] = devm_gpiod_get_index_optional(dev, + NULL, i, GPIOD_OUT_LOW); + + if (IS_ERR(fhci->gpiods[i])) { + dev_err(dev, "incorrect GPIO%d: %ld\n", + i, PTR_ERR(fhci->gpiods[i])); + goto err_gpios; + } + if (!fhci->gpiods[i]) { + dev_info(dev, "assuming board doesn't have " + "%s gpio\n", i == GPIO_SPEED ? + "speed" : "power"); + } + } + + for (j = 0; j < NUM_PINS; j++) { + fhci->pins[j] = qe_pin_request(dev, 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) { + 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; + + device_wakeup_enable(hcd->self.controller); + + 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: + cpm_muram_free(pram_addr); +err_pram: + iounmap(hcd->regs); +err_regs: + usb_put_hcd(hcd); + return ret; +} + +static void fhci_remove(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + 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 (j = 0; j < NUM_PINS; j++) + qe_pin_free(fhci->pins[j]); + fhci_dfs_destroy(fhci); + usb_put_hcd(hcd); +} + +static void of_fhci_remove(struct platform_device *ofdev) +{ + 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", + .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 000000000..5f48660eb --- /dev/null +++ b/drivers/usb/host/fhci-hub.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#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 */ + USB_DT_HUB, /* bDescriptorType;hub-descriptor */ + 0x01, /* bNbrPorts */ + HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_NO_OCPM, /* wHubCharacteristics */ + 0x00, /* per-port power, no overcurrent */ + 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) +{ + struct gpio_desc *gpiod = fhci->gpiods[gpio_nr]; + + if (!gpiod) + return; + + gpiod_set_value(gpiod, on); + 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__); + + gpiod_direction_output(fhci->gpiods[GPIO_USBOE], 0); + gpiod_direction_output(fhci->gpiods[GPIO_USBTP], 0); + gpiod_direction_output(fhci->gpiods[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; + 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)); + 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); + 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); + 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 000000000..d5951fc19 --- /dev/null +++ b/drivers/usb/host/fhci-mem.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#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_obj(*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_obj(*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 000000000..669c24052 --- /dev/null +++ b/drivers/usb/host/fhci-q.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#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 000000000..c3acd410c --- /dev/null +++ b/drivers/usb/host/fhci-sched.c @@ -0,0 +1,891 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#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; + + /* calculate 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->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_OLD(fhci_tasklet, process_done_list); + +/* transfer completed 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; + 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_endpoint_maxp(&urb->ep->desc); + 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) { + /* Ignore the possibility of underruns */ + urb->start_frame = ed->td_head ? ed->next_iso : + get_frame_num(fhci); + ed->next_iso = (urb->start_frame + urb->interval * + urb->number_of_packets) & 0x07ff; + } + + /* + * 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_endpoint_maxp(&urb->ep->desc)) == 0)) + urb_state = US_BULK0; + while (data_len > 4096) { + 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++; + } + + 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) { + 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; + 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_endpoint_maxp(&urb->ep->desc); + + /* setup stage */ + 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) { + 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) + 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 + 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; + 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 000000000..d7482dc06 --- /dev/null +++ b/drivers/usb/host/fhci-tds.c @@ -0,0 +1,619 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#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) { + 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_obj(*ep); + 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_obj(*pkt); + if (!pkt) { + err_for = "frame"; + goto err; + } + + buff = kmalloc_array(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 td_status; + struct usb_td __iomem *td; + struct endpoint *ep = usb->ep0; + + td = ep->td_base; + while (1) { + td_status = in_be16(&td->status); + in_be32(&td->buf_ptr); + 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 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); + 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); + 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 000000000..e221b28e8 --- /dev/null +++ b/drivers/usb/host/fhci.h @@ -0,0 +1,589 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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 + */ + +#ifndef __FHCI_H +#define __FHCI_H + +#include +#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]; + struct gpio_desc *gpiods[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; +#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 */ + u16 next_iso; /* time stamp of next 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 ed *ed; + struct timer_list time_out; + + struct td *tds[] __counted_by(num_of_tds); +}; + +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 000000000..7d46586e2 --- /dev/null +++ b/drivers/usb/host/fsl-mph-dr-of.c @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#include +#include +#include +#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 */ +}; + +static 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, + }, +}; + +static 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("%pOF: Invalid 'dr_mode' property, fallback to host mode\n", + np); + 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, "utmi_dual")) + return FSL_USB2_PHY_UTMI_DUAL; + if (!strcasecmp(phy_type, "serial")) + return FSL_USB2_PHY_SERIAL; + + return FSL_USB2_PHY_NONE; +} + +static 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; + + if (!pdev->dev.dma_mask) { + pdev->dev.dma_mask = &ofdev->dev.coherent_dma_mask; + } else { + retval = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (retval) + goto error; + } + + 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; + } + + device_set_of_node_from_dev(&pdev->dev, &ofdev->dev); + + 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 enum fsl_usb2_controller_ver usb_get_ver_info(struct device_node *np) +{ + enum fsl_usb2_controller_ver ver = FSL_USB_VER_NONE; + + /* + * returns 1 for usb controller version 1.6 + * returns 2 for usb controller version 2.2 + * returns 3 for usb controller version 2.4 + * returns 4 for usb controller version 2.5 + * 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 if (of_device_is_compatible(np, "fsl-usb2-dr-v2.5")) + ver = FSL_USB_VER_2_5; + else /* for previous controller versions */ + ver = FSL_USB_VER_OLD; + + if (ver > FSL_USB_VER_NONE) + 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 if (of_device_is_compatible(np, "fsl-usb2-mph-v2.4")) + ver = FSL_USB_VER_2_4; + else if (of_device_is_compatible(np, "fsl-usb2-mph-v2.5")) + ver = FSL_USB_VER_2_5; + 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, err; + + 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_property_present(np, "port0")) + pdata->port_enables |= FSL_USB2_PORT0_ENABLED; + + if (of_property_present(np, "port1")) + pdata->port_enables |= FSL_USB2_PORT1_ENABLED; + + pdata->operating_mode = FSL_USB2_MPH_HOST; + } else { + pdata->invert_drvvbus = of_property_read_bool(np, "fsl,invert-drvvbus"); + pdata->invert_pwr_fault = of_property_read_bool(np, "fsl,invert-pwr-fault"); + + /* 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); + + /* Activate Erratum by reading property in device tree */ + pdata->has_fsl_erratum_a007792 = + of_property_read_bool(np, "fsl,usb-erratum-a007792"); + pdata->has_fsl_erratum_a005275 = + of_property_read_bool(np, "fsl,usb-erratum-a005275"); + pdata->has_fsl_erratum_a005697 = + of_property_read_bool(np, "fsl,usb_erratum-a005697"); + pdata->has_fsl_erratum_a006918 = + of_property_read_bool(np, "fsl,usb_erratum-a006918"); + pdata->has_fsl_erratum_14 = + of_property_read_bool(np, "fsl,usb_erratum-14"); + + /* + * Determine whether phy_clk_valid needs to be checked + * by reading property in device tree + */ + pdata->check_phy_clk_valid = + of_property_read_bool(np, "phy-clk-valid"); + + if (pdata->have_sysif_regs) { + if (pdata->controller_ver == FSL_USB_VER_NONE) { + dev_warn(&ofdev->dev, "Could not get controller version\n"); + return -ENODEV; + } + } + + err = devm_regulator_get_enable_optional(&ofdev->dev, "vbus"); + if (err) + return dev_err_probe(&ofdev->dev, err, "failed to get vbus regulator\n"); + + 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 void fsl_usb2_mph_dr_of_remove(struct platform_device *ofdev) +{ + device_for_each_child(&ofdev->dev, NULL, __unregister_subdev); +} + +#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 */ + +static int fsl_usb2_mpc5121_init(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct clk *clk; + int err; + + clk = devm_clk_get(pdev->dev.parent, "ipg"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clk\n"); + return PTR_ERR(clk); + } + err = clk_prepare_enable(clk); + if (err) { + dev_err(&pdev->dev, "failed to enable clk\n"); + return err; + } + 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 = dev_get_platdata(&pdev->dev); + + pdata->regs = NULL; + + clk_disable_unprepare(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 + {}, +}; +MODULE_DEVICE_TABLE(of, fsl_usb2_mph_dr_of_match); + +static struct platform_driver fsl_usb2_mph_dr_driver = { + .driver = { + .name = "fsl-usb2-mph-dr", + .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/isp116x-hcd.c b/drivers/usb/host/isp116x-hcd.c new file mode 100644 index 000000000..b5e905689 --- /dev/null +++ b/drivers/usb/host/isp116x-hcd.c @@ -0,0 +1,1695 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 "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); + load = isp116x->load[index]; + if (load) { + /* 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_obj(*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); + 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)) / + 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 = USB_DT_HUB; + desc->bDescLength = 9; + desc->bHubContrCurrent = 0; + desc->bNbrPorts = (u8) (reg & 0x3); + /* Power switching, device type, overcurrent. */ + desc->wHubCharacteristics = cpu_to_le16((u16) ((reg >> 8) & + (HUB_CHAR_LPSM | + HUB_CHAR_COMPOUND | + HUB_CHAR_OCPM))); + 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); + fallthrough; + 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_debug_show(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; +} +DEFINE_SHOW_ATTRIBUTE(isp116x_debug); + +static void create_debug_file(struct isp116x *isp116x) +{ + debugfs_create_file(hcd_name, S_IRUGO, usb_debug_root, isp116x, + &isp116x_debug_fops); +} + +static void remove_debug_file(struct isp116x *isp116x) +{ + debugfs_lookup_and_remove(hcd_name, usb_debug_root); +} + +#else + +static inline void create_debug_file(struct isp116x *isp116x) { } +static inline void remove_debug_file(struct isp116x *isp116x) { } + +#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); + fallthrough; + case HCCONTROL_USB_RESET: + ret = -EBUSY; + fallthrough; + 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); + break; + 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(USB_RESUME_TIMEOUT); + + /* 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 const 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 void isp116x_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct isp116x *isp116x; + struct resource *res; + + if (!hcd) + return; + 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); + if (res) + release_mem_region(res->start, 2); + iounmap(isp116x->addr_reg); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, 2); + + usb_put_hcd(hcd); +} + +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 (!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 = dev_get_platdata(&pdev->dev); + + 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; + + device_wakeup_enable(hcd->self.controller); + + create_debug_file(isp116x); + + return 0; + + 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 = hcd_name, + }, +}; + +module_platform_driver(isp116x_driver); diff --git a/drivers/usb/host/isp116x.h b/drivers/usb/host/isp116x.h new file mode 100644 index 000000000..84904025f --- /dev/null +++ b/drivers/usb/host/isp116x.h @@ -0,0 +1,595 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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; + + 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; +}; + +/*-------------------------------------------------------------------------*/ + +#define DBG(stuff...) pr_debug("116x: " stuff) + +#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 + +static inline void isp116x_write_addr(struct isp116x *isp116x, unsigned reg) +{ + 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/max3421-hcd.c b/drivers/usb/host/max3421-hcd.c new file mode 100644 index 000000000..73e76d0e6 --- /dev/null +++ b/drivers/usb/host/max3421-hcd.c @@ -0,0 +1,1983 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MAX3421 Host Controller driver for USB. + * + * Author: David Mosberger-Tang + * + * (C) Copyright 2014 David Mosberger-Tang + * + * MAX3421 is a chip implementing a USB 2.0 Full-/Low-Speed host + * controller on a SPI bus. + * + * Based on: + * o MAX3421E datasheet + * https://datasheets.maximintegrated.com/en/ds/MAX3421E.pdf + * o MAX3421E Programming Guide + * https://www.hdl.co.jp/ftpdata/utl-001/AN3785.pdf + * o gadget/dummy_hcd.c + * For USB HCD implementation. + * o Arduino MAX3421 driver + * https://github.com/felis/USB_Host_Shield_2.0/blob/master/Usb.cpp + * + * This file is licenced under the GPL v2. + * + * Important note on worst-case (full-speed) packet size constraints + * (See USB 2.0 Section 5.6.3 and following): + * + * - control: 64 bytes + * - isochronous: 1023 bytes + * - interrupt: 64 bytes + * - bulk: 64 bytes + * + * Since the MAX3421 FIFO size is 64 bytes, we do not have to work about + * multi-FIFO writes/reads for a single USB packet *except* for isochronous + * transfers. We don't support isochronous transfers at this time, so we + * just assume that a USB packet always fits into a single FIFO buffer. + * + * NOTE: The June 2006 version of "MAX3421E Programming Guide" + * (AN3785) has conflicting info for the RCVDAVIRQ bit: + * + * The description of RCVDAVIRQ says "The CPU *must* clear + * this IRQ bit (by writing a 1 to it) before reading the + * RCVFIFO data. + * + * However, the earlier section on "Programming BULK-IN + * Transfers" says * that: + * + * After the CPU retrieves the data, it clears the + * RCVDAVIRQ bit. + * + * The December 2006 version has been corrected and it consistently + * states the second behavior is the correct one. + * + * Synchronous SPI transactions sleep so we can't perform any such + * transactions while holding a spin-lock (and/or while interrupts are + * masked). To achieve this, all SPI transactions are issued from a + * single thread (max3421_spi_thread). + */ + +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_DESC "MAX3421 USB Host-Controller Driver" +#define DRIVER_VERSION "1.0" + +/* 11-bit counter that wraps around (USB 2.0 Section 8.3.3): */ +#define USB_MAX_FRAME_NUMBER 0x7ff +#define USB_MAX_RETRIES 3 /* # of retries before error is reported */ + +#define POWER_BUDGET 500 /* in mA; use 8 for low-power port testing */ + +/* Port-change mask: */ +#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) + +#define MAX3421_GPOUT_COUNT 8 + +enum max3421_rh_state { + MAX3421_RH_RESET, + MAX3421_RH_SUSPENDED, + MAX3421_RH_RUNNING +}; + +enum pkt_state { + PKT_STATE_SETUP, /* waiting to send setup packet to ctrl pipe */ + PKT_STATE_TRANSFER, /* waiting to xfer transfer_buffer */ + PKT_STATE_TERMINATE /* waiting to terminate control transfer */ +}; + +enum scheduling_pass { + SCHED_PASS_PERIODIC, + SCHED_PASS_NON_PERIODIC, + SCHED_PASS_DONE +}; + +/* Bit numbers for max3421_hcd->todo: */ +enum { + ENABLE_IRQ = 0, + RESET_HCD, + RESET_PORT, + CHECK_UNLINK, + IOPIN_UPDATE +}; + +struct max3421_dma_buf { + u8 data[2]; +}; + +struct max3421_hcd { + spinlock_t lock; + + struct task_struct *spi_thread; + + enum max3421_rh_state rh_state; + /* lower 16 bits contain port status, upper 16 bits the change mask: */ + u32 port_status; + + unsigned active:1; + + struct list_head ep_list; /* list of EP's with work */ + + /* + * The following are owned by spi_thread (may be accessed by + * SPI-thread without acquiring the HCD lock: + */ + u8 rev; /* chip revision */ + u16 frame_number; + /* + * kmalloc'd buffers guaranteed to be in separate (DMA) + * cache-lines: + */ + struct max3421_dma_buf *tx; + struct max3421_dma_buf *rx; + /* + * URB we're currently processing. Must not be reset to NULL + * unless MAX3421E chip is idle: + */ + struct urb *curr_urb; + enum scheduling_pass sched_pass; + int urb_done; /* > 0 -> no errors, < 0: errno */ + size_t curr_len; + u8 hien; + u8 mode; + u8 iopins[2]; + unsigned long todo; +#ifdef DEBUG + unsigned long err_stat[16]; +#endif +}; + +struct max3421_ep { + struct usb_host_endpoint *ep; + struct list_head ep_list; + u32 naks; + u16 last_active; /* frame # this ep was last active */ + enum pkt_state pkt_state; + u8 retries; + u8 retransmit; /* packet needs retransmission */ +}; + +#define MAX3421_FIFO_SIZE 64 + +#define MAX3421_SPI_DIR_RD 0 /* read register from MAX3421 */ +#define MAX3421_SPI_DIR_WR 1 /* write register to MAX3421 */ + +/* SPI commands: */ +#define MAX3421_SPI_DIR_SHIFT 1 +#define MAX3421_SPI_REG_SHIFT 3 + +#define MAX3421_REG_RCVFIFO 1 +#define MAX3421_REG_SNDFIFO 2 +#define MAX3421_REG_SUDFIFO 4 +#define MAX3421_REG_RCVBC 6 +#define MAX3421_REG_SNDBC 7 +#define MAX3421_REG_USBIRQ 13 +#define MAX3421_REG_USBIEN 14 +#define MAX3421_REG_USBCTL 15 +#define MAX3421_REG_CPUCTL 16 +#define MAX3421_REG_PINCTL 17 +#define MAX3421_REG_REVISION 18 +#define MAX3421_REG_IOPINS1 20 +#define MAX3421_REG_IOPINS2 21 +#define MAX3421_REG_GPINIRQ 22 +#define MAX3421_REG_GPINIEN 23 +#define MAX3421_REG_GPINPOL 24 +#define MAX3421_REG_HIRQ 25 +#define MAX3421_REG_HIEN 26 +#define MAX3421_REG_MODE 27 +#define MAX3421_REG_PERADDR 28 +#define MAX3421_REG_HCTL 29 +#define MAX3421_REG_HXFR 30 +#define MAX3421_REG_HRSL 31 + +enum { + MAX3421_USBIRQ_OSCOKIRQ_BIT = 0, + MAX3421_USBIRQ_NOVBUSIRQ_BIT = 5, + MAX3421_USBIRQ_VBUSIRQ_BIT +}; + +enum { + MAX3421_CPUCTL_IE_BIT = 0, + MAX3421_CPUCTL_PULSEWID0_BIT = 6, + MAX3421_CPUCTL_PULSEWID1_BIT +}; + +enum { + MAX3421_USBCTL_PWRDOWN_BIT = 4, + MAX3421_USBCTL_CHIPRES_BIT +}; + +enum { + MAX3421_PINCTL_GPXA_BIT = 0, + MAX3421_PINCTL_GPXB_BIT, + MAX3421_PINCTL_POSINT_BIT, + MAX3421_PINCTL_INTLEVEL_BIT, + MAX3421_PINCTL_FDUPSPI_BIT, + MAX3421_PINCTL_EP0INAK_BIT, + MAX3421_PINCTL_EP2INAK_BIT, + MAX3421_PINCTL_EP3INAK_BIT, +}; + +enum { + MAX3421_HI_BUSEVENT_BIT = 0, /* bus-reset/-resume */ + MAX3421_HI_RWU_BIT, /* remote wakeup */ + MAX3421_HI_RCVDAV_BIT, /* receive FIFO data available */ + MAX3421_HI_SNDBAV_BIT, /* send buffer available */ + MAX3421_HI_SUSDN_BIT, /* suspend operation done */ + MAX3421_HI_CONDET_BIT, /* peripheral connect/disconnect */ + MAX3421_HI_FRAME_BIT, /* frame generator */ + MAX3421_HI_HXFRDN_BIT, /* host transfer done */ +}; + +enum { + MAX3421_HCTL_BUSRST_BIT = 0, + MAX3421_HCTL_FRMRST_BIT, + MAX3421_HCTL_SAMPLEBUS_BIT, + MAX3421_HCTL_SIGRSM_BIT, + MAX3421_HCTL_RCVTOG0_BIT, + MAX3421_HCTL_RCVTOG1_BIT, + MAX3421_HCTL_SNDTOG0_BIT, + MAX3421_HCTL_SNDTOG1_BIT +}; + +enum { + MAX3421_MODE_HOST_BIT = 0, + MAX3421_MODE_LOWSPEED_BIT, + MAX3421_MODE_HUBPRE_BIT, + MAX3421_MODE_SOFKAENAB_BIT, + MAX3421_MODE_SEPIRQ_BIT, + MAX3421_MODE_DELAYISO_BIT, + MAX3421_MODE_DMPULLDN_BIT, + MAX3421_MODE_DPPULLDN_BIT +}; + +enum { + MAX3421_HRSL_OK = 0, + MAX3421_HRSL_BUSY, + MAX3421_HRSL_BADREQ, + MAX3421_HRSL_UNDEF, + MAX3421_HRSL_NAK, + MAX3421_HRSL_STALL, + MAX3421_HRSL_TOGERR, + MAX3421_HRSL_WRONGPID, + MAX3421_HRSL_BADBC, + MAX3421_HRSL_PIDERR, + MAX3421_HRSL_PKTERR, + MAX3421_HRSL_CRCERR, + MAX3421_HRSL_KERR, + MAX3421_HRSL_JERR, + MAX3421_HRSL_TIMEOUT, + MAX3421_HRSL_BABBLE, + MAX3421_HRSL_RESULT_MASK = 0xf, + MAX3421_HRSL_RCVTOGRD_BIT = 4, + MAX3421_HRSL_SNDTOGRD_BIT, + MAX3421_HRSL_KSTATUS_BIT, + MAX3421_HRSL_JSTATUS_BIT +}; + +/* Return same error-codes as ohci.h:cc_to_error: */ +static const int hrsl_to_error[] = { + [MAX3421_HRSL_OK] = 0, + [MAX3421_HRSL_BUSY] = -EINVAL, + [MAX3421_HRSL_BADREQ] = -EINVAL, + [MAX3421_HRSL_UNDEF] = -EINVAL, + [MAX3421_HRSL_NAK] = -EAGAIN, + [MAX3421_HRSL_STALL] = -EPIPE, + [MAX3421_HRSL_TOGERR] = -EILSEQ, + [MAX3421_HRSL_WRONGPID] = -EPROTO, + [MAX3421_HRSL_BADBC] = -EREMOTEIO, + [MAX3421_HRSL_PIDERR] = -EPROTO, + [MAX3421_HRSL_PKTERR] = -EPROTO, + [MAX3421_HRSL_CRCERR] = -EILSEQ, + [MAX3421_HRSL_KERR] = -EIO, + [MAX3421_HRSL_JERR] = -EIO, + [MAX3421_HRSL_TIMEOUT] = -ETIME, + [MAX3421_HRSL_BABBLE] = -EOVERFLOW +}; + +/* + * See https://www.beyondlogic.org/usbnutshell/usb4.shtml#Control for a + * reasonable overview of how control transfers use the IN/OUT + * tokens. + */ +#define MAX3421_HXFR_BULK_IN(ep) (0x00 | (ep)) /* bulk or interrupt */ +#define MAX3421_HXFR_SETUP 0x10 +#define MAX3421_HXFR_BULK_OUT(ep) (0x20 | (ep)) /* bulk or interrupt */ +#define MAX3421_HXFR_ISO_IN(ep) (0x40 | (ep)) +#define MAX3421_HXFR_ISO_OUT(ep) (0x60 | (ep)) +#define MAX3421_HXFR_HS_IN 0x80 /* handshake in */ +#define MAX3421_HXFR_HS_OUT 0xa0 /* handshake out */ + +#define field(val, bit) ((val) << (bit)) + +static inline s16 +frame_diff(u16 left, u16 right) +{ + return ((unsigned) (left - right)) % (USB_MAX_FRAME_NUMBER + 1); +} + +static inline struct max3421_hcd * +hcd_to_max3421(struct usb_hcd *hcd) +{ + return (struct max3421_hcd *) hcd->hcd_priv; +} + +static inline struct usb_hcd * +max3421_to_hcd(struct max3421_hcd *max3421_hcd) +{ + return container_of((void *) max3421_hcd, struct usb_hcd, hcd_priv); +} + +static u8 +spi_rd8(struct usb_hcd *hcd, unsigned int reg) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + struct spi_device *spi = to_spi_device(hcd->self.controller); + struct spi_transfer transfer; + struct spi_message msg; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + max3421_hcd->tx->data[0] = + (field(reg, MAX3421_SPI_REG_SHIFT) | + field(MAX3421_SPI_DIR_RD, MAX3421_SPI_DIR_SHIFT)); + + transfer.tx_buf = max3421_hcd->tx->data; + transfer.rx_buf = max3421_hcd->rx->data; + transfer.len = 2; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); + + return max3421_hcd->rx->data[1]; +} + +static void +spi_wr8(struct usb_hcd *hcd, unsigned int reg, u8 val) +{ + struct spi_device *spi = to_spi_device(hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + struct spi_transfer transfer; + struct spi_message msg; + + memset(&transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + max3421_hcd->tx->data[0] = + (field(reg, MAX3421_SPI_REG_SHIFT) | + field(MAX3421_SPI_DIR_WR, MAX3421_SPI_DIR_SHIFT)); + max3421_hcd->tx->data[1] = val; + + transfer.tx_buf = max3421_hcd->tx->data; + transfer.len = 2; + + spi_message_add_tail(&transfer, &msg); + spi_sync(spi, &msg); +} + +static void +spi_rd_buf(struct usb_hcd *hcd, unsigned int reg, void *buf, size_t len) +{ + struct spi_device *spi = to_spi_device(hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + struct spi_transfer transfer[2]; + struct spi_message msg; + + memset(transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + max3421_hcd->tx->data[0] = + (field(reg, MAX3421_SPI_REG_SHIFT) | + field(MAX3421_SPI_DIR_RD, MAX3421_SPI_DIR_SHIFT)); + transfer[0].tx_buf = max3421_hcd->tx->data; + transfer[0].len = 1; + + transfer[1].rx_buf = buf; + transfer[1].len = len; + + spi_message_add_tail(&transfer[0], &msg); + spi_message_add_tail(&transfer[1], &msg); + spi_sync(spi, &msg); +} + +static void +spi_wr_buf(struct usb_hcd *hcd, unsigned int reg, void *buf, size_t len) +{ + struct spi_device *spi = to_spi_device(hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + struct spi_transfer transfer[2]; + struct spi_message msg; + + memset(transfer, 0, sizeof(transfer)); + + spi_message_init(&msg); + + max3421_hcd->tx->data[0] = + (field(reg, MAX3421_SPI_REG_SHIFT) | + field(MAX3421_SPI_DIR_WR, MAX3421_SPI_DIR_SHIFT)); + + transfer[0].tx_buf = max3421_hcd->tx->data; + transfer[0].len = 1; + + transfer[1].tx_buf = buf; + transfer[1].len = len; + + spi_message_add_tail(&transfer[0], &msg); + spi_message_add_tail(&transfer[1], &msg); + spi_sync(spi, &msg); +} + +/* + * Figure out the correct setting for the LOWSPEED and HUBPRE mode + * bits. The HUBPRE bit needs to be set when MAX3421E operates at + * full speed, but it's talking to a low-speed device (i.e., through a + * hub). Setting that bit ensures that every low-speed packet is + * preceded by a full-speed PRE PID. Possible configurations: + * + * Hub speed: Device speed: => LOWSPEED bit: HUBPRE bit: + * FULL FULL => 0 0 + * FULL LOW => 1 1 + * LOW LOW => 1 0 + * LOW FULL => 1 0 + */ +static void +max3421_set_speed(struct usb_hcd *hcd, struct usb_device *dev) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + u8 mode_lowspeed, mode_hubpre, mode = max3421_hcd->mode; + + mode_lowspeed = BIT(MAX3421_MODE_LOWSPEED_BIT); + mode_hubpre = BIT(MAX3421_MODE_HUBPRE_BIT); + if (max3421_hcd->port_status & USB_PORT_STAT_LOW_SPEED) { + mode |= mode_lowspeed; + mode &= ~mode_hubpre; + } else if (dev->speed == USB_SPEED_LOW) { + mode |= mode_lowspeed | mode_hubpre; + } else { + mode &= ~(mode_lowspeed | mode_hubpre); + } + if (mode != max3421_hcd->mode) { + max3421_hcd->mode = mode; + spi_wr8(hcd, MAX3421_REG_MODE, max3421_hcd->mode); + } + +} + +/* + * Caller must NOT hold HCD spinlock. + */ +static void +max3421_set_address(struct usb_hcd *hcd, struct usb_device *dev, int epnum) +{ + int rcvtog, sndtog; + u8 hctl; + + /* setup new endpoint's toggle bits: */ + rcvtog = usb_gettoggle(dev, epnum, 0); + sndtog = usb_gettoggle(dev, epnum, 1); + hctl = (BIT(rcvtog + MAX3421_HCTL_RCVTOG0_BIT) | + BIT(sndtog + MAX3421_HCTL_SNDTOG0_BIT)); + + spi_wr8(hcd, MAX3421_REG_HCTL, hctl); + + /* + * Note: devnum for one and the same device can change during + * address-assignment so it's best to just always load the + * address whenever the end-point changed/was forced. + */ + spi_wr8(hcd, MAX3421_REG_PERADDR, dev->devnum); +} + +static int +max3421_ctrl_setup(struct usb_hcd *hcd, struct urb *urb) +{ + spi_wr_buf(hcd, MAX3421_REG_SUDFIFO, urb->setup_packet, 8); + return MAX3421_HXFR_SETUP; +} + +static int +max3421_transfer_in(struct usb_hcd *hcd, struct urb *urb) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + int epnum = usb_pipeendpoint(urb->pipe); + + max3421_hcd->curr_len = 0; + max3421_hcd->hien |= BIT(MAX3421_HI_RCVDAV_BIT); + return MAX3421_HXFR_BULK_IN(epnum); +} + +static int +max3421_transfer_out(struct usb_hcd *hcd, struct urb *urb, int fast_retransmit) +{ + struct spi_device *spi = to_spi_device(hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + int epnum = usb_pipeendpoint(urb->pipe); + u32 max_packet; + void *src; + + src = urb->transfer_buffer + urb->actual_length; + + if (fast_retransmit) { + if (max3421_hcd->rev == 0x12) { + /* work around rev 0x12 bug: */ + spi_wr8(hcd, MAX3421_REG_SNDBC, 0); + spi_wr8(hcd, MAX3421_REG_SNDFIFO, ((u8 *) src)[0]); + spi_wr8(hcd, MAX3421_REG_SNDBC, max3421_hcd->curr_len); + } + return MAX3421_HXFR_BULK_OUT(epnum); + } + + max_packet = usb_maxpacket(urb->dev, urb->pipe); + + if (max_packet > MAX3421_FIFO_SIZE) { + /* + * We do not support isochronous transfers at this + * time. + */ + dev_err(&spi->dev, + "%s: packet-size of %u too big (limit is %u bytes)", + __func__, max_packet, MAX3421_FIFO_SIZE); + max3421_hcd->urb_done = -EMSGSIZE; + return -EMSGSIZE; + } + max3421_hcd->curr_len = min((urb->transfer_buffer_length - + urb->actual_length), max_packet); + + spi_wr_buf(hcd, MAX3421_REG_SNDFIFO, src, max3421_hcd->curr_len); + spi_wr8(hcd, MAX3421_REG_SNDBC, max3421_hcd->curr_len); + return MAX3421_HXFR_BULK_OUT(epnum); +} + +/* + * Issue the next host-transfer command. + * Caller must NOT hold HCD spinlock. + */ +static void +max3421_next_transfer(struct usb_hcd *hcd, int fast_retransmit) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + struct urb *urb = max3421_hcd->curr_urb; + struct max3421_ep *max3421_ep; + int cmd = -EINVAL; + + if (!urb) + return; /* nothing to do */ + + max3421_ep = urb->ep->hcpriv; + + switch (max3421_ep->pkt_state) { + case PKT_STATE_SETUP: + cmd = max3421_ctrl_setup(hcd, urb); + break; + + case PKT_STATE_TRANSFER: + if (usb_urb_dir_in(urb)) + cmd = max3421_transfer_in(hcd, urb); + else + cmd = max3421_transfer_out(hcd, urb, fast_retransmit); + break; + + case PKT_STATE_TERMINATE: + /* + * IN transfers are terminated with HS_OUT token, + * OUT transfers with HS_IN: + */ + if (usb_urb_dir_in(urb)) + cmd = MAX3421_HXFR_HS_OUT; + else + cmd = MAX3421_HXFR_HS_IN; + break; + } + + if (cmd < 0) + return; + + /* issue the command and wait for host-xfer-done interrupt: */ + + spi_wr8(hcd, MAX3421_REG_HXFR, cmd); + max3421_hcd->hien |= BIT(MAX3421_HI_HXFRDN_BIT); +} + +/* + * Find the next URB to process and start its execution. + * + * At this time, we do not anticipate ever connecting a USB hub to the + * MAX3421 chip, so at most USB device can be connected and we can use + * a simplistic scheduler: at the start of a frame, schedule all + * periodic transfers. Once that is done, use the remainder of the + * frame to process non-periodic (bulk & control) transfers. + * + * Preconditions: + * o Caller must NOT hold HCD spinlock. + * o max3421_hcd->curr_urb MUST BE NULL. + * o MAX3421E chip must be idle. + */ +static int +max3421_select_and_start_urb(struct usb_hcd *hcd) +{ + struct spi_device *spi = to_spi_device(hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + struct urb *urb, *curr_urb = NULL; + struct max3421_ep *max3421_ep; + int epnum; + struct usb_host_endpoint *ep; + struct list_head *pos; + unsigned long flags; + + spin_lock_irqsave(&max3421_hcd->lock, flags); + + for (; + max3421_hcd->sched_pass < SCHED_PASS_DONE; + ++max3421_hcd->sched_pass) + list_for_each(pos, &max3421_hcd->ep_list) { + urb = NULL; + max3421_ep = container_of(pos, struct max3421_ep, + ep_list); + ep = max3421_ep->ep; + + switch (usb_endpoint_type(&ep->desc)) { + case USB_ENDPOINT_XFER_ISOC: + case USB_ENDPOINT_XFER_INT: + if (max3421_hcd->sched_pass != + SCHED_PASS_PERIODIC) + continue; + break; + + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + if (max3421_hcd->sched_pass != + SCHED_PASS_NON_PERIODIC) + continue; + break; + } + + if (list_empty(&ep->urb_list)) + continue; /* nothing to do */ + urb = list_first_entry(&ep->urb_list, struct urb, + urb_list); + if (urb->unlinked) { + dev_dbg(&spi->dev, "%s: URB %p unlinked=%d", + __func__, urb, urb->unlinked); + max3421_hcd->curr_urb = urb; + max3421_hcd->urb_done = 1; + spin_unlock_irqrestore(&max3421_hcd->lock, + flags); + return 1; + } + + switch (usb_endpoint_type(&ep->desc)) { + case USB_ENDPOINT_XFER_CONTROL: + /* + * Allow one control transaction per + * frame per endpoint: + */ + if (frame_diff(max3421_ep->last_active, + max3421_hcd->frame_number) == 0) + continue; + break; + + case USB_ENDPOINT_XFER_BULK: + if (max3421_ep->retransmit + && (frame_diff(max3421_ep->last_active, + max3421_hcd->frame_number) + == 0)) + /* + * We already tried this EP + * during this frame and got a + * NAK or error; wait for next frame + */ + continue; + break; + + case USB_ENDPOINT_XFER_ISOC: + case USB_ENDPOINT_XFER_INT: + if (frame_diff(max3421_hcd->frame_number, + max3421_ep->last_active) + < urb->interval) + /* + * We already processed this + * end-point in the current + * frame + */ + continue; + break; + } + + /* move current ep to tail: */ + list_move_tail(pos, &max3421_hcd->ep_list); + curr_urb = urb; + goto done; + } +done: + if (!curr_urb) { + spin_unlock_irqrestore(&max3421_hcd->lock, flags); + return 0; + } + + urb = max3421_hcd->curr_urb = curr_urb; + epnum = usb_endpoint_num(&urb->ep->desc); + if (max3421_ep->retransmit) + /* restart (part of) a USB transaction: */ + max3421_ep->retransmit = 0; + else { + /* start USB transaction: */ + if (usb_endpoint_xfer_control(&ep->desc)) { + /* + * See USB 2.0 spec section 8.6.1 + * Initialization via SETUP Token: + */ + usb_settoggle(urb->dev, epnum, 0, 1); + usb_settoggle(urb->dev, epnum, 1, 1); + max3421_ep->pkt_state = PKT_STATE_SETUP; + } else + max3421_ep->pkt_state = PKT_STATE_TRANSFER; + } + + spin_unlock_irqrestore(&max3421_hcd->lock, flags); + + max3421_ep->last_active = max3421_hcd->frame_number; + max3421_set_address(hcd, urb->dev, epnum); + max3421_set_speed(hcd, urb->dev); + max3421_next_transfer(hcd, 0); + return 1; +} + +/* + * Check all endpoints for URBs that got unlinked. + * + * Caller must NOT hold HCD spinlock. + */ +static int +max3421_check_unlink(struct usb_hcd *hcd) +{ + struct spi_device *spi = to_spi_device(hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + struct max3421_ep *max3421_ep; + struct usb_host_endpoint *ep; + struct urb *urb, *next; + unsigned long flags; + int retval = 0; + + spin_lock_irqsave(&max3421_hcd->lock, flags); + list_for_each_entry(max3421_ep, &max3421_hcd->ep_list, ep_list) { + ep = max3421_ep->ep; + list_for_each_entry_safe(urb, next, &ep->urb_list, urb_list) { + if (urb->unlinked) { + retval = 1; + dev_dbg(&spi->dev, "%s: URB %p unlinked=%d", + __func__, urb, urb->unlinked); + if (urb == max3421_hcd->curr_urb) { + max3421_hcd->urb_done = 1; + max3421_hcd->hien &= ~(BIT(MAX3421_HI_HXFRDN_BIT) | + BIT(MAX3421_HI_RCVDAV_BIT)); + } else { + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock_irqrestore(&max3421_hcd->lock, + flags); + usb_hcd_giveback_urb(hcd, urb, 0); + spin_lock_irqsave(&max3421_hcd->lock, flags); + } + } + } + } + spin_unlock_irqrestore(&max3421_hcd->lock, flags); + return retval; +} + +/* + * Caller must NOT hold HCD spinlock. + */ +static void +max3421_slow_retransmit(struct usb_hcd *hcd) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + struct urb *urb = max3421_hcd->curr_urb; + struct max3421_ep *max3421_ep; + + max3421_ep = urb->ep->hcpriv; + max3421_ep->retransmit = 1; + max3421_hcd->curr_urb = NULL; +} + +/* + * Caller must NOT hold HCD spinlock. + */ +static void +max3421_recv_data_available(struct usb_hcd *hcd) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + struct urb *urb = max3421_hcd->curr_urb; + size_t remaining, transfer_size; + u8 rcvbc; + + rcvbc = spi_rd8(hcd, MAX3421_REG_RCVBC); + + if (rcvbc > MAX3421_FIFO_SIZE) + rcvbc = MAX3421_FIFO_SIZE; + if (urb->actual_length >= urb->transfer_buffer_length) + remaining = 0; + else + remaining = urb->transfer_buffer_length - urb->actual_length; + transfer_size = rcvbc; + if (transfer_size > remaining) + transfer_size = remaining; + if (transfer_size > 0) { + void *dst = urb->transfer_buffer + urb->actual_length; + + spi_rd_buf(hcd, MAX3421_REG_RCVFIFO, dst, transfer_size); + urb->actual_length += transfer_size; + max3421_hcd->curr_len = transfer_size; + } + + /* ack the RCVDAV irq now that the FIFO has been read: */ + spi_wr8(hcd, MAX3421_REG_HIRQ, BIT(MAX3421_HI_RCVDAV_BIT)); +} + +static void +max3421_handle_error(struct usb_hcd *hcd, u8 hrsl) +{ + struct spi_device *spi = to_spi_device(hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + u8 result_code = hrsl & MAX3421_HRSL_RESULT_MASK; + struct urb *urb = max3421_hcd->curr_urb; + struct max3421_ep *max3421_ep = urb->ep->hcpriv; + int switch_sndfifo; + + /* + * If an OUT command results in any response other than OK + * (i.e., error or NAK), we have to perform a dummy-write to + * SNDBC so the FIFO gets switched back to us. Otherwise, we + * get out of sync with the SNDFIFO double buffer. + */ + switch_sndfifo = (max3421_ep->pkt_state == PKT_STATE_TRANSFER && + usb_urb_dir_out(urb)); + + switch (result_code) { + case MAX3421_HRSL_OK: + return; /* this shouldn't happen */ + + case MAX3421_HRSL_WRONGPID: /* received wrong PID */ + case MAX3421_HRSL_BUSY: /* SIE busy */ + case MAX3421_HRSL_BADREQ: /* bad val in HXFR */ + case MAX3421_HRSL_UNDEF: /* reserved */ + case MAX3421_HRSL_KERR: /* K-state instead of response */ + case MAX3421_HRSL_JERR: /* J-state instead of response */ + /* + * packet experienced an error that we cannot recover + * from; report error + */ + max3421_hcd->urb_done = hrsl_to_error[result_code]; + dev_dbg(&spi->dev, "%s: unexpected error HRSL=0x%02x", + __func__, hrsl); + break; + + case MAX3421_HRSL_TOGERR: + if (usb_urb_dir_in(urb)) + ; /* don't do anything (device will switch toggle) */ + else { + /* flip the send toggle bit: */ + int sndtog = (hrsl >> MAX3421_HRSL_SNDTOGRD_BIT) & 1; + + sndtog ^= 1; + spi_wr8(hcd, MAX3421_REG_HCTL, + BIT(sndtog + MAX3421_HCTL_SNDTOG0_BIT)); + } + fallthrough; + case MAX3421_HRSL_BADBC: /* bad byte count */ + case MAX3421_HRSL_PIDERR: /* received PID is corrupted */ + case MAX3421_HRSL_PKTERR: /* packet error (stuff, EOP) */ + case MAX3421_HRSL_CRCERR: /* CRC error */ + case MAX3421_HRSL_BABBLE: /* device talked too long */ + case MAX3421_HRSL_TIMEOUT: + if (max3421_ep->retries++ < USB_MAX_RETRIES) + /* retry the packet again in the next frame */ + max3421_slow_retransmit(hcd); + else { + /* Based on ohci.h cc_to_err[]: */ + max3421_hcd->urb_done = hrsl_to_error[result_code]; + dev_dbg(&spi->dev, "%s: unexpected error HRSL=0x%02x", + __func__, hrsl); + } + break; + + case MAX3421_HRSL_STALL: + dev_dbg(&spi->dev, "%s: unexpected error HRSL=0x%02x", + __func__, hrsl); + max3421_hcd->urb_done = hrsl_to_error[result_code]; + break; + + case MAX3421_HRSL_NAK: + /* + * Device wasn't ready for data or has no data + * available: retry the packet again. + */ + max3421_next_transfer(hcd, 1); + switch_sndfifo = 0; + break; + } + if (switch_sndfifo) + spi_wr8(hcd, MAX3421_REG_SNDBC, 0); +} + +/* + * Caller must NOT hold HCD spinlock. + */ +static int +max3421_transfer_in_done(struct usb_hcd *hcd, struct urb *urb) +{ + struct spi_device *spi = to_spi_device(hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + u32 max_packet; + + if (urb->actual_length >= urb->transfer_buffer_length) + return 1; /* read is complete, so we're done */ + + /* + * USB 2.0 Section 5.3.2 Pipes: packets must be full size + * except for last one. + */ + max_packet = usb_maxpacket(urb->dev, urb->pipe); + if (max_packet > MAX3421_FIFO_SIZE) { + /* + * We do not support isochronous transfers at this + * time... + */ + dev_err(&spi->dev, + "%s: packet-size of %u too big (limit is %u bytes)", + __func__, max_packet, MAX3421_FIFO_SIZE); + return -EINVAL; + } + + if (max3421_hcd->curr_len < max_packet) { + if (urb->transfer_flags & URB_SHORT_NOT_OK) { + /* + * remaining > 0 and received an + * unexpected partial packet -> + * error + */ + return -EREMOTEIO; + } else + /* short read, but it's OK */ + return 1; + } + return 0; /* not done */ +} + +/* + * Caller must NOT hold HCD spinlock. + */ +static int +max3421_transfer_out_done(struct usb_hcd *hcd, struct urb *urb) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + + urb->actual_length += max3421_hcd->curr_len; + if (urb->actual_length < urb->transfer_buffer_length) + return 0; + if (urb->transfer_flags & URB_ZERO_PACKET) { + /* + * Some hardware needs a zero-size packet at the end + * of a bulk-out transfer if the last transfer was a + * full-sized packet (i.e., such hardware use < + * max_packet as an indicator that the end of the + * packet has been reached). + */ + u32 max_packet = usb_maxpacket(urb->dev, urb->pipe); + + if (max3421_hcd->curr_len == max_packet) + return 0; + } + return 1; +} + +/* + * Caller must NOT hold HCD spinlock. + */ +static void +max3421_host_transfer_done(struct usb_hcd *hcd) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + struct urb *urb = max3421_hcd->curr_urb; + struct max3421_ep *max3421_ep; + u8 result_code, hrsl; + int urb_done = 0; + + max3421_hcd->hien &= ~(BIT(MAX3421_HI_HXFRDN_BIT) | + BIT(MAX3421_HI_RCVDAV_BIT)); + + hrsl = spi_rd8(hcd, MAX3421_REG_HRSL); + result_code = hrsl & MAX3421_HRSL_RESULT_MASK; + +#ifdef DEBUG + ++max3421_hcd->err_stat[result_code]; +#endif + + max3421_ep = urb->ep->hcpriv; + + if (unlikely(result_code != MAX3421_HRSL_OK)) { + max3421_handle_error(hcd, hrsl); + return; + } + + max3421_ep->naks = 0; + max3421_ep->retries = 0; + switch (max3421_ep->pkt_state) { + + case PKT_STATE_SETUP: + if (urb->transfer_buffer_length > 0) + max3421_ep->pkt_state = PKT_STATE_TRANSFER; + else + max3421_ep->pkt_state = PKT_STATE_TERMINATE; + break; + + case PKT_STATE_TRANSFER: + if (usb_urb_dir_in(urb)) + urb_done = max3421_transfer_in_done(hcd, urb); + else + urb_done = max3421_transfer_out_done(hcd, urb); + if (urb_done > 0 && usb_pipetype(urb->pipe) == PIPE_CONTROL) { + /* + * We aren't really done - we still need to + * terminate the control transfer: + */ + max3421_hcd->urb_done = urb_done = 0; + max3421_ep->pkt_state = PKT_STATE_TERMINATE; + } + break; + + case PKT_STATE_TERMINATE: + urb_done = 1; + break; + } + + if (urb_done) + max3421_hcd->urb_done = urb_done; + else + max3421_next_transfer(hcd, 0); +} + +/* + * Caller must NOT hold HCD spinlock. + */ +static void +max3421_detect_conn(struct usb_hcd *hcd) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + unsigned int jk, have_conn = 0; + u32 old_port_status, chg; + unsigned long flags; + u8 hrsl, mode; + + hrsl = spi_rd8(hcd, MAX3421_REG_HRSL); + + jk = ((((hrsl >> MAX3421_HRSL_JSTATUS_BIT) & 1) << 0) | + (((hrsl >> MAX3421_HRSL_KSTATUS_BIT) & 1) << 1)); + + mode = max3421_hcd->mode; + + switch (jk) { + case 0x0: /* SE0: disconnect */ + /* + * Turn off SOFKAENAB bit to avoid getting interrupt + * every milli-second: + */ + mode &= ~BIT(MAX3421_MODE_SOFKAENAB_BIT); + break; + + case 0x1: /* J=0,K=1: low-speed (in full-speed or vice versa) */ + case 0x2: /* J=1,K=0: full-speed (in full-speed or vice versa) */ + if (jk == 0x2) + /* need to switch to the other speed: */ + mode ^= BIT(MAX3421_MODE_LOWSPEED_BIT); + /* turn on SOFKAENAB bit: */ + mode |= BIT(MAX3421_MODE_SOFKAENAB_BIT); + have_conn = 1; + break; + + case 0x3: /* illegal */ + break; + } + + max3421_hcd->mode = mode; + spi_wr8(hcd, MAX3421_REG_MODE, max3421_hcd->mode); + + spin_lock_irqsave(&max3421_hcd->lock, flags); + old_port_status = max3421_hcd->port_status; + if (have_conn) + max3421_hcd->port_status |= USB_PORT_STAT_CONNECTION; + else + max3421_hcd->port_status &= ~USB_PORT_STAT_CONNECTION; + if (mode & BIT(MAX3421_MODE_LOWSPEED_BIT)) + max3421_hcd->port_status |= USB_PORT_STAT_LOW_SPEED; + else + max3421_hcd->port_status &= ~USB_PORT_STAT_LOW_SPEED; + chg = (old_port_status ^ max3421_hcd->port_status); + max3421_hcd->port_status |= chg << 16; + spin_unlock_irqrestore(&max3421_hcd->lock, flags); +} + +static irqreturn_t +max3421_irq_handler(int irq, void *dev_id) +{ + struct usb_hcd *hcd = dev_id; + struct spi_device *spi = to_spi_device(hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + + if (max3421_hcd->spi_thread) + wake_up_process(max3421_hcd->spi_thread); + if (!test_and_set_bit(ENABLE_IRQ, &max3421_hcd->todo)) + disable_irq_nosync(spi->irq); + return IRQ_HANDLED; +} + +#ifdef DEBUG + +static void +dump_eps(struct usb_hcd *hcd) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + struct max3421_ep *max3421_ep; + struct usb_host_endpoint *ep; + char ubuf[512], *dp, *end; + unsigned long flags; + struct urb *urb; + int epnum, ret; + + spin_lock_irqsave(&max3421_hcd->lock, flags); + list_for_each_entry(max3421_ep, &max3421_hcd->ep_list, ep_list) { + ep = max3421_ep->ep; + + dp = ubuf; + end = dp + sizeof(ubuf); + *dp = '\0'; + list_for_each_entry(urb, &ep->urb_list, urb_list) { + ret = scnprintf(dp, end - dp, " %p(%d.%s %d/%d)", urb, + usb_pipetype(urb->pipe), + usb_urb_dir_in(urb) ? "IN" : "OUT", + urb->actual_length, + urb->transfer_buffer_length); + if (ret == end - dp - 1) + break; /* error or buffer full */ + dp += ret; + } + + epnum = usb_endpoint_num(&ep->desc); + pr_info("EP%0u %u lst %04u rtr %u nak %6u rxmt %u: %s\n", + epnum, max3421_ep->pkt_state, max3421_ep->last_active, + max3421_ep->retries, max3421_ep->naks, + max3421_ep->retransmit, ubuf); + } + spin_unlock_irqrestore(&max3421_hcd->lock, flags); +} + +#endif /* DEBUG */ + +/* Return zero if no work was performed, 1 otherwise. */ +static int +max3421_handle_irqs(struct usb_hcd *hcd) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + u32 chg, old_port_status; + unsigned long flags; + u8 hirq; + + /* + * Read and ack pending interrupts (CPU must never + * clear SNDBAV directly and RCVDAV must be cleared by + * max3421_recv_data_available()!): + */ + hirq = spi_rd8(hcd, MAX3421_REG_HIRQ); + hirq &= max3421_hcd->hien; + if (!hirq) + return 0; + + spi_wr8(hcd, MAX3421_REG_HIRQ, + hirq & ~(BIT(MAX3421_HI_SNDBAV_BIT) | + BIT(MAX3421_HI_RCVDAV_BIT))); + + if (hirq & BIT(MAX3421_HI_FRAME_BIT)) { + max3421_hcd->frame_number = ((max3421_hcd->frame_number + 1) + & USB_MAX_FRAME_NUMBER); + max3421_hcd->sched_pass = SCHED_PASS_PERIODIC; + } + + if (hirq & BIT(MAX3421_HI_RCVDAV_BIT)) + max3421_recv_data_available(hcd); + + if (hirq & BIT(MAX3421_HI_HXFRDN_BIT)) + max3421_host_transfer_done(hcd); + + if (hirq & BIT(MAX3421_HI_CONDET_BIT)) + max3421_detect_conn(hcd); + + /* + * Now process interrupts that may affect HCD state + * other than the end-points: + */ + spin_lock_irqsave(&max3421_hcd->lock, flags); + + old_port_status = max3421_hcd->port_status; + if (hirq & BIT(MAX3421_HI_BUSEVENT_BIT)) { + if (max3421_hcd->port_status & USB_PORT_STAT_RESET) { + /* BUSEVENT due to completion of Bus Reset */ + max3421_hcd->port_status &= ~USB_PORT_STAT_RESET; + max3421_hcd->port_status |= USB_PORT_STAT_ENABLE; + } else { + /* BUSEVENT due to completion of Bus Resume */ + pr_info("%s: BUSEVENT Bus Resume Done\n", __func__); + } + } + if (hirq & BIT(MAX3421_HI_RWU_BIT)) + pr_info("%s: RWU\n", __func__); + if (hirq & BIT(MAX3421_HI_SUSDN_BIT)) + pr_info("%s: SUSDN\n", __func__); + + chg = (old_port_status ^ max3421_hcd->port_status); + max3421_hcd->port_status |= chg << 16; + + spin_unlock_irqrestore(&max3421_hcd->lock, flags); + +#ifdef DEBUG + { + static unsigned long last_time; + char sbuf[16 * 16], *dp, *end; + int i; + + if (time_after(jiffies, last_time + 5*HZ)) { + dp = sbuf; + end = sbuf + sizeof(sbuf); + *dp = '\0'; + for (i = 0; i < 16; ++i) { + int ret = scnprintf(dp, end - dp, " %lu", + max3421_hcd->err_stat[i]); + if (ret == end - dp - 1) + break; /* error or buffer full */ + dp += ret; + } + pr_info("%s: hrsl_stats %s\n", __func__, sbuf); + memset(max3421_hcd->err_stat, 0, + sizeof(max3421_hcd->err_stat)); + last_time = jiffies; + + dump_eps(hcd); + } + } +#endif + return 1; +} + +static int +max3421_reset_hcd(struct usb_hcd *hcd) +{ + struct spi_device *spi = to_spi_device(hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + int timeout; + + /* perform a chip reset and wait for OSCIRQ signal to appear: */ + spi_wr8(hcd, MAX3421_REG_USBCTL, BIT(MAX3421_USBCTL_CHIPRES_BIT)); + /* clear reset: */ + spi_wr8(hcd, MAX3421_REG_USBCTL, 0); + timeout = 1000; + while (1) { + if (spi_rd8(hcd, MAX3421_REG_USBIRQ) + & BIT(MAX3421_USBIRQ_OSCOKIRQ_BIT)) + break; + if (--timeout < 0) { + dev_err(&spi->dev, + "timed out waiting for oscillator OK signal"); + return 1; + } + cond_resched(); + } + + /* + * Turn on host mode, automatic generation of SOF packets, and + * enable pull-down registers on DM/DP: + */ + max3421_hcd->mode = (BIT(MAX3421_MODE_HOST_BIT) | + BIT(MAX3421_MODE_SOFKAENAB_BIT) | + BIT(MAX3421_MODE_DMPULLDN_BIT) | + BIT(MAX3421_MODE_DPPULLDN_BIT)); + spi_wr8(hcd, MAX3421_REG_MODE, max3421_hcd->mode); + + /* reset frame-number: */ + max3421_hcd->frame_number = USB_MAX_FRAME_NUMBER; + spi_wr8(hcd, MAX3421_REG_HCTL, BIT(MAX3421_HCTL_FRMRST_BIT)); + + /* sample the state of the D+ and D- lines */ + spi_wr8(hcd, MAX3421_REG_HCTL, BIT(MAX3421_HCTL_SAMPLEBUS_BIT)); + max3421_detect_conn(hcd); + + /* enable frame, connection-detected, and bus-event interrupts: */ + max3421_hcd->hien = (BIT(MAX3421_HI_FRAME_BIT) | + BIT(MAX3421_HI_CONDET_BIT) | + BIT(MAX3421_HI_BUSEVENT_BIT)); + spi_wr8(hcd, MAX3421_REG_HIEN, max3421_hcd->hien); + + /* enable interrupts: */ + spi_wr8(hcd, MAX3421_REG_CPUCTL, BIT(MAX3421_CPUCTL_IE_BIT)); + return 1; +} + +static int +max3421_urb_done(struct usb_hcd *hcd) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + unsigned long flags; + struct urb *urb; + int status; + + status = max3421_hcd->urb_done; + max3421_hcd->urb_done = 0; + if (status > 0) + status = 0; + urb = max3421_hcd->curr_urb; + if (urb) { + /* save the old end-points toggles: */ + u8 hrsl = spi_rd8(hcd, MAX3421_REG_HRSL); + int rcvtog = (hrsl >> MAX3421_HRSL_RCVTOGRD_BIT) & 1; + int sndtog = (hrsl >> MAX3421_HRSL_SNDTOGRD_BIT) & 1; + int epnum = usb_endpoint_num(&urb->ep->desc); + + /* no locking: HCD (i.e., we) own toggles, don't we? */ + usb_settoggle(urb->dev, epnum, 0, rcvtog); + usb_settoggle(urb->dev, epnum, 1, sndtog); + + max3421_hcd->curr_urb = NULL; + spin_lock_irqsave(&max3421_hcd->lock, flags); + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock_irqrestore(&max3421_hcd->lock, flags); + + /* must be called without the HCD spinlock: */ + usb_hcd_giveback_urb(hcd, urb, status); + } + return 1; +} + +static int +max3421_spi_thread(void *dev_id) +{ + struct usb_hcd *hcd = dev_id; + struct spi_device *spi = to_spi_device(hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + int i, i_worked = 1; + + /* set full-duplex SPI mode, low-active interrupt pin: */ + spi_wr8(hcd, MAX3421_REG_PINCTL, + (BIT(MAX3421_PINCTL_FDUPSPI_BIT) | /* full-duplex */ + BIT(MAX3421_PINCTL_INTLEVEL_BIT))); /* low-active irq */ + + while (!kthread_should_stop()) { + max3421_hcd->rev = spi_rd8(hcd, MAX3421_REG_REVISION); + if (max3421_hcd->rev == 0x12 || max3421_hcd->rev == 0x13) + break; + dev_err(&spi->dev, "bad rev 0x%02x", max3421_hcd->rev); + msleep(10000); + } + dev_info(&spi->dev, "rev 0x%x, SPI clk %dHz, bpw %u, irq %d\n", + max3421_hcd->rev, spi->max_speed_hz, spi->bits_per_word, + spi->irq); + + while (!kthread_should_stop()) { + if (!i_worked) { + /* + * We'll be waiting for wakeups from the hard + * interrupt handler, so now is a good time to + * sync our hien with the chip: + */ + spi_wr8(hcd, MAX3421_REG_HIEN, max3421_hcd->hien); + + set_current_state(TASK_INTERRUPTIBLE); + if (test_and_clear_bit(ENABLE_IRQ, &max3421_hcd->todo)) + enable_irq(spi->irq); + schedule(); + __set_current_state(TASK_RUNNING); + } + + i_worked = 0; + + if (max3421_hcd->urb_done) + i_worked |= max3421_urb_done(hcd); + else if (max3421_handle_irqs(hcd)) + i_worked = 1; + else if (!max3421_hcd->curr_urb) + i_worked |= max3421_select_and_start_urb(hcd); + + if (test_and_clear_bit(RESET_HCD, &max3421_hcd->todo)) + /* reset the HCD: */ + i_worked |= max3421_reset_hcd(hcd); + if (test_and_clear_bit(RESET_PORT, &max3421_hcd->todo)) { + /* perform a USB bus reset: */ + spi_wr8(hcd, MAX3421_REG_HCTL, + BIT(MAX3421_HCTL_BUSRST_BIT)); + i_worked = 1; + } + if (test_and_clear_bit(CHECK_UNLINK, &max3421_hcd->todo)) + i_worked |= max3421_check_unlink(hcd); + if (test_and_clear_bit(IOPIN_UPDATE, &max3421_hcd->todo)) { + /* + * IOPINS1/IOPINS2 do not auto-increment, so we can't + * use spi_wr_buf(). + */ + for (i = 0; i < ARRAY_SIZE(max3421_hcd->iopins); ++i) { + u8 val = spi_rd8(hcd, MAX3421_REG_IOPINS1 + i); + + val = ((val & 0xf0) | + (max3421_hcd->iopins[i] & 0x0f)); + spi_wr8(hcd, MAX3421_REG_IOPINS1 + i, val); + max3421_hcd->iopins[i] = val; + } + i_worked = 1; + } + } + set_current_state(TASK_RUNNING); + dev_info(&spi->dev, "SPI thread exiting"); + return 0; +} + +static int +max3421_reset_port(struct usb_hcd *hcd) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + + max3421_hcd->port_status &= ~(USB_PORT_STAT_ENABLE | + USB_PORT_STAT_LOW_SPEED); + max3421_hcd->port_status |= USB_PORT_STAT_RESET; + set_bit(RESET_PORT, &max3421_hcd->todo); + wake_up_process(max3421_hcd->spi_thread); + return 0; +} + +static int +max3421_reset(struct usb_hcd *hcd) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + + hcd->self.sg_tablesize = 0; + hcd->speed = HCD_USB2; + hcd->self.root_hub->speed = USB_SPEED_FULL; + set_bit(RESET_HCD, &max3421_hcd->todo); + wake_up_process(max3421_hcd->spi_thread); + return 0; +} + +static int +max3421_start(struct usb_hcd *hcd) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + + spin_lock_init(&max3421_hcd->lock); + max3421_hcd->rh_state = MAX3421_RH_RUNNING; + + INIT_LIST_HEAD(&max3421_hcd->ep_list); + + hcd->power_budget = POWER_BUDGET; + hcd->state = HC_STATE_RUNNING; + hcd->uses_new_polling = 1; + return 0; +} + +static void +max3421_stop(struct usb_hcd *hcd) +{ +} + +static int +max3421_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) +{ + struct spi_device *spi = to_spi_device(hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + struct max3421_ep *max3421_ep; + unsigned long flags; + int retval; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_INTERRUPT: + case PIPE_ISOCHRONOUS: + if (urb->interval < 0) { + dev_err(&spi->dev, + "%s: interval=%d for intr-/iso-pipe; expected > 0\n", + __func__, urb->interval); + return -EINVAL; + } + break; + default: + break; + } + + spin_lock_irqsave(&max3421_hcd->lock, flags); + + max3421_ep = urb->ep->hcpriv; + if (!max3421_ep) { + /* gets freed in max3421_endpoint_disable: */ + max3421_ep = kzalloc_obj(struct max3421_ep, GFP_ATOMIC); + if (!max3421_ep) { + retval = -ENOMEM; + goto out; + } + max3421_ep->ep = urb->ep; + max3421_ep->last_active = max3421_hcd->frame_number; + urb->ep->hcpriv = max3421_ep; + + list_add_tail(&max3421_ep->ep_list, &max3421_hcd->ep_list); + } + + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (retval == 0) { + /* Since we added to the queue, restart scheduling: */ + max3421_hcd->sched_pass = SCHED_PASS_PERIODIC; + wake_up_process(max3421_hcd->spi_thread); + } + +out: + spin_unlock_irqrestore(&max3421_hcd->lock, flags); + return retval; +} + +static int +max3421_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + unsigned long flags; + int retval; + + spin_lock_irqsave(&max3421_hcd->lock, flags); + + /* + * This will set urb->unlinked which in turn causes the entry + * to be dropped at the next opportunity. + */ + retval = usb_hcd_check_unlink_urb(hcd, urb, status); + if (retval == 0) { + set_bit(CHECK_UNLINK, &max3421_hcd->todo); + wake_up_process(max3421_hcd->spi_thread); + } + spin_unlock_irqrestore(&max3421_hcd->lock, flags); + return retval; +} + +static void +max3421_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + unsigned long flags; + + spin_lock_irqsave(&max3421_hcd->lock, flags); + + if (ep->hcpriv) { + struct max3421_ep *max3421_ep = ep->hcpriv; + + /* remove myself from the ep_list: */ + if (!list_empty(&max3421_ep->ep_list)) + list_del(&max3421_ep->ep_list); + kfree(max3421_ep); + ep->hcpriv = NULL; + } + + spin_unlock_irqrestore(&max3421_hcd->lock, flags); +} + +static int +max3421_get_frame_number(struct usb_hcd *hcd) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + return max3421_hcd->frame_number; +} + +/* + * Should return a non-zero value when any port is undergoing a resume + * transition while the root hub is suspended. + */ +static int +max3421_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + unsigned long flags; + int retval = 0; + + spin_lock_irqsave(&max3421_hcd->lock, flags); + if (!HCD_HW_ACCESSIBLE(hcd)) + goto done; + + *buf = 0; + if ((max3421_hcd->port_status & PORT_C_MASK) != 0) { + *buf = (1 << 1); /* a hub over-current condition exists */ + dev_dbg(hcd->self.controller, + "port status 0x%08x has changes\n", + max3421_hcd->port_status); + retval = 1; + if (max3421_hcd->rh_state == MAX3421_RH_SUSPENDED) + usb_hcd_resume_root_hub(hcd); + } +done: + spin_unlock_irqrestore(&max3421_hcd->lock, flags); + return retval; +} + +static inline void +hub_descriptor(struct usb_hub_descriptor *desc) +{ + memset(desc, 0, sizeof(*desc)); + /* + * See Table 11-13: Hub Descriptor in USB 2.0 spec. + */ + desc->bDescriptorType = USB_DT_HUB; /* hub descriptor */ + desc->bDescLength = 9; + desc->wHubCharacteristics = cpu_to_le16(HUB_CHAR_INDV_PORT_LPSM | + HUB_CHAR_COMMON_OCPM); + desc->bNbrPorts = 1; +} + +/* + * Set the MAX3421E general-purpose output with number PIN_NUMBER to + * VALUE (0 or 1). PIN_NUMBER may be in the range from 1-8. For + * any other value, this function acts as a no-op. + */ +static void +max3421_gpout_set_value(struct usb_hcd *hcd, u8 pin_number, u8 value) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + u8 mask, idx; + + --pin_number; + if (pin_number >= MAX3421_GPOUT_COUNT) + return; + + mask = 1u << (pin_number % 4); + idx = pin_number / 4; + + if (value) + max3421_hcd->iopins[idx] |= mask; + else + max3421_hcd->iopins[idx] &= ~mask; + set_bit(IOPIN_UPDATE, &max3421_hcd->todo); + wake_up_process(max3421_hcd->spi_thread); +} + +static int +max3421_hub_control(struct usb_hcd *hcd, u16 type_req, u16 value, u16 index, + char *buf, u16 length) +{ + struct spi_device *spi = to_spi_device(hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421(hcd); + struct max3421_hcd_platform_data *pdata; + unsigned long flags; + int retval = 0; + + pdata = spi->dev.platform_data; + + spin_lock_irqsave(&max3421_hcd->lock, flags); + + switch (type_req) { + case ClearHubFeature: + break; + case ClearPortFeature: + if (index != 1) + goto error; + switch (value) { + case USB_PORT_FEAT_SUSPEND: + break; + case USB_PORT_FEAT_POWER: + dev_dbg(hcd->self.controller, "power-off\n"); + max3421_gpout_set_value(hcd, pdata->vbus_gpout, + !pdata->vbus_active_level); + fallthrough; + default: + if (value >= 32) + goto error; + max3421_hcd->port_status &= ~(1 << value); + } + break; + case GetHubDescriptor: + hub_descriptor((struct usb_hub_descriptor *) buf); + break; + + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + case GetPortErrorCount: + case SetHubDepth: + /* USB3 only */ + goto error; + + case GetHubStatus: + *(__le32 *) buf = cpu_to_le32(0); + break; + + case GetPortStatus: + if (index != 1) { + retval = -EPIPE; + goto error; + } + ((__le16 *) buf)[0] = cpu_to_le16(max3421_hcd->port_status); + ((__le16 *) buf)[1] = + cpu_to_le16(max3421_hcd->port_status >> 16); + break; + + case SetHubFeature: + retval = -EPIPE; + break; + + case SetPortFeature: + if (index != 1) + goto error; + switch (value) { + case USB_PORT_FEAT_LINK_STATE: + case USB_PORT_FEAT_U1_TIMEOUT: + case USB_PORT_FEAT_U2_TIMEOUT: + case USB_PORT_FEAT_BH_PORT_RESET: + goto error; + case USB_PORT_FEAT_SUSPEND: + if (max3421_hcd->active) + max3421_hcd->port_status |= + USB_PORT_STAT_SUSPEND; + break; + case USB_PORT_FEAT_POWER: + dev_dbg(hcd->self.controller, "power-on\n"); + max3421_hcd->port_status |= USB_PORT_STAT_POWER; + max3421_gpout_set_value(hcd, pdata->vbus_gpout, + pdata->vbus_active_level); + break; + case USB_PORT_FEAT_RESET: + max3421_reset_port(hcd); + fallthrough; + default: + if (value >= 32) + goto error; + if ((max3421_hcd->port_status & USB_PORT_STAT_POWER) + != 0) + max3421_hcd->port_status |= (1 << value); + } + break; + + default: + dev_dbg(hcd->self.controller, + "hub control req%04x v%04x i%04x l%d\n", + type_req, value, index, length); +error: /* "protocol stall" on error */ + retval = -EPIPE; + } + + spin_unlock_irqrestore(&max3421_hcd->lock, flags); + return retval; +} + +static int +max3421_bus_suspend(struct usb_hcd *hcd) +{ + return -1; +} + +static int +max3421_bus_resume(struct usb_hcd *hcd) +{ + return -1; +} + +static const struct hc_driver max3421_hcd_desc = { + .description = "max3421", + .product_desc = DRIVER_DESC, + .hcd_priv_size = sizeof(struct max3421_hcd), + .flags = HCD_USB11, + .reset = max3421_reset, + .start = max3421_start, + .stop = max3421_stop, + .get_frame_number = max3421_get_frame_number, + .urb_enqueue = max3421_urb_enqueue, + .urb_dequeue = max3421_urb_dequeue, + .endpoint_disable = max3421_endpoint_disable, + .hub_status_data = max3421_hub_status_data, + .hub_control = max3421_hub_control, + .bus_suspend = max3421_bus_suspend, + .bus_resume = max3421_bus_resume, +}; + +static int +max3421_of_vbus_en_pin(struct device *dev, struct max3421_hcd_platform_data *pdata) +{ + int retval; + uint32_t value[2]; + + if (!pdata) + return -EINVAL; + + retval = of_property_read_u32_array(dev->of_node, "maxim,vbus-en-pin", value, 2); + if (retval) { + dev_err(dev, "device tree node property 'maxim,vbus-en-pin' is missing\n"); + return retval; + } + dev_info(dev, "property 'maxim,vbus-en-pin' value is <%d %d>\n", value[0], value[1]); + + pdata->vbus_gpout = value[0]; + pdata->vbus_active_level = value[1]; + + return 0; +} + +static int +max3421_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct max3421_hcd *max3421_hcd; + struct usb_hcd *hcd = NULL; + struct max3421_hcd_platform_data *pdata = NULL; + int retval; + + if (spi_setup(spi) < 0) { + dev_err(&spi->dev, "Unable to setup SPI bus"); + return -EFAULT; + } + + if (!spi->irq) { + dev_err(dev, "Failed to get SPI IRQ"); + return -EFAULT; + } + + if (IS_ENABLED(CONFIG_OF) && dev->of_node) { + pdata = devm_kzalloc(&spi->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + retval = -ENOMEM; + goto error; + } + retval = max3421_of_vbus_en_pin(dev, pdata); + if (retval) + goto error; + + spi->dev.platform_data = pdata; + } + + pdata = spi->dev.platform_data; + if (!pdata) { + dev_err(&spi->dev, "driver configuration data is not provided\n"); + retval = -EFAULT; + goto error; + } + if (pdata->vbus_active_level > 1) { + dev_err(&spi->dev, "vbus active level value %d is out of range (0/1)\n", pdata->vbus_active_level); + retval = -EINVAL; + goto error; + } + if (pdata->vbus_gpout < 1 || pdata->vbus_gpout > MAX3421_GPOUT_COUNT) { + dev_err(&spi->dev, "vbus gpout value %d is out of range (1..8)\n", pdata->vbus_gpout); + retval = -EINVAL; + goto error; + } + + retval = -ENOMEM; + hcd = usb_create_hcd(&max3421_hcd_desc, &spi->dev, + dev_name(&spi->dev)); + if (!hcd) { + dev_err(&spi->dev, "failed to create HCD structure\n"); + goto error; + } + set_bit(HCD_FLAG_POLL_RH, &hcd->flags); + max3421_hcd = hcd_to_max3421(hcd); + INIT_LIST_HEAD(&max3421_hcd->ep_list); + spi_set_drvdata(spi, max3421_hcd); + + max3421_hcd->tx = kmalloc_obj(*max3421_hcd->tx); + if (!max3421_hcd->tx) + goto error; + max3421_hcd->rx = kmalloc_obj(*max3421_hcd->rx); + if (!max3421_hcd->rx) + goto error; + + max3421_hcd->spi_thread = kthread_run(max3421_spi_thread, hcd, + "max3421_spi_thread"); + if (max3421_hcd->spi_thread == ERR_PTR(-ENOMEM)) { + dev_err(&spi->dev, + "failed to create SPI thread (out of memory)\n"); + goto error; + } + + retval = usb_add_hcd(hcd, 0, 0); + if (retval) { + dev_err(&spi->dev, "failed to add HCD\n"); + goto error; + } + + retval = request_irq(spi->irq, max3421_irq_handler, + IRQF_TRIGGER_LOW, "max3421", hcd); + if (retval < 0) { + dev_err(&spi->dev, "failed to request irq %d\n", spi->irq); + goto error; + } + return 0; + +error: + if (IS_ENABLED(CONFIG_OF) && dev->of_node && pdata) { + devm_kfree(&spi->dev, pdata); + spi->dev.platform_data = NULL; + } + + if (hcd) { + kfree(max3421_hcd->tx); + kfree(max3421_hcd->rx); + if (!IS_ERR_OR_NULL(max3421_hcd->spi_thread)) + kthread_stop(max3421_hcd->spi_thread); + usb_put_hcd(hcd); + } + return retval; +} + +static void +max3421_remove(struct spi_device *spi) +{ + struct max3421_hcd *max3421_hcd; + struct usb_hcd *hcd; + unsigned long flags; + + max3421_hcd = spi_get_drvdata(spi); + hcd = max3421_to_hcd(max3421_hcd); + + usb_remove_hcd(hcd); + + spin_lock_irqsave(&max3421_hcd->lock, flags); + + kthread_stop(max3421_hcd->spi_thread); + + spin_unlock_irqrestore(&max3421_hcd->lock, flags); + + free_irq(spi->irq, hcd); + + usb_put_hcd(hcd); +} + +static const struct spi_device_id max3421_spi_ids[] = { + { "max3421" }, + { }, +}; +MODULE_DEVICE_TABLE(spi, max3421_spi_ids); + +static const struct of_device_id max3421_of_match_table[] = { + { .compatible = "maxim,max3421", }, + {}, +}; +MODULE_DEVICE_TABLE(of, max3421_of_match_table); + +static struct spi_driver max3421_driver = { + .probe = max3421_probe, + .remove = max3421_remove, + .id_table = max3421_spi_ids, + .driver = { + .name = "max3421-hcd", + .of_match_table = max3421_of_match_table, + }, +}; + +module_spi_driver(max3421_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("David Mosberger "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c new file mode 100644 index 000000000..34ce771fa --- /dev/null +++ b/drivers/usb/host/octeon-hcd.c @@ -0,0 +1,3737 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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) 2008 Cavium Networks + * + * Some parts of the code were originally released under BSD license: + * + * Copyright (c) 2003-2010 Cavium Networks (support@cavium.com). All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Cavium Networks 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, including technical data, may be subject to U.S. export + * control laws, including the U.S. Export Administration Act and its associated + * regulations, and may be subject to export or import regulations in other + * countries. + * + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" + * AND WITH ALL FAULTS AND CAVIUM NETWORKS MAKES NO PROMISES, REPRESENTATIONS OR + * WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO + * THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY REPRESENTATION + * OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT DEFECTS, AND CAVIUM + * SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES OF TITLE, + * MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, LACK OF + * VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION OR + * CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK ARISING OUT OF USE OR + * PERFORMANCE OF THE SOFTWARE LIES WITH YOU. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "octeon-hcd.h" + +/** + * enum cvmx_usb_speed - the possible USB device speeds + * + * @CVMX_USB_SPEED_HIGH: Device is operation at 480Mbps + * @CVMX_USB_SPEED_FULL: Device is operation at 12Mbps + * @CVMX_USB_SPEED_LOW: Device is operation at 1.5Mbps + */ +enum cvmx_usb_speed { + CVMX_USB_SPEED_HIGH = 0, + CVMX_USB_SPEED_FULL = 1, + CVMX_USB_SPEED_LOW = 2, +}; + +/** + * enum cvmx_usb_transfer - the possible USB transfer types + * + * @CVMX_USB_TRANSFER_CONTROL: USB transfer type control for hub and status + * transfers + * @CVMX_USB_TRANSFER_ISOCHRONOUS: USB transfer type isochronous for low + * priority periodic transfers + * @CVMX_USB_TRANSFER_BULK: USB transfer type bulk for large low priority + * transfers + * @CVMX_USB_TRANSFER_INTERRUPT: USB transfer type interrupt for high priority + * periodic transfers + */ +enum cvmx_usb_transfer { + CVMX_USB_TRANSFER_CONTROL = 0, + CVMX_USB_TRANSFER_ISOCHRONOUS = 1, + CVMX_USB_TRANSFER_BULK = 2, + CVMX_USB_TRANSFER_INTERRUPT = 3, +}; + +/** + * enum cvmx_usb_direction - the transfer directions + * + * @CVMX_USB_DIRECTION_OUT: Data is transferring from Octeon to the device/host + * @CVMX_USB_DIRECTION_IN: Data is transferring from the device/host to Octeon + */ +enum cvmx_usb_direction { + CVMX_USB_DIRECTION_OUT, + CVMX_USB_DIRECTION_IN, +}; + +/** + * enum cvmx_usb_status - possible callback function status codes + * + * @CVMX_USB_STATUS_OK: The transaction / operation finished without + * any errors + * @CVMX_USB_STATUS_SHORT: FIXME: This is currently not implemented + * @CVMX_USB_STATUS_CANCEL: The transaction was canceled while in flight + * by a user call to cvmx_usb_cancel + * @CVMX_USB_STATUS_ERROR: The transaction aborted with an unexpected + * error status + * @CVMX_USB_STATUS_STALL: The transaction received a USB STALL response + * from the device + * @CVMX_USB_STATUS_XACTERR: The transaction failed with an error from the + * device even after a number of retries + * @CVMX_USB_STATUS_DATATGLERR: The transaction failed with a data toggle + * error even after a number of retries + * @CVMX_USB_STATUS_BABBLEERR: The transaction failed with a babble error + * @CVMX_USB_STATUS_FRAMEERR: The transaction failed with a frame error + * even after a number of retries + */ +enum cvmx_usb_status { + CVMX_USB_STATUS_OK, + CVMX_USB_STATUS_SHORT, + CVMX_USB_STATUS_CANCEL, + CVMX_USB_STATUS_ERROR, + CVMX_USB_STATUS_STALL, + CVMX_USB_STATUS_XACTERR, + CVMX_USB_STATUS_DATATGLERR, + CVMX_USB_STATUS_BABBLEERR, + CVMX_USB_STATUS_FRAMEERR, +}; + +/** + * struct cvmx_usb_port_status - the USB port status information + * + * @port_enabled: 1 = Usb port is enabled, 0 = disabled + * @port_over_current: 1 = Over current detected, 0 = Over current not + * detected. Octeon doesn't support over current detection. + * @port_powered: 1 = Port power is being supplied to the device, 0 = + * power is off. Octeon doesn't support turning port power + * off. + * @port_speed: Current port speed. + * @connected: 1 = A device is connected to the port, 0 = No device is + * connected. + * @connect_change: 1 = Device connected state changed since the last set + * status call. + */ +struct cvmx_usb_port_status { + u32 reserved : 25; + u32 port_enabled : 1; + u32 port_over_current : 1; + u32 port_powered : 1; + enum cvmx_usb_speed port_speed : 2; + u32 connected : 1; + u32 connect_change : 1; +}; + +/** + * struct cvmx_usb_iso_packet - descriptor for Isochronous packets + * + * @offset: This is the offset in bytes into the main buffer where this data + * is stored. + * @length: This is the length in bytes of the data. + * @status: This is the status of this individual packet transfer. + */ +struct cvmx_usb_iso_packet { + int offset; + int length; + enum cvmx_usb_status status; +}; + +/** + * enum cvmx_usb_initialize_flags - flags used by the initialization function + * + * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_XI: The USB port uses a 12MHz crystal + * as clock source at USB_XO and + * USB_XI. + * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND: The USB port uses 12/24/48MHz 2.5V + * board clock source at USB_XO. + * USB_XI should be tied to GND. + * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_MHZ_MASK: Mask for clock speed field + * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_12MHZ: Speed of reference clock or + * crystal + * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_24MHZ: Speed of reference clock + * @CVMX_USB_INITIALIZE_FLAGS_CLOCK_48MHZ: Speed of reference clock + * @CVMX_USB_INITIALIZE_FLAGS_NO_DMA: Disable DMA and used polled IO for + * data transfer use for the USB + */ +enum cvmx_usb_initialize_flags { + CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_XI = 1 << 0, + CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND = 1 << 1, + CVMX_USB_INITIALIZE_FLAGS_CLOCK_MHZ_MASK = 3 << 3, + CVMX_USB_INITIALIZE_FLAGS_CLOCK_12MHZ = 1 << 3, + CVMX_USB_INITIALIZE_FLAGS_CLOCK_24MHZ = 2 << 3, + CVMX_USB_INITIALIZE_FLAGS_CLOCK_48MHZ = 3 << 3, + /* Bits 3-4 used to encode the clock frequency */ + CVMX_USB_INITIALIZE_FLAGS_NO_DMA = 1 << 5, +}; + +/** + * enum cvmx_usb_pipe_flags - internal flags for a pipe. + * + * @CVMX_USB_PIPE_FLAGS_SCHEDULED: Used internally to determine if a pipe is + * actively using hardware. + * @CVMX_USB_PIPE_FLAGS_NEED_PING: Used internally to determine if a high speed + * pipe is in the ping state. + */ +enum cvmx_usb_pipe_flags { + CVMX_USB_PIPE_FLAGS_SCHEDULED = 1 << 17, + CVMX_USB_PIPE_FLAGS_NEED_PING = 1 << 18, +}; + +/* Maximum number of times to retry failed transactions */ +#define MAX_RETRIES 3 + +/* Maximum number of hardware channels supported by the USB block */ +#define MAX_CHANNELS 8 + +/* + * The low level hardware can transfer a maximum of this number of bytes in each + * transfer. The field is 19 bits wide + */ +#define MAX_TRANSFER_BYTES ((1 << 19) - 1) + +/* + * The low level hardware can transfer a maximum of this number of packets in + * each transfer. The field is 10 bits wide + */ +#define MAX_TRANSFER_PACKETS ((1 << 10) - 1) + +/** + * Logical transactions may take numerous low level + * transactions, especially when splits are concerned. This + * enum represents all of the possible stages a transaction can + * be in. Note that split completes are always even. This is so + * the NAK handler can backup to the previous low level + * transaction with a simple clearing of bit 0. + */ +enum cvmx_usb_stage { + CVMX_USB_STAGE_NON_CONTROL, + CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE, + CVMX_USB_STAGE_SETUP, + CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE, + CVMX_USB_STAGE_DATA, + CVMX_USB_STAGE_DATA_SPLIT_COMPLETE, + CVMX_USB_STAGE_STATUS, + CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE, +}; + +/** + * struct cvmx_usb_transaction - describes each pending USB transaction + * regardless of type. These are linked together + * to form a list of pending requests for a pipe. + * + * @node: List node for transactions in the pipe. + * @type: Type of transaction, duplicated of the pipe. + * @flags: State flags for this transaction. + * @buffer: User's physical buffer address to read/write. + * @buffer_length: Size of the user's buffer in bytes. + * @control_header: For control transactions, physical address of the 8 + * byte standard header. + * @iso_start_frame: For ISO transactions, the starting frame number. + * @iso_number_packets: For ISO transactions, the number of packets in the + * request. + * @iso_packets: For ISO transactions, the sub packets in the request. + * @actual_bytes: Actual bytes transfer for this transaction. + * @stage: For control transactions, the current stage. + * @urb: URB. + */ +struct cvmx_usb_transaction { + struct list_head node; + enum cvmx_usb_transfer type; + u64 buffer; + int buffer_length; + u64 control_header; + int iso_start_frame; + int iso_number_packets; + struct cvmx_usb_iso_packet *iso_packets; + int xfersize; + int pktcnt; + int retries; + int actual_bytes; + enum cvmx_usb_stage stage; + struct urb *urb; +}; + +/** + * struct cvmx_usb_pipe - a pipe represents a virtual connection between Octeon + * and some USB device. It contains a list of pending + * request to the device. + * + * @node: List node for pipe list + * @next: Pipe after this one in the list + * @transactions: List of pending transactions + * @interval: For periodic pipes, the interval between packets in + * frames + * @next_tx_frame: The next frame this pipe is allowed to transmit on + * @flags: State flags for this pipe + * @device_speed: Speed of device connected to this pipe + * @transfer_type: Type of transaction supported by this pipe + * @transfer_dir: IN or OUT. Ignored for Control + * @multi_count: Max packet in a row for the device + * @max_packet: The device's maximum packet size in bytes + * @device_addr: USB device address at other end of pipe + * @endpoint_num: USB endpoint number at other end of pipe + * @hub_device_addr: Hub address this device is connected to + * @hub_port: Hub port this device is connected to + * @pid_toggle: This toggles between 0/1 on every packet send to track + * the data pid needed + * @channel: Hardware DMA channel for this pipe + * @split_sc_frame: The low order bits of the frame number the split + * complete should be sent on + */ +struct cvmx_usb_pipe { + struct list_head node; + struct list_head transactions; + u64 interval; + u64 next_tx_frame; + enum cvmx_usb_pipe_flags flags; + enum cvmx_usb_speed device_speed; + enum cvmx_usb_transfer transfer_type; + enum cvmx_usb_direction transfer_dir; + int multi_count; + u16 max_packet; + u8 device_addr; + u8 endpoint_num; + u8 hub_device_addr; + u8 hub_port; + u8 pid_toggle; + u8 channel; + s8 split_sc_frame; +}; + +struct cvmx_usb_tx_fifo { + struct { + int channel; + int size; + u64 address; + } entry[MAX_CHANNELS + 1]; + int head; + int tail; +}; + +/** + * struct octeon_hcd - the state of the USB block + * + * lock: Serialization lock. + * init_flags: Flags passed to initialize. + * index: Which USB block this is for. + * idle_hardware_channels: Bit set for every idle hardware channel. + * usbcx_hprt: Stored port status so we don't need to read a CSR to + * determine splits. + * pipe_for_channel: Map channels to pipes. + * pipe: Storage for pipes. + * indent: Used by debug output to indent functions. + * port_status: Last port status used for change notification. + * idle_pipes: List of open pipes that have no transactions. + * active_pipes: Active pipes indexed by transfer type. + * frame_number: Increments every SOF interrupt for time keeping. + * active_split: Points to the current active split, or NULL. + */ +struct octeon_hcd { + spinlock_t lock; /* serialization lock */ + int init_flags; + int index; + int idle_hardware_channels; + union cvmx_usbcx_hprt usbcx_hprt; + struct cvmx_usb_pipe *pipe_for_channel[MAX_CHANNELS]; + int indent; + struct cvmx_usb_port_status port_status; + struct list_head idle_pipes; + struct list_head active_pipes[4]; + u64 frame_number; + struct cvmx_usb_transaction *active_split; + struct cvmx_usb_tx_fifo periodic; + struct cvmx_usb_tx_fifo nonperiodic; +}; + +/* + * This macro logically sets a single field in a CSR. It does the sequence + * read, modify, and write + */ +#define USB_SET_FIELD32(address, _union, field, value) \ + do { \ + union _union c; \ + \ + c.u32 = cvmx_usb_read_csr32(usb, address); \ + c.s.field = value; \ + cvmx_usb_write_csr32(usb, address, c.u32); \ + } while (0) + +/* Returns the IO address to push/pop stuff data from the FIFOs */ +#define USB_FIFO_ADDRESS(channel, usb_index) \ + (CVMX_USBCX_GOTGCTL(usb_index) + ((channel) + 1) * 0x1000) + +/** + * struct octeon_temp_buffer - a bounce buffer for USB transfers + * @orig_buffer: the original buffer passed by the USB stack + * @data: the newly allocated temporary buffer (excluding meta-data) + * + * Both the DMA engine and FIFO mode will always transfer full 32-bit words. If + * the buffer is too short, we need to allocate a temporary one, and this struct + * represents it. + */ +struct octeon_temp_buffer { + void *orig_buffer; + u8 data[]; +}; + +static inline struct usb_hcd *octeon_to_hcd(struct octeon_hcd *p) +{ + return container_of((void *)p, struct usb_hcd, hcd_priv); +} + +/** + * octeon_alloc_temp_buffer - allocate a temporary buffer for USB transfer + * (if needed) + * @urb: URB. + * @mem_flags: Memory allocation flags. + * + * This function allocates a temporary bounce buffer whenever it's needed + * due to HW limitations. + */ +static int octeon_alloc_temp_buffer(struct urb *urb, gfp_t mem_flags) +{ + struct octeon_temp_buffer *temp; + + if (urb->num_sgs || urb->sg || + (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) || + !(urb->transfer_buffer_length % sizeof(u32))) + return 0; + + temp = kmalloc(ALIGN(urb->transfer_buffer_length, sizeof(u32)) + + sizeof(*temp), mem_flags); + if (!temp) + return -ENOMEM; + + temp->orig_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; +} + +/** + * octeon_free_temp_buffer - free a temporary buffer used by USB transfers. + * @urb: URB. + * + * Frees a buffer allocated by octeon_alloc_temp_buffer(). + */ +static void octeon_free_temp_buffer(struct urb *urb) +{ + struct octeon_temp_buffer *temp; + size_t length; + + if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) + return; + + temp = container_of(urb->transfer_buffer, struct octeon_temp_buffer, + data); + if (usb_urb_dir_in(urb)) { + if (usb_pipeisoc(urb->pipe)) + length = urb->transfer_buffer_length; + else + length = urb->actual_length; + + memcpy(temp->orig_buffer, urb->transfer_buffer, length); + } + urb->transfer_buffer = temp->orig_buffer; + urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; + kfree(temp); +} + +/** + * octeon_map_urb_for_dma - Octeon-specific map_urb_for_dma(). + * @hcd: USB HCD structure. + * @urb: URB. + * @mem_flags: Memory allocation flags. + */ +static int octeon_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + int ret; + + ret = octeon_alloc_temp_buffer(urb, mem_flags); + if (ret) + return ret; + + ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); + if (ret) + octeon_free_temp_buffer(urb); + + return ret; +} + +/** + * octeon_unmap_urb_for_dma - Octeon-specific unmap_urb_for_dma() + * @hcd: USB HCD structure. + * @urb: URB. + */ +static void octeon_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ + usb_hcd_unmap_urb_for_dma(hcd, urb); + octeon_free_temp_buffer(urb); +} + +/** + * Read a USB 32bit CSR. It performs the necessary address swizzle + * for 32bit CSRs and logs the value in a readable format if + * debugging is on. + * + * @usb: USB block this access is for + * @address: 64bit address to read + * + * Returns: Result of the read + */ +static inline u32 cvmx_usb_read_csr32(struct octeon_hcd *usb, u64 address) +{ + return cvmx_read64_uint32(address ^ 4); +} + +/** + * Write a USB 32bit CSR. It performs the necessary address + * swizzle for 32bit CSRs and logs the value in a readable format + * if debugging is on. + * + * @usb: USB block this access is for + * @address: 64bit address to write + * @value: Value to write + */ +static inline void cvmx_usb_write_csr32(struct octeon_hcd *usb, + u64 address, u32 value) +{ + cvmx_write64_uint32(address ^ 4, value); + cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index)); +} + +/** + * Return non zero if this pipe connects to a non HIGH speed + * device through a high speed hub. + * + * @usb: USB block this access is for + * @pipe: Pipe to check + * + * Returns: Non zero if we need to do split transactions + */ +static inline int cvmx_usb_pipe_needs_split(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe) +{ + return pipe->device_speed != CVMX_USB_SPEED_HIGH && + usb->usbcx_hprt.s.prtspd == CVMX_USB_SPEED_HIGH; +} + +/** + * Trivial utility function to return the correct PID for a pipe + * + * @pipe: pipe to check + * + * Returns: PID for pipe + */ +static inline int cvmx_usb_get_data_pid(struct cvmx_usb_pipe *pipe) +{ + if (pipe->pid_toggle) + return 2; /* Data1 */ + return 0; /* Data0 */ +} + +/* Loops through register until txfflsh or rxfflsh become zero.*/ +static int cvmx_wait_tx_rx(struct octeon_hcd *usb, int fflsh_type) +{ + int result; + u64 address = CVMX_USBCX_GRSTCTL(usb->index); + u64 done = cvmx_get_cycle() + 100 * + (u64)octeon_get_clock_rate / 1000000; + union cvmx_usbcx_grstctl c; + + while (1) { + c.u32 = cvmx_usb_read_csr32(usb, address); + if (fflsh_type == 0 && c.s.txfflsh == 0) { + result = 0; + break; + } else if (fflsh_type == 1 && c.s.rxfflsh == 0) { + result = 0; + break; + } else if (cvmx_get_cycle() > done) { + result = -1; + break; + } + + __delay(100); + } + return result; +} + +static void cvmx_fifo_setup(struct octeon_hcd *usb) +{ + union cvmx_usbcx_ghwcfg3 usbcx_ghwcfg3; + union cvmx_usbcx_gnptxfsiz npsiz; + union cvmx_usbcx_hptxfsiz psiz; + + usbcx_ghwcfg3.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GHWCFG3(usb->index)); + + /* + * Program the USBC_GRXFSIZ register to select the size of the receive + * FIFO (25%). + */ + USB_SET_FIELD32(CVMX_USBCX_GRXFSIZ(usb->index), cvmx_usbcx_grxfsiz, + rxfdep, usbcx_ghwcfg3.s.dfifodepth / 4); + + /* + * Program the USBC_GNPTXFSIZ register to select the size and the start + * address of the non-periodic transmit FIFO for nonperiodic + * transactions (50%). + */ + npsiz.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_GNPTXFSIZ(usb->index)); + npsiz.s.nptxfdep = usbcx_ghwcfg3.s.dfifodepth / 2; + npsiz.s.nptxfstaddr = usbcx_ghwcfg3.s.dfifodepth / 4; + cvmx_usb_write_csr32(usb, CVMX_USBCX_GNPTXFSIZ(usb->index), npsiz.u32); + + /* + * Program the USBC_HPTXFSIZ register to select the size and start + * address of the periodic transmit FIFO for periodic transactions + * (25%). + */ + psiz.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HPTXFSIZ(usb->index)); + psiz.s.ptxfsize = usbcx_ghwcfg3.s.dfifodepth / 4; + psiz.s.ptxfstaddr = 3 * usbcx_ghwcfg3.s.dfifodepth / 4; + cvmx_usb_write_csr32(usb, CVMX_USBCX_HPTXFSIZ(usb->index), psiz.u32); + + /* Flush all FIFOs */ + USB_SET_FIELD32(CVMX_USBCX_GRSTCTL(usb->index), + cvmx_usbcx_grstctl, txfnum, 0x10); + USB_SET_FIELD32(CVMX_USBCX_GRSTCTL(usb->index), + cvmx_usbcx_grstctl, txfflsh, 1); + cvmx_wait_tx_rx(usb, 0); + USB_SET_FIELD32(CVMX_USBCX_GRSTCTL(usb->index), + cvmx_usbcx_grstctl, rxfflsh, 1); + cvmx_wait_tx_rx(usb, 1); +} + +/** + * Shutdown a USB port after a call to cvmx_usb_initialize(). + * The port should be disabled with all pipes closed when this + * function is called. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * + * Returns: 0 or a negative error code. + */ +static int cvmx_usb_shutdown(struct octeon_hcd *usb) +{ + union cvmx_usbnx_clk_ctl usbn_clk_ctl; + + /* Make sure all pipes are closed */ + if (!list_empty(&usb->idle_pipes) || + !list_empty(&usb->active_pipes[CVMX_USB_TRANSFER_ISOCHRONOUS]) || + !list_empty(&usb->active_pipes[CVMX_USB_TRANSFER_INTERRUPT]) || + !list_empty(&usb->active_pipes[CVMX_USB_TRANSFER_CONTROL]) || + !list_empty(&usb->active_pipes[CVMX_USB_TRANSFER_BULK])) + return -EBUSY; + + /* Disable the clocks and put them in power on reset */ + usbn_clk_ctl.u64 = cvmx_read64_uint64(CVMX_USBNX_CLK_CTL(usb->index)); + usbn_clk_ctl.s.enable = 1; + usbn_clk_ctl.s.por = 1; + usbn_clk_ctl.s.hclk_rst = 1; + usbn_clk_ctl.s.prst = 0; + usbn_clk_ctl.s.hrst = 0; + cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); + return 0; +} + +/** + * Initialize a USB port for use. This must be called before any + * other access to the Octeon USB port is made. The port starts + * off in the disabled state. + * + * @dev: Pointer to struct device for logging purposes. + * @usb: Pointer to struct octeon_hcd. + * + * Returns: 0 or a negative error code. + */ +static int cvmx_usb_initialize(struct device *dev, + struct octeon_hcd *usb) +{ + int channel; + int divisor; + int retries = 0; + union cvmx_usbcx_hcfg usbcx_hcfg; + union cvmx_usbnx_clk_ctl usbn_clk_ctl; + union cvmx_usbcx_gintsts usbc_gintsts; + union cvmx_usbcx_gahbcfg usbcx_gahbcfg; + union cvmx_usbcx_gintmsk usbcx_gintmsk; + union cvmx_usbcx_gusbcfg usbcx_gusbcfg; + union cvmx_usbnx_usbp_ctl_status usbn_usbp_ctl_status; + +retry: + /* + * Power On Reset and PHY Initialization + * + * 1. Wait for DCOK to assert (nothing to do) + * + * 2a. Write USBN0/1_CLK_CTL[POR] = 1 and + * USBN0/1_CLK_CTL[HRST,PRST,HCLK_RST] = 0 + */ + usbn_clk_ctl.u64 = cvmx_read64_uint64(CVMX_USBNX_CLK_CTL(usb->index)); + usbn_clk_ctl.s.por = 1; + usbn_clk_ctl.s.hrst = 0; + usbn_clk_ctl.s.prst = 0; + usbn_clk_ctl.s.hclk_rst = 0; + usbn_clk_ctl.s.enable = 0; + /* + * 2b. Select the USB reference clock/crystal parameters by writing + * appropriate values to USBN0/1_CLK_CTL[P_C_SEL, P_RTYPE, P_COM_ON] + */ + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND) { + /* + * The USB port uses 12/24/48MHz 2.5V board clock + * source at USB_XO. USB_XI should be tied to GND. + * Most Octeon evaluation boards require this setting + */ + if (OCTEON_IS_MODEL(OCTEON_CN3XXX) || + OCTEON_IS_MODEL(OCTEON_CN56XX) || + OCTEON_IS_MODEL(OCTEON_CN50XX)) + /* From CN56XX,CN50XX,CN31XX,CN30XX manuals */ + usbn_clk_ctl.s.p_rtype = 2; /* p_rclk=1 & p_xenbn=0 */ + else + /* From CN52XX manual */ + usbn_clk_ctl.s.p_rtype = 1; + + switch (usb->init_flags & + CVMX_USB_INITIALIZE_FLAGS_CLOCK_MHZ_MASK) { + case CVMX_USB_INITIALIZE_FLAGS_CLOCK_12MHZ: + usbn_clk_ctl.s.p_c_sel = 0; + break; + case CVMX_USB_INITIALIZE_FLAGS_CLOCK_24MHZ: + usbn_clk_ctl.s.p_c_sel = 1; + break; + case CVMX_USB_INITIALIZE_FLAGS_CLOCK_48MHZ: + usbn_clk_ctl.s.p_c_sel = 2; + break; + } + } else { + /* + * The USB port uses a 12MHz crystal as clock source + * at USB_XO and USB_XI + */ + if (OCTEON_IS_MODEL(OCTEON_CN3XXX)) + /* From CN31XX,CN30XX manual */ + usbn_clk_ctl.s.p_rtype = 3; /* p_rclk=1 & p_xenbn=1 */ + else + /* From CN56XX,CN52XX,CN50XX manuals. */ + usbn_clk_ctl.s.p_rtype = 0; + + usbn_clk_ctl.s.p_c_sel = 0; + } + /* + * 2c. Select the HCLK via writing USBN0/1_CLK_CTL[DIVIDE, DIVIDE2] and + * setting USBN0/1_CLK_CTL[ENABLE] = 1. Divide the core clock down + * such that USB is as close as possible to 125Mhz + */ + divisor = DIV_ROUND_UP(octeon_get_clock_rate(), 125000000); + /* Lower than 4 doesn't seem to work properly */ + if (divisor < 4) + divisor = 4; + usbn_clk_ctl.s.divide = divisor; + usbn_clk_ctl.s.divide2 = 0; + cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); + + /* 2d. Write USBN0/1_CLK_CTL[HCLK_RST] = 1 */ + usbn_clk_ctl.s.hclk_rst = 1; + cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); + /* 2e. Wait 64 core-clock cycles for HCLK to stabilize */ + __delay(64); + /* + * 3. Program the power-on reset field in the USBN clock-control + * register: + * USBN_CLK_CTL[POR] = 0 + */ + usbn_clk_ctl.s.por = 0; + cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); + /* 4. Wait 1 ms for PHY clock to start */ + mdelay(1); + /* + * 5. Program the Reset input from automatic test equipment field in the + * USBP control and status register: + * USBN_USBP_CTL_STATUS[ATE_RESET] = 1 + */ + usbn_usbp_ctl_status.u64 = + cvmx_read64_uint64(CVMX_USBNX_USBP_CTL_STATUS(usb->index)); + usbn_usbp_ctl_status.s.ate_reset = 1; + cvmx_write64_uint64(CVMX_USBNX_USBP_CTL_STATUS(usb->index), + usbn_usbp_ctl_status.u64); + /* 6. Wait 10 cycles */ + __delay(10); + /* + * 7. Clear ATE_RESET field in the USBN clock-control register: + * USBN_USBP_CTL_STATUS[ATE_RESET] = 0 + */ + usbn_usbp_ctl_status.s.ate_reset = 0; + cvmx_write64_uint64(CVMX_USBNX_USBP_CTL_STATUS(usb->index), + usbn_usbp_ctl_status.u64); + /* + * 8. Program the PHY reset field in the USBN clock-control register: + * USBN_CLK_CTL[PRST] = 1 + */ + usbn_clk_ctl.s.prst = 1; + cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); + /* + * 9. Program the USBP control and status register to select host or + * device mode. USBN_USBP_CTL_STATUS[HST_MODE] = 0 for host, = 1 for + * device + */ + usbn_usbp_ctl_status.s.hst_mode = 0; + cvmx_write64_uint64(CVMX_USBNX_USBP_CTL_STATUS(usb->index), + usbn_usbp_ctl_status.u64); + /* 10. Wait 1 us */ + udelay(1); + /* + * 11. Program the hreset_n field in the USBN clock-control register: + * USBN_CLK_CTL[HRST] = 1 + */ + usbn_clk_ctl.s.hrst = 1; + cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); + /* 12. Proceed to USB core initialization */ + usbn_clk_ctl.s.enable = 1; + cvmx_write64_uint64(CVMX_USBNX_CLK_CTL(usb->index), usbn_clk_ctl.u64); + udelay(1); + + /* + * USB Core Initialization + * + * 1. Read USBC_GHWCFG1, USBC_GHWCFG2, USBC_GHWCFG3, USBC_GHWCFG4 to + * determine USB core configuration parameters. + * + * Nothing needed + * + * 2. Program the following fields in the global AHB configuration + * register (USBC_GAHBCFG) + * DMA mode, USBC_GAHBCFG[DMAEn]: 1 = DMA mode, 0 = slave mode + * Burst length, USBC_GAHBCFG[HBSTLEN] = 0 + * Nonperiodic TxFIFO empty level (slave mode only), + * USBC_GAHBCFG[NPTXFEMPLVL] + * Periodic TxFIFO empty level (slave mode only), + * USBC_GAHBCFG[PTXFEMPLVL] + * Global interrupt mask, USBC_GAHBCFG[GLBLINTRMSK] = 1 + */ + usbcx_gahbcfg.u32 = 0; + usbcx_gahbcfg.s.dmaen = !(usb->init_flags & + CVMX_USB_INITIALIZE_FLAGS_NO_DMA); + usbcx_gahbcfg.s.hbstlen = 0; + usbcx_gahbcfg.s.nptxfemplvl = 1; + usbcx_gahbcfg.s.ptxfemplvl = 1; + usbcx_gahbcfg.s.glblintrmsk = 1; + cvmx_usb_write_csr32(usb, CVMX_USBCX_GAHBCFG(usb->index), + usbcx_gahbcfg.u32); + + /* + * 3. Program the following fields in USBC_GUSBCFG register. + * HS/FS timeout calibration, USBC_GUSBCFG[TOUTCAL] = 0 + * ULPI DDR select, USBC_GUSBCFG[DDRSEL] = 0 + * USB turnaround time, USBC_GUSBCFG[USBTRDTIM] = 0x5 + * PHY low-power clock select, USBC_GUSBCFG[PHYLPWRCLKSEL] = 0 + */ + usbcx_gusbcfg.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GUSBCFG(usb->index)); + usbcx_gusbcfg.s.toutcal = 0; + usbcx_gusbcfg.s.ddrsel = 0; + usbcx_gusbcfg.s.usbtrdtim = 0x5; + usbcx_gusbcfg.s.phylpwrclksel = 0; + cvmx_usb_write_csr32(usb, CVMX_USBCX_GUSBCFG(usb->index), + usbcx_gusbcfg.u32); + + /* + * 4. The software must unmask the following bits in the USBC_GINTMSK + * register. + * OTG interrupt mask, USBC_GINTMSK[OTGINTMSK] = 1 + * Mode mismatch interrupt mask, USBC_GINTMSK[MODEMISMSK] = 1 + */ + usbcx_gintmsk.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GINTMSK(usb->index)); + usbcx_gintmsk.s.otgintmsk = 1; + usbcx_gintmsk.s.modemismsk = 1; + usbcx_gintmsk.s.hchintmsk = 1; + usbcx_gintmsk.s.sofmsk = 0; + /* We need RX FIFO interrupts if we don't have DMA */ + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) + usbcx_gintmsk.s.rxflvlmsk = 1; + cvmx_usb_write_csr32(usb, CVMX_USBCX_GINTMSK(usb->index), + usbcx_gintmsk.u32); + + /* + * Disable all channel interrupts. We'll enable them per channel later. + */ + for (channel = 0; channel < 8; channel++) + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCINTMSKX(channel, usb->index), + 0); + + /* + * Host Port Initialization + * + * 1. Program the host-port interrupt-mask field to unmask, + * USBC_GINTMSK[PRTINT] = 1 + */ + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, prtintmsk, 1); + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, disconnintmsk, 1); + + /* + * 2. Program the USBC_HCFG register to select full-speed host + * or high-speed host. + */ + usbcx_hcfg.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HCFG(usb->index)); + usbcx_hcfg.s.fslssupp = 0; + usbcx_hcfg.s.fslspclksel = 0; + cvmx_usb_write_csr32(usb, CVMX_USBCX_HCFG(usb->index), usbcx_hcfg.u32); + + cvmx_fifo_setup(usb); + + /* + * If the controller is getting port events right after the reset, it + * means the initialization failed. Try resetting the controller again + * in such case. This is seen to happen after cold boot on DSR-1000N. + */ + usbc_gintsts.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GINTSTS(usb->index)); + cvmx_usb_write_csr32(usb, CVMX_USBCX_GINTSTS(usb->index), + usbc_gintsts.u32); + dev_dbg(dev, "gintsts after reset: 0x%x\n", (int)usbc_gintsts.u32); + if (!usbc_gintsts.s.disconnint && !usbc_gintsts.s.prtint) + return 0; + if (retries++ >= 5) + return -EAGAIN; + dev_info(dev, "controller reset failed (gintsts=0x%x) - retrying\n", + (int)usbc_gintsts.u32); + msleep(50); + cvmx_usb_shutdown(usb); + msleep(50); + goto retry; +} + +/** + * Reset a USB port. After this call succeeds, the USB port is + * online and servicing requests. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + */ +static void cvmx_usb_reset_port(struct octeon_hcd *usb) +{ + usb->usbcx_hprt.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HPRT(usb->index)); + + /* Program the port reset bit to start the reset process */ + USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), cvmx_usbcx_hprt, + prtrst, 1); + + /* + * Wait at least 50ms (high speed), or 10ms (full speed) for the reset + * process to complete. + */ + mdelay(50); + + /* Program the port reset bit to 0, USBC_HPRT[PRTRST] = 0 */ + USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), cvmx_usbcx_hprt, + prtrst, 0); + + /* + * Read the port speed field to get the enumerated speed, + * USBC_HPRT[PRTSPD]. + */ + usb->usbcx_hprt.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HPRT(usb->index)); +} + +/** + * Disable a USB port. After this call the USB port will not + * generate data transfers and will not generate events. + * Transactions in process will fail and call their + * associated callbacks. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * + * Returns: 0 or a negative error code. + */ +static int cvmx_usb_disable(struct octeon_hcd *usb) +{ + /* Disable the port */ + USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), cvmx_usbcx_hprt, + prtena, 1); + return 0; +} + +/** + * Get the current state of the USB port. Use this call to + * determine if the usb port has anything connected, is enabled, + * or has some sort of error condition. The return value of this + * call has "changed" bits to signal of the value of some fields + * have changed between calls. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * + * Returns: Port status information + */ +static struct cvmx_usb_port_status cvmx_usb_get_status(struct octeon_hcd *usb) +{ + union cvmx_usbcx_hprt usbc_hprt; + struct cvmx_usb_port_status result; + + memset(&result, 0, sizeof(result)); + + usbc_hprt.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HPRT(usb->index)); + result.port_enabled = usbc_hprt.s.prtena; + result.port_over_current = usbc_hprt.s.prtovrcurract; + result.port_powered = usbc_hprt.s.prtpwr; + result.port_speed = usbc_hprt.s.prtspd; + result.connected = usbc_hprt.s.prtconnsts; + result.connect_change = + result.connected != usb->port_status.connected; + + return result; +} + +/** + * Open a virtual pipe between the host and a USB device. A pipe + * must be opened before data can be transferred between a device + * and Octeon. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @device_addr: + * USB device address to open the pipe to + * (0-127). + * @endpoint_num: + * USB endpoint number to open the pipe to + * (0-15). + * @device_speed: + * The speed of the device the pipe is going + * to. This must match the device's speed, + * which may be different than the port speed. + * @max_packet: The maximum packet length the device can + * transmit/receive (low speed=0-8, full + * speed=0-1023, high speed=0-1024). This value + * comes from the standard endpoint descriptor + * field wMaxPacketSize bits <10:0>. + * @transfer_type: + * The type of transfer this pipe is for. + * @transfer_dir: + * The direction the pipe is in. This is not + * used for control pipes. + * @interval: For ISOCHRONOUS and INTERRUPT transfers, + * this is how often the transfer is scheduled + * for. All other transfers should specify + * zero. The units are in frames (8000/sec at + * high speed, 1000/sec for full speed). + * @multi_count: + * For high speed devices, this is the maximum + * allowed number of packet per microframe. + * Specify zero for non high speed devices. This + * value comes from the standard endpoint descriptor + * field wMaxPacketSize bits <12:11>. + * @hub_device_addr: + * Hub device address this device is connected + * to. Devices connected directly to Octeon + * use zero. This is only used when the device + * is full/low speed behind a high speed hub. + * The address will be of the high speed hub, + * not and full speed hubs after it. + * @hub_port: Which port on the hub the device is + * connected. Use zero for devices connected + * directly to Octeon. Like hub_device_addr, + * this is only used for full/low speed + * devices behind a high speed hub. + * + * Returns: A non-NULL value is a pipe. NULL means an error. + */ +static struct cvmx_usb_pipe *cvmx_usb_open_pipe(struct octeon_hcd *usb, + int device_addr, + int endpoint_num, + enum cvmx_usb_speed + device_speed, + int max_packet, + enum cvmx_usb_transfer + transfer_type, + enum cvmx_usb_direction + transfer_dir, + int interval, int multi_count, + int hub_device_addr, + int hub_port) +{ + struct cvmx_usb_pipe *pipe; + + pipe = kzalloc_obj(*pipe, GFP_ATOMIC); + if (!pipe) + return NULL; + if ((device_speed == CVMX_USB_SPEED_HIGH) && + (transfer_dir == CVMX_USB_DIRECTION_OUT) && + (transfer_type == CVMX_USB_TRANSFER_BULK)) + pipe->flags |= CVMX_USB_PIPE_FLAGS_NEED_PING; + pipe->device_addr = device_addr; + pipe->endpoint_num = endpoint_num; + pipe->device_speed = device_speed; + pipe->max_packet = max_packet; + pipe->transfer_type = transfer_type; + pipe->transfer_dir = transfer_dir; + INIT_LIST_HEAD(&pipe->transactions); + + /* + * All pipes use interval to rate limit NAK processing. Force an + * interval if one wasn't supplied + */ + if (!interval) + interval = 1; + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + pipe->interval = interval * 8; + /* Force start splits to be schedule on uFrame 0 */ + pipe->next_tx_frame = ((usb->frame_number + 7) & ~7) + + pipe->interval; + } else { + pipe->interval = interval; + pipe->next_tx_frame = usb->frame_number + pipe->interval; + } + pipe->multi_count = multi_count; + pipe->hub_device_addr = hub_device_addr; + pipe->hub_port = hub_port; + pipe->pid_toggle = 0; + pipe->split_sc_frame = -1; + list_add_tail(&pipe->node, &usb->idle_pipes); + + /* + * We don't need to tell the hardware about this pipe yet since + * it doesn't have any submitted requests + */ + + return pipe; +} + +/** + * Poll the RX FIFOs and remove data as needed. This function is only used + * in non DMA mode. It is very important that this function be called quickly + * enough to prevent FIFO overflow. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + */ +static void cvmx_usb_poll_rx_fifo(struct octeon_hcd *usb) +{ + union cvmx_usbcx_grxstsph rx_status; + int channel; + int bytes; + u64 address; + u32 *ptr; + + rx_status.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GRXSTSPH(usb->index)); + /* Only read data if IN data is there */ + if (rx_status.s.pktsts != 2) + return; + /* Check if no data is available */ + if (!rx_status.s.bcnt) + return; + + channel = rx_status.s.chnum; + bytes = rx_status.s.bcnt; + if (!bytes) + return; + + /* Get where the DMA engine would have written this data */ + address = cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index) + + channel * 8); + + ptr = cvmx_phys_to_ptr(address); + cvmx_write64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index) + channel * 8, + address + bytes); + + /* Loop writing the FIFO data for this packet into memory */ + while (bytes > 0) { + *ptr++ = cvmx_usb_read_csr32(usb, + USB_FIFO_ADDRESS(channel, usb->index)); + bytes -= 4; + } + CVMX_SYNCW; +} + +/** + * Fill the TX hardware fifo with data out of the software + * fifos + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @fifo: Software fifo to use + * @available: Amount of space in the hardware fifo + * + * Returns: Non zero if the hardware fifo was too small and needs + * to be serviced again. + */ +static int cvmx_usb_fill_tx_hw(struct octeon_hcd *usb, + struct cvmx_usb_tx_fifo *fifo, int available) +{ + /* + * We're done either when there isn't anymore space or the software FIFO + * is empty + */ + while (available && (fifo->head != fifo->tail)) { + int i = fifo->tail; + const u32 *ptr = cvmx_phys_to_ptr(fifo->entry[i].address); + u64 csr_address = USB_FIFO_ADDRESS(fifo->entry[i].channel, + usb->index) ^ 4; + int words = available; + + /* Limit the amount of data to what the SW fifo has */ + if (fifo->entry[i].size <= available) { + words = fifo->entry[i].size; + fifo->tail++; + if (fifo->tail > MAX_CHANNELS) + fifo->tail = 0; + } + + /* Update the next locations and counts */ + available -= words; + fifo->entry[i].address += words * 4; + fifo->entry[i].size -= words; + + /* + * Write the HW fifo data. The read every three writes is due + * to an errata on CN3XXX chips + */ + while (words > 3) { + cvmx_write64_uint32(csr_address, *ptr++); + cvmx_write64_uint32(csr_address, *ptr++); + cvmx_write64_uint32(csr_address, *ptr++); + cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index)); + words -= 3; + } + cvmx_write64_uint32(csr_address, *ptr++); + if (--words) { + cvmx_write64_uint32(csr_address, *ptr++); + if (--words) + cvmx_write64_uint32(csr_address, *ptr++); + } + cvmx_read64_uint64(CVMX_USBNX_DMA0_INB_CHN0(usb->index)); + } + return fifo->head != fifo->tail; +} + +/** + * Check the hardware FIFOs and fill them as needed + * + * @usb: USB device state populated by cvmx_usb_initialize(). + */ +static void cvmx_usb_poll_tx_fifo(struct octeon_hcd *usb) +{ + if (usb->periodic.head != usb->periodic.tail) { + union cvmx_usbcx_hptxsts tx_status; + + tx_status.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HPTXSTS(usb->index)); + if (cvmx_usb_fill_tx_hw(usb, &usb->periodic, + tx_status.s.ptxfspcavail)) + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, ptxfempmsk, 1); + else + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, ptxfempmsk, 0); + } + + if (usb->nonperiodic.head != usb->nonperiodic.tail) { + union cvmx_usbcx_gnptxsts tx_status; + + tx_status.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GNPTXSTS(usb->index)); + if (cvmx_usb_fill_tx_hw(usb, &usb->nonperiodic, + tx_status.s.nptxfspcavail)) + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, nptxfempmsk, 1); + else + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, nptxfempmsk, 0); + } +} + +/** + * Fill the TX FIFO with an outgoing packet + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @channel: Channel number to get packet from + */ +static void cvmx_usb_fill_tx_fifo(struct octeon_hcd *usb, int channel) +{ + union cvmx_usbcx_hccharx hcchar; + union cvmx_usbcx_hcspltx usbc_hcsplt; + union cvmx_usbcx_hctsizx usbc_hctsiz; + struct cvmx_usb_tx_fifo *fifo; + + /* We only need to fill data on outbound channels */ + hcchar.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCCHARX(channel, usb->index)); + if (hcchar.s.epdir != CVMX_USB_DIRECTION_OUT) + return; + + /* OUT Splits only have data on the start and not the complete */ + usbc_hcsplt.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCSPLTX(channel, usb->index)); + if (usbc_hcsplt.s.spltena && usbc_hcsplt.s.compsplt) + return; + + /* + * Find out how many bytes we need to fill and convert it into 32bit + * words. + */ + usbc_hctsiz.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCTSIZX(channel, usb->index)); + if (!usbc_hctsiz.s.xfersize) + return; + + if ((hcchar.s.eptype == CVMX_USB_TRANSFER_INTERRUPT) || + (hcchar.s.eptype == CVMX_USB_TRANSFER_ISOCHRONOUS)) + fifo = &usb->periodic; + else + fifo = &usb->nonperiodic; + + fifo->entry[fifo->head].channel = channel; + fifo->entry[fifo->head].address = + cvmx_read64_uint64(CVMX_USBNX_DMA0_OUTB_CHN0(usb->index) + + channel * 8); + fifo->entry[fifo->head].size = (usbc_hctsiz.s.xfersize + 3) >> 2; + fifo->head++; + if (fifo->head > MAX_CHANNELS) + fifo->head = 0; + + cvmx_usb_poll_tx_fifo(usb); +} + +/** + * Perform channel specific setup for Control transactions. All + * the generic stuff will already have been done in cvmx_usb_start_channel(). + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @channel: Channel to setup + * @pipe: Pipe for control transaction + */ +static void cvmx_usb_start_channel_control(struct octeon_hcd *usb, + int channel, + struct cvmx_usb_pipe *pipe) +{ + struct usb_hcd *hcd = octeon_to_hcd(usb); + struct device *dev = hcd->self.controller; + struct cvmx_usb_transaction *transaction = + list_first_entry(&pipe->transactions, typeof(*transaction), + node); + struct usb_ctrlrequest *header = + cvmx_phys_to_ptr(transaction->control_header); + int bytes_to_transfer = transaction->buffer_length - + transaction->actual_bytes; + int packets_to_transfer; + union cvmx_usbcx_hctsizx usbc_hctsiz; + + usbc_hctsiz.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCTSIZX(channel, usb->index)); + + switch (transaction->stage) { + case CVMX_USB_STAGE_NON_CONTROL: + case CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE: + dev_err(dev, "%s: ERROR - Non control stage\n", __func__); + break; + case CVMX_USB_STAGE_SETUP: + usbc_hctsiz.s.pid = 3; /* Setup */ + bytes_to_transfer = sizeof(*header); + /* All Control operations start with a setup going OUT */ + USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), + cvmx_usbcx_hccharx, epdir, + CVMX_USB_DIRECTION_OUT); + /* + * Setup send the control header instead of the buffer data. The + * buffer data will be used in the next stage + */ + cvmx_write64_uint64(CVMX_USBNX_DMA0_OUTB_CHN0(usb->index) + + channel * 8, + transaction->control_header); + break; + case CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE: + usbc_hctsiz.s.pid = 3; /* Setup */ + bytes_to_transfer = 0; + /* All Control operations start with a setup going OUT */ + USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), + cvmx_usbcx_hccharx, epdir, + CVMX_USB_DIRECTION_OUT); + + USB_SET_FIELD32(CVMX_USBCX_HCSPLTX(channel, usb->index), + cvmx_usbcx_hcspltx, compsplt, 1); + break; + case CVMX_USB_STAGE_DATA: + usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + if (header->bRequestType & USB_DIR_IN) + bytes_to_transfer = 0; + else if (bytes_to_transfer > pipe->max_packet) + bytes_to_transfer = pipe->max_packet; + } + USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), + cvmx_usbcx_hccharx, epdir, + ((header->bRequestType & USB_DIR_IN) ? + CVMX_USB_DIRECTION_IN : + CVMX_USB_DIRECTION_OUT)); + break; + case CVMX_USB_STAGE_DATA_SPLIT_COMPLETE: + usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); + if (!(header->bRequestType & USB_DIR_IN)) + bytes_to_transfer = 0; + USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), + cvmx_usbcx_hccharx, epdir, + ((header->bRequestType & USB_DIR_IN) ? + CVMX_USB_DIRECTION_IN : + CVMX_USB_DIRECTION_OUT)); + USB_SET_FIELD32(CVMX_USBCX_HCSPLTX(channel, usb->index), + cvmx_usbcx_hcspltx, compsplt, 1); + break; + case CVMX_USB_STAGE_STATUS: + usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); + bytes_to_transfer = 0; + USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), + cvmx_usbcx_hccharx, epdir, + ((header->bRequestType & USB_DIR_IN) ? + CVMX_USB_DIRECTION_OUT : + CVMX_USB_DIRECTION_IN)); + break; + case CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE: + usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); + bytes_to_transfer = 0; + USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), + cvmx_usbcx_hccharx, epdir, + ((header->bRequestType & USB_DIR_IN) ? + CVMX_USB_DIRECTION_OUT : + CVMX_USB_DIRECTION_IN)); + USB_SET_FIELD32(CVMX_USBCX_HCSPLTX(channel, usb->index), + cvmx_usbcx_hcspltx, compsplt, 1); + break; + } + + /* + * Make sure the transfer never exceeds the byte limit of the hardware. + * Further bytes will be sent as continued transactions + */ + if (bytes_to_transfer > MAX_TRANSFER_BYTES) { + /* Round MAX_TRANSFER_BYTES to a multiple of out packet size */ + bytes_to_transfer = MAX_TRANSFER_BYTES / pipe->max_packet; + bytes_to_transfer *= pipe->max_packet; + } + + /* + * Calculate the number of packets to transfer. If the length is zero + * we still need to transfer one packet + */ + packets_to_transfer = DIV_ROUND_UP(bytes_to_transfer, + pipe->max_packet); + if (packets_to_transfer == 0) { + packets_to_transfer = 1; + } else if ((packets_to_transfer > 1) && + (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA)) { + /* + * Limit to one packet when not using DMA. Channels must be + * restarted between every packet for IN transactions, so there + * is no reason to do multiple packets in a row + */ + packets_to_transfer = 1; + bytes_to_transfer = packets_to_transfer * pipe->max_packet; + } else if (packets_to_transfer > MAX_TRANSFER_PACKETS) { + /* + * Limit the number of packet and data transferred to what the + * hardware can handle + */ + packets_to_transfer = MAX_TRANSFER_PACKETS; + bytes_to_transfer = packets_to_transfer * pipe->max_packet; + } + + usbc_hctsiz.s.xfersize = bytes_to_transfer; + usbc_hctsiz.s.pktcnt = packets_to_transfer; + + cvmx_usb_write_csr32(usb, CVMX_USBCX_HCTSIZX(channel, usb->index), + usbc_hctsiz.u32); +} + +/** + * Start a channel to perform the pipe's head transaction + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @channel: Channel to setup + * @pipe: Pipe to start + */ +static void cvmx_usb_start_channel(struct octeon_hcd *usb, int channel, + struct cvmx_usb_pipe *pipe) +{ + struct cvmx_usb_transaction *transaction = + list_first_entry(&pipe->transactions, typeof(*transaction), + node); + + /* Make sure all writes to the DMA region get flushed */ + CVMX_SYNCW; + + /* Attach the channel to the pipe */ + usb->pipe_for_channel[channel] = pipe; + pipe->channel = channel; + pipe->flags |= CVMX_USB_PIPE_FLAGS_SCHEDULED; + + /* Mark this channel as in use */ + usb->idle_hardware_channels &= ~(1 << channel); + + /* Enable the channel interrupt bits */ + { + union cvmx_usbcx_hcintx usbc_hcint; + union cvmx_usbcx_hcintmskx usbc_hcintmsk; + union cvmx_usbcx_haintmsk usbc_haintmsk; + + /* Clear all channel status bits */ + usbc_hcint.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCINTX(channel, usb->index)); + + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCINTX(channel, usb->index), + usbc_hcint.u32); + + usbc_hcintmsk.u32 = 0; + usbc_hcintmsk.s.chhltdmsk = 1; + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { + /* + * Channels need these extra interrupts when we aren't + * in DMA mode. + */ + usbc_hcintmsk.s.datatglerrmsk = 1; + usbc_hcintmsk.s.frmovrunmsk = 1; + usbc_hcintmsk.s.bblerrmsk = 1; + usbc_hcintmsk.s.xacterrmsk = 1; + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + /* + * Splits don't generate xfercompl, so we need + * ACK and NYET. + */ + usbc_hcintmsk.s.nyetmsk = 1; + usbc_hcintmsk.s.ackmsk = 1; + } + usbc_hcintmsk.s.nakmsk = 1; + usbc_hcintmsk.s.stallmsk = 1; + usbc_hcintmsk.s.xfercomplmsk = 1; + } + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCINTMSKX(channel, usb->index), + usbc_hcintmsk.u32); + + /* Enable the channel interrupt to propagate */ + usbc_haintmsk.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HAINTMSK(usb->index)); + usbc_haintmsk.s.haintmsk |= 1 << channel; + cvmx_usb_write_csr32(usb, CVMX_USBCX_HAINTMSK(usb->index), + usbc_haintmsk.u32); + } + + /* Setup the location the DMA engine uses. */ + { + u64 reg; + u64 dma_address = transaction->buffer + + transaction->actual_bytes; + + if (transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS) + dma_address = transaction->buffer + + transaction->iso_packets[0].offset + + transaction->actual_bytes; + + if (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) + reg = CVMX_USBNX_DMA0_OUTB_CHN0(usb->index); + else + reg = CVMX_USBNX_DMA0_INB_CHN0(usb->index); + cvmx_write64_uint64(reg + channel * 8, dma_address); + } + + /* Setup both the size of the transfer and the SPLIT characteristics */ + { + union cvmx_usbcx_hcspltx usbc_hcsplt = {.u32 = 0}; + union cvmx_usbcx_hctsizx usbc_hctsiz = {.u32 = 0}; + int packets_to_transfer; + int bytes_to_transfer = transaction->buffer_length - + transaction->actual_bytes; + + /* + * ISOCHRONOUS transactions store each individual transfer size + * in the packet structure, not the global buffer_length + */ + if (transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS) + bytes_to_transfer = + transaction->iso_packets[0].length - + transaction->actual_bytes; + + /* + * We need to do split transactions when we are talking to non + * high speed devices that are behind a high speed hub + */ + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + /* + * On the start split phase (stage is even) record the + * frame number we will need to send the split complete. + * We only store the lower two bits since the time ahead + * can only be two frames + */ + if ((transaction->stage & 1) == 0) { + if (transaction->type == CVMX_USB_TRANSFER_BULK) + pipe->split_sc_frame = + (usb->frame_number + 1) & 0x7f; + else + pipe->split_sc_frame = + (usb->frame_number + 2) & 0x7f; + } else { + pipe->split_sc_frame = -1; + } + + usbc_hcsplt.s.spltena = 1; + usbc_hcsplt.s.hubaddr = pipe->hub_device_addr; + usbc_hcsplt.s.prtaddr = pipe->hub_port; + usbc_hcsplt.s.compsplt = (transaction->stage == + CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE); + + /* + * SPLIT transactions can only ever transmit one data + * packet so limit the transfer size to the max packet + * size + */ + if (bytes_to_transfer > pipe->max_packet) + bytes_to_transfer = pipe->max_packet; + + /* + * ISOCHRONOUS OUT splits are unique in that they limit + * data transfers to 188 byte chunks representing the + * begin/middle/end of the data or all + */ + if (!usbc_hcsplt.s.compsplt && + (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) && + (pipe->transfer_type == + CVMX_USB_TRANSFER_ISOCHRONOUS)) { + /* + * Clear the split complete frame number as + * there isn't going to be a split complete + */ + pipe->split_sc_frame = -1; + /* + * See if we've started this transfer and sent + * data + */ + if (transaction->actual_bytes == 0) { + /* + * Nothing sent yet, this is either a + * begin or the entire payload + */ + if (bytes_to_transfer <= 188) + /* Entire payload in one go */ + usbc_hcsplt.s.xactpos = 3; + else + /* First part of payload */ + usbc_hcsplt.s.xactpos = 2; + } else { + /* + * Continuing the previous data, we must + * either be in the middle or at the end + */ + if (bytes_to_transfer <= 188) + /* End of payload */ + usbc_hcsplt.s.xactpos = 1; + else + /* Middle of payload */ + usbc_hcsplt.s.xactpos = 0; + } + /* + * Again, the transfer size is limited to 188 + * bytes + */ + if (bytes_to_transfer > 188) + bytes_to_transfer = 188; + } + } + + /* + * Make sure the transfer never exceeds the byte limit of the + * hardware. Further bytes will be sent as continued + * transactions + */ + if (bytes_to_transfer > MAX_TRANSFER_BYTES) { + /* + * Round MAX_TRANSFER_BYTES to a multiple of out packet + * size + */ + bytes_to_transfer = MAX_TRANSFER_BYTES / + pipe->max_packet; + bytes_to_transfer *= pipe->max_packet; + } + + /* + * Calculate the number of packets to transfer. If the length is + * zero we still need to transfer one packet + */ + packets_to_transfer = + DIV_ROUND_UP(bytes_to_transfer, pipe->max_packet); + if (packets_to_transfer == 0) { + packets_to_transfer = 1; + } else if ((packets_to_transfer > 1) && + (usb->init_flags & + CVMX_USB_INITIALIZE_FLAGS_NO_DMA)) { + /* + * Limit to one packet when not using DMA. Channels must + * be restarted between every packet for IN + * transactions, so there is no reason to do multiple + * packets in a row + */ + packets_to_transfer = 1; + bytes_to_transfer = packets_to_transfer * + pipe->max_packet; + } else if (packets_to_transfer > MAX_TRANSFER_PACKETS) { + /* + * Limit the number of packet and data transferred to + * what the hardware can handle + */ + packets_to_transfer = MAX_TRANSFER_PACKETS; + bytes_to_transfer = packets_to_transfer * + pipe->max_packet; + } + + usbc_hctsiz.s.xfersize = bytes_to_transfer; + usbc_hctsiz.s.pktcnt = packets_to_transfer; + + /* Update the DATA0/DATA1 toggle */ + usbc_hctsiz.s.pid = cvmx_usb_get_data_pid(pipe); + /* + * High speed pipes may need a hardware ping before they start + */ + if (pipe->flags & CVMX_USB_PIPE_FLAGS_NEED_PING) + usbc_hctsiz.s.dopng = 1; + + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCSPLTX(channel, usb->index), + usbc_hcsplt.u32); + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCTSIZX(channel, usb->index), + usbc_hctsiz.u32); + } + + /* Setup the Host Channel Characteristics Register */ + { + union cvmx_usbcx_hccharx usbc_hcchar = {.u32 = 0}; + + /* + * Set the startframe odd/even properly. This is only used for + * periodic + */ + usbc_hcchar.s.oddfrm = usb->frame_number & 1; + + /* + * Set the number of back to back packets allowed by this + * endpoint. Split transactions interpret "ec" as the number of + * immediate retries of failure. These retries happen too + * quickly, so we disable these entirely for splits + */ + if (cvmx_usb_pipe_needs_split(usb, pipe)) + usbc_hcchar.s.ec = 1; + else if (pipe->multi_count < 1) + usbc_hcchar.s.ec = 1; + else if (pipe->multi_count > 3) + usbc_hcchar.s.ec = 3; + else + usbc_hcchar.s.ec = pipe->multi_count; + + /* Set the rest of the endpoint specific settings */ + usbc_hcchar.s.devaddr = pipe->device_addr; + usbc_hcchar.s.eptype = transaction->type; + usbc_hcchar.s.lspddev = + (pipe->device_speed == CVMX_USB_SPEED_LOW); + usbc_hcchar.s.epdir = pipe->transfer_dir; + usbc_hcchar.s.epnum = pipe->endpoint_num; + usbc_hcchar.s.mps = pipe->max_packet; + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCCHARX(channel, usb->index), + usbc_hcchar.u32); + } + + /* Do transaction type specific fixups as needed */ + switch (transaction->type) { + case CVMX_USB_TRANSFER_CONTROL: + cvmx_usb_start_channel_control(usb, channel, pipe); + break; + case CVMX_USB_TRANSFER_BULK: + case CVMX_USB_TRANSFER_INTERRUPT: + break; + case CVMX_USB_TRANSFER_ISOCHRONOUS: + if (!cvmx_usb_pipe_needs_split(usb, pipe)) { + /* + * ISO transactions require different PIDs depending on + * direction and how many packets are needed + */ + if (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) { + if (pipe->multi_count < 2) /* Need DATA0 */ + USB_SET_FIELD32( + CVMX_USBCX_HCTSIZX(channel, + usb->index), + cvmx_usbcx_hctsizx, pid, 0); + else /* Need MDATA */ + USB_SET_FIELD32( + CVMX_USBCX_HCTSIZX(channel, + usb->index), + cvmx_usbcx_hctsizx, pid, 3); + } + } + break; + } + { + union cvmx_usbcx_hctsizx usbc_hctsiz = { .u32 = + cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCTSIZX(channel, + usb->index)) + }; + transaction->xfersize = usbc_hctsiz.s.xfersize; + transaction->pktcnt = usbc_hctsiz.s.pktcnt; + } + /* Remember when we start a split transaction */ + if (cvmx_usb_pipe_needs_split(usb, pipe)) + usb->active_split = transaction; + USB_SET_FIELD32(CVMX_USBCX_HCCHARX(channel, usb->index), + cvmx_usbcx_hccharx, chena, 1); + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) + cvmx_usb_fill_tx_fifo(usb, channel); +} + +/** + * Find a pipe that is ready to be scheduled to hardware. + * @usb: USB device state populated by cvmx_usb_initialize(). + * @xfer_type: Transfer type + * + * Returns: Pipe or NULL if none are ready + */ +static struct cvmx_usb_pipe *cvmx_usb_find_ready_pipe(struct octeon_hcd *usb, + enum cvmx_usb_transfer xfer_type) +{ + struct list_head *list = usb->active_pipes + xfer_type; + u64 current_frame = usb->frame_number; + struct cvmx_usb_pipe *pipe; + + list_for_each_entry(pipe, list, node) { + struct cvmx_usb_transaction *t = + list_first_entry(&pipe->transactions, typeof(*t), + node); + if (!(pipe->flags & CVMX_USB_PIPE_FLAGS_SCHEDULED) && t && + (pipe->next_tx_frame <= current_frame) && + ((pipe->split_sc_frame == -1) || + ((((int)current_frame - pipe->split_sc_frame) & 0x7f) < + 0x40)) && + (!usb->active_split || (usb->active_split == t))) { + prefetch(t); + return pipe; + } + } + return NULL; +} + +static struct cvmx_usb_pipe *cvmx_usb_next_pipe(struct octeon_hcd *usb, + int is_sof) +{ + struct cvmx_usb_pipe *pipe; + + /* Find a pipe needing service. */ + if (is_sof) { + /* + * Only process periodic pipes on SOF interrupts. This way we + * are sure that the periodic data is sent in the beginning of + * the frame. + */ + pipe = cvmx_usb_find_ready_pipe(usb, + CVMX_USB_TRANSFER_ISOCHRONOUS); + if (pipe) + return pipe; + pipe = cvmx_usb_find_ready_pipe(usb, + CVMX_USB_TRANSFER_INTERRUPT); + if (pipe) + return pipe; + } + pipe = cvmx_usb_find_ready_pipe(usb, CVMX_USB_TRANSFER_CONTROL); + if (pipe) + return pipe; + return cvmx_usb_find_ready_pipe(usb, CVMX_USB_TRANSFER_BULK); +} + +/** + * Called whenever a pipe might need to be scheduled to the + * hardware. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @is_sof: True if this schedule was called on a SOF interrupt. + */ +static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) +{ + int channel; + struct cvmx_usb_pipe *pipe; + int need_sof; + enum cvmx_usb_transfer ttype; + + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { + /* + * Without DMA we need to be careful to not schedule something + * at the end of a frame and cause an overrun. + */ + union cvmx_usbcx_hfnum hfnum = { + .u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HFNUM(usb->index)) + }; + + union cvmx_usbcx_hfir hfir = { + .u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HFIR(usb->index)) + }; + + if (hfnum.s.frrem < hfir.s.frint / 4) + goto done; + } + + while (usb->idle_hardware_channels) { + /* Find an idle channel */ + channel = __fls(usb->idle_hardware_channels); + if (unlikely(channel > 7)) + break; + + pipe = cvmx_usb_next_pipe(usb, is_sof); + if (!pipe) + break; + + cvmx_usb_start_channel(usb, channel, pipe); + } + +done: + /* + * Only enable SOF interrupts when we have transactions pending in the + * future that might need to be scheduled + */ + need_sof = 0; + for (ttype = CVMX_USB_TRANSFER_CONTROL; + ttype <= CVMX_USB_TRANSFER_INTERRUPT; ttype++) { + list_for_each_entry(pipe, &usb->active_pipes[ttype], node) { + if (pipe->next_tx_frame > usb->frame_number) { + need_sof = 1; + break; + } + } + } + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, sofmsk, need_sof); +} + +static void octeon_usb_urb_complete_callback(struct octeon_hcd *usb, + enum cvmx_usb_status status, + struct cvmx_usb_pipe *pipe, + struct cvmx_usb_transaction + *transaction, + int bytes_transferred, + struct urb *urb) +{ + struct usb_hcd *hcd = octeon_to_hcd(usb); + struct device *dev = hcd->self.controller; + + if (likely(status == CVMX_USB_STATUS_OK)) + urb->actual_length = bytes_transferred; + else + urb->actual_length = 0; + + urb->hcpriv = NULL; + + /* For Isochronous transactions we need to update the URB packet status + * list from data in our private copy + */ + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + int i; + /* + * The pointer to the private list is stored in the setup_packet + * field. + */ + struct cvmx_usb_iso_packet *iso_packet = + (struct cvmx_usb_iso_packet *)urb->setup_packet; + /* Recalculate the transfer size by adding up each packet */ + urb->actual_length = 0; + for (i = 0; i < urb->number_of_packets; i++) { + if (iso_packet[i].status == CVMX_USB_STATUS_OK) { + urb->iso_frame_desc[i].status = 0; + urb->iso_frame_desc[i].actual_length = + iso_packet[i].length; + urb->actual_length += + urb->iso_frame_desc[i].actual_length; + } else { + dev_dbg(dev, "ISOCHRONOUS packet=%d of %d status=%d pipe=%p transaction=%p size=%d\n", + i, urb->number_of_packets, + iso_packet[i].status, pipe, + transaction, iso_packet[i].length); + urb->iso_frame_desc[i].status = -EREMOTEIO; + } + } + /* Free the private list now that we don't need it anymore */ + kfree(iso_packet); + urb->setup_packet = NULL; + } + + switch (status) { + case CVMX_USB_STATUS_OK: + urb->status = 0; + break; + case CVMX_USB_STATUS_CANCEL: + if (urb->status == 0) + urb->status = -ENOENT; + break; + case CVMX_USB_STATUS_STALL: + dev_dbg(dev, "status=stall pipe=%p transaction=%p size=%d\n", + pipe, transaction, bytes_transferred); + urb->status = -EPIPE; + break; + case CVMX_USB_STATUS_BABBLEERR: + dev_dbg(dev, "status=babble pipe=%p transaction=%p size=%d\n", + pipe, transaction, bytes_transferred); + urb->status = -EPIPE; + break; + case CVMX_USB_STATUS_SHORT: + dev_dbg(dev, "status=short pipe=%p transaction=%p size=%d\n", + pipe, transaction, bytes_transferred); + urb->status = -EREMOTEIO; + break; + case CVMX_USB_STATUS_ERROR: + case CVMX_USB_STATUS_XACTERR: + case CVMX_USB_STATUS_DATATGLERR: + case CVMX_USB_STATUS_FRAMEERR: + dev_dbg(dev, "status=%d pipe=%p transaction=%p size=%d\n", + status, pipe, transaction, bytes_transferred); + urb->status = -EPROTO; + break; + } + usb_hcd_unlink_urb_from_ep(octeon_to_hcd(usb), urb); + spin_unlock(&usb->lock); + usb_hcd_giveback_urb(octeon_to_hcd(usb), urb, urb->status); + spin_lock(&usb->lock); +} + +/** + * Signal the completion of a transaction and free it. The + * transaction will be removed from the pipe transaction list. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Pipe the transaction is on + * @transaction: + * Transaction that completed + * @complete_code: + * Completion code + */ +static void cvmx_usb_complete(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct cvmx_usb_transaction *transaction, + enum cvmx_usb_status complete_code) +{ + /* If this was a split then clear our split in progress marker */ + if (usb->active_split == transaction) + usb->active_split = NULL; + + /* + * Isochronous transactions need extra processing as they might not be + * done after a single data transfer + */ + if (unlikely(transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS)) { + /* Update the number of bytes transferred in this ISO packet */ + transaction->iso_packets[0].length = transaction->actual_bytes; + transaction->iso_packets[0].status = complete_code; + + /* + * If there are more ISOs pending and we succeeded, schedule the + * next one + */ + if ((transaction->iso_number_packets > 1) && + (complete_code == CVMX_USB_STATUS_OK)) { + /* No bytes transferred for this packet as of yet */ + transaction->actual_bytes = 0; + /* One less ISO waiting to transfer */ + transaction->iso_number_packets--; + /* Increment to the next location in our packet array */ + transaction->iso_packets++; + transaction->stage = CVMX_USB_STAGE_NON_CONTROL; + return; + } + } + + /* Remove the transaction from the pipe list */ + list_del(&transaction->node); + if (list_empty(&pipe->transactions)) + list_move_tail(&pipe->node, &usb->idle_pipes); + octeon_usb_urb_complete_callback(usb, complete_code, pipe, + transaction, + transaction->actual_bytes, + transaction->urb); + kfree(transaction); +} + +/** + * Submit a usb transaction to a pipe. Called for all types + * of transactions. + * + * @usb: + * @pipe: Which pipe to submit to. + * @type: Transaction type + * @buffer: User buffer for the transaction + * @buffer_length: + * User buffer's length in bytes + * @control_header: + * For control transactions, the 8 byte standard header + * @iso_start_frame: + * For ISO transactions, the start frame + * @iso_number_packets: + * For ISO, the number of packet in the transaction. + * @iso_packets: + * A description of each ISO packet + * @urb: URB for the callback + * + * Returns: Transaction or NULL on failure. + */ +static struct cvmx_usb_transaction *cvmx_usb_submit_transaction( + struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + enum cvmx_usb_transfer type, + u64 buffer, + int buffer_length, + u64 control_header, + int iso_start_frame, + int iso_number_packets, + struct cvmx_usb_iso_packet *iso_packets, + struct urb *urb) +{ + struct cvmx_usb_transaction *transaction; + + if (unlikely(pipe->transfer_type != type)) + return NULL; + + transaction = kzalloc_obj(*transaction, GFP_ATOMIC); + if (unlikely(!transaction)) + return NULL; + + transaction->type = type; + transaction->buffer = buffer; + transaction->buffer_length = buffer_length; + transaction->control_header = control_header; + /* FIXME: This is not used, implement it. */ + transaction->iso_start_frame = iso_start_frame; + transaction->iso_number_packets = iso_number_packets; + transaction->iso_packets = iso_packets; + transaction->urb = urb; + if (transaction->type == CVMX_USB_TRANSFER_CONTROL) + transaction->stage = CVMX_USB_STAGE_SETUP; + else + transaction->stage = CVMX_USB_STAGE_NON_CONTROL; + + if (!list_empty(&pipe->transactions)) { + list_add_tail(&transaction->node, &pipe->transactions); + } else { + list_add_tail(&transaction->node, &pipe->transactions); + list_move_tail(&pipe->node, + &usb->active_pipes[pipe->transfer_type]); + + /* + * We may need to schedule the pipe if this was the head of the + * pipe. + */ + cvmx_usb_schedule(usb, 0); + } + + return transaction; +} + +/** + * Call to submit a USB Bulk transfer to a pipe. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Handle to the pipe for the transfer. + * @urb: URB. + * + * Returns: A submitted transaction or NULL on failure. + */ +static struct cvmx_usb_transaction *cvmx_usb_submit_bulk( + struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct urb *urb) +{ + return cvmx_usb_submit_transaction(usb, pipe, CVMX_USB_TRANSFER_BULK, + urb->transfer_dma, + urb->transfer_buffer_length, + 0, /* control_header */ + 0, /* iso_start_frame */ + 0, /* iso_number_packets */ + NULL, /* iso_packets */ + urb); +} + +/** + * Call to submit a USB Interrupt transfer to a pipe. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Handle to the pipe for the transfer. + * @urb: URB returned when the callback is called. + * + * Returns: A submitted transaction or NULL on failure. + */ +static struct cvmx_usb_transaction *cvmx_usb_submit_interrupt( + struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct urb *urb) +{ + return cvmx_usb_submit_transaction(usb, pipe, + CVMX_USB_TRANSFER_INTERRUPT, + urb->transfer_dma, + urb->transfer_buffer_length, + 0, /* control_header */ + 0, /* iso_start_frame */ + 0, /* iso_number_packets */ + NULL, /* iso_packets */ + urb); +} + +/** + * Call to submit a USB Control transfer to a pipe. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Handle to the pipe for the transfer. + * @urb: URB. + * + * Returns: A submitted transaction or NULL on failure. + */ +static struct cvmx_usb_transaction *cvmx_usb_submit_control( + struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct urb *urb) +{ + int buffer_length = urb->transfer_buffer_length; + u64 control_header = urb->setup_dma; + struct usb_ctrlrequest *header = cvmx_phys_to_ptr(control_header); + + if ((header->bRequestType & USB_DIR_IN) == 0) + buffer_length = le16_to_cpu(header->wLength); + + return cvmx_usb_submit_transaction(usb, pipe, + CVMX_USB_TRANSFER_CONTROL, + urb->transfer_dma, buffer_length, + control_header, + 0, /* iso_start_frame */ + 0, /* iso_number_packets */ + NULL, /* iso_packets */ + urb); +} + +/** + * Call to submit a USB Isochronous transfer to a pipe. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Handle to the pipe for the transfer. + * @urb: URB returned when the callback is called. + * + * Returns: A submitted transaction or NULL on failure. + */ +static struct cvmx_usb_transaction *cvmx_usb_submit_isochronous( + struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct urb *urb) +{ + struct cvmx_usb_iso_packet *packets; + + packets = (struct cvmx_usb_iso_packet *)urb->setup_packet; + return cvmx_usb_submit_transaction(usb, pipe, + CVMX_USB_TRANSFER_ISOCHRONOUS, + urb->transfer_dma, + urb->transfer_buffer_length, + 0, /* control_header */ + urb->start_frame, + urb->number_of_packets, + packets, urb); +} + +/** + * Cancel one outstanding request in a pipe. Canceling a request + * can fail if the transaction has already completed before cancel + * is called. Even after a successful cancel call, it may take + * a frame or two for the cvmx_usb_poll() function to call the + * associated callback. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Pipe to cancel requests in. + * @transaction: Transaction to cancel, returned by the submit function. + * + * Returns: 0 or a negative error code. + */ +static int cvmx_usb_cancel(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct cvmx_usb_transaction *transaction) +{ + /* + * If the transaction is the HEAD of the queue and scheduled. We need to + * treat it special + */ + if (list_first_entry(&pipe->transactions, typeof(*transaction), node) == + transaction && (pipe->flags & CVMX_USB_PIPE_FLAGS_SCHEDULED)) { + union cvmx_usbcx_hccharx usbc_hcchar; + + usb->pipe_for_channel[pipe->channel] = NULL; + pipe->flags &= ~CVMX_USB_PIPE_FLAGS_SCHEDULED; + + CVMX_SYNCW; + + usbc_hcchar.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCCHARX(pipe->channel, + usb->index)); + /* + * If the channel isn't enabled then the transaction already + * completed. + */ + if (usbc_hcchar.s.chena) { + usbc_hcchar.s.chdis = 1; + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCCHARX(pipe->channel, + usb->index), + usbc_hcchar.u32); + } + } + cvmx_usb_complete(usb, pipe, transaction, CVMX_USB_STATUS_CANCEL); + return 0; +} + +/** + * Cancel all outstanding requests in a pipe. Logically all this + * does is call cvmx_usb_cancel() in a loop. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Pipe to cancel requests in. + * + * Returns: 0 or a negative error code. + */ +static int cvmx_usb_cancel_all(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe) +{ + struct cvmx_usb_transaction *transaction, *next; + + /* Simply loop through and attempt to cancel each transaction */ + list_for_each_entry_safe(transaction, next, &pipe->transactions, node) { + int result = cvmx_usb_cancel(usb, pipe, transaction); + + if (unlikely(result != 0)) + return result; + } + return 0; +} + +/** + * Close a pipe created with cvmx_usb_open_pipe(). + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * @pipe: Pipe to close. + * + * Returns: 0 or a negative error code. EBUSY is returned if the pipe has + * outstanding transfers. + */ +static int cvmx_usb_close_pipe(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe) +{ + /* Fail if the pipe has pending transactions */ + if (!list_empty(&pipe->transactions)) + return -EBUSY; + + list_del(&pipe->node); + kfree(pipe); + + return 0; +} + +/** + * Get the current USB protocol level frame number. The frame + * number is always in the range of 0-0x7ff. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * + * Returns: USB frame number + */ +static int cvmx_usb_get_frame_number(struct octeon_hcd *usb) +{ + union cvmx_usbcx_hfnum usbc_hfnum; + + usbc_hfnum.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HFNUM(usb->index)); + + return usbc_hfnum.s.frnum; +} + +static void cvmx_usb_transfer_control(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct cvmx_usb_transaction *transaction, + union cvmx_usbcx_hccharx usbc_hcchar, + int buffer_space_left, + int bytes_in_last_packet) +{ + switch (transaction->stage) { + case CVMX_USB_STAGE_NON_CONTROL: + case CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE: + /* This should be impossible */ + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_ERROR); + break; + case CVMX_USB_STAGE_SETUP: + pipe->pid_toggle = 1; + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + transaction->stage = + CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE; + } else { + struct usb_ctrlrequest *header = + cvmx_phys_to_ptr(transaction->control_header); + if (header->wLength) + transaction->stage = CVMX_USB_STAGE_DATA; + else + transaction->stage = CVMX_USB_STAGE_STATUS; + } + break; + case CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE: + { + struct usb_ctrlrequest *header = + cvmx_phys_to_ptr(transaction->control_header); + if (header->wLength) + transaction->stage = CVMX_USB_STAGE_DATA; + else + transaction->stage = CVMX_USB_STAGE_STATUS; + } + break; + case CVMX_USB_STAGE_DATA: + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + transaction->stage = CVMX_USB_STAGE_DATA_SPLIT_COMPLETE; + /* + * For setup OUT data that are splits, + * the hardware doesn't appear to count + * transferred data. Here we manually + * update the data transferred + */ + if (!usbc_hcchar.s.epdir) { + if (buffer_space_left < pipe->max_packet) + transaction->actual_bytes += + buffer_space_left; + else + transaction->actual_bytes += + pipe->max_packet; + } + } else if ((buffer_space_left == 0) || + (bytes_in_last_packet < pipe->max_packet)) { + pipe->pid_toggle = 1; + transaction->stage = CVMX_USB_STAGE_STATUS; + } + break; + case CVMX_USB_STAGE_DATA_SPLIT_COMPLETE: + if ((buffer_space_left == 0) || + (bytes_in_last_packet < pipe->max_packet)) { + pipe->pid_toggle = 1; + transaction->stage = CVMX_USB_STAGE_STATUS; + } else { + transaction->stage = CVMX_USB_STAGE_DATA; + } + break; + case CVMX_USB_STAGE_STATUS: + if (cvmx_usb_pipe_needs_split(usb, pipe)) + transaction->stage = + CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE; + else + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_OK); + break; + case CVMX_USB_STAGE_STATUS_SPLIT_COMPLETE: + cvmx_usb_complete(usb, pipe, transaction, CVMX_USB_STATUS_OK); + break; + } +} + +static void cvmx_usb_transfer_bulk(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct cvmx_usb_transaction *transaction, + union cvmx_usbcx_hcintx usbc_hcint, + int buffer_space_left, + int bytes_in_last_packet) +{ + /* + * The only time a bulk transfer isn't complete when it finishes with + * an ACK is during a split transaction. For splits we need to continue + * the transfer if more data is needed. + */ + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + if (transaction->stage == CVMX_USB_STAGE_NON_CONTROL) + transaction->stage = + CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE; + else if (buffer_space_left && + (bytes_in_last_packet == pipe->max_packet)) + transaction->stage = CVMX_USB_STAGE_NON_CONTROL; + else + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_OK); + } else { + if ((pipe->device_speed == CVMX_USB_SPEED_HIGH) && + (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) && + (usbc_hcint.s.nak)) + pipe->flags |= CVMX_USB_PIPE_FLAGS_NEED_PING; + if (!buffer_space_left || + (bytes_in_last_packet < pipe->max_packet)) + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_OK); + } +} + +static void cvmx_usb_transfer_intr(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct cvmx_usb_transaction *transaction, + int buffer_space_left, + int bytes_in_last_packet) +{ + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + if (transaction->stage == CVMX_USB_STAGE_NON_CONTROL) { + transaction->stage = + CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE; + } else if (buffer_space_left && + (bytes_in_last_packet == pipe->max_packet)) { + transaction->stage = CVMX_USB_STAGE_NON_CONTROL; + } else { + pipe->next_tx_frame += pipe->interval; + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_OK); + } + } else if (!buffer_space_left || + (bytes_in_last_packet < pipe->max_packet)) { + pipe->next_tx_frame += pipe->interval; + cvmx_usb_complete(usb, pipe, transaction, CVMX_USB_STATUS_OK); + } +} + +static void cvmx_usb_transfer_isoc(struct octeon_hcd *usb, + struct cvmx_usb_pipe *pipe, + struct cvmx_usb_transaction *transaction, + int buffer_space_left, + int bytes_in_last_packet, + int bytes_this_transfer) +{ + if (cvmx_usb_pipe_needs_split(usb, pipe)) { + /* + * ISOCHRONOUS OUT splits don't require a complete split stage. + * Instead they use a sequence of begin OUT splits to transfer + * the data 188 bytes at a time. Once the transfer is complete, + * the pipe sleeps until the next schedule interval. + */ + if (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT) { + /* + * If no space left or this wasn't a max size packet + * then this transfer is complete. Otherwise start it + * again to send the next 188 bytes + */ + if (!buffer_space_left || (bytes_this_transfer < 188)) { + pipe->next_tx_frame += pipe->interval; + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_OK); + } + return; + } + if (transaction->stage == + CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE) { + /* + * We are in the incoming data phase. Keep getting data + * until we run out of space or get a small packet + */ + if ((buffer_space_left == 0) || + (bytes_in_last_packet < pipe->max_packet)) { + pipe->next_tx_frame += pipe->interval; + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_OK); + } + } else { + transaction->stage = + CVMX_USB_STAGE_NON_CONTROL_SPLIT_COMPLETE; + } + } else { + pipe->next_tx_frame += pipe->interval; + cvmx_usb_complete(usb, pipe, transaction, CVMX_USB_STATUS_OK); + } +} + +/** + * Poll a channel for status + * + * @usb: USB device + * @channel: Channel to poll + * + * Returns: Zero on success + */ +static int cvmx_usb_poll_channel(struct octeon_hcd *usb, int channel) +{ + struct usb_hcd *hcd = octeon_to_hcd(usb); + struct device *dev = hcd->self.controller; + union cvmx_usbcx_hcintx usbc_hcint; + union cvmx_usbcx_hctsizx usbc_hctsiz; + union cvmx_usbcx_hccharx usbc_hcchar; + struct cvmx_usb_pipe *pipe; + struct cvmx_usb_transaction *transaction; + int bytes_this_transfer; + int bytes_in_last_packet; + int packets_processed; + int buffer_space_left; + + /* Read the interrupt status bits for the channel */ + usbc_hcint.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCINTX(channel, usb->index)); + + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { + usbc_hcchar.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCCHARX(channel, + usb->index)); + + if (usbc_hcchar.s.chena && usbc_hcchar.s.chdis) { + /* + * There seems to be a bug in CN31XX which can cause + * interrupt IN transfers to get stuck until we do a + * write of HCCHARX without changing things + */ + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCCHARX(channel, + usb->index), + usbc_hcchar.u32); + return 0; + } + + /* + * In non DMA mode the channels don't halt themselves. We need + * to manually disable channels that are left running + */ + if (!usbc_hcint.s.chhltd) { + if (usbc_hcchar.s.chena) { + union cvmx_usbcx_hcintmskx hcintmsk; + /* Disable all interrupts except CHHLTD */ + hcintmsk.u32 = 0; + hcintmsk.s.chhltdmsk = 1; + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCINTMSKX(channel, usb->index), + hcintmsk.u32); + usbc_hcchar.s.chdis = 1; + cvmx_usb_write_csr32(usb, + CVMX_USBCX_HCCHARX(channel, usb->index), + usbc_hcchar.u32); + return 0; + } else if (usbc_hcint.s.xfercompl) { + /* + * Successful IN/OUT with transfer complete. + * Channel halt isn't needed. + */ + } else { + dev_err(dev, "USB%d: Channel %d interrupt without halt\n", + usb->index, channel); + return 0; + } + } + } else { + /* + * There is are no interrupts that we need to process when the + * channel is still running + */ + if (!usbc_hcint.s.chhltd) + return 0; + } + + /* Disable the channel interrupts now that it is done */ + cvmx_usb_write_csr32(usb, CVMX_USBCX_HCINTMSKX(channel, usb->index), 0); + usb->idle_hardware_channels |= (1 << channel); + + /* Make sure this channel is tied to a valid pipe */ + pipe = usb->pipe_for_channel[channel]; + prefetch(pipe); + if (!pipe) + return 0; + transaction = list_first_entry(&pipe->transactions, + typeof(*transaction), + node); + prefetch(transaction); + + /* + * Disconnect this pipe from the HW channel. Later the schedule + * function will figure out which pipe needs to go + */ + usb->pipe_for_channel[channel] = NULL; + pipe->flags &= ~CVMX_USB_PIPE_FLAGS_SCHEDULED; + + /* + * Read the channel config info so we can figure out how much data + * transferred + */ + usbc_hcchar.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCCHARX(channel, usb->index)); + usbc_hctsiz.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HCTSIZX(channel, usb->index)); + + /* + * Calculating the number of bytes successfully transferred is dependent + * on the transfer direction + */ + packets_processed = transaction->pktcnt - usbc_hctsiz.s.pktcnt; + if (usbc_hcchar.s.epdir) { + /* + * IN transactions are easy. For every byte received the + * hardware decrements xfersize. All we need to do is subtract + * the current value of xfersize from its starting value and we + * know how many bytes were written to the buffer + */ + bytes_this_transfer = transaction->xfersize - + usbc_hctsiz.s.xfersize; + } else { + /* + * OUT transaction don't decrement xfersize. Instead pktcnt is + * decremented on every successful packet send. The hardware + * does this when it receives an ACK, or NYET. If it doesn't + * receive one of these responses pktcnt doesn't change + */ + bytes_this_transfer = packets_processed * usbc_hcchar.s.mps; + /* + * The last packet may not be a full transfer if we didn't have + * enough data + */ + if (bytes_this_transfer > transaction->xfersize) + bytes_this_transfer = transaction->xfersize; + } + /* Figure out how many bytes were in the last packet of the transfer */ + if (packets_processed) + bytes_in_last_packet = bytes_this_transfer - + (packets_processed - 1) * usbc_hcchar.s.mps; + else + bytes_in_last_packet = bytes_this_transfer; + + /* + * As a special case, setup transactions output the setup header, not + * the user's data. For this reason we don't count setup data as bytes + * transferred + */ + if ((transaction->stage == CVMX_USB_STAGE_SETUP) || + (transaction->stage == CVMX_USB_STAGE_SETUP_SPLIT_COMPLETE)) + bytes_this_transfer = 0; + + /* + * Add the bytes transferred to the running total. It is important that + * bytes_this_transfer doesn't count any data that needs to be + * retransmitted + */ + transaction->actual_bytes += bytes_this_transfer; + if (transaction->type == CVMX_USB_TRANSFER_ISOCHRONOUS) + buffer_space_left = transaction->iso_packets[0].length - + transaction->actual_bytes; + else + buffer_space_left = transaction->buffer_length - + transaction->actual_bytes; + + /* + * We need to remember the PID toggle state for the next transaction. + * The hardware already updated it for the next transaction + */ + pipe->pid_toggle = !(usbc_hctsiz.s.pid == 0); + + /* + * For high speed bulk out, assume the next transaction will need to do + * a ping before proceeding. If this isn't true the ACK processing below + * will clear this flag + */ + if ((pipe->device_speed == CVMX_USB_SPEED_HIGH) && + (pipe->transfer_type == CVMX_USB_TRANSFER_BULK) && + (pipe->transfer_dir == CVMX_USB_DIRECTION_OUT)) + pipe->flags |= CVMX_USB_PIPE_FLAGS_NEED_PING; + + if (WARN_ON_ONCE(bytes_this_transfer < 0)) { + /* + * In some rare cases the DMA engine seems to get stuck and + * keeps substracting same byte count over and over again. In + * such case we just need to fail every transaction. + */ + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_ERROR); + return 0; + } + + if (usbc_hcint.s.stall) { + /* + * STALL as a response means this transaction cannot be + * completed because the device can't process transactions. Tell + * the user. Any data that was transferred will be counted on + * the actual bytes transferred + */ + pipe->pid_toggle = 0; + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_STALL); + } else if (usbc_hcint.s.xacterr) { + /* + * XactErr as a response means the device signaled + * something wrong with the transfer. For example, PID + * toggle errors cause these. + */ + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_XACTERR); + } else if (usbc_hcint.s.bblerr) { + /* Babble Error (BblErr) */ + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_BABBLEERR); + } else if (usbc_hcint.s.datatglerr) { + /* Data toggle error */ + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_DATATGLERR); + } else if (usbc_hcint.s.nyet) { + /* + * NYET as a response is only allowed in three cases: as a + * response to a ping, as a response to a split transaction, and + * as a response to a bulk out. The ping case is handled by + * hardware, so we only have splits and bulk out + */ + if (!cvmx_usb_pipe_needs_split(usb, pipe)) { + transaction->retries = 0; + /* + * If there is more data to go then we need to try + * again. Otherwise this transaction is complete + */ + if ((buffer_space_left == 0) || + (bytes_in_last_packet < pipe->max_packet)) + cvmx_usb_complete(usb, pipe, + transaction, + CVMX_USB_STATUS_OK); + } else { + /* + * Split transactions retry the split complete 4 times + * then rewind to the start split and do the entire + * transactions again + */ + transaction->retries++; + if ((transaction->retries & 0x3) == 0) { + /* + * Rewind to the beginning of the transaction by + * anding off the split complete bit + */ + transaction->stage &= ~1; + pipe->split_sc_frame = -1; + } + } + } else if (usbc_hcint.s.ack) { + transaction->retries = 0; + /* + * The ACK bit can only be checked after the other error bits. + * This is because a multi packet transfer may succeed in a + * number of packets and then get a different response on the + * last packet. In this case both ACK and the last response bit + * will be set. If none of the other response bits is set, then + * the last packet must have been an ACK + * + * Since we got an ACK, we know we don't need to do a ping on + * this pipe + */ + pipe->flags &= ~CVMX_USB_PIPE_FLAGS_NEED_PING; + + switch (transaction->type) { + case CVMX_USB_TRANSFER_CONTROL: + cvmx_usb_transfer_control(usb, pipe, transaction, + usbc_hcchar, + buffer_space_left, + bytes_in_last_packet); + break; + case CVMX_USB_TRANSFER_BULK: + cvmx_usb_transfer_bulk(usb, pipe, transaction, + usbc_hcint, buffer_space_left, + bytes_in_last_packet); + break; + case CVMX_USB_TRANSFER_INTERRUPT: + cvmx_usb_transfer_intr(usb, pipe, transaction, + buffer_space_left, + bytes_in_last_packet); + break; + case CVMX_USB_TRANSFER_ISOCHRONOUS: + cvmx_usb_transfer_isoc(usb, pipe, transaction, + buffer_space_left, + bytes_in_last_packet, + bytes_this_transfer); + break; + } + } else if (usbc_hcint.s.nak) { + /* + * If this was a split then clear our split in progress marker. + */ + if (usb->active_split == transaction) + usb->active_split = NULL; + /* + * NAK as a response means the device couldn't accept the + * transaction, but it should be retried in the future. Rewind + * to the beginning of the transaction by anding off the split + * complete bit. Retry in the next interval + */ + transaction->retries = 0; + transaction->stage &= ~1; + pipe->next_tx_frame += pipe->interval; + if (pipe->next_tx_frame < usb->frame_number) + pipe->next_tx_frame = usb->frame_number + + pipe->interval - + (usb->frame_number - pipe->next_tx_frame) % + pipe->interval; + } else { + struct cvmx_usb_port_status port; + + port = cvmx_usb_get_status(usb); + if (port.port_enabled) { + /* We'll retry the exact same transaction again */ + transaction->retries++; + } else { + /* + * We get channel halted interrupts with no result bits + * sets when the cable is unplugged + */ + cvmx_usb_complete(usb, pipe, transaction, + CVMX_USB_STATUS_ERROR); + } + } + return 0; +} + +static void octeon_usb_port_callback(struct octeon_hcd *usb) +{ + spin_unlock(&usb->lock); + usb_hcd_poll_rh_status(octeon_to_hcd(usb)); + spin_lock(&usb->lock); +} + +/** + * Poll the USB block for status and call all needed callback + * handlers. This function is meant to be called in the interrupt + * handler for the USB controller. It can also be called + * periodically in a loop for non-interrupt based operation. + * + * @usb: USB device state populated by cvmx_usb_initialize(). + * + * Returns: 0 or a negative error code. + */ +static int cvmx_usb_poll(struct octeon_hcd *usb) +{ + union cvmx_usbcx_hfnum usbc_hfnum; + union cvmx_usbcx_gintsts usbc_gintsts; + + prefetch_range(usb, sizeof(*usb)); + + /* Update the frame counter */ + usbc_hfnum.u32 = cvmx_usb_read_csr32(usb, CVMX_USBCX_HFNUM(usb->index)); + if ((usb->frame_number & 0x3fff) > usbc_hfnum.s.frnum) + usb->frame_number += 0x4000; + usb->frame_number &= ~0x3fffull; + usb->frame_number |= usbc_hfnum.s.frnum; + + /* Read the pending interrupts */ + usbc_gintsts.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GINTSTS(usb->index)); + + /* Clear the interrupts now that we know about them */ + cvmx_usb_write_csr32(usb, CVMX_USBCX_GINTSTS(usb->index), + usbc_gintsts.u32); + + if (usbc_gintsts.s.rxflvl) { + /* + * RxFIFO Non-Empty (RxFLvl) + * Indicates that there is at least one packet pending to be + * read from the RxFIFO. + * + * In DMA mode this is handled by hardware + */ + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) + cvmx_usb_poll_rx_fifo(usb); + } + if (usbc_gintsts.s.ptxfemp || usbc_gintsts.s.nptxfemp) { + /* Fill the Tx FIFOs when not in DMA mode */ + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) + cvmx_usb_poll_tx_fifo(usb); + } + if (usbc_gintsts.s.disconnint || usbc_gintsts.s.prtint) { + union cvmx_usbcx_hprt usbc_hprt; + /* + * Disconnect Detected Interrupt (DisconnInt) + * Asserted when a device disconnect is detected. + * + * Host Port Interrupt (PrtInt) + * The core sets this bit to indicate a change in port status of + * one of the O2P USB core ports in Host mode. The application + * must read the Host Port Control and Status (HPRT) register to + * determine the exact event that caused this interrupt. The + * application must clear the appropriate status bit in the Host + * Port Control and Status register to clear this bit. + * + * Call the user's port callback + */ + octeon_usb_port_callback(usb); + /* Clear the port change bits */ + usbc_hprt.u32 = + cvmx_usb_read_csr32(usb, CVMX_USBCX_HPRT(usb->index)); + usbc_hprt.s.prtena = 0; + cvmx_usb_write_csr32(usb, CVMX_USBCX_HPRT(usb->index), + usbc_hprt.u32); + } + if (usbc_gintsts.s.hchint) { + /* + * Host Channels Interrupt (HChInt) + * The core sets this bit to indicate that an interrupt is + * pending on one of the channels of the core (in Host mode). + * The application must read the Host All Channels Interrupt + * (HAINT) register to determine the exact number of the channel + * on which the interrupt occurred, and then read the + * corresponding Host Channel-n Interrupt (HCINTn) register to + * determine the exact cause of the interrupt. The application + * must clear the appropriate status bit in the HCINTn register + * to clear this bit. + */ + union cvmx_usbcx_haint usbc_haint; + + usbc_haint.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_HAINT(usb->index)); + while (usbc_haint.u32) { + int channel; + + channel = __fls(usbc_haint.u32); + cvmx_usb_poll_channel(usb, channel); + usbc_haint.u32 ^= 1 << channel; + } + } + + cvmx_usb_schedule(usb, usbc_gintsts.s.sof); + + return 0; +} + +/* convert between an HCD pointer and the corresponding struct octeon_hcd */ +static inline struct octeon_hcd *hcd_to_octeon(struct usb_hcd *hcd) +{ + return (struct octeon_hcd *)(hcd->hcd_priv); +} + +static irqreturn_t octeon_usb_irq(struct usb_hcd *hcd) +{ + struct octeon_hcd *usb = hcd_to_octeon(hcd); + unsigned long flags; + + spin_lock_irqsave(&usb->lock, flags); + cvmx_usb_poll(usb); + spin_unlock_irqrestore(&usb->lock, flags); + return IRQ_HANDLED; +} + +static int octeon_usb_start(struct usb_hcd *hcd) +{ + hcd->state = HC_STATE_RUNNING; + return 0; +} + +static void octeon_usb_stop(struct usb_hcd *hcd) +{ + hcd->state = HC_STATE_HALT; +} + +static int octeon_usb_get_frame_number(struct usb_hcd *hcd) +{ + struct octeon_hcd *usb = hcd_to_octeon(hcd); + + return cvmx_usb_get_frame_number(usb); +} + +static int octeon_usb_urb_enqueue(struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags) +{ + struct octeon_hcd *usb = hcd_to_octeon(hcd); + struct device *dev = hcd->self.controller; + struct cvmx_usb_transaction *transaction = NULL; + struct cvmx_usb_pipe *pipe; + unsigned long flags; + struct cvmx_usb_iso_packet *iso_packet; + struct usb_host_endpoint *ep = urb->ep; + int rc; + + urb->status = 0; + spin_lock_irqsave(&usb->lock, flags); + + rc = usb_hcd_link_urb_to_ep(hcd, urb); + if (rc) { + spin_unlock_irqrestore(&usb->lock, flags); + return rc; + } + + if (!ep->hcpriv) { + enum cvmx_usb_transfer transfer_type; + enum cvmx_usb_speed speed; + int split_device = 0; + int split_port = 0; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + transfer_type = CVMX_USB_TRANSFER_ISOCHRONOUS; + break; + case PIPE_INTERRUPT: + transfer_type = CVMX_USB_TRANSFER_INTERRUPT; + break; + case PIPE_CONTROL: + transfer_type = CVMX_USB_TRANSFER_CONTROL; + break; + default: + transfer_type = CVMX_USB_TRANSFER_BULK; + break; + } + switch (urb->dev->speed) { + case USB_SPEED_LOW: + speed = CVMX_USB_SPEED_LOW; + break; + case USB_SPEED_FULL: + speed = CVMX_USB_SPEED_FULL; + break; + default: + speed = CVMX_USB_SPEED_HIGH; + break; + } + /* + * For slow devices on high speed ports we need to find the hub + * that does the speed translation so we know where to send the + * split transactions. + */ + if (speed != CVMX_USB_SPEED_HIGH) { + /* + * Start at this device and work our way up the usb + * tree. + */ + struct usb_device *dev = urb->dev; + + while (dev->parent) { + /* + * If our parent is high speed then he'll + * receive the splits. + */ + if (dev->parent->speed == USB_SPEED_HIGH) { + split_device = dev->parent->devnum; + split_port = dev->portnum; + break; + } + /* + * Move up the tree one level. If we make it all + * the way up the tree, then the port must not + * be in high speed mode and we don't need a + * split. + */ + dev = dev->parent; + } + } + pipe = cvmx_usb_open_pipe(usb, usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), speed, + le16_to_cpu(ep->desc.wMaxPacketSize) + & 0x7ff, + transfer_type, + usb_pipein(urb->pipe) ? + CVMX_USB_DIRECTION_IN : + CVMX_USB_DIRECTION_OUT, + urb->interval, + (le16_to_cpu(ep->desc.wMaxPacketSize) + >> 11) & 0x3, + split_device, split_port); + if (!pipe) { + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock_irqrestore(&usb->lock, flags); + dev_dbg(dev, "Failed to create pipe\n"); + return -ENOMEM; + } + ep->hcpriv = pipe; + } else { + pipe = ep->hcpriv; + } + + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + dev_dbg(dev, "Submit isochronous to %d.%d\n", + usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe)); + /* + * Allocate a structure to use for our private list of + * isochronous packets. + */ + iso_packet = kmalloc_objs(struct cvmx_usb_iso_packet, + urb->number_of_packets, GFP_ATOMIC); + if (iso_packet) { + int i; + /* Fill the list with the data from the URB */ + for (i = 0; i < urb->number_of_packets; i++) { + iso_packet[i].offset = + urb->iso_frame_desc[i].offset; + iso_packet[i].length = + urb->iso_frame_desc[i].length; + iso_packet[i].status = CVMX_USB_STATUS_ERROR; + } + /* + * Store a pointer to the list in the URB setup_packet + * field. We know this currently isn't being used and + * this saves us a bunch of logic. + */ + urb->setup_packet = (char *)iso_packet; + transaction = cvmx_usb_submit_isochronous(usb, + pipe, urb); + /* + * If submit failed we need to free our private packet + * list. + */ + if (!transaction) { + urb->setup_packet = NULL; + kfree(iso_packet); + } + } + break; + case PIPE_INTERRUPT: + dev_dbg(dev, "Submit interrupt to %d.%d\n", + usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe)); + transaction = cvmx_usb_submit_interrupt(usb, pipe, urb); + break; + case PIPE_CONTROL: + dev_dbg(dev, "Submit control to %d.%d\n", + usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe)); + transaction = cvmx_usb_submit_control(usb, pipe, urb); + break; + case PIPE_BULK: + dev_dbg(dev, "Submit bulk to %d.%d\n", + usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe)); + transaction = cvmx_usb_submit_bulk(usb, pipe, urb); + break; + } + if (!transaction) { + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock_irqrestore(&usb->lock, flags); + dev_dbg(dev, "Failed to submit\n"); + return -ENOMEM; + } + urb->hcpriv = transaction; + spin_unlock_irqrestore(&usb->lock, flags); + return 0; +} + +static int octeon_usb_urb_dequeue(struct usb_hcd *hcd, + struct urb *urb, + int status) +{ + struct octeon_hcd *usb = hcd_to_octeon(hcd); + unsigned long flags; + int rc; + + if (!urb->dev) + return -EINVAL; + + spin_lock_irqsave(&usb->lock, flags); + + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc) + goto out; + + urb->status = status; + cvmx_usb_cancel(usb, urb->ep->hcpriv, urb->hcpriv); + +out: + spin_unlock_irqrestore(&usb->lock, flags); + + return rc; +} + +static void octeon_usb_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct device *dev = hcd->self.controller; + + if (ep->hcpriv) { + struct octeon_hcd *usb = hcd_to_octeon(hcd); + struct cvmx_usb_pipe *pipe = ep->hcpriv; + unsigned long flags; + + spin_lock_irqsave(&usb->lock, flags); + cvmx_usb_cancel_all(usb, pipe); + if (cvmx_usb_close_pipe(usb, pipe)) + dev_dbg(dev, "Closing pipe %p failed\n", pipe); + spin_unlock_irqrestore(&usb->lock, flags); + ep->hcpriv = NULL; + } +} + +static int octeon_usb_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct octeon_hcd *usb = hcd_to_octeon(hcd); + struct cvmx_usb_port_status port_status; + unsigned long flags; + + spin_lock_irqsave(&usb->lock, flags); + port_status = cvmx_usb_get_status(usb); + spin_unlock_irqrestore(&usb->lock, flags); + buf[0] = port_status.connect_change << 1; + + return buf[0] != 0; +} + +static int octeon_usb_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct octeon_hcd *usb = hcd_to_octeon(hcd); + struct device *dev = hcd->self.controller; + struct cvmx_usb_port_status usb_port_status; + int port_status; + struct usb_hub_descriptor *desc; + unsigned long flags; + + switch (typeReq) { + case ClearHubFeature: + dev_dbg(dev, "ClearHubFeature\n"); + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* Nothing required here */ + break; + default: + return -EINVAL; + } + break; + case ClearPortFeature: + dev_dbg(dev, "ClearPortFeature\n"); + if (wIndex != 1) { + dev_dbg(dev, " INVALID\n"); + return -EINVAL; + } + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + dev_dbg(dev, " ENABLE\n"); + spin_lock_irqsave(&usb->lock, flags); + cvmx_usb_disable(usb); + spin_unlock_irqrestore(&usb->lock, flags); + break; + case USB_PORT_FEAT_SUSPEND: + dev_dbg(dev, " SUSPEND\n"); + /* Not supported on Octeon */ + break; + case USB_PORT_FEAT_POWER: + dev_dbg(dev, " POWER\n"); + /* Not supported on Octeon */ + break; + case USB_PORT_FEAT_INDICATOR: + dev_dbg(dev, " INDICATOR\n"); + /* Port indicator not supported */ + break; + case USB_PORT_FEAT_C_CONNECTION: + dev_dbg(dev, " C_CONNECTION\n"); + /* Clears drivers internal connect status change flag */ + spin_lock_irqsave(&usb->lock, flags); + usb->port_status = cvmx_usb_get_status(usb); + spin_unlock_irqrestore(&usb->lock, flags); + break; + case USB_PORT_FEAT_C_RESET: + dev_dbg(dev, " C_RESET\n"); + /* + * Clears the driver's internal Port Reset Change flag. + */ + spin_lock_irqsave(&usb->lock, flags); + usb->port_status = cvmx_usb_get_status(usb); + spin_unlock_irqrestore(&usb->lock, flags); + break; + case USB_PORT_FEAT_C_ENABLE: + dev_dbg(dev, " C_ENABLE\n"); + /* + * Clears the driver's internal Port Enable/Disable + * Change flag. + */ + spin_lock_irqsave(&usb->lock, flags); + usb->port_status = cvmx_usb_get_status(usb); + spin_unlock_irqrestore(&usb->lock, flags); + break; + case USB_PORT_FEAT_C_SUSPEND: + dev_dbg(dev, " C_SUSPEND\n"); + /* + * Clears the driver's internal Port Suspend Change + * flag, which is set when resume signaling on the host + * port is complete. + */ + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + dev_dbg(dev, " C_OVER_CURRENT\n"); + /* Clears the driver's overcurrent Change flag */ + spin_lock_irqsave(&usb->lock, flags); + usb->port_status = cvmx_usb_get_status(usb); + spin_unlock_irqrestore(&usb->lock, flags); + break; + default: + dev_dbg(dev, " UNKNOWN\n"); + return -EINVAL; + } + break; + case GetHubDescriptor: + dev_dbg(dev, "GetHubDescriptor\n"); + desc = (struct usb_hub_descriptor *)buf; + desc->bDescLength = 9; + desc->bDescriptorType = 0x29; + desc->bNbrPorts = 1; + desc->wHubCharacteristics = cpu_to_le16(0x08); + desc->bPwrOn2PwrGood = 1; + desc->bHubContrCurrent = 0; + desc->u.hs.DeviceRemovable[0] = 0; + desc->u.hs.DeviceRemovable[1] = 0xff; + break; + case GetHubStatus: + dev_dbg(dev, "GetHubStatus\n"); + *(__le32 *)buf = 0; + break; + case GetPortStatus: + dev_dbg(dev, "GetPortStatus\n"); + if (wIndex != 1) { + dev_dbg(dev, " INVALID\n"); + return -EINVAL; + } + + spin_lock_irqsave(&usb->lock, flags); + usb_port_status = cvmx_usb_get_status(usb); + spin_unlock_irqrestore(&usb->lock, flags); + port_status = 0; + + if (usb_port_status.connect_change) { + port_status |= (1 << USB_PORT_FEAT_C_CONNECTION); + dev_dbg(dev, " C_CONNECTION\n"); + } + + if (usb_port_status.port_enabled) { + port_status |= (1 << USB_PORT_FEAT_C_ENABLE); + dev_dbg(dev, " C_ENABLE\n"); + } + + if (usb_port_status.connected) { + port_status |= (1 << USB_PORT_FEAT_CONNECTION); + dev_dbg(dev, " CONNECTION\n"); + } + + if (usb_port_status.port_enabled) { + port_status |= (1 << USB_PORT_FEAT_ENABLE); + dev_dbg(dev, " ENABLE\n"); + } + + if (usb_port_status.port_over_current) { + port_status |= (1 << USB_PORT_FEAT_OVER_CURRENT); + dev_dbg(dev, " OVER_CURRENT\n"); + } + + if (usb_port_status.port_powered) { + port_status |= (1 << USB_PORT_FEAT_POWER); + dev_dbg(dev, " POWER\n"); + } + + if (usb_port_status.port_speed == CVMX_USB_SPEED_HIGH) { + port_status |= USB_PORT_STAT_HIGH_SPEED; + dev_dbg(dev, " HIGHSPEED\n"); + } else if (usb_port_status.port_speed == CVMX_USB_SPEED_LOW) { + port_status |= (1 << USB_PORT_FEAT_LOWSPEED); + dev_dbg(dev, " LOWSPEED\n"); + } + + *((__le32 *)buf) = cpu_to_le32(port_status); + break; + case SetHubFeature: + dev_dbg(dev, "SetHubFeature\n"); + /* No HUB features supported */ + break; + case SetPortFeature: + dev_dbg(dev, "SetPortFeature\n"); + if (wIndex != 1) { + dev_dbg(dev, " INVALID\n"); + return -EINVAL; + } + + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + dev_dbg(dev, " SUSPEND\n"); + return -EINVAL; + case USB_PORT_FEAT_POWER: + dev_dbg(dev, " POWER\n"); + /* + * Program the port power bit to drive VBUS on the USB. + */ + spin_lock_irqsave(&usb->lock, flags); + USB_SET_FIELD32(CVMX_USBCX_HPRT(usb->index), + cvmx_usbcx_hprt, prtpwr, 1); + spin_unlock_irqrestore(&usb->lock, flags); + return 0; + case USB_PORT_FEAT_RESET: + dev_dbg(dev, " RESET\n"); + spin_lock_irqsave(&usb->lock, flags); + cvmx_usb_reset_port(usb); + spin_unlock_irqrestore(&usb->lock, flags); + return 0; + case USB_PORT_FEAT_INDICATOR: + dev_dbg(dev, " INDICATOR\n"); + /* Not supported */ + break; + default: + dev_dbg(dev, " UNKNOWN\n"); + return -EINVAL; + } + break; + default: + dev_dbg(dev, "Unknown root hub request\n"); + return -EINVAL; + } + return 0; +} + +static const struct hc_driver octeon_hc_driver = { + .description = "Octeon USB", + .product_desc = "Octeon Host Controller", + .hcd_priv_size = sizeof(struct octeon_hcd), + .irq = octeon_usb_irq, + .flags = HCD_MEMORY | HCD_DMA | HCD_USB2, + .start = octeon_usb_start, + .stop = octeon_usb_stop, + .urb_enqueue = octeon_usb_urb_enqueue, + .urb_dequeue = octeon_usb_urb_dequeue, + .endpoint_disable = octeon_usb_endpoint_disable, + .get_frame_number = octeon_usb_get_frame_number, + .hub_status_data = octeon_usb_hub_status_data, + .hub_control = octeon_usb_hub_control, + .map_urb_for_dma = octeon_map_urb_for_dma, + .unmap_urb_for_dma = octeon_unmap_urb_for_dma, +}; + +static int octeon_usb_probe(struct platform_device *pdev) +{ + int status; + int initialize_flags; + int usb_num; + struct resource *res_mem; + struct device_node *usbn_node; + int irq = platform_get_irq(pdev, 0); + struct device *dev = &pdev->dev; + struct octeon_hcd *usb; + struct usb_hcd *hcd; + u32 clock_rate = 48000000; + bool is_crystal_clock = false; + const char *clock_type; + int i; + + if (!dev->of_node) { + dev_err(dev, "Error: empty of_node\n"); + return -ENXIO; + } + usbn_node = dev->of_node->parent; + + i = of_property_read_u32(usbn_node, + "clock-frequency", &clock_rate); + if (i) + i = of_property_read_u32(usbn_node, + "refclk-frequency", &clock_rate); + if (i) { + dev_err(dev, "No USBN \"clock-frequency\"\n"); + return -ENXIO; + } + switch (clock_rate) { + case 12000000: + initialize_flags = CVMX_USB_INITIALIZE_FLAGS_CLOCK_12MHZ; + break; + case 24000000: + initialize_flags = CVMX_USB_INITIALIZE_FLAGS_CLOCK_24MHZ; + break; + case 48000000: + initialize_flags = CVMX_USB_INITIALIZE_FLAGS_CLOCK_48MHZ; + break; + default: + dev_err(dev, "Illegal USBN \"clock-frequency\" %u\n", + clock_rate); + return -ENXIO; + } + + i = of_property_read_string(usbn_node, + "cavium,refclk-type", &clock_type); + if (i) + i = of_property_read_string(usbn_node, + "refclk-type", &clock_type); + + if (!i && strcmp("crystal", clock_type) == 0) + is_crystal_clock = true; + + if (is_crystal_clock) + initialize_flags |= CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_XI; + else + initialize_flags |= CVMX_USB_INITIALIZE_FLAGS_CLOCK_XO_GND; + + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res_mem) { + dev_err(dev, "found no memory resource\n"); + return -ENXIO; + } + usb_num = (res_mem->start >> 44) & 1; + + if (irq < 0) { + /* Defective device tree, but we know how to fix it. */ + irq_hw_number_t hwirq = usb_num ? (1 << 6) + 17 : 56; + + irq = irq_create_mapping(NULL, hwirq); + } + + /* + * Set the DMA mask to 64bits so we get buffers already translated for + * DMA. + */ + i = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64)); + if (i) + return i; + + /* + * Only cn52XX and cn56XX have DWC_OTG USB hardware and the + * IOB priority registers. Under heavy network load USB + * hardware can be starved by the IOB causing a crash. Give + * it a priority boost if it has been waiting more than 400 + * cycles to avoid this situation. + * + * Testing indicates that a cnt_val of 8192 is not sufficient, + * but no failures are seen with 4096. We choose a value of + * 400 to give a safety factor of 10. + */ + if (OCTEON_IS_MODEL(OCTEON_CN52XX) || OCTEON_IS_MODEL(OCTEON_CN56XX)) { + union cvmx_iob_n2c_l2c_pri_cnt pri_cnt; + + pri_cnt.u64 = 0; + pri_cnt.s.cnt_enb = 1; + pri_cnt.s.cnt_val = 400; + cvmx_write_csr(CVMX_IOB_N2C_L2C_PRI_CNT, pri_cnt.u64); + } + + hcd = usb_create_hcd(&octeon_hc_driver, dev, dev_name(dev)); + if (!hcd) { + dev_dbg(dev, "Failed to allocate memory for HCD\n"); + return -1; + } + hcd->uses_new_polling = 1; + usb = (struct octeon_hcd *)hcd->hcd_priv; + + spin_lock_init(&usb->lock); + + usb->init_flags = initialize_flags; + + /* Initialize the USB state structure */ + usb->index = usb_num; + INIT_LIST_HEAD(&usb->idle_pipes); + for (i = 0; i < ARRAY_SIZE(usb->active_pipes); i++) + INIT_LIST_HEAD(&usb->active_pipes[i]); + + /* Due to an errata, CN31XX doesn't support DMA */ + if (OCTEON_IS_MODEL(OCTEON_CN31XX)) { + usb->init_flags |= CVMX_USB_INITIALIZE_FLAGS_NO_DMA; + /* Only use one channel with non DMA */ + usb->idle_hardware_channels = 0x1; + } else if (OCTEON_IS_MODEL(OCTEON_CN5XXX)) { + /* CN5XXX have an errata with channel 3 */ + usb->idle_hardware_channels = 0xf7; + } else { + usb->idle_hardware_channels = 0xff; + } + + status = cvmx_usb_initialize(dev, usb); + if (status) { + dev_dbg(dev, "USB initialization failed with %d\n", status); + usb_put_hcd(hcd); + return -1; + } + + status = usb_add_hcd(hcd, irq, 0); + if (status) { + dev_dbg(dev, "USB add HCD failed with %d\n", status); + usb_put_hcd(hcd); + return -1; + } + device_wakeup_enable(hcd->self.controller); + + dev_info(dev, "Registered HCD for port %d on irq %d\n", usb_num, irq); + + return 0; +} + +static void octeon_usb_remove(struct platform_device *pdev) +{ + int status; + struct device *dev = &pdev->dev; + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct octeon_hcd *usb = hcd_to_octeon(hcd); + unsigned long flags; + + usb_remove_hcd(hcd); + spin_lock_irqsave(&usb->lock, flags); + status = cvmx_usb_shutdown(usb); + spin_unlock_irqrestore(&usb->lock, flags); + if (status) + dev_dbg(dev, "USB shutdown failed with %d\n", status); + + usb_put_hcd(hcd); +} + +static const struct of_device_id octeon_usb_match[] = { + { + .compatible = "cavium,octeon-5750-usbc", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, octeon_usb_match); + +static struct platform_driver octeon_usb_driver = { + .driver = { + .name = "octeon-hcd", + .of_match_table = octeon_usb_match, + }, + .probe = octeon_usb_probe, + .remove = octeon_usb_remove, +}; + +static int __init octeon_usb_driver_init(void) +{ + if (usb_disabled()) + return 0; + + return platform_driver_register(&octeon_usb_driver); +} +module_init(octeon_usb_driver_init); + +static void __exit octeon_usb_driver_exit(void) +{ + if (usb_disabled()) + return; + + platform_driver_unregister(&octeon_usb_driver); +} +module_exit(octeon_usb_driver_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cavium, Inc. "); +MODULE_DESCRIPTION("Cavium Inc. OCTEON USB Host driver."); diff --git a/drivers/usb/host/octeon-hcd.h b/drivers/usb/host/octeon-hcd.h new file mode 100644 index 000000000..9ed619c93 --- /dev/null +++ b/drivers/usb/host/octeon-hcd.h @@ -0,0 +1,1847 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Octeon HCD hardware register definitions. + * + * 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. + * + * Some parts of the code were originally released under BSD license: + * + * Copyright (c) 2003-2010 Cavium Networks (support@cavium.com). All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Cavium Networks 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, including technical data, may be subject to U.S. export + * control laws, including the U.S. Export Administration Act and its associated + * regulations, and may be subject to export or import regulations in other + * countries. + * + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" + * AND WITH ALL FAULTS AND CAVIUM NETWORKS MAKES NO PROMISES, REPRESENTATIONS OR + * WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO + * THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY REPRESENTATION + * OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT DEFECTS, AND CAVIUM + * SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES OF TITLE, + * MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, LACK OF + * VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION OR + * CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK ARISING OUT OF USE OR + * PERFORMANCE OF THE SOFTWARE LIES WITH YOU. + */ + +#ifndef __OCTEON_HCD_H__ +#define __OCTEON_HCD_H__ + +#include + +#define CVMX_USBCXBASE 0x00016F0010000000ull +#define CVMX_USBCXREG1(reg, bid) \ + (CVMX_ADD_IO_SEG(CVMX_USBCXBASE | reg) + \ + ((bid) & 1) * 0x100000000000ull) +#define CVMX_USBCXREG2(reg, bid, off) \ + (CVMX_ADD_IO_SEG(CVMX_USBCXBASE | reg) + \ + (((off) & 7) + ((bid) & 1) * 0x8000000000ull) * 32) + +#define CVMX_USBCX_GAHBCFG(bid) CVMX_USBCXREG1(0x008, bid) +#define CVMX_USBCX_GHWCFG3(bid) CVMX_USBCXREG1(0x04c, bid) +#define CVMX_USBCX_GINTMSK(bid) CVMX_USBCXREG1(0x018, bid) +#define CVMX_USBCX_GINTSTS(bid) CVMX_USBCXREG1(0x014, bid) +#define CVMX_USBCX_GNPTXFSIZ(bid) CVMX_USBCXREG1(0x028, bid) +#define CVMX_USBCX_GNPTXSTS(bid) CVMX_USBCXREG1(0x02c, bid) +#define CVMX_USBCX_GOTGCTL(bid) CVMX_USBCXREG1(0x000, bid) +#define CVMX_USBCX_GRSTCTL(bid) CVMX_USBCXREG1(0x010, bid) +#define CVMX_USBCX_GRXFSIZ(bid) CVMX_USBCXREG1(0x024, bid) +#define CVMX_USBCX_GRXSTSPH(bid) CVMX_USBCXREG1(0x020, bid) +#define CVMX_USBCX_GUSBCFG(bid) CVMX_USBCXREG1(0x00c, bid) +#define CVMX_USBCX_HAINT(bid) CVMX_USBCXREG1(0x414, bid) +#define CVMX_USBCX_HAINTMSK(bid) CVMX_USBCXREG1(0x418, bid) +#define CVMX_USBCX_HCCHARX(off, bid) CVMX_USBCXREG2(0x500, bid, off) +#define CVMX_USBCX_HCFG(bid) CVMX_USBCXREG1(0x400, bid) +#define CVMX_USBCX_HCINTMSKX(off, bid) CVMX_USBCXREG2(0x50c, bid, off) +#define CVMX_USBCX_HCINTX(off, bid) CVMX_USBCXREG2(0x508, bid, off) +#define CVMX_USBCX_HCSPLTX(off, bid) CVMX_USBCXREG2(0x504, bid, off) +#define CVMX_USBCX_HCTSIZX(off, bid) CVMX_USBCXREG2(0x510, bid, off) +#define CVMX_USBCX_HFIR(bid) CVMX_USBCXREG1(0x404, bid) +#define CVMX_USBCX_HFNUM(bid) CVMX_USBCXREG1(0x408, bid) +#define CVMX_USBCX_HPRT(bid) CVMX_USBCXREG1(0x440, bid) +#define CVMX_USBCX_HPTXFSIZ(bid) CVMX_USBCXREG1(0x100, bid) +#define CVMX_USBCX_HPTXSTS(bid) CVMX_USBCXREG1(0x410, bid) + +#define CVMX_USBNXBID1(bid) (((bid) & 1) * 0x10000000ull) +#define CVMX_USBNXBID2(bid) (((bid) & 1) * 0x100000000000ull) + +#define CVMX_USBNXREG1(reg, bid) \ + (CVMX_ADD_IO_SEG(0x0001180068000000ull | reg) + CVMX_USBNXBID1(bid)) +#define CVMX_USBNXREG2(reg, bid) \ + (CVMX_ADD_IO_SEG(0x00016F0000000000ull | reg) + CVMX_USBNXBID2(bid)) + +#define CVMX_USBNX_CLK_CTL(bid) CVMX_USBNXREG1(0x10, bid) +#define CVMX_USBNX_DMA0_INB_CHN0(bid) CVMX_USBNXREG2(0x818, bid) +#define CVMX_USBNX_DMA0_OUTB_CHN0(bid) CVMX_USBNXREG2(0x858, bid) +#define CVMX_USBNX_USBP_CTL_STATUS(bid) CVMX_USBNXREG1(0x18, bid) + +/** + * cvmx_usbc#_gahbcfg + * + * Core AHB Configuration Register (GAHBCFG) + * + * This register can be used to configure the core after power-on or a change in + * mode of operation. This register mainly contains AHB system-related + * configuration parameters. The AHB is the processor interface to the O2P USB + * core. In general, software need not know about this interface except to + * program the values as specified. + * + * The application must program this register as part of the O2P USB core + * initialization. Do not change this register after the initial programming. + */ +union cvmx_usbcx_gahbcfg { + u32 u32; + /** + * struct cvmx_usbcx_gahbcfg_s + * @ptxfemplvl: Periodic TxFIFO Empty Level (PTxFEmpLvl) + * Software should set this bit to 0x1. + * Indicates when the Periodic TxFIFO Empty Interrupt bit in the + * Core Interrupt register (GINTSTS.PTxFEmp) is triggered. This + * bit is used only in Slave mode. + * * 1'b0: GINTSTS.PTxFEmp interrupt indicates that the Periodic + * TxFIFO is half empty + * * 1'b1: GINTSTS.PTxFEmp interrupt indicates that the Periodic + * TxFIFO is completely empty + * @nptxfemplvl: Non-Periodic TxFIFO Empty Level (NPTxFEmpLvl) + * Software should set this bit to 0x1. + * Indicates when the Non-Periodic TxFIFO Empty Interrupt bit in + * the Core Interrupt register (GINTSTS.NPTxFEmp) is triggered. + * This bit is used only in Slave mode. + * * 1'b0: GINTSTS.NPTxFEmp interrupt indicates that the Non- + * Periodic TxFIFO is half empty + * * 1'b1: GINTSTS.NPTxFEmp interrupt indicates that the Non- + * Periodic TxFIFO is completely empty + * @dmaen: DMA Enable (DMAEn) + * * 1'b0: Core operates in Slave mode + * * 1'b1: Core operates in a DMA mode + * @hbstlen: Burst Length/Type (HBstLen) + * This field has not effect and should be left as 0x0. + * @glblintrmsk: Global Interrupt Mask (GlblIntrMsk) + * Software should set this field to 0x1. + * The application uses this bit to mask or unmask the interrupt + * line assertion to itself. Irrespective of this bit's setting, + * the interrupt status registers are updated by the core. + * * 1'b0: Mask the interrupt assertion to the application. + * * 1'b1: Unmask the interrupt assertion to the application. + */ + struct cvmx_usbcx_gahbcfg_s { + __BITFIELD_FIELD(u32 reserved_9_31 : 23, + __BITFIELD_FIELD(u32 ptxfemplvl : 1, + __BITFIELD_FIELD(u32 nptxfemplvl : 1, + __BITFIELD_FIELD(u32 reserved_6_6 : 1, + __BITFIELD_FIELD(u32 dmaen : 1, + __BITFIELD_FIELD(u32 hbstlen : 4, + __BITFIELD_FIELD(u32 glblintrmsk : 1, + ;))))))) + } s; +}; + +/** + * cvmx_usbc#_ghwcfg3 + * + * User HW Config3 Register (GHWCFG3) + * + * This register contains the configuration options of the O2P USB core. + */ +union cvmx_usbcx_ghwcfg3 { + u32 u32; + /** + * struct cvmx_usbcx_ghwcfg3_s + * @dfifodepth: DFIFO Depth (DfifoDepth) + * This value is in terms of 32-bit words. + * * Minimum value is 32 + * * Maximum value is 32768 + * @ahbphysync: AHB and PHY Synchronous (AhbPhySync) + * Indicates whether AHB and PHY clocks are synchronous to + * each other. + * * 1'b0: No + * * 1'b1: Yes + * This bit is tied to 1. + * @rsttype: Reset Style for Clocked always Blocks in RTL (RstType) + * * 1'b0: Asynchronous reset is used in the core + * * 1'b1: Synchronous reset is used in the core + * @optfeature: Optional Features Removed (OptFeature) + * Indicates whether the User ID register, GPIO interface ports, + * and SOF toggle and counter ports were removed for gate count + * optimization. + * @vendor_control_interface_support: Vendor Control Interface Support + * * 1'b0: Vendor Control Interface is not available on the core. + * * 1'b1: Vendor Control Interface is available. + * @i2c_selection: I2C Selection + * * 1'b0: I2C Interface is not available on the core. + * * 1'b1: I2C Interface is available on the core. + * @otgen: OTG Function Enabled (OtgEn) + * The application uses this bit to indicate the O2P USB core's + * OTG capabilities. + * * 1'b0: Not OTG capable + * * 1'b1: OTG Capable + * @pktsizewidth: Width of Packet Size Counters (PktSizeWidth) + * * 3'b000: 4 bits + * * 3'b001: 5 bits + * * 3'b010: 6 bits + * * 3'b011: 7 bits + * * 3'b100: 8 bits + * * 3'b101: 9 bits + * * 3'b110: 10 bits + * * Others: Reserved + * @xfersizewidth: Width of Transfer Size Counters (XferSizeWidth) + * * 4'b0000: 11 bits + * * 4'b0001: 12 bits + * - ... + * * 4'b1000: 19 bits + * * Others: Reserved + */ + struct cvmx_usbcx_ghwcfg3_s { + __BITFIELD_FIELD(u32 dfifodepth : 16, + __BITFIELD_FIELD(u32 reserved_13_15 : 3, + __BITFIELD_FIELD(u32 ahbphysync : 1, + __BITFIELD_FIELD(u32 rsttype : 1, + __BITFIELD_FIELD(u32 optfeature : 1, + __BITFIELD_FIELD(u32 vendor_control_interface_support : 1, + __BITFIELD_FIELD(u32 i2c_selection : 1, + __BITFIELD_FIELD(u32 otgen : 1, + __BITFIELD_FIELD(u32 pktsizewidth : 3, + __BITFIELD_FIELD(u32 xfersizewidth : 4, + ;)))))))))) + } s; +}; + +/** + * cvmx_usbc#_gintmsk + * + * Core Interrupt Mask Register (GINTMSK) + * + * This register works with the Core Interrupt register to interrupt the + * application. When an interrupt bit is masked, the interrupt associated with + * that bit will not be generated. However, the Core Interrupt (GINTSTS) + * register bit corresponding to that interrupt will still be set. + * Mask interrupt: 1'b0, Unmask interrupt: 1'b1 + */ +union cvmx_usbcx_gintmsk { + u32 u32; + /** + * struct cvmx_usbcx_gintmsk_s + * @wkupintmsk: Resume/Remote Wakeup Detected Interrupt Mask + * (WkUpIntMsk) + * @sessreqintmsk: Session Request/New Session Detected Interrupt Mask + * (SessReqIntMsk) + * @disconnintmsk: Disconnect Detected Interrupt Mask (DisconnIntMsk) + * @conidstschngmsk: Connector ID Status Change Mask (ConIDStsChngMsk) + * @ptxfempmsk: Periodic TxFIFO Empty Mask (PTxFEmpMsk) + * @hchintmsk: Host Channels Interrupt Mask (HChIntMsk) + * @prtintmsk: Host Port Interrupt Mask (PrtIntMsk) + * @fetsuspmsk: Data Fetch Suspended Mask (FetSuspMsk) + * @incomplpmsk: Incomplete Periodic Transfer Mask (incomplPMsk) + * Incomplete Isochronous OUT Transfer Mask + * (incompISOOUTMsk) + * @incompisoinmsk: Incomplete Isochronous IN Transfer Mask + * (incompISOINMsk) + * @oepintmsk: OUT Endpoints Interrupt Mask (OEPIntMsk) + * @inepintmsk: IN Endpoints Interrupt Mask (INEPIntMsk) + * @epmismsk: Endpoint Mismatch Interrupt Mask (EPMisMsk) + * @eopfmsk: End of Periodic Frame Interrupt Mask (EOPFMsk) + * @isooutdropmsk: Isochronous OUT Packet Dropped Interrupt Mask + * (ISOOutDropMsk) + * @enumdonemsk: Enumeration Done Mask (EnumDoneMsk) + * @usbrstmsk: USB Reset Mask (USBRstMsk) + * @usbsuspmsk: USB Suspend Mask (USBSuspMsk) + * @erlysuspmsk: Early Suspend Mask (ErlySuspMsk) + * @i2cint: I2C Interrupt Mask (I2CINT) + * @ulpickintmsk: ULPI Carkit Interrupt Mask (ULPICKINTMsk) + * I2C Carkit Interrupt Mask (I2CCKINTMsk) + * @goutnakeffmsk: Global OUT NAK Effective Mask (GOUTNakEffMsk) + * @ginnakeffmsk: Global Non-Periodic IN NAK Effective Mask + * (GINNakEffMsk) + * @nptxfempmsk: Non-Periodic TxFIFO Empty Mask (NPTxFEmpMsk) + * @rxflvlmsk: Receive FIFO Non-Empty Mask (RxFLvlMsk) + * @sofmsk: Start of (micro)Frame Mask (SofMsk) + * @otgintmsk: OTG Interrupt Mask (OTGIntMsk) + * @modemismsk: Mode Mismatch Interrupt Mask (ModeMisMsk) + */ + struct cvmx_usbcx_gintmsk_s { + __BITFIELD_FIELD(u32 wkupintmsk : 1, + __BITFIELD_FIELD(u32 sessreqintmsk : 1, + __BITFIELD_FIELD(u32 disconnintmsk : 1, + __BITFIELD_FIELD(u32 conidstschngmsk : 1, + __BITFIELD_FIELD(u32 reserved_27_27 : 1, + __BITFIELD_FIELD(u32 ptxfempmsk : 1, + __BITFIELD_FIELD(u32 hchintmsk : 1, + __BITFIELD_FIELD(u32 prtintmsk : 1, + __BITFIELD_FIELD(u32 reserved_23_23 : 1, + __BITFIELD_FIELD(u32 fetsuspmsk : 1, + __BITFIELD_FIELD(u32 incomplpmsk : 1, + __BITFIELD_FIELD(u32 incompisoinmsk : 1, + __BITFIELD_FIELD(u32 oepintmsk : 1, + __BITFIELD_FIELD(u32 inepintmsk : 1, + __BITFIELD_FIELD(u32 epmismsk : 1, + __BITFIELD_FIELD(u32 reserved_16_16 : 1, + __BITFIELD_FIELD(u32 eopfmsk : 1, + __BITFIELD_FIELD(u32 isooutdropmsk : 1, + __BITFIELD_FIELD(u32 enumdonemsk : 1, + __BITFIELD_FIELD(u32 usbrstmsk : 1, + __BITFIELD_FIELD(u32 usbsuspmsk : 1, + __BITFIELD_FIELD(u32 erlysuspmsk : 1, + __BITFIELD_FIELD(u32 i2cint : 1, + __BITFIELD_FIELD(u32 ulpickintmsk : 1, + __BITFIELD_FIELD(u32 goutnakeffmsk : 1, + __BITFIELD_FIELD(u32 ginnakeffmsk : 1, + __BITFIELD_FIELD(u32 nptxfempmsk : 1, + __BITFIELD_FIELD(u32 rxflvlmsk : 1, + __BITFIELD_FIELD(u32 sofmsk : 1, + __BITFIELD_FIELD(u32 otgintmsk : 1, + __BITFIELD_FIELD(u32 modemismsk : 1, + __BITFIELD_FIELD(u32 reserved_0_0 : 1, + ;)))))))))))))))))))))))))))))))) + } s; +}; + +/** + * cvmx_usbc#_gintsts + * + * Core Interrupt Register (GINTSTS) + * + * This register interrupts the application for system-level events in the + * current mode of operation (Device mode or Host mode). It is shown in + * Interrupt. Some of the bits in this register are valid only in Host mode, + * while others are valid in Device mode only. This register also indicates the + * current mode of operation. In order to clear the interrupt status bits of + * type R_SS_WC, the application must write 1'b1 into the bit. The FIFO status + * interrupts are read only; once software reads from or writes to the FIFO + * while servicing these interrupts, FIFO interrupt conditions are cleared + * automatically. + */ +union cvmx_usbcx_gintsts { + u32 u32; + /** + * struct cvmx_usbcx_gintsts_s + * @wkupint: Resume/Remote Wakeup Detected Interrupt (WkUpInt) + * In Device mode, this interrupt is asserted when a resume is + * detected on the USB. In Host mode, this interrupt is asserted + * when a remote wakeup is detected on the USB. + * For more information on how to use this interrupt, see "Partial + * Power-Down and Clock Gating Programming Model" on + * page 353. + * @sessreqint: Session Request/New Session Detected Interrupt + * (SessReqInt) + * In Host mode, this interrupt is asserted when a session request + * is detected from the device. In Device mode, this interrupt is + * asserted when the utmiotg_bvalid signal goes high. + * For more information on how to use this interrupt, see "Partial + * Power-Down and Clock Gating Programming Model" on + * page 353. + * @disconnint: Disconnect Detected Interrupt (DisconnInt) + * Asserted when a device disconnect is detected. + * @conidstschng: Connector ID Status Change (ConIDStsChng) + * The core sets this bit when there is a change in connector ID + * status. + * @ptxfemp: Periodic TxFIFO Empty (PTxFEmp) + * Asserted when the Periodic Transmit FIFO is either half or + * completely empty and there is space for at least one entry to be + * written in the Periodic Request Queue. The half or completely + * empty status is determined by the Periodic TxFIFO Empty Level + * bit in the Core AHB Configuration register + * (GAHBCFG.PTxFEmpLvl). + * @hchint: Host Channels Interrupt (HChInt) + * The core sets this bit to indicate that an interrupt is pending + * on one of the channels of the core (in Host mode). The + * application must read the Host All Channels Interrupt (HAINT) + * register to determine the exact number of the channel on which + * the interrupt occurred, and then read the corresponding Host + * Channel-n Interrupt (HCINTn) register to determine the exact + * cause of the interrupt. The application must clear the + * appropriate status bit in the HCINTn register to clear this bit. + * @prtint: Host Port Interrupt (PrtInt) + * The core sets this bit to indicate a change in port status of + * one of the O2P USB core ports in Host mode. The application must + * read the Host Port Control and Status (HPRT) register to + * determine the exact event that caused this interrupt. The + * application must clear the appropriate status bit in the Host + * Port Control and Status register to clear this bit. + * @fetsusp: Data Fetch Suspended (FetSusp) + * 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. + * @incomplp: Incomplete Periodic Transfer (incomplP) + * In Host mode, the core sets this interrupt bit when there are + * incomplete periodic transactions still pending which are + * scheduled for the current microframe. + * Incomplete Isochronous OUT Transfer (incompISOOUT) + * The Device mode, the core sets this interrupt to indicate that + * there is at least one isochronous OUT endpoint on which the + * transfer is not completed in the current microframe. This + * interrupt is asserted along with the End of Periodic Frame + * Interrupt (EOPF) bit in this register. + * @incompisoin: Incomplete Isochronous IN Transfer (incompISOIN) + * The core sets this interrupt to indicate that there is at least + * one isochronous IN endpoint on which the transfer is not + * completed in the current microframe. This interrupt is asserted + * along with the End of Periodic Frame Interrupt (EOPF) bit in + * this register. + * @oepint: OUT Endpoints Interrupt (OEPInt) + * The core sets this bit to indicate that an interrupt is pending + * on one of the OUT endpoints of the core (in Device mode). The + * application must read the Device All Endpoints Interrupt + * (DAINT) register to determine the exact number of the OUT + * endpoint on which the interrupt occurred, and then read the + * corresponding Device OUT Endpoint-n Interrupt (DOEPINTn) + * register to determine the exact cause of the interrupt. The + * application must clear the appropriate status bit in the + * corresponding DOEPINTn register to clear this bit. + * @iepint: IN Endpoints Interrupt (IEPInt) + * The core sets this bit to indicate that an interrupt is pending + * on one of the IN endpoints of the core (in Device mode). The + * application must read the Device All Endpoints Interrupt + * (DAINT) register to determine the exact number of the IN + * endpoint on which the interrupt occurred, and then read the + * corresponding Device IN Endpoint-n Interrupt (DIEPINTn) + * register to determine the exact cause of the interrupt. The + * application must clear the appropriate status bit in the + * corresponding DIEPINTn register to clear this bit. + * @epmis: Endpoint Mismatch Interrupt (EPMis) + * Indicates that an IN token has been received for a non-periodic + * endpoint, but the data for another endpoint is present in the + * top of the Non-Periodic Transmit FIFO and the IN endpoint + * mismatch count programmed by the application has expired. + * @eopf: End of Periodic Frame Interrupt (EOPF) + * Indicates that the period specified in the Periodic Frame + * Interval field of the Device Configuration register + * (DCFG.PerFrInt) has been reached in the current microframe. + * @isooutdrop: Isochronous OUT Packet Dropped Interrupt (ISOOutDrop) + * The core sets this bit when it fails to write an isochronous OUT + * packet into the RxFIFO because the RxFIFO doesn't have + * enough space to accommodate a maximum packet size packet + * for the isochronous OUT endpoint. + * @enumdone: Enumeration Done (EnumDone) + * The core sets this bit to indicate that speed enumeration is + * complete. The application must read the Device Status (DSTS) + * register to obtain the enumerated speed. + * @usbrst: USB Reset (USBRst) + * The core sets this bit to indicate that a reset is detected on + * the USB. + * @usbsusp: USB Suspend (USBSusp) + * The core sets this bit to indicate that a suspend was detected + * on the USB. The core enters the Suspended state when there + * is no activity on the phy_line_state_i signal for an extended + * period of time. + * @erlysusp: Early Suspend (ErlySusp) + * The core sets this bit to indicate that an Idle state has been + * detected on the USB for 3 ms. + * @i2cint: I2C Interrupt (I2CINT) + * This bit is always 0x0. + * @ulpickint: ULPI Carkit Interrupt (ULPICKINT) + * This bit is always 0x0. + * @goutnakeff: Global OUT NAK Effective (GOUTNakEff) + * Indicates that the Set Global OUT NAK bit in the Device Control + * register (DCTL.SGOUTNak), set by the application, has taken + * effect in the core. This bit can be cleared by writing the Clear + * Global OUT NAK bit in the Device Control register + * (DCTL.CGOUTNak). + * @ginnakeff: Global IN Non-Periodic NAK Effective (GINNakEff) + * Indicates that the Set Global Non-Periodic IN NAK bit in the + * Device Control register (DCTL.SGNPInNak), set by the + * application, has taken effect in the core. That is, the core has + * sampled the Global IN NAK bit set by the application. This bit + * can be cleared by clearing the Clear Global Non-Periodic IN + * NAK bit in the Device Control register (DCTL.CGNPInNak). + * This interrupt does not necessarily mean that a NAK handshake + * is sent out on the USB. The STALL bit takes precedence over + * the NAK bit. + * @nptxfemp: Non-Periodic TxFIFO Empty (NPTxFEmp) + * This interrupt is asserted when the Non-Periodic TxFIFO is + * either half or completely empty, and there is space for at least + * one entry to be written to the Non-Periodic Transmit Request + * Queue. The half or completely empty status is determined by + * the Non-Periodic TxFIFO Empty Level bit in the Core AHB + * Configuration register (GAHBCFG.NPTxFEmpLvl). + * @rxflvl: RxFIFO Non-Empty (RxFLvl) + * Indicates that there is at least one packet pending to be read + * from the RxFIFO. + * @sof: Start of (micro)Frame (Sof) + * In Host mode, the core sets this bit to indicate that an SOF + * (FS), micro-SOF (HS), or Keep-Alive (LS) is transmitted on the + * USB. The application must write a 1 to this bit to clear the + * interrupt. + * In Device mode, in the core sets this bit to indicate that an + * SOF token has been received on the USB. The application can read + * the Device Status register to get the current (micro)frame + * number. This interrupt is seen only when the core is operating + * at either HS or FS. + * @otgint: OTG Interrupt (OTGInt) + * The core sets this bit to indicate an OTG protocol event. The + * application must read the OTG Interrupt Status (GOTGINT) + * register to determine the exact event that caused this + * interrupt. The application must clear the appropriate status bit + * in the GOTGINT register to clear this bit. + * @modemis: Mode Mismatch Interrupt (ModeMis) + * The core sets this bit when the application is trying to access: + * * A Host mode register, when the core is operating in Device + * mode + * * A Device mode register, when the core is operating in Host + * mode + * The register access is completed on the AHB with an OKAY + * response, but is ignored by the core internally and doesn't + * affect the operation of the core. + * @curmod: Current Mode of Operation (CurMod) + * Indicates the current mode of operation. + * * 1'b0: Device mode + * * 1'b1: Host mode + */ + struct cvmx_usbcx_gintsts_s { + __BITFIELD_FIELD(u32 wkupint : 1, + __BITFIELD_FIELD(u32 sessreqint : 1, + __BITFIELD_FIELD(u32 disconnint : 1, + __BITFIELD_FIELD(u32 conidstschng : 1, + __BITFIELD_FIELD(u32 reserved_27_27 : 1, + __BITFIELD_FIELD(u32 ptxfemp : 1, + __BITFIELD_FIELD(u32 hchint : 1, + __BITFIELD_FIELD(u32 prtint : 1, + __BITFIELD_FIELD(u32 reserved_23_23 : 1, + __BITFIELD_FIELD(u32 fetsusp : 1, + __BITFIELD_FIELD(u32 incomplp : 1, + __BITFIELD_FIELD(u32 incompisoin : 1, + __BITFIELD_FIELD(u32 oepint : 1, + __BITFIELD_FIELD(u32 iepint : 1, + __BITFIELD_FIELD(u32 epmis : 1, + __BITFIELD_FIELD(u32 reserved_16_16 : 1, + __BITFIELD_FIELD(u32 eopf : 1, + __BITFIELD_FIELD(u32 isooutdrop : 1, + __BITFIELD_FIELD(u32 enumdone : 1, + __BITFIELD_FIELD(u32 usbrst : 1, + __BITFIELD_FIELD(u32 usbsusp : 1, + __BITFIELD_FIELD(u32 erlysusp : 1, + __BITFIELD_FIELD(u32 i2cint : 1, + __BITFIELD_FIELD(u32 ulpickint : 1, + __BITFIELD_FIELD(u32 goutnakeff : 1, + __BITFIELD_FIELD(u32 ginnakeff : 1, + __BITFIELD_FIELD(u32 nptxfemp : 1, + __BITFIELD_FIELD(u32 rxflvl : 1, + __BITFIELD_FIELD(u32 sof : 1, + __BITFIELD_FIELD(u32 otgint : 1, + __BITFIELD_FIELD(u32 modemis : 1, + __BITFIELD_FIELD(u32 curmod : 1, + ;)))))))))))))))))))))))))))))))) + } s; +}; + +/** + * cvmx_usbc#_gnptxfsiz + * + * Non-Periodic Transmit FIFO Size Register (GNPTXFSIZ) + * + * The application can program the RAM size and the memory start address for the + * Non-Periodic TxFIFO. + */ +union cvmx_usbcx_gnptxfsiz { + u32 u32; + /** + * struct cvmx_usbcx_gnptxfsiz_s + * @nptxfdep: Non-Periodic TxFIFO Depth (NPTxFDep) + * This value is in terms of 32-bit words. + * Minimum value is 16 + * Maximum value is 32768 + * @nptxfstaddr: Non-Periodic Transmit RAM Start Address (NPTxFStAddr) + * This field contains the memory start address for Non-Periodic + * Transmit FIFO RAM. + */ + struct cvmx_usbcx_gnptxfsiz_s { + __BITFIELD_FIELD(u32 nptxfdep : 16, + __BITFIELD_FIELD(u32 nptxfstaddr : 16, + ;)) + } s; +}; + +/** + * cvmx_usbc#_gnptxsts + * + * Non-Periodic Transmit FIFO/Queue Status Register (GNPTXSTS) + * + * This read-only register contains the free space information for the + * Non-Periodic TxFIFO and the Non-Periodic Transmit Request Queue. + */ +union cvmx_usbcx_gnptxsts { + u32 u32; + /** + * struct cvmx_usbcx_gnptxsts_s + * @nptxqtop: Top of the Non-Periodic Transmit Request Queue (NPTxQTop) + * Entry in the Non-Periodic Tx Request Queue that is currently + * being processed by the MAC. + * * Bits [30:27]: Channel/endpoint number + * * Bits [26:25]: + * - 2'b00: IN/OUT token + * - 2'b01: Zero-length transmit packet (device IN/host OUT) + * - 2'b10: PING/CSPLIT token + * - 2'b11: Channel halt command + * * Bit [24]: Terminate (last entry for selected channel/endpoint) + * @nptxqspcavail: Non-Periodic Transmit Request Queue Space Available + * (NPTxQSpcAvail) + * Indicates the amount of free space available in the Non- + * Periodic Transmit Request Queue. This queue holds both IN + * and OUT requests in Host mode. Device mode has only IN + * requests. + * * 8'h0: Non-Periodic Transmit Request Queue is full + * * 8'h1: 1 location available + * * 8'h2: 2 locations available + * * n: n locations available (0..8) + * * Others: Reserved + * @nptxfspcavail: Non-Periodic TxFIFO Space Avail (NPTxFSpcAvail) + * Indicates the amount of free space available in the Non- + * Periodic TxFIFO. + * Values are in terms of 32-bit words. + * * 16'h0: Non-Periodic TxFIFO is full + * * 16'h1: 1 word available + * * 16'h2: 2 words available + * * 16'hn: n words available (where 0..32768) + * * 16'h8000: 32768 words available + * * Others: Reserved + */ + struct cvmx_usbcx_gnptxsts_s { + __BITFIELD_FIELD(u32 reserved_31_31 : 1, + __BITFIELD_FIELD(u32 nptxqtop : 7, + __BITFIELD_FIELD(u32 nptxqspcavail : 8, + __BITFIELD_FIELD(u32 nptxfspcavail : 16, + ;)))) + } s; +}; + +/** + * cvmx_usbc#_grstctl + * + * Core Reset Register (GRSTCTL) + * + * The application uses this register to reset various hardware features inside + * the core. + */ +union cvmx_usbcx_grstctl { + u32 u32; + /** + * struct cvmx_usbcx_grstctl_s + * @ahbidle: AHB Master Idle (AHBIdle) + * Indicates that the AHB Master State Machine is in the IDLE + * condition. + * @dmareq: DMA Request Signal (DMAReq) + * Indicates that the DMA request is in progress. Used for debug. + * @txfnum: TxFIFO Number (TxFNum) + * This is the FIFO number that must be flushed using the TxFIFO + * Flush bit. This field must not be changed until the core clears + * the TxFIFO Flush bit. + * * 5'h0: Non-Periodic TxFIFO flush + * * 5'h1: Periodic TxFIFO 1 flush in Device mode or Periodic + * TxFIFO flush in Host mode + * * 5'h2: Periodic TxFIFO 2 flush in Device mode + * - ... + * * 5'hF: Periodic TxFIFO 15 flush in Device mode + * * 5'h10: Flush all the Periodic and Non-Periodic TxFIFOs in the + * core + * @txfflsh: TxFIFO Flush (TxFFlsh) + * This bit selectively flushes a single or all transmit FIFOs, but + * cannot do so if the core is in the midst of a transaction. + * The application must only write this bit after checking that the + * core is neither writing to the TxFIFO nor reading from the + * TxFIFO. + * The application must wait until the core clears this bit before + * performing any operations. This bit takes 8 clocks (of phy_clk + * or hclk, whichever is slower) to clear. + * @rxfflsh: RxFIFO Flush (RxFFlsh) + * The application can flush the entire RxFIFO using this bit, but + * must first ensure that the core is not in the middle of a + * transaction. + * The application must only write to this bit after checking that + * the core is neither reading from the RxFIFO nor writing to the + * RxFIFO. + * The application must wait until the bit is cleared before + * performing any other operations. This bit will take 8 clocks + * (slowest of PHY or AHB clock) to clear. + * @intknqflsh: IN Token Sequence Learning Queue Flush (INTknQFlsh) + * The application writes this bit to flush the IN Token Sequence + * Learning Queue. + * @frmcntrrst: Host Frame Counter Reset (FrmCntrRst) + * The application writes this bit to reset the (micro)frame number + * counter inside the core. When the (micro)frame counter is reset, + * the subsequent SOF sent out by the core will have a + * (micro)frame number of 0. + * @hsftrst: HClk Soft Reset (HSftRst) + * The application uses this bit to flush the control logic in the + * AHB Clock domain. Only AHB Clock Domain pipelines are reset. + * * FIFOs are not flushed with this bit. + * * All state machines in the AHB clock domain are reset to the + * Idle state after terminating the transactions on the AHB, + * following the protocol. + * * CSR control bits used by the AHB clock domain state + * machines are cleared. + * * To clear this interrupt, status mask bits that control the + * interrupt status and are generated by the AHB clock domain + * state machine are cleared. + * * Because interrupt status bits are not cleared, the application + * can get the status of any core events that occurred after it set + * this bit. + * This is a self-clearing bit that the core clears after all + * necessary logic is reset in the core. This may take several + * clocks, depending on the core's current state. + * @csftrst: Core Soft Reset (CSftRst) + * Resets the hclk and phy_clock domains as follows: + * * Clears the interrupts and all the CSR registers except the + * following register bits: + * - PCGCCTL.RstPdwnModule + * - PCGCCTL.GateHclk + * - PCGCCTL.PwrClmp + * - PCGCCTL.StopPPhyLPwrClkSelclk + * - GUSBCFG.PhyLPwrClkSel + * - GUSBCFG.DDRSel + * - GUSBCFG.PHYSel + * - GUSBCFG.FSIntf + * - GUSBCFG.ULPI_UTMI_Sel + * - GUSBCFG.PHYIf + * - HCFG.FSLSPclkSel + * - DCFG.DevSpd + * * All module state machines (except the AHB Slave Unit) are + * reset to the IDLE state, and all the transmit FIFOs and the + * receive FIFO are flushed. + * * Any transactions on the AHB Master are terminated as soon + * as possible, after gracefully completing the last data phase of + * an AHB transfer. Any transactions on the USB are terminated + * immediately. + * 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. Once this bit is cleared software should wait at least 3 + * PHY clocks before doing any access to the PHY domain + * (synchronization delay). Software should also should check that + * bit 31 of this register is 1 (AHB Master is IDLE) before + * starting any operation. + * Typically software reset is used during software development + * and also when you dynamically change the PHY selection bits + * in the USB configuration registers listed above. When you + * change the PHY, the corresponding clock for the PHY is + * selected and used in the PHY domain. Once a new clock is + * selected, the PHY domain has to be reset for proper operation. + */ + struct cvmx_usbcx_grstctl_s { + __BITFIELD_FIELD(u32 ahbidle : 1, + __BITFIELD_FIELD(u32 dmareq : 1, + __BITFIELD_FIELD(u32 reserved_11_29 : 19, + __BITFIELD_FIELD(u32 txfnum : 5, + __BITFIELD_FIELD(u32 txfflsh : 1, + __BITFIELD_FIELD(u32 rxfflsh : 1, + __BITFIELD_FIELD(u32 intknqflsh : 1, + __BITFIELD_FIELD(u32 frmcntrrst : 1, + __BITFIELD_FIELD(u32 hsftrst : 1, + __BITFIELD_FIELD(u32 csftrst : 1, + ;)))))))))) + } s; +}; + +/** + * cvmx_usbc#_grxfsiz + * + * Receive FIFO Size Register (GRXFSIZ) + * + * The application can program the RAM size that must be allocated to the + * RxFIFO. + */ +union cvmx_usbcx_grxfsiz { + u32 u32; + /** + * struct cvmx_usbcx_grxfsiz_s + * @rxfdep: RxFIFO Depth (RxFDep) + * This value is in terms of 32-bit words. + * * Minimum value is 16 + * * Maximum value is 32768 + */ + struct cvmx_usbcx_grxfsiz_s { + __BITFIELD_FIELD(u32 reserved_16_31 : 16, + __BITFIELD_FIELD(u32 rxfdep : 16, + ;)) + } s; +}; + +/** + * cvmx_usbc#_grxstsph + * + * Receive Status Read and Pop Register, Host Mode (GRXSTSPH) + * + * A read to the Receive Status Read and Pop register returns and additionally + * pops the top data entry out of the RxFIFO. + * This Description is only valid when the core is in Host Mode. For Device Mode + * use USBC_GRXSTSPD instead. + * NOTE: GRXSTSPH and GRXSTSPD are physically the same register and share the + * same offset in the O2P USB core. The offset difference shown in this + * document is for software clarity and is actually ignored by the + * hardware. + */ +union cvmx_usbcx_grxstsph { + u32 u32; + /** + * struct cvmx_usbcx_grxstsph_s + * @pktsts: Packet Status (PktSts) + * Indicates the status of the received packet + * * 4'b0010: IN data packet received + * * 4'b0011: IN transfer completed (triggers an interrupt) + * * 4'b0101: Data toggle error (triggers an interrupt) + * * 4'b0111: Channel halted (triggers an interrupt) + * * Others: Reserved + * @dpid: Data PID (DPID) + * * 2'b00: DATA0 + * * 2'b10: DATA1 + * * 2'b01: DATA2 + * * 2'b11: MDATA + * @bcnt: Byte Count (BCnt) + * Indicates the byte count of the received IN data packet + * @chnum: Channel Number (ChNum) + * Indicates the channel number to which the current received + * packet belongs. + */ + struct cvmx_usbcx_grxstsph_s { + __BITFIELD_FIELD(u32 reserved_21_31 : 11, + __BITFIELD_FIELD(u32 pktsts : 4, + __BITFIELD_FIELD(u32 dpid : 2, + __BITFIELD_FIELD(u32 bcnt : 11, + __BITFIELD_FIELD(u32 chnum : 4, + ;))))) + } s; +}; + +/** + * cvmx_usbc#_gusbcfg + * + * Core USB Configuration Register (GUSBCFG) + * + * This register can be used to configure the core after power-on or a changing + * to Host mode or Device mode. It contains USB and USB-PHY related + * configuration parameters. The application must program this register before + * starting any transactions on either the AHB or the USB. Do not make changes + * to this register after the initial programming. + */ +union cvmx_usbcx_gusbcfg { + u32 u32; + /** + * struct cvmx_usbcx_gusbcfg_s + * @otgi2csel: UTMIFS or I2C Interface Select (OtgI2CSel) + * This bit is always 0x0. + * @phylpwrclksel: PHY Low-Power Clock Select (PhyLPwrClkSel) + * Software should set this bit to 0x0. + * Selects either 480-MHz or 48-MHz (low-power) PHY mode. In + * FS and LS modes, the PHY can usually operate on a 48-MHz + * clock to save power. + * * 1'b0: 480-MHz Internal PLL clock + * * 1'b1: 48-MHz External Clock + * In 480 MHz mode, the UTMI interface operates at either 60 or + * 30-MHz, depending upon whether 8- or 16-bit data width is + * selected. In 48-MHz mode, the UTMI interface operates at 48 + * MHz in FS mode and at either 48 or 6 MHz in LS mode + * (depending on the PHY vendor). + * This bit drives the utmi_fsls_low_power core output signal, and + * is valid only for UTMI+ PHYs. + * @usbtrdtim: USB Turnaround Time (USBTrdTim) + * Sets the turnaround time in PHY clocks. + * Specifies the response time for a MAC request to the Packet + * FIFO Controller (PFC) to fetch data from the DFIFO (SPRAM). + * This must be programmed to 0x5. + * @hnpcap: HNP-Capable (HNPCap) + * This bit is always 0x0. + * @srpcap: SRP-Capable (SRPCap) + * This bit is always 0x0. + * @ddrsel: ULPI DDR Select (DDRSel) + * Software should set this bit to 0x0. + * @physel: USB 2.0 High-Speed PHY or USB 1.1 Full-Speed Serial + * Software should set this bit to 0x0. + * @fsintf: Full-Speed Serial Interface Select (FSIntf) + * Software should set this bit to 0x0. + * @ulpi_utmi_sel: ULPI or UTMI+ Select (ULPI_UTMI_Sel) + * This bit is always 0x0. + * @phyif: PHY Interface (PHYIf) + * This bit is always 0x1. + * @toutcal: HS/FS Timeout Calibration (TOutCal) + * The number of PHY clocks that the application programs in this + * field is added to the high-speed/full-speed interpacket timeout + * duration in the core to account for any additional delays + * introduced by the PHY. This may be required, since the delay + * introduced by the PHY in generating the linestate condition may + * vary from one PHY to another. + * The USB standard timeout value for high-speed operation is + * 736 to 816 (inclusive) bit times. The USB standard timeout + * value for full-speed operation is 16 to 18 (inclusive) bit + * times. The application must program this field based on the + * speed of enumeration. The number of bit times added per PHY + * clock are: + * High-speed operation: + * * One 30-MHz PHY clock = 16 bit times + * * One 60-MHz PHY clock = 8 bit times + * Full-speed operation: + * * One 30-MHz PHY clock = 0.4 bit times + * * One 60-MHz PHY clock = 0.2 bit times + * * One 48-MHz PHY clock = 0.25 bit times + */ + struct cvmx_usbcx_gusbcfg_s { + __BITFIELD_FIELD(u32 reserved_17_31 : 15, + __BITFIELD_FIELD(u32 otgi2csel : 1, + __BITFIELD_FIELD(u32 phylpwrclksel : 1, + __BITFIELD_FIELD(u32 reserved_14_14 : 1, + __BITFIELD_FIELD(u32 usbtrdtim : 4, + __BITFIELD_FIELD(u32 hnpcap : 1, + __BITFIELD_FIELD(u32 srpcap : 1, + __BITFIELD_FIELD(u32 ddrsel : 1, + __BITFIELD_FIELD(u32 physel : 1, + __BITFIELD_FIELD(u32 fsintf : 1, + __BITFIELD_FIELD(u32 ulpi_utmi_sel : 1, + __BITFIELD_FIELD(u32 phyif : 1, + __BITFIELD_FIELD(u32 toutcal : 3, + ;))))))))))))) + } s; +}; + +/** + * cvmx_usbc#_haint + * + * Host All Channels Interrupt Register (HAINT) + * + * When a significant event occurs on a channel, the Host All Channels Interrupt + * register interrupts the application using the Host Channels Interrupt bit of + * the Core Interrupt register (GINTSTS.HChInt). This is shown in Interrupt. + * There is one interrupt bit per channel, up to a maximum of 16 bits. Bits in + * this register are set and cleared when the application sets and clears bits + * in the corresponding Host Channel-n Interrupt register. + */ +union cvmx_usbcx_haint { + u32 u32; + /** + * struct cvmx_usbcx_haint_s + * @haint: Channel Interrupts (HAINT) + * One bit per channel: Bit 0 for Channel 0, bit 15 for Channel 15 + */ + struct cvmx_usbcx_haint_s { + __BITFIELD_FIELD(u32 reserved_16_31 : 16, + __BITFIELD_FIELD(u32 haint : 16, + ;)) + } s; +}; + +/** + * cvmx_usbc#_haintmsk + * + * Host All Channels Interrupt Mask Register (HAINTMSK) + * + * The Host All Channel Interrupt Mask register works with the Host All Channel + * Interrupt register to interrupt the application when an event occurs on a + * channel. There is one interrupt mask bit per channel, up to a maximum of 16 + * bits. + * Mask interrupt: 1'b0 Unmask interrupt: 1'b1 + */ +union cvmx_usbcx_haintmsk { + u32 u32; + /** + * struct cvmx_usbcx_haintmsk_s + * @haintmsk: Channel Interrupt Mask (HAINTMsk) + * One bit per channel: Bit 0 for channel 0, bit 15 for channel 15 + */ + struct cvmx_usbcx_haintmsk_s { + __BITFIELD_FIELD(u32 reserved_16_31 : 16, + __BITFIELD_FIELD(u32 haintmsk : 16, + ;)) + } s; +}; + +/** + * cvmx_usbc#_hcchar# + * + * Host Channel-n Characteristics Register (HCCHAR) + * + */ +union cvmx_usbcx_hccharx { + u32 u32; + /** + * struct cvmx_usbcx_hccharx_s + * @chena: Channel Enable (ChEna) + * This field is set by the application and cleared by the OTG + * host. + * * 1'b0: Channel disabled + * * 1'b1: Channel enabled + * @chdis: Channel Disable (ChDis) + * The application sets this bit to stop transmitting/receiving + * data on a channel, even before the transfer for that channel is + * complete. The application must wait for the Channel Disabled + * interrupt before treating the channel as disabled. + * @oddfrm: Odd Frame (OddFrm) + * This field is set (reset) by the application to indicate that + * the OTG host must perform a transfer in an odd (micro)frame. + * This field is applicable for only periodic (isochronous and + * interrupt) transactions. + * * 1'b0: Even (micro)frame + * * 1'b1: Odd (micro)frame + * @devaddr: Device Address (DevAddr) + * This field selects the specific device serving as the data + * source or sink. + * @ec: Multi Count (MC) / Error Count (EC) + * When the Split Enable bit of the Host Channel-n Split Control + * register (HCSPLTn.SpltEna) is reset (1'b0), this field indicates + * to the host the number of transactions that should be executed + * per microframe for this endpoint. + * * 2'b00: Reserved. This field yields undefined results. + * * 2'b01: 1 transaction + * * 2'b10: 2 transactions to be issued for this endpoint per + * microframe + * * 2'b11: 3 transactions to be issued for this endpoint per + * microframe + * When HCSPLTn.SpltEna is set (1'b1), this field indicates the + * number of immediate retries to be performed for a periodic split + * transactions on transaction errors. This field must be set to at + * least 2'b01. + * @eptype: Endpoint Type (EPType) + * Indicates the transfer type selected. + * * 2'b00: Control + * * 2'b01: Isochronous + * * 2'b10: Bulk + * * 2'b11: Interrupt + * @lspddev: Low-Speed Device (LSpdDev) + * This field is set by the application to indicate that this + * channel is communicating to a low-speed device. + * @epdir: Endpoint Direction (EPDir) + * Indicates whether the transaction is IN or OUT. + * * 1'b0: OUT + * * 1'b1: IN + * @epnum: Endpoint Number (EPNum) + * Indicates the endpoint number on the device serving as the + * data source or sink. + * @mps: Maximum Packet Size (MPS) + * Indicates the maximum packet size of the associated endpoint. + */ + struct cvmx_usbcx_hccharx_s { + __BITFIELD_FIELD(u32 chena : 1, + __BITFIELD_FIELD(u32 chdis : 1, + __BITFIELD_FIELD(u32 oddfrm : 1, + __BITFIELD_FIELD(u32 devaddr : 7, + __BITFIELD_FIELD(u32 ec : 2, + __BITFIELD_FIELD(u32 eptype : 2, + __BITFIELD_FIELD(u32 lspddev : 1, + __BITFIELD_FIELD(u32 reserved_16_16 : 1, + __BITFIELD_FIELD(u32 epdir : 1, + __BITFIELD_FIELD(u32 epnum : 4, + __BITFIELD_FIELD(u32 mps : 11, + ;))))))))))) + } s; +}; + +/** + * cvmx_usbc#_hcfg + * + * Host Configuration Register (HCFG) + * + * This register configures the core after power-on. Do not make changes to this + * register after initializing the host. + */ +union cvmx_usbcx_hcfg { + u32 u32; + /** + * struct cvmx_usbcx_hcfg_s + * @fslssupp: FS- and LS-Only Support (FSLSSupp) + * The application uses this bit to control the core's enumeration + * speed. Using this bit, the application can make the core + * enumerate as a FS host, even if the connected device supports + * HS traffic. Do not make changes to this field after initial + * programming. + * * 1'b0: HS/FS/LS, based on the maximum speed supported by + * the connected device + * * 1'b1: FS/LS-only, even if the connected device can support HS + * @fslspclksel: FS/LS PHY Clock Select (FSLSPclkSel) + * When the core is in FS Host mode + * * 2'b00: PHY clock is running at 30/60 MHz + * * 2'b01: PHY clock is running at 48 MHz + * * Others: Reserved + * When the core is in LS Host mode + * * 2'b00: PHY clock is running at 30/60 MHz. When the + * UTMI+/ULPI PHY Low Power mode is not selected, use + * 30/60 MHz. + * * 2'b01: PHY clock is running at 48 MHz. When the UTMI+ + * PHY Low Power mode is selected, use 48MHz if the PHY + * supplies a 48 MHz clock during LS mode. + * * 2'b10: PHY clock is running at 6 MHz. In USB 1.1 FS mode, + * use 6 MHz when the UTMI+ PHY Low Power mode is + * selected and the PHY supplies a 6 MHz clock during LS + * mode. If you select a 6 MHz clock during LS mode, you must + * do a soft reset. + * * 2'b11: Reserved + */ + struct cvmx_usbcx_hcfg_s { + __BITFIELD_FIELD(u32 reserved_3_31 : 29, + __BITFIELD_FIELD(u32 fslssupp : 1, + __BITFIELD_FIELD(u32 fslspclksel : 2, + ;))) + } s; +}; + +/** + * cvmx_usbc#_hcint# + * + * Host Channel-n Interrupt Register (HCINT) + * + * This register indicates the status of a channel with respect to USB- and + * AHB-related events. The application must read this register when the Host + * Channels Interrupt bit of the Core Interrupt register (GINTSTS.HChInt) is + * set. Before the application can read this register, it must first read + * the Host All Channels Interrupt (HAINT) register to get the exact channel + * number for the Host Channel-n Interrupt register. The application must clear + * the appropriate bit in this register to clear the corresponding bits in the + * HAINT and GINTSTS registers. + */ +union cvmx_usbcx_hcintx { + u32 u32; + /** + * struct cvmx_usbcx_hcintx_s + * @datatglerr: Data Toggle Error (DataTglErr) + * @frmovrun: Frame Overrun (FrmOvrun) + * @bblerr: Babble Error (BblErr) + * @xacterr: Transaction Error (XactErr) + * @nyet: NYET Response Received Interrupt (NYET) + * @ack: ACK Response Received Interrupt (ACK) + * @nak: NAK Response Received Interrupt (NAK) + * @stall: STALL Response Received Interrupt (STALL) + * @ahberr: This bit is always 0x0. + * @chhltd: Channel Halted (ChHltd) + * Indicates the transfer completed abnormally either because of + * any USB transaction error or in response to disable request by + * the application. + * @xfercompl: Transfer Completed (XferCompl) + * Transfer completed normally without any errors. + */ + struct cvmx_usbcx_hcintx_s { + __BITFIELD_FIELD(u32 reserved_11_31 : 21, + __BITFIELD_FIELD(u32 datatglerr : 1, + __BITFIELD_FIELD(u32 frmovrun : 1, + __BITFIELD_FIELD(u32 bblerr : 1, + __BITFIELD_FIELD(u32 xacterr : 1, + __BITFIELD_FIELD(u32 nyet : 1, + __BITFIELD_FIELD(u32 ack : 1, + __BITFIELD_FIELD(u32 nak : 1, + __BITFIELD_FIELD(u32 stall : 1, + __BITFIELD_FIELD(u32 ahberr : 1, + __BITFIELD_FIELD(u32 chhltd : 1, + __BITFIELD_FIELD(u32 xfercompl : 1, + ;)))))))))))) + } s; +}; + +/** + * cvmx_usbc#_hcintmsk# + * + * Host Channel-n Interrupt Mask Register (HCINTMSKn) + * + * This register reflects the mask for each channel status described in the + * previous section. + * Mask interrupt: 1'b0 Unmask interrupt: 1'b1 + */ +union cvmx_usbcx_hcintmskx { + u32 u32; + /** + * struct cvmx_usbcx_hcintmskx_s + * @datatglerrmsk: Data Toggle Error Mask (DataTglErrMsk) + * @frmovrunmsk: Frame Overrun Mask (FrmOvrunMsk) + * @bblerrmsk: Babble Error Mask (BblErrMsk) + * @xacterrmsk: Transaction Error Mask (XactErrMsk) + * @nyetmsk: NYET Response Received Interrupt Mask (NyetMsk) + * @ackmsk: ACK Response Received Interrupt Mask (AckMsk) + * @nakmsk: NAK Response Received Interrupt Mask (NakMsk) + * @stallmsk: STALL Response Received Interrupt Mask (StallMsk) + * @ahberrmsk: AHB Error Mask (AHBErrMsk) + * @chhltdmsk: Channel Halted Mask (ChHltdMsk) + * @xfercomplmsk: Transfer Completed Mask (XferComplMsk) + */ + struct cvmx_usbcx_hcintmskx_s { + __BITFIELD_FIELD(u32 reserved_11_31 : 21, + __BITFIELD_FIELD(u32 datatglerrmsk : 1, + __BITFIELD_FIELD(u32 frmovrunmsk : 1, + __BITFIELD_FIELD(u32 bblerrmsk : 1, + __BITFIELD_FIELD(u32 xacterrmsk : 1, + __BITFIELD_FIELD(u32 nyetmsk : 1, + __BITFIELD_FIELD(u32 ackmsk : 1, + __BITFIELD_FIELD(u32 nakmsk : 1, + __BITFIELD_FIELD(u32 stallmsk : 1, + __BITFIELD_FIELD(u32 ahberrmsk : 1, + __BITFIELD_FIELD(u32 chhltdmsk : 1, + __BITFIELD_FIELD(u32 xfercomplmsk : 1, + ;)))))))))))) + } s; +}; + +/** + * cvmx_usbc#_hcsplt# + * + * Host Channel-n Split Control Register (HCSPLT) + * + */ +union cvmx_usbcx_hcspltx { + u32 u32; + /** + * struct cvmx_usbcx_hcspltx_s + * @spltena: Split Enable (SpltEna) + * The application sets this field to indicate that this channel is + * enabled to perform split transactions. + * @compsplt: Do Complete Split (CompSplt) + * The application sets this field to request the OTG host to + * perform a complete split transaction. + * @xactpos: Transaction Position (XactPos) + * This field is used to determine whether to send all, first, + * middle, or last payloads with each OUT transaction. + * * 2'b11: All. This is the entire data payload is of this + * transaction (which is less than or equal to 188 bytes). + * * 2'b10: Begin. This is the first data payload of this + * transaction (which is larger than 188 bytes). + * * 2'b00: Mid. This is the middle payload of this transaction + * (which is larger than 188 bytes). + * * 2'b01: End. This is the last payload of this transaction + * (which is larger than 188 bytes). + * @hubaddr: Hub Address (HubAddr) + * This field holds the device address of the transaction + * translator's hub. + * @prtaddr: Port Address (PrtAddr) + * This field is the port number of the recipient transaction + * translator. + */ + struct cvmx_usbcx_hcspltx_s { + __BITFIELD_FIELD(u32 spltena : 1, + __BITFIELD_FIELD(u32 reserved_17_30 : 14, + __BITFIELD_FIELD(u32 compsplt : 1, + __BITFIELD_FIELD(u32 xactpos : 2, + __BITFIELD_FIELD(u32 hubaddr : 7, + __BITFIELD_FIELD(u32 prtaddr : 7, + ;)))))) + } s; +}; + +/** + * cvmx_usbc#_hctsiz# + * + * Host Channel-n Transfer Size Register (HCTSIZ) + * + */ +union cvmx_usbcx_hctsizx { + u32 u32; + /** + * struct cvmx_usbcx_hctsizx_s + * @dopng: Do Ping (DoPng) + * Setting this field to 1 directs the host to do PING protocol. + * @pid: PID (Pid) + * The application programs this field with the type of PID to use + * for the initial transaction. The host will maintain this field + * for the rest of the transfer. + * * 2'b00: DATA0 + * * 2'b01: DATA2 + * * 2'b10: DATA1 + * * 2'b11: MDATA (non-control)/SETUP (control) + * @pktcnt: Packet Count (PktCnt) + * This field is programmed by the application with the expected + * number of packets to be transmitted (OUT) or received (IN). + * The host decrements this count on every successful + * transmission or reception of an OUT/IN packet. Once this count + * reaches zero, the application is interrupted to indicate normal + * completion. + * @xfersize: Transfer Size (XferSize) + * For an OUT, this field is the number of data bytes the host will + * send during the transfer. + * For an IN, this field is the buffer size that the application + * has reserved for the transfer. The application is expected to + * program this field as an integer multiple of the maximum packet + * size for IN transactions (periodic and non-periodic). + */ + struct cvmx_usbcx_hctsizx_s { + __BITFIELD_FIELD(u32 dopng : 1, + __BITFIELD_FIELD(u32 pid : 2, + __BITFIELD_FIELD(u32 pktcnt : 10, + __BITFIELD_FIELD(u32 xfersize : 19, + ;)))) + } s; +}; + +/** + * cvmx_usbc#_hfir + * + * Host Frame Interval Register (HFIR) + * + * This register stores the frame interval information for the current speed to + * which the O2P USB core has enumerated. + */ +union cvmx_usbcx_hfir { + u32 u32; + /** + * struct cvmx_usbcx_hfir_s + * @frint: Frame Interval (FrInt) + * The value that the application programs to this field specifies + * the interval between two consecutive SOFs (FS) or micro- + * SOFs (HS) or Keep-Alive tokens (HS). This field contains the + * number of PHY clocks that constitute the required frame + * interval. The default value set in this field for a FS operation + * when the PHY clock frequency is 60 MHz. The application can + * write a value to this register only after the Port Enable bit of + * the Host Port Control and Status register (HPRT.PrtEnaPort) + * has been set. If no value is programmed, the core calculates + * the value based on the PHY clock specified in the FS/LS PHY + * Clock Select field of the Host Configuration register + * (HCFG.FSLSPclkSel). Do not change the value of this field + * after the initial configuration. + * * 125 us (PHY clock frequency for HS) + * * 1 ms (PHY clock frequency for FS/LS) + */ + struct cvmx_usbcx_hfir_s { + __BITFIELD_FIELD(u32 reserved_16_31 : 16, + __BITFIELD_FIELD(u32 frint : 16, + ;)) + } s; +}; + +/** + * cvmx_usbc#_hfnum + * + * Host Frame Number/Frame Time Remaining Register (HFNUM) + * + * This register indicates the current frame number. + * It also indicates the time remaining (in terms of the number of PHY clocks) + * in the current (micro)frame. + */ +union cvmx_usbcx_hfnum { + u32 u32; + /** + * struct cvmx_usbcx_hfnum_s + * @frrem: Frame Time Remaining (FrRem) + * Indicates the amount of time remaining in the current + * microframe (HS) or frame (FS/LS), in terms of PHY clocks. + * This field decrements on each PHY clock. When it reaches + * zero, this field is reloaded with the value in the Frame + * Interval register and a new SOF is transmitted on the USB. + * @frnum: Frame Number (FrNum) + * This field increments when a new SOF is transmitted on the + * USB, and is reset to 0 when it reaches 16'h3FFF. + */ + struct cvmx_usbcx_hfnum_s { + __BITFIELD_FIELD(u32 frrem : 16, + __BITFIELD_FIELD(u32 frnum : 16, + ;)) + } s; +}; + +/** + * cvmx_usbc#_hprt + * + * Host Port Control and Status Register (HPRT) + * + * This register is available in both Host and Device modes. + * Currently, the OTG Host supports only one port. + * A single register holds USB port-related information such as USB reset, + * enable, suspend, resume, connect status, and test mode for each port. The + * R_SS_WC bits in this register can trigger an interrupt to the application + * through the Host Port Interrupt bit of the Core Interrupt register + * (GINTSTS.PrtInt). On a Port Interrupt, the application must read this + * register and clear the bit that caused the interrupt. For the R_SS_WC bits, + * the application must write a 1 to the bit to clear the interrupt. + */ +union cvmx_usbcx_hprt { + u32 u32; + /** + * struct cvmx_usbcx_hprt_s + * @prtspd: Port Speed (PrtSpd) + * Indicates the speed of the device attached to this port. + * * 2'b00: High speed + * * 2'b01: Full speed + * * 2'b10: Low speed + * * 2'b11: Reserved + * @prttstctl: Port Test Control (PrtTstCtl) + * The application writes a nonzero value to this field to put + * the port into a Test mode, and the corresponding pattern is + * signaled on the port. + * * 4'b0000: Test mode disabled + * * 4'b0001: Test_J mode + * * 4'b0010: Test_K mode + * * 4'b0011: Test_SE0_NAK mode + * * 4'b0100: Test_Packet mode + * * 4'b0101: Test_Force_Enable + * * Others: Reserved + * PrtSpd must be zero (i.e. the interface must be in high-speed + * mode) to use the PrtTstCtl test modes. + * @prtpwr: Port Power (PrtPwr) + * The application uses this field to control power to this port, + * and the core clears this bit on an overcurrent condition. + * * 1'b0: Power off + * * 1'b1: Power on + * @prtlnsts: Port Line Status (PrtLnSts) + * Indicates the current logic level USB data lines + * * Bit [10]: Logic level of D- + * * Bit [11]: Logic level of D+ + * @prtrst: Port Reset (PrtRst) + * When the application sets this bit, a reset sequence is + * started on this port. The application must time the reset + * period and clear this bit after the reset sequence is + * complete. + * * 1'b0: Port not in reset + * * 1'b1: Port in reset + * The application must leave this bit set for at least a + * minimum duration mentioned below to start a reset on the + * port. The application can leave it set for another 10 ms in + * addition to the required minimum duration, before clearing + * the bit, even though there is no maximum limit set by the + * USB standard. + * * High speed: 50 ms + * * Full speed/Low speed: 10 ms + * @prtsusp: Port Suspend (PrtSusp) + * The application sets this bit to put this port in Suspend + * mode. The core only stops sending SOFs when this is set. + * To stop the PHY clock, the application must set the Port + * Clock Stop bit, which will assert the suspend input pin of + * the PHY. + * The read value of this bit reflects the current suspend + * status of the port. This bit is cleared by the core after a + * remote wakeup signal is detected or the application sets + * the Port Reset bit or Port Resume bit in this register or the + * Resume/Remote Wakeup Detected Interrupt bit or + * Disconnect Detected Interrupt bit in the Core Interrupt + * register (GINTSTS.WkUpInt or GINTSTS.DisconnInt, + * respectively). + * * 1'b0: Port not in Suspend mode + * * 1'b1: Port in Suspend mode + * @prtres: Port Resume (PrtRes) + * The application sets this bit to drive resume signaling on + * the port. The core continues to drive the resume signal + * until the application clears this bit. + * If the core detects a USB remote wakeup sequence, as + * indicated by the Port Resume/Remote Wakeup Detected + * Interrupt bit of the Core Interrupt register + * (GINTSTS.WkUpInt), the core starts driving resume + * signaling without application intervention and clears this bit + * when it detects a disconnect condition. The read value of + * this bit indicates whether the core is currently driving + * resume signaling. + * * 1'b0: No resume driven + * * 1'b1: Resume driven + * @prtovrcurrchng: Port Overcurrent Change (PrtOvrCurrChng) + * The core sets this bit when the status of the Port + * Overcurrent Active bit (bit 4) in this register changes. + * @prtovrcurract: Port Overcurrent Active (PrtOvrCurrAct) + * Indicates the overcurrent condition of the port. + * * 1'b0: No overcurrent condition + * * 1'b1: Overcurrent condition + * @prtenchng: Port Enable/Disable Change (PrtEnChng) + * The core sets this bit when the status of the Port Enable bit + * [2] of this register changes. + * @prtena: Port Enable (PrtEna) + * A port is enabled only by the core after a reset sequence, + * and is disabled by an overcurrent condition, a disconnect + * condition, or by the application clearing this bit. The + * application cannot set this bit by a register write. It can only + * clear it to disable the port. This bit does not trigger any + * interrupt to the application. + * * 1'b0: Port disabled + * * 1'b1: Port enabled + * @prtconndet: Port Connect Detected (PrtConnDet) + * The core sets this bit when a device connection is detected + * to trigger an interrupt to the application using the Host Port + * Interrupt bit of the Core Interrupt register (GINTSTS.PrtInt). + * The application must write a 1 to this bit to clear the + * interrupt. + * @prtconnsts: Port Connect Status (PrtConnSts) + * * 0: No device is attached to the port. + * * 1: A device is attached to the port. + */ + struct cvmx_usbcx_hprt_s { + __BITFIELD_FIELD(u32 reserved_19_31 : 13, + __BITFIELD_FIELD(u32 prtspd : 2, + __BITFIELD_FIELD(u32 prttstctl : 4, + __BITFIELD_FIELD(u32 prtpwr : 1, + __BITFIELD_FIELD(u32 prtlnsts : 2, + __BITFIELD_FIELD(u32 reserved_9_9 : 1, + __BITFIELD_FIELD(u32 prtrst : 1, + __BITFIELD_FIELD(u32 prtsusp : 1, + __BITFIELD_FIELD(u32 prtres : 1, + __BITFIELD_FIELD(u32 prtovrcurrchng : 1, + __BITFIELD_FIELD(u32 prtovrcurract : 1, + __BITFIELD_FIELD(u32 prtenchng : 1, + __BITFIELD_FIELD(u32 prtena : 1, + __BITFIELD_FIELD(u32 prtconndet : 1, + __BITFIELD_FIELD(u32 prtconnsts : 1, + ;))))))))))))))) + } s; +}; + +/** + * cvmx_usbc#_hptxfsiz + * + * Host Periodic Transmit FIFO Size Register (HPTXFSIZ) + * + * This register holds the size and the memory start address of the Periodic + * TxFIFO, as shown in Figures 310 and 311. + */ +union cvmx_usbcx_hptxfsiz { + u32 u32; + /** + * struct cvmx_usbcx_hptxfsiz_s + * @ptxfsize: Host Periodic TxFIFO Depth (PTxFSize) + * This value is in terms of 32-bit words. + * * Minimum value is 16 + * * Maximum value is 32768 + * @ptxfstaddr: Host Periodic TxFIFO Start Address (PTxFStAddr) + */ + struct cvmx_usbcx_hptxfsiz_s { + __BITFIELD_FIELD(u32 ptxfsize : 16, + __BITFIELD_FIELD(u32 ptxfstaddr : 16, + ;)) + } s; +}; + +/** + * cvmx_usbc#_hptxsts + * + * Host Periodic Transmit FIFO/Queue Status Register (HPTXSTS) + * + * This read-only register contains the free space information for the Periodic + * TxFIFO and the Periodic Transmit Request Queue + */ +union cvmx_usbcx_hptxsts { + u32 u32; + /** + * struct cvmx_usbcx_hptxsts_s + * @ptxqtop: Top of the Periodic Transmit Request Queue (PTxQTop) + * This indicates the entry in the Periodic Tx Request Queue that + * is currently being processes by the MAC. + * This register is used for debugging. + * * Bit [31]: Odd/Even (micro)frame + * - 1'b0: send in even (micro)frame + * - 1'b1: send in odd (micro)frame + * * Bits [30:27]: Channel/endpoint number + * * Bits [26:25]: Type + * - 2'b00: IN/OUT + * - 2'b01: Zero-length packet + * - 2'b10: CSPLIT + * - 2'b11: Disable channel command + * * Bit [24]: Terminate (last entry for the selected + * channel/endpoint) + * @ptxqspcavail: Periodic Transmit Request Queue Space Available + * (PTxQSpcAvail) + * Indicates the number of free locations available to be written + * in the Periodic Transmit Request Queue. This queue holds both + * IN and OUT requests. + * * 8'h0: Periodic Transmit Request Queue is full + * * 8'h1: 1 location available + * * 8'h2: 2 locations available + * * n: n locations available (0..8) + * * Others: Reserved + * @ptxfspcavail: Periodic Transmit Data FIFO Space Available + * (PTxFSpcAvail) + * Indicates the number of free locations available to be written + * to in the Periodic TxFIFO. + * Values are in terms of 32-bit words + * * 16'h0: Periodic TxFIFO is full + * * 16'h1: 1 word available + * * 16'h2: 2 words available + * * 16'hn: n words available (where 0..32768) + * * 16'h8000: 32768 words available + * * Others: Reserved + */ + struct cvmx_usbcx_hptxsts_s { + __BITFIELD_FIELD(u32 ptxqtop : 8, + __BITFIELD_FIELD(u32 ptxqspcavail : 8, + __BITFIELD_FIELD(u32 ptxfspcavail : 16, + ;))) + } s; +}; + +/** + * cvmx_usbn#_clk_ctl + * + * USBN_CLK_CTL = USBN's Clock Control + * + * This register is used to control the frequency of the hclk and the + * hreset and phy_rst signals. + */ +union cvmx_usbnx_clk_ctl { + u64 u64; + /** + * struct cvmx_usbnx_clk_ctl_s + * @divide2: The 'hclk' used by the USB subsystem is derived + * from the eclk. + * Also see the field DIVIDE. DIVIDE2<1> must currently + * be zero because it is not implemented, so the maximum + * ratio of eclk/hclk is currently 16. + * The actual divide number for hclk is: + * (DIVIDE2 + 1) * (DIVIDE + 1) + * @hclk_rst: When this field is '0' the HCLK-DIVIDER used to + * generate the hclk in the USB Subsystem is held + * in reset. This bit must be set to '0' before + * changing the value os DIVIDE in this register. + * The reset to the HCLK_DIVIDERis also asserted + * when core reset is asserted. + * @p_x_on: Force USB-PHY on during suspend. + * '1' USB-PHY XO block is powered-down during + * suspend. + * '0' USB-PHY XO block is powered-up during + * suspend. + * The value of this field must be set while POR is + * active. + * @p_rtype: PHY reference clock type + * On CN50XX/CN52XX/CN56XX the values are: + * '0' The USB-PHY uses a 12MHz crystal as a clock source + * at the USB_XO and USB_XI pins. + * '1' Reserved. + * '2' The USB_PHY uses 12/24/48MHz 2.5V board clock at the + * USB_XO pin. USB_XI should be tied to ground in this + * case. + * '3' Reserved. + * On CN3xxx bits 14 and 15 are p_xenbn and p_rclk and values are: + * '0' Reserved. + * '1' Reserved. + * '2' The PHY PLL uses the XO block output as a reference. + * The XO block uses an external clock supplied on the + * XO pin. USB_XI should be tied to ground for this + * usage. + * '3' The XO block uses the clock from a crystal. + * @p_com_on: '0' Force USB-PHY XO Bias, Bandgap and PLL to + * remain powered in Suspend Mode. + * '1' The USB-PHY XO Bias, Bandgap and PLL are + * powered down in suspend mode. + * The value of this field must be set while POR is + * active. + * @p_c_sel: Phy clock speed select. + * Selects the reference clock / crystal frequency. + * '11': Reserved + * '10': 48 MHz (reserved when a crystal is used) + * '01': 24 MHz (reserved when a crystal is used) + * '00': 12 MHz + * The value of this field must be set while POR is + * active. + * NOTE: if a crystal is used as a reference clock, + * this field must be set to 12 MHz. + * @cdiv_byp: Used to enable the bypass input to the USB_CLK_DIV. + * @sd_mode: Scaledown mode for the USBC. Control timing events + * in the USBC, for normal operation this must be '0'. + * @s_bist: Starts bist on the hclk memories, during the '0' + * to '1' transition. + * @por: Power On Reset for the PHY. + * Resets all the PHYS registers and state machines. + * @enable: When '1' allows the generation of the hclk. When + * '0' the hclk will not be generated. SEE DIVIDE + * field of this register. + * @prst: When this field is '0' the reset associated with + * the phy_clk functionality in the USB Subsystem is + * help in reset. This bit should not be set to '1' + * until the time it takes 6 clocks (hclk or phy_clk, + * whichever is slower) has passed. Under normal + * operation once this bit is set to '1' it should not + * be set to '0'. + * @hrst: When this field is '0' the reset associated with + * the hclk functioanlity in the USB Subsystem is + * held in reset.This bit should not be set to '1' + * until 12ms after phy_clk is stable. Under normal + * operation, once this bit is set to '1' it should + * not be set to '0'. + * @divide: The frequency of 'hclk' used by the USB subsystem + * is the eclk frequency divided by the value of + * (DIVIDE2 + 1) * (DIVIDE + 1), also see the field + * DIVIDE2 of this register. + * The hclk frequency should be less than 125Mhz. + * After writing a value to this field the SW should + * read the field for the value written. + * The ENABLE field of this register should not be set + * until AFTER this field is set and then read. + */ + struct cvmx_usbnx_clk_ctl_s { + __BITFIELD_FIELD(u64 reserved_20_63 : 44, + __BITFIELD_FIELD(u64 divide2 : 2, + __BITFIELD_FIELD(u64 hclk_rst : 1, + __BITFIELD_FIELD(u64 p_x_on : 1, + __BITFIELD_FIELD(u64 p_rtype : 2, + __BITFIELD_FIELD(u64 p_com_on : 1, + __BITFIELD_FIELD(u64 p_c_sel : 2, + __BITFIELD_FIELD(u64 cdiv_byp : 1, + __BITFIELD_FIELD(u64 sd_mode : 2, + __BITFIELD_FIELD(u64 s_bist : 1, + __BITFIELD_FIELD(u64 por : 1, + __BITFIELD_FIELD(u64 enable : 1, + __BITFIELD_FIELD(u64 prst : 1, + __BITFIELD_FIELD(u64 hrst : 1, + __BITFIELD_FIELD(u64 divide : 3, + ;))))))))))))))) + } s; +}; + +/** + * cvmx_usbn#_usbp_ctl_status + * + * USBN_USBP_CTL_STATUS = USBP Control And Status Register + * + * Contains general control and status information for the USBN block. + */ +union cvmx_usbnx_usbp_ctl_status { + u64 u64; + /** + * struct cvmx_usbnx_usbp_ctl_status_s + * @txrisetune: HS Transmitter Rise/Fall Time Adjustment + * @txvreftune: HS DC Voltage Level Adjustment + * @txfslstune: FS/LS Source Impedance Adjustment + * @txhsxvtune: Transmitter High-Speed Crossover Adjustment + * @sqrxtune: Squelch Threshold Adjustment + * @compdistune: Disconnect Threshold Adjustment + * @otgtune: VBUS Valid Threshold Adjustment + * @otgdisable: OTG Block Disable + * @portreset: Per_Port Reset + * @drvvbus: Drive VBUS + * @lsbist: Low-Speed BIST Enable. + * @fsbist: Full-Speed BIST Enable. + * @hsbist: High-Speed BIST Enable. + * @bist_done: PHY Bist Done. + * Asserted at the end of the PHY BIST sequence. + * @bist_err: PHY Bist Error. + * Indicates an internal error was detected during + * the BIST sequence. + * @tdata_out: PHY Test Data Out. + * Presents either internally generated signals or + * test register contents, based upon the value of + * test_data_out_sel. + * @siddq: Drives the USBP (USB-PHY) SIDDQ input. + * Normally should be set to zero. + * When customers have no intent to use USB PHY + * interface, they should: + * - still provide 3.3V to USB_VDD33, and + * - tie USB_REXT to 3.3V supply, and + * - set USBN*_USBP_CTL_STATUS[SIDDQ]=1 + * @txpreemphasistune: HS Transmitter Pre-Emphasis Enable + * @dma_bmode: When set to 1 the L2C DMA address will be updated + * with byte-counts between packets. When set to 0 + * the L2C DMA address is incremented to the next + * 4-byte aligned address after adding byte-count. + * @usbc_end: Bigendian input to the USB Core. This should be + * set to '0' for operation. + * @usbp_bist: PHY, This is cleared '0' to run BIST on the USBP. + * @tclk: PHY Test Clock, used to load TDATA_IN to the USBP. + * @dp_pulld: PHY DP_PULLDOWN input to the USB-PHY. + * This signal enables the pull-down resistance on + * the D+ line. '1' pull down-resistance is connected + * to D+/ '0' pull down resistance is not connected + * to D+. When an A/B device is acting as a host + * (downstream-facing port), dp_pulldown and + * dm_pulldown are enabled. This must not toggle + * during normal operation. + * @dm_pulld: PHY DM_PULLDOWN input to the USB-PHY. + * This signal enables the pull-down resistance on + * the D- line. '1' pull down-resistance is connected + * to D-. '0' pull down resistance is not connected + * to D-. When an A/B device is acting as a host + * (downstream-facing port), dp_pulldown and + * dm_pulldown are enabled. This must not toggle + * during normal operation. + * @hst_mode: When '0' the USB is acting as HOST, when '1' + * USB is acting as device. This field needs to be + * set while the USB is in reset. + * @tuning: Transmitter Tuning for High-Speed Operation. + * Tunes the current supply and rise/fall output + * times for high-speed operation. + * [20:19] == 11: Current supply increased + * approximately 9% + * [20:19] == 10: Current supply increased + * approximately 4.5% + * [20:19] == 01: Design default. + * [20:19] == 00: Current supply decreased + * approximately 4.5% + * [22:21] == 11: Rise and fall times are increased. + * [22:21] == 10: Design default. + * [22:21] == 01: Rise and fall times are decreased. + * [22:21] == 00: Rise and fall times are decreased + * further as compared to the 01 setting. + * @tx_bs_enh: Transmit Bit Stuffing on [15:8]. + * Enables or disables bit stuffing on data[15:8] + * when bit-stuffing is enabled. + * @tx_bs_en: Transmit Bit Stuffing on [7:0]. + * Enables or disables bit stuffing on data[7:0] + * when bit-stuffing is enabled. + * @loop_enb: PHY Loopback Test Enable. + * '1': During data transmission the receive is + * enabled. + * '0': During data transmission the receive is + * disabled. + * Must be '0' for normal operation. + * @vtest_enb: Analog Test Pin Enable. + * '1' The PHY's analog_test pin is enabled for the + * input and output of applicable analog test signals. + * '0' THe analog_test pin is disabled. + * @bist_enb: Built-In Self Test Enable. + * Used to activate BIST in the PHY. + * @tdata_sel: Test Data Out Select. + * '1' test_data_out[3:0] (PHY) register contents + * are output. '0' internally generated signals are + * output. + * @taddr_in: Mode Address for Test Interface. + * Specifies the register address for writing to or + * reading from the PHY test interface register. + * @tdata_in: Internal Testing Register Input Data and Select + * This is a test bus. Data is present on [3:0], + * and its corresponding select (enable) is present + * on bits [7:4]. + * @ate_reset: Reset input from automatic test equipment. + * This is a test signal. When the USB Core is + * powered up (not in Susned Mode), an automatic + * tester can use this to disable phy_clock and + * free_clk, then re-enable them with an aligned + * phase. + * '1': The phy_clk and free_clk outputs are + * disabled. "0": The phy_clock and free_clk outputs + * are available within a specific period after the + * de-assertion. + */ + struct cvmx_usbnx_usbp_ctl_status_s { + __BITFIELD_FIELD(u64 txrisetune : 1, + __BITFIELD_FIELD(u64 txvreftune : 4, + __BITFIELD_FIELD(u64 txfslstune : 4, + __BITFIELD_FIELD(u64 txhsxvtune : 2, + __BITFIELD_FIELD(u64 sqrxtune : 3, + __BITFIELD_FIELD(u64 compdistune : 3, + __BITFIELD_FIELD(u64 otgtune : 3, + __BITFIELD_FIELD(u64 otgdisable : 1, + __BITFIELD_FIELD(u64 portreset : 1, + __BITFIELD_FIELD(u64 drvvbus : 1, + __BITFIELD_FIELD(u64 lsbist : 1, + __BITFIELD_FIELD(u64 fsbist : 1, + __BITFIELD_FIELD(u64 hsbist : 1, + __BITFIELD_FIELD(u64 bist_done : 1, + __BITFIELD_FIELD(u64 bist_err : 1, + __BITFIELD_FIELD(u64 tdata_out : 4, + __BITFIELD_FIELD(u64 siddq : 1, + __BITFIELD_FIELD(u64 txpreemphasistune : 1, + __BITFIELD_FIELD(u64 dma_bmode : 1, + __BITFIELD_FIELD(u64 usbc_end : 1, + __BITFIELD_FIELD(u64 usbp_bist : 1, + __BITFIELD_FIELD(u64 tclk : 1, + __BITFIELD_FIELD(u64 dp_pulld : 1, + __BITFIELD_FIELD(u64 dm_pulld : 1, + __BITFIELD_FIELD(u64 hst_mode : 1, + __BITFIELD_FIELD(u64 tuning : 4, + __BITFIELD_FIELD(u64 tx_bs_enh : 1, + __BITFIELD_FIELD(u64 tx_bs_en : 1, + __BITFIELD_FIELD(u64 loop_enb : 1, + __BITFIELD_FIELD(u64 vtest_enb : 1, + __BITFIELD_FIELD(u64 bist_enb : 1, + __BITFIELD_FIELD(u64 tdata_sel : 1, + __BITFIELD_FIELD(u64 taddr_in : 4, + __BITFIELD_FIELD(u64 tdata_in : 8, + __BITFIELD_FIELD(u64 ate_reset : 1, + ;))))))))))))))))))))))))))))))))))) + } s; +}; + +#endif /* __OCTEON_HCD_H__ */ diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c new file mode 100644 index 000000000..12fdb1893 --- /dev/null +++ b/drivers/usb/host/ohci-at91.c @@ -0,0 +1,728 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "ohci.h" + +#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, function and usb clocks; sometimes also an AHB clock */ +#define hcd_to_ohci_at91_priv(h) \ + ((struct ohci_at91_priv *)hcd_to_ohci(h)->priv) + +#define AT91_MAX_USBH_PORTS 3 +struct at91_usbh_data { + struct gpio_desc *vbus_pin[AT91_MAX_USBH_PORTS]; + struct gpio_desc *overcurrent_pin[AT91_MAX_USBH_PORTS]; + u8 ports; /* number of ports on root hub */ + u8 overcurrent_supported; + u8 overcurrent_status[AT91_MAX_USBH_PORTS]; + u8 overcurrent_changed[AT91_MAX_USBH_PORTS]; +}; + +struct ohci_at91_priv { + struct clk *iclk; + struct clk *fclk; + struct clk *hclk; + bool clocked; + bool wakeup; /* Saved wake-up state for resume */ + struct regmap *sfr_regmap; + u32 suspend_smc_id; +}; +/* interface and function clocks; sometimes also an AHB clock */ + +#define DRIVER_DESC "OHCI Atmel driver" + +static struct hc_driver __read_mostly ohci_at91_hc_driver; + +static const struct ohci_driver_overrides ohci_at91_drv_overrides __initconst = { + .extra_priv_size = sizeof(struct ohci_at91_priv), +}; + +/*-------------------------------------------------------------------------*/ + +static void at91_start_clock(struct ohci_at91_priv *ohci_at91) +{ + if (ohci_at91->clocked) + return; + + clk_set_rate(ohci_at91->fclk, 48000000); + clk_prepare_enable(ohci_at91->hclk); + clk_prepare_enable(ohci_at91->iclk); + clk_prepare_enable(ohci_at91->fclk); + ohci_at91->clocked = true; +} + +static void at91_stop_clock(struct ohci_at91_priv *ohci_at91) +{ + if (!ohci_at91->clocked) + return; + + clk_disable_unprepare(ohci_at91->fclk); + clk_disable_unprepare(ohci_at91->iclk); + clk_disable_unprepare(ohci_at91->hclk); + ohci_at91->clocked = false; +} + +static void at91_start_hc(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_regs __iomem *regs = hcd->regs; + struct ohci_at91_priv *ohci_at91 = hcd_to_ohci_at91_priv(hcd); + + dev_dbg(&pdev->dev, "start\n"); + + /* + * Start the USB clocks. + */ + at91_start_clock(ohci_at91); + + /* + * 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_at91_priv *ohci_at91 = hcd_to_ohci_at91_priv(hcd); + + dev_dbg(&pdev->dev, "stop\n"); + + /* + * Put the USB host controller into reset. + */ + usb_hcd_platform_shutdown(pdev); + + /* + * Stop the USB clocks. + */ + at91_stop_clock(ohci_at91); +} + + +/*-------------------------------------------------------------------------*/ + +static void usb_hcd_at91_remove (struct usb_hcd *, struct platform_device *); + +static u32 at91_dt_suspend_smc(struct device *dev) +{ + u32 suspend_smc_id; + + if (!dev->of_node) + return 0; + + if (of_property_read_u32(dev->of_node, "microchip,suspend-smc-id", &suspend_smc_id)) + return 0; + + return suspend_smc_id; +} + +static struct regmap *at91_dt_syscon_sfr(void) +{ + struct regmap *regmap; + + regmap = syscon_regmap_lookup_by_compatible("atmel,sama5d2-sfr"); + if (IS_ERR(regmap)) { + regmap = syscon_regmap_lookup_by_compatible("microchip,sam9x60-sfr"); + if (IS_ERR(regmap)) + regmap = NULL; + } + + return regmap; +} + +/* 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 + * @driver: Pointer to hc driver instance + * @pdev: USB controller to probe + * + * Context: task context, might sleep + * + * 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) +{ + struct at91_usbh_data *board; + struct ohci_hcd *ohci; + int retval; + struct usb_hcd *hcd; + struct ohci_at91_priv *ohci_at91; + struct device *dev = &pdev->dev; + struct resource *res; + int irq; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + hcd = usb_create_hcd(driver, dev, dev_name(dev)); + if (!hcd) + return -ENOMEM; + ohci_at91 = hcd_to_ohci_at91_priv(hcd); + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(hcd->regs)) { + retval = PTR_ERR(hcd->regs); + goto err; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + ohci_at91->iclk = devm_clk_get(dev, "ohci_clk"); + if (IS_ERR(ohci_at91->iclk)) { + dev_err(dev, "failed to get ohci_clk\n"); + retval = PTR_ERR(ohci_at91->iclk); + goto err; + } + ohci_at91->fclk = devm_clk_get(dev, "uhpck"); + if (IS_ERR(ohci_at91->fclk)) { + dev_err(dev, "failed to get uhpck\n"); + retval = PTR_ERR(ohci_at91->fclk); + goto err; + } + ohci_at91->hclk = devm_clk_get(dev, "hclk"); + if (IS_ERR(ohci_at91->hclk)) { + dev_err(dev, "failed to get hclk\n"); + retval = PTR_ERR(ohci_at91->hclk); + goto err; + } + + ohci_at91->suspend_smc_id = at91_dt_suspend_smc(dev); + if (!ohci_at91->suspend_smc_id) { + dev_dbg(dev, "failed to find sfr suspend smc id, using regmap\n"); + ohci_at91->sfr_regmap = at91_dt_syscon_sfr(); + if (!ohci_at91->sfr_regmap) + dev_dbg(dev, "failed to find sfr node\n"); + } + + board = hcd->self.controller->platform_data; + ohci = hcd_to_ohci(hcd); + ohci->num_ports = board->ports; + at91_start_hc(pdev); + + /* + * The RemoteWakeupConnected bit has to be set explicitly + * before calling ohci_run. The reset value of this bit is 0. + */ + ohci->hc_control = OHCI_CTRL_RWC; + + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval == 0) { + device_wakeup_enable(hcd->self.controller); + return retval; + } + + /* Error handling */ + at91_stop_hc(pdev); + + err: + 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 + * @hcd: USB controller to remove + * @pdev: Platform device required for cleanup + * + * Context: task context, might sleep + * + * 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); + usb_put_hcd(hcd); +} + +/*-------------------------------------------------------------------------*/ +static void ohci_at91_usb_set_power(struct at91_usbh_data *pdata, int port, int enable) +{ + if (!valid_port(port)) + return; + + gpiod_set_value(pdata->vbus_pin[port], enable); +} + +static int ohci_at91_usb_get_power(struct at91_usbh_data *pdata, int port) +{ + if (!valid_port(port)) + return -EINVAL; + + return gpiod_get_value(pdata->vbus_pin[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; +} + +static int ohci_at91_port_suspend(struct ohci_at91_priv *ohci_at91, u8 set) +{ + struct regmap *regmap = ohci_at91->sfr_regmap; + u32 regval; + int ret; + + if (ohci_at91->suspend_smc_id) { + struct arm_smccc_res res; + + arm_smccc_smc(ohci_at91->suspend_smc_id, set, 0, 0, 0, 0, 0, 0, &res); + if (res.a0) + return -EINVAL; + } else if (regmap) { + ret = regmap_read(regmap, AT91_SFR_OHCIICR, ®val); + if (ret) + return ret; + + if (set) + regval |= AT91_OHCIICR_USB_SUSPEND; + else + regval &= ~AT91_OHCIICR_USB_SUSPEND; + + regmap_write(regmap, AT91_SFR_OHCIICR, regval); + } + + return 0; +} + +/* + * 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 = dev_get_platdata(hcd->self.controller); + struct ohci_at91_priv *ohci_at91 = hcd_to_ohci_at91_priv(hcd); + 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: + switch (wValue) { + case 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; + + case USB_PORT_FEAT_SUSPEND: + dev_dbg(hcd->self.controller, "SetPortFeat: SUSPEND\n"); + if (valid_port(wIndex)) { + ohci_at91_port_suspend(ohci_at91, 1); + return 0; + } + break; + } + 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; + + case USB_PORT_FEAT_SUSPEND: + dev_dbg(hcd->self.controller, "ClearPortFeature: SUSPEND\n"); + if (valid_port(wIndex)) { + ohci_at91_port_suspend(ohci_at91, 0); + return 0; + } + break; + } + 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(HUB_CHAR_INDV_PORT_LPSM); + + if (pdata->overcurrent_supported) { + desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_OCPM); + desc->wHubCharacteristics |= + cpu_to_le16(HUB_CHAR_INDV_PORT_OCPM); + } + + 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 irqreturn_t ohci_hcd_at91_overcurrent_irq(int irq, void *data) +{ + struct platform_device *pdev = data; + struct at91_usbh_data *pdata = dev_get_platdata(&pdev->dev); + int val, port; + + /* From the GPIO notifying the over-current situation, find + * out the corresponding port */ + at91_for_each_port(port) { + if (gpiod_to_irq(pdata->overcurrent_pin[port]) == irq) + break; + } + + if (port == AT91_MAX_USBH_PORTS) { + dev_err(& pdev->dev, "overcurrent interrupt from unknown GPIO\n"); + return IRQ_HANDLED; + } + + val = gpiod_get_value(pdata->overcurrent_pin[port]); + + /* 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; +} + +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_hcd_at91_drv_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct at91_usbh_data *pdata; + int i; + int ret; + int err; + u32 ports; + + /* 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(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdev->dev.platform_data = pdata; + + if (!of_property_read_u32(np, "num-ports", &ports)) + pdata->ports = ports; + + at91_for_each_port(i) { + if (i >= pdata->ports) + break; + + pdata->vbus_pin[i] = + devm_gpiod_get_index_optional(&pdev->dev, "atmel,vbus", + i, GPIOD_OUT_HIGH); + if (IS_ERR(pdata->vbus_pin[i])) { + err = PTR_ERR(pdata->vbus_pin[i]); + dev_err(&pdev->dev, "unable to claim gpio \"vbus\": %d\n", err); + continue; + } + } + + at91_for_each_port(i) { + if (i >= pdata->ports) + break; + + pdata->overcurrent_pin[i] = + devm_gpiod_get_index_optional(&pdev->dev, "atmel,oc", + i, GPIOD_IN); + if (!pdata->overcurrent_pin[i]) + continue; + if (IS_ERR(pdata->overcurrent_pin[i])) { + err = PTR_ERR(pdata->overcurrent_pin[i]); + dev_err(&pdev->dev, "unable to claim gpio \"overcurrent\": %d\n", err); + continue; + } + + ret = devm_request_irq(&pdev->dev, + gpiod_to_irq(pdata->overcurrent_pin[i]), + ohci_hcd_at91_overcurrent_irq, + IRQF_SHARED, + "ohci_overcurrent", pdev); + if (ret) + dev_info(&pdev->dev, "failed to request gpio \"overcurrent\" IRQ\n"); + } + + device_init_wakeup(&pdev->dev, 1); + return usb_hcd_at91_probe(&ohci_at91_hc_driver, pdev); +} + +static void ohci_hcd_at91_drv_remove(struct platform_device *pdev) +{ + struct at91_usbh_data *pdata = dev_get_platdata(&pdev->dev); + int i; + + if (pdata) { + at91_for_each_port(i) + ohci_at91_usb_set_power(pdata, i, 0); + } + + device_init_wakeup(&pdev->dev, 0); + usb_hcd_at91_remove(platform_get_drvdata(pdev), pdev); +} + +static int __maybe_unused +ohci_hcd_at91_drv_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct ohci_at91_priv *ohci_at91 = hcd_to_ohci_at91_priv(hcd); + int ret; + + /* + * Disable wakeup if we are going to sleep with slow clock mode + * enabled. + */ + ohci_at91->wakeup = device_may_wakeup(dev) + && !at91_suspend_entering_slow_clock(); + + if (ohci_at91->wakeup) + enable_irq_wake(hcd->irq); + + ret = ohci_suspend(hcd, ohci_at91->wakeup); + if (ret) { + if (ohci_at91->wakeup) + disable_irq_wake(hcd->irq); + return ret; + } + /* + * 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 (!ohci_at91->wakeup) { + ohci->rh_state = OHCI_RH_HALTED; + + /* flush the writes */ + (void) ohci_readl (ohci, &ohci->regs->control); + msleep(1); + ohci_at91_port_suspend(ohci_at91, 1); + at91_stop_clock(ohci_at91); + } else { + ohci_at91_port_suspend(ohci_at91, 1); + } + + return ret; +} + +static int __maybe_unused +ohci_hcd_at91_drv_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct ohci_at91_priv *ohci_at91 = hcd_to_ohci_at91_priv(hcd); + + ohci_at91_port_suspend(ohci_at91, 0); + + if (ohci_at91->wakeup) + disable_irq_wake(hcd->irq); + else + at91_start_clock(ohci_at91); + + /* + * According to the comment in ohci_hcd_at91_drv_suspend() + * we need to do a reset if the 48Mhz clock was stopped, + * that is, if ohci_at91->wakeup is clear. Tell ohci_resume() + * to reset in this case by setting its "hibernated" flag. + */ + ohci_resume(hcd, !ohci_at91->wakeup); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(ohci_hcd_at91_pm_ops, ohci_hcd_at91_drv_suspend, + ohci_hcd_at91_drv_resume); + +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, + .driver = { + .name = "at91_ohci", + .pm = &ohci_hcd_at91_pm_ops, + .of_match_table = at91_ohci_dt_ids, + }, +}; + +static int __init ohci_at91_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ohci_init_driver(&ohci_at91_hc_driver, &ohci_at91_drv_overrides); + + /* + * The Atmel HW has some unusual quirks, which require Atmel-specific + * workarounds. We override certain hc_driver functions here to + * achieve that. We explicitly do not enhance ohci_driver_overrides to + * allow this more easily, since this is an unusual case, and we don't + * want to encourage others to override these functions by making it + * too easy. + */ + + ohci_at91_hc_driver.hub_status_data = ohci_at91_hub_status_data; + ohci_at91_hc_driver.hub_control = ohci_at91_hub_control; + + return platform_driver_register(&ohci_hcd_at91_driver); +} +module_init(ohci_at91_init); + +static void __exit ohci_at91_cleanup(void) +{ + platform_driver_unregister(&ohci_hcd_at91_driver); +} +module_exit(ohci_at91_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:at91_ohci"); diff --git a/drivers/usb/host/ohci-da8xx.c b/drivers/usb/host/ohci-da8xx.c new file mode 100644 index 000000000..0938c0e7a --- /dev/null +++ b/drivers/usb/host/ohci-da8xx.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ohci.h" + +#define DRIVER_DESC "DA8XX" +#define DRV_NAME "ohci-da8xx" + +static struct hc_driver __read_mostly ohci_da8xx_hc_driver; + +static int (*orig_ohci_hub_control)(struct usb_hcd *hcd, u16 typeReq, + u16 wValue, u16 wIndex, char *buf, u16 wLength); +static int (*orig_ohci_hub_status_data)(struct usb_hcd *hcd, char *buf); + +struct da8xx_ohci_hcd { + struct usb_hcd *hcd; + struct clk *usb11_clk; + struct phy *usb11_phy; + struct regulator *vbus_reg; + struct notifier_block nb; + struct gpio_desc *oc_gpio; +}; + +#define to_da8xx_ohci(hcd) (struct da8xx_ohci_hcd *)(hcd_to_ohci(hcd)->priv) + +/* Over-current indicator change bitmask */ +static volatile u16 ocic_mask; + +static int ohci_da8xx_enable(struct usb_hcd *hcd) +{ + struct da8xx_ohci_hcd *da8xx_ohci = to_da8xx_ohci(hcd); + int ret; + + ret = clk_prepare_enable(da8xx_ohci->usb11_clk); + if (ret) + return ret; + + ret = phy_init(da8xx_ohci->usb11_phy); + if (ret) + goto err_phy_init; + + ret = phy_power_on(da8xx_ohci->usb11_phy); + if (ret) + goto err_phy_power_on; + + return 0; + +err_phy_power_on: + phy_exit(da8xx_ohci->usb11_phy); +err_phy_init: + clk_disable_unprepare(da8xx_ohci->usb11_clk); + + return ret; +} + +static void ohci_da8xx_disable(struct usb_hcd *hcd) +{ + struct da8xx_ohci_hcd *da8xx_ohci = to_da8xx_ohci(hcd); + + phy_power_off(da8xx_ohci->usb11_phy); + phy_exit(da8xx_ohci->usb11_phy); + clk_disable_unprepare(da8xx_ohci->usb11_clk); +} + +static int ohci_da8xx_set_power(struct usb_hcd *hcd, int on) +{ + struct da8xx_ohci_hcd *da8xx_ohci = to_da8xx_ohci(hcd); + struct device *dev = hcd->self.controller; + int ret; + + if (!da8xx_ohci->vbus_reg) + return 0; + + if (on) { + ret = regulator_enable(da8xx_ohci->vbus_reg); + if (ret) { + dev_err(dev, "Failed to enable regulator: %d\n", ret); + return ret; + } + } else { + ret = regulator_disable(da8xx_ohci->vbus_reg); + if (ret) { + dev_err(dev, "Failed to disable regulator: %d\n", ret); + return ret; + } + } + + return 0; +} + +static int ohci_da8xx_get_power(struct usb_hcd *hcd) +{ + struct da8xx_ohci_hcd *da8xx_ohci = to_da8xx_ohci(hcd); + + if (da8xx_ohci->vbus_reg) + return regulator_is_enabled(da8xx_ohci->vbus_reg); + + return 1; +} + +static int ohci_da8xx_get_oci(struct usb_hcd *hcd) +{ + struct da8xx_ohci_hcd *da8xx_ohci = to_da8xx_ohci(hcd); + unsigned int flags; + int ret; + + if (da8xx_ohci->oc_gpio) + return gpiod_get_value_cansleep(da8xx_ohci->oc_gpio); + + if (!da8xx_ohci->vbus_reg) + return 0; + + ret = regulator_get_error_flags(da8xx_ohci->vbus_reg, &flags); + if (ret) + return ret; + + if (flags & REGULATOR_ERROR_OVER_CURRENT) + return 1; + + return 0; +} + +static int ohci_da8xx_has_set_power(struct usb_hcd *hcd) +{ + struct da8xx_ohci_hcd *da8xx_ohci = to_da8xx_ohci(hcd); + + if (da8xx_ohci->vbus_reg) + return 1; + + return 0; +} + +static int ohci_da8xx_has_oci(struct usb_hcd *hcd) +{ + struct da8xx_ohci_hcd *da8xx_ohci = to_da8xx_ohci(hcd); + + if (da8xx_ohci->oc_gpio) + return 1; + + if (da8xx_ohci->vbus_reg) + return 1; + + return 0; +} + +static int ohci_da8xx_regulator_event(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct da8xx_ohci_hcd *da8xx_ohci = + container_of(nb, struct da8xx_ohci_hcd, nb); + + if (event & REGULATOR_EVENT_OVER_CURRENT) { + ocic_mask |= 1 << 1; + ohci_da8xx_set_power(da8xx_ohci->hcd, 0); + } + + return 0; +} + +static irqreturn_t ohci_da8xx_oc_thread(int irq, void *data) +{ + struct da8xx_ohci_hcd *da8xx_ohci = data; + struct device *dev = da8xx_ohci->hcd->self.controller; + int ret; + + if (gpiod_get_value_cansleep(da8xx_ohci->oc_gpio) && + da8xx_ohci->vbus_reg) { + ret = regulator_disable(da8xx_ohci->vbus_reg); + if (ret) + dev_err(dev, "Failed to disable regulator: %d\n", ret); + } + + return IRQ_HANDLED; +} + +static int ohci_da8xx_register_notify(struct usb_hcd *hcd) +{ + struct da8xx_ohci_hcd *da8xx_ohci = to_da8xx_ohci(hcd); + struct device *dev = hcd->self.controller; + int ret = 0; + + if (!da8xx_ohci->oc_gpio && da8xx_ohci->vbus_reg) { + da8xx_ohci->nb.notifier_call = ohci_da8xx_regulator_event; + ret = devm_regulator_register_notifier(da8xx_ohci->vbus_reg, + &da8xx_ohci->nb); + } + + if (ret) + dev_err(dev, "Failed to register notifier: %d\n", ret); + + return ret; +} + +static int ohci_da8xx_reset(struct usb_hcd *hcd) +{ + struct device *dev = hcd->self.controller; + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int result; + u32 rh_a; + + dev_dbg(dev, "starting USB controller\n"); + + result = ohci_da8xx_enable(hcd); + if (result < 0) + return result; + + /* + * 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_setup(hcd); + if (result < 0) { + ohci_da8xx_disable(hcd); + 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 (ohci_da8xx_has_set_power(hcd)) { + rh_a &= ~RH_A_NPS; + rh_a |= RH_A_PSM; + } + if (ohci_da8xx_has_oci(hcd)) { + rh_a &= ~RH_A_NOCP; + rh_a |= RH_A_OCPM; + } + ohci_writel(ohci, rh_a, &ohci->regs->roothub.a); + + 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 = orig_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; + 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 (!ohci_da8xx_get_power(hcd)) + temp &= ~RH_PS_PPS; + + /* The port over-current indicator (POCI) bit is always 0 */ + if (ohci_da8xx_get_oci(hcd) > 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"); + + return ohci_da8xx_set_power(hcd, 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 orig_ohci_hub_control(hcd, typeReq, wValue, + wIndex, buf, wLength); +} + +/*-------------------------------------------------------------------------*/ +#ifdef CONFIG_OF +static const struct of_device_id da8xx_ohci_ids[] = { + { .compatible = "ti,da830-ohci" }, + { } +}; +MODULE_DEVICE_TABLE(of, da8xx_ohci_ids); +#endif + +static int ohci_da8xx_probe(struct platform_device *pdev) +{ + struct da8xx_ohci_hcd *da8xx_ohci; + struct device *dev = &pdev->dev; + int error, hcd_irq, oc_irq; + struct usb_hcd *hcd; + struct resource *mem; + + hcd = usb_create_hcd(&ohci_da8xx_hc_driver, dev, dev_name(dev)); + if (!hcd) + return -ENOMEM; + + da8xx_ohci = to_da8xx_ohci(hcd); + da8xx_ohci->hcd = hcd; + + da8xx_ohci->usb11_clk = devm_clk_get(dev, NULL); + if (IS_ERR(da8xx_ohci->usb11_clk)) { + error = PTR_ERR(da8xx_ohci->usb11_clk); + if (error != -EPROBE_DEFER) + dev_err(dev, "Failed to get clock.\n"); + goto err; + } + + da8xx_ohci->usb11_phy = devm_phy_get(dev, "usb-phy"); + if (IS_ERR(da8xx_ohci->usb11_phy)) { + error = PTR_ERR(da8xx_ohci->usb11_phy); + if (error != -EPROBE_DEFER) + dev_err(dev, "Failed to get phy.\n"); + goto err; + } + + da8xx_ohci->vbus_reg = devm_regulator_get_optional(dev, "vbus"); + if (IS_ERR(da8xx_ohci->vbus_reg)) { + error = PTR_ERR(da8xx_ohci->vbus_reg); + if (error == -ENODEV) { + da8xx_ohci->vbus_reg = NULL; + } else if (error == -EPROBE_DEFER) { + goto err; + } else { + dev_err(dev, "Failed to get regulator\n"); + goto err; + } + } + + da8xx_ohci->oc_gpio = devm_gpiod_get_optional(dev, "oc", GPIOD_IN); + if (IS_ERR(da8xx_ohci->oc_gpio)) { + error = PTR_ERR(da8xx_ohci->oc_gpio); + goto err; + } + + if (da8xx_ohci->oc_gpio) { + oc_irq = gpiod_to_irq(da8xx_ohci->oc_gpio); + if (oc_irq < 0) { + error = oc_irq; + goto err; + } + + error = devm_request_threaded_irq(dev, oc_irq, NULL, + ohci_da8xx_oc_thread, IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "OHCI over-current indicator", da8xx_ohci); + if (error) + goto err; + } + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); + if (IS_ERR(hcd->regs)) { + error = PTR_ERR(hcd->regs); + goto err; + } + hcd->rsrc_start = mem->start; + hcd->rsrc_len = resource_size(mem); + + hcd_irq = platform_get_irq(pdev, 0); + if (hcd_irq < 0) { + error = -ENODEV; + goto err; + } + + error = usb_add_hcd(hcd, hcd_irq, 0); + if (error) + goto err; + + device_wakeup_enable(hcd->self.controller); + + error = ohci_da8xx_register_notify(hcd); + if (error) + goto err_remove_hcd; + + return 0; + +err_remove_hcd: + usb_remove_hcd(hcd); +err: + usb_put_hcd(hcd); + return error; +} + +static void ohci_da8xx_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); +} + +#ifdef CONFIG_PM +static int ohci_da8xx_suspend(struct platform_device *pdev, + pm_message_t message) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + bool do_wakeup = device_may_wakeup(&pdev->dev); + int ret; + + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + ret = ohci_suspend(hcd, do_wakeup); + if (ret) + return ret; + + ohci_da8xx_disable(hcd); + hcd->state = HC_STATE_SUSPENDED; + + return ret; +} + +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); + int ret; + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + ret = ohci_da8xx_enable(hcd); + if (ret) + return ret; + + ohci_resume(hcd, false); + + return 0; +} +#endif + +static const struct ohci_driver_overrides da8xx_overrides __initconst = { + .reset = ohci_da8xx_reset, + .extra_priv_size = sizeof(struct da8xx_ohci_hcd), +}; + +/* + * Driver definition to register with platform structure. + */ +static struct platform_driver ohci_hcd_da8xx_driver = { + .probe = ohci_da8xx_probe, + .remove = ohci_da8xx_remove, + .shutdown = usb_hcd_platform_shutdown, +#ifdef CONFIG_PM + .suspend = ohci_da8xx_suspend, + .resume = ohci_da8xx_resume, +#endif + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(da8xx_ohci_ids), + }, +}; + +static int __init ohci_da8xx_init(void) +{ + + if (usb_disabled()) + return -ENODEV; + + ohci_init_driver(&ohci_da8xx_hc_driver, &da8xx_overrides); + + /* + * The Davinci da8xx HW has some unusual quirks, which require + * da8xx-specific workarounds. We override certain hc_driver + * functions here to achieve that. We explicitly do not enhance + * ohci_driver_overrides to allow this more easily, since this + * is an unusual case, and we don't want to encourage others to + * override these functions by making it too easy. + */ + + orig_ohci_hub_control = ohci_da8xx_hc_driver.hub_control; + orig_ohci_hub_status_data = ohci_da8xx_hc_driver.hub_status_data; + + ohci_da8xx_hc_driver.hub_status_data = ohci_da8xx_hub_status_data; + ohci_da8xx_hc_driver.hub_control = ohci_da8xx_hub_control; + + return platform_driver_register(&ohci_hcd_da8xx_driver); +} +module_init(ohci_da8xx_init); + +static void __exit ohci_da8xx_exit(void) +{ + platform_driver_unregister(&ohci_hcd_da8xx_driver); +} +module_exit(ohci_da8xx_exit); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c new file mode 100644 index 000000000..23dc9eddc --- /dev/null +++ b/drivers/usb/host/ohci-dbg.c @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * 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. + */ + +/*-------------------------------------------------------------------------*/ + +#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)) + + +#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) +{ + 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); + } + } +} + +/*-------------------------------------------------------------------------*/ + +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, size; + unsigned long flags; + + ohci = buf->ohci; + size = PAGE_SIZE; + + /* display control and bulk lists together, for simplicity */ + spin_lock_irqsave (&ohci->lock, flags); + temp = show_list(ohci, buf->page, size, ohci->ed_controltail); + temp += show_list(ohci, buf->page + temp, size - 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; + + seen = kmalloc_objs(*seen, DBG_SCHED_LIMIT, GFP_ATOMIC); + if (!seen) + 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++) { + ed = ohci->periodic[i]; + if (!ed) + 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_obj(struct debug_buffer); + + 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; + + if (!buf->page) + buf->page = kzalloc(PAGE_SIZE, 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; + + 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; + + kfree(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; + struct dentry *root; + + root = debugfs_create_dir(bus->bus_name, ohci_debug_root); + ohci->debug_dir = root; + + debugfs_create_file("async", S_IRUGO, root, ohci, &debug_async_fops); + debugfs_create_file("periodic", S_IRUGO, root, ohci, + &debug_periodic_fops); + debugfs_create_file("registers", S_IRUGO, root, ohci, + &debug_registers_fops); + + ohci_dbg (ohci, "created debug files\n"); +} + +static inline void remove_debug_files (struct ohci_hcd *ohci) +{ + debugfs_remove_recursive(ohci->debug_dir); +} + +/*-------------------------------------------------------------------------*/ + diff --git a/drivers/usb/host/ohci-exynos.c b/drivers/usb/host/ohci-exynos.c new file mode 100644 index 000000000..cc5cb0900 --- /dev/null +++ b/drivers/usb/host/ohci-exynos.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * SAMSUNG EXYNOS USB HOST OHCI Controller + * + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * Author: Jingoo Han + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ohci.h" + +#define DRIVER_DESC "OHCI Exynos driver" + +static struct hc_driver __read_mostly exynos_ohci_hc_driver; + +#define to_exynos_ohci(hcd) (struct exynos_ohci_hcd *)(hcd_to_ohci(hcd)->priv) + +#define PHY_NUMBER 3 + +struct exynos_ohci_hcd { + struct clk *clk; + struct device_node *of_node; + struct phy *phy[PHY_NUMBER]; + bool legacy_phy; +}; + +static int exynos_ohci_get_phy(struct device *dev, + struct exynos_ohci_hcd *exynos_ohci) +{ + struct phy *phy; + int phy_number, num_phys; + int ret; + + /* Get PHYs for the controller */ + num_phys = of_count_phandle_with_args(dev->of_node, "phys", + "#phy-cells"); + for (phy_number = 0; phy_number < num_phys; phy_number++) { + phy = devm_of_phy_get_by_index(dev, dev->of_node, phy_number); + if (IS_ERR(phy)) + return PTR_ERR(phy); + exynos_ohci->phy[phy_number] = phy; + } + if (num_phys > 0) + return 0; + + /* Get PHYs using legacy bindings */ + for_each_available_child_of_node_scoped(dev->of_node, child) { + ret = of_property_read_u32(child, "reg", &phy_number); + if (ret) { + dev_err(dev, "Failed to parse device tree\n"); + return ret; + } + + if (phy_number >= PHY_NUMBER) { + dev_err(dev, "Invalid number of PHYs\n"); + return -EINVAL; + } + + phy = devm_of_phy_optional_get(dev, child, NULL); + exynos_ohci->phy[phy_number] = phy; + if (IS_ERR(phy)) + return PTR_ERR(phy); + } + + exynos_ohci->legacy_phy = true; + return 0; +} + +static int exynos_ohci_phy_enable(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); + int i; + int ret = 0; + + for (i = 0; ret == 0 && i < PHY_NUMBER; i++) + ret = phy_power_on(exynos_ohci->phy[i]); + if (ret) + for (i--; i >= 0; i--) + phy_power_off(exynos_ohci->phy[i]); + + return ret; +} + +static void exynos_ohci_phy_disable(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); + int i; + + for (i = 0; i < PHY_NUMBER; i++) + phy_power_off(exynos_ohci->phy[i]); +} + +static int exynos_ohci_probe(struct platform_device *pdev) +{ + struct exynos_ohci_hcd *exynos_ohci; + struct usb_hcd *hcd; + struct resource *res; + 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. + */ + err = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (err) + return err; + + 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 = to_exynos_ohci(hcd); + + err = exynos_ohci_get_phy(&pdev->dev, exynos_ohci); + if (err) + goto fail_io; + + exynos_ohci->clk = devm_clk_get_enabled(&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_io; + } + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(hcd->regs)) { + err = PTR_ERR(hcd->regs); + goto fail_io; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + err = irq; + goto fail_io; + } + + platform_set_drvdata(pdev, hcd); + + err = exynos_ohci_phy_enable(&pdev->dev); + if (err) { + dev_err(&pdev->dev, "Failed to enable USB phy\n"); + goto fail_io; + } + + /* + * Workaround: reset of_node pointer to avoid conflict between legacy + * Exynos OHCI port subnodes and generic USB device bindings + */ + exynos_ohci->of_node = pdev->dev.of_node; + if (exynos_ohci->legacy_phy) + pdev->dev.of_node = NULL; + + err = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (err) { + dev_err(&pdev->dev, "Failed to add USB HCD\n"); + goto fail_add_hcd; + } + device_wakeup_enable(hcd->self.controller); + return 0; + +fail_add_hcd: + exynos_ohci_phy_disable(&pdev->dev); + pdev->dev.of_node = exynos_ohci->of_node; +fail_io: + usb_put_hcd(hcd); + return err; +} + +static void exynos_ohci_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); + + pdev->dev.of_node = exynos_ohci->of_node; + + usb_remove_hcd(hcd); + + exynos_ohci_phy_disable(&pdev->dev); + + usb_put_hcd(hcd); +} + +static void exynos_ohci_shutdown(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + +static int exynos_ohci_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); + bool do_wakeup = device_may_wakeup(dev); + int rc = ohci_suspend(hcd, do_wakeup); + + if (rc) + return rc; + + exynos_ohci_phy_disable(dev); + + clk_disable_unprepare(exynos_ohci->clk); + + return 0; +} + +static int exynos_ohci_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); + int ret; + + clk_prepare_enable(exynos_ohci->clk); + + ret = exynos_ohci_phy_enable(dev); + if (ret) { + dev_err(dev, "Failed to enable USB phy\n"); + clk_disable_unprepare(exynos_ohci->clk); + return ret; + } + + ohci_resume(hcd, false); + + return 0; +} + +static const struct ohci_driver_overrides exynos_overrides __initconst = { + .extra_priv_size = sizeof(struct exynos_ohci_hcd), +}; + +static DEFINE_SIMPLE_DEV_PM_OPS(exynos_ohci_pm_ops, + exynos_ohci_suspend, exynos_ohci_resume); + +#ifdef CONFIG_OF +static const struct of_device_id exynos_ohci_match[] = { + { .compatible = "samsung,exynos4210-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", + .pm = pm_ptr(&exynos_ohci_pm_ops), + .of_match_table = of_match_ptr(exynos_ohci_match), + } +}; +static int __init ohci_exynos_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ohci_init_driver(&exynos_ohci_hc_driver, &exynos_overrides); + return platform_driver_register(&exynos_ohci_driver); +} +module_init(ohci_exynos_init); + +static void __exit ohci_exynos_cleanup(void) +{ + platform_driver_unregister(&exynos_ohci_driver); +} +module_exit(ohci_exynos_cleanup); + +MODULE_ALIAS("platform:exynos-ohci"); +MODULE_AUTHOR("Jingoo Han "); +MODULE_DESCRIPTION("OHCI support for Samsung S5P/Exynos SoC Series"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c new file mode 100644 index 000000000..129468b1e --- /dev/null +++ b/drivers/usb/host/ohci-hcd.c @@ -0,0 +1,1355 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * 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 https://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 +#include + + +#define DRIVER_AUTHOR "Roman Weissgaerber, David Brownell" +#define DRIVER_DESC "USB 1.1 'Open' Host Controller (OHCI) Driver" + +/*-------------------------------------------------------------------------*/ + +/* 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) +#define IO_WATCHDOG_DELAY msecs_to_jiffies(275) +#define IO_WATCHDOG_OFF 0xffffff00 + +#include "ohci.h" +#include "pci-quirks.h" + +static void ohci_dump(struct ohci_hcd *ohci); +static void ohci_stop(struct usb_hcd *hcd); +static void io_watchdog_func(struct timer_list *t); + +#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; +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; +module_param (no_handshake, bool, 0); +MODULE_PARM_DESC (no_handshake, "true (not default) disables BIOS handshake"); + +/*-------------------------------------------------------------------------*/ + +static int number_of_tds(struct urb *urb) +{ + int len, i, num, this_sg_len; + struct scatterlist *sg; + + len = urb->transfer_buffer_length; + i = urb->num_mapped_sgs; + + if (len > 0 && i > 0) { /* Scatter-gather transfer */ + num = 0; + sg = urb->sg; + for (;;) { + this_sg_len = min_t(int, sg_dma_len(sg), len); + num += DIV_ROUND_UP(this_sg_len, 4096); + len -= this_sg_len; + if (--i <= 0 || len <= 0) + break; + sg = sg_next(sg); + } + + } else { /* Non-SG transfer */ + /* one TD for every 4096 Bytes (could be up to 8K) */ + num = DIV_ROUND_UP(len, 4096); + } + return num; +} + +/* + * 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; + + /* every endpoint has a ed, locate and maybe (re)initialize it */ + ed = ed_get(ohci, urb->ep, urb->dev, pipe, urb->interval); + if (! ed) + 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: + size += number_of_tds(urb); + /* 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)) == 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_flex(*urb_priv, td, size, 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; + } + + /* Start up the I/O watchdog timer, if it's not running */ + if (ohci->prev_frame_no == IO_WATCHDOG_OFF && + list_empty(&ohci->eds_in_use) && + !(ohci->flags & OHCI_QUIRK_QEMU)) { + ohci->prev_frame_no = ohci_frame_no(ohci); + mod_timer(&ohci->io_watchdog, + jiffies + IO_WATCHDOG_DELAY); + } + list_add(&ed->in_use_list, &ohci->eds_in_use); + + 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; + urb_priv_t *urb_priv; + + spin_lock_irqsave (&ohci->lock, flags); + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc == 0) { + + /* 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->ed->state == ED_OPER) + start_ed_unlink(ohci, urb_priv->ed); + + if (ohci->rh_state != OHCI_RH_RUNNING) { + /* With HC dead, we can clean up right away */ + ohci_work(ohci); + } + } + 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; + ohci_work(ohci); + } + + 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"); + 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; + } + fallthrough; + 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); + ohci->rh_state = OHCI_RH_HALTED; +} + +static void ohci_shutdown(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + unsigned long flags; + + spin_lock_irqsave(&ohci->lock, flags); + _ohci_shutdown(hcd); + 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); + + /* Accept arbitrarily long scatter-gather lists */ + if (!hcd->localmem_pool) + hcd->self.sg_tablesize = ~0; + + 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; + + timer_setup(&ohci->io_watchdog, io_watchdog_func, 0); + ohci->prev_frame_no = IO_WATCHDOG_OFF; + + if (hcd->localmem_pool) + ohci->hcca = gen_pool_dma_alloc_align(hcd->localmem_pool, + sizeof(*ohci->hcca), + &ohci->hcca_dma, 256); + else + ohci->hcca = dma_alloc_coherent(hcd->self.controller, + sizeof(*ohci->hcca), + &ohci->hcca_dma, + GFP_KERNEL); + 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 hub_wq is allocated */ + 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); + /* Configure for per-port over-current protection by default */ + val &= ~RH_A_NOCP; + val |= RH_A_OCPM; + if (ohci->flags & OHCI_QUIRK_SUPERIO) { + /* NSC 87560 and maybe others. + * Ganged power switching, no over-current protection. + */ + val |= RH_A_NOCP; + val &= ~(RH_A_POTPGT | RH_A_NPS | RH_A_PSM | RH_A_OCPM); + } 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. + */ + 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); + + ohci_dump(ohci); + + return 0; +} + +/* ohci_setup routine for generic controller initialization */ + +int ohci_setup(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + ohci_hcd_init(ohci); + + return ohci_init(ohci); +} +EXPORT_SYMBOL_GPL(ohci_setup); + +/* ohci_start routine for generic controller start of all OHCI bus glue */ +static int ohci_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; +} + +/*-------------------------------------------------------------------------*/ + +/* + * Some OHCI controllers are known to lose track of completed TDs. They + * don't add the TDs to the hardware done queue, which means we never see + * them as being completed. + * + * This watchdog routine checks for such problems. Without some way to + * tell when those TDs have completed, we would never take their EDs off + * the unlink list. As a result, URBs could never be dequeued and + * endpoints could never be released. + */ +static void io_watchdog_func(struct timer_list *t) +{ + struct ohci_hcd *ohci = timer_container_of(ohci, t, + io_watchdog); + bool takeback_all_pending = false; + u32 status; + u32 head; + struct ed *ed; + struct td *td, *td_start, *td_next; + unsigned frame_no, prev_frame_no = IO_WATCHDOG_OFF; + unsigned long flags; + + spin_lock_irqsave(&ohci->lock, flags); + + /* + * One way to lose track of completed TDs is if the controller + * never writes back the done queue head. If it hasn't been + * written back since the last time this function ran and if it + * was non-empty at that time, something is badly wrong with the + * hardware. + */ + status = ohci_readl(ohci, &ohci->regs->intrstatus); + if (!(status & OHCI_INTR_WDH) && ohci->wdh_cnt == ohci->prev_wdh_cnt) { + if (ohci->prev_donehead) { + ohci_err(ohci, "HcDoneHead not written back; disabled\n"); + died: + usb_hc_died(ohci_to_hcd(ohci)); + ohci_dump(ohci); + _ohci_shutdown(ohci_to_hcd(ohci)); + goto done; + } else { + /* No write back because the done queue was empty */ + takeback_all_pending = true; + } + } + + /* Check every ED which might have pending TDs */ + list_for_each_entry(ed, &ohci->eds_in_use, in_use_list) { + if (ed->pending_td) { + if (takeback_all_pending || + OKAY_TO_TAKEBACK(ohci, ed)) { + unsigned tmp = hc32_to_cpu(ohci, ed->hwINFO); + + ohci_dbg(ohci, "takeback pending TD for dev %d ep 0x%x\n", + 0x007f & tmp, + (0x000f & (tmp >> 7)) + + ((tmp & ED_IN) >> 5)); + add_to_done_list(ohci, ed->pending_td); + } + } + + /* Starting from the latest pending TD, */ + td = ed->pending_td; + + /* or the last TD on the done list, */ + if (!td) { + list_for_each_entry(td_next, &ed->td_list, td_list) { + if (!td_next->next_dl_td) + break; + td = td_next; + } + } + + /* find the last TD processed by the controller. */ + head = hc32_to_cpu(ohci, READ_ONCE(ed->hwHeadP)) & TD_MASK; + td_start = td; + td_next = list_prepare_entry(td, &ed->td_list, td_list); + list_for_each_entry_continue(td_next, &ed->td_list, td_list) { + if (head == (u32) td_next->td_dma) + break; + td = td_next; /* head pointer has passed this TD */ + } + if (td != td_start) { + /* + * In case a WDH cycle is in progress, we will wait + * for the next two cycles to complete before assuming + * this TD will never get on the done queue. + */ + ed->takeback_wdh_cnt = ohci->wdh_cnt + 2; + ed->pending_td = td; + } + } + + ohci_work(ohci); + + if (ohci->rh_state == OHCI_RH_RUNNING) { + + /* + * Sometimes a controller just stops working. We can tell + * by checking that the frame counter has advanced since + * the last time we ran. + * + * But be careful: Some controllers violate the spec by + * stopping their frame counter when no ports are active. + */ + frame_no = ohci_frame_no(ohci); + if (frame_no == ohci->prev_frame_no) { + int active_cnt = 0; + int i; + unsigned tmp; + + for (i = 0; i < ohci->num_ports; ++i) { + tmp = roothub_portstatus(ohci, i); + /* Enabled and not suspended? */ + if ((tmp & RH_PS_PES) && !(tmp & RH_PS_PSS)) + ++active_cnt; + } + + if (active_cnt > 0) { + ohci_err(ohci, "frame counter not updating; disabled\n"); + goto died; + } + } + if (!list_empty(&ohci->eds_in_use)) { + prev_frame_no = frame_no; + ohci->prev_wdh_cnt = ohci->wdh_cnt; + ohci->prev_donehead = ohci_readl(ohci, + &ohci->regs->donehead); + mod_timer(&ohci->io_watchdog, + jiffies + IO_WATCHDOG_DELAY); + } + } + + done: + ohci->prev_frame_no = prev_frame_no; + spin_unlock_irqrestore(&ohci->lock, flags); +} + +/* 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 + */ +again: + 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); + ohci_usb_reset (ohci); + } + + if (ints & OHCI_INTR_RHSC) { + ohci_dbg(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 hub_wq clears all the port status change bits. We'll + * always disable it here and rely on polling until hub_wq + * 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_dbg(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); + } + + spin_lock(&ohci->lock); + if (ints & OHCI_INTR_WDH) + update_done_list(ohci); + + /* 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). + */ + ohci_work(ohci); + if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list + && ohci->rh_state == OHCI_RH_RUNNING) + ohci_writel (ohci, OHCI_INTR_SF, ®s->intrdisable); + + if (ohci->rh_state == OHCI_RH_RUNNING) { + ohci_writel (ohci, ints, ®s->intrstatus); + if (ints & OHCI_INTR_WDH) + ++ohci->wdh_cnt; + + ohci_writel (ohci, OHCI_INTR_MIE, ®s->intrenable); + // flush those writes + (void) ohci_readl (ohci, &ohci->regs->control); + } + spin_unlock(&ohci->lock); + + /* repeat until all enabled interrupts are handled */ + if (ohci->rh_state != OHCI_RH_HALTED) { + ints = ohci_readl(ohci, ®s->intrstatus); + if (ints && (ints & ohci_readl(ohci, ®s->intrenable))) + goto again; + } + + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +static void ohci_stop (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + + ohci_dump(ohci); + + if (quirk_nec(ohci)) + flush_work(&ohci->nec_work); + timer_delete_sync(&ohci->io_watchdog); + ohci->prev_frame_no = IO_WATCHDOG_OFF; + + ohci_writel (ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable); + ohci_usb_reset(ohci); + free_irq(hcd->irq, hcd); + hcd->irq = 0; + + if (quirk_amdiso(ohci)) + usb_amd_dev_put(); + + remove_debug_files (ohci); + ohci_mem_cleanup (ohci); + if (ohci->hcca) { + if (hcd->localmem_pool) + gen_pool_free(hcd->localmem_pool, + (unsigned long)ohci->hcca, + sizeof(*ohci->hcca)); + else + 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_USB_PCI) + +/* must not be called from interrupt context */ +int ohci_restart(struct ohci_hcd *ohci) +{ + int temp; + int i; + struct urb_priv *priv; + + ohci_init(ohci); + 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; + } + ohci_work(ohci); + 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; +} +EXPORT_SYMBOL_GPL(ohci_restart); + +#endif + +#ifdef CONFIG_PM + +int ohci_suspend(struct usb_hcd *hcd, bool do_wakeup) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + unsigned long flags; + int rc = 0; + + /* 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); + + synchronize_irq(hcd->irq); + + if (do_wakeup && HCD_WAKEUP_PENDING(hcd)) { + ohci_resume(hcd, false); + rc = -EBUSY; + } + return rc; +} +EXPORT_SYMBOL_GPL(ohci_suspend); + + +int 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; +} +EXPORT_SYMBOL_GPL(ohci_resume); + +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * Generic structure: This gets copied for platform drivers so that + * individual entries can be overridden as needed. + */ + +static const struct hc_driver ohci_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_DMA | HCD_USB11, + + /* + * basic lifecycle operations + */ + .reset = ohci_setup, + .start = 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, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +void ohci_init_driver(struct hc_driver *drv, + const struct ohci_driver_overrides *over) +{ + /* Copy the generic table to drv and then apply the overrides */ + *drv = ohci_hc_driver; + + if (over) { + drv->product_desc = over->product_desc; + drv->hcd_priv_size += over->extra_priv_size; + if (over->reset) + drv->reset = over->reset; + } +} +EXPORT_SYMBOL_GPL(ohci_init_driver); + +/*-------------------------------------------------------------------------*/ + +MODULE_AUTHOR (DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE ("GPL"); + +#if defined(CONFIG_ARCH_SA1100) && defined(CONFIG_SA1111) +#include "ohci-sa1111.c" +#define SA1111_DRIVER ohci_hcd_sa1111_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_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 + +static int __init ohci_hcd_mod_init(void) +{ + int retval = 0; + + if (usb_disabled()) + return -ENODEV; + + pr_debug ("%s: block sizes: ed %zd td %zd\n", hcd_name, + sizeof (struct ed), sizeof (struct td)); + + ohci_debug_root = debugfs_create_dir("ohci", usb_debug_root); + +#ifdef PS3_SYSTEM_BUS_DRIVER + retval = ps3_ohci_driver_register(&PS3_SYSTEM_BUS_DRIVER); + if (retval < 0) + goto error_ps3; +#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 SM501_OHCI_DRIVER + retval = platform_driver_register(&SM501_OHCI_DRIVER); + if (retval < 0) + goto error_sm501; +#endif + + return retval; + + /* Error path */ +#ifdef SM501_OHCI_DRIVER + platform_driver_unregister(&SM501_OHCI_DRIVER); + error_sm501: +#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 PS3_SYSTEM_BUS_DRIVER + ps3_ohci_driver_unregister(&PS3_SYSTEM_BUS_DRIVER); + error_ps3: +#endif + debugfs_remove(ohci_debug_root); + ohci_debug_root = NULL; + + return retval; +} +module_init(ohci_hcd_mod_init); + +static void __exit ohci_hcd_mod_exit(void) +{ +#ifdef SM501_OHCI_DRIVER + platform_driver_unregister(&SM501_OHCI_DRIVER); +#endif +#ifdef SA1111_DRIVER + sa1111_driver_unregister(&SA1111_DRIVER); +#endif +#ifdef OF_PLATFORM_DRIVER + platform_driver_unregister(&OF_PLATFORM_DRIVER); +#endif +#ifdef PS3_SYSTEM_BUS_DRIVER + ps3_ohci_driver_unregister(&PS3_SYSTEM_BUS_DRIVER); +#endif + debugfs_remove(ohci_debug_root); +} +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 000000000..b3d734ab6 --- /dev/null +++ b/drivers/usb/host/ohci-hub.c @@ -0,0 +1,803 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * 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, value, \ + (value & RH_PS_PRSC) ? " PRSC" : "", \ + (value & RH_PS_OCIC) ? " OCIC" : "", \ + (value & RH_PS_PSSC) ? " PSSC" : "", \ + (value & RH_PS_PESC) ? " PESC" : "", \ + (value & RH_PS_CSC) ? " CSC" : "", \ + \ + (value & RH_PS_LSDA) ? " LSDA" : "", \ + (value & RH_PS_PPS) ? " PPS" : "", \ + (value & RH_PS_PRS) ? " PRS" : "", \ + (value & RH_PS_POCI) ? " POCI" : "", \ + (value & RH_PS_PSS) ? " PSS" : "", \ + \ + (value & RH_PS_PES) ? " PES" : "", \ + (value & RH_PS_CCS) ? " CCS" : "" \ + ); + +/*-------------------------------------------------------------------------*/ + +#define OHCI_SCHED_ENABLES \ + (OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE) + +static void update_done_list(struct ohci_hcd *); +static void ohci_work(struct ohci_hcd *); + +#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); + fallthrough; + 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); + } + update_done_list(ohci); + ohci_work(ohci); + + /* All ED unlinks should be finished, no need for SOF interrupts */ + ohci_writel(ohci, OHCI_INTR_SF, &ohci->regs->intrdisable); + + /* + * 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); + 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. This + * will cause sparse to warn about a "context imbalance". + */ +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); + + if (rc == 0) { + timer_delete_sync(&ohci->io_watchdog); + ohci->prev_frame_no = IO_WATCHDOG_OFF; + } + 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 */ + +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; +} +EXPORT_SYMBOL_GPL(ohci_hub_status_data); + +/*-------------------------------------------------------------------------*/ + +static void +ohci_hub_descriptor ( + struct ohci_hcd *ohci, + struct usb_hub_descriptor *desc +) { + u32 rh = roothub_a (ohci); + u16 temp; + + desc->bDescriptorType = USB_DT_HUB; + 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 = HUB_CHAR_COMMON_LPSM | HUB_CHAR_COMMON_OCPM; + if (rh & RH_A_NPS) /* no power switching? */ + temp |= HUB_CHAR_NO_LPSM; + if (rh & RH_A_PSM) /* per-port power switching? */ + temp |= HUB_CHAR_INDV_PORT_LPSM; + if (rh & RH_A_NOCP) /* no overcurrent reporting? */ + temp |= HUB_CHAR_NO_OCPM; + else if (rh & RH_A_OCPM) /* per-port overcurrent reporting? */ + temp |= HUB_CHAR_INDV_PORT_OCPM; + desc->wHubCharacteristics = cpu_to_le16(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; + + /* hub_wq 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 hub_wq */ +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; +} + +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); + break; + 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); + + if (*(u16*)(buf+2)) /* only if wPortChange is interesting */ + 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; +} +EXPORT_SYMBOL_GPL(ohci_hub_control); diff --git a/drivers/usb/host/ohci-mem.c b/drivers/usb/host/ohci-mem.c new file mode 100644 index 000000000..1425335c6 --- /dev/null +++ b/drivers/usb/host/ohci-mem.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * 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); + INIT_LIST_HEAD(&ohci->eds_in_use); +} + +/*-------------------------------------------------------------------------*/ + +static int ohci_mem_init (struct ohci_hcd *ohci) +{ + /* + * HCs with local memory allocate from localmem_pool so there's + * no need to create the below dma pools. + */ + if (ohci_to_hcd(ohci)->localmem_pool) + return 0; + + 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) +{ + dma_pool_destroy(ohci->td_cache); + ohci->td_cache = NULL; + 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; + struct usb_hcd *hcd = ohci_to_hcd(hc); + + if (hcd->localmem_pool) + td = gen_pool_dma_zalloc_align(hcd->localmem_pool, + sizeof(*td), &dma, 32); + else + td = dma_pool_zalloc(hc->td_cache, mem_flags, &dma); + if (td) { + /* in case hc fetches it, make it look dead */ + 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)]; + struct usb_hcd *hcd = ohci_to_hcd(hc); + + 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); + + if (hcd->localmem_pool) + gen_pool_free(hcd->localmem_pool, (unsigned long)td, + sizeof(*td)); + else + 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; + struct usb_hcd *hcd = ohci_to_hcd(hc); + + if (hcd->localmem_pool) + ed = gen_pool_dma_zalloc_align(hcd->localmem_pool, + sizeof(*ed), &dma, 16); + else + ed = dma_pool_zalloc(hc->ed_cache, mem_flags, &dma); + if (ed) { + INIT_LIST_HEAD (&ed->td_list); + ed->dma = dma; + } + return ed; +} + +static void +ed_free (struct ohci_hcd *hc, struct ed *ed) +{ + struct usb_hcd *hcd = ohci_to_hcd(hc); + + if (hcd->localmem_pool) + gen_pool_free(hcd->localmem_pool, (unsigned long)ed, + sizeof(*ed)); + else + 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 000000000..7663f2aa3 --- /dev/null +++ b/drivers/usb/host/ohci-nxp.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ohci.h" + +#define USB_CONFIG_BASE 0x31020000 + +/* 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 + +#define DRIVER_DESC "OHCI NXP driver" + +static const char hcd_name[] = "ohci-nxp"; +static struct hc_driver __read_mostly ohci_nxp_hc_driver; + +static struct i2c_client *isp1301_i2c_client; + +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); + + 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 ohci_nxp_start_hc(void) +{ + void __iomem *usb_otg_stat_control = ioremap(USB_CONFIG_BASE + 0x110, 4); + unsigned long tmp; + + if (WARN_ON(!usb_otg_stat_control)) + return; + + tmp = __raw_readl(usb_otg_stat_control) | HOST_EN; + + __raw_writel(tmp, usb_otg_stat_control); + isp1301_vbus_on(); + + iounmap(usb_otg_stat_control); +} + +static void ohci_nxp_stop_hc(void) +{ + void __iomem *usb_otg_stat_control = ioremap(USB_CONFIG_BASE + 0x110, 4); + unsigned long tmp; + + if (WARN_ON(!usb_otg_stat_control)) + return; + + isp1301_vbus_off(); + tmp = __raw_readl(usb_otg_stat_control) & ~HOST_EN; + __raw_writel(tmp, usb_otg_stat_control); + + iounmap(usb_otg_stat_control); +} + +static int ohci_hcd_nxp_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd = NULL; + const struct hc_driver *driver = &ohci_nxp_hc_driver; + struct resource *res; + int ret = 0, irq; + struct device_node *isp1301_node; + struct clk *usb_host_clk; + + 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); + of_node_put(isp1301_node); + if (!isp1301_i2c_client) + return -EPROBE_DEFER; + + ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + goto err_put_client; + + 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 err_put_client; + } + + /* Enable USB host clock */ + usb_host_clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(usb_host_clk)) { + dev_err(&pdev->dev, "failed to acquire and start USB OHCI clock\n"); + ret = PTR_ERR(usb_host_clk); + goto err_put_client; + } + + 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 err_put_client; + } + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(hcd->regs)) { + ret = PTR_ERR(hcd->regs); + goto err_put_hcd; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = -ENXIO; + goto err_put_hcd; + } + + ohci_nxp_start_hc(); + platform_set_drvdata(pdev, hcd); + + dev_info(&pdev->dev, "at 0x%p, irq %d\n", hcd->regs, hcd->irq); + ret = usb_add_hcd(hcd, irq, 0); + if (ret == 0) { + device_wakeup_enable(hcd->self.controller); + return ret; + } + + ohci_nxp_stop_hc(); +err_put_hcd: + usb_put_hcd(hcd); +err_put_client: + put_device(&isp1301_i2c_client->dev); + isp1301_i2c_client = NULL; + return ret; +} + +static void ohci_hcd_nxp_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); + ohci_nxp_stop_hc(); + usb_put_hcd(hcd); + put_device(&isp1301_i2c_client->dev); + isp1301_i2c_client = NULL; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:usb-ohci"); + +#ifdef CONFIG_OF +static const struct of_device_id ohci_hcd_nxp_match[] = { + { .compatible = "nxp,ohci-nxp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ohci_hcd_nxp_match); +#endif + +static struct platform_driver ohci_hcd_nxp_driver = { + .driver = { + .name = "usb-ohci", + .of_match_table = of_match_ptr(ohci_hcd_nxp_match), + }, + .probe = ohci_hcd_nxp_probe, + .remove = ohci_hcd_nxp_remove, +}; + +static int __init ohci_nxp_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ohci_init_driver(&ohci_nxp_hc_driver, NULL); + return platform_driver_register(&ohci_hcd_nxp_driver); +} +module_init(ohci_nxp_init); + +static void __exit ohci_nxp_cleanup(void) +{ + platform_driver_unregister(&ohci_hcd_nxp_driver); +} +module_exit(ohci_nxp_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c new file mode 100644 index 000000000..f6e56c4b9 --- /dev/null +++ b/drivers/usb/host/ohci-omap.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include "ohci.h" + +#include +#include + +#define DRIVER_DESC "OHCI OMAP driver" + +struct ohci_omap_priv { + struct clk *usb_host_ck; + struct clk *usb_dc_ck; + struct gpio_desc *power; + struct gpio_desc *overcurrent; +}; + +static const char hcd_name[] = "ohci-omap"; +static struct hc_driver __read_mostly ohci_omap_hc_driver; + +#define hcd_to_ohci_omap_priv(h) \ + ((struct ohci_omap_priv *)hcd_to_ohci(h)->priv) + +static void omap_ohci_clock_power(struct ohci_omap_priv *priv, int on) +{ + if (on) { + clk_enable(priv->usb_dc_ck); + clk_enable(priv->usb_host_ck); + /* guesstimate for T5 == 1x 32K clock + APLL lock time */ + udelay(100); + } else { + clk_disable(priv->usb_host_ck); + clk_disable(priv->usb_dc_ck); + } +} + +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->usb_phy->otg); + + local_irq_save(flags); + hcd->usb_phy->otg->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); +} + +/*-------------------------------------------------------------------------*/ + +static int ohci_omap_reset(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct omap_usb_config *config = dev_get_platdata(hcd->self.controller); + struct ohci_omap_priv *priv = hcd_to_ohci_omap_priv(hcd); + int need_transceiver = (config->otg != 0); + int ret; + + dev_dbg(hcd->self.controller, "starting USB Controller\n"); + + if (config->otg) { + hcd->self.otg_port = config->otg; + /* default/minimum OTG power budget: 8 mA */ + hcd->power_budget = 8; + } + + /* XXX OMAP16xx only */ + if (config->ocpi_enable) + config->ocpi_enable(); + + if (IS_ENABLED(CONFIG_USB_OTG) && need_transceiver) { + hcd->usb_phy = usb_get_phy(USB_PHY_TYPE_USB2); + if (!IS_ERR_OR_NULL(hcd->usb_phy)) { + int status = otg_set_host(hcd->usb_phy->otg, + &ohci_to_hcd(ohci)->self); + dev_dbg(hcd->self.controller, "init %s phy, status %d\n", + hcd->usb_phy->label, status); + if (status) { + usb_put_phy(hcd->usb_phy); + return status; + } + } else { + return -EPROBE_DEFER; + } + hcd->skip_phy_initialization = 1; + ohci->start_hnp = start_hnp; + } + + omap_ohci_clock_power(priv, 1); + + if (config->lb_reset) + config->lb_reset(); + + ret = ohci_setup(hcd); + if (ret < 0) + return ret; + + if (config->otg || config->rwc) { + ohci->hc_control = OHCI_CTRL_RWC; + writel(OHCI_CTRL_RWC, &ohci->regs->control); + } + + /* board-specific power switching and overcurrent support */ + if (machine_is_omap_osk()) { + 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 detection */ + omap_cfg_reg(W8_1610_GPIO9); + + /* 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 hub_wq hub requests should manage power switching */ + if (config->transceiver_power) + return config->transceiver_power(1); + + if (priv->power) + gpiod_set_value_cansleep(priv->power, 0); + + /* 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; +} + +/*-------------------------------------------------------------------------*/ + +/** + * ohci_hcd_omap_probe - initialize OMAP-based HCDs + * @pdev: USB controller to probe + * + * Context: task context, might sleep + * + * 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_omap_probe(struct platform_device *pdev) +{ + int retval, irq; + struct usb_hcd *hcd = 0; + struct ohci_omap_priv *priv; + + if (pdev->num_resources != 2) { + dev_err(&pdev->dev, "invalid num_resources: %i\n", + pdev->num_resources); + return -ENODEV; + } + + if (pdev->resource[0].flags != IORESOURCE_MEM + || pdev->resource[1].flags != IORESOURCE_IRQ) { + dev_err(&pdev->dev, "invalid resource type\n"); + return -ENODEV; + } + + hcd = usb_create_hcd(&ohci_omap_hc_driver, &pdev->dev, + dev_name(&pdev->dev)); + if (!hcd) + return -ENOMEM; + + hcd->rsrc_start = pdev->resource[0].start; + hcd->rsrc_len = pdev->resource[0].end - pdev->resource[0].start + 1; + priv = hcd_to_ohci_omap_priv(hcd); + + /* Obtain two optional GPIO lines */ + priv->power = devm_gpiod_get_optional(&pdev->dev, "power", GPIOD_ASIS); + if (IS_ERR(priv->power)) { + retval = PTR_ERR(priv->power); + goto err_put_hcd; + } + if (priv->power) + gpiod_set_consumer_name(priv->power, "OHCI power"); + + /* + * This "overcurrent" GPIO line isn't really used in the code, + * but has a designated hardware function. + * TODO: implement proper overcurrent handling. + */ + priv->overcurrent = devm_gpiod_get_optional(&pdev->dev, "overcurrent", + GPIOD_IN); + if (IS_ERR(priv->overcurrent)) { + retval = PTR_ERR(priv->overcurrent); + goto err_put_hcd; + } + if (priv->overcurrent) + gpiod_set_consumer_name(priv->overcurrent, "OHCI overcurrent"); + + priv->usb_host_ck = clk_get(&pdev->dev, "usb_hhc_ck"); + if (IS_ERR(priv->usb_host_ck)) { + retval = PTR_ERR(priv->usb_host_ck); + goto err_put_hcd; + } + + retval = clk_prepare(priv->usb_host_ck); + if (retval) + goto err_put_host_ck; + + if (!cpu_is_omap15xx()) + priv->usb_dc_ck = clk_get(&pdev->dev, "usb_dc_ck"); + else + priv->usb_dc_ck = clk_get(&pdev->dev, "lb_ck"); + + if (IS_ERR(priv->usb_dc_ck)) { + retval = PTR_ERR(priv->usb_dc_ck); + goto err_unprepare_host_ck; + } + + retval = clk_prepare(priv->usb_dc_ck); + if (retval) + goto err_put_dc_ck; + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + dev_dbg(&pdev->dev, "request_mem_region failed\n"); + retval = -EBUSY; + goto err_unprepare_dc_ck; + } + + 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; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + retval = irq; + goto err3; + } + retval = usb_add_hcd(hcd, irq, 0); + if (retval) + goto err3; + + device_wakeup_enable(hcd->self.controller); + return 0; +err3: + iounmap(hcd->regs); +err2: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err_unprepare_dc_ck: + clk_unprepare(priv->usb_dc_ck); +err_put_dc_ck: + clk_put(priv->usb_dc_ck); +err_unprepare_host_ck: + clk_unprepare(priv->usb_host_ck); +err_put_host_ck: + clk_put(priv->usb_host_ck); +err_put_hcd: + usb_put_hcd(hcd); + return retval; +} + + +/* may be called with controller, bus, and devices active */ + +/** + * ohci_hcd_omap_remove - shutdown processing for OMAP-based HCDs + * @pdev: USB Host Controller being removed + * + * Context: task context, might sleep + * + * Reverses the effect of ohci_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 void ohci_hcd_omap_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_omap_priv *priv = hcd_to_ohci_omap_priv(hcd); + + dev_dbg(hcd->self.controller, "stopping USB Controller\n"); + usb_remove_hcd(hcd); + omap_ohci_clock_power(priv, 0); + if (!IS_ERR_OR_NULL(hcd->usb_phy)) { + (void) otg_set_host(hcd->usb_phy->otg, 0); + usb_put_phy(hcd->usb_phy); + } + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + clk_unprepare(priv->usb_dc_ck); + clk_put(priv->usb_dc_ck); + clk_unprepare(priv->usb_host_ck); + clk_put(priv->usb_host_ck); + usb_put_hcd(hcd); +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM + +static int ohci_omap_suspend(struct platform_device *pdev, pm_message_t message) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct ohci_omap_priv *priv = hcd_to_ohci_omap_priv(hcd); + bool do_wakeup = device_may_wakeup(&pdev->dev); + int ret; + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + ret = ohci_suspend(hcd, do_wakeup); + if (ret) + return ret; + + omap_ohci_clock_power(priv, 0); + return ret; +} + +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); + struct ohci_omap_priv *priv = hcd_to_ohci_omap_priv(hcd); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + omap_ohci_clock_power(priv, 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_probe, + .remove = ohci_hcd_omap_remove, + .shutdown = usb_hcd_platform_shutdown, +#ifdef CONFIG_PM + .suspend = ohci_omap_suspend, + .resume = ohci_omap_resume, +#endif + .driver = { + .name = "ohci", + }, +}; + +static const struct ohci_driver_overrides omap_overrides __initconst = { + .product_desc = "OMAP OHCI", + .reset = ohci_omap_reset, + .extra_priv_size = sizeof(struct ohci_omap_priv), +}; + +static int __init ohci_omap_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ohci_init_driver(&ohci_omap_hc_driver, &omap_overrides); + return platform_driver_register(&ohci_hcd_omap_driver); +} +module_init(ohci_omap_init); + +static void __exit ohci_omap_cleanup(void) +{ + platform_driver_unregister(&ohci_hcd_omap_driver); +} +module_exit(ohci_omap_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_ALIAS("platform:ohci"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c new file mode 100644 index 000000000..9f0a6b27e --- /dev/null +++ b/drivers/usb/host/ohci-pci.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "ohci.h" +#include "pci-quirks.h" + +#define DRIVER_DESC "OHCI PCI platform driver" + +static const char hcd_name[] = "ohci-pci"; + + +/*-------------------------------------------------------------------------*/ + +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_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); + + if (usb_amd_quirk_pll_check()) + ohci->flags |= OHCI_QUIRK_AMD_PLL; + + /* SB800 needs pre-fetch fix */ + if (usb_amd_prefetch_quirk()) { + ohci->flags |= OHCI_QUIRK_AMD_PREFETCH; + ohci_dbg(ohci, "enabled AMD prefetch quirk\n"); + } + + ohci->flags |= OHCI_QUIRK_GLOBAL_SUSPEND; + return 0; +} + +static int ohci_quirk_loongson(struct usb_hcd *hcd) +{ + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + + /* + * Loongson's LS7A OHCI controller (rev 0x02) has a + * flaw. MMIO register with offset 0x60/64 is treated + * as legacy PS2-compatible keyboard/mouse interface. + * Since OHCI only use 4KB BAR resource, LS7A OHCI's + * 32KB BAR is wrapped around (the 2nd 4KB BAR space + * is the same as the 1st 4KB internally). So add 4KB + * offset (0x1000) to the OHCI registers as a quirk. + */ + if (pdev->revision == 0x2) + hcd->regs += SZ_4K; /* SZ_4K = 0x1000 */ + + return 0; +} + +static int ohci_quirk_qemu(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + ohci->flags |= OHCI_QUIRK_QEMU; + ohci_dbg(ohci, "enabled qemu quirk\n"); + return 0; +} + +/* 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, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a24), + .driver_data = (unsigned long)ohci_quirk_loongson, + }, + { + .vendor = PCI_VENDOR_ID_APPLE, + .device = 0x003f, + .subvendor = PCI_SUBVENDOR_ID_REDHAT_QUMRANET, + .subdevice = PCI_SUBDEVICE_ID_QEMU, + .driver_data = (unsigned long)ohci_quirk_qemu, + }, + + {}, +}; + +static int ohci_pci_reset (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + int ret = 0; + + if (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) + ret = ohci_setup(hcd); + /* + * After ohci setup RWC may not be set for add-in PCI cards. + * This transfers PCI PM wakeup capabilities. + */ + if (device_can_wakeup(&pdev->dev)) + ohci->hc_control |= OHCI_CTRL_RWC; + return ret; +} + +static struct hc_driver __read_mostly ohci_pci_hc_driver; + +static const struct ohci_driver_overrides pci_overrides __initconst = { + .product_desc = "OHCI PCI host controller", + .reset = ohci_pci_reset, +}; + +static const struct pci_device_id pci_ids[] = { { + /* handle any USB OHCI controller */ + PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_OHCI, ~0), + }, { + /* The device in the ConneXT I/O hub has no class reg */ + PCI_VDEVICE(STMICRO, PCI_DEVICE_ID_STMICRO_USB_OHCI), + }, { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE (pci, pci_ids); + +static int ohci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + return usb_hcd_pci_probe(dev, &ohci_pci_hc_driver); +} + +/* pci driver glue; this is a "new style" PCI driver module */ +static struct pci_driver ohci_pci_driver = { + .name = hcd_name, + .id_table = pci_ids, + + .probe = ohci_pci_probe, + .remove = usb_hcd_pci_remove, + .shutdown = usb_hcd_pci_shutdown, + +#ifdef CONFIG_PM + .driver = { + .pm = &usb_hcd_pci_pm_ops + }, +#endif +}; + +#ifdef CONFIG_PM +static int ohci_pci_resume(struct usb_hcd *hcd, pm_message_t msg) +{ + return ohci_resume(hcd, msg.event == PM_EVENT_RESTORE); +} +#endif +static int __init ohci_pci_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ohci_init_driver(&ohci_pci_hc_driver, &pci_overrides); + +#ifdef CONFIG_PM + /* Entries for the PCI suspend/resume callbacks are special */ + ohci_pci_hc_driver.pci_suspend = ohci_suspend; + ohci_pci_hc_driver.pci_resume = ohci_pci_resume; +#endif + + return pci_register_driver(&ohci_pci_driver); +} +module_init(ohci_pci_init); + +static void __exit ohci_pci_cleanup(void) +{ + pci_unregister_driver(&ohci_pci_driver); +} +module_exit(ohci_pci_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_SOFTDEP("pre: ehci_pci"); diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c new file mode 100644 index 000000000..c801527d5 --- /dev/null +++ b/drivers/usb/host/ohci-platform.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic platform ohci driver + * + * Copyright 2007 Michael Buesch + * Copyright 2011-2012 Hauke Mehrtens + * Copyright 2014 Hans de Goede + * + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ohci.h" + +#define DRIVER_DESC "OHCI generic platform driver" +#define OHCI_MAX_CLKS 4 +#define hcd_to_ohci_priv(h) ((struct ohci_platform_priv *)hcd_to_ohci(h)->priv) + +struct ohci_platform_priv { + struct clk *clks[OHCI_MAX_CLKS]; + struct reset_control *resets; +}; + +static int ohci_platform_power_on(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); + int clk, ret; + + for (clk = 0; clk < OHCI_MAX_CLKS && priv->clks[clk]; clk++) { + ret = clk_prepare_enable(priv->clks[clk]); + if (ret) + goto err_disable_clks; + } + + return 0; + +err_disable_clks: + while (--clk >= 0) + clk_disable_unprepare(priv->clks[clk]); + + return ret; +} + +static void ohci_platform_power_off(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); + int clk; + + for (clk = OHCI_MAX_CLKS - 1; clk >= 0; clk--) + clk_disable_unprepare(priv->clks[clk]); +} + +static struct hc_driver __read_mostly ohci_platform_hc_driver; + +static const struct ohci_driver_overrides platform_overrides __initconst = { + .product_desc = "Generic Platform OHCI controller", + .extra_priv_size = sizeof(struct ohci_platform_priv), +}; + +static struct usb_ohci_pdata ohci_platform_defaults = { + .power_on = ohci_platform_power_on, + .power_suspend = ohci_platform_power_off, + .power_off = ohci_platform_power_off, +}; + +static int ohci_platform_probe(struct platform_device *dev) +{ + struct usb_hcd *hcd; + struct resource *res_mem; + struct usb_ohci_pdata *pdata = dev_get_platdata(&dev->dev); + struct ohci_platform_priv *priv; + struct ohci_hcd *ohci; + int err, irq, clk = 0; + + if (usb_disabled()) + return -ENODEV; + + /* + * Use reasonable defaults so platforms don't have to provide these + * with DT probing on ARM. + */ + if (!pdata) + pdata = &ohci_platform_defaults; + + err = dma_coerce_mask_and_coherent(&dev->dev, DMA_BIT_MASK(32)); + if (err) + return err; + + irq = platform_get_irq(dev, 0); + if (irq < 0) + return irq; + + hcd = usb_create_hcd(&ohci_platform_hc_driver, &dev->dev, + dev_name(&dev->dev)); + if (!hcd) + return -ENOMEM; + + platform_set_drvdata(dev, hcd); + dev->dev.platform_data = pdata; + priv = hcd_to_ohci_priv(hcd); + ohci = hcd_to_ohci(hcd); + + if (pdata == &ohci_platform_defaults && dev->dev.of_node) { + if (of_property_read_bool(dev->dev.of_node, "big-endian-regs")) + ohci->flags |= OHCI_QUIRK_BE_MMIO; + + if (of_property_read_bool(dev->dev.of_node, "big-endian-desc")) + ohci->flags |= OHCI_QUIRK_BE_DESC; + + if (of_property_read_bool(dev->dev.of_node, "big-endian")) + ohci->flags |= OHCI_QUIRK_BE_MMIO | OHCI_QUIRK_BE_DESC; + + if (of_property_read_bool(dev->dev.of_node, "no-big-frame-no")) + ohci->flags |= OHCI_QUIRK_FRAME_NO; + + if (of_property_read_bool(dev->dev.of_node, + "remote-wakeup-connected")) + ohci->hc_control = OHCI_CTRL_RWC; + + of_property_read_u32(dev->dev.of_node, "num-ports", + &ohci->num_ports); + + for (clk = 0; clk < OHCI_MAX_CLKS; clk++) { + priv->clks[clk] = of_clk_get(dev->dev.of_node, clk); + if (IS_ERR(priv->clks[clk])) { + err = PTR_ERR(priv->clks[clk]); + if (err == -EPROBE_DEFER) + goto err_put_clks; + priv->clks[clk] = NULL; + break; + } + } + + priv->resets = devm_reset_control_array_get_optional_shared( + &dev->dev); + if (IS_ERR(priv->resets)) { + err = PTR_ERR(priv->resets); + goto err_put_clks; + } + + err = reset_control_deassert(priv->resets); + if (err) + goto err_put_clks; + } + + 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; + if (pdata->num_ports) + ohci->num_ports = pdata->num_ports; + +#ifndef CONFIG_USB_OHCI_BIG_ENDIAN_MMIO + if (ohci->flags & OHCI_QUIRK_BE_MMIO) { + dev_err(&dev->dev, + "Error: CONFIG_USB_OHCI_BIG_ENDIAN_MMIO not set\n"); + err = -EINVAL; + goto err_reset; + } +#endif +#ifndef CONFIG_USB_OHCI_BIG_ENDIAN_DESC + if (ohci->flags & OHCI_QUIRK_BE_DESC) { + dev_err(&dev->dev, + "Error: CONFIG_USB_OHCI_BIG_ENDIAN_DESC not set\n"); + err = -EINVAL; + goto err_reset; + } +#endif + + pm_runtime_set_active(&dev->dev); + pm_runtime_enable(&dev->dev); + if (pdata->power_on) { + err = pdata->power_on(dev); + if (err < 0) + goto err_reset; + } + + hcd->regs = devm_platform_get_and_ioremap_resource(dev, 0, &res_mem); + if (IS_ERR(hcd->regs)) { + err = PTR_ERR(hcd->regs); + goto err_power; + } + hcd->rsrc_start = res_mem->start; + hcd->rsrc_len = resource_size(res_mem); + + hcd->tpl_support = of_usb_host_tpl_support(dev->dev.of_node); + + err = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (err) + goto err_power; + + device_wakeup_enable(hcd->self.controller); + + platform_set_drvdata(dev, hcd); + + return err; + +err_power: + if (pdata->power_off) + pdata->power_off(dev); +err_reset: + pm_runtime_disable(&dev->dev); + reset_control_assert(priv->resets); +err_put_clks: + while (--clk >= 0) + clk_put(priv->clks[clk]); + + if (pdata == &ohci_platform_defaults) + dev->dev.platform_data = NULL; + + usb_put_hcd(hcd); + + return err; +} + +static void ohci_platform_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct usb_ohci_pdata *pdata = dev_get_platdata(&dev->dev); + struct ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); + int clk; + + pm_runtime_get_sync(&dev->dev); + usb_remove_hcd(hcd); + + if (pdata->power_off) + pdata->power_off(dev); + + reset_control_assert(priv->resets); + + for (clk = 0; clk < OHCI_MAX_CLKS && priv->clks[clk]; clk++) + clk_put(priv->clks[clk]); + + usb_put_hcd(hcd); + + pm_runtime_put_sync(&dev->dev); + pm_runtime_disable(&dev->dev); + + if (pdata == &ohci_platform_defaults) + dev->dev.platform_data = NULL; +} + +#ifdef CONFIG_PM_SLEEP +static int ohci_platform_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct usb_ohci_pdata *pdata = dev->platform_data; + struct platform_device *pdev = to_platform_device(dev); + struct ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); + bool do_wakeup = device_may_wakeup(dev); + int ret; + + ret = ohci_suspend(hcd, do_wakeup); + if (ret) + return ret; + + if (pdata->power_suspend) + pdata->power_suspend(pdev); + + ret = reset_control_assert(priv->resets); + if (ret) { + if (pdata->power_on) + pdata->power_on(pdev); + + ohci_resume(hcd, false); + } + + return ret; +} + +static int ohci_platform_resume_common(struct device *dev, bool hibernated) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct usb_ohci_pdata *pdata = dev_get_platdata(dev); + struct platform_device *pdev = to_platform_device(dev); + struct ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); + int err; + + err = reset_control_deassert(priv->resets); + if (err) + return err; + + if (pdata->power_on) { + err = pdata->power_on(pdev); + if (err < 0) { + reset_control_assert(priv->resets); + return err; + } + } + + ohci_resume(hcd, hibernated); + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} + +static int ohci_platform_resume(struct device *dev) +{ + return ohci_platform_resume_common(dev, false); +} + +static int ohci_platform_restore(struct device *dev) +{ + return ohci_platform_resume_common(dev, true); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct of_device_id ohci_platform_ids[] = { + { .compatible = "generic-ohci", }, + { .compatible = "cavium,octeon-6335-ohci", }, + { .compatible = "ti,ohci-omap3", }, + { } +}; +MODULE_DEVICE_TABLE(of, ohci_platform_ids); + +static const struct platform_device_id ohci_platform_table[] = { + { "ohci-platform", 0 }, + { } +}; +MODULE_DEVICE_TABLE(platform, ohci_platform_table); + +#ifdef CONFIG_PM_SLEEP +static const struct dev_pm_ops ohci_platform_pm_ops = { + .suspend = ohci_platform_suspend, + .resume = ohci_platform_resume, + .freeze = ohci_platform_suspend, + .thaw = ohci_platform_resume, + .poweroff = ohci_platform_suspend, + .restore = ohci_platform_restore, +}; +#endif + +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 = { + .name = "ohci-platform", +#ifdef CONFIG_PM_SLEEP + .pm = &ohci_platform_pm_ops, +#endif + .of_match_table = ohci_platform_ids, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + } +}; + +static int __init ohci_platform_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ohci_init_driver(&ohci_platform_hc_driver, &platform_overrides); + return platform_driver_register(&ohci_platform_driver); +} +module_init(ohci_platform_init); + +static void __exit ohci_platform_cleanup(void) +{ + platform_driver_unregister(&ohci_platform_driver); +} +module_exit(ohci_platform_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Hauke Mehrtens"); +MODULE_AUTHOR("Alan Stern"); +MODULE_LICENSE("GPL"); +MODULE_SOFTDEP("pre: ehci_platform"); diff --git a/drivers/usb/host/ohci-ppc-of.c b/drivers/usb/host/ohci-ppc-of.c new file mode 100644 index 000000000..acd0a0e39 --- /dev/null +++ b/drivers/usb/host/ohci-ppc-of.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * 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 +#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_DMA | 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); + + hcd->regs = devm_ioremap_resource(&op->dev, &res); + if (IS_ERR(hcd->regs)) { + rv = PTR_ERR(hcd->regs); + goto err_rmr; + } + + irq = irq_of_parse_and_map(dn, 0); + if (!irq) { + dev_err(&op->dev, "%s: irq_of_parse_and_map failed\n", + __FILE__); + rv = -EBUSY; + goto err_rmr; + } + + 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) { + device_wakeup_enable(hcd->self.controller); + 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__); + of_node_put(np); + } + + irq_dispose_mapping(irq); +err_rmr: + usb_put_hcd(hcd); + + return rv; +} + +static void ohci_hcd_ppc_of_remove(struct platform_device *op) +{ + struct usb_hcd *hcd = platform_get_drvdata(op); + + dev_dbg(&op->dev, "stopping PPC-OF USB Controller\n"); + + usb_remove_hcd(hcd); + + irq_dispose_mapping(hcd->irq); + + usb_put_hcd(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-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 = usb_hcd_platform_shutdown, + .driver = { + .name = "ppc-of-ohci", + .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 000000000..4f5af929c --- /dev/null +++ b/drivers/usb/host/ohci-ps3.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PS3 OHCI Host Controller driver + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + */ + +#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_DMA | 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; + + 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; + } + + dummy_mask = DMA_BIT_MASK(32); + dev->core.dma_mask = &dummy_mask; + dma_set_coherent_mask(&dev->core, dummy_mask); + + 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; + } + + device_wakeup_enable(hcd->self.controller); + 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 void 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); +} + +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 000000000..45d026e85 --- /dev/null +++ b/drivers/usb/host/ohci-pxa27x.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "ohci.h" + +#define DRIVER_DESC "OHCI PXA27x/PXA3x driver" + +/* + * 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 + +static struct hc_driver __read_mostly ohci_pxa27x_hc_driver; + +struct pxa27x_ohci { + struct clk *clk; + void __iomem *mmio_base; + struct regulator *vbus[3]; + bool vbus_enabled[3]; +}; + +#define to_pxa27x_ohci(hcd) (struct pxa27x_ohci *)(hcd_to_ohci(hcd)->priv) + +/* + 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 *pxa_ohci, int mode) +{ + uint32_t uhcrhda = __raw_readl(pxa_ohci->mmio_base + UHCRHDA); + uint32_t uhcrhdb = __raw_readl(pxa_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, pxa_ohci->mmio_base + UHCRHDA); + __raw_writel(uhcrhdb, pxa_ohci->mmio_base + UHCRHDB); + return 0; +} + +static int pxa27x_ohci_set_vbus_power(struct pxa27x_ohci *pxa_ohci, + unsigned int port, bool enable) +{ + struct regulator *vbus = pxa_ohci->vbus[port]; + int ret = 0; + + if (IS_ERR_OR_NULL(vbus)) + return 0; + + if (enable && !pxa_ohci->vbus_enabled[port]) + ret = regulator_enable(vbus); + else if (!enable && pxa_ohci->vbus_enabled[port]) + ret = regulator_disable(vbus); + + if (ret < 0) + return ret; + + pxa_ohci->vbus_enabled[port] = enable; + + return 0; +} + +static int pxa27x_ohci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct pxa27x_ohci *pxa_ohci = to_pxa27x_ohci(hcd); + int ret; + + switch (typeReq) { + case SetPortFeature: + case ClearPortFeature: + if (!wIndex || wIndex > 3) + return -EPIPE; + + if (wValue != USB_PORT_FEAT_POWER) + break; + + ret = pxa27x_ohci_set_vbus_power(pxa_ohci, wIndex - 1, + typeReq == SetPortFeature); + if (ret) + return ret; + break; + } + + return ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); +} +/*-------------------------------------------------------------------------*/ + +static inline void pxa27x_setup_hc(struct pxa27x_ohci *pxa_ohci, + struct pxaohci_platform_data *inf) +{ + uint32_t uhchr = __raw_readl(pxa_ohci->mmio_base + UHCHR); + uint32_t uhcrhda = __raw_readl(pxa_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, pxa_ohci->mmio_base + UHCHR); + __raw_writel(uhcrhda, pxa_ohci->mmio_base + UHCRHDA); +} + +static inline void pxa27x_reset_hc(struct pxa27x_ohci *pxa_ohci) +{ + uint32_t uhchr = __raw_readl(pxa_ohci->mmio_base + UHCHR); + + __raw_writel(uhchr | UHCHR_FHR, pxa_ohci->mmio_base + UHCHR); + udelay(11); + __raw_writel(uhchr & ~UHCHR_FHR, pxa_ohci->mmio_base + UHCHR); +} + +static int pxa27x_start_hc(struct pxa27x_ohci *pxa_ohci, struct device *dev) +{ + int retval; + struct pxaohci_platform_data *inf; + uint32_t uhchr; + + inf = dev_get_platdata(dev); + + retval = clk_prepare_enable(pxa_ohci->clk); + if (retval) + return retval; + + pxa27x_reset_hc(pxa_ohci); + + uhchr = __raw_readl(pxa_ohci->mmio_base + UHCHR) | UHCHR_FSBIR; + __raw_writel(uhchr, pxa_ohci->mmio_base + UHCHR); + + while (__raw_readl(pxa_ohci->mmio_base + UHCHR) & UHCHR_FSBIR) + cpu_relax(); + + pxa27x_setup_hc(pxa_ohci, inf); + + if (inf->init) + retval = inf->init(dev); + + if (retval < 0) { + clk_disable_unprepare(pxa_ohci->clk); + return retval; + } + + uhchr = __raw_readl(pxa_ohci->mmio_base + UHCHR) & ~UHCHR_SSE; + __raw_writel(uhchr, pxa_ohci->mmio_base + UHCHR); + __raw_writel(UHCHIE_UPRIE | UHCHIE_RWIE, pxa_ohci->mmio_base + UHCHIE); + + /* Clear any OTG Pin Hold */ + pxa27x_clear_otgph(); + return 0; +} + +static void pxa27x_stop_hc(struct pxa27x_ohci *pxa_ohci, struct device *dev) +{ + struct pxaohci_platform_data *inf; + uint32_t uhccoms; + + inf = dev_get_platdata(dev); + + if (inf->exit) + inf->exit(dev); + + pxa27x_reset_hc(pxa_ohci); + + /* Host Controller Reset */ + uhccoms = __raw_readl(pxa_ohci->mmio_base + UHCCOMS) | 0x01; + __raw_writel(uhccoms, pxa_ohci->mmio_base + UHCCOMS); + udelay(10); + + clk_disable_unprepare(pxa_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; + int ret; + + 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. + */ + ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + if (of_property_read_bool(np, "marvell,enable-port1")) + pdata->flags |= ENABLE_PORT1; + if (of_property_read_bool(np, "marvell,enable-port2")) + pdata->flags |= ENABLE_PORT2; + if (of_property_read_bool(np, "marvell,enable-port3")) + pdata->flags |= ENABLE_PORT3; + if (of_property_read_bool(np, "marvell,port-sense-low")) + pdata->flags |= POWER_SENSE_LOW; + if (of_property_read_bool(np, "marvell,power-control-low")) + pdata->flags |= POWER_CONTROL_LOW; + if (of_property_read_bool(np, "marvell,no-oc-protection")) + pdata->flags |= NO_OC_PROTECTION; + if (of_property_read_bool(np, "marvell,oc-mode-perport")) + 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 */ + + +/** + * ohci_hcd_pxa27x_probe - initialize pxa27x-based HCDs + * @pdev: USB Host controller to probe + * + * Context: task context, might sleep + * + * 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_pxa27x_probe(struct platform_device *pdev) +{ + int retval, irq; + struct usb_hcd *hcd; + struct pxaohci_platform_data *inf; + struct pxa27x_ohci *pxa_ohci; + struct ohci_hcd *ohci; + struct resource *r; + struct clk *usb_clk; + unsigned int i; + + retval = ohci_pxa_of_init(pdev); + if (retval) + return retval; + + inf = dev_get_platdata(&pdev->dev); + + if (!inf) + return -ENODEV; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + pr_err("no resource of IORESOURCE_IRQ"); + return irq; + } + + usb_clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(usb_clk)) + return PTR_ERR(usb_clk); + + hcd = usb_create_hcd(&ohci_pxa27x_hc_driver, &pdev->dev, "pxa27x"); + if (!hcd) + return -ENOMEM; + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &r); + if (IS_ERR(hcd->regs)) { + retval = PTR_ERR(hcd->regs); + goto err; + } + hcd->rsrc_start = r->start; + hcd->rsrc_len = resource_size(r); + + /* initialize "struct pxa27x_ohci" */ + pxa_ohci = to_pxa27x_ohci(hcd); + pxa_ohci->clk = usb_clk; + pxa_ohci->mmio_base = (void __iomem *)hcd->regs; + + for (i = 0; i < 3; ++i) { + char name[6]; + + if (!(inf->flags & (ENABLE_PORT1 << i))) + continue; + + sprintf(name, "vbus%u", i + 1); + pxa_ohci->vbus[i] = devm_regulator_get(&pdev->dev, name); + } + + retval = pxa27x_start_hc(pxa_ohci, &pdev->dev); + if (retval < 0) { + pr_debug("pxa27x_start_hc failed"); + goto err; + } + + /* Select Power Management Mode */ + pxa27x_ohci_select_pmm(pxa_ohci, inf->port_mode); + + if (inf->power_budget) + hcd->power_budget = inf->power_budget; + + /* The value of NDP in roothub_a is incorrect on this hardware */ + ohci = hcd_to_ohci(hcd); + ohci->num_ports = 3; + + retval = usb_add_hcd(hcd, irq, 0); + if (retval == 0) { + device_wakeup_enable(hcd->self.controller); + return retval; + } + + pxa27x_stop_hc(pxa_ohci, &pdev->dev); + err: + usb_put_hcd(hcd); + return retval; +} + + +/* may be called without controller electrically present */ +/* may be called with controller, bus, and devices active */ + +/** + * ohci_hcd_pxa27x_remove - shutdown processing for pxa27x-based HCDs + * @pdev: USB Host Controller being removed + * + * Context: task context, might sleep + * + * Reverses the effect of ohci_hcd_pxa27x_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + */ +static void ohci_hcd_pxa27x_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct pxa27x_ohci *pxa_ohci = to_pxa27x_ohci(hcd); + unsigned int i; + + usb_remove_hcd(hcd); + pxa27x_stop_hc(pxa_ohci, &pdev->dev); + + for (i = 0; i < 3; ++i) + pxa27x_ohci_set_vbus_power(pxa_ohci, i, false); + + usb_put_hcd(hcd); +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM +static int ohci_hcd_pxa27x_drv_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct pxa27x_ohci *pxa_ohci = to_pxa27x_ohci(hcd); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + bool do_wakeup = device_may_wakeup(dev); + int ret; + + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + ret = ohci_suspend(hcd, do_wakeup); + if (ret) + return ret; + + pxa27x_stop_hc(pxa_ohci, dev); + return ret; +} + +static int ohci_hcd_pxa27x_drv_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct pxa27x_ohci *pxa_ohci = to_pxa27x_ohci(hcd); + struct pxaohci_platform_data *inf = dev_get_platdata(dev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int status; + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + status = pxa27x_start_hc(pxa_ohci, dev); + if (status < 0) + return status; + + /* Select Power Management Mode */ + pxa27x_ohci_select_pmm(pxa_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 + +static struct platform_driver ohci_hcd_pxa27x_driver = { + .probe = ohci_hcd_pxa27x_probe, + .remove = ohci_hcd_pxa27x_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "pxa27x-ohci", + .of_match_table = of_match_ptr(pxa_ohci_dt_ids), +#ifdef CONFIG_PM + .pm = &ohci_hcd_pxa27x_pm_ops, +#endif + }, +}; + +static const struct ohci_driver_overrides pxa27x_overrides __initconst = { + .extra_priv_size = sizeof(struct pxa27x_ohci), +}; + +static int __init ohci_pxa27x_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ohci_init_driver(&ohci_pxa27x_hc_driver, &pxa27x_overrides); + ohci_pxa27x_hc_driver.hub_control = pxa27x_ohci_hub_control; + + return platform_driver_register(&ohci_hcd_pxa27x_driver); +} +module_init(ohci_pxa27x_init); + +static void __exit ohci_pxa27x_cleanup(void) +{ + platform_driver_unregister(&ohci_hcd_pxa27x_driver); +} +module_exit(ohci_pxa27x_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa27x-ohci"); diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c new file mode 100644 index 000000000..3b445312b --- /dev/null +++ b/drivers/usb/host/ohci-q.c @@ -0,0 +1,1235 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * 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 device *dev = ohci_to_hcd(ohci)->self.controller; + 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(dev, 0); + } + break; + case PIPE_INTERRUPT: + ohci_to_hcd(ohci)->self.bandwidth_int_reqs--; + break; + } + + /* 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_dbg(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->ed_prev = NULL; + ed->ed_next = NULL; + ed->hwNextED = 0; + 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. + */ + + ed->state = ED_OPER; + 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_dbg(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); + + ed = ep->hcpriv; + if (!ed) { + 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; + struct device *dev = ohci_to_hcd(ohci)->self.controller; + 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; + int i, this_sg_len, n; + struct scatterlist *sg; + + /* 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); + + i = urb->num_mapped_sgs; + if (data_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), data_len); + } else { + sg = NULL; + if (data_len) + data = urb->transfer_dma; + else + data = 0; + this_sg_len = data_len; + } + + /* 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 */ + for (;;) { + n = min(this_sg_len, 4096); + + /* maybe avoid ED halt on final TD short read */ + if (n >= data_len || (i == 1 && n >= this_sg_len)) { + if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) + info |= TD_R; + } + td_fill(ohci, info, data, n, urb, cnt); + this_sg_len -= n; + data_len -= n; + data += n; + cnt++; + + if (this_sg_len <= 0) { + if (--i <= 0 || data_len <= 0) + break; + sg = sg_next(sg); + data = sg_dma_address(sg); + this_sg_len = min_t(int, sg_dma_len(sg), + data_len); + } + } + 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(dev, 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_dbg(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_dbg(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]); + } +} + +/* Add a TD to the done list */ +static void add_to_done_list(struct ohci_hcd *ohci, struct td *td) +{ + struct td *td2, *td_prev; + struct ed *ed; + + if (td->next_dl_td) + return; /* Already on the list */ + + /* Add all the TDs going back until we reach one that's on the list */ + ed = td->ed; + td2 = td_prev = td; + list_for_each_entry_continue_reverse(td2, &ed->td_list, td_list) { + if (td2->next_dl_td) + break; + td2->next_dl_td = td_prev; + td_prev = td2; + } + + if (ohci->dl_end) + ohci->dl_end->next_dl_td = td_prev; + else + ohci->dl_start = td_prev; + + /* + * Make td->next_dl_td point to td itself, to mark the fact + * that td is on the done list. + */ + ohci->dl_end = td->next_dl_td = td; + + /* Did we just add the latest pending TD? */ + td2 = ed->pending_td; + if (td2 && td2->next_dl_td) + ed->pending_td = NULL; +} + +/* Get the entries on the hardware done queue and put them on our list */ +static void update_done_list(struct ohci_hcd *ohci) +{ + u32 td_dma; + 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 + * add 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_dma = hc32_to_cpup (ohci, &td->hwNextTD); + add_to_done_list(ohci, td); + } +} + +/*-------------------------------------------------------------------------*/ + +/* there are some urbs/eds to unlink; called in_irq(), with HCD locked */ +static void finish_unlinks(struct ohci_hcd *ohci) +{ + unsigned tick = ohci_frame_no(ohci); + 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; + + /* 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) && + 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_first_entry(&ed->td_list, struct td, td_list); + + /* INTR_WDH may need to clean up first */ + head = hc32_to_cpu(ohci, ed->hwHeadP) & TD_MASK; + if (td->td_dma != head && + ohci->rh_state == OHCI_RH_RUNNING) + goto skip_ed; + + /* Don't mess up anything already on the done list */ + if (td->next_dl_td) + goto skip_ed; + } + + /* ED's now officially unlinked, hc doesn't see */ + ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_H); + ed->hwNextED = 0; + wmb(); + ed->hwINFO &= ~cpu_to_hc32(ohci, ED_SKIP | ED_DEQUEUE); + + /* 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. + */ + *last = ed->ed_next; + ed->ed_next = NULL; + 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, ED is now idle. + * Otherwise, if the HC is running, reschedule. + * If the HC isn't running, add ED back to the + * start of the list for later processing. + */ + if (list_empty(&ed->td_list)) { + ed->state = ED_IDLE; + list_del(&ed->in_use_list); + } else if (ohci->rh_state == OHCI_RH_RUNNING) { + ed_schedule(ohci, ed); + } else { + ed->ed_next = ohci->ed_rm_list; + ohci->ed_rm_list = ed; + /* Don't loop on the same ED */ + if (last == &ohci->ed_rm_list) + 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); + } + } +} + + + +/*-------------------------------------------------------------------------*/ + +/* Take back a TD from the host controller */ +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. + */ +static void process_done_list(struct ohci_hcd *ohci) +{ + struct td *td; + + while (ohci->dl_start) { + td = ohci->dl_start; + if (td == ohci->dl_end) + ohci->dl_start = ohci->dl_end = NULL; + else + ohci->dl_start = td->next_dl_td; + + takeback_td(ohci, td); + } +} + +/* + * TD takeback and URB giveback must be single-threaded. + * This routine takes care of it all. + */ +static void ohci_work(struct ohci_hcd *ohci) +{ + if (ohci->working) { + ohci->restart_work = 1; + return; + } + ohci->working = 1; + + restart: + process_done_list(ohci); + if (ohci->ed_rm_list) + finish_unlinks(ohci); + + if (ohci->restart_work) { + ohci->restart_work = 0; + goto restart; + } + ohci->working = 0; +} diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c new file mode 100644 index 000000000..e623e24d3 --- /dev/null +++ b/drivers/usb/host/ohci-s3c2410.c @@ -0,0 +1,492 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * 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 +#include +#include +#include +#include +#include + +#include "ohci.h" + + +#define valid_port(idx) ((idx) == 1 || (idx) == 2) + +/* clock device associated with the hcd */ + + +#define DRIVER_DESC "OHCI S3C2410 driver" + +static struct clk *clk; +static struct clk *usb_clk; + +static struct hc_driver __read_mostly ohci_s3c2410_hc_driver; + +/* 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 dev_get_platdata(hcd->self.controller); +} + +static void s3c2410_start_hc(struct platform_device *dev, struct usb_hcd *hcd) +{ + struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev); + + dev_dbg(&dev->dev, "s3c2410_start_hc:\n"); + + clk_prepare_enable(usb_clk); + mdelay(2); /* let the bus clock stabilise */ + + clk_prepare_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_get_platdata(&dev->dev); + + 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_unprepare(clk); + clk_disable_unprepare(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( + HUB_CHAR_INDV_PORT_LPSM); + + if (info->enable_oc) { + desc->wHubCharacteristics &= ~cpu_to_le16( + HUB_CHAR_OCPM); + desc->wHubCharacteristics |= cpu_to_le16( + HUB_CHAR_INDV_PORT_OCPM); + } + + 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; + unsigned long flags; + int portno; + + if (info == NULL) + return; + + port = &info->port[0]; + + 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 */ + +/* + * ohci_hcd_s3c2410_remove - shutdown processing for HCD + * @dev: USB Host Controller being removed + * + * Context: task context, might sleep + * + * Reverses the effect of ohci_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 +ohci_hcd_s3c2410_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + + usb_remove_hcd(hcd); + s3c2410_stop_hc(dev); + usb_put_hcd(hcd); +} + +/* + * ohci_hcd_s3c2410_probe - initialize S3C2410-based HCDs + * @dev: USB Host Controller to be probed + * + * Context: task context, might sleep + * + * 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_s3c2410_probe(struct platform_device *dev) +{ + struct usb_hcd *hcd = NULL; + struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev); + int retval, irq; + + s3c2410_usb_set_power(info, 1, 1); + s3c2410_usb_set_power(info, 2, 1); + + hcd = usb_create_hcd(&ohci_s3c2410_hc_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; + } + + irq = platform_get_irq(dev, 0); + if (irq < 0) { + retval = irq; + goto err_put; + } + + s3c2410_start_hc(dev, hcd); + + retval = usb_add_hcd(hcd, irq, 0); + if (retval != 0) + goto err_ioremap; + + device_wakeup_enable(hcd->self.controller); + return 0; + + err_ioremap: + s3c2410_stop_hc(dev); + + err_put: + usb_put_hcd(hcd); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM +static int ohci_hcd_s3c2410_drv_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct platform_device *pdev = to_platform_device(dev); + bool do_wakeup = device_may_wakeup(dev); + int rc = 0; + + rc = ohci_suspend(hcd, do_wakeup); + if (rc) + return rc; + + s3c2410_stop_hc(pdev); + + 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_probe, + .remove = ohci_hcd_s3c2410_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "s3c2410-ohci", + .pm = &ohci_hcd_s3c2410_pm_ops, + }, +}; + +static int __init ohci_s3c2410_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ohci_init_driver(&ohci_s3c2410_hc_driver, NULL); + + /* + * The Samsung HW has some unusual quirks, which require + * Sumsung-specific workarounds. We override certain hc_driver + * functions here to achieve that. We explicitly do not enhance + * ohci_driver_overrides to allow this more easily, since this + * is an unusual case, and we don't want to encourage others to + * override these functions by making it too easy. + */ + + ohci_s3c2410_hc_driver.hub_status_data = ohci_s3c2410_hub_status_data; + ohci_s3c2410_hc_driver.hub_control = ohci_s3c2410_hub_control; + + return platform_driver_register(&ohci_hcd_s3c2410_driver); +} +module_init(ohci_s3c2410_init); + +static void __exit ohci_s3c2410_cleanup(void) +{ + platform_driver_unregister(&ohci_hcd_s3c2410_driver); +} +module_exit(ohci_s3c2410_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +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 000000000..aca0338a2 --- /dev/null +++ b/drivers/usb/host/ohci-sa1111.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * 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 + +#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 = readl_relaxed(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_DMA | 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_assabet()) + usb_rst = USB_RESET_PWRSENSELOW | USB_RESET_PWRCTRLLOW; + + /* + * Configure the power sense and control lines. Place the USB + * host controller in reset. + */ + writel_relaxed(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); + writel_relaxed(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 = readl_relaxed(dev->mapbase + USB_RESET); + writel_relaxed(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, irq; + + if (usb_disabled()) + return -ENODEV; + + /* + * We don't call dma_set_mask_and_coherent() here because the + * DMA mask has already been appropraitely setup by the core + * SA-1111 bus code (which includes bug workarounds.) + */ + + 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); + + irq = sa1111_get_irq(dev, 1); + if (irq <= 0) { + ret = irq ? : -ENXIO; + goto err1; + } + + /* + * According to the "Intel StrongARM SA-1111 Microprocessor Companion + * Chip Specification Update" (June 2000), erratum #7, there is a + * significant bug in the SA1111 SDRAM shared memory controller. If + * an access to a region of memory above 1MB relative to the bank base, + * it is important that address bit 10 _NOT_ be asserted. Depending + * on the configuration of the RAM, bit 10 may correspond to one + * of several different (processor-relative) address bits. + * + * Section 4.6 of the "Intel StrongARM SA-1111 Development Module + * User's Guide" mentions that jumpers R51 and R52 control the + * target of SA-1111 DMA (either SDRAM bank 0 on Assabet, or + * SDRAM bank 1 on Neponset). The default configuration selects + * Assabet, so any address in bank 1 is necessarily invalid. + * + * As a workaround, use a bounce buffer in addressable memory + * as local_mem, relying on ZONE_DMA to provide an area that + * fits within the above constraints. + * + * SZ_64K is an estimate for what size this might need. + */ + ret = usb_hcd_setup_local_mem(hcd, 0, 0, SZ_64K); + if (ret) + goto err1; + + 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, irq, 0); + if (ret == 0) { + device_wakeup_enable(hcd->self.controller); + 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 void 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); +} + +static void ohci_hcd_sa1111_shutdown(struct device *_dev) +{ + struct sa1111_dev *dev = to_sa1111_device(_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, + .shutdown = ohci_hcd_sa1111_shutdown, + }, + .devid = SA1111_DEVID_USB, + .probe = ohci_hcd_sa1111_probe, + .remove = ohci_hcd_sa1111_remove, +}; diff --git a/drivers/usb/host/ohci-sm501.c b/drivers/usb/host/ohci-sm501.c new file mode 100644 index 000000000..843a53787 --- /dev/null +++ b/drivers/usb/host/ohci-sm501.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * 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, + + /* + * 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; + } + + /* 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 err1; + } + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + 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, 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)); + + /* 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 genalloc so that usb allocations with + * gen_pool_dma_alloc() allocate from this local memory. The dma_handle + * returned by gen_pool_dma_alloc() will be an offset starting from 0 + * for the first local memory byte. + * + * So as long as data is allocated using gen_pool_dma_alloc() 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. A non-null hcd->localmem_pool initialized by + * the call to usb_hcd_setup_local_mem() below does just that. + */ + + retval = usb_hcd_setup_local_mem(hcd, mem->start, + mem->start - mem->parent->start, + resource_size(mem)); + if (retval < 0) + goto err5; + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval) + goto err5; + device_wakeup_enable(hcd->self.controller); + + /* 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); +err1: + release_mem_region(mem->start, resource_size(mem)); +err0: + return retval; +} + +static void ohci_hcd_sm501_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct resource *mem; + + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + 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); +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM +static int ohci_sm501_suspend(struct platform_device *pdev, pm_message_t msg) +{ + struct device *dev = &pdev->dev; + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + bool do_wakeup = device_may_wakeup(dev); + int ret; + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + ret = ohci_suspend(hcd, do_wakeup); + if (ret) + return ret; + + sm501_unit_power(dev->parent, SM501_GATE_USB_HOST, 0); + return ret; +} + +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 = { + .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 000000000..6843d7cb3 --- /dev/null +++ b/drivers/usb/host/ohci-spear.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* OHCI HCD (Host Controller Driver) for USB. +* +* Copyright (C) 2010 ST Microelectronics. +* Deepak Sikri +* +* Based on various ohci-*.c drivers +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ohci.h" + +#define DRIVER_DESC "OHCI SPEAr driver" + +struct spear_ohci { + struct clk *clk; +}; + +#define to_spear_ohci(hcd) (struct spear_ohci *)(hcd_to_ohci(hcd)->priv) + +static struct hc_driver __read_mostly ohci_spear_hc_driver; + +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 *sohci_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. + */ + retval = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (retval) + goto fail; + + 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; + } + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(hcd->regs)) { + retval = PTR_ERR(hcd->regs); + goto err_put_hcd; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + sohci_p = to_spear_ohci(hcd); + sohci_p->clk = usbh_clk; + + clk_prepare_enable(sohci_p->clk); + + retval = usb_add_hcd(hcd, irq, 0); + if (retval == 0) { + device_wakeup_enable(hcd->self.controller); + return retval; + } + + clk_disable_unprepare(sohci_p->clk); +err_put_hcd: + usb_put_hcd(hcd); +fail: + dev_err(&pdev->dev, "init fail, %d\n", retval); + + return retval; +} + +static void spear_ohci_hcd_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct spear_ohci *sohci_p = to_spear_ohci(hcd); + + usb_remove_hcd(hcd); + clk_disable_unprepare(sohci_p->clk); + + usb_put_hcd(hcd); +} + +#if defined(CONFIG_PM) +static int spear_ohci_hcd_drv_suspend(struct platform_device *pdev, + pm_message_t message) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct spear_ohci *sohci_p = to_spear_ohci(hcd); + bool do_wakeup = device_may_wakeup(&pdev->dev); + int ret; + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + ret = ohci_suspend(hcd, do_wakeup); + if (ret) + return ret; + + clk_disable_unprepare(sohci_p->clk); + + return ret; +} + +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 *sohci_p = to_spear_ohci(hcd); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + clk_prepare_enable(sohci_p->clk); + ohci_resume(hcd, false); + return 0; +} +#endif + +static const struct of_device_id spear_ohci_id_table[] = { + { .compatible = "st,spear600-ohci", }, + { }, +}; +MODULE_DEVICE_TABLE(of, spear_ohci_id_table); + +/* 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 = { + .name = "spear-ohci", + .of_match_table = spear_ohci_id_table, + }, +}; + +static const struct ohci_driver_overrides spear_overrides __initconst = { + .extra_priv_size = sizeof(struct spear_ohci), +}; +static int __init ohci_spear_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ohci_init_driver(&ohci_spear_hc_driver, &spear_overrides); + return platform_driver_register(&spear_ohci_hcd_driver); +} +module_init(ohci_spear_init); + +static void __exit ohci_spear_cleanup(void) +{ + platform_driver_unregister(&spear_ohci_hcd_driver); +} +module_exit(ohci_spear_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Deepak Sikri"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:spear-ohci"); diff --git a/drivers/usb/host/ohci-st.c b/drivers/usb/host/ohci-st.c new file mode 100644 index 000000000..d1656fce5 --- /dev/null +++ b/drivers/usb/host/ohci-st.c @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ST OHCI driver + * + * Copyright (C) 2014 STMicroelectronics – All Rights Reserved + * + * Author: Peter Griffin + * + * Derived from ohci-platform.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ohci.h" + +#define USB_MAX_CLKS 3 + +struct st_ohci_platform_priv { + struct clk *clks[USB_MAX_CLKS]; + struct clk *clk48; + struct reset_control *rst; + struct reset_control *pwr; + struct phy *phy; +}; + +#define DRIVER_DESC "OHCI STMicroelectronics driver" + +#define hcd_to_ohci_priv(h) \ + ((struct st_ohci_platform_priv *)hcd_to_ohci(h)->priv) + +static int st_ohci_platform_power_on(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct st_ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); + int clk, ret; + + ret = reset_control_deassert(priv->pwr); + if (ret) + return ret; + + ret = reset_control_deassert(priv->rst); + if (ret) + goto err_assert_power; + + /* some SoCs don't have a dedicated 48Mhz clock, but those that do + need the rate to be explicitly set */ + if (priv->clk48) { + ret = clk_set_rate(priv->clk48, 48000000); + if (ret) + goto err_assert_reset; + } + + for (clk = 0; clk < USB_MAX_CLKS && priv->clks[clk]; clk++) { + ret = clk_prepare_enable(priv->clks[clk]); + if (ret) + goto err_disable_clks; + } + + ret = phy_init(priv->phy); + if (ret) + goto err_disable_clks; + + ret = phy_power_on(priv->phy); + if (ret) + goto err_exit_phy; + + return 0; + +err_exit_phy: + phy_exit(priv->phy); +err_disable_clks: + while (--clk >= 0) + clk_disable_unprepare(priv->clks[clk]); +err_assert_reset: + reset_control_assert(priv->rst); +err_assert_power: + reset_control_assert(priv->pwr); + + return ret; +} + +static void st_ohci_platform_power_off(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct st_ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); + + int clk; + + reset_control_assert(priv->pwr); + + reset_control_assert(priv->rst); + + phy_power_off(priv->phy); + + phy_exit(priv->phy); + + for (clk = USB_MAX_CLKS - 1; clk >= 0; clk--) + if (priv->clks[clk]) + clk_disable_unprepare(priv->clks[clk]); +} + +static struct hc_driver __read_mostly ohci_platform_hc_driver; + +static const struct ohci_driver_overrides platform_overrides __initconst = { + .product_desc = "ST OHCI controller", + .extra_priv_size = sizeof(struct st_ohci_platform_priv), +}; + +static struct usb_ohci_pdata ohci_platform_defaults = { + .power_on = st_ohci_platform_power_on, + .power_suspend = st_ohci_platform_power_off, + .power_off = st_ohci_platform_power_off, +}; + +static int st_ohci_platform_probe(struct platform_device *dev) +{ + struct usb_hcd *hcd; + struct resource *res_mem; + struct usb_ohci_pdata *pdata = &ohci_platform_defaults; + struct st_ohci_platform_priv *priv; + int err, irq, clk = 0; + + if (usb_disabled()) + return -ENODEV; + + irq = platform_get_irq(dev, 0); + if (irq < 0) + return irq; + + hcd = usb_create_hcd(&ohci_platform_hc_driver, &dev->dev, + dev_name(&dev->dev)); + if (!hcd) + return -ENOMEM; + + platform_set_drvdata(dev, hcd); + dev->dev.platform_data = pdata; + priv = hcd_to_ohci_priv(hcd); + + priv->phy = devm_phy_get(&dev->dev, "usb"); + if (IS_ERR(priv->phy)) { + err = PTR_ERR(priv->phy); + goto err_put_hcd; + } + + for (clk = 0; clk < USB_MAX_CLKS; clk++) { + priv->clks[clk] = of_clk_get(dev->dev.of_node, clk); + if (IS_ERR(priv->clks[clk])) { + err = PTR_ERR(priv->clks[clk]); + if (err == -EPROBE_DEFER) + goto err_put_clks; + priv->clks[clk] = NULL; + break; + } + } + + /* some SoCs don't have a dedicated 48Mhz clock, but those that + do need the rate to be explicitly set */ + priv->clk48 = devm_clk_get(&dev->dev, "clk48"); + if (IS_ERR(priv->clk48)) { + dev_info(&dev->dev, "48MHz clk not found\n"); + priv->clk48 = NULL; + } + + priv->pwr = + devm_reset_control_get_optional_shared(&dev->dev, "power"); + if (IS_ERR(priv->pwr)) { + err = PTR_ERR(priv->pwr); + goto err_put_clks; + } + + priv->rst = + devm_reset_control_get_optional_shared(&dev->dev, "softreset"); + if (IS_ERR(priv->rst)) { + err = PTR_ERR(priv->rst); + goto err_put_clks; + } + + if (pdata->power_on) { + err = pdata->power_on(dev); + if (err < 0) + goto err_power; + } + + hcd->regs = devm_platform_get_and_ioremap_resource(dev, 0, &res_mem); + if (IS_ERR(hcd->regs)) { + err = PTR_ERR(hcd->regs); + goto err_power; + } + hcd->rsrc_start = res_mem->start; + hcd->rsrc_len = resource_size(res_mem); + + err = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (err) + goto err_power; + + device_wakeup_enable(hcd->self.controller); + + platform_set_drvdata(dev, hcd); + + return err; + +err_power: + if (pdata->power_off) + pdata->power_off(dev); + +err_put_clks: + while (--clk >= 0) + clk_put(priv->clks[clk]); +err_put_hcd: + if (pdata == &ohci_platform_defaults) + dev->dev.platform_data = NULL; + + usb_put_hcd(hcd); + + return err; +} + +static void st_ohci_platform_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct usb_ohci_pdata *pdata = dev_get_platdata(&dev->dev); + struct st_ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); + int clk; + + usb_remove_hcd(hcd); + + if (pdata->power_off) + pdata->power_off(dev); + + + for (clk = 0; clk < USB_MAX_CLKS && priv->clks[clk]; clk++) + clk_put(priv->clks[clk]); + + usb_put_hcd(hcd); + + if (pdata == &ohci_platform_defaults) + dev->dev.platform_data = NULL; +} + +#ifdef CONFIG_PM_SLEEP + +static int st_ohci_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct usb_ohci_pdata *pdata = dev->platform_data; + struct platform_device *pdev = to_platform_device(dev); + bool do_wakeup = device_may_wakeup(dev); + int ret; + + ret = ohci_suspend(hcd, do_wakeup); + if (ret) + return ret; + + if (pdata->power_suspend) + pdata->power_suspend(pdev); + + return ret; +} + +static int st_ohci_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct usb_ohci_pdata *pdata = dev_get_platdata(dev); + struct platform_device *pdev = to_platform_device(dev); + int err; + + if (pdata->power_on) { + err = pdata->power_on(pdev); + if (err < 0) + return err; + } + + ohci_resume(hcd, false); + return 0; +} + +static SIMPLE_DEV_PM_OPS(st_ohci_pm_ops, st_ohci_suspend, st_ohci_resume); + +#endif /* CONFIG_PM_SLEEP */ + +static const struct of_device_id st_ohci_platform_ids[] = { + { .compatible = "st,st-ohci-300x", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, st_ohci_platform_ids); + +static struct platform_driver ohci_platform_driver = { + .probe = st_ohci_platform_probe, + .remove = st_ohci_platform_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "st-ohci", +#ifdef CONFIG_PM_SLEEP + .pm = &st_ohci_pm_ops, +#endif + .of_match_table = st_ohci_platform_ids, + } +}; + +static int __init ohci_platform_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ohci_init_driver(&ohci_platform_hc_driver, &platform_overrides); + return platform_driver_register(&ohci_platform_driver); +} +module_init(ohci_platform_init); + +static void __exit ohci_platform_cleanup(void) +{ + platform_driver_unregister(&ohci_platform_driver); +} +module_exit(ohci_platform_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Peter Griffin "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h new file mode 100644 index 000000000..631dda617 --- /dev/null +++ b/drivers/usb/host/ohci.h @@ -0,0 +1,743 @@ +/* SPDX-License-Identifier: GPL-1.0+ */ +/* + * 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 */ + struct list_head in_use_list; + + /* 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; + + /* Detect TDs not added to the done queue */ + unsigned takeback_wdh_cnt; + struct td *pending_td; +#define OKAY_TO_TAKEBACK(ohci, ed) \ + ((int) (ohci->wdh_cnt - ed->takeback_wdh_cnt) >= 0) + +} __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 __maybe_unused 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[] __counted_by(length); // 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 + * + * @td_cache and @ed_cache are %NULL if &usb_hcd.localmem_pool is used. + */ + struct dma_pool *td_cache; + struct dma_pool *ed_cache; + struct td *td_hash [TD_HASH_SIZE]; + struct td *dl_start, *dl_end; /* the done list */ + struct list_head pending; + struct list_head eds_in_use; /* all EDs with at least 1 TD */ + + /* + * 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 working:1; + unsigned restart_work:1; + + 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 */ +#define OHCI_QUIRK_QEMU 0x1000 /* relax timing expectations */ + + // there are also chip quirks/bugs in init logic + + unsigned prev_frame_no; + unsigned wdh_cnt, prev_wdh_cnt; + u32 prev_donehead; + struct timer_list io_watchdog; + + struct work_struct nec_work; /* Worker for NEC quirk */ + + struct dentry *debug_dir; + + /* platform-specific data -- must come last */ + unsigned long priv[] __aligned(sizeof(s64)); + +}; + +#ifdef CONFIG_USB_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); +} + +/*-------------------------------------------------------------------------*/ + +#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 ) + +/*-------------------------------------------------------------------------*/ + +/* + * 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); +} + +/*-------------------------------------------------------------------------*/ + +/* + * The HCCA frame number is 16 bits, but is accessed as 32 bits since not all + * hardware handles 16 bit reads. Depending on the SoC implementation, the + * frame number can wind up in either bits [31:16] (default) or + * [15:0] (OHCI_QUIRK_FRAME_NO) on big endian hosts. + * + * Somewhat similarly, the 16-bit PSW fields in a transfer descriptor are + * reordered on BE. + */ + +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 (!(ohci->flags & OHCI_QUIRK_FRAME_NO)) + 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); } + +/* Declarations of things exported for use by ohci platform drivers */ + +struct ohci_driver_overrides { + const char *product_desc; + size_t extra_priv_size; + int (*reset)(struct usb_hcd *hcd); +}; + +extern void ohci_init_driver(struct hc_driver *drv, + const struct ohci_driver_overrides *over); +extern int ohci_restart(struct ohci_hcd *ohci); +extern int ohci_setup(struct usb_hcd *hcd); +extern int ohci_suspend(struct usb_hcd *hcd, bool do_wakeup); +extern int ohci_resume(struct usb_hcd *hcd, bool hibernated); +extern int ohci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength); +extern int ohci_hub_status_data(struct usb_hcd *hcd, char *buf); diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c new file mode 100644 index 000000000..ca96067e6 --- /dev/null +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -0,0 +1,4307 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#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_VERSION "0.0.50" + +#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 */ +} __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[]; /* 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) +} __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; +} __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 int 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 */ +} __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]; +} __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]; +} __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 int reclaim_ready:1; + unsigned int scanning:1; + + /* periodic schedule support */ + unsigned int periodic_size; + __le32 *periodic; /* hw periodic table */ + dma_addr_t periodic_dma; + unsigned int i_thresh; /* uframes HC might cache */ + + union ehci_shadow *pshadow; /* mirror hw periodic table */ + int next_uframe; /* scan periodic, start here */ + unsigned int 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 int 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 int 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, +}; + +/* + * 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) + +#ifdef CONFIG_DYNAMIC_DEBUG +#define DEBUG +#endif + +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 __iomem *base, u32 reg) +{ + return readl(base + reg); +} + +static inline void oxu_writel(void __iomem *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; + int ret; + + ret = readl_poll_timeout_atomic(ptr, result, + ((result & mask) == done || + result == U32_MAX), + 1, usec); + if (result == U32_MAX) /* card removed */ + return -ENODEV; + + return ret; +} + +/* 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 + BUG_ON(!HC_IS_RUNNING(oxu_to_hcd(oxu)->state)); +#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 = USB_DT_HUB; + 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 = HUB_CHAR_INDV_PORT_OCPM; /* per-port overcurrent reporting */ + if (HCS_PPC(oxu->hcs_params)) + temp |= HUB_CHAR_INDV_PORT_LPSM; /* per-port power control */ + else + temp |= HUB_CHAR_NO_LPSM; /* 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 allocate 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_t(int, a_blocks, 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; + + timer_delete(&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 = kzalloc_objs(struct oxu_murb, MURB_NUM, 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; + break; + 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 ehci_qtd *qtd, *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_entry_safe(qtd, tmp, &qh->qtd_list, qtd_list) { + struct urb *urb; + u32 token = 0; + + 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 *head) +{ + struct ehci_qtd *qtd, *temp; + + list_for_each_entry_safe(qtd, temp, head, 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 = usb_maxpacket(urb->dev, 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 + */ + 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); + + /* 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" */ + fallthrough; + + 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) +{ + int epnum = urb->ep->desc.bEndpointAddress; + unsigned long flags; + struct ehci_qh *qh = NULL; + int rc = 0; +#ifdef OXU_URB_TRACE + struct ehci_qtd *qtd; + + qtd = list_entry(qtd_list->next, struct ehci_qtd, qtd_list); + + 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); + BUG_ON(oxu->reclaim || (qh->qh_state != QH_STATE_LINKED + && qh->qh_state != QH_STATE_UNLINK_WAIT)); +#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 hub_wq 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; + + /* don't scan past the live uframe */ + frame = now_uframe >> 3; + if (frame != (clock >> 3)) { + /* safe to scan the whole frame at once */ + now_uframe |= 0x07; + } + +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; + + 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", str_up_down(is_on)); + for (port = HCS_N_PORTS(oxu->hcs_params); port > 0; ) { + if (is_on) + oxu_hub_control(oxu_to_hcd(oxu), SetPortFeature, + USB_PORT_FEAT_POWER, port--, NULL, 0); + else + oxu_hub_control(oxu_to_hcd(oxu), 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 USB_RESUME_TIMEOUT resume signaling from this + * port, and make hub_wq collect PORT_STAT_C_SUSPEND to + * stop that signaling. + */ + oxu->reset_done[i] = jiffies + + msecs_to_jiffies(USB_RESUME_TIMEOUT); + 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(struct timer_list *t) +{ + struct oxu_hcd *oxu = timer_container_of(oxu, t, watchdog); + 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); + + timer_setup(&oxu->watchdog, oxu_watchdog, 0); + + /* + * 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_t(unsigned int, park, 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); + + spin_lock_init(&oxu->mem_lock); + INIT_LIST_HEAD(&oxu->urb_list); + oxu->urb_len = 0; + + 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; + + return oxu_hcd_init(hcd); +} + +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. + * dma_pool consistent memory always uses segment zero. + * streaming mappings for I/O buffers, like dma_map_single(), + * can return segments above 4GB, if the device allows. + * + * NOTE: the dma mask is visible through dev->dma_mask, 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 ... */ + timer_delete_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; + 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; + + 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); + fallthrough; + 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); + fallthrough; + 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; + } + fallthrough; + 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, 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 hub_wq 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, hub_wq 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 hub_wq + * 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); + } + } + + spin_unlock_irq(&oxu->lock); + /* turn off now-idle HC */ + timer_delete_sync(&oxu->watchdog); + spin_lock_irq(&oxu->lock); + 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 __iomem *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 __iomem *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 __iomem *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) { + usb_put_hcd(hcd); + return ERR_PTR(ret); + } + + device_wakeup_enable(hcd->self.controller); + return hcd; +} + +static int oxu_init(struct platform_device *pdev, + unsigned long memstart, unsigned long memlen, + void __iomem *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 __iomem *base; + unsigned long memstart, memlen; + int irq, ret; + struct oxu_info *info; + + if (usb_disabled()) + return -ENODEV; + + /* + * Get the platform resources + */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + dev_dbg(&pdev->dev, "IRQ resource %d\n", irq); + + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) { + ret = PTR_ERR(base); + goto error; + } + memstart = res->start; + memlen = resource_size(res); + + ret = irq_set_irq_type(irq, IRQF_TRIGGER_FALLING); + if (ret) { + dev_err(&pdev->dev, "error setting irq type\n"); + ret = -EFAULT; + goto error; + } + + /* Allocate a driver data struct to hold useful info for both + * SPH & OTG devices + */ + info = devm_kzalloc(&pdev->dev, sizeof(struct oxu_info), GFP_KERNEL); + if (!info) { + ret = -EFAULT; + goto error; + } + 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; + } + + dev_info(&pdev->dev, "devices enabled and running\n"); + platform_set_drvdata(pdev, info); + + return 0; + +error: + 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 void oxu_drv_remove(struct platform_device *pdev) +{ + struct oxu_info *info = platform_get_drvdata(pdev); + + oxu_remove(pdev, info->hcd[0]); + oxu_remove(pdev, info->hcd[1]); +} + +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/pci-quirks.c b/drivers/usb/host/pci-quirks.c new file mode 100644 index 000000000..0404489c2 --- /dev/null +++ b/drivers/usb/host/pci-quirks.c @@ -0,0 +1,1306 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 */ + +/* ASMEDIA quirk use */ +#define ASMT_DATA_WRITE0_REG 0xF8 +#define ASMT_DATA_WRITE1_REG 0xFC +#define ASMT_CONTROL_REG 0xE0 +#define ASMT_CONTROL_WRITE_BIT 0x02 +#define ASMT_WRITEREG_CMD 0x10423 +#define ASMT_FLOWCTL_ADDR 0xFA30 +#define ASMT_FLOWCTL_DATA 0xBA +#define ASMT_PSEUDO_DATA 0 + +/* Intel quirk use */ +#define USB_INTEL_XUSB2PR 0xD0 +#define USB_INTEL_USB2PRM 0xD4 +#define USB_INTEL_USB3_PSSEN 0xD8 +#define USB_INTEL_USB3PRM 0xDC + +#ifdef CONFIG_USB_PCI_AMD +/* 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 PT_ADDR_INDX 0xE8 +#define PT_READ_INDX 0xE4 +#define PT_SIG_1_ADDR 0xA520 +#define PT_SIG_2_ADDR 0xA521 +#define PT_SIG_3_ADDR 0xA522 +#define PT_SIG_4_ADDR 0xA523 +#define PT_SIG_1_DATA 0x78 +#define PT_SIG_2_DATA 0x56 +#define PT_SIG_3_DATA 0x34 +#define PT_SIG_4_DATA 0x12 +#define PT4_P1_REG 0xB521 +#define PT4_P2_REG 0xB522 +#define PT2_P1_REG 0xD520 +#define PT2_P2_REG 0xD521 +#define PT1_P1_REG 0xD522 +#define PT1_P2_REG 0xD523 + +#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 + +/* + * amd_chipset_gen values represent AMD different chipset generations + */ +enum amd_chipset_gen { + NOT_AMD_CHIPSET = 0, + AMD_CHIPSET_SB600, + AMD_CHIPSET_SB700, + AMD_CHIPSET_SB800, + AMD_CHIPSET_HUDSON2, + AMD_CHIPSET_BOLTON, + AMD_CHIPSET_YANGTZE, + AMD_CHIPSET_TAISHAN, + AMD_CHIPSET_UNKNOWN, +}; + +struct amd_chipset_type { + enum amd_chipset_gen gen; + u8 rev; +}; + +static struct amd_chipset_info { + struct pci_dev *nb_dev; + struct pci_dev *smbus_dev; + int nb_type; + struct amd_chipset_type sb_type; + int isoc_reqs; + int probe_count; + bool need_pll_quirk; +} amd_chipset; + +static DEFINE_SPINLOCK(amd_lock); + +/* + * amd_chipset_sb_type_init - initialize amd chipset southbridge type + * + * AMD FCH/SB generation and revision is identified by SMBus controller + * vendor, device and revision IDs. + * + * Returns: 1 if it is an AMD chipset, 0 otherwise. + */ +static int amd_chipset_sb_type_init(struct amd_chipset_info *pinfo) +{ + u8 rev = 0; + pinfo->sb_type.gen = AMD_CHIPSET_UNKNOWN; + + pinfo->smbus_dev = pci_get_device(PCI_VENDOR_ID_ATI, + PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL); + if (pinfo->smbus_dev) { + rev = pinfo->smbus_dev->revision; + if (rev >= 0x10 && rev <= 0x1f) + pinfo->sb_type.gen = AMD_CHIPSET_SB600; + else if (rev >= 0x30 && rev <= 0x3f) + pinfo->sb_type.gen = AMD_CHIPSET_SB700; + else if (rev >= 0x40 && rev <= 0x4f) + pinfo->sb_type.gen = AMD_CHIPSET_SB800; + } else { + pinfo->smbus_dev = pci_get_device(PCI_VENDOR_ID_AMD, + PCI_DEVICE_ID_AMD_HUDSON2_SMBUS, NULL); + + if (pinfo->smbus_dev) { + rev = pinfo->smbus_dev->revision; + if (rev >= 0x11 && rev <= 0x14) + pinfo->sb_type.gen = AMD_CHIPSET_HUDSON2; + else if (rev >= 0x15 && rev <= 0x18) + pinfo->sb_type.gen = AMD_CHIPSET_BOLTON; + else if (rev >= 0x39 && rev <= 0x3a) + pinfo->sb_type.gen = AMD_CHIPSET_YANGTZE; + } else { + pinfo->smbus_dev = pci_get_device(PCI_VENDOR_ID_AMD, + 0x145c, NULL); + if (pinfo->smbus_dev) { + rev = pinfo->smbus_dev->revision; + pinfo->sb_type.gen = AMD_CHIPSET_TAISHAN; + } else { + pinfo->sb_type.gen = NOT_AMD_CHIPSET; + return 0; + } + } + } + pinfo->sb_type.rev = rev; + return 1; +} + +void sb800_prefetch(struct device *dev, int on) +{ + u16 misc; + struct pci_dev *pdev = to_pci_dev(dev); + + 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); +} +EXPORT_SYMBOL_GPL(sb800_prefetch); + +static void usb_amd_find_chipset_info(void) +{ + unsigned long flags; + struct amd_chipset_info info = { }; + + 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; + } + spin_unlock_irqrestore(&amd_lock, flags); + + if (!amd_chipset_sb_type_init(&info)) { + goto commit; + } + + switch (info.sb_type.gen) { + case AMD_CHIPSET_SB700: + info.need_pll_quirk = info.sb_type.rev <= 0x3B; + break; + case AMD_CHIPSET_SB800: + case AMD_CHIPSET_HUDSON2: + case AMD_CHIPSET_BOLTON: + info.need_pll_quirk = true; + break; + default: + info.need_pll_quirk = false; + break; + } + + if (!info.need_pll_quirk) { + if (info.smbus_dev) { + pci_dev_put(info.smbus_dev); + info.smbus_dev = NULL; + } + 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; + } + } + + 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++; + + spin_unlock_irqrestore(&amd_lock, flags); + + pci_dev_put(info.nb_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); + } +} + +int usb_hcd_amd_remote_wakeup_quirk(struct pci_dev *pdev) +{ + /* Make sure amd chipset type has already been initialized */ + usb_amd_find_chipset_info(); + if (amd_chipset.sb_type.gen == AMD_CHIPSET_YANGTZE || + amd_chipset.sb_type.gen == AMD_CHIPSET_TAISHAN) { + dev_dbg(&pdev->dev, "QUIRK: Enable AMD remote wakeup fix\n"); + return 1; + } + return 0; +} +EXPORT_SYMBOL_GPL(usb_hcd_amd_remote_wakeup_quirk); + +bool usb_amd_hang_symptom_quirk(void) +{ + u8 rev; + + usb_amd_find_chipset_info(); + rev = amd_chipset.sb_type.rev; + /* SB600 and old version of SB700 have hang symptom bug */ + return amd_chipset.sb_type.gen == AMD_CHIPSET_SB600 || + (amd_chipset.sb_type.gen == AMD_CHIPSET_SB700 && + rev >= 0x3a && rev <= 0x3b); +} +EXPORT_SYMBOL_GPL(usb_amd_hang_symptom_quirk); + +bool usb_amd_prefetch_quirk(void) +{ + usb_amd_find_chipset_info(); + /* SB800 needs pre-fetch fix */ + return amd_chipset.sb_type.gen == AMD_CHIPSET_SB800; +} +EXPORT_SYMBOL_GPL(usb_amd_prefetch_quirk); + +bool usb_amd_quirk_pll_check(void) +{ + usb_amd_find_chipset_info(); + return amd_chipset.need_pll_quirk; +} +EXPORT_SYMBOL_GPL(usb_amd_quirk_pll_check); + +/* + * 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.gen == AMD_CHIPSET_SB800 || + amd_chipset.sb_type.gen == AMD_CHIPSET_HUDSON2 || + amd_chipset.sb_type.gen == AMD_CHIPSET_BOLTON) { + 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.gen == AMD_CHIPSET_SB700 && + amd_chipset.sb_type.rev <= 0x3b) { + 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; + memset(&amd_chipset.sb_type, 0, sizeof(amd_chipset.sb_type)); + amd_chipset.isoc_reqs = 0; + amd_chipset.need_pll_quirk = false; + + spin_unlock_irqrestore(&amd_lock, flags); + + pci_dev_put(nb); + pci_dev_put(smbus); +} +EXPORT_SYMBOL_GPL(usb_amd_dev_put); + +/* + * Check if port is disabled in BIOS on AMD Promontory host. + * BIOS Disabled ports may wake on connect/disconnect and need + * driver workaround to keep them disabled. + * Returns true if port is marked disabled. + */ +bool usb_amd_pt_check_port(struct device *device, int port) +{ + unsigned char value, port_shift; + struct pci_dev *pdev; + u16 reg; + + pdev = to_pci_dev(device); + pci_write_config_word(pdev, PT_ADDR_INDX, PT_SIG_1_ADDR); + + pci_read_config_byte(pdev, PT_READ_INDX, &value); + if (value != PT_SIG_1_DATA) + return false; + + pci_write_config_word(pdev, PT_ADDR_INDX, PT_SIG_2_ADDR); + + pci_read_config_byte(pdev, PT_READ_INDX, &value); + if (value != PT_SIG_2_DATA) + return false; + + pci_write_config_word(pdev, PT_ADDR_INDX, PT_SIG_3_ADDR); + + pci_read_config_byte(pdev, PT_READ_INDX, &value); + if (value != PT_SIG_3_DATA) + return false; + + pci_write_config_word(pdev, PT_ADDR_INDX, PT_SIG_4_ADDR); + + pci_read_config_byte(pdev, PT_READ_INDX, &value); + if (value != PT_SIG_4_DATA) + return false; + + /* Check disabled port setting, if bit is set port is enabled */ + switch (pdev->device) { + case 0x43b9: + case 0x43ba: + /* + * device is AMD_PROMONTORYA_4(0x43b9) or PROMONTORYA_3(0x43ba) + * PT4_P1_REG bits[7..1] represents USB2.0 ports 6 to 0 + * PT4_P2_REG bits[6..0] represents ports 13 to 7 + */ + if (port > 6) { + reg = PT4_P2_REG; + port_shift = port - 7; + } else { + reg = PT4_P1_REG; + port_shift = port + 1; + } + break; + case 0x43bb: + /* + * device is AMD_PROMONTORYA_2(0x43bb) + * PT2_P1_REG bits[7..5] represents USB2.0 ports 2 to 0 + * PT2_P2_REG bits[5..0] represents ports 9 to 3 + */ + if (port > 2) { + reg = PT2_P2_REG; + port_shift = port - 3; + } else { + reg = PT2_P1_REG; + port_shift = port + 5; + } + break; + case 0x43bc: + /* + * device is AMD_PROMONTORYA_1(0x43bc) + * PT1_P1_REG[7..4] represents USB2.0 ports 3 to 0 + * PT1_P2_REG[5..0] represents ports 9 to 4 + */ + if (port > 3) { + reg = PT1_P2_REG; + port_shift = port - 4; + } else { + reg = PT1_P1_REG; + port_shift = port + 4; + } + break; + default: + return false; + } + pci_write_config_word(pdev, PT_ADDR_INDX, reg); + pci_read_config_byte(pdev, PT_READ_INDX, &value); + + return !(value & BIT(port_shift)); +} +EXPORT_SYMBOL_GPL(usb_amd_pt_check_port); +#endif /* CONFIG_USB_PCI_AMD */ + +static int usb_asmedia_wait_write(struct pci_dev *pdev) +{ + unsigned long retry_count; + unsigned char value; + + for (retry_count = 1000; retry_count > 0; --retry_count) { + + pci_read_config_byte(pdev, ASMT_CONTROL_REG, &value); + + if (value == 0xff) { + dev_err(&pdev->dev, "%s: check_ready ERROR", __func__); + return -EIO; + } + + if ((value & ASMT_CONTROL_WRITE_BIT) == 0) + return 0; + + udelay(50); + } + + dev_warn(&pdev->dev, "%s: check_write_ready timeout", __func__); + return -ETIMEDOUT; +} + +void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev) +{ + if (usb_asmedia_wait_write(pdev) != 0) + return; + + /* send command and address to device */ + pci_write_config_dword(pdev, ASMT_DATA_WRITE0_REG, ASMT_WRITEREG_CMD); + pci_write_config_dword(pdev, ASMT_DATA_WRITE1_REG, ASMT_FLOWCTL_ADDR); + pci_write_config_byte(pdev, ASMT_CONTROL_REG, ASMT_CONTROL_WRITE_BIT); + + if (usb_asmedia_wait_write(pdev) != 0) + return; + + /* send data to device */ + pci_write_config_dword(pdev, ASMT_DATA_WRITE0_REG, ASMT_FLOWCTL_DATA); + pci_write_config_dword(pdev, ASMT_DATA_WRITE1_REG, ASMT_PSEUDO_DATA); + pci_write_config_byte(pdev, ASMT_CONTROL_REG, ASMT_CONTROL_WRITE_BIT); +} +EXPORT_SYMBOL_GPL(usb_asmedia_modifyflowcontrol); + +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 mmio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_MEMORY) + +#if defined(CONFIG_HAS_IOPORT) && IS_ENABLED(CONFIG_USB_UHCI_HCD) +/* + * 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); + +#define pio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_IO) + +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_STD_NUM_BARS; 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); +} + +#else /* defined(CONFIG_HAS_IOPORT && IS_ENABLED(CONFIG_USB_UHCI_HCD) */ + +static void quirk_usb_handoff_uhci(struct pci_dev *pdev) {} + +#endif /* defined(CONFIG_HAS_IOPORT && IS_ENABLED(CONFIG_USB_UHCI_HCD) */ + +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 = 0; + bool no_fminterval = false; + int cnt; + + if (!mmio_resource_enabled(pdev, 0)) + return; + + base = pci_ioremap_bar(pdev, 0); + if (base == NULL) + return; + + /* + * ULi M5237 OHCI controller locks the whole system when accessing + * the OHCI_FMINTERVAL offset. + */ + if (pdev->vendor == PCI_VENDOR_ID_AL && pdev->device == 0x5237) + no_fminterval = true; + + 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); + + /* Go into the USB_RESET state, preserving RWC (and possibly IR) */ + writel(control & OHCI_CTRL_MASK, base + OHCI_CONTROL); + readl(base + OHCI_CONTROL); + + /* software reset of the controller, preserving HcFmInterval */ + if (!no_fminterval) + 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); + } + + if (!no_fminterval) + 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); + + /* LS7A EHCI controller doesn't have extended capabilities, the + * EECP (EHCI Extended Capabilities Pointer) field of HCCPARAMS + * register should be 0x0 but it reads as 0xa0. So clear it to + * avoid error messages on boot. + */ + if (pdev->vendor == PCI_VENDOR_ID_LOONGSON && pdev->device == 0x7a14) + hcc_params &= ~(0xffL << 8); + + 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; + 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; + + return readl_poll_timeout_atomic(ptr, result, + ((result & mask) == done), + delay_usec, wait_usec); +} + +/* + * 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_intel_xhci_ports(struct pci_dev *xhci_pdev) +{ + u32 ports_available; + bool ehci_found = false; + struct pci_dev *companion = NULL; + + /* Sony VAIO t-series with subsystem device ID 90a8 is not capable of + * switching ports from EHCI to xHCI + */ + if (xhci_pdev->subsystem_vendor == PCI_VENDOR_ID_SONY && + xhci_pdev->subsystem_device == 0x90a8) + return; + + /* make sure an intel EHCI controller exists */ + for_each_pci_dev(companion) { + if (companion->class == PCI_CLASS_SERIAL_USB_EHCI && + companion->vendor == PCI_VENDOR_ID_INTEL) { + ehci_found = true; + break; + } + } + + if (!ehci_found) + return; + + /* 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, + 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, + 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_intel_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 1 second 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(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_ext_cap(base, 0, XHCI_EXT_CAPS_LEGACY); + + if (!ext_cap_offset) + goto hc_init; + + if ((ext_cap_offset + sizeof(val)) > len) { + /* We're reading garbage from the controller */ + dev_warn(&pdev->dev, "xHCI controller failing to respond"); + goto iounmap; + } + val = readl(base + ext_cap_offset); + + /* Auto handoff never worked for these devices. Force it and continue */ + if ((pdev->vendor == PCI_VENDOR_ID_TI && pdev->device == 0x8241) || + (pdev->vendor == PCI_VENDOR_ID_RENESAS + && pdev->device == 0x0014)) { + val = (val | XHCI_HC_OS_OWNED) & ~XHCI_HC_BIOS_OWNED; + writel(val, base + ext_cap_offset); + } + + /* 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 1 second with 10 microsecond polling interval */ + timeout = handshake(base + ext_cap_offset, XHCI_HC_BIOS_OWNED, + 0, 1000000, 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 (pdev->vendor == PCI_VENDOR_ID_INTEL) + usb_enable_intel_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, + 5000000, 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: + iounmap(base); +} + +static void quirk_usb_early_handoff(struct pci_dev *pdev) +{ + struct device_node *parent; + bool is_rpi; + + /* 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; + + /* + * Bypass the Raspberry Pi 4 controller xHCI controller, things are + * taken care of by the board's co-processor. + */ + if (pdev->vendor == PCI_VENDOR_ID_VIA && pdev->device == 0x3483) { + parent = of_get_parent(pdev->bus->dev.of_node); + is_rpi = of_device_is_compatible(parent, "brcm,bcm2711-pcie"); + of_node_put(parent); + if (is_rpi) + 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 000000000..a5230b0b9 --- /dev/null +++ b/drivers/usb/host/pci-quirks.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LINUX_USB_PCI_QUIRKS_H +#define __LINUX_USB_PCI_QUIRKS_H + +#ifdef CONFIG_USB_PCI_AMD +int usb_hcd_amd_remote_wakeup_quirk(struct pci_dev *pdev); +bool usb_amd_hang_symptom_quirk(void); +bool usb_amd_prefetch_quirk(void); +void usb_amd_dev_put(void); +bool usb_amd_quirk_pll_check(void); +void usb_amd_quirk_pll_disable(void); +void usb_amd_quirk_pll_enable(void); +void sb800_prefetch(struct device *dev, int on); +bool usb_amd_pt_check_port(struct device *device, int port); +#else +static inline bool usb_amd_hang_symptom_quirk(void) +{ + return false; +}; +static inline bool usb_amd_prefetch_quirk(void) +{ + return false; +} +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 bool usb_amd_quirk_pll_check(void) +{ + return false; +} +static inline void sb800_prefetch(struct device *dev, int on) {} +static inline bool usb_amd_pt_check_port(struct device *device, int port) +{ + return false; +} +#endif /* CONFIG_USB_PCI_AMD */ + +#ifdef CONFIG_USB_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); +void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev); +void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev); +void usb_disable_xhci_ports(struct pci_dev *xhci_pdev); +#else +struct pci_dev; +static inline void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev) {} +static inline void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) {} +#endif /* CONFIG_USB_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 000000000..0a26489a4 --- /dev/null +++ b/drivers/usb/host/r8a66597-hcd.c @@ -0,0 +1,2520 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + */ + +#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_prepare_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_unprepare(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; + static const unsigned long fifoaddr[] = {D0FIFO, D1FIFO, CFIFO}; + static const unsigned long fifosel[] = {D0FIFOSEL, D1FIFOSEL, CFIFOSEL}; + static 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_obj(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) +{ + if (!pipe || pipe->info.pipenum == 0) + return; + + pipe_stop(r8a66597, pipe); + r8a66597_bset(r8a66597, ACLRM, pipe->pipectr); + r8a66597_read(r8a66597, pipe->pipectr); + r8a66597_read(r8a66597, pipe->pipectr); + 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 controllers */ + 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 = 50; + break; + } + + mod_timer(&r8a66597->timers[td->pipenum].td, + 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_t(int, urb_len, 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(struct timer_list *t) +{ + struct r8a66597_timers *timers = timer_container_of(timers, t, + interval); + struct r8a66597 *r8a66597 = timers->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->timers[pipenum].interval)) + continue; + + td = r8a66597_get_td(r8a66597, pipenum); + if (td) + start_transfer(r8a66597, td); + } + + spin_unlock_irqrestore(&r8a66597->lock, flags); +} + +static void r8a66597_td_timer(struct timer_list *t) +{ + struct r8a66597_timers *timers = timer_container_of(timers, t, td); + struct r8a66597 *r8a66597 = timers->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->timers[pipenum].td)) + 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); + + /* Select a different address or endpoint */ + 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 && + td->pipe->info.epnum == new_td->pipe->info.epnum); + + 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(struct timer_list *t) +{ + struct r8a66597 *r8a66597 = timer_container_of(r8a66597, t, rh_timer); + 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_obj(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); + 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_obj(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->timers[td->pipenum].interval, + 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) +__acquires(r8a66597->lock) +__releases(r8a66597->lock) +{ + 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; + + spin_lock_irqsave(&r8a66597->lock, flags); + if (pipenum == 0) { + kfree(hep->hcpriv); + hep->hcpriv = NULL; + spin_unlock_irqrestore(&r8a66597->lock, flags); + return; + } + + 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)); + + mutex_lock(&usb_bus_idr_lock); + bus = idr_find(&usb_bus_idr, hcd->self.busnum); + if (bus && bus->root_hub) { + collect_usb_address_map(bus->root_hub, now_map); + update_usb_address_map(r8a66597, bus->root_hub, now_map); + } + mutex_unlock(&usb_bus_idr_lock); +} + +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 = USB_DT_HUB; + desc->bHubContrCurrent = 0; + desc->bNbrPorts = r8a66597->max_root_hub; + desc->bDescLength = 9; + desc->bPwrOn2PwrGood = 0; + desc->wHubCharacteristics = + cpu_to_le16(HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_NO_OCPM); + 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(USB_RESUME_TIMEOUT); + r8a66597_mdfy(r8a66597, UACT, RESUME | UACT, dvstctr_reg); + } + + return 0; + +} +#else +#define r8a66597_bus_suspend NULL +#define r8a66597_bus_resume NULL +#endif + +static const 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 void r8a66597_remove(struct platform_device *pdev) +{ + struct r8a66597 *r8a66597 = platform_get_drvdata(pdev); + struct usb_hcd *hcd = r8a66597_to_hcd(r8a66597); + + timer_delete_sync(&r8a66597->rh_timer); + usb_remove_hcd(hcd); + iounmap(r8a66597->reg); + if (r8a66597->pdata->on_chip) + clk_put(r8a66597->clk); + usb_put_hcd(hcd); +} + +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; + + 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)); + platform_set_drvdata(pdev, r8a66597); + r8a66597->pdata = dev_get_platdata(&pdev->dev); + 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); + timer_setup(&r8a66597->rh_timer, r8a66597_timer, 0); + 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]); + r8a66597->timers[i].r8a66597 = r8a66597; + timer_setup(&r8a66597->timers[i].td, r8a66597_td_timer, 0); + timer_setup(&r8a66597->timers[i].interval, + r8a66597_interval_timer, 0); + } + 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; + } + device_wakeup_enable(hcd->self.controller); + + 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 = hcd_name, + .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 000000000..ab081475c --- /dev/null +++ b/drivers/usb/host/r8a66597.h @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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 + */ + +#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; + +struct r8a66597_timers { + struct timer_list td; + struct timer_list interval; + struct r8a66597 *r8a66597; +}; + +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 r8a66597_timers timers[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 000000000..b044977f6 --- /dev/null +++ b/drivers/usb/host/sl811-hcd.c @@ -0,0 +1,1800 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + * - 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 +#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" + +/* 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 */ + dev_dbg(hcd->self.controller, "power %s\n", + str_on_off(is_on)); + 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; + dev_dbg(sl811_to_hcd(sl811)->self.controller, "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; + dev_dbg(sl811_to_hcd(sl811)->self.controller, "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))) { + dev_dbg(sl811_to_hcd(sl811)->self.controller, + "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: + dev_dbg(sl811_to_hcd(sl811)->self.controller, + "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 */ + dev_dbg(sl811_to_hcd(sl811)->self.controller, + "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); +} + +#ifdef QUIRK2 +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); + dev_dbg(sl811_to_hcd(sl811)->self.controller, + "%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); + dev_dbg(sl811_to_hcd(sl811)->self.controller, + "%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; +} +#endif + +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) { + // dev_err(hcd->self.controller, "overrun to slot %d\n", index); + sl811->stat_overrun++; + } + if (sl811->periodic[index]) + sl811->next_periodic = sl811->periodic[index]; + } + + /* hub_wq 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 hub_wq 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) { + dev_dbg(hcd->self.controller, "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_obj(*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); + 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... */ + dev_dbg(hcd->self.controller, + "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)) + / 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 + */ + dev_dbg(hcd->self.controller, "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?? */ + dev_dbg(hcd->self.controller, + "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?? */ + dev_dbg(hcd->self.controller, + "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 + dev_dbg(sl811_to_hcd(sl811)->self.controller, + "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)) + dev_warn(hcd->self.controller, "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 hub_wq 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 = USB_DT_HUB; + 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 = HUB_CHAR_INDV_PORT_LPSM; + } else + temp = HUB_CHAR_NO_LPSM; + + /* no overcurrent errors detection/handling */ + temp |= HUB_CHAR_NO_OCPM; + + 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(struct timer_list *t) +{ + struct sl811 *sl811 = timer_container_of(sl811, t, timer); + 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: + dev_dbg(sl811_to_hcd(sl811)->self.controller, "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: + dev_dbg(sl811_to_hcd(sl811)->self.controller, "end resume\n"); + sl811->port1 &= ~USB_PORT_STAT_SUSPEND; + break; + default: + dev_dbg(sl811_to_hcd(sl811)->self.controller, + "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); + + /* hub_wq 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 */ + dev_dbg(hcd->self.controller, "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(USB_RESUME_TIMEOUT)); + 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); + + if (__is_defined(VERBOSE) || + *(u16*)(buf+2)) /* only if wPortChange is interesting */ + dev_dbg(hcd->self.controller, "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; + + dev_dbg(hcd->self.controller,"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 + dev_dbg(hcd->self.controller, "%s\n", __func__); + return 0; +} + +static int +sl811h_bus_resume(struct usb_hcd *hcd) +{ + // SOFs on + dev_dbg(hcd->self.controller, "%s\n", __func__); + return 0; +} + +#else + +#define sl811h_bus_suspend NULL +#define sl811h_bus_resume NULL + +#endif + + +/*-------------------------------------------------------------------------*/ + +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 sl811h_debug_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; +} +DEFINE_SHOW_ATTRIBUTE(sl811h_debug); + +/* expect just one sl811 per system */ +static void create_debug_file(struct sl811 *sl811) +{ + debugfs_create_file("sl811h", S_IRUGO, usb_debug_root, sl811, + &sl811h_debug_fops); +} + +static void remove_debug_file(struct sl811 *sl811) +{ + debugfs_lookup_and_remove("sl811h", usb_debug_root); +} + +/*-------------------------------------------------------------------------*/ + +static void +sl811h_stop(struct usb_hcd *hcd) +{ + struct sl811 *sl811 = hcd_to_sl811(hcd); + unsigned long flags; + + timer_delete_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 const 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 void +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); + device_wakeup_disable(hcd->self.controller); + + /* 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); +} + +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; + unsigned long irqflags; + + if (usb_disabled()) + return -ENODEV; + + /* the chip may be wired for either kind of addressing */ + addr = platform_get_mem_or_io(dev, 0); + data = platform_get_mem_or_io(dev, 1); + if (!addr || !data || resource_type(addr) != resource_type(data)) + 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; + + ioaddr = resource_type(addr) == IORESOURCE_IO; + if (ioaddr) { + /* + * 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_get_platdata(&dev->dev); + timer_setup(&sl811->timer, sl811h_timer, 0); + 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 */ + dev_dbg(&dev->dev, "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; + + device_wakeup_enable(hcd->self.controller); + + create_debug_file(sl811); + return retval; + + err6: + usb_put_hcd(hcd); + err5: + if (!ioaddr) + iounmap(data_reg); + err4: + if (!ioaddr) + iounmap(addr_reg); + err2: + dev_dbg(&dev->dev, "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. + */ + +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_POWEROFF: + 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 = hcd_name, + }, +}; +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 000000000..ba8c9aa7d --- /dev/null +++ b/drivers/usb/host/sl811.h @@ -0,0 +1,249 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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; + + 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 PACKET_TRACE +# define PACKET pr_debug("sl811: "stuff) +#else +# define PACKET(stuff...) do{}while(0) +#endif diff --git a/drivers/usb/host/sl811_cs.c b/drivers/usb/host/sl811_cs.c new file mode 100644 index 000000000..ada91ca33 --- /dev/null +++ b/drivers/usb/host/sl811_cs.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + +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 setting 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_obj(local_info_t); + 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 000000000..016987764 --- /dev/null +++ b/drivers/usb/host/ssb-hcd.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + */ +#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; + + 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 ERR_PTR(-ENOMEM); + + 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_and_coherent(dev->dma_dev, DMA_BIT_MASK(32))) + return -EOPNOTSUPP; + + usb_dev = devm_kzalloc(dev->dev, 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)) + return PTR_ERR(usb_dev->ohci_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); + 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), + {}, +}; +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/uhci-debug.c b/drivers/usb/host/uhci-debug.c new file mode 100644 index 000000000..e91ec6a4f --- /dev/null +++ b/drivers/usb/host/uhci-debug.c @@ -0,0 +1,637 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 CONFIG_DYNAMIC_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, USBCMD); + usbstat = uhci_readw(uhci, USBSTS); + usbint = uhci_readw(uhci, USBINTR); + usbfrnum = uhci_readw(uhci, USBFRNUM); + flbaseadd = uhci_readl(uhci, USBFLBASEADD); + sof = uhci_readb(uhci, USBSOF); + portsc1 = uhci_readw(uhci, USBPORTSC1); + portsc2 = uhci_readw(uhci, USBPORTSC2); + + 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_obj(*up); + 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 = file->private_data; + return no_seek_end_llseek_size(file, off, whence, up->size); +} + +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 /* CONFIG_DYNAMIC_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 000000000..8a1f6d1b5 --- /dev/null +++ b/drivers/usb/host/uhci-grlib.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 +#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 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_DMA | 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); + + 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_usb; + } + + hcd->regs = devm_ioremap_resource(&op->dev, &res); + if (IS_ERR(hcd->regs)) { + rv = PTR_ERR(hcd->regs); + goto err_irq; + } + + uhci = hcd_to_uhci(hcd); + + uhci->regs = hcd->regs; + + rv = usb_add_hcd(hcd, irq, 0); + if (rv) + goto err_irq; + + device_wakeup_enable(hcd->self.controller); + return 0; + +err_irq: + irq_dispose_mapping(irq); +err_usb: + usb_put_hcd(hcd); + + return rv; +} + +static void uhci_hcd_grlib_remove(struct platform_device *op) +{ + struct usb_hcd *hcd = platform_get_drvdata(op); + + dev_dbg(&op->dev, "stopping GRLIB GRUSBHC UHCI USB Controller\n"); + + usb_remove_hcd(hcd); + + irq_dispose_mapping(hcd->irq); + usb_put_hcd(hcd); +} + +/* 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 = platform_get_drvdata(op); + + 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", + .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 000000000..433d64a5f --- /dev/null +++ b/drivers/usb/host/uhci-hcd.c @@ -0,0 +1,935 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 CONFIG_DYNAMIC_DEBUG + +static int debug = 1; +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug level"); +static char *errbuf; + +#else + +#define debug 0 +#define errbuf NULL + +#endif + + +#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. + * + * Those interrupts also don't seem to work on ASpeed SoCs. + */ + if (ignore_oc || uhci_is_aspeed(uhci)) + 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; + + /* + * Clear stale status bits on Aspeed as we get a stale HCH + * which causes problems later on + */ + if (uhci_is_aspeed(uhci)) + uhci_writew(uhci, uhci_readw(uhci, USBSTS), USBSTS); + + /* 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; + + + spin_lock_irq(&uhci->lock); + uhci->is_initialized = 0; + spin_unlock_irq(&uhci->lock); + + debugfs_lookup_and_remove(uhci_to_hcd(uhci)->self.bus_name, + uhci_debugfs_root); + + 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; + + hcd->uses_new_polling = 1; + /* Accept arbitrarily long scatter-gather lists */ + if (!hcd->localmem_pool) + hcd->self.sg_tablesize = ~0; + + spin_lock_init(&uhci->lock); + timer_setup(&uhci->fsbr_timer, uhci_fsbr_timeout, 0); + INIT_LIST_HEAD(&uhci->idle_qh_list); + init_waitqueue_head(&uhci->waitqh); + +#ifdef UHCI_DEBUG_OPS + debugfs_create_file(hcd->self.bus_name, S_IFREG|S_IRUGO|S_IWUSR, + uhci_debugfs_root, uhci, &uhci_debug_operations); +#endif + + uhci->frame = dma_alloc_coherent(uhci_dev(uhci), + UHCI_NUMFRAMES * sizeof(*uhci->frame), + &uhci->frame_dma_handle, GFP_KERNEL); + if (!uhci->frame) { + dev_err(uhci_dev(uhci), + "unable to allocate consistent memory for frame list\n"); + goto err_alloc_frame; + } + + uhci->frame_cpu = kzalloc_objs(*uhci->frame_cpu, UHCI_NUMFRAMES); + if (!uhci->frame_cpu) + 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_lookup_and_remove(hcd->self.bus_name, uhci_debugfs_root); + + 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); + + timer_delete_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"; + +#if defined(CONFIG_USB_PCI) && defined(CONFIG_HAS_IOPORT) +#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; + +#ifdef CONFIG_DYNAMIC_DEBUG + errbuf = kmalloc(ERRBUF_LEN, GFP_KERNEL); + if (!errbuf) + goto errbuf_failed; + uhci_debugfs_root = debugfs_create_dir("uhci", usb_debug_root); +#endif + + 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: +#if defined(DEBUG) || defined(CONFIG_DYNAMIC_DEBUG) + debugfs_remove(uhci_debugfs_root); + + kfree(errbuf); + +errbuf_failed: +#endif + 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); +#ifdef CONFIG_DYNAMIC_DEBUG + kfree(errbuf); +#endif +} + +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 000000000..4326d1f3c --- /dev/null +++ b/drivers/usb/host/uhci-hcd.h @@ -0,0 +1,722 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LINUX_UHCI_HCD_H +#define __LINUX_UHCI_HCD_H + +#include +#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 USBPORTSC3 20 +#define USBPORTSC4 22 +#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) READ_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), \ + READ_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 + * 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 { + /* 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 */ + unsigned int is_aspeed:1; /* Aspeed impl. workarounds */ + + /* 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 */ + + struct clk *clk; /* (optional) clock source */ + struct reset_control *rsts; /* (optional) clock reset */ + + /* 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 + +/* Aspeed SoC needs some quirks */ +static inline bool uhci_is_aspeed(const struct uhci_hcd *uhci) +{ + return IS_ENABLED(CONFIG_USB_UHCI_ASPEED) && uhci->is_aspeed; +} + +/* + * 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. + */ + +#ifdef CONFIG_HAS_IOPORT +#define UHCI_IN(x) x +#define UHCI_OUT(x) x +#else +#define UHCI_IN(x) 0 +#define UHCI_OUT(x) do { } while (0) +#endif + +#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 */ +#if defined(CONFIG_USB_PCI) && defined(HAS_IOPORT) +/* 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 int uhci_aspeed_reg(unsigned int reg) +{ + switch (reg) { + case USBCMD: + return 00; + case USBSTS: + return 0x04; + case USBINTR: + return 0x08; + case USBFRNUM: + return 0x80; + case USBFLBASEADD: + return 0x0c; + case USBSOF: + return 0x84; + case USBPORTSC1: + return 0x88; + case USBPORTSC2: + return 0x8c; + case USBPORTSC3: + return 0x90; + case USBPORTSC4: + return 0x94; + default: + pr_warn("UHCI: Unsupported register 0x%02x on Aspeed\n", reg); + /* Return an unimplemented register */ + return 0x10; + } +} + +static inline u32 uhci_readl(const struct uhci_hcd *uhci, int reg) +{ + if (uhci_has_pci_registers(uhci)) + return UHCI_IN(inl(uhci->io_addr + reg)); + else if (uhci_is_aspeed(uhci)) + return readl(uhci->regs + uhci_aspeed_reg(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)) + UHCI_OUT(outl(val, uhci->io_addr + reg)); + else if (uhci_is_aspeed(uhci)) + writel(val, uhci->regs + uhci_aspeed_reg(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 UHCI_IN(inw(uhci->io_addr + reg)); + else if (uhci_is_aspeed(uhci)) + return readl(uhci->regs + uhci_aspeed_reg(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)) + UHCI_OUT(outw(val, uhci->io_addr + reg)); + else if (uhci_is_aspeed(uhci)) + writel(val, uhci->regs + uhci_aspeed_reg(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 UHCI_IN(inb(uhci->io_addr + reg)); + else if (uhci_is_aspeed(uhci)) + return readl(uhci->regs + uhci_aspeed_reg(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)) + UHCI_OUT(outb(val, uhci->io_addr + reg)); + else if (uhci_is_aspeed(uhci)) + writel(val, uhci->regs + uhci_aspeed_reg(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 */ +#undef UHCI_IN +#undef UHCI_OUT + +/* + * 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 000000000..47106dd8c --- /dev/null +++ b/drivers/usb/host/uhci-hub.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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; */ + USB_DT_HUB, /* __u8 bDescriptorType; Hub-descriptor */ + 0x02, /* __u8 bNbrPorts; */ + HUB_CHAR_NO_LPSM | /* __u16 wHubCharacteristics; */ + HUB_CHAR_INDV_PORT_OCPM, /* (per-port OC, no power switching) */ + 0x00, + 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 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(USB_RESUME_TIMEOUT); + 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; + 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); + retval = 4; /* hub power */ + break; + 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); + retval = 4; + break; + case SetHubFeature: /* We don't implement these */ + case ClearHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + break; + 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); + break; + 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(USB_RESUME_TIMEOUT); + break; + case USB_PORT_FEAT_POWER: + /* UHCI has no power switching */ + break; + 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); + break; + case USB_PORT_FEAT_C_ENABLE: + CLR_RH_PORTSTAT(USBPORTSC_PEC); + break; + 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); + } + break; + case USB_PORT_FEAT_C_SUSPEND: + clear_bit(port, &uhci->port_c_suspend); + break; + case USB_PORT_FEAT_POWER: + /* UHCI has no power switching */ + goto err; + case USB_PORT_FEAT_C_CONNECTION: + CLR_RH_PORTSTAT(USBPORTSC_CSC); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + CLR_RH_PORTSTAT(USBPORTSC_OCC); + break; + case USB_PORT_FEAT_C_RESET: + /* this driver won't report these */ + break; + default: + goto err; + } + break; + case GetHubDescriptor: + retval = min_t(unsigned int, sizeof(root_hub_hub_des), wLength); + memcpy(buf, root_hub_hub_des, retval); + if (retval > 2) + buf[2] = uhci->rh_numports; + break; + 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 000000000..5edf6a08c --- /dev/null +++ b/drivers/usb/host/uhci-pci.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + * and ZHAOXIN 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 || + to_pci_dev(uhci_dev(uhci))->vendor == PCI_VENDOR_ID_ZHAOXIN) + 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; + + /* Intel controllers use non-PME wakeup signalling */ + if (to_pci_dev(uhci_dev(uhci))->vendor == PCI_VENDOR_ID_INTEL) + device_set_wakeup_capable(uhci_dev(uhci), true); + + /* 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_resume(struct usb_hcd *hcd, pm_message_t state); + +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 */ + + /* 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); + spin_unlock_irq(&uhci->lock); + + synchronize_irq(hcd->irq); + + /* Check for race with a wakeup request */ + if (do_wakeup && HCD_WAKEUP_PENDING(hcd)) { + uhci_pci_resume(hcd, PMSG_SUSPEND); + rc = -EBUSY; + } + return rc; +} + +static int uhci_pci_resume(struct usb_hcd *hcd, pm_message_t msg) +{ + bool hibernated = (msg.event == PM_EVENT_RESTORE); + 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_DMA | 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 const struct pci_device_id uhci_pci_ids[] = { { + /* handle any USB UHCI controller */ + PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_UHCI, ~0), + }, { /* end: all zeroes */ } +}; + +MODULE_DEVICE_TABLE(pci, uhci_pci_ids); + +static int uhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + return usb_hcd_pci_probe(dev, &uhci_driver); +} + +static struct pci_driver uhci_pci_driver = { + .name = hcd_name, + .id_table = uhci_pci_ids, + + .probe = uhci_pci_probe, + .remove = usb_hcd_pci_remove, + .shutdown = uhci_shutdown, + +#ifdef CONFIG_PM + .driver = { + .pm = &usb_hcd_pci_pm_ops + }, +#endif +}; + +MODULE_SOFTDEP("pre: ehci_pci"); diff --git a/drivers/usb/host/uhci-platform.c b/drivers/usb/host/uhci-platform.c new file mode 100644 index 000000000..f4419d452 --- /dev/null +++ b/drivers/usb/host/uhci-platform.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 +#include +#include + +static int uhci_platform_init(struct usb_hcd *hcd) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + + /* Probe number of ports if not already provided by DT */ + if (!uhci->rh_numports) + 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_DMA | 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 device_node *np = pdev->dev.of_node; + bool dma_mask_64 = false; + 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 (of_device_get_match_data(&pdev->dev)) + dma_mask_64 = true; + + ret = dma_coerce_mask_and_coherent(&pdev->dev, + dma_mask_64 ? DMA_BIT_MASK(64) : DMA_BIT_MASK(32)); + if (ret) + return ret; + + hcd = usb_create_hcd(&uhci_platform_hc_driver, &pdev->dev, + pdev->name); + if (!hcd) + return -ENOMEM; + + uhci = hcd_to_uhci(hcd); + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(hcd->regs)) { + ret = PTR_ERR(hcd->regs); + goto err_rmr; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + uhci->regs = hcd->regs; + + /* Grab some things from the device-tree */ + if (np) { + u32 num_ports; + + if (of_property_read_u32(np, "#ports", &num_ports) == 0) { + uhci->rh_numports = num_ports; + dev_info(&pdev->dev, + "Detected %d ports from device-tree\n", + num_ports); + } + if (of_device_is_compatible(np, "aspeed,ast2400-uhci") || + of_device_is_compatible(np, "aspeed,ast2500-uhci") || + of_device_is_compatible(np, "aspeed,ast2600-uhci") || + of_device_is_compatible(np, "aspeed,ast2700-uhci")) { + uhci->is_aspeed = 1; + dev_info(&pdev->dev, + "Enabled Aspeed implementation workarounds\n"); + } + } + + /* Get and enable clock if any specified */ + uhci->clk = devm_clk_get_optional(&pdev->dev, NULL); + if (IS_ERR(uhci->clk)) { + ret = PTR_ERR(uhci->clk); + goto err_rmr; + } + ret = clk_prepare_enable(uhci->clk); + if (ret) { + dev_err(&pdev->dev, "Error couldn't enable clock (%d)\n", ret); + goto err_rmr; + } + + uhci->rsts = devm_reset_control_array_get_optional_shared(&pdev->dev); + if (IS_ERR(uhci->rsts)) { + ret = PTR_ERR(uhci->rsts); + goto err_clk; + } + ret = reset_control_deassert(uhci->rsts); + if (ret) + goto err_clk; + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto err_reset; + + ret = usb_add_hcd(hcd, ret, IRQF_SHARED); + if (ret) + goto err_reset; + + device_wakeup_enable(hcd->self.controller); + return 0; + +err_reset: + reset_control_assert(uhci->rsts); +err_clk: + clk_disable_unprepare(uhci->clk); +err_rmr: + usb_put_hcd(hcd); + + return ret; +} + +static void uhci_hcd_platform_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + + reset_control_assert(uhci->rsts); + clk_disable_unprepare(uhci->clk); + usb_remove_hcd(hcd); + usb_put_hcd(hcd); +} + +/* 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 = platform_get_drvdata(op); + + uhci_hc_died(hcd_to_uhci(hcd)); +} + +static const struct of_device_id platform_uhci_ids[] = { + { .compatible = "generic-uhci", }, + { .compatible = "platform-uhci", }, + { .compatible = "aspeed,ast2700-uhci", .data = (void *)1 }, + {} +}; +MODULE_DEVICE_TABLE(of, platform_uhci_ids); + +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", + .of_match_table = platform_uhci_ids, + }, +}; +MODULE_SOFTDEP("pre: ehci_platform"); diff --git a/drivers/usb/host/uhci-q.c b/drivers/usb/host/uhci-q.c new file mode 100644 index 000000000..9480d4ff0 --- /dev/null +++ b/drivers/usb/host/uhci-q.c @@ -0,0 +1,1793 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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; + + 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; + timer_delete(&uhci->fsbr_timer); + } + } +} + +static void uhci_fsbr_timeout(struct timer_list *t) +{ + struct uhci_hcd *uhci = timer_container_of(uhci, t, fsbr_timer); + 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_zalloc(uhci->qh_pool, GFP_ATOMIC, &dma_handle); + if (!qh) + return NULL; + + 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/xen-hcd.c b/drivers/usb/host/xen-hcd.c new file mode 100644 index 000000000..0a94d3029 --- /dev/null +++ b/drivers/usb/host/xen-hcd.c @@ -0,0 +1,1611 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * xen-hcd.c + * + * Xen USB Virtual Host Controller driver + * + * Copyright (C) 2009, FUJITSU LABORATORIES LTD. + * Author: Noboru Iwamatsu + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +/* Private per-URB data */ +struct urb_priv { + struct list_head list; + struct urb *urb; + int req_id; /* RING_REQUEST id for submitting */ + int unlink_req_id; /* RING_REQUEST id for unlinking */ + int status; + bool unlinked; /* dequeued marker */ +}; + +/* virtual roothub port status */ +struct rhport_status { + __u32 status; + bool resuming; /* in resuming */ + bool c_connection; /* connection changed */ + unsigned long timeout; +}; + +/* status of attached device */ +struct vdevice_status { + int devnum; + enum usb_device_state status; + enum usb_device_speed speed; +}; + +/* RING request shadow */ +struct usb_shadow { + struct xenusb_urb_request req; + struct urb *urb; + bool in_flight; +}; + +struct xenhcd_info { + /* Virtual Host Controller has 4 urb queues */ + struct list_head pending_submit_list; + struct list_head pending_unlink_list; + struct list_head in_progress_list; + struct list_head giveback_waiting_list; + + spinlock_t lock; + + /* timer that kick pending and giveback waiting urbs */ + struct timer_list watchdog; + unsigned long actions; + + /* virtual root hub */ + int rh_numports; + struct rhport_status ports[XENUSB_MAX_PORTNR]; + struct vdevice_status devices[XENUSB_MAX_PORTNR]; + + /* Xen related staff */ + struct xenbus_device *xbdev; + int urb_ring_ref; + int conn_ring_ref; + struct xenusb_urb_front_ring urb_ring; + struct xenusb_conn_front_ring conn_ring; + + unsigned int evtchn; + unsigned int irq; + struct usb_shadow shadow[XENUSB_URB_RING_SIZE]; + unsigned int shadow_free; + + bool error; +}; + +#define XENHCD_RING_JIFFIES (HZ/200) +#define XENHCD_SCAN_JIFFIES 1 + +enum xenhcd_timer_action { + TIMER_RING_WATCHDOG, + TIMER_SCAN_PENDING_URBS, +}; + +static struct kmem_cache *xenhcd_urbp_cachep; + +static inline struct xenhcd_info *xenhcd_hcd_to_info(struct usb_hcd *hcd) +{ + return (struct xenhcd_info *)hcd->hcd_priv; +} + +static inline struct usb_hcd *xenhcd_info_to_hcd(struct xenhcd_info *info) +{ + return container_of((void *)info, struct usb_hcd, hcd_priv); +} + +static void xenhcd_set_error(struct xenhcd_info *info, const char *msg) +{ + info->error = true; + + pr_alert("xen-hcd: protocol error: %s!\n", msg); +} + +static inline void xenhcd_timer_action_done(struct xenhcd_info *info, + enum xenhcd_timer_action action) +{ + clear_bit(action, &info->actions); +} + +static void xenhcd_timer_action(struct xenhcd_info *info, + enum xenhcd_timer_action action) +{ + if (timer_pending(&info->watchdog) && + test_bit(TIMER_SCAN_PENDING_URBS, &info->actions)) + return; + + if (!test_and_set_bit(action, &info->actions)) { + unsigned long t; + + switch (action) { + case TIMER_RING_WATCHDOG: + t = XENHCD_RING_JIFFIES; + break; + default: + t = XENHCD_SCAN_JIFFIES; + break; + } + mod_timer(&info->watchdog, t + jiffies); + } +} + +/* + * set virtual port connection status + */ +static void xenhcd_set_connect_state(struct xenhcd_info *info, int portnum) +{ + int port; + + port = portnum - 1; + if (info->ports[port].status & USB_PORT_STAT_POWER) { + switch (info->devices[port].speed) { + case XENUSB_SPEED_NONE: + info->ports[port].status &= + ~(USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_ENABLE | + USB_PORT_STAT_LOW_SPEED | + USB_PORT_STAT_HIGH_SPEED | + USB_PORT_STAT_SUSPEND); + break; + case XENUSB_SPEED_LOW: + info->ports[port].status |= USB_PORT_STAT_CONNECTION; + info->ports[port].status |= USB_PORT_STAT_LOW_SPEED; + break; + case XENUSB_SPEED_FULL: + info->ports[port].status |= USB_PORT_STAT_CONNECTION; + break; + case XENUSB_SPEED_HIGH: + info->ports[port].status |= USB_PORT_STAT_CONNECTION; + info->ports[port].status |= USB_PORT_STAT_HIGH_SPEED; + break; + default: /* error */ + return; + } + info->ports[port].status |= (USB_PORT_STAT_C_CONNECTION << 16); + } +} + +/* + * set virtual device connection status + */ +static int xenhcd_rhport_connect(struct xenhcd_info *info, __u8 portnum, + __u8 speed) +{ + int port; + + if (portnum < 1 || portnum > info->rh_numports) + return -EINVAL; /* invalid port number */ + + port = portnum - 1; + if (info->devices[port].speed != speed) { + switch (speed) { + case XENUSB_SPEED_NONE: /* disconnect */ + info->devices[port].status = USB_STATE_NOTATTACHED; + break; + case XENUSB_SPEED_LOW: + case XENUSB_SPEED_FULL: + case XENUSB_SPEED_HIGH: + info->devices[port].status = USB_STATE_ATTACHED; + break; + default: /* error */ + return -EINVAL; + } + info->devices[port].speed = speed; + info->ports[port].c_connection = true; + + xenhcd_set_connect_state(info, portnum); + } + + return 0; +} + +/* + * SetPortFeature(PORT_SUSPENDED) + */ +static void xenhcd_rhport_suspend(struct xenhcd_info *info, int portnum) +{ + int port; + + port = portnum - 1; + info->ports[port].status |= USB_PORT_STAT_SUSPEND; + info->devices[port].status = USB_STATE_SUSPENDED; +} + +/* + * ClearPortFeature(PORT_SUSPENDED) + */ +static void xenhcd_rhport_resume(struct xenhcd_info *info, int portnum) +{ + int port; + + port = portnum - 1; + if (info->ports[port].status & USB_PORT_STAT_SUSPEND) { + info->ports[port].resuming = true; + info->ports[port].timeout = jiffies + msecs_to_jiffies(20); + } +} + +/* + * SetPortFeature(PORT_POWER) + */ +static void xenhcd_rhport_power_on(struct xenhcd_info *info, int portnum) +{ + int port; + + port = portnum - 1; + if ((info->ports[port].status & USB_PORT_STAT_POWER) == 0) { + info->ports[port].status |= USB_PORT_STAT_POWER; + if (info->devices[port].status != USB_STATE_NOTATTACHED) + info->devices[port].status = USB_STATE_POWERED; + if (info->ports[port].c_connection) + xenhcd_set_connect_state(info, portnum); + } +} + +/* + * ClearPortFeature(PORT_POWER) + * SetConfiguration(non-zero) + * Power_Source_Off + * Over-current + */ +static void xenhcd_rhport_power_off(struct xenhcd_info *info, int portnum) +{ + int port; + + port = portnum - 1; + if (info->ports[port].status & USB_PORT_STAT_POWER) { + info->ports[port].status = 0; + if (info->devices[port].status != USB_STATE_NOTATTACHED) + info->devices[port].status = USB_STATE_ATTACHED; + } +} + +/* + * ClearPortFeature(PORT_ENABLE) + */ +static void xenhcd_rhport_disable(struct xenhcd_info *info, int portnum) +{ + int port; + + port = portnum - 1; + info->ports[port].status &= ~USB_PORT_STAT_ENABLE; + info->ports[port].status &= ~USB_PORT_STAT_SUSPEND; + info->ports[port].resuming = false; + if (info->devices[port].status != USB_STATE_NOTATTACHED) + info->devices[port].status = USB_STATE_POWERED; +} + +/* + * SetPortFeature(PORT_RESET) + */ +static void xenhcd_rhport_reset(struct xenhcd_info *info, int portnum) +{ + int port; + + port = portnum - 1; + info->ports[port].status &= ~(USB_PORT_STAT_ENABLE | + USB_PORT_STAT_LOW_SPEED | + USB_PORT_STAT_HIGH_SPEED); + info->ports[port].status |= USB_PORT_STAT_RESET; + + if (info->devices[port].status != USB_STATE_NOTATTACHED) + info->devices[port].status = USB_STATE_ATTACHED; + + /* 10msec reset signaling */ + info->ports[port].timeout = jiffies + msecs_to_jiffies(10); +} + +#ifdef CONFIG_PM +static int xenhcd_bus_suspend(struct usb_hcd *hcd) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + int ret = 0; + int i, ports; + + ports = info->rh_numports; + + spin_lock_irq(&info->lock); + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + ret = -ESHUTDOWN; + } else { + /* suspend any active ports*/ + for (i = 1; i <= ports; i++) + xenhcd_rhport_suspend(info, i); + } + spin_unlock_irq(&info->lock); + + timer_delete_sync(&info->watchdog); + + return ret; +} + +static int xenhcd_bus_resume(struct usb_hcd *hcd) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + int ret = 0; + int i, ports; + + ports = info->rh_numports; + + spin_lock_irq(&info->lock); + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + ret = -ESHUTDOWN; + } else { + /* resume any suspended ports*/ + for (i = 1; i <= ports; i++) + xenhcd_rhport_resume(info, i); + } + spin_unlock_irq(&info->lock); + + return ret; +} +#endif + +static void xenhcd_hub_descriptor(struct xenhcd_info *info, + struct usb_hub_descriptor *desc) +{ + __u16 temp; + int ports = info->rh_numports; + + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = 10; /* EHCI says 20ms max */ + desc->bHubContrCurrent = 0; + desc->bNbrPorts = ports; + + /* size of DeviceRemovable and PortPwrCtrlMask fields */ + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + /* bitmaps for DeviceRemovable and PortPwrCtrlMask */ + memset(&desc->u.hs.DeviceRemovable[0], 0, temp); + memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); + + /* per-port over current reporting and no power switching */ + temp = 0x000a; + desc->wHubCharacteristics = cpu_to_le16(temp); +} + +/* port status change mask for hub_status_data */ +#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) + +/* + * See USB 2.0 Spec, 11.12.4 Hub and Port Status Change Bitmap. + * If port status changed, writes the bitmap to buf and return + * that length(number of bytes). + * If Nothing changed, return 0. + */ +static int xenhcd_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + int ports; + int i; + unsigned long flags; + int ret; + int changed = 0; + + /* initialize the status to no-changes */ + ports = info->rh_numports; + ret = 1 + (ports / 8); + memset(buf, 0, ret); + + spin_lock_irqsave(&info->lock, flags); + + for (i = 0; i < ports; i++) { + /* check status for each port */ + if (info->ports[i].status & PORT_C_MASK) { + buf[(i + 1) / 8] |= 1 << (i + 1) % 8; + changed = 1; + } + } + + if ((hcd->state == HC_STATE_SUSPENDED) && (changed == 1)) + usb_hcd_resume_root_hub(hcd); + + spin_unlock_irqrestore(&info->lock, flags); + + return changed ? ret : 0; +} + +static int xenhcd_hub_control(struct usb_hcd *hcd, __u16 typeReq, __u16 wValue, + __u16 wIndex, char *buf, __u16 wLength) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + int ports = info->rh_numports; + unsigned long flags; + int ret = 0; + int i; + int changed = 0; + + spin_lock_irqsave(&info->lock, flags); + switch (typeReq) { + case ClearHubFeature: + /* ignore this request */ + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + xenhcd_rhport_resume(info, wIndex); + break; + case USB_PORT_FEAT_POWER: + xenhcd_rhport_power_off(info, wIndex); + break; + case USB_PORT_FEAT_ENABLE: + xenhcd_rhport_disable(info, wIndex); + break; + case USB_PORT_FEAT_C_CONNECTION: + info->ports[wIndex - 1].c_connection = false; + fallthrough; + default: + info->ports[wIndex - 1].status &= ~(1 << wValue); + break; + } + break; + case GetHubDescriptor: + xenhcd_hub_descriptor(info, (struct usb_hub_descriptor *)buf); + break; + case GetHubStatus: + /* always local power supply good and no over-current exists. */ + *(__le32 *)buf = cpu_to_le32(0); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + + wIndex--; + + /* resume completion */ + if (info->ports[wIndex].resuming && + time_after_eq(jiffies, info->ports[wIndex].timeout)) { + info->ports[wIndex].status |= + USB_PORT_STAT_C_SUSPEND << 16; + info->ports[wIndex].status &= ~USB_PORT_STAT_SUSPEND; + } + + /* reset completion */ + if ((info->ports[wIndex].status & USB_PORT_STAT_RESET) != 0 && + time_after_eq(jiffies, info->ports[wIndex].timeout)) { + info->ports[wIndex].status |= + USB_PORT_STAT_C_RESET << 16; + info->ports[wIndex].status &= ~USB_PORT_STAT_RESET; + + if (info->devices[wIndex].status != + USB_STATE_NOTATTACHED) { + info->ports[wIndex].status |= + USB_PORT_STAT_ENABLE; + info->devices[wIndex].status = + USB_STATE_DEFAULT; + } + + switch (info->devices[wIndex].speed) { + case XENUSB_SPEED_LOW: + info->ports[wIndex].status |= + USB_PORT_STAT_LOW_SPEED; + break; + case XENUSB_SPEED_HIGH: + info->ports[wIndex].status |= + USB_PORT_STAT_HIGH_SPEED; + break; + default: + break; + } + } + + *(__le32 *)buf = cpu_to_le32(info->ports[wIndex].status); + break; + case SetPortFeature: + if (!wIndex || wIndex > ports) + goto error; + + switch (wValue) { + case USB_PORT_FEAT_POWER: + xenhcd_rhport_power_on(info, wIndex); + break; + case USB_PORT_FEAT_RESET: + xenhcd_rhport_reset(info, wIndex); + break; + case USB_PORT_FEAT_SUSPEND: + xenhcd_rhport_suspend(info, wIndex); + break; + default: + if (info->ports[wIndex-1].status & USB_PORT_STAT_POWER) + info->ports[wIndex-1].status |= (1 << wValue); + } + break; + + case SetHubFeature: + /* not supported */ + default: +error: + ret = -EPIPE; + } + spin_unlock_irqrestore(&info->lock, flags); + + /* check status for each port */ + for (i = 0; i < ports; i++) { + if (info->ports[i].status & PORT_C_MASK) + changed = 1; + } + if (changed) + usb_hcd_poll_rh_status(hcd); + + return ret; +} + +static void xenhcd_free_urb_priv(struct urb_priv *urbp) +{ + urbp->urb->hcpriv = NULL; + kmem_cache_free(xenhcd_urbp_cachep, urbp); +} + +static inline unsigned int xenhcd_get_id_from_freelist(struct xenhcd_info *info) +{ + unsigned int free; + + free = info->shadow_free; + info->shadow_free = info->shadow[free].req.id; + info->shadow[free].req.id = 0x0fff; /* debug */ + return free; +} + +static inline void xenhcd_add_id_to_freelist(struct xenhcd_info *info, + unsigned int id) +{ + info->shadow[id].req.id = info->shadow_free; + info->shadow[id].urb = NULL; + info->shadow_free = id; +} + +static inline int xenhcd_count_pages(void *addr, int length) +{ + unsigned long vaddr = (unsigned long)addr; + + return PFN_UP(vaddr + length) - PFN_DOWN(vaddr); +} + +static void xenhcd_gnttab_map(struct xenhcd_info *info, void *addr, int length, + grant_ref_t *gref_head, + struct xenusb_request_segment *seg, + int nr_pages, int flags) +{ + grant_ref_t ref; + unsigned int offset; + unsigned int len = length; + unsigned int bytes; + int i; + + for (i = 0; i < nr_pages; i++) { + offset = offset_in_page(addr); + + bytes = PAGE_SIZE - offset; + if (bytes > len) + bytes = len; + + ref = gnttab_claim_grant_reference(gref_head); + gnttab_grant_foreign_access_ref(ref, info->xbdev->otherend_id, + virt_to_gfn(addr), flags); + seg[i].gref = ref; + seg[i].offset = (__u16)offset; + seg[i].length = (__u16)bytes; + + addr += bytes; + len -= bytes; + } +} + +static __u32 xenhcd_pipe_urb_to_xenusb(__u32 urb_pipe, __u8 port) +{ + static __u32 pipe; + + pipe = usb_pipedevice(urb_pipe) << XENUSB_PIPE_DEV_SHIFT; + pipe |= usb_pipeendpoint(urb_pipe) << XENUSB_PIPE_EP_SHIFT; + if (usb_pipein(urb_pipe)) + pipe |= XENUSB_PIPE_DIR; + switch (usb_pipetype(urb_pipe)) { + case PIPE_ISOCHRONOUS: + pipe |= XENUSB_PIPE_TYPE_ISOC << XENUSB_PIPE_TYPE_SHIFT; + break; + case PIPE_INTERRUPT: + pipe |= XENUSB_PIPE_TYPE_INT << XENUSB_PIPE_TYPE_SHIFT; + break; + case PIPE_CONTROL: + pipe |= XENUSB_PIPE_TYPE_CTRL << XENUSB_PIPE_TYPE_SHIFT; + break; + case PIPE_BULK: + pipe |= XENUSB_PIPE_TYPE_BULK << XENUSB_PIPE_TYPE_SHIFT; + break; + } + pipe = xenusb_setportnum_pipe(pipe, port); + + return pipe; +} + +static int xenhcd_map_urb_for_request(struct xenhcd_info *info, struct urb *urb, + struct xenusb_urb_request *req) +{ + grant_ref_t gref_head; + int nr_buff_pages = 0; + int nr_isodesc_pages = 0; + int nr_grants = 0; + + if (urb->transfer_buffer_length) { + nr_buff_pages = xenhcd_count_pages(urb->transfer_buffer, + urb->transfer_buffer_length); + + if (usb_pipeisoc(urb->pipe)) + nr_isodesc_pages = xenhcd_count_pages( + &urb->iso_frame_desc[0], + sizeof(struct usb_iso_packet_descriptor) * + urb->number_of_packets); + + nr_grants = nr_buff_pages + nr_isodesc_pages; + if (nr_grants > XENUSB_MAX_SEGMENTS_PER_REQUEST) { + pr_err("xenhcd: error: %d grants\n", nr_grants); + return -E2BIG; + } + + if (gnttab_alloc_grant_references(nr_grants, &gref_head)) { + pr_err("xenhcd: gnttab_alloc_grant_references() error\n"); + return -ENOMEM; + } + + xenhcd_gnttab_map(info, urb->transfer_buffer, + urb->transfer_buffer_length, &gref_head, + &req->seg[0], nr_buff_pages, + usb_pipein(urb->pipe) ? 0 : GTF_readonly); + } + + req->pipe = xenhcd_pipe_urb_to_xenusb(urb->pipe, urb->dev->portnum); + req->transfer_flags = 0; + if (urb->transfer_flags & URB_SHORT_NOT_OK) + req->transfer_flags |= XENUSB_SHORT_NOT_OK; + req->buffer_length = urb->transfer_buffer_length; + req->nr_buffer_segs = nr_buff_pages; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + req->u.isoc.interval = urb->interval; + req->u.isoc.start_frame = urb->start_frame; + req->u.isoc.number_of_packets = urb->number_of_packets; + req->u.isoc.nr_frame_desc_segs = nr_isodesc_pages; + + xenhcd_gnttab_map(info, &urb->iso_frame_desc[0], + sizeof(struct usb_iso_packet_descriptor) * + urb->number_of_packets, + &gref_head, &req->seg[nr_buff_pages], + nr_isodesc_pages, 0); + break; + case PIPE_INTERRUPT: + req->u.intr.interval = urb->interval; + break; + case PIPE_CONTROL: + if (urb->setup_packet) + memcpy(req->u.ctrl, urb->setup_packet, 8); + break; + case PIPE_BULK: + break; + default: + break; + } + + if (nr_grants) + gnttab_free_grant_references(gref_head); + + return 0; +} + +static void xenhcd_gnttab_done(struct xenhcd_info *info, unsigned int id) +{ + struct usb_shadow *shadow = info->shadow + id; + int nr_segs = 0; + int i; + + if (!shadow->in_flight) { + xenhcd_set_error(info, "Illegal request id"); + return; + } + shadow->in_flight = false; + + nr_segs = shadow->req.nr_buffer_segs; + + if (xenusb_pipeisoc(shadow->req.pipe)) + nr_segs += shadow->req.u.isoc.nr_frame_desc_segs; + + for (i = 0; i < nr_segs; i++) { + if (!gnttab_try_end_foreign_access(shadow->req.seg[i].gref)) + xenhcd_set_error(info, "backend didn't release grant"); + } + + shadow->req.nr_buffer_segs = 0; + shadow->req.u.isoc.nr_frame_desc_segs = 0; +} + +static int xenhcd_translate_status(int status) +{ + switch (status) { + case XENUSB_STATUS_OK: + return 0; + case XENUSB_STATUS_NODEV: + return -ENODEV; + case XENUSB_STATUS_INVAL: + return -EINVAL; + case XENUSB_STATUS_STALL: + return -EPIPE; + case XENUSB_STATUS_IOERROR: + return -EPROTO; + case XENUSB_STATUS_BABBLE: + return -EOVERFLOW; + default: + return -ESHUTDOWN; + } +} + +static void xenhcd_giveback_urb(struct xenhcd_info *info, struct urb *urb, + int status) +{ + struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; + int priv_status = urbp->status; + + list_del_init(&urbp->list); + xenhcd_free_urb_priv(urbp); + + if (urb->status == -EINPROGRESS) + urb->status = xenhcd_translate_status(status); + + spin_unlock(&info->lock); + usb_hcd_giveback_urb(xenhcd_info_to_hcd(info), urb, + priv_status <= 0 ? priv_status : urb->status); + spin_lock(&info->lock); +} + +static int xenhcd_do_request(struct xenhcd_info *info, struct urb_priv *urbp) +{ + struct xenusb_urb_request *req; + struct urb *urb = urbp->urb; + unsigned int id; + int notify; + int ret; + + id = xenhcd_get_id_from_freelist(info); + req = &info->shadow[id].req; + req->id = id; + + if (unlikely(urbp->unlinked)) { + req->u.unlink.unlink_id = urbp->req_id; + req->pipe = xenusb_setunlink_pipe(xenhcd_pipe_urb_to_xenusb( + urb->pipe, urb->dev->portnum)); + urbp->unlink_req_id = id; + } else { + ret = xenhcd_map_urb_for_request(info, urb, req); + if (ret) { + xenhcd_add_id_to_freelist(info, id); + return ret; + } + urbp->req_id = id; + } + + req = RING_GET_REQUEST(&info->urb_ring, info->urb_ring.req_prod_pvt); + *req = info->shadow[id].req; + + info->urb_ring.req_prod_pvt++; + info->shadow[id].urb = urb; + info->shadow[id].in_flight = true; + + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->urb_ring, notify); + if (notify) + notify_remote_via_irq(info->irq); + + return 0; +} + +static void xenhcd_kick_pending_urbs(struct xenhcd_info *info) +{ + struct urb_priv *urbp; + + while (!list_empty(&info->pending_submit_list)) { + if (RING_FULL(&info->urb_ring)) { + xenhcd_timer_action(info, TIMER_RING_WATCHDOG); + return; + } + + urbp = list_entry(info->pending_submit_list.next, + struct urb_priv, list); + if (!xenhcd_do_request(info, urbp)) + list_move_tail(&urbp->list, &info->in_progress_list); + else + xenhcd_giveback_urb(info, urbp->urb, -ESHUTDOWN); + } + xenhcd_timer_action_done(info, TIMER_SCAN_PENDING_URBS); +} + +/* + * caller must lock info->lock + */ +static void xenhcd_cancel_all_enqueued_urbs(struct xenhcd_info *info) +{ + struct urb_priv *urbp, *tmp; + int req_id; + + list_for_each_entry_safe(urbp, tmp, &info->in_progress_list, list) { + req_id = urbp->req_id; + if (!urbp->unlinked) { + xenhcd_gnttab_done(info, req_id); + if (info->error) + return; + if (urbp->urb->status == -EINPROGRESS) + /* not dequeued */ + xenhcd_giveback_urb(info, urbp->urb, + -ESHUTDOWN); + else /* dequeued */ + xenhcd_giveback_urb(info, urbp->urb, + urbp->urb->status); + } + info->shadow[req_id].urb = NULL; + } + + list_for_each_entry_safe(urbp, tmp, &info->pending_submit_list, list) + xenhcd_giveback_urb(info, urbp->urb, -ESHUTDOWN); +} + +/* + * caller must lock info->lock + */ +static void xenhcd_giveback_unlinked_urbs(struct xenhcd_info *info) +{ + struct urb_priv *urbp, *tmp; + + list_for_each_entry_safe(urbp, tmp, &info->giveback_waiting_list, list) + xenhcd_giveback_urb(info, urbp->urb, urbp->urb->status); +} + +static int xenhcd_submit_urb(struct xenhcd_info *info, struct urb_priv *urbp) +{ + int ret; + + if (RING_FULL(&info->urb_ring)) { + list_add_tail(&urbp->list, &info->pending_submit_list); + xenhcd_timer_action(info, TIMER_RING_WATCHDOG); + return 0; + } + + if (!list_empty(&info->pending_submit_list)) { + list_add_tail(&urbp->list, &info->pending_submit_list); + xenhcd_timer_action(info, TIMER_SCAN_PENDING_URBS); + return 0; + } + + ret = xenhcd_do_request(info, urbp); + if (ret == 0) + list_add_tail(&urbp->list, &info->in_progress_list); + + return ret; +} + +static int xenhcd_unlink_urb(struct xenhcd_info *info, struct urb_priv *urbp) +{ + int ret; + + /* already unlinked? */ + if (urbp->unlinked) + return -EBUSY; + + urbp->unlinked = true; + + /* the urb is still in pending_submit queue */ + if (urbp->req_id == ~0) { + list_move_tail(&urbp->list, &info->giveback_waiting_list); + xenhcd_timer_action(info, TIMER_SCAN_PENDING_URBS); + return 0; + } + + /* send unlink request to backend */ + if (RING_FULL(&info->urb_ring)) { + list_move_tail(&urbp->list, &info->pending_unlink_list); + xenhcd_timer_action(info, TIMER_RING_WATCHDOG); + return 0; + } + + if (!list_empty(&info->pending_unlink_list)) { + list_move_tail(&urbp->list, &info->pending_unlink_list); + xenhcd_timer_action(info, TIMER_SCAN_PENDING_URBS); + return 0; + } + + ret = xenhcd_do_request(info, urbp); + if (ret == 0) + list_move_tail(&urbp->list, &info->in_progress_list); + + return ret; +} + +static void xenhcd_res_to_urb(struct xenhcd_info *info, + struct xenusb_urb_response *res, struct urb *urb) +{ + if (unlikely(!urb)) + return; + + if (res->actual_length > urb->transfer_buffer_length) + urb->actual_length = urb->transfer_buffer_length; + else if (res->actual_length < 0) + urb->actual_length = 0; + else + urb->actual_length = res->actual_length; + urb->error_count = res->error_count; + urb->start_frame = res->start_frame; + xenhcd_giveback_urb(info, urb, res->status); +} + +static int xenhcd_urb_request_done(struct xenhcd_info *info, + unsigned int *eoiflag) +{ + struct xenusb_urb_response res; + RING_IDX i, rp; + __u16 id; + int more_to_do = 0; + unsigned long flags; + + spin_lock_irqsave(&info->lock, flags); + + rp = info->urb_ring.sring->rsp_prod; + if (RING_RESPONSE_PROD_OVERFLOW(&info->urb_ring, rp)) { + xenhcd_set_error(info, "Illegal index on urb-ring"); + goto err; + } + rmb(); /* ensure we see queued responses up to "rp" */ + + for (i = info->urb_ring.rsp_cons; i != rp; i++) { + RING_COPY_RESPONSE(&info->urb_ring, i, &res); + id = res.id; + if (id >= XENUSB_URB_RING_SIZE) { + xenhcd_set_error(info, "Illegal data on urb-ring"); + goto err; + } + + if (likely(xenusb_pipesubmit(info->shadow[id].req.pipe))) { + xenhcd_gnttab_done(info, id); + if (info->error) + goto err; + xenhcd_res_to_urb(info, &res, info->shadow[id].urb); + } + + xenhcd_add_id_to_freelist(info, id); + + *eoiflag = 0; + } + info->urb_ring.rsp_cons = i; + + if (i != info->urb_ring.req_prod_pvt) + RING_FINAL_CHECK_FOR_RESPONSES(&info->urb_ring, more_to_do); + else + info->urb_ring.sring->rsp_event = i + 1; + + spin_unlock_irqrestore(&info->lock, flags); + + return more_to_do; + + err: + spin_unlock_irqrestore(&info->lock, flags); + return 0; +} + +static int xenhcd_conn_notify(struct xenhcd_info *info, unsigned int *eoiflag) +{ + struct xenusb_conn_response res; + struct xenusb_conn_request *req; + RING_IDX rc, rp; + __u16 id; + __u8 portnum, speed; + int more_to_do = 0; + int notify; + int port_changed = 0; + unsigned long flags; + + spin_lock_irqsave(&info->lock, flags); + + rc = info->conn_ring.rsp_cons; + rp = info->conn_ring.sring->rsp_prod; + if (RING_RESPONSE_PROD_OVERFLOW(&info->conn_ring, rp)) { + xenhcd_set_error(info, "Illegal index on conn-ring"); + spin_unlock_irqrestore(&info->lock, flags); + return 0; + } + rmb(); /* ensure we see queued responses up to "rp" */ + + while (rc != rp) { + RING_COPY_RESPONSE(&info->conn_ring, rc, &res); + id = res.id; + portnum = res.portnum; + speed = res.speed; + info->conn_ring.rsp_cons = ++rc; + + if (xenhcd_rhport_connect(info, portnum, speed)) { + xenhcd_set_error(info, "Illegal data on conn-ring"); + spin_unlock_irqrestore(&info->lock, flags); + return 0; + } + + if (info->ports[portnum - 1].c_connection) + port_changed = 1; + + barrier(); + + req = RING_GET_REQUEST(&info->conn_ring, + info->conn_ring.req_prod_pvt); + req->id = id; + info->conn_ring.req_prod_pvt++; + + *eoiflag = 0; + } + + if (rc != info->conn_ring.req_prod_pvt) + RING_FINAL_CHECK_FOR_RESPONSES(&info->conn_ring, more_to_do); + else + info->conn_ring.sring->rsp_event = rc + 1; + + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->conn_ring, notify); + if (notify) + notify_remote_via_irq(info->irq); + + spin_unlock_irqrestore(&info->lock, flags); + + if (port_changed) + usb_hcd_poll_rh_status(xenhcd_info_to_hcd(info)); + + return more_to_do; +} + +static irqreturn_t xenhcd_int(int irq, void *dev_id) +{ + struct xenhcd_info *info = (struct xenhcd_info *)dev_id; + unsigned int eoiflag = XEN_EOI_FLAG_SPURIOUS; + + if (unlikely(info->error)) { + xen_irq_lateeoi(irq, XEN_EOI_FLAG_SPURIOUS); + return IRQ_HANDLED; + } + + while (xenhcd_urb_request_done(info, &eoiflag) | + xenhcd_conn_notify(info, &eoiflag)) + /* Yield point for this unbounded loop. */ + cond_resched(); + + xen_irq_lateeoi(irq, eoiflag); + return IRQ_HANDLED; +} + +static void xenhcd_destroy_rings(struct xenhcd_info *info) +{ + if (info->irq) + unbind_from_irqhandler(info->irq, info); + info->irq = 0; + + xenbus_teardown_ring((void **)&info->urb_ring.sring, 1, + &info->urb_ring_ref); + xenbus_teardown_ring((void **)&info->conn_ring.sring, 1, + &info->conn_ring_ref); +} + +static int xenhcd_setup_rings(struct xenbus_device *dev, + struct xenhcd_info *info) +{ + struct xenusb_urb_sring *urb_sring; + struct xenusb_conn_sring *conn_sring; + int err; + + info->conn_ring_ref = INVALID_GRANT_REF; + err = xenbus_setup_ring(dev, GFP_NOIO | __GFP_HIGH, + (void **)&urb_sring, 1, &info->urb_ring_ref); + if (err) { + xenbus_dev_fatal(dev, err, "allocating urb ring"); + return err; + } + XEN_FRONT_RING_INIT(&info->urb_ring, urb_sring, PAGE_SIZE); + + err = xenbus_setup_ring(dev, GFP_NOIO | __GFP_HIGH, + (void **)&conn_sring, 1, &info->conn_ring_ref); + if (err) { + xenbus_dev_fatal(dev, err, "allocating conn ring"); + goto fail; + } + XEN_FRONT_RING_INIT(&info->conn_ring, conn_sring, PAGE_SIZE); + + err = xenbus_alloc_evtchn(dev, &info->evtchn); + if (err) { + xenbus_dev_fatal(dev, err, "xenbus_alloc_evtchn"); + goto fail; + } + + err = bind_evtchn_to_irq_lateeoi(info->evtchn); + if (err <= 0) { + xenbus_dev_fatal(dev, err, "bind_evtchn_to_irq_lateeoi"); + goto fail; + } + + info->irq = err; + + err = request_threaded_irq(info->irq, NULL, xenhcd_int, + IRQF_ONESHOT, "xenhcd", info); + if (err) { + xenbus_dev_fatal(dev, err, "request_threaded_irq"); + goto free_irq; + } + + return 0; + +free_irq: + unbind_from_irqhandler(info->irq, info); +fail: + xenhcd_destroy_rings(info); + return err; +} + +static int xenhcd_talk_to_backend(struct xenbus_device *dev, + struct xenhcd_info *info) +{ + const char *message; + struct xenbus_transaction xbt; + int err; + + err = xenhcd_setup_rings(dev, info); + if (err) + return err; + +again: + err = xenbus_transaction_start(&xbt); + if (err) { + xenbus_dev_fatal(dev, err, "starting transaction"); + goto destroy_ring; + } + + err = xenbus_printf(xbt, dev->nodename, "urb-ring-ref", "%u", + info->urb_ring_ref); + if (err) { + message = "writing urb-ring-ref"; + goto abort_transaction; + } + + err = xenbus_printf(xbt, dev->nodename, "conn-ring-ref", "%u", + info->conn_ring_ref); + if (err) { + message = "writing conn-ring-ref"; + goto abort_transaction; + } + + err = xenbus_printf(xbt, dev->nodename, "event-channel", "%u", + info->evtchn); + if (err) { + message = "writing event-channel"; + goto abort_transaction; + } + + err = xenbus_transaction_end(xbt, 0); + if (err) { + if (err == -EAGAIN) + goto again; + xenbus_dev_fatal(dev, err, "completing transaction"); + goto destroy_ring; + } + + return 0; + +abort_transaction: + xenbus_transaction_end(xbt, 1); + xenbus_dev_fatal(dev, err, "%s", message); + +destroy_ring: + xenhcd_destroy_rings(info); + + return err; +} + +static int xenhcd_connect(struct xenbus_device *dev) +{ + struct xenhcd_info *info = dev_get_drvdata(&dev->dev); + struct xenusb_conn_request *req; + int idx, err; + int notify; + char name[TASK_COMM_LEN]; + struct usb_hcd *hcd; + + hcd = xenhcd_info_to_hcd(info); + snprintf(name, TASK_COMM_LEN, "xenhcd.%d", hcd->self.busnum); + + err = xenhcd_talk_to_backend(dev, info); + if (err) + return err; + + /* prepare ring for hotplug notification */ + for (idx = 0; idx < XENUSB_CONN_RING_SIZE; idx++) { + req = RING_GET_REQUEST(&info->conn_ring, idx); + req->id = idx; + } + info->conn_ring.req_prod_pvt = idx; + + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->conn_ring, notify); + if (notify) + notify_remote_via_irq(info->irq); + + return 0; +} + +static void xenhcd_disconnect(struct xenbus_device *dev) +{ + struct xenhcd_info *info = dev_get_drvdata(&dev->dev); + struct usb_hcd *hcd = xenhcd_info_to_hcd(info); + + usb_remove_hcd(hcd); + xenbus_frontend_closed(dev); +} + +static void xenhcd_watchdog(struct timer_list *timer) +{ + struct xenhcd_info *info = timer_container_of(info, timer, watchdog); + unsigned long flags; + + spin_lock_irqsave(&info->lock, flags); + if (likely(HC_IS_RUNNING(xenhcd_info_to_hcd(info)->state))) { + xenhcd_timer_action_done(info, TIMER_RING_WATCHDOG); + xenhcd_giveback_unlinked_urbs(info); + xenhcd_kick_pending_urbs(info); + } + spin_unlock_irqrestore(&info->lock, flags); +} + +/* + * one-time HC init + */ +static int xenhcd_setup(struct usb_hcd *hcd) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + + spin_lock_init(&info->lock); + INIT_LIST_HEAD(&info->pending_submit_list); + INIT_LIST_HEAD(&info->pending_unlink_list); + INIT_LIST_HEAD(&info->in_progress_list); + INIT_LIST_HEAD(&info->giveback_waiting_list); + timer_setup(&info->watchdog, xenhcd_watchdog, 0); + + hcd->has_tt = (hcd->driver->flags & HCD_MASK) != HCD_USB11; + + return 0; +} + +/* + * start HC running + */ +static int xenhcd_run(struct usb_hcd *hcd) +{ + hcd->uses_new_polling = 1; + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + hcd->state = HC_STATE_RUNNING; + return 0; +} + +/* + * stop running HC + */ +static void xenhcd_stop(struct usb_hcd *hcd) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + + timer_delete_sync(&info->watchdog); + spin_lock_irq(&info->lock); + /* cancel all urbs */ + hcd->state = HC_STATE_HALT; + xenhcd_cancel_all_enqueued_urbs(info); + xenhcd_giveback_unlinked_urbs(info); + spin_unlock_irq(&info->lock); +} + +/* + * called as .urb_enqueue() + * non-error returns are promise to giveback the urb later + */ +static int xenhcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + struct urb_priv *urbp; + unsigned long flags; + int ret; + + if (unlikely(info->error)) + return -ESHUTDOWN; + + urbp = kmem_cache_zalloc(xenhcd_urbp_cachep, mem_flags); + if (!urbp) + return -ENOMEM; + + spin_lock_irqsave(&info->lock, flags); + + urbp->urb = urb; + urb->hcpriv = urbp; + urbp->req_id = ~0; + urbp->unlink_req_id = ~0; + INIT_LIST_HEAD(&urbp->list); + urbp->status = 1; + urb->unlinked = false; + + ret = xenhcd_submit_urb(info, urbp); + + if (ret) + xenhcd_free_urb_priv(urbp); + + spin_unlock_irqrestore(&info->lock, flags); + + return ret; +} + +/* + * called as .urb_dequeue() + */ +static int xenhcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + struct urb_priv *urbp; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&info->lock, flags); + + urbp = urb->hcpriv; + if (urbp) { + urbp->status = status; + ret = xenhcd_unlink_urb(info, urbp); + } + + spin_unlock_irqrestore(&info->lock, flags); + + return ret; +} + +/* + * called from usb_get_current_frame_number(), + * but, almost all drivers not use such function. + */ +static int xenhcd_get_frame(struct usb_hcd *hcd) +{ + /* it means error, but probably no problem :-) */ + return 0; +} + +static const struct hc_driver xenhcd_usb20_hc_driver = { + .description = "xen-hcd", + .product_desc = "Xen USB2.0 Virtual Host Controller", + .hcd_priv_size = sizeof(struct xenhcd_info), + .flags = HCD_USB2, + + /* basic HC lifecycle operations */ + .reset = xenhcd_setup, + .start = xenhcd_run, + .stop = xenhcd_stop, + + /* managing urb I/O */ + .urb_enqueue = xenhcd_urb_enqueue, + .urb_dequeue = xenhcd_urb_dequeue, + .get_frame_number = xenhcd_get_frame, + + /* root hub operations */ + .hub_status_data = xenhcd_hub_status_data, + .hub_control = xenhcd_hub_control, +#ifdef CONFIG_PM + .bus_suspend = xenhcd_bus_suspend, + .bus_resume = xenhcd_bus_resume, +#endif +}; + +static const struct hc_driver xenhcd_usb11_hc_driver = { + .description = "xen-hcd", + .product_desc = "Xen USB1.1 Virtual Host Controller", + .hcd_priv_size = sizeof(struct xenhcd_info), + .flags = HCD_USB11, + + /* basic HC lifecycle operations */ + .reset = xenhcd_setup, + .start = xenhcd_run, + .stop = xenhcd_stop, + + /* managing urb I/O */ + .urb_enqueue = xenhcd_urb_enqueue, + .urb_dequeue = xenhcd_urb_dequeue, + .get_frame_number = xenhcd_get_frame, + + /* root hub operations */ + .hub_status_data = xenhcd_hub_status_data, + .hub_control = xenhcd_hub_control, +#ifdef CONFIG_PM + .bus_suspend = xenhcd_bus_suspend, + .bus_resume = xenhcd_bus_resume, +#endif +}; + +static struct usb_hcd *xenhcd_create_hcd(struct xenbus_device *dev) +{ + int i; + int err = 0; + int num_ports; + int usb_ver; + struct usb_hcd *hcd = NULL; + struct xenhcd_info *info; + + err = xenbus_scanf(XBT_NIL, dev->otherend, "num-ports", "%d", + &num_ports); + if (err != 1) { + xenbus_dev_fatal(dev, err, "reading num-ports"); + return ERR_PTR(-EINVAL); + } + if (num_ports < 1 || num_ports > XENUSB_MAX_PORTNR) { + xenbus_dev_fatal(dev, err, "invalid num-ports"); + return ERR_PTR(-EINVAL); + } + + err = xenbus_scanf(XBT_NIL, dev->otherend, "usb-ver", "%d", &usb_ver); + if (err != 1) { + xenbus_dev_fatal(dev, err, "reading usb-ver"); + return ERR_PTR(-EINVAL); + } + switch (usb_ver) { + case XENUSB_VER_USB11: + hcd = usb_create_hcd(&xenhcd_usb11_hc_driver, &dev->dev, + dev_name(&dev->dev)); + break; + case XENUSB_VER_USB20: + hcd = usb_create_hcd(&xenhcd_usb20_hc_driver, &dev->dev, + dev_name(&dev->dev)); + break; + default: + xenbus_dev_fatal(dev, err, "invalid usb-ver"); + return ERR_PTR(-EINVAL); + } + if (!hcd) { + xenbus_dev_fatal(dev, err, + "fail to allocate USB host controller"); + return ERR_PTR(-ENOMEM); + } + + info = xenhcd_hcd_to_info(hcd); + info->xbdev = dev; + info->rh_numports = num_ports; + + for (i = 0; i < XENUSB_URB_RING_SIZE; i++) { + info->shadow[i].req.id = i + 1; + info->shadow[i].urb = NULL; + info->shadow[i].in_flight = false; + } + info->shadow[XENUSB_URB_RING_SIZE - 1].req.id = 0x0fff; + + return hcd; +} + +static void xenhcd_backend_changed(struct xenbus_device *dev, + enum xenbus_state backend_state) +{ + switch (backend_state) { + case XenbusStateInitialising: + case XenbusStateReconfiguring: + case XenbusStateReconfigured: + case XenbusStateUnknown: + break; + + case XenbusStateInitWait: + case XenbusStateInitialised: + case XenbusStateConnected: + if (dev->state != XenbusStateInitialising) + break; + if (!xenhcd_connect(dev)) + xenbus_switch_state(dev, XenbusStateConnected); + break; + + case XenbusStateClosed: + if (dev->state == XenbusStateClosed) + break; + fallthrough; /* Missed the backend's Closing state. */ + case XenbusStateClosing: + xenhcd_disconnect(dev); + break; + + default: + xenbus_dev_fatal(dev, -EINVAL, "saw state %d at frontend", + backend_state); + break; + } +} + +static void xenhcd_remove(struct xenbus_device *dev) +{ + struct xenhcd_info *info = dev_get_drvdata(&dev->dev); + struct usb_hcd *hcd = xenhcd_info_to_hcd(info); + + xenhcd_destroy_rings(info); + usb_put_hcd(hcd); +} + +static int xenhcd_probe(struct xenbus_device *dev, + const struct xenbus_device_id *id) +{ + int err; + struct usb_hcd *hcd; + struct xenhcd_info *info; + + if (usb_disabled()) + return -ENODEV; + + hcd = xenhcd_create_hcd(dev); + if (IS_ERR(hcd)) { + err = PTR_ERR(hcd); + xenbus_dev_fatal(dev, err, + "fail to create usb host controller"); + return err; + } + + info = xenhcd_hcd_to_info(hcd); + dev_set_drvdata(&dev->dev, info); + + err = usb_add_hcd(hcd, 0, 0); + if (err) { + xenbus_dev_fatal(dev, err, "fail to add USB host controller"); + usb_put_hcd(hcd); + dev_set_drvdata(&dev->dev, NULL); + } + + return err; +} + +static const struct xenbus_device_id xenhcd_ids[] = { + { "vusb" }, + { "" }, +}; + +static struct xenbus_driver xenhcd_driver = { + .ids = xenhcd_ids, + .probe = xenhcd_probe, + .otherend_changed = xenhcd_backend_changed, + .remove = xenhcd_remove, +}; + +static int __init xenhcd_init(void) +{ + if (!xen_domain()) + return -ENODEV; + + xenhcd_urbp_cachep = kmem_cache_create("xenhcd_urb_priv", + sizeof(struct urb_priv), 0, 0, NULL); + if (!xenhcd_urbp_cachep) { + pr_err("xenhcd failed to create kmem cache\n"); + return -ENOMEM; + } + + return xenbus_register_frontend(&xenhcd_driver); +} +module_init(xenhcd_init); + +static void __exit xenhcd_exit(void) +{ + kmem_cache_destroy(xenhcd_urbp_cachep); + xenbus_unregister_driver(&xenhcd_driver); +} +module_exit(xenhcd_exit); + +MODULE_ALIAS("xen:vusb"); +MODULE_AUTHOR("Juergen Gross "); +MODULE_DESCRIPTION("Xen USB Virtual Host Controller driver (xen-hcd)"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h new file mode 100644 index 000000000..68c27831e --- /dev/null +++ b/drivers/usb/host/xhci-caps.h @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xHCI Host Controller Capability Registers. + * xHCI Specification Section 5.3, Revision 1.2. + */ + +#include +#include + +/* hc_capbase - bitmasks */ +/* bits 7:0 - Capability Registers Length */ +#define HC_LENGTH GENMASK(7, 0) +/* bits 15:8 - Rsvd */ +/* bits 31:16 - Host Controller Interface Version Number */ +#define HC_VERSION GENMASK(31, 16) + +/* HCSPARAMS1 - hcs_params1 - bitmasks */ +/* bits 7:0 - Number of Device Slots */ +#define HCS_SLOTS_MASK GENMASK(7, 0) +/* bits 18:8 - Number of Interrupters, max values is 1024 */ +#define HCS_MAX_INTRS GENMASK(18, 8) +/* bits 23:19 - Rsvd */ +/* bits 31:24 - Max Ports, max values is 255 */ +#define HCS_MAX_PORTS GENMASK(31, 24) + +/* HCSPARAMS2 - hcs_params2 - bitmasks */ +/* + * bits 3:0 - Isochronous Scheduling Threshold, frames or uframes that SW + * needs to queue transactions ahead of the HW to meet periodic deadlines. + * - Bits 2:0: Threshold value + * - Bit 3: Unit indicator + * - '1': Threshold in Frames + * - '0': Threshold in Microframes (uframes) + * Note: 1 Frame = 8 Microframes + * xHCI specification section 5.3.4. + */ +#define HCS_IST_VALUE GENMASK(2, 0) +#define HCS_IST_UNIT BIT(3) +/* bits 7:4 - Event Ring Segment Table Max, 2^(n) */ +#define HCS_ERST_MAX GENMASK(7, 4) +/* bits 20:8 - Rsvd */ +/* bits 25:21 - Max Scratchpad Buffers (Hi), 5 Most significant bits */ +#define HCS_MAX_SP_HI GENMASK(25, 21) +/* bit 26 - Scratchpad restore, for save/restore HW state */ +/* bits 31:27 - Max Scratchpad Buffers (Lo), 5 Least significant bits */ +#define HCS_MAX_SP_LO GENMASK(31, 27) +#define HCS_MAX_SCRATCHPAD(p) (FIELD_GET(HCS_MAX_SP_HI, (p)) << 5 | \ + FIELD_GET(HCS_MAX_SP_LO, (p))) + +/* HCSPARAMS3 - hcs_params3 - bitmasks */ +/* bits 7:0 - U1 Device Exit Latency, Max U1 to U0 latency for the roothub ports */ +#define HCS_U1_LATENCY GENMASK(7, 0) +/* bits 15:8 - Rsvd */ +/* bits 31:16 - U2 Device Exit Latency, Max U2 to U0 latency for the roothub ports */ +#define HCS_U2_LATENCY GENMASK(31, 16) + +/* HCCPARAMS1 - hcc_params - bitmasks */ +/* bit 0 - 64-bit Addressing Capability */ +#define HCC_64BIT_ADDR BIT(0) +/* bit 1 - BW Negotiation Capability */ +#define HCC_BANDWIDTH_NEG BIT(1) +/* bit 2 - Context Size */ +#define HCC_64BYTE_CONTEXT BIT(2) +#define CTX_SIZE(_hcc) (_hcc & HCC_64BYTE_CONTEXT ? 64 : 32) +/* bit 3 - Port Power Control */ +#define HCC_PPC BIT(3) +/* bit 4 - Port Indicators */ +#define HCS_INDICATOR BIT(4) +/* bit 5 - Light HC Reset Capability */ +#define HCC_LIGHT_RESET BIT(5) +/* bit 6 - Latency Tolerance Messaging Capability */ +#define HCC_LTC BIT(6) +/* bit 7 - No Secondary Stream ID Support */ +#define HCC_NSS BIT(7) +/* bit 8 - Parse All Event Data */ +/* bit 9 - Short Packet Capability */ +#define HCC_SPC BIT(9) +/* bit 10 - Stopped EDTLA Capability */ +/* bit 11 - Contiguous Frame ID Capability */ +#define HCC_CFC BIT(11) +/* bits 15:12 - Max size for Primary Stream Arrays, 2^(n+1) */ +#define HCC_MAX_PSA GENMASK(15, 12) +#define GET_MAX_PSA_SIZE(p) (1 << (FIELD_GET(HCC_MAX_PSA, (p)) + 1)) +/* bits 31:16 - xHCI Extended Capabilities Pointer, from PCI base: 2^(n) */ +#define HCC_EXT_CAPS GENMASK(31, 16) + +/* DBOFF - db_off - bitmasks */ +/* bits 1:0 - Rsvd */ +/* bits 31:2 - Doorbell Array Offset */ +#define DBOFF_MASK GENMASK(31, 2) + +/* RTSOFF - run_regs_off - bitmasks */ +/* bits 4:0 - Rsvd */ +/* bits 31:5 - Runtime Register Space Offse */ +#define RTSOFF_MASK GENMASK(31, 5) + +/* HCCPARAMS2 - hcc_params2 - bitmasks */ +/* bit 0 - U3 Entry Capability */ +#define HCC2_U3C BIT(0) +/* bit 1 - Configure Endpoint Command Max Exit Latency Too Large Capability */ +#define HCC2_CMC BIT(1) +/* bit 2 - Force Save Context Capabilitu */ +#define HCC2_FSC BIT(2) +/* bit 3 - Compliance Transition Capability, false: compliance is enabled by default */ +#define HCC2_CTC BIT(3) +/* bit 4 - Large ESIT Payload Capability, true: HC support ESIT payload > 48k */ +#define HCC2_LEC BIT(4) +/* bit 5 - Configuration Information Capability */ +#define HCC2_CIC BIT(5) +/* bit 6 - Extended TBC Capability, true: Isoc burst count > 65535 */ +#define HCC2_ETC BIT(6) +/* bit 7 - Extended TBC TRB Status Capability */ +#define HCC2_ETC_TSC BIT(7) +/* bit 8 - Get/Set Extended Property Capability */ +#define HCC2_GSC BIT(8) +/* bit 9 - Virtualization Based Trusted I/O Capability */ +#define HCC2_VTC BIT(9) +/* bit 10 - Rsvd */ +/* bit 11 - HC support Double BW on a eUSB2 HS ISOC EP */ +#define HCC2_EUSB2_DIC BIT(11) +/* bit 12 - HC support eUSB2V2 capability */ +#define HCC2_E2V2C BIT(12) +/* bits 31:13 - Rsvd */ diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c new file mode 100644 index 000000000..386abf266 --- /dev/null +++ b/drivers/usb/host/xhci-dbg.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + */ + +#include "xhci.h" + +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); + int state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); + + return xhci_slot_state_string(state); +} + +void xhci_dbg_trace(struct xhci_hcd *xhci, void (*trace)(struct va_format *), + const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + xhci_dbg(xhci, "%pV\n", &vaf); + trace(&vaf); + va_end(args); +} +EXPORT_SYMBOL_GPL(xhci_dbg_trace); diff --git a/drivers/usb/host/xhci-dbgcap.c b/drivers/usb/host/xhci-dbgcap.c new file mode 100644 index 000000000..48ee6a4f9 --- /dev/null +++ b/drivers/usb/host/xhci-dbgcap.c @@ -0,0 +1,1664 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xhci-dbgcap.c - xHCI debug capability support + * + * Copyright (C) 2017 Intel Corporation + * + * Author: Lu Baolu + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "xhci.h" +#include "xhci-trace.h" +#include "xhci-dbgcap.h" + +static const struct dbc_str dbc_str_default = { + .manufacturer = "Linux Foundation", + .product = "Linux USB Debug Target", + .serial = "0001", +}; + +static void dbc_free_ctx(struct device *dev, struct xhci_container_ctx *ctx) +{ + if (!ctx) + return; + dma_free_coherent(dev, ctx->size, ctx->bytes, ctx->dma); + kfree(ctx); +} + +/* we use only one segment for DbC rings */ +static void dbc_ring_free(struct device *dev, struct xhci_ring *ring) +{ + if (!ring) + return; + + if (ring->first_seg) { + dma_free_coherent(dev, TRB_SEGMENT_SIZE, + ring->first_seg->trbs, + ring->first_seg->dma); + kfree(ring->first_seg); + } + kfree(ring); +} + +static void xhci_dbc_init_ep_contexts(struct xhci_dbc *dbc) +{ + struct xhci_ep_ctx *ep_ctx; + unsigned int max_burst; + dma_addr_t deq; + + max_burst = DBC_CTRL_MAXBURST(readl(&dbc->regs->control)); + + /* Populate bulk out endpoint context: */ + ep_ctx = dbc_bulkout_ctx(dbc); + deq = dbc_bulkout_enq(dbc); + ep_ctx->ep_info = 0; + ep_ctx->ep_info2 = dbc_epctx_info2(BULK_OUT_EP, 1024, max_burst); + ep_ctx->deq = cpu_to_le64(deq | dbc->ring_out->cycle_state); + + /* Populate bulk in endpoint context: */ + ep_ctx = dbc_bulkin_ctx(dbc); + deq = dbc_bulkin_enq(dbc); + ep_ctx->ep_info = 0; + ep_ctx->ep_info2 = dbc_epctx_info2(BULK_IN_EP, 1024, max_burst); + ep_ctx->deq = cpu_to_le64(deq | dbc->ring_in->cycle_state); +} + +static u8 get_str_desc_len(const char *desc) +{ + return ((struct usb_string_descriptor *)desc)->bLength; +} + +static u32 dbc_prepare_info_context_str_len(struct dbc_str_descs *descs) +{ + u32 len; + + len = get_str_desc_len(descs->serial); + len <<= 8; + len += get_str_desc_len(descs->product); + len <<= 8; + len += get_str_desc_len(descs->manufacturer); + len <<= 8; + len += get_str_desc_len(descs->string0); + + return len; +} + +static int xhci_dbc_populate_str_desc(char *desc, const char *src) +{ + struct usb_string_descriptor *s_desc; + int len; + + s_desc = (struct usb_string_descriptor *)desc; + + /* len holds number of 2 byte UTF-16 characters */ + len = utf8s_to_utf16s(src, strlen(src), UTF16_LITTLE_ENDIAN, + (wchar_t *)s_desc->wData, USB_MAX_STRING_LEN * 2); + if (len < 0) + return len; + + s_desc->bLength = len * 2 + 2; + s_desc->bDescriptorType = USB_DT_STRING; + + return s_desc->bLength; +} + +static void xhci_dbc_populate_str_descs(struct dbc_str_descs *str_descs, + struct dbc_str *str) +{ + /* Serial string: */ + xhci_dbc_populate_str_desc(str_descs->serial, str->serial); + + /* Product string: */ + xhci_dbc_populate_str_desc(str_descs->product, str->product); + + /* Manufacturer string: */ + xhci_dbc_populate_str_desc(str_descs->manufacturer, str->manufacturer); + + /* String0: */ + str_descs->string0[0] = 4; + str_descs->string0[1] = USB_DT_STRING; + str_descs->string0[2] = 0x09; + str_descs->string0[3] = 0x04; +} + +static void xhci_dbc_init_contexts(struct xhci_dbc *dbc) +{ + struct dbc_info_context *info; + u32 dev_info; + dma_addr_t dma; + + if (!dbc) + return; + + /* Populate info Context: */ + info = (struct dbc_info_context *)dbc->ctx->bytes; + dma = dbc->str_descs_dma; + info->string0 = cpu_to_le64(dma); + info->manufacturer = cpu_to_le64(dma + USB_MAX_STRING_DESC_LEN); + info->product = cpu_to_le64(dma + USB_MAX_STRING_DESC_LEN * 2); + info->serial = cpu_to_le64(dma + USB_MAX_STRING_DESC_LEN * 3); + info->length = cpu_to_le32(dbc_prepare_info_context_str_len(dbc->str_descs)); + + /* Populate bulk in and out endpoint contexts: */ + xhci_dbc_init_ep_contexts(dbc); + + /* Set DbC context and info registers: */ + lo_hi_writeq(dbc->ctx->dma, &dbc->regs->dccp); + + dev_info = (dbc->idVendor << 16) | dbc->bInterfaceProtocol; + writel(dev_info, &dbc->regs->devinfo1); + + dev_info = (dbc->bcdDevice << 16) | dbc->idProduct; + writel(dev_info, &dbc->regs->devinfo2); +} + +static void xhci_dbc_giveback(struct dbc_request *req, int status) + __releases(&dbc->lock) + __acquires(&dbc->lock) +{ + struct xhci_dbc *dbc = req->dbc; + struct device *dev = dbc->dev; + + list_del_init(&req->list_pending); + req->trb_dma = 0; + req->trb = NULL; + + if (req->status == -EINPROGRESS) + req->status = status; + + trace_xhci_dbc_giveback_request(req); + + dma_unmap_single(dev, + req->dma, + req->length, + dbc_ep_dma_direction(req)); + + /* Give back the transfer request: */ + spin_unlock(&dbc->lock); + req->complete(dbc, req); + spin_lock(&dbc->lock); +} + +static void trb_to_noop(union xhci_trb *trb) +{ + trb->generic.field[0] = 0; + trb->generic.field[1] = 0; + trb->generic.field[2] = 0; + trb->generic.field[3] &= cpu_to_le32(TRB_CYCLE); + trb->generic.field[3] |= cpu_to_le32(TRB_TYPE(TRB_TR_NOOP)); +} + +static void xhci_dbc_flush_single_request(struct dbc_request *req) +{ + trb_to_noop(req->trb); + xhci_dbc_giveback(req, -ESHUTDOWN); +} + +static void xhci_dbc_flush_endpoint_requests(struct dbc_ep *dep) +{ + struct dbc_request *req, *tmp; + + list_for_each_entry_safe(req, tmp, &dep->list_pending, list_pending) + xhci_dbc_flush_single_request(req); +} + +static void xhci_dbc_flush_requests(struct xhci_dbc *dbc) +{ + xhci_dbc_flush_endpoint_requests(&dbc->eps[BULK_OUT]); + xhci_dbc_flush_endpoint_requests(&dbc->eps[BULK_IN]); +} + +struct dbc_request * +dbc_alloc_request(struct xhci_dbc *dbc, unsigned int direction, gfp_t flags) +{ + struct dbc_request *req; + + if (direction != BULK_IN && + direction != BULK_OUT) + return NULL; + + if (!dbc) + return NULL; + + req = kzalloc_obj(*req, flags); + if (!req) + return NULL; + + req->dbc = dbc; + INIT_LIST_HEAD(&req->list_pending); + INIT_LIST_HEAD(&req->list_pool); + req->direction = direction; + + trace_xhci_dbc_alloc_request(req); + + return req; +} + +void +dbc_free_request(struct dbc_request *req) +{ + trace_xhci_dbc_free_request(req); + + kfree(req); +} + +static void +xhci_dbc_queue_trb(struct xhci_ring *ring, u32 field1, + u32 field2, u32 field3, u32 field4) +{ + union xhci_trb *trb, *next; + + trb = ring->enqueue; + trb->generic.field[0] = cpu_to_le32(field1); + trb->generic.field[1] = cpu_to_le32(field2); + trb->generic.field[2] = cpu_to_le32(field3); + trb->generic.field[3] = cpu_to_le32(field4); + + trace_xhci_dbc_gadget_ep_queue(ring, &trb->generic, + xhci_trb_virt_to_dma(ring->enq_seg, + ring->enqueue)); + next = ++(ring->enqueue); + if (TRB_TYPE_LINK_LE32(next->link.control)) { + next->link.control ^= cpu_to_le32(TRB_CYCLE); + ring->enqueue = ring->enq_seg->trbs; + ring->cycle_state ^= 1; + } +} + +static int xhci_dbc_queue_bulk_tx(struct dbc_ep *dep, + struct dbc_request *req) +{ + u64 addr; + union xhci_trb *trb; + unsigned int num_trbs; + struct xhci_dbc *dbc = req->dbc; + struct xhci_ring *ring = dep->ring; + u32 length, control, cycle; + + num_trbs = count_trbs(req->dma, req->length); + WARN_ON(num_trbs != 1); + if (xhci_num_trbs_free(ring) <= num_trbs) + return -EBUSY; + + addr = req->dma; + trb = ring->enqueue; + cycle = ring->cycle_state; + length = TRB_LEN(req->length); + control = TRB_TYPE(TRB_NORMAL) | TRB_IOC; + + if (cycle) + control &= cpu_to_le32(~TRB_CYCLE); + else + control |= cpu_to_le32(TRB_CYCLE); + + req->trb = ring->enqueue; + req->trb_dma = xhci_trb_virt_to_dma(ring->enq_seg, ring->enqueue); + xhci_dbc_queue_trb(ring, + lower_32_bits(addr), + upper_32_bits(addr), + length, control); + + /* + * Add a barrier between writes of trb fields and flipping + * the cycle bit: + */ + wmb(); + + if (cycle) + trb->generic.field[3] |= cpu_to_le32(TRB_CYCLE); + else + trb->generic.field[3] &= cpu_to_le32(~TRB_CYCLE); + + writel(DBC_DOOR_BELL_TARGET(dep->direction), &dbc->regs->doorbell); + + return 0; +} + +static int +dbc_ep_do_queue(struct dbc_request *req) +{ + int ret; + struct xhci_dbc *dbc = req->dbc; + struct device *dev = dbc->dev; + struct dbc_ep *dep = &dbc->eps[req->direction]; + + if (!req->length || !req->buf) + return -EINVAL; + + req->actual = 0; + req->status = -EINPROGRESS; + + req->dma = dma_map_single(dev, + req->buf, + req->length, + dbc_ep_dma_direction(dep)); + if (dma_mapping_error(dev, req->dma)) { + dev_err(dbc->dev, "failed to map buffer\n"); + return -EFAULT; + } + + ret = xhci_dbc_queue_bulk_tx(dep, req); + if (ret) { + dev_err(dbc->dev, "failed to queue trbs\n"); + dma_unmap_single(dev, + req->dma, + req->length, + dbc_ep_dma_direction(dep)); + return -EFAULT; + } + + list_add_tail(&req->list_pending, &dep->list_pending); + + return 0; +} + +int dbc_ep_queue(struct dbc_request *req) +{ + unsigned long flags; + struct xhci_dbc *dbc = req->dbc; + int ret = -ESHUTDOWN; + + if (!dbc) + return -ENODEV; + + if (req->direction != BULK_IN && + req->direction != BULK_OUT) + return -EINVAL; + + spin_lock_irqsave(&dbc->lock, flags); + if (dbc->state == DS_CONFIGURED) + ret = dbc_ep_do_queue(req); + spin_unlock_irqrestore(&dbc->lock, flags); + + mod_delayed_work(system_percpu_wq, &dbc->event_work, 0); + + trace_xhci_dbc_queue_request(req); + + return ret; +} + +static inline void xhci_dbc_do_eps_init(struct xhci_dbc *dbc, bool direction) +{ + struct dbc_ep *dep; + + dep = &dbc->eps[direction]; + dep->dbc = dbc; + dep->direction = direction; + dep->ring = direction ? dbc->ring_in : dbc->ring_out; + + INIT_LIST_HEAD(&dep->list_pending); +} + +static void xhci_dbc_eps_init(struct xhci_dbc *dbc) +{ + xhci_dbc_do_eps_init(dbc, BULK_OUT); + xhci_dbc_do_eps_init(dbc, BULK_IN); +} + +static void xhci_dbc_eps_exit(struct xhci_dbc *dbc) +{ + memset(dbc->eps, 0, sizeof_field(struct xhci_dbc, eps)); +} + +static int dbc_erst_alloc(struct device *dev, struct xhci_ring *evt_ring, + struct xhci_erst *erst, gfp_t flags) +{ + erst->entries = dma_alloc_coherent(dev, sizeof(*erst->entries), + &erst->erst_dma_addr, flags); + if (!erst->entries) + return -ENOMEM; + + erst->num_entries = 1; + erst->entries[0].seg_addr = cpu_to_le64(evt_ring->first_seg->dma); + erst->entries[0].seg_size = cpu_to_le32(TRBS_PER_SEGMENT); + erst->entries[0].rsvd = 0; + return 0; +} + +static void dbc_erst_free(struct device *dev, struct xhci_erst *erst) +{ + dma_free_coherent(dev, sizeof(*erst->entries), erst->entries, + erst->erst_dma_addr); + erst->entries = NULL; +} + +static struct xhci_container_ctx * +dbc_alloc_ctx(struct device *dev, gfp_t flags) +{ + struct xhci_container_ctx *ctx; + + ctx = kzalloc_obj(*ctx, flags); + if (!ctx) + return NULL; + + /* xhci 7.6.9, all three contexts; info, ep-out and ep-in. Each 64 bytes*/ + ctx->size = 3 * DBC_CONTEXT_SIZE; + ctx->bytes = dma_alloc_coherent(dev, ctx->size, &ctx->dma, flags); + if (!ctx->bytes) { + kfree(ctx); + return NULL; + } + return ctx; +} + +static void xhci_dbc_ring_init(struct xhci_ring *ring) +{ + struct xhci_segment *seg = ring->first_seg; + + /* clear all trbs on ring in case of old ring */ + memset(seg->trbs, 0, TRB_SEGMENT_SIZE); + + /* Only event ring does not use link TRB */ + if (ring->type != TYPE_EVENT) { + union xhci_trb *trb = &seg->trbs[TRBS_PER_SEGMENT - 1]; + + trb->link.segment_ptr = cpu_to_le64(ring->first_seg->dma); + trb->link.control = cpu_to_le32(LINK_TOGGLE | TRB_TYPE(TRB_LINK)); + } + xhci_initialize_ring_info(ring); +} + +static int xhci_dbc_reinit_ep_rings(struct xhci_dbc *dbc) +{ + struct xhci_ring *in_ring = dbc->eps[BULK_IN].ring; + struct xhci_ring *out_ring = dbc->eps[BULK_OUT].ring; + + if (!in_ring || !out_ring || !dbc->ctx) { + dev_warn(dbc->dev, "Can't re-init unallocated endpoints\n"); + return -ENODEV; + } + + xhci_dbc_ring_init(in_ring); + xhci_dbc_ring_init(out_ring); + + /* set ep context enqueue, dequeue, and cycle to initial values */ + xhci_dbc_init_ep_contexts(dbc); + + return 0; +} + +static struct xhci_ring * +xhci_dbc_ring_alloc(struct device *dev, enum xhci_ring_type type, gfp_t flags) +{ + struct xhci_ring *ring; + struct xhci_segment *seg; + dma_addr_t dma; + + ring = kzalloc_obj(*ring, flags); + if (!ring) + return NULL; + + ring->num_segs = 1; + ring->type = type; + + seg = kzalloc_obj(*seg, flags); + if (!seg) + goto seg_fail; + + ring->first_seg = seg; + ring->last_seg = seg; + seg->next = seg; + + seg->trbs = dma_alloc_coherent(dev, TRB_SEGMENT_SIZE, &dma, flags); + if (!seg->trbs) + goto dma_fail; + + seg->dma = dma; + + INIT_LIST_HEAD(&ring->td_list); + + xhci_dbc_ring_init(ring); + + return ring; +dma_fail: + kfree(seg); +seg_fail: + kfree(ring); + return NULL; +} + +static int xhci_dbc_mem_init(struct xhci_dbc *dbc, gfp_t flags) +{ + int ret; + dma_addr_t deq; + struct device *dev = dbc->dev; + + /* Allocate various rings for events and transfers: */ + dbc->ring_evt = xhci_dbc_ring_alloc(dev, TYPE_EVENT, flags); + if (!dbc->ring_evt) + goto evt_fail; + + dbc->ring_in = xhci_dbc_ring_alloc(dev, TYPE_BULK, flags); + if (!dbc->ring_in) + goto in_fail; + + dbc->ring_out = xhci_dbc_ring_alloc(dev, TYPE_BULK, flags); + if (!dbc->ring_out) + goto out_fail; + + /* Allocate and populate ERST: */ + ret = dbc_erst_alloc(dev, dbc->ring_evt, &dbc->erst, flags); + if (ret) + goto erst_fail; + + /* Allocate context data structure: */ + dbc->ctx = dbc_alloc_ctx(dev, flags); /* was sysdev, and is still */ + if (!dbc->ctx) + goto ctx_fail; + + /* Allocate the string table: */ + dbc->str_descs_size = sizeof(*dbc->str_descs); + dbc->str_descs = dma_alloc_coherent(dev, dbc->str_descs_size, + &dbc->str_descs_dma, flags); + if (!dbc->str_descs) + goto str_descs_fail; + + /* Setup ERST register: */ + writel(dbc->erst.num_entries, &dbc->regs->ersts); + + lo_hi_writeq(dbc->erst.erst_dma_addr, &dbc->regs->erstba); + deq = xhci_trb_virt_to_dma(dbc->ring_evt->deq_seg, + dbc->ring_evt->dequeue); + lo_hi_writeq(deq, &dbc->regs->erdp); + + /* Setup string descriptors and contexts: */ + xhci_dbc_populate_str_descs(dbc->str_descs, &dbc->str); + xhci_dbc_init_contexts(dbc); + + xhci_dbc_eps_init(dbc); + dbc->state = DS_INITIALIZED; + + return 0; + +str_descs_fail: + dbc_free_ctx(dev, dbc->ctx); + dbc->ctx = NULL; +ctx_fail: + dbc_erst_free(dev, &dbc->erst); +erst_fail: + dbc_ring_free(dev, dbc->ring_out); + dbc->ring_out = NULL; +out_fail: + dbc_ring_free(dev, dbc->ring_in); + dbc->ring_in = NULL; +in_fail: + dbc_ring_free(dev, dbc->ring_evt); + dbc->ring_evt = NULL; +evt_fail: + return -ENOMEM; +} + +static void xhci_dbc_mem_cleanup(struct xhci_dbc *dbc) +{ + if (!dbc) + return; + + xhci_dbc_eps_exit(dbc); + + dma_free_coherent(dbc->dev, dbc->str_descs_size, dbc->str_descs, dbc->str_descs_dma); + dbc->str_descs = NULL; + + dbc_free_ctx(dbc->dev, dbc->ctx); + dbc->ctx = NULL; + + dbc_erst_free(dbc->dev, &dbc->erst); + dbc_ring_free(dbc->dev, dbc->ring_out); + dbc_ring_free(dbc->dev, dbc->ring_in); + dbc_ring_free(dbc->dev, dbc->ring_evt); + dbc->ring_in = NULL; + dbc->ring_out = NULL; + dbc->ring_evt = NULL; +} + +static int xhci_dbc_enable_dce(struct xhci_dbc *dbc, bool enable) +{ + u32 done_state = 0; + u32 ctrl = 0; + + if (enable) { + ctrl = readl(&dbc->regs->control); + ctrl |= DBC_CTRL_DBC_ENABLE | DBC_CTRL_PORT_ENABLE; + done_state = DBC_CTRL_DBC_ENABLE; + } + + writel(ctrl, &dbc->regs->control); + return xhci_handshake(&dbc->regs->control, DBC_CTRL_DBC_ENABLE, + done_state, 1000); +} + +static void xhci_dbc_set_state(struct xhci_dbc *dbc, enum dbc_state new_state) +{ + if (dbc->state == new_state) + return; + + switch (new_state) { + case DS_ENABLED: + /* + * DbC pm usage is 1 here, both when moved from disconnect or + * configured states, or when setting initial DbC enable state. + * Just enable pending put + */ + dev_dbg(dbc->dev, "DbC set pending_rpm_put = 1\n"); + dbc->pending_rpm_put = 1; + break; + case DS_CONNECTED: + if (dbc->pending_rpm_put) + /* DbC pm usage still 1, just remove pending put */ + dbc->pending_rpm_put = 0; + else + /* DbC pm usage was put to 0, call get */ + pm_runtime_get(dbc->dev); + break; + default: + break; + } + + dbc->state_timestamp = jiffies; + dbc->state = new_state; +} + +static int xhci_do_dbc_start(struct xhci_dbc *dbc) +{ + int ret; + + if (dbc->state != DS_DISABLED) + return -EINVAL; + + ret = xhci_dbc_enable_dce(dbc, false); + if (ret) + return ret; + + ret = xhci_dbc_mem_init(dbc, GFP_ATOMIC); + if (ret) + return ret; + + ret = xhci_dbc_enable_dce(dbc, true); + if (ret) + return ret; + + xhci_dbc_set_state(dbc, DS_ENABLED); + + return 0; +} + +static int xhci_dbc_start(struct xhci_dbc *dbc) +{ + int ret; + unsigned long flags; + + WARN_ON(!dbc); + + pm_runtime_get(dbc->dev); + + spin_lock_irqsave(&dbc->lock, flags); + ret = xhci_do_dbc_start(dbc); + if (ret) + goto err_unlock; + + spin_unlock_irqrestore(&dbc->lock, flags); + + mod_delayed_work(system_percpu_wq, &dbc->event_work, + msecs_to_jiffies(dbc->poll_interval)); + + return 0; + +err_unlock: + + spin_unlock_irqrestore(&dbc->lock, flags); + pm_runtime_put(dbc->dev); /* note this was self.controller */ + + return ret; +} + +static void xhci_dbc_stop(struct xhci_dbc *dbc) +{ + unsigned long flags; + bool need_rpm_put = false; + + WARN_ON(!dbc); + + spin_lock(&dbc->lock); + + switch (dbc->state) { + case DS_DISABLED: + spin_unlock(&dbc->lock); + return; + case DS_CONFIGURED: + xhci_dbc_flush_requests(dbc); + spin_unlock(&dbc->lock); + + if (dbc->driver->disconnect) + dbc->driver->disconnect(dbc); + break; + default: + spin_unlock(&dbc->lock); + break; + } + + cancel_delayed_work_sync(&dbc->event_work); + + spin_lock_irqsave(&dbc->lock, flags); + writel(0, &dbc->regs->control); + + if (dbc->state == DS_CONNECTED || dbc->state == DS_CONFIGURED || + dbc->pending_rpm_put) + need_rpm_put = true; + + dbc->pending_rpm_put = 0; + + xhci_dbc_set_state(dbc, DS_DISABLED); + spin_unlock_irqrestore(&dbc->lock, flags); + + xhci_dbc_mem_cleanup(dbc); + + if (need_rpm_put) + pm_runtime_put(dbc->dev); +} + +static void +handle_ep_halt_changes(struct xhci_dbc *dbc, struct dbc_ep *dep, bool halted) +{ + if (halted) { + dev_info(dbc->dev, "DbC Endpoint halted\n"); + dep->halted = 1; + + } else if (dep->halted) { + dev_info(dbc->dev, "DbC Endpoint halt cleared\n"); + dep->halted = 0; + + if (!list_empty(&dep->list_pending)) + writel(DBC_DOOR_BELL_TARGET(dep->direction), + &dbc->regs->doorbell); + } +} + +static void +dbc_handle_port_status(struct xhci_dbc *dbc, union xhci_trb *event) +{ + u32 portsc; + + portsc = readl(&dbc->regs->portsc); + if (portsc & DBC_PORTSC_CONN_CHANGE) + dev_info(dbc->dev, "DbC port connect change\n"); + + if (portsc & DBC_PORTSC_RESET_CHANGE) + dev_info(dbc->dev, "DbC port reset change\n"); + + if (portsc & DBC_PORTSC_LINK_CHANGE) + dev_info(dbc->dev, "DbC port link status change\n"); + + if (portsc & DBC_PORTSC_CONFIG_CHANGE) + dev_info(dbc->dev, "DbC config error change\n"); + + /* Port reset change bit will be cleared in other place: */ + writel(portsc & ~DBC_PORTSC_RESET_CHANGE, &dbc->regs->portsc); +} + +static void dbc_handle_xfer_event(struct xhci_dbc *dbc, union xhci_trb *event) +{ + struct dbc_ep *dep; + struct xhci_ring *ring; + int ep_id; + int status; + struct xhci_ep_ctx *ep_ctx; + u32 comp_code; + size_t remain_length; + struct dbc_request *req = NULL, *r; + + comp_code = GET_COMP_CODE(le32_to_cpu(event->generic.field[2])); + remain_length = EVENT_TRB_LEN(le32_to_cpu(event->generic.field[2])); + ep_id = TRB_TO_EP_ID(le32_to_cpu(event->generic.field[3])); + dep = (ep_id == EPID_OUT) ? + get_out_ep(dbc) : get_in_ep(dbc); + ep_ctx = (ep_id == EPID_OUT) ? + dbc_bulkout_ctx(dbc) : dbc_bulkin_ctx(dbc); + ring = dep->ring; + + /* Match the pending request: */ + list_for_each_entry(r, &dep->list_pending, list_pending) { + if (r->trb_dma == event->trans_event.buffer) { + req = r; + break; + } + if (r->status == -COMP_STALL_ERROR) { + dev_warn(dbc->dev, "Give back stale stalled req\n"); + xhci_dbc_giveback(r, 0); + } + } + + if (!req) { + dev_warn(dbc->dev, "no matched request\n"); + return; + } + + trace_xhci_dbc_handle_transfer(ring, &req->trb->generic, req->trb_dma); + + switch (comp_code) { + case COMP_SUCCESS: + remain_length = 0; + fallthrough; + case COMP_SHORT_PACKET: + status = 0; + break; + case COMP_TRB_ERROR: + case COMP_BABBLE_DETECTED_ERROR: + case COMP_USB_TRANSACTION_ERROR: + dev_warn(dbc->dev, "tx error %d detected\n", comp_code); + status = -comp_code; + break; + case COMP_STALL_ERROR: + dev_warn(dbc->dev, "Stall error at bulk TRB %llx, remaining %zu, ep deq %llx\n", + event->trans_event.buffer, remain_length, ep_ctx->deq); + status = 0; + dep->halted = 1; + + /* + * xHC DbC may trigger a STALL bulk xfer event when host sends a + * ClearFeature(ENDPOINT_HALT) request even if there wasn't an + * active bulk transfer. + * + * Don't give back this transfer request as hardware will later + * start processing TRBs starting from this 'STALLED' TRB, + * causing TRBs and requests to be out of sync. + * + * If STALL event shows some bytes were transferred then assume + * it's an actual transfer issue and give back the request. + * In this case mark the TRB as No-Op to avoid hw from using the + * TRB again. + */ + + if ((ep_ctx->deq & ~TRB_CYCLE) == event->trans_event.buffer) { + dev_dbg(dbc->dev, "Ep stopped on Stalled TRB\n"); + if (remain_length == req->length) { + dev_dbg(dbc->dev, "Spurious stall event, keep req\n"); + req->status = -COMP_STALL_ERROR; + req->actual = 0; + return; + } + dev_dbg(dbc->dev, "Give back stalled req, but turn TRB to No-op\n"); + trb_to_noop(req->trb); + } + break; + + default: + dev_err(dbc->dev, "unknown tx error %d\n", comp_code); + status = -comp_code; + break; + } + + req->actual = req->length - remain_length; + xhci_dbc_giveback(req, status); +} + +static void inc_evt_deq(struct xhci_ring *ring) +{ + /* If on the last TRB of the segment go back to the beginning */ + if (ring->dequeue == &ring->deq_seg->trbs[TRBS_PER_SEGMENT - 1]) { + ring->cycle_state ^= 1; + ring->dequeue = ring->deq_seg->trbs; + return; + } + ring->dequeue++; +} + +static enum evtreturn xhci_dbc_do_handle_events(struct xhci_dbc *dbc) +{ + dma_addr_t deq; + union xhci_trb *evt; + enum evtreturn ret = EVT_DONE; + u32 ctrl, portsc; + bool update_erdp = false; + + /* DbC state machine: */ + switch (dbc->state) { + case DS_DISABLED: + case DS_INITIALIZED: + + return EVT_ERR; + case DS_ENABLED: + portsc = readl(&dbc->regs->portsc); + ctrl = readl(&dbc->regs->control); + + if (portsc & DBC_PORTSC_CONN_STATUS) { + xhci_dbc_set_state(dbc, DS_CONNECTED); + dev_info(dbc->dev, "DbC connected\n"); + } else if (!(ctrl & DBC_CTRL_DBC_ENABLE)) { + dev_err(dbc->dev, "unexpected DbC disable, xHC reset?\n"); + } else if (dbc->pending_rpm_put && + time_is_before_jiffies(dbc->state_timestamp + + msecs_to_jiffies(DBC_AUTOSUSPEND_DELAY))) { + dbc->pending_rpm_put = 0; + dev_dbg(dbc->dev, "DbC Enabled state for 15 seconds, allow rpm suspend\n"); + pm_runtime_put(dbc->dev); + } + + return EVT_DONE; + case DS_CONNECTED: + ctrl = readl(&dbc->regs->control); + portsc = readl(&dbc->regs->portsc); + if (ctrl & DBC_CTRL_DBC_RUN) { + xhci_dbc_set_state(dbc, DS_CONFIGURED); + dev_info(dbc->dev, "DbC configured\n"); + writel(portsc, &dbc->regs->portsc); + ret = EVT_GSER; + break; + } + + /* Connection lost */ + if (!(portsc & DBC_PORTSC_CONN_STATUS)) { + /* covers DCE == 0 as it also sets CONN_STATUS to 0 */ + dev_warn(dbc->dev, "DbC connection lost mid enumeration\n"); + xhci_dbc_set_state(dbc, DS_ENABLED); + + return EVT_DONE; + } + + /* Enumeration timeout */ + if (time_is_before_jiffies(dbc->state_timestamp + + msecs_to_jiffies(DBC_ENUMERATION_TIMEOUT))) { + dev_err(dbc->dev, "DbC enumeration timeout, re-enabling DbC\n"); + dev_dbg(dbc->dev, "dcctrl %x, dcportsc %x\n", ctrl, portsc); + + /* Toggle DCE to retry enumeration */ + ret = xhci_dbc_enable_dce(dbc, false); + udelay(100); + ret = xhci_dbc_enable_dce(dbc, true); + xhci_dbc_set_state(dbc, DS_ENABLED); + } + + return EVT_DONE; + case DS_CONFIGURED: + /* Handle cable unplug event: */ + portsc = readl(&dbc->regs->portsc); + if (!(portsc & DBC_PORTSC_PORT_ENABLED) && + !(portsc & DBC_PORTSC_CONN_STATUS)) { + dev_info(dbc->dev, "DbC cable unplugged\n"); + xhci_dbc_set_state(dbc, DS_ENABLED); + xhci_dbc_flush_requests(dbc); + xhci_dbc_reinit_ep_rings(dbc); + return EVT_DISC; + } + + /* Handle debug port reset event: */ + if (portsc & DBC_PORTSC_RESET_CHANGE) { + dev_info(dbc->dev, "DbC port reset\n"); + writel(portsc, &dbc->regs->portsc); + xhci_dbc_set_state(dbc, DS_ENABLED); + xhci_dbc_flush_requests(dbc); + xhci_dbc_reinit_ep_rings(dbc); + return EVT_DISC; + } + + /* Check and handle changes in endpoint halt status */ + ctrl = readl(&dbc->regs->control); + handle_ep_halt_changes(dbc, get_in_ep(dbc), ctrl & DBC_CTRL_HALT_IN_TR); + handle_ep_halt_changes(dbc, get_out_ep(dbc), ctrl & DBC_CTRL_HALT_OUT_TR); + + /* Clear DbC run change bit: */ + if (ctrl & DBC_CTRL_DBC_RUN_CHANGE) { + writel(ctrl, &dbc->regs->control); + ctrl = readl(&dbc->regs->control); + } + break; + default: + dev_err(dbc->dev, "Unknown DbC state %d\n", dbc->state); + break; + } + + /* Handle the events in the event ring: */ + evt = dbc->ring_evt->dequeue; + while ((le32_to_cpu(evt->event_cmd.flags) & TRB_CYCLE) == + dbc->ring_evt->cycle_state) { + /* + * Add a barrier between reading the cycle flag and any + * reads of the event's flags/data below: + */ + rmb(); + + trace_xhci_dbc_handle_event(dbc->ring_evt, &evt->generic, + xhci_trb_virt_to_dma(dbc->ring_evt->deq_seg, + dbc->ring_evt->dequeue)); + + switch (le32_to_cpu(evt->event_cmd.flags) & TRB_TYPE_BITMASK) { + case TRB_TYPE(TRB_PORT_STATUS): + dbc_handle_port_status(dbc, evt); + break; + case TRB_TYPE(TRB_TRANSFER): + dbc_handle_xfer_event(dbc, evt); + if (ret != EVT_GSER) + ret = EVT_XFER_DONE; + break; + default: + break; + } + + inc_evt_deq(dbc->ring_evt); + + evt = dbc->ring_evt->dequeue; + update_erdp = true; + } + + /* Update event ring dequeue pointer: */ + if (update_erdp) { + deq = xhci_trb_virt_to_dma(dbc->ring_evt->deq_seg, + dbc->ring_evt->dequeue); + lo_hi_writeq(deq, &dbc->regs->erdp); + } + + return ret; +} + +static void xhci_dbc_handle_events(struct work_struct *work) +{ + enum evtreturn evtr; + struct xhci_dbc *dbc; + unsigned long flags; + unsigned int poll_interval; + unsigned long busypoll_timelimit; + + dbc = container_of(to_delayed_work(work), struct xhci_dbc, event_work); + poll_interval = dbc->poll_interval; + + spin_lock_irqsave(&dbc->lock, flags); + evtr = xhci_dbc_do_handle_events(dbc); + spin_unlock_irqrestore(&dbc->lock, flags); + + switch (evtr) { + case EVT_GSER: + if (dbc->driver->configure) + dbc->driver->configure(dbc); + break; + case EVT_DISC: + if (dbc->driver->disconnect) + dbc->driver->disconnect(dbc); + break; + case EVT_DONE: + /* + * Set fast poll rate if there are pending out transfers, or + * a transfer was recently processed + */ + busypoll_timelimit = dbc->xfer_timestamp + + msecs_to_jiffies(DBC_XFER_INACTIVITY_TIMEOUT); + + if (!list_empty(&dbc->eps[BULK_OUT].list_pending) || + time_is_after_jiffies(busypoll_timelimit)) + poll_interval = 0; + break; + case EVT_XFER_DONE: + dbc->xfer_timestamp = jiffies; + poll_interval = 0; + break; + default: + dev_info(dbc->dev, "stop handling dbc events\n"); + return; + } + + mod_delayed_work(system_percpu_wq, &dbc->event_work, + msecs_to_jiffies(poll_interval)); +} + +static const char * const dbc_state_strings[DS_MAX] = { + [DS_DISABLED] = "disabled", + [DS_INITIALIZED] = "initialized", + [DS_ENABLED] = "enabled", + [DS_CONNECTED] = "connected", + [DS_CONFIGURED] = "configured", +}; + +static ssize_t dbc_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct xhci_dbc *dbc; + struct xhci_hcd *xhci; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + dbc = xhci->dbc; + + if (dbc->state >= ARRAY_SIZE(dbc_state_strings)) + return sysfs_emit(buf, "unknown\n"); + + if (dbc->resume_required) + return sysfs_emit(buf, "suspended\n"); + + return sysfs_emit(buf, "%s\n", dbc_state_strings[dbc->state]); +} + +static ssize_t dbc_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct xhci_hcd *xhci; + struct xhci_dbc *dbc; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + dbc = xhci->dbc; + + if (sysfs_streq(buf, "enable")) { + pm_runtime_get_sync(dbc->dev); + + mutex_lock(&dbc->enable_mutex); + /* + * DbC may already be enabled here if xhci was suspended with + * dbc->resume_required set, and resumed by pm_runtime_get_sync() + * above. In this case we end up calling xhci_dbc_start() twice, + * second time returns an error but is harmless + */ + xhci_dbc_start(dbc); + + mutex_unlock(&dbc->enable_mutex); + pm_runtime_put(dbc->dev); + } else if (sysfs_streq(buf, "disable")) { + mutex_lock(&dbc->enable_mutex); + + dbc->resume_required = 0; + xhci_dbc_stop(dbc); + + mutex_unlock(&dbc->enable_mutex); + } else { + return -EINVAL; + } + + return count; +} + +static ssize_t dbc_idVendor_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct xhci_dbc *dbc; + struct xhci_hcd *xhci; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + dbc = xhci->dbc; + + return sysfs_emit(buf, "%04x\n", dbc->idVendor); +} + +static ssize_t dbc_idVendor_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct xhci_dbc *dbc; + struct xhci_hcd *xhci; + void __iomem *ptr; + u16 value; + u32 dev_info; + int ret; + + ret = kstrtou16(buf, 0, &value); + if (ret) + return ret; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + dbc = xhci->dbc; + if (dbc->state != DS_DISABLED) + return -EBUSY; + + dbc->idVendor = value; + ptr = &dbc->regs->devinfo1; + dev_info = readl(ptr); + dev_info = (dev_info & ~(0xffffu << 16)) | (value << 16); + writel(dev_info, ptr); + + return size; +} + +static ssize_t dbc_idProduct_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct xhci_dbc *dbc; + struct xhci_hcd *xhci; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + dbc = xhci->dbc; + + return sysfs_emit(buf, "%04x\n", dbc->idProduct); +} + +static ssize_t dbc_idProduct_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct xhci_dbc *dbc; + struct xhci_hcd *xhci; + void __iomem *ptr; + u32 dev_info; + u16 value; + int ret; + + ret = kstrtou16(buf, 0, &value); + if (ret) + return ret; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + dbc = xhci->dbc; + if (dbc->state != DS_DISABLED) + return -EBUSY; + + dbc->idProduct = value; + ptr = &dbc->regs->devinfo2; + dev_info = readl(ptr); + dev_info = (dev_info & ~(0xffffu)) | value; + writel(dev_info, ptr); + return size; +} + +static ssize_t dbc_bcdDevice_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct xhci_dbc *dbc; + struct xhci_hcd *xhci; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + dbc = xhci->dbc; + + return sysfs_emit(buf, "%04x\n", dbc->bcdDevice); +} + +static ssize_t dbc_bcdDevice_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct xhci_dbc *dbc; + struct xhci_hcd *xhci; + void __iomem *ptr; + u32 dev_info; + u16 value; + int ret; + + ret = kstrtou16(buf, 0, &value); + if (ret) + return ret; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + dbc = xhci->dbc; + if (dbc->state != DS_DISABLED) + return -EBUSY; + + dbc->bcdDevice = value; + ptr = &dbc->regs->devinfo2; + dev_info = readl(ptr); + dev_info = (dev_info & ~(0xffffu << 16)) | (value << 16); + writel(dev_info, ptr); + + return size; +} + +static ssize_t dbc_manufacturer_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct xhci_hcd *xhci = hcd_to_xhci(dev_get_drvdata(dev)); + struct xhci_dbc *dbc = xhci->dbc; + + return sysfs_emit(buf, "%s\n", dbc->str.manufacturer); +} + +static ssize_t dbc_manufacturer_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct xhci_hcd *xhci = hcd_to_xhci(dev_get_drvdata(dev)); + struct xhci_dbc *dbc = xhci->dbc; + size_t len; + + if (dbc->state != DS_DISABLED) + return -EBUSY; + + len = strcspn(buf, "\n"); + if (!len) + return -EINVAL; + + if (len > USB_MAX_STRING_LEN) + return -E2BIG; + + memcpy(dbc->str.manufacturer, buf, len); + dbc->str.manufacturer[len] = '\0'; + + return size; +} + +static ssize_t dbc_product_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct xhci_hcd *xhci = hcd_to_xhci(dev_get_drvdata(dev)); + struct xhci_dbc *dbc = xhci->dbc; + + return sysfs_emit(buf, "%s\n", dbc->str.product); +} + +static ssize_t dbc_product_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct xhci_hcd *xhci = hcd_to_xhci(dev_get_drvdata(dev)); + struct xhci_dbc *dbc = xhci->dbc; + size_t len; + + if (dbc->state != DS_DISABLED) + return -EBUSY; + + len = strcspn(buf, "\n"); + if (!len) + return -EINVAL; + + if (len > USB_MAX_STRING_LEN) + return -E2BIG; + + memcpy(dbc->str.product, buf, len); + dbc->str.product[len] = '\0'; + + return size; +} + +static ssize_t dbc_serial_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct xhci_hcd *xhci = hcd_to_xhci(dev_get_drvdata(dev)); + struct xhci_dbc *dbc = xhci->dbc; + + return sysfs_emit(buf, "%s\n", dbc->str.serial); +} + +static ssize_t dbc_serial_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct xhci_hcd *xhci = hcd_to_xhci(dev_get_drvdata(dev)); + struct xhci_dbc *dbc = xhci->dbc; + size_t len; + + if (dbc->state != DS_DISABLED) + return -EBUSY; + + len = strcspn(buf, "\n"); + if (!len) + return -EINVAL; + + if (len > USB_MAX_STRING_LEN) + return -E2BIG; + + memcpy(dbc->str.serial, buf, len); + dbc->str.serial[len] = '\0'; + + return size; +} + +static ssize_t dbc_bInterfaceProtocol_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct xhci_dbc *dbc; + struct xhci_hcd *xhci; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + dbc = xhci->dbc; + + return sysfs_emit(buf, "%02x\n", dbc->bInterfaceProtocol); +} + +static ssize_t dbc_bInterfaceProtocol_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct xhci_dbc *dbc; + struct xhci_hcd *xhci; + void __iomem *ptr; + u32 dev_info; + u8 value; + int ret; + + /* bInterfaceProtocol is 8 bit, but... */ + ret = kstrtou8(buf, 0, &value); + if (ret) + return ret; + + /* ...xhci only supports values 0 and 1 */ + if (value > 1) + return -EINVAL; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + dbc = xhci->dbc; + if (dbc->state != DS_DISABLED) + return -EBUSY; + + dbc->bInterfaceProtocol = value; + ptr = &dbc->regs->devinfo1; + dev_info = readl(ptr); + dev_info = (dev_info & ~(0xffu)) | value; + writel(dev_info, ptr); + + return size; +} + +static ssize_t dbc_poll_interval_ms_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct xhci_dbc *dbc; + struct xhci_hcd *xhci; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + dbc = xhci->dbc; + + return sysfs_emit(buf, "%u\n", dbc->poll_interval); +} + +static ssize_t dbc_poll_interval_ms_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct xhci_dbc *dbc; + struct xhci_hcd *xhci; + u32 value; + int ret; + + ret = kstrtou32(buf, 0, &value); + if (ret || value > DBC_POLL_INTERVAL_MAX) + return -EINVAL; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + dbc = xhci->dbc; + + dbc->poll_interval = value; + + mod_delayed_work(system_percpu_wq, &dbc->event_work, 0); + + return size; +} + +static DEVICE_ATTR_RW(dbc); +static DEVICE_ATTR_RW(dbc_idVendor); +static DEVICE_ATTR_RW(dbc_idProduct); +static DEVICE_ATTR_RW(dbc_bcdDevice); +static DEVICE_ATTR_RW(dbc_serial); +static DEVICE_ATTR_RW(dbc_product); +static DEVICE_ATTR_RW(dbc_manufacturer); +static DEVICE_ATTR_RW(dbc_bInterfaceProtocol); +static DEVICE_ATTR_RW(dbc_poll_interval_ms); + +static struct attribute *dbc_dev_attrs[] = { + &dev_attr_dbc.attr, + &dev_attr_dbc_idVendor.attr, + &dev_attr_dbc_idProduct.attr, + &dev_attr_dbc_bcdDevice.attr, + &dev_attr_dbc_serial.attr, + &dev_attr_dbc_product.attr, + &dev_attr_dbc_manufacturer.attr, + &dev_attr_dbc_bInterfaceProtocol.attr, + &dev_attr_dbc_poll_interval_ms.attr, + NULL +}; +ATTRIBUTE_GROUPS(dbc_dev); + +struct xhci_dbc * +xhci_alloc_dbc(struct device *dev, void __iomem *base, const struct dbc_driver *driver) +{ + struct xhci_dbc *dbc; + int ret; + + dbc = kzalloc_obj(*dbc); + if (!dbc) + return NULL; + + dbc->regs = base; + dbc->dev = dev; + dbc->driver = driver; + dbc->idProduct = DBC_PRODUCT_ID; + dbc->idVendor = DBC_VENDOR_ID; + dbc->bcdDevice = DBC_DEVICE_REV; + dbc->bInterfaceProtocol = DBC_PROTOCOL; + dbc->poll_interval = DBC_POLL_INTERVAL_DEFAULT; + + /* initialize serial, product and manufacturer with default values */ + dbc->str = dbc_str_default; + + if (readl(&dbc->regs->control) & DBC_CTRL_DBC_ENABLE) + goto err; + + INIT_DELAYED_WORK(&dbc->event_work, xhci_dbc_handle_events); + spin_lock_init(&dbc->lock); + mutex_init(&dbc->enable_mutex); + + ret = sysfs_create_groups(&dev->kobj, dbc_dev_groups); + if (ret) + goto err; + + return dbc; +err: + kfree(dbc); + return NULL; +} + +/* undo what xhci_alloc_dbc() did */ +void xhci_dbc_remove(struct xhci_dbc *dbc) +{ + if (!dbc) + return; + /* stop hw, stop wq and call dbc->ops->stop() */ + mutex_lock(&dbc->enable_mutex); + xhci_dbc_stop(dbc); + mutex_unlock(&dbc->enable_mutex); + /* remove sysfs files */ + sysfs_remove_groups(&dbc->dev->kobj, dbc_dev_groups); + + kfree(dbc); +} + + +int xhci_create_dbc_dev(struct xhci_hcd *xhci) +{ + struct device *dev; + void __iomem *base; + int ret; + int dbc_cap_offs; + + /* create all parameters needed resembling a dbc device */ + dev = xhci_to_hcd(xhci)->self.controller; + base = &xhci->cap_regs->hc_capbase; + + dbc_cap_offs = xhci_find_next_ext_cap(base, 0, XHCI_EXT_CAPS_DEBUG); + if (!dbc_cap_offs) + return -ENODEV; + + /* already allocated and in use */ + if (xhci->dbc) + return -EBUSY; + + ret = xhci_dbc_tty_probe(dev, base + dbc_cap_offs, xhci); + + return ret; +} + +void xhci_remove_dbc_dev(struct xhci_hcd *xhci) +{ + unsigned long flags; + + if (!xhci->dbc) + return; + + xhci_dbc_tty_remove(xhci->dbc); + spin_lock_irqsave(&xhci->lock, flags); + xhci->dbc = NULL; + spin_unlock_irqrestore(&xhci->lock, flags); +} + +#ifdef CONFIG_PM +int xhci_dbc_suspend(struct xhci_hcd *xhci) +{ + struct xhci_dbc *dbc = xhci->dbc; + + if (!dbc) + return 0; + + mutex_lock(&dbc->enable_mutex); + + switch (dbc->state) { + case DS_ENABLED: + case DS_CONNECTED: + case DS_CONFIGURED: + dbc->resume_required = 1; + break; + default: + break; + } + + xhci_dbc_stop(dbc); + mutex_unlock(&dbc->enable_mutex); + + return 0; +} + +int xhci_dbc_resume(struct xhci_hcd *xhci) +{ + int ret = 0; + struct xhci_dbc *dbc = xhci->dbc; + + if (!dbc) + return 0; + + mutex_lock(&dbc->enable_mutex); + + if (dbc->resume_required) { + dbc->resume_required = 0; + xhci_dbc_start(dbc); + } + + mutex_unlock(&dbc->enable_mutex); + + return ret; +} +#endif /* CONFIG_PM */ + +int xhci_dbc_init(void) +{ + return dbc_tty_init(); +} + +void xhci_dbc_exit(void) +{ + dbc_tty_exit(); +} diff --git a/drivers/usb/host/xhci-dbgcap.h b/drivers/usb/host/xhci-dbgcap.h new file mode 100644 index 000000000..5b18efb2c --- /dev/null +++ b/drivers/usb/host/xhci-dbgcap.h @@ -0,0 +1,278 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/** + * xhci-dbgcap.h - xHCI debug capability support + * + * Copyright (C) 2017 Intel Corporation + * + * Author: Lu Baolu + */ +#ifndef __LINUX_XHCI_DBGCAP_H +#define __LINUX_XHCI_DBGCAP_H + +#include +#include + +struct dbc_regs { + __le32 capability; + __le32 doorbell; + __le32 ersts; /* Event Ring Segment Table Size*/ + __le32 __reserved_0; /* 0c~0f reserved bits */ + __le64 erstba; /* Event Ring Segment Table Base Address */ + __le64 erdp; /* Event Ring Dequeue Pointer */ + __le32 control; + __le32 status; + __le32 portsc; /* Port status and control */ + __le32 __reserved_1; /* 2b~28 reserved bits */ + __le64 dccp; /* Debug Capability Context Pointer */ + __le32 devinfo1; /* Device Descriptor Info Register 1 */ + __le32 devinfo2; /* Device Descriptor Info Register 2 */ +}; + +struct dbc_info_context { + __le64 string0; + __le64 manufacturer; + __le64 product; + __le64 serial; + __le32 length; + __le32 __reserved_0[7]; +}; + +#define DBC_CTRL_DBC_RUN BIT(0) +#define DBC_CTRL_PORT_ENABLE BIT(1) +#define DBC_CTRL_HALT_OUT_TR BIT(2) +#define DBC_CTRL_HALT_IN_TR BIT(3) +#define DBC_CTRL_DBC_RUN_CHANGE BIT(4) +#define DBC_CTRL_DBC_ENABLE BIT(31) +#define DBC_CTRL_MAXBURST(p) (((p) >> 16) & 0xff) +#define DBC_DOOR_BELL_TARGET(p) (((p) & 0xff) << 8) + +#define DBC_MAX_PACKET 1024 +#define DBC_CONTEXT_SIZE 64 + +/* + * Port status: + */ +#define DBC_PORTSC_CONN_STATUS BIT(0) +#define DBC_PORTSC_PORT_ENABLED BIT(1) +#define DBC_PORTSC_CONN_CHANGE BIT(17) +#define DBC_PORTSC_RESET_CHANGE BIT(21) +#define DBC_PORTSC_LINK_CHANGE BIT(22) +#define DBC_PORTSC_CONFIG_CHANGE BIT(23) + +/* + * The maximum length of a string descriptor is 255, because the bLength + * field in the usb_string_descriptor struct is __u8. In practice the + * maximum length is 254, because a string descriptor consists of a 2 byte + * header followed by UTF-16 characters (2 bytes each). This allows for + * only 126 characters (code points) in the string, which is where + * USB_MAX_STRING_LEN comes from. + */ +#define USB_MAX_STRING_DESC_LEN 254 + +struct dbc_str_descs { + char string0[USB_MAX_STRING_DESC_LEN]; + char manufacturer[USB_MAX_STRING_DESC_LEN]; + char product[USB_MAX_STRING_DESC_LEN]; + char serial[USB_MAX_STRING_DESC_LEN]; +}; + +/* + * NULL terminated UTF-8 strings used to create UTF-16 strings + * (with maxiumum USB_MAX_STRING_LEN 2 byte characters). + */ +struct dbc_str { + char manufacturer[USB_MAX_STRING_LEN+1]; + char product[USB_MAX_STRING_LEN+1]; + char serial[USB_MAX_STRING_LEN+1]; +}; + +#define DBC_PROTOCOL 1 /* GNU Remote Debug Command */ +#define DBC_VENDOR_ID 0x1d6b /* Linux Foundation 0x1d6b */ +#define DBC_PRODUCT_ID 0x0010 /* device 0010 */ +#define DBC_DEVICE_REV 0x0010 /* 0.10 */ + +enum dbc_state { + DS_DISABLED = 0, + DS_INITIALIZED, + DS_ENABLED, + DS_CONNECTED, + DS_CONFIGURED, + DS_MAX +}; + +struct dbc_ep { + struct xhci_dbc *dbc; + struct list_head list_pending; + struct xhci_ring *ring; + unsigned int direction:1; + unsigned int halted:1; +}; + +#define DBC_QUEUE_SIZE 16 +#define DBC_WRITE_BUF_SIZE 8192 +#define DBC_POLL_INTERVAL_DEFAULT 64 /* milliseconds */ +#define DBC_POLL_INTERVAL_MAX 5000 /* milliseconds */ +#define DBC_XFER_INACTIVITY_TIMEOUT 10 /* milliseconds */ +#define DBC_ENUMERATION_TIMEOUT 2000 /* milliseconds */ +#define DBC_AUTOSUSPEND_DELAY 15000 /* milliseconds */ + +/* + * Private structure for DbC hardware state: + */ +struct dbc_port { + struct tty_port port; + spinlock_t port_lock; /* port access */ + int minor; + + struct list_head read_pool; + struct list_head read_queue; + unsigned int n_read; + struct tasklet_struct push; + + struct list_head write_pool; + unsigned int tx_boundary; + + bool registered; + bool tx_running; +}; + +struct dbc_driver { + int (*configure)(struct xhci_dbc *dbc); + void (*disconnect)(struct xhci_dbc *dbc); +}; + +struct xhci_dbc { + spinlock_t lock; /* device access */ + struct mutex enable_mutex; + struct device *dev; + struct xhci_hcd *xhci; + struct dbc_regs __iomem *regs; + struct xhci_ring *ring_evt; + struct xhci_ring *ring_in; + struct xhci_ring *ring_out; + struct xhci_erst erst; + struct xhci_container_ctx *ctx; + + struct dbc_str_descs *str_descs; + dma_addr_t str_descs_dma; + size_t str_descs_size; + struct dbc_str str; + u16 idVendor; + u16 idProduct; + u16 bcdDevice; + u8 bInterfaceProtocol; + + enum dbc_state state; + struct delayed_work event_work; + unsigned int poll_interval; /* ms */ + unsigned long xfer_timestamp; + unsigned long state_timestamp; + unsigned resume_required:1; + unsigned pending_rpm_put:1; + struct dbc_ep eps[2]; + + const struct dbc_driver *driver; + void *priv; +}; + +struct dbc_request { + void *buf; + unsigned int length; + dma_addr_t dma; + void (*complete)(struct xhci_dbc *dbc, + struct dbc_request *req); + struct list_head list_pool; + int status; + unsigned int actual; + + struct xhci_dbc *dbc; + struct list_head list_pending; + dma_addr_t trb_dma; + union xhci_trb *trb; + unsigned direction:1; +}; + +#define dbc_bulkout_ctx(d) \ + ((struct xhci_ep_ctx *)((d)->ctx->bytes + DBC_CONTEXT_SIZE)) +#define dbc_bulkin_ctx(d) \ + ((struct xhci_ep_ctx *)((d)->ctx->bytes + DBC_CONTEXT_SIZE * 2)) +#define dbc_bulkout_enq(d) \ + xhci_trb_virt_to_dma((d)->ring_out->enq_seg, (d)->ring_out->enqueue) +#define dbc_bulkin_enq(d) \ + xhci_trb_virt_to_dma((d)->ring_in->enq_seg, (d)->ring_in->enqueue) +#define dbc_epctx_info2(t, p, b) \ + cpu_to_le32(EP_TYPE(t) | MAX_PACKET(p) | MAX_BURST(b)) +#define dbc_ep_dma_direction(d) \ + ((d)->direction ? DMA_FROM_DEVICE : DMA_TO_DEVICE) + +#define BULK_OUT 0 +#define BULK_IN 1 +#define EPID_OUT 2 +#define EPID_IN 3 + +enum evtreturn { + EVT_ERR = -1, + EVT_DONE, + EVT_XFER_DONE, + EVT_GSER, + EVT_DISC, +}; + +static inline struct dbc_ep *get_in_ep(struct xhci_dbc *dbc) +{ + return &dbc->eps[BULK_IN]; +} + +static inline struct dbc_ep *get_out_ep(struct xhci_dbc *dbc) +{ + return &dbc->eps[BULK_OUT]; +} + +#ifdef CONFIG_USB_XHCI_DBGCAP +int xhci_create_dbc_dev(struct xhci_hcd *xhci); +void xhci_remove_dbc_dev(struct xhci_hcd *xhci); +int xhci_dbc_init(void); +void xhci_dbc_exit(void); +int dbc_tty_init(void); +void dbc_tty_exit(void); +int xhci_dbc_tty_probe(struct device *dev, void __iomem *res, struct xhci_hcd *xhci); +void xhci_dbc_tty_remove(struct xhci_dbc *dbc); +struct xhci_dbc *xhci_alloc_dbc(struct device *dev, void __iomem *res, + const struct dbc_driver *driver); +void xhci_dbc_remove(struct xhci_dbc *dbc); +struct dbc_request *dbc_alloc_request(struct xhci_dbc *dbc, + unsigned int direction, + gfp_t flags); +void dbc_free_request(struct dbc_request *req); +int dbc_ep_queue(struct dbc_request *req); +#ifdef CONFIG_PM +int xhci_dbc_suspend(struct xhci_hcd *xhci); +int xhci_dbc_resume(struct xhci_hcd *xhci); +#endif /* CONFIG_PM */ +#else +static inline int xhci_create_dbc_dev(struct xhci_hcd *xhci) +{ + return 0; +} + +static inline void xhci_remove_dbc_dev(struct xhci_hcd *xhci) +{ +} +static inline int xhci_dbc_init(void) +{ + return 0; +} +static inline void xhci_dbc_exit(void) +{ +} +static inline int xhci_dbc_suspend(struct xhci_hcd *xhci) +{ + return 0; +} + +static inline int xhci_dbc_resume(struct xhci_hcd *xhci) +{ + return 0; +} +#endif /* CONFIG_USB_XHCI_DBGCAP */ +#endif /* __LINUX_XHCI_DBGCAP_H */ diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c new file mode 100644 index 000000000..3d51e8d82 --- /dev/null +++ b/drivers/usb/host/xhci-dbgtty.c @@ -0,0 +1,675 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xhci-dbgtty.c - tty glue for xHCI debug capability + * + * Copyright (C) 2017 Intel Corporation + * + * Author: Lu Baolu + */ + +#include +#include +#include +#include + +#include "xhci.h" +#include "xhci-dbgcap.h" + +static struct tty_driver *dbc_tty_driver; +static struct idr dbc_tty_minors; +static DEFINE_MUTEX(dbc_tty_minors_lock); + +static inline struct dbc_port *dbc_to_port(struct xhci_dbc *dbc) +{ + return dbc->priv; +} + +static unsigned int +dbc_kfifo_to_req(struct dbc_port *port, char *packet) +{ + unsigned int len; + + len = kfifo_len(&port->port.xmit_fifo); + + if (len == 0) + return 0; + + len = min(len, DBC_MAX_PACKET); + + if (port->tx_boundary) + len = min(port->tx_boundary, len); + + len = kfifo_out(&port->port.xmit_fifo, packet, len); + + if (port->tx_boundary) + port->tx_boundary -= len; + + return len; +} + +static int dbc_do_start_tx(struct dbc_port *port) + __releases(&port->port_lock) + __acquires(&port->port_lock) +{ + int len; + struct dbc_request *req; + int status = 0; + bool do_tty_wake = false; + struct list_head *pool = &port->write_pool; + + port->tx_running = true; + + while (!list_empty(pool)) { + req = list_entry(pool->next, struct dbc_request, list_pool); + len = dbc_kfifo_to_req(port, req->buf); + if (len == 0) + break; + do_tty_wake = true; + + req->length = len; + list_del(&req->list_pool); + + spin_unlock(&port->port_lock); + status = dbc_ep_queue(req); + spin_lock(&port->port_lock); + + if (status) { + list_add(&req->list_pool, pool); + break; + } + } + + port->tx_running = false; + + if (do_tty_wake && port->port.tty) + tty_wakeup(port->port.tty); + + return status; +} + +/* must be called with port->port_lock held */ +static int dbc_start_tx(struct dbc_port *port) +{ + lockdep_assert_held(&port->port_lock); + + if (port->tx_running) + return -EBUSY; + + return dbc_do_start_tx(port); +} + +static void dbc_start_rx(struct dbc_port *port) + __releases(&port->port_lock) + __acquires(&port->port_lock) +{ + struct dbc_request *req; + int status; + struct list_head *pool = &port->read_pool; + + while (!list_empty(pool)) { + if (!port->port.tty) + break; + + req = list_entry(pool->next, struct dbc_request, list_pool); + list_del(&req->list_pool); + req->length = DBC_MAX_PACKET; + + spin_unlock(&port->port_lock); + status = dbc_ep_queue(req); + spin_lock(&port->port_lock); + + if (status) { + list_add(&req->list_pool, pool); + break; + } + } +} + +/* + * Queue received data to tty buffer and push it. + * + * Returns nr of remaining bytes that didn't fit tty buffer, i.e. 0 if all + * bytes sucessfullt moved. In case of error returns negative errno. + * Call with lock held + */ +static int dbc_rx_push_buffer(struct dbc_port *port, struct dbc_request *req) +{ + char *packet = req->buf; + unsigned int n, size = req->actual; + int count; + + if (!req->actual) + return 0; + + /* if n_read is set then request was partially moved to tty buffer */ + n = port->n_read; + if (n) { + packet += n; + size -= n; + } + + count = tty_insert_flip_string(&port->port, packet, size); + if (count) + tty_flip_buffer_push(&port->port); + if (count != size) { + port->n_read += count; + return size - count; + } + + port->n_read = 0; + return 0; +} + +static void +dbc_read_complete(struct xhci_dbc *dbc, struct dbc_request *req) +{ + unsigned long flags; + struct dbc_port *port = dbc_to_port(dbc); + struct tty_struct *tty; + int untransferred; + + tty = port->port.tty; + + spin_lock_irqsave(&port->port_lock, flags); + + /* + * Only defer copyig data to tty buffer in case: + * - !list_empty(&port->read_queue), there are older pending data + * - tty is throttled + * - failed to copy all data to buffer, defer remaining part + */ + + if (list_empty(&port->read_queue) && tty && !tty_throttled(tty)) { + untransferred = dbc_rx_push_buffer(port, req); + if (untransferred == 0) { + list_add_tail(&req->list_pool, &port->read_pool); + if (req->status != -ESHUTDOWN) + dbc_start_rx(port); + goto out; + } + } + + /* defer moving data from req to tty buffer to a tasklet */ + list_add_tail(&req->list_pool, &port->read_queue); + tasklet_schedule(&port->push); +out: + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static void dbc_write_complete(struct xhci_dbc *dbc, struct dbc_request *req) +{ + unsigned long flags; + struct dbc_port *port = dbc_to_port(dbc); + + spin_lock_irqsave(&port->port_lock, flags); + list_add(&req->list_pool, &port->write_pool); + switch (req->status) { + case 0: + dbc_start_tx(port); + break; + case -ESHUTDOWN: + break; + default: + dev_warn(dbc->dev, "unexpected write complete status %d\n", + req->status); + break; + } + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static void xhci_dbc_free_req(struct dbc_request *req) +{ + kfree(req->buf); + dbc_free_request(req); +} + +static int +xhci_dbc_alloc_requests(struct xhci_dbc *dbc, unsigned int direction, + struct list_head *head, + void (*fn)(struct xhci_dbc *, struct dbc_request *)) +{ + int i; + struct dbc_request *req; + + for (i = 0; i < DBC_QUEUE_SIZE; i++) { + req = dbc_alloc_request(dbc, direction, GFP_KERNEL); + if (!req) + break; + + req->length = DBC_MAX_PACKET; + req->buf = kmalloc(req->length, GFP_KERNEL); + if (!req->buf) { + dbc_free_request(req); + break; + } + + req->complete = fn; + list_add_tail(&req->list_pool, head); + } + + return list_empty(head) ? -ENOMEM : 0; +} + +static void +xhci_dbc_free_requests(struct list_head *head) +{ + struct dbc_request *req; + + while (!list_empty(head)) { + req = list_entry(head->next, struct dbc_request, list_pool); + list_del(&req->list_pool); + xhci_dbc_free_req(req); + } +} + +static int dbc_tty_install(struct tty_driver *driver, struct tty_struct *tty) +{ + struct dbc_port *port; + + mutex_lock(&dbc_tty_minors_lock); + port = idr_find(&dbc_tty_minors, tty->index); + mutex_unlock(&dbc_tty_minors_lock); + + if (!port) + return -ENXIO; + + tty->driver_data = port; + + return tty_port_install(&port->port, driver, tty); +} + +static int dbc_tty_open(struct tty_struct *tty, struct file *file) +{ + struct dbc_port *port = tty->driver_data; + + return tty_port_open(&port->port, tty, file); +} + +static void dbc_tty_close(struct tty_struct *tty, struct file *file) +{ + struct dbc_port *port = tty->driver_data; + + tty_port_close(&port->port, tty, file); +} + +static ssize_t dbc_tty_write(struct tty_struct *tty, const u8 *buf, + size_t count) +{ + struct dbc_port *port = tty->driver_data; + unsigned long flags; + unsigned int written = 0; + + spin_lock_irqsave(&port->port_lock, flags); + + /* + * Treat tty write as one usb transfer. Make sure the writes are turned + * into TRB request having the same size boundaries as the tty writes. + * Don't add data to kfifo before previous write is turned into TRBs + */ + if (port->tx_boundary) { + spin_unlock_irqrestore(&port->port_lock, flags); + return 0; + } + + if (count) { + written = kfifo_in(&port->port.xmit_fifo, buf, count); + + if (written == count) + port->tx_boundary = kfifo_len(&port->port.xmit_fifo); + + dbc_start_tx(port); + } + + spin_unlock_irqrestore(&port->port_lock, flags); + + return written; +} + +static int dbc_tty_put_char(struct tty_struct *tty, u8 ch) +{ + struct dbc_port *port = tty->driver_data; + unsigned long flags; + int status; + + spin_lock_irqsave(&port->port_lock, flags); + status = kfifo_put(&port->port.xmit_fifo, ch); + spin_unlock_irqrestore(&port->port_lock, flags); + + return status; +} + +static void dbc_tty_flush_chars(struct tty_struct *tty) +{ + struct dbc_port *port = tty->driver_data; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + dbc_start_tx(port); + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static unsigned int dbc_tty_write_room(struct tty_struct *tty) +{ + struct dbc_port *port = tty->driver_data; + unsigned long flags; + unsigned int room; + + spin_lock_irqsave(&port->port_lock, flags); + room = kfifo_avail(&port->port.xmit_fifo); + + if (port->tx_boundary) + room = 0; + + spin_unlock_irqrestore(&port->port_lock, flags); + + return room; +} + +static unsigned int dbc_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct dbc_port *port = tty->driver_data; + unsigned long flags; + unsigned int chars; + + spin_lock_irqsave(&port->port_lock, flags); + chars = kfifo_len(&port->port.xmit_fifo); + spin_unlock_irqrestore(&port->port_lock, flags); + + return chars; +} + +static void dbc_tty_unthrottle(struct tty_struct *tty) +{ + struct dbc_port *port = tty->driver_data; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + tasklet_schedule(&port->push); + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static const struct tty_operations dbc_tty_ops = { + .install = dbc_tty_install, + .open = dbc_tty_open, + .close = dbc_tty_close, + .write = dbc_tty_write, + .put_char = dbc_tty_put_char, + .flush_chars = dbc_tty_flush_chars, + .write_room = dbc_tty_write_room, + .chars_in_buffer = dbc_tty_chars_in_buffer, + .unthrottle = dbc_tty_unthrottle, +}; + +static void dbc_rx_push(struct tasklet_struct *t) +{ + struct dbc_request *req; + struct tty_struct *tty; + unsigned long flags; + bool disconnect = false; + struct dbc_port *port = from_tasklet(port, t, push); + struct list_head *queue = &port->read_queue; + int untransferred; + + spin_lock_irqsave(&port->port_lock, flags); + tty = port->port.tty; + while (!list_empty(queue)) { + req = list_first_entry(queue, struct dbc_request, list_pool); + + if (tty && tty_throttled(tty)) + break; + + switch (req->status) { + case 0: + break; + case -ESHUTDOWN: + disconnect = true; + break; + default: + pr_warn("ttyDBC0: unexpected RX status %d\n", + req->status); + break; + } + + untransferred = dbc_rx_push_buffer(port, req); + if (untransferred > 0) + break; + + list_move_tail(&req->list_pool, &port->read_pool); + } + + if (!list_empty(queue)) + tasklet_schedule(&port->push); + + if (!disconnect) + dbc_start_rx(port); + + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static int dbc_port_activate(struct tty_port *_port, struct tty_struct *tty) +{ + unsigned long flags; + struct dbc_port *port = container_of(_port, struct dbc_port, port); + + spin_lock_irqsave(&port->port_lock, flags); + dbc_start_rx(port); + spin_unlock_irqrestore(&port->port_lock, flags); + + return 0; +} + +static const struct tty_port_operations dbc_port_ops = { + .activate = dbc_port_activate, +}; + +static void +xhci_dbc_tty_init_port(struct xhci_dbc *dbc, struct dbc_port *port) +{ + tty_port_init(&port->port); + spin_lock_init(&port->port_lock); + tasklet_setup(&port->push, dbc_rx_push); + INIT_LIST_HEAD(&port->read_pool); + INIT_LIST_HEAD(&port->read_queue); + INIT_LIST_HEAD(&port->write_pool); + + port->port.ops = &dbc_port_ops; + port->n_read = 0; +} + +static void +xhci_dbc_tty_exit_port(struct dbc_port *port) +{ + tasklet_kill(&port->push); + tty_port_destroy(&port->port); +} + +static int xhci_dbc_tty_register_device(struct xhci_dbc *dbc) +{ + int ret; + struct device *tty_dev; + struct dbc_port *port = dbc_to_port(dbc); + + if (port->registered) + return -EBUSY; + + xhci_dbc_tty_init_port(dbc, port); + + mutex_lock(&dbc_tty_minors_lock); + port->minor = idr_alloc(&dbc_tty_minors, port, 0, 64, GFP_KERNEL); + mutex_unlock(&dbc_tty_minors_lock); + + if (port->minor < 0) { + ret = port->minor; + goto err_idr; + } + + ret = kfifo_alloc(&port->port.xmit_fifo, DBC_WRITE_BUF_SIZE, + GFP_KERNEL); + if (ret) + goto err_exit_port; + + ret = xhci_dbc_alloc_requests(dbc, BULK_IN, &port->read_pool, + dbc_read_complete); + if (ret) + goto err_free_fifo; + + ret = xhci_dbc_alloc_requests(dbc, BULK_OUT, &port->write_pool, + dbc_write_complete); + if (ret) + goto err_free_requests; + + tty_dev = tty_port_register_device(&port->port, + dbc_tty_driver, port->minor, NULL); + if (IS_ERR(tty_dev)) { + ret = PTR_ERR(tty_dev); + goto err_free_requests; + } + + port->registered = true; + + return 0; + +err_free_requests: + xhci_dbc_free_requests(&port->read_pool); + xhci_dbc_free_requests(&port->write_pool); +err_free_fifo: + kfifo_free(&port->port.xmit_fifo); +err_exit_port: + idr_remove(&dbc_tty_minors, port->minor); +err_idr: + xhci_dbc_tty_exit_port(port); + + dev_err(dbc->dev, "can't register tty port, err %d\n", ret); + + return ret; +} + +static void xhci_dbc_tty_unregister_device(struct xhci_dbc *dbc) +{ + struct dbc_port *port = dbc_to_port(dbc); + + if (!port->registered) + return; + /* + * Hang up the TTY. This wakes up any blocked + * writers and causes subsequent writes to fail. + */ + tty_port_tty_vhangup(&port->port); + + tty_unregister_device(dbc_tty_driver, port->minor); + xhci_dbc_tty_exit_port(port); + port->registered = false; + + mutex_lock(&dbc_tty_minors_lock); + idr_remove(&dbc_tty_minors, port->minor); + mutex_unlock(&dbc_tty_minors_lock); + + kfifo_free(&port->port.xmit_fifo); + xhci_dbc_free_requests(&port->read_pool); + xhci_dbc_free_requests(&port->read_queue); + xhci_dbc_free_requests(&port->write_pool); +} + +static const struct dbc_driver dbc_driver = { + .configure = xhci_dbc_tty_register_device, + .disconnect = xhci_dbc_tty_unregister_device, +}; + +int xhci_dbc_tty_probe(struct device *dev, void __iomem *base, struct xhci_hcd *xhci) +{ + struct xhci_dbc *dbc; + struct dbc_port *port; + int status; + + if (!dbc_tty_driver) + return -ENODEV; + + port = kzalloc_obj(*port); + if (!port) + return -ENOMEM; + + dbc = xhci_alloc_dbc(dev, base, &dbc_driver); + + if (!dbc) { + status = -ENOMEM; + goto out2; + } + + dbc->priv = port; + + /* get rid of xhci once this is a real driver binding to a device */ + xhci->dbc = dbc; + + return 0; +out2: + kfree(port); + + return status; +} + +/* + * undo what probe did, assume dbc is stopped already. + * we also assume tty_unregister_device() is called before this + */ +void xhci_dbc_tty_remove(struct xhci_dbc *dbc) +{ + struct dbc_port *port = dbc_to_port(dbc); + + xhci_dbc_remove(dbc); + kfree(port); +} + +int dbc_tty_init(void) +{ + int ret; + + idr_init(&dbc_tty_minors); + + dbc_tty_driver = tty_alloc_driver(64, TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV); + if (IS_ERR(dbc_tty_driver)) { + ret = PTR_ERR(dbc_tty_driver); + goto fail; + } + + dbc_tty_driver->driver_name = "dbc_serial"; + dbc_tty_driver->name = "ttyDBC"; + + dbc_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + dbc_tty_driver->subtype = SERIAL_TYPE_NORMAL; + dbc_tty_driver->init_termios = tty_std_termios; + dbc_tty_driver->init_termios.c_lflag &= ~ECHO; + dbc_tty_driver->init_termios.c_cflag = + B9600 | CS8 | CREAD | HUPCL | CLOCAL; + dbc_tty_driver->init_termios.c_ispeed = 9600; + dbc_tty_driver->init_termios.c_ospeed = 9600; + + tty_set_operations(dbc_tty_driver, &dbc_tty_ops); + + ret = tty_register_driver(dbc_tty_driver); + if (ret) { + pr_err("Can't register dbc tty driver\n"); + goto fail_put; + } + + return ret; + +fail_put: + tty_driver_kref_put(dbc_tty_driver); +fail: + idr_destroy(&dbc_tty_minors); + dbc_tty_driver = NULL; + + return ret; +} + +void dbc_tty_exit(void) +{ + if (IS_ERR_OR_NULL(dbc_tty_driver)) + return; + + tty_unregister_driver(dbc_tty_driver); + tty_driver_kref_put(dbc_tty_driver); + idr_destroy(&dbc_tty_minors); + dbc_tty_driver = NULL; +} diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c new file mode 100644 index 000000000..2aa01d99f --- /dev/null +++ b/drivers/usb/host/xhci-debugfs.c @@ -0,0 +1,855 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xhci-debugfs.c - xHCI debugfs interface + * + * Copyright (C) 2017 Intel Corporation + * + * Author: Lu Baolu + */ + +#include +#include +#include + +#include "xhci.h" +#include "xhci-debugfs.h" + +static const struct debugfs_reg32 xhci_cap_regs[] = { + dump_register(CAPLENGTH), + dump_register(HCSPARAMS1), + dump_register(HCSPARAMS2), + dump_register(HCSPARAMS3), + dump_register(HCCPARAMS1), + dump_register(DOORBELLOFF), + dump_register(RUNTIMEOFF), + dump_register(HCCPARAMS2), +}; + +static const struct debugfs_reg32 xhci_op_regs[] = { + dump_register(USBCMD), + dump_register(USBSTS), + dump_register(PAGESIZE), + dump_register(DNCTRL), + dump_register(CRCR), + dump_register(DCBAAP_LOW), + dump_register(DCBAAP_HIGH), + dump_register(CONFIG), +}; + +static const struct debugfs_reg32 xhci_runtime_regs[] = { + dump_register(MFINDEX), + dump_register(IR0_IMAN), + dump_register(IR0_IMOD), + dump_register(IR0_ERSTSZ), + dump_register(IR0_ERSTBA_LOW), + dump_register(IR0_ERSTBA_HIGH), + dump_register(IR0_ERDP_LOW), + dump_register(IR0_ERDP_HIGH), +}; + +static const struct debugfs_reg32 xhci_extcap_legsup[] = { + dump_register(EXTCAP_USBLEGSUP), + dump_register(EXTCAP_USBLEGCTLSTS), +}; + +static const struct debugfs_reg32 xhci_extcap_protocol[] = { + dump_register(EXTCAP_REVISION), + dump_register(EXTCAP_NAME), + dump_register(EXTCAP_PORTINFO), + dump_register(EXTCAP_PORTTYPE), + dump_register(EXTCAP_MANTISSA1), + dump_register(EXTCAP_MANTISSA2), + dump_register(EXTCAP_MANTISSA3), + dump_register(EXTCAP_MANTISSA4), + dump_register(EXTCAP_MANTISSA5), + dump_register(EXTCAP_MANTISSA6), +}; + +static const struct debugfs_reg32 xhci_extcap_dbc[] = { + dump_register(EXTCAP_DBC_CAPABILITY), + dump_register(EXTCAP_DBC_DOORBELL), + dump_register(EXTCAP_DBC_ERSTSIZE), + dump_register(EXTCAP_DBC_ERST_LOW), + dump_register(EXTCAP_DBC_ERST_HIGH), + dump_register(EXTCAP_DBC_ERDP_LOW), + dump_register(EXTCAP_DBC_ERDP_HIGH), + dump_register(EXTCAP_DBC_CONTROL), + dump_register(EXTCAP_DBC_STATUS), + dump_register(EXTCAP_DBC_PORTSC), + dump_register(EXTCAP_DBC_CONT_LOW), + dump_register(EXTCAP_DBC_CONT_HIGH), + dump_register(EXTCAP_DBC_DEVINFO1), + dump_register(EXTCAP_DBC_DEVINFO2), +}; + +static struct dentry *xhci_debugfs_root; + +static struct xhci_regset *xhci_debugfs_alloc_regset(struct xhci_hcd *xhci) +{ + struct xhci_regset *regset; + + regset = kzalloc_obj(*regset); + if (!regset) + return NULL; + + /* + * The allocation and free of regset are executed in order. + * We needn't a lock here. + */ + INIT_LIST_HEAD(®set->list); + list_add_tail(®set->list, &xhci->regset_list); + + return regset; +} + +static void xhci_debugfs_free_regset(struct xhci_regset *regset) +{ + if (!regset) + return; + + list_del(®set->list); + kfree(regset); +} + +__printf(6, 7) +static void xhci_debugfs_regset(struct xhci_hcd *xhci, u32 base, + const struct debugfs_reg32 *regs, + size_t nregs, struct dentry *parent, + const char *fmt, ...) +{ + struct xhci_regset *rgs; + va_list args; + struct debugfs_regset32 *regset; + struct usb_hcd *hcd = xhci_to_hcd(xhci); + + rgs = xhci_debugfs_alloc_regset(xhci); + if (!rgs) + return; + + va_start(args, fmt); + vsnprintf(rgs->name, sizeof(rgs->name), fmt, args); + va_end(args); + + regset = &rgs->regset; + regset->regs = regs; + regset->nregs = nregs; + regset->base = hcd->regs + base; + regset->dev = hcd->self.controller; + + debugfs_create_regset32((const char *)rgs->name, 0444, parent, regset); +} + +static void xhci_debugfs_extcap_regset(struct xhci_hcd *xhci, int cap_id, + const struct debugfs_reg32 *regs, + size_t n, const char *cap_name) +{ + u32 offset; + int index = 0; + size_t psic, nregs = n; + void __iomem *base = &xhci->cap_regs->hc_capbase; + + offset = xhci_find_next_ext_cap(base, 0, cap_id); + while (offset) { + if (cap_id == XHCI_EXT_CAPS_PROTOCOL) { + psic = XHCI_EXT_PORT_PSIC(readl(base + offset + 8)); + nregs = min(4 + psic, n); + } + + xhci_debugfs_regset(xhci, offset, regs, nregs, + xhci->debugfs_root, "%s:%02d", + cap_name, index); + offset = xhci_find_next_ext_cap(base, offset, cap_id); + index++; + } +} + +static int xhci_ring_enqueue_show(struct seq_file *s, void *unused) +{ + dma_addr_t dma; + struct xhci_ring *ring = *(struct xhci_ring **)s->private; + + dma = xhci_trb_virt_to_dma(ring->enq_seg, ring->enqueue); + seq_printf(s, "%pad\n", &dma); + + return 0; +} + +static int xhci_ring_dequeue_show(struct seq_file *s, void *unused) +{ + dma_addr_t dma; + struct xhci_ring *ring = *(struct xhci_ring **)s->private; + + dma = xhci_trb_virt_to_dma(ring->deq_seg, ring->dequeue); + seq_printf(s, "%pad\n", &dma); + + return 0; +} + +static int xhci_ring_cycle_show(struct seq_file *s, void *unused) +{ + struct xhci_ring *ring = *(struct xhci_ring **)s->private; + + seq_printf(s, "%d\n", ring->cycle_state); + + return 0; +} + +static void xhci_ring_dump_segment(struct seq_file *s, + struct xhci_segment *seg) +{ + int i; + dma_addr_t dma; + union xhci_trb *trb; + char str[XHCI_MSG_MAX]; + + for (i = 0; i < TRBS_PER_SEGMENT; i++) { + trb = &seg->trbs[i]; + dma = seg->dma + i * sizeof(*trb); + seq_printf(s, "%2u %pad: %s\n", seg->num, &dma, + xhci_decode_trb(str, XHCI_MSG_MAX, le32_to_cpu(trb->generic.field[0]), + le32_to_cpu(trb->generic.field[1]), + le32_to_cpu(trb->generic.field[2]), + le32_to_cpu(trb->generic.field[3]))); + } +} + +static int xhci_ring_trb_show(struct seq_file *s, void *unused) +{ + struct xhci_ring *ring = *(struct xhci_ring **)s->private; + struct xhci_segment *seg = ring->first_seg; + + xhci_for_each_ring_seg(ring->first_seg, seg) + xhci_ring_dump_segment(s, seg); + + return 0; +} + +static struct xhci_file_map ring_files[] = { + {"enqueue", xhci_ring_enqueue_show, }, + {"dequeue", xhci_ring_dequeue_show, }, + {"cycle", xhci_ring_cycle_show, }, + {"trbs", xhci_ring_trb_show, }, +}; + +static int xhci_ring_open(struct inode *inode, struct file *file) +{ + const struct xhci_file_map *f_map = debugfs_get_aux(file); + + return single_open(file, f_map->show, inode->i_private); +} + +static const struct file_operations xhci_ring_fops = { + .open = xhci_ring_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int xhci_slot_context_show(struct seq_file *s, void *unused) +{ + struct xhci_hcd *xhci; + struct xhci_slot_ctx *slot_ctx; + struct xhci_slot_priv *priv = s->private; + struct xhci_virt_device *dev = priv->dev; + char str[XHCI_MSG_MAX]; + + xhci = hcd_to_xhci(bus_to_hcd(dev->udev->bus)); + slot_ctx = xhci_get_slot_ctx(xhci, dev->out_ctx); + seq_printf(s, "%pad: %s\n", &dev->out_ctx->dma, + xhci_decode_slot_context(str, + le32_to_cpu(slot_ctx->dev_info), + le32_to_cpu(slot_ctx->dev_info2), + le32_to_cpu(slot_ctx->tt_info), + le32_to_cpu(slot_ctx->dev_state))); + + return 0; +} + +static int xhci_endpoint_context_show(struct seq_file *s, void *unused) +{ + int ep_index; + dma_addr_t dma; + struct xhci_hcd *xhci; + struct xhci_ep_ctx *ep_ctx; + struct xhci_slot_priv *priv = s->private; + struct xhci_virt_device *dev = priv->dev; + char str[XHCI_MSG_MAX]; + + xhci = hcd_to_xhci(bus_to_hcd(dev->udev->bus)); + + for (ep_index = 0; ep_index < 31; ep_index++) { + ep_ctx = xhci_get_ep_ctx(xhci, dev->out_ctx, ep_index); + dma = dev->out_ctx->dma + (ep_index + 1) * CTX_SIZE(xhci->hcc_params); + seq_printf(s, "%pad: %s, virt_state:%#x\n", &dma, + xhci_decode_ep_context(str, + le32_to_cpu(ep_ctx->ep_info), + le32_to_cpu(ep_ctx->ep_info2), + le64_to_cpu(ep_ctx->deq), + le32_to_cpu(ep_ctx->tx_info)), + dev->eps[ep_index].ep_state); + } + + return 0; +} + +static int xhci_device_name_show(struct seq_file *s, void *unused) +{ + struct xhci_slot_priv *priv = s->private; + struct xhci_virt_device *dev = priv->dev; + + seq_printf(s, "%s\n", dev_name(&dev->udev->dev)); + + return 0; +} + +static struct xhci_file_map context_files[] = { + {"name", xhci_device_name_show, }, + {"slot-context", xhci_slot_context_show, }, + {"ep-context", xhci_endpoint_context_show, }, +}; + +static int xhci_context_open(struct inode *inode, struct file *file) +{ + const struct xhci_file_map *f_map = debugfs_get_aux(file); + + return single_open(file, f_map->show, inode->i_private); +} + +static const struct file_operations xhci_context_fops = { + .open = xhci_context_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + + + +static int xhci_portsc_show(struct seq_file *s, void *unused) +{ + struct xhci_port *port = s->private; + u32 portsc; + char str[XHCI_MSG_MAX]; + + portsc = xhci_portsc_readl(port); + seq_printf(s, "%s\n", xhci_decode_portsc(str, portsc)); + + return 0; +} + +static int xhci_port_open(struct inode *inode, struct file *file) +{ + return single_open(file, xhci_portsc_show, inode->i_private); +} + +static ssize_t xhci_port_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct xhci_port *port = s->private; + struct xhci_hcd *xhci = hcd_to_xhci(port->rhub->hcd); + char buf[32]; + u32 portsc; + unsigned long flags; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "compliance", 10)) { + /* If CTC is clear, compliance is enabled by default */ + if (!(xhci->hcc_params2 & HCC2_CTC)) + return count; + spin_lock_irqsave(&xhci->lock, flags); + /* compliance mode can only be enabled on ports in RxDetect */ + portsc = xhci_portsc_readl(port); + if ((portsc & PORT_PLS_MASK) != XDEV_RXDETECT) { + spin_unlock_irqrestore(&xhci->lock, flags); + return -EPERM; + } + portsc = xhci_port_state_to_neutral(portsc); + portsc &= ~PORT_PLS_MASK; + portsc |= PORT_LINK_STROBE | XDEV_COMP_MODE; + xhci_portsc_writel(port, portsc); + spin_unlock_irqrestore(&xhci->lock, flags); + } else { + return -EINVAL; + } + return count; +} + +static const struct file_operations port_fops = { + .open = xhci_port_open, + .write = xhci_port_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int xhci_portli_show(struct seq_file *s, void *unused) +{ + struct xhci_port *port = s->private; + struct xhci_hcd *xhci; + u32 portli; + + portli = readl(&port->port_reg->portli); + + /* port without protocol capability isn't added to a roothub */ + if (!port->rhub) { + seq_printf(s, "0x%08x\n", portli); + return 0; + } + + xhci = hcd_to_xhci(port->rhub->hcd); + + /* PORTLI fields are valid if port is a USB3 or eUSB2V2 port */ + if (port->rhub == &xhci->usb3_rhub) + seq_printf(s, "0x%08x LEC=%u RLC=%u TLC=%u\n", portli, + PORT_LEC(portli), PORT_RX_LANES(portli), PORT_TX_LANES(portli)); + else if (xhci->hcc_params2 & HCC2_E2V2C) + seq_printf(s, "0x%08x RDR=%u TDR=%u\n", portli, + PORTLI_RDR(portli), PORTLI_TDR(portli)); + else + seq_printf(s, "0x%08x RsvdP\n", portli); + + return 0; +} + +static int xhci_portli_open(struct inode *inode, struct file *file) +{ + return single_open(file, xhci_portli_show, inode->i_private); +} + +static const struct file_operations portli_fops = { + .open = xhci_portli_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void xhci_debugfs_create_files(struct xhci_hcd *xhci, + struct xhci_file_map *files, + size_t nentries, void *data, + struct dentry *parent, + const struct file_operations *fops) +{ + int i; + + for (i = 0; i < nentries; i++) + debugfs_create_file_aux(files[i].name, 0444, parent, + data, &files[i], fops); +} + +static struct dentry *xhci_debugfs_create_ring_dir(struct xhci_hcd *xhci, + struct xhci_ring **ring, + const char *name, + struct dentry *parent) +{ + struct dentry *dir; + + dir = debugfs_create_dir(name, parent); + xhci_debugfs_create_files(xhci, ring_files, ARRAY_SIZE(ring_files), + ring, dir, &xhci_ring_fops); + + return dir; +} + +static void xhci_debugfs_create_context_files(struct xhci_hcd *xhci, + struct dentry *parent, + int slot_id) +{ + struct xhci_virt_device *dev = xhci->devs[slot_id]; + + xhci_debugfs_create_files(xhci, context_files, + ARRAY_SIZE(context_files), + dev->debugfs_private, + parent, &xhci_context_fops); +} + +void xhci_debugfs_create_endpoint(struct xhci_hcd *xhci, + struct xhci_virt_device *dev, + int ep_index) +{ + struct xhci_ep_priv *epriv; + struct xhci_slot_priv *spriv = dev->debugfs_private; + + if (!spriv) + return; + + if (spriv->eps[ep_index]) + return; + + epriv = kzalloc_obj(*epriv); + if (!epriv) + return; + + epriv->show_ring = dev->eps[ep_index].ring; + + snprintf(epriv->name, sizeof(epriv->name), "ep%02d", ep_index); + epriv->root = xhci_debugfs_create_ring_dir(xhci, + &epriv->show_ring, + epriv->name, + spriv->root); + spriv->eps[ep_index] = epriv; +} + +void xhci_debugfs_remove_endpoint(struct xhci_hcd *xhci, + struct xhci_virt_device *dev, + int ep_index) +{ + struct xhci_ep_priv *epriv; + struct xhci_slot_priv *spriv = dev->debugfs_private; + + if (!spriv || !spriv->eps[ep_index]) + return; + + epriv = spriv->eps[ep_index]; + debugfs_remove_recursive(epriv->root); + spriv->eps[ep_index] = NULL; + kfree(epriv); +} + +static int xhci_stream_id_show(struct seq_file *s, void *unused) +{ + struct xhci_ep_priv *epriv = s->private; + + if (!epriv->stream_info) + return -EPERM; + + seq_printf(s, "Show stream ID %d trb ring, supported [1 - %d]\n", + epriv->stream_id, epriv->stream_info->num_streams - 1); + + return 0; +} + +static int xhci_stream_id_open(struct inode *inode, struct file *file) +{ + return single_open(file, xhci_stream_id_show, inode->i_private); +} + +static ssize_t xhci_stream_id_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct xhci_ep_priv *epriv = s->private; + int ret; + u16 stream_id; /* MaxPStreams + 1 <= 16 */ + + if (!epriv->stream_info) + return -EPERM; + + /* Decimal number */ + ret = kstrtou16_from_user(ubuf, count, 10, &stream_id); + if (ret) + return ret; + + if (stream_id == 0 || stream_id >= epriv->stream_info->num_streams) + return -EINVAL; + + epriv->stream_id = stream_id; + epriv->show_ring = epriv->stream_info->stream_rings[stream_id]; + + return count; +} + +static const struct file_operations stream_id_fops = { + .open = xhci_stream_id_open, + .write = xhci_stream_id_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int xhci_stream_context_array_show(struct seq_file *s, void *unused) +{ + struct xhci_ep_priv *epriv = s->private; + struct xhci_stream_ctx *stream_ctx; + dma_addr_t dma; + int id; + + if (!epriv->stream_info) + return -EPERM; + + seq_printf(s, "Allocated %d streams and %d stream context array entries\n", + epriv->stream_info->num_streams, + epriv->stream_info->num_stream_ctxs); + + for (id = 0; id < epriv->stream_info->num_stream_ctxs; id++) { + stream_ctx = epriv->stream_info->stream_ctx_array + id; + dma = epriv->stream_info->ctx_array_dma + id * 16; + if (id < epriv->stream_info->num_streams) + seq_printf(s, "%pad stream id %d deq %016llx\n", &dma, + id, le64_to_cpu(stream_ctx->stream_ring)); + else + seq_printf(s, "%pad stream context entry not used deq %016llx\n", + &dma, le64_to_cpu(stream_ctx->stream_ring)); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(xhci_stream_context_array); + +void xhci_debugfs_create_stream_files(struct xhci_hcd *xhci, + struct xhci_virt_device *dev, + int ep_index) +{ + struct xhci_slot_priv *spriv = dev->debugfs_private; + struct xhci_ep_priv *epriv; + + if (!spriv || !spriv->eps[ep_index] || + !dev->eps[ep_index].stream_info) + return; + + epriv = spriv->eps[ep_index]; + epriv->stream_info = dev->eps[ep_index].stream_info; + + /* Show trb ring of stream ID 1 by default */ + epriv->stream_id = 1; + epriv->show_ring = epriv->stream_info->stream_rings[1]; + debugfs_create_file("stream_id", 0644, + epriv->root, epriv, + &stream_id_fops); + debugfs_create_file("stream_context_array", 0444, + epriv->root, epriv, + &xhci_stream_context_array_fops); +} + +void xhci_debugfs_create_slot(struct xhci_hcd *xhci, int slot_id) +{ + struct xhci_slot_priv *priv; + struct xhci_virt_device *dev = xhci->devs[slot_id]; + + priv = kzalloc_obj(*priv); + if (!priv) + return; + + snprintf(priv->name, sizeof(priv->name), "%02d", slot_id); + priv->root = debugfs_create_dir(priv->name, xhci->debugfs_slots); + priv->dev = dev; + dev->debugfs_private = priv; + + xhci_debugfs_create_ring_dir(xhci, &dev->eps[0].ring, + "ep00", priv->root); + + xhci_debugfs_create_context_files(xhci, priv->root, slot_id); +} + +void xhci_debugfs_remove_slot(struct xhci_hcd *xhci, int slot_id) +{ + int i; + struct xhci_slot_priv *priv; + struct xhci_virt_device *dev = xhci->devs[slot_id]; + + if (!dev || !dev->debugfs_private) + return; + + priv = dev->debugfs_private; + + debugfs_remove_recursive(priv->root); + + for (i = 0; i < 31; i++) + kfree(priv->eps[i]); + + kfree(priv); + dev->debugfs_private = NULL; +} + +static void xhci_debugfs_create_ports(struct xhci_hcd *xhci, + struct dentry *parent) +{ + char port_name[8]; + struct xhci_port *port; + struct dentry *dir; + + parent = debugfs_create_dir("ports", parent); + + for (int i = 0; i < xhci->max_ports; i++) { + scnprintf(port_name, sizeof(port_name), "port%02d", i + 1); + dir = debugfs_create_dir(port_name, parent); + port = &xhci->hw_ports[i]; + debugfs_create_file("portsc", 0644, dir, port, &port_fops); + debugfs_create_file("portli", 0444, dir, port, &portli_fops); + } +} + +static int xhci_port_bw_show(struct xhci_hcd *xhci, u8 dev_speed, + struct seq_file *s) +{ + unsigned int i; + int ret; + struct xhci_container_ctx *ctx; + struct usb_hcd *hcd = xhci_to_hcd(xhci); + struct device *dev = hcd->self.controller; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) + return ret; + + ctx = xhci_alloc_port_bw_ctx(xhci, 0); + if (!ctx) { + pm_runtime_put_sync(dev); + return -ENOMEM; + } + + /* get roothub port bandwidth */ + ret = xhci_get_port_bandwidth(xhci, ctx, dev_speed); + if (ret) + goto err_out; + + /* print all roothub ports available bandwidth + * refer to xhci rev1_2 protocol 6.2.6 , byte 0 is reserved + */ + for (i = 1; i <= xhci->max_ports; i++) + seq_printf(s, "port[%d] available bw: %d%%.\n", i, + ctx->bytes[i]); +err_out: + if (ret == -EIO) { + seq_puts(s, "Get Port Bandwidth failed\n"); + ret = 0; + } + pm_runtime_put_sync(dev); + xhci_free_port_bw_ctx(xhci, ctx); + return ret; +} + +static int xhci_ss_bw_show(struct seq_file *s, void *unused) +{ + int ret; + struct xhci_hcd *xhci = (struct xhci_hcd *)s->private; + + ret = xhci_port_bw_show(xhci, DEV_PORT_SPEED(XDEV_SS), s); + return ret; +} + +static int xhci_hs_bw_show(struct seq_file *s, void *unused) +{ + int ret; + struct xhci_hcd *xhci = (struct xhci_hcd *)s->private; + + ret = xhci_port_bw_show(xhci, DEV_PORT_SPEED(XDEV_HS), s); + return ret; +} + +static int xhci_fs_bw_show(struct seq_file *s, void *unused) +{ + int ret; + struct xhci_hcd *xhci = (struct xhci_hcd *)s->private; + + ret = xhci_port_bw_show(xhci, DEV_PORT_SPEED(XDEV_FS), s); + return ret; +} + +static struct xhci_file_map bw_context_files[] = { + {"SS_BW", xhci_ss_bw_show, }, + {"HS_BW", xhci_hs_bw_show, }, + {"FS_BW", xhci_fs_bw_show, }, +}; + +static int bw_context_open(struct inode *inode, struct file *file) +{ + int i; + struct xhci_file_map *f_map; + const char *file_name = file_dentry(file)->d_iname; + + for (i = 0; i < ARRAY_SIZE(bw_context_files); i++) { + f_map = &bw_context_files[i]; + + if (strcmp(f_map->name, file_name) == 0) + break; + } + + return single_open(file, f_map->show, inode->i_private); +} + +static const struct file_operations bw_fops = { + .open = bw_context_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void xhci_debugfs_create_bandwidth(struct xhci_hcd *xhci, + struct dentry *parent) +{ + parent = debugfs_create_dir("port_bandwidth", parent); + + xhci_debugfs_create_files(xhci, bw_context_files, + ARRAY_SIZE(bw_context_files), + xhci, + parent, &bw_fops); +} + +void xhci_debugfs_init(struct xhci_hcd *xhci) +{ + struct device *dev = xhci_to_hcd(xhci)->self.controller; + + xhci->debugfs_root = debugfs_create_dir(dev_name(dev), + xhci_debugfs_root); + + INIT_LIST_HEAD(&xhci->regset_list); + + xhci_debugfs_regset(xhci, + 0, + xhci_cap_regs, ARRAY_SIZE(xhci_cap_regs), + xhci->debugfs_root, "reg-cap"); + + xhci_debugfs_regset(xhci, + FIELD_GET(HC_LENGTH, readl(&xhci->cap_regs->hc_capbase)), + xhci_op_regs, ARRAY_SIZE(xhci_op_regs), + xhci->debugfs_root, "reg-op"); + + xhci_debugfs_regset(xhci, + readl(&xhci->cap_regs->run_regs_off) & RTSOFF_MASK, + xhci_runtime_regs, ARRAY_SIZE(xhci_runtime_regs), + xhci->debugfs_root, "reg-runtime"); + + xhci_debugfs_extcap_regset(xhci, XHCI_EXT_CAPS_LEGACY, + xhci_extcap_legsup, + ARRAY_SIZE(xhci_extcap_legsup), + "reg-ext-legsup"); + + xhci_debugfs_extcap_regset(xhci, XHCI_EXT_CAPS_PROTOCOL, + xhci_extcap_protocol, + ARRAY_SIZE(xhci_extcap_protocol), + "reg-ext-protocol"); + + xhci_debugfs_extcap_regset(xhci, XHCI_EXT_CAPS_DEBUG, + xhci_extcap_dbc, + ARRAY_SIZE(xhci_extcap_dbc), + "reg-ext-dbc"); + + xhci_debugfs_create_ring_dir(xhci, &xhci->cmd_ring, + "command-ring", + xhci->debugfs_root); + + xhci_debugfs_create_ring_dir(xhci, &xhci->interrupters[0]->event_ring, + "event-ring", + xhci->debugfs_root); + + xhci->debugfs_slots = debugfs_create_dir("devices", xhci->debugfs_root); + + xhci_debugfs_create_ports(xhci, xhci->debugfs_root); + + xhci_debugfs_create_bandwidth(xhci, xhci->debugfs_root); +} + +void xhci_debugfs_exit(struct xhci_hcd *xhci) +{ + struct xhci_regset *rgs, *tmp; + + debugfs_remove_recursive(xhci->debugfs_root); + xhci->debugfs_root = NULL; + xhci->debugfs_slots = NULL; + + list_for_each_entry_safe(rgs, tmp, &xhci->regset_list, list) + xhci_debugfs_free_regset(rgs); +} + +void __init xhci_debugfs_create_root(void) +{ + xhci_debugfs_root = debugfs_create_dir("xhci", usb_debug_root); +} + +void __exit xhci_debugfs_remove_root(void) +{ + debugfs_remove_recursive(xhci_debugfs_root); + xhci_debugfs_root = NULL; +} diff --git a/drivers/usb/host/xhci-debugfs.h b/drivers/usb/host/xhci-debugfs.h new file mode 100644 index 000000000..7c074b4be --- /dev/null +++ b/drivers/usb/host/xhci-debugfs.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xhci-debugfs.h - xHCI debugfs interface + * + * Copyright (C) 2017 Intel Corporation + * + * Author: Lu Baolu + */ + +#ifndef __LINUX_XHCI_DEBUGFS_H +#define __LINUX_XHCI_DEBUGFS_H + +#include + +#define DEBUGFS_NAMELEN 32 + +#define REG_CAPLENGTH 0x00 +#define REG_HCSPARAMS1 0x04 +#define REG_HCSPARAMS2 0x08 +#define REG_HCSPARAMS3 0x0c +#define REG_HCCPARAMS1 0x10 +#define REG_DOORBELLOFF 0x14 +#define REG_RUNTIMEOFF 0x18 +#define REG_HCCPARAMS2 0x1c + +#define REG_USBCMD 0x00 +#define REG_USBSTS 0x04 +#define REG_PAGESIZE 0x08 +#define REG_DNCTRL 0x14 +#define REG_CRCR 0x18 +#define REG_DCBAAP_LOW 0x30 +#define REG_DCBAAP_HIGH 0x34 +#define REG_CONFIG 0x38 + +#define REG_MFINDEX 0x00 +#define REG_IR0_IMAN 0x20 +#define REG_IR0_IMOD 0x24 +#define REG_IR0_ERSTSZ 0x28 +#define REG_IR0_ERSTBA_LOW 0x30 +#define REG_IR0_ERSTBA_HIGH 0x34 +#define REG_IR0_ERDP_LOW 0x38 +#define REG_IR0_ERDP_HIGH 0x3c + +#define REG_EXTCAP_USBLEGSUP 0x00 +#define REG_EXTCAP_USBLEGCTLSTS 0x04 + +#define REG_EXTCAP_REVISION 0x00 +#define REG_EXTCAP_NAME 0x04 +#define REG_EXTCAP_PORTINFO 0x08 +#define REG_EXTCAP_PORTTYPE 0x0c +#define REG_EXTCAP_MANTISSA1 0x10 +#define REG_EXTCAP_MANTISSA2 0x14 +#define REG_EXTCAP_MANTISSA3 0x18 +#define REG_EXTCAP_MANTISSA4 0x1c +#define REG_EXTCAP_MANTISSA5 0x20 +#define REG_EXTCAP_MANTISSA6 0x24 + +#define REG_EXTCAP_DBC_CAPABILITY 0x00 +#define REG_EXTCAP_DBC_DOORBELL 0x04 +#define REG_EXTCAP_DBC_ERSTSIZE 0x08 +#define REG_EXTCAP_DBC_ERST_LOW 0x10 +#define REG_EXTCAP_DBC_ERST_HIGH 0x14 +#define REG_EXTCAP_DBC_ERDP_LOW 0x18 +#define REG_EXTCAP_DBC_ERDP_HIGH 0x1c +#define REG_EXTCAP_DBC_CONTROL 0x20 +#define REG_EXTCAP_DBC_STATUS 0x24 +#define REG_EXTCAP_DBC_PORTSC 0x28 +#define REG_EXTCAP_DBC_CONT_LOW 0x30 +#define REG_EXTCAP_DBC_CONT_HIGH 0x34 +#define REG_EXTCAP_DBC_DEVINFO1 0x38 +#define REG_EXTCAP_DBC_DEVINFO2 0x3c + +#define dump_register(nm) \ +{ \ + .name = __stringify(nm), \ + .offset = REG_ ##nm, \ +} + +struct xhci_regset { + char name[DEBUGFS_NAMELEN]; + struct debugfs_regset32 regset; + size_t nregs; + struct list_head list; +}; + +struct xhci_file_map { + const char *name; + int (*show)(struct seq_file *s, void *unused); +}; + +struct xhci_ep_priv { + char name[DEBUGFS_NAMELEN]; + struct dentry *root; + struct xhci_stream_info *stream_info; + struct xhci_ring *show_ring; + unsigned int stream_id; +}; + +struct xhci_slot_priv { + char name[DEBUGFS_NAMELEN]; + struct dentry *root; + struct xhci_ep_priv *eps[31]; + struct xhci_virt_device *dev; +}; + +#ifdef CONFIG_DEBUG_FS +void xhci_debugfs_init(struct xhci_hcd *xhci); +void xhci_debugfs_exit(struct xhci_hcd *xhci); +void __init xhci_debugfs_create_root(void); +void __exit xhci_debugfs_remove_root(void); +void xhci_debugfs_create_slot(struct xhci_hcd *xhci, int slot_id); +void xhci_debugfs_remove_slot(struct xhci_hcd *xhci, int slot_id); +void xhci_debugfs_create_endpoint(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + int ep_index); +void xhci_debugfs_remove_endpoint(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + int ep_index); +void xhci_debugfs_create_stream_files(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + int ep_index); +#else +static inline void xhci_debugfs_init(struct xhci_hcd *xhci) { } +static inline void xhci_debugfs_exit(struct xhci_hcd *xhci) { } +static inline void __init xhci_debugfs_create_root(void) { } +static inline void __exit xhci_debugfs_remove_root(void) { } +static inline void xhci_debugfs_create_slot(struct xhci_hcd *x, int s) { } +static inline void xhci_debugfs_remove_slot(struct xhci_hcd *x, int s) { } +static inline void +xhci_debugfs_create_endpoint(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + int ep_index) { } +static inline void +xhci_debugfs_remove_endpoint(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + int ep_index) { } +static inline void +xhci_debugfs_create_stream_files(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + int ep_index) { } +#endif /* CONFIG_DEBUG_FS */ + +#endif /* __LINUX_XHCI_DEBUGFS_H */ diff --git a/drivers/usb/host/xhci-ext-caps.c b/drivers/usb/host/xhci-ext-caps.c new file mode 100644 index 000000000..7a4c2c4ad --- /dev/null +++ b/drivers/usb/host/xhci-ext-caps.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * XHCI extended capability handling + * + * Copyright (c) 2017 Hans de Goede + */ + +#include +#include +#include +#include "xhci.h" + +#define USB_SW_DRV_NAME "intel_xhci_usb_sw" +#define USB_SW_RESOURCE_SIZE 0x400 + +#define PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI 0x22b5 + +static const struct property_entry role_switch_props[] = { + PROPERTY_ENTRY_BOOL("sw_switch_disable"), + {}, +}; + +static void xhci_intel_unregister_pdev(void *arg) +{ + platform_device_unregister(arg); +} + +static int xhci_create_intel_xhci_sw_pdev(struct xhci_hcd *xhci, u32 cap_offset) +{ + struct usb_hcd *hcd = xhci_to_hcd(xhci); + struct device *dev = hcd->self.controller; + struct platform_device *pdev; + struct pci_dev *pci = to_pci_dev(dev); + struct resource res = { 0, }; + int ret; + + pdev = platform_device_alloc(USB_SW_DRV_NAME, PLATFORM_DEVID_NONE); + if (!pdev) { + xhci_err(xhci, "couldn't allocate %s platform device\n", + USB_SW_DRV_NAME); + return -ENOMEM; + } + + res.start = hcd->rsrc_start + cap_offset; + res.end = res.start + USB_SW_RESOURCE_SIZE - 1; + res.name = USB_SW_DRV_NAME; + res.flags = IORESOURCE_MEM; + + ret = platform_device_add_resources(pdev, &res, 1); + if (ret) { + dev_err(dev, "couldn't add resources to intel_xhci_usb_sw pdev\n"); + platform_device_put(pdev); + return ret; + } + + if (pci->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI) { + ret = device_create_managed_software_node(&pdev->dev, role_switch_props, + NULL); + if (ret) { + dev_err(dev, "failed to register device properties\n"); + platform_device_put(pdev); + return ret; + } + } + + pdev->dev.parent = dev; + + ret = platform_device_add(pdev); + if (ret) { + dev_err(dev, "couldn't register intel_xhci_usb_sw pdev\n"); + platform_device_put(pdev); + return ret; + } + + ret = devm_add_action_or_reset(dev, xhci_intel_unregister_pdev, pdev); + if (ret) { + dev_err(dev, "couldn't add unregister action for intel_xhci_usb_sw pdev\n"); + return ret; + } + + return 0; +} + +int xhci_ext_cap_init(struct xhci_hcd *xhci) +{ + void __iomem *base = &xhci->cap_regs->hc_capbase; + u32 offset, val; + int ret; + + offset = xhci_find_next_ext_cap(base, 0, 0); + + while (offset) { + val = readl(base + offset); + + switch (XHCI_EXT_CAPS_ID(val)) { + case XHCI_EXT_CAPS_VENDOR_INTEL: + if (xhci->quirks & XHCI_INTEL_USB_ROLE_SW) { + ret = xhci_create_intel_xhci_sw_pdev(xhci, + offset); + if (ret) + return ret; + } + break; + } + offset = xhci_find_next_ext_cap(base, offset, 0); + } + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_ext_cap_init); diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h new file mode 100644 index 000000000..67ecf7320 --- /dev/null +++ b/drivers/usb/host/xhci-ext-caps.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + */ + +/* HC should halt within 16 ms, but use 32 ms as some hosts take longer */ +#define XHCI_MAX_HALT_USEC (32 * 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 +/* Vendor caps */ +#define XHCI_EXT_CAPS_VENDOR_INTEL 192 +#define XHCI_EXT_CAPS_INTEL_SPR_SHADOW 206 +/* 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) +#define XHCI_BLC (1 << 20) + +/* Intel SPR shadow capability */ +#define XHCI_INTEL_SPR_ESS_PORT_OFFSET 0x8ac4 /* SuperSpeed port control */ +#define XHCI_INTEL_SPR_TUNEN BIT(4) /* Tunnel mode enabled */ + +/* 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) + +/** + * 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_MINOR(x) (((x) >> 16) & 0xff) +#define XHCI_EXT_PORT_PSIC(x) (((x) >> 28) & 0x0f) +#define XHCI_EXT_PORT_OFF(x) ((x) & 0xff) +#define XHCI_EXT_PORT_COUNT(x) (((x) >> 8) & 0xff) + +#define XHCI_EXT_PORT_PSIV(x) (((x) >> 0) & 0x0f) +#define XHCI_EXT_PORT_PSIE(x) (((x) >> 4) & 0x03) +#define XHCI_EXT_PORT_PLT(x) (((x) >> 6) & 0x03) +#define XHCI_EXT_PORT_PFD(x) (((x) >> 8) & 0x01) +#define XHCI_EXT_PORT_LP(x) (((x) >> 14) & 0x03) +#define XHCI_EXT_PORT_PSIM(x) (((x) >> 16) & 0xffff) + +#include + +/** + * Find the offset of the extended capabilities with capability ID id. + * + * @base PCI MMIO registers base address. + * @start address at which to start looking, (0 or HCC_PARAMS to start at + * beginning of list) + * @id Extended capability ID to search for, or 0 for the next + * capability + * + * Returns the offset of the next matching extended capability structure. + * Some capabilities can occur several times, e.g., the XHCI_EXT_CAPS_PROTOCOL, + * and this provides a way to find them all. + */ + +static inline int xhci_find_next_ext_cap(void __iomem *base, u32 start, int id) +{ + u32 val; + u32 next; + u32 offset; + + offset = start; + if (!start || start == XHCI_HCC_PARAMS_OFFSET) { + val = readl(base + XHCI_HCC_PARAMS_OFFSET); + if (val == ~0) + return 0; + offset = XHCI_HCC_EXT_CAPS(val) << 2; + if (!offset) + return 0; + } + do { + val = readl(base + offset); + if (val == ~0) + return 0; + if (offset != start && (id == 0 || XHCI_EXT_CAPS_ID(val) == id)) + return offset; + + next = XHCI_EXT_CAPS_NEXT(val); + offset += next << 2; + } while (next); + + return 0; +} diff --git a/drivers/usb/host/xhci-histb.c b/drivers/usb/host/xhci-histb.c new file mode 100644 index 000000000..fddb43bf6 --- /dev/null +++ b/drivers/usb/host/xhci-histb.c @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xHCI host controller driver for HiSilicon STB SoCs + * + * Copyright (C) 2017-2018 HiSilicon Co., Ltd. http://www.hisilicon.com + * + * Authors: Jianguo Sun + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xhci.h" + +#define GTXTHRCFG 0xc108 +#define GRXTHRCFG 0xc10c +#define REG_GUSB2PHYCFG0 0xc200 +#define BIT_UTMI_8_16 BIT(3) +#define BIT_UTMI_ULPI BIT(4) +#define BIT_FREECLK_EXIST BIT(30) + +#define REG_GUSB3PIPECTL0 0xc2c0 +#define USB3_DEEMPHASIS_MASK GENMASK(2, 1) +#define USB3_DEEMPHASIS0 BIT(1) +#define USB3_TX_MARGIN1 BIT(4) + +struct xhci_hcd_histb { + struct device *dev; + struct usb_hcd *hcd; + void __iomem *ctrl; + struct clk *bus_clk; + struct clk *utmi_clk; + struct clk *pipe_clk; + struct clk *suspend_clk; + struct reset_control *soft_reset; +}; + +static inline struct xhci_hcd_histb *hcd_to_histb(struct usb_hcd *hcd) +{ + return dev_get_drvdata(hcd->self.controller); +} + +static int xhci_histb_config(struct xhci_hcd_histb *histb) +{ + struct device_node *np = histb->dev->of_node; + u32 regval; + + if (of_property_match_string(np, "phys-names", "inno") >= 0) { + /* USB2 PHY chose ulpi 8bit interface */ + regval = readl(histb->ctrl + REG_GUSB2PHYCFG0); + regval &= ~BIT_UTMI_ULPI; + regval &= ~(BIT_UTMI_8_16); + regval &= ~BIT_FREECLK_EXIST; + writel(regval, histb->ctrl + REG_GUSB2PHYCFG0); + } + + if (of_property_match_string(np, "phys-names", "combo") >= 0) { + /* + * write 0x010c0012 to GUSB3PIPECTL0 + * GUSB3PIPECTL0[5:3] = 010 : Tx Margin = 900mV , + * decrease TX voltage + * GUSB3PIPECTL0[2:1] = 01 : Tx Deemphasis = -3.5dB, + * refer to xHCI spec + */ + regval = readl(histb->ctrl + REG_GUSB3PIPECTL0); + regval &= ~USB3_DEEMPHASIS_MASK; + regval |= USB3_DEEMPHASIS0; + regval |= USB3_TX_MARGIN1; + writel(regval, histb->ctrl + REG_GUSB3PIPECTL0); + } + + writel(0x23100000, histb->ctrl + GTXTHRCFG); + writel(0x23100000, histb->ctrl + GRXTHRCFG); + + return 0; +} + +static int xhci_histb_clks_get(struct xhci_hcd_histb *histb) +{ + struct device *dev = histb->dev; + + histb->bus_clk = devm_clk_get(dev, "bus"); + if (IS_ERR(histb->bus_clk)) { + dev_err(dev, "fail to get bus clk\n"); + return PTR_ERR(histb->bus_clk); + } + + histb->utmi_clk = devm_clk_get(dev, "utmi"); + if (IS_ERR(histb->utmi_clk)) { + dev_err(dev, "fail to get utmi clk\n"); + return PTR_ERR(histb->utmi_clk); + } + + histb->pipe_clk = devm_clk_get(dev, "pipe"); + if (IS_ERR(histb->pipe_clk)) { + dev_err(dev, "fail to get pipe clk\n"); + return PTR_ERR(histb->pipe_clk); + } + + histb->suspend_clk = devm_clk_get(dev, "suspend"); + if (IS_ERR(histb->suspend_clk)) { + dev_err(dev, "fail to get suspend clk\n"); + return PTR_ERR(histb->suspend_clk); + } + + return 0; +} + +static int xhci_histb_host_enable(struct xhci_hcd_histb *histb) +{ + int ret; + + ret = clk_prepare_enable(histb->bus_clk); + if (ret) { + dev_err(histb->dev, "failed to enable bus clk\n"); + return ret; + } + + ret = clk_prepare_enable(histb->utmi_clk); + if (ret) { + dev_err(histb->dev, "failed to enable utmi clk\n"); + goto err_utmi_clk; + } + + ret = clk_prepare_enable(histb->pipe_clk); + if (ret) { + dev_err(histb->dev, "failed to enable pipe clk\n"); + goto err_pipe_clk; + } + + ret = clk_prepare_enable(histb->suspend_clk); + if (ret) { + dev_err(histb->dev, "failed to enable suspend clk\n"); + goto err_suspend_clk; + } + + reset_control_deassert(histb->soft_reset); + + return 0; + +err_suspend_clk: + clk_disable_unprepare(histb->pipe_clk); +err_pipe_clk: + clk_disable_unprepare(histb->utmi_clk); +err_utmi_clk: + clk_disable_unprepare(histb->bus_clk); + + return ret; +} + +static void xhci_histb_host_disable(struct xhci_hcd_histb *histb) +{ + reset_control_assert(histb->soft_reset); + + clk_disable_unprepare(histb->suspend_clk); + clk_disable_unprepare(histb->pipe_clk); + clk_disable_unprepare(histb->utmi_clk); + clk_disable_unprepare(histb->bus_clk); +} + +/* called during probe() after chip reset completes */ +static int xhci_histb_setup(struct usb_hcd *hcd) +{ + struct xhci_hcd_histb *histb = hcd_to_histb(hcd); + int ret; + + if (usb_hcd_is_primary_hcd(hcd)) { + ret = xhci_histb_config(histb); + if (ret) + return ret; + } + + return xhci_gen_setup(hcd, NULL); +} + +static const struct xhci_driver_overrides xhci_histb_overrides __initconst = { + .reset = xhci_histb_setup, +}; + +static struct hc_driver __read_mostly xhci_histb_hc_driver; +static int xhci_histb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct xhci_hcd_histb *histb; + const struct hc_driver *driver; + struct usb_hcd *hcd; + struct xhci_hcd *xhci; + struct resource *res; + int irq; + int ret = -ENODEV; + + if (usb_disabled()) + return -ENODEV; + + driver = &xhci_histb_hc_driver; + histb = devm_kzalloc(dev, sizeof(*histb), GFP_KERNEL); + if (!histb) + return -ENOMEM; + + histb->dev = dev; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + histb->ctrl = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(histb->ctrl)) + return PTR_ERR(histb->ctrl); + + ret = xhci_histb_clks_get(histb); + if (ret) + return ret; + + histb->soft_reset = devm_reset_control_get(dev, "soft"); + if (IS_ERR(histb->soft_reset)) { + dev_err(dev, "failed to get soft reset\n"); + return PTR_ERR(histb->soft_reset); + } + + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + device_enable_async_suspend(dev); + + /* Initialize dma_mask and coherent_dma_mask to 32-bits */ + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + goto disable_pm; + + hcd = usb_create_hcd(driver, dev, dev_name(dev)); + if (!hcd) { + ret = -ENOMEM; + goto disable_pm; + } + + hcd->regs = histb->ctrl; + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + histb->hcd = hcd; + dev_set_drvdata(hcd->self.controller, histb); + + ret = xhci_histb_host_enable(histb); + if (ret) + goto put_hcd; + + xhci = hcd_to_xhci(hcd); + + device_wakeup_enable(hcd->self.controller); + + xhci->main_hcd = hcd; + xhci->shared_hcd = usb_create_shared_hcd(driver, dev, dev_name(dev), + hcd); + if (!xhci->shared_hcd) { + ret = -ENOMEM; + goto disable_host; + } + + if (device_property_read_bool(dev, "usb2-lpm-disable")) + xhci->quirks |= XHCI_HW_LPM_DISABLE; + + if (device_property_read_bool(dev, "usb3-lpm-capable")) + xhci->quirks |= XHCI_LPM_SUPPORT; + + /* imod_interval is the interrupt moderation value in nanoseconds. */ + xhci->imod_interval = 40000; + device_property_read_u32(dev, "imod-interval-ns", + &xhci->imod_interval); + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret) + goto put_usb3_hcd; + + if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + + ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); + if (ret) + goto dealloc_usb2_hcd; + + device_enable_async_suspend(dev); + pm_runtime_put_noidle(dev); + + /* + * Prevent runtime pm from being on as default, users should enable + * runtime pm using power/control in sysfs. + */ + pm_runtime_forbid(dev); + + return 0; + +dealloc_usb2_hcd: + usb_remove_hcd(hcd); +put_usb3_hcd: + usb_put_hcd(xhci->shared_hcd); +disable_host: + xhci_histb_host_disable(histb); +put_hcd: + usb_put_hcd(hcd); +disable_pm: + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + return ret; +} + +static void xhci_histb_remove(struct platform_device *dev) +{ + struct xhci_hcd_histb *histb = platform_get_drvdata(dev); + struct usb_hcd *hcd = histb->hcd; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct usb_hcd *shared_hcd = xhci->shared_hcd; + + xhci->xhc_state |= XHCI_STATE_REMOVING; + + usb_remove_hcd(shared_hcd); + xhci->shared_hcd = NULL; + device_wakeup_disable(&dev->dev); + + usb_remove_hcd(hcd); + usb_put_hcd(shared_hcd); + + xhci_histb_host_disable(histb); + usb_put_hcd(hcd); + pm_runtime_put_sync(&dev->dev); + pm_runtime_disable(&dev->dev); +} + +static int __maybe_unused xhci_histb_suspend(struct device *dev) +{ + struct xhci_hcd_histb *histb = dev_get_drvdata(dev); + struct usb_hcd *hcd = histb->hcd; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int ret; + + ret = xhci_suspend(xhci, device_may_wakeup(dev)); + + if (!device_may_wakeup(dev)) + xhci_histb_host_disable(histb); + + return ret; +} + +static int __maybe_unused xhci_histb_resume(struct device *dev) +{ + struct xhci_hcd_histb *histb = dev_get_drvdata(dev); + struct usb_hcd *hcd = histb->hcd; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + if (!device_may_wakeup(dev)) + xhci_histb_host_enable(histb); + + return xhci_resume(xhci, false, false); +} + +static const struct dev_pm_ops xhci_histb_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(xhci_histb_suspend, xhci_histb_resume) +}; +#define DEV_PM_OPS (IS_ENABLED(CONFIG_PM) ? &xhci_histb_pm_ops : NULL) + +#ifdef CONFIG_OF +static const struct of_device_id histb_xhci_of_match[] = { + { .compatible = "hisilicon,hi3798cv200-xhci"}, + { }, +}; +MODULE_DEVICE_TABLE(of, histb_xhci_of_match); +#endif + +static struct platform_driver histb_xhci_driver = { + .probe = xhci_histb_probe, + .remove = xhci_histb_remove, + .driver = { + .name = "xhci-histb", + .pm = DEV_PM_OPS, + .of_match_table = of_match_ptr(histb_xhci_of_match), + }, +}; +MODULE_ALIAS("platform:xhci-histb"); + +static int __init xhci_histb_init(void) +{ + xhci_init_driver(&xhci_histb_hc_driver, &xhci_histb_overrides); + return platform_driver_register(&histb_xhci_driver); +} +module_init(xhci_histb_init); + +static void __exit xhci_histb_exit(void) +{ + platform_driver_unregister(&histb_xhci_driver); +} +module_exit(xhci_histb_exit); + +MODULE_DESCRIPTION("HiSilicon STB xHCI Host Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c new file mode 100644 index 000000000..470bafe18 --- /dev/null +++ b/drivers/usb/host/xhci-hub.c @@ -0,0 +1,1967 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + */ + + +#include +#include +#include +#include + +#include "xhci.h" +#include "xhci-trace.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) + +/* Default sublink speed attribute of each lane */ +static u32 ssp_cap_default_ssa[] = { + 0x00050034, /* USB 3.0 SS Gen1x1 id:4 symmetric rx 5Gbps */ + 0x000500b4, /* USB 3.0 SS Gen1x1 id:4 symmetric tx 5Gbps */ + 0x000a4035, /* USB 3.1 SSP Gen2x1 id:5 symmetric rx 10Gbps */ + 0x000a40b5, /* USB 3.1 SSP Gen2x1 id:5 symmetric tx 10Gbps */ + 0x00054036, /* USB 3.2 SSP Gen1x2 id:6 symmetric rx 5Gbps */ + 0x000540b6, /* USB 3.2 SSP Gen1x2 id:6 symmetric tx 5Gbps */ + 0x000a4037, /* USB 3.2 SSP Gen2x2 id:7 symmetric rx 10Gbps */ + 0x000a40b7, /* USB 3.2 SSP Gen2x2 id:7 symmetric tx 10Gbps */ +}; + +static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf, + u16 wLength) +{ + struct usb_bos_descriptor *bos; + struct usb_ss_cap_descriptor *ss_cap; + struct usb_ssp_cap_descriptor *ssp_cap; + struct xhci_port_cap *port_cap = NULL; + u16 bcdUSB; + u32 reg; + u32 min_rate = 0; + u8 min_ssid; + u8 ssac; + u8 ssic; + int offset; + int i; + + /* BOS descriptor */ + bos = (struct usb_bos_descriptor *)buf; + bos->bLength = USB_DT_BOS_SIZE; + bos->bDescriptorType = USB_DT_BOS; + bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE + + USB_DT_USB_SS_CAP_SIZE); + bos->bNumDeviceCaps = 1; + + /* Create the descriptor for port with the highest revision */ + for (i = 0; i < xhci->num_port_caps; i++) { + u8 major = xhci->port_caps[i].maj_rev; + u8 minor = xhci->port_caps[i].min_rev; + u16 rev = (major << 8) | minor; + + if (i == 0 || bcdUSB < rev) { + bcdUSB = rev; + port_cap = &xhci->port_caps[i]; + } + } + + if (bcdUSB >= 0x0310) { + if (port_cap->psi_count) { + u8 num_sym_ssa = 0; + + for (i = 0; i < port_cap->psi_count; i++) { + if ((port_cap->psi[i] & PLT_MASK) == PLT_SYM) + num_sym_ssa++; + } + + ssac = port_cap->psi_count + num_sym_ssa - 1; + ssic = port_cap->psi_uid_count - 1; + } else { + if (bcdUSB >= 0x0320) + ssac = 7; + else + ssac = 3; + + ssic = (ssac + 1) / 2 - 1; + } + + bos->bNumDeviceCaps++; + bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE + + USB_DT_USB_SS_CAP_SIZE + + USB_DT_USB_SSP_CAP_SIZE(ssac)); + } + + if (wLength < USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE) + return wLength; + + /* SuperSpeed USB Device Capability */ + ss_cap = (struct usb_ss_cap_descriptor *)&buf[USB_DT_BOS_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; /* set later */ + ss_cap->wSpeedSupported = cpu_to_le16(USB_5GBPS_OPERATION); + ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION; + ss_cap->bU1devExitLat = 0; /* set later */ + ss_cap->bU2DevExitLat = 0; /* set later */ + + reg = readl(&xhci->cap_regs->hcc_params); + if (reg & HCC_LTC) + ss_cap->bmAttributes |= USB_LTM_SUPPORT; + + if ((xhci->quirks & XHCI_LPM_SUPPORT)) { + reg = readl(&xhci->cap_regs->hcs_params3); + ss_cap->bU1devExitLat = FIELD_GET(HCS_U1_LATENCY, reg); + ss_cap->bU2DevExitLat = cpu_to_le16(FIELD_GET(HCS_U2_LATENCY, reg)); + } + + if (wLength < le16_to_cpu(bos->wTotalLength)) + return wLength; + + if (bcdUSB < 0x0310) + return le16_to_cpu(bos->wTotalLength); + + ssp_cap = (struct usb_ssp_cap_descriptor *)&buf[USB_DT_BOS_SIZE + + USB_DT_USB_SS_CAP_SIZE]; + ssp_cap->bLength = USB_DT_USB_SSP_CAP_SIZE(ssac); + ssp_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + ssp_cap->bDevCapabilityType = USB_SSP_CAP_TYPE; + ssp_cap->bReserved = 0; + ssp_cap->wReserved = 0; + ssp_cap->bmAttributes = + cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_ATTRIBS, ssac) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_IDS, ssic)); + + if (!port_cap->psi_count) { + for (i = 0; i < ssac + 1; i++) + ssp_cap->bmSublinkSpeedAttr[i] = + cpu_to_le32(ssp_cap_default_ssa[i]); + + min_ssid = 4; + goto out; + } + + offset = 0; + for (i = 0; i < port_cap->psi_count; i++) { + u32 psi; + u32 attr; + u8 ssid; + u8 lp; + u8 lse; + u8 psie; + u16 lane_mantissa; + u16 psim; + u16 plt; + + psi = port_cap->psi[i]; + ssid = XHCI_EXT_PORT_PSIV(psi); + lp = XHCI_EXT_PORT_LP(psi); + psie = XHCI_EXT_PORT_PSIE(psi); + psim = XHCI_EXT_PORT_PSIM(psi); + plt = psi & PLT_MASK; + + lse = psie; + lane_mantissa = psim; + + /* Shift to Gbps and set SSP Link Protocol if 10Gpbs */ + for (; psie < USB_SSP_SUBLINK_SPEED_LSE_GBPS; psie++) + psim /= 1000; + + if (!min_rate || psim < min_rate) { + min_ssid = ssid; + min_rate = psim; + } + + /* Some host controllers don't set the link protocol for SSP */ + if (psim >= 10) + lp = USB_SSP_SUBLINK_SPEED_LP_SSP; + + /* + * PSIM and PSIE represent the total speed of PSI. The BOS + * descriptor SSP sublink speed attribute lane mantissa + * describes the lane speed. E.g. PSIM and PSIE for gen2x2 + * is 20Gbps, but the BOS descriptor lane speed mantissa is + * 10Gbps. Check and modify the mantissa value to match the + * lane speed. + */ + if (bcdUSB == 0x0320 && plt == PLT_SYM) { + /* + * The PSI dword for gen1x2 and gen2x1 share the same + * values. But the lane speed for gen1x2 is 5Gbps while + * gen2x1 is 10Gbps. If the previous PSI dword SSID is + * 5 and the PSIE and PSIM match with SSID 6, let's + * assume that the controller follows the default speed + * id with SSID 6 for gen1x2. + */ + if (ssid == 6 && psie == 3 && psim == 10 && i) { + u32 prev = port_cap->psi[i - 1]; + + if ((prev & PLT_MASK) == PLT_SYM && + XHCI_EXT_PORT_PSIV(prev) == 5 && + XHCI_EXT_PORT_PSIE(prev) == 3 && + XHCI_EXT_PORT_PSIM(prev) == 10) { + lse = USB_SSP_SUBLINK_SPEED_LSE_GBPS; + lane_mantissa = 5; + } + } + + if (psie == 3 && psim > 10) { + lse = USB_SSP_SUBLINK_SPEED_LSE_GBPS; + lane_mantissa = 10; + } + } + + attr = (FIELD_PREP(USB_SSP_SUBLINK_SPEED_SSID, ssid) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LP, lp) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSE, lse) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSM, lane_mantissa)); + + switch (plt) { + case PLT_SYM: + attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, + USB_SSP_SUBLINK_SPEED_ST_SYM_RX); + ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr); + + FIELD_MODIFY(USB_SSP_SUBLINK_SPEED_ST, &attr, + USB_SSP_SUBLINK_SPEED_ST_SYM_TX); + ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr); + break; + case PLT_ASYM_RX: + attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, + USB_SSP_SUBLINK_SPEED_ST_ASYM_RX); + ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr); + break; + case PLT_ASYM_TX: + attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, + USB_SSP_SUBLINK_SPEED_ST_ASYM_TX); + ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr); + break; + } + } +out: + ssp_cap->wFunctionalitySupport = + cpu_to_le16(FIELD_PREP(USB_SSP_MIN_SUBLINK_SPEED_ATTRIBUTE_ID, + min_ssid) | + FIELD_PREP(USB_SSP_MIN_RX_LANE_COUNT, 1) | + FIELD_PREP(USB_SSP_MIN_TX_LANE_COUNT, 1)); + + return le16_to_cpu(bos->wTotalLength); +} + +static void xhci_common_hub_descriptor(struct xhci_hcd *xhci, + struct usb_hub_descriptor *desc, int ports) +{ + u16 temp; + + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 0; + /* Bits 1:0 - support per-port power switching, or power always on */ + if (xhci->hcc_params & HCC_PPC) + 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; + struct xhci_hub *rhub; + + rhub = &xhci->usb2_rhub; + ports = rhub->num_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; + desc->bPwrOn2PwrGood = 10; /* xhci section 5.4.8 says 20ms */ + + /* 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_portsc_readl(rhub->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; + struct xhci_hub *rhub; + + rhub = &xhci->usb3_rhub; + ports = rhub->num_ports; + xhci_common_hub_descriptor(xhci, desc, ports); + desc->bDescriptorType = USB_DT_SS_HUB; + desc->bDescLength = USB_DT_SS_HUB_SIZE; + desc->bPwrOn2PwrGood = 50; /* usb 3.1 may fail if less than 100ms */ + + /* 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_portsc_readl(rhub->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(int portsc) +{ + if (DEV_LOWSPEED(portsc)) + return USB_PORT_STAT_LOW_SPEED; + if (DEV_HIGHSPEED(portsc)) + 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)) + +/** + * xhci_port_state_to_neutral() - Clean up read portsc value back into writeable + * @portsc: u32 port value read from portsc register to be cleanup up + * + * Given a portsc, 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. + * + * Return: u32 value that can be written back to portsc register without + * changing port state. + */ + +u32 xhci_port_state_to_neutral(u32 portsc) +{ + /* Save read-only status and port state */ + return (portsc & XHCI_PORT_RO) | (portsc & XHCI_PORT_RWS); +} +EXPORT_SYMBOL_GPL(xhci_port_state_to_neutral); + +/* + * 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 ret; + int i; + + ret = 0; + virt_dev = xhci->devs[slot_id]; + if (!virt_dev) + return -ENODEV; + + trace_xhci_stop_device(virt_dev); + + cmd = xhci_alloc_command(xhci, true, GFP_NOIO); + if (!cmd) + 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) { + struct xhci_ep_ctx *ep_ctx; + struct xhci_command *command; + + ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->out_ctx, i); + + /* Check ep is running, required by AMD SNPS 3.1 xHC */ + if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) + continue; + + command = xhci_alloc_command(xhci, false, GFP_NOWAIT); + if (!command) { + spin_unlock_irqrestore(&xhci->lock, flags); + ret = -ENOMEM; + goto cmd_cleanup; + } + + ret = xhci_queue_stop_endpoint(xhci, command, slot_id, + i, suspend); + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_free_command(xhci, command); + goto cmd_cleanup; + } + } + } + ret = xhci_queue_stop_endpoint(xhci, cmd, slot_id, 0, suspend); + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + goto cmd_cleanup; + } + + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + /* Wait for last stop endpoint command to finish */ + wait_for_completion(cmd->completion); + + if (cmd->status == COMP_COMMAND_ABORTED || + cmd->status == COMP_COMMAND_RING_STOPPED) { + xhci_warn(xhci, "Timeout while waiting for stop endpoint command\n"); + ret = -ETIME; + } + +cmd_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, s; + struct xhci_virt_ep *ep; + + for (i = 0; i < LAST_EP_INDEX + 1; i++) { + ep = &xhci->devs[slot_id]->eps[i]; + + if (ep->ep_state & EP_HAS_STREAMS) { + for (s = 1; s < ep->stream_info->num_streams; s++) + xhci_ring_ep_doorbell(xhci, slot_id, i, s); + } else if (ep->ring && ep->ring->dequeue) { + xhci_ring_ep_doorbell(xhci, slot_id, i, 0); + } + } + + return; +} + +static void xhci_disable_port(struct xhci_hcd *xhci, struct xhci_port *port) +{ + struct usb_hcd *hcd; + u32 portsc; + + hcd = port->rhub->hcd; + + /* 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; + } + + if (xhci->quirks & XHCI_BROKEN_PORT_PED) { + xhci_dbg(xhci, + "Broken Port Enabled/Disabled, ignoring port disable request.\n"); + return; + } + + portsc = xhci_portsc_readl(port); + portsc = xhci_port_state_to_neutral(portsc); + + /* Write 1 to disable the port */ + xhci_portsc_writel(port, portsc | PORT_PE); + + portsc = xhci_portsc_readl(port); + xhci_dbg(xhci, "disable port %d-%d, portsc: 0x%x\n", + hcd->self.busnum, port->hcd_portnum + 1, portsc); +} + +static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue, struct xhci_port *port, + u32 portsc) +{ + 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; + case USB_PORT_FEAT_C_PORT_CONFIG_ERROR: + status = PORT_CEC; + port_change_bit = "config error"; + break; + default: + /* Should never happen */ + return; + } + /* Change bits are all write 1 to clear */ + xhci_portsc_writel(port, portsc | status); + portsc = xhci_portsc_readl(port); + + xhci_dbg(xhci, "clear port%d %s change, portsc: 0x%x\n", + port->hcd_portnum + 1, port_change_bit, portsc); +} + +struct xhci_hub *xhci_get_rhub(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + if (hcd->speed >= HCD_USB3) + return &xhci->usb3_rhub; + return &xhci->usb2_rhub; +} + +/* + * xhci_set_port_power() must be called with xhci->lock held. + * It will release and re-acquire the lock while calling ACPI + * method. + */ +static void xhci_set_port_power(struct xhci_hcd *xhci, struct xhci_port *port, + bool on, unsigned long *flags) + __must_hold(&xhci->lock) +{ + struct usb_hcd *hcd; + u32 temp; + + hcd = port->rhub->hcd; + temp = xhci_portsc_readl(port); + + xhci_dbg(xhci, "set port power %d-%d %s, portsc: 0x%x\n", + hcd->self.busnum, port->hcd_portnum + 1, on ? "ON" : "OFF", temp); + + temp = xhci_port_state_to_neutral(temp); + + if (on) { + /* Power on */ + xhci_portsc_writel(port, temp | PORT_POWER); + xhci_portsc_readl(port); + } else { + /* Power off */ + xhci_portsc_writel(port, temp & ~PORT_POWER); + } + + spin_unlock_irqrestore(&xhci->lock, *flags); + temp = usb_acpi_power_manageable(hcd->self.root_hub, + port->hcd_portnum); + if (temp) + usb_acpi_set_power_state(hcd->self.root_hub, + port->hcd_portnum, on); + spin_lock_irqsave(&xhci->lock, *flags); +} + +static void xhci_port_set_test_mode(struct xhci_hcd *xhci, u16 test_mode, int portnum) +{ + u32 temp; + struct xhci_port *port; + + /* xhci only supports test mode for usb2 ports */ + port = xhci->usb2_rhub.ports[portnum]; + temp = readl(&port->port_reg->portpmsc); + temp |= test_mode << PORT_TEST_MODE_SHIFT; + writel(temp, &port->port_reg->portpmsc); + xhci->test_mode = test_mode; + if (test_mode == USB_TEST_FORCE_ENABLE) + xhci_start(xhci); +} + +static int xhci_enter_test_mode(struct xhci_hcd *xhci, u16 test_mode, int portnum, + unsigned long *flags) + __must_hold(&xhci->lock) +{ + int i, retval; + + /* Disable all Device Slots */ + xhci_dbg(xhci, "Disable all slots\n"); + spin_unlock_irqrestore(&xhci->lock, *flags); + for (i = 1; i <= xhci->max_slots; i++) { + if (!xhci->devs[i]) + continue; + + retval = xhci_disable_and_free_slot(xhci, i); + if (retval) + xhci_err(xhci, "Failed to disable slot %d, %d. Enter test mode anyway\n", + i, retval); + } + spin_lock_irqsave(&xhci->lock, *flags); + /* Put all ports to the Disable state by clear PP */ + xhci_dbg(xhci, "Disable all port (PP = 0)\n"); + /* Power off USB3 ports*/ + for (i = 0; i < xhci->usb3_rhub.num_ports; i++) + xhci_set_port_power(xhci, xhci->usb3_rhub.ports[i], false, flags); + /* Power off USB2 ports*/ + for (i = 0; i < xhci->usb2_rhub.num_ports; i++) + xhci_set_port_power(xhci, xhci->usb2_rhub.ports[i], false, flags); + /* Stop the controller */ + xhci_dbg(xhci, "Stop controller\n"); + retval = xhci_halt(xhci); + if (retval) + return retval; + /* Disable runtime PM for test mode */ + pm_runtime_forbid(xhci_to_hcd(xhci)->self.controller); + /* Set PORTPMSC.PTC field to enter selected test mode */ + xhci_dbg(xhci, "Enter Test Mode: %u, Port_id=%d\n", test_mode, portnum + 1); + xhci_port_set_test_mode(xhci, test_mode, portnum); + return retval; +} + +static int xhci_exit_test_mode(struct xhci_hcd *xhci) +{ + int retval; + + if (!xhci->test_mode) { + xhci_err(xhci, "Not in test mode, do nothing.\n"); + return 0; + } + if (xhci->test_mode == USB_TEST_FORCE_ENABLE && + !(xhci->xhc_state & XHCI_STATE_HALTED)) { + retval = xhci_halt(xhci); + if (retval) + return retval; + } + pm_runtime_allow(xhci_to_hcd(xhci)->self.controller); + xhci->test_mode = 0; + return xhci_reset(xhci, XHCI_RESET_SHORT_USEC); +} + +/** + * xhci_port_is_tunneled() - Check if USB3 connection is tunneled over USB4 + * @xhci: xhci host controller + * @port: USB3 port to be checked. + * + * Some hosts can detect if a USB3 connection is native USB3 or tunneled over + * USB4. Intel hosts expose this via vendor specific extended capability 206 + * eSS PORT registers TUNEN (tunnel enabled) bit. + * + * A USB3 device must be connected to the port to detect the tunnel. + * + * Return: link tunnel mode enum, USB_LINK_UNKNOWN if host is incapable of + * detecting USB3 over USB4 tunnels. USB_LINK_NATIVE or USB_LINK_TUNNELED + * otherwise. + */ +enum usb_link_tunnel_mode xhci_port_is_tunneled(struct xhci_hcd *xhci, + struct xhci_port *port) +{ + struct usb_hcd *hcd; + void __iomem *base; + u32 offset; + + /* Don't try and probe this capability for non-Intel hosts */ + hcd = xhci_to_hcd(xhci); + if (!dev_is_pci(hcd->self.controller) || + to_pci_dev(hcd->self.controller)->vendor != PCI_VENDOR_ID_INTEL) + return USB_LINK_UNKNOWN; + + base = &xhci->cap_regs->hc_capbase; + offset = xhci_find_next_ext_cap(base, 0, XHCI_EXT_CAPS_INTEL_SPR_SHADOW); + + if (offset && offset <= XHCI_INTEL_SPR_ESS_PORT_OFFSET) { + offset = XHCI_INTEL_SPR_ESS_PORT_OFFSET + port->hcd_portnum * 0x20; + + if (readl(base + offset) & XHCI_INTEL_SPR_TUNEN) + return USB_LINK_TUNNELED; + else + return USB_LINK_NATIVE; + } + + return USB_LINK_UNKNOWN; +} + +void xhci_set_link_state(struct xhci_hcd *xhci, struct xhci_port *port, + u32 link_state) +{ + u32 temp; + u32 portsc; + + portsc = xhci_portsc_readl(port); + temp = xhci_port_state_to_neutral(portsc); + temp &= ~PORT_PLS_MASK; + temp |= PORT_LINK_STROBE | link_state; + xhci_portsc_writel(port, temp); + + xhci_dbg(xhci, "Set port %d-%d link state, portsc: 0x%x, write 0x%x", + port->rhub->hcd->self.busnum, port->hcd_portnum + 1, + portsc, temp); +} + +static void xhci_set_remote_wake_mask(struct xhci_hcd *xhci, + struct xhci_port *port, u16 wake_mask) +{ + u32 temp; + + temp = xhci_portsc_readl(port); + 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_portsc_writel(port, temp); +} + +/* Test and clear port RWC bit */ +void xhci_test_and_clear_bit(struct xhci_hcd *xhci, struct xhci_port *port, + u32 port_bit) +{ + u32 temp; + + temp = xhci_portsc_readl(port); + if (temp & port_bit) { + temp = xhci_port_state_to_neutral(temp); + temp |= port_bit; + xhci_portsc_writel(port, temp); + } +} + +/* Updates Link Status for super Speed port */ +static void xhci_hub_report_usb3_link_state(struct xhci_hcd *xhci, u32 *status, u32 portsc) +{ + u32 pls = portsc & PORT_PLS_MASK; + + /* + * CAS indicates that a warm reset is required, it may be set in any + * link state and is only present on roothubs. + */ + if (portsc & PORT_CAS) { + /* + * If not already in Compliance or Inactive state, + * report Compliance Mode so the hub logic triggers a warm reset. + */ + if (pls != XDEV_COMP_MODE && pls != XDEV_INACTIVE) + pls = USB_SS_PORT_LS_COMP_MOD; + + /* Signal a connection change to force a reset */ + *status |= USB_PORT_STAT_CONNECTION; + } else if (pls == XDEV_RESUME) { + /* + * Resume is an internal xHCI-only state and must not be exposed + * to usbcore. Report it as U3 so transfers are blocked. + */ + pls = USB_SS_PORT_LS_U3; + } else if (pls == XDEV_COMP_MODE) { + /* + * Some hardware may enter Compliance Mode without CAS. + * Fake a connection event so usbcore notices and resets the port. + */ + if (xhci->quirks & XHCI_COMP_MODE_QUIRK) + *status |= 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. + */ +static void xhci_del_comp_mod_timer(struct xhci_hcd *xhci, u32 portsc, int portnum) +{ + u32 all_ports_seen_u0 = ((1 << xhci->usb3_rhub.num_ports) - 1); + bool port_in_u0 = ((portsc & 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 << portnum; + if (xhci->port_status_u0 == all_ports_seen_u0) { + timer_delete_sync(&xhci->comp_mode_recovery_timer); + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "All USB3 ports have entered U0 already!"); + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "Compliance Mode Recovery Timer Deleted."); + } + } +} + +static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, + u32 portsc, + unsigned long *flags) +{ + struct xhci_bus_state *bus_state; + struct xhci_hcd *xhci; + struct usb_hcd *hcd; + int portnum; + + hcd = port->rhub->hcd; + bus_state = &port->rhub->bus_state; + xhci = hcd_to_xhci(hcd); + portnum = port->hcd_portnum; + + if ((portsc & PORT_RESET) || !(portsc & PORT_PE)) { + return -EINVAL; + } + /* did port event handler already start resume timing? */ + if (!port->resume_timestamp) { + /* If not, maybe we are in a host initiated resume? */ + if (test_bit(portnum, &bus_state->resuming_ports)) { + /* Host initiated resume doesn't time the resume + * signalling using resume_done[]. + * It manually sets RESUME state, sleeps 20ms + * and sets U0 state. This should probably be + * changed, but not right now. + */ + } else { + /* port resume was discovered now and here, + * start resume timing + */ + unsigned long timeout = jiffies + + msecs_to_jiffies(USB_RESUME_TIMEOUT); + + set_bit(portnum, &bus_state->resuming_ports); + port->resume_timestamp = timeout; + mod_timer(&hcd->rh_timer, timeout); + usb_hcd_start_port_resume(&hcd->self, portnum); + } + /* Has resume been signalled for USB_RESUME_TIME yet? */ + } else if (time_after_eq(jiffies, port->resume_timestamp)) { + int time_left; + + xhci_dbg(xhci, "resume USB2 port %d-%d\n", + hcd->self.busnum, portnum + 1); + + port->resume_timestamp = 0; + clear_bit(portnum, &bus_state->resuming_ports); + + reinit_completion(&port->rexit_done); + port->rexit_active = true; + + xhci_test_and_clear_bit(xhci, port, PORT_PLC); + xhci_set_link_state(xhci, port, XDEV_U0); + + spin_unlock_irqrestore(&xhci->lock, *flags); + time_left = wait_for_completion_timeout( + &port->rexit_done, + msecs_to_jiffies(XHCI_MAX_REXIT_TIMEOUT_MS)); + spin_lock_irqsave(&xhci->lock, *flags); + + if (time_left) { + if (!port->slot_id) { + xhci_dbg(xhci, "slot_id is zero\n"); + return -ENODEV; + } + xhci_ring_device(xhci, port->slot_id); + } else { + int port_status = xhci_portsc_readl(port); + + xhci_warn(xhci, "Port resume timed out, port %d-%d: 0x%x\n", + hcd->self.busnum, portnum + 1, port_status); + /* + * keep rexit_active set if U0 transition failed so we + * know to report PORT_STAT_SUSPEND status back to + * usbcore. It will be cleared later once the port is + * out of RESUME/U3 state + */ + } + + usb_hcd_end_port_resume(&hcd->self, portnum); + bus_state->port_c_suspend |= 1 << portnum; + bus_state->suspended_ports &= ~(1 << portnum); + } + + return 0; +} + +static u32 xhci_get_ext_port_status(u32 portsc, u32 port_li) +{ + u32 ext_stat = 0; + int speed_id; + + /* only support rx and tx lane counts of 1 in usb3.1 spec */ + speed_id = DEV_PORT_SPEED(portsc); + ext_stat |= speed_id; /* bits 3:0, RX speed id */ + ext_stat |= speed_id << 4; /* bits 7:4, TX speed id */ + + ext_stat |= PORT_RX_LANES(port_li) << 8; /* bits 11:8 Rx lane count */ + ext_stat |= PORT_TX_LANES(port_li) << 12; /* bits 15:12 Tx lane count */ + + return ext_stat; +} + +static void xhci_get_usb3_port_status(struct xhci_port *port, u32 *status, + u32 portsc) +{ + struct xhci_bus_state *bus_state; + struct xhci_hcd *xhci; + struct usb_hcd *hcd; + u32 link_state; + u32 portnum; + + bus_state = &port->rhub->bus_state; + xhci = hcd_to_xhci(port->rhub->hcd); + hcd = port->rhub->hcd; + link_state = portsc & PORT_PLS_MASK; + portnum = port->hcd_portnum; + + /* USB3 specific wPortChange bits + * + * Port link change with port in resume state should not be + * reported to usbcore, as this is an internal state to be + * handled by xhci driver. Reporting PLC to usbcore may + * cause usbcore clearing PLC first and port change event + * irq won't be generated. + */ + + if (portsc & PORT_PLC && (link_state != XDEV_RESUME)) + *status |= USB_PORT_STAT_C_LINK_STATE << 16; + if (portsc & PORT_WRC) + *status |= USB_PORT_STAT_C_BH_RESET << 16; + if (portsc & PORT_CEC) + *status |= USB_PORT_STAT_C_CONFIG_ERROR << 16; + + /* USB3 specific wPortStatus bits */ + if (portsc & PORT_POWER) + *status |= USB_SS_PORT_STAT_POWER; + + /* no longer suspended or resuming */ + if (link_state != XDEV_U3 && + link_state != XDEV_RESUME && + link_state != XDEV_RECOVERY) { + /* remote wake resume signaling complete */ + if (bus_state->port_remote_wakeup & (1 << portnum)) { + bus_state->port_remote_wakeup &= ~(1 << portnum); + usb_hcd_end_port_resume(&hcd->self, portnum); + } + bus_state->suspended_ports &= ~(1 << portnum); + } + + xhci_hub_report_usb3_link_state(xhci, status, portsc); + xhci_del_comp_mod_timer(xhci, portsc, portnum); +} + +static void xhci_get_usb2_port_status(struct xhci_port *port, u32 *status, + u32 portsc, unsigned long *flags) +{ + struct xhci_bus_state *bus_state; + u32 link_state; + u32 portnum; + int err; + + bus_state = &port->rhub->bus_state; + link_state = portsc & PORT_PLS_MASK; + portnum = port->hcd_portnum; + + /* USB2 wPortStatus bits */ + if (portsc & PORT_POWER) { + *status |= USB_PORT_STAT_POWER; + + /* link state is only valid if port is powered */ + if (link_state == XDEV_U3) + *status |= USB_PORT_STAT_SUSPEND; + if (link_state == XDEV_U2) + *status |= USB_PORT_STAT_L1; + if (link_state == XDEV_U0) { + if (bus_state->suspended_ports & (1 << portnum)) { + bus_state->suspended_ports &= ~(1 << portnum); + bus_state->port_c_suspend |= 1 << portnum; + } + } + if (link_state == XDEV_RESUME) { + err = xhci_handle_usb2_port_link_resume(port, portsc, + flags); + if (err < 0) + *status = 0xffffffff; + else if (port->resume_timestamp || port->rexit_active) + *status |= USB_PORT_STAT_SUSPEND; + } + } + + /* + * Clear usb2 resume signalling variables if port is no longer suspended + * or resuming. Port either resumed to U0/U1/U2, disconnected, or in a + * error state. Resume related variables should be cleared in all those cases. + */ + if (link_state != XDEV_U3 && link_state != XDEV_RESUME) { + if (port->resume_timestamp || + test_bit(portnum, &bus_state->resuming_ports)) { + port->resume_timestamp = 0; + clear_bit(portnum, &bus_state->resuming_ports); + usb_hcd_end_port_resume(&port->rhub->hcd->self, portnum); + } + port->rexit_active = 0; + bus_state->suspended_ports &= ~(1 << portnum); + } +} + +/* + * Converts a raw xHCI port status into the format that external USB 2.0 or USB + * 3.0 hubs use. + * + * Possible side effects: + * - Mark a port as being done with device resume, + * and ring the endpoint doorbells. + * - Stop the Synopsys redriver Compliance Mode polling. + * - Drop and reacquire the xHCI lock, in order to wait for port resume. + */ +static u32 xhci_get_port_status(struct usb_hcd *hcd, struct xhci_bus_state *bus_state, + int portnum, u32 portsc, unsigned long *flags) + __releases(&xhci->lock) + __acquires(&xhci->lock) +{ + u32 status = 0; + struct xhci_hub *rhub; + struct xhci_port *port; + + rhub = xhci_get_rhub(hcd); + port = rhub->ports[portnum]; + + /* common wPortChange bits */ + if (portsc & PORT_CSC) + status |= USB_PORT_STAT_C_CONNECTION << 16; + if (portsc & PORT_PEC) + status |= USB_PORT_STAT_C_ENABLE << 16; + if (portsc & PORT_OCC) + status |= USB_PORT_STAT_C_OVERCURRENT << 16; + if (portsc & PORT_RC) + status |= USB_PORT_STAT_C_RESET << 16; + + /* common wPortStatus bits */ + if (portsc & PORT_CONNECT) { + status |= USB_PORT_STAT_CONNECTION; + status |= xhci_port_speed(portsc); + } + if (portsc & PORT_PE) + status |= USB_PORT_STAT_ENABLE; + if (portsc & PORT_OC) + status |= USB_PORT_STAT_OVERCURRENT; + if (portsc & PORT_RESET) + status |= USB_PORT_STAT_RESET; + + /* USB2 and USB3 specific bits, including Port Link State */ + if (hcd->speed >= HCD_USB3) + xhci_get_usb3_port_status(port, &status, portsc); + else + xhci_get_usb2_port_status(port, &status, portsc, flags); + + if (bus_state->port_c_suspend & (1 << portnum)) + status |= USB_PORT_STAT_C_SUSPEND << 16; + + return status; +} + +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 portsc, portpmsc, status; + int retval = 0; + struct xhci_bus_state *bus_state; + u16 link_state; + u16 wake_mask; + u8 timeout; + u8 test_mode; + u8 desc_type; + struct xhci_hub *rhub; + struct xhci_port **ports; + struct xhci_port *port; + int portnum; + + rhub = xhci_get_rhub(hcd); + ports = rhub->ports; + max_ports = rhub->num_ports; + bus_state = &rhub->bus_state; + + 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: + desc_type = (wValue & 0xff00) >> 8; + /* 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 || desc_type != USB_DT_SS_HUB)) { + 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: + desc_type = (wValue & 0xff00) >> 8; + if (desc_type != USB_DT_BOS) + goto error; + + if (hcd->speed < HCD_USB3) + goto error; + + retval = xhci_create_usb3x_bos_desc(xhci, buf, wLength); + spin_unlock_irqrestore(&xhci->lock, flags); + return retval; + case GetPortStatus: + portnum = (wIndex & 0xff) - 1; + if (!in_range(portnum, 0, max_ports)) + goto error; + + port = ports[portnum]; + portsc = xhci_portsc_readl(port); + if (portsc == ~(u32)0) { + xhci_hc_died(xhci); + retval = -ENODEV; + break; + } + trace_xhci_get_port_status(port, portsc); + status = xhci_get_port_status(hcd, bus_state, portnum, portsc, &flags); + if (status == 0xffffffff) + goto error; + + xhci_dbg(xhci, "Get port status %d-%d read: 0x%x, return 0x%x", + hcd->self.busnum, portnum + 1, portsc, status); + + put_unaligned(cpu_to_le32(status), (__le32 *) buf); + /* if USB 3.1 extended port status return additional 4 bytes */ + if (wValue == HUB_EXT_PORT_STATUS) { + u32 port_li; + + if (hcd->speed < HCD_USB31 || wLength != 8) { + xhci_err(xhci, "get ext port status invalid parameter\n"); + retval = -EINVAL; + break; + } + port_li = readl(&port->port_reg->portli); + status = xhci_get_ext_port_status(portsc, port_li); + put_unaligned_le32(status, &buf[4]); + } + break; + case SetPortFeature: + portnum = (wIndex & 0xff) - 1; + if (!in_range(portnum, 0, max_ports)) + goto error; + + port = ports[portnum]; + portsc = xhci_portsc_readl(port); + if (portsc == ~(u32)0) { + xhci_hc_died(xhci); + retval = -ENODEV; + break; + } + portsc = xhci_port_state_to_neutral(portsc); + /* FIXME: What new port features do we need to support? */ + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + portsc = xhci_portsc_readl(port); + if ((portsc & PORT_PLS_MASK) != XDEV_U0) { + /* Resume the port to U0 first */ + xhci_set_link_state(xhci, port, 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. + */ + portsc = xhci_portsc_readl(port); + if ((portsc & PORT_PE) == 0 || (portsc & PORT_RESET) || + (portsc & PORT_PLS_MASK) >= XDEV_U3) { + xhci_warn(xhci, "USB core suspending port %d-%d not in U0/U1/U2\n", + hcd->self.busnum, portnum + 1); + goto error; + } + + if (!port->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, port->slot_id, 1); + spin_lock_irqsave(&xhci->lock, flags); + + xhci_set_link_state(xhci, port, XDEV_U3); + + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(10); /* wait device to enter */ + spin_lock_irqsave(&xhci->lock, flags); + + portsc = xhci_portsc_readl(port); + bus_state->suspended_ports |= 1 << portnum; + break; + case USB_PORT_FEAT_LINK_STATE: + link_state = (wIndex & 0xff00) >> 3; + portsc = xhci_portsc_readl(port); + /* Disable port */ + if (link_state == USB_SS_PORT_LS_SS_DISABLED) { + xhci_dbg(xhci, "Disable port %d-%d\n", + hcd->self.busnum, portnum + 1); + portsc = xhci_port_state_to_neutral(portsc); + /* + * Clear all change bits, so that we get a new + * connection event. + */ + portsc |= PORT_CSC | PORT_PEC | PORT_WRC | + PORT_OCC | PORT_RC | PORT_PLC | + PORT_CEC; + xhci_portsc_writel(port, portsc | PORT_PE); + portsc = xhci_portsc_readl(port); + break; + } + + /* Put link in RxDetect (enable port) */ + if (link_state == USB_SS_PORT_LS_RX_DETECT) { + xhci_dbg(xhci, "Enable port %d-%d\n", + hcd->self.busnum, portnum + 1); + xhci_set_link_state(xhci, port, XDEV_RXDETECT); + portsc = xhci_portsc_readl(port); + break; + } + + /* + * For xHCI 1.1 according to section 4.19.1.2.4.1 a + * root hub port's transition to compliance mode upon + * detecting LFPS timeout may be controlled by an + * Compliance Transition Enabled (CTE) flag (not + * software visible). This flag is set by writing 0xA + * to PORTSC PLS field which will allow transition to + * compliance mode the next time LFPS timeout is + * encountered. A warm reset will clear it. + * + * The CTE flag is only supported if the HCCPARAMS2 CTC + * flag is set, otherwise, the compliance substate is + * automatically entered as on 1.0 and prior. + */ + if (link_state == USB_SS_PORT_LS_COMP_MOD) { + if (!(xhci->hcc_params2 & HCC2_CTC)) { + xhci_dbg(xhci, "CTC flag is 0, port already supports entering compliance mode\n"); + break; + } + + if ((portsc & PORT_CONNECT)) { + xhci_warn(xhci, "Can't set compliance mode when port is connected\n"); + goto error; + } + + xhci_dbg(xhci, "Enable compliance mode transition for port %d-%d\n", + hcd->self.busnum, portnum + 1); + xhci_set_link_state(xhci, port, XDEV_COMP_MODE); + + portsc = xhci_portsc_readl(port); + break; + } + /* Port must be enabled */ + if (!(portsc & PORT_PE)) { + retval = -ENODEV; + break; + } + /* Can't set port link state above '3' (U3) */ + if (link_state > USB_SS_PORT_LS_U3) { + xhci_warn(xhci, "Cannot set port %d-%d link state %d\n", + hcd->self.busnum, portnum + 1, link_state); + goto error; + } + + /* + * set link to U0, steps depend on current link state. + * U3: set link to U0 and wait for u3exit completion. + * U1/U2: no PLC complete event, only set link to U0. + * Resume/Recovery: device initiated U0, only wait for + * completion + */ + if (link_state == USB_SS_PORT_LS_U0) { + u32 pls = portsc & PORT_PLS_MASK; + bool wait_u0 = false; + + /* already in U0 */ + if (pls == XDEV_U0) + break; + if (pls == XDEV_U3 || + pls == XDEV_RESUME || + pls == XDEV_RECOVERY) { + wait_u0 = true; + reinit_completion(&port->u3exit_done); + } + if (pls <= XDEV_U3) /* U1, U2, U3 */ + xhci_set_link_state(xhci, port, XDEV_U0); + if (!wait_u0) { + if (pls > XDEV_U3) + goto error; + break; + } + spin_unlock_irqrestore(&xhci->lock, flags); + if (!wait_for_completion_timeout(&port->u3exit_done, + msecs_to_jiffies(500))) + xhci_dbg(xhci, "missing U0 port change event for port %d-%d\n", + hcd->self.busnum, portnum + 1); + spin_lock_irqsave(&xhci->lock, flags); + portsc = xhci_portsc_readl(port); + break; + } + + if (link_state == USB_SS_PORT_LS_U3) { + int retries = 16; + if (port->slot_id) { + /* unlock to execute stop endpoint + * commands */ + spin_unlock_irqrestore(&xhci->lock, + flags); + xhci_stop_device(xhci, port->slot_id, 1); + spin_lock_irqsave(&xhci->lock, flags); + } + xhci_set_link_state(xhci, port, XDEV_U3); + spin_unlock_irqrestore(&xhci->lock, flags); + while (retries--) { + usleep_range(4000, 8000); + portsc = xhci_portsc_readl(port); + if ((portsc & PORT_PLS_MASK) == XDEV_U3) + break; + } + spin_lock_irqsave(&xhci->lock, flags); + portsc = xhci_portsc_readl(port); + bus_state->suspended_ports |= 1 << portnum; + } + 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, hub_wq will ignore the roothub events until + * the roothub is registered. + */ + xhci_set_port_power(xhci, port, true, &flags); + break; + case USB_PORT_FEAT_RESET: + portsc |= PORT_RESET; + xhci_portsc_writel(port, portsc); + + portsc = xhci_portsc_readl(port); + xhci_dbg(xhci, "set port reset, actual port %d-%d status = 0x%x\n", + hcd->self.busnum, portnum + 1, portsc); + break; + case USB_PORT_FEAT_REMOTE_WAKE_MASK: + wake_mask = wIndex & 0xff00; + xhci_set_remote_wake_mask(xhci, port, wake_mask); + portsc = xhci_portsc_readl(port); + xhci_dbg(xhci, "set port remote wake mask, actual port %d-%d status = 0x%x\n", + hcd->self.busnum, portnum + 1, portsc); + break; + case USB_PORT_FEAT_BH_PORT_RESET: + portsc |= PORT_WR; + xhci_portsc_writel(port, portsc); + portsc = xhci_portsc_readl(port); + break; + case USB_PORT_FEAT_U1_TIMEOUT: + if (hcd->speed < HCD_USB3) + goto error; + + timeout = (wIndex & 0xff00) >> 8; + portpmsc = readl(&port->port_reg->portpmsc); + portpmsc &= ~PORT_U1_TIMEOUT_MASK; + portpmsc |= PORT_U1_TIMEOUT(timeout); + writel(portpmsc, &port->port_reg->portpmsc); + break; + case USB_PORT_FEAT_U2_TIMEOUT: + if (hcd->speed < HCD_USB3) + goto error; + + timeout = (wIndex & 0xff00) >> 8; + portpmsc = readl(&port->port_reg->portpmsc); + portpmsc &= ~PORT_U2_TIMEOUT_MASK; + portpmsc |= PORT_U2_TIMEOUT(timeout); + writel(portpmsc, &port->port_reg->portpmsc); + break; + case USB_PORT_FEAT_TEST: + /* 4.19.6 Port Test Modes (USB2 Test Mode) */ + if (hcd->speed != HCD_USB2) + goto error; + + test_mode = (wIndex & 0xff00) >> 8; + if (test_mode > USB_TEST_FORCE_ENABLE || + test_mode < USB_TEST_J) + goto error; + retval = xhci_enter_test_mode(xhci, test_mode, portnum, &flags); + break; + default: + goto error; + } + /* unblock any posted writes */ + portsc = xhci_portsc_readl(port); + break; + case ClearPortFeature: + portnum = (wIndex & 0xff) - 1; + if (!in_range(portnum, 0, max_ports)) + goto error; + + port = ports[portnum]; + portsc = xhci_portsc_readl(port); + if (portsc == ~(u32)0) { + xhci_hc_died(xhci); + retval = -ENODEV; + break; + } + /* FIXME: What new port features do we need to support? */ + portsc = xhci_port_state_to_neutral(portsc); + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + portsc = xhci_portsc_readl(port); + xhci_dbg(xhci, "clear USB_PORT_FEAT_SUSPEND\n"); + xhci_dbg(xhci, "PORTSC %04x\n", portsc); + if (portsc & PORT_RESET) + goto error; + if ((portsc & PORT_PLS_MASK) == XDEV_U3) { + if ((portsc & PORT_PE) == 0) + goto error; + + set_bit(portnum, &bus_state->resuming_ports); + usb_hcd_start_port_resume(&hcd->self, portnum); + xhci_set_link_state(xhci, port, XDEV_RESUME); + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(USB_RESUME_TIMEOUT); + spin_lock_irqsave(&xhci->lock, flags); + xhci_set_link_state(xhci, port, XDEV_U0); + clear_bit(portnum, &bus_state->resuming_ports); + usb_hcd_end_port_resume(&hcd->self, portnum); + } + bus_state->port_c_suspend |= 1 << portnum; + + if (!port->slot_id) { + xhci_dbg(xhci, "slot_id is zero\n"); + goto error; + } + xhci_ring_device(xhci, port->slot_id); + break; + case USB_PORT_FEAT_C_SUSPEND: + bus_state->port_c_suspend &= ~(1 << portnum); + fallthrough; + 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: + case USB_PORT_FEAT_C_PORT_CONFIG_ERROR: + xhci_clear_port_change_bit(xhci, wValue, port, portsc); + break; + case USB_PORT_FEAT_ENABLE: + xhci_disable_port(xhci, port); + break; + case USB_PORT_FEAT_POWER: + xhci_set_port_power(xhci, port, false, &flags); + break; + case USB_PORT_FEAT_TEST: + retval = xhci_exit_test_mode(xhci); + break; + default: + goto error; + } + break; + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore(&xhci->lock, flags); + return retval; +} +EXPORT_SYMBOL_GPL(xhci_hub_control); + +/* + * 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; + struct xhci_bus_state *bus_state; + bool reset_change = false; + struct xhci_hub *rhub; + struct xhci_port **ports; + + rhub = xhci_get_rhub(hcd); + ports = rhub->ports; + max_ports = rhub->num_ports; + bus_state = &rhub->bus_state; + + /* 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. + */ + spin_lock_irqsave(&xhci->lock, flags); + + status = bus_state->resuming_ports; + + /* + * SS devices are only visible to roothub after link training completes. + * Keep polling roothubs for a grace period after xHC start + */ + if (hcd->speed >= HCD_USB3 && xhci->run_graceperiod) { + if (time_before(jiffies, xhci->run_graceperiod)) + status = 1; + else + xhci->run_graceperiod = 0; + } + + mask = PORT_CSC | PORT_PEC | PORT_OCC | PORT_PLC | PORT_WRC | PORT_CEC; + + /* For each port, did anything change? If so, set that bit in buf. */ + for (i = 0; i < max_ports; i++) { + temp = xhci_portsc_readl(ports[i]); + if (temp == ~(u32)0) { + xhci_hc_died(xhci); + retval = -ENODEV; + break; + } + trace_xhci_hub_status_data(ports[i], temp); + + if ((temp & mask) != 0 || + (bus_state->port_c_suspend & 1 << i) || + (ports[i]->resume_timestamp && time_after_eq( + jiffies, ports[i]->resume_timestamp))) { + buf[(i + 1) / 8] |= 1 << (i + 1) % 8; + status = 1; + } + if ((temp & PORT_RC)) + reset_change = true; + if (temp & PORT_OC) + status = 1; + } + if (!status && !reset_change) { + xhci_dbg(xhci, "%s: stopping usb%d port polling\n", + __func__, hcd->self.busnum); + 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; + struct xhci_bus_state *bus_state; + unsigned long flags; + struct xhci_hub *rhub; + struct xhci_port **ports; + u32 portsc_buf[USB_MAXCHILDREN]; + bool wake_enabled; + + rhub = xhci_get_rhub(hcd); + ports = rhub->ports; + max_ports = rhub->num_ports; + bus_state = &rhub->bus_state; + wake_enabled = hcd->self.root_hub->do_remote_wakeup; + + spin_lock_irqsave(&xhci->lock, flags); + + if (wake_enabled) { + if (bus_state->resuming_ports || /* USB2 */ + bus_state->port_remote_wakeup) { /* USB3 */ + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "usb%d bus suspend to fail because a port is resuming\n", + hcd->self.busnum); + return -EBUSY; + } + } + /* + * Prepare ports for suspend, but don't write anything before all ports + * are checked and we know bus suspend can proceed + */ + bus_state->bus_suspended = 0; + port_index = max_ports; + while (port_index--) { + u32 t1, t2; + int retries = 10; +retry: + t1 = xhci_portsc_readl(ports[port_index]); + t2 = xhci_port_state_to_neutral(t1); + portsc_buf[port_index] = 0; + + /* + * Give a USB3 port in link training time to finish, but don't + * prevent suspend as port might be stuck + */ + if ((hcd->speed >= HCD_USB3) && retries-- && + (t1 & PORT_PLS_MASK) == XDEV_POLLING) { + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(XHCI_PORT_POLLING_LFPS_TIME); + spin_lock_irqsave(&xhci->lock, flags); + xhci_dbg(xhci, "port %d-%d polling in bus suspend, waiting\n", + hcd->self.busnum, port_index + 1); + goto retry; + } + /* bail out if port detected a over-current condition */ + if (t1 & PORT_OC) { + bus_state->bus_suspended = 0; + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "Bus suspend bailout, port over-current detected\n"); + return -EBUSY; + } + /* suspend ports in U0, or bail out for new connect changes */ + if ((t1 & PORT_PE) && (t1 & PORT_PLS_MASK) == XDEV_U0) { + if ((t1 & PORT_CSC) && wake_enabled) { + bus_state->bus_suspended = 0; + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "Bus suspend bailout, port connect change\n"); + return -EBUSY; + } + xhci_dbg(xhci, "port %d-%d not suspended\n", + hcd->self.busnum, port_index + 1); + 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 + * is enabled, so also enable remote wake here. + */ + if (wake_enabled) { + 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; + } + + if ((xhci->quirks & XHCI_U2_DISABLE_WAKE) && + (hcd->speed < HCD_USB3)) { + if (usb_amd_pt_check_port(hcd->self.controller, + port_index)) + t2 &= ~PORT_WAKE_BITS; + } + } else + t2 &= ~PORT_WAKE_BITS; + + t1 = xhci_port_state_to_neutral(t1); + if (t1 != t2) + portsc_buf[port_index] = t2; + } + + /* write port settings, stopping and suspending ports if needed */ + port_index = max_ports; + while (port_index--) { + if (!portsc_buf[port_index]) + continue; + if (test_bit(port_index, &bus_state->bus_suspended)) { + int slot_id = ports[port_index]->slot_id; + if (slot_id) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_stop_device(xhci, slot_id, 1); + spin_lock_irqsave(&xhci->lock, flags); + } + } + xhci_portsc_writel(ports[port_index], portsc_buf[port_index]); + } + hcd->state = HC_STATE_SUSPENDED; + bus_state->next_statechange = jiffies + msecs_to_jiffies(10); + spin_unlock_irqrestore(&xhci->lock, flags); + + if (bus_state->bus_suspended) + usleep_range(5000, 10000); + + return 0; +} + +/* + * Workaround for missing Cold Attach Status (CAS) if device re-plugged in S3. + * warm reset a USB3 device stuck in polling or compliance mode after resume. + * See Intel 100/c230 series PCH specification update Doc #332692-006 Errata #8 + */ +static bool xhci_port_missing_cas_quirk(struct xhci_port *port) +{ + u32 portsc; + + portsc = xhci_portsc_readl(port); + + /* if any of these are set we are not stuck */ + if (portsc & (PORT_CONNECT | PORT_CAS)) + return false; + + if (((portsc & PORT_PLS_MASK) != XDEV_POLLING) && + ((portsc & PORT_PLS_MASK) != XDEV_COMP_MODE)) + return false; + + /* clear wakeup/change bits, and do a warm port reset */ + portsc &= ~(PORT_RWC_BITS | PORT_CEC | PORT_WAKE_BITS); + portsc |= PORT_WR; + xhci_portsc_writel(port, portsc); + /* flush write */ + xhci_portsc_readl(port); + return true; +} + +int xhci_bus_resume(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_bus_state *bus_state; + unsigned long flags; + int max_ports, port_index; + int sret; + u32 next_state; + u32 portsc; + struct xhci_hub *rhub; + struct xhci_port **ports; + bool disabled_irq = false; + + rhub = xhci_get_rhub(hcd); + ports = rhub->ports; + max_ports = rhub->num_ports; + bus_state = &rhub->bus_state; + + 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; + } + + /* bus specific resume for ports we suspended at bus_suspend */ + if (hcd->speed >= HCD_USB3) { + next_state = XDEV_U0; + } else { + next_state = XDEV_RESUME; + if (bus_state->bus_suspended) { + /* + * prevent port event interrupts from interfering + * with usb2 port resume process + */ + xhci_disable_interrupter(xhci, xhci->interrupters[0]); + disabled_irq = true; + } + } + port_index = max_ports; + while (port_index--) { + portsc = xhci_portsc_readl(ports[port_index]); + + /* warm reset CAS limited ports stuck in polling/compliance */ + if ((xhci->quirks & XHCI_MISSING_CAS) && + (hcd->speed >= HCD_USB3) && + xhci_port_missing_cas_quirk(ports[port_index])) { + xhci_dbg(xhci, "reset stuck port %d-%d\n", + hcd->self.busnum, port_index + 1); + clear_bit(port_index, &bus_state->bus_suspended); + continue; + } + /* resume if we suspended the link, and it is still suspended */ + if (test_bit(port_index, &bus_state->bus_suspended)) + switch (portsc & PORT_PLS_MASK) { + case XDEV_U3: + portsc = xhci_port_state_to_neutral(portsc); + portsc &= ~PORT_PLS_MASK; + portsc |= PORT_LINK_STROBE | next_state; + break; + case XDEV_RESUME: + /* resume already initiated */ + break; + default: + /* not in a resumable state, ignore it */ + clear_bit(port_index, + &bus_state->bus_suspended); + break; + } + /* disable wake for all ports, write new link state if needed */ + portsc &= ~(PORT_RWC_BITS | PORT_CEC | PORT_WAKE_BITS); + xhci_portsc_writel(ports[port_index], portsc); + } + + /* USB2 specific resume signaling delay and U0 link state transition */ + if (hcd->speed < HCD_USB3) { + if (bus_state->bus_suspended) { + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(USB_RESUME_TIMEOUT); + spin_lock_irqsave(&xhci->lock, flags); + } + for_each_set_bit(port_index, &bus_state->bus_suspended, + BITS_PER_LONG) { + /* Clear PLC to poll it later for U0 transition */ + xhci_test_and_clear_bit(xhci, ports[port_index], + PORT_PLC); + xhci_set_link_state(xhci, ports[port_index], XDEV_U0); + } + } + + /* poll for U0 link state complete, both USB2 and USB3 */ + for_each_set_bit(port_index, &bus_state->bus_suspended, BITS_PER_LONG) { + sret = xhci_handshake(&ports[port_index]->port_reg->portsc, PORT_PLC, + PORT_PLC, 10 * 1000); + if (sret) { + xhci_warn(xhci, "port %d-%d resume PLC timeout\n", + hcd->self.busnum, port_index + 1); + continue; + } + xhci_test_and_clear_bit(xhci, ports[port_index], PORT_PLC); + if (ports[port_index]->slot_id) + xhci_ring_device(xhci, ports[port_index]->slot_id); + } + (void) readl(&xhci->op_regs->command); + + bus_state->next_statechange = jiffies + msecs_to_jiffies(5); + /* re-enable interrupter */ + if (disabled_irq) + xhci_enable_interrupter(xhci->interrupters[0]); + + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; +} + +unsigned long xhci_get_resuming_ports(struct usb_hcd *hcd) +{ + struct xhci_hub *rhub = xhci_get_rhub(hcd); + + /* USB3 port wakeups are reported via usb_wakeup_notification() */ + return rhub->bus_state.resuming_ports; /* USB2 ports only */ +} + +#endif /* CONFIG_PM */ diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c new file mode 100644 index 000000000..7a21ac81f --- /dev/null +++ b/drivers/usb/host/xhci-mem.c @@ -0,0 +1,2519 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "xhci.h" +#include "xhci-trace.h" +#include "xhci-debugfs.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 max_packet, + unsigned int num, + gfp_t flags) +{ + struct xhci_segment *seg; + dma_addr_t dma; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + + seg = kzalloc_node(sizeof(*seg), flags, dev_to_node(dev)); + if (!seg) + return NULL; + + seg->trbs = dma_pool_zalloc(xhci->segment_pool, flags, &dma); + if (!seg->trbs) { + kfree(seg); + return NULL; + } + + if (max_packet) { + seg->bounce_buf = kzalloc_node(max_packet, flags, + dev_to_node(dev)); + if (!seg->bounce_buf) { + dma_pool_free(xhci->segment_pool, seg->trbs, dma); + kfree(seg); + return NULL; + } + } + seg->num = num; + 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->bounce_buf); + kfree(seg); +} + +static void xhci_ring_segments_free(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + struct xhci_segment *seg, *next; + + ring->last_seg->next = NULL; + seg = ring->first_seg; + + while (seg) { + next = seg->next; + xhci_segment_free(xhci, seg); + seg = next; + } +} + +/* + * Only for transfer and command rings where driver is the producer, not for + * event rings. + * + * Change the last TRB in the 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_set_link_trb(struct xhci_segment *seg, bool chain_links) +{ + union xhci_trb *trb; + u32 val; + + if (!seg || !seg->next) + return; + + trb = &seg->trbs[TRBS_PER_SEGMENT - 1]; + + /* Set the last TRB in the segment to have a TRB type ID of Link TRB */ + val = le32_to_cpu(trb->link.control); + val &= ~TRB_TYPE_BITMASK; + val |= TRB_TYPE(TRB_LINK); + if (chain_links) + val |= TRB_CHAIN; + trb->link.control = cpu_to_le32(val); + trb->link.segment_ptr = cpu_to_le64(seg->next->dma); +} + +static void xhci_initialize_ring_segments(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + struct xhci_segment *seg; + bool chain_links; + + if (ring->type == TYPE_EVENT) + return; + + chain_links = xhci_link_chain_quirk(xhci, ring->type); + xhci_for_each_ring_seg(ring->first_seg, seg) + xhci_set_link_trb(seg, chain_links); + + /* 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); +} + +void xhci_ring_init(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + xhci_initialize_ring_segments(xhci, ring); + xhci_initialize_ring_info(ring); + trace_xhci_ring_alloc(ring); +} + +/* + * Link the src ring segments to the dst ring. + * Set Toggle Cycle for the new ring if needed. + */ +static void xhci_link_rings(struct xhci_hcd *xhci, struct xhci_ring *src, struct xhci_ring *dst) +{ + struct xhci_segment *seg; + bool chain_links; + + if (!src || !dst) + return; + + /* If the cycle state is 0, set the cycle bit to 1 for all the TRBs */ + if (dst->cycle_state == 0) { + xhci_for_each_ring_seg(src->first_seg, seg) { + for (int i = 0; i < TRBS_PER_SEGMENT; i++) + seg->trbs[i].link.control |= cpu_to_le32(TRB_CYCLE); + } + } + + src->last_seg->next = dst->enq_seg->next; + dst->enq_seg->next = src->first_seg; + if (dst->type != TYPE_EVENT) { + chain_links = xhci_link_chain_quirk(xhci, dst->type); + xhci_set_link_trb(dst->enq_seg, chain_links); + xhci_set_link_trb(src->last_seg, chain_links); + } + dst->num_segs += src->num_segs; + + if (dst->enq_seg == dst->last_seg) { + if (dst->type != TYPE_EVENT) + dst->last_seg->trbs[TRBS_PER_SEGMENT-1].link.control + &= ~cpu_to_le32(LINK_TOGGLE); + + dst->last_seg = src->last_seg; + } else if (dst->type != TYPE_EVENT) { + src->last_seg->trbs[TRBS_PER_SEGMENT-1].link.control &= ~cpu_to_le32(LINK_TOGGLE); + } + + for (seg = dst->enq_seg; seg != dst->last_seg; seg = seg->next) + seg->next->num = seg->num + 1; +} + +/* + * 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 1KB 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. + */ +static int xhci_insert_segment_mapping(struct radix_tree_root *trb_address_map, + struct xhci_ring *ring, + struct xhci_segment *seg, + gfp_t mem_flags) +{ + unsigned long key; + int ret; + + key = (unsigned long)(seg->dma >> TRB_SEGMENT_SHIFT); + /* Skip any segments that were already added. */ + if (radix_tree_lookup(trb_address_map, key)) + return 0; + + ret = radix_tree_maybe_preload(mem_flags); + if (ret) + return ret; + ret = radix_tree_insert(trb_address_map, + key, ring); + radix_tree_preload_end(); + return ret; +} + +static void xhci_remove_segment_mapping(struct radix_tree_root *trb_address_map, + struct xhci_segment *seg) +{ + unsigned long key; + + key = (unsigned long)(seg->dma >> TRB_SEGMENT_SHIFT); + if (radix_tree_lookup(trb_address_map, key)) + radix_tree_delete(trb_address_map, key); +} + +static int xhci_update_stream_segment_mapping( + struct radix_tree_root *trb_address_map, + struct xhci_ring *ring, + struct xhci_segment *first_seg, + gfp_t mem_flags) +{ + struct xhci_segment *seg; + struct xhci_segment *failed_seg; + int ret; + + if (WARN_ON_ONCE(trb_address_map == NULL)) + return 0; + + xhci_for_each_ring_seg(first_seg, seg) { + ret = xhci_insert_segment_mapping(trb_address_map, + ring, seg, mem_flags); + if (ret) + goto remove_streams; + } + + return 0; + +remove_streams: + failed_seg = seg; + xhci_for_each_ring_seg(first_seg, seg) { + xhci_remove_segment_mapping(trb_address_map, seg); + if (seg == failed_seg) + return ret; + } + + return ret; +} + +static void xhci_remove_stream_mapping(struct xhci_ring *ring) +{ + struct xhci_segment *seg; + + if (WARN_ON_ONCE(ring->trb_address_map == NULL)) + return; + + xhci_for_each_ring_seg(ring->first_seg, seg) + xhci_remove_segment_mapping(ring->trb_address_map, seg); +} + +static int xhci_update_stream_mapping(struct xhci_ring *ring, gfp_t mem_flags) +{ + return xhci_update_stream_segment_mapping(ring->trb_address_map, ring, + ring->first_seg, mem_flags); +} + +/* 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; + + trace_xhci_ring_free(ring); + + if (ring->first_seg) { + if (ring->type == TYPE_STREAM) + xhci_remove_stream_mapping(ring); + xhci_ring_segments_free(xhci, ring); + } + + kfree(ring); +} + +void xhci_initialize_ring_info(struct xhci_ring *ring) +{ + /* 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 = 1; +} +EXPORT_SYMBOL_GPL(xhci_initialize_ring_info); + +/* Allocate segments and link them for a ring */ +static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci, struct xhci_ring *ring, gfp_t flags) +{ + struct xhci_segment *prev; + unsigned int num = 0; + + prev = xhci_segment_alloc(xhci, ring->bounce_buf_len, num, flags); + if (!prev) + return -ENOMEM; + num++; + + ring->first_seg = prev; + while (num < ring->num_segs) { + struct xhci_segment *next; + + next = xhci_segment_alloc(xhci, ring->bounce_buf_len, num, flags); + if (!next) + goto free_segments; + + prev->next = next; + prev = next; + num++; + } + ring->last_seg = prev; + + ring->last_seg->next = ring->first_seg; + return 0; + +free_segments: + ring->last_seg = prev; + xhci_ring_segments_free(xhci, ring); + return -ENOMEM; +} + +/* + * 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. + */ +struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, unsigned int num_segs, + enum xhci_ring_type type, unsigned int max_packet, gfp_t flags) +{ + struct xhci_ring *ring; + int ret; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + + ring = kzalloc_node(sizeof(*ring), flags, dev_to_node(dev)); + if (!ring) + return NULL; + + ring->num_segs = num_segs; + ring->bounce_buf_len = max_packet; + INIT_LIST_HEAD(&ring->td_list); + ring->type = type; + if (num_segs == 0) + return ring; + + ret = xhci_alloc_segments_for_ring(xhci, ring, flags); + if (ret) + goto fail; + + return ring; + +fail: + kfree(ring); + return NULL; +} + +void xhci_free_endpoint_ring(struct xhci_hcd *xhci, + struct xhci_virt_device *virt_dev, + unsigned int ep_index) +{ + xhci_ring_free(xhci, virt_dev->eps[ep_index].ring); + virt_dev->eps[ep_index].ring = NULL; +} + +/* + * Expand an existing ring. + * 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_new_segs, gfp_t flags) +{ + struct xhci_ring new_ring; + int ret; + + if (num_new_segs == 0) + return 0; + + new_ring.num_segs = num_new_segs; + new_ring.bounce_buf_len = ring->bounce_buf_len; + new_ring.type = ring->type; + ret = xhci_alloc_segments_for_ring(xhci, &new_ring, flags); + if (ret) + return -ENOMEM; + + xhci_initialize_ring_segments(xhci, &new_ring); + + if (ring->type == TYPE_STREAM) { + ret = xhci_update_stream_segment_mapping(ring->trb_address_map, ring, + new_ring.first_seg, flags); + if (ret) + goto free_segments; + } + + xhci_link_rings(xhci, &new_ring, ring); + trace_xhci_ring_expansion(ring); + xhci_dbg_trace(xhci, trace_xhci_dbg_ring_expansion, + "ring expansion succeed, now has %d segments", + ring->num_segs); + + return 0; + +free_segments: + xhci_ring_segments_free(xhci, &new_ring); + return ret; +} + +struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci, + int type, gfp_t flags) +{ + struct xhci_container_ctx *ctx; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + + if ((type != XHCI_CTX_TYPE_DEVICE) && (type != XHCI_CTX_TYPE_INPUT)) + return NULL; + + ctx = kzalloc_node(sizeof(*ctx), flags, dev_to_node(dev)); + if (!ctx) + return NULL; + + ctx->type = type; + ctx->size = xhci->hcc_params & HCC_64BYTE_CONTEXT ? 2048 : 1024; + if (type == XHCI_CTX_TYPE_INPUT) + ctx->size += CTX_SIZE(xhci->hcc_params); + + ctx->bytes = dma_pool_zalloc(xhci->device_pool, flags, &ctx->dma); + if (!ctx->bytes) { + kfree(ctx); + return NULL; + } + return ctx; +} + +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_container_ctx *xhci_alloc_port_bw_ctx(struct xhci_hcd *xhci, + gfp_t flags) +{ + struct xhci_container_ctx *ctx; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + + ctx = kzalloc_node(sizeof(*ctx), flags, dev_to_node(dev)); + if (!ctx) + return NULL; + + ctx->size = GET_PORT_BW_ARRAY_SIZE; + + ctx->bytes = dma_pool_zalloc(xhci->port_bw_pool, flags, &ctx->dma); + if (!ctx->bytes) { + kfree(ctx); + return NULL; + } + return ctx; +} + +void xhci_free_port_bw_ctx(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx) +{ + if (!ctx) + return; + dma_pool_free(xhci->port_bw_pool, ctx->bytes, ctx->dma); + kfree(ctx); +} + +struct xhci_input_control_ctx *xhci_get_input_control_ctx( + struct xhci_container_ctx *ctx) +{ + if (ctx->type != XHCI_CTX_TYPE_INPUT) + return NULL; + + 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))); +} +EXPORT_SYMBOL_GPL(xhci_get_ep_ctx); + +/***************** 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 device *dev = xhci_to_hcd(xhci)->self.sysdev; + size_t size = array_size(sizeof(struct xhci_stream_ctx), num_stream_ctxs); + + if (size > MEDIUM_STREAM_ARRAY_SIZE) + dma_free_coherent(dev, size, stream_ctx, dma); + else if (size > SMALL_STREAM_ARRAY_SIZE) + dma_pool_free(xhci->medium_streams_pool, stream_ctx, dma); + else + dma_pool_free(xhci->small_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 device *dev = xhci_to_hcd(xhci)->self.sysdev; + size_t size = array_size(sizeof(struct xhci_stream_ctx), num_stream_ctxs); + + if (size > MEDIUM_STREAM_ARRAY_SIZE) + return dma_alloc_coherent(dev, size, dma, mem_flags); + if (size > SMALL_STREAM_ARRAY_SIZE) + return dma_pool_zalloc(xhci->medium_streams_pool, mem_flags, dma); + else + return dma_pool_zalloc(xhci->small_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; +} + +/* + * 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. + */ +struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, + unsigned int num_stream_ctxs, + unsigned int num_streams, + unsigned int max_packet, gfp_t mem_flags) +{ + struct xhci_stream_info *stream_info; + u32 cur_stream; + struct xhci_ring *cur_ring; + u64 addr; + int ret; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + + 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_node(sizeof(*stream_info), mem_flags, + dev_to_node(dev)); + 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 = kcalloc_node( + num_streams, sizeof(struct xhci_ring *), mem_flags, + dev_to_node(dev)); + 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_ring_array; + + /* Allocate everything needed to free the stream rings later */ + stream_info->free_streams_command = + xhci_alloc_command_with_ctx(xhci, 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, TYPE_STREAM, max_packet, mem_flags); + cur_ring = stream_info->stream_rings[cur_stream]; + if (!cur_ring) + goto cleanup_rings; + + xhci_ring_init(xhci, cur_ring); + cur_ring->stream_id = cur_stream; + cur_ring->trb_address_map = &stream_info->trb_address_map; + /* 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, addr); + + ret = xhci_update_stream_mapping(cur_ring, mem_flags); + + trace_xhci_alloc_stream_info_ctx(stream_info, cur_stream); + 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). + */ + + 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) { + xhci_ring_free(xhci, cur_ring); + stream_info->stream_rings[cur_stream] = NULL; + } + } + xhci_free_command(xhci, stream_info->free_streams_command); +cleanup_ctx: + xhci_free_stream_ctx(xhci, + stream_info->num_stream_ctxs, + stream_info->stream_ctx_array, + stream_info->ctx_array_dma); +cleanup_ring_array: + 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_trace(xhci, trace_xhci_dbg_context_change, + "Setting number of stream ctx array entries to %u", + 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_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; + + 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) { + 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); + + kfree(stream_info->stream_rings); + kfree(stream_info); +} + + +/***************** Device context manipulation *************************/ + +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 root hub port pointer set correctly. + */ + if (!virt_dev->rhub_port) { + xhci_dbg(xhci, "Bad rhub port.\n"); + return; + } + + tt_list_head = &(xhci->rh_bw[virt_dev->rhub_port->hw_portnum].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; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + + 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_node(sizeof(*tt_info), mem_flags, + dev_to_node(dev)); + if (!tt_info) + goto free_tts; + INIT_LIST_HEAD(&tt_info->tt_list); + list_add(&tt_info->tt_list, + &xhci->rh_bw[virt_dev->rhub_port->hw_portnum].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, struct xhci_virt_device *dev, + int slot_id) +{ + int i; + int old_active_eps = 0; + + /* Slot ID 0 is reserved */ + if (slot_id == 0 || !dev) + return; + + /* If device ctx array still points to _this_ device, clear it */ + if (dev->out_ctx && + xhci->dcbaa.ctx_array[slot_id] == cpu_to_le64(dev->out_ctx->dma)) + xhci->dcbaa.ctx_array[slot_id] = 0; + + trace_xhci_free_virt_device(dev); + + 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 are normally deleted from the bandwidth list when + * endpoints are dropped, before device is freed. + * If host is dying or being removed then endpoints aren't + * dropped cleanly, so delete the endpoint from list here. + * Only applicable for hosts with software bandwidth checking. + */ + + if (!list_empty(&dev->eps[i].bw_endpoint_list)) { + list_del_init(&dev->eps[i].bw_endpoint_list); + xhci_dbg(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->in_ctx) + xhci_free_container_ctx(xhci, dev->in_ctx); + if (dev->out_ctx) + xhci_free_container_ctx(xhci, dev->out_ctx); + + if (dev->udev && dev->udev->slot_id) + dev->udev->slot_id = 0; + if (dev->rhub_port && dev->rhub_port->slot_id == slot_id) + dev->rhub_port->slot_id = 0; + if (xhci->devs[slot_id] == dev) + xhci->devs[slot_id] = NULL; + kfree(dev); +} + +/* + * Free a virt_device structure. + * If the virt_device added a tt_info (a hub) and has children pointing to + * that tt_info, then free the child first. Recursive. + * We can't rely on udev at this point to find child-parent relationships. + */ +void xhci_free_virt_devices_depth_first(struct xhci_hcd *xhci, int slot_id) +{ + struct xhci_virt_device *vdev; + struct list_head *tt_list_head; + struct xhci_tt_bw_info *tt_info, *next; + int i; + + vdev = xhci->devs[slot_id]; + if (!vdev) + return; + + if (!vdev->rhub_port) { + xhci_dbg(xhci, "Bad rhub port.\n"); + goto out; + } + + tt_list_head = &(xhci->rh_bw[vdev->rhub_port->hw_portnum].tts); + list_for_each_entry_safe(tt_info, next, tt_list_head, tt_list) { + /* is this a hub device that added a tt_info to the tts list */ + if (tt_info->slot_id == slot_id) { + /* are any devices using this tt_info? */ + for (i = 1; i < xhci->max_slots; i++) { + vdev = xhci->devs[i]; + if (vdev && (vdev->tt_info == tt_info)) + xhci_free_virt_devices_depth_first( + xhci, i); + } + } + } +out: + /* we are now at a leaf device */ + xhci_debugfs_remove_slot(xhci, slot_id); + xhci_free_virt_device(xhci, xhci->devs[slot_id], slot_id); +} + +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; + } + + dev = kzalloc_obj(*dev, flags); + if (!dev) + return 0; + + dev->slot_id = 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 = %pad (dma)\n", slot_id, &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 = %pad (dma)\n", slot_id, &dev->in_ctx->dma); + + /* Initialize the cancellation and bandwidth list for each ep */ + for (i = 0; i < 31; i++) { + dev->eps[i].ep_index = i; + dev->eps[i].vdev = dev; + 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, TYPE_CTRL, 0, flags); + if (!dev->eps[0].ring) + goto fail; + + xhci_ring_init(xhci, dev->eps[0].ring); + + dev->udev = udev; + + /* Point to output device context in dcbaa. */ + xhci->dcbaa.ctx_array[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.ctx_array[slot_id], + le64_to_cpu(xhci->dcbaa.ctx_array[slot_id])); + + trace_xhci_alloc_virt_device(dev); + + xhci->devs[slot_id] = dev; + + return 1; +fail: + + 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(dev); + + 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. + * + * The xHCI hardware wants to know the roothub port 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. + */ +static struct xhci_port *xhci_find_rhub_port(struct xhci_hcd *xhci, struct usb_device *udev) +{ + struct usb_device *top_dev; + struct xhci_hub *rhub; + struct usb_hcd *hcd; + + if (udev->speed >= USB_SPEED_SUPER) + hcd = xhci_get_usb3_hcd(xhci); + 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 */; + + rhub = xhci_get_rhub(hcd); + return rhub->ports[top_dev->portnum - 1]; +} + +/* 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 max_packets; + + 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_PLUS: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_SSP); + max_packets = MAX_PACKET(512); + break; + case USB_SPEED_SUPER: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_SS); + max_packets = MAX_PACKET(512); + break; + case USB_SPEED_HIGH: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_HS); + max_packets = MAX_PACKET(64); + break; + /* USB core guesses at a 64-byte max packet first for FS devices */ + case USB_SPEED_FULL: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_FS); + max_packets = MAX_PACKET(64); + break; + case USB_SPEED_LOW: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_LS); + max_packets = MAX_PACKET(8); + break; + default: + /* Speed was set earlier, this shouldn't happen. */ + return -EINVAL; + } + /* Find the root hub port this device is under */ + dev->rhub_port = xhci_find_rhub_port(xhci, udev); + if (!dev->rhub_port) + return -EINVAL; + /* Slot ID is set to the device directly below the root hub */ + if (!udev->parent->parent) + dev->rhub_port->slot_id = udev->slot_id; + slot_ctx->dev_info2 |= cpu_to_le32(ROOT_HUB_PORT(dev->rhub_port->hw_portnum + 1)); + xhci_dbg(xhci, "Slot ID %d: HW portnum %d, hcd portnum %d\n", + udev->slot_id, dev->rhub_port->hw_portnum, dev->rhub_port->hcd_portnum); + + /* 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[dev->rhub_port->hw_portnum].bw_table; + } else { + struct xhci_root_port_bw_info *rh_bw; + struct xhci_tt_bw_info *tt_bw; + + rh_bw = &xhci->rh_bw[dev->rhub_port->hw_portnum]; + /* 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)); + + /* 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) | + max_packets); + + ep0_ctx->deq = cpu_to_le64(dev->eps[0].ring->first_seg->dma | + dev->eps[0].ring->cycle_state); + + ep0_ctx->tx_info = cpu_to_le32(EP_AVG_TRB_LENGTH(8)); + + trace_xhci_setup_addressable_virt_device(dev); + + /* 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_dbg(&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; + } + fallthrough; /* SS and HS isoc/int have same decoding */ + + case USB_SPEED_SUPER_PLUS: + 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. + */ + fallthrough; + + 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 interval; +} + +/* + * xHCs without LEC use the "Mult" field in the endpoint context for SuperSpeed + * isoc eps, and High speed isoc eps that support bandwidth doubling. Standard + * 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 xhci_hcd *xhci, + struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + bool lec; + + /* xHCI 1.1 with LEC set does not use mult field, except intel eUSB2 */ + lec = xhci->hci_version > 0x100 && (xhci->hcc_params2 & HCC2_LEC); + + /* eUSB2 double isoc bw devices are the only USB2 devices using mult */ + if (usb_endpoint_is_hs_isoc_double(udev, ep) && + (!lec || xhci->quirks & XHCI_INTEL_HOST)) + return 1; + + /* SuperSpeed isoc transfers on hosts without LEC uses mult field */ + if (udev->speed >= USB_SPEED_SUPER && + usb_endpoint_xfer_isoc(&ep->desc) && !lec) + return ep->ss_ep_comp.bmAttributes; + + return 0; +} + +static u32 xhci_get_endpoint_max_burst(struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + /* Super speed and Plus have max burst in ep companion desc */ + if (udev->speed >= USB_SPEED_SUPER) + return ep->ss_ep_comp.bMaxBurst; + + if (udev->speed == USB_SPEED_HIGH && + (usb_endpoint_xfer_isoc(&ep->desc) || + usb_endpoint_xfer_int(&ep->desc))) { + /* + * USB 2 Isochronous Double IN Bandwidth ECN uses fixed burst + * size and max packets bits 12:11 are invalid. + */ + if (usb_endpoint_is_hs_isoc_double(udev, ep)) + return 2; + + return usb_endpoint_maxp_mult(&ep->desc) - 1; + } + + return 0; +} + +static u32 xhci_get_endpoint_type(struct usb_host_endpoint *ep) +{ + int in; + + in = usb_endpoint_dir_in(&ep->desc); + + switch (usb_endpoint_type(&ep->desc)) { + case USB_ENDPOINT_XFER_CONTROL: + return CTRL_EP; + case USB_ENDPOINT_XFER_BULK: + return in ? BULK_IN_EP : BULK_OUT_EP; + case USB_ENDPOINT_XFER_ISOC: + return in ? ISOC_IN_EP : ISOC_OUT_EP; + case USB_ENDPOINT_XFER_INT: + return in ? INT_IN_EP : INT_OUT_EP; + } + return 0; +} + +/* 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; + enum xhci_ring_type ring_type; + u32 max_esit_payload; + u32 endpoint_type; + unsigned int max_burst; + unsigned int interval; + unsigned int mult; + unsigned int avg_trb_len; + unsigned int err_count = 0; + + ep_index = xhci_get_endpoint_index(&ep->desc); + ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); + + endpoint_type = xhci_get_endpoint_type(ep); + if (!endpoint_type) + return -EINVAL; + + ring_type = usb_endpoint_type(&ep->desc); + + /* Ensure host supports double isoc bandwidth for eUSB2 devices */ + if (usb_endpoint_is_hs_isoc_double(udev, ep) && !(xhci->hcc_params2 & HCC2_EUSB2_DIC)) { + dev_dbg(&udev->dev, "Double Isoc Bandwidth not supported by xhci\n"); + return -EINVAL; + } + + /* + * Get values to fill the endpoint context, mostly from ep descriptor. + * The average TRB buffer lengt for bulk endpoints is unclear as we + * have no clue on scatter gather list entry size. For Isoc and Int, + * set it to max available. See xHCI 1.1 spec 4.14.1.1 for details. + */ + max_esit_payload = usb_endpoint_max_periodic_payload(udev, ep); + interval = xhci_get_endpoint_interval(udev, ep); + + /* Periodic endpoint bInterval limit quirk */ + if (usb_endpoint_xfer_int(&ep->desc) || + usb_endpoint_xfer_isoc(&ep->desc)) { + if ((xhci->quirks & XHCI_LIMIT_ENDPOINT_INTERVAL_9) && + interval >= 9) { + interval = 8; + } + if ((xhci->quirks & XHCI_LIMIT_ENDPOINT_INTERVAL_7) && + udev->speed >= USB_SPEED_HIGH && + interval >= 7) { + interval = 6; + } + } + + mult = xhci_get_endpoint_mult(xhci, udev, ep); + max_packet = xhci_usb_endpoint_maxp(udev, ep); + max_burst = xhci_get_endpoint_max_burst(udev, ep); + avg_trb_len = max_esit_payload; + + /* FIXME dig Mult and streams info out of ep companion desc */ + + /* Allow 3 retries for everything but isoc, set CErr = 3 */ + if (!usb_endpoint_xfer_isoc(&ep->desc)) + err_count = 3; + /* HS bulk max packet should be 512, FS bulk supports 8, 16, 32 or 64 */ + if (usb_endpoint_xfer_bulk(&ep->desc)) { + if (udev->speed == USB_SPEED_HIGH) + max_packet = 512; + if (udev->speed == USB_SPEED_FULL) { + max_packet = rounddown_pow_of_two(max_packet); + max_packet = clamp_val(max_packet, 8, 64); + } + } + /* xHCI 1.0 and 1.1 indicates that ctrl ep avg TRB Length should be 8 */ + if (usb_endpoint_xfer_control(&ep->desc) && xhci->hci_version >= 0x100) + avg_trb_len = 8; + + /* Set up the endpoint ring */ + virt_dev->eps[ep_index].new_ring = + xhci_ring_alloc(xhci, 2, ring_type, max_packet, mem_flags); + if (!virt_dev->eps[ep_index].new_ring) + return -ENOMEM; + + virt_dev->eps[ep_index].skip = false; + virt_dev->eps[ep_index].next_uframe = -1; + ep_ring = virt_dev->eps[ep_index].new_ring; + xhci_ring_init(xhci, ep_ring); + + /* Fill the endpoint context */ + ep_ctx->ep_info = cpu_to_le32(EP_MAX_ESIT_PAYLOAD_HI(max_esit_payload) | + EP_INTERVAL(interval) | + EP_MULT(mult)); + ep_ctx->ep_info2 = cpu_to_le32(EP_TYPE(endpoint_type) | + MAX_PACKET(max_packet) | + MAX_BURST(max_burst) | + ERROR_COUNT(err_count)); + ep_ctx->deq = cpu_to_le64(ep_ring->first_seg->dma | + ep_ring->cycle_state); + + ep_ctx->tx_info = cpu_to_le32(EP_MAX_ESIT_PAYLOAD_LO(max_esit_payload) | + EP_AVG_TRB_LENGTH(avg_trb_len)); + + 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; + if (xhci->quirks & XHCI_MTK_HOST) { + in_ep_ctx->reserved[0] = out_ep_ctx->reserved[0]; + in_ep_ctx->reserved[1] = out_ep_ctx->reserved[1]; + } +} + +/* 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.sysdev; + int num_sp = HCS_MAX_SCRATCHPAD(xhci->hcs_params2); + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "Allocating %d scratchpad buffers", num_sp); + + if (!num_sp) + return 0; + + xhci->scratchpad = kzalloc_node(sizeof(*xhci->scratchpad), flags, + dev_to_node(dev)); + if (!xhci->scratchpad) + goto fail_sp; + + xhci->scratchpad->sp_array = dma_alloc_coherent(dev, + array_size(sizeof(u64), num_sp), + &xhci->scratchpad->sp_dma, flags); + if (!xhci->scratchpad->sp_array) + goto fail_sp2; + + xhci->scratchpad->sp_buffers = kcalloc_node(num_sp, sizeof(void *), + flags, dev_to_node(dev)); + if (!xhci->scratchpad->sp_buffers) + goto fail_sp3; + + xhci->dcbaa.ctx_array[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_sp4; + + xhci->scratchpad->sp_array[i] = dma; + xhci->scratchpad->sp_buffers[i] = buf; + } + + return 0; + + fail_sp4: + while (i--) + dma_free_coherent(dev, xhci->page_size, + xhci->scratchpad->sp_buffers[i], + xhci->scratchpad->sp_array[i]); + + kfree(xhci->scratchpad->sp_buffers); + + fail_sp3: + dma_free_coherent(dev, array_size(sizeof(u64), num_sp), + 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 device *dev = xhci_to_hcd(xhci)->self.sysdev; + + if (!xhci->scratchpad) + return; + + num_sp = HCS_MAX_SCRATCHPAD(xhci->hcs_params2); + + for (i = 0; i < num_sp; i++) { + dma_free_coherent(dev, xhci->page_size, + xhci->scratchpad->sp_buffers[i], + xhci->scratchpad->sp_array[i]); + } + kfree(xhci->scratchpad->sp_buffers); + dma_free_coherent(dev, array_size(sizeof(u64), num_sp), + 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_completion, gfp_t mem_flags) +{ + struct xhci_command *command; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + + command = kzalloc_node(sizeof(*command), mem_flags, dev_to_node(dev)); + if (!command) + return NULL; + + if (allocate_completion) { + command->completion = + kzalloc_node(sizeof(struct completion), mem_flags, + dev_to_node(dev)); + if (!command->completion) { + kfree(command); + return NULL; + } + init_completion(command->completion); + } + + command->status = 0; + /* set default timeout to 5000 ms */ + command->timeout_ms = XHCI_CMD_DEFAULT_TIMEOUT; + INIT_LIST_HEAD(&command->cmd_list); + return command; +} + +struct xhci_command *xhci_alloc_command_with_ctx(struct xhci_hcd *xhci, + bool allocate_completion, gfp_t mem_flags) +{ + struct xhci_command *command; + + command = xhci_alloc_command(xhci, allocate_completion, mem_flags); + if (!command) + return NULL; + + command->in_ctx = xhci_alloc_container_ctx(xhci, XHCI_CTX_TYPE_INPUT, + mem_flags); + if (!command->in_ctx) { + kfree(command->completion); + kfree(command); + return NULL; + } + return command; +} + +void xhci_urb_free_priv(struct urb_priv *urb_priv) +{ + 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); +} + +static int xhci_alloc_erst(struct xhci_hcd *xhci, + struct xhci_ring *evt_ring, + struct xhci_erst *erst, + gfp_t flags) +{ + size_t size; + unsigned int val; + struct xhci_segment *seg; + struct xhci_erst_entry *entry; + + size = array_size(sizeof(struct xhci_erst_entry), evt_ring->num_segs); + erst->entries = dma_alloc_coherent(xhci_to_hcd(xhci)->self.sysdev, + size, &erst->erst_dma_addr, flags); + if (!erst->entries) + return -ENOMEM; + + erst->num_entries = evt_ring->num_segs; + + seg = evt_ring->first_seg; + for (val = 0; val < evt_ring->num_segs; val++) { + entry = &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; + } + + return 0; +} + +static void +xhci_remove_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) +{ + u32 tmp; + + if (!ir) + return; + + /* + * Clean out interrupter registers except ERSTBA. Clearing either the + * low or high 32 bits of ERSTBA immediately causes the controller to + * dereference the partially cleared 64 bit address, causing IOMMU error. + */ + if (ir->ir_set) { + tmp = readl(&ir->ir_set->erst_size); + tmp &= ~ERST_SIZE_MASK; + writel(tmp, &ir->ir_set->erst_size); + + xhci_update_erst_dequeue(xhci, ir, true); + } +} + +static void +xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) +{ + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + size_t erst_size; + + if (!ir) + return; + + erst_size = array_size(sizeof(struct xhci_erst_entry), ir->erst.num_entries); + if (ir->erst.entries) + dma_free_coherent(dev, erst_size, + ir->erst.entries, + ir->erst.erst_dma_addr); + ir->erst.entries = NULL; + + /* free interrupter event ring */ + if (ir->event_ring) + xhci_ring_free(xhci, ir->event_ring); + + ir->event_ring = NULL; + + kfree(ir); +} + +void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrupter *ir) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + unsigned int intr_num; + + spin_lock_irq(&xhci->lock); + + /* interrupter 0 is primary interrupter, don't touch it */ + if (!ir || !ir->intr_num || ir->intr_num >= xhci->max_interrupters) { + xhci_dbg(xhci, "Invalid secondary interrupter, can't remove\n"); + spin_unlock_irq(&xhci->lock); + return; + } + + /* + * Cleanup secondary interrupter to ensure there are no pending events. + * This also updates event ring dequeue pointer back to the start. + */ + xhci_skip_sec_intr_events(xhci, ir->event_ring, ir); + intr_num = ir->intr_num; + + xhci_remove_interrupter(xhci, ir); + xhci->interrupters[intr_num] = NULL; + + spin_unlock_irq(&xhci->lock); + + xhci_free_interrupter(xhci, ir); +} +EXPORT_SYMBOL_GPL(xhci_remove_secondary_interrupter); + +/* Cleanup roothub bandwidth data */ +void xhci_rh_bw_cleanup(struct xhci_hcd *xhci) +{ + struct xhci_root_port_bw_info *rh_bw; + struct xhci_tt_bw_info *tt_info, *tt_next; + struct list_head *eps, *ep, *ep_next; + + for (int i = 0; i < xhci->max_ports; i++) { + rh_bw = &xhci->rh_bw[i]; + + /* Clear and free all TT bandwidth entries */ + list_for_each_entry_safe(tt_info, tt_next, &rh_bw->tts, tt_list) { + list_del(&tt_info->tt_list); + kfree(tt_info); + } + + /* Clear per-interval endpoint lists */ + for (int j = 0; j < XHCI_MAX_INTERVAL; j++) { + eps = &rh_bw->bw_table.interval_bw[j].endpoints; + + list_for_each_safe(ep, ep_next, eps) + list_del_init(ep); + } + } +} + +void xhci_mem_cleanup(struct xhci_hcd *xhci) +{ + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + struct xhci_device_context_array *dcbaa; + int i; + + cancel_delayed_work_sync(&xhci->cmd_timer); + + for (i = 0; xhci->interrupters && i < xhci->max_interrupters; i++) { + if (xhci->interrupters[i]) { + xhci_remove_interrupter(xhci, xhci->interrupters[i]); + xhci_free_interrupter(xhci, xhci->interrupters[i]); + xhci->interrupters[i] = NULL; + } + } + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed interrupters"); + + if (xhci->cmd_ring) + xhci_ring_free(xhci, xhci->cmd_ring); + xhci->cmd_ring = NULL; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed command ring"); + xhci_cleanup_command_queue(xhci); + + if (xhci->devs) { + for (i = xhci->max_slots; i > 0; i--) + xhci_free_virt_devices_depth_first(xhci, i); + kfree(xhci->devs); + } + + dma_pool_destroy(xhci->segment_pool); + xhci->segment_pool = NULL; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed segment pool"); + + dma_pool_destroy(xhci->device_pool); + xhci->device_pool = NULL; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed device context pool"); + + dma_pool_destroy(xhci->small_streams_pool); + xhci->small_streams_pool = NULL; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "Freed small stream array pool"); + + dma_pool_destroy(xhci->port_bw_pool); + xhci->port_bw_pool = NULL; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "Freed xhci port bw array pool"); + + dma_pool_destroy(xhci->medium_streams_pool); + xhci->medium_streams_pool = NULL; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "Freed medium stream array pool"); + + dcbaa = &xhci->dcbaa; + if (dcbaa->ctx_array) { + dma_free_coherent(dev, array_size(sizeof(*dcbaa->ctx_array), xhci->max_slots + 1), + dcbaa->ctx_array, dcbaa->dma); + dcbaa->ctx_array = NULL; + } + + scratchpad_free(xhci); + + if (xhci->rh_bw) + xhci_rh_bw_cleanup(xhci); + + xhci->cmd_ring_reserved_trbs = 0; + xhci->usb2_rhub.num_ports = 0; + xhci->usb3_rhub.num_ports = 0; + xhci->num_active_eps = 0; + kfree(xhci->usb2_rhub.ports); + kfree(xhci->usb3_rhub.ports); + kfree(xhci->hw_ports); + kfree(xhci->rh_bw); + for (i = 0; i < xhci->num_port_caps; i++) + kfree(xhci->port_caps[i].psi); + kfree(xhci->port_caps); + kfree(xhci->interrupters); + xhci->num_port_caps = 0; + + xhci->usb2_rhub.ports = NULL; + xhci->usb3_rhub.ports = NULL; + xhci->hw_ports = NULL; + xhci->rh_bw = NULL; + xhci->port_caps = NULL; + xhci->interrupters = NULL; + xhci->devs = NULL; + + xhci->usb2_rhub.bus_state.bus_suspended = 0; + xhci->usb3_rhub.bus_state.bus_suspended = 0; +} + +static void xhci_set_hc_event_deq(struct xhci_hcd *xhci, struct xhci_interrupter *ir) +{ + dma_addr_t deq; + + deq = xhci_trb_virt_to_dma(ir->event_ring->deq_seg, + ir->event_ring->dequeue); + if (!deq) + xhci_warn(xhci, "WARN something wrong with SW event ring dequeue ptr.\n"); + /* Update HC event ring dequeue pointer */ + /* Don't clear the EHB bit (which is RW1C) because + * there might be more events to service. + */ + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "// Write event ring dequeue pointer, preserving EHB bit"); + xhci_write_64(xhci, deq & ERST_PTR_MASK, &ir->ir_set->erst_dequeue); +} + +static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, + __le32 __iomem *addr, int max_caps) +{ + u32 temp, port_offset, port_count; + int i; + u8 major_revision, minor_revision, tmp_minor_revision; + struct xhci_hub *rhub; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + struct xhci_port_cap *port_cap; + + temp = readl(addr); + major_revision = XHCI_EXT_PORT_MAJOR(temp); + minor_revision = XHCI_EXT_PORT_MINOR(temp); + + if (major_revision == 0x03) { + rhub = &xhci->usb3_rhub; + /* + * Some hosts incorrectly use sub-minor version for minor + * version (i.e. 0x02 instead of 0x20 for bcdUSB 0x320 and 0x01 + * for bcdUSB 0x310). Since there is no USB release with sub + * minor version 0x301 to 0x309, we can assume that they are + * incorrect and fix it here. + */ + if (minor_revision > 0x00 && minor_revision < 0x10) + minor_revision <<= 4; + /* + * Some zhaoxin's xHCI controller that follow usb3.1 spec + * but only support Gen1. + */ + if (xhci->quirks & XHCI_ZHAOXIN_HOST) { + tmp_minor_revision = minor_revision; + minor_revision = 0; + } + + } else if (major_revision <= 0x02) { + rhub = &xhci->usb2_rhub; + } else { + 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 = readl(addr + 2); + port_offset = XHCI_EXT_PORT_OFF(temp); + port_count = XHCI_EXT_PORT_COUNT(temp); + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "Ext Cap %p, port offset = %u, count = %u, revision = 0x%x", + 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; + + port_cap = &xhci->port_caps[xhci->num_port_caps++]; + if (xhci->num_port_caps > max_caps) + return; + + port_cap->psi_count = XHCI_EXT_PORT_PSIC(temp); + + if (port_cap->psi_count) { + port_cap->psi = kcalloc_node(port_cap->psi_count, + sizeof(*port_cap->psi), + GFP_KERNEL, dev_to_node(dev)); + if (!port_cap->psi) + port_cap->psi_count = 0; + + port_cap->psi_uid_count++; + for (i = 0; i < port_cap->psi_count; i++) { + port_cap->psi[i] = readl(addr + 4 + i); + + /* count unique ID values, two consecutive entries can + * have the same ID if link is assymetric + */ + if (i && (XHCI_EXT_PORT_PSIV(port_cap->psi[i]) != + XHCI_EXT_PORT_PSIV(port_cap->psi[i - 1]))) + port_cap->psi_uid_count++; + + if (xhci->quirks & XHCI_ZHAOXIN_HOST && + major_revision == 0x03 && + XHCI_EXT_PORT_PSIV(port_cap->psi[i]) >= 5) + minor_revision = tmp_minor_revision; + + xhci_dbg(xhci, "PSIV:%d PSIE:%d PLT:%d PFD:%d LP:%d PSIM:%d\n", + XHCI_EXT_PORT_PSIV(port_cap->psi[i]), + XHCI_EXT_PORT_PSIE(port_cap->psi[i]), + XHCI_EXT_PORT_PLT(port_cap->psi[i]), + XHCI_EXT_PORT_PFD(port_cap->psi[i]), + XHCI_EXT_PORT_LP(port_cap->psi[i]), + XHCI_EXT_PORT_PSIM(port_cap->psi[i])); + } + } + + rhub->maj_rev = major_revision; + + if (rhub->min_rev < minor_revision) + rhub->min_rev = minor_revision; + + port_cap->maj_rev = major_revision; + port_cap->min_rev = minor_revision; + port_cap->protocol_caps = temp; + + if ((xhci->hci_version >= 0x100) && (major_revision != 0x03) && + (temp & XHCI_HLC)) { + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "xHCI 1.0: support USB2 hardware lpm"); + xhci->hw_lpm_support = 1; + } + + port_offset--; + for (i = port_offset; i < (port_offset + port_count); i++) { + struct xhci_port *hw_port = &xhci->hw_ports[i]; + /* Duplicate entry. Ignore the port if the revisions differ. */ + if (hw_port->rhub) { + 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", + hw_port->rhub->maj_rev, major_revision); + /* Only adjust the roothub port counts if we haven't + * found a similar duplicate. + */ + if (hw_port->rhub != rhub && + hw_port->hcd_portnum != DUPLICATE_ENTRY) { + hw_port->rhub->num_ports--; + hw_port->hcd_portnum = DUPLICATE_ENTRY; + } + continue; + } + hw_port->rhub = rhub; + hw_port->port_cap = port_cap; + rhub->num_ports++; + } + /* FIXME: Should we disable ports not in the Extended Capabilities? */ +} + +static void xhci_create_rhub_port_array(struct xhci_hcd *xhci, struct xhci_hub *rhub, + unsigned int max_ports, gfp_t flags) +{ + int port_index = 0; + int i; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + + if (!rhub->num_ports) { + xhci_info(xhci, "USB%u root hub has no ports\n", rhub->maj_rev); + return; + } + + /* + * Place limits on the number of roothub ports so that the hub + * descriptors aren't longer than the USB core will allocate. + */ + if (rhub->num_ports > max_ports) { + xhci->usb3_rhub.num_ports = max_ports; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Limiting USB%u root hub ports to %u", + rhub->maj_rev, max_ports); + } + + rhub->ports = kcalloc_node(rhub->num_ports, sizeof(*rhub->ports), + flags, dev_to_node(dev)); + if (!rhub->ports) + return; + + for (i = 0; i < xhci->max_ports; i++) { + if (xhci->hw_ports[i].rhub != rhub || + xhci->hw_ports[i].hcd_portnum == DUPLICATE_ENTRY) + continue; + xhci->hw_ports[i].hcd_portnum = port_index; + rhub->ports[port_index] = &xhci->hw_ports[i]; + port_index++; + if (port_index == rhub->num_ports) + break; + } +} + +/* + * 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) +{ + void __iomem *base; + u32 offset; + int i, j; + int cap_count = 0; + u32 cap_start; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + + xhci->hw_ports = kcalloc_node(xhci->max_ports, sizeof(*xhci->hw_ports), + flags, dev_to_node(dev)); + if (!xhci->hw_ports) + return -ENOMEM; + + for (i = 0; i < xhci->max_ports; i++) { + xhci->hw_ports[i].port_reg = &xhci->op_regs->port_regs[i]; + xhci->hw_ports[i].hw_portnum = i; + + init_completion(&xhci->hw_ports[i].rexit_done); + init_completion(&xhci->hw_ports[i].u3exit_done); + } + + xhci->rh_bw = kcalloc_node(xhci->max_ports, sizeof(*xhci->rh_bw), flags, dev_to_node(dev)); + if (!xhci->rh_bw) + return -ENOMEM; + for (i = 0; i < xhci->max_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); + } + base = &xhci->cap_regs->hc_capbase; + + cap_start = xhci_find_next_ext_cap(base, 0, XHCI_EXT_CAPS_PROTOCOL); + if (!cap_start) { + xhci_err(xhci, "No Extended Capability registers, unable to set up roothub\n"); + return -ENODEV; + } + + offset = cap_start; + /* count extended protocol capability entries for later caching */ + while (offset) { + cap_count++; + offset = xhci_find_next_ext_cap(base, offset, + XHCI_EXT_CAPS_PROTOCOL); + } + + xhci->port_caps = kcalloc_node(cap_count, sizeof(*xhci->port_caps), + flags, dev_to_node(dev)); + if (!xhci->port_caps) + return -ENOMEM; + + offset = cap_start; + + while (offset) { + xhci_add_in_port(xhci, xhci->max_ports, base + offset, cap_count); + if (xhci->usb2_rhub.num_ports + xhci->usb3_rhub.num_ports == xhci->max_ports) + break; + offset = xhci_find_next_ext_cap(base, offset, + XHCI_EXT_CAPS_PROTOCOL); + } + if (xhci->usb2_rhub.num_ports == 0 && xhci->usb3_rhub.num_ports == 0) { + xhci_warn(xhci, "No ports on the roothubs?\n"); + return -ENODEV; + } + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "Found %u USB 2.0 ports and %u USB 3.0 ports.", + xhci->usb2_rhub.num_ports, xhci->usb3_rhub.num_ports); + + xhci_create_rhub_port_array(xhci, &xhci->usb2_rhub, USB_MAXCHILDREN, flags); + xhci_create_rhub_port_array(xhci, &xhci->usb3_rhub, USB_SS_MAXPORTS, flags); + + return 0; +} + +static struct xhci_interrupter * +xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) +{ + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + struct xhci_interrupter *ir; + unsigned int max_segs; + int ret; + + if (!segs) + segs = ERST_DEFAULT_SEGS; + + max_segs = FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2) << 2; + segs = min(segs, max_segs); + + ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); + if (!ir) + return NULL; + + ir->event_ring = xhci_ring_alloc(xhci, segs, TYPE_EVENT, 0, flags); + if (!ir->event_ring) { + xhci_warn(xhci, "Failed to allocate interrupter event ring\n"); + kfree(ir); + return NULL; + } + + ret = xhci_alloc_erst(xhci, ir->event_ring, &ir->erst, flags); + if (ret) { + xhci_warn(xhci, "Failed to allocate interrupter erst\n"); + xhci_ring_free(xhci, ir->event_ring); + kfree(ir); + return NULL; + } + + return ir; +} + +void xhci_add_interrupter(struct xhci_hcd *xhci, unsigned int intr_num) +{ + struct xhci_interrupter *ir; + u64 erst_base; + u32 erst_size; + + ir = xhci->interrupters[intr_num]; + ir->intr_num = intr_num; + ir->ir_set = &xhci->run_regs->ir_set[intr_num]; + + /* set ERST count with the number of entries in the segment table */ + erst_size = readl(&ir->ir_set->erst_size); + erst_size &= ~ERST_SIZE_MASK; + erst_size |= ir->event_ring->num_segs; + writel(erst_size, &ir->ir_set->erst_size); + + erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base); + erst_base &= ~ERST_BASE_ADDRESS_MASK; + erst_base |= ir->erst.erst_dma_addr & ERST_BASE_ADDRESS_MASK; + if (xhci->quirks & XHCI_WRITE_64_HI_LO) + hi_lo_writeq(erst_base, &ir->ir_set->erst_base); + else + xhci_write_64(xhci, erst_base, &ir->ir_set->erst_base); + + /* Set the event ring dequeue address of this interrupter */ + xhci_set_hc_event_deq(xhci, ir); +} + +struct xhci_interrupter * +xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, + u32 imod_interval, unsigned int intr_num) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_interrupter *ir; + unsigned int i; + int err = -ENOSPC; + + if (!xhci->interrupters || xhci->max_interrupters <= 1 || + intr_num >= xhci->max_interrupters) + return NULL; + + ir = xhci_alloc_interrupter(xhci, segs, GFP_KERNEL); + if (!ir) + return NULL; + + xhci_ring_init(xhci, ir->event_ring); + + spin_lock_irq(&xhci->lock); + if (!intr_num) { + /* Find available secondary interrupter, interrupter 0 is reserved for primary */ + for (i = 1; i < xhci->max_interrupters; i++) { + if (!xhci->interrupters[i]) { + xhci->interrupters[i] = ir; + xhci_add_interrupter(xhci, i); + err = 0; + break; + } + } + } else { + if (!xhci->interrupters[intr_num]) { + xhci->interrupters[intr_num] = ir; + xhci_add_interrupter(xhci, intr_num); + err = 0; + } + } + spin_unlock_irq(&xhci->lock); + + if (err) { + xhci_warn(xhci, "Failed to add secondary interrupter, max interrupters %d\n", + xhci->max_interrupters); + xhci_free_interrupter(xhci, ir); + return NULL; + } + + xhci_set_interrupter_moderation(ir, imod_interval); + + xhci_dbg(xhci, "Add secondary interrupter %d, max interrupters %d\n", + ir->intr_num, xhci->max_interrupters); + + return ir; +} +EXPORT_SYMBOL_GPL(xhci_create_secondary_interrupter); + +int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) +{ + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + struct xhci_device_context_array *dcbaa = &xhci->dcbaa; + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Starting %s", __func__); + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocating internal virtual device array"); + xhci->devs = kcalloc_node(xhci->max_slots + 1, sizeof(*xhci->devs), flags, + dev_to_node(dev)); + if (!xhci->devs) + goto fail; + + xhci->dcbaa.ctx_array = + dma_alloc_coherent(dev, array_size(sizeof(*dcbaa->ctx_array), xhci->max_slots + 1), + &dcbaa->dma, flags); + if (!dcbaa->ctx_array) + goto fail; + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "Device context base array address = %pad (DMA), %p (virt)", + &dcbaa->dma, dcbaa->ctx_array); + + /* + * 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 + * and our use of dma addresses in the trb_address_map radix tree needs + * TRB_SEGMENT_SIZE alignment, so we pick the greater alignment need. + */ + if (xhci->quirks & XHCI_TRB_OVERFETCH) + /* Buggy HC prefetches beyond segment bounds - allocate dummy space at the end */ + xhci->segment_pool = dma_pool_create("xHCI ring segments", dev, + TRB_SEGMENT_SIZE * 2, TRB_SEGMENT_SIZE * 2, xhci->page_size * 2); + else + xhci->segment_pool = dma_pool_create("xHCI ring segments", dev, + TRB_SEGMENT_SIZE, TRB_SEGMENT_SIZE, xhci->page_size); + if (!xhci->segment_pool) + goto fail; + + /* 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->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); + if (!xhci->small_streams_pool) + goto fail; + + /* + * Any stream context array bigger than MEDIUM_STREAM_ARRAY_SIZE will be + * allocated with dma_alloc_coherent(). + */ + + xhci->medium_streams_pool = dma_pool_create("xHCI 1KB stream ctx arrays", + dev, MEDIUM_STREAM_ARRAY_SIZE, 16, 0); + if (!xhci->medium_streams_pool) + goto fail; + + /* + * refer to xhci rev1_2 protocol 5.3.3 max ports is 255. + * refer to xhci rev1_2 protocol 6.4.3.14 port bandwidth buffer need + * to be 16-byte aligned. + */ + xhci->port_bw_pool = dma_pool_create("xHCI 256 port bw ctx arrays", + dev, GET_PORT_BW_ARRAY_SIZE, 16, 0); + if (!xhci->port_bw_pool) + goto fail; + + /* Set up the command ring to have one segments for now. */ + xhci->cmd_ring = xhci_ring_alloc(xhci, 1, TYPE_COMMAND, 0, flags); + if (!xhci->cmd_ring) + goto fail; + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocated command ring at %p", xhci->cmd_ring); + + /* Allocate and set up primary interrupter 0 with an event ring. */ + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocating primary event ring"); + xhci->interrupters = kcalloc_node(xhci->max_interrupters, sizeof(*xhci->interrupters), + flags, dev_to_node(dev)); + if (!xhci->interrupters) + goto fail; + + xhci->interrupters[0] = xhci_alloc_interrupter(xhci, 0, flags); + if (!xhci->interrupters[0]) + goto fail; + + if (scratchpad_alloc(xhci, flags)) + goto fail; + + if (xhci_setup_port_arrays(xhci, flags)) + goto fail; + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished %s", __func__); + return 0; + +fail: + xhci_halt(xhci); + xhci_reset(xhci, XHCI_RESET_SHORT_USEC); + xhci_mem_cleanup(xhci); + return -ENOMEM; +} diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c new file mode 100644 index 000000000..583623cd4 --- /dev/null +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -0,0 +1,1055 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: + * Zhigang.Wei + * Chunfeng.Yun + */ + +#include +#include +#include + +#include "xhci.h" +#include "xhci-mtk.h" + +#define SSP_BW_BOUNDARY 130000 +#define SS_BW_BOUNDARY 51000 +/* table 5-5. High-speed Isoc Transaction Limits in usb_20 spec */ +#define HS_BW_BOUNDARY 6144 +/* usb2 spec section11.18.1: at most 188 FS bytes per microframe */ +#define FS_PAYLOAD_MAX 188 +#define LS_PAYLOAD_MAX 18 +/* section 11.18.1, per fs frame */ +#define FS_BW_BOUNDARY 1157 +#define LS_BW_BOUNDARY 144 + +/* + * max number of microframes for split transfer, assume extra-cs budget is 0 + * for fs isoc in : 1 ss + 1 idle + 6 cs (roundup(1023/188)) + */ +#define TT_MICROFRAMES_MAX 8 +/* offset from SS for fs/ls isoc/intr ep (ss + idle) */ +#define CS_OFFSET 2 + +#define DBG_BUF_EN 64 + +/* schedule error type */ +#define ESCH_SS_Y6 1001 +#define ESCH_SS_OVERLAP 1002 +#define ESCH_CS_OVERFLOW 1003 +#define ESCH_BW_OVERFLOW 1004 +#define ESCH_FIXME 1005 + +/* mtk scheduler bitmasks */ +#define EP_BPKTS(p) ((p) & 0x7f) +#define EP_BCSCOUNT(p) (((p) & 0x7) << 8) +#define EP_BBM(p) ((p) << 11) +#define EP_BOFFSET(p) ((p) & 0x3fff) +#define EP_BREPEAT(p) (((p) & 0x7fff) << 16) + +static char *sch_error_string(int err_num) +{ + switch (err_num) { + case ESCH_SS_Y6: + return "Can't schedule Start-Split in Y6"; + case ESCH_SS_OVERLAP: + return "Can't find a suitable Start-Split location"; + case ESCH_CS_OVERFLOW: + return "The last Complete-Split is greater than 7"; + case ESCH_BW_OVERFLOW: + return "Bandwidth exceeds the maximum limit"; + case ESCH_FIXME: + return "FIXME, to be resolved"; + default: + return "Unknown"; + } +} + +static int is_fs_or_ls(enum usb_device_speed speed) +{ + return speed == USB_SPEED_FULL || speed == USB_SPEED_LOW; +} + +static const char * +decode_ep(struct usb_host_endpoint *ep, enum usb_device_speed speed) +{ + static char buf[DBG_BUF_EN]; + struct usb_endpoint_descriptor *epd = &ep->desc; + unsigned int interval; + const char *unit; + + interval = usb_decode_interval(epd, speed); + if (interval % 1000) { + unit = "us"; + } else { + unit = "ms"; + interval /= 1000; + } + + snprintf(buf, DBG_BUF_EN, "%s ep%d%s %s, mpkt:%d, interval:%d/%d%s", + usb_speed_string(speed), usb_endpoint_num(epd), + usb_endpoint_dir_in(epd) ? "in" : "out", + usb_ep_type_string(usb_endpoint_type(epd)), + usb_endpoint_maxp(epd), epd->bInterval, interval, unit); + + return buf; +} + +static u32 get_bw_boundary(enum usb_device_speed speed) +{ + u32 boundary; + + switch (speed) { + case USB_SPEED_SUPER_PLUS: + boundary = SSP_BW_BOUNDARY; + break; + case USB_SPEED_SUPER: + boundary = SS_BW_BOUNDARY; + break; + default: + boundary = HS_BW_BOUNDARY; + break; + } + + return boundary; +} + +/* +* get the bandwidth domain which @ep belongs to. +* +* the bandwidth domain array is saved to @sch_array of struct xhci_hcd_mtk, +* each HS root port is treated as a single bandwidth domain, +* but each SS root port is treated as two bandwidth domains, one for IN eps, +* one for OUT eps. +*/ +static struct mu3h_sch_bw_info * +get_bw_info(struct xhci_hcd_mtk *mtk, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd); + struct xhci_virt_device *virt_dev; + int bw_index; + + virt_dev = xhci->devs[udev->slot_id]; + if (!virt_dev->rhub_port) { + WARN_ONCE(1, "%s invalid rhub port\n", dev_name(&udev->dev)); + return NULL; + } + + if (udev->speed >= USB_SPEED_SUPER) { + if (usb_endpoint_dir_out(&ep->desc)) + bw_index = (virt_dev->rhub_port->hw_portnum) * 2; + else + bw_index = (virt_dev->rhub_port->hw_portnum) * 2 + 1; + } else { + /* add one more for each SS port */ + bw_index = virt_dev->rhub_port->hw_portnum + xhci->usb3_rhub.num_ports; + } + + return &mtk->sch_array[bw_index]; +} + +static u32 get_esit(struct xhci_ep_ctx *ep_ctx) +{ + u32 esit; + + esit = 1 << CTX_TO_EP_INTERVAL(le32_to_cpu(ep_ctx->ep_info)); + if (esit > XHCI_MTK_MAX_ESIT) + esit = XHCI_MTK_MAX_ESIT; + + return esit; +} + +static struct mu3h_sch_tt *find_tt(struct usb_device *udev) +{ + struct usb_tt *utt = udev->tt; + struct mu3h_sch_tt *tt, **tt_index, **ptt; + bool allocated_index = false; + + if (!utt) + return NULL; /* Not below a TT */ + + /* + * Find/create our data structure. + * For hubs with a single TT, we get it directly. + * For hubs with multiple TTs, there's an extra level of pointers. + */ + tt_index = NULL; + if (utt->multi) { + tt_index = utt->hcpriv; + if (!tt_index) { /* Create the index array */ + tt_index = kzalloc_objs(*tt_index, utt->hub->maxchild); + if (!tt_index) + return ERR_PTR(-ENOMEM); + utt->hcpriv = tt_index; + allocated_index = true; + } + ptt = &tt_index[udev->ttport - 1]; + } else { + ptt = (struct mu3h_sch_tt **) &utt->hcpriv; + } + + tt = *ptt; + if (!tt) { /* Create the mu3h_sch_tt */ + tt = kzalloc_obj(*tt); + if (!tt) { + if (allocated_index) { + utt->hcpriv = NULL; + kfree(tt_index); + } + return ERR_PTR(-ENOMEM); + } + INIT_LIST_HEAD(&tt->ep_list); + *ptt = tt; + } + + return tt; +} + +/* Release the TT above udev, if it's not in use */ +static void drop_tt(struct usb_device *udev) +{ + struct usb_tt *utt = udev->tt; + struct mu3h_sch_tt *tt, **tt_index, **ptt; + int i, cnt; + + if (!utt || !utt->hcpriv) + return; /* Not below a TT, or never allocated */ + + cnt = 0; + if (utt->multi) { + tt_index = utt->hcpriv; + ptt = &tt_index[udev->ttport - 1]; + /* How many entries are left in tt_index? */ + for (i = 0; i < utt->hub->maxchild; ++i) + cnt += !!tt_index[i]; + } else { + tt_index = NULL; + ptt = (struct mu3h_sch_tt **)&utt->hcpriv; + } + + tt = *ptt; + if (!tt || !list_empty(&tt->ep_list)) + return; /* never allocated , or still in use*/ + + *ptt = NULL; + kfree(tt); + + if (cnt == 1) { + utt->hcpriv = NULL; + kfree(tt_index); + } +} + +static struct mu3h_sch_ep_info * +create_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev, + struct usb_host_endpoint *ep, struct xhci_ep_ctx *ep_ctx) +{ + struct mu3h_sch_ep_info *sch_ep; + struct mu3h_sch_bw_info *bw_info; + struct mu3h_sch_tt *tt = NULL; + u32 len; + + bw_info = get_bw_info(mtk, udev, ep); + if (!bw_info) + return ERR_PTR(-ENODEV); + + if (is_fs_or_ls(udev->speed)) + len = TT_MICROFRAMES_MAX; + else if ((udev->speed >= USB_SPEED_SUPER) && + usb_endpoint_xfer_isoc(&ep->desc)) + len = get_esit(ep_ctx); + else + len = 1; + + sch_ep = kzalloc_flex(*sch_ep, bw_budget_table, len); + if (!sch_ep) + return ERR_PTR(-ENOMEM); + + if (is_fs_or_ls(udev->speed)) { + tt = find_tt(udev); + if (IS_ERR(tt)) { + kfree(sch_ep); + return ERR_PTR(-ENOMEM); + } + } + + sch_ep->bw_info = bw_info; + sch_ep->sch_tt = tt; + sch_ep->ep = ep; + sch_ep->speed = udev->speed; + INIT_LIST_HEAD(&sch_ep->endpoint); + INIT_LIST_HEAD(&sch_ep->tt_endpoint); + INIT_HLIST_NODE(&sch_ep->hentry); + + return sch_ep; +} + +static void setup_sch_info(struct xhci_ep_ctx *ep_ctx, + struct mu3h_sch_ep_info *sch_ep) +{ + u32 ep_type; + u32 maxpkt; + u32 max_burst; + u32 mult; + u32 esit_pkts; + u32 max_esit_payload; + u32 bw_per_microframe; + u32 *bwb_table; + int i; + + bwb_table = sch_ep->bw_budget_table; + ep_type = CTX_TO_EP_TYPE(le32_to_cpu(ep_ctx->ep_info2)); + maxpkt = MAX_PACKET_DECODED(le32_to_cpu(ep_ctx->ep_info2)); + max_burst = CTX_TO_MAX_BURST(le32_to_cpu(ep_ctx->ep_info2)); + mult = CTX_TO_EP_MULT(le32_to_cpu(ep_ctx->ep_info)); + max_esit_payload = + (CTX_TO_MAX_ESIT_PAYLOAD_HI( + le32_to_cpu(ep_ctx->ep_info)) << 16) | + CTX_TO_MAX_ESIT_PAYLOAD(le32_to_cpu(ep_ctx->tx_info)); + + sch_ep->esit = get_esit(ep_ctx); + sch_ep->num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; + sch_ep->ep_type = ep_type; + sch_ep->maxpkt = maxpkt; + sch_ep->offset = 0; + sch_ep->burst_mode = 0; + sch_ep->repeat = 0; + + if (sch_ep->speed == USB_SPEED_HIGH) { + sch_ep->cs_count = 0; + + /* + * usb_20 spec section5.9 + * a single microframe is enough for HS synchromous endpoints + * in a interval + */ + sch_ep->num_budget_microframes = 1; + + /* + * xHCI spec section6.2.3.4 + * @max_burst is the number of additional transactions + * opportunities per microframe + */ + sch_ep->pkts = max_burst + 1; + bwb_table[0] = maxpkt * sch_ep->pkts; + } else if (sch_ep->speed >= USB_SPEED_SUPER) { + /* usb3_r1 spec section4.4.7 & 4.4.8 */ + sch_ep->cs_count = 0; + sch_ep->burst_mode = 1; + /* + * some device's (d)wBytesPerInterval is set as 0, + * then max_esit_payload is 0, so evaluate esit_pkts from + * mult and burst + */ + esit_pkts = DIV_ROUND_UP(max_esit_payload, maxpkt); + if (esit_pkts == 0) + esit_pkts = (mult + 1) * (max_burst + 1); + + if (ep_type == INT_IN_EP || ep_type == INT_OUT_EP) { + sch_ep->pkts = esit_pkts; + sch_ep->num_budget_microframes = 1; + bwb_table[0] = maxpkt * sch_ep->pkts; + } + + if (ep_type == ISOC_IN_EP || ep_type == ISOC_OUT_EP) { + + if (sch_ep->esit == 1) + sch_ep->pkts = esit_pkts; + else if (esit_pkts <= sch_ep->esit) + sch_ep->pkts = 1; + else + sch_ep->pkts = roundup_pow_of_two(esit_pkts) + / sch_ep->esit; + + sch_ep->num_budget_microframes = + DIV_ROUND_UP(esit_pkts, sch_ep->pkts); + + sch_ep->repeat = !!(sch_ep->num_budget_microframes > 1); + bw_per_microframe = maxpkt * sch_ep->pkts; + + for (i = 0; i < sch_ep->num_budget_microframes - 1; i++) + bwb_table[i] = bw_per_microframe; + + /* last one <= bw_per_microframe */ + bwb_table[i] = maxpkt * esit_pkts - i * bw_per_microframe; + } + } else if (is_fs_or_ls(sch_ep->speed)) { + sch_ep->pkts = 1; /* at most one packet for each microframe */ + + /* + * @cs_count will be updated to add extra-cs when + * check TT for INT_OUT_EP, ISOC/INT_IN_EP type + * @maxpkt <= 1023; + */ + sch_ep->cs_count = DIV_ROUND_UP(maxpkt, FS_PAYLOAD_MAX); + sch_ep->num_budget_microframes = sch_ep->cs_count; + + /* init budget table */ + if (ep_type == ISOC_OUT_EP) { + for (i = 0; i < sch_ep->cs_count - 1; i++) + bwb_table[i] = FS_PAYLOAD_MAX; + + bwb_table[i] = maxpkt - i * FS_PAYLOAD_MAX; + } else if (ep_type == INT_OUT_EP) { + /* only first one used (maxpkt <= 64), others zero */ + bwb_table[0] = maxpkt; + } else { /* INT_IN_EP or ISOC_IN_EP */ + bwb_table[0] = 0; /* start split */ + bwb_table[1] = 0; /* idle */ + /* + * @cs_count will be updated according to cs position + * (add 1 or 2 extra-cs), but assume only first + * @num_budget_microframes elements will be used later, + * although in fact it does not (extra-cs budget many receive + * some data for IN ep); + * @cs_count is 1 for INT_IN_EP (maxpkt <= 64); + */ + for (i = 0; i < sch_ep->cs_count - 1; i++) + bwb_table[i + CS_OFFSET] = FS_PAYLOAD_MAX; + + bwb_table[i + CS_OFFSET] = maxpkt - i * FS_PAYLOAD_MAX; + /* ss + idle */ + sch_ep->num_budget_microframes += CS_OFFSET; + } + } +} + +/* Get maximum bandwidth when we schedule at offset slot. */ +static u32 get_max_bw(struct mu3h_sch_bw_info *sch_bw, + struct mu3h_sch_ep_info *sch_ep, u32 offset) +{ + u32 max_bw = 0; + u32 bw; + int i, j, k; + + for (i = 0; i < sch_ep->num_esit; i++) { + u32 base = offset + i * sch_ep->esit; + + for (j = 0; j < sch_ep->num_budget_microframes; j++) { + k = XHCI_MTK_BW_INDEX(base + j); + bw = sch_bw->bus_bw[k] + sch_ep->bw_budget_table[j]; + if (bw > max_bw) + max_bw = bw; + } + } + return max_bw; +} + +/* + * for OUT: get first SS consumed bw; + * for IN: get first CS consumed bw; + */ +static u16 get_fs_bw(struct mu3h_sch_ep_info *sch_ep, int offset) +{ + struct mu3h_sch_tt *tt = sch_ep->sch_tt; + u16 fs_bw; + + if (sch_ep->ep_type == ISOC_OUT_EP || sch_ep->ep_type == INT_OUT_EP) + fs_bw = tt->fs_bus_bw_out[XHCI_MTK_BW_INDEX(offset)]; + else /* skip ss + idle */ + fs_bw = tt->fs_bus_bw_in[XHCI_MTK_BW_INDEX(offset + CS_OFFSET)]; + + return fs_bw; +} + +static void update_bus_bw(struct mu3h_sch_bw_info *sch_bw, + struct mu3h_sch_ep_info *sch_ep, bool used) +{ + u32 base; + int i, j, k; + + for (i = 0; i < sch_ep->num_esit; i++) { + base = sch_ep->offset + i * sch_ep->esit; + for (j = 0; j < sch_ep->num_budget_microframes; j++) { + k = XHCI_MTK_BW_INDEX(base + j); + if (used) + sch_bw->bus_bw[k] += sch_ep->bw_budget_table[j]; + else + sch_bw->bus_bw[k] -= sch_ep->bw_budget_table[j]; + } + } +} + +static int check_ls_budget_microframes(struct mu3h_sch_ep_info *sch_ep, int offset) +{ + struct mu3h_sch_tt *tt = sch_ep->sch_tt; + int i; + + if (sch_ep->speed != USB_SPEED_LOW) + return 0; + + if (sch_ep->ep_type == INT_OUT_EP) + i = XHCI_MTK_BW_INDEX(offset); + else if (sch_ep->ep_type == INT_IN_EP) + i = XHCI_MTK_BW_INDEX(offset + CS_OFFSET); /* skip ss + idle */ + else + return -EINVAL; + + if (tt->ls_bus_bw[i] + sch_ep->maxpkt > LS_PAYLOAD_MAX) + return -ESCH_BW_OVERFLOW; + + return 0; +} + +static int check_fs_budget_microframes(struct mu3h_sch_ep_info *sch_ep, int offset) +{ + struct mu3h_sch_tt *tt = sch_ep->sch_tt; + u32 tmp; + int i, k; + + /* + * for OUT eps, will transfer exactly assigned length of data, + * so can't allocate more than 188 bytes; + * but it's not for IN eps, usually it can't receive full + * 188 bytes in a uframe, if it not assign full 188 bytes, + * can add another one; + */ + for (i = 0; i < sch_ep->num_budget_microframes; i++) { + k = XHCI_MTK_BW_INDEX(offset + i); + if (sch_ep->ep_type == ISOC_OUT_EP || sch_ep->ep_type == INT_OUT_EP) + tmp = tt->fs_bus_bw_out[k] + sch_ep->bw_budget_table[i]; + else /* ep_type : ISOC IN / INTR IN */ + tmp = tt->fs_bus_bw_in[k]; + + if (tmp > FS_PAYLOAD_MAX) + return -ESCH_BW_OVERFLOW; + } + + return 0; +} + +static int check_fs_budget_frames(struct mu3h_sch_ep_info *sch_ep, int offset) +{ + struct mu3h_sch_tt *tt = sch_ep->sch_tt; + u32 head, tail; + int i, j, k; + + /* bugdet scheduled may cross at most two fs frames */ + j = XHCI_MTK_BW_INDEX(offset) / UFRAMES_PER_FRAME; + k = XHCI_MTK_BW_INDEX(offset + sch_ep->num_budget_microframes - 1) / UFRAMES_PER_FRAME; + + if (j != k) { + head = tt->fs_frame_bw[j]; + tail = tt->fs_frame_bw[k]; + } else { + head = tt->fs_frame_bw[j]; + tail = 0; + } + + j = roundup(offset, UFRAMES_PER_FRAME); + for (i = 0; i < sch_ep->num_budget_microframes; i++) { + if ((offset + i) < j) + head += sch_ep->bw_budget_table[i]; + else + tail += sch_ep->bw_budget_table[i]; + } + + if (head > FS_BW_BOUNDARY || tail > FS_BW_BOUNDARY) + return -ESCH_BW_OVERFLOW; + + return 0; +} + +static int check_fs_bus_bw(struct mu3h_sch_ep_info *sch_ep, int offset) +{ + int i, base; + int ret = 0; + + for (i = 0; i < sch_ep->num_esit; i++) { + base = offset + i * sch_ep->esit; + + ret = check_ls_budget_microframes(sch_ep, base); + if (ret) + goto err; + + ret = check_fs_budget_microframes(sch_ep, base); + if (ret) + goto err; + + ret = check_fs_budget_frames(sch_ep, base); + if (ret) + goto err; + } + +err: + return ret; +} + +static int check_ss_and_cs(struct mu3h_sch_ep_info *sch_ep, u32 offset) +{ + u32 start_ss, last_ss; + u32 start_cs, last_cs; + + start_ss = offset % UFRAMES_PER_FRAME; + + if (sch_ep->ep_type == ISOC_OUT_EP) { + last_ss = start_ss + sch_ep->cs_count - 1; + + /* + * usb_20 spec section11.18: + * must never schedule Start-Split in Y6 + */ + if (!(start_ss == 7 || last_ss < 6)) + return -ESCH_SS_Y6; + + } else { + /* maxpkt <= 1023, cs <= 6 */ + u32 cs_count = DIV_ROUND_UP(sch_ep->maxpkt, FS_PAYLOAD_MAX); + + /* + * usb_20 spec section11.18: + * must never schedule Start-Split in Y6 + */ + if (start_ss == 6) + return -ESCH_SS_Y6; + + /* one uframe for ss + one uframe for idle */ + start_cs = (start_ss + CS_OFFSET) % UFRAMES_PER_FRAME; + last_cs = start_cs + cs_count - 1; + if (last_cs > 7) + return -ESCH_CS_OVERFLOW; + + /* add extra-cs */ + cs_count += (last_cs == 7) ? 1 : 2; + if (cs_count > 7) + cs_count = 7; /* HW limit */ + + sch_ep->cs_count = cs_count; + + } + + return 0; +} + +/* + * when isoc-out transfers 188 bytes in a uframe, and send isoc/intr's + * ss token in the uframe, may cause 'bit stuff error' in downstream + * port; + * when isoc-out transfer less than 188 bytes in a uframe, shall send + * isoc-in's ss after isoc-out's ss (but hw can't ensure the sequence, + * so just avoid overlap). + */ +static int check_isoc_ss_overlap(struct mu3h_sch_ep_info *sch_ep, u32 offset) +{ + struct mu3h_sch_tt *tt = sch_ep->sch_tt; + int base; + int i, j, k; + + if (!tt) + return 0; + + for (i = 0; i < sch_ep->num_esit; i++) { + base = offset + i * sch_ep->esit; + + if (sch_ep->ep_type == ISOC_OUT_EP) { + for (j = 0; j < sch_ep->num_budget_microframes; j++) { + k = XHCI_MTK_BW_INDEX(base + j); + if (tt->in_ss_cnt[k]) + return -ESCH_SS_OVERLAP; + } + } else if (sch_ep->ep_type == ISOC_IN_EP || sch_ep->ep_type == INT_IN_EP) { + k = XHCI_MTK_BW_INDEX(base); + /* only check IN's ss */ + if (tt->fs_bus_bw_out[k]) + return -ESCH_SS_OVERLAP; + } + } + + return 0; +} + +static int check_sch_tt_budget(struct mu3h_sch_ep_info *sch_ep, u32 offset) +{ + int ret; + + ret = check_ss_and_cs(sch_ep, offset); + if (ret) + return ret; + + ret = check_isoc_ss_overlap(sch_ep, offset); + if (ret) + return ret; + + return check_fs_bus_bw(sch_ep, offset); +} + +/* allocate microframes in the ls/fs frame */ +static int alloc_sch_portion_of_frame(struct mu3h_sch_ep_info *sch_ep) +{ + struct mu3h_sch_bw_info *sch_bw = sch_ep->bw_info; + const u32 bw_boundary = get_bw_boundary(sch_ep->speed); + u32 bw_max, fs_bw_min; + u32 offset, offset_min; + u16 fs_bw; + int frames; + int i, j; + int ret; + + frames = sch_ep->esit / UFRAMES_PER_FRAME; + + for (i = 0; i < UFRAMES_PER_FRAME; i++) { + fs_bw_min = FS_PAYLOAD_MAX; + offset_min = XHCI_MTK_MAX_ESIT; + + for (j = 0; j < frames; j++) { + offset = (i + j * UFRAMES_PER_FRAME) % sch_ep->esit; + + ret = check_sch_tt_budget(sch_ep, offset); + if (ret) + continue; + + /* check hs bw domain */ + bw_max = get_max_bw(sch_bw, sch_ep, offset); + if (bw_max > bw_boundary) { + ret = -ESCH_BW_OVERFLOW; + continue; + } + + /* use best-fit between frames */ + fs_bw = get_fs_bw(sch_ep, offset); + if (fs_bw < fs_bw_min) { + fs_bw_min = fs_bw; + offset_min = offset; + } + + if (!fs_bw_min) + break; + } + + /* use first-fit between microframes in a frame */ + if (offset_min < XHCI_MTK_MAX_ESIT) + break; + } + + if (offset_min == XHCI_MTK_MAX_ESIT) + return -ESCH_BW_OVERFLOW; + + sch_ep->offset = offset_min; + + return 0; +} + +static void update_sch_tt(struct mu3h_sch_ep_info *sch_ep, bool used) +{ + struct mu3h_sch_tt *tt = sch_ep->sch_tt; + u16 *fs_bus_bw; + u32 base; + int i, j, k, f; + + if (sch_ep->ep_type == ISOC_OUT_EP || sch_ep->ep_type == INT_OUT_EP) + fs_bus_bw = tt->fs_bus_bw_out; + else + fs_bus_bw = tt->fs_bus_bw_in; + + for (i = 0; i < sch_ep->num_esit; i++) { + base = sch_ep->offset + i * sch_ep->esit; + + for (j = 0; j < sch_ep->num_budget_microframes; j++) { + k = XHCI_MTK_BW_INDEX(base + j); + f = k / UFRAMES_PER_FRAME; + if (used) { + if (sch_ep->speed == USB_SPEED_LOW) + tt->ls_bus_bw[k] += (u8)sch_ep->bw_budget_table[j]; + + fs_bus_bw[k] += (u16)sch_ep->bw_budget_table[j]; + tt->fs_frame_bw[f] += (u16)sch_ep->bw_budget_table[j]; + } else { + if (sch_ep->speed == USB_SPEED_LOW) + tt->ls_bus_bw[k] -= (u8)sch_ep->bw_budget_table[j]; + + fs_bus_bw[k] -= (u16)sch_ep->bw_budget_table[j]; + tt->fs_frame_bw[f] -= (u16)sch_ep->bw_budget_table[j]; + } + } + + if (sch_ep->ep_type == ISOC_IN_EP || sch_ep->ep_type == INT_IN_EP) { + k = XHCI_MTK_BW_INDEX(base); + if (used) + tt->in_ss_cnt[k]++; + else + tt->in_ss_cnt[k]--; + } + } + + if (used) + list_add_tail(&sch_ep->tt_endpoint, &tt->ep_list); + else + list_del(&sch_ep->tt_endpoint); +} + +static int load_ep_bw(struct mu3h_sch_bw_info *sch_bw, + struct mu3h_sch_ep_info *sch_ep, bool loaded) +{ + if (sch_ep->sch_tt) + update_sch_tt(sch_ep, loaded); + + /* update bus bandwidth info */ + update_bus_bw(sch_bw, sch_ep, loaded); + sch_ep->allocated = loaded; + + return 0; +} + +/* allocate microframes for hs/ss/ssp */ +static int alloc_sch_microframes(struct mu3h_sch_ep_info *sch_ep) +{ + struct mu3h_sch_bw_info *sch_bw = sch_ep->bw_info; + const u32 bw_boundary = get_bw_boundary(sch_ep->speed); + u32 offset; + u32 worst_bw; + u32 min_bw = ~0; + int min_index = -1; + + /* + * Search through all possible schedule microframes. + * and find a microframe where its worst bandwidth is minimum. + */ + for (offset = 0; offset < sch_ep->esit; offset++) { + + worst_bw = get_max_bw(sch_bw, sch_ep, offset); + if (worst_bw > bw_boundary) + continue; + + if (min_bw > worst_bw) { + min_bw = worst_bw; + min_index = offset; + } + } + + if (min_index < 0) + return -ESCH_BW_OVERFLOW; + + sch_ep->offset = min_index; + + return 0; +} + +static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep) +{ + int ret; + + if (sch_ep->sch_tt) + ret = alloc_sch_portion_of_frame(sch_ep); + else + ret = alloc_sch_microframes(sch_ep); + + if (ret) + return ret; + + return load_ep_bw(sch_ep->bw_info, sch_ep, true); +} + +static void destroy_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev, + struct mu3h_sch_ep_info *sch_ep) +{ + /* only release ep bw check passed by check_sch_bw() */ + if (sch_ep->allocated) + load_ep_bw(sch_ep->bw_info, sch_ep, false); + + if (sch_ep->sch_tt) + drop_tt(udev); + + list_del(&sch_ep->endpoint); + hlist_del(&sch_ep->hentry); + kfree(sch_ep); +} + +static bool need_bw_sch(struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + bool has_tt = udev->tt && udev->tt->hub->parent; + + /* only for periodic endpoints */ + if (usb_endpoint_xfer_control(&ep->desc) + || usb_endpoint_xfer_bulk(&ep->desc)) + return false; + + /* + * for LS & FS periodic endpoints which its device is not behind + * a TT are also ignored, root-hub will schedule them directly, + * but need set @bpkts field of endpoint context to 1. + */ + if (is_fs_or_ls(udev->speed) && !has_tt) + return false; + + /* skip endpoint with zero maxpkt */ + if (usb_endpoint_maxp(&ep->desc) == 0) + return false; + + return true; +} + +int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk) +{ + struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd); + struct mu3h_sch_bw_info *sch_array; + int num_usb_bus; + + /* ss IN and OUT are separated */ + num_usb_bus = xhci->usb3_rhub.num_ports * 2 + xhci->usb2_rhub.num_ports; + + sch_array = kzalloc_objs(*sch_array, num_usb_bus); + if (sch_array == NULL) + return -ENOMEM; + + mtk->sch_array = sch_array; + + INIT_LIST_HEAD(&mtk->bw_ep_chk_list); + hash_init(mtk->sch_ep_hash); + + return 0; +} + +void xhci_mtk_sch_exit(struct xhci_hcd_mtk *mtk) +{ + kfree(mtk->sch_array); +} + +static int add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_ep_ctx *ep_ctx; + struct xhci_virt_device *virt_dev; + struct mu3h_sch_ep_info *sch_ep; + unsigned int ep_index; + + virt_dev = xhci->devs[udev->slot_id]; + ep_index = xhci_get_endpoint_index(&ep->desc); + ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); + + if (!need_bw_sch(udev, ep)) { + /* + * set @bpkts to 1 if it is LS or FS periodic endpoint, and its + * device does not connected through an external HS hub + */ + if (usb_endpoint_xfer_int(&ep->desc) + || usb_endpoint_xfer_isoc(&ep->desc)) + ep_ctx->reserved[0] = cpu_to_le32(EP_BPKTS(1)); + + return 0; + } + + xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed)); + + sch_ep = create_sch_ep(mtk, udev, ep, ep_ctx); + if (IS_ERR_OR_NULL(sch_ep)) + return -ENOMEM; + + setup_sch_info(ep_ctx, sch_ep); + + list_add_tail(&sch_ep->endpoint, &mtk->bw_ep_chk_list); + hash_add(mtk->sch_ep_hash, &sch_ep->hentry, (unsigned long)ep); + + return 0; +} + +static void drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct mu3h_sch_ep_info *sch_ep; + struct hlist_node *hn; + + if (!need_bw_sch(udev, ep)) + return; + + xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed)); + + hash_for_each_possible_safe(mtk->sch_ep_hash, sch_ep, + hn, hentry, (unsigned long)ep) { + if (sch_ep->ep == ep) { + destroy_sch_ep(mtk, udev, sch_ep); + break; + } + } +} + +int xhci_mtk_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_virt_device *virt_dev = xhci->devs[udev->slot_id]; + struct mu3h_sch_ep_info *sch_ep; + int ret; + + xhci_dbg(xhci, "%s() udev %s\n", __func__, dev_name(&udev->dev)); + + list_for_each_entry(sch_ep, &mtk->bw_ep_chk_list, endpoint) { + struct xhci_ep_ctx *ep_ctx; + struct usb_host_endpoint *ep = sch_ep->ep; + unsigned int ep_index = xhci_get_endpoint_index(&ep->desc); + + ret = check_sch_bw(sch_ep); + if (ret) { + xhci_err(xhci, "Not enough bandwidth! (%s)\n", + sch_error_string(-ret)); + return -ENOSPC; + } + + ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); + ep_ctx->reserved[0] = cpu_to_le32(EP_BPKTS(sch_ep->pkts) + | EP_BCSCOUNT(sch_ep->cs_count) + | EP_BBM(sch_ep->burst_mode)); + ep_ctx->reserved[1] = cpu_to_le32(EP_BOFFSET(sch_ep->offset) + | EP_BREPEAT(sch_ep->repeat)); + + xhci_dbg(xhci, " PKTS:%x, CSCOUNT:%x, BM:%x, OFFSET:%x, REPEAT:%x\n", + sch_ep->pkts, sch_ep->cs_count, sch_ep->burst_mode, + sch_ep->offset, sch_ep->repeat); + } + + ret = xhci_check_bandwidth(hcd, udev); + if (!ret) + list_del_init(&mtk->bw_ep_chk_list); + + return ret; +} + +void xhci_mtk_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct mu3h_sch_ep_info *sch_ep, *tmp; + + xhci_dbg(xhci, "%s() udev %s\n", __func__, dev_name(&udev->dev)); + + list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_chk_list, endpoint) + destroy_sch_ep(mtk, udev, sch_ep); + + xhci_reset_bandwidth(hcd, udev); +} + +int xhci_mtk_add_ep(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + int ret; + + ret = xhci_add_endpoint(hcd, udev, ep); + if (ret) + return ret; + + if (ep->hcpriv) + ret = add_ep_quirk(hcd, udev, ep); + + return ret; +} + +int xhci_mtk_drop_ep(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + int ret; + + ret = xhci_drop_endpoint(hcd, udev, ep); + if (ret) + return ret; + + /* needn't check @ep->hcpriv, xhci_endpoint_disable set it NULL */ + drop_ep_quirk(hcd, udev, ep); + + return 0; +} diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c new file mode 100644 index 000000000..60af5fe29 --- /dev/null +++ b/drivers/usb/host/xhci-mtk.c @@ -0,0 +1,878 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MediaTek xHCI Host Controller Driver + * + * Copyright (c) 2015 MediaTek Inc. + * Author: + * Chunfeng Yun + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xhci.h" +#include "xhci-mtk.h" + +/* ip_pw_ctrl0 register */ +#define CTRL0_IP_SW_RST BIT(0) + +/* ip_pw_ctrl1 register */ +#define CTRL1_IP_HOST_PDN BIT(0) + +/* ip_pw_ctrl2 register */ +#define CTRL2_IP_DEV_PDN BIT(0) + +/* ip_pw_sts1 register */ +#define STS1_IP_SLEEP_STS BIT(30) +#define STS1_U3_MAC_RST BIT(16) +#define STS1_XHCI_RST BIT(11) +#define STS1_SYS125_RST BIT(10) +#define STS1_REF_RST BIT(8) +#define STS1_SYSPLL_STABLE BIT(0) + +/* ip_xhci_cap register */ +#define CAP_U3_PORT_NUM(p) ((p) & 0xff) +#define CAP_U2_PORT_NUM(p) (((p) >> 8) & 0xff) + +/* u3_ctrl_p register */ +#define CTRL_U3_PORT_HOST_SEL BIT(2) +#define CTRL_U3_PORT_PDN BIT(1) +#define CTRL_U3_PORT_DIS BIT(0) + +/* u2_ctrl_p register */ +#define CTRL_U2_PORT_HOST_SEL BIT(2) +#define CTRL_U2_PORT_PDN BIT(1) +#define CTRL_U2_PORT_DIS BIT(0) + +/* u2_phy_pll register */ +#define CTRL_U2_FORCE_PLL_STB BIT(28) + +/* xHCI CSR */ +#define LS_EOF_CFG 0x930 +#define LSEOF_OFFSET 0x89 + +#define FS_EOF_CFG 0x934 +#define FSEOF_OFFSET 0x2e + +#define SS_GEN1_EOF_CFG 0x93c +#define SSG1EOF_OFFSET 0x78 + +#define HFCNTR_CFG 0x944 +#define ITP_DELTA_CLK (0xa << 1) +#define ITP_DELTA_CLK_MASK GENMASK(5, 1) +#define FRMCNT_LEV1_RANG (0x12b << 8) +#define FRMCNT_LEV1_RANG_MASK GENMASK(19, 8) + +#define HSCH_CFG1 0x960 +#define SCH3_RXFIFO_DEPTH_MASK GENMASK(21, 20) + +#define SS_GEN2_EOF_CFG 0x990 +#define SSG2EOF_OFFSET 0x3c + +#define XSEOF_OFFSET_MASK GENMASK(11, 0) + +/* usb remote wakeup registers in syscon */ + +/* mt8173 etc */ +#define PERI_WK_CTRL1 0x4 +#define WC1_IS_C(x) (((x) & 0xf) << 26) /* cycle debounce */ +#define WC1_IS_EN BIT(25) +#define WC1_IS_P BIT(6) /* polarity for ip sleep */ + +/* mt8183 */ +#define PERI_WK_CTRL0 0x0 +#define WC0_IS_C(x) ((u32)(((x) & 0xf) << 28)) /* cycle debounce */ +#define WC0_IS_P BIT(12) /* polarity */ +#define WC0_IS_EN BIT(6) + +/* mt8192 */ +#define WC0_SSUSB0_CDEN BIT(6) +#define WC0_IS_SPM_EN BIT(1) + +/* mt8195 */ +#define PERI_WK_CTRL0_8195 0x04 +#define WC0_IS_P_95 BIT(30) /* polarity */ +#define WC0_IS_C_95(x) ((u32)(((x) & 0x7) << 27)) +#define WC0_IS_EN_P3_95 BIT(26) +#define WC0_IS_EN_P2_95 BIT(25) +#define WC0_IS_EN_P1_95 BIT(24) + +#define PERI_WK_CTRL1_8195 0x20 +#define WC1_IS_C_95(x) ((u32)(((x) & 0xf) << 28)) +#define WC1_IS_P_95 BIT(12) +#define WC1_IS_EN_P0_95 BIT(6) + +/* mt2712 etc */ +#define PERI_SSUSB_SPM_CTRL 0x0 +#define SSC_IP_SLEEP_EN BIT(4) +#define SSC_SPM_INT_EN BIT(1) + +#define SCH_FIFO_TO_KB(x) ((x) >> 10) + +enum ssusb_uwk_vers { + SSUSB_UWK_V1 = 1, + SSUSB_UWK_V2, + SSUSB_UWK_V1_1 = 101, /* specific revision 1.01 */ + SSUSB_UWK_V1_2, /* specific revision 1.2 */ + SSUSB_UWK_V1_3, /* mt8195 IP0 */ + SSUSB_UWK_V1_4, /* mt8195 IP1 */ + SSUSB_UWK_V1_5, /* mt8195 IP2 */ + SSUSB_UWK_V1_6, /* mt8195 IP3 */ +}; + +/* + * MT8195 has 4 controllers, the controller1~3's default SOF/ITP interval + * is calculated from the frame counter clock 24M, but in fact, the clock + * is 48M, add workaround for it. + */ +static void xhci_mtk_set_frame_interval(struct xhci_hcd_mtk *mtk) +{ + struct device *dev = mtk->dev; + struct usb_hcd *hcd = mtk->hcd; + u32 value; + + if (!of_device_is_compatible(dev->of_node, "mediatek,mt8195-xhci")) + return; + + value = readl(hcd->regs + HFCNTR_CFG); + value &= ~(ITP_DELTA_CLK_MASK | FRMCNT_LEV1_RANG_MASK); + value |= (ITP_DELTA_CLK | FRMCNT_LEV1_RANG); + writel(value, hcd->regs + HFCNTR_CFG); + + value = readl(hcd->regs + LS_EOF_CFG); + value &= ~XSEOF_OFFSET_MASK; + value |= LSEOF_OFFSET; + writel(value, hcd->regs + LS_EOF_CFG); + + value = readl(hcd->regs + FS_EOF_CFG); + value &= ~XSEOF_OFFSET_MASK; + value |= FSEOF_OFFSET; + writel(value, hcd->regs + FS_EOF_CFG); + + value = readl(hcd->regs + SS_GEN1_EOF_CFG); + value &= ~XSEOF_OFFSET_MASK; + value |= SSG1EOF_OFFSET; + writel(value, hcd->regs + SS_GEN1_EOF_CFG); + + value = readl(hcd->regs + SS_GEN2_EOF_CFG); + value &= ~XSEOF_OFFSET_MASK; + value |= SSG2EOF_OFFSET; + writel(value, hcd->regs + SS_GEN2_EOF_CFG); +} + +/* + * workaround: usb3.2 gen1 isoc rx hw issue + * host send out unexpected ACK afer device fininsh a burst transfer with + * a short packet. + */ +static void xhci_mtk_rxfifo_depth_set(struct xhci_hcd_mtk *mtk) +{ + struct usb_hcd *hcd = mtk->hcd; + u32 value; + + if (!mtk->rxfifo_depth) + return; + + value = readl(hcd->regs + HSCH_CFG1); + FIELD_MODIFY(SCH3_RXFIFO_DEPTH_MASK, &value, + SCH_FIFO_TO_KB(mtk->rxfifo_depth) - 1); + + writel(value, hcd->regs + HSCH_CFG1); +} + +static void xhci_mtk_init_quirk(struct xhci_hcd_mtk *mtk) +{ + /* workaround only for mt8195 */ + xhci_mtk_set_frame_interval(mtk); + + /* workaround for SoCs using SSUSB about before IPM v1.6.0 */ + xhci_mtk_rxfifo_depth_set(mtk); +} + +static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk) +{ + struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs; + u32 value, check_val; + int u3_ports_disabled = 0; + int ret; + int i; + + if (!mtk->has_ippc) + return 0; + + /* power on host ip */ + value = readl(&ippc->ip_pw_ctr1); + value &= ~CTRL1_IP_HOST_PDN; + writel(value, &ippc->ip_pw_ctr1); + + /* power on and enable u3 ports except skipped ones */ + for (i = 0; i < mtk->num_u3_ports; i++) { + if ((0x1 << i) & mtk->u3p_dis_msk) { + u3_ports_disabled++; + continue; + } + + value = readl(&ippc->u3_ctrl_p[i]); + value &= ~(CTRL_U3_PORT_PDN | CTRL_U3_PORT_DIS); + value |= CTRL_U3_PORT_HOST_SEL; + writel(value, &ippc->u3_ctrl_p[i]); + } + + /* power on and enable all u2 ports except skipped ones */ + for (i = 0; i < mtk->num_u2_ports; i++) { + if (BIT(i) & mtk->u2p_dis_msk) + continue; + + value = readl(&ippc->u2_ctrl_p[i]); + value &= ~(CTRL_U2_PORT_PDN | CTRL_U2_PORT_DIS); + value |= CTRL_U2_PORT_HOST_SEL; + writel(value, &ippc->u2_ctrl_p[i]); + } + + /* + * wait for clocks to be stable, and clock domains reset to + * be inactive after power on and enable ports + */ + check_val = STS1_SYSPLL_STABLE | STS1_REF_RST | + STS1_SYS125_RST | STS1_XHCI_RST; + + if (mtk->num_u3_ports > u3_ports_disabled) + check_val |= STS1_U3_MAC_RST; + + ret = readl_poll_timeout(&ippc->ip_pw_sts1, value, + (check_val == (value & check_val)), 100, 20000); + if (ret) { + dev_err(mtk->dev, "clocks are not stable (0x%x)\n", value); + return ret; + } + + return 0; +} + +static int xhci_mtk_host_disable(struct xhci_hcd_mtk *mtk) +{ + struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs; + u32 value; + int ret; + int i; + + if (!mtk->has_ippc) + return 0; + + /* power down u3 ports except skipped ones */ + for (i = 0; i < mtk->num_u3_ports; i++) { + if ((0x1 << i) & mtk->u3p_dis_msk) + continue; + + value = readl(&ippc->u3_ctrl_p[i]); + value |= CTRL_U3_PORT_PDN; + writel(value, &ippc->u3_ctrl_p[i]); + } + + /* power down all u2 ports except skipped ones */ + for (i = 0; i < mtk->num_u2_ports; i++) { + if (BIT(i) & mtk->u2p_dis_msk) + continue; + + value = readl(&ippc->u2_ctrl_p[i]); + value |= CTRL_U2_PORT_PDN; + writel(value, &ippc->u2_ctrl_p[i]); + } + + /* power down host ip */ + value = readl(&ippc->ip_pw_ctr1); + value |= CTRL1_IP_HOST_PDN; + writel(value, &ippc->ip_pw_ctr1); + + /* wait for host ip to sleep */ + ret = readl_poll_timeout(&ippc->ip_pw_sts1, value, + (value & STS1_IP_SLEEP_STS), 100, 100000); + if (ret) + dev_err(mtk->dev, "ip sleep failed!!!\n"); + else /* workaound for platforms using low level latch */ + usleep_range(100, 200); + + return ret; +} + +static int xhci_mtk_ssusb_config(struct xhci_hcd_mtk *mtk) +{ + struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs; + u32 value; + + if (!mtk->has_ippc) + return 0; + + /* reset whole ip */ + value = readl(&ippc->ip_pw_ctr0); + value |= CTRL0_IP_SW_RST; + writel(value, &ippc->ip_pw_ctr0); + udelay(1); + value = readl(&ippc->ip_pw_ctr0); + value &= ~CTRL0_IP_SW_RST; + writel(value, &ippc->ip_pw_ctr0); + + /* + * device ip is default power-on in fact + * power down device ip, otherwise ip-sleep will fail + */ + value = readl(&ippc->ip_pw_ctr2); + value |= CTRL2_IP_DEV_PDN; + writel(value, &ippc->ip_pw_ctr2); + + value = readl(&ippc->ip_xhci_cap); + mtk->num_u3_ports = CAP_U3_PORT_NUM(value); + mtk->num_u2_ports = CAP_U2_PORT_NUM(value); + dev_dbg(mtk->dev, "%s u2p:%d, u3p:%d\n", __func__, + mtk->num_u2_ports, mtk->num_u3_ports); + + return xhci_mtk_host_enable(mtk); +} + +/* only clocks can be turn off for ip-sleep wakeup mode */ +static void usb_wakeup_ip_sleep_set(struct xhci_hcd_mtk *mtk, bool enable) +{ + u32 reg, msk, val; + + switch (mtk->uwk_vers) { + case SSUSB_UWK_V1: + reg = mtk->uwk_reg_base + PERI_WK_CTRL1; + msk = WC1_IS_EN | WC1_IS_C(0xf) | WC1_IS_P; + val = enable ? (WC1_IS_EN | WC1_IS_C(0x8)) : 0; + break; + case SSUSB_UWK_V1_1: + reg = mtk->uwk_reg_base + PERI_WK_CTRL0; + msk = WC0_IS_EN | WC0_IS_C(0xf) | WC0_IS_P; + val = enable ? (WC0_IS_EN | WC0_IS_C(0x1)) : 0; + break; + case SSUSB_UWK_V1_2: + reg = mtk->uwk_reg_base + PERI_WK_CTRL0; + msk = WC0_SSUSB0_CDEN | WC0_IS_SPM_EN; + val = enable ? msk : 0; + break; + case SSUSB_UWK_V1_3: + reg = mtk->uwk_reg_base + PERI_WK_CTRL1_8195; + msk = WC1_IS_EN_P0_95 | WC1_IS_C_95(0xf) | WC1_IS_P_95; + val = enable ? (WC1_IS_EN_P0_95 | WC1_IS_C_95(0x1)) : 0; + break; + case SSUSB_UWK_V1_4: + reg = mtk->uwk_reg_base + PERI_WK_CTRL0_8195; + msk = WC0_IS_EN_P1_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P1_95 | WC0_IS_C_95(0x1)) : 0; + break; + case SSUSB_UWK_V1_5: + reg = mtk->uwk_reg_base + PERI_WK_CTRL0_8195; + msk = WC0_IS_EN_P2_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P2_95 | WC0_IS_C_95(0x1)) : 0; + break; + case SSUSB_UWK_V1_6: + reg = mtk->uwk_reg_base + PERI_WK_CTRL0_8195; + msk = WC0_IS_EN_P3_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P3_95 | WC0_IS_C_95(0x1)) : 0; + break; + case SSUSB_UWK_V2: + reg = mtk->uwk_reg_base + PERI_SSUSB_SPM_CTRL; + msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; + val = enable ? msk : 0; + break; + default: + return; + } + regmap_update_bits(mtk->uwk, reg, msk, val); +} + +static int usb_wakeup_of_property_parse(struct xhci_hcd_mtk *mtk, + struct device_node *dn) +{ + struct of_phandle_args args; + int ret; + + /* Wakeup function is optional */ + mtk->uwk_en = of_property_read_bool(dn, "wakeup-source"); + if (!mtk->uwk_en) + return 0; + + ret = of_parse_phandle_with_fixed_args(dn, + "mediatek,syscon-wakeup", 2, 0, &args); + if (ret) + return ret; + + mtk->uwk_reg_base = args.args[0]; + mtk->uwk_vers = args.args[1]; + mtk->uwk = syscon_node_to_regmap(args.np); + of_node_put(args.np); + dev_info(mtk->dev, "uwk - reg:0x%x, version:%d\n", + mtk->uwk_reg_base, mtk->uwk_vers); + + return PTR_ERR_OR_ZERO(mtk->uwk); +} + +static void usb_wakeup_set(struct xhci_hcd_mtk *mtk, bool enable) +{ + if (mtk->uwk_en) + usb_wakeup_ip_sleep_set(mtk, enable); +} + +static int xhci_mtk_clks_get(struct xhci_hcd_mtk *mtk) +{ + struct clk_bulk_data *clks = mtk->clks; + + clks[0].id = "sys_ck"; + clks[1].id = "xhci_ck"; + clks[2].id = "ref_ck"; + clks[3].id = "mcu_ck"; + clks[4].id = "dma_ck"; + clks[5].id = "frmcnt_ck"; + + return devm_clk_bulk_get_optional(mtk->dev, BULK_CLKS_NUM, clks); +} + +static int xhci_mtk_vregs_get(struct xhci_hcd_mtk *mtk) +{ + struct regulator_bulk_data *supplies = mtk->supplies; + + supplies[0].supply = "vbus"; + supplies[1].supply = "vusb33"; + + return devm_regulator_bulk_get(mtk->dev, BULK_VREGS_NUM, supplies); +} + +static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci) +{ + struct usb_hcd *hcd = xhci_to_hcd(xhci); + struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); + + xhci->quirks |= XHCI_MTK_HOST; + /* + * MTK host controller gives a spurious successful event after a + * short transfer. Ignore it. + */ + xhci->quirks |= XHCI_SPURIOUS_SUCCESS; + if (mtk->lpm_support) + xhci->quirks |= XHCI_LPM_SUPPORT; + if (mtk->u2_lpm_disable) + xhci->quirks |= XHCI_HW_LPM_DISABLE; + + /* + * MTK xHCI 0.96: PSA is 1 by default even if doesn't support stream, + * and it's 3 when support it. + */ + if (xhci->hci_version < 0x100 && GET_MAX_PSA_SIZE(xhci->hcc_params) == 4) + xhci->quirks |= XHCI_BROKEN_STREAMS; +} + +/* called during probe() after chip reset completes */ +static int xhci_mtk_setup(struct usb_hcd *hcd) +{ + struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd); + int ret; + + if (usb_hcd_is_primary_hcd(hcd)) { + ret = xhci_mtk_ssusb_config(mtk); + if (ret) + return ret; + + xhci_mtk_init_quirk(mtk); + } + + ret = xhci_gen_setup(hcd, xhci_mtk_quirks); + if (ret) + return ret; + + if (usb_hcd_is_primary_hcd(hcd)) + ret = xhci_mtk_sch_init(mtk); + + return ret; +} + +static const struct xhci_driver_overrides xhci_mtk_overrides __initconst = { + .reset = xhci_mtk_setup, + .add_endpoint = xhci_mtk_add_ep, + .drop_endpoint = xhci_mtk_drop_ep, + .check_bandwidth = xhci_mtk_check_bandwidth, + .reset_bandwidth = xhci_mtk_reset_bandwidth, +}; + +static struct hc_driver __read_mostly xhci_mtk_hc_driver; + +static int xhci_mtk_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct xhci_hcd_mtk *mtk; + const struct hc_driver *driver; + struct xhci_hcd *xhci; + struct resource *res; + struct usb_hcd *usb3_hcd; + struct usb_hcd *hcd; + int ret = -ENODEV; + int wakeup_irq; + int irq; + + if (usb_disabled()) + return -ENODEV; + + driver = &xhci_mtk_hc_driver; + mtk = devm_kzalloc(dev, sizeof(*mtk), GFP_KERNEL); + if (!mtk) + return -ENOMEM; + + mtk->dev = dev; + + ret = xhci_mtk_vregs_get(mtk); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ret = xhci_mtk_clks_get(mtk); + if (ret) + return ret; + + irq = platform_get_irq_byname_optional(pdev, "host"); + if (irq < 0) { + if (irq == -EPROBE_DEFER) + return irq; + + /* for backward compatibility */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + } + + wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup"); + if (wakeup_irq == -EPROBE_DEFER) + return wakeup_irq; + + mtk->lpm_support = of_property_read_bool(node, "usb3-lpm-capable"); + mtk->u2_lpm_disable = of_property_read_bool(node, "usb2-lpm-disable"); + /* optional property, ignore the error if it does not exist */ + of_property_read_u32(node, "mediatek,u3p-dis-msk", + &mtk->u3p_dis_msk); + of_property_read_u32(node, "mediatek,u2p-dis-msk", + &mtk->u2p_dis_msk); + + of_property_read_u32(node, "rx-fifo-depth", &mtk->rxfifo_depth); + + ret = usb_wakeup_of_property_parse(mtk, node); + if (ret) { + dev_err(dev, "failed to parse uwk property\n"); + return ret; + } + + pm_runtime_set_active(dev); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, 4000); + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + ret = regulator_bulk_enable(BULK_VREGS_NUM, mtk->supplies); + if (ret) + goto disable_pm; + + ret = clk_bulk_prepare_enable(BULK_CLKS_NUM, mtk->clks); + if (ret) + goto disable_ldos; + + ret = device_reset_optional(dev); + if (ret) { + dev_err_probe(dev, ret, "failed to reset controller\n"); + goto disable_clk; + } + + hcd = usb_create_hcd(driver, dev, dev_name(dev)); + if (!hcd) { + ret = -ENOMEM; + goto disable_clk; + } + + /* + * USB 2.0 roothub is stored in the platform_device. + * Swap it with mtk HCD. + */ + mtk->hcd = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, mtk); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mac"); + hcd->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(hcd->regs)) { + ret = PTR_ERR(hcd->regs); + goto put_usb2_hcd; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ippc"); + if (res) { /* ippc register is optional */ + mtk->ippc_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(mtk->ippc_regs)) { + ret = PTR_ERR(mtk->ippc_regs); + goto put_usb2_hcd; + } + mtk->has_ippc = true; + } + + device_init_wakeup(dev, true); + dma_set_max_seg_size(dev, UINT_MAX); + + xhci = hcd_to_xhci(hcd); + xhci->main_hcd = hcd; + xhci->allow_single_roothub = 1; + + /* + * imod_interval is the interrupt moderation value in nanoseconds. + * The increment interval is 8 times as much as that defined in + * the xHCI spec on MTK's controller. + */ + xhci->imod_interval = 5000; + device_property_read_u32(dev, "imod-interval-ns", &xhci->imod_interval); + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret) + goto disable_device_wakeup; + + if (!xhci_has_one_roothub(xhci)) { + xhci->shared_hcd = usb_create_shared_hcd(driver, dev, + dev_name(dev), hcd); + if (!xhci->shared_hcd) { + ret = -ENOMEM; + goto dealloc_usb2_hcd; + } + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); + if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 && + !(xhci->quirks & XHCI_BROKEN_STREAMS)) + usb3_hcd->can_do_streams = 1; + + if (xhci->shared_hcd) { + ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); + if (ret) + goto put_usb3_hcd; + } + + if (wakeup_irq > 0) { + ret = dev_pm_set_dedicated_wake_irq_reverse(dev, wakeup_irq); + if (ret) { + dev_err(dev, "set wakeup irq %d failed\n", wakeup_irq); + goto dealloc_usb3_hcd; + } + dev_info(dev, "wakeup irq %d\n", wakeup_irq); + } + + device_enable_async_suspend(dev); + pm_runtime_put_autosuspend(dev); + pm_runtime_forbid(dev); + + return 0; + +dealloc_usb3_hcd: + usb_remove_hcd(xhci->shared_hcd); + +put_usb3_hcd: + usb_put_hcd(xhci->shared_hcd); + +dealloc_usb2_hcd: + xhci_mtk_sch_exit(mtk); + usb_remove_hcd(hcd); + +disable_device_wakeup: + device_init_wakeup(dev, false); + +put_usb2_hcd: + usb_put_hcd(hcd); + +disable_clk: + clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks); + +disable_ldos: + regulator_bulk_disable(BULK_VREGS_NUM, mtk->supplies); + +disable_pm: + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + return ret; +} + +static void xhci_mtk_remove(struct platform_device *pdev) +{ + struct xhci_hcd_mtk *mtk = platform_get_drvdata(pdev); + struct usb_hcd *hcd = mtk->hcd; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct usb_hcd *shared_hcd = xhci->shared_hcd; + struct device *dev = &pdev->dev; + + pm_runtime_get_sync(dev); + xhci->xhc_state |= XHCI_STATE_REMOVING; + dev_pm_clear_wake_irq(dev); + device_init_wakeup(dev, false); + + if (shared_hcd) { + usb_remove_hcd(shared_hcd); + xhci->shared_hcd = NULL; + } + usb_remove_hcd(hcd); + + if (shared_hcd) + usb_put_hcd(shared_hcd); + + usb_put_hcd(hcd); + xhci_mtk_sch_exit(mtk); + clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks); + regulator_bulk_disable(BULK_VREGS_NUM, mtk->supplies); + + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + pm_runtime_set_suspended(dev); +} + +static int __maybe_unused xhci_mtk_suspend(struct device *dev) +{ + struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev); + struct usb_hcd *hcd = mtk->hcd; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct usb_hcd *shared_hcd = xhci->shared_hcd; + int ret; + + xhci_dbg(xhci, "%s: stop port polling\n", __func__); + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + timer_delete_sync(&hcd->rh_timer); + if (shared_hcd) { + clear_bit(HCD_FLAG_POLL_RH, &shared_hcd->flags); + timer_delete_sync(&shared_hcd->rh_timer); + } + + ret = xhci_mtk_host_disable(mtk); + if (ret) + goto restart_poll_rh; + + clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks); + usb_wakeup_set(mtk, true); + return 0; + +restart_poll_rh: + xhci_dbg(xhci, "%s: restart port polling\n", __func__); + if (shared_hcd) { + set_bit(HCD_FLAG_POLL_RH, &shared_hcd->flags); + usb_hcd_poll_rh_status(shared_hcd); + } + set_bit(HCD_FLAG_POLL_RH, &hcd->flags); + usb_hcd_poll_rh_status(hcd); + return ret; +} + +static int __maybe_unused xhci_mtk_resume(struct device *dev) +{ + struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev); + struct usb_hcd *hcd = mtk->hcd; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct usb_hcd *shared_hcd = xhci->shared_hcd; + int ret; + + usb_wakeup_set(mtk, false); + ret = clk_bulk_prepare_enable(BULK_CLKS_NUM, mtk->clks); + if (ret) + goto enable_wakeup; + + ret = xhci_mtk_host_enable(mtk); + if (ret) + goto disable_clks; + + xhci_dbg(xhci, "%s: restart port polling\n", __func__); + if (shared_hcd) { + set_bit(HCD_FLAG_POLL_RH, &shared_hcd->flags); + usb_hcd_poll_rh_status(shared_hcd); + } + set_bit(HCD_FLAG_POLL_RH, &hcd->flags); + usb_hcd_poll_rh_status(hcd); + return 0; + +disable_clks: + clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks); +enable_wakeup: + usb_wakeup_set(mtk, true); + return ret; +} + +static int __maybe_unused xhci_mtk_runtime_suspend(struct device *dev) +{ + struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev); + struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd); + int ret = 0; + + if (xhci->xhc_state) + return -ESHUTDOWN; + + if (device_may_wakeup(dev)) + ret = xhci_mtk_suspend(dev); + + /* -EBUSY: let PM automatically reschedule another autosuspend */ + return ret ? -EBUSY : 0; +} + +static int __maybe_unused xhci_mtk_runtime_resume(struct device *dev) +{ + struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev); + struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd); + int ret = 0; + + if (xhci->xhc_state) + return -ESHUTDOWN; + + if (device_may_wakeup(dev)) + ret = xhci_mtk_resume(dev); + + return ret; +} + +static const struct dev_pm_ops xhci_mtk_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(xhci_mtk_suspend, xhci_mtk_resume) + SET_RUNTIME_PM_OPS(xhci_mtk_runtime_suspend, + xhci_mtk_runtime_resume, NULL) +}; + +#define DEV_PM_OPS (IS_ENABLED(CONFIG_PM) ? &xhci_mtk_pm_ops : NULL) + +static const struct of_device_id mtk_xhci_of_match[] = { + { .compatible = "mediatek,mt8173-xhci"}, + { .compatible = "mediatek,mt8195-xhci"}, + { .compatible = "mediatek,mtk-xhci"}, + { }, +}; +MODULE_DEVICE_TABLE(of, mtk_xhci_of_match); + +static struct platform_driver mtk_xhci_driver = { + .probe = xhci_mtk_probe, + .remove = xhci_mtk_remove, + .driver = { + .name = "xhci-mtk", + .pm = DEV_PM_OPS, + .of_match_table = mtk_xhci_of_match, + }, +}; + +static int __init xhci_mtk_init(void) +{ + xhci_init_driver(&xhci_mtk_hc_driver, &xhci_mtk_overrides); + return platform_driver_register(&mtk_xhci_driver); +} +module_init(xhci_mtk_init); + +static void __exit xhci_mtk_exit(void) +{ + platform_driver_unregister(&mtk_xhci_driver); +} +module_exit(xhci_mtk_exit); + +MODULE_AUTHOR("Chunfeng Yun "); +MODULE_DESCRIPTION("MediaTek xHCI Host Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h new file mode 100644 index 000000000..2274f5995 --- /dev/null +++ b/drivers/usb/host/xhci-mtk.h @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: + * Zhigang.Wei + * Chunfeng.Yun + */ + +#ifndef _XHCI_MTK_H_ +#define _XHCI_MTK_H_ + +#include +#include +#include + +#include "xhci.h" + +#define BULK_CLKS_NUM 6 +#define BULK_VREGS_NUM 2 + +/* support at most 64 ep, use 32 size hash table */ +#define SCH_EP_HASH_BITS 5 + +/* + * To simplify scheduler algorithm, set a upper limit for ESIT, + * if a synchromous ep's ESIT is larger than @XHCI_MTK_MAX_ESIT, + * round down to the limit value, that means allocating more + * bandwidth to it. + */ +#define XHCI_MTK_MAX_ESIT (1 << 6) +#define XHCI_MTK_BW_INDEX(x) ((x) & (XHCI_MTK_MAX_ESIT - 1)) + +#define UFRAMES_PER_FRAME 8 +#define XHCI_MTK_FRAMES_CNT (XHCI_MTK_MAX_ESIT / UFRAMES_PER_FRAME) + +/** + * struct mu3h_sch_tt - TT scheduling data + * @fs_bus_bw_out: save bandwidth used by FS/LS OUT eps in each uframes + * @fs_bus_bw_in: save bandwidth used by FS/LS IN eps in each uframes + * @ls_bus_bw: save bandwidth used by LS eps in each uframes + * @fs_frame_bw: save bandwidth used by FS/LS eps in each FS frames + * @in_ss_cnt: the count of Start-Split for IN eps + * @ep_list: Endpoints using this TT + */ +struct mu3h_sch_tt { + u16 fs_bus_bw_out[XHCI_MTK_MAX_ESIT]; + u16 fs_bus_bw_in[XHCI_MTK_MAX_ESIT]; + u8 ls_bus_bw[XHCI_MTK_MAX_ESIT]; + u16 fs_frame_bw[XHCI_MTK_FRAMES_CNT]; + u8 in_ss_cnt[XHCI_MTK_MAX_ESIT]; + struct list_head ep_list; +}; + +/** + * struct mu3h_sch_bw_info - schedule information for bandwidth domain + * + * @bus_bw: array to keep track of bandwidth already used at each uframes + * + * treat a HS root port as a bandwidth domain, but treat a SS root port as + * two bandwidth domains, one for IN eps and another for OUT eps. + */ +struct mu3h_sch_bw_info { + u32 bus_bw[XHCI_MTK_MAX_ESIT]; +}; + +/** + * struct mu3h_sch_ep_info - schedule information for endpoint + * + * @esit: unit is 125us, equal to 2 << Interval field in ep-context + * @num_esit: number of @esit in a period + * @num_budget_microframes: number of continuous uframes + * (@repeat==1) scheduled within the interval + * @hentry: hash table entry + * @endpoint: linked into bandwidth domain which it belongs to + * @tt_endpoint: linked into mu3h_sch_tt's list which it belongs to + * @bw_info: bandwidth domain which this endpoint belongs + * @sch_tt: mu3h_sch_tt linked into + * @ep_type: endpoint type + * @maxpkt: max packet size of endpoint + * @ep: address of usb_host_endpoint struct + * @speed: usb device speed + * @allocated: the bandwidth is aready allocated from bus_bw + * @offset: which uframe of the interval that transfer should be + * scheduled first time within the interval + * @repeat: the time gap between two uframes that transfers are + * scheduled within a interval. in the simple algorithm, only + * assign 0 or 1 to it; 0 means using only one uframe in a + * interval, and 1 means using @num_budget_microframes + * continuous uframes + * @pkts: number of packets to be transferred in the scheduled uframes + * @cs_count: number of CS that host will trigger + * @burst_mode: burst mode for scheduling. 0: normal burst mode, + * distribute the bMaxBurst+1 packets for a single burst + * according to @pkts and @repeat, repeate the burst multiple + * times; 1: distribute the (bMaxBurst+1)*(Mult+1) packets + * according to @pkts and @repeat. normal mode is used by + * default + * @bw_budget_table: table to record bandwidth budget per microframe + */ +struct mu3h_sch_ep_info { + u32 esit; + u32 num_esit; + u32 num_budget_microframes; + struct list_head endpoint; + struct hlist_node hentry; + struct list_head tt_endpoint; + struct mu3h_sch_bw_info *bw_info; + struct mu3h_sch_tt *sch_tt; + u32 ep_type; + u32 maxpkt; + struct usb_host_endpoint *ep; + enum usb_device_speed speed; + bool allocated; + /* + * mtk xHCI scheduling information put into reserved DWs + * in ep context + */ + u32 offset; + u32 repeat; + u32 pkts; + u32 cs_count; + u32 burst_mode; + u32 bw_budget_table[]; +}; + +#define MU3C_U3_PORT_MAX 4 +#define MU3C_U2_PORT_MAX 5 + +/** + * struct mu3c_ippc_regs - MTK ssusb ip port control registers + * @ip_pw_ctr0~3: ip power and clock control registers + * @ip_pw_sts1~2: ip power and clock status registers + * @ip_xhci_cap: ip xHCI capability register + * @u3_ctrl_p[x]: ip usb3 port x control register, only low 4bytes are used + * @u2_ctrl_p[x]: ip usb2 port x control register, only low 4bytes are used + * @u2_phy_pll: usb2 phy pll control register + */ +struct mu3c_ippc_regs { + __le32 ip_pw_ctr0; + __le32 ip_pw_ctr1; + __le32 ip_pw_ctr2; + __le32 ip_pw_ctr3; + __le32 ip_pw_sts1; + __le32 ip_pw_sts2; + __le32 reserved0[3]; + __le32 ip_xhci_cap; + __le32 reserved1[2]; + __le64 u3_ctrl_p[MU3C_U3_PORT_MAX]; + __le64 u2_ctrl_p[MU3C_U2_PORT_MAX]; + __le32 reserved2; + __le32 u2_phy_pll; + __le32 reserved3[33]; /* 0x80 ~ 0xff */ +}; + +struct xhci_hcd_mtk { + struct device *dev; + struct usb_hcd *hcd; + struct mu3h_sch_bw_info *sch_array; + struct list_head bw_ep_chk_list; + DECLARE_HASHTABLE(sch_ep_hash, SCH_EP_HASH_BITS); + struct mu3c_ippc_regs __iomem *ippc_regs; + int num_u2_ports; + int num_u3_ports; + int u2p_dis_msk; + int u3p_dis_msk; + struct clk_bulk_data clks[BULK_CLKS_NUM]; + struct regulator_bulk_data supplies[BULK_VREGS_NUM]; + unsigned int has_ippc:1; + unsigned int lpm_support:1; + unsigned int u2_lpm_disable:1; + /* usb remote wakeup */ + unsigned int uwk_en:1; + struct regmap *uwk; + u32 uwk_reg_base; + u32 uwk_vers; + /* quirk */ + u32 rxfifo_depth; +}; + +static inline struct xhci_hcd_mtk *hcd_to_mtk(struct usb_hcd *hcd) +{ + return dev_get_drvdata(hcd->self.controller); +} + +int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk); +void xhci_mtk_sch_exit(struct xhci_hcd_mtk *mtk); +int xhci_mtk_add_ep(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep); +int xhci_mtk_drop_ep(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep); +int xhci_mtk_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); +void xhci_mtk_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); + +#endif /* _XHCI_MTK_H_ */ diff --git a/drivers/usb/host/xhci-mvebu.c b/drivers/usb/host/xhci-mvebu.c new file mode 100644 index 000000000..f91c5004f --- /dev/null +++ b/drivers/usb/host/xhci-mvebu.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2014 Marvell + * Author: Gregory CLEMENT + */ + +#include +#include +#include +#include + +#include +#include + +#include "xhci-mvebu.h" +#include "xhci.h" + +#define USB3_MAX_WINDOWS 4 +#define USB3_WIN_CTRL(w) (0x0 + ((w) * 8)) +#define USB3_WIN_BASE(w) (0x4 + ((w) * 8)) + +static void xhci_mvebu_mbus_config(void __iomem *base, + const struct mbus_dram_target_info *dram) +{ + int win; + + /* Clear all existing windows */ + for (win = 0; win < USB3_MAX_WINDOWS; win++) { + writel(0, base + USB3_WIN_CTRL(win)); + writel(0, base + USB3_WIN_BASE(win)); + } + + /* Program each DRAM CS in a separate window */ + for (win = 0; win < dram->num_cs; win++) { + const struct mbus_dram_window *cs = &dram->cs[win]; + + writel(((cs->size - 1) & 0xffff0000) | (cs->mbus_attr << 8) | + (dram->mbus_dram_target_id << 4) | 1, + base + USB3_WIN_CTRL(win)); + + writel((cs->base & 0xffff0000), base + USB3_WIN_BASE(win)); + } +} + +int xhci_mvebu_mbus_init_quirk(struct usb_hcd *hcd) +{ + struct device *dev = hcd->self.controller; + struct platform_device *pdev = to_platform_device(dev); + struct resource *res; + void __iomem *base; + const struct mbus_dram_target_info *dram; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) + return -ENODEV; + + /* + * We don't use devm_ioremap() because this mapping should + * only exists for the duration of this probe function. + */ + base = ioremap(res->start, resource_size(res)); + if (!base) + return -ENODEV; + + dram = mv_mbus_dram_info(); + xhci_mvebu_mbus_config(base, dram); + + /* + * This memory area was only needed to configure the MBus + * windows, and is therefore no longer useful. + */ + iounmap(base); + + return 0; +} diff --git a/drivers/usb/host/xhci-mvebu.h b/drivers/usb/host/xhci-mvebu.h new file mode 100644 index 000000000..9d26e22c4 --- /dev/null +++ b/drivers/usb/host/xhci-mvebu.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2014 Marvell + * + * Gregory Clement + */ + +#ifndef __LINUX_XHCI_MVEBU_H +#define __LINUX_XHCI_MVEBU_H + +struct usb_hcd; + +#if IS_ENABLED(CONFIG_USB_XHCI_MVEBU) +int xhci_mvebu_mbus_init_quirk(struct usb_hcd *hcd); +#else +static inline int xhci_mvebu_mbus_init_quirk(struct usb_hcd *hcd) +{ + return 0; +} +#endif +#endif /* __LINUX_XHCI_MVEBU_H */ diff --git a/drivers/usb/host/xhci-pci-prom21.c b/drivers/usb/host/xhci-pci-prom21.c new file mode 100644 index 000000000..6486f4a09 --- /dev/null +++ b/drivers/usb/host/xhci-pci-prom21.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD Promontory 21 xHCI host controller PCI Bus Glue. + * + * This does not add any PROM21-specific USB or xHCI operation. It exists only + * to publish an auxiliary device for integrated temperature sensor support. + * + * Copyright (C) 2026 Jihong Min + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xhci-pci.h" + +struct prom21_xhci_auxdev { + struct auxiliary_device *auxdev; + struct prom21_xhci_pdata pdata; + int id; +}; + +static DEFINE_IDA(prom21_xhci_auxdev_ida); + +static void prom21_xhci_auxdev_release(struct device *dev, void *res) +{ + struct prom21_xhci_auxdev *prom21_auxdev = res; + + auxiliary_device_destroy(prom21_auxdev->auxdev); + ida_free(&prom21_xhci_auxdev_ida, prom21_auxdev->id); +} + +static int prom21_xhci_create_auxdev(struct pci_dev *pdev) +{ + struct prom21_xhci_auxdev *prom21_auxdev; + struct usb_hcd *hcd = pci_get_drvdata(pdev); + int ret; + + prom21_auxdev = devres_alloc(prom21_xhci_auxdev_release, + sizeof(*prom21_auxdev), GFP_KERNEL); + if (!prom21_auxdev) + return -ENOMEM; + + prom21_auxdev->pdata.pdev = pdev; + prom21_auxdev->pdata.regs = hcd->regs; + prom21_auxdev->pdata.rsrc_len = hcd->rsrc_len; + + prom21_auxdev->id = ida_alloc(&prom21_xhci_auxdev_ida, GFP_KERNEL); + if (prom21_auxdev->id < 0) { + ret = prom21_auxdev->id; + goto err_free_devres; + } + + prom21_auxdev->auxdev = auxiliary_device_create(&pdev->dev, + KBUILD_MODNAME, "hwmon", + &prom21_auxdev->pdata, + prom21_auxdev->id); + if (!prom21_auxdev->auxdev) { + ret = -ENOMEM; + goto err_free_ida; + } + + devres_add(&pdev->dev, prom21_auxdev); + return 0; + +err_free_ida: + ida_free(&prom21_xhci_auxdev_ida, prom21_auxdev->id); +err_free_devres: + devres_free(prom21_auxdev); + return ret; +} + +static void prom21_xhci_destroy_auxdev(struct pci_dev *pdev) +{ + devres_release(&pdev->dev, prom21_xhci_auxdev_release, NULL, NULL); +} + +static int prom21_xhci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + int retval; + + retval = xhci_pci_common_probe(dev, id); + if (retval) + return retval; + + retval = prom21_xhci_create_auxdev(dev); + if (retval) { + /* + * The auxiliary device only provides optional temperature sensor + * support. Keep the xHCI controller usable if it fails. + */ + dev_err(&dev->dev, + "failed to create PROM21 hwmon auxiliary device: %d\n", + retval); + } + + return 0; +} + +static void prom21_xhci_remove(struct pci_dev *dev) +{ + prom21_xhci_destroy_auxdev(dev); + xhci_pci_remove(dev); +} + +static const struct pci_device_id pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD) }, + { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, pci_ids); + +static struct pci_driver prom21_xhci_driver = { + .name = "xhci-pci-prom21", + .id_table = pci_ids, + + .probe = prom21_xhci_probe, + .remove = prom21_xhci_remove, + + .shutdown = usb_hcd_pci_shutdown, + .driver = { + .pm = pm_ptr(&usb_hcd_pci_pm_ops), + }, +}; +module_pci_driver(prom21_xhci_driver); + +MODULE_AUTHOR("Jihong Min "); +MODULE_DESCRIPTION("AMD Promontory 21 xHCI PCI Host Controller Driver"); +MODULE_IMPORT_NS("xhci"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/xhci-pci-renesas.c b/drivers/usb/host/xhci-pci-renesas.c new file mode 100644 index 000000000..86df80399 --- /dev/null +++ b/drivers/usb/host/xhci-pci-renesas.c @@ -0,0 +1,666 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2019-2020 Linaro Limited */ + +#include +#include +#include +#include +#include +#include + +#include "xhci.h" +#include "xhci-trace.h" +#include "xhci-pci.h" + +#define RENESAS_FW_VERSION 0x6C +#define RENESAS_ROM_CONFIG 0xF0 +#define RENESAS_FW_STATUS 0xF4 +#define RENESAS_FW_STATUS_MSB 0xF5 +#define RENESAS_ROM_STATUS 0xF6 +#define RENESAS_ROM_STATUS_MSB 0xF7 +#define RENESAS_DATA0 0xF8 +#define RENESAS_DATA1 0xFC + +#define RENESAS_FW_VERSION_FIELD GENMASK(23, 7) +#define RENESAS_FW_VERSION_OFFSET 8 + +#define RENESAS_FW_STATUS_DOWNLOAD_ENABLE BIT(0) +#define RENESAS_FW_STATUS_LOCK BIT(1) +#define RENESAS_FW_STATUS_RESULT GENMASK(6, 4) + #define RENESAS_FW_STATUS_INVALID 0 + #define RENESAS_FW_STATUS_SUCCESS BIT(4) + #define RENESAS_FW_STATUS_ERROR BIT(5) +#define RENESAS_FW_STATUS_SET_DATA0 BIT(8) +#define RENESAS_FW_STATUS_SET_DATA1 BIT(9) + +#define RENESAS_ROM_STATUS_ACCESS BIT(0) +#define RENESAS_ROM_STATUS_ERASE BIT(1) +#define RENESAS_ROM_STATUS_RELOAD BIT(2) +#define RENESAS_ROM_STATUS_RESULT GENMASK(6, 4) + #define RENESAS_ROM_STATUS_NO_RESULT 0 + #define RENESAS_ROM_STATUS_SUCCESS BIT(4) + #define RENESAS_ROM_STATUS_ERROR BIT(5) +#define RENESAS_ROM_STATUS_SET_DATA0 BIT(8) +#define RENESAS_ROM_STATUS_SET_DATA1 BIT(9) +#define RENESAS_ROM_STATUS_ROM_EXISTS BIT(15) + +#define RENESAS_ROM_ERASE_MAGIC 0x5A65726F +#define RENESAS_ROM_WRITE_MAGIC 0x53524F4D + +#define RENESAS_RETRY 50000 /* 50000 * RENESAS_DELAY ~= 500ms */ +#define RENESAS_CHIP_ERASE_RETRY 500000 /* 500000 * RENESAS_DELAY ~= 5s */ +#define RENESAS_DELAY 10 + +#define RENESAS_FW_NAME "renesas_usb_fw.mem" + +static int renesas_fw_download_image(struct pci_dev *dev, + const u32 *fw, size_t step, bool rom) +{ + size_t i; + int err; + u8 fw_status; + bool data0_or_data1; + u32 status_reg; + + if (rom) + status_reg = RENESAS_ROM_STATUS_MSB; + else + status_reg = RENESAS_FW_STATUS_MSB; + + /* + * The hardware does alternate between two 32-bit pages. + * (This is because each row of the firmware is 8 bytes). + * + * for even steps we use DATA0, for odd steps DATA1. + */ + data0_or_data1 = (step & 1) == 1; + + /* step+1. Read "Set DATAX" and confirm it is cleared. */ + for (i = 0; i < RENESAS_RETRY; i++) { + err = pci_read_config_byte(dev, status_reg, &fw_status); + if (err) { + dev_err(&dev->dev, "Read Status failed: %d\n", + pcibios_err_to_errno(err)); + return pcibios_err_to_errno(err); + } + if (!(fw_status & BIT(data0_or_data1))) + break; + + udelay(RENESAS_DELAY); + } + if (i == RENESAS_RETRY) { + dev_err(&dev->dev, "Timeout for Set DATAX step: %zd\n", step); + return -ETIMEDOUT; + } + + /* + * step+2. Write FW data to "DATAX". + * "LSB is left" => force little endian + */ + err = pci_write_config_dword(dev, data0_or_data1 ? + RENESAS_DATA1 : RENESAS_DATA0, + (__force u32)cpu_to_le32(fw[step])); + if (err) { + dev_err(&dev->dev, "Write to DATAX failed: %d\n", + pcibios_err_to_errno(err)); + return pcibios_err_to_errno(err); + } + + udelay(100); + + /* step+3. Set "Set DATAX". */ + err = pci_write_config_byte(dev, status_reg, BIT(data0_or_data1)); + if (err) { + dev_err(&dev->dev, "Write config for DATAX failed: %d\n", + pcibios_err_to_errno(err)); + return pcibios_err_to_errno(err); + } + + return 0; +} + +static int renesas_fw_verify(const void *fw_data, + size_t length) +{ + u16 fw_version_pointer; + + /* + * The Firmware's Data Format is describe in + * "6.3 Data Format" R19UH0078EJ0500 Rev.5.00 page 124 + */ + + /* + * The bootrom chips of the big brother have sizes up to 64k, let's + * assume that's the biggest the firmware can get. + */ + if (length < 0x1000 || length >= 0x10000) { + pr_err("firmware is size %zd is not (4k - 64k).", + length); + return -EINVAL; + } + + /* The First 2 bytes are fixed value (55aa). "LSB on Left" */ + if (get_unaligned_le16(fw_data) != 0x55aa) { + pr_err("no valid firmware header found."); + return -EINVAL; + } + + /* verify the firmware version position and print it. */ + fw_version_pointer = get_unaligned_le16(fw_data + 4); + if (fw_version_pointer + 2 >= length) { + pr_err("fw ver pointer is outside of the firmware image"); + return -EINVAL; + } + + return 0; +} + +static bool renesas_check_rom(struct pci_dev *pdev) +{ + u16 rom_status; + int retval; + + /* Check if external ROM exists */ + retval = pci_read_config_word(pdev, RENESAS_ROM_STATUS, &rom_status); + if (retval) + return false; + + rom_status &= RENESAS_ROM_STATUS_ROM_EXISTS; + if (rom_status) { + dev_dbg(&pdev->dev, "External ROM exists\n"); + return true; /* External ROM exists */ + } + + return false; +} + +static int renesas_check_rom_state(struct pci_dev *pdev) +{ + u16 rom_state; + u32 version; + int err; + + /* check FW version */ + err = pci_read_config_dword(pdev, RENESAS_FW_VERSION, &version); + if (err) + return pcibios_err_to_errno(err); + + version &= RENESAS_FW_VERSION_FIELD; + version = version >> RENESAS_FW_VERSION_OFFSET; + dev_dbg(&pdev->dev, "Found ROM version: %x\n", version); + + /* + * Test if ROM is present and loaded, if so we can skip everything + */ + err = pci_read_config_word(pdev, RENESAS_ROM_STATUS, &rom_state); + if (err) + return pcibios_err_to_errno(err); + + if (rom_state & RENESAS_ROM_STATUS_ROM_EXISTS) { + /* ROM exists */ + dev_dbg(&pdev->dev, "ROM exists\n"); + + /* Check the "Result Code" Bits (6:4) and act accordingly */ + switch (rom_state & RENESAS_ROM_STATUS_RESULT) { + case RENESAS_ROM_STATUS_SUCCESS: + return 0; + + case RENESAS_ROM_STATUS_NO_RESULT: /* No result yet */ + dev_dbg(&pdev->dev, "Unknown ROM status ...\n"); + return -ENOENT; + + case RENESAS_ROM_STATUS_ERROR: /* Error State */ + default: /* All other states are marked as "Reserved states" */ + dev_err(&pdev->dev, "Invalid ROM.."); + break; + } + } + + return -EIO; +} + +static int renesas_fw_check_running(struct pci_dev *pdev) +{ + u8 fw_state; + int err; + + /* + * Test if the device is actually needing the firmware. As most + * BIOSes will initialize the device for us. If the device is + * initialized. + */ + err = pci_read_config_byte(pdev, RENESAS_FW_STATUS, &fw_state); + if (err) + return pcibios_err_to_errno(err); + + /* + * Check if "FW Download Lock" is locked. If it is and the FW is + * ready we can simply continue. If the FW is not ready, we have + * to give up. + */ + if (fw_state & RENESAS_FW_STATUS_LOCK) { + dev_dbg(&pdev->dev, "FW Download Lock is engaged."); + + if (fw_state & RENESAS_FW_STATUS_SUCCESS) + return 0; + + dev_err(&pdev->dev, + "FW Download Lock is set and FW is not ready. Giving Up."); + return -EIO; + } + + /* + * Check if "FW Download Enable" is set. If someone (us?) tampered + * with it and it can't be reset, we have to give up too... and + * ask for a forgiveness and a reboot. + */ + if (fw_state & RENESAS_FW_STATUS_DOWNLOAD_ENABLE) { + dev_err(&pdev->dev, + "FW Download Enable is stale. Giving Up (poweroff/reboot needed)."); + return -EIO; + } + + /* Otherwise, Check the "Result Code" Bits (6:4) and act accordingly */ + switch (fw_state & RENESAS_FW_STATUS_RESULT) { + case 0: /* No result yet */ + dev_dbg(&pdev->dev, "FW is not ready/loaded yet."); + + /* tell the caller, that this device needs the firmware. */ + return 1; + + case RENESAS_FW_STATUS_SUCCESS: /* Success, device should be working. */ + dev_dbg(&pdev->dev, "FW is ready."); + return 0; + + case RENESAS_FW_STATUS_ERROR: /* Error State */ + dev_err(&pdev->dev, + "hardware is in an error state. Giving up (poweroff/reboot needed)."); + return -ENODEV; + + default: /* All other states are marked as "Reserved states" */ + dev_err(&pdev->dev, + "hardware is in an invalid state %lx. Giving up (poweroff/reboot needed).", + (fw_state & RENESAS_FW_STATUS_RESULT) >> 4); + return -EINVAL; + } +} + +static int renesas_fw_download(struct pci_dev *pdev, + const struct firmware *fw) +{ + const u32 *fw_data = (const u32 *)fw->data; + size_t i; + int err; + u8 fw_status; + + /* + * For more information and the big picture: please look at the + * "Firmware Download Sequence" in "7.1 FW Download Interface" + * of R19UH0078EJ0500 Rev.5.00 page 131 + */ + + /* + * 0. Set "FW Download Enable" bit in the + * "FW Download Control & Status Register" at 0xF4 + */ + err = pci_write_config_byte(pdev, RENESAS_FW_STATUS, + RENESAS_FW_STATUS_DOWNLOAD_ENABLE); + if (err) + return pcibios_err_to_errno(err); + + /* 1 - 10 follow one step after the other. */ + for (i = 0; i < fw->size / 4; i++) { + err = renesas_fw_download_image(pdev, fw_data, i, false); + if (err) { + dev_err(&pdev->dev, + "Firmware Download Step %zd failed at position %zd bytes with (%d).", + i, i * 4, err); + return err; + } + } + + /* + * This sequence continues until the last data is written to + * "DATA0" or "DATA1". Naturally, we wait until "SET DATA0/1" + * is cleared by the hardware beforehand. + */ + for (i = 0; i < RENESAS_RETRY; i++) { + err = pci_read_config_byte(pdev, RENESAS_FW_STATUS_MSB, + &fw_status); + if (err) + return pcibios_err_to_errno(err); + if (!(fw_status & (BIT(0) | BIT(1)))) + break; + + udelay(RENESAS_DELAY); + } + if (i == RENESAS_RETRY) + dev_warn(&pdev->dev, "Final Firmware Download step timed out."); + + /* + * 11. After finishing writing the last data of FW, the + * System Software must clear "FW Download Enable" + */ + err = pci_write_config_byte(pdev, RENESAS_FW_STATUS, 0); + if (err) + return pcibios_err_to_errno(err); + + /* 12. Read "Result Code" and confirm it is good. */ + for (i = 0; i < RENESAS_RETRY; i++) { + err = pci_read_config_byte(pdev, RENESAS_FW_STATUS, &fw_status); + if (err) + return pcibios_err_to_errno(err); + if (fw_status & RENESAS_FW_STATUS_SUCCESS) + break; + + udelay(RENESAS_DELAY); + } + if (i == RENESAS_RETRY) { + /* Timed out / Error - let's see if we can fix this */ + err = renesas_fw_check_running(pdev); + switch (err) { + case 0: /* + * we shouldn't end up here. + * maybe it took a little bit longer. + * But all should be well? + */ + break; + + case 1: /* (No result yet! */ + dev_err(&pdev->dev, "FW Load timedout"); + return -ETIMEDOUT; + + default: + return err; + } + } + + return 0; +} + +static void renesas_rom_erase(struct pci_dev *pdev) +{ + int retval, i; + u8 status; + + dev_dbg(&pdev->dev, "Performing ROM Erase...\n"); + retval = pci_write_config_dword(pdev, RENESAS_DATA0, + RENESAS_ROM_ERASE_MAGIC); + if (retval) { + dev_err(&pdev->dev, "ROM erase, magic word write failed: %d\n", + pcibios_err_to_errno(retval)); + return; + } + + retval = pci_read_config_byte(pdev, RENESAS_ROM_STATUS, &status); + if (retval) { + dev_err(&pdev->dev, "ROM status read failed: %d\n", + pcibios_err_to_errno(retval)); + return; + } + status |= RENESAS_ROM_STATUS_ERASE; + retval = pci_write_config_byte(pdev, RENESAS_ROM_STATUS, status); + if (retval) { + dev_err(&pdev->dev, "ROM erase set word write failed\n"); + return; + } + + /* sleep a bit while ROM is erased */ + msleep(20); + + for (i = 0; i < RENESAS_CHIP_ERASE_RETRY; i++) { + retval = pci_read_config_byte(pdev, RENESAS_ROM_STATUS, + &status); + status &= RENESAS_ROM_STATUS_ERASE; + if (!status) + break; + + mdelay(RENESAS_DELAY); + } + + if (i == RENESAS_RETRY) + dev_dbg(&pdev->dev, "Chip erase timedout: %x\n", status); + + dev_dbg(&pdev->dev, "ROM Erase... Done success\n"); +} + +static bool renesas_setup_rom(struct pci_dev *pdev, const struct firmware *fw) +{ + const u32 *fw_data = (const u32 *)fw->data; + int err, i; + u8 status; + + /* 2. Write magic word to Data0 */ + err = pci_write_config_dword(pdev, RENESAS_DATA0, + RENESAS_ROM_WRITE_MAGIC); + if (err) + return false; + + /* 3. Set External ROM access */ + err = pci_write_config_byte(pdev, RENESAS_ROM_STATUS, + RENESAS_ROM_STATUS_ACCESS); + if (err) + goto remove_bypass; + + /* 4. Check the result */ + err = pci_read_config_byte(pdev, RENESAS_ROM_STATUS, &status); + if (err) + goto remove_bypass; + status &= GENMASK(6, 4); + if (status) { + dev_err(&pdev->dev, + "setting external rom failed: %x\n", status); + goto remove_bypass; + } + + /* 5 to 16 Write FW to DATA0/1 while checking SetData0/1 */ + for (i = 0; i < fw->size / 4; i++) { + err = renesas_fw_download_image(pdev, fw_data, i, true); + if (err) { + dev_err(&pdev->dev, + "ROM Download Step %d failed at position %d bytes with (%d)\n", + i, i * 4, err); + goto remove_bypass; + } + } + + /* + * wait till DATA0/1 is cleared + */ + for (i = 0; i < RENESAS_RETRY; i++) { + err = pci_read_config_byte(pdev, RENESAS_ROM_STATUS_MSB, + &status); + if (err) + goto remove_bypass; + if (!(status & (BIT(0) | BIT(1)))) + break; + + udelay(RENESAS_DELAY); + } + if (i == RENESAS_RETRY) { + dev_err(&pdev->dev, "Final Firmware ROM Download step timed out\n"); + goto remove_bypass; + } + + /* 17. Remove bypass */ + err = pci_write_config_byte(pdev, RENESAS_ROM_STATUS, 0); + if (err) + return false; + + udelay(10); + + /* 18. check result */ + for (i = 0; i < RENESAS_RETRY; i++) { + err = pci_read_config_byte(pdev, RENESAS_ROM_STATUS, &status); + if (err) { + dev_err(&pdev->dev, "Read ROM status failed:%d\n", + pcibios_err_to_errno(err)); + return false; + } + status &= RENESAS_ROM_STATUS_RESULT; + if (status == RENESAS_ROM_STATUS_SUCCESS) { + dev_dbg(&pdev->dev, "Download ROM success\n"); + break; + } + udelay(RENESAS_DELAY); + } + if (i == RENESAS_RETRY) { /* Timed out */ + dev_err(&pdev->dev, + "Download to external ROM TO: %x\n", status); + return false; + } + + dev_dbg(&pdev->dev, "Download to external ROM succeeded\n"); + + /* Last step set Reload */ + err = pci_write_config_byte(pdev, RENESAS_ROM_STATUS, + RENESAS_ROM_STATUS_RELOAD); + if (err) { + dev_err(&pdev->dev, "Set ROM execute failed: %d\n", + pcibios_err_to_errno(err)); + return false; + } + + /* + * wait till Reload is cleared + */ + for (i = 0; i < RENESAS_RETRY; i++) { + err = pci_read_config_byte(pdev, RENESAS_ROM_STATUS, &status); + if (err) + return false; + if (!(status & RENESAS_ROM_STATUS_RELOAD)) + break; + + udelay(RENESAS_DELAY); + } + if (i == RENESAS_RETRY) { + dev_err(&pdev->dev, "ROM Exec timed out: %x\n", status); + return false; + } + + return true; + +remove_bypass: + pci_write_config_byte(pdev, RENESAS_ROM_STATUS, 0); + return false; +} + +static int renesas_load_fw(struct pci_dev *pdev, const struct firmware *fw) +{ + int err = 0; + bool rom; + + /* Check if the device has external ROM */ + rom = renesas_check_rom(pdev); + if (rom) { + /* perform chip erase first */ + renesas_rom_erase(pdev); + + /* lets try loading fw on ROM first */ + rom = renesas_setup_rom(pdev, fw); + if (!rom) { + dev_dbg(&pdev->dev, + "ROM load failed, falling back on FW load\n"); + } else { + dev_dbg(&pdev->dev, + "ROM load success\n"); + goto exit; + } + } + + err = renesas_fw_download(pdev, fw); + +exit: + if (err) + dev_err(&pdev->dev, "firmware failed to download (%d).", err); + return err; +} + +static int renesas_xhci_check_request_fw(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + const char fw_name[] = RENESAS_FW_NAME; + const struct firmware *fw; + bool has_rom; + int err; + + /* Check if device has ROM and loaded, if so skip everything */ + has_rom = renesas_check_rom(pdev); + if (has_rom) { + err = renesas_check_rom_state(pdev); + if (!err) + return 0; + else if (err != -ENOENT) + has_rom = false; + } + + err = renesas_fw_check_running(pdev); + /* Continue ahead, if the firmware is already running. */ + if (!err) + return 0; + + /* no firmware interface available */ + if (err != 1) + return has_rom ? 0 : err; + + pci_dev_get(pdev); + err = firmware_request_nowarn(&fw, fw_name, &pdev->dev); + pci_dev_put(pdev); + if (err) { + if (has_rom) { + dev_info(&pdev->dev, "failed to load firmware %s, fallback to ROM\n", + fw_name); + return 0; + } + dev_err(&pdev->dev, "failed to load firmware %s: %d\n", + fw_name, err); + return err; + } + + err = renesas_fw_verify(fw->data, fw->size); + if (err) + goto exit; + + err = renesas_load_fw(pdev, fw); +exit: + release_firmware(fw); + return err; +} + +static int +xhci_pci_renesas_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + int retval; + + retval = renesas_xhci_check_request_fw(dev, id); + if (retval) + return retval; + + return xhci_pci_common_probe(dev, id); +} + +static const struct pci_device_id pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_RENESAS, 0x0014) }, + { PCI_DEVICE(PCI_VENDOR_ID_RENESAS, 0x0015) }, + { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, pci_ids); + +static struct pci_driver xhci_renesas_pci_driver = { + .name = "xhci-pci-renesas", + .id_table = pci_ids, + + .probe = xhci_pci_renesas_probe, + .remove = xhci_pci_remove, + + .shutdown = usb_hcd_pci_shutdown, + .driver = { + .pm = pm_ptr(&usb_hcd_pci_pm_ops), + }, +}; +module_pci_driver(xhci_renesas_pci_driver); + +MODULE_DESCRIPTION("Renesas xHCI PCI Host Controller Driver"); +MODULE_FIRMWARE(RENESAS_FW_NAME); +MODULE_IMPORT_NS("xhci"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c new file mode 100644 index 000000000..a8889081a --- /dev/null +++ b/drivers/usb/host/xhci-pci.c @@ -0,0 +1,1000 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xHCI host controller driver PCI Bus Glue. + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "xhci.h" +#include "xhci-trace.h" +#include "xhci-pci.h" + +#define SSIC_PORT_NUM 2 +#define SSIC_PORT_CFG2 0x880c +#define SSIC_PORT_CFG2_OFFSET 0x30 +#define PROG_DONE (1 << 30) +#define SSIC_PORT_UNUSED (1 << 31) +#define SPARSE_DISABLE_BIT 17 +#define SPARSE_CNTL_ENABLE 0xC12C + +/* 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_FL1009 0x1009 +#define PCI_DEVICE_ID_FRESCO_LOGIC_FL1100 0x1100 +#define PCI_DEVICE_ID_FRESCO_LOGIC_FL1400 0x1400 + +#define PCI_VENDOR_ID_ETRON 0x1b6f +#define PCI_DEVICE_ID_ETRON_EJ168 0x7023 +#define PCI_DEVICE_ID_ETRON_EJ188 0x7052 + +#define PCI_DEVICE_ID_VIA_VL805 0x3483 + +#define PCI_DEVICE_ID_INTEL_LYNXPOINT_XHCI 0x8c31 +#define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI 0x9c31 +#define PCI_DEVICE_ID_INTEL_WILDCATPOINT_LP_XHCI 0x9cb1 +#define PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI 0x22b5 +#define PCI_DEVICE_ID_INTEL_SUNRISEPOINT_H_XHCI 0xa12f +#define PCI_DEVICE_ID_INTEL_SUNRISEPOINT_LP_XHCI 0x9d2f +#define PCI_DEVICE_ID_INTEL_BROXTON_M_XHCI 0x0aa8 +#define PCI_DEVICE_ID_INTEL_BROXTON_B_XHCI 0x1aa8 +#define PCI_DEVICE_ID_INTEL_APOLLO_LAKE_XHCI 0x5aa8 +#define PCI_DEVICE_ID_INTEL_DENVERTON_XHCI 0x19d0 +#define PCI_DEVICE_ID_INTEL_ICE_LAKE_XHCI 0x8a13 +#define PCI_DEVICE_ID_INTEL_TIGER_LAKE_XHCI 0x9a13 +#define PCI_DEVICE_ID_INTEL_TIGER_LAKE_PCH_XHCI 0xa0ed +#define PCI_DEVICE_ID_INTEL_COMET_LAKE_XHCI 0xa3af +#define PCI_DEVICE_ID_INTEL_ALDER_LAKE_PCH_XHCI 0x51ed +#define PCI_DEVICE_ID_INTEL_ALDER_LAKE_N_PCH_XHCI 0x54ed + +#define PCI_VENDOR_ID_PHYTIUM 0x1db7 +#define PCI_DEVICE_ID_PHYTIUM_XHCI 0xdc27 + +/* Thunderbolt */ +#define PCI_DEVICE_ID_INTEL_MAPLE_RIDGE_XHCI 0x1138 +#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_XHCI 0x15b5 +#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_XHCI 0x15b6 +#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_XHCI 0x15c1 +#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_XHCI 0x15db +#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_XHCI 0x15d4 +#define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_XHCI 0x15e9 +#define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_XHCI 0x15ec +#define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_XHCI 0x15f0 + +#define PCI_DEVICE_ID_AMD_ARIEL_TYPEC_XHCI 0x13ed +#define PCI_DEVICE_ID_AMD_ARIEL_TYPEA_XHCI 0x13ee +#define PCI_DEVICE_ID_AMD_STARSHIP_XHCI 0x148c +#define PCI_DEVICE_ID_AMD_FIREFLIGHT_15D4_XHCI 0x15d4 +#define PCI_DEVICE_ID_AMD_FIREFLIGHT_15D5_XHCI 0x15d5 +#define PCI_DEVICE_ID_AMD_RAVEN_15E0_XHCI 0x15e0 +#define PCI_DEVICE_ID_AMD_RAVEN_15E1_XHCI 0x15e1 +#define PCI_DEVICE_ID_AMD_RAVEN2_XHCI 0x15e5 +#define PCI_DEVICE_ID_AMD_RENOIR_XHCI 0x1639 +#define PCI_DEVICE_ID_AMD_PROMONTORYA_4 0x43b9 +#define PCI_DEVICE_ID_AMD_PROMONTORYA_3 0x43ba +#define PCI_DEVICE_ID_AMD_PROMONTORYA_2 0x43bb +#define PCI_DEVICE_ID_AMD_PROMONTORYA_1 0x43bc + +#define PCI_DEVICE_ID_ATI_NAVI10_7316_XHCI 0x7316 + +#define PCI_DEVICE_ID_ASMEDIA_1042_XHCI 0x1042 +#define PCI_DEVICE_ID_ASMEDIA_1042A_XHCI 0x1142 +#define PCI_DEVICE_ID_ASMEDIA_1142_XHCI 0x1242 +#define PCI_DEVICE_ID_ASMEDIA_2142_XHCI 0x2142 +#define PCI_DEVICE_ID_ASMEDIA_3042_XHCI 0x3042 +#define PCI_DEVICE_ID_ASMEDIA_3242_XHCI 0x3242 + +static const char hcd_name[] = "xhci_hcd"; + +static struct hc_driver __read_mostly xhci_pci_hc_driver; + +static int xhci_pci_setup(struct usb_hcd *hcd); +static int xhci_pci_run(struct usb_hcd *hcd); +static int xhci_pci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, + struct usb_tt *tt, gfp_t mem_flags); + +static const struct xhci_driver_overrides xhci_pci_overrides __initconst = { + .reset = xhci_pci_setup, + .start = xhci_pci_run, + .update_hub_device = xhci_pci_update_hub_device, +}; + +/* + * Primary Legacy and MSI IRQ are synced in suspend_common(). + * All MSI-X IRQs and secondary MSI IRQs should be synced here. + */ +static void xhci_msix_sync_irqs(struct xhci_hcd *xhci) +{ + struct usb_hcd *hcd = xhci_to_hcd(xhci); + + if (hcd->msix_enabled) { + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + + /* for now, the driver only supports one primary interrupter */ + synchronize_irq(pci_irq_vector(pdev, 0)); + } +} + +/* Legacy IRQ is freed by usb_remove_hcd() or usb_hcd_pci_shutdown() */ +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 (hcd->irq > 0) + return; + + free_irq(pci_irq_vector(pdev, 0), xhci_to_hcd(xhci)); + pci_free_irq_vectors(pdev); + hcd->msix_enabled = 0; +} + +/* Try enabling MSI-X with MSI and legacy IRQ as fallback */ +static int xhci_try_enable_msi(struct usb_hcd *hcd) +{ + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int ret; + + /* + * 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; + + /* + * Calculate number of MSI/MSI-X vectors supported. + * - max_interrupters: the max number of interrupts requested, capped to xhci HCSPARAMS1. + * - num_online_cpus: one vector per CPUs core, with at least one overall. + */ + xhci->nvecs = min(num_online_cpus() + 1, xhci->max_interrupters); + + /* TODO: Check with MSI Soc for sysdev */ + xhci->nvecs = pci_alloc_irq_vectors(pdev, 1, xhci->nvecs, + PCI_IRQ_MSIX | PCI_IRQ_MSI); + if (xhci->nvecs < 0) { + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "failed to allocate IRQ vectors"); + goto legacy_irq; + } + + ret = request_irq(pci_irq_vector(pdev, 0), xhci_msi_irq, 0, "xhci_hcd", + xhci_to_hcd(xhci)); + if (ret) + goto free_irq_vectors; + + hcd->msi_enabled = 1; + hcd->msix_enabled = pdev->msix_enabled; + return 0; + +free_irq_vectors: + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "disable %s interrupt", + pdev->msix_enabled ? "MSI-X" : "MSI"); + pci_free_irq_vectors(pdev); + +legacy_irq: + if (!pdev->irq) { + xhci_err(xhci, "No msi-x/msi found and no IRQ in BIOS\n"); + return -EINVAL; + } + + if (!strlen(hcd->irq_descr)) + snprintf(hcd->irq_descr, sizeof(hcd->irq_descr), "%s:usb%d", + hcd->driver->description, hcd->self.busnum); + + /* 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; +} + +static int xhci_pci_run(struct usb_hcd *hcd) +{ + int ret; + + if (usb_hcd_is_primary_hcd(hcd)) { + ret = xhci_try_enable_msi(hcd); + if (ret) + return ret; + } + + return xhci_run(hcd); +} + +static void xhci_pci_stop(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + xhci_stop(hcd); + + if (usb_hcd_is_primary_hcd(hcd)) + xhci_cleanup_msix(xhci); +} + +/* 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_trace(xhci, trace_xhci_dbg_quirks, + "XHCI_RESET_EP_QUIRK for this evaluation HW is deprecated"); + } + if (pdev->device == PCI_DEVICE_ID_FRESCO_LOGIC_PDK && + pdev->revision == 0x4) { + xhci->quirks |= XHCI_SLOW_SUSPEND; + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "QUIRK: Fresco Logic xHC revision %u" + "must be suspended extra slowly", + pdev->revision); + } + if (pdev->device == PCI_DEVICE_ID_FRESCO_LOGIC_PDK) + xhci->quirks |= XHCI_BROKEN_STREAMS; + /* 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_trace(xhci, trace_xhci_dbg_quirks, + "QUIRK: Fresco Logic revision %u " + "has broken MSI implementation", + pdev->revision); + } + + if (pdev->vendor == PCI_VENDOR_ID_FRESCO_LOGIC && + pdev->device == PCI_DEVICE_ID_FRESCO_LOGIC_FL1009) + xhci->quirks |= XHCI_BROKEN_STREAMS; + + if (pdev->vendor == PCI_VENDOR_ID_NEC) + xhci->quirks |= XHCI_NEC_HOST; + + if (pdev->vendor == PCI_VENDOR_ID_AMD && + (pdev->device == PCI_DEVICE_ID_AMD_ARIEL_TYPEC_XHCI || + pdev->device == PCI_DEVICE_ID_AMD_ARIEL_TYPEA_XHCI || + pdev->device == PCI_DEVICE_ID_AMD_STARSHIP_XHCI || + pdev->device == PCI_DEVICE_ID_AMD_FIREFLIGHT_15D4_XHCI || + pdev->device == PCI_DEVICE_ID_AMD_FIREFLIGHT_15D5_XHCI || + pdev->device == PCI_DEVICE_ID_AMD_RAVEN_15E0_XHCI || + pdev->device == PCI_DEVICE_ID_AMD_RAVEN_15E1_XHCI || + pdev->device == PCI_DEVICE_ID_AMD_RAVEN2_XHCI)) + xhci->quirks |= XHCI_LIMIT_ENDPOINT_INTERVAL_9; + + if (pdev->vendor == PCI_VENDOR_ID_ATI && + pdev->device == PCI_DEVICE_ID_ATI_NAVI10_7316_XHCI) + xhci->quirks |= XHCI_LIMIT_ENDPOINT_INTERVAL_9; + + 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_quirk_pll_check()) + xhci->quirks |= XHCI_AMD_PLL_FIX; + + if (pdev->vendor == PCI_VENDOR_ID_AMD && + (pdev->device == 0x145c || + pdev->device == 0x15e0 || + pdev->device == 0x15e1 || + pdev->device == 0x43bb)) + xhci->quirks |= XHCI_SUSPEND_DELAY; + + if (pdev->vendor == PCI_VENDOR_ID_AMD && + (pdev->device == 0x15e0 || pdev->device == 0x15e1)) + xhci->quirks |= XHCI_SNPS_BROKEN_SUSPEND; + + if (pdev->vendor == PCI_VENDOR_ID_AMD && pdev->device == 0x15e5) { + xhci->quirks |= XHCI_DISABLE_SPARSE; + xhci->quirks |= XHCI_RESET_ON_RESUME; + } + + if (pdev->vendor == PCI_VENDOR_ID_AMD && pdev->device == 0x43f7) + xhci->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW; + + if ((pdev->vendor == PCI_VENDOR_ID_AMD) && + ((pdev->device == PCI_DEVICE_ID_AMD_PROMONTORYA_4) || + (pdev->device == PCI_DEVICE_ID_AMD_PROMONTORYA_3) || + (pdev->device == PCI_DEVICE_ID_AMD_PROMONTORYA_2) || + (pdev->device == PCI_DEVICE_ID_AMD_PROMONTORYA_1))) + xhci->quirks |= XHCI_U2_DISABLE_WAKE; + + if (pdev->vendor == PCI_VENDOR_ID_AMD && + pdev->device == PCI_DEVICE_ID_AMD_RENOIR_XHCI) + xhci->quirks |= XHCI_BROKEN_D3COLD_S2I; + + if (pdev->vendor == PCI_VENDOR_ID_INTEL) { + xhci->quirks |= XHCI_LPM_SUPPORT; + xhci->quirks |= XHCI_INTEL_HOST; + xhci->quirks |= XHCI_AVOID_BEI; + } + 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; + } + if (pdev->vendor == PCI_VENDOR_ID_INTEL && + (pdev->device == PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_WILDCATPOINT_LP_XHCI)) { + xhci->quirks |= XHCI_SPURIOUS_REBOOT; + xhci->quirks |= XHCI_SPURIOUS_WAKEUP; + } + if (pdev->vendor == PCI_VENDOR_ID_INTEL && + (pdev->device == PCI_DEVICE_ID_INTEL_SUNRISEPOINT_LP_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_SUNRISEPOINT_H_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_BROXTON_M_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_BROXTON_B_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_APOLLO_LAKE_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_DENVERTON_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_COMET_LAKE_XHCI)) { + xhci->quirks |= XHCI_PME_STUCK_QUIRK; + } + if (pdev->vendor == PCI_VENDOR_ID_INTEL && + pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI) + xhci->quirks |= XHCI_SSIC_PORT_UNUSED; + if (pdev->vendor == PCI_VENDOR_ID_INTEL && + (pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_SUNRISEPOINT_LP_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_APOLLO_LAKE_XHCI)) + xhci->quirks |= XHCI_INTEL_USB_ROLE_SW; + if (pdev->vendor == PCI_VENDOR_ID_INTEL && + (pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_SUNRISEPOINT_LP_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_SUNRISEPOINT_H_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_APOLLO_LAKE_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_DENVERTON_XHCI)) + xhci->quirks |= XHCI_MISSING_CAS; + + if (pdev->vendor == PCI_VENDOR_ID_INTEL && + (pdev->device == PCI_DEVICE_ID_INTEL_TIGER_LAKE_PCH_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_ALDER_LAKE_PCH_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_ALDER_LAKE_N_PCH_XHCI)) + xhci->quirks |= XHCI_RESET_TO_DEFAULT; + + if (pdev->vendor == PCI_VENDOR_ID_INTEL && + (pdev->device == PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_ICE_LAKE_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_TIGER_LAKE_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_MAPLE_RIDGE_XHCI)) + xhci->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW; + + if (pdev->vendor == PCI_VENDOR_ID_ETRON && + (pdev->device == PCI_DEVICE_ID_ETRON_EJ168 || + pdev->device == PCI_DEVICE_ID_ETRON_EJ188)) { + xhci->quirks |= XHCI_ETRON_HOST; + xhci->quirks |= XHCI_RESET_ON_RESUME; + xhci->quirks |= XHCI_BROKEN_STREAMS; + xhci->quirks |= XHCI_NO_SOFT_RETRY; + } + + if (pdev->vendor == PCI_VENDOR_ID_RENESAS && + pdev->device == 0x0014) { + xhci->quirks |= XHCI_ZERO_64B_REGS; + } + if (pdev->vendor == PCI_VENDOR_ID_RENESAS && + pdev->device == 0x0015) { + xhci->quirks |= XHCI_RESET_ON_RESUME; + xhci->quirks |= XHCI_ZERO_64B_REGS; + } + if (pdev->vendor == PCI_VENDOR_ID_VIA) + xhci->quirks |= XHCI_RESET_ON_RESUME; + + if (pdev->vendor == PCI_VENDOR_ID_PHYTIUM && + pdev->device == PCI_DEVICE_ID_PHYTIUM_XHCI) + xhci->quirks |= XHCI_RESET_ON_RESUME; + + /* See https://bugzilla.kernel.org/show_bug.cgi?id=79511 */ + if (pdev->vendor == PCI_VENDOR_ID_VIA && + pdev->device == 0x3432) + xhci->quirks |= XHCI_BROKEN_STREAMS; + + if (pdev->vendor == PCI_VENDOR_ID_VIA && pdev->device == PCI_DEVICE_ID_VIA_VL805) { + xhci->quirks |= XHCI_LPM_SUPPORT; + xhci->quirks |= XHCI_TRB_OVERFETCH; + xhci->dma_mask_bits = 36; + } + + if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA && + pdev->device == PCI_DEVICE_ID_ASMEDIA_1042_XHCI) { + /* + * try to tame the ASMedia 1042 controller which reports 0.96 + * but appears to behave more like 1.0 + */ + xhci->quirks |= XHCI_SPURIOUS_SUCCESS; + xhci->quirks |= XHCI_BROKEN_STREAMS; + } + if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA && + pdev->device == PCI_DEVICE_ID_ASMEDIA_1042A_XHCI) { + xhci->quirks |= XHCI_NO_64BIT_SUPPORT; + } + if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA && + (pdev->device == PCI_DEVICE_ID_ASMEDIA_1142_XHCI || + pdev->device == PCI_DEVICE_ID_ASMEDIA_2142_XHCI || + pdev->device == PCI_DEVICE_ID_ASMEDIA_3242_XHCI)) + xhci->quirks |= XHCI_NO_64BIT_SUPPORT; + + if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA && + pdev->device == PCI_DEVICE_ID_ASMEDIA_1042A_XHCI) + xhci->quirks |= XHCI_ASMEDIA_MODIFY_FLOWCONTROL; + + if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA && + pdev->device == PCI_DEVICE_ID_ASMEDIA_3042_XHCI) + xhci->quirks |= XHCI_RESET_ON_RESUME; + + if (pdev->vendor == PCI_VENDOR_ID_TI && pdev->device == 0x8241) + xhci->quirks |= XHCI_LIMIT_ENDPOINT_INTERVAL_7; + + if ((pdev->vendor == PCI_VENDOR_ID_BROADCOM || + pdev->vendor == PCI_VENDOR_ID_CAVIUM) && + pdev->device == 0x9026) + xhci->quirks |= XHCI_RESET_PLL_ON_DISCONNECT; + + if (pdev->vendor == PCI_VENDOR_ID_AMD && + (pdev->device == PCI_DEVICE_ID_AMD_PROMONTORYA_2 || + pdev->device == PCI_DEVICE_ID_AMD_PROMONTORYA_4)) + xhci->quirks |= XHCI_NO_SOFT_RETRY; + + if (pdev->vendor == PCI_VENDOR_ID_ZHAOXIN) { + xhci->quirks |= XHCI_ZHAOXIN_HOST; + xhci->quirks |= XHCI_LPM_SUPPORT; + + if (pdev->device == 0x9202) { + xhci->quirks |= XHCI_RESET_ON_RESUME; + xhci->quirks |= XHCI_TRB_OVERFETCH; + } + + if (pdev->device == 0x9203) + xhci->quirks |= XHCI_TRB_OVERFETCH; + } + + if (pdev->vendor == PCI_VENDOR_ID_CDNS && + pdev->device == PCI_DEVICE_ID_CDNS_USBSSP) + xhci->quirks |= XHCI_CDNS_SCTX_QUIRK; + + /* xHC spec requires PCI devices to support D3hot and D3cold */ + if (xhci->hci_version >= 0x120) + xhci->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW; + + if (xhci->quirks & XHCI_RESET_ON_RESUME) + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "QUIRK: Resetting on resume"); +} + +#ifdef CONFIG_ACPI +static void xhci_pme_acpi_rtd3_enable(struct pci_dev *dev) +{ + static const guid_t intel_dsm_guid = + GUID_INIT(0xac340cb7, 0xe901, 0x45bf, + 0xb7, 0xe6, 0x2b, 0x34, 0xec, 0x93, 0x1e, 0x23); + union acpi_object *obj; + + obj = acpi_evaluate_dsm(ACPI_HANDLE(&dev->dev), &intel_dsm_guid, 3, 1, + NULL); + ACPI_FREE(obj); +} + +static void xhci_find_lpm_incapable_ports(struct usb_hcd *hcd, struct usb_device *hdev) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_hub *rhub = &xhci->usb3_rhub; + int ret; + int i; + + /* This is not the usb3 roothub we are looking for */ + if (hcd != rhub->hcd) + return; + + if (hdev->maxchild > rhub->num_ports) { + dev_err(&hdev->dev, "USB3 roothub port number mismatch\n"); + return; + } + + for (i = 0; i < hdev->maxchild; i++) { + ret = usb_acpi_port_lpm_incapable(hdev, i); + + dev_dbg(&hdev->dev, "port-%d disable U1/U2 _DSM: %d\n", i + 1, ret); + + if (ret >= 0) { + rhub->ports[i]->lpm_incapable = ret; + continue; + } + } +} + +#else +static void xhci_pme_acpi_rtd3_enable(struct pci_dev *dev) { } +static void xhci_find_lpm_incapable_ports(struct usb_hcd *hcd, struct usb_device *hdev) { } +#endif /* CONFIG_ACPI */ + +/* 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; + u8 sbrn; + + xhci = hcd_to_xhci(hcd); + + /* imod_interval is the interrupt moderation value in nanoseconds. */ + xhci->imod_interval = 40000; + + retval = xhci_gen_setup(hcd, xhci_pci_quirks); + if (retval) + return retval; + + if (!usb_hcd_is_primary_hcd(hcd)) + return 0; + + xhci->allow_single_roothub = 1; + + if (xhci->quirks & XHCI_PME_STUCK_QUIRK) + xhci_pme_acpi_rtd3_enable(pdev); + + pci_read_config_byte(pdev, XHCI_SBRN_OFFSET, &sbrn); + xhci_dbg(xhci, "Got SBRN %u\n", (unsigned int)sbrn); + + /* Find any debug ports */ + return xhci_pci_reinit(xhci, pdev); +} + +static int xhci_pci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, + struct usb_tt *tt, gfp_t mem_flags) +{ + /* Check if acpi claims some USB3 roothub ports are lpm incapable */ + if (!hdev->parent) + xhci_find_lpm_incapable_ports(hcd, hdev); + + return xhci_update_hub_device(hcd, hdev, tt, mem_flags); +} + +/* + * 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. + */ +int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + int retval; + struct xhci_hcd *xhci; + struct usb_hcd *hcd, *usb3_hcd; + struct reset_control *reset; + + reset = devm_reset_control_get_optional_exclusive(&dev->dev, NULL); + if (IS_ERR(reset)) + return PTR_ERR(reset); + reset_control_reset(reset); + + /* 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, &xhci_pci_hc_driver); + + 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->reset = reset; + + if (!xhci_has_one_roothub(xhci)) { + xhci->shared_hcd = usb_create_shared_hcd(&xhci_pci_hc_driver, &dev->dev, + pci_name(dev), hcd); + if (!xhci->shared_hcd) { + retval = -ENOMEM; + goto dealloc_usb2_hcd; + } + + retval = xhci_ext_cap_init(xhci); + if (retval) + goto put_usb3_hcd; + + retval = usb_add_hcd(xhci->shared_hcd, dev->irq, IRQF_SHARED); + if (retval) + goto put_usb3_hcd; + } else { + retval = xhci_ext_cap_init(xhci); + if (retval) + goto dealloc_usb2_hcd; + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); + if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && + GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + usb3_hcd->can_do_streams = 1; + + /* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */ + pm_runtime_put_noidle(&dev->dev); + + if (pci_choose_state(dev, PMSG_SUSPEND) == PCI_D0) + pm_runtime_get(&dev->dev); + else if (xhci->quirks & XHCI_DEFAULT_PM_RUNTIME_ALLOW) + pm_runtime_allow(&dev->dev); + + dma_set_max_seg_size(&dev->dev, UINT_MAX); + + if (device_property_read_bool(&dev->dev, "ti,pwron-active-high")) + pci_clear_and_set_config_dword(dev, 0xE0, 0, 1 << 22); + + 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; +} +EXPORT_SYMBOL_NS_GPL(xhci_pci_common_probe, "xhci"); + +/* handled by xhci-pci-renesas if enabled */ +static const struct pci_device_id pci_ids_renesas[] = { + { PCI_DEVICE(PCI_VENDOR_ID_RENESAS, 0x0014) }, + { PCI_DEVICE(PCI_VENDOR_ID_RENESAS, 0x0015) }, + { /* end: all zeroes */ } +}; + +/* handled by xhci-pci-prom21 if enabled */ +static const struct pci_device_id pci_ids_prom21[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD) }, + { /* end: all zeroes */ } +}; + +static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + if (IS_ENABLED(CONFIG_USB_XHCI_PCI_RENESAS) && + pci_match_id(pci_ids_renesas, dev)) + return -ENODEV; + + if (IS_ENABLED(CONFIG_USB_XHCI_PCI_PROM21) && + pci_match_id(pci_ids_prom21, dev)) + return -ENODEV; + + return xhci_pci_common_probe(dev, id); +} + +void xhci_pci_remove(struct pci_dev *dev) +{ + struct xhci_hcd *xhci; + bool set_power_d3; + + xhci = hcd_to_xhci(pci_get_drvdata(dev)); + set_power_d3 = xhci->quirks & XHCI_SPURIOUS_WAKEUP; + + xhci->xhc_state |= XHCI_STATE_REMOVING; + + if (pci_choose_state(dev, PMSG_SUSPEND) == PCI_D0) + pm_runtime_put(&dev->dev); + else if (xhci->quirks & XHCI_DEFAULT_PM_RUNTIME_ALLOW) + pm_runtime_forbid(&dev->dev); + + if (xhci->shared_hcd) { + usb_remove_hcd(xhci->shared_hcd); + usb_put_hcd(xhci->shared_hcd); + xhci->shared_hcd = NULL; + } + + usb_hcd_pci_remove(dev); + + /* Workaround for spurious wakeups at shutdown with HSW */ + if (set_power_d3) + pci_set_power_state(dev, PCI_D3hot); +} +EXPORT_SYMBOL_NS_GPL(xhci_pci_remove, "xhci"); + +/* + * In some Intel xHCI controllers, in order to get D3 working, + * through a vendor specific SSIC CONFIG register at offset 0x883c, + * SSIC PORT need to be marked as "unused" before putting xHCI + * into D3. After D3 exit, the SSIC port need to be marked as "used". + * Without this change, xHCI might not enter D3 state. + */ +static void xhci_ssic_port_unused_quirk(struct usb_hcd *hcd, bool suspend) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + u32 val; + void __iomem *reg; + int i; + + for (i = 0; i < SSIC_PORT_NUM; i++) { + reg = (void __iomem *) xhci->cap_regs + + SSIC_PORT_CFG2 + + i * SSIC_PORT_CFG2_OFFSET; + + /* Notify SSIC that SSIC profile programming is not done. */ + val = readl(reg) & ~PROG_DONE; + writel(val, reg); + + /* Mark SSIC port as unused(suspend) or used(resume) */ + val = readl(reg); + if (suspend) + val |= SSIC_PORT_UNUSED; + else + val &= ~SSIC_PORT_UNUSED; + writel(val, reg); + + /* Notify SSIC that SSIC profile programming is done */ + val = readl(reg) | PROG_DONE; + writel(val, reg); + readl(reg); + } +} + +/* + * Make sure PME works on some Intel xHCI controllers by writing 1 to clear + * the Internal PME flag bit in vendor specific PMCTRL register at offset 0x80a4 + */ +static void xhci_pme_quirk(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + void __iomem *reg; + u32 val; + + reg = (void __iomem *) xhci->cap_regs + 0x80a4; + val = readl(reg); + writel(val | BIT(28), reg); + readl(reg); +} + +static void xhci_sparse_control_quirk(struct usb_hcd *hcd) +{ + u32 reg; + + reg = readl(hcd->regs + SPARSE_CNTL_ENABLE); + reg &= ~BIT(SPARSE_DISABLE_BIT); + writel(reg, hcd->regs + SPARSE_CNTL_ENABLE); +} + +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); + int ret; + + /* + * Systems with the TI redriver that loses port status change events + * need to have the registers polled during D3, so avoid D3cold. + */ + if (xhci->quirks & XHCI_COMP_MODE_QUIRK) + pci_d3cold_disable(pdev); + +#ifdef CONFIG_SUSPEND + /* d3cold is broken, but only when s2idle is used */ + if (pm_suspend_target_state == PM_SUSPEND_TO_IDLE && + xhci->quirks & (XHCI_BROKEN_D3COLD_S2I)) + pci_d3cold_disable(pdev); +#endif + + if (xhci->quirks & XHCI_PME_STUCK_QUIRK) + xhci_pme_quirk(hcd); + + if (xhci->quirks & XHCI_SSIC_PORT_UNUSED) + xhci_ssic_port_unused_quirk(hcd, true); + + if (xhci->quirks & XHCI_DISABLE_SPARSE) + xhci_sparse_control_quirk(hcd); + + ret = xhci_suspend(xhci, do_wakeup); + + /* synchronize irq when using MSI-X */ + xhci_msix_sync_irqs(xhci); + + if (ret && (xhci->quirks & XHCI_SSIC_PORT_UNUSED)) + xhci_ssic_port_unused_quirk(hcd, false); + + return ret; +} + +static int xhci_pci_resume(struct usb_hcd *hcd, pm_message_t msg) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + bool power_lost = msg.event == PM_EVENT_RESTORE; + bool is_auto_resume = msg.event == PM_EVENT_AUTO_RESUME; + + reset_control_reset(xhci->reset); + + /* 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. + * It should not matter whether the EHCI or xHCI controller is + * resumed first. It's enough to do the switchover in xHCI because + * USB core won't notice anything as the hub driver doesn't start + * running again until after all the devices (including both EHCI and + * xHCI host controllers) have been resumed. + */ + + if (pdev->vendor == PCI_VENDOR_ID_INTEL) + usb_enable_intel_xhci_ports(pdev); + + if (xhci->quirks & XHCI_SSIC_PORT_UNUSED) + xhci_ssic_port_unused_quirk(hcd, false); + + if (xhci->quirks & XHCI_PME_STUCK_QUIRK) + xhci_pme_quirk(hcd); + + return xhci_resume(xhci, power_lost, is_auto_resume); +} + +static int xhci_pci_poweroff_late(struct usb_hcd *hcd, bool do_wakeup) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_port *port; + struct usb_device *udev; + u32 portsc; + int i; + + /* + * Systems with XHCI_RESET_TO_DEFAULT quirk have boot firmware that + * cause significant boot delay if usb ports are in suspended U3 state + * during boot. Some USB devices survive in U3 state over S4 hibernate + * + * Disable ports that are in U3 if remote wake is not enabled for either + * host controller or connected device + */ + + if (!(xhci->quirks & XHCI_RESET_TO_DEFAULT)) + return 0; + + for (i = 0; i < xhci->max_ports; i++) { + port = &xhci->hw_ports[i]; + portsc = xhci_portsc_readl(port); + + if ((portsc & PORT_PLS_MASK) != XDEV_U3) + continue; + + if (!port->slot_id || !xhci->devs[port->slot_id]) { + xhci_err(xhci, "No dev for slot_id %d for port %d-%d in U3\n", + port->slot_id, port->rhub->hcd->self.busnum, + port->hcd_portnum + 1); + continue; + } + + udev = xhci->devs[port->slot_id]->udev; + + /* if wakeup is enabled then don't disable the port */ + if (udev->do_remote_wakeup && do_wakeup) + continue; + + xhci_dbg(xhci, "port %d-%d in U3 without wakeup, disable it\n", + port->rhub->hcd->self.busnum, port->hcd_portnum + 1); + portsc = xhci_port_state_to_neutral(portsc); + xhci_portsc_writel(port, portsc | PORT_PE); + } + + return 0; +} + +static void xhci_pci_shutdown(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + + xhci_shutdown(hcd); + xhci_cleanup_msix(xhci); + + /* Yet another workaround for spurious wakeups at shutdown with HSW */ + if (xhci->quirks & XHCI_SPURIOUS_WAKEUP) + pci_set_power_state(pdev, PCI_D3hot); +} + +/*-------------------------------------------------------------------------*/ + +/* 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), + }, + { /* 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 = hcd_name, + .id_table = pci_ids, + + .probe = xhci_pci_probe, + .remove = xhci_pci_remove, + /* suspend and resume implemented later */ + + .shutdown = usb_hcd_pci_shutdown, + .driver = { + .pm = pm_ptr(&usb_hcd_pci_pm_ops), + }, +}; + +static int __init xhci_pci_init(void) +{ + xhci_init_driver(&xhci_pci_hc_driver, &xhci_pci_overrides); + xhci_pci_hc_driver.pci_suspend = pm_ptr(xhci_pci_suspend); + xhci_pci_hc_driver.pci_resume = pm_ptr(xhci_pci_resume); + xhci_pci_hc_driver.pci_poweroff_late = pm_ptr(xhci_pci_poweroff_late); + xhci_pci_hc_driver.shutdown = pm_ptr(xhci_pci_shutdown); + xhci_pci_hc_driver.stop = xhci_pci_stop; + return pci_register_driver(&xhci_pci_driver); +} +module_init(xhci_pci_init); + +static void __exit xhci_pci_exit(void) +{ + pci_unregister_driver(&xhci_pci_driver); +} +module_exit(xhci_pci_exit); + +MODULE_DESCRIPTION("xHCI PCI Host Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/xhci-pci.h b/drivers/usb/host/xhci-pci.h new file mode 100644 index 000000000..11f435f94 --- /dev/null +++ b/drivers/usb/host/xhci-pci.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2019-2020 Linaro Limited */ + +#ifndef XHCI_PCI_H +#define XHCI_PCI_H + +#define PCI_DEVICE_ID_AMD_PROM21_XHCI_43FC 0x43fc +#define PCI_DEVICE_ID_AMD_PROM21_XHCI_43FD 0x43fd + +int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id); +void xhci_pci_remove(struct pci_dev *dev); + +#endif diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c new file mode 100644 index 000000000..6fd595f81 --- /dev/null +++ b/drivers/usb/host/xhci-plat.c @@ -0,0 +1,658 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xhci-plat.c - xHCI host controller driver platform Bus Glue. + * + * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com + * Author: Sebastian Andrzej Siewior + * + * A lot of code borrowed from the Linux xHCI driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xhci.h" +#include "xhci-plat.h" +#include "xhci-mvebu.h" + +static struct hc_driver __read_mostly xhci_plat_hc_driver; + +static int xhci_plat_setup(struct usb_hcd *hcd); +static int xhci_plat_start(struct usb_hcd *hcd); + +static const struct xhci_driver_overrides xhci_plat_overrides __initconst = { + .extra_priv_size = sizeof(struct xhci_plat_priv), + .reset = xhci_plat_setup, + .start = xhci_plat_start, +}; + +static void xhci_priv_plat_start(struct usb_hcd *hcd) +{ + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (priv->plat_start) + priv->plat_start(hcd); +} + +static int xhci_priv_init_quirk(struct usb_hcd *hcd) +{ + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (!priv->init_quirk) + return 0; + + return priv->init_quirk(hcd); +} + +static int xhci_priv_suspend_quirk(struct usb_hcd *hcd) +{ + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (!priv->suspend_quirk) + return 0; + + return priv->suspend_quirk(hcd); +} + +static int xhci_priv_resume_quirk(struct usb_hcd *hcd) +{ + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (!priv->resume_quirk) + return 0; + + return priv->resume_quirk(hcd); +} + +static int xhci_priv_post_resume_quirk(struct usb_hcd *hcd) +{ + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (!priv->post_resume_quirk) + return 0; + + return priv->post_resume_quirk(hcd); +} + +static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci) +{ + struct xhci_plat_priv *priv = xhci_to_priv(xhci); + + xhci->quirks |= priv->quirks; +} + +/* called during probe() after chip reset completes */ +static int xhci_plat_setup(struct usb_hcd *hcd) +{ + int ret; + + + ret = xhci_priv_init_quirk(hcd); + if (ret) + return ret; + + return xhci_gen_setup(hcd, xhci_plat_quirks); +} + +static int xhci_plat_start(struct usb_hcd *hcd) +{ + xhci_priv_plat_start(hcd); + return xhci_run(hcd); +} + +#ifdef CONFIG_OF +static const struct xhci_plat_priv xhci_plat_marvell_armada = { + .init_quirk = xhci_mvebu_mbus_init_quirk, +}; + +static const struct xhci_plat_priv xhci_plat_marvell_armada3700 = { + .quirks = XHCI_RESET_ON_RESUME, +}; + +static const struct xhci_plat_priv xhci_plat_brcm = { + .quirks = XHCI_RESET_ON_RESUME | XHCI_SUSPEND_RESUME_CLKS, +}; + +static const struct of_device_id usb_xhci_of_match[] = { + { + .compatible = "generic-xhci", + }, { + .compatible = "xhci-platform", + }, { + .compatible = "marvell,armada-375-xhci", + .data = &xhci_plat_marvell_armada, + }, { + .compatible = "marvell,armada-380-xhci", + .data = &xhci_plat_marvell_armada, + }, { + .compatible = "marvell,armada3700-xhci", + .data = &xhci_plat_marvell_armada3700, + }, { + .compatible = "brcm,xhci-brcm-v2", + .data = &xhci_plat_brcm, + }, { + .compatible = "brcm,bcm2711-xhci", + .data = &xhci_plat_brcm, + }, { + .compatible = "brcm,bcm7445-xhci", + .data = &xhci_plat_brcm, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, usb_xhci_of_match); +#endif + +int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const struct xhci_plat_priv *priv_match) +{ + const struct hc_driver *driver; + struct device *tmpdev; + struct xhci_hcd *xhci; + struct resource *res; + struct usb_hcd *hcd, *usb3_hcd; + int ret; + int irq; + struct xhci_plat_priv *priv = NULL; + const struct of_device_id *of_match; + + if (usb_disabled()) + return -ENODEV; + + driver = &xhci_plat_hc_driver; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + if (!sysdev) + sysdev = &pdev->dev; + + ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); + if (ret) + return ret; + + pm_runtime_set_active(&pdev->dev); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_enable(&pdev->dev); + pm_runtime_get_noresume(&pdev->dev); + + hcd = __usb_create_hcd(driver, sysdev, &pdev->dev, + dev_name(&pdev->dev), NULL); + if (!hcd) { + ret = -ENOMEM; + goto disable_runtime; + } + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(hcd->regs)) { + ret = PTR_ERR(hcd->regs); + goto put_hcd; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + xhci = hcd_to_xhci(hcd); + + xhci->allow_single_roothub = 1; + + /* + * Not all platforms have clks so it is not an error if the + * clock do not exist. + */ + xhci->reg_clk = devm_clk_get_optional(&pdev->dev, "reg"); + if (IS_ERR(xhci->reg_clk)) { + ret = PTR_ERR(xhci->reg_clk); + goto put_hcd; + } + + xhci->clk = devm_clk_get_optional(&pdev->dev, NULL); + if (IS_ERR(xhci->clk)) { + ret = PTR_ERR(xhci->clk); + goto put_hcd; + } + + xhci->reset = devm_reset_control_array_get_optional_shared(&pdev->dev); + if (IS_ERR(xhci->reset)) { + ret = PTR_ERR(xhci->reset); + goto put_hcd; + } + + ret = reset_control_deassert(xhci->reset); + if (ret) + goto put_hcd; + + ret = clk_prepare_enable(xhci->reg_clk); + if (ret) + goto err_reset; + + ret = clk_prepare_enable(xhci->clk); + if (ret) + goto disable_reg_clk; + + if (priv_match) { + priv = hcd_to_xhci_priv(hcd); + /* Just copy data for now */ + *priv = *priv_match; + } + + device_set_wakeup_capable(&pdev->dev, true); + + xhci->main_hcd = hcd; + + /* imod_interval is the interrupt moderation value in nanoseconds. */ + xhci->imod_interval = 40000; + + /* Iterate over all parent nodes for finding quirks */ + for (tmpdev = &pdev->dev; tmpdev; tmpdev = tmpdev->parent) { + + if (device_property_read_bool(tmpdev, "usb2-lpm-disable")) + xhci->quirks |= XHCI_HW_LPM_DISABLE; + + if (device_property_read_bool(tmpdev, "usb3-lpm-capable")) + xhci->quirks |= XHCI_LPM_SUPPORT; + + if (device_property_read_bool(tmpdev, "quirk-broken-port-ped")) + xhci->quirks |= XHCI_BROKEN_PORT_PED; + + if (device_property_read_bool(tmpdev, "xhci-sg-trb-cache-size-quirk")) + xhci->quirks |= XHCI_SG_TRB_CACHE_SIZE_QUIRK; + + if (device_property_read_bool(tmpdev, "write-64-hi-lo-quirk")) + xhci->quirks |= XHCI_WRITE_64_HI_LO; + + if (device_property_read_bool(tmpdev, "xhci-missing-cas-quirk")) + xhci->quirks |= XHCI_MISSING_CAS; + + if (device_property_read_bool(tmpdev, "xhci-skip-phy-init-quirk")) + xhci->quirks |= XHCI_SKIP_PHY_INIT; + + device_property_read_u32(tmpdev, "imod-interval-ns", + &xhci->imod_interval); + device_property_read_u16(tmpdev, "num-hc-interrupters", + &xhci->max_interrupters); + } + + /* + * Drivers such as dwc3 manages PHYs themself (and rely on driver name + * matching for the xhci platform device). + */ + of_match = of_match_device(pdev->dev.driver->of_match_table, &pdev->dev); + if (of_match) { + hcd->usb_phy = devm_usb_get_phy_by_phandle(sysdev, "usb-phy", 0); + if (IS_ERR(hcd->usb_phy)) { + ret = PTR_ERR(hcd->usb_phy); + if (ret == -EPROBE_DEFER) + goto disable_clk; + hcd->usb_phy = NULL; + } else { + ret = usb_phy_init(hcd->usb_phy); + if (ret) + goto disable_clk; + } + } + + hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node); + + if ((priv && (priv->quirks & XHCI_SKIP_PHY_INIT)) || + (xhci->quirks & XHCI_SKIP_PHY_INIT)) + hcd->skip_phy_initialization = 1; + + if (priv && (priv->quirks & XHCI_SG_TRB_CACHE_SIZE_QUIRK)) + xhci->quirks |= XHCI_SG_TRB_CACHE_SIZE_QUIRK; + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret) + goto disable_usb_phy; + + if (!xhci_has_one_roothub(xhci)) { + xhci->shared_hcd = __usb_create_hcd(driver, sysdev, &pdev->dev, + dev_name(&pdev->dev), hcd); + if (!xhci->shared_hcd) { + ret = -ENOMEM; + goto dealloc_usb2_hcd; + } + + if (of_match) { + xhci->shared_hcd->usb_phy = devm_usb_get_phy_by_phandle(sysdev, + "usb-phy", 1); + if (IS_ERR(xhci->shared_hcd->usb_phy)) { + xhci->shared_hcd->usb_phy = NULL; + } else { + ret = usb_phy_init(xhci->shared_hcd->usb_phy); + if (ret) + dev_err(sysdev, "%s init usb3phy fail (ret=%d)\n", + __func__, ret); + } + } + + xhci->shared_hcd->tpl_support = hcd->tpl_support; + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); + if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 && + !(xhci->quirks & XHCI_BROKEN_STREAMS)) + usb3_hcd->can_do_streams = 1; + + if (xhci->shared_hcd) { + xhci->shared_hcd->rsrc_start = hcd->rsrc_start; + xhci->shared_hcd->rsrc_len = hcd->rsrc_len; + ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); + if (ret) + goto put_usb3_hcd; + } + + device_enable_async_suspend(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + + /* + * Prevent runtime pm from being on as default, users should enable + * runtime pm using power/control in sysfs. + */ + pm_runtime_forbid(&pdev->dev); + + return 0; + + +put_usb3_hcd: + usb_put_hcd(xhci->shared_hcd); + +dealloc_usb2_hcd: + usb_remove_hcd(hcd); + +disable_usb_phy: + usb_phy_shutdown(hcd->usb_phy); + +disable_clk: + clk_disable_unprepare(xhci->clk); + +disable_reg_clk: + clk_disable_unprepare(xhci->reg_clk); + +err_reset: + reset_control_assert(xhci->reset); + +put_hcd: + usb_put_hcd(hcd); + +disable_runtime: + pm_runtime_put_noidle(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(xhci_plat_probe); + +static int xhci_generic_plat_probe(struct platform_device *pdev) +{ + const struct xhci_plat_priv *priv_match; + struct device *sysdev; + int ret; + + /* + * sysdev must point to a device that is known to the system firmware + * or PCI hardware. We handle these three cases here: + * 1. xhci_plat comes from firmware + * 2. xhci_plat is child of a device from firmware (dwc3-plat) + * 3. xhci_plat is grandchild of a pci device (dwc3-pci) + */ + for (sysdev = &pdev->dev; sysdev; sysdev = sysdev->parent) { + if (is_of_node(sysdev->fwnode) || + is_acpi_device_node(sysdev->fwnode)) + break; + else if (dev_is_pci(sysdev)) + break; + } + + if (!sysdev) + sysdev = &pdev->dev; + + if (WARN_ON(!sysdev->dma_mask)) { + /* Platform did not initialize dma_mask */ + ret = dma_coerce_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); + if (ret) + return ret; + } + + if (pdev->dev.of_node) + priv_match = of_device_get_match_data(&pdev->dev); + else + priv_match = dev_get_platdata(&pdev->dev); + + return xhci_plat_probe(pdev, sysdev, priv_match); +} + +void xhci_plat_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct clk *clk = xhci->clk; + struct clk *reg_clk = xhci->reg_clk; + struct usb_hcd *shared_hcd = xhci->shared_hcd; + + xhci->xhc_state |= XHCI_STATE_REMOVING; + pm_runtime_get_sync(&dev->dev); + + if (shared_hcd) { + usb_remove_hcd(shared_hcd); + xhci->shared_hcd = NULL; + } + + usb_phy_shutdown(hcd->usb_phy); + + usb_remove_hcd(hcd); + + if (shared_hcd) + usb_put_hcd(shared_hcd); + + clk_disable_unprepare(clk); + clk_disable_unprepare(reg_clk); + reset_control_assert(xhci->reset); + usb_put_hcd(hcd); + + pm_runtime_disable(&dev->dev); + pm_runtime_put_noidle(&dev->dev); + pm_runtime_set_suspended(&dev->dev); +} +EXPORT_SYMBOL_GPL(xhci_plat_remove); + +static int xhci_plat_suspend_common(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int ret; + + if (pm_runtime_suspended(dev)) + pm_runtime_resume(dev); + + ret = xhci_priv_suspend_quirk(hcd); + if (ret) + return ret; + /* + * xhci_suspend() needs `do_wakeup` to know whether host is allowed + * to do wakeup during suspend. + */ + ret = xhci_suspend(xhci, device_may_wakeup(dev)); + if (ret) + return ret; + + if (!device_may_wakeup(dev) && (xhci->quirks & XHCI_SUSPEND_RESUME_CLKS)) { + clk_disable_unprepare(xhci->clk); + clk_disable_unprepare(xhci->reg_clk); + } + + return 0; +} + +static int xhci_plat_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (xhci_sideband_check(hcd)) { + priv->sideband_at_suspend = 1; + dev_dbg(dev, "sideband instance active, skip suspend.\n"); + return 0; + } + + return xhci_plat_suspend_common(dev); +} + +static int xhci_plat_freeze(struct device *dev) +{ + return xhci_plat_suspend_common(dev); +} + +static int xhci_plat_resume_common(struct device *dev, bool power_lost) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int ret; + + if (!device_may_wakeup(dev) && (xhci->quirks & XHCI_SUSPEND_RESUME_CLKS)) { + ret = clk_prepare_enable(xhci->clk); + if (ret) + return ret; + + ret = clk_prepare_enable(xhci->reg_clk); + if (ret) { + clk_disable_unprepare(xhci->clk); + return ret; + } + } + + ret = xhci_priv_resume_quirk(hcd); + if (ret) + goto disable_clks; + + ret = xhci_resume(xhci, power_lost || priv->power_lost, false); + if (ret) + goto disable_clks; + + ret = xhci_priv_post_resume_quirk(hcd); + if (ret) + goto disable_clks; + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; + +disable_clks: + if (!device_may_wakeup(dev) && (xhci->quirks & XHCI_SUSPEND_RESUME_CLKS)) { + clk_disable_unprepare(xhci->clk); + clk_disable_unprepare(xhci->reg_clk); + } + + return ret; +} + +static int xhci_plat_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (priv->sideband_at_suspend) { + priv->sideband_at_suspend = 0; + dev_dbg(dev, "sideband instance active, skip resume.\n"); + return 0; + } + + return xhci_plat_resume_common(dev, false); +} + +static int xhci_plat_thaw(struct device *dev) +{ + return xhci_plat_resume_common(dev, false); +} + +static int xhci_plat_restore(struct device *dev) +{ + return xhci_plat_resume_common(dev, true); +} + +static int __maybe_unused xhci_plat_runtime_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + int ret; + + ret = xhci_priv_suspend_quirk(hcd); + if (ret) + return ret; + + return xhci_suspend(xhci, true); +} + +static int __maybe_unused xhci_plat_runtime_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + return xhci_resume(xhci, false, true); +} + +const struct dev_pm_ops xhci_plat_pm_ops = { + .suspend = pm_sleep_ptr(xhci_plat_suspend), + .resume = pm_sleep_ptr(xhci_plat_resume), + .freeze = pm_sleep_ptr(xhci_plat_freeze), + .thaw = pm_sleep_ptr(xhci_plat_thaw), + .poweroff = pm_sleep_ptr(xhci_plat_freeze), + .restore = pm_sleep_ptr(xhci_plat_restore), + + SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend, + xhci_plat_runtime_resume, + NULL) +}; +EXPORT_SYMBOL_GPL(xhci_plat_pm_ops); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id usb_xhci_acpi_match[] = { + /* XHCI-compliant USB Controller */ + { "PNP0D10", }, + { "PNP0D15", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, usb_xhci_acpi_match); +#endif + +static struct platform_driver usb_generic_xhci_driver = { + .probe = xhci_generic_plat_probe, + .remove = xhci_plat_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "xhci-hcd", + .pm = &xhci_plat_pm_ops, + .of_match_table = of_match_ptr(usb_xhci_of_match), + .acpi_match_table = ACPI_PTR(usb_xhci_acpi_match), + }, +}; +MODULE_ALIAS("platform:xhci-hcd"); + +static int __init xhci_plat_init(void) +{ + xhci_init_driver(&xhci_plat_hc_driver, &xhci_plat_overrides); + return platform_driver_register(&usb_generic_xhci_driver); +} +module_init(xhci_plat_init); + +static void __exit xhci_plat_exit(void) +{ + platform_driver_unregister(&usb_generic_xhci_driver); +} +module_exit(xhci_plat_exit); + +MODULE_DESCRIPTION("xHCI Platform Host Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h new file mode 100644 index 000000000..00751d851 --- /dev/null +++ b/drivers/usb/host/xhci-plat.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xhci-plat.h - xHCI host controller driver platform Bus Glue. + * + * Copyright (C) 2015 Renesas Electronics Corporation + */ + +#ifndef _XHCI_PLAT_H +#define _XHCI_PLAT_H + +struct device; +struct platform_device; +struct usb_hcd; + +struct xhci_plat_priv { + const char *firmware_name; + unsigned long long quirks; + bool power_lost; + unsigned sideband_at_suspend:1; + void (*plat_start)(struct usb_hcd *); + int (*init_quirk)(struct usb_hcd *); + int (*suspend_quirk)(struct usb_hcd *); + int (*resume_quirk)(struct usb_hcd *); + int (*post_resume_quirk)(struct usb_hcd *); +}; + +#define hcd_to_xhci_priv(h) ((struct xhci_plat_priv *)hcd_to_xhci(h)->priv) +#define xhci_to_priv(x) ((struct xhci_plat_priv *)(x)->priv) + +int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, + const struct xhci_plat_priv *priv_match); + +void xhci_plat_remove(struct platform_device *dev); +extern const struct dev_pm_ops xhci_plat_pm_ops; + +#endif /* _XHCI_PLAT_H */ diff --git a/drivers/usb/host/xhci-port.h b/drivers/usb/host/xhci-port.h new file mode 100644 index 000000000..889b5fb0f --- /dev/null +++ b/drivers/usb/host/xhci-port.h @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* 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_U1 (0x1 << 5) +#define XDEV_U2 (0x2 << 5) +#define XDEV_U3 (0x3 << 5) +#define XDEV_DISABLED (0x4 << 5) +#define XDEV_RXDETECT (0x5 << 5) +#define XDEV_INACTIVE (0x6 << 5) +#define XDEV_POLLING (0x7 << 5) +#define XDEV_RECOVERY (0x8 << 5) +#define XDEV_HOT_RESET (0x9 << 5) +#define XDEV_COMP_MODE (0xa << 5) +#define XDEV_TEST_MODE (0xb << 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 XDEV_SSP (0x5 << 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) +#define DEV_SUPERSPEEDPLUS(p) (((p) & DEV_SPEED_MASK) == XDEV_SSP) +#define DEV_SUPERSPEED_ANY(p) (((p) & DEV_SPEED_MASK) >= XDEV_SS) +#define DEV_PORT_SPEED(p) (((p) >> 10) & 0x0f) + +/* 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) +#define SLOT_SPEED_SSP (XDEV_SSP << 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) +#define PORT_CHANGE_MASK (PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | \ + PORT_RC | PORT_PLC | PORT_CEC) + + +/* 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 non-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_MASK (0xff << 8) +#define PORT_L1DS(p) (((p) & 0xff) << 8) +#define PORT_HLE (1 << 16) +#define PORT_TEST_MODE_SHIFT 28 + +/* USB3 Protocol PORTLI Port Link Information */ +#define PORT_LEC(p) ((p) & 0xffff) +#define PORT_RX_LANES(p) (((p) >> 16) & 0xf) +#define PORT_TX_LANES(p) (((p) >> 20) & 0xf) + +/* eUSB2v2 protocol PORTLI Port Link information, RsvdP for normal USB2 */ +#define PORTLI_RDR(p) ((p) & 0xf) +#define PORTLI_TDR(p) (((p) >> 4) & 0xf) + +/* USB2 Protocol PORTHLPMC */ +#define PORT_HIRDM(p)((p) & 3) +#define PORT_L1_TIMEOUT(p)(((p) & 0xff) << 2) +#define PORT_BESLD(p)(((p) & 0xf) << 10) + +/* use 512 microseconds as USB2 LPM L1 default timeout. */ +#define XHCI_L1_TIMEOUT 512 + +/* Set default HIRD/BESL value to 4 (350/400us) for USB2 L1 LPM resume latency. + * Safe to use with mixed HIRD and BESL systems (host and device) and is used + * by other operating systems. + * + * XHCI 1.0 errata 8/14/12 Table 13 notes: + * "Software should choose xHC BESL/BESLD field values that do not violate a + * device's resume latency requirements, + * e.g. not program values > '4' if BLC = '1' and a HIRD device is attached, + * or not program values < '4' if BLC = '0' and a BESL device is attached. + */ +#define XHCI_DEFAULT_BESL 4 + +/* + * USB3 specification define a 360ms tPollingLFPSTiemout for USB3 ports + * to complete link training. usually link trainig completes much faster + * so check status 10 times with 36ms sleep in places we need to wait for + * polling to complete. + */ +#define XHCI_PORT_POLLING_LFPS_TIME 36 diff --git a/drivers/usb/host/xhci-rcar-regs.h b/drivers/usb/host/xhci-rcar-regs.h new file mode 100644 index 000000000..5ecbda858 --- /dev/null +++ b/drivers/usb/host/xhci-rcar-regs.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __XHCI_RCAR_H +#define __XHCI_RCAR_H + +/*** Register Offset ***/ +#define RCAR_USB3_AXH_STA 0x104 /* AXI Host Control Status */ +#define RCAR_USB3_INT_ENA 0x224 /* Interrupt Enable */ +#define RCAR_USB3_DL_CTRL 0x250 /* FW Download Control & Status */ +#define RCAR_USB3_FW_DATA0 0x258 /* FW Data0 */ + +#define RCAR_USB3_LCLK 0xa44 /* LCLK Select */ +#define RCAR_USB3_CONF1 0xa48 /* USB3.0 Configuration1 */ +#define RCAR_USB3_CONF2 0xa5c /* USB3.0 Configuration2 */ +#define RCAR_USB3_CONF3 0xaa8 /* USB3.0 Configuration3 */ +#define RCAR_USB3_RX_POL 0xab0 /* USB3.0 RX Polarity */ +#define RCAR_USB3_TX_POL 0xab8 /* USB3.0 TX Polarity */ + +/*** Register Settings ***/ +/* AXI Host Control Status */ +#define RCAR_USB3_AXH_STA_B3_PLL_ACTIVE 0x00010000 +#define RCAR_USB3_AXH_STA_B2_PLL_ACTIVE 0x00000001 +#define RCAR_USB3_AXH_STA_PLL_ACTIVE_MASK (RCAR_USB3_AXH_STA_B3_PLL_ACTIVE | \ + RCAR_USB3_AXH_STA_B2_PLL_ACTIVE) + +/* Interrupt Enable */ +#define RCAR_USB3_INT_XHC_ENA 0x00000001 +#define RCAR_USB3_INT_PME_ENA 0x00000002 +#define RCAR_USB3_INT_HSE_ENA 0x00000004 +#define RCAR_USB3_INT_ENA_VAL (RCAR_USB3_INT_XHC_ENA | \ + RCAR_USB3_INT_PME_ENA | RCAR_USB3_INT_HSE_ENA) + +/* FW Download Control & Status */ +#define RCAR_USB3_DL_CTRL_ENABLE 0x00000001 +#define RCAR_USB3_DL_CTRL_FW_SUCCESS 0x00000010 +#define RCAR_USB3_DL_CTRL_FW_SET_DATA0 0x00000100 + +/* LCLK Select */ +#define RCAR_USB3_LCLK_ENA_VAL 0x01030001 + +/* USB3.0 Configuration */ +#define RCAR_USB3_CONF1_VAL 0x00030204 +#define RCAR_USB3_CONF2_VAL 0x00030300 +#define RCAR_USB3_CONF3_VAL 0x13802007 + +/* USB3.0 Polarity */ +#define RCAR_USB3_RX_POL_VAL BIT(21) +#define RCAR_USB3_TX_POL_VAL BIT(4) + +#endif /* __XHCI_RCAR_H */ diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c new file mode 100644 index 000000000..d747c0905 --- /dev/null +++ b/drivers/usb/host/xhci-rcar.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xHCI host controller driver for R-Car SoCs + * + * Copyright (C) 2014 Renesas Electronics Corporation + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "xhci.h" +#include "xhci-plat.h" +#include "xhci-rcar-regs.h" +#include "xhci-rzg3e-regs.h" +#include "xhci-rzv2m.h" + +#define XHCI_RCAR_FIRMWARE_NAME_V1 "r8a779x_usb3_v1.dlmem" +#define XHCI_RCAR_FIRMWARE_NAME_V3 "r8a779x_usb3_v3.dlmem" + +/* +* - The V3 firmware is for all R-Car Gen3 +* - The V2 firmware is possible to use on R-Car Gen2. However, the V2 causes +* performance degradation. So, this driver continues to use the V1 if R-Car +* Gen2. +* - The V1 firmware is impossible to use on R-Car Gen3. +*/ +MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V1); +MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V3); + +static void xhci_rcar_start(struct usb_hcd *hcd) +{ + u32 temp; + + if (hcd->regs != NULL) { + /* Interrupt Enable */ + temp = readl(hcd->regs + RCAR_USB3_INT_ENA); + temp |= RCAR_USB3_INT_ENA_VAL; + writel(temp, hcd->regs + RCAR_USB3_INT_ENA); + } +} + +static void xhci_rcar_gen2_start(struct usb_hcd *hcd) +{ + if (hcd->regs != NULL) { + xhci_rcar_start(hcd); + + /* LCLK Select */ + writel(RCAR_USB3_LCLK_ENA_VAL, hcd->regs + RCAR_USB3_LCLK); + /* USB3.0 Configuration */ + writel(RCAR_USB3_CONF1_VAL, hcd->regs + RCAR_USB3_CONF1); + writel(RCAR_USB3_CONF2_VAL, hcd->regs + RCAR_USB3_CONF2); + writel(RCAR_USB3_CONF3_VAL, hcd->regs + RCAR_USB3_CONF3); + /* USB3.0 Polarity */ + writel(RCAR_USB3_RX_POL_VAL, hcd->regs + RCAR_USB3_RX_POL); + writel(RCAR_USB3_TX_POL_VAL, hcd->regs + RCAR_USB3_TX_POL); + } +} + +static void xhci_rzg3e_start(struct usb_hcd *hcd) +{ + u32 int_en; + + if (hcd->regs) { + /* Update the controller initial setting */ + writel(0x03130200, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(0)); + writel(0x00160200, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(1)); + writel(0x03150000, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(2)); + writel(0x03130200, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(3)); + writel(0x00180000, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(4)); + + /* Interrupt Enable */ + int_en = readl(hcd->regs + RZG3E_USB3_HOST_INTEN); + int_en |= RZG3E_USB3_HOST_INTEN_ENA; + writel(int_en, hcd->regs + RZG3E_USB3_HOST_INTEN); + } +} + +static int xhci_rzg3e_resume(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + return reset_control_deassert(xhci->reset); +} + +static int xhci_rzg3e_post_resume(struct usb_hcd *hcd) +{ + xhci_rzg3e_start(hcd); + + return 0; +} + +static int xhci_rzg3e_suspend(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + reset_control_assert(xhci->reset); + + return 0; +} + +static int xhci_rcar_download_firmware(struct usb_hcd *hcd) +{ + struct device *dev = hcd->self.controller; + void __iomem *regs = hcd->regs; + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + const struct firmware *fw; + int retval, index, j; + u32 data, val, temp; + + /* + * According to the datasheet, "Upon the completion of FW Download, + * there is no need to write or reload FW". + */ + if (readl(regs + RCAR_USB3_DL_CTRL) & RCAR_USB3_DL_CTRL_FW_SUCCESS) + return 0; + + /* request R-Car USB3.0 firmware */ + retval = request_firmware(&fw, priv->firmware_name, dev); + if (retval) + return retval; + + /* download R-Car USB3.0 firmware */ + temp = readl(regs + RCAR_USB3_DL_CTRL); + temp |= RCAR_USB3_DL_CTRL_ENABLE; + writel(temp, regs + RCAR_USB3_DL_CTRL); + + for (index = 0; index < fw->size; index += 4) { + /* to avoid reading beyond the end of the buffer */ + for (data = 0, j = 3; j >= 0; j--) { + if ((j + index) < fw->size) + data |= fw->data[index + j] << (8 * j); + } + writel(data, regs + RCAR_USB3_FW_DATA0); + temp = readl(regs + RCAR_USB3_DL_CTRL); + temp |= RCAR_USB3_DL_CTRL_FW_SET_DATA0; + writel(temp, regs + RCAR_USB3_DL_CTRL); + + retval = readl_poll_timeout_atomic(regs + RCAR_USB3_DL_CTRL, + val, !(val & RCAR_USB3_DL_CTRL_FW_SET_DATA0), + 1, 10000); + if (retval < 0) + break; + } + + temp = readl(regs + RCAR_USB3_DL_CTRL); + temp &= ~RCAR_USB3_DL_CTRL_ENABLE; + writel(temp, regs + RCAR_USB3_DL_CTRL); + + retval = readl_poll_timeout_atomic((regs + RCAR_USB3_DL_CTRL), + val, val & RCAR_USB3_DL_CTRL_FW_SUCCESS, 1, 10000); + + release_firmware(fw); + + return retval; +} + +static bool xhci_rcar_wait_for_pll_active(struct usb_hcd *hcd) +{ + int retval; + u32 val, mask = RCAR_USB3_AXH_STA_PLL_ACTIVE_MASK; + + retval = readl_poll_timeout_atomic(hcd->regs + RCAR_USB3_AXH_STA, + val, (val & mask) == mask, 1, 1000); + return !retval; +} + +/* This function needs to initialize a "phy" of usb before */ +static int xhci_rcar_init_quirk(struct usb_hcd *hcd) +{ + /* If hcd->regs is NULL, we don't just call the following function */ + if (!hcd->regs) + return 0; + + if (!xhci_rcar_wait_for_pll_active(hcd)) + return -ETIMEDOUT; + + return xhci_rcar_download_firmware(hcd); +} + +static int xhci_rcar_resume_quirk(struct usb_hcd *hcd) +{ + struct xhci_plat_priv *priv; + int ret; + + ret = xhci_rcar_download_firmware(hcd); + if (ret) + return ret; + + priv = hcd_to_xhci_priv(hcd); + priv->plat_start(hcd); + return 0; +} + +/* + * On R-Car Gen2 and Gen3, the AC64 bit (bit 0) of HCCPARAMS1 is set + * to 1. However, these SoCs don't support 64-bit address memory + * pointers. So, this driver clears the AC64 bit of xhci->hcc_params + * to call dma_set_coherent_mask(dev, DMA_BIT_MASK(32)) in + * xhci_gen_setup() by using the XHCI_NO_64BIT_SUPPORT quirk. + * + * And, since the firmware/internal CPU control the USBSTS.STS_HALT + * and the process speed is down when the roothub port enters U3, + * long delay for the handshake of STS_HALT is neeed in xhci_suspend() + * by using the XHCI_SLOW_SUSPEND quirk. + */ +static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen2 = { + .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V1, + .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_SLOW_SUSPEND, + .init_quirk = xhci_rcar_init_quirk, + .plat_start = xhci_rcar_gen2_start, + .resume_quirk = xhci_rcar_resume_quirk, +}; + +static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen3 = { + .firmware_name = XHCI_RCAR_FIRMWARE_NAME_V3, + .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_SLOW_SUSPEND, + .init_quirk = xhci_rcar_init_quirk, + .plat_start = xhci_rcar_start, + .resume_quirk = xhci_rcar_resume_quirk, +}; + +static const struct xhci_plat_priv xhci_plat_renesas_rzv2m = { + .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_SLOW_SUSPEND, + .init_quirk = xhci_rzv2m_init_quirk, + .plat_start = xhci_rzv2m_start, +}; + +static const struct xhci_plat_priv xhci_plat_renesas_rzg3e = { + .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_RESET_ON_RESUME | XHCI_SUSPEND_RESUME_CLKS, + .plat_start = xhci_rzg3e_start, + .suspend_quirk = xhci_rzg3e_suspend, + .resume_quirk = xhci_rzg3e_resume, + .post_resume_quirk = xhci_rzg3e_post_resume, +}; + +static const struct of_device_id usb_xhci_of_match[] = { + { + .compatible = "renesas,xhci-r8a7790", + .data = &xhci_plat_renesas_rcar_gen2, + }, { + .compatible = "renesas,xhci-r8a7791", + .data = &xhci_plat_renesas_rcar_gen2, + }, { + .compatible = "renesas,xhci-r8a7793", + .data = &xhci_plat_renesas_rcar_gen2, + }, { + .compatible = "renesas,xhci-r8a7795", + .data = &xhci_plat_renesas_rcar_gen3, + }, { + .compatible = "renesas,xhci-r8a7796", + .data = &xhci_plat_renesas_rcar_gen3, + }, { + .compatible = "renesas,r9a09g047-xhci", + .data = &xhci_plat_renesas_rzg3e, + }, { + .compatible = "renesas,rcar-gen2-xhci", + .data = &xhci_plat_renesas_rcar_gen2, + }, { + .compatible = "renesas,rcar-gen3-xhci", + .data = &xhci_plat_renesas_rcar_gen3, + }, { + .compatible = "renesas,rzv2m-xhci", + .data = &xhci_plat_renesas_rzv2m, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, usb_xhci_of_match); + +static int xhci_renesas_probe(struct platform_device *pdev) +{ + const struct xhci_plat_priv *priv_match; + + priv_match = of_device_get_match_data(&pdev->dev); + + return xhci_plat_probe(pdev, NULL, priv_match); +} + +static struct platform_driver usb_xhci_renesas_driver = { + .probe = xhci_renesas_probe, + .remove = xhci_plat_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "xhci-renesas-hcd", + .pm = &xhci_plat_pm_ops, + .of_match_table = usb_xhci_of_match, + }, +}; +module_platform_driver(usb_xhci_renesas_driver); + +MODULE_DESCRIPTION("xHCI Platform Host Controller Driver for Renesas R-Car and RZ"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c new file mode 100644 index 000000000..97a1b53c1 --- /dev/null +++ b/drivers/usb/host/xhci-ring.c @@ -0,0 +1,4476 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + */ + +/* + * 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 +#include +#include +#include + +#include "xhci.h" +#include "xhci-trace.h" + +static int queue_command(struct xhci_hcd *xhci, struct xhci_command *cmd, + u32 field1, u32 field2, + u32 field3, u32 field4, bool command_must_succeed); + +/* + * 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)); +} + +static union xhci_trb *xhci_dma_to_trb(struct xhci_segment *start_seg, + dma_addr_t dma, + struct xhci_segment **match_seg) +{ + struct xhci_segment *seg; + + xhci_for_each_ring_seg(start_seg, seg) { + if (in_range(dma, seg->dma, TRB_SEGMENT_SIZE)) { + if (match_seg) + *match_seg = seg; + return &seg->trbs[(dma - seg->dma) / sizeof(union xhci_trb)]; + } + } + + return NULL; +} + +static bool trb_is_noop(union xhci_trb *trb) +{ + return TRB_TYPE_NOOP_LE32(trb->generic.field[3]); +} + +static bool trb_is_link(union xhci_trb *trb) +{ + return TRB_TYPE_LINK_LE32(trb->link.control); +} + +static bool last_trb_on_seg(struct xhci_segment *seg, union xhci_trb *trb) +{ + return trb == &seg->trbs[TRBS_PER_SEGMENT - 1]; +} + +static bool last_trb_on_ring(struct xhci_ring *ring, + struct xhci_segment *seg, union xhci_trb *trb) +{ + return last_trb_on_seg(seg, trb) && (seg->next == ring->first_seg); +} + +static bool link_trb_toggles_cycle(union xhci_trb *trb) +{ + return le32_to_cpu(trb->link.control) & LINK_TOGGLE; +} + +static bool last_td_in_urb(struct xhci_td *td) +{ + struct urb_priv *urb_priv = td->urb->hcpriv; + + return urb_priv->num_tds_done == urb_priv->num_tds; +} + +static bool unhandled_event_trb(struct xhci_ring *ring) +{ + return ((le32_to_cpu(ring->dequeue->event_cmd.flags) & TRB_CYCLE) == + ring->cycle_state); +} + +static void inc_td_cnt(struct urb *urb) +{ + struct urb_priv *urb_priv = urb->hcpriv; + + urb_priv->num_tds_done++; +} + +static void trb_to_noop(union xhci_trb *trb, u32 noop_type, bool unchain_links) +{ + if (trb_is_link(trb)) { + if (unchain_links) + trb->link.control &= cpu_to_le32(~TRB_CHAIN); + } else { + trb->generic.field[0] = 0; + trb->generic.field[1] = 0; + trb->generic.field[2] = 0; + /* Preserve only the cycle bit of this TRB */ + trb->generic.field[3] &= cpu_to_le32(TRB_CYCLE); + trb->generic.field[3] |= cpu_to_le32(TRB_TYPE(noop_type)); + } +} + +static unsigned int trb_to_pos(struct xhci_segment *seg, union xhci_trb *trb) +{ + return seg->num * TRBS_PER_SEGMENT + (trb - seg->trbs); +} + +/* 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_segment **seg, + union xhci_trb **trb) +{ + if (trb_is_link(*trb) || last_trb_on_seg(*seg, *trb)) { + *seg = (*seg)->next; + *trb = ((*seg)->trbs); + } else { + (*trb)++; + } +} + +/* + * See Cycle bit rules. SW is the consumer for the event ring only. + */ +void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + unsigned int link_trb_count = 0; + + /* event ring doesn't have link trbs, check for last trb */ + if (ring->type == TYPE_EVENT) { + if (!last_trb_on_seg(ring->deq_seg, ring->dequeue)) { + ring->dequeue++; + return; + } + if (last_trb_on_ring(ring, ring->deq_seg, ring->dequeue)) + ring->cycle_state ^= 1; + ring->deq_seg = ring->deq_seg->next; + ring->dequeue = ring->deq_seg->trbs; + + trace_xhci_inc_deq(ring); + + return; + } + + /* All other rings have link trbs */ + if (!trb_is_link(ring->dequeue)) { + if (last_trb_on_seg(ring->deq_seg, ring->dequeue)) + xhci_warn(xhci, "Missing link TRB at end of segment\n"); + else + ring->dequeue++; + } + + while (trb_is_link(ring->dequeue)) { + ring->deq_seg = ring->deq_seg->next; + ring->dequeue = ring->deq_seg->trbs; + + trace_xhci_inc_deq(ring); + + if (link_trb_count++ > ring->num_segs) { + xhci_warn(xhci, "Ring is an endless link TRB loop\n"); + break; + } + } + return; +} + +/* + * If enqueue points at a link TRB, follow links until an ordinary TRB is reached. + * Toggle the cycle bit of passed link TRBs and optionally chain them. + */ +static void inc_enq_past_link(struct xhci_hcd *xhci, struct xhci_ring *ring, u32 chain) +{ + unsigned int link_trb_count = 0; + + while (trb_is_link(ring->enqueue)) { + + /* + * 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. + * + * On 0.95 and some 0.96 HCs the chain bit is set once at segment initalization + * and never changed here. On all others, modify it as requested by the caller. + */ + if (!xhci_link_chain_quirk(xhci, ring->type)) { + ring->enqueue->link.control &= cpu_to_le32(~TRB_CHAIN); + ring->enqueue->link.control |= cpu_to_le32(chain); + } + + /* Give this link TRB to the hardware */ + wmb(); + ring->enqueue->link.control ^= cpu_to_le32(TRB_CYCLE); + + /* Toggle the cycle bit after the last ring segment. */ + if (link_trb_toggles_cycle(ring->enqueue)) + ring->cycle_state ^= 1; + + ring->enq_seg = ring->enq_seg->next; + ring->enqueue = ring->enq_seg->trbs; + + trace_xhci_inc_enq(ring); + + if (link_trb_count++ > ring->num_segs) { + xhci_warn(xhci, "Link TRB loop at enqueue\n"); + break; + } + } +} + +/* + * See Cycle bit rules. SW is the consumer for the event ring only. + * + * 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). + * + * @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; + + chain = le32_to_cpu(ring->enqueue->generic.field[3]) & TRB_CHAIN; + + if (last_trb_on_seg(ring->enq_seg, ring->enqueue)) { + xhci_err(xhci, "Tried to move enqueue past ring segment\n"); + return; + } + + ring->enqueue++; + + /* + * If we are in the middle of a TD or the caller plans to enqueue more + * TDs as one transfer (eg. control), traverse any link TRBs right now. + * Otherwise, enqueue can stay on a link until the next prepare_ring(). + * This avoids enqueue entering deq_seg and simplifies ring expansion. + */ + if (trb_is_link(ring->enqueue) && (chain || more_trbs_coming)) + inc_enq_past_link(xhci, ring, chain); +} + +static bool dma_in_range(dma_addr_t dma, + struct xhci_segment *start_seg, union xhci_trb *start_trb, + struct xhci_segment *end_seg, union xhci_trb *end_trb) +{ + unsigned int pos, start, end; + struct xhci_segment *pos_seg; + union xhci_trb *pos_trb = xhci_dma_to_trb(start_seg, dma, &pos_seg); + + /* Is the trb dma address even part of the whole ring? */ + if (!pos_trb) + return false; + + pos = trb_to_pos(pos_seg, pos_trb); + start = trb_to_pos(start_seg, start_trb); + end = trb_to_pos(end_seg, end_trb); + + /* end position is smaller than start, search range wraps around */ + if (end < start) + return !(pos > end && pos < start); + + return (pos >= start && pos <= end); +} + +/* If the suspect DMA address is a TRB in this TD, this function returns true */ +static bool trb_in_td(struct xhci_td *td, dma_addr_t suspect_dma) +{ + return dma_in_range(suspect_dma, td->start_seg, td->start_trb, + td->end_seg, td->end_trb); +} + +/* + * Return number of free normal TRBs from enqueue to dequeue pointer on ring. + * Not counting an assumed link TRB at end of each TRBS_PER_SEGMENT sized segment. + * Only for transfer and command rings where driver is the producer, not for + * event rings. + */ +unsigned int xhci_num_trbs_free(struct xhci_ring *ring) +{ + struct xhci_segment *enq_seg = ring->enq_seg; + union xhci_trb *enq = ring->enqueue; + union xhci_trb *last_on_seg; + unsigned int free = 0; + int i = 0; + + /* Ring might be empty even if enq != deq if enq is left on a link trb */ + if (trb_is_link(enq)) { + enq_seg = enq_seg->next; + enq = enq_seg->trbs; + } + + /* Empty ring, common case, don't walk the segments */ + if (enq == ring->dequeue) + return ring->num_segs * (TRBS_PER_SEGMENT - 1); + + do { + if (ring->deq_seg == enq_seg && ring->dequeue >= enq) + return free + (ring->dequeue - enq); + last_on_seg = &enq_seg->trbs[TRBS_PER_SEGMENT - 1]; + free += last_on_seg - enq; + enq_seg = enq_seg->next; + enq = enq_seg->trbs; + } while (i++ < ring->num_segs); + + return free; +} + +/* + * 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. + * return number of new segments needed to ensure this. + */ + +static unsigned int xhci_ring_expansion_needed(struct xhci_hcd *xhci, struct xhci_ring *ring, + unsigned int num_trbs) +{ + struct xhci_segment *seg; + int trbs_past_seg; + int enq_used; + int new_segs; + + enq_used = ring->enqueue - ring->enq_seg->trbs; + + /* how many trbs will be queued past the enqueue segment? */ + trbs_past_seg = enq_used + num_trbs - (TRBS_PER_SEGMENT - 1); + + /* + * Consider expanding the ring already if num_trbs fills the current + * segment (i.e. trbs_past_seg == 0), not only when num_trbs goes into + * the next segment. Avoids confusing full ring with special empty ring + * case below + */ + if (trbs_past_seg < 0) + return 0; + + /* Empty ring special case, enqueue stuck on link trb while dequeue advanced */ + if (trb_is_link(ring->enqueue) && ring->enq_seg->next->trbs == ring->dequeue) + return 0; + + new_segs = 1 + (trbs_past_seg / (TRBS_PER_SEGMENT - 1)); + seg = ring->enq_seg; + + while (new_segs > 0) { + seg = seg->next; + if (seg == ring->deq_seg) { + xhci_dbg(xhci, "Adding %d trbs requires expanding ring by %d segments\n", + num_trbs, new_segs); + return new_segs; + } + new_segs--; + } + + return 0; +} + +/* 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"); + + trace_xhci_ring_host_doorbell(0, DB_VALUE_HOST); + + writel(DB_VALUE_HOST, &xhci->dba->doorbell[0]); + /* Flush PCI posted writes */ + readl(&xhci->dba->doorbell[0]); +} + +static bool xhci_mod_cmd_timer(struct xhci_hcd *xhci) +{ + return mod_delayed_work(system_percpu_wq, &xhci->cmd_timer, + msecs_to_jiffies(xhci->current_cmd->timeout_ms)); +} + +static struct xhci_command *xhci_next_queued_cmd(struct xhci_hcd *xhci) +{ + return list_first_entry_or_null(&xhci->cmd_list, struct xhci_command, + cmd_list); +} + +/* + * Turn all commands on command ring with status set to "aborted" to no-op trbs. + * If there are other commands waiting then restart the ring and kick the timer. + * This must be called with command ring stopped and xhci->lock held. + */ +static void xhci_handle_stopped_cmd_ring(struct xhci_hcd *xhci, + struct xhci_command *cur_cmd) +{ + struct xhci_command *i_cmd; + + /* Turn all aborted commands in list to no-ops, then restart */ + list_for_each_entry(i_cmd, &xhci->cmd_list, cmd_list) { + + if (i_cmd->status != COMP_COMMAND_ABORTED) + continue; + + i_cmd->status = COMP_COMMAND_RING_STOPPED; + + xhci_dbg(xhci, "Turn aborted command %p to no-op\n", + i_cmd->command_trb); + + trb_to_noop(i_cmd->command_trb, TRB_CMD_NOOP, false); + + /* + * caller waiting for completion is called when command + * completion event is received for these no-op commands + */ + } + + xhci->cmd_ring_state = CMD_RING_STATE_RUNNING; + + /* ring command ring doorbell to restart the command ring */ + if ((xhci->cmd_ring->dequeue != xhci->cmd_ring->enqueue) && + !(xhci->xhc_state & XHCI_STATE_DYING)) { + xhci->current_cmd = cur_cmd; + if (cur_cmd) + xhci_mod_cmd_timer(xhci); + xhci_ring_cmd_db(xhci); + } +} + +/* Must be called with xhci->lock held, releases and acquires lock back */ +static int xhci_abort_cmd_ring(struct xhci_hcd *xhci, unsigned long flags) +{ + struct xhci_segment *new_seg = xhci->cmd_ring->deq_seg; + union xhci_trb *new_deq = xhci->cmd_ring->dequeue; + u64 crcr; + int ret; + + xhci_dbg(xhci, "Abort command ring\n"); + + reinit_completion(&xhci->cmd_ring_stop_completion); + + /* + * The control bits like command stop, abort are located in lower + * dword of the command ring control register. + * Some controllers require all 64 bits to be written to abort the ring. + * Make sure the upper dword is valid, pointing to the next command, + * avoiding corrupting the command ring pointer in case the command ring + * is stopped by the time the upper dword is written. + */ + next_trb(&new_seg, &new_deq); + if (trb_is_link(new_deq)) + next_trb(&new_seg, &new_deq); + + crcr = xhci_trb_virt_to_dma(new_seg, new_deq); + xhci_write_64(xhci, crcr | CMD_RING_ABORT, &xhci->op_regs->cmd_ring); + + /* Section 4.6.1.2 of xHCI 1.0 spec says software should also time the + * completion of the Command Abort operation. If CRR is not negated in 5 + * seconds then driver handles it as if host died (-ENODEV). + * In the future we should distinguish between -ENODEV and -ETIMEDOUT + * and try to recover a -ETIMEDOUT with a host controller reset. + */ + ret = xhci_handshake(&xhci->op_regs->cmd_ring, + CMD_RING_RUNNING, 0, 5 * 1000 * 1000); + if (ret < 0) { + xhci_err(xhci, "Abort failed to stop command ring: %d\n", ret); + xhci_halt(xhci); + xhci_hc_died(xhci); + return ret; + } + /* + * Writing the CMD_RING_ABORT bit should cause a cmd completion event, + * however on some host hw the CMD_RING_RUNNING bit is correctly cleared + * but the completion event in never sent. Wait 2 secs (arbitrary + * number) to handle those cases after negation of CMD_RING_RUNNING. + */ + spin_unlock_irqrestore(&xhci->lock, flags); + ret = wait_for_completion_timeout(&xhci->cmd_ring_stop_completion, + msecs_to_jiffies(2000)); + spin_lock_irqsave(&xhci->lock, flags); + if (!ret) { + xhci_dbg(xhci, "No stop event for abort, ring start fail?\n"); + xhci_cleanup_command_queue(xhci); + } else { + xhci_handle_stopped_cmd_ring(xhci, xhci_next_queued_cmd(xhci)); + } + return 0; +} + +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. + */ + if (ep_state & (EP_STOP_CMD_PENDING | SET_DEQ_PENDING | EP_HALTED | + EP_CLEARING_TT | EP_DROP_PENDING)) + return; + + trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id)); + + writel(DB_VALUE(ep_index, stream_id), db_addr); + /* flush the write */ + readl(db_addr); +} + +/* Ring the doorbell for any rings with pending URBs */ +void xhci_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); + } +} + +static struct xhci_virt_ep *xhci_get_virt_ep(struct xhci_hcd *xhci, + unsigned int slot_id, + unsigned int ep_index) +{ + if (slot_id == 0 || slot_id > xhci->max_slots) { + xhci_warn(xhci, "Invalid slot_id %u\n", slot_id); + return NULL; + } + if (ep_index >= EP_CTX_PER_DEV) { + xhci_warn(xhci, "Invalid endpoint index %u\n", ep_index); + return NULL; + } + if (!xhci->devs[slot_id]) { + xhci_warn(xhci, "No xhci virt device for slot_id %u\n", slot_id); + return NULL; + } + + return &xhci->devs[slot_id]->eps[ep_index]; +} + +static struct xhci_ring *xhci_virt_ep_to_ring(struct xhci_hcd *xhci, + struct xhci_virt_ep *ep, + unsigned int stream_id) +{ + /* common case, no streams */ + if (!(ep->ep_state & EP_HAS_STREAMS)) + return ep->ring; + + if (!ep->stream_info) + return NULL; + + if (stream_id == 0 || stream_id >= ep->stream_info->num_streams) { + xhci_warn(xhci, "Invalid stream_id %u request for slot_id %u ep_index %u\n", + stream_id, ep->vdev->slot_id, ep->ep_index); + return NULL; + } + + return ep->stream_info->stream_rings[stream_id]; +} + +/* Get the right ring for the given slot_id, ep_index and stream_id. + * If the endpoint supports streams, boundary check the URB's stream ID. + * If the endpoint doesn't support streams, return the singular endpoint ring. + */ +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_get_virt_ep(xhci, slot_id, ep_index); + if (!ep) + return NULL; + + return xhci_virt_ep_to_ring(xhci, ep, stream_id); +} + + +/* + * Get the hw dequeue pointer xHC stopped on, either directly from the + * endpoint context, or if streams are in use from the stream context. + * The returned hw_dequeue contains the lowest four bits with cycle state + * and possbile stream context type. + */ +static u64 xhci_get_hw_deq(struct xhci_hcd *xhci, struct xhci_virt_device *vdev, + unsigned int ep_index, unsigned int stream_id) +{ + struct xhci_ep_ctx *ep_ctx; + struct xhci_stream_ctx *st_ctx; + struct xhci_virt_ep *ep; + + ep = &vdev->eps[ep_index]; + + if (ep->ep_state & EP_HAS_STREAMS) { + st_ctx = &ep->stream_info->stream_ctx_array[stream_id]; + return le64_to_cpu(st_ctx->stream_ring); + } + ep_ctx = xhci_get_ep_ctx(xhci, vdev->out_ctx, ep_index); + return le64_to_cpu(ep_ctx->deq); +} + +/* + * Move the endpoint dequeue pointer to the next queued TD on ring->td_list or + * to enqueue if no TDs are queued (empty ring) + * All cancelled TDs on ring->td_list should be moved to ep->cancelled_td_list + * before calling this function + */ +static int xhci_move_deq_to_next_td(struct xhci_hcd *xhci, + struct xhci_virt_ep *ep, + unsigned int stream_id) +{ + struct xhci_command *cmd; + struct xhci_ring *ring; + struct xhci_td *td; + dma_addr_t addr; + int new_cycle; + u32 trb_sct = 0; + int ret = 0; + + ring = xhci_virt_ep_to_ring(xhci, ep, stream_id); + if (!ring) { + xhci_warn(xhci, "WARN can't find new dequeue, invalid stream ID %u\n", + stream_id); + return -ENODEV; + } + + if ((ep->ep_state & SET_DEQ_PENDING)) { + xhci_warn(xhci, "Set TR Deq already pending\n"); + return -EBUSY; + } + + /* This function gets called from contexts where it cannot sleep */ + cmd = xhci_alloc_command(xhci, false, GFP_ATOMIC); + if (!cmd) { + xhci_warn(xhci, "Can't alloc Set TR Deq cmd\n"); + return -ENOMEM; + } + + /* + * Move dequeue to the beginning of next td, or to enqueue if ring is + * empty. Avoid moving dequeue to a link trb (empty ring) as it causes + * issues on some hosts. In that case advance the enqueue to next segment + * before moving dequeue to it + */ + + if (list_empty(&ring->td_list)) { + if (trb_is_link(ring->enqueue)) + inc_enq_past_link(xhci, ring, 0); + ep->queued_deq_seg = ring->enq_seg; + ep->queued_deq_ptr = ring->enqueue; + new_cycle = ring->cycle_state; + } else { + td = list_first_entry(&ring->td_list, struct xhci_td, td_list); + ep->queued_deq_seg = td->start_seg; + ep->queued_deq_ptr = td->start_trb; + new_cycle = le32_to_cpu(td->start_trb->generic.field[3]) & TRB_CYCLE; + } + + addr = xhci_trb_virt_to_dma(ep->queued_deq_seg, ep->queued_deq_ptr); + if (addr == 0) { + xhci_warn(xhci, "Can't find new dequeue dma of seg %p, ptr %p\n", + ep->queued_deq_seg, ep->queued_deq_ptr); + ret = -EINVAL; + goto err_out; + } + + if (stream_id) + trb_sct = SCT_FOR_TRB(SCT_PRI_TR); + ret = queue_command(xhci, cmd, + lower_32_bits(addr) | trb_sct | new_cycle, + upper_32_bits(addr), + STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(ep->vdev->slot_id) | + EP_INDEX_FOR_TRB(ep->ep_index) | TRB_TYPE(TRB_SET_DEQ), false); + if (ret < 0) + goto err_out; + + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Set TR Deq ptr 0x%llx, cycle %u\n", addr, new_cycle); + + /* + * 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. + */ + ep->ep_state |= SET_DEQ_PENDING; + xhci_ring_cmd_db(xhci); + + return 0; + +err_out: + xhci_free_command(xhci, cmd); + ep->queued_deq_seg = NULL; + ep->queued_deq_ptr = NULL; + + return ret; +} + +/* 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_virt_ep *ep, + struct xhci_td *td, bool flip_cycle) +{ + bool unchain_links; + struct xhci_segment *seg = td->start_seg; + union xhci_trb *trb = td->start_trb; + + /* link TRBs should now be unchained, but some old HCs expect otherwise */ + unchain_links = !xhci_link_chain_quirk(xhci, ep->ring ? ep->ring->type : TYPE_STREAM); + + while (1) { + trb_to_noop(trb, TRB_TR_NOOP, unchain_links); + + /* flip cycle if asked to */ + if (flip_cycle && trb != td->start_trb && trb != td->end_trb) + trb->generic.field[3] ^= cpu_to_le32(TRB_CYCLE); + + if (trb == td->end_trb) + break; + + next_trb(&seg, &trb); + } +} + +static void xhci_giveback_urb_in_irq(struct xhci_hcd *xhci, + struct xhci_td *cur_td, int status) +{ + struct urb *urb = cur_td->urb; + struct urb_priv *urb_priv = urb->hcpriv; + struct usb_hcd *hcd = bus_to_hcd(urb->dev->bus); + + 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(); + } + } + xhci_urb_free_priv(urb_priv); + usb_hcd_unlink_urb_from_ep(hcd, urb); + trace_xhci_urb_giveback(urb); + usb_hcd_giveback_urb(hcd, urb, status); +} + +static void xhci_unmap_td_bounce_buffer(struct xhci_hcd *xhci, + struct xhci_ring *ring, struct xhci_td *td) +{ + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + struct xhci_segment *seg = td->bounce_seg; + struct urb *urb = td->urb; + size_t len; + + if (!ring || !seg || !urb) + return; + + if (usb_urb_dir_out(urb)) { + dma_unmap_single(dev, seg->bounce_dma, ring->bounce_buf_len, + DMA_TO_DEVICE); + return; + } + + dma_unmap_single(dev, seg->bounce_dma, ring->bounce_buf_len, + DMA_FROM_DEVICE); + /* for in transfers we need to copy the data from bounce to sg */ + if (urb->num_sgs) { + len = sg_pcopy_from_buffer(urb->sg, urb->num_sgs, seg->bounce_buf, + seg->bounce_len, seg->bounce_offs); + if (len != seg->bounce_len) + xhci_warn(xhci, "WARN Wrong bounce buffer read length: %zu != %d\n", + len, seg->bounce_len); + } else { + memcpy(urb->transfer_buffer + seg->bounce_offs, seg->bounce_buf, + seg->bounce_len); + } + seg->bounce_len = 0; + seg->bounce_offs = 0; +} + +static void xhci_td_cleanup(struct xhci_hcd *xhci, struct xhci_td *td, + struct xhci_ring *ep_ring, int status) +{ + struct urb *urb = NULL; + + /* Clean up the endpoint's TD list */ + urb = td->urb; + + /* if a bounce buffer was used to align this td then unmap it */ + xhci_unmap_td_bounce_buffer(xhci, ep_ring, td); + + /* 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 req %u and actual %u transfer length mismatch\n", + urb->transfer_buffer_length, urb->actual_length); + urb->actual_length = 0; + status = 0; + } + /* TD might be removed from td_list if we are giving back a cancelled URB */ + if (!list_empty(&td->td_list)) + list_del_init(&td->td_list); + /* Giving back a cancelled URB, or if a slated TD completed anyway */ + if (!list_empty(&td->cancelled_td_list)) + list_del_init(&td->cancelled_td_list); + + inc_td_cnt(urb); + /* Giveback the urb when all the tds are completed */ + if (last_td_in_urb(td)) { + 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); + + /* set isoc urb status to 0 just as EHCI, UHCI, and OHCI */ + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) + status = 0; + xhci_giveback_urb_in_irq(xhci, td, status); + } +} + +/* Give back previous TD and move on to the next TD. */ +static void xhci_dequeue_td(struct xhci_hcd *xhci, struct xhci_td *td, struct xhci_ring *ring, + u32 status) +{ + ring->dequeue = td->end_trb; + ring->deq_seg = td->end_seg; + inc_deq(xhci, ring); + + xhci_td_cleanup(xhci, td, ring, status); +} + +/* Complete the cancelled URBs we unlinked from td_list. */ +static void xhci_giveback_invalidated_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) +{ + struct xhci_ring *ring; + struct xhci_td *td, *tmp_td; + + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, + cancelled_td_list) { + + ring = xhci_urb_to_transfer_ring(xhci, td->urb); + + if (td->cancel_status == TD_CLEARED) { + xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n", + __func__, td->urb); + xhci_td_cleanup(xhci, td, ring, td->status); + } else { + xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + __func__, td->urb, td->cancel_status); + } + if (xhci->xhc_state & XHCI_STATE_DYING) + return; + } +} + +static int xhci_reset_halted_ep(struct xhci_hcd *xhci, unsigned int slot_id, + unsigned int ep_index, enum xhci_ep_reset_type reset_type) +{ + struct xhci_command *command; + int ret = 0; + + command = xhci_alloc_command(xhci, false, GFP_ATOMIC); + if (!command) { + ret = -ENOMEM; + goto done; + } + + xhci_dbg(xhci, "%s-reset ep %u, slot %u\n", + (reset_type == EP_HARD_RESET) ? "Hard" : "Soft", + ep_index, slot_id); + + ret = xhci_queue_reset_ep(xhci, command, slot_id, ep_index, reset_type); +done: + if (ret) + xhci_err(xhci, "ERROR queuing reset endpoint for slot %d ep_index %d, %d\n", + slot_id, ep_index, ret); + return ret; +} + +static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, + struct xhci_virt_ep *ep, + struct xhci_td *td, + enum xhci_ep_reset_type reset_type) +{ + struct xhci_port *rhub_port = ep->vdev->rhub_port; + unsigned int slot_id = ep->vdev->slot_id; + int err; + + /* + * Avoid resetting endpoint if link is inactive or device disonnected. + * Can cause host hang. + * Device will be reset to recover an inactive link, so don't do anything + */ + if (rhub_port->link_inactive || !rhub_port->connected) { + ep->ep_state |= EP_DROP_PENDING; + return -ENODEV; + } + + /* add td to cancelled list and let reset ep handler take care of it */ + if (reset_type == EP_HARD_RESET) { + ep->ep_state |= EP_HARD_CLEAR_TOGGLE; + if (td && list_empty(&td->cancelled_td_list)) { + list_add_tail(&td->cancelled_td_list, &ep->cancelled_td_list); + td->cancel_status = TD_HALTED; + } + } + + if (ep->ep_state & EP_HALTED) { + xhci_dbg(xhci, "Reset ep command for ep_index %d already pending\n", + ep->ep_index); + return 0; + } + + err = xhci_reset_halted_ep(xhci, slot_id, ep->ep_index, reset_type); + if (err) + return err; + + ep->ep_state |= EP_HALTED; + + xhci_ring_cmd_db(xhci); + + return 0; +} + +/* + * 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. + * + * only call this when ring is not in a running state + */ + +static int xhci_invalidate_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) +{ + struct xhci_td *td = NULL; + struct xhci_td *tmp_td = NULL; + struct xhci_td *cached_td = NULL; + struct xhci_ring *ring; + u64 hw_deq; + int err; + + /* + * This is not going to work if the hardware is changing its dequeue + * pointers as we look at them. Completion handler will call us later. + */ + if (ep->ep_state & SET_DEQ_PENDING) + return 0; + + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Removing canceled TD starting at 0x%llx (dma) in stream %u URB %p", + (unsigned long long)xhci_trb_virt_to_dma( + td->start_seg, td->start_trb), + td->urb->stream_id, td->urb); + list_del_init(&td->td_list); + ring = xhci_urb_to_transfer_ring(xhci, td->urb); + if (!ring) { + xhci_warn(xhci, "WARN Cancelled URB %p has invalid stream ID %u.\n", + td->urb, td->urb->stream_id); + continue; + } + + /* device disconnected or link error, ep will be dropped */ + if (ep->ep_state & EP_DROP_PENDING) { + td->cancel_status = TD_CLEARED; + continue; + } + + /* + * If a ring stopped on the TD we need to cancel then we have to + * move the xHC endpoint ring dequeue pointer past this TD. + * Rings halted due to STALL may show hw_deq is past the stalled + * TD, but still require a set TR Deq command to flush xHC cache. + */ + hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index, + td->urb->stream_id); + hw_deq &= TR_DEQ_PTR_MASK; + + if (td->cancel_status == TD_HALTED || trb_in_td(td, hw_deq)) { + switch (td->cancel_status) { + case TD_CLEARED: /* TD is already no-op */ + case TD_CLEARING_CACHE: /* set TR deq command already queued */ + break; + case TD_DIRTY: /* TD is cached, clear it */ + case TD_HALTED: + case TD_CLEARING_CACHE_DEFERRED: + if (cached_td) { + if (cached_td->urb->stream_id != td->urb->stream_id) { + /* Multiple streams case, defer move dq */ + xhci_dbg(xhci, + "Move dq deferred: stream %u URB %p\n", + td->urb->stream_id, td->urb); + td->cancel_status = TD_CLEARING_CACHE_DEFERRED; + break; + } + + /* Should never happen, but clear the TD if it does */ + xhci_warn(xhci, + "Found multiple active URBs %p and %p in stream %u?\n", + td->urb, cached_td->urb, + td->urb->stream_id); + td_to_noop(xhci, ep, cached_td, false); + cached_td->cancel_status = TD_CLEARED; + } + td_to_noop(xhci, ep, td, false); + td->cancel_status = TD_CLEARING_CACHE; + cached_td = td; + break; + } + } else { + td_to_noop(xhci, ep, td, false); + td->cancel_status = TD_CLEARED; + } + } + + /* If there's no need to move the dequeue pointer then we're done */ + if (!cached_td) + return 0; + + err = xhci_move_deq_to_next_td(xhci, ep, cached_td->urb->stream_id); + + if (err) { + /* Failed to move past cached td, just set cached TDs to no-op */ + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { + /* + * Deferred TDs need to have the deq pointer set after the above command + * completes, so if that failed we just give up on all of them (and + * complain loudly since this could cause issues due to caching). + */ + if (td->cancel_status != TD_CLEARING_CACHE && + td->cancel_status != TD_CLEARING_CACHE_DEFERRED) + continue; + xhci_warn(xhci, "Failed to clear cancelled cached URB %p, mark clear anyway\n", + td->urb); + td_to_noop(xhci, ep, td, false); + td->cancel_status = TD_CLEARED; + } + } + return 0; +} + +/* + * Erase queued TDs from transfer ring(s) and give back those the xHC didn't + * stop on. If necessary, queue commands to move the xHC off cancelled TDs it + * stopped on. Those will be given back later when the commands complete. + * + * Call under xhci->lock on a stopped endpoint. + */ +void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) +{ + xhci_invalidate_cancelled_tds(xhci, ep); + xhci_giveback_invalidated_tds(xhci, ep); +} + +/* + * Returns the TD the endpoint ring halted on. + * Only call for non-running rings without streams. + */ +static struct xhci_td *find_halted_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) +{ + struct xhci_td *td; + u64 hw_deq; + + if (!list_empty(&ep->ring->td_list)) { /* Not streams compatible */ + hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index, 0); + hw_deq &= TR_DEQ_PTR_MASK; + td = list_first_entry(&ep->ring->td_list, struct xhci_td, td_list); + if (trb_in_td(td, hw_deq)) + return td; + } + return NULL; +} + +/* + * 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 xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, + union xhci_trb *trb, u32 comp_code) +{ + unsigned int ep_index; + struct xhci_virt_ep *ep; + struct xhci_ep_ctx *ep_ctx; + struct xhci_td *td = NULL; + enum xhci_ep_reset_type reset_type; + struct xhci_command *command; + int err; + + if (unlikely(TRB_TO_SUSPEND_PORT(le32_to_cpu(trb->generic.field[3])))) { + if (!xhci->devs[slot_id]) + xhci_warn(xhci, "Stop endpoint command completion for disabled slot %u\n", + slot_id); + return; + } + + ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3])); + ep = xhci_get_virt_ep(xhci, slot_id, ep_index); + if (!ep) + return; + + ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep_index); + + trace_xhci_handle_cmd_stop_ep(ep_ctx); + + if (comp_code == COMP_CONTEXT_STATE_ERROR) { + /* + * If stop endpoint command raced with a halting endpoint we need to + * reset the host side endpoint first. + * If the TD we halted on isn't cancelled the TD should be given back + * with a proper error code, and the ring dequeue moved past the TD. + * If streams case we can't find hw_deq, or the TD we halted on so do a + * soft reset. + * + * Proper error code is unknown here, it would be -EPIPE if device side + * of enadpoit halted (aka STALL), and -EPROTO if not (transaction error) + * We use -EPROTO, if device is stalled it should return a stall error on + * next transfer, which then will return -EPIPE, and device side stall is + * noted and cleared by class driver. + */ + switch (GET_EP_CTX_STATE(ep_ctx)) { + case EP_STATE_HALTED: + xhci_dbg(xhci, "Stop ep completion raced with stall\n"); + /* + * If the halt happened before Stop Endpoint failed, its transfer event + * should have already been handled and Reset Endpoint should be pending. + */ + if (ep->ep_state & EP_HALTED) + goto reset_done; + + if (ep->ep_state & EP_HAS_STREAMS) { + reset_type = EP_SOFT_RESET; + } else { + reset_type = EP_HARD_RESET; + td = find_halted_td(xhci, ep); + if (td) + td->status = -EPROTO; + } + /* reset ep, reset handler cleans up cancelled tds */ + err = xhci_handle_halted_endpoint(xhci, ep, td, reset_type); + xhci_dbg(xhci, "Stop ep completion resetting ep, status %d\n", err); + if (err) + break; +reset_done: + /* Reset EP handler will clean up cancelled TDs */ + ep->ep_state &= ~EP_STOP_CMD_PENDING; + return; + case EP_STATE_STOPPED: + /* + * Per xHCI 4.6.9, Stop Endpoint command on a Stopped + * EP is a Context State Error, and EP stays Stopped. + * + * But maybe it failed on Halted, and somebody ran Reset + * Endpoint later. EP state is now Stopped and EP_HALTED + * still set because Reset EP handler will run after us. + */ + if (ep->ep_state & EP_HALTED) + break; + /* + * On some HCs EP state remains Stopped for some tens of + * us to a few ms or more after a doorbell ring, and any + * new Stop Endpoint fails without aborting the restart. + * This handler may run quickly enough to still see this + * Stopped state, but it will soon change to Running. + * + * Assume this bug on unexpected Stop Endpoint failures. + * Keep retrying until the EP starts and stops again or + * up to a timeout (a defective HC may never start, or a + * driver bug may cause stopping an already stopped EP). + */ + if (time_is_before_jiffies(ep->stop_time + msecs_to_jiffies(100))) + break; + fallthrough; + case EP_STATE_RUNNING: + /* Race, HW handled stop ep cmd before ep was running */ + xhci_dbg(xhci, "Stop ep completion ctx error, ctx_state %d\n", + GET_EP_CTX_STATE(ep_ctx)); + + command = xhci_alloc_command(xhci, false, GFP_ATOMIC); + if (!command) { + ep->ep_state &= ~EP_STOP_CMD_PENDING; + return; + } + xhci_queue_stop_endpoint(xhci, command, slot_id, ep_index, 0); + xhci_ring_cmd_db(xhci); + + return; + default: + break; + } + } + + /* link is inactive or disconnected, ep is not running and shouldn't be restarted */ + if (ep->vdev->rhub_port->link_inactive || !ep->vdev->rhub_port->connected) + ep->ep_state |= EP_DROP_PENDING; + + /* will queue a set TR deq if stopped on a cancelled, uncleared TD */ + xhci_invalidate_cancelled_tds(xhci, ep); + ep->ep_state &= ~EP_STOP_CMD_PENDING; + + /* Otherwise ring the doorbell(s) to restart queued transfers */ + xhci_giveback_invalidated_tds(xhci, ep); + xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); +} + +static void xhci_kill_ring_urbs(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + struct xhci_td *cur_td; + struct xhci_td *tmp; + + list_for_each_entry_safe(cur_td, tmp, &ring->td_list, 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_unmap_td_bounce_buffer(xhci, ring, cur_td); + + inc_td_cnt(cur_td->urb); + if (last_td_in_urb(cur_td)) + xhci_giveback_urb_in_irq(xhci, cur_td, -ESHUTDOWN); + } +} + +static void xhci_kill_endpoint_urbs(struct xhci_hcd *xhci, + int slot_id, int ep_index) +{ + struct xhci_td *cur_td; + struct xhci_td *tmp; + struct xhci_virt_ep *ep; + struct xhci_ring *ring; + + ep = xhci_get_virt_ep(xhci, slot_id, ep_index); + if (!ep) + return; + + if ((ep->ep_state & EP_HAS_STREAMS) || + (ep->ep_state & EP_GETTING_NO_STREAMS)) { + int stream_id; + + for (stream_id = 1; stream_id < ep->stream_info->num_streams; + stream_id++) { + ring = ep->stream_info->stream_rings[stream_id]; + if (!ring) + continue; + + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Killing URBs for slot ID %u, ep index %u, stream %u", + slot_id, ep_index, stream_id); + xhci_kill_ring_urbs(xhci, ring); + } + } else { + ring = ep->ring; + if (!ring) + return; + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Killing URBs for slot ID %u, ep index %u", + slot_id, ep_index); + xhci_kill_ring_urbs(xhci, ring); + } + + list_for_each_entry_safe(cur_td, tmp, &ep->cancelled_td_list, + cancelled_td_list) { + list_del_init(&cur_td->cancelled_td_list); + inc_td_cnt(cur_td->urb); + + if (last_td_in_urb(cur_td)) + xhci_giveback_urb_in_irq(xhci, cur_td, -ESHUTDOWN); + } +} + +/* + * host controller died, register read returns 0xffffffff + * Complete pending commands, mark them ABORTED. + * URBs need to be given back as usb core might be waiting with device locks + * held for the URBs to finish during device disconnect, blocking host remove. + * + * Call with xhci->lock held. + * lock is relased and re-acquired while giving back urb. + */ +void xhci_hc_died(struct xhci_hcd *xhci) +{ + bool notify; + int i, j; + + if (xhci->xhc_state & XHCI_STATE_DYING) + return; + + notify = !(xhci->xhc_state & XHCI_STATE_REMOVING); + if (notify) + xhci_err(xhci, "xHCI host controller not responding, assume dead\n"); + xhci->xhc_state |= XHCI_STATE_DYING; + + xhci_cleanup_command_queue(xhci); + + /* return any pending urbs, remove may be waiting for them */ + for (i = 0; i <= xhci->max_slots; i++) { + if (!xhci->devs[i]) + continue; + for (j = 0; j < 31; j++) + xhci_kill_endpoint_urbs(xhci, i, j); + } + + /* inform usb core hc died if PCI remove isn't already handling it */ + if (notify) + usb_hc_died(xhci_to_hcd(xhci)); +} + +/* + * 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 xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, + union xhci_trb *trb, u32 cmd_comp_code) +{ + unsigned int ep_index; + unsigned int stream_id; + struct xhci_ring *ep_ring; + struct xhci_virt_ep *ep; + struct xhci_ep_ctx *ep_ctx; + struct xhci_slot_ctx *slot_ctx; + struct xhci_stream_ctx *stream_ctx; + struct xhci_td *td, *tmp_td; + + 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])); + ep = xhci_get_virt_ep(xhci, slot_id, ep_index); + if (!ep) + return; + + ep_ring = xhci_virt_ep_to_ring(xhci, ep, stream_id); + if (!ep_ring) { + xhci_warn(xhci, "WARN Set TR deq ptr command for freed stream ID %u\n", + stream_id); + /* XXX: Harmless??? */ + goto cleanup; + } + + ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep_index); + slot_ctx = xhci_get_slot_ctx(xhci, ep->vdev->out_ctx); + trace_xhci_handle_cmd_set_deq(slot_ctx); + trace_xhci_handle_cmd_set_deq_ep(ep_ctx); + + if (ep->ep_state & EP_HAS_STREAMS) { + stream_ctx = &ep->stream_info->stream_ctx_array[stream_id]; + trace_xhci_handle_cmd_set_deq_stream(ep->stream_info, stream_id); + } + + if (cmd_comp_code != COMP_SUCCESS) { + unsigned int ep_state; + unsigned int slot_state; + + switch (cmd_comp_code) { + case COMP_TRB_ERROR: + xhci_warn(xhci, "WARN Set TR Deq Ptr cmd invalid because of stream ID configuration\n"); + break; + case COMP_CONTEXT_STATE_ERROR: + xhci_warn(xhci, "WARN Set TR Deq Ptr cmd failed due to incorrect slot or ep state.\n"); + ep_state = GET_EP_CTX_STATE(ep_ctx); + slot_state = le32_to_cpu(slot_ctx->dev_state); + slot_state = GET_SLOT_STATE(slot_state); + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Slot state = %u, EP state = %u", + slot_state, ep_state); + break; + case COMP_SLOT_NOT_ENABLED_ERROR: + 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", + cmd_comp_code); + 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 { + u64 deq; + /* 4.6.10 deq ptr is written to the stream ctx for streams */ + if (ep->ep_state & EP_HAS_STREAMS) { + deq = le64_to_cpu(stream_ctx->stream_ring) & TR_DEQ_PTR_MASK; + + /* + * Cadence xHCI controllers store some endpoint state + * information within Rsvd0 fields of Stream Endpoint + * context. This field is not cleared during Set TR + * Dequeue Pointer command which causes XDMA to skip + * over transfer ring and leads to data loss on stream + * pipe. + * To fix this issue driver must clear Rsvd0 field. + */ + if (xhci->quirks & XHCI_CDNS_SCTX_QUIRK) { + stream_ctx->reserved[0] = 0; + stream_ctx->reserved[1] = 0; + } + } else { + deq = le64_to_cpu(ep_ctx->deq) & TR_DEQ_PTR_MASK; + } + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Successful Set TR Deq Ptr cmd, deq = @%08llx", deq); + if (xhci_trb_virt_to_dma(ep->queued_deq_seg, + ep->queued_deq_ptr) == deq) { + /* Update the ring's dequeue segment and dequeue pointer + * to reflect the new position. + */ + ep_ring->deq_seg = ep->queued_deq_seg; + ep_ring->dequeue = ep->queued_deq_ptr; + } 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", + ep->queued_deq_seg, ep->queued_deq_ptr); + } + } + /* HW cached TDs cleared from cache, give them back */ + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, + cancelled_td_list) { + ep_ring = xhci_urb_to_transfer_ring(xhci, td->urb); + if (td->cancel_status == TD_CLEARING_CACHE) { + td->cancel_status = TD_CLEARED; + xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n", + __func__, td->urb); + xhci_td_cleanup(xhci, td, ep_ring, td->status); + } else { + xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + __func__, td->urb, td->cancel_status); + } + } +cleanup: + ep->ep_state &= ~SET_DEQ_PENDING; + ep->queued_deq_seg = NULL; + ep->queued_deq_ptr = NULL; + + /* Check for deferred or newly cancelled TDs */ + if (!list_empty(&ep->cancelled_td_list)) { + xhci_dbg(xhci, "%s: Pending TDs to clear, continuing with invalidation\n", + __func__); + xhci_invalidate_cancelled_tds(xhci, ep); + /* Try to restart the endpoint if all is done */ + xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + /* Start giving back any TDs invalidated above */ + xhci_giveback_invalidated_tds(xhci, ep); + } else { + /* Restart any rings with pending URBs */ + xhci_dbg(xhci, "%s: All TDs cleared, ring doorbell\n", __func__); + xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + } +} + +static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, + union xhci_trb *trb, u32 cmd_comp_code) +{ + struct xhci_virt_ep *ep; + struct xhci_ep_ctx *ep_ctx; + unsigned int ep_index; + + ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3])); + ep = xhci_get_virt_ep(xhci, slot_id, ep_index); + if (!ep) + return; + + ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep_index); + trace_xhci_handle_cmd_reset_ep(ep_ctx); + + /* This command will only fail if the endpoint wasn't halted, + * but we don't care. + */ + xhci_dbg_trace(xhci, trace_xhci_dbg_reset_ep, + "Ignoring reset ep completion code of %u", cmd_comp_code); + + /* Cleanup cancelled TDs as ep is stopped. May queue a Set TR Deq cmd */ + xhci_invalidate_cancelled_tds(xhci, ep); + + /* Clear our internal halted state */ + ep->ep_state &= ~EP_HALTED; + + xhci_giveback_invalidated_tds(xhci, ep); + + /* if this was a soft reset, then restart */ + if ((le32_to_cpu(trb->generic.field[3])) & TRB_TSP) + xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); +} + +static void xhci_handle_cmd_enable_slot(int slot_id, struct xhci_command *command, + u32 cmd_comp_code) +{ + if (cmd_comp_code == COMP_SUCCESS) + command->slot_id = slot_id; + else + command->slot_id = 0; +} + +static void xhci_handle_cmd_disable_slot(struct xhci_hcd *xhci, int slot_id, + u32 cmd_comp_code) +{ + struct xhci_virt_device *virt_dev; + struct xhci_slot_ctx *slot_ctx; + + virt_dev = xhci->devs[slot_id]; + if (!virt_dev) + return; + + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->out_ctx); + trace_xhci_handle_cmd_disable_slot(slot_ctx); + + if (xhci->quirks & XHCI_EP_LIMIT_QUIRK) + /* Delete default control endpoint resources */ + xhci_free_device_endpoint_resources(xhci, virt_dev, true); + if (cmd_comp_code == COMP_SUCCESS) { + xhci->dcbaa.ctx_array[slot_id] = 0; + xhci->devs[slot_id] = NULL; + } +} + +static void xhci_handle_cmd_config_ep(struct xhci_hcd *xhci, int slot_id) +{ + struct xhci_virt_device *virt_dev; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_ep_ctx *ep_ctx; + unsigned int ep_index; + u32 add_flags; + + /* + * Configure endpoint commands can come from the USB core configuration + * or alt setting changes, or when streams were being configured. + */ + + virt_dev = xhci->devs[slot_id]; + if (!virt_dev) + return; + ctrl_ctx = xhci_get_input_control_ctx(virt_dev->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "Could not get input context, bad type.\n"); + return; + } + + add_flags = le32_to_cpu(ctrl_ctx->add_flags); + + /* Input ctx add_flags are the endpoint index plus one */ + ep_index = xhci_last_valid_endpoint(add_flags) - 1; + + ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->out_ctx, ep_index); + trace_xhci_handle_cmd_config_ep(ep_ctx); + + return; +} + +static void xhci_handle_cmd_addr_dev(struct xhci_hcd *xhci, int slot_id) +{ + struct xhci_virt_device *vdev; + struct xhci_slot_ctx *slot_ctx; + + vdev = xhci->devs[slot_id]; + if (!vdev) + return; + slot_ctx = xhci_get_slot_ctx(xhci, vdev->out_ctx); + trace_xhci_handle_cmd_addr_dev(slot_ctx); +} + +static void xhci_handle_cmd_reset_dev(struct xhci_hcd *xhci, int slot_id) +{ + struct xhci_virt_device *vdev; + struct xhci_slot_ctx *slot_ctx; + + vdev = xhci->devs[slot_id]; + if (!vdev) { + xhci_warn(xhci, "Reset device command completion for disabled slot %u\n", + slot_id); + return; + } + slot_ctx = xhci_get_slot_ctx(xhci, vdev->out_ctx); + trace_xhci_handle_cmd_reset_dev(slot_ctx); + + xhci_dbg(xhci, "Completed reset device command.\n"); +} + +static void xhci_handle_cmd_nec_get_fw(struct xhci_hcd *xhci, + struct xhci_event_cmd *event) +{ + if (!(xhci->quirks & XHCI_NEC_HOST)) { + xhci_warn(xhci, "WARN NEC_GET_FW command on non-NEC host\n"); + return; + } + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "NEC firmware version %2x.%02x", + NEC_FW_MAJOR(le32_to_cpu(event->status)), + NEC_FW_MINOR(le32_to_cpu(event->status))); +} + +static void xhci_complete_del_and_free_cmd(struct xhci_command *cmd, u32 comp_code, u32 comp_param) +{ + list_del(&cmd->cmd_list); + + if (cmd->completion) { + cmd->status = comp_code; + cmd->comp_param = comp_param; + complete(cmd->completion); + } else { + kfree(cmd); + } +} + +void xhci_cleanup_command_queue(struct xhci_hcd *xhci) +{ + struct xhci_command *cur_cmd, *tmp_cmd; + xhci->current_cmd = NULL; + list_for_each_entry_safe(cur_cmd, tmp_cmd, &xhci->cmd_list, cmd_list) + xhci_complete_del_and_free_cmd(cur_cmd, COMP_COMMAND_ABORTED, 0); +} + +void xhci_handle_command_timeout(struct work_struct *work) +{ + struct xhci_hcd *xhci; + unsigned long flags; + char str[XHCI_MSG_MAX]; + u64 hw_ring_state; + u32 cmd_field3; + u32 usbsts; + + xhci = container_of(to_delayed_work(work), struct xhci_hcd, cmd_timer); + + spin_lock_irqsave(&xhci->lock, flags); + + /* + * If timeout work is pending, or current_cmd is NULL, it means we + * raced with command completion. Command is handled so just return. + */ + if (!xhci->current_cmd || delayed_work_pending(&xhci->cmd_timer)) { + spin_unlock_irqrestore(&xhci->lock, flags); + return; + } + + cmd_field3 = le32_to_cpu(xhci->current_cmd->command_trb->generic.field[3]); + usbsts = readl(&xhci->op_regs->status); + xhci_dbg(xhci, "Command timeout, USBSTS:%s\n", xhci_decode_usbsts(str, usbsts)); + + /* Bail out and tear down xhci if a stop endpoint command failed */ + if (TRB_FIELD_TO_TYPE(cmd_field3) == TRB_STOP_RING) { + struct xhci_virt_ep *ep; + + xhci_warn(xhci, "xHCI host not responding to stop endpoint command\n"); + + ep = xhci_get_virt_ep(xhci, TRB_TO_SLOT_ID(cmd_field3), + TRB_TO_EP_INDEX(cmd_field3)); + if (ep) + ep->ep_state &= ~EP_STOP_CMD_PENDING; + + xhci_halt(xhci); + xhci_hc_died(xhci); + goto time_out_completed; + } + + /* mark this command to be cancelled */ + xhci->current_cmd->status = COMP_COMMAND_ABORTED; + + /* Make sure command ring is running before aborting it */ + hw_ring_state = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); + if (hw_ring_state == ~(u64)0) { + xhci_hc_died(xhci); + goto time_out_completed; + } + + if ((xhci->cmd_ring_state & CMD_RING_STATE_RUNNING) && + (hw_ring_state & CMD_RING_RUNNING)) { + /* Prevent new doorbell, and start command abort */ + xhci->cmd_ring_state = CMD_RING_STATE_ABORTED; + xhci_dbg(xhci, "Command timeout\n"); + xhci_abort_cmd_ring(xhci, flags); + goto time_out_completed; + } + + /* host removed. Bail out */ + if (xhci->xhc_state & XHCI_STATE_REMOVING) { + xhci_dbg(xhci, "host removed, ring start fail?\n"); + xhci_cleanup_command_queue(xhci); + + goto time_out_completed; + } + + /* command timeout on stopped ring, ring can't be aborted */ + xhci_dbg(xhci, "Command timeout on stopped ring\n"); + xhci_handle_stopped_cmd_ring(xhci, xhci->current_cmd); + +time_out_completed: + spin_unlock_irqrestore(&xhci->lock, flags); + return; +} + +static void handle_cmd_completion(struct xhci_hcd *xhci, + struct xhci_event_cmd *event) +{ + unsigned int slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); + u32 status = le32_to_cpu(event->status); + u64 cmd_dma; + dma_addr_t cmd_dequeue_dma; + u32 cmd_comp_code; + union xhci_trb *cmd_trb; + struct xhci_command *cmd; + u32 cmd_type; + + if (slot_id > xhci->max_slots) { + xhci_warn(xhci, "Invalid slot_id %u\n", slot_id); + return; + } + + cmd_dma = le64_to_cpu(event->cmd_trb); + cmd_trb = xhci->cmd_ring->dequeue; + + trace_xhci_handle_command(xhci->cmd_ring, &cmd_trb->generic, cmd_dma); + + cmd_comp_code = GET_COMP_CODE(le32_to_cpu(event->status)); + + /* If CMD ring stopped we own the trbs between enqueue and dequeue */ + if (cmd_comp_code == COMP_COMMAND_RING_STOPPED) { + complete_all(&xhci->cmd_ring_stop_completion); + return; + } + + cmd_dequeue_dma = xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, + cmd_trb); + /* + * Check whether the completion event is for our internal kept + * command. + */ + if (!cmd_dequeue_dma || cmd_dma != (u64)cmd_dequeue_dma) { + xhci_warn(xhci, + "ERROR mismatched command completion event\n"); + return; + } + + cmd = list_first_entry(&xhci->cmd_list, struct xhci_command, cmd_list); + + cancel_delayed_work(&xhci->cmd_timer); + + if (cmd->command_trb != xhci->cmd_ring->dequeue) { + xhci_err(xhci, + "Command completion event does not match command\n"); + return; + } + + /* + * Host aborted the command ring, check if the current command was + * supposed to be aborted, otherwise continue normally. + * The command ring is stopped now, but the xHC will issue a Command + * Ring Stopped event which will cause us to restart it. + */ + if (cmd_comp_code == COMP_COMMAND_ABORTED) { + xhci->cmd_ring_state = CMD_RING_STATE_STOPPED; + if (cmd->status == COMP_COMMAND_ABORTED) { + if (xhci->current_cmd == cmd) + xhci->current_cmd = NULL; + goto event_handled; + } + } + + cmd_type = TRB_FIELD_TO_TYPE(le32_to_cpu(cmd_trb->generic.field[3])); + switch (cmd_type) { + case TRB_ENABLE_SLOT: + xhci_handle_cmd_enable_slot(slot_id, cmd, cmd_comp_code); + break; + case TRB_DISABLE_SLOT: + xhci_handle_cmd_disable_slot(xhci, slot_id, cmd_comp_code); + break; + case TRB_CONFIG_EP: + if (!cmd->completion) + xhci_handle_cmd_config_ep(xhci, slot_id); + break; + case TRB_EVAL_CONTEXT: + break; + case TRB_ADDR_DEV: + xhci_handle_cmd_addr_dev(xhci, slot_id); + break; + case TRB_STOP_RING: + WARN_ON(slot_id != TRB_TO_SLOT_ID( + le32_to_cpu(cmd_trb->generic.field[3]))); + if (!cmd->completion) + xhci_handle_cmd_stop_ep(xhci, slot_id, cmd_trb, + cmd_comp_code); + break; + case TRB_SET_DEQ: + WARN_ON(slot_id != TRB_TO_SLOT_ID( + le32_to_cpu(cmd_trb->generic.field[3]))); + xhci_handle_cmd_set_deq(xhci, slot_id, cmd_trb, cmd_comp_code); + break; + case TRB_CMD_NOOP: + /* Is this an aborted command turned to NO-OP? */ + if (cmd->status == COMP_COMMAND_RING_STOPPED) + cmd_comp_code = COMP_COMMAND_RING_STOPPED; + break; + case TRB_RESET_EP: + WARN_ON(slot_id != TRB_TO_SLOT_ID( + le32_to_cpu(cmd_trb->generic.field[3]))); + xhci_handle_cmd_reset_ep(xhci, slot_id, cmd_trb, cmd_comp_code); + break; + case TRB_RESET_DEV: + /* SLOT_ID field in reset device cmd completion event TRB is 0. + * Use the SLOT_ID from the command TRB instead (xhci 4.6.11) + */ + slot_id = TRB_TO_SLOT_ID( + le32_to_cpu(cmd_trb->generic.field[3])); + xhci_handle_cmd_reset_dev(xhci, slot_id); + break; + case TRB_NEC_GET_FW: + xhci_handle_cmd_nec_get_fw(xhci, event); + break; + case TRB_GET_BW: + break; + default: + /* Skip over unknown commands on the event ring */ + xhci_info(xhci, "INFO unknown command type %d\n", cmd_type); + break; + } + + /* restart timer if this wasn't the last command */ + if (!list_is_singular(&xhci->cmd_list)) { + xhci->current_cmd = list_first_entry(&cmd->cmd_list, + struct xhci_command, cmd_list); + xhci_mod_cmd_timer(xhci); + } else if (xhci->current_cmd == cmd) { + xhci->current_cmd = NULL; + } + +event_handled: + xhci_complete_del_and_free_cmd(cmd, cmd_comp_code, COMP_PARAM(status)); + + inc_deq(xhci, xhci->cmd_ring); +} + +static void handle_vendor_event(struct xhci_hcd *xhci, + union xhci_trb *event, u32 trb_type) +{ + 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); +} + +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(le32_to_cpu(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); +} + +/* + * Quirk hanlder for errata seen on Cavium ThunderX2 processor XHCI + * Controller. + * As per ThunderX2errata-129 USB 2 device may come up as USB 1 + * If a connection to a USB 1 device is followed by another connection + * to a USB 2 device. + * + * Reset the PHY after the USB device is disconnected if device speed + * is less than HCD_USB3. + * Retry the reset sequence max of 4 times checking the PLL lock status. + * + */ +static void xhci_cavium_reset_phy_quirk(struct xhci_hcd *xhci) +{ + struct usb_hcd *hcd = xhci_to_hcd(xhci); + u32 pll_lock_check; + u32 retry_count = 4; + + do { + /* Assert PHY reset */ + writel(0x6F, hcd->regs + 0x1048); + udelay(10); + /* De-assert the PHY reset */ + writel(0x7F, hcd->regs + 0x1048); + udelay(200); + pll_lock_check = readl(hcd->regs + 0x1070); + } while (!(pll_lock_check & 0x1) && --retry_count); +} + +static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) +{ + struct xhci_virt_device *vdev = NULL; + struct xhci_bus_state *bus_state; + struct xhci_port *port; + struct usb_hcd *hcd; + bool bogus_port_status = false; + unsigned int hcd_portnum; + u32 cmd_reg; + u32 port_id; + u32 portsc; + u32 pls; + + /* 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"); + + port_id = GET_PORT_ID(le32_to_cpu(event->generic.field[0])); + + if ((port_id <= 0) || (port_id > xhci->max_ports)) { + xhci_warn(xhci, "Port change event with invalid port ID %d\n", + port_id); + return; + } + + port = &xhci->hw_ports[port_id - 1]; + if (!port || !port->rhub || port->hcd_portnum == DUPLICATE_ENTRY) { + xhci_warn(xhci, "Port change event, no port for port ID %u\n", + port_id); + bogus_port_status = true; + goto cleanup; + } + + if (port->slot_id) + vdev = xhci->devs[port->slot_id]; + + /* We might get interrupts after shared_hcd is removed */ + if (port->rhub == &xhci->usb3_rhub && xhci_get_usb3_hcd(xhci) == NULL) { + xhci_dbg(xhci, "ignore port event for removed USB3 hcd\n"); + bogus_port_status = true; + goto cleanup; + } + + hcd = port->rhub->hcd; + bus_state = &port->rhub->bus_state; + hcd_portnum = port->hcd_portnum; + portsc = xhci_portsc_readl(port); + pls = portsc & PORT_PLS_MASK; + + xhci_dbg(xhci, "Port change event, %d-%d, id %d, portsc: 0x%x\n", + hcd->self.busnum, hcd_portnum + 1, port_id, portsc); + + trace_xhci_handle_port_status(port, portsc); + + if (hcd->state == HC_STATE_SUSPENDED) { + xhci_dbg(xhci, "resume root hub\n"); + usb_hcd_resume_root_hub(hcd); + } + + /* + * Tag broken links to avoid retries while hub driver sorts it out. + * Link status is not relible while port is in reset. + */ + if (!(portsc & PORT_RESET)) { + port->link_inactive = (pls == XDEV_INACTIVE); + port->connected = !!(portsc & PORT_CONNECT); + } + + if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_RESUME) { + xhci_dbg(xhci, "port resume event for port %d\n", port_id); + + cmd_reg = readl(&xhci->op_regs->command); + if (!(cmd_reg & CMD_RUN)) { + xhci_warn(xhci, "xHC is not running.\n"); + goto cleanup; + } + + if (DEV_SUPERSPEED_ANY(portsc)) { + 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 << hcd_portnum; + xhci_test_and_clear_bit(xhci, port, PORT_PLC); + usb_hcd_start_port_resume(&hcd->self, hcd_portnum); + xhci_set_link_state(xhci, port, 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 if (!test_bit(hcd_portnum, &bus_state->resuming_ports)) { + xhci_dbg(xhci, "resume HS port %d\n", port_id); + port->resume_timestamp = jiffies + + msecs_to_jiffies(USB_RESUME_TIMEOUT); + set_bit(hcd_portnum, &bus_state->resuming_ports); + /* Do the rest in GetPortStatus after resume time delay. + * Avoid polling roothub status before that so that a + * usb device auto-resume latency around ~40ms. + */ + set_bit(HCD_FLAG_POLL_RH, &hcd->flags); + mod_timer(&hcd->rh_timer, + port->resume_timestamp); + usb_hcd_start_port_resume(&hcd->self, hcd_portnum); + bogus_port_status = true; + } + } + + if ((portsc & PORT_PLC) && + DEV_SUPERSPEED_ANY(portsc) && + ((portsc & PORT_PLS_MASK) == XDEV_U0 || + (portsc & PORT_PLS_MASK) == XDEV_U1 || + (portsc & PORT_PLS_MASK) == XDEV_U2)) { + xhci_dbg(xhci, "resume SS port %d finished\n", port_id); + complete(&port->u3exit_done); + /* We've just brought the device into U0/1/2 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. + */ + if (vdev) + xhci_ring_device(xhci, port->slot_id); + if (bus_state->port_remote_wakeup & (1 << hcd_portnum)) { + xhci_test_and_clear_bit(xhci, port, PORT_PLC); + usb_wakeup_notification(hcd->self.root_hub, + hcd_portnum + 1); + bogus_port_status = true; + goto cleanup; + } + } + + /* + * Check to see if xhci-hub.c is waiting on RExit to U0 transition (or + * RExit to a disconnect state). If so, let the driver know it's + * out of the RExit state. + */ + if (hcd->speed < HCD_USB3 && port->rexit_active) { + complete(&port->rexit_done); + port->rexit_active = false; + bogus_port_status = true; + goto cleanup; + } + + if (hcd->speed < HCD_USB3) { + xhci_test_and_clear_bit(xhci, port, PORT_PLC); + if ((xhci->quirks & XHCI_RESET_PLL_ON_DISCONNECT) && + (portsc & PORT_CSC) && !(portsc & PORT_CONNECT)) + xhci_cavium_reset_phy_quirk(xhci); + } + +cleanup: + + /* 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 usb%d port polling.\n", + __func__, hcd->self.busnum); + 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); +} + +static void xhci_clear_hub_tt_buffer(struct xhci_hcd *xhci, struct xhci_td *td, + struct xhci_virt_ep *ep) +{ + /* + * As part of low/full-speed endpoint-halt processing + * we must clear the TT buffer (USB 2.0 specification 11.17.5). + */ + if (td->urb->dev->tt && !usb_pipeint(td->urb->pipe) && + (td->urb->dev->tt->hub != xhci_to_hcd(xhci)->self.root_hub) && + !(ep->ep_state & EP_CLEARING_TT)) { + ep->ep_state |= EP_CLEARING_TT; + td->urb->ep->hcpriv = td->urb->dev; + if (usb_hub_clear_tt_buffer(td->urb)) + ep->ep_state &= ~EP_CLEARING_TT; + } +} + +/* + * Check if xhci internal endpoint state has gone to a "halt" state due to an + * error or stall, including default control pipe protocol stall. + * The internal halt needs to be cleared with a reset endpoint command. + * + * External device side is also halted in functional stall cases. Class driver + * will clear the device halt with a CLEAR_FEATURE(ENDPOINT_HALT) request later. + */ +static bool xhci_halted_host_endpoint(struct xhci_hcd *xhci, struct xhci_ep_ctx *ep_ctx, + unsigned int comp_code) +{ + int ep_type = CTX_TO_EP_TYPE(le32_to_cpu(ep_ctx->ep_info2)); + + switch (comp_code) { + case COMP_STALL_ERROR: + /* on xHCI this always halts, including protocol stall */ + return true; + case COMP_BABBLE_DETECTED_ERROR: + /* + * The 0.95 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 endpoint context if endpoint is halted. + */ + if (xhci->hci_version <= 0x95 && ep_type == CTRL_EP) + return GET_EP_CTX_STATE(ep_ctx) == EP_STATE_HALTED; + + fallthrough; + case COMP_USB_TRANSACTION_ERROR: + case COMP_SPLIT_TRANSACTION_ERROR: + /* these errors halt all non-isochronous endpoints */ + return ep_type != ISOC_IN_EP && ep_type != ISOC_OUT_EP; + } + + return false; +} + +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; +} + +static void finish_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, + struct xhci_ring *ep_ring, struct xhci_td *td, + u32 trb_comp_code) +{ + struct xhci_ep_ctx *ep_ctx; + + ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep->ep_index); + + switch (trb_comp_code) { + case COMP_STOPPED_LENGTH_INVALID: + case COMP_STOPPED_SHORT_PACKET: + case COMP_STOPPED: + /* + * The "Stop Endpoint" 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. + */ + return; + } + + if (xhci_halted_host_endpoint(xhci, ep_ctx, trb_comp_code)) { + /* + * xhci internal endpoint state will go to a "halt" state for + * any stall, including default control pipe protocol stall. + * To clear the host side halt we need to issue a reset endpoint + * command, followed by a set dequeue command to move past the + * TD. + * Class drivers clear the device side halt from a functional + * stall later. Hub TT buffer should only be cleared for FS/LS + * devices behind HS hubs for functional stalls. + */ + if (!(ep->ep_index == 0 && trb_comp_code == COMP_STALL_ERROR)) + xhci_clear_hub_tt_buffer(xhci, td, ep); + + xhci_handle_halted_endpoint(xhci, ep, td, EP_HARD_RESET); + + return; /* xhci_handle_halted_endpoint marked td cancelled */ + } + + xhci_dequeue_td(xhci, td, ep_ring, td->status); +} + +/* sum trb lengths from the first trb up to stop_trb, _excluding_ stop_trb */ +static u32 sum_trb_lengths(struct xhci_td *td, union xhci_trb *stop_trb) +{ + u32 sum; + union xhci_trb *trb = td->start_trb; + struct xhci_segment *seg = td->start_seg; + + for (sum = 0; trb != stop_trb; next_trb(&seg, &trb)) { + if (!trb_is_noop(trb) && !trb_is_link(trb)) + sum += TRB_LEN(le32_to_cpu(trb->generic.field[2])); + } + return sum; +} + +/* + * Process control tds, update urb status and actual_length. + */ +static void process_ctrl_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, + struct xhci_ring *ep_ring, struct xhci_td *td, + union xhci_trb *ep_trb, struct xhci_transfer_event *event) +{ + struct xhci_ep_ctx *ep_ctx; + u32 trb_comp_code; + u32 remaining, requested; + u32 trb_type; + + trb_type = TRB_FIELD_TO_TYPE(le32_to_cpu(ep_trb->generic.field[3])); + ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep->ep_index); + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + requested = td->urb->transfer_buffer_length; + remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + + switch (trb_comp_code) { + case COMP_SUCCESS: + if (trb_type != TRB_STATUS) { + xhci_warn(xhci, "WARN: Success on ctrl %s TRB without IOC set?\n", + (trb_type == TRB_DATA) ? "data" : "setup"); + td->status = -ESHUTDOWN; + break; + } + td->status = 0; + break; + case COMP_SHORT_PACKET: + td->status = 0; + break; + case COMP_STOPPED_SHORT_PACKET: + if (trb_type == TRB_DATA || trb_type == TRB_NORMAL) + td->urb->actual_length = remaining; + else + xhci_warn(xhci, "WARN: Stopped Short Packet on ctrl setup or status TRB\n"); + goto finish_td; + case COMP_STOPPED: + switch (trb_type) { + case TRB_SETUP: + td->urb->actual_length = 0; + goto finish_td; + case TRB_DATA: + case TRB_NORMAL: + td->urb->actual_length = requested - remaining; + goto finish_td; + case TRB_STATUS: + td->urb->actual_length = requested; + goto finish_td; + default: + xhci_warn(xhci, "WARN: unexpected TRB Type %d\n", + trb_type); + goto finish_td; + } + case COMP_STOPPED_LENGTH_INVALID: + goto finish_td; + default: + if (!xhci_halted_host_endpoint(xhci, ep_ctx, trb_comp_code)) + break; + xhci_dbg(xhci, "TRB error %u, halted endpoint index = %u\n", + trb_comp_code, ep->ep_index); + fallthrough; + case COMP_STALL_ERROR: + /* Did we transfer part of the data (middle) phase? */ + if (trb_type == TRB_DATA || trb_type == TRB_NORMAL) + td->urb->actual_length = requested - remaining; + else if (!td->urb_length_set) + td->urb->actual_length = 0; + goto finish_td; + } + + /* stopped at setup stage, no data transferred */ + if (trb_type == TRB_SETUP) + goto finish_td; + + /* + * if on data stage then update the actual_length of the URB and flag it + * as set, so it won't be overwritten in the event for the last TRB. + */ + if (trb_type == TRB_DATA || + trb_type == TRB_NORMAL) { + td->urb_length_set = true; + td->urb->actual_length = requested - remaining; + xhci_dbg(xhci, "Waiting for status stage event\n"); + return; + } + + /* at status stage */ + if (!td->urb_length_set) + td->urb->actual_length = requested; + +finish_td: + finish_td(xhci, ep, ep_ring, td, trb_comp_code); +} + +/* + * Process isochronous tds, update urb packet status and actual_length. + */ +static void process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, + struct xhci_ring *ep_ring, struct xhci_td *td, + union xhci_trb *ep_trb, struct xhci_transfer_event *event) +{ + struct urb_priv *urb_priv; + int idx; + struct usb_iso_packet_descriptor *frame; + u32 trb_comp_code; + bool sum_trbs_for_length = false; + u32 remaining, requested, ep_trb_len; + + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + urb_priv = td->urb->hcpriv; + idx = urb_priv->num_tds_done; + frame = &td->urb->iso_frame_desc[idx]; + requested = frame->length; + remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + ep_trb_len = TRB_LEN(le32_to_cpu(ep_trb->generic.field[2])); + + /* handle completion code */ + switch (trb_comp_code) { + case COMP_SUCCESS: + /* Don't overwrite status if TD had an error, see xHCI 4.9.1 */ + if (td->error_mid_td) + break; + if (remaining) + sum_trbs_for_length = true; + frame->status = 0; + break; + case COMP_SHORT_PACKET: + frame->status = 0; + sum_trbs_for_length = true; + break; + case COMP_BANDWIDTH_OVERRUN_ERROR: + frame->status = -ECOMM; + break; + case COMP_BABBLE_DETECTED_ERROR: + sum_trbs_for_length = true; + fallthrough; + case COMP_ISOCH_BUFFER_OVERRUN: + frame->status = -EOVERFLOW; + if (ep_trb != td->end_trb) + td->error_mid_td = true; + break; + case COMP_MISSED_SERVICE_ERROR: + frame->status = -EXDEV; + sum_trbs_for_length = true; + if (ep_trb != td->end_trb) + td->error_mid_td = true; + break; + case COMP_INCOMPATIBLE_DEVICE_ERROR: + case COMP_STALL_ERROR: + frame->status = -EPROTO; + break; + case COMP_USB_TRANSACTION_ERROR: + frame->status = -EPROTO; + sum_trbs_for_length = true; + if (ep_trb != td->end_trb) + td->error_mid_td = true; + break; + case COMP_STOPPED: + sum_trbs_for_length = true; + break; + case COMP_STOPPED_SHORT_PACKET: + /* field normally containing residue now contains transferred */ + frame->status = 0; + requested = remaining; + break; + case COMP_STOPPED_LENGTH_INVALID: + /* exclude stopped trb with invalid length from length sum */ + sum_trbs_for_length = true; + ep_trb_len = 0; + remaining = 0; + break; + default: + sum_trbs_for_length = true; + frame->status = -1; + break; + } + + if (td->urb_length_set) + goto finish_td; + + if (sum_trbs_for_length) + frame->actual_length = sum_trb_lengths(td, ep_trb) + + ep_trb_len - remaining; + else + frame->actual_length = requested; + + td->urb->actual_length += frame->actual_length; + +finish_td: + /* Don't give back TD yet if we encountered an error mid TD */ + if (td->error_mid_td && ep_trb != td->end_trb) { + xhci_dbg(xhci, "Error mid isoc TD, wait for final completion event\n"); + td->urb_length_set = true; + return; + } + finish_td(xhci, ep, ep_ring, td, trb_comp_code); +} + +/* + * Process bulk and interrupt tds, update urb status and actual_length. + */ +static void process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, + struct xhci_ring *ep_ring, struct xhci_td *td, + union xhci_trb *ep_trb, struct xhci_transfer_event *event) +{ + struct xhci_slot_ctx *slot_ctx; + u32 trb_comp_code; + u32 remaining, requested, ep_trb_len; + + slot_ctx = xhci_get_slot_ctx(xhci, ep->vdev->out_ctx); + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + ep_trb_len = TRB_LEN(le32_to_cpu(ep_trb->generic.field[2])); + requested = td->urb->transfer_buffer_length; + + switch (trb_comp_code) { + case COMP_SUCCESS: + ep->err_count = 0; + /* handle success with untransferred data as short packet */ + if (ep_trb != td->end_trb || remaining) { + xhci_warn(xhci, "WARN Successful completion on short TX\n"); + xhci_dbg(xhci, "ep %#x - asked for %d bytes, %d bytes untransferred\n", + td->urb->ep->desc.bEndpointAddress, + requested, remaining); + } + td->status = 0; + break; + case COMP_SHORT_PACKET: + ep->err_count = 0; + td->status = 0; + break; + case COMP_STOPPED_SHORT_PACKET: + td->urb->actual_length = remaining; + goto finish_td; + case COMP_STOPPED_LENGTH_INVALID: + /* stopped on ep trb with invalid length, exclude it */ + td->urb->actual_length = sum_trb_lengths(td, ep_trb); + goto finish_td; + case COMP_USB_TRANSACTION_ERROR: + if (xhci->quirks & XHCI_NO_SOFT_RETRY || + (ep->err_count++ > MAX_SOFT_RETRY) || + le32_to_cpu(slot_ctx->tt_info) & TT_SLOT) + break; + + td->status = 0; + + xhci_handle_halted_endpoint(xhci, ep, td, EP_SOFT_RESET); + return; + default: + /* do nothing */ + break; + } + + if (ep_trb == td->end_trb) + td->urb->actual_length = requested - remaining; + else + td->urb->actual_length = + sum_trb_lengths(td, ep_trb) + + ep_trb_len - remaining; +finish_td: + if (remaining > requested) { + xhci_warn(xhci, "bad transfer trb length %d in event trb\n", + remaining); + td->urb->actual_length = 0; + } + + finish_td(xhci, ep, ep_ring, td, trb_comp_code); +} + +/* Transfer events which don't point to a transfer TRB, see xhci 4.17.4 */ +static int handle_transferless_tx_event(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, + u32 trb_comp_code) +{ + switch (trb_comp_code) { + case COMP_STALL_ERROR: + case COMP_USB_TRANSACTION_ERROR: + case COMP_INVALID_STREAM_TYPE_ERROR: + case COMP_INVALID_STREAM_ID_ERROR: + xhci_dbg(xhci, "Stream transaction error ep %u no id\n", ep->ep_index); + if (ep->err_count++ > MAX_SOFT_RETRY) + xhci_handle_halted_endpoint(xhci, ep, NULL, EP_HARD_RESET); + else + xhci_handle_halted_endpoint(xhci, ep, NULL, EP_SOFT_RESET); + break; + case COMP_RING_UNDERRUN: + case COMP_RING_OVERRUN: + case COMP_STOPPED_LENGTH_INVALID: + break; + default: + xhci_err(xhci, "Transfer event %u for unknown stream ring slot %u ep %u\n", + trb_comp_code, ep->vdev->slot_id, ep->ep_index); + return -ENODEV; + } + return 0; +} + +static bool xhci_spurious_success_tx_event(struct xhci_hcd *xhci, + struct xhci_ring *ring) +{ + switch (ring->old_trb_comp_code) { + case COMP_SHORT_PACKET: + return xhci->quirks & XHCI_SPURIOUS_SUCCESS; + case COMP_USB_TRANSACTION_ERROR: + case COMP_BABBLE_DETECTED_ERROR: + case COMP_ISOCH_BUFFER_OVERRUN: + return xhci->quirks & XHCI_ETRON_HOST && + ring->type == TYPE_ISOC; + default: + return false; + } +} + +static struct xhci_td *find_td_by_dma(struct xhci_ring *ep_ring, dma_addr_t dma) +{ + struct xhci_td *td; + + if (dma) + list_for_each_entry(td, &ep_ring->td_list, td_list) + if (trb_in_td(td, dma)) + return td; + return NULL; +} + +/* + * 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_interrupter *ir, + struct xhci_transfer_event *event) +{ + 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 ep_trb_dma; + union xhci_trb *ep_trb; + int status = -EINPROGRESS; + struct xhci_ep_ctx *ep_ctx; + u32 trb_comp_code; + bool ring_xrun_event = false; + + slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); + ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + ep_trb_dma = le64_to_cpu(event->buffer); + + ep = xhci_get_virt_ep(xhci, slot_id, ep_index); + if (!ep) { + xhci_err(xhci, "ERROR Invalid Transfer event\n"); + goto err_out; + } + + ep_ring = xhci_dma_to_transfer_ring(ep, ep_trb_dma); + ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep_index); + + if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_DISABLED) { + xhci_err(xhci, + "ERROR Transfer event for disabled endpoint slot %u ep %u\n", + slot_id, ep_index); + goto err_out; + } + + if (!ep_ring) + return handle_transferless_tx_event(xhci, ep, trb_comp_code); + + /* find the transfer trb this events points to */ + ep_trb = xhci_dma_to_trb(ep_ring->deq_seg, ep_trb_dma, NULL); + + /* 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) { + trb_comp_code = COMP_SHORT_PACKET; + xhci_dbg(xhci, "Successful completion on short TX for slot %u ep %u with last td comp code %d\n", + slot_id, ep_index, ep_ring->old_trb_comp_code); + } + break; + case COMP_SHORT_PACKET: + break; + /* Completion codes for endpoint stopped state */ + case COMP_STOPPED: + xhci_dbg(xhci, "Stopped on Transfer TRB for slot %u ep %u\n", + slot_id, ep_index); + break; + case COMP_STOPPED_LENGTH_INVALID: + xhci_dbg(xhci, + "Stopped on No-op or Link TRB for slot %u ep %u\n", + slot_id, ep_index); + break; + case COMP_STOPPED_SHORT_PACKET: + xhci_dbg(xhci, + "Stopped with short packet transfer detected for slot %u ep %u\n", + slot_id, ep_index); + break; + /* Completion codes for endpoint halted state */ + case COMP_STALL_ERROR: + xhci_dbg(xhci, "Stalled endpoint for slot %u ep %u\n", slot_id, + ep_index); + status = -EPIPE; + break; + case COMP_SPLIT_TRANSACTION_ERROR: + xhci_dbg(xhci, "Split transaction error for slot %u ep %u\n", + slot_id, ep_index); + status = -EPROTO; + break; + case COMP_USB_TRANSACTION_ERROR: + xhci_dbg(xhci, "Transfer error for slot %u ep %u on endpoint\n", + slot_id, ep_index); + status = -EPROTO; + break; + case COMP_BABBLE_DETECTED_ERROR: + xhci_dbg(xhci, "Babble error for slot %u ep %u on endpoint\n", + slot_id, ep_index); + status = -EOVERFLOW; + break; + /* Completion codes for endpoint error state */ + case COMP_TRB_ERROR: + xhci_warn(xhci, + "WARN: TRB error for slot %u ep %u on endpoint\n", + slot_id, ep_index); + status = -EILSEQ; + break; + /* completion codes not indicating endpoint state change */ + case COMP_DATA_BUFFER_ERROR: + xhci_warn(xhci, + "WARN: HC couldn't access mem fast enough for slot %u ep %u\n", + slot_id, ep_index); + status = -ENOSR; + break; + case COMP_BANDWIDTH_OVERRUN_ERROR: + xhci_warn(xhci, + "WARN: bandwidth overrun event for slot %u ep %u on endpoint\n", + slot_id, ep_index); + break; + case COMP_ISOCH_BUFFER_OVERRUN: + xhci_warn(xhci, + "WARN: buffer overrun event for slot %u ep %u on endpoint", + slot_id, ep_index); + break; + case COMP_RING_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 slot %u ep %u\n", slot_id, ep_index); + ring_xrun_event = true; + break; + case COMP_RING_OVERRUN: + xhci_dbg(xhci, "Overrun event on slot %u ep %u\n", slot_id, ep_index); + ring_xrun_event = true; + break; + case COMP_MISSED_SERVICE_ERROR: + /* + * 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 for slot %u ep %u, set skip flag%s\n", + slot_id, ep_index, ep_trb_dma ? ", skip now" : ""); + break; + case COMP_NO_PING_RESPONSE_ERROR: + ep->skip = true; + xhci_dbg(xhci, + "No Ping response error for slot %u ep %u, Skip one Isoc TD\n", + slot_id, ep_index); + return 0; + + case COMP_INCOMPATIBLE_DEVICE_ERROR: + /* needs disable slot command to recover */ + xhci_warn(xhci, + "WARN: detect an incompatible device for slot %u ep %u", + slot_id, ep_index); + status = -EPROTO; + break; + default: + if (xhci_is_vendor_info_code(xhci, trb_comp_code)) { + status = 0; + break; + } + xhci_warn(xhci, + "ERROR Unknown event condition %u for slot %u ep %u , HC probably busted\n", + trb_comp_code, slot_id, ep_index); + if (ep->skip) + break; + return 0; + } + + /* + * xhci 4.10.2 states isoc endpoints should continue + * processing the next TD if there was an error mid TD. + * So host like NEC don't generate an event for the last + * isoc TRB even if the IOC flag is set. + * xhci 4.9.1 states that if there are errors in mult-TRB + * TDs xHC should generate an error for that TRB, and if xHC + * proceeds to the next TD it should genete an event for + * any TRB with IOC flag on the way. Other host follow this. + * + * We wait for the final IOC event, but if we get an event + * anywhere outside this TD, just give it back already. + */ + td = list_first_entry_or_null(&ep_ring->td_list, struct xhci_td, td_list); + + if (td && td->error_mid_td && !trb_in_td(td, ep_trb_dma)) { + xhci_dbg(xhci, "Missing TD completion event after mid TD error\n"); + xhci_dequeue_td(xhci, td, ep_ring, td->status); + } + + /* + * We don't know how many TDs were missed when ep_trb_dma is zero (as permitted by + * xHCI 1.0) or bogus. Bail out leaving ep->skip set, next event will sort it out. + */ + if (trb_comp_code == COMP_MISSED_SERVICE_ERROR && !find_td_by_dma(ep_ring, ep_trb_dma)) + return 0; + + if (list_empty(&ep_ring->td_list)) { + /* + * Don't print wanings if ring is empty due to a stopped endpoint generating an + * extra completion event if the device was suspended. Or, a event for the last TRB + * of a short TD we already got a short event for. The short TD is already removed + * from the TD list. + */ + if (trb_comp_code != COMP_STOPPED && + trb_comp_code != COMP_STOPPED_LENGTH_INVALID && + !ring_xrun_event && + !xhci_spurious_success_tx_event(xhci, ep_ring)) { + xhci_warn(xhci, "Event TRB for slot %u ep %u with no TDs queued\n", + slot_id, ep_index); + } + + ep->skip = false; + goto check_endpoint_halted; + } + + do { + td = list_first_entry(&ep_ring->td_list, struct xhci_td, + td_list); + + /* Is this TRB not part of the currently executing TD? */ + if (!trb_in_td(td, ep_trb_dma)) { + + if (ep->skip && usb_endpoint_xfer_isoc(&td->urb->ep->desc)) { + /* this event is unlikely to match any TD, don't skip them all */ + if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID) + return 0; + + /* + * TD was missed, skip it. Core already initialized frame->status + * to -EXDEV and frame->actual_length to 0, nothing more to do. + */ + xhci_dequeue_td(xhci, td, ep_ring, 0); + + if (!list_empty(&ep_ring->td_list)) { + if (ring_xrun_event) { + /* + * If we are here, we are on xHCI 1.0 host with no + * idea how many TDs were missed or where the xrun + * occurred. New TDs may have been added after the + * xrun, so skip only one TD to be safe. + */ + xhci_dbg(xhci, "Skipped one TD for slot %u ep %u", + slot_id, ep_index); + return 0; + } + continue; + } + + xhci_dbg(xhci, "All TDs skipped for slot %u ep %u. Clear skip flag.\n", + slot_id, ep_index); + ep->skip = false; + td = NULL; + goto check_endpoint_halted; + } + + /* TD was queued after xrun, maybe xrun was on a link, don't panic yet */ + if (ring_xrun_event) + return 0; + + /* + * Skip the Force Stopped Event. The 'ep_trb' 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 (trb_comp_code == COMP_STOPPED || + trb_comp_code == COMP_STOPPED_LENGTH_INVALID) { + return 0; + } + + /* + * Some hosts give a spurious success event after a short + * transfer or error on last TRB. Ignore it. + */ + if (xhci_spurious_success_tx_event(xhci, ep_ring)) { + xhci_dbg(xhci, "Spurious event dma %pad, comp_code %u after %u\n", + &ep_trb_dma, trb_comp_code, ep_ring->old_trb_comp_code); + ep_ring->old_trb_comp_code = 0; + return 0; + } + + /* HC is busted, give up! */ + goto debug_finding_td; + } + + if (ep->skip) { + xhci_dbg(xhci, + "Found td. Clear skip flag for slot %u ep %u.\n", + slot_id, ep_index); + ep->skip = false; + } + + /* + * 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); + + ep_ring->old_trb_comp_code = trb_comp_code; + + /* Get out if a TD was queued at enqueue after the xrun occurred */ + if (ring_xrun_event) + return 0; + + trace_xhci_handle_transfer(ep_ring, (struct xhci_generic_trb *) ep_trb, ep_trb_dma); + + /* + * No-op TRB could trigger interrupts in a case where a URB was killed + * and a STALL_ERROR happens right after the endpoint ring stopped. + * Reset the halted endpoint. Otherwise, the endpoint remains stalled + * indefinitely. + */ + + if (trb_is_noop(ep_trb)) + goto check_endpoint_halted; + + td->status = status; + + /* update the urb's actual_length and give back to the core */ + if (usb_endpoint_xfer_control(&td->urb->ep->desc)) + process_ctrl_td(xhci, ep, ep_ring, td, ep_trb, event); + else if (usb_endpoint_xfer_isoc(&td->urb->ep->desc)) + process_isoc_td(xhci, ep, ep_ring, td, ep_trb, event); + else + process_bulk_intr_td(xhci, ep, ep_ring, td, ep_trb, event); + return 0; + +check_endpoint_halted: + if (xhci_halted_host_endpoint(xhci, ep_ctx, trb_comp_code)) + xhci_handle_halted_endpoint(xhci, ep, td, EP_HARD_RESET); + + return 0; + +debug_finding_td: + xhci_err(xhci, "Event dma %pad for ep %d status %d not part of TD at %016llx - %016llx\n", + &ep_trb_dma, ep_index, trb_comp_code, + (unsigned long long)xhci_trb_virt_to_dma(td->start_seg, td->start_trb), + (unsigned long long)xhci_trb_virt_to_dma(td->end_seg, td->end_trb)); + + return -ESHUTDOWN; + +err_out: + xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n", + (unsigned long long) xhci_trb_virt_to_dma( + ir->event_ring->deq_seg, + ir->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)); + return -ENODEV; +} + +/* + * This function handles one OS-owned event on the event ring. It may drop + * xhci->lock between event processing (e.g. to pass up port status changes). + */ +static int xhci_handle_event_trb(struct xhci_hcd *xhci, struct xhci_interrupter *ir, + union xhci_trb *event) +{ + u32 trb_type; + + trace_xhci_handle_event(ir->event_ring, &event->generic, + xhci_trb_virt_to_dma(ir->event_ring->deq_seg, + ir->event_ring->dequeue)); + + /* + * Barrier between reading the TRB_CYCLE (valid) flag before, and any + * speculative reads of the event's flags/data below. + */ + rmb(); + trb_type = TRB_FIELD_TO_TYPE(le32_to_cpu(event->event_cmd.flags)); + /* FIXME: Handle more event types. */ + + switch (trb_type) { + case TRB_COMPLETION: + handle_cmd_completion(xhci, &event->event_cmd); + break; + case TRB_PORT_STATUS: + handle_port_status(xhci, event); + break; + case TRB_TRANSFER: + handle_tx_event(xhci, ir, &event->trans_event); + break; + case TRB_DEV_NOTE: + handle_device_notification(xhci, event); + break; + default: + if (trb_type >= TRB_VENDOR_DEFINED_LOW) + handle_vendor_event(xhci, event, trb_type); + else + xhci_warn(xhci, "ERROR unknown event type %d\n", trb_type); + } + /* 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 -ENODEV; + } + + return 0; +} + +/* + * Update Event Ring Dequeue Pointer: + * - When all events have finished + * - To avoid "Event Ring Full Error" condition + */ +void xhci_update_erst_dequeue(struct xhci_hcd *xhci, + struct xhci_interrupter *ir, + bool clear_ehb) +{ + u64 temp_64; + dma_addr_t deq; + + temp_64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); + deq = xhci_trb_virt_to_dma(ir->event_ring->deq_seg, + ir->event_ring->dequeue); + if (deq == 0) + xhci_warn(xhci, "WARN something wrong with SW event ring dequeue ptr\n"); + /* + * Per 4.9.4, Software writes to the ERDP register shall always advance + * the Event Ring Dequeue Pointer value. + */ + if ((temp_64 & ERST_PTR_MASK) == (deq & ERST_PTR_MASK) && !clear_ehb) + return; + + /* Update HC event ring dequeue pointer */ + temp_64 = ir->event_ring->deq_seg->num & ERST_DESI_MASK; + temp_64 |= deq & ERST_PTR_MASK; + + /* Clear the event handler busy flag (RW1C) */ + if (clear_ehb) + temp_64 |= ERST_EHB; + xhci_write_64(xhci, temp_64, &ir->ir_set->erst_dequeue); +} + +/* Clear the interrupt pending bit for a specific interrupter. */ +static void xhci_clear_interrupt_pending(struct xhci_interrupter *ir) +{ + if (!ir->ip_autoclear) { + u32 iman; + + iman = readl(&ir->ir_set->iman); + iman |= IMAN_IP; + writel(iman, &ir->ir_set->iman); + + /* Read operation to guarantee the write has been flushed from posted buffers */ + readl(&ir->ir_set->iman); + } +} + +/* + * Handle all OS-owned events on an interrupter event ring. It may drop + * and reaquire xhci->lock between event processing. + */ +static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir, + bool skip_events) +{ + int event_loop = 0; + int err = 0; + u64 temp; + + xhci_clear_interrupt_pending(ir); + + /* Event ring hasn't been allocated yet. */ + if (!ir->event_ring || !ir->event_ring->dequeue) { + xhci_err(xhci, "ERROR interrupter event ring not ready\n"); + return -ENOMEM; + } + + if (xhci->xhc_state & XHCI_STATE_DYING || + xhci->xhc_state & XHCI_STATE_HALTED) { + xhci_dbg(xhci, "xHCI dying, ignoring interrupt. Shouldn't IRQs be disabled?\n"); + + /* Clear the event handler busy flag (RW1C) */ + temp = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); + xhci_write_64(xhci, temp | ERST_EHB, &ir->ir_set->erst_dequeue); + return -ENODEV; + } + + /* Process all OS owned event TRBs on this event ring */ + while (unhandled_event_trb(ir->event_ring)) { + if (!skip_events) + err = xhci_handle_event_trb(xhci, ir, ir->event_ring->dequeue); + + /* + * If half a segment of events have been handled in one go then + * update ERDP, and force isoc trbs to interrupt more often + */ + if (event_loop++ > TRBS_PER_SEGMENT / 2) { + xhci_update_erst_dequeue(xhci, ir, false); + + if (ir->isoc_bei_interval > AVOID_BEI_INTERVAL_MIN) + ir->isoc_bei_interval = ir->isoc_bei_interval / 2; + + event_loop = 0; + } + + /* Update SW event ring dequeue pointer */ + inc_deq(xhci, ir->event_ring); + + if (err) + break; + } + + xhci_update_erst_dequeue(xhci, ir, true); + + return 0; +} + +/* + * Move the event ring dequeue pointer to skip events kept in the secondary + * event ring. This is used to ensure that pending events in the ring are + * acknowledged, so the xHCI HCD can properly enter suspend/resume. The + * secondary ring is typically maintained by an external component. + */ +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, + struct xhci_ring *ring, struct xhci_interrupter *ir) +{ + union xhci_trb *current_trb; + u64 erdp_reg; + dma_addr_t deq; + + /* disable irq, ack pending interrupt and ack all pending events */ + xhci_disable_interrupter(xhci, ir); + + /* last acked event trb is in erdp reg */ + erdp_reg = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); + deq = (dma_addr_t)(erdp_reg & ERST_PTR_MASK); + if (!deq) { + xhci_err(xhci, "event ring handling not required\n"); + return; + } + + current_trb = ir->event_ring->dequeue; + /* read cycle state of the last acked trb to find out CCS */ + ring->cycle_state = le32_to_cpu(current_trb->event_cmd.flags) & TRB_CYCLE; + + xhci_handle_events(xhci, ir, true); +} + +/* + * 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); + irqreturn_t ret = IRQ_HANDLED; + u32 status; + + spin_lock(&xhci->lock); + /* Check if the xHC generated the interrupt, or the irq is shared */ + status = readl(&xhci->op_regs->status); + if (status == ~(u32)0) { + xhci_hc_died(xhci); + goto out; + } + + if (!(status & STS_EINT)) { + ret = IRQ_NONE; + goto out; + } + + if (status & STS_HCE) { + xhci_warn(xhci, "WARNING: Host Controller Error\n"); + xhci_halt(xhci); + goto out; + } + + if (status & STS_FATAL) { + xhci_warn(xhci, "WARNING: Host System Error\n"); + xhci_halt(xhci); + goto out; + } + + /* + * Clear the op reg interrupt status first, + * so we can receive interrupts from other MSI-X interrupters. + * USBSTS bits are write 1 to clear. + */ + writel(STS_EINT, &xhci->op_regs->status); + + /* This is the handler of the primary interrupter */ + xhci_handle_events(xhci, xhci->interrupters[0], false); +out: + spin_unlock(&xhci->lock); + + return ret; +} + +irqreturn_t xhci_msi_irq(int irq, void *hcd) +{ + return xhci_irq(hcd); +} +EXPORT_SYMBOL_GPL(xhci_msi_irq); + +/**** 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); + /* make sure TRB is fully written before giving it to the controller */ + wmb(); + trb->field[3] = cpu_to_le32(field4); + + trace_xhci_queue_trb(ring, trb, + xhci_trb_virt_to_dma(ring->enq_seg, ring->enqueue)); + + inc_enq(xhci, ring, more_trbs_coming); +} + +/* + * Does various checks on the endpoint ring, and makes it ready to queue num_trbs. + * expand ring if it start to be 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 new_segs = 0; + + /* 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"); + break; + 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; + } + + if (ep_ring != xhci->cmd_ring) { + new_segs = xhci_ring_expansion_needed(xhci, ep_ring, num_trbs); + } else if (xhci_num_trbs_free(ep_ring) <= num_trbs) { + xhci_err(xhci, "Do not support expand command ring\n"); + return -ENOMEM; + } + + if (new_segs) { + xhci_dbg_trace(xhci, trace_xhci_dbg_ring_expansion, + "ERROR no room on ep ring, try ring expansion"); + if (xhci_ring_expansion(xhci, ep_ring, new_segs, mem_flags)) { + xhci_err(xhci, "Ring expansion failed\n"); + return -ENOMEM; + } + } + + /* Ensure that new TRBs won't overwrite a link */ + if (trb_is_link(ep_ring->enqueue)) + inc_enq_past_link(xhci, ep_ring, 0); + + if (last_trb_on_seg(ep_ring->enq_seg, ep_ring->enqueue)) { + xhci_warn(xhci, "Missing link TRB at end of ring segment\n"); + return -EINVAL; + } + + 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_triad_to_transfer_ring(xhci, xdev->slot_id, 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, GET_EP_CTX_STATE(ep_ctx), + 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->start_trb = ep_ring->enqueue; + + return 0; +} + +unsigned int count_trbs(u64 addr, u64 len) +{ + unsigned int num_trbs; + + num_trbs = DIV_ROUND_UP(len + (addr & (TRB_MAX_BUFF_SIZE - 1)), + TRB_MAX_BUFF_SIZE); + if (num_trbs == 0) + num_trbs++; + + return num_trbs; +} + +static inline unsigned int count_trbs_needed(struct urb *urb) +{ + return count_trbs(urb->transfer_dma, urb->transfer_buffer_length); +} + +static unsigned int count_sg_trbs_needed(struct urb *urb) +{ + struct scatterlist *sg; + unsigned int i, len, full_len, num_trbs = 0; + + full_len = urb->transfer_buffer_length; + + for_each_sg(urb->sg, sg, urb->num_mapped_sgs, i) { + len = sg_dma_len(sg); + num_trbs += count_trbs(sg_dma_address(sg), len); + len = min_t(unsigned int, len, full_len); + full_len -= len; + if (full_len == 0) + break; + } + + return num_trbs; +} + +static unsigned int count_isoc_trbs_needed(struct urb *urb, int i) +{ + u64 addr, len; + + addr = (u64) (urb->transfer_dma + urb->iso_frame_desc[i].offset); + len = urb->iso_frame_desc[i].length; + + return count_trbs(addr, len); +} + +static void check_trb_math(struct urb *urb, int running_total) +{ + if (unlikely(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); +} + +static void check_interval(struct urb *urb, struct xhci_ep_ctx *ep_ctx) +{ + 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) { + dev_dbg_ratelimited(&urb->dev->dev, + "Driver uses different interval (%d microframe%s) than xHCI (%d microframe%s)\n", + ep_interval, str_plural(ep_interval), + xhci_interval, str_plural(xhci_interval)); + 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; + } +} + +/* + * 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; + + ep_ctx = xhci_get_ep_ctx(xhci, xhci->devs[slot_id]->out_ctx, ep_index); + check_interval(urb, ep_ctx); + + return xhci_queue_bulk_tx(xhci, mem_flags, urb, slot_id, ep_index); +} + +/* + * 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 + * + * For xHCI 0.96 and older, TD size field should be the remaining bytes + * including this TRB, right shifted by 10 + * + * For all hosts it must fit in bits 21:17, so it can't be bigger than 31. + * This is taken care of in the TRB_TD_SIZE() macro + * + * The last TRB in a TD must have the TD size set to zero. + */ +static u32 xhci_td_remainder(struct xhci_hcd *xhci, int transferred, + int trb_buff_len, unsigned int td_total_len, + struct urb *urb, bool more_trbs_coming) +{ + u32 maxp, total_packet_count; + + /* MTK xHCI 0.96 contains some features from 1.0 */ + if (xhci->hci_version < 0x100 && !(xhci->quirks & XHCI_MTK_HOST)) + return ((td_total_len - transferred) >> 10); + + /* One TRB with a zero-length data packet. */ + if (!more_trbs_coming || (transferred == 0 && trb_buff_len == 0) || + trb_buff_len == td_total_len) + return 0; + + /* for MTK xHCI 0.96, TD size include this TRB, but not in 1.x */ + if ((xhci->quirks & XHCI_MTK_HOST) && (xhci->hci_version < 0x100)) + trb_buff_len = 0; + + maxp = xhci_usb_endpoint_maxp(urb->dev, urb->ep); + total_packet_count = DIV_ROUND_UP(td_total_len, maxp); + + /* Queueing functions don't count the current TRB into transferred */ + return (total_packet_count - ((transferred + trb_buff_len) / maxp)); +} + + +static int xhci_align_td(struct xhci_hcd *xhci, struct urb *urb, u32 enqd_len, + u32 *trb_buff_len, struct xhci_segment *seg) +{ + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + unsigned int unalign; + unsigned int max_pkt; + u32 new_buff_len; + size_t len; + + max_pkt = xhci_usb_endpoint_maxp(urb->dev, urb->ep); + unalign = (enqd_len + *trb_buff_len) % max_pkt; + + /* we got lucky, last normal TRB data on segment is packet aligned */ + if (unalign == 0) + return 0; + + xhci_dbg(xhci, "Unaligned %d bytes, buff len %d\n", + unalign, *trb_buff_len); + + /* is the last nornal TRB alignable by splitting it */ + if (*trb_buff_len > unalign) { + *trb_buff_len -= unalign; + xhci_dbg(xhci, "split align, new buff len %d\n", *trb_buff_len); + return 0; + } + + /* + * We want enqd_len + trb_buff_len to sum up to a number aligned to + * number which is divisible by the endpoint's wMaxPacketSize. IOW: + * (size of currently enqueued TRBs + remainder) % wMaxPacketSize == 0. + */ + new_buff_len = max_pkt - (enqd_len % max_pkt); + + if (new_buff_len > (urb->transfer_buffer_length - enqd_len)) + new_buff_len = (urb->transfer_buffer_length - enqd_len); + + /* create a max max_pkt sized bounce buffer pointed to by last trb */ + if (usb_urb_dir_out(urb)) { + if (urb->num_sgs) { + len = sg_pcopy_to_buffer(urb->sg, urb->num_sgs, + seg->bounce_buf, new_buff_len, enqd_len); + if (len != new_buff_len) + xhci_warn(xhci, "WARN Wrong bounce buffer write length: %zu != %d\n", + len, new_buff_len); + } else { + memcpy(seg->bounce_buf, urb->transfer_buffer + enqd_len, new_buff_len); + } + + seg->bounce_dma = dma_map_single(dev, seg->bounce_buf, + max_pkt, DMA_TO_DEVICE); + } else { + seg->bounce_dma = dma_map_single(dev, seg->bounce_buf, + max_pkt, DMA_FROM_DEVICE); + } + + if (dma_mapping_error(dev, seg->bounce_dma)) { + /* try without aligning. Some host controllers survive */ + xhci_warn(xhci, "Failed mapping bounce buffer, not aligning\n"); + return 0; + } + *trb_buff_len = new_buff_len; + seg->bounce_len = new_buff_len; + seg->bounce_offs = enqd_len; + + xhci_dbg(xhci, "Bounce align, new buff len %d\n", *trb_buff_len); + + return 1; +} + +/* 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 *ring; + struct urb_priv *urb_priv; + struct xhci_td *td; + struct xhci_generic_trb *start_trb; + struct scatterlist *sg = NULL; + bool more_trbs_coming = true; + bool need_zero_pkt = false; + bool first_trb = true; + unsigned int num_trbs; + unsigned int start_cycle, num_sgs = 0; + unsigned int enqd_len, block_len, trb_buff_len, full_len; + int sent_len, ret; + u32 field, length_field, remainder; + u64 addr, send_addr; + + ring = xhci_urb_to_transfer_ring(xhci, urb); + if (!ring) + return -EINVAL; + + full_len = urb->transfer_buffer_length; + /* If we have scatter/gather list, we use it. */ + if (urb->num_sgs && !(urb->transfer_flags & URB_DMA_MAP_SINGLE)) { + num_sgs = urb->num_mapped_sgs; + sg = urb->sg; + addr = (u64) sg_dma_address(sg); + block_len = sg_dma_len(sg); + num_trbs = count_sg_trbs_needed(urb); + } else { + num_trbs = count_trbs_needed(urb); + addr = (u64) urb->transfer_dma; + block_len = full_len; + } + ret = prepare_transfer(xhci, xhci->devs[slot_id], + ep_index, urb->stream_id, + num_trbs, urb, 0, mem_flags); + if (unlikely(ret < 0)) + return ret; + + urb_priv = urb->hcpriv; + + /* Deal with URB_ZERO_PACKET - need one more td/trb */ + if (urb->transfer_flags & URB_ZERO_PACKET && urb_priv->num_tds > 1) + need_zero_pkt = true; + + 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 = &ring->enqueue->generic; + start_cycle = ring->cycle_state; + send_addr = addr; + + /* Queue the TRBs, even if they are zero-length */ + for (enqd_len = 0; first_trb || enqd_len < full_len; + enqd_len += trb_buff_len) { + field = TRB_TYPE(TRB_NORMAL); + + /* TRB buffer should not cross 64KB boundaries */ + trb_buff_len = TRB_BUFF_LEN_UP_TO_BOUNDARY(addr); + trb_buff_len = min_t(unsigned int, trb_buff_len, block_len); + + if (enqd_len + trb_buff_len > full_len) + trb_buff_len = full_len - enqd_len; + + /* Don't change the cycle bit of the first TRB until later */ + if (first_trb) { + first_trb = false; + if (start_cycle == 0) + field |= TRB_CYCLE; + } else + field |= 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 (enqd_len + trb_buff_len < full_len) { + field |= TRB_CHAIN; + if (trb_is_link(ring->enqueue + 1)) { + if (xhci_align_td(xhci, urb, enqd_len, + &trb_buff_len, + ring->enq_seg)) { + send_addr = ring->enq_seg->bounce_dma; + /* assuming TD won't span 2 segs */ + td->bounce_seg = ring->enq_seg; + } + } + } + if (enqd_len + trb_buff_len >= full_len) { + field &= ~TRB_CHAIN; + field |= TRB_IOC; + more_trbs_coming = false; + td->end_trb = ring->enqueue; + td->end_seg = ring->enq_seg; + if (xhci_urb_suitable_for_idt(urb)) { + memcpy(&send_addr, urb->transfer_buffer, + trb_buff_len); + le64_to_cpus(&send_addr); + field |= TRB_IDT; + } + } + + /* 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. */ + remainder = xhci_td_remainder(xhci, enqd_len, trb_buff_len, + full_len, urb, more_trbs_coming); + + length_field = TRB_LEN(trb_buff_len) | + TRB_TD_SIZE(remainder) | + TRB_INTR_TARGET(0); + + queue_trb(xhci, ring, more_trbs_coming | need_zero_pkt, + lower_32_bits(send_addr), + upper_32_bits(send_addr), + length_field, + field); + addr += trb_buff_len; + sent_len = trb_buff_len; + + while (sg && sent_len >= block_len) { + /* New sg entry */ + --num_sgs; + sent_len -= block_len; + sg = sg_next(sg); + if (num_sgs != 0 && sg) { + block_len = sg_dma_len(sg); + addr = (u64) sg_dma_address(sg); + addr += sent_len; + } + } + block_len -= sent_len; + send_addr = addr; + } + + if (need_zero_pkt) { + ret = prepare_transfer(xhci, xhci->devs[slot_id], + ep_index, urb->stream_id, + 1, urb, 1, mem_flags); + urb_priv->td[1].end_trb = ring->enqueue; + urb_priv->td[1].end_seg = ring->enq_seg; + field = TRB_TYPE(TRB_NORMAL) | ring->cycle_state | TRB_IOC; + queue_trb(xhci, ring, 0, 0, 0, TRB_INTR_TARGET(0), field); + } + + check_trb_math(urb, enqd_len); + 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; + 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; + + if ((xhci->quirks & XHCI_ETRON_HOST) && + urb->dev->speed >= USB_SPEED_SUPER) { + /* + * If next available TRB is the Link TRB in the ring segment then + * enqueue a No Op TRB, this can prevent the Setup and Data Stage + * TRB to be breaked by the Link TRB. + */ + if (last_trb_on_seg(ep_ring->enq_seg, ep_ring->enqueue + 1)) { + field = TRB_TYPE(TRB_TR_NOOP) | ep_ring->cycle_state; + queue_trb(xhci, ep_ring, false, 0, 0, + TRB_INTR_TARGET(0), field); + } + } + + /* 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/1.1 6.4.1.2.1: Transfer Type field */ + if ((xhci->hci_version >= 0x100) || (xhci->quirks & XHCI_MTK_HOST)) { + 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); + + if (urb->transfer_buffer_length > 0) { + u32 length_field, remainder; + u64 addr; + + if (xhci_urb_suitable_for_idt(urb)) { + memcpy(&addr, urb->transfer_buffer, + urb->transfer_buffer_length); + le64_to_cpus(&addr); + field |= TRB_IDT; + } else { + addr = (u64) urb->transfer_dma; + } + + remainder = xhci_td_remainder(xhci, 0, + urb->transfer_buffer_length, + urb->transfer_buffer_length, + urb, 1); + length_field = TRB_LEN(urb->transfer_buffer_length) | + TRB_TD_SIZE(remainder) | + TRB_INTR_TARGET(0); + if (setup->bRequestType & USB_DIR_IN) + field |= TRB_DIR_IN; + queue_trb(xhci, ep_ring, true, + lower_32_bits(addr), + upper_32_bits(addr), + length_field, + field | ep_ring->cycle_state); + } + + /* Save the DMA address of the last TRB in the TD */ + td->end_trb = ep_ring->enqueue; + td->end_seg = ep_ring->enq_seg; + + /* 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; +} + +/* + * 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 urb *urb, unsigned int total_packet_count) +{ + unsigned int max_burst; + + if (xhci->hci_version < 0x100 || urb->dev->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 urb *urb, unsigned int total_packet_count) +{ + unsigned int max_burst; + unsigned int residue; + + if (xhci->hci_version < 0x100) + return 0; + + if (urb->dev->speed >= 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; + } + if (total_packet_count == 0) + return 0; + return total_packet_count - 1; +} + +/* Returns the Isochronous Scheduling Threshold in Microframes. 1 Frame is 8 Microframes. */ +static int xhci_ist_microframes(struct xhci_hcd *xhci) +{ + int ist = FIELD_GET(HCS_IST_VALUE, xhci->hcs_params2); + + if (xhci->hcs_params2 & HCS_IST_UNIT) + ist *= 8; + return ist; +} + + +static bool xhci_isoc_td_uses_frame_id(struct xhci_hcd *xhci, struct urb *urb, + struct xhci_virt_ep *ep, int i) +{ + if (urb->transfer_flags & URB_ISO_ASAP) + return false; + + if (xhci->hcc_params & HCC_CFC) + return true; + + /* set frame id for first TD of first URB in stream */ + if (ep->next_uframe == -1 && i == 0) + return true; + + return false; +} + +/* + * Check if frame is in the valid frame window, including start and end. + * If start > end then assume window wrapped around at a limit the frame + * value won't exceed. + */ +static bool xhci_frame_in_range(u32 frame, u32 start, u32 end) +{ + /* frame window end wrapped around */ + if (start > end) + return frame >= start || frame <= end; + + return frame >= start && frame <= end; +} + +/* + * Set the urb->start_frame of the URB. + * + * Returns microframe index of first TD + */ +static int xhci_get_isoc_start_frame(struct xhci_hcd *xhci, struct urb *urb, + struct xhci_virt_ep *ep) +{ + u32 curr_frame, start_uframe; + u32 urb_start, urb_end; + u32 win_start, win_end; + bool frame_unit; + int uinterval; + u32 mfindex; + int ist; + + /* check if urb uses frame units instead of microframes */ + frame_unit = (urb->dev->speed == USB_SPEED_FULL || + urb->dev->speed == USB_SPEED_LOW); + + uinterval = urb->interval; + if (frame_unit) + uinterval *= 8; + + /* get current microframe index and isoc scheduling threshold */ + mfindex = readl(&xhci->run_regs->microframe_index); + ist = xhci_ist_microframes(xhci); + + /* calculate valid frame window, in frame units, see xhci 4.11.2.5 */ + curr_frame = MFINDEX_TO_FRAME(mfindex); + win_start = (curr_frame + DIV_ROUND_UP_POW2(ist, 8) + 1) % MAX_FRAMES; + win_end = (curr_frame + 895) % MAX_FRAMES; + + /* Is this the first URB starting the whole isoc data flow? */ + if (ep->next_uframe < 0) { + /* align first URB to next interval boundary, or at last to full frame */ + start_uframe = mfindex + ist + XHCI_CFC_DELAY; + start_uframe = roundup(start_uframe, 8); + start_uframe = roundup(start_uframe, uinterval) % MAX_UFRAMES; + } else { + /* URB is mid stream and expected to handle the next frame */ + start_uframe = ep->next_uframe; + urb_start = start_uframe / 8; + urb_end = (start_uframe + urb->number_of_packets * uinterval) / 8; + urb_end %= MAX_FRAMES; + + if (!xhci_frame_in_range(urb_start, win_start, win_end)) + xhci_dbg(xhci, "Ill-timed isoc URB %p for start frame %d, range %d-%d\n", + urb, urb_start, win_start, win_end); + + if (!xhci_frame_in_range(urb_end, win_start, win_end)) + xhci_dbg(xhci, "Ill-timed isoc URB %p for end frame %d, range %d-%d\n", + urb, urb_start, win_start, win_end); + } + /* set urb->start_frame */ + urb->start_frame = frame_unit ? start_uframe / 8 : start_uframe; + + return start_uframe; +} + +/* Check if we should generate event interrupt for a TD in an isoc URB */ +static bool trb_block_event_intr(struct xhci_hcd *xhci, int num_tds, int i, + struct xhci_interrupter *ir) +{ + if (xhci->hci_version < 0x100) + return false; + /* always generate an event interrupt for the last TD */ + if (i == num_tds - 1) + return false; + /* + * If AVOID_BEI is set the host handles full event rings poorly, + * generate an event at least every 8th TD to clear the event ring + */ + if (i && ir->isoc_bei_interval && xhci->quirks & XHCI_AVOID_BEI) + return !!(i % ir->isoc_bei_interval); + + return true; +} + +/* 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_interrupter *ir; + 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; + struct xhci_virt_ep *xep; + int uinterval = urb->interval; + int start_uframe; + + xep = &xhci->devs[slot_id]->eps[ep_index]; + ep_ring = xhci->devs[slot_id]->eps[ep_index].ring; + ir = xhci->interrupters[0]; + + 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; + + if (urb->dev->speed == USB_SPEED_FULL || urb->dev->speed == USB_SPEED_LOW) + uinterval = urb->interval * 8; + + start_uframe = xhci_get_isoc_start_frame(xhci, urb, xep); + + /* Queue the TRBs for each TD, even if they are zero-length */ + for (i = 0; i < num_tds; i++) { + unsigned int total_pkt_count, max_pkt; + unsigned int burst_count, last_burst_pkt_count; + u32 sia_frame_id; + + 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; + max_pkt = xhci_usb_endpoint_maxp(urb->dev, urb->ep); + total_pkt_count = DIV_ROUND_UP(td_len, max_pkt); + + /* A zero-length transfer still involves at least one packet. */ + if (total_pkt_count == 0) + total_pkt_count++; + burst_count = xhci_get_burst_count(xhci, urb, total_pkt_count); + last_burst_pkt_count = xhci_get_last_burst_packet_count(xhci, + urb, total_pkt_count); + + trbs_per_td = count_isoc_trbs_needed(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]; + + + /* Choose SIA or frame ID based scheduling for this TD */ + if (xhci_isoc_td_uses_frame_id(xhci, urb, xep, i)) { + sia_frame_id = (start_uframe + i * uinterval) / 8; + sia_frame_id = TRB_FRAME_ID(sia_frame_id % MAX_FRAMES); + } else { + sia_frame_id = TRB_SIA; + } + + /* + * Set isoc specific data for the first TRB in a TD. + * Prevent HW from getting the TRBs by keeping the cycle state + * inverted in the first TDs isoc TRB. + */ + field = TRB_TYPE(TRB_ISOC) | + TRB_TLBPC(last_burst_pkt_count) | + sia_frame_id | + (i ? ep_ring->cycle_state : !start_cycle); + + /* xhci 1.1 with ETE uses TD_Size field for TBC, old is Rsvdz */ + if (!xep->use_extended_tbc) + field |= TRB_TBC(burst_count); + + /* fill the rest of the TRB fields, and remaining normal TRBs */ + for (j = 0; j < trbs_per_td; j++) { + u32 remainder = 0; + + /* only first TRB is isoc, overwrite otherwise */ + if (!first_trb) + field = TRB_TYPE(TRB_NORMAL) | + ep_ring->cycle_state; + + /* Only set interrupt on short packet for IN EPs */ + if (usb_urb_dir_in(urb)) + field |= TRB_ISP; + + /* Set the chain bit for all except the last TRB */ + if (j < trbs_per_td - 1) { + more_trbs_coming = true; + field |= TRB_CHAIN; + } else { + more_trbs_coming = false; + td->end_trb = ep_ring->enqueue; + td->end_seg = ep_ring->enq_seg; + field |= TRB_IOC; + if (trb_block_event_intr(xhci, num_tds, i, ir)) + field |= TRB_BEI; + } + /* Calculate TRB length */ + trb_buff_len = TRB_BUFF_LEN_UP_TO_BOUNDARY(addr); + if (trb_buff_len > td_remain_len) + trb_buff_len = td_remain_len; + + /* Set the TRB length, TD size, & interrupter fields. */ + remainder = xhci_td_remainder(xhci, running_total, + trb_buff_len, td_len, + urb, more_trbs_coming); + + length_field = TRB_LEN(trb_buff_len) | + TRB_INTR_TARGET(0); + + /* xhci 1.1 with ETE uses TD Size field for TBC */ + if (first_trb && xep->use_extended_tbc) + length_field |= TRB_TD_SIZE_TBC(burst_count); + else + length_field |= TRB_TD_SIZE(remainder); + first_trb = false; + + 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; + } + } + + xep->next_uframe = (start_uframe + num_tds * uinterval) % MAX_UFRAMES; + + 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->start_trb and td->start_seg are already set. + */ + urb_priv->td[0].end_trb = ep_ring->enqueue; + /* Every TRB except the first & last will have its cycle bit flipped. */ + td_to_noop(xhci, xep, &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].start_trb; + ep_ring->enq_seg = urb_priv->td[0].start_seg; + ep_ring->cycle_state = start_cycle; + 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. Use xhci frame_index to + * update urb->start_frame if URB_ISO_ASAP is set in transfer_flags or + * Contiguous Frame ID is not supported by HC. + */ +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; + struct xhci_virt_ep *xep; + int num_tds, num_trbs, i; + int ret; + + xdev = xhci->devs[slot_id]; + xep = &xhci->devs[slot_id]->eps[ep_index]; + 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(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, GET_EP_CTX_STATE(ep_ctx), + num_trbs, mem_flags); + if (ret) + return ret; + + /* + * Check interval value. This should be done before we start to + * calculate the start frame value. + */ + check_interval(urb, ep_ctx); + + /* + * Check if this starts the isoc data flow. Relies on hw setting ep ctx + * state after doorbell ring. Consider adding list_empty(td_list) check + */ + if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) + xep->next_uframe = -1; + + 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, struct xhci_command *cmd, + u32 field1, u32 field2, + u32 field3, u32 field4, bool command_must_succeed) +{ + int reserved_trbs = xhci->cmd_ring_reserved_trbs; + struct usb_hcd *hcd = xhci_to_hcd(xhci); + int ret; + + if ((xhci->xhc_state & XHCI_STATE_DYING) || + (xhci->xhc_state & XHCI_STATE_HALTED)) { + xhci_dbg(xhci, "xHCI dying or halted, can't queue_command. state: 0x%x\n", + xhci->xhc_state); + return -ESHUTDOWN; + } + + if (!HCD_HW_ACCESSIBLE(hcd)) { + xhci_warn(xhci, "Can't queue command, xHC not accessible\n"); + return -ESHUTDOWN; + } + + 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; + } + + cmd->command_trb = xhci->cmd_ring->enqueue; + + /* if there are no other commands queued we start the timeout timer */ + if (list_empty(&xhci->cmd_list)) { + xhci->current_cmd = cmd; + xhci_mod_cmd_timer(xhci); + } + + list_add_tail(&cmd->cmd_list, &xhci->cmd_list); + + 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, struct xhci_command *cmd, + u32 trb_type, u32 slot_id) +{ + return queue_command(xhci, cmd, 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, struct xhci_command *cmd, + dma_addr_t in_ctx_ptr, u32 slot_id, enum xhci_setup_dev setup) +{ + return queue_command(xhci, cmd, lower_32_bits(in_ctx_ptr), + upper_32_bits(in_ctx_ptr), 0, + TRB_TYPE(TRB_ADDR_DEV) | SLOT_ID_FOR_TRB(slot_id) + | (setup == SETUP_CONTEXT_ONLY ? TRB_BSR : 0), false); +} + +int xhci_queue_vendor_command(struct xhci_hcd *xhci, struct xhci_command *cmd, + u32 field1, u32 field2, u32 field3, u32 field4) +{ + return queue_command(xhci, cmd, field1, field2, field3, field4, false); +} + +/* Queue a reset device command TRB */ +int xhci_queue_reset_device(struct xhci_hcd *xhci, struct xhci_command *cmd, + u32 slot_id) +{ + return queue_command(xhci, cmd, 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, + struct xhci_command *cmd, dma_addr_t in_ctx_ptr, + u32 slot_id, bool command_must_succeed) +{ + return queue_command(xhci, cmd, 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 a get root hub port bandwidth command TRB */ +int xhci_queue_get_port_bw(struct xhci_hcd *xhci, + struct xhci_command *cmd, dma_addr_t in_ctx_ptr, + u8 dev_speed, bool command_must_succeed) +{ + return queue_command(xhci, cmd, lower_32_bits(in_ctx_ptr), + upper_32_bits(in_ctx_ptr), 0, + TRB_TYPE(TRB_GET_BW) | DEV_SPEED_FOR_TRB(dev_speed), + command_must_succeed); +} + +/* Queue an evaluate context command TRB */ +int xhci_queue_evaluate_context(struct xhci_hcd *xhci, struct xhci_command *cmd, + dma_addr_t in_ctx_ptr, u32 slot_id, bool command_must_succeed) +{ + return queue_command(xhci, cmd, 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, struct xhci_command *cmd, + int slot_id, unsigned int ep_index, int suspend) +{ + u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); + u32 trb_ep_index = EP_INDEX_FOR_TRB(ep_index); + u32 type = TRB_TYPE(TRB_STOP_RING); + u32 trb_suspend = SUSPEND_PORT_FOR_TRB(suspend); + + return queue_command(xhci, cmd, 0, 0, 0, + trb_slot_id | trb_ep_index | type | trb_suspend, false); +} + +int xhci_queue_reset_ep(struct xhci_hcd *xhci, struct xhci_command *cmd, + int slot_id, unsigned int ep_index, + enum xhci_ep_reset_type reset_type) +{ + u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); + u32 trb_ep_index = EP_INDEX_FOR_TRB(ep_index); + u32 type = TRB_TYPE(TRB_RESET_EP); + + if (reset_type == EP_SOFT_RESET) + type |= TRB_TSP; + + return queue_command(xhci, cmd, 0, 0, 0, + trb_slot_id | trb_ep_index | type, false); +} diff --git a/drivers/usb/host/xhci-rzg3e-regs.h b/drivers/usb/host/xhci-rzg3e-regs.h new file mode 100644 index 000000000..7a244a47b --- /dev/null +++ b/drivers/usb/host/xhci-rzg3e-regs.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __XHCI_RZG3E_H +#define __XHCI_RZG3E_H + +#define RZG3E_USB3_HOST_INTEN 0x1044 /* Interrupt Enable */ +#define RZG3E_USB3_HOST_U3P0PIPESC(x) (0x10c0 + (x) * 4) /* PIPE Status and Control Register */ + +#define RZG3E_USB3_HOST_INTEN_XHC BIT(0) +#define RZG3E_USB3_HOST_INTEN_HSE BIT(2) +#define RZG3E_USB3_HOST_INTEN_ENA (RZG3E_USB3_HOST_INTEN_XHC | RZG3E_USB3_HOST_INTEN_HSE) + +#endif /* __XHCI_RZG3E_H */ diff --git a/drivers/usb/host/xhci-rzv2m.c b/drivers/usb/host/xhci-rzv2m.c new file mode 100644 index 000000000..4f59867d7 --- /dev/null +++ b/drivers/usb/host/xhci-rzv2m.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xHCI host controller driver for RZ/V2M + * + * Copyright (C) 2022 Renesas Electronics Corporation + */ + +#include +#include "xhci.h" +#include "xhci-plat.h" +#include "xhci-rzv2m.h" + +#define RZV2M_USB3_INTEN 0x1044 /* Interrupt Enable */ + +#define RZV2M_USB3_INT_XHC_ENA BIT(0) +#define RZV2M_USB3_INT_HSE_ENA BIT(2) +#define RZV2M_USB3_INT_ENA_VAL (RZV2M_USB3_INT_XHC_ENA \ + | RZV2M_USB3_INT_HSE_ENA) + +int xhci_rzv2m_init_quirk(struct usb_hcd *hcd) +{ + struct device *dev = hcd->self.controller; + + rzv2m_usb3drd_reset(dev->parent, true); + + return 0; +} + +void xhci_rzv2m_start(struct usb_hcd *hcd) +{ + u32 int_en; + + if (hcd->regs) { + /* Interrupt Enable */ + int_en = readl(hcd->regs + RZV2M_USB3_INTEN); + int_en |= RZV2M_USB3_INT_ENA_VAL; + writel(int_en, hcd->regs + RZV2M_USB3_INTEN); + } +} diff --git a/drivers/usb/host/xhci-rzv2m.h b/drivers/usb/host/xhci-rzv2m.h new file mode 100644 index 000000000..12448b0e8 --- /dev/null +++ b/drivers/usb/host/xhci-rzv2m.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __XHCI_RZV2M_H +#define __XHCI_RZV2M_H + +#if IS_ENABLED(CONFIG_USB_XHCI_RZV2M) +void xhci_rzv2m_start(struct usb_hcd *hcd); +int xhci_rzv2m_init_quirk(struct usb_hcd *hcd); +#else +static inline void xhci_rzv2m_start(struct usb_hcd *hcd) {} +static inline int xhci_rzv2m_init_quirk(struct usb_hcd *hcd) +{ + return -EINVAL; +} +#endif + +#endif /* __XHCI_RZV2M_H */ diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c new file mode 100644 index 000000000..a5deeee4d --- /dev/null +++ b/drivers/usb/host/xhci-sideband.c @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * xHCI host controller sideband support + * + * Copyright (c) 2023-2025, Intel Corporation. + * + * Author: Mathias Nyman + */ + +#include +#include + +#include "xhci.h" + +/* sideband internal helpers */ +static struct sg_table * +xhci_ring_to_sgtable(struct xhci_sideband *sb, struct xhci_ring *ring) +{ + struct xhci_segment *seg; + struct sg_table *sgt; + unsigned int n_pages; + struct page **pages; + struct device *dev; + size_t sz; + int i; + + dev = xhci_to_hcd(sb->xhci)->self.sysdev; + sz = ring->num_segs * TRB_SEGMENT_SIZE; + n_pages = PAGE_ALIGN(sz) >> PAGE_SHIFT; + pages = kvmalloc_objs(struct page *, n_pages); + if (!pages) + return NULL; + + sgt = kzalloc_obj(*sgt); + if (!sgt) { + kvfree(pages); + return NULL; + } + + seg = ring->first_seg; + if (!seg) + goto err; + /* + * Rings can potentially have multiple segments, create an array that + * carries page references to allocated segments. Utilize the + * sg_alloc_table_from_pages() to create the sg table, and to ensure + * that page links are created. + */ + for (i = 0; i < ring->num_segs; i++) { + dma_get_sgtable(dev, sgt, seg->trbs, seg->dma, + TRB_SEGMENT_SIZE); + pages[i] = sg_page(sgt->sgl); + sg_free_table(sgt); + seg = seg->next; + } + + if (sg_alloc_table_from_pages(sgt, pages, n_pages, 0, sz, GFP_KERNEL)) + goto err; + + kvfree(pages); + + /* + * Save first segment dma address to sg dma_address field for the sideband + * client to have access to the IOVA of the ring. + */ + sg_dma_address(sgt->sgl) = ring->first_seg->dma; + + return sgt; + +err: + kvfree(pages); + kfree(sgt); + + return NULL; +} + +/* Caller must hold sb->mutex */ +static void +__xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *ep) +{ + lockdep_assert_held(&sb->mutex); + + /* + * Issue a stop endpoint command when an endpoint is removed. + * The stop ep cmd handler will handle the ring cleanup. + */ + xhci_stop_endpoint_sync(sb->xhci, ep, 0, GFP_KERNEL); + + ep->sideband = NULL; + sb->eps[ep->ep_index] = NULL; +} + +/* Caller must hold sb->mutex */ +static void +__xhci_sideband_remove_interrupter(struct xhci_sideband *sb) +{ + lockdep_assert_held(&sb->mutex); + + if (!sb->ir) + return; + + xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir); + sb->ir = NULL; +} + +/* sideband api functions */ + +/** + * xhci_sideband_notify_ep_ring_free - notify client of xfer ring free + * @sb: sideband instance for this usb device + * @ep_index: usb endpoint index + * + * Notifies the xHCI sideband client driver of a xHCI transfer ring free + * routine. This will allow for the client to ensure that all transfers + * are completed. + * + * The callback should be synchronous, as the ring free happens after. + */ +void xhci_sideband_notify_ep_ring_free(struct xhci_sideband *sb, + unsigned int ep_index) +{ + struct xhci_sideband_event evt; + + evt.type = XHCI_SIDEBAND_XFER_RING_FREE; + evt.evt_data = &ep_index; + + if (sb->notify_client) + sb->notify_client(sb->intf, &evt); +} +EXPORT_SYMBOL_GPL(xhci_sideband_notify_ep_ring_free); + +/** + * xhci_sideband_add_endpoint - add endpoint to sideband access list + * @sb: sideband instance for this usb device + * @host_ep: usb host endpoint + * + * Adds an endpoint to the list of sideband accessed endpoints for this usb + * device. + * After an endpoint is added the sideband client can get the endpoint transfer + * ring buffer by calling xhci_sideband_endpoint_buffer() + * + * Return: 0 on success, negative error otherwise. + */ +int +xhci_sideband_add_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep) +{ + struct xhci_virt_ep *ep; + unsigned int ep_index; + + guard(mutex)(&sb->mutex); + + if (!sb->vdev) + return -ENODEV; + + ep_index = xhci_get_endpoint_index(&host_ep->desc); + ep = &sb->vdev->eps[ep_index]; + + if (ep->ep_state & EP_HAS_STREAMS) + return -EINVAL; + + /* + * Note, we don't know the DMA mask of the audio DSP device, if its + * smaller than for xhci it won't be able to access the endpoint ring + * buffer. This could be solved by not allowing the audio class driver + * to add the endpoint the normal way, but instead offload it immediately, + * and let this function add the endpoint and allocate the ring buffer + * with the smallest common DMA mask + */ + if (sb->eps[ep_index] || ep->sideband) + return -EBUSY; + + ep->sideband = sb; + sb->eps[ep_index] = ep; + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_sideband_add_endpoint); + +/** + * xhci_sideband_remove_endpoint - remove endpoint from sideband access list + * @sb: sideband instance for this usb device + * @host_ep: usb host endpoint + * + * Removes an endpoint from the list of sideband accessed endpoints for this usb + * device. + * sideband client should no longer touch the endpoint transfer buffer after + * calling this. + * + * Return: 0 on success, negative error otherwise. + */ +int +xhci_sideband_remove_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep) +{ + struct xhci_virt_ep *ep; + unsigned int ep_index; + + guard(mutex)(&sb->mutex); + + ep_index = xhci_get_endpoint_index(&host_ep->desc); + ep = sb->eps[ep_index]; + + if (!ep || !ep->sideband || ep->sideband != sb) + return -ENODEV; + + __xhci_sideband_remove_endpoint(sb, ep); + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_sideband_remove_endpoint); + +int +xhci_sideband_stop_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep) +{ + struct xhci_virt_ep *ep; + unsigned int ep_index; + + ep_index = xhci_get_endpoint_index(&host_ep->desc); + ep = sb->eps[ep_index]; + + if (!ep || !ep->sideband || ep->sideband != sb) + return -EINVAL; + + return xhci_stop_endpoint_sync(sb->xhci, ep, 0, GFP_KERNEL); +} +EXPORT_SYMBOL_GPL(xhci_sideband_stop_endpoint); + +/** + * xhci_sideband_get_endpoint_buffer - gets the endpoint transfer buffer address + * @sb: sideband instance for this usb device + * @host_ep: usb host endpoint + * + * Returns the address of the endpoint buffer where xHC controller reads queued + * transfer TRBs from. This is the starting address of the ringbuffer where the + * sideband client should write TRBs to. + * + * Caller needs to free the returned sg_table + * + * Return: struct sg_table * if successful. NULL otherwise. + */ +struct sg_table * +xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep) +{ + struct xhci_virt_ep *ep; + unsigned int ep_index; + + ep_index = xhci_get_endpoint_index(&host_ep->desc); + ep = sb->eps[ep_index]; + + if (!ep || !ep->ring || !ep->sideband || ep->sideband != sb) + return NULL; + + return xhci_ring_to_sgtable(sb, ep->ring); +} +EXPORT_SYMBOL_GPL(xhci_sideband_get_endpoint_buffer); + +/** + * xhci_sideband_get_event_buffer - return the event buffer for this device + * @sb: sideband instance for this usb device + * + * If a secondary xhci interupter is set up for this usb device then this + * function returns the address of the event buffer where xHC writes + * the transfer completion events. + * + * Caller needs to free the returned sg_table + * + * Return: struct sg_table * if successful. NULL otherwise. + */ +struct sg_table * +xhci_sideband_get_event_buffer(struct xhci_sideband *sb) +{ + if (!sb || !sb->ir) + return NULL; + + return xhci_ring_to_sgtable(sb, sb->ir->event_ring); +} +EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer); + +/** + * xhci_sideband_check - check the existence of active sidebands + * @hcd: the host controller driver associated with the target host controller + * + * Allow other drivers, such as usb controller driver, to check if there are + * any sideband activity on the host controller. This information could be used + * for power management or other forms of resource management. The caller should + * ensure downstream usb devices are all marked as "offload_pm_locked" to ensure + * the correctness of the return value. + * + * Returns true on any active sideband existence, false otherwise. + */ +bool xhci_sideband_check(struct usb_hcd *hcd) +{ + struct usb_device *udev = hcd->self.root_hub; + bool active; + + usb_lock_device(udev); + active = usb_offload_check(udev); + usb_unlock_device(udev); + + return active; +} +EXPORT_SYMBOL_GPL(xhci_sideband_check); + +/** + * xhci_sideband_create_interrupter - creates a new interrupter for this sideband + * @sb: sideband instance for this usb device + * @num_seg: number of event ring segments to allocate + * @ip_autoclear: IP autoclearing support such as MSI implemented + * + * Sets up a xhci interrupter that can be used for this sideband accessed usb + * device. Transfer events for this device can be routed to this interrupters + * event ring by setting the 'Interrupter Target' field correctly when queueing + * the transfer TRBs. + * Once this interrupter is created the interrupter target ID can be obtained + * by calling xhci_sideband_interrupter_id() + * + * Returns 0 on success, negative error otherwise + */ +int +xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, + bool ip_autoclear, u32 imod_interval, int intr_num) +{ + if (!sb || !sb->xhci) + return -ENODEV; + + guard(mutex)(&sb->mutex); + + if (!sb->vdev) + return -ENODEV; + + if (sb->ir) + return -EBUSY; + + sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci), + num_seg, imod_interval, + intr_num); + if (!sb->ir) + return -ENOMEM; + + sb->ir->ip_autoclear = ip_autoclear; + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter); + +/** + * xhci_sideband_remove_interrupter - remove the interrupter from a sideband + * @sb: sideband instance for this usb device + * + * Removes a registered interrupt for a sideband. This would allow for other + * sideband users to utilize this interrupter. + */ +void +xhci_sideband_remove_interrupter(struct xhci_sideband *sb) +{ + if (!sb) + return; + + guard(mutex)(&sb->mutex); + + __xhci_sideband_remove_interrupter(sb); +} +EXPORT_SYMBOL_GPL(xhci_sideband_remove_interrupter); + +/** + * xhci_sideband_interrupter_id - return the interrupter target id + * @sb: sideband instance for this usb device + * + * If a secondary xhci interrupter is set up for this usb device then this + * function returns the ID used by the interrupter. The sideband client + * needs to write this ID to the 'Interrupter Target' field of the transfer TRBs + * it queues on the endpoints transfer ring to ensure transfer completion event + * are written by xHC to the correct interrupter event ring. + * + * Returns interrupter id on success, negative error othgerwise + */ +int +xhci_sideband_interrupter_id(struct xhci_sideband *sb) +{ + if (!sb || !sb->ir) + return -ENODEV; + + return sb->ir->intr_num; +} +EXPORT_SYMBOL_GPL(xhci_sideband_interrupter_id); + +/** + * xhci_sideband_register - register a sideband for a usb device + * @intf: usb interface associated with the sideband device + * + * Allows for clients to utilize XHCI interrupters and fetch transfer and event + * ring parameters for executing data transfers. + * + * Return: pointer to a new xhci_sideband instance if successful. NULL otherwise. + */ +struct xhci_sideband * +xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type, + int (*notify_client)(struct usb_interface *intf, + struct xhci_sideband_event *evt)) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_virt_device *vdev; + struct xhci_sideband *sb; + + /* + * Make sure the usb device is connected to a xhci controller. Fail + * registration if the type is anything other than XHCI_SIDEBAND_VENDOR, + * as this is the only type that is currently supported by xhci-sideband. + */ + if (!udev->slot_id || type != XHCI_SIDEBAND_VENDOR) + return NULL; + + sb = kzalloc_node(sizeof(*sb), GFP_KERNEL, dev_to_node(hcd->self.sysdev)); + if (!sb) + return NULL; + + mutex_init(&sb->mutex); + + /* check this device isn't already controlled via sideband */ + spin_lock_irq(&xhci->lock); + + vdev = xhci->devs[udev->slot_id]; + + if (!vdev || vdev->sideband) { + xhci_warn(xhci, "XHCI sideband for slot %d already in use\n", + udev->slot_id); + spin_unlock_irq(&xhci->lock); + kfree(sb); + return NULL; + } + + sb->xhci = xhci; + sb->vdev = vdev; + sb->intf = intf; + sb->type = type; + sb->notify_client = notify_client; + vdev->sideband = sb; + + spin_unlock_irq(&xhci->lock); + + return sb; +} +EXPORT_SYMBOL_GPL(xhci_sideband_register); + +/** + * xhci_sideband_unregister - unregister sideband access to a usb device + * @sb: sideband instance to be unregistered + * + * Unregisters sideband access to a usb device and frees the sideband + * instance. + * After this the endpoint and interrupter event buffers should no longer + * be accessed via sideband. The xhci driver can now take over handling + * the buffers. + */ +void +xhci_sideband_unregister(struct xhci_sideband *sb) +{ + struct xhci_virt_device *vdev; + struct xhci_hcd *xhci; + int i; + + if (!sb) + return; + + xhci = sb->xhci; + + scoped_guard(mutex, &sb->mutex) { + vdev = sb->vdev; + if (!vdev) + return; + + for (i = 0; i < EP_CTX_PER_DEV; i++) + if (sb->eps[i]) + __xhci_sideband_remove_endpoint(sb, sb->eps[i]); + + __xhci_sideband_remove_interrupter(sb); + + sb->vdev = NULL; + } + + spin_lock_irq(&xhci->lock); + sb->xhci = NULL; + vdev->sideband = NULL; + spin_unlock_irq(&xhci->lock); + + kfree(sb); +} +EXPORT_SYMBOL_GPL(xhci_sideband_unregister); +MODULE_DESCRIPTION("xHCI sideband driver for secondary interrupter management"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c new file mode 100644 index 000000000..6f235d1e1 --- /dev/null +++ b/drivers/usb/host/xhci-tegra.c @@ -0,0 +1,2877 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NVIDIA Tegra xHCI host controller driver + * + * Copyright (c) 2014-2020, NVIDIA CORPORATION. All rights reserved. + * Copyright (C) 2014 Google, Inc. + */ + +#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 "xhci.h" + +#define TEGRA_XHCI_SS_HIGH_SPEED 120000000 +#define TEGRA_XHCI_SS_LOW_SPEED 12000000 + +/* FPCI CFG registers */ +#define XUSB_CFG_1 0x004 +#define XUSB_IO_SPACE_EN BIT(0) +#define XUSB_MEM_SPACE_EN BIT(1) +#define XUSB_BUS_MASTER_EN BIT(2) +#define XUSB_CFG_4 0x010 +#define XUSB_BASE_ADDR_SHIFT 15 +#define XUSB_BASE_ADDR_MASK 0x1ffff +#define XUSB_CFG_7 0x01c +#define XUSB_BASE2_ADDR_SHIFT 16 +#define XUSB_BASE2_ADDR_MASK 0xffff +#define XUSB_CFG_16 0x040 +#define XUSB_CFG_24 0x060 +#define XUSB_CFG_AXI_CFG 0x0f8 +#define XUSB_CFG_ARU_C11_CSBRANGE 0x41c +#define XUSB_CFG_ARU_CONTEXT 0x43c +#define XUSB_CFG_ARU_CONTEXT_HS_PLS 0x478 +#define XUSB_CFG_ARU_CONTEXT_FS_PLS 0x47c +#define XUSB_CFG_ARU_CONTEXT_HSFS_SPEED 0x480 +#define XUSB_CFG_ARU_CONTEXT_HSFS_PP 0x484 +#define XUSB_CFG_CSB_BASE_ADDR 0x800 + +/* FPCI mailbox registers */ +/* XUSB_CFG_ARU_MBOX_CMD */ +#define MBOX_DEST_FALC BIT(27) +#define MBOX_DEST_PME BIT(28) +#define MBOX_DEST_SMI BIT(29) +#define MBOX_DEST_XHCI BIT(30) +#define MBOX_INT_EN BIT(31) +/* XUSB_CFG_ARU_MBOX_DATA_IN and XUSB_CFG_ARU_MBOX_DATA_OUT */ +#define CMD_DATA_SHIFT 0 +#define CMD_DATA_MASK 0xffffff +#define CMD_TYPE_SHIFT 24 +#define CMD_TYPE_MASK 0xff +/* XUSB_CFG_ARU_MBOX_OWNER */ +#define MBOX_OWNER_NONE 0 +#define MBOX_OWNER_FW 1 +#define MBOX_OWNER_SW 2 +#define XUSB_CFG_ARU_SMI_INTR 0x428 +#define MBOX_SMI_INTR_FW_HANG BIT(1) +#define MBOX_SMI_INTR_EN BIT(3) + +/* BAR2 registers */ +#define XUSB_BAR2_ARU_MBOX_CMD 0x004 +#define XUSB_BAR2_ARU_MBOX_DATA_IN 0x008 +#define XUSB_BAR2_ARU_MBOX_DATA_OUT 0x00c +#define XUSB_BAR2_ARU_MBOX_OWNER 0x010 +#define XUSB_BAR2_ARU_SMI_INTR 0x014 +#define XUSB_BAR2_ARU_SMI_ARU_FW_SCRATCH_DATA0 0x01c +#define XUSB_BAR2_ARU_IFRDMA_CFG0 0x0e0 +#define XUSB_BAR2_ARU_IFRDMA_CFG1 0x0e4 +#define XUSB_BAR2_ARU_IFRDMA_STREAMID_FIELD 0x0e8 +#define XUSB_BAR2_ARU_C11_CSBRANGE 0x9c +#define XUSB_BAR2_ARU_FW_SCRATCH 0x1000 +#define XUSB_BAR2_CSB_BASE_ADDR 0x2000 + +/* IPFS registers */ +#define IPFS_XUSB_HOST_MSI_BAR_SZ_0 0x0c0 +#define IPFS_XUSB_HOST_MSI_AXI_BAR_ST_0 0x0c4 +#define IPFS_XUSB_HOST_MSI_FPCI_BAR_ST_0 0x0c8 +#define IPFS_XUSB_HOST_MSI_VEC0_0 0x100 +#define IPFS_XUSB_HOST_MSI_EN_VEC0_0 0x140 +#define IPFS_XUSB_HOST_CONFIGURATION_0 0x180 +#define IPFS_EN_FPCI BIT(0) +#define IPFS_XUSB_HOST_FPCI_ERROR_MASKS_0 0x184 +#define IPFS_XUSB_HOST_INTR_MASK_0 0x188 +#define IPFS_IP_INT_MASK BIT(16) +#define IPFS_XUSB_HOST_INTR_ENABLE_0 0x198 +#define IPFS_XUSB_HOST_UFPCI_CONFIG_0 0x19c +#define IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0 0x1bc +#define IPFS_XUSB_HOST_MCCIF_FIFOCTRL_0 0x1dc + +#define CSB_PAGE_SELECT_MASK 0x7fffff +#define CSB_PAGE_SELECT_SHIFT 9 +#define CSB_PAGE_OFFSET_MASK 0x1ff +#define CSB_PAGE_SELECT(addr) ((addr) >> (CSB_PAGE_SELECT_SHIFT) & \ + CSB_PAGE_SELECT_MASK) +#define CSB_PAGE_OFFSET(addr) ((addr) & CSB_PAGE_OFFSET_MASK) + +/* Falcon CSB registers */ +#define XUSB_FALC_CPUCTL 0x100 +#define CPUCTL_STARTCPU BIT(1) +#define CPUCTL_STATE_HALTED BIT(4) +#define CPUCTL_STATE_STOPPED BIT(5) +#define XUSB_FALC_BOOTVEC 0x104 +#define XUSB_FALC_DMACTL 0x10c +#define XUSB_FALC_IMFILLRNG1 0x154 +#define IMFILLRNG1_TAG_MASK 0xffff +#define IMFILLRNG1_TAG_LO_SHIFT 0 +#define IMFILLRNG1_TAG_HI_SHIFT 16 +#define XUSB_FALC_IMFILLCTL 0x158 + +/* CSB ARU registers */ +#define XUSB_CSB_ARU_SCRATCH0 0x100100 + +/* MP CSB registers */ +#define XUSB_CSB_MP_ILOAD_ATTR 0x101a00 +#define XUSB_CSB_MP_ILOAD_BASE_LO 0x101a04 +#define XUSB_CSB_MP_ILOAD_BASE_HI 0x101a08 +#define XUSB_CSB_MP_L2IMEMOP_SIZE 0x101a10 +#define L2IMEMOP_SIZE_SRC_OFFSET_SHIFT 8 +#define L2IMEMOP_SIZE_SRC_OFFSET_MASK 0x3ff +#define L2IMEMOP_SIZE_SRC_COUNT_SHIFT 24 +#define L2IMEMOP_SIZE_SRC_COUNT_MASK 0xff +#define XUSB_CSB_MP_L2IMEMOP_TRIG 0x101a14 +#define L2IMEMOP_ACTION_SHIFT 24 +#define L2IMEMOP_INVALIDATE_ALL (0x40 << L2IMEMOP_ACTION_SHIFT) +#define L2IMEMOP_LOAD_LOCKED_RESULT (0x11 << L2IMEMOP_ACTION_SHIFT) +#define XUSB_CSB_MEMPOOL_L2IMEMOP_RESULT 0x101a18 +#define L2IMEMOP_RESULT_VLD BIT(31) +#define XUSB_CSB_MP_APMAP 0x10181c +#define APMAP_BOOTPATH BIT(31) + +#define IMEM_BLOCK_SIZE 256 + +#define FW_IOCTL_TYPE_SHIFT 24 +#define FW_IOCTL_CFGTBL_READ 17 + +#define WAKE_IRQ_START_INDEX 2 + +struct tegra_xusb_fw_header { + __le32 boot_loadaddr_in_imem; + __le32 boot_codedfi_offset; + __le32 boot_codetag; + __le32 boot_codesize; + __le32 phys_memaddr; + __le16 reqphys_memsize; + __le16 alloc_phys_memsize; + __le32 rodata_img_offset; + __le32 rodata_section_start; + __le32 rodata_section_end; + __le32 main_fnaddr; + __le32 fwimg_cksum; + __le32 fwimg_created_time; + __le32 imem_resident_start; + __le32 imem_resident_end; + __le32 idirect_start; + __le32 idirect_end; + __le32 l2_imem_start; + __le32 l2_imem_end; + __le32 version_id; + u8 init_ddirect; + u8 reserved[3]; + __le32 phys_addr_log_buffer; + __le32 total_log_entries; + __le32 dequeue_ptr; + __le32 dummy_var[2]; + __le32 fwimg_len; + u8 magic[8]; + __le32 ss_low_power_entry_timeout; + u8 num_hsic_port; + u8 padding[139]; /* Pad to 256 bytes */ +}; + +struct tegra_xusb_phy_type { + const char *name; + unsigned int num; +}; + +struct tegra_xusb_mbox_regs { + u16 cmd; + u16 data_in; + u16 data_out; + u16 owner; + u16 smi_intr; +}; + +struct tegra_xusb_context_soc { + struct { + const unsigned int *offsets; + unsigned int num_offsets; + } ipfs; + + struct { + const unsigned int *offsets; + unsigned int num_offsets; + } fpci; +}; + +struct tegra_xusb; +struct tegra_xusb_soc_ops { + u32 (*mbox_reg_readl)(struct tegra_xusb *tegra, unsigned int offset); + void (*mbox_reg_writel)(struct tegra_xusb *tegra, u32 value, unsigned int offset); + u32 (*csb_reg_readl)(struct tegra_xusb *tegra, unsigned int offset); + void (*csb_reg_writel)(struct tegra_xusb *tegra, u32 value, unsigned int offset); +}; + +struct tegra_xusb_soc { + const char *firmware; + const char * const *supply_names; + unsigned int num_supplies; + const struct tegra_xusb_phy_type *phy_types; + unsigned int num_types; + unsigned int max_num_wakes; + const struct tegra_xusb_context_soc *context; + + struct { + struct { + unsigned int offset; + unsigned int count; + } usb2, ulpi, hsic, usb3; + } ports; + + struct tegra_xusb_mbox_regs mbox; + const struct tegra_xusb_soc_ops *ops; + + bool scale_ss_clock; + bool has_ipfs; + bool lpm_support; + bool otg_reset_sspi; + bool otg_set_port_power; + + bool has_bar2; +}; + +struct tegra_xusb_context { + u32 *ipfs; + u32 *fpci; +}; + +struct tegra_xusb { + struct device *dev; + void __iomem *regs; + struct usb_hcd *hcd; + + struct mutex lock; + + int xhci_irq; + int mbox_irq; + int padctl_irq; + int *wake_irqs; + + void __iomem *ipfs_base; + void __iomem *fpci_base; + void __iomem *bar2_base; + struct resource *bar2; + + const struct tegra_xusb_soc *soc; + + struct regulator_bulk_data *supplies; + + struct tegra_xusb_padctl *padctl; + + struct clk *host_clk; + struct clk *falcon_clk; + struct clk *ss_clk; + struct clk *ss_src_clk; + struct clk *hs_src_clk; + struct clk *fs_src_clk; + struct clk *pll_u_480m; + struct clk *clk_m; + struct clk *pll_e; + + struct reset_control *host_rst; + struct reset_control *ss_rst; + + struct tegra_pmc *pmc; + struct device *genpd_dev_host; + struct device *genpd_dev_ss; + bool use_genpd; + + struct phy **phys; + unsigned int num_phys; + + struct usb_phy **usbphy; + unsigned int num_usb_phys; + int otg_usb2_port; + int otg_usb3_port; + bool host_mode; + struct notifier_block id_nb; + struct work_struct id_work; + + /* Firmware loading related */ + struct { + size_t size; + void *virt; + dma_addr_t phys; + } fw; + + bool suspended; + struct tegra_xusb_context context; + u8 lp0_utmi_pad_mask; + int num_wakes; +}; + +static struct hc_driver __read_mostly tegra_xhci_hc_driver; + +static inline u32 fpci_readl(struct tegra_xusb *tegra, unsigned int offset) +{ + return readl(tegra->fpci_base + offset); +} + +static inline void fpci_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + writel(value, tegra->fpci_base + offset); +} + +static inline u32 ipfs_readl(struct tegra_xusb *tegra, unsigned int offset) +{ + return readl(tegra->ipfs_base + offset); +} + +static inline void ipfs_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + writel(value, tegra->ipfs_base + offset); +} + +static inline u32 bar2_readl(struct tegra_xusb *tegra, unsigned int offset) +{ + return readl(tegra->bar2_base + offset); +} + +static inline void bar2_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + writel(value, tegra->bar2_base + offset); +} + +static u32 csb_readl(struct tegra_xusb *tegra, unsigned int offset) +{ + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; + + return ops->csb_reg_readl(tegra, offset); +} + +static void csb_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; + + ops->csb_reg_writel(tegra, value, offset); +} + +static u32 fpci_csb_readl(struct tegra_xusb *tegra, unsigned int offset) +{ + u32 page = CSB_PAGE_SELECT(offset); + u32 ofs = CSB_PAGE_OFFSET(offset); + + fpci_writel(tegra, page, XUSB_CFG_ARU_C11_CSBRANGE); + + return fpci_readl(tegra, XUSB_CFG_CSB_BASE_ADDR + ofs); +} + +static void fpci_csb_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + u32 page = CSB_PAGE_SELECT(offset); + u32 ofs = CSB_PAGE_OFFSET(offset); + + fpci_writel(tegra, page, XUSB_CFG_ARU_C11_CSBRANGE); + fpci_writel(tegra, value, XUSB_CFG_CSB_BASE_ADDR + ofs); +} + +static u32 bar2_csb_readl(struct tegra_xusb *tegra, unsigned int offset) +{ + u32 page = CSB_PAGE_SELECT(offset); + u32 ofs = CSB_PAGE_OFFSET(offset); + + bar2_writel(tegra, page, XUSB_BAR2_ARU_C11_CSBRANGE); + + return bar2_readl(tegra, XUSB_BAR2_CSB_BASE_ADDR + ofs); +} + +static void bar2_csb_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + u32 page = CSB_PAGE_SELECT(offset); + u32 ofs = CSB_PAGE_OFFSET(offset); + + bar2_writel(tegra, page, XUSB_BAR2_ARU_C11_CSBRANGE); + bar2_writel(tegra, value, XUSB_BAR2_CSB_BASE_ADDR + ofs); +} + +static int tegra_xusb_set_ss_clk(struct tegra_xusb *tegra, + unsigned long rate) +{ + unsigned long new_parent_rate, old_parent_rate; + struct clk *clk = tegra->ss_src_clk; + unsigned int div; + int err; + + if (clk_get_rate(clk) == rate) + return 0; + + switch (rate) { + case TEGRA_XHCI_SS_HIGH_SPEED: + /* + * Reparent to PLLU_480M. Set divider first to avoid + * overclocking. + */ + old_parent_rate = clk_get_rate(clk_get_parent(clk)); + new_parent_rate = clk_get_rate(tegra->pll_u_480m); + div = new_parent_rate / rate; + + err = clk_set_rate(clk, old_parent_rate / div); + if (err) + return err; + + err = clk_set_parent(clk, tegra->pll_u_480m); + if (err) + return err; + + /* + * The rate should already be correct, but set it again just + * to be sure. + */ + err = clk_set_rate(clk, rate); + if (err) + return err; + + break; + + case TEGRA_XHCI_SS_LOW_SPEED: + /* Reparent to CLK_M */ + err = clk_set_parent(clk, tegra->clk_m); + if (err) + return err; + + err = clk_set_rate(clk, rate); + if (err) + return err; + + break; + + default: + dev_err(tegra->dev, "Invalid SS rate: %lu Hz\n", rate); + return -EINVAL; + } + + if (clk_get_rate(clk) != rate) { + dev_err(tegra->dev, "SS clock doesn't match requested rate\n"); + return -EINVAL; + } + + return 0; +} + +static unsigned long extract_field(u32 value, unsigned int start, + unsigned int count) +{ + return (value >> start) & ((1 << count) - 1); +} + +/* Command requests from the firmware */ +enum tegra_xusb_mbox_cmd { + MBOX_CMD_MSG_ENABLED = 1, + MBOX_CMD_INC_FALC_CLOCK, + MBOX_CMD_DEC_FALC_CLOCK, + MBOX_CMD_INC_SSPI_CLOCK, + MBOX_CMD_DEC_SSPI_CLOCK, + MBOX_CMD_SET_BW, /* no ACK/NAK required */ + MBOX_CMD_SET_SS_PWR_GATING, + MBOX_CMD_SET_SS_PWR_UNGATING, + MBOX_CMD_SAVE_DFE_CTLE_CTX, + MBOX_CMD_AIRPLANE_MODE_ENABLED, /* unused */ + MBOX_CMD_AIRPLANE_MODE_DISABLED, /* unused */ + MBOX_CMD_START_HSIC_IDLE, + MBOX_CMD_STOP_HSIC_IDLE, + MBOX_CMD_DBC_WAKE_STACK, /* unused */ + MBOX_CMD_HSIC_PRETEND_CONNECT, + MBOX_CMD_RESET_SSPI, + MBOX_CMD_DISABLE_SS_LFPS_DETECTION, + MBOX_CMD_ENABLE_SS_LFPS_DETECTION, + + MBOX_CMD_MAX, + + /* Response message to above commands */ + MBOX_CMD_ACK = 128, + MBOX_CMD_NAK +}; + +struct tegra_xusb_mbox_msg { + u32 cmd; + u32 data; +}; + +static inline u32 tegra_xusb_mbox_pack(const struct tegra_xusb_mbox_msg *msg) +{ + return (msg->cmd & CMD_TYPE_MASK) << CMD_TYPE_SHIFT | + (msg->data & CMD_DATA_MASK) << CMD_DATA_SHIFT; +} +static inline void tegra_xusb_mbox_unpack(struct tegra_xusb_mbox_msg *msg, + u32 value) +{ + msg->cmd = (value >> CMD_TYPE_SHIFT) & CMD_TYPE_MASK; + msg->data = (value >> CMD_DATA_SHIFT) & CMD_DATA_MASK; +} + +static bool tegra_xusb_mbox_cmd_requires_ack(enum tegra_xusb_mbox_cmd cmd) +{ + switch (cmd) { + case MBOX_CMD_SET_BW: + case MBOX_CMD_ACK: + case MBOX_CMD_NAK: + return false; + + default: + return true; + } +} + +static int tegra_xusb_mbox_send(struct tegra_xusb *tegra, + const struct tegra_xusb_mbox_msg *msg) +{ + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; + bool wait_for_idle = false; + u32 value; + + /* + * Acquire the mailbox. The firmware still owns the mailbox for + * ACK/NAK messages. + */ + if (!(msg->cmd == MBOX_CMD_ACK || msg->cmd == MBOX_CMD_NAK)) { + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.owner); + if (value != MBOX_OWNER_NONE) { + dev_err(tegra->dev, "mailbox is busy\n"); + return -EBUSY; + } + + ops->mbox_reg_writel(tegra, MBOX_OWNER_SW, tegra->soc->mbox.owner); + + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.owner); + if (value != MBOX_OWNER_SW) { + dev_err(tegra->dev, "failed to acquire mailbox\n"); + return -EBUSY; + } + + wait_for_idle = true; + } + + value = tegra_xusb_mbox_pack(msg); + ops->mbox_reg_writel(tegra, value, tegra->soc->mbox.data_in); + + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.cmd); + value |= MBOX_INT_EN | MBOX_DEST_FALC; + ops->mbox_reg_writel(tegra, value, tegra->soc->mbox.cmd); + + if (wait_for_idle) { + unsigned long timeout = jiffies + msecs_to_jiffies(250); + + while (time_before(jiffies, timeout)) { + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.owner); + if (value == MBOX_OWNER_NONE) + break; + + usleep_range(10, 20); + } + + if (time_after(jiffies, timeout)) + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.owner); + + if (value != MBOX_OWNER_NONE) + return -ETIMEDOUT; + } + + return 0; +} + +static irqreturn_t tegra_xusb_mbox_irq(int irq, void *data) +{ + struct tegra_xusb *tegra = data; + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; + u32 value; + + /* clear mailbox interrupts */ + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.smi_intr); + ops->mbox_reg_writel(tegra, value, tegra->soc->mbox.smi_intr); + + if (value & MBOX_SMI_INTR_FW_HANG) + dev_err(tegra->dev, "controller firmware hang\n"); + + return IRQ_WAKE_THREAD; +} + +static void tegra_xusb_mbox_handle(struct tegra_xusb *tegra, + const struct tegra_xusb_mbox_msg *msg) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + const struct tegra_xusb_soc *soc = tegra->soc; + struct device *dev = tegra->dev; + struct tegra_xusb_mbox_msg rsp; + unsigned long mask; + unsigned int port; + bool idle, enable; + int err = 0; + + memset(&rsp, 0, sizeof(rsp)); + + switch (msg->cmd) { + case MBOX_CMD_INC_FALC_CLOCK: + case MBOX_CMD_DEC_FALC_CLOCK: + rsp.data = clk_get_rate(tegra->falcon_clk) / 1000; + if (rsp.data != msg->data) + rsp.cmd = MBOX_CMD_NAK; + else + rsp.cmd = MBOX_CMD_ACK; + + break; + + case MBOX_CMD_INC_SSPI_CLOCK: + case MBOX_CMD_DEC_SSPI_CLOCK: + if (tegra->soc->scale_ss_clock) { + err = tegra_xusb_set_ss_clk(tegra, msg->data * 1000); + if (err < 0) + rsp.cmd = MBOX_CMD_NAK; + else + rsp.cmd = MBOX_CMD_ACK; + + rsp.data = clk_get_rate(tegra->ss_src_clk) / 1000; + } else { + rsp.cmd = MBOX_CMD_ACK; + rsp.data = msg->data; + } + + break; + + case MBOX_CMD_SET_BW: + /* + * TODO: Request bandwidth once EMC scaling is supported. + * Ignore for now since ACK/NAK is not required for SET_BW + * messages. + */ + break; + + case MBOX_CMD_SAVE_DFE_CTLE_CTX: + err = tegra_xusb_padctl_usb3_save_context(padctl, msg->data); + if (err < 0) { + dev_err(dev, "failed to save context for USB3#%u: %d\n", + msg->data, err); + rsp.cmd = MBOX_CMD_NAK; + } else { + rsp.cmd = MBOX_CMD_ACK; + } + + rsp.data = msg->data; + break; + + case MBOX_CMD_START_HSIC_IDLE: + case MBOX_CMD_STOP_HSIC_IDLE: + if (msg->cmd == MBOX_CMD_STOP_HSIC_IDLE) + idle = false; + else + idle = true; + + mask = extract_field(msg->data, 1 + soc->ports.hsic.offset, + soc->ports.hsic.count); + + for_each_set_bit(port, &mask, 32) { + err = tegra_xusb_padctl_hsic_set_idle(padctl, port, + idle); + if (err < 0) + break; + } + + if (err < 0) { + dev_err(dev, "failed to set HSIC#%u %s: %d\n", port, + idle ? "idle" : "busy", err); + rsp.cmd = MBOX_CMD_NAK; + } else { + rsp.cmd = MBOX_CMD_ACK; + } + + rsp.data = msg->data; + break; + + case MBOX_CMD_DISABLE_SS_LFPS_DETECTION: + case MBOX_CMD_ENABLE_SS_LFPS_DETECTION: + if (msg->cmd == MBOX_CMD_DISABLE_SS_LFPS_DETECTION) + enable = false; + else + enable = true; + + mask = extract_field(msg->data, 1 + soc->ports.usb3.offset, + soc->ports.usb3.count); + + for_each_set_bit(port, &mask, soc->ports.usb3.count) { + err = tegra_xusb_padctl_usb3_set_lfps_detect(padctl, + port, + enable); + if (err < 0) + break; + + /* + * wait 500us for LFPS detector to be disabled before + * sending ACK + */ + if (!enable) + usleep_range(500, 1000); + } + + if (err < 0) { + dev_err(dev, + "failed to %s LFPS detection on USB3#%u: %d\n", + str_enable_disable(enable), port, err); + rsp.cmd = MBOX_CMD_NAK; + } else { + rsp.cmd = MBOX_CMD_ACK; + } + + rsp.data = msg->data; + break; + + default: + dev_warn(dev, "unknown message: %#x\n", msg->cmd); + break; + } + + if (rsp.cmd) { + const char *cmd = (rsp.cmd == MBOX_CMD_ACK) ? "ACK" : "NAK"; + + err = tegra_xusb_mbox_send(tegra, &rsp); + if (err < 0) + dev_err(dev, "failed to send %s: %d\n", cmd, err); + } +} + +static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data) +{ + struct tegra_xusb *tegra = data; + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; + struct tegra_xusb_mbox_msg msg; + u32 value; + + mutex_lock(&tegra->lock); + + if (pm_runtime_suspended(tegra->dev) || tegra->suspended) + goto out; + + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.data_out); + tegra_xusb_mbox_unpack(&msg, value); + + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.cmd); + value &= ~MBOX_DEST_SMI; + ops->mbox_reg_writel(tegra, value, tegra->soc->mbox.cmd); + + /* clear mailbox owner if no ACK/NAK is required */ + if (!tegra_xusb_mbox_cmd_requires_ack(msg.cmd)) + ops->mbox_reg_writel(tegra, MBOX_OWNER_NONE, tegra->soc->mbox.owner); + + tegra_xusb_mbox_handle(tegra, &msg); + +out: + mutex_unlock(&tegra->lock); + return IRQ_HANDLED; +} + +static void tegra_xusb_config(struct tegra_xusb *tegra) +{ + u32 regs = tegra->hcd->rsrc_start; + u32 value; + + if (tegra->soc->has_ipfs) { + value = ipfs_readl(tegra, IPFS_XUSB_HOST_CONFIGURATION_0); + value |= IPFS_EN_FPCI; + ipfs_writel(tegra, value, IPFS_XUSB_HOST_CONFIGURATION_0); + + usleep_range(10, 20); + } + + /* Program BAR0 space */ + value = fpci_readl(tegra, XUSB_CFG_4); + value &= ~(XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT); + value |= regs & (XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT); + fpci_writel(tegra, value, XUSB_CFG_4); + + /* Program BAR2 space */ + if (tegra->bar2) { + value = fpci_readl(tegra, XUSB_CFG_7); + value &= ~(XUSB_BASE2_ADDR_MASK << XUSB_BASE2_ADDR_SHIFT); + value |= tegra->bar2->start & + (XUSB_BASE2_ADDR_MASK << XUSB_BASE2_ADDR_SHIFT); + fpci_writel(tegra, value, XUSB_CFG_7); + } + + usleep_range(100, 200); + + /* Enable bus master */ + value = fpci_readl(tegra, XUSB_CFG_1); + value |= XUSB_IO_SPACE_EN | XUSB_MEM_SPACE_EN | XUSB_BUS_MASTER_EN; + fpci_writel(tegra, value, XUSB_CFG_1); + + if (tegra->soc->has_ipfs) { + /* Enable interrupt assertion */ + value = ipfs_readl(tegra, IPFS_XUSB_HOST_INTR_MASK_0); + value |= IPFS_IP_INT_MASK; + ipfs_writel(tegra, value, IPFS_XUSB_HOST_INTR_MASK_0); + + /* Set hysteresis */ + ipfs_writel(tegra, 0x80, IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0); + } +} + +static int tegra_xusb_clk_enable(struct tegra_xusb *tegra) +{ + int err; + + err = clk_prepare_enable(tegra->pll_e); + if (err < 0) + return err; + + err = clk_prepare_enable(tegra->host_clk); + if (err < 0) + goto disable_plle; + + err = clk_prepare_enable(tegra->ss_clk); + if (err < 0) + goto disable_host; + + err = clk_prepare_enable(tegra->falcon_clk); + if (err < 0) + goto disable_ss; + + err = clk_prepare_enable(tegra->fs_src_clk); + if (err < 0) + goto disable_falc; + + err = clk_prepare_enable(tegra->hs_src_clk); + if (err < 0) + goto disable_fs_src; + + if (tegra->soc->scale_ss_clock) { + err = tegra_xusb_set_ss_clk(tegra, TEGRA_XHCI_SS_HIGH_SPEED); + if (err < 0) + goto disable_hs_src; + } + + return 0; + +disable_hs_src: + clk_disable_unprepare(tegra->hs_src_clk); +disable_fs_src: + clk_disable_unprepare(tegra->fs_src_clk); +disable_falc: + clk_disable_unprepare(tegra->falcon_clk); +disable_ss: + clk_disable_unprepare(tegra->ss_clk); +disable_host: + clk_disable_unprepare(tegra->host_clk); +disable_plle: + clk_disable_unprepare(tegra->pll_e); + return err; +} + +static void tegra_xusb_clk_disable(struct tegra_xusb *tegra) +{ + clk_disable_unprepare(tegra->pll_e); + clk_disable_unprepare(tegra->host_clk); + clk_disable_unprepare(tegra->ss_clk); + clk_disable_unprepare(tegra->falcon_clk); + clk_disable_unprepare(tegra->fs_src_clk); + clk_disable_unprepare(tegra->hs_src_clk); +} + +static int tegra_xusb_phy_enable(struct tegra_xusb *tegra) +{ + unsigned int i; + int err; + + for (i = 0; i < tegra->num_phys; i++) { + err = phy_init(tegra->phys[i]); + if (err) + goto disable_phy; + + err = phy_power_on(tegra->phys[i]); + if (err) { + phy_exit(tegra->phys[i]); + goto disable_phy; + } + } + + return 0; + +disable_phy: + while (i--) { + phy_power_off(tegra->phys[i]); + phy_exit(tegra->phys[i]); + } + + return err; +} + +static void tegra_xusb_phy_disable(struct tegra_xusb *tegra) +{ + unsigned int i; + + for (i = 0; i < tegra->num_phys; i++) { + phy_power_off(tegra->phys[i]); + phy_exit(tegra->phys[i]); + } +} + +#ifdef CONFIG_PM_SLEEP +static int tegra_xusb_init_context(struct tegra_xusb *tegra) +{ + const struct tegra_xusb_context_soc *soc = tegra->soc->context; + + tegra->context.ipfs = devm_kcalloc(tegra->dev, soc->ipfs.num_offsets, + sizeof(u32), GFP_KERNEL); + if (!tegra->context.ipfs) + return -ENOMEM; + + tegra->context.fpci = devm_kcalloc(tegra->dev, soc->fpci.num_offsets, + sizeof(u32), GFP_KERNEL); + if (!tegra->context.fpci) + return -ENOMEM; + + return 0; +} +#else +static inline int tegra_xusb_init_context(struct tegra_xusb *tegra) +{ + return 0; +} +#endif + +static int tegra_xusb_request_firmware(struct tegra_xusb *tegra) +{ + struct tegra_xusb_fw_header *header; + const struct firmware *fw; + int err; + + err = request_firmware(&fw, tegra->soc->firmware, tegra->dev); + if (err < 0) { + dev_err(tegra->dev, "failed to request firmware: %d\n", err); + return err; + } + + /* Load Falcon controller with its firmware. */ + header = (struct tegra_xusb_fw_header *)fw->data; + tegra->fw.size = le32_to_cpu(header->fwimg_len); + + tegra->fw.virt = dma_alloc_coherent(tegra->dev, tegra->fw.size, + &tegra->fw.phys, GFP_KERNEL); + if (!tegra->fw.virt) { + dev_err(tegra->dev, "failed to allocate memory for firmware\n"); + release_firmware(fw); + return -ENOMEM; + } + + header = (struct tegra_xusb_fw_header *)tegra->fw.virt; + memcpy(tegra->fw.virt, fw->data, tegra->fw.size); + release_firmware(fw); + + return 0; +} + +static int tegra_xusb_wait_for_falcon(struct tegra_xusb *tegra) +{ + struct xhci_cap_regs __iomem *cap_regs; + struct xhci_op_regs __iomem *op_regs; + int ret; + u32 value; + + cap_regs = tegra->regs; + op_regs = tegra->regs + FIELD_GET(HC_LENGTH, readl(&cap_regs->hc_capbase)), + + ret = readl_poll_timeout(&op_regs->status, value, !(value & STS_CNR), 1000, 200000); + + if (ret) + dev_err(tegra->dev, "XHCI Controller not ready. Falcon state: 0x%x\n", + csb_readl(tegra, XUSB_FALC_CPUCTL)); + + return ret; +} + +static int tegra_xusb_load_firmware_rom(struct tegra_xusb *tegra) +{ + unsigned int code_tag_blocks, code_size_blocks, code_blocks; + struct tegra_xusb_fw_header *header; + struct device *dev = tegra->dev; + time64_t timestamp; + u64 address; + u32 value; + int err; + + header = (struct tegra_xusb_fw_header *)tegra->fw.virt; + + if (csb_readl(tegra, XUSB_CSB_MP_ILOAD_BASE_LO) != 0) { + dev_info(dev, "Firmware already loaded, Falcon state %#x\n", + csb_readl(tegra, XUSB_FALC_CPUCTL)); + return 0; + } + + /* Program the size of DFI into ILOAD_ATTR. */ + csb_writel(tegra, tegra->fw.size, XUSB_CSB_MP_ILOAD_ATTR); + + /* + * Boot code of the firmware reads the ILOAD_BASE registers + * to get to the start of the DFI in system memory. + */ + address = tegra->fw.phys + sizeof(*header); + csb_writel(tegra, address >> 32, XUSB_CSB_MP_ILOAD_BASE_HI); + csb_writel(tegra, address, XUSB_CSB_MP_ILOAD_BASE_LO); + + /* Set BOOTPATH to 1 in APMAP. */ + csb_writel(tegra, APMAP_BOOTPATH, XUSB_CSB_MP_APMAP); + + /* Invalidate L2IMEM. */ + csb_writel(tegra, L2IMEMOP_INVALIDATE_ALL, XUSB_CSB_MP_L2IMEMOP_TRIG); + + /* + * Initiate fetch of bootcode from system memory into L2IMEM. + * Program bootcode location and size in system memory. + */ + code_tag_blocks = DIV_ROUND_UP(le32_to_cpu(header->boot_codetag), + IMEM_BLOCK_SIZE); + code_size_blocks = DIV_ROUND_UP(le32_to_cpu(header->boot_codesize), + IMEM_BLOCK_SIZE); + code_blocks = code_tag_blocks + code_size_blocks; + + value = ((code_tag_blocks & L2IMEMOP_SIZE_SRC_OFFSET_MASK) << + L2IMEMOP_SIZE_SRC_OFFSET_SHIFT) | + ((code_size_blocks & L2IMEMOP_SIZE_SRC_COUNT_MASK) << + L2IMEMOP_SIZE_SRC_COUNT_SHIFT); + csb_writel(tegra, value, XUSB_CSB_MP_L2IMEMOP_SIZE); + + /* Trigger L2IMEM load operation. */ + csb_writel(tegra, L2IMEMOP_LOAD_LOCKED_RESULT, + XUSB_CSB_MP_L2IMEMOP_TRIG); + + /* Setup Falcon auto-fill. */ + csb_writel(tegra, code_size_blocks, XUSB_FALC_IMFILLCTL); + + value = ((code_tag_blocks & IMFILLRNG1_TAG_MASK) << + IMFILLRNG1_TAG_LO_SHIFT) | + ((code_blocks & IMFILLRNG1_TAG_MASK) << + IMFILLRNG1_TAG_HI_SHIFT); + csb_writel(tegra, value, XUSB_FALC_IMFILLRNG1); + + csb_writel(tegra, 0, XUSB_FALC_DMACTL); + + /* wait for RESULT_VLD to get set */ +#define tegra_csb_readl(offset) csb_readl(tegra, offset) + err = readx_poll_timeout(tegra_csb_readl, + XUSB_CSB_MEMPOOL_L2IMEMOP_RESULT, value, + value & L2IMEMOP_RESULT_VLD, 100, 10000); + if (err < 0) { + dev_err(dev, "DMA controller not ready %#010x\n", value); + return err; + } +#undef tegra_csb_readl + + csb_writel(tegra, le32_to_cpu(header->boot_codetag), + XUSB_FALC_BOOTVEC); + + /* Boot Falcon CPU and wait for USBSTS_CNR to get cleared. */ + csb_writel(tegra, CPUCTL_STARTCPU, XUSB_FALC_CPUCTL); + + if (tegra_xusb_wait_for_falcon(tegra)) + return -EIO; + + timestamp = le32_to_cpu(header->fwimg_created_time); + + dev_info(dev, "Firmware timestamp: %ptTs UTC\n", ×tamp); + + return 0; +} + +static u32 tegra_xusb_read_firmware_header(struct tegra_xusb *tegra, u32 offset) +{ + /* + * We only accept reading the firmware config table + * The offset should not exceed the fw header structure + */ + if (offset >= sizeof(struct tegra_xusb_fw_header)) + return 0; + + bar2_writel(tegra, (FW_IOCTL_CFGTBL_READ << FW_IOCTL_TYPE_SHIFT) | offset, + XUSB_BAR2_ARU_FW_SCRATCH); + return bar2_readl(tegra, XUSB_BAR2_ARU_SMI_ARU_FW_SCRATCH_DATA0); +} + +static int tegra_xusb_init_ifr_firmware(struct tegra_xusb *tegra) +{ + time64_t timestamp; + + if (tegra_xusb_wait_for_falcon(tegra)) + return -EIO; + +#define offsetof_32(X, Y) ((u8)(offsetof(X, Y) / sizeof(__le32))) + timestamp = tegra_xusb_read_firmware_header(tegra, offsetof_32(struct tegra_xusb_fw_header, + fwimg_created_time) << 2); + + dev_info(tegra->dev, "Firmware timestamp: %ptTs UTC\n", ×tamp); + + return 0; +} + +static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) +{ + if (!tegra->soc->firmware) + return tegra_xusb_init_ifr_firmware(tegra); + else + return tegra_xusb_load_firmware_rom(tegra); +} + +static void tegra_xusb_powerdomain_remove(struct device *dev, + struct tegra_xusb *tegra) +{ + if (!tegra->use_genpd) + return; + + if (!IS_ERR_OR_NULL(tegra->genpd_dev_ss)) + dev_pm_domain_detach(tegra->genpd_dev_ss, true); + if (!IS_ERR_OR_NULL(tegra->genpd_dev_host)) + dev_pm_domain_detach(tegra->genpd_dev_host, true); +} + +static int tegra_xusb_powerdomain_init(struct device *dev, + struct tegra_xusb *tegra) +{ + int err; + + tegra->genpd_dev_host = dev_pm_domain_attach_by_name(dev, "xusb_host"); + if (IS_ERR(tegra->genpd_dev_host)) { + err = PTR_ERR(tegra->genpd_dev_host); + dev_err(dev, "failed to get host pm-domain: %d\n", err); + return err; + } + + tegra->genpd_dev_ss = dev_pm_domain_attach_by_name(dev, "xusb_ss"); + if (IS_ERR(tegra->genpd_dev_ss)) { + err = PTR_ERR(tegra->genpd_dev_ss); + dev_err(dev, "failed to get superspeed pm-domain: %d\n", err); + return err; + } + + tegra->use_genpd = true; + + return 0; +} + +static int tegra_xusb_unpowergate_partitions(struct tegra_xusb *tegra) +{ + struct device *dev = tegra->dev; + int rc; + + if (tegra->use_genpd) { + rc = pm_runtime_resume_and_get(tegra->genpd_dev_ss); + if (rc < 0) { + dev_err(dev, "failed to enable XUSB SS partition\n"); + return rc; + } + + rc = pm_runtime_resume_and_get(tegra->genpd_dev_host); + if (rc < 0) { + dev_err(dev, "failed to enable XUSB Host partition\n"); + pm_runtime_put_sync(tegra->genpd_dev_ss); + return rc; + } + } else { + rc = tegra_pmc_powergate_sequence_power_up(tegra->pmc, + TEGRA_POWERGATE_XUSBA, + tegra->ss_clk, + tegra->ss_rst); + if (rc < 0) { + dev_err(dev, "failed to enable XUSB SS partition\n"); + return rc; + } + + rc = tegra_pmc_powergate_sequence_power_up(tegra->pmc, + TEGRA_POWERGATE_XUSBC, + tegra->host_clk, + tegra->host_rst); + if (rc < 0) { + dev_err(dev, "failed to enable XUSB Host partition\n"); + tegra_pmc_powergate_power_off(tegra->pmc, + TEGRA_POWERGATE_XUSBA); + return rc; + } + } + + return 0; +} + +static int tegra_xusb_powergate_partitions(struct tegra_xusb *tegra) +{ + struct device *dev = tegra->dev; + int rc; + + if (tegra->use_genpd) { + rc = pm_runtime_put_sync(tegra->genpd_dev_host); + if (rc < 0) { + dev_err(dev, "failed to disable XUSB Host partition\n"); + return rc; + } + + rc = pm_runtime_put_sync(tegra->genpd_dev_ss); + if (rc < 0) { + dev_err(dev, "failed to disable XUSB SS partition\n"); + pm_runtime_get_sync(tegra->genpd_dev_host); + return rc; + } + } else { + rc = tegra_pmc_powergate_power_off(tegra->pmc, + TEGRA_POWERGATE_XUSBC); + if (rc < 0) { + dev_err(dev, "failed to disable XUSB Host partition\n"); + return rc; + } + + rc = tegra_pmc_powergate_power_off(tegra->pmc, + TEGRA_POWERGATE_XUSBA); + if (rc < 0) { + dev_err(dev, "failed to disable XUSB SS partition\n"); + tegra_pmc_powergate_sequence_power_up(tegra->pmc, + TEGRA_POWERGATE_XUSBC, + tegra->host_clk, + tegra->host_rst); + return rc; + } + } + + return 0; +} + +static int __tegra_xusb_enable_firmware_messages(struct tegra_xusb *tegra) +{ + struct tegra_xusb_mbox_msg msg; + int err; + + /* Enable firmware messages from controller. */ + msg.cmd = MBOX_CMD_MSG_ENABLED; + msg.data = 0; + + err = tegra_xusb_mbox_send(tegra, &msg); + if (err < 0) + dev_err(tegra->dev, "failed to enable messages: %d\n", err); + + return err; +} + +static irqreturn_t tegra_xusb_padctl_irq(int irq, void *data) +{ + struct tegra_xusb *tegra = data; + + mutex_lock(&tegra->lock); + + if (tegra->suspended) { + mutex_unlock(&tegra->lock); + return IRQ_HANDLED; + } + + mutex_unlock(&tegra->lock); + + pm_runtime_resume(tegra->dev); + + return IRQ_HANDLED; +} + +static int tegra_xusb_enable_firmware_messages(struct tegra_xusb *tegra) +{ + int err; + + mutex_lock(&tegra->lock); + err = __tegra_xusb_enable_firmware_messages(tegra); + mutex_unlock(&tegra->lock); + + return err; +} + +static void tegra_xhci_set_port_power(struct tegra_xusb *tegra, bool main, + bool set) +{ + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct usb_hcd *hcd = main ? xhci->main_hcd : xhci->shared_hcd; + unsigned int wait = (!main && !set) ? 1000 : 10; + u16 typeReq = set ? SetPortFeature : ClearPortFeature; + u16 wIndex = main ? tegra->otg_usb2_port + 1 : tegra->otg_usb3_port + 1; + u32 status; + u32 stat_power = main ? USB_PORT_STAT_POWER : USB_SS_PORT_STAT_POWER; + u32 status_val = set ? stat_power : 0; + + dev_dbg(tegra->dev, "%s():%s %s port power\n", __func__, + set ? "set" : "clear", main ? "HS" : "SS"); + + hcd->driver->hub_control(hcd, typeReq, USB_PORT_FEAT_POWER, wIndex, + NULL, 0); + + do { + tegra_xhci_hc_driver.hub_control(hcd, GetPortStatus, 0, wIndex, + (char *) &status, sizeof(status)); + if (status_val == (status & stat_power)) + break; + + if (!main && !set) + usleep_range(600, 700); + else + usleep_range(10, 20); + } while (--wait > 0); + + if (status_val != (status & stat_power)) + dev_info(tegra->dev, "failed to %s %s PP %d\n", + set ? "set" : "clear", + main ? "HS" : "SS", status); +} + +static struct phy *tegra_xusb_get_phy(struct tegra_xusb *tegra, char *name, + int port) +{ + unsigned int i, phy_count = 0; + + for (i = 0; i < tegra->soc->num_types; i++) { + if (!strncmp(tegra->soc->phy_types[i].name, name, + strlen(name))) + return tegra->phys[phy_count+port]; + + phy_count += tegra->soc->phy_types[i].num; + } + + return NULL; +} + +static void tegra_xhci_id_work(struct work_struct *work) +{ + struct tegra_xusb *tegra = container_of(work, struct tegra_xusb, + id_work); + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct tegra_xusb_mbox_msg msg; + struct phy *phy = tegra_xusb_get_phy(tegra, "usb2", + tegra->otg_usb2_port); + bool host_mode = tegra->host_mode; + u32 status; + int ret; + + dev_dbg(tegra->dev, "host mode %s\n", str_on_off(host_mode)); + + if (host_mode) + phy_set_mode_ext(phy, PHY_MODE_USB_OTG, USB_ROLE_HOST); + else + phy_set_mode_ext(phy, PHY_MODE_USB_OTG, USB_ROLE_NONE); + + tegra->otg_usb3_port = tegra_xusb_padctl_get_usb3_companion(tegra->padctl, + tegra->otg_usb2_port); + + pm_runtime_get_sync(tegra->dev); + if (tegra->soc->otg_set_port_power) { + if (host_mode) { + /* switch to host mode */ + if (tegra->otg_usb3_port >= 0) { + if (tegra->soc->otg_reset_sspi) { + /* set PP=0 */ + tegra_xhci_hc_driver.hub_control( + xhci->shared_hcd, GetPortStatus, + 0, tegra->otg_usb3_port+1, + (char *) &status, sizeof(status)); + if (status & USB_SS_PORT_STAT_POWER) + tegra_xhci_set_port_power(tegra, false, + false); + + /* reset OTG port SSPI */ + msg.cmd = MBOX_CMD_RESET_SSPI; + msg.data = tegra->otg_usb3_port+1; + + ret = tegra_xusb_mbox_send(tegra, &msg); + if (ret < 0) { + dev_info(tegra->dev, + "failed to RESET_SSPI %d\n", + ret); + } + } + + tegra_xhci_set_port_power(tegra, false, true); + } + + tegra_xhci_set_port_power(tegra, true, true); + + } else { + if (tegra->otg_usb3_port >= 0) + tegra_xhci_set_port_power(tegra, false, false); + + tegra_xhci_set_port_power(tegra, true, false); + } + } + pm_runtime_put_autosuspend(tegra->dev); +} + +#if IS_ENABLED(CONFIG_PM) || IS_ENABLED(CONFIG_PM_SLEEP) +static bool is_usb2_otg_phy(struct tegra_xusb *tegra, unsigned int index) +{ + return (tegra->usbphy[index] != NULL); +} + +static bool is_usb3_otg_phy(struct tegra_xusb *tegra, unsigned int index) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + unsigned int i; + int port; + + for (i = 0; i < tegra->num_usb_phys; i++) { + if (is_usb2_otg_phy(tegra, i)) { + port = tegra_xusb_padctl_get_usb3_companion(padctl, i); + if ((port >= 0) && (index == (unsigned int)port)) + return true; + } + } + + return false; +} + +static bool is_host_mode_phy(struct tegra_xusb *tegra, unsigned int phy_type, unsigned int index) +{ + if (strcmp(tegra->soc->phy_types[phy_type].name, "hsic") == 0) + return true; + + if (strcmp(tegra->soc->phy_types[phy_type].name, "usb2") == 0) { + if (is_usb2_otg_phy(tegra, index)) + return ((index == tegra->otg_usb2_port) && tegra->host_mode); + else + return true; + } + + if (strcmp(tegra->soc->phy_types[phy_type].name, "usb3") == 0) { + if (is_usb3_otg_phy(tegra, index)) + return ((index == tegra->otg_usb3_port) && tegra->host_mode); + else + return true; + } + + return false; +} +#endif + +static int tegra_xusb_get_usb2_port(struct tegra_xusb *tegra, + struct usb_phy *usbphy) +{ + unsigned int i; + + for (i = 0; i < tegra->num_usb_phys; i++) { + if (tegra->usbphy[i] && usbphy == tegra->usbphy[i]) + return i; + } + + return -1; +} + +static int tegra_xhci_id_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct tegra_xusb *tegra = container_of(nb, struct tegra_xusb, + id_nb); + struct usb_phy *usbphy = (struct usb_phy *)data; + + dev_dbg(tegra->dev, "%s(): action is %d", __func__, usbphy->last_event); + + if ((tegra->host_mode && usbphy->last_event == USB_EVENT_ID) || + (!tegra->host_mode && usbphy->last_event != USB_EVENT_ID)) { + dev_dbg(tegra->dev, "Same role(%d) received. Ignore", + tegra->host_mode); + return NOTIFY_OK; + } + + tegra->otg_usb2_port = tegra_xusb_get_usb2_port(tegra, usbphy); + + tegra->host_mode = usbphy->last_event == USB_EVENT_ID; + + schedule_work(&tegra->id_work); + + return NOTIFY_OK; +} + +static int tegra_xusb_init_usb_phy(struct tegra_xusb *tegra) +{ + unsigned int i; + + tegra->usbphy = devm_kcalloc(tegra->dev, tegra->num_usb_phys, + sizeof(*tegra->usbphy), GFP_KERNEL); + if (!tegra->usbphy) + return -ENOMEM; + + INIT_WORK(&tegra->id_work, tegra_xhci_id_work); + tegra->id_nb.notifier_call = tegra_xhci_id_notify; + tegra->otg_usb2_port = -EINVAL; + tegra->otg_usb3_port = -EINVAL; + + for (i = 0; i < tegra->num_usb_phys; i++) { + struct phy *phy = tegra_xusb_get_phy(tegra, "usb2", i); + + if (!phy) + continue; + + tegra->usbphy[i] = devm_usb_get_phy_by_node(tegra->dev, + phy->dev.of_node, + &tegra->id_nb); + if (!IS_ERR(tegra->usbphy[i])) { + dev_dbg(tegra->dev, "usbphy-%d registered", i); + otg_set_host(tegra->usbphy[i]->otg, &tegra->hcd->self); + } else { + /* + * usb-phy is optional, continue if its not available. + */ + tegra->usbphy[i] = NULL; + } + } + + return 0; +} + +static void tegra_xusb_deinit_usb_phy(struct tegra_xusb *tegra) +{ + unsigned int i; + + cancel_work_sync(&tegra->id_work); + + for (i = 0; i < tegra->num_usb_phys; i++) + if (tegra->usbphy[i]) + otg_set_host(tegra->usbphy[i]->otg, NULL); +} + +static int tegra_xusb_setup_wakeup(struct platform_device *pdev, struct tegra_xusb *tegra) +{ + unsigned int i; + + if (tegra->soc->max_num_wakes == 0) + return 0; + + tegra->wake_irqs = devm_kcalloc(tegra->dev, + tegra->soc->max_num_wakes, + sizeof(*tegra->wake_irqs), GFP_KERNEL); + if (!tegra->wake_irqs) + return -ENOMEM; + + /* + * USB wake events are independent of each other, so it is not necessary for a platform + * to utilize all wake-up events supported for a given device. The USB host can operate + * even if wake-up events are not defined or fail to be configured. Therefore, we only + * return critical errors, such as -ENOMEM. + */ + for (i = 0; i < tegra->soc->max_num_wakes; i++) { + struct irq_data *data; + + tegra->wake_irqs[i] = platform_get_irq_optional(pdev, i + WAKE_IRQ_START_INDEX); + if (tegra->wake_irqs[i] < 0) + break; + + data = irq_get_irq_data(tegra->wake_irqs[i]); + if (!data) { + dev_warn(tegra->dev, "get wake event %d irq data fail\n", i); + break; + } + + irq_set_irq_type(tegra->wake_irqs[i], irqd_get_trigger_type(data)); + } + + tegra->num_wakes = i; + dev_dbg(tegra->dev, "setup %d wake events\n", tegra->num_wakes); + + return 0; +} + +static int tegra_xusb_probe(struct platform_device *pdev) +{ + struct tegra_xusb *tegra; + struct device_node *np; + struct resource *regs; + struct xhci_hcd *xhci; + unsigned int i, j, k; + struct phy *phy; + int err; + + BUILD_BUG_ON(sizeof(struct tegra_xusb_fw_header) != 256); + + tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL); + if (!tegra) + return -ENOMEM; + + tegra->soc = of_device_get_match_data(&pdev->dev); + mutex_init(&tegra->lock); + tegra->dev = &pdev->dev; + + err = tegra_xusb_init_context(tegra); + if (err < 0) + return err; + + tegra->regs = devm_platform_get_and_ioremap_resource(pdev, 0, ®s); + if (IS_ERR(tegra->regs)) + return PTR_ERR(tegra->regs); + + tegra->fpci_base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(tegra->fpci_base)) + return PTR_ERR(tegra->fpci_base); + + if (tegra->soc->has_ipfs) { + tegra->ipfs_base = devm_platform_ioremap_resource(pdev, 2); + if (IS_ERR(tegra->ipfs_base)) + return PTR_ERR(tegra->ipfs_base); + } else if (tegra->soc->has_bar2) { + tegra->bar2_base = devm_platform_get_and_ioremap_resource(pdev, 2, &tegra->bar2); + if (IS_ERR(tegra->bar2_base)) + return PTR_ERR(tegra->bar2_base); + } + + tegra->xhci_irq = platform_get_irq(pdev, 0); + if (tegra->xhci_irq < 0) + return tegra->xhci_irq; + + tegra->mbox_irq = platform_get_irq(pdev, 1); + if (tegra->mbox_irq < 0) + return tegra->mbox_irq; + + err = tegra_xusb_setup_wakeup(pdev, tegra); + if (err) + return err; + + tegra->padctl = tegra_xusb_padctl_get(&pdev->dev); + if (IS_ERR(tegra->padctl)) + return PTR_ERR(tegra->padctl); + + np = of_parse_phandle(pdev->dev.of_node, "nvidia,xusb-padctl", 0); + if (!np) { + err = -ENODEV; + goto put_padctl; + } + + tegra->padctl_irq = of_irq_get(np, 0); + if (tegra->padctl_irq == -EPROBE_DEFER) { + err = tegra->padctl_irq; + goto put_padctl; + } else if (tegra->padctl_irq <= 0) { + /* Older device-trees don't have padctrl interrupt */ + tegra->padctl_irq = 0; + dev_dbg(&pdev->dev, + "%pOF is missing an interrupt, disabling PM support\n", np); + } + + tegra->host_clk = devm_clk_get(&pdev->dev, "xusb_host"); + if (IS_ERR(tegra->host_clk)) { + err = PTR_ERR(tegra->host_clk); + dev_err(&pdev->dev, "failed to get xusb_host: %d\n", err); + goto put_padctl; + } + + tegra->falcon_clk = devm_clk_get(&pdev->dev, "xusb_falcon_src"); + if (IS_ERR(tegra->falcon_clk)) { + err = PTR_ERR(tegra->falcon_clk); + dev_err(&pdev->dev, "failed to get xusb_falcon_src: %d\n", err); + goto put_padctl; + } + + tegra->ss_clk = devm_clk_get(&pdev->dev, "xusb_ss"); + if (IS_ERR(tegra->ss_clk)) { + err = PTR_ERR(tegra->ss_clk); + dev_err(&pdev->dev, "failed to get xusb_ss: %d\n", err); + goto put_padctl; + } + + tegra->ss_src_clk = devm_clk_get(&pdev->dev, "xusb_ss_src"); + if (IS_ERR(tegra->ss_src_clk)) { + err = PTR_ERR(tegra->ss_src_clk); + dev_err(&pdev->dev, "failed to get xusb_ss_src: %d\n", err); + goto put_padctl; + } + + tegra->hs_src_clk = devm_clk_get(&pdev->dev, "xusb_hs_src"); + if (IS_ERR(tegra->hs_src_clk)) { + err = PTR_ERR(tegra->hs_src_clk); + dev_err(&pdev->dev, "failed to get xusb_hs_src: %d\n", err); + goto put_padctl; + } + + tegra->fs_src_clk = devm_clk_get(&pdev->dev, "xusb_fs_src"); + if (IS_ERR(tegra->fs_src_clk)) { + err = PTR_ERR(tegra->fs_src_clk); + dev_err(&pdev->dev, "failed to get xusb_fs_src: %d\n", err); + goto put_padctl; + } + + tegra->pll_u_480m = devm_clk_get(&pdev->dev, "pll_u_480m"); + if (IS_ERR(tegra->pll_u_480m)) { + err = PTR_ERR(tegra->pll_u_480m); + dev_err(&pdev->dev, "failed to get pll_u_480m: %d\n", err); + goto put_padctl; + } + + tegra->clk_m = devm_clk_get(&pdev->dev, "clk_m"); + if (IS_ERR(tegra->clk_m)) { + err = PTR_ERR(tegra->clk_m); + dev_err(&pdev->dev, "failed to get clk_m: %d\n", err); + goto put_padctl; + } + + tegra->pll_e = devm_clk_get(&pdev->dev, "pll_e"); + if (IS_ERR(tegra->pll_e)) { + err = PTR_ERR(tegra->pll_e); + dev_err(&pdev->dev, "failed to get pll_e: %d\n", err); + goto put_padctl; + } + + if (!of_property_present(pdev->dev.of_node, "power-domains")) { + tegra->host_rst = devm_reset_control_get(&pdev->dev, + "xusb_host"); + if (IS_ERR(tegra->host_rst)) { + err = PTR_ERR(tegra->host_rst); + dev_err(&pdev->dev, + "failed to get xusb_host reset: %d\n", err); + goto put_padctl; + } + + tegra->ss_rst = devm_reset_control_get(&pdev->dev, "xusb_ss"); + if (IS_ERR(tegra->ss_rst)) { + err = PTR_ERR(tegra->ss_rst); + dev_err(&pdev->dev, "failed to get xusb_ss reset: %d\n", + err); + goto put_padctl; + } + + tegra->pmc = devm_tegra_pmc_get(&pdev->dev); + if (IS_ERR(tegra->pmc)) { + err = dev_err_probe(&pdev->dev, PTR_ERR(tegra->pmc), + "failed to get PMC\n"); + goto put_padctl; + } + } else { + err = tegra_xusb_powerdomain_init(&pdev->dev, tegra); + if (err) + goto put_powerdomains; + } + + tegra->supplies = devm_kcalloc(&pdev->dev, tegra->soc->num_supplies, + sizeof(*tegra->supplies), GFP_KERNEL); + if (!tegra->supplies) { + err = -ENOMEM; + goto put_powerdomains; + } + + regulator_bulk_set_supply_names(tegra->supplies, + tegra->soc->supply_names, + tegra->soc->num_supplies); + + err = devm_regulator_bulk_get(&pdev->dev, tegra->soc->num_supplies, + tegra->supplies); + if (err) { + dev_err(&pdev->dev, "failed to get regulators: %d\n", err); + goto put_powerdomains; + } + + for (i = 0; i < tegra->soc->num_types; i++) { + if (!strncmp(tegra->soc->phy_types[i].name, "usb2", 4)) + tegra->num_usb_phys = tegra->soc->phy_types[i].num; + tegra->num_phys += tegra->soc->phy_types[i].num; + } + + tegra->phys = devm_kcalloc(&pdev->dev, tegra->num_phys, + sizeof(*tegra->phys), GFP_KERNEL); + if (!tegra->phys) { + err = -ENOMEM; + goto put_powerdomains; + } + + for (i = 0, k = 0; i < tegra->soc->num_types; i++) { + char prop[8]; + + for (j = 0; j < tegra->soc->phy_types[i].num; j++) { + snprintf(prop, sizeof(prop), "%s-%d", + tegra->soc->phy_types[i].name, j); + + phy = devm_phy_optional_get(&pdev->dev, prop); + if (IS_ERR(phy)) { + dev_err(&pdev->dev, + "failed to get PHY %s: %ld\n", prop, + PTR_ERR(phy)); + err = PTR_ERR(phy); + goto put_powerdomains; + } + + tegra->phys[k++] = phy; + } + } + + tegra->hcd = usb_create_hcd(&tegra_xhci_hc_driver, &pdev->dev, + dev_name(&pdev->dev)); + if (!tegra->hcd) { + err = -ENOMEM; + goto put_powerdomains; + } + + tegra->hcd->skip_phy_initialization = 1; + tegra->hcd->regs = tegra->regs; + tegra->hcd->rsrc_start = regs->start; + tegra->hcd->rsrc_len = resource_size(regs); + + /* + * This must happen after usb_create_hcd(), because usb_create_hcd() + * will overwrite the drvdata of the device with the hcd it creates. + */ + platform_set_drvdata(pdev, tegra); + + err = tegra_xusb_clk_enable(tegra); + if (err) { + dev_err(tegra->dev, "failed to enable clocks: %d\n", err); + goto put_hcd; + } + + err = regulator_bulk_enable(tegra->soc->num_supplies, tegra->supplies); + if (err) { + dev_err(tegra->dev, "failed to enable regulators: %d\n", err); + goto disable_clk; + } + + err = tegra_xusb_phy_enable(tegra); + if (err < 0) { + dev_err(&pdev->dev, "failed to enable PHYs: %d\n", err); + goto disable_regulator; + } + + /* + * The XUSB Falcon microcontroller can only address 40 bits, so set + * the DMA mask accordingly. + */ + err = dma_set_mask_and_coherent(tegra->dev, DMA_BIT_MASK(40)); + if (err < 0) { + dev_err(&pdev->dev, "failed to set DMA mask: %d\n", err); + goto disable_phy; + } + + if (tegra->soc->firmware) { + err = tegra_xusb_request_firmware(tegra); + if (err < 0) { + dev_err(&pdev->dev, + "failed to request firmware: %d\n", err); + goto disable_phy; + } + } + + err = tegra_xusb_unpowergate_partitions(tegra); + if (err) + goto free_firmware; + + tegra_xusb_config(tegra); + + err = tegra_xusb_load_firmware(tegra); + if (err < 0) { + dev_err(&pdev->dev, "failed to load firmware: %d\n", err); + goto powergate; + } + + err = usb_add_hcd(tegra->hcd, tegra->xhci_irq, IRQF_SHARED); + if (err < 0) { + dev_err(&pdev->dev, "failed to add USB HCD: %d\n", err); + goto powergate; + } + + device_wakeup_enable(tegra->hcd->self.controller); + + xhci = hcd_to_xhci(tegra->hcd); + + xhci->shared_hcd = usb_create_shared_hcd(&tegra_xhci_hc_driver, + &pdev->dev, + dev_name(&pdev->dev), + tegra->hcd); + if (!xhci->shared_hcd) { + dev_err(&pdev->dev, "failed to create shared HCD\n"); + err = -ENOMEM; + goto remove_usb2; + } + + if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + + err = usb_add_hcd(xhci->shared_hcd, tegra->xhci_irq, IRQF_SHARED); + if (err < 0) { + dev_err(&pdev->dev, "failed to add shared HCD: %d\n", err); + goto put_usb3; + } + + err = devm_request_threaded_irq(&pdev->dev, tegra->mbox_irq, + tegra_xusb_mbox_irq, + tegra_xusb_mbox_thread, 0, + dev_name(&pdev->dev), tegra); + if (err < 0) { + dev_err(&pdev->dev, "failed to request IRQ: %d\n", err); + goto remove_usb3; + } + + if (tegra->padctl_irq) { + err = devm_request_threaded_irq(&pdev->dev, tegra->padctl_irq, + NULL, tegra_xusb_padctl_irq, + IRQF_ONESHOT, dev_name(&pdev->dev), + tegra); + if (err < 0) { + dev_err(&pdev->dev, "failed to request padctl IRQ: %d\n", err); + goto remove_usb3; + } + } + + err = tegra_xusb_enable_firmware_messages(tegra); + if (err < 0) { + dev_err(&pdev->dev, "failed to enable messages: %d\n", err); + goto remove_usb3; + } + + err = tegra_xusb_init_usb_phy(tegra); + if (err < 0) { + dev_err(&pdev->dev, "failed to init USB PHY: %d\n", err); + goto remove_usb3; + } + + /* Enable wake for both USB 2.0 and USB 3.0 roothubs */ + device_init_wakeup(&tegra->hcd->self.root_hub->dev, true); + device_init_wakeup(&xhci->shared_hcd->self.root_hub->dev, true); + + pm_runtime_use_autosuspend(tegra->dev); + pm_runtime_set_autosuspend_delay(tegra->dev, 2000); + pm_runtime_mark_last_busy(tegra->dev); + pm_runtime_set_active(tegra->dev); + + if (tegra->padctl_irq) { + device_init_wakeup(tegra->dev, true); + pm_runtime_enable(tegra->dev); + } + + return 0; + +remove_usb3: + usb_remove_hcd(xhci->shared_hcd); +put_usb3: + usb_put_hcd(xhci->shared_hcd); +remove_usb2: + usb_remove_hcd(tegra->hcd); +powergate: + tegra_xusb_powergate_partitions(tegra); +free_firmware: + dma_free_coherent(&pdev->dev, tegra->fw.size, tegra->fw.virt, + tegra->fw.phys); +disable_phy: + tegra_xusb_phy_disable(tegra); +disable_regulator: + regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); +disable_clk: + tegra_xusb_clk_disable(tegra); +put_hcd: + usb_put_hcd(tegra->hcd); +put_powerdomains: + tegra_xusb_powerdomain_remove(&pdev->dev, tegra); +put_padctl: + of_node_put(np); + tegra_xusb_padctl_put(tegra->padctl); + return err; +} + +static void tegra_xusb_disable(struct tegra_xusb *tegra) +{ + tegra_xusb_powergate_partitions(tegra); + tegra_xusb_powerdomain_remove(tegra->dev, tegra); + tegra_xusb_phy_disable(tegra); + tegra_xusb_clk_disable(tegra); + regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); +} + +static void tegra_xusb_remove(struct platform_device *pdev) +{ + struct tegra_xusb *tegra = platform_get_drvdata(pdev); + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + + tegra_xusb_deinit_usb_phy(tegra); + + pm_runtime_get_sync(&pdev->dev); + usb_remove_hcd(xhci->shared_hcd); + usb_put_hcd(xhci->shared_hcd); + xhci->shared_hcd = NULL; + usb_remove_hcd(tegra->hcd); + usb_put_hcd(tegra->hcd); + + dma_free_coherent(&pdev->dev, tegra->fw.size, tegra->fw.virt, + tegra->fw.phys); + + if (tegra->padctl_irq) + pm_runtime_disable(&pdev->dev); + + pm_runtime_put(&pdev->dev); + + tegra_xusb_disable(tegra); + tegra_xusb_padctl_put(tegra->padctl); +} + +static void tegra_xusb_shutdown(struct platform_device *pdev) +{ + struct tegra_xusb *tegra = platform_get_drvdata(pdev); + + pm_runtime_get_sync(&pdev->dev); + disable_irq(tegra->xhci_irq); + xhci_shutdown(tegra->hcd); + tegra_xusb_disable(tegra); +} + +static bool xhci_hub_ports_suspended(struct xhci_hub *hub) +{ + struct device *dev = hub->hcd->self.controller; + bool status = true; + unsigned int i; + u32 value; + + for (i = 0; i < hub->num_ports; i++) { + value = xhci_portsc_readl(hub->ports[i]); + if ((value & PORT_PE) == 0) + continue; + + if ((value & PORT_PLS_MASK) != XDEV_U3) { + dev_info(dev, "%u-%u isn't suspended: %#010x\n", + hub->hcd->self.busnum, i + 1, value); + status = false; + } + } + + return status; +} + +static int tegra_xusb_check_ports(struct tegra_xusb *tegra) +{ + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct xhci_bus_state *bus_state = &xhci->usb2_rhub.bus_state; + unsigned long flags; + int err = 0; + + if (bus_state->bus_suspended) { + /* xusb_hub_suspend() has just directed one or more USB2 port(s) + * to U3 state, it takes 3ms to enter U3. + */ + usleep_range(3000, 4000); + } + + spin_lock_irqsave(&xhci->lock, flags); + + if (!xhci_hub_ports_suspended(&xhci->usb2_rhub) || + !xhci_hub_ports_suspended(&xhci->usb3_rhub)) + err = -EBUSY; + + spin_unlock_irqrestore(&xhci->lock, flags); + + return err; +} + +static void tegra_xusb_save_context(struct tegra_xusb *tegra) +{ + const struct tegra_xusb_context_soc *soc = tegra->soc->context; + struct tegra_xusb_context *ctx = &tegra->context; + unsigned int i; + + if (soc->ipfs.num_offsets > 0) { + for (i = 0; i < soc->ipfs.num_offsets; i++) + ctx->ipfs[i] = ipfs_readl(tegra, soc->ipfs.offsets[i]); + } + + if (soc->fpci.num_offsets > 0) { + for (i = 0; i < soc->fpci.num_offsets; i++) + ctx->fpci[i] = fpci_readl(tegra, soc->fpci.offsets[i]); + } +} + +static void tegra_xusb_restore_context(struct tegra_xusb *tegra) +{ + const struct tegra_xusb_context_soc *soc = tegra->soc->context; + struct tegra_xusb_context *ctx = &tegra->context; + unsigned int i; + + if (soc->fpci.num_offsets > 0) { + for (i = 0; i < soc->fpci.num_offsets; i++) + fpci_writel(tegra, ctx->fpci[i], soc->fpci.offsets[i]); + } + + if (soc->ipfs.num_offsets > 0) { + for (i = 0; i < soc->ipfs.num_offsets; i++) + ipfs_writel(tegra, ctx->ipfs[i], soc->ipfs.offsets[i]); + } +} + +static enum usb_device_speed tegra_xhci_portsc_to_speed(struct tegra_xusb *tegra, u32 portsc) +{ + if (DEV_LOWSPEED(portsc)) + return USB_SPEED_LOW; + + if (DEV_HIGHSPEED(portsc)) + return USB_SPEED_HIGH; + + if (DEV_FULLSPEED(portsc)) + return USB_SPEED_FULL; + + if (DEV_SUPERSPEED_ANY(portsc)) + return USB_SPEED_SUPER; + + return USB_SPEED_UNKNOWN; +} + +static void tegra_xhci_enable_phy_sleepwalk_wake(struct tegra_xusb *tegra) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + enum usb_device_speed speed; + struct phy *phy; + unsigned int index, offset; + unsigned int i, j, k; + struct xhci_hub *rhub; + u32 portsc; + + for (i = 0, k = 0; i < tegra->soc->num_types; i++) { + if (strcmp(tegra->soc->phy_types[i].name, "usb3") == 0) + rhub = &xhci->usb3_rhub; + else + rhub = &xhci->usb2_rhub; + + if (strcmp(tegra->soc->phy_types[i].name, "hsic") == 0) + offset = tegra->soc->ports.usb2.count; + else + offset = 0; + + for (j = 0; j < tegra->soc->phy_types[i].num; j++) { + phy = tegra->phys[k++]; + + if (!phy) + continue; + + index = j + offset; + + if (index >= rhub->num_ports) + continue; + + if (!is_host_mode_phy(tegra, i, j)) + continue; + + portsc = xhci_portsc_readl(rhub->ports[index]); + speed = tegra_xhci_portsc_to_speed(tegra, portsc); + tegra_xusb_padctl_enable_phy_sleepwalk(padctl, phy, speed); + tegra_xusb_padctl_enable_phy_wake(padctl, phy); + } + } +} + +static void tegra_xhci_disable_phy_wake(struct tegra_xusb *tegra) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + unsigned int i; + + for (i = 0; i < tegra->num_usb_phys; i++) { + struct phy *phy = tegra_xusb_get_phy(tegra, "usb2", i); + + if (!phy) + continue; + + if (tegra_xusb_padctl_remote_wake_detected(padctl, phy)) + tegra_phy_xusb_utmi_pad_power_on(phy); + } + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + if (tegra_xusb_padctl_remote_wake_detected(padctl, tegra->phys[i])) + dev_dbg(tegra->dev, "%pOF remote wake detected\n", + tegra->phys[i]->dev.of_node); + + tegra_xusb_padctl_disable_phy_wake(padctl, tegra->phys[i]); + } +} + +static void tegra_xhci_disable_phy_sleepwalk(struct tegra_xusb *tegra) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + unsigned int i; + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + tegra_xusb_padctl_disable_phy_sleepwalk(padctl, tegra->phys[i]); + } +} + +static void tegra_xhci_program_utmi_power_lp0_exit(struct tegra_xusb *tegra) +{ + unsigned int i, index_to_usb2; + struct phy *phy; + + for (i = 0; i < tegra->soc->num_types; i++) { + if (strcmp(tegra->soc->phy_types[i].name, "usb2") == 0) + index_to_usb2 = i; + } + + for (i = 0; i < tegra->num_usb_phys; i++) { + if (!is_host_mode_phy(tegra, index_to_usb2, i)) + continue; + + phy = tegra_xusb_get_phy(tegra, "usb2", i); + if (tegra->lp0_utmi_pad_mask & BIT(i)) + tegra_phy_xusb_utmi_pad_power_on(phy); + else + tegra_phy_xusb_utmi_pad_power_down(phy); + } +} + +static int tegra_xusb_enter_elpg(struct tegra_xusb *tegra, bool is_auto_resume) +{ + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct device *dev = tegra->dev; + bool wakeup = is_auto_resume ? true : device_may_wakeup(dev); + unsigned int i; + int err; + u32 usbcmd; + u32 portsc; + + dev_dbg(dev, "entering ELPG\n"); + + usbcmd = readl(&xhci->op_regs->command); + usbcmd &= ~CMD_EIE; + writel(usbcmd, &xhci->op_regs->command); + + err = tegra_xusb_check_ports(tegra); + if (err < 0) { + dev_err(tegra->dev, "not all ports suspended: %d\n", err); + goto out; + } + + for (i = 0; i < xhci->usb2_rhub.num_ports; i++) { + if (!xhci->usb2_rhub.ports[i]) + continue; + portsc = xhci_portsc_readl(xhci->usb2_rhub.ports[i]); + tegra->lp0_utmi_pad_mask &= ~BIT(i); + if (((portsc & PORT_PLS_MASK) == XDEV_U3) || ((portsc & DEV_SPEED_MASK) == XDEV_FS)) + tegra->lp0_utmi_pad_mask |= BIT(i); + } + + err = xhci_suspend(xhci, wakeup); + if (err < 0) { + dev_err(tegra->dev, "failed to suspend XHCI: %d\n", err); + goto out; + } + + tegra_xusb_save_context(tegra); + + if (wakeup) + tegra_xhci_enable_phy_sleepwalk_wake(tegra); + + tegra_xusb_powergate_partitions(tegra); + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + phy_power_off(tegra->phys[i]); + if (!wakeup) + phy_exit(tegra->phys[i]); + } + + tegra_xusb_clk_disable(tegra); + +out: + if (!err) + dev_dbg(tegra->dev, "entering ELPG done\n"); + else { + usbcmd = readl(&xhci->op_regs->command); + usbcmd |= CMD_EIE; + writel(usbcmd, &xhci->op_regs->command); + + dev_dbg(tegra->dev, "entering ELPG failed\n"); + pm_runtime_mark_last_busy(tegra->dev); + } + + return err; +} + +static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool is_auto_resume) +{ + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct device *dev = tegra->dev; + bool wakeup = is_auto_resume ? true : device_may_wakeup(dev); + unsigned int i; + u32 usbcmd; + int err; + + dev_dbg(dev, "exiting ELPG\n"); + pm_runtime_mark_last_busy(tegra->dev); + + err = tegra_xusb_clk_enable(tegra); + if (err < 0) { + dev_err(tegra->dev, "failed to enable clocks: %d\n", err); + goto out; + } + + err = tegra_xusb_unpowergate_partitions(tegra); + if (err) + goto disable_clks; + + if (wakeup) + tegra_xhci_disable_phy_wake(tegra); + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + if (!wakeup) + phy_init(tegra->phys[i]); + + phy_power_on(tegra->phys[i]); + } + if (tegra->suspended) + tegra_xhci_program_utmi_power_lp0_exit(tegra); + + tegra_xusb_config(tegra); + tegra_xusb_restore_context(tegra); + + err = tegra_xusb_load_firmware(tegra); + if (err < 0) { + dev_err(tegra->dev, "failed to load firmware: %d\n", err); + goto disable_phy; + } + + err = __tegra_xusb_enable_firmware_messages(tegra); + if (err < 0) { + dev_err(tegra->dev, "failed to enable messages: %d\n", err); + goto disable_phy; + } + + if (wakeup) + tegra_xhci_disable_phy_sleepwalk(tegra); + + err = xhci_resume(xhci, false, is_auto_resume); + if (err < 0) { + dev_err(tegra->dev, "failed to resume XHCI: %d\n", err); + goto disable_phy; + } + + usbcmd = readl(&xhci->op_regs->command); + usbcmd |= CMD_EIE; + writel(usbcmd, &xhci->op_regs->command); + + goto out; + +disable_phy: + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + phy_power_off(tegra->phys[i]); + if (!wakeup) + phy_exit(tegra->phys[i]); + } + tegra_xusb_powergate_partitions(tegra); +disable_clks: + tegra_xusb_clk_disable(tegra); +out: + if (!err) + dev_dbg(dev, "exiting ELPG done\n"); + else + dev_dbg(dev, "exiting ELPG failed\n"); + + return err; +} + +static __maybe_unused int tegra_xusb_suspend(struct device *dev) +{ + struct tegra_xusb *tegra = dev_get_drvdata(dev); + int err; + + synchronize_irq(tegra->mbox_irq); + + mutex_lock(&tegra->lock); + + if (pm_runtime_suspended(dev)) { + err = tegra_xusb_exit_elpg(tegra, true); + if (err < 0) + goto out; + } + + err = tegra_xusb_enter_elpg(tegra, false); + if (err < 0) { + if (pm_runtime_suspended(dev)) { + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + goto out; + } + +out: + if (!err) { + tegra->suspended = true; + pm_runtime_disable(dev); + + if (device_may_wakeup(dev)) { + unsigned int i; + + if (enable_irq_wake(tegra->padctl_irq)) + dev_err(dev, "failed to enable padctl wakes\n"); + + for (i = 0; i < tegra->num_wakes; i++) + enable_irq_wake(tegra->wake_irqs[i]); + } + } + + mutex_unlock(&tegra->lock); + + return err; +} + +static __maybe_unused int tegra_xusb_resume(struct device *dev) +{ + struct tegra_xusb *tegra = dev_get_drvdata(dev); + int err; + + mutex_lock(&tegra->lock); + + if (!tegra->suspended) { + mutex_unlock(&tegra->lock); + return 0; + } + + err = tegra_xusb_exit_elpg(tegra, false); + if (err < 0) { + mutex_unlock(&tegra->lock); + return err; + } + + if (device_may_wakeup(dev)) { + unsigned int i; + + if (disable_irq_wake(tegra->padctl_irq)) + dev_err(dev, "failed to disable padctl wakes\n"); + + for (i = 0; i < tegra->num_wakes; i++) + disable_irq_wake(tegra->wake_irqs[i]); + } + tegra->suspended = false; + mutex_unlock(&tegra->lock); + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} + +static __maybe_unused int tegra_xusb_runtime_suspend(struct device *dev) +{ + struct tegra_xusb *tegra = dev_get_drvdata(dev); + int ret; + + synchronize_irq(tegra->mbox_irq); + mutex_lock(&tegra->lock); + ret = tegra_xusb_enter_elpg(tegra, true); + mutex_unlock(&tegra->lock); + + return ret; +} + +static __maybe_unused int tegra_xusb_runtime_resume(struct device *dev) +{ + struct tegra_xusb *tegra = dev_get_drvdata(dev); + int err; + + mutex_lock(&tegra->lock); + err = tegra_xusb_exit_elpg(tegra, true); + mutex_unlock(&tegra->lock); + + return err; +} + +static const struct dev_pm_ops tegra_xusb_pm_ops = { + SET_RUNTIME_PM_OPS(tegra_xusb_runtime_suspend, + tegra_xusb_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(tegra_xusb_suspend, tegra_xusb_resume) +}; + +static const char * const tegra124_supply_names[] = { + "avddio-pex", + "dvddio-pex", + "avdd-usb", + "hvdd-usb-ss", +}; + +static const struct tegra_xusb_phy_type tegra124_phy_types[] = { + { .name = "usb3", .num = 2, }, + { .name = "usb2", .num = 3, }, + { .name = "hsic", .num = 2, }, +}; + +static const unsigned int tegra124_xusb_context_ipfs[] = { + IPFS_XUSB_HOST_MSI_BAR_SZ_0, + IPFS_XUSB_HOST_MSI_AXI_BAR_ST_0, + IPFS_XUSB_HOST_MSI_FPCI_BAR_ST_0, + IPFS_XUSB_HOST_MSI_VEC0_0, + IPFS_XUSB_HOST_MSI_EN_VEC0_0, + IPFS_XUSB_HOST_FPCI_ERROR_MASKS_0, + IPFS_XUSB_HOST_INTR_MASK_0, + IPFS_XUSB_HOST_INTR_ENABLE_0, + IPFS_XUSB_HOST_UFPCI_CONFIG_0, + IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0, + IPFS_XUSB_HOST_MCCIF_FIFOCTRL_0, +}; + +static const unsigned int tegra124_xusb_context_fpci[] = { + XUSB_CFG_ARU_CONTEXT_HS_PLS, + XUSB_CFG_ARU_CONTEXT_FS_PLS, + XUSB_CFG_ARU_CONTEXT_HSFS_SPEED, + XUSB_CFG_ARU_CONTEXT_HSFS_PP, + XUSB_CFG_ARU_CONTEXT, + XUSB_CFG_AXI_CFG, + XUSB_CFG_24, + XUSB_CFG_16, +}; + +static const struct tegra_xusb_context_soc tegra124_xusb_context = { + .ipfs = { + .num_offsets = ARRAY_SIZE(tegra124_xusb_context_ipfs), + .offsets = tegra124_xusb_context_ipfs, + }, + .fpci = { + .num_offsets = ARRAY_SIZE(tegra124_xusb_context_fpci), + .offsets = tegra124_xusb_context_fpci, + }, +}; + +static const struct tegra_xusb_soc_ops tegra124_ops = { + .mbox_reg_readl = &fpci_readl, + .mbox_reg_writel = &fpci_writel, + .csb_reg_readl = &fpci_csb_readl, + .csb_reg_writel = &fpci_csb_writel, +}; + +static const struct tegra_xusb_soc tegra124_soc = { + .firmware = "nvidia/tegra124/xusb.bin", + .supply_names = tegra124_supply_names, + .num_supplies = ARRAY_SIZE(tegra124_supply_names), + .phy_types = tegra124_phy_types, + .num_types = ARRAY_SIZE(tegra124_phy_types), + .context = &tegra124_xusb_context, + .ports = { + .usb2 = { .offset = 4, .count = 4, }, + .hsic = { .offset = 6, .count = 2, }, + .usb3 = { .offset = 0, .count = 2, }, + }, + .scale_ss_clock = true, + .has_ipfs = true, + .otg_reset_sspi = false, + .otg_set_port_power = true, + .ops = &tegra124_ops, + .mbox = { + .cmd = 0xe4, + .data_in = 0xe8, + .data_out = 0xec, + .owner = 0xf0, + .smi_intr = XUSB_CFG_ARU_SMI_INTR, + }, +}; +#if IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC) || IS_ENABLED(CONFIG_ARCH_TEGRA_132_SOC) +MODULE_FIRMWARE("nvidia/tegra124/xusb.bin"); +#endif + +static const char * const tegra210_supply_names[] = { + "dvddio-pex", + "hvddio-pex", + "avdd-usb", +}; + +static const struct tegra_xusb_phy_type tegra210_phy_types[] = { + { .name = "usb3", .num = 4, }, + { .name = "usb2", .num = 4, }, + { .name = "hsic", .num = 1, }, +}; + +static const struct tegra_xusb_soc tegra210_soc = { + .firmware = "nvidia/tegra210/xusb.bin", + .supply_names = tegra210_supply_names, + .num_supplies = ARRAY_SIZE(tegra210_supply_names), + .phy_types = tegra210_phy_types, + .num_types = ARRAY_SIZE(tegra210_phy_types), + .context = &tegra124_xusb_context, + .ports = { + .usb2 = { .offset = 4, .count = 4, }, + .hsic = { .offset = 8, .count = 1, }, + .usb3 = { .offset = 0, .count = 4, }, + }, + .scale_ss_clock = false, + .has_ipfs = true, + .otg_reset_sspi = true, + .otg_set_port_power = true, + .ops = &tegra124_ops, + .mbox = { + .cmd = 0xe4, + .data_in = 0xe8, + .data_out = 0xec, + .owner = 0xf0, + .smi_intr = XUSB_CFG_ARU_SMI_INTR, + }, +}; +#if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) +MODULE_FIRMWARE("nvidia/tegra210/xusb.bin"); +#endif + +static const char * const tegra186_supply_names[] = { +}; +#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) +MODULE_FIRMWARE("nvidia/tegra186/xusb.bin"); +#endif + +static const struct tegra_xusb_phy_type tegra186_phy_types[] = { + { .name = "usb3", .num = 3, }, + { .name = "usb2", .num = 3, }, + { .name = "hsic", .num = 1, }, +}; + +static const struct tegra_xusb_context_soc tegra186_xusb_context = { + .fpci = { + .num_offsets = ARRAY_SIZE(tegra124_xusb_context_fpci), + .offsets = tegra124_xusb_context_fpci, + }, +}; + +static const struct tegra_xusb_soc tegra186_soc = { + .firmware = "nvidia/tegra186/xusb.bin", + .supply_names = tegra186_supply_names, + .num_supplies = ARRAY_SIZE(tegra186_supply_names), + .phy_types = tegra186_phy_types, + .num_types = ARRAY_SIZE(tegra186_phy_types), + .context = &tegra186_xusb_context, + .ports = { + .usb3 = { .offset = 0, .count = 3, }, + .usb2 = { .offset = 3, .count = 3, }, + .hsic = { .offset = 6, .count = 1, }, + }, + .scale_ss_clock = false, + .has_ipfs = false, + .otg_reset_sspi = false, + .otg_set_port_power = true, + .ops = &tegra124_ops, + .mbox = { + .cmd = 0xe4, + .data_in = 0xe8, + .data_out = 0xec, + .owner = 0xf0, + .smi_intr = XUSB_CFG_ARU_SMI_INTR, + }, + .lpm_support = true, +}; + +static const char * const tegra194_supply_names[] = { +}; + +static const struct tegra_xusb_phy_type tegra194_phy_types[] = { + { .name = "usb3", .num = 4, }, + { .name = "usb2", .num = 4, }, +}; + +static const struct tegra_xusb_soc tegra194_soc = { + .firmware = "nvidia/tegra194/xusb.bin", + .supply_names = tegra194_supply_names, + .num_supplies = ARRAY_SIZE(tegra194_supply_names), + .phy_types = tegra194_phy_types, + .num_types = ARRAY_SIZE(tegra194_phy_types), + .context = &tegra186_xusb_context, + .ports = { + .usb3 = { .offset = 0, .count = 4, }, + .usb2 = { .offset = 4, .count = 4, }, + }, + .scale_ss_clock = false, + .has_ipfs = false, + .otg_reset_sspi = false, + .otg_set_port_power = false, + .ops = &tegra124_ops, + .mbox = { + .cmd = 0x68, + .data_in = 0x6c, + .data_out = 0x70, + .owner = 0x74, + .smi_intr = XUSB_CFG_ARU_SMI_INTR, + }, + .lpm_support = true, +}; +#if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) +MODULE_FIRMWARE("nvidia/tegra194/xusb.bin"); +#endif + +static const struct tegra_xusb_soc_ops tegra234_ops = { + .mbox_reg_readl = &bar2_readl, + .mbox_reg_writel = &bar2_writel, + .csb_reg_readl = &bar2_csb_readl, + .csb_reg_writel = &bar2_csb_writel, +}; + +static const struct tegra_xusb_soc tegra234_soc = { + .supply_names = tegra194_supply_names, + .num_supplies = ARRAY_SIZE(tegra194_supply_names), + .phy_types = tegra194_phy_types, + .num_types = ARRAY_SIZE(tegra194_phy_types), + .max_num_wakes = 7, + .context = &tegra186_xusb_context, + .ports = { + .usb3 = { .offset = 0, .count = 4, }, + .usb2 = { .offset = 4, .count = 4, }, + }, + .scale_ss_clock = false, + .has_ipfs = false, + .otg_reset_sspi = false, + .otg_set_port_power = false, + .ops = &tegra234_ops, + .mbox = { + .cmd = XUSB_BAR2_ARU_MBOX_CMD, + .data_in = XUSB_BAR2_ARU_MBOX_DATA_IN, + .data_out = XUSB_BAR2_ARU_MBOX_DATA_OUT, + .owner = XUSB_BAR2_ARU_MBOX_OWNER, + .smi_intr = XUSB_BAR2_ARU_SMI_INTR, + }, + .lpm_support = true, + .has_bar2 = true, +}; + +static const struct of_device_id tegra_xusb_of_match[] = { + { .compatible = "nvidia,tegra124-xusb", .data = &tegra124_soc }, + { .compatible = "nvidia,tegra210-xusb", .data = &tegra210_soc }, + { .compatible = "nvidia,tegra186-xusb", .data = &tegra186_soc }, + { .compatible = "nvidia,tegra194-xusb", .data = &tegra194_soc }, + { .compatible = "nvidia,tegra234-xusb", .data = &tegra234_soc }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_xusb_of_match); + +static struct platform_driver tegra_xusb_driver = { + .probe = tegra_xusb_probe, + .remove = tegra_xusb_remove, + .shutdown = tegra_xusb_shutdown, + .driver = { + .name = "tegra-xusb", + .pm = &tegra_xusb_pm_ops, + .of_match_table = tegra_xusb_of_match, + }, +}; + +static void tegra_xhci_quirks(struct device *dev, struct xhci_hcd *xhci) +{ + struct tegra_xusb *tegra = dev_get_drvdata(dev); + + if (tegra && tegra->soc->lpm_support) + xhci->quirks |= XHCI_LPM_SUPPORT; +} + +static int tegra_xhci_setup(struct usb_hcd *hcd) +{ + return xhci_gen_setup(hcd, tegra_xhci_quirks); +} + +static int tegra_xhci_hub_control(struct usb_hcd *hcd, u16 type_req, u16 value, u16 index, + char *buf, u16 length) +{ + struct tegra_xusb *tegra = dev_get_drvdata(hcd->self.controller); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_hub *rhub; + struct xhci_bus_state *bus_state; + int port = (index & 0xff) - 1; + unsigned int i; + struct xhci_port **ports; + u32 portsc; + int ret; + struct phy *phy; + + rhub = &xhci->usb2_rhub; + bus_state = &rhub->bus_state; + if (bus_state->resuming_ports && hcd->speed == HCD_USB2) { + ports = rhub->ports; + i = rhub->num_ports; + while (i--) { + if (!test_bit(i, &bus_state->resuming_ports)) + continue; + portsc = xhci_portsc_readl(ports[i]); + if ((portsc & PORT_PLS_MASK) == XDEV_RESUME) + tegra_phy_xusb_utmi_pad_power_on( + tegra_xusb_get_phy(tegra, "usb2", (int) i)); + } + } + + if (hcd->speed == HCD_USB2) { + phy = tegra_xusb_get_phy(tegra, "usb2", port); + if ((type_req == ClearPortFeature) && (value == USB_PORT_FEAT_SUSPEND)) { + if (!index || index > rhub->num_ports) + return -EPIPE; + tegra_phy_xusb_utmi_pad_power_on(phy); + } + if ((type_req == SetPortFeature) && (value == USB_PORT_FEAT_RESET)) { + if (!index || index > rhub->num_ports) + return -EPIPE; + ports = rhub->ports; + portsc = xhci_portsc_readl(ports[port]); + if (portsc & PORT_CONNECT) + tegra_phy_xusb_utmi_pad_power_on(phy); + } + } + + ret = xhci_hub_control(hcd, type_req, value, index, buf, length); + if (ret < 0) + return ret; + + if (hcd->speed == HCD_USB2) { + /* Use phy where we set previously */ + if ((type_req == SetPortFeature) && (value == USB_PORT_FEAT_SUSPEND)) + /* We don't suspend the PAD while HNP role swap happens on the OTG port */ + if (!((hcd->self.otg_port == (port + 1)) && hcd->self.b_hnp_enable)) + tegra_phy_xusb_utmi_pad_power_down(phy); + + if ((type_req == ClearPortFeature) && (value == USB_PORT_FEAT_C_CONNECTION)) { + ports = rhub->ports; + portsc = xhci_portsc_readl(ports[port]); + if (!(portsc & PORT_CONNECT)) { + /* We don't suspend the PAD while HNP role swap happens on the OTG + * port + */ + if (!((hcd->self.otg_port == (port + 1)) && hcd->self.b_hnp_enable)) + tegra_phy_xusb_utmi_pad_power_down(phy); + } + } + if ((type_req == SetPortFeature) && (value == USB_PORT_FEAT_TEST)) + tegra_phy_xusb_utmi_pad_power_on(phy); + } + + return ret; +} + +static const struct xhci_driver_overrides tegra_xhci_overrides __initconst = { + .reset = tegra_xhci_setup, + .hub_control = tegra_xhci_hub_control, +}; + +static int __init tegra_xusb_init(void) +{ + xhci_init_driver(&tegra_xhci_hc_driver, &tegra_xhci_overrides); + + return platform_driver_register(&tegra_xusb_driver); +} +module_init(tegra_xusb_init); + +static void __exit tegra_xusb_exit(void) +{ + platform_driver_unregister(&tegra_xusb_driver); +} +module_exit(tegra_xusb_exit); + +MODULE_AUTHOR("Andrew Bresticker "); +MODULE_DESCRIPTION("NVIDIA Tegra XUSB xHCI host-controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/xhci-trace.c b/drivers/usb/host/xhci-trace.c new file mode 100644 index 000000000..062662d23 --- /dev/null +++ b/drivers/usb/host/xhci-trace.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xHCI host controller driver + * + * Copyright (C) 2013 Xenia Ragiadakou + * + * Author: Xenia Ragiadakou + * Email : burzalodowa@gmail.com + */ + +#define CREATE_TRACE_POINTS +#include "xhci-trace.h" + +EXPORT_TRACEPOINT_SYMBOL_GPL(xhci_dbg_quirks); +EXPORT_TRACEPOINT_SYMBOL_GPL(xhci_dbg_init); diff --git a/drivers/usb/host/xhci-trace.h b/drivers/usb/host/xhci-trace.h new file mode 100644 index 000000000..724cba2db --- /dev/null +++ b/drivers/usb/host/xhci-trace.h @@ -0,0 +1,652 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xHCI host controller driver + * + * Copyright (C) 2013 Xenia Ragiadakou + * + * Author: Xenia Ragiadakou + * Email : burzalodowa@gmail.com + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM xhci-hcd + +/* + * The TRACE_SYSTEM_VAR defaults to TRACE_SYSTEM, but must be a + * legitimate C variable. It is not exported to user space. + */ +#undef TRACE_SYSTEM_VAR +#define TRACE_SYSTEM_VAR xhci_hcd + +#if !defined(__XHCI_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __XHCI_TRACE_H + +#include +#include "xhci.h" +#include "xhci-dbgcap.h" + +DECLARE_EVENT_CLASS(xhci_log_msg, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf), + TP_STRUCT__entry(__vstring(msg, vaf->fmt, vaf->va)), + TP_fast_assign( + __assign_vstr(msg, vaf->fmt, vaf->va); + ), + TP_printk("%s", __get_str(msg)) +); + +DEFINE_EVENT(xhci_log_msg, xhci_dbg_address, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(xhci_log_msg, xhci_dbg_context_change, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(xhci_log_msg, xhci_dbg_quirks, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(xhci_log_msg, xhci_dbg_reset_ep, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(xhci_log_msg, xhci_dbg_cancel_urb, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(xhci_log_msg, xhci_dbg_init, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(xhci_log_msg, xhci_dbg_ring_expansion, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DECLARE_EVENT_CLASS(xhci_log_ctx, + TP_PROTO(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx), + TP_ARGS(xhci, ctx), + TP_STRUCT__entry( + __field(int, ctx_64) + __field(unsigned, ctx_type) + __field(dma_addr_t, ctx_dma) + __field(u8 *, ctx_va) + ), + TP_fast_assign( + + __entry->ctx_64 = xhci->hcc_params & HCC_64BYTE_CONTEXT; + __entry->ctx_type = ctx->type; + __entry->ctx_dma = ctx->dma; + __entry->ctx_va = ctx->bytes; + ), + TP_printk("ctx_64=%d, ctx_type=%u, ctx_dma=@%llx, ctx_va=@%p", + __entry->ctx_64, __entry->ctx_type, + (unsigned long long) __entry->ctx_dma, __entry->ctx_va + ) +); + +DEFINE_EVENT(xhci_log_ctx, xhci_address_ctx, + TP_PROTO(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx), + TP_ARGS(xhci, ctx) +); + +DECLARE_EVENT_CLASS(xhci_log_trb, + TP_PROTO(struct xhci_ring *ring, struct xhci_generic_trb *trb, dma_addr_t dma), + TP_ARGS(ring, trb, dma), + TP_STRUCT__entry( + __field(dma_addr_t, dma) + __field(u32, type) + __field(u32, field0) + __field(u32, field1) + __field(u32, field2) + __field(u32, field3) + ), + TP_fast_assign( + __entry->dma = dma; + __entry->type = ring->type; + __entry->field0 = le32_to_cpu(trb->field[0]); + __entry->field1 = le32_to_cpu(trb->field[1]); + __entry->field2 = le32_to_cpu(trb->field[2]); + __entry->field3 = le32_to_cpu(trb->field[3]); + ), + TP_printk("%s: @%pad %s", + xhci_ring_type_string(__entry->type), &__entry->dma, + xhci_decode_trb(__get_buf(XHCI_MSG_MAX), XHCI_MSG_MAX, __entry->field0, + __entry->field1, __entry->field2, __entry->field3) + ) +); + +DEFINE_EVENT(xhci_log_trb, xhci_handle_event, + TP_PROTO(struct xhci_ring *ring, struct xhci_generic_trb *trb, dma_addr_t dma), + TP_ARGS(ring, trb, dma) +); + +DEFINE_EVENT(xhci_log_trb, xhci_handle_command, + TP_PROTO(struct xhci_ring *ring, struct xhci_generic_trb *trb, dma_addr_t dma), + TP_ARGS(ring, trb, dma) +); + +DEFINE_EVENT(xhci_log_trb, xhci_handle_transfer, + TP_PROTO(struct xhci_ring *ring, struct xhci_generic_trb *trb, dma_addr_t dma), + TP_ARGS(ring, trb, dma) +); + +DEFINE_EVENT(xhci_log_trb, xhci_queue_trb, + TP_PROTO(struct xhci_ring *ring, struct xhci_generic_trb *trb, dma_addr_t dma), + TP_ARGS(ring, trb, dma) + +); + +DEFINE_EVENT(xhci_log_trb, xhci_dbc_handle_event, + TP_PROTO(struct xhci_ring *ring, struct xhci_generic_trb *trb, dma_addr_t dma), + TP_ARGS(ring, trb, dma) +); + +DEFINE_EVENT(xhci_log_trb, xhci_dbc_handle_transfer, + TP_PROTO(struct xhci_ring *ring, struct xhci_generic_trb *trb, dma_addr_t dma), + TP_ARGS(ring, trb, dma) +); + +DEFINE_EVENT(xhci_log_trb, xhci_dbc_gadget_ep_queue, + TP_PROTO(struct xhci_ring *ring, struct xhci_generic_trb *trb, dma_addr_t dma), + TP_ARGS(ring, trb, dma) +); + +DECLARE_EVENT_CLASS(xhci_log_free_virt_dev, + TP_PROTO(struct xhci_virt_device *vdev), + TP_ARGS(vdev), + TP_STRUCT__entry( + __field(void *, vdev) + __field(unsigned long long, out_ctx) + __field(unsigned long long, in_ctx) + __field(int, slot_id) + __field(u16, current_mel) + + ), + TP_fast_assign( + __entry->vdev = vdev; + __entry->in_ctx = (unsigned long long) vdev->in_ctx->dma; + __entry->out_ctx = (unsigned long long) vdev->out_ctx->dma; + __entry->slot_id = (int) vdev->slot_id; + __entry->current_mel = (u16) vdev->current_mel; + ), + TP_printk("vdev %p slot %d ctx %llx | %llx current_mel %d", + __entry->vdev, __entry->slot_id, __entry->in_ctx, + __entry->out_ctx, __entry->current_mel + ) +); + +DEFINE_EVENT(xhci_log_free_virt_dev, xhci_free_virt_device, + TP_PROTO(struct xhci_virt_device *vdev), + TP_ARGS(vdev) +); + +DECLARE_EVENT_CLASS(xhci_log_virt_dev, + TP_PROTO(struct xhci_virt_device *vdev), + TP_ARGS(vdev), + TP_STRUCT__entry( + __field(void *, vdev) + __field(unsigned long long, out_ctx) + __field(unsigned long long, in_ctx) + __field(int, devnum) + __field(int, state) + __field(int, speed) + __field(u8, portnum) + __field(u8, level) + __field(int, slot_id) + ), + TP_fast_assign( + __entry->vdev = vdev; + __entry->in_ctx = (unsigned long long) vdev->in_ctx->dma; + __entry->out_ctx = (unsigned long long) vdev->out_ctx->dma; + __entry->devnum = vdev->udev->devnum; + __entry->state = vdev->udev->state; + __entry->speed = vdev->udev->speed; + __entry->portnum = vdev->udev->portnum; + __entry->level = vdev->udev->level; + __entry->slot_id = vdev->udev->slot_id; + ), + TP_printk("vdev %p ctx %llx | %llx num %d state %d speed %d port %d level %d slot %d", + __entry->vdev, __entry->in_ctx, __entry->out_ctx, + __entry->devnum, __entry->state, __entry->speed, + __entry->portnum, __entry->level, __entry->slot_id + ) +); + +DEFINE_EVENT(xhci_log_virt_dev, xhci_alloc_virt_device, + TP_PROTO(struct xhci_virt_device *vdev), + TP_ARGS(vdev) +); + +DEFINE_EVENT(xhci_log_virt_dev, xhci_setup_device, + TP_PROTO(struct xhci_virt_device *vdev), + TP_ARGS(vdev) +); + +DEFINE_EVENT(xhci_log_virt_dev, xhci_setup_addressable_virt_device, + TP_PROTO(struct xhci_virt_device *vdev), + TP_ARGS(vdev) +); + +DEFINE_EVENT(xhci_log_virt_dev, xhci_stop_device, + TP_PROTO(struct xhci_virt_device *vdev), + TP_ARGS(vdev) +); + +DECLARE_EVENT_CLASS(xhci_log_urb, + TP_PROTO(struct urb *urb), + TP_ARGS(urb), + TP_STRUCT__entry( + __string(devname, dev_name(&urb->dev->dev)) + __field(void *, urb) + __field(unsigned int, pipe) + __field(unsigned int, stream) + __field(int, status) + __field(unsigned int, flags) + __field(int, num_mapped_sgs) + __field(int, num_sgs) + __field(int, length) + __field(int, actual) + __field(int, epnum) + __field(int, dir_in) + __field(int, type) + __field(int, slot_id) + ), + TP_fast_assign( + __assign_str(devname); + __entry->urb = urb; + __entry->pipe = urb->pipe; + __entry->stream = urb->stream_id; + __entry->status = urb->status; + __entry->flags = urb->transfer_flags; + __entry->num_mapped_sgs = urb->num_mapped_sgs; + __entry->num_sgs = urb->num_sgs; + __entry->length = urb->transfer_buffer_length; + __entry->actual = urb->actual_length; + __entry->epnum = usb_endpoint_num(&urb->ep->desc); + __entry->dir_in = usb_endpoint_dir_in(&urb->ep->desc); + __entry->type = usb_endpoint_type(&urb->ep->desc); + __entry->slot_id = urb->dev->slot_id; + ), + TP_printk("%s ep%d%s-%s: urb %p pipe %u slot %d length %d/%d sgs %d/%d stream %d flags %08x", + __get_str(devname), + __entry->epnum, __entry->dir_in ? "in" : "out", + __print_symbolic(__entry->type, + { USB_ENDPOINT_XFER_INT, "intr" }, + { USB_ENDPOINT_XFER_CONTROL, "control" }, + { USB_ENDPOINT_XFER_BULK, "bulk" }, + { USB_ENDPOINT_XFER_ISOC, "isoc" }), + __entry->urb, __entry->pipe, __entry->slot_id, + __entry->actual, __entry->length, __entry->num_mapped_sgs, + __entry->num_sgs, __entry->stream, __entry->flags + ) +); + +DEFINE_EVENT(xhci_log_urb, xhci_urb_enqueue, + TP_PROTO(struct urb *urb), + TP_ARGS(urb) +); + +DEFINE_EVENT(xhci_log_urb, xhci_urb_giveback, + TP_PROTO(struct urb *urb), + TP_ARGS(urb) +); + +DEFINE_EVENT(xhci_log_urb, xhci_urb_dequeue, + TP_PROTO(struct urb *urb), + TP_ARGS(urb) +); + +DECLARE_EVENT_CLASS(xhci_log_stream_ctx, + TP_PROTO(struct xhci_stream_info *info, unsigned int stream_id), + TP_ARGS(info, stream_id), + TP_STRUCT__entry( + __field(unsigned int, stream_id) + __field(u64, stream_ring) + __field(dma_addr_t, ctx_array_dma) + + ), + TP_fast_assign( + __entry->stream_id = stream_id; + __entry->stream_ring = le64_to_cpu(info->stream_ctx_array[stream_id].stream_ring); + __entry->ctx_array_dma = info->ctx_array_dma + stream_id * 16; + + ), + TP_printk("stream %u ctx @%pad: SCT %llu deq %llx", __entry->stream_id, + &__entry->ctx_array_dma, CTX_TO_SCT(__entry->stream_ring), + __entry->stream_ring + ) +); + +DEFINE_EVENT(xhci_log_stream_ctx, xhci_alloc_stream_info_ctx, + TP_PROTO(struct xhci_stream_info *info, unsigned int stream_id), + TP_ARGS(info, stream_id) +); + +DEFINE_EVENT(xhci_log_stream_ctx, xhci_handle_cmd_set_deq_stream, + TP_PROTO(struct xhci_stream_info *info, unsigned int stream_id), + TP_ARGS(info, stream_id) +); + +DECLARE_EVENT_CLASS(xhci_log_ep_ctx, + TP_PROTO(struct xhci_ep_ctx *ctx), + TP_ARGS(ctx), + TP_STRUCT__entry( + __field(u32, info) + __field(u32, info2) + __field(u64, deq) + __field(u32, tx_info) + ), + TP_fast_assign( + __entry->info = le32_to_cpu(ctx->ep_info); + __entry->info2 = le32_to_cpu(ctx->ep_info2); + __entry->deq = le64_to_cpu(ctx->deq); + __entry->tx_info = le32_to_cpu(ctx->tx_info); + ), + TP_printk("%s", xhci_decode_ep_context(__get_buf(XHCI_MSG_MAX), + __entry->info, __entry->info2, __entry->deq, __entry->tx_info) + ) +); + +DEFINE_EVENT(xhci_log_ep_ctx, xhci_handle_cmd_stop_ep, + TP_PROTO(struct xhci_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_ep_ctx, xhci_handle_cmd_set_deq_ep, + TP_PROTO(struct xhci_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_ep_ctx, xhci_handle_cmd_reset_ep, + TP_PROTO(struct xhci_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_ep_ctx, xhci_handle_cmd_config_ep, + TP_PROTO(struct xhci_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_ep_ctx, xhci_add_endpoint, + TP_PROTO(struct xhci_ep_ctx *ctx), + TP_ARGS(ctx) +); + +DECLARE_EVENT_CLASS(xhci_log_slot_ctx, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx), + TP_STRUCT__entry( + __field(u32, info) + __field(u32, info2) + __field(u32, tt_info) + __field(u32, state) + ), + TP_fast_assign( + __entry->info = le32_to_cpu(ctx->dev_info); + __entry->info2 = le32_to_cpu(ctx->dev_info2); + __entry->tt_info = le64_to_cpu(ctx->tt_info); + __entry->state = le32_to_cpu(ctx->dev_state); + ), + TP_printk("%s", xhci_decode_slot_context(__get_buf(XHCI_MSG_MAX), + __entry->info, __entry->info2, + __entry->tt_info, __entry->state) + ) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_alloc_dev, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_free_dev, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_handle_cmd_disable_slot, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_discover_or_reset_device, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_setup_device_slot, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_handle_cmd_addr_dev, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_handle_cmd_reset_dev, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_handle_cmd_set_deq, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DEFINE_EVENT(xhci_log_slot_ctx, xhci_configure_endpoint, + TP_PROTO(struct xhci_slot_ctx *ctx), + TP_ARGS(ctx) +); + +DECLARE_EVENT_CLASS(xhci_log_ctrl_ctx, + TP_PROTO(struct xhci_input_control_ctx *ctrl_ctx), + TP_ARGS(ctrl_ctx), + TP_STRUCT__entry( + __field(u32, drop) + __field(u32, add) + ), + TP_fast_assign( + __entry->drop = le32_to_cpu(ctrl_ctx->drop_flags); + __entry->add = le32_to_cpu(ctrl_ctx->add_flags); + ), + TP_printk("%s", xhci_decode_ctrl_ctx(__get_buf(XHCI_MSG_MAX), __entry->drop, __entry->add) + ) +); + +DEFINE_EVENT(xhci_log_ctrl_ctx, xhci_address_ctrl_ctx, + TP_PROTO(struct xhci_input_control_ctx *ctrl_ctx), + TP_ARGS(ctrl_ctx) +); + +DEFINE_EVENT(xhci_log_ctrl_ctx, xhci_configure_endpoint_ctrl_ctx, + TP_PROTO(struct xhci_input_control_ctx *ctrl_ctx), + TP_ARGS(ctrl_ctx) +); + +DECLARE_EVENT_CLASS(xhci_log_ring, + TP_PROTO(struct xhci_ring *ring), + TP_ARGS(ring), + TP_STRUCT__entry( + __field(u32, type) + __field(void *, ring) + __field(dma_addr_t, enq) + __field(dma_addr_t, deq) + __field(unsigned int, num_segs) + __field(unsigned int, stream_id) + __field(unsigned int, cycle_state) + __field(unsigned int, bounce_buf_len) + ), + TP_fast_assign( + __entry->ring = ring; + __entry->type = ring->type; + __entry->num_segs = ring->num_segs; + __entry->stream_id = ring->stream_id; + __entry->cycle_state = ring->cycle_state; + __entry->bounce_buf_len = ring->bounce_buf_len; + __entry->enq = xhci_trb_virt_to_dma(ring->enq_seg, ring->enqueue); + __entry->deq = xhci_trb_virt_to_dma(ring->deq_seg, ring->dequeue); + ), + TP_printk("%s %p: enq %pad deq %pad segs %d stream %d bounce %d cycle %d", + xhci_ring_type_string(__entry->type), __entry->ring, + &__entry->enq, + &__entry->deq, + __entry->num_segs, + __entry->stream_id, + __entry->bounce_buf_len, + __entry->cycle_state + ) +); + +DEFINE_EVENT(xhci_log_ring, xhci_ring_alloc, + TP_PROTO(struct xhci_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(xhci_log_ring, xhci_ring_free, + TP_PROTO(struct xhci_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(xhci_log_ring, xhci_ring_expansion, + TP_PROTO(struct xhci_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(xhci_log_ring, xhci_inc_enq, + TP_PROTO(struct xhci_ring *ring), + TP_ARGS(ring) +); + +DEFINE_EVENT(xhci_log_ring, xhci_inc_deq, + TP_PROTO(struct xhci_ring *ring), + TP_ARGS(ring) +); + +DECLARE_EVENT_CLASS(xhci_log_portsc, + TP_PROTO(struct xhci_port *port, u32 portsc), + TP_ARGS(port, portsc), + TP_STRUCT__entry( + __field(u32, busnum) + __field(u32, portnum) + __field(u32, portsc) + ), + TP_fast_assign( + __entry->busnum = port->rhub->hcd->self.busnum; + __entry->portnum = port->hcd_portnum + 1; + __entry->portsc = portsc; + ), + TP_printk("port %d-%d: %s", + __entry->busnum, + __entry->portnum, + xhci_decode_portsc(__get_buf(XHCI_MSG_MAX), __entry->portsc) + ) +); + +DEFINE_EVENT(xhci_log_portsc, xhci_handle_port_status, + TP_PROTO(struct xhci_port *port, u32 portsc), + TP_ARGS(port, portsc) +); + +DEFINE_EVENT(xhci_log_portsc, xhci_get_port_status, + TP_PROTO(struct xhci_port *port, u32 portsc), + TP_ARGS(port, portsc) +); + +DEFINE_EVENT(xhci_log_portsc, xhci_hub_status_data, + TP_PROTO(struct xhci_port *port, u32 portsc), + TP_ARGS(port, portsc) +); + +DEFINE_EVENT(xhci_log_portsc, xhci_portsc_writel, + TP_PROTO(struct xhci_port *port, u32 portsc), + TP_ARGS(port, portsc) +); + +DECLARE_EVENT_CLASS(xhci_log_doorbell, + TP_PROTO(u32 slot, u32 doorbell), + TP_ARGS(slot, doorbell), + TP_STRUCT__entry( + __field(u32, slot) + __field(u32, doorbell) + ), + TP_fast_assign( + __entry->slot = slot; + __entry->doorbell = doorbell; + ), + TP_printk("Ring doorbell for %s", + xhci_decode_doorbell(__get_buf(XHCI_MSG_MAX), __entry->slot, __entry->doorbell) + ) +); + +DEFINE_EVENT(xhci_log_doorbell, xhci_ring_ep_doorbell, + TP_PROTO(u32 slot, u32 doorbell), + TP_ARGS(slot, doorbell) +); + +DEFINE_EVENT(xhci_log_doorbell, xhci_ring_host_doorbell, + TP_PROTO(u32 slot, u32 doorbell), + TP_ARGS(slot, doorbell) +); + +DECLARE_EVENT_CLASS(xhci_dbc_log_request, + TP_PROTO(struct dbc_request *req), + TP_ARGS(req), + TP_STRUCT__entry( + __field(struct dbc_request *, req) + __field(bool, dir) + __field(unsigned int, actual) + __field(unsigned int, length) + __field(int, status) + ), + TP_fast_assign( + __entry->req = req; + __entry->dir = req->direction; + __entry->actual = req->actual; + __entry->length = req->length; + __entry->status = req->status; + ), + TP_printk("%s: req %p length %u/%u ==> %d", + __entry->dir ? "bulk-in" : "bulk-out", + __entry->req, __entry->actual, + __entry->length, __entry->status + ) +); + +DEFINE_EVENT(xhci_dbc_log_request, xhci_dbc_alloc_request, + TP_PROTO(struct dbc_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(xhci_dbc_log_request, xhci_dbc_free_request, + TP_PROTO(struct dbc_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(xhci_dbc_log_request, xhci_dbc_queue_request, + TP_PROTO(struct dbc_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(xhci_dbc_log_request, xhci_dbc_giveback_request, + TP_PROTO(struct dbc_request *req), + TP_ARGS(req) +); +#endif /* __XHCI_TRACE_H */ + +/* this part must be outside header guard */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE xhci-trace + +#include diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c new file mode 100644 index 000000000..a9e47e178 --- /dev/null +++ b/drivers/usb/host/xhci.c @@ -0,0 +1,5738 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xhci.h" +#include "xhci-trace.h" +#include "xhci-debugfs.h" +#include "xhci-dbgcap.h" + +#define DRIVER_AUTHOR "Sarah Sharp" +#define DRIVER_DESC "'eXtensible' Host Controller (xHC) Driver" + +#define PORT_WAKE_BITS (PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E) + +/* 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"); + +static unsigned long long quirks; +module_param(quirks, ullong, S_IRUGO); +MODULE_PARM_DESC(quirks, "Bit flags for quirks to be enabled as default"); + +void xhci_portsc_writel(struct xhci_port *port, u32 val) +{ + trace_xhci_portsc_writel(port, val); + writel(val, &port->port_reg->portsc); +} +EXPORT_SYMBOL_GPL(xhci_portsc_writel); + +u32 xhci_portsc_readl(struct xhci_port *port) +{ + return readl(&port->port_reg->portsc); +} +EXPORT_SYMBOL_GPL(xhci_portsc_readl); + +static bool td_on_ring(struct xhci_td *td, struct xhci_ring *ring) +{ + struct xhci_segment *seg; + + if (!td || !td->start_seg) + return false; + + xhci_for_each_ring_seg(ring->first_seg, seg) { + if (seg == td->start_seg) + return true; + } + + return false; +} + +/* + * 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(void __iomem *ptr, u32 mask, u32 done, u64 timeout_us) +{ + u32 result; + int ret; + + ret = readl_poll_timeout_atomic(ptr, result, + (result & mask) == done || + result == U32_MAX, + 1, timeout_us); + if (result == U32_MAX) /* card removed */ + return -ENODEV; + + return ret; +} + +/* + * Disable interrupts and begin the xHCI halting process. + */ +void xhci_quiesce(struct xhci_hcd *xhci) +{ + u32 cmd; + + cmd = readl(&xhci->op_regs->command); + cmd &= ~(CMD_RUN | XHCI_IRQS); + writel(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_trace(xhci, trace_xhci_dbg_init, "// Halt the HC"); + xhci_quiesce(xhci); + + ret = xhci_handshake(&xhci->op_regs->status, + STS_HALT, STS_HALT, XHCI_MAX_HALT_USEC); + if (ret) { + if (!(xhci->xhc_state & XHCI_STATE_DYING)) + xhci_warn(xhci, "Host halt failed, %d\n", ret); + return ret; + } + + xhci->xhc_state |= XHCI_STATE_HALTED; + xhci->cmd_ring_state = CMD_RING_STATE_STOPPED; + + return ret; +} + +/* + * Set the run bit and wait for the host to be running. + */ +int xhci_start(struct xhci_hcd *xhci) +{ + u32 temp; + int ret; + + temp = readl(&xhci->op_regs->command); + temp |= (CMD_RUN); + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "// Turn on HC, cmd = 0x%x.", + temp); + writel(temp, &xhci->op_regs->command); + + /* + * Wait for the HCHalted Status bit to be 0 to indicate the host is + * running. + */ + ret = xhci_handshake(&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) { + /* clear state flags. Including dying, halted or removing */ + xhci->xhc_state = 0; + xhci->run_graceperiod = jiffies + msecs_to_jiffies(500); + } + + 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, u64 timeout_us) +{ + u32 command; + u32 state; + int ret; + + state = readl(&xhci->op_regs->status); + + if (state == ~(u32)0) { + if (!(xhci->xhc_state & XHCI_STATE_DYING)) + xhci_warn(xhci, "Host not accessible, reset failed.\n"); + return -ENODEV; + } + + if ((state & STS_HALT) == 0) { + xhci_warn(xhci, "Host controller not halted, aborting reset.\n"); + return 0; + } + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "// Reset the HC"); + command = readl(&xhci->op_regs->command); + command |= CMD_RESET; + writel(command, &xhci->op_regs->command); + + /* Existing Intel xHCI controllers require a delay of 1 mS, + * after setting the CMD_RESET bit, and before accessing any + * HC registers. This allows the HC to complete the + * reset operation and be ready for HC register access. + * Without this delay, the subsequent HC register access, + * may result in a system hang very rarely. + */ + if (xhci->quirks & XHCI_INTEL_HOST) + udelay(1000); + + ret = xhci_handshake(&xhci->op_regs->command, CMD_RESET, 0, timeout_us); + if (ret) + return ret; + + if (xhci->quirks & XHCI_ASMEDIA_MODIFY_FLOWCONTROL) + usb_asmedia_modifyflowcontrol(to_pci_dev(xhci_to_hcd(xhci)->self.controller)); + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "Wait for controller to be ready for doorbell rings"); + /* + * xHCI cannot write to any doorbells or operational registers other + * than status until the "Controller Not Ready" flag is cleared. + */ + ret = xhci_handshake(&xhci->op_regs->status, STS_CNR, 0, timeout_us); + + xhci->usb2_rhub.bus_state.port_c_suspend = 0; + xhci->usb2_rhub.bus_state.suspended_ports = 0; + xhci->usb2_rhub.bus_state.resuming_ports = 0; + xhci->usb3_rhub.bus_state.port_c_suspend = 0; + xhci->usb3_rhub.bus_state.suspended_ports = 0; + xhci->usb3_rhub.bus_state.resuming_ports = 0; + + return ret; +} + +static void xhci_zero_64b_regs(struct xhci_hcd *xhci) +{ + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + struct iommu_domain *domain; + int err, i; + u64 val; + + /* + * Some Renesas controllers get into a weird state if they are + * reset while programmed with 64bit addresses (they will preserve + * the top half of the address in internal, non visible + * registers). You end up with half the address coming from the + * kernel, and the other half coming from the firmware. Also, + * changing the programming leads to extra accesses even if the + * controller is supposed to be halted. The controller ends up with + * a fatal fault, and is then ripe for being properly reset. + * + * Special care is taken to only apply this if the device is behind + * an iommu. Doing anything when there is no iommu is definitely + * unsafe... + */ + domain = iommu_get_domain_for_dev(dev); + if (!(xhci->quirks & XHCI_ZERO_64B_REGS) || !domain || + domain->type == IOMMU_DOMAIN_IDENTITY) + return; + + xhci_info(xhci, "Zeroing 64bit base registers, expecting fault\n"); + + /* Clear HSEIE so that faults do not get signaled */ + val = readl(&xhci->op_regs->command); + val &= ~CMD_HSEIE; + writel(val, &xhci->op_regs->command); + + /* Clear HSE (aka FATAL) */ + val = readl(&xhci->op_regs->status); + val |= STS_FATAL; + writel(val, &xhci->op_regs->status); + + /* Now zero the registers, and brace for impact */ + val = xhci_read_64(xhci, &xhci->op_regs->dcbaa_ptr); + if (upper_32_bits(val)) + xhci_write_64(xhci, 0, &xhci->op_regs->dcbaa_ptr); + val = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); + if (upper_32_bits(val)) + xhci_write_64(xhci, 0, &xhci->op_regs->cmd_ring); + + for (i = 0; i < xhci->max_interrupters; i++) { + struct xhci_intr_reg __iomem *ir; + + ir = &xhci->run_regs->ir_set[i]; + val = xhci_read_64(xhci, &ir->erst_base); + if (upper_32_bits(val)) + xhci_write_64(xhci, 0, &ir->erst_base); + val= xhci_read_64(xhci, &ir->erst_dequeue); + if (upper_32_bits(val)) + xhci_write_64(xhci, 0, &ir->erst_dequeue); + } + + /* Wait for the fault to appear. It will be cleared on reset */ + err = xhci_handshake(&xhci->op_regs->status, + STS_FATAL, STS_FATAL, + XHCI_MAX_HALT_USEC); + if (!err) + xhci_info(xhci, "Fault detected\n"); +} + +int xhci_enable_interrupter(struct xhci_interrupter *ir) +{ + u32 iman; + + if (!ir || !ir->ir_set) + return -EINVAL; + + iman = readl(&ir->ir_set->iman); + iman &= ~IMAN_IP; + iman |= IMAN_IE; + writel(iman, &ir->ir_set->iman); + + /* Read operation to guarantee the write has been flushed from posted buffers */ + readl(&ir->ir_set->iman); + return 0; +} + +int xhci_disable_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) +{ + u32 iman; + + if (!ir || !ir->ir_set) + return -EINVAL; + + iman = readl(&ir->ir_set->iman); + iman &= ~IMAN_IP; + iman &= ~IMAN_IE; + writel(iman, &ir->ir_set->iman); + + iman = readl(&ir->ir_set->iman); + if (iman & IMAN_IP) + xhci_dbg(xhci, "%s: Interrupt pending\n", __func__); + + return 0; +} + +/* interrupt moderation interval imod_interval in nanoseconds */ +int xhci_set_interrupter_moderation(struct xhci_interrupter *ir, + u32 imod_interval) +{ + u32 imod; + + if (!ir || !ir->ir_set) + return -EINVAL; + + /* IMODI value in IMOD register is in 250ns increments */ + imod_interval = umin(imod_interval / 250, IMODI_MASK); + + imod = readl(&ir->ir_set->imod); + imod &= ~IMODI_MASK; + imod |= imod_interval; + writel(imod, &ir->ir_set->imod); + + return 0; +} + +static void compliance_mode_recovery(struct timer_list *t) +{ + struct xhci_hcd *xhci; + struct usb_hcd *hcd; + struct xhci_hub *rhub; + u32 temp; + int i; + + xhci = timer_container_of(xhci, t, comp_mode_recovery_timer); + rhub = &xhci->usb3_rhub; + hcd = rhub->hcd; + + if (!hcd) + return; + + for (i = 0; i < rhub->num_ports; i++) { + temp = xhci_portsc_readl(rhub->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_trace(xhci, trace_xhci_dbg_quirks, + "Compliance mode detected->port %d", + i + 1); + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "Attempting compliance mode recovery"); + + if (hcd->state == HC_STATE_SUSPENDED) + usb_hcd_resume_root_hub(hcd); + + usb_hcd_poll_rh_status(hcd); + } + } + + if (xhci->port_status_u0 != ((1 << rhub->num_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; + timer_setup(&xhci->comp_mode_recovery_timer, compliance_mode_recovery, + 0); + xhci->comp_mode_recovery_timer.expires = jiffies + + msecs_to_jiffies(COMP_MODE_RCVRY_MSECS); + + add_timer(&xhci->comp_mode_recovery_timer); + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "Compliance mode recovery timer initialized"); +} + +/* + * 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 + */ +static 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->usb3_rhub.num_ports) - 1)); +} + +static void xhci_hcd_page_size(struct xhci_hcd *xhci) +{ + u32 page_size; + + page_size = readl(&xhci->op_regs->page_size) & XHCI_PAGE_SIZE_MASK; + if (!is_power_of_2(page_size)) { + xhci_warn(xhci, "Invalid page size register = 0x%x\n", page_size); + /* Fallback to 4K page size, since that's common */ + page_size = 1; + } + + xhci->page_size = page_size << 12; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "HCD page size set to %iK", + xhci->page_size >> 10); +} + +static void xhci_enable_max_dev_slots(struct xhci_hcd *xhci) +{ + u32 config_reg; + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "xHC can handle at most %d device slots", + xhci->max_slots); + + config_reg = readl(&xhci->op_regs->config_reg); + config_reg &= ~HCS_SLOTS_MASK; + config_reg |= xhci->max_slots; + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Setting Max device slots reg = 0x%x", + config_reg); + writel(config_reg, &xhci->op_regs->config_reg); +} + +static void xhci_set_cmd_ring_deq(struct xhci_hcd *xhci) +{ + dma_addr_t deq_dma; + u64 crcr; + + deq_dma = xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, xhci->cmd_ring->dequeue); + deq_dma &= CMD_RING_PTR_MASK; + + crcr = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); + crcr &= ~CMD_RING_PTR_MASK; + crcr |= deq_dma; + + crcr &= ~CMD_RING_CYCLE; + crcr |= xhci->cmd_ring->cycle_state; + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Setting command ring address to 0x%llx", crcr); + xhci_write_64(xhci, crcr, &xhci->op_regs->cmd_ring); +} + +static void xhci_set_doorbell_ptr(struct xhci_hcd *xhci) +{ + u32 offset; + + offset = readl(&xhci->cap_regs->db_off) & DBOFF_MASK; + xhci->dba = (void __iomem *)xhci->cap_regs + offset; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "Doorbell array is located at offset 0x%x from cap regs base addr", offset); +} + +/* + * 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). + */ +static void xhci_set_dev_notifications(struct xhci_hcd *xhci) +{ + u32 dev_notf; + + dev_notf = readl(&xhci->op_regs->dev_notification); + dev_notf &= ~DEV_NOTE_MASK; + dev_notf |= DEV_NOTE_FWAKE; + writel(dev_notf, &xhci->op_regs->dev_notification); +} + +/* Setup basic xHCI registers */ +static void xhci_init(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Starting %s", __func__); + + /* Set the Number of Device Slots Enabled to the maximum supported value */ + xhci_enable_max_dev_slots(xhci); + + /* Initialize the Command ring */ + xhci_ring_init(xhci, xhci->cmd_ring); + /* + * 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 = 1; + /* Set the address in the Command Ring Control register */ + xhci_set_cmd_ring_deq(xhci); + + /* Set Device Context Base Address Array pointer */ + xhci_write_64(xhci, xhci->dcbaa.dma, &xhci->op_regs->dcbaa_ptr); + + /* Set Doorbell array pointer */ + xhci_set_doorbell_ptr(xhci); + + /* Set USB 3.0 device notifications for function remote wake */ + xhci_set_dev_notifications(xhci); + + /* Initialize the Primary interrupter */ + xhci_ring_init(xhci, xhci->interrupters[0]->event_ring); + xhci_add_interrupter(xhci, 0); + xhci->interrupters[0]->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX; + + /* 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); + } + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished %s", __func__); +} + +/*-------------------------------------------------------------------------*/ + +static int xhci_run_finished(struct xhci_hcd *xhci) +{ + struct xhci_interrupter *ir = xhci->interrupters[0]; + unsigned long flags; + u32 temp; + + /* + * Enable interrupts before starting the host (xhci 4.2 and 5.5.2). + * Protect the short window before host is running with a lock + */ + spin_lock_irqsave(&xhci->lock, flags); + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Enable interrupts"); + temp = readl(&xhci->op_regs->command); + temp |= (CMD_EIE); + writel(temp, &xhci->op_regs->command); + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Enable primary interrupter"); + xhci_enable_interrupter(ir); + + if (xhci_start(xhci)) { + xhci_halt(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + return -ENODEV; + } + + xhci->cmd_ring_state = CMD_RING_STATE_RUNNING; + + if (xhci->quirks & XHCI_NEC_HOST) + xhci_ring_cmd_db(xhci); + + spin_unlock_irqrestore(&xhci->lock, flags); + + 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) +{ + u64 temp_64; + int ret; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_interrupter *ir = xhci->interrupters[0]; + /* Start the xHCI host controller running only after the USB 2.0 roothub + * is setup. + */ + + hcd->uses_new_polling = 1; + if (hcd->msi_enabled) + ir->ip_autoclear = true; + + if (!usb_hcd_is_primary_hcd(hcd)) + return xhci_run_finished(xhci); + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "xhci_run"); + + temp_64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); + temp_64 &= ERST_PTR_MASK; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "ERST deq = 64'h%0lx", (long unsigned int) temp_64); + + xhci_set_interrupter_moderation(ir, xhci->imod_interval); + + if (xhci->quirks & XHCI_NEC_HOST) { + struct xhci_command *command; + + command = xhci_alloc_command(xhci, false, GFP_KERNEL); + if (!command) + return -ENOMEM; + + ret = xhci_queue_vendor_command(xhci, command, 0, 0, 0, + TRB_TYPE(TRB_NEC_GET_FW)); + if (ret) + xhci_free_command(xhci, command); + } + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "Finished %s for main hcd", __func__); + + xhci_create_dbc_dev(xhci); + + xhci_debugfs_init(xhci); + + if (xhci_has_one_roothub(xhci)) + return xhci_run_finished(xhci); + + set_bit(HCD_FLAG_DEFER_RH_REGISTER, &hcd->flags); + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_run); + +/* + * 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); + struct xhci_interrupter *ir = xhci->interrupters[0]; + + mutex_lock(&xhci->mutex); + + /* Only halt host and free memory after both hcds are removed */ + if (!usb_hcd_is_primary_hcd(hcd)) { + mutex_unlock(&xhci->mutex); + return; + } + + xhci_remove_dbc_dev(xhci); + + spin_lock_irq(&xhci->lock); + xhci->xhc_state |= XHCI_STATE_HALTED; + xhci->cmd_ring_state = CMD_RING_STATE_STOPPED; + xhci_halt(xhci); + xhci_reset(xhci, XHCI_RESET_SHORT_USEC); + spin_unlock_irq(&xhci->lock); + + /* Deleting Compliance Mode Recovery Timer */ + if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && + (!(xhci_all_ports_seen_u0(xhci)))) { + timer_delete_sync(&xhci->comp_mode_recovery_timer); + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "%s: compliance mode recovery timer deleted", + __func__); + } + + if (xhci->quirks & XHCI_AMD_PLL_FIX) + usb_amd_dev_put(); + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "// Disabling event ring interrupts"); + temp = readl(&xhci->op_regs->status); + writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status); + xhci_disable_interrupter(xhci, ir); + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "cleaning up memory"); + xhci_mem_cleanup(xhci); + xhci_debugfs_exit(xhci); + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "xhci_stop completed - status = %x", + readl(&xhci->op_regs->status)); + mutex_unlock(&xhci->mutex); +} +EXPORT_SYMBOL_GPL(xhci_stop); + +/* + * 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.sysdev)); + + /* Don't poll the roothubs after shutdown. */ + xhci_dbg(xhci, "%s: stopping usb%d port polling.\n", + __func__, hcd->self.busnum); + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + timer_delete_sync(&hcd->rh_timer); + + if (xhci->shared_hcd) { + clear_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags); + timer_delete_sync(&xhci->shared_hcd->rh_timer); + } + + spin_lock_irq(&xhci->lock); + xhci_halt(xhci); + + /* + * Workaround for spurious wakeps at shutdown with HSW, and for boot + * firmware delay in ADL-P PCH if port are left in U3 at shutdown + */ + if (xhci->quirks & XHCI_SPURIOUS_WAKEUP || + xhci->quirks & XHCI_RESET_TO_DEFAULT) + xhci_reset(xhci, XHCI_RESET_SHORT_USEC); + + spin_unlock_irq(&xhci->lock); + + xhci_dbg_trace(xhci, trace_xhci_dbg_init, + "xhci_shutdown completed - status = %x", + readl(&xhci->op_regs->status)); +} +EXPORT_SYMBOL_GPL(xhci_shutdown); + +#ifdef CONFIG_PM +static void xhci_save_registers(struct xhci_hcd *xhci) +{ + struct xhci_interrupter *ir; + unsigned int i; + + xhci->s3.command = readl(&xhci->op_regs->command); + xhci->s3.dev_nt = readl(&xhci->op_regs->dev_notification); + xhci->s3.dcbaa_ptr = xhci_read_64(xhci, &xhci->op_regs->dcbaa_ptr); + xhci->s3.config_reg = readl(&xhci->op_regs->config_reg); + + /* save both primary and all secondary interrupters */ + /* fixme, shold we lock to prevent race with remove secondary interrupter? */ + for (i = 0; i < xhci->max_interrupters; i++) { + ir = xhci->interrupters[i]; + if (!ir) + continue; + + ir->s3_erst_size = readl(&ir->ir_set->erst_size); + ir->s3_erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base); + ir->s3_erst_dequeue = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); + ir->s3_iman = readl(&ir->ir_set->iman); + ir->s3_imod = readl(&ir->ir_set->imod); + } +} + +static void xhci_restore_registers(struct xhci_hcd *xhci) +{ + struct xhci_interrupter *ir; + unsigned int i; + + writel(xhci->s3.command, &xhci->op_regs->command); + writel(xhci->s3.dev_nt, &xhci->op_regs->dev_notification); + xhci_write_64(xhci, xhci->s3.dcbaa_ptr, &xhci->op_regs->dcbaa_ptr); + writel(xhci->s3.config_reg, &xhci->op_regs->config_reg); + + /* FIXME should we lock to protect against freeing of interrupters */ + for (i = 0; i < xhci->max_interrupters; i++) { + ir = xhci->interrupters[i]; + if (!ir) + continue; + + writel(ir->s3_erst_size, &ir->ir_set->erst_size); + xhci_write_64(xhci, ir->s3_erst_base, &ir->ir_set->erst_base); + xhci_write_64(xhci, ir->s3_erst_dequeue, &ir->ir_set->erst_dequeue); + writel(ir->s3_iman, &ir->ir_set->iman); + writel(ir->s3_imod, &ir->ir_set->imod); + } +} + +/* + * 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; + xhci_for_each_ring_seg(ring->first_seg, seg) { + /* erase all TRBs before the link */ + memset(seg->trbs, 0, sizeof(union xhci_trb) * (TRBS_PER_SEGMENT - 1)); + /* clear link cycle bit */ + seg->trbs[TRBS_PER_SEGMENT - 1].link.control &= cpu_to_le32(~TRB_CYCLE); + } + + xhci_initialize_ring_info(ring); + /* + * 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); +} + +/* + * Disable port wake bits if do_wakeup is not set. + * + * Also clear a possible internal port wake state left hanging for ports that + * detected termination but never successfully enumerated (trained to 0U). + * Internal wake causes immediate xHCI wake after suspend. PORT_CSC write done + * at enumeration clears this wake, force one here as well for unconnected ports + */ + +static void xhci_disable_hub_port_wake(struct xhci_hcd *xhci, + struct xhci_hub *rhub, + bool do_wakeup) +{ + unsigned long flags; + u32 t1, t2, portsc; + int i; + + spin_lock_irqsave(&xhci->lock, flags); + + for (i = 0; i < rhub->num_ports; i++) { + portsc = xhci_portsc_readl(rhub->ports[i]); + t1 = xhci_port_state_to_neutral(portsc); + t2 = t1; + + /* clear wake bits if do_wake is not set */ + if (!do_wakeup) + t2 &= ~PORT_WAKE_BITS; + + /* Don't touch csc bit if connected or connect change is set */ + if (!(portsc & (PORT_CSC | PORT_CONNECT))) + t2 |= PORT_CSC; + + if (t1 != t2) { + xhci_portsc_writel(rhub->ports[i], t2); + xhci_dbg(xhci, "config port %d-%d wake bits, portsc: 0x%x, write: 0x%x\n", + rhub->hcd->self.busnum, i + 1, portsc, t2); + } + } + spin_unlock_irqrestore(&xhci->lock, flags); +} + +static bool xhci_pending_portevent(struct xhci_hcd *xhci) +{ + struct xhci_port **ports; + int port_index; + u32 status; + u32 portsc; + + status = readl(&xhci->op_regs->status); + if (status & STS_EINT) + return true; + /* + * Checking STS_EINT is not enough as there is a lag between a change + * bit being set and the Port Status Change Event that it generated + * being written to the Event Ring. See note in xhci 1.1 section 4.19.2. + */ + + port_index = xhci->usb2_rhub.num_ports; + ports = xhci->usb2_rhub.ports; + while (port_index--) { + portsc = xhci_portsc_readl(ports[port_index]); + if (portsc & PORT_CHANGE_MASK || + (portsc & PORT_PLS_MASK) == XDEV_RESUME) + return true; + } + port_index = xhci->usb3_rhub.num_ports; + ports = xhci->usb3_rhub.ports; + while (port_index--) { + portsc = xhci_portsc_readl(ports[port_index]); + if (portsc & (PORT_CHANGE_MASK | PORT_CAS) || + (portsc & PORT_PLS_MASK) == XDEV_RESUME) + return true; + } + return false; +} + +/* + * Stop HC (not bus-specific) + * + * This is called when the machine transition into S3/S4 mode. + * + */ +int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup) +{ + int err; + unsigned int delay = XHCI_MAX_HALT_USEC * 2; + struct usb_hcd *hcd = xhci_to_hcd(xhci); + u32 command; + u32 usbsts; + + if (!hcd->state) + return 0; + + if (hcd->state != HC_STATE_SUSPENDED || + (xhci->shared_hcd && xhci->shared_hcd->state != HC_STATE_SUSPENDED)) + return -EINVAL; + + /* Clear root port wake on bits if wakeup not allowed. */ + xhci_disable_hub_port_wake(xhci, &xhci->usb3_rhub, do_wakeup); + xhci_disable_hub_port_wake(xhci, &xhci->usb2_rhub, do_wakeup); + + if (!HCD_HW_ACCESSIBLE(hcd)) + return 0; + + xhci_dbc_suspend(xhci); + + /* Don't poll the roothubs on bus suspend. */ + xhci_dbg(xhci, "%s: stopping usb%d port polling.\n", + __func__, hcd->self.busnum); + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + timer_delete_sync(&hcd->rh_timer); + if (xhci->shared_hcd) { + clear_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags); + timer_delete_sync(&xhci->shared_hcd->rh_timer); + } + + if (xhci->quirks & XHCI_SUSPEND_DELAY) + usleep_range(1000, 1500); + + spin_lock_irq(&xhci->lock); + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + if (xhci->shared_hcd) + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &xhci->shared_hcd->flags); + /* step 1: stop endpoint */ + /* skipped assuming that port suspend has done */ + + /* Check if command ring is empty */ + if (!list_empty(&xhci->cmd_list)) + xhci_warn(xhci, "Suspending and stopping xHC with pending command!\n"); + + /* step 2: clear Run/Stop bit */ + command = readl(&xhci->op_regs->command); + command &= ~CMD_RUN; + writel(command, &xhci->op_regs->command); + + /* Some chips from Fresco Logic need an extraordinary delay */ + delay *= (xhci->quirks & XHCI_SLOW_SUSPEND) ? 10 : 1; + + err = xhci_handshake(&xhci->op_regs->status, STS_HALT, STS_HALT, delay); + if (err) { + xhci_warn(xhci, "Clearing Run/Stop bit failed %d\n", err); + goto handshake_error; + } + xhci_clear_command_ring(xhci); + + /* step 3: save registers */ + xhci_save_registers(xhci); + + /* step 4: set CSS flag */ + command = readl(&xhci->op_regs->command); + command |= CMD_CSS; + writel(command, &xhci->op_regs->command); + + err = xhci_handshake(&xhci->op_regs->status, STS_SAVE, 0, 20 * USEC_PER_MSEC); + usbsts = readl(&xhci->op_regs->status); + xhci->broken_suspend = 0; + if (err) { + /* + * AMD SNPS xHC 3.0 occasionally does not clear the + * SSS bit of USBSTS and when driver tries to poll + * to see if the xHC clears BIT(8) which never happens + * and driver assumes that controller is not responding + * and times out. To workaround this, its good to check + * if SRE and HCE bits are not set (as per xhci + * Section 5.4.2) and bypass the timeout. + */ + if (!(xhci->quirks & XHCI_SNPS_BROKEN_SUSPEND)) { + xhci_warn(xhci, "Controller Save State failed %d\n", err); + goto handshake_error; + } + + if (usbsts & (STS_SRE | STS_HCE)) { + xhci_warn(xhci, "Controller Save State failed, USBSTS 0x%08x\n", usbsts); + goto handshake_error; + } + + xhci_dbg(xhci, "SNPS broken suspend, save state unreliable\n"); + xhci->broken_suspend = 1; + } else if (usbsts & STS_SRE) { + xhci_warn(xhci, "Suspend Save Error (SRE), USBSTS 0x%08x\n", usbsts); + } + 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)))) { + timer_delete_sync(&xhci->comp_mode_recovery_timer); + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "%s: compliance mode recovery timer deleted", + __func__); + } + + return 0; + +handshake_error: + spin_unlock_irq(&xhci->lock); + return -ETIMEDOUT; +} +EXPORT_SYMBOL_GPL(xhci_suspend); + +/* + * start xHC (not bus-specific) + * + * This is called when the machine transition from S3/S4 mode. + * + */ +int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume) +{ + u32 command, temp = 0; + struct usb_hcd *hcd = xhci_to_hcd(xhci); + struct xhci_segment *seg; + int retval = 0; + bool pending_portevent = false; + bool suspended_usb3_devs = false; + bool reset_registers = false; + + if (!hcd->state) + return 0; + + /* Wait a bit if either of the roothubs need to settle from the + * transition into bus suspend. + */ + + if (time_before(jiffies, xhci->usb2_rhub.bus_state.next_statechange) || + time_before(jiffies, xhci->usb3_rhub.bus_state.next_statechange)) + msleep(100); + + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + if (xhci->shared_hcd) + set_bit(HCD_FLAG_HW_ACCESSIBLE, &xhci->shared_hcd->flags); + + spin_lock_irq(&xhci->lock); + + if (power_lost || xhci->broken_suspend || xhci->quirks & XHCI_RESET_ON_RESUME) { + xhci_dbg(xhci, "HC state lost, performing host controller reset\n"); + reset_registers = true; + } else { + xhci_dbg(xhci, "HC state intact, continuing without reset\n"); + /* + * Some controllers might lose power during suspend, so wait + * for controller not ready bit to clear, just as in xHC init. + */ + retval = xhci_handshake(&xhci->op_regs->status, + STS_CNR, 0, 10 * 1000 * 1000); + if (retval) { + xhci_warn(xhci, "Controller not ready at resume %d\n", + retval); + spin_unlock_irq(&xhci->lock); + return retval; + } + /* 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 = readl(&xhci->op_regs->command); + command |= CMD_CRS; + writel(command, &xhci->op_regs->command); + /* + * Some controllers take up to 55+ ms to complete the controller + * restore so setting the timeout to 100ms. Xhci specification + * doesn't mention any timeout value. + */ + if (xhci_handshake(&xhci->op_regs->status, + STS_RESTORE, 0, 100 * 1000)) { + xhci_warn(xhci, "WARN: xHC restore state timeout\n"); + spin_unlock_irq(&xhci->lock); + return -ETIMEDOUT; + } + + /* re-initialize the HC on Restore Error, or Host Controller Error */ + temp = readl(&xhci->op_regs->status); + if ((temp & (STS_SRE | STS_HCE)) && !(xhci->xhc_state & XHCI_STATE_REMOVING)) { + xhci_warn(xhci, "xHC error in resume, USBSTS 0x%x, Reinit\n", temp); + reset_registers = true; + } + } + + if (reset_registers) { + if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && + !(xhci_all_ports_seen_u0(xhci))) { + timer_delete_sync(&xhci->comp_mode_recovery_timer); + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "Compliance Mode Recovery Timer deleted!"); + } + + /* Let the USB core know _both_ roothubs lost power. */ + usb_root_hub_lost_power(xhci->main_hcd->self.root_hub); + if (xhci->shared_hcd) + usb_root_hub_lost_power(xhci->shared_hcd->self.root_hub); + + xhci_dbg(xhci, "Stop HCD\n"); + xhci_halt(xhci); + xhci_zero_64b_regs(xhci); + if (xhci->xhc_state & XHCI_STATE_REMOVING) + retval = -ENODEV; + else + retval = xhci_reset(xhci, XHCI_RESET_LONG_USEC); + spin_unlock_irq(&xhci->lock); + if (retval) + return retval; + + cancel_delayed_work_sync(&xhci->cmd_timer); + + /* Delete all remaining commands */ + xhci_cleanup_command_queue(xhci); + + /* Clear data which is re-initilized during runtime */ + xhci_for_each_ring_seg(xhci->interrupters[0]->event_ring->first_seg, seg) + memset(seg->trbs, 0, sizeof(union xhci_trb) * TRBS_PER_SEGMENT); + + for (int i = xhci->max_slots; i > 0; i--) + xhci_free_virt_devices_depth_first(xhci, i); + + xhci_rh_bw_cleanup(xhci); + + xhci->cmd_ring_reserved_trbs = 0; + xhci_for_each_ring_seg(xhci->cmd_ring->first_seg, seg) + memset(seg->trbs, 0, sizeof(union xhci_trb) * TRBS_PER_SEGMENT); + + xhci_debugfs_exit(xhci); + + xhci_init(hcd); + + /* + * 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. + */ + xhci_dbg(xhci, "Start the primary HCD\n"); + retval = xhci_run(hcd); + if (!retval && xhci->shared_hcd) { + xhci_dbg(xhci, "Start the secondary HCD\n"); + retval = xhci_run(xhci->shared_hcd); + } + if (retval) + return retval; + /* + * Resume roothubs unconditionally as PORTSC change bits are not + * immediately visible after xHC reset + */ + hcd->state = HC_STATE_SUSPENDED; + + if (xhci->shared_hcd) { + xhci->shared_hcd->state = HC_STATE_SUSPENDED; + usb_hcd_resume_root_hub(xhci->shared_hcd); + } + usb_hcd_resume_root_hub(hcd); + + goto done; + } + + /* step 4: set Run/Stop bit */ + command = readl(&xhci->op_regs->command); + command |= CMD_RUN; + writel(command, &xhci->op_regs->command); + xhci_handshake(&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); + + xhci_dbc_resume(xhci); + + /* + * Resume roothubs only if there are pending events. + * USB 3 devices resend U3 LFPS wake after a 100ms delay if + * the first wake signalling failed, give it that chance if + * there are suspended USB 3 devices. + */ + if (xhci->usb3_rhub.bus_state.suspended_ports || xhci->usb3_rhub.bus_state.bus_suspended) + suspended_usb3_devs = true; + + pending_portevent = xhci_pending_portevent(xhci); + if (suspended_usb3_devs && !pending_portevent && is_auto_resume) { + msleep(120); + pending_portevent = xhci_pending_portevent(xhci); + } + + if (pending_portevent) { + if (xhci->shared_hcd) + usb_hcd_resume_root_hub(xhci->shared_hcd); + usb_hcd_resume_root_hub(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) + compliance_mode_recovery_timer_init(xhci); +done: + if (xhci->quirks & XHCI_ASMEDIA_MODIFY_FLOWCONTROL) + usb_asmedia_modifyflowcontrol(to_pci_dev(hcd->self.controller)); + + /* Re-enable port polling. */ + xhci_dbg(xhci, "%s: starting usb%d port polling.\n", + __func__, hcd->self.busnum); + if (xhci->shared_hcd) { + set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags); + usb_hcd_poll_rh_status(xhci->shared_hcd); + } + set_bit(HCD_FLAG_POLL_RH, &hcd->flags); + usb_hcd_poll_rh_status(hcd); + + return retval; +} +EXPORT_SYMBOL_GPL(xhci_resume); +#endif /* CONFIG_PM */ + +/*-------------------------------------------------------------------------*/ + +static int xhci_map_temp_buffer(struct usb_hcd *hcd, struct urb *urb) +{ + void *temp; + int ret = 0; + unsigned int buf_len; + enum dma_data_direction dir; + + dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + buf_len = urb->transfer_buffer_length; + + temp = kzalloc_node(buf_len, GFP_ATOMIC, + dev_to_node(hcd->self.sysdev)); + if (!temp) + return -ENOMEM; + + if (usb_urb_dir_out(urb)) + sg_pcopy_to_buffer(urb->sg, urb->num_sgs, + temp, buf_len, 0); + + urb->transfer_buffer = temp; + urb->transfer_dma = dma_map_single(hcd->self.sysdev, + urb->transfer_buffer, + urb->transfer_buffer_length, + dir); + + if (dma_mapping_error(hcd->self.sysdev, + urb->transfer_dma)) { + ret = -EAGAIN; + kfree(temp); + } else { + urb->transfer_flags |= URB_DMA_MAP_SINGLE; + } + + return ret; +} + +static bool xhci_urb_temp_buffer_required(struct usb_hcd *hcd, + struct urb *urb) +{ + bool ret = false; + unsigned int i; + unsigned int len = 0; + unsigned int trb_size; + unsigned int max_pkt; + struct scatterlist *sg; + struct scatterlist *tail_sg; + + tail_sg = urb->sg; + max_pkt = xhci_usb_endpoint_maxp(urb->dev, urb->ep); + + if (!urb->num_sgs) + return ret; + + if (urb->dev->speed >= USB_SPEED_SUPER) + trb_size = TRB_CACHE_SIZE_SS; + else + trb_size = TRB_CACHE_SIZE_HS; + + if (urb->transfer_buffer_length != 0 && + !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) { + for_each_sg(urb->sg, sg, urb->num_sgs, i) { + len = len + sg->length; + if (i > trb_size - 2) { + len = len - tail_sg->length; + if (len < max_pkt) { + ret = true; + break; + } + + tail_sg = sg_next(tail_sg); + } + } + } + return ret; +} + +static void xhci_unmap_temp_buf(struct usb_hcd *hcd, struct urb *urb) +{ + unsigned int len; + unsigned int buf_len; + enum dma_data_direction dir; + + dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + buf_len = urb->transfer_buffer_length; + + if (IS_ENABLED(CONFIG_HAS_DMA) && + (urb->transfer_flags & URB_DMA_MAP_SINGLE)) + dma_unmap_single(hcd->self.sysdev, + urb->transfer_dma, + urb->transfer_buffer_length, + dir); + + if (usb_urb_dir_in(urb)) { + len = sg_pcopy_from_buffer(urb->sg, urb->num_sgs, + urb->transfer_buffer, + buf_len, + 0); + if (len != buf_len) { + xhci_dbg(hcd_to_xhci(hcd), + "Copy from tmp buf to urb sg list failed\n"); + urb->actual_length = len; + } + } + urb->transfer_flags &= ~URB_DMA_MAP_SINGLE; + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; +} + +/* + * Bypass the DMA mapping if URB is suitable for Immediate Transfer (IDT), + * we'll copy the actual data into the TRB address register. This is limited to + * transfers up to 8 bytes on output endpoints of any kind with wMaxPacketSize + * >= 8 bytes. If suitable for IDT only one Transfer TRB per TD is allowed. + */ +static int xhci_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct xhci_hcd *xhci; + + xhci = hcd_to_xhci(hcd); + + if (xhci_urb_suitable_for_idt(urb)) + return 0; + + if (xhci->quirks & XHCI_SG_TRB_CACHE_SIZE_QUIRK) { + if (xhci_urb_temp_buffer_required(hcd, urb)) + return xhci_map_temp_buffer(hcd, urb); + } + return usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); +} + +static void xhci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ + struct xhci_hcd *xhci; + bool unmap_temp_buf = false; + + xhci = hcd_to_xhci(hcd); + + if (urb->num_sgs && (urb->transfer_flags & URB_DMA_MAP_SINGLE)) + unmap_temp_buf = true; + + if ((xhci->quirks & XHCI_SG_TRB_CACHE_SIZE_QUIRK) && unmap_temp_buf) + xhci_unmap_temp_buf(hcd, urb); + else + usb_hcd_unmap_urb_for_dma(hcd, urb); +} + +/** + * 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. + * @desc: USB endpoint descriptor to determine index for + * + * 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; +} +EXPORT_SYMBOL_GPL(xhci_get_endpoint_index); + +/* The reverse operation to xhci_get_endpoint_index. Calculate the USB endpoint + * address from the XHCI endpoint index. + */ +static unsigned int xhci_get_endpoint_address(unsigned int ep_index) +{ + unsigned int number = DIV_ROUND_UP(ep_index, 2); + unsigned int direction = ep_index % 2 ? USB_DIR_OUT : USB_DIR_IN; + return direction | number; +} + +/* 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. + */ +static unsigned int xhci_get_endpoint_flag(struct usb_endpoint_descriptor *desc) +{ + return 1 << (xhci_get_endpoint_index(desc) + 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) { + pr_debug("xHCI %s called with invalid args\n", func); + return -EINVAL; + } + if (!udev->parent) { + pr_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]) { + xhci_dbg(xhci, "xHCI %s called with unaddressed device\n", + func); + return -EINVAL; + } + + virt_dev = xhci->devs[udev->slot_id]; + if (virt_dev->udev != udev) { + xhci_dbg(xhci, "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_ep0_maxpacket(struct xhci_hcd *xhci, struct xhci_virt_device *vdev) +{ + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_ep_ctx *ep_ctx; + struct xhci_command *command; + int max_packet_size; + int hw_max_packet_size; + int ret = 0; + + ep_ctx = xhci_get_ep_ctx(xhci, vdev->out_ctx, 0); + hw_max_packet_size = MAX_PACKET_DECODED(le32_to_cpu(ep_ctx->ep_info2)); + max_packet_size = usb_endpoint_maxp(&vdev->udev->ep0.desc); + + if (hw_max_packet_size == max_packet_size) + return 0; + + switch (max_packet_size) { + case 8: case 16: case 32: case 64: case 9: + xhci_dbg_trace(xhci, trace_xhci_dbg_context_change, + "Max Packet Size for ep 0 changed."); + xhci_dbg_trace(xhci, trace_xhci_dbg_context_change, + "Max packet size in usb_device = %d", + max_packet_size); + xhci_dbg_trace(xhci, trace_xhci_dbg_context_change, + "Max packet size in xHCI HW = %d", + hw_max_packet_size); + xhci_dbg_trace(xhci, trace_xhci_dbg_context_change, + "Issuing evaluate context command."); + + command = xhci_alloc_command(xhci, true, GFP_KERNEL); + if (!command) + return -ENOMEM; + + command->in_ctx = vdev->in_ctx; + ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + ret = -ENOMEM; + break; + } + /* Set up the modified control endpoint 0 */ + xhci_endpoint_copy(xhci, vdev->in_ctx, vdev->out_ctx, 0); + + ep_ctx = xhci_get_ep_ctx(xhci, command->in_ctx, 0); + ep_ctx->ep_info &= cpu_to_le32(~EP_STATE_MASK);/* must clear */ + ep_ctx->ep_info2 &= cpu_to_le32(~MAX_PACKET_MASK); + ep_ctx->ep_info2 |= cpu_to_le32(MAX_PACKET(max_packet_size)); + + ctrl_ctx->add_flags = cpu_to_le32(EP0_FLAG); + ctrl_ctx->drop_flags = 0; + + ret = xhci_configure_endpoint(xhci, vdev->udev, command, + true, false); + /* Clean up the input context for later use by bandwidth functions */ + ctrl_ctx->add_flags = cpu_to_le32(SLOT_FLAG); + break; + default: + dev_dbg(&vdev->udev->dev, "incorrect max packet size %d for ep0\n", + max_packet_size); + return -EINVAL; + } + + kfree(command->completion); + kfree(command); + + 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 + */ +static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + unsigned long flags; + int ret = 0; + unsigned int slot_id, ep_index; + unsigned int *ep_state; + struct urb_priv *urb_priv; + int num_tds; + + ep_index = xhci_get_endpoint_index(&urb->ep->desc); + + if (usb_endpoint_xfer_isoc(&urb->ep->desc)) + num_tds = urb->number_of_packets; + else if (usb_endpoint_is_bulk_out(&urb->ep->desc) && + urb->transfer_buffer_length > 0 && + urb->transfer_flags & URB_ZERO_PACKET && + !(urb->transfer_buffer_length % usb_endpoint_maxp(&urb->ep->desc))) + num_tds = 2; + else + num_tds = 1; + + urb_priv = kzalloc_flex(*urb_priv, td, num_tds, mem_flags); + if (!urb_priv) + return -ENOMEM; + + urb_priv->num_tds = num_tds; + urb_priv->num_tds_done = 0; + urb->hcpriv = urb_priv; + + trace_xhci_urb_enqueue(urb); + + spin_lock_irqsave(&xhci->lock, flags); + + ret = xhci_check_args(hcd, urb->dev, urb->ep, + true, true, __func__); + if (ret <= 0) { + ret = ret ? ret : -EINVAL; + goto free_priv; + } + + slot_id = urb->dev->slot_id; + + if (!HCD_HW_ACCESSIBLE(hcd)) { + ret = -ESHUTDOWN; + goto free_priv; + } + + if (xhci->devs[slot_id]->rhub_port->link_inactive) { + xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n"); + ret = -ENODEV; + goto free_priv; + } + + if (xhci->xhc_state & XHCI_STATE_DYING) { + xhci_dbg(xhci, "Ep 0x%x: URB %p submitted for non-responsive xHCI host.\n", + urb->ep->desc.bEndpointAddress, urb); + ret = -ESHUTDOWN; + goto free_priv; + } + + ep_state = &xhci->devs[slot_id]->eps[ep_index].ep_state; + + if (*ep_state & (EP_GETTING_STREAMS | EP_GETTING_NO_STREAMS)) { + xhci_warn(xhci, "WARN: Can't enqueue URB, ep in streams transition state %x\n", + *ep_state); + ret = -EINVAL; + goto free_priv; + } + if (*ep_state & EP_SOFT_CLEAR_TOGGLE) { + xhci_warn(xhci, "Can't enqueue URB while manually clearing toggle\n"); + ret = -EINVAL; + goto free_priv; + } + + switch (usb_endpoint_type(&urb->ep->desc)) { + + case USB_ENDPOINT_XFER_CONTROL: + ret = xhci_queue_ctrl_tx(xhci, GFP_ATOMIC, urb, + slot_id, ep_index); + break; + case USB_ENDPOINT_XFER_BULK: + ret = xhci_queue_bulk_tx(xhci, GFP_ATOMIC, urb, + slot_id, ep_index); + break; + case USB_ENDPOINT_XFER_INT: + ret = xhci_queue_intr_tx(xhci, GFP_ATOMIC, urb, + slot_id, ep_index); + break; + case USB_ENDPOINT_XFER_ISOC: + ret = xhci_queue_isoc_tx_prepare(xhci, GFP_ATOMIC, urb, + slot_id, ep_index); + } + + if (ret) { +free_priv: + xhci_urb_free_priv(urb_priv); + urb->hcpriv = NULL; + } + spin_unlock_irqrestore(&xhci->lock, flags); + return ret; +} + +/* + * 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() + */ +static 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; + struct xhci_command *command; + struct xhci_virt_device *vdev; + + xhci = hcd_to_xhci(hcd); + spin_lock_irqsave(&xhci->lock, flags); + + trace_xhci_urb_dequeue(urb); + + /* Make sure the URB hasn't completed or been unlinked already */ + ret = usb_hcd_check_unlink_urb(hcd, urb, status); + if (ret) + goto done; + + /* give back URB now if we can't queue it for cancel */ + vdev = xhci->devs[urb->dev->slot_id]; + urb_priv = urb->hcpriv; + if (!vdev || !urb_priv) + goto err_giveback; + + ep_index = xhci_get_endpoint_index(&urb->ep->desc); + ep = &vdev->eps[ep_index]; + ep_ring = xhci_urb_to_transfer_ring(xhci, urb); + if (!ep || !ep_ring) + goto err_giveback; + + /* If xHC is dead take it down and return ALL URBs in xhci_hc_died() */ + temp = readl(&xhci->op_regs->status); + if (temp == ~(u32)0 || xhci->xhc_state & XHCI_STATE_DYING) { + xhci_hc_died(xhci); + goto done; + } + + /* + * check ring is not re-allocated since URB was enqueued. If it is, then + * make sure none of the ring related pointers in this URB private data + * are touched, such as td_list, otherwise we overwrite freed data + */ + if (!td_on_ring(&urb_priv->td[0], ep_ring)) { + xhci_err(xhci, "Canceled URB td not found on endpoint ring"); + for (i = urb_priv->num_tds_done; i < urb_priv->num_tds; i++) { + td = &urb_priv->td[i]; + if (!list_empty(&td->cancelled_td_list)) + list_del_init(&td->cancelled_td_list); + } + goto err_giveback; + } + + if (xhci->xhc_state & XHCI_STATE_HALTED) { + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "HC halted, freeing TD manually."); + for (i = urb_priv->num_tds_done; + i < urb_priv->num_tds; + 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); + } + goto err_giveback; + } + + i = urb_priv->num_tds_done; + if (i < urb_priv->num_tds) + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Cancel URB %p, dev %s, ep 0x%x, " + "starting at offset 0x%llx", + 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].start_trb)); + + for (; i < urb_priv->num_tds; i++) { + td = &urb_priv->td[i]; + /* TD can already be on cancelled list if ep halted on it */ + if (list_empty(&td->cancelled_td_list)) { + td->cancel_status = TD_DIRTY; + list_add_tail(&td->cancelled_td_list, + &ep->cancelled_td_list); + } + } + + /* These completion handlers will sort out cancelled TDs for us */ + if (ep->ep_state & (EP_STOP_CMD_PENDING | EP_HALTED | SET_DEQ_PENDING)) { + xhci_dbg(xhci, "Not queuing Stop Endpoint on slot %d ep %d in state 0x%x\n", + urb->dev->slot_id, ep_index, ep->ep_state); + goto done; + } + + /* In this case no commands are pending but the endpoint is stopped */ + if (ep->ep_state & (EP_CLEARING_TT | EP_DROP_PENDING)) { + /* and cancelled TDs can be given back right away */ + xhci_dbg(xhci, "Invalidating TDs instantly on slot %d ep %d in state 0x%x\n", + urb->dev->slot_id, ep_index, ep->ep_state); + xhci_process_cancelled_tds(xhci, ep); + } else { + /* Otherwise, queue a new Stop Endpoint command */ + command = xhci_alloc_command(xhci, false, GFP_ATOMIC); + if (!command) { + ret = -ENOMEM; + goto done; + } + ep->stop_time = jiffies; + ep->ep_state |= EP_STOP_CMD_PENDING; + xhci_queue_stop_endpoint(xhci, command, urb->dev->slot_id, + ep_index, 0); + xhci_ring_cmd_db(xhci); + } +done: + spin_unlock_irqrestore(&xhci->lock, flags); + return ret; + +err_giveback: + if (urb_priv) + xhci_urb_free_priv(urb_priv); + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock_irqrestore(&xhci->lock, flags); + usb_hcd_giveback_urb(hcd, urb, -ESHUTDOWN); + 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; + unsigned int ep_index; + struct xhci_ep_ctx *ep_ctx; + u32 drop_flag; + u32 new_add_flags, new_drop_flags; + 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(in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return 0; + } + + 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 ((GET_EP_CTX_STATE(ep_ctx) == EP_STATE_DISABLED) || + le32_to_cpu(ctrl_ctx->drop_flags) & + xhci_get_endpoint_flag(&ep->desc)) { + /* Do not warn when called after a usb_device_reset */ + if (xhci->devs[udev->slot_id]->eps[ep_index].ring != NULL) + 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); + + xhci_debugfs_remove_endpoint(xhci, xhci->devs[udev->slot_id], ep_index); + + 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\n", + (unsigned int) ep->desc.bEndpointAddress, + udev->slot_id, + (unsigned int) new_drop_flags, + (unsigned int) new_add_flags); + return 0; +} +EXPORT_SYMBOL_GPL(xhci_drop_endpoint); + +/* 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; + unsigned int ep_index; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_ep_ctx *ep_ctx; + u32 added_ctxs; + u32 new_add_flags, new_drop_flags; + 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); + 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; + ctrl_ctx = xhci_get_input_control_ctx(in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return 0; + } + + 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) & added_ctxs)) { + 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) & added_ctxs) { + 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); + + /* Store the usb_device pointer for later use */ + ep->hcpriv = udev; + + ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); + trace_xhci_add_endpoint(ep_ctx); + + xhci_dbg(xhci, "add ep 0x%x, slot id %d, new drop flags = %#x, new add flags = %#x\n", + (unsigned int) ep->desc.bEndpointAddress, + udev->slot_id, + (unsigned int) new_drop_flags, + (unsigned int) new_add_flags); + return 0; +} +EXPORT_SYMBOL_GPL(xhci_add_endpoint); + +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; + + ctrl_ctx = xhci_get_input_control_ctx(virt_dev->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return; + } + + /* 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->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_COMMAND_ABORTED: + case COMP_COMMAND_RING_STOPPED: + xhci_warn(xhci, "Timeout while waiting for configure endpoint command\n"); + ret = -ETIME; + break; + case COMP_RESOURCE_ERROR: + 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_BANDWIDTH_ERROR: + case COMP_SECONDARY_BANDWIDTH_ERROR: + 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_ERROR: + /* 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_INCOMPATIBLE_DEVICE_ERROR: + dev_warn(&udev->dev, + "ERROR: Incompatible device for endpoint configure command.\n"); + ret = -ENODEV; + break; + case COMP_SUCCESS: + xhci_dbg_trace(xhci, trace_xhci_dbg_context_change, + "Successful Endpoint Configure command"); + 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; + + switch (*cmd_status) { + case COMP_COMMAND_ABORTED: + case COMP_COMMAND_RING_STOPPED: + xhci_warn(xhci, "Timeout while waiting for evaluate context command\n"); + ret = -ETIME; + break; + case COMP_PARAMETER_ERROR: + dev_warn(&udev->dev, + "WARN: xHCI driver setup invalid evaluate context command.\n"); + ret = -EINVAL; + break; + case COMP_SLOT_NOT_ENABLED_ERROR: + dev_warn(&udev->dev, + "WARN: slot not enabled for evaluate context command.\n"); + ret = -EINVAL; + break; + case COMP_CONTEXT_STATE_ERROR: + dev_warn(&udev->dev, + "WARN: invalid context state for evaluate context command.\n"); + ret = -EINVAL; + break; + case COMP_INCOMPATIBLE_DEVICE_ERROR: + dev_warn(&udev->dev, + "ERROR: Incompatible device for evaluate context command.\n"); + ret = -ENODEV; + break; + case COMP_MAX_EXIT_LATENCY_TOO_LARGE_ERROR: + /* Max Exit Latency too large error */ + dev_warn(&udev->dev, "WARN: Max Exit Latency too large\n"); + ret = -EINVAL; + break; + case COMP_SUCCESS: + xhci_dbg_trace(xhci, trace_xhci_dbg_context_change, + "Successful evaluate context command"); + 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_input_control_ctx *ctrl_ctx) +{ + u32 valid_add_flags; + u32 valid_drop_flags; + + /* 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 = le32_to_cpu(ctrl_ctx->add_flags) >> 2; + valid_drop_flags = le32_to_cpu(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_input_control_ctx *ctrl_ctx) +{ + u32 valid_add_flags; + u32 valid_drop_flags; + + valid_add_flags = le32_to_cpu(ctrl_ctx->add_flags) >> 2; + valid_drop_flags = le32_to_cpu(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_input_control_ctx *ctrl_ctx) +{ + u32 added_eps; + + added_eps = xhci_count_num_new_endpoints(xhci, ctrl_ctx); + if (xhci->num_active_eps + added_eps > xhci->limit_active_eps) { + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "Not enough ep ctxs: " + "%u active, need to add %u, limit is %u.", + xhci->num_active_eps, added_eps, + xhci->limit_active_eps); + return -ENOMEM; + } + xhci->num_active_eps += added_eps; + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "Adding %u ep ctxs, %u now active.", 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_input_control_ctx *ctrl_ctx) +{ + u32 num_failed_eps; + + num_failed_eps = xhci_count_num_new_endpoints(xhci, ctrl_ctx); + xhci->num_active_eps -= num_failed_eps; + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "Removing %u failed ep ctxs, %u now active.", + 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_input_control_ctx *ctrl_ctx) +{ + u32 num_dropped_eps; + + num_dropped_eps = xhci_count_num_dropped_endpoints(xhci, ctrl_ctx); + xhci->num_active_eps -= num_dropped_eps; + if (num_dropped_eps) + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "Removing %u dropped ep ctxs, %u now active.", + 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: + case USB_SPEED_SUPER_PLUS: + return SS_BLOCK; + case USB_SPEED_UNKNOWN: + 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->rhub_port->hw_portnum].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_trace(xhci, trace_xhci_dbg_quirks, + "Recalculating BW for rootport %u", + virt_dev->rhub_port->hw_portnum + 1); + 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_trace(xhci, trace_xhci_dbg_quirks, + "Recalculating BW for TT slot %u port %u", + virt_dev->tt_info->slot_id, + virt_dev->tt_info->ttport); + } else { + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "Recalculating BW for rootport %u", + virt_dev->rhub_port->hw_portnum + 1); + } + + /* 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) { + /* 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[virt_dev->rhub_port->hw_portnum].num_active_tts; + } + + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "Final bandwidth: %u, Limit: %u, Reserved: %u, " + "Available: %u " "percent", + 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); + +} + +static 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; + default: + /* 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; + default: + /* 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->rhub_port->hw_portnum]; + 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(in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return -ENOMEM; + } + + 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; +} + +/* + * Synchronous XHCI stop endpoint helper. Issues the stop endpoint command and + * waits for the command completion before returning. This does not call + * xhci_handle_cmd_stop_ep(), which has additional handling for 'context error' + * cases, along with transfer ring cleanup. + * + * xhci_stop_endpoint_sync() is intended to be utilized by clients that manage + * their own transfer ring, such as offload situations. + */ +int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, int suspend, + gfp_t gfp_flags) +{ + struct xhci_command *command; + struct xhci_ep_ctx *ep_ctx; + unsigned long flags; + int ret = -ENODEV; + + command = xhci_alloc_command(xhci, true, gfp_flags); + if (!command) + return -ENOMEM; + + spin_lock_irqsave(&xhci->lock, flags); + + /* make sure endpoint exists and is running before stopping it */ + if (ep->ring) { + ep_ctx = xhci_get_ep_ctx(xhci, ep->vdev->out_ctx, ep->ep_index); + if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) + ret = xhci_queue_stop_endpoint(xhci, command, + ep->vdev->slot_id, + ep->ep_index, suspend); + } + + if (ret < 0) { + spin_unlock_irqrestore(&xhci->lock, flags); + goto out; + } + + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + wait_for_completion(command->completion); + + /* No handling for COMP_CONTEXT_STATE_ERROR done at command completion*/ + if (command->status == COMP_COMMAND_ABORTED || + command->status == COMP_COMMAND_RING_STOPPED) { + xhci_warn(xhci, "Timeout while waiting for stop endpoint command\n"); + ret = -ETIME; + } +out: + xhci_free_command(xhci, command); + + return ret; +} +EXPORT_SYMBOL_GPL(xhci_stop_endpoint_sync); + +/* + * xhci_usb_endpoint_maxp - get endpoint max packet size + * @host_ep: USB host endpoint to be checked + * + * Returns max packet from the correct descriptor + */ +int xhci_usb_endpoint_maxp(struct usb_device *udev, + struct usb_host_endpoint *host_ep) +{ + if (usb_endpoint_is_hs_isoc_double(udev, host_ep)) + return le16_to_cpu(host_ep->eusb2_isoc_ep_comp.wMaxPacketSize); + return usb_endpoint_maxp(&host_ep->desc); +} + +/* 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; + unsigned long flags; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_virt_device *virt_dev; + struct xhci_slot_ctx *slot_ctx; + + if (!command) + return -EINVAL; + + spin_lock_irqsave(&xhci->lock, flags); + + if (xhci->xhc_state & XHCI_STATE_DYING) { + spin_unlock_irqrestore(&xhci->lock, flags); + return -ESHUTDOWN; + } + + virt_dev = xhci->devs[udev->slot_id]; + + ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx); + if (!ctrl_ctx) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return -ENOMEM; + } + + if ((xhci->quirks & XHCI_EP_LIMIT_QUIRK) && + xhci_reserve_host_resources(xhci, ctrl_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) && !ctx_change && + xhci_reserve_bandwidth(xhci, virt_dev, command->in_ctx)) { + if ((xhci->quirks & XHCI_EP_LIMIT_QUIRK)) + xhci_free_host_resources(xhci, ctrl_ctx); + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_warn(xhci, "Not enough bandwidth\n"); + return -ENOMEM; + } + + slot_ctx = xhci_get_slot_ctx(xhci, command->in_ctx); + + trace_xhci_configure_endpoint_ctrl_ctx(ctrl_ctx); + trace_xhci_configure_endpoint(slot_ctx); + + if (!ctx_change) + ret = xhci_queue_configure_endpoint(xhci, command, + command->in_ctx->dma, + udev->slot_id, must_succeed); + else + ret = xhci_queue_evaluate_context(xhci, command, + command->in_ctx->dma, + udev->slot_id, must_succeed); + if (ret < 0) { + if ((xhci->quirks & XHCI_EP_LIMIT_QUIRK)) + xhci_free_host_resources(xhci, ctrl_ctx); + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg_trace(xhci, trace_xhci_dbg_context_change, + "FIXME allocate a new ring segment"); + return -ENOMEM; + } + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + /* Wait for the configure endpoint command to complete */ + wait_for_completion(command->completion); + + if (!ctx_change) + ret = xhci_configure_endpoint_result(xhci, udev, + &command->status); + else + ret = xhci_evaluate_context_result(xhci, udev, + &command->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, ctrl_ctx); + else + xhci_finish_resource_reservation(xhci, ctrl_ctx); + spin_unlock_irqrestore(&xhci->lock, flags); + } + return ret; +} + +static void xhci_check_bw_drop_ep_streams(struct xhci_hcd *xhci, + struct xhci_virt_device *vdev, int i) +{ + struct xhci_virt_ep *ep = &vdev->eps[i]; + + if (ep->ep_state & EP_HAS_STREAMS) { + xhci_warn(xhci, "WARN: endpoint 0x%02x has streams on set_interface, freeing streams.\n", + xhci_get_endpoint_address(i)); + xhci_free_stream_info(xhci, ep->stream_info); + ep->stream_info = NULL; + ep->ep_state &= ~EP_HAS_STREAMS; + } +} + +/* 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; + struct xhci_command *command; + + 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) || + (xhci->xhc_state & XHCI_STATE_REMOVING)) + return -ENODEV; + + xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev); + virt_dev = xhci->devs[udev->slot_id]; + + command = xhci_alloc_command(xhci, true, GFP_KERNEL); + if (!command) + return -ENOMEM; + + command->in_ctx = virt_dev->in_ctx; + + /* See section 4.6.6 - A0 = 1; A1 = D0 = D1 = 0 */ + ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + ret = -ENOMEM; + goto command_cleanup; + } + 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) { + ret = 0; + goto command_cleanup; + } + /* Fix up Context Entries field. Minimum value is EP0 == BIT(1). */ + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx); + for (i = 31; i >= 1; i--) { + __le32 le32 = cpu_to_le32(BIT(i)); + + if ((virt_dev->eps[i-1].ring && !(ctrl_ctx->drop_flags & le32)) + || (ctrl_ctx->add_flags & le32) || i == 1) { + slot_ctx->dev_info &= cpu_to_le32(~LAST_CTX_MASK); + slot_ctx->dev_info |= cpu_to_le32(LAST_CTX(i)); + break; + } + } + + ret = xhci_configure_endpoint(xhci, udev, command, + false, false); + if (ret) + /* Callee should call reset_bandwidth() */ + goto command_cleanup; + + /* 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_endpoint_ring(xhci, virt_dev, i); + xhci_check_bw_drop_ep_streams(xhci, virt_dev, i); + } + } + xhci_zero_in_ctx(xhci, virt_dev); + /* + * Install any rings for completely new endpoints or changed endpoints, + * and free any old rings from changed endpoints. + */ + for (i = 1; i < 31; i++) { + if (!virt_dev->eps[i].new_ring) + continue; + /* Only 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_endpoint_ring(xhci, virt_dev, i); + } + xhci_check_bw_drop_ep_streams(xhci, virt_dev, i); + virt_dev->eps[i].ring = virt_dev->eps[i].new_ring; + virt_dev->eps[i].new_ring = NULL; + xhci_debugfs_create_endpoint(xhci, virt_dev, i); + } +command_cleanup: + kfree(command->completion); + kfree(command); + + return ret; +} +EXPORT_SYMBOL_GPL(xhci_check_bandwidth); + +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_debugfs_remove_endpoint(xhci, virt_dev, i); + xhci_ring_free(xhci, virt_dev->eps[i].new_ring); + virt_dev->eps[i].new_ring = NULL; + } + } + xhci_zero_in_ctx(xhci, virt_dev); +} +EXPORT_SYMBOL_GPL(xhci_reset_bandwidth); + +/* + * Get the available bandwidth of the ports under the xhci roothub. + * EIO means the command failed: command not implemented or unsupported + * speed (TRB Error), some ASMedia complete with Parameter Error when + * querying the root hub (slot_id = 0), or other error or timeout. + */ +int xhci_get_port_bandwidth(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx, + u8 dev_speed) +{ + struct xhci_command *cmd; + unsigned long flags; + int ret; + + if (!ctx || !xhci) + return -EINVAL; + + cmd = xhci_alloc_command(xhci, true, GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + cmd->in_ctx = ctx; + + /* get xhci port bandwidth, refer to xhci rev1_2 protocol 4.6.15 */ + spin_lock_irqsave(&xhci->lock, flags); + + ret = xhci_queue_get_port_bw(xhci, cmd, ctx->dma, dev_speed, 0); + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + goto err_out; + } + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + wait_for_completion(cmd->completion); + if (cmd->status != COMP_SUCCESS) + ret = -EIO; +err_out: + kfree(cmd->completion); + kfree(cmd); + + return ret; +} + +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, + struct xhci_input_control_ctx *ctrl_ctx, + u32 add_flags, u32 drop_flags) +{ + 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); +} + +static void xhci_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *host_ep) +{ + struct xhci_hcd *xhci; + struct xhci_virt_device *vdev; + struct xhci_virt_ep *ep; + struct usb_device *udev; + unsigned long flags; + unsigned int ep_index; + + xhci = hcd_to_xhci(hcd); +rescan: + spin_lock_irqsave(&xhci->lock, flags); + + udev = (struct usb_device *)host_ep->hcpriv; + if (!udev || !udev->slot_id) + goto done; + + vdev = xhci->devs[udev->slot_id]; + if (!vdev) + goto done; + + ep_index = xhci_get_endpoint_index(&host_ep->desc); + ep = &vdev->eps[ep_index]; + + /* wait for hub_tt_work to finish clearing hub TT */ + if (ep->ep_state & EP_CLEARING_TT) { + spin_unlock_irqrestore(&xhci->lock, flags); + schedule_timeout_uninterruptible(1); + goto rescan; + } + + if (ep->ep_state) + xhci_dbg(xhci, "endpoint disable with ep_state 0x%x\n", + ep->ep_state); +done: + spin_unlock_irqrestore(&xhci->lock, flags); +} + +/* + * Called after usb core issues a clear halt control message. + * The host side of the halt should already be cleared by a reset endpoint + * command issued when the STALL event was received. + * + * The reset endpoint command may only be issued to endpoints in the halted + * state. For software that wishes to reset the data toggle or sequence number + * of an endpoint that isn't in the halted state this function will issue a + * configure endpoint command with the Drop and Add bits set for the target + * endpoint. Refer to the additional note in xhci spcification section 4.6.8. + * + * vdev may be lost due to xHC restore error and re-initialization during S3/S4 + * resume. A new vdev will be allocated later by xhci_discover_or_reset_device() + */ + +static void xhci_endpoint_reset(struct usb_hcd *hcd, + struct usb_host_endpoint *host_ep) +{ + struct xhci_hcd *xhci; + struct usb_device *udev; + struct xhci_virt_device *vdev; + struct xhci_virt_ep *ep; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_command *stop_cmd, *cfg_cmd; + unsigned int ep_index; + unsigned long flags; + u32 ep_flag; + int err; + + xhci = hcd_to_xhci(hcd); + ep_index = xhci_get_endpoint_index(&host_ep->desc); + + /* + * Usb core assumes a max packet value for ep0 on FS devices until the + * real value is read from the descriptor. Core resets Ep0 if values + * mismatch. Reconfigure the xhci ep0 endpoint context here in that case + */ + if (usb_endpoint_xfer_control(&host_ep->desc) && ep_index == 0) { + + udev = container_of(host_ep, struct usb_device, ep0); + if (udev->speed != USB_SPEED_FULL || !udev->slot_id) + return; + + vdev = xhci->devs[udev->slot_id]; + if (!vdev || vdev->udev != udev) + return; + + xhci_check_ep0_maxpacket(xhci, vdev); + + /* Nothing else should be done here for ep0 during ep reset */ + return; + } + + if (!host_ep->hcpriv) + return; + udev = (struct usb_device *) host_ep->hcpriv; + vdev = xhci->devs[udev->slot_id]; + + if (!udev->slot_id || !vdev) + return; + + ep = &vdev->eps[ep_index]; + + /* Bail out if toggle is already being cleared by a endpoint reset */ + spin_lock_irqsave(&xhci->lock, flags); + if (ep->ep_state & EP_HARD_CLEAR_TOGGLE) { + ep->ep_state &= ~EP_HARD_CLEAR_TOGGLE; + spin_unlock_irqrestore(&xhci->lock, flags); + return; + } + spin_unlock_irqrestore(&xhci->lock, flags); + /* Only interrupt and bulk ep's use data toggle, USB2 spec 5.5.4-> */ + if (usb_endpoint_xfer_control(&host_ep->desc) || + usb_endpoint_xfer_isoc(&host_ep->desc)) + return; + + ep_flag = xhci_get_endpoint_flag(&host_ep->desc); + + if (ep_flag == SLOT_FLAG || ep_flag == EP0_FLAG) + return; + + stop_cmd = xhci_alloc_command(xhci, true, GFP_NOWAIT); + if (!stop_cmd) + return; + + cfg_cmd = xhci_alloc_command_with_ctx(xhci, true, GFP_NOWAIT); + if (!cfg_cmd) + goto cleanup; + + spin_lock_irqsave(&xhci->lock, flags); + + /* block queuing new trbs and ringing ep doorbell */ + ep->ep_state |= EP_SOFT_CLEAR_TOGGLE; + + /* + * Make sure endpoint ring is empty before resetting the toggle/seq. + * Driver is required to synchronously cancel all transfer request. + * Stop the endpoint to force xHC to update the output context + */ + + if (!list_empty(&ep->ring->td_list)) { + dev_err(&udev->dev, "EP not empty, refuse reset\n"); + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_free_command(xhci, cfg_cmd); + goto cleanup; + } + + err = xhci_queue_stop_endpoint(xhci, stop_cmd, udev->slot_id, + ep_index, 0); + if (err < 0) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_free_command(xhci, cfg_cmd); + xhci_dbg(xhci, "%s: Failed to queue stop ep command, %d ", + __func__, err); + goto cleanup; + } + + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + wait_for_completion(stop_cmd->completion); + + spin_lock_irqsave(&xhci->lock, flags); + + /* config ep command clears toggle if add and drop ep flags are set */ + ctrl_ctx = xhci_get_input_control_ctx(cfg_cmd->in_ctx); + if (!ctrl_ctx) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_free_command(xhci, cfg_cmd); + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + goto cleanup; + } + + xhci_setup_input_ctx_for_config_ep(xhci, cfg_cmd->in_ctx, vdev->out_ctx, + ctrl_ctx, ep_flag, ep_flag); + xhci_endpoint_copy(xhci, cfg_cmd->in_ctx, vdev->out_ctx, ep_index); + + err = xhci_queue_configure_endpoint(xhci, cfg_cmd, cfg_cmd->in_ctx->dma, + udev->slot_id, false); + if (err < 0) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_free_command(xhci, cfg_cmd); + xhci_dbg(xhci, "%s: Failed to queue config ep command, %d ", + __func__, err); + goto cleanup; + } + + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + wait_for_completion(cfg_cmd->completion); + + xhci_free_command(xhci, cfg_cmd); +cleanup: + xhci_free_command(xhci, stop_cmd); + spin_lock_irqsave(&xhci->lock, flags); + if (ep->ep_state & EP_SOFT_CLEAR_TOGGLE) + ep->ep_state &= ~EP_SOFT_CLEAR_TOGGLE; + spin_unlock_irqrestore(&xhci->lock, flags); +} + +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 ret ? ret : -EINVAL; + if (usb_ss_max_streams(&ep->ss_ep_comp) == 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 = GET_MAX_PSA_SIZE(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, " + "streams are being disabled already\n", + 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, " + "streams are already disabled!\n", + 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 (through 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. + */ +static 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; + struct xhci_input_control_ctx *ctrl_ctx; + unsigned int ep_index; + unsigned int num_stream_ctxs; + unsigned int max_packet; + 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); + + /* MaxPSASize value 0 (2 streams) means streams are not supported */ + if ((xhci->quirks & XHCI_BROKEN_STREAMS) || + GET_MAX_PSA_SIZE(xhci->hcc_params) < 4) { + xhci_dbg(xhci, "xHCI controller does not support streams.\n"); + return -ENOSYS; + } + + config_cmd = xhci_alloc_command_with_ctx(xhci, true, mem_flags); + if (!config_cmd) + return -ENOMEM; + + ctrl_ctx = xhci_get_input_control_ctx(config_cmd->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + xhci_free_command(xhci, config_cmd); + 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); + max_packet = usb_endpoint_maxp(&eps[i]->desc); + vdev->eps[ep_index].stream_info = xhci_alloc_stream_info(xhci, + num_stream_ctxs, + num_streams, + max_packet, 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, ctrl_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); + + for (i = 0; i < num_eps; i++) { + ep_index = xhci_get_endpoint_index(&eps[i]->desc); + xhci_debugfs_create_stream_files(xhci, vdev, ep_index); + } + /* 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. + */ +static 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; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_stream_info *stream_info[EP_CTX_PER_DEV]; + 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; + ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx); + if (!ctrl_ctx) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return -EINVAL; + } + + 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(ep_ctx, + &vdev->eps[ep_index]); + } + xhci_setup_input_ctx_for_config_ep(xhci, command->in_ctx, + vdev->out_ctx, ctrl_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; + + /* + * dma_free_coherent() called by xhci_free_stream_info() may sleep, + * so save stream_info pointers and clear references under lock, + * then free the memory outside lock. + */ + spin_lock_irqsave(&xhci->lock, flags); + for (i = 0; i < num_eps; i++) { + ep_index = xhci_get_endpoint_index(&eps[i]->desc); + stream_info[i] = 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); + + for (i = 0; i < num_eps; i++) + xhci_free_stream_info(xhci, stream_info[i]); + + 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_trace(xhci, trace_xhci_dbg_quirks, + "Dropped %u ep ctxs, flags = 0x%x, " + "%u now active.", + num_dropped_eps, drop_flags, + xhci->num_active_eps); +} + +static void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev); + +/* + * 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? 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. + */ +static 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; + 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->tt_info) + old_active_eps = virt_dev->tt_info->active_eps; + + 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; + + if (xhci->quirks & XHCI_ETRON_HOST) { + /* + * Obtaining a new device slot to inform the xHCI host that + * the USB device has been reset. + */ + ret = xhci_disable_and_free_slot(xhci, udev->slot_id); + if (!ret) { + ret = xhci_alloc_dev(hcd, udev); + if (ret == 1) + ret = 0; + else + ret = -EINVAL; + } + return ret; + } + + trace_xhci_discover_or_reset_device(slot_ctx); + + 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, 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); + + ret = xhci_queue_reset_device(xhci, reset_device_cmd, slot_id); + if (ret) { + xhci_dbg(xhci, "FIXME: allocate a command ring segment\n"); + 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 */ + wait_for_completion(reset_device_cmd->completion); + + /* 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_COMMAND_ABORTED: + case COMP_COMMAND_RING_STOPPED: + xhci_warn(xhci, "Timeout waiting for reset device command\n"); + ret = -ETIME; + goto command_cleanup; + case COMP_SLOT_NOT_ENABLED_ERROR: /* 0.95 completion for bad slot ID */ + case COMP_CONTEXT_STATE_ERROR: /* 0.96 completion code for same thing */ + xhci_dbg(xhci, "Can't reset device (slot ID %u) in %s state\n", + slot_id, + xhci_get_slot_state(xhci, virt_dev->out_ctx)); + xhci_dbg(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 the rings. */ + for (i = 1; i < 31; i++) { + struct xhci_virt_ep *ep = &virt_dev->eps[i]; + + if (ep->ep_state & EP_HAS_STREAMS) { + xhci_warn(xhci, "WARN: endpoint 0x%02x has streams on device reset, freeing streams.\n", + xhci_get_endpoint_address(i)); + xhci_free_stream_info(xhci, ep->stream_info); + ep->stream_info = NULL; + ep->ep_state &= ~EP_HAS_STREAMS; + } + + if (ep->ring) { + if (ep->sideband) + xhci_sideband_notify_ep_ring_free(ep->sideband, i); + xhci_debugfs_remove_endpoint(xhci, virt_dev, i); + xhci_free_endpoint_ring(xhci, virt_dev, 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); + ret = 0; + +command_cleanup: + for (i = 0; i < EP_CTX_PER_DEV; i++) + virt_dev->eps[i].ep_state &= ~EP_DROP_PENDING; + + 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. + */ +static 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 xhci_slot_ctx *slot_ctx; + unsigned long flags; + int i, ret; + + /* + * 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(hcd->self.controller); + + 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]; + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->out_ctx); + trace_xhci_free_dev(slot_ctx); + + /* Stop any wayward timer functions (which may grab the lock) */ + for (i = 0; i < 31; i++) + virt_dev->eps[i].ep_state &= ~EP_STOP_CMD_PENDING; + virt_dev->udev = NULL; + xhci_disable_slot(xhci, udev->slot_id); + + spin_lock_irqsave(&xhci->lock, flags); + xhci_free_virt_device(xhci, virt_dev, udev->slot_id); + spin_unlock_irqrestore(&xhci->lock, flags); + +} + +int xhci_disable_slot(struct xhci_hcd *xhci, u32 slot_id) +{ + struct xhci_command *command; + unsigned long flags; + u32 state; + int ret; + + command = xhci_alloc_command(xhci, true, GFP_KERNEL); + if (!command) + return -ENOMEM; + + xhci_debugfs_remove_slot(xhci, slot_id); + + spin_lock_irqsave(&xhci->lock, flags); + /* Don't disable the slot if the host controller is dead. */ + state = readl(&xhci->op_regs->status); + if (state == 0xffffffff || (xhci->xhc_state & XHCI_STATE_DYING) || + (xhci->xhc_state & XHCI_STATE_HALTED)) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_free_command(xhci, command); + return -ENODEV; + } + + ret = xhci_queue_slot_control(xhci, command, TRB_DISABLE_SLOT, + slot_id); + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_free_command(xhci, command); + return ret; + } + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + wait_for_completion(command->completion); + + if (command->status != COMP_SUCCESS) + xhci_warn(xhci, "Unsuccessful disable slot %u command, status %d\n", + slot_id, command->status); + + xhci_free_command(xhci, command); + + return 0; +} + +int xhci_disable_and_free_slot(struct xhci_hcd *xhci, u32 slot_id) +{ + struct xhci_virt_device *vdev = xhci->devs[slot_id]; + int ret; + + ret = xhci_disable_slot(xhci, slot_id); + xhci_free_virt_device(xhci, vdev, slot_id); + return ret; +} + +/* + * 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_trace(xhci, trace_xhci_dbg_quirks, + "Not enough ep ctxs: " + "%u active, need to add 1, limit is %u.", + xhci->num_active_eps, xhci->limit_active_eps); + return -ENOMEM; + } + xhci->num_active_eps += 1; + xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, + "Adding 1 ep ctx, %u now active.", + 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 xhci_virt_device *vdev; + struct xhci_slot_ctx *slot_ctx; + unsigned long flags; + int ret, slot_id; + struct xhci_command *command; + + command = xhci_alloc_command(xhci, true, GFP_KERNEL); + if (!command) + return 0; + + spin_lock_irqsave(&xhci->lock, flags); + ret = xhci_queue_slot_control(xhci, command, TRB_ENABLE_SLOT, 0); + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg(xhci, "FIXME: allocate a command ring segment\n"); + xhci_free_command(xhci, command); + return 0; + } + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + wait_for_completion(command->completion); + slot_id = command->slot_id; + + if (!slot_id || command->status != COMP_SUCCESS) { + xhci_err(xhci, "Error while assigning device slot ID: %s\n", + xhci_trb_comp_code_string(command->status)); + xhci_err(xhci, "Max number of devices this xHCI host supports is %u.\n", + xhci->max_slots); + xhci_free_command(xhci, command); + return 0; + } + + xhci_free_command(xhci, command); + + 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, slot_id, udev, GFP_NOIO)) { + xhci_warn(xhci, "Could not allocate xHCI USB device data structures\n"); + goto disable_slot; + } + vdev = xhci->devs[slot_id]; + slot_ctx = xhci_get_slot_ctx(xhci, vdev->out_ctx); + trace_xhci_alloc_dev(slot_ctx); + + udev->slot_id = slot_id; + + xhci_debugfs_create_slot(xhci, slot_id); + + /* + * 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(hcd->self.controller); + + /* Is this a LS or FS device under a HS hub? */ + /* Hub or peripherial? */ + return 1; + +disable_slot: + xhci_disable_and_free_slot(xhci, udev->slot_id); + + return 0; +} + +/** + * xhci_setup_device - issues an Address Device command to assign a unique + * USB bus address. + * @hcd: USB host controller data structure. + * @udev: USB dev structure representing the connected device. + * @setup: Enum specifying setup mode: address only or with context. + * @timeout_ms: Max wait time (ms) for the command operation to complete. + * + * Return: 0 if successful; otherwise, negative error code. + */ +static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev, + enum xhci_setup_dev setup, unsigned int timeout_ms) +{ + const char *act = setup == SETUP_CONTEXT_ONLY ? "context" : "address"; + unsigned long flags; + 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; + struct xhci_command *command = NULL; + + mutex_lock(&xhci->mutex); + + if (xhci->xhc_state) { /* dying, removing or halted */ + ret = -ESHUTDOWN; + goto out; + } + + if (!udev->slot_id) { + xhci_dbg_trace(xhci, trace_xhci_dbg_address, + "Bad Slot ID %d", udev->slot_id); + ret = -EINVAL; + goto out; + } + + 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); + ret = -EINVAL; + goto out; + } + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->out_ctx); + trace_xhci_setup_device_slot(slot_ctx); + + if (setup == SETUP_CONTEXT_ONLY) { + if (GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)) == + SLOT_STATE_DEFAULT) { + xhci_dbg(xhci, "Slot already in default state\n"); + goto out; + } + } + + command = xhci_alloc_command(xhci, true, GFP_KERNEL); + if (!command) { + ret = -ENOMEM; + goto out; + } + + command->in_ctx = virt_dev->in_ctx; + command->timeout_ms = timeout_ms; + + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(virt_dev->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + ret = -EINVAL; + goto out; + } + /* + * 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->add_flags = cpu_to_le32(SLOT_FLAG | EP0_FLAG); + ctrl_ctx->drop_flags = 0; + + trace_xhci_address_ctx(xhci, virt_dev->in_ctx); + + trace_xhci_address_ctrl_ctx(ctrl_ctx); + spin_lock_irqsave(&xhci->lock, flags); + trace_xhci_setup_device(virt_dev); + ret = xhci_queue_address_device(xhci, command, virt_dev->in_ctx->dma, + udev->slot_id, setup); + if (ret) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_dbg_trace(xhci, trace_xhci_dbg_address, + "FIXME: allocate a command ring segment"); + goto out; + } + 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? */ + wait_for_completion(command->completion); + + /* 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. + */ + switch (command->status) { + case COMP_COMMAND_ABORTED: + case COMP_COMMAND_RING_STOPPED: + xhci_warn(xhci, "Timeout while waiting for setup device command\n"); + ret = -ETIME; + break; + case COMP_CONTEXT_STATE_ERROR: + case COMP_SLOT_NOT_ENABLED_ERROR: + xhci_err(xhci, "Setup ERROR: setup %s command for slot %d.\n", + act, udev->slot_id); + ret = -EINVAL; + break; + case COMP_USB_TRANSACTION_ERROR: + dev_warn(&udev->dev, "Device not responding to setup %s.\n", act); + + mutex_unlock(&xhci->mutex); + ret = xhci_disable_and_free_slot(xhci, udev->slot_id); + if (!ret) { + if (xhci_alloc_dev(hcd, udev) == 1) + xhci_setup_addressable_virt_dev(xhci, udev); + } + kfree(command->completion); + kfree(command); + return -EPROTO; + case COMP_INCOMPATIBLE_DEVICE_ERROR: + dev_warn(&udev->dev, + "ERROR: Incompatible device for setup %s command\n", act); + ret = -ENODEV; + break; + case COMP_SUCCESS: + xhci_dbg_trace(xhci, trace_xhci_dbg_address, + "Successful setup %s command", act); + break; + default: + xhci_err(xhci, + "ERROR: unexpected setup %s command completion code 0x%x.\n", + act, command->status); + trace_xhci_address_ctx(xhci, virt_dev->out_ctx); + ret = -EINVAL; + break; + } + if (ret) + goto out; + temp_64 = xhci_read_64(xhci, &xhci->op_regs->dcbaa_ptr); + xhci_dbg_trace(xhci, trace_xhci_dbg_address, + "Op regs DCBAA ptr = %#016llx", temp_64); + xhci_dbg_trace(xhci, trace_xhci_dbg_address, + "Slot ID %d dcbaa entry @%p = %#016llx", + udev->slot_id, + &xhci->dcbaa.ctx_array[udev->slot_id], + (unsigned long long) + le64_to_cpu(xhci->dcbaa.ctx_array[udev->slot_id])); + xhci_dbg_trace(xhci, trace_xhci_dbg_address, + "Output Context DMA address = %#08llx", + (unsigned long long)virt_dev->out_ctx->dma); + trace_xhci_address_ctx(xhci, virt_dev->in_ctx); + /* + * USB core uses address 1 for the roothubs, so we add one to the + * address given back to us by the HC. + */ + trace_xhci_address_ctx(xhci, virt_dev->out_ctx); + /* Zero the input context control for later use */ + ctrl_ctx->add_flags = 0; + ctrl_ctx->drop_flags = 0; + slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->out_ctx); + udev->devaddr = (u8)(le32_to_cpu(slot_ctx->dev_state) & DEV_ADDR_MASK); + + xhci_dbg_trace(xhci, trace_xhci_dbg_address, + "Internal device address = %d", + le32_to_cpu(slot_ctx->dev_state) & DEV_ADDR_MASK); +out: + mutex_unlock(&xhci->mutex); + if (command) { + kfree(command->completion); + kfree(command); + } + return ret; +} + +static int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev, + unsigned int timeout_ms) +{ + return xhci_setup_device(hcd, udev, SETUP_CONTEXT_ADDRESS, timeout_ms); +} + +static int xhci_enable_device(struct usb_hcd *hcd, struct usb_device *udev) +{ + return xhci_setup_device(hcd, udev, SETUP_CONTEXT_ONLY, + XHCI_CMD_DEFAULT_TIMEOUT); +} + +/* + * 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_hub *rhub; + + rhub = xhci_get_rhub(hcd); + return rhub->ports[port1 - 1]->hw_portnum + 1; +} + +/* + * 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 __maybe_unused 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; + + command = xhci_alloc_command_with_ctx(xhci, true, GFP_KERNEL); + if (!command) + return -ENOMEM; + + 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); + xhci_free_command(xhci, command); + return 0; + } + + /* Attempt to issue an Evaluate Context command to change the MEL. */ + ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx); + if (!ctrl_ctx) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_free_command(xhci, command); + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return -ENOMEM; + } + + xhci_slot_copy(xhci, command->in_ctx, virt_dev->out_ctx); + spin_unlock_irqrestore(&xhci->lock, flags); + + 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); + slot_ctx->dev_state = 0; + + xhci_dbg_trace(xhci, trace_xhci_dbg_context_change, + "Set up evaluate context for LPM MEL change."); + + /* Issue and wait for the evaluate context command. */ + ret = xhci_configure_endpoint(xhci, udev, command, + true, true); + + if (!ret) { + spin_lock_irqsave(&xhci->lock, flags); + virt_dev->current_mel = max_exit_latency; + spin_unlock_irqrestore(&xhci->lock, flags); + } + + xhci_free_command(xhci, command); + + return ret; +} + +#ifdef CONFIG_PM + +/* 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 = FIELD_GET(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; +} + +/* Calculate BESLD, L1 timeout and HIRDM for USB2 PORTHLPMC */ +static int xhci_calculate_usb2_hw_lpm_params(struct usb_device *udev) +{ + u32 field; + int l1; + int besld = 0; + int hirdm = 0; + + field = le32_to_cpu(udev->bos->ext_cap->bmAttributes); + + /* xHCI l1 is set in steps of 256us, xHCI 1.0 section 5.4.11.2 */ + l1 = udev->l1_params.timeout / 256; + + /* device has preferred BESLD */ + if (field & USB_BESL_DEEP_VALID) { + besld = USB_GET_BESL_DEEP(field); + hirdm = 1; + } + + return PORT_BESLD(besld) | PORT_L1_TIMEOUT(l1) | PORT_HIRDM(hirdm); +} + +static int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, + struct usb_device *udev, int enable) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_port **ports; + struct xhci_port_regs __iomem *port_reg; + u32 pm_val, hlpm_val, field; + unsigned int port_num; + unsigned long flags; + int hird, exit_latency; + int ret; + + if (xhci->quirks & XHCI_HW_LPM_DISABLE) + return -EPERM; + + 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); + + ports = xhci->usb2_rhub.ports; + port_num = udev->portnum - 1; + port_reg = ports[port_num]->port_reg; + pm_val = readl(&port_reg->portpmsc); + + xhci_dbg(xhci, "%s port %d USB2 hardware LPM\n", + str_enable_disable(enable), port_num + 1); + + if (enable) { + /* Host supports BESL timeout instead of HIRD */ + if (udev->usb2_hw_lpm_besl_capable) { + /* if device doesn't have a preferred BESL value use a + * default one which works with mixed HIRD and BESL + * systems. See XHCI_DEFAULT_BESL definition in xhci.h + */ + field = le32_to_cpu(udev->bos->ext_cap->bmAttributes); + if ((field & USB_BESL_SUPPORT) && + (field & USB_BESL_BASELINE_VALID)) + hird = USB_GET_BESL_BASELINE(field); + else + hird = udev->l1_params.besl; + + exit_latency = xhci_besl_encoding[hird]; + spin_unlock_irqrestore(&xhci->lock, flags); + + ret = xhci_change_max_exit_latency(xhci, udev, + exit_latency); + if (ret < 0) + return ret; + spin_lock_irqsave(&xhci->lock, flags); + + hlpm_val = xhci_calculate_usb2_hw_lpm_params(udev); + writel(hlpm_val, &port_reg->porthlmpc); + /* flush write */ + readl(&port_reg->porthlmpc); + } else { + hird = xhci_calculate_hird_besl(xhci, udev); + } + + pm_val &= ~PORT_HIRD_MASK; + pm_val |= PORT_HIRD(hird) | PORT_RWE | PORT_L1DS(udev->slot_id); + writel(pm_val, &port_reg->portpmsc); + pm_val = readl(&port_reg->portpmsc); + pm_val |= PORT_HLE; + writel(pm_val, &port_reg->portpmsc); + /* flush write */ + readl(&port_reg->portpmsc); + } else { + pm_val &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK | PORT_L1DS_MASK); + writel(pm_val, &port_reg->portpmsc); + /* flush write */ + readl(&port_reg->portpmsc); + if (udev->usb2_hw_lpm_besl_capable) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_change_max_exit_latency(xhci, udev, 0); + readl_poll_timeout(&ports[port_num]->port_reg->portsc, pm_val, + (pm_val & PORT_PLS_MASK) == XDEV_U0, + 100, 10000); + return 0; + } + } + + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; +} + +static int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_port *port; + u32 capability; + + /* Check if USB3 device at root port is tunneled over USB4 */ + if (hcd->speed >= HCD_USB3 && !udev->parent->parent) { + port = xhci->usb3_rhub.ports[udev->portnum - 1]; + + udev->tunnel_mode = xhci_port_is_tunneled(xhci, port); + if (udev->tunnel_mode == USB_LINK_UNKNOWN) + dev_dbg(&udev->dev, "link tunnel state unknown\n"); + else if (udev->tunnel_mode == USB_LINK_TUNNELED) + dev_dbg(&udev->dev, "tunneled over USB4 link\n"); + else if (udev->tunnel_mode == USB_LINK_NATIVE) + dev_dbg(&udev->dev, "native USB 3.x link\n"); + return 0; + } + + if (hcd->speed >= HCD_USB3 || !udev->lpm_capable || !xhci->hw_lpm_support) + return 0; + + /* 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 0; + + port = xhci->usb2_rhub.ports[udev->portnum - 1]; + capability = port->port_cap->protocol_caps; + + if (capability & XHCI_HLC) { + udev->usb2_hw_lpm_capable = 1; + udev->l1_params.timeout = XHCI_L1_TIMEOUT; + udev->l1_params.besl = XHCI_DEFAULT_BESL; + if (capability & XHCI_BLC) + udev->usb2_hw_lpm_besl_capable = 1; + } + + return 0; +} + +/*---------------------- USB 3.0 Link PM functions ------------------------*/ + +/* 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 ms\n", + state_name, pel); + return USB3_LPM_DISABLED; +} + +/* 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 unsigned long long 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 */ + fallthrough; + 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; + } + + return timeout_ns; +} + +/* Returns the hub-encoded U1 timeout value. */ +static u16 xhci_calculate_u1_timeout(struct xhci_hcd *xhci, + struct usb_device *udev, + struct usb_endpoint_descriptor *desc) +{ + unsigned long long timeout_ns; + + /* Prevent U1 if service interval is shorter than U1 exit latency */ + if (usb_endpoint_xfer_int(desc) || usb_endpoint_xfer_isoc(desc)) { + if (xhci_service_interval_to_ns(desc) <= udev->u1_params.mel) { + dev_dbg(&udev->dev, "Disable U1, ESIT shorter than exit latency\n"); + return USB3_LPM_DISABLED; + } + } + + if (xhci->quirks & (XHCI_INTEL_HOST | XHCI_ZHAOXIN_HOST)) + timeout_ns = xhci_calculate_intel_u1_timeout(udev, desc); + else + timeout_ns = udev->u1_params.sel; + + /* The U1 timeout is encoded in 1us intervals. + * Don't return a timeout of zero, because that's USB3_LPM_DISABLED. + */ + if (timeout_ns == USB3_LPM_DISABLED) + timeout_ns = 1; + else + timeout_ns = DIV_ROUND_UP_ULL(timeout_ns, 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 U1. + */ + if (timeout_ns <= USB3_LPM_U1_MAX_TIMEOUT) + return timeout_ns; + dev_dbg(&udev->dev, "Hub-initiated U1 disabled due to long timeout %lluus\n", + timeout_ns); + return xhci_get_timeout_no_hub_lpm(udev, USB3_LPM_U1); +} + +/* 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 unsigned long long 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; + + return timeout_ns; +} + +/* Returns the hub-encoded U2 timeout value. */ +static u16 xhci_calculate_u2_timeout(struct xhci_hcd *xhci, + struct usb_device *udev, + struct usb_endpoint_descriptor *desc) +{ + unsigned long long timeout_ns; + + /* Prevent U2 if service interval is shorter than U2 exit latency */ + if (usb_endpoint_xfer_int(desc) || usb_endpoint_xfer_isoc(desc)) { + if (xhci_service_interval_to_ns(desc) <= udev->u2_params.mel) { + dev_dbg(&udev->dev, "Disable U2, ESIT shorter than exit latency\n"); + return USB3_LPM_DISABLED; + } + } + + if (xhci->quirks & (XHCI_INTEL_HOST | XHCI_ZHAOXIN_HOST)) + timeout_ns = xhci_calculate_intel_u2_timeout(udev, desc); + else + timeout_ns = udev->u2_params.sel; + + /* 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 %lluus\n", + timeout_ns * 256); + 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) + return xhci_calculate_u1_timeout(xhci, udev, desc); + else if (state == USB3_LPM_U2) + return xhci_calculate_u2_timeout(xhci, 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, and + * the U1 or U2 exit latency was too high to allow + * device-initiated LPM as well, then we will disable LPM + * for this device, so stop searching any further. + */ + if (alt_timeout == USB3_LPM_DISABLED) { + *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; + } + return 0; +} + +static int xhci_check_tier_policy(struct xhci_hcd *xhci, + struct usb_device *udev, + enum usb3_link_state state) +{ + struct usb_device *parent = udev->parent; + int tier = 1; /* roothub is tier1 */ + + while (parent) { + parent = parent->parent; + tier++; + } + + if (xhci->quirks & XHCI_INTEL_HOST && tier > 3) + goto fail; + if (xhci->quirks & XHCI_ZHAOXIN_HOST && tier > 2) + goto fail; + + return 0; +fail: + dev_dbg(&udev->dev, "Tier policy prevents U1/U2 LPM states for devices at tier %d\n", + tier); + return -E2BIG; +} + +/* 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; + } + + /* 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 < config->desc.bNumInterfaces; 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); + timeout = xhci_get_timeout_no_hub_lpm(udev, + state); + if (timeout == USB3_LPM_DISABLED) + return timeout; + } + } + + /* 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; +} + +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); + + mel_us = max(u1_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. */ +static int xhci_enable_usb3_lpm_timeout(struct usb_hcd *hcd, + struct usb_device *udev, enum usb3_link_state state) +{ + struct xhci_hcd *xhci; + struct xhci_port *port; + 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; + + if (xhci_check_tier_policy(xhci, udev, state) < 0) + return USB3_LPM_DISABLED; + + /* If connected to root port then check port can handle lpm */ + if (udev->parent && !udev->parent->parent) { + port = xhci->usb3_rhub.ports[udev->portnum - 1]; + if (port->lpm_incapable) + 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; +} + +static 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; + + 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); + return xhci_change_max_exit_latency(xhci, udev, mel); +} +#else /* CONFIG_PM */ + +static int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, + struct usb_device *udev, int enable) +{ + return 0; +} + +static int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev) +{ + return 0; +} + +static int xhci_enable_usb3_lpm_timeout(struct usb_hcd *hcd, + struct usb_device *udev, enum usb3_link_state state) +{ + return USB3_LPM_DISABLED; +} + +static 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_with_ctx(xhci, true, mem_flags); + if (!config_cmd) + return -ENOMEM; + + ctrl_ctx = xhci_get_input_control_ctx(config_cmd->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + xhci_free_command(xhci, config_cmd); + 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->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); + /* + * refer to section 6.2.2: MTT should be 0 for full speed hub, + * but it may be already set to 1 when setup an xHCI virtual + * device, so clear it anyway. + */ + if (tt->multi) + slot_ctx->dev_info |= cpu_to_le32(DEV_MTT); + else if (hdev->speed == USB_SPEED_FULL) + 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"); + + /* 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_free_command(xhci, config_cmd); + return ret; +} +EXPORT_SYMBOL_GPL(xhci_update_hub_device); + +static int xhci_get_frame(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + /* EHCI mods by the periodic size. Why? */ + return readl(&xhci->run_regs->microframe_index) >> 3; +} + +static void xhci_hcd_init_usb2_data(struct xhci_hcd *xhci, struct usb_hcd *hcd) +{ + xhci->usb2_rhub.hcd = hcd; + 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; +} + +static void xhci_hcd_init_usb3_data(struct xhci_hcd *xhci, struct usb_hcd *hcd) +{ + unsigned int minor_rev; + + /* + * Early xHCI 1.1 spec did not mention USB 3.1 capable hosts + * should return 0x31 for sbrn, or that the minor revision + * is a two digit BCD containig minor and sub-minor numbers. + * This was later clarified in xHCI 1.2. + * + * Some USB 3.1 capable hosts therefore have sbrn 0x30, and + * minor revision set to 0x1 instead of 0x10. + */ + if (xhci->usb3_rhub.min_rev == 0x1) + minor_rev = 1; + else + minor_rev = xhci->usb3_rhub.min_rev / 0x10; + + switch (minor_rev) { + case 2: + hcd->speed = HCD_USB32; + hcd->self.root_hub->speed = USB_SPEED_SUPER_PLUS; + hcd->self.root_hub->rx_lanes = 2; + hcd->self.root_hub->tx_lanes = 2; + hcd->self.root_hub->ssp_rate = USB_SSP_GEN_2x2; + break; + case 1: + hcd->speed = HCD_USB31; + hcd->self.root_hub->speed = USB_SPEED_SUPER_PLUS; + hcd->self.root_hub->ssp_rate = USB_SSP_GEN_2x1; + break; + } + xhci_info(xhci, "Host supports USB 3.%x %sSuperSpeed\n", + minor_rev, minor_rev ? "Enhanced " : ""); + + xhci->usb3_rhub.hcd = hcd; +} + +int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) +{ + struct xhci_hcd *xhci; + /* + * TODO: Check with DWC3 clients for sysdev according to + * quirks + */ + struct device *dev = hcd->self.sysdev; + int retval; + u32 hcs_params1; + u32 hc_capbase; + + /* Accept arbitrarily long scatter-gather lists */ + hcd->self.sg_tablesize = ~0; + + /* support to build packet from discontinuous buffers */ + hcd->self.no_sg_constraint = 1; + + /* XHCI controllers don't stop the ep queue on short packets :| */ + hcd->self.no_stop_on_short = 1; + + xhci = hcd_to_xhci(hcd); + + if (!usb_hcd_is_primary_hcd(hcd)) { + xhci_hcd_init_usb3_data(xhci, hcd); + return 0; + } + + mutex_init(&xhci->mutex); + xhci->main_hcd = hcd; + xhci->cap_regs = hcd->regs; + hc_capbase = readl(&xhci->cap_regs->hc_capbase); + if (hc_capbase == U32_MAX) { + xhci_warn(xhci, "Host controller not accessible, removed?\n"); + return -ENODEV; + } + xhci->op_regs = hcd->regs + FIELD_GET(HC_LENGTH, hc_capbase); + + xhci->run_regs = hcd->regs + + (readl(&xhci->cap_regs->run_regs_off) & RTSOFF_MASK); + /* Cache read-only capability registers */ + hcs_params1 = readl(&xhci->cap_regs->hcs_params1); + xhci->hcs_params2 = readl(&xhci->cap_regs->hcs_params2); + xhci->hcs_params3 = readl(&xhci->cap_regs->hcs_params3); + xhci->hci_version = FIELD_GET(HC_VERSION, hc_capbase); + xhci->hcc_params = readl(&xhci->cap_regs->hcc_params); + if (xhci->hci_version > 0x100) + xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2); + + xhci->dma_mask_bits = 64; + xhci->max_slots = min(FIELD_GET(HCS_SLOTS_MASK, hcs_params1), MAX_HC_SLOTS); + xhci->max_ports = min(FIELD_GET(HCS_MAX_PORTS, hcs_params1), MAX_HC_PORTS); + + /* xhci-plat or xhci-pci might have set max_interrupters already */ + if (!xhci->max_interrupters) + xhci->max_interrupters = min(FIELD_GET(HCS_MAX_INTRS, hcs_params1), MAX_HC_INTRS); + else if (xhci->max_interrupters > FIELD_GET(HCS_MAX_INTRS, hcs_params1)) + xhci->max_interrupters = FIELD_GET(HCS_MAX_INTRS, hcs_params1); + + xhci->quirks |= quirks; + + if (get_quirks) + 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; + + if (xhci->hci_version == 0x95 && link_quirk) { + xhci_dbg(xhci, "QUIRK: Not clearing Link TRB chain bits"); + xhci->quirks |= XHCI_LINK_TRB_QUIRK; + } + + /* Make sure the HC is halted. */ + retval = xhci_halt(xhci); + if (retval) + return retval; + + xhci_zero_64b_regs(xhci); + + xhci_dbg(xhci, "Resetting HCD\n"); + /* Reset the internal HC memory state and registers. */ + retval = xhci_reset(xhci, XHCI_RESET_LONG_USEC); + if (retval) + return retval; + xhci_dbg(xhci, "Reset complete\n"); + + /* + * On some xHCI controllers (e.g. R-Car SoCs), the AC64 bit (bit 0) + * of HCCPARAMS1 is set to 1. However, the xHCs don't support 64-bit + * address memory pointers actually. So, this driver clears the AC64 + * bit of xhci->hcc_params to call dma_set_coherent_mask(dev, + * DMA_BIT_MASK(32)) in this xhci_gen_setup(). + */ + if (xhci->quirks & XHCI_NO_64BIT_SUPPORT) + xhci->hcc_params &= ~HCC_64BIT_ADDR; + + /* + * Set dma_mask and coherent_dma_mask to 64-bits if xHC supports + * 64-bit addressing, unless a controller-specific quirk callback + * limits the usable address width. + */ + if ((xhci->hcc_params & HCC_64BIT_ADDR) && + !dma_set_mask(dev, DMA_BIT_MASK(xhci->dma_mask_bits))) { + xhci_dbg(xhci, "Enabling %u-bit DMA addresses.\n", + xhci->dma_mask_bits); + dma_set_coherent_mask(dev, DMA_BIT_MASK(xhci->dma_mask_bits)); + } else { + /* + * This is to avoid error in cases where a 32-bit USB + * controller is used on a 64-bit capable system. + */ + retval = dma_set_mask(dev, DMA_BIT_MASK(32)); + if (retval) + return retval; + xhci_dbg(xhci, "Enabling 32-bit DMA addresses.\n"); + dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); + } + + spin_lock_init(&xhci->lock); + INIT_LIST_HEAD(&xhci->cmd_list); + INIT_DELAYED_WORK(&xhci->cmd_timer, xhci_handle_command_timeout); + init_completion(&xhci->cmd_ring_stop_completion); + xhci_hcd_page_size(xhci); + + /* Allocate xHCI data structures */ + retval = xhci_mem_init(xhci, GFP_KERNEL); + if (retval) + return retval; + + /* Initialize HCD and host controller data structures */ + xhci_init(hcd); + + if (xhci_hcd_is_usb3(hcd)) + xhci_hcd_init_usb3_data(xhci, hcd); + else + xhci_hcd_init_usb2_data(xhci, hcd); + + xhci_info(xhci, "hcc params 0x%08x hci version 0x%x quirks 0x%016llx\n", + xhci->hcc_params, xhci->hci_version, xhci->quirks); + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_gen_setup); + +static void xhci_clear_tt_buffer_complete(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct xhci_hcd *xhci; + struct usb_device *udev; + unsigned int slot_id; + unsigned int ep_index; + unsigned long flags; + + xhci = hcd_to_xhci(hcd); + + spin_lock_irqsave(&xhci->lock, flags); + udev = (struct usb_device *)ep->hcpriv; + slot_id = udev->slot_id; + ep_index = xhci_get_endpoint_index(&ep->desc); + + xhci->devs[slot_id]->eps[ep_index].ep_state &= ~EP_CLEARING_TT; + xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + spin_unlock_irqrestore(&xhci->lock, flags); +} + +static const struct hc_driver xhci_hc_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_DMA | HCD_USB3 | HCD_SHARED | + HCD_BH, + + /* + * basic lifecycle operations + */ + .reset = NULL, /* set in xhci_init_driver() */ + .start = xhci_run, + .stop = xhci_stop, + .shutdown = xhci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .map_urb_for_dma = xhci_map_urb_for_dma, + .unmap_urb_for_dma = xhci_unmap_urb_for_dma, + .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_disable = xhci_endpoint_disable, + .endpoint_reset = xhci_endpoint_reset, + .check_bandwidth = xhci_check_bandwidth, + .reset_bandwidth = xhci_reset_bandwidth, + .address_device = xhci_address_device, + .enable_device = xhci_enable_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, + .get_resuming_ports = xhci_get_resuming_ports, + + /* + * 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, + .clear_tt_buffer_complete = xhci_clear_tt_buffer_complete, +}; + +void xhci_init_driver(struct hc_driver *drv, + const struct xhci_driver_overrides *over) +{ + BUG_ON(!over); + + /* Copy the generic table to drv then apply the overrides */ + *drv = xhci_hc_driver; + + if (over) { + drv->hcd_priv_size += over->extra_priv_size; + if (over->reset) + drv->reset = over->reset; + if (over->start) + drv->start = over->start; + if (over->add_endpoint) + drv->add_endpoint = over->add_endpoint; + if (over->drop_endpoint) + drv->drop_endpoint = over->drop_endpoint; + if (over->check_bandwidth) + drv->check_bandwidth = over->check_bandwidth; + if (over->reset_bandwidth) + drv->reset_bandwidth = over->reset_bandwidth; + if (over->update_hub_device) + drv->update_hub_device = over->update_hub_device; + if (over->hub_control) + drv->hub_control = over->hub_control; + } +} +EXPORT_SYMBOL_GPL(xhci_init_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); + +static int __init xhci_hcd_init(void) +{ + /* + * 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) != 8*32/8); + BUILD_BUG_ON(sizeof(struct xhci_intr_reg) != 8*32/8); + /* xhci_run_regs has eight fields and embeds 1024 xhci_intr_regs */ + BUILD_BUG_ON(sizeof(struct xhci_run_regs) != (8+8*1024)*32/8); + + if (usb_disabled()) + return -ENODEV; + + xhci_debugfs_create_root(); + xhci_dbc_init(); + + return 0; +} + +/* + * If an init function is provided, an exit function must also be provided + * to allow module unload. + */ +static void __exit xhci_hcd_fini(void) +{ + xhci_debugfs_remove_root(); + xhci_dbc_exit(); +} + +module_init(xhci_hcd_init); +module_exit(xhci_hcd_fini); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h new file mode 100644 index 000000000..c7bfa7f02 --- /dev/null +++ b/drivers/usb/host/xhci.h @@ -0,0 +1,2616 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * + * Author: Sarah Sharp + * Some code borrowed from the Linux EHCI driver. + */ + +#ifndef __LINUX_XHCI_HCD_H +#define __LINUX_XHCI_HCD_H + +#include +#include +#include +#include +#include +#include +#include + +/* Code sharing between pci-quirks and xhci hcd */ +#include "xhci-ext-caps.h" +#include "pci-quirks.h" + +#include "xhci-port.h" +#include "xhci-caps.h" + +/* max buffer size for trace and debug messages */ +#define XHCI_MSG_MAX 500 + +/* xHCI PCI Configuration Registers */ +#define XHCI_SBRN_OFFSET (0x60) + +/* + * Max number of Devices Slots. xHCI specification section 5.3.3 + * Valid values are in the range of 1 to 255. + */ +#define MAX_HC_SLOTS 255 +/* + * Max Number of Ports. xHCI specification section 5.3.3 + * Valid values are in the range of 1 to 255. + */ +#define MAX_HC_PORTS 127 +/* + * Max number of Interrupter Register Sets. xHCI specification section 5.3.3 + * Valid values are in the range of 1 to 1024. + */ +#define MAX_HC_INTRS 128 + +/* + * 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 + * @hcc_params2: HCCPARAMS2 Capability Parameters 2, xhci 1.1 only + */ +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; + __le32 hcc_params2; /* xhci 1.1 */ + /* Reserved up to (CAPLENGTH - 0x1C) */ +}; + +/* + * struct xhci_port_regs - Host Controller USB Port Register Set. xHCI spec 5.4.8 + * @portsc: Port Status and Control + * @portpmsc: Port Power Management Status and Control + * @portli: Port Link Info + * @porthlmpc: Port Hardware LPM Control + */ +struct xhci_port_regs { + __le32 portsc; + __le32 portpmsc; + __le32 portli; + __le32 porthlmpc; +}; + +/** + * 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_regs: Port Register Sets, from 1 to MaxPorts (defined by HCSPARAMS1). + */ +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]; + struct xhci_port_regs port_regs[]; +}; + +/* 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 BIT(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 BIT(7) +/* host controller save/restore state. */ +#define CMD_CSS BIT(8) +#define CMD_CRS BIT(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 BIT(11) +/* bit 14 Extended TBC Enable, changes Isoc TRB fields to support larger TBC */ +#define CMD_ETE BIT(14) +/* bits 15:31 are reserved (and should be preserved on writes). */ + +#define XHCI_RESET_LONG_USEC (10 * 1000 * 1000) +#define XHCI_RESET_SHORT_USEC (250 * 1000) + +/* 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 BIT(2) +/* event interrupt - clear this prior to clearing any IP flags in IR set*/ +#define STS_EINT BIT(3) +/* port change detect */ +#define STS_PORT BIT(4) +/* bits 5:7 reserved and zeroed */ +/* save state status - '1' means xHC is saving state */ +#define STS_SAVE BIT(8) +/* restore state status - '1' means xHC is restoring state */ +#define STS_RESTORE BIT(9) +/* true: save or restore error */ +#define STS_SRE BIT(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 BIT(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) +/* 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 BIT(1) + +/* CRCR - Command Ring Control Register - cmd_ring bitmasks */ +/* bit 0 - Cycle bit indicates the ownership of the command ring */ +#define CMD_RING_CYCLE BIT_ULL(0) +/* stop ring operation after completion of the currently executing command */ +#define CMD_RING_PAUSE BIT_ULL(1) +/* stop ring immediately - abort the currently executing command */ +#define CMD_RING_ABORT BIT_ULL(2) +/* true: command ring is running */ +#define CMD_RING_RUNNING BIT_ULL(3) +/* bits 63:6 - Command Ring pointer */ +#define CMD_RING_PTR_MASK GENMASK_ULL(63, 6) + +/* CONFIG - Configure Register - config_reg bitmasks */ +/* bits 0:7 - maximum number of device slots enabled (NumSlotsEn) */ +#define MAX_DEVS(p) ((p) & 0xff) +/* bit 8: U3 Entry Enabled, assert PLC when root port enters U3, xhci 1.1 */ +#define CONFIG_U3E BIT(8) +/* bit 9: Configuration Information Enable, xhci 1.1 */ +#define CONFIG_CIE BIT(9) +/* bits 10:31 - reserved and should be preserved */ + +/* bits 15:0 - HCD page shift bit */ +#define XHCI_PAGE_SIZE_MASK 0xffff + +/** + * struct xhci_intr_reg - Interrupt Register Set, v1.2 section 5.5.2. + * @iman: IMAN - Interrupt Management Register. Used to enable + * interrupts and check for pending interrupts. + * @imod: IMOD - Interrupt Moderation Register. Used to throttle interrupts. + * @erst_size: ERSTSZ - Number of segments in the Event Ring Segment Table (ERST). + * @erst_base: ERSTBA - Event ring segment table base address. + * @erst_dequeue: ERDP - 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 iman; + __le32 imod; + __le32 erst_size; + __le32 rsvd; + __le64 erst_base; + __le64 erst_dequeue; +}; + +/* iman bitmasks */ +/* bit 0 - Interrupt Pending (IP), whether there is an interrupt pending. Write-1-to-clear. */ +#define IMAN_IP BIT(0) +/* bit 1 - Interrupt Enable (IE), whether the interrupter is capable of generating an interrupt */ +#define IMAN_IE BIT(1) + +/* imod bitmasks */ +/* + * bits 15:0 - Interrupt Moderation Interval, the 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 IMODI_MASK (0xffff) +/* bits 31:16 - Interrupt Moderation Counter, used to count down the time to the next interrupt */ +#define IMODC_MASK (0xffff << 16) + +/* erst_size bitmasks */ +/* bits 15:0 - Event Ring Segment Table Size, number of ERST entries */ +#define ERST_SIZE_MASK (0xffff) + +/* erst_base bitmasks */ +/* bits 63:6 - Event Ring Segment Table Base Address Register */ +#define ERST_BASE_ADDRESS_MASK GENMASK_ULL(63, 6) + +/* erst_dequeue bitmasks */ +/* + * bits 2:0 - Dequeue ERST Segment Index (DESI), is the segment number (or alias) where the + * current dequeue pointer lies. This is an optional HW hint. + */ +#define ERST_DESI_MASK (0x7) +/* + * bit 3 - Event Handler Busy (EHB), whether the event ring is scheduled to be serviced by + * a work queue (or delayed service routine)? + */ +#define ERST_EHB BIT_ULL(3) +/* bits 63:4 - Event Ring Dequeue Pointer */ +#define ERST_PTR_MASK GENMASK_ULL(63, 4) + +/** + * 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[1024]; +}; + +/* Bits [13:3] of the microframe index equals the 1ms frame index */ +#define MFINDEX_TO_FRAME(p) (((p) >> 3) & 0x7ff) +#define MAX_FRAMES 2048 +#define MAX_UFRAMES (MAX_FRAMES * 8) + +/** + * 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 + +#define PLT_MASK (0x03 << 6) +#define PLT_SYM (0x00 << 6) +#define PLT_ASYM_RX (0x02 << 6) +#define PLT_ASYM_TX (0x03 << 6) + +/** + * 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) +#define GET_DEV_SPEED(n) (((n) & DEV_SPEED) >> 20) +/* bit 24 reserved */ +/* Is this LS/FS device connected through a HS hub? - bit 25 */ +#define DEV_MTT BIT(25) +/* Set if the device is a hub - bit 26 */ +#define DEV_HUB BIT(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 BIT(0) +#define EP0_FLAG BIT(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) +#define DEVINFO_TO_MAX_PORTS(p) (((p) & (0xff << 24)) >> 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) +#define GET_TT_THINK_TIME(p) (((p) & (0x3 << 16)) >> 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 (0x7) +#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 +#define GET_EP_CTX_STATE(ctx) (le32_to_cpu((ctx)->ep_info) & EP_STATE_MASK) + +/* 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) +#define CTX_TO_EP_MAXPSTREAMS(p) (((p) & EP_MAXPSTREAMS_MASK) >> 10) +/* Endpoint is set up with a Linear Stream Array (vs. Secondary Stream Array) */ +#define EP_HAS_LSA BIT(15) +/* hosts with LEC=1 use bits 31:24 as ESIT high bits. */ +#define CTX_TO_MAX_ESIT_PAYLOAD_HI(p) (((p) >> 24) & 0xff) + +/* 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) + +/* tx_info bitmasks */ +#define EP_AVG_TRB_LENGTH(p) ((p) & 0xffff) +#define EP_MAX_ESIT_PAYLOAD_LO(p) (((p) & 0xffff) << 16) +#define EP_MAX_ESIT_PAYLOAD_HI(p) ((((p) >> 16) & 0xff) << 24) +#define CTX_TO_MAX_ESIT_PAYLOAD(p) (((p) >> 16) & 0xffff) + +/* deq bitmasks */ +#define EP_CTX_CYCLE_MASK BIT_ULL(0) +/* bits 63:4 - TR Dequeue Pointer */ +#define TR_DEQ_PTR_MASK GENMASK_ULL(63, 4) + + +/** + * 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; + u32 comp_param; + int slot_id; + /* 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; + /* xHCI command response timeout in milliseconds */ + unsigned int timeout_ms; +}; + +/* 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) & 0x7) << 1) +#define CTX_TO_SCT(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 +#define GET_PORT_BW_ARRAY_SIZE 256 + +/* 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_virt_device *vdev; /* parent */ + unsigned int ep_index; + 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 err_count; + unsigned int ep_state; +#define SET_DEQ_PENDING BIT(0) +#define EP_HALTED BIT(1) /* For stall handling */ +#define EP_STOP_CMD_PENDING BIT(2) /* For URB cancellation */ +/* Transitioning the endpoint to using streams, don't enqueue URBs */ +#define EP_GETTING_STREAMS BIT(3) +#define EP_HAS_STREAMS BIT(4) +/* Transitioning the endpoint to not using streams, don't enqueue URBs */ +#define EP_GETTING_NO_STREAMS BIT(5) +#define EP_HARD_CLEAR_TOGGLE BIT(6) +#define EP_SOFT_CLEAR_TOGGLE BIT(7) +/* usb_hub_clear_tt_buffer is in progress */ +#define EP_CLEARING_TT BIT(8) +#define EP_DROP_PENDING BIT(9) /* port disconnect or link error, don't restart */ + /* ---- Related to URB cancellation ---- */ + struct list_head cancelled_td_list; + /* 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; + unsigned long stop_time; + /* Isoch Frame ID checking storage */ + int next_uframe; + /* Use new Isoch TRB layout needed for extended TBC support */ + bool use_extended_tbc; + /* set if this endpoint is controlled via sideband access*/ + struct xhci_sideband *sideband; +}; + +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; +}; + +#define EP_CTX_PER_DEV 31 + +struct xhci_virt_device { + int slot_id; + 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; + struct xhci_virt_ep eps[EP_CTX_PER_DEV]; + struct xhci_port *rhub_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; + /* Used for the debugfs interfaces. */ + void *debugfs_private; + /* set if this endpoint is controlled via sideband access*/ + struct xhci_sideband *sideband; +}; + +/* + * 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 + * @ctx_array: Pointer to an array of addresses. The array size depends on Max + * Slots read from HCSPARAMS1. + * @dma: DMA address to @ctx_array + * + * Device Context Base Address Array (DCBAA) - Section 6.1. + * ctx_array[0]: Scratchpad Buffer Array Base Address + * ctx_array[1-MaxSlots]: Device Context Base Address + */ +struct xhci_device_context_array { + __le64 *ctx_array; + dma_addr_t dma; +}; + + +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 flags bitfield, also for select command completion events */ +#define TRB_TO_SLOT_ID(p) (((p) >> 24) & 0xff) +#define SLOT_ID_FOR_TRB(p) (((p) & 0xff) << 24) + +#define TRB_TO_EP_ID(p) (((p) >> 16) & 0x1f) /* Endpoint ID 1 - 31 */ +#define EP_ID_FOR_TRB(p) (((p) & 0x1f) << 16) + +#define TRB_TO_EP_INDEX(p) (TRB_TO_EP_ID(p) - 1) /* Endpoint index 0 - 30 */ +#define EP_INDEX_FOR_TRB(p) ((((p) + 1) & 0x1f) << 16) + +/* Transfer event TRB length bit mask */ +#define EVENT_TRB_LEN(p) ((p) & 0xffffff) + +/* 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_INVALID 0 +#define COMP_SUCCESS 1 +#define COMP_DATA_BUFFER_ERROR 2 +#define COMP_BABBLE_DETECTED_ERROR 3 +#define COMP_USB_TRANSACTION_ERROR 4 +#define COMP_TRB_ERROR 5 +#define COMP_STALL_ERROR 6 +#define COMP_RESOURCE_ERROR 7 +#define COMP_BANDWIDTH_ERROR 8 +#define COMP_NO_SLOTS_AVAILABLE_ERROR 9 +#define COMP_INVALID_STREAM_TYPE_ERROR 10 +#define COMP_SLOT_NOT_ENABLED_ERROR 11 +#define COMP_ENDPOINT_NOT_ENABLED_ERROR 12 +#define COMP_SHORT_PACKET 13 +#define COMP_RING_UNDERRUN 14 +#define COMP_RING_OVERRUN 15 +#define COMP_VF_EVENT_RING_FULL_ERROR 16 +#define COMP_PARAMETER_ERROR 17 +#define COMP_BANDWIDTH_OVERRUN_ERROR 18 +#define COMP_CONTEXT_STATE_ERROR 19 +#define COMP_NO_PING_RESPONSE_ERROR 20 +#define COMP_EVENT_RING_FULL_ERROR 21 +#define COMP_INCOMPATIBLE_DEVICE_ERROR 22 +#define COMP_MISSED_SERVICE_ERROR 23 +#define COMP_COMMAND_RING_STOPPED 24 +#define COMP_COMMAND_ABORTED 25 +#define COMP_STOPPED 26 +#define COMP_STOPPED_LENGTH_INVALID 27 +#define COMP_STOPPED_SHORT_PACKET 28 +#define COMP_MAX_EXIT_LATENCY_TOO_LARGE_ERROR 29 +#define COMP_ISOCH_BUFFER_OVERRUN 31 +#define COMP_EVENT_LOST_ERROR 32 +#define COMP_UNDEFINED_ERROR 33 +#define COMP_INVALID_STREAM_ID_ERROR 34 +#define COMP_SECONDARY_BANDWIDTH_ERROR 35 +#define COMP_SPLIT_TRANSACTION_ERROR 36 + +static inline const char *xhci_trb_comp_code_string(u8 status) +{ + switch (status) { + case COMP_INVALID: + return "Invalid"; + case COMP_SUCCESS: + return "Success"; + case COMP_DATA_BUFFER_ERROR: + return "Data Buffer Error"; + case COMP_BABBLE_DETECTED_ERROR: + return "Babble Detected"; + case COMP_USB_TRANSACTION_ERROR: + return "USB Transaction Error"; + case COMP_TRB_ERROR: + return "TRB Error"; + case COMP_STALL_ERROR: + return "Stall Error"; + case COMP_RESOURCE_ERROR: + return "Resource Error"; + case COMP_BANDWIDTH_ERROR: + return "Bandwidth Error"; + case COMP_NO_SLOTS_AVAILABLE_ERROR: + return "No Slots Available Error"; + case COMP_INVALID_STREAM_TYPE_ERROR: + return "Invalid Stream Type Error"; + case COMP_SLOT_NOT_ENABLED_ERROR: + return "Slot Not Enabled Error"; + case COMP_ENDPOINT_NOT_ENABLED_ERROR: + return "Endpoint Not Enabled Error"; + case COMP_SHORT_PACKET: + return "Short Packet"; + case COMP_RING_UNDERRUN: + return "Ring Underrun"; + case COMP_RING_OVERRUN: + return "Ring Overrun"; + case COMP_VF_EVENT_RING_FULL_ERROR: + return "VF Event Ring Full Error"; + case COMP_PARAMETER_ERROR: + return "Parameter Error"; + case COMP_BANDWIDTH_OVERRUN_ERROR: + return "Bandwidth Overrun Error"; + case COMP_CONTEXT_STATE_ERROR: + return "Context State Error"; + case COMP_NO_PING_RESPONSE_ERROR: + return "No Ping Response Error"; + case COMP_EVENT_RING_FULL_ERROR: + return "Event Ring Full Error"; + case COMP_INCOMPATIBLE_DEVICE_ERROR: + return "Incompatible Device Error"; + case COMP_MISSED_SERVICE_ERROR: + return "Missed Service Error"; + case COMP_COMMAND_RING_STOPPED: + return "Command Ring Stopped"; + case COMP_COMMAND_ABORTED: + return "Command Aborted"; + case COMP_STOPPED: + return "Stopped"; + case COMP_STOPPED_LENGTH_INVALID: + return "Stopped - Length Invalid"; + case COMP_STOPPED_SHORT_PACKET: + return "Stopped - Short Packet"; + case COMP_MAX_EXIT_LATENCY_TOO_LARGE_ERROR: + return "Max Exit Latency Too Large Error"; + case COMP_ISOCH_BUFFER_OVERRUN: + return "Isoch Buffer Overrun"; + case COMP_EVENT_LOST_ERROR: + return "Event Lost Error"; + case COMP_UNDEFINED_ERROR: + return "Undefined Error"; + case COMP_INVALID_STREAM_ID_ERROR: + return "Invalid Stream ID Error"; + case COMP_SECONDARY_BANDWIDTH_ERROR: + return "Secondary Bandwidth Error"; + case COMP_SPLIT_TRANSACTION_ERROR: + return "Split Transaction Error"; + default: + return "Unknown!!"; + } +} + +struct xhci_link_trb { + /* 64-bit segment pointer*/ + __le64 segment_ptr; + __le32 intr_target; + __le32 control; +}; + +/* control bitfields */ +#define LINK_TOGGLE BIT(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; +}; + +/* status bitmasks */ +#define COMP_PARAM(p) ((p) & 0xffffff) /* Command Completion Parameter */ + +/* Address device - disable SetAddress */ +#define TRB_BSR BIT(9) + +/* Configure Endpoint - Deconfigure */ +#define TRB_DC BIT(9) + +/* Stop Ring - Transfer State Preserve */ +#define TRB_TSP BIT(9) + +enum xhci_ep_reset_type { + EP_HARD_RESET, + EP_SOFT_RESET, +}; + +/* Force Event */ +#define TRB_TO_VF_INTR_TARGET(p) (((p) & (0x3ff << 22)) >> 22) +#define TRB_TO_VF_ID(p) (((p) & (0xff << 16)) >> 16) + +/* Set Latency Tolerance Value */ +#define TRB_TO_BELT(p) (((p) & (0xfff << 16)) >> 16) + +/* Get Port Bandwidth */ +#define TRB_TO_DEV_SPEED(p) (((p) & (0xf << 16)) >> 16) + +/* Force Header */ +#define TRB_TO_PACKET_TYPE(p) ((p) & 0x1f) +#define TRB_TO_ROOTHUB_PORT(p) (((p) & (0xff << 24)) >> 24) + +enum xhci_setup_dev { + SETUP_CONTEXT_ONLY, + SETUP_CONTEXT_ADDRESS, +}; + +/* bits 16:23 are the virtual function ID */ +/* bits 24:31 are the slot ID */ + +/* bits 19:16 are the dev speed */ +#define DEV_SPEED_FOR_TRB(p) ((p) << 16) + +/* Stop Endpoint TRB - ep_index to endpoint ID for this TRB */ +#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, 6.4.3.9 */ +#define TRB_TO_STREAM_ID(p) ((((p) & (0xffff << 16)) >> 16)) +#define STREAM_ID_FOR_TRB(p) ((((p)) & 0xffff) << 16) +#define SCT_FOR_TRB(p) (((p) & 0x7) << 1) + +/* Link TRB specific fields */ +#define TRB_TC BIT(1) + +/* Port Status Change Event TRB fields */ +/* Port ID - bits 31:24 */ +#define GET_PORT_ID(p) (((p) & (0xff << 24)) >> 24) + +#define EVENT_DATA BIT(2) + +/* Normal TRB fields */ +/* transfer_len bitmasks - bits 0:16 */ +#define TRB_LEN(p) ((p) & 0x1ffff) +/* TD Size, packets remaining in this TD, bits 21:17 (5 bits, so max 31) */ +#define TRB_TD_SIZE(p) (min((p), (u32)31) << 17) +#define GET_TD_SIZE(p) (((p) & 0x3e0000) >> 17) +/* xhci 1.1 uses the TD_SIZE field for TBC if Extended TBC is enabled (ETE) */ +#define TRB_TD_SIZE_TBC(p) (min((p), (u32)31) << 17) +/* 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) + +/* Cycle bit - indicates TRB ownership by HC or HCD */ +#define TRB_CYCLE BIT(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 BIT(1) +/* Interrupt on short packet */ +#define TRB_ISP BIT(2) +/* Set PCIe no snoop attribute */ +#define TRB_NO_SNOOP BIT(3) +/* Chain multiple TRBs into a TD */ +#define TRB_CHAIN BIT(4) +/* Interrupt on completion */ +#define TRB_IOC BIT(5) +/* The buffer pointer contains immediate data */ +#define TRB_IDT BIT(6) +/* TDs smaller than this might use IDT */ +#define TRB_IDT_MAX_SIZE 8 + +/* Block Event Interrupt */ +#define TRB_BEI BIT(9) + +/* Control transfer TRB specific fields */ +#define TRB_DIR_IN BIT(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 BIT(31) +#define TRB_FRAME_ID(p) (((p) & 0x7ff) << 20) +#define GET_FRAME_ID(p) (((p) >> 20) & 0x7ff) +/* Total burst count field, Rsvdz on xhci 1.1 with Extended TBC enabled (ETE) */ +#define TRB_TBC(p) (((p) & 0x3) << 7) +#define GET_TBC(p) (((p) >> 7) & 0x3) +#define TRB_TLBPC(p) (((p) & 0xf) << 16) +#define GET_TLBPC(p) (((p) >> 16) & 0xf) + +/* TRB cache size for xHC with TRB cache */ +#define TRB_CACHE_SIZE_HS 8 +#define TRB_CACHE_SIZE_SS 16 + +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 */ +#define TRB_VENDOR_DEFINED_LOW 48 +/* Nec vendor-specific command completion event. */ +#define TRB_NEC_CMD_COMP 48 +/* Get NEC firmware revision. */ +#define TRB_NEC_GET_FW 49 + +static inline const char *xhci_trb_type_string(u8 type) +{ + switch (type) { + case TRB_NORMAL: + return "Normal"; + case TRB_SETUP: + return "Setup Stage"; + case TRB_DATA: + return "Data Stage"; + case TRB_STATUS: + return "Status Stage"; + case TRB_ISOC: + return "Isoch"; + case TRB_LINK: + return "Link"; + case TRB_EVENT_DATA: + return "Event Data"; + case TRB_TR_NOOP: + return "No-Op"; + case TRB_ENABLE_SLOT: + return "Enable Slot Command"; + case TRB_DISABLE_SLOT: + return "Disable Slot Command"; + case TRB_ADDR_DEV: + return "Address Device Command"; + case TRB_CONFIG_EP: + return "Configure Endpoint Command"; + case TRB_EVAL_CONTEXT: + return "Evaluate Context Command"; + case TRB_RESET_EP: + return "Reset Endpoint Command"; + case TRB_STOP_RING: + return "Stop Ring Command"; + case TRB_SET_DEQ: + return "Set TR Dequeue Pointer Command"; + case TRB_RESET_DEV: + return "Reset Device Command"; + case TRB_FORCE_EVENT: + return "Force Event Command"; + case TRB_NEG_BANDWIDTH: + return "Negotiate Bandwidth Command"; + case TRB_SET_LT: + return "Set Latency Tolerance Value Command"; + case TRB_GET_BW: + return "Get Port Bandwidth Command"; + case TRB_FORCE_HEADER: + return "Force Header Command"; + case TRB_CMD_NOOP: + return "No-Op Command"; + case TRB_TRANSFER: + return "Transfer Event"; + case TRB_COMPLETION: + return "Command Completion Event"; + case TRB_PORT_STATUS: + return "Port Status Change Event"; + case TRB_BANDWIDTH_EVENT: + return "Bandwidth Request Event"; + case TRB_DOORBELL: + return "Doorbell Event"; + case TRB_HC_EVENT: + return "Host Controller Event"; + case TRB_DEV_NOTE: + return "Device Notification Event"; + case TRB_MFINDEX_WRAP: + return "MFINDEX Wrap Event"; + case TRB_NEC_CMD_COMP: + return "NEC Command Completion Event"; + case TRB_NEC_GET_FW: + return "NET Get Firmware Revision Command"; + default: + return "UNKNOWN"; + } +} + +#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 256 +/* 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) +/* How much data is left before the 64KB boundary? */ +#define TRB_BUFF_LEN_UP_TO_BOUNDARY(addr) (TRB_MAX_BUFF_SIZE - \ + (addr & (TRB_MAX_BUFF_SIZE - 1))) +#define MAX_SOFT_RETRY 3 +/* + * Limits of consecutive isoc trbs that can Block Event Interrupt (BEI) if + * XHCI_AVOID_BEI quirk is in use. + */ +#define AVOID_BEI_INTERVAL_MIN 8 +#define AVOID_BEI_INTERVAL_MAX 32 + +#define xhci_for_each_ring_seg(head, seg) \ + for (seg = head; seg != NULL; seg = (seg->next != head ? seg->next : NULL)) + +struct xhci_segment { + union xhci_trb *trbs; + /* private to HCD */ + struct xhci_segment *next; + unsigned int num; + dma_addr_t dma; + /* Max packet sized bounce buffer for td-fragmant alignment */ + dma_addr_t bounce_dma; + void *bounce_buf; + unsigned int bounce_offs; + unsigned int bounce_len; +}; + +enum xhci_cancelled_td_status { + TD_DIRTY = 0, + TD_HALTED, + TD_CLEARING_CACHE, + TD_CLEARING_CACHE_DEFERRED, + TD_CLEARED, +}; + +struct xhci_td { + struct list_head td_list; + struct list_head cancelled_td_list; + int status; + enum xhci_cancelled_td_status cancel_status; + struct urb *urb; + struct xhci_segment *start_seg; + union xhci_trb *start_trb; + struct xhci_segment *end_seg; + union xhci_trb *end_trb; + struct xhci_segment *bounce_seg; + /* actual_length of the URB has already been set */ + bool urb_length_set; + bool error_mid_td; +}; + +/* + * xHCI command default timeout value in milliseconds. + * USB 3.2 spec, section 9.2.6.1 + */ +#define XHCI_CMD_DEFAULT_TIMEOUT 5000 + +/* command descriptor */ +struct xhci_cd { + struct xhci_command *command; + union xhci_trb *cmd_trb; +}; + +enum xhci_ring_type { + TYPE_CTRL = 0, + TYPE_ISOC, + TYPE_BULK, + TYPE_INTR, + TYPE_STREAM, + TYPE_COMMAND, + TYPE_EVENT, +}; + +static inline const char *xhci_ring_type_string(enum xhci_ring_type type) +{ + switch (type) { + case TYPE_CTRL: + return "CTRL"; + case TYPE_ISOC: + return "ISOC"; + case TYPE_BULK: + return "BULK"; + case TYPE_INTR: + return "INTR"; + case TYPE_STREAM: + return "STREAM"; + case TYPE_COMMAND: + return "CMD"; + case TYPE_EVENT: + return "EVENT"; + } + + return "UNKNOWN"; +} + +struct xhci_ring { + struct xhci_segment *first_seg; + struct xhci_segment *last_seg; + union xhci_trb *enqueue; + struct xhci_segment *enq_seg; + union xhci_trb *dequeue; + struct xhci_segment *deq_seg; + 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 bounce_buf_len; + enum xhci_ring_type type; + u32 old_trb_comp_code; + struct radix_tree_root *trb_address_map; +}; + +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; +}; + +struct xhci_scratchpad { + u64 *sp_array; + dma_addr_t sp_dma; + void **sp_buffers; +}; + +struct urb_priv { + int num_tds; + int num_tds_done; + struct xhci_td td[] __counted_by(num_tds); +}; + +/* Number of Event Ring segments to allocate, when amount is not specified. (spec allows 32k) */ +#define ERST_DEFAULT_SEGS 2 +/* 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; +}; + +/* 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; + /* which ports have started to resume */ + unsigned long resuming_ports; +}; + +struct xhci_interrupter { + struct xhci_ring *event_ring; + struct xhci_erst erst; + struct xhci_intr_reg __iomem *ir_set; + unsigned int intr_num; + bool ip_autoclear; + u32 isoc_bei_interval; + /* For interrupter registers save and restore over suspend/resume */ + u32 s3_iman; + u32 s3_imod; + u32 s3_erst_size; + u64 s3_erst_base; + u64 s3_erst_dequeue; +}; +/* + * It can take up to 20 ms to transition from RExit to U0 on the + * Intel Lynx Point LP xHCI host. + */ +#define XHCI_MAX_REXIT_TIMEOUT_MS 20 +struct xhci_port_cap { + u32 *psi; /* array of protocol speed ID entries */ + u8 psi_count; + u8 psi_uid_count; + u8 maj_rev; + u8 min_rev; + u32 protocol_caps; +}; + +struct xhci_port { + struct xhci_port_regs __iomem *port_reg; + int hw_portnum; + int hcd_portnum; + struct xhci_hub *rhub; + struct xhci_port_cap *port_cap; + unsigned int link_inactive:1; + unsigned int connected:1; + unsigned int lpm_incapable:1; + unsigned long resume_timestamp; + bool rexit_active; + /* Slot ID is the index of the device directly connected to the port */ + int slot_id; + struct completion rexit_done; + struct completion u3exit_done; +}; + +struct xhci_hub { + struct xhci_port **ports; + unsigned int num_ports; + struct usb_hcd *hcd; + /* keep track of bus suspend info */ + struct xhci_bus_state bus_state; + /* supported prococol extended capabiliy values */ + u8 maj_rev; + u8 min_rev; +}; + +/* 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; + + /* Cached register copies of read-only HC data */ + __u32 hcs_params2; + __u32 hcs_params3; + __u32 hcc_params; + __u32 hcc_params2; + + spinlock_t lock; + + /* packed release number */ + u16 hci_version; + u16 max_interrupters; + u8 max_slots; + u8 max_ports; + /* imod_interval in ns (I * 250ns) */ + u32 imod_interval; + u32 page_size; + unsigned int dma_mask_bits; + /* MSI-X/MSI vectors */ + int nvecs; + /* optional clocks */ + struct clk *clk; + struct clk *reg_clk; + /* optional reset controller */ + struct reset_control *reset; + /* data structures */ + struct xhci_device_context_array dcbaa; + struct xhci_interrupter **interrupters; + struct xhci_ring *cmd_ring; + unsigned int cmd_ring_state; +#define CMD_RING_STATE_RUNNING BIT(0) +#define CMD_RING_STATE_ABORTED BIT(1) +#define CMD_RING_STATE_STOPPED BIT(2) + struct list_head cmd_list; + unsigned int cmd_ring_reserved_trbs; + struct delayed_work cmd_timer; + struct completion cmd_ring_stop_completion; + struct xhci_command *current_cmd; + + /* Scratchpad */ + struct xhci_scratchpad *scratchpad; + + /* slot enabling and address device helpers */ + /* these are not thread safe so use mutex */ + struct mutex mutex; + /* Internal mirror of the HW's dcbaa */ + struct xhci_virt_device **devs; + /* 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 *port_bw_pool; + struct dma_pool *medium_streams_pool; + + /* Host controller watchdog timer structures */ + unsigned int xhc_state; + unsigned long run_graceperiod; + 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 BIT(0) +#define XHCI_STATE_HALTED BIT(1) +#define XHCI_STATE_REMOVING BIT(2) + unsigned long long quirks; +#define XHCI_LINK_TRB_QUIRK BIT_ULL(0) +#define XHCI_RESET_EP_QUIRK BIT_ULL(1) /* Deprecated */ +#define XHCI_NEC_HOST BIT_ULL(2) +#define XHCI_AMD_PLL_FIX BIT_ULL(3) +#define XHCI_SPURIOUS_SUCCESS BIT_ULL(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 BIT_ULL(5) +#define XHCI_BROKEN_MSI BIT_ULL(6) +#define XHCI_RESET_ON_RESUME BIT_ULL(7) +#define XHCI_SW_BW_CHECKING BIT_ULL(8) +#define XHCI_AMD_0x96_HOST BIT_ULL(9) +#define XHCI_TRUST_TX_LENGTH BIT_ULL(10) /* Deprecated */ +#define XHCI_LPM_SUPPORT BIT_ULL(11) +#define XHCI_INTEL_HOST BIT_ULL(12) +#define XHCI_SPURIOUS_REBOOT BIT_ULL(13) +#define XHCI_COMP_MODE_QUIRK BIT_ULL(14) +#define XHCI_AVOID_BEI BIT_ULL(15) +#define XHCI_PLAT BIT_ULL(16) /* Deprecated */ +#define XHCI_SLOW_SUSPEND BIT_ULL(17) +#define XHCI_SPURIOUS_WAKEUP BIT_ULL(18) +/* For controllers with a broken beyond repair streams implementation */ +#define XHCI_BROKEN_STREAMS BIT_ULL(19) +#define XHCI_PME_STUCK_QUIRK BIT_ULL(20) +#define XHCI_MTK_HOST BIT_ULL(21) +#define XHCI_SSIC_PORT_UNUSED BIT_ULL(22) +#define XHCI_NO_64BIT_SUPPORT BIT_ULL(23) +#define XHCI_MISSING_CAS BIT_ULL(24) +/* For controller with a broken Port Disable implementation */ +#define XHCI_BROKEN_PORT_PED BIT_ULL(25) +#define XHCI_LIMIT_ENDPOINT_INTERVAL_7 BIT_ULL(26) +#define XHCI_U2_DISABLE_WAKE BIT_ULL(27) +#define XHCI_ASMEDIA_MODIFY_FLOWCONTROL BIT_ULL(28) +#define XHCI_HW_LPM_DISABLE BIT_ULL(29) +#define XHCI_SUSPEND_DELAY BIT_ULL(30) +#define XHCI_INTEL_USB_ROLE_SW BIT_ULL(31) +#define XHCI_ZERO_64B_REGS BIT_ULL(32) +#define XHCI_DEFAULT_PM_RUNTIME_ALLOW BIT_ULL(33) +#define XHCI_RESET_PLL_ON_DISCONNECT BIT_ULL(34) +#define XHCI_SNPS_BROKEN_SUSPEND BIT_ULL(35) +/* Reserved. It was XHCI_RENESAS_FW_QUIRK */ +#define XHCI_SKIP_PHY_INIT BIT_ULL(37) +#define XHCI_DISABLE_SPARSE BIT_ULL(38) +#define XHCI_SG_TRB_CACHE_SIZE_QUIRK BIT_ULL(39) +#define XHCI_NO_SOFT_RETRY BIT_ULL(40) +#define XHCI_BROKEN_D3COLD_S2I BIT_ULL(41) +#define XHCI_EP_CTX_BROKEN_DCS BIT_ULL(42) +#define XHCI_SUSPEND_RESUME_CLKS BIT_ULL(43) +#define XHCI_RESET_TO_DEFAULT BIT_ULL(44) +#define XHCI_TRB_OVERFETCH BIT_ULL(45) +#define XHCI_ZHAOXIN_HOST BIT_ULL(46) +#define XHCI_WRITE_64_HI_LO BIT_ULL(47) +#define XHCI_CDNS_SCTX_QUIRK BIT_ULL(48) +#define XHCI_ETRON_HOST BIT_ULL(49) +#define XHCI_LIMIT_ENDPOINT_INTERVAL_9 BIT_ULL(50) + + unsigned int num_active_eps; + unsigned int limit_active_eps; + struct xhci_port *hw_ports; + struct xhci_hub usb2_rhub; + struct xhci_hub usb3_rhub; + /* support xHCI 1.0 spec USB2 hardware LPM */ + unsigned hw_lpm_support:1; + /* Broken Suspend flag for SNPS Suspend resume issue */ + unsigned broken_suspend:1; + /* Indicates that omitting hcd is supported if root hub has no ports */ + unsigned allow_single_roothub:1; + /* cached extended protocol port capabilities */ + struct xhci_port_cap *port_caps; + unsigned int num_port_caps; + /* Compliance Mode Recovery Data */ + struct timer_list comp_mode_recovery_timer; + u32 port_status_u0; + u16 test_mode; +/* Compliance Mode Timer Triggered every 2 seconds */ +#define COMP_MODE_RCVRY_MSECS 2000 + + struct dentry *debugfs_root; + struct dentry *debugfs_slots; + struct list_head regset_list; + + void *dbc; + /* platform-specific data -- must come last */ + unsigned long priv[] __aligned(sizeof(s64)); +}; + +/* Platform specific overrides to generic XHCI hc_driver ops */ +struct xhci_driver_overrides { + size_t extra_priv_size; + int (*reset)(struct usb_hcd *hcd); + int (*start)(struct usb_hcd *hcd); + int (*add_endpoint)(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep); + int (*drop_endpoint)(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep); + int (*check_bandwidth)(struct usb_hcd *, struct usb_device *); + void (*reset_bandwidth)(struct usb_hcd *, struct usb_device *); + int (*update_hub_device)(struct usb_hcd *hcd, struct usb_device *hdev, + struct usb_tt *tt, gfp_t mem_flags); + int (*hub_control)(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength); +}; + +#define XHCI_CFC_DELAY 10 + +/* convert between an HCD pointer and the corresponding EHCI_HCD */ +static inline struct xhci_hcd *hcd_to_xhci(struct usb_hcd *hcd) +{ + struct usb_hcd *primary_hcd; + + if (usb_hcd_is_primary_hcd(hcd)) + primary_hcd = hcd; + else + primary_hcd = hcd->primary_hcd; + + return (struct xhci_hcd *) (primary_hcd->hcd_priv); +} + +static inline struct usb_hcd *xhci_to_hcd(struct xhci_hcd *xhci) +{ + return xhci->main_hcd; +} + +static inline struct usb_hcd *xhci_get_usb3_hcd(struct xhci_hcd *xhci) +{ + if (xhci->shared_hcd) + return xhci->shared_hcd; + + if (!xhci->usb2_rhub.num_ports) + return xhci->main_hcd; + + return NULL; +} + +static inline bool xhci_hcd_is_usb3(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + return hcd == xhci_get_usb3_hcd(xhci); +} + +static inline bool xhci_has_one_roothub(struct xhci_hcd *xhci) +{ + return xhci->allow_single_roothub && + (!xhci->usb2_rhub.num_ports || !xhci->usb3_rhub.num_ports); +} + +#define xhci_dbg(xhci, fmt, args...) \ + dev_dbg(xhci_to_hcd(xhci)->self.controller , fmt , ## args) +#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_info(xhci, fmt, args...) \ + dev_info(xhci_to_hcd(xhci)->self.controller , fmt , ## args) + +/* + * 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) +{ + return lo_hi_readq(regs); +} +static inline void xhci_write_64(struct xhci_hcd *xhci, + const u64 val, __le64 __iomem *regs) +{ + lo_hi_writeq(val, regs); +} + + +/* + * Reportedly, some chapters of v0.95 spec said that Link TRB always has its chain bit set. + * Other chapters and later specs say that it should only be set if the link is inside a TD + * which continues from the end of one segment to the next segment. + * + * Some 0.95 hardware was found to misbehave if any link TRB doesn't have the chain bit set. + * + * 0.96 hardware from AMD and NEC was found to ignore unchained isochronous link TRBs when + * "resynchronizing the pipe" after a Missed Service Error. + */ +static inline bool xhci_link_chain_quirk(struct xhci_hcd *xhci, enum xhci_ring_type type) +{ + return (xhci->quirks & XHCI_LINK_TRB_QUIRK) || + (type == TYPE_ISOC && (xhci->quirks & (XHCI_AMD_0x96_HOST | XHCI_NEC_HOST))); +} + +/* xHCI debugging */ +char *xhci_get_slot_state(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx); +void xhci_dbg_trace(struct xhci_hcd *xhci, void (*trace)(struct va_format *), + const char *fmt, ...); + +/* 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, struct xhci_virt_device *dev, int slot_id); +void xhci_free_virt_devices_depth_first(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_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_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_rh_bw_cleanup(struct xhci_hcd *xhci); +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); +struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, unsigned int num_segs, + enum xhci_ring_type type, unsigned int max_packet, gfp_t 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_initialize_ring_info(struct xhci_ring *ring); +void xhci_ring_init(struct xhci_hcd *xhci, struct xhci_ring *ring); +void xhci_free_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, + unsigned int max_packet, 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_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_command *xhci_alloc_command(struct xhci_hcd *xhci, + bool allocate_completion, gfp_t mem_flags); +struct xhci_command *xhci_alloc_command_with_ctx(struct xhci_hcd *xhci, + bool allocate_completion, gfp_t mem_flags); +void xhci_urb_free_priv(struct urb_priv *urb_priv); +void xhci_free_command(struct xhci_hcd *xhci, + struct xhci_command *command); +struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci, + int type, gfp_t flags); +void xhci_free_container_ctx(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx); +struct xhci_container_ctx *xhci_alloc_port_bw_ctx(struct xhci_hcd *xhci, + gfp_t flags); +void xhci_free_port_bw_ctx(struct xhci_hcd *xhci, + struct xhci_container_ctx *ctx); +struct xhci_interrupter * +xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, + u32 imod_interval, unsigned int intr_num); +void xhci_remove_secondary_interrupter(struct usb_hcd + *hcd, struct xhci_interrupter *ir); +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, + struct xhci_ring *ring, + struct xhci_interrupter *ir); + +/* xHCI host controller glue */ +typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *); +int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, u64 timeout_us); +void xhci_quiesce(struct xhci_hcd *xhci); +int xhci_halt(struct xhci_hcd *xhci); +int xhci_start(struct xhci_hcd *xhci); +int xhci_reset(struct xhci_hcd *xhci, u64 timeout_us); +int xhci_run(struct usb_hcd *hcd); +int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks); +void xhci_shutdown(struct usb_hcd *hcd); +void xhci_stop(struct usb_hcd *hcd); +void xhci_init_driver(struct hc_driver *drv, + const struct xhci_driver_overrides *over); +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); +int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); +void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev); +int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, + struct usb_tt *tt, gfp_t mem_flags); +int xhci_disable_slot(struct xhci_hcd *xhci, u32 slot_id); +int xhci_disable_and_free_slot(struct xhci_hcd *xhci, u32 slot_id); +int xhci_ext_cap_init(struct xhci_hcd *xhci); + +int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup); +int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume); + +irqreturn_t xhci_irq(struct usb_hcd *hcd); +irqreturn_t xhci_msi_irq(int irq, void *hcd); +int xhci_alloc_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_set_interrupter_moderation(struct xhci_interrupter *ir, + u32 imod_interval); +int xhci_enable_interrupter(struct xhci_interrupter *ir); +int xhci_disable_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir); + +/* xHCI ring, segment, TRB, and TD functions */ +dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb); +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, struct xhci_command *cmd, + u32 trb_type, u32 slot_id); +int xhci_queue_address_device(struct xhci_hcd *xhci, struct xhci_command *cmd, + dma_addr_t in_ctx_ptr, u32 slot_id, enum xhci_setup_dev); +int xhci_queue_vendor_command(struct xhci_hcd *xhci, struct xhci_command *cmd, + u32 field1, u32 field2, u32 field3, u32 field4); +int xhci_queue_stop_endpoint(struct xhci_hcd *xhci, struct xhci_command *cmd, + 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, + struct xhci_command *cmd, dma_addr_t in_ctx_ptr, u32 slot_id, + bool command_must_succeed); +int xhci_queue_get_port_bw(struct xhci_hcd *xhci, + struct xhci_command *cmd, dma_addr_t in_ctx_ptr, + u8 dev_speed, bool command_must_succeed); +int xhci_get_port_bandwidth(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx, + u8 dev_speed); +int xhci_queue_evaluate_context(struct xhci_hcd *xhci, struct xhci_command *cmd, + dma_addr_t in_ctx_ptr, u32 slot_id, bool command_must_succeed); +int xhci_queue_reset_ep(struct xhci_hcd *xhci, struct xhci_command *cmd, + int slot_id, unsigned int ep_index, + enum xhci_ep_reset_type reset_type); +int xhci_queue_reset_device(struct xhci_hcd *xhci, struct xhci_command *cmd, + u32 slot_id); +void xhci_handle_command_timeout(struct work_struct *work); + +void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id, + unsigned int ep_index, unsigned int stream_id); +void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, + unsigned int slot_id, + unsigned int ep_index); +void xhci_cleanup_command_queue(struct xhci_hcd *xhci); +void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring); +unsigned int count_trbs(u64 addr, u64 len); +unsigned int xhci_num_trbs_free(struct xhci_ring *ring); +int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, + int suspend, gfp_t gfp_flags); +void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep); +void xhci_update_erst_dequeue(struct xhci_hcd *xhci, + struct xhci_interrupter *ir, + bool clear_ehb); +void xhci_add_interrupter(struct xhci_hcd *xhci, unsigned int intr_num); +int xhci_usb_endpoint_maxp(struct usb_device *udev, + struct usb_host_endpoint *host_ep); +void xhci_portsc_writel(struct xhci_port *port, u32 val); +u32 xhci_portsc_readl(struct xhci_port *port); + +/* xHCI roothub code */ +void xhci_set_link_state(struct xhci_hcd *xhci, struct xhci_port *port, + u32 link_state); +void xhci_test_and_clear_bit(struct xhci_hcd *xhci, struct xhci_port *port, + 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); +struct xhci_hub *xhci_get_rhub(struct usb_hcd *hcd); +enum usb_link_tunnel_mode xhci_port_is_tunneled(struct xhci_hcd *xhci, + struct xhci_port *port); +void xhci_hc_died(struct xhci_hcd *xhci); + +#ifdef CONFIG_PM +int xhci_bus_suspend(struct usb_hcd *hcd); +int xhci_bus_resume(struct usb_hcd *hcd); +unsigned long xhci_get_resuming_ports(struct usb_hcd *hcd); +#else +#define xhci_bus_suspend NULL +#define xhci_bus_resume NULL +#define xhci_get_resuming_ports NULL +#endif /* CONFIG_PM */ + +u32 xhci_port_state_to_neutral(u32 state); +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_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); + +struct xhci_ring *xhci_triad_to_transfer_ring(struct xhci_hcd *xhci, + unsigned int slot_id, unsigned int ep_index, + unsigned int stream_id); + +static inline 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); +} + +/* + * TODO: As per spec Isochronous IDT transmissions are supported. We bypass + * them anyways as we where unable to find a device that matches the + * constraints. + */ +static inline bool xhci_urb_suitable_for_idt(struct urb *urb) +{ + if (!usb_endpoint_xfer_isoc(&urb->ep->desc) && usb_urb_dir_out(urb) && + usb_endpoint_maxp(&urb->ep->desc) >= TRB_IDT_MAX_SIZE && + urb->transfer_buffer_length <= TRB_IDT_MAX_SIZE && + !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) && + !urb->num_sgs) + return true; + + return false; +} + +static inline char *xhci_slot_state_string(u32 state) +{ + switch (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 inline const char *xhci_decode_trb(char *str, size_t size, + u32 field0, u32 field1, u32 field2, u32 field3) +{ + int type = TRB_FIELD_TO_TYPE(field3); + + switch (type) { + case TRB_LINK: + snprintf(str, size, + "LINK %08x%08x intr %d type '%s' flags %c:%c:%c:%c", + field1, field0, GET_INTR_TARGET(field2), + xhci_trb_type_string(type), + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_TC ? 'T' : 't', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_TRANSFER: + case TRB_COMPLETION: + case TRB_PORT_STATUS: + case TRB_BANDWIDTH_EVENT: + case TRB_DOORBELL: + case TRB_HC_EVENT: + case TRB_DEV_NOTE: + case TRB_MFINDEX_WRAP: + snprintf(str, size, + "TRB %08x%08x status '%s' len %d slot %d ep %d type '%s' flags %c:%c", + field1, field0, + xhci_trb_comp_code_string(GET_COMP_CODE(field2)), + EVENT_TRB_LEN(field2), TRB_TO_SLOT_ID(field3), + TRB_TO_EP_ID(field3), + xhci_trb_type_string(type), + field3 & EVENT_DATA ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); + + break; + case TRB_SETUP: + snprintf(str, size, + "bRequestType %02x bRequest %02x wValue %02x%02x wIndex %02x%02x wLength %d length %d TD size %d intr %d type '%s' flags %c:%c:%c", + field0 & 0xff, + (field0 & 0xff00) >> 8, + (field0 & 0xff000000) >> 24, + (field0 & 0xff0000) >> 16, + (field1 & 0xff00) >> 8, + field1 & 0xff, + (field1 & 0xff000000) >> 16 | + (field1 & 0xff0000) >> 16, + TRB_LEN(field2), GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + xhci_trb_type_string(type), + field3 & TRB_IDT ? 'I' : 'i', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_DATA: + snprintf(str, size, + "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c:%c:%c:%c", + field1, field0, TRB_LEN(field2), GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + xhci_trb_type_string(type), + field3 & TRB_IDT ? 'I' : 'i', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_NO_SNOOP ? 'S' : 's', + field3 & TRB_ISP ? 'I' : 'i', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_STATUS: + snprintf(str, size, + "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c", + field1, field0, TRB_LEN(field2), GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + xhci_trb_type_string(type), + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_NORMAL: + case TRB_EVENT_DATA: + case TRB_TR_NOOP: + snprintf(str, size, + "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c:%c:%c:%c:%c", + field1, field0, TRB_LEN(field2), GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + xhci_trb_type_string(type), + field3 & TRB_BEI ? 'B' : 'b', + field3 & TRB_IDT ? 'I' : 'i', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_NO_SNOOP ? 'S' : 's', + field3 & TRB_ISP ? 'I' : 'i', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_ISOC: + snprintf(str, size, + "Buffer %08x%08x length %d TD size/TBC %d intr %d type '%s' TBC %u TLBPC %u frame_id %u flags %c:%c:%c:%c:%c:%c:%c:%c:%c", + field1, field0, TRB_LEN(field2), GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + xhci_trb_type_string(type), + GET_TBC(field3), + GET_TLBPC(field3), + GET_FRAME_ID(field3), + field3 & TRB_SIA ? 'S' : 's', + field3 & TRB_BEI ? 'B' : 'b', + field3 & TRB_IDT ? 'I' : 'i', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_NO_SNOOP ? 'S' : 's', + field3 & TRB_ISP ? 'I' : 'i', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_CMD_NOOP: + case TRB_ENABLE_SLOT: + snprintf(str, size, + "%s: flags %c", + xhci_trb_type_string(type), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_DISABLE_SLOT: + case TRB_NEG_BANDWIDTH: + snprintf(str, size, + "%s: slot %d flags %c", + xhci_trb_type_string(type), + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_ADDR_DEV: + snprintf(str, size, + "%s: ctx %08x%08x slot %d flags %c:%c", + xhci_trb_type_string(type), + field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_BSR ? 'B' : 'b', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_CONFIG_EP: + snprintf(str, size, + "%s: ctx %08x%08x slot %d flags %c:%c", + xhci_trb_type_string(type), + field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_DC ? 'D' : 'd', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_EVAL_CONTEXT: + snprintf(str, size, + "%s: ctx %08x%08x slot %d flags %c", + xhci_trb_type_string(type), + field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_RESET_EP: + snprintf(str, size, + "%s: ctx %08x%08x slot %d ep %d flags %c:%c", + xhci_trb_type_string(type), + field1, field0, + TRB_TO_SLOT_ID(field3), + TRB_TO_EP_ID(field3), + field3 & TRB_TSP ? 'T' : 't', + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_STOP_RING: + snprintf(str, size, + "%s: slot %d sp %d ep %d flags %c", + xhci_trb_type_string(type), + TRB_TO_SLOT_ID(field3), + TRB_TO_SUSPEND_PORT(field3), + TRB_TO_EP_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_SET_DEQ: + snprintf(str, size, + "%s: deq %08x%08x stream %d slot %d ep %d flags %c", + xhci_trb_type_string(type), + field1, field0, + TRB_TO_STREAM_ID(field2), + TRB_TO_SLOT_ID(field3), + TRB_TO_EP_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_RESET_DEV: + snprintf(str, size, + "%s: slot %d flags %c", + xhci_trb_type_string(type), + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_FORCE_EVENT: + snprintf(str, size, + "%s: event %08x%08x vf intr %d vf id %d flags %c", + xhci_trb_type_string(type), + field1, field0, + TRB_TO_VF_INTR_TARGET(field2), + TRB_TO_VF_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_SET_LT: + snprintf(str, size, + "%s: belt %d flags %c", + xhci_trb_type_string(type), + TRB_TO_BELT(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_GET_BW: + snprintf(str, size, + "%s: ctx %08x%08x slot %d speed %d flags %c", + xhci_trb_type_string(type), + field1, field0, + TRB_TO_SLOT_ID(field3), + TRB_TO_DEV_SPEED(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + case TRB_FORCE_HEADER: + snprintf(str, size, + "%s: info %08x%08x%08x pkt type %d roothub port %d flags %c", + xhci_trb_type_string(type), + field2, field1, field0 & 0xffffffe0, + TRB_TO_PACKET_TYPE(field0), + TRB_TO_ROOTHUB_PORT(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); + break; + default: + snprintf(str, size, + "type '%s' -> raw %08x %08x %08x %08x", + xhci_trb_type_string(type), + field0, field1, field2, field3); + } + + return str; +} + +static inline const char *xhci_decode_ctrl_ctx(char *str, + unsigned long drop, unsigned long add) +{ + unsigned int bit; + int ret = 0; + + str[0] = '\0'; + + if (drop) { + ret = sprintf(str, "Drop:"); + for_each_set_bit(bit, &drop, 32) + ret += sprintf(str + ret, " %d%s", + bit / 2, + bit % 2 ? "in":"out"); + ret += sprintf(str + ret, ", "); + } + + if (add) { + ret += sprintf(str + ret, "Add:%s%s", + (add & SLOT_FLAG) ? " slot":"", + (add & EP0_FLAG) ? " ep0":""); + add &= ~(SLOT_FLAG | EP0_FLAG); + for_each_set_bit(bit, &add, 32) + ret += sprintf(str + ret, " %d%s", + bit / 2, + bit % 2 ? "in":"out"); + } + return str; +} + +static inline const char *xhci_decode_slot_context(char *str, + u32 info, u32 info2, u32 tt_info, u32 state) +{ + u32 speed; + u32 hub; + u32 mtt; + int ret = 0; + + speed = info & DEV_SPEED; + hub = info & DEV_HUB; + mtt = info & DEV_MTT; + + ret = sprintf(str, "RS %05x %s%s%s Ctx Entries %d MEL %d us Port# %d/%d", + info & ROUTE_STRING_MASK, + ({ char *s; + switch (speed) { + case SLOT_SPEED_FS: + s = "full-speed"; + break; + case SLOT_SPEED_LS: + s = "low-speed"; + break; + case SLOT_SPEED_HS: + s = "high-speed"; + break; + case SLOT_SPEED_SS: + s = "super-speed"; + break; + case SLOT_SPEED_SSP: + s = "super-speed plus"; + break; + default: + s = "UNKNOWN speed"; + } s; }), + mtt ? " multi-TT" : "", + hub ? " Hub" : "", + (info & LAST_CTX_MASK) >> 27, + info2 & MAX_EXIT, + DEVINFO_TO_ROOT_HUB_PORT(info2), + DEVINFO_TO_MAX_PORTS(info2)); + + ret += sprintf(str + ret, " [TT Slot %d Port# %d TTT %d Intr %d] Addr %d State %s", + tt_info & TT_SLOT, (tt_info & TT_PORT) >> 8, + GET_TT_THINK_TIME(tt_info), GET_INTR_TARGET(tt_info), + state & DEV_ADDR_MASK, + xhci_slot_state_string(GET_SLOT_STATE(state))); + + return str; +} + + +static inline const char *xhci_portsc_link_state_string(u32 portsc) +{ + switch (portsc & PORT_PLS_MASK) { + case XDEV_U0: + return "U0"; + case XDEV_U1: + return "U1"; + case XDEV_U2: + return "U2"; + case XDEV_U3: + return "U3"; + case XDEV_DISABLED: + return "Disabled"; + case XDEV_RXDETECT: + return "RxDetect"; + case XDEV_INACTIVE: + return "Inactive"; + case XDEV_POLLING: + return "Polling"; + case XDEV_RECOVERY: + return "Recovery"; + case XDEV_HOT_RESET: + return "Hot Reset"; + case XDEV_COMP_MODE: + return "Compliance mode"; + case XDEV_TEST_MODE: + return "Test mode"; + case XDEV_RESUME: + return "Resume"; + default: + break; + } + return "Unknown"; +} + +static inline const char *xhci_decode_portsc(char *str, u32 portsc) +{ + int ret; + + ret = sprintf(str, "0x%08x ", portsc); + + if (portsc == ~(u32)0) + return str; + + ret += sprintf(str + ret, "Speed=%d ", DEV_PORT_SPEED(portsc)); + ret += sprintf(str + ret, "Link=%s ", xhci_portsc_link_state_string(portsc)); + + /* RO/ROS: Read-only */ + if (portsc & PORT_CONNECT) + ret += sprintf(str + ret, "CCS "); + if (portsc & PORT_OC) + ret += sprintf(str + ret, "OCA "); /* No set for USB2 ports */ + if (portsc & PORT_CAS) + ret += sprintf(str + ret, "CAS "); + if (portsc & PORT_DEV_REMOVE) + ret += sprintf(str + ret, "DR "); + + /* RWS; writing 1 sets the bit, writing 0 clears the bit. */ + if (portsc & PORT_POWER) + ret += sprintf(str + ret, "PP "); + if (portsc & PORT_WKCONN_E) + ret += sprintf(str + ret, "WCE "); + if (portsc & PORT_WKDISC_E) + ret += sprintf(str + ret, "WDE "); + if (portsc & PORT_WKOC_E) + ret += sprintf(str + ret, "WOE "); + + /* RW; writing 1 sets the bit, writing 0 clears the bit */ + if (portsc & PORT_LINK_STROBE) + ret += sprintf(str + ret, "LWS "); /* LWS 0 write is ignored */ + + /* RW1S; writing 1 sets the bit, writing 0 has no effect */ + if (portsc & PORT_RESET) + ret += sprintf(str + ret, "PR "); + if (portsc & PORT_WR) + ret += sprintf(str + ret, "WPR "); /* RsvdZ for USB2 ports */ + + /* RW1CS; writing 1 clears the bit, writing 0 has no effect. */ + if (portsc & PORT_PE) + ret += sprintf(str + ret, "PED "); + if (portsc & PORT_CSC) + ret += sprintf(str + ret, "CSC "); + if (portsc & PORT_PEC) + ret += sprintf(str + ret, "PEC "); /* No set for USB3 ports */ + if (portsc & PORT_WRC) + ret += sprintf(str + ret, "WRC "); /* RsvdZ for USB2 ports */ + if (portsc & PORT_OCC) + ret += sprintf(str + ret, "OCC "); + if (portsc & PORT_RC) + ret += sprintf(str + ret, "PRC "); + if (portsc & PORT_PLC) + ret += sprintf(str + ret, "PLC "); + if (portsc & PORT_CEC) + ret += sprintf(str + ret, "CEC "); /* RsvdZ for USB2 ports */ + + return str; +} + +static inline const char *xhci_decode_usbsts(char *str, u32 usbsts) +{ + int ret = 0; + + ret = sprintf(str, " 0x%08x", usbsts); + + if (usbsts == ~(u32)0) + return str; + + if (usbsts & STS_HALT) + ret += sprintf(str + ret, " HCHalted"); + if (usbsts & STS_FATAL) + ret += sprintf(str + ret, " HSE"); + if (usbsts & STS_EINT) + ret += sprintf(str + ret, " EINT"); + if (usbsts & STS_PORT) + ret += sprintf(str + ret, " PCD"); + if (usbsts & STS_SAVE) + ret += sprintf(str + ret, " SSS"); + if (usbsts & STS_RESTORE) + ret += sprintf(str + ret, " RSS"); + if (usbsts & STS_SRE) + ret += sprintf(str + ret, " SRE"); + if (usbsts & STS_CNR) + ret += sprintf(str + ret, " CNR"); + if (usbsts & STS_HCE) + ret += sprintf(str + ret, " HCE"); + + return str; +} + +static inline const char *xhci_decode_doorbell(char *str, u32 slot, u32 doorbell) +{ + u8 ep; + u16 stream; + int ret; + + ep = (doorbell & 0xff); + stream = doorbell >> 16; + + if (slot == 0) { + sprintf(str, "Command Ring %d", doorbell); + return str; + } + ret = sprintf(str, "Slot %d ", slot); + if (ep > 0 && ep < 32) + ret = sprintf(str + ret, "ep%d%s", + ep / 2, + ep % 2 ? "in" : "out"); + else if (ep == 0 || ep < 248) + ret = sprintf(str + ret, "Reserved %d", ep); + else + ret = sprintf(str + ret, "Vendor Defined %d", ep); + if (stream) + ret = sprintf(str + ret, " Stream %d", stream); + + return str; +} + +static inline const char *xhci_ep_state_string(u8 state) +{ + switch (state) { + case EP_STATE_DISABLED: + return "disabled"; + case EP_STATE_RUNNING: + return "running"; + case EP_STATE_HALTED: + return "halted"; + case EP_STATE_STOPPED: + return "stopped"; + case EP_STATE_ERROR: + return "error"; + default: + return "INVALID"; + } +} + +static inline const char *xhci_ep_type_string(u8 type) +{ + switch (type) { + case ISOC_OUT_EP: + return "Isoc OUT"; + case BULK_OUT_EP: + return "Bulk OUT"; + case INT_OUT_EP: + return "Int OUT"; + case CTRL_EP: + return "Ctrl"; + case ISOC_IN_EP: + return "Isoc IN"; + case BULK_IN_EP: + return "Bulk IN"; + case INT_IN_EP: + return "Int IN"; + default: + return "INVALID"; + } +} + +static inline const char *xhci_decode_ep_context(char *str, u32 info, + u32 info2, u64 deq, u32 tx_info) +{ + int ret; + + u32 esit; + u16 maxp; + u16 avg; + + u8 max_pstr; + u8 ep_state; + u8 interval; + u8 ep_type; + u8 burst; + u8 cerr; + u8 mult; + + bool lsa; + bool hid; + + esit = CTX_TO_MAX_ESIT_PAYLOAD_HI(info) << 16 | + CTX_TO_MAX_ESIT_PAYLOAD(tx_info); + + ep_state = info & EP_STATE_MASK; + max_pstr = CTX_TO_EP_MAXPSTREAMS(info); + interval = CTX_TO_EP_INTERVAL(info); + mult = CTX_TO_EP_MULT(info) + 1; + lsa = !!(info & EP_HAS_LSA); + + cerr = (info2 & (3 << 1)) >> 1; + ep_type = CTX_TO_EP_TYPE(info2); + hid = !!(info2 & (1 << 7)); + burst = CTX_TO_MAX_BURST(info2); + maxp = MAX_PACKET_DECODED(info2); + + avg = EP_AVG_TRB_LENGTH(tx_info); + + ret = sprintf(str, "State %s mult %d max P. Streams %d %s", + xhci_ep_state_string(ep_state), mult, + max_pstr, lsa ? "LSA " : ""); + + ret += sprintf(str + ret, "interval %d us max ESIT payload %d CErr %d ", + (1 << interval) * 125, esit, cerr); + + ret += sprintf(str + ret, "Type %s %sburst %d maxp %d deq %016llx ", + xhci_ep_type_string(ep_type), hid ? "HID" : "", + burst, maxp, deq); + + ret += sprintf(str + ret, "avg trb len %d", avg); + + return str; +} + +#endif /* __LINUX_XHCI_HCD_H */ diff --git a/drivers/usb/image/Kconfig b/drivers/usb/image/Kconfig new file mode 100644 index 000000000..a65c638e7 --- /dev/null +++ b/drivers/usb/image/Kconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# 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 000000000..8997c81ba --- /dev/null +++ b/drivers/usb/image/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# 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 000000000..ca287b770 --- /dev/null +++ b/drivers/usb/image/mdc800.c @@ -0,0 +1,1078 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * copyright (C) 1999/2000 by Henning Zabel + */ + + +/* + * 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 + * disconnect 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, + msecs_to_jiffies(msec)); + 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; + + 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 len == left ? -EINTR : len-left; + } + + 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; + } + retval = wait_event_timeout(mdc800->download_wait, + mdc800->downloaded, + msecs_to_jiffies(TO_DOWNLOAD_GET_READY)); + if (!retval) + usb_kill_urb(mdc800->download_urb); + 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, + msecs_to_jiffies(TO_WRITE_GET_READY)); + 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; + fallthrough; + + 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_obj(struct mdc800_data); + 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 000000000..1f0b3c44d --- /dev/null +++ b/drivers/usb/image/microtek.c @@ -0,0 +1,769 @@ +// SPDX-License-Identifier: GPL-2.0 +/* 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 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 +#include +#include +#include +#include + +#include "microtek.h" + +#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 "): " + +#if defined MTS_DO_DEBUG + +#define MTS_DEBUG(x...) \ + printk( KERN_DEBUG MTS_NAME x ) + +#define MTS_DEBUG_INT() \ + do { 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_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( " %10ph\n", srb->cmnd); +} + +#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_dump(desc); + + usb_kill_urb( desc->urb ); +} + +static int mts_sdev_init (struct scsi_device *s) +{ + s->inquiry_len = 0x24; + return 0; +} + +static int mts_scsi_abort(struct scsi_cmnd *srb) +{ + struct mts_desc* desc = (struct mts_desc*)(srb->device->host->hostdata[0]); + + 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_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 enum scsi_qc_status 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) ) { + dev_err(&context->instance->usb_dev->dev, + "could not submit URB! Error was %d\n",(int)res ); + set_host_byte(context->srb, DID_ERROR); + 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) ) { + set_host_byte(context->srb, (status == -ENOENT ? DID_ABORT : DID_ERROR)); + } + + 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 */ + set_host_byte(context->srb, DID_ABORT); + } else { + /* A genuine error has occurred */ + set_host_byte(context->srb, DID_ERROR); + } + 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) +{ + 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)) { + set_host_byte(context->srb, (status == -ENOENT ? DID_ABORT : DID_ERROR)); + mts_transfer_cleanup(transfer); + } + + context->curr_sg = sg_next(context->curr_sg); + mts_int_submit_urb(transfer, + context->data_pipe, + sg_virt(context->curr_sg), + context->curr_sg->length, + sg_is_last(context->curr_sg) ? + 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; + + desc->context.instance = desc; + desc->context.srb = srb; + + if (!scsi_bufflen(srb)) { + desc->context.data = NULL; + desc->context.data_length = 0; + return; + } else { + desc->context.curr_sg = scsi_sglist(srb); + desc->context.data = sg_virt(desc->context.curr_sg); + desc->context.data_length = desc->context.curr_sg->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 enum scsi_qc_status mts_scsi_queuecommand_lck(struct scsi_cmnd *srb) +{ + mts_scsi_cmnd_callback callback = scsi_done; + struct mts_desc* desc = (struct mts_desc*)(srb->device->host->hostdata[0]); + int res; + + 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"); + + set_host_byte(srb, DID_BAD_TARGET); + + 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)){ + dev_err(&desc->usb_dev->dev, "error %d submitting URB\n",(int)res); + set_host_byte(srb, DID_ERROR); + + if(likely(callback != NULL)) + callback(srb); + + } +out: + return 0; +} + +static DEF_SCSI_QCMD(mts_scsi_queuecommand) + +static const 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, + .emulated = 1, + .dma_alignment = 511, + .sdev_init = mts_sdev_init, + .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( "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) ); + + /* 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 ) { + dev_warn(&dev->dev, "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 (usb_endpoint_is_bulk_in(&altsetting->endpoint[i].desc)) { + *ep_in_current++ = usb_endpoint_num(&altsetting->endpoint[i].desc); + } else if (usb_endpoint_is_bulk_out(&altsetting->endpoint[i].desc)) { + if (ep_out == -1) { + ep_out = usb_endpoint_num(&altsetting->endpoint[i].desc); + } else { + dev_warn(&dev->dev, "can only deal with bulk endpoints; endpoint %d is not bulk.\n", + usb_endpoint_num(&altsetting->endpoint[i].desc)); + return -ENODEV; + } + } else { + dev_warn(&dev->dev, "can only deal with bulk endpoints; endpoint %d is not bulk.\n", + usb_endpoint_num(&altsetting->endpoint[i].desc)); + } + } + + if (ep_in_current != &ep_in_set[2]) { + dev_warn(&dev->dev, "couldn't find two input bulk endpoints. Bailing out.\n"); + return -ENODEV; + } + + if ( ep_out == -1 ) { + dev_warn(&dev->dev, "couldn't find an output bulk endpoint. Bailing out.\n" ); + return -ENODEV; + } + + + new_desc = kzalloc_obj(struct mts_desc); + 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 ) + dev_warn(&dev->dev, "will this work? Command EP is not usually %d\n", + (int)new_desc->ep_out ); + + if ( new_desc->ep_response != MTS_EP_RESPONSE ) + dev_warn(&dev->dev, "will this work? Response EP is not usually %d\n", + (int)new_desc->ep_response ); + + if ( new_desc->ep_image != MTS_EP_IMAGE ) + dev_warn(&dev->dev, "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 000000000..7bd5f4639 --- /dev/null +++ b/drivers/usb/image/microtek.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + /* + * 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; + struct scatterlist *curr_sg; + + 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/isp1760/Kconfig b/drivers/usb/isp1760/Kconfig new file mode 100644 index 000000000..e19178f3c --- /dev/null +++ b/drivers/usb/isp1760/Kconfig @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-2.0 + +config USB_ISP1760 + tristate "NXP ISP 1760/1761/1763 support" + depends on USB || USB_GADGET + select REGMAP_MMIO + help + Say Y or M here if your system as an ISP1760/1763 USB host controller + or an ISP1761 USB dual-role 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_ISP1760_HCD + bool + +config USB_ISP1761_UDC + bool + +if USB_ISP1760 + +choice + prompt "ISP1760 Mode Selection" + default USB_ISP1760_DUAL_ROLE if (USB && USB_GADGET) + default USB_ISP1760_HOST_ROLE if (USB && !USB_GADGET) + default USB_ISP1760_GADGET_ROLE if (!USB && USB_GADGET) + +config USB_ISP1760_HOST_ROLE + bool "Host only mode" + depends on USB=y || USB=USB_ISP1760 + select USB_ISP1760_HCD + help + Select this if you want to use the ISP1760 in host mode only. The + gadget function will be disabled. + +config USB_ISP1760_GADGET_ROLE + bool "Gadget only mode" + depends on USB_GADGET=y || USB_GADGET=USB_ISP1760 + select USB_ISP1761_UDC + help + Select this if you want to use the ISP1760 in peripheral mode only. + The host function will be disabled. + +config USB_ISP1760_DUAL_ROLE + bool "Dual Role mode" + depends on USB=y || USB=USB_ISP1760 + depends on USB_GADGET=y || USB_GADGET=USB_ISP1760 + select USB_ISP1760_HCD + select USB_ISP1761_UDC + help + Select this if you want to use the ISP1760 in both host and + peripheral modes. + +endchoice + +endif diff --git a/drivers/usb/isp1760/Makefile b/drivers/usb/isp1760/Makefile new file mode 100644 index 000000000..9bb09e829 --- /dev/null +++ b/drivers/usb/isp1760/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +isp1760-y := isp1760-core.o isp1760-if.o +isp1760-$(CONFIG_USB_ISP1760_HCD) += isp1760-hcd.o +isp1760-$(CONFIG_USB_ISP1761_UDC) += isp1760-udc.o + +obj-$(CONFIG_USB_ISP1760) += isp1760.o diff --git a/drivers/usb/isp1760/isp1760-core.c b/drivers/usb/isp1760/isp1760-core.c new file mode 100644 index 000000000..af88f4fe0 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-core.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2021 Linaro, Rui Miguel Silva + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * Rui Miguel Silva + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isp1760-core.h" +#include "isp1760-hcd.h" +#include "isp1760-regs.h" +#include "isp1760-udc.h" + +static int isp1760_init_core(struct isp1760_device *isp) +{ + struct isp1760_hcd *hcd = &isp->hcd; + struct isp1760_udc *udc = &isp->udc; + u32 otg_ctrl; + + /* Low-level chip reset */ + if (isp->rst_gpio) { + gpiod_set_value_cansleep(isp->rst_gpio, 1); + msleep(50); + gpiod_set_value_cansleep(isp->rst_gpio, 0); + } + + /* + * Reset the host controller, including the CPU interface + * configuration. + */ + isp1760_field_set(hcd->fields, SW_RESET_RESET_ALL); + msleep(100); + + /* Setup HW Mode Control: This assumes a level active-low interrupt */ + if ((isp->devflags & ISP1760_FLAG_ANALOG_OC) && hcd->is_isp1763) { + dev_err(isp->dev, "isp1763 analog overcurrent not available\n"); + return -EINVAL; + } + + if (isp->devflags & ISP1760_FLAG_BUS_WIDTH_16) + isp1760_field_clear(hcd->fields, HW_DATA_BUS_WIDTH); + if (isp->devflags & ISP1760_FLAG_BUS_WIDTH_8) + isp1760_field_set(hcd->fields, HW_DATA_BUS_WIDTH); + if (isp->devflags & ISP1760_FLAG_ANALOG_OC) + isp1760_field_set(hcd->fields, HW_ANA_DIGI_OC); + if (isp->devflags & ISP1760_FLAG_DACK_POL_HIGH) + isp1760_field_set(hcd->fields, HW_DACK_POL_HIGH); + if (isp->devflags & ISP1760_FLAG_DREQ_POL_HIGH) + isp1760_field_set(hcd->fields, HW_DREQ_POL_HIGH); + if (isp->devflags & ISP1760_FLAG_INTR_POL_HIGH) + isp1760_field_set(hcd->fields, HW_INTR_HIGH_ACT); + if (isp->devflags & ISP1760_FLAG_INTR_EDGE_TRIG) + isp1760_field_set(hcd->fields, HW_INTR_EDGE_TRIG); + + /* + * The ISP1761 has a dedicated DC IRQ line but supports sharing the HC + * IRQ line for both the host and device controllers. Hardcode IRQ + * sharing for now and disable the DC interrupts globally to avoid + * spurious interrupts during HCD registration. + */ + if (isp->devflags & ISP1760_FLAG_ISP1761) { + isp1760_reg_write(udc->regs, ISP176x_DC_MODE, 0); + isp1760_field_set(hcd->fields, HW_COMN_IRQ); + } + + /* + * PORT 1 Control register of the ISP1760 is the OTG control register + * on ISP1761. + * + * TODO: Really support OTG. For now we configure port 1 in device mode + */ + if (isp->devflags & ISP1760_FLAG_ISP1761) { + if (isp->devflags & ISP1760_FLAG_PERIPHERAL_EN) { + otg_ctrl = (ISP176x_HW_DM_PULLDOWN_CLEAR | + ISP176x_HW_DP_PULLDOWN_CLEAR | + ISP176x_HW_OTG_DISABLE); + } else { + otg_ctrl = (ISP176x_HW_SW_SEL_HC_DC_CLEAR | + ISP176x_HW_VBUS_DRV | + ISP176x_HW_SEL_CP_EXT); + } + isp1760_reg_write(hcd->regs, ISP176x_HC_OTG_CTRL, otg_ctrl); + } + + dev_info(isp->dev, "%s bus width: %u, oc: %s\n", + hcd->is_isp1763 ? "isp1763" : "isp1760", + isp->devflags & ISP1760_FLAG_BUS_WIDTH_8 ? 8 : + isp->devflags & ISP1760_FLAG_BUS_WIDTH_16 ? 16 : 32, + hcd->is_isp1763 ? "not available" : + isp->devflags & ISP1760_FLAG_ANALOG_OC ? "analog" : "digital"); + + return 0; +} + +void isp1760_set_pullup(struct isp1760_device *isp, bool enable) +{ + struct isp1760_udc *udc = &isp->udc; + + if (enable) + isp1760_field_set(udc->fields, HW_DP_PULLUP); + else + isp1760_field_set(udc->fields, HW_DP_PULLUP_CLEAR); +} + +/* + * ISP1760/61: + * + * 60kb divided in: + * - 32 blocks @ 256 bytes + * - 20 blocks @ 1024 bytes + * - 4 blocks @ 8192 bytes + */ +static const struct isp1760_memory_layout isp176x_memory_conf = { + .blocks[0] = 32, + .blocks_size[0] = 256, + .blocks[1] = 20, + .blocks_size[1] = 1024, + .blocks[2] = 4, + .blocks_size[2] = 8192, + + .slot_num = 32, + .payload_blocks = 32 + 20 + 4, + .payload_area_size = 0xf000, +}; + +/* + * ISP1763: + * + * 20kb divided in: + * - 8 blocks @ 256 bytes + * - 2 blocks @ 1024 bytes + * - 4 blocks @ 4096 bytes + */ +static const struct isp1760_memory_layout isp1763_memory_conf = { + .blocks[0] = 8, + .blocks_size[0] = 256, + .blocks[1] = 2, + .blocks_size[1] = 1024, + .blocks[2] = 4, + .blocks_size[2] = 4096, + + .slot_num = 16, + .payload_blocks = 8 + 2 + 4, + .payload_area_size = 0x5000, +}; + +static const struct regmap_range isp176x_hc_volatile_ranges[] = { + regmap_reg_range(ISP176x_HC_USBCMD, ISP176x_HC_ATL_PTD_LASTPTD), + regmap_reg_range(ISP176x_HC_BUFFER_STATUS, ISP176x_HC_MEMORY), + regmap_reg_range(ISP176x_HC_INTERRUPT, ISP176x_HC_OTG_CTRL_CLEAR), +}; + +static const struct regmap_access_table isp176x_hc_volatile_table = { + .yes_ranges = isp176x_hc_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(isp176x_hc_volatile_ranges), +}; + +static const struct regmap_config isp1760_hc_regmap_conf = { + .name = "isp1760-hc", + .reg_bits = 16, + .reg_stride = 4, + .val_bits = 32, + .fast_io = true, + .max_register = ISP176x_HC_OTG_CTRL_CLEAR, + .volatile_table = &isp176x_hc_volatile_table, +}; + +static const struct reg_field isp1760_hc_reg_fields[] = { + [HCS_PPC] = REG_FIELD(ISP176x_HC_HCSPARAMS, 4, 4), + [HCS_N_PORTS] = REG_FIELD(ISP176x_HC_HCSPARAMS, 0, 3), + [HCC_ISOC_CACHE] = REG_FIELD(ISP176x_HC_HCCPARAMS, 7, 7), + [HCC_ISOC_THRES] = REG_FIELD(ISP176x_HC_HCCPARAMS, 4, 6), + [CMD_LRESET] = REG_FIELD(ISP176x_HC_USBCMD, 7, 7), + [CMD_RESET] = REG_FIELD(ISP176x_HC_USBCMD, 1, 1), + [CMD_RUN] = REG_FIELD(ISP176x_HC_USBCMD, 0, 0), + [STS_PCD] = REG_FIELD(ISP176x_HC_USBSTS, 2, 2), + [HC_FRINDEX] = REG_FIELD(ISP176x_HC_FRINDEX, 0, 13), + [FLAG_CF] = REG_FIELD(ISP176x_HC_CONFIGFLAG, 0, 0), + [HC_ISO_PTD_DONEMAP] = REG_FIELD(ISP176x_HC_ISO_PTD_DONEMAP, 0, 31), + [HC_ISO_PTD_SKIPMAP] = REG_FIELD(ISP176x_HC_ISO_PTD_SKIPMAP, 0, 31), + [HC_ISO_PTD_LASTPTD] = REG_FIELD(ISP176x_HC_ISO_PTD_LASTPTD, 0, 31), + [HC_INT_PTD_DONEMAP] = REG_FIELD(ISP176x_HC_INT_PTD_DONEMAP, 0, 31), + [HC_INT_PTD_SKIPMAP] = REG_FIELD(ISP176x_HC_INT_PTD_SKIPMAP, 0, 31), + [HC_INT_PTD_LASTPTD] = REG_FIELD(ISP176x_HC_INT_PTD_LASTPTD, 0, 31), + [HC_ATL_PTD_DONEMAP] = REG_FIELD(ISP176x_HC_ATL_PTD_DONEMAP, 0, 31), + [HC_ATL_PTD_SKIPMAP] = REG_FIELD(ISP176x_HC_ATL_PTD_SKIPMAP, 0, 31), + [HC_ATL_PTD_LASTPTD] = REG_FIELD(ISP176x_HC_ATL_PTD_LASTPTD, 0, 31), + [PORT_OWNER] = REG_FIELD(ISP176x_HC_PORTSC1, 13, 13), + [PORT_POWER] = REG_FIELD(ISP176x_HC_PORTSC1, 12, 12), + [PORT_LSTATUS] = REG_FIELD(ISP176x_HC_PORTSC1, 10, 11), + [PORT_RESET] = REG_FIELD(ISP176x_HC_PORTSC1, 8, 8), + [PORT_SUSPEND] = REG_FIELD(ISP176x_HC_PORTSC1, 7, 7), + [PORT_RESUME] = REG_FIELD(ISP176x_HC_PORTSC1, 6, 6), + [PORT_PE] = REG_FIELD(ISP176x_HC_PORTSC1, 2, 2), + [PORT_CSC] = REG_FIELD(ISP176x_HC_PORTSC1, 1, 1), + [PORT_CONNECT] = REG_FIELD(ISP176x_HC_PORTSC1, 0, 0), + [ALL_ATX_RESET] = REG_FIELD(ISP176x_HC_HW_MODE_CTRL, 31, 31), + [HW_ANA_DIGI_OC] = REG_FIELD(ISP176x_HC_HW_MODE_CTRL, 15, 15), + [HW_COMN_IRQ] = REG_FIELD(ISP176x_HC_HW_MODE_CTRL, 10, 10), + [HW_DATA_BUS_WIDTH] = REG_FIELD(ISP176x_HC_HW_MODE_CTRL, 8, 8), + [HW_DACK_POL_HIGH] = REG_FIELD(ISP176x_HC_HW_MODE_CTRL, 6, 6), + [HW_DREQ_POL_HIGH] = REG_FIELD(ISP176x_HC_HW_MODE_CTRL, 5, 5), + [HW_INTR_HIGH_ACT] = REG_FIELD(ISP176x_HC_HW_MODE_CTRL, 2, 2), + [HW_INTR_EDGE_TRIG] = REG_FIELD(ISP176x_HC_HW_MODE_CTRL, 1, 1), + [HW_GLOBAL_INTR_EN] = REG_FIELD(ISP176x_HC_HW_MODE_CTRL, 0, 0), + [HC_CHIP_REV] = REG_FIELD(ISP176x_HC_CHIP_ID, 16, 31), + [HC_CHIP_ID_HIGH] = REG_FIELD(ISP176x_HC_CHIP_ID, 8, 15), + [HC_CHIP_ID_LOW] = REG_FIELD(ISP176x_HC_CHIP_ID, 0, 7), + [HC_SCRATCH] = REG_FIELD(ISP176x_HC_SCRATCH, 0, 31), + [SW_RESET_RESET_ALL] = REG_FIELD(ISP176x_HC_RESET, 0, 0), + [ISO_BUF_FILL] = REG_FIELD(ISP176x_HC_BUFFER_STATUS, 2, 2), + [INT_BUF_FILL] = REG_FIELD(ISP176x_HC_BUFFER_STATUS, 1, 1), + [ATL_BUF_FILL] = REG_FIELD(ISP176x_HC_BUFFER_STATUS, 0, 0), + [MEM_BANK_SEL] = REG_FIELD(ISP176x_HC_MEMORY, 16, 17), + [MEM_START_ADDR] = REG_FIELD(ISP176x_HC_MEMORY, 0, 15), + [HC_INTERRUPT] = REG_FIELD(ISP176x_HC_INTERRUPT, 0, 9), + [HC_ATL_IRQ_ENABLE] = REG_FIELD(ISP176x_HC_INTERRUPT_ENABLE, 8, 8), + [HC_INT_IRQ_ENABLE] = REG_FIELD(ISP176x_HC_INTERRUPT_ENABLE, 7, 7), + [HC_ISO_IRQ_MASK_OR] = REG_FIELD(ISP176x_HC_ISO_IRQ_MASK_OR, 0, 31), + [HC_INT_IRQ_MASK_OR] = REG_FIELD(ISP176x_HC_INT_IRQ_MASK_OR, 0, 31), + [HC_ATL_IRQ_MASK_OR] = REG_FIELD(ISP176x_HC_ATL_IRQ_MASK_OR, 0, 31), + [HC_ISO_IRQ_MASK_AND] = REG_FIELD(ISP176x_HC_ISO_IRQ_MASK_AND, 0, 31), + [HC_INT_IRQ_MASK_AND] = REG_FIELD(ISP176x_HC_INT_IRQ_MASK_AND, 0, 31), + [HC_ATL_IRQ_MASK_AND] = REG_FIELD(ISP176x_HC_ATL_IRQ_MASK_AND, 0, 31), + [HW_OTG_DISABLE_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 26, 26), + [HW_SW_SEL_HC_DC_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 23, 23), + [HW_VBUS_DRV_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 20, 20), + [HW_SEL_CP_EXT_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 19, 19), + [HW_DM_PULLDOWN_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 18, 18), + [HW_DP_PULLDOWN_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 17, 17), + [HW_DP_PULLUP_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 16, 16), + [HW_OTG_DISABLE] = REG_FIELD(ISP176x_HC_OTG_CTRL, 10, 10), + [HW_SW_SEL_HC_DC] = REG_FIELD(ISP176x_HC_OTG_CTRL, 7, 7), + [HW_VBUS_DRV] = REG_FIELD(ISP176x_HC_OTG_CTRL, 4, 4), + [HW_SEL_CP_EXT] = REG_FIELD(ISP176x_HC_OTG_CTRL, 3, 3), + [HW_DM_PULLDOWN] = REG_FIELD(ISP176x_HC_OTG_CTRL, 2, 2), + [HW_DP_PULLDOWN] = REG_FIELD(ISP176x_HC_OTG_CTRL, 1, 1), + [HW_DP_PULLUP] = REG_FIELD(ISP176x_HC_OTG_CTRL, 0, 0), + /* Make sure the array is sized properly during compilation */ + [HC_FIELD_MAX] = {}, +}; + +static const struct reg_field isp1763_hc_reg_fields[] = { + [CMD_LRESET] = REG_FIELD(ISP1763_HC_USBCMD, 7, 7), + [CMD_RESET] = REG_FIELD(ISP1763_HC_USBCMD, 1, 1), + [CMD_RUN] = REG_FIELD(ISP1763_HC_USBCMD, 0, 0), + [STS_PCD] = REG_FIELD(ISP1763_HC_USBSTS, 2, 2), + [HC_FRINDEX] = REG_FIELD(ISP1763_HC_FRINDEX, 0, 13), + [FLAG_CF] = REG_FIELD(ISP1763_HC_CONFIGFLAG, 0, 0), + [HC_ISO_PTD_DONEMAP] = REG_FIELD(ISP1763_HC_ISO_PTD_DONEMAP, 0, 15), + [HC_ISO_PTD_SKIPMAP] = REG_FIELD(ISP1763_HC_ISO_PTD_SKIPMAP, 0, 15), + [HC_ISO_PTD_LASTPTD] = REG_FIELD(ISP1763_HC_ISO_PTD_LASTPTD, 0, 15), + [HC_INT_PTD_DONEMAP] = REG_FIELD(ISP1763_HC_INT_PTD_DONEMAP, 0, 15), + [HC_INT_PTD_SKIPMAP] = REG_FIELD(ISP1763_HC_INT_PTD_SKIPMAP, 0, 15), + [HC_INT_PTD_LASTPTD] = REG_FIELD(ISP1763_HC_INT_PTD_LASTPTD, 0, 15), + [HC_ATL_PTD_DONEMAP] = REG_FIELD(ISP1763_HC_ATL_PTD_DONEMAP, 0, 15), + [HC_ATL_PTD_SKIPMAP] = REG_FIELD(ISP1763_HC_ATL_PTD_SKIPMAP, 0, 15), + [HC_ATL_PTD_LASTPTD] = REG_FIELD(ISP1763_HC_ATL_PTD_LASTPTD, 0, 15), + [PORT_OWNER] = REG_FIELD(ISP1763_HC_PORTSC1, 13, 13), + [PORT_POWER] = REG_FIELD(ISP1763_HC_PORTSC1, 12, 12), + [PORT_LSTATUS] = REG_FIELD(ISP1763_HC_PORTSC1, 10, 11), + [PORT_RESET] = REG_FIELD(ISP1763_HC_PORTSC1, 8, 8), + [PORT_SUSPEND] = REG_FIELD(ISP1763_HC_PORTSC1, 7, 7), + [PORT_RESUME] = REG_FIELD(ISP1763_HC_PORTSC1, 6, 6), + [PORT_PE] = REG_FIELD(ISP1763_HC_PORTSC1, 2, 2), + [PORT_CSC] = REG_FIELD(ISP1763_HC_PORTSC1, 1, 1), + [PORT_CONNECT] = REG_FIELD(ISP1763_HC_PORTSC1, 0, 0), + [HW_DATA_BUS_WIDTH] = REG_FIELD(ISP1763_HC_HW_MODE_CTRL, 4, 4), + [HW_DACK_POL_HIGH] = REG_FIELD(ISP1763_HC_HW_MODE_CTRL, 6, 6), + [HW_DREQ_POL_HIGH] = REG_FIELD(ISP1763_HC_HW_MODE_CTRL, 5, 5), + [HW_INTF_LOCK] = REG_FIELD(ISP1763_HC_HW_MODE_CTRL, 3, 3), + [HW_INTR_HIGH_ACT] = REG_FIELD(ISP1763_HC_HW_MODE_CTRL, 2, 2), + [HW_INTR_EDGE_TRIG] = REG_FIELD(ISP1763_HC_HW_MODE_CTRL, 1, 1), + [HW_GLOBAL_INTR_EN] = REG_FIELD(ISP1763_HC_HW_MODE_CTRL, 0, 0), + [SW_RESET_RESET_ATX] = REG_FIELD(ISP1763_HC_RESET, 3, 3), + [SW_RESET_RESET_ALL] = REG_FIELD(ISP1763_HC_RESET, 0, 0), + [HC_CHIP_ID_HIGH] = REG_FIELD(ISP1763_HC_CHIP_ID, 0, 15), + [HC_CHIP_ID_LOW] = REG_FIELD(ISP1763_HC_CHIP_REV, 8, 15), + [HC_CHIP_REV] = REG_FIELD(ISP1763_HC_CHIP_REV, 0, 7), + [HC_SCRATCH] = REG_FIELD(ISP1763_HC_SCRATCH, 0, 15), + [ISO_BUF_FILL] = REG_FIELD(ISP1763_HC_BUFFER_STATUS, 2, 2), + [INT_BUF_FILL] = REG_FIELD(ISP1763_HC_BUFFER_STATUS, 1, 1), + [ATL_BUF_FILL] = REG_FIELD(ISP1763_HC_BUFFER_STATUS, 0, 0), + [MEM_START_ADDR] = REG_FIELD(ISP1763_HC_MEMORY, 0, 15), + [HC_DATA] = REG_FIELD(ISP1763_HC_DATA, 0, 15), + [HC_INTERRUPT] = REG_FIELD(ISP1763_HC_INTERRUPT, 0, 10), + [HC_ATL_IRQ_ENABLE] = REG_FIELD(ISP1763_HC_INTERRUPT_ENABLE, 8, 8), + [HC_INT_IRQ_ENABLE] = REG_FIELD(ISP1763_HC_INTERRUPT_ENABLE, 7, 7), + [HC_ISO_IRQ_MASK_OR] = REG_FIELD(ISP1763_HC_ISO_IRQ_MASK_OR, 0, 15), + [HC_INT_IRQ_MASK_OR] = REG_FIELD(ISP1763_HC_INT_IRQ_MASK_OR, 0, 15), + [HC_ATL_IRQ_MASK_OR] = REG_FIELD(ISP1763_HC_ATL_IRQ_MASK_OR, 0, 15), + [HC_ISO_IRQ_MASK_AND] = REG_FIELD(ISP1763_HC_ISO_IRQ_MASK_AND, 0, 15), + [HC_INT_IRQ_MASK_AND] = REG_FIELD(ISP1763_HC_INT_IRQ_MASK_AND, 0, 15), + [HC_ATL_IRQ_MASK_AND] = REG_FIELD(ISP1763_HC_ATL_IRQ_MASK_AND, 0, 15), + [HW_HC_2_DIS] = REG_FIELD(ISP1763_HC_OTG_CTRL_SET, 15, 15), + [HW_OTG_DISABLE] = REG_FIELD(ISP1763_HC_OTG_CTRL_SET, 10, 10), + [HW_SW_SEL_HC_DC] = REG_FIELD(ISP1763_HC_OTG_CTRL_SET, 7, 7), + [HW_VBUS_DRV] = REG_FIELD(ISP1763_HC_OTG_CTRL_SET, 4, 4), + [HW_SEL_CP_EXT] = REG_FIELD(ISP1763_HC_OTG_CTRL_SET, 3, 3), + [HW_DM_PULLDOWN] = REG_FIELD(ISP1763_HC_OTG_CTRL_SET, 2, 2), + [HW_DP_PULLDOWN] = REG_FIELD(ISP1763_HC_OTG_CTRL_SET, 1, 1), + [HW_DP_PULLUP] = REG_FIELD(ISP1763_HC_OTG_CTRL_SET, 0, 0), + [HW_HC_2_DIS_CLEAR] = REG_FIELD(ISP1763_HC_OTG_CTRL_CLEAR, 15, 15), + [HW_OTG_DISABLE_CLEAR] = REG_FIELD(ISP1763_HC_OTG_CTRL_CLEAR, 10, 10), + [HW_SW_SEL_HC_DC_CLEAR] = REG_FIELD(ISP1763_HC_OTG_CTRL_CLEAR, 7, 7), + [HW_VBUS_DRV_CLEAR] = REG_FIELD(ISP1763_HC_OTG_CTRL_CLEAR, 4, 4), + [HW_SEL_CP_EXT_CLEAR] = REG_FIELD(ISP1763_HC_OTG_CTRL_CLEAR, 3, 3), + [HW_DM_PULLDOWN_CLEAR] = REG_FIELD(ISP1763_HC_OTG_CTRL_CLEAR, 2, 2), + [HW_DP_PULLDOWN_CLEAR] = REG_FIELD(ISP1763_HC_OTG_CTRL_CLEAR, 1, 1), + [HW_DP_PULLUP_CLEAR] = REG_FIELD(ISP1763_HC_OTG_CTRL_CLEAR, 0, 0), + /* Make sure the array is sized properly during compilation */ + [HC_FIELD_MAX] = {}, +}; + +static const struct regmap_range isp1763_hc_volatile_ranges[] = { + regmap_reg_range(ISP1763_HC_USBCMD, ISP1763_HC_ATL_PTD_LASTPTD), + regmap_reg_range(ISP1763_HC_BUFFER_STATUS, ISP1763_HC_DATA), + regmap_reg_range(ISP1763_HC_INTERRUPT, ISP1763_HC_OTG_CTRL_CLEAR), +}; + +static const struct regmap_access_table isp1763_hc_volatile_table = { + .yes_ranges = isp1763_hc_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(isp1763_hc_volatile_ranges), +}; + +static const struct regmap_config isp1763_hc_regmap_conf = { + .name = "isp1763-hc", + .reg_bits = 8, + .reg_stride = 2, + .val_bits = 16, + .fast_io = true, + .max_register = ISP1763_HC_OTG_CTRL_CLEAR, + .volatile_table = &isp1763_hc_volatile_table, +}; + +static const struct regmap_range isp176x_dc_volatile_ranges[] = { + regmap_reg_range(ISP176x_DC_EPMAXPKTSZ, ISP176x_DC_EPTYPE), + regmap_reg_range(ISP176x_DC_BUFLEN, ISP176x_DC_EPINDEX), +}; + +static const struct regmap_access_table isp176x_dc_volatile_table = { + .yes_ranges = isp176x_dc_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(isp176x_dc_volatile_ranges), +}; + +static const struct regmap_config isp1761_dc_regmap_conf = { + .name = "isp1761-dc", + .reg_bits = 16, + .reg_stride = 4, + .val_bits = 32, + .fast_io = true, + .max_register = ISP176x_DC_TESTMODE, + .volatile_table = &isp176x_dc_volatile_table, +}; + +static const struct reg_field isp1761_dc_reg_fields[] = { + [DC_DEVEN] = REG_FIELD(ISP176x_DC_ADDRESS, 7, 7), + [DC_DEVADDR] = REG_FIELD(ISP176x_DC_ADDRESS, 0, 6), + [DC_VBUSSTAT] = REG_FIELD(ISP176x_DC_MODE, 8, 8), + [DC_SFRESET] = REG_FIELD(ISP176x_DC_MODE, 4, 4), + [DC_GLINTENA] = REG_FIELD(ISP176x_DC_MODE, 3, 3), + [DC_CDBGMOD_ACK] = REG_FIELD(ISP176x_DC_INTCONF, 6, 6), + [DC_DDBGMODIN_ACK] = REG_FIELD(ISP176x_DC_INTCONF, 4, 4), + [DC_DDBGMODOUT_ACK] = REG_FIELD(ISP176x_DC_INTCONF, 2, 2), + [DC_INTPOL] = REG_FIELD(ISP176x_DC_INTCONF, 0, 0), + [DC_IEPRXTX_7] = REG_FIELD(ISP176x_DC_INTENABLE, 25, 25), + [DC_IEPRXTX_6] = REG_FIELD(ISP176x_DC_INTENABLE, 23, 23), + [DC_IEPRXTX_5] = REG_FIELD(ISP176x_DC_INTENABLE, 21, 21), + [DC_IEPRXTX_4] = REG_FIELD(ISP176x_DC_INTENABLE, 19, 19), + [DC_IEPRXTX_3] = REG_FIELD(ISP176x_DC_INTENABLE, 17, 17), + [DC_IEPRXTX_2] = REG_FIELD(ISP176x_DC_INTENABLE, 15, 15), + [DC_IEPRXTX_1] = REG_FIELD(ISP176x_DC_INTENABLE, 13, 13), + [DC_IEPRXTX_0] = REG_FIELD(ISP176x_DC_INTENABLE, 11, 11), + [DC_IEP0SETUP] = REG_FIELD(ISP176x_DC_INTENABLE, 8, 8), + [DC_IEVBUS] = REG_FIELD(ISP176x_DC_INTENABLE, 7, 7), + [DC_IEHS_STA] = REG_FIELD(ISP176x_DC_INTENABLE, 5, 5), + [DC_IERESM] = REG_FIELD(ISP176x_DC_INTENABLE, 4, 4), + [DC_IESUSP] = REG_FIELD(ISP176x_DC_INTENABLE, 3, 3), + [DC_IEBRST] = REG_FIELD(ISP176x_DC_INTENABLE, 0, 0), + [DC_EP0SETUP] = REG_FIELD(ISP176x_DC_EPINDEX, 5, 5), + [DC_ENDPIDX] = REG_FIELD(ISP176x_DC_EPINDEX, 1, 4), + [DC_EPDIR] = REG_FIELD(ISP176x_DC_EPINDEX, 0, 0), + [DC_CLBUF] = REG_FIELD(ISP176x_DC_CTRLFUNC, 4, 4), + [DC_VENDP] = REG_FIELD(ISP176x_DC_CTRLFUNC, 3, 3), + [DC_DSEN] = REG_FIELD(ISP176x_DC_CTRLFUNC, 2, 2), + [DC_STATUS] = REG_FIELD(ISP176x_DC_CTRLFUNC, 1, 1), + [DC_STALL] = REG_FIELD(ISP176x_DC_CTRLFUNC, 0, 0), + [DC_BUFLEN] = REG_FIELD(ISP176x_DC_BUFLEN, 0, 15), + [DC_FFOSZ] = REG_FIELD(ISP176x_DC_EPMAXPKTSZ, 0, 10), + [DC_EPENABLE] = REG_FIELD(ISP176x_DC_EPTYPE, 3, 3), + [DC_ENDPTYP] = REG_FIELD(ISP176x_DC_EPTYPE, 0, 1), + [DC_UFRAMENUM] = REG_FIELD(ISP176x_DC_FRAMENUM, 11, 13), + [DC_FRAMENUM] = REG_FIELD(ISP176x_DC_FRAMENUM, 0, 10), + [DC_CHIP_ID_HIGH] = REG_FIELD(ISP176x_DC_CHIPID, 16, 31), + [DC_CHIP_ID_LOW] = REG_FIELD(ISP176x_DC_CHIPID, 0, 15), + [DC_SCRATCH] = REG_FIELD(ISP176x_DC_SCRATCH, 0, 15), + /* Make sure the array is sized properly during compilation */ + [DC_FIELD_MAX] = {}, +}; + +static const struct regmap_range isp1763_dc_volatile_ranges[] = { + regmap_reg_range(ISP1763_DC_EPMAXPKTSZ, ISP1763_DC_EPTYPE), + regmap_reg_range(ISP1763_DC_BUFLEN, ISP1763_DC_EPINDEX), +}; + +static const struct regmap_access_table isp1763_dc_volatile_table = { + .yes_ranges = isp1763_dc_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(isp1763_dc_volatile_ranges), +}; + +static const struct reg_field isp1763_dc_reg_fields[] = { + [DC_DEVEN] = REG_FIELD(ISP1763_DC_ADDRESS, 7, 7), + [DC_DEVADDR] = REG_FIELD(ISP1763_DC_ADDRESS, 0, 6), + [DC_VBUSSTAT] = REG_FIELD(ISP1763_DC_MODE, 8, 8), + [DC_SFRESET] = REG_FIELD(ISP1763_DC_MODE, 4, 4), + [DC_GLINTENA] = REG_FIELD(ISP1763_DC_MODE, 3, 3), + [DC_CDBGMOD_ACK] = REG_FIELD(ISP1763_DC_INTCONF, 6, 6), + [DC_DDBGMODIN_ACK] = REG_FIELD(ISP1763_DC_INTCONF, 4, 4), + [DC_DDBGMODOUT_ACK] = REG_FIELD(ISP1763_DC_INTCONF, 2, 2), + [DC_INTPOL] = REG_FIELD(ISP1763_DC_INTCONF, 0, 0), + [DC_IEPRXTX_7] = REG_FIELD(ISP1763_DC_INTENABLE, 25, 25), + [DC_IEPRXTX_6] = REG_FIELD(ISP1763_DC_INTENABLE, 23, 23), + [DC_IEPRXTX_5] = REG_FIELD(ISP1763_DC_INTENABLE, 21, 21), + [DC_IEPRXTX_4] = REG_FIELD(ISP1763_DC_INTENABLE, 19, 19), + [DC_IEPRXTX_3] = REG_FIELD(ISP1763_DC_INTENABLE, 17, 17), + [DC_IEPRXTX_2] = REG_FIELD(ISP1763_DC_INTENABLE, 15, 15), + [DC_IEPRXTX_1] = REG_FIELD(ISP1763_DC_INTENABLE, 13, 13), + [DC_IEPRXTX_0] = REG_FIELD(ISP1763_DC_INTENABLE, 11, 11), + [DC_IEP0SETUP] = REG_FIELD(ISP1763_DC_INTENABLE, 8, 8), + [DC_IEVBUS] = REG_FIELD(ISP1763_DC_INTENABLE, 7, 7), + [DC_IEHS_STA] = REG_FIELD(ISP1763_DC_INTENABLE, 5, 5), + [DC_IERESM] = REG_FIELD(ISP1763_DC_INTENABLE, 4, 4), + [DC_IESUSP] = REG_FIELD(ISP1763_DC_INTENABLE, 3, 3), + [DC_IEBRST] = REG_FIELD(ISP1763_DC_INTENABLE, 0, 0), + [DC_EP0SETUP] = REG_FIELD(ISP1763_DC_EPINDEX, 5, 5), + [DC_ENDPIDX] = REG_FIELD(ISP1763_DC_EPINDEX, 1, 4), + [DC_EPDIR] = REG_FIELD(ISP1763_DC_EPINDEX, 0, 0), + [DC_CLBUF] = REG_FIELD(ISP1763_DC_CTRLFUNC, 4, 4), + [DC_VENDP] = REG_FIELD(ISP1763_DC_CTRLFUNC, 3, 3), + [DC_DSEN] = REG_FIELD(ISP1763_DC_CTRLFUNC, 2, 2), + [DC_STATUS] = REG_FIELD(ISP1763_DC_CTRLFUNC, 1, 1), + [DC_STALL] = REG_FIELD(ISP1763_DC_CTRLFUNC, 0, 0), + [DC_BUFLEN] = REG_FIELD(ISP1763_DC_BUFLEN, 0, 15), + [DC_FFOSZ] = REG_FIELD(ISP1763_DC_EPMAXPKTSZ, 0, 10), + [DC_EPENABLE] = REG_FIELD(ISP1763_DC_EPTYPE, 3, 3), + [DC_ENDPTYP] = REG_FIELD(ISP1763_DC_EPTYPE, 0, 1), + [DC_UFRAMENUM] = REG_FIELD(ISP1763_DC_FRAMENUM, 11, 13), + [DC_FRAMENUM] = REG_FIELD(ISP1763_DC_FRAMENUM, 0, 10), + [DC_CHIP_ID_HIGH] = REG_FIELD(ISP1763_DC_CHIPID_HIGH, 0, 15), + [DC_CHIP_ID_LOW] = REG_FIELD(ISP1763_DC_CHIPID_LOW, 0, 15), + [DC_SCRATCH] = REG_FIELD(ISP1763_DC_SCRATCH, 0, 15), + /* Make sure the array is sized properly during compilation */ + [DC_FIELD_MAX] = {}, +}; + +static const struct regmap_config isp1763_dc_regmap_conf = { + .name = "isp1763-dc", + .reg_bits = 8, + .reg_stride = 2, + .val_bits = 16, + .fast_io = true, + .max_register = ISP1763_DC_TESTMODE, + .volatile_table = &isp1763_dc_volatile_table, +}; + +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags) +{ + const struct regmap_config *hc_regmap; + const struct reg_field *hc_reg_fields; + const struct regmap_config *dc_regmap; + const struct reg_field *dc_reg_fields; + struct isp1760_device *isp; + struct isp1760_hcd *hcd; + struct isp1760_udc *udc; + struct regmap_field *f; + bool udc_enabled; + int ret; + int i; + + /* + * If neither the HCD not the UDC is enabled return an error, as no + * device would be registered. + */ + udc_enabled = ((devflags & ISP1760_FLAG_ISP1763) || + (devflags & ISP1760_FLAG_ISP1761)); + + if ((!IS_ENABLED(CONFIG_USB_ISP1760_HCD) || usb_disabled()) && + (!udc_enabled || !IS_ENABLED(CONFIG_USB_ISP1761_UDC))) + return -ENODEV; + + isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); + if (!isp) + return -ENOMEM; + + isp->dev = dev; + isp->devflags = devflags; + hcd = &isp->hcd; + udc = &isp->udc; + + hcd->is_isp1763 = !!(devflags & ISP1760_FLAG_ISP1763); + udc->is_isp1763 = !!(devflags & ISP1760_FLAG_ISP1763); + + if (!hcd->is_isp1763 && (devflags & ISP1760_FLAG_BUS_WIDTH_8)) { + dev_err(dev, "isp1760/61 do not support data width 8\n"); + return -EINVAL; + } + + if (hcd->is_isp1763) { + hc_regmap = &isp1763_hc_regmap_conf; + hc_reg_fields = &isp1763_hc_reg_fields[0]; + dc_regmap = &isp1763_dc_regmap_conf; + dc_reg_fields = &isp1763_dc_reg_fields[0]; + } else { + hc_regmap = &isp1760_hc_regmap_conf; + hc_reg_fields = &isp1760_hc_reg_fields[0]; + dc_regmap = &isp1761_dc_regmap_conf; + dc_reg_fields = &isp1761_dc_reg_fields[0]; + } + + isp->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); + if (IS_ERR(isp->rst_gpio)) + return PTR_ERR(isp->rst_gpio); + + hcd->base = devm_ioremap_resource(dev, mem); + if (IS_ERR(hcd->base)) + return PTR_ERR(hcd->base); + + hcd->regs = devm_regmap_init_mmio(dev, hcd->base, hc_regmap); + if (IS_ERR(hcd->regs)) + return PTR_ERR(hcd->regs); + + for (i = 0; i < HC_FIELD_MAX; i++) { + f = devm_regmap_field_alloc(dev, hcd->regs, hc_reg_fields[i]); + if (IS_ERR(f)) + return PTR_ERR(f); + + hcd->fields[i] = f; + } + + udc->regs = devm_regmap_init_mmio(dev, hcd->base, dc_regmap); + if (IS_ERR(udc->regs)) + return PTR_ERR(udc->regs); + + for (i = 0; i < DC_FIELD_MAX; i++) { + f = devm_regmap_field_alloc(dev, udc->regs, dc_reg_fields[i]); + if (IS_ERR(f)) + return PTR_ERR(f); + + udc->fields[i] = f; + } + + if (hcd->is_isp1763) + hcd->memory_layout = &isp1763_memory_conf; + else + hcd->memory_layout = &isp176x_memory_conf; + + ret = isp1760_init_core(isp); + if (ret < 0) + return ret; + + if (IS_ENABLED(CONFIG_USB_ISP1760_HCD) && !usb_disabled()) { + ret = isp1760_hcd_register(hcd, mem, irq, + irqflags | IRQF_SHARED, dev); + if (ret < 0) + return ret; + } + + if (udc_enabled && IS_ENABLED(CONFIG_USB_ISP1761_UDC)) { + ret = isp1760_udc_register(isp, irq, irqflags); + if (ret < 0) { + isp1760_hcd_unregister(hcd); + return ret; + } + } + + dev_set_drvdata(dev, isp); + + return 0; +} + +void isp1760_unregister(struct device *dev) +{ + struct isp1760_device *isp = dev_get_drvdata(dev); + + isp1760_udc_unregister(isp); + isp1760_hcd_unregister(&isp->hcd); +} + +MODULE_DESCRIPTION("Driver for the ISP1760 USB-controller from NXP"); +MODULE_AUTHOR("Sebastian Siewior "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/isp1760/isp1760-core.h b/drivers/usb/isp1760/isp1760-core.h new file mode 100644 index 000000000..91e0ee399 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-core.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2021 Linaro, Rui Miguel Silva + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * Rui Miguel Silva + */ + +#ifndef _ISP1760_CORE_H_ +#define _ISP1760_CORE_H_ + +#include +#include + +#include "isp1760-hcd.h" +#include "isp1760-udc.h" + +struct device; +struct gpio_desc; + +/* + * 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_PERIPHERAL_EN 0x00000004 /* Port 1 supports Peripheral mode*/ +#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_ISP1763 0x00000200 /* Chip is ISP1763 */ +#define ISP1760_FLAG_BUS_WIDTH_8 0x00000400 /* 8-bit data bus width */ + +struct isp1760_device { + struct device *dev; + + unsigned int devflags; + struct gpio_desc *rst_gpio; + + struct isp1760_hcd hcd; + struct isp1760_udc udc; +}; + +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags); +void isp1760_unregister(struct device *dev); + +void isp1760_set_pullup(struct isp1760_device *isp, bool enable); + +static inline u32 isp1760_field_read(struct regmap_field **fields, u32 field) +{ + unsigned int val; + + regmap_field_read(fields[field], &val); + + return val; +} + +static inline void isp1760_field_write(struct regmap_field **fields, u32 field, + u32 val) +{ + regmap_field_write(fields[field], val); +} + +static inline void isp1760_field_set(struct regmap_field **fields, u32 field) +{ + isp1760_field_write(fields, field, 0xFFFFFFFF); +} + +static inline void isp1760_field_clear(struct regmap_field **fields, u32 field) +{ + isp1760_field_write(fields, field, 0); +} + +static inline u32 isp1760_reg_read(struct regmap *regs, u32 reg) +{ + unsigned int val; + + regmap_read(regs, reg, &val); + + return val; +} + +static inline void isp1760_reg_write(struct regmap *regs, u32 reg, u32 val) +{ + regmap_write(regs, reg, val); +} +#endif diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c new file mode 100644 index 000000000..6c52c6f59 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -0,0 +1,2624 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + * + * Copyright 2021 Linaro, Rui Miguel Silva + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isp1760-core.h" +#include "isp1760-hcd.h" +#include "isp1760-regs.h" + +static struct kmem_cache *qtd_cachep; +static struct kmem_cache *qh_cachep; +static struct kmem_cache *urb_listitem_cachep; + +typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, + struct isp1760_qtd *qtd); + +static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) +{ + return *(struct isp1760_hcd **)hcd->hcd_priv; +} + +#define dw_to_le32(x) (cpu_to_le32((__force u32)x)) +#define le32_to_dw(x) ((__force __dw)(le32_to_cpu(x))) + +/* 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; +}; + +struct ptd_le32 { + __le32 dw0; + __le32 dw1; + __le32 dw2; + __le32 dw3; + __le32 dw4; + __le32 dw5; + __le32 dw6; + __le32 dw7; +}; + +#define PTD_OFFSET 0x0400 +#define ISO_PTD_OFFSET 0x0400 +#define INT_PTD_OFFSET 0x0800 +#define ATL_PTD_OFFSET 0x0c00 +#define PAYLOAD_OFFSET 0x1000 + +#define ISP_BANK_0 0x00 +#define ISP_BANK_1 0x01 +#define ISP_BANK_2 0x02 +#define ISP_BANK_3 0x03 + +#define TO_DW(x) ((__force __dw)x) +#define TO_U32(x) ((__force u32)x) + + /* ATL */ + /* DW0 */ +#define DW0_VALID_BIT TO_DW(1) +#define FROM_DW0_VALID(x) (TO_U32(x) & 0x01) +#define TO_DW0_LENGTH(x) TO_DW((((u32)x) << 3)) +#define TO_DW0_MAXPACKET(x) TO_DW((((u32)x) << 18)) +#define TO_DW0_MULTI(x) TO_DW((((u32)x) << 29)) +#define TO_DW0_ENDPOINT(x) TO_DW((((u32)x) << 31)) +/* DW1 */ +#define TO_DW1_DEVICE_ADDR(x) TO_DW((((u32)x) << 3)) +#define TO_DW1_PID_TOKEN(x) TO_DW((((u32)x) << 10)) +#define DW1_TRANS_BULK TO_DW(((u32)2 << 12)) +#define DW1_TRANS_INT TO_DW(((u32)3 << 12)) +#define DW1_TRANS_SPLIT TO_DW(((u32)1 << 14)) +#define DW1_SE_USB_LOSPEED TO_DW(((u32)2 << 16)) +#define TO_DW1_PORT_NUM(x) TO_DW((((u32)x) << 18)) +#define TO_DW1_HUB_NUM(x) TO_DW((((u32)x) << 25)) +/* DW2 */ +#define TO_DW2_DATA_START_ADDR(x) TO_DW((((u32)x) << 8)) +#define TO_DW2_RL(x) TO_DW(((x) << 25)) +#define FROM_DW2_RL(x) ((TO_U32(x) >> 25) & 0xf) +/* DW3 */ +#define FROM_DW3_NRBYTESTRANSFERRED(x) TO_U32((x) & 0x3fff) +#define FROM_DW3_SCS_NRBYTESTRANSFERRED(x) TO_U32((x) & 0x07ff) +#define TO_DW3_NAKCOUNT(x) TO_DW(((x) << 19)) +#define FROM_DW3_NAKCOUNT(x) ((TO_U32(x) >> 19) & 0xf) +#define TO_DW3_CERR(x) TO_DW(((x) << 23)) +#define FROM_DW3_CERR(x) ((TO_U32(x) >> 23) & 0x3) +#define TO_DW3_DATA_TOGGLE(x) TO_DW(((x) << 25)) +#define FROM_DW3_DATA_TOGGLE(x) ((TO_U32(x) >> 25) & 0x1) +#define TO_DW3_PING(x) TO_DW(((x) << 26)) +#define FROM_DW3_PING(x) ((TO_U32(x) >> 26) & 0x1) +#define DW3_ERROR_BIT TO_DW((1 << 28)) +#define DW3_BABBLE_BIT TO_DW((1 << 29)) +#define DW3_HALT_BIT TO_DW((1 << 30)) +#define DW3_ACTIVE_BIT TO_DW((1 << 31)) +#define FROM_DW3_ACTIVE(x) ((TO_U32(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 (3) + +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; +}; + +static const u32 isp176x_hc_portsc1_fields[] = { + [PORT_OWNER] = BIT(13), + [PORT_POWER] = BIT(12), + [PORT_LSTATUS] = BIT(10), + [PORT_RESET] = BIT(8), + [PORT_SUSPEND] = BIT(7), + [PORT_RESUME] = BIT(6), + [PORT_PE] = BIT(2), + [PORT_CSC] = BIT(1), + [PORT_CONNECT] = BIT(0), +}; + +/* + * Access functions for isp176x registers regmap fields + */ +static u32 isp1760_hcd_read(struct usb_hcd *hcd, u32 field) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + return isp1760_field_read(priv->fields, field); +} + +/* + * We need, in isp176x, to write directly the values to the portsc1 + * register so it will make the other values to trigger. + */ +static void isp1760_hcd_portsc1_set_clear(struct isp1760_hcd *priv, u32 field, + u32 val) +{ + u32 bit = isp176x_hc_portsc1_fields[field]; + u16 portsc1_reg = priv->is_isp1763 ? ISP1763_HC_PORTSC1 : + ISP176x_HC_PORTSC1; + u32 port_status = readl(priv->base + portsc1_reg); + + if (val) + writel(port_status | bit, priv->base + portsc1_reg); + else + writel(port_status & ~bit, priv->base + portsc1_reg); +} + +static void isp1760_hcd_write(struct usb_hcd *hcd, u32 field, u32 val) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + if (unlikely((field >= PORT_OWNER && field <= PORT_CONNECT))) + return isp1760_hcd_portsc1_set_clear(priv, field, val); + + isp1760_field_write(priv->fields, field, val); +} + +static void isp1760_hcd_set(struct usb_hcd *hcd, u32 field) +{ + isp1760_hcd_write(hcd, field, 0xFFFFFFFF); +} + +static void isp1760_hcd_clear(struct usb_hcd *hcd, u32 field) +{ + isp1760_hcd_write(hcd, field, 0); +} + +static int isp1760_hcd_set_and_wait(struct usb_hcd *hcd, u32 field, + u32 timeout_us) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 val; + + isp1760_hcd_set(hcd, field); + + return regmap_field_read_poll_timeout(priv->fields[field], val, + val, 0, timeout_us); +} + +static int isp1760_hcd_set_and_wait_swap(struct usb_hcd *hcd, u32 field, + u32 timeout_us) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 val; + + isp1760_hcd_set(hcd, field); + + return regmap_field_read_poll_timeout(priv->fields[field], val, + !val, 0, timeout_us); +} + +static int isp1760_hcd_clear_and_wait(struct usb_hcd *hcd, u32 field, + u32 timeout_us) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 val; + + isp1760_hcd_clear(hcd, field); + + return regmap_field_read_poll_timeout(priv->fields[field], val, + !val, 0, timeout_us); +} + +static bool isp1760_hcd_is_set(struct usb_hcd *hcd, u32 field) +{ + return !!isp1760_hcd_read(hcd, field); +} + +static bool isp1760_hcd_ppc_is_set(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + if (priv->is_isp1763) + return true; + + return isp1760_hcd_is_set(hcd, HCS_PPC); +} + +static u32 isp1760_hcd_n_ports(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + if (priv->is_isp1763) + return 1; + + return isp1760_hcd_read(hcd, HCS_N_PORTS); +} + +/* + * 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_read() 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 = readl_relaxed(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 = readl_relaxed(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 isp1760_mem_read(struct usb_hcd *hcd, u32 src_offset, void *dst, + u32 bytes) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + isp1760_reg_write(priv->regs, ISP176x_HC_MEMORY, src_offset); + ndelay(100); + + bank_reads8(priv->base, src_offset, ISP_BANK_0, dst, bytes); +} + +/* + * ISP1763 does not have the banks direct host controller memory access, + * needs to use the HC_DATA register. Add data read/write according to this, + * and also adjust 16bit access. + */ +static void isp1763_mem_read(struct usb_hcd *hcd, u16 srcaddr, + u16 *dstptr, u32 bytes) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + /* Write the starting device address to the hcd memory register */ + isp1760_reg_write(priv->regs, ISP1763_HC_MEMORY, srcaddr); + ndelay(100); /* Delay between consecutive access */ + + /* As long there are at least 16-bit to read ... */ + while (bytes >= 2) { + *dstptr = __raw_readw(priv->base + ISP1763_HC_DATA); + bytes -= 2; + dstptr++; + } + + /* If there are no more bytes to read, return */ + if (bytes <= 0) + return; + + *((u8 *)dstptr) = (u8)(readw(priv->base + ISP1763_HC_DATA) & 0xFF); +} + +static void mem_read(struct usb_hcd *hcd, u32 src_offset, __u32 *dst, + u32 bytes) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + if (!priv->is_isp1763) + return isp1760_mem_read(hcd, src_offset, (u16 *)dst, bytes); + + isp1763_mem_read(hcd, (u16)src_offset, (u16 *)dst, bytes); +} + +static void isp1760_mem_write(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) { + writel_relaxed(*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) + writel_relaxed(*src, dst); + else + __raw_writel(*src, dst); +} + +static void isp1763_mem_write(struct usb_hcd *hcd, u16 dstaddr, u16 *src, + u32 bytes) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + /* Write the starting device address to the hcd memory register */ + isp1760_reg_write(priv->regs, ISP1763_HC_MEMORY, dstaddr); + ndelay(100); /* Delay between consecutive access */ + + while (bytes >= 2) { + /* Get and write the data; then adjust the data ptr and len */ + __raw_writew(*src, priv->base + ISP1763_HC_DATA); + bytes -= 2; + src++; + } + + /* If there are no more bytes to process, return */ + if (bytes <= 0) + return; + + /* + * The only way to get here is if there is a single byte left, + * get it and write it to the data reg; + */ + writew(*((u8 *)src), priv->base + ISP1763_HC_DATA); +} + +static void mem_write(struct usb_hcd *hcd, u32 dst_offset, __u32 *src, + u32 bytes) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + if (!priv->is_isp1763) + return isp1760_mem_write(priv->base, dst_offset, src, bytes); + + isp1763_mem_write(hcd, dst_offset, (u16 *)src, bytes); +} + +/* + * 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 isp1760_ptd_read(struct usb_hcd *hcd, u32 ptd_offset, u32 slot, + struct ptd *ptd) +{ + u16 src_offset = ptd_offset + slot * sizeof(*ptd); + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + isp1760_reg_write(priv->regs, ISP176x_HC_MEMORY, src_offset); + ndelay(90); + + bank_reads8(priv->base, src_offset, ISP_BANK_0, (void *)ptd, + sizeof(*ptd)); +} + +static void isp1763_ptd_read(struct usb_hcd *hcd, u32 ptd_offset, u32 slot, + struct ptd *ptd) +{ + u16 src_offset = ptd_offset + slot * sizeof(*ptd); + struct ptd_le32 le32_ptd; + + isp1763_mem_read(hcd, src_offset, (u16 *)&le32_ptd, sizeof(le32_ptd)); + /* Normalize the data obtained */ + ptd->dw0 = le32_to_dw(le32_ptd.dw0); + ptd->dw1 = le32_to_dw(le32_ptd.dw1); + ptd->dw2 = le32_to_dw(le32_ptd.dw2); + ptd->dw3 = le32_to_dw(le32_ptd.dw3); + ptd->dw4 = le32_to_dw(le32_ptd.dw4); + ptd->dw5 = le32_to_dw(le32_ptd.dw5); + ptd->dw6 = le32_to_dw(le32_ptd.dw6); + ptd->dw7 = le32_to_dw(le32_ptd.dw7); +} + +static void ptd_read(struct usb_hcd *hcd, u32 ptd_offset, u32 slot, + struct ptd *ptd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + if (!priv->is_isp1763) + return isp1760_ptd_read(hcd, ptd_offset, slot, ptd); + + isp1763_ptd_read(hcd, ptd_offset, slot, ptd); +} + +static void isp1763_ptd_write(struct usb_hcd *hcd, u32 ptd_offset, u32 slot, + struct ptd *cpu_ptd) +{ + u16 dst_offset = ptd_offset + slot * sizeof(*cpu_ptd); + struct ptd_le32 ptd; + + ptd.dw0 = dw_to_le32(cpu_ptd->dw0); + ptd.dw1 = dw_to_le32(cpu_ptd->dw1); + ptd.dw2 = dw_to_le32(cpu_ptd->dw2); + ptd.dw3 = dw_to_le32(cpu_ptd->dw3); + ptd.dw4 = dw_to_le32(cpu_ptd->dw4); + ptd.dw5 = dw_to_le32(cpu_ptd->dw5); + ptd.dw6 = dw_to_le32(cpu_ptd->dw6); + ptd.dw7 = dw_to_le32(cpu_ptd->dw7); + + isp1763_mem_write(hcd, dst_offset, (u16 *)&ptd.dw0, + 8 * sizeof(ptd.dw0)); +} + +static void isp1760_ptd_write(void __iomem *base, u32 ptd_offset, u32 slot, + struct ptd *ptd) +{ + u32 dst_offset = ptd_offset + slot * sizeof(*ptd); + + /* + * Make sure dw0 gets written last (after other dw's and after payload) + * since it contains the enable bit + */ + isp1760_mem_write(base, dst_offset + sizeof(ptd->dw0), + (__force u32 *)&ptd->dw1, 7 * sizeof(ptd->dw1)); + wmb(); + isp1760_mem_write(base, dst_offset, (__force u32 *)&ptd->dw0, + sizeof(ptd->dw0)); +} + +static void ptd_write(struct usb_hcd *hcd, u32 ptd_offset, u32 slot, + struct ptd *ptd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + if (!priv->is_isp1763) + return isp1760_ptd_write(priv->base, ptd_offset, slot, ptd); + + isp1763_ptd_write(hcd, ptd_offset, slot, ptd); +} + +/* memory management of the 60kb on the chip from 0x1000 to 0xffff */ +static void init_memory(struct isp1760_hcd *priv) +{ + const struct isp1760_memory_layout *mem = priv->memory_layout; + int i, j, curr; + u32 payload_addr; + + payload_addr = PAYLOAD_OFFSET; + + for (i = 0, curr = 0; i < ARRAY_SIZE(mem->blocks); i++, curr += j) { + for (j = 0; j < mem->blocks[i]; j++) { + priv->memory_pool[curr + j].start = payload_addr; + priv->memory_pool[curr + j].size = mem->blocks_size[i]; + priv->memory_pool[curr + j].free = 1; + payload_addr += priv->memory_pool[curr + j].size; + } + } + + WARN_ON(payload_addr - priv->memory_pool[0].start > + mem->payload_area_size); +} + +static void alloc_mem(struct usb_hcd *hcd, struct isp1760_qtd *qtd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + const struct isp1760_memory_layout *mem = priv->memory_layout; + int i; + + WARN_ON(qtd->payload_addr); + + if (!qtd->length) + return; + + for (i = 0; i < mem->payload_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); + const struct isp1760_memory_layout *mem = priv->memory_layout; + int i; + + if (!qtd->payload_addr) + return; + + for (i = 0; i < mem->payload_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; +} + +/* reset a non-running (STS_HALT == 1) controller */ +static int ehci_reset(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + hcd->state = HC_STATE_HALT; + priv->next_statechange = jiffies; + + return isp1760_hcd_set_and_wait_swap(hcd, CMD_RESET, 250 * 1000); +} + +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 isoc_cache; + u32 isoc_thres; + 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; + + if (priv->is_isp1763) { + priv->i_thresh = 2; + return 0; + } + + /* controllers may cache some of the periodic schedule ... */ + isoc_cache = isp1760_hcd_read(hcd, HCC_ISOC_CACHE); + isoc_thres = isp1760_hcd_read(hcd, HCC_ISOC_THRES); + + /* full frame cache */ + if (isoc_cache) + priv->i_thresh = 8; + else /* N microframes cached */ + priv->i_thresh = 2 + isoc_thres; + + return 0; +} + +static int isp1760_hc_setup(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 atx_reset; + int result; + u32 scratch; + u32 pattern; + + if (priv->is_isp1763) + pattern = 0xcafe; + else + pattern = 0xdeadcafe; + + isp1760_hcd_write(hcd, HC_SCRATCH, pattern); + + /* + * we do not care about the read value here we just want to + * change bus pattern. + */ + isp1760_hcd_read(hcd, HC_CHIP_ID_HIGH); + scratch = isp1760_hcd_read(hcd, HC_SCRATCH); + if (scratch != pattern) { + dev_err(hcd->self.controller, "Scratch test failed. 0x%08x\n", + scratch); + return -ENODEV; + } + + /* + * The RESET_HC bit in the SW_RESET register is supposed to reset the + * host controller without touching the CPU interface registers, but at + * least on the ISP1761 it seems to behave as the RESET_ALL bit and + * reset the whole device. We thus can't use it here, so let's reset + * the host controller through the EHCI USB Command register. The device + * has been reset in core code anyway, so this shouldn't matter. + */ + isp1760_hcd_clear(hcd, ISO_BUF_FILL); + isp1760_hcd_clear(hcd, INT_BUF_FILL); + isp1760_hcd_clear(hcd, ATL_BUF_FILL); + + isp1760_hcd_set(hcd, HC_ATL_PTD_SKIPMAP); + isp1760_hcd_set(hcd, HC_INT_PTD_SKIPMAP); + isp1760_hcd_set(hcd, HC_ISO_PTD_SKIPMAP); + + result = ehci_reset(hcd); + if (result) + return result; + + /* Step 11 passed */ + + /* ATL reset */ + if (priv->is_isp1763) + atx_reset = SW_RESET_RESET_ATX; + else + atx_reset = ALL_ATX_RESET; + + isp1760_hcd_set(hcd, atx_reset); + mdelay(10); + isp1760_hcd_clear(hcd, atx_reset); + + if (priv->is_isp1763) { + isp1760_hcd_set(hcd, HW_OTG_DISABLE); + isp1760_hcd_set(hcd, HW_SW_SEL_HC_DC_CLEAR); + isp1760_hcd_set(hcd, HW_HC_2_DIS_CLEAR); + mdelay(10); + + isp1760_hcd_set(hcd, HW_INTF_LOCK); + } + + isp1760_hcd_set(hcd, HC_INT_IRQ_ENABLE); + isp1760_hcd_set(hcd, HC_ATL_IRQ_ENABLE); + + 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); + 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 = TO_DW((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 |= DW1_SE_USB_LOSPEED; + + 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 = TO_DW(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 |= TO_DW(period); + ptd->dw4 = TO_DW(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 isp1760_slotinfo *slots, + struct isp1760_qtd *qtd, struct isp1760_qh *qh, + struct ptd *ptd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + const struct isp1760_memory_layout *mem = priv->memory_layout; + int skip_map; + + WARN_ON((slot < 0) || (slot > mem->slot_num - 1)); + WARN_ON(qtd->length && !qtd->payload_addr); + WARN_ON(slots[slot].qtd); + WARN_ON(slots[slot].qh); + WARN_ON(qtd->status != QTD_PAYLOAD_ALLOC); + + if (priv->is_isp1763) + ndelay(100); + + /* Make sure done map has not triggered from some unlinked transfer */ + if (ptd_offset == ATL_PTD_OFFSET) { + skip_map = isp1760_hcd_read(hcd, HC_ATL_PTD_SKIPMAP); + isp1760_hcd_write(hcd, HC_ATL_PTD_SKIPMAP, + skip_map | (1 << slot)); + priv->atl_done_map |= isp1760_hcd_read(hcd, HC_ATL_PTD_DONEMAP); + priv->atl_done_map &= ~(1 << slot); + } else { + skip_map = isp1760_hcd_read(hcd, HC_INT_PTD_SKIPMAP); + isp1760_hcd_write(hcd, HC_INT_PTD_SKIPMAP, + skip_map | (1 << slot)); + priv->int_done_map |= isp1760_hcd_read(hcd, HC_INT_PTD_DONEMAP); + priv->int_done_map &= ~(1 << slot); + } + + skip_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, ptd_offset, slot, ptd); + + if (ptd_offset == ATL_PTD_OFFSET) + isp1760_hcd_write(hcd, HC_ATL_PTD_SKIPMAP, skip_map); + else + isp1760_hcd_write(hcd, HC_INT_PTD_SKIPMAP, 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) +{ + struct isp1760_qtd *qtd, *qtd_next; + struct urb_listitem *urb_listitem; + int last_qtd; + + 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_read(hcd, qtd->payload_addr, + qtd->data_buffer, + qtd->actual_length); + fallthrough; + case OUT_PID: + qtd->urb->actual_length += + qtd->actual_length; + fallthrough; + 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); + const struct isp1760_memory_layout *mem = priv->memory_layout; + int slot_num = mem->slot_num; + int ptd_offset; + struct isp1760_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 < slot_num; 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_write(hcd, 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 > slot_num - 1) && (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) +{ + u32 dw4; + int i; + + dw4 = TO_U32(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 + 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 isp1760_slotinfo *slots; + u32 ptd_offset; + struct isp1760_qtd *qtd; + int modified; + int skip_map; + + skip_map = isp1760_hcd_read(hcd, HC_INT_PTD_SKIPMAP); + priv->int_done_map &= ~skip_map; + skip_map = isp1760_hcd_read(hcd, HC_ATL_PTD_SKIPMAP); + 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, 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, 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); + irqreturn_t irqret = IRQ_NONE; + u32 int_reg; + u32 imask; + + spin_lock(&priv->lock); + + if (!(hcd->state & HC_STATE_RUNNING)) + goto leave; + + imask = isp1760_hcd_read(hcd, HC_INTERRUPT); + if (unlikely(!imask)) + goto leave; + + int_reg = priv->is_isp1763 ? ISP1763_HC_INTERRUPT : + ISP176x_HC_INTERRUPT; + isp1760_reg_write(priv->regs, int_reg, imask); + + priv->int_done_map |= isp1760_hcd_read(hcd, HC_INT_PTD_DONEMAP); + priv->atl_done_map |= isp1760_hcd_read(hcd, HC_ATL_PTD_DONEMAP); + + 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 struct usb_hcd *errata2_timer_hcd; + +static void errata2_function(struct timer_list *unused) +{ + struct usb_hcd *hcd = errata2_timer_hcd; + struct isp1760_hcd *priv = hcd_to_priv(hcd); + const struct isp1760_memory_layout *mem = priv->memory_layout; + int slot; + struct ptd ptd; + unsigned long spinflags; + + spin_lock_irqsave(&priv->lock, spinflags); + + for (slot = 0; slot < mem->slot_num; slot++) + if (priv->atl_slots[slot].qh && time_after(jiffies, + priv->atl_slots[slot].timestamp + + msecs_to_jiffies(SLOT_TIMEOUT))) { + ptd_read(hcd, 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 + msecs_to_jiffies(SLOT_CHECK_PERIOD); + add_timer(&errata2_timer); +} + +static int isp1763_run(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int retval; + u32 chipid_h; + u32 chipid_l; + u32 chip_rev; + u32 ptd_atl_int; + u32 ptd_iso; + + hcd->uses_new_polling = 1; + hcd->state = HC_STATE_RUNNING; + + chipid_h = isp1760_hcd_read(hcd, HC_CHIP_ID_HIGH); + chipid_l = isp1760_hcd_read(hcd, HC_CHIP_ID_LOW); + chip_rev = isp1760_hcd_read(hcd, HC_CHIP_REV); + dev_info(hcd->self.controller, "USB ISP %02x%02x HW rev. %d started\n", + chipid_h, chipid_l, chip_rev); + + isp1760_hcd_clear(hcd, ISO_BUF_FILL); + isp1760_hcd_clear(hcd, INT_BUF_FILL); + isp1760_hcd_clear(hcd, ATL_BUF_FILL); + + isp1760_hcd_set(hcd, HC_ATL_PTD_SKIPMAP); + isp1760_hcd_set(hcd, HC_INT_PTD_SKIPMAP); + isp1760_hcd_set(hcd, HC_ISO_PTD_SKIPMAP); + ndelay(100); + isp1760_hcd_clear(hcd, HC_ATL_PTD_DONEMAP); + isp1760_hcd_clear(hcd, HC_INT_PTD_DONEMAP); + isp1760_hcd_clear(hcd, HC_ISO_PTD_DONEMAP); + + isp1760_hcd_set(hcd, HW_OTG_DISABLE); + isp1760_reg_write(priv->regs, ISP1763_HC_OTG_CTRL_CLEAR, BIT(7)); + isp1760_reg_write(priv->regs, ISP1763_HC_OTG_CTRL_CLEAR, BIT(15)); + mdelay(10); + + isp1760_hcd_set(hcd, HC_INT_IRQ_ENABLE); + isp1760_hcd_set(hcd, HC_ATL_IRQ_ENABLE); + + isp1760_hcd_set(hcd, HW_GLOBAL_INTR_EN); + + isp1760_hcd_clear(hcd, HC_ATL_IRQ_MASK_AND); + isp1760_hcd_clear(hcd, HC_INT_IRQ_MASK_AND); + isp1760_hcd_clear(hcd, HC_ISO_IRQ_MASK_AND); + + isp1760_hcd_set(hcd, HC_ATL_IRQ_MASK_OR); + isp1760_hcd_set(hcd, HC_INT_IRQ_MASK_OR); + isp1760_hcd_set(hcd, HC_ISO_IRQ_MASK_OR); + + ptd_atl_int = 0x8000; + ptd_iso = 0x0001; + + isp1760_hcd_write(hcd, HC_ATL_PTD_LASTPTD, ptd_atl_int); + isp1760_hcd_write(hcd, HC_INT_PTD_LASTPTD, ptd_atl_int); + isp1760_hcd_write(hcd, HC_ISO_PTD_LASTPTD, ptd_iso); + + isp1760_hcd_set(hcd, ATL_BUF_FILL); + isp1760_hcd_set(hcd, INT_BUF_FILL); + + isp1760_hcd_clear(hcd, CMD_LRESET); + isp1760_hcd_clear(hcd, CMD_RESET); + + retval = isp1760_hcd_set_and_wait(hcd, CMD_RUN, 250 * 1000); + if (retval) + return retval; + + down_write(&ehci_cf_port_reset_rwsem); + retval = isp1760_hcd_set_and_wait(hcd, FLAG_CF, 250 * 1000); + up_write(&ehci_cf_port_reset_rwsem); + if (retval) + return retval; + + return 0; +} + +static int isp1760_run(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int retval; + u32 chipid_h; + u32 chipid_l; + u32 chip_rev; + u32 ptd_atl_int; + u32 ptd_iso; + + /* + * ISP1763 have some differences in the setup and order to enable + * the ports, disable otg, setup buffers, and ATL, INT, ISO status. + * So, just handle it a separate sequence. + */ + if (priv->is_isp1763) + return isp1763_run(hcd); + + hcd->uses_new_polling = 1; + + hcd->state = HC_STATE_RUNNING; + + /* Set PTD interrupt AND & OR maps */ + isp1760_hcd_clear(hcd, HC_ATL_IRQ_MASK_AND); + isp1760_hcd_clear(hcd, HC_INT_IRQ_MASK_AND); + isp1760_hcd_clear(hcd, HC_ISO_IRQ_MASK_AND); + + isp1760_hcd_set(hcd, HC_ATL_IRQ_MASK_OR); + isp1760_hcd_set(hcd, HC_INT_IRQ_MASK_OR); + isp1760_hcd_set(hcd, HC_ISO_IRQ_MASK_OR); + + /* step 23 passed */ + + isp1760_hcd_set(hcd, HW_GLOBAL_INTR_EN); + + isp1760_hcd_clear(hcd, CMD_LRESET); + isp1760_hcd_clear(hcd, CMD_RESET); + + retval = isp1760_hcd_set_and_wait(hcd, 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); + + retval = isp1760_hcd_set_and_wait(hcd, FLAG_CF, 250 * 1000); + up_write(&ehci_cf_port_reset_rwsem); + if (retval) + return retval; + + errata2_timer_hcd = hcd; + timer_setup(&errata2_timer, errata2_function, 0); + errata2_timer.expires = jiffies + msecs_to_jiffies(SLOT_CHECK_PERIOD); + add_timer(&errata2_timer); + + chipid_h = isp1760_hcd_read(hcd, HC_CHIP_ID_HIGH); + chipid_l = isp1760_hcd_read(hcd, HC_CHIP_ID_LOW); + chip_rev = isp1760_hcd_read(hcd, HC_CHIP_REV); + dev_info(hcd->self.controller, "USB ISP %02x%02x HW rev. %d started\n", + chipid_h, chipid_l, chip_rev); + + /* PTD Register Init Part 2, Step 28 */ + + /* Setup registers controlling PTD checking */ + ptd_atl_int = 0x80000000; + ptd_iso = 0x00000001; + + isp1760_hcd_write(hcd, HC_ATL_PTD_LASTPTD, ptd_atl_int); + isp1760_hcd_write(hcd, HC_INT_PTD_LASTPTD, ptd_atl_int); + isp1760_hcd_write(hcd, HC_ISO_PTD_LASTPTD, ptd_iso); + + isp1760_hcd_set(hcd, HC_ATL_PTD_SKIPMAP); + isp1760_hcd_set(hcd, HC_INT_PTD_SKIPMAP); + isp1760_hcd_set(hcd, HC_ISO_PTD_SKIPMAP); + + isp1760_hcd_set(hcd, ATL_BUF_FILL); + isp1760_hcd_set(hcd, 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; + + 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. + */ +static void packetize_urb(struct usb_hcd *hcd, + struct urb *urb, struct list_head *head, gfp_t flags) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + const struct isp1760_memory_layout *mem = priv->memory_layout; + 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 = usb_maxpacket(urb->dev, 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; + + if (len > mem->blocks_size[ISP176x_BLOCK_NUM - 1]) + this_qtd_len = mem->blocks_size[ISP176x_BLOCK_NUM - 1]; + else + this_qtd_len = len; + + this_qtd_len = qtd_fill(qtd, buf, this_qtd_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) && maxpacketsize + && (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; + + 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)) { + if (qh->slot != -1) { + skip_map = isp1760_hcd_read(hcd, HC_ATL_PTD_SKIPMAP); + skip_map |= (1 << qh->slot); + isp1760_hcd_write(hcd, HC_ATL_PTD_SKIPMAP, skip_map); + ndelay(100); + } + priv->atl_slots[qh->slot].qh = NULL; + priv->atl_slots[qh->slot].qtd = NULL; + } else { + if (qh->slot != -1) { + skip_map = isp1760_hcd_read(hcd, HC_INT_PTD_SKIPMAP); + skip_map |= (1 << qh->slot); + isp1760_hcd_write(hcd, HC_INT_PTD_SKIPMAP, 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 status = 0; + int retval = 1; + unsigned long flags; + + /* if !PM, root hub timers won't get shut down ... */ + if (!HC_IS_RUNNING(hcd->state)) + return 0; + + /* init status to no-changes */ + buf[0] = 0; + + spin_lock_irqsave(&priv->lock, flags); + + if (isp1760_hcd_is_set(hcd, PORT_OWNER) && + isp1760_hcd_is_set(hcd, PORT_CSC)) { + isp1760_hcd_clear(hcd, PORT_CSC); + goto done; + } + + /* + * Return status information even for ports with OWNER set. + * Otherwise hub_wq wouldn't see the disconnect event when a + * high-speed device is switched over to the companion + * controller by the user. + */ + if (isp1760_hcd_is_set(hcd, PORT_CSC) || + (isp1760_hcd_is_set(hcd, PORT_RESUME) && + 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; + u16 temp; + + ports = isp1760_hcd_n_ports(priv->hcd); + + desc->bDescriptorType = USB_DT_HUB; + /* 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 = HUB_CHAR_INDV_PORT_OCPM; + if (isp1760_hcd_ppc_is_set(priv->hcd)) + /* per-port power control */ + temp |= HUB_CHAR_INDV_PORT_LPSM; + else + /* no power switching */ + temp |= HUB_CHAR_NO_LPSM; + desc->wHubCharacteristics = cpu_to_le16(temp); +} + +#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) + +static void check_reset_complete(struct usb_hcd *hcd, int index) +{ + if (!(isp1760_hcd_is_set(hcd, PORT_CONNECT))) + return; + + /* if reset finished and it's still not enabled -- handoff */ + if (!isp1760_hcd_is_set(hcd, PORT_PE)) { + dev_info(hcd->self.controller, + "port %d full speed --> companion\n", index + 1); + + isp1760_hcd_set(hcd, PORT_OWNER); + + isp1760_hcd_clear(hcd, PORT_CSC); + } else { + dev_info(hcd->self.controller, "port %d high speed\n", + index + 1); + } + + return; +} + +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); + u32 status; + unsigned long flags; + int retval = 0; + int ports; + + ports = isp1760_hcd_n_ports(hcd); + + /* + * 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--; + + /* + * Even if OWNER is set, so the port is owned by the + * companion controller, hub_wq needs to be able to clear + * the port-change status bits (especially + * USB_PORT_STAT_C_CONNECTION). + */ + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + isp1760_hcd_clear(hcd, PORT_PE); + break; + case USB_PORT_FEAT_C_ENABLE: + /* XXX error? */ + break; + case USB_PORT_FEAT_SUSPEND: + if (isp1760_hcd_is_set(hcd, PORT_RESET)) + goto error; + + if (isp1760_hcd_is_set(hcd, PORT_SUSPEND)) { + if (!isp1760_hcd_is_set(hcd, PORT_PE)) + goto error; + /* resume signaling for 20 msec */ + isp1760_hcd_clear(hcd, PORT_CSC); + isp1760_hcd_set(hcd, PORT_RESUME); + + priv->reset_done = jiffies + + msecs_to_jiffies(USB_RESUME_TIMEOUT); + } + break; + case USB_PORT_FEAT_C_SUSPEND: + /* we auto-clear this feature */ + break; + case USB_PORT_FEAT_POWER: + if (isp1760_hcd_ppc_is_set(hcd)) + isp1760_hcd_clear(hcd, PORT_POWER); + break; + case USB_PORT_FEAT_C_CONNECTION: + isp1760_hcd_set(hcd, 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; + } + isp1760_hcd_read(hcd, CMD_RUN); + 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; + + /* wPortChange bits */ + if (isp1760_hcd_is_set(hcd, PORT_CSC)) + status |= USB_PORT_STAT_C_CONNECTION << 16; + + /* whoever resumes must GetPortStatus to complete it!! */ + if (isp1760_hcd_is_set(hcd, 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 */ + isp1760_hcd_clear(hcd, PORT_CSC); + + retval = isp1760_hcd_clear_and_wait(hcd, + PORT_RESUME, 2000); + if (retval != 0) { + dev_err(hcd->self.controller, + "port %d resume error %d\n", + wIndex + 1, retval); + goto error; + } + } + } + + /* whoever resets must GetPortStatus to complete it!! */ + if (isp1760_hcd_is_set(hcd, PORT_RESET) && + time_after_eq(jiffies, priv->reset_done)) { + status |= USB_PORT_STAT_C_RESET << 16; + priv->reset_done = 0; + + /* force reset to complete */ + /* REVISIT: some hardware needs 550+ usec to clear + * this bit; seems too long to spin routinely... + */ + retval = isp1760_hcd_clear_and_wait(hcd, PORT_RESET, + 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 */ + check_reset_complete(hcd, wIndex); + } + /* + * Even if OWNER is set, there's no harm letting hub_wq + * see the wPortStatus values (they should all be 0 except + * for PORT_POWER anyway). + */ + + if (isp1760_hcd_is_set(hcd, PORT_OWNER)) + dev_err(hcd->self.controller, "PORT_OWNER is set\n"); + + if (isp1760_hcd_is_set(hcd, PORT_CONNECT)) { + status |= USB_PORT_STAT_CONNECTION; + /* status may be from integrated TT */ + status |= USB_PORT_STAT_HIGH_SPEED; + } + if (isp1760_hcd_is_set(hcd, PORT_PE)) + status |= USB_PORT_STAT_ENABLE; + if (isp1760_hcd_is_set(hcd, PORT_SUSPEND) && + isp1760_hcd_is_set(hcd, PORT_RESUME)) + status |= USB_PORT_STAT_SUSPEND; + if (isp1760_hcd_is_set(hcd, PORT_RESET)) + status |= USB_PORT_STAT_RESET; + if (isp1760_hcd_is_set(hcd, 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: + wIndex &= 0xff; + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + + if (isp1760_hcd_is_set(hcd, PORT_OWNER)) + break; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + isp1760_hcd_set(hcd, PORT_PE); + break; + + case USB_PORT_FEAT_SUSPEND: + if (!isp1760_hcd_is_set(hcd, PORT_PE) || + isp1760_hcd_is_set(hcd, PORT_RESET)) + goto error; + + isp1760_hcd_set(hcd, PORT_SUSPEND); + break; + case USB_PORT_FEAT_POWER: + if (isp1760_hcd_ppc_is_set(hcd)) + isp1760_hcd_set(hcd, PORT_POWER); + break; + case USB_PORT_FEAT_RESET: + if (isp1760_hcd_is_set(hcd, 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 ((isp1760_hcd_is_set(hcd, PORT_CONNECT) && + !isp1760_hcd_is_set(hcd, PORT_PE)) && + (isp1760_hcd_read(hcd, PORT_LSTATUS) == 1)) { + isp1760_hcd_set(hcd, PORT_OWNER); + } else { + isp1760_hcd_set(hcd, PORT_RESET); + isp1760_hcd_clear(hcd, 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); + } + break; + default: + goto error; + } + 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 = isp1760_hcd_read(hcd, HC_FRINDEX); + return (fr >> 3) % priv->periodic_size; +} + +static void isp1760_stop(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + timer_delete(&errata2_timer); + + isp1760_hub_control(hcd, ClearPortFeature, USB_PORT_FEAT_POWER, 1, + NULL, 0); + msleep(20); + + spin_lock_irq(&priv->lock); + ehci_reset(hcd); + /* Disable IRQ */ + isp1760_hcd_clear(hcd, HW_GLOBAL_INTR_EN); + spin_unlock_irq(&priv->lock); + + isp1760_hcd_clear(hcd, FLAG_CF); +} + +static void isp1760_shutdown(struct usb_hcd *hcd) +{ + isp1760_stop(hcd); + + isp1760_hcd_clear(hcd, HW_GLOBAL_INTR_EN); + + isp1760_hcd_clear(hcd, CMD_RUN); +} + +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 isp1760_init_kmem_once(void) +{ + urb_listitem_cachep = kmem_cache_create("isp1760_urb_listitem", + sizeof(struct urb_listitem), 0, SLAB_TEMPORARY, NULL); + + if (!urb_listitem_cachep) + return -ENOMEM; + + qtd_cachep = kmem_cache_create("isp1760_qtd", + sizeof(struct isp1760_qtd), 0, SLAB_TEMPORARY, NULL); + + if (!qtd_cachep) + goto destroy_urb_listitem; + + qh_cachep = kmem_cache_create("isp1760_qh", sizeof(struct isp1760_qh), + 0, SLAB_TEMPORARY, NULL); + + if (!qh_cachep) + goto destroy_qtd; + + return 0; + +destroy_qtd: + kmem_cache_destroy(qtd_cachep); + +destroy_urb_listitem: + kmem_cache_destroy(urb_listitem_cachep); + + return -ENOMEM; +} + +void isp1760_deinit_kmem_cache(void) +{ + kmem_cache_destroy(qtd_cachep); + kmem_cache_destroy(qh_cachep); + kmem_cache_destroy(urb_listitem_cachep); +} + +int isp1760_hcd_register(struct isp1760_hcd *priv, struct resource *mem, + int irq, unsigned long irqflags, + struct device *dev) +{ + const struct isp1760_memory_layout *mem_layout = priv->memory_layout; + struct usb_hcd *hcd; + int ret; + + hcd = usb_create_hcd(&isp1760_hc_driver, dev, dev_name(dev)); + if (!hcd) + return -ENOMEM; + + *(struct isp1760_hcd **)hcd->hcd_priv = priv; + + priv->hcd = hcd; + + priv->atl_slots = kzalloc_objs(struct isp1760_slotinfo, + mem_layout->slot_num); + if (!priv->atl_slots) { + ret = -ENOMEM; + goto put_hcd; + } + + priv->int_slots = kzalloc_objs(struct isp1760_slotinfo, + mem_layout->slot_num); + if (!priv->int_slots) { + ret = -ENOMEM; + goto free_atl_slots; + } + + init_memory(priv); + + hcd->irq = irq; + hcd->rsrc_start = mem->start; + hcd->rsrc_len = resource_size(mem); + + /* This driver doesn't support wakeup requests */ + hcd->cant_recv_wakeups = 1; + + ret = usb_add_hcd(hcd, irq, irqflags); + if (ret) + goto free_int_slots; + + device_wakeup_enable(hcd->self.controller); + + return 0; + +free_int_slots: + kfree(priv->int_slots); +free_atl_slots: + kfree(priv->atl_slots); +put_hcd: + usb_put_hcd(hcd); + return ret; +} + +void isp1760_hcd_unregister(struct isp1760_hcd *priv) +{ + if (!priv->hcd) + return; + + usb_remove_hcd(priv->hcd); + usb_put_hcd(priv->hcd); + kfree(priv->atl_slots); + kfree(priv->int_slots); +} diff --git a/drivers/usb/isp1760/isp1760-hcd.h b/drivers/usb/isp1760/isp1760-hcd.h new file mode 100644 index 000000000..ee3063a34 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-hcd.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ISP1760_HCD_H_ +#define _ISP1760_HCD_H_ + +#include +#include + +#include "isp1760-regs.h" + +struct isp1760_qh; +struct isp1760_qtd; +struct resource; +struct usb_hcd; + +struct isp1760_slotinfo { + struct isp1760_qh *qh; + struct isp1760_qtd *qtd; + unsigned long timestamp; +}; + +/* chip memory management */ +#define ISP176x_BLOCK_MAX (32 + 20 + 4) +#define ISP176x_BLOCK_NUM 3 + +struct isp1760_memory_layout { + unsigned int blocks[ISP176x_BLOCK_NUM]; + unsigned int blocks_size[ISP176x_BLOCK_NUM]; + + unsigned int slot_num; + unsigned int payload_blocks; + unsigned int payload_area_size; +}; + +struct isp1760_memory_chunk { + unsigned int start; + unsigned int size; + unsigned int free; +}; + +enum isp1760_queue_head_types { + QH_CONTROL, + QH_BULK, + QH_INTERRUPT, + QH_END +}; + +struct isp1760_hcd { + struct usb_hcd *hcd; + + void __iomem *base; + + struct regmap *regs; + struct regmap_field *fields[HC_FIELD_MAX]; + + bool is_isp1763; + const struct isp1760_memory_layout *memory_layout; + + spinlock_t lock; + struct isp1760_slotinfo *atl_slots; + int atl_done_map; + struct isp1760_slotinfo *int_slots; + int int_done_map; + struct isp1760_memory_chunk memory_pool[ISP176x_BLOCK_MAX]; + 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; +}; + +#ifdef CONFIG_USB_ISP1760_HCD +int isp1760_hcd_register(struct isp1760_hcd *priv, struct resource *mem, + int irq, unsigned long irqflags, struct device *dev); +void isp1760_hcd_unregister(struct isp1760_hcd *priv); + +int isp1760_init_kmem_once(void); +void isp1760_deinit_kmem_cache(void); +#else +static inline int isp1760_hcd_register(struct isp1760_hcd *priv, + struct resource *mem, + int irq, unsigned long irqflags, + struct device *dev) +{ + return 0; +} + +static inline void isp1760_hcd_unregister(struct isp1760_hcd *priv) +{ +} + +static inline int isp1760_init_kmem_once(void) +{ + return 0; +} + +static inline void isp1760_deinit_kmem_cache(void) +{ +} +#endif + +#endif /* _ISP1760_HCD_H_ */ diff --git a/drivers/usb/isp1760/isp1760-if.c b/drivers/usb/isp1760/isp1760-if.c new file mode 100644 index 000000000..a64190add --- /dev/null +++ b/drivers/usb/isp1760/isp1760-if.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + * Copyright 2021 Linaro, Rui Miguel Silva + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isp1760-core.h" +#include "isp1760-regs.h" + +#ifdef CONFIG_USB_PCI +#include +#endif + +#ifdef CONFIG_USB_PCI +static int isp1761_pci_init(struct pci_dev *dev) +{ + resource_size_t mem_start; + resource_size_t mem_length; + u8 __iomem *iobase; + u8 latency, limit; + int retry_count; + u32 reg_data; + + /* Grab the PLX PCI shared memory of the ISP 1761 we need */ + mem_start = pci_resource_start(dev, 3); + mem_length = pci_resource_len(dev, 3); + if (mem_length < 0xffff) { + printk(KERN_ERR "memory length for this resource is wrong\n"); + return -ENOMEM; + } + + if (!request_mem_region(mem_start, mem_length, "ISP-PCI")) { + printk(KERN_ERR "host controller already in use\n"); + return -EBUSY; + } + + /* map available memory */ + iobase = ioremap(mem_start, mem_length); + if (!iobase) { + printk(KERN_ERR "Error ioremap failed\n"); + release_mem_region(mem_start, mem_length); + return -ENOMEM; + } + + /* 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, iobase + ISP176x_HC_SCRATCH); + udelay(100); + reg_data = readl(iobase + ISP176x_HC_SCRATCH) & 0x0000ffff; + retry_count--; + } + + iounmap(iobase); + release_mem_region(mem_start, mem_length); + + /* 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); + return -ENOMEM; + } + + /* Grab the PLX PCI mem maped port start address we need */ + mem_start = pci_resource_start(dev, 0); + mem_length = pci_resource_len(dev, 0); + + if (!request_mem_region(mem_start, mem_length, "ISP1761 IO MEM")) { + printk(KERN_ERR "request region #1\n"); + return -EBUSY; + } + + iobase = ioremap(mem_start, mem_length); + if (!iobase) { + printk(KERN_ERR "ioremap #1\n"); + release_mem_region(mem_start, mem_length); + return -ENOMEM; + } + + /* 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); + + /* done with PLX IO access */ + iounmap(iobase); + release_mem_region(mem_start, mem_length); + + return 0; +} + +static int isp1761_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + unsigned int devflags = 0; + int ret; + + if (!dev->irq) + return -ENODEV; + + if (pci_enable_device(dev) < 0) + return -ENODEV; + + ret = isp1761_pci_init(dev); + if (ret < 0) + goto error; + + pci_set_master(dev); + + ret = isp1760_register(&dev->resource[3], dev->irq, 0, &dev->dev, + devflags); + if (ret < 0) + goto error; + + return 0; + +error: + pci_disable_device(dev); + return ret; +} + +static void isp1761_pci_remove(struct pci_dev *dev) +{ + isp1760_unregister(&dev->dev); + + 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) +{ + unsigned long irqflags; + unsigned int devflags = 0; + struct resource *mem_res; + int irq; + int ret; + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + irqflags = irq_get_trigger_type(irq); + + if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { + struct device_node *dp = pdev->dev.of_node; + u32 bus_width = 0; + + if (of_device_is_compatible(dp, "nxp,usb-isp1761")) + devflags |= ISP1760_FLAG_ISP1761; + + if (of_device_is_compatible(dp, "nxp,usb-isp1763")) + devflags |= ISP1760_FLAG_ISP1763; + + /* + * Some systems wire up only 8 of 16 data lines or + * 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; + else if (bus_width == 8) + devflags |= ISP1760_FLAG_BUS_WIDTH_8; + + if (usb_get_dr_mode(&pdev->dev) == USB_DR_MODE_PERIPHERAL) + devflags |= ISP1760_FLAG_PERIPHERAL_EN; + + if (of_property_read_bool(dp, "analog-oc")) + devflags |= ISP1760_FLAG_ANALOG_OC; + + if (of_property_read_bool(dp, "dack-polarity")) + devflags |= ISP1760_FLAG_DACK_POL_HIGH; + + if (of_property_read_bool(dp, "dreq-polarity")) + devflags |= ISP1760_FLAG_DREQ_POL_HIGH; + } else { + pr_err("isp1760: no platform data\n"); + return -ENXIO; + } + + ret = isp1760_register(mem_res, irq, irqflags, &pdev->dev, devflags); + if (ret < 0) + return ret; + + pr_info("ISP1760 USB device initialised\n"); + return 0; +} + +static void isp1760_plat_remove(struct platform_device *pdev) +{ + isp1760_unregister(&pdev->dev); +} + +#ifdef CONFIG_OF +static const struct of_device_id isp1760_of_match[] = { + { .compatible = "nxp,usb-isp1760", }, + { .compatible = "nxp,usb-isp1761", }, + { .compatible = "nxp,usb-isp1763", }, + { }, +}; +MODULE_DEVICE_TABLE(of, isp1760_of_match); +#endif + +static struct platform_driver isp1760_plat_driver = { + .probe = isp1760_plat_probe, + .remove = isp1760_plat_remove, + .driver = { + .name = "isp1760", + .of_match_table = of_match_ptr(isp1760_of_match), + }, +}; + +static int __init isp1760_init(void) +{ + int ret, any_ret = -ENODEV; + + isp1760_init_kmem_once(); + + ret = platform_driver_register(&isp1760_plat_driver); + if (!ret) + any_ret = 0; +#ifdef CONFIG_USB_PCI + ret = pci_register_driver(&isp1761_pci_driver); + if (!ret) + any_ret = 0; +#endif + + if (any_ret) + isp1760_deinit_kmem_cache(); + return any_ret; +} +module_init(isp1760_init); + +static void __exit isp1760_exit(void) +{ + platform_driver_unregister(&isp1760_plat_driver); +#ifdef CONFIG_USB_PCI + pci_unregister_driver(&isp1761_pci_driver); +#endif + isp1760_deinit_kmem_cache(); +} +module_exit(isp1760_exit); diff --git a/drivers/usb/isp1760/isp1760-regs.h b/drivers/usb/isp1760/isp1760-regs.h new file mode 100644 index 000000000..3a6751197 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-regs.h @@ -0,0 +1,308 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2021 Linaro, Rui Miguel Silva + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * Rui Miguel Silva + */ + +#ifndef _ISP176x_REGS_H_ +#define _ISP176x_REGS_H_ + +/* ----------------------------------------------------------------------------- + * Host Controller + */ + +/* ISP1760/31 */ +/* EHCI capability registers */ +#define ISP176x_HC_VERSION 0x002 +#define ISP176x_HC_HCSPARAMS 0x004 +#define ISP176x_HC_HCCPARAMS 0x008 + +/* EHCI operational registers */ +#define ISP176x_HC_USBCMD 0x020 +#define ISP176x_HC_USBSTS 0x024 +#define ISP176x_HC_FRINDEX 0x02c + +#define ISP176x_HC_CONFIGFLAG 0x060 +#define ISP176x_HC_PORTSC1 0x064 + +#define ISP176x_HC_ISO_PTD_DONEMAP 0x130 +#define ISP176x_HC_ISO_PTD_SKIPMAP 0x134 +#define ISP176x_HC_ISO_PTD_LASTPTD 0x138 +#define ISP176x_HC_INT_PTD_DONEMAP 0x140 +#define ISP176x_HC_INT_PTD_SKIPMAP 0x144 +#define ISP176x_HC_INT_PTD_LASTPTD 0x148 +#define ISP176x_HC_ATL_PTD_DONEMAP 0x150 +#define ISP176x_HC_ATL_PTD_SKIPMAP 0x154 +#define ISP176x_HC_ATL_PTD_LASTPTD 0x158 + +/* Configuration Register */ +#define ISP176x_HC_HW_MODE_CTRL 0x300 +#define ISP176x_HC_CHIP_ID 0x304 +#define ISP176x_HC_SCRATCH 0x308 +#define ISP176x_HC_RESET 0x30c +#define ISP176x_HC_BUFFER_STATUS 0x334 +#define ISP176x_HC_MEMORY 0x33c + +/* Interrupt Register */ +#define ISP176x_HC_INTERRUPT 0x310 +#define ISP176x_HC_INTERRUPT_ENABLE 0x314 +#define ISP176x_HC_ISO_IRQ_MASK_OR 0x318 +#define ISP176x_HC_INT_IRQ_MASK_OR 0x31c +#define ISP176x_HC_ATL_IRQ_MASK_OR 0x320 +#define ISP176x_HC_ISO_IRQ_MASK_AND 0x324 +#define ISP176x_HC_INT_IRQ_MASK_AND 0x328 +#define ISP176x_HC_ATL_IRQ_MASK_AND 0x32c + +#define ISP176x_HC_OTG_CTRL 0x374 +#define ISP176x_HC_OTG_CTRL_SET 0x374 +#define ISP176x_HC_OTG_CTRL_CLEAR 0x376 + +enum isp176x_host_controller_fields { + /* HC_PORTSC1 */ + PORT_OWNER, PORT_POWER, PORT_LSTATUS, PORT_RESET, PORT_SUSPEND, + PORT_RESUME, PORT_PE, PORT_CSC, PORT_CONNECT, + /* HC_HCSPARAMS */ + HCS_PPC, HCS_N_PORTS, + /* HC_HCCPARAMS */ + HCC_ISOC_CACHE, HCC_ISOC_THRES, + /* HC_USBCMD */ + CMD_LRESET, CMD_RESET, CMD_RUN, + /* HC_USBSTS */ + STS_PCD, + /* HC_FRINDEX */ + HC_FRINDEX, + /* HC_CONFIGFLAG */ + FLAG_CF, + /* ISO/INT/ATL PTD */ + HC_ISO_PTD_DONEMAP, HC_ISO_PTD_SKIPMAP, HC_ISO_PTD_LASTPTD, + HC_INT_PTD_DONEMAP, HC_INT_PTD_SKIPMAP, HC_INT_PTD_LASTPTD, + HC_ATL_PTD_DONEMAP, HC_ATL_PTD_SKIPMAP, HC_ATL_PTD_LASTPTD, + /* HC_HW_MODE_CTRL */ + ALL_ATX_RESET, HW_ANA_DIGI_OC, HW_DEV_DMA, HW_COMN_IRQ, HW_COMN_DMA, + HW_DATA_BUS_WIDTH, HW_DACK_POL_HIGH, HW_DREQ_POL_HIGH, HW_INTR_HIGH_ACT, + HW_INTF_LOCK, HW_INTR_EDGE_TRIG, HW_GLOBAL_INTR_EN, + /* HC_CHIP_ID */ + HC_CHIP_ID_HIGH, HC_CHIP_ID_LOW, HC_CHIP_REV, + /* HC_SCRATCH */ + HC_SCRATCH, + /* HC_RESET */ + SW_RESET_RESET_ATX, SW_RESET_RESET_HC, SW_RESET_RESET_ALL, + /* HC_BUFFER_STATUS */ + ISO_BUF_FILL, INT_BUF_FILL, ATL_BUF_FILL, + /* HC_MEMORY */ + MEM_BANK_SEL, MEM_START_ADDR, + /* HC_DATA */ + HC_DATA, + /* HC_INTERRUPT */ + HC_INTERRUPT, + /* HC_INTERRUPT_ENABLE */ + HC_INT_IRQ_ENABLE, HC_ATL_IRQ_ENABLE, + /* INTERRUPT MASKS */ + HC_ISO_IRQ_MASK_OR, HC_INT_IRQ_MASK_OR, HC_ATL_IRQ_MASK_OR, + HC_ISO_IRQ_MASK_AND, HC_INT_IRQ_MASK_AND, HC_ATL_IRQ_MASK_AND, + /* HW_OTG_CTRL_SET */ + HW_OTG_DISABLE, HW_SW_SEL_HC_DC, HW_VBUS_DRV, HW_SEL_CP_EXT, + HW_DM_PULLDOWN, HW_DP_PULLDOWN, HW_DP_PULLUP, HW_HC_2_DIS, + /* HW_OTG_CTRL_CLR */ + HW_OTG_DISABLE_CLEAR, HW_SW_SEL_HC_DC_CLEAR, HW_VBUS_DRV_CLEAR, + HW_SEL_CP_EXT_CLEAR, HW_DM_PULLDOWN_CLEAR, HW_DP_PULLDOWN_CLEAR, + HW_DP_PULLUP_CLEAR, HW_HC_2_DIS_CLEAR, + /* Last element */ + HC_FIELD_MAX, +}; + +/* ISP1763 */ +/* EHCI operational registers */ +#define ISP1763_HC_USBCMD 0x8c +#define ISP1763_HC_USBSTS 0x90 +#define ISP1763_HC_FRINDEX 0x98 + +#define ISP1763_HC_CONFIGFLAG 0x9c +#define ISP1763_HC_PORTSC1 0xa0 + +#define ISP1763_HC_ISO_PTD_DONEMAP 0xa4 +#define ISP1763_HC_ISO_PTD_SKIPMAP 0xa6 +#define ISP1763_HC_ISO_PTD_LASTPTD 0xa8 +#define ISP1763_HC_INT_PTD_DONEMAP 0xaa +#define ISP1763_HC_INT_PTD_SKIPMAP 0xac +#define ISP1763_HC_INT_PTD_LASTPTD 0xae +#define ISP1763_HC_ATL_PTD_DONEMAP 0xb0 +#define ISP1763_HC_ATL_PTD_SKIPMAP 0xb2 +#define ISP1763_HC_ATL_PTD_LASTPTD 0xb4 + +/* Configuration Register */ +#define ISP1763_HC_HW_MODE_CTRL 0xb6 +#define ISP1763_HC_CHIP_REV 0x70 +#define ISP1763_HC_CHIP_ID 0x72 +#define ISP1763_HC_SCRATCH 0x78 +#define ISP1763_HC_RESET 0xb8 +#define ISP1763_HC_BUFFER_STATUS 0xba +#define ISP1763_HC_MEMORY 0xc4 +#define ISP1763_HC_DATA 0xc6 + +/* Interrupt Register */ +#define ISP1763_HC_INTERRUPT 0xd4 +#define ISP1763_HC_INTERRUPT_ENABLE 0xd6 +#define ISP1763_HC_ISO_IRQ_MASK_OR 0xd8 +#define ISP1763_HC_INT_IRQ_MASK_OR 0xda +#define ISP1763_HC_ATL_IRQ_MASK_OR 0xdc +#define ISP1763_HC_ISO_IRQ_MASK_AND 0xde +#define ISP1763_HC_INT_IRQ_MASK_AND 0xe0 +#define ISP1763_HC_ATL_IRQ_MASK_AND 0xe2 + +#define ISP1763_HC_OTG_CTRL_SET 0xe4 +#define ISP1763_HC_OTG_CTRL_CLEAR 0xe6 + +/* ----------------------------------------------------------------------------- + * Peripheral Controller + */ + +#define DC_IEPTX(n) (1 << (11 + 2 * (n))) +#define DC_IEPRX(n) (1 << (10 + 2 * (n))) +#define DC_IEPRXTX(n) (3 << (10 + 2 * (n))) + +#define ISP176x_DC_CDBGMOD_ACK BIT(6) +#define ISP176x_DC_DDBGMODIN_ACK BIT(4) +#define ISP176x_DC_DDBGMODOUT_ACK BIT(2) + +#define ISP176x_DC_IEP0SETUP BIT(8) +#define ISP176x_DC_IEVBUS BIT(7) +#define ISP176x_DC_IEHS_STA BIT(5) +#define ISP176x_DC_IERESM BIT(4) +#define ISP176x_DC_IESUSP BIT(3) +#define ISP176x_DC_IEBRST BIT(0) + +#define ISP176x_HW_OTG_DISABLE_CLEAR BIT(26) +#define ISP176x_HW_SW_SEL_HC_DC_CLEAR BIT(23) +#define ISP176x_HW_VBUS_DRV_CLEAR BIT(20) +#define ISP176x_HW_SEL_CP_EXT_CLEAR BIT(19) +#define ISP176x_HW_DM_PULLDOWN_CLEAR BIT(18) +#define ISP176x_HW_DP_PULLDOWN_CLEAR BIT(17) +#define ISP176x_HW_DP_PULLUP_CLEAR BIT(16) +#define ISP176x_HW_OTG_DISABLE BIT(10) +#define ISP176x_HW_SW_SEL_HC_DC BIT(7) +#define ISP176x_HW_VBUS_DRV BIT(4) +#define ISP176x_HW_SEL_CP_EXT BIT(3) +#define ISP176x_HW_DM_PULLDOWN BIT(2) +#define ISP176x_HW_DP_PULLDOWN BIT(1) +#define ISP176x_HW_DP_PULLUP BIT(0) + +#define ISP176x_DC_ENDPTYP_ISOC 0x01 +#define ISP176x_DC_ENDPTYP_BULK 0x02 +#define ISP176x_DC_ENDPTYP_INTERRUPT 0x03 + +/* Initialization Registers */ +#define ISP176x_DC_ADDRESS 0x0200 +#define ISP176x_DC_MODE 0x020c +#define ISP176x_DC_INTCONF 0x0210 +#define ISP176x_DC_DEBUG 0x0212 +#define ISP176x_DC_INTENABLE 0x0214 + +/* Data Flow Registers */ +#define ISP176x_DC_EPMAXPKTSZ 0x0204 +#define ISP176x_DC_EPTYPE 0x0208 + +#define ISP176x_DC_BUFLEN 0x021c +#define ISP176x_DC_BUFSTAT 0x021e +#define ISP176x_DC_DATAPORT 0x0220 + +#define ISP176x_DC_CTRLFUNC 0x0228 +#define ISP176x_DC_EPINDEX 0x022c + +/* DMA Registers */ +#define ISP176x_DC_DMACMD 0x0230 +#define ISP176x_DC_DMATXCOUNT 0x0234 +#define ISP176x_DC_DMACONF 0x0238 +#define ISP176x_DC_DMAHW 0x023c +#define ISP176x_DC_DMAINTREASON 0x0250 +#define ISP176x_DC_DMAINTEN 0x0254 +#define ISP176x_DC_DMAEP 0x0258 +#define ISP176x_DC_DMABURSTCOUNT 0x0264 + +/* General Registers */ +#define ISP176x_DC_INTERRUPT 0x0218 +#define ISP176x_DC_CHIPID 0x0270 +#define ISP176x_DC_FRAMENUM 0x0274 +#define ISP176x_DC_SCRATCH 0x0278 +#define ISP176x_DC_UNLOCKDEV 0x027c +#define ISP176x_DC_INTPULSEWIDTH 0x0280 +#define ISP176x_DC_TESTMODE 0x0284 + +enum isp176x_device_controller_fields { + /* DC_ADDRESS */ + DC_DEVEN, DC_DEVADDR, + /* DC_MODE */ + DC_VBUSSTAT, DC_SFRESET, DC_GLINTENA, + /* DC_INTCONF */ + DC_CDBGMOD_ACK, DC_DDBGMODIN_ACK, DC_DDBGMODOUT_ACK, DC_INTPOL, + /* DC_INTENABLE */ + DC_IEPRXTX_7, DC_IEPRXTX_6, DC_IEPRXTX_5, DC_IEPRXTX_4, DC_IEPRXTX_3, + DC_IEPRXTX_2, DC_IEPRXTX_1, DC_IEPRXTX_0, + DC_IEP0SETUP, DC_IEVBUS, DC_IEHS_STA, DC_IERESM, DC_IESUSP, DC_IEBRST, + /* DC_EPINDEX */ + DC_EP0SETUP, DC_ENDPIDX, DC_EPDIR, + /* DC_CTRLFUNC */ + DC_CLBUF, DC_VENDP, DC_DSEN, DC_STATUS, DC_STALL, + /* DC_BUFLEN */ + DC_BUFLEN, + /* DC_EPMAXPKTSZ */ + DC_FFOSZ, + /* DC_EPTYPE */ + DC_EPENABLE, DC_ENDPTYP, + /* DC_FRAMENUM */ + DC_FRAMENUM, DC_UFRAMENUM, + /* DC_CHIP_ID */ + DC_CHIP_ID_HIGH, DC_CHIP_ID_LOW, + /* DC_SCRATCH */ + DC_SCRATCH, + /* Last element */ + DC_FIELD_MAX, +}; + +/* ISP1763 */ +/* Initialization Registers */ +#define ISP1763_DC_ADDRESS 0x00 +#define ISP1763_DC_MODE 0x0c +#define ISP1763_DC_INTCONF 0x10 +#define ISP1763_DC_INTENABLE 0x14 + +/* Data Flow Registers */ +#define ISP1763_DC_EPMAXPKTSZ 0x04 +#define ISP1763_DC_EPTYPE 0x08 + +#define ISP1763_DC_BUFLEN 0x1c +#define ISP1763_DC_BUFSTAT 0x1e +#define ISP1763_DC_DATAPORT 0x20 + +#define ISP1763_DC_CTRLFUNC 0x28 +#define ISP1763_DC_EPINDEX 0x2c + +/* DMA Registers */ +#define ISP1763_DC_DMACMD 0x30 +#define ISP1763_DC_DMATXCOUNT 0x34 +#define ISP1763_DC_DMACONF 0x38 +#define ISP1763_DC_DMAHW 0x3c +#define ISP1763_DC_DMAINTREASON 0x50 +#define ISP1763_DC_DMAINTEN 0x54 +#define ISP1763_DC_DMAEP 0x58 +#define ISP1763_DC_DMABURSTCOUNT 0x64 + +/* General Registers */ +#define ISP1763_DC_INTERRUPT 0x18 +#define ISP1763_DC_CHIPID_LOW 0x70 +#define ISP1763_DC_CHIPID_HIGH 0x72 +#define ISP1763_DC_FRAMENUM 0x74 +#define ISP1763_DC_SCRATCH 0x78 +#define ISP1763_DC_UNLOCKDEV 0x7c +#define ISP1763_DC_INTPULSEWIDTH 0x80 +#define ISP1763_DC_TESTMODE 0x84 + +#endif diff --git a/drivers/usb/isp1760/isp1760-udc.c b/drivers/usb/isp1760/isp1760-udc.c new file mode 100644 index 000000000..e45a54c4a --- /dev/null +++ b/drivers/usb/isp1760/isp1760-udc.c @@ -0,0 +1,1600 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the NXP ISP1761 device controller + * + * Copyright 2021 Linaro, Rui Miguel Silva + * Copyright 2014 Ideas on Board Oy + * + * Contacts: + * Laurent Pinchart + * Rui Miguel Silva + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isp1760-core.h" +#include "isp1760-regs.h" +#include "isp1760-udc.h" + +#define ISP1760_VBUS_POLL_INTERVAL msecs_to_jiffies(500) + +struct isp1760_request { + struct usb_request req; + struct list_head queue; + struct isp1760_ep *ep; + unsigned int packet_size; +}; + +static inline struct isp1760_udc *gadget_to_udc(struct usb_gadget *gadget) +{ + return container_of(gadget, struct isp1760_udc, gadget); +} + +static inline struct isp1760_ep *ep_to_udc_ep(struct usb_ep *ep) +{ + return container_of(ep, struct isp1760_ep, ep); +} + +static inline struct isp1760_request *req_to_udc_req(struct usb_request *req) +{ + return container_of(req, struct isp1760_request, req); +} + +static u32 isp1760_udc_read(struct isp1760_udc *udc, u16 field) +{ + return isp1760_field_read(udc->fields, field); +} + +static void isp1760_udc_write(struct isp1760_udc *udc, u16 field, u32 val) +{ + isp1760_field_write(udc->fields, field, val); +} + +static u32 isp1760_udc_read_raw(struct isp1760_udc *udc, u16 reg) +{ + __le32 val; + + regmap_raw_read(udc->regs, reg, &val, 4); + + return le32_to_cpu(val); +} + +static u16 isp1760_udc_read_raw16(struct isp1760_udc *udc, u16 reg) +{ + __le16 val; + + regmap_raw_read(udc->regs, reg, &val, 2); + + return le16_to_cpu(val); +} + +static void isp1760_udc_write_raw(struct isp1760_udc *udc, u16 reg, u32 val) +{ + __le32 val_le = cpu_to_le32(val); + + regmap_raw_write(udc->regs, reg, &val_le, 4); +} + +static void isp1760_udc_write_raw16(struct isp1760_udc *udc, u16 reg, u16 val) +{ + __le16 val_le = cpu_to_le16(val); + + regmap_raw_write(udc->regs, reg, &val_le, 2); +} + +static void isp1760_udc_set(struct isp1760_udc *udc, u32 field) +{ + isp1760_udc_write(udc, field, 0xFFFFFFFF); +} + +static void isp1760_udc_clear(struct isp1760_udc *udc, u32 field) +{ + isp1760_udc_write(udc, field, 0); +} + +static bool isp1760_udc_is_set(struct isp1760_udc *udc, u32 field) +{ + return !!isp1760_udc_read(udc, field); +} +/* ----------------------------------------------------------------------------- + * Endpoint Management + */ + +static struct isp1760_ep *isp1760_udc_find_ep(struct isp1760_udc *udc, + u16 index) +{ + unsigned int i; + + if (index == 0) + return &udc->ep[0]; + + for (i = 1; i < ARRAY_SIZE(udc->ep); ++i) { + if (udc->ep[i].addr == index) + return udc->ep[i].desc ? &udc->ep[i] : NULL; + } + + return NULL; +} + +static void __isp1760_udc_select_ep(struct isp1760_udc *udc, + struct isp1760_ep *ep, int dir) +{ + isp1760_udc_write(udc, DC_ENDPIDX, ep->addr & USB_ENDPOINT_NUMBER_MASK); + + if (dir == USB_DIR_IN) + isp1760_udc_set(udc, DC_EPDIR); + else + isp1760_udc_clear(udc, DC_EPDIR); +} + +/** + * isp1760_udc_select_ep - Select an endpoint for register access + * @ep: The endpoint + * @udc: Reference to the device controller + * + * The ISP1761 endpoint registers are banked. This function selects the target + * endpoint for banked register access. The selection remains valid until the + * next call to this function, the next direct access to the EPINDEX register + * or the next reset, whichever comes first. + * + * Called with the UDC spinlock held. + */ +static void isp1760_udc_select_ep(struct isp1760_udc *udc, + struct isp1760_ep *ep) +{ + __isp1760_udc_select_ep(udc, ep, ep->addr & USB_ENDPOINT_DIR_MASK); +} + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_ctrl_send_status(struct isp1760_ep *ep, int dir) +{ + struct isp1760_udc *udc = ep->udc; + + /* + * Proceed to the status stage. The status stage data packet flows in + * the direction opposite to the data stage data packets, we thus need + * to select the OUT/IN endpoint for IN/OUT transfers. + */ + if (dir == USB_DIR_IN) + isp1760_udc_clear(udc, DC_EPDIR); + else + isp1760_udc_set(udc, DC_EPDIR); + + isp1760_udc_write(udc, DC_ENDPIDX, 1); + isp1760_udc_set(udc, DC_STATUS); + + /* + * The hardware will terminate the request automatically and go back to + * the setup stage without notifying us. + */ + udc->ep0_state = ISP1760_CTRL_SETUP; +} + +/* Called without the UDC spinlock held. */ +static void isp1760_udc_request_complete(struct isp1760_ep *ep, + struct isp1760_request *req, + int status) +{ + struct isp1760_udc *udc = ep->udc; + unsigned long flags; + + dev_dbg(ep->udc->isp->dev, "completing request %p with status %d\n", + req, status); + + req->ep = NULL; + req->req.status = status; + req->req.complete(&ep->ep, &req->req); + + spin_lock_irqsave(&udc->lock, flags); + + /* + * When completing control OUT requests, move to the status stage after + * calling the request complete callback. This gives the gadget an + * opportunity to stall the control transfer if needed. + */ + if (status == 0 && ep->addr == 0 && udc->ep0_dir == USB_DIR_OUT) + isp1760_udc_ctrl_send_status(ep, USB_DIR_OUT); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void isp1760_udc_ctrl_send_stall(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + unsigned long flags; + + dev_dbg(ep->udc->isp->dev, "%s(ep%02x)\n", __func__, ep->addr); + + spin_lock_irqsave(&udc->lock, flags); + + /* Stall both the IN and OUT endpoints. */ + __isp1760_udc_select_ep(udc, ep, USB_DIR_OUT); + isp1760_udc_set(udc, DC_STALL); + __isp1760_udc_select_ep(udc, ep, USB_DIR_IN); + isp1760_udc_set(udc, DC_STALL); + + /* A protocol stall completes the control transaction. */ + udc->ep0_state = ISP1760_CTRL_SETUP; + + spin_unlock_irqrestore(&udc->lock, flags); +} + +/* ----------------------------------------------------------------------------- + * Data Endpoints + */ + +/* Called with the UDC spinlock held. */ +static bool isp1760_udc_receive(struct isp1760_ep *ep, + struct isp1760_request *req) +{ + struct isp1760_udc *udc = ep->udc; + unsigned int len; + u32 *buf; + int i; + + isp1760_udc_select_ep(udc, ep); + len = isp1760_udc_read(udc, DC_BUFLEN); + + dev_dbg(udc->isp->dev, "%s: received %u bytes (%u/%u done)\n", + __func__, len, req->req.actual, req->req.length); + + len = min(len, req->req.length - req->req.actual); + + if (!len) { + /* + * There's no data to be read from the FIFO, acknowledge the RX + * interrupt by clearing the buffer. + * + * TODO: What if another packet arrives in the meantime ? The + * datasheet doesn't clearly document how this should be + * handled. + */ + isp1760_udc_set(udc, DC_CLBUF); + return false; + } + + buf = req->req.buf + req->req.actual; + + /* + * Make sure not to read more than one extra byte, otherwise data from + * the next packet might be removed from the FIFO. + */ + for (i = len; i > 2; i -= 4, ++buf) + *buf = isp1760_udc_read_raw(udc, ISP176x_DC_DATAPORT); + if (i > 0) + *(u16 *)buf = isp1760_udc_read_raw16(udc, ISP176x_DC_DATAPORT); + + req->req.actual += len; + + /* + * TODO: The short_not_ok flag isn't supported yet, but isn't used by + * any gadget driver either. + */ + + dev_dbg(udc->isp->dev, + "%s: req %p actual/length %u/%u maxpacket %u packet size %u\n", + __func__, req, req->req.actual, req->req.length, ep->maxpacket, + len); + + ep->rx_pending = false; + + /* + * Complete the request if all data has been received or if a short + * packet has been received. + */ + if (req->req.actual == req->req.length || len < ep->maxpacket) { + list_del(&req->queue); + return true; + } + + return false; +} + +static void isp1760_udc_transmit(struct isp1760_ep *ep, + struct isp1760_request *req) +{ + struct isp1760_udc *udc = ep->udc; + u32 *buf = req->req.buf + req->req.actual; + int i; + + req->packet_size = min(req->req.length - req->req.actual, + ep->maxpacket); + + dev_dbg(udc->isp->dev, "%s: transferring %u bytes (%u/%u done)\n", + __func__, req->packet_size, req->req.actual, + req->req.length); + + __isp1760_udc_select_ep(udc, ep, USB_DIR_IN); + + if (req->packet_size) + isp1760_udc_write(udc, DC_BUFLEN, req->packet_size); + + /* + * Make sure not to write more than one extra byte, otherwise extra data + * will stay in the FIFO and will be transmitted during the next control + * request. The endpoint control CLBUF bit is supposed to allow flushing + * the FIFO for this kind of conditions, but doesn't seem to work. + */ + for (i = req->packet_size; i > 2; i -= 4, ++buf) + isp1760_udc_write_raw(udc, ISP176x_DC_DATAPORT, *buf); + if (i > 0) + isp1760_udc_write_raw16(udc, ISP176x_DC_DATAPORT, *(u16 *)buf); + + if (ep->addr == 0) + isp1760_udc_set(udc, DC_DSEN); + if (!req->packet_size) + isp1760_udc_set(udc, DC_VENDP); +} + +static void isp1760_ep_rx_ready(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + struct isp1760_request *req; + bool complete; + + spin_lock(&udc->lock); + + if (ep->addr == 0 && udc->ep0_state != ISP1760_CTRL_DATA_OUT) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: invalid ep0 state %u\n", __func__, + udc->ep0_state); + return; + } + + if (ep->addr != 0 && !ep->desc) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x is disabled\n", __func__, + ep->addr); + return; + } + + if (list_empty(&ep->queue)) { + ep->rx_pending = true; + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x (%p) has no request queued\n", + __func__, ep->addr, ep); + return; + } + + req = list_first_entry(&ep->queue, struct isp1760_request, + queue); + complete = isp1760_udc_receive(ep, req); + + spin_unlock(&udc->lock); + + if (complete) + isp1760_udc_request_complete(ep, req, 0); +} + +static void isp1760_ep_tx_complete(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + struct isp1760_request *complete = NULL; + struct isp1760_request *req; + bool need_zlp; + + spin_lock(&udc->lock); + + if (ep->addr == 0 && udc->ep0_state != ISP1760_CTRL_DATA_IN) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "TX IRQ: invalid endpoint state %u\n", + udc->ep0_state); + return; + } + + if (list_empty(&ep->queue)) { + /* + * This can happen for the control endpoint when the reply to + * the GET_STATUS IN control request is sent directly by the + * setup IRQ handler. Just proceed to the status stage. + */ + if (ep->addr == 0) { + isp1760_udc_ctrl_send_status(ep, USB_DIR_IN); + spin_unlock(&udc->lock); + return; + } + + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x has no request queued\n", + __func__, ep->addr); + return; + } + + req = list_first_entry(&ep->queue, struct isp1760_request, + queue); + req->req.actual += req->packet_size; + + need_zlp = req->req.actual == req->req.length && + !(req->req.length % ep->maxpacket) && + req->packet_size && req->req.zero; + + dev_dbg(udc->isp->dev, + "TX IRQ: req %p actual/length %u/%u maxpacket %u packet size %u zero %u need zlp %u\n", + req, req->req.actual, req->req.length, ep->maxpacket, + req->packet_size, req->req.zero, need_zlp); + + /* + * Complete the request if all data has been sent and we don't need to + * transmit a zero length packet. + */ + if (req->req.actual == req->req.length && !need_zlp) { + complete = req; + list_del(&req->queue); + + if (ep->addr == 0) + isp1760_udc_ctrl_send_status(ep, USB_DIR_IN); + + if (!list_empty(&ep->queue)) + req = list_first_entry(&ep->queue, + struct isp1760_request, queue); + else + req = NULL; + } + + /* + * Transmit the next packet or start the next request, if any. + * + * TODO: If the endpoint is stalled the next request shouldn't be + * started, but what about the next packet ? + */ + if (req) + isp1760_udc_transmit(ep, req); + + spin_unlock(&udc->lock); + + if (complete) + isp1760_udc_request_complete(ep, complete, 0); +} + +static int __isp1760_udc_set_halt(struct isp1760_ep *ep, bool halt) +{ + struct isp1760_udc *udc = ep->udc; + + dev_dbg(udc->isp->dev, "%s: %s halt on ep%02x\n", __func__, + halt ? "set" : "clear", ep->addr); + + if (ep->desc && usb_endpoint_xfer_isoc(ep->desc)) { + dev_dbg(udc->isp->dev, "%s: ep%02x is isochronous\n", __func__, + ep->addr); + return -EINVAL; + } + + isp1760_udc_select_ep(udc, ep); + + if (halt) + isp1760_udc_set(udc, DC_STALL); + else + isp1760_udc_clear(udc, DC_STALL); + + if (ep->addr == 0) { + /* When halting the control endpoint, stall both IN and OUT. */ + __isp1760_udc_select_ep(udc, ep, USB_DIR_IN); + if (halt) + isp1760_udc_set(udc, DC_STALL); + else + isp1760_udc_clear(udc, DC_STALL); + } else if (!halt) { + /* Reset the data PID by cycling the endpoint enable bit. */ + isp1760_udc_clear(udc, DC_EPENABLE); + isp1760_udc_set(udc, DC_EPENABLE); + + /* + * Disabling the endpoint emptied the transmit FIFO, fill it + * again if a request is pending. + * + * TODO: Does the gadget framework require synchronizatino with + * the TX IRQ handler ? + */ + if ((ep->addr & USB_DIR_IN) && !list_empty(&ep->queue)) { + struct isp1760_request *req; + + req = list_first_entry(&ep->queue, + struct isp1760_request, queue); + isp1760_udc_transmit(ep, req); + } + } + + ep->halted = halt; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Control Endpoint + */ + +static int isp1760_udc_get_status(struct isp1760_udc *udc, + const struct usb_ctrlrequest *req) +{ + struct isp1760_ep *ep; + u16 status; + + if (req->wLength != cpu_to_le16(2) || req->wValue != cpu_to_le16(0)) + return -EINVAL; + + switch (req->bRequestType) { + case USB_DIR_IN | USB_RECIP_DEVICE: + status = udc->devstatus; + break; + + case USB_DIR_IN | USB_RECIP_INTERFACE: + status = 0; + break; + + case USB_DIR_IN | USB_RECIP_ENDPOINT: + ep = isp1760_udc_find_ep(udc, le16_to_cpu(req->wIndex)); + if (!ep) + return -EINVAL; + + status = 0; + if (ep->halted) + status |= 1 << USB_ENDPOINT_HALT; + break; + + default: + return -EINVAL; + } + + isp1760_udc_set(udc, DC_EPDIR); + isp1760_udc_write(udc, DC_ENDPIDX, 1); + + isp1760_udc_write(udc, DC_BUFLEN, 2); + + isp1760_udc_write_raw16(udc, ISP176x_DC_DATAPORT, status); + + isp1760_udc_set(udc, DC_DSEN); + + dev_dbg(udc->isp->dev, "%s: status 0x%04x\n", __func__, status); + + return 0; +} + +static int isp1760_udc_set_address(struct isp1760_udc *udc, u16 addr) +{ + if (addr > 127) { + dev_dbg(udc->isp->dev, "invalid device address %u\n", addr); + return -EINVAL; + } + + if (udc->gadget.state != USB_STATE_DEFAULT && + udc->gadget.state != USB_STATE_ADDRESS) { + dev_dbg(udc->isp->dev, "can't set address in state %u\n", + udc->gadget.state); + return -EINVAL; + } + + usb_gadget_set_state(&udc->gadget, addr ? USB_STATE_ADDRESS : + USB_STATE_DEFAULT); + + isp1760_udc_write(udc, DC_DEVADDR, addr); + isp1760_udc_set(udc, DC_DEVEN); + + spin_lock(&udc->lock); + isp1760_udc_ctrl_send_status(&udc->ep[0], USB_DIR_OUT); + spin_unlock(&udc->lock); + + return 0; +} + +static bool isp1760_ep0_setup_standard(struct isp1760_udc *udc, + struct usb_ctrlrequest *req) +{ + bool stall; + + switch (req->bRequest) { + case USB_REQ_GET_STATUS: + return isp1760_udc_get_status(udc, req); + + case USB_REQ_CLEAR_FEATURE: + switch (req->bRequestType) { + case USB_DIR_OUT | USB_RECIP_DEVICE: { + /* TODO: Handle remote wakeup feature. */ + return true; + } + + case USB_DIR_OUT | USB_RECIP_ENDPOINT: { + u16 index = le16_to_cpu(req->wIndex); + struct isp1760_ep *ep; + + if (req->wLength != cpu_to_le16(0) || + req->wValue != cpu_to_le16(USB_ENDPOINT_HALT)) + return true; + + ep = isp1760_udc_find_ep(udc, index); + if (!ep) + return true; + + spin_lock(&udc->lock); + + /* + * If the endpoint is wedged only the gadget can clear + * the halt feature. Pretend success in that case, but + * keep the endpoint halted. + */ + if (!ep->wedged) + stall = __isp1760_udc_set_halt(ep, false); + else + stall = false; + + if (!stall) + isp1760_udc_ctrl_send_status(&udc->ep[0], + USB_DIR_OUT); + + spin_unlock(&udc->lock); + return stall; + } + + default: + return true; + } + break; + + case USB_REQ_SET_FEATURE: + switch (req->bRequestType) { + case USB_DIR_OUT | USB_RECIP_DEVICE: { + /* TODO: Handle remote wakeup and test mode features */ + return true; + } + + case USB_DIR_OUT | USB_RECIP_ENDPOINT: { + u16 index = le16_to_cpu(req->wIndex); + struct isp1760_ep *ep; + + if (req->wLength != cpu_to_le16(0) || + req->wValue != cpu_to_le16(USB_ENDPOINT_HALT)) + return true; + + ep = isp1760_udc_find_ep(udc, index); + if (!ep) + return true; + + spin_lock(&udc->lock); + + stall = __isp1760_udc_set_halt(ep, true); + if (!stall) + isp1760_udc_ctrl_send_status(&udc->ep[0], + USB_DIR_OUT); + + spin_unlock(&udc->lock); + return stall; + } + + default: + return true; + } + break; + + case USB_REQ_SET_ADDRESS: + if (req->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + return true; + + return isp1760_udc_set_address(udc, le16_to_cpu(req->wValue)); + + case USB_REQ_SET_CONFIGURATION: + if (req->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + return true; + + if (udc->gadget.state != USB_STATE_ADDRESS && + udc->gadget.state != USB_STATE_CONFIGURED) + return true; + + stall = udc->driver->setup(&udc->gadget, req) < 0; + if (stall) + return true; + + usb_gadget_set_state(&udc->gadget, req->wValue ? + USB_STATE_CONFIGURED : USB_STATE_ADDRESS); + + /* + * SET_CONFIGURATION (and SET_INTERFACE) must reset the halt + * feature on all endpoints. There is however no need to do so + * explicitly here as the gadget driver will disable and + * reenable endpoints, clearing the halt feature. + */ + return false; + + default: + return udc->driver->setup(&udc->gadget, req) < 0; + } +} + +static void isp1760_ep0_setup(struct isp1760_udc *udc) +{ + union { + struct usb_ctrlrequest r; + u32 data[2]; + } req; + unsigned int count; + bool stall = false; + + spin_lock(&udc->lock); + + isp1760_udc_set(udc, DC_EP0SETUP); + + count = isp1760_udc_read(udc, DC_BUFLEN); + if (count != sizeof(req)) { + spin_unlock(&udc->lock); + + dev_err(udc->isp->dev, "invalid length %u for setup packet\n", + count); + + isp1760_udc_ctrl_send_stall(&udc->ep[0]); + return; + } + + req.data[0] = isp1760_udc_read_raw(udc, ISP176x_DC_DATAPORT); + req.data[1] = isp1760_udc_read_raw(udc, ISP176x_DC_DATAPORT); + + if (udc->ep0_state != ISP1760_CTRL_SETUP) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "unexpected SETUP packet\n"); + return; + } + + /* Move to the data stage. */ + if (!req.r.wLength) + udc->ep0_state = ISP1760_CTRL_STATUS; + else if (req.r.bRequestType & USB_DIR_IN) + udc->ep0_state = ISP1760_CTRL_DATA_IN; + else + udc->ep0_state = ISP1760_CTRL_DATA_OUT; + + udc->ep0_dir = req.r.bRequestType & USB_DIR_IN; + udc->ep0_length = le16_to_cpu(req.r.wLength); + + spin_unlock(&udc->lock); + + dev_dbg(udc->isp->dev, + "%s: bRequestType 0x%02x bRequest 0x%02x wValue 0x%04x wIndex 0x%04x wLength 0x%04x\n", + __func__, req.r.bRequestType, req.r.bRequest, + le16_to_cpu(req.r.wValue), le16_to_cpu(req.r.wIndex), + le16_to_cpu(req.r.wLength)); + + if ((req.r.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + stall = isp1760_ep0_setup_standard(udc, &req.r); + else + stall = udc->driver->setup(&udc->gadget, &req.r) < 0; + + if (stall) + isp1760_udc_ctrl_send_stall(&udc->ep[0]); +} + +/* ----------------------------------------------------------------------------- + * Gadget Endpoint Operations + */ + +static int isp1760_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + unsigned int type; + + dev_dbg(uep->udc->isp->dev, "%s\n", __func__); + + /* + * Validate the descriptor. The control endpoint can't be enabled + * manually. + */ + if (desc->bDescriptorType != USB_DT_ENDPOINT || + desc->bEndpointAddress == 0 || + desc->bEndpointAddress != uep->addr || + le16_to_cpu(desc->wMaxPacketSize) > ep->maxpacket) { + dev_dbg(udc->isp->dev, + "%s: invalid descriptor type %u addr %02x ep addr %02x max packet size %u/%u\n", + __func__, desc->bDescriptorType, + desc->bEndpointAddress, uep->addr, + le16_to_cpu(desc->wMaxPacketSize), ep->maxpacket); + return -EINVAL; + } + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_ISOC: + type = ISP176x_DC_ENDPTYP_ISOC; + break; + case USB_ENDPOINT_XFER_BULK: + type = ISP176x_DC_ENDPTYP_BULK; + break; + case USB_ENDPOINT_XFER_INT: + type = ISP176x_DC_ENDPTYP_INTERRUPT; + break; + case USB_ENDPOINT_XFER_CONTROL: + default: + dev_dbg(udc->isp->dev, "%s: control endpoints unsupported\n", + __func__); + return -EINVAL; + } + + spin_lock_irqsave(&udc->lock, flags); + + uep->desc = desc; + uep->maxpacket = le16_to_cpu(desc->wMaxPacketSize); + uep->rx_pending = false; + uep->halted = false; + uep->wedged = false; + + isp1760_udc_select_ep(udc, uep); + + isp1760_udc_write(udc, DC_FFOSZ, uep->maxpacket); + isp1760_udc_write(udc, DC_BUFLEN, uep->maxpacket); + + isp1760_udc_write(udc, DC_ENDPTYP, type); + isp1760_udc_set(udc, DC_EPENABLE); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int isp1760_ep_disable(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + struct isp1760_request *req, *nreq; + LIST_HEAD(req_list); + unsigned long flags; + + dev_dbg(udc->isp->dev, "%s\n", __func__); + + spin_lock_irqsave(&udc->lock, flags); + + if (!uep->desc) { + dev_dbg(udc->isp->dev, "%s: endpoint not enabled\n", __func__); + spin_unlock_irqrestore(&udc->lock, flags); + return -EINVAL; + } + + uep->desc = NULL; + uep->maxpacket = 0; + + isp1760_udc_select_ep(udc, uep); + isp1760_udc_clear(udc, DC_EPENABLE); + isp1760_udc_clear(udc, DC_ENDPTYP); + + /* TODO Synchronize with the IRQ handler */ + + list_splice_init(&uep->queue, &req_list); + + spin_unlock_irqrestore(&udc->lock, flags); + + list_for_each_entry_safe(req, nreq, &req_list, queue) { + list_del(&req->queue); + isp1760_udc_request_complete(uep, req, -ESHUTDOWN); + } + + return 0; +} + +static struct usb_request *isp1760_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct isp1760_request *req; + + req = kzalloc_obj(*req, gfp_flags); + if (!req) + return NULL; + + return &req->req; +} + +static void isp1760_ep_free_request(struct usb_ep *ep, struct usb_request *_req) +{ + struct isp1760_request *req = req_to_udc_req(_req); + + kfree(req); +} + +static int isp1760_ep_queue(struct usb_ep *ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct isp1760_request *req = req_to_udc_req(_req); + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + bool complete = false; + unsigned long flags; + int ret = 0; + + _req->status = -EINPROGRESS; + _req->actual = 0; + + spin_lock_irqsave(&udc->lock, flags); + + dev_dbg(udc->isp->dev, + "%s: req %p (%u bytes%s) ep %p(0x%02x)\n", __func__, _req, + _req->length, _req->zero ? " (zlp)" : "", uep, uep->addr); + + req->ep = uep; + + if (uep->addr == 0) { + if (_req->length != udc->ep0_length && + udc->ep0_state != ISP1760_CTRL_DATA_IN) { + dev_dbg(udc->isp->dev, + "%s: invalid length %u for req %p\n", + __func__, _req->length, req); + ret = -EINVAL; + goto done; + } + + switch (udc->ep0_state) { + case ISP1760_CTRL_DATA_IN: + dev_dbg(udc->isp->dev, "%s: transmitting req %p\n", + __func__, req); + + list_add_tail(&req->queue, &uep->queue); + isp1760_udc_transmit(uep, req); + break; + + case ISP1760_CTRL_DATA_OUT: + list_add_tail(&req->queue, &uep->queue); + __isp1760_udc_select_ep(udc, uep, USB_DIR_OUT); + isp1760_udc_set(udc, DC_DSEN); + break; + + case ISP1760_CTRL_STATUS: + complete = true; + break; + + default: + dev_dbg(udc->isp->dev, "%s: invalid ep0 state\n", + __func__); + ret = -EINVAL; + break; + } + } else if (uep->desc) { + bool empty = list_empty(&uep->queue); + + list_add_tail(&req->queue, &uep->queue); + if ((uep->addr & USB_DIR_IN) && !uep->halted && empty) + isp1760_udc_transmit(uep, req); + else if (!(uep->addr & USB_DIR_IN) && uep->rx_pending) + complete = isp1760_udc_receive(uep, req); + } else { + dev_dbg(udc->isp->dev, + "%s: can't queue request to disabled ep%02x\n", + __func__, uep->addr); + ret = -ESHUTDOWN; + } + +done: + if (ret < 0) + req->ep = NULL; + + spin_unlock_irqrestore(&udc->lock, flags); + + if (complete) + isp1760_udc_request_complete(uep, req, 0); + + return ret; +} + +static int isp1760_ep_dequeue(struct usb_ep *ep, struct usb_request *_req) +{ + struct isp1760_request *req = req_to_udc_req(_req); + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + + dev_dbg(uep->udc->isp->dev, "%s(ep%02x)\n", __func__, uep->addr); + + spin_lock_irqsave(&udc->lock, flags); + + if (req->ep != uep) + req = NULL; + else + list_del(&req->queue); + + spin_unlock_irqrestore(&udc->lock, flags); + + if (!req) + return -EINVAL; + + isp1760_udc_request_complete(uep, req, -ECONNRESET); + return 0; +} + +static int __isp1760_ep_set_halt(struct isp1760_ep *uep, bool stall, bool wedge) +{ + struct isp1760_udc *udc = uep->udc; + int ret; + + if (!uep->addr) { + /* + * Halting the control endpoint is only valid as a delayed error + * response to a SETUP packet. Make sure EP0 is in the right + * stage and that the gadget isn't trying to clear the halt + * condition. + */ + if (WARN_ON(udc->ep0_state == ISP1760_CTRL_SETUP || !stall || + wedge)) { + return -EINVAL; + } + } + + if (uep->addr && !uep->desc) { + dev_dbg(udc->isp->dev, "%s: ep%02x is disabled\n", __func__, + uep->addr); + return -EINVAL; + } + + if (uep->addr & USB_DIR_IN) { + /* Refuse to halt IN endpoints with active transfers. */ + if (!list_empty(&uep->queue)) { + dev_dbg(udc->isp->dev, + "%s: ep%02x has request pending\n", __func__, + uep->addr); + return -EAGAIN; + } + } + + ret = __isp1760_udc_set_halt(uep, stall); + if (ret < 0) + return ret; + + if (!uep->addr) { + /* + * Stalling EP0 completes the control transaction, move back to + * the SETUP state. + */ + udc->ep0_state = ISP1760_CTRL_SETUP; + return 0; + } + + if (wedge) + uep->wedged = true; + else if (!stall) + uep->wedged = false; + + return 0; +} + +static int isp1760_ep_set_halt(struct usb_ep *ep, int value) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + unsigned long flags; + int ret; + + dev_dbg(uep->udc->isp->dev, "%s: %s halt on ep%02x\n", __func__, + value ? "set" : "clear", uep->addr); + + spin_lock_irqsave(&uep->udc->lock, flags); + ret = __isp1760_ep_set_halt(uep, value, false); + spin_unlock_irqrestore(&uep->udc->lock, flags); + + return ret; +} + +static int isp1760_ep_set_wedge(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + unsigned long flags; + int ret; + + dev_dbg(uep->udc->isp->dev, "%s: set wedge on ep%02x)\n", __func__, + uep->addr); + + spin_lock_irqsave(&uep->udc->lock, flags); + ret = __isp1760_ep_set_halt(uep, true, true); + spin_unlock_irqrestore(&uep->udc->lock, flags); + + return ret; +} + +static void isp1760_ep_fifo_flush(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + isp1760_udc_select_ep(udc, uep); + + /* + * Set the CLBUF bit twice to flush both buffers in case double + * buffering is enabled. + */ + isp1760_udc_set(udc, DC_CLBUF); + isp1760_udc_set(udc, DC_CLBUF); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static const struct usb_ep_ops isp1760_ep_ops = { + .enable = isp1760_ep_enable, + .disable = isp1760_ep_disable, + .alloc_request = isp1760_ep_alloc_request, + .free_request = isp1760_ep_free_request, + .queue = isp1760_ep_queue, + .dequeue = isp1760_ep_dequeue, + .set_halt = isp1760_ep_set_halt, + .set_wedge = isp1760_ep_set_wedge, + .fifo_flush = isp1760_ep_fifo_flush, +}; + +/* ----------------------------------------------------------------------------- + * Device States + */ + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_connect(struct isp1760_udc *udc) +{ + usb_gadget_set_state(&udc->gadget, USB_STATE_POWERED); + mod_timer(&udc->vbus_timer, jiffies + ISP1760_VBUS_POLL_INTERVAL); +} + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_disconnect(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_POWERED) + return; + + dev_dbg(udc->isp->dev, "Device disconnected in state %u\n", + udc->gadget.state); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&udc->gadget, USB_STATE_ATTACHED); + + if (udc->driver->disconnect) + udc->driver->disconnect(&udc->gadget); + + timer_delete(&udc->vbus_timer); + + /* TODO Reset all endpoints ? */ +} + +static void isp1760_udc_init_hw(struct isp1760_udc *udc) +{ + u32 intconf = udc->is_isp1763 ? ISP1763_DC_INTCONF : ISP176x_DC_INTCONF; + u32 intena = udc->is_isp1763 ? ISP1763_DC_INTENABLE : + ISP176x_DC_INTENABLE; + + /* + * The device controller currently shares its interrupt with the host + * controller, the DC_IRQ polarity and signaling mode are ignored. Set + * the to active-low level-triggered. + * + * Configure the control, in and out pipes to generate interrupts on + * ACK tokens only (and NYET for the out pipe). The default + * configuration also generates an interrupt on the first NACK token. + */ + isp1760_reg_write(udc->regs, intconf, + ISP176x_DC_CDBGMOD_ACK | ISP176x_DC_DDBGMODIN_ACK | + ISP176x_DC_DDBGMODOUT_ACK); + + isp1760_reg_write(udc->regs, intena, DC_IEPRXTX(7) | + DC_IEPRXTX(6) | DC_IEPRXTX(5) | DC_IEPRXTX(4) | + DC_IEPRXTX(3) | DC_IEPRXTX(2) | DC_IEPRXTX(1) | + DC_IEPRXTX(0) | ISP176x_DC_IEP0SETUP | + ISP176x_DC_IEVBUS | ISP176x_DC_IERESM | + ISP176x_DC_IESUSP | ISP176x_DC_IEHS_STA | + ISP176x_DC_IEBRST); + + if (udc->connected) + isp1760_set_pullup(udc->isp, true); + + isp1760_udc_set(udc, DC_DEVEN); +} + +static void isp1760_udc_reset(struct isp1760_udc *udc) +{ + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + /* + * The bus reset has reset most registers to their default value, + * reinitialize the UDC hardware. + */ + isp1760_udc_init_hw(udc); + + udc->ep0_state = ISP1760_CTRL_SETUP; + udc->gadget.speed = USB_SPEED_FULL; + + usb_gadget_udc_reset(&udc->gadget, udc->driver); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void isp1760_udc_suspend(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_DEFAULT) + return; + + if (udc->driver->suspend) + udc->driver->suspend(&udc->gadget); +} + +static void isp1760_udc_resume(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_DEFAULT) + return; + + if (udc->driver->resume) + udc->driver->resume(&udc->gadget); +} + +/* ----------------------------------------------------------------------------- + * Gadget Operations + */ + +static int isp1760_udc_get_frame(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + return isp1760_udc_read(udc, DC_FRAMENUM); +} + +static int isp1760_udc_wakeup(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + dev_dbg(udc->isp->dev, "%s\n", __func__); + return -ENOTSUPP; +} + +static int isp1760_udc_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + if (is_selfpowered) + udc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; + else + udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + + return 0; +} + +static int isp1760_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + isp1760_set_pullup(udc->isp, is_on); + udc->connected = is_on; + + return 0; +} + +static int isp1760_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + unsigned long flags; + + /* The hardware doesn't support low speed. */ + if (driver->max_speed < USB_SPEED_FULL) { + dev_err(udc->isp->dev, "Invalid gadget driver\n"); + return -EINVAL; + } + + spin_lock_irqsave(&udc->lock, flags); + + if (udc->driver) { + dev_err(udc->isp->dev, "UDC already has a gadget driver\n"); + spin_unlock_irqrestore(&udc->lock, flags); + return -EBUSY; + } + + udc->driver = driver; + + spin_unlock_irqrestore(&udc->lock, flags); + + dev_dbg(udc->isp->dev, "starting UDC with driver %s\n", + driver->function); + + udc->devstatus = 0; + udc->connected = true; + + usb_gadget_set_state(&udc->gadget, USB_STATE_ATTACHED); + + /* DMA isn't supported yet, don't enable the DMA clock. */ + isp1760_udc_set(udc, DC_GLINTENA); + + isp1760_udc_init_hw(udc); + + dev_dbg(udc->isp->dev, "UDC started with driver %s\n", + driver->function); + + return 0; +} + +static int isp1760_udc_stop(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + u32 mode_reg = udc->is_isp1763 ? ISP1763_DC_MODE : ISP176x_DC_MODE; + unsigned long flags; + + dev_dbg(udc->isp->dev, "%s\n", __func__); + + timer_delete_sync(&udc->vbus_timer); + + isp1760_reg_write(udc->regs, mode_reg, 0); + + spin_lock_irqsave(&udc->lock, flags); + udc->driver = NULL; + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static const struct usb_gadget_ops isp1760_udc_ops = { + .get_frame = isp1760_udc_get_frame, + .wakeup = isp1760_udc_wakeup, + .set_selfpowered = isp1760_udc_set_selfpowered, + .pullup = isp1760_udc_pullup, + .udc_start = isp1760_udc_start, + .udc_stop = isp1760_udc_stop, +}; + +/* ----------------------------------------------------------------------------- + * Interrupt Handling + */ + +static u32 isp1760_udc_irq_get_status(struct isp1760_udc *udc) +{ + u32 status; + + if (udc->is_isp1763) { + status = isp1760_reg_read(udc->regs, ISP1763_DC_INTERRUPT) + & isp1760_reg_read(udc->regs, ISP1763_DC_INTENABLE); + isp1760_reg_write(udc->regs, ISP1763_DC_INTERRUPT, status); + } else { + status = isp1760_reg_read(udc->regs, ISP176x_DC_INTERRUPT) + & isp1760_reg_read(udc->regs, ISP176x_DC_INTENABLE); + isp1760_reg_write(udc->regs, ISP176x_DC_INTERRUPT, status); + } + + return status; +} + +static irqreturn_t isp1760_udc_irq(int irq, void *dev) +{ + struct isp1760_udc *udc = dev; + unsigned int i; + u32 status; + + status = isp1760_udc_irq_get_status(udc); + + if (status & ISP176x_DC_IEVBUS) { + dev_dbg(udc->isp->dev, "%s(VBUS)\n", __func__); + /* The VBUS interrupt is only triggered when VBUS appears. */ + spin_lock(&udc->lock); + isp1760_udc_connect(udc); + spin_unlock(&udc->lock); + } + + if (status & ISP176x_DC_IEBRST) { + dev_dbg(udc->isp->dev, "%s(BRST)\n", __func__); + + isp1760_udc_reset(udc); + } + + for (i = 0; i <= 7; ++i) { + struct isp1760_ep *ep = &udc->ep[i*2]; + + if (status & DC_IEPTX(i)) { + dev_dbg(udc->isp->dev, "%s(EPTX%u)\n", __func__, i); + isp1760_ep_tx_complete(ep); + } + + if (status & DC_IEPRX(i)) { + dev_dbg(udc->isp->dev, "%s(EPRX%u)\n", __func__, i); + isp1760_ep_rx_ready(i ? ep - 1 : ep); + } + } + + if (status & ISP176x_DC_IEP0SETUP) { + dev_dbg(udc->isp->dev, "%s(EP0SETUP)\n", __func__); + + isp1760_ep0_setup(udc); + } + + if (status & ISP176x_DC_IERESM) { + dev_dbg(udc->isp->dev, "%s(RESM)\n", __func__); + isp1760_udc_resume(udc); + } + + if (status & ISP176x_DC_IESUSP) { + dev_dbg(udc->isp->dev, "%s(SUSP)\n", __func__); + + spin_lock(&udc->lock); + if (!isp1760_udc_is_set(udc, DC_VBUSSTAT)) + isp1760_udc_disconnect(udc); + else + isp1760_udc_suspend(udc); + spin_unlock(&udc->lock); + } + + if (status & ISP176x_DC_IEHS_STA) { + dev_dbg(udc->isp->dev, "%s(HS_STA)\n", __func__); + udc->gadget.speed = USB_SPEED_HIGH; + } + + return status ? IRQ_HANDLED : IRQ_NONE; +} + +static void isp1760_udc_vbus_poll(struct timer_list *t) +{ + struct isp1760_udc *udc = timer_container_of(udc, t, vbus_timer); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + if (!(isp1760_udc_is_set(udc, DC_VBUSSTAT))) + isp1760_udc_disconnect(udc); + else if (udc->gadget.state >= USB_STATE_POWERED) + mod_timer(&udc->vbus_timer, + jiffies + ISP1760_VBUS_POLL_INTERVAL); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +/* ----------------------------------------------------------------------------- + * Registration + */ + +static void isp1760_udc_init_eps(struct isp1760_udc *udc) +{ + unsigned int i; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + for (i = 0; i < ARRAY_SIZE(udc->ep); ++i) { + struct isp1760_ep *ep = &udc->ep[i]; + unsigned int ep_num = (i + 1) / 2; + bool is_in = !(i & 1); + + ep->udc = udc; + + INIT_LIST_HEAD(&ep->queue); + + ep->addr = (ep_num && is_in ? USB_DIR_IN : USB_DIR_OUT) + | ep_num; + ep->desc = NULL; + + sprintf(ep->name, "ep%u%s", ep_num, + ep_num ? (is_in ? "in" : "out") : ""); + + ep->ep.ops = &isp1760_ep_ops; + ep->ep.name = ep->name; + + /* + * Hardcode the maximum packet sizes for now, to 64 bytes for + * the control endpoint and 512 bytes for all other endpoints. + * This fits in the 8kB FIFO without double-buffering. + */ + if (ep_num == 0) { + usb_ep_set_maxpacket_limit(&ep->ep, 64); + ep->ep.caps.type_control = true; + ep->ep.caps.dir_in = true; + ep->ep.caps.dir_out = true; + ep->maxpacket = 64; + udc->gadget.ep0 = &ep->ep; + } else { + usb_ep_set_maxpacket_limit(&ep->ep, 512); + ep->ep.caps.type_iso = true; + ep->ep.caps.type_bulk = true; + ep->ep.caps.type_int = true; + ep->maxpacket = 0; + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + } + + if (is_in) + ep->ep.caps.dir_in = true; + else + ep->ep.caps.dir_out = true; + } +} + +static int isp1760_udc_init(struct isp1760_udc *udc) +{ + u32 mode_reg = udc->is_isp1763 ? ISP1763_DC_MODE : ISP176x_DC_MODE; + u16 scratch; + u32 chipid; + + /* + * Check that the controller is present by writing to the scratch + * register, modifying the bus pattern by reading from the chip ID + * register, and reading the scratch register value back. The chip ID + * and scratch register contents must match the expected values. + */ + isp1760_udc_write(udc, DC_SCRATCH, 0xbabe); + chipid = isp1760_udc_read(udc, DC_CHIP_ID_HIGH) << 16; + chipid |= isp1760_udc_read(udc, DC_CHIP_ID_LOW); + scratch = isp1760_udc_read(udc, DC_SCRATCH); + + if (scratch != 0xbabe) { + dev_err(udc->isp->dev, + "udc: scratch test failed (0x%04x/0x%08x)\n", + scratch, chipid); + return -ENODEV; + } + + if (chipid != 0x00011582 && chipid != 0x00158210 && + chipid != 0x00176320) { + dev_err(udc->isp->dev, "udc: invalid chip ID 0x%08x\n", chipid); + return -ENODEV; + } + + /* Reset the device controller. */ + isp1760_udc_set(udc, DC_SFRESET); + usleep_range(10000, 11000); + isp1760_reg_write(udc->regs, mode_reg, 0); + usleep_range(10000, 11000); + + return 0; +} + +int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags) +{ + struct isp1760_udc *udc = &isp->udc; + int ret; + + udc->irq = -1; + udc->isp = isp; + + spin_lock_init(&udc->lock); + timer_setup(&udc->vbus_timer, isp1760_udc_vbus_poll, 0); + + ret = isp1760_udc_init(udc); + if (ret < 0) + return ret; + + udc->irqname = kasprintf(GFP_KERNEL, "%s (udc)", dev_name(isp->dev)); + if (!udc->irqname) + return -ENOMEM; + + ret = request_irq(irq, isp1760_udc_irq, IRQF_SHARED | irqflags, + udc->irqname, udc); + if (ret < 0) + goto error; + + udc->irq = irq; + + /* + * Initialize the gadget static fields and register its device. Gadget + * fields that vary during the life time of the gadget are initialized + * by the UDC core. + */ + udc->gadget.ops = &isp1760_udc_ops; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->gadget.name = "isp1761_udc"; + + isp1760_udc_init_eps(udc); + + ret = usb_add_gadget_udc(isp->dev, &udc->gadget); + if (ret < 0) + goto error; + + return 0; + +error: + if (udc->irq >= 0) + free_irq(udc->irq, udc); + kfree(udc->irqname); + + return ret; +} + +void isp1760_udc_unregister(struct isp1760_device *isp) +{ + struct isp1760_udc *udc = &isp->udc; + + if (!udc->isp) + return; + + usb_del_gadget_udc(&udc->gadget); + + free_irq(udc->irq, udc); + kfree(udc->irqname); +} diff --git a/drivers/usb/isp1760/isp1760-udc.h b/drivers/usb/isp1760/isp1760-udc.h new file mode 100644 index 000000000..22044e86b --- /dev/null +++ b/drivers/usb/isp1760/isp1760-udc.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver for the NXP ISP1761 device controller + * + * Copyright 2021 Linaro, Rui Miguel Silva + * Copyright 2014 Ideas on Board Oy + * + * Contacts: + * Laurent Pinchart + * Rui Miguel Silva + */ + +#ifndef _ISP1760_UDC_H_ +#define _ISP1760_UDC_H_ + +#include +#include +#include +#include +#include + +#include "isp1760-regs.h" + +struct isp1760_device; +struct isp1760_udc; + +enum isp1760_ctrl_state { + ISP1760_CTRL_SETUP, /* Waiting for a SETUP transaction */ + ISP1760_CTRL_DATA_IN, /* Setup received, data IN stage */ + ISP1760_CTRL_DATA_OUT, /* Setup received, data OUT stage */ + ISP1760_CTRL_STATUS, /* 0-length request in status stage */ +}; + +struct isp1760_ep { + struct isp1760_udc *udc; + struct usb_ep ep; + + struct list_head queue; + + unsigned int addr; + unsigned int maxpacket; + char name[7]; + + const struct usb_endpoint_descriptor *desc; + + bool rx_pending; + bool halted; + bool wedged; +}; + +/** + * struct isp1760_udc - UDC state information + * irq: IRQ number + * irqname: IRQ name (as passed to request_irq) + * regs: regmap for UDC registers + * driver: Gadget driver + * gadget: Gadget device + * lock: Protects driver, vbus_timer, ep, ep0_*, DC_EPINDEX register + * ep: Array of endpoints + * ep0_state: Control request state for endpoint 0 + * ep0_dir: Direction of the current control request + * ep0_length: Length of the current control request + * connected: Tracks gadget driver bus connection state + */ +struct isp1760_udc { + struct isp1760_device *isp; + + int irq; + char *irqname; + + struct regmap *regs; + struct regmap_field *fields[DC_FIELD_MAX]; + + struct usb_gadget_driver *driver; + struct usb_gadget gadget; + + spinlock_t lock; + struct timer_list vbus_timer; + + struct isp1760_ep ep[15]; + + enum isp1760_ctrl_state ep0_state; + u8 ep0_dir; + u16 ep0_length; + + bool connected; + bool is_isp1763; + + unsigned int devstatus; +}; + +#ifdef CONFIG_USB_ISP1761_UDC +int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags); +void isp1760_udc_unregister(struct isp1760_device *isp); +#else +static inline int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags) +{ + return 0; +} + +static inline void isp1760_udc_unregister(struct isp1760_device *isp) +{ +} +#endif + +#endif diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig new file mode 100644 index 000000000..91b62acb5 --- /dev/null +++ b/drivers/usb/misc/Kconfig @@ -0,0 +1,361 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# USB Miscellaneous driver configuration +# +comment "USB Miscellaneous drivers" + +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. + +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_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_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: https://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_APPLEDISPLAY + tristate "Apple Cinema Display 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. + +config USB_QCOM_EUD + tristate "Qualcomm Embedded USB Debugger(EUD) Driver" + depends on ARCH_QCOM || COMPILE_TEST + select QCOM_SCM + select USB_ROLE_SWITCH + help + This module enables support for Qualcomm Technologies, Inc. + Embedded USB Debugger (EUD). The EUD is a control peripheral + which reports VBUS attach/detach events and has USB-based + debug and trace capabilities. On selecting m, the module name + that is built is qcom_eud.ko + +config APPLE_MFI_FASTCHARGE + tristate "Fast charge control for iOS devices" + select POWER_SUPPLY + help + Say Y here if you want to control whether iOS devices will + fast charge from the USB interface, as implemented in "MFi" + chargers. + + It is safe to say M here. + +config USB_LJCA + tristate "Intel La Jolla Cove Adapter support" + select AUXILIARY_BUS + depends on USB && ACPI + help + This adds support for Intel La Jolla Cove USB-I2C/SPI/GPIO + Master Adapter (LJCA). Additional drivers such as I2C_LJCA, + GPIO_LJCA and SPI_LJCA must be enabled in order to use the + functionality of the device. + + This driver can also be built as a module. If so, the module + will be called usb-ljca. + +config USB_USBIO + tristate "Intel USBIO Bridge support" + depends on USB && ACPI + depends on X86 || COMPILE_TEST + select AUXILIARY_BUS + help + This adds support for Intel USBIO drivers. + This enables the USBIO bridge driver module in charge to talk + to the USB device. Additional drivers such as GPIO_USBIO and + I2C_USBIO must be enabled in order to use the device's full + functionality. + + This driver can also be built as a module. If so, the module + will be called usbio. + +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_EHSET_TEST_FIXTURE + tristate "USB EHSET Test Fixture driver" + help + Say Y here if you want to support the special test fixture device + used for the USB-IF Embedded Host High-Speed Electrical Test procedure. + + When the test fixture is connected, it can enumerate as one of several + VID/PID pairs. This driver then initiates a corresponding test mode on + the downstream port to which the test fixture is attached. + + See + for more information. + +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_HUB_USB251XB + tristate "USB251XB Hub Controller Configuration Driver" + depends on I2C + help + This option enables support for configuration via SMBus of the + Microchip USB251x/xBi USB 2.0 Hub Controller series. Configuration + parameters may be set in devicetree or platform data. + Say Y or M here if you need to configure such a device via SMBus. + +config USB_HSIC_USB3503 + tristate "USB3503 HSIC to USB20 Driver" + depends on I2C + select REGMAP_I2C + help + This option enables support for SMSC USB3503 HSIC to USB 2.0 Driver. + +config USB_HSIC_USB4604 + tristate "USB4604 HSIC to USB20 Driver" + depends on I2C + help + This option enables support for SMSC USB4604 HSIC to USB 2.0 Driver. + +config USB_LINK_LAYER_TEST + tristate "USB Link Layer Test driver" + help + This driver is for generating specific traffic for Super Speed Link + Layer Test Device. Say Y only when you want to conduct USB Super Speed + Link Layer Test for host controllers. + +config USB_CHAOSKEY + tristate "ChaosKey random number generator driver support" + depends on HW_RANDOM + help + Say Y here if you want to connect an AltusMetrum ChaosKey or + Araneus Alea I to your computer's USB port. These devices + are hardware random number generators which hook into the + kernel entropy pool to ensure a large supply of entropy for + /dev/random and /dev/urandom and also provides direct access + via /dev/chaoskeyX + + To compile this driver as a module, choose M here: the + module will be called chaoskey. + +config BRCM_USB_PINMAP + tristate "Broadcom pinmap driver support" + depends on (ARCH_BRCMSTB && PHY_BRCM_USB) || COMPILE_TEST + default ARCH_BRCMSTB && PHY_BRCM_USB + help + This option enables support for remapping some USB external + signals, which are typically on dedicated pins on the chip, + to any gpio. + +config USB_ONBOARD_DEV + tristate "Onboard USB device support" + depends on OF + help + Say Y here if you want to support discrete onboard USB devices + that don't require an additional control bus for initialization, + but need some non-trivial form of initialization, such as + enabling a power regulator. An example for such device is the + Realtek RTS5411 hub. + + This driver can be used as a module but its state (module vs + builtin) must match the state of the USB subsystem. Enabling + this config will enable the driver and it will automatically + match the state of the USB subsystem. If this driver is a + module it will be called onboard_usb_dev. + +config USB_ONBOARD_DEV_USB5744 + bool "Onboard USB Microchip usb5744 hub with SMBus support" + depends on (USB_ONBOARD_DEV && I2C=y) || (USB_ONBOARD_DEV=m && I2C=m) + help + Say Y here if you want to support onboard USB Microchip usb5744 + hub that requires SMBus initialization. + + This options enables usb5744 i2c default initialization sequence + during hub start-up configuration stage. It is must to enable this + option on AMD Kria KR260 Robotics Starter Kit as this hub is + connected to USB-SD converter which mounts the root filesystem. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile new file mode 100644 index 000000000..494ab0377 --- /dev/null +++ b/drivers/usb/misc/Makefile @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the rest of the USB drivers +# (the ones that don't fit into any other categories) +# +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_APPLE_MFI_FASTCHARGE) += apple-mfi-fastcharge.o +obj-$(CONFIG_USB_LJCA) += usb-ljca.o +obj-$(CONFIG_USB_USBIO) += usbio.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_LEGOTOWER) += legousbtower.o +obj-$(CONFIG_USB_QCOM_EUD) += qcom_eud.o +obj-$(CONFIG_USB_TEST) += usbtest.o +obj-$(CONFIG_USB_EHSET_TEST_FIXTURE) += ehset.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_HUB_USB251XB) += usb251xb.o +obj-$(CONFIG_USB_HSIC_USB3503) += usb3503.o +obj-$(CONFIG_USB_HSIC_USB4604) += usb4604.o +obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o + +obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ +obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o +obj-$(CONFIG_BRCM_USB_PINMAP) += brcmstb-usb-pinmap.o +obj-$(CONFIG_USB_ONBOARD_DEV) += onboard_usb_dev.o diff --git a/drivers/usb/misc/adutux.c b/drivers/usb/misc/adutux.c new file mode 100644 index 000000000..369d0d2ee --- /dev/null +++ b/drivers/usb/misc/adutux.c @@ -0,0 +1,796 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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) + * + * 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) + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_AUTHOR "John Homppi" +#define DRIVER_DESC "adutux (see www.ontrak.net)" + +/* 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) + +/* + * 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 */ + unsigned long disconnected:1; + + 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 inline void adu_debug_data(struct device *dev, const char *function, + int size, const unsigned char *data) +{ + dev_dbg(dev, "%s - length = %d, data = %*ph\n", + function, size, size, data); +} + +/* + * adu_abort_transfers + * aborts transfers and frees associated data structures + */ +static void adu_abort_transfers(struct adu_device *dev) +{ + unsigned long flags; + + if (dev->disconnected) + return; + + /* 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); + wait_event_timeout(dev->write_wait, dev->out_urb_finished, + COMMAND_TIMEOUT); + usb_kill_urb(dev->interrupt_out_urb); + } else + spin_unlock_irqrestore(&dev->buflock, flags); +} + +static void adu_delete(struct adu_device *dev) +{ + /* 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); + usb_put_dev(dev->udev); + kfree(dev); +} + +static void adu_interrupt_in_callback(struct urb *urb) +{ + struct adu_device *dev = urb->context; + int status = urb->status; + unsigned long flags; + + adu_debug_data(&dev->udev->dev, __func__, + urb->actual_length, urb->transfer_buffer); + + spin_lock_irqsave(&dev->buflock, flags); + + if (status != 0) { + if ((status != -ENOENT) && (status != -ECONNRESET) && + (status != -ESHUTDOWN)) { + dev_dbg(&dev->udev->dev, + "%s : nonzero status received: %d\n", + __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; + dev_dbg(&dev->udev->dev, "%s reading %d\n", __func__, + urb->actual_length); + } else { + dev_dbg(&dev->udev->dev, "%s : read_buffer overflow\n", + __func__); + } + } + +exit: + dev->read_urb_finished = 1; + spin_unlock_irqrestore(&dev->buflock, flags); + /* always wake up so we recover from errors */ + wake_up_interruptible(&dev->read_wait); +} + +static void adu_interrupt_out_callback(struct urb *urb) +{ + struct adu_device *dev = urb->context; + int status = urb->status; + unsigned long flags; + + adu_debug_data(&dev->udev->dev, __func__, + urb->actual_length, urb->transfer_buffer); + + if (status != 0) { + if ((status != -ENOENT) && + (status != -ESHUTDOWN) && + (status != -ECONNRESET)) { + dev_dbg(&dev->udev->dev, + "%s :nonzero status received: %d\n", __func__, + status); + } + return; + } + + spin_lock_irqsave(&dev->buflock, flags); + dev->out_urb_finished = 1; + wake_up(&dev->write_wait); + spin_unlock_irqrestore(&dev->buflock, flags); +} + +static int adu_open(struct inode *inode, struct file *file) +{ + struct adu_device *dev = NULL; + struct usb_interface *interface; + int subminor; + int retval; + + subminor = iminor(inode); + + retval = mutex_lock_interruptible(&adutux_mutex); + if (retval) + goto exit_no_lock; + + interface = usb_find_interface(&adu_driver, subminor); + if (!interface) { + pr_err("%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) { + 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; + dev_dbg(&dev->udev->dev, "%s: open count %d\n", __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: + return retval; +} + +static void adu_release_internal(struct adu_device *dev) +{ + /* decrement our usage count for the device */ + --dev->open_count; + dev_dbg(&dev->udev->dev, "%s : open count %d\n", __func__, + dev->open_count); + if (dev->open_count <= 0) { + adu_abort_transfers(dev); + dev->open_count = 0; + } +} + +static int adu_release(struct inode *inode, struct file *file) +{ + struct adu_device *dev; + int retval = 0; + + if (file == NULL) { + retval = -ENODEV; + goto exit; + } + + dev = file->private_data; + if (dev == NULL) { + retval = -ENODEV; + goto exit; + } + + mutex_lock(&adutux_mutex); /* not interruptible */ + + if (dev->open_count <= 0) { + dev_dbg(&dev->udev->dev, "%s : device not opened\n", __func__); + retval = -ENODEV; + goto unlock; + } + + adu_release_internal(dev); + if (dev->disconnected) { + /* 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: + 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 retval = 0; + int timeout = 0; + int should_submit = 0; + unsigned long flags; + DECLARE_WAITQUEUE(wait, current); + + dev = file->private_data; + if (mutex_lock_interruptible(&dev->mtx)) + return -ERESTARTSYS; + + /* verify that the device wasn't unplugged */ + if (dev->disconnected) { + retval = -ENODEV; + pr_err("No device or device unplugged %d\n", retval); + goto exit; + } + + /* verify that some data was requested */ + if (count == 0) { + dev_dbg(&dev->udev->dev, "%s : read request of 0 bytes\n", + __func__); + goto exit; + } + + timeout = COMMAND_TIMEOUT; + dev_dbg(&dev->udev->dev, "%s : about to start looping\n", __func__); + while (bytes_to_read) { + size_t data_in_secondary = dev->secondary_tail - dev->secondary_head; + dev_dbg(&dev->udev->dev, + "%s : while, data_in_secondary=%zu, status=%d\n", + __func__, data_in_secondary, + dev->interrupt_in_urb->status); + + if (data_in_secondary) { + /* drain secondary buffer */ + size_t amount = min(bytes_to_read, data_in_secondary); + if (copy_to_user(buffer, dev->read_buffer_secondary+dev->secondary_head, amount)) { + retval = -EFAULT; + goto exit; + } + dev->secondary_head += amount; + bytes_read += amount; + bytes_to_read -= amount; + } else { + /* we check the primary buffer */ + spin_lock_irqsave (&dev->buflock, flags); + if (dev->read_buffer_length) { + /* we secure access to the primary */ + dev_dbg(&dev->udev->dev, + "%s : swap, read_buffer_length = %d\n", + __func__, dev->read_buffer_length); + swap(dev->read_buffer_primary, dev->read_buffer_secondary); + 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); + dev_dbg(&dev->udev->dev, + "%s : submitted already\n", + __func__); + } else { + /* we must initiate input */ + dev_dbg(&dev->udev->dev, + "%s : initiate input\n", + __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; + } + dev_dbg(&dev->udev->dev, + "%s : submit failed\n", + __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) { + dev_dbg(&dev->udev->dev, + "%s : timeout\n", __func__); + retval = bytes_read ? bytes_read : -ETIMEDOUT; + goto exit; + } + + if (signal_pending(current)) { + dev_dbg(&dev->udev->dev, + "%s : signal pending\n", + __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); + + 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; + + dev = file->private_data; + + retval = mutex_lock_interruptible(&dev->mtx); + if (retval) + goto exit_nolock; + + /* verify that the device wasn't unplugged */ + if (dev->disconnected) { + retval = -ENODEV; + pr_err("No device or device unplugged %d\n", retval); + goto exit; + } + + /* verify that we actually have some data to write */ + if (count == 0) { + dev_dbg(&dev->udev->dev, "%s : write request of 0 bytes\n", + __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)) { + dev_dbg(&dev->udev->dev, "%s : interrupted\n", + __func__); + set_current_state(TASK_RUNNING); + retval = -EINTR; + goto exit_onqueue; + } + if (schedule_timeout(COMMAND_TIMEOUT) == 0) { + dev_dbg(&dev->udev->dev, + "%s - command timed out.\n", __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; + } + + dev_dbg(&dev->udev->dev, + "%s : in progress, count = %zd\n", + __func__, count); + } else { + spin_unlock_irqrestore(&dev->buflock, flags); + set_current_state(TASK_RUNNING); + remove_wait_queue(&dev->write_wait, &waita); + dev_dbg(&dev->udev->dev, "%s : sending, count = %zd\n", + __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; + dev_dbg(&dev->udev->dev, + "%s : buffer_size = %zd, count = %zd, bytes_to_write = %zd\n", + __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: + 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; + int retval = -ENOMEM; + int in_end_size; + int out_end_size; + int res; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc_obj(struct adu_device); + if (!dev) + return -ENOMEM; + + mutex_init(&dev->mtx); + spin_lock_init(&dev->buflock); + dev->udev = usb_get_dev(udev); + init_waitqueue_head(&dev->read_wait); + init_waitqueue_head(&dev->write_wait); + + res = usb_find_common_endpoints_reverse(interface->cur_altsetting, + NULL, NULL, + &dev->interrupt_in_endpoint, + &dev->interrupt_out_endpoint); + if (res) { + dev_err(&interface->dev, "interrupt endpoints not found\n"); + retval = res; + 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) + 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) + 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) + 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) + goto error; + dev->interrupt_out_buffer = kmalloc(out_end_size, GFP_KERNEL); + if (!dev->interrupt_out_buffer) + goto error; + dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_out_urb) + 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"); + retval = -EIO; + goto error; + } + dev_dbg(&interface->dev, "serial_number=%s", 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", + le16_to_cpu(udev->descriptor.idProduct), dev->serial_number, + (dev->minor - ADU_MINOR_BASE)); + + return 0; + +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; + + dev = usb_get_intfdata(interface); + + usb_deregister_dev(interface, &adu_class); + + usb_poison_urb(dev->interrupt_in_urb); + usb_poison_urb(dev->interrupt_out_urb); + + mutex_lock(&adutux_mutex); + usb_set_intfdata(interface, NULL); + + mutex_lock(&dev->mtx); /* not interruptible */ + dev->disconnected = 1; + mutex_unlock(&dev->mtx); + + /* if the device is not opened, then we clean up right now */ + if (!dev->open_count) + adu_delete(dev); + + mutex_unlock(&adutux_mutex); +} + +/* 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/apple-mfi-fastcharge.c b/drivers/usb/misc/apple-mfi-fastcharge.c new file mode 100644 index 000000000..af266e19f --- /dev/null +++ b/drivers/usb/misc/apple-mfi-fastcharge.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Fast-charge control for Apple "MFi" devices + * + * Copyright (C) 2019 Bastien Nocera + */ + +/* Standard include files */ +#include +#include +#include +#include + +MODULE_AUTHOR("Bastien Nocera "); +MODULE_DESCRIPTION("Fast-charge control for Apple \"MFi\" devices"); +MODULE_LICENSE("GPL"); + +#define TRICKLE_CURRENT_MA 0 +#define FAST_CURRENT_MA 2500 + +#define APPLE_VENDOR_ID 0x05ac /* Apple */ + +/* The product ID is defined as starting with 0x12nn, as per the + * "Choosing an Apple Device USB Configuration" section in + * release R9 (2012) of the "MFi Accessory Hardware Specification" + * + * To distinguish an Apple device, a USB host can check the device + * descriptor of attached USB devices for the following fields: + * ■ Vendor ID: 0x05AC + * ■ Product ID: 0x12nn + * + * Those checks will be done in .match() and .probe(). + */ + +static const struct usb_device_id mfi_fc_id_table[] = { + { .idVendor = APPLE_VENDOR_ID, + .match_flags = USB_DEVICE_ID_MATCH_VENDOR }, + {}, +}; + +MODULE_DEVICE_TABLE(usb, mfi_fc_id_table); + +/* Driver-local specific stuff */ +struct mfi_device { + struct usb_device *udev; + struct power_supply *battery; + struct power_supply_desc battery_desc; + int charge_type; +}; + +static int apple_mfi_fc_set_charge_type(struct mfi_device *mfi, + const union power_supply_propval *val) +{ + int current_ma; + int retval; + __u8 request_type; + + if (mfi->charge_type == val->intval) { + dev_dbg(&mfi->udev->dev, "charge type %d already set\n", + mfi->charge_type); + return 0; + } + + switch (val->intval) { + case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: + current_ma = TRICKLE_CURRENT_MA; + break; + case POWER_SUPPLY_CHARGE_TYPE_FAST: + current_ma = FAST_CURRENT_MA; + break; + default: + return -EINVAL; + } + + request_type = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE; + retval = usb_control_msg(mfi->udev, usb_sndctrlpipe(mfi->udev, 0), + 0x40, /* Vendor‐defined power request */ + request_type, + current_ma, /* wValue, current offset */ + current_ma, /* wIndex, current offset */ + NULL, 0, USB_CTRL_GET_TIMEOUT); + if (retval) { + dev_dbg(&mfi->udev->dev, "retval = %d\n", retval); + return retval; + } + + mfi->charge_type = val->intval; + + return 0; +} + +static int apple_mfi_fc_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mfi_device *mfi = power_supply_get_drvdata(psy); + + dev_dbg(&mfi->udev->dev, "prop: %d\n", psp); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = mfi->charge_type; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_DEVICE; + break; + default: + return -ENODATA; + } + + return 0; +} + +static int apple_mfi_fc_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct mfi_device *mfi = power_supply_get_drvdata(psy); + int ret; + + dev_dbg(&mfi->udev->dev, "prop: %d\n", psp); + + ret = pm_runtime_get_sync(&mfi->udev->dev); + if (ret < 0) { + pm_runtime_put_noidle(&mfi->udev->dev); + return ret; + } + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = apple_mfi_fc_set_charge_type(mfi, val); + break; + default: + ret = -EINVAL; + } + + pm_runtime_put_autosuspend(&mfi->udev->dev); + + return ret; +} + +static int apple_mfi_fc_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + return 1; + default: + return 0; + } +} + +static enum power_supply_property apple_mfi_fc_properties[] = { + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_SCOPE +}; + +static const struct power_supply_desc apple_mfi_fc_desc = { + .name = "apple_mfi_fastcharge", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = apple_mfi_fc_properties, + .num_properties = ARRAY_SIZE(apple_mfi_fc_properties), + .get_property = apple_mfi_fc_get_property, + .set_property = apple_mfi_fc_set_property, + .property_is_writeable = apple_mfi_fc_property_is_writeable +}; + +static bool mfi_fc_match(struct usb_device *udev) +{ + int idProduct; + + idProduct = le16_to_cpu(udev->descriptor.idProduct); + /* See comment above mfi_fc_id_table[] */ + return (idProduct >= 0x1200 && idProduct <= 0x12ff); +} + +static int mfi_fc_probe(struct usb_device *udev) +{ + struct power_supply_config battery_cfg = {}; + struct mfi_device *mfi = NULL; + char *battery_name; + int err; + + if (!mfi_fc_match(udev)) + return -ENODEV; + + mfi = kzalloc_obj(struct mfi_device); + if (!mfi) + return -ENOMEM; + + battery_name = kasprintf(GFP_KERNEL, "apple_mfi_fastcharge_%d-%d", + udev->bus->busnum, udev->devnum); + if (!battery_name) { + err = -ENOMEM; + goto err_free_mfi; + } + + mfi->battery_desc = apple_mfi_fc_desc; + mfi->battery_desc.name = battery_name; + + battery_cfg.drv_data = mfi; + + mfi->charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + mfi->battery = power_supply_register(&udev->dev, + &mfi->battery_desc, + &battery_cfg); + if (IS_ERR(mfi->battery)) { + dev_err(&udev->dev, "Can't register battery\n"); + err = PTR_ERR(mfi->battery); + goto err_free_name; + } + + mfi->udev = udev; + dev_set_drvdata(&udev->dev, mfi); + + return 0; + +err_free_name: + kfree(battery_name); +err_free_mfi: + kfree(mfi); + return err; +} + +static void mfi_fc_disconnect(struct usb_device *udev) +{ + struct mfi_device *mfi; + + mfi = dev_get_drvdata(&udev->dev); + if (mfi->battery) + power_supply_unregister(mfi->battery); + kfree(mfi->battery_desc.name); + dev_set_drvdata(&udev->dev, NULL); + kfree(mfi); +} + +static struct usb_device_driver mfi_fc_driver = { + .name = "apple-mfi-fastcharge", + .probe = mfi_fc_probe, + .disconnect = mfi_fc_disconnect, + .id_table = mfi_fc_id_table, + .match = mfi_fc_match, + .generic_subclass = 1, +}; + +static int __init mfi_fc_driver_init(void) +{ + return usb_register_device_driver(&mfi_fc_driver, THIS_MODULE); +} + +static void __exit mfi_fc_driver_exit(void) +{ + usb_deregister_device_driver(&mfi_fc_driver); +} + +module_init(mfi_fc_driver_init); +module_exit(mfi_fc_driver_exit); diff --git a/drivers/usb/misc/appledisplay.c b/drivers/usb/misc/appledisplay.c new file mode 100644 index 000000000..16883592f --- /dev/null +++ b/drivers/usb/misc/appledisplay.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Apple Cinema Display driver + * + * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) + * + * Thanks to Caskey L. Dickson for his work with acdctl. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define APPLE_VENDOR_ID 0x05AC + +#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(0x9222) }, + { APPLEDISPLAY_DEVICE(0x9226) }, + { 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; + struct mutex sysfslock; /* concurrent read and write */ +}; + +static atomic_t count_displays = ATOMIC_INIT(0); + +static void appledisplay_complete(struct urb *urb) +{ + struct appledisplay *pdata = urb->context; + struct device *dev = &pdata->udev->dev; + 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); + fallthrough; + 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; + } + + switch(pdata->urbdata[1]) { + case ACD_BTN_BRIGHT_UP: + case ACD_BTN_BRIGHT_DOWN: + pdata->button_pressed = 1; + /* + * there is a window during which no device + * is registered + */ + if (pdata->bd ) + schedule_delayed_work(&pdata->work, 0); + break; + case ACD_BTN_NONE: + default: + pdata->button_pressed = 0; + break; + } + +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; + + mutex_lock(&pdata->sysfslock); + pdata->msgdata[0] = 0x10; + pdata->msgdata[1] = bd->props.brightness; + + retval = usb_control_msg( + pdata->udev, + usb_sndctrlpipe(pdata->udev, 0), + HID_REQ_SET_REPORT, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ACD_USB_BRIGHTNESS, + 0, + pdata->msgdata, 2, + ACD_USB_TIMEOUT); + mutex_unlock(&pdata->sysfslock); + + if (retval < 0) + return retval; + else + return 0; +} + +static int appledisplay_bl_get_brightness(struct backlight_device *bd) +{ + struct appledisplay *pdata = bl_get_data(bd); + int retval, brightness; + + mutex_lock(&pdata->sysfslock); + retval = usb_control_msg( + pdata->udev, + usb_rcvctrlpipe(pdata->udev, 0), + HID_REQ_GET_REPORT, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ACD_USB_BRIGHTNESS, + 0, + pdata->msgdata, 2, + ACD_USB_TIMEOUT); + if (retval < 2) { + if (retval >= 0) + retval = -EMSGSIZE; + } else { + brightness = pdata->msgdata[1]; + } + mutex_unlock(&pdata->sysfslock); + + if (retval < 0) + return retval; + else + return brightness; +} + +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 backlight_device *backlight; + struct appledisplay *pdata; + struct usb_device *udev = interface_to_usbdev(iface); + struct usb_endpoint_descriptor *endpoint; + int int_in_endpointAddr = 0; + int retval, brightness; + char bl_name[20]; + + /* set up the endpoint information */ + /* use only the first interrupt-in endpoint */ + retval = usb_find_int_in_endpoint(iface->cur_altsetting, &endpoint); + if (retval) { + dev_err(&iface->dev, "Could not find int-in endpoint\n"); + return retval; + } + + int_in_endpointAddr = endpoint->bEndpointAddress; + + /* allocate memory for our device state and initialize it */ + pdata = kzalloc_obj(struct appledisplay); + if (!pdata) { + retval = -ENOMEM; + goto error; + } + + pdata->udev = udev; + + INIT_DELAYED_WORK(&pdata->work, appledisplay_work); + mutex_init(&pdata->sysfslock); + + /* Allocate buffer for control messages */ + pdata->msgdata = kmalloc(ACD_MSG_BUFFER_LEN, GFP_KERNEL); + if (!pdata->msgdata) { + retval = -ENOMEM; + goto error; + } + + /* Allocate interrupt URB */ + pdata->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!pdata->urb) { + retval = -ENOMEM; + 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); + pdata->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + 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; + backlight = backlight_device_register(bl_name, NULL, pdata, + &appledisplay_bl_data, &props); + if (IS_ERR(backlight)) { + dev_err(&iface->dev, "Backlight registration failed\n"); + retval = PTR_ERR(backlight); + goto error; + } + pdata->bd = backlight; + + /* 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); + cancel_delayed_work_sync(&pdata->work); + usb_free_coherent(pdata->udev, ACD_URB_BUFFER_LEN, + pdata->urbdata, pdata->urb->transfer_dma); + usb_free_urb(pdata->urb); + } + if (!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_sync(&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, +}; +module_usb_driver(appledisplay_driver); + +MODULE_AUTHOR("Michael Hanselmann"); +MODULE_DESCRIPTION("Apple Cinema Display driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/brcmstb-usb-pinmap.c b/drivers/usb/misc/brcmstb-usb-pinmap.c new file mode 100644 index 000000000..1ce885e41 --- /dev/null +++ b/drivers/usb/misc/brcmstb-usb-pinmap.c @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2020, Broadcom */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct out_pin { + u32 enable_mask; + u32 value_mask; + u32 changed_mask; + u32 clr_changed_mask; + struct gpio_desc *gpiod; + const char *name; +}; + +struct in_pin { + u32 enable_mask; + u32 value_mask; + struct gpio_desc *gpiod; + const char *name; + struct brcmstb_usb_pinmap_data *pdata; +}; + +struct brcmstb_usb_pinmap_data { + void __iomem *regs; + int in_count; + struct in_pin *in_pins; + int out_count; + struct out_pin *out_pins; +}; + + +static void pinmap_set(void __iomem *reg, u32 mask) +{ + u32 val; + + val = readl(reg); + val |= mask; + writel(val, reg); +} + +static void pinmap_unset(void __iomem *reg, u32 mask) +{ + u32 val; + + val = readl(reg); + val &= ~mask; + writel(val, reg); +} + +static void sync_in_pin(struct in_pin *pin) +{ + u32 val; + + val = gpiod_get_value(pin->gpiod); + if (val) + pinmap_set(pin->pdata->regs, pin->value_mask); + else + pinmap_unset(pin->pdata->regs, pin->value_mask); +} + +/* + * Interrupt from override register, propagate from override bit + * to GPIO. + */ +static irqreturn_t brcmstb_usb_pinmap_ovr_isr(int irq, void *dev_id) +{ + struct brcmstb_usb_pinmap_data *pdata = dev_id; + struct out_pin *pout; + u32 val; + u32 bit; + int x; + + pr_debug("%s: reg: 0x%x\n", __func__, readl(pdata->regs)); + pout = pdata->out_pins; + for (x = 0; x < pdata->out_count; x++) { + val = readl(pdata->regs); + if (val & pout->changed_mask) { + pinmap_set(pdata->regs, pout->clr_changed_mask); + pinmap_unset(pdata->regs, pout->clr_changed_mask); + bit = val & pout->value_mask; + gpiod_set_value(pout->gpiod, bit ? 1 : 0); + pr_debug("%s: %s bit changed state to %d\n", + __func__, pout->name, bit ? 1 : 0); + } + } + return IRQ_HANDLED; +} + +/* + * Interrupt from GPIO, propagate from GPIO to override bit. + */ +static irqreturn_t brcmstb_usb_pinmap_gpio_isr(int irq, void *dev_id) +{ + struct in_pin *pin = dev_id; + + pr_debug("%s: %s pin changed state\n", __func__, pin->name); + sync_in_pin(pin); + return IRQ_HANDLED; +} + + +static void get_pin_counts(struct device_node *dn, int *in_count, + int *out_count) +{ + int in; + int out; + + *in_count = 0; + *out_count = 0; + in = of_property_count_strings(dn, "brcm,in-functions"); + if (in < 0) + return; + out = of_property_count_strings(dn, "brcm,out-functions"); + if (out < 0) + return; + *in_count = in; + *out_count = out; +} + +static int parse_pins(struct device *dev, struct device_node *dn, + struct brcmstb_usb_pinmap_data *pdata) +{ + struct out_pin *pout; + struct in_pin *pin; + int index; + int res; + int x; + + pin = pdata->in_pins; + for (x = 0, index = 0; x < pdata->in_count; x++) { + pin->gpiod = devm_gpiod_get_index(dev, "in", x, GPIOD_IN); + if (IS_ERR(pin->gpiod)) { + dev_err(dev, "Error getting gpio %s\n", pin->name); + return PTR_ERR(pin->gpiod); + + } + res = of_property_read_string_index(dn, "brcm,in-functions", x, + &pin->name); + if (res < 0) { + dev_err(dev, "Error getting brcm,in-functions for %s\n", + pin->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,in-masks", index++, + &pin->enable_mask); + if (res < 0) { + dev_err(dev, "Error getting 1st brcm,in-masks for %s\n", + pin->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,in-masks", index++, + &pin->value_mask); + if (res < 0) { + dev_err(dev, "Error getting 2nd brcm,in-masks for %s\n", + pin->name); + return res; + } + pin->pdata = pdata; + pin++; + } + pout = pdata->out_pins; + for (x = 0, index = 0; x < pdata->out_count; x++) { + pout->gpiod = devm_gpiod_get_index(dev, "out", x, + GPIOD_OUT_HIGH); + if (IS_ERR(pout->gpiod)) { + dev_err(dev, "Error getting gpio %s\n", pin->name); + return PTR_ERR(pout->gpiod); + } + res = of_property_read_string_index(dn, "brcm,out-functions", x, + &pout->name); + if (res < 0) { + dev_err(dev, "Error getting brcm,out-functions for %s\n", + pout->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,out-masks", index++, + &pout->enable_mask); + if (res < 0) { + dev_err(dev, "Error getting 1st brcm,out-masks for %s\n", + pout->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,out-masks", index++, + &pout->value_mask); + if (res < 0) { + dev_err(dev, "Error getting 2nd brcm,out-masks for %s\n", + pout->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,out-masks", index++, + &pout->changed_mask); + if (res < 0) { + dev_err(dev, "Error getting 3rd brcm,out-masks for %s\n", + pout->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,out-masks", index++, + &pout->clr_changed_mask); + if (res < 0) { + dev_err(dev, "Error getting 4th out-masks for %s\n", + pout->name); + return res; + } + pout++; + } + return 0; +} + +static void sync_all_pins(struct brcmstb_usb_pinmap_data *pdata) +{ + struct out_pin *pout; + struct in_pin *pin; + int val; + int x; + + /* + * Enable the override, clear any changed condition and + * propagate the state to the GPIO for all out pins. + */ + pout = pdata->out_pins; + for (x = 0; x < pdata->out_count; x++) { + pinmap_set(pdata->regs, pout->enable_mask); + pinmap_set(pdata->regs, pout->clr_changed_mask); + pinmap_unset(pdata->regs, pout->clr_changed_mask); + val = readl(pdata->regs) & pout->value_mask; + gpiod_set_value(pout->gpiod, val ? 1 : 0); + pout++; + } + + /* sync and enable all in pins. */ + pin = pdata->in_pins; + for (x = 0; x < pdata->in_count; x++) { + sync_in_pin(pin); + pinmap_set(pdata->regs, pin->enable_mask); + pin++; + } +} + +static int __init brcmstb_usb_pinmap_probe(struct platform_device *pdev) +{ + struct device_node *dn = pdev->dev.of_node; + struct brcmstb_usb_pinmap_data *pdata; + struct in_pin *pin; + struct resource *r; + int out_count; + int in_count; + int err; + int irq; + int x; + + get_pin_counts(dn, &in_count, &out_count); + if ((in_count + out_count) == 0) + return -EINVAL; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) + return -EINVAL; + + pdata = devm_kzalloc(&pdev->dev, + sizeof(*pdata) + + (sizeof(struct in_pin) * in_count) + + (sizeof(struct out_pin) * out_count), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->in_count = in_count; + pdata->out_count = out_count; + pdata->in_pins = (struct in_pin *)(pdata + 1); + pdata->out_pins = (struct out_pin *)(pdata->in_pins + in_count); + + pdata->regs = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (!pdata->regs) + return -ENOMEM; + platform_set_drvdata(pdev, pdata); + + err = parse_pins(&pdev->dev, dn, pdata); + if (err) + return err; + + sync_all_pins(pdata); + + if (out_count) { + + /* Enable interrupt for out pins */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + err = devm_request_irq(&pdev->dev, irq, + brcmstb_usb_pinmap_ovr_isr, + IRQF_TRIGGER_RISING, + pdev->name, pdata); + if (err < 0) { + dev_err(&pdev->dev, "Error requesting IRQ\n"); + return err; + } + } + + for (x = 0, pin = pdata->in_pins; x < pdata->in_count; x++, pin++) { + irq = gpiod_to_irq(pin->gpiod); + if (irq < 0) { + dev_err(&pdev->dev, "Error getting IRQ for %s pin\n", + pin->name); + return irq; + } + err = devm_request_irq(&pdev->dev, irq, + brcmstb_usb_pinmap_gpio_isr, + IRQF_SHARED | IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + pdev->name, pin); + if (err < 0) { + dev_err(&pdev->dev, "Error requesting IRQ for %s pin\n", + pin->name); + return err; + } + } + + dev_dbg(&pdev->dev, "Driver probe succeeded\n"); + dev_dbg(&pdev->dev, "In pin count: %d, out pin count: %d\n", + pdata->in_count, pdata->out_count); + return 0; +} + + +static const struct of_device_id brcmstb_usb_pinmap_of_match[] = { + { .compatible = "brcm,usb-pinmap" }, + { }, +}; +MODULE_DEVICE_TABLE(of, brcmstb_usb_pinmap_of_match); + +static struct platform_driver brcmstb_usb_pinmap_driver = { + .driver = { + .name = "brcm-usb-pinmap", + .of_match_table = brcmstb_usb_pinmap_of_match, + }, +}; + +static int __init brcmstb_usb_pinmap_init(void) +{ + return platform_driver_probe(&brcmstb_usb_pinmap_driver, + brcmstb_usb_pinmap_probe); +} + +module_init(brcmstb_usb_pinmap_init); +MODULE_AUTHOR("Al Cooper "); +MODULE_DESCRIPTION("Broadcom USB Pinmap Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/chaoskey.c b/drivers/usb/misc/chaoskey.c new file mode 100644 index 000000000..9c06f7775 --- /dev/null +++ b/drivers/usb/misc/chaoskey.c @@ -0,0 +1,605 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * chaoskey - driver for ChaosKey device from Altus Metrum. + * + * This device provides true random numbers using a noise source based + * on a reverse-biased p-n junction in avalanche breakdown. More + * details can be found at http://chaoskey.org + * + * The driver connects to the kernel hardware RNG interface to provide + * entropy for /dev/random and other kernel activities. It also offers + * a separate /dev/ entry to allow for direct access to the random + * bit stream. + * + * Copyright © 2015 Keith Packard + */ + +#include +#include +#include +#include +#include +#include +#include + +static struct usb_driver chaoskey_driver; +static struct usb_class_driver chaoskey_class; +static int chaoskey_rng_read(struct hwrng *rng, void *data, + size_t max, bool wait); + +static DEFINE_MUTEX(chaoskey_list_lock); + +#define usb_dbg(usb_if, format, arg...) \ + dev_dbg(&(usb_if)->dev, format, ## arg) + +#define usb_err(usb_if, format, arg...) \ + dev_err(&(usb_if)->dev, format, ## arg) + +/* Version Information */ +#define DRIVER_AUTHOR "Keith Packard, keithp@keithp.com" +#define DRIVER_DESC "Altus Metrum ChaosKey driver" +#define DRIVER_SHORT "chaoskey" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define CHAOSKEY_VENDOR_ID 0x1d50 /* OpenMoko */ +#define CHAOSKEY_PRODUCT_ID 0x60c6 /* ChaosKey */ + +#define ALEA_VENDOR_ID 0x12d8 /* Araneus */ +#define ALEA_PRODUCT_ID 0x0001 /* Alea I */ + +#define CHAOSKEY_BUF_LEN 64 /* max size of USB full speed packet */ + +#define NAK_TIMEOUT (HZ) /* normal stall/wait timeout */ +#define ALEA_FIRST_TIMEOUT (HZ*3) /* first stall/wait timeout for Alea */ + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define USB_CHAOSKEY_MINOR_BASE 0 +#else + +/* IOWARRIOR_MINOR_BASE + 16, not official yet */ +#define USB_CHAOSKEY_MINOR_BASE 224 +#endif + +static const struct usb_device_id chaoskey_table[] = { + { USB_DEVICE(CHAOSKEY_VENDOR_ID, CHAOSKEY_PRODUCT_ID) }, + { USB_DEVICE(ALEA_VENDOR_ID, ALEA_PRODUCT_ID) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, chaoskey_table); + +static void chaos_read_callback(struct urb *urb); + +/* Driver-local specific stuff */ +struct chaoskey { + struct usb_interface *interface; + char in_ep; + struct mutex lock; + struct mutex rng_lock; + int open; /* open count */ + bool present; /* device not disconnected */ + bool reading; /* ongoing IO */ + bool reads_started; /* track first read for Alea */ + int size; /* size of buf */ + int valid; /* bytes of buf read */ + int used; /* bytes of buf consumed */ + char *name; /* product + serial */ + struct hwrng hwrng; /* Embedded struct for hwrng */ + int hwrng_registered; /* registered with hwrng API */ + wait_queue_head_t wait_q; /* for timeouts */ + struct urb *urb; /* for performing IO */ + char *buf; +}; + +static void chaoskey_free(struct chaoskey *dev) +{ + if (dev) { + usb_dbg(dev->interface, "free"); + usb_free_urb(dev->urb); + kfree(dev->name); + kfree(dev->buf); + usb_put_intf(dev->interface); + kfree(dev); + } +} + +static int chaoskey_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_host_interface *altsetting = interface->cur_altsetting; + struct usb_endpoint_descriptor *epd; + int in_ep; + struct chaoskey *dev; + int result = -ENOMEM; + int size; + int res; + + usb_dbg(interface, "probe %s-%s", udev->product, udev->serial); + + /* Find the first bulk IN endpoint and its packet size */ + res = usb_find_bulk_in_endpoint(altsetting, &epd); + if (res) { + usb_dbg(interface, "no IN endpoint found"); + return res; + } + + in_ep = usb_endpoint_num(epd); + size = usb_endpoint_maxp(epd); + + /* Validate endpoint and size */ + if (size <= 0) { + usb_dbg(interface, "invalid size (%d)", size); + return -ENODEV; + } + + if (size > CHAOSKEY_BUF_LEN) { + usb_dbg(interface, "size reduced from %d to %d\n", + size, CHAOSKEY_BUF_LEN); + size = CHAOSKEY_BUF_LEN; + } + + /* Looks good, allocate and initialize */ + + dev = kzalloc_obj(struct chaoskey); + + if (dev == NULL) + goto out; + + dev->interface = usb_get_intf(interface); + + dev->buf = kmalloc(size, GFP_KERNEL); + + if (dev->buf == NULL) + goto out; + + dev->urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!dev->urb) + goto out; + + usb_fill_bulk_urb(dev->urb, + udev, + usb_rcvbulkpipe(udev, in_ep), + dev->buf, + size, + chaos_read_callback, + dev); + + /* Construct a name using the product and serial values. Each + * device needs a unique name for the hwrng code + */ + + if (udev->product && udev->serial) { + dev->name = kasprintf(GFP_KERNEL, "%s-%s", udev->product, + udev->serial); + if (dev->name == NULL) + goto out; + } + + dev->in_ep = in_ep; + + if (le16_to_cpu(udev->descriptor.idVendor) != ALEA_VENDOR_ID) + dev->reads_started = true; + + dev->size = size; + dev->present = true; + + init_waitqueue_head(&dev->wait_q); + + mutex_init(&dev->lock); + mutex_init(&dev->rng_lock); + + usb_set_intfdata(interface, dev); + + result = usb_register_dev(interface, &chaoskey_class); + if (result) { + usb_err(interface, "Unable to allocate minor number."); + goto out; + } + + dev->hwrng.name = dev->name ? dev->name : chaoskey_driver.name; + dev->hwrng.read = chaoskey_rng_read; + + dev->hwrng_registered = (hwrng_register(&dev->hwrng) == 0); + if (!dev->hwrng_registered) + usb_err(interface, "Unable to register with hwrng"); + + usb_enable_autosuspend(udev); + + usb_dbg(interface, "chaoskey probe success, size %d", dev->size); + return 0; + +out: + usb_set_intfdata(interface, NULL); + chaoskey_free(dev); + return result; +} + +static void chaoskey_disconnect(struct usb_interface *interface) +{ + struct chaoskey *dev; + + usb_dbg(interface, "disconnect"); + dev = usb_get_intfdata(interface); + if (!dev) { + usb_dbg(interface, "disconnect failed - no dev"); + return; + } + + if (dev->hwrng_registered) + hwrng_unregister(&dev->hwrng); + + usb_deregister_dev(interface, &chaoskey_class); + + usb_set_intfdata(interface, NULL); + mutex_lock(&chaoskey_list_lock); + mutex_lock(&dev->lock); + + dev->present = false; + usb_poison_urb(dev->urb); + + if (!dev->open) { + mutex_unlock(&dev->lock); + chaoskey_free(dev); + } else + mutex_unlock(&dev->lock); + + mutex_unlock(&chaoskey_list_lock); + usb_dbg(interface, "disconnect done"); +} + +static int chaoskey_open(struct inode *inode, struct file *file) +{ + struct chaoskey *dev; + struct usb_interface *interface; + int rv = 0; + + /* get the interface from minor number and driver information */ + interface = usb_find_interface(&chaoskey_driver, iminor(inode)); + if (!interface) + return -ENODEV; + + usb_dbg(interface, "open"); + + dev = usb_get_intfdata(interface); + if (!dev) { + usb_dbg(interface, "open (dev)"); + return -ENODEV; + } + + file->private_data = dev; + mutex_lock(&chaoskey_list_lock); + mutex_lock(&dev->lock); + if (dev->present) + ++dev->open; + else + rv = -ENODEV; + mutex_unlock(&dev->lock); + mutex_unlock(&chaoskey_list_lock); + + return rv; +} + +static int chaoskey_release(struct inode *inode, struct file *file) +{ + struct chaoskey *dev = file->private_data; + struct usb_interface *interface; + int rv = 0; + + if (dev == NULL) + return -ENODEV; + + interface = dev->interface; + + usb_dbg(interface, "release"); + + mutex_lock(&chaoskey_list_lock); + mutex_lock(&dev->lock); + + usb_dbg(interface, "open count at release is %d", dev->open); + + if (dev->open <= 0) { + usb_dbg(interface, "invalid open count (%d)", dev->open); + rv = -ENODEV; + goto bail; + } + + --dev->open; + + if (!dev->present) { + if (dev->open == 0) { + mutex_unlock(&dev->lock); + chaoskey_free(dev); + goto destruction; + } + } +bail: + mutex_unlock(&dev->lock); +destruction: + mutex_unlock(&chaoskey_list_lock); + return rv; +} + +static void chaos_read_callback(struct urb *urb) +{ + struct chaoskey *dev = urb->context; + int status = urb->status; + + usb_dbg(dev->interface, "callback status (%d)", status); + + if (status == 0) + dev->valid = urb->actual_length; + else + dev->valid = 0; + + dev->used = 0; + + /* must be seen first before validity is announced */ + smp_wmb(); + + dev->reading = false; + wake_up(&dev->wait_q); +} + +/* Fill the buffer. Called with dev->lock held + */ +static int _chaoskey_fill(struct chaoskey *dev) +{ + DEFINE_WAIT(wait); + int result; + bool started; + + usb_dbg(dev->interface, "fill"); + + /* Return immediately if someone called before the buffer was + * empty */ + if (dev->valid != dev->used) { + usb_dbg(dev->interface, "not empty yet (valid %d used %d)", + dev->valid, dev->used); + return 0; + } + + /* Bail if the device has been removed */ + if (!dev->present) { + usb_dbg(dev->interface, "device not present"); + return -ENODEV; + } + + /* Make sure the device is awake */ + result = usb_autopm_get_interface(dev->interface); + if (result) { + usb_dbg(dev->interface, "wakeup failed (result %d)", result); + return result; + } + + dev->reading = true; + result = usb_submit_urb(dev->urb, GFP_KERNEL); + if (result < 0) { + result = usb_translate_errors(result); + dev->reading = false; + goto out; + } + + /* The first read on the Alea takes a little under 2 seconds. + * Reads after the first read take only a few microseconds + * though. Presumably the entropy-generating circuit needs + * time to ramp up. So, we wait longer on the first read. + */ + started = dev->reads_started; + dev->reads_started = true; + result = wait_event_interruptible_timeout( + dev->wait_q, + !dev->reading, + (started ? NAK_TIMEOUT : ALEA_FIRST_TIMEOUT) ); + + if (result < 0) { + usb_kill_urb(dev->urb); + goto out; + } + + if (result == 0) { + result = -ETIMEDOUT; + usb_kill_urb(dev->urb); + } else { + result = dev->valid; + } +out: + /* Let the device go back to sleep eventually */ + usb_autopm_put_interface(dev->interface); + + usb_dbg(dev->interface, "read %d bytes", dev->valid); + + return result; +} + +static ssize_t chaoskey_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *ppos) +{ + struct chaoskey *dev; + ssize_t read_count = 0; + int this_time; + int result = 0; + unsigned long remain; + + dev = file->private_data; + + if (dev == NULL || !dev->present) + return -ENODEV; + + usb_dbg(dev->interface, "read %zu", count); + + while (count > 0) { + + /* Grab the rng_lock briefly to ensure that the hwrng interface + * gets priority over other user access + */ + result = mutex_lock_interruptible(&dev->rng_lock); + if (result) + goto bail; + mutex_unlock(&dev->rng_lock); + + if (file->f_flags & O_NONBLOCK) { + result = mutex_trylock(&dev->lock); + if (result == 0) { + result = -EAGAIN; + goto bail; + } else { + result = 0; + } + } else { + result = mutex_lock_interruptible(&dev->lock); + if (result) + goto bail; + } + if (dev->valid == dev->used) { + result = _chaoskey_fill(dev); + if (result < 0) { + mutex_unlock(&dev->lock); + goto bail; + } + } + + this_time = dev->valid - dev->used; + if (this_time > count) + this_time = count; + + remain = copy_to_user(buffer, dev->buf + dev->used, this_time); + if (remain) { + result = -EFAULT; + + /* Consume the bytes that were copied so we don't leak + * data to user space + */ + dev->used += this_time - remain; + mutex_unlock(&dev->lock); + goto bail; + } + + count -= this_time; + read_count += this_time; + buffer += this_time; + dev->used += this_time; + mutex_unlock(&dev->lock); + } +bail: + if (read_count) { + usb_dbg(dev->interface, "read %zu bytes", read_count); + return read_count; + } + usb_dbg(dev->interface, "empty read, result %d", result); + if (result == -ETIMEDOUT) + result = -EAGAIN; + return result; +} + +static int chaoskey_rng_read(struct hwrng *rng, void *data, + size_t max, bool wait) +{ + struct chaoskey *dev = container_of(rng, struct chaoskey, hwrng); + int this_time; + + usb_dbg(dev->interface, "rng_read max %zu wait %d", max, wait); + + if (!dev->present) { + usb_dbg(dev->interface, "device not present"); + return 0; + } + + /* Hold the rng_lock until we acquire the device lock so that + * this operation gets priority over other user access to the + * device + */ + mutex_lock(&dev->rng_lock); + + mutex_lock(&dev->lock); + + mutex_unlock(&dev->rng_lock); + + /* Try to fill the buffer if empty. It doesn't actually matter + * if _chaoskey_fill works; we'll just return zero bytes as + * the buffer will still be empty + */ + if (dev->valid == dev->used) + (void) _chaoskey_fill(dev); + + this_time = dev->valid - dev->used; + if (this_time > max) + this_time = max; + + memcpy(data, dev->buf + dev->used, this_time); + + dev->used += this_time; + + mutex_unlock(&dev->lock); + + usb_dbg(dev->interface, "rng_read this_time %d\n", this_time); + return this_time; +} + +#ifdef CONFIG_PM +static int chaoskey_suspend(struct usb_interface *interface, + pm_message_t message) +{ + usb_dbg(interface, "suspend"); + return 0; +} + +static int chaoskey_resume(struct usb_interface *interface) +{ + struct chaoskey *dev; + struct usb_device *udev = interface_to_usbdev(interface); + + usb_dbg(interface, "resume"); + dev = usb_get_intfdata(interface); + + /* + * We may have lost power. + * In that case the device that needs a long time + * for the first requests needs an extended timeout + * again + */ + if (le16_to_cpu(udev->descriptor.idVendor) == ALEA_VENDOR_ID) + dev->reads_started = false; + + return 0; +} +#else +#define chaoskey_suspend NULL +#define chaoskey_resume NULL +#endif + +/* file operation pointers */ +static const struct file_operations chaoskey_fops = { + .owner = THIS_MODULE, + .read = chaoskey_read, + .open = chaoskey_open, + .release = chaoskey_release, + .llseek = default_llseek, +}; + +/* class driver information */ +static struct usb_class_driver chaoskey_class = { + .name = "chaoskey%d", + .fops = &chaoskey_fops, + .minor_base = USB_CHAOSKEY_MINOR_BASE, +}; + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver chaoskey_driver = { + .name = DRIVER_SHORT, + .probe = chaoskey_probe, + .disconnect = chaoskey_disconnect, + .suspend = chaoskey_suspend, + .resume = chaoskey_resume, + .reset_resume = chaoskey_resume, + .id_table = chaoskey_table, + .supports_autosuspend = 1, +}; + +module_usb_driver(chaoskey_driver); + diff --git a/drivers/usb/misc/cypress_cy7c63.c b/drivers/usb/misc/cypress_cy7c63.c new file mode 100644 index 000000000..ecc81c93c --- /dev/null +++ b/drivers/usb/misc/cypress_cy7c63.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* 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 +*/ + +#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) { + 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); + /* we must not process garbage */ + if (retval < 2) { + if (retval >= 0) + retval = -EIO; + goto err_buf; + } + + /* 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; + } + +err_buf: + 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 port0_store(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 port1_store(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); + if (result < 0) + return result; + + return sprintf(buf, "%d", cyp->port[port_num]); +} + +/* attribute callback handler (read) */ +static ssize_t port0_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return read_port(dev, attr, buf, 0, CYPRESS_READ_PORT_ID0); +} +static DEVICE_ATTR_RW(port0); + +/* attribute callback handler (read) */ +static ssize_t port1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return read_port(dev, attr, buf, 1, CYPRESS_READ_PORT_ID1); +} +static DEVICE_ATTR_RW(port1); + +static struct attribute *cypress_attrs[] = { + &dev_attr_port0.attr, + &dev_attr_port1.attr, + NULL, +}; +ATTRIBUTE_GROUPS(cypress); + +static int cypress_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct cypress *dev; + int retval = -ENOMEM; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc_obj(*dev); + if (!dev) + goto error_mem; + + dev->udev = interface_to_usbdev(interface); + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + /* let the user know that the device is now attached */ + dev_info(&interface->dev, + "Cypress CY7C63xxx device now attached\n"); + return 0; + +error_mem: + return retval; +} + +static void cypress_disconnect(struct usb_interface *interface) +{ + struct cypress *dev; + + dev = usb_get_intfdata(interface); + + /* the intfdata can be set to NULL only after the + * device files have been removed */ + usb_set_intfdata(interface, NULL); + + 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, + .dev_groups = cypress_groups, +}; + +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 000000000..b183df982 --- /dev/null +++ b/drivers/usb/misc/cytherm.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0 +/* -*- 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. + */ + + +#include +#include +#include +#include +#include + +#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; +}; + + +/* 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 brightness_show(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 brightness_store(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) + 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_RW(brightness); + + +#define TEMP 0x33 /* RAM location for temperature */ +#define SIGN 0x34 /* RAM location for temperature sign */ + +static ssize_t temp_show(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) + 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 DEVICE_ATTR_RO(temp); + + +#define BUTTON 0x7a + +static ssize_t button_show(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) + 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 DEVICE_ATTR_RO(button); + + +static ssize_t port0_show(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) + 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 port0_store(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) + 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_RW(port0); + +static ssize_t port1_show(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) + 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 port1_store(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) + 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_RW(port1); + +static struct attribute *cytherm_attrs[] = { + &dev_attr_brightness.attr, + &dev_attr_temp.attr, + &dev_attr_button.attr, + &dev_attr_port0.attr, + &dev_attr_port1.attr, + NULL, +}; +ATTRIBUTE_GROUPS(cytherm); + +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; + int retval = -ENOMEM; + + dev = kzalloc_obj(struct usb_cytherm); + if (!dev) + goto error_mem; + + dev->udev = udev; + + usb_set_intfdata(interface, dev); + + dev->brightness = 0xFF; + + dev_info(&interface->dev, + "Cypress thermometer device now attached\n"); + return 0; + +error_mem: + return retval; +} + +static void cytherm_disconnect(struct usb_interface *interface) +{ + struct usb_cytherm *dev; + + dev = usb_get_intfdata(interface); + + /* first remove the files, then NULL the pointer */ + usb_set_intfdata(interface, NULL); + + kfree(dev); + + dev_info(&interface->dev, "Cypress thermometer now disconnected\n"); +} + +/* 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, + .dev_groups = cytherm_groups, +}; + +module_usb_driver(cytherm_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/ehset.c b/drivers/usb/misc/ehset.c new file mode 100644 index 000000000..36b6e9fa7 --- /dev/null +++ b/drivers/usb/misc/ehset.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include + +#define TEST_SE0_NAK_PID 0x0101 +#define TEST_J_PID 0x0102 +#define TEST_K_PID 0x0103 +#define TEST_PACKET_PID 0x0104 +#define TEST_HS_HOST_PORT_SUSPEND_RESUME 0x0106 +#define TEST_SINGLE_STEP_GET_DEV_DESC 0x0107 +#define TEST_SINGLE_STEP_SET_FEATURE 0x0108 + +extern const struct usb_device_id *usb_device_match_id(struct usb_device *udev, + const struct usb_device_id *id); + +/* + * A list of USB hubs which requires to disable the power + * to the port before starting the testing procedures. + */ +static const struct usb_device_id ehset_hub_list[] = { + { USB_DEVICE(0x0424, 0x4502) }, + { USB_DEVICE(0x0424, 0x4913) }, + { USB_DEVICE(0x0451, 0x8027) }, + { } +}; + +static int ehset_prepare_port_for_testing(struct usb_device *hub_udev, u16 portnum) +{ + int ret = 0; + + /* + * The USB2.0 spec chapter 11.24.2.13 says that the USB port which is + * going under test needs to be put in suspend before sending the + * test command. Most hubs don't enforce this precondition, but there + * are some hubs which needs to disable the power to the port before + * starting the test. + */ + if (usb_device_match_id(hub_udev, ehset_hub_list)) { + ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE, + USB_RT_PORT, USB_PORT_FEAT_ENABLE, + portnum, NULL, 0, 1000, GFP_KERNEL); + /* + * Wait for the port to be disabled. It's an arbitrary value + * which worked every time. + */ + msleep(100); + } else { + /* + * For the hubs which are compliant with the spec, + * put the port in SUSPEND. + */ + ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE, + USB_RT_PORT, USB_PORT_FEAT_SUSPEND, + portnum, NULL, 0, 1000, GFP_KERNEL); + } + return ret; +} + +static int ehset_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int ret = -EINVAL; + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_device *hub_udev = dev->parent; + struct usb_device_descriptor buf; + u8 portnum = dev->portnum; + u16 test_pid = le16_to_cpu(dev->descriptor.idProduct); + + switch (test_pid) { + case TEST_SE0_NAK_PID: + ret = ehset_prepare_port_for_testing(hub_udev, portnum); + if (ret < 0) + break; + ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE, + USB_RT_PORT, USB_PORT_FEAT_TEST, + (USB_TEST_SE0_NAK << 8) | portnum, + NULL, 0, 1000, GFP_KERNEL); + break; + case TEST_J_PID: + ret = ehset_prepare_port_for_testing(hub_udev, portnum); + if (ret < 0) + break; + ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE, + USB_RT_PORT, USB_PORT_FEAT_TEST, + (USB_TEST_J << 8) | portnum, NULL, 0, + 1000, GFP_KERNEL); + break; + case TEST_K_PID: + ret = ehset_prepare_port_for_testing(hub_udev, portnum); + if (ret < 0) + break; + ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE, + USB_RT_PORT, USB_PORT_FEAT_TEST, + (USB_TEST_K << 8) | portnum, NULL, 0, + 1000, GFP_KERNEL); + break; + case TEST_PACKET_PID: + ret = ehset_prepare_port_for_testing(hub_udev, portnum); + if (ret < 0) + break; + ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE, + USB_RT_PORT, USB_PORT_FEAT_TEST, + (USB_TEST_PACKET << 8) | portnum, + NULL, 0, 1000, GFP_KERNEL); + break; + case TEST_HS_HOST_PORT_SUSPEND_RESUME: + /* Test: wait for 15secs -> suspend -> 15secs delay -> resume */ + msleep(15 * 1000); + ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE, + USB_RT_PORT, USB_PORT_FEAT_SUSPEND, + portnum, NULL, 0, 1000, GFP_KERNEL); + if (ret < 0) + break; + + msleep(15 * 1000); + ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE, + USB_RT_PORT, USB_PORT_FEAT_SUSPEND, + portnum, NULL, 0, 1000, GFP_KERNEL); + break; + case TEST_SINGLE_STEP_GET_DEV_DESC: + /* Test: wait for 15secs -> GetDescriptor request */ + msleep(15 * 1000); + + ret = usb_control_msg_recv(dev, 0, USB_REQ_GET_DESCRIPTOR, + USB_DIR_IN, USB_DT_DEVICE << 8, 0, + &buf, USB_DT_DEVICE_SIZE, + USB_CTRL_GET_TIMEOUT, GFP_KERNEL); + break; + case TEST_SINGLE_STEP_SET_FEATURE: + /* + * GetDescriptor SETUP request -> 15secs delay -> IN & STATUS + * + * Note, this test is only supported on root hubs since the + * SetPortFeature handling can only be done inside the HCD's + * hub_control callback function. + */ + if (hub_udev != dev->bus->root_hub) { + dev_err(&intf->dev, "SINGLE_STEP_SET_FEATURE test only supported on root hub\n"); + break; + } + + ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE, + USB_RT_PORT, USB_PORT_FEAT_TEST, + (6 << 8) | portnum, NULL, 0, + 60 * 1000, GFP_KERNEL); + + break; + default: + dev_err(&intf->dev, "%s: unsupported PID: 0x%x\n", + __func__, test_pid); + } + + return ret; +} + +static void ehset_disconnect(struct usb_interface *intf) +{ +} + +static const struct usb_device_id ehset_id_table[] = { + { USB_DEVICE(0x1a0a, TEST_SE0_NAK_PID) }, + { USB_DEVICE(0x1a0a, TEST_J_PID) }, + { USB_DEVICE(0x1a0a, TEST_K_PID) }, + { USB_DEVICE(0x1a0a, TEST_PACKET_PID) }, + { USB_DEVICE(0x1a0a, TEST_HS_HOST_PORT_SUSPEND_RESUME) }, + { USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_GET_DEV_DESC) }, + { USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_SET_FEATURE) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, ehset_id_table); + +static struct usb_driver ehset_driver = { + .name = "usb_ehset_test", + .probe = ehset_probe, + .disconnect = ehset_disconnect, + .id_table = ehset_id_table, +}; + +module_usb_driver(ehset_driver); + +MODULE_DESCRIPTION("USB Driver for EHSET Test Fixture"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/emi26.c b/drivers/usb/misc/emi26.c new file mode 100644 index 000000000..24d841850 --- /dev/null +++ b/drivers/usb/misc/emi26.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Emagic EMI 2|6 usb audio interface firmware loader. + * Copyright (C) 2002 + * Tapio Laxström (tapio.laxstrom@iptime.fi) + * + * emi26.c,v 1.13 2002/03/08 13:10:26 tapio Exp + */ +#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 000000000..3eea60437 --- /dev/null +++ b/drivers/usb/misc/emi62.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Emagic EMI 2|6 usb audio interface firmware loader. + * Copyright (C) 2002 + * Tapio Laxström (tapio.laxstrom@iptime.fi) + */ +#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 000000000..7ab4856ab --- /dev/null +++ b/drivers/usb/misc/ezusb.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * EZ-USB specific functions used by some of the USB to Serial drivers. + * + * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com) + */ + +#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 const 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) +{ + if (!dev) + return -ENODEV; + + return usb_control_msg_send(dev, 0, request, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + address, 0, data, length, 3000, GFP_KERNEL); +} + +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_DESCRIPTION("EZUSB device support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/idmouse.c b/drivers/usb/misc/idmouse.c new file mode 100644 index 000000000..3e37adf2b --- /dev/null +++ b/drivers/usb/misc/idmouse.c @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Siemens ID Mouse driver v0.6 + + 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) + +#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); + +/* structure to hold all of our device specific stuff */ +struct usb_idmouse { + struct kref kref; + + 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; + } + + /* 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; + + /* reset the device */ +reset: + ftip_command(dev, FTIP_RELEASE, 0, 0); + + /* 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 kref *kref) +{ + struct usb_idmouse *dev = container_of(kref, struct usb_idmouse, kref); + + 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; + + /* get the device information block from the interface */ + dev = usb_get_intfdata(interface); + if (!dev) + return -ENODEV; + + /* lock this device */ + mutex_lock(&dev->lock); + + /* 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); + usb_autopm_put_interface(interface); + if (result) + goto error; + + /* increment our usage count for the driver */ + ++dev->open; + + kref_get(&dev->kref); + + /* 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; + + /* lock our device */ + mutex_lock(&dev->lock); + --dev->open; + mutex_unlock(&dev->lock); + + kref_put(&dev->kref, idmouse_delete); + + 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->cur_altsetting; + if (iface_desc->desc.bInterfaceClass != 0x0A) + return -ENODEV; + + if (iface_desc->desc.bNumEndpoints < 1) + return -ENODEV; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc_obj(*dev); + if (dev == NULL) + return -ENOMEM; + + kref_init(&dev->kref); + mutex_init(&dev->lock); + dev->udev = udev; + dev->interface = interface; + + /* set up the endpoint information - use only the first bulk-in endpoint */ + result = usb_find_bulk_in_endpoint(iface_desc, &endpoint); + if (result) { + dev_err(&interface->dev, "Unable to find bulk-in endpoint.\n"); + goto err_put_kref; + } + + 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) { + result = -ENOMEM; + goto err_put_kref; + } + + /* 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, "Unable to allocate minor number.\n"); + goto err_put_kref; + } + + /* be noisy */ + dev_info(&interface->dev,"%s now attached\n",DRIVER_DESC); + + return 0; + +err_put_kref: + kref_put(&dev->kref, idmouse_delete); + + return result; +} + +static void idmouse_disconnect(struct usb_interface *interface) +{ + struct usb_idmouse *dev = usb_get_intfdata(interface); + + /* give back our minor */ + usb_deregister_dev(interface, &idmouse_class); + + /* lock the device */ + mutex_lock(&dev->lock); + + /* prevent device read, write and ioctl */ + dev->present = 0; + + mutex_unlock(&dev->lock); + + kref_put(&dev->kref, idmouse_delete); + + 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 000000000..de2b236ef --- /dev/null +++ b/drivers/usb/misc/iowarrior.c @@ -0,0 +1,926 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Native support for the I/O-Warrior USB devices + * + * Copyright (c) 2003-2005, 2020 Code Mercenaries GmbH + * written by Christian Lucht and + * Christoph Jung + * + * based on + + * usb-skeleton.c by Greg Kroah-Hartman + * brlvger.c by Stephane Dalton + * and Stephane Doyon + * + * Released under the GPLv2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_AUTHOR "Christian Lucht " +#define DRIVER_DESC "USB IO-Warrior driver" + +#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 +/* fuller speed iowarrior */ +#define USB_DEVICE_ID_CODEMERCS_IOW28 0x1504 +#define USB_DEVICE_ID_CODEMERCS_IOW28L 0x1505 +#define USB_DEVICE_ID_CODEMERCS_IOW100 0x1506 + +/* OEMed devices */ +#define USB_DEVICE_ID_CODEMERCS_IOW24SAG 0x158a +#define USB_DEVICE_ID_CODEMERCS_IOW56AM 0x158b + +/* 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 + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static struct usb_driver iowarrior_driver; + +/*--------------*/ +/* data */ +/*--------------*/ + +/* Structure to hold all of our device specific stuff */ +struct iowarrior { + struct kref kref; + 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 */ + 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; + 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; + struct usb_anchor submitted; +}; + +/*--------------*/ +/* globals */ +/*--------------*/ + +//#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), + HID_REQ_GET_REPORT, + USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, (type << 8) + id, + inter->desc.bInterfaceNumber, buf, size, + USB_CTRL_GET_TIMEOUT); +} +//#endif + +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), + HID_REQ_SET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, + (type << 8) + id, + intf->cur_altsetting->desc.bInterfaceNumber, buf, + size, 1000); +} + +/*---------------------*/ +/* 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)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW24SAG)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW56AM)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW28)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW28L)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW100)}, + {} /* 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; + } + + 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 */ + 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); + /* 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)) { + dev_dbg(&dev->interface->dev, + "nonzero write bulk status received: %d\n", status); + } + /* free up our allocated buffer */ + kfree(urb->transfer_buffer); + /* 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 kref *kref) +{ + struct iowarrior *dev = container_of(kref, struct iowarrior, kref); + + kfree(dev->int_in_buffer); + usb_free_urb(dev->int_in_urb); + kfree(dev->read_queue); + usb_put_intf(dev->interface); + 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; + int retval; + + dev = file->private_data; + + if (file->f_flags & O_NONBLOCK) { + retval = mutex_trylock(&dev->mutex); + if (!retval) + return -EAGAIN; + } else { + retval = mutex_lock_interruptible(&dev->mutex); + if (retval) + return -ERESTARTSYS; + } + + /* verify that the device wasn't unplugged */ + if (!dev->present) { + retval = -ENODEV; + goto exit; + } + + /* read count must be packet size (+ time stamp) */ + if ((count != dev->report_size) + && (count != (dev->report_size + 1))) { + retval = -EINVAL; + goto exit; + } + + /* repeat until no buffer overrun in callback handler occur */ + do { + atomic_set(&dev->overflow_flag, 0); + if ((read_idx = read_index(dev)) == -1) { + /* queue empty */ + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto exit; + } + 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 + retval = -ERESTART; + goto exit; + } + if (!dev->present) { + //The device was unplugged + retval = -ENODEV; + goto exit; + } + if (read_idx == -1) { + // Can this happen ??? + retval = 0; + goto exit; + } + } + } + + offset = read_idx * (dev->report_size + 1); + if (copy_to_user(buffer, dev->read_queue + offset, count)) { + retval = -EFAULT; + goto exit; + } + } while (atomic_read(&dev->overflow_flag)); + + read_idx = ++read_idx == MAX_INTERRUPT_BUFFER ? 0 : read_idx; + atomic_set(&dev->read_idx, read_idx); + mutex_unlock(&dev->mutex); + return count; + +exit: + mutex_unlock(&dev->mutex); + return retval; +} + +/* + * 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; + char *buf = NULL; /* for IOW24 and IOW56 we need a buffer */ + struct urb *int_out_urb = NULL; + + dev = file->private_data; + + retval = mutex_lock_interruptible(&dev->mutex); + if (retval < 0) + return -EINTR; + + /* verify that the device wasn't unplugged */ + if (!dev->present) { + retval = -ENODEV; + goto exit; + } + /* 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_IOW24SAG: + 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 = memdup_user(user_buffer, count); + if (IS_ERR(buf)) { + retval = PTR_ERR(buf); + goto exit; + } + retval = usb_set_report(dev->interface, 2, 0, buf, count); + kfree(buf); + goto exit; + case USB_DEVICE_ID_CODEMERCS_IOW56: + case USB_DEVICE_ID_CODEMERCS_IOW56AM: + case USB_DEVICE_ID_CODEMERCS_IOW28: + case USB_DEVICE_ID_CODEMERCS_IOW28L: + case USB_DEVICE_ID_CODEMERCS_IOW100: + /* 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; + goto error_no_urb; + } + buf = kmalloc(dev->report_size, GFP_KERNEL); + if (!buf) { + retval = -ENOMEM; + dev_dbg(&dev->interface->dev, + "Unable to allocate buffer\n"); + 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); + if (copy_from_user(buf, user_buffer, count)) { + retval = -EFAULT; + goto error; + } + usb_anchor_urb(int_out_urb, &dev->submitted); + retval = usb_submit_urb(int_out_urb, GFP_KERNEL); + if (retval) { + dev_dbg(&dev->interface->dev, + "submit error %d for urb nr.%d\n", + retval, atomic_read(&dev->write_busy)); + usb_unanchor_urb(int_out_urb); + goto error; + } + /* submit was ok */ + retval = count; + usb_free_urb(int_out_urb); + goto exit; + 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; + } +error: + kfree(buf); +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) + return -ENODEV; + + buffer = kzalloc(dev->report_size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + mutex_lock(&dev->mutex); + + /* verify that the device wasn't unplugged */ + if (!dev->present) { + retval = -ENODEV; + goto error_out; + } + + retval = 0; + switch (cmd) { + case IOW_WRITE: + if (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW24 || + dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW24SAG || + 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 = 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); + 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; + + subminor = iminor(inode); + + interface = usb_find_interface(&iowarrior_driver, subminor); + if (!interface) { + pr_err("%s - error, can't find device for minor %d\n", + __func__, subminor); + return -ENODEV; + } + + dev = usb_get_intfdata(interface); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->mutex); + + /* 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; + + kref_get(&dev->kref); + + /* save our object in the file's private structure */ + file->private_data = dev; + retval = 0; + +out: + mutex_unlock(&dev->mutex); + return retval; +} + +/* + * iowarrior_release + */ +static int iowarrior_release(struct inode *inode, struct file *file) +{ + struct iowarrior *dev; + + dev = file->private_data; + if (!dev) + return -ENODEV; + + /* lock our device */ + mutex_lock(&dev->mutex); + dev->opened = 0; /* we're closing now */ + + if (dev->present) { + usb_kill_urb(dev->int_in_urb); + wake_up_interruptible(&dev->read_wait); + wake_up_interruptible(&dev->write_wait); + } + mutex_unlock(&dev->mutex); + + kref_put(&dev->kref, iowarrior_delete); + + return 0; +} + +static __poll_t iowarrior_poll(struct file *file, poll_table * wait) +{ + struct iowarrior *dev = file->private_data; + __poll_t mask = 0; + + if (!dev->present) + return EPOLLERR | EPOLLHUP; + + poll_wait(file, &dev->read_wait, wait); + poll_wait(file, &dev->write_wait, wait); + + if (!dev->present) + return EPOLLERR | EPOLLHUP; + + if (read_index(dev) != -1) + mask |= EPOLLIN | EPOLLRDNORM; + + if (atomic_read(&dev->write_busy) < MAX_WRITES_IN_FLIGHT) + mask |= EPOLLOUT | EPOLLWRNORM; + 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(const 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; + int retval = -ENOMEM; + int res; + int minor; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc_obj(struct iowarrior); + if (!dev) + return retval; + + kref_init(&dev->kref); + mutex_init(&dev->mutex); + + atomic_set(&dev->intr_idx, 0); + atomic_set(&dev->read_idx, 0); + 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 = usb_get_intf(interface); + + iface_desc = interface->cur_altsetting; + dev->product_id = le16_to_cpu(udev->descriptor.idProduct); + + init_usb_anchor(&dev->submitted); + + res = usb_find_last_int_in_endpoint(iface_desc, &dev->int_in_endpoint); + if (res) { + dev_err(&interface->dev, "no interrupt-in endpoint found\n"); + retval = res; + goto error; + } + + if ((dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW56) || + (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW56AM) || + (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW28) || + (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW28L) || + (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW100)) { + res = usb_find_last_int_out_endpoint(iface_desc, + &dev->int_out_endpoint); + if (res) { + dev_err(&interface->dev, "no interrupt-out endpoint found\n"); + retval = res; + goto error; + } + } + + /* 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); + + /* + * Some devices need the report size to be different than the + * endpoint size. + */ + if (dev->interface->cur_altsetting->desc.bInterfaceNumber == 0) { + switch (dev->product_id) { + case USB_DEVICE_ID_CODEMERCS_IOW56: + case USB_DEVICE_ID_CODEMERCS_IOW56AM: + dev->report_size = 7; + break; + + case USB_DEVICE_ID_CODEMERCS_IOW28: + case USB_DEVICE_ID_CODEMERCS_IOW28L: + dev->report_size = 4; + break; + + case USB_DEVICE_ID_CODEMERCS_IOW100: + dev->report_size = 12; + break; + } + } + + /* create the urb and buffer for reading */ + dev->int_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->int_in_urb) + goto error; + dev->int_in_buffer = kmalloc(dev->report_size, GFP_KERNEL); + if (!dev->int_in_buffer) + 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_array(dev->report_size + 1, MAX_INTERRUPT_BUFFER, + GFP_KERNEL); + if (!dev->read_queue) + 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"); + goto error; + } + + 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, minor - IOWARRIOR_MINOR_BASE); + return retval; + +error: + kref_put(&dev->kref, iowarrior_delete); + + 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 = usb_get_intfdata(interface); + + usb_deregister_dev(interface, &iowarrior_class); + + mutex_lock(&dev->mutex); + + /* prevent device read, write and ioctl */ + dev->present = 0; + + /* write urbs are not stopped on close() so kill unconditionally */ + usb_kill_anchored_urbs(&dev->submitted); + + if (dev->opened) { + usb_kill_urb(dev->int_in_urb); + wake_up_interruptible(&dev->read_wait); + wake_up_interruptible(&dev->write_wait); + } + + mutex_unlock(&dev->mutex); + + kref_put(&dev->kref, iowarrior_delete); +} + +/* 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 000000000..2d9af74a6 --- /dev/null +++ b/drivers/usb/misc/isight_firmware.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for loading USB isight firmware + * + * Copyright (C) 2008 Matthew Garrett + * + * 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_DESCRIPTION("iSight firmware loading support"); +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 000000000..71132a15e --- /dev/null +++ b/drivers/usb/misc/ldusb.c @@ -0,0 +1,794 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + * + * Derived from Lego USB Tower driver + * Copyright (C) 2003 David Glance + * 2001-2004 Juergen Stuber + */ + +#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_POWERANALYSERCASSY 0x1040 /* USB Product ID of Power Analyser CASSY */ +#define USB_DEVICE_ID_LD_CONVERTERCONTROLLERCASSY 0x1042 /* USB Product ID of Converter Controller CASSY */ +#define USB_DEVICE_ID_LD_MACHINETESTCASSY 0x1043 /* USB Product ID of Machine Test CASSY */ +#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 */ + +#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_POWERANALYSERCASSY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_CONVERTERCONTROLLERCASSY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MACHINETESTCASSY) }, + { 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) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, ld_usb_table); +MODULE_AUTHOR("Michael Hund "); +MODULE_DESCRIPTION("LD USB Driver"); +MODULE_LICENSE("GPL"); + +/* 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, 0000); +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, 0000); +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, 0000); +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, 0000); +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 kref kref; + struct mutex mutex; /* locks this structure */ + struct usb_interface *intf; /* save off the usb interface pointer */ + unsigned long disconnected:1; + + 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; + usb_kill_urb(dev->interrupt_in_urb); + } + if (dev->interrupt_out_busy) + usb_kill_urb(dev->interrupt_out_urb); +} + +/* + * ld_usb_delete + */ +static void ld_usb_delete(struct kref *kref) +{ + struct ld_usb *dev = container_of(kref, struct ld_usb, kref); + + /* 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; + unsigned long flags; + int retval; + + if (status) { + if (status == -ENOENT || + status == -ECONNRESET || + status == -ESHUTDOWN) { + goto exit; + } else { + dev_dbg(&dev->intf->dev, + "%s: nonzero status received: %d\n", __func__, + status); + spin_lock_irqsave(&dev->rbsl, flags); + goto resubmit; /* maybe we can recover */ + } + } + + spin_lock_irqsave(&dev->rbsl, flags); + 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; + dev_dbg(&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) { + 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_irqrestore(&dev->rbsl, flags); +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)) + dev_dbg(&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; + + stream_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; + } + + kref_get(&dev->kref); + + /* 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; + } + + mutex_lock(&dev->mutex); + + if (dev->disconnected) + goto unlock_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); + kref_put(&dev->kref, ld_usb_delete); +exit: + return retval; +} + +/* + * ld_usb_poll + */ +static __poll_t ld_usb_poll(struct file *file, poll_table *wait) +{ + struct ld_usb *dev; + __poll_t mask = 0; + + dev = file->private_data; + + if (dev->disconnected) + return EPOLLERR | EPOLLHUP; + + poll_wait(file, &dev->read_wait, wait); + poll_wait(file, &dev->write_wait, wait); + + if (dev->ring_head != dev->ring_tail) + mask |= EPOLLIN | EPOLLRDNORM; + if (!dev->interrupt_out_busy) + mask |= EPOLLOUT | EPOLLWRNORM; + + 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->disconnected) { + 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); + while (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; + + spin_lock_irq(&dev->rbsl); + } + 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)); + if (*actual_buffer > dev->interrupt_in_endpoint_size) { + retval = -EIO; + goto unlock_exit; + } + bytes_to_read = min(count, *actual_buffer); + if (bytes_to_read < *actual_buffer) + dev_warn(&dev->intf->dev, "Read buffer overflow, %zu 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; + } + retval = bytes_to_read; + + spin_lock_irq(&dev->rbsl); + dev->ring_tail = (dev->ring_tail + 1) % ring_buffer_size; + + 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->disconnected) { + 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, %zu bytes dropped\n", + count - bytes_to_write); + dev_dbg(&dev->intf->dev, "%s: count = %zu, bytes_to_write = %zu\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); + 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, +}; + +/* + * 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; + char *buffer; + int retval = -ENOMEM; + int res; + + /* allocate memory for our device state and initialize it */ + + dev = kzalloc_obj(*dev); + if (!dev) + goto exit; + + kref_init(&dev->kref); + 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) + goto error; + /* usb_string makes SETUP+STALL to leave always ControlReadLoop */ + usb_string(udev, 255, buffer, 256); + kfree(buffer); + } + + iface_desc = intf->cur_altsetting; + + res = usb_find_last_int_in_endpoint(iface_desc, + &dev->interrupt_in_endpoint); + if (res) { + dev_err(&intf->dev, "Interrupt in endpoint not found\n"); + retval = res; + goto error; + } + + res = usb_find_last_int_out_endpoint(iface_desc, + &dev->interrupt_out_endpoint); + if (res) + 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 = kcalloc(ring_buffer_size, + sizeof(size_t) + dev->interrupt_in_endpoint_size, + GFP_KERNEL); + if (!dev->ring_buffer) + goto error; + dev->interrupt_in_buffer = kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL); + if (!dev->interrupt_in_buffer) + goto error; + dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_in_urb) + 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_array(write_buffer_size, + dev->interrupt_out_endpoint_size, GFP_KERNEL); + if (!dev->interrupt_out_buffer) + goto error; + dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_out_urb) + goto error; + dev->interrupt_in_interval = max_t(int, min_interrupt_in_interval, + dev->interrupt_in_endpoint->bInterval); + if (dev->interrupt_out_endpoint) + dev->interrupt_out_interval = max_t(int, 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: + kref_put(&dev->kref, ld_usb_delete); + + 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); + + usb_poison_urb(dev->interrupt_in_urb); + usb_poison_urb(dev->interrupt_out_urb); + + mutex_lock(&dev->mutex); + + dev->disconnected = 1; + + if (dev->open_count) { + /* wake up pollers */ + wake_up_interruptible_all(&dev->read_wait); + wake_up_interruptible_all(&dev->write_wait); + } + + mutex_unlock(&dev->mutex); + + kref_put(&dev->kref, ld_usb_delete); + + 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 000000000..18dd4115b --- /dev/null +++ b/drivers/usb/misc/legousbtower.c @@ -0,0 +1,880 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * LEGO USB Tower driver + * + * Copyright (C) 2003 David Glance + * 2001-2004 Juergen Stuber + * + * 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 + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define DRIVER_AUTHOR "Juergen Stuber " +#define DRIVER_DESC "LEGO USB Tower Driver" + + +/* 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; + __u8 err_code; + __u8 spare; +}; + +struct tower_get_version_reply { + __le16 size; + __u8 err_code; + __u8 spare; + __u8 major; + __u8 minor; + __le16 build_no; +}; + + +/* 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); + +#define LEGO_USB_TOWER_MINOR_BASE 160 + + +/* Structure to hold all of our device specific stuff */ +struct lego_usb_tower { + struct kref kref; + 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 */ + unsigned long disconnected:1; + + 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_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 int tower_open(struct inode *inode, struct file *file); +static int tower_release(struct inode *inode, struct file *file); +static __poll_t tower_poll(struct file *file, poll_table *wait); +static loff_t tower_llseek(struct file *file, loff_t off, int whence); + +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(const 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(struct device *dev, + const char *function, int size, + const unsigned char *data) +{ + dev_dbg(dev, "%s - length = %d, data = %*ph\n", + function, size, size, data); +} + + +/* + * tower_delete + */ +static inline void tower_delete(struct kref *kref) +{ + struct lego_usb_tower *dev = container_of(kref, struct lego_usb_tower, kref); + + /* 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); + usb_put_dev(dev->udev); + kfree(dev); +} + + +/* + * 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; + + nonseekable_open(inode, file); + subminor = iminor(inode); + + interface = usb_find_interface(&tower_driver, subminor); + if (!interface) { + pr_err("error, can't find device for minor %d\n", subminor); + retval = -ENODEV; + goto exit; + } + + dev = usb_get_intfdata(interface); + if (!dev) { + retval = -ENODEV; + goto exit; + } + + /* lock this device */ + if (mutex_lock_interruptible(&dev->lock)) { + retval = -ERESTARTSYS; + goto exit; + } + + + /* allow opening only once */ + if (dev->open_count) { + retval = -EBUSY; + goto unlock_exit; + } + + /* reset the tower */ + result = usb_control_msg_recv(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, + GFP_KERNEL); + 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_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); + goto unlock_exit; + } + + /* save device in the file's private structure */ + file->private_data = dev; + + dev->open_count = 1; + + kref_get(&dev->kref); + +unlock_exit: + mutex_unlock(&dev->lock); + +exit: + return retval; +} + +/* + * tower_release + */ +static int tower_release(struct inode *inode, struct file *file) +{ + struct lego_usb_tower *dev; + int retval = 0; + + dev = file->private_data; + if (dev == NULL) { + retval = -ENODEV; + goto exit; + } + + mutex_lock(&dev->lock); + + if (dev->disconnected) + goto out_unlock; + + /* wait until write transfer is finished */ + if (dev->interrupt_out_busy) { + wait_event_interruptible_timeout(dev->write_wait, !dev->interrupt_out_busy, + 2 * HZ); + } + + /* shutdown transfers */ + usb_kill_urb(dev->interrupt_in_urb); + usb_kill_urb(dev->interrupt_out_urb); + + dev->open_count = 0; + +out_unlock: + mutex_unlock(&dev->lock); + kref_put(&dev->kref, tower_delete); +exit: + return retval; +} + +/* + * 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 __poll_t tower_poll(struct file *file, poll_table *wait) +{ + struct lego_usb_tower *dev; + __poll_t mask = 0; + + dev = file->private_data; + + if (dev->disconnected) + return EPOLLERR | EPOLLHUP; + + 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 |= EPOLLIN | EPOLLRDNORM; + if (!dev->interrupt_out_busy) + mask |= EPOLLOUT | EPOLLWRNORM; + + 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; + + 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->disconnected) { + retval = -ENODEV; + goto unlock_exit; + } + + /* verify that we actually have some data to read */ + if (count == 0) { + dev_dbg(&dev->udev->dev, "read request of 0 bytes\n"); + goto unlock_exit; + } + + if (read_timeout) + timeout = jiffies + msecs_to_jiffies(read_timeout); + + /* 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 + msecs_to_jiffies(read_timeout); + } + /* 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; i < dev->read_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: + 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; + + 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->disconnected) { + retval = -ENODEV; + goto unlock_exit; + } + + /* verify that we actually have some data to write */ + if (count == 0) { + dev_dbg(&dev->udev->dev, "write request of 0 bytes\n"); + 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); + dev_dbg(&dev->udev->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; + } + + /* 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: + 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; + unsigned long flags; + + lego_usb_tower_debug_data(&dev->udev->dev, __func__, + urb->actual_length, urb->transfer_buffer); + + if (status) { + if (status == -ENOENT || + status == -ECONNRESET || + status == -ESHUTDOWN) { + goto exit; + } else { + dev_dbg(&dev->udev->dev, + "%s: nonzero status received: %d\n", __func__, + status); + goto resubmit; /* maybe we can recover */ + } + } + + if (urb->actual_length > 0) { + spin_lock_irqsave(&dev->read_buffer_lock, flags); + 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; + dev_dbg(&dev->udev->dev, "%s: received %d bytes\n", + __func__, urb->actual_length); + } else { + pr_warn("read_buffer overflow, %d bytes dropped\n", + urb->actual_length); + } + spin_unlock_irqrestore(&dev->read_buffer_lock, flags); + } + +resubmit: + 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); +} + + +/* + * tower_interrupt_out_callback + */ +static void tower_interrupt_out_callback(struct urb *urb) +{ + struct lego_usb_tower *dev = urb->context; + int status = urb->status; + + lego_usb_tower_debug_data(&dev->udev->dev, __func__, + urb->actual_length, urb->transfer_buffer); + + /* sync/async unlink faults aren't errors */ + if (status && !(status == -ENOENT || + status == -ECONNRESET || + status == -ESHUTDOWN)) { + dev_dbg(&dev->udev->dev, + "%s: nonzero write bulk status received: %d\n", __func__, + status); + } + + dev->interrupt_out_busy = 0; + wake_up_interruptible(&dev->write_wait); +} + + +/* + * 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; + struct tower_get_version_reply get_version_reply; + int retval = -ENOMEM; + int result; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc_obj(*dev); + if (!dev) + goto exit; + + kref_init(&dev->kref); + mutex_init(&dev->lock); + dev->udev = usb_get_dev(udev); + spin_lock_init(&dev->read_buffer_lock); + dev->packet_timeout_jiffies = msecs_to_jiffies(packet_timeout); + dev->read_last_arrival = jiffies; + init_waitqueue_head(&dev->read_wait); + init_waitqueue_head(&dev->write_wait); + + result = usb_find_common_endpoints_reverse(interface->cur_altsetting, + NULL, NULL, + &dev->interrupt_in_endpoint, + &dev->interrupt_out_endpoint); + if (result) { + dev_err(idev, "interrupt endpoints not found\n"); + retval = result; + goto error; + } + + dev->read_buffer = kmalloc(read_buffer_size, GFP_KERNEL); + if (!dev->read_buffer) + goto error; + dev->interrupt_in_buffer = kmalloc(usb_endpoint_maxp(dev->interrupt_in_endpoint), GFP_KERNEL); + if (!dev->interrupt_in_buffer) + goto error; + dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_in_urb) + goto error; + dev->interrupt_out_buffer = kmalloc(write_buffer_size, GFP_KERNEL); + if (!dev->interrupt_out_buffer) + goto error; + dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_out_urb) + 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; + + /* get the firmware version and log it */ + result = usb_control_msg_recv(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, GFP_KERNEL); + if (result) { + dev_err(idev, "get version request failed: %d\n", result); + 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)); + + /* 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"); + 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); + +exit: + return retval; + +error: + kref_put(&dev->kref, tower_delete); + 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; + + dev = usb_get_intfdata(interface); + + minor = dev->minor; + + /* give back our minor and prevent further open() */ + usb_deregister_dev(interface, &tower_class); + + /* stop I/O */ + usb_poison_urb(dev->interrupt_in_urb); + usb_poison_urb(dev->interrupt_out_urb); + + mutex_lock(&dev->lock); + + dev->disconnected = 1; + + if (dev->open_count) { + /* wake up pollers */ + wake_up_interruptible_all(&dev->read_wait); + wake_up_interruptible_all(&dev->write_wait); + } + + mutex_unlock(&dev->lock); + + kref_put(&dev->kref, tower_delete); + + dev_info(&interface->dev, "LEGO USB Tower #%d now disconnected\n", + (minor - LEGO_USB_TOWER_MINOR_BASE)); +} + +module_usb_driver(tower_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/lvstest.c b/drivers/usb/misc/lvstest.c new file mode 100644 index 000000000..9b6e10177 --- /dev/null +++ b/drivers/usb/misc/lvstest.c @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drivers/usb/misc/lvstest.c + * + * Test pattern generation for Link Layer Validation System Tests + * + * Copyright (C) 2014 ST Microelectronics + * Pratyush Anand + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct lvs_rh { + /* root hub interface */ + struct usb_interface *intf; + /* if lvs device connected */ + bool present; + /* port no at which lvs device is present */ + int portnum; + /* urb buffer */ + u8 buffer[8]; + /* class descriptor */ + struct usb_hub_descriptor descriptor; + /* urb for polling interrupt pipe */ + struct urb *urb; + /* LVH RH work */ + struct work_struct rh_work; + /* RH port status */ + struct usb_port_status port_status; +}; + +static struct usb_device *create_lvs_device(struct usb_interface *intf) +{ + struct usb_device *udev, *hdev; + struct usb_hcd *hcd; + struct lvs_rh *lvs = usb_get_intfdata(intf); + + if (!lvs->present) { + dev_err(&intf->dev, "No LVS device is present\n"); + return NULL; + } + + hdev = interface_to_usbdev(intf); + hcd = bus_to_hcd(hdev->bus); + + udev = usb_alloc_dev(hdev, hdev->bus, lvs->portnum); + if (!udev) { + dev_err(&intf->dev, "Could not allocate lvs udev\n"); + return NULL; + } + udev->speed = USB_SPEED_SUPER; + udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512); + usb_set_device_state(udev, USB_STATE_DEFAULT); + + if (hcd->driver->enable_device) { + if (hcd->driver->enable_device(hcd, udev) < 0) { + dev_err(&intf->dev, "Failed to enable\n"); + usb_put_dev(udev); + return NULL; + } + } + + return udev; +} + +static void destroy_lvs_device(struct usb_device *udev) +{ + struct usb_device *hdev = udev->parent; + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); + + if (hcd->driver->free_dev) + hcd->driver->free_dev(hcd, udev); + + usb_put_dev(udev); +} + +static int lvs_rh_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); +} + +static int lvs_rh_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); +} + +static ssize_t u3_entry_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + struct usb_device *udev; + int ret; + + udev = create_lvs_device(intf); + if (!udev) { + dev_err(dev, "failed to create lvs device\n"); + return -ENOMEM; + } + + ret = lvs_rh_set_port_feature(hdev, lvs->portnum, + USB_PORT_FEAT_SUSPEND); + if (ret < 0) + dev_err(dev, "can't issue U3 entry %d\n", ret); + + destroy_lvs_device(udev); + + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_WO(u3_entry); + +static ssize_t u3_exit_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + struct usb_device *udev; + int ret; + + udev = create_lvs_device(intf); + if (!udev) { + dev_err(dev, "failed to create lvs device\n"); + return -ENOMEM; + } + + ret = lvs_rh_clear_port_feature(hdev, lvs->portnum, + USB_PORT_FEAT_SUSPEND); + if (ret < 0) + dev_err(dev, "can't issue U3 exit %d\n", ret); + + destroy_lvs_device(udev); + + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_WO(u3_exit); + +static ssize_t hot_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + int ret; + + ret = lvs_rh_set_port_feature(hdev, lvs->portnum, + USB_PORT_FEAT_RESET); + if (ret < 0) { + dev_err(dev, "can't issue hot reset %d\n", ret); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(hot_reset); + +static ssize_t warm_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + int ret; + + ret = lvs_rh_set_port_feature(hdev, lvs->portnum, + USB_PORT_FEAT_BH_PORT_RESET); + if (ret < 0) { + dev_err(dev, "can't issue warm reset %d\n", ret); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(warm_reset); + +static ssize_t u2_timeout_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret < 0) { + dev_err(dev, "couldn't parse string %d\n", ret); + return ret; + } + + if (val > 127) + return -EINVAL; + + ret = lvs_rh_set_port_feature(hdev, lvs->portnum | (val << 8), + USB_PORT_FEAT_U2_TIMEOUT); + if (ret < 0) { + dev_err(dev, "Error %d while setting U2 timeout %ld\n", ret, val); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(u2_timeout); + +static ssize_t u1_timeout_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret < 0) { + dev_err(dev, "couldn't parse string %d\n", ret); + return ret; + } + + if (val > 127) + return -EINVAL; + + ret = lvs_rh_set_port_feature(hdev, lvs->portnum | (val << 8), + USB_PORT_FEAT_U1_TIMEOUT); + if (ret < 0) { + dev_err(dev, "Error %d while setting U1 timeout %ld\n", ret, val); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(u1_timeout); + +static ssize_t get_dev_desc_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *udev; + struct usb_device_descriptor *descriptor; + int ret; + + descriptor = kmalloc_obj(*descriptor); + if (!descriptor) + return -ENOMEM; + + udev = create_lvs_device(intf); + if (!udev) { + dev_err(dev, "failed to create lvs device\n"); + ret = -ENOMEM; + goto free_desc; + } + + ret = usb_control_msg(udev, (PIPE_CONTROL << 30) | USB_DIR_IN, + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, USB_DT_DEVICE << 8, + 0, descriptor, sizeof(*descriptor), + USB_CTRL_GET_TIMEOUT); + if (ret < 0) + dev_err(dev, "can't read device descriptor %d\n", ret); + + destroy_lvs_device(udev); + +free_desc: + kfree(descriptor); + + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_WO(get_dev_desc); + +static ssize_t enable_compliance_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + int ret; + + ret = lvs_rh_set_port_feature(hdev, + lvs->portnum | USB_SS_PORT_LS_COMP_MOD << 3, + USB_PORT_FEAT_LINK_STATE); + if (ret < 0) { + dev_err(dev, "can't enable compliance mode %d\n", ret); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(enable_compliance); + +static struct attribute *lvs_attrs[] = { + &dev_attr_get_dev_desc.attr, + &dev_attr_u1_timeout.attr, + &dev_attr_u2_timeout.attr, + &dev_attr_hot_reset.attr, + &dev_attr_warm_reset.attr, + &dev_attr_u3_entry.attr, + &dev_attr_u3_exit.attr, + &dev_attr_enable_compliance.attr, + NULL +}; +ATTRIBUTE_GROUPS(lvs); + +static void lvs_rh_work(struct work_struct *work) +{ + struct lvs_rh *lvs = container_of(work, struct lvs_rh, rh_work); + struct usb_interface *intf = lvs->intf; + struct usb_device *hdev = interface_to_usbdev(intf); + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); + struct usb_hub_descriptor *descriptor = &lvs->descriptor; + struct usb_port_status *port_status = &lvs->port_status; + int i, ret = 0; + u16 portchange; + + /* Examine each root port */ + for (i = 1; i <= descriptor->bNbrPorts; i++) { + ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), + USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, i, + port_status, sizeof(*port_status), 1000); + if (ret < 4) + continue; + + portchange = le16_to_cpu(port_status->wPortChange); + + if (portchange & USB_PORT_STAT_C_LINK_STATE) + lvs_rh_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_PORT_LINK_STATE); + if (portchange & USB_PORT_STAT_C_ENABLE) + lvs_rh_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_ENABLE); + if (portchange & USB_PORT_STAT_C_RESET) + lvs_rh_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_RESET); + if (portchange & USB_PORT_STAT_C_BH_RESET) + lvs_rh_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_BH_PORT_RESET); + if (portchange & USB_PORT_STAT_C_CONNECTION) { + lvs_rh_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_CONNECTION); + + if (le16_to_cpu(port_status->wPortStatus) & + USB_PORT_STAT_CONNECTION) { + lvs->present = true; + lvs->portnum = i; + if (hcd->usb_phy) + usb_phy_notify_connect(hcd->usb_phy, + USB_SPEED_SUPER); + } else { + lvs->present = false; + if (hcd->usb_phy) + usb_phy_notify_disconnect(hcd->usb_phy, + USB_SPEED_SUPER); + } + break; + } + } + + ret = usb_submit_urb(lvs->urb, GFP_KERNEL); + if (ret != 0 && ret != -ENODEV && ret != -EPERM) + dev_err(&intf->dev, "urb resubmit error %d\n", ret); +} + +static void lvs_rh_irq(struct urb *urb) +{ + struct lvs_rh *lvs = urb->context; + + schedule_work(&lvs->rh_work); +} + +static int lvs_rh_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *hdev; + struct usb_host_interface *desc; + struct usb_endpoint_descriptor *endpoint; + struct lvs_rh *lvs; + unsigned int pipe; + int ret, maxp; + + hdev = interface_to_usbdev(intf); + desc = intf->cur_altsetting; + + ret = usb_find_int_in_endpoint(desc, &endpoint); + if (ret) + return ret; + + /* valid only for SS root hub */ + if (hdev->descriptor.bDeviceProtocol != USB_HUB_PR_SS || hdev->parent) { + dev_err(&intf->dev, "Bind LVS driver with SS root Hub only\n"); + return -EINVAL; + } + + lvs = devm_kzalloc(&intf->dev, sizeof(*lvs), GFP_KERNEL); + if (!lvs) + return -ENOMEM; + + lvs->intf = intf; + usb_set_intfdata(intf, lvs); + + /* how many number of ports this root hub has */ + ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB, + USB_DT_SS_HUB << 8, 0, &lvs->descriptor, + USB_DT_SS_HUB_SIZE, USB_CTRL_GET_TIMEOUT); + if (ret < (USB_DT_HUB_NONVAR_SIZE + 2)) { + dev_err(&hdev->dev, "wrong root hub descriptor read %d\n", ret); + return ret < 0 ? ret : -EINVAL; + } + + /* submit urb to poll interrupt endpoint */ + lvs->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!lvs->urb) + return -ENOMEM; + + INIT_WORK(&lvs->rh_work, lvs_rh_work); + + pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress); + maxp = usb_maxpacket(hdev, pipe); + usb_fill_int_urb(lvs->urb, hdev, pipe, &lvs->buffer[0], maxp, + lvs_rh_irq, lvs, endpoint->bInterval); + + ret = usb_submit_urb(lvs->urb, GFP_KERNEL); + if (ret < 0) { + dev_err(&intf->dev, "couldn't submit lvs urb %d\n", ret); + goto free_urb; + } + + return ret; + +free_urb: + usb_free_urb(lvs->urb); + return ret; +} + +static void lvs_rh_disconnect(struct usb_interface *intf) +{ + struct lvs_rh *lvs = usb_get_intfdata(intf); + + usb_poison_urb(lvs->urb); /* used in scheduled work */ + flush_work(&lvs->rh_work); + usb_free_urb(lvs->urb); +} + +static struct usb_driver lvs_driver = { + .name = "lvs", + .probe = lvs_rh_probe, + .disconnect = lvs_rh_disconnect, + .dev_groups = lvs_groups, +}; + +module_usb_driver(lvs_driver); + +MODULE_DESCRIPTION("Link Layer Validation System Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/onboard_usb_dev.c b/drivers/usb/misc/onboard_usb_dev.c new file mode 100644 index 000000000..11508ed4d --- /dev/null +++ b/drivers/usb/misc/onboard_usb_dev.c @@ -0,0 +1,732 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for onboard USB devices + * + * Copyright (c) 2022, Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "onboard_usb_dev.h" + +/* USB5744 register offset and mask */ +#define USB5744_CMD_ATTACH 0xAA +#define USB5744_CMD_ATTACH_LSB 0x56 +#define USB5744_CMD_CREG_ACCESS 0x99 +#define USB5744_CMD_CREG_ACCESS_LSB 0x37 +#define USB5744_CREG_MEM_ADDR 0x00 +#define USB5744_CREG_MEM_RD_ADDR 0x04 +#define USB5744_CREG_WRITE 0x00 +#define USB5744_CREG_READ 0x01 +#define USB5744_CREG_RUNTIMEFLAGS2 0x411D +#define USB5744_CREG_BYPASS_UDC_SUSPEND BIT(3) + +static void onboard_dev_attach_usb_driver(struct work_struct *work); + +static struct usb_device_driver onboard_dev_usbdev_driver; +static DECLARE_WORK(attach_usb_driver_work, onboard_dev_attach_usb_driver); + +/************************** Platform driver **************************/ + +struct usbdev_node { + struct usb_device *udev; + struct list_head list; +}; + +struct onboard_dev { + struct regulator_bulk_data supplies[MAX_SUPPLIES]; + struct device *dev; + const struct onboard_dev_pdata *pdata; + struct gpio_desc *reset_gpio; + bool always_powered_in_suspend; + bool is_powered_on; + bool going_away; + struct list_head udev_list; + struct mutex lock; + struct clk *clk; +}; + +static int onboard_dev_get_regulators(struct onboard_dev *onboard_dev) +{ + const char * const *supply_names = onboard_dev->pdata->supply_names; + unsigned int num_supplies = onboard_dev->pdata->num_supplies; + struct device *dev = onboard_dev->dev; + unsigned int i; + int err; + + if (num_supplies > MAX_SUPPLIES) + return dev_err_probe(dev, -EINVAL, "max %d supplies supported!\n", + MAX_SUPPLIES); + + for (i = 0; i < num_supplies; i++) + onboard_dev->supplies[i].supply = supply_names[i]; + + err = devm_regulator_bulk_get(dev, num_supplies, onboard_dev->supplies); + if (err) + dev_err(dev, "Failed to get regulator supplies: %pe\n", + ERR_PTR(err)); + + return err; +} + +static int onboard_dev_power_on(struct onboard_dev *onboard_dev) +{ + int err; + + err = clk_prepare_enable(onboard_dev->clk); + if (err) { + dev_err(onboard_dev->dev, "failed to enable clock: %pe\n", + ERR_PTR(err)); + return err; + } + + err = regulator_bulk_enable(onboard_dev->pdata->num_supplies, + onboard_dev->supplies); + if (err) { + dev_err(onboard_dev->dev, "failed to enable supplies: %pe\n", + ERR_PTR(err)); + goto disable_clk; + } + + fsleep(onboard_dev->pdata->reset_us); + gpiod_set_value_cansleep(onboard_dev->reset_gpio, 0); + fsleep(onboard_dev->pdata->power_on_delay_us); + + onboard_dev->is_powered_on = true; + + return 0; + +disable_clk: + clk_disable_unprepare(onboard_dev->clk); + return err; +} + +static int onboard_dev_power_off(struct onboard_dev *onboard_dev) +{ + int err; + + gpiod_set_value_cansleep(onboard_dev->reset_gpio, 1); + + err = regulator_bulk_disable(onboard_dev->pdata->num_supplies, + onboard_dev->supplies); + if (err) { + dev_err(onboard_dev->dev, "failed to disable supplies: %pe\n", + ERR_PTR(err)); + return err; + } + + clk_disable_unprepare(onboard_dev->clk); + + onboard_dev->is_powered_on = false; + + return 0; +} + +static int __maybe_unused onboard_dev_suspend(struct device *dev) +{ + struct onboard_dev *onboard_dev = dev_get_drvdata(dev); + struct usbdev_node *node; + bool power_off = true; + + if (onboard_dev->always_powered_in_suspend) + return 0; + + mutex_lock(&onboard_dev->lock); + + list_for_each_entry(node, &onboard_dev->udev_list, list) { + if (!device_may_wakeup(node->udev->bus->controller)) + continue; + + if (usb_wakeup_enabled_descendants(node->udev)) { + power_off = false; + break; + } + } + + mutex_unlock(&onboard_dev->lock); + + if (!power_off) + return 0; + + return onboard_dev_power_off(onboard_dev); +} + +static int __maybe_unused onboard_dev_resume(struct device *dev) +{ + struct onboard_dev *onboard_dev = dev_get_drvdata(dev); + + if (onboard_dev->is_powered_on) + return 0; + + return onboard_dev_power_on(onboard_dev); +} + +static inline void get_udev_link_name(const struct usb_device *udev, char *buf, + size_t size) +{ + snprintf(buf, size, "usb_dev.%s", dev_name(&udev->dev)); +} + +static int onboard_dev_add_usbdev(struct onboard_dev *onboard_dev, + struct usb_device *udev) +{ + struct usbdev_node *node; + char link_name[64]; + int err; + + mutex_lock(&onboard_dev->lock); + + if (onboard_dev->going_away) { + err = -EINVAL; + goto error; + } + + node = kzalloc_obj(*node); + if (!node) { + err = -ENOMEM; + goto error; + } + + node->udev = udev; + + list_add(&node->list, &onboard_dev->udev_list); + + mutex_unlock(&onboard_dev->lock); + + get_udev_link_name(udev, link_name, sizeof(link_name)); + WARN_ON(sysfs_create_link(&onboard_dev->dev->kobj, &udev->dev.kobj, + link_name)); + + return 0; + +error: + mutex_unlock(&onboard_dev->lock); + + return err; +} + +static void onboard_dev_remove_usbdev(struct onboard_dev *onboard_dev, + const struct usb_device *udev) +{ + struct usbdev_node *node; + char link_name[64]; + + get_udev_link_name(udev, link_name, sizeof(link_name)); + sysfs_remove_link(&onboard_dev->dev->kobj, link_name); + + mutex_lock(&onboard_dev->lock); + + list_for_each_entry(node, &onboard_dev->udev_list, list) { + if (node->udev == udev) { + list_del(&node->list); + kfree(node); + break; + } + } + + mutex_unlock(&onboard_dev->lock); +} + +static ssize_t always_powered_in_suspend_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const struct onboard_dev *onboard_dev = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", onboard_dev->always_powered_in_suspend); +} + +static ssize_t always_powered_in_suspend_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct onboard_dev *onboard_dev = dev_get_drvdata(dev); + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret < 0) + return ret; + + onboard_dev->always_powered_in_suspend = val; + + return count; +} +static DEVICE_ATTR_RW(always_powered_in_suspend); + +static struct attribute *onboard_dev_attrs[] = { + &dev_attr_always_powered_in_suspend.attr, + NULL, +}; + +static umode_t onboard_dev_attrs_are_visible(struct kobject *kobj, + struct attribute *attr, + int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct onboard_dev *onboard_dev = dev_get_drvdata(dev); + + if (attr == &dev_attr_always_powered_in_suspend.attr && + !onboard_dev->pdata->is_hub) + return 0; + + return attr->mode; +} + +static const struct attribute_group onboard_dev_group = { + .is_visible = onboard_dev_attrs_are_visible, + .attrs = onboard_dev_attrs, +}; +__ATTRIBUTE_GROUPS(onboard_dev); + + +static void onboard_dev_attach_usb_driver(struct work_struct *work) +{ + int err; + + err = driver_attach(&onboard_dev_usbdev_driver.driver); + if (err) + pr_err("Failed to attach USB driver: %pe\n", ERR_PTR(err)); +} + +#if IS_ENABLED(CONFIG_USB_ONBOARD_DEV_USB5744) +static int onboard_dev_5744_i2c_read_byte(struct i2c_client *client, u16 addr, u8 *data) +{ + struct i2c_msg msg[2]; + u8 rd_buf[3]; + int ret; + + u8 wr_buf[7] = {0, USB5744_CREG_MEM_ADDR, 4, + USB5744_CREG_READ, 1, + addr >> 8 & 0xff, + addr & 0xff}; + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = sizeof(wr_buf); + msg[0].buf = wr_buf; + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + + wr_buf[0] = USB5744_CMD_CREG_ACCESS; + wr_buf[1] = USB5744_CMD_CREG_ACCESS_LSB; + wr_buf[2] = 0; + msg[0].len = 3; + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + + wr_buf[0] = 0; + wr_buf[1] = USB5744_CREG_MEM_RD_ADDR; + msg[0].len = 2; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 2; + msg[1].buf = rd_buf; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) + return ret; + *data = rd_buf[1]; + + return 0; +} + +static int onboard_dev_5744_i2c_write_byte(struct i2c_client *client, u16 addr, u8 data) +{ + struct i2c_msg msg[2]; + int ret; + + u8 wr_buf[8] = {0, USB5744_CREG_MEM_ADDR, 5, + USB5744_CREG_WRITE, 1, + addr >> 8 & 0xff, + addr & 0xff, + data}; + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = sizeof(wr_buf); + msg[0].buf = wr_buf; + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + + msg[0].len = 3; + wr_buf[0] = USB5744_CMD_CREG_ACCESS; + wr_buf[1] = USB5744_CMD_CREG_ACCESS_LSB; + wr_buf[2] = 0; + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + + return 0; +} + +static int onboard_dev_5744_i2c_init(struct i2c_client *client) +{ + struct device *dev = &client->dev; + int ret; + u8 reg; + + /* + * Set BYPASS_UDC_SUSPEND bit to ensure MCU is always enabled + * and ready to respond to SMBus runtime commands. + * The command writes 5 bytes to memory and single data byte in + * configuration register. + */ + ret = onboard_dev_5744_i2c_read_byte(client, + USB5744_CREG_RUNTIMEFLAGS2, ®); + if (ret) + return dev_err_probe(dev, ret, "CREG_RUNTIMEFLAGS2 read failed\n"); + + reg |= USB5744_CREG_BYPASS_UDC_SUSPEND; + ret = onboard_dev_5744_i2c_write_byte(client, + USB5744_CREG_RUNTIMEFLAGS2, reg); + if (ret) + return dev_err_probe(dev, ret, "BYPASS_UDC_SUSPEND bit configuration failed\n"); + + /* Send SMBus command to boot hub. */ + ret = i2c_smbus_write_word_data(client, USB5744_CMD_ATTACH, + USB5744_CMD_ATTACH_LSB); + if (ret < 0) + return dev_err_probe(dev, ret, "USB Attach with SMBus command failed\n"); + + return ret; +} +#else +static int onboard_dev_5744_i2c_init(struct i2c_client *client) +{ + return -ENODEV; +} +#endif + +static int onboard_dev_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct onboard_dev *onboard_dev; + struct device_node *i2c_node; + int err; + + onboard_dev = devm_kzalloc(dev, sizeof(*onboard_dev), GFP_KERNEL); + if (!onboard_dev) + return -ENOMEM; + + onboard_dev->pdata = device_get_match_data(dev); + if (!onboard_dev->pdata) + return -EINVAL; + + if (!onboard_dev->pdata->is_hub) + onboard_dev->always_powered_in_suspend = true; + + onboard_dev->dev = dev; + + err = onboard_dev_get_regulators(onboard_dev); + if (err) + return err; + + onboard_dev->clk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(onboard_dev->clk)) + return dev_err_probe(dev, PTR_ERR(onboard_dev->clk), + "failed to get clock\n"); + + onboard_dev->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(onboard_dev->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(onboard_dev->reset_gpio), + "failed to get reset GPIO\n"); + + mutex_init(&onboard_dev->lock); + INIT_LIST_HEAD(&onboard_dev->udev_list); + + dev_set_drvdata(dev, onboard_dev); + + err = onboard_dev_power_on(onboard_dev); + if (err) + return err; + + i2c_node = of_parse_phandle(pdev->dev.of_node, "i2c-bus", 0); + if (i2c_node) { + struct i2c_client *client = NULL; + +#if IS_ENABLED(CONFIG_USB_ONBOARD_DEV_USB5744) + client = of_find_i2c_device_by_node(i2c_node); +#endif + of_node_put(i2c_node); + + if (!client) { + err = -EPROBE_DEFER; + goto err_power_off; + } + + if (of_device_is_compatible(pdev->dev.of_node, "usb424,2744") || + of_device_is_compatible(pdev->dev.of_node, "usb424,5744")) { + err = onboard_dev_5744_i2c_init(client); + onboard_dev->always_powered_in_suspend = true; + } + + put_device(&client->dev); + if (err < 0) + goto err_power_off; + } + + /* + * The USB driver might have been detached from the USB devices by + * onboard_dev_remove() (e.g. through an 'unbind' by userspace), + * make sure to re-attach it if needed. + * + * This needs to be done deferred to avoid self-deadlocks on systems + * with nested onboard hubs. + */ + schedule_work(&attach_usb_driver_work); + + return 0; + +err_power_off: + onboard_dev_power_off(onboard_dev); + return err; +} + +static void onboard_dev_remove(struct platform_device *pdev) +{ + struct onboard_dev *onboard_dev = dev_get_drvdata(&pdev->dev); + struct usbdev_node *node; + struct usb_device *udev; + + onboard_dev->going_away = true; + + mutex_lock(&onboard_dev->lock); + + /* unbind the USB devices to avoid dangling references to this device */ + while (!list_empty(&onboard_dev->udev_list)) { + node = list_first_entry(&onboard_dev->udev_list, + struct usbdev_node, list); + udev = node->udev; + + /* + * Unbinding the driver will call onboard_dev_remove_usbdev(), + * which acquires onboard_dev->lock. We must release the lock + * first. + */ + get_device(&udev->dev); + mutex_unlock(&onboard_dev->lock); + device_release_driver(&udev->dev); + put_device(&udev->dev); + mutex_lock(&onboard_dev->lock); + } + + mutex_unlock(&onboard_dev->lock); + + onboard_dev_power_off(onboard_dev); +} + +MODULE_DEVICE_TABLE(of, onboard_dev_match); + +static const struct dev_pm_ops __maybe_unused onboard_dev_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(onboard_dev_suspend, onboard_dev_resume) +}; + +static struct platform_driver onboard_dev_driver = { + .probe = onboard_dev_probe, + .remove = onboard_dev_remove, + + .driver = { + .name = "onboard-usb-dev", + .of_match_table = onboard_dev_match, + .pm = pm_ptr(&onboard_dev_pm_ops), + .dev_groups = onboard_dev_groups, + }, +}; + +/************************** USB driver **************************/ + +#define VENDOR_ID_BISON 0x5986 +#define VENDOR_ID_CORECHIPS 0x3431 +#define VENDOR_ID_CYPRESS 0x04b4 +#define VENDOR_ID_GENESYS 0x05e3 +#define VENDOR_ID_MICROCHIP 0x0424 +#define VENDOR_ID_PARADE 0x1da0 +#define VENDOR_ID_REALTEK 0x0bda +#define VENDOR_ID_TERMINUS 0x1a40 +#define VENDOR_ID_TI 0x0451 +#define VENDOR_ID_VIA 0x2109 +#define VENDOR_ID_XMOS 0x20B1 + +/* + * Returns the onboard_dev platform device that is associated with the USB + * device passed as parameter. + */ +static struct onboard_dev *_find_onboard_dev(struct device *dev) +{ + struct platform_device *pdev; + struct device_node *np; + struct onboard_dev *onboard_dev; + + pdev = of_find_device_by_node(dev->of_node); + if (!pdev) { + np = of_parse_phandle(dev->of_node, "peer-hub", 0); + if (!np) { + dev_err(dev, "failed to find device node for peer hub\n"); + return ERR_PTR(-EINVAL); + } + + pdev = of_find_device_by_node(np); + of_node_put(np); + + if (!pdev) + return ERR_PTR(-ENODEV); + } + + onboard_dev = dev_get_drvdata(&pdev->dev); + put_device(&pdev->dev); + + /* + * The presence of drvdata indicates that the platform driver finished + * probing. This handles the case where (conceivably) we could be + * running at the exact same time as the platform driver's probe. If + * we detect the race we request probe deferral and we'll come back and + * try again. + */ + if (!onboard_dev) + return ERR_PTR(-EPROBE_DEFER); + + return onboard_dev; +} + +static bool onboard_dev_usbdev_match(struct usb_device *udev) +{ + /* Onboard devices using this driver must have a device tree node */ + return !!udev->dev.of_node; +} + +static int onboard_dev_usbdev_probe(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + struct onboard_dev *onboard_dev; + int err; + + onboard_dev = _find_onboard_dev(dev); + if (IS_ERR(onboard_dev)) + return PTR_ERR(onboard_dev); + + dev_set_drvdata(dev, onboard_dev); + + err = onboard_dev_add_usbdev(onboard_dev, udev); + if (err) + return err; + + return 0; +} + +static void onboard_dev_usbdev_disconnect(struct usb_device *udev) +{ + struct onboard_dev *onboard_dev = dev_get_drvdata(&udev->dev); + + onboard_dev_remove_usbdev(onboard_dev, udev); +} + +static const struct usb_device_id onboard_dev_id_table[] = { + { USB_DEVICE(VENDOR_ID_BISON, 0x1198) }, /* Bison Electronics Inc. Integrated Camera */ + { USB_DEVICE(VENDOR_ID_CORECHIPS, 0x6241) }, /* SL6341 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_CORECHIPS, 0x6341) }, /* SL6341 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6500) }, /* CYUSB330x 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6502) }, /* CYUSB330x 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6503) }, /* CYUSB33{0,1}x 2.0 HUB, Vendor Mode */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6504) }, /* CYUSB331x 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6506) }, /* CYUSB331x 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6507) }, /* CYUSB332x 2.0 HUB, Vendor Mode */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6508) }, /* CYUSB332x 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x650a) }, /* CYUSB332x 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6570) }, /* CY7C6563x 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0608) }, /* Genesys Logic GL850G USB 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0610) }, /* Genesys Logic GL852G USB 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0620) }, /* Genesys Logic GL3523 USB 3.1 HUB */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0625) }, /* Genesys Logic GL3590 USB 3.2 HUB */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2412) }, /* USB2412 USB 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2514) }, /* USB2514B USB 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2517) }, /* USB2517 USB 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2744) }, /* USB5744 USB 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x5744) }, /* USB5744 USB 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_PARADE, 0x5511) }, /* PS5511 USB 3.2 */ + { USB_DEVICE(VENDOR_ID_PARADE, 0x55a1) }, /* PS5511 USB 2.0 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 HUB */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 HUB */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 HUB */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 HUB */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0179) }, /* RTL8188ETV 2.4GHz WiFi */ + { USB_DEVICE(VENDOR_ID_TERMINUS, 0x0101) }, /* Terminus FE1.1s 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_TI, 0x8025) }, /* TI USB8020B 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_TI, 0x8027) }, /* TI USB8020B 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_TI, 0x8140) }, /* TI USB8041 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_TI, 0x8142) }, /* TI USB8041 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_TI, 0x8440) }, /* TI USB8044 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_TI, 0x8442) }, /* TI USB8044 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_VIA, 0x0817) }, /* VIA VL817 3.1 HUB */ + { USB_DEVICE(VENDOR_ID_VIA, 0x2817) }, /* VIA VL817 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_XMOS, 0x0013) }, /* XMOS XVF3500 Voice Processor */ + {} +}; +MODULE_DEVICE_TABLE(usb, onboard_dev_id_table); + +static struct usb_device_driver onboard_dev_usbdev_driver = { + .name = "onboard-usb-dev", + .match = onboard_dev_usbdev_match, + .probe = onboard_dev_usbdev_probe, + .disconnect = onboard_dev_usbdev_disconnect, + .generic_subclass = 1, + .supports_autosuspend = 1, + .id_table = onboard_dev_id_table, +}; + +static int __init onboard_dev_init(void) +{ + int ret; + + ret = usb_register_device_driver(&onboard_dev_usbdev_driver, THIS_MODULE); + if (ret) + return ret; + + ret = platform_driver_register(&onboard_dev_driver); + if (ret) + usb_deregister_device_driver(&onboard_dev_usbdev_driver); + + return ret; +} +module_init(onboard_dev_init); + +static void __exit onboard_dev_exit(void) +{ + usb_deregister_device_driver(&onboard_dev_usbdev_driver); + platform_driver_unregister(&onboard_dev_driver); + + cancel_work_sync(&attach_usb_driver_work); +} +module_exit(onboard_dev_exit); + +MODULE_AUTHOR("Matthias Kaehlcke "); +MODULE_DESCRIPTION("Driver for discrete onboard USB devices"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/onboard_usb_dev.h b/drivers/usb/misc/onboard_usb_dev.h new file mode 100644 index 000000000..3523f8f8a --- /dev/null +++ b/drivers/usb/misc/onboard_usb_dev.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2022, Google LLC + */ + +#ifndef _USB_MISC_ONBOARD_USB_DEV_H +#define _USB_MISC_ONBOARD_USB_DEV_H + +#define MAX_SUPPLIES 2 + +struct onboard_dev_pdata { + unsigned long reset_us; /* reset pulse width in us */ + unsigned long power_on_delay_us; /* power on delay in us */ + unsigned int num_supplies; /* number of supplies */ + const char * const supply_names[MAX_SUPPLIES]; + bool is_hub; +}; + +static const struct onboard_dev_pdata microchip_usb424_data = { + .reset_us = 1, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata microchip_usb2514_data = { + .reset_us = 1, + .num_supplies = 2, + .supply_names = { "vdd", "vdda" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata microchip_usb5744_data = { + .reset_us = 0, + .power_on_delay_us = 10000, + .num_supplies = 2, + .supply_names = { "vdd", "vdd2" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata parade_ps5511_data = { + .reset_us = 500, + .num_supplies = 2, + .supply_names = { "vddd11", "vdd33"}, + .is_hub = true, +}; + +static const struct onboard_dev_pdata realtek_rts5411_data = { + .reset_us = 0, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata realtek_rtl8188etv_data = { + .reset_us = 0, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = false, +}; + +static const struct onboard_dev_pdata ti_tusb8020b_data = { + .reset_us = 3000, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata bison_intcamera_data = { + .reset_us = 1000, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = false, +}; + +static const struct onboard_dev_pdata corechips_sl6341_data = { + .reset_us = 10000, + .num_supplies = 2, + .supply_names = { "vdd1v1", "vdd3v3" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata cypress_hx3_data = { + .reset_us = 10000, + .num_supplies = 2, + .supply_names = { "vdd", "vdd2" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata genesys_gl850g_data = { + .reset_us = 3, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata genesys_gl852g_data = { + .reset_us = 50, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata genesys_gl3590_data = { + .reset_us = 50, + .num_supplies = 2, + .supply_names = { "vdd", "vdd12" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata usb_a_conn_data = { + .num_supplies = 1, + .supply_names = { "vbus" }, +}; + +static const struct onboard_dev_pdata vialab_vl817_data = { + .reset_us = 10, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata wch_ch334_data = { + .reset_us = 14000, + .num_supplies = 2, + .supply_names = { "vdd33", "v5" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata xmos_xvf3500_data = { + .reset_us = 1, + .num_supplies = 2, + .supply_names = { "vdd", "vddio" }, + .is_hub = false, +}; + +static const struct of_device_id onboard_dev_match[] = { + { .compatible = "usb-a-connector", .data = &usb_a_conn_data, }, + { .compatible = "usb424,2412", .data = µchip_usb424_data, }, + { .compatible = "usb424,2514", .data = µchip_usb2514_data, }, + { .compatible = "usb424,2517", .data = µchip_usb424_data, }, + { .compatible = "usb424,2744", .data = µchip_usb5744_data, }, + { .compatible = "usb424,5744", .data = µchip_usb5744_data, }, + { .compatible = "usb451,8025", .data = &ti_tusb8020b_data, }, + { .compatible = "usb451,8027", .data = &ti_tusb8020b_data, }, + { .compatible = "usb451,8140", .data = &ti_tusb8020b_data, }, + { .compatible = "usb451,8142", .data = &ti_tusb8020b_data, }, + { .compatible = "usb451,8440", .data = &ti_tusb8020b_data, }, + { .compatible = "usb451,8442", .data = &ti_tusb8020b_data, }, + { .compatible = "usb4b4,6504", .data = &cypress_hx3_data, }, + { .compatible = "usb4b4,6506", .data = &cypress_hx3_data, }, + { .compatible = "usb4b4,6570", .data = µchip_usb424_data, }, + { .compatible = "usb5e3,608", .data = &genesys_gl850g_data, }, + { .compatible = "usb5e3,610", .data = &genesys_gl852g_data, }, + { .compatible = "usb5e3,620", .data = &genesys_gl852g_data, }, + { .compatible = "usb5e3,625", .data = &genesys_gl3590_data, }, + { .compatible = "usb5e3,626", .data = &genesys_gl852g_data, }, + { .compatible = "usbbda,179", .data = &realtek_rtl8188etv_data, }, + { .compatible = "usbbda,411", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,5411", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,414", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,5414", .data = &realtek_rts5411_data, }, + { .compatible = "usb1a40,0101", .data = &vialab_vl817_data, }, + { .compatible = "usb1a86,8091", .data = &wch_ch334_data, }, + { .compatible = "usb1da0,5511", .data = ¶de_ps5511_data, }, + { .compatible = "usb1da0,55a1", .data = ¶de_ps5511_data, }, + { .compatible = "usb2109,817", .data = &vialab_vl817_data, }, + { .compatible = "usb2109,2817", .data = &vialab_vl817_data, }, + { .compatible = "usb20b1,0013", .data = &xmos_xvf3500_data, }, + { .compatible = "usb3431,6241", .data = &corechips_sl6341_data, }, + { .compatible = "usb3431,6341", .data = &corechips_sl6341_data, }, + { .compatible = "usb5986,1198", .data = &bison_intcamera_data, }, + {} +}; + +#endif /* _USB_MISC_ONBOARD_USB_DEV_H */ diff --git a/drivers/usb/misc/onboard_usb_dev_pdevs.c b/drivers/usb/misc/onboard_usb_dev_pdevs.c new file mode 100644 index 000000000..cdb6949e1 --- /dev/null +++ b/drivers/usb/misc/onboard_usb_dev_pdevs.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * API for creating and destroying USB onboard platform devices + * + * Copyright (c) 2022, Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "onboard_usb_dev.h" + +struct pdev_list_entry { + struct platform_device *pdev; + struct list_head node; +}; + +static bool of_is_onboard_usb_dev(struct device_node *np) +{ + return !!of_match_node(onboard_dev_match, np); +} + +/** + * onboard_dev_create_pdevs -- create platform devices for onboard USB devices + * @parent_hub : parent hub to scan for connected onboard devices + * @pdev_list : list of onboard platform devices owned by the parent hub + * + * Creates a platform device for each supported onboard device that is connected + * to the given parent hub. The platform device is in charge of initializing the + * device (enable regulators, take the device out of reset, ...). For onboard + * hubs, it can optionally control whether the device remains powered during + * system suspend or not. + * + * To keep track of the platform devices they are added to a list that is owned + * by the parent hub. + * + * Some background about the logic in this function, which can be a bit hard + * to follow: + * + * Root hubs don't have dedicated device tree nodes, but use the node of their + * HCD. The primary and secondary HCD are usually represented by a single DT + * node. That means the root hubs of the primary and secondary HCD share the + * same device tree node (the HCD node). As a result this function can be called + * twice with the same DT node for root hubs. We only want to create a single + * platform device for each physical onboard device, hence for root hubs the + * loop is only executed for the root hub of the primary HCD. Since the function + * scans through all child nodes it still creates pdevs for onboard devices + * connected to the root hub of the secondary HCD if needed. + * + * Further there must be only one platform device for onboard hubs with a peer + * hub (the hub is a single physical device). To achieve this two measures are + * taken: pdevs for onboard hubs with a peer are only created when the function + * is called on behalf of the parent hub that is connected to the primary HCD + * (directly or through other hubs). For onboard hubs connected to root hubs + * the function processes the nodes of both peers. A platform device is only + * created if the peer hub doesn't have one already. + */ +void onboard_dev_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list) +{ + int i; + struct usb_hcd *hcd = bus_to_hcd(parent_hub->bus); + struct device_node *np, *npc; + struct platform_device *pdev; + struct pdev_list_entry *pdle; + + if (!parent_hub->dev.of_node) + return; + + if (!parent_hub->parent && !usb_hcd_is_primary_hcd(hcd)) + return; + + for (i = 1; i <= parent_hub->maxchild; i++) { + np = usb_of_get_device_node(parent_hub, i); + if (!np) + continue; + + if (!of_is_onboard_usb_dev(np)) + goto node_put; + + npc = of_parse_phandle(np, "peer-hub", 0); + if (npc) { + if (!usb_hcd_is_primary_hcd(hcd)) { + of_node_put(npc); + goto node_put; + } + + pdev = of_find_device_by_node(npc); + of_node_put(npc); + + if (pdev) { + put_device(&pdev->dev); + goto node_put; + } + } + + pdev = of_platform_device_create(np, NULL, &parent_hub->dev); + if (!pdev) { + dev_err(&parent_hub->dev, + "failed to create platform device for onboard dev '%pOF'\n", np); + goto node_put; + } + + pdle = kzalloc_obj(*pdle); + if (!pdle) { + of_platform_device_destroy(&pdev->dev, NULL); + goto node_put; + } + + pdle->pdev = pdev; + list_add(&pdle->node, pdev_list); + +node_put: + of_node_put(np); + } +} +EXPORT_SYMBOL_GPL(onboard_dev_create_pdevs); + +/** + * onboard_dev_destroy_pdevs -- free resources of onboard platform devices + * @pdev_list : list of onboard platform devices + * + * Destroys the platform devices in the given list and frees the memory associated + * with the list entry. + */ +void onboard_dev_destroy_pdevs(struct list_head *pdev_list) +{ + struct pdev_list_entry *pdle, *tmp; + + list_for_each_entry_safe(pdle, tmp, pdev_list, node) { + list_del(&pdle->node); + of_platform_device_destroy(&pdle->pdev->dev, NULL); + kfree(pdle); + } +} +EXPORT_SYMBOL_GPL(onboard_dev_destroy_pdevs); diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c new file mode 100644 index 000000000..926419ca5 --- /dev/null +++ b/drivers/usb/misc/qcom_eud.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EUD_REG_INT1_EN_MASK 0x0024 +#define EUD_REG_INT_STATUS_1 0x0044 +#define EUD_REG_CTL_OUT_1 0x0074 +#define EUD_REG_VBUS_INT_CLR 0x0080 +#define EUD_REG_CSR_EUD_EN 0x1014 +#define EUD_REG_SW_ATTACH_DET 0x1018 +#define EUD_REG_EUD_EN2 0x0000 + +#define EUD_ENABLE BIT(0) +#define EUD_INT_PET_EUD BIT(0) +#define EUD_INT_VBUS BIT(2) +#define EUD_INT_SAFE_MODE BIT(4) +#define EUD_INT_ALL (EUD_INT_VBUS | EUD_INT_SAFE_MODE) + +struct eud_chip { + struct device *dev; + struct usb_role_switch *role_sw; + void __iomem *base; + phys_addr_t mode_mgr; + unsigned int int_status; + int irq; + bool enabled; + bool usb_attached; +}; + +static int enable_eud(struct eud_chip *priv) +{ + int ret; + + ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 1); + if (ret) + return ret; + + writel(EUD_ENABLE, priv->base + EUD_REG_CSR_EUD_EN); + writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE, + priv->base + EUD_REG_INT1_EN_MASK); + + return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE); +} + +static int disable_eud(struct eud_chip *priv) +{ + int ret; + + ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 0); + if (ret) + return ret; + + writel(0, priv->base + EUD_REG_CSR_EUD_EN); + return 0; +} + +static ssize_t enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct eud_chip *chip = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", chip->enabled); +} + +static ssize_t enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct eud_chip *chip = dev_get_drvdata(dev); + bool enable; + int ret; + + if (kstrtobool(buf, &enable)) + return -EINVAL; + + if (enable) { + ret = enable_eud(chip); + if (!ret) + chip->enabled = enable; + else + disable_eud(chip); + + } else { + ret = disable_eud(chip); + } + + return ret < 0 ? ret : count; +} + +static DEVICE_ATTR_RW(enable); + +static struct attribute *eud_attrs[] = { + &dev_attr_enable.attr, + NULL, +}; +ATTRIBUTE_GROUPS(eud); + +static void usb_attach_detach(struct eud_chip *chip) +{ + u32 reg; + + /* read ctl_out_1[4] to find USB attach or detach event */ + reg = readl(chip->base + EUD_REG_CTL_OUT_1); + chip->usb_attached = reg & EUD_INT_SAFE_MODE; +} + +static void pet_eud(struct eud_chip *chip) +{ + u32 reg; + int ret; + + /* When the EUD_INT_PET_EUD in SW_ATTACH_DET is set, the cable has been + * disconnected and we need to detach the pet to check if EUD is in safe + * mode before attaching again. + */ + reg = readl(chip->base + EUD_REG_SW_ATTACH_DET); + if (reg & EUD_INT_PET_EUD) { + /* Detach & Attach pet for EUD */ + writel(0, chip->base + EUD_REG_SW_ATTACH_DET); + /* Delay to make sure detach pet is done before attach pet */ + ret = readl_poll_timeout(chip->base + EUD_REG_SW_ATTACH_DET, + reg, (reg == 0), 1, 100); + if (ret) { + dev_err(chip->dev, "Detach pet failed\n"); + return; + } + } + /* Attach pet for EUD */ + writel(EUD_INT_PET_EUD, chip->base + EUD_REG_SW_ATTACH_DET); +} + +static irqreturn_t handle_eud_irq(int irq, void *data) +{ + struct eud_chip *chip = data; + u32 reg; + + reg = readl(chip->base + EUD_REG_INT_STATUS_1); + switch (reg & EUD_INT_ALL) { + case EUD_INT_VBUS: + usb_attach_detach(chip); + return IRQ_WAKE_THREAD; + case EUD_INT_SAFE_MODE: + pet_eud(chip); + return IRQ_HANDLED; + default: + return IRQ_NONE; + } +} + +static irqreturn_t handle_eud_irq_thread(int irq, void *data) +{ + struct eud_chip *chip = data; + int ret; + + if (chip->usb_attached) + ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_DEVICE); + else + ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_HOST); + if (ret) + dev_err(chip->dev, "failed to set role switch\n"); + + /* set and clear vbus_int_clr[0] to clear interrupt */ + writel(BIT(0), chip->base + EUD_REG_VBUS_INT_CLR); + writel(0, chip->base + EUD_REG_VBUS_INT_CLR); + + return IRQ_HANDLED; +} + +static void eud_role_switch_release(void *data) +{ + struct eud_chip *chip = data; + + usb_role_switch_put(chip->role_sw); +} + +static int eud_probe(struct platform_device *pdev) +{ + struct eud_chip *chip; + struct resource *res; + int ret; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &pdev->dev; + + chip->role_sw = usb_role_switch_get(&pdev->dev); + if (IS_ERR(chip->role_sw)) + return dev_err_probe(chip->dev, PTR_ERR(chip->role_sw), + "failed to get role switch\n"); + + ret = devm_add_action_or_reset(chip->dev, eud_role_switch_release, chip); + if (ret) + return ret; + + chip->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(chip->base)) + return PTR_ERR(chip->base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) + return -ENODEV; + chip->mode_mgr = res->start; + + chip->irq = platform_get_irq(pdev, 0); + if (chip->irq < 0) + return chip->irq; + + ret = devm_request_threaded_irq(&pdev->dev, chip->irq, handle_eud_irq, + handle_eud_irq_thread, IRQF_ONESHOT, NULL, chip); + if (ret) + return dev_err_probe(chip->dev, ret, "failed to allocate irq\n"); + + enable_irq_wake(chip->irq); + + platform_set_drvdata(pdev, chip); + + return 0; +} + +static void eud_remove(struct platform_device *pdev) +{ + struct eud_chip *chip = platform_get_drvdata(pdev); + + if (chip->enabled) + disable_eud(chip); + + device_init_wakeup(&pdev->dev, false); + disable_irq_wake(chip->irq); +} + +static const struct of_device_id eud_dt_match[] = { + { .compatible = "qcom,eud" }, + { } +}; +MODULE_DEVICE_TABLE(of, eud_dt_match); + +static struct platform_driver eud_driver = { + .probe = eud_probe, + .remove = eud_remove, + .driver = { + .name = "qcom_eud", + .dev_groups = eud_groups, + .of_match_table = eud_dt_match, + }, +}; +module_platform_driver(eud_driver); + +MODULE_DESCRIPTION("QTI EUD driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/sisusbvga/Kconfig b/drivers/usb/misc/sisusbvga/Kconfig new file mode 100644 index 000000000..42f81c8ea --- /dev/null +++ b/drivers/usb/misc/sisusbvga/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 + +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. diff --git a/drivers/usb/misc/sisusbvga/Makefile b/drivers/usb/misc/sisusbvga/Makefile new file mode 100644 index 000000000..28aa1e6ef --- /dev/null +++ b/drivers/usb/misc/sisusbvga/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the sisusb driver (if driver is inside kernel tree). +# + +obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga.o diff --git a/drivers/usb/misc/sisusbvga/sisusb.h b/drivers/usb/misc/sisusbvga/sisusb.h new file mode 100644 index 000000000..e5b122865 --- /dev/null +++ b/drivers/usb/misc/sisusbvga/sisusb.h @@ -0,0 +1,278 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* + * 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_ + +#include + +/* Version Information */ + +#define SISUSB_VERSION 0 +#define SISUSB_REVISION 0 +#define SISUSB_PATCHLEVEL 8 + +/* Include console and mode switching code? */ + +#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; +}; + +#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_struct.h b/drivers/usb/misc/sisusbvga/sisusb_struct.h new file mode 100644 index 000000000..a86032a26 --- /dev/null +++ b/drivers/usb/misc/sisusbvga/sisusb_struct.h @@ -0,0 +1,162 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */ +/* + * 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; + signed 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/sisusbvga/sisusb_tables.h b/drivers/usb/misc/sisusbvga/sisusb_tables.h new file mode 100644 index 000000000..56972f1ec --- /dev/null +++ b/drivers/usb/misc/sisusbvga/sisusb_tables.h @@ -0,0 +1,688 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */ +/* $XFree86$ */ +/* $XdotOrg$ */ +/* + * Data tables 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_TABLES_H_ +#define _SISUSB_TABLES_H_ + +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_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 */ +}; + +#endif diff --git a/drivers/usb/misc/sisusbvga/sisusbvga.c b/drivers/usb/misc/sisusbvga/sisusbvga.c new file mode 100644 index 000000000..3e75a7c24 --- /dev/null +++ b/drivers/usb/misc/sisusbvga/sisusbvga.c @@ -0,0 +1,2969 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +/* + * 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" + +#define SISUSB_DONTSYNC + +/* Forward declarations / clean-up routines */ + +static struct usb_driver sisusb_driver; + +static void sisusb_free_buffers(struct sisusb_usb_data *sisusb) +{ + int i; + + for (i = 0; i < NUMOBUFS; i++) { + kfree(sisusb->obuf[i]); + sisusb->obuf[i] = NULL; + } + 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; + + packet.header = (1 << (addr & 3)) | (type << 6); + packet.address = addr & ~3; + packet.data = data << ((addr & 3) << 3); + return sisusb_send_packet(sisusb, 10, &packet); +} + +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; + u8 swap8, fromkern = kernbuffer ? 1 : 0; + u16 swap16; + u32 swap32, flag = (length >> 28) & 1; + u8 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 */ + +static int sisusb_setidxreg(struct sisusb_usb_data *sisusb, u32 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; +} + +static int sisusb_getidxreg(struct sisusb_usb_data *sisusb, u32 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; +} + +static int sisusb_setidxregandor(struct sisusb_usb_data *sisusb, u32 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, + u32 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; +} + +static int sisusb_setidxregor(struct sisusb_usb_data *sisusb, u32 port, + u8 index, u8 myor) +{ + return sisusb_setidxregandor(sisusb, port, index, 0xff, myor); +} + +static int sisusb_setidxregand(struct sisusb_usb_data *sisusb, u32 port, + u8 idx, u8 myand) +{ + return sisusb_setidxregandor(sisusb, port, idx, myand, 0x00); +} + +/* Write/read video ram */ + +#ifdef SISUSBENDIANTEST +static void sisusb_testreadwrite(struct sisusb_usb_data *sisusb) +{ + static u8 srcbuffer[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }; + char destbuffer[10]; + int i, j; + + sisusb_copy_memory(sisusb, srcbuffer, sisusb->vrambase, 7); + + 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); + 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; + + packet.header = 0x008f; + packet.address = regnum | 0x10000; + packet.data = data; + return sisusb_send_packet(sisusb, 10, &packet); +} + +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 */ + i = sisusb_alloc_outbuf(sisusb); + if (i < 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 void sisusb_set_default_mode(struct sisusb_usb_data *sisusb, + int touchengines) +{ + int i, j, modex, 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; 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 */ + SETIREGANDOR(SISSR, 0x0e, 0xf0, ((du >> 8) & 0x0f)); + SETIREG(SISCR, 0x13, (du & 0xff)); + du <<= 5; + tmp8 = du >> 8; + 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 */ +} + +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); + + 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; + static const char ram_datarate[4] = {'S', 'S', 'D', 'D'}; + static const char ram_dynamictype[4] = {'D', 'G', 'D', 'G'}; + 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 = "asymmetric"; + 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 %cDR S%cRAM, bus width %d\n", + sisusb->vramsize >> 20, ramtypetext1, + ram_datarate[ramtype], ram_dynamictype[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); + sisusb_set_default_mode(sisusb, 1); + ret |= sisusb_setup_screen(sisusb, 1, initscreen); + } + } + + return ret; +} + +/* fops */ + +static int sisusb_open(struct inode *inode, struct file *file) +{ + struct sisusb_usb_data *sisusb; + struct usb_interface *interface; + int subminor = iminor(inode); + + interface = usb_find_interface(&sisusb_driver, subminor); + if (!interface) + return -ENODEV; + + sisusb = usb_get_intfdata(interface); + if (!sisusb) + 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; +} + +static void sisusb_delete(struct kref *kref) +{ + struct sisusb_usb_data *sisusb = to_sisusb_dev(kref); + + if (!sisusb) + return; + + usb_put_dev(sisusb->sisusb_dev); + + sisusb->sisusb_dev = NULL; + sisusb_free_buffers(sisusb); + sisusb_free_urbs(sisusb); + kfree(sisusb); +} + +static int sisusb_release(struct inode *inode, struct file *file) +{ + struct sisusb_usb_data *sisusb; + + sisusb = file->private_data; + if (!sisusb) + 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; + + sisusb = file->private_data; + if (!sisusb) + 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; + + sisusb = file->private_data; + if (!sisusb) + 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; + + sisusb = file->private_data; + if (!sisusb) + return -ENODEV; + + mutex_lock(&sisusb->lock); + + /* Sanity check */ + if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev) { + mutex_unlock(&sisusb->lock); + return -ENODEV; + } + + ret = no_seek_end_llseek(file, offset, orig); + + 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, length; + u32 port, 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; + break; + + 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; + + sisusb = file->private_data; + if (!sisusb) + 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; + x.sisusb_conactive = 0; + 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 CONFIG_COMPAT +static long sisusb_compat_ioctl(struct file *f, unsigned int cmd, + unsigned long arg) +{ + switch (cmd) { + case SISUSB_GET_CONFIG_SIZE: + case SISUSB_GET_CONFIG: + case SISUSB_COMMAND: + return sisusb_ioctl(f, cmd, arg); + + 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 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; + static const u8 ep_addresses[] = { + SISUSB_EP_GFX_IN | USB_DIR_IN, + SISUSB_EP_GFX_OUT | USB_DIR_OUT, + SISUSB_EP_GFX_BULK_OUT | USB_DIR_OUT, + SISUSB_EP_GFX_LBULK_OUT | USB_DIR_OUT, + SISUSB_EP_BRIDGE_IN | USB_DIR_IN, + SISUSB_EP_BRIDGE_OUT | USB_DIR_OUT, + 0}; + + /* Are the expected endpoints present? */ + if (!usb_check_bulk_endpoints(intf, ep_addresses)) { + dev_err(&intf->dev, "Invalid USB2VGA device\n"); + return -EINVAL; + } + + dev_info(&dev->dev, "USB2VGA dongle found at address %d\n", + dev->devnum); + + /* Allocate memory for our private */ + sisusb = kzalloc_obj(*sisusb); + if (!sisusb) + return -ENOMEM; + + kref_init(&sisusb->kref); + + mutex_init(&(sisusb->lock)); + + sisusb->sisusb_dev = dev; + sisusb->vrambase = SISUSB_PCI_MEMBASE; + sisusb->mmiobase = SISUSB_PCI_MMIOBASE; + sisusb->mmiosize = SISUSB_PCI_MMIOSIZE; + sisusb->ioportbase = SISUSB_PCI_IOPORTBASE; + /* Everything else is zero */ + + /* Register device */ + retval = usb_register_dev(intf, &usb_sisusb_class); + if (retval) { + dev_err(&sisusb->sisusb_dev->dev, + "Failed to get a minor for device %d\n", + dev->devnum); + retval = -ENODEV; + goto error_1; + } + + sisusb->minor = intf->minor; + + /* Allocate buffers */ + sisusb->ibufsize = SISUSB_IBUF_SIZE; + sisusb->ibuf = kmalloc(SISUSB_IBUF_SIZE, GFP_KERNEL); + if (!sisusb->ibuf) { + retval = -ENOMEM; + goto error_2; + } + + sisusb->numobufs = 0; + sisusb->obufsize = SISUSB_OBUF_SIZE; + for (i = 0; i < NUMOBUFS; i++) { + sisusb->obuf[i] = kmalloc(SISUSB_OBUF_SIZE, GFP_KERNEL); + if (!sisusb->obuf[i]) { + if (i == 0) { + retval = -ENOMEM; + goto error_3; + } + break; + } + sisusb->numobufs++; + } + + /* Allocate URBs */ + sisusb->sisurbin = usb_alloc_urb(0, GFP_KERNEL); + if (!sisusb->sisurbin) { + retval = -ENOMEM; + goto error_3; + } + sisusb->completein = 1; + + for (i = 0; i < sisusb->numobufs; i++) { + sisusb->sisurbout[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!sisusb->sisurbout[i]) { + 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); + + /* 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; + 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 + + 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 */ + sisusb = usb_get_intfdata(intf); + if (!sisusb) + return; + + 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, +}; + +module_usb_driver(sisusb_driver); + +MODULE_AUTHOR("Thomas Winischhofer "); +MODULE_DESCRIPTION("sisusbvga - Driver for Net2280/SiS315-based USB2VGA dongles"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/misc/trancevibrator.c b/drivers/usb/misc/trancevibrator.c new file mode 100644 index 000000000..37f6b7988 --- /dev/null +++ b/drivers/usb/misc/trancevibrator.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PlayStation 2 Trance Vibrator driver + * + * Copyright (C) 2006 Sam Hocevar + */ + +/* Standard include files */ +#include +#include +#include +#include +#include + +#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 speed_show(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 speed_store(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; + + retval = kstrtoint(buf, 10, &temp); + if (retval) + return retval; + 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_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + tv->speed, /* speed value */ + 0, NULL, 0, USB_CTRL_SET_TIMEOUT); + if (retval) { + tv->speed = old; + dev_dbg(&tv->udev->dev, "retval = %d\n", retval); + return retval; + } + return count; +} +static DEVICE_ATTR_RW(speed); + +static struct attribute *tv_attrs[] = { + &dev_attr_speed.attr, + NULL, +}; +ATTRIBUTE_GROUPS(tv); + +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_obj(struct trancevibrator); + if (!dev) { + retval = -ENOMEM; + goto error; + } + + dev->udev = udev; + usb_set_intfdata(interface, dev); + + return 0; + +error: + kfree(dev); + return retval; +} + +static void tv_disconnect(struct usb_interface *interface) +{ + struct trancevibrator *dev; + + dev = usb_get_intfdata (interface); + usb_set_intfdata(interface, NULL); + kfree(dev); +} + +/* USB subsystem object */ +static struct usb_driver tv_driver = { + .name = "trancevibrator", + .probe = tv_probe, + .disconnect = tv_disconnect, + .id_table = id_table, + .dev_groups = tv_groups, +}; + +module_usb_driver(tv_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usb-ljca.c b/drivers/usb/misc/usb-ljca.c new file mode 100644 index 000000000..fcb627b49 --- /dev/null +++ b/drivers/usb/misc/usb-ljca.c @@ -0,0 +1,906 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel La Jolla Cove Adapter USB driver + * + * Copyright (c) 2023, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* command flags */ +#define LJCA_ACK_FLAG BIT(0) +#define LJCA_RESP_FLAG BIT(1) +#define LJCA_CMPL_FLAG BIT(2) + +#define LJCA_MAX_PACKET_SIZE 64u +#define LJCA_MAX_PAYLOAD_SIZE \ + (LJCA_MAX_PACKET_SIZE - sizeof(struct ljca_msg)) + +#define LJCA_WRITE_TIMEOUT_MS 200 +#define LJCA_WRITE_ACK_TIMEOUT_MS 500 +#define LJCA_ENUM_CLIENT_TIMEOUT_MS 20 + +/* ljca client type */ +enum ljca_client_type { + LJCA_CLIENT_MNG = 1, + LJCA_CLIENT_GPIO = 3, + LJCA_CLIENT_I2C = 4, + LJCA_CLIENT_SPI = 5, +}; + +/* MNG client commands */ +enum ljca_mng_cmd { + LJCA_MNG_RESET = 2, + LJCA_MNG_ENUM_GPIO = 4, + LJCA_MNG_ENUM_I2C = 5, + LJCA_MNG_ENUM_SPI = 8, +}; + +/* ljca client acpi _ADR */ +enum ljca_client_acpi_adr { + LJCA_GPIO_ACPI_ADR, + LJCA_I2C1_ACPI_ADR, + LJCA_I2C2_ACPI_ADR, + LJCA_SPI1_ACPI_ADR, + LJCA_SPI2_ACPI_ADR, + LJCA_CLIENT_ACPI_ADR_MAX, +}; + +/* ljca cmd message structure */ +struct ljca_msg { + u8 type; + u8 cmd; + u8 flags; + u8 len; + u8 data[] __counted_by(len); +} __packed; + +struct ljca_i2c_ctr_info { + u8 id; + u8 capacity; + u8 intr_pin; +} __packed; + +struct ljca_i2c_descriptor { + u8 num; + struct ljca_i2c_ctr_info info[] __counted_by(num); +} __packed; + +struct ljca_spi_ctr_info { + u8 id; + u8 capacity; + u8 intr_pin; +} __packed; + +struct ljca_spi_descriptor { + u8 num; + struct ljca_spi_ctr_info info[] __counted_by(num); +} __packed; + +struct ljca_bank_descriptor { + u8 bank_id; + u8 pin_num; + + /* 1 bit for each gpio, 1 means valid */ + __le32 valid_pins; +} __packed; + +struct ljca_gpio_descriptor { + u8 pins_per_bank; + u8 bank_num; + struct ljca_bank_descriptor bank_desc[] __counted_by(bank_num); +} __packed; + +/** + * struct ljca_adapter - represent a ljca adapter + * + * @intf: the usb interface for this ljca adapter + * @usb_dev: the usb device for this ljca adapter + * @dev: the specific device info of the usb interface + * @rx_pipe: bulk in pipe for receive data from firmware + * @tx_pipe: bulk out pipe for send data to firmware + * @rx_urb: urb used for the bulk in pipe + * @rx_buf: buffer used to receive command response and event + * @rx_len: length of rx buffer + * @ex_buf: external buffer to save command response + * @ex_buf_len: length of external buffer + * @actual_length: actual length of data copied to external buffer + * @tx_buf: buffer used to download command to firmware + * @tx_buf_len: length of tx buffer + * @lock: spinlock to protect tx_buf and ex_buf + * @cmd_completion: completion object as the command receives ack + * @mutex: mutex to avoid command download concurrently + * @client_list: client device list + * @disconnect: usb disconnect ongoing or not + * @reset_id: used to reset firmware + */ +struct ljca_adapter { + struct usb_interface *intf; + struct usb_device *usb_dev; + struct device *dev; + + unsigned int rx_pipe; + unsigned int tx_pipe; + + struct urb *rx_urb; + void *rx_buf; + unsigned int rx_len; + + u8 *ex_buf; + u8 ex_buf_len; + u8 actual_length; + + void *tx_buf; + u8 tx_buf_len; + + spinlock_t lock; + + struct completion cmd_completion; + struct mutex mutex; + + struct list_head client_list; + + bool disconnect; + + u32 reset_id; +}; + +struct ljca_match_ids_walk_data { + const struct acpi_device_id *ids; + const char *uid; + struct acpi_device *adev; +}; + +/* + * ACPI hardware IDs for LJCA client devices. + * + * [1] Some BIOS implementations use these IDs for denoting LJCA client devices + * even though the IDs have been allocated for USBIO. This isn't a problem + * as the usb-ljca driver is probed based on the USB device's vendor and + * product IDs and its client drivers are probed based on auxiliary device + * names, not these ACPI _HIDs. List of such systems: + * + * Dell Precision 5490 + */ +static const struct acpi_device_id ljca_gpio_hids[] = { + { "INTC100B" }, /* RPL LJCA GPIO */ + { "INTC1074" }, /* CVF LJCA GPIO */ + { "INTC1096" }, /* ADL LJCA GPIO */ + { "INTC10B5" }, /* LNL LJCA GPIO */ + { "INTC10D1" }, /* MTL (CVF VSC) USBIO GPIO [1] */ + {}, +}; + +static const struct acpi_device_id ljca_i2c_hids[] = { + { "INTC100C" }, /* RPL LJCA I2C */ + { "INTC1075" }, /* CVF LJCA I2C */ + { "INTC1097" }, /* ADL LJCA I2C */ + { "INTC10D2" }, /* MTL (CVF VSC) USBIO I2C [1] */ + {}, +}; + +static const struct acpi_device_id ljca_spi_hids[] = { + { "INTC100D" }, /* RPL LJCA SPI */ + { "INTC1091" }, /* TGL/ADL LJCA SPI */ + { "INTC1098" }, /* ADL LJCA SPI */ + { "INTC10D3" }, /* MTL (CVF VSC) USBIO SPI [1] */ + {}, +}; + +static void ljca_handle_event(struct ljca_adapter *adap, + struct ljca_msg *header) +{ + struct ljca_client *client; + + list_for_each_entry(client, &adap->client_list, link) { + /* + * Currently only GPIO register event callback, but + * firmware message structure should include id when + * multiple same type clients register event callback. + */ + if (client->type == header->type) { + unsigned long flags; + + spin_lock_irqsave(&client->event_cb_lock, flags); + client->event_cb(client->context, header->cmd, + header->data, header->len); + spin_unlock_irqrestore(&client->event_cb_lock, flags); + + break; + } + } +} + +/* process command ack and received data if available */ +static void ljca_handle_cmd_ack(struct ljca_adapter *adap, struct ljca_msg *header) +{ + struct ljca_msg *tx_header = adap->tx_buf; + u8 ibuf_len, actual_len = 0; + unsigned long flags; + u8 *ibuf; + + spin_lock_irqsave(&adap->lock, flags); + + if (tx_header->type != header->type || tx_header->cmd != header->cmd) { + spin_unlock_irqrestore(&adap->lock, flags); + dev_err(adap->dev, "cmd ack mismatch error\n"); + return; + } + + ibuf_len = adap->ex_buf_len; + ibuf = adap->ex_buf; + + if (ibuf && ibuf_len) { + actual_len = min(header->len, ibuf_len); + + /* copy received data to external buffer */ + memcpy(ibuf, header->data, actual_len); + } + /* update copied data length */ + adap->actual_length = actual_len; + + spin_unlock_irqrestore(&adap->lock, flags); + + complete(&adap->cmd_completion); +} + +static void ljca_recv(struct urb *urb) +{ + struct ljca_msg *header = urb->transfer_buffer; + struct ljca_adapter *adap = urb->context; + int ret; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ENOENT: + /* + * directly complete the possible ongoing transfer + * during disconnect + */ + if (adap->disconnect) + complete(&adap->cmd_completion); + return; + case -ECONNRESET: + case -ESHUTDOWN: + case -EPIPE: + /* rx urb is terminated */ + dev_dbg(adap->dev, "rx urb terminated with status: %d\n", + urb->status); + return; + default: + dev_dbg(adap->dev, "rx urb error: %d\n", urb->status); + goto resubmit; + } + + if (header->len + sizeof(*header) != urb->actual_length) + goto resubmit; + + if (header->flags & LJCA_ACK_FLAG) + ljca_handle_cmd_ack(adap, header); + else + ljca_handle_event(adap, header); + +resubmit: + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret && ret != -EPERM) + dev_err(adap->dev, "resubmit rx urb error %d\n", ret); +} + +static int ljca_send(struct ljca_adapter *adap, u8 type, u8 cmd, + const u8 *obuf, u8 obuf_len, u8 *ibuf, u8 ibuf_len, + bool ack, unsigned long timeout) +{ + unsigned int msg_len = sizeof(struct ljca_msg) + obuf_len; + struct ljca_msg *header = adap->tx_buf; + unsigned int transferred; + unsigned long flags; + int ret; + + if (adap->disconnect) + return -ENODEV; + + if (msg_len > adap->tx_buf_len) + return -EINVAL; + + mutex_lock(&adap->mutex); + + spin_lock_irqsave(&adap->lock, flags); + + header->type = type; + header->cmd = cmd; + header->len = obuf_len; + if (obuf) + memcpy(header->data, obuf, obuf_len); + + header->flags = LJCA_CMPL_FLAG | (ack ? LJCA_ACK_FLAG : 0); + + adap->ex_buf = ibuf; + adap->ex_buf_len = ibuf_len; + adap->actual_length = 0; + + spin_unlock_irqrestore(&adap->lock, flags); + + reinit_completion(&adap->cmd_completion); + + ret = usb_autopm_get_interface(adap->intf); + if (ret < 0) + goto out; + + ret = usb_bulk_msg(adap->usb_dev, adap->tx_pipe, header, + msg_len, &transferred, LJCA_WRITE_TIMEOUT_MS); + if (ret < 0) + goto out_put; + if (transferred != msg_len) { + ret = -EIO; + goto out_put; + } + + if (ack) { + ret = wait_for_completion_timeout(&adap->cmd_completion, + timeout); + if (!ret) { + ret = -ETIMEDOUT; + goto out_put; + } + } + ret = adap->actual_length; + +out_put: + usb_autopm_put_interface(adap->intf); + +out: + spin_lock_irqsave(&adap->lock, flags); + adap->ex_buf = NULL; + adap->ex_buf_len = 0; + + memset(header, 0, sizeof(*header)); + spin_unlock_irqrestore(&adap->lock, flags); + + mutex_unlock(&adap->mutex); + + return ret; +} + +int ljca_transfer(struct ljca_client *client, u8 cmd, const u8 *obuf, + u8 obuf_len, u8 *ibuf, u8 ibuf_len) +{ + return ljca_send(client->adapter, client->type, cmd, + obuf, obuf_len, ibuf, ibuf_len, true, + LJCA_WRITE_ACK_TIMEOUT_MS); +} +EXPORT_SYMBOL_NS_GPL(ljca_transfer, "LJCA"); + +int ljca_transfer_noack(struct ljca_client *client, u8 cmd, const u8 *obuf, + u8 obuf_len) +{ + return ljca_send(client->adapter, client->type, cmd, obuf, + obuf_len, NULL, 0, false, LJCA_WRITE_ACK_TIMEOUT_MS); +} +EXPORT_SYMBOL_NS_GPL(ljca_transfer_noack, "LJCA"); + +int ljca_register_event_cb(struct ljca_client *client, ljca_event_cb_t event_cb, + void *context) +{ + unsigned long flags; + + if (!event_cb) + return -EINVAL; + + spin_lock_irqsave(&client->event_cb_lock, flags); + + if (client->event_cb) { + spin_unlock_irqrestore(&client->event_cb_lock, flags); + return -EALREADY; + } + + client->event_cb = event_cb; + client->context = context; + + spin_unlock_irqrestore(&client->event_cb_lock, flags); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ljca_register_event_cb, "LJCA"); + +void ljca_unregister_event_cb(struct ljca_client *client) +{ + unsigned long flags; + + spin_lock_irqsave(&client->event_cb_lock, flags); + + client->event_cb = NULL; + client->context = NULL; + + spin_unlock_irqrestore(&client->event_cb_lock, flags); +} +EXPORT_SYMBOL_NS_GPL(ljca_unregister_event_cb, "LJCA"); + +static int ljca_match_device_ids(struct acpi_device *adev, void *data) +{ + struct ljca_match_ids_walk_data *wd = data; + const char *uid = acpi_device_uid(adev); + + if (acpi_match_device_ids(adev, wd->ids)) + return 0; + + if (!wd->uid) + goto match; + + if (!uid) + /* + * Some DSDTs have only one ACPI companion for the two I2C + * controllers and they don't set a UID at all (e.g. Dell + * Latitude 9420). On these platforms only the first I2C + * controller is used, so if a HID match has no UID we use + * "0" as the UID and assign ACPI companion to the first + * I2C controller. + */ + uid = "0"; + else + uid = strchr(uid, wd->uid[0]); + + if (!uid || strcmp(uid, wd->uid)) + return 0; + +match: + wd->adev = adev; + + return 1; +} + +/* bind auxiliary device to acpi device */ +static void ljca_auxdev_acpi_bind(struct ljca_adapter *adap, + struct auxiliary_device *auxdev, + u64 adr, u8 id) +{ + struct ljca_match_ids_walk_data wd = { 0 }; + struct device *dev = adap->dev; + struct acpi_device *parent; + char uid[4]; + + parent = ACPI_COMPANION(dev); + if (!parent) + return; + + /* + * Currently LJCA hw doesn't use _ADR instead the shipped + * platforms use _HID to distinguish children devices. + */ + switch (adr) { + case LJCA_GPIO_ACPI_ADR: + wd.ids = ljca_gpio_hids; + break; + case LJCA_I2C1_ACPI_ADR: + case LJCA_I2C2_ACPI_ADR: + snprintf(uid, sizeof(uid), "%d", id); + wd.uid = uid; + wd.ids = ljca_i2c_hids; + break; + case LJCA_SPI1_ACPI_ADR: + case LJCA_SPI2_ACPI_ADR: + wd.ids = ljca_spi_hids; + break; + default: + dev_warn(dev, "unsupported _ADR\n"); + return; + } + + acpi_dev_for_each_child(parent, ljca_match_device_ids, &wd); + if (wd.adev) { + ACPI_COMPANION_SET(&auxdev->dev, wd.adev); + return; + } + + parent = ACPI_COMPANION(dev->parent->parent); + if (!parent) + return; + + acpi_dev_for_each_child(parent, ljca_match_device_ids, &wd); + if (wd.adev) + ACPI_COMPANION_SET(&auxdev->dev, wd.adev); +} + +static void ljca_auxdev_release(struct device *dev) +{ + struct auxiliary_device *auxdev = to_auxiliary_dev(dev); + + kfree(auxdev->dev.platform_data); +} + +static int ljca_new_client_device(struct ljca_adapter *adap, u8 type, u8 id, + char *name, void *data, u64 adr) +{ + struct auxiliary_device *auxdev; + struct ljca_client *client; + int ret; + + client = kzalloc_obj(*client); + if (!client) { + kfree(data); + return -ENOMEM; + } + + client->type = type; + client->id = id; + client->adapter = adap; + spin_lock_init(&client->event_cb_lock); + + auxdev = &client->auxdev; + auxdev->name = name; + auxdev->id = id; + + auxdev->dev.parent = adap->dev; + auxdev->dev.platform_data = data; + auxdev->dev.release = ljca_auxdev_release; + + ret = auxiliary_device_init(auxdev); + if (ret) { + kfree(data); + goto err_free; + } + + ljca_auxdev_acpi_bind(adap, auxdev, adr, id); + + ret = auxiliary_device_add(auxdev); + if (ret) + goto err_uninit; + + list_add_tail(&client->link, &adap->client_list); + + return 0; + +err_uninit: + auxiliary_device_uninit(auxdev); + +err_free: + kfree(client); + + return ret; +} + +static int ljca_enumerate_gpio(struct ljca_adapter *adap) +{ + u32 valid_pin[LJCA_MAX_GPIO_NUM / BITS_PER_TYPE(u32)]; + struct ljca_gpio_descriptor *desc; + struct ljca_gpio_info *gpio_info; + u8 buf[LJCA_MAX_PAYLOAD_SIZE]; + int ret, gpio_num; + unsigned int i; + + ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_GPIO, NULL, 0, buf, + sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS); + if (ret < 0) + return ret; + + /* check firmware response */ + desc = (struct ljca_gpio_descriptor *)buf; + if (ret != struct_size(desc, bank_desc, desc->bank_num)) + return -EINVAL; + + gpio_num = desc->pins_per_bank * desc->bank_num; + if (gpio_num > LJCA_MAX_GPIO_NUM) + return -EINVAL; + + if (desc->bank_num > ARRAY_SIZE(valid_pin)) + return -EINVAL; + + /* construct platform data */ + gpio_info = kzalloc_obj(*gpio_info); + if (!gpio_info) + return -ENOMEM; + gpio_info->num = gpio_num; + + for (i = 0; i < desc->bank_num; i++) + valid_pin[i] = get_unaligned_le32(&desc->bank_desc[i].valid_pins); + bitmap_from_arr32(gpio_info->valid_pin_map, valid_pin, gpio_num); + + return ljca_new_client_device(adap, LJCA_CLIENT_GPIO, 0, "ljca-gpio", + gpio_info, LJCA_GPIO_ACPI_ADR); +} + +static int ljca_enumerate_i2c(struct ljca_adapter *adap) +{ + struct ljca_i2c_descriptor *desc; + struct ljca_i2c_info *i2c_info; + u8 buf[LJCA_MAX_PAYLOAD_SIZE]; + unsigned int i; + int ret; + + ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_I2C, NULL, 0, buf, + sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS); + if (ret < 0) + return ret; + + /* check firmware response */ + desc = (struct ljca_i2c_descriptor *)buf; + if (ret != struct_size(desc, info, desc->num)) + return -EINVAL; + + for (i = 0; i < desc->num; i++) { + /* construct platform data */ + i2c_info = kzalloc_obj(*i2c_info); + if (!i2c_info) + return -ENOMEM; + + i2c_info->id = desc->info[i].id; + i2c_info->capacity = desc->info[i].capacity; + i2c_info->intr_pin = desc->info[i].intr_pin; + + ret = ljca_new_client_device(adap, LJCA_CLIENT_I2C, i, + "ljca-i2c", i2c_info, + LJCA_I2C1_ACPI_ADR + i); + if (ret) + return ret; + } + + return 0; +} + +static int ljca_enumerate_spi(struct ljca_adapter *adap) +{ + struct ljca_spi_descriptor *desc; + struct ljca_spi_info *spi_info; + u8 buf[LJCA_MAX_PAYLOAD_SIZE]; + unsigned int i; + int ret; + + /* Not all LJCA chips implement SPI, a timeout reading the descriptors is normal */ + ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_SPI, NULL, 0, buf, + sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS); + if (ret < 0) + return (ret == -ETIMEDOUT) ? 0 : ret; + + /* check firmware response */ + desc = (struct ljca_spi_descriptor *)buf; + if (ret != struct_size(desc, info, desc->num)) + return -EINVAL; + + for (i = 0; i < desc->num; i++) { + /* construct platform data */ + spi_info = kzalloc_obj(*spi_info); + if (!spi_info) + return -ENOMEM; + + spi_info->id = desc->info[i].id; + spi_info->capacity = desc->info[i].capacity; + + ret = ljca_new_client_device(adap, LJCA_CLIENT_SPI, i, + "ljca-spi", spi_info, + LJCA_SPI1_ACPI_ADR + i); + if (ret) + return ret; + } + + return 0; +} + +static int ljca_reset_handshake(struct ljca_adapter *adap) +{ + __le32 reset_id = cpu_to_le32(adap->reset_id); + __le32 reset_id_ret = 0; + int ret; + + adap->reset_id++; + + ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_RESET, (u8 *)&reset_id, + sizeof(__le32), (u8 *)&reset_id_ret, sizeof(__le32), + true, LJCA_WRITE_ACK_TIMEOUT_MS); + if (ret < 0) + return ret; + + if (reset_id_ret != reset_id) + return -EINVAL; + + return 0; +} + +static int ljca_enumerate_clients(struct ljca_adapter *adap) +{ + struct ljca_client *client, *next; + int ret; + + ret = ljca_reset_handshake(adap); + if (ret) + goto err_kill; + + ret = ljca_enumerate_gpio(adap); + if (ret) { + dev_err(adap->dev, "enumerate GPIO error\n"); + goto err_kill; + } + + ret = ljca_enumerate_i2c(adap); + if (ret) { + dev_err(adap->dev, "enumerate I2C error\n"); + goto err_kill; + } + + ret = ljca_enumerate_spi(adap); + if (ret) { + dev_err(adap->dev, "enumerate SPI error\n"); + goto err_kill; + } + + return 0; + +err_kill: + adap->disconnect = true; + + usb_kill_urb(adap->rx_urb); + + list_for_each_entry_safe_reverse(client, next, &adap->client_list, link) { + auxiliary_device_delete(&client->auxdev); + auxiliary_device_uninit(&client->auxdev); + + list_del_init(&client->link); + kfree(client); + } + + return ret; +} + +static int ljca_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *usb_dev = interface_to_usbdev(interface); + struct usb_host_interface *alt = interface->cur_altsetting; + struct usb_endpoint_descriptor *ep_in, *ep_out; + struct device *dev = &interface->dev; + struct ljca_adapter *adap; + int ret; + + adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL); + if (!adap) + return -ENOMEM; + + /* separate tx buffer allocation for alignment */ + adap->tx_buf = devm_kzalloc(dev, LJCA_MAX_PACKET_SIZE, GFP_KERNEL); + if (!adap->tx_buf) + return -ENOMEM; + adap->tx_buf_len = LJCA_MAX_PACKET_SIZE; + + mutex_init(&adap->mutex); + spin_lock_init(&adap->lock); + init_completion(&adap->cmd_completion); + INIT_LIST_HEAD(&adap->client_list); + + adap->intf = interface; + adap->usb_dev = usb_dev; + adap->dev = dev; + + /* + * find the first bulk in and out endpoints. + * ignore any others. + */ + ret = usb_find_common_endpoints(alt, &ep_in, &ep_out, NULL, NULL); + if (ret) { + dev_err(dev, "bulk endpoints not found\n"); + goto err_destroy_mutex; + } + adap->rx_pipe = usb_rcvbulkpipe(usb_dev, usb_endpoint_num(ep_in)); + adap->tx_pipe = usb_sndbulkpipe(usb_dev, usb_endpoint_num(ep_out)); + + /* setup rx buffer */ + adap->rx_len = usb_endpoint_maxp(ep_in); + adap->rx_buf = devm_kzalloc(dev, adap->rx_len, GFP_KERNEL); + if (!adap->rx_buf) { + ret = -ENOMEM; + goto err_destroy_mutex; + } + + /* alloc rx urb */ + adap->rx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!adap->rx_urb) { + ret = -ENOMEM; + goto err_destroy_mutex; + } + usb_fill_bulk_urb(adap->rx_urb, usb_dev, adap->rx_pipe, + adap->rx_buf, adap->rx_len, ljca_recv, adap); + + usb_set_intfdata(interface, adap); + + /* submit rx urb before enumerate clients */ + ret = usb_submit_urb(adap->rx_urb, GFP_KERNEL); + if (ret) { + dev_err(dev, "submit rx urb failed: %d\n", ret); + goto err_free; + } + + ret = ljca_enumerate_clients(adap); + if (ret) + goto err_free; + + /* + * This works around problems with ov2740 initialization on some + * Lenovo platforms. The autosuspend delay, has to be smaller than + * the delay after setting the reset_gpio line in ov2740_resume(). + * Otherwise the sensor randomly fails to initialize. + */ + pm_runtime_set_autosuspend_delay(&usb_dev->dev, 10); + + usb_enable_autosuspend(usb_dev); + + return 0; + +err_free: + usb_free_urb(adap->rx_urb); +err_destroy_mutex: + mutex_destroy(&adap->mutex); + + return ret; +} + +static void ljca_disconnect(struct usb_interface *interface) +{ + struct ljca_adapter *adap = usb_get_intfdata(interface); + struct ljca_client *client, *next; + + adap->disconnect = true; + + usb_kill_urb(adap->rx_urb); + + list_for_each_entry_safe_reverse(client, next, &adap->client_list, link) { + auxiliary_device_delete(&client->auxdev); + auxiliary_device_uninit(&client->auxdev); + + list_del_init(&client->link); + kfree(client); + } + + usb_free_urb(adap->rx_urb); + + mutex_destroy(&adap->mutex); +} + +static int ljca_suspend(struct usb_interface *interface, pm_message_t message) +{ + struct ljca_adapter *adap = usb_get_intfdata(interface); + + usb_kill_urb(adap->rx_urb); + + return 0; +} + +static int ljca_resume(struct usb_interface *interface) +{ + struct ljca_adapter *adap = usb_get_intfdata(interface); + + return usb_submit_urb(adap->rx_urb, GFP_KERNEL); +} + +static const struct usb_device_id ljca_table[] = { + { USB_DEVICE(0x8086, 0x0b63) }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(usb, ljca_table); + +static struct usb_driver ljca_driver = { + .name = "ljca", + .id_table = ljca_table, + .probe = ljca_probe, + .disconnect = ljca_disconnect, + .suspend = ljca_suspend, + .resume = ljca_resume, + .supports_autosuspend = 1, +}; +module_usb_driver(ljca_driver); + +MODULE_AUTHOR("Wentong Wu"); +MODULE_AUTHOR("Zhifeng Wang "); +MODULE_AUTHOR("Lixu Zhang "); +MODULE_DESCRIPTION("Intel La Jolla Cove Adapter USB driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c new file mode 100644 index 000000000..fb0693742 --- /dev/null +++ b/drivers/usb/misc/usb251xb.c @@ -0,0 +1,834 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Microchip USB251xB USB 2.0 Hi-Speed Hub Controller + * Configuration via SMBus. + * + * Copyright (c) 2017 SKIDATA AG + * + * This work is based on the USB3503 driver by Dongjin Kim and + * a not-accepted patch by Fabien Lahoudere, see: + * https://patchwork.kernel.org/patch/9257715/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Internal Register Set Addresses & Default Values acc. to DS00001692C */ +#define USB251XB_ADDR_VENDOR_ID_LSB 0x00 +#define USB251XB_ADDR_VENDOR_ID_MSB 0x01 +#define USB251XB_DEF_VENDOR_ID 0x0424 + +#define USB251XB_ADDR_PRODUCT_ID_LSB 0x02 +#define USB251XB_ADDR_PRODUCT_ID_MSB 0x03 + +#define USB251XB_ADDR_DEVICE_ID_LSB 0x04 +#define USB251XB_ADDR_DEVICE_ID_MSB 0x05 +#define USB251XB_DEF_DEVICE_ID 0x0BB3 + +#define USB251XB_ADDR_CONFIG_DATA_1 0x06 +#define USB251XB_DEF_CONFIG_DATA_1 0x9B +#define USB251XB_ADDR_CONFIG_DATA_2 0x07 +#define USB251XB_DEF_CONFIG_DATA_2 0x20 +#define USB251XB_ADDR_CONFIG_DATA_3 0x08 +#define USB251XB_DEF_CONFIG_DATA_3 0x02 + +#define USB251XB_ADDR_NON_REMOVABLE_DEVICES 0x09 +#define USB251XB_DEF_NON_REMOVABLE_DEVICES 0x00 + +#define USB251XB_ADDR_PORT_DISABLE_SELF 0x0A +#define USB251XB_DEF_PORT_DISABLE_SELF 0x00 +#define USB251XB_ADDR_PORT_DISABLE_BUS 0x0B +#define USB251XB_DEF_PORT_DISABLE_BUS 0x00 + +#define USB251XB_ADDR_MAX_POWER_SELF 0x0C +#define USB251XB_DEF_MAX_POWER_SELF 0x01 +#define USB251XB_ADDR_MAX_POWER_BUS 0x0D +#define USB251XB_DEF_MAX_POWER_BUS 0x32 + +#define USB251XB_ADDR_MAX_CURRENT_SELF 0x0E +#define USB251XB_DEF_MAX_CURRENT_SELF 0x01 +#define USB251XB_ADDR_MAX_CURRENT_BUS 0x0F +#define USB251XB_DEF_MAX_CURRENT_BUS 0x32 + +#define USB251XB_ADDR_POWER_ON_TIME 0x10 +#define USB251XB_DEF_POWER_ON_TIME 0x32 + +#define USB251XB_ADDR_LANGUAGE_ID_HIGH 0x11 +#define USB251XB_ADDR_LANGUAGE_ID_LOW 0x12 +#define USB251XB_DEF_LANGUAGE_ID 0x0000 + +#define USB251XB_STRING_BUFSIZE 62 +#define USB251XB_ADDR_MANUFACTURER_STRING_LEN 0x13 +#define USB251XB_ADDR_MANUFACTURER_STRING 0x16 +#define USB251XB_DEF_MANUFACTURER_STRING "Microchip" + +#define USB251XB_ADDR_PRODUCT_STRING_LEN 0x14 +#define USB251XB_ADDR_PRODUCT_STRING 0x54 + +#define USB251XB_ADDR_SERIAL_STRING_LEN 0x15 +#define USB251XB_ADDR_SERIAL_STRING 0x92 +#define USB251XB_DEF_SERIAL_STRING "" + +#define USB251XB_ADDR_BATTERY_CHARGING_ENABLE 0xD0 +#define USB251XB_DEF_BATTERY_CHARGING_ENABLE 0x00 + +#define USB251XB_ADDR_BOOST_UP 0xF6 +#define USB251XB_DEF_BOOST_UP 0x00 +#define USB251XB_ADDR_BOOST_57 0xF7 +#define USB251XB_DEF_BOOST_57 0x00 +#define USB251XB_ADDR_BOOST_14 0xF8 +#define USB251XB_DEF_BOOST_14 0x00 + +#define USB251XB_ADDR_PORT_SWAP 0xFA +#define USB251XB_DEF_PORT_SWAP 0x00 + +#define USB251XB_ADDR_PORT_MAP_12 0xFB +#define USB251XB_DEF_PORT_MAP_12 0x00 +#define USB251XB_ADDR_PORT_MAP_34 0xFC +#define USB251XB_DEF_PORT_MAP_34 0x00 /* USB251{3B/i,4B/i,7/i} only */ +#define USB251XB_ADDR_PORT_MAP_56 0xFD +#define USB251XB_DEF_PORT_MAP_56 0x00 /* USB2517/i only */ +#define USB251XB_ADDR_PORT_MAP_7 0xFE +#define USB251XB_DEF_PORT_MAP_7 0x00 /* USB2517/i only */ + +#define USB251XB_ADDR_STATUS_COMMAND 0xFF +#define USB251XB_STATUS_COMMAND_SMBUS_DOWN 0x04 +#define USB251XB_STATUS_COMMAND_RESET 0x02 +#define USB251XB_STATUS_COMMAND_ATTACH 0x01 + +#define USB251XB_I2C_REG_SZ 0x100 +#define USB251XB_I2C_WRITE_SZ 0x10 + +#define DRIVER_NAME "usb251xb" +#define DRIVER_DESC "Microchip USB 2.0 Hi-Speed Hub Controller" + +struct usb251xb { + struct device *dev; + struct i2c_client *i2c; + struct regulator *vdd; + u8 skip_config; + struct gpio_desc *gpio_reset; + u16 vendor_id; + u16 product_id; + u16 device_id; + u8 conf_data1; + u8 conf_data2; + u8 conf_data3; + u8 non_rem_dev; + u8 port_disable_sp; + u8 port_disable_bp; + u8 max_power_sp; + u8 max_power_bp; + u8 max_current_sp; + u8 max_current_bp; + u8 power_on_time; + u16 lang_id; + u8 manufacturer_len; + u8 product_len; + u8 serial_len; + char manufacturer[USB251XB_STRING_BUFSIZE]; + char product[USB251XB_STRING_BUFSIZE]; + char serial[USB251XB_STRING_BUFSIZE]; + u8 bat_charge_en; + u8 boost_up; + u8 boost_57; + u8 boost_14; + u8 port_swap; + u8 port_map12; + u8 port_map34; + u8 port_map56; + u8 port_map7; + u8 status; +}; + +struct usb251xb_data { + u16 product_id; + u8 port_cnt; + bool led_support; + bool bat_support; + char product_str[USB251XB_STRING_BUFSIZE / 2]; /* ASCII string */ +}; + +static const struct usb251xb_data usb2422_data = { + .product_id = 0x2422, + .port_cnt = 2, + .led_support = false, + .bat_support = true, + .product_str = "USB2422", +}; + +static const struct usb251xb_data usb2512b_data = { + .product_id = 0x2512, + .port_cnt = 2, + .led_support = false, + .bat_support = true, + .product_str = "USB2512B", +}; + +static const struct usb251xb_data usb2512bi_data = { + .product_id = 0x2512, + .port_cnt = 2, + .led_support = false, + .bat_support = true, + .product_str = "USB2512Bi", +}; + +static const struct usb251xb_data usb2513b_data = { + .product_id = 0x2513, + .port_cnt = 3, + .led_support = false, + .bat_support = true, + .product_str = "USB2513B", +}; + +static const struct usb251xb_data usb2513bi_data = { + .product_id = 0x2513, + .port_cnt = 3, + .led_support = false, + .bat_support = true, + .product_str = "USB2513Bi", +}; + +static const struct usb251xb_data usb2514b_data = { + .product_id = 0x2514, + .port_cnt = 4, + .led_support = false, + .bat_support = true, + .product_str = "USB2514B", +}; + +static const struct usb251xb_data usb2514bi_data = { + .product_id = 0x2514, + .port_cnt = 4, + .led_support = false, + .bat_support = true, + .product_str = "USB2514Bi", +}; + +static const struct usb251xb_data usb2517_data = { + .product_id = 0x2517, + .port_cnt = 7, + .led_support = true, + .bat_support = false, + .product_str = "USB2517", +}; + +static const struct usb251xb_data usb2517i_data = { + .product_id = 0x2517, + .port_cnt = 7, + .led_support = true, + .bat_support = false, + .product_str = "USB2517i", +}; + +#ifdef CONFIG_GPIOLIB +static int usb251xb_check_dev_children(struct device *dev, void *child) +{ + if (dev->type == &i2c_adapter_type) { + return device_for_each_child(dev, child, + usb251xb_check_dev_children); + } + + return (dev == child); +} + +static int usb251x_check_gpio_chip(struct usb251xb *hub) +{ + struct gpio_chip *gc = gpiod_to_chip(hub->gpio_reset); + struct i2c_adapter *adap; + int ret; + + if (!hub->i2c) + return 0; + + if (!hub->gpio_reset) + return 0; + + if (!gc) + return -EINVAL; + + adap = hub->i2c->adapter; + ret = usb251xb_check_dev_children(&adap->dev, gc->parent); + if (ret) { + dev_err(hub->dev, "Reset GPIO chip is at the same i2c-bus\n"); + return -EINVAL; + } + + return 0; +} +#else +static int usb251x_check_gpio_chip(struct usb251xb *hub) +{ + return 0; +} +#endif + +static void usb251xb_reset(struct usb251xb *hub) +{ + if (!hub->gpio_reset) + return; + + if (hub->i2c) + i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); + + gpiod_set_value_cansleep(hub->gpio_reset, 1); + usleep_range(1, 10); /* >=1us RESET_N asserted */ + gpiod_set_value_cansleep(hub->gpio_reset, 0); + + /* wait for hub recovery/stabilization */ + usleep_range(500, 750); /* >=500us after RESET_N deasserted */ + + if (hub->i2c) + i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); +} + +static int usb251xb_connect(struct usb251xb *hub) +{ + struct device *dev = hub->dev; + int err, i; + char i2c_wb[USB251XB_I2C_REG_SZ]; + + if (!hub->i2c) { + usb251xb_reset(hub); + dev_info(dev, "hub is put in default configuration.\n"); + return 0; + } + + memset(i2c_wb, 0, USB251XB_I2C_REG_SZ); + + if (hub->skip_config) { + dev_info(dev, "Skip hub configuration, only attach.\n"); + i2c_wb[0] = 0x01; + i2c_wb[1] = USB251XB_STATUS_COMMAND_ATTACH; + + usb251xb_reset(hub); + + err = i2c_smbus_write_i2c_block_data(hub->i2c, + USB251XB_ADDR_STATUS_COMMAND, 2, i2c_wb); + if (err) { + dev_err(dev, "attaching hub failed: %d\n", err); + return err; + } + return 0; + } + + i2c_wb[USB251XB_ADDR_VENDOR_ID_MSB] = (hub->vendor_id >> 8) & 0xFF; + i2c_wb[USB251XB_ADDR_VENDOR_ID_LSB] = hub->vendor_id & 0xFF; + i2c_wb[USB251XB_ADDR_PRODUCT_ID_MSB] = (hub->product_id >> 8) & 0xFF; + i2c_wb[USB251XB_ADDR_PRODUCT_ID_LSB] = hub->product_id & 0xFF; + i2c_wb[USB251XB_ADDR_DEVICE_ID_MSB] = (hub->device_id >> 8) & 0xFF; + i2c_wb[USB251XB_ADDR_DEVICE_ID_LSB] = hub->device_id & 0xFF; + i2c_wb[USB251XB_ADDR_CONFIG_DATA_1] = hub->conf_data1; + i2c_wb[USB251XB_ADDR_CONFIG_DATA_2] = hub->conf_data2; + i2c_wb[USB251XB_ADDR_CONFIG_DATA_3] = hub->conf_data3; + i2c_wb[USB251XB_ADDR_NON_REMOVABLE_DEVICES] = hub->non_rem_dev; + i2c_wb[USB251XB_ADDR_PORT_DISABLE_SELF] = hub->port_disable_sp; + i2c_wb[USB251XB_ADDR_PORT_DISABLE_BUS] = hub->port_disable_bp; + i2c_wb[USB251XB_ADDR_MAX_POWER_SELF] = hub->max_power_sp; + i2c_wb[USB251XB_ADDR_MAX_POWER_BUS] = hub->max_power_bp; + i2c_wb[USB251XB_ADDR_MAX_CURRENT_SELF] = hub->max_current_sp; + i2c_wb[USB251XB_ADDR_MAX_CURRENT_BUS] = hub->max_current_bp; + i2c_wb[USB251XB_ADDR_POWER_ON_TIME] = hub->power_on_time; + i2c_wb[USB251XB_ADDR_LANGUAGE_ID_HIGH] = (hub->lang_id >> 8) & 0xFF; + i2c_wb[USB251XB_ADDR_LANGUAGE_ID_LOW] = hub->lang_id & 0xFF; + i2c_wb[USB251XB_ADDR_MANUFACTURER_STRING_LEN] = hub->manufacturer_len; + i2c_wb[USB251XB_ADDR_PRODUCT_STRING_LEN] = hub->product_len; + i2c_wb[USB251XB_ADDR_SERIAL_STRING_LEN] = hub->serial_len; + memcpy(&i2c_wb[USB251XB_ADDR_MANUFACTURER_STRING], hub->manufacturer, + USB251XB_STRING_BUFSIZE); + memcpy(&i2c_wb[USB251XB_ADDR_SERIAL_STRING], hub->serial, + USB251XB_STRING_BUFSIZE); + memcpy(&i2c_wb[USB251XB_ADDR_PRODUCT_STRING], hub->product, + USB251XB_STRING_BUFSIZE); + i2c_wb[USB251XB_ADDR_BATTERY_CHARGING_ENABLE] = hub->bat_charge_en; + i2c_wb[USB251XB_ADDR_BOOST_UP] = hub->boost_up; + i2c_wb[USB251XB_ADDR_BOOST_57] = hub->boost_57; + i2c_wb[USB251XB_ADDR_BOOST_14] = hub->boost_14; + i2c_wb[USB251XB_ADDR_PORT_SWAP] = hub->port_swap; + i2c_wb[USB251XB_ADDR_PORT_MAP_12] = hub->port_map12; + i2c_wb[USB251XB_ADDR_PORT_MAP_34] = hub->port_map34; + i2c_wb[USB251XB_ADDR_PORT_MAP_56] = hub->port_map56; + i2c_wb[USB251XB_ADDR_PORT_MAP_7] = hub->port_map7; + i2c_wb[USB251XB_ADDR_STATUS_COMMAND] = USB251XB_STATUS_COMMAND_ATTACH; + + usb251xb_reset(hub); + + /* write registers */ + for (i = 0; i < (USB251XB_I2C_REG_SZ / USB251XB_I2C_WRITE_SZ); i++) { + int offset = i * USB251XB_I2C_WRITE_SZ; + char wbuf[USB251XB_I2C_WRITE_SZ + 1]; + + /* The first data byte transferred tells the hub how many data + * bytes will follow (byte count). + */ + wbuf[0] = USB251XB_I2C_WRITE_SZ; + memcpy(&wbuf[1], &i2c_wb[offset], USB251XB_I2C_WRITE_SZ); + + dev_dbg(dev, "writing %d byte block %d to 0x%02X\n", + USB251XB_I2C_WRITE_SZ, i, offset); + + err = i2c_smbus_write_i2c_block_data(hub->i2c, offset, + USB251XB_I2C_WRITE_SZ + 1, + wbuf); + if (err) + goto out_err; + } + + dev_info(dev, "Hub configuration was successful.\n"); + return 0; + +out_err: + dev_err(dev, "configuring block %d failed: %d\n", i, err); + return err; +} + +static void usb251xb_get_ports_field(struct usb251xb *hub, + const char *prop_name, u8 port_cnt, + bool ds_only, u8 *fld) +{ + struct device *dev = hub->dev; + u32 port; + + of_property_for_each_u32(dev->of_node, prop_name, port) { + if ((port >= ds_only ? 1 : 0) && (port <= port_cnt)) + *fld |= BIT(port); + else + dev_warn(dev, "port %u doesn't exist\n", port); + } +} + +static int usb251xb_get_ofdata(struct usb251xb *hub, + const struct usb251xb_data *data) +{ + struct device *dev = hub->dev; + struct device_node *np = dev->of_node; + int len; + u32 property_u32 = 0; + const char *cproperty_char; + char str[USB251XB_STRING_BUFSIZE / 2]; + + if (!np) { + dev_err(dev, "failed to get ofdata\n"); + return -ENODEV; + } + + hub->skip_config = of_property_read_bool(np, "skip-config"); + + hub->gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(hub->gpio_reset)) + return dev_err_probe(dev, PTR_ERR(hub->gpio_reset), + "unable to request GPIO reset pin\n"); + + if (of_property_read_u16(np, "vendor-id", &hub->vendor_id)) + hub->vendor_id = USB251XB_DEF_VENDOR_ID; + + if (of_property_read_u16(np, "product-id", &hub->product_id)) + hub->product_id = data->product_id; + + if (of_property_read_u16(np, "device-id", &hub->device_id)) + hub->device_id = USB251XB_DEF_DEVICE_ID; + + hub->conf_data1 = USB251XB_DEF_CONFIG_DATA_1; + if (of_property_read_bool(np, "self-powered")) { + hub->conf_data1 |= BIT(7); + + /* Configure Over-Current sens when self-powered */ + hub->conf_data1 &= ~BIT(2); + if (of_property_read_bool(np, "ganged-sensing")) + hub->conf_data1 &= ~BIT(1); + else if (of_property_read_bool(np, "individual-sensing")) + hub->conf_data1 |= BIT(1); + } else if (of_property_read_bool(np, "bus-powered")) { + hub->conf_data1 &= ~BIT(7); + + /* Disable Over-Current sense when bus-powered */ + hub->conf_data1 |= BIT(2); + } + + if (of_property_read_bool(np, "disable-hi-speed")) + hub->conf_data1 |= BIT(5); + + if (of_property_read_bool(np, "multi-tt")) + hub->conf_data1 |= BIT(4); + else if (of_property_read_bool(np, "single-tt")) + hub->conf_data1 &= ~BIT(4); + + if (of_property_read_bool(np, "disable-eop")) + hub->conf_data1 |= BIT(3); + + if (of_property_read_bool(np, "individual-port-switching")) + hub->conf_data1 |= BIT(0); + else if (of_property_read_bool(np, "ganged-port-switching")) + hub->conf_data1 &= ~BIT(0); + + hub->conf_data2 = USB251XB_DEF_CONFIG_DATA_2; + if (of_property_read_bool(np, "dynamic-power-switching")) + hub->conf_data2 |= BIT(7); + + if (!of_property_read_u32(np, "oc-delay-us", &property_u32)) { + if (property_u32 == 100) { + /* 100 us*/ + hub->conf_data2 &= ~BIT(5); + hub->conf_data2 &= ~BIT(4); + } else if (property_u32 == 4000) { + /* 4 ms */ + hub->conf_data2 &= ~BIT(5); + hub->conf_data2 |= BIT(4); + } else if (property_u32 == 16000) { + /* 16 ms */ + hub->conf_data2 |= BIT(5); + hub->conf_data2 |= BIT(4); + } else { + /* 8 ms (DEFAULT) */ + hub->conf_data2 |= BIT(5); + hub->conf_data2 &= ~BIT(4); + } + } + + if (of_property_read_bool(np, "compound-device")) + hub->conf_data2 |= BIT(3); + + hub->conf_data3 = USB251XB_DEF_CONFIG_DATA_3; + if (of_property_read_bool(np, "port-mapping-mode")) + hub->conf_data3 |= BIT(3); + + if (data->led_support && of_get_property(np, "led-usb-mode", NULL)) + hub->conf_data3 &= ~BIT(1); + + if (of_property_read_bool(np, "string-support")) + hub->conf_data3 |= BIT(0); + + hub->non_rem_dev = USB251XB_DEF_NON_REMOVABLE_DEVICES; + usb251xb_get_ports_field(hub, "non-removable-ports", data->port_cnt, + true, &hub->non_rem_dev); + + hub->port_disable_sp = USB251XB_DEF_PORT_DISABLE_SELF; + usb251xb_get_ports_field(hub, "sp-disabled-ports", data->port_cnt, + true, &hub->port_disable_sp); + + hub->port_disable_bp = USB251XB_DEF_PORT_DISABLE_BUS; + usb251xb_get_ports_field(hub, "bp-disabled-ports", data->port_cnt, + true, &hub->port_disable_bp); + + hub->max_power_sp = USB251XB_DEF_MAX_POWER_SELF; + if (!of_property_read_u32(np, "sp-max-total-current-microamp", + &property_u32)) + hub->max_power_sp = min_t(u8, property_u32 / 2000, 50); + + hub->max_power_bp = USB251XB_DEF_MAX_POWER_BUS; + if (!of_property_read_u32(np, "bp-max-total-current-microamp", + &property_u32)) + hub->max_power_bp = min_t(u8, property_u32 / 2000, 255); + + hub->max_current_sp = USB251XB_DEF_MAX_CURRENT_SELF; + if (!of_property_read_u32(np, "sp-max-removable-current-microamp", + &property_u32)) + hub->max_current_sp = min_t(u8, property_u32 / 2000, 50); + + hub->max_current_bp = USB251XB_DEF_MAX_CURRENT_BUS; + if (!of_property_read_u32(np, "bp-max-removable-current-microamp", + &property_u32)) + hub->max_current_bp = min_t(u8, property_u32 / 2000, 255); + + hub->power_on_time = USB251XB_DEF_POWER_ON_TIME; + if (!of_property_read_u32(np, "power-on-time-ms", &property_u32)) + hub->power_on_time = min_t(u8, property_u32 / 2, 255); + + if (of_property_read_u16(np, "language-id", &hub->lang_id)) + hub->lang_id = USB251XB_DEF_LANGUAGE_ID; + + if (of_property_read_u8(np, "boost-up", &hub->boost_up)) + hub->boost_up = USB251XB_DEF_BOOST_UP; + + cproperty_char = of_get_property(np, "manufacturer", NULL); + strscpy(str, cproperty_char ? : USB251XB_DEF_MANUFACTURER_STRING, + sizeof(str)); + hub->manufacturer_len = strlen(str) & 0xFF; + memset(hub->manufacturer, 0, USB251XB_STRING_BUFSIZE); + len = min_t(size_t, USB251XB_STRING_BUFSIZE / 2, strlen(str)); + len = utf8s_to_utf16s(str, len, UTF16_LITTLE_ENDIAN, + (wchar_t *)hub->manufacturer, + USB251XB_STRING_BUFSIZE); + + cproperty_char = of_get_property(np, "product", NULL); + strscpy(str, cproperty_char ? : data->product_str, sizeof(str)); + hub->product_len = strlen(str) & 0xFF; + memset(hub->product, 0, USB251XB_STRING_BUFSIZE); + len = min_t(size_t, USB251XB_STRING_BUFSIZE / 2, strlen(str)); + len = utf8s_to_utf16s(str, len, UTF16_LITTLE_ENDIAN, + (wchar_t *)hub->product, + USB251XB_STRING_BUFSIZE); + + cproperty_char = of_get_property(np, "serial", NULL); + strscpy(str, cproperty_char ? : USB251XB_DEF_SERIAL_STRING, + sizeof(str)); + hub->serial_len = strlen(str) & 0xFF; + memset(hub->serial, 0, USB251XB_STRING_BUFSIZE); + len = min_t(size_t, USB251XB_STRING_BUFSIZE / 2, strlen(str)); + len = utf8s_to_utf16s(str, len, UTF16_LITTLE_ENDIAN, + (wchar_t *)hub->serial, + USB251XB_STRING_BUFSIZE); + + /* + * The datasheet documents the register as 'Port Swap' but in real the + * register controls the USB DP/DM signal swapping for each port. + */ + hub->port_swap = USB251XB_DEF_PORT_SWAP; + usb251xb_get_ports_field(hub, "swap-dx-lanes", data->port_cnt, + false, &hub->port_swap); + + /* The following parameters are currently not exposed to devicetree, but + * may be as soon as needed. + */ + hub->bat_charge_en = USB251XB_DEF_BATTERY_CHARGING_ENABLE; + hub->boost_57 = USB251XB_DEF_BOOST_57; + hub->boost_14 = USB251XB_DEF_BOOST_14; + hub->port_map12 = USB251XB_DEF_PORT_MAP_12; + hub->port_map34 = USB251XB_DEF_PORT_MAP_34; + hub->port_map56 = USB251XB_DEF_PORT_MAP_56; + hub->port_map7 = USB251XB_DEF_PORT_MAP_7; + + return 0; +} + +static const struct of_device_id usb251xb_of_match[] = { + { + .compatible = "microchip,usb2422", + .data = &usb2422_data, + }, { + .compatible = "microchip,usb2512b", + .data = &usb2512b_data, + }, { + .compatible = "microchip,usb2512bi", + .data = &usb2512bi_data, + }, { + .compatible = "microchip,usb2513b", + .data = &usb2513b_data, + }, { + .compatible = "microchip,usb2513bi", + .data = &usb2513bi_data, + }, { + .compatible = "microchip,usb2514b", + .data = &usb2514b_data, + }, { + .compatible = "microchip,usb2514bi", + .data = &usb2514bi_data, + }, { + .compatible = "microchip,usb2517", + .data = &usb2517_data, + }, { + .compatible = "microchip,usb2517i", + .data = &usb2517i_data, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, usb251xb_of_match); + +static void usb251xb_regulator_disable_action(void *data) +{ + struct usb251xb *hub = data; + + regulator_disable(hub->vdd); +} + +static int usb251xb_probe(struct usb251xb *hub) +{ + struct device *dev = hub->dev; + struct device_node *np = dev->of_node; + const struct usb251xb_data *usb_data = of_device_get_match_data(dev); + int err; + + if (np && usb_data) { + err = usb251xb_get_ofdata(hub, usb_data); + if (err) + return dev_err_probe(dev, err, "failed to get ofdata\n"); + } + + /* + * usb251x SMBus-slave SCL lane is muxed with CFG_SEL0 pin. So if anyone + * tries to work with the bus at the moment the hub reset is released, + * it may cause an invalid config being latched by usb251x. Particularly + * one of the config modes makes the hub loading a default registers + * value without SMBus-slave interface activation. If the hub + * accidentally gets this mode, this will cause the driver SMBus- + * functions failure. Normally we could just lock the SMBus-segment the + * hub i2c-interface resides for the device-specific reset timing. But + * the GPIO controller, which is used to handle the hub reset, might be + * placed at the same i2c-bus segment. In this case an error should be + * returned since we can't safely use the GPIO controller to clear the + * reset state (it may affect the hub configuration) and we can't lock + * the i2c-bus segment (it will cause a deadlock). + */ + err = usb251x_check_gpio_chip(hub); + if (err) + return err; + + hub->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(hub->vdd)) + return PTR_ERR(hub->vdd); + + err = regulator_enable(hub->vdd); + if (err) + return err; + + err = devm_add_action_or_reset(dev, + usb251xb_regulator_disable_action, hub); + if (err) + return err; + + err = usb251xb_connect(hub); + if (err) { + dev_err(dev, "Failed to connect hub (%d)\n", err); + return err; + } + + dev_info(dev, "Hub probed successfully\n"); + + return 0; +} + +static int usb251xb_i2c_probe(struct i2c_client *i2c) +{ + struct usb251xb *hub; + + hub = devm_kzalloc(&i2c->dev, sizeof(struct usb251xb), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + i2c_set_clientdata(i2c, hub); + hub->dev = &i2c->dev; + hub->i2c = i2c; + + return usb251xb_probe(hub); +} + +static int usb251xb_suspend(struct usb251xb *hub) +{ + return regulator_disable(hub->vdd); +} + +static int usb251xb_resume(struct usb251xb *hub) +{ + int err; + + err = regulator_enable(hub->vdd); + if (err) + return err; + + return usb251xb_connect(hub); +} + +static int usb251xb_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct usb251xb *hub = i2c_get_clientdata(client); + + return usb251xb_suspend(hub); +} + +static int usb251xb_i2c_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct usb251xb *hub = i2c_get_clientdata(client); + + return usb251xb_resume(hub); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_i2c_pm_ops, usb251xb_i2c_suspend, usb251xb_i2c_resume); + +static const struct i2c_device_id usb251xb_id[] = { + { .name = "usb2422" }, + { .name = "usb2512b" }, + { .name = "usb2512bi" }, + { .name = "usb2513b" }, + { .name = "usb2513bi" }, + { .name = "usb2514b" }, + { .name = "usb2514bi" }, + { .name = "usb2517" }, + { .name = "usb2517i" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, usb251xb_id); + +static struct i2c_driver usb251xb_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = usb251xb_of_match, + .pm = pm_sleep_ptr(&usb251xb_i2c_pm_ops), + }, + .probe = usb251xb_i2c_probe, + .id_table = usb251xb_id, +}; + +static int usb251xb_plat_probe(struct platform_device *pdev) +{ + struct usb251xb *hub; + + hub = devm_kzalloc(&pdev->dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + platform_set_drvdata(pdev, hub); + hub->dev = &pdev->dev; + + return usb251xb_probe(hub); +} + +static int usb251xb_plat_suspend(struct device *dev) +{ + return usb251xb_suspend(dev_get_drvdata(dev)); +} + +static int usb251xb_plat_resume(struct device *dev) +{ + return usb251xb_resume(dev_get_drvdata(dev)); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_plat_pm_ops, usb251xb_plat_suspend, usb251xb_plat_resume); + +static struct platform_driver usb251xb_plat_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = usb251xb_of_match, + .pm = pm_sleep_ptr(&usb251xb_plat_pm_ops), + }, + .probe = usb251xb_plat_probe, +}; + +static int __init usb251xb_init(void) +{ + int err; + + err = i2c_add_driver(&usb251xb_i2c_driver); + if (err) + return err; + + err = platform_driver_register(&usb251xb_plat_driver); + if (err) { + i2c_del_driver(&usb251xb_i2c_driver); + return err; + } + + return 0; +} +module_init(usb251xb_init); + +static void __exit usb251xb_exit(void) +{ + platform_driver_unregister(&usb251xb_plat_driver); + i2c_del_driver(&usb251xb_i2c_driver); +} +module_exit(usb251xb_exit); + +MODULE_AUTHOR("Richard Leitner "); +MODULE_DESCRIPTION("USB251x/xBi USB 2.0 Hub Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usb3503.c b/drivers/usb/misc/usb3503.c new file mode 100644 index 000000000..759770a13 --- /dev/null +++ b/drivers/usb/misc/usb3503.c @@ -0,0 +1,460 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for SMSC USB3503 USB 2.0 hub controller driver + * + * Copyright (c) 2012-2013 Dongjin Kim (tobetter@gmail.com) + */ + +#include +#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_SP_ILOCK 0xe7 +#define USB3503_SPILOCK_CONNECT (1 << 1) +#define USB3503_SPILOCK_CONFIG (1 << 0) + +#define USB3503_CFGP 0xee +#define USB3503_CLKSUSP (1 << 7) + +#define USB3503_RESET 0xff + +struct usb3503 { + enum usb3503_mode mode; + struct regmap *regmap; + struct device *dev; + struct clk *clk; + u8 port_off_mask; + struct gpio_desc *bypass; + struct gpio_desc *intn; + struct gpio_desc *reset; + struct gpio_desc *connect; + bool secondary_ref_clk; +}; + +static int usb3503_connect(struct usb3503 *hub) +{ + struct device *dev = hub->dev; + int err; + + if (hub->regmap) { + /* SP_ILOCK: set connect_n, config_n for config */ + err = regmap_write(hub->regmap, USB3503_SP_ILOCK, + (USB3503_SPILOCK_CONNECT + | USB3503_SPILOCK_CONFIG)); + if (err < 0) { + dev_err(dev, "SP_ILOCK failed (%d)\n", err); + return err; + } + + /* PDS : Set the ports which are disabled in self-powered mode. */ + if (hub->port_off_mask) { + err = regmap_update_bits(hub->regmap, USB3503_PDS, + hub->port_off_mask, + hub->port_off_mask); + if (err < 0) { + dev_err(dev, "PDS failed (%d)\n", err); + return err; + } + } + + /* CFG1 : Set SELF_BUS_PWR, this enables self-powered operation. */ + err = regmap_update_bits(hub->regmap, USB3503_CFG1, + USB3503_SELF_BUS_PWR, + USB3503_SELF_BUS_PWR); + if (err < 0) { + dev_err(dev, "CFG1 failed (%d)\n", err); + return err; + } + + /* SP_LOCK: clear connect_n, config_n for hub connect */ + err = regmap_update_bits(hub->regmap, USB3503_SP_ILOCK, + (USB3503_SPILOCK_CONNECT + | USB3503_SPILOCK_CONFIG), 0); + if (err < 0) { + dev_err(dev, "SP_ILOCK failed (%d)\n", err); + return err; + } + } + + if (hub->connect) + gpiod_set_value_cansleep(hub->connect, 1); + + hub->mode = USB3503_MODE_HUB; + dev_info(dev, "switched to HUB mode\n"); + + return 0; +} + +static int usb3503_switch_mode(struct usb3503 *hub, enum usb3503_mode mode) +{ + struct device *dev = hub->dev; + int rst, bypass, conn; + + switch (mode) { + case USB3503_MODE_HUB: + conn = 1; + rst = 0; + bypass = 0; + break; + case USB3503_MODE_STANDBY: + conn = 0; + rst = 1; + bypass = 1; + dev_info(dev, "switched to STANDBY mode\n"); + break; + case USB3503_MODE_BYPASS: + conn = 0; + rst = 0; + bypass = 1; + break; + default: + dev_err(dev, "unknown mode is requested\n"); + return -EINVAL; + } + + if (!conn && hub->connect) + gpiod_set_value_cansleep(hub->connect, 0); + + if (hub->reset) + gpiod_set_value_cansleep(hub->reset, rst); + + if (hub->bypass) + gpiod_set_value_cansleep(hub->bypass, bypass); + + if (conn) { + /* Wait T_HUBINIT == 4ms for hub logic to stabilize */ + usleep_range(4000, 10000); + return usb3503_connect(hub); + } + + return 0; +} + +static const struct regmap_config usb3503_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = USB3503_RESET, +}; + +static int usb3503_probe(struct usb3503 *hub) +{ + struct device *dev = hub->dev; + struct usb3503_platform_data *pdata = dev_get_platdata(dev); + struct device_node *np = dev->of_node; + int err; + bool is_clk_enabled = false; + u32 mode = USB3503_MODE_HUB; + const u32 *property; + enum gpiod_flags flags; + int len; + + if (pdata) { + hub->port_off_mask = pdata->port_off_mask; + hub->mode = pdata->initial_mode; + } else if (np) { + u32 rate = 0; + hub->port_off_mask = 0; + + if (!of_property_read_u32(np, "refclk-frequency", &rate)) { + switch (rate) { + case 38400000: + case 26000000: + case 19200000: + case 12000000: + hub->secondary_ref_clk = 0; + break; + case 24000000: + case 27000000: + case 25000000: + case 50000000: + hub->secondary_ref_clk = 1; + break; + default: + dev_err(dev, + "unsupported reference clock rate (%d)\n", + (int) rate); + return -EINVAL; + } + } + + hub->clk = devm_clk_get_optional(dev, "refclk"); + if (IS_ERR(hub->clk)) { + dev_err(dev, "unable to request refclk (%ld)\n", + PTR_ERR(hub->clk)); + return PTR_ERR(hub->clk); + } + + if (rate != 0) { + err = clk_set_rate(hub->clk, rate); + if (err) { + dev_err(dev, + "unable to set reference clock rate to %d\n", + (int)rate); + return err; + } + } + + err = clk_prepare_enable(hub->clk); + if (err) { + dev_err(dev, "unable to enable reference clock\n"); + return err; + } + + is_clk_enabled = true; + property = of_get_property(np, "disabled-ports", &len); + if (property && (len / sizeof(u32)) > 0) { + int i; + for (i = 0; i < len / sizeof(u32); i++) { + u32 port = be32_to_cpu(property[i]); + if ((1 <= port) && (port <= 3)) + hub->port_off_mask |= (1 << port); + } + } + + of_property_read_u32(np, "initial-mode", &mode); + hub->mode = mode; + } + + if (hub->secondary_ref_clk) + flags = GPIOD_OUT_LOW; + else + flags = GPIOD_OUT_HIGH; + hub->intn = devm_gpiod_get_optional(dev, "intn", flags); + if (IS_ERR(hub->intn)) { + err = PTR_ERR(hub->intn); + goto err_clk; + } + if (hub->intn) + gpiod_set_consumer_name(hub->intn, "usb3503 intn"); + + hub->connect = devm_gpiod_get_optional(dev, "connect", GPIOD_OUT_LOW); + if (IS_ERR(hub->connect)) { + err = PTR_ERR(hub->connect); + goto err_clk; + } + if (hub->connect) + gpiod_set_consumer_name(hub->connect, "usb3503 connect"); + + hub->bypass = devm_gpiod_get_optional(dev, "bypass", GPIOD_OUT_HIGH); + if (IS_ERR(hub->bypass)) { + err = PTR_ERR(hub->bypass); + goto err_clk; + } + if (hub->bypass) + gpiod_set_consumer_name(hub->bypass, "usb3503 bypass"); + + hub->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(hub->reset)) { + err = PTR_ERR(hub->reset); + goto err_clk; + } + if (hub->reset) { + /* Datasheet defines a hardware reset to be at least 100us */ + usleep_range(100, 10000); + gpiod_set_consumer_name(hub->reset, "usb3503 reset"); + } + + if (hub->port_off_mask && !hub->regmap) + dev_err(dev, "Ports disabled with no control interface\n"); + + usb3503_switch_mode(hub, hub->mode); + + dev_info(dev, "%s: probed in %s mode\n", __func__, + (hub->mode == USB3503_MODE_HUB) ? "hub" : "standby"); + + return 0; + +err_clk: + if (is_clk_enabled) + clk_disable_unprepare(hub->clk); + return err; +} + +static int usb3503_i2c_probe(struct i2c_client *i2c) +{ + struct usb3503 *hub; + int err; + + hub = devm_kzalloc(&i2c->dev, sizeof(struct usb3503), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + i2c_set_clientdata(i2c, hub); + hub->regmap = devm_regmap_init_i2c(i2c, &usb3503_regmap_config); + if (IS_ERR(hub->regmap)) { + err = PTR_ERR(hub->regmap); + dev_err(&i2c->dev, "Failed to initialise regmap: %d\n", err); + return err; + } + hub->dev = &i2c->dev; + + return usb3503_probe(hub); +} + +static void usb3503_i2c_remove(struct i2c_client *i2c) +{ + struct usb3503 *hub; + + hub = i2c_get_clientdata(i2c); + clk_disable_unprepare(hub->clk); +} + +static int usb3503_platform_probe(struct platform_device *pdev) +{ + struct usb3503 *hub; + + hub = devm_kzalloc(&pdev->dev, sizeof(struct usb3503), GFP_KERNEL); + if (!hub) + return -ENOMEM; + hub->dev = &pdev->dev; + platform_set_drvdata(pdev, hub); + + return usb3503_probe(hub); +} + +static void usb3503_platform_remove(struct platform_device *pdev) +{ + struct usb3503 *hub; + + hub = platform_get_drvdata(pdev); + clk_disable_unprepare(hub->clk); +} + +static int __maybe_unused usb3503_suspend(struct usb3503 *hub) +{ + usb3503_switch_mode(hub, USB3503_MODE_STANDBY); + clk_disable_unprepare(hub->clk); + + return 0; +} + +static int __maybe_unused usb3503_resume(struct usb3503 *hub) +{ + clk_prepare_enable(hub->clk); + usb3503_switch_mode(hub, hub->mode); + + return 0; +} + +static int __maybe_unused usb3503_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + return usb3503_suspend(i2c_get_clientdata(client)); +} + +static int __maybe_unused usb3503_i2c_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + return usb3503_resume(i2c_get_clientdata(client)); +} + +static int __maybe_unused usb3503_platform_suspend(struct device *dev) +{ + return usb3503_suspend(dev_get_drvdata(dev)); +} + +static int __maybe_unused usb3503_platform_resume(struct device *dev) +{ + return usb3503_resume(dev_get_drvdata(dev)); +} + +static SIMPLE_DEV_PM_OPS(usb3503_i2c_pm_ops, usb3503_i2c_suspend, + usb3503_i2c_resume); + +static SIMPLE_DEV_PM_OPS(usb3503_platform_pm_ops, usb3503_platform_suspend, + usb3503_platform_resume); + +static const struct i2c_device_id usb3503_id[] = { + { .name = USB3503_I2C_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, usb3503_id); + +#ifdef CONFIG_OF +static const struct of_device_id usb3503_of_match[] = { + { .compatible = "smsc,usb3503", }, + { .compatible = "smsc,usb3503a", }, + { .compatible = "smsc,usb3803", }, + {}, +}; +MODULE_DEVICE_TABLE(of, usb3503_of_match); +#endif + +static struct i2c_driver usb3503_i2c_driver = { + .driver = { + .name = USB3503_I2C_NAME, + .pm = pm_ptr(&usb3503_i2c_pm_ops), + .of_match_table = of_match_ptr(usb3503_of_match), + }, + .probe = usb3503_i2c_probe, + .remove = usb3503_i2c_remove, + .id_table = usb3503_id, +}; + +static struct platform_driver usb3503_platform_driver = { + .driver = { + .name = USB3503_I2C_NAME, + .of_match_table = of_match_ptr(usb3503_of_match), + .pm = pm_ptr(&usb3503_platform_pm_ops), + }, + .probe = usb3503_platform_probe, + .remove = usb3503_platform_remove, +}; + +static int __init usb3503_init(void) +{ + int err; + + err = i2c_add_driver(&usb3503_i2c_driver); + if (err) { + pr_err("usb3503: Failed to register I2C driver: %d\n", err); + return err; + } + + err = platform_driver_register(&usb3503_platform_driver); + if (err) { + pr_err("usb3503: Failed to register platform driver: %d\n", + err); + i2c_del_driver(&usb3503_i2c_driver); + return err; + } + + return 0; +} +module_init(usb3503_init); + +static void __exit usb3503_exit(void) +{ + platform_driver_unregister(&usb3503_platform_driver); + i2c_del_driver(&usb3503_i2c_driver); +} +module_exit(usb3503_exit); + +MODULE_AUTHOR("Dongjin Kim "); +MODULE_DESCRIPTION("USB3503 USB HUB driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usb4604.c b/drivers/usb/misc/usb4604.c new file mode 100644 index 000000000..2ae965671 --- /dev/null +++ b/drivers/usb/misc/usb4604.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for SMSC USB4604 USB HSIC 4-port 2.0 hub controller driver + * Based on usb3503 driver + * + * Copyright (c) 2012-2013 Dongjin Kim (tobetter@gmail.com) + * Copyright (c) 2016 Linaro Ltd. + */ + +#include +#include +#include +#include +#include + +enum usb4604_mode { + USB4604_MODE_UNKNOWN, + USB4604_MODE_HUB, + USB4604_MODE_STANDBY, +}; + +struct usb4604 { + enum usb4604_mode mode; + struct device *dev; + struct gpio_desc *gpio_reset; +}; + +static void usb4604_reset(struct usb4604 *hub, int state) +{ + gpiod_set_value_cansleep(hub->gpio_reset, state); + + /* Wait for i2c logic to come up */ + if (state) + msleep(250); +} + +static int usb4604_connect(struct usb4604 *hub) +{ + struct device *dev = hub->dev; + struct i2c_client *client = to_i2c_client(dev); + int err; + u8 connect_cmd[] = { 0xaa, 0x55, 0x00 }; + + usb4604_reset(hub, 1); + + err = i2c_master_send(client, connect_cmd, ARRAY_SIZE(connect_cmd)); + if (err < 0) { + usb4604_reset(hub, 0); + return err; + } + + hub->mode = USB4604_MODE_HUB; + dev_dbg(dev, "switched to HUB mode\n"); + + return 0; +} + +static int usb4604_switch_mode(struct usb4604 *hub, enum usb4604_mode mode) +{ + struct device *dev = hub->dev; + int err = 0; + + switch (mode) { + case USB4604_MODE_HUB: + err = usb4604_connect(hub); + break; + + case USB4604_MODE_STANDBY: + usb4604_reset(hub, 0); + dev_dbg(dev, "switched to STANDBY mode\n"); + break; + + default: + dev_err(dev, "unknown mode is requested\n"); + err = -EINVAL; + break; + } + + return err; +} + +static int usb4604_probe(struct usb4604 *hub) +{ + struct device *dev = hub->dev; + struct device_node *np = dev->of_node; + struct gpio_desc *gpio; + u32 mode = USB4604_MODE_HUB; + + gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + hub->gpio_reset = gpio; + + if (of_property_read_u32(np, "initial-mode", &hub->mode)) + hub->mode = mode; + + return usb4604_switch_mode(hub, hub->mode); +} + +static int usb4604_i2c_probe(struct i2c_client *i2c) +{ + struct usb4604 *hub; + + hub = devm_kzalloc(&i2c->dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + i2c_set_clientdata(i2c, hub); + hub->dev = &i2c->dev; + + return usb4604_probe(hub); +} + +static int __maybe_unused usb4604_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct usb4604 *hub = i2c_get_clientdata(client); + + usb4604_switch_mode(hub, USB4604_MODE_STANDBY); + + return 0; +} + +static int __maybe_unused usb4604_i2c_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct usb4604 *hub = i2c_get_clientdata(client); + + usb4604_switch_mode(hub, hub->mode); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(usb4604_i2c_pm_ops, usb4604_i2c_suspend, + usb4604_i2c_resume); + +static const struct i2c_device_id usb4604_id[] = { + { .name = "usb4604" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, usb4604_id); + +#ifdef CONFIG_OF +static const struct of_device_id usb4604_of_match[] = { + { .compatible = "smsc,usb4604" }, + {} +}; +MODULE_DEVICE_TABLE(of, usb4604_of_match); +#endif + +static struct i2c_driver usb4604_i2c_driver = { + .driver = { + .name = "usb4604", + .pm = pm_ptr(&usb4604_i2c_pm_ops), + .of_match_table = of_match_ptr(usb4604_of_match), + }, + .probe = usb4604_i2c_probe, + .id_table = usb4604_id, +}; +module_i2c_driver(usb4604_i2c_driver); + +MODULE_DESCRIPTION("USB4604 USB HUB driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/usbio.c b/drivers/usb/misc/usbio.c new file mode 100644 index 000000000..fe093e776 --- /dev/null +++ b/drivers/usb/misc/usbio.c @@ -0,0 +1,756 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel USBIO Bridge driver + * + * Copyright (c) 2025 Intel Corporation. + * Copyright (c) 2025 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/************************************* + * USBIO Bridge Protocol Definitions * + *************************************/ + +/* USBIO Control Commands */ +#define USBIO_CTRLCMD_PROTVER 0 +#define USBIO_CTRLCMD_FWVER 1 +#define USBIO_CTRLCMD_HS 2 +#define USBIO_CTRLCMD_ENUMGPIO 16 +#define USBIO_CTRLCMD_ENUMI2C 17 + +/* USBIO Packet Flags */ +#define USBIO_PKTFLAG_ACK BIT(0) +#define USBIO_PKTFLAG_RSP BIT(1) +#define USBIO_PKTFLAG_CMP BIT(2) +#define USBIO_PKTFLAG_ERR BIT(3) + +#define USBIO_PKTFLAGS_REQRESP (USBIO_PKTFLAG_CMP | USBIO_PKTFLAG_ACK) + +#define USBIO_CTRLXFER_TIMEOUT 0 +#define USBIO_BULKXFER_TIMEOUT 100 + +struct usbio_protver { + u8 ver; +} __packed; + +struct usbio_fwver { + u8 major; + u8 minor; + __le16 patch; + __le16 build; +} __packed; + +/*********************************** + * USBIO Bridge Device Definitions * + ***********************************/ + +/** + * struct usbio_device - the usb device exposing IOs + * + * @dev: the device in the usb interface + * @udev: the detected usb device + * @intf: the usb interface + * @quirks: quirks + * @ctrl_mutex: protects ctrl_buf + * @ctrl_pipe: the control transfer pipe + * @ctrlbuf_len: the size of the control transfer pipe + * @ctrlbuf: the buffer used for control transfers + * @bulk_mutex: protects tx_buf, rx_buf and split bulk-transfers getting interrupted + * @tx_pipe: the bulk out pipe + * @txbuf_len: the size of the bulk out pipe + * @txbuf: the buffer used for bulk out transfers + * @rx_pipe: the bulk in pipe + * @rxbuf_len: the size of the bulk in pipe + * @rxdat_len: the data length at rx buffer + * @rxbuf: the buffer used for bulk in transfers + * @urb: the urb to read bulk pipe + * @done: completion object as request is done + * @cli_list: device's client list + * @nr_gpio_banks: Number of GPIO banks + * @gpios: GPIO bank descriptors + * @nr_gpio_banks: Number of I2C busses + * @gpios: I2C bank descriptors + */ +struct usbio_device { + struct device *dev; + struct usb_device *udev; + struct usb_interface *intf; + unsigned long quirks; + + struct mutex ctrl_mutex; + unsigned int ctrl_pipe; + u16 ctrlbuf_len; + void *ctrlbuf; + + struct mutex bulk_mutex; + unsigned int tx_pipe; + u16 txbuf_len; + void *txbuf; + + unsigned int rx_pipe; + u16 rxbuf_len; + u16 rxdat_len; + void *rxbuf; + struct urb *urb; + + struct completion done; + + struct list_head cli_list; + + unsigned int nr_gpio_banks; + struct usbio_gpio_bank_desc gpios[USBIO_MAX_GPIOBANKS]; + + unsigned int nr_i2c_buses; + struct usbio_i2c_bus_desc i2cs[USBIO_MAX_I2CBUSES]; +}; + +/** + * struct usbio_client - represents a usbio client + * + * @auxdev: auxiliary device object + * @mutex: protects @bridge + * @bridge: usbio bridge who service the client + * @link: usbio bridge clients list member + */ +struct usbio_client { + struct auxiliary_device auxdev; + struct mutex mutex; + struct usbio_device *bridge; + struct list_head link; +}; + +#define adev_to_client(adev) container_of_const(adev, struct usbio_client, auxdev) + +static int usbio_ctrl_msg(struct usbio_device *usbio, u8 type, u8 cmd, + const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len) +{ + u8 request = USB_TYPE_VENDOR | USB_RECIP_DEVICE; + struct usbio_ctrl_packet *cpkt; + unsigned int pipe; + u16 cpkt_len; + int ret; + + lockdep_assert_held(&usbio->ctrl_mutex); + + if ((obuf_len > (usbio->ctrlbuf_len - sizeof(*cpkt))) || + (ibuf_len > (usbio->ctrlbuf_len - sizeof(*cpkt)))) + return -EMSGSIZE; + + /* Prepare Control Packet Header */ + cpkt = usbio->ctrlbuf; + cpkt->header.type = type; + cpkt->header.cmd = cmd; + if (type == USBIO_PKTTYPE_CTRL || ibuf_len) + cpkt->header.flags = USBIO_PKTFLAGS_REQRESP; + else + cpkt->header.flags = USBIO_PKTFLAG_CMP; + cpkt->len = obuf_len; + + /* Copy the data */ + memcpy(cpkt->data, obuf, obuf_len); + + pipe = usb_sndctrlpipe(usbio->udev, usbio->ctrl_pipe); + cpkt_len = sizeof(*cpkt) + obuf_len; + ret = usb_control_msg(usbio->udev, pipe, 0, request | USB_DIR_OUT, 0, 0, + cpkt, cpkt_len, USBIO_CTRLXFER_TIMEOUT); + dev_dbg(usbio->dev, "control out %d hdr %*phN data %*phN\n", ret, + (int)sizeof(*cpkt), cpkt, (int)cpkt->len, cpkt->data); + + if (ret != cpkt_len) { + dev_err(usbio->dev, "USB control out failed: %d\n", ret); + return (ret < 0) ? ret : -EPROTO; + } + + if (!(cpkt->header.flags & USBIO_PKTFLAG_ACK)) + return 0; + + pipe = usb_rcvctrlpipe(usbio->udev, usbio->ctrl_pipe); + cpkt_len = sizeof(*cpkt) + ibuf_len; + ret = usb_control_msg(usbio->udev, pipe, 0, request | USB_DIR_IN, 0, 0, + cpkt, cpkt_len, USBIO_CTRLXFER_TIMEOUT); + dev_dbg(usbio->dev, "control in %d hdr %*phN data %*phN\n", ret, + (int)sizeof(*cpkt), cpkt, (int)cpkt->len, cpkt->data); + + if (ret < sizeof(*cpkt)) { + dev_err(usbio->dev, "USB control in failed: %d\n", ret); + return (ret < 0) ? ret : -EPROTO; + } + + if (cpkt->header.type != type || cpkt->header.cmd != cmd || + !(cpkt->header.flags & USBIO_PKTFLAG_RSP)) { + dev_err(usbio->dev, "Unexpected reply type: %u, cmd: %u, flags: %u\n", + cpkt->header.type, cpkt->header.cmd, cpkt->header.flags); + return -EPROTO; + } + + if (cpkt->header.flags & USBIO_PKTFLAG_ERR) + return -EREMOTEIO; + + if (ibuf_len < cpkt->len) + return -ENOSPC; + + memcpy(ibuf, cpkt->data, cpkt->len); + + return cpkt->len; +} + +int usbio_control_msg(struct auxiliary_device *adev, u8 type, u8 cmd, + const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + int ret; + + guard(mutex)(&client->mutex); + + usbio = client->bridge; + if (!usbio) + return -ENODEV; /* Disconnected */ + + ret = usb_autopm_get_interface(usbio->intf); + if (ret) + return ret; + + mutex_lock(&usbio->ctrl_mutex); + + ret = usbio_ctrl_msg(client->bridge, type, cmd, obuf, obuf_len, ibuf, ibuf_len); + + mutex_unlock(&usbio->ctrl_mutex); + usb_autopm_put_interface(usbio->intf); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(usbio_control_msg, "USBIO"); + +static void usbio_bulk_recv(struct urb *urb) +{ + struct usbio_bulk_packet *bpkt = urb->transfer_buffer; + struct usbio_device *usbio = urb->context; + + if (!urb->status) { + if (bpkt->header.flags & USBIO_PKTFLAG_RSP) { + usbio->rxdat_len = urb->actual_length; + complete(&usbio->done); + } + } else if (urb->status != -ENOENT) { + dev_err(usbio->dev, "Bulk in error %d\n", urb->status); + } + + usb_submit_urb(usbio->urb, GFP_ATOMIC); +} + +int usbio_bulk_msg(struct auxiliary_device *adev, u8 type, u8 cmd, bool last, + const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio = client->bridge; + struct usbio_bulk_packet *bpkt; + int ret, act = 0; + u16 bpkt_len; + + lockdep_assert_held(&client->mutex); + lockdep_assert_held(&usbio->bulk_mutex); + + if ((obuf_len > (usbio->txbuf_len - sizeof(*bpkt))) || + (ibuf_len > (usbio->rxbuf_len - sizeof(*bpkt)))) + return -EMSGSIZE; + + if (ibuf_len) + reinit_completion(&usbio->done); + + /* If no data to send, skip to read */ + if (!obuf_len) + goto read; + + /* Prepare Bulk Packet Header */ + bpkt = usbio->txbuf; + bpkt->header.type = type; + bpkt->header.cmd = cmd; + if (!last) + bpkt->header.flags = 0; + else if (ibuf_len) + bpkt->header.flags = USBIO_PKTFLAGS_REQRESP; + else + bpkt->header.flags = USBIO_PKTFLAG_CMP; + bpkt->len = cpu_to_le16(obuf_len); + + /* Copy the data */ + memcpy(bpkt->data, obuf, obuf_len); + + bpkt_len = sizeof(*bpkt) + obuf_len; + ret = usb_bulk_msg(usbio->udev, usbio->tx_pipe, bpkt, bpkt_len, &act, + USBIO_BULKXFER_TIMEOUT); + dev_dbg(usbio->dev, "bulk out %d hdr %*phN data %*phN\n", act, + (int)sizeof(*bpkt), bpkt, obuf_len, bpkt->data); + + if (ret || act != bpkt_len) { + dev_err(usbio->dev, "Bulk out failed: %d\n", ret); + return ret ?: -EPROTO; + } + + if (!(bpkt->header.flags & USBIO_PKTFLAG_ACK)) + return obuf_len; + +read: + ret = wait_for_completion_timeout(&usbio->done, USBIO_BULKXFER_TIMEOUT); + if (ret <= 0) { + dev_err(usbio->dev, "Bulk in wait failed: %d\n", ret); + return ret ?: -ETIMEDOUT; + } + + act = usbio->rxdat_len; + bpkt = usbio->rxbuf; + bpkt_len = le16_to_cpu(bpkt->len); + dev_dbg(usbio->dev, "bulk in %d hdr %*phN data %*phN\n", act, + (int)sizeof(*bpkt), bpkt, bpkt_len, bpkt->data); + + /* + * Unsupported bulk commands get only an usbio_packet_header with + * the error flag set as reply. Return -EPIPE for this case. + */ + if (act == sizeof(struct usbio_packet_header) && + (bpkt->header.flags & USBIO_PKTFLAG_ERR)) + return -EPIPE; + + if (act < sizeof(*bpkt)) { + dev_err(usbio->dev, "Bulk in short read: %d\n", act); + return -EPROTO; + } + + if (bpkt->header.type != type || bpkt->header.cmd != cmd || + !(bpkt->header.flags & USBIO_PKTFLAG_RSP)) { + dev_err(usbio->dev, + "Unexpected bulk in type 0x%02x cmd 0x%02x flags 0x%02x\n", + bpkt->header.type, bpkt->header.cmd, bpkt->header.flags); + return -EPROTO; + } + + if (bpkt->header.flags & USBIO_PKTFLAG_ERR) + return -EREMOTEIO; + + if (ibuf_len < bpkt_len) + return -ENOSPC; + + /* The device must not claim more payload than it actually sent. */ + if (bpkt_len > act - sizeof(*bpkt)) + return -EPROTO; + + memcpy(ibuf, bpkt->data, bpkt_len); + + return bpkt_len; +} +EXPORT_SYMBOL_NS_GPL(usbio_bulk_msg, "USBIO"); + +int usbio_acquire(struct auxiliary_device *adev) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + int ret; + + mutex_lock(&client->mutex); + + usbio = client->bridge; + if (!usbio) { + ret = -ENODEV; /* Disconnected */ + goto err_unlock; + } + + ret = usb_autopm_get_interface(usbio->intf); + if (ret) + goto err_unlock; + + mutex_lock(&usbio->bulk_mutex); + + /* Leave client locked until release to avoid abba deadlock issues */ + return 0; + +err_unlock: + mutex_unlock(&client->mutex); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(usbio_acquire, "USBIO"); + +void usbio_release(struct auxiliary_device *adev) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio = client->bridge; + + lockdep_assert_held(&client->mutex); + + mutex_unlock(&usbio->bulk_mutex); + usb_autopm_put_interface(usbio->intf); + mutex_unlock(&client->mutex); +} +EXPORT_SYMBOL_NS_GPL(usbio_release, "USBIO"); + +void usbio_get_txrxbuf_len(struct auxiliary_device *adev, u16 *txbuf_len, u16 *rxbuf_len) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + + guard(mutex)(&client->mutex); + + usbio = client->bridge; + if (!usbio) + return; /* Disconnected */ + + *txbuf_len = usbio->txbuf_len; + *rxbuf_len = usbio->rxbuf_len; +} +EXPORT_SYMBOL_NS_GPL(usbio_get_txrxbuf_len, "USBIO"); + +unsigned long usbio_get_quirks(struct auxiliary_device *adev) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + + guard(mutex)(&client->mutex); + + usbio = client->bridge; + if (!usbio) + return 0; /* Disconnected */ + + return usbio->quirks; +} +EXPORT_SYMBOL_NS_GPL(usbio_get_quirks, "USBIO"); + +static void usbio_auxdev_release(struct device *dev) +{ + struct auxiliary_device *adev = to_auxiliary_dev(dev); + struct usbio_client *client = adev_to_client(adev); + + mutex_destroy(&client->mutex); + kfree(client); +} + +static int usbio_add_client(struct usbio_device *usbio, char *name, u8 id, void *data) +{ + struct usbio_client *client; + struct auxiliary_device *adev; + int ret; + + client = kzalloc_obj(*client); + if (!client) + return -ENOMEM; + + mutex_init(&client->mutex); + client->bridge = usbio; + adev = &client->auxdev; + adev->name = name; + adev->id = id; + + adev->dev.parent = usbio->dev; + adev->dev.platform_data = data; + adev->dev.release = usbio_auxdev_release; + + ret = auxiliary_device_init(adev); + if (ret) { + usbio_auxdev_release(&adev->dev); + return ret; + } + + ret = auxiliary_device_add(adev); + if (ret) { + auxiliary_device_uninit(adev); + return ret; + } + + list_add_tail(&client->link, &usbio->cli_list); + + return 0; +} + +static int usbio_enum_gpios(struct usbio_device *usbio) +{ + struct usbio_gpio_bank_desc *gpio = usbio->gpios; + + dev_dbg(usbio->dev, "GPIO Banks: %d\n", usbio->nr_gpio_banks); + + for (unsigned int i = 0; i < usbio->nr_gpio_banks; i++) + dev_dbg(usbio->dev, "\tBank%d[%d] map: %#08x\n", + gpio[i].id, gpio[i].pins, gpio[i].bmap); + + usbio_add_client(usbio, USBIO_GPIO_CLIENT, 0, gpio); + + return 0; +} + +static int usbio_enum_i2cs(struct usbio_device *usbio) +{ + struct usbio_i2c_bus_desc *i2c = usbio->i2cs; + + dev_dbg(usbio->dev, "I2C Busses: %d\n", usbio->nr_i2c_buses); + + for (unsigned int i = 0; i < usbio->nr_i2c_buses; i++) { + dev_dbg(usbio->dev, "\tBus%d caps: %#02x\n", i2c[i].id, i2c[i].caps); + usbio_add_client(usbio, USBIO_I2C_CLIENT, i, &i2c[i]); + } + + return 0; +} + +static int usbio_suspend(struct usb_interface *intf, pm_message_t msg) +{ + struct usbio_device *usbio = usb_get_intfdata(intf); + + usb_kill_urb(usbio->urb); + + return 0; +} + +static int usbio_resume(struct usb_interface *intf) +{ + struct usbio_device *usbio = usb_get_intfdata(intf); + + return usb_submit_urb(usbio->urb, GFP_KERNEL); +} + +static void usbio_disconnect(struct usb_interface *intf) +{ + struct usbio_device *usbio = usb_get_intfdata(intf); + struct usbio_client *client, *next; + + /* Wakeup any clients waiting for a reply */ + usbio->rxdat_len = 0; + complete(&usbio->done); + + /* Let clients know the bridge is gone */ + list_for_each_entry(client, &usbio->cli_list, link) { + mutex_lock(&client->mutex); + client->bridge = NULL; + mutex_unlock(&client->mutex); + } + + /* From here on clients will no longer touch struct usbio_device */ + usb_kill_urb(usbio->urb); + usb_free_urb(usbio->urb); + + list_for_each_entry_safe_reverse(client, next, &usbio->cli_list, link) { + auxiliary_device_delete(&client->auxdev); + auxiliary_device_uninit(&client->auxdev); + } +} + +static int usbio_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_endpoint_descriptor *ep_in, *ep_out; + struct device *dev = &intf->dev; + struct usbio_protver protver; + struct usbio_device *usbio; + struct usbio_fwver fwver; + int ret; + + usbio = devm_kzalloc(dev, sizeof(*usbio), GFP_KERNEL); + if (!usbio) + return -ENOMEM; + + ret = devm_mutex_init(dev, &usbio->ctrl_mutex); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &usbio->bulk_mutex); + if (ret) + return ret; + + usbio->dev = dev; + usbio->udev = udev; + usbio->intf = intf; + usbio->quirks = id ? id->driver_info : 0; + init_completion(&usbio->done); + INIT_LIST_HEAD(&usbio->cli_list); + usb_set_intfdata(intf, usbio); + + usbio->ctrl_pipe = usb_endpoint_num(&udev->ep0.desc); + usbio->ctrlbuf_len = usb_maxpacket(udev, usbio->ctrl_pipe); + usbio->ctrlbuf = devm_kzalloc(dev, usbio->ctrlbuf_len, GFP_KERNEL); + if (!usbio->ctrlbuf) + return -ENOMEM; + + /* Find the first bulk-in and bulk-out endpoints */ + ret = usb_find_common_endpoints(intf->cur_altsetting, &ep_in, &ep_out, + NULL, NULL); + if (ret) { + dev_err(dev, "Cannot find bulk endpoints: %d\n", ret); + return ret; + } + + usbio->tx_pipe = usb_sndbulkpipe(udev, usb_endpoint_num(ep_out)); + + if (usbio->quirks & USBIO_QUIRK_BULK_MAXP_63) + usbio->txbuf_len = 63; + else + usbio->txbuf_len = usb_endpoint_maxp(ep_out); + + usbio->txbuf = devm_kzalloc(dev, usbio->txbuf_len, GFP_KERNEL); + if (!usbio->txbuf) + return -ENOMEM; + + usbio->rx_pipe = usb_rcvbulkpipe(udev, usb_endpoint_num(ep_in)); + + if (usbio->quirks & USBIO_QUIRK_BULK_MAXP_63) + usbio->rxbuf_len = 63; + else + usbio->rxbuf_len = usb_endpoint_maxp(ep_in); + + usbio->rxbuf = devm_kzalloc(dev, usbio->rxbuf_len, GFP_KERNEL); + if (!usbio->rxbuf) + return -ENOMEM; + + usbio->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!usbio->urb) + return -ENOMEM; + + usb_fill_bulk_urb(usbio->urb, udev, usbio->rx_pipe, usbio->rxbuf, + usbio->rxbuf_len, usbio_bulk_recv, usbio); + ret = usb_submit_urb(usbio->urb, GFP_KERNEL); + if (ret) { + dev_err_probe(dev, ret, "Submitting usb urb\n"); + goto err_free_urb; + } + + mutex_lock(&usbio->ctrl_mutex); + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_HS, NULL, 0, NULL, 0); + if (ret < 0) + goto err_unlock; + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_PROTVER, NULL, 0, + &protver, sizeof(protver)); + if (ret < 0) + goto err_unlock; + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_FWVER, NULL, 0, + &fwver, sizeof(fwver)); + if (ret < 0) + goto err_unlock; + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_ENUMGPIO, NULL, 0, + usbio->gpios, sizeof(usbio->gpios)); + if (ret < 0 || ret % sizeof(struct usbio_gpio_bank_desc)) { + ret = (ret < 0) ? ret : -EPROTO; + goto err_unlock; + } + usbio->nr_gpio_banks = ret / sizeof(struct usbio_gpio_bank_desc); + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_ENUMI2C, NULL, 0, + usbio->i2cs, sizeof(usbio->i2cs)); + if (ret < 0 || ret % sizeof(struct usbio_i2c_bus_desc)) { + ret = (ret < 0) ? ret : -EPROTO; + goto err_unlock; + } + usbio->nr_i2c_buses = ret / sizeof(struct usbio_i2c_bus_desc); + + mutex_unlock(&usbio->ctrl_mutex); + + dev_dbg(dev, "ProtVer(BCD): %02x FwVer: %d.%d.%d.%d\n", + protver.ver, fwver.major, fwver.minor, + le16_to_cpu(fwver.patch), le16_to_cpu(fwver.build)); + + usbio_enum_gpios(usbio); + usbio_enum_i2cs(usbio); + + return 0; + +err_unlock: + mutex_unlock(&usbio->ctrl_mutex); + usb_kill_urb(usbio->urb); +err_free_urb: + usb_free_urb(usbio->urb); + + return ret; +} + +static const struct usb_device_id usbio_table[] = { + { USB_DEVICE(0x2ac1, 0x20c1), /* Lattice NX40 */ + .driver_info = USBIO_QUIRK_I2C_MAX_RW_LEN_52 }, + { USB_DEVICE(0x2ac1, 0x20c9), /* Lattice NX33 */ + .driver_info = USBIO_QUIRK_I2C_NO_INIT_ACK | USBIO_QUIRK_I2C_MAX_RW_LEN_52 | + USBIO_QUIRK_I2C_ALLOW_400KHZ }, + { USB_DEVICE(0x2ac1, 0x20cb) }, /* Lattice NX33U */ + { USB_DEVICE(0x06cb, 0x0701), /* Synaptics Sabre */ + .driver_info = USBIO_QUIRK_BULK_MAXP_63 | USBIO_QUIRK_I2C_USE_CHUNK_LEN }, + { } +}; +MODULE_DEVICE_TABLE(usb, usbio_table); + +static struct usb_driver usbio_driver = { + .name = "usbio-bridge", + .probe = usbio_probe, + .disconnect = usbio_disconnect, + .suspend = usbio_suspend, + .resume = usbio_resume, + .id_table = usbio_table, + .supports_autosuspend = 1, +}; +module_usb_driver(usbio_driver); + +struct usbio_match_ids_walk_data { + struct acpi_device *adev; + const struct acpi_device_id *hids; + unsigned int id; +}; + +static int usbio_match_device_ids(struct acpi_device *adev, void *data) +{ + struct usbio_match_ids_walk_data *wd = data; + unsigned int id = 0; + char *uid; + + if (acpi_match_device_ids(adev, wd->hids)) + return 0; + + uid = acpi_device_uid(adev); + if (uid) { + for (int i = 0; i < strlen(uid); i++) { + if (!kstrtouint(&uid[i], 10, &id)) + break; + } + } + + if (!uid || wd->id == id) { + wd->adev = adev; + return 1; + } + + return 0; +} + +void usbio_acpi_bind(struct auxiliary_device *adev, const struct acpi_device_id *hids) +{ + struct device *dev = &adev->dev; + struct acpi_device *parent; + struct usbio_match_ids_walk_data wd = { + .adev = NULL, + .hids = hids, + .id = adev->id, + }; + + parent = ACPI_COMPANION(dev->parent); + if (!parent) + return; + + acpi_dev_for_each_child(parent, usbio_match_device_ids, &wd); + if (wd.adev) + ACPI_COMPANION_SET(dev, wd.adev); +} +EXPORT_SYMBOL_NS_GPL(usbio_acpi_bind, "USBIO"); + +MODULE_DESCRIPTION("Intel USBIO Bridge driver"); +MODULE_AUTHOR("Israel Cepeda "); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usblcd.c b/drivers/usb/misc/usblcd.c new file mode 100644 index 000000000..f794695de --- /dev/null +++ b/drivers/usb/misc/usblcd.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0 +/***************************************************************************** + * 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 const struct usb_device_id id_table[] = { + { .idVendor = 0x10D2, .match_flags = USB_DEVICE_ID_MATCH_VENDOR, }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +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 */ + struct rw_semaphore io_rwsem; + unsigned long disconnected:1; +}; +#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; + + subminor = iminor(inode); + + interface = usb_find_interface(&lcd_driver, subminor); + if (!interface) { + pr_err("USBLCD: %s - error, can't find device for minor %d\n", + __func__, subminor); + return -ENODEV; + } + + dev = usb_get_intfdata(interface); + + /* increment our usage count for the device */ + kref_get(&dev->kref); + + /* grab a power reference */ + r = usb_autopm_get_interface(interface); + if (r < 0) { + kref_put(&dev->kref, lcd_delete); + return r; + } + + /* save our object in the file's private structure */ + file->private_data = dev; + + 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; + + down_read(&dev->io_rwsem); + + if (dev->disconnected) { + retval = -ENODEV; + goto out_up_io; + } + + /* 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; + } + +out_up_io: + up_read(&dev->io_rwsem); + + 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: + 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)); + 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; + } + + 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; + + down_read(&dev->io_rwsem); + + if (dev->disconnected) { + retval = -ENODEV; + goto err_up_io; + } + + /* 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_up_io; + } + + 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); + + up_read(&dev->io_rwsem); +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_up_io: + up_read(&dev->io_rwsem); + 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_endpoint_descriptor *bulk_in, *bulk_out; + int i; + int retval; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc_obj(*dev); + if (!dev) + return -ENOMEM; + + kref_init(&dev->kref); + sema_init(&dev->limit_sem, USB_LCD_CONCURRENT_WRITES); + init_rwsem(&dev->io_rwsem); + 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 */ + retval = usb_find_common_endpoints(interface->cur_altsetting, + &bulk_in, &bulk_out, NULL, NULL); + if (retval) { + dev_err(&interface->dev, + "Could not find both bulk-in and bulk-out endpoints\n"); + goto error; + } + + dev->bulk_in_size = usb_endpoint_maxp(bulk_in); + dev->bulk_in_endpointAddr = bulk_in->bEndpointAddress; + dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL); + if (!dev->bulk_in_buffer) { + retval = -ENOMEM; + goto error; + } + + dev->bulk_out_endpointAddr = bulk_out->bEndpointAddress; + + /* 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"); + 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: + 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 = usb_get_intfdata(interface); + int minor = interface->minor; + + /* give back our minor */ + usb_deregister_dev(interface, &lcd_class); + + down_write(&dev->io_rwsem); + dev->disconnected = 1; + up_write(&dev->io_rwsem); + + usb_kill_anchored_urbs(&dev->submitted); + + /* 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/usbsevseg.c b/drivers/usb/misc/usbsevseg.c new file mode 100644 index 000000000..89d25fcef --- /dev/null +++ b/drivers/usb/misc/usbsevseg.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB 7 Segment Driver + * + * Copyright (C) 2008 Harrison Metzger + * Based on usbled.c by Greg Kroah-Hartman (greg@kroah.com) + */ + +#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 const char *display_textmodes[] = {"raw", "hex", "ascii"}; + +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. + */ +static 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_send(mydev->udev, 0, 0x12, 0x48, + (80 * 0x100) + 10, /* (power mode) */ + (0x00 * 0x100) + (mydev->powered ? 1 : 0), + NULL, 0, 2000, GFP_KERNEL); + 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_send(mydev->udev, 0, 0x12, 0x48, + (82 * 0x100) + 10, /* (set mode) */ + (mydev->mode_msb * 0x100) + mydev->mode_lsb, + NULL, 0, 2000, GFP_NOIO); + + 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[MAXLEN] = {0}; + u8 decimals = 0; + + if(mydev->shadow_power != 1) + 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_send(mydev->udev, 0, 0x12, 0x48, + (85 * 0x100) + 10, /* (write text) */ + (0 * 0x100) + mydev->textmode, /* mode */ + &buffer, mydev->textlength, 2000, mf); + + if (rc < 0) + dev_dbg(&mydev->udev->dev, "write retval = %d\n", rc); + + /* 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_send(mydev->udev, 0, 0x12, 0x48, + (86 * 0x100) + 10, /* (set decimal) */ + (0 * 0x100) + decimals, /* decimals */ + NULL, 0, 2000, mf); + + if (rc < 0) + dev_dbg(&mydev->udev->dev, "decimal retval = %d\n", rc); +} + +#define MYDEV_ATTR_SIMPLE_UNSIGNED(name, update_fcn) \ +static ssize_t name##_show(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 name##_store(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_RW(name); + +static ssize_t text_show(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 sysfs_emit(buf, "%s\n", mydev->text); +} + +static ssize_t text_store(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_RW(text); + +static ssize_t decimals_show(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 decimals_store(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_RW(decimals); + +static ssize_t textmode_show(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; i < ARRAY_SIZE(display_textmodes); 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 textmode_store(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; + + i = sysfs_match_string(display_textmodes, buf); + if (i < 0) + return i; + + mydev->textmode = i; + update_display_visual(mydev, GFP_KERNEL); + return count; +} + +static DEVICE_ATTR_RW(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 *sevseg_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 +}; +ATTRIBUTE_GROUPS(sevseg); + +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; + int rc = -ENOMEM; + + mydev = kzalloc_obj(struct usb_sevsegdev); + if (!mydev) + goto error_mem; + + mydev->udev = 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 */ + + dev_info(&interface->dev, "USB 7 Segment device now attached\n"); + return 0; + +error_mem: + return rc; +} + +static void sevseg_disconnect(struct usb_interface *interface) +{ + struct usb_sevsegdev *mydev; + + mydev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + 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, + .dev_groups = sevseg_groups, + .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 000000000..8759df49b --- /dev/null +++ b/drivers/usb/misc/usbtest.c @@ -0,0 +1,3080 @@ +// SPDX-License-Identifier: GPL-2.0 +#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"); +static void complicated_callback(struct urb *urb); + +/*-------------------------------------------------------------------------*/ + +/* FIXME make these public somewhere; usbdevfs.h? */ + +/* Parameter for usbtest driver. */ +struct usbtest_param_32 { + /* inputs */ + __u32 test_num; /* 0..(TEST_CASES-1) */ + __u32 iterations; + __u32 length; + __u32 vary; + __u32 sglen; + + /* outputs */ + __s32 duration_sec; + __s32 duration_usec; +}; + +/* + * Compat parameter to the usbtest driver. + * This supports older user space binaries compiled with 64 bit compiler. + */ +struct usbtest_param_64 { + /* inputs */ + __u32 test_num; /* 0..(TEST_CASES-1) */ + __u32 iterations; + __u32 length; + __u32 vary; + __u32 sglen; + + /* outputs */ + __s64 duration_sec; + __s64 duration_usec; +}; + +/* IOCTL interface to the driver. */ +#define USBTEST_REQUEST_32 _IOWR('U', 100, struct usbtest_param_32) +/* COMPAT IOCTL interface to the driver. */ +#define USBTEST_REQUEST_64 _IOWR('U', 100, struct usbtest_param_64) + +/*-------------------------------------------------------------------------*/ + +#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 */ + unsigned intr:1; /* try interrupt 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; + int in_int_pipe; + int out_int_pipe; + struct usb_endpoint_descriptor *iso_in, *iso_out; + struct usb_endpoint_descriptor *int_in, *int_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 +#define MAX_SGLEN 128 + +/*-------------------------------------------------------------------------*/ + +static inline void endpoint_update(int edi, + struct usb_host_endpoint **in, + struct usb_host_endpoint **out, + struct usb_host_endpoint *e) +{ + if (edi) { + if (!*in) + *in = e; + } else { + if (!*out) + *out = e; + } +} + +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_host_endpoint *int_in, *int_out; + struct usb_device *udev; + + for (tmp = 0; tmp < intf->num_altsetting; tmp++) { + unsigned ep; + + in = out = NULL; + iso_in = iso_out = NULL; + int_in = int_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; + int edi; + + e = alt->endpoint + ep; + edi = usb_endpoint_dir_in(&e->desc); + + switch (usb_endpoint_type(&e->desc)) { + case USB_ENDPOINT_XFER_BULK: + endpoint_update(edi, &in, &out, e); + continue; + case USB_ENDPOINT_XFER_INT: + if (dev->info->intr) + endpoint_update(edi, &int_in, &int_out, e); + continue; + case USB_ENDPOINT_XFER_ISOC: + if (dev->info->iso) + endpoint_update(edi, &iso_in, &iso_out, e); + fallthrough; + default: + continue; + } + } + if ((in && out) || iso_in || iso_out || int_in || int_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); + if (out) + 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); + } + + if (int_in) { + dev->int_in = &int_in->desc; + dev->in_int_pipe = usb_rcvintpipe(udev, + int_in->desc.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK); + } + + if (int_out) { + dev->int_out = &int_out->desc; + dev->out_int_pipe = usb_sndintpipe(udev, + int_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, + u8 bInterval, + usb_complete_t complete_fn) +{ + struct urb *urb; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return urb; + + if (bInterval) + usb_fill_int_urb(urb, udev, pipe, NULL, bytes, complete_fn, + NULL, bInterval); + else + usb_fill_bulk_urb(urb, udev, pipe, NULL, bytes, complete_fn, + 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 ((bytes + offset) == 0) + return urb; + + 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, + u8 bInterval) +{ + return usbtest_alloc_urb(udev, pipe, bytes, URB_NO_TRANSFER_DMA_MAP, 0, + bInterval, simple_callback); +} + +static struct urb *complicated_alloc_urb( + struct usb_device *udev, + int pipe, + unsigned long bytes, + u8 bInterval) +{ + return usbtest_alloc_urb(udev, pipe, bytes, URB_NO_TRANSFER_DMA_MAP, 0, + bInterval, complicated_callback); +} + +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 unsigned get_maxpacket(struct usb_device *udev, int pipe) +{ + struct usb_host_endpoint *ep; + + ep = usb_pipe_endpoint(udev, pipe); + return le16_to_cpup(&ep->desc.wMaxPacketSize); +} + +static int ss_isoc_get_packet_num(struct usb_device *udev, int pipe) +{ + struct usb_host_endpoint *ep = usb_pipe_endpoint(udev, pipe); + + return USB_SS_MULT(ep->ss_ep_comp.bmAttributes) + * (1 + ep->ss_ep_comp.bMaxBurst); +} + +static void simple_fill_buf(struct urb *urb) +{ + unsigned i; + u8 *buf = urb->transfer_buffer; + unsigned len = urb->transfer_buffer_length; + unsigned maxpacket; + + switch (pattern) { + default: + fallthrough; + case 0: + memset(buf, 0, len); + break; + case 1: /* mod63 */ + maxpacket = get_maxpacket(urb->dev, urb->pipe); + for (i = 0; i < len; i++) + *buf++ = (u8) ((i % maxpacket) % 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; + unsigned maxpacket = get_maxpacket(urb->dev, urb->pipe); + + 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 % maxpacket) % 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 usbtest_dev *dev, int pipe) +{ + struct scatterlist *sg; + unsigned int n_size = 0; + unsigned i; + unsigned size = max; + unsigned maxpacket = + get_maxpacket(interface_to_usbdev(dev->intf), pipe); + + if (max == 0) + return NULL; + + sg = kmalloc_objs(*sg, nents); + 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 + n_size) % maxpacket) % 63); + n_size += size; + break; + } + + if (vary) { + size += vary; + size %= max; + if (size == 0) + size = (vary < max) ? vary : max; + } + } + + return sg; +} + +struct sg_timeout { + struct timer_list timer; + struct usb_sg_request *req; +}; + +static void sg_timeout(struct timer_list *t) +{ + struct sg_timeout *timeout = timer_container_of(timeout, t, timer); + + usb_sg_cancel(timeout->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 sg_timeout timeout = { + .req = req, + }; + + timer_setup_on_stack(&timeout.timer, sg_timeout, 0); + + 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(&timeout.timer, jiffies + + msecs_to_jiffies(SIMPLE_IO_TIMEOUT)); + usb_sg_wait(req); + if (!timer_delete_sync(&timeout.timer)) + retval = -ETIMEDOUT; + else + retval = req->status; + timer_destroy_on_stack(&timeout.timer); + + /* 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 < (int)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; +} + +static int is_good_ext(struct usbtest_dev *tdev, u8 *buf) +{ + struct usb_ext_cap_descriptor *ext; + u32 attr; + + ext = (struct usb_ext_cap_descriptor *) buf; + + if (ext->bLength != USB_DT_USB_EXT_CAP_SIZE) { + ERROR(tdev, "bogus usb 2.0 extension descriptor length\n"); + return 0; + } + + attr = le32_to_cpu(ext->bmAttributes); + /* bits[1:15] is used and others are reserved */ + if (attr & ~0xfffe) { /* reserved == 0 */ + ERROR(tdev, "reserved bits set\n"); + return 0; + } + + return 1; +} + +static int is_good_ss_cap(struct usbtest_dev *tdev, u8 *buf) +{ + struct usb_ss_cap_descriptor *ss; + + ss = (struct usb_ss_cap_descriptor *) buf; + + if (ss->bLength != USB_DT_USB_SS_CAP_SIZE) { + ERROR(tdev, "bogus superspeed device capability descriptor length\n"); + return 0; + } + + /* + * only bit[1] of bmAttributes is used for LTM and others are + * reserved + */ + if (ss->bmAttributes & ~0x02) { /* reserved == 0 */ + ERROR(tdev, "reserved bits set in bmAttributes\n"); + return 0; + } + + /* bits[0:3] of wSpeedSupported is used and others are reserved */ + if (le16_to_cpu(ss->wSpeedSupported) & ~0x0f) { /* reserved == 0 */ + ERROR(tdev, "reserved bits set in wSpeedSupported\n"); + return 0; + } + + return 1; +} + +static int is_good_con_id(struct usbtest_dev *tdev, u8 *buf) +{ + struct usb_ss_container_id_descriptor *con_id; + + con_id = (struct usb_ss_container_id_descriptor *) buf; + + if (con_id->bLength != USB_DT_USB_SS_CONTN_ID_SIZE) { + ERROR(tdev, "bogus container id descriptor length\n"); + return 0; + } + + if (con_id->bReserved) { /* reserved == 0 */ + ERROR(tdev, "reserved bits set\n"); + return 0; + } + + return 1; +} + +/* 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] a bos device descriptor [9.6.2] in USB + * 3.0 spec + */ + if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0210) { + struct usb_bos_descriptor *bos = NULL; + struct usb_dev_cap_header *header = NULL; + unsigned total, num, length; + u8 *buf; + + retval = usb_get_descriptor(udev, USB_DT_BOS, 0, dev->buf, + sizeof(*udev->bos->desc)); + if (retval != sizeof(*udev->bos->desc)) { + dev_err(&iface->dev, "bos descriptor --> %d\n", retval); + return (retval < 0) ? retval : -EDOM; + } + + bos = (struct usb_bos_descriptor *)dev->buf; + total = le16_to_cpu(bos->wTotalLength); + num = bos->bNumDeviceCaps; + + if (total > TBUF_SIZE) + total = TBUF_SIZE; + + /* + * get generic device-level capability descriptors [9.6.2] + * in USB 3.0 spec + */ + retval = usb_get_descriptor(udev, USB_DT_BOS, 0, dev->buf, + total); + if (retval != total) { + dev_err(&iface->dev, "bos descriptor set --> %d\n", + retval); + return (retval < 0) ? retval : -EDOM; + } + + length = sizeof(*udev->bos->desc); + buf = dev->buf; + for (i = 0; i < num; i++) { + buf += length; + if (buf + sizeof(struct usb_dev_cap_header) > + dev->buf + total) + break; + + header = (struct usb_dev_cap_header *)buf; + length = header->bLength; + + if (header->bDescriptorType != + USB_DT_DEVICE_CAPABILITY) { + dev_warn(&udev->dev, "not device capability descriptor, skip\n"); + continue; + } + + switch (header->bDevCapabilityType) { + case USB_CAP_TYPE_EXT: + if (buf + USB_DT_USB_EXT_CAP_SIZE > + dev->buf + total || + !is_good_ext(dev, buf)) { + dev_err(&iface->dev, "bogus usb 2.0 extension descriptor\n"); + return -EDOM; + } + break; + case USB_SS_CAP_TYPE: + if (buf + USB_DT_USB_SS_CAP_SIZE > + dev->buf + total || + !is_good_ss_cap(dev, buf)) { + dev_err(&iface->dev, "bogus superspeed device capability descriptor\n"); + return -EDOM; + } + break; + case CONTAINER_ID_TYPE: + if (buf + USB_DT_USB_SS_CONTN_ID_SIZE > + dev->buf + total || + !is_good_con_id(dev, buf)) { + dev_err(&iface->dev, "bogus container id descriptor\n"); + return -EDOM; + } + break; + default: + break; + } + } + } + + /* 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; + } + /* 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_std_status(udev, USB_RECIP_DEVICE, 0, dev->buf); + if (retval) { + dev_err(&iface->dev, "get dev status --> %d\n", retval); + return retval; + } + + /* 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_std_status(udev, USB_RECIP_INTERFACE, + iface->altsetting[0].desc.bInterfaceNumber, dev->buf); + if (retval) { + dev_err(&iface->dev, "get interface status --> %d\n", retval); + return retval; + } + /* 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_32 *param; + int last; +}; + +#define NUM_SUBCASES 16 /* 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; + unsigned long flags; + + reqp = (struct usb_ctrlrequest *)urb->setup_packet; + subcase = container_of(reqp, struct subcase, setup); + + spin_lock_irqsave(&ctx->lock, flags); + 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_irqrestore(&ctx->lock, flags); +} + +static int +test_ctrl_queue(struct usbtest_dev *dev, struct usbtest_param_32 *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 = kzalloc_objs(struct urb *, param->sglen); + 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; + case 15: + req.wValue = cpu_to_le16(USB_DT_BOS << 8); + if (udev->bos) + len = le16_to_cpu(udev->bos->desc->wTotalLength); + else + len = sizeof(struct usb_bos_descriptor); + if (le16_to_cpu(udev->descriptor.bcdUSB) < 0x0201) + expected = -EPIPE; + 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, 0); + if (!u) + goto cleanup; + + reqp = kmalloc_obj(*reqp); + 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, 0); + 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); + + if (retval == 0 && usb_pipein(urb->pipe)) + retval = simple_check_buf(dev, 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 = kzalloc_objs(struct urb *, num); + 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_std_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_std_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; + } + 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) { + int ret; + + /* clear halt anyways, else further tests will fail */ + ret = usb_clear_halt(urb->dev, urb->pipe); + if (ret) + ERROR(tdev, "ep %02x couldn't clear halt, %d\n", + ep, ret); + + 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 test_toggle_sync(struct usbtest_dev *tdev, int ep, struct urb *urb) +{ + int retval; + + /* clear initial data toggle to DATA0 */ + 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; + } + + /* transfer 3 data packets, should be DATA0, DATA1, DATA0 */ + retval = simple_io(tdev, urb, 1, 0, 0, __func__); + if (retval != 0) + return -EINVAL; + + /* clear halt resets device side data toggle, host should react to it */ + 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; + } + + /* host should use DATA0 again after clear halt */ + retval = simple_io(tdev, urb, 1, 0, 0, __func__); + + return retval; +} + +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, 0); + else + urb = simple_alloc_urb(udev, 0, 512, 0); + 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; +} + +static int toggle_sync_simple(struct usbtest_dev *dev) +{ + int ep; + int retval = 0; + struct urb *urb; + struct usb_device *udev = testdev_to_usbdev(dev); + unsigned maxp = get_maxpacket(udev, dev->out_pipe); + + /* + * Create a URB that causes a transfer of uneven amount of data packets + * This way the clear toggle has an impact on the data toggle sequence. + * Use 2 maxpacket length packets and one zero packet. + */ + urb = simple_alloc_urb(udev, 0, 2 * maxp, 0); + if (urb == NULL) + return -ENOMEM; + + urb->transfer_flags |= URB_ZERO_PACKET; + + ep = usb_pipeendpoint(dev->out_pipe); + urb->pipe = dev->out_pipe; + retval = test_toggle_sync(dev, ep, urb); + + 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] = (u8)(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/BULK tests ... mimics common usage + * - buffer length is split into N packets (mostly maxpacket sized) + * - multi-buffers according to sglen + */ + +struct transfer_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; + bool is_iso; +}; + +static void complicated_callback(struct urb *urb) +{ + struct transfer_context *ctx = urb->context; + unsigned long flags; + + spin_lock_irqsave(&ctx->lock, flags); + 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 += (ctx->is_iso ? urb->number_of_packets : 1); + 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, + "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, + "during the test, %lu errors out of %lu\n", + ctx->errors, ctx->packet_count); + complete(&ctx->done); + } +done: + spin_unlock_irqrestore(&ctx->lock, flags); +} + +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 = usb_endpoint_maxp(desc); + if (udev->speed >= USB_SPEED_SUPER) + maxp *= ss_isoc_get_packet_num(udev, pipe); + else + maxp *= usb_endpoint_maxp_mult(desc); + + 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_t(unsigned int, + bytes, maxp); + bytes -= urb->iso_frame_desc[i].length; + + urb->iso_frame_desc[i].offset = maxp * i; + } + + urb->complete = complicated_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_queue(struct usbtest_dev *dev, struct usbtest_param_32 *param, + int pipe, struct usb_endpoint_descriptor *desc, unsigned offset) +{ + struct transfer_context context; + struct usb_device *udev; + unsigned i; + unsigned long packets = 0; + int status = 0; + struct urb **urbs; + + if (!param->sglen || param->iterations > UINT_MAX / param->sglen) + return -EINVAL; + + if (param->sglen > MAX_SGLEN) + return -EINVAL; + + urbs = kzalloc_objs(*urbs, param->sglen); + if (!urbs) + return -ENOMEM; + + memset(&context, 0, sizeof(context)); + context.count = param->iterations * param->sglen; + context.dev = dev; + context.is_iso = !!desc; + init_completion(&context.done); + spin_lock_init(&context.lock); + + udev = testdev_to_usbdev(dev); + + for (i = 0; i < param->sglen; i++) { + if (context.is_iso) + urbs[i] = iso_alloc_urb(udev, pipe, desc, + param->length, offset); + else + urbs[i] = complicated_alloc_urb(udev, pipe, + param->length, 0); + + if (!urbs[i]) { + status = -ENOMEM; + goto fail; + } + packets += urbs[i]->number_of_packets; + urbs[i]->context = &context; + } + packets *= param->iterations; + + if (context.is_iso) { + int transaction_num; + + if (udev->speed >= USB_SPEED_SUPER) + transaction_num = ss_isoc_get_packet_num(udev, pipe); + else + transaction_num = usb_endpoint_maxp_mult(desc); + + dev_info(&dev->intf->dev, + "iso period %d %sframes, wMaxPacket %d, transactions: %d\n", + 1 << (desc->bInterval - 1), + (udev->speed >= USB_SPEED_HIGH) ? "micro" : "", + usb_endpoint_maxp(desc), + transaction_num); + + 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.is_iso ? context.packet_count / 10 : 0)) + status = -EIO; + + kfree(urbs); + return status; + +fail: + for (i = 0; i < param->sglen; i++) { + if (urbs[i]) + simple_free_urb(urbs[i]); + } + + kfree(urbs); + 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, 0, simple_callback); + + if (!urb) + return -ENOMEM; + + retval = simple_io(tdev, urb, iterations, 0, 0, label); + simple_free_urb(urb); + return retval; +} + +/* Run tests. */ +static int +usbtest_do_ioctl(struct usb_interface *intf, struct usbtest_param_32 *param) +{ + struct usbtest_dev *dev = usb_get_intfdata(intf); + struct usb_device *udev = testdev_to_usbdev(dev); + struct urb *urb; + struct scatterlist *sg; + struct usb_sg_request req; + unsigned i; + int retval = -EOPNOTSUPP; + + if (param->iterations <= 0) + return -EINVAL; + if (param->sglen > MAX_SGLEN) + return -EINVAL; + /* + * 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. + */ + 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, 0); + 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, 0); + 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, 0); + 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, 0); + 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, dev, dev->out_pipe); + 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, dev, dev->in_pipe); + 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, dev, dev->out_pipe); + 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, dev, dev->in_pipe); + 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_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_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_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_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; + + /* Simple non-queued interrupt I/O tests */ + case 25: + if (dev->out_int_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 25: write %d bytes %u times\n", + param->length, param->iterations); + urb = simple_alloc_urb(udev, dev->out_int_pipe, param->length, + dev->int_out->bInterval); + if (!urb) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: interrupt sink (maybe accepts short writes) */ + retval = simple_io(dev, urb, param->iterations, 0, 0, "test25"); + simple_free_urb(urb); + break; + case 26: + if (dev->in_int_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 26: read %d bytes %u times\n", + param->length, param->iterations); + urb = simple_alloc_urb(udev, dev->in_int_pipe, param->length, + dev->int_in->bInterval); + if (!urb) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: interrupt source (maybe generates short writes) */ + retval = simple_io(dev, urb, param->iterations, 0, 0, "test26"); + simple_free_urb(urb); + break; + case 27: + /* We do performance test, so ignore data compare */ + if (dev->out_pipe == 0 || param->sglen == 0 || pattern != 0) + break; + dev_info(&intf->dev, + "TEST 27: bulk write %dMbytes\n", (param->iterations * + param->sglen * param->length) / (1024 * 1024)); + retval = test_queue(dev, param, + dev->out_pipe, NULL, 0); + break; + case 28: + if (dev->in_pipe == 0 || param->sglen == 0 || pattern != 0) + break; + dev_info(&intf->dev, + "TEST 28: bulk read %dMbytes\n", (param->iterations * + param->sglen * param->length) / (1024 * 1024)); + retval = test_queue(dev, param, + dev->in_pipe, NULL, 0); + break; + /* Test data Toggle/seq_nr clear between bulk out transfers */ + case 29: + if (dev->out_pipe == 0) + break; + retval = 0; + dev_info(&intf->dev, "TEST 29: Clear toggle between bulk writes %d times\n", + param->iterations); + for (i = param->iterations; retval == 0 && i > 0; --i) + retval = toggle_sync_simple(dev); + + if (retval) + ERROR(dev, "toggle sync failed, iterations left %d\n", + i); + break; + } + 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 + * 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, hub_wq 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 usbtest_param_64 *param_64 = buf; + struct usbtest_param_32 temp; + struct usbtest_param_32 *param_32 = buf; + struct timespec64 start; + struct timespec64 end; + struct timespec64 duration; + int retval = -EOPNOTSUPP; + + /* FIXME USBDEVFS_CONNECTINFO doesn't say how fast the device is. */ + + pattern = mod_pattern; + + 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) { + if (intf->altsetting->desc.bInterfaceNumber) { + retval = -ENODEV; + goto free_mutex; + } + retval = set_altsetting(dev, dev->info->alt); + if (retval) { + dev_err(&intf->dev, + "set altsetting to %d failed, %d\n", + dev->info->alt, retval); + goto free_mutex; + } + } + + switch (code) { + case USBTEST_REQUEST_64: + temp.test_num = param_64->test_num; + temp.iterations = param_64->iterations; + temp.length = param_64->length; + temp.sglen = param_64->sglen; + temp.vary = param_64->vary; + param_32 = &temp; + break; + + case USBTEST_REQUEST_32: + break; + + default: + retval = -EOPNOTSUPP; + goto free_mutex; + } + + ktime_get_ts64(&start); + + retval = usbtest_do_ioctl(intf, param_32); + if (retval < 0) + goto free_mutex; + + ktime_get_ts64(&end); + + duration = timespec64_sub(end, start); + + temp.duration_sec = duration.tv_sec; + temp.duration_usec = duration.tv_nsec/NSEC_PER_USEC; + + switch (code) { + case USBTEST_REQUEST_32: + param_32->duration_sec = temp.duration_sec; + param_32->duration_usec = temp.duration_usec; + break; + + case USBTEST_REQUEST_64: + param_64->duration_sec = temp.duration_sec; + param_64->duration_usec = temp.duration_usec; + break; + } + +free_mutex: + 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; + char *intrtest, *intwtest; + + 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_obj(*dev); + 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 = ""; + intrtest = intwtest = ""; + 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"; + if (dev->in_int_pipe) + intrtest = " int-in"; + if (dev->out_int_pipe) + intwtest = " int-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%s%s} tests%s\n", + usb_speed_string(udev->speed), + info->ctrl_out ? " in/out" : "", + rtest, wtest, + irtest, iwtest, + intrtest, intwtest, + 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->buf); + 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, + .intr = 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, + .no_dynamic_id = 1, +}; + +/*-------------------------------------------------------------------------*/ + +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 000000000..bd0ad7c32 --- /dev/null +++ b/drivers/usb/misc/uss720.c @@ -0,0 +1,839 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*****************************************************************************/ + +/* + * uss720.c -- USS720 USB Parport Cable. + * + * Copyright (C) 1999, 2005, 2010 + * Thomas Sailer (t.sailer@alumni.ethz.ch) + * + * 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 +#include + +#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); + priv->usbdev = NULL; + 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); + kfree(rq->dr); + 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 %7ph\n", + priv->reg); +#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 = kzalloc_obj(struct uss720_async_request, mem_flags); + if (!rq) + 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); + return NULL; + } + rq->dr = kmalloc_obj(*rq->dr, mem_flags); + if (!rq->dr) { + kref_put(&rq->ref_count, destroy_async); + 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); + kref_put(&rq->ref_count, destroy_async); + 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_ATOMIC)) + 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_ATOMIC)) + 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 = 0; + 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 = 0; + 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 = 0; + 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 = 0; + 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_endpoint_descriptor *epd; + struct parport_uss720_private *priv; + struct parport *pp; + unsigned char reg; + int ret = -ENODEV; + + 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) + goto bail_out_early; + + ret = usb_set_interface(usbdev, intf->altsetting->desc.bInterfaceNumber, 2); + dev_dbg(&intf->dev, "set interface result %d\n", ret); + + interface = intf->cur_altsetting; + + if (interface->desc.bNumEndpoints < 2) + goto bail_out_early; + + /* + * Allocate parport interface + */ + ret = -ENOMEM; + priv = kzalloc_obj(struct parport_uss720_private); + if (!priv) + goto bail_out_early; + + priv->pp = NULL; + priv->usbdev = usbdev; + kref_init(&priv->ref_count); + spin_lock_init(&priv->asynclock); + INIT_LIST_HEAD(&priv->asynclist); + pp = parport_register_port(0, PARPORT_IRQ_NONE, PARPORT_DMA_NONE, &parport_uss720_ops); + if (!pp) { + 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_COMPAT; + if (interface->desc.bNumEndpoints >= 3) + pp->modes |= PARPORT_MODE_ECP; + pp->dev = &usbdev->dev; + + /* 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); + + /* The Belkin F5U002 Rev 2 P80453-B USB parallel port adapter shares the + * device ID 050d:0002 with some other device that works with this + * driver, but it itself does not. Detect and handle the bad cable + * here. */ + ret = get_1284_register(pp, 0, ®, GFP_KERNEL); + dev_dbg(&intf->dev, "reg: %7ph\n", priv->reg); + if (ret < 0) { + priv->pp = NULL; + parport_del_port(pp); + goto probe_abort; + } + + ret = usb_find_last_int_in_endpoint(interface, &epd); + if (!ret) { + dev_dbg(&intf->dev, "epaddr %d interval %d\n", + epd->bEndpointAddress, epd->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; + +bail_out_early: + usb_put_dev(usbdev); + return ret; +} + +static void uss720_disconnect(struct usb_interface *intf) +{ + struct parport *pp = usb_get_intfdata(intf); + struct parport_uss720_private *priv; + + dev_dbg(&intf->dev, "disconnect\n"); + usb_set_intfdata(intf, NULL); + if (pp) { + priv = pp->private_data; + 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) }, /* Infowave 901-0030 */ + { USB_DEVICE(0x04b8, 0x0002) }, /* Epson CAEUL0002 ISD-103 */ + { USB_DEVICE(0x04b8, 0x0003) }, /* Epson ISD-101 */ + { USB_DEVICE(0x050d, 0x0002) }, + { USB_DEVICE(0x050d, 0x1202) }, /* Belkin F5U120-PC */ + { USB_DEVICE(0x0557, 0x2001) }, + { USB_DEVICE(0x05ab, 0x0002) }, /* Belkin F5U002 ISD-101 */ + { USB_DEVICE(0x05ab, 0x1001) }, /* Belkin F5U002 P80453-A */ + { USB_DEVICE(0x06c6, 0x0100) }, /* Infowave ISD-103 */ + { USB_DEVICE(0x0729, 0x1284) }, + { USB_DEVICE(0x1293, 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_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 000000000..7a482cdee --- /dev/null +++ b/drivers/usb/misc/yurex.c @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Meywa-Denki & KAYAC YUREX + * + * Copyright (C) 2010 Tomoki Sekiyama (tomoki.sekiyama@gmail.com) + */ + +#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; + unsigned long disconnected:1; + 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__); + + if (dev->cntl_urb) { + usb_kill_urb(dev->cntl_urb); + kfree(dev->cntl_req); + 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); + usb_free_coherent(dev->udev, YUREX_BUF_SIZE, + dev->int_buffer, dev->urb->transfer_dma); + usb_free_urb(dev->urb); + } + usb_put_intf(dev->interface); + usb_put_dev(dev->udev); + 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; + /* The device is terminated or messed up, give up */ + 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); + return; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -EILSEQ: + case -EPROTO: + case -ETIME: + return; + default: + dev_err(&dev->interface->dev, + "%s - unknown status received: %d\n", __func__, status); + return; + } + + /* 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; + } + + 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; + DEFINE_WAIT(wait); + int res; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc_obj(*dev); + if (!dev) + 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 = usb_get_intf(interface); + + /* set up the endpoint information */ + iface_desc = interface->cur_altsetting; + res = usb_find_int_in_endpoint(iface_desc, &endpoint); + if (res) { + dev_err(&interface->dev, "Could not find endpoints\n"); + retval = res; + goto error; + } + + dev->int_in_endpointAddr = endpoint->bEndpointAddress; + + /* allocate control URB */ + dev->cntl_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->cntl_urb) + goto error; + + /* allocate buffer for control req */ + dev->cntl_req = kmalloc(YUREX_BUF_SIZE, GFP_KERNEL); + if (!dev->cntl_req) + 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) + 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; + dev->bbu = -1; + 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_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 */ + usb_poison_urb(dev->urb); + usb_poison_urb(dev->cntl_urb); + mutex_lock(&dev->io_mutex); + dev->disconnected = 1; + 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 = 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 = 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 __user *buffer, size_t count, + loff_t *ppos) +{ + struct usb_yurex *dev; + int len; + char in_buffer[20]; + unsigned long flags; + + dev = file->private_data; + + mutex_lock(&dev->io_mutex); + if (dev->disconnected) { /* already disconnected */ + mutex_unlock(&dev->io_mutex); + return -ENODEV; + } + + spin_lock_irqsave(&dev->lock, flags); + len = snprintf(in_buffer, 20, "%lld\n", dev->bbu); + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->io_mutex); + + if (WARN_ON_ONCE(len >= sizeof(in_buffer))) + return -EIO; + + return simple_read_from_buffer(buffer, count, ppos, in_buffer, len); +} + +static ssize_t yurex_write(struct file *file, const char __user *user_buffer, + size_t count, loff_t *ppos) +{ + struct usb_yurex *dev; + int i, set = 0, retval = 0; + char buffer[16 + 1]; + char *data = buffer; + unsigned long long c, c2 = 0; + signed long timeout = 0; + DEFINE_WAIT(wait); + + count = min(sizeof(buffer) - 1, count); + dev = file->private_data; + + /* verify that we actually have some data to write */ + if (count == 0) + goto error; + + retval = mutex_lock_interruptible(&dev->io_mutex); + if (retval < 0) + return -EINTR; + + if (dev->disconnected) { /* already 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; + } + buffer[count] = 0; + 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++; + fallthrough; + 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_ATOMIC); + if (retval >= 0) + timeout = schedule_timeout(YUREX_WRITE_TIMEOUT); + finish_wait(&dev->waitq, &wait); + + /* make sure URB is idle after timeout or (spurious) CMD_ACK */ + usb_kill_urb(dev->cntl_urb); + + 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) { + spin_lock_irq(&dev->lock); + dev->bbu = c2; + spin_unlock_irq(&dev->lock); + } + 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_DESCRIPTION("USB YUREX driver support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/mon/Kconfig b/drivers/usb/mon/Kconfig new file mode 100644 index 000000000..ffc7cd422 --- /dev/null +++ b/drivers/usb/mon/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# 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 000000000..09f43e896 --- /dev/null +++ b/drivers/usb/mon/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# 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 000000000..687f6a898 --- /dev/null +++ b/drivers/usb/mon/mon_bin.c @@ -0,0 +1,1420 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + +#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. + * + * Originally, for a 480 Mbit/s bus this required a buffer of about 1 MB. For + * modern 20 Gbps buses, this value increases to over 50 MB. The maximum + * buffer size is set to 64 MiB to accommodate this. + * + * 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 typical, low-throughput use cases. + * + * XXX What about multi-megabyte URBs which take minutes to transfer? + */ +#define BUFF_MAX CHUNK_ALIGN(64*1024*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; /* ktime_get_real_ts64 */ + s32 ts_usec; /* ktime_get_real_ts64 */ + 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 const struct class mon_bin_class = { + .name = "usbmon", +}; + +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 timespec64 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; + + ktime_get_real_ts64(&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_nsec / NSEC_PER_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 timespec64 ts; + unsigned long flags; + unsigned int offset; + struct mon_bin_hdr *ep; + + ktime_get_real_ts64(&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_nsec / NSEC_PER_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); + mbus = mon_bus_lookup(iminor(inode)); + if (mbus == 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_obj(struct mon_reader_bin); + 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_t(size_t, nbytes, 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: + mutex_lock(&rp->fetch_lock); + ret = rp->b_size; + mutex_unlock(&rp->fetch_lock); + 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); + vec = kzalloc_objs(struct mon_pgmap, size / CHUNK_SIZE); + if (vec == 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); + if (rp->mmap_active) { + mon_free_buff(vec, size/CHUNK_SIZE); + kfree(vec); + ret = -EBUSY; + } else { + 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 __poll_t +mon_bin_poll(struct file *file, struct poll_table_struct *wait) +{ + struct mon_reader_bin *rp = file->private_data; + __poll_t 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 |= EPOLLIN | EPOLLRDNORM; /* 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; + unsigned long flags; + + spin_lock_irqsave(&rp->b_lock, flags); + rp->mmap_active++; + spin_unlock_irqrestore(&rp->b_lock, flags); +} + +static void mon_bin_vma_close(struct vm_area_struct *vma) +{ + unsigned long flags; + + struct mon_reader_bin *rp = vma->vm_private_data; + spin_lock_irqsave(&rp->b_lock, flags); + rp->mmap_active--; + spin_unlock_irqrestore(&rp->b_lock, flags); +} + +/* + * Map ring pages to user space. + */ +static vm_fault_t mon_bin_vma_fault(struct vm_fault *vmf) +{ + struct mon_reader_bin *rp = vmf->vma->vm_private_data; + unsigned long offset, chunk_idx; + struct page *pageptr; + unsigned long flags; + + spin_lock_irqsave(&rp->b_lock, flags); + offset = vmf->pgoff << PAGE_SHIFT; + if (offset >= rp->b_size) { + spin_unlock_irqrestore(&rp->b_lock, flags); + return VM_FAULT_SIGBUS; + } + chunk_idx = offset / CHUNK_SIZE; + pageptr = rp->b_vec[chunk_idx].pg; + get_page(pageptr); + vmf->page = pageptr; + spin_unlock_irqrestore(&rp->b_lock, flags); + 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; + + if (vma->vm_flags & VM_WRITE) + return -EPERM; + + vm_flags_mod(vma, VM_DONTEXPAND | VM_DONTDUMP, VM_MAYWRITE); + 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, + .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; + + rc = class_register(&mon_bin_class); + if (rc) + 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_unregister(&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_unregister(&mon_bin_class); +} diff --git a/drivers/usb/mon/mon_main.c b/drivers/usb/mon/mon_main.c new file mode 100644 index 000000000..e4fff194f --- /dev/null +++ b/drivers/usb/mon/mon_main.c @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 mon_reader *r; + + spin_lock_irqsave(&mbus->lock, flags); + mbus->cnt_events++; + list_for_each_entry(r, &mbus->r_list, 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; + + mbus = ubus->mon_bus; + if (mbus != 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 mon_reader *r; + + spin_lock_irqsave(&mbus->lock, flags); + mbus->cnt_events++; + list_for_each_entry(r, &mbus->r_list, 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; + + mbus = ubus->mon_bus; + if (mbus != 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 mon_reader *r; + + spin_lock_irqsave(&mbus->lock, flags); + mbus->cnt_events++; + list_for_each_entry(r, &mbus->r_list, 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; + + mbus = ubus->mon_bus; + if (mbus != 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; + + if (mbus == &mon_bus0) { + list_for_each_entry(mbus, &mon_buses, 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 const 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; + + mbus = kzalloc_obj(struct mon_bus); + if (mbus == 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 mon_bus *mbus; + + if (num == 0) { + return &mon_bus0; + } + list_for_each_entry(mbus, &mon_buses, bus_link) { + if (mbus->u_bus->busnum == num) { + return mbus; + } + } + return NULL; +} + +static int __init mon_init(void) +{ + struct usb_bus *ubus; + int rc, id; + + 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_idr_lock); + idr_for_each_entry(&usb_bus_idr, ubus, id) + mon_bus_init(ubus); + usb_register_notify(&mon_nb); + mutex_unlock(&usb_bus_idr_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); + kref_get(&mbus->ref); /* 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_DESCRIPTION("USB Monitor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/mon/mon_stat.c b/drivers/usb/mon/mon_stat.c new file mode 100644 index 000000000..3ee2e483d --- /dev/null +++ b/drivers/usb/mon/mon_stat.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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; + + sp = kmalloc_obj(struct snap); + if (sp == NULL) + return -ENOMEM; + + mbus = inode->i_private; + + sp->slen = scnprintf(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, + .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 000000000..4c8e2518e --- /dev/null +++ b/drivers/usb/mon/mon_text.c @@ -0,0 +1,753 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 +#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; + size_t printf_offset; + size_t printf_togo; + 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 timespec64 now; + unsigned int stamp; + + ktime_get_ts64(&now); + stamp = now.tv_sec & 0xFFF; /* 2^32 = 4294967296. Limit to 4096s. */ + stamp = stamp * USEC_PER_SEC + now.tv_nsec / NSEC_PER_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_obj(struct mon_reader_text); + 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; + + scnprintf(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; +} + +static ssize_t mon_text_copy_to_user(struct mon_reader_text *rp, + char __user * const buf, const size_t nbytes) +{ + const size_t togo = min(nbytes, rp->printf_togo); + + if (copy_to_user(buf, &rp->printf_buf[rp->printf_offset], togo)) + return -EFAULT; + rp->printf_togo -= togo; + rp->printf_offset += togo; + return togo; +} + +/* ppos is not advanced since the llseek operation is not permitted. */ +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; + ssize_t ret; + + mutex_lock(&rp->printf_lock); + + if (rp->printf_togo == 0) { + + ep = mon_text_read_wait(rp, file); + if (IS_ERR(ep)) { + mutex_unlock(&rp->printf_lock); + return PTR_ERR(ep); + } + 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 += scnprintf(ptr.pbuf + ptr.cnt, ptr.limit - ptr.cnt, + " %d", ep->length); + mon_text_read_data(rp, &ptr, ep); + + rp->printf_togo = ptr.cnt; + rp->printf_offset = 0; + + kmem_cache_free(rp->e_slab, ep); + } + + ret = mon_text_copy_to_user(rp, buf, nbytes); + mutex_unlock(&rp->printf_lock); + return ret; +} + +/* ppos is not advanced since the llseek operation is not permitted. */ +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; + ssize_t ret; + + mutex_lock(&rp->printf_lock); + + if (rp->printf_togo == 0) { + + ep = mon_text_read_wait(rp, file); + if (IS_ERR(ep)) { + mutex_unlock(&rp->printf_lock); + return PTR_ERR(ep); + } + 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 += scnprintf(ptr.pbuf + ptr.cnt, ptr.limit - ptr.cnt, + " %d", ep->length); + mon_text_read_data(rp, &ptr, ep); + + rp->printf_togo = ptr.cnt; + rp->printf_offset = 0; + + kmem_cache_free(rp->e_slab, ep); + } + + ret = mon_text_copy_to_user(rp, buf, nbytes); + mutex_unlock(&rp->printf_lock); + return ret; +} + +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 += scnprintf(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 += scnprintf(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 += scnprintf(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 += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, + " %c __ __ ____ ____ ____", ep->setup_flag); + } else { /* No setup for this kind of URB */ + p->cnt += scnprintf(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 += scnprintf(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 += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, + " %d:%d:%d", ep->status, ep->interval, ep->start_frame); + } else { + p->cnt += scnprintf(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 += scnprintf(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 += scnprintf(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 += scnprintf(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 += scnprintf(p->pbuf + p->cnt, + p->limit - p->cnt, + " "); + } + p->cnt += scnprintf(p->pbuf + p->cnt, + p->limit - p->cnt, + "%02x", ep->data[i]); + } + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, + "\n"); + } else { + p->cnt += scnprintf(p->pbuf + p->cnt, p->limit - p->cnt, + " %c\n", ep->data_flag); + } + } else { + p->cnt += scnprintf(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, + .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, + .read = mon_text_read_u, + .release = mon_text_release, +}; + +int mon_text_add(struct mon_bus *mbus, const struct usb_bus *ubus) +{ + enum { NAMESZ = 12 }; + char name[NAMESZ]; + int busnum = ubus? ubus->busnum: 0; + + if (mon_dir == NULL) + return 0; + + if (ubus != NULL) { + scnprintf(name, NAMESZ, "%dt", busnum); + mbus->dent_t = debugfs_create_file(name, 0600, mon_dir, mbus, + &mon_fops_text_t); + } + + scnprintf(name, NAMESZ, "%du", busnum); + mbus->dent_u = debugfs_create_file(name, 0600, mon_dir, mbus, + &mon_fops_text_u); + + scnprintf(name, NAMESZ, "%ds", busnum); + mbus->dent_s = debugfs_create_file(name, 0600, mon_dir, mbus, + &mon_fops_stat); + + return 1; +} + +void mon_text_del(struct mon_bus *mbus) +{ + debugfs_remove(mbus->dent_u); + 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) +{ + mon_dir = debugfs_create_dir("usbmon", usb_debug_root); + 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 000000000..aa64efaba --- /dev/null +++ b/drivers/usb/mon/usb_mon.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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/mtu3/Kconfig b/drivers/usb/mtu3/Kconfig new file mode 100644 index 000000000..d281da1cd --- /dev/null +++ b/drivers/usb/mtu3/Kconfig @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# For MTK USB3.0 IP + +config USB_MTU3 + tristate "MediaTek USB3 Dual Role controller" + depends on USB || USB_GADGET + depends on ARCH_MEDIATEK || COMPILE_TEST + depends on EXTCON || !EXTCON + select USB_XHCI_MTK if USB_SUPPORT && USB_XHCI_HCD + help + Say Y or M here if your system runs on MediaTek SoCs with + Dual Role SuperSpeed USB controller. You can select usb + mode as peripheral role or host role, or both. + + If you don't know what this is, please say N. + + Choose M here to compile this driver as a module, and it + will be called mtu3.ko. + + +if USB_MTU3 +choice + prompt "MTU3 Mode Selection" + default USB_MTU3_DUAL_ROLE if (USB && USB_GADGET) + default USB_MTU3_HOST if (USB && !USB_GADGET) + default USB_MTU3_GADGET if (!USB && USB_GADGET) + +config USB_MTU3_HOST + bool "Host only mode" + depends on USB=y || USB=USB_MTU3 + help + Select this when you want to use MTU3 in host mode only, + thereby the gadget feature will be regressed. + +config USB_MTU3_GADGET + bool "Gadget only mode" + depends on USB_GADGET=y || USB_GADGET=USB_MTU3 + help + Select this when you want to use MTU3 in gadget mode only, + thereby the host feature will be regressed. + +config USB_MTU3_DUAL_ROLE + bool "Dual Role mode" + depends on ((USB=y || USB=USB_MTU3) && (USB_GADGET=y || USB_GADGET=USB_MTU3)) + depends on (EXTCON=y || EXTCON=USB_MTU3) + select USB_ROLE_SWITCH + help + This is the default mode of working of MTU3 controller where + both host and gadget features are enabled. + +endchoice + +config USB_MTU3_DEBUG + bool "Enable Debugging Messages" + help + Say Y here to enable debugging messages in the MTU3 Driver. + +endif diff --git a/drivers/usb/mtu3/Makefile b/drivers/usb/mtu3/Makefile new file mode 100644 index 000000000..3bf8cbcc1 --- /dev/null +++ b/drivers/usb/mtu3/Makefile @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0 + +ccflags-$(CONFIG_USB_MTU3_DEBUG) += -DDEBUG + +# define_trace.h needs to know how to find our header +CFLAGS_mtu3_trace.o := -I$(src) + +obj-$(CONFIG_USB_MTU3) += mtu3.o + +mtu3-y := mtu3_plat.o + +ifneq ($(CONFIG_TRACING),) + mtu3-y += mtu3_trace.o +endif + +ifneq ($(filter y,$(CONFIG_USB_MTU3_HOST) $(CONFIG_USB_MTU3_DUAL_ROLE)),) + mtu3-y += mtu3_host.o +endif + +ifneq ($(filter y,$(CONFIG_USB_MTU3_GADGET) $(CONFIG_USB_MTU3_DUAL_ROLE)),) + mtu3-y += mtu3_core.o mtu3_gadget_ep0.o mtu3_gadget.o mtu3_qmu.o +endif + +ifneq ($(CONFIG_USB_MTU3_DUAL_ROLE),) + mtu3-y += mtu3_dr.o +endif + +ifneq ($(CONFIG_DEBUG_FS),) + mtu3-y += mtu3_debugfs.o +endif diff --git a/drivers/usb/mtu3/mtu3.h b/drivers/usb/mtu3/mtu3.h new file mode 100644 index 000000000..ba5a63669 --- /dev/null +++ b/drivers/usb/mtu3/mtu3.h @@ -0,0 +1,449 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mtu3.h - MediaTek USB3 DRD header + * + * Copyright (C) 2016 MediaTek Inc. + * + * Author: Chunfeng Yun + */ + +#ifndef __MTU3_H__ +#define __MTU3_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct mtu3; +struct mtu3_ep; +struct mtu3_request; + +#include "mtu3_hw_regs.h" +#include "mtu3_qmu.h" + +#define MU3D_EP_TXCR0(epnum) (U3D_TX1CSR0 + (((epnum) - 1) * 0x10)) +#define MU3D_EP_TXCR1(epnum) (U3D_TX1CSR1 + (((epnum) - 1) * 0x10)) +#define MU3D_EP_TXCR2(epnum) (U3D_TX1CSR2 + (((epnum) - 1) * 0x10)) + +#define MU3D_EP_RXCR0(epnum) (U3D_RX1CSR0 + (((epnum) - 1) * 0x10)) +#define MU3D_EP_RXCR1(epnum) (U3D_RX1CSR1 + (((epnum) - 1) * 0x10)) +#define MU3D_EP_RXCR2(epnum) (U3D_RX1CSR2 + (((epnum) - 1) * 0x10)) + +#define USB_QMU_TQHIAR(epnum) (U3D_TXQHIAR1 + (((epnum) - 1) * 0x4)) +#define USB_QMU_RQHIAR(epnum) (U3D_RXQHIAR1 + (((epnum) - 1) * 0x4)) + +#define USB_QMU_RQCSR(epnum) (U3D_RXQCSR1 + (((epnum) - 1) * 0x10)) +#define USB_QMU_RQSAR(epnum) (U3D_RXQSAR1 + (((epnum) - 1) * 0x10)) +#define USB_QMU_RQCPR(epnum) (U3D_RXQCPR1 + (((epnum) - 1) * 0x10)) + +#define USB_QMU_TQCSR(epnum) (U3D_TXQCSR1 + (((epnum) - 1) * 0x10)) +#define USB_QMU_TQSAR(epnum) (U3D_TXQSAR1 + (((epnum) - 1) * 0x10)) +#define USB_QMU_TQCPR(epnum) (U3D_TXQCPR1 + (((epnum) - 1) * 0x10)) + +#define SSUSB_U3_CTRL(p) (U3D_SSUSB_U3_CTRL_0P + ((p) * 0x08)) +#define SSUSB_U2_CTRL(p) (U3D_SSUSB_U2_CTRL_0P + ((p) * 0x08)) + +#define MTU3_DRIVER_NAME "mtu3" +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +#define MTU3_EP_ENABLED BIT(0) +#define MTU3_EP_STALL BIT(1) +#define MTU3_EP_WEDGE BIT(2) +#define MTU3_EP_BUSY BIT(3) + +#define MTU3_U3_IP_SLOT_DEFAULT 2 +#define MTU3_U2_IP_SLOT_DEFAULT 1 + +/* + * IP TRUNK version + * from 0x1003 version, USB3 Gen2 is supported, two changes affect driver: + * 1. MAXPKT and MULTI bits layout of TXCSR1 and RXCSR1 are adjusted, + * but not backward compatible + * 2. QMU extend buffer length supported + */ +#define MTU3_TRUNK_VERS_1003 0x1003 + +/* + * Normally the device works on HS or SS, to simplify fifo management, + * divide fifo into some 512B parts, use bitmap to manage it; And + * 128 bits size of bitmap is large enough, that means it can manage + * up to 64KB fifo size. + * NOTE: MTU3_EP_FIFO_UNIT should be power of two + */ +#define MTU3_EP_FIFO_UNIT (1 << 9) +#define MTU3_FIFO_BIT_SIZE 128 +#define MTU3_U2_IP_EP0_FIFO_SIZE 64 + +/* + * Maximum size of ep0 response buffer for ch9 requests, + * the SET_SEL request uses 6 so far, and GET_STATUS is 2 + */ +#define EP0_RESPONSE_BUF 6 + +#define BULK_CLKS_CNT 6 + +/* device operated link and speed got from DEVICE_CONF register */ +enum mtu3_speed { + MTU3_SPEED_INACTIVE = 0, + MTU3_SPEED_FULL = 1, + MTU3_SPEED_HIGH = 3, + MTU3_SPEED_SUPER = 4, + MTU3_SPEED_SUPER_PLUS = 5, +}; + +/** + * enum mtu3_g_ep0_state - endpoint 0 states + * @MU3D_EP0_STATE_SETUP: waits for SETUP or received a SETUP + * without data stage. + * @MU3D_EP0_STATE_TX: IN data stage + * @MU3D_EP0_STATE_RX: OUT data stage + * @MU3D_EP0_STATE_TX_END: the last IN data is transferred, and + * waits for its completion interrupt + * @MU3D_EP0_STATE_STALL: ep0 is in stall status, will be auto-cleared + * after receives a SETUP. + */ +enum mtu3_g_ep0_state { + MU3D_EP0_STATE_SETUP = 1, + MU3D_EP0_STATE_TX, + MU3D_EP0_STATE_RX, + MU3D_EP0_STATE_TX_END, + MU3D_EP0_STATE_STALL, +}; + +/** + * enum mtu3_dr_force_mode - indicates host/OTG operating mode + * @MTU3_DR_FORCE_NONE: automatically switch host and peripheral mode + * by IDPIN signal. + * @MTU3_DR_FORCE_HOST: force to enter host mode and override OTG + * IDPIN signal. + * @MTU3_DR_FORCE_DEVICE: force to enter peripheral mode. + */ +enum mtu3_dr_force_mode { + MTU3_DR_FORCE_NONE = 0, + MTU3_DR_FORCE_HOST, + MTU3_DR_FORCE_DEVICE, +}; + +/** + * struct mtu3_fifo_info - HW FIFO description and management data + * @base: the base address of fifo + * @limit: the bitmap size in bits + * @bitmap: fifo bitmap in unit of @MTU3_EP_FIFO_UNIT + */ +struct mtu3_fifo_info { + u32 base; + u32 limit; + DECLARE_BITMAP(bitmap, MTU3_FIFO_BIT_SIZE); +}; + +/** + * struct qmu_gpd - General Purpose Descriptor (GPD): + * The format of TX GPD is a little different from RX one. + * And the size of GPD is 16 bytes. + * + * @dw0_info: + * bit0: Hardware Own (HWO) + * bit1: Buffer Descriptor Present (BDP), always 0, BD is not supported + * bit2: Bypass (BPS), 1: HW skips this GPD if HWO = 1 + * bit6: [EL] Zero Length Packet (ZLP), moved from @dw3_info[29] + * bit7: Interrupt On Completion (IOC) + * bit[31:16]: ([EL] bit[31:12]) allow data buffer length (RX ONLY), + * the buffer length of the data to receive + * bit[23:16]: ([EL] bit[31:24]) extension address (TX ONLY), + * lower 4 bits are extension bits of @buffer, + * upper 4 bits are extension bits of @next_gpd + * @next_gpd: Physical address of the next GPD + * @buffer: Physical address of the data buffer + * @dw3_info: + * bit[15:0]: ([EL] bit[19:0]) data buffer length, + * (TX): the buffer length of the data to transmit + * (RX): The total length of data received + * bit[23:16]: ([EL] bit[31:24]) extension address (RX ONLY), + * lower 4 bits are extension bits of @buffer, + * upper 4 bits are extension bits of @next_gpd + * bit29: ([EL] abandoned) Zero Length Packet (ZLP) (TX ONLY) + */ +struct qmu_gpd { + __le32 dw0_info; + __le32 next_gpd; + __le32 buffer; + __le32 dw3_info; +} __packed; + +/** +* struct mtu3_gpd_ring - GPD ring descriptor +* @dma: physical base address of GPD segment +* @start: virtual base address of GPD segment +* @end: the last GPD element +* @enqueue: the first empty GPD to use +* @dequeue: the first completed GPD serviced by ISR +* +* NOTE: the size of GPD ring should be >= 2 +*/ +struct mtu3_gpd_ring { + dma_addr_t dma; + struct qmu_gpd *start; + struct qmu_gpd *end; + struct qmu_gpd *enqueue; + struct qmu_gpd *dequeue; +}; + +/** +* struct otg_switch_mtk - OTG/dual-role switch management +* @vbus: vbus 5V used by host mode +* @edev: external connector used to detect vbus and iddig changes +* @id_nb : notifier for iddig(idpin) detection +* @dr_work : work for drd mode switch, used to avoid sleep in atomic context +* @desired_role : role desired to switch +* @default_role : default mode while usb role is USB_ROLE_NONE +* @role_sw : use USB Role Switch to support dual-role switch, can't use +* extcon at the same time, and extcon is deprecated. +* @role_sw_used : true when the USB Role Switch is used. +* @is_u3_drd: whether port0 supports usb3.0 dual-role device or not +* @manual_drd_enabled: it's true when supports dual-role device by debugfs +* to switch host/device modes depending on user input. +*/ +struct otg_switch_mtk { + struct regulator *vbus; + struct extcon_dev *edev; + struct notifier_block id_nb; + struct work_struct dr_work; + enum usb_role desired_role; + enum usb_role default_role; + struct usb_role_switch *role_sw; + bool role_sw_used; + bool is_u3_drd; + bool manual_drd_enabled; +}; + +/** + * struct ssusb_mtk - SuperSpeed USB descriptor (MTK) + * @mac_base: register base address of device MAC, exclude xHCI's + * @ippc_base: register base address of IP Power and Clock interface (IPPC) + * @vusb33: usb3.3V shared by device/host IP + * @dr_mode: works in which mode: + * host only, device only or dual-role mode + * @u2_ports: number of usb2.0 host ports + * @u3_ports: number of usb3.0 host ports + * @u2p_dis_msk: mask of disabling usb2 ports, e.g. bit0==1 to + * disable u2port0, bit1==1 to disable u2port1,... etc, + * but when use dual-role mode, can't disable u2port0 + * @u3p_dis_msk: mask of disabling usb3 ports, for example, bit0==1 to + * disable u3port0, bit1==1 to disable u3port1,... etc + * @dbgfs_root: only used when supports manual dual-role switch via debugfs + * @uwk_en: it's true when supports remote wakeup in host mode + * @uwk: syscon including usb wakeup glue layer between SSUSB IP and SPM + * @uwk_reg_base: the base address of the wakeup glue layer in @uwk + * @uwk_vers: the version of the wakeup glue layer + */ +struct ssusb_mtk { + struct device *dev; + struct mtu3 *u3d; + void __iomem *mac_base; + void __iomem *ippc_base; + struct phy **phys; + int num_phys; + int wakeup_irq; + /* common power & clock */ + struct regulator *vusb33; + struct clk_bulk_data clks[BULK_CLKS_CNT]; + /* otg */ + struct otg_switch_mtk otg_switch; + enum usb_dr_mode dr_mode; + bool is_host; + int u2_ports; + int u3_ports; + int u2p_dis_msk; + int u3p_dis_msk; + struct dentry *dbgfs_root; + /* usb wakeup for host mode */ + bool uwk_en; + struct regmap *uwk; + u32 uwk_reg_base; + u32 uwk_vers; +}; + +/** + * struct mtu3_ep - common mtu3 endpoint description + * @fifo_size: it is (@slot + 1) * @fifo_seg_size + * @fifo_seg_size: it is roundup_pow_of_two(@maxp) + */ +struct mtu3_ep { + struct usb_ep ep; + char name[12]; + struct mtu3 *mtu; + u8 epnum; + u8 type; + u8 is_in; + u16 maxp; + int slot; + u32 fifo_size; + u32 fifo_addr; + u32 fifo_seg_size; + struct mtu3_fifo_info *fifo; + + struct list_head req_list; + struct mtu3_gpd_ring gpd_ring; + const struct usb_ss_ep_comp_descriptor *comp_desc; + const struct usb_endpoint_descriptor *desc; + + int flags; +}; + +struct mtu3_request { + struct usb_request request; + struct list_head list; + struct mtu3_ep *mep; + struct mtu3 *mtu; + struct qmu_gpd *gpd; + int epnum; +}; + +static inline struct ssusb_mtk *dev_to_ssusb(struct device *dev) +{ + return dev_get_drvdata(dev); +} + +/** + * struct mtu3 - device driver instance data. + * @slot: MTU3_U2_IP_SLOT_DEFAULT for U2 IP only, + * MTU3_U3_IP_SLOT_DEFAULT for U3 IP + * @may_wakeup: means device's remote wakeup is enabled + * @is_self_powered: is reported in device status and the config descriptor + * @delayed_status: true when function drivers ask for delayed status + * @gen2cp: compatible with USB3 Gen2 IP + * @ep0_req: dummy request used while handling standard USB requests + * for GET_STATUS and SET_SEL + * @setup_buf: ep0 response buffer for GET_STATUS and SET_SEL requests + * @u3_capable: is capable of supporting USB3 + */ +struct mtu3 { + spinlock_t lock; + struct ssusb_mtk *ssusb; + struct device *dev; + void __iomem *mac_base; + void __iomem *ippc_base; + int irq; + + struct mtu3_fifo_info tx_fifo; + struct mtu3_fifo_info rx_fifo; + + struct mtu3_ep *ep_array; + struct mtu3_ep *in_eps; + struct mtu3_ep *out_eps; + struct mtu3_ep *ep0; + int num_eps; + int slot; + int active_ep; + + struct dma_pool *qmu_gpd_pool; + enum mtu3_g_ep0_state ep0_state; + struct usb_gadget g; /* the gadget */ + struct usb_gadget_driver *gadget_driver; + struct mtu3_request ep0_req; + u8 setup_buf[EP0_RESPONSE_BUF]; + enum usb_device_speed max_speed; + enum usb_device_speed speed; + + unsigned is_active:1; + unsigned may_wakeup:1; + unsigned is_self_powered:1; + unsigned test_mode:1; + unsigned softconnect:1; + unsigned u1_enable:1; + unsigned u2_enable:1; + unsigned u3_capable:1; + unsigned delayed_status:1; + unsigned gen2cp:1; + unsigned connected:1; + unsigned async_callbacks:1; + unsigned separate_fifo:1; + + u8 address; + u8 test_mode_nr; + u32 hw_version; +}; + +static inline struct mtu3 *gadget_to_mtu3(struct usb_gadget *g) +{ + return container_of(g, struct mtu3, g); +} + +static inline struct mtu3_request *to_mtu3_request(struct usb_request *req) +{ + return req ? container_of(req, struct mtu3_request, request) : NULL; +} + +static inline struct mtu3_ep *to_mtu3_ep(struct usb_ep *ep) +{ + return ep ? container_of(ep, struct mtu3_ep, ep) : NULL; +} + +static inline struct mtu3_request *next_request(struct mtu3_ep *mep) +{ + return list_first_entry_or_null(&mep->req_list, struct mtu3_request, + list); +} + +static inline void mtu3_writel(void __iomem *base, u32 offset, u32 data) +{ + writel(data, base + offset); +} + +static inline u32 mtu3_readl(void __iomem *base, u32 offset) +{ + return readl(base + offset); +} + +static inline void mtu3_setbits(void __iomem *base, u32 offset, u32 bits) +{ + void __iomem *addr = base + offset; + u32 tmp = readl(addr); + + writel((tmp | (bits)), addr); +} + +static inline void mtu3_clrbits(void __iomem *base, u32 offset, u32 bits) +{ + void __iomem *addr = base + offset; + u32 tmp = readl(addr); + + writel((tmp & ~(bits)), addr); +} + +int ssusb_check_clocks(struct ssusb_mtk *ssusb, u32 ex_clks); +struct usb_request *mtu3_alloc_request(struct usb_ep *ep, gfp_t gfp_flags); +void mtu3_free_request(struct usb_ep *ep, struct usb_request *req); +void mtu3_req_complete(struct mtu3_ep *mep, + struct usb_request *req, int status); + +int mtu3_config_ep(struct mtu3 *mtu, struct mtu3_ep *mep, + int interval, int burst, int mult); +void mtu3_deconfig_ep(struct mtu3 *mtu, struct mtu3_ep *mep); +void mtu3_ep_stall_set(struct mtu3_ep *mep, bool set); +void mtu3_start(struct mtu3 *mtu); +void mtu3_stop(struct mtu3 *mtu); +void mtu3_dev_on_off(struct mtu3 *mtu, int is_on); + +int mtu3_gadget_setup(struct mtu3 *mtu); +void mtu3_gadget_cleanup(struct mtu3 *mtu); +void mtu3_gadget_reset(struct mtu3 *mtu); +void mtu3_gadget_suspend(struct mtu3 *mtu); +void mtu3_gadget_resume(struct mtu3 *mtu); +void mtu3_gadget_disconnect(struct mtu3 *mtu); + +irqreturn_t mtu3_ep0_isr(struct mtu3 *mtu); +extern const struct usb_ep_ops mtu3_ep0_ops; + +#endif diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c new file mode 100644 index 000000000..a40bf5bad --- /dev/null +++ b/drivers/usb/mtu3/mtu3_core.c @@ -0,0 +1,1068 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mtu3_core.c - hardware access layer and gadget init/exit of + * MediaTek usb3 Dual-Role Controller Driver + * + * Copyright (C) 2016 MediaTek Inc. + * + * Author: Chunfeng Yun + */ + +#include +#include +#include +#include +#include +#include + +#include "mtu3.h" +#include "mtu3_dr.h" +#include "mtu3_debug.h" +#include "mtu3_trace.h" + +static int ep_fifo_alloc(struct mtu3_ep *mep, u32 seg_size) +{ + struct mtu3_fifo_info *fifo = mep->fifo; + u32 num_bits = DIV_ROUND_UP(seg_size, MTU3_EP_FIFO_UNIT); + u32 start_bit; + + /* ensure that @mep->fifo_seg_size is power of two */ + num_bits = roundup_pow_of_two(num_bits); + if (num_bits > fifo->limit) + return -EINVAL; + + mep->fifo_seg_size = num_bits * MTU3_EP_FIFO_UNIT; + num_bits = num_bits * (mep->slot + 1); + start_bit = bitmap_find_next_zero_area(fifo->bitmap, + fifo->limit, 0, num_bits, 0); + if (start_bit >= fifo->limit) + return -EOVERFLOW; + + bitmap_set(fifo->bitmap, start_bit, num_bits); + mep->fifo_size = num_bits * MTU3_EP_FIFO_UNIT; + mep->fifo_addr = fifo->base + MTU3_EP_FIFO_UNIT * start_bit; + + dev_dbg(mep->mtu->dev, "%s fifo:%#x/%#x, start_bit: %d\n", + __func__, mep->fifo_seg_size, mep->fifo_size, start_bit); + + return mep->fifo_addr; +} + +static void ep_fifo_free(struct mtu3_ep *mep) +{ + struct mtu3_fifo_info *fifo = mep->fifo; + u32 addr = mep->fifo_addr; + u32 bits = mep->fifo_size / MTU3_EP_FIFO_UNIT; + u32 start_bit; + + if (unlikely(addr < fifo->base || bits > fifo->limit)) + return; + + start_bit = (addr - fifo->base) / MTU3_EP_FIFO_UNIT; + bitmap_clear(fifo->bitmap, start_bit, bits); + mep->fifo_size = 0; + mep->fifo_seg_size = 0; + + dev_dbg(mep->mtu->dev, "%s size:%#x/%#x, start_bit: %d\n", + __func__, mep->fifo_seg_size, mep->fifo_size, start_bit); +} + +/* enable/disable U3D SS function */ +static inline void mtu3_ss_func_set(struct mtu3 *mtu, bool enable) +{ + /* If usb3_en==0, LTSSM will go to SS.Disable state */ + if (enable) + mtu3_setbits(mtu->mac_base, U3D_USB3_CONFIG, USB3_EN); + else + mtu3_clrbits(mtu->mac_base, U3D_USB3_CONFIG, USB3_EN); + + dev_dbg(mtu->dev, "USB3_EN = %d\n", !!enable); +} + +/* set/clear U3D HS device soft connect */ +static inline void mtu3_hs_softconn_set(struct mtu3 *mtu, bool enable) +{ + if (enable) { + mtu3_setbits(mtu->mac_base, U3D_POWER_MANAGEMENT, + SOFT_CONN | SUSPENDM_ENABLE); + } else { + mtu3_clrbits(mtu->mac_base, U3D_POWER_MANAGEMENT, + SOFT_CONN | SUSPENDM_ENABLE); + } + dev_dbg(mtu->dev, "SOFTCONN = %d\n", !!enable); +} + +/* only port0 of U2/U3 supports device mode */ +static int mtu3_device_enable(struct mtu3 *mtu) +{ + void __iomem *ibase = mtu->ippc_base; + u32 check_clk = 0; + + mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN); + + if (mtu->u3_capable) { + check_clk = SSUSB_U3_MAC_RST_B_STS; + mtu3_clrbits(ibase, SSUSB_U3_CTRL(0), + (SSUSB_U3_PORT_DIS | SSUSB_U3_PORT_PDN | + SSUSB_U3_PORT_HOST_SEL)); + } + mtu3_clrbits(ibase, SSUSB_U2_CTRL(0), + (SSUSB_U2_PORT_DIS | SSUSB_U2_PORT_PDN | + SSUSB_U2_PORT_HOST_SEL)); + + if (mtu->ssusb->dr_mode == USB_DR_MODE_OTG) { + mtu3_setbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_OTG_SEL); + if (mtu->u3_capable) + mtu3_setbits(ibase, SSUSB_U3_CTRL(0), + SSUSB_U3_PORT_DUAL_MODE); + } + + return ssusb_check_clocks(mtu->ssusb, check_clk); +} + +static void mtu3_device_disable(struct mtu3 *mtu) +{ + void __iomem *ibase = mtu->ippc_base; + + if (mtu->u3_capable) + mtu3_setbits(ibase, SSUSB_U3_CTRL(0), + (SSUSB_U3_PORT_DIS | SSUSB_U3_PORT_PDN)); + + mtu3_setbits(ibase, SSUSB_U2_CTRL(0), + SSUSB_U2_PORT_DIS | SSUSB_U2_PORT_PDN); + + if (mtu->ssusb->dr_mode == USB_DR_MODE_OTG) { + mtu3_clrbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_OTG_SEL); + if (mtu->u3_capable) + mtu3_clrbits(ibase, SSUSB_U3_CTRL(0), + SSUSB_U3_PORT_DUAL_MODE); + } + + mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN); +} + +static void mtu3_dev_power_on(struct mtu3 *mtu) +{ + void __iomem *ibase = mtu->ippc_base; + + mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN); + if (mtu->u3_capable) + mtu3_clrbits(ibase, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN); + + mtu3_clrbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN); +} + +static void mtu3_dev_power_down(struct mtu3 *mtu) +{ + void __iomem *ibase = mtu->ippc_base; + + if (mtu->u3_capable) + mtu3_setbits(ibase, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN); + + mtu3_setbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN); + mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN); +} + +/* reset U3D's device module. */ +static void mtu3_device_reset(struct mtu3 *mtu) +{ + void __iomem *ibase = mtu->ippc_base; + + mtu3_setbits(ibase, U3D_SSUSB_DEV_RST_CTRL, SSUSB_DEV_SW_RST); + udelay(1); + mtu3_clrbits(ibase, U3D_SSUSB_DEV_RST_CTRL, SSUSB_DEV_SW_RST); +} + +static void mtu3_intr_status_clear(struct mtu3 *mtu) +{ + void __iomem *mbase = mtu->mac_base; + + /* Clear EP0 and Tx/Rx EPn interrupts status */ + mtu3_writel(mbase, U3D_EPISR, ~0x0); + /* Clear U2 USB common interrupts status */ + mtu3_writel(mbase, U3D_COMMON_USB_INTR, ~0x0); + /* Clear U3 LTSSM interrupts status */ + mtu3_writel(mbase, U3D_LTSSM_INTR, ~0x0); + /* Clear speed change interrupt status */ + mtu3_writel(mbase, U3D_DEV_LINK_INTR, ~0x0); + /* Clear QMU interrupt status */ + mtu3_writel(mbase, U3D_QISAR0, ~0x0); +} + +/* disable all interrupts */ +static void mtu3_intr_disable(struct mtu3 *mtu) +{ + /* Disable level 1 interrupts */ + mtu3_writel(mtu->mac_base, U3D_LV1IECR, ~0x0); + /* Disable endpoint interrupts */ + mtu3_writel(mtu->mac_base, U3D_EPIECR, ~0x0); + mtu3_intr_status_clear(mtu); +} + +/* enable system global interrupt */ +static void mtu3_intr_enable(struct mtu3 *mtu) +{ + void __iomem *mbase = mtu->mac_base; + u32 value; + + /*Enable level 1 interrupts (BMU, QMU, MAC3, DMA, MAC2, EPCTL) */ + value = BMU_INTR | QMU_INTR | MAC3_INTR | MAC2_INTR | EP_CTRL_INTR; + mtu3_writel(mbase, U3D_LV1IESR, value); + + /* Enable U2 common USB interrupts */ + value = SUSPEND_INTR | RESUME_INTR | RESET_INTR; + mtu3_writel(mbase, U3D_COMMON_USB_INTR_ENABLE, value); + + if (mtu->u3_capable) { + /* Enable U3 LTSSM interrupts */ + value = HOT_RST_INTR | WARM_RST_INTR | + ENTER_U3_INTR | EXIT_U3_INTR; + mtu3_writel(mbase, U3D_LTSSM_INTR_ENABLE, value); + } + + /* Enable QMU interrupts. */ + value = TXQ_CSERR_INT | TXQ_LENERR_INT | RXQ_CSERR_INT | + RXQ_LENERR_INT | RXQ_ZLPERR_INT; + mtu3_writel(mbase, U3D_QIESR1, value); + + /* Enable speed change interrupt */ + mtu3_writel(mbase, U3D_DEV_LINK_INTR_ENABLE, SSUSB_DEV_SPEED_CHG_INTR); +} + +static void mtu3_set_speed(struct mtu3 *mtu, enum usb_device_speed speed) +{ + void __iomem *mbase = mtu->mac_base; + + if (speed > mtu->max_speed) + speed = mtu->max_speed; + + switch (speed) { + case USB_SPEED_FULL: + /* disable U3 SS function */ + mtu3_clrbits(mbase, U3D_USB3_CONFIG, USB3_EN); + /* disable HS function */ + mtu3_clrbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE); + break; + case USB_SPEED_HIGH: + mtu3_clrbits(mbase, U3D_USB3_CONFIG, USB3_EN); + /* HS/FS detected by HW */ + mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE); + break; + case USB_SPEED_SUPER: + mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE); + mtu3_clrbits(mtu->ippc_base, SSUSB_U3_CTRL(0), + SSUSB_U3_PORT_SSP_SPEED); + break; + case USB_SPEED_SUPER_PLUS: + mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE); + mtu3_setbits(mtu->ippc_base, SSUSB_U3_CTRL(0), + SSUSB_U3_PORT_SSP_SPEED); + break; + default: + dev_err(mtu->dev, "invalid speed: %s\n", + usb_speed_string(speed)); + return; + } + + mtu->speed = speed; + dev_dbg(mtu->dev, "set speed: %s\n", usb_speed_string(speed)); +} + +/* CSR registers will be reset to default value if port is disabled */ +static void mtu3_csr_init(struct mtu3 *mtu) +{ + void __iomem *mbase = mtu->mac_base; + + if (mtu->u3_capable) { + /* disable LGO_U1/U2 by default */ + mtu3_clrbits(mbase, U3D_LINK_POWER_CONTROL, + SW_U1_REQUEST_ENABLE | SW_U2_REQUEST_ENABLE); + /* enable accept LGO_U1/U2 link command from host */ + mtu3_setbits(mbase, U3D_LINK_POWER_CONTROL, + SW_U1_ACCEPT_ENABLE | SW_U2_ACCEPT_ENABLE); + /* device responses to u3_exit from host automatically */ + mtu3_clrbits(mbase, U3D_LTSSM_CTRL, SOFT_U3_EXIT_EN); + /* automatically build U2 link when U3 detect fail */ + mtu3_setbits(mbase, U3D_USB2_TEST_MODE, U2U3_AUTO_SWITCH); + /* auto clear SOFT_CONN when clear USB3_EN if work as HS */ + mtu3_setbits(mbase, U3D_U3U2_SWITCH_CTRL, SOFTCON_CLR_AUTO_EN); + } + + /* delay about 0.1us from detecting reset to send chirp-K */ + mtu3_clrbits(mbase, U3D_LINK_RESET_INFO, WTCHRP_MSK); + /* enable automatic HWRW from L1 */ + mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, LPM_HRWE); +} + +/* reset: u2 - data toggle, u3 - SeqN, flow control status etc */ +static void mtu3_ep_reset(struct mtu3_ep *mep) +{ + struct mtu3 *mtu = mep->mtu; + u32 rst_bit = EP_RST(mep->is_in, mep->epnum); + + mtu3_setbits(mtu->mac_base, U3D_EP_RST, rst_bit); + mtu3_clrbits(mtu->mac_base, U3D_EP_RST, rst_bit); +} + +/* set/clear the stall and toggle bits for non-ep0 */ +void mtu3_ep_stall_set(struct mtu3_ep *mep, bool set) +{ + struct mtu3 *mtu = mep->mtu; + void __iomem *mbase = mtu->mac_base; + u8 epnum = mep->epnum; + u32 csr; + + if (mep->is_in) { /* TX */ + csr = mtu3_readl(mbase, MU3D_EP_TXCR0(epnum)) & TX_W1C_BITS; + if (set) + csr |= TX_SENDSTALL; + else + csr = (csr & (~TX_SENDSTALL)) | TX_SENTSTALL; + mtu3_writel(mbase, MU3D_EP_TXCR0(epnum), csr); + } else { /* RX */ + csr = mtu3_readl(mbase, MU3D_EP_RXCR0(epnum)) & RX_W1C_BITS; + if (set) + csr |= RX_SENDSTALL; + else + csr = (csr & (~RX_SENDSTALL)) | RX_SENTSTALL; + mtu3_writel(mbase, MU3D_EP_RXCR0(epnum), csr); + } + + if (!set) { + mtu3_ep_reset(mep); + mep->flags &= ~MTU3_EP_STALL; + } else { + mep->flags |= MTU3_EP_STALL; + } + + dev_dbg(mtu->dev, "%s: %s\n", mep->name, + set ? "SEND STALL" : "CLEAR STALL, with EP RESET"); +} + +void mtu3_dev_on_off(struct mtu3 *mtu, int is_on) +{ + if (mtu->u3_capable && mtu->speed >= USB_SPEED_SUPER) + mtu3_ss_func_set(mtu, is_on); + else + mtu3_hs_softconn_set(mtu, is_on); + + dev_info(mtu->dev, "gadget (%s) pullup D%s\n", + usb_speed_string(mtu->speed), is_on ? "+" : "-"); +} + +void mtu3_start(struct mtu3 *mtu) +{ + void __iomem *mbase = mtu->mac_base; + + dev_dbg(mtu->dev, "%s devctl 0x%x\n", __func__, + mtu3_readl(mbase, U3D_DEVICE_CONTROL)); + + mtu3_dev_power_on(mtu); + mtu3_csr_init(mtu); + mtu3_set_speed(mtu, mtu->speed); + + /* Initialize the default interrupts */ + mtu3_intr_enable(mtu); + mtu->is_active = 1; + + if (mtu->softconnect) + mtu3_dev_on_off(mtu, 1); +} + +void mtu3_stop(struct mtu3 *mtu) +{ + dev_dbg(mtu->dev, "%s\n", __func__); + + mtu3_intr_disable(mtu); + + if (mtu->softconnect) + mtu3_dev_on_off(mtu, 0); + + mtu->is_active = 0; + mtu3_dev_power_down(mtu); +} + +static void mtu3_dev_suspend(struct mtu3 *mtu) +{ + if (!mtu->is_active) + return; + + mtu3_intr_disable(mtu); + mtu3_dev_power_down(mtu); +} + +static void mtu3_dev_resume(struct mtu3 *mtu) +{ + if (!mtu->is_active) + return; + + mtu3_dev_power_on(mtu); + mtu3_intr_enable(mtu); +} + +/* for non-ep0 */ +int mtu3_config_ep(struct mtu3 *mtu, struct mtu3_ep *mep, + int interval, int burst, int mult) +{ + void __iomem *mbase = mtu->mac_base; + bool gen2cp = mtu->gen2cp; + int epnum = mep->epnum; + u32 csr0, csr1, csr2; + int fifo_sgsz, fifo_addr; + int num_pkts; + + fifo_addr = ep_fifo_alloc(mep, mep->maxp); + if (fifo_addr < 0) { + dev_err(mtu->dev, "alloc ep fifo failed(%d)\n", mep->maxp); + return -ENOMEM; + } + fifo_sgsz = ilog2(mep->fifo_seg_size); + dev_dbg(mtu->dev, "%s fifosz: %x(%x/%x)\n", __func__, fifo_sgsz, + mep->fifo_seg_size, mep->fifo_size); + + if (mep->is_in) { + csr0 = TX_TXMAXPKTSZ(mep->maxp); + csr0 |= TX_DMAREQEN; + + num_pkts = (burst + 1) * (mult + 1) - 1; + csr1 = TX_SS_BURST(burst) | TX_SLOT(mep->slot); + csr1 |= TX_MAX_PKT(gen2cp, num_pkts) | TX_MULT(gen2cp, mult); + + csr2 = TX_FIFOADDR(fifo_addr >> 4); + csr2 |= TX_FIFOSEGSIZE(fifo_sgsz); + + switch (mep->type) { + case USB_ENDPOINT_XFER_BULK: + csr1 |= TX_TYPE(TYPE_BULK); + break; + case USB_ENDPOINT_XFER_ISOC: + csr1 |= TX_TYPE(TYPE_ISO); + csr2 |= TX_BINTERVAL(interval); + break; + case USB_ENDPOINT_XFER_INT: + csr1 |= TX_TYPE(TYPE_INT); + csr2 |= TX_BINTERVAL(interval); + break; + } + + /* Enable QMU Done interrupt */ + mtu3_setbits(mbase, U3D_QIESR0, QMU_TX_DONE_INT(epnum)); + + mtu3_writel(mbase, MU3D_EP_TXCR0(epnum), csr0); + mtu3_writel(mbase, MU3D_EP_TXCR1(epnum), csr1); + mtu3_writel(mbase, MU3D_EP_TXCR2(epnum), csr2); + + dev_dbg(mtu->dev, "U3D_TX%d CSR0:%#x, CSR1:%#x, CSR2:%#x\n", + epnum, mtu3_readl(mbase, MU3D_EP_TXCR0(epnum)), + mtu3_readl(mbase, MU3D_EP_TXCR1(epnum)), + mtu3_readl(mbase, MU3D_EP_TXCR2(epnum))); + } else { + csr0 = RX_RXMAXPKTSZ(mep->maxp); + csr0 |= RX_DMAREQEN; + + num_pkts = (burst + 1) * (mult + 1) - 1; + csr1 = RX_SS_BURST(burst) | RX_SLOT(mep->slot); + csr1 |= RX_MAX_PKT(gen2cp, num_pkts) | RX_MULT(gen2cp, mult); + + csr2 = RX_FIFOADDR(fifo_addr >> 4); + csr2 |= RX_FIFOSEGSIZE(fifo_sgsz); + + switch (mep->type) { + case USB_ENDPOINT_XFER_BULK: + csr1 |= RX_TYPE(TYPE_BULK); + break; + case USB_ENDPOINT_XFER_ISOC: + csr1 |= RX_TYPE(TYPE_ISO); + csr2 |= RX_BINTERVAL(interval); + break; + case USB_ENDPOINT_XFER_INT: + csr1 |= RX_TYPE(TYPE_INT); + csr2 |= RX_BINTERVAL(interval); + break; + } + + /*Enable QMU Done interrupt */ + mtu3_setbits(mbase, U3D_QIESR0, QMU_RX_DONE_INT(epnum)); + + mtu3_writel(mbase, MU3D_EP_RXCR0(epnum), csr0); + mtu3_writel(mbase, MU3D_EP_RXCR1(epnum), csr1); + mtu3_writel(mbase, MU3D_EP_RXCR2(epnum), csr2); + + dev_dbg(mtu->dev, "U3D_RX%d CSR0:%#x, CSR1:%#x, CSR2:%#x\n", + epnum, mtu3_readl(mbase, MU3D_EP_RXCR0(epnum)), + mtu3_readl(mbase, MU3D_EP_RXCR1(epnum)), + mtu3_readl(mbase, MU3D_EP_RXCR2(epnum))); + } + + dev_dbg(mtu->dev, "csr0:%#x, csr1:%#x, csr2:%#x\n", csr0, csr1, csr2); + dev_dbg(mtu->dev, "%s: %s, fifo-addr:%#x, fifo-size:%#x(%#x/%#x)\n", + __func__, mep->name, mep->fifo_addr, mep->fifo_size, + fifo_sgsz, mep->fifo_seg_size); + + return 0; +} + +/* for non-ep0 */ +void mtu3_deconfig_ep(struct mtu3 *mtu, struct mtu3_ep *mep) +{ + void __iomem *mbase = mtu->mac_base; + int epnum = mep->epnum; + + if (mep->is_in) { + mtu3_writel(mbase, MU3D_EP_TXCR0(epnum), 0); + mtu3_writel(mbase, MU3D_EP_TXCR1(epnum), 0); + mtu3_writel(mbase, MU3D_EP_TXCR2(epnum), 0); + mtu3_setbits(mbase, U3D_QIECR0, QMU_TX_DONE_INT(epnum)); + } else { + mtu3_writel(mbase, MU3D_EP_RXCR0(epnum), 0); + mtu3_writel(mbase, MU3D_EP_RXCR1(epnum), 0); + mtu3_writel(mbase, MU3D_EP_RXCR2(epnum), 0); + mtu3_setbits(mbase, U3D_QIECR0, QMU_RX_DONE_INT(epnum)); + } + + mtu3_ep_reset(mep); + ep_fifo_free(mep); + + dev_dbg(mtu->dev, "%s: %s\n", __func__, mep->name); +} + +/* + * Two scenarios: + * 1. when device IP supports SS, the fifo of EP0, TX EPs, RX EPs + * are separated; + * 2. when supports only HS, the fifo is shared for all EPs, and + * the capability registers of @EPNTXFFSZ or @EPNRXFFSZ indicate + * the total fifo size of non-ep0, and ep0's is fixed to 64B, + * so the total fifo size is 64B + @EPNTXFFSZ; + * Due to the first 64B should be reserved for EP0, non-ep0's fifo + * starts from offset 64 and are divided into two equal parts for + * TX or RX EPs for simplification. + */ +static void get_ep_fifo_config(struct mtu3 *mtu) +{ + struct mtu3_fifo_info *tx_fifo; + struct mtu3_fifo_info *rx_fifo; + u32 fifosize; + + if (mtu->separate_fifo) { + fifosize = mtu3_readl(mtu->mac_base, U3D_CAP_EPNTXFFSZ); + tx_fifo = &mtu->tx_fifo; + tx_fifo->base = 0; + tx_fifo->limit = fifosize / MTU3_EP_FIFO_UNIT; + bitmap_zero(tx_fifo->bitmap, MTU3_FIFO_BIT_SIZE); + + fifosize = mtu3_readl(mtu->mac_base, U3D_CAP_EPNRXFFSZ); + rx_fifo = &mtu->rx_fifo; + rx_fifo->base = 0; + rx_fifo->limit = fifosize / MTU3_EP_FIFO_UNIT; + bitmap_zero(rx_fifo->bitmap, MTU3_FIFO_BIT_SIZE); + mtu->slot = MTU3_U3_IP_SLOT_DEFAULT; + } else { + fifosize = mtu3_readl(mtu->mac_base, U3D_CAP_EPNTXFFSZ); + tx_fifo = &mtu->tx_fifo; + tx_fifo->base = MTU3_U2_IP_EP0_FIFO_SIZE; + tx_fifo->limit = (fifosize / MTU3_EP_FIFO_UNIT) >> 1; + bitmap_zero(tx_fifo->bitmap, MTU3_FIFO_BIT_SIZE); + + rx_fifo = &mtu->rx_fifo; + rx_fifo->base = + tx_fifo->base + tx_fifo->limit * MTU3_EP_FIFO_UNIT; + rx_fifo->limit = tx_fifo->limit; + bitmap_zero(rx_fifo->bitmap, MTU3_FIFO_BIT_SIZE); + mtu->slot = MTU3_U2_IP_SLOT_DEFAULT; + } + + dev_dbg(mtu->dev, "%s, TX: base-%d, limit-%d; RX: base-%d, limit-%d\n", + __func__, tx_fifo->base, tx_fifo->limit, + rx_fifo->base, rx_fifo->limit); +} + +static void mtu3_ep0_setup(struct mtu3 *mtu) +{ + u32 maxpacket = mtu->g.ep0->maxpacket; + u32 csr; + + dev_dbg(mtu->dev, "%s maxpacket: %d\n", __func__, maxpacket); + + csr = mtu3_readl(mtu->mac_base, U3D_EP0CSR); + csr &= ~EP0_MAXPKTSZ_MSK; + csr |= EP0_MAXPKTSZ(maxpacket); + csr &= EP0_W1C_BITS; + mtu3_writel(mtu->mac_base, U3D_EP0CSR, csr); + + /* Enable EP0 interrupt */ + mtu3_writel(mtu->mac_base, U3D_EPIESR, EP0ISR | SETUPENDISR); +} + +static int mtu3_mem_alloc(struct mtu3 *mtu) +{ + void __iomem *mbase = mtu->mac_base; + struct mtu3_ep *ep_array; + int in_ep_num, out_ep_num; + u32 cap_epinfo; + int ret; + int i; + + cap_epinfo = mtu3_readl(mbase, U3D_CAP_EPINFO); + in_ep_num = CAP_TX_EP_NUM(cap_epinfo); + out_ep_num = CAP_RX_EP_NUM(cap_epinfo); + + dev_info(mtu->dev, "fifosz/epnum: Tx=%#x/%d, Rx=%#x/%d\n", + mtu3_readl(mbase, U3D_CAP_EPNTXFFSZ), in_ep_num, + mtu3_readl(mbase, U3D_CAP_EPNRXFFSZ), out_ep_num); + + /* one for ep0, another is reserved */ + mtu->num_eps = min(in_ep_num, out_ep_num) + 1; + ep_array = kzalloc_objs(*ep_array, mtu->num_eps * 2); + if (ep_array == NULL) + return -ENOMEM; + + mtu->ep_array = ep_array; + mtu->in_eps = ep_array; + mtu->out_eps = &ep_array[mtu->num_eps]; + /* ep0 uses in_eps[0], out_eps[0] is reserved */ + mtu->ep0 = mtu->in_eps; + mtu->ep0->mtu = mtu; + mtu->ep0->epnum = 0; + + for (i = 1; i < mtu->num_eps; i++) { + struct mtu3_ep *mep = mtu->in_eps + i; + + mep->fifo = &mtu->tx_fifo; + mep = mtu->out_eps + i; + mep->fifo = &mtu->rx_fifo; + } + + get_ep_fifo_config(mtu); + + ret = mtu3_qmu_init(mtu); + if (ret) + kfree(mtu->ep_array); + + return ret; +} + +static void mtu3_mem_free(struct mtu3 *mtu) +{ + mtu3_qmu_exit(mtu); + kfree(mtu->ep_array); +} + +static void mtu3_regs_init(struct mtu3 *mtu) +{ + void __iomem *mbase = mtu->mac_base; + + /* be sure interrupts are disabled before registration of ISR */ + mtu3_intr_disable(mtu); + + mtu3_csr_init(mtu); + + /* U2/U3 detected by HW */ + mtu3_writel(mbase, U3D_DEVICE_CONF, 0); + /* vbus detected by HW */ + mtu3_clrbits(mbase, U3D_MISC_CTRL, VBUS_FRC_EN | VBUS_ON); + /* use new QMU format when HW version >= 0x1003 */ + if (mtu->gen2cp) + mtu3_writel(mbase, U3D_QFCR, ~0x0); +} + +static irqreturn_t mtu3_link_isr(struct mtu3 *mtu) +{ + void __iomem *mbase = mtu->mac_base; + enum usb_device_speed udev_speed; + u32 maxpkt = 64; + u32 link; + u32 speed; + + link = mtu3_readl(mbase, U3D_DEV_LINK_INTR); + link &= mtu3_readl(mbase, U3D_DEV_LINK_INTR_ENABLE); + mtu3_writel(mbase, U3D_DEV_LINK_INTR, link); /* W1C */ + dev_dbg(mtu->dev, "=== LINK[%x] ===\n", link); + + if (!(link & SSUSB_DEV_SPEED_CHG_INTR)) + return IRQ_NONE; + + speed = SSUSB_DEV_SPEED(mtu3_readl(mbase, U3D_DEVICE_CONF)); + + switch (speed) { + case MTU3_SPEED_FULL: + udev_speed = USB_SPEED_FULL; + /*BESLCK = 4 < BESLCK_U3 = 10 < BESLDCK = 15 */ + mtu3_writel(mbase, U3D_USB20_LPM_PARAMETER, LPM_BESLDCK(0xf) + | LPM_BESLCK(4) | LPM_BESLCK_U3(0xa)); + mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, + LPM_BESL_STALL | LPM_BESLD_STALL); + break; + case MTU3_SPEED_HIGH: + udev_speed = USB_SPEED_HIGH; + /*BESLCK = 4 < BESLCK_U3 = 10 < BESLDCK = 15 */ + mtu3_writel(mbase, U3D_USB20_LPM_PARAMETER, LPM_BESLDCK(0xf) + | LPM_BESLCK(4) | LPM_BESLCK_U3(0xa)); + mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, + LPM_BESL_STALL | LPM_BESLD_STALL); + break; + case MTU3_SPEED_SUPER: + udev_speed = USB_SPEED_SUPER; + maxpkt = 512; + break; + case MTU3_SPEED_SUPER_PLUS: + udev_speed = USB_SPEED_SUPER_PLUS; + maxpkt = 512; + break; + default: + udev_speed = USB_SPEED_UNKNOWN; + break; + } + dev_dbg(mtu->dev, "%s: %s\n", __func__, usb_speed_string(udev_speed)); + mtu3_dbg_trace(mtu->dev, "link speed %s", + usb_speed_string(udev_speed)); + + mtu->g.speed = udev_speed; + mtu->g.ep0->maxpacket = maxpkt; + mtu->ep0_state = MU3D_EP0_STATE_SETUP; + mtu->connected = !!(udev_speed != USB_SPEED_UNKNOWN); + + if (udev_speed == USB_SPEED_UNKNOWN) { + mtu3_gadget_disconnect(mtu); + pm_runtime_put(mtu->dev); + } else { + pm_runtime_get(mtu->dev); + mtu3_ep0_setup(mtu); + } + + return IRQ_HANDLED; +} + +static irqreturn_t mtu3_u3_ltssm_isr(struct mtu3 *mtu) +{ + void __iomem *mbase = mtu->mac_base; + u32 ltssm; + + ltssm = mtu3_readl(mbase, U3D_LTSSM_INTR); + ltssm &= mtu3_readl(mbase, U3D_LTSSM_INTR_ENABLE); + mtu3_writel(mbase, U3D_LTSSM_INTR, ltssm); /* W1C */ + dev_dbg(mtu->dev, "=== LTSSM[%x] ===\n", ltssm); + trace_mtu3_u3_ltssm_isr(ltssm); + + if (ltssm & (HOT_RST_INTR | WARM_RST_INTR)) + mtu3_gadget_reset(mtu); + + if (ltssm & VBUS_FALL_INTR) { + mtu3_ss_func_set(mtu, false); + mtu3_gadget_reset(mtu); + } + + if (ltssm & VBUS_RISE_INTR) + mtu3_ss_func_set(mtu, true); + + if (ltssm & EXIT_U3_INTR) + mtu3_gadget_resume(mtu); + + if (ltssm & ENTER_U3_INTR) + mtu3_gadget_suspend(mtu); + + return IRQ_HANDLED; +} + +static irqreturn_t mtu3_u2_common_isr(struct mtu3 *mtu) +{ + void __iomem *mbase = mtu->mac_base; + u32 u2comm; + + u2comm = mtu3_readl(mbase, U3D_COMMON_USB_INTR); + u2comm &= mtu3_readl(mbase, U3D_COMMON_USB_INTR_ENABLE); + mtu3_writel(mbase, U3D_COMMON_USB_INTR, u2comm); /* W1C */ + dev_dbg(mtu->dev, "=== U2COMM[%x] ===\n", u2comm); + trace_mtu3_u2_common_isr(u2comm); + + if (u2comm & SUSPEND_INTR) + mtu3_gadget_suspend(mtu); + + if (u2comm & RESUME_INTR) + mtu3_gadget_resume(mtu); + + if (u2comm & RESET_INTR) + mtu3_gadget_reset(mtu); + + return IRQ_HANDLED; +} + +static irqreturn_t mtu3_irq(int irq, void *data) +{ + struct mtu3 *mtu = (struct mtu3 *)data; + unsigned long flags; + u32 level1; + + spin_lock_irqsave(&mtu->lock, flags); + + /* U3D_LV1ISR is RU */ + level1 = mtu3_readl(mtu->mac_base, U3D_LV1ISR); + level1 &= mtu3_readl(mtu->mac_base, U3D_LV1IER); + + if (level1 & EP_CTRL_INTR) + mtu3_link_isr(mtu); + + if (level1 & MAC2_INTR) + mtu3_u2_common_isr(mtu); + + if (level1 & MAC3_INTR) + mtu3_u3_ltssm_isr(mtu); + + if (level1 & BMU_INTR) + mtu3_ep0_isr(mtu); + + if (level1 & QMU_INTR) + mtu3_qmu_isr(mtu); + + spin_unlock_irqrestore(&mtu->lock, flags); + + return IRQ_HANDLED; +} + +static void mtu3_check_params(struct mtu3 *mtu) +{ + /* device's u3 port (port0) is disabled */ + if (mtu->u3_capable && (mtu->ssusb->u3p_dis_msk & BIT(0))) + mtu->u3_capable = 0; + + /* check the max_speed parameter */ + switch (mtu->max_speed) { + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + break; + default: + dev_err(mtu->dev, "invalid max_speed: %s\n", + usb_speed_string(mtu->max_speed)); + fallthrough; + case USB_SPEED_UNKNOWN: + /* default as SSP */ + mtu->max_speed = USB_SPEED_SUPER_PLUS; + break; + } + + if (!mtu->u3_capable && (mtu->max_speed > USB_SPEED_HIGH)) + mtu->max_speed = USB_SPEED_HIGH; + + mtu->speed = mtu->max_speed; + + dev_info(mtu->dev, "max_speed: %s\n", + usb_speed_string(mtu->max_speed)); +} + +static int mtu3_hw_init(struct mtu3 *mtu) +{ + u32 value; + int ret; + + value = mtu3_readl(mtu->ippc_base, U3D_SSUSB_IP_TRUNK_VERS); + mtu->hw_version = IP_TRUNK_VERS(value); + mtu->gen2cp = !!(mtu->hw_version >= MTU3_TRUNK_VERS_1003); + + value = mtu3_readl(mtu->ippc_base, U3D_SSUSB_IP_DEV_CAP); + mtu->u3_capable = !!SSUSB_IP_DEV_U3_PORT_NUM(value); + /* usb3 ip uses separate fifo */ + mtu->separate_fifo = mtu->u3_capable; + + dev_info(mtu->dev, "IP version 0x%x(%s IP)\n", mtu->hw_version, + mtu->u3_capable ? "U3" : "U2"); + + mtu3_check_params(mtu); + + mtu3_device_reset(mtu); + + ret = mtu3_device_enable(mtu); + if (ret) { + dev_err(mtu->dev, "device enable failed %d\n", ret); + return ret; + } + + ret = mtu3_mem_alloc(mtu); + if (ret) + return -ENOMEM; + + mtu3_regs_init(mtu); + + return 0; +} + +static void mtu3_hw_exit(struct mtu3 *mtu) +{ + mtu3_device_disable(mtu); + mtu3_mem_free(mtu); +} + +/* + * we set 32-bit DMA mask by default, here check whether the controller + * supports 36-bit DMA or not, if it does, set 36-bit DMA mask. + */ +static int mtu3_set_dma_mask(struct mtu3 *mtu) +{ + struct device *dev = mtu->dev; + bool is_36bit = false; + int ret = 0; + u32 value; + + value = mtu3_readl(mtu->mac_base, U3D_MISC_CTRL); + if (value & DMA_ADDR_36BIT) { + is_36bit = true; + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); + /* If set 36-bit DMA mask fails, fall back to 32-bit DMA mask */ + if (ret) { + is_36bit = false; + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + } + } + dev_info(dev, "dma mask: %s bits\n", is_36bit ? "36" : "32"); + + return ret; +} + +int ssusb_gadget_init(struct ssusb_mtk *ssusb) +{ + struct device *dev = ssusb->dev; + struct platform_device *pdev = to_platform_device(dev); + struct mtu3 *mtu = NULL; + int ret = -ENOMEM; + + mtu = devm_kzalloc(dev, sizeof(struct mtu3), GFP_KERNEL); + if (mtu == NULL) + return -ENOMEM; + + mtu->irq = platform_get_irq_byname_optional(pdev, "device"); + if (mtu->irq < 0) { + if (mtu->irq == -EPROBE_DEFER) + return mtu->irq; + + /* for backward compatibility */ + mtu->irq = platform_get_irq(pdev, 0); + if (mtu->irq < 0) + return mtu->irq; + } + dev_info(dev, "irq %d\n", mtu->irq); + + mtu->mac_base = devm_platform_ioremap_resource_byname(pdev, "mac"); + if (IS_ERR(mtu->mac_base)) { + dev_err(dev, "error mapping memory for dev mac\n"); + return PTR_ERR(mtu->mac_base); + } + + spin_lock_init(&mtu->lock); + mtu->dev = dev; + mtu->ippc_base = ssusb->ippc_base; + ssusb->mac_base = mtu->mac_base; + ssusb->u3d = mtu; + mtu->ssusb = ssusb; + mtu->max_speed = usb_get_maximum_speed(dev); + + dev_dbg(dev, "mac_base=0x%p, ippc_base=0x%p\n", + mtu->mac_base, mtu->ippc_base); + + ret = mtu3_hw_init(mtu); + if (ret) { + dev_err(dev, "mtu3 hw init failed:%d\n", ret); + return ret; + } + + ret = mtu3_set_dma_mask(mtu); + if (ret) { + dev_err(dev, "mtu3 set dma_mask failed:%d\n", ret); + goto dma_mask_err; + } + + ret = devm_request_threaded_irq(dev, mtu->irq, NULL, mtu3_irq, + IRQF_ONESHOT, dev_name(dev), mtu); + if (ret) { + dev_err(dev, "request irq %d failed!\n", mtu->irq); + goto irq_err; + } + + /* power down device IP for power saving by default */ + mtu3_stop(mtu); + + ret = mtu3_gadget_setup(mtu); + if (ret) { + dev_err(dev, "mtu3 gadget init failed:%d\n", ret); + goto gadget_err; + } + + ssusb_dev_debugfs_init(ssusb); + + dev_dbg(dev, " %s() done...\n", __func__); + + return 0; + +gadget_err: + device_init_wakeup(dev, false); + +dma_mask_err: +irq_err: + mtu3_hw_exit(mtu); + ssusb->u3d = NULL; + dev_err(dev, " %s() fail...\n", __func__); + + return ret; +} + +void ssusb_gadget_exit(struct ssusb_mtk *ssusb) +{ + struct mtu3 *mtu = ssusb->u3d; + + mtu3_gadget_cleanup(mtu); + device_init_wakeup(ssusb->dev, false); + mtu3_hw_exit(mtu); +} + +bool ssusb_gadget_ip_sleep_check(struct ssusb_mtk *ssusb) +{ + struct mtu3 *mtu = ssusb->u3d; + + /* host only, should wait for ip sleep */ + if (!mtu) + return true; + + /* device is started and pullup D+, ip can sleep */ + if (mtu->is_active && mtu->softconnect) + return true; + + /* ip can't sleep if not pullup D+ when support device mode */ + return false; +} + +int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg) +{ + struct mtu3 *mtu = ssusb->u3d; + + if (!mtu->gadget_driver) + return 0; + + /* Prevent runtime suspend when active connection exists */ + if (mtu->connected && PMSG_IS_AUTO(msg)) + return -EBUSY; + + /* Perform soft disconnect for system suspend */ + if (mtu->softconnect && !PMSG_IS_AUTO(msg)) + mtu3_dev_on_off(mtu, 0); + + mtu3_dev_suspend(mtu); + synchronize_irq(mtu->irq); + + return 0; +} + +int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg) +{ + struct mtu3 *mtu = ssusb->u3d; + + if (!mtu->gadget_driver) + return 0; + + mtu3_dev_resume(mtu); + + /* Restore soft connect for system resume */ + if (mtu->softconnect && !PMSG_IS_AUTO(msg)) + mtu3_dev_on_off(mtu, 1); + + return 0; +} diff --git a/drivers/usb/mtu3/mtu3_debug.h b/drivers/usb/mtu3/mtu3_debug.h new file mode 100644 index 000000000..9a36134b3 --- /dev/null +++ b/drivers/usb/mtu3/mtu3_debug.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mtu3_debug.h - debug header + * + * Copyright (C) 2019 MediaTek Inc. + * + * Author: Chunfeng Yun + */ + +#ifndef __MTU3_DEBUG_H__ +#define __MTU3_DEBUG_H__ + +#include + +struct ssusb_mtk; + +#define MTU3_DEBUGFS_NAME_LEN 32 + +struct mtu3_regset { + char name[MTU3_DEBUGFS_NAME_LEN]; + struct debugfs_regset32 regset; +}; + +struct mtu3_file_map { + const char *name; + int (*show)(struct seq_file *s, void *unused); +}; + +#if IS_ENABLED(CONFIG_DEBUG_FS) +void ssusb_dev_debugfs_init(struct ssusb_mtk *ssusb); +void ssusb_dr_debugfs_init(struct ssusb_mtk *ssusb); +void ssusb_debugfs_create_root(struct ssusb_mtk *ssusb); +void ssusb_debugfs_remove_root(struct ssusb_mtk *ssusb); + +#else +static inline void ssusb_dev_debugfs_init(struct ssusb_mtk *ssusb) {} +static inline void ssusb_dr_debugfs_init(struct ssusb_mtk *ssusb) {} +static inline void ssusb_debugfs_create_root(struct ssusb_mtk *ssusb) {} +static inline void ssusb_debugfs_remove_root(struct ssusb_mtk *ssusb) {} + +#endif /* CONFIG_DEBUG_FS */ + +#if IS_ENABLED(CONFIG_TRACING) +void mtu3_dbg_trace(struct device *dev, const char *fmt, ...); + +#else +static inline void mtu3_dbg_trace(struct device *dev, const char *fmt, ...) {} + +#endif /* CONFIG_TRACING */ + +#endif /* __MTU3_DEBUG_H__ */ diff --git a/drivers/usb/mtu3/mtu3_debugfs.c b/drivers/usb/mtu3/mtu3_debugfs.c new file mode 100644 index 000000000..c003049ba --- /dev/null +++ b/drivers/usb/mtu3/mtu3_debugfs.c @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mtu3_debugfs.c - debugfs interface + * + * Copyright (C) 2019 MediaTek Inc. + * + * Author: Chunfeng Yun + */ + +#include +#include + +#include "mtu3.h" +#include "mtu3_dr.h" +#include "mtu3_debug.h" + +#define dump_register(nm) \ +{ \ + .name = __stringify(nm), \ + .offset = U3D_ ##nm, \ +} + +#define dump_prb_reg(nm, os) \ +{ \ + .name = nm, \ + .offset = os, \ +} + +static const struct debugfs_reg32 mtu3_ippc_regs[] = { + dump_register(SSUSB_IP_PW_CTRL0), + dump_register(SSUSB_IP_PW_CTRL1), + dump_register(SSUSB_IP_PW_CTRL2), + dump_register(SSUSB_IP_PW_CTRL3), + dump_register(SSUSB_IP_PW_STS1), + dump_register(SSUSB_OTG_STS), + dump_register(SSUSB_IP_XHCI_CAP), + dump_register(SSUSB_IP_DEV_CAP), + dump_register(SSUSB_U3_CTRL_0P), + dump_register(SSUSB_U2_CTRL_0P), + dump_register(SSUSB_HW_ID), + dump_register(SSUSB_HW_SUB_ID), + dump_register(SSUSB_IP_SPARE0), +}; + +static const struct debugfs_reg32 mtu3_dev_regs[] = { + dump_register(LV1ISR), + dump_register(LV1IER), + dump_register(EPISR), + dump_register(EPIER), + dump_register(EP0CSR), + dump_register(RXCOUNT0), + dump_register(QISAR0), + dump_register(QIER0), + dump_register(QISAR1), + dump_register(QIER1), + dump_register(CAP_EPNTXFFSZ), + dump_register(CAP_EPNRXFFSZ), + dump_register(CAP_EPINFO), + dump_register(MISC_CTRL), +}; + +static const struct debugfs_reg32 mtu3_csr_regs[] = { + dump_register(DEVICE_CONF), + dump_register(DEV_LINK_INTR_ENABLE), + dump_register(DEV_LINK_INTR), + dump_register(LTSSM_CTRL), + dump_register(USB3_CONFIG), + dump_register(LINK_STATE_MACHINE), + dump_register(LTSSM_INTR_ENABLE), + dump_register(LTSSM_INTR), + dump_register(U3U2_SWITCH_CTRL), + dump_register(POWER_MANAGEMENT), + dump_register(DEVICE_CONTROL), + dump_register(COMMON_USB_INTR_ENABLE), + dump_register(COMMON_USB_INTR), + dump_register(USB20_MISC_CONTROL), + dump_register(USB20_OPSTATE), +}; + +static int mtu3_link_state_show(struct seq_file *sf, void *unused) +{ + struct mtu3 *mtu = sf->private; + void __iomem *mbase = mtu->mac_base; + + seq_printf(sf, "opstate: %#x, ltssm: %#x\n", + mtu3_readl(mbase, U3D_USB20_OPSTATE), + LTSSM_STATE(mtu3_readl(mbase, U3D_LINK_STATE_MACHINE))); + + return 0; +} + +static int mtu3_ep_used_show(struct seq_file *sf, void *unused) +{ + struct mtu3 *mtu = sf->private; + struct mtu3_ep *mep; + unsigned long flags; + int used = 0; + int i; + + spin_lock_irqsave(&mtu->lock, flags); + + for (i = 0; i < mtu->num_eps; i++) { + mep = mtu->in_eps + i; + if (mep->flags & MTU3_EP_ENABLED) { + seq_printf(sf, "%s - type: %s\n", mep->name, usb_ep_type_string(mep->type)); + used++; + } + + mep = mtu->out_eps + i; + if (mep->flags & MTU3_EP_ENABLED) { + seq_printf(sf, "%s - type: %s\n", mep->name, usb_ep_type_string(mep->type)); + used++; + } + } + seq_printf(sf, "total used: %d eps\n", used); + + spin_unlock_irqrestore(&mtu->lock, flags); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(mtu3_link_state); +DEFINE_SHOW_ATTRIBUTE(mtu3_ep_used); + +static void mtu3_debugfs_regset(struct mtu3 *mtu, void __iomem *base, + const struct debugfs_reg32 *regs, size_t nregs, + const char *name, struct dentry *parent) +{ + struct debugfs_regset32 *regset; + struct mtu3_regset *mregs; + + mregs = devm_kzalloc(mtu->dev, sizeof(*mregs), GFP_KERNEL); + if (!mregs) + return; + + sprintf(mregs->name, "%s", name); + regset = &mregs->regset; + regset->regs = regs; + regset->nregs = nregs; + regset->base = base; + + debugfs_create_regset32(mregs->name, 0444, parent, regset); +} + +static void mtu3_debugfs_ep_regset(struct mtu3 *mtu, struct mtu3_ep *mep, + struct dentry *parent) +{ + struct debugfs_reg32 *regs; + int epnum = mep->epnum; + int in = mep->is_in; + + regs = devm_kcalloc(mtu->dev, 7, sizeof(*regs), GFP_KERNEL); + if (!regs) + return; + + regs[0].name = in ? "TCR0" : "RCR0"; + regs[0].offset = in ? MU3D_EP_TXCR0(epnum) : MU3D_EP_RXCR0(epnum); + regs[1].name = in ? "TCR1" : "RCR1"; + regs[1].offset = in ? MU3D_EP_TXCR1(epnum) : MU3D_EP_RXCR1(epnum); + regs[2].name = in ? "TCR2" : "RCR2"; + regs[2].offset = in ? MU3D_EP_TXCR2(epnum) : MU3D_EP_RXCR2(epnum); + regs[3].name = in ? "TQHIAR" : "RQHIAR"; + regs[3].offset = in ? USB_QMU_TQHIAR(epnum) : USB_QMU_RQHIAR(epnum); + regs[4].name = in ? "TQCSR" : "RQCSR"; + regs[4].offset = in ? USB_QMU_TQCSR(epnum) : USB_QMU_RQCSR(epnum); + regs[5].name = in ? "TQSAR" : "RQSAR"; + regs[5].offset = in ? USB_QMU_TQSAR(epnum) : USB_QMU_RQSAR(epnum); + regs[6].name = in ? "TQCPR" : "RQCPR"; + regs[6].offset = in ? USB_QMU_TQCPR(epnum) : USB_QMU_RQCPR(epnum); + + mtu3_debugfs_regset(mtu, mtu->mac_base, regs, 7, "ep-regs", parent); +} + +static int mtu3_ep_info_show(struct seq_file *sf, void *unused) +{ + struct mtu3_ep *mep = sf->private; + struct mtu3 *mtu = mep->mtu; + unsigned long flags; + + spin_lock_irqsave(&mtu->lock, flags); + seq_printf(sf, "ep - type:%s, maxp:%d, slot:%d, flags:%x\n", + usb_ep_type_string(mep->type), mep->maxp, mep->slot, mep->flags); + spin_unlock_irqrestore(&mtu->lock, flags); + + return 0; +} + +static int mtu3_fifo_show(struct seq_file *sf, void *unused) +{ + struct mtu3_ep *mep = sf->private; + struct mtu3 *mtu = mep->mtu; + unsigned long flags; + + spin_lock_irqsave(&mtu->lock, flags); + seq_printf(sf, "fifo - seg_size:%d, addr:%d, size:%d\n", + mep->fifo_seg_size, mep->fifo_addr, mep->fifo_size); + spin_unlock_irqrestore(&mtu->lock, flags); + + return 0; +} + +static int mtu3_qmu_ring_show(struct seq_file *sf, void *unused) +{ + struct mtu3_ep *mep = sf->private; + struct mtu3 *mtu = mep->mtu; + struct mtu3_gpd_ring *ring; + unsigned long flags; + + ring = &mep->gpd_ring; + spin_lock_irqsave(&mtu->lock, flags); + seq_printf(sf, + "qmu-ring - dma:%pad, start:%p, end:%p, enq:%p, dep:%p\n", + &ring->dma, ring->start, ring->end, + ring->enqueue, ring->dequeue); + spin_unlock_irqrestore(&mtu->lock, flags); + + return 0; +} + +static int mtu3_qmu_gpd_show(struct seq_file *sf, void *unused) +{ + struct mtu3_ep *mep = sf->private; + struct mtu3 *mtu = mep->mtu; + struct mtu3_gpd_ring *ring; + struct qmu_gpd *gpd; + dma_addr_t dma; + unsigned long flags; + int i; + + spin_lock_irqsave(&mtu->lock, flags); + ring = &mep->gpd_ring; + gpd = ring->start; + if (!gpd || !(mep->flags & MTU3_EP_ENABLED)) { + seq_puts(sf, "empty!\n"); + goto out; + } + + for (i = 0; i < MAX_GPD_NUM; i++, gpd++) { + dma = ring->dma + i * sizeof(*gpd); + seq_printf(sf, "gpd.%03d -> %pad, %p: %08x %08x %08x %08x\n", + i, &dma, gpd, gpd->dw0_info, gpd->next_gpd, + gpd->buffer, gpd->dw3_info); + } + +out: + spin_unlock_irqrestore(&mtu->lock, flags); + + return 0; +} + +static const struct mtu3_file_map mtu3_ep_files[] = { + {"ep-info", mtu3_ep_info_show, }, + {"fifo", mtu3_fifo_show, }, + {"qmu-ring", mtu3_qmu_ring_show, }, + {"qmu-gpd", mtu3_qmu_gpd_show, }, +}; + +static int mtu3_ep_open(struct inode *inode, struct file *file) +{ + const struct mtu3_file_map *f_map = debugfs_get_aux(file); + + return single_open(file, f_map->show, inode->i_private); +} + +static const struct file_operations mtu3_ep_fops = { + .open = mtu3_ep_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct debugfs_reg32 mtu3_prb_regs[] = { + dump_prb_reg("enable", U3D_SSUSB_PRB_CTRL0), + dump_prb_reg("byte-sell", U3D_SSUSB_PRB_CTRL1), + dump_prb_reg("byte-selh", U3D_SSUSB_PRB_CTRL2), + dump_prb_reg("module-sel", U3D_SSUSB_PRB_CTRL3), + dump_prb_reg("sw-out", U3D_SSUSB_PRB_CTRL4), + dump_prb_reg("data", U3D_SSUSB_PRB_CTRL5), +}; + +static int mtu3_probe_show(struct seq_file *sf, void *unused) +{ + struct mtu3 *mtu = sf->private; + const struct debugfs_reg32 *regs = debugfs_get_aux(sf->file); + + seq_printf(sf, "0x%04x - 0x%08x\n", (u32)regs->offset, + mtu3_readl(mtu->ippc_base, (u32)regs->offset)); + + return 0; +} + +static int mtu3_probe_open(struct inode *inode, struct file *file) +{ + return single_open(file, mtu3_probe_show, inode->i_private); +} + +static ssize_t mtu3_probe_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *sf = file->private_data; + struct mtu3 *mtu = sf->private; + const struct debugfs_reg32 *regs = debugfs_get_aux(file); + char buf[32]; + u32 val; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (kstrtou32(buf, 0, &val)) + return -EINVAL; + + mtu3_writel(mtu->ippc_base, (u32)regs->offset, val); + + return count; +} + +static const struct file_operations mtu3_probe_fops = { + .open = mtu3_probe_open, + .write = mtu3_probe_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void mtu3_debugfs_create_prb_files(struct mtu3 *mtu) +{ + struct ssusb_mtk *ssusb = mtu->ssusb; + const struct debugfs_reg32 *regs; + struct dentry *dir_prb; + int i; + + dir_prb = debugfs_create_dir("probe", ssusb->dbgfs_root); + + for (i = 0; i < ARRAY_SIZE(mtu3_prb_regs); i++) { + regs = &mtu3_prb_regs[i]; + debugfs_create_file_aux(regs->name, 0644, dir_prb, + mtu, regs, &mtu3_probe_fops); + } + + mtu3_debugfs_regset(mtu, mtu->ippc_base, mtu3_prb_regs, + ARRAY_SIZE(mtu3_prb_regs), "regs", dir_prb); +} + +static void mtu3_debugfs_create_ep_dir(struct mtu3_ep *mep, + struct dentry *parent) +{ + const struct mtu3_file_map *files; + struct dentry *dir_ep; + int i; + + dir_ep = debugfs_create_dir(mep->name, parent); + mtu3_debugfs_ep_regset(mep->mtu, mep, dir_ep); + + for (i = 0; i < ARRAY_SIZE(mtu3_ep_files); i++) { + files = &mtu3_ep_files[i]; + + debugfs_create_file_aux(files->name, 0444, dir_ep, + mep, files, &mtu3_ep_fops); + } +} + +static void mtu3_debugfs_create_ep_dirs(struct mtu3 *mtu) +{ + struct ssusb_mtk *ssusb = mtu->ssusb; + struct dentry *dir_eps; + int i; + + dir_eps = debugfs_create_dir("eps", ssusb->dbgfs_root); + + for (i = 1; i < mtu->num_eps; i++) { + mtu3_debugfs_create_ep_dir(mtu->in_eps + i, dir_eps); + mtu3_debugfs_create_ep_dir(mtu->out_eps + i, dir_eps); + } +} + +void ssusb_dev_debugfs_init(struct ssusb_mtk *ssusb) +{ + struct mtu3 *mtu = ssusb->u3d; + struct dentry *dir_regs; + + dir_regs = debugfs_create_dir("regs", ssusb->dbgfs_root); + + mtu3_debugfs_regset(mtu, mtu->ippc_base, + mtu3_ippc_regs, ARRAY_SIZE(mtu3_ippc_regs), + "reg-ippc", dir_regs); + + mtu3_debugfs_regset(mtu, mtu->mac_base, + mtu3_dev_regs, ARRAY_SIZE(mtu3_dev_regs), + "reg-dev", dir_regs); + + mtu3_debugfs_regset(mtu, mtu->mac_base, + mtu3_csr_regs, ARRAY_SIZE(mtu3_csr_regs), + "reg-csr", dir_regs); + + mtu3_debugfs_create_ep_dirs(mtu); + + mtu3_debugfs_create_prb_files(mtu); + + debugfs_create_file("link-state", 0444, ssusb->dbgfs_root, + mtu, &mtu3_link_state_fops); + debugfs_create_file("ep-used", 0444, ssusb->dbgfs_root, + mtu, &mtu3_ep_used_fops); +} + +static int ssusb_mode_show(struct seq_file *sf, void *unused) +{ + struct ssusb_mtk *ssusb = sf->private; + + seq_printf(sf, "current mode: %s(%s drd)\n(echo device/host)\n", + ssusb->is_host ? "host" : "device", + ssusb->otg_switch.manual_drd_enabled ? "manual" : "auto"); + + return 0; +} + +static int ssusb_mode_open(struct inode *inode, struct file *file) +{ + return single_open(file, ssusb_mode_show, inode->i_private); +} + +static ssize_t ssusb_mode_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *sf = file->private_data; + struct ssusb_mtk *ssusb = sf->private; + char buf[16]; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "host", 4) && !ssusb->is_host) { + ssusb_mode_switch(ssusb, 1); + } else if (!strncmp(buf, "device", 6) && ssusb->is_host) { + ssusb_mode_switch(ssusb, 0); + } else { + dev_err(ssusb->dev, "wrong or duplicated setting\n"); + return -EINVAL; + } + + return count; +} + +static const struct file_operations ssusb_mode_fops = { + .open = ssusb_mode_open, + .write = ssusb_mode_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int ssusb_vbus_show(struct seq_file *sf, void *unused) +{ + struct ssusb_mtk *ssusb = sf->private; + struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; + + seq_printf(sf, "vbus state: %s\n(echo on/off)\n", + str_on_off(regulator_is_enabled(otg_sx->vbus))); + + return 0; +} + +static int ssusb_vbus_open(struct inode *inode, struct file *file) +{ + return single_open(file, ssusb_vbus_show, inode->i_private); +} + +static ssize_t ssusb_vbus_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *sf = file->private_data; + struct ssusb_mtk *ssusb = sf->private; + struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; + char buf[16]; + bool enable; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (kstrtobool(buf, &enable)) { + dev_err(ssusb->dev, "wrong setting\n"); + return -EINVAL; + } + + ssusb_set_vbus(otg_sx, enable); + + return count; +} + +static const struct file_operations ssusb_vbus_fops = { + .open = ssusb_vbus_open, + .write = ssusb_vbus_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +void ssusb_dr_debugfs_init(struct ssusb_mtk *ssusb) +{ + struct dentry *root = ssusb->dbgfs_root; + + debugfs_create_file("mode", 0644, root, ssusb, &ssusb_mode_fops); + debugfs_create_file("vbus", 0644, root, ssusb, &ssusb_vbus_fops); +} + +void ssusb_debugfs_create_root(struct ssusb_mtk *ssusb) +{ + ssusb->dbgfs_root = + debugfs_create_dir(dev_name(ssusb->dev), usb_debug_root); +} + +void ssusb_debugfs_remove_root(struct ssusb_mtk *ssusb) +{ + debugfs_remove_recursive(ssusb->dbgfs_root); + ssusb->dbgfs_root = NULL; +} diff --git a/drivers/usb/mtu3/mtu3_dr.c b/drivers/usb/mtu3/mtu3_dr.c new file mode 100644 index 000000000..ffa5b9401 --- /dev/null +++ b/drivers/usb/mtu3/mtu3_dr.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mtu3_dr.c - dual role switch and host glue layer + * + * Copyright (C) 2016 MediaTek Inc. + * + * Author: Chunfeng Yun + */ + +#include +#include "mtu3.h" +#include "mtu3_dr.h" +#include "mtu3_debug.h" + +#define USB2_PORT 2 +#define USB3_PORT 3 + +static inline struct ssusb_mtk *otg_sx_to_ssusb(struct otg_switch_mtk *otg_sx) +{ + return container_of(otg_sx, struct ssusb_mtk, otg_switch); +} + +static void toggle_opstate(struct ssusb_mtk *ssusb) +{ + mtu3_setbits(ssusb->mac_base, U3D_DEVICE_CONTROL, DC_SESSION); + mtu3_setbits(ssusb->mac_base, U3D_POWER_MANAGEMENT, SOFT_CONN); +} + +/* only port0 supports dual-role mode */ +static int ssusb_port0_switch(struct ssusb_mtk *ssusb, + int version, bool tohost) +{ + void __iomem *ibase = ssusb->ippc_base; + u32 value; + + dev_dbg(ssusb->dev, "%s (switch u%d port0 to %s)\n", __func__, + version, tohost ? "host" : "device"); + + if (version == USB2_PORT) { + /* 1. power off and disable u2 port0 */ + value = mtu3_readl(ibase, SSUSB_U2_CTRL(0)); + value |= SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS; + mtu3_writel(ibase, SSUSB_U2_CTRL(0), value); + + /* 2. power on, enable u2 port0 and select its mode */ + value = mtu3_readl(ibase, SSUSB_U2_CTRL(0)); + value &= ~(SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS); + value = tohost ? (value | SSUSB_U2_PORT_HOST_SEL) : + (value & (~SSUSB_U2_PORT_HOST_SEL)); + mtu3_writel(ibase, SSUSB_U2_CTRL(0), value); + } else { + /* 1. power off and disable u3 port0 */ + value = mtu3_readl(ibase, SSUSB_U3_CTRL(0)); + value |= SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS; + mtu3_writel(ibase, SSUSB_U3_CTRL(0), value); + + /* 2. power on, enable u3 port0 and select its mode */ + value = mtu3_readl(ibase, SSUSB_U3_CTRL(0)); + value &= ~(SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS); + value = tohost ? (value | SSUSB_U3_PORT_HOST_SEL) : + (value & (~SSUSB_U3_PORT_HOST_SEL)); + mtu3_writel(ibase, SSUSB_U3_CTRL(0), value); + } + + return 0; +} + +static void switch_port_to_host(struct ssusb_mtk *ssusb) +{ + u32 check_clk = 0; + + dev_dbg(ssusb->dev, "%s\n", __func__); + + ssusb_port0_switch(ssusb, USB2_PORT, true); + + if (ssusb->otg_switch.is_u3_drd) { + ssusb_port0_switch(ssusb, USB3_PORT, true); + check_clk = SSUSB_U3_MAC_RST_B_STS; + } + + ssusb_check_clocks(ssusb, check_clk); + + /* after all clocks are stable */ + toggle_opstate(ssusb); +} + +static void switch_port_to_device(struct ssusb_mtk *ssusb) +{ + u32 check_clk = 0; + + dev_dbg(ssusb->dev, "%s\n", __func__); + + ssusb_port0_switch(ssusb, USB2_PORT, false); + + if (ssusb->otg_switch.is_u3_drd) { + ssusb_port0_switch(ssusb, USB3_PORT, false); + check_clk = SSUSB_U3_MAC_RST_B_STS; + } + + ssusb_check_clocks(ssusb, check_clk); +} + +int ssusb_set_vbus(struct otg_switch_mtk *otg_sx, int is_on) +{ + struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); + struct regulator *vbus = otg_sx->vbus; + int ret; + + /* vbus is optional */ + if (!vbus) + return 0; + + dev_dbg(ssusb->dev, "%s: turn %s\n", __func__, str_on_off(is_on)); + + if (is_on) { + ret = regulator_enable(vbus); + if (ret) { + dev_err(ssusb->dev, "vbus regulator enable failed\n"); + return ret; + } + } else { + regulator_disable(vbus); + } + + return 0; +} + +static void ssusb_mode_sw_work(struct work_struct *work) +{ + struct otg_switch_mtk *otg_sx = + container_of(work, struct otg_switch_mtk, dr_work); + struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); + struct mtu3 *mtu = ssusb->u3d; + enum usb_role desired_role = otg_sx->desired_role; + enum usb_role current_role; + + current_role = ssusb->is_host ? USB_ROLE_HOST : USB_ROLE_DEVICE; + + if (desired_role == USB_ROLE_NONE) { + /* the default mode is host as probe does */ + desired_role = USB_ROLE_HOST; + if (otg_sx->default_role == USB_ROLE_DEVICE) + desired_role = USB_ROLE_DEVICE; + } + + if (current_role == desired_role) + return; + + dev_dbg(ssusb->dev, "set role : %s\n", usb_role_string(desired_role)); + mtu3_dbg_trace(ssusb->dev, "set role : %s", usb_role_string(desired_role)); + pm_runtime_get_sync(ssusb->dev); + + switch (desired_role) { + case USB_ROLE_HOST: + ssusb_set_force_mode(ssusb, MTU3_DR_FORCE_HOST); + mtu3_stop(mtu); + switch_port_to_host(ssusb); + ssusb_set_vbus(otg_sx, 1); + ssusb->is_host = true; + break; + case USB_ROLE_DEVICE: + ssusb_set_force_mode(ssusb, MTU3_DR_FORCE_DEVICE); + ssusb->is_host = false; + ssusb_set_vbus(otg_sx, 0); + switch_port_to_device(ssusb); + mtu3_start(mtu); + break; + case USB_ROLE_NONE: + default: + dev_err(ssusb->dev, "invalid role\n"); + } + pm_runtime_put(ssusb->dev); +} + +static void ssusb_set_mode(struct otg_switch_mtk *otg_sx, enum usb_role role) +{ + struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); + + if (ssusb->dr_mode != USB_DR_MODE_OTG) + return; + + otg_sx->desired_role = role; + queue_work(system_freezable_wq, &otg_sx->dr_work); +} + +static int ssusb_id_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct otg_switch_mtk *otg_sx = + container_of(nb, struct otg_switch_mtk, id_nb); + + ssusb_set_mode(otg_sx, event ? USB_ROLE_HOST : USB_ROLE_DEVICE); + + return NOTIFY_DONE; +} + +static int ssusb_extcon_register(struct otg_switch_mtk *otg_sx) +{ + struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); + struct extcon_dev *edev = otg_sx->edev; + int ret; + + /* extcon is optional */ + if (!edev) + return 0; + + otg_sx->id_nb.notifier_call = ssusb_id_notifier; + ret = devm_extcon_register_notifier(ssusb->dev, edev, EXTCON_USB_HOST, + &otg_sx->id_nb); + if (ret < 0) { + dev_err(ssusb->dev, "failed to register notifier for USB-HOST\n"); + return ret; + } + + ret = extcon_get_state(edev, EXTCON_USB_HOST); + dev_dbg(ssusb->dev, "EXTCON_USB_HOST: %d\n", ret); + + /* default as host, switch to device mode if needed */ + if (!ret) + ssusb_set_mode(otg_sx, USB_ROLE_DEVICE); + + return 0; +} + +/* + * We provide an interface via debugfs to switch between host and device modes + * depending on user input. + * This is useful in special cases, such as uses TYPE-A receptacle but also + * wants to support dual-role mode. + */ +void ssusb_mode_switch(struct ssusb_mtk *ssusb, int to_host) +{ + struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; + + ssusb_set_mode(otg_sx, to_host ? USB_ROLE_HOST : USB_ROLE_DEVICE); +} + +void ssusb_set_force_mode(struct ssusb_mtk *ssusb, + enum mtu3_dr_force_mode mode) +{ + u32 value; + + value = mtu3_readl(ssusb->ippc_base, SSUSB_U2_CTRL(0)); + switch (mode) { + case MTU3_DR_FORCE_DEVICE: + value |= SSUSB_U2_PORT_FORCE_IDDIG | SSUSB_U2_PORT_RG_IDDIG; + break; + case MTU3_DR_FORCE_HOST: + value |= SSUSB_U2_PORT_FORCE_IDDIG; + value &= ~SSUSB_U2_PORT_RG_IDDIG; + break; + case MTU3_DR_FORCE_NONE: + value &= ~(SSUSB_U2_PORT_FORCE_IDDIG | SSUSB_U2_PORT_RG_IDDIG); + break; + default: + return; + } + mtu3_writel(ssusb->ippc_base, SSUSB_U2_CTRL(0), value); +} + +static int ssusb_role_sw_set(struct usb_role_switch *sw, enum usb_role role) +{ + struct ssusb_mtk *ssusb = usb_role_switch_get_drvdata(sw); + struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; + + ssusb_set_mode(otg_sx, role); + + return 0; +} + +static enum usb_role ssusb_role_sw_get(struct usb_role_switch *sw) +{ + struct ssusb_mtk *ssusb = usb_role_switch_get_drvdata(sw); + + return ssusb->is_host ? USB_ROLE_HOST : USB_ROLE_DEVICE; +} + +static int ssusb_role_sw_register(struct otg_switch_mtk *otg_sx) +{ + struct usb_role_switch_desc role_sx_desc = { 0 }; + struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); + struct device *dev = ssusb->dev; + enum usb_dr_mode mode; + + if (!otg_sx->role_sw_used) + return 0; + + mode = usb_get_role_switch_default_mode(dev); + if (mode == USB_DR_MODE_PERIPHERAL) + otg_sx->default_role = USB_ROLE_DEVICE; + else + otg_sx->default_role = USB_ROLE_HOST; + + role_sx_desc.set = ssusb_role_sw_set; + role_sx_desc.get = ssusb_role_sw_get; + role_sx_desc.fwnode = dev_fwnode(dev); + role_sx_desc.driver_data = ssusb; + role_sx_desc.allow_userspace_control = true; + otg_sx->role_sw = usb_role_switch_register(dev, &role_sx_desc); + if (IS_ERR(otg_sx->role_sw)) + return PTR_ERR(otg_sx->role_sw); + + ssusb_set_mode(otg_sx, otg_sx->default_role); + + return 0; +} + +int ssusb_otg_switch_init(struct ssusb_mtk *ssusb) +{ + struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; + int ret = 0; + + INIT_WORK(&otg_sx->dr_work, ssusb_mode_sw_work); + + if (otg_sx->manual_drd_enabled) + ssusb_dr_debugfs_init(ssusb); + else if (otg_sx->role_sw_used) + ret = ssusb_role_sw_register(otg_sx); + else + ret = ssusb_extcon_register(otg_sx); + + return ret; +} + +void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb) +{ + struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; + + cancel_work_sync(&otg_sx->dr_work); + usb_role_switch_unregister(otg_sx->role_sw); +} diff --git a/drivers/usb/mtu3/mtu3_dr.h b/drivers/usb/mtu3/mtu3_dr.h new file mode 100644 index 000000000..e325508bd --- /dev/null +++ b/drivers/usb/mtu3/mtu3_dr.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mtu3_dr.h - dual role switch and host glue layer header + * + * Copyright (C) 2016 MediaTek Inc. + * + * Author: Chunfeng Yun + */ + +#ifndef _MTU3_DR_H_ +#define _MTU3_DR_H_ + +#if IS_ENABLED(CONFIG_USB_MTU3_HOST) || IS_ENABLED(CONFIG_USB_MTU3_DUAL_ROLE) + +int ssusb_host_init(struct ssusb_mtk *ssusb, struct device_node *parent_dn); +void ssusb_host_exit(struct ssusb_mtk *ssusb); +int ssusb_wakeup_of_property_parse(struct ssusb_mtk *ssusb, + struct device_node *dn); +int ssusb_host_resume(struct ssusb_mtk *ssusb, bool p0_skipped); +int ssusb_host_suspend(struct ssusb_mtk *ssusb); +void ssusb_wakeup_set(struct ssusb_mtk *ssusb, bool enable); + +#else + +static inline int ssusb_host_init(struct ssusb_mtk *ssusb, + + struct device_node *parent_dn) +{ + return 0; +} + +static inline void ssusb_host_exit(struct ssusb_mtk *ssusb) +{} + +static inline int ssusb_wakeup_of_property_parse( + struct ssusb_mtk *ssusb, struct device_node *dn) +{ + return 0; +} + +static inline int ssusb_host_resume(struct ssusb_mtk *ssusb, bool p0_skipped) +{ + return 0; +} + +static inline int ssusb_host_suspend(struct ssusb_mtk *ssusb) +{ + return 0; +} + +static inline void ssusb_wakeup_set(struct ssusb_mtk *ssusb, bool enable) +{} + +#endif + + +#if IS_ENABLED(CONFIG_USB_MTU3_GADGET) || IS_ENABLED(CONFIG_USB_MTU3_DUAL_ROLE) +int ssusb_gadget_init(struct ssusb_mtk *ssusb); +void ssusb_gadget_exit(struct ssusb_mtk *ssusb); +int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg); +int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg); +bool ssusb_gadget_ip_sleep_check(struct ssusb_mtk *ssusb); + +#else +static inline int ssusb_gadget_init(struct ssusb_mtk *ssusb) +{ + return 0; +} + +static inline void ssusb_gadget_exit(struct ssusb_mtk *ssusb) +{} + +static inline int +ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg) +{ + return 0; +} + +static inline int +ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg) +{ + return 0; +} + +static inline bool ssusb_gadget_ip_sleep_check(struct ssusb_mtk *ssusb) +{ + return true; +} + +#endif + + +#if IS_ENABLED(CONFIG_USB_MTU3_DUAL_ROLE) +int ssusb_otg_switch_init(struct ssusb_mtk *ssusb); +void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb); +void ssusb_mode_switch(struct ssusb_mtk *ssusb, int to_host); +int ssusb_set_vbus(struct otg_switch_mtk *otg_sx, int is_on); +void ssusb_set_force_mode(struct ssusb_mtk *ssusb, + enum mtu3_dr_force_mode mode); + +#else + +static inline int ssusb_otg_switch_init(struct ssusb_mtk *ssusb) +{ + return 0; +} + +static inline void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb) +{} + +static inline void ssusb_mode_switch(struct ssusb_mtk *ssusb, int to_host) +{} + +static inline int ssusb_set_vbus(struct otg_switch_mtk *otg_sx, int is_on) +{ + return 0; +} + +static inline void +ssusb_set_force_mode(struct ssusb_mtk *ssusb, enum mtu3_dr_force_mode mode) +{} + +#endif + +#endif /* _MTU3_DR_H_ */ diff --git a/drivers/usb/mtu3/mtu3_gadget.c b/drivers/usb/mtu3/mtu3_gadget.c new file mode 100644 index 000000000..f224f2ee3 --- /dev/null +++ b/drivers/usb/mtu3/mtu3_gadget.c @@ -0,0 +1,773 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mtu3_gadget.c - MediaTek usb3 DRD peripheral support + * + * Copyright (C) 2016 MediaTek Inc. + * + * Author: Chunfeng Yun + */ + +#include +#include "mtu3.h" +#include "mtu3_trace.h" + +void mtu3_req_complete(struct mtu3_ep *mep, + struct usb_request *req, int status) +__releases(mep->mtu->lock) +__acquires(mep->mtu->lock) +{ + struct mtu3_request *mreq = to_mtu3_request(req); + struct mtu3 *mtu = mreq->mtu; + + list_del(&mreq->list); + if (req->status == -EINPROGRESS) + req->status = status; + + trace_mtu3_req_complete(mreq); + + /* ep0 makes use of PIO, needn't unmap it */ + if (mep->epnum) + usb_gadget_unmap_request(&mtu->g, req, mep->is_in); + + dev_dbg(mtu->dev, "%s complete req: %p, sts %d, %d/%d\n", + mep->name, req, req->status, req->actual, req->length); + + spin_unlock(&mtu->lock); + usb_gadget_giveback_request(&mep->ep, req); + spin_lock(&mtu->lock); +} + +static void nuke(struct mtu3_ep *mep, const int status) +{ + struct mtu3_request *mreq = NULL; + + if (list_empty(&mep->req_list)) + return; + + dev_dbg(mep->mtu->dev, "abort %s's req: sts %d\n", mep->name, status); + + /* exclude EP0 */ + if (mep->epnum) + mtu3_qmu_flush(mep); + + while (!list_empty(&mep->req_list)) { + mreq = list_first_entry(&mep->req_list, + struct mtu3_request, list); + mtu3_req_complete(mep, &mreq->request, status); + } +} + +static int mtu3_ep_enable(struct mtu3_ep *mep) +{ + const struct usb_endpoint_descriptor *desc; + const struct usb_ss_ep_comp_descriptor *comp_desc; + struct mtu3 *mtu = mep->mtu; + u32 interval = 0; + u32 mult = 0; + u32 burst = 0; + int ret; + + desc = mep->desc; + comp_desc = mep->comp_desc; + mep->type = usb_endpoint_type(desc); + mep->maxp = usb_endpoint_maxp(desc); + + switch (mtu->g.speed) { + case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + if (usb_endpoint_xfer_int(desc) || + usb_endpoint_xfer_isoc(desc)) { + interval = desc->bInterval; + interval = clamp_val(interval, 1, 16); + if (usb_endpoint_xfer_isoc(desc) && comp_desc) + mult = comp_desc->bmAttributes; + } + if (comp_desc) + burst = comp_desc->bMaxBurst; + + break; + case USB_SPEED_HIGH: + if (usb_endpoint_xfer_isoc(desc) || + usb_endpoint_xfer_int(desc)) { + interval = desc->bInterval; + interval = clamp_val(interval, 1, 16); + mult = usb_endpoint_maxp_mult(desc) - 1; + } + break; + case USB_SPEED_FULL: + if (usb_endpoint_xfer_isoc(desc)) + interval = clamp_val(desc->bInterval, 1, 16); + else if (usb_endpoint_xfer_int(desc)) + interval = clamp_val(desc->bInterval, 1, 255); + + break; + default: + break; /*others are ignored */ + } + + dev_dbg(mtu->dev, "%s maxp:%d, interval:%d, burst:%d, mult:%d\n", + __func__, mep->maxp, interval, burst, mult); + + mep->ep.maxpacket = mep->maxp; + mep->ep.desc = desc; + mep->ep.comp_desc = comp_desc; + + /* slot mainly affects bulk/isoc transfer, so ignore int */ + mep->slot = usb_endpoint_xfer_int(desc) ? 0 : mtu->slot; + + ret = mtu3_config_ep(mtu, mep, interval, burst, mult); + if (ret < 0) + return ret; + + ret = mtu3_gpd_ring_alloc(mep); + if (ret < 0) { + mtu3_deconfig_ep(mtu, mep); + return ret; + } + + mtu3_qmu_start(mep); + + return 0; +} + +static int mtu3_ep_disable(struct mtu3_ep *mep) +{ + struct mtu3 *mtu = mep->mtu; + + /* abort all pending requests */ + nuke(mep, -ESHUTDOWN); + mtu3_qmu_stop(mep); + mtu3_deconfig_ep(mtu, mep); + mtu3_gpd_ring_free(mep); + + mep->desc = NULL; + mep->ep.desc = NULL; + mep->comp_desc = NULL; + mep->type = 0; + mep->flags = 0; + + return 0; +} + +static int mtu3_gadget_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct mtu3_ep *mep; + struct mtu3 *mtu; + unsigned long flags; + int ret = -EINVAL; + + if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) { + pr_debug("%s invalid parameters\n", __func__); + return -EINVAL; + } + + if (!desc->wMaxPacketSize) { + pr_debug("%s missing wMaxPacketSize\n", __func__); + return -EINVAL; + } + mep = to_mtu3_ep(ep); + mtu = mep->mtu; + + /* check ep number and direction against endpoint */ + if (usb_endpoint_num(desc) != mep->epnum) + return -EINVAL; + + if (!!usb_endpoint_dir_in(desc) ^ !!mep->is_in) + return -EINVAL; + + dev_dbg(mtu->dev, "%s %s\n", __func__, ep->name); + + if (mep->flags & MTU3_EP_ENABLED) { + dev_WARN_ONCE(mtu->dev, true, "%s is already enabled\n", + mep->name); + return 0; + } + + spin_lock_irqsave(&mtu->lock, flags); + mep->desc = desc; + mep->comp_desc = ep->comp_desc; + + ret = mtu3_ep_enable(mep); + if (ret) + goto error; + + mep->flags = MTU3_EP_ENABLED; + mtu->active_ep++; + +error: + spin_unlock_irqrestore(&mtu->lock, flags); + + dev_dbg(mtu->dev, "%s active_ep=%d\n", __func__, mtu->active_ep); + trace_mtu3_gadget_ep_enable(mep); + + return ret; +} + +static int mtu3_gadget_ep_disable(struct usb_ep *ep) +{ + struct mtu3_ep *mep = to_mtu3_ep(ep); + struct mtu3 *mtu = mep->mtu; + unsigned long flags; + + dev_dbg(mtu->dev, "%s %s\n", __func__, mep->name); + trace_mtu3_gadget_ep_disable(mep); + + if (!(mep->flags & MTU3_EP_ENABLED)) { + dev_warn(mtu->dev, "%s is already disabled\n", mep->name); + return 0; + } + + spin_lock_irqsave(&mtu->lock, flags); + mtu3_ep_disable(mep); + mep->flags = 0; + mtu->active_ep--; + spin_unlock_irqrestore(&(mtu->lock), flags); + + dev_dbg(mtu->dev, "%s active_ep=%d, mtu3 is_active=%d\n", + __func__, mtu->active_ep, mtu->is_active); + + return 0; +} + +struct usb_request *mtu3_alloc_request(struct usb_ep *ep, gfp_t gfp_flags) +{ + struct mtu3_ep *mep = to_mtu3_ep(ep); + struct mtu3_request *mreq; + + mreq = kzalloc_obj(*mreq, gfp_flags); + if (!mreq) + return NULL; + + mreq->request.dma = DMA_ADDR_INVALID; + mreq->epnum = mep->epnum; + mreq->mep = mep; + INIT_LIST_HEAD(&mreq->list); + trace_mtu3_alloc_request(mreq); + + return &mreq->request; +} + +void mtu3_free_request(struct usb_ep *ep, struct usb_request *req) +{ + struct mtu3_request *mreq = to_mtu3_request(req); + + trace_mtu3_free_request(mreq); + kfree(mreq); +} + +static int mtu3_gadget_queue(struct usb_ep *ep, + struct usb_request *req, gfp_t gfp_flags) +{ + struct mtu3_ep *mep = to_mtu3_ep(ep); + struct mtu3_request *mreq = to_mtu3_request(req); + struct mtu3 *mtu = mep->mtu; + unsigned long flags; + int ret = 0; + + if (!req->buf) + return -ENODATA; + + if (mreq->mep != mep) + return -EINVAL; + + dev_dbg(mtu->dev, "%s %s EP%d(%s), req=%p, maxp=%d, len#%d\n", + __func__, mep->is_in ? "TX" : "RX", mreq->epnum, ep->name, + mreq, ep->maxpacket, mreq->request.length); + + if (req->length > GPD_BUF_SIZE || + (mtu->gen2cp && req->length > GPD_BUF_SIZE_EL)) { + dev_warn(mtu->dev, + "req length > supported MAX:%d requested:%d\n", + mtu->gen2cp ? GPD_BUF_SIZE_EL : GPD_BUF_SIZE, + req->length); + return -EOPNOTSUPP; + } + + /* don't queue if the ep is down */ + if (!mep->desc) { + dev_dbg(mtu->dev, "req=%p queued to %s while it's disabled\n", + req, ep->name); + return -ESHUTDOWN; + } + + mreq->mtu = mtu; + mreq->request.actual = 0; + mreq->request.status = -EINPROGRESS; + + ret = usb_gadget_map_request(&mtu->g, req, mep->is_in); + if (ret) { + dev_err(mtu->dev, "dma mapping failed\n"); + return ret; + } + + spin_lock_irqsave(&mtu->lock, flags); + + if (mtu3_prepare_transfer(mep)) { + ret = -EAGAIN; + usb_gadget_unmap_request(&mtu->g, req, mep->is_in); + goto error; + } + + list_add_tail(&mreq->list, &mep->req_list); + mtu3_insert_gpd(mep, mreq); + mtu3_qmu_resume(mep); + +error: + spin_unlock_irqrestore(&mtu->lock, flags); + trace_mtu3_gadget_queue(mreq); + + return ret; +} + +static int mtu3_gadget_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + struct mtu3_ep *mep = to_mtu3_ep(ep); + struct mtu3_request *mreq = to_mtu3_request(req); + struct mtu3_request *r; + struct mtu3 *mtu = mep->mtu; + unsigned long flags; + int ret = 0; + + if (mreq->mep != mep) + return -EINVAL; + + dev_dbg(mtu->dev, "%s : req=%p\n", __func__, req); + trace_mtu3_gadget_dequeue(mreq); + + spin_lock_irqsave(&mtu->lock, flags); + + list_for_each_entry(r, &mep->req_list, list) { + if (r == mreq) + break; + } + if (r != mreq) { + dev_dbg(mtu->dev, "req=%p not queued to %s\n", req, ep->name); + ret = -EINVAL; + goto done; + } + + mtu3_qmu_flush(mep); /* REVISIT: set BPS ?? */ + mtu3_req_complete(mep, req, -ECONNRESET); + mtu3_qmu_start(mep); + +done: + spin_unlock_irqrestore(&mtu->lock, flags); + + return ret; +} + +/* + * Set or clear the halt bit of an EP. + * A halted EP won't TX/RX any data but will queue requests. + */ +static int mtu3_gadget_ep_set_halt(struct usb_ep *ep, int value) +{ + struct mtu3_ep *mep = to_mtu3_ep(ep); + struct mtu3 *mtu = mep->mtu; + struct mtu3_request *mreq; + unsigned long flags; + int ret = 0; + + dev_dbg(mtu->dev, "%s : %s...", __func__, ep->name); + + spin_lock_irqsave(&mtu->lock, flags); + + if (mep->type == USB_ENDPOINT_XFER_ISOC) { + ret = -EINVAL; + goto done; + } + + mreq = next_request(mep); + if (value) { + /* + * If there is not request for TX-EP, QMU will not transfer + * data to TX-FIFO, so no need check whether TX-FIFO + * holds bytes or not here + */ + if (mreq) { + dev_dbg(mtu->dev, "req in progress, cannot halt %s\n", + ep->name); + ret = -EAGAIN; + goto done; + } + } else { + mep->flags &= ~MTU3_EP_WEDGE; + } + + dev_dbg(mtu->dev, "%s %s stall\n", ep->name, value ? "set" : "clear"); + + mtu3_ep_stall_set(mep, value); + +done: + spin_unlock_irqrestore(&mtu->lock, flags); + trace_mtu3_gadget_ep_set_halt(mep); + + return ret; +} + +/* Sets the halt feature with the clear requests ignored */ +static int mtu3_gadget_ep_set_wedge(struct usb_ep *ep) +{ + struct mtu3_ep *mep = to_mtu3_ep(ep); + + mep->flags |= MTU3_EP_WEDGE; + + return usb_ep_set_halt(ep); +} + +static const struct usb_ep_ops mtu3_ep_ops = { + .enable = mtu3_gadget_ep_enable, + .disable = mtu3_gadget_ep_disable, + .alloc_request = mtu3_alloc_request, + .free_request = mtu3_free_request, + .queue = mtu3_gadget_queue, + .dequeue = mtu3_gadget_dequeue, + .set_halt = mtu3_gadget_ep_set_halt, + .set_wedge = mtu3_gadget_ep_set_wedge, +}; + +static int mtu3_gadget_get_frame(struct usb_gadget *gadget) +{ + struct mtu3 *mtu = gadget_to_mtu3(gadget); + + return (int)mtu3_readl(mtu->mac_base, U3D_USB20_FRAME_NUM); +} + +static void function_wake_notif(struct mtu3 *mtu, u8 intf) +{ + mtu3_writel(mtu->mac_base, U3D_DEV_NOTIF_0, + TYPE_FUNCTION_WAKE | DEV_NOTIF_VAL_FW(intf)); + mtu3_setbits(mtu->mac_base, U3D_DEV_NOTIF_0, SEND_DEV_NOTIF); +} + +static int mtu3_gadget_wakeup(struct usb_gadget *gadget) +{ + struct mtu3 *mtu = gadget_to_mtu3(gadget); + unsigned long flags; + + dev_dbg(mtu->dev, "%s\n", __func__); + + /* remote wakeup feature is not enabled by host */ + if (!mtu->may_wakeup) + return -EOPNOTSUPP; + + spin_lock_irqsave(&mtu->lock, flags); + if (mtu->g.speed >= USB_SPEED_SUPER) { + /* + * class driver may do function wakeup even UFP is in U0, + * and UX_EXIT only takes effect in U1/U2/U3; + */ + mtu3_setbits(mtu->mac_base, U3D_LINK_POWER_CONTROL, UX_EXIT); + /* + * Assume there's only one function on the composite device + * and enable remote wake for the first interface. + * FIXME if the IAD (interface association descriptor) shows + * there is more than one function. + */ + function_wake_notif(mtu, 0); + } else { + mtu3_setbits(mtu->mac_base, U3D_POWER_MANAGEMENT, RESUME); + spin_unlock_irqrestore(&mtu->lock, flags); + usleep_range(10000, 11000); + spin_lock_irqsave(&mtu->lock, flags); + mtu3_clrbits(mtu->mac_base, U3D_POWER_MANAGEMENT, RESUME); + } + spin_unlock_irqrestore(&mtu->lock, flags); + return 0; +} + +static int mtu3_gadget_set_self_powered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct mtu3 *mtu = gadget_to_mtu3(gadget); + + mtu->is_self_powered = !!is_selfpowered; + return 0; +} + +static int mtu3_gadget_pullup(struct usb_gadget *gadget, int is_on) +{ + struct mtu3 *mtu = gadget_to_mtu3(gadget); + unsigned long flags; + + dev_dbg(mtu->dev, "%s (%s) for %sactive device\n", __func__, + str_on_off(is_on), mtu->is_active ? "" : "in"); + + pm_runtime_get_sync(mtu->dev); + + /* we'd rather not pullup unless the device is active. */ + spin_lock_irqsave(&mtu->lock, flags); + + is_on = !!is_on; + if (!mtu->is_active) { + /* save it for mtu3_start() to process the request */ + mtu->softconnect = is_on; + } else if (is_on != mtu->softconnect) { + mtu->softconnect = is_on; + mtu3_dev_on_off(mtu, is_on); + } + + spin_unlock_irqrestore(&mtu->lock, flags); + pm_runtime_put(mtu->dev); + + return 0; +} + +static int mtu3_gadget_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct mtu3 *mtu = gadget_to_mtu3(gadget); + unsigned long flags; + + if (mtu->gadget_driver) { + dev_err(mtu->dev, "%s is already bound to %s\n", + mtu->g.name, mtu->gadget_driver->driver.name); + return -EBUSY; + } + + dev_dbg(mtu->dev, "bind driver %s\n", driver->function); + pm_runtime_get_sync(mtu->dev); + + spin_lock_irqsave(&mtu->lock, flags); + + mtu->softconnect = 0; + mtu->gadget_driver = driver; + + if (mtu->ssusb->dr_mode == USB_DR_MODE_PERIPHERAL) + mtu3_start(mtu); + + spin_unlock_irqrestore(&mtu->lock, flags); + pm_runtime_put(mtu->dev); + + return 0; +} + +static void stop_activity(struct mtu3 *mtu) +{ + struct usb_gadget_driver *driver = mtu->gadget_driver; + int i; + + /* don't disconnect if it's not connected */ + if (mtu->g.speed == USB_SPEED_UNKNOWN) + driver = NULL; + else + mtu->g.speed = USB_SPEED_UNKNOWN; + + /* deactivate the hardware */ + if (mtu->softconnect) { + mtu->softconnect = 0; + mtu3_dev_on_off(mtu, 0); + } + + /* + * killing any outstanding requests will quiesce the driver; + * then report disconnect + */ + nuke(mtu->ep0, -ESHUTDOWN); + for (i = 1; i < mtu->num_eps; i++) { + nuke(mtu->in_eps + i, -ESHUTDOWN); + nuke(mtu->out_eps + i, -ESHUTDOWN); + } + + if (driver) { + spin_unlock(&mtu->lock); + driver->disconnect(&mtu->g); + spin_lock(&mtu->lock); + } +} + +static int mtu3_gadget_stop(struct usb_gadget *g) +{ + struct mtu3 *mtu = gadget_to_mtu3(g); + unsigned long flags; + + dev_dbg(mtu->dev, "%s\n", __func__); + + spin_lock_irqsave(&mtu->lock, flags); + + stop_activity(mtu); + mtu->gadget_driver = NULL; + + if (mtu->ssusb->dr_mode == USB_DR_MODE_PERIPHERAL) + mtu3_stop(mtu); + + spin_unlock_irqrestore(&mtu->lock, flags); + + synchronize_irq(mtu->irq); + return 0; +} + +static void +mtu3_gadget_set_speed(struct usb_gadget *g, enum usb_device_speed speed) +{ + struct mtu3 *mtu = gadget_to_mtu3(g); + unsigned long flags; + + dev_dbg(mtu->dev, "%s %s\n", __func__, usb_speed_string(speed)); + + spin_lock_irqsave(&mtu->lock, flags); + mtu->speed = speed; + spin_unlock_irqrestore(&mtu->lock, flags); +} + +static void mtu3_gadget_async_callbacks(struct usb_gadget *g, bool enable) +{ + struct mtu3 *mtu = gadget_to_mtu3(g); + unsigned long flags; + + dev_dbg(mtu->dev, "%s %s\n", __func__, enable ? "en" : "dis"); + + spin_lock_irqsave(&mtu->lock, flags); + mtu->async_callbacks = enable; + spin_unlock_irqrestore(&mtu->lock, flags); +} + +static const struct usb_gadget_ops mtu3_gadget_ops = { + .get_frame = mtu3_gadget_get_frame, + .wakeup = mtu3_gadget_wakeup, + .set_selfpowered = mtu3_gadget_set_self_powered, + .pullup = mtu3_gadget_pullup, + .udc_start = mtu3_gadget_start, + .udc_stop = mtu3_gadget_stop, + .udc_set_speed = mtu3_gadget_set_speed, + .udc_async_callbacks = mtu3_gadget_async_callbacks, +}; + +static void mtu3_state_reset(struct mtu3 *mtu) +{ + mtu->address = 0; + mtu->ep0_state = MU3D_EP0_STATE_SETUP; + mtu->may_wakeup = 0; + mtu->u1_enable = 0; + mtu->u2_enable = 0; + mtu->delayed_status = false; + mtu->test_mode = false; +} + +static void init_hw_ep(struct mtu3 *mtu, struct mtu3_ep *mep, + u32 epnum, u32 is_in) +{ + mep->epnum = epnum; + mep->mtu = mtu; + mep->is_in = is_in; + + INIT_LIST_HEAD(&mep->req_list); + + sprintf(mep->name, "ep%d%s", epnum, + !epnum ? "" : (is_in ? "in" : "out")); + + mep->ep.name = mep->name; + INIT_LIST_HEAD(&mep->ep.ep_list); + + /* initialize maxpacket as SS */ + if (!epnum) { + usb_ep_set_maxpacket_limit(&mep->ep, 512); + mep->ep.caps.type_control = true; + mep->ep.ops = &mtu3_ep0_ops; + mtu->g.ep0 = &mep->ep; + } else { + usb_ep_set_maxpacket_limit(&mep->ep, 1024); + mep->ep.caps.type_iso = true; + mep->ep.caps.type_bulk = true; + mep->ep.caps.type_int = true; + mep->ep.ops = &mtu3_ep_ops; + list_add_tail(&mep->ep.ep_list, &mtu->g.ep_list); + } + + dev_dbg(mtu->dev, "%s, name=%s, maxp=%d\n", __func__, mep->ep.name, + mep->ep.maxpacket); + + if (!epnum) { + mep->ep.caps.dir_in = true; + mep->ep.caps.dir_out = true; + } else if (is_in) { + mep->ep.caps.dir_in = true; + } else { + mep->ep.caps.dir_out = true; + } +} + +static void mtu3_gadget_init_eps(struct mtu3 *mtu) +{ + u8 epnum; + + /* initialize endpoint list just once */ + INIT_LIST_HEAD(&(mtu->g.ep_list)); + + dev_dbg(mtu->dev, "%s num_eps(1 for a pair of tx&rx ep)=%d\n", + __func__, mtu->num_eps); + + init_hw_ep(mtu, mtu->ep0, 0, 0); + for (epnum = 1; epnum < mtu->num_eps; epnum++) { + init_hw_ep(mtu, mtu->in_eps + epnum, epnum, 1); + init_hw_ep(mtu, mtu->out_eps + epnum, epnum, 0); + } +} + +int mtu3_gadget_setup(struct mtu3 *mtu) +{ + mtu->g.ops = &mtu3_gadget_ops; + mtu->g.max_speed = mtu->max_speed; + mtu->g.speed = USB_SPEED_UNKNOWN; + mtu->g.sg_supported = 0; + mtu->g.name = MTU3_DRIVER_NAME; + mtu->g.irq = mtu->irq; + mtu->is_active = 0; + mtu->delayed_status = false; + + mtu3_gadget_init_eps(mtu); + + return usb_add_gadget_udc(mtu->dev, &mtu->g); +} + +void mtu3_gadget_cleanup(struct mtu3 *mtu) +{ + usb_del_gadget_udc(&mtu->g); +} + +void mtu3_gadget_resume(struct mtu3 *mtu) +{ + dev_dbg(mtu->dev, "gadget RESUME\n"); + if (mtu->async_callbacks && mtu->gadget_driver && mtu->gadget_driver->resume) { + spin_unlock(&mtu->lock); + mtu->gadget_driver->resume(&mtu->g); + spin_lock(&mtu->lock); + } +} + +/* called when SOF packets stop for 3+ msec or enters U3 */ +void mtu3_gadget_suspend(struct mtu3 *mtu) +{ + dev_dbg(mtu->dev, "gadget SUSPEND\n"); + if (mtu->async_callbacks && mtu->gadget_driver && mtu->gadget_driver->suspend) { + spin_unlock(&mtu->lock); + mtu->gadget_driver->suspend(&mtu->g); + spin_lock(&mtu->lock); + } +} + +/* called when VBUS drops below session threshold, and in other cases */ +void mtu3_gadget_disconnect(struct mtu3 *mtu) +{ + dev_dbg(mtu->dev, "gadget DISCONNECT\n"); + if (mtu->async_callbacks && mtu->gadget_driver && mtu->gadget_driver->disconnect) { + spin_unlock(&mtu->lock); + mtu->gadget_driver->disconnect(&mtu->g); + spin_lock(&mtu->lock); + } + + mtu3_state_reset(mtu); + usb_gadget_set_state(&mtu->g, USB_STATE_NOTATTACHED); +} + +void mtu3_gadget_reset(struct mtu3 *mtu) +{ + dev_dbg(mtu->dev, "gadget RESET\n"); + + /* report disconnect, if we didn't flush EP state */ + if (mtu->g.speed != USB_SPEED_UNKNOWN) + mtu3_gadget_disconnect(mtu); + else + mtu3_state_reset(mtu); +} diff --git a/drivers/usb/mtu3/mtu3_gadget_ep0.c b/drivers/usb/mtu3/mtu3_gadget_ep0.c new file mode 100644 index 000000000..e4fd1bb14 --- /dev/null +++ b/drivers/usb/mtu3/mtu3_gadget_ep0.c @@ -0,0 +1,916 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mtu3_gadget_ep0.c - MediaTek USB3 DRD peripheral driver ep0 handling + * + * Copyright (c) 2016 MediaTek Inc. + * + * Author: Chunfeng.Yun + */ + +#include +#include + +#include "mtu3.h" +#include "mtu3_debug.h" +#include "mtu3_trace.h" + +/* ep0 is always mtu3->in_eps[0] */ +#define next_ep0_request(mtu) next_request((mtu)->ep0) + +/* for high speed test mode; see USB 2.0 spec 7.1.20 */ +static const u8 mtu3_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 */ +}; + +static char *decode_ep0_state(struct mtu3 *mtu) +{ + switch (mtu->ep0_state) { + case MU3D_EP0_STATE_SETUP: + return "SETUP"; + case MU3D_EP0_STATE_TX: + return "IN"; + case MU3D_EP0_STATE_RX: + return "OUT"; + case MU3D_EP0_STATE_TX_END: + return "TX-END"; + case MU3D_EP0_STATE_STALL: + return "STALL"; + default: + return "??"; + } +} + +static void ep0_req_giveback(struct mtu3 *mtu, struct usb_request *req) +{ + mtu3_req_complete(mtu->ep0, req, 0); +} + +static int +forward_to_driver(struct mtu3 *mtu, const struct usb_ctrlrequest *setup) +__releases(mtu->lock) +__acquires(mtu->lock) +{ + int ret; + + if (!mtu->gadget_driver || !mtu->async_callbacks) + return -EOPNOTSUPP; + + spin_unlock(&mtu->lock); + ret = mtu->gadget_driver->setup(&mtu->g, setup); + spin_lock(&mtu->lock); + + dev_dbg(mtu->dev, "%s ret %d\n", __func__, ret); + return ret; +} + +static void ep0_write_fifo(struct mtu3_ep *mep, const u8 *src, u16 len) +{ + void __iomem *fifo = mep->mtu->mac_base + U3D_FIFO0; + u16 index = 0; + + dev_dbg(mep->mtu->dev, "%s: ep%din, len=%d, buf=%p\n", + __func__, mep->epnum, len, src); + + if (len >= 4) { + iowrite32_rep(fifo, src, len >> 2); + index = len & ~0x03; + } + if (len & 0x02) { + writew(*(u16 *)&src[index], fifo); + index += 2; + } + if (len & 0x01) + writeb(src[index], fifo); +} + +static void ep0_read_fifo(struct mtu3_ep *mep, u8 *dst, u16 len) +{ + void __iomem *fifo = mep->mtu->mac_base + U3D_FIFO0; + u32 value; + u16 index = 0; + + dev_dbg(mep->mtu->dev, "%s: ep%dout len=%d buf=%p\n", + __func__, mep->epnum, len, dst); + + if (len >= 4) { + ioread32_rep(fifo, dst, len >> 2); + index = len & ~0x03; + } + if (len & 0x3) { + value = readl(fifo); + memcpy(&dst[index], &value, len & 0x3); + } + +} + +static void ep0_load_test_packet(struct mtu3 *mtu) +{ + /* + * because the length of test packet is less than max packet of HS ep0, + * write it into fifo directly. + */ + ep0_write_fifo(mtu->ep0, mtu3_test_packet, sizeof(mtu3_test_packet)); +} + +/* + * A. send STALL for setup transfer without data stage: + * set SENDSTALL and SETUPPKTRDY at the same time; + * B. send STALL for other cases: + * set SENDSTALL only. + */ +static void ep0_stall_set(struct mtu3_ep *mep0, bool set, u32 pktrdy) +{ + struct mtu3 *mtu = mep0->mtu; + void __iomem *mbase = mtu->mac_base; + u32 csr; + + /* EP0_SENTSTALL is W1C */ + csr = mtu3_readl(mbase, U3D_EP0CSR) & EP0_W1C_BITS; + if (set) + csr |= EP0_SENDSTALL | pktrdy; + else + csr = (csr & ~EP0_SENDSTALL) | EP0_SENTSTALL; + mtu3_writel(mtu->mac_base, U3D_EP0CSR, csr); + + mtu->delayed_status = false; + mtu->ep0_state = MU3D_EP0_STATE_SETUP; + + dev_dbg(mtu->dev, "ep0: %s STALL, ep0_state: %s\n", + set ? "SEND" : "CLEAR", decode_ep0_state(mtu)); +} + +static void ep0_do_status_stage(struct mtu3 *mtu) +{ + void __iomem *mbase = mtu->mac_base; + u32 value; + + value = mtu3_readl(mbase, U3D_EP0CSR) & EP0_W1C_BITS; + mtu3_writel(mbase, U3D_EP0CSR, value | EP0_SETUPPKTRDY | EP0_DATAEND); +} + +static int ep0_queue(struct mtu3_ep *mep0, struct mtu3_request *mreq); + +static void ep0_dummy_complete(struct usb_ep *ep, struct usb_request *req) +{} + +static void ep0_set_sel_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct mtu3_request *mreq; + struct mtu3 *mtu; + struct usb_set_sel_req sel; + + memcpy(&sel, req->buf, sizeof(sel)); + + mreq = to_mtu3_request(req); + mtu = mreq->mtu; + dev_dbg(mtu->dev, "u1sel:%d, u1pel:%d, u2sel:%d, u2pel:%d\n", + sel.u1_sel, sel.u1_pel, sel.u2_sel, sel.u2_pel); +} + +/* queue data stage to handle 6 byte SET_SEL request */ +static int ep0_set_sel(struct mtu3 *mtu, struct usb_ctrlrequest *setup) +{ + int ret; + u16 length = le16_to_cpu(setup->wLength); + + if (unlikely(length != 6)) { + dev_err(mtu->dev, "%s wrong wLength:%d\n", + __func__, length); + return -EINVAL; + } + + mtu->ep0_req.mep = mtu->ep0; + mtu->ep0_req.request.length = 6; + mtu->ep0_req.request.buf = mtu->setup_buf; + mtu->ep0_req.request.complete = ep0_set_sel_complete; + ret = ep0_queue(mtu->ep0, &mtu->ep0_req); + + return ret < 0 ? ret : 1; +} + +static int +ep0_get_status(struct mtu3 *mtu, const struct usb_ctrlrequest *setup) +{ + struct mtu3_ep *mep = NULL; + int handled = 1; + u8 result[2] = {0, 0}; + u8 epnum = 0; + int is_in; + + switch (setup->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + result[0] = mtu->is_self_powered << USB_DEVICE_SELF_POWERED; + result[0] |= mtu->may_wakeup << USB_DEVICE_REMOTE_WAKEUP; + + if (mtu->g.speed >= USB_SPEED_SUPER) { + result[0] |= mtu->u1_enable << USB_DEV_STAT_U1_ENABLED; + result[0] |= mtu->u2_enable << USB_DEV_STAT_U2_ENABLED; + } + + dev_dbg(mtu->dev, "%s result=%x, U1=%x, U2=%x\n", __func__, + result[0], mtu->u1_enable, mtu->u2_enable); + + break; + case USB_RECIP_INTERFACE: + /* status of function remote wakeup, forward request */ + handled = 0; + break; + case USB_RECIP_ENDPOINT: + epnum = (u8) le16_to_cpu(setup->wIndex); + is_in = epnum & USB_DIR_IN; + epnum &= USB_ENDPOINT_NUMBER_MASK; + + if (epnum >= mtu->num_eps) { + handled = -EINVAL; + break; + } + if (!epnum) + break; + + mep = (is_in ? mtu->in_eps : mtu->out_eps) + epnum; + if (!mep->desc) { + handled = -EINVAL; + break; + } + if (mep->flags & MTU3_EP_STALL) + result[0] |= 1 << USB_ENDPOINT_HALT; + + break; + default: + /* class, vendor, etc ... delegate */ + handled = 0; + break; + } + + if (handled > 0) { + int ret; + + /* prepare a data stage for GET_STATUS */ + dev_dbg(mtu->dev, "get_status=%x\n", *(u16 *)result); + memcpy(mtu->setup_buf, result, sizeof(result)); + mtu->ep0_req.mep = mtu->ep0; + mtu->ep0_req.request.length = 2; + mtu->ep0_req.request.buf = &mtu->setup_buf; + mtu->ep0_req.request.complete = ep0_dummy_complete; + ret = ep0_queue(mtu->ep0, &mtu->ep0_req); + if (ret < 0) + handled = ret; + } + return handled; +} + +static int handle_test_mode(struct mtu3 *mtu, struct usb_ctrlrequest *setup) +{ + void __iomem *mbase = mtu->mac_base; + int handled = 1; + u32 value; + + switch (le16_to_cpu(setup->wIndex) >> 8) { + case USB_TEST_J: + dev_dbg(mtu->dev, "USB_TEST_J\n"); + mtu->test_mode_nr = TEST_J_MODE; + break; + case USB_TEST_K: + dev_dbg(mtu->dev, "USB_TEST_K\n"); + mtu->test_mode_nr = TEST_K_MODE; + break; + case USB_TEST_SE0_NAK: + dev_dbg(mtu->dev, "USB_TEST_SE0_NAK\n"); + mtu->test_mode_nr = TEST_SE0_NAK_MODE; + break; + case USB_TEST_PACKET: + dev_dbg(mtu->dev, "USB_TEST_PACKET\n"); + mtu->test_mode_nr = TEST_PACKET_MODE; + break; + default: + handled = -EINVAL; + goto out; + } + + mtu->test_mode = true; + + /* no TX completion interrupt, and need restart platform after test */ + if (mtu->test_mode_nr == TEST_PACKET_MODE) + ep0_load_test_packet(mtu); + + /* send status before entering test mode. */ + ep0_do_status_stage(mtu); + + /* wait for ACK status sent by host */ + readl_poll_timeout_atomic(mbase + U3D_EP0CSR, value, + !(value & EP0_DATAEND), 100, 5000); + + mtu3_writel(mbase, U3D_USB2_TEST_MODE, mtu->test_mode_nr); + + mtu->ep0_state = MU3D_EP0_STATE_SETUP; + +out: + return handled; +} + +static int ep0_handle_feature_dev(struct mtu3 *mtu, + struct usb_ctrlrequest *setup, bool set) +{ + void __iomem *mbase = mtu->mac_base; + int handled = -EINVAL; + u32 lpc; + + switch (le16_to_cpu(setup->wValue)) { + case USB_DEVICE_REMOTE_WAKEUP: + mtu->may_wakeup = !!set; + handled = 1; + break; + case USB_DEVICE_TEST_MODE: + if (!set || (mtu->g.speed != USB_SPEED_HIGH) || + (le16_to_cpu(setup->wIndex) & 0xff)) + break; + + handled = handle_test_mode(mtu, setup); + break; + case USB_DEVICE_U1_ENABLE: + if (mtu->g.speed < USB_SPEED_SUPER || + mtu->g.state != USB_STATE_CONFIGURED) + break; + + lpc = mtu3_readl(mbase, U3D_LINK_POWER_CONTROL); + if (set) + lpc |= SW_U1_REQUEST_ENABLE; + else + lpc &= ~SW_U1_REQUEST_ENABLE; + mtu3_writel(mbase, U3D_LINK_POWER_CONTROL, lpc); + + mtu->u1_enable = !!set; + handled = 1; + break; + case USB_DEVICE_U2_ENABLE: + if (mtu->g.speed < USB_SPEED_SUPER || + mtu->g.state != USB_STATE_CONFIGURED) + break; + + lpc = mtu3_readl(mbase, U3D_LINK_POWER_CONTROL); + if (set) + lpc |= SW_U2_REQUEST_ENABLE; + else + lpc &= ~SW_U2_REQUEST_ENABLE; + mtu3_writel(mbase, U3D_LINK_POWER_CONTROL, lpc); + + mtu->u2_enable = !!set; + handled = 1; + break; + default: + handled = -EINVAL; + break; + } + return handled; +} + +static int ep0_handle_feature(struct mtu3 *mtu, + struct usb_ctrlrequest *setup, bool set) +{ + struct mtu3_ep *mep; + int handled = -EINVAL; + int is_in; + u16 value; + u16 index; + u8 epnum; + + value = le16_to_cpu(setup->wValue); + index = le16_to_cpu(setup->wIndex); + + switch (setup->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + handled = ep0_handle_feature_dev(mtu, setup, set); + break; + case USB_RECIP_INTERFACE: + /* superspeed only */ + if (value == USB_INTRF_FUNC_SUSPEND && + mtu->g.speed >= USB_SPEED_SUPER) { + /* forward the request for function suspend */ + mtu->may_wakeup = !!(index & USB_INTRF_FUNC_SUSPEND_RW); + handled = 0; + } + break; + case USB_RECIP_ENDPOINT: + epnum = index & USB_ENDPOINT_NUMBER_MASK; + if (epnum == 0 || epnum >= mtu->num_eps || + value != USB_ENDPOINT_HALT) + break; + + is_in = index & USB_DIR_IN; + mep = (is_in ? mtu->in_eps : mtu->out_eps) + epnum; + if (!mep->desc) + break; + + handled = 1; + /* ignore request if endpoint is wedged */ + if (mep->flags & MTU3_EP_WEDGE) + break; + + mtu3_ep_stall_set(mep, set); + break; + default: + /* class, vendor, etc ... delegate */ + handled = 0; + break; + } + return handled; +} + +/* + * handle all control requests can be handled + * returns: + * negative errno - error happened + * zero - need delegate SETUP to gadget driver + * positive - already handled + */ +static int handle_standard_request(struct mtu3 *mtu, + struct usb_ctrlrequest *setup) +{ + void __iomem *mbase = mtu->mac_base; + enum usb_device_state state = mtu->g.state; + int handled = -EINVAL; + u32 dev_conf; + u16 value; + + value = le16_to_cpu(setup->wValue); + + /* the gadget driver handles everything except what we must handle */ + switch (setup->bRequest) { + case USB_REQ_SET_ADDRESS: + /* change it after the status stage */ + mtu->address = (u8) (value & 0x7f); + dev_dbg(mtu->dev, "set address to 0x%x\n", mtu->address); + + dev_conf = mtu3_readl(mbase, U3D_DEVICE_CONF); + dev_conf &= ~DEV_ADDR_MSK; + dev_conf |= DEV_ADDR(mtu->address); + mtu3_writel(mbase, U3D_DEVICE_CONF, dev_conf); + + if (mtu->address) + usb_gadget_set_state(&mtu->g, USB_STATE_ADDRESS); + else + usb_gadget_set_state(&mtu->g, USB_STATE_DEFAULT); + + handled = 1; + break; + case USB_REQ_SET_CONFIGURATION: + if (state == USB_STATE_ADDRESS) { + usb_gadget_set_state(&mtu->g, + USB_STATE_CONFIGURED); + } else if (state == USB_STATE_CONFIGURED) { + /* + * USB2 spec sec 9.4.7, if wValue is 0 then dev + * is moved to addressed state + */ + if (!value) + usb_gadget_set_state(&mtu->g, + USB_STATE_ADDRESS); + } + handled = 0; + break; + case USB_REQ_CLEAR_FEATURE: + handled = ep0_handle_feature(mtu, setup, 0); + break; + case USB_REQ_SET_FEATURE: + handled = ep0_handle_feature(mtu, setup, 1); + break; + case USB_REQ_GET_STATUS: + handled = ep0_get_status(mtu, setup); + break; + case USB_REQ_SET_SEL: + handled = ep0_set_sel(mtu, setup); + break; + case USB_REQ_SET_ISOCH_DELAY: + handled = 1; + break; + default: + /* delegate SET_CONFIGURATION, etc */ + handled = 0; + } + + return handled; +} + +/* receive an data packet (OUT) */ +static void ep0_rx_state(struct mtu3 *mtu) +{ + struct mtu3_request *mreq; + struct usb_request *req; + void __iomem *mbase = mtu->mac_base; + u32 maxp; + u32 csr; + u16 count = 0; + + dev_dbg(mtu->dev, "%s\n", __func__); + + csr = mtu3_readl(mbase, U3D_EP0CSR) & EP0_W1C_BITS; + mreq = next_ep0_request(mtu); + req = &mreq->request; + + /* read packet and ack; or stall because of gadget driver bug */ + if (req) { + void *buf = req->buf + req->actual; + unsigned int len = req->length - req->actual; + + /* read the buffer */ + count = mtu3_readl(mbase, U3D_RXCOUNT0); + if (count > len) { + req->status = -EOVERFLOW; + count = len; + } + ep0_read_fifo(mtu->ep0, buf, count); + req->actual += count; + csr |= EP0_RXPKTRDY; + + maxp = mtu->g.ep0->maxpacket; + if (count < maxp || req->actual == req->length) { + mtu->ep0_state = MU3D_EP0_STATE_SETUP; + dev_dbg(mtu->dev, "ep0 state: %s\n", + decode_ep0_state(mtu)); + + csr |= EP0_DATAEND; + } else { + req = NULL; + } + } else { + csr |= EP0_RXPKTRDY | EP0_SENDSTALL; + dev_dbg(mtu->dev, "%s: SENDSTALL\n", __func__); + } + + mtu3_writel(mbase, U3D_EP0CSR, csr); + + /* give back the request if have received all data */ + if (req) + ep0_req_giveback(mtu, req); + +} + +/* transmitting to the host (IN) */ +static void ep0_tx_state(struct mtu3 *mtu) +{ + struct mtu3_request *mreq = next_ep0_request(mtu); + struct usb_request *req; + u32 csr; + u8 *src; + u32 count; + u32 maxp; + + dev_dbg(mtu->dev, "%s\n", __func__); + + if (!mreq) + return; + + maxp = mtu->g.ep0->maxpacket; + req = &mreq->request; + + /* load the data */ + src = (u8 *)req->buf + req->actual; + count = min(maxp, req->length - req->actual); + if (count) + ep0_write_fifo(mtu->ep0, src, count); + + dev_dbg(mtu->dev, "%s act=%d, len=%d, cnt=%d, maxp=%d zero=%d\n", + __func__, req->actual, req->length, count, maxp, req->zero); + + req->actual += count; + + if ((count < maxp) + || ((req->actual == req->length) && !req->zero)) + mtu->ep0_state = MU3D_EP0_STATE_TX_END; + + /* send it out, triggering a "txpktrdy cleared" irq */ + csr = mtu3_readl(mtu->mac_base, U3D_EP0CSR) & EP0_W1C_BITS; + mtu3_writel(mtu->mac_base, U3D_EP0CSR, csr | EP0_TXPKTRDY); + + dev_dbg(mtu->dev, "%s ep0csr=0x%x\n", __func__, + mtu3_readl(mtu->mac_base, U3D_EP0CSR)); +} + +static void ep0_read_setup(struct mtu3 *mtu, struct usb_ctrlrequest *setup) +{ + struct mtu3_request *mreq; + u32 count; + u32 csr; + + csr = mtu3_readl(mtu->mac_base, U3D_EP0CSR) & EP0_W1C_BITS; + count = mtu3_readl(mtu->mac_base, U3D_RXCOUNT0); + + ep0_read_fifo(mtu->ep0, (u8 *)setup, count); + + dev_dbg(mtu->dev, "SETUP req%02x.%02x v%04x i%04x l%04x\n", + setup->bRequestType, setup->bRequest, + le16_to_cpu(setup->wValue), le16_to_cpu(setup->wIndex), + le16_to_cpu(setup->wLength)); + + /* clean up any leftover transfers */ + mreq = next_ep0_request(mtu); + if (mreq) + ep0_req_giveback(mtu, &mreq->request); + + if (le16_to_cpu(setup->wLength) == 0) { + ; /* no data stage, nothing to do */ + } else if (setup->bRequestType & USB_DIR_IN) { + mtu3_writel(mtu->mac_base, U3D_EP0CSR, + csr | EP0_SETUPPKTRDY | EP0_DPHTX); + mtu->ep0_state = MU3D_EP0_STATE_TX; + } else { + mtu3_writel(mtu->mac_base, U3D_EP0CSR, + (csr | EP0_SETUPPKTRDY) & (~EP0_DPHTX)); + mtu->ep0_state = MU3D_EP0_STATE_RX; + } +} + +static int ep0_handle_setup(struct mtu3 *mtu) +__releases(mtu->lock) +__acquires(mtu->lock) +{ + struct usb_ctrlrequest setup; + struct mtu3_request *mreq; + int handled = 0; + + ep0_read_setup(mtu, &setup); + trace_mtu3_handle_setup(&setup); + + if ((setup.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + handled = handle_standard_request(mtu, &setup); + + dev_dbg(mtu->dev, "handled %d, ep0_state: %s\n", + handled, decode_ep0_state(mtu)); + + if (handled < 0) + goto stall; + else if (handled > 0) + goto finish; + + handled = forward_to_driver(mtu, &setup); + if (handled < 0) { +stall: + dev_dbg(mtu->dev, "%s stall (%d)\n", __func__, handled); + + ep0_stall_set(mtu->ep0, true, + le16_to_cpu(setup.wLength) ? 0 : EP0_SETUPPKTRDY); + + return 0; + } + +finish: + if (mtu->test_mode) { + ; /* nothing to do */ + } else if (handled == USB_GADGET_DELAYED_STATUS) { + + mreq = next_ep0_request(mtu); + if (mreq) { + /* already asked us to continue delayed status */ + ep0_do_status_stage(mtu); + ep0_req_giveback(mtu, &mreq->request); + } else { + /* do delayed STATUS stage till receive ep0_queue */ + mtu->delayed_status = true; + } + } else if (le16_to_cpu(setup.wLength) == 0) { /* no data stage */ + + ep0_do_status_stage(mtu); + /* complete zlp request directly */ + mreq = next_ep0_request(mtu); + if (mreq && !mreq->request.length) + ep0_req_giveback(mtu, &mreq->request); + } + + return 0; +} + +irqreturn_t mtu3_ep0_isr(struct mtu3 *mtu) +{ + void __iomem *mbase = mtu->mac_base; + struct mtu3_request *mreq; + u32 int_status; + irqreturn_t ret = IRQ_NONE; + u32 csr; + u32 len; + + int_status = mtu3_readl(mbase, U3D_EPISR); + int_status &= mtu3_readl(mbase, U3D_EPIER); + mtu3_writel(mbase, U3D_EPISR, int_status); /* W1C */ + + /* only handle ep0's */ + if (!(int_status & (EP0ISR | SETUPENDISR))) + return IRQ_NONE; + + /* abort current SETUP, and process new one */ + if (int_status & SETUPENDISR) + mtu->ep0_state = MU3D_EP0_STATE_SETUP; + + csr = mtu3_readl(mbase, U3D_EP0CSR); + + dev_dbg(mtu->dev, "%s csr=0x%x\n", __func__, csr); + + /* we sent a stall.. need to clear it now.. */ + if (csr & EP0_SENTSTALL) { + ep0_stall_set(mtu->ep0, false, 0); + csr = mtu3_readl(mbase, U3D_EP0CSR); + ret = IRQ_HANDLED; + } + dev_dbg(mtu->dev, "ep0_state: %s\n", decode_ep0_state(mtu)); + mtu3_dbg_trace(mtu->dev, "ep0_state %s", decode_ep0_state(mtu)); + + switch (mtu->ep0_state) { + case MU3D_EP0_STATE_TX: + /* irq on clearing txpktrdy */ + if ((csr & EP0_FIFOFULL) == 0) { + ep0_tx_state(mtu); + ret = IRQ_HANDLED; + } + break; + case MU3D_EP0_STATE_RX: + /* irq on set rxpktrdy */ + if (csr & EP0_RXPKTRDY) { + ep0_rx_state(mtu); + ret = IRQ_HANDLED; + } + break; + case MU3D_EP0_STATE_TX_END: + mtu3_writel(mbase, U3D_EP0CSR, + (csr & EP0_W1C_BITS) | EP0_DATAEND); + + mreq = next_ep0_request(mtu); + if (mreq) + ep0_req_giveback(mtu, &mreq->request); + + mtu->ep0_state = MU3D_EP0_STATE_SETUP; + ret = IRQ_HANDLED; + dev_dbg(mtu->dev, "ep0_state: %s\n", decode_ep0_state(mtu)); + break; + case MU3D_EP0_STATE_SETUP: + if (!(csr & EP0_SETUPPKTRDY)) + break; + + len = mtu3_readl(mbase, U3D_RXCOUNT0); + if (len != 8) { + dev_err(mtu->dev, "SETUP packet len %d != 8 ?\n", len); + break; + } + + ep0_handle_setup(mtu); + ret = IRQ_HANDLED; + break; + default: + /* can't happen */ + ep0_stall_set(mtu->ep0, true, 0); + WARN_ON(1); + break; + } + + return ret; +} + + +static int mtu3_ep0_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + /* always enabled */ + return -EINVAL; +} + +static int mtu3_ep0_disable(struct usb_ep *ep) +{ + /* always enabled */ + return -EINVAL; +} + +static int ep0_queue(struct mtu3_ep *mep, struct mtu3_request *mreq) +{ + struct mtu3 *mtu = mep->mtu; + + mreq->mtu = mtu; + mreq->request.actual = 0; + mreq->request.status = -EINPROGRESS; + + dev_dbg(mtu->dev, "%s %s (ep0_state: %s), len#%d\n", __func__, + mep->name, decode_ep0_state(mtu), mreq->request.length); + + switch (mtu->ep0_state) { + case MU3D_EP0_STATE_SETUP: + case MU3D_EP0_STATE_RX: /* control-OUT data */ + case MU3D_EP0_STATE_TX: /* control-IN data */ + break; + default: + dev_err(mtu->dev, "%s, error in ep0 state %s\n", __func__, + decode_ep0_state(mtu)); + return -EINVAL; + } + + if (mtu->delayed_status) { + + mtu->delayed_status = false; + ep0_do_status_stage(mtu); + /* needn't giveback the request for handling delay STATUS */ + return 0; + } + + if (!list_empty(&mep->req_list)) + return -EBUSY; + + list_add_tail(&mreq->list, &mep->req_list); + + /* sequence #1, IN ... start writing the data */ + if (mtu->ep0_state == MU3D_EP0_STATE_TX) + ep0_tx_state(mtu); + + return 0; +} + +static int mtu3_ep0_queue(struct usb_ep *ep, + struct usb_request *req, gfp_t gfp) +{ + struct mtu3_ep *mep; + struct mtu3_request *mreq; + struct mtu3 *mtu; + unsigned long flags; + int ret = 0; + + if (!ep || !req) + return -EINVAL; + + mep = to_mtu3_ep(ep); + mtu = mep->mtu; + mreq = to_mtu3_request(req); + + spin_lock_irqsave(&mtu->lock, flags); + ret = ep0_queue(mep, mreq); + spin_unlock_irqrestore(&mtu->lock, flags); + return ret; +} + +static int mtu3_ep0_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + /* we just won't support this */ + return -EINVAL; +} + +static int mtu3_ep0_halt(struct usb_ep *ep, int value) +{ + struct mtu3_ep *mep; + struct mtu3 *mtu; + unsigned long flags; + int ret = 0; + + if (!ep || !value) + return -EINVAL; + + mep = to_mtu3_ep(ep); + mtu = mep->mtu; + + dev_dbg(mtu->dev, "%s\n", __func__); + + spin_lock_irqsave(&mtu->lock, flags); + + if (!list_empty(&mep->req_list)) { + ret = -EBUSY; + goto cleanup; + } + + switch (mtu->ep0_state) { + /* + * stalls are usually issued after parsing SETUP packet, either + * directly in irq context from setup() or else later. + */ + case MU3D_EP0_STATE_TX: + case MU3D_EP0_STATE_TX_END: + case MU3D_EP0_STATE_RX: + case MU3D_EP0_STATE_SETUP: + ep0_stall_set(mtu->ep0, true, 0); + break; + default: + dev_dbg(mtu->dev, "ep0 can't halt in state %s\n", + decode_ep0_state(mtu)); + ret = -EINVAL; + } + +cleanup: + spin_unlock_irqrestore(&mtu->lock, flags); + return ret; +} + +const struct usb_ep_ops mtu3_ep0_ops = { + .enable = mtu3_ep0_enable, + .disable = mtu3_ep0_disable, + .alloc_request = mtu3_alloc_request, + .free_request = mtu3_free_request, + .queue = mtu3_ep0_queue, + .dequeue = mtu3_ep0_dequeue, + .set_halt = mtu3_ep0_halt, +}; diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c new file mode 100644 index 000000000..8138b3f30 --- /dev/null +++ b/drivers/usb/mtu3/mtu3_host.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mtu3_dr.c - dual role switch and host glue layer + * + * Copyright (C) 2016 MediaTek Inc. + * + * Author: Chunfeng Yun + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtu3.h" +#include "mtu3_dr.h" + +/* mt8173 etc */ +#define PERI_WK_CTRL1 0x4 +#define WC1_IS_C(x) (((x) & 0xf) << 26) /* cycle debounce */ +#define WC1_IS_EN BIT(25) +#define WC1_IS_P BIT(6) /* polarity for ip sleep */ + +/* mt8183 */ +#define PERI_WK_CTRL0 0x0 +#define WC0_IS_C(x) ((u32)(((x) & 0xf) << 28)) /* cycle debounce */ +#define WC0_IS_P BIT(12) /* polarity */ +#define WC0_IS_EN BIT(6) + +/* mt8192 */ +#define WC0_SSUSB0_CDEN BIT(6) +#define WC0_IS_SPM_EN BIT(1) + +/* mt8195 */ +#define PERI_WK_CTRL0_8195 0x04 +#define WC0_IS_P_95 BIT(30) /* polarity */ +#define WC0_IS_C_95(x) ((u32)(((x) & 0x7) << 27)) +#define WC0_IS_EN_P3_95 BIT(26) +#define WC0_IS_EN_P2_95 BIT(25) + +#define PERI_WK_CTRL1_8195 0x20 +#define WC1_IS_C_95(x) ((u32)(((x) & 0xf) << 28)) +#define WC1_IS_P_95 BIT(12) +#define WC1_IS_EN_P0_95 BIT(6) + +/* mt8196 */ +#define PERI_WK_CTRL0_8196 0x08 +#define WC0_IS_EN_P0_96 BIT(0) +#define WC0_IS_EN_P1_96 BIT(7) + +#define PERI_WK_CTRL1_8196 0x10 +#define WC1_IS_EN_P2_96 BIT(0) + +/* mt2712 etc */ +#define PERI_SSUSB_SPM_CTRL 0x0 +#define SSC_IP_SLEEP_EN BIT(4) +#define SSC_SPM_INT_EN BIT(1) + +enum ssusb_uwk_vers { + SSUSB_UWK_V1 = 1, + SSUSB_UWK_V2, + SSUSB_UWK_V1_1 = 101, /* specific revision 1.01 */ + SSUSB_UWK_V1_2, /* specific revision 1.02 */ + SSUSB_UWK_V1_3, /* mt8195 IP0 */ + SSUSB_UWK_V1_5 = 105, /* mt8195 IP2 */ + SSUSB_UWK_V1_6, /* mt8195 IP3 */ + SSUSB_UWK_V1_7, /* mt8196 IP0 */ + SSUSB_UWK_V1_8, /* mt8196 IP1 */ + SSUSB_UWK_V1_9, /* mt8196 IP2 */ +}; + +/* + * ip-sleep wakeup mode: + * all clocks can be turn off, but power domain should be kept on + */ +static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable) +{ + u32 reg, msk, val; + + switch (ssusb->uwk_vers) { + case SSUSB_UWK_V1: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL1; + msk = WC1_IS_EN | WC1_IS_C(0xf) | WC1_IS_P; + val = enable ? (WC1_IS_EN | WC1_IS_C(0x8)) : 0; + break; + case SSUSB_UWK_V1_1: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL0; + msk = WC0_IS_EN | WC0_IS_C(0xf) | WC0_IS_P; + val = enable ? (WC0_IS_EN | WC0_IS_C(0x1)) : 0; + break; + case SSUSB_UWK_V1_2: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL0; + msk = WC0_SSUSB0_CDEN | WC0_IS_SPM_EN; + val = enable ? msk : 0; + break; + case SSUSB_UWK_V1_3: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL1_8195; + msk = WC1_IS_EN_P0_95 | WC1_IS_C_95(0xf) | WC1_IS_P_95; + val = enable ? (WC1_IS_EN_P0_95 | WC1_IS_C_95(0x1)) : 0; + break; + case SSUSB_UWK_V1_5: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8195; + msk = WC0_IS_EN_P2_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P2_95 | WC0_IS_C_95(0x1)) : 0; + break; + case SSUSB_UWK_V1_6: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8195; + msk = WC0_IS_EN_P3_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P3_95 | WC0_IS_C_95(0x1)) : 0; + break; + case SSUSB_UWK_V1_7: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196; + msk = WC0_IS_EN_P0_96; + val = enable ? msk : 0; + break; + case SSUSB_UWK_V1_8: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196; + msk = WC0_IS_EN_P1_96; + val = enable ? msk : 0; + break; + case SSUSB_UWK_V1_9: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL1_8196; + msk = WC1_IS_EN_P2_96; + val = enable ? msk : 0; + break; + case SSUSB_UWK_V2: + reg = ssusb->uwk_reg_base + PERI_SSUSB_SPM_CTRL; + msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; + val = enable ? msk : 0; + break; + default: + return; + } + regmap_update_bits(ssusb->uwk, reg, msk, val); +} + +int ssusb_wakeup_of_property_parse(struct ssusb_mtk *ssusb, + struct device_node *dn) +{ + struct of_phandle_args args; + int ret; + + /* wakeup function is optional */ + ssusb->uwk_en = of_property_read_bool(dn, "wakeup-source"); + if (!ssusb->uwk_en) + return 0; + + ret = of_parse_phandle_with_fixed_args(dn, + "mediatek,syscon-wakeup", 2, 0, &args); + if (ret) + return ret; + + ssusb->uwk_reg_base = args.args[0]; + ssusb->uwk_vers = args.args[1]; + ssusb->uwk = syscon_node_to_regmap(args.np); + of_node_put(args.np); + dev_info(ssusb->dev, "uwk - reg:0x%x, version:%d\n", + ssusb->uwk_reg_base, ssusb->uwk_vers); + + return PTR_ERR_OR_ZERO(ssusb->uwk); +} + +void ssusb_wakeup_set(struct ssusb_mtk *ssusb, bool enable) +{ + if (ssusb->uwk_en) + ssusb_wakeup_ip_sleep_set(ssusb, enable); +} + +static void host_ports_num_get(struct ssusb_mtk *ssusb) +{ + u32 xhci_cap; + + xhci_cap = mtu3_readl(ssusb->ippc_base, U3D_SSUSB_IP_XHCI_CAP); + ssusb->u2_ports = SSUSB_IP_XHCI_U2_PORT_NUM(xhci_cap); + ssusb->u3_ports = SSUSB_IP_XHCI_U3_PORT_NUM(xhci_cap); + + dev_dbg(ssusb->dev, "host - u2_ports:%d, u3_ports:%d\n", + ssusb->u2_ports, ssusb->u3_ports); +} + +/* only configure ports will be used later */ +static int ssusb_host_enable(struct ssusb_mtk *ssusb) +{ + void __iomem *ibase = ssusb->ippc_base; + int num_u3p = ssusb->u3_ports; + int num_u2p = ssusb->u2_ports; + int u3_ports_disabled; + u32 check_clk; + u32 value; + int i; + + /* power on host ip */ + mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); + + /* power on and enable u3 ports except skipped ones */ + u3_ports_disabled = 0; + for (i = 0; i < num_u3p; i++) { + if ((0x1 << i) & ssusb->u3p_dis_msk) { + u3_ports_disabled++; + continue; + } + + value = mtu3_readl(ibase, SSUSB_U3_CTRL(i)); + value &= ~(SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS); + value |= SSUSB_U3_PORT_HOST_SEL; + mtu3_writel(ibase, SSUSB_U3_CTRL(i), value); + } + + /* power on and enable all u2 ports */ + for (i = 0; i < num_u2p; i++) { + if ((0x1 << i) & ssusb->u2p_dis_msk) + continue; + + value = mtu3_readl(ibase, SSUSB_U2_CTRL(i)); + value &= ~(SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS); + value |= SSUSB_U2_PORT_HOST_SEL; + mtu3_writel(ibase, SSUSB_U2_CTRL(i), value); + } + + check_clk = SSUSB_XHCI_RST_B_STS; + if (num_u3p > u3_ports_disabled) + check_clk = SSUSB_U3_MAC_RST_B_STS; + + return ssusb_check_clocks(ssusb, check_clk); +} + +static int ssusb_host_disable(struct ssusb_mtk *ssusb) +{ + void __iomem *ibase = ssusb->ippc_base; + int num_u3p = ssusb->u3_ports; + int num_u2p = ssusb->u2_ports; + u32 value; + int i; + + /* power down and disable u3 ports except skipped ones */ + for (i = 0; i < num_u3p; i++) { + if ((0x1 << i) & ssusb->u3p_dis_msk) + continue; + + value = mtu3_readl(ibase, SSUSB_U3_CTRL(i)); + value |= SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS; + mtu3_writel(ibase, SSUSB_U3_CTRL(i), value); + } + + /* power down and disable u2 ports except skipped ones */ + for (i = 0; i < num_u2p; i++) { + if ((0x1 << i) & ssusb->u2p_dis_msk) + continue; + + value = mtu3_readl(ibase, SSUSB_U2_CTRL(i)); + value |= SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS; + mtu3_writel(ibase, SSUSB_U2_CTRL(i), value); + } + + /* power down host ip */ + mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); + + return 0; +} + +int ssusb_host_resume(struct ssusb_mtk *ssusb, bool p0_skipped) +{ + void __iomem *ibase = ssusb->ippc_base; + int u3p_skip_msk = ssusb->u3p_dis_msk; + int u2p_skip_msk = ssusb->u2p_dis_msk; + int num_u3p = ssusb->u3_ports; + int num_u2p = ssusb->u2_ports; + u32 value; + int i; + + if (p0_skipped) { + u2p_skip_msk |= 0x1; + if (ssusb->otg_switch.is_u3_drd) + u3p_skip_msk |= 0x1; + } + + /* power on host ip */ + mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); + + /* power on u3 ports except skipped ones */ + for (i = 0; i < num_u3p; i++) { + if ((0x1 << i) & u3p_skip_msk) + continue; + + value = mtu3_readl(ibase, SSUSB_U3_CTRL(i)); + value &= ~SSUSB_U3_PORT_PDN; + mtu3_writel(ibase, SSUSB_U3_CTRL(i), value); + } + + /* power on all u2 ports except skipped ones */ + for (i = 0; i < num_u2p; i++) { + if ((0x1 << i) & u2p_skip_msk) + continue; + + value = mtu3_readl(ibase, SSUSB_U2_CTRL(i)); + value &= ~SSUSB_U2_PORT_PDN; + mtu3_writel(ibase, SSUSB_U2_CTRL(i), value); + } + + return 0; +} + +/* here not skip port0 due to PDN can be set repeatedly */ +int ssusb_host_suspend(struct ssusb_mtk *ssusb) +{ + void __iomem *ibase = ssusb->ippc_base; + int num_u3p = ssusb->u3_ports; + int num_u2p = ssusb->u2_ports; + u32 value; + int i; + + /* power down u3 ports except skipped ones */ + for (i = 0; i < num_u3p; i++) { + if ((0x1 << i) & ssusb->u3p_dis_msk) + continue; + + value = mtu3_readl(ibase, SSUSB_U3_CTRL(i)); + value |= SSUSB_U3_PORT_PDN; + mtu3_writel(ibase, SSUSB_U3_CTRL(i), value); + } + + /* power down u2 ports except skipped ones */ + for (i = 0; i < num_u2p; i++) { + if ((0x1 << i) & ssusb->u2p_dis_msk) + continue; + + value = mtu3_readl(ibase, SSUSB_U2_CTRL(i)); + value |= SSUSB_U2_PORT_PDN; + mtu3_writel(ibase, SSUSB_U2_CTRL(i), value); + } + + /* power down host ip */ + mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); + + return 0; +} + +static void ssusb_host_setup(struct ssusb_mtk *ssusb) +{ + host_ports_num_get(ssusb); + + /* + * power on host and power on/enable all ports + * if support OTG, gadget driver will switch port0 to device mode + */ + ssusb_host_enable(ssusb); + ssusb_set_force_mode(ssusb, MTU3_DR_FORCE_HOST); + + /* if port0 supports dual-role, works as host mode by default */ + ssusb_set_vbus(&ssusb->otg_switch, 1); +} + +static void ssusb_host_cleanup(struct ssusb_mtk *ssusb) +{ + if (ssusb->is_host) + ssusb_set_vbus(&ssusb->otg_switch, 0); + + ssusb_host_disable(ssusb); +} + +/* + * If host supports multiple ports, the VBUSes(5V) of ports except port0 + * which supports OTG are better to be enabled by default in DTS. + * Because the host driver will keep link with devices attached when system + * enters suspend mode, so no need to control VBUSes after initialization. + */ +int ssusb_host_init(struct ssusb_mtk *ssusb, struct device_node *parent_dn) +{ + struct device *parent_dev = ssusb->dev; + int ret; + + ssusb_host_setup(ssusb); + + ret = of_platform_populate(parent_dn, NULL, NULL, parent_dev); + if (ret) { + dev_dbg(parent_dev, "failed to create child devices at %pOF\n", + parent_dn); + return ret; + } + + dev_info(parent_dev, "xHCI platform device register success...\n"); + + return 0; +} + +void ssusb_host_exit(struct ssusb_mtk *ssusb) +{ + of_platform_depopulate(ssusb->dev); + ssusb_host_cleanup(ssusb); +} diff --git a/drivers/usb/mtu3/mtu3_hw_regs.h b/drivers/usb/mtu3/mtu3_hw_regs.h new file mode 100644 index 000000000..ee30ae0a4 --- /dev/null +++ b/drivers/usb/mtu3/mtu3_hw_regs.h @@ -0,0 +1,543 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mtu3_hw_regs.h - MediaTek USB3 DRD register and field definitions + * + * Copyright (C) 2016 MediaTek Inc. + * + * Author: Chunfeng Yun + */ + +#ifndef _SSUSB_HW_REGS_H_ +#define _SSUSB_HW_REGS_H_ + +/* segment offset of MAC register */ +#define SSUSB_DEV_BASE 0x0000 +#define SSUSB_EPCTL_CSR_BASE 0x0800 +#define SSUSB_USB3_MAC_CSR_BASE 0x1400 +#define SSUSB_USB3_SYS_CSR_BASE 0x1400 +#define SSUSB_USB2_CSR_BASE 0x2400 + +/* IPPC register in Infra */ +#define SSUSB_SIFSLV_IPPC_BASE 0x0000 + +/* --------------- SSUSB_DEV REGISTER DEFINITION --------------- */ + +#define U3D_LV1ISR (SSUSB_DEV_BASE + 0x0000) +#define U3D_LV1IER (SSUSB_DEV_BASE + 0x0004) +#define U3D_LV1IESR (SSUSB_DEV_BASE + 0x0008) +#define U3D_LV1IECR (SSUSB_DEV_BASE + 0x000C) + +#define U3D_EPISR (SSUSB_DEV_BASE + 0x0080) +#define U3D_EPIER (SSUSB_DEV_BASE + 0x0084) +#define U3D_EPIESR (SSUSB_DEV_BASE + 0x0088) +#define U3D_EPIECR (SSUSB_DEV_BASE + 0x008C) + +#define U3D_EP0CSR (SSUSB_DEV_BASE + 0x0100) +#define U3D_RXCOUNT0 (SSUSB_DEV_BASE + 0x0108) +#define U3D_RESERVED (SSUSB_DEV_BASE + 0x010C) +#define U3D_TX1CSR0 (SSUSB_DEV_BASE + 0x0110) +#define U3D_TX1CSR1 (SSUSB_DEV_BASE + 0x0114) +#define U3D_TX1CSR2 (SSUSB_DEV_BASE + 0x0118) + +#define U3D_RX1CSR0 (SSUSB_DEV_BASE + 0x0210) +#define U3D_RX1CSR1 (SSUSB_DEV_BASE + 0x0214) +#define U3D_RX1CSR2 (SSUSB_DEV_BASE + 0x0218) + +#define U3D_FIFO0 (SSUSB_DEV_BASE + 0x0300) + +#define U3D_QCR0 (SSUSB_DEV_BASE + 0x0400) +#define U3D_QCR1 (SSUSB_DEV_BASE + 0x0404) +#define U3D_QCR2 (SSUSB_DEV_BASE + 0x0408) +#define U3D_QCR3 (SSUSB_DEV_BASE + 0x040C) +#define U3D_QFCR (SSUSB_DEV_BASE + 0x0428) +#define U3D_TXQHIAR1 (SSUSB_DEV_BASE + 0x0484) +#define U3D_RXQHIAR1 (SSUSB_DEV_BASE + 0x04C4) + +#define U3D_TXQCSR1 (SSUSB_DEV_BASE + 0x0510) +#define U3D_TXQSAR1 (SSUSB_DEV_BASE + 0x0514) +#define U3D_TXQCPR1 (SSUSB_DEV_BASE + 0x0518) + +#define U3D_RXQCSR1 (SSUSB_DEV_BASE + 0x0610) +#define U3D_RXQSAR1 (SSUSB_DEV_BASE + 0x0614) +#define U3D_RXQCPR1 (SSUSB_DEV_BASE + 0x0618) +#define U3D_RXQLDPR1 (SSUSB_DEV_BASE + 0x061C) + +#define U3D_QISAR0 (SSUSB_DEV_BASE + 0x0700) +#define U3D_QIER0 (SSUSB_DEV_BASE + 0x0704) +#define U3D_QIESR0 (SSUSB_DEV_BASE + 0x0708) +#define U3D_QIECR0 (SSUSB_DEV_BASE + 0x070C) +#define U3D_QISAR1 (SSUSB_DEV_BASE + 0x0710) +#define U3D_QIER1 (SSUSB_DEV_BASE + 0x0714) +#define U3D_QIESR1 (SSUSB_DEV_BASE + 0x0718) +#define U3D_QIECR1 (SSUSB_DEV_BASE + 0x071C) + +#define U3D_TQERRIR0 (SSUSB_DEV_BASE + 0x0780) +#define U3D_TQERRIER0 (SSUSB_DEV_BASE + 0x0784) +#define U3D_TQERRIESR0 (SSUSB_DEV_BASE + 0x0788) +#define U3D_TQERRIECR0 (SSUSB_DEV_BASE + 0x078C) +#define U3D_RQERRIR0 (SSUSB_DEV_BASE + 0x07C0) +#define U3D_RQERRIER0 (SSUSB_DEV_BASE + 0x07C4) +#define U3D_RQERRIESR0 (SSUSB_DEV_BASE + 0x07C8) +#define U3D_RQERRIECR0 (SSUSB_DEV_BASE + 0x07CC) +#define U3D_RQERRIR1 (SSUSB_DEV_BASE + 0x07D0) +#define U3D_RQERRIER1 (SSUSB_DEV_BASE + 0x07D4) +#define U3D_RQERRIESR1 (SSUSB_DEV_BASE + 0x07D8) +#define U3D_RQERRIECR1 (SSUSB_DEV_BASE + 0x07DC) + +#define U3D_CAP_EP0FFSZ (SSUSB_DEV_BASE + 0x0C04) +#define U3D_CAP_EPNTXFFSZ (SSUSB_DEV_BASE + 0x0C08) +#define U3D_CAP_EPNRXFFSZ (SSUSB_DEV_BASE + 0x0C0C) +#define U3D_CAP_EPINFO (SSUSB_DEV_BASE + 0x0C10) +#define U3D_MISC_CTRL (SSUSB_DEV_BASE + 0x0C84) + +/*---------------- SSUSB_DEV FIELD DEFINITION ---------------*/ + +/* U3D_LV1ISR */ +#define EP_CTRL_INTR BIT(5) +#define MAC2_INTR BIT(4) +#define DMA_INTR BIT(3) +#define MAC3_INTR BIT(2) +#define QMU_INTR BIT(1) +#define BMU_INTR BIT(0) + +/* U3D_LV1IECR */ +#define LV1IECR_MSK GENMASK(31, 0) + +/* U3D_EPISR */ +#define EPRISR(x) (BIT(16) << (x)) +#define SETUPENDISR BIT(16) +#define EPTISR(x) (BIT(0) << (x)) +#define EP0ISR BIT(0) + +/* U3D_EP0CSR */ +#define EP0_SENDSTALL BIT(25) +#define EP0_FIFOFULL BIT(23) +#define EP0_SENTSTALL BIT(22) +#define EP0_DPHTX BIT(20) +#define EP0_DATAEND BIT(19) +#define EP0_TXPKTRDY BIT(18) +#define EP0_SETUPPKTRDY BIT(17) +#define EP0_RXPKTRDY BIT(16) +#define EP0_MAXPKTSZ_MSK GENMASK(9, 0) +#define EP0_MAXPKTSZ(x) ((x) & EP0_MAXPKTSZ_MSK) +#define EP0_W1C_BITS (~(EP0_RXPKTRDY | EP0_SETUPPKTRDY | EP0_SENTSTALL)) + +/* U3D_TX1CSR0 */ +#define TX_DMAREQEN BIT(29) +#define TX_FIFOFULL BIT(25) +#define TX_FIFOEMPTY BIT(24) +#define TX_SENTSTALL BIT(22) +#define TX_SENDSTALL BIT(21) +#define TX_FLUSHFIFO BIT(20) +#define TX_TXPKTRDY BIT(16) +#define TX_TXMAXPKTSZ_MSK GENMASK(10, 0) +#define TX_TXMAXPKTSZ(x) ((x) & TX_TXMAXPKTSZ_MSK) +#define TX_W1C_BITS (~(TX_SENTSTALL)) + +/* U3D_TX1CSR1 */ +#define TX_MAX_PKT_G2(x) (((x) & 0xff) << 24) +#define TX_MULT_G2(x) (((x) & 0x7) << 21) +#define TX_MULT_OG(x) (((x) & 0x3) << 22) +#define TX_MAX_PKT_OG(x) (((x) & 0x3f) << 16) +#define TX_SLOT(x) (((x) & 0x3f) << 8) +#define TX_TYPE(x) (((x) & 0x3) << 4) +#define TX_SS_BURST(x) (((x) & 0xf) << 0) +#define TX_MULT(g2c, x) \ +({ \ + typeof(x) x_ = (x); \ + (g2c) ? TX_MULT_G2(x_) : TX_MULT_OG(x_); \ +}) +#define TX_MAX_PKT(g2c, x) \ +({ \ + typeof(x) x_ = (x); \ + (g2c) ? TX_MAX_PKT_G2(x_) : TX_MAX_PKT_OG(x_); \ +}) + +/* for TX_TYPE & RX_TYPE */ +#define TYPE_BULK (0x0) +#define TYPE_INT (0x1) +#define TYPE_ISO (0x2) +#define TYPE_MASK (0x3) + +/* U3D_TX1CSR2 */ +#define TX_BINTERVAL(x) (((x) & 0xff) << 24) +#define TX_FIFOSEGSIZE(x) (((x) & 0xf) << 16) +#define TX_FIFOADDR(x) (((x) & 0x1fff) << 0) + +/* U3D_RX1CSR0 */ +#define RX_DMAREQEN BIT(29) +#define RX_SENTSTALL BIT(22) +#define RX_SENDSTALL BIT(21) +#define RX_RXPKTRDY BIT(16) +#define RX_RXMAXPKTSZ_MSK GENMASK(10, 0) +#define RX_RXMAXPKTSZ(x) ((x) & RX_RXMAXPKTSZ_MSK) +#define RX_W1C_BITS (~(RX_SENTSTALL | RX_RXPKTRDY)) + +/* U3D_RX1CSR1 */ +#define RX_MAX_PKT_G2(x) (((x) & 0xff) << 24) +#define RX_MULT_G2(x) (((x) & 0x7) << 21) +#define RX_MULT_OG(x) (((x) & 0x3) << 22) +#define RX_MAX_PKT_OG(x) (((x) & 0x3f) << 16) +#define RX_SLOT(x) (((x) & 0x3f) << 8) +#define RX_TYPE(x) (((x) & 0x3) << 4) +#define RX_SS_BURST(x) (((x) & 0xf) << 0) +#define RX_MULT(g2c, x) \ +({ \ + typeof(x) x_ = (x); \ + (g2c) ? RX_MULT_G2(x_) : RX_MULT_OG(x_); \ +}) +#define RX_MAX_PKT(g2c, x) \ +({ \ + typeof(x) x_ = (x); \ + (g2c) ? RX_MAX_PKT_G2(x_) : RX_MAX_PKT_OG(x_); \ +}) + +/* U3D_RX1CSR2 */ +#define RX_BINTERVAL(x) (((x) & 0xff) << 24) +#define RX_FIFOSEGSIZE(x) (((x) & 0xf) << 16) +#define RX_FIFOADDR(x) (((x) & 0x1fff) << 0) + +/* U3D_QCR0 */ +#define QMU_RX_CS_EN(x) (BIT(16) << (x)) +#define QMU_TX_CS_EN(x) (BIT(0) << (x)) +#define QMU_CS16B_EN BIT(0) + +/* U3D_QCR1 */ +#define QMU_TX_ZLP(x) (BIT(0) << (x)) + +/* U3D_QCR3 */ +#define QMU_RX_COZ(x) (BIT(16) << (x)) +#define QMU_RX_ZLP(x) (BIT(0) << (x)) + +/* U3D_TXQHIAR1 */ +/* U3D_RXQHIAR1 */ +#define QMU_LAST_DONE_PTR_HI(x) (((x) >> 16) & 0xf) +#define QMU_CUR_GPD_ADDR_HI(x) (((x) >> 8) & 0xf) +#define QMU_START_ADDR_HI_MSK GENMASK(3, 0) +#define QMU_START_ADDR_HI(x) (((x) & 0xf) << 0) + +/* U3D_TXQCSR1 */ +/* U3D_RXQCSR1 */ +#define QMU_Q_ACTIVE BIT(15) +#define QMU_Q_STOP BIT(2) +#define QMU_Q_RESUME BIT(1) +#define QMU_Q_START BIT(0) + +/* U3D_QISAR0, U3D_QIER0, U3D_QIESR0, U3D_QIECR0 */ +#define QMU_RX_DONE_INT(x) (BIT(16) << (x)) +#define QMU_TX_DONE_INT(x) (BIT(0) << (x)) + +/* U3D_QISAR1, U3D_QIER1, U3D_QIESR1, U3D_QIECR1 */ +#define RXQ_ZLPERR_INT BIT(20) +#define RXQ_LENERR_INT BIT(18) +#define RXQ_CSERR_INT BIT(17) +#define RXQ_EMPTY_INT BIT(16) +#define TXQ_LENERR_INT BIT(2) +#define TXQ_CSERR_INT BIT(1) +#define TXQ_EMPTY_INT BIT(0) + +/* U3D_TQERRIR0, U3D_TQERRIER0, U3D_TQERRIESR0, U3D_TQERRIECR0 */ +#define QMU_TX_LEN_ERR(x) (BIT(16) << (x)) +#define QMU_TX_CS_ERR(x) (BIT(0) << (x)) + +/* U3D_RQERRIR0, U3D_RQERRIER0, U3D_RQERRIESR0, U3D_RQERRIECR0 */ +#define QMU_RX_LEN_ERR(x) (BIT(16) << (x)) +#define QMU_RX_CS_ERR(x) (BIT(0) << (x)) + +/* U3D_RQERRIR1, U3D_RQERRIER1, U3D_RQERRIESR1, U3D_RQERRIECR1 */ +#define QMU_RX_ZLP_ERR(n) (BIT(16) << (n)) + +/* U3D_CAP_EPINFO */ +#define CAP_RX_EP_NUM(x) (((x) >> 8) & 0x1f) +#define CAP_TX_EP_NUM(x) ((x) & 0x1f) + +/* U3D_MISC_CTRL */ +#define DMA_ADDR_36BIT BIT(31) +#define VBUS_ON BIT(1) +#define VBUS_FRC_EN BIT(0) + + +/*---------------- SSUSB_EPCTL_CSR REGISTER DEFINITION ----------------*/ + +#define U3D_DEVICE_CONF (SSUSB_EPCTL_CSR_BASE + 0x0000) +#define U3D_EP_RST (SSUSB_EPCTL_CSR_BASE + 0x0004) + +#define U3D_DEV_LINK_INTR_ENABLE (SSUSB_EPCTL_CSR_BASE + 0x0050) +#define U3D_DEV_LINK_INTR (SSUSB_EPCTL_CSR_BASE + 0x0054) + +/*---------------- SSUSB_EPCTL_CSR FIELD DEFINITION ----------------*/ + +/* U3D_DEVICE_CONF */ +#define DEV_ADDR_MSK GENMASK(30, 24) +#define DEV_ADDR(x) ((0x7f & (x)) << 24) +#define HW_USB2_3_SEL BIT(18) +#define SW_USB2_3_SEL_EN BIT(17) +#define SW_USB2_3_SEL BIT(16) +#define SSUSB_DEV_SPEED(x) ((x) & 0x7) + +/* U3D_EP_RST */ +#define EP1_IN_RST BIT(17) +#define EP1_OUT_RST BIT(1) +#define EP_RST(is_in, epnum) (((is_in) ? BIT(16) : BIT(0)) << (epnum)) +#define EP0_RST BIT(0) + +/* U3D_DEV_LINK_INTR_ENABLE */ +/* U3D_DEV_LINK_INTR */ +#define SSUSB_DEV_SPEED_CHG_INTR BIT(0) + + +/*---------------- SSUSB_USB3_MAC_CSR REGISTER DEFINITION ----------------*/ + +#define U3D_LTSSM_CTRL (SSUSB_USB3_MAC_CSR_BASE + 0x0010) +#define U3D_USB3_CONFIG (SSUSB_USB3_MAC_CSR_BASE + 0x001C) + +#define U3D_LINK_STATE_MACHINE (SSUSB_USB3_MAC_CSR_BASE + 0x0134) +#define U3D_LTSSM_INTR_ENABLE (SSUSB_USB3_MAC_CSR_BASE + 0x013C) +#define U3D_LTSSM_INTR (SSUSB_USB3_MAC_CSR_BASE + 0x0140) + +#define U3D_U3U2_SWITCH_CTRL (SSUSB_USB3_MAC_CSR_BASE + 0x0170) + +/*---------------- SSUSB_USB3_MAC_CSR FIELD DEFINITION ----------------*/ + +/* U3D_LTSSM_CTRL */ +#define FORCE_POLLING_FAIL BIT(4) +#define FORCE_RXDETECT_FAIL BIT(3) +#define SOFT_U3_EXIT_EN BIT(2) +#define COMPLIANCE_EN BIT(1) +#define U1_GO_U2_EN BIT(0) + +/* U3D_USB3_CONFIG */ +#define USB3_EN BIT(0) + +/* U3D_LINK_STATE_MACHINE */ +#define LTSSM_STATE(x) ((x) & 0x1f) + +/* U3D_LTSSM_INTR_ENABLE */ +/* U3D_LTSSM_INTR */ +#define U3_RESUME_INTR BIT(18) +#define U3_LFPS_TMOUT_INTR BIT(17) +#define VBUS_FALL_INTR BIT(16) +#define VBUS_RISE_INTR BIT(15) +#define RXDET_SUCCESS_INTR BIT(14) +#define EXIT_U3_INTR BIT(13) +#define EXIT_U2_INTR BIT(12) +#define EXIT_U1_INTR BIT(11) +#define ENTER_U3_INTR BIT(10) +#define ENTER_U2_INTR BIT(9) +#define ENTER_U1_INTR BIT(8) +#define ENTER_U0_INTR BIT(7) +#define RECOVERY_INTR BIT(6) +#define WARM_RST_INTR BIT(5) +#define HOT_RST_INTR BIT(4) +#define LOOPBACK_INTR BIT(3) +#define COMPLIANCE_INTR BIT(2) +#define SS_DISABLE_INTR BIT(1) +#define SS_INACTIVE_INTR BIT(0) + +/* U3D_U3U2_SWITCH_CTRL */ +#define SOFTCON_CLR_AUTO_EN BIT(0) + +/*---------------- SSUSB_USB3_SYS_CSR REGISTER DEFINITION ----------------*/ + +#define U3D_LINK_UX_INACT_TIMER (SSUSB_USB3_SYS_CSR_BASE + 0x020C) +#define U3D_LINK_POWER_CONTROL (SSUSB_USB3_SYS_CSR_BASE + 0x0210) +#define U3D_LINK_ERR_COUNT (SSUSB_USB3_SYS_CSR_BASE + 0x0214) +#define U3D_DEV_NOTIF_0 (SSUSB_USB3_SYS_CSR_BASE + 0x0290) +#define U3D_DEV_NOTIF_1 (SSUSB_USB3_SYS_CSR_BASE + 0x0294) + +/*---------------- SSUSB_USB3_SYS_CSR FIELD DEFINITION ----------------*/ + +/* U3D_LINK_UX_INACT_TIMER */ +#define DEV_U2_INACT_TIMEOUT_MSK GENMASK(23, 16) +#define DEV_U2_INACT_TIMEOUT_VALUE(x) (((x) & 0xff) << 16) +#define U2_INACT_TIMEOUT_MSK GENMASK(15, 8) +#define U1_INACT_TIMEOUT_MSK GENMASK(7, 0) +#define U1_INACT_TIMEOUT_VALUE(x) ((x) & 0xff) + +/* U3D_LINK_POWER_CONTROL */ +#define SW_U2_ACCEPT_ENABLE BIT(9) +#define SW_U1_ACCEPT_ENABLE BIT(8) +#define UX_EXIT BIT(5) +#define LGO_U3 BIT(4) +#define LGO_U2 BIT(3) +#define LGO_U1 BIT(2) +#define SW_U2_REQUEST_ENABLE BIT(1) +#define SW_U1_REQUEST_ENABLE BIT(0) + +/* U3D_LINK_ERR_COUNT */ +#define CLR_LINK_ERR_CNT BIT(16) +#define LINK_ERROR_COUNT GENMASK(15, 0) + +/* U3D_DEV_NOTIF_0 */ +#define DEV_NOTIF_TYPE_SPECIFIC_LOW_MSK GENMASK(31, 8) +#define DEV_NOTIF_VAL_FW(x) (((x) & 0xff) << 8) +#define DEV_NOTIF_VAL_LTM(x) (((x) & 0xfff) << 8) +#define DEV_NOTIF_VAL_IAM(x) (((x) & 0xffff) << 8) +#define DEV_NOTIF_TYPE_MSK GENMASK(7, 4) +/* Notification Type */ +#define TYPE_FUNCTION_WAKE (0x1 << 4) +#define TYPE_LATENCY_TOLERANCE_MESSAGE (0x2 << 4) +#define TYPE_BUS_INTERVAL_ADJUST_MESSAGE (0x3 << 4) +#define TYPE_HOST_ROLE_REQUEST (0x4 << 4) +#define TYPE_SUBLINK_SPEED (0x5 << 4) +#define SEND_DEV_NOTIF BIT(0) + +/*---------------- SSUSB_USB2_CSR REGISTER DEFINITION ----------------*/ + +#define U3D_POWER_MANAGEMENT (SSUSB_USB2_CSR_BASE + 0x0004) +#define U3D_DEVICE_CONTROL (SSUSB_USB2_CSR_BASE + 0x000C) +#define U3D_USB2_TEST_MODE (SSUSB_USB2_CSR_BASE + 0x0014) +#define U3D_COMMON_USB_INTR_ENABLE (SSUSB_USB2_CSR_BASE + 0x0018) +#define U3D_COMMON_USB_INTR (SSUSB_USB2_CSR_BASE + 0x001C) +#define U3D_LINK_RESET_INFO (SSUSB_USB2_CSR_BASE + 0x0024) +#define U3D_USB20_FRAME_NUM (SSUSB_USB2_CSR_BASE + 0x003C) +#define U3D_USB20_LPM_PARAMETER (SSUSB_USB2_CSR_BASE + 0x0044) +#define U3D_USB20_MISC_CONTROL (SSUSB_USB2_CSR_BASE + 0x004C) +#define U3D_USB20_OPSTATE (SSUSB_USB2_CSR_BASE + 0x0060) + +/*---------------- SSUSB_USB2_CSR FIELD DEFINITION ----------------*/ + +/* U3D_POWER_MANAGEMENT */ +#define LPM_BESL_STALL BIT(14) +#define LPM_BESLD_STALL BIT(13) +#define LPM_RWP BIT(11) +#define LPM_HRWE BIT(10) +#define LPM_MODE(x) (((x) & 0x3) << 8) +#define ISO_UPDATE BIT(7) +#define SOFT_CONN BIT(6) +#define HS_ENABLE BIT(5) +#define RESUME BIT(2) +#define SUSPENDM_ENABLE BIT(0) + +/* U3D_DEVICE_CONTROL */ +#define DC_HOSTREQ BIT(1) +#define DC_SESSION BIT(0) + +/* U3D_USB2_TEST_MODE */ +#define U2U3_AUTO_SWITCH BIT(10) +#define LPM_FORCE_STALL BIT(8) +#define FIFO_ACCESS BIT(6) +#define FORCE_FS BIT(5) +#define FORCE_HS BIT(4) +#define TEST_PACKET_MODE BIT(3) +#define TEST_K_MODE BIT(2) +#define TEST_J_MODE BIT(1) +#define TEST_SE0_NAK_MODE BIT(0) + +/* U3D_COMMON_USB_INTR_ENABLE */ +/* U3D_COMMON_USB_INTR */ +#define LPM_RESUME_INTR BIT(9) +#define LPM_INTR BIT(8) +#define DISCONN_INTR BIT(5) +#define CONN_INTR BIT(4) +#define SOF_INTR BIT(3) +#define RESET_INTR BIT(2) +#define RESUME_INTR BIT(1) +#define SUSPEND_INTR BIT(0) + +/* U3D_LINK_RESET_INFO */ +#define WTCHRP_MSK GENMASK(19, 16) + +/* U3D_USB20_LPM_PARAMETER */ +#define LPM_BESLCK_U3(x) (((x) & 0xf) << 12) +#define LPM_BESLCK(x) (((x) & 0xf) << 8) +#define LPM_BESLDCK(x) (((x) & 0xf) << 4) +#define LPM_BESL GENMASK(3, 0) + +/* U3D_USB20_MISC_CONTROL */ +#define LPM_U3_ACK_EN BIT(0) + +/*---------------- SSUSB_SIFSLV_IPPC REGISTER DEFINITION ----------------*/ + +#define U3D_SSUSB_IP_PW_CTRL0 (SSUSB_SIFSLV_IPPC_BASE + 0x0000) +#define U3D_SSUSB_IP_PW_CTRL1 (SSUSB_SIFSLV_IPPC_BASE + 0x0004) +#define U3D_SSUSB_IP_PW_CTRL2 (SSUSB_SIFSLV_IPPC_BASE + 0x0008) +#define U3D_SSUSB_IP_PW_CTRL3 (SSUSB_SIFSLV_IPPC_BASE + 0x000C) +#define U3D_SSUSB_IP_PW_STS1 (SSUSB_SIFSLV_IPPC_BASE + 0x0010) +#define U3D_SSUSB_IP_PW_STS2 (SSUSB_SIFSLV_IPPC_BASE + 0x0014) +#define U3D_SSUSB_OTG_STS (SSUSB_SIFSLV_IPPC_BASE + 0x0018) +#define U3D_SSUSB_OTG_STS_CLR (SSUSB_SIFSLV_IPPC_BASE + 0x001C) +#define U3D_SSUSB_IP_XHCI_CAP (SSUSB_SIFSLV_IPPC_BASE + 0x0024) +#define U3D_SSUSB_IP_DEV_CAP (SSUSB_SIFSLV_IPPC_BASE + 0x0028) +#define U3D_SSUSB_OTG_INT_EN (SSUSB_SIFSLV_IPPC_BASE + 0x002C) +#define U3D_SSUSB_U3_CTRL_0P (SSUSB_SIFSLV_IPPC_BASE + 0x0030) +#define U3D_SSUSB_U2_CTRL_0P (SSUSB_SIFSLV_IPPC_BASE + 0x0050) +#define U3D_SSUSB_REF_CK_CTRL (SSUSB_SIFSLV_IPPC_BASE + 0x008C) +#define U3D_SSUSB_DEV_RST_CTRL (SSUSB_SIFSLV_IPPC_BASE + 0x0098) +#define U3D_SSUSB_HW_ID (SSUSB_SIFSLV_IPPC_BASE + 0x00A0) +#define U3D_SSUSB_HW_SUB_ID (SSUSB_SIFSLV_IPPC_BASE + 0x00A4) +#define U3D_SSUSB_IP_TRUNK_VERS (U3D_SSUSB_HW_SUB_ID) +#define U3D_SSUSB_PRB_CTRL0 (SSUSB_SIFSLV_IPPC_BASE + 0x00B0) +#define U3D_SSUSB_PRB_CTRL1 (SSUSB_SIFSLV_IPPC_BASE + 0x00B4) +#define U3D_SSUSB_PRB_CTRL2 (SSUSB_SIFSLV_IPPC_BASE + 0x00B8) +#define U3D_SSUSB_PRB_CTRL3 (SSUSB_SIFSLV_IPPC_BASE + 0x00BC) +#define U3D_SSUSB_PRB_CTRL4 (SSUSB_SIFSLV_IPPC_BASE + 0x00C0) +#define U3D_SSUSB_PRB_CTRL5 (SSUSB_SIFSLV_IPPC_BASE + 0x00C4) +#define U3D_SSUSB_IP_SPARE0 (SSUSB_SIFSLV_IPPC_BASE + 0x00C8) + +/*---------------- SSUSB_SIFSLV_IPPC FIELD DEFINITION ----------------*/ + +/* U3D_SSUSB_IP_PW_CTRL0 */ +#define SSUSB_IP_SW_RST BIT(0) + +/* U3D_SSUSB_IP_PW_CTRL1 */ +#define SSUSB_IP_HOST_PDN BIT(0) + +/* U3D_SSUSB_IP_PW_CTRL2 */ +#define SSUSB_IP_DEV_PDN BIT(0) + +/* U3D_SSUSB_IP_PW_CTRL3 */ +#define SSUSB_IP_PCIE_PDN BIT(0) + +/* U3D_SSUSB_IP_PW_STS1 */ +#define SSUSB_IP_SLEEP_STS BIT(30) +#define SSUSB_U3_MAC_RST_B_STS BIT(16) +#define SSUSB_XHCI_RST_B_STS BIT(11) +#define SSUSB_SYS125_RST_B_STS BIT(10) +#define SSUSB_REF_RST_B_STS BIT(8) +#define SSUSB_SYSPLL_STABLE BIT(0) + +/* U3D_SSUSB_IP_PW_STS2 */ +#define SSUSB_U2_MAC_SYS_RST_B_STS BIT(0) + +/* U3D_SSUSB_OTG_STS */ +#define SSUSB_VBUS_VALID BIT(9) + +/* U3D_SSUSB_OTG_STS_CLR */ +#define SSUSB_VBUS_INTR_CLR BIT(6) + +/* U3D_SSUSB_IP_XHCI_CAP */ +#define SSUSB_IP_XHCI_U2_PORT_NUM(x) (((x) >> 8) & 0xff) +#define SSUSB_IP_XHCI_U3_PORT_NUM(x) ((x) & 0xff) + +/* U3D_SSUSB_IP_DEV_CAP */ +#define SSUSB_IP_DEV_U3_PORT_NUM(x) ((x) & 0xff) + +/* U3D_SSUSB_OTG_INT_EN */ +#define SSUSB_VBUS_CHG_INT_A_EN BIT(7) +#define SSUSB_VBUS_CHG_INT_B_EN BIT(6) + +/* U3D_SSUSB_U3_CTRL_0P */ +#define SSUSB_U3_PORT_SSP_SPEED BIT(9) +#define SSUSB_U3_PORT_DUAL_MODE BIT(7) +#define SSUSB_U3_PORT_HOST_SEL BIT(2) +#define SSUSB_U3_PORT_PDN BIT(1) +#define SSUSB_U3_PORT_DIS BIT(0) + +/* U3D_SSUSB_U2_CTRL_0P */ +#define SSUSB_U2_PORT_RG_IDDIG BIT(12) +#define SSUSB_U2_PORT_FORCE_IDDIG BIT(11) +#define SSUSB_U2_PORT_VBUSVALID BIT(9) +#define SSUSB_U2_PORT_OTG_SEL BIT(7) +#define SSUSB_U2_PORT_HOST BIT(2) +#define SSUSB_U2_PORT_PDN BIT(1) +#define SSUSB_U2_PORT_DIS BIT(0) +#define SSUSB_U2_PORT_HOST_SEL (SSUSB_U2_PORT_VBUSVALID | SSUSB_U2_PORT_HOST) + +/* U3D_SSUSB_DEV_RST_CTRL */ +#define SSUSB_DEV_SW_RST BIT(0) + +/* U3D_SSUSB_IP_TRUNK_VERS */ +#define IP_TRUNK_VERS(x) (((x) >> 16) & 0xffff) + +#endif /* _SSUSB_HW_REGS_H_ */ diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c new file mode 100644 index 000000000..b4f4c776a --- /dev/null +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -0,0 +1,635 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016 MediaTek Inc. + * + * Author: Chunfeng Yun + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtu3.h" +#include "mtu3_dr.h" +#include "mtu3_debug.h" + +/* u2-port0 should be powered on and enabled; */ +int ssusb_check_clocks(struct ssusb_mtk *ssusb, u32 ex_clks) +{ + void __iomem *ibase = ssusb->ippc_base; + u32 value, check_val; + int ret; + + check_val = ex_clks | SSUSB_SYS125_RST_B_STS | SSUSB_SYSPLL_STABLE | + SSUSB_REF_RST_B_STS; + + ret = readl_poll_timeout(ibase + U3D_SSUSB_IP_PW_STS1, value, + (check_val == (value & check_val)), 100, 20000); + if (ret) { + dev_err(ssusb->dev, "clks of sts1 are not stable!\n"); + return ret; + } + + ret = readl_poll_timeout(ibase + U3D_SSUSB_IP_PW_STS2, value, + (value & SSUSB_U2_MAC_SYS_RST_B_STS), 100, 10000); + if (ret) { + dev_err(ssusb->dev, "mac2 clock is not stable\n"); + return ret; + } + + return 0; +} + +static int wait_for_ip_sleep(struct ssusb_mtk *ssusb) +{ + bool sleep_check = true; + u32 value; + int ret; + + if (!ssusb->is_host) + sleep_check = ssusb_gadget_ip_sleep_check(ssusb); + + if (!sleep_check) + return 0; + + /* wait for ip enter sleep mode */ + ret = readl_poll_timeout(ssusb->ippc_base + U3D_SSUSB_IP_PW_STS1, value, + (value & SSUSB_IP_SLEEP_STS), 100, 100000); + if (ret) { + dev_err(ssusb->dev, "ip sleep failed!!!\n"); + ret = -EBUSY; + } else { + /* workaround: avoid wrong wakeup signal latch for some soc */ + usleep_range(100, 200); + } + + return ret; +} + +static int ssusb_phy_init(struct ssusb_mtk *ssusb) +{ + int i; + int ret; + + for (i = 0; i < ssusb->num_phys; i++) { + ret = phy_init(ssusb->phys[i]); + if (ret) + goto exit_phy; + } + return 0; + +exit_phy: + for (; i > 0; i--) + phy_exit(ssusb->phys[i - 1]); + + return ret; +} + +static int ssusb_phy_exit(struct ssusb_mtk *ssusb) +{ + int i; + + for (i = 0; i < ssusb->num_phys; i++) + phy_exit(ssusb->phys[i]); + + return 0; +} + +static int ssusb_phy_power_on(struct ssusb_mtk *ssusb) +{ + int i; + int ret; + + for (i = 0; i < ssusb->num_phys; i++) { + ret = phy_power_on(ssusb->phys[i]); + if (ret) + goto power_off_phy; + } + return 0; + +power_off_phy: + for (; i > 0; i--) + phy_power_off(ssusb->phys[i - 1]); + + return ret; +} + +static void ssusb_phy_power_off(struct ssusb_mtk *ssusb) +{ + unsigned int i; + + for (i = 0; i < ssusb->num_phys; i++) + phy_power_off(ssusb->phys[i]); +} + +static int ssusb_rscs_init(struct ssusb_mtk *ssusb) +{ + int ret = 0; + + ret = regulator_enable(ssusb->vusb33); + if (ret) { + dev_err(ssusb->dev, "failed to enable vusb33\n"); + goto vusb33_err; + } + + ret = clk_bulk_prepare_enable(BULK_CLKS_CNT, ssusb->clks); + if (ret) + goto clks_err; + + ret = ssusb_phy_init(ssusb); + if (ret) { + dev_err(ssusb->dev, "failed to init phy\n"); + goto phy_init_err; + } + + ret = ssusb_phy_power_on(ssusb); + if (ret) { + dev_err(ssusb->dev, "failed to power on phy\n"); + goto phy_err; + } + + return 0; + +phy_err: + ssusb_phy_exit(ssusb); +phy_init_err: + clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); +clks_err: + regulator_disable(ssusb->vusb33); +vusb33_err: + return ret; +} + +static void ssusb_rscs_exit(struct ssusb_mtk *ssusb) +{ + clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); + regulator_disable(ssusb->vusb33); + ssusb_phy_power_off(ssusb); + ssusb_phy_exit(ssusb); +} + +static void ssusb_ip_sw_reset(struct ssusb_mtk *ssusb) +{ + /* reset whole ip (xhci & u3d) */ + mtu3_setbits(ssusb->ippc_base, U3D_SSUSB_IP_PW_CTRL0, SSUSB_IP_SW_RST); + udelay(1); + mtu3_clrbits(ssusb->ippc_base, U3D_SSUSB_IP_PW_CTRL0, SSUSB_IP_SW_RST); + + /* + * device ip may be powered on in firmware/BROM stage before entering + * kernel stage; + * power down device ip, otherwise ip-sleep will fail when working as + * host only mode + */ + mtu3_setbits(ssusb->ippc_base, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN); +} + +static void ssusb_u3_drd_check(struct ssusb_mtk *ssusb) +{ + struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; + u32 dev_u3p_num; + u32 host_u3p_num; + u32 value; + + /* u3 port0 is disabled */ + if (ssusb->u3p_dis_msk & BIT(0)) { + otg_sx->is_u3_drd = false; + goto out; + } + + value = mtu3_readl(ssusb->ippc_base, U3D_SSUSB_IP_DEV_CAP); + dev_u3p_num = SSUSB_IP_DEV_U3_PORT_NUM(value); + + value = mtu3_readl(ssusb->ippc_base, U3D_SSUSB_IP_XHCI_CAP); + host_u3p_num = SSUSB_IP_XHCI_U3_PORT_NUM(value); + + otg_sx->is_u3_drd = !!(dev_u3p_num && host_u3p_num); + +out: + dev_info(ssusb->dev, "usb3-drd: %d\n", otg_sx->is_u3_drd); +} + +static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb) +{ + struct device_node *node = pdev->dev.of_node; + struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; + struct clk_bulk_data *clks = ssusb->clks; + struct device *dev = &pdev->dev; + int i; + int ret; + + ssusb->vusb33 = devm_regulator_get(dev, "vusb33"); + if (IS_ERR(ssusb->vusb33)) { + dev_err(dev, "failed to get vusb33\n"); + return PTR_ERR(ssusb->vusb33); + } + + clks[0].id = "sys_ck"; + clks[1].id = "ref_ck"; + clks[2].id = "mcu_ck"; + clks[3].id = "dma_ck"; + clks[4].id = "xhci_ck"; + clks[5].id = "frmcnt_ck"; + ret = devm_clk_bulk_get_optional(dev, BULK_CLKS_CNT, clks); + if (ret) + return ret; + + ssusb->num_phys = of_count_phandle_with_args(node, + "phys", "#phy-cells"); + if (ssusb->num_phys > 0) { + ssusb->phys = devm_kcalloc(dev, ssusb->num_phys, + sizeof(*ssusb->phys), GFP_KERNEL); + if (!ssusb->phys) + return -ENOMEM; + } else { + ssusb->num_phys = 0; + } + + for (i = 0; i < ssusb->num_phys; i++) { + ssusb->phys[i] = devm_of_phy_get_by_index(dev, node, i); + if (IS_ERR(ssusb->phys[i])) { + dev_err(dev, "failed to get phy-%d\n", i); + return PTR_ERR(ssusb->phys[i]); + } + } + + ssusb->ippc_base = devm_platform_ioremap_resource_byname(pdev, "ippc"); + if (IS_ERR(ssusb->ippc_base)) + return PTR_ERR(ssusb->ippc_base); + + ssusb->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup"); + if (ssusb->wakeup_irq == -EPROBE_DEFER) + return ssusb->wakeup_irq; + + ssusb->dr_mode = usb_get_dr_mode(dev); + if (ssusb->dr_mode == USB_DR_MODE_UNKNOWN) + ssusb->dr_mode = USB_DR_MODE_OTG; + + of_property_read_u32(node, "mediatek,u3p-dis-msk", &ssusb->u3p_dis_msk); + + if (ssusb->dr_mode == USB_DR_MODE_PERIPHERAL) + goto out; + + /* if host role is supported */ + ret = ssusb_wakeup_of_property_parse(ssusb, node); + if (ret) { + dev_err(dev, "failed to parse uwk property\n"); + return ret; + } + + /* optional property, ignore the error if it does not exist */ + of_property_read_u32(node, "mediatek,u2p-dis-msk", + &ssusb->u2p_dis_msk); + + otg_sx->vbus = devm_regulator_get(dev, "vbus"); + if (IS_ERR(otg_sx->vbus)) { + dev_err(dev, "failed to get vbus\n"); + return PTR_ERR(otg_sx->vbus); + } + + if (ssusb->dr_mode == USB_DR_MODE_HOST) + goto out; + + /* if dual-role mode is supported */ + otg_sx->manual_drd_enabled = + of_property_read_bool(node, "enable-manual-drd"); + otg_sx->role_sw_used = of_property_read_bool(node, "usb-role-switch"); + + /* can't disable port0 when use dual-role mode */ + ssusb->u2p_dis_msk &= ~0x1; + + if (otg_sx->role_sw_used || otg_sx->manual_drd_enabled) + goto out; + + if (of_property_present(node, "extcon")) { + otg_sx->edev = extcon_get_edev_by_phandle(ssusb->dev, 0); + if (IS_ERR(otg_sx->edev)) { + return dev_err_probe(dev, PTR_ERR(otg_sx->edev), + "couldn't get extcon device\n"); + } + } + +out: + dev_info(dev, "dr_mode: %d, drd: %s\n", ssusb->dr_mode, + otg_sx->manual_drd_enabled ? "manual" : "auto"); + dev_info(dev, "u2p_dis_msk: %x, u3p_dis_msk: %x\n", + ssusb->u2p_dis_msk, ssusb->u3p_dis_msk); + + return 0; +} + +static int mtu3_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct ssusb_mtk *ssusb; + int ret = -ENOMEM; + + /* all elements are set to ZERO as default value */ + ssusb = devm_kzalloc(dev, sizeof(*ssusb), GFP_KERNEL); + if (!ssusb) + return -ENOMEM; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(dev, "No suitable DMA config available\n"); + return -ENOTSUPP; + } + + platform_set_drvdata(pdev, ssusb); + ssusb->dev = dev; + + ret = get_ssusb_rscs(pdev, ssusb); + if (ret) + return ret; + + ssusb_debugfs_create_root(ssusb); + + /* enable power domain */ + pm_runtime_set_active(dev); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, 4000); + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + device_init_wakeup(dev, true); + + ret = ssusb_rscs_init(ssusb); + if (ret) + goto comm_init_err; + + if (ssusb->wakeup_irq > 0) { + ret = dev_pm_set_dedicated_wake_irq_reverse(dev, ssusb->wakeup_irq); + if (ret) { + dev_err(dev, "failed to set wakeup irq %d\n", ssusb->wakeup_irq); + goto comm_exit; + } + dev_info(dev, "wakeup irq %d\n", ssusb->wakeup_irq); + } + + ret = device_reset_optional(dev); + if (ret) { + dev_err_probe(dev, ret, "failed to reset controller\n"); + goto comm_exit; + } + + ssusb_ip_sw_reset(ssusb); + ssusb_u3_drd_check(ssusb); + + if (IS_ENABLED(CONFIG_USB_MTU3_HOST)) + ssusb->dr_mode = USB_DR_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_MTU3_GADGET)) + ssusb->dr_mode = USB_DR_MODE_PERIPHERAL; + + /* default as host */ + ssusb->is_host = !(ssusb->dr_mode == USB_DR_MODE_PERIPHERAL); + + switch (ssusb->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + ret = ssusb_gadget_init(ssusb); + if (ret) { + dev_err(dev, "failed to initialize gadget\n"); + goto comm_exit; + } + break; + case USB_DR_MODE_HOST: + ret = ssusb_host_init(ssusb, node); + if (ret) { + dev_err(dev, "failed to initialize host\n"); + goto comm_exit; + } + break; + case USB_DR_MODE_OTG: + ret = ssusb_gadget_init(ssusb); + if (ret) { + dev_err(dev, "failed to initialize gadget\n"); + goto comm_exit; + } + + ret = ssusb_host_init(ssusb, node); + if (ret) { + dev_err(dev, "failed to initialize host\n"); + goto gadget_exit; + } + + ret = ssusb_otg_switch_init(ssusb); + if (ret) { + dev_err(dev, "failed to initialize switch\n"); + goto host_exit; + } + break; + default: + dev_err(dev, "unsupported mode: %d\n", ssusb->dr_mode); + ret = -EINVAL; + goto comm_exit; + } + + device_enable_async_suspend(dev); + pm_runtime_put_autosuspend(dev); + pm_runtime_forbid(dev); + + return 0; + +host_exit: + ssusb_host_exit(ssusb); +gadget_exit: + ssusb_gadget_exit(ssusb); +comm_exit: + ssusb_rscs_exit(ssusb); +comm_init_err: + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + ssusb_debugfs_remove_root(ssusb); + + return ret; +} + +static void mtu3_remove(struct platform_device *pdev) +{ + struct ssusb_mtk *ssusb = platform_get_drvdata(pdev); + + pm_runtime_get_sync(&pdev->dev); + + switch (ssusb->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + ssusb_gadget_exit(ssusb); + break; + case USB_DR_MODE_HOST: + ssusb_host_exit(ssusb); + break; + case USB_DR_MODE_OTG: + ssusb_otg_switch_exit(ssusb); + ssusb_gadget_exit(ssusb); + ssusb_host_exit(ssusb); + break; + case USB_DR_MODE_UNKNOWN: + /* + * This cannot happen because with dr_mode == + * USB_DR_MODE_UNKNOWN, .probe() doesn't succeed and so + * .remove() wouldn't be called at all. However (little + * surprising) the compiler isn't smart enough to see that, so + * we explicitly have this case item to not make the compiler + * wail about an unhandled enumeration value. + */ + break; + } + + ssusb_rscs_exit(ssusb); + ssusb_debugfs_remove_root(ssusb); + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); +} + +static int resume_ip_and_ports(struct ssusb_mtk *ssusb, pm_message_t msg) +{ + switch (ssusb->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + ssusb_gadget_resume(ssusb, msg); + break; + case USB_DR_MODE_HOST: + ssusb_host_resume(ssusb, false); + break; + case USB_DR_MODE_OTG: + ssusb_host_resume(ssusb, !ssusb->is_host); + if (!ssusb->is_host) + ssusb_gadget_resume(ssusb, msg); + + break; + default: + return -EINVAL; + } + + return 0; +} + +static int mtu3_suspend_common(struct device *dev, pm_message_t msg) +{ + struct ssusb_mtk *ssusb = dev_get_drvdata(dev); + int ret = 0; + + dev_dbg(dev, "%s\n", __func__); + + switch (ssusb->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + ret = ssusb_gadget_suspend(ssusb, msg); + if (ret) + goto err; + + break; + case USB_DR_MODE_HOST: + ssusb_host_suspend(ssusb); + break; + case USB_DR_MODE_OTG: + if (!ssusb->is_host) { + ret = ssusb_gadget_suspend(ssusb, msg); + if (ret) + goto err; + } + ssusb_host_suspend(ssusb); + break; + default: + return -EINVAL; + } + + ret = wait_for_ip_sleep(ssusb); + if (ret) + goto sleep_err; + + ssusb_phy_power_off(ssusb); + clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); + if (device_may_wakeup(dev) && (ssusb->is_host || PMSG_IS_AUTO(msg))) + ssusb_wakeup_set(ssusb, true); + return 0; + +sleep_err: + resume_ip_and_ports(ssusb, msg); +err: + return ret; +} + +static int mtu3_resume_common(struct device *dev, pm_message_t msg) +{ + struct ssusb_mtk *ssusb = dev_get_drvdata(dev); + int ret; + + dev_dbg(dev, "%s\n", __func__); + + ssusb_wakeup_set(ssusb, false); + ret = clk_bulk_prepare_enable(BULK_CLKS_CNT, ssusb->clks); + if (ret) + goto clks_err; + + ret = ssusb_phy_power_on(ssusb); + if (ret) + goto phy_err; + + return resume_ip_and_ports(ssusb, msg); + +phy_err: + clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); +clks_err: + return ret; +} + +static int __maybe_unused mtu3_suspend(struct device *dev) +{ + return mtu3_suspend_common(dev, PMSG_SUSPEND); +} + +static int __maybe_unused mtu3_resume(struct device *dev) +{ + return mtu3_resume_common(dev, PMSG_SUSPEND); +} + +static int __maybe_unused mtu3_runtime_suspend(struct device *dev) +{ + if (!device_may_wakeup(dev)) + return 0; + + return mtu3_suspend_common(dev, PMSG_AUTO_SUSPEND); +} + +static int __maybe_unused mtu3_runtime_resume(struct device *dev) +{ + if (!device_may_wakeup(dev)) + return 0; + + return mtu3_resume_common(dev, PMSG_AUTO_SUSPEND); +} + +static const struct dev_pm_ops mtu3_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mtu3_suspend, mtu3_resume) + SET_RUNTIME_PM_OPS(mtu3_runtime_suspend, + mtu3_runtime_resume, NULL) +}; + +#define DEV_PM_OPS (IS_ENABLED(CONFIG_PM) ? &mtu3_pm_ops : NULL) + +static const struct of_device_id mtu3_of_match[] = { + {.compatible = "mediatek,mt8173-mtu3",}, + {.compatible = "mediatek,mtu3",}, + {}, +}; +MODULE_DEVICE_TABLE(of, mtu3_of_match); + +static struct platform_driver mtu3_driver = { + .probe = mtu3_probe, + .remove = mtu3_remove, + .driver = { + .name = MTU3_DRIVER_NAME, + .pm = DEV_PM_OPS, + .of_match_table = mtu3_of_match, + }, +}; +module_platform_driver(mtu3_driver); + +MODULE_AUTHOR("Chunfeng Yun "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MediaTek USB3 DRD Controller Driver"); diff --git a/drivers/usb/mtu3/mtu3_qmu.c b/drivers/usb/mtu3/mtu3_qmu.c new file mode 100644 index 000000000..03f26589b --- /dev/null +++ b/drivers/usb/mtu3/mtu3_qmu.c @@ -0,0 +1,686 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mtu3_qmu.c - Queue Management Unit driver for device controller + * + * Copyright (C) 2016 MediaTek Inc. + * + * Author: Chunfeng Yun + */ + +/* + * Queue Management Unit (QMU) is designed to unload SW effort + * to serve DMA interrupts. + * By preparing General Purpose Descriptor (GPD) and Buffer Descriptor (BD), + * SW links data buffers and triggers QMU to send / receive data to + * host / from device at a time. + * And now only GPD is supported. + * + * For more detailed information, please refer to QMU Programming Guide + */ + +#include +#include + +#include "mtu3.h" +#include "mtu3_trace.h" + +#define QMU_CHECKSUM_LEN 16 + +#define GPD_FLAGS_HWO BIT(0) +#define GPD_FLAGS_BDP BIT(1) +#define GPD_FLAGS_BPS BIT(2) +#define GPD_FLAGS_ZLP BIT(6) +#define GPD_FLAGS_IOC BIT(7) +#define GET_GPD_HWO(gpd) (le32_to_cpu((gpd)->dw0_info) & GPD_FLAGS_HWO) + +#define GPD_RX_BUF_LEN_OG(x) (((x) & 0xffff) << 16) +#define GPD_RX_BUF_LEN_EL(x) (((x) & 0xfffff) << 12) +#define GPD_RX_BUF_LEN(mtu, x) \ +({ \ + typeof(x) x_ = (x); \ + ((mtu)->gen2cp) ? GPD_RX_BUF_LEN_EL(x_) : GPD_RX_BUF_LEN_OG(x_); \ +}) + +#define GPD_DATA_LEN_OG(x) ((x) & 0xffff) +#define GPD_DATA_LEN_EL(x) ((x) & 0xfffff) +#define GPD_DATA_LEN(mtu, x) \ +({ \ + typeof(x) x_ = (x); \ + ((mtu)->gen2cp) ? GPD_DATA_LEN_EL(x_) : GPD_DATA_LEN_OG(x_); \ +}) + +#define GPD_EXT_FLAG_ZLP BIT(29) +#define GPD_EXT_NGP_OG(x) (((x) & 0xf) << 20) +#define GPD_EXT_BUF_OG(x) (((x) & 0xf) << 16) +#define GPD_EXT_NGP_EL(x) (((x) & 0xf) << 28) +#define GPD_EXT_BUF_EL(x) (((x) & 0xf) << 24) +#define GPD_EXT_NGP(mtu, x) \ +({ \ + typeof(x) x_ = (x); \ + ((mtu)->gen2cp) ? GPD_EXT_NGP_EL(x_) : GPD_EXT_NGP_OG(x_); \ +}) + +#define GPD_EXT_BUF(mtu, x) \ +({ \ + typeof(x) x_ = (x); \ + ((mtu)->gen2cp) ? GPD_EXT_BUF_EL(x_) : GPD_EXT_BUF_OG(x_); \ +}) + +#define HILO_GEN64(hi, lo) (((u64)(hi) << 32) + (lo)) +#define HILO_DMA(hi, lo) \ + ((dma_addr_t)HILO_GEN64((le32_to_cpu(hi)), (le32_to_cpu(lo)))) + +static dma_addr_t read_txq_cur_addr(void __iomem *mbase, u8 epnum) +{ + u32 txcpr; + u32 txhiar; + + txcpr = mtu3_readl(mbase, USB_QMU_TQCPR(epnum)); + txhiar = mtu3_readl(mbase, USB_QMU_TQHIAR(epnum)); + + return HILO_DMA(QMU_CUR_GPD_ADDR_HI(txhiar), txcpr); +} + +static dma_addr_t read_rxq_cur_addr(void __iomem *mbase, u8 epnum) +{ + u32 rxcpr; + u32 rxhiar; + + rxcpr = mtu3_readl(mbase, USB_QMU_RQCPR(epnum)); + rxhiar = mtu3_readl(mbase, USB_QMU_RQHIAR(epnum)); + + return HILO_DMA(QMU_CUR_GPD_ADDR_HI(rxhiar), rxcpr); +} + +static void write_txq_start_addr(void __iomem *mbase, u8 epnum, dma_addr_t dma) +{ + u32 tqhiar; + + mtu3_writel(mbase, USB_QMU_TQSAR(epnum), + cpu_to_le32(lower_32_bits(dma))); + tqhiar = mtu3_readl(mbase, USB_QMU_TQHIAR(epnum)); + tqhiar &= ~QMU_START_ADDR_HI_MSK; + tqhiar |= QMU_START_ADDR_HI(upper_32_bits(dma)); + mtu3_writel(mbase, USB_QMU_TQHIAR(epnum), tqhiar); +} + +static void write_rxq_start_addr(void __iomem *mbase, u8 epnum, dma_addr_t dma) +{ + u32 rqhiar; + + mtu3_writel(mbase, USB_QMU_RQSAR(epnum), + cpu_to_le32(lower_32_bits(dma))); + rqhiar = mtu3_readl(mbase, USB_QMU_RQHIAR(epnum)); + rqhiar &= ~QMU_START_ADDR_HI_MSK; + rqhiar |= QMU_START_ADDR_HI(upper_32_bits(dma)); + mtu3_writel(mbase, USB_QMU_RQHIAR(epnum), rqhiar); +} + +static struct qmu_gpd *gpd_dma_to_virt(struct mtu3_gpd_ring *ring, + dma_addr_t dma_addr) +{ + dma_addr_t dma_base = ring->dma; + struct qmu_gpd *gpd_head = ring->start; + u32 offset = (dma_addr - dma_base) / sizeof(*gpd_head); + + if (offset >= MAX_GPD_NUM) + return NULL; + + return gpd_head + offset; +} + +static dma_addr_t gpd_virt_to_dma(struct mtu3_gpd_ring *ring, + struct qmu_gpd *gpd) +{ + dma_addr_t dma_base = ring->dma; + struct qmu_gpd *gpd_head = ring->start; + u32 offset; + + offset = gpd - gpd_head; + if (offset >= MAX_GPD_NUM) + return 0; + + return dma_base + (offset * sizeof(*gpd)); +} + +static void gpd_ring_init(struct mtu3_gpd_ring *ring, struct qmu_gpd *gpd) +{ + ring->start = gpd; + ring->enqueue = gpd; + ring->dequeue = gpd; + ring->end = gpd + MAX_GPD_NUM - 1; +} + +static void reset_gpd_list(struct mtu3_ep *mep) +{ + struct mtu3_gpd_ring *ring = &mep->gpd_ring; + struct qmu_gpd *gpd = ring->start; + + if (gpd) { + gpd->dw0_info &= cpu_to_le32(~GPD_FLAGS_HWO); + gpd_ring_init(ring, gpd); + } +} + +int mtu3_gpd_ring_alloc(struct mtu3_ep *mep) +{ + struct qmu_gpd *gpd; + struct mtu3_gpd_ring *ring = &mep->gpd_ring; + + /* software own all gpds as default */ + gpd = dma_pool_zalloc(mep->mtu->qmu_gpd_pool, GFP_ATOMIC, &ring->dma); + if (gpd == NULL) + return -ENOMEM; + + gpd_ring_init(ring, gpd); + + return 0; +} + +void mtu3_gpd_ring_free(struct mtu3_ep *mep) +{ + struct mtu3_gpd_ring *ring = &mep->gpd_ring; + + dma_pool_free(mep->mtu->qmu_gpd_pool, + ring->start, ring->dma); + memset(ring, 0, sizeof(*ring)); +} + +void mtu3_qmu_resume(struct mtu3_ep *mep) +{ + struct mtu3 *mtu = mep->mtu; + void __iomem *mbase = mtu->mac_base; + int epnum = mep->epnum; + u32 offset; + + offset = mep->is_in ? USB_QMU_TQCSR(epnum) : USB_QMU_RQCSR(epnum); + + mtu3_writel(mbase, offset, QMU_Q_RESUME); + if (!(mtu3_readl(mbase, offset) & QMU_Q_ACTIVE)) + mtu3_writel(mbase, offset, QMU_Q_RESUME); +} + +static struct qmu_gpd *advance_enq_gpd(struct mtu3_gpd_ring *ring) +{ + if (ring->enqueue < ring->end) + ring->enqueue++; + else + ring->enqueue = ring->start; + + return ring->enqueue; +} + +/* @dequeue may be NULL if ring is unallocated or freed */ +static struct qmu_gpd *advance_deq_gpd(struct mtu3_gpd_ring *ring) +{ + if (ring->dequeue < ring->end) + ring->dequeue++; + else + ring->dequeue = ring->start; + + return ring->dequeue; +} + +/* check if a ring is empty */ +static bool gpd_ring_empty(struct mtu3_gpd_ring *ring) +{ + struct qmu_gpd *enq = ring->enqueue; + struct qmu_gpd *next; + + if (ring->enqueue < ring->end) + next = enq + 1; + else + next = ring->start; + + /* one gpd is reserved to simplify gpd preparation */ + return next == ring->dequeue; +} + +int mtu3_prepare_transfer(struct mtu3_ep *mep) +{ + return gpd_ring_empty(&mep->gpd_ring); +} + +static int mtu3_prepare_tx_gpd(struct mtu3_ep *mep, struct mtu3_request *mreq) +{ + struct qmu_gpd *enq; + struct mtu3_gpd_ring *ring = &mep->gpd_ring; + struct qmu_gpd *gpd = ring->enqueue; + struct usb_request *req = &mreq->request; + struct mtu3 *mtu = mep->mtu; + dma_addr_t enq_dma; + u32 ext_addr; + + gpd->dw0_info = 0; /* SW own it */ + gpd->buffer = cpu_to_le32(lower_32_bits(req->dma)); + ext_addr = GPD_EXT_BUF(mtu, upper_32_bits(req->dma)); + gpd->dw3_info = cpu_to_le32(GPD_DATA_LEN(mtu, req->length)); + + /* get the next GPD */ + enq = advance_enq_gpd(ring); + enq_dma = gpd_virt_to_dma(ring, enq); + dev_dbg(mep->mtu->dev, "TX-EP%d queue gpd=%p, enq=%p, qdma=%pad\n", + mep->epnum, gpd, enq, &enq_dma); + + enq->dw0_info &= cpu_to_le32(~GPD_FLAGS_HWO); + gpd->next_gpd = cpu_to_le32(lower_32_bits(enq_dma)); + ext_addr |= GPD_EXT_NGP(mtu, upper_32_bits(enq_dma)); + gpd->dw0_info = cpu_to_le32(ext_addr); + + if (req->zero) { + if (mtu->gen2cp) + gpd->dw0_info |= cpu_to_le32(GPD_FLAGS_ZLP); + else + gpd->dw3_info |= cpu_to_le32(GPD_EXT_FLAG_ZLP); + } + + /* prevent reorder, make sure GPD's HWO is set last */ + mb(); + gpd->dw0_info |= cpu_to_le32(GPD_FLAGS_IOC | GPD_FLAGS_HWO); + + mreq->gpd = gpd; + trace_mtu3_prepare_gpd(mep, gpd); + + return 0; +} + +static int mtu3_prepare_rx_gpd(struct mtu3_ep *mep, struct mtu3_request *mreq) +{ + struct qmu_gpd *enq; + struct mtu3_gpd_ring *ring = &mep->gpd_ring; + struct qmu_gpd *gpd = ring->enqueue; + struct usb_request *req = &mreq->request; + struct mtu3 *mtu = mep->mtu; + dma_addr_t enq_dma; + u32 ext_addr; + + gpd->dw0_info = 0; /* SW own it */ + gpd->buffer = cpu_to_le32(lower_32_bits(req->dma)); + ext_addr = GPD_EXT_BUF(mtu, upper_32_bits(req->dma)); + gpd->dw0_info = cpu_to_le32(GPD_RX_BUF_LEN(mtu, req->length)); + + /* get the next GPD */ + enq = advance_enq_gpd(ring); + enq_dma = gpd_virt_to_dma(ring, enq); + dev_dbg(mep->mtu->dev, "RX-EP%d queue gpd=%p, enq=%p, qdma=%pad\n", + mep->epnum, gpd, enq, &enq_dma); + + enq->dw0_info &= cpu_to_le32(~GPD_FLAGS_HWO); + gpd->next_gpd = cpu_to_le32(lower_32_bits(enq_dma)); + ext_addr |= GPD_EXT_NGP(mtu, upper_32_bits(enq_dma)); + gpd->dw3_info = cpu_to_le32(ext_addr); + /* prevent reorder, make sure GPD's HWO is set last */ + mb(); + gpd->dw0_info |= cpu_to_le32(GPD_FLAGS_IOC | GPD_FLAGS_HWO); + + mreq->gpd = gpd; + trace_mtu3_prepare_gpd(mep, gpd); + + return 0; +} + +void mtu3_insert_gpd(struct mtu3_ep *mep, struct mtu3_request *mreq) +{ + + if (mep->is_in) + mtu3_prepare_tx_gpd(mep, mreq); + else + mtu3_prepare_rx_gpd(mep, mreq); +} + +int mtu3_qmu_start(struct mtu3_ep *mep) +{ + struct mtu3 *mtu = mep->mtu; + void __iomem *mbase = mtu->mac_base; + struct mtu3_gpd_ring *ring = &mep->gpd_ring; + u8 epnum = mep->epnum; + + if (mep->is_in) { + /* set QMU start address */ + write_txq_start_addr(mbase, epnum, ring->dma); + mtu3_setbits(mbase, MU3D_EP_TXCR0(epnum), TX_DMAREQEN); + /* send zero length packet according to ZLP flag in GPD */ + mtu3_setbits(mbase, U3D_QCR1, QMU_TX_ZLP(epnum)); + mtu3_writel(mbase, U3D_TQERRIESR0, + QMU_TX_LEN_ERR(epnum) | QMU_TX_CS_ERR(epnum)); + + if (mtu3_readl(mbase, USB_QMU_TQCSR(epnum)) & QMU_Q_ACTIVE) { + dev_warn(mtu->dev, "Tx %d Active Now!\n", epnum); + return 0; + } + mtu3_writel(mbase, USB_QMU_TQCSR(epnum), QMU_Q_START); + + } else { + write_rxq_start_addr(mbase, epnum, ring->dma); + mtu3_setbits(mbase, MU3D_EP_RXCR0(epnum), RX_DMAREQEN); + /* don't expect ZLP */ + mtu3_clrbits(mbase, U3D_QCR3, QMU_RX_ZLP(epnum)); + /* move to next GPD when receive ZLP */ + mtu3_setbits(mbase, U3D_QCR3, QMU_RX_COZ(epnum)); + mtu3_writel(mbase, U3D_RQERRIESR0, + QMU_RX_LEN_ERR(epnum) | QMU_RX_CS_ERR(epnum)); + mtu3_writel(mbase, U3D_RQERRIESR1, QMU_RX_ZLP_ERR(epnum)); + + if (mtu3_readl(mbase, USB_QMU_RQCSR(epnum)) & QMU_Q_ACTIVE) { + dev_warn(mtu->dev, "Rx %d Active Now!\n", epnum); + return 0; + } + mtu3_writel(mbase, USB_QMU_RQCSR(epnum), QMU_Q_START); + } + + return 0; +} + +/* may called in atomic context */ +void mtu3_qmu_stop(struct mtu3_ep *mep) +{ + struct mtu3 *mtu = mep->mtu; + void __iomem *mbase = mtu->mac_base; + int epnum = mep->epnum; + u32 value = 0; + u32 qcsr; + int ret; + + qcsr = mep->is_in ? USB_QMU_TQCSR(epnum) : USB_QMU_RQCSR(epnum); + + if (!(mtu3_readl(mbase, qcsr) & QMU_Q_ACTIVE)) { + dev_dbg(mtu->dev, "%s's qmu is inactive now!\n", mep->name); + return; + } + mtu3_writel(mbase, qcsr, QMU_Q_STOP); + + if (mep->is_in) + mtu3_setbits(mbase, MU3D_EP_TXCR0(epnum), TX_FLUSHFIFO); + + ret = readl_poll_timeout_atomic(mbase + qcsr, value, + !(value & QMU_Q_ACTIVE), 1, 1000); + if (ret) { + dev_err(mtu->dev, "stop %s's qmu failed\n", mep->name); + return; + } + + /* flush fifo again to make sure the fifo is empty */ + if (mep->is_in) + mtu3_setbits(mbase, MU3D_EP_TXCR0(epnum), TX_FLUSHFIFO); + + dev_dbg(mtu->dev, "%s's qmu stop now!\n", mep->name); +} + +void mtu3_qmu_flush(struct mtu3_ep *mep) +{ + + dev_dbg(mep->mtu->dev, "%s flush QMU %s\n", __func__, + ((mep->is_in) ? "TX" : "RX")); + + /*Stop QMU */ + mtu3_qmu_stop(mep); + reset_gpd_list(mep); +} + +/* + * QMU can't transfer zero length packet directly (a hardware limit + * on old SoCs), so when needs to send ZLP, we intentionally trigger + * a length error interrupt, and in the ISR sends a ZLP by BMU. + */ +static void qmu_tx_zlp_error_handler(struct mtu3 *mtu, u8 epnum) +{ + struct mtu3_ep *mep = mtu->in_eps + epnum; + struct mtu3_gpd_ring *ring = &mep->gpd_ring; + void __iomem *mbase = mtu->mac_base; + struct qmu_gpd *gpd_current = NULL; + struct mtu3_request *mreq; + dma_addr_t cur_gpd_dma; + u32 txcsr = 0; + int ret; + + mreq = next_request(mep); + if (mreq && mreq->request.length != 0) + return; + + cur_gpd_dma = read_txq_cur_addr(mbase, epnum); + gpd_current = gpd_dma_to_virt(ring, cur_gpd_dma); + + if (GPD_DATA_LEN(mtu, le32_to_cpu(gpd_current->dw3_info)) != 0) { + dev_err(mtu->dev, "TX EP%d buffer length error(!=0)\n", epnum); + return; + } + + dev_dbg(mtu->dev, "%s send ZLP for req=%p\n", __func__, mreq); + trace_mtu3_zlp_exp_gpd(mep, gpd_current); + + mtu3_clrbits(mbase, MU3D_EP_TXCR0(mep->epnum), TX_DMAREQEN); + + ret = readl_poll_timeout_atomic(mbase + MU3D_EP_TXCR0(mep->epnum), + txcsr, !(txcsr & TX_FIFOFULL), 1, 1000); + if (ret) { + dev_err(mtu->dev, "%s wait for fifo empty fail\n", __func__); + return; + } + mtu3_setbits(mbase, MU3D_EP_TXCR0(mep->epnum), TX_TXPKTRDY); + /* prevent reorder, make sure GPD's HWO is set last */ + mb(); + /* by pass the current GDP */ + gpd_current->dw0_info |= cpu_to_le32(GPD_FLAGS_BPS | GPD_FLAGS_HWO); + + /*enable DMAREQEN, switch back to QMU mode */ + mtu3_setbits(mbase, MU3D_EP_TXCR0(mep->epnum), TX_DMAREQEN); + mtu3_qmu_resume(mep); +} + +/* + * when rx error happens (except zlperr), QMU will stop, and RQCPR saves + * the GPD encountered error, Done irq will arise after resuming QMU again. + */ +static void qmu_error_rx(struct mtu3 *mtu, u8 epnum) +{ + struct mtu3_ep *mep = mtu->out_eps + epnum; + struct mtu3_gpd_ring *ring = &mep->gpd_ring; + struct qmu_gpd *gpd_current = NULL; + struct mtu3_request *mreq; + dma_addr_t cur_gpd_dma; + + cur_gpd_dma = read_rxq_cur_addr(mtu->mac_base, epnum); + gpd_current = gpd_dma_to_virt(ring, cur_gpd_dma); + + mreq = next_request(mep); + if (!mreq || mreq->gpd != gpd_current) { + dev_err(mtu->dev, "no correct RX req is found\n"); + return; + } + + mreq->request.status = -EAGAIN; + + /* by pass the current GDP */ + gpd_current->dw0_info |= cpu_to_le32(GPD_FLAGS_BPS | GPD_FLAGS_HWO); + mtu3_qmu_resume(mep); + + dev_dbg(mtu->dev, "%s EP%d, current=%p, req=%p\n", + __func__, epnum, gpd_current, mreq); +} + +/* + * NOTE: request list maybe is already empty as following case: + * queue_tx --> qmu_interrupt(clear interrupt pending, schedule tasklet)--> + * queue_tx --> process_tasklet(meanwhile, the second one is transferred, + * tasklet process both of them)-->qmu_interrupt for second one. + * To avoid upper case, put qmu_done_tx in ISR directly to process it. + */ +static void qmu_done_tx(struct mtu3 *mtu, u8 epnum) +{ + struct mtu3_ep *mep = mtu->in_eps + epnum; + struct mtu3_gpd_ring *ring = &mep->gpd_ring; + void __iomem *mbase = mtu->mac_base; + struct qmu_gpd *gpd = ring->dequeue; + struct qmu_gpd *gpd_current = NULL; + struct usb_request *request = NULL; + struct mtu3_request *mreq; + dma_addr_t cur_gpd_dma; + + /*transfer phy address got from QMU register to virtual address */ + cur_gpd_dma = read_txq_cur_addr(mbase, epnum); + gpd_current = gpd_dma_to_virt(ring, cur_gpd_dma); + + dev_dbg(mtu->dev, "%s EP%d, last=%p, current=%p, enq=%p\n", + __func__, epnum, gpd, gpd_current, ring->enqueue); + + while (gpd && gpd != gpd_current && !GET_GPD_HWO(gpd)) { + + mreq = next_request(mep); + + if (mreq == NULL || mreq->gpd != gpd) { + dev_err(mtu->dev, "no correct TX req is found\n"); + break; + } + + request = &mreq->request; + request->actual = GPD_DATA_LEN(mtu, le32_to_cpu(gpd->dw3_info)); + trace_mtu3_complete_gpd(mep, gpd); + mtu3_req_complete(mep, request, 0); + + gpd = advance_deq_gpd(ring); + } + + dev_dbg(mtu->dev, "%s EP%d, deq=%p, enq=%p, complete\n", + __func__, epnum, ring->dequeue, ring->enqueue); + +} + +static void qmu_done_rx(struct mtu3 *mtu, u8 epnum) +{ + struct mtu3_ep *mep = mtu->out_eps + epnum; + struct mtu3_gpd_ring *ring = &mep->gpd_ring; + void __iomem *mbase = mtu->mac_base; + struct qmu_gpd *gpd = ring->dequeue; + struct qmu_gpd *gpd_current = NULL; + struct usb_request *req = NULL; + struct mtu3_request *mreq; + dma_addr_t cur_gpd_dma; + + cur_gpd_dma = read_rxq_cur_addr(mbase, epnum); + gpd_current = gpd_dma_to_virt(ring, cur_gpd_dma); + + dev_dbg(mtu->dev, "%s EP%d, last=%p, current=%p, enq=%p\n", + __func__, epnum, gpd, gpd_current, ring->enqueue); + + while (gpd && gpd != gpd_current && !GET_GPD_HWO(gpd)) { + + mreq = next_request(mep); + + if (mreq == NULL || mreq->gpd != gpd) { + dev_err(mtu->dev, "no correct RX req is found\n"); + break; + } + req = &mreq->request; + + req->actual = GPD_DATA_LEN(mtu, le32_to_cpu(gpd->dw3_info)); + trace_mtu3_complete_gpd(mep, gpd); + mtu3_req_complete(mep, req, 0); + + gpd = advance_deq_gpd(ring); + } + + dev_dbg(mtu->dev, "%s EP%d, deq=%p, enq=%p, complete\n", + __func__, epnum, ring->dequeue, ring->enqueue); +} + +static void qmu_done_isr(struct mtu3 *mtu, u32 done_status) +{ + int i; + + for (i = 1; i < mtu->num_eps; i++) { + if (done_status & QMU_RX_DONE_INT(i)) + qmu_done_rx(mtu, i); + if (done_status & QMU_TX_DONE_INT(i)) + qmu_done_tx(mtu, i); + } +} + +static void qmu_exception_isr(struct mtu3 *mtu, u32 qmu_status) +{ + void __iomem *mbase = mtu->mac_base; + u32 errval; + int i; + + if ((qmu_status & RXQ_CSERR_INT) || (qmu_status & RXQ_LENERR_INT)) { + errval = mtu3_readl(mbase, U3D_RQERRIR0); + mtu3_writel(mbase, U3D_RQERRIR0, errval); + + for (i = 1; i < mtu->num_eps; i++) { + if (errval & QMU_RX_CS_ERR(i)) + dev_err(mtu->dev, "Rx %d CS error!\n", i); + + if (errval & QMU_RX_LEN_ERR(i)) + dev_err(mtu->dev, "RX %d Length error\n", i); + + if (errval & (QMU_RX_CS_ERR(i) | QMU_RX_LEN_ERR(i))) + qmu_error_rx(mtu, i); + } + } + + if (qmu_status & RXQ_ZLPERR_INT) { + errval = mtu3_readl(mbase, U3D_RQERRIR1); + for (i = 1; i < mtu->num_eps; i++) { + if (errval & QMU_RX_ZLP_ERR(i)) + dev_dbg(mtu->dev, "RX EP%d Recv ZLP\n", i); + } + mtu3_writel(mbase, U3D_RQERRIR1, errval); + } + + if ((qmu_status & TXQ_CSERR_INT) || (qmu_status & TXQ_LENERR_INT)) { + errval = mtu3_readl(mbase, U3D_TQERRIR0); + for (i = 1; i < mtu->num_eps; i++) { + if (errval & QMU_TX_CS_ERR(i)) + dev_err(mtu->dev, "Tx %d checksum error!\n", i); + + if (errval & QMU_TX_LEN_ERR(i)) + qmu_tx_zlp_error_handler(mtu, i); + } + mtu3_writel(mbase, U3D_TQERRIR0, errval); + } +} + +irqreturn_t mtu3_qmu_isr(struct mtu3 *mtu) +{ + void __iomem *mbase = mtu->mac_base; + u32 qmu_status; + u32 qmu_done_status; + + /* U3D_QISAR1 is read update */ + qmu_status = mtu3_readl(mbase, U3D_QISAR1); + qmu_status &= mtu3_readl(mbase, U3D_QIER1); + + qmu_done_status = mtu3_readl(mbase, U3D_QISAR0); + qmu_done_status &= mtu3_readl(mbase, U3D_QIER0); + mtu3_writel(mbase, U3D_QISAR0, qmu_done_status); /* W1C */ + dev_dbg(mtu->dev, "=== QMUdone[tx=%x, rx=%x] QMUexp[%x] ===\n", + (qmu_done_status & 0xFFFF), qmu_done_status >> 16, + qmu_status); + trace_mtu3_qmu_isr(qmu_done_status, qmu_status); + + if (qmu_done_status) + qmu_done_isr(mtu, qmu_done_status); + + if (qmu_status) + qmu_exception_isr(mtu, qmu_status); + + return IRQ_HANDLED; +} + +int mtu3_qmu_init(struct mtu3 *mtu) +{ + + compiletime_assert(QMU_GPD_SIZE == 16, "QMU_GPD size SHOULD be 16B"); + + mtu->qmu_gpd_pool = dma_pool_create("QMU_GPD", mtu->dev, + QMU_GPD_RING_SIZE, QMU_GPD_SIZE, 0); + + if (!mtu->qmu_gpd_pool) + return -ENOMEM; + + return 0; +} + +void mtu3_qmu_exit(struct mtu3 *mtu) +{ + dma_pool_destroy(mtu->qmu_gpd_pool); +} diff --git a/drivers/usb/mtu3/mtu3_qmu.h b/drivers/usb/mtu3/mtu3_qmu.h new file mode 100644 index 000000000..66e1c0ab5 --- /dev/null +++ b/drivers/usb/mtu3/mtu3_qmu.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mtu3_qmu.h - Queue Management Unit driver header + * + * Copyright (C) 2016 MediaTek Inc. + * + * Author: Chunfeng Yun + */ + +#ifndef __MTK_QMU_H__ +#define __MTK_QMU_H__ + +#define MAX_GPD_NUM 64 +#define QMU_GPD_SIZE (sizeof(struct qmu_gpd)) +#define QMU_GPD_RING_SIZE (MAX_GPD_NUM * QMU_GPD_SIZE) + +#define GPD_BUF_SIZE 65532 +#define GPD_BUF_SIZE_EL 1048572 + +void mtu3_qmu_stop(struct mtu3_ep *mep); +int mtu3_qmu_start(struct mtu3_ep *mep); +void mtu3_qmu_resume(struct mtu3_ep *mep); +void mtu3_qmu_flush(struct mtu3_ep *mep); + +void mtu3_insert_gpd(struct mtu3_ep *mep, struct mtu3_request *mreq); +int mtu3_prepare_transfer(struct mtu3_ep *mep); + +int mtu3_gpd_ring_alloc(struct mtu3_ep *mep); +void mtu3_gpd_ring_free(struct mtu3_ep *mep); + +irqreturn_t mtu3_qmu_isr(struct mtu3 *mtu); +int mtu3_qmu_init(struct mtu3 *mtu); +void mtu3_qmu_exit(struct mtu3 *mtu); + +#endif diff --git a/drivers/usb/mtu3/mtu3_trace.c b/drivers/usb/mtu3/mtu3_trace.c new file mode 100644 index 000000000..d17ddb87c --- /dev/null +++ b/drivers/usb/mtu3/mtu3_trace.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mtu3_trace.c - trace support + * + * Copyright (C) 2019 MediaTek Inc. + * + * Author: Chunfeng Yun + */ + +#define CREATE_TRACE_POINTS +#include "mtu3_debug.h" +#include "mtu3_trace.h" + +void mtu3_dbg_trace(struct device *dev, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + trace_mtu3_log(dev, &vaf); + va_end(args); +} diff --git a/drivers/usb/mtu3/mtu3_trace.h b/drivers/usb/mtu3/mtu3_trace.h new file mode 100644 index 000000000..89870175d --- /dev/null +++ b/drivers/usb/mtu3/mtu3_trace.h @@ -0,0 +1,277 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/** + * mtu3_trace.h - trace support + * + * Copyright (C) 2019 MediaTek Inc. + * + * Author: Chunfeng Yun + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM mtu3 + +#if !defined(__MTU3_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ) +#define __MTU3_TRACE_H__ + +#include +#include + +#include "mtu3.h" + +TRACE_EVENT(mtu3_log, + TP_PROTO(struct device *dev, struct va_format *vaf), + TP_ARGS(dev, vaf), + TP_STRUCT__entry( + __string(name, dev_name(dev)) + __vstring(msg, vaf->fmt, vaf->va) + ), + TP_fast_assign( + __assign_str(name); + __assign_vstr(msg, vaf->fmt, vaf->va); + ), + TP_printk("%s: %s", __get_str(name), __get_str(msg)) +); + +TRACE_EVENT(mtu3_u3_ltssm_isr, + TP_PROTO(u32 intr), + TP_ARGS(intr), + TP_STRUCT__entry( + __field(u32, intr) + ), + TP_fast_assign( + __entry->intr = intr; + ), + TP_printk("(%08x) %s %s %s %s %s %s", __entry->intr, + __entry->intr & HOT_RST_INTR ? "HOT_RST" : "", + __entry->intr & WARM_RST_INTR ? "WARM_RST" : "", + __entry->intr & ENTER_U3_INTR ? "ENT_U3" : "", + __entry->intr & EXIT_U3_INTR ? "EXIT_U3" : "", + __entry->intr & VBUS_RISE_INTR ? "VBUS_RISE" : "", + __entry->intr & VBUS_FALL_INTR ? "VBUS_FALL" : "" + ) +); + +TRACE_EVENT(mtu3_u2_common_isr, + TP_PROTO(u32 intr), + TP_ARGS(intr), + TP_STRUCT__entry( + __field(u32, intr) + ), + TP_fast_assign( + __entry->intr = intr; + ), + TP_printk("(%08x) %s %s %s", __entry->intr, + __entry->intr & SUSPEND_INTR ? "SUSPEND" : "", + __entry->intr & RESUME_INTR ? "RESUME" : "", + __entry->intr & RESET_INTR ? "RESET" : "" + ) +); + +TRACE_EVENT(mtu3_qmu_isr, + TP_PROTO(u32 done_intr, u32 exp_intr), + TP_ARGS(done_intr, exp_intr), + TP_STRUCT__entry( + __field(u32, done_intr) + __field(u32, exp_intr) + ), + TP_fast_assign( + __entry->done_intr = done_intr; + __entry->exp_intr = exp_intr; + ), + TP_printk("done (tx %04x, rx %04x), exp (%08x)", + __entry->done_intr & 0xffff, + __entry->done_intr >> 16, + __entry->exp_intr + ) +); + +DECLARE_EVENT_CLASS(mtu3_log_setup, + TP_PROTO(struct usb_ctrlrequest *setup), + TP_ARGS(setup), + TP_STRUCT__entry( + __field(__u8, bRequestType) + __field(__u8, bRequest) + __field(__u16, wValue) + __field(__u16, wIndex) + __field(__u16, wLength) + ), + TP_fast_assign( + __entry->bRequestType = setup->bRequestType; + __entry->bRequest = setup->bRequest; + __entry->wValue = le16_to_cpu(setup->wValue); + __entry->wIndex = le16_to_cpu(setup->wIndex); + __entry->wLength = le16_to_cpu(setup->wLength); + ), + TP_printk("setup - %02x %02x %04x %04x %04x", + __entry->bRequestType, __entry->bRequest, + __entry->wValue, __entry->wIndex, __entry->wLength + ) +); + +DEFINE_EVENT(mtu3_log_setup, mtu3_handle_setup, + TP_PROTO(struct usb_ctrlrequest *setup), + TP_ARGS(setup) +); + +DECLARE_EVENT_CLASS(mtu3_log_request, + TP_PROTO(struct mtu3_request *mreq), + TP_ARGS(mreq), + TP_STRUCT__entry( + __string(name, mreq->mep->name) + __field(struct mtu3_request *, mreq) + __field(struct qmu_gpd *, gpd) + __field(unsigned int, actual) + __field(unsigned int, length) + __field(int, status) + __field(int, zero) + __field(int, no_interrupt) + ), + TP_fast_assign( + __assign_str(name); + __entry->mreq = mreq; + __entry->gpd = mreq->gpd; + __entry->actual = mreq->request.actual; + __entry->length = mreq->request.length; + __entry->status = mreq->request.status; + __entry->zero = mreq->request.zero; + __entry->no_interrupt = mreq->request.no_interrupt; + ), + TP_printk("%s: req %p gpd %p len %u/%u %s%s --> %d", + __get_str(name), __entry->mreq, __entry->gpd, + __entry->actual, __entry->length, + __entry->zero ? "Z" : "z", + __entry->no_interrupt ? "i" : "I", + __entry->status + ) +); + +DEFINE_EVENT(mtu3_log_request, mtu3_alloc_request, + TP_PROTO(struct mtu3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(mtu3_log_request, mtu3_free_request, + TP_PROTO(struct mtu3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(mtu3_log_request, mtu3_gadget_queue, + TP_PROTO(struct mtu3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(mtu3_log_request, mtu3_gadget_dequeue, + TP_PROTO(struct mtu3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(mtu3_log_request, mtu3_req_complete, + TP_PROTO(struct mtu3_request *req), + TP_ARGS(req) +); + +DECLARE_EVENT_CLASS(mtu3_log_gpd, + TP_PROTO(struct mtu3_ep *mep, struct qmu_gpd *gpd), + TP_ARGS(mep, gpd), + TP_STRUCT__entry( + __string(name, mep->name) + __field(struct qmu_gpd *, gpd) + __field(u32, dw0) + __field(u32, dw1) + __field(u32, dw2) + __field(u32, dw3) + ), + TP_fast_assign( + __assign_str(name); + __entry->gpd = gpd; + __entry->dw0 = le32_to_cpu(gpd->dw0_info); + __entry->dw1 = le32_to_cpu(gpd->next_gpd); + __entry->dw2 = le32_to_cpu(gpd->buffer); + __entry->dw3 = le32_to_cpu(gpd->dw3_info); + ), + TP_printk("%s: gpd %p - %08x %08x %08x %08x", + __get_str(name), __entry->gpd, + __entry->dw0, __entry->dw1, + __entry->dw2, __entry->dw3 + ) +); + +DEFINE_EVENT(mtu3_log_gpd, mtu3_prepare_gpd, + TP_PROTO(struct mtu3_ep *mep, struct qmu_gpd *gpd), + TP_ARGS(mep, gpd) +); + +DEFINE_EVENT(mtu3_log_gpd, mtu3_complete_gpd, + TP_PROTO(struct mtu3_ep *mep, struct qmu_gpd *gpd), + TP_ARGS(mep, gpd) +); + +DEFINE_EVENT(mtu3_log_gpd, mtu3_zlp_exp_gpd, + TP_PROTO(struct mtu3_ep *mep, struct qmu_gpd *gpd), + TP_ARGS(mep, gpd) +); + +DECLARE_EVENT_CLASS(mtu3_log_ep, + TP_PROTO(struct mtu3_ep *mep), + TP_ARGS(mep), + TP_STRUCT__entry( + __string(name, mep->name) + __field(unsigned int, type) + __field(unsigned int, slot) + __field(unsigned int, maxp) + __field(unsigned int, mult) + __field(unsigned int, maxburst) + __field(unsigned int, flags) + __field(unsigned int, direction) + __field(struct mtu3_gpd_ring *, gpd_ring) + ), + TP_fast_assign( + __assign_str(name); + __entry->type = mep->type; + __entry->slot = mep->slot; + __entry->maxp = mep->ep.maxpacket; + __entry->mult = mep->ep.mult; + __entry->maxburst = mep->ep.maxburst; + __entry->flags = mep->flags; + __entry->direction = mep->is_in; + __entry->gpd_ring = &mep->gpd_ring; + ), + TP_printk("%s: type %s maxp %d slot %d mult %d burst %d ring %p/%pad flags %c:%c%c%c:%c", + __get_str(name), usb_ep_type_string(__entry->type), + __entry->maxp, __entry->slot, + __entry->mult, __entry->maxburst, + __entry->gpd_ring, &__entry->gpd_ring->dma, + __entry->flags & MTU3_EP_ENABLED ? 'E' : 'e', + __entry->flags & MTU3_EP_STALL ? 'S' : 's', + __entry->flags & MTU3_EP_WEDGE ? 'W' : 'w', + __entry->flags & MTU3_EP_BUSY ? 'B' : 'b', + __entry->direction ? '<' : '>' + ) +); + +DEFINE_EVENT(mtu3_log_ep, mtu3_gadget_ep_enable, + TP_PROTO(struct mtu3_ep *mep), + TP_ARGS(mep) +); + +DEFINE_EVENT(mtu3_log_ep, mtu3_gadget_ep_disable, + TP_PROTO(struct mtu3_ep *mep), + TP_ARGS(mep) +); + +DEFINE_EVENT(mtu3_log_ep, mtu3_gadget_ep_set_halt, + TP_PROTO(struct mtu3_ep *mep), + TP_ARGS(mep) +); + +#endif /* __MTU3_TRACE_H__ */ + +/* this part has to be here */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE mtu3_trace + +#include diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig new file mode 100644 index 000000000..1b0d27d8e --- /dev/null +++ b/drivers/usb/musb/Kconfig @@ -0,0 +1,166 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# 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' + depends on (USB || USB_GADGET) + depends on HAS_IOMEM + 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. + + Allwinner SoCs using this IP include A10, A13, A20, ... + + 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 "MUSB Mode Selection" + default USB_MUSB_DUAL_ROLE if (USB && USB_GADGET) + default USB_MUSB_HOST if (USB && !USB_GADGET) + default USB_MUSB_GADGET if (!USB && USB_GADGET) + +config USB_MUSB_HOST + bool "Host only mode" + depends on USB=y || USB=USB_MUSB_HDRC + help + Select this when you want to use MUSB in host mode only, + thereby the gadget feature will be regressed. + +config USB_MUSB_GADGET + bool "Gadget only mode" + depends on USB_GADGET=y || USB_GADGET=USB_MUSB_HDRC + depends on HAS_DMA + help + Select this when you want to use MUSB in gadget mode only, + thereby the host feature will be regressed. + +config USB_MUSB_DUAL_ROLE + bool "Dual Role mode" + depends on ((USB=y || USB=USB_MUSB_HDRC) && (USB_GADGET=y || USB_GADGET=USB_MUSB_HDRC)) + depends on HAS_DMA + help + This is the default mode of working of MUSB controller where + both host and gadget features are enabled. + +endchoice + +comment "Platform Glue Layer" + +config USB_MUSB_SUNXI + tristate "Allwinner (sunxi)" + depends on ARCH_SUNXI + depends on NOP_USB_XCEIV + depends on PHY_SUN4I_USB + depends on EXTCON + select GENERIC_PHY + select SUNXI_SRAM + +config USB_MUSB_DA8XX + tristate "DA8xx/OMAP-L1x" + depends on ARCH_DAVINCI_DA8XX + depends on NOP_USB_XCEIV + select PHY_DA8XX_USB + +config USB_MUSB_TUSB6010 + tristate "TUSB6010" + depends on HAS_IOMEM + depends on ARCH_OMAP2PLUS || COMPILE_TEST + depends on NOP_USB_XCEIV!=m || USB_MUSB_HDRC=m + +config USB_MUSB_OMAP2PLUS + tristate "OMAP2430 and onwards" + depends on ARCH_OMAP2PLUS || COMPILE_TEST + depends on USB + depends on OMAP_CONTROL_PHY || !OMAP_CONTROL_PHY + select GENERIC_PHY + +config USB_MUSB_DSPS + tristate "TI DSPS platforms" + depends on ARCH_OMAP2PLUS || COMPILE_TEST + depends on OF_IRQ + +config USB_MUSB_UX500 + tristate "Ux500 platforms" + depends on ARCH_U8500 || COMPILE_TEST + +config USB_MUSB_JZ4740 + tristate "JZ4740" + depends on OF + depends on MIPS || COMPILE_TEST + depends on USB_MUSB_GADGET + select USB_ROLE_SWITCH + +config USB_MUSB_MEDIATEK + tristate "MediaTek platforms" + depends on ARCH_MEDIATEK || COMPILE_TEST + depends on NOP_USB_XCEIV + select GENERIC_PHY + select USB_ROLE_SWITCH + +config USB_MUSB_POLARFIRE_SOC + tristate "Microchip PolarFire SoC platforms" + depends on ARCH_MICROCHIP_POLARFIRE || COMPILE_TEST + depends on NOP_USB_XCEIV + help + Say Y here to enable support for USB on Microchip's PolarFire SoC. + + This support is also available as a module. If so, the module + will be called mpfs. + +comment "MUSB DMA mode" + +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. + +if !MUSB_PIO_ONLY + +config USB_UX500_DMA + bool 'ST Ericsson Ux500' + 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_MEDIATEK || USB_MUSB_JZ4740 || USB_MUSB_POLARFIRE_SOC + help + Enable DMA transfers using Mentor's engine. + +config USB_TI_CPPI41_DMA + bool 'TI CPPI 4.1' + depends on (ARCH_OMAP || ARCH_DAVINCI_DA8XX) && DMADEVICES + select TI_CPPI41 + +config USB_TUSB_OMAP_DMA + bool 'TUSB 6010' + depends on USB_MUSB_TUSB6010 = USB_MUSB_HDRC # both built-in or both modules + depends on ARCH_OMAP + help + Enable DMA transfers on TUSB 6010 when OMAP DMA is available. + +endif # !MUSB_PIO_ONLY + +endif # USB_MUSB_HDRC diff --git a/drivers/usb/musb/Makefile b/drivers/usb/musb/Makefile new file mode 100644 index 000000000..5dccf0e45 --- /dev/null +++ b/drivers/usb/musb/Makefile @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# for USB OTG silicon based on Mentor Graphics INVENTRA designs +# + +# define_trace.h needs to know how to find our header +CFLAGS_musb_trace.o := -I$(src) + +obj-$(CONFIG_USB_MUSB_HDRC) += musb_hdrc.o + +musb_hdrc-y := musb_core.o musb_trace.o + +musb_hdrc-$(CONFIG_USB_MUSB_HOST)$(CONFIG_USB_MUSB_DUAL_ROLE) += musb_virthub.o musb_host.o +musb_hdrc-$(CONFIG_USB_MUSB_GADGET)$(CONFIG_USB_MUSB_DUAL_ROLE) += musb_gadget_ep0.o musb_gadget.o +musb_hdrc-$(CONFIG_DEBUG_FS) += musb_debugfs.o + +# Hardware Glue Layer +obj-$(CONFIG_USB_MUSB_OMAP2PLUS) += omap2430.o +obj-$(CONFIG_USB_MUSB_DSPS) += musb_dsps.o +obj-$(CONFIG_USB_MUSB_TUSB6010) += tusb6010.o +obj-$(CONFIG_USB_MUSB_DA8XX) += da8xx.o +obj-$(CONFIG_USB_MUSB_UX500) += ux500.o +obj-$(CONFIG_USB_MUSB_JZ4740) += jz4740.o +obj-$(CONFIG_USB_MUSB_SUNXI) += sunxi.o +obj-$(CONFIG_USB_MUSB_MEDIATEK) += mediatek.o +obj-$(CONFIG_USB_MUSB_POLARFIRE_SOC) += mpfs.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_TUSB_OMAP_DMA) += tusb6010_omap.o +musb_hdrc-$(CONFIG_USB_UX500_DMA) += ux500_dma.o +musb_hdrc-$(CONFIG_USB_TI_CPPI41_DMA) += musb_cppi41.o diff --git a/drivers/usb/musb/cppi_dma.h b/drivers/usb/musb/cppi_dma.h new file mode 100644 index 000000000..3606be897 --- /dev/null +++ b/drivers/usb/musb/cppi_dma.h @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2005-2006 by Texas Instruments */ + +#ifndef _CPPI_DMA_H_ +#define _CPPI_DMA_H_ + +#include +#include +#include +#include +#include + +#include "musb_core.h" +#include "musb_dma.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; + 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; +}; + +struct cppi41_dma_channel { + struct dma_channel channel; + struct cppi41_dma_controller *controller; + struct musb_hw_ep *hw_ep; + struct dma_chan *dc; + dma_cookie_t cookie; + u8 port_num; + u8 is_tx; + u8 is_allocated; + u8 usb_toggle; + + dma_addr_t buf_addr; + u32 total_len; + u32 prog_len; + u32 transferred; + u32 packet_sz; + struct list_head tx_check; + int tx_zlp; +}; + +#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 000000000..4209f438b --- /dev/null +++ b/drivers/usb/musb/da8xx.c @@ -0,0 +1,649 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + * + * DT support + * Copyright (c) 2016 Petr Kulhavy + * + * This file is part of the Inventra Controller Driver for Linux. + */ + +#include +#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_SRP_FIX_TIME_REG 0x18 +#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 + +struct da8xx_glue { + struct device *dev; + struct platform_device *musb; + struct platform_device *usb_phy; + struct clk *clk; + struct phy *phy; +}; + +/* + * 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_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 void otg_timer(struct timer_list *t) +{ + struct musb *musb = timer_container_of(musb, t, + dev_timer); + 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->otg->state)); + + spin_lock_irqsave(&musb->lock, flags); + switch (musb->xceiv->otg->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->otg->state = OTG_STATE_B_IDLE; + MUSB_DEV_MODE(musb); + } else { + musb->xceiv->otg->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(&musb->dev_timer, jiffies + POLL_SECONDS * HZ); + break; + } + musb->xceiv->otg->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(&musb->dev_timer, jiffies + POLL_SECONDS * HZ); + else + musb->xceiv->otg->state = OTG_STATE_A_IDLE; + break; + default: + break; + } + spin_unlock_irqrestore(&musb->lock, flags); +} + +static void __maybe_unused 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->otg->state == OTG_STATE_A_WAIT_BCON)) { + dev_dbg(musb->controller, "%s active, deleting timer\n", + usb_otg_state_string(musb->xceiv->otg->state)); + timer_delete(&musb->dev_timer); + last_timer = jiffies; + return; + } + + if (time_after(last_timer, timeout) && timer_pending(&musb->dev_timer)) { + 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->otg->state), + jiffies_to_msecs(timeout - jiffies)); + mod_timer(&musb->dev_timer, timeout); +} + +static int da8xx_babble_recover(struct musb *musb) +{ + dev_dbg(musb->controller, "resetting controller to recover from babble\n"); + musb_writel(musb->ctrl_base, DA8XX_USB_CTRL_REG, DA8XX_SOFT_RESET_MASK); + return 0; +} + +static irqreturn_t da8xx_musb_interrupt(int irq, void *hci) +{ + struct musb *musb = hci; + void __iomem *reg_base = musb->ctrl_base; + 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->otg->state = OTG_STATE_A_WAIT_VFALL; + mod_timer(&musb->dev_timer, jiffies + POLL_SECONDS * HZ); + WARNING("VBUS error workaround (delay coming)\n"); + } else if (drvvbus) { + MUSB_HST_MODE(musb); + musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE; + portstate(musb->port1_status |= USB_PORT_STAT_POWER); + timer_delete(&musb->dev_timer); + } else if (!(musb->int_usb & MUSB_INTR_BABBLE)) { + /* + * When babble condition happens, drvvbus interrupt + * is also generated. Ignore this drvvbus interrupt + * and let babble interrupt handler recovers the + * controller; otherwise, the host-mode flag is lost + * due to the MUSB_DEV_MODE() call below and babble + * recovery logic will not be called. + */ + musb->is_active = 0; + MUSB_DEV_MODE(musb); + musb->xceiv->otg->state = OTG_STATE_B_IDLE; + portstate(musb->port1_status &= ~USB_PORT_STAT_POWER); + } + + dev_dbg(musb->controller, "VBUS %s (%s)%s, devctl %02x\n", + str_on_off(drvvbus), + usb_otg_state_string(musb->xceiv->otg->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->otg->state == OTG_STATE_B_IDLE) + mod_timer(&musb->dev_timer, jiffies + POLL_SECONDS * HZ); + + spin_unlock_irqrestore(&musb->lock, flags); + + return ret; +} + +static int da8xx_musb_set_mode(struct musb *musb, u8 musb_mode) +{ + struct da8xx_glue *glue = dev_get_drvdata(musb->controller->parent); + enum phy_mode phy_mode; + + switch (musb_mode) { + case MUSB_HOST: /* Force VBUS valid, ID = 0 */ + phy_mode = PHY_MODE_USB_HOST; + break; + case MUSB_PERIPHERAL: /* Force VBUS valid, ID = 1 */ + phy_mode = PHY_MODE_USB_DEVICE; + break; + case MUSB_OTG: /* Don't override the VBUS/ID comparators */ + phy_mode = PHY_MODE_USB_OTG; + break; + default: + return -EINVAL; + } + + return phy_set_mode(glue->phy, phy_mode); +} + +static int da8xx_musb_init(struct musb *musb) +{ + struct da8xx_glue *glue = dev_get_drvdata(musb->controller->parent); + void __iomem *reg_base = musb->ctrl_base; + u32 rev; + int ret = -ENODEV; + + musb->mregs += DA8XX_MENTOR_CORE_OFFSET; + + ret = clk_prepare_enable(glue->clk); + if (ret) { + dev_err(glue->dev, "failed to enable clock\n"); + return ret; + } + + /* Returns zero if e.g. not clocked */ + rev = musb_readl(reg_base, DA8XX_USB_REVISION_REG); + if (!rev) { + ret = -ENODEV; + goto fail; + } + + musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR_OR_NULL(musb->xceiv)) { + ret = -EPROBE_DEFER; + goto fail; + } + + timer_setup(&musb->dev_timer, otg_timer, 0); + + /* Reset the controller */ + musb_writel(reg_base, DA8XX_USB_CTRL_REG, DA8XX_SOFT_RESET_MASK); + + /* Start the on-chip PHY and its PLL. */ + ret = phy_init(glue->phy); + if (ret) { + dev_err(glue->dev, "Failed to init phy.\n"); + goto fail; + } + + ret = phy_power_on(glue->phy); + if (ret) { + dev_err(glue->dev, "Failed to power on phy.\n"); + goto err_phy_power_on; + } + + msleep(5); + + /* NOTE: IRQs are in mixed mode, not bypass to pure MUSB */ + pr_debug("DA8xx OTG revision %08x, control %02x\n", rev, + musb_readb(reg_base, DA8XX_USB_CTRL_REG)); + + musb->isr = da8xx_musb_interrupt; + return 0; + +err_phy_power_on: + phy_exit(glue->phy); +fail: + clk_disable_unprepare(glue->clk); + return ret; +} + +static int da8xx_musb_exit(struct musb *musb) +{ + struct da8xx_glue *glue = dev_get_drvdata(musb->controller->parent); + + timer_delete_sync(&musb->dev_timer); + + phy_power_off(glue->phy); + phy_exit(glue->phy); + clk_disable_unprepare(glue->clk); + + usb_put_phy(musb->xceiv); + + return 0; +} + +static inline u8 get_vbus_power(struct device *dev) +{ + struct regulator *vbus_supply; + int current_uA; + + vbus_supply = regulator_get_optional(dev, "vbus"); + if (IS_ERR(vbus_supply)) + return 255; + current_uA = regulator_get_current_limit(vbus_supply); + regulator_put(vbus_supply); + if (current_uA <= 0 || current_uA > 510000) + return 255; + return current_uA / 1000 / 2; +} + +#ifdef CONFIG_USB_TI_CPPI41_DMA +static void da8xx_dma_controller_callback(struct dma_controller *c) +{ + struct musb *musb = c->musb; + void __iomem *reg_base = musb->ctrl_base; + + musb_writel(reg_base, DA8XX_USB_END_OF_INTR_REG, 0); +} + +static struct dma_controller * +da8xx_dma_controller_create(struct musb *musb, void __iomem *base) +{ + struct dma_controller *controller; + + controller = cppi41_dma_controller_create(musb, base); + if (IS_ERR_OR_NULL(controller)) + return controller; + + controller->dma_callback = da8xx_dma_controller_callback; + + return controller; +} +#endif + +static const struct musb_platform_ops da8xx_ops = { + .quirks = MUSB_INDEXED_EP | MUSB_PRESERVE_SESSION | + MUSB_DMA_CPPI41 | MUSB_DA8XX, + .init = da8xx_musb_init, + .exit = da8xx_musb_exit, + + .fifo_mode = 2, +#ifdef CONFIG_USB_TI_CPPI41_DMA + .dma_init = da8xx_dma_controller_create, + .dma_exit = cppi41_dma_controller_destroy, +#endif + .enable = da8xx_musb_enable, + .disable = da8xx_musb_disable, + + .set_mode = da8xx_musb_set_mode, + +#ifndef CONFIG_USB_MUSB_HOST + .try_idle = da8xx_musb_try_idle, +#endif + .recover = da8xx_babble_recover, + + .set_vbus = da8xx_musb_set_vbus, +}; + +static const struct platform_device_info da8xx_dev_info = { + .name = "musb-hdrc", + .id = PLATFORM_DEVID_AUTO, + .dma_mask = DMA_BIT_MASK(32), +}; + +static const struct musb_hdrc_config da8xx_config = { + .ram_bits = 10, + .num_eps = 5, + .multipoint = 1, +}; + +static struct of_dev_auxdata da8xx_auxdata_lookup[] = { + OF_DEV_AUXDATA("ti,da830-cppi41", 0x01e01000, "cppi41-dmaengine", + NULL), + {} +}; + +static int da8xx_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct da8xx_glue *glue; + struct platform_device_info pinfo; + struct clk *clk; + struct device_node *np = pdev->dev.of_node; + int ret; + + glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL); + if (!glue) + return -ENOMEM; + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return PTR_ERR(clk); + } + + glue->phy = devm_phy_get(&pdev->dev, "usb-phy"); + if (IS_ERR(glue->phy)) + return dev_err_probe(&pdev->dev, PTR_ERR(glue->phy), + "failed to get phy\n"); + + glue->dev = &pdev->dev; + glue->clk = clk; + + if (IS_ENABLED(CONFIG_OF) && np) { + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->config = &da8xx_config; + pdata->mode = musb_get_mode(&pdev->dev); + pdata->power = get_vbus_power(&pdev->dev); + } + + pdata->platform_ops = &da8xx_ops; + + glue->usb_phy = usb_phy_generic_register(); + ret = PTR_ERR_OR_ZERO(glue->usb_phy); + if (ret) { + dev_err(&pdev->dev, "failed to register usb_phy\n"); + return ret; + } + platform_set_drvdata(pdev, glue); + + ret = of_platform_populate(pdev->dev.of_node, NULL, + da8xx_auxdata_lookup, &pdev->dev); + if (ret) + goto err_unregister_phy; + + pinfo = da8xx_dev_info; + pinfo.parent = &pdev->dev; + pinfo.res = pdev->resource; + pinfo.num_res = pdev->num_resources; + pinfo.data = pdata; + pinfo.size_data = sizeof(*pdata); + pinfo.fwnode = of_fwnode_handle(np); + pinfo.of_node_reused = true; + + glue->musb = platform_device_register_full(&pinfo); + ret = PTR_ERR_OR_ZERO(glue->musb); + if (ret) { + dev_err(&pdev->dev, "failed to register musb device: %d\n", ret); + goto err_unregister_phy; + } + + return 0; + +err_unregister_phy: + usb_phy_generic_unregister(glue->usb_phy); + return ret; +} + +static void da8xx_remove(struct platform_device *pdev) +{ + struct da8xx_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + usb_phy_generic_unregister(glue->usb_phy); +} + +#ifdef CONFIG_PM_SLEEP +static int da8xx_suspend(struct device *dev) +{ + int ret; + struct da8xx_glue *glue = dev_get_drvdata(dev); + + ret = phy_power_off(glue->phy); + if (ret) + return ret; + clk_disable_unprepare(glue->clk); + + return 0; +} + +static int da8xx_resume(struct device *dev) +{ + int ret; + struct da8xx_glue *glue = dev_get_drvdata(dev); + + ret = clk_prepare_enable(glue->clk); + if (ret) + return ret; + return phy_power_on(glue->phy); +} +#endif + +static SIMPLE_DEV_PM_OPS(da8xx_pm_ops, da8xx_suspend, da8xx_resume); + +#ifdef CONFIG_OF +static const struct of_device_id da8xx_id_table[] = { + { + .compatible = "ti,da830-musb", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, da8xx_id_table); +#endif + +static struct platform_driver da8xx_driver = { + .probe = da8xx_probe, + .remove = da8xx_remove, + .driver = { + .name = "musb-da8xx", + .pm = &da8xx_pm_ops, + .of_match_table = of_match_ptr(da8xx_id_table), + }, +}; + +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/jz4740.c b/drivers/usb/musb/jz4740.c new file mode 100644 index 000000000..c770ba576 --- /dev/null +++ b/drivers/usb/musb/jz4740.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Ingenic JZ4740 "glue layer" + * + * Copyright (C) 2013, Apelete Seketeli + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "musb_core.h" + +struct jz4740_glue { + struct platform_device *pdev; + struct musb *musb; + struct clk *clk; + struct usb_role_switch *role_sw; +}; + +static irqreturn_t jz4740_musb_interrupt(int irq, void *__hci) +{ + unsigned long flags; + irqreturn_t retval = IRQ_NONE, retval_dma = IRQ_NONE; + struct musb *musb = __hci; + + if (IS_ENABLED(CONFIG_USB_INVENTRA_DMA) && musb->dma_controller) + retval_dma = dma_controller_irq(irq, musb->dma_controller); + + 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); + + /* + * The controller is gadget only, the state of the host mode IRQ bits is + * undefined. Mask them to make sure that the musb driver core will + * never see them set + */ + musb->int_usb &= MUSB_INTR_SUSPEND | MUSB_INTR_RESUME | + MUSB_INTR_RESET | MUSB_INTR_SOF; + + if (musb->int_usb || musb->int_tx || musb->int_rx) + retval = musb_interrupt(musb); + + spin_unlock_irqrestore(&musb->lock, flags); + + if (retval == IRQ_HANDLED || retval_dma == IRQ_HANDLED) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +static const struct musb_fifo_cfg jz4740_musb_fifo_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 = 64, }, +}; + +static const struct musb_hdrc_config jz4740_musb_config = { + /* Silicon does not implement USB OTG. */ + .multipoint = 0, + /* Max EPs scanned, driver will decide which EP can be used. */ + .num_eps = 4, + /* RAMbits needed to configure EPs from table */ + .ram_bits = 9, + .fifo_cfg = jz4740_musb_fifo_cfg, + .fifo_cfg_size = ARRAY_SIZE(jz4740_musb_fifo_cfg), +}; + +static int jz4740_musb_role_switch_set(struct usb_role_switch *sw, + enum usb_role role) +{ + struct jz4740_glue *glue = usb_role_switch_get_drvdata(sw); + struct usb_phy *phy = glue->musb->xceiv; + + if (!phy) + return 0; + + switch (role) { + case USB_ROLE_NONE: + atomic_notifier_call_chain(&phy->notifier, USB_EVENT_NONE, phy); + break; + case USB_ROLE_DEVICE: + atomic_notifier_call_chain(&phy->notifier, USB_EVENT_VBUS, phy); + break; + case USB_ROLE_HOST: + atomic_notifier_call_chain(&phy->notifier, USB_EVENT_ID, phy); + break; + } + + return 0; +} + +static int jz4740_musb_init(struct musb *musb) +{ + struct device *dev = musb->controller->parent; + struct jz4740_glue *glue = dev_get_drvdata(dev); + struct usb_role_switch_desc role_sw_desc = { + .set = jz4740_musb_role_switch_set, + .driver_data = glue, + .fwnode = dev_fwnode(dev), + }; + int err; + + glue->musb = musb; + + if (IS_ENABLED(CONFIG_GENERIC_PHY)) { + musb->phy = devm_of_phy_get_by_index(dev, dev->of_node, 0); + if (IS_ERR(musb->phy)) { + err = PTR_ERR(musb->phy); + if (err != -ENODEV) { + dev_err(dev, "Unable to get PHY\n"); + return err; + } + + musb->phy = NULL; + } + } + + if (musb->phy) { + err = phy_init(musb->phy); + if (err) { + dev_err(dev, "Failed to init PHY\n"); + return err; + } + + err = phy_power_on(musb->phy); + if (err) { + dev_err(dev, "Unable to power on PHY\n"); + goto err_phy_shutdown; + } + } else { + if (dev->of_node) + musb->xceiv = devm_usb_get_phy_by_phandle(dev, "phys", 0); + else + musb->xceiv = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + if (IS_ERR(musb->xceiv)) { + dev_err(dev, "No transceiver configured\n"); + return PTR_ERR(musb->xceiv); + } + } + + glue->role_sw = usb_role_switch_register(dev, &role_sw_desc); + if (IS_ERR(glue->role_sw)) { + dev_err(dev, "Failed to register USB role switch\n"); + err = PTR_ERR(glue->role_sw); + goto err_phy_power_down; + } + + /* + * Silicon does not implement ConfigData register. + * Set dyn_fifo to avoid reading EP config from hardware. + */ + musb->dyn_fifo = true; + + musb->isr = jz4740_musb_interrupt; + + return 0; + +err_phy_power_down: + if (musb->phy) + phy_power_off(musb->phy); +err_phy_shutdown: + if (musb->phy) + phy_exit(musb->phy); + return err; +} + +static int jz4740_musb_exit(struct musb *musb) +{ + struct jz4740_glue *glue = dev_get_drvdata(musb->controller->parent); + + usb_role_switch_unregister(glue->role_sw); + if (musb->phy) { + phy_power_off(musb->phy); + phy_exit(musb->phy); + } + + return 0; +} + +static const struct musb_platform_ops jz4740_musb_ops = { + .quirks = MUSB_DMA_INVENTRA | MUSB_INDEXED_EP, + .fifo_mode = 2, + .init = jz4740_musb_init, + .exit = jz4740_musb_exit, +#ifdef CONFIG_USB_INVENTRA_DMA + .dma_init = musbhs_dma_controller_create_noirq, + .dma_exit = musbhs_dma_controller_destroy, +#endif +}; + +static const struct musb_hdrc_platform_data jz4740_musb_pdata = { + .mode = MUSB_PERIPHERAL, + .config = &jz4740_musb_config, + .platform_ops = &jz4740_musb_ops, +}; + +static const struct musb_fifo_cfg jz4770_musb_fifo_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, }, +}; + +static struct musb_hdrc_config jz4770_musb_config = { + .multipoint = 1, + .num_eps = 11, + .ram_bits = 11, + .fifo_cfg = jz4770_musb_fifo_cfg, + .fifo_cfg_size = ARRAY_SIZE(jz4770_musb_fifo_cfg), +}; + +static const struct musb_hdrc_platform_data jz4770_musb_pdata = { + .mode = MUSB_PERIPHERAL, /* TODO: support OTG */ + .config = &jz4770_musb_config, + .platform_ops = &jz4740_musb_ops, +}; + +static int jz4740_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct musb_hdrc_platform_data *pdata; + struct platform_device *musb; + struct jz4740_glue *glue; + struct clk *clk; + int ret; + + glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); + if (!glue) + return -ENOMEM; + + pdata = of_device_get_match_data(dev); + if (!pdata) { + dev_err(dev, "missing platform data\n"); + return -EINVAL; + } + + musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb) { + dev_err(dev, "failed to allocate musb device\n"); + return -ENOMEM; + } + + clk = devm_clk_get(dev, "udc"); + if (IS_ERR(clk)) { + dev_err(dev, "failed to get clock\n"); + ret = PTR_ERR(clk); + goto err_platform_device_put; + } + + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(dev, "failed to enable clock\n"); + goto err_platform_device_put; + } + + musb->dev.parent = dev; + musb->dev.dma_mask = &musb->dev.coherent_dma_mask; + musb->dev.coherent_dma_mask = DMA_BIT_MASK(32); + platform_device_set_of_node_from_dev(musb, dev); + + glue->pdev = musb; + glue->clk = clk; + + platform_set_drvdata(pdev, glue); + + ret = platform_device_add_resources(musb, pdev->resource, + pdev->num_resources); + if (ret) { + dev_err(dev, "failed to add resources\n"); + goto err_clk_disable; + } + + ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); + if (ret) { + dev_err(dev, "failed to add platform_data\n"); + goto err_clk_disable; + } + + ret = platform_device_add(musb); + if (ret) { + dev_err(dev, "failed to register musb device\n"); + goto err_clk_disable; + } + + return 0; + +err_clk_disable: + clk_disable_unprepare(clk); +err_platform_device_put: + platform_device_put(musb); + return ret; +} + +static void jz4740_remove(struct platform_device *pdev) +{ + struct jz4740_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->pdev); + clk_disable_unprepare(glue->clk); +} + +static const struct of_device_id jz4740_musb_of_match[] = { + { .compatible = "ingenic,jz4740-musb", .data = &jz4740_musb_pdata }, + { .compatible = "ingenic,jz4770-musb", .data = &jz4770_musb_pdata }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, jz4740_musb_of_match); + +static struct platform_driver jz4740_driver = { + .probe = jz4740_probe, + .remove = jz4740_remove, + .driver = { + .name = "musb-jz4740", + .of_match_table = jz4740_musb_of_match, + }, +}; + +MODULE_DESCRIPTION("JZ4740 MUSB Glue Layer"); +MODULE_AUTHOR("Apelete Seketeli "); +MODULE_LICENSE("GPL v2"); +module_platform_driver(jz4740_driver); diff --git a/drivers/usb/musb/mediatek.c b/drivers/usb/musb/mediatek.c new file mode 100644 index 000000000..c6cbe718b --- /dev/null +++ b/drivers/usb/musb/mediatek.c @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 MediaTek Inc. + * + * Author: + * Min Guo + * Yonglong Wu + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "musb_core.h" +#include "musb_dma.h" + +#define USB_L1INTS 0x00a0 +#define USB_L1INTM 0x00a4 +#define MTK_MUSB_TXFUNCADDR 0x0480 + +/* MediaTek controller toggle enable and status reg */ +#define MUSB_RXTOG 0x80 +#define MUSB_RXTOGEN 0x82 +#define MUSB_TXTOG 0x84 +#define MUSB_TXTOGEN 0x86 +#define MTK_TOGGLE_EN GENMASK(15, 0) + +#define TX_INT_STATUS BIT(0) +#define RX_INT_STATUS BIT(1) +#define USBCOM_INT_STATUS BIT(2) +#define DMA_INT_STATUS BIT(3) + +#define DMA_INTR_STATUS_MSK GENMASK(7, 0) +#define DMA_INTR_UNMASK_SET_MSK GENMASK(31, 24) + +#define MTK_MUSB_CLKS_NUM 3 + +struct mtk_glue { + struct device *dev; + struct musb *musb; + struct platform_device *musb_pdev; + struct platform_device *usb_phy; + struct phy *phy; + struct usb_phy *xceiv; + enum phy_mode phy_mode; + struct clk_bulk_data clks[MTK_MUSB_CLKS_NUM]; + enum usb_role role; + struct usb_role_switch *role_sw; +}; + +static int mtk_musb_clks_get(struct mtk_glue *glue) +{ + struct device *dev = glue->dev; + + glue->clks[0].id = "main"; + glue->clks[1].id = "mcu"; + glue->clks[2].id = "univpll"; + + return devm_clk_bulk_get(dev, MTK_MUSB_CLKS_NUM, glue->clks); +} + +static int mtk_otg_switch_set(struct mtk_glue *glue, enum usb_role role) +{ + struct musb *musb = glue->musb; + u8 devctl = readb(musb->mregs + MUSB_DEVCTL); + enum usb_role new_role; + + if (role == glue->role) + return 0; + + switch (role) { + case USB_ROLE_HOST: + musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE; + glue->phy_mode = PHY_MODE_USB_HOST; + new_role = USB_ROLE_HOST; + if (glue->role == USB_ROLE_NONE) + phy_power_on(glue->phy); + + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + MUSB_HST_MODE(musb); + break; + case USB_ROLE_DEVICE: + musb->xceiv->otg->state = OTG_STATE_B_IDLE; + glue->phy_mode = PHY_MODE_USB_DEVICE; + new_role = USB_ROLE_DEVICE; + devctl &= ~MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + if (glue->role == USB_ROLE_NONE) + phy_power_on(glue->phy); + + MUSB_DEV_MODE(musb); + break; + case USB_ROLE_NONE: + glue->phy_mode = PHY_MODE_USB_OTG; + new_role = USB_ROLE_NONE; + devctl &= ~MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + if (glue->role != USB_ROLE_NONE) + phy_power_off(glue->phy); + + break; + default: + dev_err(glue->dev, "Invalid State\n"); + return -EINVAL; + } + + glue->role = new_role; + phy_set_mode(glue->phy, glue->phy_mode); + + return 0; +} + +static int musb_usb_role_sx_set(struct usb_role_switch *sw, enum usb_role role) +{ + return mtk_otg_switch_set(usb_role_switch_get_drvdata(sw), role); +} + +static enum usb_role musb_usb_role_sx_get(struct usb_role_switch *sw) +{ + struct mtk_glue *glue = usb_role_switch_get_drvdata(sw); + + return glue->role; +} + +static int mtk_otg_switch_init(struct mtk_glue *glue) +{ + struct usb_role_switch_desc role_sx_desc = { 0 }; + + role_sx_desc.set = musb_usb_role_sx_set; + role_sx_desc.get = musb_usb_role_sx_get; + role_sx_desc.allow_userspace_control = true; + role_sx_desc.fwnode = dev_fwnode(glue->dev); + role_sx_desc.driver_data = glue; + glue->role_sw = usb_role_switch_register(glue->dev, &role_sx_desc); + + return PTR_ERR_OR_ZERO(glue->role_sw); +} + +static void mtk_otg_switch_exit(struct mtk_glue *glue) +{ + return usb_role_switch_unregister(glue->role_sw); +} + +static irqreturn_t generic_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_clearb(musb->mregs, MUSB_INTRUSB); + musb->int_rx = musb_clearw(musb->mregs, MUSB_INTRRX); + musb->int_tx = musb_clearw(musb->mregs, MUSB_INTRTX); + + if ((musb->int_usb & MUSB_INTR_RESET) && !is_host_active(musb)) { + /* ep0 FADDR must be 0 when (re)entering peripheral mode */ + musb_ep_select(musb->mregs, 0); + musb_writeb(musb->mregs, MUSB_FADDR, 0); + } + + if (musb->int_usb || musb->int_tx || musb->int_rx) + retval = musb_interrupt(musb); + + spin_unlock_irqrestore(&musb->lock, flags); + + return retval; +} + +static irqreturn_t mtk_musb_interrupt(int irq, void *dev_id) +{ + irqreturn_t retval = IRQ_NONE; + struct musb *musb = (struct musb *)dev_id; + u32 l1_ints; + + l1_ints = musb_readl(musb->mregs, USB_L1INTS) & + musb_readl(musb->mregs, USB_L1INTM); + + if (l1_ints & (TX_INT_STATUS | RX_INT_STATUS | USBCOM_INT_STATUS)) + retval = generic_interrupt(irq, musb); + +#if defined(CONFIG_USB_INVENTRA_DMA) + if (l1_ints & DMA_INT_STATUS) + retval = dma_controller_irq(irq, musb->dma_controller); +#endif + return retval; +} + +static u32 mtk_musb_busctl_offset(u8 epnum, u16 offset) +{ + return MTK_MUSB_TXFUNCADDR + offset + 8 * epnum; +} + +static u8 mtk_musb_clearb(void __iomem *addr, unsigned int offset) +{ + u8 data; + + /* W1C */ + data = musb_readb(addr, offset); + musb_writeb(addr, offset, data); + return data; +} + +static u16 mtk_musb_clearw(void __iomem *addr, unsigned int offset) +{ + u16 data; + + /* W1C */ + data = musb_readw(addr, offset); + musb_writew(addr, offset, data); + return data; +} + +static int mtk_musb_set_mode(struct musb *musb, u8 mode) +{ + struct device *dev = musb->controller; + struct mtk_glue *glue = dev_get_drvdata(dev->parent); + enum phy_mode new_mode; + enum usb_role new_role; + + switch (mode) { + case MUSB_HOST: + new_mode = PHY_MODE_USB_HOST; + new_role = USB_ROLE_HOST; + break; + case MUSB_PERIPHERAL: + new_mode = PHY_MODE_USB_DEVICE; + new_role = USB_ROLE_DEVICE; + break; + case MUSB_OTG: + new_mode = PHY_MODE_USB_OTG; + new_role = USB_ROLE_NONE; + break; + default: + dev_err(glue->dev, "Invalid mode request\n"); + return -EINVAL; + } + + if (glue->phy_mode == new_mode) + return 0; + + if (musb->port_mode != MUSB_OTG) { + dev_err(glue->dev, "Does not support changing modes\n"); + return -EINVAL; + } + + mtk_otg_switch_set(glue, new_role); + return 0; +} + +static int mtk_musb_init(struct musb *musb) +{ + struct device *dev = musb->controller; + struct mtk_glue *glue = dev_get_drvdata(dev->parent); + int ret; + + glue->musb = musb; + musb->phy = glue->phy; + musb->xceiv = glue->xceiv; + musb->is_host = false; + musb->isr = mtk_musb_interrupt; + + /* Set TX/RX toggle enable */ + musb_writew(musb->mregs, MUSB_TXTOGEN, MTK_TOGGLE_EN); + musb_writew(musb->mregs, MUSB_RXTOGEN, MTK_TOGGLE_EN); + + if (musb->port_mode == MUSB_OTG) { + ret = mtk_otg_switch_init(glue); + if (ret) + return ret; + } + + ret = phy_init(glue->phy); + if (ret) + goto err_phy_init; + + ret = phy_power_on(glue->phy); + if (ret) + goto err_phy_power_on; + + phy_set_mode(glue->phy, glue->phy_mode); + +#if defined(CONFIG_USB_INVENTRA_DMA) + musb_writel(musb->mregs, MUSB_HSDMA_INTR, + DMA_INTR_STATUS_MSK | DMA_INTR_UNMASK_SET_MSK); +#endif + musb_writel(musb->mregs, USB_L1INTM, TX_INT_STATUS | RX_INT_STATUS | + USBCOM_INT_STATUS | DMA_INT_STATUS); + return 0; + +err_phy_power_on: + phy_exit(glue->phy); +err_phy_init: + if (musb->port_mode == MUSB_OTG) + mtk_otg_switch_exit(glue); + return ret; +} + +static u16 mtk_musb_get_toggle(struct musb_qh *qh, int is_out) +{ + struct musb *musb = qh->hw_ep->musb; + u8 epnum = qh->hw_ep->epnum; + u16 toggle; + + toggle = musb_readw(musb->mregs, is_out ? MUSB_TXTOG : MUSB_RXTOG); + return toggle & (1 << epnum); +} + +static u16 mtk_musb_set_toggle(struct musb_qh *qh, int is_out, struct urb *urb) +{ + struct musb *musb = qh->hw_ep->musb; + u8 epnum = qh->hw_ep->epnum; + u16 value, toggle; + + toggle = usb_gettoggle(urb->dev, qh->epnum, is_out); + + if (is_out) { + value = musb_readw(musb->mregs, MUSB_TXTOG); + value |= toggle << epnum; + musb_writew(musb->mregs, MUSB_TXTOG, value); + } else { + value = musb_readw(musb->mregs, MUSB_RXTOG); + value |= toggle << epnum; + musb_writew(musb->mregs, MUSB_RXTOG, value); + } + + return 0; +} + +static int mtk_musb_exit(struct musb *musb) +{ + struct device *dev = musb->controller; + struct mtk_glue *glue = dev_get_drvdata(dev->parent); + + mtk_otg_switch_exit(glue); + phy_power_off(glue->phy); + phy_exit(glue->phy); + clk_bulk_disable_unprepare(MTK_MUSB_CLKS_NUM, glue->clks); + + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + return 0; +} + +static const struct musb_platform_ops mtk_musb_ops = { + .quirks = MUSB_DMA_INVENTRA, + .init = mtk_musb_init, + .get_toggle = mtk_musb_get_toggle, + .set_toggle = mtk_musb_set_toggle, + .exit = mtk_musb_exit, +#ifdef CONFIG_USB_INVENTRA_DMA + .dma_init = musbhs_dma_controller_create_noirq, + .dma_exit = musbhs_dma_controller_destroy, +#endif + .clearb = mtk_musb_clearb, + .clearw = mtk_musb_clearw, + .busctl_offset = mtk_musb_busctl_offset, + .set_mode = mtk_musb_set_mode, +}; + +#define MTK_MUSB_MAX_EP_NUM 8 +#define MTK_MUSB_RAM_BITS 11 + +static const struct musb_fifo_cfg mtk_musb_mode_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 = 1024, }, + { .hw_ep_num = 6, .style = FIFO_RX, .maxpacket = 1024, }, + { .hw_ep_num = 7, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 7, .style = FIFO_RX, .maxpacket = 64, }, +}; + +static const struct musb_hdrc_config mtk_musb_hdrc_config = { + .fifo_cfg = mtk_musb_mode_cfg, + .fifo_cfg_size = ARRAY_SIZE(mtk_musb_mode_cfg), + .multipoint = true, + .dyn_fifo = true, + .num_eps = MTK_MUSB_MAX_EP_NUM, + .ram_bits = MTK_MUSB_RAM_BITS, +}; + +static const struct platform_device_info mtk_dev_info = { + .name = "musb-hdrc", + .id = PLATFORM_DEVID_AUTO, + .dma_mask = DMA_BIT_MASK(32), +}; + +static int mtk_musb_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata; + struct mtk_glue *glue; + struct platform_device_info pinfo; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + int ret; + + glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); + if (!glue) + return -ENOMEM; + + glue->dev = dev; + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + ret = of_platform_populate(np, NULL, NULL, dev); + if (ret) + return dev_err_probe(dev, ret, + "failed to create child devices at %p\n", np); + + ret = mtk_musb_clks_get(glue); + if (ret) + return ret; + + pdata->config = &mtk_musb_hdrc_config; + pdata->platform_ops = &mtk_musb_ops; + pdata->mode = usb_get_dr_mode(dev); + + if (IS_ENABLED(CONFIG_USB_MUSB_HOST)) + pdata->mode = USB_DR_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_MUSB_GADGET)) + pdata->mode = USB_DR_MODE_PERIPHERAL; + + switch (pdata->mode) { + case USB_DR_MODE_HOST: + glue->phy_mode = PHY_MODE_USB_HOST; + glue->role = USB_ROLE_HOST; + break; + case USB_DR_MODE_PERIPHERAL: + glue->phy_mode = PHY_MODE_USB_DEVICE; + glue->role = USB_ROLE_DEVICE; + break; + case USB_DR_MODE_OTG: + glue->phy_mode = PHY_MODE_USB_OTG; + glue->role = USB_ROLE_NONE; + break; + default: + return dev_err_probe(&pdev->dev, -EINVAL, + "Error 'dr_mode' property\n"); + } + + glue->phy = devm_of_phy_get_by_index(dev, np, 0); + if (IS_ERR(glue->phy)) + return dev_err_probe(dev, PTR_ERR(glue->phy), + "fail to getting phy\n"); + + glue->usb_phy = usb_phy_generic_register(); + if (IS_ERR(glue->usb_phy)) + return dev_err_probe(dev, PTR_ERR(glue->usb_phy), + "fail to registering usb-phy\n"); + + glue->xceiv = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + if (IS_ERR(glue->xceiv)) { + ret = PTR_ERR(glue->xceiv); + dev_err(dev, "fail to getting usb-phy %d\n", ret); + goto err_unregister_usb_phy; + } + + platform_set_drvdata(pdev, glue); + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + ret = clk_bulk_prepare_enable(MTK_MUSB_CLKS_NUM, glue->clks); + if (ret) + goto err_enable_clk; + + pinfo = mtk_dev_info; + pinfo.parent = dev; + pinfo.res = pdev->resource; + pinfo.num_res = pdev->num_resources; + pinfo.data = pdata; + pinfo.size_data = sizeof(*pdata); + pinfo.fwnode = of_fwnode_handle(np); + pinfo.of_node_reused = true; + + glue->musb_pdev = platform_device_register_full(&pinfo); + if (IS_ERR(glue->musb_pdev)) { + ret = PTR_ERR(glue->musb_pdev); + dev_err(dev, "failed to register musb device: %d\n", ret); + goto err_device_register; + } + + return 0; + +err_device_register: + clk_bulk_disable_unprepare(MTK_MUSB_CLKS_NUM, glue->clks); +err_enable_clk: + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); +err_unregister_usb_phy: + usb_phy_generic_unregister(glue->usb_phy); + return ret; +} + +static void mtk_musb_remove(struct platform_device *pdev) +{ + struct mtk_glue *glue = platform_get_drvdata(pdev); + struct platform_device *usb_phy = glue->usb_phy; + + platform_device_unregister(glue->musb_pdev); + usb_phy_generic_unregister(usb_phy); +} + +#ifdef CONFIG_OF +static const struct of_device_id mtk_musb_match[] = { + {.compatible = "mediatek,mtk-musb",}, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_musb_match); +#endif + +static struct platform_driver mtk_musb_driver = { + .probe = mtk_musb_probe, + .remove = mtk_musb_remove, + .driver = { + .name = "musb-mtk", + .of_match_table = of_match_ptr(mtk_musb_match), + }, +}; + +module_platform_driver(mtk_musb_driver); + +MODULE_DESCRIPTION("MediaTek MUSB Glue Layer"); +MODULE_AUTHOR("Min Guo "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/musb/mpfs.c b/drivers/usb/musb/mpfs.c new file mode 100644 index 000000000..587127abd --- /dev/null +++ b/drivers/usb/musb/mpfs.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PolarFire SoC (MPFS) MUSB Glue Layer + * + * Copyright (c) 2020-2022 Microchip Corporation. All rights reserved. + * Based on {omap2430,tusb6010,ux500}.c + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "musb_core.h" +#include "musb_dma.h" + +#define MPFS_MUSB_MAX_EP_NUM 8 +#define MPFS_MUSB_RAM_BITS 12 + +struct mpfs_glue { + struct device *dev; + struct platform_device *musb; + struct platform_device *phy; + struct clk *clk; +}; + +static const struct musb_fifo_cfg mpfs_musb_mode_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 = 1024, }, + { .hw_ep_num = 4, .style = FIFO_RX, .maxpacket = 4096, }, +}; + +static const struct musb_hdrc_config mpfs_musb_hdrc_config = { + .fifo_cfg = mpfs_musb_mode_cfg, + .fifo_cfg_size = ARRAY_SIZE(mpfs_musb_mode_cfg), + .multipoint = true, + .dyn_fifo = true, + .num_eps = MPFS_MUSB_MAX_EP_NUM, + .ram_bits = MPFS_MUSB_RAM_BITS, +}; + +static void mpfs_musb_set_vbus(struct musb *musb, int is_on) +{ + u8 devctl; + + /* + * 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) { + musb->is_active = 1; + musb->xceiv->otg->default_a = 1; + musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE; + devctl |= MUSB_DEVCTL_SESSION; + MUSB_HST_MODE(musb); + } else { + musb->is_active = 0; + + /* + * NOTE: skipping A_WAIT_VFALL -> A_IDLE and + * jumping right to B_IDLE... + */ + musb->xceiv->otg->default_a = 0; + musb->xceiv->otg->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\n", + usb_otg_state_string(musb->xceiv->otg->state), + musb_readb(musb->mregs, MUSB_DEVCTL)); +} + +#define POLL_SECONDS 2 + +static void otg_timer(struct timer_list *t) +{ + struct musb *musb = timer_container_of(musb, t, + dev_timer); + void __iomem *mregs = musb->mregs; + u8 devctl; + unsigned long flags; + + /* + * We poll because PolarFire SoC 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->otg->state)); + + spin_lock_irqsave(&musb->lock, flags); + switch (musb->xceiv->otg->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->otg->state = OTG_STATE_B_IDLE; + MUSB_DEV_MODE(musb); + mod_timer(&musb->dev_timer, jiffies + POLL_SECONDS * HZ); + } else { + musb->xceiv->otg->state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + } + break; + case OTG_STATE_A_WAIT_VFALL: + if (devctl & MUSB_DEVCTL_VBUS) { + mod_timer(&musb->dev_timer, jiffies + POLL_SECONDS * HZ); + break; + } + musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE; + 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(&musb->dev_timer, jiffies + POLL_SECONDS * HZ); + else + musb->xceiv->otg->state = OTG_STATE_A_IDLE; + break; + default: + break; + } + spin_unlock_irqrestore(&musb->lock, flags); +} + +static void __maybe_unused mpfs_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->otg->state == OTG_STATE_A_WAIT_BCON)) { + dev_dbg(musb->controller, "%s active, deleting timer\n", + usb_otg_state_string(musb->xceiv->otg->state)); + timer_delete(&musb->dev_timer); + last_timer = jiffies; + return; + } + + if (time_after(last_timer, timeout) && timer_pending(&musb->dev_timer)) { + 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->otg->state), + jiffies_to_msecs(timeout - jiffies)); + mod_timer(&musb->dev_timer, timeout); +} + +static irqreturn_t mpfs_musb_interrupt(int irq, void *__hci) +{ + unsigned long flags; + irqreturn_t ret = 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); + ret = musb_interrupt(musb); + } + + /* Poll for ID change */ + if (musb->xceiv->otg->state == OTG_STATE_B_IDLE) + mod_timer(&musb->dev_timer, jiffies + POLL_SECONDS * HZ); + + spin_unlock_irqrestore(&musb->lock, flags); + + return ret; +} + +static int mpfs_musb_init(struct musb *musb) +{ + struct device *dev = musb->controller; + + musb->xceiv = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + if (IS_ERR(musb->xceiv)) { + dev_err(dev, "HS UDC: no transceiver configured\n"); + return PTR_ERR(musb->xceiv); + } + + timer_setup(&musb->dev_timer, otg_timer, 0); + + musb->dyn_fifo = true; + musb->isr = mpfs_musb_interrupt; + + musb_platform_set_vbus(musb, 1); + + return 0; +} + +static int mpfs_musb_exit(struct musb *musb) +{ + timer_delete_sync(&musb->dev_timer); + + return 0; +} + +static const struct musb_platform_ops mpfs_ops = { + .quirks = MUSB_DMA_INVENTRA, + .init = mpfs_musb_init, + .exit = mpfs_musb_exit, + .fifo_mode = 2, +#ifdef CONFIG_USB_INVENTRA_DMA + .dma_init = musbhs_dma_controller_create, + .dma_exit = musbhs_dma_controller_destroy, +#endif +#ifndef CONFIG_USB_MUSB_HOST + .try_idle = mpfs_musb_try_idle, +#endif + .set_vbus = mpfs_musb_set_vbus +}; + +static int mpfs_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct mpfs_glue *glue; + struct platform_device *musb_pdev; + struct device *dev = &pdev->dev; + struct clk *clk; + int ret; + + glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); + if (!glue) + return -ENOMEM; + + musb_pdev = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb_pdev) { + dev_err(dev, "failed to allocate musb device\n"); + return -ENOMEM; + } + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + ret = PTR_ERR(clk); + goto err_phy_release; + } + + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable clock\n"); + goto err_phy_release; + } + + musb_pdev->dev.parent = dev; + musb_pdev->dev.coherent_dma_mask = DMA_BIT_MASK(39); + musb_pdev->dev.dma_mask = &musb_pdev->dev.coherent_dma_mask; + device_set_of_node_from_dev(&musb_pdev->dev, dev); + + glue->dev = dev; + glue->musb = musb_pdev; + glue->clk = clk; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + ret = -ENOMEM; + goto err_clk_disable; + } + + pdata->config = &mpfs_musb_hdrc_config; + pdata->platform_ops = &mpfs_ops; + + pdata->extvbus = device_property_read_bool(dev, "microchip,ext-vbus-drv"); + + pdata->mode = usb_get_dr_mode(dev); + if (pdata->mode == USB_DR_MODE_UNKNOWN) { + dev_info(dev, "No dr_mode property found, defaulting to otg\n"); + pdata->mode = USB_DR_MODE_OTG; + } + + glue->phy = usb_phy_generic_register(); + if (IS_ERR(glue->phy)) { + dev_err(dev, "failed to register usb-phy %ld\n", + PTR_ERR(glue->phy)); + ret = PTR_ERR(glue->phy); + goto err_clk_disable; + } + + platform_set_drvdata(pdev, glue); + + ret = platform_device_add_resources(musb_pdev, pdev->resource, pdev->num_resources); + if (ret) { + dev_err(dev, "failed to add resources\n"); + goto err_clk_disable; + } + + ret = platform_device_add_data(musb_pdev, pdata, sizeof(*pdata)); + if (ret) { + dev_err(dev, "failed to add platform_data\n"); + goto err_clk_disable; + } + + ret = platform_device_add(musb_pdev); + if (ret) { + dev_err(dev, "failed to register musb device\n"); + goto err_clk_disable; + } + + dev_info(&pdev->dev, "Registered MPFS MUSB driver\n"); + return 0; + +err_clk_disable: + clk_disable_unprepare(clk); + +err_phy_release: + usb_phy_generic_unregister(glue->phy); + platform_device_put(musb_pdev); + return ret; +} + +static void mpfs_remove(struct platform_device *pdev) +{ + struct mpfs_glue *glue = platform_get_drvdata(pdev); + + clk_disable_unprepare(glue->clk); + platform_device_unregister(glue->musb); + usb_phy_generic_unregister(pdev); +} + +#ifdef CONFIG_OF +static const struct of_device_id mpfs_id_table[] = { + { .compatible = "microchip,mpfs-musb" }, + { } +}; +MODULE_DEVICE_TABLE(of, mpfs_id_table); +#endif + +static struct platform_driver mpfs_musb_driver = { + .probe = mpfs_probe, + .remove = mpfs_remove, + .driver = { + .name = "mpfs-musb", + .of_match_table = of_match_ptr(mpfs_id_table) + }, +}; + +module_platform_driver(mpfs_musb_driver); + +MODULE_DESCRIPTION("PolarFire SoC MUSB Glue Layer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c new file mode 100644 index 000000000..73ac25f53 --- /dev/null +++ b/drivers/usb/musb/musb_core.c @@ -0,0 +1,2955 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MUSB OTG driver core code + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + */ + +/* + * 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 information + * (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 +#include + +#include "musb_core.h" +#include "musb_trace.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); +} + +enum musb_mode musb_get_mode(struct device *dev) +{ + enum usb_dr_mode mode; + + mode = usb_get_dr_mode(dev); + switch (mode) { + case USB_DR_MODE_HOST: + return MUSB_HOST; + case USB_DR_MODE_PERIPHERAL: + return MUSB_PERIPHERAL; + case USB_DR_MODE_OTG: + case USB_DR_MODE_UNKNOWN: + default: + return MUSB_OTG; + } +} +EXPORT_SYMBOL_GPL(musb_get_mode); + +/*-------------------------------------------------------------------------*/ + +static int musb_ulpi_read(struct usb_phy *phy, u32 reg) +{ + 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)reg); + 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 val, u32 reg) +{ + 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)reg); + musb_writeb(addr, MUSB_ULPI_REG_DATA, (u8)val); + 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; +} + +static struct usb_phy_io_ops musb_ulpi_access = { + .read = musb_ulpi_read, + .write = musb_ulpi_write, +}; + +/*-------------------------------------------------------------------------*/ + +static u32 musb_default_fifo_offset(u8 epnum) +{ + return 0x20 + (epnum * 4); +} + +/* "flat" mapping: each endpoint has its own i/o address */ +static void musb_flat_ep_select(void __iomem *mbase, u8 epnum) +{ +} + +static u32 musb_flat_ep_offset(u8 epnum, u16 offset) +{ + return 0x100 + (0x10 * epnum) + offset; +} + +/* "indexed" mapping: INDEX register controls register bank select */ +static void musb_indexed_ep_select(void __iomem *mbase, u8 epnum) +{ + musb_writeb(mbase, MUSB_INDEX, epnum); +} + +static u32 musb_indexed_ep_offset(u8 epnum, u16 offset) +{ + return 0x10 + offset; +} + +static u32 musb_default_busctl_offset(u8 epnum, u16 offset) +{ + return 0x80 + (0x08 * epnum) + offset; +} + +static u8 musb_default_readb(void __iomem *addr, u32 offset) +{ + u8 data = __raw_readb(addr + offset); + + trace_musb_readb(__builtin_return_address(0), addr, offset, data); + return data; +} + +static void musb_default_writeb(void __iomem *addr, u32 offset, u8 data) +{ + trace_musb_writeb(__builtin_return_address(0), addr, offset, data); + __raw_writeb(data, addr + offset); +} + +static u16 musb_default_readw(void __iomem *addr, u32 offset) +{ + u16 data = __raw_readw(addr + offset); + + trace_musb_readw(__builtin_return_address(0), addr, offset, data); + return data; +} + +static void musb_default_writew(void __iomem *addr, u32 offset, u16 data) +{ + trace_musb_writew(__builtin_return_address(0), addr, offset, data); + __raw_writew(data, addr + offset); +} + +static u16 musb_default_get_toggle(struct musb_qh *qh, int is_out) +{ + void __iomem *epio = qh->hw_ep->regs; + u16 csr; + + if (is_out) + csr = musb_readw(epio, MUSB_TXCSR) & MUSB_TXCSR_H_DATATOGGLE; + else + csr = musb_readw(epio, MUSB_RXCSR) & MUSB_RXCSR_H_DATATOGGLE; + + return csr; +} + +static u16 musb_default_set_toggle(struct musb_qh *qh, int is_out, + struct urb *urb) +{ + u16 csr; + u16 toggle; + + toggle = usb_gettoggle(urb->dev, qh->epnum, is_out); + + if (is_out) + csr = toggle ? (MUSB_TXCSR_H_WR_DATATOGGLE + | MUSB_TXCSR_H_DATATOGGLE) + : MUSB_TXCSR_CLRDATATOG; + else + csr = toggle ? (MUSB_RXCSR_H_WR_DATATOGGLE + | MUSB_RXCSR_H_DATATOGGLE) : 0; + + return csr; +} + +/* + * Load an endpoint's FIFO + */ +static void musb_default_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) { + __raw_writew(*(u16 *)&src[index], fifo); + index += 2; + } + } else { + if (len >= 2) { + iowrite16_rep(fifo, src + index, len >> 1); + index += len & ~0x01; + } + } + if (len & 0x01) + __raw_writeb(src[index], fifo); + } else { + /* byte aligned */ + iowrite8_rep(fifo, src, len); + } +} + +/* + * Unload an endpoint's FIFO + */ +static void musb_default_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] = __raw_readw(fifo); + index += 2; + } + } else { + if (len >= 2) { + ioread16_rep(fifo, dst, len >> 1); + index = len & ~0x01; + } + } + if (len & 0x01) + dst[index] = __raw_readb(fifo); + } else { + /* byte aligned */ + ioread8_rep(fifo, dst, len); + } +} + +/* + * Old style IO functions + */ +u8 (*musb_readb)(void __iomem *addr, u32 offset); +EXPORT_SYMBOL_GPL(musb_readb); + +void (*musb_writeb)(void __iomem *addr, u32 offset, u8 data); +EXPORT_SYMBOL_GPL(musb_writeb); + +u8 (*musb_clearb)(void __iomem *addr, u32 offset); +EXPORT_SYMBOL_GPL(musb_clearb); + +u16 (*musb_readw)(void __iomem *addr, u32 offset); +EXPORT_SYMBOL_GPL(musb_readw); + +void (*musb_writew)(void __iomem *addr, u32 offset, u16 data); +EXPORT_SYMBOL_GPL(musb_writew); + +u16 (*musb_clearw)(void __iomem *addr, u32 offset); +EXPORT_SYMBOL_GPL(musb_clearw); + +u32 musb_readl(void __iomem *addr, u32 offset) +{ + u32 data = __raw_readl(addr + offset); + + trace_musb_readl(__builtin_return_address(0), addr, offset, data); + return data; +} +EXPORT_SYMBOL_GPL(musb_readl); + +void musb_writel(void __iomem *addr, u32 offset, u32 data) +{ + trace_musb_writel(__builtin_return_address(0), addr, offset, data); + __raw_writel(data, addr + offset); +} +EXPORT_SYMBOL_GPL(musb_writel); + +#ifndef CONFIG_MUSB_PIO_ONLY +struct dma_controller * +(*musb_dma_controller_create)(struct musb *musb, void __iomem *base); +EXPORT_SYMBOL(musb_dma_controller_create); + +void (*musb_dma_controller_destroy)(struct dma_controller *c); +EXPORT_SYMBOL(musb_dma_controller_destroy); +#endif + +/* + * New style IO functions + */ +void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) +{ + return hw_ep->musb->io.read_fifo(hw_ep, len, dst); +} + +void musb_write_fifo(struct musb_hw_ep *hw_ep, u16 len, const u8 *src) +{ + return hw_ep->musb->io.write_fifo(hw_ep, len, src); +} + +static u8 musb_read_devctl(struct musb *musb) +{ + return musb_readb(musb->mregs, MUSB_DEVCTL); +} + +/** + * musb_set_host - set and initialize host mode + * @musb: musb controller driver data + * + * At least some musb revisions need to enable devctl session bit in + * peripheral mode to switch to host mode. Initializes things to host + * mode and sets A_IDLE. SoC glue needs to advance state further + * based on phy provided VBUS state. + * + * Note that the SoC glue code may need to wait for musb to settle + * on enable before calling this to avoid babble. + */ +int musb_set_host(struct musb *musb) +{ + int error = 0; + u8 devctl; + + if (!musb) + return -EINVAL; + + devctl = musb_read_devctl(musb); + if (!(devctl & MUSB_DEVCTL_BDEVICE)) { + trace_musb_state(musb, devctl, "Already in host mode"); + goto init_data; + } + + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + error = readx_poll_timeout(musb_read_devctl, musb, devctl, + !(devctl & MUSB_DEVCTL_BDEVICE), 5000, + 1000000); + if (error) { + dev_err(musb->controller, "%s: could not set host: %02x\n", + __func__, devctl); + + return error; + } + + devctl = musb_read_devctl(musb); + trace_musb_state(musb, devctl, "Host mode set"); + +init_data: + musb->is_active = 1; + musb_set_state(musb, OTG_STATE_A_IDLE); + MUSB_HST_MODE(musb); + + return error; +} +EXPORT_SYMBOL_GPL(musb_set_host); + +/** + * musb_set_peripheral - set and initialize peripheral mode + * @musb: musb controller driver data + * + * Clears devctl session bit and initializes things for peripheral + * mode and sets B_IDLE. SoC glue needs to advance state further + * based on phy provided VBUS state. + */ +int musb_set_peripheral(struct musb *musb) +{ + int error = 0; + u8 devctl; + + if (!musb) + return -EINVAL; + + devctl = musb_read_devctl(musb); + if (devctl & MUSB_DEVCTL_BDEVICE) { + trace_musb_state(musb, devctl, "Already in peripheral mode"); + goto init_data; + } + + devctl &= ~MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + error = readx_poll_timeout(musb_read_devctl, musb, devctl, + devctl & MUSB_DEVCTL_BDEVICE, 5000, + 1000000); + if (error) { + dev_err(musb->controller, "%s: could not set peripheral: %02x\n", + __func__, devctl); + + return error; + } + + devctl = musb_read_devctl(musb); + trace_musb_state(musb, devctl, "Peripheral mode set"); + +init_data: + musb->is_active = 0; + musb_set_state(musb, OTG_STATE_B_IDLE); + MUSB_DEV_MODE(musb); + + return error; +} +EXPORT_SYMBOL_GPL(musb_set_peripheral); + +/*-------------------------------------------------------------------------*/ + +/* 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(struct timer_list *t) +{ + struct musb *musb = timer_container_of(musb, t, otg_timer); + unsigned long flags; + + spin_lock_irqsave(&musb->lock, flags); + switch (musb_get_state(musb)) { + case OTG_STATE_B_WAIT_ACON: + musb_dbg(musb, + "HNP: b_wait_acon timeout; back to b_peripheral"); + musb_g_disconnect(musb); + musb_set_state(musb, OTG_STATE_B_PERIPHERAL); + musb->is_active = 0; + break; + case OTG_STATE_A_SUSPEND: + case OTG_STATE_A_WAIT_BCON: + musb_dbg(musb, "HNP: %s timeout", + musb_otg_state_string(musb)); + musb_platform_set_vbus(musb, 0); + musb_set_state(musb, OTG_STATE_A_WAIT_VFALL); + break; + default: + musb_dbg(musb, "HNP: Unhandled mode %s", + musb_otg_state_string(musb)); + } + 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->hcd; + void __iomem *mbase = musb->mregs; + u8 reg; + + musb_dbg(musb, "HNP: stop from %s", musb_otg_state_string(musb)); + + switch (musb_get_state(musb)) { + case OTG_STATE_A_PERIPHERAL: + musb_g_disconnect(musb); + musb_dbg(musb, "HNP: back to %s", musb_otg_state_string(musb)); + break; + case OTG_STATE_B_HOST: + musb_dbg(musb, "HNP: Disabling HR"); + if (hcd) + hcd->self.is_b_host = 0; + musb_set_state(musb, 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: + musb_dbg(musb, "HNP: Stopping in unknown state %s", + musb_otg_state_string(musb)); + } + + /* + * 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); +} + +static void musb_recover_from_babble(struct musb *musb); + +static void musb_handle_intr_resume(struct musb *musb, u8 devctl) +{ + musb_dbg(musb, "RESUME (%s)", musb_otg_state_string(musb)); + + if (devctl & MUSB_DEVCTL_HM) { + switch (musb_get_state(musb)) { + case OTG_STATE_A_SUSPEND: + /* remote wakeup? */ + musb->port1_status |= + (USB_PORT_STAT_C_SUSPEND << 16) + | MUSB_PORT_STAT_RESUME; + musb->rh_timer = jiffies + + msecs_to_jiffies(USB_RESUME_TIMEOUT); + musb_set_state(musb, OTG_STATE_A_HOST); + musb->is_active = 1; + musb_host_resume_root_hub(musb); + schedule_delayed_work(&musb->finish_resume_work, + msecs_to_jiffies(USB_RESUME_TIMEOUT)); + break; + case OTG_STATE_B_WAIT_ACON: + musb_set_state(musb, OTG_STATE_B_PERIPHERAL); + musb->is_active = 1; + MUSB_DEV_MODE(musb); + break; + default: + WARNING("bogus %s RESUME (%s)\n", + "host", + musb_otg_state_string(musb)); + } + } else { + switch (musb_get_state(musb)) { + case OTG_STATE_A_SUSPEND: + /* possibly DISCONNECT is upcoming */ + musb_set_state(musb, OTG_STATE_A_HOST); + musb_host_resume_root_hub(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", + musb_otg_state_string(musb)); + } + } +} + +/* return IRQ_HANDLED to tell the caller to return immediately */ +static irqreturn_t musb_handle_intr_sessreq(struct musb *musb, u8 devctl) +{ + void __iomem *mbase = musb->mregs; + + if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS + && (devctl & MUSB_DEVCTL_BDEVICE)) { + musb_dbg(musb, "SessReq while on B state"); + return IRQ_HANDLED; + } + + musb_dbg(musb, "SESSION_REQUEST (%s)", musb_otg_state_string(musb)); + + /* 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_set_state(musb, OTG_STATE_A_IDLE); + MUSB_HST_MODE(musb); + musb_platform_set_vbus(musb, 1); + + return IRQ_NONE; +} + +static void musb_handle_intr_vbuserr(struct musb *musb, u8 devctl) +{ + 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_get_state(musb)) { + 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", + musb_otg_state_string(musb), + 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); +} + +static void musb_handle_intr_suspend(struct musb *musb, u8 devctl) +{ + musb_dbg(musb, "SUSPEND (%s) devctl %02x", + musb_otg_state_string(musb), devctl); + + switch (musb_get_state(musb)) { + 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); + musb_host_resume_root_hub(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; + fallthrough; + case OTG_STATE_B_PERIPHERAL: + musb_g_suspend(musb); + musb->is_active = musb->g.b_hnp_enable; + if (musb->is_active) { + musb_set_state(musb, OTG_STATE_B_WAIT_ACON); + musb_dbg(musb, "HNP: Setting timer for b_ase0_brst"); + 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_set_state(musb, OTG_STATE_A_SUSPEND); + musb->is_active = musb->hcd->self.b_hnp_enable; + break; + case OTG_STATE_B_HOST: + /* Transition to B_PERIPHERAL, see 6.8.2.6 p 44 */ + musb_dbg(musb, "REVISIT: SUSPEND as B_HOST"); + break; + default: + /* "should not happen" */ + musb->is_active = 0; + break; + } +} + +static void musb_handle_intr_connect(struct musb *musb, u8 devctl, u8 int_usb) +{ + struct usb_hcd *hcd = musb->hcd; + + musb->is_active = 1; + musb->ep0_stage = MUSB_EP0_START; + + 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_get_state(musb)) { + case OTG_STATE_B_PERIPHERAL: + if (int_usb & MUSB_INTR_SUSPEND) { + musb_dbg(musb, "HNP: SUSPEND+CONNECT, now b_host"); + int_usb &= ~MUSB_INTR_SUSPEND; + goto b_host; + } else + musb_dbg(musb, "CONNECT as b_peripheral???"); + break; + case OTG_STATE_B_WAIT_ACON: + musb_dbg(musb, "HNP: CONNECT, now b_host"); +b_host: + musb_set_state(musb, OTG_STATE_B_HOST); + if (musb->hcd) + musb->hcd->self.is_b_host = 1; + timer_delete(&musb->otg_timer); + break; + default: + if ((devctl & MUSB_DEVCTL_VBUS) + == (3 << MUSB_DEVCTL_VBUS_SHIFT)) { + musb_set_state(musb, OTG_STATE_A_HOST); + if (hcd) + hcd->self.is_b_host = 0; + } + break; + } + + musb_host_poke_root_hub(musb); + + musb_dbg(musb, "CONNECT (%s) devctl %02x", + musb_otg_state_string(musb), devctl); +} + +static void musb_handle_intr_disconnect(struct musb *musb, u8 devctl) +{ + musb_dbg(musb, "DISCONNECT (%s) as %s, devctl %02x", + musb_otg_state_string(musb), + MUSB_MODE(musb), devctl); + + switch (musb_get_state(musb)) { + case OTG_STATE_A_HOST: + case OTG_STATE_A_SUSPEND: + musb_host_resume_root_hub(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); + if (musb->hcd) + musb->hcd->self.is_b_host = 0; + musb_set_state(musb, 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: + case OTG_STATE_B_PERIPHERAL: + case OTG_STATE_B_IDLE: + musb_g_disconnect(musb); + break; + default: + WARNING("unhandled DISCONNECT transition (%s)\n", + musb_otg_state_string(musb)); + break; + } +} + +/* + * mentor saves a bit: bus reset and babble share the same irq. + * only host sees babble; only peripheral sees bus reset. + */ +static void musb_handle_intr_reset(struct musb *musb) +{ + if (is_host_active(musb)) { + /* + * When BABBLE happens what we can depends on which + * platform MUSB is running, because some platforms + * implemented proprietary means for 'recovering' from + * Babble conditions. One such platform is AM335x. In + * most cases, however, the only thing we can do is + * drop the session. + */ + dev_err(musb->controller, "Babble\n"); + musb_recover_from_babble(musb); + } else { + musb_dbg(musb, "BUS RESET as %s", musb_otg_state_string(musb)); + switch (musb_get_state(musb)) { + case OTG_STATE_A_SUSPEND: + musb_g_reset(musb); + fallthrough; + case OTG_STATE_A_WAIT_BCON: /* OPT TD.4.7-900ms */ + /* never use invalid T(a_wait_bcon) */ + musb_dbg(musb, "HNP: in %s, %d msec timeout", + musb_otg_state_string(musb), + TA_WAIT_BCON(musb)); + mod_timer(&musb->otg_timer, jiffies + + msecs_to_jiffies(TA_WAIT_BCON(musb))); + break; + case OTG_STATE_A_PERIPHERAL: + timer_delete(&musb->otg_timer); + musb_g_reset(musb); + break; + case OTG_STATE_B_WAIT_ACON: + musb_dbg(musb, "HNP: RESET (%s), to b_peripheral", + musb_otg_state_string(musb)); + musb_set_state(musb, OTG_STATE_B_PERIPHERAL); + musb_g_reset(musb); + break; + case OTG_STATE_B_IDLE: + musb_set_state(musb, OTG_STATE_B_PERIPHERAL); + fallthrough; + case OTG_STATE_B_PERIPHERAL: + musb_g_reset(musb); + break; + default: + musb_dbg(musb, "Unhandled BUS RESET as %s", + musb_otg_state_string(musb)); + } + } +} + +/* + * 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 + */ + +static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, + u8 devctl) +{ + irqreturn_t handled = IRQ_NONE; + + musb_dbg(musb, "<== DevCtl=%02x, int_usb=0x%x", 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) { + musb_handle_intr_resume(musb, devctl); + handled = IRQ_HANDLED; + } + + /* see manual for the order of the tests */ + if (int_usb & MUSB_INTR_SESSREQ) { + if (musb_handle_intr_sessreq(musb, devctl)) + return IRQ_HANDLED; + handled = IRQ_HANDLED; + } + + if (int_usb & MUSB_INTR_VBUSERROR) { + musb_handle_intr_vbuserr(musb, devctl); + handled = IRQ_HANDLED; + } + + if (int_usb & MUSB_INTR_SUSPEND) { + musb_handle_intr_suspend(musb, devctl); + handled = IRQ_HANDLED; + } + + if (int_usb & MUSB_INTR_CONNECT) { + musb_handle_intr_connect(musb, devctl, int_usb); + handled = IRQ_HANDLED; + } + + if (int_usb & MUSB_INTR_DISCONNECT) { + musb_handle_intr_disconnect(musb, devctl); + handled = IRQ_HANDLED; + } + + if (int_usb & MUSB_INTR_RESET) { + musb_handle_intr_reset(musb); + handled = IRQ_HANDLED; + } + +#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_delayed_work(&musb->irq_work, 0); + + return handled; +} + +/*-------------------------------------------------------------------------*/ + +static void musb_disable_interrupts(struct musb *musb) +{ + void __iomem *mbase = musb->mregs; + + /* 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); + + /* flush pending interrupts */ + musb_clearb(mbase, MUSB_INTRUSB); + musb_clearw(mbase, MUSB_INTRTX); + musb_clearw(mbase, MUSB_INTRRX); +} + +static void musb_enable_interrupts(struct musb *musb) +{ + void __iomem *regs = musb->mregs; + + /* 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); + +} + +/* + * 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); + u8 power; + + musb_dbg(musb, "<== devctl %02x", devctl); + + musb_enable_interrupts(musb); + musb_writeb(regs, MUSB_TESTMODE, 0); + + power = MUSB_POWER_ISOUPDATE; + /* + * treating UNKNOWN as unspecified maximum speed, in which case + * we will default to high-speed. + */ + if (musb->config->maximum_speed == USB_SPEED_HIGH || + musb->config->maximum_speed == USB_SPEED_UNKNOWN) + power |= MUSB_POWER_HSENAB; + musb_writeb(regs, MUSB_POWER, power); + + 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 (musb->port_mode != MUSB_HOST && + musb_get_state(musb) != OTG_STATE_A_WAIT_BCON && + (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); +} + +/* + * 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_disable_interrupts(musb); + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + + /* 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); +} + +/*-------------------------------------------------------------------------*/ + +/* + * 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. + */ +static ushort fifo_mode; + +/* "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 const 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 const 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 const 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 = 960, }, +{ .hw_ep_num = 4, .style = FIFO_RXTX, .maxpacket = 1024, }, +}; + +/* mode 3 - fits in 4KB */ +static const 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 const 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 const 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_t(u16, maxpacket, 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 unidirectional 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_writeb(mbase, MUSB_TXFIFOSZ, c_size); + musb_writew(mbase, MUSB_TXFIFOADD, c_off); + hw_ep->tx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB); + hw_ep->max_packet_sz_tx = maxpacket; + break; + case FIFO_RX: + musb_writeb(mbase, MUSB_RXFIFOSZ, c_size); + musb_writew(mbase, MUSB_RXFIFOADD, c_off); + hw_ep->rx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB); + hw_ep->max_packet_sz_rx = maxpacket; + break; + case FIFO_RXTX: + musb_writeb(mbase, MUSB_TXFIFOSZ, c_size); + musb_writew(mbase, MUSB_TXFIFOADD, c_off); + hw_ep->rx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB); + hw_ep->max_packet_sz_rx = maxpacket; + + musb_writeb(mbase, MUSB_RXFIFOSZ, c_size); + musb_writew(mbase, MUSB_RXFIFOADD, 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 const 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; + } + + pr_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); + } + + pr_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; + + musb_dbg(musb, "<== static silicon ep config"); + + /* 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]; + void __iomem *mbase = musb->mregs; + int status = 0; + int i; + + /* log core options (read using indexed model) */ + reg = musb_read_configdata(mbase); + + strscpy(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"); + + pr_debug("%s: ConfigData=0x%02x (%s)\n", musb_driver_name, reg, aInfo); + + if (MUSB_CONTROLLER_MHDRC == musb_type) { + musb->is_multipoint = 1; + type = "M"; + } else { + musb->is_multipoint = 0; + type = ""; + if (IS_ENABLED(CONFIG_USB) && + !IS_ENABLED(CONFIG_USB_OTG_DISABLE_EXTERNAL_HUB)) { + pr_err("%s: kernel must disable external hubs, please fix the configuration\n", + musb_driver_name); + } + } + + /* log release info */ + musb->hwvers = musb_readw(mbase, MUSB_HWVERS); + pr_debug("%s: %sHDRC RTL version %d.%d%s\n", + musb_driver_name, type, MUSB_HWVERS_MAJOR(musb->hwvers), + MUSB_HWVERS_MINOR(musb->hwvers), + (musb->hwvers & MUSB_HWVERS_RC) ? "RC" : ""); + + /* 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->io.fifo_offset(i) + mbase; +#if IS_ENABLED(CONFIG_USB_MUSB_TUSB6010) + if (musb->ops->quirks & MUSB_IN_TUSB) { + hw_ep->fifo_async = musb->async + 0x400 + + musb->io.fifo_offset(i); + hw_ep->fifo_sync = musb->sync + 0x400 + + musb->io.fifo_offset(i); + hw_ep->fifo_sync_va = + musb->sync_va + 0x400 + musb->io.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->io.ep_offset(i, 0) + mbase; + hw_ep->rx_reinit = 1; + hw_ep->tx_reinit = 1; + + if (hw_ep->max_packet_sz_tx) { + musb_dbg(musb, "%s: hw_ep %d%s, %smax %d", + 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) { + musb_dbg(musb, "%s: hw_ep %d%s, %smax %d", + 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)) + musb_dbg(musb, "hw_ep %d not configured", 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; + unsigned long status; + unsigned long epnum; + u8 devctl; + + if (!musb->int_usb && !musb->int_tx && !musb->int_rx) + return IRQ_NONE; + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + trace_musb_isr(musb); + + /** + * According to Mentor Graphics' documentation, flowchart on page 98, + * IRQ should be handled as follows: + * + * . Resume IRQ + * . Session Request IRQ + * . VBUS Error IRQ + * . Suspend IRQ + * . Connect IRQ + * . Disconnect IRQ + * . Reset/Babble IRQ + * . SOF IRQ (we're not using this one) + * . Endpoint 0 IRQ + * . TX Endpoints + * . RX Endpoints + * + * We will be following that flowchart in order to avoid any problems + * that might arise with internal Finite State Machine. + */ + + if (musb->int_usb) + retval |= musb_stage0_irq(musb, musb->int_usb, devctl); + + if (musb->int_tx & 1) { + if (is_host_active(musb)) + retval |= musb_h_ep0_irq(musb); + else + retval |= musb_g_ep0_irq(musb); + + /* we have just handled endpoint 0 IRQ, clear it */ + musb->int_tx &= ~BIT(0); + } + + status = musb->int_tx; + + for_each_set_bit(epnum, &status, 16) { + retval = IRQ_HANDLED; + if (is_host_active(musb)) + musb_host_tx(musb, epnum); + else + musb_g_tx(musb, epnum); + } + + status = musb->int_rx; + + for_each_set_bit(epnum, &status, 16) { + retval = IRQ_HANDLED; + if (is_host_active(musb)) + musb_host_rx(musb, epnum); + else + musb_g_rx(musb, epnum); + } + + return retval; +} +EXPORT_SYMBOL_GPL(musb_interrupt); + +#ifndef CONFIG_MUSB_PIO_ONLY +static bool use_dma = true; + +/* "modprobe ... use_dma=0" etc */ +module_param(use_dma, bool, 0644); +MODULE_PARM_DESC(use_dma, "enable/disable use of DMA"); + +void musb_dma_completion(struct musb *musb, u8 epnum, u8 transmit) +{ + /* called with controller lock already held */ + + if (!epnum) { + if (!is_cppi_enabled(musb)) { + /* endpoint 0 */ + if (is_host_active(musb)) + musb_h_ep0_irq(musb); + else + musb_g_ep0_irq(musb); + } + } else { + /* endpoints 1..15 */ + if (transmit) { + if (is_host_active(musb)) + musb_host_tx(musb, epnum); + else + musb_g_tx(musb, epnum); + } else { + /* receive */ + if (is_host_active(musb)) + musb_host_rx(musb, epnum); + else + musb_g_rx(musb, epnum); + } + } +} +EXPORT_SYMBOL_GPL(musb_dma_completion); + +#else +#define use_dma 0 +#endif + +static int (*musb_phy_callback)(enum musb_vbus_id_status status); + +/* + * musb_mailbox - optional phy notifier function + * @status phy state change + * + * Optionally gets called from the USB PHY. Note that the USB PHY must be + * disabled at the point the phy_callback is registered or unregistered. + */ +int musb_mailbox(enum musb_vbus_id_status status) +{ + if (musb_phy_callback) + return musb_phy_callback(status); + + return -ENODEV; +}; +EXPORT_SYMBOL_GPL(musb_mailbox); + +/*-------------------------------------------------------------------------*/ + +static ssize_t +mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct musb *musb = dev_to_musb(dev); + unsigned long flags; + int ret; + + spin_lock_irqsave(&musb->lock, flags); + ret = sprintf(buf, "%s\n", musb_otg_state_string(musb)); + spin_unlock_irqrestore(&musb->lock, flags); + + return ret; +} + +static ssize_t +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_RW(mode); + +static ssize_t +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_get_state(musb) == 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 +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; + u8 devctl; + + pm_runtime_get_sync(dev); + spin_lock_irqsave(&musb->lock, flags); + val = musb->a_wait_bcon; + vbus = musb_platform_get_vbus_status(musb); + if (vbus < 0) { + /* Use default MUSB method by means of DEVCTL register */ + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + if ((devctl & MUSB_DEVCTL_VBUS) + == (3 << MUSB_DEVCTL_VBUS_SHIFT)) + vbus = 1; + else + vbus = 0; + } + spin_unlock_irqrestore(&musb->lock, flags); + pm_runtime_put_sync(dev); + + return sprintf(buf, "Vbus %s, timeout %lu msec\n", + str_on_off(vbus), val); +} +static DEVICE_ATTR_RW(vbus); + +/* 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 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_WO(srp); + +static struct attribute *musb_attrs[] = { + &dev_attr_mode.attr, + &dev_attr_vbus.attr, + &dev_attr_srp.attr, + NULL +}; +ATTRIBUTE_GROUPS(musb); + +#define MUSB_QUIRK_B_INVALID_VBUS_91 (MUSB_DEVCTL_BDEVICE | \ + (2 << MUSB_DEVCTL_VBUS_SHIFT) | \ + MUSB_DEVCTL_SESSION) +#define MUSB_QUIRK_B_DISCONNECT_99 (MUSB_DEVCTL_BDEVICE | \ + (3 << MUSB_DEVCTL_VBUS_SHIFT) | \ + MUSB_DEVCTL_SESSION) +#define MUSB_QUIRK_A_DISCONNECT_19 ((3 << MUSB_DEVCTL_VBUS_SHIFT) | \ + MUSB_DEVCTL_SESSION) + +static bool musb_state_needs_recheck(struct musb *musb, u8 devctl, + const char *desc) +{ + if (musb->quirk_retries && !musb->flush_irq_work) { + trace_musb_state(musb, devctl, desc); + schedule_delayed_work(&musb->irq_work, + msecs_to_jiffies(1000)); + musb->quirk_retries--; + + return true; + } + + return false; +} + +/* + * Check the musb devctl session bit to determine if we want to + * allow PM runtime for the device. In general, we want to keep things + * active when the session bit is set except after host disconnect. + * + * Only called from musb_irq_work. If this ever needs to get called + * elsewhere, proper locking must be implemented for musb->session. + */ +static void musb_pm_runtime_check_session(struct musb *musb) +{ + u8 devctl, s; + int error; + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + /* Handle session status quirks first */ + s = MUSB_DEVCTL_FSDEV | MUSB_DEVCTL_LSDEV | + MUSB_DEVCTL_HR; + switch (devctl & ~s) { + case MUSB_QUIRK_B_DISCONNECT_99: + musb_state_needs_recheck(musb, devctl, + "Poll devctl in case of suspend after disconnect"); + break; + case MUSB_QUIRK_B_INVALID_VBUS_91: + if (musb_state_needs_recheck(musb, devctl, + "Poll devctl on invalid vbus, assume no session")) + return; + fallthrough; + case MUSB_QUIRK_A_DISCONNECT_19: + if (musb_state_needs_recheck(musb, devctl, + "Poll devctl on possible host mode disconnect")) + return; + if (!musb->session) + break; + trace_musb_state(musb, devctl, "Allow PM on possible host mode disconnect"); + pm_runtime_put_autosuspend(musb->controller); + musb->session = false; + return; + default: + break; + } + + /* No need to do anything if session has not changed */ + s = devctl & MUSB_DEVCTL_SESSION; + if (s == musb->session) + return; + + /* Block PM or allow PM? */ + if (s) { + trace_musb_state(musb, devctl, "Block PM on active session"); + error = pm_runtime_get_sync(musb->controller); + if (error < 0) + dev_err(musb->controller, "Could not enable: %i\n", + error); + musb->quirk_retries = 3; + + /* + * We can get a spurious MUSB_INTR_SESSREQ interrupt on start-up + * in B-peripheral mode with nothing connected and the session + * bit clears silently. Check status again in 3 seconds. + */ + if (devctl & MUSB_DEVCTL_BDEVICE) + schedule_delayed_work(&musb->irq_work, + msecs_to_jiffies(3000)); + } else { + trace_musb_state(musb, devctl, "Allow PM with no session"); + pm_runtime_put_autosuspend(musb->controller); + } + + musb->session = s; +} + +/* 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.work); + int error; + + error = pm_runtime_resume_and_get(musb->controller); + if (error < 0) { + dev_err(musb->controller, "Could not enable: %i\n", error); + + return; + } + + musb_pm_runtime_check_session(musb); + + if (musb_get_state(musb) != musb->xceiv_old_state) { + musb->xceiv_old_state = musb_get_state(musb); + sysfs_notify(&musb->controller->kobj, NULL, "mode"); + } + + pm_runtime_put_autosuspend(musb->controller); +} + +static void musb_recover_from_babble(struct musb *musb) +{ + int ret; + u8 devctl; + + musb_disable_interrupts(musb); + + /* + * wait at least 320 cycles of 60MHz clock. That's 5.3us, we will give + * it some slack and wait for 10us. + */ + udelay(10); + + ret = musb_platform_recover(musb); + if (ret) { + musb_enable_interrupts(musb); + return; + } + + /* drop session bit */ + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + devctl &= ~MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + /* tell usbcore about it */ + musb_root_disconnect(musb); + + /* + * When a babble condition occurs, the musb controller + * removes the session bit and the endpoint config is lost. + */ + if (musb->dyn_fifo) + ret = ep_config_from_table(musb); + else + ret = ep_config_from_hw(musb); + + /* restart session */ + if (ret == 0) + musb_start(musb); +} + +/* -------------------------------------------------------------------------- + * Init support + */ + +static struct musb *allocate_instance(struct device *dev, + const struct musb_hdrc_config *config, void __iomem *mbase) +{ + struct musb *musb; + struct musb_hw_ep *ep; + int epnum; + int ret; + + musb = devm_kzalloc(dev, sizeof(*musb), GFP_KERNEL); + if (!musb) + return NULL; + + INIT_LIST_HEAD(&musb->control); + INIT_LIST_HEAD(&musb->in_bulk); + INIT_LIST_HEAD(&musb->out_bulk); + INIT_LIST_HEAD(&musb->pending_list); + + musb->vbuserr_retry = VBUSERR_RETRY_COUNT; + musb->a_wait_bcon = OTG_TIME_A_WAIT_BCON; + 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; + + ret = musb_host_alloc(musb); + if (ret < 0) + goto err_free; + + dev_set_drvdata(dev, musb); + + return musb; + +err_free: + return NULL; +} + +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. + */ + + if (musb->nIrq >= 0) { + if (musb->irq_wake) + disable_irq_wake(musb->nIrq); + free_irq(musb->nIrq, musb); + } + + musb_host_free(musb); +} + +struct musb_pending_work { + int (*callback)(struct musb *musb, void *data); + void *data; + struct list_head node; +}; + +#ifdef CONFIG_PM +/* + * Called from musb_runtime_resume(), musb_resume(), and + * musb_queue_resume_work(). Callers must take musb->lock. + */ +static int musb_run_resume_work(struct musb *musb) +{ + struct musb_pending_work *w, *_w; + unsigned long flags; + int error = 0; + + spin_lock_irqsave(&musb->list_lock, flags); + list_for_each_entry_safe(w, _w, &musb->pending_list, node) { + if (w->callback) { + error = w->callback(musb, w->data); + if (error < 0) { + dev_err(musb->controller, + "resume callback %p failed: %i\n", + w->callback, error); + } + } + list_del(&w->node); + devm_kfree(musb->controller, w); + } + spin_unlock_irqrestore(&musb->list_lock, flags); + + return error; +} +#endif + +/* + * Called to run work if device is active or else queue the work to happen + * on resume. Caller must take musb->lock and must hold an RPM reference. + * + * Note that we cowardly refuse queuing work after musb PM runtime + * resume is done calling musb_run_resume_work() and return -EINPROGRESS + * instead. + */ +int musb_queue_resume_work(struct musb *musb, + int (*callback)(struct musb *musb, void *data), + void *data) +{ + struct musb_pending_work *w; + unsigned long flags; + bool is_suspended; + int error; + + if (WARN_ON(!callback)) + return -EINVAL; + + spin_lock_irqsave(&musb->list_lock, flags); + is_suspended = musb->is_runtime_suspended; + + if (is_suspended) { + w = devm_kzalloc(musb->controller, sizeof(*w), GFP_ATOMIC); + if (!w) { + error = -ENOMEM; + goto out_unlock; + } + + w->callback = callback; + w->data = data; + + list_add_tail(&w->node, &musb->pending_list); + error = 0; + } + +out_unlock: + spin_unlock_irqrestore(&musb->list_lock, flags); + + if (!is_suspended) + error = callback(musb, data); + + return error; +} +EXPORT_SYMBOL_GPL(musb_queue_resume_work); + +static void musb_deassert_reset(struct work_struct *work) +{ + struct musb *musb; + unsigned long flags; + + musb = container_of(work, struct musb, deassert_reset_work.work); + + spin_lock_irqsave(&musb->lock, flags); + + if (musb->port1_status & USB_PORT_STAT_RESET) + musb_port_reset(musb, false); + + spin_unlock_irqrestore(&musb->lock, flags); +} + +/* + * 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_get_platdata(dev); + + /* The driver might handle more features than the board; OK. + * Fail when the board needs a feature that's not enabled. + */ + if (!plat) { + dev_err(dev, "no platform_data?\n"); + status = -ENODEV; + goto fail0; + } + + /* allocate */ + musb = allocate_instance(dev, plat->config, ctrl); + if (!musb) { + status = -ENOMEM; + goto fail0; + } + + spin_lock_init(&musb->lock); + spin_lock_init(&musb->list_lock); + musb->min_power = plat->min_power; + musb->ops = plat->platform_ops; + musb->port_mode = plat->mode; + + /* + * Initialize the default IO functions. At least omap2430 needs + * these early. We initialize the platform specific IO functions + * later on. + */ + musb_readb = musb_default_readb; + musb_writeb = musb_default_writeb; + musb_readw = musb_default_readw; + musb_writew = musb_default_writew; + + /* The musb_platform_init() call: + * - adjusts musb->mregs + * - sets the musb->isr + * - may initialize an integrated transceiver + * - initializes musb->xceiv, usually by otg_get_phy() + * - stops powering VBUS + * + * There are various transceiver configurations. + * 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; + } + + + /* Most devices use indexed offset or flat offset */ + if (musb->ops->quirks & MUSB_INDEXED_EP) { + musb->io.ep_offset = musb_indexed_ep_offset; + musb->io.ep_select = musb_indexed_ep_select; + } else { + musb->io.ep_offset = musb_flat_ep_offset; + musb->io.ep_select = musb_flat_ep_select; + } + + if (musb->ops->quirks & MUSB_G_NO_SKB_RESERVE) + musb->g.quirk_avoids_skb_reserve = 1; + + /* At least tusb6010 has its own offsets */ + if (musb->ops->ep_offset) + musb->io.ep_offset = musb->ops->ep_offset; + if (musb->ops->ep_select) + musb->io.ep_select = musb->ops->ep_select; + + if (musb->ops->fifo_mode) + fifo_mode = musb->ops->fifo_mode; + else + fifo_mode = 4; + + if (musb->ops->fifo_offset) + musb->io.fifo_offset = musb->ops->fifo_offset; + else + musb->io.fifo_offset = musb_default_fifo_offset; + + if (musb->ops->busctl_offset) + musb->io.busctl_offset = musb->ops->busctl_offset; + else + musb->io.busctl_offset = musb_default_busctl_offset; + + if (musb->ops->readb) + musb_readb = musb->ops->readb; + if (musb->ops->writeb) + musb_writeb = musb->ops->writeb; + if (musb->ops->clearb) + musb_clearb = musb->ops->clearb; + else + musb_clearb = musb_readb; + + if (musb->ops->readw) + musb_readw = musb->ops->readw; + if (musb->ops->writew) + musb_writew = musb->ops->writew; + if (musb->ops->clearw) + musb_clearw = musb->ops->clearw; + else + musb_clearw = musb_readw; + +#ifndef CONFIG_MUSB_PIO_ONLY + if (!musb->ops->dma_init || !musb->ops->dma_exit) { + dev_err(dev, "DMA controller not set\n"); + status = -ENODEV; + goto fail2; + } + musb_dma_controller_create = musb->ops->dma_init; + musb_dma_controller_destroy = musb->ops->dma_exit; +#endif + + if (musb->ops->read_fifo) + musb->io.read_fifo = musb->ops->read_fifo; + else + musb->io.read_fifo = musb_default_read_fifo; + + if (musb->ops->write_fifo) + musb->io.write_fifo = musb->ops->write_fifo; + else + musb->io.write_fifo = musb_default_write_fifo; + + if (musb->ops->get_toggle) + musb->io.get_toggle = musb->ops->get_toggle; + else + musb->io.get_toggle = musb_default_get_toggle; + + if (musb->ops->set_toggle) + musb->io.set_toggle = musb->ops->set_toggle; + else + musb->io.set_toggle = musb_default_set_toggle; + + if (IS_ENABLED(CONFIG_USB_PHY) && musb->xceiv && !musb->xceiv->io_ops) { + musb->xceiv->io_dev = musb->controller; + musb->xceiv->io_priv = musb->mregs; + musb->xceiv->io_ops = &musb_ulpi_access; + } + + if (musb->ops->phy_callback) + musb_phy_callback = musb->ops->phy_callback; + + /* + * We need musb_read/write functions initialized for PM. + * Note that at least 2430 glue needs autosuspend delay + * somewhere above 300 ms for the hardware to idle properly + * after disconnecting the cable in host mode. Let's use + * 500 ms for some margin. + */ + pm_runtime_use_autosuspend(musb->controller); + pm_runtime_set_autosuspend_delay(musb->controller, 500); + pm_runtime_enable(musb->controller); + pm_runtime_get_sync(musb->controller); + + status = usb_phy_init(musb->xceiv); + if (status < 0) + goto err_usb_phy_init; + + if (use_dma && dev->dma_mask) { + musb->dma_controller = + musb_dma_controller_create(musb, musb->mregs); + if (IS_ERR(musb->dma_controller)) { + status = PTR_ERR(musb->dma_controller); + goto fail2_5; + } + } + + /* be sure interrupts are disabled before connecting ISR */ + musb_platform_disable(musb); + musb_disable_interrupts(musb); + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + + /* MUSB_POWER_SOFTCONN might be already set, JZ4740 does this. */ + musb_writeb(musb->mregs, MUSB_POWER, 0); + + /* Init IRQ workqueue before request_irq */ + INIT_DELAYED_WORK(&musb->irq_work, musb_irq_work); + INIT_DELAYED_WORK(&musb->deassert_reset_work, musb_deassert_reset); + INIT_DELAYED_WORK(&musb->finish_resume_work, musb_host_finish_resume); + + /* 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; + + timer_setup(&musb->otg_timer, musb_otg_timer_func, 0); + + /* attach to the IRQ */ + if (request_irq(nIrq, musb->isr, IRQF_SHARED, 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; + } + + /* program PHY to use external vBus if required */ + if (plat->extvbus) { + u8 busctl = musb_readb(musb->mregs, MUSB_ULPI_BUSCONTROL); + busctl |= MUSB_ULPI_USE_EXTVBUS; + musb_writeb(musb->mregs, MUSB_ULPI_BUSCONTROL, busctl); + } + + MUSB_DEV_MODE(musb); + musb_set_state(musb, OTG_STATE_B_IDLE); + + switch (musb->port_mode) { + case MUSB_HOST: + status = musb_host_setup(musb, plat->power); + if (status < 0) + goto fail3; + status = musb_platform_set_mode(musb, MUSB_HOST); + break; + case MUSB_PERIPHERAL: + status = musb_gadget_setup(musb); + if (status < 0) + goto fail3; + status = musb_platform_set_mode(musb, MUSB_PERIPHERAL); + break; + case MUSB_OTG: + status = musb_host_setup(musb, plat->power); + if (status < 0) + goto fail3; + status = musb_gadget_setup(musb); + if (status) { + musb_host_cleanup(musb); + goto fail3; + } + status = musb_platform_set_mode(musb, MUSB_OTG); + break; + default: + dev_err(dev, "unsupported port mode %d\n", musb->port_mode); + break; + } + + if (status < 0) + goto fail3; + + musb_init_debugfs(musb); + + musb->is_initialized = 1; + pm_runtime_put_autosuspend(musb->controller); + + return 0; + +fail3: + cancel_delayed_work_sync(&musb->irq_work); + cancel_delayed_work_sync(&musb->finish_resume_work); + cancel_delayed_work_sync(&musb->deassert_reset_work); + if (musb->dma_controller) + musb_dma_controller_destroy(musb->dma_controller); + +fail2_5: + usb_phy_shutdown(musb->xceiv); + +err_usb_phy_init: + pm_runtime_dont_use_autosuspend(musb->controller); + pm_runtime_put_sync(musb->controller); + pm_runtime_disable(musb->controller); + +fail2: + if (musb->irq_wake) + device_init_wakeup(dev, 0); + musb_platform_exit(musb); + +fail1: + dev_err_probe(musb->controller, status, "%s failed\n", __func__); + + 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"); + void __iomem *base; + + if (irq < 0) + return irq; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + return musb_init_controller(dev, irq, base); +} + +static void musb_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct musb *musb = dev_to_musb(dev); + unsigned long flags; + + /* 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); + + cancel_delayed_work_sync(&musb->irq_work); + cancel_delayed_work_sync(&musb->finish_resume_work); + cancel_delayed_work_sync(&musb->deassert_reset_work); + pm_runtime_get_sync(musb->controller); + musb_host_cleanup(musb); + musb_gadget_cleanup(musb); + + musb_platform_disable(musb); + spin_lock_irqsave(&musb->lock, flags); + musb_disable_interrupts(musb); + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + spin_unlock_irqrestore(&musb->lock, flags); + musb_platform_exit(musb); + + pm_runtime_dont_use_autosuspend(musb->controller); + pm_runtime_put_sync(musb->controller); + pm_runtime_disable(musb->controller); + musb_phy_callback = NULL; + if (musb->dma_controller) + musb_dma_controller_destroy(musb->dma_controller); + usb_phy_shutdown(musb->xceiv); + musb_free(musb); + device_init_wakeup(dev, 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_readb(musb_base, MUSB_ULPI_BUSCONTROL); + 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) { + epio = musb->endpoints[i].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_readw(musb_base, MUSB_TXFIFOADD); + musb->context.index_regs[i].rxfifoadd = + musb_readw(musb_base, MUSB_RXFIFOADD); + musb->context.index_regs[i].txfifosz = + musb_readb(musb_base, MUSB_TXFIFOSZ); + musb->context.index_regs[i].rxfifosz = + musb_readb(musb_base, MUSB_RXFIFOSZ); + } + + 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, i); + musb->context.index_regs[i].txhubaddr = + musb_read_txhubaddr(musb, i); + musb->context.index_regs[i].txhubport = + musb_read_txhubport(musb, i); + + musb->context.index_regs[i].rxfunaddr = + musb_read_rxfunaddr(musb, i); + musb->context.index_regs[i].rxhubaddr = + musb_read_rxhubaddr(musb, i); + musb->context.index_regs[i].rxhubport = + musb_read_rxhubport(musb, i); + } +} + +static void musb_restore_context(struct musb *musb) +{ + int i; + void __iomem *musb_base = musb->mregs; + void __iomem *epio; + u8 power; + + musb_writew(musb_base, MUSB_FRAME, musb->context.frame); + musb_writeb(musb_base, MUSB_TESTMODE, musb->context.testmode); + musb_writeb(musb_base, MUSB_ULPI_BUSCONTROL, musb->context.busctl); + + /* Don't affect SUSPENDM/RESUME bits in POWER reg */ + power = musb_readb(musb_base, MUSB_POWER); + power &= MUSB_POWER_SUSPENDM | MUSB_POWER_RESUME; + musb->context.power &= ~(MUSB_POWER_SUSPENDM | MUSB_POWER_RESUME); + power |= musb->context.power; + musb_writeb(musb_base, MUSB_POWER, 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); + if (musb->context.devctl & MUSB_DEVCTL_SESSION) + musb_writeb(musb_base, MUSB_DEVCTL, musb->context.devctl); + + for (i = 0; i < musb->config->num_eps; ++i) { + epio = musb->endpoints[i].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_writeb(musb_base, MUSB_TXFIFOSZ, + musb->context.index_regs[i].txfifosz); + musb_writeb(musb_base, MUSB_RXFIFOSZ, + musb->context.index_regs[i].rxfifosz); + musb_writew(musb_base, MUSB_TXFIFOADD, + musb->context.index_regs[i].txfifoadd); + musb_writew(musb_base, MUSB_RXFIFOADD, + 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, i, + musb->context.index_regs[i].txfunaddr); + musb_write_txhubaddr(musb, i, + musb->context.index_regs[i].txhubaddr); + musb_write_txhubport(musb, i, + musb->context.index_regs[i].txhubport); + + musb_write_rxfunaddr(musb, i, + musb->context.index_regs[i].rxfunaddr); + musb_write_rxhubaddr(musb, i, + musb->context.index_regs[i].rxhubaddr); + musb_write_rxhubport(musb, i, + 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; + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put_noidle(dev); + return ret; + } + + musb_platform_disable(musb); + musb_disable_interrupts(musb); + + musb->flush_irq_work = true; + while (flush_delayed_work(&musb->irq_work)) + ; + musb->flush_irq_work = false; + + if (!(musb->ops->quirks & MUSB_PRESERVE_SESSION)) + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + + WARN_ON(!list_empty(&musb->pending_list)); + + 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. + */ + } + + musb_save_context(musb); + + spin_unlock_irqrestore(&musb->lock, flags); + return 0; +} + +static int musb_resume(struct device *dev) +{ + struct musb *musb = dev_to_musb(dev); + unsigned long flags; + int error; + u8 devctl; + u8 mask; + + /* + * 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). + * + * For the DSPS glue layer though, a full register restore has to + * be done. As it shouldn't harm other platforms, we do it + * unconditionally. + */ + + musb_restore_context(musb); + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + mask = MUSB_DEVCTL_BDEVICE | MUSB_DEVCTL_FSDEV | MUSB_DEVCTL_LSDEV; + if ((devctl & mask) != (musb->context.devctl & mask)) + musb->port1_status = 0; + + musb_enable_interrupts(musb); + musb_platform_enable(musb); + + /* session might be disabled in suspend */ + if (musb->port_mode == MUSB_HOST && + !(musb->ops->quirks & MUSB_PRESERVE_SESSION)) { + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + } + + spin_lock_irqsave(&musb->lock, flags); + error = musb_run_resume_work(musb); + if (error) + dev_err(musb->controller, "resume work failed with %i\n", + error); + spin_unlock_irqrestore(&musb->lock, flags); + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static int musb_runtime_suspend(struct device *dev) +{ + struct musb *musb = dev_to_musb(dev); + + musb_save_context(musb); + musb->is_runtime_suspended = 1; + + return 0; +} + +static int musb_runtime_resume(struct device *dev) +{ + struct musb *musb = dev_to_musb(dev); + unsigned long flags; + int error; + + /* + * 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 (!musb->is_initialized) + return 0; + + musb_restore_context(musb); + + spin_lock_irqsave(&musb->lock, flags); + error = musb_run_resume_work(musb); + if (error) + dev_err(musb->controller, "resume work failed with %i\n", + error); + musb->is_runtime_suspended = 0; + spin_unlock_irqrestore(&musb->lock, flags); + + return 0; +} + +static const struct dev_pm_ops musb_dev_pm_ops = { + .suspend = musb_suspend, + .resume = musb_resume, + .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 = musb_driver_name, + .bus = &platform_bus_type, + .pm = MUSB_DEV_PM_OPS, + .dev_groups = musb_groups, + }, + .probe = musb_probe, + .remove = musb_remove, +}; + +module_platform_driver(musb_driver); diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h new file mode 100644 index 000000000..91b5b6b66 --- /dev/null +++ b/drivers/usb/musb/musb_core.h @@ -0,0 +1,623 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MUSB OTG driver defines + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + */ + +#ifndef __MUSB_CORE_H__ +#define __MUSB_CORE_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct musb; +struct musb_hw_ep; +struct musb_ep; +struct musb_qh; + +/* 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_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) + +/****************************** 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 */ + +/****************************** 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_io; + +/** + * struct musb_platform_ops - Operations passed to musb_core by HW glue layer + * @quirks: flags for platform specific quirks + * @enable: enable device + * @disable: disable device + * @ep_offset: returns the end point offset + * @ep_select: selects the specified end point + * @fifo_mode: sets the fifo mode + * @fifo_offset: returns the fifo offset + * @readb: read 8 bits + * @writeb: write 8 bits + * @clearb: could be clear-on-readb or W1C + * @readw: read 16 bits + * @writew: write 16 bits + * @clearw: could be clear-on-readw or W1C + * @read_fifo: reads the fifo + * @write_fifo: writes to fifo + * @get_toggle: platform specific get toggle function + * @set_toggle: platform specific set toggle function + * @dma_init: platform specific dma init function + * @dma_exit: platform specific dma exit function + * @init: turns on clocks, sets up platform-specific registers, etc + * @exit: undoes @init + * @set_mode: forcefully changes operating mode + * @try_idle: tries to idle the IP + * @recover: platform-specific babble recovery + * @vbus_status: returns vbus status if possible + * @set_vbus: forces vbus status + * @pre_root_reset_end: called before the root usb port reset flag gets cleared + * @post_root_reset_end: called after the root usb port reset flag gets cleared + * @phy_callback: optional callback function for the phy to call + */ +struct musb_platform_ops { + +#define MUSB_G_NO_SKB_RESERVE BIT(9) +#define MUSB_DA8XX BIT(8) +#define MUSB_PRESERVE_SESSION BIT(7) +#define MUSB_DMA_UX500 BIT(6) +#define MUSB_DMA_CPPI41 BIT(5) +#define MUSB_DMA_CPPI BIT(4) +#define MUSB_DMA_TUSB_OMAP BIT(3) +#define MUSB_DMA_INVENTRA BIT(2) +#define MUSB_IN_TUSB BIT(1) +#define MUSB_INDEXED_EP BIT(0) + u32 quirks; + + int (*init)(struct musb *musb); + int (*exit)(struct musb *musb); + + void (*enable)(struct musb *musb); + void (*disable)(struct musb *musb); + + u32 (*ep_offset)(u8 epnum, u16 offset); + void (*ep_select)(void __iomem *mbase, u8 epnum); + u16 fifo_mode; + u32 (*fifo_offset)(u8 epnum); + u32 (*busctl_offset)(u8 epnum, u16 offset); + u8 (*readb)(void __iomem *addr, u32 offset); + void (*writeb)(void __iomem *addr, u32 offset, u8 data); + u8 (*clearb)(void __iomem *addr, u32 offset); + u16 (*readw)(void __iomem *addr, u32 offset); + void (*writew)(void __iomem *addr, u32 offset, u16 data); + u16 (*clearw)(void __iomem *addr, u32 offset); + void (*read_fifo)(struct musb_hw_ep *hw_ep, u16 len, u8 *buf); + void (*write_fifo)(struct musb_hw_ep *hw_ep, u16 len, const u8 *buf); + u16 (*get_toggle)(struct musb_qh *qh, int is_out); + u16 (*set_toggle)(struct musb_qh *qh, int is_out, struct urb *urb); + struct dma_controller * + (*dma_init) (struct musb *musb, void __iomem *base); + void (*dma_exit)(struct dma_controller *c); + int (*set_mode)(struct musb *musb, u8 mode); + void (*try_idle)(struct musb *musb, unsigned long timeout); + int (*recover)(struct musb *musb); + + int (*vbus_status)(struct musb *musb); + void (*set_vbus)(struct musb *musb, int on); + + void (*pre_root_reset_end)(struct musb *musb); + void (*post_root_reset_end)(struct musb *musb); + int (*phy_callback)(enum musb_vbus_id_status status); + void (*clear_ep_rxintr)(struct musb *musb, int epnum); +}; + +/* + * 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 IS_ENABLED(CONFIG_USB_MUSB_TUSB6010) + 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 IS_ENABLED(CONFIG_USB_MUSB_TUSB6010) + /* TUSB has "asynchronous" and "synchronous" dma modes */ + dma_addr_t fifo_async; + dma_addr_t fifo_sync; + void __iomem *fifo_sync_va; +#endif + + /* 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; + spinlock_t list_lock; /* resume work list lock */ + + struct musb_io io; + const struct musb_platform_ops *ops; + struct musb_context_registers context; + + irqreturn_t (*isr)(int, void *); + struct delayed_work irq_work; + struct delayed_work deassert_reset_work; + struct delayed_work finish_resume_work; + struct delayed_work gadget_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 list_head pending_list; /* pending work list */ + + struct timer_list otg_timer; + struct timer_list dev_timer; + struct notifier_block nb; + + struct dma_controller *dma_controller; + + struct device *controller; + void __iomem *ctrl_base; + void __iomem *mregs; + +#if IS_ENABLED(CONFIG_USB_MUSB_TUSB6010) + dma_addr_t async; + dma_addr_t sync; + void __iomem *sync_va; + u8 tusb_revision; +#endif + + /* passed down from chip/board specific irq handlers */ + u8 int_usb; + u16 int_rx; + u16 int_tx; + + struct usb_phy *xceiv; + struct phy *phy; + + enum usb_otg_state otg_state; + + 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; + + u8 min_power; /* vbus for periph, in mA/2 */ + + enum musb_mode port_mode; + bool session; + unsigned long quirk_retries; + bool is_host; + + int a_wait_bcon; /* VBUS timeout in msecs */ + unsigned long idle_timeout; /* Next timeout in jiffies */ + + unsigned is_initialized:1; + unsigned is_runtime_suspended:1; + + /* active means connected and not suspended */ + unsigned is_active:1; + + unsigned is_multipoint:1; + + 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; + + unsigned flush_irq_work: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 */ + struct usb_hcd *hcd; /* the usb hcd */ + + const struct musb_hdrc_config *config; + + int xceiv_old_state; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_root; +#endif +}; + +/* This must be included after struct musb is defined */ +#include "musb_regs.h" + +static inline struct musb *gadget_to_musb(struct usb_gadget *g) +{ + return container_of(g, struct musb, g); +} + +static inline char *musb_ep_xfertype_string(u8 type) +{ + char *s; + + switch (type) { + case USB_ENDPOINT_XFER_CONTROL: + s = "ctrl"; + break; + case USB_ENDPOINT_XFER_ISOC: + s = "iso"; + break; + case USB_ENDPOINT_XFER_BULK: + s = "bulk"; + break; + case USB_ENDPOINT_XFER_INT: + s = "int"; + break; + default: + s = ""; + break; + } + return s; +} + +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->io.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; +} + +/***************************** Glue it together *****************************/ + +extern const char musb_driver_name[]; + +extern void musb_stop(struct musb *musb); +extern void musb_start(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 int musb_set_host(struct musb *musb); +extern int musb_set_peripheral(struct musb *musb); + +extern void musb_load_testpacket(struct musb *); + +extern irqreturn_t musb_interrupt(struct musb *); + +extern void musb_hnp_stop(struct musb *musb); + +int musb_queue_resume_work(struct musb *musb, + int (*callback)(struct musb *musb, void *data), + void *data); + +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_recover(struct musb *musb) +{ + if (!musb->ops->recover) + return 0; + + return musb->ops->recover(musb); +} + +static inline int musb_platform_get_vbus_status(struct musb *musb) +{ + if (!musb->ops->vbus_status) + return -EINVAL; + + 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); +} + +static inline void musb_platform_pre_root_reset_end(struct musb *musb) +{ + if (musb->ops->pre_root_reset_end) + musb->ops->pre_root_reset_end(musb); +} + +static inline void musb_platform_post_root_reset_end(struct musb *musb) +{ + if (musb->ops->post_root_reset_end) + musb->ops->post_root_reset_end(musb); +} + +static inline void musb_platform_clear_ep_rxintr(struct musb *musb, int epnum) +{ + if (musb->ops->clear_ep_rxintr) + musb->ops->clear_ep_rxintr(musb, epnum); +} + +static inline void musb_set_state(struct musb *musb, + enum usb_otg_state otg_state) +{ + if (musb->xceiv) + musb->xceiv->otg->state = otg_state; + else + musb->otg_state = otg_state; +} + +static inline enum usb_otg_state musb_get_state(struct musb *musb) +{ + if (musb->xceiv) + return musb->xceiv->otg->state; + + return musb->otg_state; +} + +static inline const char *musb_otg_state_string(struct musb *musb) +{ + return usb_otg_state_string(musb_get_state(musb)); +} + +/* + * gets the "dr_mode" property from DT and converts it into musb_mode + * if the property is not found or not recognized returns MUSB_OTG + */ +extern enum musb_mode musb_get_mode(struct device *dev); + +#endif /* __MUSB_CORE_H__ */ diff --git a/drivers/usb/musb/musb_cppi41.c b/drivers/usb/musb/musb_cppi41.c new file mode 100644 index 000000000..28e958fcc --- /dev/null +++ b/drivers/usb/musb/musb_cppi41.c @@ -0,0 +1,811 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include + +#include "cppi_dma.h" +#include "musb_core.h" +#include "musb_trace.h" + +#define RNDIS_REG(x) (0x80 + ((x - 1) * 4)) + +#define EP_MODE_AUTOREQ_NONE 0 +#define EP_MODE_AUTOREQ_ALL_NEOP 1 +#define EP_MODE_AUTOREQ_ALWAYS 3 + +#define EP_MODE_DMA_TRANSPARENT 0 +#define EP_MODE_DMA_RNDIS 1 +#define EP_MODE_DMA_GEN_RNDIS 3 + +#define USB_CTRL_TX_MODE 0x70 +#define USB_CTRL_RX_MODE 0x74 +#define USB_CTRL_AUTOREQ 0xd0 +#define USB_TDOWN 0xd8 + +#define MUSB_DMA_NUM_CHANNELS 15 + +#define DA8XX_USB_MODE 0x10 +#define DA8XX_USB_AUTOREQ 0x14 +#define DA8XX_USB_TEARDOWN 0x1c + +#define DA8XX_DMA_NUM_CHANNELS 4 + +struct cppi41_dma_controller { + struct dma_controller controller; + struct cppi41_dma_channel *rx_channel; + struct cppi41_dma_channel *tx_channel; + struct hrtimer early_tx; + struct list_head early_tx_list; + u32 rx_mode; + u32 tx_mode; + u32 auto_req; + + u32 tdown_reg; + u32 autoreq_reg; + + void (*set_dma_mode)(struct cppi41_dma_channel *cppi41_channel, + unsigned int mode); + u8 num_channels; +}; + +static void save_rx_toggle(struct cppi41_dma_channel *cppi41_channel) +{ + u16 csr; + u8 toggle; + + if (cppi41_channel->is_tx) + return; + if (!is_host_active(cppi41_channel->controller->controller.musb)) + return; + + csr = musb_readw(cppi41_channel->hw_ep->regs, MUSB_RXCSR); + toggle = csr & MUSB_RXCSR_H_DATATOGGLE ? 1 : 0; + + cppi41_channel->usb_toggle = toggle; +} + +static void update_rx_toggle(struct cppi41_dma_channel *cppi41_channel) +{ + struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep; + struct musb *musb = hw_ep->musb; + u16 csr; + u8 toggle; + + if (cppi41_channel->is_tx) + return; + if (!is_host_active(musb)) + return; + + musb_ep_select(musb->mregs, hw_ep->epnum); + csr = musb_readw(hw_ep->regs, MUSB_RXCSR); + toggle = csr & MUSB_RXCSR_H_DATATOGGLE ? 1 : 0; + + /* + * AM335x Advisory 1.0.13: Due to internal synchronisation error the + * data toggle may reset from DATA1 to DATA0 during receiving data from + * more than one endpoint. + */ + if (!toggle && toggle == cppi41_channel->usb_toggle) { + csr |= MUSB_RXCSR_H_DATATOGGLE | MUSB_RXCSR_H_WR_DATATOGGLE; + musb_writew(cppi41_channel->hw_ep->regs, MUSB_RXCSR, csr); + musb_dbg(musb, "Restoring DATA1 toggle."); + } + + cppi41_channel->usb_toggle = toggle; +} + +static bool musb_is_tx_fifo_empty(struct musb_hw_ep *hw_ep) +{ + u8 epnum = hw_ep->epnum; + struct musb *musb = hw_ep->musb; + void __iomem *epio = musb->endpoints[epnum].regs; + u16 csr; + + musb_ep_select(musb->mregs, hw_ep->epnum); + csr = musb_readw(epio, MUSB_TXCSR); + if (csr & MUSB_TXCSR_TXPKTRDY) + return false; + return true; +} + +static void cppi41_dma_callback(void *private_data, + const struct dmaengine_result *result); + +static void cppi41_trans_done(struct cppi41_dma_channel *cppi41_channel) +{ + struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep; + struct musb *musb = hw_ep->musb; + void __iomem *epio = hw_ep->regs; + u16 csr; + + if (!cppi41_channel->prog_len || + (cppi41_channel->channel.status == MUSB_DMA_STATUS_FREE)) { + + /* done, complete */ + cppi41_channel->channel.actual_len = + cppi41_channel->transferred; + cppi41_channel->channel.status = MUSB_DMA_STATUS_FREE; + cppi41_channel->channel.rx_packet_done = true; + + /* + * transmit ZLP using PIO mode for transfers which size is + * multiple of EP packet size. + */ + if (cppi41_channel->tx_zlp && (cppi41_channel->transferred % + cppi41_channel->packet_sz) == 0) { + musb_ep_select(musb->mregs, hw_ep->epnum); + csr = MUSB_TXCSR_MODE | MUSB_TXCSR_TXPKTRDY; + musb_writew(epio, MUSB_TXCSR, csr); + } + + trace_musb_cppi41_done(cppi41_channel); + musb_dma_completion(musb, hw_ep->epnum, cppi41_channel->is_tx); + } else { + /* next iteration, reload */ + struct dma_chan *dc = cppi41_channel->dc; + struct dma_async_tx_descriptor *dma_desc; + enum dma_transfer_direction direction; + u32 remain_bytes; + + cppi41_channel->buf_addr += cppi41_channel->packet_sz; + + remain_bytes = cppi41_channel->total_len; + remain_bytes -= cppi41_channel->transferred; + remain_bytes = min(remain_bytes, cppi41_channel->packet_sz); + cppi41_channel->prog_len = remain_bytes; + + direction = cppi41_channel->is_tx ? DMA_MEM_TO_DEV + : DMA_DEV_TO_MEM; + dma_desc = dmaengine_prep_slave_single(dc, + cppi41_channel->buf_addr, + remain_bytes, + direction, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (WARN_ON(!dma_desc)) + return; + + dma_desc->callback_result = cppi41_dma_callback; + dma_desc->callback_param = &cppi41_channel->channel; + cppi41_channel->cookie = dma_desc->tx_submit(dma_desc); + trace_musb_cppi41_cont(cppi41_channel); + dma_async_issue_pending(dc); + + if (!cppi41_channel->is_tx) { + musb_ep_select(musb->mregs, hw_ep->epnum); + csr = musb_readw(epio, MUSB_RXCSR); + csr |= MUSB_RXCSR_H_REQPKT; + musb_writew(epio, MUSB_RXCSR, csr); + } + } +} + +static enum hrtimer_restart cppi41_recheck_tx_req(struct hrtimer *timer) +{ + struct cppi41_dma_controller *controller; + struct cppi41_dma_channel *cppi41_channel, *n; + struct musb *musb; + unsigned long flags; + enum hrtimer_restart ret = HRTIMER_NORESTART; + + controller = container_of(timer, struct cppi41_dma_controller, + early_tx); + musb = controller->controller.musb; + + spin_lock_irqsave(&musb->lock, flags); + list_for_each_entry_safe(cppi41_channel, n, &controller->early_tx_list, + tx_check) { + bool empty; + struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep; + + empty = musb_is_tx_fifo_empty(hw_ep); + if (empty) { + list_del_init(&cppi41_channel->tx_check); + cppi41_trans_done(cppi41_channel); + } + } + + if (!list_empty(&controller->early_tx_list) && + !hrtimer_is_queued(&controller->early_tx)) { + ret = HRTIMER_RESTART; + hrtimer_forward_now(&controller->early_tx, 20 * NSEC_PER_USEC); + } + + spin_unlock_irqrestore(&musb->lock, flags); + return ret; +} + +static void cppi41_dma_callback(void *private_data, + const struct dmaengine_result *result) +{ + struct dma_channel *channel = private_data; + struct cppi41_dma_channel *cppi41_channel = channel->private_data; + struct musb_hw_ep *hw_ep = cppi41_channel->hw_ep; + struct cppi41_dma_controller *controller; + struct musb *musb = hw_ep->musb; + unsigned long flags; + struct dma_tx_state txstate; + u32 transferred; + int is_hs = 0; + bool empty; + + controller = cppi41_channel->controller; + if (controller->controller.dma_callback) + controller->controller.dma_callback(&controller->controller); + + if (result->result == DMA_TRANS_ABORTED) + return; + + spin_lock_irqsave(&musb->lock, flags); + + dmaengine_tx_status(cppi41_channel->dc, cppi41_channel->cookie, + &txstate); + transferred = cppi41_channel->prog_len - txstate.residue; + cppi41_channel->transferred += transferred; + + trace_musb_cppi41_gb(cppi41_channel); + update_rx_toggle(cppi41_channel); + + if (cppi41_channel->transferred == cppi41_channel->total_len || + transferred < cppi41_channel->packet_sz) + cppi41_channel->prog_len = 0; + + if (cppi41_channel->is_tx) { + u8 type; + + if (is_host_active(musb)) + type = hw_ep->out_qh->type; + else + type = hw_ep->ep_in.type; + + if (type == USB_ENDPOINT_XFER_ISOC) + /* + * Don't use the early-TX-interrupt workaround below + * for Isoch transfter. Since Isoch are periodic + * transfer, by the time the next transfer is + * scheduled, the current one should be done already. + * + * This avoids audio playback underrun issue. + */ + empty = true; + else + empty = musb_is_tx_fifo_empty(hw_ep); + } + + if (!cppi41_channel->is_tx || empty) { + cppi41_trans_done(cppi41_channel); + goto out; + } + + /* + * On AM335x it has been observed that the TX interrupt fires + * too early that means the TXFIFO is not yet empty but the DMA + * engine says that it is done with the transfer. We don't + * receive a FIFO empty interrupt so the only thing we can do is + * to poll for the bit. On HS it usually takes 2us, on FS around + * 110us - 150us depending on the transfer size. + * We spin on HS (no longer than 25us and setup a timer on + * FS to check for the bit and complete the transfer. + */ + if (is_host_active(musb)) { + if (musb->port1_status & USB_PORT_STAT_HIGH_SPEED) + is_hs = 1; + } else { + if (musb->g.speed == USB_SPEED_HIGH) + is_hs = 1; + } + if (is_hs) { + unsigned wait = 25; + + do { + empty = musb_is_tx_fifo_empty(hw_ep); + if (empty) { + cppi41_trans_done(cppi41_channel); + goto out; + } + wait--; + if (!wait) + break; + cpu_relax(); + } while (1); + } + list_add_tail(&cppi41_channel->tx_check, + &controller->early_tx_list); + if (!hrtimer_is_queued(&controller->early_tx)) { + unsigned long usecs = cppi41_channel->total_len / 10; + + hrtimer_start_range_ns(&controller->early_tx, + usecs * NSEC_PER_USEC, + 20 * NSEC_PER_USEC, + HRTIMER_MODE_REL); + } + +out: + spin_unlock_irqrestore(&musb->lock, flags); +} + +static u32 update_ep_mode(unsigned ep, unsigned mode, u32 old) +{ + unsigned shift; + + shift = (ep - 1) * 2; + old &= ~(3 << shift); + old |= mode << shift; + return old; +} + +static void cppi41_set_dma_mode(struct cppi41_dma_channel *cppi41_channel, + unsigned mode) +{ + struct cppi41_dma_controller *controller = cppi41_channel->controller; + struct musb *musb = controller->controller.musb; + u32 port; + u32 new_mode; + u32 old_mode; + + if (cppi41_channel->is_tx) + old_mode = controller->tx_mode; + else + old_mode = controller->rx_mode; + port = cppi41_channel->port_num; + new_mode = update_ep_mode(port, mode, old_mode); + + if (new_mode == old_mode) + return; + if (cppi41_channel->is_tx) { + controller->tx_mode = new_mode; + musb_writel(musb->ctrl_base, USB_CTRL_TX_MODE, new_mode); + } else { + controller->rx_mode = new_mode; + musb_writel(musb->ctrl_base, USB_CTRL_RX_MODE, new_mode); + } +} + +static void da8xx_set_dma_mode(struct cppi41_dma_channel *cppi41_channel, + unsigned int mode) +{ + struct cppi41_dma_controller *controller = cppi41_channel->controller; + struct musb *musb = controller->controller.musb; + unsigned int shift; + u32 port; + u32 new_mode; + u32 old_mode; + + old_mode = controller->tx_mode; + port = cppi41_channel->port_num; + + shift = (port - 1) * 4; + if (!cppi41_channel->is_tx) + shift += 16; + new_mode = old_mode & ~(3 << shift); + new_mode |= mode << shift; + + if (new_mode == old_mode) + return; + controller->tx_mode = new_mode; + musb_writel(musb->ctrl_base, DA8XX_USB_MODE, new_mode); +} + + +static void cppi41_set_autoreq_mode(struct cppi41_dma_channel *cppi41_channel, + unsigned mode) +{ + struct cppi41_dma_controller *controller = cppi41_channel->controller; + u32 port; + u32 new_mode; + u32 old_mode; + + old_mode = controller->auto_req; + port = cppi41_channel->port_num; + new_mode = update_ep_mode(port, mode, old_mode); + + if (new_mode == old_mode) + return; + controller->auto_req = new_mode; + musb_writel(controller->controller.musb->ctrl_base, + controller->autoreq_reg, new_mode); +} + +static bool cppi41_configure_channel(struct dma_channel *channel, + u16 packet_sz, u8 mode, + dma_addr_t dma_addr, u32 len) +{ + struct cppi41_dma_channel *cppi41_channel = channel->private_data; + struct cppi41_dma_controller *controller = cppi41_channel->controller; + struct dma_chan *dc = cppi41_channel->dc; + struct dma_async_tx_descriptor *dma_desc; + enum dma_transfer_direction direction; + struct musb *musb = cppi41_channel->controller->controller.musb; + unsigned use_gen_rndis = 0; + + cppi41_channel->buf_addr = dma_addr; + cppi41_channel->total_len = len; + cppi41_channel->transferred = 0; + cppi41_channel->packet_sz = packet_sz; + cppi41_channel->tx_zlp = (cppi41_channel->is_tx && mode) ? 1 : 0; + + /* + * Due to AM335x' Advisory 1.0.13 we are not allowed to transfer more + * than max packet size at a time. + */ + if (cppi41_channel->is_tx) + use_gen_rndis = 1; + + if (use_gen_rndis) { + /* RNDIS mode */ + if (len > packet_sz) { + musb_writel(musb->ctrl_base, + RNDIS_REG(cppi41_channel->port_num), len); + /* gen rndis */ + controller->set_dma_mode(cppi41_channel, + EP_MODE_DMA_GEN_RNDIS); + + /* auto req */ + cppi41_set_autoreq_mode(cppi41_channel, + EP_MODE_AUTOREQ_ALL_NEOP); + } else { + musb_writel(musb->ctrl_base, + RNDIS_REG(cppi41_channel->port_num), 0); + controller->set_dma_mode(cppi41_channel, + EP_MODE_DMA_TRANSPARENT); + cppi41_set_autoreq_mode(cppi41_channel, + EP_MODE_AUTOREQ_NONE); + } + } else { + /* fallback mode */ + controller->set_dma_mode(cppi41_channel, + EP_MODE_DMA_TRANSPARENT); + cppi41_set_autoreq_mode(cppi41_channel, EP_MODE_AUTOREQ_NONE); + len = min_t(u32, packet_sz, len); + } + cppi41_channel->prog_len = len; + direction = cppi41_channel->is_tx ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; + dma_desc = dmaengine_prep_slave_single(dc, dma_addr, len, direction, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!dma_desc) + return false; + + dma_desc->callback_result = cppi41_dma_callback; + dma_desc->callback_param = channel; + cppi41_channel->cookie = dma_desc->tx_submit(dma_desc); + cppi41_channel->channel.rx_packet_done = false; + + trace_musb_cppi41_config(cppi41_channel); + + save_rx_toggle(cppi41_channel); + dma_async_issue_pending(dc); + return true; +} + +static struct dma_channel *cppi41_dma_channel_allocate(struct dma_controller *c, + struct musb_hw_ep *hw_ep, u8 is_tx) +{ + struct cppi41_dma_controller *controller = container_of(c, + struct cppi41_dma_controller, controller); + struct cppi41_dma_channel *cppi41_channel = NULL; + u8 ch_num = hw_ep->epnum - 1; + + if (ch_num >= controller->num_channels) + return NULL; + + if (is_tx) + cppi41_channel = &controller->tx_channel[ch_num]; + else + cppi41_channel = &controller->rx_channel[ch_num]; + + if (!cppi41_channel->dc) + return NULL; + + if (cppi41_channel->is_allocated) + return NULL; + + cppi41_channel->hw_ep = hw_ep; + cppi41_channel->is_allocated = 1; + + trace_musb_cppi41_alloc(cppi41_channel); + return &cppi41_channel->channel; +} + +static void cppi41_dma_channel_release(struct dma_channel *channel) +{ + struct cppi41_dma_channel *cppi41_channel = channel->private_data; + + trace_musb_cppi41_free(cppi41_channel); + if (cppi41_channel->is_allocated) { + cppi41_channel->is_allocated = 0; + channel->status = MUSB_DMA_STATUS_FREE; + channel->actual_len = 0; + } +} + +static int cppi41_dma_channel_program(struct dma_channel *channel, + u16 packet_sz, u8 mode, + dma_addr_t dma_addr, u32 len) +{ + int ret; + struct cppi41_dma_channel *cppi41_channel = channel->private_data; + int hb_mult = 0; + + BUG_ON(channel->status == MUSB_DMA_STATUS_UNKNOWN || + channel->status == MUSB_DMA_STATUS_BUSY); + + if (is_host_active(cppi41_channel->controller->controller.musb)) { + if (cppi41_channel->is_tx) + hb_mult = cppi41_channel->hw_ep->out_qh->hb_mult; + else + hb_mult = cppi41_channel->hw_ep->in_qh->hb_mult; + } + + channel->status = MUSB_DMA_STATUS_BUSY; + channel->actual_len = 0; + + if (hb_mult) + packet_sz = hb_mult * (packet_sz & 0x7FF); + + ret = cppi41_configure_channel(channel, packet_sz, mode, dma_addr, len); + if (!ret) + channel->status = MUSB_DMA_STATUS_FREE; + + return ret; +} + +static int cppi41_is_compatible(struct dma_channel *channel, u16 maxpacket, + void *buf, u32 length) +{ + struct cppi41_dma_channel *cppi41_channel = channel->private_data; + struct cppi41_dma_controller *controller = cppi41_channel->controller; + struct musb *musb = controller->controller.musb; + + if (is_host_active(musb)) { + WARN_ON(1); + return 1; + } + if (cppi41_channel->hw_ep->ep_in.type != USB_ENDPOINT_XFER_BULK) + return 0; + if (cppi41_channel->is_tx) + return 1; + /* AM335x Advisory 1.0.13. No workaround for device RX mode */ + return 0; +} + +static int cppi41_dma_channel_abort(struct dma_channel *channel) +{ + struct cppi41_dma_channel *cppi41_channel = channel->private_data; + struct cppi41_dma_controller *controller = cppi41_channel->controller; + struct musb *musb = controller->controller.musb; + void __iomem *epio = cppi41_channel->hw_ep->regs; + int tdbit; + int ret; + unsigned is_tx; + u16 csr; + + is_tx = cppi41_channel->is_tx; + trace_musb_cppi41_abort(cppi41_channel); + + if (cppi41_channel->channel.status == MUSB_DMA_STATUS_FREE) + return 0; + + list_del_init(&cppi41_channel->tx_check); + if (is_tx) { + csr = musb_readw(epio, MUSB_TXCSR); + csr &= ~MUSB_TXCSR_DMAENAB; + musb_writew(epio, MUSB_TXCSR, csr); + } else { + cppi41_set_autoreq_mode(cppi41_channel, EP_MODE_AUTOREQ_NONE); + + /* delay to drain to cppi dma pipeline for isoch */ + udelay(250); + + csr = musb_readw(epio, MUSB_RXCSR); + csr &= ~(MUSB_RXCSR_H_REQPKT | MUSB_RXCSR_DMAENAB); + musb_writew(epio, MUSB_RXCSR, csr); + + /* wait to drain cppi dma pipe line */ + udelay(50); + + csr = musb_readw(epio, MUSB_RXCSR); + if (csr & MUSB_RXCSR_RXPKTRDY) { + csr |= MUSB_RXCSR_FLUSHFIFO; + musb_writew(epio, MUSB_RXCSR, csr); + musb_writew(epio, MUSB_RXCSR, csr); + } + } + + /* DA8xx Advisory 2.3.27: wait 250 ms before to start the teardown */ + if (musb->ops->quirks & MUSB_DA8XX) + mdelay(250); + + tdbit = 1 << cppi41_channel->port_num; + if (is_tx) + tdbit <<= 16; + + do { + if (is_tx) + musb_writel(musb->ctrl_base, controller->tdown_reg, + tdbit); + ret = dmaengine_terminate_all(cppi41_channel->dc); + } while (ret == -EAGAIN); + + if (is_tx) { + musb_writel(musb->ctrl_base, controller->tdown_reg, tdbit); + + csr = musb_readw(epio, MUSB_TXCSR); + if (csr & MUSB_TXCSR_TXPKTRDY) { + csr |= MUSB_TXCSR_FLUSHFIFO; + musb_writew(epio, MUSB_TXCSR, csr); + } + } + + cppi41_channel->channel.status = MUSB_DMA_STATUS_FREE; + return 0; +} + +static void cppi41_release_all_dma_chans(struct cppi41_dma_controller *ctrl) +{ + struct dma_chan *dc; + int i; + + for (i = 0; i < ctrl->num_channels; i++) { + dc = ctrl->tx_channel[i].dc; + if (dc) + dma_release_channel(dc); + dc = ctrl->rx_channel[i].dc; + if (dc) + dma_release_channel(dc); + } +} + +static void cppi41_dma_controller_stop(struct cppi41_dma_controller *controller) +{ + cppi41_release_all_dma_chans(controller); +} + +static int cppi41_dma_controller_start(struct cppi41_dma_controller *controller) +{ + struct musb *musb = controller->controller.musb; + struct device *dev = musb->controller; + struct device_node *np = dev->parent->of_node; + struct cppi41_dma_channel *cppi41_channel; + int count; + int i; + int ret; + + count = of_property_count_strings(np, "dma-names"); + if (count < 0) + return count; + + for (i = 0; i < count; i++) { + struct dma_chan *dc; + struct dma_channel *musb_dma; + const char *str; + unsigned is_tx; + unsigned int port; + + ret = of_property_read_string_index(np, "dma-names", i, &str); + if (ret) + goto err; + if (strstarts(str, "tx")) + is_tx = 1; + else if (strstarts(str, "rx")) + is_tx = 0; + else { + dev_err(dev, "Wrong dmatype %s\n", str); + goto err; + } + ret = kstrtouint(str + 2, 0, &port); + if (ret) + goto err; + + ret = -EINVAL; + if (port > controller->num_channels || !port) + goto err; + if (is_tx) + cppi41_channel = &controller->tx_channel[port - 1]; + else + cppi41_channel = &controller->rx_channel[port - 1]; + + cppi41_channel->controller = controller; + cppi41_channel->port_num = port; + cppi41_channel->is_tx = is_tx; + INIT_LIST_HEAD(&cppi41_channel->tx_check); + + musb_dma = &cppi41_channel->channel; + musb_dma->private_data = cppi41_channel; + musb_dma->status = MUSB_DMA_STATUS_FREE; + musb_dma->max_len = SZ_4M; + + dc = dma_request_chan(dev->parent, str); + if (IS_ERR(dc)) { + ret = dev_err_probe(dev, PTR_ERR(dc), + "Failed to request %s.\n", str); + goto err; + } + + cppi41_channel->dc = dc; + } + return 0; +err: + cppi41_release_all_dma_chans(controller); + return ret; +} + +void cppi41_dma_controller_destroy(struct dma_controller *c) +{ + struct cppi41_dma_controller *controller = container_of(c, + struct cppi41_dma_controller, controller); + + hrtimer_cancel(&controller->early_tx); + cppi41_dma_controller_stop(controller); + kfree(controller->rx_channel); + kfree(controller->tx_channel); + kfree(controller); +} +EXPORT_SYMBOL_GPL(cppi41_dma_controller_destroy); + +struct dma_controller * +cppi41_dma_controller_create(struct musb *musb, void __iomem *base) +{ + struct cppi41_dma_controller *controller; + int channel_size; + int ret = 0; + + if (!musb->controller->parent->of_node) { + dev_err(musb->controller, "Need DT for the DMA engine.\n"); + return NULL; + } + + controller = kzalloc_obj(*controller); + if (!controller) + goto kzalloc_fail; + + hrtimer_setup(&controller->early_tx, cppi41_recheck_tx_req, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + INIT_LIST_HEAD(&controller->early_tx_list); + + controller->controller.channel_alloc = cppi41_dma_channel_allocate; + controller->controller.channel_release = cppi41_dma_channel_release; + controller->controller.channel_program = cppi41_dma_channel_program; + controller->controller.channel_abort = cppi41_dma_channel_abort; + controller->controller.is_compatible = cppi41_is_compatible; + controller->controller.musb = musb; + + if (musb->ops->quirks & MUSB_DA8XX) { + controller->tdown_reg = DA8XX_USB_TEARDOWN; + controller->autoreq_reg = DA8XX_USB_AUTOREQ; + controller->set_dma_mode = da8xx_set_dma_mode; + controller->num_channels = DA8XX_DMA_NUM_CHANNELS; + } else { + controller->tdown_reg = USB_TDOWN; + controller->autoreq_reg = USB_CTRL_AUTOREQ; + controller->set_dma_mode = cppi41_set_dma_mode; + controller->num_channels = MUSB_DMA_NUM_CHANNELS; + } + + channel_size = controller->num_channels * + sizeof(struct cppi41_dma_channel); + controller->rx_channel = kzalloc(channel_size, GFP_KERNEL); + if (!controller->rx_channel) + goto rx_channel_alloc_fail; + controller->tx_channel = kzalloc(channel_size, GFP_KERNEL); + if (!controller->tx_channel) + goto tx_channel_alloc_fail; + + ret = cppi41_dma_controller_start(controller); + if (ret) + goto plat_get_fail; + return &controller->controller; + +plat_get_fail: + kfree(controller->tx_channel); +tx_channel_alloc_fail: + kfree(controller->rx_channel); +rx_channel_alloc_fail: + kfree(controller); +kzalloc_fail: + if (ret == -EPROBE_DEFER) + return ERR_PTR(ret); + return NULL; +} +EXPORT_SYMBOL_GPL(cppi41_dma_controller_create); diff --git a/drivers/usb/musb/musb_debug.h b/drivers/usb/musb/musb_debug.h new file mode 100644 index 000000000..e5b3506c7 --- /dev/null +++ b/drivers/usb/musb/musb_debug.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MUSB OTG driver debug defines + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + */ + +#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) + +void musb_dbg(struct musb *musb, const char *fmt, ...); + +#ifdef CONFIG_DEBUG_FS +void musb_init_debugfs(struct musb *musb); +void musb_exit_debugfs(struct musb *musb); +#else +static inline void musb_init_debugfs(struct musb *musb) +{ +} +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 000000000..5092d62c2 --- /dev/null +++ b/drivers/usb/musb/musb_debugfs.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MUSB OTG driver debugfs support + * + * Copyright 2010 Nokia Corporation + * Contact: Felipe Balbi + */ + +#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", MUSB_FADDR, 8 }, + { "Power", MUSB_POWER, 8 }, + { "Frame", MUSB_FRAME, 16 }, + { "Index", MUSB_INDEX, 8 }, + { "Testmode", MUSB_TESTMODE, 8 }, + { "TxMaxPp", MUSB_TXMAXP, 16 }, + { "TxCSRp", MUSB_TXCSR, 16 }, + { "RxMaxPp", MUSB_RXMAXP, 16 }, + { "RxCSR", MUSB_RXCSR, 16 }, + { "RxCount", MUSB_RXCOUNT, 16 }, + { "IntrRxE", MUSB_INTRRXE, 16 }, + { "IntrTxE", MUSB_INTRTXE, 16 }, + { "IntrUsbE", MUSB_INTRUSBE, 8 }, + { "DevCtl", MUSB_DEVCTL, 8 }, + { "VControl", 0x68, 32 }, + { "HWVers", MUSB_HWVERS, 16 }, + { "LinkInfo", MUSB_LINKINFO, 8 }, + { "VPLen", MUSB_VPLEN, 8 }, + { "HS_EOF1", MUSB_HS_EOF1, 8 }, + { "FS_EOF1", MUSB_FS_EOF1, 8 }, + { "LS_EOF1", MUSB_LS_EOF1, 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 }, + { "ConfigData", MUSB_CONFIGDATA,8 }, + { "BabbleCtl", MUSB_BABBLE_CTL,8 }, + { "TxFIFOsz", MUSB_TXFIFOSZ, 8 }, + { "RxFIFOsz", MUSB_RXFIFOSZ, 8 }, + { "TxFIFOadd", MUSB_TXFIFOADD, 16 }, + { "RxFIFOadd", MUSB_RXFIFOADD, 16 }, + { "EPInfo", MUSB_EPINFO, 8 }, + { "RAMInfo", MUSB_RAMINFO, 8 }, + { } /* 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"); + pm_runtime_get_sync(musb->controller); + + 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; + } + } + + pm_runtime_put_autosuspend(musb->controller); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(musb_regdump); + +static int musb_test_mode_show(struct seq_file *s, void *unused) +{ + struct musb *musb = s->private; + unsigned test; + + pm_runtime_get_sync(musb->controller); + test = musb_readb(musb->mregs, MUSB_TESTMODE); + pm_runtime_put_autosuspend(musb->controller); + + if (test == (MUSB_TEST_FORCE_HOST | MUSB_TEST_FORCE_FS)) + seq_printf(s, "force host full-speed\n"); + + else if (test == (MUSB_TEST_FORCE_HOST | MUSB_TEST_FORCE_HS)) + seq_printf(s, "force host high-speed\n"); + + else if (test == MUSB_TEST_FORCE_HOST) + seq_printf(s, "force host\n"); + + else if (test == MUSB_TEST_FIFO_ACCESS) + seq_printf(s, "fifo access\n"); + + else if (test == MUSB_TEST_FORCE_FS) + seq_printf(s, "force full-speed\n"); + + else if (test == MUSB_TEST_FORCE_HS) + seq_printf(s, "force high-speed\n"); + + else if (test == MUSB_TEST_PACKET) + seq_printf(s, "test packet\n"); + + else if (test == MUSB_TEST_K) + seq_printf(s, "test K\n"); + + else if (test == MUSB_TEST_J) + seq_printf(s, "test J\n"); + + else if (test == MUSB_TEST_SE0_NAK) + seq_printf(s, "test SE0 NAK\n"); + + return 0; +} + +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; + char buf[24]; + + memset(buf, 0x00, sizeof(buf)); + + if (copy_from_user(buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + pm_runtime_get_sync(musb->controller); + test = musb_readb(musb->mregs, MUSB_TESTMODE); + if (test) { + dev_err(musb->controller, "Error: test mode is already set. " + "Please do USB Bus Reset to start a new test.\n"); + goto ret; + } + + if (strstarts(buf, "force host full-speed")) + test = MUSB_TEST_FORCE_HOST | MUSB_TEST_FORCE_FS; + + else if (strstarts(buf, "force host high-speed")) + test = MUSB_TEST_FORCE_HOST | MUSB_TEST_FORCE_HS; + + else if (strstarts(buf, "force host")) + test = MUSB_TEST_FORCE_HOST; + + else if (strstarts(buf, "fifo access")) + test = MUSB_TEST_FIFO_ACCESS; + + else if (strstarts(buf, "force full-speed")) + test = MUSB_TEST_FORCE_FS; + + else if (strstarts(buf, "force high-speed")) + test = MUSB_TEST_FORCE_HS; + + else if (strstarts(buf, "test packet")) { + test = MUSB_TEST_PACKET; + musb_load_testpacket(musb); + } + + else if (strstarts(buf, "test K")) + test = MUSB_TEST_K; + + else if (strstarts(buf, "test J")) + test = MUSB_TEST_J; + + else if (strstarts(buf, "test SE0 NAK")) + test = MUSB_TEST_SE0_NAK; + + musb_writeb(musb->mregs, MUSB_TESTMODE, test); + +ret: + pm_runtime_put_autosuspend(musb->controller); + 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, +}; + +static int musb_softconnect_show(struct seq_file *s, void *unused) +{ + struct musb *musb = s->private; + u8 reg; + int connect; + + switch (musb_get_state(musb)) { + case OTG_STATE_A_HOST: + case OTG_STATE_A_WAIT_BCON: + pm_runtime_get_sync(musb->controller); + + reg = musb_readb(musb->mregs, MUSB_DEVCTL); + connect = reg & MUSB_DEVCTL_SESSION ? 1 : 0; + + pm_runtime_put_autosuspend(musb->controller); + break; + default: + connect = -1; + } + + seq_printf(s, "%d\n", connect); + + return 0; +} + +static int musb_softconnect_open(struct inode *inode, struct file *file) +{ + return single_open(file, musb_softconnect_show, inode->i_private); +} + +static ssize_t musb_softconnect_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; + char buf[2]; + u8 reg; + + memset(buf, 0x00, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + pm_runtime_get_sync(musb->controller); + if (!strncmp(buf, "0", 1)) { + switch (musb_get_state(musb)) { + case OTG_STATE_A_HOST: + musb_root_disconnect(musb); + reg = musb_readb(musb->mregs, MUSB_DEVCTL); + reg &= ~MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, reg); + break; + default: + break; + } + } else if (!strncmp(buf, "1", 1)) { + switch (musb_get_state(musb)) { + case OTG_STATE_A_WAIT_BCON: + /* + * musb_save_context() called in musb_runtime_suspend() + * might cache devctl with SESSION bit cleared during + * soft-disconnect, so specifically set SESSION bit + * here to preserve it for musb_runtime_resume(). + */ + musb->context.devctl |= MUSB_DEVCTL_SESSION; + reg = musb_readb(musb->mregs, MUSB_DEVCTL); + reg |= MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, reg); + break; + default: + break; + } + } + + pm_runtime_put_autosuspend(musb->controller); + return count; +} + +/* + * In host mode, connect/disconnect the bus without physically + * remove the devices. + */ +static const struct file_operations musb_softconnect_fops = { + .open = musb_softconnect_open, + .write = musb_softconnect_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +void musb_init_debugfs(struct musb *musb) +{ + struct dentry *root; + + root = debugfs_create_dir(dev_name(musb->controller), usb_debug_root); + musb->debugfs_root = root; + + debugfs_create_file("regdump", S_IRUGO, root, musb, &musb_regdump_fops); + debugfs_create_file("testmode", S_IRUGO | S_IWUSR, root, musb, + &musb_test_mode_fops); + debugfs_create_file("softconnect", S_IRUGO | S_IWUSR, root, musb, + &musb_softconnect_fops); +} + +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 000000000..0cd7fc468 --- /dev/null +++ b/drivers/usb/musb/musb_dma.h @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MUSB OTG driver DMA controller abstraction + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + */ + +#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 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 DMA_ADDR_INVALID (~(dma_addr_t)0) + +#ifdef CONFIG_MUSB_PIO_ONLY +#define is_dma_capable() (0) +#else +#define is_dma_capable() (1) +#endif + +#ifdef CONFIG_USB_UX500_DMA +#define musb_dma_ux500(musb) (musb->ops->quirks & MUSB_DMA_UX500) +#else +#define musb_dma_ux500(musb) 0 +#endif + +#ifdef CONFIG_USB_TI_CPPI41_DMA +#define musb_dma_cppi41(musb) (musb->ops->quirks & MUSB_DMA_CPPI41) +#else +#define musb_dma_cppi41(musb) 0 +#endif + +#ifdef CONFIG_USB_TUSB_OMAP_DMA +#define tusb_dma_omap(musb) (musb->ops->quirks & MUSB_DMA_TUSB_OMAP) +#else +#define tusb_dma_omap(musb) 0 +#endif + +#ifdef CONFIG_USB_INVENTRA_DMA +#define musb_dma_inventra(musb) (musb->ops->quirks & MUSB_DMA_INVENTRA) +#else +#define musb_dma_inventra(musb) 0 +#endif + +#if defined(CONFIG_USB_TI_CPPI41_DMA) +#define is_cppi_enabled(musb) musb_dma_cppi41(musb) +#else +#define is_cppi_enabled(musb) 0 +#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; + bool rx_packet_done; +}; + +/* + * 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. + * @musb: the usb 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 + * @dma_callback: invoked on DMA completion, useful to run platform + * code such IRQ acknowledgment. + * + * Controllers manage dma channels. + */ +struct dma_controller { + struct musb *musb; + 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); + void (*dma_callback)(struct dma_controller *); +}; + +/* called after channel_program(), may indicate a fault */ +extern void musb_dma_completion(struct musb *musb, u8 epnum, u8 transmit); + +#ifdef CONFIG_MUSB_PIO_ONLY +static inline struct dma_controller * +musb_dma_controller_create(struct musb *m, void __iomem *io) +{ + return NULL; +} + +static inline void musb_dma_controller_destroy(struct dma_controller *d) { } + +#else + +extern struct dma_controller * +(*musb_dma_controller_create)(struct musb *, void __iomem *); + +extern void (*musb_dma_controller_destroy)(struct dma_controller *); +#endif + +/* Platform specific DMA functions */ +extern struct dma_controller * +musbhs_dma_controller_create(struct musb *musb, void __iomem *base); +extern void musbhs_dma_controller_destroy(struct dma_controller *c); +extern struct dma_controller * +musbhs_dma_controller_create_noirq(struct musb *musb, void __iomem *base); +extern irqreturn_t dma_controller_irq(int irq, void *private_data); + +extern struct dma_controller * +tusb_dma_controller_create(struct musb *musb, void __iomem *base); +extern void tusb_dma_controller_destroy(struct dma_controller *c); + +extern struct dma_controller * +cppi41_dma_controller_create(struct musb *musb, void __iomem *base); +extern void cppi41_dma_controller_destroy(struct dma_controller *c); + +extern struct dma_controller * +ux500_dma_controller_create(struct musb *musb, void __iomem *base); +extern void ux500_dma_controller_destroy(struct dma_controller *c); + +#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 000000000..e3935f18d --- /dev/null +++ b/drivers/usb/musb/musb_dsps.c @@ -0,0 +1,1048 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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. + * + * 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 + +#include + +#include "musb_core.h" + +static const struct of_device_id musb_dsps_of_match[]; + +/* + * 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 epintr_set; + u16 epintr_clear; + u16 epintr_status; + u16 coreintr_set; + u16 coreintr_clear; + u16 coreintr_status; + u16 phy_utmi; + u16 mode; + u16 tx_mode; + u16 rx_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; + unsigned iddig_mux:5; + /* miscellaneous stuff */ + unsigned poll_timeout; +}; + +/* + * register shadow for suspend + */ +struct dsps_context { + u32 control; + u32 epintr; + u32 coreintr; + u32 phy_utmi; + u32 mode; + u32 tx_mode; + u32 rx_mode; +}; + +/* + * DSPS glue structure. + */ +struct dsps_glue { + struct device *dev; + struct platform_device *musb; /* child musb pdev */ + const struct dsps_musb_wrapper *wrp; /* wrapper register offsets */ + int vbus_irq; /* optional vbus irq */ + unsigned long last_timer; /* last timer data for each instance */ + bool sw_babble_enabled; + void __iomem *usbss_base; + + struct dsps_context context; + struct debugfs_regset32 regset; + struct dentry *dbgfs_root; +}; + +static const struct debugfs_reg32 dsps_musb_regs[] = { + { "revision", 0x00 }, + { "control", 0x14 }, + { "status", 0x18 }, + { "eoi", 0x24 }, + { "intr0_stat", 0x30 }, + { "intr1_stat", 0x34 }, + { "intr0_set", 0x38 }, + { "intr1_set", 0x3c }, + { "txmode", 0x70 }, + { "rxmode", 0x74 }, + { "autoreq", 0xd0 }, + { "srpfixtime", 0xd4 }, + { "tdown", 0xd8 }, + { "phy_utmi", 0xe0 }, + { "mode", 0xe8 }, +}; + +static void dsps_mod_timer(struct dsps_glue *glue, int wait_ms) +{ + struct musb *musb = platform_get_drvdata(glue->musb); + int wait; + + if (wait_ms < 0) + wait = msecs_to_jiffies(glue->wrp->poll_timeout); + else + wait = msecs_to_jiffies(wait_ms); + + mod_timer(&musb->dev_timer, jiffies + wait); +} + +/* + * If no vbus irq from the PMIC is configured, we need to poll VBUS status. + */ +static void dsps_mod_timer_optional(struct dsps_glue *glue) +{ + if (glue->vbus_irq) + return; + + dsps_mod_timer(glue, -1); +} + +/* USBSS / USB AM335x */ +#define USBSS_IRQ_STATUS 0x28 +#define USBSS_IRQ_ENABLER 0x2c +#define USBSS_IRQ_CLEARR 0x30 + +#define USBSS_IRQ_PD_COMP (1 << 2) + +/* + * dsps_musb_enable - enable interrupts + */ +static void dsps_musb_enable(struct musb *musb) +{ + struct device *dev = musb->controller; + 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 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); + + musb_writel(reg_base, wrp->epintr_set, epmask); + musb_writel(reg_base, wrp->coreintr_set, coremask); + /* + * start polling for runtime PM active and idle, + * and for ID change in dual-role idle mode. + */ + if (musb->xceiv->otg->state == OTG_STATE_B_IDLE) + dsps_mod_timer(glue, -1); +} + +/* + * dsps_musb_disable - disable HDRC and flush interrupts + */ +static void dsps_musb_disable(struct musb *musb) +{ + struct device *dev = musb->controller; + struct dsps_glue *glue = dev_get_drvdata(dev->parent); + const struct dsps_musb_wrapper *wrp = glue->wrp; + void __iomem *reg_base = musb->ctrl_base; + + musb_writel(reg_base, wrp->coreintr_clear, wrp->usb_bitmap); + musb_writel(reg_base, wrp->epintr_clear, + wrp->txep_bitmap | wrp->rxep_bitmap); + timer_delete_sync(&musb->dev_timer); +} + +/* Caller must take musb->lock */ +static int dsps_check_status(struct musb *musb, void *unused) +{ + void __iomem *mregs = musb->mregs; + struct device *dev = musb->controller; + struct dsps_glue *glue = dev_get_drvdata(dev->parent); + const struct dsps_musb_wrapper *wrp = glue->wrp; + u8 devctl; + int skip_session = 0; + + if (glue->vbus_irq) + timer_delete(&musb->dev_timer); + + /* + * We poll because DSPS IP'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->otg->state)); + + switch (musb->xceiv->otg->state) { + case OTG_STATE_A_WAIT_VRISE: + if (musb->port_mode == MUSB_HOST) { + musb->xceiv->otg->state = OTG_STATE_A_WAIT_BCON; + dsps_mod_timer_optional(glue); + break; + } + fallthrough; + + case OTG_STATE_A_WAIT_BCON: + /* keep VBUS on for host-only mode */ + if (musb->port_mode == MUSB_HOST) { + dsps_mod_timer_optional(glue); + break; + } + musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + skip_session = 1; + fallthrough; + + case OTG_STATE_A_IDLE: + case OTG_STATE_B_IDLE: + if (!glue->vbus_irq) { + if (devctl & MUSB_DEVCTL_BDEVICE) { + musb->xceiv->otg->state = OTG_STATE_B_IDLE; + MUSB_DEV_MODE(musb); + } else { + musb->xceiv->otg->state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + } + + if (musb->port_mode == MUSB_PERIPHERAL) + skip_session = 1; + + if (!(devctl & MUSB_DEVCTL_SESSION) && !skip_session) + musb_writeb(mregs, MUSB_DEVCTL, + MUSB_DEVCTL_SESSION); + } + dsps_mod_timer_optional(glue); + break; + case OTG_STATE_A_WAIT_VFALL: + musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE; + musb_writel(musb->ctrl_base, wrp->coreintr_set, + MUSB_INTR_VBUSERROR << wrp->usb_shift); + break; + default: + break; + } + + return 0; +} + +static void otg_timer(struct timer_list *t) +{ + struct musb *musb = timer_container_of(musb, t, dev_timer); + struct device *dev = musb->controller; + unsigned long flags; + int err; + + err = pm_runtime_get(dev); + if ((err != -EINPROGRESS) && err < 0) { + dev_err(dev, "Poll could not pm_runtime_get: %i\n", err); + pm_runtime_put_noidle(dev); + + return; + } + + spin_lock_irqsave(&musb->lock, flags); + err = musb_queue_resume_work(musb, dsps_check_status, NULL); + if (err < 0) + dev_err(dev, "%s resume work: %i\n", __func__, err); + spin_unlock_irqrestore(&musb->lock, flags); + pm_runtime_put_autosuspend(dev); +} + +static void dsps_musb_clear_ep_rxintr(struct musb *musb, int epnum) +{ + u32 epintr; + struct dsps_glue *glue = dev_get_drvdata(musb->controller->parent); + const struct dsps_musb_wrapper *wrp = glue->wrp; + + /* musb->lock might already been held */ + epintr = (1 << epnum) << wrp->rxep_shift; + musb_writel(musb->ctrl_base, wrp->epintr_status, epintr); +} + +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 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 = musb_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) + musb_writel(reg_base, wrp->epintr_status, epintr); + + /* Get usb core interrupts */ + usbintr = musb_readl(reg_base, wrp->coreintr_status); + if (!usbintr && !epintr) + goto out; + + musb->int_usb = (usbintr & wrp->usb_bitmap) >> wrp->usb_shift; + if (usbintr) + musb_writel(reg_base, wrp->coreintr_status, usbintr); + + dev_dbg(musb->controller, "usbintr (%x) epintr(%x)\n", + usbintr, epintr); + + if (usbintr & ((1 << wrp->drvvbus) << wrp->usb_shift)) { + int drvvbus = musb_readl(reg_base, wrp->status); + 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->otg->state = OTG_STATE_A_WAIT_VFALL; + dsps_mod_timer_optional(glue); + WARNING("VBUS error workaround (delay coming)\n"); + } else if (drvvbus) { + MUSB_HST_MODE(musb); + musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE; + dsps_mod_timer_optional(glue); + } else { + musb->is_active = 0; + MUSB_DEV_MODE(musb); + musb->xceiv->otg->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", + str_on_off(drvvbus), + usb_otg_state_string(musb->xceiv->otg->state), + err ? " ERROR" : "", + devctl); + ret = IRQ_HANDLED; + } + + if (musb->int_tx || musb->int_rx || musb->int_usb) + ret |= musb_interrupt(musb); + + /* Poll for ID change and connect */ + switch (musb->xceiv->otg->state) { + case OTG_STATE_B_IDLE: + case OTG_STATE_A_WAIT_BCON: + dsps_mod_timer_optional(glue); + break; + default: + break; + } + +out: + spin_unlock_irqrestore(&musb->lock, flags); + + return ret; +} + +static int dsps_musb_dbg_init(struct musb *musb, struct dsps_glue *glue) +{ + struct dentry *root; + char buf[128]; + + sprintf(buf, "%s.dsps", dev_name(musb->controller)); + root = debugfs_create_dir(buf, usb_debug_root); + glue->dbgfs_root = root; + + glue->regset.regs = dsps_musb_regs; + glue->regset.nregs = ARRAY_SIZE(dsps_musb_regs); + glue->regset.base = musb->ctrl_base; + + debugfs_create_regset32("regdump", S_IRUGO, root, &glue->regset); + return 0; +} + +static int dsps_musb_init(struct musb *musb) +{ + struct device *dev = musb->controller; + struct dsps_glue *glue = dev_get_drvdata(dev->parent); + struct platform_device *parent = to_platform_device(dev->parent); + const struct dsps_musb_wrapper *wrp = glue->wrp; + void __iomem *reg_base; + struct resource *r; + u32 rev, val; + int ret; + + r = platform_get_resource_byname(parent, IORESOURCE_MEM, "control"); + reg_base = devm_ioremap_resource(dev, r); + if (IS_ERR(reg_base)) + return PTR_ERR(reg_base); + musb->ctrl_base = reg_base; + + /* NOP driver needs change if supporting dual instance */ + musb->xceiv = devm_usb_get_phy_by_phandle(dev->parent, "phys", 0); + if (IS_ERR(musb->xceiv)) + return PTR_ERR(musb->xceiv); + + musb->phy = devm_phy_get(dev->parent, "usb2-phy"); + + /* Returns zero if e.g. not clocked */ + rev = musb_readl(reg_base, wrp->revision); + if (!rev) + return -ENODEV; + + if (IS_ERR(musb->phy)) { + musb->phy = NULL; + } else { + ret = phy_init(musb->phy); + if (ret < 0) + return ret; + ret = phy_power_on(musb->phy); + if (ret) { + phy_exit(musb->phy); + return ret; + } + } + + timer_setup(&musb->dev_timer, otg_timer, 0); + + /* Reset the musb */ + musb_writel(reg_base, wrp->control, (1 << wrp->reset)); + + musb->isr = dsps_interrupt; + + /* reset the otgdisable bit, needed for host mode to work */ + val = musb_readl(reg_base, wrp->phy_utmi); + val &= ~(1 << wrp->otg_disable); + musb_writel(musb->ctrl_base, wrp->phy_utmi, val); + + /* + * Check whether the dsps version has babble control enabled. + * In latest silicon revision the babble control logic is enabled. + * If MUSB_BABBLE_CTL returns 0x4 then we have the babble control + * logic enabled. + */ + val = musb_readb(musb->mregs, MUSB_BABBLE_CTL); + if (val & MUSB_BABBLE_RCV_DISABLE) { + glue->sw_babble_enabled = true; + val |= MUSB_BABBLE_SW_SESSION_CTRL; + musb_writeb(musb->mregs, MUSB_BABBLE_CTL, val); + } + + dsps_mod_timer(glue, -1); + + return dsps_musb_dbg_init(musb, glue); +} + +static int dsps_musb_exit(struct musb *musb) +{ + struct device *dev = musb->controller; + struct dsps_glue *glue = dev_get_drvdata(dev->parent); + + timer_delete_sync(&musb->dev_timer); + phy_power_off(musb->phy); + phy_exit(musb->phy); + debugfs_remove_recursive(glue->dbgfs_root); + + return 0; +} + +static int dsps_musb_set_mode(struct musb *musb, u8 mode) +{ + struct device *dev = musb->controller; + struct dsps_glue *glue = dev_get_drvdata(dev->parent); + const struct dsps_musb_wrapper *wrp = glue->wrp; + void __iomem *ctrl_base = musb->ctrl_base; + u32 reg; + + reg = musb_readl(ctrl_base, wrp->mode); + + switch (mode) { + case MUSB_HOST: + reg &= ~(1 << wrp->iddig); + + /* + * if we're setting mode to host-only or device-only, we're + * going to ignore whatever the PHY sends us and just force + * ID pin status by SW + */ + reg |= (1 << wrp->iddig_mux); + + musb_writel(ctrl_base, wrp->mode, reg); + musb_writel(ctrl_base, wrp->phy_utmi, 0x02); + break; + case MUSB_PERIPHERAL: + reg |= (1 << wrp->iddig); + + /* + * if we're setting mode to host-only or device-only, we're + * going to ignore whatever the PHY sends us and just force + * ID pin status by SW + */ + reg |= (1 << wrp->iddig_mux); + + musb_writel(ctrl_base, wrp->mode, reg); + break; + case MUSB_OTG: + musb_writel(ctrl_base, wrp->phy_utmi, 0x02); + break; + default: + dev_err(glue->dev, "unsupported mode %d\n", mode); + return -EINVAL; + } + + return 0; +} + +static bool dsps_sw_babble_control(struct musb *musb) +{ + u8 babble_ctl; + bool session_restart = false; + + babble_ctl = musb_readb(musb->mregs, MUSB_BABBLE_CTL); + dev_dbg(musb->controller, "babble: MUSB_BABBLE_CTL value %x\n", + babble_ctl); + /* + * check line monitor flag to check whether babble is + * due to noise + */ + dev_dbg(musb->controller, "STUCK_J is %s\n", + babble_ctl & MUSB_BABBLE_STUCK_J ? "set" : "reset"); + + if (babble_ctl & MUSB_BABBLE_STUCK_J) { + int timeout = 10; + + /* + * babble is due to noise, then set transmit idle (d7 bit) + * to resume normal operation + */ + babble_ctl = musb_readb(musb->mregs, MUSB_BABBLE_CTL); + babble_ctl |= MUSB_BABBLE_FORCE_TXIDLE; + musb_writeb(musb->mregs, MUSB_BABBLE_CTL, babble_ctl); + + /* wait till line monitor flag cleared */ + dev_dbg(musb->controller, "Set TXIDLE, wait J to clear\n"); + do { + babble_ctl = musb_readb(musb->mregs, MUSB_BABBLE_CTL); + udelay(1); + } while ((babble_ctl & MUSB_BABBLE_STUCK_J) && timeout--); + + /* check whether stuck_at_j bit cleared */ + if (babble_ctl & MUSB_BABBLE_STUCK_J) { + /* + * real babble condition has occurred + * restart the controller to start the + * session again + */ + dev_dbg(musb->controller, "J not cleared, misc (%x)\n", + babble_ctl); + session_restart = true; + } + } else { + session_restart = true; + } + + return session_restart; +} + +static int dsps_musb_recover(struct musb *musb) +{ + struct device *dev = musb->controller; + struct dsps_glue *glue = dev_get_drvdata(dev->parent); + int session_restart = 0; + + if (glue->sw_babble_enabled) + session_restart = dsps_sw_babble_control(musb); + else + session_restart = 1; + + return session_restart ? 0 : -EPIPE; +} + +/* Similar to am35x, dm81xx support only 32-bit read operation */ +static void dsps_read_fifo32(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) +{ + void __iomem *fifo = hw_ep->fifo; + + if (len >= 4) { + ioread32_rep(fifo, dst, len >> 2); + dst += len & ~0x03; + len &= 0x03; + } + + /* Read any remaining 1 to 3 bytes */ + if (len > 0) { + u32 val = musb_readl(fifo, 0); + memcpy(dst, &val, len); + } +} + +#ifdef CONFIG_USB_TI_CPPI41_DMA +static void dsps_dma_controller_callback(struct dma_controller *c) +{ + struct musb *musb = c->musb; + struct dsps_glue *glue = dev_get_drvdata(musb->controller->parent); + void __iomem *usbss_base = glue->usbss_base; + u32 status; + + status = musb_readl(usbss_base, USBSS_IRQ_STATUS); + if (status & USBSS_IRQ_PD_COMP) + musb_writel(usbss_base, USBSS_IRQ_STATUS, USBSS_IRQ_PD_COMP); +} + +static struct dma_controller * +dsps_dma_controller_create(struct musb *musb, void __iomem *base) +{ + struct dma_controller *controller; + struct dsps_glue *glue = dev_get_drvdata(musb->controller->parent); + void __iomem *usbss_base = glue->usbss_base; + + controller = cppi41_dma_controller_create(musb, base); + if (IS_ERR_OR_NULL(controller)) + return controller; + + musb_writel(usbss_base, USBSS_IRQ_ENABLER, USBSS_IRQ_PD_COMP); + controller->dma_callback = dsps_dma_controller_callback; + + return controller; +} + +#ifdef CONFIG_PM_SLEEP +static void dsps_dma_controller_suspend(struct dsps_glue *glue) +{ + void __iomem *usbss_base = glue->usbss_base; + + musb_writel(usbss_base, USBSS_IRQ_CLEARR, USBSS_IRQ_PD_COMP); +} + +static void dsps_dma_controller_resume(struct dsps_glue *glue) +{ + void __iomem *usbss_base = glue->usbss_base; + + musb_writel(usbss_base, USBSS_IRQ_ENABLER, USBSS_IRQ_PD_COMP); +} +#endif +#else /* CONFIG_USB_TI_CPPI41_DMA */ +#ifdef CONFIG_PM_SLEEP +static void dsps_dma_controller_suspend(struct dsps_glue *glue) {} +static void dsps_dma_controller_resume(struct dsps_glue *glue) {} +#endif +#endif /* CONFIG_USB_TI_CPPI41_DMA */ + +static struct musb_platform_ops dsps_ops = { + .quirks = MUSB_DMA_CPPI41 | MUSB_INDEXED_EP, + .init = dsps_musb_init, + .exit = dsps_musb_exit, + +#ifdef CONFIG_USB_TI_CPPI41_DMA + .dma_init = dsps_dma_controller_create, + .dma_exit = cppi41_dma_controller_destroy, +#endif + .enable = dsps_musb_enable, + .disable = dsps_musb_disable, + + .set_mode = dsps_musb_set_mode, + .recover = dsps_musb_recover, + .clear_ep_rxintr = dsps_musb_clear_ep_rxintr, +}; + +static u64 musb_dmamask = DMA_BIT_MASK(32); + +static int get_int_prop(struct device_node *dn, const char *s) +{ + int ret; + u32 val; + + ret = of_property_read_u32(dn, s, &val); + if (ret) + return 0; + return val; +} + +static int dsps_create_musb_pdev(struct dsps_glue *glue, + struct platform_device *parent) +{ + struct musb_hdrc_platform_data pdata; + struct resource resources[2]; + struct resource *res; + struct device *dev = &parent->dev; + struct musb_hdrc_config *config; + struct platform_device *musb; + struct device_node *dn = parent->dev.of_node; + int ret, val; + + memset(resources, 0, sizeof(resources)); + res = platform_get_resource_byname(parent, IORESOURCE_MEM, "mc"); + if (!res) { + dev_err(dev, "failed to get memory.\n"); + return -EINVAL; + } + resources[0] = *res; + + ret = platform_get_irq_byname(parent, "mc"); + if (ret < 0) + return ret; + + resources[1].start = ret; + resources[1].end = ret; + resources[1].flags = IORESOURCE_IRQ | irq_get_trigger_type(ret); + resources[1].name = "mc"; + + /* allocate the child platform device */ + musb = platform_device_alloc("musb-hdrc", + (resources[0].start & 0xFFF) == 0x400 ? 0 : 1); + if (!musb) { + dev_err(dev, "failed to allocate musb device\n"); + return -ENOMEM; + } + + musb->dev.parent = dev; + musb->dev.dma_mask = &musb_dmamask; + musb->dev.coherent_dma_mask = musb_dmamask; + device_set_of_node_from_dev(&musb->dev, &parent->dev); + + glue->musb = musb; + + ret = platform_device_add_resources(musb, resources, + ARRAY_SIZE(resources)); + if (ret) { + dev_err(dev, "failed to add resources\n"); + goto err; + } + + config = devm_kzalloc(&parent->dev, sizeof(*config), GFP_KERNEL); + if (!config) { + ret = -ENOMEM; + goto err; + } + pdata.config = config; + pdata.platform_ops = &dsps_ops; + + config->num_eps = get_int_prop(dn, "mentor,num-eps"); + config->ram_bits = get_int_prop(dn, "mentor,ram-bits"); + config->host_port_deassert_reset_at_resume = 1; + pdata.mode = musb_get_mode(dev); + /* DT keeps this entry in mA, musb expects it as per USB spec */ + pdata.power = get_int_prop(dn, "mentor,power") / 2; + + ret = of_property_read_u32(dn, "mentor,multipoint", &val); + if (!ret && val) + config->multipoint = true; + + config->maximum_speed = usb_get_maximum_speed(&parent->dev); + switch (config->maximum_speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + break; + case USB_SPEED_SUPER: + dev_warn(dev, "ignore incorrect maximum_speed " + "(super-speed) setting in dts"); + fallthrough; + default: + config->maximum_speed = USB_SPEED_HIGH; + } + + ret = platform_device_add_data(musb, &pdata, sizeof(pdata)); + if (ret) { + dev_err(dev, "failed to add platform_data\n"); + goto err; + } + + ret = platform_device_add(musb); + if (ret) { + dev_err(dev, "failed to register musb device\n"); + goto err; + } + return 0; + +err: + platform_device_put(musb); + return ret; +} + +static irqreturn_t dsps_vbus_threaded_irq(int irq, void *priv) +{ + struct dsps_glue *glue = priv; + struct musb *musb = platform_get_drvdata(glue->musb); + + if (!musb) + return IRQ_NONE; + + dev_dbg(glue->dev, "VBUS interrupt\n"); + dsps_mod_timer(glue, 0); + + return IRQ_HANDLED; +} + +static int dsps_setup_optional_vbus_irq(struct platform_device *pdev, + struct dsps_glue *glue) +{ + int error; + + glue->vbus_irq = platform_get_irq_byname_optional(pdev, "vbus"); + if (glue->vbus_irq == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (glue->vbus_irq <= 0) { + glue->vbus_irq = 0; + return 0; + } + + error = devm_request_threaded_irq(glue->dev, glue->vbus_irq, + NULL, dsps_vbus_threaded_irq, + IRQF_SHARED, + "vbus", glue); + if (error) { + glue->vbus_irq = 0; + return error; + } + dev_dbg(glue->dev, "VBUS irq %i configured\n", glue->vbus_irq); + + return 0; +} + +static int dsps_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + const struct dsps_musb_wrapper *wrp; + struct dsps_glue *glue; + int ret; + + if (!strcmp(pdev->name, "musb-hdrc")) + return -ENODEV; + + 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"); + return -EINVAL; + } + wrp = match->data; + + if (of_device_is_compatible(pdev->dev.of_node, "ti,musb-dm816")) + dsps_ops.read_fifo = dsps_read_fifo32; + + /* allocate glue */ + glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL); + if (!glue) + return -ENOMEM; + + glue->dev = &pdev->dev; + glue->wrp = wrp; + glue->usbss_base = of_iomap(pdev->dev.parent->of_node, 0); + if (!glue->usbss_base) + return -ENXIO; + + platform_set_drvdata(pdev, glue); + pm_runtime_enable(&pdev->dev); + ret = dsps_create_musb_pdev(glue, pdev); + if (ret) + goto err; + + if (usb_get_dr_mode(&pdev->dev) == USB_DR_MODE_PERIPHERAL) { + ret = dsps_setup_optional_vbus_irq(pdev, glue); + if (ret) + goto unregister_pdev; + } + + return 0; + +unregister_pdev: + platform_device_unregister(glue->musb); +err: + pm_runtime_disable(&pdev->dev); + iounmap(glue->usbss_base); + return ret; +} + +static void dsps_remove(struct platform_device *pdev) +{ + struct dsps_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + + pm_runtime_disable(&pdev->dev); + iounmap(glue->usbss_base); +} + +static const struct dsps_musb_wrapper am33xx_driver_data = { + .revision = 0x00, + .control = 0x14, + .status = 0x18, + .epintr_set = 0x38, + .epintr_clear = 0x40, + .epintr_status = 0x30, + .coreintr_set = 0x3c, + .coreintr_clear = 0x44, + .coreintr_status = 0x34, + .phy_utmi = 0xe0, + .mode = 0xe8, + .tx_mode = 0x70, + .rx_mode = 0x74, + .reset = 0, + .otg_disable = 21, + .iddig = 8, + .iddig_mux = 7, + .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), + .poll_timeout = 2000, /* ms */ +}; + +static const struct of_device_id musb_dsps_of_match[] = { + { .compatible = "ti,musb-am33xx", + .data = &am33xx_driver_data, }, + { .compatible = "ti,musb-dm816", + .data = &am33xx_driver_data, }, + { }, +}; +MODULE_DEVICE_TABLE(of, musb_dsps_of_match); + +#ifdef CONFIG_PM_SLEEP +static int dsps_suspend(struct device *dev) +{ + struct dsps_glue *glue = dev_get_drvdata(dev); + const struct dsps_musb_wrapper *wrp = glue->wrp; + struct musb *musb = platform_get_drvdata(glue->musb); + void __iomem *mbase; + int ret; + + if (!musb) + /* This can happen if the musb device is in -EPROBE_DEFER */ + return 0; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put_noidle(dev); + return ret; + } + + timer_delete_sync(&musb->dev_timer); + + mbase = musb->ctrl_base; + glue->context.control = musb_readl(mbase, wrp->control); + glue->context.epintr = musb_readl(mbase, wrp->epintr_set); + glue->context.coreintr = musb_readl(mbase, wrp->coreintr_set); + glue->context.phy_utmi = musb_readl(mbase, wrp->phy_utmi); + glue->context.mode = musb_readl(mbase, wrp->mode); + glue->context.tx_mode = musb_readl(mbase, wrp->tx_mode); + glue->context.rx_mode = musb_readl(mbase, wrp->rx_mode); + + dsps_dma_controller_suspend(glue); + + return 0; +} + +static int dsps_resume(struct device *dev) +{ + struct dsps_glue *glue = dev_get_drvdata(dev); + const struct dsps_musb_wrapper *wrp = glue->wrp; + struct musb *musb = platform_get_drvdata(glue->musb); + void __iomem *mbase; + + if (!musb) + return 0; + + dsps_dma_controller_resume(glue); + + mbase = musb->ctrl_base; + musb_writel(mbase, wrp->control, glue->context.control); + musb_writel(mbase, wrp->epintr_set, glue->context.epintr); + musb_writel(mbase, wrp->coreintr_set, glue->context.coreintr); + musb_writel(mbase, wrp->phy_utmi, glue->context.phy_utmi); + musb_writel(mbase, wrp->mode, glue->context.mode); + musb_writel(mbase, wrp->tx_mode, glue->context.tx_mode); + musb_writel(mbase, wrp->rx_mode, glue->context.rx_mode); + if (musb->xceiv->otg->state == OTG_STATE_B_IDLE && + musb->port_mode == MUSB_OTG) + dsps_mod_timer(glue, -1); + + pm_runtime_put(dev); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(dsps_pm_ops, dsps_suspend, dsps_resume); + +static struct platform_driver dsps_usbss_driver = { + .probe = dsps_probe, + .remove = dsps_remove, + .driver = { + .name = "musb-dsps", + .pm = &dsps_pm_ops, + .of_match_table = musb_dsps_of_match, + }, +}; + +MODULE_DESCRIPTION("TI DSPS MUSB Glue Layer"); +MODULE_AUTHOR("Ravi B "); +MODULE_AUTHOR("Ajay Kumar Gupta "); +MODULE_LICENSE("GPL v2"); + +module_platform_driver(dsps_usbss_driver); diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c new file mode 100644 index 000000000..016d3f3fc --- /dev/null +++ b/drivers/usb/musb/musb_gadget.c @@ -0,0 +1,2095 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "musb_core.h" +#include "musb_trace.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) { + dma_addr_t dma_addr; + int ret; + + dma_addr = dma_map_single( + musb->controller, + request->request.buf, + request->request.length, + request->tx + ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + ret = dma_mapping_error(musb->controller, dma_addr); + if (ret) + return; + + request->request.dma = dma_addr; + 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); + + trace_musb_req_gb(req); + usb_gadget_giveback_request(&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); + musb_dbg(musb, "%s: abort DMA --> %d", 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) { + musb_dbg(musb, "ep:%s disabled - ignore request", + 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) { + musb_dbg(musb, "dma pending..."); + 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) { + musb_dbg(musb, "%s old packet still ready , txcsr %03x", + musb_ep->end_point.name, csr); + return; + } + + if (csr & MUSB_TXCSR_P_SENDSTALL) { + musb_dbg(musb, "%s stalling, txcsr %03x", + musb_ep->end_point.name, csr); + return; + } + + musb_dbg(musb, "hw_ep%d, maxpacket %d, fifo count %d, txcsr %03x", + 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 (musb_dma_inventra(musb) || musb_dma_ux500(musb)) { + 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 || + can_bulk_split(musb, + musb_ep->type)) + csr |= MUSB_TXCSR_AUTOSET; + } + csr &= ~MUSB_TXCSR_P_UNDERRUN; + + musb_writew(epio, MUSB_TXCSR, csr); + } + } + + if (is_cppi_enabled(musb)) { + /* 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 */ + } + } else if (tusb_dma_omap(musb)) + use_dma = use_dma && c->channel_program( + musb_ep->dma, musb_ep->packet_sz, + request->zero, + request->dma + request->actual, + request_size); + } +#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... */ + musb_dbg(musb, "%s TX/IN %s len %d/%d, txcsr %04x, fifo %d/%d", + 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); + musb_dbg(musb, "<== %s, txcsr %04x", 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? + */ + musb_dbg(musb, "%s dma still busy?", musb_ep->end_point.name); + return; + } + + if (req) { + + trace_musb_req_tx(req); + + if (dma && (csr & MUSB_TXCSR_DMAENAB)) { + 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; + musb_dbg(musb, "TXCSR%d %04x, DMA off, len %zu, req %p", + 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)) { + + /* + * On DMA completion, FIFO may not be + * available yet... + */ + if (csr & MUSB_TXCSR_TXPKTRDY) + return; + + 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) { + musb_dbg(musb, "%s idle now", + 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) { + musb_dbg(musb, "ep:%s disabled - ignore request", + 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) { + musb_dbg(musb, "DMA pending..."); + return; + } + + if (csr & MUSB_RXCSR_P_SENDSTALL) { + musb_dbg(musb, "%s stalling, RXCSR %04x", + musb_ep->end_point.name, csr); + return; + } + + if (is_cppi_enabled(musb) && 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) { + if (!is_buffer_mapped(req)) + goto buffer_aint_mapped; + + if (musb_dma_inventra(musb)) { + 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 transfer + * 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; + } + + if ((musb_dma_ux500(musb)) && + (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; + } + + len = request->length - request->actual; + musb_dbg(musb, "%s OUT/RX pio fifo %d/%d, maxpacket %d", + musb_ep->end_point.name, + fifo_count, len, + musb_ep->packet_sz); + + fifo_count = min_t(unsigned, len, fifo_count); + + if (tusb_dma_omap(musb)) { + 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; + } + + /* + * Unmap the dma buffer back to cpu if dma channel + * programming fails. This buffer is mapped if the + * channel allocation is successful + */ + 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); + +buffer_aint_mapped: + fifo_count = min_t(unsigned int, + request->length - request->actual, + (unsigned int)fifo_count); + 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; + + trace_musb_req_rx(req); + request = &req->request; + + csr = musb_readw(epio, MUSB_RXCSR); + dma = is_dma_capable() ? musb_ep->dma : NULL; + + musb_dbg(musb, "<== %s, rxcsr %04x%s %p", 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); + + musb_dbg(musb, "%s iso overrun on %p", musb_ep->name, request); + if (request->status == -EINPROGRESS) + request->status = -EOVERFLOW; + } + if (csr & MUSB_RXCSR_INCOMPRX) { + /* REVISIT not necessarily an error */ + musb_dbg(musb, "%s, incomprx", musb_ep->end_point.name); + } + + if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) { + /* "should not happen"; likely RXPKTRDY pending for DMA */ + musb_dbg(musb, "%s busy, csr %04x", + 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; + +#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_mult(desc) - 1; + if (tmp) { + int ok; + + if (usb_endpoint_dir_in(desc)) + ok = musb->hb_iso_tx; + else + ok = musb->hb_iso_rx; + + if (!ok) { + musb_dbg(musb, "no support for high bandwidth ISO"); + goto fail; + } + musb_ep->hb_mult = tmp; + } else { + musb_ep->hb_mult = 0; + } + + musb_ep->packet_sz = usb_endpoint_maxp(desc); + 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) { + musb_dbg(musb, "packet size beyond hardware FIFO size"); + 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 (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) { + musb_dbg(musb, "packet size beyond hardware FIFO size"); + 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. + */ + 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, + musb_ep_xfertype_string(musb_ep->type), + musb_ep->is_in ? "IN" : "OUT", + musb_ep->dma ? "dma, " : "", + musb_ep->packet_sz); + + schedule_delayed_work(&musb->irq_work, 0); + +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; + + 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); + } + + /* abort all pending DMA and requests */ + nuke(musb_ep, -ESHUTDOWN); + + musb_ep->desc = NULL; + musb_ep->end_point.desc = NULL; + + schedule_delayed_work(&musb->irq_work, 0); + + spin_unlock_irqrestore(&(musb->lock), flags); + + musb_dbg(musb, "%s", musb_ep->end_point.name); + + return 0; +} + +/* + * 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_request *request; + + request = kzalloc_obj(*request, gfp_flags); + if (!request) + return NULL; + + request->request.dma = DMA_ADDR_INVALID; + request->epnum = musb_ep->current_epnum; + request->ep = musb_ep; + + trace_musb_req_alloc(request); + return &request->request; +} + +/* + * Free a request + * Reused by ep0 code. + */ +void musb_free_request(struct usb_ep *ep, struct usb_request *req) +{ + struct musb_request *request = to_musb_request(req); + + trace_musb_req_free(request); + kfree(request); +} + +/* + * Context: controller locked, IRQs blocked. + */ +void musb_ep_restart(struct musb *musb, struct musb_request *req) +{ + u16 csr; + void __iomem *epio = req->ep->hw_ep->regs; + + trace_musb_req_start(req); + musb_ep_select(musb->mregs, req->epnum); + if (req->tx) { + txstate(musb, req); + } 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); + } +} + +static int musb_ep_restart_resume_work(struct musb *musb, void *data) +{ + struct musb_request *req = data; + + musb_ep_restart(musb, req); + + return 0; +} + +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; + 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; + + status = pm_runtime_get(musb->controller); + if ((status != -EINPROGRESS) && status < 0) { + dev_err(musb->controller, + "pm runtime get failed in %s\n", + __func__); + pm_runtime_put_noidle(musb->controller); + + return status; + } + status = 0; + + trace_musb_req_enq(request); + + /* 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) { + musb_dbg(musb, "req %p queued to %s while ep %s", + req, ep->name, "disabled"); + status = -ESHUTDOWN; + unmap_dma_buffer(request, musb); + goto unlock; + } + + /* 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) { + status = musb_queue_resume_work(musb, + musb_ep_restart_resume_work, + request); + if (status < 0) { + dev_err(musb->controller, "%s resume work: %i\n", + __func__, status); + list_del(&request->list); + } + } + +unlock: + spin_unlock_irqrestore(&musb->lock, lockflags); + pm_runtime_put_autosuspend(musb->controller); + + 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 || req->ep != musb_ep) + return -EINVAL; + + trace_musb_req_deq(req); + + spin_lock_irqsave(&musb->lock, flags); + + list_for_each_entry(r, &musb_ep->req_list, list) { + if (r == req) + break; + } + if (r != req) { + dev_err(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 endpoint 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) { + musb_dbg(musb, "request in progress, cannot halt %s", + 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) { + musb_dbg(musb, "FIFO busy, cannot halt %s", + ep->name); + status = -EAGAIN; + goto done; + } + } + } else + musb_ep->wedged = 0; + + /* set/clear the stall and toggle bits */ + musb_dbg(musb, "%s: %s stall", 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) { + musb_dbg(musb, "restarting the request"); + 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_get_state(musb)) { + 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); + musb_dbg(musb, "Sending SRP: devctl: %02x", 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; + } + + if (musb->xceiv) { + 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: + musb_dbg(musb, "Unhandled wake: %s", + musb_otg_state_string(musb)); + goto done; + } + + status = 0; + + power = musb_readb(mregs, MUSB_POWER); + power |= MUSB_POWER_RESUME; + musb_writeb(mregs, MUSB_POWER, power); + musb_dbg(musb, "issue wakeup"); + + /* 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) +{ + gadget->is_selfpowered = !!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 */ + + musb_dbg(musb, "gadget D+ pullup %s", + str_on_off(is_on)); + musb_writeb(musb->mregs, MUSB_POWER, power); +} + +#if 0 +static int musb_gadget_vbus_session(struct usb_gadget *gadget, int is_active) +{ + musb_dbg(musb, "<= %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); + + return usb_phy_set_power(musb->xceiv, mA); +} + +static void musb_gadget_work(struct work_struct *work) +{ + struct musb *musb; + unsigned long flags; + + musb = container_of(work, struct musb, gadget_work.work); + pm_runtime_get_sync(musb->controller); + spin_lock_irqsave(&musb->lock, flags); + musb_pullup(musb, musb->softconnect); + spin_unlock_irqrestore(&musb->lock, flags); + pm_runtime_put_autosuspend(musb->controller); +} + +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; + + /* 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; + schedule_delayed_work(&musb->gadget_work, 0); + } + spin_unlock_irqrestore(&musb->lock, flags); + + 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); + +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) { + usb_ep_set_maxpacket_limit(&ep->end_point, 64); + ep->end_point.caps.type_control = true; + ep->end_point.ops = &musb_g_ep0_ops; + musb->g.ep0 = &ep->end_point; + } else { + if (is_in) + usb_ep_set_maxpacket_limit(&ep->end_point, hw_ep->max_packet_sz_tx); + else + usb_ep_set_maxpacket_limit(&ep->end_point, hw_ep->max_packet_sz_rx); + ep->end_point.caps.type_iso = true; + ep->end_point.caps.type_bulk = true; + ep->end_point.caps.type_int = true; + ep->end_point.ops = &musb_ep_ops; + list_add_tail(&ep->end_point.ep_list, &musb->g.ep_list); + } + + if (!epnum || hw_ep->is_shared_fifo) { + ep->end_point.caps.dir_in = true; + ep->end_point.caps.dir_out = true; + } else if (is_in) + ep->end_point.caps.dir_in = true; + else + ep->end_point.caps.dir_out = true; +} + +/* + * 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; + + /* 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); + } else { + if (hw_ep->max_packet_sz_tx) { + init_peripheral_ep(musb, &hw_ep->ep_in, + epnum, 1); + } + if (hw_ep->max_packet_sz_rx) { + init_peripheral_ep(musb, &hw_ep->ep_out, + epnum, 0); + } + } + } +} + +/* 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; + + MUSB_DEV_MODE(musb); + musb_set_state(musb, OTG_STATE_B_IDLE); + + /* this "gadget" abstracts/virtualizes the controller */ + musb->g.name = musb_driver_name; + /* don't support otg protocols */ + musb->g.is_otg = 0; + INIT_DELAYED_WORK(&musb->gadget_work, musb_gadget_work); + 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) +{ + if (musb->port_mode == MUSB_HOST) + return; + + cancel_delayed_work_sync(&musb->gadget_work); + 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); + unsigned long flags; + int retval = 0; + + if (driver->max_speed < USB_SPEED_HIGH) { + retval = -EINVAL; + goto err; + } + + pm_runtime_get_sync(musb->controller); + + musb->softconnect = 0; + musb->gadget_driver = driver; + + spin_lock_irqsave(&musb->lock, flags); + musb->is_active = 1; + + if (musb->xceiv) + otg_set_peripheral(musb->xceiv->otg, &musb->g); + else + phy_set_mode(musb->phy, PHY_MODE_USB_DEVICE); + + musb_set_state(musb, OTG_STATE_B_IDLE); + spin_unlock_irqrestore(&musb->lock, flags); + + musb_start(musb); + + /* REVISIT: funcall to other code, which also + * handles power budgeting ... this way also + * ensures HdrcStart is indirectly called. + */ + if (musb->xceiv && musb->xceiv->last_event == USB_EVENT_ID) + musb_platform_set_vbus(musb, 1); + + pm_runtime_put_autosuspend(musb->controller); + + return 0; + +err: + return retval; +} + +/* + * 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 musb *musb = gadget_to_musb(g); + unsigned long flags; + + 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_set_state(musb, OTG_STATE_UNDEFINED); + musb_stop(musb); + + if (musb->xceiv) + otg_set_peripheral(musb->xceiv->otg, NULL); + else + phy_set_mode(musb->phy, PHY_MODE_INVALID); + + musb->is_active = 0; + musb->gadget_driver = NULL; + musb_platform_try_idle(musb, 0); + spin_unlock_irqrestore(&musb->lock, flags); + + /* + * FIXME we need to be able to register another + * gadget driver here and have everything work; + * that currently misbehaves. + */ + usb_gadget_set_state(g, USB_STATE_NOTATTACHED); + + /* Force check of devctl register for PM runtime */ + pm_runtime_put_autosuspend(musb->controller); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* lifecycle operations called through plat_uds.c */ + +void musb_g_resume(struct musb *musb) +{ + musb->is_suspended = 0; + switch (musb_get_state(musb)) { + 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", + musb_otg_state_string(musb)); + } +} + +/* called when SOF packets stop for 3+ msec */ +void musb_g_suspend(struct musb *musb) +{ + u8 devctl; + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + musb_dbg(musb, "musb_g_suspend: devctl %02x", devctl); + + switch (musb_get_state(musb)) { + case OTG_STATE_B_IDLE: + if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS) + musb_set_state(musb, 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)", + musb_otg_state_string(musb)); + } +} + +/* 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); + + musb_dbg(musb, "musb_g_disconnect: devctl %02x", 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_get_state(musb)) { + default: + musb_dbg(musb, "Unhandled disconnect %s, setting a_idle", + musb_otg_state_string(musb)); + musb_set_state(musb, OTG_STATE_A_IDLE); + MUSB_HST_MODE(musb); + break; + case OTG_STATE_A_PERIPHERAL: + musb_set_state(musb, 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_set_state(musb, OTG_STATE_B_IDLE); + usb_gadget_set_state(&musb->g, USB_STATE_NOTATTACHED); + 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; + + musb_dbg(musb, "<== %s driver '%s'", + (devctl & MUSB_DEVCTL_BDEVICE) + ? "B-Device" : "A-Device", + musb->gadget_driver + ? musb->gadget_driver->driver.name + : NULL + ); + + /* report reset, if we didn't already (flushing EP state) */ + if (musb->gadget_driver && musb->g.speed != USB_SPEED_UNKNOWN) { + spin_unlock(&musb->lock); + usb_gadget_udc_reset(&musb->g, musb->gadget_driver); + spin_lock(&musb->lock); + } + + /* 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; + musb->g.quirk_zlp_not_supp = 1; + + /* Normal reset, as B-Device; + * or else after HNP, as A-Device + */ + if (!musb->g.is_otg) { + /* USB device controllers that are not OTG compatible + * may not have DEVCTL register in silicon. + * In that case, do not rely on devctl for setting + * peripheral mode. + */ + musb_set_state(musb, OTG_STATE_B_PERIPHERAL); + musb->g.is_a_peripheral = 0; + } else if (devctl & MUSB_DEVCTL_BDEVICE) { + musb_set_state(musb, OTG_STATE_B_PERIPHERAL); + musb->g.is_a_peripheral = 0; + } else { + musb_set_state(musb, 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 000000000..f49f25b3b --- /dev/null +++ b/drivers/usb/musb/musb_gadget.h @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MUSB OTG driver peripheral defines + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + */ + +#ifndef __MUSB_GADGET_H +#define __MUSB_GADGET_H + +#include + +#if IS_ENABLED(CONFIG_USB_MUSB_GADGET) || IS_ENABLED(CONFIG_USB_MUSB_DUAL_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 *); +extern void musb_gadget_cleanup(struct musb *); +extern int musb_gadget_setup(struct musb *); + +#else +static inline irqreturn_t musb_g_ep0_irq(struct musb *musb) +{ + return 0; +} + +static inline void musb_g_tx(struct musb *musb, u8 epnum) {} +static inline void musb_g_rx(struct musb *musb, u8 epnum) {} +static inline void musb_g_reset(struct musb *musb) {} +static inline void musb_g_suspend(struct musb *musb) {} +static inline void musb_g_resume(struct musb *musb) {} +static inline void musb_g_wakeup(struct musb *musb) {} +static inline void musb_g_disconnect(struct musb *musb) {} +static inline void musb_gadget_cleanup(struct musb *musb) {} +static inline int musb_gadget_setup(struct musb *musb) +{ + return 0; +} +#endif + +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; +}; + +#define to_musb_request(r) container_of((r), struct musb_request, request) + +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; +}; + +#define to_musb_ep(ep) container_of((ep), struct musb_ep, end_point) + +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 const struct usb_ep_ops musb_g_ep0_ops; + +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 000000000..f0786f8fb --- /dev/null +++ b/drivers/usb/musb/musb_gadget_ep0.c @@ -0,0 +1,1058 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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. + */ + +#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->g.is_selfpowered << 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; + epnum &= 0x0f; + if (epnum >= MUSB_C_NUM_EPS) { + handled = -EINVAL; + break; + } + + if (is_in) + ep = &musb->endpoints[epnum].ep_in; + else + ep = &musb->endpoints[epnum].ep_out; + regs = musb->endpoints[epnum].regs; + + if (!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; + + musb_dbg(musb, "HNP: Setting HR"); + 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) { + musb_dbg(musb, "restarting the request"); + 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 USB_TEST_J: + pr_debug("USB_TEST_J\n"); + musb->test_mode_nr = + MUSB_TEST_J; + break; + case USB_TEST_K: + pr_debug("USB_TEST_K\n"); + musb->test_mode_nr = + MUSB_TEST_K; + break; + case USB_TEST_SE0_NAK: + pr_debug("USB_TEST_SE0_NAK\n"); + musb->test_mode_nr = + MUSB_TEST_SE0_NAK; + break; + case USB_TEST_PACKET: + pr_debug("USB_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); */ + musb_dbg(musb, "odd; csr0 %04x", musb_readw(regs, MUSB_CSR0)); + return; + } + + request = &req->request; + + /* load the data */ + fifo_src = (u8 *) request->buf + request->actual; + fifo_count = min_t(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. + */ + musb_dbg(musb, "SETUP req%02x.%02x v%04x i%04x l%d", + 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); + + musb_dbg(musb, "csr %04x, count %d, ep0stage %s", + 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) { + musb_dbg(musb, "entering TESTMODE"); + + 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; + } + + musb_dbg(musb, "handled %d, csr %04x, ep0stage %s", + 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: + musb_dbg(musb, "stall (%d)", 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: + musb_dbg(musb, "ep0 request queued in state %d", + musb->ep0_state); + status = -EINVAL; + goto cleanup; + } + + /* add request to the list */ + list_add_tail(&req->list, &ep->req_list); + + musb_dbg(musb, "queue to %s (%s), length=%d", + 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: + musb_dbg(musb, "ep0 can't halt in state %d", 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 000000000..31b061248 --- /dev/null +++ b/drivers/usb/musb/musb_host.c @@ -0,0 +1,2777 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "musb_core.h" +#include "musb_host.h" +#include "musb_trace.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. + */ + +struct musb *hcd_to_musb(struct usb_hcd *hcd) +{ + return *(struct musb **) hcd->hcd_priv; +} + + +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; + int retries = 1000; + + csr = musb_readw(epio, MUSB_TXCSR); + while (csr & MUSB_TXCSR_FIFONOTEMPTY) { + csr |= MUSB_TXCSR_FLUSHFIFO | MUSB_TXCSR_TXPKTRDY; + musb_writew(epio, MUSB_TXCSR, csr); + csr = musb_readw(epio, MUSB_TXCSR); + + /* + * FIXME: sometimes the tx fifo flush failed, it has been + * observed during device disconnect on AM335x. + * + * To reproduce the issue, ensure tx urb(s) are queued when + * unplug the usb device which is connected to AM335x usb + * host port. + * + * I found using a usb-ethernet device and running iperf + * (client on AM335x) has very high chance to trigger it. + * + * Better to turn on musb_dbg() in musb_cleanup_urb() with + * CPPI enabled to see the issue when aborting the tx channel. + */ + if (dev_WARN_ONCE(musb->controller, 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(ep->musb)) + 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) +{ + 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; + 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; + } + + trace_musb_urb_start(musb, urb); + + /* 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: + musb_dbg(musb, "check whether there's still time for periodic Tx"); + /* FIXME this doesn't implement that scheduling policy ... + * or handle framecounter wrapping + */ + if (1) { /* Always assume URB_ISO_ASAP */ + /* 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 */ + musb_dbg(musb, "SOF for %d", epnum); +#if 1 /* ifndef CONFIG_ARCH_DAVINCI */ + musb_writeb(mbase, MUSB_INTRUSBE, 0xff); +#endif + } + break; + default: +start: + musb_dbg(musb, "Start TX%d %s", epnum, + hw_ep->tx_channel ? "dma" : "pio"); + + if (!hw_ep->tx_channel) + musb_h_tx_start(hw_ep); + else if (is_cppi_enabled(musb) || tusb_dma_omap(musb)) + 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) +{ + trace_musb_urb_gb(musb, urb); + + usb_hcd_unlink_urb_from_ep(musb->hcd, urb); + spin_unlock(&musb->lock); + usb_hcd_giveback_urb(musb->hcd, urb, status); + spin_lock(&musb->lock); +} + +/* + * 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; + u16 toggle; + + 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: + toggle = musb->io.get_toggle(qh, !is_in); + usb_settoggle(urb->dev, qh->epnum, !is_in, toggle ? 1 : 0); + 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; + + /* + * musb->lock had been unlocked in musb_giveback, so qh may + * be freed, need to get it again + */ + qh = musb_ep_get_qh(hw_ep, is_in); + + /* reclaim resources (and bandwidth) ASAP; deschedule it, and + * invalidate qh as soon as list_empty(&hep->urb_list) + */ + if (qh && 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; + } + fallthrough; + + 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) { + musb_dbg(musb, "... next ep%d %cX urb %p", + 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); + musb_dbg(musb, "RX%d count %d, buffer %p len %d/%d", 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++; + } + musb_dbg(musb, "OVERFLOW %d into %d", 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; + musb_dbg(musb, "OVERFLOW %d into %d", 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, u8 epnum) +{ + struct musb_hw_ep *ep = musb->endpoints + epnum; + 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 */ + } + 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(musb, epnum, qh->addr_reg); + musb_write_rxhubaddr(musb, epnum, qh->h_addr_reg); + musb_write_rxhubport(musb, epnum, 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. + */ + musb_writew(ep->regs, MUSB_RXMAXP, + qh->maxpacket | ((qh->hb_mult - 1) << 11)); + + ep->rx_reinit = 0; +} + +static void musb_tx_dma_set_mode_mentor(struct musb_hw_ep *hw_ep, + struct musb_qh *qh, + u32 *length, u8 *mode) +{ + struct dma_channel *channel = hw_ep->tx_channel; + void __iomem *epio = hw_ep->regs; + u16 pkt_size = qh->maxpacket; + u16 csr; + + 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); +} + +static void musb_tx_dma_set_mode_cppi_tusb(struct musb_hw_ep *hw_ep, + struct urb *urb, + u8 *mode) +{ + struct dma_channel *channel = hw_ep->tx_channel; + + 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; +} + +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; + u16 pkt_size = qh->maxpacket; + u8 mode; + + if (musb_dma_inventra(hw_ep->musb) || musb_dma_ux500(hw_ep->musb)) + musb_tx_dma_set_mode_mentor(hw_ep, qh, + &length, &mode); + else if (is_cppi_enabled(hw_ep->musb) || tusb_dma_omap(hw_ep->musb)) + musb_tx_dma_set_mode_cppi_tusb(hw_ep, urb, &mode); + else + return false; + + 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)) { + void __iomem *epio = hw_ep->regs; + u16 csr; + + 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; + + musb_dbg(musb, "%s hw%d urb %p spd%d dev%d ep%d%s " + "h_addr%02x h_port%02x bytes %d", + 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) + csr |= musb->io.set_toggle(qh, is_out, urb); + + 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(musb, epnum, qh->addr_reg); + musb_write_txhubaddr(musb, epnum, qh->h_addr_reg); + musb_write_txhubport(musb, 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 (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_t(u32, hw_ep->max_packet_sz_tx, len); + else + load_count = min_t(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 = 0; + + if (hw_ep->rx_reinit) { + musb_rx_reinit(musb, qh, epnum); + csr |= musb->io.set_toggle(qh, is_out, urb); + + 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(musb) || tusb_dma_omap(musb)) && 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; + musb_dbg(musb, "RXCSR%d := %04x", 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; + u16 toggle; + + musb_ep_select(mbase, ep->epnum); + if (is_in) { + dma = is_dma_capable() ? ep->rx_channel : NULL; + + /* + * Need to stop the transaction by clearing REQPKT first + * then the NAK Timeout bit ref MUSBMHDRC USB 2.0 HIGH-SPEED + * DUAL-ROLE CONTROLLER Programmer's Guide, section 9.2.2 + */ + rx_csr = musb_readw(epio, MUSB_RXCSR); + rx_csr |= MUSB_RXCSR_H_WZC_BITS; + rx_csr &= ~MUSB_RXCSR_H_REQPKT; + musb_writew(epio, MUSB_RXCSR, rx_csr); + 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; + } + toggle = musb->io.get_toggle(cur_qh, !is_in); + usb_settoggle(urb->dev, cur_qh->epnum, !is_in, toggle ? 1 : 0); + + 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; + } + + if (next_qh) + 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) { + musb_dbg(musb, "start no-DATA"); + break; + } else if (request->bRequestType & USB_DIR_IN) { + musb_dbg(musb, "start IN-DATA"); + musb->ep0_stage = MUSB_EP0_IN; + more = true; + break; + } else { + musb_dbg(musb, "start OUT-DATA"); + 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); + musb_dbg(musb, "Sending %d byte%s to ep0 fifo %p", + fifo_count, + str_plural(fifo_count), + 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; + + musb_dbg(musb, "<== csr0 %04x, qh %p, count %d, urb %p, stage %d", + 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) { + musb_dbg(musb, "STALLING ENDPOINT"); + status = -EPIPE; + + } else if (csr & MUSB_CSR0_H_ERROR) { + musb_dbg(musb, "no response, csr0 %04x", csr); + status = -EPROTO; + + } else if (csr & MUSB_CSR0_H_NAKTIMEOUT) { + musb_dbg(musb, "control NAK timeout"); + + /* 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) { + musb_dbg(musb, "aborting"); + 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; + + /* disable ping token in status phase */ + csr |= MUSB_CSR0_H_DIS_PING; + + /* flag status stage */ + musb->ep0_stage = MUSB_EP0_STATUS; + + musb_dbg(musb, "ep0 STATUS, csr %04x", 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) { + musb_dbg(musb, "extra TX%d ready, csr %04x", epnum, tx_csr); + return; + } + + pipe = urb->pipe; + dma = is_dma_capable() ? hw_ep->tx_channel : NULL; + trace_musb_urb_tx(musb, urb); + musb_dbg(musb, "OUT/TX%d end, csr %04x%s", epnum, tx_csr, + dma ? ", dma" : ""); + + /* check for errors */ + if (tx_csr & MUSB_TXCSR_H_RXSTALL) { + /* dma was disabled, fifo flushed */ + musb_dbg(musb, "TX end %d stall", epnum); + + /* stall; record URB status */ + status = -EPIPE; + + } else if (tx_csr & MUSB_TXCSR_H_ERROR) { + /* (NON-ISO) dma was disabled, fifo flushed */ + musb_dbg(musb, "TX 3strikes on ep=%d", 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)) { + musb_dbg(musb, "NAK timeout on TX%d ep", epnum); + musb_bulk_nak_timeout(musb, hw_ep, 0); + } else { + musb_dbg(musb, "TX ep%d device not responding", 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; + 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) { + musb_dbg(musb, "extra TX%d ready, csr %04x", 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)) { + musb_dbg(musb, + "DMA complete but FIFO not empty, CSR %04x", + 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(musb) || tusb_dma_omap(musb)) + musb_h_tx_dma_start(hw_ep); + return; + } + } else if (tx_csr & MUSB_TXCSR_DMAENAB) { + musb_dbg(musb, "not complete, but DMA enabled?"); + 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->hcd, urb); + + /* + * We need to map sg if the transfer_buffer is + * NULL. + */ + if (!urb->transfer_buffer) { + /* 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; + } + length = min_t(u32, length, qh->sg_miter.length); + musb_write_fifo(hw_ep, length, qh->sg_miter.addr); + 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; + + musb_ep_select(mbase, epnum); + musb_writew(epio, MUSB_TXCSR, + MUSB_TXCSR_H_WZC_BITS | MUSB_TXCSR_TXPKTRDY); +} + +#ifdef CONFIG_USB_TI_CPPI41_DMA +/* Seems to set up ISO for cppi41 and not advance len. See commit c57c41d */ +static int musb_rx_dma_iso_cppi41(struct dma_controller *dma, + struct musb_hw_ep *hw_ep, + struct musb_qh *qh, + struct urb *urb, + size_t len) +{ + struct dma_channel *channel = hw_ep->rx_channel; + void __iomem *epio = hw_ep->regs; + dma_addr_t *buf; + u32 length; + u16 val; + + buf = (void *)urb->iso_frame_desc[qh->iso_idx].offset + + (u32)urb->transfer_dma; + + length = urb->iso_frame_desc[qh->iso_idx].length; + + val = musb_readw(epio, MUSB_RXCSR); + val |= MUSB_RXCSR_DMAENAB; + musb_writew(hw_ep->regs, MUSB_RXCSR, val); + + return dma->channel_program(channel, qh->maxpacket, 0, + (u32)buf, length); +} +#else +static inline int musb_rx_dma_iso_cppi41(struct dma_controller *dma, + struct musb_hw_ep *hw_ep, + struct musb_qh *qh, + struct urb *urb, + size_t len) +{ + return false; +} +#endif + +#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_UX500_DMA) || \ + defined(CONFIG_USB_TI_CPPI41_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. + */ +static int musb_rx_dma_inventra_cppi41(struct dma_controller *dma, + struct musb_hw_ep *hw_ep, + struct musb_qh *qh, + struct urb *urb, + size_t len) +{ + struct dma_channel *channel = hw_ep->rx_channel; + void __iomem *epio = hw_ep->regs; + u16 val; + int pipe; + bool done; + + pipe = urb->pipe; + + if (usb_pipeisoc(pipe)) { + struct usb_iso_packet_descriptor *d; + + d = urb->iso_frame_desc + qh->iso_idx; + d->actual_length = 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 { + /* REVISIT: Why ignore return value here? */ + if (musb_dma_cppi41(hw_ep->musb)) + done = musb_rx_dma_iso_cppi41(dma, hw_ep, qh, + urb, len); + done = false; + } + + } else { + /* done if urb buffer is full or short packet is recd */ + done = (urb->actual_length + len >= + urb->transfer_buffer_length + || channel->actual_len < qh->maxpacket + || channel->rx_packet_done); + } + + /* send IN token for next packet, without AUTOREQ */ + if (!done) { + val = musb_readw(epio, MUSB_RXCSR); + val |= MUSB_RXCSR_H_REQPKT; + musb_writew(epio, MUSB_RXCSR, MUSB_RXCSR_H_WZC_BITS | val); + } + + return done; +} + +/* 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! + */ +static int musb_rx_dma_in_inventra_cppi41(struct dma_controller *dma, + struct musb_hw_ep *hw_ep, + struct musb_qh *qh, + struct urb *urb, + size_t len, + u8 iso_err) +{ + struct musb *musb = hw_ep->musb; + void __iomem *epio = hw_ep->regs; + struct dma_channel *channel = hw_ep->rx_channel; + u16 rx_count, val; + int length, pipe, done; + dma_addr_t buf; + + rx_count = musb_readw(epio, MUSB_RXCOUNT); + pipe = urb->pipe; + + 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++; + } + musb_dbg(musb, "** OVERFLOW %d into %d", + 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; + } + + channel->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) + channel->desired_mode = 1; + if (rx_count < hw_ep->max_packet_sz_rx) { + length = rx_count; + channel->desired_mode = 0; + } else { + length = urb->transfer_buffer_length; + } +#endif + + /* See comments above on disadvantages of using mode 1 */ + val = musb_readw(epio, MUSB_RXCSR); + val &= ~MUSB_RXCSR_H_REQPKT; + + if (channel->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... + */ + done = dma->channel_program(channel, qh->maxpacket, + channel->desired_mode, + buf, length); + + if (!done) { + dma->channel_release(channel); + hw_ep->rx_channel = NULL; + channel = NULL; + val = musb_readw(epio, MUSB_RXCSR); + val &= ~(MUSB_RXCSR_DMAENAB + | MUSB_RXCSR_H_AUTOREQ + | MUSB_RXCSR_AUTOCLEAR); + musb_writew(epio, MUSB_RXCSR, val); + } + + return done; +} +#else +static inline int musb_rx_dma_inventra_cppi41(struct dma_controller *dma, + struct musb_hw_ep *hw_ep, + struct musb_qh *qh, + struct urb *urb, + size_t len) +{ + return false; +} + +static inline int musb_rx_dma_in_inventra_cppi41(struct dma_controller *dma, + struct musb_hw_ep *hw_ep, + struct musb_qh *qh, + struct urb *urb, + size_t len, + u8 iso_err) +{ + return false; +} +#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; + struct dma_controller *c = musb->dma_controller; + void __iomem *epio = hw_ep->regs; + struct musb_qh *qh = hw_ep->in_qh; + size_t xfer_len; + void __iomem *mbase = musb->mregs; + 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??) + */ + musb_dbg(musb, "BOGUS RX%d ready, csr %04x, count %d", + epnum, val, musb_readw(epio, MUSB_RXCOUNT)); + musb_h_flush_rxfifo(hw_ep, MUSB_RXCSR_CLRDATATOG); + return; + } + + trace_musb_urb_rx(musb, urb); + + /* check for errors, concurrent stall & unlink is not really + * handled yet! */ + if (rx_csr & MUSB_RXCSR_H_RXSTALL) { + musb_dbg(musb, "RX end %d STALL", epnum); + + /* stall; record URB status */ + status = -EPIPE; + + } else if (rx_csr & MUSB_RXCSR_H_ERROR) { + dev_err_ratelimited(musb->controller, + "ep%d RX three-strikes error\n", epnum); + + /* + * The three-strikes error could only happen when the USB + * device is not accessible, for example detached or powered + * off. So return the fatal error -ESHUTDOWN so hopefully the + * USB device drivers won't immediately resubmit the same URB. + */ + status = -ESHUTDOWN; + musb_writeb(epio, MUSB_RXINTERVAL, 0); + + rx_csr &= ~MUSB_RXCSR_H_ERROR; + musb_writew(epio, MUSB_RXCSR, rx_csr); + + /* + * Unplugging a USB-Ethernet adapter while it is busy can make + * the controller keep re-asserting the three-strikes error for + * this endpoint before the disconnect is processed. That floods + * the log and can wedge the host port until reboot. Drop the + * stale pending RX interrupt on platforms that support it (e.g. + * AM335x/DSPS) to break the storm; the transfer is still + * aborted below via the fault path. + */ + musb_platform_clear_ep_rxintr(musb, epnum); + + } else if (rx_csr & MUSB_RXCSR_DATAERROR) { + + if (USB_ENDPOINT_XFER_ISOC != qh->type) { + musb_dbg(musb, "RX end %d NAK timeout", 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 { + musb_dbg(musb, "RX end %d ISO data error", epnum); + /* packet error reported later */ + iso_err = true; + } + } else if (rx_csr & MUSB_RXCSR_INCOMPRX) { + musb_dbg(musb, "end %d high bandwidth incomplete ISO packet RX", + 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; + 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 */ + if (!musb_dma_inventra(musb) && !musb_dma_ux500(musb) && + (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; + musb->dma_controller->channel_abort(dma); + xfer_len = dma->actual_len; + done = true; + } + + musb_dbg(musb, "RXCSR%d %04x, reqpkt, len %zu%s", 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); + } + + 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); + + if (musb_dma_inventra(musb) || musb_dma_ux500(musb) || + musb_dma_cppi41(musb)) { + done = musb_rx_dma_inventra_cppi41(c, hw_ep, qh, urb, xfer_len); + musb_dbg(hw_ep->musb, + "ep %d dma %s, rxcsr %04x, rxcount %d", + epnum, done ? "off" : "reset", + musb_readw(epio, MUSB_RXCSR), + musb_readw(epio, MUSB_RXCOUNT)); + } else { + done = true; + } + + } 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 */ + if ((musb_dma_inventra(musb) || musb_dma_ux500(musb) || + musb_dma_cppi41(musb)) && dma) { + musb_dbg(hw_ep->musb, + "RX%d count %d, buffer 0x%llx len %d/%d", + epnum, musb_readw(epio, MUSB_RXCOUNT), + (unsigned long long) urb->transfer_dma + + urb->actual_length, + qh->offset, + urb->transfer_buffer_length); + + if (musb_rx_dma_in_inventra_cppi41(c, hw_ep, qh, urb, + xfer_len, iso_err)) + goto finish; + else + dev_err(musb->controller, "error: rx_dma failed\n"); + } + + if (!dma) { + unsigned int received_len; + + /* Unmap the buffer so that CPU can use it */ + usb_hcd_unmap_urb_for_dma(musb->hcd, 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); + } + musb_dbg(musb, "read %spacket", done ? "last " : ""); + } + } + +finish: + urb->actual_length += xfer_len; + qh->offset += xfer_len; + if (done) { + if (qh->use_sg) { + qh->use_sg = false; + urb->transfer_buffer = NULL; + } + + 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 = 0; + 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 does not 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) { + dev_err(musb->controller, + "%s hwep alloc failed for %dx%d\n", + musb_ep_xfertype_string(qh->type), + qh->hb_mult, qh->maxpacket); + return -ENOSPC; + } + + idle = 1; + qh->mux = 0; + hw_ep = musb->endpoints + best_end; + musb_dbg(musb, "qh %p periodic slot %d", 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; + + trace_musb_urb_enq(musb, urb); + + 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_obj(*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 = usb_endpoint_maxp_mult(epd); + 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) { + dev_err(musb->controller, + "high bandwidth %s (%dx%d) not supported\n", + musb_ep_xfertype_string(qh->type), + qh->hb_mult, qh->maxpacket & 0x7ff); + 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; + struct dma_channel *dma = NULL; + + musb_ep_select(regs, hw_end); + + if (is_dma_capable()) { + dma = is_in ? ep->rx_channel : ep->tx_channel; + if (dma) { + status = ep->musb->dma_controller->channel_abort(dma); + musb_dbg(musb, "abort %cX%d DMA for urb %p --> %d", + 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); + + /* clear the endpoint's irq status here to avoid bogus irqs */ + if (is_dma_capable() && dma) + musb_platform_clear_ep_rxintr(musb, ep->epnum); + } 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; + + trace_musb_urb_deq(musb, urb); + + 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)) { + musb_ep_set_qh(qh->hw_ep, is_in, NULL); + 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; + int ret; + + ret = musb_port_suspend(musb, true); + if (ret) + return ret; + + if (!is_host_active(musb)) + return 0; + + switch (musb_get_state(musb)) { + 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_set_state(musb, OTG_STATE_A_WAIT_BCON); + break; + default: + break; + } + + if (musb->is_active) { + WARNING("trying to suspend as %s while active\n", + musb_otg_state_string(musb)); + return -EBUSY; + } else + return 0; +} + +static int musb_bus_resume(struct usb_hcd *hcd) +{ + struct musb *musb = hcd_to_musb(hcd); + + if (musb->config && + musb->config->host_port_deassert_reset_at_resume) + musb_port_reset(musb, false); + + 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[]; +}; + +static void musb_free_temp_buffer(struct urb *urb) +{ + enum dma_data_direction dir; + struct musb_temp_buffer *temp; + size_t length; + + 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) { + if (usb_pipeisoc(urb->pipe)) + length = urb->transfer_buffer_length; + else + length = urb->actual_length; + + memcpy(temp->old_xfer_buffer, temp->data, 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 */ + +static 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_DMA | 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, */ +}; + +int musb_host_alloc(struct musb *musb) +{ + struct device *dev = musb->controller; + + /* usbcore sets dev->driver_data to hcd, and sometimes uses that... */ + musb->hcd = usb_create_hcd(&musb_hc_driver, dev, dev_name(dev)); + if (!musb->hcd) + return -EINVAL; + + *musb->hcd->hcd_priv = (unsigned long) musb; + musb->hcd->self.uses_pio_for_control = 1; + musb->hcd->uses_new_polling = 1; + musb->hcd->has_tt = 1; + + return 0; +} + +void musb_host_cleanup(struct musb *musb) +{ + if (musb->port_mode == MUSB_PERIPHERAL) + return; + usb_remove_hcd(musb->hcd); +} + +void musb_host_free(struct musb *musb) +{ + usb_put_hcd(musb->hcd); +} + +int musb_host_setup(struct musb *musb, int power_budget) +{ + int ret; + struct usb_hcd *hcd = musb->hcd; + + if (musb->port_mode == MUSB_HOST) { + MUSB_HST_MODE(musb); + musb_set_state(musb, OTG_STATE_A_IDLE); + } + + if (musb->xceiv) { + otg_set_host(musb->xceiv->otg, &hcd->self); + musb->xceiv->otg->host = &hcd->self; + } else { + phy_set_mode(musb->phy, PHY_MODE_USB_HOST); + } + + /* don't support otg protocols */ + hcd->self.otg_port = 0; + hcd->power_budget = 2 * (power_budget ? : 250); + hcd->skip_phy_initialization = 1; + + ret = usb_add_hcd(hcd, 0, 0); + if (ret < 0) + return ret; + + device_wakeup_enable(hcd->self.controller); + return 0; +} + +void musb_host_resume_root_hub(struct musb *musb) +{ + usb_hcd_resume_root_hub(musb->hcd); +} + +void musb_host_poke_root_hub(struct musb *musb) +{ + MUSB_HST_MODE(musb); + if (musb->hcd->status_urb) + usb_hcd_poll_rh_status(musb->hcd); + else + usb_hcd_resume_root_hub(musb->hcd); +} diff --git a/drivers/usb/musb/musb_host.h b/drivers/usb/musb/musb_host.h new file mode 100644 index 000000000..63298bd23 --- /dev/null +++ b/drivers/usb/musb/musb_host.h @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MUSB OTG driver host defines + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + */ + +#ifndef _MUSB_HOST_H +#define _MUSB_HOST_H + +#include + +/* 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); +} + + +#if IS_ENABLED(CONFIG_USB_MUSB_HOST) || IS_ENABLED(CONFIG_USB_MUSB_DUAL_ROLE) +extern struct musb *hcd_to_musb(struct usb_hcd *); +extern irqreturn_t musb_h_ep0_irq(struct musb *); +extern int musb_host_alloc(struct musb *); +extern int musb_host_setup(struct musb *, int); +extern void musb_host_cleanup(struct musb *); +extern void musb_host_tx(struct musb *, u8); +extern void musb_host_rx(struct musb *, u8); +extern void musb_root_disconnect(struct musb *musb); +extern void musb_host_free(struct musb *); +extern void musb_host_resume_root_hub(struct musb *musb); +extern void musb_host_poke_root_hub(struct musb *musb); +extern int musb_port_suspend(struct musb *musb, bool do_suspend); +extern void musb_port_reset(struct musb *musb, bool do_reset); +extern void musb_host_finish_resume(struct work_struct *work); +#else +static inline struct musb *hcd_to_musb(struct usb_hcd *hcd) +{ + return NULL; +} + +static inline irqreturn_t musb_h_ep0_irq(struct musb *musb) +{ + return 0; +} + +static inline int musb_host_alloc(struct musb *musb) +{ + return 0; +} + +static inline int musb_host_setup(struct musb *musb, int power_budget) +{ + return 0; +} + +static inline void musb_host_cleanup(struct musb *musb) {} +static inline void musb_host_free(struct musb *musb) {} +static inline void musb_host_tx(struct musb *musb, u8 epnum) {} +static inline void musb_host_rx(struct musb *musb, u8 epnum) {} +static inline void musb_root_disconnect(struct musb *musb) {} +static inline void musb_host_resume_root_hub(struct musb *musb) {} +static inline void musb_host_poke_root_hub(struct musb *musb) {} +static inline int musb_port_suspend(struct musb *musb, bool do_suspend) +{ + return 0; +} +static inline void musb_port_reset(struct musb *musb, bool do_reset) {} +static inline void musb_host_finish_resume(struct work_struct *work) {} +#endif + +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); + +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 000000000..12874d3b2 --- /dev/null +++ b/drivers/usb/musb/musb_io.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MUSB OTG driver register I/O + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + */ + +#ifndef __MUSB_LINUX_PLATFORM_ARCH_H__ +#define __MUSB_LINUX_PLATFORM_ARCH_H__ + +#include + +#define musb_ep_select(_mbase, _epnum) musb->io.ep_select((_mbase), (_epnum)) + +/** + * struct musb_io - IO functions for MUSB + * @ep_offset: platform specific function to get end point offset + * @ep_select: platform specific function to select end point + * @fifo_offset: platform specific function to get fifo offset + * @read_fifo: platform specific function to read fifo + * @write_fifo: platform specific function to write fifo + * @busctl_offset: platform specific function to get busctl offset + * @get_toggle: platform specific function to get toggle + * @set_toggle: platform specific function to set toggle + */ +struct musb_io { + u32 (*ep_offset)(u8 epnum, u16 offset); + void (*ep_select)(void __iomem *mbase, u8 epnum); + u32 (*fifo_offset)(u8 epnum); + void (*read_fifo)(struct musb_hw_ep *hw_ep, u16 len, u8 *buf); + void (*write_fifo)(struct musb_hw_ep *hw_ep, u16 len, const u8 *buf); + u32 (*busctl_offset)(u8 epnum, u16 offset); + u16 (*get_toggle)(struct musb_qh *qh, int is_out); + u16 (*set_toggle)(struct musb_qh *qh, int is_out, struct urb *urb); +}; + +/* Do not add new entries here, add them the struct musb_io instead */ +extern u8 (*musb_readb)(void __iomem *addr, u32 offset); +extern void (*musb_writeb)(void __iomem *addr, u32 offset, u8 data); +extern u8 (*musb_clearb)(void __iomem *addr, u32 offset); +extern u16 (*musb_readw)(void __iomem *addr, u32 offset); +extern void (*musb_writew)(void __iomem *addr, u32 offset, u16 data); +extern u16 (*musb_clearw)(void __iomem *addr, u32 offset); +extern u32 musb_readl(void __iomem *addr, u32 offset); +extern void musb_writel(void __iomem *addr, u32 offset, u32 data); + +#endif diff --git a/drivers/usb/musb/musb_regs.h b/drivers/usb/musb/musb_regs.h new file mode 100644 index 000000000..5fa110978 --- /dev/null +++ b/drivers/usb/musb/musb_regs.h @@ -0,0 +1,362 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MUSB OTG driver register defines + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2006 by Texas Instruments + * Copyright (C) 2006-2007 Nokia Corporation + */ + +#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 + +/* BABBLE_CTL */ +#define MUSB_BABBLE_FORCE_TXIDLE 0x80 +#define MUSB_BABBLE_SW_SESSION_CTRL 0x40 +#define MUSB_BABBLE_STUCK_J 0x20 +#define MUSB_BABBLE_RCV_DISABLE 0x04 + +/* 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 + + +/* + * 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 */ + +/* + * Additional Control Registers + */ + +#define MUSB_DEVCTL 0x60 /* 8 bit */ +#define MUSB_BABBLE_CTL 0x61 /* 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 */ + +#include "tusb6010.h" /* Needed "only" for TUSB_EP0_CONF */ + +#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 + +static inline u8 musb_read_configdata(void __iomem *mbase) +{ + musb_writeb(mbase, MUSB_INDEX, 0); + return musb_readb(mbase, 0x10 + MUSB_CONFIGDATA); +} + +static inline void musb_write_rxfunaddr(struct musb *musb, u8 epnum, + u8 qh_addr_reg) +{ + musb_writeb(musb->mregs, + musb->io.busctl_offset(epnum, MUSB_RXFUNCADDR), + qh_addr_reg); +} + +static inline void musb_write_rxhubaddr(struct musb *musb, u8 epnum, + u8 qh_h_addr_reg) +{ + musb_writeb(musb->mregs, musb->io.busctl_offset(epnum, MUSB_RXHUBADDR), + qh_h_addr_reg); +} + +static inline void musb_write_rxhubport(struct musb *musb, u8 epnum, + u8 qh_h_port_reg) +{ + musb_writeb(musb->mregs, musb->io.busctl_offset(epnum, MUSB_RXHUBPORT), + qh_h_port_reg); +} + +static inline void musb_write_txfunaddr(struct musb *musb, u8 epnum, + u8 qh_addr_reg) +{ + musb_writeb(musb->mregs, + musb->io.busctl_offset(epnum, MUSB_TXFUNCADDR), + qh_addr_reg); +} + +static inline void musb_write_txhubaddr(struct musb *musb, u8 epnum, + u8 qh_addr_reg) +{ + musb_writeb(musb->mregs, musb->io.busctl_offset(epnum, MUSB_TXHUBADDR), + qh_addr_reg); +} + +static inline void musb_write_txhubport(struct musb *musb, u8 epnum, + u8 qh_h_port_reg) +{ + musb_writeb(musb->mregs, musb->io.busctl_offset(epnum, MUSB_TXHUBPORT), + qh_h_port_reg); +} + +static inline u8 musb_read_rxfunaddr(struct musb *musb, u8 epnum) +{ + return musb_readb(musb->mregs, + musb->io.busctl_offset(epnum, MUSB_RXFUNCADDR)); +} + +static inline u8 musb_read_rxhubaddr(struct musb *musb, u8 epnum) +{ + return musb_readb(musb->mregs, + musb->io.busctl_offset(epnum, MUSB_RXHUBADDR)); +} + +static inline u8 musb_read_rxhubport(struct musb *musb, u8 epnum) +{ + return musb_readb(musb->mregs, + musb->io.busctl_offset(epnum, MUSB_RXHUBPORT)); +} + +static inline u8 musb_read_txfunaddr(struct musb *musb, u8 epnum) +{ + return musb_readb(musb->mregs, + musb->io.busctl_offset(epnum, MUSB_TXFUNCADDR)); +} + +static inline u8 musb_read_txhubaddr(struct musb *musb, u8 epnum) +{ + return musb_readb(musb->mregs, + musb->io.busctl_offset(epnum, MUSB_TXHUBADDR)); +} + +static inline u8 musb_read_txhubport(struct musb *musb, u8 epnum) +{ + return musb_readb(musb->mregs, + musb->io.busctl_offset(epnum, MUSB_TXHUBPORT)); +} + +#endif /* __MUSB_REGS_H__ */ diff --git a/drivers/usb/musb/musb_trace.c b/drivers/usb/musb/musb_trace.c new file mode 100644 index 000000000..476872adc --- /dev/null +++ b/drivers/usb/musb/musb_trace.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * musb_trace.c - MUSB Controller Trace Support + * + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Bin Liu + */ + +#define CREATE_TRACE_POINTS +#include "musb_trace.h" + +void musb_dbg(struct musb *musb, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + + trace_musb_log(musb, &vaf); + + va_end(args); +} diff --git a/drivers/usb/musb/musb_trace.h b/drivers/usb/musb/musb_trace.h new file mode 100644 index 000000000..726e6697d --- /dev/null +++ b/drivers/usb/musb/musb_trace.h @@ -0,0 +1,389 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * musb_trace.h - MUSB Controller Trace Support + * + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Bin Liu + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM musb + +#if !defined(__MUSB_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __MUSB_TRACE_H + +#include +#include +#include +#include "musb_core.h" +#ifdef CONFIG_USB_TI_CPPI41_DMA +#include "cppi_dma.h" +#endif + +#define MUSB_MSG_MAX 500 + +TRACE_EVENT(musb_log, + TP_PROTO(struct musb *musb, struct va_format *vaf), + TP_ARGS(musb, vaf), + TP_STRUCT__entry( + __string(name, dev_name(musb->controller)) + __vstring(msg, vaf->fmt, vaf->va) + ), + TP_fast_assign( + __assign_str(name); + __assign_vstr(msg, vaf->fmt, vaf->va); + ), + TP_printk("%s: %s", __get_str(name), __get_str(msg)) +); + +TRACE_EVENT(musb_state, + TP_PROTO(struct musb *musb, u8 devctl, const char *desc), + TP_ARGS(musb, devctl, desc), + TP_STRUCT__entry( + __string(name, dev_name(musb->controller)) + __field(u8, devctl) + __string(desc, desc) + ), + TP_fast_assign( + __assign_str(name); + __entry->devctl = devctl; + __assign_str(desc); + ), + TP_printk("%s: devctl: %02x %s", __get_str(name), __entry->devctl, + __get_str(desc)) +); + +DECLARE_EVENT_CLASS(musb_regb, + TP_PROTO(void *caller, const void __iomem *addr, + unsigned int offset, u8 data), + TP_ARGS(caller, addr, offset, data), + TP_STRUCT__entry( + __field(void *, caller) + __field(const void __iomem *, addr) + __field(unsigned int, offset) + __field(u8, data) + ), + TP_fast_assign( + __entry->caller = caller; + __entry->addr = addr; + __entry->offset = offset; + __entry->data = data; + ), + TP_printk("%pS: %p + %04x: %02x", + __entry->caller, __entry->addr, __entry->offset, __entry->data) +); + +DEFINE_EVENT(musb_regb, musb_readb, + TP_PROTO(void *caller, const void __iomem *addr, + unsigned int offset, u8 data), + TP_ARGS(caller, addr, offset, data) +); + +DEFINE_EVENT(musb_regb, musb_writeb, + TP_PROTO(void *caller, const void __iomem *addr, + unsigned int offset, u8 data), + TP_ARGS(caller, addr, offset, data) +); + +DECLARE_EVENT_CLASS(musb_regw, + TP_PROTO(void *caller, const void __iomem *addr, + unsigned int offset, u16 data), + TP_ARGS(caller, addr, offset, data), + TP_STRUCT__entry( + __field(void *, caller) + __field(const void __iomem *, addr) + __field(unsigned int, offset) + __field(u16, data) + ), + TP_fast_assign( + __entry->caller = caller; + __entry->addr = addr; + __entry->offset = offset; + __entry->data = data; + ), + TP_printk("%pS: %p + %04x: %04x", + __entry->caller, __entry->addr, __entry->offset, __entry->data) +); + +DEFINE_EVENT(musb_regw, musb_readw, + TP_PROTO(void *caller, const void __iomem *addr, + unsigned int offset, u16 data), + TP_ARGS(caller, addr, offset, data) +); + +DEFINE_EVENT(musb_regw, musb_writew, + TP_PROTO(void *caller, const void __iomem *addr, + unsigned int offset, u16 data), + TP_ARGS(caller, addr, offset, data) +); + +DECLARE_EVENT_CLASS(musb_regl, + TP_PROTO(void *caller, const void __iomem *addr, + unsigned int offset, u32 data), + TP_ARGS(caller, addr, offset, data), + TP_STRUCT__entry( + __field(void *, caller) + __field(const void __iomem *, addr) + __field(unsigned int, offset) + __field(u32, data) + ), + TP_fast_assign( + __entry->caller = caller; + __entry->addr = addr; + __entry->offset = offset; + __entry->data = data; + ), + TP_printk("%pS: %p + %04x: %08x", + __entry->caller, __entry->addr, __entry->offset, __entry->data) +); + +DEFINE_EVENT(musb_regl, musb_readl, + TP_PROTO(void *caller, const void __iomem *addr, + unsigned int offset, u32 data), + TP_ARGS(caller, addr, offset, data) +); + +DEFINE_EVENT(musb_regl, musb_writel, + TP_PROTO(void *caller, const void __iomem *addr, + unsigned int offset, u32 data), + TP_ARGS(caller, addr, offset, data) +); + +TRACE_EVENT(musb_isr, + TP_PROTO(struct musb *musb), + TP_ARGS(musb), + TP_STRUCT__entry( + __string(name, dev_name(musb->controller)) + __field(u8, int_usb) + __field(u16, int_tx) + __field(u16, int_rx) + ), + TP_fast_assign( + __assign_str(name); + __entry->int_usb = musb->int_usb; + __entry->int_tx = musb->int_tx; + __entry->int_rx = musb->int_rx; + ), + TP_printk("%s: usb %02x, tx %04x, rx %04x", + __get_str(name), __entry->int_usb, + __entry->int_tx, __entry->int_rx + ) +); + +DECLARE_EVENT_CLASS(musb_urb, + TP_PROTO(struct musb *musb, struct urb *urb), + TP_ARGS(musb, urb), + TP_STRUCT__entry( + __string(name, dev_name(musb->controller)) + __field(struct urb *, urb) + __field(unsigned int, pipe) + __field(int, status) + __field(unsigned int, flag) + __field(u32, buf_len) + __field(u32, actual_len) + ), + TP_fast_assign( + __assign_str(name); + __entry->urb = urb; + __entry->pipe = urb->pipe; + __entry->status = urb->status; + __entry->flag = urb->transfer_flags; + __entry->buf_len = urb->transfer_buffer_length; + __entry->actual_len = urb->actual_length; + ), + TP_printk("%s: %p, dev%d ep%d%s, flag 0x%x, len %d/%d, status %d", + __get_str(name), __entry->urb, + usb_pipedevice(__entry->pipe), + usb_pipeendpoint(__entry->pipe), + usb_pipein(__entry->pipe) ? "in" : "out", + __entry->flag, + __entry->actual_len, __entry->buf_len, + __entry->status + ) +); + +DEFINE_EVENT(musb_urb, musb_urb_start, + TP_PROTO(struct musb *musb, struct urb *urb), + TP_ARGS(musb, urb) +); + +DEFINE_EVENT(musb_urb, musb_urb_gb, + TP_PROTO(struct musb *musb, struct urb *urb), + TP_ARGS(musb, urb) +); + +DEFINE_EVENT(musb_urb, musb_urb_rx, + TP_PROTO(struct musb *musb, struct urb *urb), + TP_ARGS(musb, urb) +); + +DEFINE_EVENT(musb_urb, musb_urb_tx, + TP_PROTO(struct musb *musb, struct urb *urb), + TP_ARGS(musb, urb) +); + +DEFINE_EVENT(musb_urb, musb_urb_enq, + TP_PROTO(struct musb *musb, struct urb *urb), + TP_ARGS(musb, urb) +); + +DEFINE_EVENT(musb_urb, musb_urb_deq, + TP_PROTO(struct musb *musb, struct urb *urb), + TP_ARGS(musb, urb) +); + +DECLARE_EVENT_CLASS(musb_req, + TP_PROTO(struct musb_request *req), + TP_ARGS(req), + TP_STRUCT__entry( + __field(struct usb_request *, req) + __field(u8, is_tx) + __field(u8, epnum) + __field(int, status) + __field(unsigned int, buf_len) + __field(unsigned int, actual_len) + __field(unsigned int, zero) + __field(unsigned int, short_not_ok) + __field(unsigned int, no_interrupt) + ), + TP_fast_assign( + __entry->req = &req->request; + __entry->is_tx = req->tx; + __entry->epnum = req->epnum; + __entry->status = req->request.status; + __entry->buf_len = req->request.length; + __entry->actual_len = req->request.actual; + __entry->zero = req->request.zero; + __entry->short_not_ok = req->request.short_not_ok; + __entry->no_interrupt = req->request.no_interrupt; + ), + TP_printk("%p, ep%d %s, %s%s%s, len %d/%d, status %d", + __entry->req, __entry->epnum, + __entry->is_tx ? "tx/IN" : "rx/OUT", + __entry->zero ? "Z" : "z", + __entry->short_not_ok ? "S" : "s", + __entry->no_interrupt ? "I" : "i", + __entry->actual_len, __entry->buf_len, + __entry->status + ) +); + +DEFINE_EVENT(musb_req, musb_req_gb, + TP_PROTO(struct musb_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(musb_req, musb_req_tx, + TP_PROTO(struct musb_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(musb_req, musb_req_rx, + TP_PROTO(struct musb_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(musb_req, musb_req_alloc, + TP_PROTO(struct musb_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(musb_req, musb_req_free, + TP_PROTO(struct musb_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(musb_req, musb_req_start, + TP_PROTO(struct musb_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(musb_req, musb_req_enq, + TP_PROTO(struct musb_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(musb_req, musb_req_deq, + TP_PROTO(struct musb_request *req), + TP_ARGS(req) +); + +#ifdef CONFIG_USB_TI_CPPI41_DMA +DECLARE_EVENT_CLASS(musb_cppi41, + TP_PROTO(struct cppi41_dma_channel *ch), + TP_ARGS(ch), + TP_STRUCT__entry( + __field(struct cppi41_dma_channel *, ch) + __string(name, dev_name(ch->hw_ep->musb->controller)) + __field(u8, hwep) + __field(u8, port) + __field(u8, is_tx) + __field(u32, len) + __field(u32, prog_len) + __field(u32, xferred) + ), + TP_fast_assign( + __entry->ch = ch; + __assign_str(name); + __entry->hwep = ch->hw_ep->epnum; + __entry->port = ch->port_num; + __entry->is_tx = ch->is_tx; + __entry->len = ch->total_len; + __entry->prog_len = ch->prog_len; + __entry->xferred = ch->transferred; + ), + TP_printk("%s: %p, hwep%d ch%d%s, prog_len %d, len %d/%d", + __get_str(name), __entry->ch, __entry->hwep, + __entry->port, __entry->is_tx ? "tx" : "rx", + __entry->prog_len, __entry->xferred, __entry->len + ) +); + +DEFINE_EVENT(musb_cppi41, musb_cppi41_done, + TP_PROTO(struct cppi41_dma_channel *ch), + TP_ARGS(ch) +); + +DEFINE_EVENT(musb_cppi41, musb_cppi41_gb, + TP_PROTO(struct cppi41_dma_channel *ch), + TP_ARGS(ch) +); + +DEFINE_EVENT(musb_cppi41, musb_cppi41_config, + TP_PROTO(struct cppi41_dma_channel *ch), + TP_ARGS(ch) +); + +DEFINE_EVENT(musb_cppi41, musb_cppi41_cont, + TP_PROTO(struct cppi41_dma_channel *ch), + TP_ARGS(ch) +); + +DEFINE_EVENT(musb_cppi41, musb_cppi41_alloc, + TP_PROTO(struct cppi41_dma_channel *ch), + TP_ARGS(ch) +); + +DEFINE_EVENT(musb_cppi41, musb_cppi41_abort, + TP_PROTO(struct cppi41_dma_channel *ch), + TP_ARGS(ch) +); + +DEFINE_EVENT(musb_cppi41, musb_cppi41_free, + TP_PROTO(struct cppi41_dma_channel *ch), + TP_ARGS(ch) +); +#endif /* CONFIG_USB_TI_CPPI41_DMA */ + +#endif /* __MUSB_TRACE_H */ + +/* this part has to be here */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE musb_trace + +#include diff --git a/drivers/usb/musb/musb_virthub.c b/drivers/usb/musb/musb_virthub.c new file mode 100644 index 000000000..ce6f25a96 --- /dev/null +++ b/drivers/usb/musb/musb_virthub.c @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "musb_core.h" + +void musb_host_finish_resume(struct work_struct *work) +{ + struct musb *musb; + unsigned long flags; + u8 power; + + musb = container_of(work, struct musb, finish_resume_work.work); + + spin_lock_irqsave(&musb->lock, flags); + + power = musb_readb(musb->mregs, MUSB_POWER); + power &= ~MUSB_POWER_RESUME; + musb_dbg(musb, "root port resume stopped, power %02x", 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->hcd); + /* NOTE: it might really be A_WAIT_BCON ... */ + musb_set_state(musb, OTG_STATE_A_HOST); + + spin_unlock_irqrestore(&musb->lock, flags); +} + +int musb_port_suspend(struct musb *musb, bool do_suspend) +{ + u8 power; + void __iomem *mbase = musb->mregs; + + if (!is_host_active(musb)) + return 0; + + /* 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; + + if (power & MUSB_POWER_RESUME) + return -EBUSY; + + if (!(power & MUSB_POWER_SUSPENDM)) { + 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; + } + } + + musb_dbg(musb, "Root port suspended, power %02x", power); + + musb->port1_status |= USB_PORT_STAT_SUSPEND; + switch (musb_get_state(musb)) { + case OTG_STATE_A_HOST: + musb_set_state(musb, OTG_STATE_A_SUSPEND); + musb->is_active = musb->xceiv && + musb->xceiv->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_set_state(musb, OTG_STATE_B_WAIT_ACON); + musb->is_active = musb->xceiv && + musb->xceiv->otg->host->b_hnp_enable; + musb_platform_try_idle(musb, 0); + break; + default: + musb_dbg(musb, "bogus rh suspend? %s", + musb_otg_state_string(musb)); + } + } else if (power & MUSB_POWER_SUSPENDM) { + power &= ~MUSB_POWER_SUSPENDM; + power |= MUSB_POWER_RESUME; + musb_writeb(mbase, MUSB_POWER, power); + + musb_dbg(musb, "Root port resuming, power %02x", power); + + musb->port1_status |= MUSB_PORT_STAT_RESUME; + schedule_delayed_work(&musb->finish_resume_work, + msecs_to_jiffies(USB_RESUME_TIMEOUT)); + } + return 0; +} + +void musb_port_reset(struct musb *musb, bool do_reset) +{ + u8 power; + void __iomem *mbase = musb->mregs; + + if (musb_get_state(musb) == OTG_STATE_B_IDLE) { + musb_dbg(musb, "HNP: Returning from HNP; no hub reset from b_idle"); + 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) { + long remain = (unsigned long) musb->rh_timer - jiffies; + + if (musb->rh_timer > 0 && remain > 0) { + /* take into account the minimum delay after resume */ + schedule_delayed_work( + &musb->deassert_reset_work, remain); + return; + } + + musb_writeb(mbase, MUSB_POWER, + power & ~MUSB_POWER_RESUME); + + /* Give the core 1 ms to clear MUSB_POWER_RESUME */ + schedule_delayed_work(&musb->deassert_reset_work, + msecs_to_jiffies(1)); + return; + } + + 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; + schedule_delayed_work(&musb->deassert_reset_work, + msecs_to_jiffies(50)); + } else { + musb_dbg(musb, "root port reset stopped"); + musb_platform_pre_root_reset_end(musb); + musb_writeb(mbase, MUSB_POWER, + power & ~MUSB_POWER_RESET); + musb_platform_post_root_reset_end(musb); + + power = musb_readb(mbase, MUSB_POWER); + if (power & MUSB_POWER_HSMODE) { + musb_dbg(musb, "high-speed device connected"); + 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->hcd); + + musb->vbuserr_retry = VBUSERR_RETRY_COUNT; + } +} + +void musb_root_disconnect(struct musb *musb) +{ + musb->port1_status = USB_PORT_STAT_POWER + | (USB_PORT_STAT_C_CONNECTION << 16); + + usb_hcd_poll_rh_status(musb->hcd); + musb->is_active = 0; + + switch (musb_get_state(musb)) { + case OTG_STATE_A_SUSPEND: + if (musb->xceiv && musb->xceiv->otg->host->b_hnp_enable) { + musb_set_state(musb, OTG_STATE_A_PERIPHERAL); + musb->g.is_a_peripheral = 1; + break; + } + fallthrough; + case OTG_STATE_A_HOST: + musb_set_state(musb, OTG_STATE_A_WAIT_BCON); + musb->is_active = 0; + break; + case OTG_STATE_A_WAIT_VFALL: + musb_set_state(musb, OTG_STATE_B_IDLE); + break; + default: + musb_dbg(musb, "host disconnect (%s)", + musb_otg_state_string(musb)); + } +} +EXPORT_SYMBOL_GPL(musb_root_disconnect); + + +/*---------------------------------------------------------------------*/ + +/* 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; +} + +static int musb_has_gadget(struct musb *musb) +{ + /* + * In host-only mode we start a connection right away. In OTG mode + * we have to wait until we loaded a gadget. We don't really need a + * gadget if we operate as a host but we should not start a session + * as a device without a gadget or else we explode. + */ +#ifdef CONFIG_USB_MUSB_HOST + return 1; +#else + return musb->port_mode == MUSB_HOST; +#endif +} + +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; + bool start_musb = false; + + 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; + } + musb_dbg(musb, "clear feature %d", wValue); + musb->port1_status &= ~(1 << wValue); + break; + case GetHubDescriptor: + { + struct usb_hub_descriptor *desc = (void *)buf; + + desc->bDescLength = 9; + desc->bDescriptorType = USB_DT_HUB; + desc->bNbrPorts = 1; + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM /* per-port power switching */ + | HUB_CHAR_NO_OCPM /* 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; + + put_unaligned(cpu_to_le32(musb->port1_status + & ~MUSB_PORT_STAT_RESUME), + (__le32 *) buf); + + /* port change status is more interesting */ + musb_dbg(musb, "port status %08x", 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_has_gadget(musb)) + start_musb = true; + 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 USB_TEST_J: + pr_debug("USB_TEST_J\n"); + temp = MUSB_TEST_J; + break; + case USB_TEST_K: + pr_debug("USB_TEST_K\n"); + temp = MUSB_TEST_K; + break; + case USB_TEST_SE0_NAK: + pr_debug("USB_TEST_SE0_NAK\n"); + temp = MUSB_TEST_SE0_NAK; + break; + case USB_TEST_PACKET: + pr_debug("USB_TEST_PACKET\n"); + temp = MUSB_TEST_PACKET; + musb_load_testpacket(musb); + break; + case USB_TEST_FORCE_ENABLE: + pr_debug("USB_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; + } + musb_dbg(musb, "set feature %d", wValue); + musb->port1_status |= 1 << wValue; + break; + + default: +error: + /* "protocol stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore(&musb->lock, flags); + + if (start_musb) + musb_start(musb); + + return retval; +} diff --git a/drivers/usb/musb/musbhsdma.c b/drivers/usb/musb/musbhsdma.c new file mode 100644 index 000000000..8c6c643fc --- /dev/null +++ b/drivers/usb/musb/musbhsdma.c @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MUSB OTG driver - support for Mentor's DMA controller + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2007 by Texas Instruments + */ +#include +#include +#include +#include +#include "musb_core.h" +#include "musb_dma.h" + +#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) +/* 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; + int irq; +}; + +static void dma_channel_release(struct dma_channel *channel); + +static void dma_controller_stop(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; + } + } + } +} + +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; + + musb_dbg(musb, "%p, pkt_sz %d, addr %pad, len %d, mode %d", + 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; + + musb_dbg(musb, "ep%d-%s pkt_sz %d, dma_addr %pad length %d, mode %d", + 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); + + /* + * 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; + struct musb *musb = musb_channel->controller->private_data; + + u8 bchannel = musb_channel->idx; + int offset; + u16 csr; + + if (channel->status == MUSB_DMA_STATUS_BUSY) { + if (musb_channel->transmit) { + offset = musb->io.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->io.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; +} + +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_clearb(mbase, MUSB_HSDMA_INTR); + + if (!int_hsdma) { + musb_dbg(musb, "spurious DMA irq"); + + 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); + } + } + + musb_dbg(musb, "int_hsdma = 0x%x", 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 { + addr = musb_read_hsdma_addr(mbase, + bchannel); + channel->actual_len = addr + - musb_channel->start_addr; + + musb_dbg(musb, "ch %p, 0x%x -> 0x%x (%zu / %d) %s", + channel, musb_channel->start_addr, + addr, channel->actual_len, + musb_channel->len, + (channel->actual_len + < musb_channel->len) ? + "=> reconfig 0" : "=> complete"); + + channel->status = MUSB_DMA_STATUS_FREE; + + /* completed */ + if (musb_channel->transmit && + (!channel->desired_mode || + (channel->actual_len % + musb_channel->max_packet_sz))) { + u8 epnum = musb_channel->epnum; + int offset = musb->io.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); + if (channel->desired_mode == 1) { + txcsr &= ~(MUSB_TXCSR_DMAENAB + | MUSB_TXCSR_AUTOSET); + musb_writew(mbase, offset, txcsr); + /* Send out the packet */ + txcsr &= ~MUSB_TXCSR_DMAMODE; + txcsr |= MUSB_TXCSR_DMAENAB; + } + 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; +} +EXPORT_SYMBOL_GPL(dma_controller_irq); + +void musbhs_dma_controller_destroy(struct dma_controller *c) +{ + struct musb_dma_controller *controller = container_of(c, + struct musb_dma_controller, controller); + + dma_controller_stop(controller); + + if (controller->irq) + free_irq(controller->irq, c); + + kfree(controller); +} +EXPORT_SYMBOL_GPL(musbhs_dma_controller_destroy); + +static struct musb_dma_controller * +dma_controller_alloc(struct musb *musb, void __iomem *base) +{ + struct musb_dma_controller *controller; + + controller = kzalloc_obj(*controller); + if (!controller) + return NULL; + + controller->channel_count = MUSB_HSDMA_CHANNELS; + controller->private_data = musb; + controller->base = base; + + 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; + return controller; +} + +struct dma_controller * +musbhs_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 = dma_controller_alloc(musb, base); + if (!controller) + return NULL; + + if (request_irq(irq, dma_controller_irq, 0, + dev_name(musb->controller), controller)) { + dev_err(dev, "request_irq %d failed!\n", irq); + musb_dma_controller_destroy(&controller->controller); + + return NULL; + } + + controller->irq = irq; + + return &controller->controller; +} +EXPORT_SYMBOL_GPL(musbhs_dma_controller_create); + +struct dma_controller * +musbhs_dma_controller_create_noirq(struct musb *musb, void __iomem *base) +{ + struct musb_dma_controller *controller; + + controller = dma_controller_alloc(musb, base); + if (!controller) + return NULL; + + return &controller->controller; +} +EXPORT_SYMBOL_GPL(musbhs_dma_controller_create_noirq); diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c new file mode 100644 index 000000000..6e749faac --- /dev/null +++ b/drivers/usb/musb/omap2430.c @@ -0,0 +1,622 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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. + */ +#include +#include +#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 musb_vbus_id_status status; + struct work_struct omap_musb_mailbox_work; + struct device *control_otghs; + unsigned int is_runtime_suspended:1; + unsigned int needs_resume:1; + unsigned int phy_suspended:1; +}; +#define glue_to_musb(g) platform_get_drvdata(g->musb) + +static struct omap2430_glue *_glue; + +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); +} + +static int omap2430_musb_mailbox(enum musb_vbus_id_status status) +{ + struct omap2430_glue *glue = _glue; + + if (!glue) { + pr_err("%s: musb core is not yet initialized\n", __func__); + return -EPROBE_DEFER; + } + glue->status = status; + + if (!glue_to_musb(glue)) { + pr_err("%s: musb core is not yet ready\n", __func__); + return -EPROBE_DEFER; + } + + schedule_work(&glue->omap_musb_mailbox_work); + + return 0; +} + +/* + * HDRC controls CPEN, but beware current surges during device connect. + * They can trigger transient overcurrent conditions that must be ignored. + * + * Note that we're skipping A_WAIT_VFALL -> A_IDLE and jumping right to B_IDLE + * as set by musb_set_peripheral(). + */ +static void omap_musb_set_mailbox(struct omap2430_glue *glue) +{ + struct musb *musb = glue_to_musb(glue); + int error; + + pm_runtime_get_sync(musb->controller); + + dev_dbg(musb->controller, "VBUS %s, devctl %02x\n", + usb_otg_state_string(musb->xceiv->otg->state), + musb_readb(musb->mregs, MUSB_DEVCTL)); + + switch (glue->status) { + case MUSB_ID_GROUND: + dev_dbg(musb->controller, "ID GND\n"); + switch (musb->xceiv->otg->state) { + case OTG_STATE_A_IDLE: + error = musb_set_host(musb); + if (error) + break; + musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE; + fallthrough; + case OTG_STATE_A_WAIT_VRISE: + case OTG_STATE_A_WAIT_BCON: + case OTG_STATE_A_HOST: + /* + * On multiple ID ground interrupts just keep enabling + * VBUS. At least cpcap VBUS shuts down otherwise. + */ + otg_set_vbus(musb->xceiv->otg, 1); + break; + default: + musb->xceiv->otg->state = OTG_STATE_A_IDLE; + musb->xceiv->last_event = USB_EVENT_ID; + if (musb->gadget_driver) { + omap_control_usb_set_mode(glue->control_otghs, + USB_MODE_HOST); + otg_set_vbus(musb->xceiv->otg, 1); + } + break; + } + break; + + case MUSB_VBUS_VALID: + dev_dbg(musb->controller, "VBUS Connect\n"); + + musb->xceiv->otg->state = OTG_STATE_B_IDLE; + musb->xceiv->last_event = USB_EVENT_VBUS; + omap_control_usb_set_mode(glue->control_otghs, USB_MODE_DEVICE); + break; + + case MUSB_ID_FLOAT: + case MUSB_VBUS_OFF: + dev_dbg(musb->controller, "VBUS Disconnect\n"); + + musb->xceiv->last_event = USB_EVENT_NONE; + musb_set_peripheral(musb); + otg_set_vbus(musb->xceiv->otg, 0); + omap_control_usb_set_mode(glue->control_otghs, + USB_MODE_DISCONNECT); + break; + default: + dev_dbg(musb->controller, "ID float\n"); + } + pm_runtime_put_autosuspend(musb->controller); + atomic_notifier_call_chain(&musb->xceiv->notifier, + musb->xceiv->last_event, NULL); +} + + +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 musb_hdrc_platform_data *plat = dev_get_platdata(dev); + 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. + */ + musb->phy = devm_phy_get(dev->parent, "usb2-phy"); + + /* We can't totally remove musb->xceiv as of now because + * musb core uses xceiv.state and xceiv.otg. Once we have + * a separate state machine to handle otg, these can be moved + * out of xceiv and then we can start using the generic PHY + * framework + */ + musb->xceiv = devm_usb_get_phy_by_phandle(dev->parent, "usb-phy", 0); + + if (IS_ERR(musb->xceiv)) { + status = PTR_ERR(musb->xceiv); + + if (status == -ENXIO) + return status; + + dev_dbg(dev, "HS USB OTG: no transceiver configured\n"); + return -EPROBE_DEFER; + } + + if (IS_ERR(musb->phy)) { + dev_err(dev, "HS USB OTG: no PHY configured\n"); + return PTR_ERR(musb->phy); + } + musb->isr = omap2430_musb_interrupt; + phy_init(musb->phy); + phy_power_on(musb->phy); + + 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); + + dev_dbg(dev, "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)); + + return 0; +} + +static void omap2430_musb_enable(struct musb *musb) +{ + struct device *dev = musb->controller; + struct omap2430_glue *glue = dev_get_drvdata(dev->parent); + + if (glue->status == MUSB_UNKNOWN) + glue->status = MUSB_VBUS_OFF; + omap_musb_set_mailbox(glue); +} + +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 != MUSB_UNKNOWN) + omap_control_usb_set_mode(glue->control_otghs, + USB_MODE_DISCONNECT); +} + +static int omap2430_musb_exit(struct musb *musb) +{ + struct device *dev = musb->controller; + struct omap2430_glue *glue = dev_get_drvdata(dev->parent); + + omap2430_low_level_exit(musb); + phy_power_off(musb->phy); + phy_exit(musb->phy); + musb->phy = NULL; + cancel_work_sync(&glue->omap_musb_mailbox_work); + + return 0; +} + +static const struct musb_platform_ops omap2430_ops = { + .quirks = MUSB_DMA_INVENTRA, +#ifdef CONFIG_USB_INVENTRA_DMA + .dma_init = musbhs_dma_controller_create, + .dma_exit = musbhs_dma_controller_destroy, +#endif + .init = omap2430_musb_init, + .exit = omap2430_musb_exit, + + .enable = omap2430_musb_enable, + .disable = omap2430_musb_disable, + + .phy_callback = omap2430_musb_mailbox, +}; + +static u64 omap2430_dmamask = DMA_BIT_MASK(32); + +static int omap2430_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev); + 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; + struct device_node *control_node; + struct platform_device *control_pdev; + int ret = -ENOMEM, val; + bool populate_irqs = false; + + if (!np) + return -ENODEV; + + glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL); + if (!glue) + return -ENOMEM; + + musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb) + return -ENOMEM; + + musb->dev.parent = &pdev->dev; + musb->dev.dma_mask = &omap2430_dmamask; + musb->dev.coherent_dma_mask = omap2430_dmamask; + + /* + * Legacy SoCs using omap_device get confused if node is moved + * because of interconnect properties mixed into the node. + */ + if (of_property_present(np, "ti,hwmods")) { + dev_warn(&pdev->dev, "please update to probe with ti-sysc\n"); + populate_irqs = true; + } else { + device_set_of_node_from_dev(&musb->dev, &pdev->dev); + } + + glue->dev = &pdev->dev; + glue->musb = musb; + glue->status = MUSB_UNKNOWN; + glue->control_otghs = ERR_PTR(-ENODEV); + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto err_put_musb; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + goto err_put_musb; + + config = devm_kzalloc(&pdev->dev, sizeof(*config), GFP_KERNEL); + if (!config) + goto err_put_musb; + + 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); + + ret = of_property_read_u32(np, "multipoint", &val); + if (!ret && val) + config->multipoint = true; + + pdata->board_data = data; + pdata->config = config; + + control_node = of_parse_phandle(np, "ctrl-module", 0); + if (control_node) { + control_pdev = of_find_device_by_node(control_node); + of_node_put(control_node); + if (!control_pdev) { + dev_err(&pdev->dev, "Failed to get control device\n"); + ret = -EINVAL; + goto err_put_musb; + } + glue->control_otghs = &control_pdev->dev; + } + + 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 err_put_control_otghs; + } + + if (populate_irqs) { + struct resource musb_res[3]; + struct resource *res; + int i = 0; + + memset(musb_res, 0, sizeof(*musb_res) * ARRAY_SIZE(musb_res)); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -EINVAL; + goto err_put_control_otghs; + } + + musb_res[i].start = res->start; + musb_res[i].end = res->end; + musb_res[i].flags = res->flags; + musb_res[i].name = res->name; + i++; + + ret = of_irq_get_byname(np, "mc"); + if (ret > 0) { + musb_res[i].start = ret; + musb_res[i].flags = IORESOURCE_IRQ; + musb_res[i].name = "mc"; + i++; + } + + ret = of_irq_get_byname(np, "dma"); + if (ret > 0) { + musb_res[i].start = ret; + musb_res[i].flags = IORESOURCE_IRQ; + musb_res[i].name = "dma"; + i++; + } + + ret = platform_device_add_resources(musb, musb_res, i); + if (ret) { + dev_err(&pdev->dev, "failed to add IRQ resources\n"); + goto err_put_control_otghs; + } + } + + ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); + if (ret) { + dev_err(&pdev->dev, "failed to add platform_data\n"); + goto err_put_control_otghs; + } + + pm_runtime_enable(glue->dev); + + ret = platform_device_add(musb); + if (ret) { + dev_err(&pdev->dev, "failed to register musb device\n"); + goto err_disable_rpm; + } + + return 0; + +err_disable_rpm: + pm_runtime_disable(glue->dev); +err_put_control_otghs: + if (!IS_ERR(glue->control_otghs)) + put_device(glue->control_otghs); +err_put_musb: + platform_device_put(musb); + + return ret; +} + +static void omap2430_remove(struct platform_device *pdev) +{ + struct omap2430_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + pm_runtime_disable(glue->dev); + if (!IS_ERR(glue->control_otghs)) + put_device(glue->control_otghs); +} + +#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) + return 0; + + musb->context.otg_interfsel = musb_readl(musb->mregs, + OTG_INTERFSEL); + + omap2430_low_level_exit(musb); + + if (!glue->phy_suspended) { + phy_power_off(musb->phy); + phy_exit(musb->phy); + } + + glue->is_runtime_suspended = 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) + return 0; + + if (!glue->phy_suspended) { + phy_init(musb->phy); + phy_power_on(musb->phy); + } + + omap2430_low_level_init(musb); + musb_writel(musb->mregs, OTG_INTERFSEL, + musb->context.otg_interfsel); + + /* Wait for musb to get oriented. Otherwise we can get babble */ + usleep_range(200000, 250000); + + glue->is_runtime_suspended = 0; + + return 0; +} + +/* I2C and SPI PHYs need to be suspended before the glue layer */ +static int omap2430_suspend(struct device *dev) +{ + struct omap2430_glue *glue = dev_get_drvdata(dev); + struct musb *musb = glue_to_musb(glue); + + phy_power_off(musb->phy); + phy_exit(musb->phy); + glue->phy_suspended = 1; + + return 0; +} + +/* Glue layer needs to be suspended after musb_suspend() */ +static int omap2430_suspend_late(struct device *dev) +{ + struct omap2430_glue *glue = dev_get_drvdata(dev); + + if (glue->is_runtime_suspended) + return 0; + + glue->needs_resume = 1; + + return omap2430_runtime_suspend(dev); +} + +static int omap2430_resume_early(struct device *dev) +{ + struct omap2430_glue *glue = dev_get_drvdata(dev); + + if (!glue->needs_resume) + return 0; + + glue->needs_resume = 0; + + return omap2430_runtime_resume(dev); +} + +static int omap2430_resume(struct device *dev) +{ + struct omap2430_glue *glue = dev_get_drvdata(dev); + struct musb *musb = glue_to_musb(glue); + + phy_init(musb->phy); + phy_power_on(musb->phy); + glue->phy_suspended = 0; + + return 0; +} + +static const struct dev_pm_ops omap2430_pm_ops = { + .runtime_suspend = omap2430_runtime_suspend, + .runtime_resume = omap2430_runtime_resume, + .suspend = omap2430_suspend, + .suspend_late = omap2430_suspend_late, + .resume_early = omap2430_resume_early, + .resume = omap2430_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_platform_driver(omap2430_driver); + +MODULE_DESCRIPTION("OMAP2PLUS MUSB Glue Layer"); +MODULE_AUTHOR("Felipe Balbi "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/musb/omap2430.h b/drivers/usb/musb/omap2430.h new file mode 100644 index 000000000..939a0361a --- /dev/null +++ b/drivers/usb/musb/omap2430.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2005-2006 by Texas Instruments + */ + +#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/sunxi.c b/drivers/usb/musb/sunxi.c new file mode 100644 index 000000000..a6bd3e968 --- /dev/null +++ b/drivers/usb/musb/sunxi.c @@ -0,0 +1,870 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Allwinner sun4i MUSB Glue Layer + * + * Copyright (C) 2015 Hans de Goede + * + * Based on code from + * Allwinner Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "musb_core.h" + +/* + * Register offsets, note sunxi musb has a different layout then most + * musb implementations, we translate the layout in musb_readb & friends. + */ +#define SUNXI_MUSB_POWER 0x0040 +#define SUNXI_MUSB_DEVCTL 0x0041 +#define SUNXI_MUSB_INDEX 0x0042 +#define SUNXI_MUSB_VEND0 0x0043 +#define SUNXI_MUSB_INTRTX 0x0044 +#define SUNXI_MUSB_INTRRX 0x0046 +#define SUNXI_MUSB_INTRTXE 0x0048 +#define SUNXI_MUSB_INTRRXE 0x004a +#define SUNXI_MUSB_INTRUSB 0x004c +#define SUNXI_MUSB_INTRUSBE 0x0050 +#define SUNXI_MUSB_FRAME 0x0054 +#define SUNXI_MUSB_TXFIFOSZ 0x0090 +#define SUNXI_MUSB_TXFIFOADD 0x0092 +#define SUNXI_MUSB_RXFIFOSZ 0x0094 +#define SUNXI_MUSB_RXFIFOADD 0x0096 +#define SUNXI_MUSB_FADDR 0x0098 +#define SUNXI_MUSB_TXFUNCADDR 0x0098 +#define SUNXI_MUSB_TXHUBADDR 0x009a +#define SUNXI_MUSB_TXHUBPORT 0x009b +#define SUNXI_MUSB_RXFUNCADDR 0x009c +#define SUNXI_MUSB_RXHUBADDR 0x009e +#define SUNXI_MUSB_RXHUBPORT 0x009f +#define SUNXI_MUSB_CONFIGDATA 0x00c0 + +/* VEND0 bits */ +#define SUNXI_MUSB_VEND0_PIO_MODE 0 + +/* flags */ +#define SUNXI_MUSB_FL_ENABLED 0 +#define SUNXI_MUSB_FL_HOSTMODE 1 +#define SUNXI_MUSB_FL_HOSTMODE_PEND 2 +#define SUNXI_MUSB_FL_VBUS_ON 3 +#define SUNXI_MUSB_FL_PHY_ON 4 +#define SUNXI_MUSB_FL_HAS_SRAM 5 +#define SUNXI_MUSB_FL_HAS_RESET 6 +#define SUNXI_MUSB_FL_NO_CONFIGDATA 7 +#define SUNXI_MUSB_FL_PHY_MODE_PEND 8 + +struct sunxi_musb_cfg { + const struct musb_hdrc_config *hdrc_config; + bool has_sram; + bool has_reset; + bool no_configdata; +}; + +/* Our read/write methods need access and do not get passed in a musb ref :| */ +static struct musb *sunxi_musb; + +struct sunxi_glue { + struct device *dev; + struct musb *musb; + struct platform_device *musb_pdev; + struct clk *clk; + struct reset_control *rst; + struct phy *phy; + struct platform_device *usb_phy; + struct usb_phy *xceiv; + enum phy_mode phy_mode; + unsigned long flags; + struct work_struct work; + struct extcon_dev *extcon; + struct notifier_block host_nb; +}; + +/* phy_power_on / off may sleep, so we use a workqueue */ +static void sunxi_musb_work(struct work_struct *work) +{ + struct sunxi_glue *glue = container_of(work, struct sunxi_glue, work); + bool vbus_on, phy_on; + + if (!test_bit(SUNXI_MUSB_FL_ENABLED, &glue->flags)) + return; + + if (test_and_clear_bit(SUNXI_MUSB_FL_HOSTMODE_PEND, &glue->flags)) { + struct musb *musb = glue->musb; + unsigned long flags; + u8 devctl; + + spin_lock_irqsave(&musb->lock, flags); + + devctl = readb(musb->mregs + SUNXI_MUSB_DEVCTL); + if (test_bit(SUNXI_MUSB_FL_HOSTMODE, &glue->flags)) { + set_bit(SUNXI_MUSB_FL_VBUS_ON, &glue->flags); + musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE; + MUSB_HST_MODE(musb); + devctl |= MUSB_DEVCTL_SESSION; + } else { + clear_bit(SUNXI_MUSB_FL_VBUS_ON, &glue->flags); + musb->xceiv->otg->state = OTG_STATE_B_IDLE; + MUSB_DEV_MODE(musb); + devctl &= ~MUSB_DEVCTL_SESSION; + } + writeb(devctl, musb->mregs + SUNXI_MUSB_DEVCTL); + + spin_unlock_irqrestore(&musb->lock, flags); + } + + vbus_on = test_bit(SUNXI_MUSB_FL_VBUS_ON, &glue->flags); + phy_on = test_bit(SUNXI_MUSB_FL_PHY_ON, &glue->flags); + + if (phy_on != vbus_on) { + if (vbus_on) { + phy_power_on(glue->phy); + set_bit(SUNXI_MUSB_FL_PHY_ON, &glue->flags); + } else { + phy_power_off(glue->phy); + clear_bit(SUNXI_MUSB_FL_PHY_ON, &glue->flags); + } + } + + if (test_and_clear_bit(SUNXI_MUSB_FL_PHY_MODE_PEND, &glue->flags)) + phy_set_mode(glue->phy, glue->phy_mode); +} + +static void sunxi_musb_set_vbus(struct musb *musb, int is_on) +{ + struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent); + + if (is_on) { + set_bit(SUNXI_MUSB_FL_VBUS_ON, &glue->flags); + musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE; + } else { + clear_bit(SUNXI_MUSB_FL_VBUS_ON, &glue->flags); + } + + schedule_work(&glue->work); +} + +static void sunxi_musb_pre_root_reset_end(struct musb *musb) +{ + struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent); + + sun4i_usb_phy_set_squelch_detect(glue->phy, false); +} + +static void sunxi_musb_post_root_reset_end(struct musb *musb) +{ + struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent); + + sun4i_usb_phy_set_squelch_detect(glue->phy, true); +} + +static irqreturn_t sunxi_musb_interrupt(int irq, void *__hci) +{ + struct musb *musb = __hci; + unsigned long flags; + + spin_lock_irqsave(&musb->lock, flags); + + musb->int_usb = readb(musb->mregs + SUNXI_MUSB_INTRUSB); + if (musb->int_usb) + writeb(musb->int_usb, musb->mregs + SUNXI_MUSB_INTRUSB); + + if ((musb->int_usb & MUSB_INTR_RESET) && !is_host_active(musb)) { + /* ep0 FADDR must be 0 when (re)entering peripheral mode */ + musb_ep_select(musb->mregs, 0); + musb_writeb(musb->mregs, MUSB_FADDR, 0); + } + + musb->int_tx = readw(musb->mregs + SUNXI_MUSB_INTRTX); + if (musb->int_tx) + writew(musb->int_tx, musb->mregs + SUNXI_MUSB_INTRTX); + + musb->int_rx = readw(musb->mregs + SUNXI_MUSB_INTRRX); + if (musb->int_rx) + writew(musb->int_rx, musb->mregs + SUNXI_MUSB_INTRRX); + + musb_interrupt(musb); + + spin_unlock_irqrestore(&musb->lock, flags); + + return IRQ_HANDLED; +} + +static int sunxi_musb_host_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct sunxi_glue *glue = container_of(nb, struct sunxi_glue, host_nb); + + if (event) + set_bit(SUNXI_MUSB_FL_HOSTMODE, &glue->flags); + else + clear_bit(SUNXI_MUSB_FL_HOSTMODE, &glue->flags); + + set_bit(SUNXI_MUSB_FL_HOSTMODE_PEND, &glue->flags); + schedule_work(&glue->work); + + return NOTIFY_DONE; +} + +static int sunxi_musb_init(struct musb *musb) +{ + struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent); + int ret; + + sunxi_musb = musb; + musb->phy = glue->phy; + musb->xceiv = glue->xceiv; + + if (test_bit(SUNXI_MUSB_FL_HAS_SRAM, &glue->flags)) { + ret = sunxi_sram_claim(musb->controller->parent); + if (ret) + return ret; + } + + ret = clk_prepare_enable(glue->clk); + if (ret) + goto error_sram_release; + + if (test_bit(SUNXI_MUSB_FL_HAS_RESET, &glue->flags)) { + ret = reset_control_deassert(glue->rst); + if (ret) + goto error_clk_disable; + } + + writeb(SUNXI_MUSB_VEND0_PIO_MODE, musb->mregs + SUNXI_MUSB_VEND0); + + /* Register notifier before calling phy_init() */ + ret = devm_extcon_register_notifier(glue->dev, glue->extcon, + EXTCON_USB_HOST, &glue->host_nb); + if (ret) + goto error_reset_assert; + + ret = phy_init(glue->phy); + if (ret) + goto error_reset_assert; + + musb->isr = sunxi_musb_interrupt; + + /* Stop the musb-core from doing runtime pm (not supported on sunxi) */ + pm_runtime_get(musb->controller); + + return 0; + +error_reset_assert: + if (test_bit(SUNXI_MUSB_FL_HAS_RESET, &glue->flags)) + reset_control_assert(glue->rst); +error_clk_disable: + clk_disable_unprepare(glue->clk); +error_sram_release: + if (test_bit(SUNXI_MUSB_FL_HAS_SRAM, &glue->flags)) + sunxi_sram_release(musb->controller->parent); + return ret; +} + +static int sunxi_musb_exit(struct musb *musb) +{ + struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent); + + pm_runtime_put(musb->controller); + + cancel_work_sync(&glue->work); + if (test_bit(SUNXI_MUSB_FL_PHY_ON, &glue->flags)) + phy_power_off(glue->phy); + + phy_exit(glue->phy); + + if (test_bit(SUNXI_MUSB_FL_HAS_RESET, &glue->flags)) + reset_control_assert(glue->rst); + + clk_disable_unprepare(glue->clk); + if (test_bit(SUNXI_MUSB_FL_HAS_SRAM, &glue->flags)) + sunxi_sram_release(musb->controller->parent); + + return 0; +} + +static void sunxi_musb_enable(struct musb *musb) +{ + struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent); + + glue->musb = musb; + + /* musb_core does not call us in a balanced manner */ + if (test_and_set_bit(SUNXI_MUSB_FL_ENABLED, &glue->flags)) + return; + + schedule_work(&glue->work); +} + +static void sunxi_musb_disable(struct musb *musb) +{ + struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent); + + clear_bit(SUNXI_MUSB_FL_ENABLED, &glue->flags); +} + +static struct dma_controller * +sunxi_musb_dma_controller_create(struct musb *musb, void __iomem *base) +{ + return NULL; +} + +static void sunxi_musb_dma_controller_destroy(struct dma_controller *c) +{ +} + +static int sunxi_musb_set_mode(struct musb *musb, u8 mode) +{ + struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent); + enum phy_mode new_mode; + + switch (mode) { + case MUSB_HOST: + new_mode = PHY_MODE_USB_HOST; + break; + case MUSB_PERIPHERAL: + new_mode = PHY_MODE_USB_DEVICE; + break; + case MUSB_OTG: + new_mode = PHY_MODE_USB_OTG; + break; + default: + dev_err(musb->controller->parent, + "Error requested mode not supported by this kernel\n"); + return -EINVAL; + } + + if (glue->phy_mode == new_mode) + return 0; + + if (musb->port_mode != MUSB_OTG) { + dev_err(musb->controller->parent, + "Error changing modes is only supported in dual role mode\n"); + return -EINVAL; + } + + if (musb->port1_status & USB_PORT_STAT_ENABLE) + musb_root_disconnect(musb); + + /* + * phy_set_mode may sleep, and we're called with a spinlock held, + * so let sunxi_musb_work deal with it. + */ + glue->phy_mode = new_mode; + set_bit(SUNXI_MUSB_FL_PHY_MODE_PEND, &glue->flags); + schedule_work(&glue->work); + + return 0; +} + +static int sunxi_musb_recover(struct musb *musb) +{ + struct sunxi_glue *glue = dev_get_drvdata(musb->controller->parent); + + /* + * Schedule a phy_set_mode with the current glue->phy_mode value, + * this will force end the current session. + */ + set_bit(SUNXI_MUSB_FL_PHY_MODE_PEND, &glue->flags); + schedule_work(&glue->work); + + return 0; +} + +/* + * sunxi musb register layout + * 0x00 - 0x17 fifo regs, 1 long per fifo + * 0x40 - 0x57 generic control regs (power - frame) + * 0x80 - 0x8f ep control regs (addressed through hw_ep->regs, indexed) + * 0x90 - 0x97 fifo control regs (indexed) + * 0x98 - 0x9f multipoint / busctl regs (indexed) + * 0xc0 configdata reg + */ + +static u32 sunxi_musb_fifo_offset(u8 epnum) +{ + return (epnum * 4); +} + +static u32 sunxi_musb_ep_offset(u8 epnum, u16 offset) +{ + WARN_ONCE(offset != 0, + "sunxi_musb_ep_offset called with non 0 offset\n"); + + return 0x80; /* indexed, so ignore epnum */ +} + +static u32 sunxi_musb_busctl_offset(u8 epnum, u16 offset) +{ + return SUNXI_MUSB_TXFUNCADDR + offset; +} + +static u8 sunxi_musb_readb(void __iomem *addr, u32 offset) +{ + struct sunxi_glue *glue; + + if (addr == sunxi_musb->mregs) { + /* generic control or fifo control reg access */ + switch (offset) { + case MUSB_FADDR: + return readb(addr + SUNXI_MUSB_FADDR); + case MUSB_POWER: + return readb(addr + SUNXI_MUSB_POWER); + case MUSB_INTRUSB: + return readb(addr + SUNXI_MUSB_INTRUSB); + case MUSB_INTRUSBE: + return readb(addr + SUNXI_MUSB_INTRUSBE); + case MUSB_INDEX: + return readb(addr + SUNXI_MUSB_INDEX); + case MUSB_TESTMODE: + return 0; /* No testmode on sunxi */ + case MUSB_DEVCTL: + return readb(addr + SUNXI_MUSB_DEVCTL); + case MUSB_TXFIFOSZ: + return readb(addr + SUNXI_MUSB_TXFIFOSZ); + case MUSB_RXFIFOSZ: + return readb(addr + SUNXI_MUSB_RXFIFOSZ); + case MUSB_CONFIGDATA + 0x10: /* See musb_read_configdata() */ + glue = dev_get_drvdata(sunxi_musb->controller->parent); + /* A33 saves a reg, and we get to hardcode this */ + if (test_bit(SUNXI_MUSB_FL_NO_CONFIGDATA, + &glue->flags)) + return 0xde; + + return readb(addr + SUNXI_MUSB_CONFIGDATA); + case MUSB_ULPI_BUSCONTROL: + dev_warn(sunxi_musb->controller->parent, + "sunxi-musb does not have ULPI bus control register\n"); + return 0; + /* Offset for these is fixed by sunxi_musb_busctl_offset() */ + case SUNXI_MUSB_TXFUNCADDR: + case SUNXI_MUSB_TXHUBADDR: + case SUNXI_MUSB_TXHUBPORT: + case SUNXI_MUSB_RXFUNCADDR: + case SUNXI_MUSB_RXHUBADDR: + case SUNXI_MUSB_RXHUBPORT: + /* multipoint / busctl reg access */ + return readb(addr + offset); + default: + dev_err(sunxi_musb->controller->parent, + "Error unknown readb offset %u\n", offset); + return 0; + } + } else if (addr == (sunxi_musb->mregs + 0x80)) { + /* ep control reg access */ + /* sunxi has a 2 byte hole before the txtype register */ + if (offset >= MUSB_TXTYPE) + offset += 2; + return readb(addr + offset); + } + + dev_err(sunxi_musb->controller->parent, + "Error unknown readb at 0x%x bytes offset\n", + (int)(addr - sunxi_musb->mregs)); + return 0; +} + +static void sunxi_musb_writeb(void __iomem *addr, unsigned offset, u8 data) +{ + if (addr == sunxi_musb->mregs) { + /* generic control or fifo control reg access */ + switch (offset) { + case MUSB_FADDR: + return writeb(data, addr + SUNXI_MUSB_FADDR); + case MUSB_POWER: + return writeb(data, addr + SUNXI_MUSB_POWER); + case MUSB_INTRUSB: + return writeb(data, addr + SUNXI_MUSB_INTRUSB); + case MUSB_INTRUSBE: + return writeb(data, addr + SUNXI_MUSB_INTRUSBE); + case MUSB_INDEX: + return writeb(data, addr + SUNXI_MUSB_INDEX); + case MUSB_TESTMODE: + if (data) + dev_warn(sunxi_musb->controller->parent, + "sunxi-musb does not have testmode\n"); + return; + case MUSB_DEVCTL: + return writeb(data, addr + SUNXI_MUSB_DEVCTL); + case MUSB_TXFIFOSZ: + return writeb(data, addr + SUNXI_MUSB_TXFIFOSZ); + case MUSB_RXFIFOSZ: + return writeb(data, addr + SUNXI_MUSB_RXFIFOSZ); + case MUSB_ULPI_BUSCONTROL: + dev_warn(sunxi_musb->controller->parent, + "sunxi-musb does not have ULPI bus control register\n"); + return; + /* Offset for these is fixed by sunxi_musb_busctl_offset() */ + case SUNXI_MUSB_TXFUNCADDR: + case SUNXI_MUSB_TXHUBADDR: + case SUNXI_MUSB_TXHUBPORT: + case SUNXI_MUSB_RXFUNCADDR: + case SUNXI_MUSB_RXHUBADDR: + case SUNXI_MUSB_RXHUBPORT: + /* multipoint / busctl reg access */ + return writeb(data, addr + offset); + default: + dev_err(sunxi_musb->controller->parent, + "Error unknown writeb offset %u\n", offset); + return; + } + } else if (addr == (sunxi_musb->mregs + 0x80)) { + /* ep control reg access */ + if (offset >= MUSB_TXTYPE) + offset += 2; + return writeb(data, addr + offset); + } + + dev_err(sunxi_musb->controller->parent, + "Error unknown writeb at 0x%x bytes offset\n", + (int)(addr - sunxi_musb->mregs)); +} + +static u16 sunxi_musb_readw(void __iomem *addr, u32 offset) +{ + if (addr == sunxi_musb->mregs) { + /* generic control or fifo control reg access */ + switch (offset) { + case MUSB_INTRTX: + return readw(addr + SUNXI_MUSB_INTRTX); + case MUSB_INTRRX: + return readw(addr + SUNXI_MUSB_INTRRX); + case MUSB_INTRTXE: + return readw(addr + SUNXI_MUSB_INTRTXE); + case MUSB_INTRRXE: + return readw(addr + SUNXI_MUSB_INTRRXE); + case MUSB_FRAME: + return readw(addr + SUNXI_MUSB_FRAME); + case MUSB_TXFIFOADD: + return readw(addr + SUNXI_MUSB_TXFIFOADD); + case MUSB_RXFIFOADD: + return readw(addr + SUNXI_MUSB_RXFIFOADD); + case MUSB_HWVERS: + return 0; /* sunxi musb version is not known */ + default: + dev_err(sunxi_musb->controller->parent, + "Error unknown readw offset %u\n", offset); + return 0; + } + } else if (addr == (sunxi_musb->mregs + 0x80)) { + /* ep control reg access */ + return readw(addr + offset); + } + + dev_err(sunxi_musb->controller->parent, + "Error unknown readw at 0x%x bytes offset\n", + (int)(addr - sunxi_musb->mregs)); + return 0; +} + +static void sunxi_musb_writew(void __iomem *addr, unsigned offset, u16 data) +{ + if (addr == sunxi_musb->mregs) { + /* generic control or fifo control reg access */ + switch (offset) { + case MUSB_INTRTX: + return writew(data, addr + SUNXI_MUSB_INTRTX); + case MUSB_INTRRX: + return writew(data, addr + SUNXI_MUSB_INTRRX); + case MUSB_INTRTXE: + return writew(data, addr + SUNXI_MUSB_INTRTXE); + case MUSB_INTRRXE: + return writew(data, addr + SUNXI_MUSB_INTRRXE); + case MUSB_FRAME: + return writew(data, addr + SUNXI_MUSB_FRAME); + case MUSB_TXFIFOADD: + return writew(data, addr + SUNXI_MUSB_TXFIFOADD); + case MUSB_RXFIFOADD: + return writew(data, addr + SUNXI_MUSB_RXFIFOADD); + default: + dev_err(sunxi_musb->controller->parent, + "Error unknown writew offset %u\n", offset); + return; + } + } else if (addr == (sunxi_musb->mregs + 0x80)) { + /* ep control reg access */ + return writew(data, addr + offset); + } + + dev_err(sunxi_musb->controller->parent, + "Error unknown writew at 0x%x bytes offset\n", + (int)(addr - sunxi_musb->mregs)); +} + +static const struct musb_platform_ops sunxi_musb_ops = { + .quirks = MUSB_INDEXED_EP, + .init = sunxi_musb_init, + .exit = sunxi_musb_exit, + .enable = sunxi_musb_enable, + .disable = sunxi_musb_disable, + .fifo_offset = sunxi_musb_fifo_offset, + .ep_offset = sunxi_musb_ep_offset, + .busctl_offset = sunxi_musb_busctl_offset, + .readb = sunxi_musb_readb, + .writeb = sunxi_musb_writeb, + .readw = sunxi_musb_readw, + .writew = sunxi_musb_writew, + .dma_init = sunxi_musb_dma_controller_create, + .dma_exit = sunxi_musb_dma_controller_destroy, + .set_mode = sunxi_musb_set_mode, + .recover = sunxi_musb_recover, + .set_vbus = sunxi_musb_set_vbus, + .pre_root_reset_end = sunxi_musb_pre_root_reset_end, + .post_root_reset_end = sunxi_musb_post_root_reset_end, +}; + +#define SUNXI_MUSB_RAM_BITS 11 + +/* Allwinner OTG supports up to 5 endpoints */ +static const struct musb_fifo_cfg sunxi_musb_mode_cfg_5eps[] = { + MUSB_EP_FIFO_SINGLE(1, FIFO_TX, 512), + MUSB_EP_FIFO_SINGLE(1, FIFO_RX, 512), + MUSB_EP_FIFO_SINGLE(2, FIFO_TX, 512), + MUSB_EP_FIFO_SINGLE(2, FIFO_RX, 512), + MUSB_EP_FIFO_SINGLE(3, FIFO_TX, 512), + MUSB_EP_FIFO_SINGLE(3, FIFO_RX, 512), + MUSB_EP_FIFO_SINGLE(4, FIFO_TX, 512), + MUSB_EP_FIFO_SINGLE(4, FIFO_RX, 512), + MUSB_EP_FIFO_SINGLE(5, FIFO_TX, 512), + MUSB_EP_FIFO_SINGLE(5, FIFO_RX, 512), +}; + +/* H3/V3s OTG supports only 4 endpoints */ +static const struct musb_fifo_cfg sunxi_musb_mode_cfg_4eps[] = { + MUSB_EP_FIFO_SINGLE(1, FIFO_TX, 512), + MUSB_EP_FIFO_SINGLE(1, FIFO_RX, 512), + MUSB_EP_FIFO_SINGLE(2, FIFO_TX, 512), + MUSB_EP_FIFO_SINGLE(2, FIFO_RX, 512), + MUSB_EP_FIFO_SINGLE(3, FIFO_TX, 512), + MUSB_EP_FIFO_SINGLE(3, FIFO_RX, 512), + MUSB_EP_FIFO_SINGLE(4, FIFO_TX, 512), + MUSB_EP_FIFO_SINGLE(4, FIFO_RX, 512), +}; + +static const struct musb_hdrc_config sunxi_musb_hdrc_config_5eps = { + .fifo_cfg = sunxi_musb_mode_cfg_5eps, + .fifo_cfg_size = ARRAY_SIZE(sunxi_musb_mode_cfg_5eps), + .multipoint = true, + .dyn_fifo = true, + /* Two FIFOs per endpoint, plus ep_0. */ + .num_eps = (ARRAY_SIZE(sunxi_musb_mode_cfg_5eps) / 2) + 1, + .ram_bits = SUNXI_MUSB_RAM_BITS, +}; + +static const struct musb_hdrc_config sunxi_musb_hdrc_config_4eps = { + .fifo_cfg = sunxi_musb_mode_cfg_4eps, + .fifo_cfg_size = ARRAY_SIZE(sunxi_musb_mode_cfg_4eps), + .multipoint = true, + .dyn_fifo = true, + /* Two FIFOs per endpoint, plus ep_0. */ + .num_eps = (ARRAY_SIZE(sunxi_musb_mode_cfg_4eps) / 2) + 1, + .ram_bits = SUNXI_MUSB_RAM_BITS, +}; + +static int sunxi_musb_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data pdata; + struct platform_device_info pinfo; + struct sunxi_glue *glue; + struct device_node *np = pdev->dev.of_node; + const struct sunxi_musb_cfg *cfg; + int ret; + + if (!np) { + dev_err(&pdev->dev, "Error no device tree node found\n"); + return -EINVAL; + } + + glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL); + if (!glue) + return -ENOMEM; + + memset(&pdata, 0, sizeof(pdata)); + switch (usb_get_dr_mode(&pdev->dev)) { +#if defined CONFIG_USB_MUSB_DUAL_ROLE || defined CONFIG_USB_MUSB_HOST + case USB_DR_MODE_HOST: + pdata.mode = MUSB_HOST; + glue->phy_mode = PHY_MODE_USB_HOST; + break; +#endif +#if defined CONFIG_USB_MUSB_DUAL_ROLE || defined CONFIG_USB_MUSB_GADGET + case USB_DR_MODE_PERIPHERAL: + pdata.mode = MUSB_PERIPHERAL; + glue->phy_mode = PHY_MODE_USB_DEVICE; + break; +#endif +#ifdef CONFIG_USB_MUSB_DUAL_ROLE + case USB_DR_MODE_OTG: + pdata.mode = MUSB_OTG; + glue->phy_mode = PHY_MODE_USB_OTG; + break; +#endif + default: + dev_err(&pdev->dev, "Invalid or missing 'dr_mode' property\n"); + return -EINVAL; + } + pdata.platform_ops = &sunxi_musb_ops; + + cfg = of_device_get_match_data(&pdev->dev); + if (!cfg) + return -EINVAL; + + pdata.config = cfg->hdrc_config; + + glue->dev = &pdev->dev; + INIT_WORK(&glue->work, sunxi_musb_work); + glue->host_nb.notifier_call = sunxi_musb_host_notifier; + + if (cfg->has_sram) + set_bit(SUNXI_MUSB_FL_HAS_SRAM, &glue->flags); + + if (cfg->has_reset) + set_bit(SUNXI_MUSB_FL_HAS_RESET, &glue->flags); + + if (cfg->no_configdata) + set_bit(SUNXI_MUSB_FL_NO_CONFIGDATA, &glue->flags); + + glue->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(glue->clk)) { + dev_err(&pdev->dev, "Error getting clock: %ld\n", + PTR_ERR(glue->clk)); + return PTR_ERR(glue->clk); + } + + if (test_bit(SUNXI_MUSB_FL_HAS_RESET, &glue->flags)) { + glue->rst = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(glue->rst)) + return dev_err_probe(&pdev->dev, PTR_ERR(glue->rst), + "Error getting reset\n"); + } + + glue->extcon = extcon_get_edev_by_phandle(&pdev->dev, 0); + if (IS_ERR(glue->extcon)) + return dev_err_probe(&pdev->dev, PTR_ERR(glue->extcon), + "Invalid or missing extcon\n"); + + glue->phy = devm_phy_get(&pdev->dev, "usb"); + if (IS_ERR(glue->phy)) + return dev_err_probe(&pdev->dev, PTR_ERR(glue->phy), + "Error getting phy\n"); + + glue->usb_phy = usb_phy_generic_register(); + if (IS_ERR(glue->usb_phy)) { + dev_err(&pdev->dev, "Error registering usb-phy %ld\n", + PTR_ERR(glue->usb_phy)); + return PTR_ERR(glue->usb_phy); + } + + glue->xceiv = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); + if (IS_ERR(glue->xceiv)) { + ret = PTR_ERR(glue->xceiv); + dev_err(&pdev->dev, "Error getting usb-phy %d\n", ret); + goto err_unregister_usb_phy; + } + + platform_set_drvdata(pdev, glue); + + memset(&pinfo, 0, sizeof(pinfo)); + pinfo.name = "musb-hdrc"; + pinfo.id = PLATFORM_DEVID_AUTO; + pinfo.parent = &pdev->dev; + pinfo.fwnode = of_fwnode_handle(pdev->dev.of_node); + pinfo.of_node_reused = true; + pinfo.res = pdev->resource; + pinfo.num_res = pdev->num_resources; + pinfo.data = &pdata; + pinfo.size_data = sizeof(pdata); + + glue->musb_pdev = platform_device_register_full(&pinfo); + if (IS_ERR(glue->musb_pdev)) { + ret = PTR_ERR(glue->musb_pdev); + dev_err(&pdev->dev, "Error registering musb dev: %d\n", ret); + goto err_unregister_usb_phy; + } + + return 0; + +err_unregister_usb_phy: + usb_phy_generic_unregister(glue->usb_phy); + return ret; +} + +static void sunxi_musb_remove(struct platform_device *pdev) +{ + struct sunxi_glue *glue = platform_get_drvdata(pdev); + struct platform_device *usb_phy = glue->usb_phy; + + platform_device_unregister(glue->musb_pdev); + usb_phy_generic_unregister(usb_phy); +} + +static const struct sunxi_musb_cfg sun4i_a10_musb_cfg = { + .hdrc_config = &sunxi_musb_hdrc_config_5eps, + .has_sram = true, +}; + +static const struct sunxi_musb_cfg sun6i_a31_musb_cfg = { + .hdrc_config = &sunxi_musb_hdrc_config_5eps, + .has_reset = true, +}; + +static const struct sunxi_musb_cfg sun8i_a33_musb_cfg = { + .hdrc_config = &sunxi_musb_hdrc_config_5eps, + .has_reset = true, + .no_configdata = true, +}; + +static const struct sunxi_musb_cfg sun8i_h3_musb_cfg = { + .hdrc_config = &sunxi_musb_hdrc_config_4eps, + .has_reset = true, + .no_configdata = true, +}; + +static const struct sunxi_musb_cfg suniv_f1c100s_musb_cfg = { + .hdrc_config = &sunxi_musb_hdrc_config_5eps, + .has_sram = true, + .has_reset = true, + .no_configdata = true, +}; + +static const struct of_device_id sunxi_musb_match[] = { + { .compatible = "allwinner,sun4i-a10-musb", + .data = &sun4i_a10_musb_cfg, }, + { .compatible = "allwinner,sun6i-a31-musb", + .data = &sun6i_a31_musb_cfg, }, + { .compatible = "allwinner,sun8i-a33-musb", + .data = &sun8i_a33_musb_cfg, }, + { .compatible = "allwinner,sun8i-h3-musb", + .data = &sun8i_h3_musb_cfg, }, + { .compatible = "allwinner,suniv-f1c100s-musb", + .data = &suniv_f1c100s_musb_cfg, }, + {} +}; +MODULE_DEVICE_TABLE(of, sunxi_musb_match); + +static struct platform_driver sunxi_musb_driver = { + .probe = sunxi_musb_probe, + .remove = sunxi_musb_remove, + .driver = { + .name = "musb-sunxi", + .of_match_table = sunxi_musb_match, + }, +}; +module_platform_driver(sunxi_musb_driver); + +MODULE_DESCRIPTION("Allwinner sunxi MUSB Glue Layer"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/musb/tusb6010.c b/drivers/usb/musb/tusb6010.c new file mode 100644 index 000000000..1d9571f24 --- /dev/null +++ b/drivers/usb/musb/tusb6010.c @@ -0,0 +1,1302 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TUSB6010 USB 2.0 OTG Dual Role controller + * + * Copyright (C) 2006 Nokia Corporation + * Tony Lindgren + * + * 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 +#include +#include + +#include "musb_core.h" + +struct tusb6010_glue { + struct device *dev; + struct platform_device *musb; + struct platform_device *phy; + struct gpio_desc *enable; + struct gpio_desc *intpin; +}; + +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. + */ +static 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; +} + +static void tusb_print_revision(struct musb *musb) +{ + void __iomem *tbase = musb->ctrl_base; + u8 rev; + + rev = musb->tusb_revision; + + 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)); +} + +#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; + } +} + +static u32 tusb_fifo_offset(u8 epnum) +{ + return 0x200 + (epnum * 0x20); +} + +static u32 tusb_ep_offset(u8 epnum, u16 offset) +{ + return 0x10 + offset; +} + +/* TUSB mapping: "flat" plus ep0 special cases */ +static void tusb_ep_select(void __iomem *mbase, u8 epnum) +{ + musb_writeb(mbase, MUSB_INDEX, epnum); +} + +/* + * TUSB6010 doesn't allow 8-bit access; 16-bit access is the minimum. + */ +static u8 tusb_readb(void __iomem *addr, u32 offset) +{ + u16 tmp; + u8 val; + + tmp = __raw_readw(addr + (offset & ~1)); + if (offset & 1) + val = (tmp >> 8); + else + val = tmp & 0xff; + + return val; +} + +static void tusb_writeb(void __iomem *addr, u32 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)); +} + +/* + * 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 */ + val = 0; + 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); + } +} + +static void tusb_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); +} + +static void tusb_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) + && (musb->tusb_revision == 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 void musb_do_idle(struct timer_list *t) +{ + struct musb *musb = timer_container_of(musb, t, dev_timer); + unsigned long flags; + + spin_lock_irqsave(&musb->lock, flags); + + switch (musb->xceiv->otg->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->otg->state)); + } + fallthrough; + case OTG_STATE_A_IDLE: + tusb_musb_set_vbus(musb, 0); + break; + default: + break; + } + + if (!musb->is_active) { + u32 wakeups; + + /* wait until hub_wq 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 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->otg->state == OTG_STATE_A_WAIT_BCON))) { + dev_dbg(musb->controller, "%s active, deleting timer\n", + usb_otg_state_string(musb->xceiv->otg->state)); + timer_delete(&musb->dev_timer); + last_timer = jiffies; + return; + } + + if (time_after(last_timer, timeout)) { + if (!timer_pending(&musb->dev_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->otg->state), + (unsigned long)jiffies_to_msecs(timeout - jiffies)); + mod_timer(&musb->dev_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->otg->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->otg->state) { + case OTG_STATE_A_WAIT_VRISE: + case OTG_STATE_A_WAIT_BCON: + musb->xceiv->otg->state = OTG_STATE_A_WAIT_VFALL; + break; + case OTG_STATE_A_WAIT_VFALL: + musb->xceiv->otg->state = OTG_STATE_A_IDLE; + break; + default: + musb->xceiv->otg->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->otg->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->otg->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->otg->state != OTG_STATE_B_IDLE) { + /* INTR_DISCONNECT can hide... */ + musb->xceiv->otg->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->otg->state), otg_stat); + idle_timeout = jiffies + (1 * HZ); + schedule_delayed_work(&musb->irq_work, 0); + + } else /* A-dev state machine */ { + dev_dbg(musb->controller, "vbus change, %s, otg %03x\n", + usb_otg_state_string(musb->xceiv->otg->state), otg_stat); + + switch (musb->xceiv->otg->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->otg->state), otg_stat); + + switch (musb->xceiv->otg->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->otg->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_delayed_work(&musb->irq_work, 0); + + 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 (musb->tusb_revision == 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_delayed_work(&musb->irq_work, 0); + } + 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) + timer_delete(&musb->dev_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); + + /* + * Just clear the DMA interrupt if it comes as the completion for both + * TX and RX is handled by the DMA callback in tusb6010_omap + */ + if ((int_src & TUSB_INT_SRC_TXRX_DMA_DONE)) { + u32 dma_src = musb_readl(tbase, TUSB_DMA_INT_SRC); + + dev_dbg(musb->controller, "DMA IRQ %08x\n", dma_src); + 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); + + timer_delete(&musb->dev_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) +{ + struct tusb6010_glue *glue = dev_get_drvdata(musb->controller->parent); + void __iomem *tbase = musb->ctrl_base; + unsigned long flags; + u32 reg; + int ret; + + /* + * Enable or disable power to TUSB6010. When enabling, turn on 3.3 V and + * 1.5 V voltage regulators of PM companion chip. Companion chip will then + * provide then PGOOD signal to TUSB6010 which will release it from reset. + */ + gpiod_set_value(glue->enable, 1); + + /* Wait for 100ms until TUSB6010 pulls INT pin down */ + ret = read_poll_timeout(gpiod_get_value, reg, !reg, 5000, 100000, true, + glue->intpin); + if (ret) { + pr_err("tusb: Powerup response failed\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; + } + + musb->tusb_revision = tusb_get_revision(musb); + tusb_print_revision(musb); + if (musb->tusb_revision < 2) { + printk(KERN_ERR "tusb: Unsupported TUSB6010 revision %i\n", + musb->tusb_revision); + 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); + + gpiod_set_value(glue->enable, 0); + msleep(10); + + return -ENODEV; +} + +static int tusb_musb_init(struct musb *musb) +{ + struct platform_device *pdev; + struct resource *mem; + void __iomem *sync = NULL; + int ret; + + 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); + if (!mem) { + pr_debug("no async dma resource?\n"); + ret = -ENODEV; + goto done; + } + 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; + + timer_setup(&musb->dev_timer, musb_do_idle, 0); + +done: + if (ret < 0) { + if (sync) + iounmap(sync); + + usb_put_phy(musb->xceiv); + } + return ret; +} + +static int tusb_musb_exit(struct musb *musb) +{ + struct tusb6010_glue *glue = dev_get_drvdata(musb->controller->parent); + + timer_delete_sync(&musb->dev_timer); + the_musb = NULL; + + gpiod_set_value(glue->enable, 0); + msleep(10); + + iounmap(musb->sync_va); + + usb_put_phy(musb->xceiv); + return 0; +} + +static const struct musb_platform_ops tusb_ops = { + .quirks = MUSB_DMA_TUSB_OMAP | MUSB_IN_TUSB | + MUSB_G_NO_SKB_RESERVE, + .init = tusb_musb_init, + .exit = tusb_musb_exit, + + .ep_offset = tusb_ep_offset, + .ep_select = tusb_ep_select, + .fifo_offset = tusb_fifo_offset, + .readb = tusb_readb, + .writeb = tusb_writeb, + .read_fifo = tusb_read_fifo, + .write_fifo = tusb_write_fifo, +#ifdef CONFIG_USB_TUSB_OMAP_DMA + .dma_init = tusb_dma_controller_create, + .dma_exit = tusb_dma_controller_destroy, +#endif + .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 const struct platform_device_info tusb_dev_info = { + .name = "musb-hdrc", + .id = PLATFORM_DEVID_AUTO, + .dma_mask = DMA_BIT_MASK(32), +}; + +static int tusb_probe(struct platform_device *pdev) +{ + struct resource musb_resources[3]; + struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct platform_device *musb; + struct tusb6010_glue *glue; + struct platform_device_info pinfo; + int ret; + + glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL); + if (!glue) + return -ENOMEM; + + glue->dev = &pdev->dev; + + glue->enable = devm_gpiod_get(glue->dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(glue->enable)) + return dev_err_probe(glue->dev, PTR_ERR(glue->enable), + "could not obtain power on/off GPIO\n"); + glue->intpin = devm_gpiod_get(glue->dev, "int", GPIOD_IN); + if (IS_ERR(glue->intpin)) + return dev_err_probe(glue->dev, PTR_ERR(glue->intpin), + "could not obtain INT GPIO\n"); + + pdata->platform_ops = &tusb_ops; + + usb_phy_generic_register(); + platform_set_drvdata(pdev, glue); + + memset(musb_resources, 0x00, sizeof(*musb_resources) * + ARRAY_SIZE(musb_resources)); + + musb_resources[0].name = pdev->resource[0].name; + musb_resources[0].start = pdev->resource[0].start; + musb_resources[0].end = pdev->resource[0].end; + musb_resources[0].flags = pdev->resource[0].flags; + + musb_resources[1].name = pdev->resource[1].name; + musb_resources[1].start = pdev->resource[1].start; + musb_resources[1].end = pdev->resource[1].end; + musb_resources[1].flags = pdev->resource[1].flags; + + musb_resources[2] = DEFINE_RES_IRQ_NAMED(gpiod_to_irq(glue->intpin), "mc"); + + pinfo = tusb_dev_info; + pinfo.parent = &pdev->dev; + pinfo.res = musb_resources; + pinfo.num_res = ARRAY_SIZE(musb_resources); + pinfo.data = pdata; + pinfo.size_data = sizeof(*pdata); + + glue->musb = musb = platform_device_register_full(&pinfo); + if (IS_ERR(musb)) { + ret = PTR_ERR(musb); + dev_err(&pdev->dev, "failed to register musb device: %d\n", ret); + return ret; + } + + return 0; +} + +static void tusb_remove(struct platform_device *pdev) +{ + struct tusb6010_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + usb_phy_generic_unregister(glue->phy); +} + +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 000000000..8a253564f --- /dev/null +++ b/drivers/usb/musb/tusb6010.h @@ -0,0 +1,215 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Definitions for TUSB6010 USB 2.0 OTG Dual Role controller + * + * Copyright (C) 2006 Nokia Corporation + * Tony Lindgren + */ + +#ifndef __TUSB6010_H__ +#define __TUSB6010_H__ + +/* 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 000000000..36ee61a52 --- /dev/null +++ b/drivers/usb/musb/tusb6010_omap.c @@ -0,0 +1,642 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TUSB6010 USB 2.0 OTG Dual Role controller OMAP DMA interface + * + * Copyright (C) 2006 Nokia Corporation + * Tony Lindgren + */ +#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 */ + +struct tusb_dma_data { + s8 dmareq; + struct dma_chan *chan; +}; + +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; + + struct tusb_dma_data *dma_data; + + 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; + void __iomem *tbase; + + struct tusb_dma_data dma_pool[MAX_DMAREQ]; + unsigned multichannel:1; +}; + +/* + * 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(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; + + spin_lock_irqsave(&musb->lock, flags); + + dev_dbg(musb->controller, "ep%i %s dma callback\n", + chdat->epnum, chdat->tx ? "tx" : "rx"); + + 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 XFR_SIZE: 0x%08lx\n", + chdat->tx ? "tx" : "rx", 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; + + 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_addr = hw_ep->fifo_sync; + u32 dma_remaining; + u16 csr; + u32 psize; + struct tusb_dma_data *dma_data; + struct dma_async_tx_descriptor *dma_desc; + struct dma_slave_config dma_cfg; + enum dma_transfer_direction dma_dir; + u32 port_window; + int ret; + + 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, not using: %08x\n", + chdat->tx ? "tx" : "rx", 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; + + dma_data = chdat->dma_data; + if (!tusb_dma->multichannel) { + 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 (dma_data->dmareq < 0) { + /* REVISIT: This should get blocked earlier, happens + * with MSC ErrorRecoveryTest + */ + WARN_ON(1); + return false; + } + } + + 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_dir = DMA_MEM_TO_DEV; + dma_map_single(dev, phys_to_virt(dma_addr), len, + DMA_TO_DEVICE); + } else { + dma_dir = DMA_DEV_TO_MEM; + dma_map_single(dev, phys_to_virt(dma_addr), len, + DMA_FROM_DEVICE); + } + + memset(&dma_cfg, 0, sizeof(dma_cfg)); + + /* Use 16-bit transfer if dma_addr is not 32-bit aligned */ + if ((dma_addr & 0x3) == 0) { + dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + port_window = 8; + } else { + dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + port_window = 16; + + fifo_addr = hw_ep->fifo_async; + } + + dev_dbg(musb->controller, + "ep%i %s dma: %pad len: %u(%u) packet_sz: %i(%i)\n", + chdat->epnum, chdat->tx ? "tx" : "rx", &dma_addr, + chdat->transfer_len, len, chdat->transfer_packet_sz, packet_sz); + + dma_cfg.src_addr = fifo_addr; + dma_cfg.dst_addr = fifo_addr; + dma_cfg.src_port_window_size = port_window; + dma_cfg.src_maxburst = port_window; + dma_cfg.dst_port_window_size = port_window; + dma_cfg.dst_maxburst = port_window; + + ret = dmaengine_slave_config(dma_data->chan, &dma_cfg); + if (ret) { + dev_err(musb->controller, "DMA slave config failed: %d\n", ret); + return false; + } + + dma_desc = dmaengine_prep_slave_single(dma_data->chan, dma_addr, + chdat->transfer_len, dma_dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!dma_desc) { + dev_err(musb->controller, "DMA prep_slave_single failed\n"); + return false; + } + + dma_desc->callback = tusb_omap_dma_cb; + dma_desc->callback_param = channel; + dmaengine_submit(dma_desc); + + dev_dbg(musb->controller, + "ep%i %s using %i-bit %s dma from %pad to %pad\n", + chdat->epnum, chdat->tx ? "tx" : "rx", + dma_cfg.src_addr_width * 8, + ((dma_addr & 0x3) == 0) ? "sync" : "async", + (dma_dir == DMA_MEM_TO_DEV) ? &dma_addr : &fifo_addr, + (dma_dir == DMA_MEM_TO_DEV) ? &fifo_addr : &dma_addr); + + /* + * Prepare MUSB for DMA transfer + */ + musb_ep_select(mbase, chdat->epnum); + if (chdat->tx) { + 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 { + 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 */ + dma_async_issue_pending(dma_data->chan); + + if (chdat->tx) { + /* Send transfer_packet_sz packets at a time */ + psize = musb_readl(ep_conf, TUSB_EP_MAX_PACKET_SIZE_OFFSET); + psize &= ~0x7ff; + psize |= chdat->transfer_packet_sz; + musb_writel(ep_conf, TUSB_EP_MAX_PACKET_SIZE_OFFSET, psize); + + 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 */ + psize = musb_readl(ep_conf, TUSB_EP_MAX_PACKET_SIZE_OFFSET); + psize &= ~(0x7ff << 16); + psize |= (chdat->transfer_packet_sz << 16); + musb_writel(ep_conf, TUSB_EP_MAX_PACKET_SIZE_OFFSET, psize); + + 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); + + if (chdat->dma_data) + dmaengine_terminate_all(chdat->dma_data->chan); + + 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; + + 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->dma_data = &chdat->tusb_dma->dma_pool[dmareq_nr]; + + return 0; +} + +static inline void tusb_omap_dma_free_dmareq(struct tusb_omap_dma_ch *chdat) +{ + u32 reg; + + if (!chdat || !chdat->dma_data || chdat->dma_data->dmareq < 0) + return; + + reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP); + reg &= ~(0x1f << (chdat->dma_data->dmareq * 5)); + musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg); + + chdat->dma_data = NULL; +} + +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; + struct tusb_omap_dma *tusb_dma; + struct musb *musb; + struct dma_channel *channel = NULL; + struct tusb_omap_dma_ch *chdat = NULL; + struct tusb_dma_data *dma_data = NULL; + + tusb_dma = container_of(c, struct tusb_omap_dma, controller); + musb = tusb_dma->controller.musb; + + /* 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; + + chdat->musb = tusb_dma->controller.musb; + chdat->tbase = tusb_dma->tbase; + chdat->hw_ep = hw_ep; + chdat->epnum = hw_ep->epnum; + chdat->completed_len = 0; + chdat->tusb_dma = tusb_dma; + if (tx) + chdat->tx = 1; + else + chdat->tx = 0; + + channel->max_len = 0x7fffffff; + channel->desired_mode = 0; + channel->actual_len = 0; + + if (!chdat->dma_data) { + if (tusb_dma->multichannel) { + ret = tusb_omap_dma_allocate_dmareq(chdat); + if (ret != 0) + goto free_dmareq; + } else { + chdat->dma_data = &tusb_dma->dma_pool[0]; + } + } + + dma_data = chdat->dma_data; + + dev_dbg(musb->controller, "ep%i %s dma: %s dmareq%i\n", + chdat->epnum, + chdat->tx ? "tx" : "rx", + tusb_dma->multichannel ? "shared" : "dedicated", + dma_data->dmareq); + + 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; + + dev_dbg(musb->controller, "Release for ep%i\n", chdat->epnum); + + channel->status = MUSB_DMA_STATUS_UNKNOWN; + + dmaengine_terminate_sync(chdat->dma_data->chan); + tusb_omap_dma_free_dmareq(chdat); + + channel = NULL; +} + +void tusb_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); + } + + /* Free up the DMA channels */ + if (tusb_dma && tusb_dma->dma_pool[i].chan) + dma_release_channel(tusb_dma->dma_pool[i].chan); + } + + kfree(tusb_dma); +} +EXPORT_SYMBOL_GPL(tusb_dma_controller_destroy); + +static int tusb_omap_allocate_dma_pool(struct tusb_omap_dma *tusb_dma) +{ + struct musb *musb = tusb_dma->controller.musb; + int i; + int ret = 0; + + for (i = 0; i < MAX_DMAREQ; i++) { + struct tusb_dma_data *dma_data = &tusb_dma->dma_pool[i]; + + /* + * Request DMA channels: + * - one channel in case of non multichannel mode + * - MAX_DMAREQ number of channels in multichannel mode + */ + if (i == 0 || tusb_dma->multichannel) { + char ch_name[8]; + + sprintf(ch_name, "dmareq%d", i); + dma_data->chan = dma_request_chan(musb->controller, + ch_name); + if (IS_ERR(dma_data->chan)) { + dev_err(musb->controller, + "Failed to request %s\n", ch_name); + ret = PTR_ERR(dma_data->chan); + goto dma_error; + } + + dma_data->dmareq = i; + } else { + dma_data->dmareq = -1; + } + } + + return 0; + +dma_error: + for (; i >= 0; i--) { + struct tusb_dma_data *dma_data = &tusb_dma->dma_pool[i]; + + if (dma_data->dmareq >= 0) + dma_release_channel(dma_data->chan); + } + + return ret; +} + +struct dma_controller * +tusb_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_obj(struct tusb_omap_dma); + if (!tusb_dma) + goto out; + + tusb_dma->controller.musb = musb; + tusb_dma->tbase = musb->ctrl_base; + + 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 (musb->tusb_revision >= 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_obj(struct dma_channel); + if (!ch) + goto cleanup; + + dma_channel_pool[i] = ch; + + chdat = kzalloc_obj(struct tusb_omap_dma_ch); + if (!chdat) + goto cleanup; + + ch->status = MUSB_DMA_STATUS_UNKNOWN; + ch->private_data = chdat; + } + + if (tusb_omap_allocate_dma_pool(tusb_dma)) + goto cleanup; + + return &tusb_dma->controller; + +cleanup: + musb_dma_controller_destroy(&tusb_dma->controller); +out: + return NULL; +} +EXPORT_SYMBOL_GPL(tusb_dma_controller_create); diff --git a/drivers/usb/musb/ux500.c b/drivers/usb/musb/ux500.c new file mode 100644 index 000000000..8c2a43d99 --- /dev/null +++ b/drivers/usb/musb/ux500.c @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2010 ST-Ericsson AB + * Mian Yousaf Kaukab + * + * Based on omap2430.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "musb_core.h" + +static const struct musb_hdrc_config ux500_musb_hdrc_config = { + .multipoint = true, + .dyn_fifo = true, + .num_eps = 16, + .ram_bits = 16, +}; + +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->otg->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->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... + */ + 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->otg->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->otg->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->otg->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 = { + .quirks = MUSB_DMA_UX500 | MUSB_INDEXED_EP, +#ifdef CONFIG_USB_UX500_DMA + .dma_init = ux500_dma_controller_create, + .dma_exit = ux500_dma_controller_destroy, +#endif + .init = ux500_musb_init, + .exit = ux500_musb_exit, + .fifo_mode = 5, + + .set_vbus = ux500_musb_set_vbus, +}; + +static struct musb_hdrc_platform_data * +ux500_of_probe(struct platform_device *pdev, struct device_node *np) +{ + struct musb_hdrc_platform_data *pdata; + const char *mode; + int strlen; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + mode = of_get_property(np, "dr_mode", &strlen); + if (!mode) { + dev_err(&pdev->dev, "No 'dr_mode' property found\n"); + return NULL; + } + + if (strlen > 0) { + if (!strcmp(mode, "host")) + pdata->mode = MUSB_HOST; + if (!strcmp(mode, "otg")) + pdata->mode = MUSB_OTG; + if (!strcmp(mode, "peripheral")) + pdata->mode = MUSB_PERIPHERAL; + } + + return pdata; +} + +static int ux500_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct device_node *np = pdev->dev.of_node; + struct platform_device *musb; + struct ux500_glue *glue; + struct clk *clk; + int ret = -ENOMEM; + + if (!pdata) { + if (np) { + pdata = ux500_of_probe(pdev, np); + if (!pdata) + goto err0; + + pdev->dev.platform_data = pdata; + } else { + dev_err(&pdev->dev, "no pdata or device tree found\n"); + goto err0; + } + } + + glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL); + if (!glue) + 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; + } + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + ret = PTR_ERR(clk); + goto err1; + } + + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable clock\n"); + goto err1; + } + + musb->dev.parent = &pdev->dev; + musb->dev.dma_mask = &pdev->dev.coherent_dma_mask; + musb->dev.coherent_dma_mask = pdev->dev.coherent_dma_mask; + device_set_of_node_from_dev(&musb->dev, &pdev->dev); + + glue->dev = &pdev->dev; + glue->musb = musb; + glue->clk = clk; + + pdata->platform_ops = &ux500_ops; + pdata->config = &ux500_musb_hdrc_config; + + 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 err2; + } + + ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); + if (ret) { + dev_err(&pdev->dev, "failed to add platform_data\n"); + goto err2; + } + + ret = platform_device_add(musb); + if (ret) { + dev_err(&pdev->dev, "failed to register musb device\n"); + goto err2; + } + + return 0; + +err2: + clk_disable_unprepare(clk); + +err1: + platform_device_put(musb); + +err0: + return ret; +} + +static void ux500_remove(struct platform_device *pdev) +{ + struct ux500_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + clk_disable_unprepare(glue->clk); +} + +#ifdef CONFIG_PM_SLEEP +static int ux500_suspend(struct device *dev) +{ + struct ux500_glue *glue = dev_get_drvdata(dev); + struct musb *musb = glue_to_musb(glue); + + if (musb) + 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; + } + + if (musb) + usb_phy_set_suspend(musb->xceiv, 0); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(ux500_pm_ops, ux500_suspend, ux500_resume); + +static const struct of_device_id ux500_match[] = { + { .compatible = "stericsson,db8500-musb", }, + {} +}; + +MODULE_DEVICE_TABLE(of, ux500_match); + +static struct platform_driver ux500_driver = { + .probe = ux500_probe, + .remove = ux500_remove, + .driver = { + .name = "musb-ux500", + .pm = &ux500_pm_ops, + .of_match_table = ux500_match, + }, +}; + +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 000000000..bbb4978c0 --- /dev/null +++ b/drivers/usb/musb/ux500_dma.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "musb_core.h" + +static const char *iep_chan_names[] = { "iep_1_9", "iep_2_10", "iep_3_11", "iep_4_12", + "iep_5_13", "iep_6_14", "iep_7_15", "iep_8" }; +static const char *oep_chan_names[] = { "oep_1_9", "oep_2_10", "oep_3_11", "oep_4_12", + "oep_5_13", "oep_6_14", "oep_7_15", "oep_8" }; + +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_TX_CHANNELS]; + struct ux500_dma_channel tx_channel[UX500_MUSB_DMA_NUM_RX_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; + struct musb *musb = ux500_channel->controller->private_data; + dma_addr_t usb_fifo_addr = (musb->io.fifo_offset(hw_ep->epnum) + + ux500_channel->controller->phy_base); + + dev_dbg(musb->controller, + "packet_sz=%d, mode=%d, dma_addr=0x%llx, 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; + + dmaengine_slave_config(dma_chan, &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; + + /* 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; + + if (ch_num >= UX500_MUSB_DMA_NUM_RX_TX_CHANNELS) + 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); + + 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); + } + + dmaengine_terminate_all(ux500_channel->dma_chan); + channel->status = MUSB_DMA_STATUS_FREE; + } + return 0; +} + +static void ux500_dma_controller_stop(struct ux500_dma_controller *controller) +{ + struct ux500_dma_channel *ux500_channel; + struct dma_channel *channel; + u8 ch_num; + + for (ch_num = 0; ch_num < UX500_MUSB_DMA_NUM_RX_TX_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 < UX500_MUSB_DMA_NUM_RX_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); + } +} + +static int ux500_dma_controller_start(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_get_platdata(dev); + struct ux500_musb_board_data *data; + struct dma_channel *dma_channel = NULL; + char **chan_names; + u32 ch_num; + u8 dir; + u8 is_tx = 0; + + void **param_array; + struct ux500_dma_channel *channel_array; + dma_cap_mask_t mask; + + if (!plat) { + dev_err(musb->controller, "No platform data\n"); + return -EINVAL; + } + + data = plat->board_data; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + /* Prepare the loop for RX channels */ + channel_array = controller->rx_channel; + param_array = data ? data->dma_rx_param_array : NULL; + chan_names = (char **)iep_chan_names; + + for (dir = 0; dir < 2; dir++) { + for (ch_num = 0; + ch_num < UX500_MUSB_DMA_NUM_RX_TX_CHANNELS; + 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_chan(dev, chan_names[ch_num]); + + if (IS_ERR(ux500_channel->dma_chan)) + ux500_channel->dma_chan = + dma_request_channel(mask, + data ? + data->dma_filter : + NULL, + param_array ? + param_array[ch_num] : + NULL); + + 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(controller); + + return -EBUSY; + } + + } + + /* Prepare the loop for TX channels */ + channel_array = controller->tx_channel; + param_array = data ? data->dma_tx_param_array : NULL; + chan_names = (char **)oep_chan_names; + is_tx = 1; + } + + return 0; +} + +void ux500_dma_controller_destroy(struct dma_controller *c) +{ + struct ux500_dma_controller *controller = container_of(c, + struct ux500_dma_controller, controller); + + ux500_dma_controller_stop(controller); + kfree(controller); +} +EXPORT_SYMBOL_GPL(ux500_dma_controller_destroy); + +struct dma_controller * +ux500_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; + int ret; + + controller = kzalloc_obj(*controller); + 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.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; + + ret = ux500_dma_controller_start(controller); + if (ret) + goto plat_get_fail; + return &controller->controller; + +plat_get_fail: + kfree(controller); +kzalloc_fail: + return NULL; +} +EXPORT_SYMBOL_GPL(ux500_dma_controller_create); diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig new file mode 100644 index 000000000..b7acf3966 --- /dev/null +++ b/drivers/usb/phy/Kconfig @@ -0,0 +1,163 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Physical Layer USB driver configuration +# +menu "USB Physical Layer drivers" + +config USB_PHY + select EXTCON + def_bool n + +# +# USB Transceiver Drivers +# +config AB8500_USB + tristate "AB8500 USB Transceiver Driver" + depends on AB8500_CORE + select USB_PHY + 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 + tristate "Freescale USB OTG Transceiver Driver" + depends on USB_EHCI_FSL && USB_FSL_USB2 && USB_OTG_FSM=y && PM + depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y' + select USB_PHY + help + Enable this to support Freescale USB OTG transceiver. + +config KEYSTONE_USB_PHY + tristate "Keystone USB PHY Driver" + depends on ARCH_KEYSTONE || COMPILE_TEST + depends on NOP_USB_XCEIV + help + Enable this to support Keystone USB phy. This driver provides + interface to interact with USB 2.0 and USB 3.0 PHY that is part + of the Keystone SOC. + +config NOP_USB_XCEIV + tristate "NOP USB Transceiver Driver" + depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, NOP can't be built-in + select USB_PHY + 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 AM335X_CONTROL_USB + tristate + +config AM335X_PHY_USB + tristate "AM335x USB PHY Driver" + depends on ARM || COMPILE_TEST + depends on NOP_USB_XCEIV + select USB_PHY + select AM335X_CONTROL_USB + select USB_COMMON + help + This driver provides PHY support for that phy which part for the + AM335x SoC. + +config TWL6030_USB + tristate "TWL6030 USB Transceiver Driver" + depends on TWL4030_CORE && OMAP_USB2 && USB_MUSB_OMAP2PLUS + depends on OF + 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 || COMPILE_TEST + depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y' + depends on !USB_CONN_GPIO + select USB_PHY + 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. This driver is for devices that do + NOT support role switch. OTG devices that can do role switch + (master/peripheral) shall use the USB based connection + detection driver USB_CONN_GPIO. + +config OMAP_OTG + tristate "OMAP USB OTG controller driver" + depends on ARCH_OMAP_OTG && EXTCON + help + Enable this to support some transceivers on OMAP1 platforms. OTG + controller is needed to switch between host and peripheral modes. + + This driver can also be built as a module. If so, the module + will be called phy-omap-otg. + +config TAHVO_USB + tristate "Tahvo USB transceiver driver" + depends on MFD_RETU + depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y' + select USB_PHY + help + Enable this to support USB transceiver on Tahvo. This is used + at least on Nokia 770. + +config TAHVO_USB_HOST_BY_DEFAULT + depends on TAHVO_USB + bool "Device in USB host mode by default" + help + Say Y here, if you want the device to enter USB host mode + by default on bootup. + +config USB_ISP1301 + tristate "NXP ISP1301 USB transceiver support" + depends on USB || USB_GADGET + depends on I2C + select USB_PHY + 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 phy-isp1301. + +config USB_MXS_PHY + tristate "Freescale MXS USB PHY support" + depends on ARCH_MXC || ARCH_MXS + select STMP_DEVICE + select USB_PHY + 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_TEGRA_PHY + tristate "NVIDIA Tegra USB PHY Driver" + depends on ARCH_TEGRA || COMPILE_TEST + select USB_COMMON + select USB_PHY + select USB_ULPI + help + This driver provides PHY support for the USB controllers found + on NVIDIA Tegra SoC's. + +config USB_ULPI + bool "Generic ULPI Transceiver Driver" + depends on ARM || ARM64 || COMPILE_TEST + select USB_ULPI_VIEWPORT + help + Enable this to support ULPI connected USB OTG transceivers which + are likely found on embedded boards. + +config USB_ULPI_VIEWPORT + bool + help + Provides read/write operations to the ULPI phy register set for + controllers with a viewport register (e.g. Chipidea/ARC controllers). + +endmenu diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile new file mode 100644 index 000000000..ceae46c53 --- /dev/null +++ b/drivers/usb/phy/Makefile @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for physical layer USB drivers +# +obj-$(CONFIG_USB_PHY) += phy.o +obj-$(CONFIG_OF) += of.o + +# transceiver drivers, keep the list sorted + +obj-$(CONFIG_AB8500_USB) += phy-ab8500-usb.o +obj-$(CONFIG_FSL_USB2_OTG) += phy-fsl-usb.o +obj-$(CONFIG_NOP_USB_XCEIV) += phy-generic.o +obj-$(CONFIG_TAHVO_USB) += phy-tahvo.o +obj-$(CONFIG_AM335X_CONTROL_USB) += phy-am335x-control.o +obj-$(CONFIG_AM335X_PHY_USB) += phy-am335x.o +obj-$(CONFIG_OMAP_OTG) += phy-omap-otg.o +obj-$(CONFIG_TWL6030_USB) += phy-twl6030-usb.o +obj-$(CONFIG_USB_TEGRA_PHY) += phy-tegra-usb.o +obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio-vbus-usb.o +obj-$(CONFIG_USB_ISP1301) += phy-isp1301.o +obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o +obj-$(CONFIG_USB_ULPI) += phy-ulpi.o +obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o +obj-$(CONFIG_KEYSTONE_USB_PHY) += phy-keystone.o diff --git a/drivers/usb/phy/of.c b/drivers/usb/phy/of.c new file mode 100644 index 000000000..1ab134f45 --- /dev/null +++ b/drivers/usb/phy/of.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * USB of helper code + */ + +#include +#include +#include +#include +#include + +static const char *const usbphy_modes[] = { + [USBPHY_INTERFACE_MODE_UNKNOWN] = "", + [USBPHY_INTERFACE_MODE_UTMI] = "utmi", + [USBPHY_INTERFACE_MODE_UTMIW] = "utmi_wide", + [USBPHY_INTERFACE_MODE_ULPI] = "ulpi", + [USBPHY_INTERFACE_MODE_SERIAL] = "serial", + [USBPHY_INTERFACE_MODE_HSIC] = "hsic", +}; + +/** + * of_usb_get_phy_mode - Get phy mode for given device_node + * @np: Pointer to the given device_node + * + * The function gets phy interface string from property 'phy_type', + * and returns the corresponding enum usb_phy_interface + */ +enum usb_phy_interface of_usb_get_phy_mode(struct device_node *np) +{ + const char *phy_type; + int err, i; + + err = of_property_read_string(np, "phy_type", &phy_type); + if (err < 0) + return USBPHY_INTERFACE_MODE_UNKNOWN; + + for (i = 0; i < ARRAY_SIZE(usbphy_modes); i++) + if (!strcmp(phy_type, usbphy_modes[i])) + return i; + + return USBPHY_INTERFACE_MODE_UNKNOWN; +} +EXPORT_SYMBOL_GPL(of_usb_get_phy_mode); diff --git a/drivers/usb/phy/phy-ab8500-usb.c b/drivers/usb/phy/phy-ab8500-usb.c new file mode 100644 index 000000000..6a98aeeea --- /dev/null +++ b/drivers/usb/phy/phy-ab8500-usb.c @@ -0,0 +1,1011 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * USB transceiver driver for AB8500 family chips + * + * Copyright (C) 2010-2013 ST-Ericsson AB + * Mian Yousaf Kaukab + * Avinash Kumar + * Thirupathi Chippakurthy + */ + +#include +#include +#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 + +/* Bank AB8500_INTERRUPT */ +#define AB8500_IT_SOURCE2_REG 0x01 + +#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_BIT_SOURCE2_VBUSDET (1 << 7) + +#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, + USB_UART +}; + +/* Register USB_LINK_STATUS interrupt */ +#define AB8500_USB_FLAG_USE_LINK_STATUS_IRQ (1 << 0) +/* Register ID_WAKEUP_F interrupt */ +#define AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ (1 << 1) +/* Register VBUS_DET_F interrupt */ +#define AB8500_USB_FLAG_USE_VBUS_DET_IRQ (1 << 2) +/* Driver is using the ab-iddet driver*/ +#define AB8500_USB_FLAG_USE_AB_IDDET (1 << 3) +/* Enable setting regulators voltage */ +#define AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE (1 << 4) + +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 clk *sysclk; + 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; + bool enabled_charging_detection; + unsigned int flags; +}; + +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 (ab->flags & AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE) { + 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_load(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 (ab->flags & AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE) { + 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 (ab->flags & AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE) { + 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_load(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"); + + if (clk_prepare_enable(ab->sysclk)) + dev_err(ab->dev, "can't prepare/enable clock\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); + + clk_disable_unprepare(ab->sysclk); + + 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 iddet, 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; + fallthrough; + 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.otg->state = OTG_STATE_B_IDLE; + usb_phy_set_event(&ab->phy, USB_EVENT_NONE); + break; + + case USB_LINK_ACA_RID_C_NM_8505: + event = UX500_MUSB_RIDC; + fallthrough; + 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); + usb_phy_set_event(&ab->phy, USB_EVENT_ENUMERATED); + } + 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; + fallthrough; + 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); + usb_phy_set_event(&ab->phy, USB_EVENT_CHARGER); + break; + + /* + * FIXME: For now we rely on the boot firmware to set up the necessary + * PHY/pin configuration for UART mode. + * + * AB8505 does not seem to report any status change for UART cables, + * possibly because it cannot detect them autonomously. + * We may need to measure the ID resistance manually to reliably + * detect UART cables after bootup. + */ + case USB_LINK_SAMSUNG_UART_CBL_PHY_EN_8505: + case USB_LINK_SAMSUNG_UART_CBL_PHY_DISB_8505: + if (ab->mode == USB_IDLE) { + ab->mode = USB_UART; + ab8500_usb_peri_phy_en(ab); + } + + 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; + fallthrough; + 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.otg->state = OTG_STATE_B_IDLE; + usb_phy_set_event(&ab->phy, USB_EVENT_NONE); + 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; + fallthrough; + 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); + usb_phy_set_event(&ab->phy, USB_EVENT_ENUMERATED); + } + if (event != UX500_MUSB_RIDC) + event = UX500_MUSB_VBUS; + break; + + case USB_LINK_ACA_RID_A_8500: + event = UX500_MUSB_RIDA; + fallthrough; + 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); + usb_phy_set_event(&ab->phy, USB_EVENT_CHARGER); + 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 function 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; + + ret = abx500_get_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_LINE_STAT_REG, ®); + if (ret < 0) + return ret; + lsts = (reg >> 3) & 0x0F; + ret = ab8500_usb_link_status_update(ab, lsts); + } else if (is_ab8505(ab->ab8500)) { + enum ab8505_usb_link_status lsts; + + ret = abx500_get_register_interruptible(ab->dev, + AB8500_USB, AB8505_USB_LINE_STAT_REG, ®); + if (ret < 0) + return ret; + lsts = (reg >> 3) & 0x1F; + ret = ab8505_usb_link_status_update(ab, lsts); + } + + return ret; +} + +/* + * Disconnection Sequence: + * 1. Disconnect 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 = USB_EVENT_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 (ab->mode == USB_UART) { + ab8500_usb_peri_phy_dis(ab); + ab->mode = USB_IDLE; + } + + 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 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->usb_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->usb_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 void ab8500_usb_restart_phy(struct ab8500_usb *ab) +{ + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + AB8500_BIT_PHY_CTRL_DEVICE_EN, + AB8500_BIT_PHY_CTRL_DEVICE_EN); + + udelay(100); + + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + AB8500_BIT_PHY_CTRL_DEVICE_EN, + 0); + + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + AB8500_BIT_PHY_CTRL_HOST_EN, + AB8500_BIT_PHY_CTRL_HOST_EN); + + udelay(100); + + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + AB8500_BIT_PHY_CTRL_HOST_EN, + 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; + + if (ab->flags & AB8500_USB_FLAG_USE_LINK_STATUS_IRQ) { + irq = platform_get_irq_byname(pdev, "USB_LINK_STATUS"); + if (irq < 0) + return irq; + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + ab8500_usb_link_status_irq, + IRQF_NO_SUSPEND | IRQF_SHARED | IRQF_ONESHOT, + "usb-link-status", ab); + if (err < 0) { + dev_err(ab->dev, "request_irq failed for link status irq\n"); + return err; + } + } + + if (ab->flags & AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ) { + irq = platform_get_irq_byname(pdev, "ID_WAKEUP_F"); + if (irq < 0) + return irq; + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + ab8500_usb_disconnect_irq, + IRQF_NO_SUSPEND | IRQF_SHARED | IRQF_ONESHOT, + "usb-id-fall", ab); + if (err < 0) { + dev_err(ab->dev, "request_irq failed for ID fall irq\n"); + return err; + } + } + + if (ab->flags & AB8500_USB_FLAG_USE_VBUS_DET_IRQ) { + irq = platform_get_irq_byname(pdev, "VBUS_DET_F"); + if (irq < 0) + return irq; + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + ab8500_usb_disconnect_irq, + IRQF_NO_SUSPEND | IRQF_SHARED | IRQF_ONESHOT, + "usb-vbus-fall", ab); + if (err < 0) { + dev_err(ab->dev, "request_irq failed for Vbus fall irq\n"); + return err; + } + } + + return 0; +} + +static void ab8500_usb_set_ab8500_tuning_values(struct ab8500_usb *ab) +{ + int err; + + /* 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 register 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); +} + +static void ab8500_usb_set_ab8505_tuning_values(struct ab8500_usb *ab) +{ + int err; + + /* 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 register 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); +} + +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.otg->state = OTG_STATE_UNDEFINED; + + otg->usb_phy = &ab->phy; + otg->set_host = ab8500_usb_set_host; + otg->set_peripheral = ab8500_usb_set_peripheral; + + if (is_ab8500(ab->ab8500)) { + ab->flags |= AB8500_USB_FLAG_USE_LINK_STATUS_IRQ | + AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ | + AB8500_USB_FLAG_USE_VBUS_DET_IRQ | + AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; + } else if (is_ab8505(ab->ab8500)) { + ab->flags |= AB8500_USB_FLAG_USE_LINK_STATUS_IRQ | + AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ | + AB8500_USB_FLAG_USE_VBUS_DET_IRQ | + AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; + } + + /* Disable regulator voltage setting for AB8500 <= v2.0 */ + if (is_ab8500_2p0_or_earlier(ab->ab8500)) + ab->flags &= ~AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; + + platform_set_drvdata(pdev, ab); + + /* 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; + + ab->sysclk = devm_clk_get(ab->dev, "sysclk"); + if (IS_ERR(ab->sysclk)) { + dev_err(ab->dev, "Could not get sysclk.\n"); + return PTR_ERR(ab->sysclk); + } + + 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; + } + + if (is_ab8500(ab->ab8500) && !is_ab8500_2p0_or_earlier(ab->ab8500)) + /* Phy tuning values for AB8500 > v2.0 */ + ab8500_usb_set_ab8500_tuning_values(ab); + else if (is_ab8505(ab->ab8500)) + /* Phy tuning values for AB8505 */ + ab8500_usb_set_ab8505_tuning_values(ab); + + /* Needed to enable ID detection. */ + ab8500_usb_wd_workaround(ab); + + /* + * This is required for usb-link-status to work properly when a + * cable is connected at boot time. + */ + ab8500_usb_restart_phy(ab); + + abx500_usb_link_status_update(ab); + + dev_info(&pdev->dev, "revision 0x%2x driver initialized\n", rev); + + return 0; +} + +static void 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); +} + +static const struct platform_device_id ab8500_usb_devtype[] = { + { .name = "ab8500-usb", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, ab8500_usb_devtype); + +static struct platform_driver ab8500_usb_driver = { + .probe = ab8500_usb_probe, + .remove = ab8500_usb_remove, + .id_table = ab8500_usb_devtype, + .driver = { + .name = "abx5x0-usb", + }, +}; + +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_AUTHOR("ST-Ericsson AB"); +MODULE_DESCRIPTION("AB8500 family usb transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-am335x-control.c b/drivers/usb/phy/phy-am335x-control.c new file mode 100644 index 000000000..ada508be0 --- /dev/null +++ b/drivers/usb/phy/phy-am335x-control.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include "phy-am335x-control.h" + +struct am335x_control_usb { + struct device *dev; + void __iomem *phy_reg; + void __iomem *wkup; + spinlock_t lock; + struct phy_control phy_ctrl; +}; + +#define AM335X_USB0_CTRL 0x0 +#define AM335X_USB1_CTRL 0x8 +#define AM335x_USB_WKUP 0x0 + +#define USBPHY_CM_PWRDN (1 << 0) +#define USBPHY_OTG_PWRDN (1 << 1) +#define USBPHY_OTGVDET_EN (1 << 19) +#define USBPHY_OTGSESSEND_EN (1 << 20) + +#define AM335X_PHY0_WK_EN (1 << 0) +#define AM335X_PHY1_WK_EN (1 << 8) + +static void am335x_phy_wkup(struct phy_control *phy_ctrl, u32 id, bool on) +{ + struct am335x_control_usb *usb_ctrl; + u32 val; + u32 reg; + + usb_ctrl = container_of(phy_ctrl, struct am335x_control_usb, phy_ctrl); + + switch (id) { + case 0: + reg = AM335X_PHY0_WK_EN; + break; + case 1: + reg = AM335X_PHY1_WK_EN; + break; + default: + WARN_ON(1); + return; + } + + spin_lock(&usb_ctrl->lock); + val = readl(usb_ctrl->wkup); + + if (on) + val |= reg; + else + val &= ~reg; + + writel(val, usb_ctrl->wkup); + spin_unlock(&usb_ctrl->lock); +} + +static void am335x_phy_power(struct phy_control *phy_ctrl, u32 id, + enum usb_dr_mode dr_mode, bool on) +{ + struct am335x_control_usb *usb_ctrl; + u32 val; + u32 reg; + + usb_ctrl = container_of(phy_ctrl, struct am335x_control_usb, phy_ctrl); + + switch (id) { + case 0: + reg = AM335X_USB0_CTRL; + break; + case 1: + reg = AM335X_USB1_CTRL; + break; + default: + WARN_ON(1); + return; + } + + val = readl(usb_ctrl->phy_reg + reg); + if (on) { + if (dr_mode == USB_DR_MODE_HOST) { + val &= ~(USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN | + USBPHY_OTGVDET_EN); + val |= USBPHY_OTGSESSEND_EN; + } else { + val &= ~(USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN); + val |= USBPHY_OTGVDET_EN | USBPHY_OTGSESSEND_EN; + } + } else { + val |= USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN; + } + + writel(val, usb_ctrl->phy_reg + reg); + + /* + * Give the PHY ~1ms to complete the power up operation. + * Tests have shown unstable behaviour if other USB PHY related + * registers are written too shortly after such a transition. + */ + if (on) + mdelay(1); +} + +static const struct phy_control ctrl_am335x = { + .phy_power = am335x_phy_power, + .phy_wkup = am335x_phy_wkup, +}; + +static const struct of_device_id omap_control_usb_id_table[] = { + { .compatible = "ti,am335x-usb-ctrl-module", .data = &ctrl_am335x }, + {} +}; +MODULE_DEVICE_TABLE(of, omap_control_usb_id_table); + +static struct platform_driver am335x_control_driver; +static int match(struct device *dev, const void *data) +{ + const struct device_node *node = (const struct device_node *)data; + return dev->of_node == node && + dev->driver == &am335x_control_driver.driver; +} + +struct phy_control *am335x_get_phy_control(struct device *dev) +{ + struct device_node *node; + struct am335x_control_usb *ctrl_usb; + + node = of_parse_phandle(dev->of_node, "ti,ctrl_mod", 0); + if (!node) + return NULL; + + dev = bus_find_device(&platform_bus_type, NULL, node, match); + of_node_put(node); + if (!dev) + return NULL; + + ctrl_usb = dev_get_drvdata(dev); + put_device(dev); + if (!ctrl_usb) + return NULL; + return &ctrl_usb->phy_ctrl; +} +EXPORT_SYMBOL_GPL(am335x_get_phy_control); + +static int am335x_control_usb_probe(struct platform_device *pdev) +{ + struct am335x_control_usb *ctrl_usb; + const struct of_device_id *of_id; + const struct phy_control *phy_ctrl; + + of_id = of_match_node(omap_control_usb_id_table, pdev->dev.of_node); + if (!of_id) + return -EINVAL; + + phy_ctrl = of_id->data; + + ctrl_usb = devm_kzalloc(&pdev->dev, sizeof(*ctrl_usb), GFP_KERNEL); + if (!ctrl_usb) + return -ENOMEM; + + ctrl_usb->dev = &pdev->dev; + + ctrl_usb->phy_reg = devm_platform_ioremap_resource_byname(pdev, "phy_ctrl"); + if (IS_ERR(ctrl_usb->phy_reg)) + return PTR_ERR(ctrl_usb->phy_reg); + + ctrl_usb->wkup = devm_platform_ioremap_resource_byname(pdev, "wakeup"); + if (IS_ERR(ctrl_usb->wkup)) + return PTR_ERR(ctrl_usb->wkup); + + spin_lock_init(&ctrl_usb->lock); + ctrl_usb->phy_ctrl = *phy_ctrl; + + dev_set_drvdata(ctrl_usb->dev, ctrl_usb); + return 0; +} + +static struct platform_driver am335x_control_driver = { + .probe = am335x_control_usb_probe, + .driver = { + .name = "am335x-control-usb", + .of_match_table = omap_control_usb_id_table, + }, +}; + +module_platform_driver(am335x_control_driver); +MODULE_DESCRIPTION("AM335x USB PHY Control Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-am335x-control.h b/drivers/usb/phy/phy-am335x-control.h new file mode 100644 index 000000000..cd4acfc6e --- /dev/null +++ b/drivers/usb/phy/phy-am335x-control.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _AM335x_PHY_CONTROL_H_ +#define _AM335x_PHY_CONTROL_H_ + +struct phy_control { + void (*phy_power)(struct phy_control *phy_ctrl, u32 id, + enum usb_dr_mode dr_mode, bool on); + void (*phy_wkup)(struct phy_control *phy_ctrl, u32 id, bool on); +}; + +static inline void phy_ctrl_power(struct phy_control *phy_ctrl, u32 id, + enum usb_dr_mode dr_mode, bool on) +{ + phy_ctrl->phy_power(phy_ctrl, id, dr_mode, on); +} + +static inline void phy_ctrl_wkup(struct phy_control *phy_ctrl, u32 id, bool on) +{ + phy_ctrl->phy_wkup(phy_ctrl, id, on); +} + +struct phy_control *am335x_get_phy_control(struct device *dev); + +#endif diff --git a/drivers/usb/phy/phy-am335x.c b/drivers/usb/phy/phy-am335x.c new file mode 100644 index 000000000..ca9353dad --- /dev/null +++ b/drivers/usb/phy/phy-am335x.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phy-am335x-control.h" +#include "phy-generic.h" + +struct am335x_phy { + struct usb_phy_generic usb_phy_gen; + struct phy_control *phy_ctrl; + int id; + enum usb_dr_mode dr_mode; +}; + +static int am335x_init(struct usb_phy *phy) +{ + struct am335x_phy *am_phy = dev_get_drvdata(phy->dev); + + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, true); + return 0; +} + +static void am335x_shutdown(struct usb_phy *phy) +{ + struct am335x_phy *am_phy = dev_get_drvdata(phy->dev); + + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, false); +} + +static int am335x_phy_probe(struct platform_device *pdev) +{ + struct am335x_phy *am_phy; + struct device *dev = &pdev->dev; + int ret; + + am_phy = devm_kzalloc(dev, sizeof(*am_phy), GFP_KERNEL); + if (!am_phy) + return -ENOMEM; + + am_phy->phy_ctrl = am335x_get_phy_control(dev); + if (!am_phy->phy_ctrl) + return -EPROBE_DEFER; + + am_phy->id = of_alias_get_id(pdev->dev.of_node, "phy"); + if (am_phy->id < 0) { + dev_err(&pdev->dev, "Missing PHY id: %d\n", am_phy->id); + return am_phy->id; + } + + am_phy->dr_mode = of_usb_get_dr_mode_by_phy(pdev->dev.of_node, -1); + + ret = usb_phy_gen_create_phy(dev, &am_phy->usb_phy_gen); + if (ret) + return ret; + + am_phy->usb_phy_gen.phy.init = am335x_init; + am_phy->usb_phy_gen.phy.shutdown = am335x_shutdown; + + platform_set_drvdata(pdev, am_phy); + device_init_wakeup(dev, true); + + /* + * If we leave PHY wakeup enabled then AM33XX wakes up + * immediately from DS0. To avoid this we mark dev->power.can_wakeup + * to false. The same is checked in suspend routine to decide + * on whether to enable PHY wakeup or not. + * PHY wakeup works fine in standby mode, there by allowing us to + * handle remote wakeup, wakeup on disconnect and connect. + */ + + device_set_wakeup_enable(dev, false); + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, false); + + return usb_add_phy_dev(&am_phy->usb_phy_gen.phy); +} + +static void am335x_phy_remove(struct platform_device *pdev) +{ + struct am335x_phy *am_phy = platform_get_drvdata(pdev); + + usb_remove_phy(&am_phy->usb_phy_gen.phy); +} + +#ifdef CONFIG_PM_SLEEP +static int am335x_phy_suspend(struct device *dev) +{ + struct am335x_phy *am_phy = dev_get_drvdata(dev); + + /* + * Enable phy wakeup only if dev->power.can_wakeup is true. + * Make sure to enable wakeup to support remote wakeup in + * standby mode ( same is not supported in OFF(DS0) mode). + * Enable it by doing + * echo enabled > /sys/bus/platform/devices//power/wakeup + */ + + if (device_may_wakeup(dev)) + phy_ctrl_wkup(am_phy->phy_ctrl, am_phy->id, true); + + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, false); + + return 0; +} + +static int am335x_phy_resume(struct device *dev) +{ + struct am335x_phy *am_phy = dev_get_drvdata(dev); + + phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, true); + + if (device_may_wakeup(dev)) + phy_ctrl_wkup(am_phy->phy_ctrl, am_phy->id, false); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(am335x_pm_ops, am335x_phy_suspend, am335x_phy_resume); + +static const struct of_device_id am335x_phy_ids[] = { + { .compatible = "ti,am335x-usb-phy" }, + { } +}; +MODULE_DEVICE_TABLE(of, am335x_phy_ids); + +static struct platform_driver am335x_phy_driver = { + .probe = am335x_phy_probe, + .remove = am335x_phy_remove, + .driver = { + .name = "am335x-phy-driver", + .pm = &am335x_pm_ops, + .of_match_table = am335x_phy_ids, + }, +}; + +module_platform_driver(am335x_phy_driver); +MODULE_DESCRIPTION("AM335x USB PHY Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-fsl-usb.c b/drivers/usb/phy/phy-fsl-usb.c new file mode 100644 index 000000000..1986f7e9d --- /dev/null +++ b/drivers/usb/phy/phy-fsl-usb.c @@ -0,0 +1,1017 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2007,2008 Freescale semiconductor, Inc. + * + * Author: Li Yang + * Jerry Huang + * + * Initialization based on code from Shlomi Gridish. + */ + +#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" + +#ifdef VERBOSE +#define VDBG(fmt, args...) pr_debug("[%s] " fmt, \ + __func__, ## args) +#else +#define VDBG(stuff...) do {} while (0) +#endif + +#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"; + +static 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 */ +static 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 */ +static 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 const 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 */ + +static 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 */ +static void fsl_otg_chrg_vbus(struct otg_fsm *fsm, 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 */ +static 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 */ +static void fsl_otg_drv_vbus(struct otg_fsm *fsm, 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 + */ +static void fsl_otg_loc_conn(struct otg_fsm *fsm, 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. + */ +static void fsl_otg_loc_sof(struct otg_fsm *fsm, 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. */ +static void fsl_otg_start_pulse(struct otg_fsm *fsm) +{ + 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(fsm, b_data_pulse_tmr); +} + +static 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(); +} + +static void fsl_otg_pulse_vbus(void) +{ + srp_wait_done = 0; + fsl_otg_chrg_vbus(&fsl_otg_dev->fsm, 1); + /* start the timer to end vbus charge */ + fsl_otg_add_timer(&fsl_otg_dev->fsm, b_vbus_pulse_tmr); +} + +static void b_vbus_pulse_end(unsigned long foo) +{ + fsl_otg_chrg_vbus(&fsl_otg_dev->fsm, 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(&fsl_otg_dev->fsm, b_srp_wait_tmr); +} + +static void b_srp_end(unsigned long foo) +{ + fsl_otg_dischrg_vbus(0); + srp_wait_done = 1; + + if ((fsl_otg_dev->phy.otg->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 + */ +static 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(&fsl_otg_dev->fsm, a_wait_enum_tmr); + else + otg_statemachine(&fsl_otg_dev->fsm); +} + +/* The timeout callback function to set time out bit */ +static void set_tmout(unsigned long indicator) +{ + *(int *)indicator = 1; +} + +/* Initialize timers */ +static 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 */ +static 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); +} + +static struct fsl_otg_timer *fsl_otg_get_timer(enum otg_fsm_timer t) +{ + struct fsl_otg_timer *timer; + + /* REVISIT: use array of pointers to timers instead */ + switch (t) { + case A_WAIT_VRISE: + timer = a_wait_vrise_tmr; + break; + case A_WAIT_BCON: + timer = a_wait_vrise_tmr; + break; + case A_AIDL_BDIS: + timer = a_wait_vrise_tmr; + break; + case B_ASE0_BRST: + timer = a_wait_vrise_tmr; + break; + case B_SE0_SRP: + timer = a_wait_vrise_tmr; + break; + case B_SRP_FAIL: + timer = a_wait_vrise_tmr; + break; + case A_WAIT_ENUM: + timer = a_wait_vrise_tmr; + break; + default: + timer = NULL; + } + + return timer; +} + +/* Add timer to timer list */ +static void fsl_otg_add_timer(struct otg_fsm *fsm, 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); +} + +static void fsl_otg_fsm_add_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) +{ + struct fsl_otg_timer *timer; + + timer = fsl_otg_get_timer(t); + if (!timer) + return; + + fsl_otg_add_timer(fsm, timer); +} + +/* Remove timer from the timer list; clear timeout status */ +static void fsl_otg_del_timer(struct otg_fsm *fsm, 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); +} + +static void fsl_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) +{ + struct fsl_otg_timer *timer; + + timer = fsl_otg_get_timer(t); + if (!timer) + return; + + fsl_otg_del_timer(fsm, timer); +} + +/* Reset controller, not reset the bus */ +static 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 */ +static 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->usb_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(fsm, 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(fsm, 0); + } + otg_dev->host_working = 0; + } + } +end: + return retval; +} + +/* + * Call suspend and resume function in udc driver + * to stop and start udc driver. + */ +static 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", str_on_off(on)); + 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->usb_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 hub_wq 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->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->usb_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 */ + pr_debug("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; +} + +/* + * 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->state != OTG_STATE_B_IDLE) + return -ENODEV; + + otg_dev = container_of(otg->usb_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->usb_phy, struct fsl_otg, phy); + if (otg_dev != fsl_otg_dev) + return -ENODEV; + + pr_debug("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. + */ +static 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_fsm_add_timer, + .del_timer = fsl_otg_fsm_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_obj(struct fsl_otg); + if (!fsl_otg_tc) + return -ENOMEM; + + fsl_otg_tc->phy.otg = kzalloc_obj(struct usb_otg); + 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; + } + mutex_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.otg->usb_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 */ +static 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 = dev_get_platdata(&pdev->dev); + + 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; + +#ifdef CONFIG_PPC32 + 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; + } +#endif + + /* request irq */ + p_otg->irq = platform_get_irq(pdev, 0); + if (p_otg->irq < 0) + return p_otg->irq; + 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; + fallthrough; + case FSL_USB2_PHY_UTMI: + temp |= PORTSC_PTS_UTMI; + fallthrough; + 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.otg->state = OTG_STATE_UNDEFINED; + p_otg->fsm.id = 1; + } else { + p_otg->phy.otg->state = OTG_STATE_A_IDLE; + p_otg->fsm.id = 0; + } + + pr_debug("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; +} + +static int fsl_otg_probe(struct platform_device *pdev) +{ + int ret; + + if (!dev_get_platdata(&pdev->dev)) + 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; + } + + return ret; +} + +static void fsl_otg_remove(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = dev_get_platdata(&pdev->dev); + + disable_delayed_work_sync(&fsl_otg_dev->otg_event); + 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); + + if (pdata->exit) + pdata->exit(pdev); +} + +static struct platform_driver fsl_otg_driver = { + .probe = fsl_otg_probe, + .remove = fsl_otg_remove, + .driver = { + .name = driver_name, + }, +}; + +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 000000000..b75407787 --- /dev/null +++ b/drivers/usb/phy/phy-fsl-usb.h @@ -0,0 +1,378 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* Copyright (C) 2007,2008 Freescale Semiconductor, Inc. */ + +#include +#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(phy, newstate) ((phy)->otg->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_obj(struct fsl_otg_timer); + 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; +}; + +#define FSL_OTG_NAME "fsl-usb2-otg" + +static void fsl_otg_add_timer(struct otg_fsm *fsm, void *timer); +static void fsl_otg_del_timer(struct otg_fsm *fsm, void *timer); +static void fsl_otg_pulse_vbus(void); diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c new file mode 100644 index 000000000..de26b3023 --- /dev/null +++ b/drivers/usb/phy/phy-generic.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + * + * 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 +#include +#include +#include + +#include "phy-generic.h" + +#define VBUS_IRQ_FLAGS \ + (IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | \ + IRQF_ONESHOT) + +struct platform_device *usb_phy_generic_register(void) +{ + return platform_device_register_simple("usb_phy_generic", + PLATFORM_DEVID_AUTO, NULL, 0); +} +EXPORT_SYMBOL_GPL(usb_phy_generic_register); + +void usb_phy_generic_unregister(struct platform_device *pdev) +{ + platform_device_unregister(pdev); +} +EXPORT_SYMBOL_GPL(usb_phy_generic_unregister); + +static int nop_set_suspend(struct usb_phy *x, int suspend) +{ + struct usb_phy_generic *nop = dev_get_drvdata(x->dev); + int ret = 0; + + if (suspend) { + clk_disable_unprepare(nop->clk); + if (!IS_ERR(nop->vcc) && !device_may_wakeup(x->dev)) + ret = regulator_disable(nop->vcc); + } else { + if (!IS_ERR(nop->vcc) && !device_may_wakeup(x->dev)) + ret = regulator_enable(nop->vcc); + clk_prepare_enable(nop->clk); + } + + return ret; +} + +static void nop_reset(struct usb_phy_generic *nop) +{ + if (!nop->gpiod_reset) + return; + + gpiod_set_value_cansleep(nop->gpiod_reset, 1); + usleep_range(10000, 20000); + gpiod_set_value_cansleep(nop->gpiod_reset, 0); + usleep_range(10000, 30000); +} + +/* interface to regulator framework */ +static int nop_set_vbus(struct usb_otg *otg, bool enable) +{ + int ret = 0; + struct usb_phy_generic *nop = dev_get_drvdata(otg->usb_phy->dev); + + if (!nop->vbus_draw) + return 0; + + if (enable && !nop->vbus_draw_enabled) { + ret = regulator_enable(nop->vbus_draw); + if (ret) + nop->vbus_draw_enabled = false; + else + nop->vbus_draw_enabled = true; + + } else if (!enable && nop->vbus_draw_enabled) { + ret = regulator_disable(nop->vbus_draw); + nop->vbus_draw_enabled = false; + } + return ret; +} + + +static irqreturn_t nop_gpio_vbus_thread(int irq, void *data) +{ + struct usb_phy_generic *nop = data; + struct usb_otg *otg = nop->phy.otg; + int vbus, status; + + vbus = gpiod_get_value(nop->gpiod_vbus); + if ((vbus ^ nop->vbus) == 0) + return IRQ_HANDLED; + nop->vbus = vbus; + + if (vbus) { + status = USB_EVENT_VBUS; + otg->state = OTG_STATE_B_PERIPHERAL; + nop->phy.last_event = status; + + atomic_notifier_call_chain(&nop->phy.notifier, status, + otg->gadget); + } else { + status = USB_EVENT_NONE; + otg->state = OTG_STATE_B_IDLE; + nop->phy.last_event = status; + + atomic_notifier_call_chain(&nop->phy.notifier, status, + otg->gadget); + } + return IRQ_HANDLED; +} + +int usb_gen_phy_init(struct usb_phy *phy) +{ + struct usb_phy_generic *nop = dev_get_drvdata(phy->dev); + int ret; + + if (!IS_ERR(nop->vcc)) { + if (regulator_enable(nop->vcc)) + dev_err(phy->dev, "Failed to enable power\n"); + } + + ret = clk_prepare_enable(nop->clk); + if (ret) + return ret; + + nop_reset(nop); + + return 0; +} +EXPORT_SYMBOL_GPL(usb_gen_phy_init); + +void usb_gen_phy_shutdown(struct usb_phy *phy) +{ + struct usb_phy_generic *nop = dev_get_drvdata(phy->dev); + + gpiod_set_value_cansleep(nop->gpiod_reset, 1); + + clk_disable_unprepare(nop->clk); + + if (!IS_ERR(nop->vcc)) { + if (regulator_disable(nop->vcc)) + dev_err(phy->dev, "Failed to disable power\n"); + } +} +EXPORT_SYMBOL_GPL(usb_gen_phy_shutdown); + +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; + if (otg->state == OTG_STATE_B_PERIPHERAL) + atomic_notifier_call_chain(&otg->usb_phy->notifier, + USB_EVENT_VBUS, otg->gadget); + else + otg->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; +} + +int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop) +{ + enum usb_phy_type type = USB_PHY_TYPE_USB2; + int err = 0; + u32 clk_rate = 0; + + device_property_read_u32(dev, "clock-frequency", &clk_rate); + nop->gpiod_reset = devm_gpiod_get_optional(dev, "reset", + GPIOD_ASIS); + err = PTR_ERR_OR_ZERO(nop->gpiod_reset); + if (!err) { + nop->gpiod_vbus = devm_gpiod_get_optional(dev, + "vbus-detect", + GPIOD_ASIS); + err = PTR_ERR_OR_ZERO(nop->gpiod_vbus); + } + + if (err) + return dev_err_probe(dev, err, + "Error requesting RESET or VBUS GPIO\n"); + if (nop->gpiod_reset) + gpiod_direction_output(nop->gpiod_reset, 1); + + nop->phy.otg = devm_kzalloc(dev, sizeof(*nop->phy.otg), + GFP_KERNEL); + if (!nop->phy.otg) + return -ENOMEM; + + nop->clk = devm_clk_get_optional(dev, "main_clk"); + if (IS_ERR(nop->clk)) + return dev_err_probe(dev, PTR_ERR(nop->clk), + "Can't get phy clock\n"); + + if (clk_rate) { + err = clk_set_rate(nop->clk, clk_rate); + if (err) + return dev_err_probe(dev, err, + "Error setting clock rate\n"); + } + + nop->vcc = devm_regulator_get_optional(dev, "vcc"); + if (IS_ERR(nop->vcc) && PTR_ERR(nop->vcc) != -ENODEV) + return dev_err_probe(dev, PTR_ERR(nop->vcc), + "could not get vcc regulator\n"); + + nop->vbus_draw = devm_regulator_get_exclusive(dev, "vbus"); + if (PTR_ERR(nop->vbus_draw) == -ENODEV) + nop->vbus_draw = NULL; + if (IS_ERR(nop->vbus_draw)) + return dev_err_probe(dev, PTR_ERR(nop->vbus_draw), + "could not get vbus regulator\n"); + + nop->dev = dev; + nop->phy.dev = nop->dev; + nop->phy.label = "nop-xceiv"; + nop->phy.set_suspend = nop_set_suspend; + nop->phy.type = type; + + nop->phy.otg->state = OTG_STATE_UNDEFINED; + nop->phy.otg->usb_phy = &nop->phy; + nop->phy.otg->set_host = nop_set_host; + nop->phy.otg->set_peripheral = nop_set_peripheral; + nop->phy.otg->set_vbus = nop_set_vbus; + + return 0; +} +EXPORT_SYMBOL_GPL(usb_phy_gen_create_phy); + +static int usb_phy_generic_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usb_phy_generic *nop; + int err; + + nop = devm_kzalloc(dev, sizeof(*nop), GFP_KERNEL); + if (!nop) + return -ENOMEM; + + err = usb_phy_gen_create_phy(dev, nop); + if (err) + return err; + + if (nop->gpiod_vbus) { + err = devm_request_threaded_irq(dev, + gpiod_to_irq(nop->gpiod_vbus), + NULL, nop_gpio_vbus_thread, + VBUS_IRQ_FLAGS, "vbus_detect", + nop); + if (err) + return dev_err_probe(dev, err, "can't request irq %i\n", + gpiod_to_irq(nop->gpiod_vbus)); + + nop->phy.otg->state = gpiod_get_value(nop->gpiod_vbus) ? + OTG_STATE_B_PERIPHERAL : OTG_STATE_B_IDLE; + } + + nop->phy.init = usb_gen_phy_init; + nop->phy.shutdown = usb_gen_phy_shutdown; + + err = usb_add_phy_dev(&nop->phy); + if (err) + return dev_err_probe(dev, err, "can't register transceiver\n"); + + platform_set_drvdata(pdev, nop); + + device_set_wakeup_capable(dev, + device_property_read_bool(dev, "wakeup-source")); + + return 0; +} + +static void usb_phy_generic_remove(struct platform_device *pdev) +{ + struct usb_phy_generic *nop = platform_get_drvdata(pdev); + + usb_remove_phy(&nop->phy); + + if (nop->vbus_draw && nop->vbus_draw_enabled) + regulator_disable(nop->vbus_draw); +} + +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 usb_phy_generic_driver = { + .probe = usb_phy_generic_probe, + .remove = usb_phy_generic_remove, + .driver = { + .name = "usb_phy_generic", + .of_match_table = nop_xceiv_dt_ids, + }, +}; + +static int __init usb_phy_generic_init(void) +{ + return platform_driver_register(&usb_phy_generic_driver); +} +subsys_initcall(usb_phy_generic_init); + +static void __exit usb_phy_generic_exit(void) +{ + platform_driver_unregister(&usb_phy_generic_driver); +} +module_exit(usb_phy_generic_exit); + +MODULE_ALIAS("platform:usb_phy_generic"); +MODULE_AUTHOR("Texas Instruments Inc"); +MODULE_DESCRIPTION("NOP USB Transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-generic.h b/drivers/usb/phy/phy-generic.h new file mode 100644 index 000000000..7ee80211a --- /dev/null +++ b/drivers/usb/phy/phy-generic.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _PHY_GENERIC_H_ +#define _PHY_GENERIC_H_ + +#include +#include +#include + +struct usb_phy_generic { + struct usb_phy phy; + struct device *dev; + struct clk *clk; + struct regulator *vcc; + struct gpio_desc *gpiod_reset; + struct gpio_desc *gpiod_vbus; + struct regulator *vbus_draw; + bool vbus_draw_enabled; + unsigned long mA; + unsigned int vbus; +}; + +int usb_gen_phy_init(struct usb_phy *phy); +void usb_gen_phy_shutdown(struct usb_phy *phy); + +int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop); + +#endif 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 000000000..ce09e789a --- /dev/null +++ b/drivers/usb/phy/phy-gpio-vbus-usb.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * gpio-vbus.c - simple GPIO VBUS sensing driver for B peripheral devices + * + * Copyright (c) 2008 Philipp Zabel + */ + +#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 gpio_desc *vbus_gpiod; + struct gpio_desc *pullup_gpiod; + 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_data *gpio_vbus) +{ + return gpiod_get_value(gpio_vbus->vbus_gpiod); +} + +static void gpio_vbus_work(struct work_struct *work) +{ + struct gpio_vbus_data *gpio_vbus = + container_of(work, struct gpio_vbus_data, work.work); + int status, vbus; + + if (!gpio_vbus->phy.otg->gadget) + return; + + vbus = is_vbus_powered(gpio_vbus); + if ((vbus ^ gpio_vbus->vbus) == 0) + return; + gpio_vbus->vbus = vbus; + + /* Peripheral controllers which manage the pullup themselves won't have + * a pullup GPIO 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. + */ + + if (vbus) { + status = USB_EVENT_VBUS; + gpio_vbus->phy.otg->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_vbus->pullup_gpiod) + gpiod_set_value(gpio_vbus->pullup_gpiod, 1); + + atomic_notifier_call_chain(&gpio_vbus->phy.notifier, + status, gpio_vbus->phy.otg->gadget); + usb_phy_set_event(&gpio_vbus->phy, USB_EVENT_ENUMERATED); + } else { + /* optionally disable D+ pullup */ + if (gpio_vbus->pullup_gpiod) + gpiod_set_value(gpio_vbus->pullup_gpiod, 0); + + set_vbus_draw(gpio_vbus, 0); + + usb_gadget_vbus_disconnect(gpio_vbus->phy.otg->gadget); + status = USB_EVENT_NONE; + gpio_vbus->phy.otg->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); + usb_phy_set_event(&gpio_vbus->phy, USB_EVENT_NONE); + } +} + +/* VBUS change IRQ handler */ +static irqreturn_t gpio_vbus_irq(int irq, void *data) +{ + struct platform_device *pdev = 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(gpio_vbus) ? "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 platform_device *pdev; + + gpio_vbus = container_of(otg->usb_phy, struct gpio_vbus_data, phy); + pdev = to_platform_device(gpio_vbus->dev); + + if (!gadget) { + dev_dbg(&pdev->dev, "unregistering gadget '%s'\n", + otg->gadget->name); + + /* optionally disable D+ pullup */ + if (gpio_vbus->pullup_gpiod) + gpiod_set_value(gpio_vbus->pullup_gpiod, 0); + + set_vbus_draw(gpio_vbus, 0); + + usb_gadget_vbus_disconnect(otg->gadget); + otg->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->otg->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 gpio_vbus_probe(struct platform_device *pdev) +{ + struct gpio_vbus_data *gpio_vbus; + struct resource *res; + struct device *dev = &pdev->dev; + int err, irq; + unsigned long irqflags; + + gpio_vbus = devm_kzalloc(&pdev->dev, sizeof(struct gpio_vbus_data), + GFP_KERNEL); + if (!gpio_vbus) + return -ENOMEM; + + gpio_vbus->phy.otg = devm_kzalloc(&pdev->dev, sizeof(struct usb_otg), + GFP_KERNEL); + if (!gpio_vbus->phy.otg) + 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.otg->state = OTG_STATE_UNDEFINED; + gpio_vbus->phy.otg->usb_phy = &gpio_vbus->phy; + gpio_vbus->phy.otg->set_peripheral = gpio_vbus_set_peripheral; + + /* Look up the VBUS sensing GPIO */ + gpio_vbus->vbus_gpiod = devm_gpiod_get(dev, "vbus", GPIOD_IN); + if (IS_ERR(gpio_vbus->vbus_gpiod)) { + err = PTR_ERR(gpio_vbus->vbus_gpiod); + dev_err(&pdev->dev, "can't request vbus gpio, err: %d\n", err); + return err; + } + gpiod_set_consumer_name(gpio_vbus->vbus_gpiod, "vbus_detect"); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res) { + irq = res->start; + irqflags = (res->flags & IRQF_TRIGGER_MASK) | IRQF_SHARED; + } else { + irq = gpiod_to_irq(gpio_vbus->vbus_gpiod); + irqflags = VBUS_IRQ_FLAGS; + } + + gpio_vbus->irq = irq; + + /* + * The VBUS sensing GPIO should have a pulldown, which will normally be + * part of a resistor ladder turning a 4.0V-5.25V level on VBUS into a + * value the GPIO detects as active. Some systems will use comparators. + * Get the optional D+ or D- pullup GPIO. If the data line pullup is + * in use, initialize it to "not pulling up" + */ + gpio_vbus->pullup_gpiod = devm_gpiod_get_optional(dev, "pullup", + GPIOD_OUT_LOW); + if (IS_ERR(gpio_vbus->pullup_gpiod)) { + err = PTR_ERR(gpio_vbus->pullup_gpiod); + dev_err(&pdev->dev, "can't request pullup gpio, err: %d\n", + err); + return err; + } + if (gpio_vbus->pullup_gpiod) + gpiod_set_consumer_name(gpio_vbus->pullup_gpiod, "udc_pullup"); + + err = devm_request_irq(&pdev->dev, irq, gpio_vbus_irq, irqflags, + "vbus_detect", pdev); + if (err) { + dev_err(&pdev->dev, "can't request irq %i, err: %d\n", + irq, err); + return err; + } + + INIT_DELAYED_WORK(&gpio_vbus->work, gpio_vbus_work); + + gpio_vbus->vbus_draw = devm_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); + return err; + } + + /* TODO: wakeup could be enabled here with device_init_wakeup(dev, 1) */ + + return 0; +} + +static void gpio_vbus_remove(struct platform_device *pdev) +{ + struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev); + + device_init_wakeup(&pdev->dev, 0); + cancel_delayed_work_sync(&gpio_vbus->work); + + usb_remove_phy(&gpio_vbus->phy); +} + +#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 + +MODULE_ALIAS("platform:gpio-vbus"); + +/* + * NOTE: this driver matches against "gpio-usb-b-connector" for + * devices that do NOT support role switch. + */ +static const struct of_device_id gpio_vbus_of_match[] = { + { + .compatible = "gpio-usb-b-connector", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, gpio_vbus_of_match); + +static struct platform_driver gpio_vbus_driver = { + .driver = { + .name = "gpio-vbus", +#ifdef CONFIG_PM + .pm = &gpio_vbus_dev_pm_ops, +#endif + .of_match_table = gpio_vbus_of_match, + }, + .probe = gpio_vbus_probe, + .remove = gpio_vbus_remove, +}; + +module_platform_driver(gpio_vbus_driver); + +MODULE_DESCRIPTION("simple GPIO controlled OTG transceiver driver"); +MODULE_AUTHOR("Philipp Zabel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-isp1301.c b/drivers/usb/phy/phy-isp1301.c new file mode 100644 index 000000000..73cc70e95 --- /dev/null +++ b/drivers/usb/phy/phy-isp1301.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NXP ISP1301 USB transceiver driver + * + * Copyright (C) 2012 Roland Stigge + * + * Author: Roland Stigge + */ + +#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[] = { + { .name = "isp1301" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isp1301_id); + +static const struct of_device_id isp1301_of_match[] = { + {.compatible = "nxp,isp1301" }, + { }, +}; +MODULE_DEVICE_TABLE(of, isp1301_of_match); + +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) +{ + 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 void isp1301_remove(struct i2c_client *client) +{ + struct isp1301 *isp = i2c_get_clientdata(client); + + usb_remove_phy(&isp->phy); + isp1301_i2c_client = NULL; +} + +static struct i2c_driver isp1301_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = isp1301_of_match, + }, + .probe = isp1301_probe, + .remove = isp1301_remove, + .id_table = isp1301_id, +}; + +module_i2c_driver(isp1301_driver); + +struct i2c_client *isp1301_get_client(struct device_node *node) +{ + struct i2c_client *client; + + /* reference of ISP1301 I2C node via DT */ + client = of_find_i2c_device_by_node(node); + if (client) + return client; + + /* non-DT: only one ISP1301 chip supported */ + if (isp1301_i2c_client) { + get_device(&isp1301_i2c_client->dev); + return isp1301_i2c_client; + } + + return NULL; +} +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-keystone.c b/drivers/usb/phy/phy-keystone.c new file mode 100644 index 000000000..51155c551 --- /dev/null +++ b/drivers/usb/phy/phy-keystone.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * phy-keystone - USB PHY, talking to dwc3 controller in Keystone. + * + * Copyright (C) 2013 Texas Instruments Incorporated - https://www.ti.com + * + * Author: WingMan Kwok + */ + +#include +#include +#include +#include +#include + +#include "phy-generic.h" + +/* USB PHY control register offsets */ +#define USB_PHY_CTL_UTMI 0x0000 +#define USB_PHY_CTL_PIPE 0x0004 +#define USB_PHY_CTL_PARAM_1 0x0008 +#define USB_PHY_CTL_PARAM_2 0x000c +#define USB_PHY_CTL_CLOCK 0x0010 +#define USB_PHY_CTL_PLL 0x0014 + +#define PHY_REF_SSP_EN BIT(29) + +struct keystone_usbphy { + struct usb_phy_generic usb_phy_gen; + void __iomem *phy_ctrl; +}; + +static inline u32 keystone_usbphy_readl(void __iomem *base, u32 offset) +{ + return readl(base + offset); +} + +static inline void keystone_usbphy_writel(void __iomem *base, + u32 offset, u32 value) +{ + writel(value, base + offset); +} + +static int keystone_usbphy_init(struct usb_phy *phy) +{ + struct keystone_usbphy *k_phy = dev_get_drvdata(phy->dev); + u32 val; + + val = keystone_usbphy_readl(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK); + keystone_usbphy_writel(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK, + val | PHY_REF_SSP_EN); + return 0; +} + +static void keystone_usbphy_shutdown(struct usb_phy *phy) +{ + struct keystone_usbphy *k_phy = dev_get_drvdata(phy->dev); + u32 val; + + val = keystone_usbphy_readl(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK); + keystone_usbphy_writel(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK, + val & ~PHY_REF_SSP_EN); +} + +static int keystone_usbphy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct keystone_usbphy *k_phy; + int ret; + + k_phy = devm_kzalloc(dev, sizeof(*k_phy), GFP_KERNEL); + if (!k_phy) + return -ENOMEM; + + k_phy->phy_ctrl = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(k_phy->phy_ctrl)) + return PTR_ERR(k_phy->phy_ctrl); + + ret = usb_phy_gen_create_phy(dev, &k_phy->usb_phy_gen); + if (ret) + return ret; + + k_phy->usb_phy_gen.phy.init = keystone_usbphy_init; + k_phy->usb_phy_gen.phy.shutdown = keystone_usbphy_shutdown; + + platform_set_drvdata(pdev, k_phy); + + return usb_add_phy_dev(&k_phy->usb_phy_gen.phy); +} + +static void keystone_usbphy_remove(struct platform_device *pdev) +{ + struct keystone_usbphy *k_phy = platform_get_drvdata(pdev); + + usb_remove_phy(&k_phy->usb_phy_gen.phy); +} + +static const struct of_device_id keystone_usbphy_ids[] = { + { .compatible = "ti,keystone-usbphy" }, + { } +}; +MODULE_DEVICE_TABLE(of, keystone_usbphy_ids); + +static struct platform_driver keystone_usbphy_driver = { + .probe = keystone_usbphy_probe, + .remove = keystone_usbphy_remove, + .driver = { + .name = "keystone-usbphy", + .of_match_table = keystone_usbphy_ids, + }, +}; + +module_platform_driver(keystone_usbphy_driver); + +MODULE_ALIAS("platform:keystone-usbphy"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("Keystone USB phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-mv-usb.h b/drivers/usb/phy/phy-mv-usb.h new file mode 100644 index 000000000..5d5c0abb0 --- /dev/null +++ b/drivers/usb/phy/phy-mv-usb.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + */ + +#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 000000000..7069dd3f4 --- /dev/null +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -0,0 +1,977 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2012-2014 Freescale Semiconductor, Inc. + * Copyright (C) 2012 Marek Vasut + * on behalf of DENX Software Engineering GmbH + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "mxs_phy" + +/* Register Macro */ +#define HW_USBPHY_PWD 0x00 +#define HW_USBPHY_TX 0x10 +#define HW_USBPHY_CTRL 0x30 +#define HW_USBPHY_CTRL_SET 0x34 +#define HW_USBPHY_CTRL_CLR 0x38 + +#define HW_USBPHY_DEBUG_SET 0x54 +#define HW_USBPHY_DEBUG_CLR 0x58 + +#define HW_USBPHY_IP 0x90 +#define HW_USBPHY_IP_SET 0x94 +#define HW_USBPHY_IP_CLR 0x98 + +#define GM_USBPHY_TX_TXCAL45DP(x) (((x) & 0xf) << 16) +#define GM_USBPHY_TX_TXCAL45DN(x) (((x) & 0xf) << 8) +#define GM_USBPHY_TX_D_CAL(x) (((x) & 0xf) << 0) + +/* imx7ulp */ +#define HW_USBPHY_PLL_SIC 0xa0 +#define HW_USBPHY_PLL_SIC_SET 0xa4 +#define HW_USBPHY_PLL_SIC_CLR 0xa8 + +#define BM_USBPHY_CTRL_SFTRST BIT(31) +#define BM_USBPHY_CTRL_CLKGATE BIT(30) +#define BM_USBPHY_CTRL_OTG_ID_VALUE BIT(27) +#define BM_USBPHY_CTRL_ENAUTOSET_USBCLKS BIT(26) +#define BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE BIT(25) +#define BM_USBPHY_CTRL_ENVBUSCHG_WKUP BIT(23) +#define BM_USBPHY_CTRL_ENIDCHG_WKUP BIT(22) +#define BM_USBPHY_CTRL_ENDPDMCHG_WKUP BIT(21) +#define BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD BIT(20) +#define BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE BIT(19) +#define BM_USBPHY_CTRL_ENAUTO_PWRON_PLL BIT(18) +#define BM_USBPHY_CTRL_ENUTMILEVEL3 BIT(15) +#define BM_USBPHY_CTRL_ENUTMILEVEL2 BIT(14) +#define BM_USBPHY_CTRL_ENHOSTDISCONDETECT BIT(1) + +#define BM_USBPHY_IP_FIX (BIT(17) | BIT(18)) + +#define BM_USBPHY_DEBUG_CLKGATE BIT(30) +/* imx7ulp */ +#define BM_USBPHY_PLL_LOCK BIT(31) +#define BM_USBPHY_PLL_REG_ENABLE BIT(21) +#define BM_USBPHY_PLL_BYPASS BIT(16) +#define BM_USBPHY_PLL_POWER BIT(12) +#define BM_USBPHY_PLL_EN_USB_CLKS BIT(6) + +/* Anatop Registers */ +#define ANADIG_REG_1P1_SET 0x114 +#define ANADIG_REG_1P1_CLR 0x118 + +#define ANADIG_ANA_MISC0 0x150 +#define ANADIG_ANA_MISC0_SET 0x154 +#define ANADIG_ANA_MISC0_CLR 0x158 + +#define ANADIG_USB1_CHRG_DETECT_SET 0x1b4 +#define ANADIG_USB1_CHRG_DETECT_CLR 0x1b8 +#define ANADIG_USB2_CHRG_DETECT_SET 0x214 +#define ANADIG_USB1_CHRG_DETECT_EN_B BIT(20) +#define ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B BIT(19) +#define ANADIG_USB1_CHRG_DETECT_CHK_CONTACT BIT(18) + +#define ANADIG_USB1_VBUS_DET_STAT 0x1c0 +#define ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID BIT(3) + +#define ANADIG_USB1_CHRG_DET_STAT 0x1d0 +#define ANADIG_USB1_CHRG_DET_STAT_DM_STATE BIT(2) +#define ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED BIT(1) +#define ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT BIT(0) + +#define ANADIG_USB2_VBUS_DET_STAT 0x220 + +#define ANADIG_USB1_LOOPBACK_SET 0x1e4 +#define ANADIG_USB1_LOOPBACK_CLR 0x1e8 +#define ANADIG_USB1_LOOPBACK_UTMI_TESTSTART BIT(0) + +#define ANADIG_USB2_LOOPBACK_SET 0x244 +#define ANADIG_USB2_LOOPBACK_CLR 0x248 + +#define ANADIG_USB1_MISC 0x1f0 +#define ANADIG_USB2_MISC 0x250 + +#define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG BIT(12) +#define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL BIT(11) + +#define BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID BIT(3) +#define BM_ANADIG_USB2_VBUS_DET_STAT_VBUS_VALID BIT(3) + +#define BM_ANADIG_USB1_LOOPBACK_UTMI_DIG_TST1 BIT(2) +#define BM_ANADIG_USB1_LOOPBACK_TSTI_TX_EN BIT(5) +#define BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1 BIT(2) +#define BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN BIT(5) + +#define BM_ANADIG_USB1_MISC_RX_VPIN_FS BIT(29) +#define BM_ANADIG_USB1_MISC_RX_VMIN_FS BIT(28) +#define BM_ANADIG_USB2_MISC_RX_VPIN_FS BIT(29) +#define BM_ANADIG_USB2_MISC_RX_VMIN_FS BIT(28) + +/* System Integration Module (SIM) Registers */ +#define SIM_GPR1 0x30 + +#define USB_PHY_VLLS_WAKEUP_EN BIT(0) + +#define BM_ANADIG_REG_1P1_ENABLE_WEAK_LINREG BIT(18) +#define BM_ANADIG_REG_1P1_TRACK_VDD_SOC_CAP BIT(19) + +#define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) + +/* Do disconnection between PHY and controller without vbus */ +#define MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS BIT(0) + +/* + * The PHY will be in messy if there is a wakeup after putting + * bus to suspend (set portsc.suspendM) but before setting PHY to low + * power mode (set portsc.phcd). + */ +#define MXS_PHY_ABNORMAL_IN_SUSPEND BIT(1) + +/* + * The SOF sends too fast after resuming, it will cause disconnection + * between host and high speed device. + */ +#define MXS_PHY_SENDING_SOF_TOO_FAST BIT(2) + +/* + * IC has bug fixes logic, they include + * MXS_PHY_ABNORMAL_IN_SUSPEND and MXS_PHY_SENDING_SOF_TOO_FAST + * which are described at above flags, the RTL will handle it + * according to different versions. + */ +#define MXS_PHY_NEED_IP_FIX BIT(3) + +/* Minimum and maximum values for device tree entries */ +#define MXS_PHY_TX_CAL45_MIN 35 +#define MXS_PHY_TX_CAL45_MAX 54 +#define MXS_PHY_TX_D_CAL_MIN 79 +#define MXS_PHY_TX_D_CAL_MAX 119 + +/* + * At imx6q/6sl/6sx, the PHY2's clock is controlled by hardware directly, + * eg, according to PHY's suspend status. In these PHYs, we only need to + * open the clock at the initialization and close it at its shutdown routine. + * These PHYs can send resume signal without software interfere if not + * gate clock. + */ +#define MXS_PHY_HARDWARE_CONTROL_PHY2_CLK BIT(4) + +struct mxs_phy_data { + unsigned int flags; +}; + +static const struct mxs_phy_data imx23_phy_data = { + .flags = MXS_PHY_ABNORMAL_IN_SUSPEND | MXS_PHY_SENDING_SOF_TOO_FAST, +}; + +static const struct mxs_phy_data imx6q_phy_data = { + .flags = MXS_PHY_SENDING_SOF_TOO_FAST | + MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | + MXS_PHY_NEED_IP_FIX | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, +}; + +static const struct mxs_phy_data imx6sl_phy_data = { + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | + MXS_PHY_NEED_IP_FIX | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, +}; + +static const struct mxs_phy_data vf610_phy_data = { + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | + MXS_PHY_NEED_IP_FIX, +}; + +static const struct mxs_phy_data imx6sx_phy_data = { + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, +}; + +static const struct mxs_phy_data imx6ul_phy_data = { + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, +}; + +static const struct mxs_phy_data imx7ulp_phy_data = { +}; + +static const struct of_device_id mxs_phy_dt_ids[] = { + { .compatible = "fsl,imx6sx-usbphy", .data = &imx6sx_phy_data, }, + { .compatible = "fsl,imx6sl-usbphy", .data = &imx6sl_phy_data, }, + { .compatible = "fsl,imx6q-usbphy", .data = &imx6q_phy_data, }, + { .compatible = "fsl,imx23-usbphy", .data = &imx23_phy_data, }, + { .compatible = "fsl,vf610-usbphy", .data = &vf610_phy_data, }, + { .compatible = "fsl,imx6ul-usbphy", .data = &imx6ul_phy_data, }, + { .compatible = "fsl,imx7ulp-usbphy", .data = &imx7ulp_phy_data, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_phy_dt_ids); + +struct mxs_phy { + struct usb_phy phy; + struct clk *clk; + const struct mxs_phy_data *data; + struct regmap *regmap_anatop; + struct regmap *regmap_sim; + int port_id; + u32 tx_reg_set; + u32 tx_reg_mask; + struct regulator *phy_3p0; +}; + +static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy) +{ + return mxs_phy->data == &imx6q_phy_data; +} + +static inline bool is_imx6sl_phy(struct mxs_phy *mxs_phy) +{ + return mxs_phy->data == &imx6sl_phy_data; +} + +static inline bool is_imx7ulp_phy(struct mxs_phy *mxs_phy) +{ + return mxs_phy->data == &imx7ulp_phy_data; +} + +static inline bool is_imx6ul_phy(struct mxs_phy *mxs_phy) +{ + return mxs_phy->data == &imx6ul_phy_data; +} + +/* + * PHY needs some 32K cycles to switch from 32K clock to + * bus (such as AHB/AXI, etc) clock. + */ +static void mxs_phy_clock_switch_delay(void) +{ + usleep_range(300, 400); +} + +static void mxs_phy_tx_init(struct mxs_phy *mxs_phy) +{ + void __iomem *base = mxs_phy->phy.io_priv; + u32 phytx; + + /* Update TX register if there is anything to write */ + if (mxs_phy->tx_reg_mask) { + phytx = readl(base + HW_USBPHY_TX); + phytx &= ~mxs_phy->tx_reg_mask; + phytx |= mxs_phy->tx_reg_set; + writel(phytx, base + HW_USBPHY_TX); + } +} + +static int mxs_phy_pll_enable(void __iomem *base, bool enable) +{ + int ret = 0; + + if (enable) { + u32 value; + + writel(BM_USBPHY_PLL_REG_ENABLE, base + HW_USBPHY_PLL_SIC_SET); + writel(BM_USBPHY_PLL_BYPASS, base + HW_USBPHY_PLL_SIC_CLR); + writel(BM_USBPHY_PLL_POWER, base + HW_USBPHY_PLL_SIC_SET); + ret = readl_poll_timeout(base + HW_USBPHY_PLL_SIC, + value, (value & BM_USBPHY_PLL_LOCK) != 0, + 100, 10000); + if (ret) + return ret; + + writel(BM_USBPHY_PLL_EN_USB_CLKS, base + + HW_USBPHY_PLL_SIC_SET); + } else { + writel(BM_USBPHY_PLL_EN_USB_CLKS, base + + HW_USBPHY_PLL_SIC_CLR); + writel(BM_USBPHY_PLL_POWER, base + HW_USBPHY_PLL_SIC_CLR); + writel(BM_USBPHY_PLL_BYPASS, base + HW_USBPHY_PLL_SIC_SET); + writel(BM_USBPHY_PLL_REG_ENABLE, base + HW_USBPHY_PLL_SIC_CLR); + } + + return ret; +} + +static int mxs_phy_hw_init(struct mxs_phy *mxs_phy) +{ + int ret; + void __iomem *base = mxs_phy->phy.io_priv; + + if (is_imx7ulp_phy(mxs_phy)) { + ret = mxs_phy_pll_enable(base, true); + if (ret) + return ret; + } + + ret = stmp_reset_block(base + HW_USBPHY_CTRL); + if (ret) + goto disable_pll; + + if (mxs_phy->phy_3p0) { + ret = regulator_enable(mxs_phy->phy_3p0); + if (ret) { + dev_err(mxs_phy->phy.dev, + "Failed to enable 3p0 regulator, ret=%d\n", + ret); + return ret; + } + } + + /* Power up the PHY */ + writel(0, base + HW_USBPHY_PWD); + + /* + * USB PHY Ctrl Setting + * - Auto clock/power on + * - Enable full/low speed support + */ + writel(BM_USBPHY_CTRL_ENAUTOSET_USBCLKS | + BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE | + BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD | + BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE | + BM_USBPHY_CTRL_ENAUTO_PWRON_PLL | + BM_USBPHY_CTRL_ENUTMILEVEL2 | + BM_USBPHY_CTRL_ENUTMILEVEL3, + base + HW_USBPHY_CTRL_SET); + + if (mxs_phy->data->flags & MXS_PHY_NEED_IP_FIX) + writel(BM_USBPHY_IP_FIX, base + HW_USBPHY_IP_SET); + + if (mxs_phy->regmap_anatop) { + unsigned int reg = mxs_phy->port_id ? + ANADIG_USB1_CHRG_DETECT_SET : + ANADIG_USB2_CHRG_DETECT_SET; + /* + * The external charger detector needs to be disabled, + * or the signal at DP will be poor + */ + regmap_write(mxs_phy->regmap_anatop, reg, + ANADIG_USB1_CHRG_DETECT_EN_B | + ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B); + } + + mxs_phy_tx_init(mxs_phy); + + return 0; + +disable_pll: + if (is_imx7ulp_phy(mxs_phy)) + mxs_phy_pll_enable(base, false); + return ret; +} + +/* Return true if the vbus is there */ +static bool mxs_phy_get_vbus_status(struct mxs_phy *mxs_phy) +{ + unsigned int vbus_value = 0; + + if (!mxs_phy->regmap_anatop) + return false; + + if (mxs_phy->port_id == 0) + regmap_read(mxs_phy->regmap_anatop, + ANADIG_USB1_VBUS_DET_STAT, + &vbus_value); + else if (mxs_phy->port_id == 1) + regmap_read(mxs_phy->regmap_anatop, + ANADIG_USB2_VBUS_DET_STAT, + &vbus_value); + + if (vbus_value & BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID) + return true; + else + return false; +} + +static void __mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool disconnect) +{ + void __iomem *base = mxs_phy->phy.io_priv; + u32 reg; + + if (disconnect) + writel_relaxed(BM_USBPHY_DEBUG_CLKGATE, + base + HW_USBPHY_DEBUG_CLR); + + if (mxs_phy->port_id == 0) { + reg = disconnect ? ANADIG_USB1_LOOPBACK_SET + : ANADIG_USB1_LOOPBACK_CLR; + regmap_write(mxs_phy->regmap_anatop, reg, + BM_ANADIG_USB1_LOOPBACK_UTMI_DIG_TST1 | + BM_ANADIG_USB1_LOOPBACK_TSTI_TX_EN); + } else if (mxs_phy->port_id == 1) { + reg = disconnect ? ANADIG_USB2_LOOPBACK_SET + : ANADIG_USB2_LOOPBACK_CLR; + regmap_write(mxs_phy->regmap_anatop, reg, + BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1 | + BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN); + } + + if (!disconnect) + writel_relaxed(BM_USBPHY_DEBUG_CLKGATE, + base + HW_USBPHY_DEBUG_SET); + + /* Delay some time, and let Linestate be SE0 for controller */ + if (disconnect) + usleep_range(500, 1000); +} + +static bool mxs_phy_is_otg_host(struct mxs_phy *mxs_phy) +{ + return mxs_phy->phy.last_event == USB_EVENT_ID; +} + +static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on) +{ + bool vbus_is_on = false; + enum usb_phy_events last_event = mxs_phy->phy.last_event; + + /* If the SoCs don't need to disconnect line without vbus, quit */ + if (!(mxs_phy->data->flags & MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS)) + return; + + /* If the SoCs don't have anatop, quit */ + if (!mxs_phy->regmap_anatop) + return; + + vbus_is_on = mxs_phy_get_vbus_status(mxs_phy); + + if (on && ((!vbus_is_on && !mxs_phy_is_otg_host(mxs_phy)) + || (last_event == USB_EVENT_VBUS))) + __mxs_phy_disconnect_line(mxs_phy, true); + else + __mxs_phy_disconnect_line(mxs_phy, false); + +} + +static int mxs_phy_init(struct usb_phy *phy) +{ + int ret; + struct mxs_phy *mxs_phy = to_mxs_phy(phy); + + mxs_phy_clock_switch_delay(); + ret = clk_prepare_enable(mxs_phy->clk); + if (ret) + return ret; + + return mxs_phy_hw_init(mxs_phy); +} + +static void mxs_phy_shutdown(struct usb_phy *phy) +{ + struct mxs_phy *mxs_phy = to_mxs_phy(phy); + u32 value = BM_USBPHY_CTRL_ENVBUSCHG_WKUP | + BM_USBPHY_CTRL_ENDPDMCHG_WKUP | + BM_USBPHY_CTRL_ENIDCHG_WKUP | + BM_USBPHY_CTRL_ENAUTOSET_USBCLKS | + BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE | + BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD | + BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE | + BM_USBPHY_CTRL_ENAUTO_PWRON_PLL; + + writel(value, phy->io_priv + HW_USBPHY_CTRL_CLR); + writel(0xffffffff, phy->io_priv + HW_USBPHY_PWD); + + writel(BM_USBPHY_CTRL_CLKGATE, + phy->io_priv + HW_USBPHY_CTRL_SET); + + if (is_imx7ulp_phy(mxs_phy)) + mxs_phy_pll_enable(phy->io_priv, false); + + if (mxs_phy->phy_3p0) + regulator_disable(mxs_phy->phy_3p0); + + clk_disable_unprepare(mxs_phy->clk); +} + +static bool mxs_phy_is_low_speed_connection(struct mxs_phy *mxs_phy) +{ + unsigned int line_state; + /* bit definition is the same for all controllers */ + unsigned int dp_bit = BM_ANADIG_USB1_MISC_RX_VPIN_FS, + dm_bit = BM_ANADIG_USB1_MISC_RX_VMIN_FS; + unsigned int reg = ANADIG_USB1_MISC; + + /* If the SoCs don't have anatop, quit */ + if (!mxs_phy->regmap_anatop) + return false; + + if (mxs_phy->port_id == 0) + reg = ANADIG_USB1_MISC; + else if (mxs_phy->port_id == 1) + reg = ANADIG_USB2_MISC; + + regmap_read(mxs_phy->regmap_anatop, reg, &line_state); + + if ((line_state & (dp_bit | dm_bit)) == dm_bit) + return true; + else + return false; +} + +static int mxs_phy_suspend(struct usb_phy *x, int suspend) +{ + int ret; + struct mxs_phy *mxs_phy = to_mxs_phy(x); + bool low_speed_connection, vbus_is_on; + + low_speed_connection = mxs_phy_is_low_speed_connection(mxs_phy); + vbus_is_on = mxs_phy_get_vbus_status(mxs_phy); + + if (suspend) { + /* + * FIXME: Do not power down RXPWD1PT1 bit for low speed + * connect. The low speed connection will have problem at + * very rare cases during usb suspend and resume process. + */ + if (low_speed_connection & vbus_is_on) { + /* + * If value to be set as pwd value is not 0xffffffff, + * several 32Khz cycles are needed. + */ + mxs_phy_clock_switch_delay(); + writel(0xffbfffff, x->io_priv + HW_USBPHY_PWD); + } else { + writel(0xffffffff, x->io_priv + HW_USBPHY_PWD); + } + writel(BM_USBPHY_CTRL_CLKGATE, + x->io_priv + HW_USBPHY_CTRL_SET); + if (!(mxs_phy->port_id == 1 && + (mxs_phy->data->flags & + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK))) + clk_disable_unprepare(mxs_phy->clk); + } else { + mxs_phy_clock_switch_delay(); + if (!(mxs_phy->port_id == 1 && + (mxs_phy->data->flags & + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK))) { + ret = clk_prepare_enable(mxs_phy->clk); + if (ret) + return ret; + } + 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_set_wakeup(struct usb_phy *x, bool enabled) +{ + struct mxs_phy *mxs_phy = to_mxs_phy(x); + u32 value = BM_USBPHY_CTRL_ENVBUSCHG_WKUP | + BM_USBPHY_CTRL_ENDPDMCHG_WKUP | + BM_USBPHY_CTRL_ENIDCHG_WKUP; + if (enabled) { + mxs_phy_disconnect_line(mxs_phy, true); + writel_relaxed(value, x->io_priv + HW_USBPHY_CTRL_SET); + } else { + writel_relaxed(value, x->io_priv + HW_USBPHY_CTRL_CLR); + mxs_phy_disconnect_line(mxs_phy, false); + } + + return 0; +} + +static int mxs_phy_on_connect(struct usb_phy *phy, + enum usb_device_speed speed) +{ + dev_dbg(phy->dev, "%s device has connected\n", + (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS"); + + 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 device has disconnected\n", + (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS"); + + /* Sometimes, the speed is not high speed when the error occurs */ + if (readl(phy->io_priv + HW_USBPHY_CTRL) & + BM_USBPHY_CTRL_ENHOSTDISCONDETECT) + writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, + phy->io_priv + HW_USBPHY_CTRL_CLR); + + return 0; +} + +#define MXS_USB_CHARGER_DATA_CONTACT_TIMEOUT 100 +static int mxs_charger_data_contact_detect(struct mxs_phy *x) +{ + struct regmap *regmap = x->regmap_anatop; + int i, stable_contact_count = 0; + u32 val; + + /* Check if vbus is valid */ + regmap_read(regmap, ANADIG_USB1_VBUS_DET_STAT, &val); + if (!(val & ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID)) { + dev_err(x->phy.dev, "vbus is not valid\n"); + return -EINVAL; + } + + /* Enable charger detector */ + regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_CLR, + ANADIG_USB1_CHRG_DETECT_EN_B); + /* + * - Do not check whether a charger is connected to the USB port + * - Check whether the USB plug has been in contact with each other + */ + regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET, + ANADIG_USB1_CHRG_DETECT_CHK_CONTACT | + ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B); + + /* Check if plug is connected */ + for (i = 0; i < MXS_USB_CHARGER_DATA_CONTACT_TIMEOUT; i++) { + regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val); + if (val & ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT) { + stable_contact_count++; + if (stable_contact_count > 5) + /* Data pin makes contact */ + break; + else + usleep_range(5000, 10000); + } else { + stable_contact_count = 0; + usleep_range(5000, 6000); + } + } + + if (i == MXS_USB_CHARGER_DATA_CONTACT_TIMEOUT) { + dev_err(x->phy.dev, + "Data pin can't make good contact.\n"); + /* Disable charger detector */ + regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET, + ANADIG_USB1_CHRG_DETECT_EN_B | + ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B); + return -ENXIO; + } + + return 0; +} + +static enum usb_charger_type mxs_charger_primary_detection(struct mxs_phy *x) +{ + struct regmap *regmap = x->regmap_anatop; + enum usb_charger_type chgr_type = UNKNOWN_TYPE; + u32 val; + + /* + * - Do check whether a charger is connected to the USB port + * - Do not Check whether the USB plug has been in contact with + * each other + */ + regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_CLR, + ANADIG_USB1_CHRG_DETECT_CHK_CONTACT | + ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B); + + msleep(100); + + /* Check if it is a charger */ + regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val); + if (!(val & ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED)) { + chgr_type = SDP_TYPE; + dev_dbg(x->phy.dev, "It is a standard downstream port\n"); + } + + /* Disable charger detector */ + regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET, + ANADIG_USB1_CHRG_DETECT_EN_B | + ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B); + + return chgr_type; +} + +/* + * It must be called after DP is pulled up, which is used to + * differentiate DCP and CDP. + */ +static enum usb_charger_type mxs_charger_secondary_detection(struct mxs_phy *x) +{ + struct regmap *regmap = x->regmap_anatop; + int val; + + msleep(80); + + regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val); + if (val & ANADIG_USB1_CHRG_DET_STAT_DM_STATE) { + dev_dbg(x->phy.dev, "It is a dedicate charging port\n"); + return DCP_TYPE; + } else { + dev_dbg(x->phy.dev, "It is a charging downstream port\n"); + return CDP_TYPE; + } +} + +static enum usb_charger_type mxs_phy_charger_detect(struct usb_phy *phy) +{ + struct mxs_phy *mxs_phy = to_mxs_phy(phy); + struct regmap *regmap = mxs_phy->regmap_anatop; + void __iomem *base = phy->io_priv; + enum usb_charger_type chgr_type = UNKNOWN_TYPE; + + if (!regmap) + return UNKNOWN_TYPE; + + if (mxs_charger_data_contact_detect(mxs_phy)) + return chgr_type; + + chgr_type = mxs_charger_primary_detection(mxs_phy); + + if (chgr_type != SDP_TYPE) { + /* Pull up DP via test */ + writel_relaxed(BM_USBPHY_DEBUG_CLKGATE, + base + HW_USBPHY_DEBUG_CLR); + regmap_write(regmap, ANADIG_USB1_LOOPBACK_SET, + ANADIG_USB1_LOOPBACK_UTMI_TESTSTART); + + chgr_type = mxs_charger_secondary_detection(mxs_phy); + + /* Stop the test */ + regmap_write(regmap, ANADIG_USB1_LOOPBACK_CLR, + ANADIG_USB1_LOOPBACK_UTMI_TESTSTART); + writel_relaxed(BM_USBPHY_DEBUG_CLKGATE, + base + HW_USBPHY_DEBUG_SET); + } + + return chgr_type; +} + +static int mxs_phy_probe(struct platform_device *pdev) +{ + void __iomem *base; + struct clk *clk; + struct mxs_phy *mxs_phy; + int ret; + struct device_node *np = pdev->dev.of_node; + u32 val; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(clk), + "can't get the clock\n"); + + mxs_phy = devm_kzalloc(&pdev->dev, sizeof(*mxs_phy), GFP_KERNEL); + if (!mxs_phy) + return -ENOMEM; + + /* Some SoCs don't have anatop registers */ + if (of_property_present(np, "fsl,anatop")) { + mxs_phy->regmap_anatop = syscon_regmap_lookup_by_phandle + (np, "fsl,anatop"); + if (IS_ERR(mxs_phy->regmap_anatop)) { + dev_dbg(&pdev->dev, + "failed to find regmap for anatop\n"); + return PTR_ERR(mxs_phy->regmap_anatop); + } + } + + /* Currently, only imx7ulp has SIM module */ + if (of_get_property(np, "nxp,sim", NULL)) { + mxs_phy->regmap_sim = syscon_regmap_lookup_by_phandle + (np, "nxp,sim"); + if (IS_ERR(mxs_phy->regmap_sim)) { + dev_dbg(&pdev->dev, + "failed to find regmap for sim\n"); + return PTR_ERR(mxs_phy->regmap_sim); + } + } + + /* Precompute which bits of the TX register are to be updated, if any */ + if (!of_property_read_u32(np, "fsl,tx-cal-45-dn-ohms", &val) && + val >= MXS_PHY_TX_CAL45_MIN && val <= MXS_PHY_TX_CAL45_MAX) { + /* Scale to a 4-bit value */ + val = (MXS_PHY_TX_CAL45_MAX - val) * 0xF + / (MXS_PHY_TX_CAL45_MAX - MXS_PHY_TX_CAL45_MIN); + mxs_phy->tx_reg_mask |= GM_USBPHY_TX_TXCAL45DN(~0); + mxs_phy->tx_reg_set |= GM_USBPHY_TX_TXCAL45DN(val); + } + + if (!of_property_read_u32(np, "fsl,tx-cal-45-dp-ohms", &val) && + val >= MXS_PHY_TX_CAL45_MIN && val <= MXS_PHY_TX_CAL45_MAX) { + /* Scale to a 4-bit value. */ + val = (MXS_PHY_TX_CAL45_MAX - val) * 0xF + / (MXS_PHY_TX_CAL45_MAX - MXS_PHY_TX_CAL45_MIN); + mxs_phy->tx_reg_mask |= GM_USBPHY_TX_TXCAL45DP(~0); + mxs_phy->tx_reg_set |= GM_USBPHY_TX_TXCAL45DP(val); + } + + if (!of_property_read_u32(np, "fsl,tx-d-cal", &val) && + val >= MXS_PHY_TX_D_CAL_MIN && val <= MXS_PHY_TX_D_CAL_MAX) { + /* Scale to a 4-bit value. Round up the values and heavily + * weight the rounding by adding 2/3 of the denominator. + */ + val = ((MXS_PHY_TX_D_CAL_MAX - val) * 0xF + + (MXS_PHY_TX_D_CAL_MAX - MXS_PHY_TX_D_CAL_MIN) * 2/3) + / (MXS_PHY_TX_D_CAL_MAX - MXS_PHY_TX_D_CAL_MIN); + mxs_phy->tx_reg_mask |= GM_USBPHY_TX_D_CAL(~0); + mxs_phy->tx_reg_set |= GM_USBPHY_TX_D_CAL(val); + } + + ret = of_alias_get_id(np, "usbphy"); + if (ret < 0) + dev_dbg(&pdev->dev, "failed to get alias id, errno %d\n", ret); + mxs_phy->port_id = ret; + + 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; + mxs_phy->phy.set_wakeup = mxs_phy_set_wakeup; + mxs_phy->phy.charger_detect = mxs_phy_charger_detect; + + mxs_phy->clk = clk; + mxs_phy->data = of_device_get_match_data(&pdev->dev); + + mxs_phy->phy_3p0 = devm_regulator_get(&pdev->dev, "phy-3p0"); + if (PTR_ERR(mxs_phy->phy_3p0) == -ENODEV) + /* not exist */ + mxs_phy->phy_3p0 = NULL; + else if (IS_ERR(mxs_phy->phy_3p0)) + return dev_err_probe(&pdev->dev, PTR_ERR(mxs_phy->phy_3p0), + "Getting regulator error\n"); + + if (mxs_phy->phy_3p0) + regulator_set_voltage(mxs_phy->phy_3p0, 3200000, 3200000); + + platform_set_drvdata(pdev, mxs_phy); + + device_set_wakeup_capable(&pdev->dev, true); + + return usb_add_phy_dev(&mxs_phy->phy); +} + +static void mxs_phy_remove(struct platform_device *pdev) +{ + struct mxs_phy *mxs_phy = platform_get_drvdata(pdev); + + usb_remove_phy(&mxs_phy->phy); +} + +#ifdef CONFIG_PM_SLEEP +static void mxs_phy_wakeup_enable(struct mxs_phy *mxs_phy, bool on) +{ + u32 mask = USB_PHY_VLLS_WAKEUP_EN; + + /* If the SoCs don't have SIM, quit */ + if (!mxs_phy->regmap_sim) + return; + + if (on) { + regmap_update_bits(mxs_phy->regmap_sim, SIM_GPR1, mask, mask); + udelay(500); + } else { + regmap_update_bits(mxs_phy->regmap_sim, SIM_GPR1, mask, 0); + } +} + +static void mxs_phy_enable_ldo_in_suspend(struct mxs_phy *mxs_phy, bool on) +{ + unsigned int reg; + u32 value; + + /* If the SoCs don't have anatop, quit */ + if (!mxs_phy->regmap_anatop) + return; + + if (is_imx6q_phy(mxs_phy)) { + reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR; + regmap_write(mxs_phy->regmap_anatop, reg, + BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG); + } else if (is_imx6sl_phy(mxs_phy)) { + reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR; + regmap_write(mxs_phy->regmap_anatop, + reg, BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL); + } else if (is_imx6ul_phy(mxs_phy)) { + reg = on ? ANADIG_REG_1P1_SET : ANADIG_REG_1P1_CLR; + value = BM_ANADIG_REG_1P1_ENABLE_WEAK_LINREG | + BM_ANADIG_REG_1P1_TRACK_VDD_SOC_CAP; + if (mxs_phy_get_vbus_status(mxs_phy) && on) + regmap_write(mxs_phy->regmap_anatop, reg, value); + else if (!on) + regmap_write(mxs_phy->regmap_anatop, reg, value); + } +} + +static int mxs_phy_system_suspend(struct device *dev) +{ + struct mxs_phy *mxs_phy = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) { + mxs_phy_enable_ldo_in_suspend(mxs_phy, true); + mxs_phy_wakeup_enable(mxs_phy, true); + } + + return 0; +} + +static int mxs_phy_system_resume(struct device *dev) +{ + struct mxs_phy *mxs_phy = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) { + mxs_phy_enable_ldo_in_suspend(mxs_phy, false); + mxs_phy_wakeup_enable(mxs_phy, false); + } + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(mxs_phy_pm, mxs_phy_system_suspend, + mxs_phy_system_resume); + +static struct platform_driver mxs_phy_driver = { + .probe = mxs_phy_probe, + .remove = mxs_phy_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = mxs_phy_dt_ids, + .pm = &mxs_phy_pm, + }, +}; + +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-omap-otg.c b/drivers/usb/phy/phy-omap-otg.c new file mode 100644 index 000000000..6e6ef8c0b --- /dev/null +++ b/drivers/usb/phy/phy-omap-otg.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * OMAP OTG controller driver + * + * Based on code from tahvo-usb.c and isp1301_omap.c drivers. + * + * Copyright (C) 2005-2006 Nokia Corporation + * Copyright (C) 2004 Texas Instruments + * Copyright (C) 2004 David Brownell + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct otg_device { + void __iomem *base; + bool id; + bool vbus; + struct extcon_dev *extcon; + struct notifier_block vbus_nb; + struct notifier_block id_nb; +}; + +#define OMAP_OTG_CTRL 0x0c +#define OMAP_OTG_ASESSVLD (1 << 20) +#define OMAP_OTG_BSESSEND (1 << 19) +#define OMAP_OTG_BSESSVLD (1 << 18) +#define OMAP_OTG_VBUSVLD (1 << 17) +#define OMAP_OTG_ID (1 << 16) +#define OMAP_OTG_XCEIV_OUTPUTS \ + (OMAP_OTG_ASESSVLD | OMAP_OTG_BSESSEND | OMAP_OTG_BSESSVLD | \ + OMAP_OTG_VBUSVLD | OMAP_OTG_ID) + +static void omap_otg_ctrl(struct otg_device *otg_dev, u32 outputs) +{ + u32 l; + + l = readl(otg_dev->base + OMAP_OTG_CTRL); + l &= ~OMAP_OTG_XCEIV_OUTPUTS; + l |= outputs; + writel(l, otg_dev->base + OMAP_OTG_CTRL); +} + +static void omap_otg_set_mode(struct otg_device *otg_dev) +{ + if (!otg_dev->id && otg_dev->vbus) + /* Set B-session valid. */ + omap_otg_ctrl(otg_dev, OMAP_OTG_ID | OMAP_OTG_BSESSVLD); + else if (otg_dev->vbus) + /* Set A-session valid. */ + omap_otg_ctrl(otg_dev, OMAP_OTG_ASESSVLD); + else if (!otg_dev->id) + /* Set B-session end to indicate no VBUS. */ + omap_otg_ctrl(otg_dev, OMAP_OTG_ID | OMAP_OTG_BSESSEND); +} + +static int omap_otg_id_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct otg_device *otg_dev = container_of(nb, struct otg_device, id_nb); + + otg_dev->id = event; + omap_otg_set_mode(otg_dev); + + return NOTIFY_DONE; +} + +static int omap_otg_vbus_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct otg_device *otg_dev = container_of(nb, struct otg_device, + vbus_nb); + + otg_dev->vbus = event; + omap_otg_set_mode(otg_dev); + + return NOTIFY_DONE; +} + +static int omap_otg_probe(struct platform_device *pdev) +{ + const struct omap_usb_config *config = pdev->dev.platform_data; + struct otg_device *otg_dev; + struct extcon_dev *extcon; + int ret; + u32 rev; + + if (!config || !config->extcon) + return -ENODEV; + + extcon = extcon_get_extcon_dev(config->extcon); + if (IS_ERR(extcon)) + return PTR_ERR(extcon); + + otg_dev = devm_kzalloc(&pdev->dev, sizeof(*otg_dev), GFP_KERNEL); + if (!otg_dev) + return -ENOMEM; + + otg_dev->base = devm_ioremap_resource(&pdev->dev, &pdev->resource[0]); + if (IS_ERR(otg_dev->base)) + return PTR_ERR(otg_dev->base); + + otg_dev->extcon = extcon; + otg_dev->id_nb.notifier_call = omap_otg_id_notifier; + otg_dev->vbus_nb.notifier_call = omap_otg_vbus_notifier; + + ret = devm_extcon_register_notifier(&pdev->dev, extcon, + EXTCON_USB_HOST, &otg_dev->id_nb); + if (ret) + return ret; + + ret = devm_extcon_register_notifier(&pdev->dev, extcon, + EXTCON_USB, &otg_dev->vbus_nb); + if (ret) { + return ret; + } + + otg_dev->id = extcon_get_state(extcon, EXTCON_USB_HOST); + otg_dev->vbus = extcon_get_state(extcon, EXTCON_USB); + omap_otg_set_mode(otg_dev); + + rev = readl(otg_dev->base); + + dev_info(&pdev->dev, + "OMAP USB OTG controller rev %d.%d (%s, id=%d, vbus=%d)\n", + (rev >> 4) & 0xf, rev & 0xf, config->extcon, otg_dev->id, + otg_dev->vbus); + + platform_set_drvdata(pdev, otg_dev); + + return 0; +} + +static struct platform_driver omap_otg_driver = { + .probe = omap_otg_probe, + .driver = { + .name = "omap_otg", + }, +}; +module_platform_driver(omap_otg_driver); + +MODULE_DESCRIPTION("OMAP USB OTG controller driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Aaro Koskinen "); diff --git a/drivers/usb/phy/phy-tahvo.c b/drivers/usb/phy/phy-tahvo.c new file mode 100644 index 000000000..88607d0ed --- /dev/null +++ b/drivers/usb/phy/phy-tahvo.c @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Tahvo USB transceiver driver + * + * Copyright (C) 2005-2006 Nokia Corporation + * + * Parts copied from isp1301_omap.c. + * Copyright (C) 2004 Texas Instruments + * Copyright (C) 2004 David Brownell + * + * Original driver written by Juha Yrjölä, Tony Lindgren and Timo Teräs. + * Modified for Retu/Tahvo MFD by Aaro Koskinen. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "tahvo-usb" + +#define TAHVO_REG_IDSR 0x02 +#define TAHVO_REG_USBR 0x06 + +#define USBR_SLAVE_CONTROL (1 << 8) +#define USBR_VPPVIO_SW (1 << 7) +#define USBR_SPEED (1 << 6) +#define USBR_REGOUT (1 << 5) +#define USBR_MASTER_SW2 (1 << 4) +#define USBR_MASTER_SW1 (1 << 3) +#define USBR_SLAVE_SW (1 << 2) +#define USBR_NSUSPEND (1 << 1) +#define USBR_SEMODE (1 << 0) + +#define TAHVO_MODE_HOST 0 +#define TAHVO_MODE_PERIPHERAL 1 + +struct tahvo_usb { + struct platform_device *pt_dev; + struct usb_phy phy; + int vbus_state; + struct mutex serialize; + struct clk *ick; + int irq; + int tahvo_mode; + struct extcon_dev *extcon; +}; + +static const unsigned int tahvo_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + + EXTCON_NONE, +}; + +static ssize_t vbus_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct tahvo_usb *tu = dev_get_drvdata(device); + return sprintf(buf, "%s\n", str_on_off(tu->vbus_state)); +} +static DEVICE_ATTR_RO(vbus); + +static void check_vbus_state(struct tahvo_usb *tu) +{ + struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); + int reg, prev_state; + + reg = retu_read(rdev, TAHVO_REG_IDSR); + if (reg & TAHVO_STAT_VBUS) { + switch (tu->phy.otg->state) { + case OTG_STATE_B_IDLE: + /* Enable the gadget driver */ + if (tu->phy.otg->gadget) + usb_gadget_vbus_connect(tu->phy.otg->gadget); + tu->phy.otg->state = OTG_STATE_B_PERIPHERAL; + usb_phy_set_event(&tu->phy, USB_EVENT_ENUMERATED); + break; + case OTG_STATE_A_IDLE: + /* + * Session is now valid assuming the USB hub is driving + * Vbus. + */ + tu->phy.otg->state = OTG_STATE_A_HOST; + break; + default: + break; + } + dev_info(&tu->pt_dev->dev, "USB cable connected\n"); + } else { + switch (tu->phy.otg->state) { + case OTG_STATE_B_PERIPHERAL: + if (tu->phy.otg->gadget) + usb_gadget_vbus_disconnect(tu->phy.otg->gadget); + tu->phy.otg->state = OTG_STATE_B_IDLE; + usb_phy_set_event(&tu->phy, USB_EVENT_NONE); + break; + case OTG_STATE_A_HOST: + tu->phy.otg->state = OTG_STATE_A_IDLE; + break; + default: + break; + } + dev_info(&tu->pt_dev->dev, "USB cable disconnected\n"); + } + + prev_state = tu->vbus_state; + tu->vbus_state = reg & TAHVO_STAT_VBUS; + if (prev_state != tu->vbus_state) { + extcon_set_state_sync(tu->extcon, EXTCON_USB, tu->vbus_state); + sysfs_notify(&tu->pt_dev->dev.kobj, NULL, "vbus_state"); + } +} + +static void tahvo_usb_become_host(struct tahvo_usb *tu) +{ + struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); + + extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST, true); + + /* Power up the transceiver in USB host mode */ + retu_write(rdev, TAHVO_REG_USBR, USBR_REGOUT | USBR_NSUSPEND | + USBR_MASTER_SW2 | USBR_MASTER_SW1); + tu->phy.otg->state = OTG_STATE_A_IDLE; + + check_vbus_state(tu); +} + +static void tahvo_usb_stop_host(struct tahvo_usb *tu) +{ + tu->phy.otg->state = OTG_STATE_A_IDLE; +} + +static void tahvo_usb_become_peripheral(struct tahvo_usb *tu) +{ + struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); + + extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST, false); + + /* Power up transceiver and set it in USB peripheral mode */ + retu_write(rdev, TAHVO_REG_USBR, USBR_SLAVE_CONTROL | USBR_REGOUT | + USBR_NSUSPEND | USBR_SLAVE_SW); + tu->phy.otg->state = OTG_STATE_B_IDLE; + + check_vbus_state(tu); +} + +static void tahvo_usb_stop_peripheral(struct tahvo_usb *tu) +{ + if (tu->phy.otg->gadget) + usb_gadget_vbus_disconnect(tu->phy.otg->gadget); + tu->phy.otg->state = OTG_STATE_B_IDLE; +} + +static void tahvo_usb_power_off(struct tahvo_usb *tu) +{ + struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); + + /* Disable gadget controller if any */ + if (tu->phy.otg->gadget) + usb_gadget_vbus_disconnect(tu->phy.otg->gadget); + + /* Power off transceiver */ + retu_write(rdev, TAHVO_REG_USBR, 0); + tu->phy.otg->state = OTG_STATE_UNDEFINED; +} + +static int tahvo_usb_set_suspend(struct usb_phy *dev, int suspend) +{ + struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, phy); + struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent); + u16 w; + + dev_dbg(&tu->pt_dev->dev, "%s\n", __func__); + + w = retu_read(rdev, TAHVO_REG_USBR); + if (suspend) + w &= ~USBR_NSUSPEND; + else + w |= USBR_NSUSPEND; + retu_write(rdev, TAHVO_REG_USBR, w); + + return 0; +} + +static int tahvo_usb_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + struct tahvo_usb *tu = container_of(otg->usb_phy, struct tahvo_usb, + phy); + + mutex_lock(&tu->serialize); + + if (host == NULL) { + if (tu->tahvo_mode == TAHVO_MODE_HOST) + tahvo_usb_power_off(tu); + otg->host = NULL; + mutex_unlock(&tu->serialize); + return 0; + } + + if (tu->tahvo_mode == TAHVO_MODE_HOST) { + otg->host = NULL; + tahvo_usb_become_host(tu); + } + + otg->host = host; + + mutex_unlock(&tu->serialize); + + return 0; +} + +static int tahvo_usb_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + struct tahvo_usb *tu = container_of(otg->usb_phy, struct tahvo_usb, + phy); + + mutex_lock(&tu->serialize); + + if (!gadget) { + if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL) + tahvo_usb_power_off(tu); + tu->phy.otg->gadget = NULL; + mutex_unlock(&tu->serialize); + return 0; + } + + tu->phy.otg->gadget = gadget; + if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL) + tahvo_usb_become_peripheral(tu); + + mutex_unlock(&tu->serialize); + + return 0; +} + +static irqreturn_t tahvo_usb_vbus_interrupt(int irq, void *_tu) +{ + struct tahvo_usb *tu = _tu; + + mutex_lock(&tu->serialize); + check_vbus_state(tu); + mutex_unlock(&tu->serialize); + + return IRQ_HANDLED; +} + +static ssize_t otg_mode_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct tahvo_usb *tu = dev_get_drvdata(device); + + switch (tu->tahvo_mode) { + case TAHVO_MODE_HOST: + return sprintf(buf, "host\n"); + case TAHVO_MODE_PERIPHERAL: + return sprintf(buf, "peripheral\n"); + } + + return -EINVAL; +} + +static ssize_t otg_mode_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tahvo_usb *tu = dev_get_drvdata(device); + int r; + + mutex_lock(&tu->serialize); + if (count >= 4 && strncmp(buf, "host", 4) == 0) { + if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL) + tahvo_usb_stop_peripheral(tu); + tu->tahvo_mode = TAHVO_MODE_HOST; + if (tu->phy.otg->host) { + dev_info(device, "HOST mode: host controller present\n"); + tahvo_usb_become_host(tu); + } else { + dev_info(device, "HOST mode: no host controller, powering off\n"); + tahvo_usb_power_off(tu); + } + r = strlen(buf); + } else if (count >= 10 && strncmp(buf, "peripheral", 10) == 0) { + if (tu->tahvo_mode == TAHVO_MODE_HOST) + tahvo_usb_stop_host(tu); + tu->tahvo_mode = TAHVO_MODE_PERIPHERAL; + if (tu->phy.otg->gadget) { + dev_info(device, "PERIPHERAL mode: gadget driver present\n"); + tahvo_usb_become_peripheral(tu); + } else { + dev_info(device, "PERIPHERAL mode: no gadget driver, powering off\n"); + tahvo_usb_power_off(tu); + } + r = strlen(buf); + } else { + r = -EINVAL; + } + mutex_unlock(&tu->serialize); + + return r; +} +static DEVICE_ATTR_RW(otg_mode); + +static struct attribute *tahvo_attrs[] = { + &dev_attr_vbus.attr, + &dev_attr_otg_mode.attr, + NULL +}; +ATTRIBUTE_GROUPS(tahvo); + +static int tahvo_usb_probe(struct platform_device *pdev) +{ + struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent); + struct tahvo_usb *tu; + int ret; + + tu = devm_kzalloc(&pdev->dev, sizeof(*tu), GFP_KERNEL); + if (!tu) + return -ENOMEM; + + tu->phy.otg = devm_kzalloc(&pdev->dev, sizeof(*tu->phy.otg), + GFP_KERNEL); + if (!tu->phy.otg) + return -ENOMEM; + + tu->pt_dev = pdev; + + /* Default mode */ +#ifdef CONFIG_TAHVO_USB_HOST_BY_DEFAULT + tu->tahvo_mode = TAHVO_MODE_HOST; +#else + tu->tahvo_mode = TAHVO_MODE_PERIPHERAL; +#endif + + mutex_init(&tu->serialize); + + tu->ick = devm_clk_get(&pdev->dev, "usb_l4_ick"); + if (!IS_ERR(tu->ick)) + clk_enable(tu->ick); + + /* + * Set initial state, so that we generate kevents only on state changes. + */ + tu->vbus_state = retu_read(rdev, TAHVO_REG_IDSR) & TAHVO_STAT_VBUS; + + tu->extcon = devm_extcon_dev_allocate(&pdev->dev, tahvo_cable); + if (IS_ERR(tu->extcon)) { + dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); + ret = PTR_ERR(tu->extcon); + goto err_disable_clk; + } + + ret = devm_extcon_dev_register(&pdev->dev, tu->extcon); + if (ret) { + dev_err(&pdev->dev, "could not register extcon device: %d\n", + ret); + goto err_disable_clk; + } + + /* Set the initial cable state. */ + extcon_set_state_sync(tu->extcon, EXTCON_USB_HOST, + tu->tahvo_mode == TAHVO_MODE_HOST); + extcon_set_state_sync(tu->extcon, EXTCON_USB, tu->vbus_state); + + /* Create OTG interface */ + tahvo_usb_power_off(tu); + tu->phy.dev = &pdev->dev; + tu->phy.otg->state = OTG_STATE_UNDEFINED; + tu->phy.label = DRIVER_NAME; + tu->phy.set_suspend = tahvo_usb_set_suspend; + + tu->phy.otg->usb_phy = &tu->phy; + tu->phy.otg->set_host = tahvo_usb_set_host; + tu->phy.otg->set_peripheral = tahvo_usb_set_peripheral; + + ret = usb_add_phy(&tu->phy, USB_PHY_TYPE_USB2); + if (ret < 0) { + dev_err(&pdev->dev, "cannot register USB transceiver: %d\n", + ret); + goto err_disable_clk; + } + + dev_set_drvdata(&pdev->dev, tu); + + tu->irq = ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto err_remove_phy; + ret = request_threaded_irq(tu->irq, NULL, tahvo_usb_vbus_interrupt, + IRQF_ONESHOT, + "tahvo-vbus", tu); + if (ret) { + dev_err(&pdev->dev, "could not register tahvo-vbus irq: %d\n", + ret); + goto err_remove_phy; + } + + return 0; + +err_remove_phy: + usb_remove_phy(&tu->phy); +err_disable_clk: + if (!IS_ERR(tu->ick)) + clk_disable(tu->ick); + + return ret; +} + +static void tahvo_usb_remove(struct platform_device *pdev) +{ + struct tahvo_usb *tu = platform_get_drvdata(pdev); + + free_irq(tu->irq, tu); + usb_remove_phy(&tu->phy); + if (!IS_ERR(tu->ick)) + clk_disable(tu->ick); +} + +static struct platform_driver tahvo_usb_driver = { + .probe = tahvo_usb_probe, + .remove = tahvo_usb_remove, + .driver = { + .name = "tahvo-usb", + .dev_groups = tahvo_groups, + }, +}; +module_platform_driver(tahvo_usb_driver); + +MODULE_DESCRIPTION("Tahvo USB transceiver driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Juha Yrjölä, Tony Lindgren, and Timo Teräs"); +MODULE_AUTHOR("Aaro Koskinen "); diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c new file mode 100644 index 000000000..00443a7be --- /dev/null +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -0,0 +1,1655 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2013 NVIDIA Corporation + * + * Author: + * Erik Gilling + * Benoit Goby + * Venu Byravarasu + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#define USB_TXFILLTUNING 0x154 +#define USB_FIFO_TXFILL_THRES(x) (((x) & 0x1f) << 16) +#define USB_FIFO_TXFILL_MASK 0x1f0000 + +#define ULPI_VIEWPORT 0x170 + +/* PORTSC PTS/PHCD bits, Tegra20 only */ +#define TEGRA_USB_PORTSC1 0x184 +#define TEGRA_USB_PORTSC1_PTS(x) (((x) & 0x3) << 30) +#define TEGRA_USB_PORTSC1_PHCD BIT(23) +#define TEGRA_USB_PORTSC1_WKOC BIT(22) +#define TEGRA_USB_PORTSC1_WKDS BIT(21) +#define TEGRA_USB_PORTSC1_WKCN BIT(20) + +/* HOSTPC1 PTS/PHCD bits, Tegra30 and above */ +#define TEGRA30_USB_PORTSC1 0x174 +#define TEGRA_USB_HOSTPC1_DEVLC 0x1b4 +#define TEGRA_USB_HOSTPC1_DEVLC_PTS(x) (((x) & 0x7) << 29) +#define TEGRA_USB_HOSTPC1_DEVLC_PHCD BIT(22) +#define TEGRA_USB_HOSTPC1_DEVLC_PTS_HSIC 4 + +/* Bits of PORTSC1, which will get cleared by writing 1 into them */ +#define TEGRA_PORTSC1_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) + +#define USB_SUSP_CTRL 0x400 +#define USB_WAKE_ON_RESUME_EN BIT(2) +#define USB_WAKE_ON_CNNT_EN_DEV BIT(3) +#define USB_WAKE_ON_DISCON_EN_DEV BIT(4) +#define USB_SUSP_CLR BIT(5) +#define USB_PHY_CLK_VALID BIT(7) +#define UTMIP_RESET BIT(11) +#define UTMIP_PHY_ENABLE BIT(12) +#define ULPI_PHY_ENABLE BIT(13) +#define USB_SUSP_SET BIT(14) +#define UHSIC_RESET BIT(14) +#define USB_WAKEUP_DEBOUNCE_COUNT(x) (((x) & 0x7) << 16) +#define UHSIC_PHY_ENABLE BIT(19) + +#define USB_PHY_VBUS_SENSORS 0x404 +#define B_SESS_VLD_WAKEUP_EN BIT(14) +#define A_SESS_VLD_WAKEUP_EN BIT(22) +#define A_VBUS_VLD_WAKEUP_EN BIT(30) + +#define USB_PHY_VBUS_WAKEUP_ID 0x408 +#define ID_INT_EN BIT(0) +#define ID_CHG_DET BIT(1) +#define VBUS_WAKEUP_INT_EN BIT(8) +#define VBUS_WAKEUP_CHG_DET BIT(9) +#define VBUS_WAKEUP_STS BIT(10) +#define VBUS_WAKEUP_WAKEUP_EN BIT(30) + +#define USB1_LEGACY_CTRL 0x410 +#define USB1_NO_LEGACY_MODE BIT(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 BIT(10) +#define ULPI_CLKOUT_PINMUX_BYP BIT(11) + +#define ULPI_TIMING_CTRL_1 0x428 +#define ULPI_DATA_TRIMMER_LOAD BIT(0) +#define ULPI_DATA_TRIMMER_SEL(x) (((x) & 0x7) << 1) +#define ULPI_STPDIRNXT_TRIMMER_LOAD BIT(16) +#define ULPI_STPDIRNXT_TRIMMER_SEL(x) (((x) & 0x7) << 17) +#define ULPI_DIR_TRIMMER_LOAD BIT(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_SETUP_MSB(x) ((((x) & 0x70) >> 4) << 22) +#define UTMIP_XCVR_LSRSLEW(x) (((x) & 0x3) << 8) +#define UTMIP_XCVR_LSFSLEW(x) (((x) & 0x3) << 10) +#define UTMIP_FORCE_PD_POWERDOWN BIT(14) +#define UTMIP_FORCE_PD2_POWERDOWN BIT(16) +#define UTMIP_FORCE_PDZI_POWERDOWN BIT(18) +#define UTMIP_XCVR_LSBIAS_SEL BIT(21) +#define UTMIP_XCVR_HSSLEW(x) (((x) & 0x3) << 4) +#define UTMIP_XCVR_HSSLEW_MSB(x) ((((x) & 0x1fc) >> 2) << 25) + +#define UTMIP_BIAS_CFG0 0x80c +#define UTMIP_OTGPD BIT(11) +#define UTMIP_BIASPD BIT(10) +#define UTMIP_HSSQUELCH_LEVEL(x) (((x) & 0x3) << 0) +#define UTMIP_HSDISCON_LEVEL(x) (((x) & 0x3) << 2) +#define UTMIP_HSDISCON_LEVEL_MSB(x) ((((x) & 0x4) >> 2) << 24) + +#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 BIT(19) +#define UTMIP_HS_DISCON_DISABLE BIT(8) + +#define UTMIP_MISC_CFG0 0x824 +#define UTMIP_DPDM_OBSERVE BIT(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 BIT(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 BIT(0) + +#define UTMIP_SPARE_CFG0 0x834 +#define FUSE_SETUP_SEL BIT(3) + +#define UTMIP_XCVR_CFG1 0x838 +#define UTMIP_FORCE_PDDISC_POWERDOWN BIT(0) +#define UTMIP_FORCE_PDCHRP_POWERDOWN BIT(2) +#define UTMIP_FORCE_PDDR_POWERDOWN BIT(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) + +/* + * Tegra20 has no UTMIP registers on PHY2 and UHSIC registers start from 0x800 + * just where UTMIP registers should have been. This is the case only with Tegra20 + * Tegra30+ have UTMIP registers at 0x800 and UHSIC registers are shifted by 0x400 + * to 0xc00, but register layout is preserved. + */ +#define UHSIC_PLL_CFG1 0x804 +#define UHSIC_XTAL_FREQ_COUNT(x) (((x) & 0xfff) << 0) +#define UHSIC_PLLU_ENABLE_DLY_COUNT(x) (((x) & 0x1f) << 14) + +#define UHSIC_HSRX_CFG0 0x808 +#define UHSIC_ELASTIC_UNDERRUN_LIMIT(x) (((x) & 0x1f) << 2) +#define UHSIC_ELASTIC_OVERRUN_LIMIT(x) (((x) & 0x1f) << 8) +#define UHSIC_IDLE_WAIT(x) (((x) & 0x1f) << 13) + +#define UHSIC_HSRX_CFG1 0x80c +#define UHSIC_HS_SYNC_START_DLY(x) (((x) & 0x1f) << 1) + +#define UHSIC_TX_CFG0 0x810 +#define UHSIC_HS_READY_WAIT_FOR_VALID BIT(9) + +#define UHSIC_MISC_CFG0 0x814 +#define UHSIC_SUSPEND_EXIT_ON_EDGE BIT(7) +#define UHSIC_DETECT_SHORT_CONNECT BIT(8) +#define UHSIC_FORCE_XCVR_MODE BIT(15) +#define UHSIC_DISABLE_BUSRESET BIT(20) + +#define UHSIC_MISC_CFG1 0x818 +#define UHSIC_PLLU_STABLE_COUNT(x) (((x) & 0xfff) << 2) + +#define UHSIC_PADS_CFG0 0x81c +#define UHSIC_TX_RTUNEN 0xf000 +#define UHSIC_TX_RTUNE(x) (((x) & 0xf) << 12) + +#define UHSIC_PADS_CFG1 0x820 +#define UHSIC_PD_BG BIT(2) +#define UHSIC_PD_TX BIT(3) +#define UHSIC_PD_TRK BIT(4) +#define UHSIC_PD_RX BIT(5) +#define UHSIC_PD_ZI BIT(6) +#define UHSIC_RX_SEL BIT(7) +#define UHSIC_RPD_DATA BIT(9) +#define UHSIC_RPD_STROBE BIT(10) +#define UHSIC_RPU_DATA BIT(11) +#define UHSIC_RPU_STROBE BIT(12) + +#define UHSIC_CMD_CFG0 0x824 +#define UHSIC_PRETEND_CONNECT_DETECT BIT(5) + +#define UHSIC_STAT_CFG0 0x828 +#define UHSIC_CONNECT_DETECT BIT(0) + +/* For Tegra30 and above only, the address is different in Tegra20 */ +#define USB_USBMODE 0x1f8 +#define USB_USBMODE_MASK (3 << 0) +#define USB_USBMODE_HOST (3 << 0) +#define USB_USBMODE_DEVICE (2 << 0) + +#define PMC_USB_AO 0xf0 +#define VBUS_WAKEUP_PD_P0 BIT(2) +#define ID_PD_P0 BIT(3) + +static DEFINE_SPINLOCK(utmip_pad_lock); +static unsigned int utmip_pad_count; + +struct tegra_xtal_freq { + unsigned int freq; + u8 enable_delay; + u8 stable_count; + u8 active_delay; + u8 utmi_xtal_freq_count; + u16 hsic_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, + .utmi_xtal_freq_count = 0x76, + .hsic_xtal_freq_count = 0x1CA, + .debounce = 0x7530, + }, + { + .freq = 13000000, + .enable_delay = 0x02, + .stable_count = 0x33, + .active_delay = 0x05, + .utmi_xtal_freq_count = 0x7F, + .hsic_xtal_freq_count = 0x1F0, + .debounce = 0x7EF4, + }, + { + .freq = 19200000, + .enable_delay = 0x03, + .stable_count = 0x4B, + .active_delay = 0x06, + .utmi_xtal_freq_count = 0xBB, + .hsic_xtal_freq_count = 0x2DD, + .debounce = 0xBB80, + }, + { + .freq = 26000000, + .enable_delay = 0x04, + .stable_count = 0x66, + .active_delay = 0x09, + .utmi_xtal_freq_count = 0xFE, + .hsic_xtal_freq_count = 0x3E0, + .debounce = 0xFDE8, + }, +}; + +static inline struct tegra_usb_phy *to_tegra_usb_phy(struct usb_phy *u_phy) +{ + return container_of(u_phy, struct tegra_usb_phy, u_phy); +} + +static void set_pts(struct tegra_usb_phy *phy, u8 pts_val) +{ + void __iomem *base = phy->regs; + u32 val; + + if (phy->soc_config->has_hostpc) { + val = readl_relaxed(base + TEGRA_USB_HOSTPC1_DEVLC); + val &= ~TEGRA_USB_HOSTPC1_DEVLC_PTS(~0); + val |= TEGRA_USB_HOSTPC1_DEVLC_PTS(pts_val); + writel_relaxed(val, base + TEGRA_USB_HOSTPC1_DEVLC); + } else { + val = readl_relaxed(base + TEGRA_USB_PORTSC1); + val &= ~TEGRA_PORTSC1_RWC_BITS; + val &= ~TEGRA_USB_PORTSC1_PTS(~0); + val |= TEGRA_USB_PORTSC1_PTS(pts_val); + writel_relaxed(val, base + TEGRA_USB_PORTSC1); + } +} + +static void set_phcd(struct tegra_usb_phy *phy, bool enable) +{ + void __iomem *base = phy->regs; + u32 val; + + if (phy->soc_config->has_hostpc) { + val = readl_relaxed(base + TEGRA_USB_HOSTPC1_DEVLC); + if (enable) + val |= TEGRA_USB_HOSTPC1_DEVLC_PHCD; + else + val &= ~TEGRA_USB_HOSTPC1_DEVLC_PHCD; + writel_relaxed(val, base + TEGRA_USB_HOSTPC1_DEVLC); + } else { + val = readl_relaxed(base + TEGRA_USB_PORTSC1) & ~PORT_RWC_BITS; + if (enable) + val |= TEGRA_USB_PORTSC1_PHCD; + else + val &= ~TEGRA_USB_PORTSC1_PHCD; + writel_relaxed(val, base + TEGRA_USB_PORTSC1); + } +} + +static int utmip_pad_open(struct tegra_usb_phy *phy) +{ + int ret; + + ret = clk_prepare_enable(phy->pad_clk); + if (ret) { + dev_err(phy->u_phy.dev, + "Failed to enable UTMI-pads clock: %d\n", ret); + return ret; + } + + spin_lock(&utmip_pad_lock); + + ret = reset_control_deassert(phy->pad_rst); + if (ret) { + dev_err(phy->u_phy.dev, + "Failed to initialize UTMI-pads reset: %d\n", ret); + goto unlock; + } + + ret = reset_control_assert(phy->pad_rst); + if (ret) { + dev_err(phy->u_phy.dev, + "Failed to assert UTMI-pads reset: %d\n", ret); + goto unlock; + } + + udelay(1); + + ret = reset_control_deassert(phy->pad_rst); + if (ret) + dev_err(phy->u_phy.dev, + "Failed to deassert UTMI-pads reset: %d\n", ret); +unlock: + spin_unlock(&utmip_pad_lock); + + clk_disable_unprepare(phy->pad_clk); + + return ret; +} + +static int utmip_pad_close(struct tegra_usb_phy *phy) +{ + int ret; + + ret = clk_prepare_enable(phy->pad_clk); + if (ret) { + dev_err(phy->u_phy.dev, + "Failed to enable UTMI-pads clock: %d\n", ret); + return ret; + } + + ret = reset_control_assert(phy->pad_rst); + if (ret) + dev_err(phy->u_phy.dev, + "Failed to assert UTMI-pads reset: %d\n", ret); + + udelay(1); + + clk_disable_unprepare(phy->pad_clk); + + return ret; +} + +static int utmip_pad_power_on(struct tegra_usb_phy *phy) +{ + struct tegra_utmip_config *config = phy->config; + void __iomem *base = phy->pad_regs; + u32 val; + int err; + + err = clk_prepare_enable(phy->pad_clk); + if (err) + return err; + + spin_lock(&utmip_pad_lock); + + if (utmip_pad_count++ == 0) { + val = readl_relaxed(base + UTMIP_BIAS_CFG0); + val &= ~(UTMIP_OTGPD | UTMIP_BIASPD); + + if (phy->soc_config->requires_extra_tuning_parameters) { + val &= ~(UTMIP_HSSQUELCH_LEVEL(~0) | + UTMIP_HSDISCON_LEVEL(~0) | + UTMIP_HSDISCON_LEVEL_MSB(~0)); + + val |= UTMIP_HSSQUELCH_LEVEL(config->hssquelch_level); + val |= UTMIP_HSDISCON_LEVEL(config->hsdiscon_level); + val |= UTMIP_HSDISCON_LEVEL_MSB(config->hsdiscon_level); + } + writel_relaxed(val, base + UTMIP_BIAS_CFG0); + } + + if (phy->pad_wakeup) { + phy->pad_wakeup = false; + utmip_pad_count--; + } + + spin_unlock(&utmip_pad_lock); + + clk_disable_unprepare(phy->pad_clk); + + return 0; +} + +static int utmip_pad_power_off(struct tegra_usb_phy *phy) +{ + void __iomem *base = phy->pad_regs; + u32 val; + int ret; + + ret = clk_prepare_enable(phy->pad_clk); + if (ret) + return ret; + + spin_lock(&utmip_pad_lock); + + if (!utmip_pad_count) { + dev_err(phy->u_phy.dev, "UTMIP pad already powered off\n"); + ret = -EINVAL; + goto ulock; + } + + /* + * In accordance to TRM, OTG and Bias pad circuits could be turned off + * to save power if wake is enabled, but the VBUS-change detection + * method is board-specific and these circuits may need to be enabled + * to generate wakeup event, hence we will just keep them both enabled. + */ + if (phy->wakeup_enabled) { + phy->pad_wakeup = true; + utmip_pad_count++; + } + + if (--utmip_pad_count == 0) { + val = readl_relaxed(base + UTMIP_BIAS_CFG0); + val |= UTMIP_OTGPD | UTMIP_BIASPD; + writel_relaxed(val, base + UTMIP_BIAS_CFG0); + } +ulock: + spin_unlock(&utmip_pad_lock); + + clk_disable_unprepare(phy->pad_clk); + + return ret; +} + +static int utmi_wait_register(void __iomem *reg, u32 mask, u32 result) +{ + u32 tmp; + + return readl_relaxed_poll_timeout(reg, tmp, (tmp & mask) == result, + 2000, 6000); +} + +static void utmi_phy_clk_disable(struct tegra_usb_phy *phy) +{ + void __iomem *base = phy->regs; + u32 val; + + /* + * The USB driver may have already initiated the phy clock + * disable so wait to see if the clock turns off and if not + * then proceed with gating the clock. + */ + if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, 0) == 0) + return; + + if (phy->is_legacy_phy) { + val = readl_relaxed(base + USB_SUSP_CTRL); + val |= USB_SUSP_SET; + writel_relaxed(val, base + USB_SUSP_CTRL); + + usleep_range(10, 100); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~USB_SUSP_SET; + writel_relaxed(val, base + USB_SUSP_CTRL); + } else { + set_phcd(phy, true); + } + + if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, 0)) + dev_err(phy->u_phy.dev, + "Timeout waiting for PHY to stabilize on disable\n"); +} + +static void utmi_phy_clk_enable(struct tegra_usb_phy *phy) +{ + void __iomem *base = phy->regs; + u32 val; + + /* + * The USB driver may have already initiated the phy clock + * enable so wait to see if the clock turns on and if not + * then proceed with ungating the clock. + */ + if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, + USB_PHY_CLK_VALID) == 0) + return; + + if (phy->is_legacy_phy) { + val = readl_relaxed(base + USB_SUSP_CTRL); + val |= USB_SUSP_CLR; + writel_relaxed(val, base + USB_SUSP_CTRL); + + usleep_range(10, 100); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~USB_SUSP_CLR; + writel_relaxed(val, base + USB_SUSP_CTRL); + } else { + set_phcd(phy, false); + } + + if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, + USB_PHY_CLK_VALID)) + dev_err(phy->u_phy.dev, + "Timeout waiting for PHY to stabilize on enable\n"); +} + +static int utmi_phy_power_on(struct tegra_usb_phy *phy) +{ + struct tegra_utmip_config *config = phy->config; + void __iomem *base = phy->regs; + u32 val; + int err; + + val = readl_relaxed(base + USB_SUSP_CTRL); + val |= UTMIP_RESET; + writel_relaxed(val, base + USB_SUSP_CTRL); + + if (phy->is_legacy_phy) { + val = readl_relaxed(base + USB1_LEGACY_CTRL); + val |= USB1_NO_LEGACY_MODE; + writel_relaxed(val, base + USB1_LEGACY_CTRL); + } + + val = readl_relaxed(base + UTMIP_TX_CFG0); + val |= UTMIP_FS_PREABMLE_J; + writel_relaxed(val, base + UTMIP_TX_CFG0); + + val = readl_relaxed(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_relaxed(val, base + UTMIP_HSRX_CFG0); + + val = readl_relaxed(base + UTMIP_HSRX_CFG1); + val &= ~UTMIP_HS_SYNC_START_DLY(~0); + val |= UTMIP_HS_SYNC_START_DLY(config->hssync_start_delay); + writel_relaxed(val, base + UTMIP_HSRX_CFG1); + + val = readl_relaxed(base + UTMIP_DEBOUNCE_CFG0); + val &= ~UTMIP_BIAS_DEBOUNCE_A(~0); + val |= UTMIP_BIAS_DEBOUNCE_A(phy->freq->debounce); + writel_relaxed(val, base + UTMIP_DEBOUNCE_CFG0); + + val = readl_relaxed(base + UTMIP_MISC_CFG0); + val &= ~UTMIP_SUSPEND_EXIT_ON_EDGE; + writel_relaxed(val, base + UTMIP_MISC_CFG0); + + if (!phy->soc_config->utmi_pll_config_in_car_module) { + val = readl_relaxed(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_relaxed(val, base + UTMIP_MISC_CFG1); + + val = readl_relaxed(base + UTMIP_PLL_CFG1); + val &= ~(UTMIP_XTAL_FREQ_COUNT(~0) | + UTMIP_PLLU_ENABLE_DLY_COUNT(~0)); + val |= UTMIP_XTAL_FREQ_COUNT(phy->freq->utmi_xtal_freq_count) | + UTMIP_PLLU_ENABLE_DLY_COUNT(phy->freq->enable_delay); + writel_relaxed(val, base + UTMIP_PLL_CFG1); + } + + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~USB_WAKE_ON_RESUME_EN; + writel_relaxed(val, base + USB_SUSP_CTRL); + + if (phy->mode != USB_DR_MODE_HOST) { + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~(USB_WAKE_ON_CNNT_EN_DEV | USB_WAKE_ON_DISCON_EN_DEV); + writel_relaxed(val, base + USB_SUSP_CTRL); + + val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID); + val &= ~VBUS_WAKEUP_WAKEUP_EN; + val &= ~(ID_CHG_DET | VBUS_WAKEUP_CHG_DET); + writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID); + + val = readl_relaxed(base + USB_PHY_VBUS_SENSORS); + val &= ~(A_VBUS_VLD_WAKEUP_EN | A_SESS_VLD_WAKEUP_EN); + val &= ~(B_SESS_VLD_WAKEUP_EN); + writel_relaxed(val, base + USB_PHY_VBUS_SENSORS); + + val = readl_relaxed(base + UTMIP_BAT_CHRG_CFG0); + val &= ~UTMIP_PD_CHRG; + writel_relaxed(val, base + UTMIP_BAT_CHRG_CFG0); + } else { + val = readl_relaxed(base + UTMIP_BAT_CHRG_CFG0); + val |= UTMIP_PD_CHRG; + writel_relaxed(val, base + UTMIP_BAT_CHRG_CFG0); + } + + err = utmip_pad_power_on(phy); + if (err) + return err; + + val = readl_relaxed(base + UTMIP_XCVR_CFG0); + val &= ~(UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN | + UTMIP_FORCE_PDZI_POWERDOWN | UTMIP_XCVR_LSBIAS_SEL | + UTMIP_XCVR_SETUP(~0) | UTMIP_XCVR_SETUP_MSB(~0) | + UTMIP_XCVR_LSFSLEW(~0) | UTMIP_XCVR_LSRSLEW(~0)); + + if (!config->xcvr_setup_use_fuses) { + val |= UTMIP_XCVR_SETUP(config->xcvr_setup); + val |= UTMIP_XCVR_SETUP_MSB(config->xcvr_setup); + } + val |= UTMIP_XCVR_LSFSLEW(config->xcvr_lsfslew); + val |= UTMIP_XCVR_LSRSLEW(config->xcvr_lsrslew); + + if (phy->soc_config->requires_extra_tuning_parameters) { + val &= ~(UTMIP_XCVR_HSSLEW(~0) | UTMIP_XCVR_HSSLEW_MSB(~0)); + val |= UTMIP_XCVR_HSSLEW(config->xcvr_hsslew); + val |= UTMIP_XCVR_HSSLEW_MSB(config->xcvr_hsslew); + } + writel_relaxed(val, base + UTMIP_XCVR_CFG0); + + val = readl_relaxed(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_relaxed(val, base + UTMIP_XCVR_CFG1); + + val = readl_relaxed(base + UTMIP_BIAS_CFG1); + val &= ~UTMIP_BIAS_PDTRK_COUNT(~0); + val |= UTMIP_BIAS_PDTRK_COUNT(0x5); + writel_relaxed(val, base + UTMIP_BIAS_CFG1); + + val = readl_relaxed(base + UTMIP_SPARE_CFG0); + if (config->xcvr_setup_use_fuses) + val |= FUSE_SETUP_SEL; + else + val &= ~FUSE_SETUP_SEL; + writel_relaxed(val, base + UTMIP_SPARE_CFG0); + + if (!phy->is_legacy_phy) { + val = readl_relaxed(base + USB_SUSP_CTRL); + val |= UTMIP_PHY_ENABLE; + writel_relaxed(val, base + USB_SUSP_CTRL); + } + + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~UTMIP_RESET; + writel_relaxed(val, base + USB_SUSP_CTRL); + + if (phy->is_legacy_phy) { + val = readl_relaxed(base + USB1_LEGACY_CTRL); + val &= ~USB1_VBUS_SENSE_CTL_MASK; + val |= USB1_VBUS_SENSE_CTL_A_SESS_VLD; + writel_relaxed(val, base + USB1_LEGACY_CTRL); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~USB_SUSP_SET; + writel_relaxed(val, base + USB_SUSP_CTRL); + } + + utmi_phy_clk_enable(phy); + + if (phy->soc_config->requires_usbmode_setup) { + val = readl_relaxed(base + USB_USBMODE); + val &= ~USB_USBMODE_MASK; + if (phy->mode == USB_DR_MODE_HOST) + val |= USB_USBMODE_HOST; + else + val |= USB_USBMODE_DEVICE; + writel_relaxed(val, base + USB_USBMODE); + } + + if (!phy->is_legacy_phy) + set_pts(phy, 0); + + return 0; +} + +static int utmi_phy_power_off(struct tegra_usb_phy *phy) +{ + void __iomem *base = phy->regs; + u32 val; + + /* + * Give hardware time to settle down after VBUS disconnection, + * otherwise PHY will immediately wake up from suspend. + */ + if (phy->wakeup_enabled && phy->mode != USB_DR_MODE_HOST) + readl_relaxed_poll_timeout(base + USB_PHY_VBUS_WAKEUP_ID, + val, !(val & VBUS_WAKEUP_STS), + 5000, 100000); + + utmi_phy_clk_disable(phy); + + /* PHY won't resume if reset is asserted */ + if (!phy->wakeup_enabled) { + val = readl_relaxed(base + USB_SUSP_CTRL); + val |= UTMIP_RESET; + writel_relaxed(val, base + USB_SUSP_CTRL); + } + + val = readl_relaxed(base + UTMIP_BAT_CHRG_CFG0); + val |= UTMIP_PD_CHRG; + writel_relaxed(val, base + UTMIP_BAT_CHRG_CFG0); + + if (!phy->wakeup_enabled) { + val = readl_relaxed(base + UTMIP_XCVR_CFG0); + val |= UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN | + UTMIP_FORCE_PDZI_POWERDOWN; + writel_relaxed(val, base + UTMIP_XCVR_CFG0); + } + + val = readl_relaxed(base + UTMIP_XCVR_CFG1); + val |= UTMIP_FORCE_PDDISC_POWERDOWN | UTMIP_FORCE_PDCHRP_POWERDOWN | + UTMIP_FORCE_PDDR_POWERDOWN; + writel_relaxed(val, base + UTMIP_XCVR_CFG1); + + if (phy->wakeup_enabled) { + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~USB_WAKEUP_DEBOUNCE_COUNT(~0); + val |= USB_WAKEUP_DEBOUNCE_COUNT(5); + val |= USB_WAKE_ON_RESUME_EN; + writel_relaxed(val, base + USB_SUSP_CTRL); + + /* + * Ask VBUS sensor to generate wake event once cable is + * connected. + */ + if (phy->mode != USB_DR_MODE_HOST) { + val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID); + val |= VBUS_WAKEUP_WAKEUP_EN; + val &= ~(ID_CHG_DET | VBUS_WAKEUP_CHG_DET); + writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID); + + val = readl_relaxed(base + USB_PHY_VBUS_SENSORS); + val |= A_VBUS_VLD_WAKEUP_EN; + writel_relaxed(val, base + USB_PHY_VBUS_SENSORS); + } + } + + return utmip_pad_power_off(phy); +} + +static int ulpi_phy_power_on(struct tegra_usb_phy *phy) +{ + void __iomem *base = phy->regs; + u32 val; + int err; + + gpiod_set_value_cansleep(phy->reset_gpio, 1); + + err = clk_prepare_enable(phy->clk); + if (err) + return err; + + usleep_range(5000, 6000); + + gpiod_set_value_cansleep(phy->reset_gpio, 0); + + usleep_range(1000, 2000); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val |= UHSIC_RESET; + writel_relaxed(val, base + USB_SUSP_CTRL); + + val = readl_relaxed(base + ULPI_TIMING_CTRL_0); + val |= ULPI_OUTPUT_PINMUX_BYP | ULPI_CLKOUT_PINMUX_BYP; + writel_relaxed(val, base + ULPI_TIMING_CTRL_0); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val |= ULPI_PHY_ENABLE; + writel_relaxed(val, base + USB_SUSP_CTRL); + + val = 0; + writel_relaxed(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_relaxed(val, base + ULPI_TIMING_CTRL_1); + usleep_range(10, 100); + + val |= ULPI_DATA_TRIMMER_LOAD; + val |= ULPI_STPDIRNXT_TRIMMER_LOAD; + val |= ULPI_DIR_TRIMMER_LOAD; + writel_relaxed(val, base + ULPI_TIMING_CTRL_1); + + /* Fix VbusInvalid due to floating VBUS */ + err = usb_phy_io_write(phy->ulpi, 0x40, 0x08); + if (err) { + dev_err(phy->u_phy.dev, "ULPI write failed: %d\n", err); + goto disable_clk; + } + + err = usb_phy_io_write(phy->ulpi, 0x80, 0x0B); + if (err) { + dev_err(phy->u_phy.dev, "ULPI write failed: %d\n", err); + goto disable_clk; + } + + val = readl_relaxed(base + USB_SUSP_CTRL); + val |= USB_SUSP_CLR; + writel_relaxed(val, base + USB_SUSP_CTRL); + usleep_range(100, 1000); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~USB_SUSP_CLR; + writel_relaxed(val, base + USB_SUSP_CTRL); + + return 0; + +disable_clk: + clk_disable_unprepare(phy->clk); + + return err; +} + +static int ulpi_phy_power_off(struct tegra_usb_phy *phy) +{ + gpiod_set_value_cansleep(phy->reset_gpio, 1); + usleep_range(5000, 6000); + clk_disable_unprepare(phy->clk); + + /* + * Wakeup currently unimplemented for ULPI, thus PHY needs to be + * force-resumed. + */ + if (WARN_ON_ONCE(phy->wakeup_enabled)) { + ulpi_phy_power_on(phy); + return -EOPNOTSUPP; + } + + return 0; +} + +static u32 tegra_hsic_readl(struct tegra_usb_phy *phy, u32 reg) +{ + void __iomem *base = phy->regs; + u32 shift = phy->soc_config->uhsic_registers_offset; + + return readl_relaxed(base + shift + reg); +} + +static void tegra_hsic_writel(struct tegra_usb_phy *phy, u32 reg, u32 value) +{ + void __iomem *base = phy->regs; + u32 shift = phy->soc_config->uhsic_registers_offset; + + writel_relaxed(value, base + shift + reg); +} + +static int uhsic_phy_power_on(struct tegra_usb_phy *phy) +{ + struct tegra_utmip_config *config = phy->config; + void __iomem *base = phy->regs; + u32 val; + int err = 0; + + val = tegra_hsic_readl(phy, UHSIC_PADS_CFG1); + val &= ~(UHSIC_PD_BG | UHSIC_PD_TX | UHSIC_PD_TRK | UHSIC_PD_RX | + UHSIC_PD_ZI | UHSIC_RPD_DATA | UHSIC_RPD_STROBE); + val |= UHSIC_RX_SEL; + tegra_hsic_writel(phy, UHSIC_PADS_CFG1, val); + + udelay(2); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val |= UHSIC_RESET; + writel_relaxed(val, base + USB_SUSP_CTRL); + + udelay(30); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val |= UHSIC_PHY_ENABLE; + writel_relaxed(val, base + USB_SUSP_CTRL); + + val = tegra_hsic_readl(phy, UHSIC_HSRX_CFG0); + val &= ~(UHSIC_IDLE_WAIT(~0) | + UHSIC_ELASTIC_UNDERRUN_LIMIT(~0) | + UHSIC_ELASTIC_OVERRUN_LIMIT(~0)); + val |= UHSIC_IDLE_WAIT(config->idle_wait_delay) | + UHSIC_ELASTIC_UNDERRUN_LIMIT(config->elastic_limit) | + UHSIC_ELASTIC_OVERRUN_LIMIT(config->elastic_limit); + tegra_hsic_writel(phy, UHSIC_HSRX_CFG0, val); + + val = tegra_hsic_readl(phy, UHSIC_HSRX_CFG1); + val &= ~UHSIC_HS_SYNC_START_DLY(~0); + val |= UHSIC_HS_SYNC_START_DLY(config->hssync_start_delay); + tegra_hsic_writel(phy, UHSIC_HSRX_CFG1, val); + + val = tegra_hsic_readl(phy, UHSIC_MISC_CFG0); + val |= UHSIC_SUSPEND_EXIT_ON_EDGE; + tegra_hsic_writel(phy, UHSIC_MISC_CFG0, val); + + val = tegra_hsic_readl(phy, UHSIC_MISC_CFG1); + val &= ~UHSIC_PLLU_STABLE_COUNT(~0); + val |= UHSIC_PLLU_STABLE_COUNT(phy->freq->stable_count); + tegra_hsic_writel(phy, UHSIC_MISC_CFG1, val); + + val = tegra_hsic_readl(phy, UHSIC_PLL_CFG1); + val &= ~(UHSIC_XTAL_FREQ_COUNT(~0) | + UHSIC_PLLU_ENABLE_DLY_COUNT(~0)); + val |= UHSIC_XTAL_FREQ_COUNT(phy->freq->hsic_xtal_freq_count) | + UHSIC_PLLU_ENABLE_DLY_COUNT(phy->freq->enable_delay); + tegra_hsic_writel(phy, UHSIC_PLL_CFG1, val); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~UHSIC_RESET; + writel_relaxed(val, base + USB_SUSP_CTRL); + + udelay(2); + + if (phy->soc_config->requires_usbmode_setup) { + val = readl_relaxed(base + USB_USBMODE); + val &= ~USB_USBMODE_MASK; + if (phy->mode == USB_DR_MODE_HOST) + val |= USB_USBMODE_HOST; + else + val |= USB_USBMODE_DEVICE; + writel_relaxed(val, base + USB_USBMODE); + } + + set_pts(phy, phy->soc_config->uhsic_pts_value); + + val = readl_relaxed(base + USB_TXFILLTUNING); + if ((val & USB_FIFO_TXFILL_MASK) != USB_FIFO_TXFILL_THRES(0x10)) { + val = USB_FIFO_TXFILL_THRES(0x10); + writel_relaxed(val, base + USB_TXFILLTUNING); + } + + val = readl_relaxed(base + phy->soc_config->portsc1_offset); + val &= ~(TEGRA_USB_PORTSC1_WKOC | TEGRA_USB_PORTSC1_WKDS | + TEGRA_USB_PORTSC1_WKCN); + writel_relaxed(val, base + phy->soc_config->portsc1_offset); + + val = tegra_hsic_readl(phy, UHSIC_PADS_CFG0); + val &= ~UHSIC_TX_RTUNEN; + val |= UHSIC_TX_RTUNE(phy->soc_config->uhsic_tx_rtune); + tegra_hsic_writel(phy, UHSIC_PADS_CFG0, val); + + err = utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, + USB_PHY_CLK_VALID); + + if (err) + dev_err(phy->u_phy.dev, + "Timeout waiting for PHY to stabilize on enable (HSIC)\n"); + + return err; +} + +static int uhsic_phy_power_off(struct tegra_usb_phy *phy) +{ + void __iomem *base = phy->regs; + u32 val; + + set_phcd(phy, true); + + val = tegra_hsic_readl(phy, UHSIC_PADS_CFG1); + val |= (UHSIC_PD_BG | UHSIC_PD_TX | UHSIC_PD_TRK | UHSIC_PD_RX | + UHSIC_PD_ZI | UHSIC_RPD_DATA | UHSIC_RPD_STROBE); + tegra_hsic_writel(phy, UHSIC_PADS_CFG1, val); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val |= UHSIC_RESET; + writel_relaxed(val, base + USB_SUSP_CTRL); + + udelay(30); + + val = readl_relaxed(base + USB_SUSP_CTRL); + val &= ~UHSIC_PHY_ENABLE; + writel_relaxed(val, base + USB_SUSP_CTRL); + + return 0; +} + +static int tegra_usb_phy_power_on(struct tegra_usb_phy *phy) +{ + int err = 0; + + if (phy->powered_on) + return 0; + + switch (phy->phy_type) { + case USBPHY_INTERFACE_MODE_UTMI: + err = utmi_phy_power_on(phy); + break; + + case USBPHY_INTERFACE_MODE_ULPI: + err = ulpi_phy_power_on(phy); + break; + + case USBPHY_INTERFACE_MODE_HSIC: + err = uhsic_phy_power_on(phy); + break; + + default: + break; + } + + if (err) + return err; + + phy->powered_on = true; + + /* Let PHY settle down */ + usleep_range(2000, 2500); + + return 0; +} + +static int tegra_usb_phy_power_off(struct tegra_usb_phy *phy) +{ + int err = 0; + + if (!phy->powered_on) + return 0; + + switch (phy->phy_type) { + case USBPHY_INTERFACE_MODE_UTMI: + err = utmi_phy_power_off(phy); + break; + + case USBPHY_INTERFACE_MODE_ULPI: + err = ulpi_phy_power_off(phy); + break; + + case USBPHY_INTERFACE_MODE_HSIC: + err = uhsic_phy_power_off(phy); + break; + + default: + break; + } + + if (err) + return err; + + phy->powered_on = false; + + return 0; +} + +static void tegra_usb_phy_shutdown(struct usb_phy *u_phy) +{ + struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy); + + if (WARN_ON(!phy->freq)) + return; + + usb_phy_set_wakeup(u_phy, false); + tegra_usb_phy_power_off(phy); + + if (phy->phy_type == USBPHY_INTERFACE_MODE_UTMI) + utmip_pad_close(phy); + + regulator_disable(phy->vbus); + clk_disable_unprepare(phy->pll_u); + + phy->freq = NULL; +} + +static irqreturn_t tegra_usb_phy_isr(int irq, void *data) +{ + u32 val, int_mask = ID_CHG_DET | VBUS_WAKEUP_CHG_DET; + struct tegra_usb_phy *phy = data; + void __iomem *base = phy->regs; + + /* + * The PHY interrupt also wakes the USB controller driver since + * interrupt is shared. We don't do anything in the PHY driver, + * so just clear the interrupt. + */ + val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID); + writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID); + + return val & int_mask ? IRQ_HANDLED : IRQ_NONE; +} + +static int tegra_usb_phy_set_wakeup(struct usb_phy *u_phy, bool enable) +{ + struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy); + void __iomem *base = phy->regs; + int ret = 0; + u32 val; + + if (phy->wakeup_enabled && phy->mode != USB_DR_MODE_HOST && + phy->irq > 0) { + disable_irq(phy->irq); + + val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID); + val &= ~(ID_INT_EN | VBUS_WAKEUP_INT_EN); + writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID); + + enable_irq(phy->irq); + + free_irq(phy->irq, phy); + + phy->wakeup_enabled = false; + } + + if (enable && phy->mode != USB_DR_MODE_HOST && phy->irq > 0) { + ret = request_irq(phy->irq, tegra_usb_phy_isr, IRQF_SHARED, + dev_name(phy->u_phy.dev), phy); + if (!ret) { + disable_irq(phy->irq); + + /* + * USB clock will be resumed once wake event will be + * generated. The ID-change event requires to have + * interrupts enabled, otherwise it won't be generated. + */ + val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID); + val |= ID_INT_EN | VBUS_WAKEUP_INT_EN; + writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID); + + enable_irq(phy->irq); + } else { + dev_err(phy->u_phy.dev, + "Failed to request interrupt: %d", ret); + enable = false; + } + } + + phy->wakeup_enabled = enable; + + return ret; +} + +static int tegra_usb_phy_set_suspend(struct usb_phy *u_phy, int suspend) +{ + struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy); + int ret; + + if (WARN_ON(!phy->freq)) + return -EINVAL; + + /* + * PHY is sharing IRQ with the CI driver, hence here we either + * disable interrupt for both PHY and CI or for CI only. The + * interrupt needs to be disabled while hardware is reprogrammed + * because interrupt touches the programmed registers, and thus, + * there could be a race condition. + */ + if (phy->irq > 0) + disable_irq(phy->irq); + + if (suspend) + ret = tegra_usb_phy_power_off(phy); + else + ret = tegra_usb_phy_power_on(phy); + + if (phy->irq > 0) + enable_irq(phy->irq); + + return ret; +} + +static int tegra_usb_phy_configure_pmc(struct tegra_usb_phy *phy) +{ + int err, val = 0; + + /* older device-trees don't have PMC regmap */ + if (!phy->pmc_regmap) + return 0; + + /* + * Tegra20 has a different layout of PMC USB register bits and AO is + * enabled by default after system reset on Tegra20, so assume nothing + * to do on Tegra20. + */ + if (!phy->soc_config->requires_pmc_ao_power_up) + return 0; + + /* enable VBUS wake-up detector */ + if (phy->mode != USB_DR_MODE_HOST) + val |= VBUS_WAKEUP_PD_P0 << phy->instance * 4; + + /* enable ID-pin ACC detector for OTG mode switching */ + if (phy->mode == USB_DR_MODE_OTG) + val |= ID_PD_P0 << phy->instance * 4; + + /* disable detectors to reset them */ + err = regmap_set_bits(phy->pmc_regmap, PMC_USB_AO, val); + if (err) { + dev_err(phy->u_phy.dev, "Failed to disable PMC AO: %d\n", err); + return err; + } + + usleep_range(10, 100); + + /* enable detectors */ + err = regmap_clear_bits(phy->pmc_regmap, PMC_USB_AO, val); + if (err) { + dev_err(phy->u_phy.dev, "Failed to enable PMC AO: %d\n", err); + return err; + } + + /* detectors starts to work after 10ms */ + usleep_range(10000, 15000); + + return 0; +} + +static int tegra_usb_phy_init(struct usb_phy *u_phy) +{ + struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy); + unsigned long parent_rate; + unsigned int i; + int err; + + if (WARN_ON(phy->freq)) + return 0; + + err = clk_prepare_enable(phy->pll_u); + if (err) + return err; + + 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) { + dev_err(phy->u_phy.dev, "Invalid pll_u parent rate %ld\n", + parent_rate); + err = -EINVAL; + goto disable_clk; + } + + err = regulator_enable(phy->vbus); + if (err) { + dev_err(phy->u_phy.dev, + "Failed to enable USB VBUS regulator: %d\n", err); + goto disable_clk; + } + + if (phy->phy_type == USBPHY_INTERFACE_MODE_UTMI) { + err = utmip_pad_open(phy); + if (err) + goto disable_vbus; + } + + err = tegra_usb_phy_configure_pmc(phy); + if (err) + goto close_phy; + + err = tegra_usb_phy_power_on(phy); + if (err) + goto close_phy; + + return 0; + +close_phy: + if (phy->phy_type == USBPHY_INTERFACE_MODE_UTMI) + utmip_pad_close(phy); + +disable_vbus: + regulator_disable(phy->vbus); + +disable_clk: + clk_disable_unprepare(phy->pll_u); + + phy->freq = NULL; + + return err; +} + +static int read_utmi_param(struct platform_device *pdev, const char *param, + u8 *dest) +{ + u32 value; + int err; + + err = of_property_read_u32(pdev->dev.of_node, param, &value); + if (err) + dev_err(&pdev->dev, + "Failed to read USB UTMI parameter %s: %d\n", + param, err); + else + *dest = value; + + return err; +} + +static int utmi_phy_probe(struct tegra_usb_phy *tegra_phy, + struct platform_device *pdev) +{ + struct tegra_utmip_config *config; + struct resource *res; + int err; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_err(&pdev->dev, "Failed to get UTMI pad regs\n"); + return -ENXIO; + } + + /* + * Note that UTMI pad registers are shared by all PHYs, therefore + * devm_platform_ioremap_resource() can't be used here. + */ + tegra_phy->pad_regs = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!tegra_phy->pad_regs) { + dev_err(&pdev->dev, "Failed to remap UTMI pad regs\n"); + return -ENOMEM; + } + + tegra_phy->config = devm_kzalloc(&pdev->dev, sizeof(*config), + GFP_KERNEL); + if (!tegra_phy->config) + return -ENOMEM; + + config = tegra_phy->config; + + err = read_utmi_param(pdev, "nvidia,hssync-start-delay", + &config->hssync_start_delay); + if (err) + return err; + + err = read_utmi_param(pdev, "nvidia,elastic-limit", + &config->elastic_limit); + if (err) + return err; + + err = read_utmi_param(pdev, "nvidia,idle-wait-delay", + &config->idle_wait_delay); + if (err) + return err; + + err = read_utmi_param(pdev, "nvidia,term-range-adj", + &config->term_range_adj); + if (err) + return err; + + err = read_utmi_param(pdev, "nvidia,xcvr-lsfslew", + &config->xcvr_lsfslew); + if (err) + return err; + + err = read_utmi_param(pdev, "nvidia,xcvr-lsrslew", + &config->xcvr_lsrslew); + if (err) + return err; + + if (tegra_phy->soc_config->requires_extra_tuning_parameters) { + err = read_utmi_param(pdev, "nvidia,xcvr-hsslew", + &config->xcvr_hsslew); + if (err) + return err; + + err = read_utmi_param(pdev, "nvidia,hssquelch-level", + &config->hssquelch_level); + if (err) + return err; + + err = read_utmi_param(pdev, "nvidia,hsdiscon-level", + &config->hsdiscon_level); + if (err) + return err; + } + + config->xcvr_setup_use_fuses = of_property_read_bool( + pdev->dev.of_node, "nvidia,xcvr-setup-use-fuses"); + + if (!config->xcvr_setup_use_fuses) { + err = read_utmi_param(pdev, "nvidia,xcvr-setup", + &config->xcvr_setup); + if (err) + return err; + } + + return 0; +} + +static void tegra_usb_phy_put_pmc_device(void *dev) +{ + put_device(dev); +} + +static int tegra_usb_phy_parse_pmc(struct device *dev, + struct tegra_usb_phy *phy) +{ + struct platform_device *pmc_pdev; + struct of_phandle_args args; + int err; + + err = of_parse_phandle_with_fixed_args(dev->of_node, "nvidia,pmc", + 1, 0, &args); + if (err) { + if (err != -ENOENT) + return err; + + dev_warn_once(dev, "nvidia,pmc is missing, please update your device-tree\n"); + return 0; + } + + pmc_pdev = of_find_device_by_node(args.np); + of_node_put(args.np); + if (!pmc_pdev) + return -ENODEV; + + err = devm_add_action_or_reset(dev, tegra_usb_phy_put_pmc_device, + &pmc_pdev->dev); + if (err) + return err; + + if (!platform_get_drvdata(pmc_pdev)) + return -EPROBE_DEFER; + + phy->pmc_regmap = dev_get_regmap(&pmc_pdev->dev, "usb_sleepwalk"); + if (!phy->pmc_regmap) + return -EINVAL; + + phy->instance = args.args[0]; + + return 0; +} + +static const struct tegra_phy_soc_config tegra20_soc_config = { + .utmi_pll_config_in_car_module = false, + .has_hostpc = false, + .requires_usbmode_setup = false, + .requires_extra_tuning_parameters = false, + .requires_pmc_ao_power_up = false, + .uhsic_registers_offset = 0, + .uhsic_tx_rtune = 0, /* 40 ohm */ + .uhsic_pts_value = 0, /* UTMI */ + .portsc1_offset = TEGRA_USB_PORTSC1, +}; + +static const struct tegra_phy_soc_config tegra30_soc_config = { + .utmi_pll_config_in_car_module = true, + .has_hostpc = true, + .requires_usbmode_setup = true, + .requires_extra_tuning_parameters = true, + .requires_pmc_ao_power_up = true, + .uhsic_registers_offset = 0x400, + .uhsic_tx_rtune = 8, /* 50 ohm */ + .uhsic_pts_value = TEGRA_USB_HOSTPC1_DEVLC_PTS_HSIC, + .portsc1_offset = TEGRA30_USB_PORTSC1, +}; + +static const struct of_device_id tegra_usb_phy_id_table[] = { + { .compatible = "nvidia,tegra30-usb-phy", .data = &tegra30_soc_config }, + { .compatible = "nvidia,tegra20-usb-phy", .data = &tegra20_soc_config }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_usb_phy_id_table); + +static int tegra_usb_phy_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct tegra_usb_phy *tegra_phy; + struct reset_control *reset; + struct gpio_desc *gpiod; + struct resource *res; + struct usb_phy *phy; + int err; + + tegra_phy = devm_kzalloc(&pdev->dev, sizeof(*tegra_phy), GFP_KERNEL); + if (!tegra_phy) + return -ENOMEM; + + tegra_phy->soc_config = of_device_get_match_data(&pdev->dev); + tegra_phy->irq = platform_get_irq_optional(pdev, 0); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get I/O memory\n"); + return -ENXIO; + } + + /* + * Note that PHY and USB controller are using shared registers, + * therefore devm_platform_ioremap_resource() can't be used here. + */ + tegra_phy->regs = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!tegra_phy->regs) { + dev_err(&pdev->dev, "Failed to remap I/O memory\n"); + return -ENOMEM; + } + + tegra_phy->is_legacy_phy = + of_property_read_bool(np, "nvidia,has-legacy-mode"); + + if (of_property_present(np, "dr_mode")) + tegra_phy->mode = usb_get_dr_mode(&pdev->dev); + else + tegra_phy->mode = USB_DR_MODE_HOST; + + if (tegra_phy->mode == USB_DR_MODE_UNKNOWN) { + dev_err(&pdev->dev, "dr_mode is invalid\n"); + return -EINVAL; + } + + /* On some boards, the VBUS regulator doesn't need to be controlled */ + tegra_phy->vbus = devm_regulator_get(&pdev->dev, "vbus"); + if (IS_ERR(tegra_phy->vbus)) + return PTR_ERR(tegra_phy->vbus); + + tegra_phy->pll_u = devm_clk_get(&pdev->dev, "pll_u"); + err = PTR_ERR_OR_ZERO(tegra_phy->pll_u); + if (err) { + dev_err(&pdev->dev, "Failed to get pll_u clock: %d\n", err); + return err; + } + + err = tegra_usb_phy_parse_pmc(&pdev->dev, tegra_phy); + if (err) { + dev_err_probe(&pdev->dev, err, "Failed to get PMC regmap\n"); + return err; + } + + tegra_phy->phy_type = of_usb_get_phy_mode(np); + switch (tegra_phy->phy_type) { + case USBPHY_INTERFACE_MODE_UTMI: + case USBPHY_INTERFACE_MODE_HSIC: + err = utmi_phy_probe(tegra_phy, pdev); + if (err) + return err; + + tegra_phy->pad_clk = devm_clk_get(&pdev->dev, "utmi-pads"); + err = PTR_ERR_OR_ZERO(tegra_phy->pad_clk); + if (err) { + dev_err(&pdev->dev, + "Failed to get UTMIP pad clock: %d\n", err); + return err; + } + + reset = devm_reset_control_get_optional_shared(&pdev->dev, + "utmi-pads"); + err = PTR_ERR_OR_ZERO(reset); + if (err) { + dev_err(&pdev->dev, + "Failed to get UTMI-pads reset: %d\n", err); + return err; + } + tegra_phy->pad_rst = reset; + break; + + case USBPHY_INTERFACE_MODE_ULPI: + tegra_phy->clk = devm_clk_get(&pdev->dev, "ulpi-link"); + err = PTR_ERR_OR_ZERO(tegra_phy->clk); + if (err) { + dev_err(&pdev->dev, + "Failed to get ULPI clock: %d\n", err); + return err; + } + + gpiod = devm_gpiod_get(&pdev->dev, "nvidia,phy-reset", + GPIOD_OUT_HIGH); + err = PTR_ERR_OR_ZERO(gpiod); + if (err) { + dev_err(&pdev->dev, + "Request failed for reset GPIO: %d\n", err); + return err; + } + + err = gpiod_set_consumer_name(gpiod, "ulpi_phy_reset_b"); + if (err) { + dev_err(&pdev->dev, + "Failed to set up reset GPIO name: %d\n", err); + return err; + } + + tegra_phy->reset_gpio = gpiod; + + phy = devm_otg_ulpi_create(&pdev->dev, + &ulpi_viewport_access_ops, 0); + if (!phy) { + dev_err(&pdev->dev, "Failed to create ULPI OTG\n"); + return -ENOMEM; + } + + tegra_phy->ulpi = phy; + tegra_phy->ulpi->io_priv = tegra_phy->regs + ULPI_VIEWPORT; + break; + + default: + dev_err(&pdev->dev, "phy_type %u is invalid or unsupported\n", + tegra_phy->phy_type); + return -EINVAL; + } + + tegra_phy->u_phy.dev = &pdev->dev; + tegra_phy->u_phy.init = tegra_usb_phy_init; + tegra_phy->u_phy.shutdown = tegra_usb_phy_shutdown; + tegra_phy->u_phy.set_wakeup = tegra_usb_phy_set_wakeup; + tegra_phy->u_phy.set_suspend = tegra_usb_phy_set_suspend; + + platform_set_drvdata(pdev, tegra_phy); + + return usb_add_phy_dev(&tegra_phy->u_phy); +} + +static void tegra_usb_phy_remove(struct platform_device *pdev) +{ + struct tegra_usb_phy *tegra_phy = platform_get_drvdata(pdev); + + usb_remove_phy(&tegra_phy->u_phy); +} + +static struct platform_driver tegra_usb_phy_driver = { + .probe = tegra_usb_phy_probe, + .remove = tegra_usb_phy_remove, + .driver = { + .name = "tegra-phy", + .of_match_table = tegra_usb_phy_id_table, + }, +}; +module_platform_driver(tegra_usb_phy_driver); + +MODULE_DESCRIPTION("Tegra USB PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-twl6030-usb.c b/drivers/usb/phy/phy-twl6030-usb.c new file mode 100644 index 000000000..8c09db750 --- /dev/null +++ b/drivers/usb/phy/phy-twl6030-usb.c @@ -0,0 +1,457 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * twl6030_usb - TWL6030 USB transceiver, talking to OMAP OTG driver. + * + * Copyright (C) 2010 Texas Instruments Incorporated - https://www.ti.com + * + * Author: Hema HK + */ + +#include +#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 check initial cable status after probe */ + struct delayed_work get_status_work; + + /* used to set vbus, in atomic path */ + struct work_struct set_vbus_work; + + int irq1; + int irq2; + enum musb_vbus_id_status linkstat; + u8 asleep; + bool vbus_enable; +}; + +#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; + int ret; + + 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, "usb"); + 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 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 MUSB_VBUS_VALID: + ret = sysfs_emit(buf, "vbus\n"); + break; + case MUSB_ID_GROUND: + ret = sysfs_emit(buf, "id\n"); + break; + case MUSB_VBUS_OFF: + ret = sysfs_emit(buf, "none\n"); + break; + default: + ret = sysfs_emit(buf, "UNKNOWN\n"); + } + spin_unlock_irqrestore(&twl->lock, flags); + + return ret; +} +static DEVICE_ATTR_RO(vbus); + +static struct attribute *twl6030_attrs[] = { + &dev_attr_vbus.attr, + NULL, +}; +ATTRIBUTE_GROUPS(twl6030); + +static irqreturn_t twl6030_usb_irq(int irq, void *_twl) +{ + struct twl6030_usb *twl = _twl; + enum musb_vbus_id_status status = 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 = MUSB_VBUS_VALID; + twl->linkstat = status; + ret = musb_mailbox(status); + if (ret) + twl->linkstat = MUSB_UNKNOWN; + } else { + if (twl->linkstat != MUSB_UNKNOWN) { + status = MUSB_VBUS_OFF; + twl->linkstat = status; + ret = musb_mailbox(status); + if (ret) + twl->linkstat = MUSB_UNKNOWN; + 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 musb_vbus_id_status status = 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 = MUSB_ID_GROUND; + twl->linkstat = status; + ret = musb_mailbox(status); + if (ret) + twl->linkstat = MUSB_UNKNOWN; + } 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 void twl6030_status_work(struct work_struct *work) +{ + struct twl6030_usb *twl = container_of(work, struct twl6030_usb, + get_status_work.work); + + twl6030_usb_irq(twl->irq2, twl); + twl6030_usbotg_irq(twl->irq1, twl); +} + +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); + + 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) +{ + struct twl6030_usb *twl; + int status, err, ret; + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + + if (!np) { + dev_err(dev, "no DT info\n"); + return -EINVAL; + } + + 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 = MUSB_UNKNOWN; + + if (twl->irq1 < 0) + return twl->irq1; + if (twl->irq2 < 0) + return twl->irq2; + + 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; + } + + /* 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); + + INIT_WORK(&twl->set_vbus_work, otg_set_vbus_work); + INIT_DELAYED_WORK(&twl->get_status_work, twl6030_status_work); + + 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); + goto err_put_regulator; + } + + 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); + goto err_free_irq1; + } + + twl->asleep = 0; + twl6030_enable_irq(twl); + schedule_delayed_work(&twl->get_status_work, HZ); + dev_info(&pdev->dev, "Initialized TWL6030 USB module\n"); + + return 0; + +err_free_irq1: + free_irq(twl->irq1, twl); +err_put_regulator: + regulator_put(twl->usb3v3); + + return status; +} + +static void twl6030_usb_remove(struct platform_device *pdev) +{ + struct twl6030_usb *twl = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&twl->get_status_work); + 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); + cancel_work_sync(&twl->set_vbus_work); +} + +static const struct of_device_id twl6030_usb_id_table[] = { + { .compatible = "ti,twl6030-usb" }, + {} +}; +MODULE_DEVICE_TABLE(of, twl6030_usb_id_table); + +static struct platform_driver twl6030_usb_driver = { + .probe = twl6030_usb_probe, + .remove = twl6030_usb_remove, + .driver = { + .name = "twl6030_usb", + .of_match_table = of_match_ptr(twl6030_usb_id_table), + .dev_groups = twl6030_groups, + }, +}; + +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 000000000..0f61e328e --- /dev/null +++ b/drivers/usb/phy/phy-ulpi-viewport.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2011 Google, Inc. + */ + +#include +#include +#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) +{ + u32 val; + + return readl_poll_timeout_atomic(view, val, !(val & mask), 1, 2000); +} + +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, +}; +EXPORT_SYMBOL_GPL(ulpi_viewport_access_ops); diff --git a/drivers/usb/phy/phy-ulpi.c b/drivers/usb/phy/phy-ulpi.c new file mode 100644 index 000000000..4df63e67b --- /dev/null +++ b/drivers/usb/phy/phy-ulpi.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Generic ULPI USB transceiver support + * + * Copyright (C) 2009 Daniel Mack + * + * Based on sources from + * + * Sascha Hauer + * Freescale Semiconductors + */ + +#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->usb_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->usb_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); +} + +static void otg_ulpi_init(struct usb_phy *phy, struct usb_otg *otg, + struct usb_phy_io_ops *ops, + unsigned int flags) +{ + phy->label = "ULPI"; + phy->flags = flags; + phy->io_ops = ops; + phy->otg = otg; + phy->init = ulpi_init; + + otg->usb_phy = phy; + otg->set_host = ulpi_set_host; + otg->set_vbus = ulpi_set_vbus; +} + +struct usb_phy * +devm_otg_ulpi_create(struct device *dev, + struct usb_phy_io_ops *ops, + unsigned int flags) +{ + struct usb_phy *phy; + struct usb_otg *otg; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return NULL; + + otg = devm_kzalloc(dev, sizeof(*otg), GFP_KERNEL); + if (!otg) { + devm_kfree(dev, phy); + return NULL; + } + + otg_ulpi_init(phy, otg, ops, flags); + + return phy; +} +EXPORT_SYMBOL_GPL(devm_otg_ulpi_create); diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c new file mode 100644 index 000000000..5a9b9353f --- /dev/null +++ b/drivers/usb/phy/phy.c @@ -0,0 +1,748 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * phy.c -- USB phy handling + * + * Copyright (C) 2004-2013 Texas Instruments + */ +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Default current range by charger type. */ +#define DEFAULT_SDP_CUR_MIN 2 +#define DEFAULT_SDP_CUR_MAX 500 +#define DEFAULT_SDP_CUR_MIN_SS 150 +#define DEFAULT_SDP_CUR_MAX_SS 900 +#define DEFAULT_DCP_CUR_MIN 500 +#define DEFAULT_DCP_CUR_MAX 5000 +#define DEFAULT_CDP_CUR_MIN 1500 +#define DEFAULT_CDP_CUR_MAX 5000 +#define DEFAULT_ACA_CUR_MIN 1500 +#define DEFAULT_ACA_CUR_MAX 5000 + +static LIST_HEAD(phy_list); +static DEFINE_SPINLOCK(phy_lock); + +struct phy_devm { + struct usb_phy *phy; + struct notifier_block *nb; +}; + +static const char *const usb_chger_type[] = { + [UNKNOWN_TYPE] = "USB_CHARGER_UNKNOWN_TYPE", + [SDP_TYPE] = "USB_CHARGER_SDP_TYPE", + [CDP_TYPE] = "USB_CHARGER_CDP_TYPE", + [DCP_TYPE] = "USB_CHARGER_DCP_TYPE", + [ACA_TYPE] = "USB_CHARGER_ACA_TYPE", +}; + +static const char *const usb_chger_state[] = { + [USB_CHARGER_DEFAULT] = "USB_CHARGER_DEFAULT", + [USB_CHARGER_PRESENT] = "USB_CHARGER_PRESENT", + [USB_CHARGER_ABSENT] = "USB_CHARGER_ABSENT", +}; + +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 *__of_usb_find_phy(struct device_node *node) +{ + struct usb_phy *phy; + + if (!of_device_is_available(node)) + return ERR_PTR(-ENODEV); + + list_for_each_entry(phy, &phy_list, head) { + if (node != phy->dev->of_node) + continue; + + return phy; + } + + return ERR_PTR(-EPROBE_DEFER); +} + +static struct usb_phy *__device_to_usb_phy(const struct device *dev) +{ + struct usb_phy *usb_phy; + + list_for_each_entry(usb_phy, &phy_list, head) { + if (usb_phy->dev == dev) + return usb_phy; + } + + return NULL; +} + +static void usb_phy_set_default_current(struct usb_phy *usb_phy) +{ + usb_phy->chg_cur.sdp_min = DEFAULT_SDP_CUR_MIN; + usb_phy->chg_cur.sdp_max = DEFAULT_SDP_CUR_MAX; + usb_phy->chg_cur.dcp_min = DEFAULT_DCP_CUR_MIN; + usb_phy->chg_cur.dcp_max = DEFAULT_DCP_CUR_MAX; + usb_phy->chg_cur.cdp_min = DEFAULT_CDP_CUR_MIN; + usb_phy->chg_cur.cdp_max = DEFAULT_CDP_CUR_MAX; + usb_phy->chg_cur.aca_min = DEFAULT_ACA_CUR_MIN; + usb_phy->chg_cur.aca_max = DEFAULT_ACA_CUR_MAX; +} + +/** + * usb_phy_notify_charger_work - notify the USB charger state + * @work: the charger work to notify the USB charger state + * + * This work can be issued when USB charger state has been changed or + * USB charger current has been changed, then we can notify the current + * what can be drawn to power user and the charger state to userspace. + * + * If we get the charger type from extcon subsystem, we can notify the + * charger state to power user automatically by usb_phy_get_charger_type() + * issuing from extcon subsystem. + * + * If we get the charger type from ->charger_detect() instead of extcon + * subsystem, the usb phy driver should issue usb_phy_set_charger_state() + * to set charger state when the charger state has been changed. + */ +static void usb_phy_notify_charger_work(struct work_struct *work) +{ + struct usb_phy *usb_phy = container_of(work, struct usb_phy, chg_work); + unsigned int min, max; + + switch (usb_phy->chg_state) { + case USB_CHARGER_PRESENT: + usb_phy_get_charger_current(usb_phy, &min, &max); + + atomic_notifier_call_chain(&usb_phy->notifier, max, usb_phy); + break; + case USB_CHARGER_ABSENT: + usb_phy_set_default_current(usb_phy); + + atomic_notifier_call_chain(&usb_phy->notifier, 0, usb_phy); + break; + default: + dev_warn(usb_phy->dev, "Unknown USB charger state: %d\n", + usb_phy->chg_state); + return; + } + + kobject_uevent(&usb_phy->dev->kobj, KOBJ_CHANGE); +} + +static int usb_phy_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + const struct usb_phy *usb_phy; + char uchger_state[50] = { 0 }; + char uchger_type[50] = { 0 }; + unsigned long flags; + + spin_lock_irqsave(&phy_lock, flags); + usb_phy = __device_to_usb_phy(dev); + spin_unlock_irqrestore(&phy_lock, flags); + + if (!usb_phy) + return -ENODEV; + + snprintf(uchger_state, ARRAY_SIZE(uchger_state), + "USB_CHARGER_STATE=%s", usb_chger_state[usb_phy->chg_state]); + + snprintf(uchger_type, ARRAY_SIZE(uchger_type), + "USB_CHARGER_TYPE=%s", usb_chger_type[usb_phy->chg_type]); + + if (add_uevent_var(env, uchger_state)) + return -ENOMEM; + + if (add_uevent_var(env, uchger_type)) + return -ENOMEM; + + return 0; +} + +static void __usb_phy_get_charger_type(struct usb_phy *usb_phy) +{ + if (extcon_get_state(usb_phy->edev, EXTCON_CHG_USB_SDP) > 0) { + usb_phy->chg_type = SDP_TYPE; + usb_phy->chg_state = USB_CHARGER_PRESENT; + } else if (extcon_get_state(usb_phy->edev, EXTCON_CHG_USB_CDP) > 0) { + usb_phy->chg_type = CDP_TYPE; + usb_phy->chg_state = USB_CHARGER_PRESENT; + } else if (extcon_get_state(usb_phy->edev, EXTCON_CHG_USB_DCP) > 0) { + usb_phy->chg_type = DCP_TYPE; + usb_phy->chg_state = USB_CHARGER_PRESENT; + } else if (extcon_get_state(usb_phy->edev, EXTCON_CHG_USB_ACA) > 0) { + usb_phy->chg_type = ACA_TYPE; + usb_phy->chg_state = USB_CHARGER_PRESENT; + } else { + usb_phy->chg_type = UNKNOWN_TYPE; + usb_phy->chg_state = USB_CHARGER_ABSENT; + } + + schedule_work(&usb_phy->chg_work); +} + +/** + * usb_phy_get_charger_type - get charger type from extcon subsystem + * @nb: the notifier block to determine charger type + * @state: the cable state + * @data: private data + * + * Determin the charger type from extcon subsystem which also means the + * charger state has been chaned, then we should notify this event. + */ +static int usb_phy_get_charger_type(struct notifier_block *nb, + unsigned long state, void *data) +{ + struct usb_phy *usb_phy = container_of(nb, struct usb_phy, type_nb); + + __usb_phy_get_charger_type(usb_phy); + return NOTIFY_OK; +} + +/** + * usb_phy_set_charger_current - set the USB charger current + * @usb_phy: the USB phy to be used + * @mA: the current need to be set + * + * Usually we only change the charger default current when USB finished the + * enumeration as one SDP charger. As one SDP charger, usb_phy_set_power() + * will issue this function to change charger current when after setting USB + * configuration, or suspend/resume USB. For other type charger, we should + * use the default charger current and we do not suggest to issue this function + * to change the charger current. + * + * When USB charger current has been changed, we need to notify the power users. + */ +void usb_phy_set_charger_current(struct usb_phy *usb_phy, unsigned int mA) +{ + switch (usb_phy->chg_type) { + case SDP_TYPE: + if (usb_phy->chg_cur.sdp_max == mA) + return; + + usb_phy->chg_cur.sdp_max = (mA > DEFAULT_SDP_CUR_MAX_SS) ? + DEFAULT_SDP_CUR_MAX_SS : mA; + break; + case DCP_TYPE: + if (usb_phy->chg_cur.dcp_max == mA) + return; + + usb_phy->chg_cur.dcp_max = (mA > DEFAULT_DCP_CUR_MAX) ? + DEFAULT_DCP_CUR_MAX : mA; + break; + case CDP_TYPE: + if (usb_phy->chg_cur.cdp_max == mA) + return; + + usb_phy->chg_cur.cdp_max = (mA > DEFAULT_CDP_CUR_MAX) ? + DEFAULT_CDP_CUR_MAX : mA; + break; + case ACA_TYPE: + if (usb_phy->chg_cur.aca_max == mA) + return; + + usb_phy->chg_cur.aca_max = (mA > DEFAULT_ACA_CUR_MAX) ? + DEFAULT_ACA_CUR_MAX : mA; + break; + default: + return; + } + + schedule_work(&usb_phy->chg_work); +} +EXPORT_SYMBOL_GPL(usb_phy_set_charger_current); + +/** + * usb_phy_get_charger_current - get the USB charger current + * @usb_phy: the USB phy to be used + * @min: the minimum current + * @max: the maximum current + * + * Usually we will notify the maximum current to power user, but for some + * special case, power user also need the minimum current value. Then the + * power user can issue this function to get the suitable current. + */ +void usb_phy_get_charger_current(struct usb_phy *usb_phy, + unsigned int *min, unsigned int *max) +{ + switch (usb_phy->chg_type) { + case SDP_TYPE: + *min = usb_phy->chg_cur.sdp_min; + *max = usb_phy->chg_cur.sdp_max; + break; + case DCP_TYPE: + *min = usb_phy->chg_cur.dcp_min; + *max = usb_phy->chg_cur.dcp_max; + break; + case CDP_TYPE: + *min = usb_phy->chg_cur.cdp_min; + *max = usb_phy->chg_cur.cdp_max; + break; + case ACA_TYPE: + *min = usb_phy->chg_cur.aca_min; + *max = usb_phy->chg_cur.aca_max; + break; + default: + *min = 0; + *max = 0; + break; + } +} +EXPORT_SYMBOL_GPL(usb_phy_get_charger_current); + +/** + * usb_phy_set_charger_state - set the USB charger state + * @usb_phy: the USB phy to be used + * @state: the new state need to be set for charger + * + * The usb phy driver can issue this function when the usb phy driver + * detected the charger state has been changed, in this case the charger + * type should be get from ->charger_detect(). + */ +void usb_phy_set_charger_state(struct usb_phy *usb_phy, + enum usb_charger_state state) +{ + if (usb_phy->chg_state == state || !usb_phy->charger_detect) + return; + + usb_phy->chg_state = state; + if (usb_phy->chg_state == USB_CHARGER_PRESENT) + usb_phy->chg_type = usb_phy->charger_detect(usb_phy); + else + usb_phy->chg_type = UNKNOWN_TYPE; + + schedule_work(&usb_phy->chg_work); +} +EXPORT_SYMBOL_GPL(usb_phy_set_charger_state); + +static void devm_usb_phy_release(struct device *dev, void *res) +{ + struct usb_phy *phy = *(struct usb_phy **)res; + + usb_put_phy(phy); +} + +static void devm_usb_phy_release2(struct device *dev, void *_res) +{ + struct phy_devm *res = _res; + + if (res->nb) + usb_unregister_notifier(res->phy, res->nb); + usb_put_phy(res->phy); +} + +static void usb_charger_init(struct usb_phy *usb_phy) +{ + usb_phy->chg_type = UNKNOWN_TYPE; + usb_phy->chg_state = USB_CHARGER_DEFAULT; + usb_phy_set_default_current(usb_phy); + INIT_WORK(&usb_phy->chg_work, usb_phy_notify_charger_work); +} + +static int usb_add_extcon(struct usb_phy *x) +{ + int ret; + + if (of_property_present(x->dev->of_node, "extcon")) { + x->edev = extcon_get_edev_by_phandle(x->dev, 0); + if (IS_ERR(x->edev)) + return PTR_ERR(x->edev); + + x->id_edev = extcon_get_edev_by_phandle(x->dev, 1); + if (IS_ERR(x->id_edev)) { + x->id_edev = NULL; + dev_info(x->dev, "No separate ID extcon device\n"); + } + + if (x->vbus_nb.notifier_call) { + ret = devm_extcon_register_notifier(x->dev, x->edev, + EXTCON_USB, + &x->vbus_nb); + if (ret < 0) { + dev_err(x->dev, + "register VBUS notifier failed\n"); + return ret; + } + } else { + x->type_nb.notifier_call = usb_phy_get_charger_type; + + ret = devm_extcon_register_notifier(x->dev, x->edev, + EXTCON_CHG_USB_SDP, + &x->type_nb); + if (ret) { + dev_err(x->dev, + "register extcon USB SDP failed.\n"); + return ret; + } + + ret = devm_extcon_register_notifier(x->dev, x->edev, + EXTCON_CHG_USB_CDP, + &x->type_nb); + if (ret) { + dev_err(x->dev, + "register extcon USB CDP failed.\n"); + return ret; + } + + ret = devm_extcon_register_notifier(x->dev, x->edev, + EXTCON_CHG_USB_DCP, + &x->type_nb); + if (ret) { + dev_err(x->dev, + "register extcon USB DCP failed.\n"); + return ret; + } + + ret = devm_extcon_register_notifier(x->dev, x->edev, + EXTCON_CHG_USB_ACA, + &x->type_nb); + if (ret) { + dev_err(x->dev, + "register extcon USB ACA failed.\n"); + return ret; + } + } + + if (x->id_nb.notifier_call) { + struct extcon_dev *id_ext; + + if (x->id_edev) + id_ext = x->id_edev; + else + id_ext = x->edev; + + ret = devm_extcon_register_notifier(x->dev, id_ext, + EXTCON_USB_HOST, + &x->id_nb); + if (ret < 0) { + dev_err(x->dev, + "register ID notifier failed\n"); + return ret; + } + } + } + + if (x->type_nb.notifier_call) + __usb_phy_get_charger_type(x); + + return 0; +} + +/** + * 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 ERR_PTR(-ENOMEM); + + 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_debug("PHY: unable to find transceiver of type %s\n", + usb_phy_type_string(type)); + if (!IS_ERR(phy)) + phy = ERR_PTR(-ENODEV); + + 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_node - find the USB PHY by device_node + * @dev: device that requests this phy + * @node: the device_node for the phy device. + * @nb: a notifier_block to register with the phy. + * + * Returns the phy driver associated with the given device_node, + * after getting a refcount to it, -ENODEV if there is no such phy or + * -EPROBE_DEFER if 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 peripheral drivers for devices related to a phy, + * such as a charger. + */ +struct usb_phy *devm_usb_get_phy_by_node(struct device *dev, + struct device_node *node, + struct notifier_block *nb) +{ + struct usb_phy *phy = ERR_PTR(-ENOMEM); + struct phy_devm *ptr; + unsigned long flags; + + ptr = devres_alloc(devm_usb_phy_release2, 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)) { + devres_free(ptr); + goto err1; + } + + if (!try_module_get(phy->dev->driver->owner)) { + phy = ERR_PTR(-ENODEV); + devres_free(ptr); + goto err1; + } + if (nb) + usb_register_notifier(phy, nb); + ptr->phy = phy; + ptr->nb = nb; + devres_add(dev, ptr); + + get_device(phy->dev); + +err1: + spin_unlock_irqrestore(&phy_lock, flags); + +err0: + + return phy; +} +EXPORT_SYMBOL_GPL(devm_usb_get_phy_by_node); + +/** + * 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 device_node *node; + struct usb_phy *phy; + + 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 %pOF node\n", phandle, + dev->of_node); + return ERR_PTR(-ENODEV); + } + phy = devm_usb_get_phy_by_node(dev, node, NULL); + of_node_put(node); + return phy; +} +EXPORT_SYMBOL_GPL(devm_usb_get_phy_by_phandle); + +/** + * 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; + } + + INIT_LIST_HEAD(&x->head); + + usb_charger_init(x); + ret = usb_add_extcon(x); + if (ret) + return ret; + + ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier); + + 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); + +static const struct device_type usb_phy_dev_type = { + .name = "usb_phy", + .uevent = usb_phy_uevent, +}; + +/** + * 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) +{ + unsigned long flags; + int ret; + + if (!x->dev) { + dev_err(x->dev, "no device provided for PHY\n"); + return -EINVAL; + } + + INIT_LIST_HEAD(&x->head); + + usb_charger_init(x); + ret = usb_add_extcon(x); + if (ret) + return ret; + + x->dev->type = &usb_phy_dev_type; + + ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier); + + spin_lock_irqsave(&phy_lock, flags); + 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; + + spin_lock_irqsave(&phy_lock, flags); + if (x) + list_del(&x->head); + spin_unlock_irqrestore(&phy_lock, flags); +} +EXPORT_SYMBOL_GPL(usb_remove_phy); + +/** + * usb_phy_set_event - set event to phy event + * @x: the phy returned by usb_get_phy(); + * @event: event to set + * + * This sets event to phy event + */ +void usb_phy_set_event(struct usb_phy *x, unsigned long event) +{ + x->last_event = event; +} +EXPORT_SYMBOL_GPL(usb_phy_set_event); diff --git a/drivers/usb/renesas_usbhs/Kconfig b/drivers/usb/renesas_usbhs/Kconfig new file mode 100644 index 000000000..d6b3fef3e --- /dev/null +++ b/drivers/usb/renesas_usbhs/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Renesas USBHS Controller Drivers +# + +config USB_RENESAS_USBHS + tristate 'Renesas USBHS controller' + depends on USB_GADGET + depends on ARCH_RENESAS || SUPERH || COMPILE_TEST + depends on EXTCON || !EXTCON # if EXTCON=m, USBHS cannot be built-in + 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 000000000..a1fed56b0 --- /dev/null +++ b/drivers/usb/renesas_usbhs/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# for Renesas USB +# + +obj-$(CONFIG_USB_RENESAS_USBHS) += renesas_usbhs.o + +renesas_usbhs-y := common.o mod.o pipe.o fifo.o rcar2.o rcar3.o rza.o rza2.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 000000000..51d3035f8 --- /dev/null +++ b/drivers/usb/renesas_usbhs/common.c @@ -0,0 +1,905 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Copyright (C) 2019 Renesas Electronics Corporation + * Kuninori Morimoto + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" +#include "rcar2.h" +#include "rcar3.h" +#include "rza.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 | + * | .... | +-----------+ + */ + +/* + * 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); +} + +int usbhs_get_id_as_gadget(struct platform_device *pdev) +{ + return USBHS_GADGET; +} + +/* + * 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; + + /* + * 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 = HSE | USBE; + + /* CNEN bit is required for function operation */ + if (usbhs_get_dparam(priv, has_cnen)) { + mask |= CNEN; + val |= CNEN; + } + + /* + * if enable + * + * - select Function mode + * - D+ Line Pull-up is disabled + * When D+ Line Pull-up is enabled, + * calling usbhs_sys_function_pullup(,1) + */ + 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 = cpu_to_le16(usbhs_read(priv, USBVAL)); + req->wIndex = cpu_to_le16(usbhs_read(priv, USBINDX)); + req->wLength = cpu_to_le16(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, le16_to_cpu(req->wValue)); + usbhs_write(priv, USBINDX, le16_to_cpu(req->wIndex)); + usbhs_write(priv, USBLENG, le16_to_cpu(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; +} + +/* + * interrupt functions + */ +void usbhs_xxxsts_clear(struct usbhs_priv *priv, u16 sts_reg, u16 bit) +{ + u16 pipe_mask = (u16)GENMASK(usbhs_get_dparam(priv, pipe_size), 0); + + usbhs_write(priv, sts_reg, ~(1 << bit) & pipe_mask); +} + +/* + * 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); +} + +static bool usbhsc_is_multi_clks(struct usbhs_priv *priv) +{ + return priv->dparam.multi_clks; +} + +static int usbhsc_clk_get(struct device *dev, struct usbhs_priv *priv) +{ + if (!usbhsc_is_multi_clks(priv)) + return 0; + + /* The first clock should exist */ + priv->clks[0] = of_clk_get(dev_of_node(dev), 0); + if (IS_ERR(priv->clks[0])) + return PTR_ERR(priv->clks[0]); + + /* + * To backward compatibility with old DT, this driver checks the return + * value if it's -ENOENT or not. + */ + priv->clks[1] = of_clk_get(dev_of_node(dev), 1); + if (PTR_ERR(priv->clks[1]) == -ENOENT) + priv->clks[1] = NULL; + else if (IS_ERR(priv->clks[1])) { + clk_put(priv->clks[0]); + return PTR_ERR(priv->clks[1]); + } + + return 0; +} + +static void usbhsc_clk_put(struct usbhs_priv *priv) +{ + int i; + + if (!usbhsc_is_multi_clks(priv)) + return; + + for (i = 0; i < ARRAY_SIZE(priv->clks); i++) + clk_put(priv->clks[i]); +} + +static int usbhsc_clk_prepare_enable(struct usbhs_priv *priv) +{ + int i, ret; + + if (!usbhsc_is_multi_clks(priv)) + return 0; + + for (i = 0; i < ARRAY_SIZE(priv->clks); i++) { + ret = clk_prepare_enable(priv->clks[i]); + if (ret) { + while (--i >= 0) + clk_disable_unprepare(priv->clks[i]); + return ret; + } + } + + return ret; +} + +static void usbhsc_clk_disable_unprepare(struct usbhs_priv *priv) +{ + int i; + + if (!usbhsc_is_multi_clks(priv)) + return; + + for (i = 0; i < ARRAY_SIZE(priv->clks); i++) + clk_disable_unprepare(priv->clks[i]); +} + +/* + * platform default param + */ + +/* commonly used on old SH-Mobile and RZ/G2L family SoCs */ +static struct renesas_usbhs_driver_pipe_config usbhsc_default_pipe[] = { + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_CONTROL, 64, 0x00, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_ISOC, 1024, 0x08, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_ISOC, 1024, 0x28, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x48, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x58, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x68, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x04, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x05, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x06, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x07, false), +}; + +/* commonly used on newer SH-Mobile and R-Car SoCs */ +static struct renesas_usbhs_driver_pipe_config usbhsc_new_pipe[] = { + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_CONTROL, 64, 0x00, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_ISOC, 1024, 0x08, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_ISOC, 1024, 0x28, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x48, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x58, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x68, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x04, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x05, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x06, false), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x78, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x88, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x98, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xa8, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xb8, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xc8, true), + RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xd8, true), +}; + +/* + * 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 clks */ + if (usbhsc_clk_prepare_enable(priv)) + return; + + /* 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 clks */ + usbhsc_clk_disable_unprepare(priv); + + /* 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 cable; + int ret; + + /* + * get vbus status from platform + */ + enable = usbhs_mod_info_call(priv, get_vbus, pdev); + + /* + * get id from platform + */ + id = usbhs_platform_call(priv, get_id, pdev); + + if (enable && !mod) { + if (priv->edev) { + cable = extcon_get_state(priv->edev, EXTCON_USB_HOST); + if ((cable > 0 && id != USBHS_HOST) || + (!cable && id != USBHS_GADGET)) { + dev_info(&pdev->dev, + "USB cable plugged in doesn't match the selected role!\n"); + return; + } + } + + ret = usbhs_mod_change(priv, id); + if (ret < 0) + return; + + dev_dbg(&pdev->dev, "%s enable\n", __func__); + + /* power on */ + if (usbhs_get_dparam(priv, 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 (usbhs_get_dparam(priv, 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); +} + +int usbhsc_schedule_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 const struct of_device_id usbhs_of_match[] = { + { + .compatible = "renesas,usbhs-r8a774c0", + .data = &usbhs_rcar_gen3_with_pll_plat_info, + }, + { + .compatible = "renesas,usbhs-r8a7790", + .data = &usbhs_rcar_gen2_plat_info, + }, + { + .compatible = "renesas,usbhs-r8a7791", + .data = &usbhs_rcar_gen2_plat_info, + }, + { + .compatible = "renesas,usbhs-r8a7794", + .data = &usbhs_rcar_gen2_plat_info, + }, + { + .compatible = "renesas,usbhs-r8a7795", + .data = &usbhs_rcar_gen3_plat_info, + }, + { + .compatible = "renesas,usbhs-r8a7796", + .data = &usbhs_rcar_gen3_plat_info, + }, + { + .compatible = "renesas,usbhs-r8a77990", + .data = &usbhs_rcar_gen3_with_pll_plat_info, + }, + { + .compatible = "renesas,usbhs-r8a77995", + .data = &usbhs_rcar_gen3_with_pll_plat_info, + }, + { + .compatible = "renesas,usbhs-r9a07g043", + .data = &usbhs_rzg2l_plat_info, + }, + { + .compatible = "renesas,usbhs-r9a07g044", + .data = &usbhs_rzg2l_plat_info, + }, + { + .compatible = "renesas,usbhs-r9a07g054", + .data = &usbhs_rzg2l_plat_info, + }, + { + .compatible = "renesas,usbhs-r9a09g077", + .data = &usbhs_rzg2l_plat_info, + }, + { + .compatible = "renesas,rcar-gen2-usbhs", + .data = &usbhs_rcar_gen2_plat_info, + }, + { + .compatible = "renesas,rcar-gen3-usbhs", + .data = &usbhs_rcar_gen3_plat_info, + }, + { + .compatible = "renesas,rza1-usbhs", + .data = &usbhs_rza1_plat_info, + }, + { + .compatible = "renesas,rza2-usbhs", + .data = &usbhs_rza2_plat_info, + }, + { + .compatible = "renesas,rzg2l-usbhs", + .data = &usbhs_rzg2l_plat_info, + }, + { } +}; +MODULE_DEVICE_TABLE(of, usbhs_of_match); + +static int usbhs_probe(struct platform_device *pdev) +{ + const struct renesas_usbhs_platform_info *info; + struct usbhs_priv *priv; + struct device *dev = &pdev->dev; + struct gpio_desc *gpiod; + int ret; + u32 tmp; + int irq; + + info = of_device_get_match_data(dev); + if (!info) { + info = dev_get_platdata(dev); + if (!info) + return dev_err_probe(dev, -EINVAL, "no platform info\n"); + } + + /* platform data */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + /* usb private data */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + if (of_property_present(dev_of_node(dev), "extcon")) { + priv->edev = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(priv->edev)) + return PTR_ERR(priv->edev); + } + + priv->rsts = devm_reset_control_array_get_optional_shared(dev); + if (IS_ERR(priv->rsts)) + return PTR_ERR(priv->rsts); + + /* + * care platform info + */ + + priv->dparam = info->driver_param; + + if (!info->platform_callback.get_id) { + dev_err(dev, "no platform callbacks\n"); + return -EINVAL; + } + priv->pfunc = &info->platform_callback; + + /* set default param if platform doesn't have */ + if (usbhs_get_dparam(priv, has_new_pipe_configs)) { + priv->dparam.pipe_configs = usbhsc_new_pipe; + priv->dparam.pipe_size = ARRAY_SIZE(usbhsc_new_pipe); + } else if (!priv->dparam.pipe_configs) { + priv->dparam.pipe_configs = usbhsc_default_pipe; + priv->dparam.pipe_size = ARRAY_SIZE(usbhsc_default_pipe); + } + if (!priv->dparam.pio_dma_border) + priv->dparam.pio_dma_border = 64; /* 64byte */ + if (!of_property_read_u32(dev_of_node(dev), "renesas,buswait", &tmp)) + priv->dparam.buswait_bwait = tmp; + gpiod = devm_gpiod_get_optional(dev, "renesas,enable", GPIOD_IN); + if (IS_ERR(gpiod)) + return PTR_ERR(gpiod); + + /* FIXME */ + /* runtime power control ? */ + if (priv->pfunc->get_vbus) + usbhs_get_dparam(priv, runtime_pwctrl) = 1; + + /* + * priv settings + */ + priv->irq = irq; + priv->pdev = pdev; + INIT_DELAYED_WORK(&priv->notify_hotplug_work, usbhsc_notify_hotplug); + spin_lock_init(usbhs_priv_to_lock(priv)); + + /* + * Acquire clocks and enable power management (PM) early in the + * probe process, as the driver accesses registers during + * initialization. Ensure the device is active before proceeding. + */ + pm_runtime_enable(dev); + + ret = usbhsc_clk_get(dev, priv); + if (ret) + goto probe_pm_disable; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + goto probe_clk_put; + + ret = usbhsc_clk_prepare_enable(priv); + if (ret) + goto probe_pm_put; + + /* call pipe and module init */ + ret = usbhs_pipe_probe(priv); + if (ret < 0) + goto probe_clk_dis_unprepare; + + 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; + + /* platform_set_drvdata() should be called after usbhs_mod_probe() */ + platform_set_drvdata(pdev, priv); + + ret = reset_control_deassert(priv->rsts); + if (ret) + goto probe_fail_rst; + + /* + * device reset here because + * USB device might be used in boot loader. + */ + usbhs_sys_clock_ctrl(priv, 0); + + /* check GPIO determining if USB function should be enabled */ + if (gpiod) { + ret = !gpiod_get_value(gpiod); + if (ret) { + dev_warn(dev, "USB function not selected (GPIO)\n"); + ret = -ENOTSUPP; + goto probe_assert_rest; + } + } + + /* + * 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(dev, "platform init failed.\n"); + goto probe_assert_rest; + } + + /* reset phy for connection */ + usbhs_platform_call(priv, phy_reset, pdev); + + /* + * Disable the clocks that were enabled earlier in the probe path, + * and let the driver handle the clocks beyond this point. + */ + usbhsc_clk_disable_unprepare(priv); + pm_runtime_put(dev); + + if (!usbhs_get_dparam(priv, runtime_pwctrl)) { + usbhsc_power_ctrl(priv, 1); + usbhs_mod_autonomy_mode(priv); + } else { + usbhs_mod_non_autonomy_mode(priv); + } + + /* + * manual call notify_hotplug for cold plug + */ + usbhsc_schedule_notify_hotplug(pdev); + + dev_info(dev, "probed\n"); + + return ret; + +probe_assert_rest: + reset_control_assert(priv->rsts); +probe_fail_rst: + usbhs_mod_remove(priv); +probe_end_fifo_exit: + usbhs_fifo_remove(priv); +probe_end_pipe_exit: + usbhs_pipe_remove(priv); +probe_clk_dis_unprepare: + usbhsc_clk_disable_unprepare(priv); +probe_pm_put: + pm_runtime_put(dev); +probe_clk_put: + usbhsc_clk_put(priv); +probe_pm_disable: + pm_runtime_disable(dev); + + dev_info(dev, "probe failed (%d)\n", ret); + + return ret; +} + +static void usbhs_remove(struct platform_device *pdev) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + + dev_dbg(&pdev->dev, "usb remove\n"); + + flush_delayed_work(&priv->notify_hotplug_work); + + /* + * Explicitly free the IRQ to ensure the interrupt handler is + * disabled and synchronized before freeing resources. + * devm_free_irq() calls free_irq() which waits for any running + * ISR to complete, preventing UAF. + */ + devm_free_irq(&pdev->dev, priv->irq, priv); + + usbhs_mod_remove(priv); + usbhs_fifo_remove(priv); + usbhs_pipe_remove(priv); + + /* power off */ + if (!usbhs_get_dparam(priv, runtime_pwctrl)) + usbhsc_power_ctrl(priv, 0); + + usbhs_platform_call(priv, hardware_exit, pdev); + reset_control_assert(priv->rsts); + + usbhsc_clk_put(priv); + pm_runtime_disable(&pdev->dev); +} + +static void usbhsc_restore(struct device *dev) +{ + struct usbhs_priv *priv = dev_get_drvdata(dev); + struct platform_device *pdev = usbhs_priv_to_pdev(priv); + + if (!usbhs_get_dparam(priv, runtime_pwctrl)) { + usbhsc_power_ctrl(priv, 1); + usbhs_mod_autonomy_mode(priv); + } + + usbhs_platform_call(priv, phy_reset, pdev); + + usbhsc_schedule_notify_hotplug(pdev); +} + +static int usbhsc_suspend(struct device *dev) +{ + struct usbhs_priv *priv = dev_get_drvdata(dev); + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + int ret; + + if (mod) { + usbhs_mod_call(priv, stop, priv); + usbhs_mod_change(priv, -1); + } + + if (mod || !usbhs_get_dparam(priv, runtime_pwctrl)) + usbhsc_power_ctrl(priv, 0); + + ret = reset_control_assert(priv->rsts); + if (ret) + usbhsc_restore(dev); + + return ret; +} + +static int usbhsc_resume(struct device *dev) +{ + struct usbhs_priv *priv = dev_get_drvdata(dev); + int ret; + + ret = reset_control_deassert(priv->rsts); + if (ret) + return ret; + + usbhsc_restore(dev); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(usbhsc_pm_ops, usbhsc_suspend, usbhsc_resume); + +static struct platform_driver renesas_usbhs_driver = { + .driver = { + .name = "renesas_usbhs", + .pm = pm_sleep_ptr(&usbhsc_pm_ops), + .of_match_table = usbhs_of_match, + }, + .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 000000000..3fb5bc94d --- /dev/null +++ b/drivers/usb/renesas_usbhs/common.h @@ -0,0 +1,346 @@ +/* SPDX-License-Identifier: GPL-1.0+ */ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Copyright (C) 2019 Renesas Electronics Corporation + * Kuninori Morimoto + */ +#ifndef RENESAS_USB_DRIVER_H +#define RENESAS_USB_DRIVER_H + +#include +#include +#include +#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 +#define D2FIFOSEL 0x00F0 /* for R-Car Gen2 */ +#define D2FIFOCTR 0x00F2 /* for R-Car Gen2 */ +#define D3FIFOSEL 0x00F4 /* for R-Car Gen2 */ +#define D3FIFOCTR 0x00F6 /* for R-Car Gen2 */ +#define SUSPMODE 0x0102 /* for RZ/A */ + +/* SYSCFG */ +#define SCKE (1 << 10) /* USB Module Clock Enable */ +#define CNEN (1 << 8) /* Single-ended receiver operation 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 */ +#define UCKSEL (1 << 2) /* Clock Select for RZ/A1 */ +#define UPLLE (1 << 1) /* USB PLL Enable for RZ/A1 */ + +/* 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 (0x7 << 4) /* Device State */ +#define POWER_STATE (0 << 4) +#define DEFAULT_STATE (1 << 4) +#define ADDRESS_STATE (2 << 4) +#define CONFIGURATION_STATE (3 << 4) +#define SUSPENDED_STATE (4 << 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 BFRE (1 << 10) /* BRDY Interrupt Operation Spec. */ +#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 INBUFM (1 << 14) /* (PIPEnCTR) Transfer Buffer Monitor */ +#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 SQMON (1 << 6) /* Toggle Bit Check */ +#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 + +/* SUSPMODE */ +#define SUSPM (1 << 14) /* SuspendM Control */ + +/* + * struct + */ +struct usbhs_priv { + + void __iomem *base; + unsigned int irq; + + const struct renesas_usbhs_platform_callback *pfunc; + struct renesas_usbhs_driver_param dparam; + + struct delayed_work notify_hotplug_work; + struct platform_device *pdev; + + struct extcon_dev *edev; + + spinlock_t lock; + + /* + * module control + */ + struct usbhs_mod_info mod_info; + + /* + * pipe control + */ + struct usbhs_pipe_info pipe_info; + + /* + * fifo control + */ + struct usbhs_fifo_info fifo_info; + + struct phy *phy; + struct reset_control *rsts; + struct clk *clks[2]; +}; + +/* + * 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) + +int usbhs_get_id_as_gadget(struct platform_device *pdev); + +/* + * 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); +int usbhsc_schedule_notify_hotplug(struct platform_device *pdev); + +/* + * 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); + +/* + * interrupt functions + */ +void usbhs_xxxsts_clear(struct usbhs_priv *priv, u16 sts_reg, u16 bit); + +/* + * 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 000000000..bac6f8fd0 --- /dev/null +++ b/drivers/usb/renesas_usbhs/fifo.c @@ -0,0 +1,1486 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Copyright (C) 2019 Renesas Electronics Corporation + * Kuninori Morimoto + */ +#include +#include +#include +#include "common.h" +#include "pipe.h" + +#define usbhsf_get_cfifo(p) (&((p)->fifo_info.cfifo)) + +#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 const 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); +} + +struct usbhs_pkt *__usbhsf_pkt_get(struct usbhs_pipe *pipe) +{ + return list_first_entry_or_null(&pipe->list, struct usbhs_pkt, node); +} + +static void usbhsf_fifo_unselect(struct usbhs_pipe *pipe, + struct usbhs_fifo *fifo); +static struct dma_chan *usbhsf_dma_chan_get(struct usbhs_fifo *fifo, + struct usbhs_pkt *pkt); +#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); +static void usbhsf_tx_irq_ctrl(struct usbhs_pipe *pipe, int enable); +static void usbhsf_rx_irq_ctrl(struct usbhs_pipe *pipe, int enable); +struct usbhs_pkt *usbhs_pkt_pop(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct usbhs_fifo *fifo = usbhs_pipe_to_fifo(pipe); + unsigned long flags; + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + + usbhs_pipe_disable(pipe); + + if (!pkt) + pkt = __usbhsf_pkt_get(pipe); + + if (pkt) { + struct dma_chan *chan = NULL; + + if (fifo) + chan = usbhsf_dma_chan_get(fifo, pkt); + if (chan) { + dmaengine_terminate_sync(chan); + usbhsf_dma_unmap(pkt); + } else { + if (usbhs_pipe_is_dir_in(pipe)) + usbhsf_rx_irq_ctrl(pipe, 0); + else + usbhsf_tx_irq_ctrl(pipe, 0); + } + + usbhs_pipe_clear_without_sequence(pipe, 0, 0); + usbhs_pipe_running(pipe, 0); + + __usbhsf_pkt_del(pkt); + } + + if (fifo) + usbhsf_fifo_unselect(pipe, fifo); + + 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) { + ret = -EINVAL; + 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; + } + + if (likely(func)) + 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) +{ + /* The FIFO port is accessible */ + if (usbhs_read(priv, fifo->ctr) & FRDY) + return 0; + + return -EBUSY; +} + +static void usbhsf_fifo_clear(struct usbhs_pipe *pipe, + struct usbhs_fifo *fifo) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + int ret = 0; + + if (!usbhs_pipe_is_dcp(pipe)) { + /* + * This driver checks the pipe condition first to avoid -EBUSY + * from usbhsf_fifo_barrier() if the pipe is RX direction and + * empty. + */ + if (usbhs_pipe_is_dir_in(pipe)) + ret = usbhs_pipe_is_accessible(pipe); + if (!ret) + ret = usbhsf_fifo_barrier(priv, fifo); + } + + /* + * if non-DCP pipe, this driver should set BCLR when + * usbhsf_fifo_barrier() returns 0. + */ + if (!ret) + 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 */ + 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() failed\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; +} + +const struct usbhs_pkt_handle usbhs_dcp_status_stage_in_handler = { + .prepare = usbhs_dcp_dir_switch_to_write, + .try_run = usbhs_dcp_dir_switch_done, +}; + +const 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); +} + +const 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); +} + +const 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 */ + if (usbhs_get_dparam(priv, cfifo_byte_addr)) { + for (i = 0; i < len; i++) + iowrite8(buf[i], addr + (i & 0x03)); + } else { + 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_running(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); + + 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); + usbhs_pipe_running(pipe, 1); + + return ret; +} + +static int usbhsf_pio_prepare_push(struct usbhs_pkt *pkt, int *is_done) +{ + if (usbhs_pipe_is_running(pkt->pipe)) + return 0; + + return usbhsf_pio_try_push(pkt, is_done); +} + +const struct usbhs_pkt_handle usbhs_fifo_pio_push_handler = { + .prepare = usbhsf_pio_prepare_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; + 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; + + if (usbhs_pipe_is_running(pipe)) + return 0; + + /* + * pipe enable to prepare packet receive + */ + usbhs_pipe_data_sequence(pipe, pkt->sequence); + pkt->sequence = -1; /* -1 sequence will be ignored */ + + if (usbhs_pipe_is_dcp(pipe)) + usbhsf_fifo_clear(pipe, fifo); + + usbhs_pipe_set_trans_count_if_bulk(pipe, pkt->length); + usbhs_pipe_enable(pipe); + usbhs_pipe_running(pipe, 1); + 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_running(pipe, 0); + /* + * If function mode, since this controller is possible to enter + * Control Write status stage at this timing, this driver + * should not disable the pipe. If such a case happens, this + * controller is not able to complete the status stage. + */ + if (!usbhs_mod_is_host(priv) && !usbhs_pipe_is_dcp(pipe)) + 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; +} + +const 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; +} + +const 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; + int i; + + usbhs_for_each_dfifo(priv, fifo, i) { + 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); +} + +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); + struct usbhs_fifo *fifo = usbhs_pipe_to_fifo(pipe); + struct dma_chan *chan = usbhsf_dma_chan_get(fifo, pkt); + + return info->dma_map_ctrl(chan->device->dev, pkt, map); +} + +static void usbhsf_dma_complete(void *arg, + const struct dmaengine_result *result); +static void usbhsf_dma_xfer_preparing(struct usbhs_pkt *pkt) +{ + struct usbhs_pipe *pipe = pkt->pipe; + struct usbhs_fifo *fifo; + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct dma_async_tx_descriptor *desc; + struct dma_chan *chan; + struct device *dev = usbhs_priv_to_dev(priv); + enum dma_transfer_direction dir; + dma_cookie_t cookie; + + fifo = usbhs_pipe_to_fifo(pipe); + if (!fifo) + return; + + chan = usbhsf_dma_chan_get(fifo, pkt); + 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_result = usbhsf_dma_complete; + desc->callback_param = pkt; + + cookie = dmaengine_submit(desc); + if (cookie < 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_running(pipe, 1); + usbhs_pipe_set_trans_count_if_bulk(pipe, pkt->trans); + dma_async_issue_pending(chan); + usbhsf_dma_start(pipe, fifo); + usbhs_pipe_enable(pipe); +} + +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_priv *priv = usbhs_pipe_to_priv(pipe); + unsigned long flags; + + usbhs_lock(priv, flags); + usbhsf_dma_xfer_preparing(pkt); + usbhs_unlock(priv, flags); +} + +/* + * 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; + uintptr_t align_mask; + + 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_type_is(pipe, USB_ENDPOINT_XFER_ISOC)) + goto usbhsf_pio_prepare_push; + + /* check data length if this driver don't use USB-DMAC */ + if (!usbhs_get_dparam(priv, has_usb_dmac) && len & 0x7) + goto usbhsf_pio_prepare_push; + + /* check buffer alignment */ + align_mask = usbhs_get_dparam(priv, has_usb_dmac) ? + USBHS_USB_DMAC_XFER_SIZE - 1 : 0x7; + if ((uintptr_t)(pkt->buf + pkt->actual) & align_mask) + goto usbhsf_pio_prepare_push; + + /* return at this time if the pipe is running */ + if (usbhs_pipe_is_running(pipe)) + return 0; + + /* get enable DMA fifo */ + fifo = usbhsf_get_dma_fifo(priv, pkt); + if (!fifo) + goto usbhsf_pio_prepare_push; + + ret = usbhsf_fifo_select(pipe, fifo, 0); + if (ret < 0) + goto usbhsf_pio_prepare_push; + + if (usbhsf_dma_map(pkt) < 0) + goto usbhsf_pio_prepare_push_unselect; + + pkt->trans = len; + + usbhsf_tx_irq_ctrl(pipe, 0); + /* FIXME: Workaound for usb dmac that driver can be used in atomic */ + if (usbhs_get_dparam(priv, has_usb_dmac)) { + usbhsf_dma_xfer_preparing(pkt); + } else { + INIT_WORK(&pkt->work, xfer_work); + schedule_work(&pkt->work); + } + + return 0; + +usbhsf_pio_prepare_push_unselect: + usbhsf_fifo_unselect(pipe, fifo); +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; + int is_short = pkt->trans % usbhs_pipe_get_maxpacket(pipe); + + pkt->actual += pkt->trans; + + 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? */ + + usbhs_pipe_running(pipe, !*is_done); + + usbhsf_dma_stop(pipe, pipe->fifo); + usbhsf_dma_unmap(pkt); + usbhsf_fifo_unselect(pipe, pipe->fifo); + + if (!*is_done) { + /* change handler to PIO */ + pkt->handler = &usbhs_fifo_pio_push_handler; + return pkt->handler->try_run(pkt, is_done); + } + + return 0; +} + +const 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_prepare_pop_with_rx_irq(struct usbhs_pkt *pkt, + int *is_done) +{ + return usbhsf_prepare_pop(pkt, is_done); +} + +static int usbhsf_dma_prepare_pop_with_usb_dmac(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 ret; + + if (usbhs_pipe_is_busy(pipe)) + return 0; + + /* use PIO if packet is less than pio_dma_border or pipe is DCP */ + if ((pkt->length < usbhs_get_dparam(priv, pio_dma_border)) || + usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_ISOC)) + goto usbhsf_pio_prepare_pop; + + fifo = usbhsf_get_dma_fifo(priv, pkt); + if (!fifo) + goto usbhsf_pio_prepare_pop; + + if ((uintptr_t)pkt->buf & (USBHS_USB_DMAC_XFER_SIZE - 1)) + goto usbhsf_pio_prepare_pop; + + /* return at this time if the pipe is running */ + if (usbhs_pipe_is_running(pipe)) + return 0; + + usbhs_pipe_config_change_bfre(pipe, 1); + + ret = usbhsf_fifo_select(pipe, fifo, 0); + if (ret < 0) + goto usbhsf_pio_prepare_pop; + + 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 = pkt->length; + + usbhsf_dma_xfer_preparing(pkt); + + 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; + usbhs_pipe_config_change_bfre(pipe, 0); + + return pkt->handler->prepare(pkt, is_done); +} + +static int usbhsf_dma_prepare_pop(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pkt->pipe); + + if (usbhs_get_dparam(priv, has_usb_dmac)) + return usbhsf_dma_prepare_pop_with_usb_dmac(pkt, is_done); + else + return usbhsf_dma_prepare_pop_with_rx_irq(pkt, is_done); +} + +static int usbhsf_dma_try_pop_with_rx_irq(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_try_pop(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pkt->pipe); + + BUG_ON(usbhs_get_dparam(priv, has_usb_dmac)); + + return usbhsf_dma_try_pop_with_rx_irq(pkt, is_done); +} + +static int usbhsf_dma_pop_done_with_rx_irq(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; + usbhs_pipe_running(pipe, 0); + } else { + /* re-enable */ + usbhs_pipe_running(pipe, 0); + usbhsf_prepare_pop(pkt, is_done); + } + + return 0; +} + +static size_t usbhs_dma_calc_received_size(struct usbhs_pkt *pkt, + struct dma_chan *chan, int dtln) +{ + struct usbhs_pipe *pipe = pkt->pipe; + size_t received_size; + int maxp = usbhs_pipe_get_maxpacket(pipe); + + received_size = pkt->length - pkt->dma_result->residue; + + if (dtln) { + received_size -= USBHS_USB_DMAC_XFER_SIZE; + received_size &= ~(maxp - 1); + received_size += dtln; + } + + return received_size; +} + +static int usbhsf_dma_pop_done_with_usb_dmac(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 = usbhs_pipe_to_fifo(pipe); + struct dma_chan *chan = usbhsf_dma_chan_get(fifo, pkt); + int rcv_len; + + /* + * Since the driver disables rx_irq in DMA mode, the interrupt handler + * cannot the BRDYSTS. So, the function clears it here because the + * driver may use PIO mode next time. + */ + usbhs_xxxsts_clear(priv, BRDYSTS, usbhs_pipe_number(pipe)); + + rcv_len = usbhsf_fifo_rcv_len(priv, fifo); + usbhsf_fifo_clear(pipe, fifo); + pkt->actual = usbhs_dma_calc_received_size(pkt, chan, rcv_len); + + usbhs_pipe_running(pipe, 0); + usbhsf_dma_stop(pipe, fifo); + usbhsf_dma_unmap(pkt); + usbhsf_fifo_unselect(pipe, pipe->fifo); + + /* The driver can assume the rx transaction is always "done" */ + *is_done = 1; + + return 0; +} + +static int usbhsf_dma_pop_done(struct usbhs_pkt *pkt, int *is_done) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pkt->pipe); + + if (usbhs_get_dparam(priv, has_usb_dmac)) + return usbhsf_dma_pop_done_with_usb_dmac(pkt, is_done); + else + return usbhsf_dma_pop_done_with_rx_irq(pkt, is_done); +} + +const struct usbhs_pkt_handle usbhs_fifo_dma_pop_handler = { + .prepare = usbhsf_dma_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_pdev(struct usbhs_fifo *fifo) +{ + 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); +} + +static void usbhsf_dma_init_dt(struct device *dev, struct usbhs_fifo *fifo, + int channel) +{ + char name[16]; + + /* + * To avoid complex handing for DnFIFOs, the driver uses each + * DnFIFO as TX or RX direction (not bi-direction). + * So, the driver uses odd channels for TX, even channels for RX. + */ + snprintf(name, sizeof(name), "ch%d", channel); + if (channel & 1) { + fifo->tx_chan = dma_request_chan(dev, name); + if (IS_ERR(fifo->tx_chan)) + fifo->tx_chan = NULL; + } else { + fifo->rx_chan = dma_request_chan(dev, name); + if (IS_ERR(fifo->rx_chan)) + fifo->rx_chan = NULL; + } +} + +static void usbhsf_dma_init(struct usbhs_priv *priv, struct usbhs_fifo *fifo, + int channel) +{ + struct device *dev = usbhs_priv_to_dev(priv); + + if (dev_of_node(dev)) + usbhsf_dma_init_dt(dev, fifo, channel); + else + usbhsf_dma_init_pdev(fifo); + + 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, + const struct dmaengine_result *result) +{ + struct usbhs_pkt *pkt = arg; + struct usbhs_pipe *pipe = pkt->pipe; + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct device *dev = usbhs_priv_to_dev(priv); + int ret; + + pkt->dma_result = result; + 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); +} + +void usbhs_fifo_clear_dcp(struct usbhs_pipe *pipe) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); /* CFIFO */ + + /* clear DCP FIFO of transmission */ + if (usbhsf_fifo_select(pipe, fifo, 1) < 0) + return; + usbhsf_fifo_clear(pipe, fifo); + usbhsf_fifo_unselect(pipe, fifo); + + /* clear DCP FIFO of reception */ + if (usbhsf_fifo_select(pipe, fifo, 0) < 0) + return; + usbhsf_fifo_clear(pipe, fifo); + usbhsf_fifo_unselect(pipe, fifo); +} + +/* + * 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 *dfifo; + int i; + + mod->irq_empty = usbhsf_irq_empty; + mod->irq_ready = usbhsf_irq_ready; + mod->irq_bempsts = 0; + mod->irq_brdysts = 0; + + cfifo->pipe = NULL; + usbhs_for_each_dfifo(priv, dfifo, i) + dfifo->pipe = NULL; +} + +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; +} + +#define __USBHS_DFIFO_INIT(priv, fifo, channel, fifo_port) \ +do { \ + fifo = usbhsf_get_dnfifo(priv, channel); \ + fifo->name = "D"#channel"FIFO"; \ + fifo->port = fifo_port; \ + fifo->sel = D##channel##FIFOSEL; \ + fifo->ctr = D##channel##FIFOCTR; \ + fifo->tx_slave.shdma_slave.slave_id = \ + usbhs_get_dparam(priv, d##channel##_tx_id); \ + fifo->rx_slave.shdma_slave.slave_id = \ + usbhs_get_dparam(priv, d##channel##_rx_id); \ + usbhsf_dma_init(priv, fifo, channel); \ +} while (0) + +#define USBHS_DFIFO_INIT(priv, fifo, channel) \ + __USBHS_DFIFO_INIT(priv, fifo, channel, D##channel##FIFO) +#define USBHS_DFIFO_INIT_NO_PORT(priv, fifo, channel) \ + __USBHS_DFIFO_INIT(priv, fifo, channel, 0) + +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; + + /* DFIFO */ + USBHS_DFIFO_INIT(priv, fifo, 0); + USBHS_DFIFO_INIT(priv, fifo, 1); + USBHS_DFIFO_INIT_NO_PORT(priv, fifo, 2); + USBHS_DFIFO_INIT_NO_PORT(priv, fifo, 3); + + return 0; +} + +void usbhs_fifo_remove(struct usbhs_priv *priv) +{ + struct usbhs_fifo *fifo; + int i; + + usbhs_for_each_dfifo(priv, fifo, i) + usbhsf_dma_quit(priv, fifo); +} diff --git a/drivers/usb/renesas_usbhs/fifo.h b/drivers/usb/renesas_usbhs/fifo.h new file mode 100644 index 000000000..039a2b993 --- /dev/null +++ b/drivers/usb/renesas_usbhs/fifo.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-1.0+ */ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto + */ +#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; +}; + +#define USBHS_MAX_NUM_DFIFO 4 +struct usbhs_fifo_info { + struct usbhs_fifo cfifo; + struct usbhs_fifo dfifo[USBHS_MAX_NUM_DFIFO]; +}; +#define usbhsf_get_dnfifo(p, n) (&((p)->fifo_info.dfifo[n])) +#define usbhs_for_each_dfifo(priv, dfifo, i) \ + for ((i) = 0; \ + ((i) < USBHS_MAX_NUM_DFIFO) && \ + ((dfifo) = usbhsf_get_dnfifo(priv, (i))); \ + (i)++) + +struct usbhs_pkt_handle; +struct usbhs_pkt { + struct list_head node; + struct usbhs_pipe *pipe; + const struct usbhs_pkt_handle *handler; + void (*done)(struct usbhs_priv *priv, + struct usbhs_pkt *pkt); + struct work_struct work; + dma_addr_t dma; + const struct dmaengine_result *dma_result; + 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); +void usbhs_fifo_clear_dcp(struct usbhs_pipe *pipe); + +/* + * packet info + */ +extern const struct usbhs_pkt_handle usbhs_fifo_pio_push_handler; +extern const struct usbhs_pkt_handle usbhs_fifo_pio_pop_handler; +extern const struct usbhs_pkt_handle usbhs_ctrl_stage_end_handler; + +extern const struct usbhs_pkt_handle usbhs_fifo_dma_push_handler; +extern const struct usbhs_pkt_handle usbhs_fifo_dma_pop_handler; + +extern const struct usbhs_pkt_handle usbhs_dcp_status_stage_in_handler; +extern const struct usbhs_pkt_handle usbhs_dcp_status_stage_out_handler; + +extern const struct usbhs_pkt_handle usbhs_dcp_data_stage_in_handler; +extern const 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); +struct usbhs_pkt *__usbhsf_pkt_get(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 000000000..f2ea3e141 --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod.c @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Copyright (C) 2019 Renesas Electronics Corporation + * Kuninori Morimoto + */ +#include + +#include "common.h" +#include "mod.h" + +/* + * 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); + + usbhsc_schedule_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; + info->get_vbus = usbhsm_autonomy_get_vbus; + + usbhs_irq_callback_update(priv, NULL); +} + +void usbhs_mod_non_autonomy_mode(struct usbhs_priv *priv) +{ + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + + info->get_vbus = priv->pfunc->get_vbus; +} + +/* + * 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, + 0, 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) +{ + return (int)irq_state->intsts0 & DVSQ_MASK; +} + +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; + unsigned long flags; + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + state->intsts0 = usbhs_read(priv, INTSTS0); + intenb0 = usbhs_read(priv, INTENB0); + + if (usbhs_mod_is_host(priv)) { + state->intsts1 = usbhs_read(priv, INTSTS1); + intenb1 = usbhs_read(priv, INTENB1); + } else { + state->intsts1 = intenb1 = 0; + } + + /* 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; + } + usbhs_unlock(priv, flags); + /******************** spin unlock ******************/ + + 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); + if (usbhs_mod_is_host(priv)) + usbhs_write(priv, INTSTS1, ~irq_state.intsts1 & INTSTS1_MAGIC); + + /* + * The driver should not clear the xxxSTS after the line of + * "call irq callback functions" because each "if" statement is + * possible to call the callback function for avoiding any side effects. + */ + if (irq_state.intsts0 & BRDY) + usbhs_write(priv, BRDYSTS, ~irq_state.brdysts); + usbhs_write(priv, NRDYSTS, ~irq_state.nrdysts); + if (irq_state.intsts0 & BEMP) + 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); + + if (usbhs_mod_is_host(priv)) { + /* 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); + if (usbhs_mod_is_host(priv)) + usbhs_write(priv, INTENB1, 0); + + usbhs_write(priv, BEMPENB, 0); + usbhs_write(priv, BRDYENB, 0); + + /* + * see also + * usbhs_interrupt + */ + + if (info->irq_vbus) + intenb0 |= VBSE; + + if (mod) { + /* + * INTSTS0 + */ + if (mod->irq_ctrl_stage) + intenb0 |= CTRE; + + if (mod->irq_dev_state) + intenb0 |= DVSE; + + 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; + } + + if (usbhs_mod_is_host(priv)) { + /* + * 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 (usbhs_mod_is_host(priv) && 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 000000000..56b7106d2 --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod.h @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: GPL-1.0+ */ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Copyright (C) 2019 Renesas Electronics Corporation + * Kuninori Morimoto + */ +#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 (runtime_pwctrl == 0) + * when the platform doesn't have own get_vbus function. + * + * 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); + + /* + * This function will be used on any gadget mode. To simplify the code, + * this member is in here. + */ + int (*get_vbus)(struct platform_device *pdev); +}; + +/* + * 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); +void usbhs_mod_non_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); \ + }) + +#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); \ +}) + +/* + * 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 000000000..0c7fe109d --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod_gadget.c @@ -0,0 +1,1184 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Copyright (C) 2019 Renesas Electronics Corporation + * Kuninori Morimoto + */ +#include +#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; + spinlock_t lock; /* protect the pipe */ + + char ep_name[EP_NAME_SIZE]; + + struct usbhsg_gpriv *gpriv; +}; + +struct usbhsg_gpriv { + struct usb_gadget gadget; + struct usbhs_mod mod; + + int uep_size; + + struct usb_gadget_driver *driver; + struct usb_phy *transceiver; + bool vbus_active; + + 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) +#define USBHSG_STATUS_SOFT_CONNECT (1 << 4) + struct usbhsg_uep uep[] __counted_by(uep_size); +}; + +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; \ + ((i) < (g)->uep_size) && ((pos) = (g)->uep + (i)); \ + (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); + struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + + if (pipe) + dev_dbg(dev, "pipe %d : queue pop\n", usbhs_pipe_number(pipe)); + + ureq->req.status = status; + spin_unlock(usbhs_priv_to_lock(priv)); + usb_gadget_giveback_request(&uep->ep, &ureq->req); + spin_lock(usbhs_priv_to_lock(priv)); +} + +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_priv *priv = usbhsg_gpriv_to_priv(gpriv); + unsigned long flags; + + usbhs_lock(priv, flags); + __usbhsg_queue_pop(uep, ureq, status); + usbhs_unlock(priv, flags); +} + +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); + unsigned long flags; + + ureq->req.actual = pkt->actual; + + usbhs_lock(priv, flags); + if (uep) + __usbhsg_queue_pop(uep, ureq, 0); + usbhs_unlock(priv, flags); +} + +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 device *dma_dev, 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; + 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_by_dev(dma_dev, req, dir); + if (ret < 0) + return ret; + + pkt->dma = req->dma; + } else { + usb_gadget_unmap_request_by_dev(dma_dev, 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; + __le16 *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_obj(*buf, GFP_ATOMIC); + if (!buf) { + usb_ep_free_request(&dcp->ep, req); + 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); + int state = usbhs_status_get_device_state(irq_state); + + gpriv->gadget.speed = usbhs_bus_get_speed(priv); + + dev_dbg(dev, "state = %x : speed : %d\n", state, gpriv->gadget.speed); + + if (gpriv->gadget.speed != USB_SPEED_UNKNOWN && + (state & SUSPENDED_STATE)) { + if (gpriv->driver && gpriv->driver->suspend) + gpriv->driver->suspend(&gpriv->gadget); + usb_gadget_set_state(&gpriv->gadget, USB_STATE_SUSPENDED); + } + + 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; + case READ_STATUS_STAGE: + case WRITE_STATUS_STAGE: + usbhs_dcp_control_transfer_done(pipe); + fallthrough; + 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), -ESHUTDOWN); + } + + 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; + unsigned long flags; + + usbhs_lock(priv, flags); + + /* + * if it already have pipe, + * nothing to do + */ + if (uep->pipe) { + usbhs_pipe_clear(uep->pipe); + usbhs_pipe_sequence_data0(uep->pipe); + ret = 0; + goto usbhsg_ep_enable_end; + } + + 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; + usbhs_xxxsts_clear(priv, BRDYSTS, + usbhs_pipe_number(pipe)); + } + + ret = 0; + } + +usbhsg_ep_enable_end: + usbhs_unlock(priv, flags); + + return ret; +} + +static int usbhsg_ep_disable(struct usb_ep *ep) +{ + struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep); + struct usbhs_pipe *pipe; + unsigned long flags; + + spin_lock_irqsave(&uep->lock, flags); + pipe = usbhsg_uep_to_pipe(uep); + if (!pipe) + goto out; + + usbhsg_pipe_disable(uep); + usbhs_pipe_free(pipe); + + uep->pipe->mod_private = NULL; + uep->pipe = NULL; + +out: + spin_unlock_irqrestore(&uep->lock, flags); + + return 0; +} + +static struct usb_request *usbhsg_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct usbhsg_request *ureq; + + ureq = kzalloc_obj(*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; + unsigned long flags; + + spin_lock_irqsave(&uep->lock, flags); + pipe = usbhsg_uep_to_pipe(uep); + if (pipe) + usbhs_pkt_pop(pipe, usbhsg_ureq_to_pkt(ureq)); + + /* + * To dequeue a request, this driver should call the usbhsg_queue_pop() + * even if the pipe is NULL. + */ + usbhsg_queue_pop(uep, ureq, -ECONNRESET); + spin_unlock_irqrestore(&uep->lock, flags); + + 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; + int ret = 0; + + dev_dbg(dev, "set halt %d (pipe %d)\n", + halt, usbhs_pipe_number(pipe)); + + /******************** spin lock ********************/ + usbhs_lock(priv, flags); + + /* + * According to usb_ep_set_halt()'s description, this function should + * return -EAGAIN if the IN endpoint has any queue or data. Note + * that the usbhs_pipe_is_dir_in() returns false if the pipe is an + * IN endpoint in the gadget mode. + */ + if (!usbhs_pipe_is_dir_in(pipe) && (__usbhsf_pkt_get(pipe) || + usbhs_pipe_contains_transmittable_data(pipe))) { + ret = -EAGAIN; + goto out; + } + + 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); + +out: + usbhs_unlock(priv, flags); + /******************** spin unlock ******************/ + + return ret; +} + +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 const 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, +}; + +/* + * pullup control + */ +static int usbhsg_can_pullup(struct usbhs_priv *priv) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + + return gpriv->driver && + usbhsg_status_has(gpriv, USBHSG_STATUS_SOFT_CONNECT); +} + +static void usbhsg_update_pullup(struct usbhs_priv *priv) +{ + if (usbhsg_can_pullup(priv)) + usbhs_sys_function_pullup(priv, 1); + else + usbhs_sys_function_pullup(priv, 0); +} + +/* + * 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_fifo_init(priv); + usbhs_pipe_init(priv, + usbhsg_dma_map_ctrl); + + /* 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); + usbhsg_update_pullup(priv); + + /* + * 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 *uep; + struct device *dev = usbhs_priv_to_dev(priv); + unsigned long flags; + int ret = 0, i; + + /******************** 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); + + /* disable all eps */ + usbhsg_for_each_uep_with_dcp(uep, gpriv, i) + usbhsg_ep_disable(&uep->ep); + + dev_dbg(dev, "stop gadget\n"); + + return 0; +} + +/* + * VBUS provided by the PHY + */ +static int usbhsm_phy_get_vbus(struct platform_device *pdev) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + + return gpriv->vbus_active; +} + +static void usbhs_mod_phy_mode(struct usbhs_priv *priv) +{ + struct usbhs_mod_info *info = &priv->mod_info; + + info->irq_vbus = NULL; + info->get_vbus = usbhsm_phy_get_vbus; + + usbhs_irq_callback_update(priv, NULL); +} + +/* + * + * 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); + struct device *dev = usbhs_priv_to_dev(priv); + int ret; + + if (!driver || + !driver->setup || + driver->max_speed < USB_SPEED_FULL) + return -EINVAL; + + /* connect to bus through transceiver */ + if (!IS_ERR_OR_NULL(gpriv->transceiver)) { + ret = otg_set_peripheral(gpriv->transceiver->otg, + &gpriv->gadget); + if (ret) { + dev_err(dev, "%s: can't bind to transceiver\n", + gpriv->gadget.name); + return ret; + } + + /* get vbus using phy versions */ + usbhs_mod_phy_mode(priv); + } + + /* 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 usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget); + struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + + usbhsg_try_stop(priv, USBHSG_STATUS_REGISTERD); + + if (!IS_ERR_OR_NULL(gpriv->transceiver)) + otg_set_peripheral(gpriv->transceiver->otg, NULL); + + 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); + unsigned long flags; + + usbhs_lock(priv, flags); + if (is_on) + usbhsg_status_set(gpriv, USBHSG_STATUS_SOFT_CONNECT); + else + usbhsg_status_clr(gpriv, USBHSG_STATUS_SOFT_CONNECT); + usbhsg_update_pullup(priv); + usbhs_unlock(priv, flags); + + 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); + + gadget->is_selfpowered = (is_self != 0); + + return 0; +} + +static int usbhsg_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget); + struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + struct platform_device *pdev = usbhs_priv_to_pdev(priv); + + gpriv->vbus_active = !!is_active; + + usbhsc_schedule_notify_hotplug(pdev); + + 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, + .vbus_session = usbhsg_vbus_session, +}; + +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); + struct renesas_usbhs_driver_pipe_config *pipe_configs = + usbhs_get_dparam(priv, pipe_configs); + int pipe_size = usbhs_get_dparam(priv, pipe_size); + int i; + int ret; + + gpriv = kzalloc_flex(*gpriv, uep, pipe_size); + if (!gpriv) + return -ENOMEM; + + gpriv->uep_size = pipe_size; + + gpriv->transceiver = devm_usb_get_phy(dev, USB_PHY_TYPE_UNDEFINED); + dev_info(dev, "%stransceiver found\n", + !IS_ERR(gpriv->transceiver) ? "" : "no "); + + /* + * 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; + 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; + gpriv->gadget.quirk_avoids_skb_reserve = usbhs_get_dparam(priv, + has_usb_dmac); + + 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); + spin_lock_init(&uep->lock); + + /* init DCP */ + if (usbhsg_is_dcp(uep)) { + gpriv->gadget.ep0 = &uep->ep; + usb_ep_set_maxpacket_limit(&uep->ep, 64); + uep->ep.caps.type_control = true; + } else { + /* init normal pipe */ + if (pipe_configs[i].type == USB_ENDPOINT_XFER_ISOC) + uep->ep.caps.type_iso = true; + if (pipe_configs[i].type == USB_ENDPOINT_XFER_BULK) + uep->ep.caps.type_bulk = true; + if (pipe_configs[i].type == USB_ENDPOINT_XFER_INT) + uep->ep.caps.type_int = true; + usb_ep_set_maxpacket_limit(&uep->ep, + pipe_configs[i].bufsize); + list_add_tail(&uep->ep.ep_list, &gpriv->gadget.ep_list); + } + uep->ep.caps.dir_in = true; + uep->ep.caps.dir_out = true; + } + + 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); + + 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); +} diff --git a/drivers/usb/renesas_usbhs/mod_host.c b/drivers/usb/renesas_usbhs/mod_host.c new file mode 100644 index 000000000..f7ef3a9f8 --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod_host.c @@ -0,0 +1,1574 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto + */ +#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; \ + ((i) < USBHSH_DEVICE_MAX) && ((pos) = (h)->udev + (i)); \ + (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; + + ureq = kzalloc_obj(struct usbhsh_request, mem_flags); + if (!ureq) + 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 doesn'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_obj(struct usbhsh_ep, mem_flags); + if (!uep) + 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 connected 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 device *dma_dev, 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 = USB_DT_HUB; + desc->bHubContrCurrent = 0; + desc->bNbrPorts = roothub_id; + desc->bDescLength = 9; + desc->bPwrOn2PwrGood = 0; + desc->wHubCharacteristics = + cpu_to_le16(HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_NO_OCPM); + 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 const struct hc_driver usbhsh_driver = { + .description = usbhsh_hcd_name, + .hcd_priv_size = sizeof(struct usbhsh_hpriv), + + /* + * generic hardware linkage + */ + .flags = HCD_DMA | 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; + struct renesas_usbhs_driver_pipe_config *pipe_configs = + usbhs_get_dparam(priv, pipe_configs); + 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_configs[i].type == old_type); + old_type = pipe_configs[i].type; + + if (USB_ENDPOINT_XFER_CONTROL == pipe_configs[i].type) { + pipe = usbhs_dcp_malloc(priv); + usbhsh_hpriv_to_dcp(hpriv) = pipe; + } else { + pipe = usbhs_pipe_malloc(priv, + pipe_configs[i].type, + 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; + device_wakeup_enable(hcd->self.controller); + + /* + * pipe initialize and enable DCP + */ + usbhs_fifo_init(priv); + usbhs_pipe_init(priv, + usbhsh_dma_map_ctrl); + 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 000000000..e4e897e82 --- /dev/null +++ b/drivers/usb/renesas_usbhs/pipe.c @@ -0,0 +1,852 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto + */ +#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)]; +} + +static struct renesas_usbhs_driver_pipe_config +*usbhsp_get_pipe_config(struct usbhs_priv *priv, int pipe_num) +{ + struct renesas_usbhs_driver_pipe_config *pipe_configs = + usbhs_get_dparam(priv, pipe_configs); + + return &pipe_configs[pipe_num]; +} + +/* + * 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); +} + +static u16 __usbhsp_pipe_xxx_get(struct usbhs_pipe *pipe, + u16 dcp_reg, u16 pipe_reg) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + + if (usbhs_pipe_is_dcp(pipe)) + return usbhs_read(priv, dcp_reg); + else + return usbhs_read(priv, pipe_reg); +} + +/* + * 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); +} + +static u16 usbhsp_pipe_cfg_get(struct usbhs_pipe *pipe) +{ + return __usbhsp_pipe_xxx_get(pipe, DCPCFG, PIPECFG); +} + +/* + * 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 mask = usbhs_mod_is_host(priv) ? (CSSTS | PID_MASK) : PID_MASK; + + /* + * 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 { + if (!(usbhsp_pipectrl_get(pipe) & mask)) + 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; +} + +bool usbhs_pipe_contains_transmittable_data(struct usbhs_pipe *pipe) +{ + u16 val; + + /* Do not support for DCP pipe */ + if (usbhs_pipe_is_dcp(pipe)) + return false; + + val = usbhsp_pipectrl_get(pipe); + if (val & INBUFM) + return true; + + return false; +} + +/* + * 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); + fallthrough; + 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_setup_pipecfg(struct usbhs_pipe *pipe, int is_host, + int dir_in, u16 *pipecfg) +{ + u16 type = 0; + u16 bfre = 0; + u16 dblb = 0; + u16 cntmd = 0; + u16 dir = 0; + u16 epnum = 0; + u16 shtnak = 0; + static const u16 type_array[] = { + [USB_ENDPOINT_XFER_BULK] = TYPE_BULK, + [USB_ENDPOINT_XFER_INT] = TYPE_INT, + [USB_ENDPOINT_XFER_ISOC] = TYPE_ISO, + }; + + 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: see usbhs_pipe_config_update() */ + + /* 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() */ + *pipecfg = type | + bfre | + dblb | + cntmd | + dir | + shtnak | + epnum; + return 0; +} + +static u16 usbhsp_setup_pipebuff(struct usbhs_pipe *pipe) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + struct device *dev = usbhs_priv_to_dev(priv); + int pipe_num = usbhs_pipe_number(pipe); + u16 buff_size; + u16 bufnmb; + u16 bufnmb_cnt; + struct renesas_usbhs_driver_pipe_config *pipe_config = + usbhsp_get_pipe_config(priv, pipe_num); + + /* + * PIPEBUF + * + * see + * - "Register Descriptions" - "PIPEBUF" register + * - "Features" - "Pipe configuration" + * - "Operation" - "FIFO Buffer Memory" + * - "Operation" - "Pipe Control" + */ + buff_size = pipe_config->bufsize; + bufnmb = pipe_config->bufnum; + + /* change buff_size to register value */ + bufnmb_cnt = (buff_size / 64) - 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) +{ + struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + int pipe_num = usbhs_pipe_number(pipe); + struct renesas_usbhs_driver_pipe_config *pipe_config = + usbhsp_get_pipe_config(priv, pipe_num); + u16 dblb = pipe_config->double_buf ? DBLB : 0; + + if (devsel > 0xA) { + 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 | DBLB, epnum | dblb); +} + +/* + * 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); +} + +int usbhs_pipe_is_running(struct usbhs_pipe *pipe) +{ + return usbhsp_flags_has(pipe, IS_RUNNING); +} + +void usbhs_pipe_running(struct usbhs_pipe *pipe, int running) +{ + if (running) + usbhsp_flags_set(pipe, IS_RUNNING); + else + usbhsp_flags_clr(pipe, IS_RUNNING); +} + +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); +} + +static int usbhs_pipe_get_data_sequence(struct usbhs_pipe *pipe) +{ + return !!(usbhsp_pipectrl_get(pipe) & SQMON); +} + +void usbhs_pipe_clear(struct usbhs_pipe *pipe) +{ + if (usbhs_pipe_is_dcp(pipe)) { + usbhs_fifo_clear_dcp(pipe); + } else { + usbhsp_pipectrl_set(pipe, ACLRM, ACLRM); + usbhsp_pipectrl_set(pipe, ACLRM, 0); + } +} + +/* Should call usbhsp_pipe_select() before */ +void usbhs_pipe_clear_without_sequence(struct usbhs_pipe *pipe, + int needs_bfre, int bfre_enable) +{ + int sequence; + + usbhsp_pipe_select(pipe); + sequence = usbhs_pipe_get_data_sequence(pipe); + if (needs_bfre) + usbhsp_pipe_cfg_set(pipe, BFRE, bfre_enable ? BFRE : 0); + usbhs_pipe_clear(pipe); + usbhs_pipe_data_sequence(pipe, sequence); +} + +void usbhs_pipe_config_change_bfre(struct usbhs_pipe *pipe, int enable) +{ + if (usbhs_pipe_is_dcp(pipe)) + return; + + usbhsp_pipe_select(pipe); + /* check if the driver needs to change the BFRE value */ + if (!(enable ^ !!(usbhsp_pipe_cfg_get(pipe) & BFRE))) + return; + + usbhs_pipe_clear_without_sequence(pipe, 1, enable); +} + +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; +} + +static void usbhsp_put_pipe(struct usbhs_pipe *pipe) +{ + usbhsp_flags_init(pipe); +} + +void usbhs_pipe_init(struct usbhs_priv *priv, + int (*dma_map_ctrl)(struct device *dma_dev, + struct usbhs_pkt *pkt, int map)) +{ + struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv); + struct usbhs_pipe *pipe; + int i; + + usbhs_for_each_pipe_with_dcp(pipe, priv, i) { + 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) { + usbhsp_put_pipe(pipe); + dev_err(dev, "pipe setup failed %d\n", usbhs_pipe_number(pipe)); + return NULL; + } + + if (usbhsp_setup_pipecfg(pipe, is_host, dir_in, &pipecfg)) { + usbhsp_put_pipe(pipe); + dev_err(dev, "can't setup pipe\n"); + return NULL; + } + + 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_clear(pipe); + + 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_free(struct usbhs_pipe *pipe) +{ + usbhsp_pipe_select(pipe); + usbhsp_pipe_cfg_set(pipe, 0xFFFF, 0); + usbhsp_put_pipe(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); + struct renesas_usbhs_driver_pipe_config *pipe_configs = + usbhs_get_dparam(priv, pipe_configs); + int pipe_size = usbhs_get_dparam(priv, pipe_size); + int i; + + /* This driver expects 1st pipe is DCP */ + if (pipe_configs[0].type != USB_ENDPOINT_XFER_CONTROL) { + dev_err(dev, "1st PIPE is not DCP\n"); + return -EINVAL; + } + + info->pipe = kzalloc_objs(struct usbhs_pipe, pipe_size); + if (!info->pipe) + 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_configs[i].type & USB_ENDPOINT_XFERTYPE_MASK; + + dev_dbg(dev, "pipe %x\t: %s\n", + i, usbhsp_pipe_name[pipe_configs[i].type]); + } + + 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 000000000..a4ae9f97d --- /dev/null +++ b/drivers/usb/renesas_usbhs/pipe.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-1.0+ */ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto + */ +#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) +#define USBHS_PIPE_FLAGS_IS_RUNNING (1 << 3) + + const struct usbhs_pkt_handle *handler; + + void *mod_private; +}; + +struct usbhs_pipe_info { + struct usbhs_pipe *pipe; + int size; /* array size of "pipe" */ + + int (*dma_map_ctrl)(struct device *dma_dev, struct usbhs_pkt *pkt, + int map); +}; + +/* + * pipe list + */ +#define __usbhs_for_each_pipe(start, pos, info, i) \ + for ((i) = start; \ + ((i) < (info)->size) && ((pos) = (info)->pipe + (i)); \ + (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); +void usbhs_pipe_free(struct usbhs_pipe *pipe); +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); +int usbhs_pipe_is_running(struct usbhs_pipe *pipe); +void usbhs_pipe_running(struct usbhs_pipe *pipe, int running); + +void usbhs_pipe_init(struct usbhs_priv *priv, + int (*dma_map_ctrl)(struct device *dma_dev, + struct usbhs_pkt *pkt, int map)); +int usbhs_pipe_get_maxpacket(struct usbhs_pipe *pipe); +void usbhs_pipe_clear(struct usbhs_pipe *pipe); +void usbhs_pipe_clear_without_sequence(struct usbhs_pipe *pipe, + int needs_bfre, int bfre_enable); +int usbhs_pipe_is_accessible(struct usbhs_pipe *pipe); +bool usbhs_pipe_contains_transmittable_data(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); +void usbhs_pipe_config_change_bfre(struct usbhs_pipe *pipe, int enable); + +#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/renesas_usbhs/rcar2.c b/drivers/usb/renesas_usbhs/rcar2.c new file mode 100644 index 000000000..52756fc2a --- /dev/null +++ b/drivers/usb/renesas_usbhs/rcar2.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * Renesas USB driver R-Car Gen. 2 initialization and power control + * + * Copyright (C) 2014 Ulrich Hecht + * Copyright (C) 2019 Renesas Electronics Corporation + */ + +#include +#include "common.h" +#include "rcar2.h" + +static int usbhs_rcar2_hardware_init(struct platform_device *pdev) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + + if (IS_ENABLED(CONFIG_GENERIC_PHY)) { + struct phy *phy = phy_get(&pdev->dev, "usb"); + + if (IS_ERR(phy)) + return PTR_ERR(phy); + + priv->phy = phy; + return 0; + } + + return -ENXIO; +} + +static int usbhs_rcar2_hardware_exit(struct platform_device *pdev) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + + if (priv->phy) { + phy_put(&pdev->dev, priv->phy); + priv->phy = NULL; + } + + return 0; +} + +static int usbhs_rcar2_power_ctrl(struct platform_device *pdev, + void __iomem *base, int enable) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + int retval = -ENODEV; + + if (priv->phy) { + if (enable) { + retval = phy_init(priv->phy); + + if (!retval) + retval = phy_power_on(priv->phy); + } else { + phy_power_off(priv->phy); + phy_exit(priv->phy); + retval = 0; + } + } + + return retval; +} + +const struct renesas_usbhs_platform_info usbhs_rcar_gen2_plat_info = { + .platform_callback = { + .hardware_init = usbhs_rcar2_hardware_init, + .hardware_exit = usbhs_rcar2_hardware_exit, + .power_ctrl = usbhs_rcar2_power_ctrl, + .get_id = usbhs_get_id_as_gadget, + }, + .driver_param = { + .has_usb_dmac = 1, + .has_new_pipe_configs = 1, + }, +}; diff --git a/drivers/usb/renesas_usbhs/rcar2.h b/drivers/usb/renesas_usbhs/rcar2.h new file mode 100644 index 000000000..046d07edb --- /dev/null +++ b/drivers/usb/renesas_usbhs/rcar2.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include "common.h" + +extern const struct renesas_usbhs_platform_info usbhs_rcar_gen2_plat_info; diff --git a/drivers/usb/renesas_usbhs/rcar3.c b/drivers/usb/renesas_usbhs/rcar3.c new file mode 100644 index 000000000..c181b2a0b --- /dev/null +++ b/drivers/usb/renesas_usbhs/rcar3.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas USB driver R-Car Gen. 3 initialization and power control + * + * Copyright (C) 2016-2019 Renesas Electronics Corporation + */ + +#include +#include +#include "common.h" +#include "rcar3.h" + +#define LPSTS 0x102 +#define UGCTRL 0x180 /* 32-bit register */ +#define UGCTRL2 0x184 /* 32-bit register */ +#define UGSTS 0x188 /* 32-bit register */ + +/* Low Power Status register (LPSTS) */ +#define LPSTS_SUSPM 0x4000 + +/* R-Car D3 only: USB General control register (UGCTRL) */ +#define UGCTRL_PLLRESET 0x00000001 +#define UGCTRL_CONNECT 0x00000004 + +/* + * USB General control register 2 (UGCTRL2) + * Remarks: bit[31:11] and bit[9:6] should be 0 + */ +#define UGCTRL2_RESERVED_3 0x00000001 /* bit[3:0] should be B'0001 */ +#define UGCTRL2_USB0SEL_HSUSB 0x00000020 +#define UGCTRL2_USB0SEL_OTG 0x00000030 +#define UGCTRL2_VBUSSEL 0x00000400 + +/* R-Car D3 only: USB General status register (UGSTS) */ +#define UGSTS_LOCK 0x00000100 + +static void usbhs_write32(struct usbhs_priv *priv, u32 reg, u32 data) +{ + iowrite32(data, priv->base + reg); +} + +static u32 usbhs_read32(struct usbhs_priv *priv, u32 reg) +{ + return ioread32(priv->base + reg); +} + +static void usbhs_rcar3_set_ugctrl2(struct usbhs_priv *priv, u32 val) +{ + usbhs_write32(priv, UGCTRL2, val | UGCTRL2_RESERVED_3); +} + +static int usbhs_rcar3_power_ctrl(struct platform_device *pdev, + void __iomem *base, int enable) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + + usbhs_rcar3_set_ugctrl2(priv, UGCTRL2_USB0SEL_OTG | UGCTRL2_VBUSSEL); + + if (enable) { + usbhs_bset(priv, LPSTS, LPSTS_SUSPM, LPSTS_SUSPM); + /* The controller on R-Car Gen3 needs to wait up to 45 usec */ + usleep_range(45, 90); + } else { + usbhs_bset(priv, LPSTS, LPSTS_SUSPM, 0); + } + + return 0; +} + +/* R-Car D3 needs to release UGCTRL.PLLRESET */ +static int usbhs_rcar3_power_and_pll_ctrl(struct platform_device *pdev, + void __iomem *base, int enable) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + u32 val; + int timeout = 1000; + + if (enable) { + usbhs_write32(priv, UGCTRL, 0); /* release PLLRESET */ + usbhs_rcar3_set_ugctrl2(priv, + UGCTRL2_USB0SEL_OTG | UGCTRL2_VBUSSEL); + + usbhs_bset(priv, LPSTS, LPSTS_SUSPM, LPSTS_SUSPM); + do { + val = usbhs_read32(priv, UGSTS); + udelay(1); + } while (!(val & UGSTS_LOCK) && timeout--); + usbhs_write32(priv, UGCTRL, UGCTRL_CONNECT); + } else { + usbhs_write32(priv, UGCTRL, 0); + usbhs_bset(priv, LPSTS, LPSTS_SUSPM, 0); + usbhs_write32(priv, UGCTRL, UGCTRL_PLLRESET); + } + + return 0; +} + +const struct renesas_usbhs_platform_info usbhs_rcar_gen3_plat_info = { + .platform_callback = { + .power_ctrl = usbhs_rcar3_power_ctrl, + .get_id = usbhs_get_id_as_gadget, + }, + .driver_param = { + .has_usb_dmac = 1, + .multi_clks = 1, + .has_new_pipe_configs = 1, + }, +}; + +const struct renesas_usbhs_platform_info usbhs_rcar_gen3_with_pll_plat_info = { + .platform_callback = { + .power_ctrl = usbhs_rcar3_power_and_pll_ctrl, + .get_id = usbhs_get_id_as_gadget, + }, + .driver_param = { + .has_usb_dmac = 1, + .multi_clks = 1, + .has_new_pipe_configs = 1, + }, +}; diff --git a/drivers/usb/renesas_usbhs/rcar3.h b/drivers/usb/renesas_usbhs/rcar3.h new file mode 100644 index 000000000..d13db30bd --- /dev/null +++ b/drivers/usb/renesas_usbhs/rcar3.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include "common.h" + +extern const struct renesas_usbhs_platform_info usbhs_rcar_gen3_plat_info; +extern const struct renesas_usbhs_platform_info + usbhs_rcar_gen3_with_pll_plat_info; diff --git a/drivers/usb/renesas_usbhs/rza.c b/drivers/usb/renesas_usbhs/rza.c new file mode 100644 index 000000000..97b5217c5 --- /dev/null +++ b/drivers/usb/renesas_usbhs/rza.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas USB driver RZ/A initialization and power control + * + * Copyright (C) 2018 Chris Brandt + * Copyright (C) 2018-2019 Renesas Electronics Corporation + */ + +#include +#include +#include +#include "common.h" +#include "rza.h" + +static int usbhs_rza1_hardware_init(struct platform_device *pdev) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + struct device_node *usb_x1_clk, *extal_clk; + u32 freq_usb = 0, freq_extal = 0; + + /* Input Clock Selection (NOTE: ch0 controls both ch0 and ch1) */ + usb_x1_clk = of_find_node_by_name(NULL, "usb_x1"); + extal_clk = of_find_node_by_name(NULL, "extal"); + of_property_read_u32(usb_x1_clk, "clock-frequency", &freq_usb); + of_property_read_u32(extal_clk, "clock-frequency", &freq_extal); + + of_node_put(usb_x1_clk); + of_node_put(extal_clk); + + if (freq_usb == 0) { + if (freq_extal == 12000000) { + /* Select 12MHz XTAL */ + usbhs_bset(priv, SYSCFG, UCKSEL, UCKSEL); + } else { + dev_err(usbhs_priv_to_dev(priv), "A 48MHz USB clock or 12MHz main clock is required.\n"); + return -EIO; + } + } + + /* Enable USB PLL (NOTE: ch0 controls both ch0 and ch1) */ + usbhs_bset(priv, SYSCFG, UPLLE, UPLLE); + usleep_range(1000, 2000); + usbhs_bset(priv, SUSPMODE, SUSPM, SUSPM); + + return 0; +} + +const struct renesas_usbhs_platform_info usbhs_rza1_plat_info = { + .platform_callback = { + .hardware_init = usbhs_rza1_hardware_init, + .get_id = usbhs_get_id_as_gadget, + }, + .driver_param = { + .has_new_pipe_configs = 1, + }, +}; diff --git a/drivers/usb/renesas_usbhs/rza.h b/drivers/usb/renesas_usbhs/rza.h new file mode 100644 index 000000000..8b879aa34 --- /dev/null +++ b/drivers/usb/renesas_usbhs/rza.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include "common.h" + +extern const struct renesas_usbhs_platform_info usbhs_rza1_plat_info; +extern const struct renesas_usbhs_platform_info usbhs_rza2_plat_info; +extern const struct renesas_usbhs_platform_info usbhs_rzg2l_plat_info; diff --git a/drivers/usb/renesas_usbhs/rza2.c b/drivers/usb/renesas_usbhs/rza2.c new file mode 100644 index 000000000..b83699eab --- /dev/null +++ b/drivers/usb/renesas_usbhs/rza2.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas USB driver RZ/A2 initialization and power control + * + * Copyright (C) 2019 Chris Brandt + * Copyright (C) 2019 Renesas Electronics Corporation + */ + +#include +#include +#include +#include "common.h" +#include "rza.h" + +static int usbhs_rza2_hardware_init(struct platform_device *pdev) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + struct phy *phy = phy_get(&pdev->dev, "usb"); + + if (IS_ERR(phy)) + return PTR_ERR(phy); + + priv->phy = phy; + return 0; +} + +static int usbhs_rza2_hardware_exit(struct platform_device *pdev) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + + phy_put(&pdev->dev, priv->phy); + priv->phy = NULL; + + return 0; +} + +static int usbhs_rza2_power_ctrl(struct platform_device *pdev, + void __iomem *base, int enable) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + int retval = 0; + + if (!priv->phy) + return -ENODEV; + + if (enable) { + retval = phy_init(priv->phy); + usbhs_bset(priv, SUSPMODE, SUSPM, SUSPM); + udelay(100); /* Wait for PLL to become stable */ + if (!retval) + retval = phy_power_on(priv->phy); + } else { + usbhs_bset(priv, SUSPMODE, SUSPM, 0); + phy_power_off(priv->phy); + phy_exit(priv->phy); + } + + return retval; +} + +const struct renesas_usbhs_platform_info usbhs_rza2_plat_info = { + .platform_callback = { + .hardware_init = usbhs_rza2_hardware_init, + .hardware_exit = usbhs_rza2_hardware_exit, + .power_ctrl = usbhs_rza2_power_ctrl, + .get_id = usbhs_get_id_as_gadget, + }, + .driver_param = { + .has_cnen = 1, + .cfifo_byte_addr = 1, + .has_new_pipe_configs = 1, + }, +}; + +const struct renesas_usbhs_platform_info usbhs_rzg2l_plat_info = { + .platform_callback = { + .hardware_init = usbhs_rza2_hardware_init, + .hardware_exit = usbhs_rza2_hardware_exit, + .power_ctrl = usbhs_rza2_power_ctrl, + .get_id = usbhs_get_id_as_gadget, + }, + .driver_param = { + .has_cnen = 1, + .cfifo_byte_addr = 1, + }, +}; diff --git a/drivers/usb/roles/Kconfig b/drivers/usb/roles/Kconfig new file mode 100644 index 000000000..f8b31aa67 --- /dev/null +++ b/drivers/usb/roles/Kconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0 + +config USB_ROLE_SWITCH + tristate "USB Role Switch Support" + help + USB Role Switch is a device that can select the USB role - host or + device - for a USB port (connector). In most cases dual-role capable + USB controller will also represent the switch, but on some platforms + multiplexer/demultiplexer switch is used to route the data lines on + the USB connector between separate USB host and device controllers. + + Say Y here if your USB connectors support both device and host roles. + To compile the driver as module, choose M here: the module will be + called roles.ko. + +if USB_ROLE_SWITCH + +config USB_ROLES_INTEL_XHCI + tristate "Intel XHCI USB Role Switch" + depends on ACPI && X86 + help + Driver for the internal USB role switch for switching the USB data + lines between the xHCI host controller and the dwc3 gadget controller + found on various Intel SoCs. + + To compile the driver as a module, choose M here: the module will + be called intel-xhci-usb-role-switch. + +endif # USB_ROLE_SWITCH diff --git a/drivers/usb/roles/Makefile b/drivers/usb/roles/Makefile new file mode 100644 index 000000000..757a7d279 --- /dev/null +++ b/drivers/usb/roles/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_USB_ROLE_SWITCH) += roles.o +roles-y := class.o +obj-$(CONFIG_USB_ROLES_INTEL_XHCI) += intel-xhci-usb-role-switch.o diff --git a/drivers/usb/roles/class.c b/drivers/usb/roles/class.c new file mode 100644 index 000000000..edec139b6 --- /dev/null +++ b/drivers/usb/roles/class.c @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Role Switch Support + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus + * Hans de Goede + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct class role_class = { + .name = "usb_role", +}; + +struct usb_role_switch { + struct device dev; + struct lock_class_key key; + struct mutex lock; /* device lock*/ + struct module *module; /* the module this device depends on */ + enum usb_role role; + bool registered; + + /* From descriptor */ + struct device *usb2_port; + struct device *usb3_port; + struct device *udc; + usb_role_switch_set_t set; + usb_role_switch_get_t get; + bool allow_userspace_control; +}; + +#define to_role_switch(d) container_of(d, struct usb_role_switch, dev) + +static int connector_bind(struct device *dev, struct device *connector, void *data) +{ + int ret; + + ret = sysfs_create_link(&dev->kobj, &connector->kobj, "connector"); + if (ret) + return ret; + + ret = sysfs_create_link(&connector->kobj, &dev->kobj, "usb-role-switch"); + if (ret) + sysfs_remove_link(&dev->kobj, "connector"); + + return ret; +} + +static void connector_unbind(struct device *dev, struct device *connector, void *data) +{ + sysfs_remove_link(&connector->kobj, "usb-role-switch"); + sysfs_remove_link(&dev->kobj, "connector"); +} + +static const struct component_ops connector_ops = { + .bind = connector_bind, + .unbind = connector_unbind, +}; + +/** + * usb_role_switch_set_role - Set USB role for a switch + * @sw: USB role switch + * @role: USB role to be switched to + * + * Set USB role @role for @sw. + */ +int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role) +{ + int ret; + + if (IS_ERR_OR_NULL(sw)) + return 0; + + if (!sw->registered) + return -EOPNOTSUPP; + + mutex_lock(&sw->lock); + + ret = sw->set(sw, role); + if (!ret) { + sw->role = role; + kobject_uevent(&sw->dev.kobj, KOBJ_CHANGE); + } + + mutex_unlock(&sw->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_role_switch_set_role); + +/** + * usb_role_switch_get_role - Get the USB role for a switch + * @sw: USB role switch + * + * Depending on the role-switch-driver this function returns either a cached + * value of the last set role, or reads back the actual value from the hardware. + */ +enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw) +{ + enum usb_role role; + + if (IS_ERR_OR_NULL(sw) || !sw->registered) + return USB_ROLE_NONE; + + mutex_lock(&sw->lock); + + if (sw->get) + role = sw->get(sw); + else + role = sw->role; + + mutex_unlock(&sw->lock); + + return role; +} +EXPORT_SYMBOL_GPL(usb_role_switch_get_role); + +static void *usb_role_switch_match(const struct fwnode_handle *fwnode, const char *id, + void *data) +{ + struct device *dev; + + if (id && !fwnode_property_present(fwnode, id)) + return NULL; + + dev = class_find_device_by_fwnode(&role_class, fwnode); + + return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER); +} + +static struct usb_role_switch * +usb_role_switch_is_parent(struct fwnode_handle *fwnode) +{ + struct fwnode_handle *parent; + struct device *dev; + + if (!fwnode_device_is_compatible(fwnode, "usb-b-connector")) + return NULL; + + parent = fwnode_get_parent(fwnode); + + if (!fwnode_property_present(parent, "usb-role-switch")) { + fwnode_handle_put(parent); + return NULL; + } + + dev = class_find_device_by_fwnode(&role_class, parent); + fwnode_handle_put(parent); + return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER); +} + +/** + * usb_role_switch_get - Find USB role switch linked with the caller + * @dev: The caller device + * + * Finds and returns role switch linked with @dev. The reference count for the + * found switch is incremented. + */ +struct usb_role_switch *usb_role_switch_get(struct device *dev) +{ + struct usb_role_switch *sw; + + sw = usb_role_switch_is_parent(dev_fwnode(dev)); + if (!sw) + sw = device_connection_find_match(dev, "usb-role-switch", NULL, + usb_role_switch_match); + + if (!IS_ERR_OR_NULL(sw)) + WARN_ON(!try_module_get(sw->module)); + + return sw; +} +EXPORT_SYMBOL_GPL(usb_role_switch_get); + +/** + * fwnode_usb_role_switch_get - Find USB role switch linked with the caller + * @fwnode: The caller device node + * + * This is similar to the usb_role_switch_get() function above, but it searches + * the switch using fwnode instead of device entry. + */ +struct usb_role_switch *fwnode_usb_role_switch_get(struct fwnode_handle *fwnode) +{ + struct usb_role_switch *sw; + + sw = usb_role_switch_is_parent(fwnode); + if (!sw) + sw = fwnode_connection_find_match(fwnode, "usb-role-switch", + NULL, usb_role_switch_match); + if (!IS_ERR_OR_NULL(sw)) + WARN_ON(!try_module_get(sw->module)); + + return sw; +} +EXPORT_SYMBOL_GPL(fwnode_usb_role_switch_get); + +/** + * usb_role_switch_put - Release handle to a switch + * @sw: USB Role Switch + * + * Decrement reference count for @sw. + */ +void usb_role_switch_put(struct usb_role_switch *sw) +{ + if (!IS_ERR_OR_NULL(sw)) { + module_put(sw->module); + put_device(&sw->dev); + } +} +EXPORT_SYMBOL_GPL(usb_role_switch_put); + +/** + * usb_role_switch_find_by_fwnode - Find USB role switch with its fwnode + * @fwnode: fwnode of the USB Role Switch + * + * Finds and returns role switch with @fwnode. The reference count for the + * found switch is incremented. + */ +struct usb_role_switch * +usb_role_switch_find_by_fwnode(const struct fwnode_handle *fwnode) +{ + struct device *dev; + struct usb_role_switch *sw = NULL; + + if (!fwnode) + return NULL; + + dev = class_find_device_by_fwnode(&role_class, fwnode); + if (dev) { + sw = to_role_switch(dev); + WARN_ON(!try_module_get(sw->module)); + } + + return sw; +} +EXPORT_SYMBOL_GPL(usb_role_switch_find_by_fwnode); + +static umode_t +usb_role_switch_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct usb_role_switch *sw = to_role_switch(dev); + + if (sw->allow_userspace_control) + return attr->mode; + + return 0; +} + +static const char * const usb_roles[] = { + [USB_ROLE_NONE] = "none", + [USB_ROLE_HOST] = "host", + [USB_ROLE_DEVICE] = "device", +}; + +const char *usb_role_string(enum usb_role role) +{ + if (role < 0 || role >= ARRAY_SIZE(usb_roles)) + return "unknown"; + + return usb_roles[role]; +} +EXPORT_SYMBOL_GPL(usb_role_string); + +static ssize_t +role_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_role_switch *sw = to_role_switch(dev); + enum usb_role role = usb_role_switch_get_role(sw); + + return sprintf(buf, "%s\n", usb_roles[role]); +} + +static ssize_t role_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_role_switch *sw = to_role_switch(dev); + int ret; + + ret = sysfs_match_string(usb_roles, buf); + if (ret < 0) { + bool res; + + /* Extra check if the user wants to disable the switch */ + ret = kstrtobool(buf, &res); + if (ret || res) + return -EINVAL; + } + + ret = usb_role_switch_set_role(sw, ret); + if (ret) + return ret; + + return size; +} +static DEVICE_ATTR_RW(role); + +static struct attribute *usb_role_switch_attrs[] = { + &dev_attr_role.attr, + NULL, +}; + +static const struct attribute_group usb_role_switch_group = { + .is_visible = usb_role_switch_is_visible, + .attrs = usb_role_switch_attrs, +}; + +static const struct attribute_group *usb_role_switch_groups[] = { + &usb_role_switch_group, + NULL, +}; + +static int usb_role_switch_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + int ret; + + ret = add_uevent_var(env, "USB_ROLE_SWITCH=%s", dev_name(dev)); + if (ret) + dev_err(dev, "failed to add uevent USB_ROLE_SWITCH\n"); + + return ret; +} + +static void usb_role_switch_release(struct device *dev) +{ + struct usb_role_switch *sw = to_role_switch(dev); + + mutex_destroy(&sw->lock); + lockdep_unregister_key(&sw->key); + kfree(sw); +} + +static const struct device_type usb_role_dev_type = { + .name = "usb_role_switch", + .groups = usb_role_switch_groups, + .uevent = usb_role_switch_uevent, + .release = usb_role_switch_release, +}; + +/** + * usb_role_switch_register - Register USB Role Switch + * @parent: Parent device for the switch + * @desc: Description of the switch + * + * USB Role Switch is a device capable or choosing the role for USB connector. + * On platforms where the USB controller is dual-role capable, the controller + * driver will need to register the switch. On platforms where the USB host and + * USB device controllers behind the connector are separate, there will be a + * mux, and the driver for that mux will need to register the switch. + * + * Returns handle to a new role switch or ERR_PTR. The content of @desc is + * copied. + */ +struct usb_role_switch * +usb_role_switch_register(struct device *parent, + const struct usb_role_switch_desc *desc) +{ + struct usb_role_switch *sw; + int ret; + + if (!desc || !desc->set) + return ERR_PTR(-EINVAL); + + sw = kzalloc_obj(*sw); + if (!sw) + return ERR_PTR(-ENOMEM); + + lockdep_register_key(&sw->key); + mutex_init_with_key(&sw->lock, &sw->key); + + sw->allow_userspace_control = desc->allow_userspace_control; + sw->usb2_port = desc->usb2_port; + sw->usb3_port = desc->usb3_port; + sw->udc = desc->udc; + sw->set = desc->set; + sw->get = desc->get; + + sw->module = parent->driver->owner; + sw->dev.parent = parent; + sw->dev.fwnode = desc->fwnode; + sw->dev.class = &role_class; + sw->dev.type = &usb_role_dev_type; + dev_set_drvdata(&sw->dev, desc->driver_data); + dev_set_name(&sw->dev, "%s-role-switch", + desc->name ? desc->name : dev_name(parent)); + + sw->registered = true; + + ret = device_register(&sw->dev); + if (ret) { + sw->registered = false; + put_device(&sw->dev); + return ERR_PTR(ret); + } + + if (dev_fwnode(&sw->dev)) { + ret = component_add(&sw->dev, &connector_ops); + if (ret) + dev_warn(&sw->dev, "failed to add component\n"); + } + + /* TODO: Symlinks for the host port and the device controller. */ + + return sw; +} +EXPORT_SYMBOL_GPL(usb_role_switch_register); + +/** + * usb_role_switch_unregister - Unregsiter USB Role Switch + * @sw: USB Role Switch + * + * Unregister switch that was registered with usb_role_switch_register(). + */ +void usb_role_switch_unregister(struct usb_role_switch *sw) +{ + if (IS_ERR_OR_NULL(sw)) + return; + sw->registered = false; + if (dev_fwnode(&sw->dev)) + component_del(&sw->dev, &connector_ops); + device_unregister(&sw->dev); +} +EXPORT_SYMBOL_GPL(usb_role_switch_unregister); + +/** + * usb_role_switch_set_drvdata - Assign private data pointer to a switch + * @sw: USB Role Switch + * @data: Private data pointer + */ +void usb_role_switch_set_drvdata(struct usb_role_switch *sw, void *data) +{ + dev_set_drvdata(&sw->dev, data); +} +EXPORT_SYMBOL_GPL(usb_role_switch_set_drvdata); + +/** + * usb_role_switch_get_drvdata - Get the private data pointer of a switch + * @sw: USB Role Switch + */ +void *usb_role_switch_get_drvdata(struct usb_role_switch *sw) +{ + return dev_get_drvdata(&sw->dev); +} +EXPORT_SYMBOL_GPL(usb_role_switch_get_drvdata); + +static int __init usb_roles_init(void) +{ + return class_register(&role_class); +} +subsys_initcall(usb_roles_init); + +static void __exit usb_roles_exit(void) +{ + class_unregister(&role_class); +} +module_exit(usb_roles_exit); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB Role Class"); diff --git a/drivers/usb/roles/intel-xhci-usb-role-switch.c b/drivers/usb/roles/intel-xhci-usb-role-switch.c new file mode 100644 index 000000000..cd2e026dc --- /dev/null +++ b/drivers/usb/roles/intel-xhci-usb-role-switch.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Intel XHCI (Cherry Trail, Broxton and others) USB OTG role switch driver + * + * Copyright (c) 2016-2017 Hans de Goede + * + * Loosely based on android x86 kernel code which is: + * + * Copyright (C) 2014 Intel Corp. + * + * Author: Wu, Hao + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* register definition */ +#define DUAL_ROLE_CFG0 0x68 +#define SW_VBUS_VALID BIT(24) +#define SW_IDPIN_EN BIT(21) +#define SW_IDPIN BIT(20) +#define SW_SWITCH_EN BIT(16) + +#define DRD_CONFIG_DYNAMIC 0 +#define DRD_CONFIG_STATIC_HOST 1 +#define DRD_CONFIG_STATIC_DEVICE 2 +#define DRD_CONFIG_MASK 3 + +#define DUAL_ROLE_CFG1 0x6c +#define HOST_MODE BIT(29) + +#define DUAL_ROLE_CFG1_POLL_TIMEOUT 1000 + +#define DRV_NAME "intel_xhci_usb_sw" + +struct intel_xhci_usb_data { + struct device *dev; + struct usb_role_switch *role_sw; + void __iomem *base; + bool enable_sw_switch; +}; + +static const struct software_node intel_xhci_usb_node = { + "intel-xhci-usb-sw", +}; + +static int intel_xhci_usb_set_role(struct usb_role_switch *sw, + enum usb_role role) +{ + struct intel_xhci_usb_data *data = usb_role_switch_get_drvdata(sw); + unsigned long timeout; + acpi_status status; + u32 glk, val; + u32 drd_config = DRD_CONFIG_DYNAMIC; + + /* + * On many CHT devices ACPI event (_AEI) handlers read / modify / + * write the cfg0 register, just like we do. Take the ACPI lock + * to avoid us racing with the AML code. + */ + status = acpi_acquire_global_lock(ACPI_WAIT_FOREVER, &glk); + if (ACPI_FAILURE(status) && status != AE_NOT_CONFIGURED) { + dev_err(data->dev, "Error could not acquire lock\n"); + return -EIO; + } + + pm_runtime_get_sync(data->dev); + + /* + * Set idpin value as requested. + * Since some devices rely on firmware setting DRD_CONFIG and + * SW_SWITCH_EN bits to be zero for role switch, + * do not set these bits for those devices. + */ + val = readl(data->base + DUAL_ROLE_CFG0); + switch (role) { + case USB_ROLE_NONE: + val |= SW_IDPIN; + val &= ~SW_VBUS_VALID; + drd_config = DRD_CONFIG_DYNAMIC; + break; + case USB_ROLE_HOST: + val &= ~SW_IDPIN; + val &= ~SW_VBUS_VALID; + drd_config = DRD_CONFIG_STATIC_HOST; + break; + case USB_ROLE_DEVICE: + val |= SW_IDPIN; + val |= SW_VBUS_VALID; + drd_config = DRD_CONFIG_STATIC_DEVICE; + break; + } + val |= SW_IDPIN_EN; + if (data->enable_sw_switch) { + val &= ~DRD_CONFIG_MASK; + val |= SW_SWITCH_EN | drd_config; + } + writel(val, data->base + DUAL_ROLE_CFG0); + + acpi_release_global_lock(glk); + + /* In most case it takes about 600ms to finish mode switching */ + timeout = jiffies + msecs_to_jiffies(DUAL_ROLE_CFG1_POLL_TIMEOUT); + + /* Polling on CFG1 register to confirm mode switch.*/ + do { + val = readl(data->base + DUAL_ROLE_CFG1); + if (!!(val & HOST_MODE) == (role == USB_ROLE_HOST)) { + pm_runtime_put(data->dev); + return 0; + } + + /* Interval for polling is set to about 5 - 10 ms */ + usleep_range(5000, 10000); + } while (time_before(jiffies, timeout)); + + pm_runtime_put(data->dev); + + dev_warn(data->dev, "Timeout waiting for role-switch\n"); + return -ETIMEDOUT; +} + +static enum usb_role intel_xhci_usb_get_role(struct usb_role_switch *sw) +{ + struct intel_xhci_usb_data *data = usb_role_switch_get_drvdata(sw); + enum usb_role role; + u32 val; + + pm_runtime_get_sync(data->dev); + val = readl(data->base + DUAL_ROLE_CFG0); + pm_runtime_put(data->dev); + + if (!(val & SW_IDPIN)) + role = USB_ROLE_HOST; + else if (val & SW_VBUS_VALID) + role = USB_ROLE_DEVICE; + else + role = USB_ROLE_NONE; + + return role; +} + +static int intel_xhci_usb_probe(struct platform_device *pdev) +{ + struct usb_role_switch_desc sw_desc = { }; + struct device *dev = &pdev->dev; + struct intel_xhci_usb_data *data; + struct resource *res; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + data->base = devm_ioremap(dev, res->start, resource_size(res)); + if (!data->base) + return -ENOMEM; + + platform_set_drvdata(pdev, data); + + ret = software_node_register(&intel_xhci_usb_node); + if (ret) + return ret; + + sw_desc.set = intel_xhci_usb_set_role, + sw_desc.get = intel_xhci_usb_get_role, + sw_desc.allow_userspace_control = true, + sw_desc.fwnode = software_node_fwnode(&intel_xhci_usb_node); + sw_desc.driver_data = data; + + data->dev = dev; + data->enable_sw_switch = !device_property_read_bool(dev, + "sw_switch_disable"); + + data->role_sw = usb_role_switch_register(dev, &sw_desc); + if (IS_ERR(data->role_sw)) { + fwnode_handle_put(sw_desc.fwnode); + return PTR_ERR(data->role_sw); + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} + +static void intel_xhci_usb_remove(struct platform_device *pdev) +{ + struct intel_xhci_usb_data *data = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + usb_role_switch_unregister(data->role_sw); + fwnode_handle_put(software_node_fwnode(&intel_xhci_usb_node)); +} + +static const struct platform_device_id intel_xhci_usb_table[] = { + { .name = DRV_NAME }, + {} +}; +MODULE_DEVICE_TABLE(platform, intel_xhci_usb_table); + +static struct platform_driver intel_xhci_usb_driver = { + .driver = { + .name = DRV_NAME, + }, + .id_table = intel_xhci_usb_table, + .probe = intel_xhci_usb_probe, + .remove = intel_xhci_usb_remove, +}; + +module_platform_driver(intel_xhci_usb_driver); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("Intel XHCI USB role switch driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig new file mode 100644 index 000000000..ef8d1c73c --- /dev/null +++ b/drivers/usb/serial/Kconfig @@ -0,0 +1,656 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# 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_SIMPLE + tristate "USB Serial Simple Driver" + help + Say Y here to use the USB serial "simple" driver. This driver + handles a wide range of very simple devices, all in one + driver. Specifically, it supports: + - Suunto ANT+ USB device. + - Medtronic CareLink USB device + - Fundamental Software dongle. + - Google USB serial devices + - HP4x calculators + - Libtransistor USB console + - a number of Motorola phones + - Motorola Tetra devices + - Nokia mobile phones + - Novatel Wireless GPS receivers + - Siemens USB/MPI adapter. + - ViVOtech ViVOpay USB device. + - Infineon Modem Flashloader USB interface + - ZIO Motherboard USB serial interface + + To compile this driver as a module, choose M here: the module + will be called usb-serial-simple. + +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. + + 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 been 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_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_F8153X + tristate "USB Fintek F81532/534 Multi-Ports Serial Driver" + help + Say Y here if you want to use the Fintek F81532/534 Multi-Ports + USB to serial adapter. + + To compile this driver as a module, choose M here: the + module will be called f81534. + + +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 / Xircom Single Port Serial Driver" + select USB_EZUSB_FX2 + help + Say Y here if you want to use a Keyspan PDA, Xircom or Entrega 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_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_MXUPORT + tristate "USB Moxa UPORT Serial Driver" + help + Say Y here if you want to use a MOXA UPort Serial hub. + + This driver supports: + + [2 Port] + - UPort 1250 : 2 Port RS-232/422/485 USB to Serial Hub + - UPort 1250I : 2 Port RS-232/422/485 USB to Serial Hub with + Isolation + + [4 Port] + - UPort 1410 : 4 Port RS-232 USB to Serial Hub + - UPort 1450 : 4 Port RS-232/422/485 USB to Serial Hub + - UPort 1450I : 4 Port RS-232/422/485 USB to Serial Hub with + Isolation + + [8 Port] + - UPort 1610-8 : 8 Port RS-232 USB to Serial Hub + - UPort 1650-8 : 8 Port RS-232/422/485 USB to Serial Hub + + [16 Port] + - UPort 1610-16 : 16 Port RS-232 USB to Serial Hub + - UPort 1650-16 : 16 Port RS-232/422/485 USB to Serial Hub + + To compile this driver as a module, choose M here: the + module will be called mxuport. + +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_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_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_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_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_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_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_UPD78F0730 + tristate "USB Renesas uPD78F0730 Single Port Serial Driver" + help + Say Y here if you want to use the Renesas uPD78F0730 + serial driver. + + To compile this driver as a module, choose M here: the + module will be called upd78f0730. + +config USB_SERIAL_XR + tristate "USB MaxLinear/Exar USB to Serial driver" + help + Say Y here if you want to use MaxLinear/Exar USB to Serial converter + devices. + + To compile this driver as a module, choose M here: the + module will be called xr_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 000000000..c7bb1a881 --- /dev/null +++ b/drivers/usb/serial/Makefile @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# 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_F8153X) += f81534.o +obj-$(CONFIG_USB_SERIAL_FTDI_SIO) += ftdi_sio.o +obj-$(CONFIG_USB_SERIAL_GARMIN) += garmin_gps.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_MXUPORT) += mxuport.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_SIERRAWIRELESS) += sierra.o +obj-$(CONFIG_USB_SERIAL_SIMPLE) += usb-serial-simple.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_UPD78F0730) += upd78f0730.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_XR) += xr_serial.o +obj-$(CONFIG_USB_SERIAL_XSENS_MT) += xsens_mt.o diff --git a/drivers/usb/serial/Makefile-keyspan_pda_fw b/drivers/usb/serial/Makefile-keyspan_pda_fw new file mode 100644 index 000000000..503b472d8 --- /dev/null +++ b/drivers/usb/serial/Makefile-keyspan_pda_fw @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 + +# 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 000000000..2a76f1f0e --- /dev/null +++ b/drivers/usb/serial/aircable.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AIRcable USB Bluetooth Dongle Driver. + * + * Copyright (C) 2010 Johan Hovold + * Copyright (C) 2006 Manuel Francisco Naranjo (naranjo.manuel@gmail.com) + * + * 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 possibilities 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 sent. + * 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. + * + * 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 knowledge in drivers development + * and made it all available 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_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + /* Ignore the first interface, which has no bulk endpoints. */ + if (epds->num_bulk_out == 0) { + dev_dbg(&serial->interface->dev, + "ignoring interface with no bulk-out endpoints\n"); + return -ENODEV; + } + + return 1; +} + +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 = 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 = { + .name = "aircable", + }, + .id_table = id_table, + .bulk_out_size = HCI_COMPLETE_FRAME, + .calc_num_ports = aircable_calc_num_ports, + .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 v2"); diff --git a/drivers/usb/serial/ark3116.c b/drivers/usb/serial/ark3116.c new file mode 100644 index 000000000..3a574b6ee --- /dev/null +++ b/drivers/usb/serial/ark3116.c @@ -0,0 +1,733 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#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 control 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); + if (result) + return result; + + return 0; +} + +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 < 1) { + dev_err(&serial->interface->dev, + "failed to read register %u: %d\n", + reg, result); + if (result >= 0) + result = -EIO; + + return result; + } + + return 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_port_probe(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct ark3116_private *priv; + + priv = kzalloc_obj(*priv); + 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(&port->dev, "using %s mode\n", priv->irda ? "IrDA" : "RS232"); + + return 0; +} + +static void 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); +} + +static void ark3116_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const 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 */ + lcr = UART_LCR_WLEN(tty_get_char_size(cflag)); + + if (cflag & CSTOPB) + lcr |= UART_LCR_STOP; + if (cflag & PARENB) + lcr |= UART_LCR_PARITY; + if (!(cflag & PARODD)) + lcr |= UART_LCR_EPAR; + if (cflag & CMSPAR) + lcr |= UART_LCR_SPAR; + + /* 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(&port->dev, + "software flow control not implemented\n"); + } + + /* 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); + + 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_free; + } + + /* remove any data still left: also clears error state */ + ark3116_read_reg(serial, UART_RX, buf); + + /* read modem status */ + result = ark3116_read_reg(serial, UART_MSR, buf); + if (result) + goto err_close; + priv->msr = *buf; + + /* read line status */ + result = ark3116_read_reg(serial, UART_LSR, buf); + if (result) + goto err_close; + priv->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); + goto err_close; + } + + /* 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); + + kfree(buf); + + return 0; + +err_close: + usb_serial_generic_close(port); +err_free: + kfree(buf); + + return result; +} + +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 int 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); + int ret; + + /* 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; + + ret = ark3116_write_reg(port->serial, UART_LCR, priv->lcr); + + mutex_unlock(&priv->hw_lock); + + return ret; +} + +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(&port->dev, "failed to resubmit interrupt urb: %d\n", + result); +} + + +/* Data comes in via the bulk (data) URB, errors/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 = { + .name = "ark3116", + }, + .id_table = id_table, + .num_ports = 1, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 1, + .port_probe = ark3116_port_probe, + .port_remove = ark3116_port_remove, + .set_termios = ark3116_set_termios, + .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 + * https://lore.kernel.org/lkml/200907261419.50702.linux@rainbow-software.org + * 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 000000000..cfff29263 --- /dev/null +++ b/drivers/usb/serial/belkin_sa.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * See Documentation/usb/usb-serial.rst for more information on using this + * driver + * + * TODO: + * -- Add true modem control 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 "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 void 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, + const struct ktermios *old_termios); +static int 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 = { + .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_obj(struct belkin_sa_private); + 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 void belkin_sa_port_remove(struct usb_serial_port *port) +{ + struct belkin_sa_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); +} + +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); + + if (urb->actual_length < BELKIN_SA_MSR_INDEX + 1) + goto exit; + + /* 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, + const 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)) { + urb_value = BELKIN_SA_DATA_BITS(tty_get_char_size(cflag)); + 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 int 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; + int ret; + + ret = BSA_USB_CMD(BELKIN_SA_SET_BREAK_REQUEST, break_state ? 1 : 0); + if (ret < 0) { + dev_err(&port->dev, "Set break_ctl %d\n", break_state); + return ret; + } + + return 0; +} + +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 = 0; + + 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); + + if ((set | clear) & TIOCM_RTS) { + retval = BSA_USB_CMD(BELKIN_SA_SET_RTS_REQUEST, + !!(control_state & TIOCM_RTS)); + if (retval < 0) { + dev_err(&port->dev, "Set RTS error %d\n", retval); + goto exit; + } + } + + if ((set | clear) & TIOCM_DTR) { + retval = BSA_USB_CMD(BELKIN_SA_SET_DTR_REQUEST, + !!(control_state & TIOCM_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 000000000..89ec30c63 --- /dev/null +++ b/drivers/usb/serial/belkin_sa.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + * + * See Documentation/usb/usb-serial.rst 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 000000000..9e2a18c0b --- /dev/null +++ b/drivers/usb/serial/bus.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Serial Converter Bus specific functions + * + * Copyright (C) 2002 Greg Kroah-Hartman (greg@kroah.com) + */ + +#include +#include +#include +#include +#include +#include +#include + +static int usb_serial_device_match(struct device *dev, + const struct device_driver *drv) +{ + const struct usb_serial_port *port = to_usb_serial_port(dev); + const struct usb_serial_driver *driver = to_usb_serial_driver(drv); + + /* + * drivers are already assigned to ports in serial_probe so it's + * a simple check here. + */ + if (driver == port->serial->type) + return 1; + + return 0; +} + +static int usb_serial_device_probe(struct device *dev) +{ + struct usb_serial_port *port = to_usb_serial_port(dev); + struct usb_serial_driver *driver; + struct device *tty_dev; + int retval = 0; + int minor; + + /* make sure suspend/resume doesn't race against port_probe */ + retval = usb_autopm_get_interface(port->serial->interface); + if (retval) + return retval; + + driver = port->serial->type; + if (driver->port_probe) { + retval = driver->port_probe(port); + if (retval) + goto err_autopm_put; + } + + minor = port->minor; + tty_dev = tty_port_register_device(&port->port, usb_serial_tty_driver, + minor, dev); + if (IS_ERR(tty_dev)) { + retval = PTR_ERR(tty_dev); + goto err_port_remove; + } + + usb_autopm_put_interface(port->serial->interface); + + dev_info(&port->serial->dev->dev, + "%s converter now attached to ttyUSB%d\n", + driver->description, minor); + + return 0; + +err_port_remove: + if (driver->port_remove) + driver->port_remove(port); +err_autopm_put: + usb_autopm_put_interface(port->serial->interface); + + return retval; +} + +static void usb_serial_device_remove(struct device *dev) +{ + struct usb_serial_port *port = to_usb_serial_port(dev); + struct usb_serial_driver *driver; + int minor; + int autopm_err; + + /* + * 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->minor; + tty_unregister_device(usb_serial_tty_driver, minor); + + driver = port->serial->type; + if (driver->port_remove) + 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); +} + +static ssize_t new_id_store(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, usb_drv->id_table, + 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->id_table, + &usb_drv->usb_driver->driver, + buf, count); + return retval; +} + +static ssize_t new_id_show(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 DRIVER_ATTR_RW(new_id); + +static struct attribute *usb_serial_drv_attrs[] = { + &driver_attr_new_id.attr, + NULL, +}; +ATTRIBUTE_GROUPS(usb_serial_drv); + +static void free_dynids(struct usb_serial_driver *drv) +{ + struct usb_dynid *dynid, *n; + + guard(mutex)(&usb_dynids_lock); + list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) { + list_del(&dynid->node); + kfree(dynid); + } +} + +const 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_groups = usb_serial_drv_groups, +}; + +int usb_serial_bus_register(struct usb_serial_driver *driver) +{ + int retval; + + driver->driver.bus = &usb_serial_bus_type; + 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 000000000..569f4aede --- /dev/null +++ b/drivers/usb/serial/ch341.c @@ -0,0 +1,893 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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. + */ + +#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 */ + +/* 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_READ_VERSION 0x5F +#define CH341_REQ_WRITE_REG 0x9A +#define CH341_REQ_READ_REG 0x95 +#define CH341_REQ_SERIAL_INIT 0xA1 +#define CH341_REQ_MODEM_CTRL 0xA4 + +#define CH341_REG_BREAK 0x05 +#define CH341_REG_PRESCALER 0x12 +#define CH341_REG_DIVISOR 0x13 +#define CH341_REG_LCR 0x18 +#define CH341_REG_LCR2 0x25 +#define CH341_REG_FLOW_CTL 0x27 + +#define CH341_NBREAK_BITS 0x01 + +#define CH341_LCR_ENABLE_RX 0x80 +#define CH341_LCR_ENABLE_TX 0x40 +#define CH341_LCR_MARK_SPACE 0x20 +#define CH341_LCR_PAR_EVEN 0x10 +#define CH341_LCR_ENABLE_PAR 0x08 +#define CH341_LCR_STOP_BITS_2 0x04 +#define CH341_LCR_CS8 0x03 +#define CH341_LCR_CS7 0x02 +#define CH341_LCR_CS6 0x01 +#define CH341_LCR_CS5 0x00 + +#define CH341_FLOW_CTL_NONE 0x00 +#define CH341_FLOW_CTL_RTSCTS 0x01 + +#define CH341_QUIRK_LIMITED_PRESCALER BIT(0) +#define CH341_QUIRK_SIMULATE_BREAK BIT(1) + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x1a86, 0x5523) }, + { USB_DEVICE(0x1a86, 0x7522) }, + { USB_DEVICE(0x1a86, 0x7523) }, + { USB_DEVICE(0x2184, 0x0057) }, + { USB_DEVICE(0x4348, 0x5523) }, + { USB_DEVICE(0x9986, 0x7523) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +struct ch341_private { + spinlock_t lock; /* access lock */ + unsigned baud_rate; /* set baud rate */ + u8 mcr; + u8 msr; + u8 lcr; + + unsigned long quirks; + u8 version; + + unsigned long break_end; +}; + +static void ch341_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios); + +static int ch341_control_out(struct usb_device *dev, u8 request, + u16 value, u16 index) +{ + int r; + + dev_dbg(&dev->dev, "%s - (%02x,%04x,%04x)\n", __func__, + request, value, 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); + if (r < 0) + dev_err(&dev->dev, "failed to send control message: %d\n", r); + + 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, "%s - (%02x,%04x,%04x,%u)\n", __func__, + request, value, index, bufsize); + + r = usb_control_msg_recv(dev, 0, request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + value, index, buf, bufsize, DEFAULT_TIMEOUT, + GFP_KERNEL); + if (r) { + dev_err(&dev->dev, "failed to receive control message: %d\n", + r); + return r; + } + + return 0; +} + +#define CH341_CLKRATE 48000000 +#define CH341_CLK_DIV(ps, fact) (1 << (12 - 3 * (ps) - (fact))) +#define CH341_MIN_RATE(ps) (CH341_CLKRATE / (CH341_CLK_DIV((ps), 1) * 512)) + +static const speed_t ch341_min_rates[] = { + CH341_MIN_RATE(0), + CH341_MIN_RATE(1), + CH341_MIN_RATE(2), + CH341_MIN_RATE(3), +}; + +/* Supported range is 46 to 3000000 bps. */ +#define CH341_MIN_BPS DIV_ROUND_UP(CH341_CLKRATE, CH341_CLK_DIV(0, 0) * 256) +#define CH341_MAX_BPS (CH341_CLKRATE / (CH341_CLK_DIV(3, 0) * 2)) + +/* + * The device line speed is given by the following equation: + * + * baudrate = 48000000 / (2^(12 - 3 * ps - fact) * div), where + * + * 0 <= ps <= 3, + * 0 <= fact <= 1, + * 2 <= div <= 256 if fact = 0, or + * 9 <= div <= 256 if fact = 1 + */ +static int ch341_get_divisor(struct ch341_private *priv, speed_t speed) +{ + unsigned int fact, div, clk_div; + bool force_fact0 = false; + int ps; + + /* + * Clamp to supported range, this makes the (ps < 0) and (div < 2) + * sanity checks below redundant. + */ + speed = clamp_val(speed, CH341_MIN_BPS, CH341_MAX_BPS); + + /* + * Start with highest possible base clock (fact = 1) that will give a + * divisor strictly less than 512. + */ + fact = 1; + for (ps = 3; ps >= 0; ps--) { + if (speed > ch341_min_rates[ps]) + break; + } + + if (ps < 0) + return -EINVAL; + + /* Determine corresponding divisor, rounding down. */ + clk_div = CH341_CLK_DIV(ps, fact); + div = CH341_CLKRATE / (clk_div * speed); + + /* Some devices require a lower base clock if ps < 3. */ + if (ps < 3 && (priv->quirks & CH341_QUIRK_LIMITED_PRESCALER)) + force_fact0 = true; + + /* Halve base clock (fact = 0) if required. */ + if (div < 9 || div > 255 || force_fact0) { + div /= 2; + clk_div *= 2; + fact = 0; + } + + if (div < 2) + return -EINVAL; + + /* + * Pick next divisor if resulting rate is closer to the requested one, + * scale up to avoid rounding errors on low rates. + */ + if (16 * CH341_CLKRATE / (clk_div * div) - 16 * speed >= + 16 * speed - 16 * CH341_CLKRATE / (clk_div * (div + 1))) + div++; + + /* + * Prefer lower base clock (fact = 0) if even divisor. + * + * Note that this makes the receiver more tolerant to errors. + */ + if (fact == 1 && div % 2 == 0) { + div /= 2; + fact = 0; + } + + return (0x100 - div) << 8 | fact << 2 | ps; +} + +static int ch341_set_baudrate_lcr(struct usb_device *dev, + struct ch341_private *priv, + speed_t baud_rate, u8 lcr) +{ + int val; + int r; + + if (!baud_rate) + return -EINVAL; + + val = ch341_get_divisor(priv, baud_rate); + if (val < 0) + return -EINVAL; + + /* + * CH341A buffers data until a full endpoint-size packet (32 bytes) + * has been received unless bit 7 is set. + * + * At least one device with version 0x27 appears to have this bit + * inverted. + */ + if (priv->version > 0x27) + val |= BIT(7); + + r = ch341_control_out(dev, CH341_REQ_WRITE_REG, + CH341_REG_DIVISOR << 8 | CH341_REG_PRESCALER, + val); + if (r) + return r; + + /* + * Chip versions before version 0x30 as read using + * CH341_REQ_READ_VERSION used separate registers for line control + * (stop bits, parity and word length). Version 0x30 and above use + * CH341_REG_LCR only and CH341_REG_LCR2 is always set to zero. + */ + if (priv->version < 0x30) + return 0; + + r = ch341_control_out(dev, CH341_REQ_WRITE_REG, + CH341_REG_LCR2 << 8 | CH341_REG_LCR, lcr); + if (r) + return r; + + return r; +} + +static int ch341_set_handshake(struct usb_device *dev, u8 control) +{ + return ch341_control_out(dev, CH341_REQ_MODEM_CTRL, ~control, 0); +} + +static int ch341_get_status(struct usb_device *dev, struct ch341_private *priv) +{ + const unsigned int size = 2; + u8 buffer[2]; + int r; + unsigned long flags; + + r = ch341_control_in(dev, CH341_REQ_READ_REG, 0x0706, 0, buffer, size); + if (r) + return r; + + spin_lock_irqsave(&priv->lock, flags); + priv->msr = (~(*buffer)) & CH341_BITS_MODEM_STAT; + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +static int ch341_configure(struct usb_device *dev, struct ch341_private *priv) +{ + const unsigned int size = 2; + u8 buffer[2]; + int r; + + /* expect two bytes 0x27 0x00 */ + r = ch341_control_in(dev, CH341_REQ_READ_VERSION, 0, 0, buffer, size); + if (r) + return r; + + priv->version = buffer[0]; + dev_dbg(&dev->dev, "Chip version: 0x%02x\n", priv->version); + + r = ch341_control_out(dev, CH341_REQ_SERIAL_INIT, 0, 0); + if (r < 0) + return r; + + r = ch341_set_baudrate_lcr(dev, priv, priv->baud_rate, priv->lcr); + if (r < 0) + return r; + + r = ch341_set_handshake(dev, priv->mcr); + if (r < 0) + return r; + + return 0; +} + +static int ch341_detect_quirks(struct usb_serial_port *port) +{ + struct ch341_private *priv = usb_get_serial_port_data(port); + struct usb_device *udev = port->serial->dev; + const unsigned int size = 2; + unsigned long quirks = 0; + u8 buffer[2]; + int r; + + /* + * A subset of CH34x devices does not support all features. The + * prescaler is limited and there is no support for sending a RS232 + * break condition. A read failure when trying to set up the latter is + * used to detect these devices. + */ + r = usb_control_msg_recv(udev, 0, CH341_REQ_READ_REG, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + CH341_REG_BREAK, 0, &buffer, size, + DEFAULT_TIMEOUT, GFP_KERNEL); + if (r == -EPIPE) { + dev_info(&port->dev, "break control not supported, using simulated break\n"); + quirks = CH341_QUIRK_LIMITED_PRESCALER | CH341_QUIRK_SIMULATE_BREAK; + r = 0; + } else if (r) { + dev_err(&port->dev, "failed to read break control: %d\n", r); + } + + if (quirks) { + dev_dbg(&port->dev, "enabling quirk flags: 0x%02lx\n", quirks); + priv->quirks |= quirks; + } + + return r; +} + +static int ch341_port_probe(struct usb_serial_port *port) +{ + struct ch341_private *priv; + int r; + + priv = kzalloc_obj(struct ch341_private); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + priv->baud_rate = DEFAULT_BAUD_RATE; + /* + * Some CH340 devices appear unable to change the initial LCR + * settings, so set a sane 8N1 default. + */ + priv->lcr = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX | CH341_LCR_CS8; + + r = ch341_configure(port->serial->dev, priv); + if (r < 0) + goto error; + + usb_set_serial_port_data(port, priv); + + r = ch341_detect_quirks(port); + if (r < 0) + goto error; + + return 0; + +error: kfree(priv); + return r; +} + +static void ch341_port_remove(struct usb_serial_port *port) +{ + struct ch341_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); +} + +static int ch341_carrier_raised(struct usb_serial_port *port) +{ + struct ch341_private *priv = usb_get_serial_port_data(port); + if (priv->msr & 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->mcr |= CH341_BIT_RTS | CH341_BIT_DTR; + else + priv->mcr &= ~(CH341_BIT_RTS | CH341_BIT_DTR); + spin_unlock_irqrestore(&priv->lock, flags); + ch341_set_handshake(port->serial->dev, priv->mcr); +} + +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 ch341_private *priv = usb_get_serial_port_data(port); + int r; + + if (tty) + ch341_set_termios(tty, port, NULL); + + dev_dbg(&port->dev, "%s - submitting interrupt urb\n", __func__); + r = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (r) { + dev_err(&port->dev, "%s - failed to submit interrupt urb: %d\n", + __func__, r); + return r; + } + + r = ch341_get_status(port->serial->dev, priv); + if (r < 0) { + dev_err(&port->dev, "failed to read modem status: %d\n", r); + goto err_kill_interrupt_urb; + } + + r = usb_serial_generic_open(tty, port); + if (r) + goto err_kill_interrupt_urb; + + return 0; + +err_kill_interrupt_urb: + usb_kill_urb(port->interrupt_in_urb); + + return r; +} + +static void ch341_set_flow_control(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +{ + u16 flow_ctl; + int r; + + if (C_CRTSCTS(tty)) + flow_ctl = CH341_FLOW_CTL_RTSCTS; + else + flow_ctl = CH341_FLOW_CTL_NONE; + + r = ch341_control_out(port->serial->dev, + CH341_REQ_WRITE_REG, + (CH341_REG_FLOW_CTL << 8) | CH341_REG_FLOW_CTL, + (flow_ctl << 8) | flow_ctl); + if (r < 0 && old_termios) { + tty->termios.c_cflag &= ~CRTSCTS; + tty->termios.c_cflag |= (old_termios->c_cflag & CRTSCTS); + } +} + +/* 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, + const struct ktermios *old_termios) +{ + struct ch341_private *priv = usb_get_serial_port_data(port); + unsigned baud_rate; + unsigned long flags; + u8 lcr; + int r; + + /* redundant changes may cause the chip to lose bytes */ + if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios)) + return; + + baud_rate = tty_get_baud_rate(tty); + + lcr = CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX; + + switch (C_CSIZE(tty)) { + case CS5: + lcr |= CH341_LCR_CS5; + break; + case CS6: + lcr |= CH341_LCR_CS6; + break; + case CS7: + lcr |= CH341_LCR_CS7; + break; + case CS8: + lcr |= CH341_LCR_CS8; + break; + } + + if (C_PARENB(tty)) { + lcr |= CH341_LCR_ENABLE_PAR; + if (C_PARODD(tty) == 0) + lcr |= CH341_LCR_PAR_EVEN; + if (C_CMSPAR(tty)) + lcr |= CH341_LCR_MARK_SPACE; + } + + if (C_CSTOPB(tty)) + lcr |= CH341_LCR_STOP_BITS_2; + + if (baud_rate) { + priv->baud_rate = baud_rate; + + r = ch341_set_baudrate_lcr(port->serial->dev, priv, + priv->baud_rate, lcr); + if (r < 0 && old_termios) { + priv->baud_rate = tty_termios_baud_rate(old_termios); + tty_termios_copy_hw(&tty->termios, old_termios); + } else if (r == 0) { + priv->lcr = lcr; + } + } + + spin_lock_irqsave(&priv->lock, flags); + if (C_BAUD(tty) == B0) + priv->mcr &= ~(CH341_BIT_DTR | CH341_BIT_RTS); + else if (old_termios && (old_termios->c_cflag & CBAUD) == B0) + priv->mcr |= (CH341_BIT_DTR | CH341_BIT_RTS); + spin_unlock_irqrestore(&priv->lock, flags); + + ch341_set_handshake(port->serial->dev, priv->mcr); + + ch341_set_flow_control(tty, port, old_termios); +} + +/* + * A subset of all CH34x devices don't support a real break condition and + * reading CH341_REG_BREAK fails (see also ch341_detect_quirks). This function + * simulates a break condition by lowering the baud rate to the minimum + * supported by the hardware upon enabling the break condition and sending + * a NUL byte. + * + * Incoming data is corrupted while the break condition is being simulated. + * + * Normally the duration of the break condition can be controlled individually + * by userspace using TIOCSBRK and TIOCCBRK or by passing an argument to + * TCSBRKP. Due to how the simulation is implemented the duration can't be + * controlled. The duration is always about (1s / 46bd * 9bit) = 196ms. + */ +static int ch341_simulate_break(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct ch341_private *priv = usb_get_serial_port_data(port); + unsigned long now, delay; + int r, r2; + + if (break_state != 0) { + dev_dbg(&port->dev, "enter break state requested\n"); + + r = ch341_set_baudrate_lcr(port->serial->dev, priv, + CH341_MIN_BPS, + CH341_LCR_ENABLE_RX | CH341_LCR_ENABLE_TX | CH341_LCR_CS8); + if (r < 0) { + dev_err(&port->dev, + "failed to change baud rate to %u: %d\n", + CH341_MIN_BPS, r); + goto restore; + } + + r = tty_put_char(tty, '\0'); + if (r < 0) { + dev_err(&port->dev, + "failed to write NUL byte for simulated break condition: %d\n", + r); + goto restore; + } + + /* + * Compute expected transmission duration including safety + * margin. The original baud rate is only restored after the + * computed point in time. + * + * 11 bits = 1 start, 8 data, 1 stop, 1 margin + */ + priv->break_end = jiffies + (11 * HZ / CH341_MIN_BPS); + + return 0; + } + + dev_dbg(&port->dev, "leave break state requested\n"); + + now = jiffies; + + if (time_before(now, priv->break_end)) { + /* Wait until NUL byte is written */ + delay = priv->break_end - now; + dev_dbg(&port->dev, + "wait %d ms while transmitting NUL byte at %u baud\n", + jiffies_to_msecs(delay), CH341_MIN_BPS); + schedule_timeout_interruptible(delay); + } + + r = 0; +restore: + /* Restore original baud rate */ + r2 = ch341_set_baudrate_lcr(port->serial->dev, priv, priv->baud_rate, + priv->lcr); + if (r2 < 0) { + dev_err(&port->dev, + "restoring original baud rate of %u failed: %d\n", + priv->baud_rate, r2); + return r2; + } + + return r; +} + +static int ch341_break_ctl(struct tty_struct *tty, int break_state) +{ + const u16 ch341_break_reg = (CH341_REG_LCR << 8) | CH341_REG_BREAK; + struct usb_serial_port *port = tty->driver_data; + struct ch341_private *priv = usb_get_serial_port_data(port); + u16 reg_contents; + u8 break_reg[2]; + int r; + + if (priv->quirks & CH341_QUIRK_SIMULATE_BREAK) + return ch341_simulate_break(tty, break_state); + + r = ch341_control_in(port->serial->dev, CH341_REQ_READ_REG, + ch341_break_reg, 0, break_reg, 2); + if (r) { + dev_err(&port->dev, "%s - USB control read error (%d)\n", + __func__, r); + if (r > 0) + r = -EIO; + return r; + } + 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; + break_reg[1] &= ~CH341_LCR_ENABLE_TX; + } else { + dev_dbg(&port->dev, "%s - Leave break state requested\n", __func__); + break_reg[0] |= CH341_NBREAK_BITS; + break_reg[1] |= CH341_LCR_ENABLE_TX; + } + 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); + return r; + } + + return 0; +} + +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->mcr |= CH341_BIT_RTS; + if (set & TIOCM_DTR) + priv->mcr |= CH341_BIT_DTR; + if (clear & TIOCM_RTS) + priv->mcr &= ~CH341_BIT_RTS; + if (clear & TIOCM_DTR) + priv->mcr &= ~CH341_BIT_DTR; + control = priv->mcr; + spin_unlock_irqrestore(&priv->lock, flags); + + return ch341_set_handshake(port->serial->dev, control); +} + +static void ch341_update_status(struct usb_serial_port *port, + unsigned char *data, size_t len) +{ + struct ch341_private *priv = usb_get_serial_port_data(port); + struct tty_struct *tty; + unsigned long flags; + u8 status; + u8 delta; + + if (len < 4) + return; + + status = ~data[2] & CH341_BITS_MODEM_STAT; + + spin_lock_irqsave(&priv->lock, flags); + delta = status ^ priv->msr; + priv->msr = status; + spin_unlock_irqrestore(&priv->lock, flags); + + if (data[1] & CH341_MULT_STAT) + dev_dbg(&port->dev, "%s - multiple status change\n", __func__); + + if (!delta) + return; + + if (delta & CH341_BIT_CTS) + port->icount.cts++; + if (delta & CH341_BIT_DSR) + port->icount.dsr++; + if (delta & CH341_BIT_RI) + port->icount.rng++; + if (delta & CH341_BIT_DCD) { + port->icount.dcd++; + tty = tty_port_tty_get(&port->port); + if (tty) { + usb_serial_handle_dcd_change(port, tty, + status & CH341_BIT_DCD); + tty_kref_put(tty); + } + } + + wake_up_interruptible(&port->port.delta_msr_wait); +} + +static void ch341_read_int_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + unsigned int len = 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: %d\n", + __func__, urb->status); + return; + default: + dev_dbg(&urb->dev->dev, "%s - nonzero urb status: %d\n", + __func__, urb->status); + goto exit; + } + + usb_serial_debug_data(&port->dev, __func__, len, data); + ch341_update_status(port, data, len); +exit: + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + dev_err(&urb->dev->dev, "%s - usb_submit_urb failed: %d\n", + __func__, status); + } +} + +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->mcr; + status = priv->msr; + 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 usb_serial_port *port = serial->port[0]; + struct ch341_private *priv; + int ret; + + priv = usb_get_serial_port_data(port); + if (!priv) + return 0; + + /* reconfigure ch341 serial port after bus-reset */ + ch341_configure(serial->dev, priv); + + if (tty_port_initialized(&port->port)) { + ret = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO); + if (ret) { + dev_err(&port->dev, "failed to submit interrupt urb: %d\n", + ret); + return ret; + } + + ret = ch341_get_status(port->serial->dev, priv); + if (ret < 0) { + dev_err(&port->dev, "failed to read modem status: %d\n", + ret); + } + } + + return usb_serial_generic_resume(serial); +} + +static struct usb_serial_driver ch341_device = { + .driver = { + .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 = usb_serial_generic_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_DESCRIPTION("Winchiphead CH341 USB Serial driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/console.c b/drivers/usb/serial/console.c new file mode 100644 index 000000000..29f091908 --- /dev/null +++ b/drivers/usb/serial/console.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Serial Console driver + * + * Copyright (C) 2001 - 2002 Greg Kroah-Hartman (greg@kroah.com) + * + * 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. + * ------------------------------------------------------------ + */ + +static const struct tty_operations usb_console_fake_tty_ops = { +}; + +/* + * 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; + } + + if (doflow) + cflag |= CRTSCTS; + + /* + * no need to check the index here: if the index is wrong, console + * code won't call us + */ + port = usb_serial_port_get_by_minor(co->index); + if (port == NULL) { + /* no device is connected yet, sorry :( */ + pr_err("No USB device connected to ttyUSB%i\n", co->index); + return -ENODEV; + } + serial = port->serial; + + retval = usb_autopm_get_interface(serial->interface); + if (retval) + goto error_get_interface; + + tty_port_tty_set(&port->port, NULL); + + info->port = port; + + ++port->port.count; + if (!tty_port_initialized(&port->port)) { + 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_obj(*tty); + if (!tty) { + retval = -ENOMEM; + goto reset_open_count; + } + kref_init(&tty->kref); + tty->driver = usb_serial_tty_driver; + tty->index = co->index; + init_ldsem(&tty->ldisc_sem); + spin_lock_init(&tty->files_lock); + INIT_LIST_HEAD(&tty->tty_files); + kref_get(&tty->driver->kref); + __module_get(tty->driver->owner); + tty->ops = &usb_console_fake_tty_ops; + tty_init_termios(tty); + tty_port_tty_set(&port->port, tty); + } + + /* only call the device specific open if this + * is the first time the port is opened */ + retval = serial->type->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); + tty_save_termios(tty); + tty_kref_put(tty); + } + tty_port_set_initialized(&port->port, true); + } + /* 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); + tty_kref_put(tty); + reset_open_count: + port->port.count = 0; + info->port = NULL; + usb_autopm_put_interface(serial->interface); + error_get_interface: + mutex_unlock(&serial->disc_mutex); + usb_serial_put(serial); + 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; + + dev_dbg(&port->dev, "%s - %d byte(s)\n", __func__, count); + + if (!port->port.console) { + dev_dbg(&port->dev, "%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 */ + retval = serial->type->write(NULL, port, buf, i); + dev_dbg(&port->dev, "%s - write: %d\n", __func__, retval); + if (lf) { + /* append CR after LF */ + unsigned char cr = 13; + retval = serial->type->write(NULL, port, &cr, 1); + dev_dbg(&port->dev, "%s - write cr: %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->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 000000000..333b8b463 --- /dev/null +++ b/drivers/usb/serial/cp210x.c @@ -0,0 +1,2201 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Silicon Laboratories CP210x USB to RS232 serial adaptor driver + * + * Copyright (C) 2005 Craig Shelley (craig@microtron.org.uk) + * Copyright (C) 2010-2021 Johan Hovold (johan@kernel.org) + * + * 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 +#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_change_speed(struct tty_struct *, struct usb_serial_port *, + const struct ktermios *); +static void cp210x_set_termios(struct tty_struct *, struct usb_serial_port *, + const struct ktermios *); +static bool cp210x_tx_empty(struct usb_serial_port *port); +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 int cp210x_break_ctl(struct tty_struct *, int); +static int cp210x_attach(struct usb_serial *); +static void cp210x_disconnect(struct usb_serial *); +static void cp210x_release(struct usb_serial *); +static int cp210x_port_probe(struct usb_serial_port *); +static void cp210x_port_remove(struct usb_serial_port *); +static void cp210x_dtr_rts(struct usb_serial_port *port, int on); +static void cp210x_process_read_urb(struct urb *urb); +static void cp210x_enable_event_mode(struct usb_serial_port *port); +static void cp210x_disable_event_mode(struct usb_serial_port *port); + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x0404, 0x034C) }, /* NCR Retail IO Box */ + { 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(0x04BF, 0x1301) }, /* TDK Corporation NC0110013M - Network Controller */ + { USB_DEVICE(0x04BF, 0x1303) }, /* TDK Corporation MM0110113M - i3 Micro Module */ + { 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(0x0908, 0x0070) }, /* Siemens SCALANCE LPE-9000 USB Serial Console */ + { USB_DEVICE(0x0908, 0x01FF) }, /* Siemens RUGGEDCOM USB Serial Console */ + { USB_DEVICE(0x0988, 0x0578) }, /* Teraoka AD2000 */ + { USB_DEVICE(0x0B00, 0x3070) }, /* Ingenico 3070 */ + { 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(0x106F, 0x0003) }, /* CPI / Money Controls Bulk Coin Recycler */ + { 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, 0x8056) }, /* Lorenz Messtechnik devices */ + { 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, 0x817C) }, /* CESINEL MEDCAL N Power Quality Monitor */ + { USB_DEVICE(0x10C4, 0x817D) }, /* CESINEL MEDCAL NT Power Quality Monitor */ + { USB_DEVICE(0x10C4, 0x817E) }, /* CESINEL MEDCAL S Power Quality Monitor */ + { 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, 0x81D7) }, /* IAI Corp. RCB-CV-USB USB to RS485 Adaptor */ + { 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, 0x82AA) }, /* Silicon Labs IFS-USB-DATACABLE used with Quint UPS */ + { USB_DEVICE(0x10C4, 0x82EF) }, /* CESINEL FALCO 6105 AC Power Supply */ + { USB_DEVICE(0x10C4, 0x82F1) }, /* CESINEL MEDCAL EFD Earth Fault Detector */ + { USB_DEVICE(0x10C4, 0x82F2) }, /* CESINEL MEDCAL ST Network Analyzer */ + { USB_DEVICE(0x10C4, 0x82F4) }, /* Starizona MicroTouch */ + { 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, 0x83AA) }, /* Mark-10 Digital Force Gauge */ + { USB_DEVICE(0x10C4, 0x83D8) }, /* DekTec DTA Plus VHF/UHF Booster/Attenuator */ + { USB_DEVICE(0x10C4, 0x8411) }, /* Kyocera GPS Module */ + { USB_DEVICE(0x10C4, 0x8414) }, /* Decagon USB Cable Adapter */ + { USB_DEVICE(0x10C4, 0x8418) }, /* IRZ Automation Teleport SG-10 GSM/GPRS Modem */ + { USB_DEVICE(0x10C4, 0x846E) }, /* BEI USB Sensor Interface (VCP) */ + { USB_DEVICE(0x10C4, 0x8470) }, /* Juniper Networks BX Series System Console */ + { USB_DEVICE(0x10C4, 0x8477) }, /* Balluff RFID */ + { USB_DEVICE(0x10C4, 0x84B6) }, /* Starizona Hyperion */ + { USB_DEVICE(0x10C4, 0x851E) }, /* CESINEL MEDCAL PT Network Analyzer */ + { USB_DEVICE(0x10C4, 0x85A7) }, /* LifeScan OneTouch Verio IQ */ + { USB_DEVICE(0x10C4, 0x85B8) }, /* CESINEL ReCon T Energy Logger */ + { 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, 0x863C) }, /* MGP Instruments PDS100 */ + { USB_DEVICE(0x10C4, 0x8664) }, /* AC-Services CAN-IF */ + { USB_DEVICE(0x10C4, 0x8665) }, /* AC-Services OBD-IF */ + { USB_DEVICE(0x10C4, 0x87ED) }, /* IMST USB-Stick for Smart Meter */ + { USB_DEVICE(0x10C4, 0x8856) }, /* CEL EM357 ZigBee USB Stick - LR */ + { USB_DEVICE(0x10C4, 0x8857) }, /* CEL EM357 ZigBee USB Stick */ + { USB_DEVICE(0x10C4, 0x88A4) }, /* MMB Networks ZigBee USB Device */ + { USB_DEVICE(0x10C4, 0x88A5) }, /* Planet Innovation Ingeni ZigBee USB Device */ + { USB_DEVICE(0x10C4, 0x88D8) }, /* Acuity Brands nLight Air Adapter */ + { USB_DEVICE(0x10C4, 0x88FB) }, /* CESINEL MEDCAL STII Network Analyzer */ + { USB_DEVICE(0x10C4, 0x8938) }, /* CESINEL MEDCAL S II Network Analyzer */ + { USB_DEVICE(0x10C4, 0x8946) }, /* Ketra N1 Wireless Interface */ + { USB_DEVICE(0x10C4, 0x8962) }, /* Brim Brothers charging dock */ + { USB_DEVICE(0x10C4, 0x8977) }, /* CEL MeshWorks DevKit Device */ + { USB_DEVICE(0x10C4, 0x8998) }, /* KCF Technologies PRN */ + { USB_DEVICE(0x10C4, 0x89A4) }, /* CESINEL FTBC Flexible Thyristor Bridge Controller */ + { USB_DEVICE(0x10C4, 0x89FB) }, /* Qivicon ZigBee USB Radio Stick */ + { USB_DEVICE(0x10C4, 0x8A2A) }, /* HubZ dual ZigBee and Z-Wave dongle */ + { USB_DEVICE(0x10C4, 0x8A5B) }, /* CEL EM3588 ZigBee USB Stick */ + { USB_DEVICE(0x10C4, 0x8A5E) }, /* CEL EM3588 ZigBee USB Stick Long Range */ + { USB_DEVICE(0x10C4, 0x8B34) }, /* Qivicon ZigBee USB Radio Stick */ + { USB_DEVICE(0x10C4, 0xEA60) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0xEA61) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0xEA63) }, /* Silicon Labs Windows Update (CP2101-4/CP2102N) */ + { USB_DEVICE(0x10C4, 0xEA70) }, /* Silicon Labs factory default */ + { USB_DEVICE(0x10C4, 0xEA71) }, /* Infinity GPS-MIC-1 Radio Monophone */ + { USB_DEVICE(0x10C4, 0xEA7A) }, /* Silicon Labs Windows Update (CP2105) */ + { USB_DEVICE(0x10C4, 0xEA7B) }, /* Silicon Labs Windows Update (CP2108) */ + { 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(0x11CA, 0x0212) }, /* Verifone USB to Printer (UART, CP2102) */ + { USB_DEVICE(0x12B8, 0xEC60) }, /* Link G4 ECU */ + { USB_DEVICE(0x12B8, 0xEC62) }, /* Link G4+ ECU */ + { USB_DEVICE(0x13AD, 0x9999) }, /* Baltech card reader */ + { USB_DEVICE(0x1555, 0x0004) }, /* Owen AC4 USB-RS485 Converter */ + { USB_DEVICE(0x155A, 0x1006) }, /* ELDAT Easywave RX09 */ + { 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(0x16C0, 0x09B0) }, /* Lunatico Seletek */ + { USB_DEVICE(0x16C0, 0x09B1) }, /* Lunatico Seletek */ + { 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(0x17A8, 0x0011) }, /* Kamstrup 444 MHz RF sniffer */ + { USB_DEVICE(0x17A8, 0x0013) }, /* Kamstrup 870 MHz RF sniffer */ + { USB_DEVICE(0x17A8, 0x0101) }, /* Kamstrup 868 MHz wM-Bus C-Mode Meter Reader (Int Ant) */ + { USB_DEVICE(0x17A8, 0x0102) }, /* Kamstrup 868 MHz wM-Bus C-Mode Meter Reader (Ext Ant) */ + { 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(0x18EF, 0xE025) }, /* ELV Marble Sound Board 1 */ + { USB_DEVICE(0x18EF, 0xE030) }, /* ELV ALC 8xxx Battery Charger */ + { USB_DEVICE(0x18EF, 0xE032) }, /* ELV TFD500 Data Logger */ + { USB_DEVICE(0x1901, 0x0190) }, /* GE B850 CP2105 Recorder interface */ + { USB_DEVICE(0x1901, 0x0193) }, /* GE B650 CP2104 PMC interface */ + { USB_DEVICE(0x1901, 0x0194) }, /* GE Healthcare Remote Alarm Box */ + { USB_DEVICE(0x1901, 0x0195) }, /* GE B850/B650/B450 CP2104 DP UART interface */ + { USB_DEVICE(0x1901, 0x0196) }, /* GE B850 CP2105 DP UART interface */ + { USB_DEVICE(0x1901, 0x0197) }, /* GE CS1000 M.2 Key E serial interface */ + { USB_DEVICE(0x1901, 0x0198) }, /* GE CS1000 Display serial interface */ + { USB_DEVICE(0x199B, 0xBA30) }, /* LORD WSDA-200-USB */ + { USB_DEVICE(0x19CF, 0x3000) }, /* Parrot NMEA GPS Flight Recorder */ + { USB_DEVICE(0x1ADB, 0x0001) }, /* Schweitzer Engineering C662 Cable */ + { USB_DEVICE(0x1B1C, 0x1C00) }, /* Corsair USB Dongle */ + { USB_DEVICE(0x1B93, 0x1013) }, /* Phoenix Contact UPS Device */ + { 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(0x2184, 0x0030) }, /* GW Instek GDM-834x Digital Multimeter */ + { USB_DEVICE(0x2626, 0xEA60) }, /* Aruba Networks 7xxx USB Serial Console */ + { 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(0x3923, 0x7A0B) }, /* National Instruments USB Serial Console */ + { USB_DEVICE(0x413C, 0x9500) }, /* DW700 GPS USB interface */ + { } /* Terminating Entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +struct cp210x_serial_private { +#ifdef CONFIG_GPIOLIB + struct gpio_chip gc; + bool gpio_registered; + u16 gpio_pushpull; + u16 gpio_altfunc; + u16 gpio_input; +#endif + u8 partnum; + u32 fw_version; + speed_t min_speed; + speed_t max_speed; + bool use_actual_rate; + bool no_flow_control; + bool no_event_mode; +}; + +enum cp210x_event_state { + ES_DATA, + ES_ESCAPE, + ES_LSR, + ES_LSR_DATA_0, + ES_LSR_DATA_1, + ES_MSR +}; + +struct cp210x_port_private { + u8 bInterfaceNumber; + bool event_mode; + enum cp210x_event_state event_state; + u8 lsr; + + struct mutex mutex; + bool crtscts; + bool dtr; + bool rts; +}; + +static struct usb_serial_driver cp210x_device = { + .driver = { + .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, + .tx_empty = cp210x_tx_empty, + .throttle = usb_serial_generic_throttle, + .unthrottle = usb_serial_generic_unthrottle, + .tiocmget = cp210x_tiocmget, + .tiocmset = cp210x_tiocmset, + .get_icount = usb_serial_generic_get_icount, + .attach = cp210x_attach, + .disconnect = cp210x_disconnect, + .release = cp210x_release, + .port_probe = cp210x_port_probe, + .port_remove = cp210x_port_remove, + .dtr_rts = cp210x_dtr_rts, + .process_read_urb = cp210x_process_read_urb, +}; + +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 +#define CP210X_VENDOR_SPECIFIC 0xFF + +/* 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|SET)_CHARS */ +struct cp210x_special_chars { + u8 bEofChar; + u8 bErrorChar; + u8 bBreakChar; + u8 bEventChar; + u8 bXonChar; + u8 bXoffChar; +}; + +/* CP210X_VENDOR_SPECIFIC values */ +#define CP210X_GET_FW_VER 0x000E +#define CP210X_READ_2NCONFIG 0x000E +#define CP210X_GET_FW_VER_2N 0x0010 +#define CP210X_READ_LATCH 0x00C2 +#define CP210X_GET_PARTNUM 0x370B +#define CP210X_GET_PORTCONFIG 0x370C +#define CP210X_GET_DEVICEMODE 0x3711 +#define CP210X_WRITE_LATCH 0x37E1 + +/* Part number definitions */ +#define CP210X_PARTNUM_CP2101 0x01 +#define CP210X_PARTNUM_CP2102 0x02 +#define CP210X_PARTNUM_CP2103 0x03 +#define CP210X_PARTNUM_CP2104 0x04 +#define CP210X_PARTNUM_CP2105 0x05 +#define CP210X_PARTNUM_CP2108 0x08 +#define CP210X_PARTNUM_CP2102N_QFN28 0x20 +#define CP210X_PARTNUM_CP2102N_QFN24 0x21 +#define CP210X_PARTNUM_CP2102N_QFN20 0x22 +#define CP210X_PARTNUM_UNKNOWN 0xFF + +/* CP210X_GET_COMM_STATUS returns these 0x13 bytes */ +struct cp210x_comm_status { + __le32 ulErrors; + __le32 ulHoldReasons; + __le32 ulAmountInInQueue; + __le32 ulAmountInOutQueue; + u8 bEofReceived; + u8 bWaitForImmediate; + u8 bReserved; +} __packed; + +/* + * CP210X_PURGE - 16 bits passed in wValue of USB request. + * SiLabs app note AN571 gives a strange description of the 4 bits: + * bit 0 or bit 2 clears the transmit queue and 1 or 3 receive. + * writing 1 to all, however, purges cp2108 well enough to avoid the hang. + */ +#define PURGE_ALL 0x000f + +/* CP210X_EMBED_EVENTS */ +#define CP210X_ESCCHAR 0xec + +#define CP210X_LSR_OVERRUN BIT(1) +#define CP210X_LSR_PARITY BIT(2) +#define CP210X_LSR_FRAME BIT(3) +#define CP210X_LSR_BREAK BIT(4) + + +/* CP210X_GET_FLOW/CP210X_SET_FLOW read/write these 0x10 bytes */ +struct cp210x_flow_ctl { + __le32 ulControlHandshake; + __le32 ulFlowReplace; + __le32 ulXonLimit; + __le32 ulXoffLimit; +}; + +/* cp210x_flow_ctl::ulControlHandshake */ +#define CP210X_SERIAL_DTR_MASK GENMASK(1, 0) +#define CP210X_SERIAL_DTR_INACTIVE (0 << 0) +#define CP210X_SERIAL_DTR_ACTIVE (1 << 0) +#define CP210X_SERIAL_DTR_FLOW_CTL (2 << 0) +#define CP210X_SERIAL_CTS_HANDSHAKE BIT(3) +#define CP210X_SERIAL_DSR_HANDSHAKE BIT(4) +#define CP210X_SERIAL_DCD_HANDSHAKE BIT(5) +#define CP210X_SERIAL_DSR_SENSITIVITY BIT(6) + +/* cp210x_flow_ctl::ulFlowReplace */ +#define CP210X_SERIAL_AUTO_TRANSMIT BIT(0) +#define CP210X_SERIAL_AUTO_RECEIVE BIT(1) +#define CP210X_SERIAL_ERROR_CHAR BIT(2) +#define CP210X_SERIAL_NULL_STRIPPING BIT(3) +#define CP210X_SERIAL_BREAK_CHAR BIT(4) +#define CP210X_SERIAL_RTS_MASK GENMASK(7, 6) +#define CP210X_SERIAL_RTS_INACTIVE (0 << 6) +#define CP210X_SERIAL_RTS_ACTIVE (1 << 6) +#define CP210X_SERIAL_RTS_FLOW_CTL (2 << 6) +#define CP210X_SERIAL_XOFF_CONTINUE BIT(31) + +/* CP210X_VENDOR_SPECIFIC, CP210X_GET_DEVICEMODE call reads these 0x2 bytes. */ +struct cp210x_pin_mode { + u8 eci; + u8 sci; +}; + +#define CP210X_PIN_MODE_MODEM 0 +#define CP210X_PIN_MODE_GPIO BIT(0) + +/* + * CP210X_VENDOR_SPECIFIC, CP210X_GET_PORTCONFIG call reads these 0xf bytes + * on a CP2105 chip. Structure needs padding due to unused/unspecified bytes. + */ +struct cp210x_dual_port_config { + __le16 gpio_mode; + u8 __pad0[2]; + __le16 reset_state; + u8 __pad1[4]; + __le16 suspend_state; + u8 sci_cfg; + u8 eci_cfg; + u8 device_cfg; +} __packed; + +/* + * CP210X_VENDOR_SPECIFIC, CP210X_GET_PORTCONFIG call reads these 0xd bytes + * on a CP2104 chip. Structure needs padding due to unused/unspecified bytes. + */ +struct cp210x_single_port_config { + __le16 gpio_mode; + u8 __pad0[2]; + __le16 reset_state; + u8 __pad1[4]; + __le16 suspend_state; + u8 device_cfg; +} __packed; + +/* GPIO modes */ +#define CP210X_SCI_GPIO_MODE_OFFSET 9 +#define CP210X_SCI_GPIO_MODE_MASK GENMASK(11, 9) + +#define CP210X_ECI_GPIO_MODE_OFFSET 2 +#define CP210X_ECI_GPIO_MODE_MASK GENMASK(3, 2) + +#define CP210X_GPIO_MODE_OFFSET 8 +#define CP210X_GPIO_MODE_MASK GENMASK(11, 8) + +/* CP2105 port configuration values */ +#define CP2105_GPIO0_TXLED_MODE BIT(0) +#define CP2105_GPIO1_RXLED_MODE BIT(1) +#define CP2105_GPIO1_RS485_MODE BIT(2) + +/* CP2104 port configuration values */ +#define CP2104_GPIO0_TXLED_MODE BIT(0) +#define CP2104_GPIO1_RXLED_MODE BIT(1) +#define CP2104_GPIO2_RS485_MODE BIT(2) + +struct cp210x_quad_port_state { + __le16 gpio_mode_pb0; + __le16 gpio_mode_pb1; + __le16 gpio_mode_pb2; + __le16 gpio_mode_pb3; + __le16 gpio_mode_pb4; + + __le16 gpio_lowpower_pb0; + __le16 gpio_lowpower_pb1; + __le16 gpio_lowpower_pb2; + __le16 gpio_lowpower_pb3; + __le16 gpio_lowpower_pb4; + + __le16 gpio_latch_pb0; + __le16 gpio_latch_pb1; + __le16 gpio_latch_pb2; + __le16 gpio_latch_pb3; + __le16 gpio_latch_pb4; +}; + +/* + * CP210X_VENDOR_SPECIFIC, CP210X_GET_PORTCONFIG call reads these 0x49 bytes + * on a CP2108 chip. + * + * See https://www.silabs.com/documents/public/application-notes/an978-cp210x-usb-to-uart-api-specification.pdf + */ +struct cp210x_quad_port_config { + struct cp210x_quad_port_state reset_state; + struct cp210x_quad_port_state suspend_state; + u8 ipdelay_ifc[4]; + u8 enhancedfxn_ifc[4]; + u8 enhancedfxn_device; + u8 extclkfreq[4]; +} __packed; + +#define CP2108_EF_IFC_GPIO_TXLED 0x01 +#define CP2108_EF_IFC_GPIO_RXLED 0x02 +#define CP2108_EF_IFC_GPIO_RS485 0x04 +#define CP2108_EF_IFC_GPIO_RS485_LOGIC 0x08 +#define CP2108_EF_IFC_GPIO_CLOCK 0x10 +#define CP2108_EF_IFC_DYNAMIC_SUSPEND 0x40 + +/* CP2102N configuration array indices */ +#define CP210X_2NCONFIG_CONFIG_VERSION_IDX 2 +#define CP210X_2NCONFIG_GPIO_MODE_IDX 581 +#define CP210X_2NCONFIG_GPIO_RSTLATCH_IDX 587 +#define CP210X_2NCONFIG_GPIO_CONTROL_IDX 600 + +/* CP2102N QFN20 port configuration values */ +#define CP2102N_QFN20_GPIO2_TXLED_MODE BIT(2) +#define CP2102N_QFN20_GPIO3_RXLED_MODE BIT(3) +#define CP2102N_QFN20_GPIO1_RS485_MODE BIT(4) +#define CP2102N_QFN20_GPIO0_CLK_MODE BIT(6) + +/* + * CP210X_VENDOR_SPECIFIC, CP210X_WRITE_LATCH call writes these 0x02 bytes + * for CP2102N, CP2103, CP2104 and CP2105. + */ +struct cp210x_gpio_write { + u8 mask; + u8 state; +}; + +/* + * CP210X_VENDOR_SPECIFIC, CP210X_WRITE_LATCH call writes these 0x04 bytes + * for CP2108. + */ +struct cp210x_gpio_write16 { + __le16 mask; + __le16 state; +}; + +/* + * Helper to get interface number when we only have struct usb_serial. + */ +static u8 cp210x_interface_num(struct usb_serial *serial) +{ + struct usb_host_interface *cur_altsetting; + + cur_altsetting = serial->interface->cur_altsetting; + + return cur_altsetting->desc.bInterfaceNumber; +} + +/* + * Reads a variable-sized block of CP210X_ registers, identified by req. + * Returns data into buf in native USB byte order. + */ +static int cp210x_read_reg_block(struct usb_serial_port *port, u8 req, + void *buf, int bufsize) +{ + struct usb_serial *serial = port->serial; + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + int result; + + + result = usb_control_msg_recv(serial->dev, 0, req, + REQTYPE_INTERFACE_TO_HOST, 0, + port_priv->bInterfaceNumber, buf, bufsize, + USB_CTRL_SET_TIMEOUT, GFP_KERNEL); + if (result) { + dev_err(&port->dev, "failed get req 0x%x size %d status: %d\n", + req, bufsize, result); + return result; + } + + return 0; +} + +/* + * Reads any 8-bit CP210X_ register identified by req. + */ +static int cp210x_read_u8_reg(struct usb_serial_port *port, u8 req, u8 *val) +{ + return cp210x_read_reg_block(port, req, val, sizeof(*val)); +} + +/* + * Reads a variable-sized vendor block of CP210X_ registers, identified by val. + * Returns data into buf in native USB byte order. + */ +static int cp210x_read_vendor_block(struct usb_serial *serial, u8 type, u16 val, + void *buf, int bufsize) +{ + int result; + + result = usb_control_msg_recv(serial->dev, 0, CP210X_VENDOR_SPECIFIC, + type, val, cp210x_interface_num(serial), buf, bufsize, + USB_CTRL_GET_TIMEOUT, GFP_KERNEL); + if (result) { + dev_err(&serial->interface->dev, + "failed to get vendor val 0x%04x size %d: %d\n", val, + bufsize, result); + return result; + } + + return 0; +} + +/* + * Writes any 16-bit CP210X_ register (req) whose value is passed + * entirely in the wValue field of the USB request. + */ +static int cp210x_write_u16_reg(struct usb_serial_port *port, u8 req, u16 val) +{ + struct usb_serial *serial = port->serial; + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + int result; + + result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + req, REQTYPE_HOST_TO_INTERFACE, val, + port_priv->bInterfaceNumber, NULL, 0, + USB_CTRL_SET_TIMEOUT); + if (result < 0) { + dev_err(&port->dev, "failed set request 0x%x status: %d\n", + req, result); + } + + return result; +} + +/* + * Writes a variable-sized block of CP210X_ registers, identified by req. + * Data in buf must be in native USB byte order. + */ +static int cp210x_write_reg_block(struct usb_serial_port *port, u8 req, + void *buf, int bufsize) +{ + struct usb_serial *serial = port->serial; + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + int result; + + result = usb_control_msg_send(serial->dev, 0, req, + REQTYPE_HOST_TO_INTERFACE, 0, + port_priv->bInterfaceNumber, buf, bufsize, + USB_CTRL_SET_TIMEOUT, GFP_KERNEL); + if (result) { + dev_err(&port->dev, "failed set req 0x%x size %d status: %d\n", + req, bufsize, result); + return result; + } + + return 0; +} + +/* + * Writes any 32-bit CP210X_ register identified by req. + */ +static int cp210x_write_u32_reg(struct usb_serial_port *port, u8 req, u32 val) +{ + __le32 le32_val; + + le32_val = cpu_to_le32(val); + + return cp210x_write_reg_block(port, req, &le32_val, sizeof(le32_val)); +} + +#ifdef CONFIG_GPIOLIB +/* + * Writes a variable-sized vendor block of CP210X_ registers, identified by val. + * Data in buf must be in native USB byte order. + */ +static int cp210x_write_vendor_block(struct usb_serial *serial, u8 type, + u16 val, void *buf, int bufsize) +{ + int result; + + result = usb_control_msg_send(serial->dev, 0, CP210X_VENDOR_SPECIFIC, + type, val, cp210x_interface_num(serial), buf, bufsize, + USB_CTRL_SET_TIMEOUT, GFP_KERNEL); + if (result) { + dev_err(&serial->interface->dev, + "failed to set vendor val 0x%04x size %d: %d\n", val, + bufsize, result); + return result; + } + + return 0; +} +#endif + +static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + int result; + + result = cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_ENABLE); + if (result) { + dev_err(&port->dev, "%s - Unable to enable UART\n", __func__); + return result; + } + + if (tty) + cp210x_set_termios(tty, port, NULL); + + result = usb_serial_generic_open(tty, port); + if (result) + goto err_disable; + + return 0; + +err_disable: + cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_DISABLE); + port_priv->event_mode = false; + + return result; +} + +static void cp210x_close(struct usb_serial_port *port) +{ + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + + usb_serial_generic_close(port); + + /* Clear both queues; cp2108 needs this to avoid an occasional hang */ + cp210x_write_u16_reg(port, CP210X_PURGE, PURGE_ALL); + + cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_DISABLE); + + /* Disabling the interface disables event-insertion mode. */ + port_priv->event_mode = false; +} + +static void cp210x_process_lsr(struct usb_serial_port *port, unsigned char lsr, char *flag) +{ + if (lsr & CP210X_LSR_BREAK) { + port->icount.brk++; + *flag = TTY_BREAK; + } else if (lsr & CP210X_LSR_PARITY) { + port->icount.parity++; + *flag = TTY_PARITY; + } else if (lsr & CP210X_LSR_FRAME) { + port->icount.frame++; + *flag = TTY_FRAME; + } + + if (lsr & CP210X_LSR_OVERRUN) { + port->icount.overrun++; + tty_insert_flip_char(&port->port, 0, TTY_OVERRUN); + } +} + +static bool cp210x_process_char(struct usb_serial_port *port, unsigned char *ch, char *flag) +{ + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + + switch (port_priv->event_state) { + case ES_DATA: + if (*ch == CP210X_ESCCHAR) { + port_priv->event_state = ES_ESCAPE; + break; + } + return false; + case ES_ESCAPE: + switch (*ch) { + case 0: + dev_dbg(&port->dev, "%s - escape char\n", __func__); + *ch = CP210X_ESCCHAR; + port_priv->event_state = ES_DATA; + return false; + case 1: + port_priv->event_state = ES_LSR_DATA_0; + break; + case 2: + port_priv->event_state = ES_LSR; + break; + case 3: + port_priv->event_state = ES_MSR; + break; + default: + dev_err(&port->dev, "malformed event 0x%02x\n", *ch); + port_priv->event_state = ES_DATA; + break; + } + break; + case ES_LSR_DATA_0: + port_priv->lsr = *ch; + port_priv->event_state = ES_LSR_DATA_1; + break; + case ES_LSR_DATA_1: + dev_dbg(&port->dev, "%s - lsr = 0x%02x, data = 0x%02x\n", + __func__, port_priv->lsr, *ch); + cp210x_process_lsr(port, port_priv->lsr, flag); + port_priv->event_state = ES_DATA; + return false; + case ES_LSR: + dev_dbg(&port->dev, "%s - lsr = 0x%02x\n", __func__, *ch); + port_priv->lsr = *ch; + cp210x_process_lsr(port, port_priv->lsr, flag); + port_priv->event_state = ES_DATA; + break; + case ES_MSR: + dev_dbg(&port->dev, "%s - msr = 0x%02x\n", __func__, *ch); + /* unimplemented */ + port_priv->event_state = ES_DATA; + break; + } + + return true; +} + +static void cp210x_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + unsigned char *ch = urb->transfer_buffer; + char flag; + int i; + + if (!urb->actual_length) + return; + + if (port_priv->event_mode) { + for (i = 0; i < urb->actual_length; i++, ch++) { + flag = TTY_NORMAL; + + if (cp210x_process_char(port, ch, &flag)) + continue; + + tty_insert_flip_char(&port->port, *ch, flag); + } + } else { + tty_insert_flip_string(&port->port, ch, urb->actual_length); + } + tty_flip_buffer_push(&port->port); +} + +/* + * Read how many bytes are waiting in the TX queue. + */ +static int cp210x_get_tx_queue_byte_count(struct usb_serial_port *port, + u32 *count) +{ + struct usb_serial *serial = port->serial; + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + struct cp210x_comm_status sts; + int result; + + result = usb_control_msg_recv(serial->dev, 0, CP210X_GET_COMM_STATUS, + REQTYPE_INTERFACE_TO_HOST, 0, + port_priv->bInterfaceNumber, &sts, sizeof(sts), + USB_CTRL_GET_TIMEOUT, GFP_KERNEL); + if (result) { + dev_err(&port->dev, "failed to get comm status: %d\n", result); + return result; + } + + *count = le32_to_cpu(sts.ulAmountInOutQueue); + + return 0; +} + +static bool cp210x_tx_empty(struct usb_serial_port *port) +{ + int err; + u32 count; + + err = cp210x_get_tx_queue_byte_count(port, &count); + if (err) + return true; + + return !count; +} + +struct cp210x_rate { + speed_t rate; + speed_t high; +}; + +static const struct cp210x_rate cp210x_an205_table1[] = { + { 300, 300 }, + { 600, 600 }, + { 1200, 1200 }, + { 1800, 1800 }, + { 2400, 2400 }, + { 4000, 4000 }, + { 4800, 4803 }, + { 7200, 7207 }, + { 9600, 9612 }, + { 14400, 14428 }, + { 16000, 16062 }, + { 19200, 19250 }, + { 28800, 28912 }, + { 38400, 38601 }, + { 51200, 51558 }, + { 56000, 56280 }, + { 57600, 58053 }, + { 64000, 64111 }, + { 76800, 77608 }, + { 115200, 117028 }, + { 128000, 129347 }, + { 153600, 156868 }, + { 230400, 237832 }, + { 250000, 254234 }, + { 256000, 273066 }, + { 460800, 491520 }, + { 500000, 567138 }, + { 576000, 670254 }, + { 921600, UINT_MAX } +}; + +/* + * Quantises the baud rate as per AN205 Table 1 + */ +static speed_t cp210x_get_an205_rate(speed_t baud) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cp210x_an205_table1); ++i) { + if (baud <= cp210x_an205_table1[i].high) + break; + } + + return cp210x_an205_table1[i].rate; +} + +static speed_t cp210x_get_actual_rate(speed_t baud) +{ + unsigned int prescale = 1; + unsigned int div; + + if (baud <= 365) + prescale = 4; + + div = DIV_ROUND_CLOSEST(48000000, 2 * prescale * baud); + baud = 48000000 / (2 * prescale * div); + + return baud; +} + +/* + * 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, + const struct ktermios *old_termios) +{ + struct usb_serial *serial = port->serial; + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + u32 baud; + + if (tty->termios.c_ospeed == 0) + return; + + /* + * This maps the requested rate to the actual rate, a valid rate on + * cp2102 or cp2103, or to an arbitrary rate in [1M, max_speed]. + */ + baud = clamp(tty->termios.c_ospeed, priv->min_speed, priv->max_speed); + + if (priv->use_actual_rate) + baud = cp210x_get_actual_rate(baud); + else if (baud < 1000000) + baud = cp210x_get_an205_rate(baud); + + dev_dbg(&port->dev, "%s - setting baud rate to %u\n", __func__, baud); + if (cp210x_write_u32_reg(port, CP210X_SET_BAUDRATE, 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_enable_event_mode(struct usb_serial_port *port) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(port->serial); + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + int ret; + + if (port_priv->event_mode) + return; + + if (priv->no_event_mode) + return; + + port_priv->event_state = ES_DATA; + port_priv->event_mode = true; + + ret = cp210x_write_u16_reg(port, CP210X_EMBED_EVENTS, CP210X_ESCCHAR); + if (ret) { + dev_err(&port->dev, "failed to enable events: %d\n", ret); + port_priv->event_mode = false; + } +} + +static void cp210x_disable_event_mode(struct usb_serial_port *port) +{ + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + int ret; + + if (!port_priv->event_mode) + return; + + ret = cp210x_write_u16_reg(port, CP210X_EMBED_EVENTS, 0); + if (ret) { + dev_err(&port->dev, "failed to disable events: %d\n", ret); + return; + } + + port_priv->event_mode = false; +} + +static bool cp210x_termios_change(const struct ktermios *a, const struct ktermios *b) +{ + bool iflag_change, cc_change; + + iflag_change = ((a->c_iflag ^ b->c_iflag) & (INPCK | IXON | IXOFF)); + cc_change = a->c_cc[VSTART] != b->c_cc[VSTART] || + a->c_cc[VSTOP] != b->c_cc[VSTOP]; + + return tty_termios_hw_change(a, b) || iflag_change || cc_change; +} + +static void cp210x_set_flow_control(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(port->serial); + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + struct cp210x_special_chars chars; + struct cp210x_flow_ctl flow_ctl; + u32 flow_repl; + u32 ctl_hs; + bool crtscts; + int ret; + + /* + * Some CP2102N interpret ulXonLimit as ulFlowReplace (erratum + * CP2102N_E104). Report back that flow control is not supported. + */ + if (priv->no_flow_control) { + tty->termios.c_cflag &= ~CRTSCTS; + tty->termios.c_iflag &= ~(IXON | IXOFF); + } + + if (tty->termios.c_ospeed != 0 && + old_termios && old_termios->c_ospeed != 0 && + C_CRTSCTS(tty) == (old_termios->c_cflag & CRTSCTS) && + I_IXON(tty) == (old_termios->c_iflag & IXON) && + I_IXOFF(tty) == (old_termios->c_iflag & IXOFF) && + START_CHAR(tty) == old_termios->c_cc[VSTART] && + STOP_CHAR(tty) == old_termios->c_cc[VSTOP]) { + return; + } + + if (I_IXON(tty) || I_IXOFF(tty)) { + memset(&chars, 0, sizeof(chars)); + + chars.bXonChar = START_CHAR(tty); + chars.bXoffChar = STOP_CHAR(tty); + + ret = cp210x_write_reg_block(port, CP210X_SET_CHARS, &chars, + sizeof(chars)); + if (ret) { + dev_err(&port->dev, "failed to set special chars: %d\n", + ret); + } + } + + mutex_lock(&port_priv->mutex); + + if (tty->termios.c_ospeed == 0) { + port_priv->dtr = false; + port_priv->rts = false; + } else if (old_termios && old_termios->c_ospeed == 0) { + port_priv->dtr = true; + port_priv->rts = true; + } + + ret = cp210x_read_reg_block(port, CP210X_GET_FLOW, &flow_ctl, + sizeof(flow_ctl)); + if (ret) + goto out_unlock; + + ctl_hs = le32_to_cpu(flow_ctl.ulControlHandshake); + flow_repl = le32_to_cpu(flow_ctl.ulFlowReplace); + + ctl_hs &= ~CP210X_SERIAL_DSR_HANDSHAKE; + ctl_hs &= ~CP210X_SERIAL_DCD_HANDSHAKE; + ctl_hs &= ~CP210X_SERIAL_DSR_SENSITIVITY; + ctl_hs &= ~CP210X_SERIAL_DTR_MASK; + if (port_priv->dtr) + ctl_hs |= CP210X_SERIAL_DTR_ACTIVE; + else + ctl_hs |= CP210X_SERIAL_DTR_INACTIVE; + + flow_repl &= ~CP210X_SERIAL_RTS_MASK; + if (C_CRTSCTS(tty)) { + ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE; + if (port_priv->rts) + flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL; + else + flow_repl |= CP210X_SERIAL_RTS_INACTIVE; + crtscts = true; + } else { + ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE; + if (port_priv->rts) + flow_repl |= CP210X_SERIAL_RTS_ACTIVE; + else + flow_repl |= CP210X_SERIAL_RTS_INACTIVE; + crtscts = false; + } + + if (I_IXOFF(tty)) { + flow_repl |= CP210X_SERIAL_AUTO_RECEIVE; + + flow_ctl.ulXonLimit = cpu_to_le32(128); + flow_ctl.ulXoffLimit = cpu_to_le32(128); + } else { + flow_repl &= ~CP210X_SERIAL_AUTO_RECEIVE; + } + + if (I_IXON(tty)) + flow_repl |= CP210X_SERIAL_AUTO_TRANSMIT; + else + flow_repl &= ~CP210X_SERIAL_AUTO_TRANSMIT; + + dev_dbg(&port->dev, "%s - ctrl = 0x%02x, flow = 0x%02x\n", __func__, + ctl_hs, flow_repl); + + flow_ctl.ulControlHandshake = cpu_to_le32(ctl_hs); + flow_ctl.ulFlowReplace = cpu_to_le32(flow_repl); + + ret = cp210x_write_reg_block(port, CP210X_SET_FLOW, &flow_ctl, + sizeof(flow_ctl)); + if (ret) + goto out_unlock; + + port_priv->crtscts = crtscts; +out_unlock: + mutex_unlock(&port_priv->mutex); +} + +static void cp210x_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(port->serial); + u16 bits; + int ret; + + if (old_termios && !cp210x_termios_change(&tty->termios, old_termios) && + tty->termios.c_ospeed != 0) + return; + + if (!old_termios || tty->termios.c_ospeed != old_termios->c_ospeed) + cp210x_change_speed(tty, port, old_termios); + + /* CP2101 only supports CS8, 1 stop bit and non-stick parity. */ + if (priv->partnum == CP210X_PARTNUM_CP2101) { + tty->termios.c_cflag &= ~(CSIZE | CSTOPB | CMSPAR); + tty->termios.c_cflag |= CS8; + } + + bits = 0; + + switch (C_CSIZE(tty)) { + case CS5: + bits |= BITS_DATA_5; + break; + case CS6: + bits |= BITS_DATA_6; + break; + case CS7: + bits |= BITS_DATA_7; + break; + case CS8: + default: + bits |= BITS_DATA_8; + break; + } + + if (C_PARENB(tty)) { + if (C_CMSPAR(tty)) { + if (C_PARODD(tty)) + bits |= BITS_PARITY_MARK; + else + bits |= BITS_PARITY_SPACE; + } else { + if (C_PARODD(tty)) + bits |= BITS_PARITY_ODD; + else + bits |= BITS_PARITY_EVEN; + } + } + + if (C_CSTOPB(tty)) + bits |= BITS_STOP_2; + else + bits |= BITS_STOP_1; + + ret = cp210x_write_u16_reg(port, CP210X_SET_LINE_CTL, bits); + if (ret) + dev_err(&port->dev, "failed to set line control: %d\n", ret); + + cp210x_set_flow_control(tty, port, old_termios); + + /* + * Enable event-insertion mode only if input parity checking is + * enabled for now. + */ + if (I_INPCK(tty)) + cp210x_enable_event_mode(port); + else + cp210x_disable_event_mode(port); +} + +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) +{ + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + struct cp210x_flow_ctl flow_ctl; + u32 ctl_hs, flow_repl; + u16 control = 0; + int ret; + + mutex_lock(&port_priv->mutex); + + if (set & TIOCM_RTS) { + port_priv->rts = true; + control |= CONTROL_RTS; + control |= CONTROL_WRITE_RTS; + } + if (set & TIOCM_DTR) { + port_priv->dtr = true; + control |= CONTROL_DTR; + control |= CONTROL_WRITE_DTR; + } + if (clear & TIOCM_RTS) { + port_priv->rts = false; + control &= ~CONTROL_RTS; + control |= CONTROL_WRITE_RTS; + } + if (clear & TIOCM_DTR) { + port_priv->dtr = false; + control &= ~CONTROL_DTR; + control |= CONTROL_WRITE_DTR; + } + + /* + * Use SET_FLOW to set DTR and enable/disable auto-RTS when hardware + * flow control is enabled. + */ + if (port_priv->crtscts && control & CONTROL_WRITE_RTS) { + ret = cp210x_read_reg_block(port, CP210X_GET_FLOW, &flow_ctl, + sizeof(flow_ctl)); + if (ret) + goto out_unlock; + + ctl_hs = le32_to_cpu(flow_ctl.ulControlHandshake); + flow_repl = le32_to_cpu(flow_ctl.ulFlowReplace); + + ctl_hs &= ~CP210X_SERIAL_DTR_MASK; + if (port_priv->dtr) + ctl_hs |= CP210X_SERIAL_DTR_ACTIVE; + else + ctl_hs |= CP210X_SERIAL_DTR_INACTIVE; + + flow_repl &= ~CP210X_SERIAL_RTS_MASK; + if (port_priv->rts) + flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL; + else + flow_repl |= CP210X_SERIAL_RTS_INACTIVE; + + flow_ctl.ulControlHandshake = cpu_to_le32(ctl_hs); + flow_ctl.ulFlowReplace = cpu_to_le32(flow_repl); + + dev_dbg(&port->dev, "%s - ctrl = 0x%02x, flow = 0x%02x\n", + __func__, ctl_hs, flow_repl); + + ret = cp210x_write_reg_block(port, CP210X_SET_FLOW, &flow_ctl, + sizeof(flow_ctl)); + } else { + dev_dbg(&port->dev, "%s - control = 0x%04x\n", __func__, control); + + ret = cp210x_write_u16_reg(port, CP210X_SET_MHS, control); + } +out_unlock: + mutex_unlock(&port_priv->mutex); + + return ret; +} + +static void cp210x_dtr_rts(struct usb_serial_port *port, int on) +{ + if (on) + cp210x_tiocmset_port(port, TIOCM_DTR | TIOCM_RTS, 0); + else + cp210x_tiocmset_port(port, 0, TIOCM_DTR | TIOCM_RTS); +} + +static int cp210x_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + u8 control; + int result; + + result = cp210x_read_u8_reg(port, CP210X_GET_MDMSTS, &control); + if (result) + return result; + + 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%02x\n", __func__, control); + + return result; +} + +static int cp210x_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct cp210x_serial_private *priv = usb_get_serial_data(port->serial); + u16 state; + + if (priv->partnum == CP210X_PARTNUM_CP2105) { + if (cp210x_interface_num(port->serial) == 1) + return -ENOTTY; + } + + 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"); + + return cp210x_write_u16_reg(port, CP210X_SET_BREAK, state); +} + +#ifdef CONFIG_GPIOLIB +static int cp210x_gpio_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct usb_serial *serial = gpiochip_get_data(gc); + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + u8 req_type; + u16 mask; + int result; + int len; + + result = usb_autopm_get_interface(serial->interface); + if (result) + return result; + + switch (priv->partnum) { + case CP210X_PARTNUM_CP2105: + req_type = REQTYPE_INTERFACE_TO_HOST; + len = 1; + break; + case CP210X_PARTNUM_CP2108: + req_type = REQTYPE_INTERFACE_TO_HOST; + len = 2; + break; + default: + req_type = REQTYPE_DEVICE_TO_HOST; + len = 1; + break; + } + + mask = 0; + result = cp210x_read_vendor_block(serial, req_type, CP210X_READ_LATCH, + &mask, len); + + usb_autopm_put_interface(serial->interface); + + if (result < 0) + return result; + + le16_to_cpus(&mask); + + return !!(mask & BIT(gpio)); +} + +static int cp210x_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) +{ + struct usb_serial *serial = gpiochip_get_data(gc); + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + struct cp210x_gpio_write16 buf16; + struct cp210x_gpio_write buf; + u16 mask, state; + u16 wIndex; + int result; + + if (value == 1) + state = BIT(gpio); + else + state = 0; + + mask = BIT(gpio); + + result = usb_autopm_get_interface(serial->interface); + if (result) + goto out; + + switch (priv->partnum) { + case CP210X_PARTNUM_CP2105: + buf.mask = (u8)mask; + buf.state = (u8)state; + result = cp210x_write_vendor_block(serial, + REQTYPE_HOST_TO_INTERFACE, + CP210X_WRITE_LATCH, &buf, + sizeof(buf)); + break; + case CP210X_PARTNUM_CP2108: + buf16.mask = cpu_to_le16(mask); + buf16.state = cpu_to_le16(state); + result = cp210x_write_vendor_block(serial, + REQTYPE_HOST_TO_INTERFACE, + CP210X_WRITE_LATCH, &buf16, + sizeof(buf16)); + break; + default: + wIndex = state << 8 | mask; + result = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + CP210X_VENDOR_SPECIFIC, + REQTYPE_HOST_TO_DEVICE, + CP210X_WRITE_LATCH, + wIndex, + NULL, 0, USB_CTRL_SET_TIMEOUT); + break; + } + + usb_autopm_put_interface(serial->interface); +out: + if (result < 0) { + dev_err(&serial->interface->dev, "failed to set GPIO value: %d\n", + result); + return result; + } + + return 0; +} + +static int cp210x_gpio_direction_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct usb_serial *serial = gpiochip_get_data(gc); + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + + return priv->gpio_input & BIT(gpio); +} + +static int cp210x_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio) +{ + struct usb_serial *serial = gpiochip_get_data(gc); + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + + if (priv->partnum == CP210X_PARTNUM_CP2105) { + /* hardware does not support an input mode */ + return -ENOTSUPP; + } + + /* push-pull pins cannot be changed to be inputs */ + if (priv->gpio_pushpull & BIT(gpio)) + return -EINVAL; + + /* make sure to release pin if it is being driven low */ + cp210x_gpio_set(gc, gpio, 1); + + priv->gpio_input |= BIT(gpio); + + return 0; +} + +static int cp210x_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio, + int value) +{ + struct usb_serial *serial = gpiochip_get_data(gc); + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + + priv->gpio_input &= ~BIT(gpio); + + return cp210x_gpio_set(gc, gpio, value); +} + +static int cp210x_gpio_set_config(struct gpio_chip *gc, unsigned int gpio, + unsigned long config) +{ + struct usb_serial *serial = gpiochip_get_data(gc); + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + enum pin_config_param param = pinconf_to_config_param(config); + + /* Succeed only if in correct mode (this can't be set at runtime) */ + if ((param == PIN_CONFIG_DRIVE_PUSH_PULL) && + (priv->gpio_pushpull & BIT(gpio))) + return 0; + + if ((param == PIN_CONFIG_DRIVE_OPEN_DRAIN) && + !(priv->gpio_pushpull & BIT(gpio))) + return 0; + + return -ENOTSUPP; +} + +static int cp210x_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, unsigned int ngpios) +{ + struct usb_serial *serial = gpiochip_get_data(gc); + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + struct device *dev = &serial->interface->dev; + unsigned long altfunc_mask = priv->gpio_altfunc; + + bitmap_complement(valid_mask, &altfunc_mask, ngpios); + + if (bitmap_empty(valid_mask, ngpios)) + dev_dbg(dev, "no pin configured for GPIO\n"); + else + dev_dbg(dev, "GPIO.%*pbl configured for GPIO\n", ngpios, + valid_mask); + return 0; +} + +/* + * This function is for configuring GPIO using shared pins, where other signals + * are made unavailable by configuring the use of GPIO. This is believed to be + * only applicable to the cp2105 at this point, the other devices supported by + * this driver that provide GPIO do so in a way that does not impact other + * signals and are thus expected to have very different initialisation. + */ +static int cp2105_gpioconf_init(struct usb_serial *serial) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + struct cp210x_pin_mode mode; + struct cp210x_dual_port_config config; + u8 intf_num = cp210x_interface_num(serial); + u8 iface_config; + int result; + + result = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST, + CP210X_GET_DEVICEMODE, &mode, + sizeof(mode)); + if (result < 0) + return result; + + result = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST, + CP210X_GET_PORTCONFIG, &config, + sizeof(config)); + if (result < 0) + return result; + + /* 2 banks of GPIO - One for the pins taken from each serial port */ + if (intf_num == 0) { + priv->gc.ngpio = 2; + + if (mode.eci == CP210X_PIN_MODE_MODEM) { + /* mark all GPIOs of this interface as reserved */ + priv->gpio_altfunc = 0xff; + return 0; + } + + iface_config = config.eci_cfg; + priv->gpio_pushpull = (u8)((le16_to_cpu(config.gpio_mode) & + CP210X_ECI_GPIO_MODE_MASK) >> + CP210X_ECI_GPIO_MODE_OFFSET); + } else if (intf_num == 1) { + priv->gc.ngpio = 3; + + if (mode.sci == CP210X_PIN_MODE_MODEM) { + /* mark all GPIOs of this interface as reserved */ + priv->gpio_altfunc = 0xff; + return 0; + } + + iface_config = config.sci_cfg; + priv->gpio_pushpull = (u8)((le16_to_cpu(config.gpio_mode) & + CP210X_SCI_GPIO_MODE_MASK) >> + CP210X_SCI_GPIO_MODE_OFFSET); + } else { + return -ENODEV; + } + + /* mark all pins which are not in GPIO mode */ + if (iface_config & CP2105_GPIO0_TXLED_MODE) /* GPIO 0 */ + priv->gpio_altfunc |= BIT(0); + if (iface_config & (CP2105_GPIO1_RXLED_MODE | /* GPIO 1 */ + CP2105_GPIO1_RS485_MODE)) + priv->gpio_altfunc |= BIT(1); + + /* driver implementation for CP2105 only supports outputs */ + priv->gpio_input = 0; + + return 0; +} + +static int cp2104_gpioconf_init(struct usb_serial *serial) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + struct cp210x_single_port_config config; + u8 iface_config; + u8 gpio_latch; + int result; + u8 i; + + result = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST, + CP210X_GET_PORTCONFIG, &config, + sizeof(config)); + if (result < 0) + return result; + + priv->gc.ngpio = 4; + + iface_config = config.device_cfg; + priv->gpio_pushpull = (u8)((le16_to_cpu(config.gpio_mode) & + CP210X_GPIO_MODE_MASK) >> + CP210X_GPIO_MODE_OFFSET); + gpio_latch = (u8)((le16_to_cpu(config.reset_state) & + CP210X_GPIO_MODE_MASK) >> + CP210X_GPIO_MODE_OFFSET); + + /* mark all pins which are not in GPIO mode */ + if (iface_config & CP2104_GPIO0_TXLED_MODE) /* GPIO 0 */ + priv->gpio_altfunc |= BIT(0); + if (iface_config & CP2104_GPIO1_RXLED_MODE) /* GPIO 1 */ + priv->gpio_altfunc |= BIT(1); + if (iface_config & CP2104_GPIO2_RS485_MODE) /* GPIO 2 */ + priv->gpio_altfunc |= BIT(2); + + /* + * Like CP2102N, CP2104 has also no strict input and output pin + * modes. + * Do the same input mode emulation as CP2102N. + */ + for (i = 0; i < priv->gc.ngpio; ++i) { + /* + * Set direction to "input" iff pin is open-drain and reset + * value is 1. + */ + if (!(priv->gpio_pushpull & BIT(i)) && (gpio_latch & BIT(i))) + priv->gpio_input |= BIT(i); + } + + return 0; +} + +static int cp2108_gpio_init(struct usb_serial *serial) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + struct cp210x_quad_port_config config; + u16 gpio_latch; + int result; + u8 i; + + result = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST, + CP210X_GET_PORTCONFIG, &config, + sizeof(config)); + if (result < 0) + return result; + + priv->gc.ngpio = 16; + priv->gpio_pushpull = le16_to_cpu(config.reset_state.gpio_mode_pb1); + gpio_latch = le16_to_cpu(config.reset_state.gpio_latch_pb1); + + /* + * Mark all pins which are not in GPIO mode. + * + * Refer to table 9.1 "GPIO Mode alternate Functions" in the datasheet: + * https://www.silabs.com/documents/public/data-sheets/cp2108-datasheet.pdf + * + * Alternate functions of GPIO0 to GPIO3 are determine by enhancedfxn_ifc[0] + * and the similarly for the other pins; enhancedfxn_ifc[1]: GPIO4 to GPIO7, + * enhancedfxn_ifc[2]: GPIO8 to GPIO11, enhancedfxn_ifc[3]: GPIO12 to GPIO15. + */ + for (i = 0; i < 4; i++) { + if (config.enhancedfxn_ifc[i] & CP2108_EF_IFC_GPIO_TXLED) + priv->gpio_altfunc |= BIT(i * 4); + if (config.enhancedfxn_ifc[i] & CP2108_EF_IFC_GPIO_RXLED) + priv->gpio_altfunc |= BIT((i * 4) + 1); + if (config.enhancedfxn_ifc[i] & CP2108_EF_IFC_GPIO_RS485) + priv->gpio_altfunc |= BIT((i * 4) + 2); + if (config.enhancedfxn_ifc[i] & CP2108_EF_IFC_GPIO_CLOCK) + priv->gpio_altfunc |= BIT((i * 4) + 3); + } + + /* + * Like CP2102N, CP2108 has also no strict input and output pin + * modes. Do the same input mode emulation as CP2102N. + */ + for (i = 0; i < priv->gc.ngpio; ++i) { + /* + * Set direction to "input" iff pin is open-drain and reset + * value is 1. + */ + if (!(priv->gpio_pushpull & BIT(i)) && (gpio_latch & BIT(i))) + priv->gpio_input |= BIT(i); + } + + return 0; +} + +static int cp2102n_gpioconf_init(struct usb_serial *serial) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + const u16 config_size = 0x02a6; + u8 gpio_rst_latch; + u8 config_version; + u8 gpio_pushpull; + u8 *config_buf; + u8 gpio_latch; + u8 gpio_ctrl; + int result; + u8 i; + + /* + * Retrieve device configuration from the device. + * The array received contains all customization settings done at the + * factory/manufacturer. Format of the array is documented at the + * time of writing at: + * https://www.silabs.com/community/interface/knowledge-base.entry.html/2017/03/31/cp2102n_setconfig-xsfa + */ + config_buf = kmalloc(config_size, GFP_KERNEL); + if (!config_buf) + return -ENOMEM; + + result = cp210x_read_vendor_block(serial, + REQTYPE_DEVICE_TO_HOST, + CP210X_READ_2NCONFIG, + config_buf, + config_size); + if (result < 0) { + kfree(config_buf); + return result; + } + + config_version = config_buf[CP210X_2NCONFIG_CONFIG_VERSION_IDX]; + gpio_pushpull = config_buf[CP210X_2NCONFIG_GPIO_MODE_IDX]; + gpio_ctrl = config_buf[CP210X_2NCONFIG_GPIO_CONTROL_IDX]; + gpio_rst_latch = config_buf[CP210X_2NCONFIG_GPIO_RSTLATCH_IDX]; + + kfree(config_buf); + + /* Make sure this is a config format we understand. */ + if (config_version != 0x01) + return -ENOTSUPP; + + priv->gc.ngpio = 4; + + /* + * Get default pin states after reset. Needed so we can determine + * the direction of an open-drain pin. + */ + gpio_latch = (gpio_rst_latch >> 3) & 0x0f; + + /* 0 indicates open-drain mode, 1 is push-pull */ + priv->gpio_pushpull = (gpio_pushpull >> 3) & 0x0f; + + /* 0 indicates GPIO mode, 1 is alternate function */ + if (priv->partnum == CP210X_PARTNUM_CP2102N_QFN20) { + /* QFN20 is special... */ + if (gpio_ctrl & CP2102N_QFN20_GPIO0_CLK_MODE) /* GPIO 0 */ + priv->gpio_altfunc |= BIT(0); + if (gpio_ctrl & CP2102N_QFN20_GPIO1_RS485_MODE) /* GPIO 1 */ + priv->gpio_altfunc |= BIT(1); + if (gpio_ctrl & CP2102N_QFN20_GPIO2_TXLED_MODE) /* GPIO 2 */ + priv->gpio_altfunc |= BIT(2); + if (gpio_ctrl & CP2102N_QFN20_GPIO3_RXLED_MODE) /* GPIO 3 */ + priv->gpio_altfunc |= BIT(3); + } else { + priv->gpio_altfunc = (gpio_ctrl >> 2) & 0x0f; + } + + if (priv->partnum == CP210X_PARTNUM_CP2102N_QFN28) { + /* + * For the QFN28 package, GPIO4-6 are controlled by + * the low three bits of the mode/latch fields. + * Contrary to the document linked above, the bits for + * the SUSPEND pins are elsewhere. No alternate + * function is available for these pins. + */ + priv->gc.ngpio = 7; + gpio_latch |= (gpio_rst_latch & 7) << 4; + priv->gpio_pushpull |= (gpio_pushpull & 7) << 4; + } + + /* + * The CP2102N does not strictly has input and output pin modes, + * it only knows open-drain and push-pull modes which is set at + * factory. An open-drain pin can function both as an + * input or an output. We emulate input mode for open-drain pins + * by making sure they are not driven low, and we do not allow + * push-pull pins to be set as an input. + */ + for (i = 0; i < priv->gc.ngpio; ++i) { + /* + * Set direction to "input" iff pin is open-drain and reset + * value is 1. + */ + if (!(priv->gpio_pushpull & BIT(i)) && (gpio_latch & BIT(i))) + priv->gpio_input |= BIT(i); + } + + return 0; +} + +static int cp210x_gpio_init(struct usb_serial *serial) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + int result; + + switch (priv->partnum) { + case CP210X_PARTNUM_CP2104: + result = cp2104_gpioconf_init(serial); + break; + case CP210X_PARTNUM_CP2105: + result = cp2105_gpioconf_init(serial); + break; + case CP210X_PARTNUM_CP2108: + /* + * The GPIOs are not tied to any specific port so only register + * once for interface 0. + */ + if (cp210x_interface_num(serial) != 0) + return 0; + result = cp2108_gpio_init(serial); + break; + case CP210X_PARTNUM_CP2102N_QFN28: + case CP210X_PARTNUM_CP2102N_QFN24: + case CP210X_PARTNUM_CP2102N_QFN20: + result = cp2102n_gpioconf_init(serial); + break; + default: + return 0; + } + + if (result < 0) + return result; + + priv->gc.label = "cp210x"; + priv->gc.get_direction = cp210x_gpio_direction_get; + priv->gc.direction_input = cp210x_gpio_direction_input; + priv->gc.direction_output = cp210x_gpio_direction_output; + priv->gc.get = cp210x_gpio_get; + priv->gc.set = cp210x_gpio_set; + priv->gc.set_config = cp210x_gpio_set_config; + priv->gc.init_valid_mask = cp210x_gpio_init_valid_mask; + priv->gc.owner = THIS_MODULE; + priv->gc.parent = &serial->interface->dev; + priv->gc.base = -1; + priv->gc.can_sleep = true; + + result = gpiochip_add_data(&priv->gc, serial); + if (!result) + priv->gpio_registered = true; + + return result; +} + +static void cp210x_gpio_remove(struct usb_serial *serial) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + + if (priv->gpio_registered) { + gpiochip_remove(&priv->gc); + priv->gpio_registered = false; + } +} + +#else + +static int cp210x_gpio_init(struct usb_serial *serial) +{ + return 0; +} + +static void cp210x_gpio_remove(struct usb_serial *serial) +{ + /* Nothing to do */ +} + +#endif + +static int cp210x_port_probe(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct cp210x_port_private *port_priv; + + port_priv = kzalloc_obj(*port_priv); + if (!port_priv) + return -ENOMEM; + + port_priv->bInterfaceNumber = cp210x_interface_num(serial); + mutex_init(&port_priv->mutex); + + usb_set_serial_port_data(port, port_priv); + + return 0; +} + +static void cp210x_port_remove(struct usb_serial_port *port) +{ + struct cp210x_port_private *port_priv; + + port_priv = usb_get_serial_port_data(port); + kfree(port_priv); +} + +static void cp210x_init_max_speed(struct usb_serial *serial) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + bool use_actual_rate = false; + speed_t min = 300; + speed_t max; + + switch (priv->partnum) { + case CP210X_PARTNUM_CP2101: + max = 921600; + break; + case CP210X_PARTNUM_CP2102: + case CP210X_PARTNUM_CP2103: + max = 1000000; + break; + case CP210X_PARTNUM_CP2104: + use_actual_rate = true; + max = 2000000; + break; + case CP210X_PARTNUM_CP2108: + max = 2000000; + break; + case CP210X_PARTNUM_CP2105: + if (cp210x_interface_num(serial) == 0) { + use_actual_rate = true; + max = 2000000; /* ECI */ + } else { + min = 2400; + max = 921600; /* SCI */ + } + break; + case CP210X_PARTNUM_CP2102N_QFN28: + case CP210X_PARTNUM_CP2102N_QFN24: + case CP210X_PARTNUM_CP2102N_QFN20: + use_actual_rate = true; + max = 3000000; + break; + default: + max = 2000000; + break; + } + + priv->min_speed = min; + priv->max_speed = max; + priv->use_actual_rate = use_actual_rate; +} + +static void cp2102_determine_quirks(struct usb_serial *serial) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + u8 *buf; + int ret; + + buf = kmalloc(2, GFP_KERNEL); + if (!buf) + return; + /* + * Some (possibly counterfeit) CP2102 do not support event-insertion + * mode and respond differently to malformed vendor requests. + * Specifically, they return one instead of two bytes when sent a + * two-byte part-number request. + */ + ret = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + CP210X_VENDOR_SPECIFIC, REQTYPE_DEVICE_TO_HOST, + CP210X_GET_PARTNUM, 0, buf, 2, USB_CTRL_GET_TIMEOUT); + if (ret == 1) { + dev_dbg(&serial->interface->dev, + "device does not support event-insertion mode\n"); + priv->no_event_mode = true; + } + + kfree(buf); +} + +static int cp210x_get_fw_version(struct usb_serial *serial, u16 value) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + u8 ver[3]; + int ret; + + ret = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST, value, + ver, sizeof(ver)); + if (ret) + return ret; + + dev_dbg(&serial->interface->dev, "%s - %d.%d.%d\n", __func__, + ver[0], ver[1], ver[2]); + + priv->fw_version = ver[0] << 16 | ver[1] << 8 | ver[2]; + + return 0; +} + +static void cp210x_determine_type(struct usb_serial *serial) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + int ret; + + ret = cp210x_read_vendor_block(serial, REQTYPE_DEVICE_TO_HOST, + CP210X_GET_PARTNUM, &priv->partnum, + sizeof(priv->partnum)); + if (ret < 0) { + dev_warn(&serial->interface->dev, + "querying part number failed\n"); + priv->partnum = CP210X_PARTNUM_UNKNOWN; + return; + } + + dev_dbg(&serial->interface->dev, "partnum = 0x%02x\n", priv->partnum); + + switch (priv->partnum) { + case CP210X_PARTNUM_CP2102: + cp2102_determine_quirks(serial); + break; + case CP210X_PARTNUM_CP2105: + case CP210X_PARTNUM_CP2108: + cp210x_get_fw_version(serial, CP210X_GET_FW_VER); + break; + case CP210X_PARTNUM_CP2102N_QFN28: + case CP210X_PARTNUM_CP2102N_QFN24: + case CP210X_PARTNUM_CP2102N_QFN20: + ret = cp210x_get_fw_version(serial, CP210X_GET_FW_VER_2N); + if (ret) + break; + if (priv->fw_version <= 0x10004) + priv->no_flow_control = true; + break; + default: + break; + } +} + +static int cp210x_attach(struct usb_serial *serial) +{ + int result; + struct cp210x_serial_private *priv; + + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; + + usb_set_serial_data(serial, priv); + + cp210x_determine_type(serial); + cp210x_init_max_speed(serial); + + result = cp210x_gpio_init(serial); + if (result < 0) { + dev_err(&serial->interface->dev, "GPIO initialisation failed: %d\n", + result); + } + + return 0; +} + +static void cp210x_disconnect(struct usb_serial *serial) +{ + cp210x_gpio_remove(serial); +} + +static void cp210x_release(struct usb_serial *serial) +{ + struct cp210x_serial_private *priv = usb_get_serial_data(serial); + + cp210x_gpio_remove(serial); + + kfree(priv); +} + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/cyberjack.c b/drivers/usb/serial/cyberjack.c new file mode 100644 index 000000000..57e20eb02 --- /dev/null +++ b/drivers/usb/serial/cyberjack.c @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * 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 + +#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 void 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 unsigned 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 = { + .name = "cyberjack", + }, + .description = "Reiner SCT Cyberjack USB card reader", + .id_table = id_table, + .num_ports = 1, + .num_bulk_out = 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_obj(struct cyberjack_private); + 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 void 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); +} + +static int cyberjack_open(struct tty_struct *tty, + struct usb_serial_port *port) +{ + struct cyberjack_private *priv; + unsigned long flags; + + 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 0; +} + +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\n", + __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 unsigned 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; + unsigned long flags; + 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_irqsave(&priv->lock, flags); + + old_rdtodo = priv->rdtodo; + + if (old_rdtodo > SHRT_MAX - size) { + dev_dbg(dev, "Too many bulk_in urbs to do.\n"); + spin_unlock_irqrestore(&priv->lock, flags); + goto resubmit; + } + + /* "+=" is probably more fault tolerant than "=" */ + priv->rdtodo += size; + + dev_dbg(dev, "%s - rdtodo: %d\n", __func__, priv->rdtodo); + + spin_unlock_irqrestore(&priv->lock, flags); + + 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; + unsigned long flags; + 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_irqsave(&priv->lock, flags); + + /* 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_irqrestore(&priv->lock, flags); + + 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; + unsigned long flags; + bool resubmitted = false; + + if (status) { + dev_dbg(dev, "%s - nonzero write bulk status received: %d\n", + __func__, status); + set_bit(0, &port->write_urbs_free); + return; + } + + spin_lock_irqsave(&priv->lock, flags); + + /* 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; + } + + resubmitted = true; + + 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_irqrestore(&priv->lock, flags); + if (!resubmitted) + set_bit(0, &port->write_urbs_free); + 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 000000000..78fb1389b --- /dev/null +++ b/drivers/usb/serial/cypress_m8.c @@ -0,0 +1,1222 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * USB Cypress M8 driver + * + * Copyright (C) 2004 + * Lonnie Mendez (dignome@gmail.com) + * Copyright (C) 2003,2004 + * Neil Whelchel (koyama@firstlight.net) + * + * See Documentation/usb/usb-serial.rst 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 "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_SAI, 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_SAI, 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 */ + __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 */ + char prev_status; /* used for TIOCMIWAIT */ +}; + +/* 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 void 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 unsigned int cypress_write_room(struct tty_struct *tty); +static void cypress_earthmate_init_termios(struct tty_struct *tty); +static void cypress_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios); +static int cypress_tiocmget(struct tty_struct *tty); +static int cypress_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear); +static unsigned 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 = { + .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, + .init_termios = cypress_earthmate_init_termios, + .set_termios = cypress_set_termios, + .tiocmget = cypress_tiocmget, + .tiocmset = cypress_tiocmset, + .tiocmiwait = usb_serial_generic_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 = { + .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 = usb_serial_generic_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 = { + .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 = usb_serial_generic_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 that 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\n", + __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 - 5; /* 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 - retrieving 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->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; + + if (!port->interrupt_out_urb || !port->interrupt_in_urb) { + dev_err(&port->dev, "required endpoint is missing\n"); + return -ENODEV; + } + + /* + * The buffer must be large enough for the one or two-byte header (and + * following data), but assume anything smaller than eight bytes is + * broken. + */ + if (port->interrupt_out_size < 8) + return -EINVAL; + + priv = kzalloc_obj(struct cypress_private); + 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->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); + + port->port.drain_delay = 256; + + 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 void 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); +} + +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, NULL); + + /* setup the port and 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, + 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); + } + + 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 - %d bytes\n", __func__, 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, actual_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 unsigned 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); + unsigned int room; + 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 %u\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 void cypress_earthmate_init_termios(struct tty_struct *tty) +{ + tty_encode_baud_rate(tty, 4800, 4800); +} + +static void cypress_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const 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 int cflag; + unsigned long flags; + __u8 oldlines; + int linechange = 0; + + /* Unsupported features need clearing */ + tty->termios.c_cflag &= ~(CMSPAR|CRTSCTS); + + cflag = tty->termios.c_cflag; + + /* 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; + + data_bits = tty_get_char_size(cflag); + + 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 unsigned 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); + unsigned int chars; + 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 %u\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 bytes = 0; + int result; + int status = urb->status; + int i; + + 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 */ + fallthrough; + 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; + i = 0; + switch (priv->pkt_fmt) { + default: + case packet_format_1: + /* This is for the CY7C64013... */ + if (result < 2) + break; + priv->current_status = data[0] & 0xF8; + bytes = data[1] + 2; + i = 2; + break; + case packet_format_2: + /* This is for the CY7C63743... */ + if (result < 1) + break; + priv->current_status = data[0] & 0xF8; + bytes = (data[0] & 0x07) + 1; + i = 1; + break; + } + spin_unlock_irqrestore(&priv->lock, flags); + if (i == 0) { + dev_dbg(dev, "%s - short packet received: %d bytes\n", + __func__, result); + goto continue_read; + } + 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) { + u8 delta = priv->current_status ^ priv->prev_status; + + if (delta & UART_MSR_MASK) { + if (delta & UART_CTS) + port->icount.cts++; + if (delta & UART_DSR) + port->icount.dsr++; + if (delta & UART_RI) + port->icount.rng++; + if (delta & UART_CD) + port->icount.dcd++; + + 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 && !C_CLOCAL(tty) && !(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 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: + /* Cannot call usb_clear_halt while in_interrupt */ + fallthrough; + 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, 0644); +MODULE_PARM_DESC(stats, "Enable statistics or not"); +module_param(interval, int, 0644); +MODULE_PARM_DESC(interval, "Overrides interrupt interval"); +module_param(unstable_bauds, bool, 0644); +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 000000000..16b7410ad --- /dev/null +++ b/drivers/usb/serial/cypress_m8.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#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 + +/* Simply Automated HID->COM UPB PIM (using Cypress PID 0x5500) */ +#define VENDOR_ID_SAI 0x17dd + +/* 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 */ +#define CONTROL_RTS 0x10 /* request to send */ +#define CONTROL_RESET 0x08 /* sent with output report */ + +#define UART_MSR_MASK 0xf0 +#define UART_RI 0x80 /* ring indicator */ +#define UART_CD 0x40 /* carrier detect */ +#define UART_DSR 0x20 /* data set ready */ +#define UART_CTS 0x10 /* clear to send */ +#define CYP_ERROR 0x08 /* received from input report */ + +/* 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 000000000..12db0589e --- /dev/null +++ b/drivers/usb/serial/digi_acceleport.c @@ -0,0 +1,1513 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* +* Digi AccelePort USB-4 and USB-2 Serial Converters +* +* Copyright 2000 by Digi International +* +* 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 + +/* 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 + +/* 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 { + struct mutex open_mutex; + struct usb_serial_port *ds_oob_port; /* out-of-band port */ + int ds_oob_port_num; /* index of out-of-band port */ + int open_count; +}; + +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 write_wait; + struct usb_serial_port *dp_port; +}; + + +/* Local Function Declarations */ + +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, + const struct ktermios *old_termios); +static int 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 unsigned int digi_write_room(struct tty_struct *tty); +static unsigned 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(struct usb_serial *serial); +static void digi_release(struct usb_serial *serial); +static int digi_port_probe(struct usb_serial_port *port); +static void 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 = { + .name = "digi_2", + }, + .description = "Digi 2 port USB adapter", + .id_table = id_table_2, + .num_ports = 3, + .num_bulk_in = 4, + .num_bulk_out = 4, + .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, + .release = digi_release, + .port_probe = digi_port_probe, + .port_remove = digi_port_remove, +}; + +static struct usb_serial_driver digi_acceleport_4_device = { + .driver = { + .name = "digi_4", + }, + .description = "Digi 4 port USB adapter", + .id_table = id_table_4, + .num_ports = 4, + .num_bulk_in = 5, + .num_bulk_out = 5, + .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, + .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; +} + +static struct usb_serial_port *digi_get_oob_port(struct usb_serial *serial) +{ + struct digi_serial *serial_priv = usb_get_serial_data(serial); + + return serial_priv->ds_oob_port; +} + +/* + * 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) +{ + struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); + struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); + unsigned long flags; + int ret = 0; + int len; + + 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_priv->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) + break; + + 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) +{ + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = port->write_urb->transfer_buffer; + unsigned long expire; + unsigned long flags; + int ret = 0; + int len; + + dev_dbg(&port->dev, "digi_write_inb_command: TOP: port=%d, count=%d\n", + priv->dp_port_num, count); + + if (timeout) + expire = jiffies + timeout; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + while (count > 0 && ret == 0) { + while (priv->dp_write_urb_in_use) { + if (timeout && time_after(jiffies, expire)) { + ret = -ETIMEDOUT; + break; + } + cond_wait_interruptible_timeout_irqrestore( + &priv->write_wait, DIGI_RETRY_TIMEOUT, + &priv->dp_port_lock, flags); + if (signal_pending(current)) + return -EINTR; + spin_lock_irqsave(&priv->dp_port_lock, flags); + } + + if (ret) + break; + + /* 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) +{ + struct digi_port *port_priv = usb_get_serial_port_data(port); + struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); + struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); + unsigned char *data = oob_port->write_urb->transfer_buffer; + unsigned long flags; + int ret; + + 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_priv->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 &= ~(TIOCM_DTR | TIOCM_RTS); + port_priv->dp_modem_signals |= + 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) +{ + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char buf[2]; + unsigned long flags; + int ret; + + 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) +{ + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned long flags; + + /* 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) +{ + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned long flags; + int ret = 0; + + 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, + const 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 (!C_CRTSCTS(tty) || !tty_throttled(tty)) + 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; + + 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 int 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 */ + + return 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 long flags; + unsigned int val; + + 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 long flags; + unsigned int val; + + 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) +{ + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = port->write_urb->transfer_buffer; + int ret, data_len, new_len; + unsigned long flags; + + dev_dbg(&port->dev, "digi_write: TOP: port=%d, count=%d\n", + priv->dp_port_num, count); + + /* 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 digi_serial *serial_priv = usb_get_serial_data(port->serial); + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = urb->transfer_buffer; + unsigned long flags; + bool wakeup; + int ret = 0; + + /* handle oob callback */ + if (priv->dp_port_num == serial_priv->ds_oob_port_num) { + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_write_urb_in_use = 0; + wake_up_interruptible(&priv->write_wait); + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return; + } + + /* try to send any buffered data on this port */ + wakeup = true; + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_write_urb_in_use = 0; + 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); + + urb->transfer_buffer_length = priv->dp_out_buf_len + 2; + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret == 0) { + priv->dp_write_urb_in_use = 1; + priv->dp_out_buf_len = 0; + wakeup = false; + } + } + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + + if (ret && ret != -EPERM) + dev_err_console(port, + "%s: usb_submit_urb failed, ret=%d, port=%d\n", + __func__, ret, priv->dp_port_num); + + if (wakeup) + tty_port_tty_wakeup(&port->port); +} + +static unsigned 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); + unsigned long flags; + unsigned int room; + + 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=%u\n", priv->dp_port_num, room); + + return room; +} + +static unsigned 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); + unsigned long flags; + unsigned int chars; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + if (priv->dp_write_urb_in_use) + chars = port->bulk_out_size - 2; + else + chars = priv->dp_out_buf_len; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + + dev_dbg(&port->dev, "%s: port=%d, chars=%d\n", __func__, + priv->dp_port_num, chars); + return chars; +} + +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_oob_port(struct usb_serial *serial) +{ + struct digi_serial *serial_priv = usb_get_serial_data(serial); + struct usb_serial_port *oob_port = serial_priv->ds_oob_port; + int ret = 0; + + mutex_lock(&serial_priv->open_mutex); + + if (serial_priv->open_count++ == 0) { + ret = usb_submit_urb(oob_port->read_urb, GFP_KERNEL); + if (ret) { + dev_err(&serial->interface->dev, "failed to submit OOB read urb: %d\n", + ret); + serial_priv->open_count--; + } + } + + mutex_unlock(&serial_priv->open_mutex); + + return ret; +} + +static void digi_close_oob_port(struct usb_serial *serial) +{ + struct digi_serial *serial_priv = usb_get_serial_data(serial); + struct usb_serial_port *oob_port = serial_priv->ds_oob_port; + + mutex_lock(&serial_priv->open_mutex); + + if (serial_priv->open_count-- == 1) { + usb_kill_urb(oob_port->read_urb); + usb_kill_urb(oob_port->write_urb); + } + + mutex_unlock(&serial_priv->open_mutex); +} + +static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct digi_port *priv = usb_get_serial_port_data(port); + struct ktermios not_termios; + unsigned char buf[32]; + int ret; + + ret = digi_open_oob_port(port->serial); + if (ret) + return ret; + + /* 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); + } + + spin_lock_irq(&priv->dp_port_lock); + priv->dp_throttled = 0; + priv->dp_throttle_restart = 0; + spin_unlock_irq(&priv->dp_port_lock); + + ret = usb_submit_urb(port->read_urb, GFP_KERNEL); + if (ret) { + dev_err(&port->dev, "failed to submit read urb: %d\n", ret); + goto err_close_oob; + } + + return 0; + +err_close_oob: + digi_close_oob_port(port->serial); + + return ret; +} + +static void digi_close(struct usb_serial_port *port) +{ + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char buf[32]; + DEFINE_WAIT(wait); + int ret; + + usb_kill_urb(port->read_urb); + + mutex_lock(&port->serial->disc_mutex); + 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); +exit: + mutex_unlock(&port->serial->disc_mutex); + + /* shutdown any outstanding bulk writes */ + usb_kill_urb(port->write_urb); + + digi_close_oob_port(port->serial); +} + +static int digi_port_init(struct usb_serial_port *port, unsigned port_num) +{ + struct digi_serial *serial_priv = usb_get_serial_data(port->serial); + struct digi_port *priv; + + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->dp_port_lock); + + if (port == serial_priv->ds_oob_port) + lockdep_set_subclass(&priv->dp_port_lock, SINGLE_DEPTH_NESTING); + + 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->write_wait); + priv->dp_port = port; + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static int digi_startup(struct usb_serial *serial) +{ + struct digi_serial *serial_priv; + int oob_port_num; + int ret; + int i; + + /* + * The port bulk-out buffers must be large enough for header and + * buffered data. + */ + for (i = 0; i < serial->type->num_ports; i++) { + if (serial->port[i]->bulk_out_size < DIGI_OUT_BUF_SIZE + 2) + return -EINVAL; + } + + /* + * The OOB port bulk-out buffer must be large enough for the two + * commands in digi_set_modem_signals(). + */ + oob_port_num = serial->type->num_ports; + if (serial->port[oob_port_num]->bulk_out_size < 8) + return -EINVAL; + + serial_priv = kzalloc_obj(*serial_priv); + if (!serial_priv) + return -ENOMEM; + + mutex_init(&serial_priv->open_mutex); + + serial_priv->ds_oob_port_num = oob_port_num; + serial_priv->ds_oob_port = serial->port[oob_port_num]; + + usb_set_serial_data(serial, serial_priv); + + ret = digi_port_init(serial_priv->ds_oob_port, + serial_priv->ds_oob_port_num); + if (ret) { + kfree(serial_priv); + return ret; + } + + return 0; +} + +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) +{ + return digi_port_init(port, port->port_number); +} + +static void digi_port_remove(struct usb_serial_port *port) +{ + struct digi_port *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); +} + +static void digi_read_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct usb_serial *serial = port->serial; + struct digi_serial *serial_priv = usb_get_serial_data(serial); + struct digi_port *priv = usb_get_serial_port_data(port); + int status = urb->status; + int ret; + + /* do not resubmit urb if it has any status error */ + switch (status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + dev_err(&serial->interface->dev, + "%s: nonzero read bulk status: status=%d, port=%d\n", + __func__, status, priv->dp_port_num); + return; + default: + dev_err(&serial->interface->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(&serial->interface->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); + unsigned char *buf = urb->transfer_buffer; + unsigned long flags; + int opcode; + int len; + int port_status; + unsigned char *data; + int tty_flag, throttled; + + /* short/multiple packet check */ + if (urb->actual_length < 2) { + dev_warn(&port->dev, "short packet received\n"); + return -1; + } + + opcode = buf[0]; + len = buf[1]; + + if (urb->actual_length != len + 2) { + dev_err(&port->dev, "malformed packet received: port=%d, opcode=%d, len=%d, actual_length=%u\n", + priv->dp_port_num, opcode, len, urb->actual_length); + return -1; + } + + if (opcode == DIGI_CMD_RECEIVE_DATA && len < 1) { + dev_err(&port->dev, "malformed data packet received\n"); + return -1; + } + + spin_lock_irqsave(&priv->dp_port_lock, flags); + + /* 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) { + port_status = buf[2]; + data = &buf[3]; + + /* get flag from port_status */ + tty_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) + tty_flag = TTY_BREAK; + else if (port_status & DIGI_PARITY_ERROR) + tty_flag = TTY_PARITY; + else if (port_status & DIGI_FRAMING_ERROR) + tty_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, + tty_flag, len); + tty_flip_buffer_push(&port->port); + } + } + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + + 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; + unsigned char *buf = urb->transfer_buffer; + int opcode, line, status, val; + unsigned long flags; + unsigned int rts; + int i; + + if (urb->actual_length < 4) + return -1; + + /* handle each oob command */ + for (i = 0; i < urb->actual_length - 3; i += 4) { + opcode = buf[i]; + line = buf[i + 1]; + status = buf[i + 2]; + val = buf[i + 3]; + + dev_dbg(&serial->interface->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 = C_CRTSCTS(tty); + + if (tty && opcode == DIGI_CMD_READ_INPUT_SIGNALS) { + bool wakeup = false; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + /* convert from digi flags to termiox flags */ + if (val & DIGI_READ_INPUT_SIGNALS_CTS) { + priv->dp_modem_signals |= TIOCM_CTS; + if (rts) + wakeup = true; + } else { + priv->dp_modem_signals &= ~TIOCM_CTS; + /* port must be open to use tty struct */ + } + 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_irqrestore(&priv->dp_port_lock, flags); + + if (wakeup) + tty_port_tty_wakeup(&port->port); + } else if (opcode == DIGI_CMD_TRANSMIT_IDLE) { + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_transmit_idle = 1; + wake_up_interruptible(&priv->dp_transmit_idle_wait); + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + } 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 000000000..4937575ca --- /dev/null +++ b/drivers/usb/serial/empeg.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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) + * + * See Documentation/usb/usb-serial.rst for more information on using this + * driver + */ + +#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 = { + .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 v2"); diff --git a/drivers/usb/serial/ezusb_convert.pl b/drivers/usb/serial/ezusb_convert.pl new file mode 100644 index 000000000..40d23f21e --- /dev/null +++ b/drivers/usb/serial/ezusb_convert.pl @@ -0,0 +1,51 @@ +#! /usr/bin/perl -w +# SPDX-License-Identifier: GPL-2.0 + + +# 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 000000000..dcceb95d7 --- /dev/null +++ b/drivers/usb/serial/f81232.c @@ -0,0 +1,1077 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Fintek F81232 USB to serial adaptor driver + * Fintek F81532A/534A/535/536 USB to 2/4/8/12 serial adaptor driver + * + * Copyright (C) 2012 Greg Kroah-Hartman (gregkh@linuxfoundation.org) + * Copyright (C) 2012 Linux Foundation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define F81232_ID \ + { USB_DEVICE(0x1934, 0x0706) } /* 1 port UART device */ + +#define F81534A_SERIES_ID \ + { USB_DEVICE(0x2c42, 0x1602) }, /* In-Box 2 port UART device */ \ + { USB_DEVICE(0x2c42, 0x1604) }, /* In-Box 4 port UART device */ \ + { USB_DEVICE(0x2c42, 0x1605) }, /* In-Box 8 port UART device */ \ + { USB_DEVICE(0x2c42, 0x1606) }, /* In-Box 12 port UART device */ \ + { USB_DEVICE(0x2c42, 0x1608) }, /* Non-Flash type */ \ + { USB_DEVICE(0x2c42, 0x1632) }, /* 2 port UART device */ \ + { USB_DEVICE(0x2c42, 0x1634) }, /* 4 port UART device */ \ + { USB_DEVICE(0x2c42, 0x1635) }, /* 8 port UART device */ \ + { USB_DEVICE(0x2c42, 0x1636) } /* 12 port UART device */ + +#define F81534A_CTRL_ID \ + { USB_DEVICE(0x2c42, 0x16f8) } /* Global control device */ + +static const struct usb_device_id f81232_id_table[] = { + F81232_ID, + { } /* Terminating entry */ +}; + +static const struct usb_device_id f81534a_id_table[] = { + F81534A_SERIES_ID, + { } /* Terminating entry */ +}; + +static const struct usb_device_id f81534a_ctrl_id_table[] = { + F81534A_CTRL_ID, + { } /* Terminating entry */ +}; + +static const struct usb_device_id combined_id_table[] = { + F81232_ID, + F81534A_SERIES_ID, + F81534A_CTRL_ID, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, combined_id_table); + +/* Maximum baudrate for F81232 */ +#define F81232_MAX_BAUDRATE 1500000 +#define F81232_DEF_BAUDRATE 9600 + +/* USB Control EP parameter */ +#define F81232_REGISTER_REQUEST 0xa0 +#define F81232_GET_REGISTER 0xc0 +#define F81232_SET_REGISTER 0x40 + +#define SERIAL_BASE_ADDRESS 0x0120 +#define RECEIVE_BUFFER_REGISTER (0x00 + SERIAL_BASE_ADDRESS) +#define INTERRUPT_ENABLE_REGISTER (0x01 + SERIAL_BASE_ADDRESS) +#define FIFO_CONTROL_REGISTER (0x02 + SERIAL_BASE_ADDRESS) +#define LINE_CONTROL_REGISTER (0x03 + SERIAL_BASE_ADDRESS) +#define MODEM_CONTROL_REGISTER (0x04 + SERIAL_BASE_ADDRESS) +#define LINE_STATUS_REGISTER (0x05 + SERIAL_BASE_ADDRESS) +#define MODEM_STATUS_REGISTER (0x06 + SERIAL_BASE_ADDRESS) + +/* + * F81232 Clock registers (106h) + * + * Bit1-0: Clock source selector + * 00: 1.846MHz. + * 01: 18.46MHz. + * 10: 24MHz. + * 11: 14.77MHz. + */ +#define F81232_CLK_REGISTER 0x106 +#define F81232_CLK_1_846_MHZ 0 +#define F81232_CLK_18_46_MHZ BIT(0) +#define F81232_CLK_24_MHZ BIT(1) +#define F81232_CLK_14_77_MHZ (BIT(1) | BIT(0)) +#define F81232_CLK_MASK GENMASK(1, 0) + +#define F81534A_MODE_REG 0x107 +#define F81534A_TRIGGER_MASK GENMASK(3, 2) +#define F81534A_TRIGGER_MULTIPLE_4X BIT(3) +#define F81534A_FIFO_128BYTE (BIT(1) | BIT(0)) + +/* Serial port self GPIO control, 2bytes [control&output data][input data] */ +#define F81534A_GPIO_REG 0x10e +#define F81534A_GPIO_MODE2_DIR BIT(6) /* 1: input, 0: output */ +#define F81534A_GPIO_MODE1_DIR BIT(5) +#define F81534A_GPIO_MODE0_DIR BIT(4) +#define F81534A_GPIO_MODE2_OUTPUT BIT(2) +#define F81534A_GPIO_MODE1_OUTPUT BIT(1) +#define F81534A_GPIO_MODE0_OUTPUT BIT(0) + +#define F81534A_CTRL_CMD_ENABLE_PORT 0x116 + +struct f81232_private { + struct mutex lock; + u8 modem_control; + u8 modem_status; + u8 shadow_lcr; + speed_t baud_base; + struct work_struct lsr_work; + struct work_struct interrupt_work; + struct usb_serial_port *port; +}; + +static u32 const baudrate_table[] = { 115200, 921600, 1152000, 1500000 }; +static u8 const clock_table[] = { F81232_CLK_1_846_MHZ, F81232_CLK_14_77_MHZ, + F81232_CLK_18_46_MHZ, F81232_CLK_24_MHZ }; + +static int calc_baud_divisor(speed_t baudrate, speed_t clockrate) +{ + return DIV_ROUND_CLOSEST(clockrate, baudrate); +} + +static int f81232_get_register(struct usb_serial_port *port, u16 reg, u8 *val) +{ + int status; + struct usb_device *dev = port->serial->dev; + + status = usb_control_msg_recv(dev, + 0, + F81232_REGISTER_REQUEST, + F81232_GET_REGISTER, + reg, + 0, + val, + sizeof(*val), + USB_CTRL_GET_TIMEOUT, + GFP_KERNEL); + if (status) { + dev_err(&port->dev, "%s failed status: %d\n", __func__, status); + status = usb_translate_errors(status); + } + + return status; +} + +static int f81232_set_register(struct usb_serial_port *port, u16 reg, u8 val) +{ + int status; + struct usb_device *dev = port->serial->dev; + + status = usb_control_msg_send(dev, + 0, + F81232_REGISTER_REQUEST, + F81232_SET_REGISTER, + reg, + 0, + &val, + sizeof(val), + USB_CTRL_SET_TIMEOUT, + GFP_KERNEL); + if (status) { + dev_err(&port->dev, "%s failed status: %d\n", __func__, status); + status = usb_translate_errors(status); + } + + return status; +} + +static int f81232_set_mask_register(struct usb_serial_port *port, u16 reg, + u8 mask, u8 val) +{ + int status; + u8 tmp; + + status = f81232_get_register(port, reg, &tmp); + if (status) + return status; + + tmp = (tmp & ~mask) | (val & mask); + + return f81232_set_register(port, reg, tmp); +} + +static void f81232_read_msr(struct usb_serial_port *port) +{ + int status; + u8 current_msr; + struct tty_struct *tty; + struct f81232_private *priv = usb_get_serial_port_data(port); + + mutex_lock(&priv->lock); + status = f81232_get_register(port, MODEM_STATUS_REGISTER, + ¤t_msr); + if (status) { + dev_err(&port->dev, "%s fail, status: %d\n", __func__, status); + mutex_unlock(&priv->lock); + return; + } + + if (!(current_msr & UART_MSR_ANY_DELTA)) { + mutex_unlock(&priv->lock); + return; + } + + priv->modem_status = current_msr; + + if (current_msr & UART_MSR_DCTS) + port->icount.cts++; + if (current_msr & UART_MSR_DDSR) + port->icount.dsr++; + if (current_msr & UART_MSR_TERI) + port->icount.rng++; + if (current_msr & UART_MSR_DDCD) { + port->icount.dcd++; + tty = tty_port_tty_get(&port->port); + if (tty) { + usb_serial_handle_dcd_change(port, tty, + current_msr & UART_MSR_DCD); + + tty_kref_put(tty); + } + } + + wake_up_interruptible(&port->port.delta_msr_wait); + mutex_unlock(&priv->lock); +} + +static int f81232_set_mctrl(struct usb_serial_port *port, + unsigned int set, unsigned int clear) +{ + u8 val; + int status; + struct f81232_private *priv = usb_get_serial_port_data(port); + + if (((set | clear) & (TIOCM_DTR | TIOCM_RTS)) == 0) + return 0; /* no change */ + + /* 'set' takes precedence over 'clear' */ + clear &= ~set; + + /* force enable interrupt with OUT2 */ + mutex_lock(&priv->lock); + val = UART_MCR_OUT2 | priv->modem_control; + + if (clear & TIOCM_DTR) + val &= ~UART_MCR_DTR; + + if (clear & TIOCM_RTS) + val &= ~UART_MCR_RTS; + + if (set & TIOCM_DTR) + val |= UART_MCR_DTR; + + if (set & TIOCM_RTS) + val |= UART_MCR_RTS; + + dev_dbg(&port->dev, "%s new:%02x old:%02x\n", __func__, + val, priv->modem_control); + + status = f81232_set_register(port, MODEM_CONTROL_REGISTER, val); + if (status) { + dev_err(&port->dev, "%s set MCR status < 0\n", __func__); + mutex_unlock(&priv->lock); + return status; + } + + priv->modem_control = val; + mutex_unlock(&priv->lock); + + return 0; +} + +static void f81232_update_line_status(struct usb_serial_port *port, + unsigned char *data, + size_t actual_length) +{ + struct f81232_private *priv = usb_get_serial_port_data(port); + + if (!actual_length) + return; + + switch (data[0] & 0x07) { + case 0x00: /* msr change */ + dev_dbg(&port->dev, "IIR: MSR Change: %02x\n", data[0]); + schedule_work(&priv->interrupt_work); + break; + case 0x02: /* tx-empty */ + break; + case 0x04: /* rx data available */ + break; + case 0x06: /* lsr change */ + /* we can forget it. the LSR will read from bulk-in */ + dev_dbg(&port->dev, "IIR: LSR Change: %02x\n", data[0]); + break; + } +} + +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 char f81232_handle_lsr(struct usb_serial_port *port, u8 lsr) +{ + struct f81232_private *priv = usb_get_serial_port_data(port); + char tty_flag = TTY_NORMAL; + + if (!(lsr & UART_LSR_BRK_ERROR_BITS)) + return tty_flag; + + if (lsr & UART_LSR_BI) { + tty_flag = TTY_BREAK; + port->icount.brk++; + usb_serial_handle_break(port); + } else if (lsr & UART_LSR_PE) { + tty_flag = TTY_PARITY; + port->icount.parity++; + } else if (lsr & UART_LSR_FE) { + tty_flag = TTY_FRAME; + port->icount.frame++; + } + + if (lsr & UART_LSR_OE) { + port->icount.overrun++; + schedule_work(&priv->lsr_work); + tty_insert_flip_char(&port->port, 0, TTY_OVERRUN); + } + + return tty_flag; +} + +static void f81232_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + char tty_flag; + unsigned int i; + u8 lsr; + + /* + * When opening the port we get a 1-byte packet with the current LSR, + * which we discard. + */ + if ((urb->actual_length < 2) || (urb->actual_length % 2)) + return; + + /* bulk-in data: [LSR(1Byte)+DATA(1Byte)][LSR(1Byte)+DATA(1Byte)]... */ + + for (i = 0; i < urb->actual_length; i += 2) { + lsr = data[i]; + tty_flag = f81232_handle_lsr(port, lsr); + + if (port->sysrq) { + if (usb_serial_handle_sysrq_char(port, data[i + 1])) + continue; + } + + tty_insert_flip_char(&port->port, data[i + 1], tty_flag); + } + + tty_flip_buffer_push(&port->port); +} + +static void f81534a_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + char tty_flag; + unsigned int i; + u8 lsr; + u8 len; + + if (urb->actual_length < 3) { + dev_err(&port->dev, "short message received: %d\n", + urb->actual_length); + return; + } + + len = data[0]; + if (len != urb->actual_length) { + dev_err(&port->dev, "malformed message received: %d (%d)\n", + urb->actual_length, len); + return; + } + + /* bulk-in data: [LEN][Data.....][LSR] */ + lsr = data[len - 1]; + tty_flag = f81232_handle_lsr(port, lsr); + + if (port->sysrq) { + for (i = 1; i < len - 1; ++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[1], + tty_flag, len - 2); + } + + tty_flip_buffer_push(&port->port); +} + +static int f81232_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct f81232_private *priv = usb_get_serial_port_data(port); + int status; + + mutex_lock(&priv->lock); + + if (break_state) + priv->shadow_lcr |= UART_LCR_SBC; + else + priv->shadow_lcr &= ~UART_LCR_SBC; + + status = f81232_set_register(port, LINE_CONTROL_REGISTER, + priv->shadow_lcr); + if (status) + dev_err(&port->dev, "set break failed: %d\n", status); + + mutex_unlock(&priv->lock); + + return status; +} + +static int f81232_find_clk(speed_t baudrate) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(baudrate_table); ++idx) { + if (baudrate <= baudrate_table[idx] && + baudrate_table[idx] % baudrate == 0) + return idx; + } + + return -EINVAL; +} + +static void f81232_set_baudrate(struct tty_struct *tty, + struct usb_serial_port *port, speed_t baudrate, + speed_t old_baudrate) +{ + struct f81232_private *priv = usb_get_serial_port_data(port); + u8 lcr; + int divisor; + int status = 0; + int i; + int idx; + speed_t baud_list[] = { baudrate, old_baudrate, F81232_DEF_BAUDRATE }; + + for (i = 0; i < ARRAY_SIZE(baud_list); ++i) { + baudrate = baud_list[i]; + if (baudrate == 0) { + tty_encode_baud_rate(tty, 0, 0); + return; + } + + idx = f81232_find_clk(baudrate); + if (idx >= 0) { + tty_encode_baud_rate(tty, baudrate, baudrate); + break; + } + } + + if (idx < 0) + return; + + priv->baud_base = baudrate_table[idx]; + divisor = calc_baud_divisor(baudrate, priv->baud_base); + + status = f81232_set_mask_register(port, F81232_CLK_REGISTER, + F81232_CLK_MASK, clock_table[idx]); + if (status) { + dev_err(&port->dev, "%s failed to set CLK_REG: %d\n", + __func__, status); + return; + } + + status = f81232_get_register(port, LINE_CONTROL_REGISTER, + &lcr); /* get LCR */ + if (status) { + dev_err(&port->dev, "%s failed to get LCR: %d\n", + __func__, status); + return; + } + + status = f81232_set_register(port, LINE_CONTROL_REGISTER, + lcr | UART_LCR_DLAB); /* Enable DLAB */ + if (status) { + dev_err(&port->dev, "%s failed to set DLAB: %d\n", + __func__, status); + return; + } + + status = f81232_set_register(port, RECEIVE_BUFFER_REGISTER, + divisor & 0x00ff); /* low */ + if (status) { + dev_err(&port->dev, "%s failed to set baudrate MSB: %d\n", + __func__, status); + goto reapply_lcr; + } + + status = f81232_set_register(port, INTERRUPT_ENABLE_REGISTER, + (divisor & 0xff00) >> 8); /* high */ + if (status) { + dev_err(&port->dev, "%s failed to set baudrate LSB: %d\n", + __func__, status); + } + +reapply_lcr: + status = f81232_set_register(port, LINE_CONTROL_REGISTER, + lcr & ~UART_LCR_DLAB); + if (status) { + dev_err(&port->dev, "%s failed to set DLAB: %d\n", + __func__, status); + } +} + +static int f81232_port_enable(struct usb_serial_port *port) +{ + u8 val; + int status; + + /* fifo on, trigger8, clear TX/RX*/ + val = UART_FCR_TRIGGER_8 | UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | + UART_FCR_CLEAR_XMIT; + + status = f81232_set_register(port, FIFO_CONTROL_REGISTER, val); + if (status) { + dev_err(&port->dev, "%s failed to set FCR: %d\n", + __func__, status); + return status; + } + + /* MSR Interrupt only, LSR will read from Bulk-in odd byte */ + status = f81232_set_register(port, INTERRUPT_ENABLE_REGISTER, + UART_IER_MSI); + if (status) { + dev_err(&port->dev, "%s failed to set IER: %d\n", + __func__, status); + return status; + } + + return 0; +} + +static int f81232_port_disable(struct usb_serial_port *port) +{ + int status; + + status = f81232_set_register(port, INTERRUPT_ENABLE_REGISTER, 0); + if (status) { + dev_err(&port->dev, "%s failed to set IER: %d\n", + __func__, status); + return status; + } + + return 0; +} + +static void f81232_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +{ + struct f81232_private *priv = usb_get_serial_port_data(port); + u8 new_lcr = 0; + int status = 0; + speed_t baudrate; + speed_t old_baud; + + /* Don't change anything if nothing has changed */ + if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios)) + return; + + if (C_BAUD(tty) == B0) + f81232_set_mctrl(port, 0, TIOCM_DTR | TIOCM_RTS); + else if (old_termios && (old_termios->c_cflag & CBAUD) == B0) + f81232_set_mctrl(port, TIOCM_DTR | TIOCM_RTS, 0); + + baudrate = tty_get_baud_rate(tty); + if (baudrate > 0) { + if (old_termios) + old_baud = tty_termios_baud_rate(old_termios); + else + old_baud = F81232_DEF_BAUDRATE; + + f81232_set_baudrate(tty, port, baudrate, old_baud); + } + + if (C_PARENB(tty)) { + new_lcr |= UART_LCR_PARITY; + + if (!C_PARODD(tty)) + new_lcr |= UART_LCR_EPAR; + + if (C_CMSPAR(tty)) + new_lcr |= UART_LCR_SPAR; + } + + if (C_CSTOPB(tty)) + new_lcr |= UART_LCR_STOP; + + new_lcr |= UART_LCR_WLEN(tty_get_char_size(tty->termios.c_cflag)); + + mutex_lock(&priv->lock); + + new_lcr |= (priv->shadow_lcr & UART_LCR_SBC); + status = f81232_set_register(port, LINE_CONTROL_REGISTER, new_lcr); + if (status) { + dev_err(&port->dev, "%s failed to set LCR: %d\n", + __func__, status); + } + + priv->shadow_lcr = new_lcr; + + mutex_unlock(&priv->lock); +} + +static int f81232_tiocmget(struct tty_struct *tty) +{ + int r; + struct usb_serial_port *port = tty->driver_data; + struct f81232_private *port_priv = usb_get_serial_port_data(port); + u8 mcr, msr; + + /* force get current MSR changed state */ + f81232_read_msr(port); + + mutex_lock(&port_priv->lock); + mcr = port_priv->modem_control; + msr = port_priv->modem_status; + mutex_unlock(&port_priv->lock); + + r = (mcr & UART_MCR_DTR ? TIOCM_DTR : 0) | + (mcr & UART_MCR_RTS ? TIOCM_RTS : 0) | + (msr & UART_MSR_CTS ? TIOCM_CTS : 0) | + (msr & UART_MSR_DCD ? TIOCM_CAR : 0) | + (msr & UART_MSR_RI ? TIOCM_RI : 0) | + (msr & UART_MSR_DSR ? TIOCM_DSR : 0); + + return r; +} + +static int f81232_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + + return f81232_set_mctrl(port, set, clear); +} + +static int f81232_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int result; + + result = f81232_port_enable(port); + if (result) + return 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; + } + + return 0; +} + +static int f81534a_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int status; + u8 mask; + u8 val; + + val = F81534A_TRIGGER_MULTIPLE_4X | F81534A_FIFO_128BYTE; + mask = F81534A_TRIGGER_MASK | F81534A_FIFO_128BYTE; + + status = f81232_set_mask_register(port, F81534A_MODE_REG, mask, val); + if (status) { + dev_err(&port->dev, "failed to set MODE_REG: %d\n", status); + return status; + } + + return f81232_open(tty, port); +} + +static void f81232_close(struct usb_serial_port *port) +{ + struct f81232_private *port_priv = usb_get_serial_port_data(port); + + f81232_port_disable(port); + usb_serial_generic_close(port); + usb_kill_urb(port->interrupt_in_urb); + flush_work(&port_priv->interrupt_work); + flush_work(&port_priv->lsr_work); +} + +static void f81232_dtr_rts(struct usb_serial_port *port, int on) +{ + if (on) + f81232_set_mctrl(port, TIOCM_DTR | TIOCM_RTS, 0); + else + f81232_set_mctrl(port, 0, TIOCM_DTR | TIOCM_RTS); +} + +static bool f81232_tx_empty(struct usb_serial_port *port) +{ + int status; + u8 tmp; + + status = f81232_get_register(port, LINE_STATUS_REGISTER, &tmp); + if (!status) { + if ((tmp & UART_LSR_TEMT) != UART_LSR_TEMT) + return false; + } + + return true; +} + +static int f81232_carrier_raised(struct usb_serial_port *port) +{ + u8 msr; + struct f81232_private *priv = usb_get_serial_port_data(port); + + mutex_lock(&priv->lock); + msr = priv->modem_status; + mutex_unlock(&priv->lock); + + if (msr & UART_MSR_DCD) + return 1; + return 0; +} + +static void f81232_get_serial(struct tty_struct *tty, struct serial_struct *ss) +{ + struct usb_serial_port *port = tty->driver_data; + struct f81232_private *priv = usb_get_serial_port_data(port); + + ss->baud_base = priv->baud_base; +} + +static void f81232_interrupt_work(struct work_struct *work) +{ + struct f81232_private *priv = + container_of(work, struct f81232_private, interrupt_work); + + f81232_read_msr(priv->port); +} + +static void f81232_lsr_worker(struct work_struct *work) +{ + struct f81232_private *priv; + struct usb_serial_port *port; + int status; + u8 tmp; + + priv = container_of(work, struct f81232_private, lsr_work); + port = priv->port; + + status = f81232_get_register(port, LINE_STATUS_REGISTER, &tmp); + if (status) + dev_warn(&port->dev, "read LSR failed: %d\n", status); +} + +static int f81534a_ctrl_set_register(struct usb_interface *intf, u16 reg, + u16 size, void *val) +{ + return usb_control_msg_send(interface_to_usbdev(intf), + 0, + F81232_REGISTER_REQUEST, + F81232_SET_REGISTER, + reg, + 0, + val, + size, + USB_CTRL_SET_TIMEOUT, + GFP_KERNEL); +} + +static int f81534a_ctrl_get_register(struct usb_interface *intf, u16 reg, + u16 size, void *val) +{ + return usb_control_msg_recv(interface_to_usbdev(intf), + 0, + F81232_REGISTER_REQUEST, + F81232_GET_REGISTER, + reg, + 0, + val, + size, + USB_CTRL_GET_TIMEOUT, + GFP_KERNEL); +} + +static int f81534a_ctrl_enable_all_ports(struct usb_interface *intf, bool en) +{ + unsigned char enable[2] = {0}; + int status; + + /* + * Enable all available serial ports, define as following: + * bit 15 : Reset behavior (when HUB got soft reset) + * 0: maintain all serial port enabled state. + * 1: disable all serial port. + * bit 0~11 : Serial port enable bit. + */ + if (en) { + /* + * The Fintek F81532A/534A/535/536 family relies on the + * F81534A_CTRL_CMD_ENABLE_PORT (116h) register during + * initialization to both determine serial port status and + * control port creation. + * + * If the driver experiences fast load/unload cycles, the + * device state may becomes unstable, resulting in the + * incomplete generation of serial ports. + * + * Performing a dummy read operation on the register prior + * to the initial write command resolves the issue. + * + * This clears the device's stale internal state. Subsequent + * write operations will correctly generate all serial ports. + */ + status = f81534a_ctrl_get_register(intf, + F81534A_CTRL_CMD_ENABLE_PORT, + sizeof(enable), + enable); + if (status) + return status; + + enable[0] = 0xff; + enable[1] = 0x8f; + } + + status = f81534a_ctrl_set_register(intf, F81534A_CTRL_CMD_ENABLE_PORT, + sizeof(enable), enable); + if (status) + dev_err(&intf->dev, "failed to enable ports: %d\n", status); + + return status; +} + +static int f81534a_ctrl_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + return f81534a_ctrl_enable_all_ports(intf, true); +} + +static void f81534a_ctrl_disconnect(struct usb_interface *intf) +{ + f81534a_ctrl_enable_all_ports(intf, false); +} + +static int f81534a_ctrl_resume(struct usb_interface *intf) +{ + return f81534a_ctrl_enable_all_ports(intf, true); +} + +static int f81232_port_probe(struct usb_serial_port *port) +{ + struct f81232_private *priv; + + priv = devm_kzalloc(&port->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->lock); + INIT_WORK(&priv->interrupt_work, f81232_interrupt_work); + INIT_WORK(&priv->lsr_work, f81232_lsr_worker); + + usb_set_serial_port_data(port, priv); + + priv->port = port; + + return 0; +} + +static int f81534a_port_probe(struct usb_serial_port *port) +{ + int status; + + /* tri-state with pull-high, default RS232 Mode */ + status = f81232_set_register(port, F81534A_GPIO_REG, + F81534A_GPIO_MODE2_DIR); + if (status) + return status; + + return f81232_port_probe(port); +} + +static int f81232_suspend(struct usb_serial *serial, pm_message_t message) +{ + struct usb_serial_port *port = serial->port[0]; + struct f81232_private *port_priv = usb_get_serial_port_data(port); + int i; + + for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) + usb_kill_urb(port->read_urbs[i]); + + usb_kill_urb(port->interrupt_in_urb); + + if (port_priv) { + flush_work(&port_priv->interrupt_work); + flush_work(&port_priv->lsr_work); + } + + return 0; +} + +static int f81232_resume(struct usb_serial *serial) +{ + struct usb_serial_port *port = serial->port[0]; + int result; + + if (tty_port_initialized(&port->port)) { + result = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO); + if (result) { + dev_err(&port->dev, "submit interrupt urb failed: %d\n", + result); + return result; + } + } + + return usb_serial_generic_resume(serial); +} + +static struct usb_serial_driver f81232_device = { + .driver = { + .name = "f81232", + }, + .id_table = f81232_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, + .get_serial = f81232_get_serial, + .break_ctl = f81232_break_ctl, + .set_termios = f81232_set_termios, + .tiocmget = f81232_tiocmget, + .tiocmset = f81232_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .tx_empty = f81232_tx_empty, + .process_read_urb = f81232_process_read_urb, + .read_int_callback = f81232_read_int_callback, + .port_probe = f81232_port_probe, + .suspend = f81232_suspend, + .resume = f81232_resume, +}; + +static struct usb_serial_driver f81534a_device = { + .driver = { + .name = "f81534a", + }, + .id_table = f81534a_id_table, + .num_ports = 1, + .open = f81534a_open, + .close = f81232_close, + .dtr_rts = f81232_dtr_rts, + .carrier_raised = f81232_carrier_raised, + .get_serial = f81232_get_serial, + .break_ctl = f81232_break_ctl, + .set_termios = f81232_set_termios, + .tiocmget = f81232_tiocmget, + .tiocmset = f81232_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .tx_empty = f81232_tx_empty, + .process_read_urb = f81534a_process_read_urb, + .read_int_callback = f81232_read_int_callback, + .port_probe = f81534a_port_probe, + .suspend = f81232_suspend, + .resume = f81232_resume, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &f81232_device, + &f81534a_device, + NULL, +}; + +static struct usb_driver f81534a_ctrl_driver = { + .name = "f81534a_ctrl", + .id_table = f81534a_ctrl_id_table, + .probe = f81534a_ctrl_probe, + .disconnect = f81534a_ctrl_disconnect, + .resume = f81534a_ctrl_resume, +}; + +static int __init f81232_init(void) +{ + int status; + + status = usb_register_driver(&f81534a_ctrl_driver, THIS_MODULE, + KBUILD_MODNAME); + if (status) + return status; + + status = usb_serial_register_drivers(serial_drivers, KBUILD_MODNAME, + combined_id_table); + if (status) { + usb_deregister(&f81534a_ctrl_driver); + return status; + } + + return 0; +} + +static void __exit f81232_exit(void) +{ + usb_serial_deregister_drivers(serial_drivers); + usb_deregister(&f81534a_ctrl_driver); +} + +module_init(f81232_init); +module_exit(f81232_exit); + +MODULE_DESCRIPTION("Fintek F81232/532A/534A/535/536 USB to serial driver"); +MODULE_AUTHOR("Greg Kroah-Hartman "); +MODULE_AUTHOR("Peter Hong "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/f81534.c b/drivers/usb/serial/f81534.c new file mode 100644 index 000000000..0037767e4 --- /dev/null +++ b/drivers/usb/serial/f81534.c @@ -0,0 +1,1574 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * F81532/F81534 USB to Serial Ports Bridge + * + * F81532 => 2 Serial Ports + * F81534 => 4 Serial Ports + * + * Copyright (C) 2016 Feature Integration Technology Inc., (Fintek) + * Copyright (C) 2016 Tom Tsai (Tom_Tsai@fintek.com.tw) + * Copyright (C) 2016 Peter Hong (Peter_Hong@fintek.com.tw) + * + * The F81532/F81534 had 1 control endpoint for setting, 1 endpoint bulk-out + * for all serial port TX and 1 endpoint bulk-in for all serial port read in + * (Read Data/MSR/LSR). + * + * Write URB is fixed with 512bytes, per serial port used 128Bytes. + * It can be described by f81534_prepare_write_buffer() + * + * Read URB is 512Bytes max, per serial port used 128Bytes. + * It can be described by f81534_process_read_urb() and maybe received with + * 128x1,2,3,4 bytes. + * + */ +#include +#include +#include +#include +#include +#include +#include + +/* Serial Port register Address */ +#define F81534_UART_BASE_ADDRESS 0x1200 +#define F81534_UART_OFFSET 0x10 +#define F81534_DIVISOR_LSB_REG (0x00 + F81534_UART_BASE_ADDRESS) +#define F81534_DIVISOR_MSB_REG (0x01 + F81534_UART_BASE_ADDRESS) +#define F81534_INTERRUPT_ENABLE_REG (0x01 + F81534_UART_BASE_ADDRESS) +#define F81534_FIFO_CONTROL_REG (0x02 + F81534_UART_BASE_ADDRESS) +#define F81534_LINE_CONTROL_REG (0x03 + F81534_UART_BASE_ADDRESS) +#define F81534_MODEM_CONTROL_REG (0x04 + F81534_UART_BASE_ADDRESS) +#define F81534_LINE_STATUS_REG (0x05 + F81534_UART_BASE_ADDRESS) +#define F81534_MODEM_STATUS_REG (0x06 + F81534_UART_BASE_ADDRESS) +#define F81534_CLOCK_REG (0x08 + F81534_UART_BASE_ADDRESS) +#define F81534_CONFIG1_REG (0x09 + F81534_UART_BASE_ADDRESS) + +#define F81534_DEF_CONF_ADDRESS_START 0x3000 +#define F81534_DEF_CONF_SIZE 12 + +#define F81534_CUSTOM_ADDRESS_START 0x2f00 +#define F81534_CUSTOM_DATA_SIZE 0x10 +#define F81534_CUSTOM_NO_CUSTOM_DATA 0xff +#define F81534_CUSTOM_VALID_TOKEN 0xf0 +#define F81534_CONF_OFFSET 1 +#define F81534_CONF_INIT_GPIO_OFFSET 4 +#define F81534_CONF_WORK_GPIO_OFFSET 8 +#define F81534_CONF_GPIO_SHUTDOWN 7 +#define F81534_CONF_GPIO_RS232 1 + +#define F81534_MAX_DATA_BLOCK 64 +#define F81534_MAX_BUS_RETRY 20 + +/* Default URB timeout for USB operations */ +#define F81534_USB_MAX_RETRY 10 +#define F81534_USB_TIMEOUT 2000 +#define F81534_SET_GET_REGISTER 0xA0 + +#define F81534_NUM_PORT 4 +#define F81534_UNUSED_PORT 0xff +#define F81534_WRITE_BUFFER_SIZE 512 + +#define DRIVER_DESC "Fintek F81532/F81534" +#define FINTEK_VENDOR_ID_1 0x1934 +#define FINTEK_VENDOR_ID_2 0x2C42 +#define FINTEK_DEVICE_ID 0x1202 +#define F81534_MAX_TX_SIZE 124 +#define F81534_MAX_RX_SIZE 124 +#define F81534_RECEIVE_BLOCK_SIZE 128 +#define F81534_MAX_RECEIVE_BLOCK_SIZE 512 + +#define F81534_TOKEN_RECEIVE 0x01 +#define F81534_TOKEN_WRITE 0x02 +#define F81534_TOKEN_TX_EMPTY 0x03 +#define F81534_TOKEN_MSR_CHANGE 0x04 + +/* + * We used interal SPI bus to access FLASH section. We must wait the SPI bus to + * idle if we performed any command. + * + * SPI Bus status register: F81534_BUS_REG_STATUS + * Bit 0/1 : BUSY + * Bit 2 : IDLE + */ +#define F81534_BUS_BUSY (BIT(0) | BIT(1)) +#define F81534_BUS_IDLE BIT(2) +#define F81534_BUS_READ_DATA 0x1004 +#define F81534_BUS_REG_STATUS 0x1003 +#define F81534_BUS_REG_START 0x1002 +#define F81534_BUS_REG_END 0x1001 + +#define F81534_CMD_READ 0x03 + +#define F81534_DEFAULT_BAUD_RATE 9600 + +#define F81534_PORT_CONF_RS232 0 +#define F81534_PORT_CONF_RS485 BIT(0) +#define F81534_PORT_CONF_RS485_INVERT (BIT(0) | BIT(1)) +#define F81534_PORT_CONF_MODE_MASK GENMASK(1, 0) +#define F81534_PORT_CONF_DISABLE_PORT BIT(3) +#define F81534_PORT_CONF_NOT_EXIST_PORT BIT(7) +#define F81534_PORT_UNAVAILABLE \ + (F81534_PORT_CONF_DISABLE_PORT | F81534_PORT_CONF_NOT_EXIST_PORT) + + +#define F81534_1X_RXTRIGGER 0xc3 +#define F81534_8X_RXTRIGGER 0xcf + +/* + * F81532/534 Clock registers (offset +08h) + * + * Bit0: UART Enable (always on) + * Bit2-1: Clock source selector + * 00: 1.846MHz. + * 01: 18.46MHz. + * 10: 24MHz. + * 11: 14.77MHz. + * Bit4: Auto direction(RTS) control (RTS pin Low when TX) + * Bit5: Invert direction(RTS) when Bit4 enabled (RTS pin high when TX) + */ + +#define F81534_UART_EN BIT(0) +#define F81534_CLK_1_846_MHZ 0 +#define F81534_CLK_18_46_MHZ BIT(1) +#define F81534_CLK_24_MHZ BIT(2) +#define F81534_CLK_14_77_MHZ (BIT(1) | BIT(2)) +#define F81534_CLK_MASK GENMASK(2, 1) +#define F81534_CLK_TX_DELAY_1BIT BIT(3) +#define F81534_CLK_RS485_MODE BIT(4) +#define F81534_CLK_RS485_INVERT BIT(5) + +static const struct usb_device_id f81534_id_table[] = { + { USB_DEVICE(FINTEK_VENDOR_ID_1, FINTEK_DEVICE_ID) }, + { USB_DEVICE(FINTEK_VENDOR_ID_2, FINTEK_DEVICE_ID) }, + {} /* Terminating entry */ +}; + +#define F81534_TX_EMPTY_BIT 0 + +struct f81534_serial_private { + u8 conf_data[F81534_DEF_CONF_SIZE]; + int tty_idx[F81534_NUM_PORT]; + u8 setting_idx; + int opened_port; + struct mutex urb_mutex; +}; + +struct f81534_port_private { + struct mutex mcr_mutex; + struct mutex lcr_mutex; + struct work_struct lsr_work; + struct usb_serial_port *port; + unsigned long tx_empty; + spinlock_t msr_lock; + u32 baud_base; + u8 shadow_mcr; + u8 shadow_lcr; + u8 shadow_msr; + u8 shadow_clk; + u8 phy_num; +}; + +struct f81534_pin_data { + const u16 reg_addr; + const u8 reg_mask; +}; + +struct f81534_port_out_pin { + struct f81534_pin_data pin[3]; +}; + +/* Pin output value for M2/M1/M0(SD) */ +static const struct f81534_port_out_pin f81534_port_out_pins[] = { + { { { 0x2ae8, BIT(7) }, { 0x2a90, BIT(5) }, { 0x2a90, BIT(4) } } }, + { { { 0x2ae8, BIT(6) }, { 0x2ae8, BIT(0) }, { 0x2ae8, BIT(3) } } }, + { { { 0x2a90, BIT(0) }, { 0x2ae8, BIT(2) }, { 0x2a80, BIT(6) } } }, + { { { 0x2a90, BIT(3) }, { 0x2a90, BIT(2) }, { 0x2a90, BIT(1) } } }, +}; + +static u32 const baudrate_table[] = { 115200, 921600, 1152000, 1500000 }; +static u8 const clock_table[] = { F81534_CLK_1_846_MHZ, F81534_CLK_14_77_MHZ, + F81534_CLK_18_46_MHZ, F81534_CLK_24_MHZ }; + +static int f81534_logic_to_phy_port(struct usb_serial *serial, + struct usb_serial_port *port) +{ + struct f81534_serial_private *serial_priv = + usb_get_serial_data(port->serial); + int count = 0; + int i; + + for (i = 0; i < F81534_NUM_PORT; ++i) { + if (serial_priv->conf_data[i] & F81534_PORT_UNAVAILABLE) + continue; + + if (port->port_number == count) + return i; + + ++count; + } + + return -ENODEV; +} + +static int f81534_set_register(struct usb_serial *serial, u16 reg, u8 data) +{ + struct usb_interface *interface = serial->interface; + struct usb_device *dev = serial->dev; + size_t count = F81534_USB_MAX_RETRY; + int status; + u8 *tmp; + + tmp = kmalloc(sizeof(u8), GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + *tmp = data; + + /* + * Our device maybe not reply when heavily loading, We'll retry for + * F81534_USB_MAX_RETRY times. + */ + while (count--) { + status = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + F81534_SET_GET_REGISTER, + USB_TYPE_VENDOR | USB_DIR_OUT, + reg, 0, tmp, sizeof(u8), + F81534_USB_TIMEOUT); + if (status == sizeof(u8)) { + status = 0; + break; + } + } + + if (status < 0) { + dev_err(&interface->dev, "%s: reg: %x data: %x failed: %d\n", + __func__, reg, data, status); + } + + kfree(tmp); + return status; +} + +static int f81534_get_register(struct usb_serial *serial, u16 reg, u8 *data) +{ + struct usb_interface *interface = serial->interface; + struct usb_device *dev = serial->dev; + size_t count = F81534_USB_MAX_RETRY; + int status; + u8 *tmp; + + tmp = kmalloc(sizeof(u8), GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + /* + * Our device maybe not reply when heavily loading, We'll retry for + * F81534_USB_MAX_RETRY times. + */ + while (count--) { + status = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + F81534_SET_GET_REGISTER, + USB_TYPE_VENDOR | USB_DIR_IN, + reg, 0, tmp, sizeof(u8), + F81534_USB_TIMEOUT); + if (status > 0) { + status = 0; + break; + } else if (status == 0) { + status = -EIO; + } + } + + if (status < 0) { + dev_err(&interface->dev, "%s: reg: %x failed: %d\n", __func__, + reg, status); + goto end; + } + + *data = *tmp; + +end: + kfree(tmp); + return status; +} + +static int f81534_set_mask_register(struct usb_serial *serial, u16 reg, + u8 mask, u8 data) +{ + int status; + u8 tmp; + + status = f81534_get_register(serial, reg, &tmp); + if (status) + return status; + + tmp &= ~mask; + tmp |= (mask & data); + + return f81534_set_register(serial, reg, tmp); +} + +static int f81534_set_phy_port_register(struct usb_serial *serial, int phy, + u16 reg, u8 data) +{ + return f81534_set_register(serial, reg + F81534_UART_OFFSET * phy, + data); +} + +static int f81534_get_phy_port_register(struct usb_serial *serial, int phy, + u16 reg, u8 *data) +{ + return f81534_get_register(serial, reg + F81534_UART_OFFSET * phy, + data); +} + +static int f81534_set_port_register(struct usb_serial_port *port, u16 reg, + u8 data) +{ + struct f81534_port_private *port_priv = usb_get_serial_port_data(port); + + return f81534_set_register(port->serial, + reg + port_priv->phy_num * F81534_UART_OFFSET, data); +} + +static int f81534_get_port_register(struct usb_serial_port *port, u16 reg, + u8 *data) +{ + struct f81534_port_private *port_priv = usb_get_serial_port_data(port); + + return f81534_get_register(port->serial, + reg + port_priv->phy_num * F81534_UART_OFFSET, data); +} + +/* + * If we try to access the internal flash via SPI bus, we should check the bus + * status for every command. e.g., F81534_BUS_REG_START/F81534_BUS_REG_END + */ +static int f81534_wait_for_spi_idle(struct usb_serial *serial) +{ + size_t count = F81534_MAX_BUS_RETRY; + u8 tmp; + int status; + + do { + status = f81534_get_register(serial, F81534_BUS_REG_STATUS, + &tmp); + if (status) + return status; + + if (tmp & F81534_BUS_BUSY) + continue; + + if (tmp & F81534_BUS_IDLE) + break; + + } while (--count); + + if (!count) { + dev_err(&serial->interface->dev, + "%s: timed out waiting for idle SPI bus\n", + __func__); + return -EIO; + } + + return f81534_set_register(serial, F81534_BUS_REG_STATUS, + tmp & ~F81534_BUS_IDLE); +} + +static int f81534_get_spi_register(struct usb_serial *serial, u16 reg, + u8 *data) +{ + int status; + + status = f81534_get_register(serial, reg, data); + if (status) + return status; + + return f81534_wait_for_spi_idle(serial); +} + +static int f81534_set_spi_register(struct usb_serial *serial, u16 reg, u8 data) +{ + int status; + + status = f81534_set_register(serial, reg, data); + if (status) + return status; + + return f81534_wait_for_spi_idle(serial); +} + +static int f81534_read_flash(struct usb_serial *serial, u32 address, + size_t size, u8 *buf) +{ + u8 tmp_buf[F81534_MAX_DATA_BLOCK]; + size_t block = 0; + size_t read_size; + size_t count; + int status; + int offset; + u16 reg_tmp; + + status = f81534_set_spi_register(serial, F81534_BUS_REG_START, + F81534_CMD_READ); + if (status) + return status; + + status = f81534_set_spi_register(serial, F81534_BUS_REG_START, + (address >> 16) & 0xff); + if (status) + return status; + + status = f81534_set_spi_register(serial, F81534_BUS_REG_START, + (address >> 8) & 0xff); + if (status) + return status; + + status = f81534_set_spi_register(serial, F81534_BUS_REG_START, + (address >> 0) & 0xff); + if (status) + return status; + + /* Continuous read mode */ + do { + read_size = min_t(size_t, F81534_MAX_DATA_BLOCK, size); + + for (count = 0; count < read_size; ++count) { + /* To write F81534_BUS_REG_END when final byte */ + if (size <= F81534_MAX_DATA_BLOCK && + read_size == count + 1) + reg_tmp = F81534_BUS_REG_END; + else + reg_tmp = F81534_BUS_REG_START; + + /* + * Dummy code, force IC to generate a read pulse, the + * set of value 0xf1 is dont care (any value is ok) + */ + status = f81534_set_spi_register(serial, reg_tmp, + 0xf1); + if (status) + return status; + + status = f81534_get_spi_register(serial, + F81534_BUS_READ_DATA, + &tmp_buf[count]); + if (status) + return status; + + offset = count + block * F81534_MAX_DATA_BLOCK; + buf[offset] = tmp_buf[count]; + } + + size -= read_size; + ++block; + } while (size); + + return 0; +} + +static void f81534_prepare_write_buffer(struct usb_serial_port *port, u8 *buf) +{ + struct f81534_port_private *port_priv = usb_get_serial_port_data(port); + int phy_num = port_priv->phy_num; + u8 tx_len; + int i; + + /* + * The block layout is fixed with 4x128 Bytes, per 128 Bytes a port. + * index 0: port phy idx (e.g., 0,1,2,3) + * index 1: only F81534_TOKEN_WRITE + * index 2: serial TX out length + * index 3: fix to 0 + * index 4~127: serial out data block + */ + for (i = 0; i < F81534_NUM_PORT; ++i) { + buf[i * F81534_RECEIVE_BLOCK_SIZE] = i; + buf[i * F81534_RECEIVE_BLOCK_SIZE + 1] = F81534_TOKEN_WRITE; + buf[i * F81534_RECEIVE_BLOCK_SIZE + 2] = 0; + buf[i * F81534_RECEIVE_BLOCK_SIZE + 3] = 0; + } + + tx_len = kfifo_out_locked(&port->write_fifo, + &buf[phy_num * F81534_RECEIVE_BLOCK_SIZE + 4], + F81534_MAX_TX_SIZE, &port->lock); + + buf[phy_num * F81534_RECEIVE_BLOCK_SIZE + 2] = tx_len; +} + +static int f81534_submit_writer(struct usb_serial_port *port, gfp_t mem_flags) +{ + struct f81534_port_private *port_priv = usb_get_serial_port_data(port); + struct urb *urb; + unsigned long flags; + int result; + + /* Check is any data in write_fifo */ + spin_lock_irqsave(&port->lock, flags); + + if (kfifo_is_empty(&port->write_fifo)) { + spin_unlock_irqrestore(&port->lock, flags); + return 0; + } + + spin_unlock_irqrestore(&port->lock, flags); + + /* Check H/W is TXEMPTY */ + if (!test_and_clear_bit(F81534_TX_EMPTY_BIT, &port_priv->tx_empty)) + return 0; + + urb = port->write_urbs[0]; + f81534_prepare_write_buffer(port, port->bulk_out_buffers[0]); + urb->transfer_buffer_length = F81534_WRITE_BUFFER_SIZE; + + result = usb_submit_urb(urb, mem_flags); + if (result) { + set_bit(F81534_TX_EMPTY_BIT, &port_priv->tx_empty); + dev_err(&port->dev, "%s: submit failed: %d\n", __func__, + result); + return result; + } + + usb_serial_port_softint(port); + return 0; +} + +static u32 f81534_calc_baud_divisor(u32 baudrate, u32 clockrate) +{ + /* Round to nearest divisor */ + return DIV_ROUND_CLOSEST(clockrate, baudrate); +} + +static int f81534_find_clk(u32 baudrate) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(baudrate_table); ++idx) { + if (baudrate <= baudrate_table[idx] && + baudrate_table[idx] % baudrate == 0) + return idx; + } + + return -EINVAL; +} + +static int f81534_set_port_config(struct usb_serial_port *port, + struct tty_struct *tty, u32 baudrate, u32 old_baudrate, u8 lcr) +{ + struct f81534_port_private *port_priv = usb_get_serial_port_data(port); + u32 divisor; + int status; + int i; + int idx; + u8 value; + u32 baud_list[] = {baudrate, old_baudrate, F81534_DEFAULT_BAUD_RATE}; + + for (i = 0; i < ARRAY_SIZE(baud_list); ++i) { + baudrate = baud_list[i]; + if (baudrate == 0) { + tty_encode_baud_rate(tty, 0, 0); + return 0; + } + + idx = f81534_find_clk(baudrate); + if (idx >= 0) { + tty_encode_baud_rate(tty, baudrate, baudrate); + break; + } + } + + if (idx < 0) + return -EINVAL; + + port_priv->baud_base = baudrate_table[idx]; + port_priv->shadow_clk &= ~F81534_CLK_MASK; + port_priv->shadow_clk |= clock_table[idx]; + + status = f81534_set_port_register(port, F81534_CLOCK_REG, + port_priv->shadow_clk); + if (status) { + dev_err(&port->dev, "CLOCK_REG setting failed\n"); + return status; + } + + if (baudrate <= 1200) + value = F81534_1X_RXTRIGGER; /* 128 FIFO & TL: 1x */ + else + value = F81534_8X_RXTRIGGER; /* 128 FIFO & TL: 8x */ + + status = f81534_set_port_register(port, F81534_CONFIG1_REG, value); + if (status) { + dev_err(&port->dev, "%s: CONFIG1 setting failed\n", __func__); + return status; + } + + if (baudrate <= 1200) + value = UART_FCR_TRIGGER_1 | UART_FCR_ENABLE_FIFO; /* TL: 1 */ + else + value = UART_FCR_TRIGGER_8 | UART_FCR_ENABLE_FIFO; /* TL: 8 */ + + status = f81534_set_port_register(port, F81534_FIFO_CONTROL_REG, + value); + if (status) { + dev_err(&port->dev, "%s: FCR setting failed\n", __func__); + return status; + } + + divisor = f81534_calc_baud_divisor(baudrate, port_priv->baud_base); + + mutex_lock(&port_priv->lcr_mutex); + + value = UART_LCR_DLAB; + status = f81534_set_port_register(port, F81534_LINE_CONTROL_REG, + value); + if (status) { + dev_err(&port->dev, "%s: set LCR failed\n", __func__); + goto out_unlock; + } + + value = divisor & 0xff; + status = f81534_set_port_register(port, F81534_DIVISOR_LSB_REG, value); + if (status) { + dev_err(&port->dev, "%s: set DLAB LSB failed\n", __func__); + goto out_unlock; + } + + value = (divisor >> 8) & 0xff; + status = f81534_set_port_register(port, F81534_DIVISOR_MSB_REG, value); + if (status) { + dev_err(&port->dev, "%s: set DLAB MSB failed\n", __func__); + goto out_unlock; + } + + value = lcr | (port_priv->shadow_lcr & UART_LCR_SBC); + status = f81534_set_port_register(port, F81534_LINE_CONTROL_REG, + value); + if (status) { + dev_err(&port->dev, "%s: set LCR failed\n", __func__); + goto out_unlock; + } + + port_priv->shadow_lcr = value; +out_unlock: + mutex_unlock(&port_priv->lcr_mutex); + + return status; +} + +static int f81534_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct f81534_port_private *port_priv = usb_get_serial_port_data(port); + int status; + + mutex_lock(&port_priv->lcr_mutex); + + if (break_state) + port_priv->shadow_lcr |= UART_LCR_SBC; + else + port_priv->shadow_lcr &= ~UART_LCR_SBC; + + status = f81534_set_port_register(port, F81534_LINE_CONTROL_REG, + port_priv->shadow_lcr); + if (status) + dev_err(&port->dev, "set break failed: %d\n", status); + + mutex_unlock(&port_priv->lcr_mutex); + + return status; +} + +static int f81534_update_mctrl(struct usb_serial_port *port, unsigned int set, + unsigned int clear) +{ + struct f81534_port_private *port_priv = usb_get_serial_port_data(port); + int status; + u8 tmp; + + if (((set | clear) & (TIOCM_DTR | TIOCM_RTS)) == 0) + return 0; /* no change */ + + mutex_lock(&port_priv->mcr_mutex); + + /* 'Set' takes precedence over 'Clear' */ + clear &= ~set; + + /* Always enable UART_MCR_OUT2 */ + tmp = UART_MCR_OUT2 | port_priv->shadow_mcr; + + if (clear & TIOCM_DTR) + tmp &= ~UART_MCR_DTR; + + if (clear & TIOCM_RTS) + tmp &= ~UART_MCR_RTS; + + if (set & TIOCM_DTR) + tmp |= UART_MCR_DTR; + + if (set & TIOCM_RTS) + tmp |= UART_MCR_RTS; + + status = f81534_set_port_register(port, F81534_MODEM_CONTROL_REG, tmp); + if (status < 0) { + dev_err(&port->dev, "%s: MCR write failed\n", __func__); + mutex_unlock(&port_priv->mcr_mutex); + return status; + } + + port_priv->shadow_mcr = tmp; + mutex_unlock(&port_priv->mcr_mutex); + return 0; +} + +/* + * This function will search the data area with token F81534_CUSTOM_VALID_TOKEN + * for latest configuration index. If nothing found + * (*index = F81534_CUSTOM_NO_CUSTOM_DATA), We'll load default configure in + * F81534_DEF_CONF_ADDRESS_START section. + * + * Due to we only use block0 to save data, so *index should be 0 or + * F81534_CUSTOM_NO_CUSTOM_DATA. + */ +static int f81534_find_config_idx(struct usb_serial *serial, u8 *index) +{ + u8 tmp; + int status; + + status = f81534_read_flash(serial, F81534_CUSTOM_ADDRESS_START, 1, + &tmp); + if (status) { + dev_err(&serial->interface->dev, "%s: read failed: %d\n", + __func__, status); + return status; + } + + /* We'll use the custom data when the data is valid. */ + if (tmp == F81534_CUSTOM_VALID_TOKEN) + *index = 0; + else + *index = F81534_CUSTOM_NO_CUSTOM_DATA; + + return 0; +} + +/* + * The F81532/534 will not report serial port to USB serial subsystem when + * H/W DCD/DSR/CTS/RI/RX pin connected to ground. + * + * To detect RX pin status, we'll enable MCR interal loopback, disable it and + * delayed for 60ms. It connected to ground If LSR register report UART_LSR_BI. + */ +static bool f81534_check_port_hw_disabled(struct usb_serial *serial, int phy) +{ + int status; + u8 old_mcr; + u8 msr; + u8 lsr; + u8 msr_mask; + + msr_mask = UART_MSR_DCD | UART_MSR_RI | UART_MSR_DSR | UART_MSR_CTS; + + status = f81534_get_phy_port_register(serial, phy, + F81534_MODEM_STATUS_REG, &msr); + if (status) + return false; + + if ((msr & msr_mask) != msr_mask) + return false; + + status = f81534_set_phy_port_register(serial, phy, + F81534_FIFO_CONTROL_REG, UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); + if (status) + return false; + + status = f81534_get_phy_port_register(serial, phy, + F81534_MODEM_CONTROL_REG, &old_mcr); + if (status) + return false; + + status = f81534_set_phy_port_register(serial, phy, + F81534_MODEM_CONTROL_REG, UART_MCR_LOOP); + if (status) + return false; + + status = f81534_set_phy_port_register(serial, phy, + F81534_MODEM_CONTROL_REG, 0x0); + if (status) + return false; + + msleep(60); + + status = f81534_get_phy_port_register(serial, phy, + F81534_LINE_STATUS_REG, &lsr); + if (status) + return false; + + status = f81534_set_phy_port_register(serial, phy, + F81534_MODEM_CONTROL_REG, old_mcr); + if (status) + return false; + + if ((lsr & UART_LSR_BI) == UART_LSR_BI) + return true; + + return false; +} + +/* + * We had 2 generation of F81532/534 IC. All has an internal storage. + * + * 1st is pure USB-to-TTL RS232 IC and designed for 4 ports only, no any + * internal data will used. All mode and gpio control should manually set + * by AP or Driver and all storage space value are 0xff. The + * f81534_calc_num_ports() will run to final we marked as "oldest version" + * for this IC. + * + * 2rd is designed to more generic to use any transceiver and this is our + * mass production type. We'll save data in F81534_CUSTOM_ADDRESS_START + * (0x2f00) with 9bytes. The 1st byte is a indicater. If the token is + * F81534_CUSTOM_VALID_TOKEN(0xf0), the IC is 2nd gen type, the following + * 4bytes save port mode (0:RS232/1:RS485 Invert/2:RS485), and the last + * 4bytes save GPIO state(value from 0~7 to represent 3 GPIO output pin). + * The f81534_calc_num_ports() will run to "new style" with checking + * F81534_PORT_UNAVAILABLE section. + */ +static int f81534_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + struct f81534_serial_private *serial_priv; + struct device *dev = &serial->interface->dev; + int size_bulk_in = usb_endpoint_maxp(epds->bulk_in[0]); + int size_bulk_out = usb_endpoint_maxp(epds->bulk_out[0]); + u8 num_port = 0; + int index = 0; + int status; + int i; + + if (size_bulk_out != F81534_WRITE_BUFFER_SIZE || + size_bulk_in != F81534_MAX_RECEIVE_BLOCK_SIZE) { + dev_err(dev, "unsupported endpoint max packet size\n"); + return -ENODEV; + } + + serial_priv = devm_kzalloc(&serial->interface->dev, + sizeof(*serial_priv), GFP_KERNEL); + if (!serial_priv) + return -ENOMEM; + + usb_set_serial_data(serial, serial_priv); + mutex_init(&serial_priv->urb_mutex); + + /* Check had custom setting */ + status = f81534_find_config_idx(serial, &serial_priv->setting_idx); + if (status) { + dev_err(&serial->interface->dev, "%s: find idx failed: %d\n", + __func__, status); + return status; + } + + /* + * We'll read custom data only when data available, otherwise we'll + * read default value instead. + */ + if (serial_priv->setting_idx != F81534_CUSTOM_NO_CUSTOM_DATA) { + status = f81534_read_flash(serial, + F81534_CUSTOM_ADDRESS_START + + F81534_CONF_OFFSET, + sizeof(serial_priv->conf_data), + serial_priv->conf_data); + if (status) { + dev_err(&serial->interface->dev, + "%s: get custom data failed: %d\n", + __func__, status); + return status; + } + + dev_dbg(&serial->interface->dev, + "%s: read config from block: %d\n", __func__, + serial_priv->setting_idx); + } else { + /* Read default board setting */ + status = f81534_read_flash(serial, + F81534_DEF_CONF_ADDRESS_START, + sizeof(serial_priv->conf_data), + serial_priv->conf_data); + if (status) { + dev_err(&serial->interface->dev, + "%s: read failed: %d\n", __func__, + status); + return status; + } + + dev_dbg(&serial->interface->dev, "%s: read default config\n", + __func__); + } + + /* New style, find all possible ports */ + for (i = 0; i < F81534_NUM_PORT; ++i) { + if (f81534_check_port_hw_disabled(serial, i)) + serial_priv->conf_data[i] |= F81534_PORT_UNAVAILABLE; + + if (serial_priv->conf_data[i] & F81534_PORT_UNAVAILABLE) + continue; + + ++num_port; + } + + if (!num_port) { + dev_warn(&serial->interface->dev, + "no config found, assuming 4 ports\n"); + num_port = 4; /* Nothing found, oldest version IC */ + } + + /* Assign phy-to-logic mapping */ + for (i = 0; i < F81534_NUM_PORT; ++i) { + if (serial_priv->conf_data[i] & F81534_PORT_UNAVAILABLE) + continue; + + serial_priv->tty_idx[i] = index++; + dev_dbg(&serial->interface->dev, + "%s: phy_num: %d, tty_idx: %d\n", __func__, i, + serial_priv->tty_idx[i]); + } + + /* + * Setup bulk-out endpoint multiplexing. All ports share the same + * bulk-out endpoint. + */ + BUILD_BUG_ON(ARRAY_SIZE(epds->bulk_out) < F81534_NUM_PORT); + + for (i = 1; i < num_port; ++i) + epds->bulk_out[i] = epds->bulk_out[0]; + + epds->num_bulk_out = num_port; + + return num_port; +} + +static void f81534_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +{ + u8 new_lcr = 0; + int status; + u32 baud; + u32 old_baud; + + if (C_BAUD(tty) == B0) + f81534_update_mctrl(port, 0, TIOCM_DTR | TIOCM_RTS); + else if (old_termios && (old_termios->c_cflag & CBAUD) == B0) + f81534_update_mctrl(port, TIOCM_DTR | TIOCM_RTS, 0); + + if (C_PARENB(tty)) { + new_lcr |= UART_LCR_PARITY; + + if (!C_PARODD(tty)) + new_lcr |= UART_LCR_EPAR; + + if (C_CMSPAR(tty)) + new_lcr |= UART_LCR_SPAR; + } + + if (C_CSTOPB(tty)) + new_lcr |= UART_LCR_STOP; + + new_lcr |= UART_LCR_WLEN(tty_get_char_size(tty->termios.c_cflag)); + + baud = tty_get_baud_rate(tty); + if (!baud) + return; + + if (old_termios) + old_baud = tty_termios_baud_rate(old_termios); + else + old_baud = F81534_DEFAULT_BAUD_RATE; + + dev_dbg(&port->dev, "%s: baud: %d\n", __func__, baud); + + status = f81534_set_port_config(port, tty, baud, old_baud, new_lcr); + if (status < 0) { + dev_err(&port->dev, "%s: set port config failed: %d\n", + __func__, status); + } +} + +static int f81534_submit_read_urb(struct usb_serial *serial, gfp_t flags) +{ + return usb_serial_generic_submit_read_urbs(serial->port[0], flags); +} + +static void f81534_msr_changed(struct usb_serial_port *port, u8 msr) +{ + struct f81534_port_private *port_priv = usb_get_serial_port_data(port); + struct tty_struct *tty; + unsigned long flags; + u8 old_msr; + + if (!(msr & UART_MSR_ANY_DELTA)) + return; + + spin_lock_irqsave(&port_priv->msr_lock, flags); + old_msr = port_priv->shadow_msr; + port_priv->shadow_msr = msr; + spin_unlock_irqrestore(&port_priv->msr_lock, flags); + + dev_dbg(&port->dev, "%s: MSR from %02x to %02x\n", __func__, old_msr, + msr); + + /* 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); + + if (!(msr & UART_MSR_DDCD)) + return; + + dev_dbg(&port->dev, "%s: DCD Changed: phy_num: %d from %x to %x\n", + __func__, port_priv->phy_num, old_msr, msr); + + tty = tty_port_tty_get(&port->port); + if (!tty) + return; + + usb_serial_handle_dcd_change(port, tty, msr & UART_MSR_DCD); + tty_kref_put(tty); +} + +static int f81534_read_msr(struct usb_serial_port *port) +{ + struct f81534_port_private *port_priv = usb_get_serial_port_data(port); + unsigned long flags; + int status; + u8 msr; + + /* Get MSR initial value */ + status = f81534_get_port_register(port, F81534_MODEM_STATUS_REG, &msr); + if (status) + return status; + + /* Force update current state */ + spin_lock_irqsave(&port_priv->msr_lock, flags); + port_priv->shadow_msr = msr; + spin_unlock_irqrestore(&port_priv->msr_lock, flags); + + return 0; +} + +static int f81534_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct f81534_serial_private *serial_priv = + usb_get_serial_data(port->serial); + struct f81534_port_private *port_priv = usb_get_serial_port_data(port); + int status; + + status = f81534_set_port_register(port, + F81534_FIFO_CONTROL_REG, UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); + if (status) { + dev_err(&port->dev, "%s: Clear FIFO failed: %d\n", __func__, + status); + return status; + } + + if (tty) + f81534_set_termios(tty, port, NULL); + + status = f81534_read_msr(port); + if (status) + return status; + + mutex_lock(&serial_priv->urb_mutex); + + /* Submit Read URBs for first port opened */ + if (!serial_priv->opened_port) { + status = f81534_submit_read_urb(port->serial, GFP_KERNEL); + if (status) + goto exit; + } + + serial_priv->opened_port++; + +exit: + mutex_unlock(&serial_priv->urb_mutex); + + set_bit(F81534_TX_EMPTY_BIT, &port_priv->tx_empty); + return status; +} + +static void f81534_close(struct usb_serial_port *port) +{ + struct f81534_serial_private *serial_priv = + usb_get_serial_data(port->serial); + struct usb_serial_port *port0 = port->serial->port[0]; + unsigned long flags; + size_t i; + + usb_kill_urb(port->write_urbs[0]); + + spin_lock_irqsave(&port->lock, flags); + kfifo_reset_out(&port->write_fifo); + spin_unlock_irqrestore(&port->lock, flags); + + /* Kill Read URBs when final port closed */ + mutex_lock(&serial_priv->urb_mutex); + serial_priv->opened_port--; + + if (!serial_priv->opened_port) { + for (i = 0; i < ARRAY_SIZE(port0->read_urbs); ++i) + usb_kill_urb(port0->read_urbs[i]); + } + + mutex_unlock(&serial_priv->urb_mutex); +} + +static void f81534_get_serial_info(struct tty_struct *tty, struct serial_struct *ss) +{ + struct usb_serial_port *port = tty->driver_data; + struct f81534_port_private *port_priv; + + port_priv = usb_get_serial_port_data(port); + + ss->baud_base = port_priv->baud_base; +} + +static void f81534_process_per_serial_block(struct usb_serial_port *port, + u8 *data) +{ + struct f81534_port_private *port_priv = usb_get_serial_port_data(port); + int phy_num = data[0]; + size_t read_size = 0; + size_t i; + char tty_flag; + int status; + u8 lsr; + + /* + * The block layout is 128 Bytes + * index 0: port phy idx (e.g., 0,1,2,3), + * index 1: It's could be + * F81534_TOKEN_RECEIVE + * F81534_TOKEN_TX_EMPTY + * F81534_TOKEN_MSR_CHANGE + * index 2: serial in size (data+lsr, must be even) + * meaningful for F81534_TOKEN_RECEIVE only + * index 3: current MSR with this device + * index 4~127: serial in data block (data+lsr, must be even) + */ + switch (data[1]) { + case F81534_TOKEN_TX_EMPTY: + set_bit(F81534_TX_EMPTY_BIT, &port_priv->tx_empty); + + /* Try to submit writer */ + status = f81534_submit_writer(port, GFP_ATOMIC); + if (status) + dev_err(&port->dev, "%s: submit failed\n", __func__); + return; + + case F81534_TOKEN_MSR_CHANGE: + f81534_msr_changed(port, data[3]); + return; + + case F81534_TOKEN_RECEIVE: + read_size = data[2]; + if (read_size > F81534_MAX_RX_SIZE) { + dev_err(&port->dev, + "%s: phy: %d read_size: %zu larger than: %d\n", + __func__, phy_num, read_size, + F81534_MAX_RX_SIZE); + return; + } + + break; + + default: + dev_warn(&port->dev, "%s: unknown token: %02x\n", __func__, + data[1]); + return; + } + + for (i = 4; i < 4 + read_size; i += 2) { + tty_flag = TTY_NORMAL; + lsr = data[i + 1]; + + if (lsr & UART_LSR_BRK_ERROR_BITS) { + if (lsr & UART_LSR_BI) { + tty_flag = TTY_BREAK; + port->icount.brk++; + usb_serial_handle_break(port); + } else if (lsr & UART_LSR_PE) { + tty_flag = TTY_PARITY; + port->icount.parity++; + } else if (lsr & UART_LSR_FE) { + tty_flag = TTY_FRAME; + port->icount.frame++; + } + + if (lsr & UART_LSR_OE) { + port->icount.overrun++; + tty_insert_flip_char(&port->port, 0, + TTY_OVERRUN); + } + + schedule_work(&port_priv->lsr_work); + } + + if (port->sysrq) { + if (usb_serial_handle_sysrq_char(port, data[i])) + continue; + } + + tty_insert_flip_char(&port->port, data[i], tty_flag); + } + + tty_flip_buffer_push(&port->port); +} + +static void f81534_process_read_urb(struct urb *urb) +{ + struct f81534_serial_private *serial_priv; + struct usb_serial_port *port; + struct usb_serial *serial; + u8 *buf; + int phy_port_num; + int tty_port_num; + size_t i; + + if (!urb->actual_length || + urb->actual_length % F81534_RECEIVE_BLOCK_SIZE) { + return; + } + + port = urb->context; + serial = port->serial; + buf = urb->transfer_buffer; + serial_priv = usb_get_serial_data(serial); + + for (i = 0; i < urb->actual_length; i += F81534_RECEIVE_BLOCK_SIZE) { + phy_port_num = buf[i]; + if (phy_port_num >= F81534_NUM_PORT) { + dev_err(&port->dev, + "%s: phy_port_num: %d larger than: %d\n", + __func__, phy_port_num, F81534_NUM_PORT); + continue; + } + + tty_port_num = serial_priv->tty_idx[phy_port_num]; + port = serial->port[tty_port_num]; + + if (tty_port_initialized(&port->port)) + f81534_process_per_serial_block(port, &buf[i]); + } +} + +static void f81534_write_usb_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + + switch (urb->status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + dev_dbg(&port->dev, "%s - urb stopped: %d\n", + __func__, urb->status); + return; + case -EPIPE: + dev_err(&port->dev, "%s - urb stopped: %d\n", + __func__, urb->status); + return; + default: + dev_dbg(&port->dev, "%s - nonzero urb status: %d\n", + __func__, urb->status); + break; + } +} + +static void f81534_lsr_worker(struct work_struct *work) +{ + struct f81534_port_private *port_priv; + struct usb_serial_port *port; + int status; + u8 tmp; + + port_priv = container_of(work, struct f81534_port_private, lsr_work); + port = port_priv->port; + + status = f81534_get_port_register(port, F81534_LINE_STATUS_REG, &tmp); + if (status) + dev_warn(&port->dev, "read LSR failed: %d\n", status); +} + +static int f81534_set_port_output_pin(struct usb_serial_port *port) +{ + struct f81534_serial_private *serial_priv; + struct f81534_port_private *port_priv; + struct usb_serial *serial; + const struct f81534_port_out_pin *pins; + int status; + int i; + u8 value; + u8 idx; + + serial = port->serial; + serial_priv = usb_get_serial_data(serial); + port_priv = usb_get_serial_port_data(port); + + idx = F81534_CONF_INIT_GPIO_OFFSET + port_priv->phy_num; + value = serial_priv->conf_data[idx]; + if (value >= F81534_CONF_GPIO_SHUTDOWN) { + /* + * Newer IC configure will make transceiver in shutdown mode on + * initial power on. We need enable it before using UARTs. + */ + idx = F81534_CONF_WORK_GPIO_OFFSET + port_priv->phy_num; + value = serial_priv->conf_data[idx]; + if (value >= F81534_CONF_GPIO_SHUTDOWN) + value = F81534_CONF_GPIO_RS232; + } + + pins = &f81534_port_out_pins[port_priv->phy_num]; + + for (i = 0; i < ARRAY_SIZE(pins->pin); ++i) { + status = f81534_set_mask_register(serial, + pins->pin[i].reg_addr, pins->pin[i].reg_mask, + value & BIT(i) ? pins->pin[i].reg_mask : 0); + if (status) + return status; + } + + dev_dbg(&port->dev, "Output pin (M0/M1/M2): %d\n", value); + return 0; +} + +static int f81534_port_probe(struct usb_serial_port *port) +{ + struct f81534_serial_private *serial_priv; + struct f81534_port_private *port_priv; + int ret; + u8 value; + + serial_priv = usb_get_serial_data(port->serial); + port_priv = devm_kzalloc(&port->dev, sizeof(*port_priv), GFP_KERNEL); + if (!port_priv) + return -ENOMEM; + + /* + * We'll make tx frame error when baud rate from 384~500kps. So we'll + * delay all tx data frame with 1bit. + */ + port_priv->shadow_clk = F81534_UART_EN | F81534_CLK_TX_DELAY_1BIT; + spin_lock_init(&port_priv->msr_lock); + mutex_init(&port_priv->mcr_mutex); + mutex_init(&port_priv->lcr_mutex); + INIT_WORK(&port_priv->lsr_work, f81534_lsr_worker); + + /* Assign logic-to-phy mapping */ + ret = f81534_logic_to_phy_port(port->serial, port); + if (ret < 0) + return ret; + + port_priv->phy_num = ret; + port_priv->port = port; + usb_set_serial_port_data(port, port_priv); + dev_dbg(&port->dev, "%s: port_number: %d, phy_num: %d\n", __func__, + port->port_number, port_priv->phy_num); + + /* + * The F81532/534 will hang-up when enable LSR interrupt in IER and + * occur data overrun. So we'll disable the LSR interrupt in probe() + * and submit the LSR worker to clear LSR state when reported LSR error + * bit with bulk-in data in f81534_process_per_serial_block(). + */ + ret = f81534_set_port_register(port, F81534_INTERRUPT_ENABLE_REG, + UART_IER_RDI | UART_IER_THRI | UART_IER_MSI); + if (ret) + return ret; + + value = serial_priv->conf_data[port_priv->phy_num]; + switch (value & F81534_PORT_CONF_MODE_MASK) { + case F81534_PORT_CONF_RS485_INVERT: + port_priv->shadow_clk |= F81534_CLK_RS485_MODE | + F81534_CLK_RS485_INVERT; + dev_dbg(&port->dev, "RS485 invert mode\n"); + break; + case F81534_PORT_CONF_RS485: + port_priv->shadow_clk |= F81534_CLK_RS485_MODE; + dev_dbg(&port->dev, "RS485 mode\n"); + break; + + default: + case F81534_PORT_CONF_RS232: + dev_dbg(&port->dev, "RS232 mode\n"); + break; + } + + return f81534_set_port_output_pin(port); +} + +static void f81534_port_remove(struct usb_serial_port *port) +{ + struct f81534_port_private *port_priv = usb_get_serial_port_data(port); + + flush_work(&port_priv->lsr_work); +} + +static int f81534_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct f81534_port_private *port_priv = usb_get_serial_port_data(port); + int status; + int r; + u8 msr; + u8 mcr; + + /* Read current MSR from device */ + status = f81534_get_port_register(port, F81534_MODEM_STATUS_REG, &msr); + if (status) + return status; + + mutex_lock(&port_priv->mcr_mutex); + mcr = port_priv->shadow_mcr; + mutex_unlock(&port_priv->mcr_mutex); + + r = (mcr & UART_MCR_DTR ? TIOCM_DTR : 0) | + (mcr & UART_MCR_RTS ? TIOCM_RTS : 0) | + (msr & UART_MSR_CTS ? TIOCM_CTS : 0) | + (msr & UART_MSR_DCD ? TIOCM_CAR : 0) | + (msr & UART_MSR_RI ? TIOCM_RI : 0) | + (msr & UART_MSR_DSR ? TIOCM_DSR : 0); + + return r; +} + +static int f81534_tiocmset(struct tty_struct *tty, unsigned int set, + unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + + return f81534_update_mctrl(port, set, clear); +} + +static void f81534_dtr_rts(struct usb_serial_port *port, int on) +{ + if (on) + f81534_update_mctrl(port, TIOCM_DTR | TIOCM_RTS, 0); + else + f81534_update_mctrl(port, 0, TIOCM_DTR | TIOCM_RTS); +} + +static int f81534_write(struct tty_struct *tty, struct usb_serial_port *port, + const u8 *buf, int count) +{ + int bytes_out, status; + + if (!count) + return 0; + + bytes_out = kfifo_in_locked(&port->write_fifo, buf, count, + &port->lock); + + status = f81534_submit_writer(port, GFP_ATOMIC); + if (status) { + dev_err(&port->dev, "%s: submit failed\n", __func__); + return status; + } + + return bytes_out; +} + +static bool f81534_tx_empty(struct usb_serial_port *port) +{ + struct f81534_port_private *port_priv = usb_get_serial_port_data(port); + + return test_bit(F81534_TX_EMPTY_BIT, &port_priv->tx_empty); +} + +static int f81534_resume(struct usb_serial *serial) +{ + struct f81534_serial_private *serial_priv = + usb_get_serial_data(serial); + struct usb_serial_port *port; + int error = 0; + int status; + size_t i; + + /* + * We'll register port 0 bulkin when port had opened, It'll take all + * port received data, MSR register change and TX_EMPTY information. + */ + mutex_lock(&serial_priv->urb_mutex); + + if (serial_priv->opened_port) { + status = f81534_submit_read_urb(serial, GFP_NOIO); + if (status) { + mutex_unlock(&serial_priv->urb_mutex); + return status; + } + } + + mutex_unlock(&serial_priv->urb_mutex); + + for (i = 0; i < serial->num_ports; i++) { + port = serial->port[i]; + if (!tty_port_initialized(&port->port)) + continue; + + status = f81534_submit_writer(port, GFP_NOIO); + if (status) { + dev_err(&port->dev, "%s: submit failed\n", __func__); + ++error; + } + } + + if (error) + return -EIO; + + return 0; +} + +static struct usb_serial_driver f81534_device = { + .driver = { + .name = "f81534", + }, + .description = DRIVER_DESC, + .id_table = f81534_id_table, + .num_bulk_in = 1, + .num_bulk_out = 1, + .open = f81534_open, + .close = f81534_close, + .write = f81534_write, + .tx_empty = f81534_tx_empty, + .calc_num_ports = f81534_calc_num_ports, + .port_probe = f81534_port_probe, + .port_remove = f81534_port_remove, + .break_ctl = f81534_break_ctl, + .dtr_rts = f81534_dtr_rts, + .process_read_urb = f81534_process_read_urb, + .get_serial = f81534_get_serial_info, + .tiocmget = f81534_tiocmget, + .tiocmset = f81534_tiocmset, + .write_bulk_callback = f81534_write_usb_callback, + .set_termios = f81534_set_termios, + .resume = f81534_resume, +}; + +static struct usb_serial_driver *const serial_drivers[] = { + &f81534_device, NULL +}; + +module_usb_serial_driver(serial_drivers, f81534_id_table); + +MODULE_DEVICE_TABLE(usb, f81534_id_table); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Peter Hong "); +MODULE_AUTHOR("Tom Tsai "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c new file mode 100644 index 000000000..c6ffa23bc --- /dev/null +++ b/drivers/usb/serial/ftdi_sio.c @@ -0,0 +1,2878 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * USB FTDI SIO driver + * + * Copyright (C) 2009 - 2013 + * Johan Hovold (jhovold@gmail.com) + * Copyright (C) 1999 - 2001 + * Greg Kroah-Hartman (greg@kroah.com) + * Bill Ryder (bryder@sgi.com) + * Copyright (C) 2002 + * Kuba Ober (kuba@mareimbrium.org) + * + * See Documentation/usb/usb-serial.rst for more information on using this + * driver + * + * See http://ftdi-usb-sio.sourceforge.net for up to date testing info + * and extra documentation + * + * Change entries from 2004 and earlier can be found in versions of this + * file in kernel versions prior to the 2.6.24 release. + * + */ + +/* Bill Ryder - bryder@sgi.com - wrote the FTDI_SIO implementation */ +/* Thanx to FTDI for so kindly providing details of the protocol required */ +/* to talk to the device */ +/* Thanx to gkh and the rest of the usb dev group for all code I have + assimilated :-) */ + +#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" + +enum ftdi_chip_type { + SIO, + FT232A, + FT232B, + FT2232C, + FT232R, + FT232H, + FT2232H, + FT4232H, + FT4232HA, + FT232HP, + FT233HP, + FT2232HP, + FT2233HP, + FT4232HP, + FT4233HP, + FTX, +}; + +struct ftdi_private { + enum ftdi_chip_type chip_type; + 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_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 channel; /* channel index, or 0 for legacy types */ + + 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() */ +#ifdef CONFIG_GPIOLIB + struct gpio_chip gc; + struct mutex gpio_lock; /* protects GPIO state */ + bool gpio_registered; /* is the gpiochip in kernel registered */ + bool gpio_used; /* true if the user requested a gpio */ + u8 gpio_altfunc; /* which pins are in gpio mode */ + u8 gpio_output; /* pin directions cache */ + u8 gpio_value; /* pin value for outputs */ +#endif +}; + +struct ftdi_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_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 const struct ftdi_quirk ftdi_jtag_quirk = { + .probe = ftdi_jtag_probe, +}; + +static const struct ftdi_quirk ftdi_ndi_quirk = { +}; + +static const struct ftdi_quirk ftdi_usb_uirt_quirk = { + .port_probe = ftdi_usb_uirt_setup, +}; + +static const struct ftdi_quirk ftdi_he_tira1_quirk = { + .port_probe = ftdi_he_tira1_setup, +}; + +static const struct ftdi_quirk ftdi_stmclite_quirk = { + .probe = ftdi_stmclite_probe, +}; + +static const struct ftdi_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 it using + * /sys/bus/usb-serial/drivers/ftdi_sio/new_id and send a patch or report. + */ +static const 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_FT2233HP_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_FT4233HP_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_FT2232HP_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_FT4232HP_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_FT233HP_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_FT232HP_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_FT4232HA_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(FTDI_VID, FTDI_AUTO_M3_OP_COM_V2_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) }, + { 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) }, + { 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) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0154_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0155_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0156_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0157_PID) }, + { USB_DEVICE(MTXORB_VID, MTXORB_FTDI_RANGE_0158_PID) }, + { 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_INTERFACE_NUMBER(FTDI_VID, FTDI_TIAO_UMPA_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_NT_ORIONLXM_PID, 1) }, + { USB_DEVICE(FTDI_VID, FTDI_NT_ORIONLX_PLUS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_NT_ORION_IO_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_NT_ORIONMX_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_SYNAPSE_SS200_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CUSTOMWARE_MINIPLEX_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CUSTOMWARE_MINIPLEX2_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CUSTOMWARE_MINIPLEX2WI_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CUSTOMWARE_MINIPLEX3_PID) }, + /* + * 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, FTDI_PALMSENS_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_IVIUM_XSTAT_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_MTDEVBOARD_PID) }, + { USB_DEVICE(XSENS_VID, XSENS_MTIUSBCONVERTER_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_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_NDI_SPECTRA_SCU_PID), + .driver_info = (kernel_ulong_t)&ftdi_ndi_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_NDI_FUTURE_2_PID), + .driver_info = (kernel_ulong_t)&ftdi_ndi_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_NDI_FUTURE_3_PID), + .driver_info = (kernel_ulong_t)&ftdi_ndi_quirk }, + { USB_DEVICE(FTDI_VID, FTDI_NDI_AURORA_SCU_PID), + .driver_info = (kernel_ulong_t)&ftdi_ndi_quirk }, + { USB_DEVICE(FTDI_NDI_VID, FTDI_NDI_EMGUIDE_GEMINI_PID), + .driver_info = (kernel_ulong_t)&ftdi_ndi_quirk }, + { USB_DEVICE(TELLDUS_VID, TELLDUS_TELLSTICK_PID) }, + { USB_DEVICE(NOVITUS_VID, NOVITUS_BONO_E_PID) }, + { USB_DEVICE(FTDI_VID, RTSYSTEMS_USB_VX8_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_INTERFACE_NUMBER(FTDI_VID, CYBER_CORTEX_AV_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(OLIMEX_VID, OLIMEX_ARM_USB_OCD_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(OLIMEX_VID, OLIMEX_ARM_USB_OCD_H_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(OLIMEX_VID, OLIMEX_ARM_USB_TINY_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(OLIMEX_VID, OLIMEX_ARM_USB_TINY_H_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FIC_VID, FIC_NEO1973_DEBUG_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_OOCDLINK_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, LMI_LM3S_DEVEL_BOARD_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, LMI_LM3S_EVAL_BOARD_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, LMI_LM3S_ICDI_BOARD_PID, 1) }, + { USB_DEVICE(FTDI_VID, FTDI_AXE027_PID) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_TURTELIZER_PID, 1) }, + { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID_USB60F) }, + { USB_DEVICE(RATOC_VENDOR_ID, RATOC_PRODUCT_ID_SCU18) }, + { 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_INTERFACE_NUMBER(ADI_VID, ADI_GNICE_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(ADI_VID, ADI_GNICEPLUS_PID, 1) }, + { USB_DEVICE_AND_INTERFACE_INFO(MICROCHIP_VID, MICROCHIP_USB_BOARD_PID, + USB_CLASS_VENDOR_SPEC, + USB_SUBCLASS_VENDOR_SPEC, 0x00) }, + { USB_DEVICE_INTERFACE_NUMBER(ACTEL_VID, MICROSEMI_ARROW_SF2PLUS_BOARD_PID, 2) }, + { USB_DEVICE(JETI_VID, JETI_SPC1201_PID) }, + { USB_DEVICE_INTERFACE_NUMBER(MARVELL_VID, MARVELL_SHEEVAPLUG_PID, 1) }, + { 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_INTERFACE_NUMBER(FTDI_VID, MARVELL_OPENRD_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, TI_XDS100V2_PID, 1) }, + { 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_INTERFACE_NUMBER(FTDI_VID, XVERVE_SIGNALYZER_ST_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, XVERVE_SIGNALYZER_SLITE_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, XVERVE_SIGNALYZER_SH2_PID, 1) }, + { 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_INTERFACE_NUMBER(IONICS_VID, IONICS_PLUGCOMPUTER_PID, 1) }, + { 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_FHE_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_DOTEC_PID) }, + { USB_DEVICE_INTERFACE_NUMBER(QIHARDWARE_VID, MILKYMISTONE_JTAGSERIAL_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(ST_VID, ST_STMCLT_2232_PID, 1) }, + { 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_INTERFACE_NUMBER(FTDI_VID, FTDI_DISTORTEC_JTAG_LOCK_PICK_PID, 1) }, + { 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_159_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_235_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_320_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_TC1798_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(INFINEON_VID, INFINEON_TRIBOARD_TC2X7_PID, 1) }, + /* GE Healthcare devices */ + { USB_DEVICE(GE_HEALTHCARE_VID, GE_HEALTHCARE_NEMO_TRACKER_PID) }, + /* Active Research (Actisense) devices */ + { USB_DEVICE(FTDI_VID, ACTISENSE_NDC_PID) }, + { USB_DEVICE(FTDI_VID, ACTISENSE_USG_PID) }, + { USB_DEVICE(FTDI_VID, ACTISENSE_NGT_PID) }, + { USB_DEVICE(FTDI_VID, ACTISENSE_NGW_PID) }, + { USB_DEVICE(FTDI_VID, ACTISENSE_UID_PID) }, + { USB_DEVICE(FTDI_VID, ACTISENSE_USA_PID) }, + { USB_DEVICE(FTDI_VID, ACTISENSE_NGX_PID) }, + { USB_DEVICE(FTDI_VID, ACTISENSE_D9AF_PID) }, + { USB_DEVICE(FTDI_VID, CHETCO_SEAGAUGE_PID) }, + { USB_DEVICE(FTDI_VID, CHETCO_SEASWITCH_PID) }, + { USB_DEVICE(FTDI_VID, CHETCO_SEASMART_NMEA2000_PID) }, + { USB_DEVICE(FTDI_VID, CHETCO_SEASMART_ETHERNET_PID) }, + { USB_DEVICE(FTDI_VID, CHETCO_SEASMART_WIFI_PID) }, + { USB_DEVICE(FTDI_VID, CHETCO_SEASMART_DISPLAY_PID) }, + { USB_DEVICE(FTDI_VID, CHETCO_SEASMART_LITE_PID) }, + { USB_DEVICE(FTDI_VID, CHETCO_SEASMART_ANALOG_PID) }, + /* Belimo Automation devices */ + { USB_DEVICE(FTDI_VID, BELIMO_ZTH_PID) }, + { USB_DEVICE(FTDI_VID, BELIMO_ZIP_PID) }, + /* ICP DAS I-756xU devices */ + { USB_DEVICE(ICPDAS_VID, ICPDAS_I7560U_PID) }, + { USB_DEVICE(ICPDAS_VID, ICPDAS_I7561U_PID) }, + { USB_DEVICE(ICPDAS_VID, ICPDAS_I7563U_PID) }, + { USB_DEVICE(WICED_VID, WICED_USB20706V2_PID) }, + { USB_DEVICE_INTERFACE_NUMBER(TI_VID, TI_CC3200_LAUNCHPAD_PID, 1) }, + { USB_DEVICE(CYPRESS_VID, CYPRESS_WICED_BT_USB_PID) }, + { USB_DEVICE(CYPRESS_VID, CYPRESS_WICED_WL_USB_PID) }, + { USB_DEVICE(AIRBUS_DS_VID, AIRBUS_DS_P8GR) }, + /* EZPrototypes devices */ + { USB_DEVICE(EZPROTOTYPES_VID, HJELMSLUND_USB485_ISO_PID) }, + { USB_DEVICE_INTERFACE_NUMBER(UNJO_VID, UNJO_ISODEBUG_V1_PID, 1) }, + /* Sienna devices */ + { USB_DEVICE(FTDI_VID, FTDI_SIENNA_PID) }, + { USB_DEVICE(ECHELON_VID, ECHELON_U20_PID) }, + /* IDS GmbH devices */ + { USB_DEVICE(IDS_VID, IDS_SI31A_PID) }, + { USB_DEVICE(IDS_VID, IDS_CM31A_PID) }, + /* Omron devices */ + { USB_DEVICE(OMRON_VID, OMRON_CS1W_CIF31_PID) }, + /* U-Blox devices */ + { USB_DEVICE(UBLOX_VID, UBLOX_C099F9P_ZED_PID) }, + { USB_DEVICE(UBLOX_VID, UBLOX_C099F9P_ODIN_PID) }, + { USB_DEVICE_INTERFACE_NUMBER(UBLOX_VID, UBLOX_EVK_M101_PID, 2) }, + /* FreeCalypso USB adapters */ + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_FALCONIA_JTAG_BUF_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(FTDI_VID, FTDI_FALCONIA_JTAG_UNBUF_PID, 1) }, + /* GMC devices */ + { USB_DEVICE(GMC_VID, GMC_Z216C_PID) }, + /* Altera USB Blaster 3 */ + { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_6022_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_6025_PID, 2) }, + { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_6026_PID, 2) }, + { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_6026_PID, 3) }, + { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_6029_PID, 2) }, + { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602A_PID, 2) }, + { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602A_PID, 3) }, + { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602C_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602D_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602D_PID, 2) }, + { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602E_PID, 1) }, + { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602E_PID, 2) }, + { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602E_PID, 3) }, + /* Abacus Electrics */ + { USB_DEVICE(FTDI_VID, ABACUS_OPTICAL_PROBE_PID) }, + /* Endress+Hauser AG devices */ + { USB_DEVICE(FTDI_VID, FTDI_EH_FXA291_PID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table_combined); + +static const char *ftdi_chip_name[] = { + [SIO] = "SIO", /* the serial part of FT8U100AX */ + [FT232A] = "FT232A", + [FT232B] = "FT232B", + [FT2232C] = "FT2232C/D", + [FT232R] = "FT232R", + [FT232H] = "FT232H", + [FT2232H] = "FT2232H", + [FT4232H] = "FT4232H", + [FT4232HA] = "FT4232HA", + [FT232HP] = "FT232HP", + [FT233HP] = "FT233HP", + [FT2232HP] = "FT2232HP", + [FT2233HP] = "FT2233HP", + [FT4232HP] = "FT4232HP", + [FT4233HP] = "FT4233HP", + [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 */ + +static void ftdi_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios); +static int ftdi_get_modem_status(struct usb_serial_port *port, + unsigned char status[2]); + +#define WDR_TIMEOUT 5000 /* default urb timeout */ +#define WDR_SHORT_TIMEOUT 1000 /* shorter urb timeout */ + +/* + * *************************************************************************** + * 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 = DIV_ROUND_CLOSEST(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; /* +0.125 */ + else if (divisor3 >= 4) + divisor |= 0x4000; /* +0.5 */ + else if (divisor3 != 0) + divisor |= 0x8000; /* +0.25 */ + 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 = DIV_ROUND_CLOSEST(base, 2 * baud); + divisor = divisor3 >> 3; + divisor |= (u32)divfrac[divisor3 & 0x7] << 14; + /* Deal with special cases for highest baud rates. */ + if (divisor == 1) /* 1.0 */ + divisor = 0; + else if (divisor == 0x4001) /* 1.5 */ + 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 = DIV_ROUND_CLOSEST(8 * base, 10 * baud); + + divisor = divisor3 >> 3; + divisor |= (u32)divfrac[divisor3 & 0x7] << 14; + /* Deal with special cases for highest baud rates. */ + if (divisor == 1) /* 1.0 */ + divisor = 0; + else if (divisor == 0x4001) /* 1.5 */ + 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 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' */ + value = 0; + if (clear & TIOCM_DTR) + value |= FTDI_SIO_SET_DTR_LOW; + if (clear & TIOCM_RTS) + value |= FTDI_SIO_SET_RTS_LOW; + if (set & TIOCM_DTR) + value |= FTDI_SIO_SET_DTR_HIGH; + if (set & TIOCM_RTS) + 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, + value, priv->channel, + 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) +{ + const struct ftdi_quirk *quirk = usb_get_serial_data(port->serial); + 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; + + baud = tty_get_baud_rate(tty); + dev_dbg(dev, "%s - tty_get_baud_rate reports speed %d\n", __func__, baud); + + /* + * Observe deprecated 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); + } + + if (!baud) + baud = 9600; + switch (priv->chip_type) { + case SIO: + 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; + default: + 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 FT232A: + 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 FT232B: + case FT2232C: + case FT232R: + case FTX: + if (baud <= 3000000) { + if (quirk == &ftdi_ndi_quirk && 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; + default: + 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; + } + + 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 value; + u16 index; + u32 index_value; + int rv; + + index_value = get_ftdi_divisor(tty, port); + value = (u16)index_value; + index = (u16)(index_value >> 16); + if (priv->channel) + index = (u16)((index << 8) | priv->channel); + + rv = usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + FTDI_SIO_SET_BAUDRATE_REQUEST, + FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE, + value, 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->chip_type == SIO || priv->chip_type == FT232A) + return -EINVAL; + + 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->channel, + 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; + u8 buf; + int rv; + + rv = usb_control_msg_recv(udev, 0, FTDI_SIO_GET_LATENCY_TIMER_REQUEST, + FTDI_SIO_GET_LATENCY_TIMER_REQUEST_TYPE, 0, + priv->channel, &buf, 1, WDR_TIMEOUT, + GFP_KERNEL); + if (rv == 0) + rv = buf; + + return rv; +} + +static int read_latency_timer(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + int rv; + + if (priv->chip_type == SIO || priv->chip_type == FT232A) + return -EINVAL; + + rv = _read_latency_timer(port); + if (rv < 0) { + dev_err(&port->dev, "Unable to read latency timer: %i\n", rv); + return rv; + } + + priv->latency = rv; + + return 0; +} + +static void get_serial_info(struct tty_struct *tty, struct serial_struct *ss) +{ + struct usb_serial_port *port = tty->driver_data; + struct ftdi_private *priv = usb_get_serial_port_data(port); + + mutex_lock(&priv->cfg_lock); + ss->flags = priv->flags; + ss->baud_base = priv->baud_base; + ss->custom_divisor = priv->custom_divisor; + mutex_unlock(&priv->cfg_lock); +} + +static int set_serial_info(struct tty_struct *tty, struct serial_struct *ss) +{ + struct usb_serial_port *port = tty->driver_data; + struct ftdi_private *priv = usb_get_serial_port_data(port); + int old_flags, old_divisor; + + mutex_lock(&priv->cfg_lock); + + if (!capable(CAP_SYS_ADMIN)) { + if ((ss->flags ^ priv->flags) & ~ASYNC_USR_MASK) { + mutex_unlock(&priv->cfg_lock); + return -EPERM; + } + } + + old_flags = priv->flags; + old_divisor = priv->custom_divisor; + + priv->flags = ss->flags & ASYNC_FLAGS; + priv->custom_divisor = ss->custom_divisor; + + write_latency_timer(port); + + if ((priv->flags ^ old_flags) & ASYNC_SPD_MASK || + ((priv->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST && + priv->custom_divisor != old_divisor)) { + + /* warn about deprecation unless clearing */ + if (priv->flags & ASYNC_SPD_MASK) + dev_warn_ratelimited(&port->dev, "use of SPD flags is deprecated\n"); + + change_speed(tty, port); + } + mutex_unlock(&priv->cfg_lock); + return 0; +} + +static int get_lsr_info(struct usb_serial_port *port, + unsigned int __user *retinfo) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + unsigned int result = 0; + + if (priv->transmit_empty) + result = TIOCSER_TEMT; + + if (copy_to_user(retinfo, &result, sizeof(unsigned int))) + return -EFAULT; + return 0; +} + +static int 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 int version, ifnum; + + version = le16_to_cpu(udev->descriptor.bcdDevice); + ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber; + + /* Assume Hi-Speed type */ + priv->baud_base = 120000000 / 2; + priv->channel = CHANNEL_A + ifnum; + + switch (version) { + case 0x200: + priv->chip_type = FT232A; + priv->baud_base = 48000000 / 2; + priv->channel = 0; + /* + * FT232B devices have a bug where bcdDevice gets set to 0x200 + * when iSerialNumber is 0. Assume it is an FT232B in case the + * latency timer is readable. + */ + if (udev->descriptor.iSerialNumber == 0 && + _read_latency_timer(port) >= 0) { + priv->chip_type = FT232B; + } + break; + case 0x400: + priv->chip_type = FT232B; + priv->baud_base = 48000000 / 2; + priv->channel = 0; + break; + case 0x500: + priv->chip_type = FT2232C; + priv->baud_base = 48000000 / 2; + break; + case 0x600: + priv->chip_type = FT232R; + priv->baud_base = 48000000 / 2; + priv->channel = 0; + break; + case 0x700: + priv->chip_type = FT2232H; + break; + case 0x800: + priv->chip_type = FT4232H; + break; + case 0x900: + priv->chip_type = FT232H; + break; + case 0x1000: + priv->chip_type = FTX; + priv->baud_base = 48000000 / 2; + break; + case 0x2800: + priv->chip_type = FT2233HP; + break; + case 0x2900: + priv->chip_type = FT4233HP; + break; + case 0x3000: + priv->chip_type = FT2232HP; + break; + case 0x3100: + priv->chip_type = FT4232HP; + break; + case 0x3200: + priv->chip_type = FT233HP; + break; + case 0x3300: + priv->chip_type = FT232HP; + break; + case 0x3600: + priv->chip_type = FT4232HA; + break; + default: + if (version < 0x200) { + priv->chip_type = SIO; + priv->baud_base = 12000000 / 16; + priv->channel = 0; + } else { + dev_err(&port->dev, "unknown device type: 0x%02x\n", version); + return -ENODEV; + } + } + + dev_info(&udev->dev, "Detected %s\n", ftdi_chip_name[priv->chip_type]); + + return 0; +} + + +/* + * 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_interface *interface = port->serial->interface; + struct usb_endpoint_descriptor *ep_desc; + unsigned num_endpoints; + unsigned i; + + num_endpoints = interface->cur_altsetting->desc.bNumEndpoints; + 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++) { + ep_desc = &interface->cur_altsetting->endpoint[i].desc; + if (!ep_desc->wMaxPacketSize) { + ep_desc->wMaxPacketSize = cpu_to_le16(0x40); + dev_warn(&port->dev, "Overriding wMaxPacketSize on endpoint %d\n", + usb_endpoint_num(ep_desc)); + } + } + + /* Set max packet size based on last descriptor. */ + priv->max_packet_size = usb_endpoint_maxp(ep_desc); +} + + +/* + * *************************************************************************** + * Sysfs Attribute + * *************************************************************************** + */ + +static ssize_t latency_timer_show(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, "%u\n", priv->latency); +} + +/* Write a new value of the latency timer, in units of milliseconds. */ +static ssize_t latency_timer_store(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); + u8 v; + int rv; + + if (kstrtou8(valbuf, 10, &v)) + return -EINVAL; + + priv->latency = v; + rv = write_latency_timer(port); + if (rv < 0) + return -EIO; + return count; +} +static DEVICE_ATTR_RW(latency_timer); + +/* 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 event_char_store(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; + unsigned int v; + int rv; + + if (kstrtouint(valbuf, 0, &v) || v >= 0x200) + return -EINVAL; + + dev_dbg(&port->dev, "%s: setting event char = 0x%03x\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->channel, + 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_WO(event_char); + +static struct attribute *ftdi_attrs[] = { + &dev_attr_event_char.attr, + &dev_attr_latency_timer.attr, + NULL +}; + +static umode_t ftdi_is_visible(struct kobject *kobj, struct attribute *attr, int idx) +{ + struct device *dev = kobj_to_dev(kobj); + struct usb_serial_port *port = to_usb_serial_port(dev); + struct ftdi_private *priv = usb_get_serial_port_data(port); + enum ftdi_chip_type type = priv->chip_type; + + if (attr == &dev_attr_event_char.attr) { + if (type == SIO) + return 0; + } + + if (attr == &dev_attr_latency_timer.attr) { + if (type == SIO || type == FT232A) + return 0; + } + + return attr->mode; +} + +static const struct attribute_group ftdi_group = { + .attrs = ftdi_attrs, + .is_visible = ftdi_is_visible, +}; + +static const struct attribute_group *ftdi_groups[] = { + &ftdi_group, + NULL +}; + +#ifdef CONFIG_GPIOLIB + +static int ftdi_set_bitmode(struct usb_serial_port *port, u8 mode) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + int result; + u16 val; + + result = usb_autopm_get_interface(serial->interface); + if (result) + return result; + + val = (mode << 8) | (priv->gpio_output << 4) | priv->gpio_value; + result = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + FTDI_SIO_SET_BITMODE_REQUEST, + FTDI_SIO_SET_BITMODE_REQUEST_TYPE, val, + priv->channel, NULL, 0, WDR_TIMEOUT); + if (result < 0) { + dev_err(&serial->interface->dev, + "bitmode request failed for value 0x%04x: %d\n", + val, result); + } + + usb_autopm_put_interface(serial->interface); + + return result; +} + +static int ftdi_set_cbus_pins(struct usb_serial_port *port) +{ + return ftdi_set_bitmode(port, FTDI_SIO_BITMODE_CBUS); +} + +static int ftdi_exit_cbus_mode(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + + priv->gpio_output = 0; + priv->gpio_value = 0; + return ftdi_set_bitmode(port, FTDI_SIO_BITMODE_RESET); +} + +static int ftdi_gpio_request(struct gpio_chip *gc, unsigned int offset) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + struct ftdi_private *priv = usb_get_serial_port_data(port); + int result; + + mutex_lock(&priv->gpio_lock); + if (!priv->gpio_used) { + /* Set default pin states, as we cannot get them from device */ + priv->gpio_output = 0x00; + priv->gpio_value = 0x00; + result = ftdi_set_cbus_pins(port); + if (result) { + mutex_unlock(&priv->gpio_lock); + return result; + } + + priv->gpio_used = true; + } + mutex_unlock(&priv->gpio_lock); + + return 0; +} + +static int ftdi_read_cbus_pins(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + u8 buf; + int result; + + result = usb_autopm_get_interface(serial->interface); + if (result) + return result; + + result = usb_control_msg_recv(serial->dev, 0, + FTDI_SIO_READ_PINS_REQUEST, + FTDI_SIO_READ_PINS_REQUEST_TYPE, 0, + priv->channel, &buf, 1, WDR_TIMEOUT, + GFP_KERNEL); + if (result == 0) + result = buf; + + usb_autopm_put_interface(serial->interface); + + return result; +} + +static int ftdi_gpio_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + int result; + + result = ftdi_read_cbus_pins(port); + if (result < 0) + return result; + + return !!(result & BIT(gpio)); +} + +static int ftdi_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + struct ftdi_private *priv = usb_get_serial_port_data(port); + int result; + + mutex_lock(&priv->gpio_lock); + + if (value) + priv->gpio_value |= BIT(gpio); + else + priv->gpio_value &= ~BIT(gpio); + + result = ftdi_set_cbus_pins(port); + + mutex_unlock(&priv->gpio_lock); + + return result; +} + +static int ftdi_gpio_get_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + int result; + + result = ftdi_read_cbus_pins(port); + if (result < 0) + return result; + + *bits = result & *mask; + + return 0; +} + +static int ftdi_gpio_set_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + struct ftdi_private *priv = usb_get_serial_port_data(port); + int result; + + mutex_lock(&priv->gpio_lock); + + priv->gpio_value &= ~(*mask); + priv->gpio_value |= *bits & *mask; + result = ftdi_set_cbus_pins(port); + + mutex_unlock(&priv->gpio_lock); + + return result; +} + +static int ftdi_gpio_direction_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + struct ftdi_private *priv = usb_get_serial_port_data(port); + + return !(priv->gpio_output & BIT(gpio)); +} + +static int ftdi_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + struct ftdi_private *priv = usb_get_serial_port_data(port); + int result; + + mutex_lock(&priv->gpio_lock); + + priv->gpio_output &= ~BIT(gpio); + result = ftdi_set_cbus_pins(port); + + mutex_unlock(&priv->gpio_lock); + + return result; +} + +static int ftdi_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio, + int value) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + struct ftdi_private *priv = usb_get_serial_port_data(port); + int result; + + mutex_lock(&priv->gpio_lock); + + priv->gpio_output |= BIT(gpio); + if (value) + priv->gpio_value |= BIT(gpio); + else + priv->gpio_value &= ~BIT(gpio); + + result = ftdi_set_cbus_pins(port); + + mutex_unlock(&priv->gpio_lock); + + return result; +} + +static int ftdi_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + struct ftdi_private *priv = usb_get_serial_port_data(port); + unsigned long map = priv->gpio_altfunc; + + bitmap_complement(valid_mask, &map, ngpios); + + if (bitmap_empty(valid_mask, ngpios)) + dev_dbg(&port->dev, "no CBUS pin configured for GPIO\n"); + else + dev_dbg(&port->dev, "CBUS%*pbl configured for GPIO\n", ngpios, + valid_mask); + + return 0; +} + +static int ftdi_read_eeprom(struct usb_serial *serial, void *dst, u16 addr, + u16 nbytes) +{ + int read = 0; + + if (addr % 2 != 0) + return -EINVAL; + if (nbytes % 2 != 0) + return -EINVAL; + + /* Read EEPROM two bytes at a time */ + while (read < nbytes) { + int rv; + + rv = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + FTDI_SIO_READ_EEPROM_REQUEST, + FTDI_SIO_READ_EEPROM_REQUEST_TYPE, + 0, (addr + read) / 2, dst + read, 2, + WDR_TIMEOUT); + if (rv < 2) { + if (rv >= 0) + return -EIO; + else + return rv; + } + + read += rv; + } + + return 0; +} + +static int ftdi_gpio_init_ft232h(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + u16 cbus_config; + u8 *buf; + int ret; + int i; + + buf = kmalloc(4, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = ftdi_read_eeprom(port->serial, buf, 0x1a, 4); + if (ret < 0) + goto out_free; + + /* + * FT232H CBUS Memory Map + * + * 0x1a: X- (upper nibble -> AC5) + * 0x1b: -X (lower nibble -> AC6) + * 0x1c: XX (upper nibble -> AC9 | lower nibble -> AC8) + */ + cbus_config = buf[2] << 8 | (buf[1] & 0xf) << 4 | (buf[0] & 0xf0) >> 4; + + priv->gc.ngpio = 4; + priv->gpio_altfunc = 0xff; + + for (i = 0; i < priv->gc.ngpio; ++i) { + if ((cbus_config & 0xf) == FTDI_FTX_CBUS_MUX_GPIO) + priv->gpio_altfunc &= ~BIT(i); + cbus_config >>= 4; + } + +out_free: + kfree(buf); + + return ret; +} + +static int ftdi_gpio_init_ft232r(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + u16 cbus_config; + u8 *buf; + int ret; + int i; + + buf = kmalloc(2, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = ftdi_read_eeprom(port->serial, buf, 0x14, 2); + if (ret < 0) + goto out_free; + + cbus_config = le16_to_cpup((__le16 *)buf); + dev_dbg(&port->dev, "cbus_config = 0x%04x\n", cbus_config); + + priv->gc.ngpio = 4; + + priv->gpio_altfunc = 0xff; + for (i = 0; i < priv->gc.ngpio; ++i) { + if ((cbus_config & 0xf) == FTDI_FT232R_CBUS_MUX_GPIO) + priv->gpio_altfunc &= ~BIT(i); + cbus_config >>= 4; + } +out_free: + kfree(buf); + + return ret; +} + +static int ftdi_gpio_init_ftx(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + const u16 cbus_cfg_addr = 0x1a; + const u16 cbus_cfg_size = 4; + u8 *cbus_cfg_buf; + int result; + u8 i; + + cbus_cfg_buf = kmalloc(cbus_cfg_size, GFP_KERNEL); + if (!cbus_cfg_buf) + return -ENOMEM; + + result = ftdi_read_eeprom(serial, cbus_cfg_buf, + cbus_cfg_addr, cbus_cfg_size); + if (result < 0) + goto out_free; + + /* FIXME: FT234XD alone has 1 GPIO, but how to recognize this IC? */ + priv->gc.ngpio = 4; + + /* Determine which pins are configured for CBUS bitbanging */ + priv->gpio_altfunc = 0xff; + for (i = 0; i < priv->gc.ngpio; ++i) { + if (cbus_cfg_buf[i] == FTDI_FTX_CBUS_MUX_GPIO) + priv->gpio_altfunc &= ~BIT(i); + } + +out_free: + kfree(cbus_cfg_buf); + + return result; +} + +static int ftdi_gpio_init(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + int result; + + switch (priv->chip_type) { + case FT232H: + result = ftdi_gpio_init_ft232h(port); + break; + case FT232R: + result = ftdi_gpio_init_ft232r(port); + break; + case FTX: + result = ftdi_gpio_init_ftx(port); + break; + default: + return 0; + } + + if (result < 0) + return result; + + mutex_init(&priv->gpio_lock); + + priv->gc.label = "ftdi-cbus"; + priv->gc.request = ftdi_gpio_request; + priv->gc.get_direction = ftdi_gpio_direction_get; + priv->gc.direction_input = ftdi_gpio_direction_input; + priv->gc.direction_output = ftdi_gpio_direction_output; + priv->gc.init_valid_mask = ftdi_gpio_init_valid_mask; + priv->gc.get = ftdi_gpio_get; + priv->gc.set = ftdi_gpio_set; + priv->gc.get_multiple = ftdi_gpio_get_multiple; + priv->gc.set_multiple = ftdi_gpio_set_multiple; + priv->gc.owner = THIS_MODULE; + priv->gc.parent = &serial->interface->dev; + priv->gc.base = -1; + priv->gc.can_sleep = true; + + result = gpiochip_add_data(&priv->gc, port); + if (!result) + priv->gpio_registered = true; + + return result; +} + +static void ftdi_gpio_remove(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + + if (priv->gpio_registered) { + gpiochip_remove(&priv->gc); + priv->gpio_registered = false; + } + + if (priv->gpio_used) { + /* Exiting CBUS-mode does not reset pin states. */ + ftdi_exit_cbus_mode(port); + priv->gpio_used = false; + } +} + +#else + +static int ftdi_gpio_init(struct usb_serial_port *port) +{ + return 0; +} + +static void ftdi_gpio_remove(struct usb_serial_port *port) { } + +#endif /* CONFIG_GPIOLIB */ + +/* + * *************************************************************************** + * FTDI driver specific functions + * *************************************************************************** + */ + +static int ftdi_probe(struct usb_serial *serial, const struct usb_device_id *id) +{ + const struct ftdi_quirk *quirk = (struct ftdi_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_port_probe(struct usb_serial_port *port) +{ + const struct ftdi_quirk *quirk = usb_get_serial_data(port->serial); + struct ftdi_private *priv; + int result; + + priv = kzalloc_obj(struct ftdi_private); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->cfg_lock); + + if (quirk && quirk->port_probe) + quirk->port_probe(priv); + + usb_set_serial_port_data(port, priv); + + result = ftdi_determine_type(port); + if (result) + goto err_free; + + ftdi_set_max_packet_size(port); + if (quirk == &ftdi_ndi_quirk) + priv->latency = 1; + else if (read_latency_timer(port) < 0) + priv->latency = 16; + write_latency_timer(port); + + result = ftdi_gpio_init(port); + if (result < 0) { + dev_err(&port->serial->interface->dev, + "GPIO initialisation failed: %d\n", + result); + } + + return 0; + +err_free: + kfree(priv); + + return result; +} + +/* + * Setup for the USB-UIRT device, which requires hardwired baudrate + * (38400 gets mapped to 312500). + */ +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; +} + +/* + * 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_interface *intf = serial->interface; + int ifnum = intf->cur_altsetting->desc.bInterfaceNumber; + + if (ifnum == 0) + return -ENODEV; + + return 0; +} + +static int ftdi_8u2232c_probe(struct usb_serial *serial) +{ + struct usb_interface *intf = serial->interface; + struct usb_device *udev = serial->dev; + int ifnum = intf->cur_altsetting->desc.bInterfaceNumber; + + if (ifnum == 0) { + if (udev->manufacturer && + !strcmp(udev->manufacturer, "CALAO Systems")) + return -ENODEV; + + if (udev->product && + (!strcmp(udev->product, "Arrow USB Blaster") || + !strcmp(udev->product, "BeagleBone/XDS100V2") || + !strcmp(udev->product, "SNAP Connect E10"))) + return -ENODEV; + } + + 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_interface *intf = serial->interface; + int ifnum = intf->cur_altsetting->desc.bInterfaceNumber; + + if (ifnum < 2) + return -ENODEV; + + return 0; +} + +static void ftdi_port_remove(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + + ftdi_gpio_remove(port); + + kfree(priv); +} + +static int ftdi_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + 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->channel, 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) + ftdi_set_termios(tty, port, NULL); + + 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->channel, 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, unsigned char *buf, int len) +{ + unsigned char status; + bool brkint = false; + int i; + char flag; + + 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 = buf[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) { + struct tty_struct *tty; + + port->icount.dcd++; + tty = tty_port_tty_get(&port->port); + if (tty) + usb_serial_handle_dcd_change(port, tty, + status & FTDI_RS0_RLSD); + tty_kref_put(tty); + } + + wake_up_interruptible(&port->port.delta_msr_wait); + priv->prev_status = status; + } + + /* save if the transmitter is empty or not */ + if (buf[1] & FTDI_RS_TEMT) + priv->transmit_empty = 1; + else + priv->transmit_empty = 0; + + if (len == 2) + return 0; /* status only */ + + /* + * Break and error status must only be processed for packets with + * data payload to avoid over-reporting. + */ + flag = TTY_NORMAL; + if (buf[1] & FTDI_RS_ERR_MASK) { + /* + * Break takes precedence over parity, which takes precedence + * over framing errors. Note that break is only associated + * with the last character in the buffer and only when it's a + * NUL. + */ + if (buf[1] & FTDI_RS_BI && buf[len - 1] == '\0') { + port->icount.brk++; + brkint = true; + } + if (buf[1] & FTDI_RS_PE) { + flag = TTY_PARITY; + port->icount.parity++; + } else if (buf[1] & FTDI_RS_FE) { + flag = TTY_FRAME; + port->icount.frame++; + } + /* Overrun is special, not associated with a char */ + if (buf[1] & FTDI_RS_OE) { + port->icount.overrun++; + tty_insert_flip_char(&port->port, 0, TTY_OVERRUN); + } + } + + port->icount.rx += len - 2; + + if (brkint || port->sysrq) { + for (i = 2; i < len; i++) { + if (brkint && i == len - 1) { + if (usb_serial_handle_break(port)) + return len - 3; + flag = TTY_BREAK; + } + if (usb_serial_handle_sysrq_char(port, buf[i])) + continue; + tty_insert_flip_char(&port->port, buf[i], flag); + } + } else { + tty_insert_flip_string_fixed_flag(&port->port, buf + 2, flag, + len - 2); + } + + return len - 2; +} + +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 = 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 int 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 value; + int ret; + + /* 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_value NEVER has the break bit set in it */ + + if (break_state) + value = priv->last_set_data_value | FTDI_SIO_SET_BREAK; + else + value = priv->last_set_data_value; + + ret = usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + FTDI_SIO_SET_DATA_REQUEST, + FTDI_SIO_SET_DATA_REQUEST_TYPE, + value, priv->channel, + NULL, 0, WDR_TIMEOUT); + if (ret < 0) { + dev_err(&port->dev, "%s FAILED to enable/disable break state (state was %d)\n", + __func__, break_state); + return ret; + } + + dev_dbg(&port->dev, "%s break state is %d - urb is %d\n", __func__, + break_state, value); + + return 0; +} + +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, + const 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; + u16 value, index; + int ret; + + /* 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 */ + + value = 0; + value |= (cflag & CSTOPB ? FTDI_SIO_SET_DATA_STOP_BITS_2 : + FTDI_SIO_SET_DATA_STOP_BITS_1); + if (cflag & PARENB) { + if (cflag & CMSPAR) + value |= cflag & PARODD ? + FTDI_SIO_SET_DATA_PARITY_MARK : + FTDI_SIO_SET_DATA_PARITY_SPACE; + else + value |= cflag & PARODD ? + FTDI_SIO_SET_DATA_PARITY_ODD : + FTDI_SIO_SET_DATA_PARITY_EVEN; + } else { + value |= FTDI_SIO_SET_DATA_PARITY_NONE; + } + switch (cflag & CSIZE) { + case CS5: + dev_dbg(ddev, "Setting CS5 quirk\n"); + break; + case CS7: + value |= 7; + dev_dbg(ddev, "Setting CS7\n"); + break; + default: + case CS8: + 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_value = value; + + if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + FTDI_SIO_SET_DATA_REQUEST, + FTDI_SIO_SET_DATA_REQUEST_TYPE, + value, priv->channel, + 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->channel, + 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); + } + +no_c_cflag_changes: + /* Set hardware-assisted flow control */ + value = 0; + + if (C_CRTSCTS(tty)) { + dev_dbg(&port->dev, "enabling rts/cts flow control\n"); + index = FTDI_SIO_RTS_CTS_HS; + } else if (I_IXON(tty)) { + dev_dbg(&port->dev, "enabling xon/xoff flow control\n"); + index = FTDI_SIO_XON_XOFF_HS; + value = STOP_CHAR(tty) << 8 | START_CHAR(tty); + } else { + dev_dbg(&port->dev, "disabling flow control\n"); + index = FTDI_SIO_DISABLE_FLOW_CTRL; + } + + index |= priv->channel; + + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + FTDI_SIO_SET_FLOW_CTRL_REQUEST, + FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE, + value, index, NULL, 0, WDR_TIMEOUT); + if (ret < 0) + dev_err(&port->dev, "failed to set flow control: %d\n", ret); +} + +/* + * 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 device returns a two byte value (the SIO a 1 byte value) in the + * same format as the data returned from the IN endpoint. + */ + if (priv->chip_type == SIO) + len = 1; + else + len = 2; + + 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->channel, + buf, len, WDR_TIMEOUT); + + /* NOTE: We allow short responses and handle that below. */ + if (ret < 1) { + dev_err(&port->dev, "failed to get modem status: %d\n", ret); + if (ret >= 0) + ret = -EIO; + 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; + void __user *argp = (void __user *)arg; + + switch (cmd) { + case TIOCSERGETLSR: + return get_lsr_info(port, argp); + default: + break; + } + + return -ENOIOCTLCMD; +} + +static struct usb_serial_driver ftdi_device = { + .driver = { + .name = "ftdi_sio", + .dev_groups = ftdi_groups, + }, + .description = "FTDI USB Serial Device", + .id_table = id_table_combined, + .num_ports = 1, + .bulk_in_size = 512, + .bulk_out_size = 256, + .probe = ftdi_probe, + .port_probe = ftdi_port_probe, + .port_remove = ftdi_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, + .get_serial = get_serial_info, + .set_serial = set_serial_info, + .set_termios = ftdi_set_termios, + .break_ctl = ftdi_break_ctl, + .tx_empty = ftdi_tx_empty, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &ftdi_device, 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/ftdi_sio.h b/drivers/usb/serial/ftdi_sio.h new file mode 100644 index 000000000..55ea61264 --- /dev/null +++ b/drivers/usb/serial/ftdi_sio.h @@ -0,0 +1,579 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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 0x0a /* Get the latency timer */ +#define FTDI_SIO_SET_BITMODE 0x0b /* Set bitbang mode */ +#define FTDI_SIO_READ_PINS 0x0c /* Read immediate value of pins */ +#define FTDI_SIO_READ_EEPROM 0x90 /* Read EEPROM */ + +/* Channel indices for FT2232, FT2232H and FT4232H devices */ +#define CHANNEL_A 1 +#define CHANNEL_B 2 +#define CHANNEL_C 3 +#define CHANNEL_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_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 ((FTDI_SIO_SET_DTR_MASK << 8) | 1) +#define FTDI_SIO_SET_DTR_LOW ((FTDI_SIO_SET_DTR_MASK << 8) | 0) +#define FTDI_SIO_SET_RTS_MASK 0x2 +#define FTDI_SIO_SET_RTS_HIGH ((FTDI_SIO_SET_RTS_MASK << 8) | 2) +#define FTDI_SIO_SET_RTS_LOW ((FTDI_SIO_SET_RTS_MASK << 8) | 0) + +/* + * 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 + * 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 + * 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 + */ + +/* FTDI_SIO_SET_BITMODE */ +#define FTDI_SIO_SET_BITMODE_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_BITMODE_REQUEST FTDI_SIO_SET_BITMODE + +/* Possible bitmodes for FTDI_SIO_SET_BITMODE_REQUEST */ +#define FTDI_SIO_BITMODE_RESET 0x00 +#define FTDI_SIO_BITMODE_CBUS 0x20 + +/* FTDI_SIO_READ_PINS */ +#define FTDI_SIO_READ_PINS_REQUEST_TYPE 0xc0 +#define FTDI_SIO_READ_PINS_REQUEST FTDI_SIO_READ_PINS + +/* + * FTDI_SIO_READ_EEPROM + * + * EEPROM format found in FTDI AN_201, "FT-X MTP memory Configuration", + * http://www.ftdichip.com/Support/Documents/AppNotes/AN_201_FT-X%20MTP%20Memory%20Configuration.pdf + */ +#define FTDI_SIO_READ_EEPROM_REQUEST_TYPE 0xc0 +#define FTDI_SIO_READ_EEPROM_REQUEST FTDI_SIO_READ_EEPROM + +#define FTDI_FTX_CBUS_MUX_GPIO 0x8 +#define FTDI_FT232R_CBUS_MUX_GPIO 0xa + + +/* 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 000000000..9c83c1785 --- /dev/null +++ b/drivers/usb/serial/ftdi_sio_ids.h @@ -0,0 +1,1643 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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_FT2233HP_PID 0x6040 /* Dual channel hi-speed device with PD */ +#define FTDI_FT4233HP_PID 0x6041 /* Quad channel hi-speed device with PD */ +#define FTDI_FT2232HP_PID 0x6042 /* Dual channel hi-speed device with PD */ +#define FTDI_FT4232HP_PID 0x6043 /* Quad channel hi-speed device with PD */ +#define FTDI_FT233HP_PID 0x6044 /* Dual channel hi-speed device with PD */ +#define FTDI_FT232HP_PID 0x6045 /* Dual channel hi-speed device with PD */ +#define FTDI_FT4232HA_PID 0x6048 /* Quad channel automotive grade hi-speed device */ +#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 + +/* + * Custom USB adapters made by Falconia Partners LLC + * for FreeCalypso project, ID codes allocated to Falconia by FTDI. + */ +#define FTDI_FALCONIA_JTAG_BUF_PID 0x7150 +#define FTDI_FALCONIA_JTAG_UNBUF_PID 0x7151 + +/* Sienna Serial Interface by Secyourit GmbH */ +#define FTDI_SIENNA_PID 0x8348 + +/* Cyber Cortex AV by Fabulous Silicon (http://fabuloussilicon.com) */ +#define CYBER_CORTEX_AV_PID 0x8698 + +/* + * 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_AXE027_PID 0xBD90 /* PICAXE AXE027 USB download cable */ + +#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 + +/* Auto-M3 Ltd. - OP-COM USB V2 - OBD interface Adapter */ +#define FTDI_AUTO_M3_OP_COM_V2_PID 0x4f50 + +/* + * 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_MTDEVBOARD_PID 0x0300 /* Motion Tracker Development Board */ +#define XSENS_MTIUSBCONVERTER_PID 0x0301 /* MTi USB converter */ +#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 */ + +#define FTDI_NDI_VID 0x23F2 +#define FTDI_NDI_EMGUIDE_GEMINI_PID 0x0003 /* NDI Emguide Gemini */ + +/* + * 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) */ + +/* + * Endress+Hauser AG product ids (FTDI_VID) + */ +#define FTDI_EH_FXA291_PID 0xE510 + +/* + * 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 + +/* + * Ivium Technologies product IDs + */ +#define FTDI_PALMSENS_PID 0xf440 +#define FTDI_IVIUM_XSTAT_PID 0xf441 + +/* + * 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 */ + +/* + * Abacus Electrics + */ +#define ABACUS_OPTICAL_PROBE_PID 0xf458 /* ABACUS ELECTRICS Optical Probe */ + +/* + * 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 */ +#define FTDI_NT_ORIONLX_PLUS_PID 0x7c91 /* OrionLX+ Substation Automation Platform */ +#define FTDI_NT_ORION_IO_PID 0x7c92 /* Orion I/O */ +#define FTDI_NT_ORIONMX_PID 0x7c93 /* OrionMX */ + +/* + * Synapse Wireless product ids (FTDI_VID) + * http://www.synapse-wireless.com + */ +#define FTDI_SYNAPSE_SS200_PID 0x9090 /* SS200 - SNAP Stick 200 */ + +/* + * CustomWare / ShipModul NMEA multiplexers product ids (FTDI_VID) + */ +#define FTDI_CUSTOMWARE_MINIPLEX_PID 0xfd48 /* MiniPlex first generation NMEA Multiplexer */ +#define FTDI_CUSTOMWARE_MINIPLEX2_PID 0xfd49 /* MiniPlex-USB and MiniPlex-2 series */ +#define FTDI_CUSTOMWARE_MINIPLEX2WI_PID 0xfd4a /* MiniPlex-2Wi */ +#define FTDI_CUSTOMWARE_MINIPLEX3_PID 0xfd4b /* MiniPlex-3 series */ + + +/********************************/ +/** third-party VID/PID combos **/ +/********************************/ + + + +/* + * Atmel STK541 + */ +#define ATMEL_VID 0x03eb /* Vendor ID */ +#define STK541_PID 0x2109 /* Zigbee Controller */ + +/* + * Texas Instruments + */ +#define TI_VID 0x0451 +#define TI_CC3200_LAUNCHPAD_PID 0xC32A /* SimpleLink Wi-Fi CC3200 LaunchPad */ + +/* + * 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 + +/* + * Cypress WICED USB UART + */ +#define CYPRESS_VID 0x04B4 +#define CYPRESS_WICED_BT_USB_PID 0x009B +#define CYPRESS_WICED_WL_USB_PID 0xF900 + +/* + * 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 +#define RATOC_PRODUCT_ID_SCU18 0xb03a + +/* + * Infineon Technologies + */ +#define INFINEON_VID 0x058b +#define INFINEON_TRIBOARD_TC1798_PID 0x0028 /* DAS JTAG TriBoard TC1798 V1.0 */ +#define INFINEON_TRIBOARD_TC2X7_PID 0x0043 /* DAS JTAG TriBoard TC2X7 V1.0 */ + +/* + * Omron corporation (https://www.omron.com) + */ + #define OMRON_VID 0x0590 + #define OMRON_CS1W_CIF31_PID 0x00b2 + +/* + * 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 + +/* + * Echelon USB Serial Interface + */ +#define ECHELON_VID 0x0920 +#define ECHELON_U20_PID 0x7500 + +/* + * Intrepid Control Systems (http://www.intrepidcs.com/) ValueCAN and NeoVI + */ +#define INTREPID_VID 0x093C +#define INTREPID_VALUECAN_PID 0x0601 +#define INTREPID_NEOVI_PID 0x0701 + +/* + * WICED USB UART + */ +#define WICED_VID 0x0A5C +#define WICED_USB20706V2_PID 0x6422 + +/* + * 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 + +/* + * Actel / Microsemi + */ +#define ACTEL_VID 0x1514 +#define MICROSEMI_ARROW_SF2PLUS_BOARD_PID 0x2008 + +/* Olimex */ +#define OLIMEX_VID 0x15BA +#define OLIMEX_ARM_USB_OCD_PID 0x0003 +#define OLIMEX_ARM_USB_TINY_PID 0x0004 +#define OLIMEX_ARM_USB_TINY_H_PID 0x002a +#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 + +/* + * ICPDAS I-756*U devices + */ +#define ICPDAS_VID 0x1b5c +#define ICPDAS_I7560U_PID 0x0103 +#define ICPDAS_I7561U_PID 0x0104 +#define ICPDAS_I7563U_PID 0x0105 + +/* + * Airbus Defence and Space + */ +#define AIRBUS_DS_VID 0x1e8e /* Vendor ID */ +#define AIRBUS_DS_P8GR 0x6001 /* Tetra P8GR */ + +/* + * RT Systems programming cables for various ham radios + */ +/* This device uses the VID of FTDI */ +#define RTSYSTEMS_USB_VX8_PID 0x9e50 /* USB-VX8 USB to 7 pin modular plug for Yaesu VX-8 radio */ + +#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 + +/* + * EZPrototypes (PID reseller) + */ +#define EZPROTOTYPES_VID 0x1c40 +#define HJELMSLUND_USB485_ISO_PID 0x0477 + +/* + * 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://zeitcontrol.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: FirmwareHubEmulator + * Manufacturer: Harman Becker Automotive Systems + */ +#define FTDI_FHE_PID 0xA9A0 + +/* + * 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_159_PID 0x1021 /* US-159 1xRS232 */ +#define BRAINBOXES_US_235_PID 0x1017 /* US-235 1xRS232 */ +#define BRAINBOXES_US_320_PID 0x1019 /* US-320 1xRS422/485 */ +#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 */ + +/* + * GE Healthcare devices + */ +#define GE_HEALTHCARE_VID 0x1901 +#define GE_HEALTHCARE_NEMO_TRACKER_PID 0x0015 + +/* + * Active Research (Actisense) devices + */ +#define ACTISENSE_NDC_PID 0xD9A8 /* NDC USB Serial Adapter */ +#define ACTISENSE_USG_PID 0xD9A9 /* USG USB Serial Adapter */ +#define ACTISENSE_NGT_PID 0xD9AA /* NGT NMEA2000 Interface */ +#define ACTISENSE_NGW_PID 0xD9AB /* NGW NMEA2000 Gateway */ +#define ACTISENSE_UID_PID 0xD9AC /* USB Isolating Device */ +#define ACTISENSE_USA_PID 0xD9AD /* USB to Serial Adapter */ +#define ACTISENSE_NGX_PID 0xD9AE /* NGX NMEA2000 Gateway */ +#define ACTISENSE_D9AF_PID 0xD9AF /* Actisense Reserved */ +#define CHETCO_SEAGAUGE_PID 0xA548 /* SeaGauge USB Adapter */ +#define CHETCO_SEASWITCH_PID 0xA549 /* SeaSwitch USB Adapter */ +#define CHETCO_SEASMART_NMEA2000_PID 0xA54A /* SeaSmart NMEA2000 Gateway */ +#define CHETCO_SEASMART_ETHERNET_PID 0xA54B /* SeaSmart Ethernet Gateway */ +#define CHETCO_SEASMART_WIFI_PID 0xA5AC /* SeaSmart Wifi Gateway */ +#define CHETCO_SEASMART_DISPLAY_PID 0xA5AD /* SeaSmart NMEA2000 Display */ +#define CHETCO_SEASMART_LITE_PID 0xA5AE /* SeaSmart Lite USB Adapter */ +#define CHETCO_SEASMART_ANALOG_PID 0xA5AF /* SeaSmart Analog Adapter */ + +/* + * Belimo Automation + */ +#define BELIMO_ZTH_PID 0x8050 +#define BELIMO_ZIP_PID 0xC811 + +/* + * Unjo AB + */ +#define UNJO_VID 0x22B7 +#define UNJO_ISODEBUG_V1_PID 0x150D + +/* + * IDS GmbH + */ +#define IDS_VID 0x2CAF +#define IDS_SI31A_PID 0x13A2 +#define IDS_CM31A_PID 0x13A3 + +/* + * U-Blox products (http://www.u-blox.com). + */ +#define UBLOX_VID 0x1546 +#define UBLOX_C099F9P_ZED_PID 0x0502 +#define UBLOX_C099F9P_ODIN_PID 0x0503 +#define UBLOX_EVK_M101_PID 0x0506 + +/* + * GMC devices + */ +#define GMC_VID 0x1cd7 +#define GMC_Z216C_PID 0x0217 /* GMC Z216C Adapter IR-USB */ + +/* + * Altera USB Blaster 3 (http://www.altera.com). + */ +#define ALTERA_VID 0x09fb +#define ALTERA_UB3_6022_PID 0x6022 +#define ALTERA_UB3_6025_PID 0x6025 +#define ALTERA_UB3_6026_PID 0x6026 +#define ALTERA_UB3_6029_PID 0x6029 +#define ALTERA_UB3_602A_PID 0x602a +#define ALTERA_UB3_602C_PID 0x602c +#define ALTERA_UB3_602D_PID 0x602d +#define ALTERA_UB3_602E_PID 0x602e diff --git a/drivers/usb/serial/garmin_gps.c b/drivers/usb/serial/garmin_gps.c new file mode 100644 index 000000000..8020149f5 --- /dev/null +++ b/drivers/usb/serial/garmin_gps.c @@ -0,0 +1,1442 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#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[] __counted_by(size); +}; + +/* 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; + struct usb_anchor write_urbs; +}; + + +#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_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 }; + +/* packets currently unused, left as documentation */ +#if 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 PRIVATE_REQ[] + = { 0x4B, 0x6E, 0x10, 0x01, 0xFF, 0, 0, 0, 0xFF, 0, 0, 0 }; +#endif + + +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 (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)) == 0) + 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 containing data ... */ + if (data_length) { + pkt = kmalloc_flex(*pkt, data, data_length, GFP_ATOMIC); + if (!pkt) + 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__, + 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++ = (-cksum) & 0xFF; + *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 (((cksum + *recpkt) & 0xff) != 0) { + dev_dbg(dev, "%s - invalid checksum, expected %02x, got %02x\n", + __func__, -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", + 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 (getLayerId(garmin_data_p->outbuffer) != GARMIN_LAYERID_APPL) { + 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 = -cksum & 0xFF; + *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; + + /* 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 0; +} + + +static int garmin_init_session(struct usb_serial_port *port) +{ + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + int status; + int i; + + usb_kill_urb(port->interrupt_in_urb); + + status = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (status) { + dev_err(&port->dev, "failed to submit interrupt urb: %d\n", + status); + return status; + } + + /* + * using the initialization method from gpsbabel. See comments in + * gpsbabel/jeeps/gpslibusb.c gusb_reset_toggles() + */ + dev_dbg(&port->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) + goto err_kill_urbs; + } + + return 0; + +err_kill_urbs: + usb_kill_anchored_urbs(&garmin_data_p->write_urbs); + usb_kill_urb(port->interrupt_in_urb); + + 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->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 - mode=%d state=%d flags=0x%X\n", + __func__, 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_anchored_urbs(&garmin_data_p->write_urbs); + + /* 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 (getLayerId(urb->transfer_buffer) == GARMIN_LAYERID_APPL) { + + 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 = kmemdup(buf, count, GFP_ATOMIC); + if (!buffer) + return -ENOMEM; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + kfree(buffer); + return -ENOMEM; + } + + 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 (getLayerId(buffer) == GARMIN_LAYERID_APPL) { + + 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 */ + usb_anchor_urb(urb, &garmin_data_p->write_urbs); + 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; + usb_unanchor_urb(urb); + kfree(buffer); + } + + /* 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) && + getLayerId(garmin_data_p->privpkt) == + GARMIN_LAYERID_PRIVATE) { + + 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 unsigned 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 active */ + 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 || (data_length >= sizeof(u32) && + 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 && + (garmin_data_p->flags & FLAGS_BULK_IN_RESTART) != 0) { + 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 ((garmin_data_p->flags & FLAGS_THROTTLED) == 0) { + 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) && + memcmp(data, GARMIN_BULK_IN_AVAIL_REPLY, + sizeof(GARMIN_BULK_IN_AVAIL_REPLY)) == 0) { + + dev_dbg(&port->dev, "%s - bulk data available.\n", __func__); + + if ((garmin_data_p->flags & FLAGS_BULK_IN_ACTIVE) == 0) { + + /* 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)) + && memcmp(data, GARMIN_START_SESSION_REPLY, + sizeof(GARMIN_START_SESSION_REPLY)) == 0) { + + 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 ((garmin_data_p->flags & FLAGS_BULK_IN_ACTIVE) != 0) { + 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(struct timer_list *t) +{ + struct garmin_data *garmin_data_p = timer_container_of(garmin_data_p, + t, timer); + + /* 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_obj(struct garmin_data); + if (!garmin_data_p) + return -ENOMEM; + + timer_setup(&garmin_data_p->timer, timeout_handler, 0); + spin_lock_init(&garmin_data_p->lock); + INIT_LIST_HEAD(&garmin_data_p->pktlist); + garmin_data_p->port = port; + garmin_data_p->state = 0; + garmin_data_p->flags = 0; + garmin_data_p->count = 0; + init_usb_anchor(&garmin_data_p->write_urbs); + usb_set_serial_port_data(port, garmin_data_p); + + status = garmin_init_session(port); + if (status) + goto err_free; + + return 0; +err_free: + kfree(garmin_data_p); + + return status; +} + + +static void garmin_port_remove(struct usb_serial_port *port) +{ + struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); + + usb_kill_anchored_urbs(&garmin_data_p->write_urbs); + usb_kill_urb(port->interrupt_in_urb); + timer_shutdown_sync(&garmin_data_p->timer); + kfree(garmin_data_p); +} + + +/* All of the device info needed */ +static struct usb_serial_driver garmin_device = { + .driver = { + .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, 0444); +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 000000000..6eaf74930 --- /dev/null +++ b/drivers/usb/serial/generic.c @@ -0,0 +1,656 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Serial Converter Generic functions + * + * Copyright (C) 2010 - 2013 Johan Hovold (jhovold@gmail.com) + * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com) + */ + +#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. */ + +static int usb_serial_generic_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + struct device *dev = &serial->interface->dev; + + dev_info(dev, "The \"generic\" usb-serial driver is only for testing and one-off prototypes.\n"); + dev_info(dev, "Tell linux-usb@vger.kernel.org to add your device to a proper driver.\n"); + + return 0; +} + +static int usb_serial_generic_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + struct device *dev = &serial->interface->dev; + int num_ports; + + num_ports = max(epds->num_bulk_in, epds->num_bulk_out); + + if (num_ports == 0) { + dev_err(dev, "device has no bulk endpoints\n"); + return -ENODEV; + } + + return num_ports; +} + +static struct usb_serial_driver usb_serial_generic_device = { + .driver = { + .name = "generic", + }, + .id_table = generic_device_ids, + .probe = usb_serial_generic_probe, + .calc_num_ports = usb_serial_generic_calc_num_ports, + .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; + + 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 + usb_serial_deregister_drivers(serial_drivers); +#endif +} + +int usb_serial_generic_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int result = 0; + + clear_bit(USB_SERIAL_THROTTLED, &port->flags); + + 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 - start writing buffered data + * @port: usb-serial port + * @mem_flags: flags to use for memory allocations + * + * Serialised using USB_SERIAL_WRITE_BUSY flag. + * + * Return: Zero on success or if busy, otherwise a negative errno value. + */ +int usb_serial_generic_write_start(struct usb_serial_port *port, + gfp_t mem_flags) +{ + 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, mem_flags); + 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 */ +} +EXPORT_SYMBOL_GPL(usb_serial_generic_write_start); + +/** + * usb_serial_generic_write - generic write function + * @tty: tty for the port + * @port: usb-serial port + * @buf: data to write + * @count: number of bytes to write + * + * Return: The number of characters buffered, which may be anything from + * zero to @count, or a 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; + + 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, GFP_ATOMIC); + if (result) + return result; + + return count; +} +EXPORT_SYMBOL_GPL(usb_serial_generic_write); + +unsigned int usb_serial_generic_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned long flags; + unsigned 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 %u\n", __func__, room); + return room; +} + +unsigned int usb_serial_generic_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned long flags; + unsigned 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 %u\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); + if (timeout) + 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 (timeout && 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 && res != -ENODEV) { + 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 = 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->sysrq) { + 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); + } + } else { + tty_insert_flip_string(&port->port, ch, urb->actual_length); + } + 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; + bool stopped = false; + int status = urb->status; + int i; + + for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) { + if (urb == port->read_urbs[i]) + break; + } + + dev_dbg(&port->dev, "%s - urb %d, len %d\n", __func__, i, + urb->actual_length); + switch (status) { + case 0: + usb_serial_debug_data(&port->dev, __func__, urb->actual_length, + data); + port->serial->type->process_read_urb(urb); + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + dev_dbg(&port->dev, "%s - urb stopped: %d\n", + __func__, status); + stopped = true; + break; + case -EPIPE: + dev_err(&port->dev, "%s - urb stopped: %d\n", + __func__, status); + stopped = true; + break; + default: + dev_dbg(&port->dev, "%s - nonzero urb status: %d\n", + __func__, status); + break; + } + + /* + * Make sure URB processing is done before marking as free to avoid + * racing with unthrottle() on another CPU. Matches the barriers + * implied by the test_and_clear_bit() in + * usb_serial_generic_submit_read_urb(). + */ + smp_mb__before_atomic(); + set_bit(i, &port->read_urbs_free); + /* + * Make sure URB is marked as free before checking the throttled flag + * to avoid racing with unthrottle() on another CPU. Matches the + * smp_mb__after_atomic() in unthrottle(). + */ + smp_mb__after_atomic(); + + if (stopped) + return; + + if (test_bit(USB_SERIAL_THROTTLED, &port->flags)) + return; + + usb_serial_generic_submit_read_urb(port, i, GFP_ATOMIC); +} +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); + + switch (status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + dev_dbg(&port->dev, "%s - urb stopped: %d\n", + __func__, status); + return; + case -EPIPE: + dev_err_console(port, "%s - urb stopped: %d\n", + __func__, status); + return; + default: + dev_err_console(port, "%s - nonzero urb status: %d\n", + __func__, status); + break; + } + + usb_serial_generic_write_start(port, GFP_ATOMIC); + 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; + + set_bit(USB_SERIAL_THROTTLED, &port->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; + + clear_bit(USB_SERIAL_THROTTLED, &port->flags); + + /* + * Matches the smp_mb__after_atomic() in + * usb_serial_generic_read_bulk_callback(). + */ + smp_mb__after_atomic(); + + 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. + */ + if (!tty_port_initialized(&port->port)) + 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 && !tty_port_initialized(&port->port)) + 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); + +#if defined(CONFIG_USB_SERIAL_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) +int usb_serial_handle_sysrq_char(struct usb_serial_port *port, unsigned int ch) +{ + if (port->sysrq) { + if (ch && time_before(jiffies, port->sysrq)) { + handle_sysrq(ch); + port->sysrq = 0; + return 1; + } + port->sysrq = 0; + } + return 0; +} +EXPORT_SYMBOL_GPL(usb_serial_handle_sysrq_char); + +int usb_serial_handle_break(struct usb_serial_port *port) +{ + if (!port->port.console) + return 0; + + if (!port->sysrq) { + port->sysrq = jiffies + HZ*5; + return 1; + } + port->sysrq = 0; + return 0; +} +EXPORT_SYMBOL_GPL(usb_serial_handle_break); +#endif + +/** + * usb_serial_handle_dcd_change - handle a change of carrier detect state + * @port: usb-serial port + * @tty: tty for the port + * @status: new carrier detect status, nonzero if active + */ +void usb_serial_handle_dcd_change(struct usb_serial_port *port, + struct tty_struct *tty, unsigned int status) +{ + dev_dbg(&port->dev, "%s - status %d\n", __func__, status); + + if (tty) { + struct tty_ldisc *ld = tty_ldisc_ref(tty); + + if (ld) { + if (ld->ops->dcd_change) + ld->ops->dcd_change(tty, status); + tty_ldisc_deref(ld); + } + } + + if (status) + wake_up_interruptible(&port->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 (!tty_port_initialized(&port->port)) + 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, GFP_NOIO); + if (r < 0) + c++; + } + } + + return c ? -EIO : 0; +} +EXPORT_SYMBOL_GPL(usb_serial_generic_resume); diff --git a/drivers/usb/serial/io_16654.h b/drivers/usb/serial/io_16654.h new file mode 100644 index 000000000..f18501f05 --- /dev/null +++ b/drivers/usb/serial/io_16654.h @@ -0,0 +1,192 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/************************************************************************ + * + * 16654.H Definitions for 16C654 UART used on EdgePorts + * + * Copyright (C) 1998 Inside Out Networks, Inc. + * + ************************************************************************/ + +#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 000000000..eaa8f0071 --- /dev/null +++ b/drivers/usb/serial/io_edgeport.c @@ -0,0 +1,3132 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Edgeport USB Serial Converter driver + * + * Copyright (C) 2000 Inside Out Networks, All rights reserved. + * Copyright (C) 2001-2002 Greg Kroah-Hartman + * + * 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 "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 OPEN_TIMEOUT (5*HZ) /* 5 seconds */ + +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) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_BLACKBOX_IC135A) }, + { } +}; + +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_ION, ION_DEVICE_ID_BLACKBOX_IC135A) }, + { 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); + + +/* 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 parameter */ + __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); + + +/* function prototypes */ + +static void edge_close(struct usb_serial_port *port); + +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 void change_port_settings(struct tty_struct *tty, + struct edgeport_port *edge_port, + const 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 rom_write(struct usb_serial *serial, __u16 extAddr, __u16 addr, + __u16 length, const __u8 *data); + +/* ************************************************************************ */ +/* ************************************************************************ */ +/* ************************************************************************ */ +/* ************************************************************************ */ + +/************************************************************************ + * * + * 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); +} + +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: + case ION_DEVICE_ID_BLACKBOX_IC135A: + 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; + struct edge_compatibility_bits *bits; + struct device *dev = &serial->dev->dev; + + ep->is_epic = 0; + + epic = kmalloc_obj(*epic); + if (!epic) + return -ENOMEM; + + result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + USB_REQUEST_ION_GET_EPIC_DESC, + 0xC0, 0x00, 0x00, + epic, sizeof(*epic), + 300); + if (result == sizeof(*epic)) { + ep->is_epic = 1; + memcpy(&ep->epic_descriptor, epic, sizeof(*epic)); + 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"); + + result = 0; + } else if (result >= 0) { + dev_warn(&serial->interface->dev, "short epic descriptor received: %d\n", + result); + result = -EIO; + } + + kfree(epic); + + 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; + unsigned long flags; + 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_irqsave(&edge_serial->es_lock, flags); + 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_irqrestore(&edge_serial->es_lock, + flags); + } + } + /* grab the txcredits for the ports if available */ + position = 2; + portNumber = 0; + while ((position < length - 1) && + (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 && edge_port->open) { + spin_lock_irqsave(&edge_port->ep_lock, + flags); + edge_port->txCredits = min(edge_port->txCredits + txCredits, + edge_port->maxTxCredits); + spin_unlock_irqrestore(&edge_port->ep_lock, + flags); + 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; + unsigned long flags; + + 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_irqsave(&edge_serial->es_lock, flags); + + /* 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_irqrestore(&edge_serial->es_lock, flags); +} + + +/***************************************************************************** + * 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; + struct device *dev = &urb->dev->dev; + int status = urb->status; + + atomic_dec(&CmdUrbs); + dev_dbg(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(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 timeout\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) { + 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) { + edge_close(port); + return -ENOMEM; + } + + dev_dbg(dev, "%s - Initialize TX fifo to %d bytes\n", + __func__, 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->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->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_t(unsigned int, count, + (edge_port->txCredits - fifo->count)); + + dev_dbg(&port->dev, "%s of %d byte(s) Fifo room %d -- will copy %d bytes\n", + __func__, 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 EXIT - fifo %d, PendingWrite = %d\n", + __func__, 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 Not enough credit - fifo %d TxCredit %d\n", + __func__, 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) { + edge_port->write_in_progress = false; + goto exit_send; + } + buffer[0] = IOSP_BUILD_DATA_HDR1(edge_port->port->port_number, count); + buffer[1] = IOSP_BUILD_DATA_HDR2(edge_port->port->port_number, 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. + *****************************************************************************/ +static unsigned 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); + unsigned int room; + unsigned long flags; + + /* 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 %u\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) + *****************************************************************************/ +static unsigned 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); + unsigned int num_chars; + unsigned long flags; + + 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 - returns %u\n", __func__, 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 (C_CRTSCTS(tty)) { + 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 (C_CRTSCTS(tty)) { + 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, + const struct ktermios *old_termios) +{ + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + + 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; +} + +/***************************************************************************** + * 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; + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + + switch (cmd) { + case TIOCSERGETLSR: + dev_dbg(&port->dev, "%s TIOCSERGETLSR\n", __func__); + return get_lsr_info(edge_port, (unsigned int __user *) arg); + } + return -ENOIOCTLCMD; +} + + +/***************************************************************************** + * SerialBreak + * this function sends a break to the port + *****************************************************************************/ +static int 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 = 0; + + if (!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->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__); + } + + return status; +} + + +/***************************************************************************** + * 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 usb_serial *serial = edge_serial->serial; + struct device *dev = &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; + } + fallthrough; + 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; + } + + 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); + + if (bufferLength == 0) { + edge_serial->rxState = EXPECT_DATA; + break; + } + fallthrough; + 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 && edge_serial->rxPort < serial->num_ports) { + port = serial->port[edge_serial->rxPort]; + edge_port = usb_get_serial_port_data(port); + if (edge_port && 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 */ + if (edge_serial->rxPort >= edge_serial->serial->num_ports) + return; + 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) + 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) + 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. + * Returns zero on success or a negative error number. + ****************************************************************************/ +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) + 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 < current_length) { + if (result >= 0) + result = -EIO; + break; + } + memcpy(data, transfer_buffer, current_length); + length -= current_length; + addr += current_length; + data += current_length; + + result = 0; + } + + 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) + return -ENOMEM; + + currentCommand = buffer; + + MAKE_CMD_EXT_CMD(¤tCommand, &length, edge_port->port->port_number, + 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_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; + u32 number = edge_port->port->port_number; + + if (edge_serial->is_epic && + !edge_serial->epic_descriptor.Supports.IOSPSetBaudRate) { + dev_dbg(dev, "SendCmdWriteBaudRate - NOT Setting baud rate for port, baud = %d\n", + baudRate); + return 0; + } + + dev_dbg(dev, "%s - baud = %d\n", __func__, 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) + 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->port_number, + 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, const 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; + + 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->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->epic_descriptor.Supports.IOSPSetRxFlow) + send_iosp_ext_cmd(edge_port, IOSP_CMD_SET_RX_FLOW, rxFlow); + if (!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 < 0) { + dev_err(dev, "error in getting manufacturer descriptor: %d\n", + response); + } 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 < 0) { + dev_err(dev, "error in getting boot descriptor: %d\n", + response); + } 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 const __u32 descriptor[3] = { EDGE_COMPATIBILITY_MASK0, + EDGE_COMPATIBILITY_MASK1, + EDGE_COMPATIBILITY_MASK2 }; + + dev = serial->dev; + + /* create our private serial structure */ + edge_serial = kzalloc_obj(struct edgeport_serial); + if (!edge_serial) + 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) { + struct usb_host_interface *alt; + + alt = serial->interface->cur_altsetting; + + /* 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 < alt->desc.bNumEndpoints; ++i) { + struct usb_endpoint_descriptor *endpoint; + int buffer_size; + + endpoint = &alt->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) { + response = -ENOMEM; + break; + } + + edge_serial->interrupt_in_buffer = + kmalloc(buffer_size, GFP_KERNEL); + if (!edge_serial->interrupt_in_buffer) { + response = -ENOMEM; + break; + } + 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) { + response = -ENOMEM; + break; + } + + edge_serial->bulk_in_buffer = + kmalloc(buffer_size, GFP_KERNEL); + if (!edge_serial->bulk_in_buffer) { + response = -ENOMEM; + break; + } + 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 (response || !interrupt_in_found || !bulk_in_found || + !bulk_out_found) { + if (!response) { + dev_err(ddev, "expected endpoints not found\n"); + response = -ENODEV; + } + + goto error; + } + + /* 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); + + goto error; + } + } + return response; + +error: + usb_free_urb(edge_serial->interrupt_read_urb); + kfree(edge_serial->interrupt_in_buffer); + + usb_free_urb(edge_serial->read_urb); + kfree(edge_serial->bulk_in_buffer); + + kfree(edge_serial); + + 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); + + if (edge_serial->is_epic) { + usb_kill_urb(edge_serial->interrupt_read_urb); + usb_kill_urb(edge_serial->read_urb); + } +} + + +/**************************************************************************** + * 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); + + 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); + } + + kfree(edge_serial); +} + +static int edge_port_probe(struct usb_serial_port *port) +{ + struct edgeport_port *edge_port; + + edge_port = kzalloc_obj(*edge_port); + 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 void edge_port_remove(struct usb_serial_port *port) +{ + struct edgeport_port *edge_port; + + edge_port = usb_get_serial_port_data(port); + kfree(edge_port); +} + +static struct usb_serial_driver edgeport_2port_device = { + .driver = { + .name = "edgeport_2", + }, + .description = "Edgeport 2 port adapter", + .id_table = edgeport_2port_id_table, + .num_ports = 2, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 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 edgeport_4port_device = { + .driver = { + .name = "edgeport_4", + }, + .description = "Edgeport 4 port adapter", + .id_table = edgeport_4port_id_table, + .num_ports = 4, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 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 edgeport_8port_device = { + .driver = { + .name = "edgeport_8", + }, + .description = "Edgeport 8 port adapter", + .id_table = edgeport_8port_id_table, + .num_ports = 8, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 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 epic_device = { + .driver = { + .name = "epic", + }, + .description = "EPiC device", + .id_table = Epic_port_id_table, + .num_ports = 1, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 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 +}; + +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 000000000..7c9f62af5 --- /dev/null +++ b/drivers/usb/serial/io_edgeport.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/************************************************************************ + * + * io_edgeport.h Edgeport Linux Interface definitions + * + * Copyright (C) 2000 Inside Out Networks, Inc. + * + ************************************************************************/ + +#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 + +#include "io_usbvend.h" + +/* + * 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; +}; + +#endif diff --git a/drivers/usb/serial/io_ionsp.h b/drivers/usb/serial/io_ionsp.h new file mode 100644 index 000000000..db4fce815 --- /dev/null +++ b/drivers/usb/serial/io_ionsp.h @@ -0,0 +1,451 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/************************************************************************ + * + * IONSP.H Definitions for I/O Networks Serial Protocol + * + * Copyright (C) 1997-1998 Inside Out Networks, Inc. + * + * 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_ti.c b/drivers/usb/serial/io_ti.c new file mode 100644 index 000000000..cc28f5869 --- /dev/null +++ b/drivers/usb/serial/io_ti.c @@ -0,0 +1,2775 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Edgeport USB Serial Converter driver + * + * Copyright (C) 2000-2002 Inside Out Networks, All rights reserved. + * Copyright (C) 2001-2002 Greg Kroah-Hartman + * + * 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 "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 + + +/* Product information read from the Edgeport */ +struct product_info { + int TiMode; /* Current TI Mode */ + u8 hardware_type; /* Type of hardware */ +} __packed; + +/* + * Edgeport firmware header + * + * "build_number" has been set to 0 in all three of the images I have + * seen, and Digi Tech Support suggests that it is safe to ignore it. + * + * "length" is the number of bytes of actual data following the header. + * + * "checksum" is the low order byte resulting from adding the values of + * all the data bytes. + */ +struct edgeport_fw_hdr { + u8 major_version; + u8 minor_version; + __le16 build_number; + __le16 length; + u8 checksum; +} __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 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; + struct delayed_work heartbeat_work; + int fw_version; + bool use_heartbeat; +}; + + +/* 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) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_E5805A) }, + { } +}; + +/* 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) }, + { USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_E5805A) }, + { } +}; + +MODULE_DEVICE_TABLE(usb, id_table_combined); + +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, + const struct ktermios *old_termios); +static void edge_send(struct usb_serial_port *port, struct tty_struct *tty); + +static int do_download_mode(struct edgeport_serial *serial, + const struct firmware *fw); +static int do_boot_mode(struct edgeport_serial *serial, + const struct firmware *fw); + +/* sysfs attributes */ +static int edge_create_sysfs_attrs(struct usb_serial_port *port); +static int edge_remove_sysfs_attrs(struct usb_serial_port *port); + +/* + * Some release of Edgeport firmware "down3.bin" after version 4.80 + * introduced code to automatically disconnect idle devices on some + * Edgeport models after periods of inactivity, typically ~60 seconds. + * This occurs without regard to whether ports on the device are open + * or not. Digi International Tech Support suggested: + * + * 1. Adding driver "heartbeat" code to reset the firmware timer by + * requesting a descriptor record every 15 seconds, which should be + * effective with newer firmware versions that require it, and benign + * with older versions that do not. In practice 40 seconds seems often + * enough. + * 2. The heartbeat code is currently required only on Edgeport/416 models. + */ +#define FW_HEARTBEAT_VERSION_CUTOFF ((4 << 8) + 80) +#define FW_HEARTBEAT_SECS 40 + +/* Timeouts in msecs: firmware downloads take longer */ +#define TI_VSEND_TIMEOUT_DEFAULT 1000 +#define TI_VSEND_TIMEOUT_FW_DOWNLOAD 10000 + +static int ti_vread_sync(struct usb_device *dev, u8 request, u16 value, + u16 index, void *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 read %d, but only read %d\n", + __func__, size, status); + return -ECOMM; + } + return 0; +} + +static int ti_vsend_sync(struct usb_device *dev, u8 request, u16 value, + u16 index, void *data, int size, int timeout) +{ + 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, timeout); + if (status < 0) + return status; + + return 0; +} + +static int read_port_cmd(struct usb_serial_port *port, u8 command, u16 value, + void *data, int size) +{ + return ti_vread_sync(port->serial->dev, command, value, + UMPM_UART1_PORT + port->port_number, + data, size); +} + +static int send_port_cmd(struct usb_serial_port *port, u8 command, u16 value, + void *data, int size) +{ + return ti_vsend_sync(port->serial->dev, command, value, + UMPM_UART1_PORT + port->port_number, + data, size, TI_VSEND_TIMEOUT_DEFAULT); +} + +/* clear tx/rx buffers and fifo in TI UMP */ +static int purge_port(struct usb_serial_port *port, u16 mask) +{ + int port_number = port->port_number; + + dev_dbg(&port->dev, "%s - port %d, mask %x\n", __func__, port_number, mask); + + return send_port_cmd(port, UMPC_PURGE_PORT, 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) + 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, TI_VSEND_TIMEOUT_DEFAULT); + 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, TI_VSEND_TIMEOUT_DEFAULT); + 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, TI_VSEND_TIMEOUT_DEFAULT); + 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_obj(*oedb); + if (!oedb) + return -ENOMEM; + + /* + * Sigh, that's right, just one byte, as not all platforms can + * do DMA from stack + */ + lsr = kmalloc(1, GFP_KERNEL); + 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_obj(*rom_desc); + if (!rom_desc) + return -ENOMEM; + + buffer = kmalloc(TI_MAX_I2C_SIZE, GFP_KERNEL); + if (!buffer) { + 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_obj(*rom_desc); + if (!rom_desc) + 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 */ + if (le16_to_cpu(rom_desc->Size) != sizeof(struct edge_ti_manuf_descriptor)) { + dev_err(dev, "unexpected Edge descriptor length: %u\n", + le16_to_cpu(rom_desc->Size)); + status = -EINVAL; + goto exit; + } + 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, const struct firmware *fw) +{ + u8 *buffer; + int buffer_size; + int i; + u8 cs = 0; + struct ti_i2c_desc *i2c_header; + struct ti_i2c_image_header *img_header; + struct ti_i2c_firmware_rec *firmware_rec; + struct edgeport_fw_hdr *fw_hdr = (struct edgeport_fw_hdr *)fw->data; + + /* + * 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) + return -ENOMEM; + + /* Set entire image of 0xffs */ + memset(buffer, 0xff, buffer_size); + + /* Copy version number into firmware record */ + firmware_rec = (struct ti_i2c_firmware_rec *)buffer; + + firmware_rec->Ver_Major = fw_hdr->major_version; + firmware_rec->Ver_Minor = fw_hdr->minor_version; + + /* Pointer to fw_down memory image */ + img_header = (struct ti_i2c_image_header *)&fw->data[4]; + + if (le16_to_cpu(img_header->Length) > + buffer_size - sizeof(struct ti_i2c_firmware_rec)) { + kfree(buffer); + return -EINVAL; + } + memcpy(buffer + sizeof(struct ti_i2c_firmware_rec), + &fw->data[4 + sizeof(struct ti_i2c_image_header)], + le16_to_cpu(img_header->Length)); + + 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 = fw_hdr->major_version; + firmware_rec->Ver_Minor = fw_hdr->minor_version; + + 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) + 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); +} + +static int check_fw_sanity(struct edgeport_serial *serial, + const struct firmware *fw) +{ + u16 length_total; + u8 checksum = 0; + int pos; + struct device *dev = &serial->serial->interface->dev; + struct edgeport_fw_hdr *fw_hdr = (struct edgeport_fw_hdr *)fw->data; + + if (fw->size < sizeof(struct edgeport_fw_hdr)) { + dev_err(dev, "incomplete fw header\n"); + return -EINVAL; + } + + length_total = le16_to_cpu(fw_hdr->length) + + sizeof(struct edgeport_fw_hdr); + + if (fw->size != length_total) { + dev_err(dev, "bad fw size (expected: %u, got: %zu)\n", + length_total, fw->size); + return -EINVAL; + } + + for (pos = sizeof(struct edgeport_fw_hdr); pos < fw->size; ++pos) + checksum += fw->data[pos]; + + if (checksum != fw_hdr->checksum) { + dev_err(dev, "bad fw checksum (expected: 0x%x, got: 0x%x)\n", + fw_hdr->checksum, checksum); + return -EINVAL; + } + + return 0; +} + +/* + * 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->interface->dev; + int status = 0; + struct usb_interface_descriptor *interface; + const struct firmware *fw; + const char *fw_name = "edgeport/down3.bin"; + struct edgeport_fw_hdr *fw_hdr; + + status = request_firmware(&fw, fw_name, dev); + if (status) { + dev_err(dev, "Failed to load image \"%s\" err %d\n", + fw_name, status); + return status; + } + + if (check_fw_sanity(serial, fw)) { + status = -EINVAL; + goto out; + } + + fw_hdr = (struct edgeport_fw_hdr *)fw->data; + + /* If on-board version is newer, "fw_version" will be updated later. */ + serial->fw_version = (fw_hdr->major_version << 8) + + fw_hdr->minor_version; + + /* + * 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) + goto out; + + interface = &serial->serial->interface->cur_altsetting->desc; + if (!interface) { + dev_err(dev, "%s - no interface set, error!\n", __func__); + status = -ENODEV; + goto out; + } + + /* + * 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; + status = do_download_mode(serial, fw); + } else { + /* Otherwise we will remain in configuring mode */ + serial->product_info.TiMode = TI_MODE_CONFIGURING; + status = do_boot_mode(serial, fw); + } + +out: + release_firmware(fw); + return status; +} + +static int do_download_mode(struct edgeport_serial *serial, + const struct firmware *fw) +{ + struct device *dev = &serial->serial->interface->dev; + int status = 0; + int start_address; + struct edge_ti_manuf_descriptor *ti_manuf_desc; + int download_cur_ver; + int download_new_ver; + struct edgeport_fw_hdr *fw_hdr = (struct edgeport_fw_hdr *)fw->data; + 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_obj(*ti_manuf_desc); + if (!ti_manuf_desc) + 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_obj(*rom_desc); + if (!rom_desc) { + 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_obj(*firmware_version); + if (!firmware_version) { + 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 = (fw_hdr->major_version << 8) + + (fw_hdr->minor_version); + + dev_dbg(dev, "%s - >> FW Versions Device %d.%d Driver %d.%d\n", + __func__, firmware_version->Ver_Major, + firmware_version->Ver_Minor, + fw_hdr->major_version, fw_hdr->minor_version); + + /* + * 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, + fw_hdr->major_version, + fw_hdr->minor_version); + + record = kmalloc(1, GFP_KERNEL); + if (!record) { + 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, + TI_VSEND_TIMEOUT_DEFAULT); + + 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; + } + /* Same or newer fw version is already loaded */ + serial->fw_version = download_cur_ver; + kfree(firmware_version); + } + /* Search for type 0xF2 record (firmware blank record) */ + else { + start_address = get_descriptor_addr(serial, + I2C_DESC_TYPE_FIRMWARE_BLANK, rom_desc); + if (start_address != 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) { + kfree(rom_desc); + kfree(ti_manuf_desc); + return -ENOMEM; + } + + vheader = kmalloc(HEADER_SIZE, GFP_KERNEL); + if (!vheader) { + 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, fw); + 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, + TI_VSEND_TIMEOUT_FW_DOWNLOAD); + + 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; +} + +static int do_boot_mode(struct edgeport_serial *serial, + const struct firmware *fw) +{ + struct device *dev = &serial->serial->interface->dev; + int status = 0; + struct edge_ti_manuf_descriptor *ti_manuf_desc; + struct edgeport_fw_hdr *fw_hdr = (struct edgeport_fw_hdr *)fw->data; + + 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; + + /* + * Validate Hardware version number + * Read Manufacturing Descriptor from TI Based Edgeport + */ + ti_manuf_desc = kmalloc_obj(*ti_manuf_desc); + if (!ti_manuf_desc) + 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)); + if (fw->size - 4 > buffer_size) { + dev_err(dev, "%s - firmware image is too large\n", + __func__); + return -EINVAL; + } + + buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + /* Initialize the buffer to 0xff (pad the buffer) */ + memset(buffer, 0xff, buffer_size); + memcpy(buffer, &fw->data[4], fw->size - 4); + + 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 version %d.%d (TI UMP)\n", + __func__, + fw_hdr->major_version, fw_hdr->minor_version); + 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 1; + } + +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 1; +} + +static int ti_do_config(struct edgeport_port *port, int feature, int on) +{ + on = !!on; /* 1 or 0 not bitmask */ + + return send_port_cmd(port->port, feature, 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_wakeup(tty); + } + 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]); + + if (port_number >= edge_serial->serial->num_ports) { + dev_err(dev, "bad port number %d\n", port_number); + goto exit; + } + + 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; + unsigned long flags; + 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->port_number; + + if (urb->actual_length > 0 && 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_irqsave(&edge_port->ep_lock, flags); + 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_irqrestore(&edge_port->ep_lock, flags); + 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 status; + u16 open_settings; + u8 transaction_timeout; + + if (edge_port == NULL) + return -ENODEV; + + 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_port_cmd(port, UMPC_OPEN_PORT, 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_port_cmd(port, UMPC_START_PORT, 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 = read_port_cmd(port, UMPC_READ_MSR, 0, &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; + 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; + 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; + + 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; + unsigned long flags; + + 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(&port->write_fifo); + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + + dev_dbg(&port->dev, "%s - send umpc_close_port\n", __func__); + send_port_cmd(port, UMPC_CLOSE_PORT, 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(&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(&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 unsigned 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); + unsigned int room; + 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(&port->write_fifo); + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + + dev_dbg(&port->dev, "%s - returns %u\n", __func__, room); + return room; +} + +static unsigned 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); + unsigned int chars; + unsigned long flags; + if (edge_port == NULL) + return 0; + + spin_lock_irqsave(&edge_port->ep_lock, flags); + chars = kfifo_len(&port->write_fifo); + spin_unlock_irqrestore(&edge_port->ep_lock, flags); + + dev_dbg(&port->dev, "%s - returns %u\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, const struct ktermios *old_termios) +{ + struct device *dev = &edge_port->port->dev; + struct ump_uart_config *config; + int baud; + unsigned cflag; + int status; + + config = kmalloc_obj(*config); + if (!config) { + tty->termios = *old_termios; + 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__); + 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 { + /* Avoid a zero divisor. */ + baud = min(baud, 461550); + 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_port_cmd(edge_port->port, UMPC_SET_CONFIG, 0, 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, + const struct ktermios *old_termios) +{ + struct edgeport_port *edge_port = usb_get_serial_port_data(port); + + 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 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); + return status; + } + + return 0; +} + +static void edge_heartbeat_schedule(struct edgeport_serial *edge_serial) +{ + if (!edge_serial->use_heartbeat) + return; + + schedule_delayed_work(&edge_serial->heartbeat_work, + FW_HEARTBEAT_SECS * HZ); +} + +static void edge_heartbeat_work(struct work_struct *work) +{ + struct edgeport_serial *serial; + struct ti_i2c_desc *rom_desc; + + serial = container_of(work, struct edgeport_serial, + heartbeat_work.work); + + rom_desc = kmalloc_obj(*rom_desc); + + /* Descriptor address request is enough to reset the firmware timer */ + if (!rom_desc || !get_descriptor_addr(serial, I2C_DESC_TYPE_ION, + rom_desc)) { + dev_err(&serial->serial->interface->dev, + "%s - Incomplete heartbeat\n", __func__); + } + kfree(rom_desc); + + edge_heartbeat_schedule(serial); +} + +static int edge_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + struct device *dev = &serial->interface->dev; + unsigned char num_ports = serial->type->num_ports; + + /* Make sure we have the required endpoints when in download mode. */ + if (serial->interface->cur_altsetting->desc.bNumEndpoints > 1) { + if (epds->num_bulk_in < num_ports || + epds->num_bulk_out < num_ports || + epds->num_interrupt_in < 1) { + dev_err(dev, "required endpoints missing\n"); + return -ENODEV; + } + } + + return num_ports; +} + +static int edge_startup(struct usb_serial *serial) +{ + struct edgeport_serial *edge_serial; + int status; + u16 product_id; + + /* create our private serial structure */ + edge_serial = kzalloc_obj(struct edgeport_serial); + if (!edge_serial) + return -ENOMEM; + + mutex_init(&edge_serial->es_lock); + edge_serial->serial = serial; + INIT_DELAYED_WORK(&edge_serial->heartbeat_work, edge_heartbeat_work); + usb_set_serial_data(serial, edge_serial); + + status = download_fw(edge_serial); + if (status < 0) { + kfree(edge_serial); + return status; + } + + if (status > 0) + return 1; /* bind but do not register any ports */ + + product_id = le16_to_cpu( + edge_serial->serial->dev->descriptor.idProduct); + + /* Currently only the EP/416 models require heartbeat support */ + if (edge_serial->fw_version > FW_HEARTBEAT_VERSION_CUTOFF) { + if (product_id == ION_DEVICE_ID_TI_EDGEPORT_416 || + product_id == ION_DEVICE_ID_TI_EDGEPORT_416B) { + edge_serial->use_heartbeat = true; + } + } + + edge_heartbeat_schedule(edge_serial); + + return 0; +} + +static void edge_disconnect(struct usb_serial *serial) +{ + struct edgeport_serial *edge_serial = usb_get_serial_data(serial); + + cancel_delayed_work_sync(&edge_serial->heartbeat_work); +} + +static void edge_release(struct usb_serial *serial) +{ + struct edgeport_serial *edge_serial = usb_get_serial_data(serial); + + cancel_delayed_work_sync(&edge_serial->heartbeat_work); + kfree(edge_serial); +} + +static int edge_port_probe(struct usb_serial_port *port) +{ + struct edgeport_port *edge_port; + int ret; + + edge_port = kzalloc_obj(*edge_port); + if (!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; + + switch (port->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"); + ret = -ENODEV; + goto err; + } + + dev_dbg(&port->dev, + "%s - port_number = %d, uart_base = %04x, dma_address = %04x\n", + __func__, port->port_number, edge_port->uart_base, + edge_port->dma_address); + + usb_set_serial_port_data(port, edge_port); + + ret = edge_create_sysfs_attrs(port); + if (ret) + goto err; + + /* + * The LSR does not tell when the transmitter shift register has + * emptied so add a one-character drain delay. + */ + port->port.drain_delay = 1; + + return 0; +err: + kfree(edge_port); + + return ret; +} + +static void 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); + kfree(edge_port); +} + +/* Sysfs Attributes */ + +static ssize_t uart_mode_show(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 uart_mode_store(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_RW(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; +} + +#ifdef CONFIG_PM +static int edge_suspend(struct usb_serial *serial, pm_message_t message) +{ + struct edgeport_serial *edge_serial = usb_get_serial_data(serial); + + cancel_delayed_work_sync(&edge_serial->heartbeat_work); + + return 0; +} + +static int edge_resume(struct usb_serial *serial) +{ + struct edgeport_serial *edge_serial = usb_get_serial_data(serial); + + edge_heartbeat_schedule(edge_serial); + + return 0; +} +#endif + +static struct usb_serial_driver edgeport_1port_device = { + .driver = { + .name = "edgeport_ti_1", + }, + .description = "Edgeport TI 1 port adapter", + .id_table = edgeport_1port_id_table, + .num_ports = 1, + .num_bulk_out = 1, + .open = edge_open, + .close = edge_close, + .throttle = edge_throttle, + .unthrottle = edge_unthrottle, + .attach = edge_startup, + .calc_num_ports = edge_calc_num_ports, + .disconnect = edge_disconnect, + .release = edge_release, + .port_probe = edge_port_probe, + .port_remove = edge_port_remove, + .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, +#ifdef CONFIG_PM + .suspend = edge_suspend, + .resume = edge_resume, +#endif +}; + +static struct usb_serial_driver edgeport_2port_device = { + .driver = { + .name = "edgeport_ti_2", + }, + .description = "Edgeport TI 2 port adapter", + .id_table = edgeport_2port_id_table, + .num_ports = 2, + .num_bulk_out = 1, + .open = edge_open, + .close = edge_close, + .throttle = edge_throttle, + .unthrottle = edge_unthrottle, + .attach = edge_startup, + .calc_num_ports = edge_calc_num_ports, + .disconnect = edge_disconnect, + .release = edge_release, + .port_probe = edge_port_probe, + .port_remove = edge_port_remove, + .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, +#ifdef CONFIG_PM + .suspend = edge_suspend, + .resume = edge_resume, +#endif +}; + +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(ignore_cpu_rev, bool, 0644); +MODULE_PARM_DESC(ignore_cpu_rev, + "Ignore the cpu revision when connecting to a device"); + +module_param(default_uart_mode, int, 0644); +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 000000000..24fe1312c --- /dev/null +++ b/drivers/usb/serial/io_ti.h @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/***************************************************************************** + * + * Copyright (C) 1997-2002 Inside Out Networks, Inc. + * + * 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; +}; + + +/* + * 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 */ +}; + + +/* + * TYPE DEFINITIONS + * Structures for USB interrupts + */ +/* Interrupt packet structure */ +struct ump_interrupt { + u8 bICode; /* Interrupt code (interrupt num) */ + u8 bIInfo; /* Interrupt information */ +}; + + +#define TIUMP_GET_PORT_FROM_CODE(c) (((c) >> 6) & 0x01) +#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 000000000..c82a275e8 --- /dev/null +++ b/drivers/usb/serial/io_usbvend.h @@ -0,0 +1,682 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/************************************************************************ + * + * 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. + * + ************************************************************************/ + +#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_BLACKBOX_IC135A 0x0801 // OEM device (rebranded Edgeport/4) +#define ION_DEVICE_ID_MT4X56USB 0x1403 // OEM device +#define ION_DEVICE_ID_E5805A 0x1A01 // OEM device (rebranded Edgeport/4) + + +#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[]; // 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[]; // 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 000000000..c01bdbcb3 --- /dev/null +++ b/drivers/usb/serial/ipaq.c @@ -0,0 +1,604 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * USB Compaq iPAQ driver + * + * Copyright (C) 2001 - 2002 + * Ganesh Varadarajan + */ + +#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, + struct usb_serial_endpoints *epds); +static int ipaq_startup(struct usb_serial *serial); + +static const 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 = { + .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) { + 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, + struct usb_serial_endpoints *epds) +{ + /* + * 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 (epds->num_bulk_in == 0 || epds->num_bulk_out == 0) + return -ENODEV; + + /* + * A few devices have four endpoints, seemingly Yakuma devices, and + * we need the second pair. + */ + if (epds->num_bulk_in > 1 && epds->num_bulk_out > 1) { + epds->bulk_in[0] = epds->bulk_in[1]; + epds->bulk_out[0] = epds->bulk_out[1]; + } + + /* + * Other devices have 3 endpoints, but we only use the first bulk in + * and out endpoints. + */ + epds->num_bulk_in = 1; + epds->num_bulk_out = 1; + + return 1; +} + +static int ipaq_startup(struct usb_serial *serial) +{ + 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; + } + + 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, 0644); +MODULE_PARM_DESC(connect_retries, + "Maximum number of connect retries (one second each)"); + +module_param(initial_wait, int, 0644); +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 000000000..5be153f2c --- /dev/null +++ b/drivers/usb/serial/ipw.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IPWireless 3G UMTS TDD Modem driver (USB connected) + * + * Copyright (C) 2004 Roelf Diedericks + * Copyright (C) 2004 Greg Kroah-Hartman + * + * 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 "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_obj(struct usb_wwan_intf_private); + 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 = { + .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 000000000..aac845397 --- /dev/null +++ b/drivers/usb/serial/ir-usb.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 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.rst for more information on using this + * driver + */ + +#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_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count); +static unsigned int ir_write_room(struct tty_struct *tty); +static void ir_write_bulk_callback(struct urb *urb); +static void ir_process_read_urb(struct urb *urb); +static void ir_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const 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 = { + .name = "ir-usb", + }, + .description = "IR Dongle", + .id_table = ir_id_table, + .num_ports = 1, + .num_bulk_in = 1, + .num_bulk_out = 1, + .set_termios = ir_set_termios, + .attach = ir_startup, + .write = ir_write, + .write_room = ir_write_room, + .write_bulk_callback = ir_write_bulk_callback, + .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_obj(*desc); + 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 < (int)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; + int rates; + + 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; + } + + rates = le16_to_cpu(irda_desc->wBaudRate); + + dev_dbg(&serial->dev->dev, + "%s - Baud rates supported:%s%s%s%s%s%s%s%s%s\n", + __func__, + (rates & USB_IRDA_BR_2400) ? " 2400" : "", + (rates & USB_IRDA_BR_9600) ? " 9600" : "", + (rates & USB_IRDA_BR_19200) ? " 19200" : "", + (rates & USB_IRDA_BR_38400) ? " 38400" : "", + (rates & USB_IRDA_BR_57600) ? " 57600" : "", + (rates & USB_IRDA_BR_115200) ? " 115200" : "", + (rates & USB_IRDA_BR_576000) ? " 576000" : "", + (rates & USB_IRDA_BR_1152000) ? " 1152000" : "", + (rates & 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_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + struct urb *urb = NULL; + unsigned long flags; + int ret; + + if (port->bulk_out_size == 0) + return -EINVAL; + + if (count == 0) + return 0; + + count = min(count, port->bulk_out_size - 1); + + spin_lock_irqsave(&port->lock, flags); + if (__test_and_clear_bit(0, &port->write_urbs_free)) { + urb = port->write_urbs[0]; + port->tx_bytes += count; + } + spin_unlock_irqrestore(&port->lock, flags); + + if (!urb) + return 0; + + /* + * The first byte of the packet we send to the device contains an + * outbound header which indicates an additional number of BOFs and + * a baud rate change. + * + * See section 5.4.2.2 of the USB IrDA spec. + */ + *(u8 *)urb->transfer_buffer = ir_xbof | ir_baud; + + memcpy(urb->transfer_buffer + 1, buf, count); + + urb->transfer_buffer_length = count + 1; + urb->transfer_flags = URB_ZERO_PACKET; + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) { + dev_err(&port->dev, "failed to submit write urb: %d\n", ret); + + spin_lock_irqsave(&port->lock, flags); + __set_bit(0, &port->write_urbs_free); + port->tx_bytes -= count; + spin_unlock_irqrestore(&port->lock, flags); + + return ret; + } + + return count; +} + +static void ir_write_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + int status = urb->status; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + __set_bit(0, &port->write_urbs_free); + port->tx_bytes -= urb->transfer_buffer_length - 1; + spin_unlock_irqrestore(&port->lock, flags); + + switch (status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + dev_dbg(&port->dev, "write urb stopped: %d\n", status); + return; + case -EPIPE: + dev_err(&port->dev, "write urb stopped: %d\n", status); + return; + default: + dev_err(&port->dev, "nonzero write-urb status: %d\n", status); + break; + } + + usb_serial_port_softint(port); +} + +static unsigned int ir_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned int count = 0; + + if (port->bulk_out_size == 0) + return 0; + + if (test_bit(0, &port->write_urbs_free)) + count = port->bulk_out_size - 1; + + return count; +} + +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(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +{ + struct usb_device *udev = port->serial->dev; + unsigned char *transfer_buffer; + int actual_length; + speed_t baud; + int ir_baud; + int ret; + + 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_LS_2400; + break; + case 9600: + ir_baud = USB_IRDA_LS_9600; + break; + case 19200: + ir_baud = USB_IRDA_LS_19200; + break; + case 38400: + ir_baud = USB_IRDA_LS_38400; + break; + case 57600: + ir_baud = USB_IRDA_LS_57600; + break; + case 115200: + ir_baud = USB_IRDA_LS_115200; + break; + case 576000: + ir_baud = USB_IRDA_LS_576000; + break; + case 1152000: + ir_baud = USB_IRDA_LS_1152000; + break; + case 4000000: + ir_baud = USB_IRDA_LS_4000000; + break; + default: + ir_baud = USB_IRDA_LS_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 + */ + transfer_buffer = kmalloc(1, GFP_KERNEL); + if (!transfer_buffer) + return; + + *transfer_buffer = ir_xbof | ir_baud; + + ret = usb_bulk_msg(udev, + usb_sndbulkpipe(udev, port->bulk_out_endpointAddress), + transfer_buffer, 1, &actual_length, 5000); + if (ret || actual_length != 1) { + if (!ret) + ret = -EIO; + dev_err(&port->dev, "failed to change line speed: %d\n", ret); + } + + kfree(transfer_buffer); +} + +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 000000000..3b1f7546f --- /dev/null +++ b/drivers/usb/serial/iuu_phoenix.c @@ -0,0 +1,1205 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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) + * + * And tested with help of WB Electronics + */ +#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 boost; + u32 clk; +}; + +static int iuu_port_probe(struct usb_serial_port *port) +{ + struct iuu_private *priv; + int ret; + + priv = kzalloc_obj(struct iuu_private); + 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 void 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); +} + +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 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); + 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 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); + 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; + char *buf_ptr = port->write_urb->transfer_buffer; + + if (xmas) { + buf_ptr[0] = IUU_SET_LED; + get_random_bytes(buf_ptr + 1, 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); + usb_submit_urb(port->write_urb, GFP_ATOMIC); +} + +static void iuu_led_activity_off(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + char *buf_ptr = port->write_urb->transfer_buffer; + + if (xmas) { + iuu_rxcmd(urb); + return; + } + + 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); + 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; + 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; + struct iuu_private *priv = usb_get_serial_port_data(port); + + if (iuu_led(port, 0xF000, 0, 0, 0xFF) < 0) + return -EIO; + + rxcmd = kmalloc(1, GFP_KERNEL); + if (!rxcmd) + return -ENOMEM; + + rxcmd[0] = IUU_UART_RX; + + 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__); + goto out_free; + } + + status = read_immediate(port, &priv->len, 1); + if (status != IUU_OPERATION_OK) { + dev_dbg(dev, "%s - uart_flush_read error\n", __func__); + goto out_free; + } + + 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__); + goto out_free; + } + } + } + dev_dbg(dev, "%s - uart_flush_read OK!\n", __func__); + iuu_led(port, 0, 0xF000, 0, 0xFF); + +out_free: + kfree(rxcmd); + + 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 (urb->actual_length) { + 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 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 (urb->actual_length == 1) + len = (int) data[0]; + + if (urb->actual_length > 1) { + dev_dbg(&port->dev, "%s - urb->actual_length = %i\n", __func__, + urb->actual_length); + return; + } + /* if len > 0 call readbuf */ + + if (len > 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; + + spin_lock_irqsave(&priv->lock, flags); + + count = min(count, 256 - priv->writelen); + if (count == 0) + goto out; + + /* fill the buffer */ + memcpy(priv->writebuf + priv->writelen, buf, count); + priv->writelen += count; +out: + 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(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; +} + +/* Disables 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(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; + } + + 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; + } + + 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, + const 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.c_cflag = B9600 | CS8 | CSTOPB | CREAD | PARENB | CLOCAL; + 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; + int result; + int baud; + u32 actual; + struct iuu_private *priv = usb_get_serial_port_data(port); + + baud = tty->termios.c_ospeed; + + 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); + + priv->poll = 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); + + iuu_led(port, 0xF000, 0xF000, 0, 0xFF); + iuu_uart_on(port); + if (boost < 100) + boost = 100; + priv->boost = boost; + 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 vcc_mode_show(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 vcc_mode_store(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\n", __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_RW(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 = { + .name = "iuu_phoenix", + }, + .id_table = id_table, + .num_ports = 1, + .num_bulk_in = 1, + .num_bulk_out = 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, 0644); +MODULE_PARM_DESC(xmas, "Xmas colors enabled or not"); + +module_param(boost, int, 0644); +MODULE_PARM_DESC(boost, "Card overclock boost (in percent 100-500)"); + +module_param(clockmode, int, 0644); +MODULE_PARM_DESC(clockmode, "Card clock mode (1=3.579 MHz, 2=3.680 MHz, " + "3=6 Mhz)"); + +module_param(cdmode, int, 0644); +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, 0644); +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 000000000..87992b24d --- /dev/null +++ b/drivers/usb/serial/iuu_phoenix.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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 ) + * + * 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 000000000..4d3746c7a --- /dev/null +++ b/drivers/usb/serial/keyspan.c @@ -0,0 +1,3105 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + Keyspan USB to Serial Converter driver + + (C) Copyright (C) 2000-2001 Hugh Blemings + (C) Copyright (C) 2002 Greg Kroah-Hartman + + 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 + +#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; + + /* FIXME: return errors */ + keyspan_send_setup(port, 0); + + return 0; +} + + +static void keyspan_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const 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->port_number; + + /* 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 - calculate 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 - %d chars, flip=%d\n", __func__, 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 %x 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 %d on endpoint %x\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: %d\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; + + /* 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); + if (!p_priv) + goto resubmit; + + /* 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: + /* 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); + + if (urb != p_priv->in_urbs[p_priv->in_flip]) + return; + + do { + if (status) { + dev_dbg(&urb->dev->dev, "%s - nonzero status %d on endpoint %x\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: %d\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; + } + + 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); + if (!p_priv) + goto resubmit; + + /* 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: + /* 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) + continue; + + 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: %d\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; + } + + 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); + if (!p_priv) + goto resubmit; + + /* 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: + /* 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 %d on endpoint %x\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: %d\n", + __func__, status); + return; + } + + /* inbound data is in the form P#, len, status, data */ + i = 0; + len = 0; + + while (i < urb->actual_length) { + if (urb->actual_length - i < 3) { + dev_warn_ratelimited(&urb->dev->dev, "malformed indat packet\n"); + break; + } + + /* 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 %d on endpoint %x\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: %d\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); + if (!p_priv) + goto resubmit; + + /* 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: + /* 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: %d\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); + if (!p_priv) + goto resubmit; + + /* 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: + /* 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) + continue; + + 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 unsigned 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; + unsigned 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->port_number; + 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 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; + + usb_kill_urb(p_priv->inack_urb); + for (i = 0; i < 2; i++) { + usb_kill_urb(p_priv->in_urbs[i]); + usb_kill_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 %x\n", + __func__, endpoint); + urb = usb_alloc_urb(0, GFP_KERNEL); /* No ISO */ + if (!urb) + return NULL; + + if (endpoint == 0) { + /* control EP filled in when used */ + return urb; + } + + ep_desc = find_ep(serial, endpoint); + if (!ep_desc) { + usb_free_urb(urb); + return NULL; + } + 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->port_number; + + this_urb = p_priv->outcont_urb; + + /* 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; + } + + dev_dbg(&port->dev, "%s - endpoint %x\n", + __func__, usb_pipeendpoint(this_urb->pipe)); + + /* 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->port_number; + + /* 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__); + + 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->port_number; + + /* Make sure we have an urb then send the message */ + if (this_urb == NULL) { + dev_dbg(&port->dev, "%s - oops no urb for port.\n", __func__); + return -1; + } + + dev_dbg(&port->dev, "%s - endpoint %x (%d)\n", + __func__, usb_pipeendpoint(this_urb->pipe), 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 = 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); + + 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->port_number; + + /* Make sure we have an urb then send the message */ + if (this_urb == NULL) { + dev_dbg(&port->dev, "%s - oops no urb for port.\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_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_obj(struct keyspan_serial_private); + if (!s_priv) + return -ENOMEM; + + s_priv->instat_buf = kzalloc(INSTAT_BUFLEN, GFP_KERNEL); + if (!s_priv->instat_buf) + goto err_instat_buf; + + s_priv->indat_buf = kzalloc(INDAT49W_BUFLEN, GFP_KERNEL); + if (!s_priv->indat_buf) + goto err_indat_buf; + + s_priv->glocont_buf = kzalloc(GLOCONT_BUFLEN, GFP_KERNEL); + if (!s_priv->glocont_buf) + goto err_glocont_buf; + + s_priv->ctrl_buf = kzalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); + if (!s_priv->ctrl_buf) + goto err_ctrl_buf; + + 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; + +err_ctrl_buf: + kfree(s_priv->glocont_buf); +err_glocont_buf: + kfree(s_priv->indat_buf); +err_indat_buf: + kfree(s_priv->instat_buf); +err_instat_buf: + kfree(s_priv); + + return -ENOMEM; +} + +static void keyspan_disconnect(struct usb_serial *serial) +{ + struct keyspan_serial_private *s_priv; + + s_priv = usb_get_serial_data(serial); + + usb_kill_urb(s_priv->instat_urb); + usb_kill_urb(s_priv->glocont_urb); + usb_kill_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); + + /* Make sure to unlink the URBs submitted in attach. */ + usb_kill_urb(s_priv->instat_urb); + usb_kill_urb(s_priv->indat_urb); + + usb_free_urb(s_priv->instat_urb); + usb_free_urb(s_priv->indat_urb); + usb_free_urb(s_priv->glocont_urb); + + kfree(s_priv->ctrl_buf); + kfree(s_priv->glocont_buf); + kfree(s_priv->indat_buf); + kfree(s_priv->instat_buf); + + 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_obj(*p_priv); + if (!p_priv) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(p_priv->in_buffer); ++i) { + p_priv->in_buffer[i] = kzalloc(IN_BUFLEN, GFP_KERNEL); + if (!p_priv->in_buffer[i]) + goto err_free_in_buffer; + } + + for (i = 0; i < ARRAY_SIZE(p_priv->out_buffer); ++i) { + p_priv->out_buffer[i] = kzalloc(OUT_BUFLEN, GFP_KERNEL); + if (!p_priv->out_buffer[i]) + goto err_free_out_buffer; + } + + p_priv->inack_buffer = kzalloc(INACK_BUFLEN, GFP_KERNEL); + if (!p_priv->inack_buffer) + goto err_free_out_buffer; + + p_priv->outcont_buffer = kzalloc(OUTCONT_BUFLEN, GFP_KERNEL); + if (!p_priv->outcont_buffer) + goto err_free_inack_buffer; + + p_priv->device_details = d_details; + + /* Setup values for the various callback routines */ + cback = &keyspan_callbacks[d_details->msg_format]; + + port_num = port->port_number; + + /* 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], + IN_BUFLEN, + 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], + OUT_BUFLEN, + 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, + INACK_BUFLEN, + 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, + OUTCONT_BUFLEN, + cback->outcont_callback); + + usb_set_serial_port_data(port, p_priv); + + return 0; + +err_free_inack_buffer: + kfree(p_priv->inack_buffer); +err_free_out_buffer: + for (i = 0; i < ARRAY_SIZE(p_priv->out_buffer); ++i) + kfree(p_priv->out_buffer[i]); +err_free_in_buffer: + for (i = 0; i < ARRAY_SIZE(p_priv->in_buffer); ++i) + kfree(p_priv->in_buffer[i]); + kfree(p_priv); + + return -ENOMEM; +} + +static void keyspan_port_remove(struct usb_serial_port *port) +{ + struct keyspan_port_private *p_priv; + int i; + + p_priv = usb_get_serial_port_data(port); + + usb_kill_urb(p_priv->inack_urb); + usb_kill_urb(p_priv->outcont_urb); + for (i = 0; i < 2; i++) { + usb_kill_urb(p_priv->in_urbs[i]); + usb_kill_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->outcont_buffer); + kfree(p_priv->inack_buffer); + for (i = 0; i < ARRAY_SIZE(p_priv->out_buffer); ++i) + kfree(p_priv->out_buffer[i]); + for (i = 0; i < ARRAY_SIZE(p_priv->in_buffer); ++i) + kfree(p_priv->in_buffer[i]); + + kfree(p_priv); +} + +/* Structs for the devices, pre and post renumeration. */ +static struct usb_serial_driver keyspan_pre_device = { + .driver = { + .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 = { + .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 = { + .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 = { + .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 +}; + +module_usb_serial_driver(serial_drivers, keyspan_ids_combined); + +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_pda.c b/drivers/usb/serial/keyspan_pda.c new file mode 100644 index 000000000..4ee0bdf98 --- /dev/null +++ b/drivers/usb/serial/keyspan_pda.c @@ -0,0 +1,745 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * USB Keyspan PDA / Xircom / Entrega Converter driver + * + * Copyright (C) 1999 - 2001 Greg Kroah-Hartman + * Copyright (C) 1999, 2000 Brian Warner + * Copyright (C) 2000 Al Borchers + * Copyright (C) 2020 Johan Hovold + * + * See Documentation/usb/usb-serial.rst for more information on using this + * driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_AUTHOR "Brian Warner , Johan Hovold " +#define DRIVER_DESC "USB Keyspan PDA Converter driver" + +#define KEYSPAN_TX_THRESHOLD 128 + +struct keyspan_pda_private { + int tx_room; + struct work_struct unthrottle_work; + struct usb_serial_port *port; + bool throttled; + bool throttle_req; +}; + +static int keyspan_pda_write_start(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 Entrega version of the same device */ +#define XIRCOM_VENDOR_ID 0x085a +#define XIRCOM_FAKE_ID 0x8027 +#define XIRCOM_FAKE_ID_2 0x8025 /* "PGMFHUB" serial */ +#define ENTREGA_VENDOR_ID 0x1645 +#define ENTREGA_FAKE_ID 0x8093 + +static const struct usb_device_id id_table_combined[] = { + { USB_DEVICE(KEYSPAN_VENDOR_ID, KEYSPAN_PDA_FAKE_ID) }, + { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID) }, + { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID_2) }, + { USB_DEVICE(ENTREGA_VENDOR_ID, ENTREGA_FAKE_ID) }, + { 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 */ +}; + +static const struct usb_device_id id_table_fake[] = { + { USB_DEVICE(KEYSPAN_VENDOR_ID, KEYSPAN_PDA_FAKE_ID) }, + { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID) }, + { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID_2) }, + { USB_DEVICE(ENTREGA_VENDOR_ID, ENTREGA_FAKE_ID) }, + { } /* Terminating entry */ +}; + +static int keyspan_pda_get_write_room(struct keyspan_pda_private *priv) +{ + struct usb_serial_port *port = priv->port; + struct usb_serial *serial = port->serial; + u8 room; + int rc; + + rc = usb_control_msg_recv(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, + GFP_KERNEL); + if (rc) { + dev_dbg(&port->dev, "roomquery failed: %d\n", rc); + return rc; + } + + dev_dbg(&port->dev, "roomquery says %d\n", room); + + return room; +} + +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_port *port = priv->port; + struct usb_serial *serial = port->serial; + unsigned long flags; + int result; + + dev_dbg(&port->dev, "%s\n", __func__); + + /* + * 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, + KEYSPAN_TX_THRESHOLD, + 0, /* index */ + NULL, + 0, + 2000); + if (result < 0) + dev_dbg(&serial->dev->dev, "%s - error %d from usb_control_msg\n", + __func__, result); + /* + * Need to check available space after requesting notification in case + * buffer is already empty so that no notification is sent. + */ + result = keyspan_pda_get_write_room(priv); + if (result > KEYSPAN_TX_THRESHOLD) { + spin_lock_irqsave(&port->lock, flags); + priv->tx_room = max(priv->tx_room, result); + spin_unlock_irqrestore(&port->lock, flags); + + usb_serial_port_softint(port); + } +} + +static void keyspan_pda_rx_interrupt(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned char *data = urb->transfer_buffer; + unsigned int len = urb->actual_length; + int retval; + int status = urb->status; + struct keyspan_pda_private *priv; + bool throttled = false; + unsigned long flags; + + 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; + } + + if (len < 1) { + dev_warn(&port->dev, "short message received\n"); + goto exit; + } + + /* see if the message is data or a status interrupt */ + switch (data[0]) { + case 0: + /* rest of message is rx data */ + if (len < 2) + break; + tty_insert_flip_string(&port->port, data + 1, len - 1); + tty_flip_buffer_push(&port->port); + break; + case 1: + /* status interrupt */ + if (len < 2) { + dev_warn(&port->dev, "short interrupt message received\n"); + break; + } + dev_dbg(&port->dev, "rx int, d1=%d\n", data[1]); + switch (data[1]) { + case 1: /* modemline change */ + break; + case 2: /* tx unthrottle interrupt */ + spin_lock_irqsave(&port->lock, flags); + priv->tx_room = max(priv->tx_room, KEYSPAN_TX_THRESHOLD); + spin_unlock_irqrestore(&port->lock, flags); + + keyspan_pda_write_start(port); + + usb_serial_port_softint(port); + break; + default: + break; + } + break; + default: + break; + } + +exit: + spin_lock_irqsave(&port->lock, flags); + if (priv->throttle_req) { + priv->throttled = true; + throttled = true; + } + spin_unlock_irqrestore(&port->lock, flags); + + if (!throttled) { + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&port->dev, "failed to resubmit in urb: %d\n", retval); + } +} + +static void keyspan_pda_rx_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct keyspan_pda_private *priv = usb_get_serial_port_data(port); + + /* + * 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. + */ + spin_lock_irq(&port->lock); + priv->throttle_req = true; + spin_unlock_irq(&port->lock); +} + +static void keyspan_pda_rx_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct keyspan_pda_private *priv = usb_get_serial_port_data(port); + bool throttled; + int ret; + + spin_lock_irq(&port->lock); + throttled = priv->throttled; + priv->throttled = false; + priv->throttle_req = false; + spin_unlock_irq(&port->lock); + + if (throttled) { + ret = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (ret) + dev_err(&port->dev, "failed to submit in urb: %d\n", ret); + } +} + +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; + } + + 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 int 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); + return result; + } + + return 0; +} + +static void keyspan_pda_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const 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; + + rc = usb_control_msg_recv(serial->dev, 0, + 3, /* get pins */ + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_IN, + 0, + 0, + &data, + 1, + 2000, + GFP_KERNEL); + if (rc == 0) + *value = 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 & BIT(7)) ? TIOCM_DTR : 0) | + ((status & BIT(6)) ? TIOCM_CAR : 0) | + ((status & BIT(5)) ? TIOCM_RNG : 0) | + ((status & BIT(4)) ? TIOCM_DSR : 0) | + ((status & BIT(3)) ? TIOCM_CTS : 0) | + ((status & BIT(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 |= BIT(2); + if (set & TIOCM_DTR) + status |= BIT(7); + + if (clear & TIOCM_RTS) + status &= ~BIT(2); + if (clear & TIOCM_DTR) + status &= ~BIT(7); + rc = keyspan_pda_set_modem_info(serial, status); + return rc; +} + +static int keyspan_pda_write_start(struct usb_serial_port *port) +{ + struct keyspan_pda_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + struct urb *urb; + int count; + int room; + int rc; + + /* + * Guess how much room is left in the device's ring buffer. 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 but also + * query how much room is currently available (in case our guess was + * too conservative and the buffer is already empty when the + * unthrottle work is scheduled). + */ + + /* + * 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_irqsave(&port->lock, flags); + + room = priv->tx_room; + count = kfifo_len(&port->write_fifo); + + if (!test_bit(0, &port->write_urbs_free) || count == 0 || room == 0) { + spin_unlock_irqrestore(&port->lock, flags); + return 0; + } + __clear_bit(0, &port->write_urbs_free); + + if (count > room) + count = room; + if (count > port->bulk_out_size) + count = port->bulk_out_size; + + urb = port->write_urb; + count = kfifo_out(&port->write_fifo, urb->transfer_buffer, count); + urb->transfer_buffer_length = count; + + port->tx_bytes += count; + priv->tx_room -= count; + + spin_unlock_irqrestore(&port->lock, flags); + + dev_dbg(&port->dev, "%s - count = %d, txroom = %d\n", __func__, count, room); + + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (rc) { + dev_dbg(&port->dev, "usb_submit_urb(write bulk) failed\n"); + + spin_lock_irqsave(&port->lock, flags); + port->tx_bytes -= count; + priv->tx_room = max(priv->tx_room, room + count); + __set_bit(0, &port->write_urbs_free); + spin_unlock_irqrestore(&port->lock, flags); + + return rc; + } + + if (count == room) + schedule_work(&priv->unthrottle_work); + + return 0; +} + +static void keyspan_pda_write_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + port->tx_bytes -= urb->transfer_buffer_length; + __set_bit(0, &port->write_urbs_free); + spin_unlock_irqrestore(&port->lock, flags); + + keyspan_pda_write_start(port); + + usb_serial_port_softint(port); +} + +static int keyspan_pda_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + int rc; + + dev_dbg(&port->dev, "%s - count = %d\n", __func__, count); + + if (!count) + return 0; + + count = kfifo_in_locked(&port->write_fifo, buf, count, &port->lock); + + rc = keyspan_pda_write_start(port); + if (rc) + return rc; + + return count; +} + +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, BIT(7) | BIT(2)); + else + keyspan_pda_set_modem_info(serial, 0); +} + + +static int keyspan_pda_open(struct tty_struct *tty, + struct usb_serial_port *port) +{ + struct keyspan_pda_private *priv = usb_get_serial_port_data(port); + int rc; + + /* find out how much room is in the Tx ring */ + rc = keyspan_pda_get_write_room(priv); + if (rc < 0) + return rc; + + spin_lock_irq(&port->lock); + priv->tx_room = rc; + priv->throttled = false; + priv->throttle_req = false; + spin_unlock_irq(&port->lock); + + 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__); + return rc; + } + + return 0; +} + +static void keyspan_pda_close(struct usb_serial_port *port) +{ + struct keyspan_pda_private *priv = usb_get_serial_port_data(port); + + /* + * Stop the interrupt URB first as its completion handler may submit + * the write URB. + */ + usb_kill_urb(port->interrupt_in_urb); + usb_kill_urb(port->write_urb); + + cancel_work_sync(&priv->unthrottle_work); + + spin_lock_irq(&port->lock); + kfifo_reset(&port->write_fifo); + spin_unlock_irq(&port->lock); +} + +/* download the firmware to a "fake" device (pre-renumeration) */ +static int keyspan_pda_fake_startup(struct usb_serial *serial) +{ + unsigned int vid = le16_to_cpu(serial->dev->descriptor.idVendor); + const char *fw_name; + + /* download the firmware here ... */ + ezusb_fx1_set_reset(serial->dev, 1); + + switch (vid) { + case KEYSPAN_VENDOR_ID: + fw_name = "keyspan_pda/keyspan_pda.fw"; + break; + case XIRCOM_VENDOR_ID: + case ENTREGA_VENDOR_ID: + fw_name = "keyspan_pda/xircom_pgs.fw"; + break; + default: + 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; +} + +MODULE_FIRMWARE("keyspan_pda/keyspan_pda.fw"); +MODULE_FIRMWARE("keyspan_pda/xircom_pgs.fw"); + +static int keyspan_pda_port_probe(struct usb_serial_port *port) +{ + + struct keyspan_pda_private *priv; + + priv = kmalloc_obj(struct keyspan_pda_private); + if (!priv) + return -ENOMEM; + + INIT_WORK(&priv->unthrottle_work, keyspan_pda_request_unthrottle); + priv->port = port; + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static void keyspan_pda_port_remove(struct usb_serial_port *port) +{ + struct keyspan_pda_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); +} + +static struct usb_serial_driver keyspan_pda_fake_device = { + .driver = { + .name = "keyspan_pda_pre", + }, + .description = "Keyspan PDA - (prerenumeration)", + .id_table = id_table_fake, + .num_ports = 1, + .attach = keyspan_pda_fake_startup, +}; + +static struct usb_serial_driver keyspan_pda_device = { + .driver = { + .name = "keyspan_pda", + }, + .description = "Keyspan PDA", + .id_table = id_table_std, + .num_ports = 1, + .num_bulk_out = 1, + .num_interrupt_in = 1, + .dtr_rts = keyspan_pda_dtr_rts, + .open = keyspan_pda_open, + .close = keyspan_pda_close, + .write = keyspan_pda_write, + .write_bulk_callback = keyspan_pda_write_bulk_callback, + .read_int_callback = keyspan_pda_rx_interrupt, + .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, + &keyspan_pda_fake_device, + 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 000000000..a68f1fb25 --- /dev/null +++ b/drivers/usb/serial/keyspan_usa26msg.h @@ -0,0 +1,261 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + 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 + indicates 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 000000000..a19f3fe5d --- /dev/null +++ b/drivers/usb/serial/keyspan_usa28msg.h @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + 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 000000000..8c3970fdd --- /dev/null +++ b/drivers/usb/serial/keyspan_usa49msg.h @@ -0,0 +1,283 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + 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 000000000..dcf502fdb --- /dev/null +++ b/drivers/usb/serial/keyspan_usa67msg.h @@ -0,0 +1,255 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + 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 000000000..c4ca0f631 --- /dev/null +++ b/drivers/usb/serial/keyspan_usa90msg.h @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + 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 000000000..b74c88fec --- /dev/null +++ b/drivers/usb/serial/kl5kusb105.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * KLSI KL5KUSB105 chip RS232 converter driver + * + * Copyright (C) 2010 Johan Hovold + * Copyright (C) 2001 Utz-Uwe Haus + * + * 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 "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 void 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, + const struct ktermios *old_termios); +static int klsi_105_tiocmget(struct tty_struct *tty); +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) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver kl5kusb105d_device = { + .driver = { + .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, + .tiocmget = klsi_105_tiocmget, + .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; +}; + +struct klsi_105_private { + struct klsi_105_port_settings cfg; + 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_send(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, + GFP_KERNEL); + if (rc) + dev_err(&port->dev, + "Change port settings failed (error = %d)\n", rc); + + dev_dbg(&port->dev, + "pktlen %u, baudrate 0x%02x, databits %u, u1 %u, u2 %u\n", + settings->pktlen, settings->baudrate, settings->databits, + settings->unknown1, settings->unknown2); + + return rc; +} + +/* + * Read line control via vendor command and return result through + * the state pointer. + */ +static int klsi_105_get_line_state(struct usb_serial_port *port, + unsigned long *state) +{ + u16 status; + int rc; + + rc = usb_control_msg_recv(port->serial->dev, 0, + KL5KUSB105A_SIO_POLL, + USB_TYPE_VENDOR | USB_DIR_IN, + 0, /* value */ + 0, /* index */ + &status, sizeof(status), + 10000, + GFP_KERNEL); + if (rc) { + dev_err(&port->dev, "reading line status failed: %d\n", rc); + return rc; + } + + le16_to_cpus(&status); + + dev_dbg(&port->dev, "read status %04x\n", status); + + *state = ((status & KL5KUSB105A_DSR) ? TIOCM_DSR : 0) | + ((status & KL5KUSB105A_CTS) ? TIOCM_CTS : 0); + + return 0; +} + + +/* + * Driver's tty interface functions + */ + +static int klsi_105_port_probe(struct usb_serial_port *port) +{ + struct klsi_105_private *priv; + + priv = kmalloc_obj(*priv); + 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); + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static void klsi_105_port_remove(struct usb_serial_port *port) +{ + struct klsi_105_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); +} + +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; + 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.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); + + spin_lock_irqsave(&priv->lock, flags); + 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) + return rc; + + 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; + goto err_generic_close; + } else + dev_dbg(&port->dev, "%s - enabled reading\n", __func__); + + rc = klsi_105_get_line_state(port, &line_state); + if (rc < 0) { + retval = rc; + goto err_disable_read; + } + + 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 0; + +err_disable_read: + 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); +err_generic_close: + usb_serial_generic_close(port); + + 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 - KLSI_HDR_LEN, &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, + const 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_obj(*cfg); + if (!cfg) + return; + + /* lock while we are modifying the settings */ + spin_lock_irqsave(&priv->lock, flags); + + /* + * Update baud rate + */ + baud = tty_get_baud_rate(tty); + + 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, "unsupported baudrate, using 9600\n"); + priv->cfg.baudrate = kl5kusb105a_sio_b9600; + baud = 9600; + break; + } + + /* + * FIXME: implement B0 handling + * + * Maybe this should be simulated by sending read disable and read + * enable messages? + */ + + 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); + } + /* + * 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; + } + 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); +} + +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; +} + +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 000000000..dbe98d85c --- /dev/null +++ b/drivers/usb/serial/kl5kusb105.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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 + +/* 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 000000000..4a622cf8e --- /dev/null +++ b/drivers/usb/serial/kobil_sct.c @@ -0,0 +1,511 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * 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 "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 void 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 unsigned 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, + const 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 = { + .name = "kobil", + }, + .description = "KOBIL USB smart card terminal", + .id_table = id_table, + .num_ports = 1, + .num_interrupt_out = 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_ctrl_send(struct usb_serial_port *port, u8 req, u16 val) +{ + return usb_control_msg(port->serial->dev, + usb_sndctrlpipe(port->serial->dev, 0), + req, USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, + val, 0, NULL, 0, KOBIL_TIMEOUT); +} + +static int kobil_ctrl_recv(struct usb_serial_port *port, u8 req, u16 val, void *buf, u16 size) +{ + return usb_control_msg(port->serial->dev, + usb_rcvctrlpipe(port->serial->dev, 0), + req, USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_IN, + val, 0, buf, size, KOBIL_TIMEOUT); +} + +static int kobil_port_probe(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct kobil_private *priv; + + priv = kmalloc_obj(struct kobil_private); + 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 void kobil_port_remove(struct usb_serial_port *port) +{ + struct kobil_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); +} + +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 = 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; + struct kobil_private *priv; + unsigned char *transfer_buffer; + int transfer_buffer_length = 8; + int result; + + 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 = kobil_ctrl_recv(port, SUSBCRequest_GetMisc, SUSBCR_MSC_GetHWVersion, + transfer_buffer, transfer_buffer_length); + dev_dbg(dev, "%s - Send get_HW_version URB returns: %i\n", __func__, result); + if (result >= 3) { + dev_dbg(dev, "Hardware version: %i.%i.%i\n", transfer_buffer[0], + transfer_buffer[1], transfer_buffer[2]); + } + + /* get firmware version */ + result = kobil_ctrl_recv(port, SUSBCRequest_GetMisc, SUSBCR_MSC_GetFWVersion, + transfer_buffer, transfer_buffer_length); + dev_dbg(dev, "%s - Send get_FW_version URB returns: %i\n", __func__, result); + if (result >= 3) { + 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 = kobil_ctrl_send(port, SUSBCRequest_SetBaudRateParityAndStopBits, + SUSBCR_SBR_9600 | SUSBCR_SPASB_EvenParity | SUSBCR_SPASB_1StopBit); + dev_dbg(dev, "%s - Send set_baudrate URB returns: %i\n", __func__, result); + + /* reset all queues */ + result = kobil_ctrl_send(port, SUSBCRequest_Misc, SUSBCR_MSC_ResetAllQueues); + 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_KERNEL); + 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) +{ + struct kobil_private *priv; + int length, todo, result; + + 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_ATOMIC); + 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_ATOMIC); + dev_dbg(&port->dev, "%s - Send read URB returns: %i\n", __func__, result); + } + } + return count; +} + + +static unsigned 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 = kobil_ctrl_recv(port, SUSBCRequest_GetStatusLineState, 0, + transfer_buffer, transfer_buffer_length); + dev_dbg(&port->dev, "Send get_status_line_state URB returns: %i\n", + result); + if (result < 1) { + if (result >= 0) + result = -EIO; + goto out_free; + } + + dev_dbg(&port->dev, "Statusline: %02x\n", transfer_buffer[0]); + + result = 0; + if ((transfer_buffer[0] & SUSBCR_GSL_DSR) != 0) + result = TIOCM_DSR; +out_free: + 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 dtr, rts; + int result; + u16 val = 0; + + 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; + } + + dtr = (set | clear) & TIOCM_DTR; + rts = (set | clear) & TIOCM_RTS; + + if (dtr && priv->device_type == KOBIL_ADAPTER_B_PRODUCT_ID) { + if (set & TIOCM_DTR) + val = SUSBCR_SSL_SETDTR; + else + val = SUSBCR_SSL_CLRDTR; + } else if (rts) { + if (set & TIOCM_RTS) + val = SUSBCR_SSL_SETRTS; + else + val = SUSBCR_SSL_CLRRTS; + } + + if (val) { + result = kobil_ctrl_send(port, SUSBCRequest_SetStatusLinesOrQueues, val); + if (result < 0) { + dev_err(dev, "failed to set status lines: %d\n", result); + return result; + } + } + + return 0; +} + +static void kobil_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old) +{ + struct kobil_private *priv; + int result; + int c_cflag = tty->termios.c_cflag; + speed_t speed; + u16 val; + + 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: + val = SUSBCR_SBR_1200; + break; + default: + speed = 9600; + fallthrough; + case 9600: + val = SUSBCR_SBR_9600; + break; + } + + if (c_cflag & CSTOPB) + val |= SUSBCR_SPASB_2StopBits; + else + val |= SUSBCR_SPASB_1StopBit; + + if (c_cflag & PARENB) { + if (c_cflag & PARODD) + val |= SUSBCR_SPASB_OddParity; + else + val |= SUSBCR_SPASB_EvenParity; + } else { + val |= SUSBCR_SPASB_NoParity; + } + + tty->termios.c_cflag &= ~CMSPAR; + tty_encode_baud_rate(tty, speed, speed); + + result = kobil_ctrl_send(port, SUSBCRequest_SetBaudRateParityAndStopBits, val); + if (result) { + dev_err(&port->dev, "failed to update line settings: %d\n", + result); + } +} + +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); + 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: + result = kobil_ctrl_send(port, SUSBCRequest_Misc, SUSBCR_MSC_ResetAllQueues); + dev_dbg(&port->dev, + "%s - Send reset_all_queues (FLUSH) URB returns: %i\n", + __func__, result); + 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 000000000..030c1b426 --- /dev/null +++ b/drivers/usb/serial/kobil_sct.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#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 000000000..a6de18917 --- /dev/null +++ b/drivers/usb/serial/mct_u232.c @@ -0,0 +1,780 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MCT (Magic Control Technology Corp.) USB RS232 Converter Driver + * + * Copyright (C) 2000 Wolfgang Grandegger (wolfgang@ces.ch) + * + * 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 "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 void 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, + const struct ktermios *old_termios); +static int 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 = { + .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(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 < MCT_U232_GET_MODEM_STAT_SIZE) { + dev_err(&port->dev, "Get MODEM STATus failed (error = %d)\n", rc); + + if (rc >= 0) + rc = -EIO; + + *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 usb_serial *serial = port->serial; + struct mct_u232_private *priv; + u16 pid; + + /* check first to simplify error handling */ + if (!serial->port[1] || !serial->port[1]->interrupt_in_urb) { + dev_err(&port->dev, "expected endpoint missing\n"); + return -ENODEV; + } + + /* + * 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...) + */ + pid = le16_to_cpu(serial->dev->descriptor.idProduct); + if (pid == MCT_U232_SITECOM_PID) + port->bulk_out_size = min(16, port->bulk_out_size); + + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; + + /* Use second interrupt-in endpoint for reading. */ + priv->read_urb = 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 void mct_u232_port_remove(struct usb_serial_port *port) +{ + struct mct_u232_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); +} + +static int mct_u232_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + 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; + + /* 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 && C_BAUD(tty)) + 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; + } + + if (urb->actual_length < 2) { + dev_warn_ratelimited(&port->dev, "short interrupt-in packet\n"); + 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, + const 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 int 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); + + return 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 000000000..e3d09a83c --- /dev/null +++ b/drivers/usb/serial/mct_u232.h @@ -0,0 +1,463 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Definitions for MCT (Magic Control Technology) USB-RS232 Converter Driver + * + * Copyright (C) 2000 Wolfgang Grandegger (wolfgang@ces.ch) + * + * 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 000000000..22c5f071f --- /dev/null +++ b/drivers/usb/serial/metro-usb.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + 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 + +#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; + int throttle_req; + unsigned long control_state; +}; + +/* Device table list. */ +static const struct usb_device_id id_table[] = { + { USB_DEVICE(FOCUS_VENDOR_ID, FOCUS_PRODUCT_ID_BI) }, + { USB_DEVICE(FOCUS_VENDOR_ID, FOCUS_PRODUCT_ID_UNI) }, + { USB_DEVICE_INTERFACE_CLASS(0x0c2e, 0x0730, 0xff) }, /* MS7820 */ + { }, /* 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 + +static int metrousb_is_unidirectional_mode(struct usb_serial *serial) +{ + u16 product_id = le16_to_cpu(serial->dev->descriptor.idProduct); + + return product_id == FOCUS_PRODUCT_ID_UNI; +} + +static int metrousb_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + if (metrousb_is_unidirectional_mode(serial)) { + if (epds->num_interrupt_out == 0) { + dev_err(&serial->interface->dev, "interrupt-out endpoint missing\n"); + return -ENODEV; + } + } + + return 1; +} + +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->serial)) + 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; + unsigned long flags; + int throttled = 0; + int result; + + 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); + if (metro_priv->throttle_req) { + metro_priv->throttled = 1; + throttled = 1; + } + spin_unlock_irqrestore(&metro_priv->lock, flags); + + if (throttled) + 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_cleanup(struct usb_serial_port *port) +{ + 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; + int result; + + /* Set the private data information for the port. */ + spin_lock_irqsave(&metro_priv->lock, flags); + metro_priv->control_state = 0; + metro_priv->throttled = 0; + metro_priv->throttle_req = 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); + return result; + } + + /* 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, error code=%d\n", + __func__, result); + goto err_kill_urb; + } + + return 0; + +err_kill_urb: + usb_kill_urb(port->interrupt_in_urb); + + 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_obj(*metro_priv); + if (!metro_priv) + return -ENOMEM; + + spin_lock_init(&metro_priv->lock); + + usb_set_serial_port_data(port, metro_priv); + + return 0; +} + +static void metrousb_port_remove(struct usb_serial_port *port) +{ + struct metrousb_private *metro_priv; + + metro_priv = usb_get_serial_port_data(port); + kfree(metro_priv); +} + +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; + + /* Set the private information for the port to stop reading data. */ + spin_lock_irqsave(&metro_priv->lock, flags); + metro_priv->throttle_req = 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; + + 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; + unsigned long control_state = 0; + + dev_dbg(&port->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; + int throttled; + int result; + + /* Set the private information for the port to resume reading data. */ + spin_lock_irqsave(&metro_priv->lock, flags); + throttled = metro_priv->throttled; + metro_priv->throttled = 0; + metro_priv->throttle_req = 0; + spin_unlock_irqrestore(&metro_priv->lock, flags); + + if (throttled) { + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) { + dev_err(&port->dev, "failed to submit interrupt in urb: %d\n", + result); + } + } +} + +static struct usb_serial_driver metrousb_device = { + .driver = { + .name = "metro-usb", + }, + .description = "Metrologic USB to Serial", + .id_table = id_table, + .num_interrupt_in = 1, + .calc_num_ports = metrousb_calc_num_ports, + .open = metrousb_open, + .close = metrousb_cleanup, + .read_int_callback = metrousb_read_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 v2"); +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 000000000..104f43a8f --- /dev/null +++ b/drivers/usb/serial/mos7720.c @@ -0,0 +1,1764 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mos7720.c + * Controls the Moschip 7720 usb to dual port serial converter + * + * Copyright 2006 Moschip Semiconductor Tech. Ltd. + * + * 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 + +#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 Routines 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]; +}; + +#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 */ + +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 */ + bool msg_pending; /* usb sync call pending */ + struct completion syncmsg_compl; /* usb sync call completed */ + struct work_struct work; /* restore deferred writes */ + 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 { + MOS7720_THR, /* serial port regs */ + MOS7720_RHR, + MOS7720_IER, + MOS7720_FCR, + MOS7720_ISR, + MOS7720_LCR, + MOS7720_MCR, + MOS7720_LSR, + MOS7720_MSR, + MOS7720_SPR, + MOS7720_DLL, + MOS7720_DLM, + MOS7720_DPR, /* parallel port regs */ + MOS7720_DSR, + MOS7720_DCR, + MOS7720_ECR, + MOS7720_SP1_REG, /* device control regs */ + MOS7720_SP2_REG, /* serial port 2 (7720 only) */ + MOS7720_PP_REG, + MOS7720_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, /* MOS7720_THR */ + 0x00, /* MOS7720_RHR */ + 0x01, /* MOS7720_IER */ + 0x02, /* MOS7720_FCR */ + 0x02, /* MOS7720_ISR */ + 0x03, /* MOS7720_LCR */ + 0x04, /* MOS7720_MCR */ + 0x05, /* MOS7720_LSR */ + 0x06, /* MOS7720_MSR */ + 0x07, /* MOS7720_SPR */ + 0x00, /* MOS7720_DLL */ + 0x01, /* MOS7720_DLM */ + 0x00, /* MOS7720_DPR */ + 0x01, /* MOS7720_DSR */ + 0x02, /* MOS7720_DCR */ + 0x0a, /* MOS7720_ECR */ + 0x01, /* MOS7720_SP1_REG */ + 0x02, /* MOS7720_SP2_REG (7720 only) */ + 0x04, /* MOS7720_PP_REG (7715 only) */ + 0x08, /* MOS7720_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 >= MOS7720_SP1_REG) /* control reg */ + return 0x0000; + + else if (reg >= MOS7720_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\n", 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) { + *data = 0; + return -ENOMEM; + } + + status = usb_control_msg(usbdev, pipe, request, requesttype, value, + index, buf, 1, MOS_WDR_TIMEOUT); + if (status == 1) { + *data = *buf; + } else { + dev_err(&usbdev->dev, + "mos7720: usb_control_msg() failed: %d\n", status); + if (status >= 0) + status = -EIO; + *data = 0; + } + + 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, MOS7720_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); +} + +/* + * This is 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 message 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 */ + reinit_completion(&mos_parport->syncmsg_compl); + spin_unlock(&release_lock); + + /* ensure writes from restore are submitted before new requests */ + if (work_pending(&mos_parport->work)) + flush_work(&mos_parport->work); + + 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 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 deferred_restore_writes(struct work_struct *work) +{ + struct mos7715_parport *mos_parport; + + mos_parport = container_of(work, struct mos7715_parport, work); + + mutex_lock(&mos_parport->serial->disc_mutex); + + /* if device disconnected, game over */ + if (mos_parport->serial->disconnected) + goto done; + + write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR, + mos_parport->shadowDCR); + write_mos_reg(mos_parport->serial, dummy, MOS7720_ECR, + mos_parport->shadowECR); +done: + mutex_unlock(&mos_parport->serial->disc_mutex); +} + +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, MOS7720_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, MOS7720_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, MOS7720_DCR, data); + mos_parport->shadowDCR = data; + parport_epilogue(pp); +} + +static unsigned char parport_mos7715_read_control(struct parport *pp) +{ + struct mos7715_parport *mos_parport; + __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, MOS7720_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; + + 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, MOS7720_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, MOS7720_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; + } + mos_parport->shadowDCR = s->u.pc.ctr; + mos_parport->shadowECR = s->u.pc.ecr; + + schedule_work(&mos_parport->work); + 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\n", 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_obj(struct mos7715_parport); + if (!mos_parport) + return -ENOMEM; + + mos_parport->msg_pending = false; + kref_init(&mos_parport->ref_count); + usb_set_serial_data(serial, mos_parport); /* hijack private pointer */ + mos_parport->serial = serial; + INIT_WORK(&mos_parport->work, deferred_restore_writes); + init_completion(&mos_parport->syncmsg_compl); + + /* cycle parallel port reset bit */ + write_mos_reg(mos_parport->serial, dummy, MOS7720_PP_REG, (__u8)0x80); + write_mos_reg(mos_parport->serial, dummy, MOS7720_PP_REG, (__u8)0x00); + + /* initialize device registers */ + mos_parport->shadowDCR = DCR_INIT_VAL; + write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR, + mos_parport->shadowDCR); + mos_parport->shadowECR = ECR_INIT_VAL; + write_mos_reg(mos_parport->serial, dummy, MOS7720_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"); + 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); +} + +static int mos77xx_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + u16 product = le16_to_cpu(serial->dev->descriptor.idProduct); + + if (product == MOSCHIP_DEVICE_ID_7715) { + /* + * The 7715 uses the first bulk in/out endpoint pair for the + * parallel port, and the second for the serial port. We swap + * the endpoint descriptors here so that the first and + * only registered port structure uses the serial-port + * endpoints. + */ + swap(epds->bulk_in[0], epds->bulk_in[1]); + swap(epds->bulk_out[0], epds->bulk_out[1]); + + 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) + continue; + + urb->transfer_buffer = kmalloc(URB_TRANSFER_BUFFER_SIZE, + GFP_KERNEL); + if (!urb->transfer_buffer) { + 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 : MOS7720_THR/MOS7720_RHR + * 1 : MOS7720_IER + * 2 : MOS7720_FCR + * 3 : MOS7720_LCR + * 4 : MOS7720_MCR + * 5 : MOS7720_LSR + * 6 : MOS7720_MSR + * 7 : MOS7720_SPR + * + * 0x08 : SP1/2 Control Reg + */ + port_number = port->port_number; + read_mos_reg(serial, port_number, MOS7720_LSR, &data); + + dev_dbg(&port->dev, "SS::%p LSR:%x\n", mos7720_port, data); + + write_mos_reg(serial, dummy, MOS7720_SP1_REG, 0x02); + write_mos_reg(serial, dummy, MOS7720_SP2_REG, 0x02); + + write_mos_reg(serial, port_number, MOS7720_IER, 0x00); + write_mos_reg(serial, port_number, MOS7720_FCR, 0x00); + + write_mos_reg(serial, port_number, MOS7720_FCR, 0xcf); + mos7720_port->shadowLCR = 0x03; + write_mos_reg(serial, port_number, MOS7720_LCR, + mos7720_port->shadowLCR); + mos7720_port->shadowMCR = 0x0b; + write_mos_reg(serial, port_number, MOS7720_MCR, + mos7720_port->shadowMCR); + + write_mos_reg(serial, port_number, MOS7720_SP_CONTROL_REG, 0x00); + read_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG, &data); + data = data | (port->port_number + 1); + write_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG, data); + mos7720_port->shadowLCR = 0x83; + write_mos_reg(serial, port_number, MOS7720_LCR, + mos7720_port->shadowLCR); + write_mos_reg(serial, port_number, MOS7720_THR, 0x0c); + write_mos_reg(serial, port_number, MOS7720_IER, 0x00); + mos7720_port->shadowLCR = 0x03; + write_mos_reg(serial, port_number, MOS7720_LCR, + mos7720_port->shadowLCR); + write_mos_reg(serial, port_number, MOS7720_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) + */ +static unsigned int mos7720_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct moschip_port *mos7720_port = usb_get_serial_port_data(port); + int i; + unsigned int chars = 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 %u\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->port_number, MOS7720_MCR, 0x00); + write_mos_reg(serial, port->port_number, MOS7720_IER, 0x00); + + mos7720_port->open = 0; +} + +static int 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 -ENODEV; + + if (break_state == -1) + data = mos7720_port->shadowLCR | UART_LCR_SBC; + else + data = mos7720_port->shadowLCR & ~UART_LCR_SBC; + + mos7720_port->shadowLCR = data; + + return write_mos_reg(serial, port->port_number, MOS7720_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. + */ +static unsigned int mos7720_write_room(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 room = 0; + int i; + + /* 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 %u\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_ATOMIC); + if (!urb->transfer_buffer) { + bytes_sent = -ENOMEM; + 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 (C_CRTSCTS(tty)) { + mos7720_port->shadowMCR &= ~UART_MCR_RTS; + write_mos_reg(port->serial, port->port_number, MOS7720_MCR, + mos7720_port->shadowMCR); + } +} + +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 (C_CRTSCTS(tty)) { + mos7720_port->shadowMCR |= UART_MCR_RTS; + write_mos_reg(port->serial, port->port_number, MOS7720_MCR, + mos7720_port->shadowMCR); + } +} + +/* 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->port_number; + + write_mos_reg(serial, port_number, MOS7720_IER, 0x00); + write_mos_reg(serial, port_number, MOS7720_FCR, 0x00); + write_mos_reg(serial, port_number, MOS7720_FCR, 0xcf); + mos7720_port->shadowMCR = 0x0b; + write_mos_reg(serial, port_number, MOS7720_MCR, + mos7720_port->shadowMCR); + write_mos_reg(serial, dummy, MOS7720_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 = MOS7720_SP1_REG; + else + sp_reg = MOS7720_SP2_REG; + write_mos_reg(serial, dummy, sp_reg, baud * 0x10); + write_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG, 0x03); + mos7720_port->shadowMCR = 0x2b; + write_mos_reg(serial, port_number, MOS7720_MCR, + mos7720_port->shadowMCR); + + /*********************************************** + * Set DLL/DLM + ***********************************************/ + mos7720_port->shadowLCR = mos7720_port->shadowLCR | UART_LCR_DLAB; + write_mos_reg(serial, port_number, MOS7720_LCR, + mos7720_port->shadowLCR); + write_mos_reg(serial, port_number, MOS7720_DLL, 0x01); + write_mos_reg(serial, port_number, MOS7720_DLM, 0x00); + mos7720_port->shadowLCR = mos7720_port->shadowLCR & ~UART_LCR_DLAB; + write_mos_reg(serial, port_number, MOS7720_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 const 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->port_number; + 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, MOS7720_LCR, mos7720_port->shadowLCR); + + /* Write the divisor */ + write_mos_reg(serial, number, MOS7720_DLL, (__u8)(divisor & 0xff)); + write_mos_reg(serial, number, MOS7720_DLM, + (__u8)((divisor & 0xff00) >> 8)); + + /* Disable access to divisor latch */ + mos7720_port->shadowLCR = mos7720_port->shadowLCR & ~UART_LCR_DLAB; + write_mos_reg(serial, number, MOS7720_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, + const struct ktermios *old_termios) +{ + struct usb_serial_port *port; + struct usb_serial *serial; + int baud; + unsigned cflag; + __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->port_number; + + if (!mos7720_port->open) { + dev_dbg(&port->dev, "%s - port not opened\n", __func__); + return; + } + + lStop = 0x00; /* 1 stop bit */ + lParity = 0x00; /* No parity */ + + cflag = tty->termios.c_cflag; + + lData = UART_LCR_WLEN(tty_get_char_size(cflag)); + + /* 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, MOS7720_IER, 0x00); + write_mos_reg(serial, port_number, MOS7720_FCR, 0x00); + write_mos_reg(serial, port_number, MOS7720_FCR, 0xcf); + + /* Send the updated LCR value to the mos7720 */ + write_mos_reg(serial, port_number, MOS7720_LCR, + mos7720_port->shadowLCR); + mos7720_port->shadowMCR = 0x0b; + write_mos_reg(serial, port_number, MOS7720_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, MOS7720_SP_CONTROL_REG, + 0x01); + else + write_mos_reg(serial, dummy, MOS7720_SP_CONTROL_REG, + 0x02); + + } else + mos7720_port->shadowMCR &= ~(UART_MCR_XONANY); + + write_mos_reg(serial, port_number, MOS7720_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, MOS7720_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, MOS7720_IER, 0x0c); + + if (port->read_urb->status != -EINPROGRESS) { + status = usb_submit_urb(port->read_urb, GFP_KERNEL); + 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, + const struct ktermios *old_termios) +{ + int status; + struct moschip_port *mos7720_port; + + 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; + } + + /* 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_KERNEL); + 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->port_number; + int count; + + count = mos7720_chars_in_buffer(tty); + if (count == 0) { + read_mos_reg(port->serial, port_number, MOS7720_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->port_number, MOS7720_MCR, + mos7720_port->shadowMCR); + + 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; + + switch (cmd) { + case TIOCSERGETLSR: + dev_dbg(&port->dev, "%s TIOCSERGETLSR\n", __func__); + return get_lsr_info(tty, mos7720_port, + (unsigned int __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; + + if (product == MOSCHIP_DEVICE_ID_7715) { + struct urb *urb = serial->port[0]->interrupt_in_urb; + + urb->complete = mos7715_interrupt_callback; + +#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT + ret_val = mos7715_parport_init(serial); + if (ret_val < 0) + return ret_val; +#endif + } + /* start the interrupt urb */ + ret_val = usb_submit_urb(serial->port[0]->interrupt_in_urb, GFP_KERNEL); + if (ret_val) { + dev_err(&dev->dev, "failed to submit interrupt urb: %d\n", + ret_val); + } + + /* LSR For Port 1 */ + read_mos_reg(serial, 0, MOS7720_LSR, &data); + dev_dbg(&dev->dev, "LSR:%x\n", data); + + return 0; +} + +static void mos7720_release(struct usb_serial *serial) +{ + usb_kill_urb(serial->port[0]->interrupt_in_urb); + +#ifdef CONFIG_USB_SERIAL_MOS7715_PARPORT + /* close the parallel port */ + + if (le16_to_cpu(serial->dev->descriptor.idProduct) + == MOSCHIP_DEVICE_ID_7715) { + 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)); + /* + * If delayed work is currently scheduled, wait for it to + * complete. This also implies barriers that ensure the + * below serial clearing is not hoisted above the ->work. + */ + cancel_work_sync(&mos_parport->work); + + parport_remove_port(mos_parport->pp); + usb_set_serial_data(serial, NULL); + mos_parport->serial = NULL; + + parport_del_port(mos_parport->pp); + + 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_obj(*mos7720_port); + if (!mos7720_port) + return -ENOMEM; + + mos7720_port->port = port; + + usb_set_serial_port_data(port, mos7720_port); + + return 0; +} + +static void mos7720_port_remove(struct usb_serial_port *port) +{ + struct moschip_port *mos7720_port; + + mos7720_port = usb_get_serial_port_data(port); + kfree(mos7720_port); +} + +static struct usb_serial_driver moschip7720_2port_driver = { + .driver = { + .name = "moschip7720", + }, + .description = "Moschip 2 port adapter", + .id_table = id_table, + .num_bulk_in = 2, + .num_bulk_out = 2, + .num_interrupt_in = 1, + .calc_num_ports = mos77xx_calc_num_ports, + .open = mos7720_open, + .close = mos7720_close, + .throttle = mos7720_throttle, + .unthrottle = mos7720_unthrottle, + .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 = mos7720_interrupt_callback, +}; + +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 v2"); diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c new file mode 100644 index 000000000..ef803e74a --- /dev/null +++ b/drivers/usb/serial/mos7840.c @@ -0,0 +1,1805 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + +#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 */ + +/* Requests */ +#define MCS_RD_RTYPE 0xC0 +#define MCS_WR_RTYPE 0x40 +#define MCS_RDREQ 0x0D +#define MCS_WRREQ 0x0E +#define VENDOR_READ_LENGTH (0x01) + +#define ZLP_REG1 0x3A /* Zero_Flag_Reg1 58 */ +#define ZLP_REG5 0x3E /* Zero_Flag_Reg5 62 */ + +/* + * Vendor id and device id defines + * + * NOTE: Do not add new defines, add entries directly to the id_table instead. + */ +#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 + +/* 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_LED_BUSY, +}; + +#define MCS_PORT_MASK GENMASK(2, 0) +#define MCS_PORTS(nr) ((nr) & MCS_PORT_MASK) +#define MCS_LED BIT(3) + +#define MCS_DEVICE(vid, pid, flags) \ + USB_DEVICE((vid), (pid)), .driver_info = (flags) + +static const struct usb_device_id id_table[] = { + { MCS_DEVICE(0x0557, 0x2011, MCS_PORTS(4)) }, /* ATEN UC2324 */ + { MCS_DEVICE(0x0557, 0x7820, MCS_PORTS(2)) }, /* ATEN UC2322 */ + { MCS_DEVICE(0x110a, 0x2210, MCS_PORTS(2)) }, /* Moxa UPort 2210 */ + { MCS_DEVICE(0x9710, 0x7810, MCS_PORTS(1) | MCS_LED) }, /* ASIX MCS7810 */ + { MCS_DEVICE(0x9710, 0x7820, MCS_PORTS(2)) }, /* MosChip MCS7820 */ + { MCS_DEVICE(0x9710, 0x7840, MCS_PORTS(4)) }, /* MosChip MCS7840 */ + { MCS_DEVICE(0x9710, 0x7843, MCS_PORTS(3)) }, /* ASIX MCS7840 3 port */ + { 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) }, + {} /* 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 *read_urb; /* read URB for this port */ + __u8 shadowLCR; /* last LCR value received */ + __u8 shadowMCR; /* last MCR value received */ + struct usb_serial_port *port; /* loop back to the owner of this object */ + + /* Offsets */ + __u8 SpRegOffset; + __u8 ControlRegOffset; + __u8 DcrRegOffset; + + 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); + if (ret < VENDOR_READ_LENGTH) { + if (ret >= 0) + ret = -EIO; + goto out; + } + + *val = buf[0]; + dev_dbg(&port->dev, "%s offset is %x, return val %x\n", __func__, reg, *val); +out: + 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 == 2 && port->port_number != 0) + val |= ((__u16)port->port_number + 2) << 8; + else + val |= ((__u16)port->port_number + 1) << 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 == 2 && port->port_number != 0) + Wval = ((__u16)port->port_number + 2) << 8; + else + Wval = ((__u16)port->port_number + 1) << 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); + if (ret < VENDOR_READ_LENGTH) { + if (ret >= 0) + ret = -EIO; + goto out; + } + *val = buf[0]; +out: + 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); + +} + +/************************************************************************/ +/************************************************************************/ +/* 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_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: %d\n", + __func__, urb->status); + break; + default: + dev_dbg(&urb->dev->dev, "%s - nonzero urb status: %d\n", + __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(struct timer_list *t) +{ + struct moschip_port *mcs = timer_container_of(mcs, t, led_timer1); + + /* 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(struct timer_list *t) +{ + struct moschip_port *mcs = timer_container_of(mcs, t, led_timer2); + + 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_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) +{ + struct moschip_port *mos7840_port = urb->context; + struct usb_serial_port *port = mos7840_port->port; + int retval; + unsigned char *data; + int status = urb->status; + + if (status) { + dev_dbg(&urb->dev->dev, "nonzero read bulk status received: %d\n", status); + 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->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 = urb->context; + struct usb_serial_port *port = mos7840_port->port; + int status = urb->status; + unsigned long flags; + int i; + + spin_lock_irqsave(&mos7840_port->pool_lock, flags); + for (i = 0; i < NUM_URBS; i++) { + if (urb == mos7840_port->write_urb_pool[i]) { + mos7840_port->busy[i] = 0; + break; + } + } + spin_unlock_irqrestore(&mos7840_port->pool_lock, flags); + + if (status) { + dev_dbg(&port->dev, "nonzero write bulk status received:%d\n", status); + return; + } + + 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) +{ + struct moschip_port *mos7840_port = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + int response; + int j; + struct urb *urb; + __u16 Data; + int status; + + 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); + mos7840_port->write_urb_pool[j] = urb; + if (!urb) + 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; + 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); + + dev_dbg(&port->dev, "port number is %d\n", port->port_number); + dev_dbg(&port->dev, "minor number is %d\n", port->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->port_number % 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 port settings */ + /* Must set to enable ints! */ + mos7840_port->shadowMCR = MCR_MASTER_IE; + + 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) + *****************************************************************************/ + +static unsigned int mos7840_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct moschip_port *mos7840_port = usb_get_serial_port_data(port); + int i; + unsigned int chars = 0; + unsigned long flags; + + 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 %u\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 moschip_port *mos7840_port = usb_get_serial_port_data(port); + int j; + __u16 Data; + + 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]) { + kfree(mos7840_port->write_urb_pool[j]->transfer_buffer); + usb_free_urb(mos7840_port->write_urb_pool[j]); + } + } + + usb_kill_urb(mos7840_port->read_urb); + mos7840_port->read_urb_busy = false; + + Data = 0x0; + mos7840_set_uart_reg(port, MODEM_CONTROL_REGISTER, Data); + + Data = 0x00; + mos7840_set_uart_reg(port, INTERRUPT_ENABLE_REGISTER, Data); +} + +/***************************************************************************** + * mos7840_break + * this function sends a break to the port + *****************************************************************************/ +static int mos7840_break(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct moschip_port *mos7840_port = usb_get_serial_port_data(port); + unsigned char data; + + 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); + + return 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. + *****************************************************************************/ + +static unsigned int mos7840_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct moschip_port *mos7840_port = usb_get_serial_port_data(port); + int i; + unsigned int room = 0; + unsigned long flags; + + 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 %u\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) +{ + struct moschip_port *mos7840_port = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + int status; + int i; + int bytes_sent = 0; + int transfer_size; + unsigned long flags; + struct urb *urb; + /* __u16 Data; */ + const unsigned char *current_position = data; + + /* 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_ATOMIC); + if (!urb->transfer_buffer) { + bytes_sent = -ENOMEM; + 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->port_number % 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); + } + + 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 = usb_get_serial_port_data(port); + int status; + + /* 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 (C_CRTSCTS(tty)) { + 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; + struct moschip_port *mos7840_port = usb_get_serial_port_data(port); + int status; + + /* 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 (C_CRTSCTS(tty)) { + 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; + unsigned int result; + __u16 msr; + __u16 mcr; + int status; + + status = mos7840_get_uart_reg(port, MODEM_STATUS_REGISTER, &msr); + if (status < 0) + return -EIO; + status = mos7840_get_uart_reg(port, MODEM_CONTROL_REGISTER, &mcr); + if (status < 0) + 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 = usb_get_serial_port_data(port); + unsigned int mcr; + int status; + + /* 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; +} + +/***************************************************************************** + * 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) +{ + struct usb_serial_port *port = mos7840_port->port; + int divisor = 0; + int status; + __u16 Data; + __u16 clk_sel_val; + + dev_dbg(&port->dev, "%s - baud = %d\n", __func__, 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, + const struct ktermios *old_termios) +{ + struct usb_serial_port *port = mos7840_port->port; + int baud; + unsigned cflag; + __u8 lData; + __u8 lParity; + __u8 lStop; + int status; + __u16 Data; + + lData = LCR_BITS_8; + lStop = LCR_STOP_1; + lParity = LCR_PAR_NONE; + + cflag = tty->termios.c_cflag; + + /* 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) { + mos7840_port->read_urb_busy = true; + status = usb_submit_urb(mos7840_port->read_urb, GFP_KERNEL); + 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, + const struct ktermios *old_termios) +{ + struct moschip_port *mos7840_port = usb_get_serial_port_data(port); + int status; + + /* change the port settings to the new ones specified */ + + mos7840_change_port_settings(tty, mos7840_port, old_termios); + + if (!mos7840_port->read_urb_busy) { + mos7840_port->read_urb_busy = true; + status = usb_submit_urb(mos7840_port->read_urb, GFP_KERNEL); + 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; +} + +/***************************************************************************** + * 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; + + switch (cmd) { + /* return number of bytes available */ + + case TIOCSERGETLSR: + dev_dbg(&port->dev, "%s TIOCSERGETLSR\n", __func__); + return mos7840_get_lsr_info(tty, argp); + + default: + break; + } + return -ENOIOCTLCMD; +} + +/* + * Check if GPO (pin 42) is connected to GPI (pin 33) as recommended by ASIX + * for MCS7810 by bit-banging a 16-bit word. + * + * Note that GPO is really RTS of the third port so this will toggle RTS of + * port two or three on two- and four-port devices. + */ +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) +{ + unsigned long device_flags = id->driver_info; + u8 *buf; + + /* Skip device-type detection if we already have device flags. */ + if (device_flags) + 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_flags = MCS_PORTS(4); + else if (mos7810_check(serial)) + device_flags = MCS_PORTS(1) | MCS_LED; + else + device_flags = MCS_PORTS(2); + + kfree(buf); +out: + usb_set_serial_data(serial, (void *)device_flags); + + return 0; +} + +static int mos7840_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + unsigned long device_flags = (unsigned long)usb_get_serial_data(serial); + int num_ports = MCS_PORTS(device_flags); + + if (num_ports == 0 || num_ports > 4) + return -ENODEV; + + if (epds->num_bulk_in < num_ports || epds->num_bulk_out < num_ports) { + dev_err(&serial->interface->dev, "missing endpoints\n"); + return -ENODEV; + } + + return num_ports; +} + +static int mos7840_attach(struct usb_serial *serial) +{ + struct device *dev = &serial->interface->dev; + int status; + u16 val; + + /* Zero Length flag enable */ + val = 0x0f; + status = mos7840_set_reg_sync(serial->port[0], ZLP_REG5, val); + if (status < 0) + dev_dbg(dev, "Writing ZLP_REG5 failed status-0x%x\n", status); + else + dev_dbg(dev, "ZLP_REG5 Writing success status%d\n", status); + + return status; +} + +static int mos7840_port_probe(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + unsigned long device_flags = (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->port_number; + + dev_dbg(&port->dev, "mos7840_startup: configuring port %d\n", pnum); + mos7840_port = kzalloc_obj(struct moschip_port); + if (!mos7840_port) + 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; + 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->minor = %d\n", port->minor); + dev_dbg(&port->dev, "mos7840_port->port_num = %d\n", mos7840_port->port_num); + + if (mos7840_port->port_num == 1) { + mos7840_port->SpRegOffset = 0x0; + mos7840_port->ControlRegOffset = 0x1; + mos7840_port->DcrRegOffset = 0x4; + } else { + u8 phy_num = mos7840_port->port_num; + + /* Port 2 in the 2-port case uses registers of port 3 */ + if (serial->num_ports == 2) + phy_num = 3; + + mos7840_port->SpRegOffset = 0x8 + 2 * (phy_num - 2); + mos7840_port->ControlRegOffset = 0x9 + 2 * (phy_num - 2); + mos7840_port->DcrRegOffset = 0x16 + 3 * (phy_num - 2); + } + mos7840_dump_serial_port(port, mos7840_port); + usb_set_serial_port_data(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 error; + } 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 error; + } 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 error; + } 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 error; + } 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 error; + } 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 error; + } 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 error; + } 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 error; + } 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 error; + } else + dev_dbg(&port->dev, "ZLP_REG%d Writing success status%d\n", pnum + 1, status); + + } + + mos7840_port->has_led = device_flags & MCS_LED; + + /* Initialize LED timers */ + if (mos7840_port->has_led) { + mos7840_port->led_urb = usb_alloc_urb(0, GFP_KERNEL); + mos7840_port->led_dr = kmalloc_obj(*mos7840_port->led_dr); + if (!mos7840_port->led_urb || !mos7840_port->led_dr) { + status = -ENOMEM; + goto error; + } + + timer_setup(&mos7840_port->led_timer1, mos7840_led_off, 0); + mos7840_port->led_timer1.expires = + jiffies + msecs_to_jiffies(LED_ON_MS); + timer_setup(&mos7840_port->led_timer2, mos7840_led_flag_off, + 0); + mos7840_port->led_timer2.expires = + jiffies + msecs_to_jiffies(LED_OFF_MS); + + /* Turn off LED */ + mos7840_set_led_sync(port, MODEM_CONTROL_REGISTER, 0x0300); + } + + return 0; +error: + kfree(mos7840_port->led_dr); + usb_free_urb(mos7840_port->led_urb); + kfree(mos7840_port); + + return status; +} + +static void mos7840_port_remove(struct usb_serial_port *port) +{ + struct moschip_port *mos7840_port = usb_get_serial_port_data(port); + + if (mos7840_port->has_led) { + /* Turn off LED */ + mos7840_set_led_sync(port, MODEM_CONTROL_REGISTER, 0x0300); + + timer_shutdown_sync(&mos7840_port->led_timer1); + timer_shutdown_sync(&mos7840_port->led_timer2); + + usb_kill_urb(mos7840_port->led_urb); + usb_free_urb(mos7840_port->led_urb); + kfree(mos7840_port->led_dr); + } + + kfree(mos7840_port); +} + +static int mos7840_suspend(struct usb_serial *serial, pm_message_t message) +{ + struct moschip_port *mos7840_port; + struct usb_serial_port *port; + int i; + + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + if (!tty_port_initialized(&port->port)) + continue; + + mos7840_port = usb_get_serial_port_data(port); + + usb_kill_urb(mos7840_port->read_urb); + mos7840_port->read_urb_busy = false; + } + + return 0; +} + +static int mos7840_resume(struct usb_serial *serial) +{ + struct moschip_port *mos7840_port; + struct usb_serial_port *port; + int res; + int i; + + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; + if (!tty_port_initialized(&port->port)) + continue; + + mos7840_port = usb_get_serial_port_data(port); + + mos7840_port->read_urb_busy = true; + res = usb_submit_urb(mos7840_port->read_urb, GFP_NOIO); + if (res) + mos7840_port->read_urb_busy = false; + } + + return 0; +} + +static struct usb_serial_driver moschip7840_4port_device = { + .driver = { + .name = "mos7840", + }, + .description = DRIVER_DESC, + .id_table = id_table, + .num_interrupt_in = 1, + .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, + .attach = mos7840_attach, + .ioctl = mos7840_ioctl, + .set_termios = mos7840_set_termios, + .break_ctl = mos7840_break, + .tiocmget = mos7840_tiocmget, + .tiocmset = mos7840_tiocmset, + .get_icount = usb_serial_generic_get_icount, + .port_probe = mos7840_port_probe, + .port_remove = mos7840_port_remove, + .read_bulk_callback = mos7840_bulk_in_callback, + .suspend = mos7840_suspend, + .resume = mos7840_resume, +}; + +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/mxuport.c b/drivers/usb/serial/mxuport.c new file mode 100644 index 000000000..088d5dd8a --- /dev/null +++ b/drivers/usb/serial/mxuport.c @@ -0,0 +1,1325 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * mxuport.c - MOXA UPort series driver + * + * Copyright (c) 2006 Moxa Technologies Co., Ltd. + * Copyright (c) 2013 Andrew Lunn + * + * Supports the following Moxa USB to serial converters: + * 2 ports : UPort 1250, UPort 1250I + * 4 ports : UPort 1410, UPort 1450, UPort 1450I + * 8 ports : UPort 1610-8, UPort 1650-8 + * 16 ports : UPort 1610-16, UPort 1650-16 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Definitions for the vendor ID and device ID */ +#define MX_USBSERIAL_VID 0x110A +#define MX_UPORT1250_PID 0x1250 +#define MX_UPORT1251_PID 0x1251 +#define MX_UPORT1410_PID 0x1410 +#define MX_UPORT1450_PID 0x1450 +#define MX_UPORT1451_PID 0x1451 +#define MX_UPORT1618_PID 0x1618 +#define MX_UPORT1658_PID 0x1658 +#define MX_UPORT1613_PID 0x1613 +#define MX_UPORT1653_PID 0x1653 + +/* Definitions for USB info */ +#define HEADER_SIZE 4 +#define EVENT_LENGTH 8 +#define DOWN_BLOCK_SIZE 64 + +/* Definitions for firmware info */ +#define VER_ADDR_1 0x20 +#define VER_ADDR_2 0x24 +#define VER_ADDR_3 0x28 + +/* Definitions for USB vendor request */ +#define RQ_VENDOR_NONE 0x00 +#define RQ_VENDOR_SET_BAUD 0x01 /* Set baud rate */ +#define RQ_VENDOR_SET_LINE 0x02 /* Set line status */ +#define RQ_VENDOR_SET_CHARS 0x03 /* Set Xon/Xoff chars */ +#define RQ_VENDOR_SET_RTS 0x04 /* Set RTS */ +#define RQ_VENDOR_SET_DTR 0x05 /* Set DTR */ +#define RQ_VENDOR_SET_XONXOFF 0x06 /* Set auto Xon/Xoff */ +#define RQ_VENDOR_SET_RX_HOST_EN 0x07 /* Set RX host enable */ +#define RQ_VENDOR_SET_OPEN 0x08 /* Set open/close port */ +#define RQ_VENDOR_PURGE 0x09 /* Purge Rx/Tx buffer */ +#define RQ_VENDOR_SET_MCR 0x0A /* Set MCR register */ +#define RQ_VENDOR_SET_BREAK 0x0B /* Set Break signal */ + +#define RQ_VENDOR_START_FW_DOWN 0x0C /* Start firmware download */ +#define RQ_VENDOR_STOP_FW_DOWN 0x0D /* Stop firmware download */ +#define RQ_VENDOR_QUERY_FW_READY 0x0E /* Query if new firmware ready */ + +#define RQ_VENDOR_SET_FIFO_DISABLE 0x0F /* Set fifo disable */ +#define RQ_VENDOR_SET_INTERFACE 0x10 /* Set interface */ +#define RQ_VENDOR_SET_HIGH_PERFOR 0x11 /* Set hi-performance */ + +#define RQ_VENDOR_ERASE_BLOCK 0x12 /* Erase flash block */ +#define RQ_VENDOR_WRITE_PAGE 0x13 /* Write flash page */ +#define RQ_VENDOR_PREPARE_WRITE 0x14 /* Prepare write flash */ +#define RQ_VENDOR_CONFIRM_WRITE 0x15 /* Confirm write flash */ +#define RQ_VENDOR_LOCATE 0x16 /* Locate the device */ + +#define RQ_VENDOR_START_ROM_DOWN 0x17 /* Start firmware download */ +#define RQ_VENDOR_ROM_DATA 0x18 /* Rom file data */ +#define RQ_VENDOR_STOP_ROM_DOWN 0x19 /* Stop firmware download */ +#define RQ_VENDOR_FW_DATA 0x20 /* Firmware data */ + +#define RQ_VENDOR_RESET_DEVICE 0x23 /* Try to reset the device */ +#define RQ_VENDOR_QUERY_FW_CONFIG 0x24 + +#define RQ_VENDOR_GET_VERSION 0x81 /* Get firmware version */ +#define RQ_VENDOR_GET_PAGE 0x82 /* Read flash page */ +#define RQ_VENDOR_GET_ROM_PROC 0x83 /* Get ROM process state */ + +#define RQ_VENDOR_GET_INQUEUE 0x84 /* Data in input buffer */ +#define RQ_VENDOR_GET_OUTQUEUE 0x85 /* Data in output buffer */ + +#define RQ_VENDOR_GET_MSR 0x86 /* Get modem status register */ + +/* Definitions for UPort event type */ +#define UPORT_EVENT_NONE 0 /* None */ +#define UPORT_EVENT_TXBUF_THRESHOLD 1 /* Tx buffer threshold */ +#define UPORT_EVENT_SEND_NEXT 2 /* Send next */ +#define UPORT_EVENT_MSR 3 /* Modem status */ +#define UPORT_EVENT_LSR 4 /* Line status */ +#define UPORT_EVENT_MCR 5 /* Modem control */ + +/* Definitions for serial event type */ +#define SERIAL_EV_CTS 0x0008 /* CTS changed state */ +#define SERIAL_EV_DSR 0x0010 /* DSR changed state */ +#define SERIAL_EV_RLSD 0x0020 /* RLSD changed state */ + +/* Definitions for modem control event type */ +#define SERIAL_EV_XOFF 0x40 /* XOFF received */ + +/* Definitions for line control of communication */ +#define MX_WORDLENGTH_5 5 +#define MX_WORDLENGTH_6 6 +#define MX_WORDLENGTH_7 7 +#define MX_WORDLENGTH_8 8 + +#define MX_PARITY_NONE 0 +#define MX_PARITY_ODD 1 +#define MX_PARITY_EVEN 2 +#define MX_PARITY_MARK 3 +#define MX_PARITY_SPACE 4 + +#define MX_STOP_BITS_1 0 +#define MX_STOP_BITS_1_5 1 +#define MX_STOP_BITS_2 2 + +#define MX_RTS_DISABLE 0x0 +#define MX_RTS_ENABLE 0x1 +#define MX_RTS_HW 0x2 +#define MX_RTS_NO_CHANGE 0x3 /* Flag, not valid register value*/ + +#define MX_INT_RS232 0 +#define MX_INT_2W_RS485 1 +#define MX_INT_RS422 2 +#define MX_INT_4W_RS485 3 + +/* Definitions for holding reason */ +#define MX_WAIT_FOR_CTS 0x0001 +#define MX_WAIT_FOR_DSR 0x0002 +#define MX_WAIT_FOR_DCD 0x0004 +#define MX_WAIT_FOR_XON 0x0008 +#define MX_WAIT_FOR_START_TX 0x0010 +#define MX_WAIT_FOR_UNTHROTTLE 0x0020 +#define MX_WAIT_FOR_LOW_WATER 0x0040 +#define MX_WAIT_FOR_SEND_NEXT 0x0080 + +/* This structure holds all of the local port information */ +struct mxuport_port { + u8 mcr_state; /* Last MCR state */ + u8 msr_state; /* Last MSR state */ + struct mutex mutex; /* Protects mcr_state */ + spinlock_t spinlock; /* Protects msr_state */ +}; + +/* Encode number of ports (2..16 or undefined) */ +#define MX_PORTS_MASK GENMASK(3, 0) +#define MX_PORTS_OFFSET 1 +#define MX_PORTS(n) (((n) - MX_PORTS_OFFSET) & MX_PORTS_MASK) + +/* Table of devices that work with this driver */ +static const struct usb_device_id mxuport_idtable[] = { + { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1250_PID), + .driver_info = MX_PORTS(2) }, + { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1251_PID), + .driver_info = MX_PORTS(2) }, + { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1410_PID), + .driver_info = MX_PORTS(4) }, + { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1450_PID), + .driver_info = MX_PORTS(4) }, + { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1451_PID), + .driver_info = MX_PORTS(4) }, + { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1618_PID), + .driver_info = MX_PORTS(8) }, + { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1658_PID), + .driver_info = MX_PORTS(8) }, + { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1613_PID), + .driver_info = MX_PORTS(16) }, + { USB_DEVICE(MX_USBSERIAL_VID, MX_UPORT1653_PID), + .driver_info = MX_PORTS(16) }, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, mxuport_idtable); + +/* + * Add a four byte header containing the port number and the number of + * bytes of data in the message. Return the number of bytes in the + * buffer. + */ +static int mxuport_prepare_write_buffer(struct usb_serial_port *port, + void *dest, size_t size) +{ + u8 *buf = dest; + int count; + + count = kfifo_out_locked(&port->write_fifo, buf + HEADER_SIZE, + size - HEADER_SIZE, + &port->lock); + + put_unaligned_be16(port->port_number, buf); + put_unaligned_be16(count, buf + 2); + + dev_dbg(&port->dev, "%s - size %zd count %d\n", __func__, + size, count); + + return count + HEADER_SIZE; +} + +/* Read the given buffer in from the control pipe. */ +static int mxuport_recv_ctrl_urb(struct usb_serial *serial, + u8 request, u16 value, u16 index, + u8 *data, size_t size) +{ + int status; + + status = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + request, + (USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_DEVICE), value, index, + data, size, + USB_CTRL_GET_TIMEOUT); + if (status < 0) { + dev_err(&serial->interface->dev, + "%s - usb_control_msg failed (%d)\n", + __func__, status); + return status; + } + + if (status != size) { + dev_err(&serial->interface->dev, + "%s - short read (%d / %zd)\n", + __func__, status, size); + return -EIO; + } + + return status; +} + +/* Write the given buffer out to the control pipe. */ +static int mxuport_send_ctrl_data_urb(struct usb_serial *serial, + u8 request, + u16 value, u16 index, + u8 *data, size_t size) +{ + int status; + + status = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + request, + (USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE), value, index, + data, size, + USB_CTRL_SET_TIMEOUT); + if (status < 0) { + dev_err(&serial->interface->dev, + "%s - usb_control_msg failed (%d)\n", + __func__, status); + return status; + } + + return 0; +} + +/* Send a vendor request without any data */ +static int mxuport_send_ctrl_urb(struct usb_serial *serial, + u8 request, u16 value, u16 index) +{ + return mxuport_send_ctrl_data_urb(serial, request, value, index, + NULL, 0); +} + +/* + * mxuport_throttle - throttle function of driver + * + * This function is called by the tty driver when it wants to stop the + * data being read from the port. Since all the data comes over one + * bulk in endpoint, we cannot stop submitting urbs by setting + * port->throttle. Instead tell the device to stop sending us data for + * the port. + */ +static void mxuport_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + + dev_dbg(&port->dev, "%s\n", __func__); + + mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_RX_HOST_EN, + 0, port->port_number); +} + +/* + * mxuport_unthrottle - unthrottle function of driver + * + * This function is called by the tty driver when it wants to resume + * the data being read from the port. Tell the device it can resume + * sending us received data from the port. + */ +static void mxuport_unthrottle(struct tty_struct *tty) +{ + + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + + dev_dbg(&port->dev, "%s\n", __func__); + + mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_RX_HOST_EN, + 1, port->port_number); +} + +/* + * Processes one chunk of data received for a port. Mostly a copy of + * usb_serial_generic_process_read_urb(). + */ +static void mxuport_process_read_urb_data(struct usb_serial_port *port, + char *data, int size) +{ + int i; + + if (port->sysrq) { + for (i = 0; i < size; i++, data++) { + if (!usb_serial_handle_sysrq_char(port, *data)) + tty_insert_flip_char(&port->port, *data, + TTY_NORMAL); + } + } else { + tty_insert_flip_string(&port->port, data, size); + } + tty_flip_buffer_push(&port->port); +} + +static void mxuport_msr_event(struct usb_serial_port *port, u8 buf[4]) +{ + struct mxuport_port *mxport = usb_get_serial_port_data(port); + u8 rcv_msr_hold = buf[2] & 0xF0; + u16 rcv_msr_event = get_unaligned_be16(buf); + unsigned long flags; + + if (rcv_msr_event == 0) + return; + + /* Update MSR status */ + spin_lock_irqsave(&mxport->spinlock, flags); + + dev_dbg(&port->dev, "%s - current MSR status = 0x%x\n", + __func__, mxport->msr_state); + + if (rcv_msr_hold & UART_MSR_CTS) { + mxport->msr_state |= UART_MSR_CTS; + dev_dbg(&port->dev, "%s - CTS high\n", __func__); + } else { + mxport->msr_state &= ~UART_MSR_CTS; + dev_dbg(&port->dev, "%s - CTS low\n", __func__); + } + + if (rcv_msr_hold & UART_MSR_DSR) { + mxport->msr_state |= UART_MSR_DSR; + dev_dbg(&port->dev, "%s - DSR high\n", __func__); + } else { + mxport->msr_state &= ~UART_MSR_DSR; + dev_dbg(&port->dev, "%s - DSR low\n", __func__); + } + + if (rcv_msr_hold & UART_MSR_DCD) { + mxport->msr_state |= UART_MSR_DCD; + dev_dbg(&port->dev, "%s - DCD high\n", __func__); + } else { + mxport->msr_state &= ~UART_MSR_DCD; + dev_dbg(&port->dev, "%s - DCD low\n", __func__); + } + spin_unlock_irqrestore(&mxport->spinlock, flags); + + if (rcv_msr_event & + (SERIAL_EV_CTS | SERIAL_EV_DSR | SERIAL_EV_RLSD)) { + + if (rcv_msr_event & SERIAL_EV_CTS) { + port->icount.cts++; + dev_dbg(&port->dev, "%s - CTS change\n", __func__); + } + + if (rcv_msr_event & SERIAL_EV_DSR) { + port->icount.dsr++; + dev_dbg(&port->dev, "%s - DSR change\n", __func__); + } + + if (rcv_msr_event & SERIAL_EV_RLSD) { + port->icount.dcd++; + dev_dbg(&port->dev, "%s - DCD change\n", __func__); + } + wake_up_interruptible(&port->port.delta_msr_wait); + } +} + +static void mxuport_lsr_event(struct usb_serial_port *port, u8 buf[4]) +{ + u8 lsr_event = buf[2]; + + if (lsr_event & UART_LSR_BI) { + port->icount.brk++; + dev_dbg(&port->dev, "%s - break error\n", __func__); + } + + if (lsr_event & UART_LSR_FE) { + port->icount.frame++; + dev_dbg(&port->dev, "%s - frame error\n", __func__); + } + + if (lsr_event & UART_LSR_PE) { + port->icount.parity++; + dev_dbg(&port->dev, "%s - parity error\n", __func__); + } + + if (lsr_event & UART_LSR_OE) { + port->icount.overrun++; + dev_dbg(&port->dev, "%s - overrun error\n", __func__); + } +} + +/* + * When something interesting happens, modem control lines XON/XOFF + * etc, the device sends an event. Process these events. + */ +static void mxuport_process_read_urb_event(struct usb_serial_port *port, + u8 buf[4], u32 event) +{ + dev_dbg(&port->dev, "%s - receive event : %04x\n", __func__, event); + + switch (event) { + case UPORT_EVENT_SEND_NEXT: + /* + * Sent as part of the flow control on device buffers. + * Not currently used. + */ + break; + case UPORT_EVENT_MSR: + mxuport_msr_event(port, buf); + break; + case UPORT_EVENT_LSR: + mxuport_lsr_event(port, buf); + break; + case UPORT_EVENT_MCR: + /* + * Event to indicate a change in XON/XOFF from the + * peer. Currently not used. We just continue + * sending the device data and it will buffer it if + * needed. This event could be used for flow control + * between the host and the device. + */ + break; + default: + dev_dbg(&port->dev, "Unexpected event\n"); + break; + } +} + +/* + * One URB can contain data for multiple ports. Demultiplex the data, + * checking the port exists, is opened and the message is valid. + */ +static void mxuport_process_read_urb_demux_data(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct usb_serial *serial = port->serial; + u8 *data = urb->transfer_buffer; + u8 *end = data + urb->actual_length; + struct usb_serial_port *demux_port; + u8 *ch; + u16 rcv_port; + u16 rcv_len; + + while (data < end) { + if (data + HEADER_SIZE > end) { + dev_warn(&port->dev, "%s - message with short header\n", + __func__); + return; + } + + rcv_port = get_unaligned_be16(data); + if (rcv_port >= serial->num_ports) { + dev_warn(&port->dev, "%s - message for invalid port\n", + __func__); + return; + } + + demux_port = serial->port[rcv_port]; + rcv_len = get_unaligned_be16(data + 2); + if (!rcv_len || data + HEADER_SIZE + rcv_len > end) { + dev_warn(&port->dev, "%s - short data\n", __func__); + return; + } + + if (tty_port_initialized(&demux_port->port)) { + ch = data + HEADER_SIZE; + mxuport_process_read_urb_data(demux_port, ch, rcv_len); + } else { + dev_dbg(&demux_port->dev, "%s - data for closed port\n", + __func__); + } + data += HEADER_SIZE + rcv_len; + } +} + +/* + * One URB can contain events for multiple ports. Demultiplex the event, + * checking the port exists, and is opened. + */ +static void mxuport_process_read_urb_demux_event(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct usb_serial *serial = port->serial; + u8 *data = urb->transfer_buffer; + u8 *end = data + urb->actual_length; + struct usb_serial_port *demux_port; + u8 *ch; + u16 rcv_port; + u16 rcv_event; + + while (data < end) { + if (data + EVENT_LENGTH > end) { + dev_warn(&port->dev, "%s - message with short event\n", + __func__); + return; + } + + rcv_port = get_unaligned_be16(data); + if (rcv_port >= serial->num_ports) { + dev_warn(&port->dev, "%s - message for invalid port\n", + __func__); + return; + } + + demux_port = serial->port[rcv_port]; + if (tty_port_initialized(&demux_port->port)) { + ch = data + HEADER_SIZE; + rcv_event = get_unaligned_be16(data + 2); + mxuport_process_read_urb_event(demux_port, ch, + rcv_event); + } else { + dev_dbg(&demux_port->dev, + "%s - event for closed port\n", __func__); + } + data += EVENT_LENGTH; + } +} + +/* + * This is called when we have received data on the bulk in + * endpoint. Depending on which port it was received on, it can + * contain serial data or events. + */ +static void mxuport_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct usb_serial *serial = port->serial; + + if (port == serial->port[0]) + mxuport_process_read_urb_demux_data(urb); + + if (port == serial->port[1]) + mxuport_process_read_urb_demux_event(urb); +} + +/* + * Ask the device how many bytes it has queued to be sent out. If + * there are none, return true. + */ +static bool mxuport_tx_empty(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + bool is_empty = true; + u32 txlen; + u8 *len_buf; + int err; + + len_buf = kzalloc(4, GFP_KERNEL); + if (!len_buf) + goto out; + + err = mxuport_recv_ctrl_urb(serial, RQ_VENDOR_GET_OUTQUEUE, 0, + port->port_number, len_buf, 4); + if (err < 0) + goto out; + + txlen = get_unaligned_be32(len_buf); + dev_dbg(&port->dev, "%s - tx len = %u\n", __func__, txlen); + + if (txlen != 0) + is_empty = false; + +out: + kfree(len_buf); + return is_empty; +} + +static int mxuport_set_mcr(struct usb_serial_port *port, u8 mcr_state) +{ + struct usb_serial *serial = port->serial; + int err; + + dev_dbg(&port->dev, "%s - %02x\n", __func__, mcr_state); + + err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_MCR, + mcr_state, port->port_number); + if (err) + dev_err(&port->dev, "%s - failed to change MCR\n", __func__); + + return err; +} + +static int mxuport_set_dtr(struct usb_serial_port *port, int on) +{ + struct mxuport_port *mxport = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + int err; + + mutex_lock(&mxport->mutex); + + err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_DTR, + !!on, port->port_number); + if (!err) { + if (on) + mxport->mcr_state |= UART_MCR_DTR; + else + mxport->mcr_state &= ~UART_MCR_DTR; + } + + mutex_unlock(&mxport->mutex); + + return err; +} + +static int mxuport_set_rts(struct usb_serial_port *port, u8 state) +{ + struct mxuport_port *mxport = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + int err; + u8 mcr_state; + + mutex_lock(&mxport->mutex); + mcr_state = mxport->mcr_state; + + switch (state) { + case MX_RTS_DISABLE: + mcr_state &= ~UART_MCR_RTS; + break; + case MX_RTS_ENABLE: + mcr_state |= UART_MCR_RTS; + break; + case MX_RTS_HW: + /* + * Do not update mxport->mcr_state when doing hardware + * flow control. + */ + break; + default: + /* + * Should not happen, but somebody might try passing + * MX_RTS_NO_CHANGE, which is not valid. + */ + err = -EINVAL; + goto out; + } + err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_RTS, + state, port->port_number); + if (!err) + mxport->mcr_state = mcr_state; + +out: + mutex_unlock(&mxport->mutex); + + return err; +} + +static void mxuport_dtr_rts(struct usb_serial_port *port, int on) +{ + struct mxuport_port *mxport = usb_get_serial_port_data(port); + u8 mcr_state; + int err; + + mutex_lock(&mxport->mutex); + mcr_state = mxport->mcr_state; + + if (on) + mcr_state |= (UART_MCR_RTS | UART_MCR_DTR); + else + mcr_state &= ~(UART_MCR_RTS | UART_MCR_DTR); + + err = mxuport_set_mcr(port, mcr_state); + if (!err) + mxport->mcr_state = mcr_state; + + mutex_unlock(&mxport->mutex); +} + +static int mxuport_tiocmset(struct tty_struct *tty, unsigned int set, + unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct mxuport_port *mxport = usb_get_serial_port_data(port); + int err; + u8 mcr_state; + + mutex_lock(&mxport->mutex); + mcr_state = mxport->mcr_state; + + if (set & TIOCM_RTS) + mcr_state |= UART_MCR_RTS; + + if (set & TIOCM_DTR) + mcr_state |= UART_MCR_DTR; + + if (clear & TIOCM_RTS) + mcr_state &= ~UART_MCR_RTS; + + if (clear & TIOCM_DTR) + mcr_state &= ~UART_MCR_DTR; + + err = mxuport_set_mcr(port, mcr_state); + if (!err) + mxport->mcr_state = mcr_state; + + mutex_unlock(&mxport->mutex); + + return err; +} + +static int mxuport_tiocmget(struct tty_struct *tty) +{ + struct mxuport_port *mxport; + struct usb_serial_port *port = tty->driver_data; + unsigned int result; + unsigned long flags; + unsigned int msr; + unsigned int mcr; + + mxport = usb_get_serial_port_data(port); + + mutex_lock(&mxport->mutex); + spin_lock_irqsave(&mxport->spinlock, flags); + + msr = mxport->msr_state; + mcr = mxport->mcr_state; + + spin_unlock_irqrestore(&mxport->spinlock, flags); + mutex_unlock(&mxport->mutex); + + 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 */ + + dev_dbg(&port->dev, "%s - 0x%04x\n", __func__, result); + + return result; +} + +static int mxuport_set_termios_flow(struct tty_struct *tty, + const struct ktermios *old_termios, + struct usb_serial_port *port, + struct usb_serial *serial) +{ + u8 xon = START_CHAR(tty); + u8 xoff = STOP_CHAR(tty); + int enable; + int err; + u8 *buf; + u8 rts; + + buf = kmalloc(2, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* S/W flow control settings */ + if (I_IXOFF(tty) || I_IXON(tty)) { + enable = 1; + buf[0] = xon; + buf[1] = xoff; + + err = mxuport_send_ctrl_data_urb(serial, RQ_VENDOR_SET_CHARS, + 0, port->port_number, + buf, 2); + if (err) + goto out; + + dev_dbg(&port->dev, "%s - XON = 0x%02x, XOFF = 0x%02x\n", + __func__, xon, xoff); + } else { + enable = 0; + } + + err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_XONXOFF, + enable, port->port_number); + if (err) + goto out; + + rts = MX_RTS_NO_CHANGE; + + /* H/W flow control settings */ + if (!old_termios || + C_CRTSCTS(tty) != (old_termios->c_cflag & CRTSCTS)) { + if (C_CRTSCTS(tty)) + rts = MX_RTS_HW; + else + rts = MX_RTS_ENABLE; + } + + if (C_BAUD(tty)) { + if (old_termios && (old_termios->c_cflag & CBAUD) == B0) { + /* Raise DTR and RTS */ + if (C_CRTSCTS(tty)) + rts = MX_RTS_HW; + else + rts = MX_RTS_ENABLE; + mxuport_set_dtr(port, 1); + } + } else { + /* Drop DTR and RTS */ + rts = MX_RTS_DISABLE; + mxuport_set_dtr(port, 0); + } + + if (rts != MX_RTS_NO_CHANGE) + err = mxuport_set_rts(port, rts); + +out: + kfree(buf); + return err; +} + +static void mxuport_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +{ + struct usb_serial *serial = port->serial; + u8 *buf; + u8 data_bits; + u8 stop_bits; + u8 parity; + int baud; + int err; + + if (old_termios && + !tty_termios_hw_change(&tty->termios, old_termios) && + tty->termios.c_iflag == old_termios->c_iflag) { + dev_dbg(&port->dev, "%s - nothing to change\n", __func__); + return; + } + + buf = kmalloc(4, GFP_KERNEL); + if (!buf) + return; + + /* Set data bit of termios */ + switch (C_CSIZE(tty)) { + case CS5: + data_bits = MX_WORDLENGTH_5; + break; + case CS6: + data_bits = MX_WORDLENGTH_6; + break; + case CS7: + data_bits = MX_WORDLENGTH_7; + break; + case CS8: + default: + data_bits = MX_WORDLENGTH_8; + break; + } + + /* Set parity of termios */ + if (C_PARENB(tty)) { + if (C_CMSPAR(tty)) { + if (C_PARODD(tty)) + parity = MX_PARITY_MARK; + else + parity = MX_PARITY_SPACE; + } else { + if (C_PARODD(tty)) + parity = MX_PARITY_ODD; + else + parity = MX_PARITY_EVEN; + } + } else { + parity = MX_PARITY_NONE; + } + + /* Set stop bit of termios */ + if (C_CSTOPB(tty)) + stop_bits = MX_STOP_BITS_2; + else + stop_bits = MX_STOP_BITS_1; + + buf[0] = data_bits; + buf[1] = parity; + buf[2] = stop_bits; + buf[3] = 0; + + err = mxuport_send_ctrl_data_urb(serial, RQ_VENDOR_SET_LINE, + 0, port->port_number, buf, 4); + if (err) + goto out; + + err = mxuport_set_termios_flow(tty, old_termios, port, serial); + if (err) + goto out; + + baud = tty_get_baud_rate(tty); + if (!baud) + baud = 9600; + + /* Note: Little Endian */ + put_unaligned_le32(baud, buf); + + err = mxuport_send_ctrl_data_urb(serial, RQ_VENDOR_SET_BAUD, + 0, port->port_number, + buf, 4); + if (err) + goto out; + + dev_dbg(&port->dev, "baud_rate : %d\n", baud); + dev_dbg(&port->dev, "data_bits : %d\n", data_bits); + dev_dbg(&port->dev, "parity : %d\n", parity); + dev_dbg(&port->dev, "stop_bits : %d\n", stop_bits); + +out: + kfree(buf); +} + +/* + * Determine how many ports this device has dynamically. It will be + * called after the probe() callback is called, but before attach(). + */ +static int mxuport_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + unsigned long features = (unsigned long)usb_get_serial_data(serial); + int num_ports; + int i; + + if (features & MX_PORTS_MASK) { + num_ports = (features & MX_PORTS_MASK) + MX_PORTS_OFFSET; + } else { + dev_warn(&serial->interface->dev, + "unknown device, assuming two ports\n"); + num_ports = 2; + } + + /* + * Setup bulk-out endpoint multiplexing. All ports share the same + * bulk-out endpoint. + */ + BUILD_BUG_ON(ARRAY_SIZE(epds->bulk_out) < 16); + + /* + * The bulk-out buffers must be large enough for the four-byte header + * (and following data), but assume anything smaller than eight bytes + * is broken. + */ + if (usb_endpoint_maxp(epds->bulk_out[0]) < 8) + return -EINVAL; + + for (i = 1; i < num_ports; ++i) + epds->bulk_out[i] = epds->bulk_out[0]; + + epds->num_bulk_out = num_ports; + + return num_ports; +} + +/* Get the version of the firmware currently running. */ +static int mxuport_get_fw_version(struct usb_serial *serial, u32 *version) +{ + u8 *ver_buf; + int err; + + ver_buf = kzalloc(4, GFP_KERNEL); + if (!ver_buf) + return -ENOMEM; + + /* Get firmware version from SDRAM */ + err = mxuport_recv_ctrl_urb(serial, RQ_VENDOR_GET_VERSION, 0, 0, + ver_buf, 4); + if (err != 4) { + err = -EIO; + goto out; + } + + *version = (ver_buf[0] << 16) | (ver_buf[1] << 8) | ver_buf[2]; + err = 0; +out: + kfree(ver_buf); + return err; +} + +/* Given a firmware blob, download it to the device. */ +static int mxuport_download_fw(struct usb_serial *serial, + const struct firmware *fw_p) +{ + u8 *fw_buf; + size_t txlen; + size_t fwidx; + int err; + + fw_buf = kmalloc(DOWN_BLOCK_SIZE, GFP_KERNEL); + if (!fw_buf) + return -ENOMEM; + + dev_dbg(&serial->interface->dev, "Starting firmware download...\n"); + err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_START_FW_DOWN, 0, 0); + if (err) + goto out; + + fwidx = 0; + do { + txlen = min_t(size_t, (fw_p->size - fwidx), DOWN_BLOCK_SIZE); + + memcpy(fw_buf, &fw_p->data[fwidx], txlen); + err = mxuport_send_ctrl_data_urb(serial, RQ_VENDOR_FW_DATA, + 0, 0, fw_buf, txlen); + if (err) { + mxuport_send_ctrl_urb(serial, RQ_VENDOR_STOP_FW_DOWN, + 0, 0); + goto out; + } + + fwidx += txlen; + usleep_range(1000, 2000); + + } while (fwidx < fw_p->size); + + msleep(1000); + err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_STOP_FW_DOWN, 0, 0); + if (err) + goto out; + + msleep(1000); + err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_QUERY_FW_READY, 0, 0); + +out: + kfree(fw_buf); + return err; +} + +static int mxuport_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + u16 productid = le16_to_cpu(serial->dev->descriptor.idProduct); + const struct firmware *fw_p = NULL; + u32 version; + int local_ver; + char buf[32]; + int err; + + /* Load our firmware */ + err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_QUERY_FW_CONFIG, 0, 0); + if (err) { + mxuport_send_ctrl_urb(serial, RQ_VENDOR_RESET_DEVICE, 0, 0); + return err; + } + + err = mxuport_get_fw_version(serial, &version); + if (err < 0) + return err; + + dev_dbg(&serial->interface->dev, "Device firmware version v%x.%x.%x\n", + (version & 0xff0000) >> 16, + (version & 0xff00) >> 8, + (version & 0xff)); + + snprintf(buf, sizeof(buf) - 1, "moxa/moxa-%04x.fw", productid); + + err = request_firmware(&fw_p, buf, &serial->interface->dev); + if (err) { + dev_warn(&serial->interface->dev, "Firmware %s not found\n", + buf); + + /* Use the firmware already in the device */ + err = 0; + } else { + if (fw_p->size <= VER_ADDR_3) { + dev_err(&serial->interface->dev, + "Firmware %s is too short\n", buf); + err = -EINVAL; + goto out; + } + + local_ver = ((fw_p->data[VER_ADDR_1] << 16) | + (fw_p->data[VER_ADDR_2] << 8) | + fw_p->data[VER_ADDR_3]); + dev_dbg(&serial->interface->dev, + "Available firmware version v%x.%x.%x\n", + fw_p->data[VER_ADDR_1], fw_p->data[VER_ADDR_2], + fw_p->data[VER_ADDR_3]); + if (local_ver > version) { + err = mxuport_download_fw(serial, fw_p); + if (err) + goto out; + err = mxuport_get_fw_version(serial, &version); + if (err < 0) + goto out; + } + } + + dev_info(&serial->interface->dev, + "Using device firmware version v%x.%x.%x\n", + (version & 0xff0000) >> 16, + (version & 0xff00) >> 8, + (version & 0xff)); + + /* + * Contains the features of this hardware. Store away for + * later use, eg, number of ports. + */ + usb_set_serial_data(serial, (void *)id->driver_info); +out: + if (fw_p) + release_firmware(fw_p); + return err; +} + + +static int mxuport_port_probe(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct mxuport_port *mxport; + int err; + + mxport = devm_kzalloc(&port->dev, sizeof(struct mxuport_port), + GFP_KERNEL); + if (!mxport) + return -ENOMEM; + + mutex_init(&mxport->mutex); + spin_lock_init(&mxport->spinlock); + + /* Set the port private data */ + usb_set_serial_port_data(port, mxport); + + /* Set FIFO (Enable) */ + err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_FIFO_DISABLE, + 0, port->port_number); + if (err) + return err; + + /* Set transmission mode (Hi-Performance) */ + err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_HIGH_PERFOR, + 0, port->port_number); + if (err) + return err; + + /* Set interface (RS-232) */ + return mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_INTERFACE, + MX_INT_RS232, + port->port_number); +} + +static int mxuport_attach(struct usb_serial *serial) +{ + struct usb_serial_port *port0 = serial->port[0]; + struct usb_serial_port *port1 = serial->port[1]; + int err; + + /* + * All data from the ports is received on the first bulk in + * endpoint, with a multiplex header. The second bulk in is + * used for events. + * + * Start to read from the device. + */ + err = usb_serial_generic_submit_read_urbs(port0, GFP_KERNEL); + if (err) + return err; + + err = usb_serial_generic_submit_read_urbs(port1, GFP_KERNEL); + if (err) { + usb_serial_generic_close(port0); + return err; + } + + return 0; +} + +static void mxuport_release(struct usb_serial *serial) +{ + struct usb_serial_port *port0 = serial->port[0]; + struct usb_serial_port *port1 = serial->port[1]; + + usb_serial_generic_close(port1); + usb_serial_generic_close(port0); +} + +static int mxuport_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct mxuport_port *mxport = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + int err; + + /* Set receive host (enable) */ + err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_RX_HOST_EN, + 1, port->port_number); + if (err) + return err; + + err = mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_OPEN, + 1, port->port_number); + if (err) { + mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_RX_HOST_EN, + 0, port->port_number); + return err; + } + + /* Initial port termios */ + if (tty) + mxuport_set_termios(tty, port, NULL); + + /* + * TODO: use RQ_VENDOR_GET_MSR, once we know what it + * returns. + */ + mxport->msr_state = 0; + + return err; +} + +static void mxuport_close(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + + mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_OPEN, 0, + port->port_number); + + mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_RX_HOST_EN, 0, + port->port_number); +} + +/* Send a break to the port. */ +static int mxuport_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + int enable; + + if (break_state == -1) { + enable = 1; + dev_dbg(&port->dev, "%s - sending break\n", __func__); + } else { + enable = 0; + dev_dbg(&port->dev, "%s - clearing break\n", __func__); + } + + return mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_BREAK, + enable, port->port_number); +} + +static int mxuport_resume(struct usb_serial *serial) +{ + struct usb_serial_port *port; + int c = 0; + int i; + int r; + + for (i = 0; i < 2; i++) { + port = serial->port[i]; + + r = usb_serial_generic_submit_read_urbs(port, GFP_NOIO); + if (r < 0) + c++; + } + + for (i = 0; i < serial->num_ports; i++) { + port = serial->port[i]; + if (!tty_port_initialized(&port->port)) + continue; + + r = usb_serial_generic_write_start(port, GFP_NOIO); + if (r < 0) + c++; + } + + return c ? -EIO : 0; +} + +static struct usb_serial_driver mxuport_device = { + .driver = { + .name = "mxuport", + }, + .description = "MOXA UPort", + .id_table = mxuport_idtable, + .num_bulk_in = 2, + .num_bulk_out = 1, + .probe = mxuport_probe, + .port_probe = mxuport_port_probe, + .attach = mxuport_attach, + .release = mxuport_release, + .calc_num_ports = mxuport_calc_num_ports, + .open = mxuport_open, + .close = mxuport_close, + .set_termios = mxuport_set_termios, + .break_ctl = mxuport_break_ctl, + .tx_empty = mxuport_tx_empty, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .get_icount = usb_serial_generic_get_icount, + .throttle = mxuport_throttle, + .unthrottle = mxuport_unthrottle, + .tiocmget = mxuport_tiocmget, + .tiocmset = mxuport_tiocmset, + .dtr_rts = mxuport_dtr_rts, + .process_read_urb = mxuport_process_read_urb, + .prepare_write_buffer = mxuport_prepare_write_buffer, + .resume = mxuport_resume, +}; + +static struct usb_serial_driver *const serial_drivers[] = { + &mxuport_device, NULL +}; + +module_usb_serial_driver(serial_drivers, mxuport_idtable); + +MODULE_AUTHOR("Andrew Lunn "); +MODULE_AUTHOR(""); +MODULE_DESCRIPTION("Moxa UPORT USB Serial driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/navman.c b/drivers/usb/serial/navman.c new file mode 100644 index 000000000..4d57a5902 --- /dev/null +++ b/drivers/usb/serial/navman.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Navman Serial USB driver + * + * Copyright (C) 2006 Greg Kroah-Hartman + * + * 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 + +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 = { + .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_DESCRIPTION("Navman USB Serial driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/omninet.c b/drivers/usb/serial/omninet.c new file mode 100644 index 000000000..363b8432c --- /dev/null +++ b/drivers/usb/serial/omninet.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB ZyXEL omni.net driver + * + * Copyright (C) 2013,2017 Johan Hovold + * + * See Documentation/usb/usb-serial.rst 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 + +#define DRIVER_AUTHOR "Alessandro Zummo" +#define DRIVER_DESC "USB ZyXEL omni.net Driver" + +#define ZYXEL_VENDOR_ID 0x0586 +#define ZYXEL_OMNINET_ID 0x1000 +#define ZYXEL_OMNI_56K_PLUS_ID 0x1500 +/* This one seems to be a re-branded ZyXEL device */ +#define BT_IGNITIONPRO_ID 0x2000 + +#define OMNINET_HEADERLEN 4 +#define OMNINET_BULKOUTSIZE 64 +#define OMNINET_PAYLOADSIZE (OMNINET_BULKOUTSIZE - OMNINET_HEADERLEN) + +/* function prototypes */ +static void omninet_process_read_urb(struct urb *urb); +static int omninet_prepare_write_buffer(struct usb_serial_port *port, + void *buf, size_t count); +static int omninet_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds); +static int omninet_port_probe(struct usb_serial_port *port); +static void 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, ZYXEL_OMNI_56K_PLUS_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 = { + .name = "omninet", + }, + .description = "ZyXEL - omni.net usb", + .id_table = id_table, + .num_bulk_out = 2, + .bulk_out_size = OMNINET_BULKOUTSIZE, + .calc_num_ports = omninet_calc_num_ports, + .port_probe = omninet_port_probe, + .port_remove = omninet_port_remove, + .process_read_urb = omninet_process_read_urb, + .prepare_write_buffer = omninet_prepare_write_buffer, +}; + +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_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + /* We need only the second bulk-out for our single-port device. */ + epds->bulk_out[0] = epds->bulk_out[1]; + epds->num_bulk_out = 1; + + return 1; +} + +static int omninet_port_probe(struct usb_serial_port *port) +{ + struct omninet_data *od; + + od = kzalloc_obj(*od); + if (!od) + return -ENOMEM; + + usb_set_serial_port_data(port, od); + + return 0; +} + +static void omninet_port_remove(struct usb_serial_port *port) +{ + struct omninet_data *od; + + od = usb_get_serial_port_data(port); + kfree(od); +} + +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_prepare_write_buffer(struct usb_serial_port *port, + void *buf, size_t count) +{ + struct omninet_data *od = usb_get_serial_port_data(port); + struct omninet_header *header = buf; + + count = min_t(size_t, count, OMNINET_PAYLOADSIZE); + + count = kfifo_out_locked(&port->write_fifo, buf + OMNINET_HEADERLEN, + count, &port->lock); + + header->oh_seq = od->od_outseq++; + header->oh_len = count; + header->oh_xxx = 0x03; + header->oh_pad = 0x00; + + /* always 64 bytes */ + return OMNINET_BULKOUTSIZE; +} + +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/opticon.c b/drivers/usb/serial/opticon.c new file mode 100644 index 000000000..152dfc901 --- /dev/null +++ b/drivers/usb/serial/opticon.c @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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. + */ + +#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; + int outstanding_bytes; + + struct usb_anchor anchor; +}; + + +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, USB_CTRL_SET_TIMEOUT); + 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 endpoint */ + 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_close(struct usb_serial_port *port) +{ + struct opticon_private *priv = usb_get_serial_port_data(port); + + usb_kill_anchored_urbs(&priv->anchor); + + usb_serial_generic_close(port); +} + +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; + priv->outstanding_bytes -= urb->transfer_buffer_length; + 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; + struct usb_ctrlrequest *dr; + int ret = -ENOMEM; + + 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++; + priv->outstanding_bytes += count; + spin_unlock_irqrestore(&priv->lock, flags); + + buffer = kmemdup(buf, count, GFP_ATOMIC); + if (!buffer) + goto error_no_buffer; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) + goto error_no_urb; + + usb_serial_debug_data(&port->dev, __func__, count, buffer); + + /* The connected devices do not have a bulk write endpoint, + * to transmit data to de barcode device the control endpoint is used */ + dr = kmalloc_obj(struct usb_ctrlrequest, GFP_ATOMIC); + if (!dr) + 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); + + usb_anchor_urb(urb, &priv->anchor); + + /* send it down the pipe */ + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) { + dev_err(&port->dev, "failed to submit write urb: %d\n", ret); + usb_unanchor_urb(urb); + 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; + priv->outstanding_bytes -= count; + spin_unlock_irqrestore(&priv->lock, flags); + + return ret; +} + +static unsigned 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 unsigned int opticon_chars_in_buffer(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; + unsigned int count; + + spin_lock_irqsave(&priv->lock, flags); + count = priv->outstanding_bytes; + spin_unlock_irqrestore(&priv->lock, flags); + + return count; +} + +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 opticon_port_probe(struct usb_serial_port *port) +{ + struct opticon_private *priv; + + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + init_usb_anchor(&priv->anchor); + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static void opticon_port_remove(struct usb_serial_port *port) +{ + struct opticon_private *priv = usb_get_serial_port_data(port); + + kfree(priv); +} + +static struct usb_serial_driver opticon_device = { + .driver = { + .name = "opticon", + }, + .id_table = id_table, + .num_ports = 1, + .num_bulk_in = 1, + .bulk_in_size = 256, + .port_probe = opticon_port_probe, + .port_remove = opticon_port_remove, + .open = opticon_open, + .close = opticon_close, + .write = opticon_write, + .write_room = opticon_write_room, + .chars_in_buffer = opticon_chars_in_buffer, + .throttle = usb_serial_generic_throttle, + .unthrottle = usb_serial_generic_unthrottle, + .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 v2"); diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c new file mode 100644 index 000000000..e4ad14375 --- /dev/null +++ b/drivers/usb/serial/option.c @@ -0,0 +1,2744 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + USB Driver for GSM modems + + Copyright (C) 2005 Matthias Urlichs + + 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 "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 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_U620L 0x9022 +#define NOVATELWIRELESS_PRODUCT_G2 0xA010 +#define NOVATELWIRELESS_PRODUCT_MC551 0xB001 + +#define UBLOX_VENDOR_ID 0x1546 + +/* 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 DELL_PRODUCT_5821E 0x81d7 +#define DELL_PRODUCT_5821E_ESIM 0x81e0 +#define DELL_PRODUCT_5829E_ESIM 0x81e4 +#define DELL_PRODUCT_5829E 0x81e6 +#define DELL_PRODUCT_5826E_ESIM 0x81ea + +#define DELL_PRODUCT_FM101R_ESIM 0x8213 +#define DELL_PRODUCT_FM101R 0x8215 + +#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 +/* These Quectel products use Qualcomm's vendor ID */ +#define QUECTEL_PRODUCT_UC20 0x9003 +#define QUECTEL_PRODUCT_UC15 0x9090 +/* These u-blox products use Qualcomm's vendor ID */ +#define UBLOX_PRODUCT_R410M 0x90b2 +/* These Yuga products use Qualcomm's vendor ID */ +#define YUGA_PRODUCT_CLM920_NC5 0x9625 + +#define QUECTEL_VENDOR_ID 0x2c7c +/* These Quectel products use Quectel's vendor ID */ +#define QUECTEL_PRODUCT_EC21 0x0121 +#define QUECTEL_PRODUCT_RG650V 0x0122 +#define QUECTEL_PRODUCT_EM061K_LTA 0x0123 +#define QUECTEL_PRODUCT_EM061K_LMS 0x0124 +#define QUECTEL_PRODUCT_EC25 0x0125 +#define QUECTEL_PRODUCT_EM060K_128 0x0128 +#define QUECTEL_PRODUCT_EM060K_129 0x0129 +#define QUECTEL_PRODUCT_EM060K_12a 0x012a +#define QUECTEL_PRODUCT_EM060K_12b 0x012b +#define QUECTEL_PRODUCT_EM060K_12c 0x012c +#define QUECTEL_PRODUCT_EG91 0x0191 +#define QUECTEL_PRODUCT_EG95 0x0195 +#define QUECTEL_PRODUCT_BG96 0x0296 +#define QUECTEL_PRODUCT_EP06 0x0306 +#define QUECTEL_PRODUCT_EM05G 0x030a +#define QUECTEL_PRODUCT_EM060K 0x030b +#define QUECTEL_PRODUCT_EM05G_CS 0x030c +#define QUECTEL_PRODUCT_EM05GV2 0x030e +#define QUECTEL_PRODUCT_EM05CN_SG 0x0310 +#define QUECTEL_PRODUCT_EM05G_SG 0x0311 +#define QUECTEL_PRODUCT_EM05CN 0x0312 +#define QUECTEL_PRODUCT_EM05G_GR 0x0313 +#define QUECTEL_PRODUCT_EM05G_RS 0x0314 +#define QUECTEL_PRODUCT_RG255C 0x0316 +#define QUECTEL_PRODUCT_EM12 0x0512 +#define QUECTEL_PRODUCT_RM500Q 0x0800 +#define QUECTEL_PRODUCT_RM520N 0x0801 +#define QUECTEL_PRODUCT_EC200U 0x0901 +#define QUECTEL_PRODUCT_EG912Y 0x6001 +#define QUECTEL_PRODUCT_EC200S_CN 0x6002 +#define QUECTEL_PRODUCT_EC200A 0x6005 +#define QUECTEL_PRODUCT_EG916Q 0x6007 +#define QUECTEL_PRODUCT_EM061K_LWW 0x6008 +#define QUECTEL_PRODUCT_EM061K_LCN 0x6009 +#define QUECTEL_PRODUCT_EC200T 0x6026 +#define QUECTEL_PRODUCT_RM500K 0x7001 + +#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_LE922_USBCFG1 0x1040 +#define TELIT_PRODUCT_LE922_USBCFG2 0x1041 +#define TELIT_PRODUCT_LE922_USBCFG0 0x1042 +#define TELIT_PRODUCT_LE922_USBCFG3 0x1043 +#define TELIT_PRODUCT_LE922_USBCFG5 0x1045 +#define TELIT_PRODUCT_ME910 0x1100 +#define TELIT_PRODUCT_ME910_DUAL_MODEM 0x1101 +#define TELIT_PRODUCT_LE920 0x1200 +#define TELIT_PRODUCT_LE910 0x1201 +#define TELIT_PRODUCT_LE910_USBCFG4 0x1206 +#define TELIT_PRODUCT_LE920A4_1207 0x1207 +#define TELIT_PRODUCT_LE920A4_1208 0x1208 +#define TELIT_PRODUCT_LE920A4_1211 0x1211 +#define TELIT_PRODUCT_LE920A4_1212 0x1212 +#define TELIT_PRODUCT_LE920A4_1213 0x1213 +#define TELIT_PRODUCT_LE920A4_1214 0x1214 + +/* 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_ZM8620_X 0x0396 +#define ZTE_PRODUCT_ME3620_MBIM 0x0426 +#define ZTE_PRODUCT_ME3620_X 0x1432 +#define ZTE_PRODUCT_ME3620_L 0x1433 +#define ZTE_PRODUCT_AC2726 0xfff1 +#define ZTE_PRODUCT_MG880 0xfffd +#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 SIMCOM_PRODUCT_SIM7100E 0x9001 /* Yes, ALINK_VENDOR_ID */ +#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 one was sold as the VW and Skoda "Carstick LTE" */ +#define FOUR_G_SYSTEMS_PRODUCT_CARSTICK_LTE 0x7605 +/* 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 +#define FOUR_G_SYSTEMS_PRODUCT_W100 0x9b01 + +/* Fujisoft products */ +#define FUJISOFT_PRODUCT_FS040U 0x9b02 + +/* 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 + +/* Gemalto's Cinterion products (formerly Siemens) */ +#define SIEMENS_VENDOR_ID 0x0681 +#define CINTERION_VENDOR_ID 0x1e2d +#define CINTERION_PRODUCT_HC25_MDMNET 0x0040 +#define CINTERION_PRODUCT_HC25_MDM 0x0047 +#define CINTERION_PRODUCT_HC28_MDMNET 0x004A /* same for HC28J */ +#define CINTERION_PRODUCT_HC28_MDM 0x004C +#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 +#define CINTERION_PRODUCT_EXS82 0x006c +#define CINTERION_PRODUCT_PH8_2RMNET 0x0082 +#define CINTERION_PRODUCT_PH8_AUDIO 0x0083 +#define CINTERION_PRODUCT_AHXX_2RMNET 0x0084 +#define CINTERION_PRODUCT_AHXX_AUDIO 0x0085 +#define CINTERION_PRODUCT_CLS8 0x00b0 +#define CINTERION_PRODUCT_MV31_MBIM 0x00b3 +#define CINTERION_PRODUCT_MV31_RMNET 0x00b7 +#define CINTERION_PRODUCT_MV31_2_MBIM 0x00b8 +#define CINTERION_PRODUCT_MV31_2_RMNET 0x00b9 +#define CINTERION_PRODUCT_MV32_WA 0x00f1 +#define CINTERION_PRODUCT_MV32_WB 0x00f2 +#define CINTERION_PRODUCT_MV32_WA_RMNET 0x00f3 +#define CINTERION_PRODUCT_MV32_WB_RMNET 0x00f4 + +/* 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 +#define CELLIENT_PRODUCT_MPL200 0x9025 + +/* 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_LTE 0x000D +#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 + +/* WeTelecom products */ +#define WETELECOM_VENDOR_ID 0x22de +#define WETELECOM_PRODUCT_WMD200 0x6801 +#define WETELECOM_PRODUCT_6802 0x6802 +#define WETELECOM_PRODUCT_WMD300 0x6803 + +/* OPPO products */ +#define OPPO_VENDOR_ID 0x22d9 +#define OPPO_PRODUCT_R11 0x276c + +/* Sierra Wireless products */ +#define SIERRA_VENDOR_ID 0x1199 +#define SIERRA_PRODUCT_EM9191 0x90d3 +#define SIERRA_PRODUCT_EM9291 0x90e3 + +/* UNISOC (Spreadtrum) products */ +#define UNISOC_VENDOR_ID 0x1782 +/* TOZED LT70-C based on UNISOC SL8563 uses UNISOC's vendor ID */ +#define TOZED_PRODUCT_LT70C 0x4055 +#define UNISOC_PRODUCT_UIS7720 0x4064 +/* Luat Air72*U series based on UNISOC UIS8910 uses UNISOC's vendor ID */ +#define LUAT_PRODUCT_AIR720U 0x4e00 + +/* Device flags */ + +/* Highest interface number which can be used with NCTRL() and RSVD() */ +#define FLAG_IFNUM_MAX 7 + +/* Interface does not support modem-control requests */ +#define NCTRL(ifnum) ((BIT(ifnum) & 0xff) << 8) + +/* Interface is reserved */ +#define RSVD(ifnum) ((BIT(ifnum) & 0xff) << 0) + +/* Interface must have two endpoints */ +#define NUMEP2 BIT(16) + +/* Device needs ZLP */ +#define ZLP BIT(17) + + +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 = RSVD(4) }, + { 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 = RSVD(1) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E173S6, 0xff, 0xff, 0xff), + .driver_info = RSVD(1) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1750, 0xff, 0xff, 0xff), + .driver_info = RSVD(2) }, + { 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 = RSVD(1) | RSVD(2) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K3765, 0xff, 0xff, 0xff), + .driver_info = RSVD(1) | RSVD(2) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0x14ac, 0xff, 0xff, 0xff), /* Huawei E1820 */ + .driver_info = RSVD(1) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K4605, 0xff, 0xff, 0xff), + .driver_info = RSVD(1) | RSVD(2) }, + { 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) }, + + /* Motorola devices */ + { USB_DEVICE_AND_INTERFACE_INFO(0x22b8, 0x2a70, 0xff, 0xff, 0xff) }, /* mdm6600 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x22b8, 0x2e0a, 0xff, 0xff, 0xff) }, /* mdm9600 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x22b8, 0x4281, 0x0a, 0x00, 0xfc) }, /* mdm ram dl */ + { USB_DEVICE_AND_INTERFACE_INFO(0x22b8, 0x900e, 0xff, 0xff, 0xff) }, /* mdm qc dl */ + + { 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_AND_INTERFACE_INFO(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_U620L, 0xff, 0x00, 0x00) }, + + { 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(DELL_VENDOR_ID, DELL_PRODUCT_5821E), + .driver_info = RSVD(0) | RSVD(1) | RSVD(6) }, + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5821E_ESIM), + .driver_info = RSVD(0) | RSVD(1) | RSVD(6) }, + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5829E), + .driver_info = RSVD(0) | RSVD(6) }, + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5829E_ESIM), + .driver_info = RSVD(0) | RSVD(6) }, + { USB_DEVICE_INTERFACE_CLASS(DELL_VENDOR_ID, DELL_PRODUCT_5826E_ESIM, 0xff), + .driver_info = RSVD(1) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(DELL_VENDOR_ID, DELL_PRODUCT_FM101R, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(DELL_VENDOR_ID, DELL_PRODUCT_FM101R_ESIM, 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_AND_INTERFACE_INFO(QUALCOMM_VENDOR_ID, 0x6001, 0xff, 0xff, 0xff), /* 4G LTE usb-modem U901 */ + .driver_info = RSVD(3) }, + { 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 */ + .driver_info = NCTRL(0) | NCTRL(1) | NCTRL(2) | NCTRL(3) | RSVD(4) }, + /* Quectel products using Qualcomm vendor ID */ + { USB_DEVICE(QUALCOMM_VENDOR_ID, QUECTEL_PRODUCT_UC15)}, + { USB_DEVICE(QUALCOMM_VENDOR_ID, QUECTEL_PRODUCT_UC20), + .driver_info = RSVD(4) }, + /* Yuga products use Qualcomm vendor ID */ + { USB_DEVICE(QUALCOMM_VENDOR_ID, YUGA_PRODUCT_CLM920_NC5), + .driver_info = RSVD(1) | RSVD(4) }, + /* u-blox products using Qualcomm vendor ID */ + { USB_DEVICE(QUALCOMM_VENDOR_ID, UBLOX_PRODUCT_R410M), + .driver_info = RSVD(1) | RSVD(3) }, + { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x908b), /* u-blox LARA-R6 00B */ + .driver_info = RSVD(4) }, + { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x90fa), + .driver_info = RSVD(3) }, + /* u-blox products */ + { USB_DEVICE(UBLOX_VENDOR_ID, 0x1311) }, /* u-blox LARA-R6 01B */ + { USB_DEVICE(UBLOX_VENDOR_ID, 0x1312), /* u-blox LARA-R6 01B (RMNET) */ + .driver_info = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(UBLOX_VENDOR_ID, 0x1313, 0xff) }, /* u-blox LARA-R6 01B (ECM) */ + { USB_DEVICE(UBLOX_VENDOR_ID, 0x1341) }, /* u-blox LARA-L6 */ + { USB_DEVICE(UBLOX_VENDOR_ID, 0x1342), /* u-blox LARA-L6 (RMNET) */ + .driver_info = RSVD(4) }, + { USB_DEVICE(UBLOX_VENDOR_ID, 0x1343), /* u-blox LARA-L6 (ECM) */ + .driver_info = RSVD(4) }, + /* Quectel products using Quectel vendor ID */ + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC21, 0xff, 0xff, 0xff), + .driver_info = NUMEP2 }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC21, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC25, 0xff, 0xff, 0xff), + .driver_info = NUMEP2 }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC25, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG91, 0xff, 0xff, 0xff), + .driver_info = NUMEP2 }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG91, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG95, 0xff, 0xff, 0xff), + .driver_info = NUMEP2 }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG95, 0xff, 0, 0) }, + { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, 0x0203, 0xff), /* BG95-M3 */ + .driver_info = ZLP }, + { USB_DEVICE(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_BG96), + .driver_info = RSVD(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EP06, 0xff, 0xff, 0xff), + .driver_info = RSVD(1) | RSVD(2) | RSVD(3) | RSVD(4) | NUMEP2 }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EP06, 0xff, 0, 0) }, + { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05CN, 0xff), + .driver_info = RSVD(6) | ZLP }, + { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05CN_SG, 0xff), + .driver_info = RSVD(6) | ZLP }, + { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05G, 0xff), + .driver_info = RSVD(6) | ZLP }, + { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05GV2, 0xff), + .driver_info = RSVD(4) | ZLP }, + { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05G_CS, 0xff), + .driver_info = RSVD(6) | ZLP }, + { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05G_GR, 0xff), + .driver_info = RSVD(6) | ZLP }, + { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05G_RS, 0xff), + .driver_info = RSVD(6) | ZLP }, + { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM05G_SG, 0xff), + .driver_info = RSVD(6) | ZLP }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_128, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_128, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_128, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_129, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_129, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_129, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12a, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12a, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12a, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12b, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12b, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12b, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12c, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12c, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12c, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LCN, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LCN, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LCN, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LMS, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LMS, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LMS, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LTA, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LTA, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LTA, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LWW, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LWW, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LWW, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM12, 0xff, 0xff, 0xff), + .driver_info = RSVD(1) | RSVD(2) | RSVD(3) | RSVD(4) | NUMEP2 }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM12, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, 0x0620, 0xff, 0xff, 0x30) }, /* EM160R-GL */ + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, 0x0620, 0xff, 0, 0) }, + { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, 0x0700, 0xff), /* BG95 */ + .driver_info = RSVD(3) | ZLP }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500Q, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500Q, 0xff, 0, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500Q, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500Q, 0xff, 0xff, 0x10), + .driver_info = ZLP }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM520N, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM520N, 0xff, 0, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM520N, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, 0x0900, 0xff, 0, 0), /* RM500U-CN */ + .driver_info = ZLP }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200A, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200U, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200S_CN, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200T, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG912Y, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG916Q, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500K, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x40) }, + + { 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 = RSVD(0) }, + { 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 = RSVD(0) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CMU_301), + .driver_info = RSVD(0) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_628), + .driver_info = RSVD(0) }, + { 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 = RSVD(0) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7002), + .driver_info = RSVD(0) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_629K), + .driver_info = RSVD(4) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7004), + .driver_info = RSVD(3) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7005) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CGU_629), + .driver_info = RSVD(5) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_629S), + .driver_info = RSVD(4) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_CHU_720I), + .driver_info = RSVD(0) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7212), + .driver_info = RSVD(0) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7213), + .driver_info = RSVD(0) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7251), + .driver_info = RSVD(1) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7252), + .driver_info = RSVD(1) }, + { USB_DEVICE(CMOTECH_VENDOR_ID, CMOTECH_PRODUCT_7253), + .driver_info = RSVD(1) }, + { 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_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0990, 0xff, 0xff, 0x30), /* Telit FE990D50 (RNDIS) */ + .driver_info = NCTRL(6) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0990, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0990, 0xff, 0xff, 0x60) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0991, 0xff, 0xff, 0x30), /* Telit FE990D50 (rmnet) */ + .driver_info = NCTRL(5) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0991, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0991, 0xff, 0xff, 0x60) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0992, 0xff, 0xff, 0x30), /* Telit FE990D50 (MBIM) */ + .driver_info = NCTRL(6) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0992, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0992, 0xff, 0xff, 0x60) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0993, 0xff, 0xff, 0x30), /* Telit FE990D50 (ECM) */ + .driver_info = NCTRL(6) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0993, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x0993, 0xff, 0xff, 0x60) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1031, 0xff), /* Telit LE910C1-EUX */ + .driver_info = NCTRL(0) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1033, 0xff), /* Telit LE910C1-EUX (ECM) */ + .driver_info = NCTRL(0) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1034, 0xff), /* Telit LE910C4-WWX (rmnet) */ + .driver_info = RSVD(2) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1035, 0xff) }, /* Telit LE910C4-WWX (ECM) */ + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1036, 0xff) }, /* Telit LE910C4-WWX */ + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1037, 0xff), /* Telit LE910C4-WWX (rmnet) */ + .driver_info = NCTRL(0) | NCTRL(1) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1038, 0xff), /* Telit LE910C4-WWX (rmnet) */ + .driver_info = NCTRL(0) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x103b, 0xff), /* Telit LE910C4-WWX */ + .driver_info = NCTRL(0) | NCTRL(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x103c, 0xff), /* Telit LE910C4-WWX */ + .driver_info = NCTRL(0) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG0), + .driver_info = RSVD(0) | RSVD(1) | NCTRL(2) | RSVD(3) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG1), + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG2), + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG3), + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, TELIT_PRODUCT_LE922_USBCFG5, 0xff), + .driver_info = RSVD(0) | RSVD(1) | NCTRL(2) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1050, 0xff), /* Telit FN980 (rmnet) */ + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1051, 0xff), /* Telit FN980 (MBIM) */ + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1052, 0xff), /* Telit FN980 (RNDIS) */ + .driver_info = NCTRL(2) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1053, 0xff), /* Telit FN980 (ECM) */ + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1054, 0xff), /* Telit FT980-KS */ + .driver_info = NCTRL(2) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1055, 0xff), /* Telit FN980 (PCIe) */ + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1056, 0xff), /* Telit FD980 */ + .driver_info = NCTRL(2) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1057, 0xff), /* Telit FN980 */ + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1058, 0xff), /* Telit FN980 (PCIe) */ + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1060, 0xff), /* Telit LN920 (rmnet) */ + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1061, 0xff), /* Telit LN920 (MBIM) */ + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1062, 0xff), /* Telit LN920 (RNDIS) */ + .driver_info = NCTRL(2) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1063, 0xff), /* Telit LN920 (ECM) */ + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1070, 0xff), /* Telit FN990A (rmnet) */ + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1071, 0xff), /* Telit FN990A (MBIM) */ + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1072, 0xff), /* Telit FN990A (RNDIS) */ + .driver_info = NCTRL(2) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1073, 0xff), /* Telit FN990A (ECM) */ + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1074, 0xff), /* Telit FN990A (MBIM) */ + .driver_info = NCTRL(5) | RSVD(6) | RSVD(7) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1075, 0xff), /* Telit FN990A (PCIe) */ + .driver_info = RSVD(0) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1077, 0xff), /* Telit FN990A (rmnet + audio) */ + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1078, 0xff), /* Telit FN990A (MBIM + audio) */ + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1079, 0xff), /* Telit FN990A (RNDIS + audio) */ + .driver_info = NCTRL(2) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1080, 0xff), /* Telit FE990A (rmnet) */ + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1081, 0xff), /* Telit FE990A (MBIM) */ + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1082, 0xff), /* Telit FE990A (RNDIS) */ + .driver_info = NCTRL(2) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1083, 0xff), /* Telit FE990A (ECM) */ + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a0, 0xff), /* Telit FN20C04 (rmnet) */ + .driver_info = RSVD(0) | NCTRL(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a1, 0xff), /* Telit FN20C04 (RNDIS) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a2, 0xff), /* Telit FN920C04 (MBIM) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a3, 0xff), /* Telit FN920C04 (ECM) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a4, 0xff), /* Telit FN20C04 (rmnet) */ + .driver_info = RSVD(0) | NCTRL(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a6, 0xff), /* Telit FN920C04 (RNDIS) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a7, 0xff), /* Telit FN920C04 (MBIM) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a8, 0xff), /* Telit FN920C04 (ECM) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a9, 0xff), /* Telit FN20C04 (rmnet) */ + .driver_info = RSVD(0) | NCTRL(2) | RSVD(3) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10aa, 0xff), /* Telit FN920C04 (MBIM) */ + .driver_info = NCTRL(3) | RSVD(4) | RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10ab, 0xff), /* Telit FN920C04 (RNDIS) */ + .driver_info = NCTRL(3) | RSVD(4) | RSVD(5) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b0, 0xff, 0xff, 0x30), /* Telit FE990B (rmnet) */ + .driver_info = NCTRL(5) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b0, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b0, 0xff, 0xff, 0x60) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b1, 0xff, 0xff, 0x30), /* Telit FE990B (MBIM) */ + .driver_info = NCTRL(6) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b1, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b1, 0xff, 0xff, 0x60) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b2, 0xff, 0xff, 0x30), /* Telit FE990B (RNDIS) */ + .driver_info = NCTRL(6) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b2, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b2, 0xff, 0xff, 0x60) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b3, 0xff, 0xff, 0x30), /* Telit FE990B (ECM) */ + .driver_info = NCTRL(6) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b3, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10b3, 0xff, 0xff, 0x60) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c0, 0xff), /* Telit FE910C04 (rmnet) */ + .driver_info = RSVD(0) | NCTRL(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c1, 0xff), /* Telit FE910C04 (RNDIS) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c2, 0xff), /* Telit FE910C04 (MBIM) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c3, 0xff), /* Telit FE910C04 (ECM) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c4, 0xff), /* Telit FE910C04 (rmnet) */ + .driver_info = RSVD(0) | NCTRL(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c5, 0xff), /* Telit FE910C04 (RNDIS) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c6, 0xff), /* Telit FE910C04 (MBIM) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10c7, 0xff, 0xff, 0x30), /* Telit FE910C04 (ECM) */ + .driver_info = NCTRL(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10c7, 0xff, 0xff, 0x40) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c8, 0xff), /* Telit FE910C04 (rmnet) */ + .driver_info = RSVD(0) | NCTRL(2) | RSVD(3) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10c9, 0xff), /* Telit FE910C04 (MBIM) */ + .driver_info = NCTRL(3) | RSVD(4) | RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10cb, 0xff), /* Telit FE910C04 (RNDIS) */ + .driver_info = NCTRL(3) | RSVD(4) | RSVD(5) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d0, 0xff, 0xff, 0x30), /* Telit FN990B (rmnet) */ + .driver_info = NCTRL(5) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d0, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d0, 0xff, 0xff, 0x60) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d1, 0xff, 0xff, 0x30), /* Telit FN990B (MBIM) */ + .driver_info = NCTRL(6) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d1, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d1, 0xff, 0xff, 0x60) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d2, 0xff, 0xff, 0x30), /* Telit FN990B (RNDIS) */ + .driver_info = NCTRL(6) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d2, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d2, 0xff, 0xff, 0x60) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d3, 0xff, 0xff, 0x30), /* Telit FN990B (ECM) */ + .driver_info = NCTRL(6) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d3, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x10d3, 0xff, 0xff, 0x60) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_ME910), + .driver_info = NCTRL(0) | RSVD(1) | RSVD(3) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_ME910_DUAL_MODEM), + .driver_info = NCTRL(0) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1102, 0xff), /* Telit ME910 (ECM) */ + .driver_info = NCTRL(0) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x110a, 0xff), /* Telit ME910G1 */ + .driver_info = NCTRL(0) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x110b, 0xff), /* Telit ME910G1 (ECM) */ + .driver_info = NCTRL(0) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE910), + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1203, 0xff), /* Telit LE910Cx (RNDIS) */ + .driver_info = NCTRL(2) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1204, 0xff), /* Telit LE910Cx (MBIM) */ + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE910_USBCFG4), + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920), + .driver_info = NCTRL(0) | RSVD(1) | RSVD(5) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1207) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1208), + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1211), + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1212), + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1213, 0xff) }, + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_LE920A4_1214), + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) | RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1230, 0xff), /* Telit LE910Cx (rmnet) */ + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1231, 0xff), /* Telit LE910Cx (RNDIS) */ + .driver_info = NCTRL(2) | RSVD(3) }, + { USB_DEVICE_AND_INTERFACE_INFO(TELIT_VENDOR_ID, 0x1250, 0xff, 0x00, 0x00) }, /* Telit LE910Cx (rmnet) */ + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1251, 0xff) }, /* Telit LE910Cx (RNDIS) */ + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1252, 0xff) }, /* Telit LE910Cx (MBIM) */ + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1253, 0xff) }, /* Telit LE910Cx (ECM) */ + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1254, 0xff) }, /* Telit LE910Cx */ + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1255, 0xff) }, /* Telit LE910Cx */ + { USB_DEVICE(TELIT_VENDOR_ID, 0x1260), + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) }, + { USB_DEVICE(TELIT_VENDOR_ID, 0x1261), + .driver_info = NCTRL(0) | RSVD(1) | RSVD(2) }, + { USB_DEVICE(TELIT_VENDOR_ID, 0x1900), /* Telit LN940 (QMI) */ + .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1901, 0xff), /* Telit LN940 (MBIM) */ + .driver_info = NCTRL(0) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x3000, 0xff), /* Telit FN912 */ + .driver_info = RSVD(0) | NCTRL(3) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x3001, 0xff), /* Telit FN912 */ + .driver_info = RSVD(0) | NCTRL(2) | RSVD(3) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x7010, 0xff), /* Telit LE910-S1 (RNDIS) */ + .driver_info = NCTRL(2) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x7011, 0xff), /* Telit LE910-S1 (ECM) */ + .driver_info = NCTRL(2) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x701a, 0xff), /* Telit LE910R1 (RNDIS) */ + .driver_info = NCTRL(2) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x701b, 0xff), /* Telit LE910R1 (ECM) */ + .driver_info = NCTRL(2) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x9000, 0xff), /* Telit generic core-dump device */ + .driver_info = NCTRL(0) }, + { USB_DEVICE(TELIT_VENDOR_ID, 0x9010), /* Telit SBL FN980 flashing device */ + .driver_info = NCTRL(0) | ZLP }, + { USB_DEVICE(TELIT_VENDOR_ID, 0x9200), /* Telit LE910S1 flashing device */ + .driver_info = NCTRL(0) | ZLP }, + { USB_DEVICE(TELIT_VENDOR_ID, 0x9201), /* Telit LE910R1 flashing device */ + .driver_info = NCTRL(0) | ZLP }, + { 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 = RSVD(1) }, + { 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 = RSVD(1) }, + { 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 = RSVD(3) }, + { 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 = RSVD(3) }, + { 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 = RSVD(4) }, + { 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 = RSVD(1) }, + { 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 = NCTRL(0) | NCTRL(1) | RSVD(4) }, + { 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 = NCTRL(0) | NCTRL(1) }, + { 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 = RSVD(4) }, + { 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 = RSVD(5) }, + { 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 = RSVD(4) }, + { 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 = RSVD(1) }, + { 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 = RSVD(4) }, + { 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 = RSVD(4) }, + { 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 = RSVD(4) }, + { 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 = RSVD(5) }, + { 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 = RSVD(5) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0121, 0xff, 0xff, 0xff), + .driver_info = RSVD(5) }, + { 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 = RSVD(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0124, 0xff, 0xff, 0xff), + .driver_info = RSVD(5) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0125, 0xff, 0xff, 0xff), + .driver_info = RSVD(6) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0126, 0xff, 0xff, 0xff), + .driver_info = RSVD(5) }, + { 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 = RSVD(5) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0158, 0xff, 0xff, 0xff), + .driver_info = RSVD(3) }, + { 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 = RSVD(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0189, 0xff, 0xff, 0xff), + .driver_info = RSVD(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0191, 0xff, 0xff, 0xff), /* ZTE EuFi890 */ + .driver_info = RSVD(4) }, + { 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 = RSVD(1) }, + { 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 = RSVD(3) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0265, 0xff, 0xff, 0xff), /* ONDA MT8205 */ + .driver_info = RSVD(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0284, 0xff, 0xff, 0xff), /* ZTE MF880 */ + .driver_info = RSVD(4) }, + { 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 = RSVD(4) }, + { 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 = RSVD(4) }, + { 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_INTERFACE_CLASS(ZTE_VENDOR_ID, 0x0601, 0xff) }, /* GosunCn ZTE WeLink ME3630 (RNDIS mode) */ + { USB_DEVICE_INTERFACE_CLASS(ZTE_VENDOR_ID, 0x0602, 0xff) }, /* GosunCn ZTE WeLink ME3630 (MBIM mode) */ + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1008, 0xff, 0xff, 0xff), + .driver_info = RSVD(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1010, 0xff, 0xff, 0xff), + .driver_info = RSVD(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1012, 0xff, 0xff, 0xff), + .driver_info = RSVD(4) }, + { 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 = RSVD(2) }, + { 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 = RSVD(4) }, + { 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 = RSVD(4) }, + { 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 = RSVD(4) }, + { 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 = RSVD(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1255, 0xff, 0xff, 0xff), + .driver_info = RSVD(3) | RSVD(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1256, 0xff, 0xff, 0xff), + .driver_info = RSVD(4) }, + { 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 = RSVD(5) }, + { 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(ZTE_VENDOR_ID, 0x1275), /* ZTE P685M */ + .driver_info = RSVD(3) | RSVD(4) }, + { 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 = RSVD(2) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1402, 0xff, 0xff, 0xff), + .driver_info = RSVD(2) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1424, 0xff, 0xff, 0xff), + .driver_info = RSVD(2) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1425, 0xff, 0xff, 0xff), + .driver_info = RSVD(2) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1426, 0xff, 0xff, 0xff), /* ZTE MF91 */ + .driver_info = RSVD(2) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1428, 0xff, 0xff, 0xff), /* Telewell TW-LTE 4G v2 */ + .driver_info = RSVD(2) }, + { USB_DEVICE_INTERFACE_CLASS(ZTE_VENDOR_ID, 0x1476, 0xff) }, /* GosunCn ZTE WeLink ME3630 (ECM/NCM mode) */ + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1481, 0xff, 0x00, 0x00) }, /* ZTE MF871A */ + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1485, 0xff, 0xff, 0xff), /* ZTE MF286D */ + .driver_info = RSVD(5) }, + { 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 = NCTRL(0) | NCTRL(1) | NCTRL(2) | RSVD(4) }, + { 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, 0x0130, 0xff, 0xff, 0xff), + .driver_info = RSVD(1) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0133, 0xff, 0xff, 0xff), + .driver_info = RSVD(3) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0141, 0xff, 0xff, 0xff), + .driver_info = RSVD(5) }, + { 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 = RSVD(4) }, + { 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 = RSVD(3) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0178, 0xff, 0xff, 0xff), + .driver_info = RSVD(3) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff42, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff43, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff44, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff45, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff46, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff47, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff48, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff49, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff4a, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff4b, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff4c, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff4d, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff4e, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff4f, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff50, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff51, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff52, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff53, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff54, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff55, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff56, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff57, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff58, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff59, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff5a, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff5b, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff5c, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff5d, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff5e, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff5f, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff60, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff61, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff62, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff63, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff64, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff65, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff66, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff67, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff68, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff69, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff6a, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff6b, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff6c, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff6d, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff6e, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff6f, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff70, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff71, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff72, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff73, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff74, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff75, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff76, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff77, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff78, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff79, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff7a, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff7b, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff7c, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff7d, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff7e, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff7f, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff80, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff81, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff82, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff83, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff84, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff85, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff86, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff87, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff88, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff89, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff8a, 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, 0xff9f, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa0, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa1, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa2, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa3, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa4, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa5, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa6, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa7, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa8, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffa9, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffaa, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffab, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffac, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffae, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffaf, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb0, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb1, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb2, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb3, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb4, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb5, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb6, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb7, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb8, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffb9, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffba, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffbb, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffbc, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffbd, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffbe, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffbf, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc0, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc1, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc2, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc3, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc4, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc5, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc6, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc7, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc8, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffc9, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffca, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffcb, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffcc, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffcd, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffce, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffcf, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffd0, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffd1, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffd2, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffd3, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffd4, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffd5, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffe9, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffec, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xffee, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xfff6, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xfff7, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xfff8, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xfff9, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xfffb, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xfffc, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MG880, 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 = NCTRL(1) | NCTRL(2) | NCTRL(3) | NCTRL(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_AD3812, 0xff, 0xff, 0xff), + .driver_info = NCTRL(0) | NCTRL(1) | NCTRL(2) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MC2716, 0xff, 0xff, 0xff), + .driver_info = NCTRL(1) | NCTRL(2) | NCTRL(3) }, + { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_ME3620_L), + .driver_info = RSVD(3) | RSVD(4) | RSVD(5) }, + { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_ME3620_MBIM), + .driver_info = RSVD(2) | RSVD(3) | RSVD(4) }, + { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_ME3620_X), + .driver_info = RSVD(3) | RSVD(4) | RSVD(5) }, + { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_ZM8620_X), + .driver_info = RSVD(3) | RSVD(4) | RSVD(5) }, + { 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(ALINK_VENDOR_ID, SIMCOM_PRODUCT_SIM7100E), + .driver_info = RSVD(5) | RSVD(6) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9003, 0xff) }, /* Simcom SIM7500/SIM7600 MBIM mode */ + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9011, 0xff), /* Simcom SIM7500/SIM7600 RNDIS mode */ + .driver_info = RSVD(7) }, + { USB_DEVICE(0x1e0e, 0x9071), /* Simcom SIM8230 RMNET mode */ + .driver_info = RSVD(3) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9078, 0xff), /* Simcom SIM8230 ECM mode */ + .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x907b, 0xff), /* Simcom SIM8230 RNDIS mode */ + .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9205, 0xff) }, /* Simcom SIM7070/SIM7080/SIM7090 AT+ECM mode */ + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9206, 0xff) }, /* Simcom SIM7070/SIM7080/SIM7090 AT-only mode */ + { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_X060S_X200), + .driver_info = NCTRL(0) | NCTRL(1) | RSVD(4) }, + { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_X220_X500D), + .driver_info = RSVD(6) }, + { USB_DEVICE(ALCATEL_VENDOR_ID, 0x0052), + .driver_info = RSVD(6) }, + { USB_DEVICE(ALCATEL_VENDOR_ID, 0x00b6), + .driver_info = RSVD(3) }, + { USB_DEVICE(ALCATEL_VENDOR_ID, 0x00b7), + .driver_info = RSVD(5) }, + { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_L100V), + .driver_info = RSVD(4) }, + { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_L800MA), + .driver_info = RSVD(2) }, + { 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_CARSTICK_LTE), + .driver_info = RSVD(0) }, + { USB_DEVICE(LONGCHEER_VENDOR_ID, FOUR_G_SYSTEMS_PRODUCT_W14), + .driver_info = NCTRL(0) | NCTRL(1) }, + { USB_DEVICE(LONGCHEER_VENDOR_ID, FOUR_G_SYSTEMS_PRODUCT_W100), + .driver_info = NCTRL(1) | NCTRL(2) | RSVD(3) }, + {USB_DEVICE(LONGCHEER_VENDOR_ID, FUJISOFT_PRODUCT_FS040U), + .driver_info = RSVD(3)}, + { USB_DEVICE_INTERFACE_CLASS(LONGCHEER_VENDOR_ID, SPEEDUP_PRODUCT_SU9800, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(LONGCHEER_VENDOR_ID, 0x9801, 0xff), + .driver_info = RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(LONGCHEER_VENDOR_ID, 0x9803, 0xff), + .driver_info = RSVD(4) }, + { USB_DEVICE(LONGCHEER_VENDOR_ID, 0x9b05), /* Longsung U8300 */ + .driver_info = RSVD(4) | RSVD(5) }, + { USB_DEVICE(LONGCHEER_VENDOR_ID, 0x9b3c), /* Longsung U9300 */ + .driver_info = RSVD(0) | RSVD(4) }, + { 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 = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_AHXX, 0xff) }, + { USB_DEVICE(CINTERION_VENDOR_ID, CINTERION_PRODUCT_PLXX), + .driver_info = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_PH8_2RMNET, 0xff), + .driver_info = RSVD(4) | RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_PH8_AUDIO, 0xff), + .driver_info = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_AHXX_2RMNET, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_AHXX_AUDIO, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_CLS8, 0xff), + .driver_info = RSVD(0) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_EXS82, 0xff) }, + { 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_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV31_MBIM, 0xff), + .driver_info = RSVD(3)}, + { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV31_RMNET, 0xff), + .driver_info = RSVD(0)}, + { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV31_2_MBIM, 0xff), + .driver_info = RSVD(3)}, + { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV31_2_RMNET, 0xff), + .driver_info = RSVD(0)}, + { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV32_WA, 0xff), + .driver_info = RSVD(3)}, + { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV32_WA_RMNET, 0xff), + .driver_info = RSVD(0) }, + { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV32_WB, 0xff), + .driver_info = RSVD(3)}, + { USB_DEVICE_INTERFACE_CLASS(CINTERION_VENDOR_ID, CINTERION_PRODUCT_MV32_WB_RMNET, 0xff), + .driver_info = RSVD(0) }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD100), + .driver_info = RSVD(4) }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD120), + .driver_info = RSVD(4) }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD140), + .driver_info = RSVD(4) }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD145) }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD155), + .driver_info = RSVD(6) }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD200), + .driver_info = RSVD(6) }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD160), + .driver_info = RSVD(6) }, + { USB_DEVICE(OLIVETTI_VENDOR_ID, OLIVETTI_PRODUCT_OLICARD500), + .driver_info = RSVD(4) }, + { 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_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x7126, 0xff, 0x00, 0x00), + .driver_info = NCTRL(2) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x7127, 0xff, 0x00, 0x00), + .driver_info = NCTRL(2) | NCTRL(3) | NCTRL(4) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x7129, 0xff, 0x00, 0x00), /* MediaTek T7XX */ + .driver_info = NCTRL(2) | NCTRL(3) | NCTRL(4) }, + { USB_DEVICE(CELLIENT_VENDOR_ID, CELLIENT_PRODUCT_MEN200) }, + { USB_DEVICE(CELLIENT_VENDOR_ID, CELLIENT_PRODUCT_MPL200), + .driver_info = RSVD(1) | RSVD(4) }, + { USB_DEVICE(PETATEL_VENDOR_ID, PETATEL_PRODUCT_NP10T_600A) }, + { USB_DEVICE(PETATEL_VENDOR_ID, PETATEL_PRODUCT_NP10T_600E) }, + { USB_DEVICE_AND_INTERFACE_INFO(TPLINK_VENDOR_ID, TPLINK_PRODUCT_LTE, 0xff, 0x00, 0x00) }, /* TP-Link LTE Module */ + { USB_DEVICE(TPLINK_VENDOR_ID, TPLINK_PRODUCT_MA180), + .driver_info = RSVD(4) }, + { USB_DEVICE(TPLINK_VENDOR_ID, 0x9000), /* TP-Link MA260 */ + .driver_info = RSVD(4) }, + { USB_DEVICE(CHANGHONG_VENDOR_ID, CHANGHONG_PRODUCT_CH690) }, + { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7d01, 0xff) }, /* D-Link DWM-156 (variant) */ + { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7d02, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7d03, 0xff) }, + { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7d04, 0xff), /* D-Link DWM-158 */ + .driver_info = RSVD(4) | RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7d0e, 0xff) }, /* D-Link DWM-157 C1 */ + { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7e19, 0xff), /* D-Link DWM-221 B1 */ + .driver_info = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7e35, 0xff), /* D-Link DWM-222 */ + .driver_info = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x2001, 0x7e3d, 0xff), /* D-Link DWM-222 A2 */ + .driver_info = RSVD(4) }, + { 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_AND_INTERFACE_INFO(0x07d1, 0x7e11, 0xff, 0xff, 0xff) }, /* D-Link DWM-156/A3 */ + { USB_DEVICE_INTERFACE_CLASS(0x1435, 0xd191, 0xff), /* Wistron Neweb D19Q1 */ + .driver_info = RSVD(1) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x1690, 0x7588, 0xff), /* ASKEY WWHC050 */ + .driver_info = RSVD(1) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x2020, 0x2031, 0xff), /* Olicard 600 */ + .driver_info = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x2020, 0x2033, 0xff), /* BroadMobi BM806U */ + .driver_info = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x2020, 0x2060, 0xff), /* BroadMobi BM818 */ + .driver_info = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x2020, 0x4000, 0xff) }, /* OLICARD300 - MT6225 */ + { USB_DEVICE(INOVIA_VENDOR_ID, INOVIA_SEW858) }, + { USB_DEVICE(VIATELECOM_VENDOR_ID, VIATELECOM_PRODUCT_CDS7) }, + { USB_DEVICE_AND_INTERFACE_INFO(WETELECOM_VENDOR_ID, WETELECOM_PRODUCT_WMD200, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(WETELECOM_VENDOR_ID, WETELECOM_PRODUCT_6802, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(WETELECOM_VENDOR_ID, WETELECOM_PRODUCT_WMD300, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0x421d, 0xff, 0xff, 0xff) }, /* HP lt2523 (Novatel E371) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0xa31d, 0xff, 0x06, 0x10) }, /* HP lt4132 (Huawei ME906s-158) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0xa31d, 0xff, 0x06, 0x12) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0xa31d, 0xff, 0x06, 0x13) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0xa31d, 0xff, 0x06, 0x14) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0xa31d, 0xff, 0x06, 0x1b) }, + { USB_DEVICE(0x0489, 0xe0b4), /* Foxconn T77W968 */ + .driver_info = RSVD(0) | RSVD(1) | RSVD(6) }, + { USB_DEVICE(0x0489, 0xe0b5), /* Foxconn T77W968 ESIM */ + .driver_info = RSVD(0) | RSVD(1) | RSVD(6) }, + { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe0da, 0xff), /* Foxconn T99W265 MBIM variant */ + .driver_info = RSVD(3) | RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe0db, 0xff), /* Foxconn T99W265 MBIM */ + .driver_info = RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe0ee, 0xff), /* Foxconn T99W368 MBIM */ + .driver_info = RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe0f0, 0xff), /* Foxconn T99W373 MBIM */ + .driver_info = RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe123, 0xff), /* Foxconn T99W760 MBIM */ + .driver_info = RSVD(3) }, + { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe145, 0xff), /* Foxconn T99W651 RNDIS */ + .driver_info = RSVD(5) | RSVD(6) }, + { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe15f, 0xff), /* Foxconn T99W709 */ + .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe167, 0xff), /* Foxconn T99W640 MBIM */ + .driver_info = RSVD(3) }, + { USB_DEVICE(0x1508, 0x1001), /* Fibocom NL668 (IOT version) */ + .driver_info = RSVD(4) | RSVD(5) | RSVD(6) }, + { USB_DEVICE(0x1782, 0x4d10) }, /* Fibocom L610 (AT mode) */ + { USB_DEVICE_INTERFACE_CLASS(0x1782, 0x4d11, 0xff) }, /* Fibocom L610 (ECM/RNDIS mode) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2cb7, 0x0001, 0xff, 0xff, 0xff) }, /* Fibocom L716-EU (ECM/RNDIS mode) */ + { USB_DEVICE(0x2cb7, 0x0104), /* Fibocom NL678 series */ + .driver_info = RSVD(4) | RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0105, 0xff), /* Fibocom NL678 series */ + .driver_info = RSVD(6) }, + { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0106, 0xff) }, /* Fibocom MA510 (ECM mode w/ diag intf.) */ + { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x010a, 0xff) }, /* Fibocom MA510 (ECM mode) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2cb7, 0x010b, 0xff, 0xff, 0x30) }, /* Fibocom FG150 Diag */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2cb7, 0x010b, 0xff, 0, 0) }, /* Fibocom FG150 AT */ + { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0111, 0xff) }, /* Fibocom FM160 (MBIM mode) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2cb7, 0x0112, 0xff, 0xff, 0x30) }, /* Fibocom FG132 Diag */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2cb7, 0x0112, 0xff, 0xff, 0x40) }, /* Fibocom FG132 AT */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2cb7, 0x0112, 0xff, 0, 0) }, /* Fibocom FG132 NMEA */ + { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0115, 0xff), /* Fibocom FM135 (laptop MBIM) */ + .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a0, 0xff) }, /* Fibocom NL668-AM/NL652-EU (laptop MBIM) */ + { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a2, 0xff) }, /* Fibocom FM101-GL (laptop MBIM) */ + { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a3, 0xff) }, /* Fibocom FM101-GL (laptop MBIM) */ + { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a4, 0xff), /* Fibocom FM101-GL (laptop MBIM) */ + .driver_info = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0a04, 0xff) }, /* Fibocom FM650-CN (ECM mode) */ + { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0a05, 0xff) }, /* Fibocom FM650-CN (NCM mode) */ + { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0a06, 0xff) }, /* Fibocom FM650-CN (RNDIS mode) */ + { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0a07, 0xff) }, /* Fibocom FM650-CN (MBIM mode) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d41, 0xff, 0, 0) }, /* MeiG Smart SLM320 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d57, 0xff, 0, 0) }, /* MeiG Smart SLM770A */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0, 0) }, /* MeiG Smart SRM815 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0x10, 0x02) }, /* MeiG Smart SLM828 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0x10, 0x03) }, /* MeiG Smart SLM828 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0xff, 0x30) }, /* MeiG Smart SRM815 and SRM825L */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0xff, 0x40) }, /* MeiG Smart SRM825L */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0xff, 0x60) }, /* MeiG Smart SRM825L */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d38, 0xff, 0xff, 0x30) }, /* MeiG Smart SRM825WN (Diag) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d38, 0xff, 0xff, 0x40) }, /* MeiG Smart SRM825WN (AT) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d38, 0xff, 0xff, 0x60) }, /* MeiG Smart SRM825WN (NMEA) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d63, 0xff, 0xff, 0x30) }, /* MeiG SRM813Q (Diag) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d63, 0xff, 0xff, 0x40) }, /* MeiG SRM813Q (AT) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d64, 0xff, 0xff, 0x30) }, /* MeiG SRM813Q (Diag) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d64, 0xff, 0xff, 0x40) }, /* MeiG SRM813Q (AT) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d64, 0xff, 0xff, 0x60) }, /* MeiG SRM813Q (NMEA) */ + + { USB_DEVICE_INTERFACE_CLASS(0x2df3, 0x9d03, 0xff) }, /* LongSung M5710 */ + { USB_DEVICE_INTERFACE_CLASS(0x305a, 0x1404, 0xff) }, /* GosunCn GM500 RNDIS */ + { USB_DEVICE_INTERFACE_CLASS(0x305a, 0x1405, 0xff) }, /* GosunCn GM500 MBIM */ + { USB_DEVICE_INTERFACE_CLASS(0x305a, 0x1406, 0xff) }, /* GosunCn GM500 ECM/NCM */ + { USB_DEVICE(0x33f8, 0x0104), /* Rolling RW101-GL (laptop RMNET) */ + .driver_info = RSVD(4) | RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x0115, 0xff), /* Rolling RW135-GL (laptop MBIM) */ + .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x01a2, 0xff) }, /* Rolling RW101-GL (laptop MBIM) */ + { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x01a3, 0xff) }, /* Rolling RW101-GL (laptop MBIM) */ + { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x01a4, 0xff), /* Rolling RW101-GL (laptop MBIM) */ + .driver_info = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x01a8, 0xff), /* Rolling RW101R-GL (laptop MBIM) */ + .driver_info = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x01a9, 0xff), /* Rolling RW101R-GL (laptop MBIM) */ + .driver_info = RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x0301, 0xff) }, /* Rolling RW101R-GL (laptop MBIM) */ + { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x0302, 0xff) }, /* Rolling RW101R-GL (laptop MBIM) */ + { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x0802, 0xff), /* Rolling RW350-GL (laptop MBIM) */ + .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x1003, 0xff), /* Rolling RW135R-GL (laptop MBIM) */ + .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x3466, 0x3301, 0xff) }, /* TDTECH MT5710-CN */ + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for Global */ + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0101, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WRD for Global SKU */ + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0101, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0101, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0106, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WRD for China SKU */ + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0106, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0106, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0111, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for SA */ + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0111, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0111, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0112, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for EU */ + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0112, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0112, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0113, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for NA */ + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0113, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0113, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0115, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for China EDU */ + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0115, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0115, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0116, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for Golbal EDU */ + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0116, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0116, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010a, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WRD for WWAN Ready */ + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010a, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010a, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010b, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for WWAN Ready */ + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010b, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010b, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010c, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WRD for WWAN Ready */ + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010c, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010c, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010d, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for WWAN Ready */ + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010d, 0xff, 0x00, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x010d, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(OPPO_VENDOR_ID, OPPO_PRODUCT_R11, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(SIERRA_VENDOR_ID, SIERRA_PRODUCT_EM9191, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(SIERRA_VENDOR_ID, SIERRA_PRODUCT_EM9191, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(SIERRA_VENDOR_ID, SIERRA_PRODUCT_EM9191, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(SIERRA_VENDOR_ID, SIERRA_PRODUCT_EM9291, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(SIERRA_VENDOR_ID, SIERRA_PRODUCT_EM9291, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(UNISOC_VENDOR_ID, TOZED_PRODUCT_LT70C, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(UNISOC_VENDOR_ID, UNISOC_PRODUCT_UIS7720, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(UNISOC_VENDOR_ID, LUAT_PRODUCT_AIR720U, 0xff, 0, 0) }, + { USB_DEVICE_INTERFACE_CLASS(0x1bbb, 0x0530, 0xff), /* TCL IK512 MBIM */ + .driver_info = NCTRL(1) }, + { USB_DEVICE_INTERFACE_CLASS(0x1bbb, 0x0640, 0xff), /* TCL IK512 ECM */ + .driver_info = NCTRL(3) }, + { USB_DEVICE_INTERFACE_CLASS(0x2949, 0x8700, 0xff) }, /* Neoway N723-EA */ + { } /* 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 = { + .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, + .tiocmget = usb_wwan_tiocmget, + .tiocmset = usb_wwan_tiocmset, + .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 +}; + +module_usb_serial_driver(serial_drivers, option_ids); + +static bool iface_is_reserved(unsigned long device_flags, u8 ifnum) +{ + if (ifnum > FLAG_IFNUM_MAX) + return false; + + return device_flags & RSVD(ifnum); +} + +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; + unsigned long device_flags = id->driver_info; + + /* Never bind to the CD-Rom emulation interface */ + if (iface_desc->bInterfaceClass == USB_CLASS_MASS_STORAGE) + 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 (iface_is_reserved(device_flags, iface_desc->bInterfaceNumber)) + return -ENODEV; + + /* + * Allow matching on bNumEndpoints for devices whose interface numbers + * can change (e.g. Quectel EP06). + */ + if (device_flags & NUMEP2 && iface_desc->bNumEndpoints != 2) + return -ENODEV; + + /* Store the device flags so we can use them during attach. */ + usb_set_serial_data(serial, (void *)device_flags); + + return 0; +} + +static bool iface_no_modem_control(unsigned long device_flags, u8 ifnum) +{ + if (ifnum > FLAG_IFNUM_MAX) + return false; + + return device_flags & NCTRL(ifnum); +} + +static int option_attach(struct usb_serial *serial) +{ + struct usb_interface_descriptor *iface_desc; + struct usb_wwan_intf_private *data; + unsigned long device_flags; + + data = kzalloc_obj(struct usb_wwan_intf_private); + if (!data) + return -ENOMEM; + + /* Retrieve device flags stored at probe. */ + device_flags = (unsigned long)usb_get_serial_data(serial); + + iface_desc = &serial->interface->cur_altsetting->desc; + + if (!iface_no_modem_control(device_flags, iface_desc->bInterfaceNumber)) + data->use_send_setup = 1; + + if (device_flags & ZLP) + data->use_zlp = 1; + + 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); + + 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 = urb->transfer_buffer; + + if (!req_pkt) { + dev_dbg(dev, "%s: NULL req_pkt\n", __func__); + return; + } + + if (urb->actual_length < sizeof(*req_pkt)) { + dev_err(dev, "%s: short packet: %u bytes\n", __func__, + urb->actual_length); + return; + } + + if ((req_pkt->bRequestType == 0xA1) && + (req_pkt->bRequest == 0x20)) { + unsigned char signals; + int old_dcd_state; + + if (urb->actual_length < sizeof(*req_pkt) + 1) { + dev_err(dev, "%s: short interrupt transfer: %u bytes\n", + __func__, urb->actual_length); + return; + } + + signals = *((unsigned char *)urb->transfer_buffer + + sizeof(*req_pkt)); + + 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_dbg(dev, "%s: error %d\n", __func__, status); + + /* Resubmit urb so we continue receiving IRQ data */ + if (status != -ESHUTDOWN && status != -ENOENT) { + usb_mark_last_busy(port->serial->dev); + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) + dev_dbg(dev, "%s: resubmit intr urb failed. (%d)\n", + __func__, err); + } +} + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/oti6858.c b/drivers/usb/serial/oti6858.c new file mode 100644 index 000000000..c6364e8ff --- /dev/null +++ b/drivers/usb/serial/oti6858.c @@ -0,0 +1,839 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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! + * + * See Documentation/usb/usb-serial.rst 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 "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_MSR_MASK 0x1b +#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, + const struct ktermios *old_termios); +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 unsigned int oti6858_write_room(struct tty_struct *tty); +static unsigned 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_port_probe(struct usb_serial_port *port); +static void oti6858_port_remove(struct usb_serial_port *port); + +/* device info */ +static struct usb_serial_driver oti6858_device = { + .driver = { + .name = "oti6858", + }, + .id_table = id_table, + .num_ports = 1, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 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 = usb_serial_generic_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) { + /* 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) + 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_obj(*priv); + 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); + + port->port.drain_delay = 256; /* FIXME: check the FIFO length */ + + return 0; +} + +static void oti6858_port_remove(struct usb_serial_port *port) +{ + struct oti6858_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); +} + +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 unsigned int oti6858_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned int room; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + room = kfifo_avail(&port->write_fifo); + spin_unlock_irqrestore(&port->lock, flags); + + return room; +} + +static unsigned int oti6858_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + unsigned int chars; + 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_encode_baud_rate(tty, 38400, 38400); +} + +static void oti6858_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const 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; + + cflag = tty->termios.c_cflag; + + spin_lock_irqsave(&priv->lock, flags); + 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 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) + 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, NULL); + + 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 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) { + u8 delta = xs->pin_state ^ priv->status.pin_state; + + if (delta & PIN_MSR_MASK) { + if (delta & PIN_CTS) + port->icount.cts++; + if (delta & PIN_DSR) + port->icount.dsr++; + if (delta & PIN_RI) + port->icount.rng++; + if (delta & PIN_DCD) + port->icount.dcd++; + + 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 v2"); diff --git a/drivers/usb/serial/oti6858.h b/drivers/usb/serial/oti6858.h new file mode 100644 index 000000000..5c25836fd --- /dev/null +++ b/drivers/usb/serial/oti6858.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Ours Technology Inc. OTi-6858 USB to serial adapter driver. + */ +#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 000000000..bf42a545c --- /dev/null +++ b/drivers/usb/serial/pl2303.c @@ -0,0 +1,1401 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + * + * See Documentation/usb/usb-serial.rst for more information on using this + * driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pl2303.h" + + +#define PL2303_QUIRK_UART_STATE_IDX0 BIT(0) +#define PL2303_QUIRK_LEGACY BIT(1) +#define PL2303_QUIRK_ENDPOINT_HACK BIT(2) +#define PL2303_QUIRK_NO_BREAK_GETLINE BIT(3) + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID), + .driver_info = PL2303_QUIRK_ENDPOINT_HACK }, + { 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_CHILITAG) }, + { 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(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_TB) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GC) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GB) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GT) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GL) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GE) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GS) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL256X_PRODUCT_ID_4P) }, + { 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), + .driver_info = PL2303_QUIRK_ENDPOINT_HACK }, + { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_UC485), + .driver_info = PL2303_QUIRK_ENDPOINT_HACK }, + { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_UC232B), + .driver_info = PL2303_QUIRK_ENDPOINT_HACK }, + { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID2) }, + { 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(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_SX1), + .driver_info = PL2303_QUIRK_UART_STATE_IDX0 }, + { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_X65), + .driver_info = PL2303_QUIRK_UART_STATE_IDX0 }, + { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_X75), + .driver_info = PL2303_QUIRK_UART_STATE_IDX0 }, + { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_EF81), + .driver_info = PL2303_QUIRK_ENDPOINT_HACK }, + { 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), + .driver_info = PL2303_QUIRK_ENDPOINT_HACK }, + { 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_LD220TA_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LD381_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LD381GC_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LD960_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LD960TA_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LCM220_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LCM960_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LM920_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LM930_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_LM940_PRODUCT_ID) }, + { USB_DEVICE(HP_VENDOR_ID, HP_TD620_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(ADLINK_VENDOR_ID, ADLINK_ND6530GC_PRODUCT_ID) }, + { USB_DEVICE(SMART_VENDOR_ID, SMART_PRODUCT_ID) }, + { USB_DEVICE(AT_VENDOR_ID, AT_VTKIT3_PRODUCT_ID) }, + { USB_DEVICE(IBM_VENDOR_ID, IBM_PRODUCT_ID) }, + { USB_DEVICE(MACROSILICON_VENDOR_ID, MACROSILICON_MS3020_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_WRITE_NREQUEST 0x80 +#define VENDOR_WRITE_MPREQUEST 0x80 + +#define VENDOR_READ_REQUEST_TYPE 0xc0 +#define VENDOR_READ_REQUEST 0x01 +#define VENDOR_READ_NREQUEST 0x81 +#define VENDOR_READ_MPREQUEST 0x80 + +#define PL256X_RESET_REQUEST_TYPE 0x40 +#define PL256X_RESET_REQUEST 0x96 + +#define UART_STATE_INDEX 8 +#define UART_STATE_MSR_MASK 0x8b +#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 + +#define PL2303_FLOWCTRL_MASK 0xf0 + +#define PL2303_READ_TYPE_HX_STATUS 0x8080 + +#define PL2303_HXN_RESET_REG 0x07 +#define PL2303_HXN_RESET_UPSTREAM_PIPE 0x02 +#define PL2303_HXN_RESET_DOWNSTREAM_PIPE 0x01 + +#define PL2303_HXN_FLOWCTRL_REG 0x0a +#define PL2303_HXN_FLOWCTRL_MASK 0x1c +#define PL2303_HXN_FLOWCTRL_NONE 0x1c +#define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18 +#define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c + +#define PL256X_PORT_A_FLOWCTRL_REG 0xc005 +#define PL256X_PORT_B_FLOWCTRL_REG 0xd005 +#define PL256X_PORT_C_FLOWCTRL_REG 0xe005 +#define PL256X_PORT_D_FLOWCTRL_REG 0xf005 + +#define PL256X_FLOWCTRL_MASK 0x43 +#define PL256X_FLOWCTRL_XON_XOFF 0x40 +#define PL256X_FLOWCTRL_RTS_CTS 0x03 +#define PL256X_FLOWCTRL_NONE 0x00 + +static int pl2303_set_break(struct usb_serial_port *port, bool enable); + +enum pl2303_type { + TYPE_H, + TYPE_HX, + TYPE_TA, + TYPE_TB, + TYPE_HXD, + TYPE_HXN, + TYPE_MP, + TYPE_COUNT +}; + +struct pl2303_type_data { + const char *name; + speed_t max_baud_rate; + unsigned long quirks; + unsigned int no_autoxonxoff:1; + unsigned int no_divisors:1; + unsigned int alt_divisors:1; +}; + +struct pl2303_serial_private { + const struct pl2303_type_data *type; + unsigned long quirks; + u16 interface_num; + u16 flowctrl_reg; +}; + +struct pl2303_private { + spinlock_t lock; + u8 line_control; + u8 line_status; + + u8 line_settings[7]; +}; + +static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = { + [TYPE_H] = { + .name = "H", + .max_baud_rate = 1228800, + .quirks = PL2303_QUIRK_LEGACY, + .no_autoxonxoff = true, + }, + [TYPE_HX] = { + .name = "HX", + .max_baud_rate = 6000000, + }, + [TYPE_TA] = { + .name = "TA", + .max_baud_rate = 6000000, + .alt_divisors = true, + }, + [TYPE_TB] = { + .name = "TB", + .max_baud_rate = 12000000, + .alt_divisors = true, + }, + [TYPE_HXD] = { + .name = "HXD", + .max_baud_rate = 12000000, + }, + [TYPE_HXN] = { + .name = "G", + .max_baud_rate = 12000000, + .no_divisors = true, + }, + [TYPE_MP] = { + .name = "MP", + .max_baud_rate = 24000000, + .no_divisors = true, + }, +}; + +static int pl256x_uart_reset(struct usb_serial *serial) +{ + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + struct device *dev = &serial->interface->dev; + int res; + + res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + PL256X_RESET_REQUEST, PL256X_RESET_REQUEST_TYPE, + 0, spriv->interface_num, NULL, 0, 100); + if (res) { + dev_err(dev, "failed to reset device: %d\n", res); + return res; + } + + return 0; +} + +static int pl2303_vendor_read(struct usb_serial *serial, u16 value, + unsigned char buf[1]) +{ + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + struct device *dev = &serial->interface->dev; + u8 request; + int res; + + if (spriv->type == &pl2303_type_data[TYPE_HXN]) + request = VENDOR_READ_NREQUEST; + else if (spriv->type == &pl2303_type_data[TYPE_MP]) + request = VENDOR_READ_MPREQUEST; + else + request = VENDOR_READ_REQUEST; + + res = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + request, VENDOR_READ_REQUEST_TYPE, + value, 0, buf, 1, 100); + if (res != 1) { + dev_err(dev, "%s - failed to read [%04x]: %d\n", __func__, + value, res); + if (res >= 0) + res = -EIO; + + return res; + } + + dev_dbg(dev, "%s - [%04x] = %02x\n", __func__, value, buf[0]); + + return 0; +} + +static int pl2303_vendor_write(struct usb_serial *serial, u16 value, u16 index) +{ + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + struct device *dev = &serial->interface->dev; + u8 request; + int res; + + dev_dbg(dev, "%s - [%04x] = %02x\n", __func__, value, index); + + if (spriv->type == &pl2303_type_data[TYPE_HXN]) + request = VENDOR_WRITE_NREQUEST; + else if (spriv->type == &pl2303_type_data[TYPE_MP]) + request = VENDOR_WRITE_MPREQUEST; + else + request = VENDOR_WRITE_REQUEST; + + res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + request, VENDOR_WRITE_REQUEST_TYPE, + value, index, NULL, 0, 100); + if (res) { + dev_err(dev, "%s - failed to write [%04x]: %d\n", __func__, + value, res); + return res; + } + + return 0; +} + +static int pl2303_update_reg(struct usb_serial *serial, u8 reg, u8 mask, u8 val) +{ + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + int ret = 0; + u8 *buf; + + buf = kmalloc(1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (spriv->type == &pl2303_type_data[TYPE_HXN] || + spriv->type == &pl2303_type_data[TYPE_MP]) { + ret = pl2303_vendor_read(serial, reg, buf); + } else { + ret = pl2303_vendor_read(serial, reg | 0x80, buf); + } + + if (ret) + goto out_free; + + *buf &= ~mask; + *buf |= val & mask; + + ret = pl2303_vendor_write(serial, reg, *buf); +out_free: + kfree(buf); + + return ret; +} + +static int pl2303_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + usb_set_serial_data(serial, (void *)id->driver_info); + + return 0; +} + +/* + * Use interrupt endpoint from first interface if available. + * + * This is needed due to the looney way its endpoints are set up. + */ +static int pl2303_endpoint_hack(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + struct usb_interface *interface = serial->interface; + struct usb_device *dev = serial->dev; + struct device *ddev = &interface->dev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + unsigned int i; + + if (interface == dev->actconfig->interface[0]) + return 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)) + continue; + + dev_dbg(ddev, "found interrupt in on separate interface\n"); + if (epds->num_interrupt_in < ARRAY_SIZE(epds->interrupt_in)) + epds->interrupt_in[epds->num_interrupt_in++] = endpoint; + } + + return 0; +} + +static int pl2303_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + unsigned long quirks = (unsigned long)usb_get_serial_data(serial); + struct device *dev = &serial->interface->dev; + int ret; + + if (quirks & PL2303_QUIRK_ENDPOINT_HACK) { + ret = pl2303_endpoint_hack(serial, epds); + if (ret) + return ret; + } + + if (epds->num_interrupt_in < 1) { + dev_err(dev, "required interrupt-in endpoint missing\n"); + return -ENODEV; + } + + return 1; +} + +static bool pl2303_supports_hx_status(struct usb_serial *serial) +{ + int ret; + u8 buf; + + ret = usb_control_msg_recv(serial->dev, 0, VENDOR_READ_REQUEST, + VENDOR_READ_REQUEST_TYPE, PL2303_READ_TYPE_HX_STATUS, + 0, &buf, 1, 100, GFP_KERNEL); + + return ret == 0; +} + +static int pl2303_detect_type(struct usb_serial *serial) +{ + struct usb_device_descriptor *desc = &serial->dev->descriptor; + u16 bcdDevice, bcdUSB; + + /* + * Legacy PL2303H, variants 0 and 1 (difference unknown). + */ + if (desc->bDeviceClass == 0x02) + return TYPE_H; /* variant 0 */ + + if (desc->bMaxPacketSize0 != 0x40) { + if (desc->bDeviceClass == 0x00 || desc->bDeviceClass == 0xff) + return TYPE_H; /* variant 1 */ + + return TYPE_H; /* variant 0 */ + } + + bcdDevice = le16_to_cpu(desc->bcdDevice); + bcdUSB = le16_to_cpu(desc->bcdUSB); + + switch (bcdUSB) { + case 0x101: + /* USB 1.0.1? Let's assume they meant 1.1... */ + fallthrough; + case 0x110: + switch (bcdDevice) { + case 0x300: + return TYPE_HX; + case 0x400: + return TYPE_HXD; + default: + return TYPE_HX; + } + break; + case 0x200: + switch (bcdDevice) { + case 0x100: /* GC */ + case 0x105: + return TYPE_HXN; + case 0x300: /* GT / TA */ + if (pl2303_supports_hx_status(serial)) + return TYPE_TA; + fallthrough; + case 0x305: + case 0x400: /* GL */ + case 0x405: + return TYPE_HXN; + case 0x500: /* GE / TB */ + if (pl2303_supports_hx_status(serial)) + return TYPE_TB; + fallthrough; + case 0x505: + case 0x600: /* GS */ + case 0x605: + case 0x700: /* GR */ + case 0x705: + case 0x905: /* GT-2AB */ + case 0x1005: /* GC-Q20 */ + return TYPE_HXN; + case 0x3302: /* PL2533 VC 2 Port */ + case 0x3304: /* PL2533 VC 4 Port */ + case 0x4302: /* PL2543 VC 2 Port */ + case 0x4304: /* PL2543 VC 4 Port */ + case 0x6502: /* PL2565 VC 2 Port */ + case 0x6504: /* PL2565 VC 4 Port */ + case 0x6506: /* PL2565 VC 4 Port QFN64 package */ + return TYPE_MP; + } + break; + } + + dev_err(&serial->interface->dev, + "unknown device type, please report to linux-usb@vger.kernel.org\n"); + return -ENODEV; +} + +static bool pl2303_is_hxd_clone(struct usb_serial *serial) +{ + struct usb_device *udev = serial->dev; + unsigned char *buf; + int ret; + + buf = kmalloc(7, GFP_KERNEL); + if (!buf) + return false; + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, + 0, 0, buf, 7, 100); + + kfree(buf); + + return ret == -EPIPE; +} + +static int pl2303_startup(struct usb_serial *serial) +{ + struct pl2303_serial_private *spriv; + enum pl2303_type type; + unsigned char *buf; + unsigned int ifnum; + int ret; + + ret = pl2303_detect_type(serial); + if (ret < 0) + return ret; + + type = ret; + dev_dbg(&serial->interface->dev, "device type: %s\n", pl2303_type_data[type].name); + + ifnum = serial->interface->altsetting->desc.bInterfaceNumber; + + spriv = kzalloc_obj(*spriv); + if (!spriv) + return -ENOMEM; + + if (type == TYPE_MP) { + switch (ifnum) { + case 0: + spriv->flowctrl_reg = PL256X_PORT_A_FLOWCTRL_REG; + break; + case 1: + spriv->flowctrl_reg = PL256X_PORT_B_FLOWCTRL_REG; + break; + case 2: + spriv->flowctrl_reg = PL256X_PORT_C_FLOWCTRL_REG; + break; + case 3: + spriv->flowctrl_reg = PL256X_PORT_D_FLOWCTRL_REG; + break; + default: + kfree(spriv); + return -ENODEV; + } + } + + spriv->type = &pl2303_type_data[type]; + spriv->quirks = (unsigned long)usb_get_serial_data(serial); + spriv->quirks |= spriv->type->quirks; + spriv->interface_num = ifnum; + + if (type == TYPE_HXD && pl2303_is_hxd_clone(serial)) + spriv->quirks |= PL2303_QUIRK_NO_BREAK_GETLINE; + + usb_set_serial_data(serial, spriv); + + if (type != TYPE_HXN && type != TYPE_MP) { + buf = kmalloc(1, GFP_KERNEL); + if (!buf) { + kfree(spriv); + return -ENOMEM; + } + + pl2303_vendor_read(serial, 0x8484, buf); + pl2303_vendor_write(serial, 0x0404, 0); + pl2303_vendor_read(serial, 0x8484, buf); + pl2303_vendor_read(serial, 0x8383, buf); + pl2303_vendor_read(serial, 0x8484, buf); + pl2303_vendor_write(serial, 0x0404, 1); + pl2303_vendor_read(serial, 0x8484, buf); + pl2303_vendor_read(serial, 0x8383, buf); + pl2303_vendor_write(serial, 0, 1); + pl2303_vendor_write(serial, 1, 0); + if (spriv->quirks & PL2303_QUIRK_LEGACY) + pl2303_vendor_write(serial, 2, 0x24); + else + pl2303_vendor_write(serial, 2, 0x44); + + kfree(buf); + } + + return 0; +} + +static void pl2303_release(struct usb_serial *serial) +{ + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + + kfree(spriv); +} + +static int pl2303_port_probe(struct usb_serial_port *port) +{ + struct pl2303_private *priv; + + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + + usb_set_serial_port_data(port, priv); + + port->port.drain_delay = 256; + + return 0; +} + +static void pl2303_port_remove(struct usb_serial_port *port) +{ + struct pl2303_private *priv = usb_get_serial_port_data(port); + + kfree(priv); +} + +static int pl2303_set_control_lines(struct usb_serial_port *port, u8 value) +{ + struct usb_device *dev = port->serial->dev; + struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + int retval; + + dev_dbg(&port->dev, "%s - %02x\n", __func__, value); + + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + SET_CONTROL_REQUEST, SET_CONTROL_REQUEST_TYPE, + value, spriv->interface_num, NULL, 0, 100); + if (retval) + dev_err(&port->dev, "%s - failed: %d\n", __func__, retval); + + return retval; +} + +/* + * Returns the nearest supported baud rate that can be set directly without + * using divisors. + */ +static speed_t pl2303_get_supported_baud_rate(speed_t baud) +{ + static const speed_t 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 + }; + + unsigned i; + + for (i = 0; i < ARRAY_SIZE(baud_sup); ++i) { + if (baud_sup[i] > baud) + break; + } + + if (i == ARRAY_SIZE(baud_sup)) + baud = baud_sup[i - 1]; + else if (i > 0 && (baud_sup[i] - baud) > (baud - baud_sup[i - 1])) + baud = baud_sup[i - 1]; + else + baud = baud_sup[i]; + + return baud; +} + +/* + * NOTE: If unsupported baud rates are set directly, the PL2303 seems to + * use 9600 baud. + */ +static speed_t pl2303_encode_baud_rate_direct(unsigned char buf[4], + speed_t baud) +{ + put_unaligned_le32(baud, buf); + + return baud; +} + +static speed_t pl2303_encode_baud_rate_divisor(unsigned char buf[4], + speed_t baud) +{ + unsigned int baseline, mantissa, exponent; + + /* + * Apparently the formula is: + * baudrate = 12M * 32 / (mantissa * 4^exponent) + * where + * mantissa = buf[8:0] + * exponent = buf[11:9] + */ + baseline = 12000000 * 32; + mantissa = baseline / baud; + if (mantissa == 0) + mantissa = 1; /* Avoid dividing by zero if baud > 32*12M. */ + exponent = 0; + while (mantissa >= 512) { + if (exponent < 7) { + mantissa >>= 2; /* divide by 4 */ + exponent++; + } else { + /* Exponent is maxed. Trim mantissa and leave. */ + mantissa = 511; + break; + } + } + + buf[3] = 0x80; + buf[2] = 0; + buf[1] = exponent << 1 | mantissa >> 8; + buf[0] = mantissa & 0xff; + + /* Calculate and return the exact baud rate. */ + baud = (baseline / mantissa) >> (exponent << 1); + + return baud; +} + +static speed_t pl2303_encode_baud_rate_divisor_alt(unsigned char buf[4], + speed_t baud) +{ + unsigned int baseline, mantissa, exponent; + + /* + * Apparently, for the TA version the formula is: + * baudrate = 12M * 32 / (mantissa * 2^exponent) + * where + * mantissa = buf[10:0] + * exponent = buf[15:13 16] + */ + baseline = 12000000 * 32; + mantissa = baseline / baud; + if (mantissa == 0) + mantissa = 1; /* Avoid dividing by zero if baud > 32*12M. */ + exponent = 0; + while (mantissa >= 2048) { + if (exponent < 15) { + mantissa >>= 1; /* divide by 2 */ + exponent++; + } else { + /* Exponent is maxed. Trim mantissa and leave. */ + mantissa = 2047; + break; + } + } + + buf[3] = 0x80; + buf[2] = exponent & 0x01; + buf[1] = (exponent & ~0x01) << 4 | mantissa >> 8; + buf[0] = mantissa & 0xff; + + /* Calculate and return the exact baud rate. */ + baud = (baseline / mantissa) >> exponent; + + return baud; +} + +static void pl2303_encode_baud_rate(struct tty_struct *tty, + struct usb_serial_port *port, + u8 buf[4]) +{ + struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + speed_t baud_sup; + speed_t baud; + + baud = tty_get_baud_rate(tty); + dev_dbg(&port->dev, "baud requested = %u\n", baud); + if (!baud) + return; + + if (spriv->type->max_baud_rate) + baud = min_t(speed_t, baud, spriv->type->max_baud_rate); + /* + * Use direct method for supported baud rates, otherwise use divisors. + * Newer chip types do not support divisor encoding. + */ + if (spriv->type->no_divisors) + baud_sup = baud; + else + baud_sup = pl2303_get_supported_baud_rate(baud); + + if (baud == baud_sup) + baud = pl2303_encode_baud_rate_direct(buf, baud); + else if (spriv->type->alt_divisors) + baud = pl2303_encode_baud_rate_divisor_alt(buf, baud); + else + baud = pl2303_encode_baud_rate_divisor(buf, baud); + + /* Save resulting baud rate */ + tty_encode_baud_rate(tty, baud, baud); + dev_dbg(&port->dev, "baud set = %u\n", baud); +} + +static int pl2303_get_line_request(struct usb_serial_port *port, + unsigned char buf[7]) +{ + struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + struct usb_device *udev = serial->dev; + int ret; + + if (spriv->quirks & PL2303_QUIRK_NO_BREAK_GETLINE) { + struct pl2303_private *priv = usb_get_serial_port_data(port); + + memcpy(buf, priv->line_settings, 7); + return 0; + } + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, + 0, spriv->interface_num, buf, 7, 100); + if (ret != 7) { + dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); + + if (ret >= 0) + ret = -EIO; + + return ret; + } + + dev_dbg(&port->dev, "%s - %7ph\n", __func__, buf); + + return 0; +} + +static int pl2303_set_line_request(struct usb_serial_port *port, + unsigned char buf[7]) +{ + struct usb_device *udev = port->serial->dev; + struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + int ret; + + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE, + 0, spriv->interface_num, buf, 7, 100); + if (ret < 0) { + dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); + return ret; + } + + dev_dbg(&port->dev, "%s - %7ph\n", __func__, buf); + + return 0; +} + +static bool pl2303_termios_change(const struct ktermios *a, const struct ktermios *b) +{ + bool ixon_change; + + ixon_change = ((a->c_iflag ^ b->c_iflag) & (IXON | IXANY)) || + a->c_cc[VSTART] != b->c_cc[VSTART] || + a->c_cc[VSTOP] != b->c_cc[VSTOP]; + + return tty_termios_hw_change(a, b) || ixon_change; +} + +static bool pl2303_enable_xonxoff(struct tty_struct *tty, const struct pl2303_type_data *type) +{ + if (!I_IXON(tty) || I_IXANY(tty)) + return false; + + if (START_CHAR(tty) != 0x11 || STOP_CHAR(tty) != 0x13) + return false; + + if (type->no_autoxonxoff) + return false; + + return true; +} + +static void pl2303_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const 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 char *buf; + int ret; + u8 control; + + if (old_termios && !pl2303_termios_change(&tty->termios, old_termios)) + return; + + buf = kzalloc(7, GFP_KERNEL); + if (!buf) { + /* Report back no change occurred */ + if (old_termios) + tty->termios = *old_termios; + return; + } + + pl2303_get_line_request(port, buf); + + buf[6] = tty_get_char_size(tty->termios.c_cflag); + dev_dbg(&port->dev, "data bits = %d\n", buf[6]); + + /* For reference buf[0]:buf[3] baud rate value */ + pl2303_encode_baud_rate(tty, port, &buf[0]); + + /* 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 (C_CSTOPB(tty)) { + /* + * NOTE: Comply with "real" UARTs / RS232: + * use 1.5 instead of 2 stop bits with 5 data bits + */ + if (C_CSIZE(tty) == 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 (C_PARENB(tty)) { + /* 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 (C_PARODD(tty)) { + if (C_CMSPAR(tty)) { + buf[5] = 3; + dev_dbg(&port->dev, "parity = mark\n"); + } else { + buf[5] = 1; + dev_dbg(&port->dev, "parity = odd\n"); + } + } else { + if (C_CMSPAR(tty)) { + 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)) { + ret = pl2303_set_line_request(port, buf); + if (!ret) + 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 (C_BAUD(tty) == 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); + } + + if (C_CRTSCTS(tty)) { + if (spriv->quirks & PL2303_QUIRK_LEGACY) { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x40); + } else if (spriv->type == &pl2303_type_data[TYPE_HXN]) { + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_RTS_CTS); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl2303_vendor_write(serial, spriv->flowctrl_reg, + PL256X_FLOWCTRL_RTS_CTS); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x60); + } + } else if (pl2303_enable_xonxoff(tty, spriv->type)) { + if (spriv->type == &pl2303_type_data[TYPE_HXN]) { + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_XON_XOFF); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl2303_vendor_write(serial, spriv->flowctrl_reg, + PL256X_FLOWCTRL_XON_XOFF); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0xc0); + } + } else { + if (spriv->type == &pl2303_type_data[TYPE_HXN]) { + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_NONE); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl2303_vendor_write(serial, spriv->flowctrl_reg, + PL256X_FLOWCTRL_NONE); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0); + } + } + + 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); + 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); + pl2303_set_break(port, false); +} + +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->quirks & PL2303_QUIRK_LEGACY) { + usb_clear_halt(serial->dev, port->write_urb->pipe); + usb_clear_halt(serial->dev, port->read_urb->pipe); + } else { + /* reset upstream data pipes */ + if (spriv->type == &pl2303_type_data[TYPE_HXN]) { + pl2303_vendor_write(serial, PL2303_HXN_RESET_REG, + PL2303_HXN_RESET_UPSTREAM_PIPE | + PL2303_HXN_RESET_DOWNSTREAM_PIPE); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl256x_uart_reset(serial); + } else { + pl2303_vendor_write(serial, 8, 0); + pl2303_vendor_write(serial, 9, 0); + } + } + + /* 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, "failed to submit interrupt urb: %d\n", + result); + return result; + } + + result = usb_serial_generic_open(tty, port); + if (result) { + usb_kill_urb(port->interrupt_in_urb); + return result; + } + + 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_set_break(struct usb_serial_port *port, bool enable) +{ + struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + u16 state; + int result; + + if (spriv->quirks & PL2303_QUIRK_NO_BREAK_GETLINE) + return -ENOTTY; + + if (enable) + state = BREAK_ON; + else + state = BREAK_OFF; + + 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, + spriv->interface_num, NULL, 0, 100); + if (result) { + dev_err(&port->dev, "error sending break = %d\n", result); + return result; + } + + return 0; +} + +static int pl2303_break_ctl(struct tty_struct *tty, int state) +{ + struct usb_serial_port *port = tty->driver_data; + + return pl2303_set_break(port, state); +} + +static void pl2303_update_line_status(struct usb_serial_port *port, + unsigned char *data, + unsigned int actual_length) +{ + 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); + struct tty_struct *tty; + unsigned long flags; + unsigned int status_idx = UART_STATE_INDEX; + u8 status; + u8 delta; + + if (spriv->quirks & PL2303_QUIRK_UART_STATE_IDX0) + status_idx = 0; + + if (actual_length < status_idx + 1) + return; + + status = data[status_idx]; + + /* Save off the uart status for others to look at */ + spin_lock_irqsave(&priv->lock, flags); + delta = priv->line_status ^ status; + priv->line_status = status; + spin_unlock_irqrestore(&priv->lock, flags); + + if (status & UART_BREAK_ERROR) + usb_serial_handle_break(port); + + if (delta & UART_STATE_MSR_MASK) { + if (delta & UART_CTS) + port->icount.cts++; + if (delta & UART_DSR) + port->icount.dsr++; + if (delta & UART_RING) + port->icount.rng++; + if (delta & UART_DCD) { + port->icount.dcd++; + tty = tty_port_tty_get(&port->port); + if (tty) { + usb_serial_handle_dcd_change(port, tty, + status & UART_DCD); + tty_kref_put(tty); + } + } + + wake_up_interruptible(&port->port.delta_msr_wait); + } +} + +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); + + 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; + + if (tty_flag != TTY_NORMAL) + 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->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 struct usb_serial_driver pl2303_device = { + .driver = { + .name = "pl2303", + }, + .id_table = id_table, + .num_bulk_in = 1, + .num_bulk_out = 1, + .num_interrupt_in = 0, /* see pl2303_calc_num_ports */ + .bulk_in_size = 256, + .bulk_out_size = 256, + .open = pl2303_open, + .close = pl2303_close, + .dtr_rts = pl2303_dtr_rts, + .carrier_raised = pl2303_carrier_raised, + .break_ctl = pl2303_break_ctl, + .set_termios = pl2303_set_termios, + .tiocmget = pl2303_tiocmget, + .tiocmset = pl2303_tiocmset, + .tiocmiwait = usb_serial_generic_tiocmiwait, + .process_read_urb = pl2303_process_read_urb, + .read_int_callback = pl2303_read_int_callback, + .probe = pl2303_probe, + .calc_num_ports = pl2303_calc_num_ports, + .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("Prolific PL2303 USB to serial adaptor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/pl2303.h b/drivers/usb/serial/pl2303.h new file mode 100644 index 000000000..8eb1e7b5d --- /dev/null +++ b/drivers/usb/serial/pl2303.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Prolific PL2303 USB to serial adaptor driver header file + */ + +#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_TB 0x2304 +#define PL2303_PRODUCT_ID_GC 0x23a3 +#define PL2303_PRODUCT_ID_GB 0x23b3 +#define PL2303_PRODUCT_ID_GT 0x23c3 +#define PL2303_PRODUCT_ID_GL 0x23d3 +#define PL2303_PRODUCT_ID_GE 0x23e3 +#define PL2303_PRODUCT_ID_GS 0x23f3 +#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_CHILITAG 0xaaa8 +#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 PL256X_PRODUCT_ID_4P 0x2533 + +#define ATEN_VENDOR_ID 0x0557 +#define ATEN_VENDOR_ID2 0x0547 +#define ATEN_PRODUCT_ID 0x2008 +#define ATEN_PRODUCT_UC485 0x2021 +#define ATEN_PRODUCT_UC232B 0x2022 +#define ATEN_PRODUCT_ID2 0x2118 + +#define IBM_VENDOR_ID 0x04b3 +#define IBM_PRODUCT_ID 0x4016 + +#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 + +#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_LD381GC_PRODUCT_ID 0x0183 +#define HP_LM920_PRODUCT_ID 0x026b +#define HP_TD620_PRODUCT_ID 0x0956 +#define HP_LD960_PRODUCT_ID 0x0b39 +#define HP_LD381_PRODUCT_ID 0x0f7f +#define HP_LM930_PRODUCT_ID 0x0f9b +#define HP_LCM220_PRODUCT_ID 0x3139 +#define HP_LCM960_PRODUCT_ID 0x3239 +#define HP_LD220_PRODUCT_ID 0x3524 +#define HP_LD220TA_PRODUCT_ID 0x4349 +#define HP_LD960TA_PRODUCT_ID 0x4439 +#define HP_LM940_PRODUCT_ID 0x5039 + +/* 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 +#define ADLINK_ND6530GC_PRODUCT_ID 0x653a + +/* SMART USB Serial Adapter */ +#define SMART_VENDOR_ID 0x0b8c +#define SMART_PRODUCT_ID 0x2303 + +/* Allied Telesis VT-Kit3 */ +#define AT_VENDOR_ID 0x0caa +#define AT_VTKIT3_PRODUCT_ID 0x3001 + +/* Macrosilicon MS3020 */ +#define MACROSILICON_VENDOR_ID 0x345f +#define MACROSILICON_MS3020_PRODUCT_ID 0x3020 diff --git a/drivers/usb/serial/qcaux.c b/drivers/usb/serial/qcaux.c new file mode 100644 index 000000000..485ec5b07 --- /dev/null +++ b/drivers/usb/serial/qcaux.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Qualcomm USB Auxiliary Serial Port driver + * + * Copyright (C) 2008 Greg Kroah-Hartman + * Copyright (C) 2010 Dan Williams + * + * 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 + +/* 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 const 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 = { + .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_DESCRIPTION("Qualcomm USB Auxiliary Serial Port driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c new file mode 100644 index 000000000..105c0a0ea --- /dev/null +++ b/drivers/usb/serial/qcserial.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Qualcomm Serial USB driver + * + * Copyright (c) 2008 QUALCOMM Incorporated. + * Copyright (c) 2009 Greg Kroah-Hartman + * Copyright (c) 2009 Novell Inc. + */ + +#include +#include +#include +#include +#include +#include "usb-wwan.h" + +#define DRIVER_AUTHOR "Qualcomm Inc" +#define DRIVER_DESC "Qualcomm USB Serial driver" + +#define QUECTEL_EC20_PID 0x9215 + +/* standard device layouts supported by this driver */ +enum qcserial_layouts { + QCSERIAL_G2K = 0, /* Gobi 2000 */ + QCSERIAL_G1K = 1, /* Gobi 1000 */ + QCSERIAL_SWI = 2, /* Sierra Wireless */ + QCSERIAL_HWI = 3, /* Huawei */ +}; + +#define DEVICE_G1K(v, p) \ + USB_DEVICE(v, p), .driver_info = QCSERIAL_G1K +#define DEVICE_SWI(v, p) \ + USB_DEVICE(v, p), .driver_info = QCSERIAL_SWI +#define DEVICE_HWI(v, p) \ + USB_DEVICE(v, p), .driver_info = QCSERIAL_HWI + +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(0x413c, 0x81a6)}, /* Dell DW5570 QDL (MC8805) */ + {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 Sierra Wireless devices */ + {DEVICE_SWI(0x03f0, 0x4e1d)}, /* HP lt4111 LTE/EV-DO/HSPA+ Gobi 4G Module */ + {DEVICE_SWI(0x0f3d, 0x68a2)}, /* Sierra Wireless MC7700 */ + {DEVICE_SWI(0x114f, 0x68a2)}, /* Sierra Wireless MC7750 */ + {DEVICE_SWI(0x1199, 0x68a2)}, /* Sierra Wireless MC7710 */ + {DEVICE_SWI(0x1199, 0x68c0)}, /* Sierra Wireless MC7304/MC7354 */ + {DEVICE_SWI(0x1199, 0x901c)}, /* Sierra Wireless EM7700 */ + {DEVICE_SWI(0x1199, 0x901e)}, /* Sierra Wireless EM7355 QDL */ + {DEVICE_SWI(0x1199, 0x901f)}, /* Sierra Wireless EM7355 */ + {DEVICE_SWI(0x1199, 0x9040)}, /* Sierra Wireless Modem */ + {DEVICE_SWI(0x1199, 0x9041)}, /* Sierra Wireless MC7305/MC7355 */ + {DEVICE_SWI(0x1199, 0x9051)}, /* Netgear AirCard 340U */ + {DEVICE_SWI(0x1199, 0x9053)}, /* Sierra Wireless Modem */ + {DEVICE_SWI(0x1199, 0x9054)}, /* Sierra Wireless Modem */ + {DEVICE_SWI(0x1199, 0x9055)}, /* Netgear AirCard 341U */ + {DEVICE_SWI(0x1199, 0x9056)}, /* Sierra Wireless Modem */ + {DEVICE_SWI(0x1199, 0x9060)}, /* Sierra Wireless Modem */ + {DEVICE_SWI(0x1199, 0x9061)}, /* Sierra Wireless Modem */ + {DEVICE_SWI(0x1199, 0x9062)}, /* Sierra Wireless EM7305 QDL */ + {DEVICE_SWI(0x1199, 0x9063)}, /* Sierra Wireless EM7305 */ + {DEVICE_SWI(0x1199, 0x9070)}, /* Sierra Wireless MC74xx */ + {DEVICE_SWI(0x1199, 0x9071)}, /* Sierra Wireless MC74xx */ + {DEVICE_SWI(0x1199, 0x9078)}, /* Sierra Wireless EM74xx */ + {DEVICE_SWI(0x1199, 0x9079)}, /* Sierra Wireless EM74xx */ + {DEVICE_SWI(0x1199, 0x907a)}, /* Sierra Wireless EM74xx QDL */ + {DEVICE_SWI(0x1199, 0x907b)}, /* Sierra Wireless EM74xx */ + {DEVICE_SWI(0x1199, 0x9090)}, /* Sierra Wireless EM7565 QDL */ + {DEVICE_SWI(0x1199, 0x9091)}, /* Sierra Wireless EM7565 */ + {DEVICE_SWI(0x1199, 0x90d2)}, /* Sierra Wireless EM9191 QDL */ + {DEVICE_SWI(0x1199, 0x90e4)}, /* Sierra Wireless EM86xx QDL*/ + {DEVICE_SWI(0x1199, 0x90e5)}, /* Sierra Wireless EM86xx */ + {DEVICE_SWI(0x1199, 0xc080)}, /* Sierra Wireless EM7590 QDL */ + {DEVICE_SWI(0x1199, 0xc081)}, /* Sierra Wireless EM7590 */ + {DEVICE_SWI(0x413c, 0x81a2)}, /* Dell Wireless 5806 Gobi(TM) 4G LTE Mobile Broadband Card */ + {DEVICE_SWI(0x413c, 0x81a3)}, /* Dell Wireless 5570 HSPA+ (42Mbps) Mobile Broadband Card */ + {DEVICE_SWI(0x413c, 0x81a4)}, /* Dell Wireless 5570e HSPA+ (42Mbps) Mobile Broadband Card */ + {DEVICE_SWI(0x413c, 0x81a8)}, /* Dell Wireless 5808 Gobi(TM) 4G LTE Mobile Broadband Card */ + {DEVICE_SWI(0x413c, 0x81a9)}, /* Dell Wireless 5808e Gobi(TM) 4G LTE Mobile Broadband Card */ + {DEVICE_SWI(0x413c, 0x81b1)}, /* Dell Wireless 5809e Gobi(TM) 4G LTE Mobile Broadband Card */ + {DEVICE_SWI(0x413c, 0x81b3)}, /* Dell Wireless 5809e Gobi(TM) 4G LTE Mobile Broadband Card (rev3) */ + {DEVICE_SWI(0x413c, 0x81b5)}, /* Dell Wireless 5811e QDL */ + {DEVICE_SWI(0x413c, 0x81b6)}, /* Dell Wireless 5811e QDL */ + {DEVICE_SWI(0x413c, 0x81c2)}, /* Dell Wireless 5811e */ + {DEVICE_SWI(0x413c, 0x81cb)}, /* Dell Wireless 5816e QDL */ + {DEVICE_SWI(0x413c, 0x81cc)}, /* Dell Wireless 5816e */ + {DEVICE_SWI(0x413c, 0x81cf)}, /* Dell Wireless 5819 */ + {DEVICE_SWI(0x413c, 0x81d0)}, /* Dell Wireless 5819 */ + {DEVICE_SWI(0x413c, 0x81d1)}, /* Dell Wireless 5818 */ + {DEVICE_SWI(0x413c, 0x81d2)}, /* Dell Wireless 5818 */ + {DEVICE_SWI(0x413c, 0x8217)}, /* Dell Wireless DW5826e */ + {DEVICE_SWI(0x413c, 0x8218)}, /* Dell Wireless DW5826e QDL */ + + /* Huawei devices */ + {DEVICE_HWI(0x03f0, 0x581d)}, /* HP lt4112 LTE/HSPA+ Gobi 4G Modem (Huawei me906e) */ + + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static int handle_quectel_ec20(struct device *dev, int ifnum) +{ + int altsetting = 0; + + /* + * Quectel EC20 Mini PCIe LTE module layout: + * 0: DM/DIAG (use libqcdm from ModemManager for communication) + * 1: NMEA + * 2: AT-capable modem port + * 3: Modem interface + * 4: NDIS + */ + switch (ifnum) { + case 0: + dev_dbg(dev, "Quectel EC20 DM/DIAG interface found\n"); + break; + case 1: + dev_dbg(dev, "Quectel EC20 NMEA GPS interface found\n"); + break; + case 2: + case 3: + dev_dbg(dev, "Quectel EC20 Modem port found\n"); + break; + case 4: + /* Don't claim the QMI/net interface */ + altsetting = -1; + break; + } + + return altsetting; +} + +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; + int altsetting = -1; + bool sendsetup = false; + + /* we only support vendor specific functions */ + if (intf->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC) + goto done; + + 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 = usb_altnum_to_altsetting(serial->interface, 1); + else if (serial->interface->num_altsetting > 2) + goto done; + + if (intf && 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; + + } + + /* default to enabling interface */ + altsetting = 0; + + /* + * Composite mode; don't bind to the QMI/net interface as that + * gets handled by other drivers. + */ + + switch (id->driver_info) { + case QCSERIAL_G1K: + /* + * 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 (nintf < 3 || nintf > 4) { + dev_err(dev, "unknown number of interfaces: %d\n", nintf); + altsetting = -1; + goto done; + } + + 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; + break; + case QCSERIAL_G2K: + /* handle non-standard layouts */ + if (nintf == 5 && id->idProduct == QUECTEL_EC20_PID) { + altsetting = handle_quectel_ec20(dev, ifnum); + goto done; + } + + /* + * Gobi 2K+ USB layout: + * 0: QMI/net + * 1: DM/DIAG (use libqcdm from ModemManager for communication) + * 2: AT-capable modem port + * 3: NMEA + */ + if (nintf < 3 || nintf > 4) { + dev_err(dev, "unknown number of interfaces: %d\n", nintf); + altsetting = -1; + goto done; + } + + 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; + } + break; + case QCSERIAL_SWI: + /* + * Sierra Wireless layout: + * 0: DM/DIAG (use libqcdm from ModemManager for communication) + * 2: NMEA + * 3: AT-capable modem port + * 8: QMI/net + */ + switch (ifnum) { + case 0: + dev_dbg(dev, "DM/DIAG interface found\n"); + break; + case 2: + dev_dbg(dev, "NMEA GPS interface found\n"); + sendsetup = true; + break; + case 3: + dev_dbg(dev, "Modem port found\n"); + sendsetup = true; + break; + default: + /* don't claim any unsupported interface */ + altsetting = -1; + break; + } + break; + case QCSERIAL_HWI: + /* + * Huawei devices map functions by subclass + protocol + * instead of interface numbers. The protocol identify + * a specific function, while the subclass indicate a + * specific firmware source + * + * This is a list of functions known to be non-serial. The rest + * are assumed to be serial and will be handled by this driver + */ + switch (intf->desc.bInterfaceProtocol) { + /* QMI combined (qmi_wwan) */ + case 0x07: + case 0x37: + case 0x67: + /* QMI data (qmi_wwan) */ + case 0x08: + case 0x38: + case 0x68: + /* QMI control (qmi_wwan) */ + case 0x09: + case 0x39: + case 0x69: + /* NCM like (huawei_cdc_ncm) */ + case 0x16: + case 0x46: + case 0x76: + altsetting = -1; + break; + default: + dev_dbg(dev, "Huawei type serial port found (%02x/%02x/%02x)\n", + intf->desc.bInterfaceClass, + intf->desc.bInterfaceSubClass, + intf->desc.bInterfaceProtocol); + } + break; + default: + dev_err(dev, "unsupported device layout type: %lu\n", + id->driver_info); + 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; + } + } + + if (!retval) + usb_set_serial_data(serial, (void *)(unsigned long)sendsetup); + + return retval; +} + +static int qc_attach(struct usb_serial *serial) +{ + struct usb_wwan_intf_private *data; + bool sendsetup; + + data = kzalloc_obj(*data); + if (!data) + return -ENOMEM; + + sendsetup = !!(unsigned long)(usb_get_serial_data(serial)); + if (sendsetup) + data->use_send_setup = 1; + + 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 = { + .name = "qcserial", + }, + .description = "Qualcomm USB modem", + .id_table = id_table, + .num_ports = 1, + .probe = qcprobe, + .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, + .tiocmget = usb_wwan_tiocmget, + .tiocmset = usb_wwan_tiocmset, + .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 000000000..dc2b39810 --- /dev/null +++ b/drivers/usb/serial/quatech2.c @@ -0,0 +1,955 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * usb-serial driver for Quatech USB 2 devices + * + * Copyright (C) 2012 Bill Pemberton (wfp5p@virginia.edu) + * + * 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 + +/* 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_READ_BUFFER_SIZE 512 /* size of read buffer */ +#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; +}; + +struct qt2_port_private { + u8 device_port; + + spinlock_t urb_lock; + bool urb_in_use; + struct urb *write_urb; + char *write_buffer; + + 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_kill_urb(serial_priv->read_urb); + usb_free_urb(serial_priv->read_urb); + kfree(serial_priv->read_buffer); + 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_getregister(struct usb_device *dev, + u8 uart, + u8 reg, + u8 *data) +{ + int ret; + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + QT_SET_GET_REGISTER, 0xc0, reg, + uart, data, sizeof(*data), QT2_USB_TIMEOUT); + if (ret < (int)sizeof(*data)) { + if (ret >= 0) + ret = -EIO; + } + + return ret; +} + +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 usb_serial_endpoints *epds) +{ + 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, + const 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; + } + + new_lcr |= UART_LCR_WLEN(tty_get_char_size(cflag)); + + 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 = port->port_number; + + 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 < 2) { + dev_err(&port->dev, "%s - open port failed %i\n", __func__, + status); + if (status >= 0) + status = -EIO; + 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 (%i)\n", + __func__, 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; + int i; + + serial = port->serial; + port_priv = usb_get_serial_port_data(port); + + usb_kill_urb(port_priv->write_urb); + + /* flush the port transmit buffer */ + i = usb_control_msg(serial->dev, + usb_sndctrlpipe(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_sndctrlpipe(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 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; + } +} + +static void qt2_process_read_urb(struct urb *urb) +{ + struct usb_serial *serial; + struct qt2_serial_private *serial_priv; + struct usb_serial_port *port; + 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]; + + 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; + } + /* bytes_written = (ch[1] << 4) + ch[0]; */ + 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]; + i += 3; + escapeflag = true; + break; + case QT2_REC_FLUSH: + case QT2_XMIT_FLUSH: + i += 2; + escapeflag = true; + break; + case QT2_CONTROL_ESCAPE: + 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_insert_flip_char(&port->port, *ch, TTY_NORMAL); + } + + 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; + unsigned long flags; + + port = urb->context; + port_priv = usb_get_serial_port_data(port); + + spin_lock_irqsave(&port_priv->urb_lock, flags); + + port_priv->urb_in_use = false; + usb_serial_port_softint(port); + + spin_unlock_irqrestore(&port_priv->urb_lock, flags); + +} + +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) + return -ENOMEM; + + usb_fill_bulk_urb(serial_priv->read_urb, serial->dev, + usb_rcvbulkpipe(serial->dev, + port0->bulk_in_endpointAddress), + serial_priv->read_buffer, + QT2_READ_BUFFER_SIZE, + 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_sndctrlpipe(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_obj(*serial_priv); + if (!serial_priv) + return -ENOMEM; + + serial_priv->read_buffer = kmalloc(QT2_READ_BUFFER_SIZE, GFP_KERNEL); + if (!serial_priv->read_buffer) { + status = -ENOMEM; + goto err_buf; + } + + 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->read_buffer); +err_buf: + 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_obj(*port_priv); + 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_buffer = kmalloc(QT2_WRITE_BUFFER_SIZE, GFP_KERNEL); + if (!port_priv->write_buffer) + goto err_buf; + + port_priv->write_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!port_priv->write_urb) + goto err_urb; + + 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, + QT2_WRITE_BUFFER_SIZE, + qt2_write_bulk_callback, port); + + usb_set_serial_port_data(port, port_priv); + + return 0; +err_urb: + kfree(port_priv->write_buffer); +err_buf: + kfree(port_priv); + return -ENOMEM; +} + +static void 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->write_buffer); + kfree(port_priv); +} + +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 int 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); + return status; + } + + return 0; +} + + + +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; + + /* May be called from qt2_process_read_urb() for an unbound port. */ + port_priv = usb_get_serial_port_data(port); + if (!port_priv) + return; + + 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; + + /* May be called from qt2_process_read_urb() for an unbound port. */ + port_priv = usb_get_serial_port_data(port); + if (!port_priv) + return; + + 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 unsigned 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; + unsigned 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) { + 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 = { + .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, + .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 v2"); diff --git a/drivers/usb/serial/safe_serial.c b/drivers/usb/serial/safe_serial.c new file mode 100644 index 000000000..55e59e341 --- /dev/null +++ b/drivers/usb/serial/safe_serial.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Safe Encapsulated USB Serial Driver + * + * Copyright (C) 2010 Johan Hovold + * Copyright (C) 2001 Lineo + * Copyright (C) 2001 Hewlett-Packard + * + * 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 + +static bool safe = true; +static bool padded = IS_ENABLED(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"); + +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 const 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 */ + {} /* 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 inline __u16 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; + + if (length < 2) { + dev_err(&port->dev, "malformed packet\n"); + return; + } + + 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) +{ + struct usb_interface_descriptor *desc; + int bulk_out_size; + + if (serial->dev->descriptor.bDeviceClass != CDC_DEVICE_CLASS) + return -ENODEV; + + desc = &serial->interface->cur_altsetting->desc; + + if (desc->bInterfaceClass != LINEO_INTERFACE_CLASS) + return -ENODEV; + if (desc->bInterfaceSubClass != LINEO_INTERFACE_SUBCLASS_SAFESERIAL) + return -ENODEV; + + switch (desc->bInterfaceProtocol) { + case LINEO_SAFESERIAL_CRC: + break; + case LINEO_SAFESERIAL_CRC_PADDED: + padded = true; + break; + default: + return -EINVAL; + } + + /* + * The bulk-out buffer needs to be large enough for the two-byte + * trailer in safe mode, but assume anything smaller than eight bytes + * is broken. + */ + bulk_out_size = serial->port[0]->bulk_out_size; + if (bulk_out_size > 0 && bulk_out_size < 8) + return -EINVAL; + + return 0; +} + +static struct usb_serial_driver safe_device = { + .driver = { + .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 +}; + +module_usb_serial_driver(serial_drivers, id_table); diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c new file mode 100644 index 000000000..6e443aaca --- /dev/null +++ b/drivers/usb/serial/sierra.c @@ -0,0 +1,1058 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + 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. + + 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; + +struct sierra_iface_list { + const u8 *nums; /* array of interface numbers */ + size_t count; /* number of elements in array */ +}; + +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, + struct usb_serial_endpoints *epds) +{ + 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 bool is_listed(const u8 ifnum, const struct sierra_iface_list *list) +{ + int i; + + if (!list) + return false; + + for (i = 0; i < list->count; i++) { + if (list->nums[i] == ifnum) + return true; + } + + return false; +} + +static u8 sierra_interface_num(struct usb_serial *serial) +{ + return serial->interface->cur_altsetting->desc.bInterfaceNumber; +} + +static int sierra_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + const struct sierra_iface_list *ignore_list; + int result = 0; + struct usb_device *udev; + u8 ifnum; + + udev = serial->dev; + ifnum = sierra_interface_num(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); + } + + ignore_list = (const struct sierra_iface_list *)id->driver_info; + + if (is_listed(ifnum, ignore_list)) { + dev_dbg(&serial->dev->dev, "Ignoring 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_list typeA_interface_list = { + .nums = hi_memory_typeA_ifaces, + .count = ARRAY_SIZE(hi_memory_typeA_ifaces), +}; + +static const u8 hi_memory_typeB_ifaces[] = { 3, 4, 5, 6 }; +static const struct sierra_iface_list typeB_interface_list = { + .nums = hi_memory_typeB_ifaces, + .count = ARRAY_SIZE(hi_memory_typeB_ifaces), +}; + +/* 'ignorelist' 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_list direct_ip_interface_ignore = { + .nums = direct_ip_non_serial_ifaces, + .count = ARRAY_SIZE(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_ignore + }, + { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x68AA, 0xFF, 0xFF, 0xFF), + .driver_info = (kernel_ulong_t)&direct_ip_interface_ignore + }, + { USB_DEVICE(0x1199, 0x68AB) }, /* Sierra Wireless AR8550 */ + /* AT&T Direct IP LTE modems */ + { USB_DEVICE_AND_INTERFACE_INFO(0x0F3D, 0x68AA, 0xFF, 0xFF, 0xFF), + .driver_info = (kernel_ulong_t)&direct_ip_interface_ignore + }, + /* Airprime/Sierra Wireless Direct IP modems */ + { USB_DEVICE_AND_INTERFACE_INFO(0x0F3D, 0x68A3, 0xFF, 0xFF, 0xFF), + .driver_info = (kernel_ulong_t)&direct_ip_interface_ignore + }, + + { } +}; +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; +}; + +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_interface_num(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_sndctrlpipe(serial->dev, 0), + 0x22, 0x21, val, interface, NULL, 0, USB_CTRL_SET_TIMEOUT); + usb_autopm_put_interface(serial->interface); + + return retval; +} + +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) +{ + if (urb) { + 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; + unsigned long flags; + + intfdata = usb_get_serial_data(port->serial); + + /* 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_irqsave(&portdata->lock, flags); + --portdata->outstanding_urbs; + spin_unlock_irqrestore(&portdata->lock, flags); + spin_lock_irqsave(&intfdata->susp_lock, flags); + --intfdata->in_flight; + spin_unlock_irqrestore(&intfdata->susp_lock, flags); + + 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_t(size_t, count, 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 = usb_get_serial_data(serial); + + 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 = kmemdup(buf, writesize, GFP_ATOMIC); + if (!buffer) { + retval = -ENOMEM; + goto error_no_buffer; + } + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + retval = -ENOMEM; + goto error_no_urb; + } + + 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 = 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 unsigned 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 unsigned int sierra_chars_in_buffer(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; + unsigned int chars; + + /* NOTE: This overcounts somewhat. */ + spin_lock_irqsave(&portdata->lock, flags); + chars = portdata->outstanding_urbs * MAX_TRANSFER; + spin_unlock_irqrestore(&portdata->lock, flags); + + dev_dbg(&port->dev, "%s - %u\n", __func__, chars); + + return chars; +} + +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; + + urb = usb_alloc_urb(0, mem_flags); + if (!urb) + 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 { + 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 = usb_get_serial_data(serial); + struct urb *urb; + + portdata = usb_get_serial_port_data(port); + + /* + * Need to take susp_lock to make sure port is not already being + * resumed, but no need to hold it due to the tty-port initialized + * flag. + */ + spin_lock_irq(&intfdata->susp_lock); + 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_irq(&portdata->lock); + portdata->outstanding_urbs--; + spin_unlock_irq(&portdata->lock); + } + + sierra_stop_rx_urbs(port); + usb_kill_anchored_urbs(&portdata->active); + + for (i = 0; i < portdata->num_in_urbs; i++) { + sierra_release_urb(portdata->in_urbs[i]); + portdata->in_urbs[i] = NULL; + } + + usb_autopm_get_interface_no_resume(serial->interface); +} + +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 = usb_get_serial_data(serial); + int i; + int err; + int endpoint; + struct urb *urb; + + portdata = usb_get_serial_port_data(port); + + 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; + + spin_lock_irq(&intfdata->susp_lock); + 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_obj(*intfdata); + 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_list *himemory_list; + u8 ifnum; + + portdata = kzalloc_obj(*portdata); + 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_interface_num(serial); + himemory_list = &typeB_interface_list; + } else { + /* This is really the usb-serial port number of the interface + * rather than the interface number. + */ + ifnum = port->port_number; + himemory_list = &typeA_interface_list; + } + + if (is_listed(ifnum, himemory_list)) { + 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 void 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); +} + +#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 = usb_get_serial_data(serial); + + 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; +} + +/* Caller must hold susp_lock. */ +static int sierra_submit_delayed_urbs(struct usb_serial_port *port) +{ + struct sierra_port_private *portdata = usb_get_serial_port_data(port); + struct sierra_intf_private *intfdata; + struct urb *urb; + int ec = 0; + int err; + + intfdata = usb_get_serial_data(port->serial); + + for (;;) { + urb = usb_get_from_anchor(&portdata->delayed); + if (!urb) + break; + + usb_anchor_urb(urb, &portdata->active); + intfdata->in_flight++; + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + dev_err(&port->dev, "%s - submit urb failed: %d", + __func__, err); + ec++; + 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); + } + } + + if (ec) + return -EIO; + + return 0; +} + +static int sierra_resume(struct usb_serial *serial) +{ + struct usb_serial_port *port; + struct sierra_intf_private *intfdata = usb_get_serial_data(serial); + int ec = 0; + int i, err; + + spin_lock_irq(&intfdata->susp_lock); + for (i = 0; i < serial->num_ports; i++) { + port = serial->port[i]; + + if (!tty_port_initialized(&port->port)) + continue; + + err = sierra_submit_delayed_urbs(port); + if (err) + ec++; + + 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 = { + .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, + .chars_in_buffer = sierra_chars_in_buffer, + .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 v2"); + +module_param(nmea, bool, 0644); +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 000000000..3ef7ca2c0 --- /dev/null +++ b/drivers/usb/serial/spcp8x5.c @@ -0,0 +1,465 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ +#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); + +/* 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->driver_info); + + return 0; +} + +static int spcp8x5_port_probe(struct usb_serial_port *port) +{ + unsigned int quirks = (unsigned int)(unsigned long)usb_get_serial_data(port->serial); + struct spcp8x5_private *priv; + + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + priv->quirks = quirks; + + usb_set_serial_port_data(port, priv); + + port->port.drain_delay = 256; + + return 0; +} + +static void spcp8x5_port_remove(struct usb_serial_port *port) +{ + struct spcp8x5_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); +} + +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 < 1) { + dev_err(&port->dev, "failed to get modem status: %d\n", ret); + if (ret >= 0) + ret = -EIO; + goto out; + } + + dev_dbg(&port->dev, "0xc0:0x22:0:6 %d - 0x02%x\n", ret, *buf); + *status = *buf; + ret = 0; +out: + 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 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_encode_baud_rate(tty, 115200, 115200); +} + +static void spcp8x5_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const 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, "unsupported baudrate, using 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); + + 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 = { + .name = "SPCP8x5", + }, + .id_table = id_table, + .num_ports = 1, + .num_bulk_in = 1, + .num_bulk_out = 1, + .open = spcp8x5_open, + .dtr_rts = spcp8x5_dtr_rts, + .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 000000000..b0d51558b --- /dev/null +++ b/drivers/usb/serial/ssu100.c @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 + +#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) +{ + int ret; + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + QT_SET_GET_DEVICE, 0xc0, 0, 0, + data, 3, 300); + if (ret < 3) { + if (ret >= 0) + ret = -EIO; + } + + return ret; +} + +static inline int ssu100_getregister(struct usb_device *dev, + unsigned short uart, + unsigned short reg, + u8 *data) +{ + int ret; + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + QT_SET_GET_REGISTER, 0xc0, reg, + uart, data, sizeof(*data), 300); + if (ret < (int)sizeof(*data)) { + if (ret >= 0) + ret = -EIO; + } + + return ret; +} + + +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, + const 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; + } + + urb_value |= UART_LCR_WLEN(tty_get_char_size(cflag)); + + 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 < 2) { + dev_dbg(&port->dev, "%s - open failed %i\n", __func__, result); + if (result >= 0) + result = -EIO; + 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 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_obj(*priv); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->status_lock); + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static void ssu100_port_remove(struct usb_serial_port *port) +{ + struct ssu100_port_private *priv; + + priv = usb_get_serial_port_data(port); + kfree(priv); +} + +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 = 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->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 = { + .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, + .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 v2"); diff --git a/drivers/usb/serial/symbolserial.c b/drivers/usb/serial/symbolserial.c new file mode 100644 index 000000000..8823671a9 --- /dev/null +++ b/drivers/usb/serial/symbolserial.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Symbol USB barcode to serial driver + * + * Copyright (C) 2013 Johan Hovold + * Copyright (C) 2009 Greg Kroah-Hartman + * Copyright (C) 2009 Novell Inc. + */ + +#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; + unsigned long flags; + 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); + + /* + * Data from the device comes with a 1 byte header: + * + * ... + */ + if (urb->actual_length > 1) { + data_length = data[0]; + if (data_length > (urb->actual_length - 1)) + data_length = urb->actual_length - 1; + tty_insert_flip_string(&port->port, &data[1], data_length); + tty_flip_buffer_push(&port->port); + } else { + dev_dbg(&port->dev, "%s - short packet\n", __func__); + } + +exit: + spin_lock_irqsave(&priv->lock, flags); + + /* 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_irqrestore(&priv->lock, flags); +} + +static int symbol_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct symbol_private *priv = usb_get_serial_port_data(port); + 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_port_data(port); + + 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_port_data(port); + 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_port_probe(struct usb_serial_port *port) +{ + struct symbol_private *priv; + + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + + usb_set_serial_port_data(port, priv); + + return 0; +} + +static void symbol_port_remove(struct usb_serial_port *port) +{ + struct symbol_private *priv = usb_get_serial_port_data(port); + + kfree(priv); +} + +static struct usb_serial_driver symbol_device = { + .driver = { + .name = "symbol", + }, + .id_table = id_table, + .num_ports = 1, + .num_interrupt_in = 1, + .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_DESCRIPTION("Symbol USB barcode to serial driver"); +MODULE_LICENSE("GPL v2"); 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 000000000..b3f00e315 --- /dev/null +++ b/drivers/usb/serial/ti_usb_3410_5052.c @@ -0,0 +1,1659 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + * + * 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 + +/* 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 STARTECH_VENDOR_ID 0x14b0 +#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 + +/* Honeywell vendor and product IDs */ +#define HONEYWELL_VENDOR_ID 0x10ac +#define HONEYWELL_HGI80_PRODUCT_ID 0x0102 /* Honeywell HGI80 */ + +/* Moxa UPORT 11x0 vendor and product IDs */ +#define MXU1_VENDOR_ID 0x110a +#define MXU1_1110_PRODUCT_ID 0x1110 +#define MXU1_1130_PRODUCT_ID 0x1130 +#define MXU1_1150_PRODUCT_ID 0x1150 +#define MXU1_1151_PRODUCT_ID 0x1151 +#define MXU1_1131_PRODUCT_ID 0x1131 + +/* 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 +#define TI_LSR_TX_EMPTY_BOTH 0x40 + +/* 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_CONTINUOUS 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 { + __be16 wBaudRate; + __be16 wFlags; + u8 bDataBits; + u8 bParity; + u8 bStopBits; + char cXon; + char cXoff; + u8 bUartMode; +}; + +/* Get port status */ +struct ti_port_status { + u8 bCmdCode; + u8 bModuleId; + u8 bErrorCode; + u8 bMSR; + u8 bLSR; +}; + +/* 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[]; +} __packed; + +struct ti_read_data_request { + u8 bAddrType; + u8 bDataType; + u8 bDataCounter; + __be16 wBaseAddrHi; + __be16 wBaseAddrLo; +} __packed; + +struct ti_read_data_bytes { + u8 bCmdCode; + u8 bModuleId; + u8 bErrorCode; + u8 bData[]; +}; + +/* Interrupt struct */ +struct ti_interrupt { + u8 bICode; + u8 bIInfo; +}; + +/* Interrupt codes */ +#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; +} __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 */ + +#define TI_DRIVER_AUTHOR "Al Borchers " +#define TI_DRIVER_DESC "TI USB 3410/5052 Serial Driver" + +#define TI_FIRMWARE_BUF_SIZE 16284 + +#define TI_TRANSFER_TIMEOUT 2 + +/* 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 + +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; + 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 ti_device { + struct mutex td_open_close_lock; + int td_open_port_count; + struct usb_serial *td_serial; + int td_is_3410; + bool td_rs485_only; +}; + +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 void 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 unsigned int ti_write_room(struct tty_struct *tty); +static unsigned 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 void ti_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const 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 int 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 void ti_get_serial_info(struct tty_struct *tty, struct serial_struct *ss); +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 usb_device *udev, u8 command, + u16 moduleid, u16 value, void *data, int size); +static int ti_command_in_sync(struct usb_device *udev, u8 command, + u16 moduleid, u16 value, void *data, int size); +static int ti_port_cmd_out(struct usb_serial_port *port, u8 command, + u16 value, void *data, int size); +static int ti_port_cmd_in(struct usb_serial_port *port, u8 command, + u16 value, void *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); + +static const struct usb_device_id ti_id_table_3410[] = { + { 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) }, + { USB_DEVICE(HONEYWELL_VENDOR_ID, HONEYWELL_HGI80_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1110_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1130_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1131_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1150_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1151_PRODUCT_ID) }, + { USB_DEVICE(STARTECH_VENDOR_ID, TI_3410_PRODUCT_ID) }, + { } /* terminator */ +}; + +static const struct usb_device_id ti_id_table_5052[] = { + { 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 const struct usb_device_id ti_id_table_combined[] = { + { 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) }, + { USB_DEVICE(HONEYWELL_VENDOR_ID, HONEYWELL_HGI80_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1110_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1130_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1131_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1150_PRODUCT_ID) }, + { USB_DEVICE(MXU1_VENDOR_ID, MXU1_1151_PRODUCT_ID) }, + { USB_DEVICE(STARTECH_VENDOR_ID, TI_3410_PRODUCT_ID) }, + { } /* terminator */ +}; + +static struct usb_serial_driver ti_1port_device = { + .driver = { + .name = "ti_usb_3410_5052_1", + }, + .description = "TI USB 3410 1 port adapter", + .id_table = ti_id_table_3410, + .num_ports = 1, + .num_bulk_out = 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, + .get_serial = ti_get_serial_info, + .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 = { + .name = "ti_usb_3410_5052_2", + }, + .description = "TI USB 5052 2 port adapter", + .id_table = ti_id_table_5052, + .num_ports = 2, + .num_bulk_out = 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, + .get_serial = ti_get_serial_info, + .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_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_FIRMWARE("moxa/moxa-1110.fw"); +MODULE_FIRMWARE("moxa/moxa-1130.fw"); +MODULE_FIRMWARE("moxa/moxa-1131.fw"); +MODULE_FIRMWARE("moxa/moxa-1150.fw"); +MODULE_FIRMWARE("moxa/moxa-1151.fw"); + +MODULE_DEVICE_TABLE(usb, ti_id_table_combined); + +module_usb_serial_driver(serial_drivers, ti_id_table_combined); + +static int ti_startup(struct usb_serial *serial) +{ + struct ti_device *tdev; + struct usb_device *dev = serial->dev; + struct usb_host_interface *cur_altsetting; + int num_endpoints; + u16 vid, pid; + int status; + + dev_dbg(&dev->dev, + "%s - product 0x%4X, num configurations %d, configuration value %d\n", + __func__, le16_to_cpu(dev->descriptor.idProduct), + dev->descriptor.bNumConfigurations, + dev->actconfig->desc.bConfigurationValue); + + tdev = kzalloc_obj(struct ti_device); + if (!tdev) + 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"); + + vid = le16_to_cpu(dev->descriptor.idVendor); + pid = le16_to_cpu(dev->descriptor.idProduct); + if (vid == MXU1_VENDOR_ID) { + switch (pid) { + case MXU1_1130_PRODUCT_ID: + case MXU1_1131_PRODUCT_ID: + tdev->td_rs485_only = true; + break; + } + } + + cur_altsetting = serial->interface->cur_altsetting; + num_endpoints = cur_altsetting->desc.bNumEndpoints; + + /* if we have only 1 configuration and 1 endpoint, download firmware */ + if (dev->descriptor.bNumConfigurations == 1 && num_endpoints == 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; + } + + if (serial->num_bulk_in < serial->num_ports || + serial->num_bulk_out < serial->num_ports) { + dev_err(&serial->interface->dev, "missing endpoints\n"); + 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_obj(*tport); + 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; + tport->tp_port = port; + tport->tp_tdev = usb_get_serial_data(port->serial); + + if (tport->tp_tdev->td_rs485_only) + tport->tp_uart_mode = TI_UART_485_RECEIVER_DISABLED; + else + tport->tp_uart_mode = TI_UART_232; + + usb_set_serial_port_data(port, tport); + + /* + * The TUSB5052 LSR does not tell when the transmitter shift register + * has emptied so add a one-character drain delay. + */ + if (!tport->tp_tdev->td_is_3410) + port->port.drain_delay = 1; + + return 0; +} + +static void ti_port_remove(struct usb_serial_port *port) +{ + struct ti_port *tport; + + tport = usb_get_serial_port_data(port); + kfree(tport); +} + +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 status; + u16 open_settings; + + open_settings = (TI_PIPE_MODE_CONTINUOUS | + TI_PIPE_TIMEOUT_ENABLE | + (TI_TRANSFER_TIMEOUT << 2)); + + 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; + + 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); + + status = ti_port_cmd_out(port, TI_OPEN_PORT, open_settings, NULL, 0); + if (status) { + dev_err(&port->dev, "%s - cannot send open command, %d\n", + __func__, status); + goto unlink_int_urb; + } + + status = ti_port_cmd_out(port, TI_START_PORT, 0, NULL, 0); + if (status) { + dev_err(&port->dev, "%s - cannot send start command, %d\n", + __func__, status); + goto unlink_int_urb; + } + + status = ti_port_cmd_out(port, TI_PURGE_PORT, 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_port_cmd_out(port, TI_PURGE_PORT, 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); + + status = ti_port_cmd_out(port, TI_OPEN_PORT, open_settings, NULL, 0); + if (status) { + dev_err(&port->dev, "%s - cannot send open command (2), %d\n", + __func__, status); + goto unlink_int_urb; + } + + status = ti_port_cmd_out(port, TI_START_PORT, 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 */ + urb = port->read_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; + + 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); + return status; +} + + +static void ti_close(struct usb_serial_port *port) +{ + struct ti_device *tdev; + struct ti_port *tport; + int status; + unsigned long flags; + + tdev = usb_get_serial_data(port->serial); + tport = usb_get_serial_port_data(port); + + 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(&port->write_fifo); + spin_unlock_irqrestore(&tport->tp_lock, flags); + + status = ti_port_cmd_out(port, TI_CLOSE_PORT, 0, NULL, 0); + if (status) + dev_err(&port->dev, + "%s - cannot send close port command, %d\n" + , __func__, status); + + mutex_lock(&tdev->td_open_close_lock); + --tdev->td_open_port_count; + if (tdev->td_open_port_count == 0) { + /* last port is closed, shut down interrupt urb */ + usb_kill_urb(port->serial->port[0]->interrupt_in_urb); + } + 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) { + return 0; + } + + if (!tport->tp_is_open) + return -ENODEV; + + count = kfifo_in_locked(&port->write_fifo, data, count, + &tport->tp_lock); + ti_send(tport); + + return count; +} + + +static unsigned 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); + unsigned int room; + unsigned long flags; + + spin_lock_irqsave(&tport->tp_lock, flags); + room = kfifo_avail(&port->write_fifo); + spin_unlock_irqrestore(&tport->tp_lock, flags); + + dev_dbg(&port->dev, "%s - returns %u\n", __func__, room); + return room; +} + + +static unsigned 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); + unsigned int chars; + unsigned long flags; + + spin_lock_irqsave(&tport->tp_lock, flags); + chars = kfifo_len(&port->write_fifo); + spin_unlock_irqrestore(&tport->tp_lock, flags); + + dev_dbg(&port->dev, "%s - returns %u\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); + u8 lsr, mask; + int ret; + + /* + * TUSB5052 does not have the TEMT bit to tell if the shift register + * is empty. + */ + if (tport->tp_tdev->td_is_3410) + mask = TI_LSR_TX_EMPTY_BOTH; + else + mask = TI_LSR_TX_EMPTY; + + ret = ti_get_lsr(tport, &lsr); + if (!ret && !(lsr & mask)) + 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 (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 (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 void ti_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +{ + struct ti_port *tport = usb_get_serial_port_data(port); + struct ti_uart_config *config; + int baud; + int status; + unsigned int mcr; + u16 wbaudrate; + u16 wflags = 0; + + config = kmalloc_obj(*config); + if (!config) + return; + + /* these flags must be set */ + wflags |= TI_UART_ENABLE_MS_INTS; + wflags |= TI_UART_ENABLE_AUTO_START_DMA; + config->bUartMode = tport->tp_uart_mode; + + switch (C_CSIZE(tty)) { + 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 (C_PARENB(tty)) { + if (C_PARODD(tty)) { + wflags |= TI_UART_ENABLE_PARITY_CHECKING; + config->bParity = TI_UART_ODD_PARITY; + } else { + wflags |= TI_UART_ENABLE_PARITY_CHECKING; + config->bParity = TI_UART_EVEN_PARITY; + } + } else { + wflags &= ~TI_UART_ENABLE_PARITY_CHECKING; + config->bParity = TI_UART_NO_PARITY; + } + + if (C_CSTOPB(tty)) + config->bStopBits = TI_UART_2_STOP_BITS; + else + config->bStopBits = TI_UART_1_STOP_BITS; + + if (C_CRTSCTS(tty)) { + /* RTS flow control must be off to drop RTS for baud rate B0 */ + if ((C_BAUD(tty)) != B0) + wflags |= TI_UART_ENABLE_RTS_IN; + wflags |= TI_UART_ENABLE_CTS_OUT; + } else { + 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)) + wflags |= TI_UART_ENABLE_X_IN; + else + ti_restart_read(tport, tty); + + if (I_IXON(tty)) + wflags |= TI_UART_ENABLE_X_OUT; + } + + baud = tty_get_baud_rate(tty); + if (!baud) + baud = 9600; + if (tport->tp_tdev->td_is_3410) + wbaudrate = (923077 + baud/2) / baud; + else + wbaudrate = (461538 + baud/2) / baud; + + /* FIXME: Should calculate resulting baud here and report it back */ + if ((C_BAUD(tty)) != 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\n", + __func__, baud, wbaudrate, wflags, + config->bDataBits, config->bParity, config->bStopBits, + config->cXon, config->cXoff, config->bUartMode); + + config->wBaudRate = cpu_to_be16(wbaudrate); + config->wFlags = cpu_to_be16(wflags); + + status = ti_port_cmd_out(port, TI_SET_CONFIG, 0, config, + sizeof(*config)); + if (status) + dev_err(&port->dev, "%s - cannot set config on port %d, %d\n", + __func__, port->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 (C_BAUD(tty) == 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->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; + + 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; + + 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 int 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); + + 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); + return status; + } + + return 0; +} + +static int ti_get_port_from_code(unsigned char code) +{ + return (code >> 6) & 0x01; +} + +static int ti_get_func_from_code(unsigned char code) +{ + return code & 0x0f; +} + +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); + return; + default: + dev_err(dev, "%s - nonzero urb status, %d\n", __func__, status); + 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; + unsigned long flags; + 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); + return; + default: + dev_err(dev, "%s - nonzero urb status, %d\n", + __func__, status); + } + + 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_irqsave(&tport->tp_lock, flags); + port->icount.rx += urb->actual_length; + spin_unlock_irqrestore(&tport->tp_lock, flags); + } + +exit: + /* continue to read unless stopping */ + spin_lock_irqsave(&tport->tp_lock, flags); + 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_irqrestore(&tport->tp_lock, flags); + 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); + return; + default: + dev_err_console(port, "%s - nonzero urb status, %d\n", + __func__, status); + } + + /* 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(&port->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); + + 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 usb_serial_port *port = tport->tp_port; + struct ti_port_status *data; + + size = sizeof(struct ti_port_status); + data = kmalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + + status = ti_port_cmd_in(port, TI_GET_PORT_STATUS, 0, 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 void ti_get_serial_info(struct tty_struct *tty, struct serial_struct *ss) +{ + struct usb_serial_port *port = tty->driver_data; + struct ti_port *tport = usb_get_serial_port_data(port); + + ss->baud_base = tport->tp_tdev->td_is_3410 ? 921600 : 460800; +} + + +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_wakeup(tty); + } + 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 usb_device *udev, u8 command, + u16 moduleid, u16 value, void *data, int size) +{ + int status; + + status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), command, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + value, moduleid, data, size, 1000); + if (status < 0) + return status; + + return 0; +} + +static int ti_command_in_sync(struct usb_device *udev, u8 command, + u16 moduleid, u16 value, void *data, int size) +{ + int status; + + status = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), command, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + value, moduleid, data, size, 1000); + if (status == size) + status = 0; + else if (status >= 0) + status = -ECOMM; + + return status; +} + +static int ti_port_cmd_out(struct usb_serial_port *port, u8 command, + u16 value, void *data, int size) +{ + return ti_command_out_sync(port->serial->dev, command, + TI_UART1_PORT + port->port_number, + value, data, size); +} + +static int ti_port_cmd_in(struct usb_serial_port *port, u8 command, + u16 value, void *data, int size) +{ + return ti_command_in_sync(port->serial->dev, command, + TI_UART1_PORT + port->port_number, + value, data, size); +} + +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) + 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(port->serial->dev, TI_WRITE_DATA, + TI_RAM_PORT, 0, 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(size - sizeof(*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]; + + if (le16_to_cpu(dev->descriptor.idVendor) == MXU1_VENDOR_ID) { + snprintf(buf, + sizeof(buf), + "moxa/moxa-%04x.fw", + le16_to_cpu(dev->descriptor.idProduct)); + + status = request_firmware(&fw_p, buf, &dev->dev); + goto check_firmware; + } + + /* 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: + strscpy(buf, "mts_cdma.fw"); + break; + case MTS_GSM_PRODUCT_ID: + strscpy(buf, "mts_gsm.fw"); + break; + case MTS_EDGE_PRODUCT_ID: + strscpy(buf, "mts_edge.fw"); + break; + case MTS_MT9234MU_PRODUCT_ID: + strscpy(buf, "mts_mt9234mu.fw"); + break; + case MTS_MT9234ZBA_PRODUCT_ID: + strscpy(buf, "mts_mt9234zba.fw"); + break; + case MTS_MT9234ZBAOLD_PRODUCT_ID: + strscpy(buf, "mts_mt9234zba.fw"); + break; } + } + if (buf[0] == '\0') { + if (tdev->td_is_3410) + strscpy(buf, "ti_3410.fw"); + else + strscpy(buf, "ti_5052.fw"); + } + status = request_firmware(&fw_p, buf, &dev->dev); + } + +check_firmware: + 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 { + 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/upd78f0730.c b/drivers/usb/serial/upd78f0730.c new file mode 100644 index 000000000..78c4d3c19 --- /dev/null +++ b/drivers/usb/serial/upd78f0730.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas Electronics uPD78F0730 USB to serial converter driver + * + * Copyright (C) 2014,2016 Maksim Salau + * + * Protocol of the adaptor is described in the application note U19660EJ1V0AN00 + * μPD78F0730 8-bit Single-Chip Microcontroller + * USB-to-Serial Conversion Software + * + * + * The adaptor functionality is limited to the following: + * - data bits: 7 or 8 + * - stop bits: 1 or 2 + * - parity: even, odd or none + * - flow control: none + * - baud rates: 0, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 153600 + * - signals: DTR, RTS and BREAK + */ + +#include +#include +#include +#include +#include + +#define DRIVER_DESC "Renesas uPD78F0730 USB to serial converter driver" + +#define DRIVER_AUTHOR "Maksim Salau " + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x0409, 0x0063) }, /* V850ESJX3-STICK */ + { USB_DEVICE(0x045B, 0x0212) }, /* YRPBRL78G13, YRPBRL78G14 */ + { USB_DEVICE(0x064B, 0x7825) }, /* Analog Devices EVAL-ADXL362Z-DB */ + {} +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +/* + * Each adaptor is associated with a private structure, that holds the current + * state of control signals (DTR, RTS and BREAK). + */ +struct upd78f0730_port_private { + struct mutex lock; /* mutex to protect line_signals */ + u8 line_signals; +}; + +/* Op-codes of control commands */ +#define UPD78F0730_CMD_LINE_CONTROL 0x00 +#define UPD78F0730_CMD_SET_DTR_RTS 0x01 +#define UPD78F0730_CMD_SET_XON_XOFF_CHR 0x02 +#define UPD78F0730_CMD_OPEN_CLOSE 0x03 +#define UPD78F0730_CMD_SET_ERR_CHR 0x04 + +/* Data sizes in UPD78F0730_CMD_LINE_CONTROL command */ +#define UPD78F0730_DATA_SIZE_7_BITS 0x00 +#define UPD78F0730_DATA_SIZE_8_BITS 0x01 +#define UPD78F0730_DATA_SIZE_MASK 0x01 + +/* Stop-bit modes in UPD78F0730_CMD_LINE_CONTROL command */ +#define UPD78F0730_STOP_BIT_1_BIT 0x00 +#define UPD78F0730_STOP_BIT_2_BIT 0x02 +#define UPD78F0730_STOP_BIT_MASK 0x02 + +/* Parity modes in UPD78F0730_CMD_LINE_CONTROL command */ +#define UPD78F0730_PARITY_NONE 0x00 +#define UPD78F0730_PARITY_EVEN 0x04 +#define UPD78F0730_PARITY_ODD 0x08 +#define UPD78F0730_PARITY_MASK 0x0C + +/* Flow control modes in UPD78F0730_CMD_LINE_CONTROL command */ +#define UPD78F0730_FLOW_CONTROL_NONE 0x00 +#define UPD78F0730_FLOW_CONTROL_HW 0x10 +#define UPD78F0730_FLOW_CONTROL_SW 0x20 +#define UPD78F0730_FLOW_CONTROL_MASK 0x30 + +/* Control signal bits in UPD78F0730_CMD_SET_DTR_RTS command */ +#define UPD78F0730_RTS 0x01 +#define UPD78F0730_DTR 0x02 +#define UPD78F0730_BREAK 0x04 + +/* Port modes in UPD78F0730_CMD_OPEN_CLOSE command */ +#define UPD78F0730_PORT_CLOSE 0x00 +#define UPD78F0730_PORT_OPEN 0x01 + +/* Error character substitution modes in UPD78F0730_CMD_SET_ERR_CHR command */ +#define UPD78F0730_ERR_CHR_DISABLED 0x00 +#define UPD78F0730_ERR_CHR_ENABLED 0x01 + +/* + * Declaration of command structures + */ + +/* UPD78F0730_CMD_LINE_CONTROL command */ +struct upd78f0730_line_control { + u8 opcode; + __le32 baud_rate; + u8 params; +} __packed; + +/* UPD78F0730_CMD_SET_DTR_RTS command */ +struct upd78f0730_set_dtr_rts { + u8 opcode; + u8 params; +}; + +/* UPD78F0730_CMD_SET_XON_OFF_CHR command */ +struct upd78f0730_set_xon_xoff_chr { + u8 opcode; + u8 xon; + u8 xoff; +}; + +/* UPD78F0730_CMD_OPEN_CLOSE command */ +struct upd78f0730_open_close { + u8 opcode; + u8 state; +}; + +/* UPD78F0730_CMD_SET_ERR_CHR command */ +struct upd78f0730_set_err_chr { + u8 opcode; + u8 state; + u8 err_char; +}; + +static int upd78f0730_send_ctl(struct usb_serial_port *port, + const void *data, int size) +{ + struct usb_device *usbdev = port->serial->dev; + void *buf; + int res; + + if (size <= 0 || !data) + return -EINVAL; + + buf = kmemdup(data, size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + res = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x00, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + 0x0000, 0x0000, buf, size, USB_CTRL_SET_TIMEOUT); + + kfree(buf); + + if (res < 0) { + struct device *dev = &port->dev; + + dev_err(dev, "failed to send control request %02x: %d\n", + *(u8 *)data, res); + + return res; + } + + return 0; +} + +static int upd78f0730_port_probe(struct usb_serial_port *port) +{ + struct upd78f0730_port_private *private; + + private = kzalloc_obj(*private); + if (!private) + return -ENOMEM; + + mutex_init(&private->lock); + usb_set_serial_port_data(port, private); + + return 0; +} + +static void upd78f0730_port_remove(struct usb_serial_port *port) +{ + struct upd78f0730_port_private *private; + + private = usb_get_serial_port_data(port); + mutex_destroy(&private->lock); + kfree(private); +} + +static int upd78f0730_tiocmget(struct tty_struct *tty) +{ + struct upd78f0730_port_private *private; + struct usb_serial_port *port = tty->driver_data; + int signals; + int res; + + private = usb_get_serial_port_data(port); + + mutex_lock(&private->lock); + signals = private->line_signals; + mutex_unlock(&private->lock); + + res = ((signals & UPD78F0730_DTR) ? TIOCM_DTR : 0) | + ((signals & UPD78F0730_RTS) ? TIOCM_RTS : 0); + + dev_dbg(&port->dev, "%s - res = %x\n", __func__, res); + + return res; +} + +static int upd78f0730_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct upd78f0730_port_private *private; + struct upd78f0730_set_dtr_rts request; + struct device *dev = &port->dev; + int res; + + private = usb_get_serial_port_data(port); + + mutex_lock(&private->lock); + if (set & TIOCM_DTR) { + private->line_signals |= UPD78F0730_DTR; + dev_dbg(dev, "%s - set DTR\n", __func__); + } + if (set & TIOCM_RTS) { + private->line_signals |= UPD78F0730_RTS; + dev_dbg(dev, "%s - set RTS\n", __func__); + } + if (clear & TIOCM_DTR) { + private->line_signals &= ~UPD78F0730_DTR; + dev_dbg(dev, "%s - clear DTR\n", __func__); + } + if (clear & TIOCM_RTS) { + private->line_signals &= ~UPD78F0730_RTS; + dev_dbg(dev, "%s - clear RTS\n", __func__); + } + request.opcode = UPD78F0730_CMD_SET_DTR_RTS; + request.params = private->line_signals; + + res = upd78f0730_send_ctl(port, &request, sizeof(request)); + mutex_unlock(&private->lock); + + return res; +} + +static int upd78f0730_break_ctl(struct tty_struct *tty, int break_state) +{ + struct upd78f0730_port_private *private; + struct usb_serial_port *port = tty->driver_data; + struct upd78f0730_set_dtr_rts request; + struct device *dev = &port->dev; + int res; + + private = usb_get_serial_port_data(port); + + mutex_lock(&private->lock); + if (break_state) { + private->line_signals |= UPD78F0730_BREAK; + dev_dbg(dev, "%s - set BREAK\n", __func__); + } else { + private->line_signals &= ~UPD78F0730_BREAK; + dev_dbg(dev, "%s - clear BREAK\n", __func__); + } + request.opcode = UPD78F0730_CMD_SET_DTR_RTS; + request.params = private->line_signals; + + res = upd78f0730_send_ctl(port, &request, sizeof(request)); + mutex_unlock(&private->lock); + + return res; +} + +static void upd78f0730_dtr_rts(struct usb_serial_port *port, int on) +{ + struct tty_struct *tty = port->port.tty; + unsigned int set = 0; + unsigned int clear = 0; + + if (on) + set = TIOCM_DTR | TIOCM_RTS; + else + clear = TIOCM_DTR | TIOCM_RTS; + + upd78f0730_tiocmset(tty, set, clear); +} + +static speed_t upd78f0730_get_baud_rate(struct tty_struct *tty) +{ + const speed_t baud_rate = tty_get_baud_rate(tty); + static const speed_t supported[] = { + 0, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 153600 + }; + int i; + + for (i = ARRAY_SIZE(supported) - 1; i >= 0; i--) { + if (baud_rate == supported[i]) + return baud_rate; + } + + /* If the baud rate is not supported, switch to the default one */ + tty_encode_baud_rate(tty, 9600, 9600); + + return tty_get_baud_rate(tty); +} + +static void upd78f0730_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +{ + struct device *dev = &port->dev; + struct upd78f0730_line_control request; + speed_t baud_rate; + + if (old_termios && !tty_termios_hw_change(&tty->termios, old_termios)) + return; + + if (C_BAUD(tty) == B0) + upd78f0730_dtr_rts(port, 0); + else if (old_termios && (old_termios->c_cflag & CBAUD) == B0) + upd78f0730_dtr_rts(port, 1); + + baud_rate = upd78f0730_get_baud_rate(tty); + request.opcode = UPD78F0730_CMD_LINE_CONTROL; + request.baud_rate = cpu_to_le32(baud_rate); + request.params = 0; + dev_dbg(dev, "%s - baud rate = %d\n", __func__, baud_rate); + + switch (C_CSIZE(tty)) { + case CS7: + request.params |= UPD78F0730_DATA_SIZE_7_BITS; + dev_dbg(dev, "%s - 7 data bits\n", __func__); + break; + default: + tty->termios.c_cflag &= ~CSIZE; + tty->termios.c_cflag |= CS8; + dev_warn(dev, "data size is not supported, using 8 bits\n"); + fallthrough; + case CS8: + request.params |= UPD78F0730_DATA_SIZE_8_BITS; + dev_dbg(dev, "%s - 8 data bits\n", __func__); + break; + } + + if (C_PARENB(tty)) { + if (C_PARODD(tty)) { + request.params |= UPD78F0730_PARITY_ODD; + dev_dbg(dev, "%s - odd parity\n", __func__); + } else { + request.params |= UPD78F0730_PARITY_EVEN; + dev_dbg(dev, "%s - even parity\n", __func__); + } + + if (C_CMSPAR(tty)) { + tty->termios.c_cflag &= ~CMSPAR; + dev_warn(dev, "MARK/SPACE parity is not supported\n"); + } + } else { + request.params |= UPD78F0730_PARITY_NONE; + dev_dbg(dev, "%s - no parity\n", __func__); + } + + if (C_CSTOPB(tty)) { + request.params |= UPD78F0730_STOP_BIT_2_BIT; + dev_dbg(dev, "%s - 2 stop bits\n", __func__); + } else { + request.params |= UPD78F0730_STOP_BIT_1_BIT; + dev_dbg(dev, "%s - 1 stop bit\n", __func__); + } + + if (C_CRTSCTS(tty)) { + tty->termios.c_cflag &= ~CRTSCTS; + dev_warn(dev, "RTSCTS flow control is not supported\n"); + } + if (I_IXOFF(tty) || I_IXON(tty)) { + tty->termios.c_iflag &= ~(IXOFF | IXON); + dev_warn(dev, "XON/XOFF flow control is not supported\n"); + } + request.params |= UPD78F0730_FLOW_CONTROL_NONE; + dev_dbg(dev, "%s - no flow control\n", __func__); + + upd78f0730_send_ctl(port, &request, sizeof(request)); +} + +static int upd78f0730_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + static const struct upd78f0730_open_close request = { + .opcode = UPD78F0730_CMD_OPEN_CLOSE, + .state = UPD78F0730_PORT_OPEN + }; + int res; + + res = upd78f0730_send_ctl(port, &request, sizeof(request)); + if (res) + return res; + + if (tty) + upd78f0730_set_termios(tty, port, NULL); + + return usb_serial_generic_open(tty, port); +} + +static void upd78f0730_close(struct usb_serial_port *port) +{ + static const struct upd78f0730_open_close request = { + .opcode = UPD78F0730_CMD_OPEN_CLOSE, + .state = UPD78F0730_PORT_CLOSE + }; + + usb_serial_generic_close(port); + upd78f0730_send_ctl(port, &request, sizeof(request)); +} + +static struct usb_serial_driver upd78f0730_device = { + .driver = { + .name = "upd78f0730", + }, + .id_table = id_table, + .num_ports = 1, + .port_probe = upd78f0730_port_probe, + .port_remove = upd78f0730_port_remove, + .open = upd78f0730_open, + .close = upd78f0730_close, + .set_termios = upd78f0730_set_termios, + .tiocmget = upd78f0730_tiocmget, + .tiocmset = upd78f0730_tiocmset, + .dtr_rts = upd78f0730_dtr_rts, + .break_ctl = upd78f0730_break_ctl, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &upd78f0730_device, + NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/usb-serial-simple.c b/drivers/usb/serial/usb-serial-simple.c new file mode 100644 index 000000000..a0afaf254 --- /dev/null +++ b/drivers/usb/serial/usb-serial-simple.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Serial "Simple" driver + * + * Copyright (C) 2001-2006,2008,2013 Greg Kroah-Hartman + * Copyright (C) 2005 Arthur Huillet (ahuillet@users.sf.net) + * Copyright (C) 2005 Thomas Hergenhahn + * Copyright (C) 2009 Outpost Embedded, LLC + * Copyright (C) 2010 Zilogic Systems + * Copyright (C) 2013 Wei Shuai + * Copyright (C) 2013 Linux Foundation + */ + +#include +#include +#include +#include +#include + +#define DEVICE_N(vendor, IDS, nport) \ +static const struct usb_device_id vendor##_id_table[] = { \ + IDS(), \ + { }, \ +}; \ +static struct usb_serial_driver vendor##_device = { \ + .driver = { \ + .name = #vendor, \ + }, \ + .id_table = vendor##_id_table, \ + .num_ports = nport, \ +}; + +#define DEVICE(vendor, IDS) DEVICE_N(vendor, IDS, 1) + +/* Medtronic CareLink USB driver */ +#define CARELINK_IDS() \ + { USB_DEVICE(0x0a21, 0x8001) } /* MMT-7305WW */ +DEVICE(carelink, CARELINK_IDS); + +/* Infineon Flashloader driver */ +#define FLASHLOADER_IDS() \ + { USB_DEVICE_INTERFACE_CLASS(0x058b, 0x0041, USB_CLASS_CDC_DATA) }, \ + { USB_DEVICE(0x8087, 0x0716) }, \ + { USB_DEVICE(0x8087, 0x0801) } +DEVICE(flashloader, FLASHLOADER_IDS); + +/* Funsoft Serial USB driver */ +#define FUNSOFT_IDS() \ + { USB_DEVICE(0x1404, 0xcddc) } +DEVICE(funsoft, FUNSOFT_IDS); + +/* Google Serial USB SubClass */ +#define GOOGLE_IDS() \ + { USB_VENDOR_AND_INTERFACE_INFO(0x18d1, \ + USB_CLASS_VENDOR_SPEC, \ + 0x50, \ + 0x01) } +DEVICE(google, GOOGLE_IDS); + +/* HP4x (48/49) Generic Serial driver */ +#define HP4X_IDS() \ + { USB_DEVICE(0x03f0, 0x0121) } +DEVICE(hp4x, HP4X_IDS); + +/* KAUFMANN RKS+CAN VCP */ +#define KAUFMANN_IDS() \ + { USB_DEVICE(0x16d0, 0x0870) } +DEVICE(kaufmann, KAUFMANN_IDS); + +/* Libtransistor USB console */ +#define LIBTRANSISTOR_IDS() \ + { USB_DEVICE(0x1209, 0x8b00) } +DEVICE(libtransistor, LIBTRANSISTOR_IDS); + +/* Motorola USB Phone driver */ +#define MOTO_IDS() \ + { USB_DEVICE(0x05c6, 0x3197) }, /* unknown Motorola phone */ \ + { USB_DEVICE(0x0c44, 0x0022) }, /* unknown Motorola phone */ \ + { USB_DEVICE(0x22b8, 0x2a64) }, /* Motorola KRZR K1m */ \ + { USB_DEVICE(0x22b8, 0x2c84) }, /* Motorola VE240 phone */ \ + { USB_DEVICE(0x22b8, 0x2c64) } /* Motorola V950 phone */ +DEVICE(moto_modem, MOTO_IDS); + +/* Motorola Tetra driver */ +#define MOTOROLA_TETRA_IDS() \ + { USB_DEVICE(0x0cad, 0x9011) }, /* Motorola Solutions TETRA PEI */ \ + { USB_DEVICE(0x0cad, 0x9012) }, /* MTP6550 */ \ + { USB_DEVICE(0x0cad, 0x9013) }, /* MTP3xxx */ \ + { USB_DEVICE(0x0cad, 0x9015) }, /* MTP85xx */ \ + { USB_DEVICE(0x0cad, 0x9016) } /* TPG2200 */ +DEVICE(motorola_tetra, MOTOROLA_TETRA_IDS); + +/* Nokia mobile phone driver */ +#define NOKIA_IDS() \ + { USB_DEVICE(0x0421, 0x069a) } /* Nokia 130 (RM-1035) */ +DEVICE(nokia, NOKIA_IDS); + +/* Novatel Wireless GPS driver */ +#define NOVATEL_IDS() \ + { USB_DEVICE(0x09d7, 0x0100) } /* NovAtel FlexPack GPS */ +DEVICE_N(novatel_gps, NOVATEL_IDS, 3); + +/* OWON electronic test and measurement equipment driver */ +#define OWON_IDS() \ + { USB_DEVICE(0x5345, 0x1234) } /* HDS200 oscilloscopes and others */ +DEVICE(owon, OWON_IDS); + +/* Siemens USB/MPI adapter */ +#define SIEMENS_IDS() \ + { USB_DEVICE(0x908, 0x0004) } +DEVICE(siemens_mpi, SIEMENS_IDS); + +/* Suunto ANT+ USB Driver */ +#define SUUNTO_IDS() \ + { USB_DEVICE(0x0fcf, 0x1008) }, \ + { USB_DEVICE(0x0fcf, 0x1009) } /* Dynastream ANT USB-m Stick */ +DEVICE(suunto, SUUNTO_IDS); + +/* ViVOpay USB Serial Driver */ +#define VIVOPAY_IDS() \ + { USB_DEVICE(0x1d5f, 0x1004) } /* ViVOpay 8800 */ +DEVICE(vivopay, VIVOPAY_IDS); + +/* ZIO Motherboard USB driver */ +#define ZIO_IDS() \ + { USB_DEVICE(0x1CBE, 0x0103) } +DEVICE(zio, ZIO_IDS); + +/* All of the above structures mushed into two lists */ +static struct usb_serial_driver * const serial_drivers[] = { + &carelink_device, + &flashloader_device, + &funsoft_device, + &google_device, + &hp4x_device, + &kaufmann_device, + &libtransistor_device, + &moto_modem_device, + &motorola_tetra_device, + &nokia_device, + &novatel_gps_device, + &owon_device, + &siemens_mpi_device, + &suunto_device, + &vivopay_device, + &zio_device, + NULL +}; + +static const struct usb_device_id id_table[] = { + CARELINK_IDS(), + FLASHLOADER_IDS(), + FUNSOFT_IDS(), + GOOGLE_IDS(), + HP4X_IDS(), + KAUFMANN_IDS(), + LIBTRANSISTOR_IDS(), + MOTO_IDS(), + MOTOROLA_TETRA_IDS(), + NOKIA_IDS(), + NOVATEL_IDS(), + OWON_IDS(), + SIEMENS_IDS(), + SUUNTO_IDS(), + VIVOPAY_IDS(), + ZIO_IDS(), + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +module_usb_serial_driver(serial_drivers, id_table); +MODULE_DESCRIPTION("USB Serial 'Simple' driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c new file mode 100644 index 000000000..17edc057a --- /dev/null +++ b/drivers/usb/serial/usb-serial.c @@ -0,0 +1,1550 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 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.rst 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 + +#define DRIVER_AUTHOR "Greg Kroah-Hartman " +#define DRIVER_DESC "USB Serial Driver core" + +#define USB_SERIAL_TTY_MAJOR 188 +#define USB_SERIAL_TTY_MINORS 512 /* should be enough for a while */ + +/* 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 DEFINE_IDR(serial_minors); +static DEFINE_MUTEX(table_lock); +static LIST_HEAD(usb_serial_driver_list); + +/* + * Look up the serial port structure. If it is found and it hasn't been + * disconnected, return with the parent usb_serial structure's disc_mutex held + * and its refcount incremented. Otherwise return NULL. + */ +struct usb_serial_port *usb_serial_port_get_by_minor(unsigned minor) +{ + struct usb_serial *serial; + struct usb_serial_port *port; + + mutex_lock(&table_lock); + port = idr_find(&serial_minors, minor); + if (!port) + goto exit; + + serial = port->serial; + mutex_lock(&serial->disc_mutex); + if (serial->disconnected) { + mutex_unlock(&serial->disc_mutex); + port = NULL; + } else { + kref_get(&serial->kref); + } +exit: + mutex_unlock(&table_lock); + return port; +} + +static int allocate_minors(struct usb_serial *serial, int num_ports) +{ + struct usb_serial_port *port; + unsigned int i, j; + int minor; + + dev_dbg(&serial->interface->dev, "%s %d\n", __func__, num_ports); + + mutex_lock(&table_lock); + for (i = 0; i < num_ports; ++i) { + port = serial->port[i]; + minor = idr_alloc(&serial_minors, port, 0, + USB_SERIAL_TTY_MINORS, GFP_KERNEL); + if (minor < 0) + goto error; + port->minor = minor; + port->port_number = i; + } + serial->minors_reserved = 1; + mutex_unlock(&table_lock); + return 0; +error: + /* unwind the already allocated minors */ + for (j = 0; j < i; ++j) + idr_remove(&serial_minors, serial->port[j]->minor); + mutex_unlock(&table_lock); + return minor; +} + +static void release_minors(struct usb_serial *serial) +{ + int i; + + mutex_lock(&table_lock); + for (i = 0; i < serial->num_ports; ++i) + idr_remove(&serial_minors, serial->port[i]->minor); + mutex_unlock(&table_lock); + serial->minors_reserved = 0; +} + +int usb_serial_claim_interface(struct usb_serial *serial, struct usb_interface *intf) +{ + struct usb_driver *driver = serial->type->usb_driver; + int ret; + + if (serial->sibling) + return -EBUSY; + + ret = usb_driver_claim_interface(driver, intf, serial); + if (ret) { + dev_err(&serial->interface->dev, + "failed to claim sibling interface: %d\n", ret); + return ret; + } + + serial->sibling = intf; + + return 0; +} +EXPORT_SYMBOL_GPL(usb_serial_claim_interface); + +static void release_sibling(struct usb_serial *serial, struct usb_interface *intf) +{ + struct usb_driver *driver = serial->type->usb_driver; + struct usb_interface *sibling; + + if (!serial->sibling) + return; + + if (intf == serial->sibling) + sibling = serial->interface; + else + sibling = serial->sibling; + + usb_set_intfdata(sibling, NULL); + usb_driver_release_interface(driver, sibling); +} + +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->minors_reserved) + release_minors(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 + * + * Initialise the termios structure for this tty. We use the default + * USB serial settings but permit them to be overridden by + * serial->type->init_termios on first open. + * + * 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. 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; + bool init_termios; + int retval = -ENODEV; + + port = usb_serial_port_get_by_minor(idx); + if (!port) + return retval; + + serial = port->serial; + if (!try_module_get(serial->type->driver.owner)) + goto err_put_serial; + + init_termios = (driver->termios[idx] == NULL); + + retval = tty_standard_install(driver, tty); + if (retval) + goto err_put_module; + + mutex_unlock(&serial->disc_mutex); + + /* allow the driver to update the initial settings */ + if (init_termios && serial->type->init_termios) + serial->type->init_termios(tty); + + tty->driver_data = port; + + return retval; + +err_put_module: + module_put(serial->type->driver.owner); +err_put_serial: + 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; + goto out_unlock; + } + + retval = usb_autopm_get_interface(serial->interface); + if (retval) + goto out_unlock; + + retval = port->serial->type->open(tty, port); + if (retval) + usb_autopm_put_interface(serial->interface); +out_unlock: + 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(&port->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 tty-port 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); + + usb_autopm_put_interface(port->serial->interface); +} + +static void serial_hangup(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->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(&port->dev, "%s\n", __func__); + + tty_port_close(&port->port, tty, filp); +} + +/** + * serial_cleanup - free resources post close/hangup + * @tty: tty to clean 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(&port->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; + + usb_serial_put(serial); + module_put(owner); +} + +static ssize_t serial_write(struct tty_struct *tty, const u8 *buf, size_t count) +{ + struct usb_serial_port *port = tty->driver_data; + int retval = -ENODEV; + + if (port->serial->dev->state == USB_STATE_NOTATTACHED) + goto exit; + + dev_dbg(&port->dev, "%s - %zu 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 unsigned int serial_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->dev, "%s\n", __func__); + + return port->serial->type->write_room(tty); +} + +static unsigned 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(&port->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(&port->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(&port->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(&port->dev, "%s\n", __func__); + + if (port->serial->type->unthrottle) + port->serial->type->unthrottle(tty); +} + +static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) +{ + struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + unsigned int close_delay, closing_wait; + + mutex_lock(&tport->mutex); + + close_delay = jiffies_to_msecs(tport->close_delay) / 10; + closing_wait = tport->closing_wait; + if (closing_wait != ASYNC_CLOSING_WAIT_NONE) + closing_wait = jiffies_to_msecs(closing_wait) / 10; + + ss->line = port->minor; + ss->close_delay = close_delay; + ss->closing_wait = closing_wait; + + if (port->serial->type->get_serial) + port->serial->type->get_serial(tty, ss); + + mutex_unlock(&tport->mutex); + + return 0; +} + +static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) +{ + struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + unsigned int close_delay, closing_wait; + int ret = 0; + + close_delay = msecs_to_jiffies(ss->close_delay * 10); + closing_wait = ss->closing_wait; + if (closing_wait != ASYNC_CLOSING_WAIT_NONE) + closing_wait = msecs_to_jiffies(closing_wait * 10); + + mutex_lock(&tport->mutex); + + if (!capable(CAP_SYS_ADMIN)) { + if (close_delay != tport->close_delay || + closing_wait != tport->closing_wait) { + ret = -EPERM; + goto out_unlock; + } + } + + if (port->serial->type->set_serial) { + ret = port->serial->type->set_serial(tty, ss); + if (ret) + goto out_unlock; + } + + tport->close_delay = close_delay; + tport->closing_wait = closing_wait; +out_unlock: + mutex_unlock(&tport->mutex); + + return ret; +} + +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(&port->dev, "%s - cmd 0x%04x\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, + const struct ktermios *old) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->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(&port->dev, "%s\n", __func__); + + if (port->serial->type->break_ctl) + return port->serial->type->break_ctl(tty, break_state); + + return -ENOTTY; +} + +static int serial_proc_show(struct seq_file *m, void *v) +{ + struct usb_serial *serial; + struct usb_serial_port *port; + int i; + char tmp[40]; + + seq_puts(m, "usbserinfo:1.0 driver:2.0\n"); + for (i = 0; i < USB_SERIAL_TTY_MINORS; ++i) { + port = usb_serial_port_get_by_minor(i); + if (port == NULL) + continue; + serial = port->serial; + + 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", port->port_number); + 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_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->dev, "%s\n", __func__); + + if (port->serial->type->tiocmget) + return port->serial->type->tiocmget(tty); + return -ENOTTY; +} + +static int serial_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->dev, "%s\n", __func__); + + if (port->serial->type->tiocmset) + return port->serial->type->tiocmset(tty, set, clear); + return -ENOTTY; +} + +static int serial_get_icount(struct tty_struct *tty, + struct serial_icounter_struct *icount) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->dev, "%s\n", __func__); + + if (port->serial->type->get_icount) + return port->serial->type->get_icount(tty, icount); + return -ENOTTY; +} + +/* + * 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_obj(*serial); + 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->minors_reserved = 0; + + return serial; +} + +static const struct usb_device_id *match_dynamic_id(struct usb_interface *intf, + struct usb_serial_driver *drv) +{ + struct usb_dynid *dynid; + + guard(mutex)(&usb_dynids_lock); + list_for_each_entry(dynid, &drv->dynids.list, node) { + if (usb_match_one_id(intf, &dynid->id)) { + return &dynid->id; + } + } + 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 bool 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 true; +} + +static void serial_port_dtr_rts(struct tty_port *port, bool on) +{ + struct usb_serial_port *p = container_of(port, struct usb_serial_port, port); + struct usb_serial_driver *drv = p->serial->type; + + if (drv->dtr_rts) + drv->dtr_rts(p, on); +} + +static ssize_t port_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_serial_port *port = to_usb_serial_port(dev); + + return sprintf(buf, "%u\n", port->port_number); +} +static DEVICE_ATTR_RO(port_number); + +static struct attribute *usb_serial_port_attrs[] = { + &dev_attr_port_number.attr, + NULL +}; +ATTRIBUTE_GROUPS(usb_serial_port); + +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 void store_endpoint(struct usb_serial *serial, + struct usb_serial_endpoints *epds, + struct usb_endpoint_descriptor *epd) +{ + struct device *dev = &serial->interface->dev; + u8 addr = epd->bEndpointAddress; + + if (usb_endpoint_is_bulk_in(epd)) { + if (epds->num_bulk_in == ARRAY_SIZE(epds->bulk_in)) + return; + dev_dbg(dev, "found bulk in endpoint %02x\n", addr); + epds->bulk_in[epds->num_bulk_in++] = epd; + } else if (usb_endpoint_is_bulk_out(epd)) { + if (epds->num_bulk_out == ARRAY_SIZE(epds->bulk_out)) + return; + dev_dbg(dev, "found bulk out endpoint %02x\n", addr); + epds->bulk_out[epds->num_bulk_out++] = epd; + } else if (usb_endpoint_is_int_in(epd)) { + if (epds->num_interrupt_in == ARRAY_SIZE(epds->interrupt_in)) + return; + dev_dbg(dev, "found interrupt in endpoint %02x\n", addr); + epds->interrupt_in[epds->num_interrupt_in++] = epd; + } else if (usb_endpoint_is_int_out(epd)) { + if (epds->num_interrupt_out == ARRAY_SIZE(epds->interrupt_out)) + return; + dev_dbg(dev, "found interrupt out endpoint %02x\n", addr); + epds->interrupt_out[epds->num_interrupt_out++] = epd; + } +} + +static void find_endpoints(struct usb_serial *serial, + struct usb_serial_endpoints *epds, + struct usb_interface *intf) +{ + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *epd; + unsigned int i; + + iface_desc = intf->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + epd = &iface_desc->endpoint[i].desc; + store_endpoint(serial, epds, epd); + } +} + +static int setup_port_bulk_in(struct usb_serial_port *port, + struct usb_endpoint_descriptor *epd) +{ + struct usb_serial_driver *type = port->serial->type; + struct usb_device *udev = port->serial->dev; + int buffer_size; + int i; + + buffer_size = max_t(int, type->bulk_in_size, usb_endpoint_maxp(epd)); + port->bulk_in_size = buffer_size; + port->bulk_in_endpointAddress = epd->bEndpointAddress; + + for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) { + set_bit(i, &port->read_urbs_free); + port->read_urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!port->read_urbs[i]) + return -ENOMEM; + port->bulk_in_buffers[i] = kmalloc(buffer_size, GFP_KERNEL); + if (!port->bulk_in_buffers[i]) + return -ENOMEM; + usb_fill_bulk_urb(port->read_urbs[i], udev, + usb_rcvbulkpipe(udev, epd->bEndpointAddress), + port->bulk_in_buffers[i], buffer_size, + type->read_bulk_callback, port); + } + + port->read_urb = port->read_urbs[0]; + port->bulk_in_buffer = port->bulk_in_buffers[0]; + + return 0; +} + +static int setup_port_bulk_out(struct usb_serial_port *port, + struct usb_endpoint_descriptor *epd) +{ + struct usb_serial_driver *type = port->serial->type; + struct usb_device *udev = port->serial->dev; + int buffer_size; + int i; + + if (kfifo_alloc(&port->write_fifo, PAGE_SIZE, GFP_KERNEL)) + return -ENOMEM; + if (type->bulk_out_size) + buffer_size = type->bulk_out_size; + else + buffer_size = usb_endpoint_maxp(epd); + port->bulk_out_size = buffer_size; + port->bulk_out_endpointAddress = epd->bEndpointAddress; + + for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) { + set_bit(i, &port->write_urbs_free); + port->write_urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!port->write_urbs[i]) + return -ENOMEM; + port->bulk_out_buffers[i] = kmalloc(buffer_size, GFP_KERNEL); + if (!port->bulk_out_buffers[i]) + return -ENOMEM; + usb_fill_bulk_urb(port->write_urbs[i], udev, + usb_sndbulkpipe(udev, epd->bEndpointAddress), + port->bulk_out_buffers[i], buffer_size, + type->write_bulk_callback, port); + } + + port->write_urb = port->write_urbs[0]; + port->bulk_out_buffer = port->bulk_out_buffers[0]; + + return 0; +} + +static int setup_port_interrupt_in(struct usb_serial_port *port, + struct usb_endpoint_descriptor *epd) +{ + struct usb_serial_driver *type = port->serial->type; + struct usb_device *udev = port->serial->dev; + int buffer_size; + + port->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!port->interrupt_in_urb) + return -ENOMEM; + buffer_size = usb_endpoint_maxp(epd); + port->interrupt_in_endpointAddress = epd->bEndpointAddress; + port->interrupt_in_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!port->interrupt_in_buffer) + return -ENOMEM; + usb_fill_int_urb(port->interrupt_in_urb, udev, + usb_rcvintpipe(udev, epd->bEndpointAddress), + port->interrupt_in_buffer, buffer_size, + type->read_int_callback, port, + epd->bInterval); + + return 0; +} + +static int setup_port_interrupt_out(struct usb_serial_port *port, + struct usb_endpoint_descriptor *epd) +{ + struct usb_serial_driver *type = port->serial->type; + struct usb_device *udev = port->serial->dev; + int buffer_size; + + port->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!port->interrupt_out_urb) + return -ENOMEM; + buffer_size = usb_endpoint_maxp(epd); + port->interrupt_out_size = buffer_size; + port->interrupt_out_endpointAddress = epd->bEndpointAddress; + port->interrupt_out_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!port->interrupt_out_buffer) + return -ENOMEM; + usb_fill_int_urb(port->interrupt_out_urb, udev, + usb_sndintpipe(udev, epd->bEndpointAddress), + port->interrupt_out_buffer, buffer_size, + type->write_int_callback, port, + epd->bInterval); + + return 0; +} + +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_serial_endpoints *epds; + struct usb_serial_driver *type = NULL; + int retval; + int i; + int num_ports = 0; + unsigned char 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) { + retval = -ENOMEM; + goto err_put_module; + } + + /* 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"); + goto err_release_sibling; + } + } + + /* descriptor matches, let's find the endpoints needed */ + epds = kzalloc_obj(*epds); + if (!epds) { + retval = -ENOMEM; + goto err_release_sibling; + } + + find_endpoints(serial, epds, interface); + if (serial->sibling) + find_endpoints(serial, epds, serial->sibling); + + if (epds->num_bulk_in < type->num_bulk_in || + epds->num_bulk_out < type->num_bulk_out || + epds->num_interrupt_in < type->num_interrupt_in || + epds->num_interrupt_out < type->num_interrupt_out) { + dev_err(ddev, "required endpoints missing\n"); + retval = -ENODEV; + goto err_free_epds; + } + + if (type->calc_num_ports) { + retval = type->calc_num_ports(serial, epds); + if (retval < 0) + goto err_free_epds; + num_ports = retval; + } + + 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 = (unsigned char)num_ports; + serial->num_bulk_in = epds->num_bulk_in; + serial->num_bulk_out = epds->num_bulk_out; + serial->num_interrupt_in = epds->num_interrupt_in; + serial->num_interrupt_out = epds->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(epds->num_bulk_in, epds->num_bulk_out); + max_endpoints = max(max_endpoints, epds->num_interrupt_in); + max_endpoints = max(max_endpoints, epds->num_interrupt_out); + max_endpoints = max(max_endpoints, serial->num_ports); + serial->num_port_pointers = max_endpoints; + + dev_dbg(ddev, "setting up %d port structure(s)\n", max_endpoints); + for (i = 0; i < max_endpoints; ++i) { + port = kzalloc_obj(struct usb_serial_port); + if (!port) { + retval = -ENOMEM; + goto err_free_epds; + } + 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; + port->dev.groups = usb_serial_port_groups; + device_initialize(&port->dev); + } + + /* set up the endpoint information */ + for (i = 0; i < epds->num_bulk_in; ++i) { + retval = setup_port_bulk_in(serial->port[i], epds->bulk_in[i]); + if (retval) + goto err_free_epds; + } + + for (i = 0; i < epds->num_bulk_out; ++i) { + retval = setup_port_bulk_out(serial->port[i], + epds->bulk_out[i]); + if (retval) + goto err_free_epds; + } + + if (serial->type->read_int_callback) { + for (i = 0; i < epds->num_interrupt_in; ++i) { + retval = setup_port_interrupt_in(serial->port[i], + epds->interrupt_in[i]); + if (retval) + goto err_free_epds; + } + } else if (epds->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 < epds->num_interrupt_out; ++i) { + retval = setup_port_interrupt_out(serial->port[i], + epds->interrupt_out[i]); + if (retval) + goto err_free_epds; + } + } else if (epds->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 err_free_epds; + 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; + } + + retval = allocate_minors(serial, num_ports); + if (retval) { + dev_err(ddev, "No more free serial minor numbers\n"); + goto err_free_epds; + } + + /* 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->minor); + dev_dbg(ddev, "registering %s\n", 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"); + } + + if (num_ports > 0) + usb_serial_console_init(serial->port[0]->minor); +exit: + kfree(epds); + module_put(type->driver.owner); + return 0; + +err_free_epds: + kfree(epds); +err_release_sibling: + release_sibling(serial, interface); + usb_serial_put(serial); +err_put_module: + module_put(type->driver.owner); + + return retval; +} + +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; + + /* sibling interface is cleaning up */ + if (!serial) + return; + + 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]; + tty_port_tty_vhangup(&port->port); + 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); + + release_sibling(serial, interface); + + /* 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); + int i, r; + + /* suspend when called for first sibling interface */ + if (serial->suspend_count++) + return 0; + + /* + * 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->suspend_count--; + return r; + } + } + + for (i = 0; i < serial->num_ports; ++i) + usb_serial_port_poison_urbs(serial->port[i]); + + return 0; +} +EXPORT_SYMBOL(usb_serial_suspend); + +static void usb_serial_unpoison_port_urbs(struct usb_serial *serial) +{ + int i; + + for (i = 0; i < serial->num_ports; ++i) + usb_serial_port_unpoison_urbs(serial->port[i]); +} + +int usb_serial_resume(struct usb_interface *intf) +{ + struct usb_serial *serial = usb_get_intfdata(intf); + int rv; + + /* resume when called for last sibling interface */ + if (--serial->suspend_count) + return 0; + + usb_serial_unpoison_port_urbs(serial); + + 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; + + /* resume when called for last sibling interface */ + if (--serial->suspend_count) + return 0; + + usb_serial_unpoison_port_urbs(serial); + + 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, + .set_serial = serial_set_serial, + .get_serial = serial_get_serial, + .cleanup = serial_cleanup, + .install = serial_install, + .proc_show = serial_proc_show, +}; + + +struct tty_driver *usb_serial_tty_driver; + +static int __init usb_serial_init(void) +{ + int result; + + usb_serial_tty_driver = tty_alloc_driver(USB_SERIAL_TTY_MINORS, + TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); + if (IS_ERR(usb_serial_tty_driver)) + return PTR_ERR(usb_serial_tty_driver); + + /* Initialize our global data */ + result = bus_register(&usb_serial_bus_type); + if (result) { + pr_err("%s - registering bus driver failed\n", __func__); + goto err_put_driver; + } + + usb_serial_tty_driver->driver_name = "usbserial"; + usb_serial_tty_driver->name = "ttyUSB"; + usb_serial_tty_driver->major = USB_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->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 err_unregister_bus; + } + + /* 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 err_unregister_driver; + } + + return result; + +err_unregister_driver: + tty_unregister_driver(usb_serial_tty_driver); +err_unregister_bus: + bus_unregister(&usb_serial_bus_type); +err_put_driver: + pr_err("%s - returning with error %d\n", __func__, result); + tty_driver_kref_put(usb_serial_tty_driver); + return result; +} + + +static void __exit usb_serial_exit(void) +{ + usb_serial_console_exit(); + + usb_serial_generic_deregister(); + + tty_unregister_driver(usb_serial_tty_driver); + tty_driver_kref_put(usb_serial_tty_driver); + bus_unregister(&usb_serial_bus_type); + idr_destroy(&serial_minors); +} + + +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; + } + + /* Prevent individual ports from being unbound. */ + driver->driver.suppress_bind_attrs = true; + + 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 + * @owner: owning module + * @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[], + struct module *owner, 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_obj(*udriver); + 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) + goto err_free_driver; + + for (sd = serial_drivers; *sd; ++sd) { + (*sd)->usb_driver = udriver; + (*sd)->driver.owner = owner; + rc = usb_serial_register(*sd); + if (rc) + goto err_deregister_drivers; + } + + /* Now set udriver's id_table and look for matches */ + udriver->id_table = id_table; + rc = driver_attach(&udriver->driver); + return 0; + +err_deregister_drivers: + while (sd-- > serial_drivers) + usb_serial_deregister(*sd); + usb_deregister(udriver); +err_free_driver: + kfree(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 v2"); diff --git a/drivers/usb/serial/usb-wwan.h b/drivers/usb/serial/usb-wwan.h new file mode 100644 index 000000000..519101945 --- /dev/null +++ b/drivers/usb/serial/usb-wwan.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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 void usb_wwan_port_remove(struct usb_serial_port *port); +extern unsigned int usb_wwan_write_room(struct tty_struct *tty); +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_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count); +extern unsigned 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; + unsigned int use_send_setup:1; + unsigned int use_zlp:1; + int in_flight; + unsigned int open_ports; + 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 */ + 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 000000000..ec9fff794 --- /dev/null +++ b/drivers/usb/serial/usb_debug.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Debug cable driver + * + * Copyright (C) 2006 Greg Kroah-Hartman + */ + +#include +#include +#include +#include +#include +#include + +#define USB_DEBUG_MAX_PACKET_SIZE 8 +#define USB_DEBUG_BRK_SIZE 8 +static const 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) }, + { }, +}; + +static const struct usb_device_id dbc_id_table[] = { + { USB_DEVICE(0x1d6b, 0x0010) }, + { USB_DEVICE(0x1d6b, 0x0011) }, + { }, +}; + +static const struct usb_device_id id_table_combined[] = { + { USB_DEVICE(0x0525, 0x127a) }, + { USB_DEVICE(0x1d6b, 0x0010) }, + { USB_DEVICE(0x1d6b, 0x0011) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table_combined); + +/* This HW really does not support a serial break, so one will be + * emulated when ever the break state is set to true. + */ +static int usb_debug_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + int ret; + + if (!break_state) + return 0; + + ret = usb_serial_generic_write(tty, port, USB_DEBUG_BRK, USB_DEBUG_BRK_SIZE); + if (ret < 0) + return ret; + + return 0; +} + +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 void usb_debug_init_termios(struct tty_struct *tty) +{ + tty->termios.c_lflag &= ~(ECHO | ECHONL); +} + +static struct usb_serial_driver debug_device = { + .driver = { + .name = "debug", + }, + .id_table = id_table, + .num_ports = 1, + .bulk_out_size = USB_DEBUG_MAX_PACKET_SIZE, + .break_ctl = usb_debug_break_ctl, + .init_termios = usb_debug_init_termios, + .process_read_urb = usb_debug_process_read_urb, +}; + +static struct usb_serial_driver dbc_device = { + .driver = { + .name = "xhci_dbc", + }, + .id_table = dbc_id_table, + .num_ports = 1, + .break_ctl = usb_debug_break_ctl, + .init_termios = usb_debug_init_termios, + .process_read_urb = usb_debug_process_read_urb, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &debug_device, &dbc_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table_combined); +MODULE_DESCRIPTION("USB Debug cable driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c new file mode 100644 index 000000000..a183cc451 --- /dev/null +++ b/drivers/usb/serial/usb_wwan.c @@ -0,0 +1,657 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + USB Driver layer for GSM modems + + Copyright (C) 2005 Matthias Urlichs + + 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" + +/* + * Generate DTR/RTS signals on the port using the SET_CONTROL_LINE_STATE request + * in CDC ACM. + */ +static int usb_wwan_send_setup(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct usb_wwan_port_private *portdata; + int val = 0; + int ifnum; + int res; + + portdata = usb_get_serial_port_data(port); + + if (portdata->dtr_state) + val |= USB_CDC_CTRL_DTR; + if (portdata->rts_state) + val |= USB_CDC_CTRL_RTS; + + ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber; + + res = usb_autopm_get_interface(serial->interface); + if (res) + return res; + + res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + USB_CDC_REQ_SET_CONTROL_LINE_STATE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + val, ifnum, NULL, 0, USB_CTRL_SET_TIMEOUT); + + usb_autopm_put_interface(port->serial->interface); + + return res; +} + +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 = usb_get_serial_data(port->serial); + + if (!intfdata->use_send_setup) + return; + + portdata = usb_get_serial_port_data(port); + /* FIXME: locking */ + portdata->rts_state = on; + portdata->dtr_state = on; + + usb_wwan_send_setup(port); +} +EXPORT_SYMBOL(usb_wwan_dtr_rts); + +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 = usb_get_serial_data(port->serial); + + if (!intfdata->use_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 usb_wwan_send_setup(port); +} +EXPORT_SYMBOL(usb_wwan_tiocmset); + +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 = usb_get_serial_data(port->serial); + + dev_dbg(&port->dev, "%s: write (%d chars)\n", __func__, count); + + 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_err(&port->dev, + "%s: submit urb %d failed: %d\n", + __func__, i, 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); + + /* don't resubmit on fatal errors */ + if (status == -ESHUTDOWN || status == -ENOENT) + return; + } 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 && err != -ENODEV) { + 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; + unsigned long flags; + int i; + + port = urb->context; + intfdata = usb_get_serial_data(port->serial); + + usb_serial_port_softint(port); + usb_autopm_put_interface_async(port->serial->interface); + portdata = usb_get_serial_port_data(port); + spin_lock_irqsave(&intfdata->susp_lock, flags); + intfdata->in_flight--; + spin_unlock_irqrestore(&intfdata->susp_lock, flags); + + for (i = 0; i < N_OUT_URB; ++i) { + if (portdata->out_urbs[i] == urb) { + smp_mb__before_atomic(); + clear_bit(i, &portdata->out_busy); + break; + } + } +} + +unsigned 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; + unsigned 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: %u\n", __func__, data_len); + return data_len; +} +EXPORT_SYMBOL(usb_wwan_write_room); + +unsigned 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; + unsigned 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: %u\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 = usb_get_serial_data(serial); + + if (port->interrupt_in_urb) { + err = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (err) { + dev_err(&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_err(&port->dev, + "%s: submit read urb %d failed: %d\n", + __func__, i, err); + } + } + + spin_lock_irq(&intfdata->susp_lock); + if (++intfdata->open_ports == 1) + serial->interface->needs_remote_wakeup = 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 = usb_get_serial_data(serial); + struct urb *urb; + + portdata = usb_get_serial_port_data(port); + + /* + * Need to take susp_lock to make sure port is not already being + * resumed, but no need to hold it due to the tty-port initialized + * flag. + */ + spin_lock_irq(&intfdata->susp_lock); + 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; + 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); + + usb_autopm_get_interface_no_resume(serial->interface); +} +EXPORT_SYMBOL(usb_wwan_close); + +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 usb_wwan_intf_private *intfdata = usb_get_serial_data(serial); + struct urb *urb; + + urb = usb_alloc_urb(0, GFP_KERNEL); /* No ISO */ + if (!urb) + return NULL; + + usb_fill_bulk_urb(urb, serial->dev, + usb_sndbulkpipe(serial->dev, endpoint) | dir, + buf, len, callback, ctx); + + if (intfdata->use_zlp && dir == USB_DIR_OUT) + urb->transfer_flags |= URB_ZERO_PACKET; + + 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_obj(*portdata); + 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); + +void 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); + + for (i = 0; i < N_IN_URB; 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_free_urb(portdata->out_urbs[i]); + kfree(portdata->out_buffer[i]); + } + + kfree(portdata); +} +EXPORT_SYMBOL(usb_wwan_port_remove); + +#ifdef CONFIG_PM +static void stop_urbs(struct usb_serial *serial) +{ + int i, j; + struct usb_serial_port *port; + struct usb_wwan_port_private *portdata; + + 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]); + usb_kill_urb(port->interrupt_in_urb); + } +} + +int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message) +{ + struct usb_wwan_intf_private *intfdata = usb_get_serial_data(serial); + + 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_urbs(serial); + + return 0; +} +EXPORT_SYMBOL(usb_wwan_suspend); + +/* Caller must hold susp_lock. */ +static int usb_wwan_submit_delayed_urbs(struct usb_serial_port *port) +{ + struct usb_serial *serial = port->serial; + struct usb_wwan_intf_private *data = usb_get_serial_data(serial); + struct usb_wwan_port_private *portdata; + struct urb *urb; + int err_count = 0; + int err; + + portdata = usb_get_serial_port_data(port); + + for (;;) { + urb = usb_get_from_anchor(&portdata->delayed); + if (!urb) + break; + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + dev_err(&port->dev, "%s: submit urb failed: %d\n", + __func__, err); + err_count++; + unbusy_queued_urb(urb, portdata); + usb_autopm_put_interface_async(serial->interface); + continue; + } + data->in_flight++; + } + + if (err_count) + return -EIO; + + return 0; +} + +int usb_wwan_resume(struct usb_serial *serial) +{ + int i, j; + struct usb_serial_port *port; + struct usb_wwan_intf_private *intfdata = usb_get_serial_data(serial); + 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++) { + port = serial->port[i]; + + if (!tty_port_initialized(&port->port)) + continue; + + portdata = usb_get_serial_port_data(port); + + 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 = usb_wwan_submit_delayed_urbs(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: submit read urb %d failed: %d\n", + __func__, i, err); + 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 v2"); diff --git a/drivers/usb/serial/visor.c b/drivers/usb/serial/visor.c new file mode 100644 index 000000000..5706c46c4 --- /dev/null +++ b/drivers/usb/serial/visor.c @@ -0,0 +1,573 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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) + * + * See Documentation/usb/usb-serial.rst for more information on using this + * driver + * + */ + +#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, + struct usb_serial_endpoints *epds); +static int clie_5_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds); +static void visor_read_int_callback(struct urb *urb); +static int clie_3_5_startup(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 const 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_INTERFACE_CLASS(SAMSUNG_VENDOR_ID, SAMSUNG_SCH_I330_ID, 0xff), + .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 const 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 const struct usb_device_id clie_id_3_5_table[] = { + { USB_DEVICE(SONY_VENDOR_ID, SONY_CLIE_3_5_ID) }, + { } /* Terminating entry */ +}; + +static const 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 = { + .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, + .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 = { + .name = "clie_5", + }, + .description = "Sony Clie 5.0", + .id_table = clie_id_5_table, + .num_ports = 2, + .num_bulk_out = 2, + .bulk_out_size = 256, + .open = visor_open, + .close = visor_close, + .throttle = usb_serial_generic_throttle, + .unthrottle = usb_serial_generic_unthrottle, + .probe = visor_probe, + .calc_num_ports = clie_5_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 = { + .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) + 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)) { + dev_err(dev, "Invalid connection information received from device\n"); + retval = -ENODEV; + goto exit; + } + + connection_info = (struct visor_connection_info *)transfer_buffer; + + num_ports = le16_to_cpu(connection_info->num_ports); + + /* 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; + } + + 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); + } + 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) + 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, + struct usb_serial_endpoints *epds) +{ + unsigned int vid = le16_to_cpu(serial->dev->descriptor.idVendor); + int num_ports = (int)(long)(usb_get_serial_data(serial)); + + if (num_ports) + usb_set_serial_data(serial, NULL); + + /* + * Only swap the bulk endpoints for the Handspring devices with + * interrupt in endpoints, which for now are the Treo devices. + */ + if (!(vid == HANDSPRING_VENDOR_ID || vid == KYOCERA_VENDOR_ID) || + epds->num_interrupt_in == 0) + goto out; + + if (epds->num_bulk_in < 2 || epds->num_interrupt_in < 2) { + dev_err(&serial->interface->dev, "missing endpoints\n"); + return -ENODEV; + } + + /* + * 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. + */ + swap(epds->bulk_in[0], epds->bulk_in[1]); + swap(epds->interrupt_in[0], epds->interrupt_in[1]); +out: + return num_ports; +} + +static int clie_5_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + /* + * TH55 registers 2 ports. + * Communication in from the UX50/TH55 uses the first bulk-in + * endpoint, while communication out to the UX50/TH55 uses the second + * bulk-out endpoint. + */ + + /* + * FIXME: Should we swap the descriptors instead of using the same + * bulk-out endpoint for both ports? + */ + epds->bulk_out[0] = epds->bulk_out[1]; + + return serial->type->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; +} + +module_usb_serial_driver(serial_drivers, id_table_combined); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/visor.h b/drivers/usb/serial/visor.h new file mode 100644 index 000000000..622d639ce --- /dev/null +++ b/drivers/usb/serial/visor.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * USB HandSpring Visor driver + * + * Copyright (C) 1999 - 2003 + * Greg Kroah-Hartman (greg@kroah.com) + * + * See Documentation/usb/usb-serial.rst 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 application 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/whiteheat.c b/drivers/usb/serial/whiteheat.c new file mode 100644 index 000000000..a2d83f69e --- /dev/null +++ b/drivers/usb/serial/whiteheat.c @@ -0,0 +1,802 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * USB ConnectTech WhiteHEAT driver + * + * Copyright (C) 2002 + * Connect Tech Inc. + * + * Copyright (C) 1999 - 2001 + * Greg Kroah-Hartman (greg@kroah.com) + * + * See Documentation/usb/usb-serial.rst for more information on using this + * driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "whiteheat.h" /* WhiteHEAT specific commands */ + +/* + * 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 void 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 void whiteheat_get_serial(struct tty_struct *tty, + struct serial_struct *ss); +static void whiteheat_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios); +static int whiteheat_tiocmget(struct tty_struct *tty); +static int whiteheat_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear); +static int whiteheat_break_ctl(struct tty_struct *tty, int break_state); + +static struct usb_serial_driver whiteheat_fake_device = { + .driver = { + .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 = { + .name = "whiteheat", + }, + .description = "Connect Tech - WhiteHEAT", + .id_table = id_table_std, + .num_ports = 4, + .num_bulk_in = 5, + .num_bulk_out = 5, + .attach = whiteheat_attach, + .release = whiteheat_release, + .port_probe = whiteheat_port_probe, + .port_remove = whiteheat_port_remove, + .open = whiteheat_open, + .close = whiteheat_close, + .get_serial = whiteheat_get_serial, + .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 + + +/***************************************************************************** + * 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_obj(struct whiteheat_command_private); + if (!command_info) + 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_obj(*info); + if (!info) + return -ENOMEM; + + usb_set_serial_port_data(port, info); + + return 0; +} + +static void whiteheat_port_remove(struct usb_serial_port *port) +{ + struct whiteheat_private *info; + + info = usb_get_serial_port_data(port); + kfree(info); +} + +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 void whiteheat_get_serial(struct tty_struct *tty, struct serial_struct *ss) +{ + ss->baud_base = 460800; +} + + +static void whiteheat_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +{ + firm_setup_port(tty); +} + +static int whiteheat_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + + return 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); + + if (command_port->bulk_out_size < datasize + 1) + return -EIO; + + 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); + info->mcr = command_info->result_buffer[0]; + 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->port_number + 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->port_number + 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; + speed_t baud; + + port_settings.port = port->port_number + 1; + + port_settings.bits = tty_get_char_size(cflag); + 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 */ + baud = tty_get_baud_rate(tty); + port_settings.baud = cpu_to_le32(baud); + dev_dbg(dev, "%s - baud rate = %u\n", __func__, baud); + + /* fixme: should set validated settings */ + tty_encode_baud_rate(tty, baud, 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->port_number + 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->port_number + 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->port_number + 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->port_number + 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->port_number + 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->port_number + 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 000000000..7e63074c9 --- /dev/null +++ b/drivers/usb/serial/whiteheat.h @@ -0,0 +1,298 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * USB ConnectTech WhiteHEAT driver + * + * Copyright (C) 2002 + * Connect Tech Inc. + * + * Copyright (C) 1999, 2000 + * Greg Kroah-Hartman (greg@kroah.com) + * + * See Documentation/usb/usb-serial.rst 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) */ + __le32 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 000000000..4a1fed4cb --- /dev/null +++ b/drivers/usb/serial/wishbone-serial.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * USB Wishbone-Serial adapter driver + * + * Copyright (C) 2013 Wesley W. Terpstra + * Copyright (C) 2013 GSI Helmholtz Centre for Heavy Ion Research GmbH + */ + +#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 = { + .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/xr_serial.c b/drivers/usb/serial/xr_serial.c new file mode 100644 index 000000000..352c765d8 --- /dev/null +++ b/drivers/usb/serial/xr_serial.c @@ -0,0 +1,1111 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MaxLinear/Exar USB to Serial driver + * + * Copyright (c) 2020 Manivannan Sadhasivam + * Copyright (c) 2021 Johan Hovold + * + * Based on the initial driver written by Patong Yang: + * + * https://lore.kernel.org/r/20180404070634.nhspvmxcjwfgjkcv@advantechmxl-desktop + * + * Copyright (c) 2018 Patong Yang + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct xr_txrx_clk_mask { + u16 tx; + u16 rx0; + u16 rx1; +}; + +#define XR_INT_OSC_HZ 48000000U +#define XR21V141X_MIN_SPEED 46U +#define XR21V141X_MAX_SPEED XR_INT_OSC_HZ + +/* XR21V141X register blocks */ +#define XR21V141X_UART_REG_BLOCK 0 +#define XR21V141X_UM_REG_BLOCK 4 +#define XR21V141X_UART_CUSTOM_BLOCK 0x66 + +/* XR21V141X UART registers */ +#define XR21V141X_CLOCK_DIVISOR_0 0x04 +#define XR21V141X_CLOCK_DIVISOR_1 0x05 +#define XR21V141X_CLOCK_DIVISOR_2 0x06 +#define XR21V141X_TX_CLOCK_MASK_0 0x07 +#define XR21V141X_TX_CLOCK_MASK_1 0x08 +#define XR21V141X_RX_CLOCK_MASK_0 0x09 +#define XR21V141X_RX_CLOCK_MASK_1 0x0a +#define XR21V141X_REG_FORMAT 0x0b + +/* XR21V141X UART Manager registers */ +#define XR21V141X_UM_FIFO_ENABLE_REG 0x10 +#define XR21V141X_UM_ENABLE_TX_FIFO 0x01 +#define XR21V141X_UM_ENABLE_RX_FIFO 0x02 + +#define XR21V141X_UM_RX_FIFO_RESET 0x18 +#define XR21V141X_UM_TX_FIFO_RESET 0x1c + +#define XR_UART_ENABLE_TX 0x1 +#define XR_UART_ENABLE_RX 0x2 + +#define XR_GPIO_RI BIT(0) +#define XR_GPIO_CD BIT(1) +#define XR_GPIO_DSR BIT(2) +#define XR_GPIO_DTR BIT(3) +#define XR_GPIO_CTS BIT(4) +#define XR_GPIO_RTS BIT(5) +#define XR_GPIO_CLK BIT(6) +#define XR_GPIO_XEN BIT(7) +#define XR_GPIO_TXT BIT(8) +#define XR_GPIO_RXT BIT(9) + +#define XR_UART_DATA_MASK GENMASK(3, 0) +#define XR_UART_DATA_7 0x7 +#define XR_UART_DATA_8 0x8 + +#define XR_UART_PARITY_MASK GENMASK(6, 4) +#define XR_UART_PARITY_SHIFT 4 +#define XR_UART_PARITY_NONE (0x0 << XR_UART_PARITY_SHIFT) +#define XR_UART_PARITY_ODD (0x1 << XR_UART_PARITY_SHIFT) +#define XR_UART_PARITY_EVEN (0x2 << XR_UART_PARITY_SHIFT) +#define XR_UART_PARITY_MARK (0x3 << XR_UART_PARITY_SHIFT) +#define XR_UART_PARITY_SPACE (0x4 << XR_UART_PARITY_SHIFT) + +#define XR_UART_STOP_MASK BIT(7) +#define XR_UART_STOP_SHIFT 7 +#define XR_UART_STOP_1 (0x0 << XR_UART_STOP_SHIFT) +#define XR_UART_STOP_2 (0x1 << XR_UART_STOP_SHIFT) + +#define XR_UART_FLOW_MODE_NONE 0x0 +#define XR_UART_FLOW_MODE_HW 0x1 +#define XR_UART_FLOW_MODE_SW 0x2 + +#define XR_GPIO_MODE_SEL_MASK GENMASK(2, 0) +#define XR_GPIO_MODE_SEL_RTS_CTS 0x1 +#define XR_GPIO_MODE_SEL_DTR_DSR 0x2 +#define XR_GPIO_MODE_SEL_RS485 0x3 +#define XR_GPIO_MODE_SEL_RS485_ADDR 0x4 +#define XR_GPIO_MODE_RS485_TX_H 0x8 +#define XR_GPIO_MODE_TX_TOGGLE 0x100 +#define XR_GPIO_MODE_RX_TOGGLE 0x200 + +#define XR_FIFO_RESET 0x1 + +#define XR_CUSTOM_DRIVER_ACTIVE 0x1 + +static int xr21v141x_uart_enable(struct usb_serial_port *port); +static int xr21v141x_uart_disable(struct usb_serial_port *port); +static int xr21v141x_fifo_reset(struct usb_serial_port *port); +static void xr21v141x_set_line_settings(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios); + +struct xr_type { + int reg_width; + u8 reg_recipient; + u8 set_reg; + u8 get_reg; + + u16 uart_enable; + u16 flow_control; + u16 xon_char; + u16 xoff_char; + u16 tx_break; + u16 gpio_mode; + u16 gpio_direction; + u16 gpio_set; + u16 gpio_clear; + u16 gpio_status; + u16 tx_fifo_reset; + u16 rx_fifo_reset; + u16 custom_driver; + + bool have_5_6_bit_mode; + bool have_xmit_toggle; + + int (*enable)(struct usb_serial_port *port); + int (*disable)(struct usb_serial_port *port); + int (*fifo_reset)(struct usb_serial_port *port); + void (*set_line_settings)(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios); +}; + +enum xr_type_id { + XR21V141X, + XR21B142X, + XR21B1411, + XR2280X, + XR_TYPE_COUNT, +}; + +static const struct xr_type xr_types[] = { + [XR21V141X] = { + .reg_width = 8, + .reg_recipient = USB_RECIP_DEVICE, + .set_reg = 0x00, + .get_reg = 0x01, + + .uart_enable = 0x03, + .flow_control = 0x0c, + .xon_char = 0x10, + .xoff_char = 0x11, + .tx_break = 0x14, + .gpio_mode = 0x1a, + .gpio_direction = 0x1b, + .gpio_set = 0x1d, + .gpio_clear = 0x1e, + .gpio_status = 0x1f, + + .enable = xr21v141x_uart_enable, + .disable = xr21v141x_uart_disable, + .fifo_reset = xr21v141x_fifo_reset, + .set_line_settings = xr21v141x_set_line_settings, + }, + [XR21B142X] = { + .reg_width = 16, + .reg_recipient = USB_RECIP_INTERFACE, + .set_reg = 0x00, + .get_reg = 0x00, + + .uart_enable = 0x00, + .flow_control = 0x06, + .xon_char = 0x07, + .xoff_char = 0x08, + .tx_break = 0x0a, + .gpio_mode = 0x0c, + .gpio_direction = 0x0d, + .gpio_set = 0x0e, + .gpio_clear = 0x0f, + .gpio_status = 0x10, + .tx_fifo_reset = 0x40, + .rx_fifo_reset = 0x43, + .custom_driver = 0x60, + + .have_5_6_bit_mode = true, + .have_xmit_toggle = true, + }, + [XR21B1411] = { + .reg_width = 12, + .reg_recipient = USB_RECIP_DEVICE, + .set_reg = 0x00, + .get_reg = 0x01, + + .uart_enable = 0xc00, + .flow_control = 0xc06, + .xon_char = 0xc07, + .xoff_char = 0xc08, + .tx_break = 0xc0a, + .gpio_mode = 0xc0c, + .gpio_direction = 0xc0d, + .gpio_set = 0xc0e, + .gpio_clear = 0xc0f, + .gpio_status = 0xc10, + .tx_fifo_reset = 0xc80, + .rx_fifo_reset = 0xcc0, + .custom_driver = 0x20d, + }, + [XR2280X] = { + .reg_width = 16, + .reg_recipient = USB_RECIP_DEVICE, + .set_reg = 0x05, + .get_reg = 0x05, + + .uart_enable = 0x40, + .flow_control = 0x46, + .xon_char = 0x47, + .xoff_char = 0x48, + .tx_break = 0x4a, + .gpio_mode = 0x4c, + .gpio_direction = 0x4d, + .gpio_set = 0x4e, + .gpio_clear = 0x4f, + .gpio_status = 0x50, + .tx_fifo_reset = 0x60, + .rx_fifo_reset = 0x63, + .custom_driver = 0x81, + }, +}; + +struct xr_data { + const struct xr_type *type; + u8 channel; /* zero-based index or interface number */ + struct serial_rs485 rs485; +}; + +static int xr_set_reg(struct usb_serial_port *port, u8 channel, u16 reg, u16 val) +{ + struct xr_data *data = usb_get_serial_port_data(port); + const struct xr_type *type = data->type; + struct usb_serial *serial = port->serial; + int ret; + + ret = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + type->set_reg, + USB_DIR_OUT | USB_TYPE_VENDOR | type->reg_recipient, + val, (channel << 8) | reg, NULL, 0, + USB_CTRL_SET_TIMEOUT); + if (ret < 0) { + dev_err(&port->dev, "Failed to set reg 0x%02x: %d\n", reg, ret); + return ret; + } + + return 0; +} + +static int xr_get_reg(struct usb_serial_port *port, u8 channel, u16 reg, u16 *val) +{ + struct xr_data *data = usb_get_serial_port_data(port); + const struct xr_type *type = data->type; + struct usb_serial *serial = port->serial; + u8 *dmabuf; + int ret, len; + + if (type->reg_width == 8) + len = 1; + else + len = 2; + + dmabuf = kmalloc(len, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + ret = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + type->get_reg, + USB_DIR_IN | USB_TYPE_VENDOR | type->reg_recipient, + 0, (channel << 8) | reg, dmabuf, len, + USB_CTRL_GET_TIMEOUT); + if (ret == len) { + if (len == 2) + *val = le16_to_cpup((__le16 *)dmabuf); + else + *val = *dmabuf; + ret = 0; + } else { + dev_err(&port->dev, "Failed to get reg 0x%02x: %d\n", reg, ret); + if (ret >= 0) + ret = -EIO; + } + + kfree(dmabuf); + + return ret; +} + +static int xr_set_reg_uart(struct usb_serial_port *port, u16 reg, u16 val) +{ + struct xr_data *data = usb_get_serial_port_data(port); + + return xr_set_reg(port, data->channel, reg, val); +} + +static int xr_get_reg_uart(struct usb_serial_port *port, u16 reg, u16 *val) +{ + struct xr_data *data = usb_get_serial_port_data(port); + + return xr_get_reg(port, data->channel, reg, val); +} + +static int xr_set_reg_um(struct usb_serial_port *port, u8 reg_base, u8 val) +{ + struct xr_data *data = usb_get_serial_port_data(port); + u8 reg; + + reg = reg_base + data->channel; + + return xr_set_reg(port, XR21V141X_UM_REG_BLOCK, reg, val); +} + +static int __xr_uart_enable(struct usb_serial_port *port) +{ + struct xr_data *data = usb_get_serial_port_data(port); + + return xr_set_reg_uart(port, data->type->uart_enable, + XR_UART_ENABLE_TX | XR_UART_ENABLE_RX); +} + +static int __xr_uart_disable(struct usb_serial_port *port) +{ + struct xr_data *data = usb_get_serial_port_data(port); + + return xr_set_reg_uart(port, data->type->uart_enable, 0); +} + +/* + * According to datasheet, below is the recommended sequence for enabling UART + * module in XR21V141X: + * + * Enable Tx FIFO + * Enable Tx and Rx + * Enable Rx FIFO + */ +static int xr21v141x_uart_enable(struct usb_serial_port *port) +{ + int ret; + + ret = xr_set_reg_um(port, XR21V141X_UM_FIFO_ENABLE_REG, + XR21V141X_UM_ENABLE_TX_FIFO); + if (ret) + return ret; + + ret = __xr_uart_enable(port); + if (ret) + return ret; + + ret = xr_set_reg_um(port, XR21V141X_UM_FIFO_ENABLE_REG, + XR21V141X_UM_ENABLE_TX_FIFO | XR21V141X_UM_ENABLE_RX_FIFO); + if (ret) + __xr_uart_disable(port); + + return ret; +} + +static int xr21v141x_uart_disable(struct usb_serial_port *port) +{ + int ret; + + ret = __xr_uart_disable(port); + if (ret) + return ret; + + ret = xr_set_reg_um(port, XR21V141X_UM_FIFO_ENABLE_REG, 0); + + return ret; +} + +static int xr_uart_enable(struct usb_serial_port *port) +{ + struct xr_data *data = usb_get_serial_port_data(port); + + if (data->type->enable) + return data->type->enable(port); + + return __xr_uart_enable(port); +} + +static int xr_uart_disable(struct usb_serial_port *port) +{ + struct xr_data *data = usb_get_serial_port_data(port); + + if (data->type->disable) + return data->type->disable(port); + + return __xr_uart_disable(port); +} + +static int xr21v141x_fifo_reset(struct usb_serial_port *port) +{ + int ret; + + ret = xr_set_reg_um(port, XR21V141X_UM_TX_FIFO_RESET, XR_FIFO_RESET); + if (ret) + return ret; + + ret = xr_set_reg_um(port, XR21V141X_UM_RX_FIFO_RESET, XR_FIFO_RESET); + if (ret) + return ret; + + return 0; +} + +static int xr_fifo_reset(struct usb_serial_port *port) +{ + struct xr_data *data = usb_get_serial_port_data(port); + int ret; + + if (data->type->fifo_reset) + return data->type->fifo_reset(port); + + ret = xr_set_reg_uart(port, data->type->tx_fifo_reset, XR_FIFO_RESET); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, data->type->rx_fifo_reset, XR_FIFO_RESET); + if (ret) + return ret; + + return 0; +} + +static int xr_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct xr_data *data = usb_get_serial_port_data(port); + u16 status; + int ret; + + ret = xr_get_reg_uart(port, data->type->gpio_status, &status); + if (ret) + return ret; + + /* + * Modem control pins are active low, so reading '0' means it is active + * and '1' means not active. + */ + ret = ((status & XR_GPIO_DTR) ? 0 : TIOCM_DTR) | + ((status & XR_GPIO_RTS) ? 0 : TIOCM_RTS) | + ((status & XR_GPIO_CTS) ? 0 : TIOCM_CTS) | + ((status & XR_GPIO_DSR) ? 0 : TIOCM_DSR) | + ((status & XR_GPIO_RI) ? 0 : TIOCM_RI) | + ((status & XR_GPIO_CD) ? 0 : TIOCM_CD); + + return ret; +} + +static int xr_tiocmset_port(struct usb_serial_port *port, + unsigned int set, unsigned int clear) +{ + struct xr_data *data = usb_get_serial_port_data(port); + const struct xr_type *type = data->type; + u16 gpio_set = 0; + u16 gpio_clr = 0; + int ret = 0; + + /* Modem control pins are active low, so set & clr are swapped */ + if (set & TIOCM_RTS) + gpio_clr |= XR_GPIO_RTS; + if (set & TIOCM_DTR) + gpio_clr |= XR_GPIO_DTR; + if (clear & TIOCM_RTS) + gpio_set |= XR_GPIO_RTS; + if (clear & TIOCM_DTR) + gpio_set |= XR_GPIO_DTR; + + /* Writing '0' to gpio_{set/clr} bits has no effect, so no need to do */ + if (gpio_clr) + ret = xr_set_reg_uart(port, type->gpio_clear, gpio_clr); + + if (gpio_set) + ret = xr_set_reg_uart(port, type->gpio_set, gpio_set); + + return ret; +} + +static int xr_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + + return xr_tiocmset_port(port, set, clear); +} + +static void xr_dtr_rts(struct usb_serial_port *port, int on) +{ + if (on) + xr_tiocmset_port(port, TIOCM_DTR | TIOCM_RTS, 0); + else + xr_tiocmset_port(port, 0, TIOCM_DTR | TIOCM_RTS); +} + +static int xr_break_ctl(struct tty_struct *tty, int break_state) +{ + struct usb_serial_port *port = tty->driver_data; + struct xr_data *data = usb_get_serial_port_data(port); + const struct xr_type *type = data->type; + u16 state; + + if (break_state == 0) + state = 0; + else + state = GENMASK(type->reg_width - 1, 0); + + dev_dbg(&port->dev, "Turning break %s\n", state == 0 ? "off" : "on"); + + return xr_set_reg_uart(port, type->tx_break, state); +} + +/* Tx and Rx clock mask values obtained from section 3.3.4 of datasheet */ +static const struct xr_txrx_clk_mask xr21v141x_txrx_clk_masks[] = { + { 0x000, 0x000, 0x000 }, + { 0x000, 0x000, 0x000 }, + { 0x100, 0x000, 0x100 }, + { 0x020, 0x400, 0x020 }, + { 0x010, 0x100, 0x010 }, + { 0x208, 0x040, 0x208 }, + { 0x104, 0x820, 0x108 }, + { 0x844, 0x210, 0x884 }, + { 0x444, 0x110, 0x444 }, + { 0x122, 0x888, 0x224 }, + { 0x912, 0x448, 0x924 }, + { 0x492, 0x248, 0x492 }, + { 0x252, 0x928, 0x292 }, + { 0x94a, 0x4a4, 0xa52 }, + { 0x52a, 0xaa4, 0x54a }, + { 0xaaa, 0x954, 0x4aa }, + { 0xaaa, 0x554, 0xaaa }, + { 0x555, 0xad4, 0x5aa }, + { 0xb55, 0xab4, 0x55a }, + { 0x6b5, 0x5ac, 0xb56 }, + { 0x5b5, 0xd6c, 0x6d6 }, + { 0xb6d, 0xb6a, 0xdb6 }, + { 0x76d, 0x6da, 0xbb6 }, + { 0xedd, 0xdda, 0x76e }, + { 0xddd, 0xbba, 0xeee }, + { 0x7bb, 0xf7a, 0xdde }, + { 0xf7b, 0xef6, 0x7de }, + { 0xdf7, 0xbf6, 0xf7e }, + { 0x7f7, 0xfee, 0xefe }, + { 0xfdf, 0xfbe, 0x7fe }, + { 0xf7f, 0xefe, 0xffe }, + { 0xfff, 0xffe, 0xffd }, +}; + +static int xr21v141x_set_baudrate(struct tty_struct *tty, struct usb_serial_port *port) +{ + u32 divisor, baud, idx; + u16 tx_mask, rx_mask; + int ret; + + baud = tty->termios.c_ospeed; + if (!baud) + return 0; + + baud = clamp(baud, XR21V141X_MIN_SPEED, XR21V141X_MAX_SPEED); + divisor = XR_INT_OSC_HZ / baud; + idx = ((32 * XR_INT_OSC_HZ) / baud) & 0x1f; + tx_mask = xr21v141x_txrx_clk_masks[idx].tx; + + if (divisor & 0x01) + rx_mask = xr21v141x_txrx_clk_masks[idx].rx1; + else + rx_mask = xr21v141x_txrx_clk_masks[idx].rx0; + + dev_dbg(&port->dev, "Setting baud rate: %u\n", baud); + /* + * XR21V141X uses fractional baud rate generator with 48MHz internal + * oscillator and 19-bit programmable divisor. So theoretically it can + * generate most commonly used baud rates with high accuracy. + */ + ret = xr_set_reg_uart(port, XR21V141X_CLOCK_DIVISOR_0, + divisor & 0xff); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, XR21V141X_CLOCK_DIVISOR_1, + (divisor >> 8) & 0xff); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, XR21V141X_CLOCK_DIVISOR_2, + (divisor >> 16) & 0xff); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, XR21V141X_TX_CLOCK_MASK_0, + tx_mask & 0xff); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, XR21V141X_TX_CLOCK_MASK_1, + (tx_mask >> 8) & 0xff); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, XR21V141X_RX_CLOCK_MASK_0, + rx_mask & 0xff); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, XR21V141X_RX_CLOCK_MASK_1, + (rx_mask >> 8) & 0xff); + if (ret) + return ret; + + tty_encode_baud_rate(tty, baud, baud); + + return 0; +} + +static void xr_set_flow_mode(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +{ + struct xr_data *data = usb_get_serial_port_data(port); + const struct xr_type *type = data->type; + u16 flow, gpio_mode; + bool rs485_enabled; + int ret; + + ret = xr_get_reg_uart(port, type->gpio_mode, &gpio_mode); + if (ret) + return; + + /* + * According to the datasheets, the UART needs to be disabled while + * writing to the FLOW_CONTROL register (XR21V141X), or any register + * but GPIO_SET, GPIO_CLEAR, TX_BREAK and ERROR_STATUS (XR21B142X). + */ + xr_uart_disable(port); + + /* Set GPIO mode for controlling the pins manually by default. */ + gpio_mode &= ~XR_GPIO_MODE_SEL_MASK; + + rs485_enabled = !!(data->rs485.flags & SER_RS485_ENABLED); + if (rs485_enabled) { + dev_dbg(&port->dev, "Enabling RS-485\n"); + gpio_mode |= XR_GPIO_MODE_SEL_RS485; + if (data->rs485.flags & SER_RS485_RTS_ON_SEND) + gpio_mode &= ~XR_GPIO_MODE_RS485_TX_H; + else + gpio_mode |= XR_GPIO_MODE_RS485_TX_H; + } + + if (C_CRTSCTS(tty) && C_BAUD(tty) != B0 && !rs485_enabled) { + dev_dbg(&port->dev, "Enabling hardware flow ctrl\n"); + gpio_mode |= XR_GPIO_MODE_SEL_RTS_CTS; + flow = XR_UART_FLOW_MODE_HW; + } else if (I_IXON(tty)) { + u8 start_char = START_CHAR(tty); + u8 stop_char = STOP_CHAR(tty); + + dev_dbg(&port->dev, "Enabling sw flow ctrl\n"); + flow = XR_UART_FLOW_MODE_SW; + + xr_set_reg_uart(port, type->xon_char, start_char); + xr_set_reg_uart(port, type->xoff_char, stop_char); + } else { + dev_dbg(&port->dev, "Disabling flow ctrl\n"); + flow = XR_UART_FLOW_MODE_NONE; + } + + xr_set_reg_uart(port, type->flow_control, flow); + xr_set_reg_uart(port, type->gpio_mode, gpio_mode); + + xr_uart_enable(port); + + if (C_BAUD(tty) == B0) + xr_dtr_rts(port, 0); + else if (old_termios && (old_termios->c_cflag & CBAUD) == B0) + xr_dtr_rts(port, 1); +} + +static void xr21v141x_set_line_settings(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +{ + struct ktermios *termios = &tty->termios; + u8 bits = 0; + int ret; + + if (!old_termios || (tty->termios.c_ospeed != old_termios->c_ospeed)) + xr21v141x_set_baudrate(tty, port); + + switch (C_CSIZE(tty)) { + case CS5: + case CS6: + /* CS5 and CS6 are not supported, so just restore old setting */ + termios->c_cflag &= ~CSIZE; + if (old_termios) + termios->c_cflag |= old_termios->c_cflag & CSIZE; + else + termios->c_cflag |= CS8; + + if (C_CSIZE(tty) == CS7) + bits |= XR_UART_DATA_7; + else + bits |= XR_UART_DATA_8; + break; + case CS7: + bits |= XR_UART_DATA_7; + break; + case CS8: + default: + bits |= XR_UART_DATA_8; + break; + } + + if (C_PARENB(tty)) { + if (C_CMSPAR(tty)) { + if (C_PARODD(tty)) + bits |= XR_UART_PARITY_MARK; + else + bits |= XR_UART_PARITY_SPACE; + } else { + if (C_PARODD(tty)) + bits |= XR_UART_PARITY_ODD; + else + bits |= XR_UART_PARITY_EVEN; + } + } + + if (C_CSTOPB(tty)) + bits |= XR_UART_STOP_2; + else + bits |= XR_UART_STOP_1; + + ret = xr_set_reg_uart(port, XR21V141X_REG_FORMAT, bits); + if (ret) + return; +} + +static void xr_cdc_set_line_coding(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +{ + struct xr_data *data = usb_get_serial_port_data(port); + struct usb_host_interface *alt = port->serial->interface->cur_altsetting; + struct usb_device *udev = port->serial->dev; + struct usb_cdc_line_coding *lc; + int ret; + + lc = kzalloc_obj(*lc); + if (!lc) + return; + + if (tty->termios.c_ospeed) + lc->dwDTERate = cpu_to_le32(tty->termios.c_ospeed); + else + lc->dwDTERate = cpu_to_le32(9600); + + if (C_CSTOPB(tty)) + lc->bCharFormat = USB_CDC_2_STOP_BITS; + else + lc->bCharFormat = USB_CDC_1_STOP_BITS; + + if (C_PARENB(tty)) { + if (C_CMSPAR(tty)) { + if (C_PARODD(tty)) + lc->bParityType = USB_CDC_MARK_PARITY; + else + lc->bParityType = USB_CDC_SPACE_PARITY; + } else { + if (C_PARODD(tty)) + lc->bParityType = USB_CDC_ODD_PARITY; + else + lc->bParityType = USB_CDC_EVEN_PARITY; + } + } else { + lc->bParityType = USB_CDC_NO_PARITY; + } + + if (!data->type->have_5_6_bit_mode && + (C_CSIZE(tty) == CS5 || C_CSIZE(tty) == CS6)) { + tty->termios.c_cflag &= ~CSIZE; + if (old_termios) + tty->termios.c_cflag |= old_termios->c_cflag & CSIZE; + else + tty->termios.c_cflag |= CS8; + } + + switch (C_CSIZE(tty)) { + case CS5: + lc->bDataBits = 5; + break; + case CS6: + lc->bDataBits = 6; + break; + case CS7: + lc->bDataBits = 7; + break; + case CS8: + default: + lc->bDataBits = 8; + break; + } + + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_CDC_REQ_SET_LINE_CODING, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, alt->desc.bInterfaceNumber, + lc, sizeof(*lc), USB_CTRL_SET_TIMEOUT); + if (ret < 0) + dev_err(&port->dev, "Failed to set line coding: %d\n", ret); + + kfree(lc); +} + +static void xr_sanitize_serial_rs485(struct serial_rs485 *rs485) +{ + if (!(rs485->flags & SER_RS485_ENABLED)) { + memset(rs485, 0, sizeof(*rs485)); + return; + } + + /* RTS always toggles after TX */ + if (rs485->flags & SER_RS485_RTS_ON_SEND) + rs485->flags &= ~SER_RS485_RTS_AFTER_SEND; + else + rs485->flags |= SER_RS485_RTS_AFTER_SEND; + + /* Only the flags are implemented at the moment */ + rs485->flags &= SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | + SER_RS485_RTS_AFTER_SEND; + rs485->delay_rts_before_send = 0; + rs485->delay_rts_after_send = 0; + memset(rs485->padding, 0, sizeof(rs485->padding)); +} + +static int xr_get_rs485_config(struct tty_struct *tty, + struct serial_rs485 __user *argp) +{ + struct usb_serial_port *port = tty->driver_data; + struct xr_data *data = usb_get_serial_port_data(port); + + down_read(&tty->termios_rwsem); + if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) { + up_read(&tty->termios_rwsem); + return -EFAULT; + } + up_read(&tty->termios_rwsem); + + return 0; +} + +static int xr_set_rs485_config(struct tty_struct *tty, + struct serial_rs485 __user *argp) +{ + struct usb_serial_port *port = tty->driver_data; + struct xr_data *data = usb_get_serial_port_data(port); + struct serial_rs485 rs485; + + if (copy_from_user(&rs485, argp, sizeof(rs485))) + return -EFAULT; + xr_sanitize_serial_rs485(&rs485); + + down_write(&tty->termios_rwsem); + data->rs485 = rs485; + xr_set_flow_mode(tty, port, NULL); + up_write(&tty->termios_rwsem); + + if (copy_to_user(argp, &rs485, sizeof(rs485))) + return -EFAULT; + + return 0; +} + +static int xr_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + + switch (cmd) { + case TIOCGRS485: + return xr_get_rs485_config(tty, argp); + case TIOCSRS485: + return xr_set_rs485_config(tty, argp); + } + + return -ENOIOCTLCMD; +} + +static void xr_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +{ + struct xr_data *data = usb_get_serial_port_data(port); + + /* + * XR21V141X does not have a CUSTOM_DRIVER flag and always enters CDC + * mode upon receiving CDC requests. + */ + if (data->type->set_line_settings) + data->type->set_line_settings(tty, port, old_termios); + else + xr_cdc_set_line_coding(tty, port, old_termios); + + xr_set_flow_mode(tty, port, old_termios); +} + +static int xr_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + int ret; + + ret = xr_fifo_reset(port); + if (ret) + return ret; + + ret = xr_uart_enable(port); + if (ret) { + dev_err(&port->dev, "Failed to enable UART\n"); + return ret; + } + + /* Setup termios */ + if (tty) + xr_set_termios(tty, port, NULL); + + ret = usb_serial_generic_open(tty, port); + if (ret) { + xr_uart_disable(port); + return ret; + } + + return 0; +} + +static void xr_close(struct usb_serial_port *port) +{ + usb_serial_generic_close(port); + + xr_uart_disable(port); +} + +static int xr_probe(struct usb_serial *serial, const struct usb_device_id *id) +{ + struct usb_interface *control = serial->interface; + struct usb_host_interface *alt = control->cur_altsetting; + struct usb_cdc_parsed_header hdrs; + struct usb_cdc_union_desc *desc; + struct usb_interface *data; + int ret; + + ret = cdc_parse_cdc_header(&hdrs, control, alt->extra, alt->extralen); + if (ret < 0) + return -ENODEV; + + desc = hdrs.usb_cdc_union_desc; + if (!desc) + return -ENODEV; + + data = usb_ifnum_to_if(serial->dev, desc->bSlaveInterface0); + if (!data) + return -ENODEV; + + ret = usb_serial_claim_interface(serial, data); + if (ret) + return ret; + + usb_set_serial_data(serial, (void *)id->driver_info); + + return 0; +} + +static int xr_gpio_init(struct usb_serial_port *port, const struct xr_type *type) +{ + u16 mask, mode; + int ret; + + /* + * Configure all pins as GPIO except for Receive and Transmit Toggle. + */ + mode = 0; + if (type->have_xmit_toggle) + mode |= XR_GPIO_MODE_RX_TOGGLE | XR_GPIO_MODE_TX_TOGGLE; + + ret = xr_set_reg_uart(port, type->gpio_mode, mode); + if (ret) + return ret; + + /* + * Configure DTR and RTS as outputs and make sure they are deasserted + * (active low), and configure RI, CD, DSR and CTS as inputs. + */ + mask = XR_GPIO_DTR | XR_GPIO_RTS; + ret = xr_set_reg_uart(port, type->gpio_direction, mask); + if (ret) + return ret; + + ret = xr_set_reg_uart(port, type->gpio_set, mask); + if (ret) + return ret; + + return 0; +} + +static int xr_port_probe(struct usb_serial_port *port) +{ + struct usb_interface_descriptor *desc; + const struct xr_type *type; + struct xr_data *data; + enum xr_type_id type_id; + int ret; + + type_id = (int)(unsigned long)usb_get_serial_data(port->serial); + type = &xr_types[type_id]; + + data = kzalloc_obj(*data); + if (!data) + return -ENOMEM; + + data->type = type; + + desc = &port->serial->interface->cur_altsetting->desc; + if (type_id == XR21V141X) + data->channel = desc->bInterfaceNumber / 2; + else + data->channel = desc->bInterfaceNumber; + + usb_set_serial_port_data(port, data); + + if (type->custom_driver) { + ret = xr_set_reg_uart(port, type->custom_driver, + XR_CUSTOM_DRIVER_ACTIVE); + if (ret) + goto err_free; + } + + ret = xr_gpio_init(port, type); + if (ret) + goto err_free; + + return 0; + +err_free: + kfree(data); + + return ret; +} + +static void xr_port_remove(struct usb_serial_port *port) +{ + struct xr_data *data = usb_get_serial_port_data(port); + + kfree(data); +} + +#define XR_DEVICE(vid, pid, type) \ + USB_DEVICE_INTERFACE_CLASS((vid), (pid), USB_CLASS_COMM), \ + .driver_info = (type) + +static const struct usb_device_id id_table[] = { + { XR_DEVICE(0x04e2, 0x1400, XR2280X) }, + { XR_DEVICE(0x04e2, 0x1401, XR2280X) }, + { XR_DEVICE(0x04e2, 0x1402, XR2280X) }, + { XR_DEVICE(0x04e2, 0x1403, XR2280X) }, + { XR_DEVICE(0x04e2, 0x1410, XR21V141X) }, + { XR_DEVICE(0x04e2, 0x1411, XR21B1411) }, + { XR_DEVICE(0x04e2, 0x1412, XR21V141X) }, + { XR_DEVICE(0x04e2, 0x1414, XR21V141X) }, + { XR_DEVICE(0x04e2, 0x1420, XR21B142X) }, + { XR_DEVICE(0x04e2, 0x1422, XR21B142X) }, + { XR_DEVICE(0x04e2, 0x1424, XR21B142X) }, + { } +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver xr_device = { + .driver = { + .name = "xr_serial", + }, + .id_table = id_table, + .num_ports = 1, + .probe = xr_probe, + .port_probe = xr_port_probe, + .port_remove = xr_port_remove, + .open = xr_open, + .close = xr_close, + .break_ctl = xr_break_ctl, + .set_termios = xr_set_termios, + .tiocmget = xr_tiocmget, + .tiocmset = xr_tiocmset, + .ioctl = xr_ioctl, + .dtr_rts = xr_dtr_rts +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &xr_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); + +MODULE_AUTHOR("Manivannan Sadhasivam "); +MODULE_DESCRIPTION("MaxLinear/Exar USB to Serial driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/xsens_mt.c b/drivers/usb/serial/xsens_mt.c new file mode 100644 index 000000000..151a6e893 --- /dev/null +++ b/drivers/usb/serial/xsens_mt.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xsens MT USB driver + * + * Copyright (C) 2013 Xsens + */ + +#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 xsens_mt_probe(struct usb_serial *serial, + const struct usb_device_id *id) +{ + if (serial->interface->cur_altsetting->desc.bInterfaceNumber == 1) + return 0; + + return -ENODEV; +} + +static struct usb_serial_driver xsens_mt_device = { + .driver = { + .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_AUTHOR("Frans Klaver "); +MODULE_DESCRIPTION("USB-serial driver for Xsens motion trackers"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/storage/Kconfig b/drivers/usb/storage/Kconfig new file mode 100644 index 000000000..4be1d617d --- /dev/null +++ b/drivers/usb/storage/Kconfig @@ -0,0 +1,199 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# USB Storage driver configuration +# + +comment "NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may 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. + + To compile this driver as a module, choose M here: the + module will be called usb-storage. + +if USB_STORAGE + +config USB_STORAGE_DEBUG + bool "USB Mass Storage verbose debug" + 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" + 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 + default y + +config USB_STORAGE_DATAFAB + tristate "Datafab Compact Flash Reader support" + 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" + 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" + 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" + 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" + 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" + 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" + 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" + 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 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" + 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" + 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" + help + Say Y here if you wish to control a ENE SD/MS Card reader. + Note that this driver does not support SM cards. + + To compile this driver as a module, choose M here: the + module will be called ums-eneub6250. + +endif # USB_STORAGE + +config USB_UAS + tristate "USB Attached SCSI" + depends on SCSI && USB_STORAGE + 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 000000000..28db337f1 --- /dev/null +++ b/drivers/usb/storage/Makefile @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the USB Mass Storage device drivers. +# +# 15 Aug 2000, Christoph Hellwig +# Rewritten to use lists instead of if-statements. +# + +ccflags-y := -I $(srctree)/drivers/scsi + +ccflags-y += -DDEFAULT_SYMBOL_NAMESPACE='"USB_STORAGE"' + +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 000000000..691fe4700 --- /dev/null +++ b/drivers/usb/storage/alauda.c @@ -0,0 +1,1274 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 + */ + +#include +#include + +#include +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" +#include "scsiglue.h" + +#define DRV_NAME "ums-alauda" + +MODULE_DESCRIPTION("Driver for Alauda-based card readers"); +MODULE_AUTHOR("Daniel Drake "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USB_STORAGE"); + +/* + * 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 */ + + bool media_initialized; +}; + +#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 const 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 const 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); + + if (rc == USB_STOR_XFER_GOOD) + 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; + const 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: %4ph\n", data); + media_info = alauda_card_find_id(data[1]); + if (media_info == NULL) { + pr_warn("alauda_init_media: Unrecognised media signature: %4ph\n", + data); + 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 (MEDIA_INFO(us).pba_to_lba == NULL || MEDIA_INFO(us).lba_to_pba == NULL) + return USB_STOR_TRANSPORT_ERROR; + + 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 = us->iobuf; + int rc; + + rc = alauda_get_media_status(us, status); + if (rc != USB_STOR_XFER_GOOD) { + status[0] = 0xF0; /* Pretend there's no media */ + status[1] = 0; + } + + /* 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 || !info->media_initialized) { + usb_stor_dbg(us, "Media change detected\n"); + alauda_free_maps(&MEDIA_INFO(us)); + rc = alauda_init_media(us); + if (rc == USB_STOR_TRANSPORT_GOOD) + info->media_initialized = true; + 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, "%3ph\n", data); + 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) + 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) + 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_array(pagesize + 64, blocksize, GFP_NOIO); + if (!blockbuffer) { + 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_obj(struct alauda_info, GFP_NOIO); + if (!us->extra) + return -ENOMEM; + + 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 0; +} + +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 const 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 struct scsi_host_template alauda_host_template; + +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, + &alauda_host_template); + 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 = DRV_NAME, + .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_stor_driver(alauda_driver, alauda_host_template, DRV_NAME); diff --git a/drivers/usb/storage/cypress_atacb.c b/drivers/usb/storage/cypress_atacb.c new file mode 100644 index 000000000..2fce5f95b --- /dev/null +++ b/drivers/usb/storage/cypress_atacb.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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) + */ + +#include +#include +#include +#include +#include + +#include "usb.h" +#include "protocol.h" +#include "scsiglue.h" +#include "debug.h" + +#define DRV_NAME "ums-cypress" + +MODULE_DESCRIPTION("SAT support for Cypress USB/ATA bridges with ATACB"); +MODULE_AUTHOR("Matthieu Castet "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USB_STORAGE"); + +/* + * 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 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 const 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[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 = SAM_STAT_CHECK_CONDITION; + } + goto end; +invalid_fld: + srb->result = 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 struct scsi_host_template cypress_host_template; + +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, + &cypress_host_template); + 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 = DRV_NAME, + .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_stor_driver(cypress_driver, cypress_host_template, DRV_NAME); diff --git a/drivers/usb/storage/datafab.c b/drivers/usb/storage/datafab.c new file mode 100644 index 000000000..0ded8ef72 --- /dev/null +++ b/drivers/usb/storage/datafab.c @@ -0,0 +1,757 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 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" +#include "scsiglue.h" + +#define DRV_NAME "ums-datafab" + +MODULE_DESCRIPTION("Driver for Datafab USB Compact Flash reader"); +MODULE_AUTHOR("Jimmie Mayfield "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USB_STORAGE"); + +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 const 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 const 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]); + 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 const 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 const 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 const unsigned char rw_err_page[12] = { + 0x1, 0xA, 0x21, 1, 0, 0, 0, 0, 1, 0, 0, 0 + }; + static const unsigned char cache_page[12] = { + 0x8, 0xA, 0x1, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + static const unsigned char rbac_page[12] = { + 0x1B, 0xA, 0, 0x81, 0, 0, 0, 0, 0, 0, 0, 0 + }; + static const 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 const unsigned char inquiry_reply[8] = { + 0x00, 0x80, 0x00, 0x01, 0x1F, 0x00, 0x00, 0x00 + }; + + if (!us->extra) { + us->extra = kzalloc_obj(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 struct scsi_host_template datafab_host_template; + +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, + &datafab_host_template); + 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 = DRV_NAME, + .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_stor_driver(datafab_driver, datafab_host_template, DRV_NAME); diff --git a/drivers/usb/storage/debug.c b/drivers/usb/storage/debug.c new file mode 100644 index 000000000..dda610f68 --- /dev/null +++ b/drivers/usb/storage/debug.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "usb.h" +#include "debug.h" + + +void usb_stor_show_command(const struct us_data *us, 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_6: what = "RESERVE"; break; + case RELEASE_6: 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: %*ph\n", min_t(int, srb->cmd_len, 16), + (const unsigned char *)srb->cmnd); +} + +void usb_stor_show_sense(const struct us_data *us, + unsigned char key, + unsigned char asc, + unsigned char ascq) +{ + const char *what, *keystr, *fmt; + + keystr = scsi_sense_key_string(key); + what = scsi_extd_sense_format(asc, ascq, &fmt); + + if (keystr == NULL) + keystr = "(Unknown Key)"; + if (what == NULL) + what = "(unknown ASC/ASCQ)"; + + if (fmt) + usb_stor_dbg(us, "%s: %s (%s%x)\n", keystr, what, fmt, ascq); + else + usb_stor_dbg(us, "%s: %s\n", keystr, what); +} + +void usb_stor_dbg(const struct us_data *us, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + + dev_vprintk_emit(LOGLEVEL_DEBUG, &us->pusb_dev->dev, fmt, args); + + va_end(args); +} +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 000000000..a6505ceb6 --- /dev/null +++ b/drivers/usb/storage/debug.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + */ + +#ifndef _DEBUG_H_ +#define _DEBUG_H_ + +#include + +#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) void usb_stor_dbg(const struct us_data *us, + const char *fmt, ...); + +#define US_DEBUG(x) x +#else +__printf(2, 3) +static inline void _usb_stor_dbg(const struct us_data *us, + const char *fmt, ...) +{ +} +#define usb_stor_dbg(us, fmt, ...) \ + do { if (0) _usb_stor_dbg(us, 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 000000000..ed49a3bc8 --- /dev/null +++ b/drivers/usb/storage/ene_ub6250.c @@ -0,0 +1,2441 @@ +// SPDX-License-Identifier: GPL-2.0+ +#include +#include +#include +#include + +#include +#include + +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" +#include "scsiglue.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" + +#define DRV_NAME "ums_eneub6250" + +MODULE_DESCRIPTION("Driver for ENE UB6250 reader"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USB_STORAGE"); +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 const 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 const 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 0x000000 /* No Sense */ +#define SS_NOT_READY 0x023A00 /* Medium not present */ +#define SS_MEDIUM_ERR 0x031100 /* Unrecovered read error */ +#define SS_HW_ERR 0x040800 /* Communication failure */ +#define SS_ILLEGAL_REQUEST 0x052000 /* Invalid command */ +#define SS_UNIT_ATTENTION 0x062900 /* Reset occurred */ + +/* 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)) + + +/* SD_STATUS bits */ +#define SD_Insert BIT(0) +#define SD_Ready BIT(1) +#define SD_MediaChange BIT(2) +#define SD_IsMMC BIT(3) +#define SD_HiCapacity BIT(4) +#define SD_HiSpeed BIT(5) +#define SD_WtP BIT(6) + /* Bit 7 reserved */ + +/* MS_STATUS bits */ +#define MS_Insert BIT(0) +#define MS_Ready BIT(1) +#define MS_MediaChange BIT(2) +#define MS_IsMSPro BIT(3) +#define MS_IsMSPHG BIT(4) + /* Bit 5 reserved */ +#define MS_WtP BIT(6) + /* Bit 7 reserved */ + +/* SM_STATUS bits */ +#define SM_Insert BIT(0) +#define SM_Ready BIT(1) +#define SM_MediaChange BIT(2) + /* Bits 3-5 reserved */ +#define SM_WtP BIT(6) +#define SM_IsMS BIT(7) + +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 { + + /* I/O bounce buffer */ + u8 *bbuf; + + /* for 6250 code */ + u8 SD_Status; + u8 MS_Status; + u8 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) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) extra; + + if (!extra) + return; + kfree(info->bbuf); +} + +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), + residue)); + } + + if (bcs->Status != US_BULK_STAT_OK) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +static int do_scsi_request_sense(struct us_data *us, struct scsi_cmnd *srb) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + unsigned char buf[18]; + + memset(buf, 0, 18); + buf[0] = 0x70; /* Current error */ + buf[2] = info->SrbStatus >> 16; /* Sense key */ + buf[7] = 10; /* Additional length */ + buf[12] = info->SrbStatus >> 8; /* ASC */ + buf[13] = info->SrbStatus; /* ASCQ */ + + usb_stor_set_xfer_buf(buf, sizeof(buf), srb); + return USB_STOR_TRANSPORT_GOOD; +} + +static int do_scsi_inquiry(struct us_data *us, struct scsi_cmnd *srb) +{ + unsigned char data_ptr[36] = { + 0x00, 0x00, 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_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 & SD_Insert) && (info->SD_Status & SD_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_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 & SD_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 & SD_HiCapacity) { + bl_len = 0x200; + if (info->SD_Status & SD_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 & SD_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 & SD_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_OUT; + 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_array(info->MS_Lib.NumberOfPhyBlock, + sizeof(u16), + GFP_KERNEL); + info->MS_Lib.Log2PhyMap = kmalloc_array(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++; + break; + 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; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + u8 *bbuf = info->bbuf; + int result; + u32 bn = PhyBlockAddr * 0x20 + 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, bbuf, 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 = bbuf[0]; + ExtraDat->mngflg = bbuf[1]; + ExtraDat->logadr = memstick_logaddr(bbuf[2], bbuf[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 = kzalloc(MS_BYTES_PER_PAGE * 2, 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(info->MS_Lib.blkpag); /* Arnold test ... */ + info->MS_Lib.blkpag = NULL; + } + + if (info->MS_Lib.blkext) { + kfree(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_array(info->MS_Lib.PagesPerBlock, + info->MS_Lib.BytesPerSector, + GFP_KERNEL); + info->MS_Lib.blkext = kmalloc_objs(struct ms_lib_type_extdat, + info->MS_Lib.PagesPerBlock); + + 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; + + 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 = US_BULK_FLAG_OUT; + 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; + + 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; + + 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; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + u8 *bbuf = info->bbuf; + int result; + + 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, bbuf, 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 = bbuf[0]; + ExtraDat->mngflg = bbuf[1]; + ExtraDat->logadr = memstick_logaddr(bbuf[2], bbuf[3]); + + return USB_STOR_TRANSPORT_GOOD; +} + +static int ms_libsearch_block_from_physical(struct us_data *us, u16 phyblk) +{ + 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; + + 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 & MS_Insert) && (info->MS_Status & MS_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_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 & MS_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; + 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, "ms_scsi_read_capacity\n"); + bl_len = 0x200; + if (info->MS_Status & MS_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; + + /* 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; + u32 count = 0, index = 0; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + u8 *bbuf = info->bbuf; + + 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, + bbuf); + count += 0x80; + } + index = (PhyBlock % 0x80) * 4; + + extdat.ovrflg = bbuf[index]; + extdat.mngflg = bbuf[index+1]; + extdat.logadr = memstick_logaddr(bbuf[index+2], + bbuf[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 & MS_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 & MS_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 = US_BULK_FLAG_OUT; + 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 & SD_HiCapacity) && !(info->SD_Status & SD_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 & SD_HiCapacity) && (info->SD_Status & SD_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 = kmemdup(sd_fw->data, sd_fw->size, GFP_KERNEL); + if (buf == NULL) + goto nofw; + + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = sd_fw->size; + bcb->Flags = US_BULK_FLAG_OUT; + bcb->CDB[0] = 0xEF; + + result = ene_send_scsi_cmd(us, FDIR_WRITE, buf, 0); + if (us->srb != NULL) + scsi_set_resid(us->srb, 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; + u16 MSP_BlockSize, MSP_UserAreaBlocks; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + u8 *bbuf = info->bbuf; + unsigned int s; + + 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, bbuf, 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 = bbuf[0]; + + s = info->MS_Status; + if ((s & MS_Insert) && (s & MS_Ready)) { + printk(KERN_INFO "Insert = %x\n", !!(s & MS_Insert)); + printk(KERN_INFO "Ready = %x\n", !!(s & MS_Ready)); + printk(KERN_INFO "IsMSPro = %x\n", !!(s & MS_IsMSPro)); + printk(KERN_INFO "IsMSPHG = %x\n", !!(s & MS_IsMSPHG)); + printk(KERN_INFO "WtP= %x\n", !!(s & MS_WtP)); + if (s & MS_IsMSPro) { + MSP_BlockSize = (bbuf[6] << 8) | bbuf[7]; + MSP_UserAreaBlocks = (bbuf[10] << 8) | bbuf[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", bbuf[0]); + return USB_STOR_TRANSPORT_ERROR; + } + + return USB_STOR_TRANSPORT_GOOD; +} + +static int ene_sd_init(struct us_data *us) +{ + int result; + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + u8 *bbuf = info->bbuf; + + 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, bbuf, 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 = bbuf[0]; + if ((info->SD_Status & SD_Insert) && (info->SD_Status & SD_Ready)) { + unsigned int s = info->SD_Status; + + ene_get_card_status(us, bbuf); + usb_stor_dbg(us, "Insert = %x\n", !!(s & SD_Insert)); + usb_stor_dbg(us, "Ready = %x\n", !!(s & SD_Ready)); + usb_stor_dbg(us, "IsMMC = %x\n", !!(s & SD_IsMMC)); + usb_stor_dbg(us, "HiCapacity = %x\n", !!(s & SD_HiCapacity)); + usb_stor_dbg(us, "HiSpeed = %x\n", !!(s & SD_HiSpeed)); + usb_stor_dbg(us, "WtP = %x\n", !!(s & SD_WtP)); + } else { + usb_stor_dbg(us, "SD Card Not Ready --- %x\n", bbuf[0]); + return USB_STOR_TRANSPORT_ERROR; + } + return USB_STOR_TRANSPORT_GOOD; +} + + +static int ene_init(struct us_data *us) +{ + int result; + u8 misc_reg03; + struct ene_ub6250_info *info = (struct ene_ub6250_info *)(us->extra); + u8 *bbuf = info->bbuf; + + result = ene_get_card_type(us, REG_CARD_STATUS, bbuf); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + misc_reg03 = bbuf[0]; + if (misc_reg03 & 0x01) { + if (!(info->SD_Status & SD_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 & MS_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; + + switch (srb->cmnd[0]) { + case TEST_UNIT_READY: + result = sd_scsi_test_unit_ready(us, srb); + break; /* 0x00 */ + case REQUEST_SENSE: + result = do_scsi_request_sense(us, srb); + break; /* 0x03 */ + case INQUIRY: + result = do_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; + } + if (result == USB_STOR_TRANSPORT_GOOD) + info->SrbStatus = SS_SUCCESS; + 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; + + switch (srb->cmnd[0]) { + case TEST_UNIT_READY: + result = ms_scsi_test_unit_ready(us, srb); + break; /* 0x00 */ + case REQUEST_SENSE: + result = do_scsi_request_sense(us, srb); + break; /* 0x03 */ + case INQUIRY: + result = do_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; + } + if (result == USB_STOR_TRANSPORT_GOOD) + info->SrbStatus = SS_SUCCESS; + return result; +} + +static int ene_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + int result = USB_STOR_XFER_GOOD; + 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 & SD_Ready) && + !(info->MS_Status & MS_Ready))) + result = ene_init(us); + if (result == USB_STOR_XFER_GOOD) { + result = USB_STOR_TRANSPORT_ERROR; + if (info->SD_Status & SD_Ready) + result = sd_scsi_irp(us, srb); + + if (info->MS_Status & MS_Ready) + result = ms_scsi_irp(us, srb); + } + return result; +} + +static struct scsi_host_template ene_ub6250_host_template; + +static int ene_ub6250_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int result; + u8 misc_reg03; + struct us_data *us; + struct ene_ub6250_info *info; + + result = usb_stor_probe1(&us, intf, id, + (id - ene_ub6250_usb_ids) + ene_ub6250_unusual_dev_list, + &ene_ub6250_host_template); + if (result) + return result; + + /* FIXME: where should the code alloc extra buf ? */ + us->extra = kzalloc_obj(struct ene_ub6250_info); + if (!us->extra) + return -ENOMEM; + us->extra_destructor = ene_ub6250_info_destructor; + + info = (struct ene_ub6250_info *)(us->extra); + info->bbuf = kmalloc(512, GFP_KERNEL); + if (!info->bbuf) { + kfree(us->extra); + return -ENOMEM; + } + + 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, info->bbuf); + if (result != USB_STOR_XFER_GOOD) { + usb_stor_disconnect(intf); + return USB_STOR_TRANSPORT_ERROR; + } + + misc_reg03 = info->bbuf[0]; + if (!(misc_reg03 & 0x01)) { + pr_info("ums_eneub6250: This driver only supports SD/MS cards. " + "It does not support SM cards.\n"); + } + + return result; +} + + +#ifdef CONFIG_PM + +static int ene_ub6250_resume(struct usb_interface *iface) +{ + 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 &= ~SD_Ready; */ + info->SD_Status = 0; + info->MS_Status = 0; + info->SM_Status = 0; + + return 0; +} + +static int ene_ub6250_reset_resume(struct usb_interface *iface) +{ + 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 &= ~SD_Ready; */ + info->SD_Status = 0; + info->MS_Status = 0; + info->SM_Status = 0; + + return 0; +} + +#else + +#define ene_ub6250_resume NULL +#define ene_ub6250_reset_resume NULL + +#endif + +static struct usb_driver ene_ub6250_driver = { + .name = DRV_NAME, + .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_stor_driver(ene_ub6250_driver, ene_ub6250_host_template, DRV_NAME); diff --git a/drivers/usb/storage/freecom.c b/drivers/usb/storage/freecom.c new file mode 100644 index 000000000..a07562090 --- /dev/null +++ b/drivers/usb/storage/freecom.c @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Freecom USB/IDE adaptor + * + * Freecom v0.1: + * + * First release + * + * Current development and maintenance by: + * (C) 2000 David Brown + * + * This driver was developed with information provided in FREECOM's USB + * Programmers Reference Guide. For further information contact Freecom + * (https://www.freecom.de/) + */ + +#include +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" +#include "scsiglue.h" + +#define DRV_NAME "ums-freecom" + +MODULE_DESCRIPTION("Driver for Freecom USB/IDE adaptor"); +MODULE_AUTHOR("David Brown "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USB_STORAGE"); + +#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 const 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 const 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; + } + + 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 */ + msleep(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 */ + msleep(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); +} +#endif + +static struct scsi_host_template freecom_host_template; + +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, + &freecom_host_template); + 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 = DRV_NAME, + .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_stor_driver(freecom_driver, freecom_host_template, DRV_NAME); diff --git a/drivers/usb/storage/initializers.c b/drivers/usb/storage/initializers.c new file mode 100644 index 000000000..b243bd552 --- /dev/null +++ b/drivers/usb/storage/initializers.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#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"); + result = usb_stor_control_msg(us, us->send_ctrl_pipe, + 0x0C, USB_RECIP_INTERFACE | USB_TYPE_VENDOR, + 0x01, 0x0, NULL, 0x0, 5 * HZ); + 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 const 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, 1 * HZ); + 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 000000000..dcd7b7e5e --- /dev/null +++ b/drivers/usb/storage/initializers.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + */ + +#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 000000000..a4fd5dda3 --- /dev/null +++ b/drivers/usb/storage/isd200.c @@ -0,0 +1,1586 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + * + * 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" + +#define DRV_NAME "ums-isd200" + +MODULE_DESCRIPTION("Driver for In-System Design, Inc. ISD200 ASIC"); +MODULE_AUTHOR("Björn Stenberg "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USB_STORAGE"); + +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 const 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 const 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[MAX_COMMAND_SIZE]; + 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)); + memcpy(srb->cmnd, info->cmnd, MAX_COMMAND_SIZE); + 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 Suspend: 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; + 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); + + /* Prevent division by 0 in isd200_scsi_to_ata() */ + if (id[ATA_ID_HEADS] == 0 || id[ATA_ID_SECTORS] == 0) { + usb_stor_dbg(us, " Invalid ATA Identify data\n"); + retStatus = ISD200_ERROR; + goto Done; + } + + 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; + } + } + + Done: + 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 removable 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: + * error status code + */ +static int isd200_init_info(struct us_data *us) +{ + struct isd200_info *info; + + info = kzalloc_obj(struct isd200_info); + if (!info) + return -ENOMEM; + + 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); + return -ENOMEM; + } + + us->extra = info; + us->extra_destructor = isd200_free_info_ptrs; + + return 0; +} + +/************************************************************************** + * Initialization for the ISD200 + */ + +static int isd200_Initialization(struct us_data *us) +{ + int rc = 0; + + usb_stor_dbg(us, "ISD200 Initialization...\n"); + + /* Initialize ISD200 info struct */ + + if (isd200_init_info(us) < 0) { + usb_stor_dbg(us, "ERROR Initializing ISD200 Info struct\n"); + rc = -ENOMEM; + } else { + /* Get device specific data */ + + if (isd200_get_inquiry_data(us) != ISD200_GOOD) { + usb_stor_dbg(us, "ISD200 Initialization Failure\n"); + rc = -EINVAL; + } else { + usb_stor_dbg(us, "ISD200 Initialization complete\n"); + } + } + + return rc; +} + + +/************************************************************************** + * 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, orig_bufflen; + union ata_cdb ataCdb; + + /* Make sure driver was initialized */ + + if (us->extra == NULL) { + usb_stor_dbg(us, "ERROR Driver not initialized\n"); + srb->result = DID_ERROR << 16; + return; + } + + 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 struct scsi_host_template isd200_host_template; + +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, + &isd200_host_template); + 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 = DRV_NAME, + .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_stor_driver(isd200_driver, isd200_host_template, DRV_NAME); diff --git a/drivers/usb/storage/jumpshot.c b/drivers/usb/storage/jumpshot.c new file mode 100644 index 000000000..a9c480914 --- /dev/null +++ b/drivers/usb/storage/jumpshot.c @@ -0,0 +1,683 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 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" +#include "scsiglue.h" + +#define DRV_NAME "ums-jumpshot" + +MODULE_DESCRIPTION("Driver for Lexar \"Jumpshot\" Compact Flash reader"); +MODULE_AUTHOR("Jimmie Mayfield "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USB_STORAGE"); + +/* + * 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 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 const 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 const unsigned char rw_err_page[12] = { + 0x1, 0xA, 0x21, 1, 0, 0, 0, 0, 1, 0, 0, 0 + }; + static const unsigned char cache_page[12] = { + 0x8, 0xA, 0x1, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + static const unsigned char rbac_page[12] = { + 0x1B, 0xA, 0, 0x81, 0, 0, 0, 0, 0, 0, 0, 0 + }; + static const 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 const unsigned char inquiry_response[8] = { + 0x00, 0x80, 0x00, 0x01, 0x1F, 0x00, 0x00, 0x00 + }; + + if (!us->extra) { + us->extra = kzalloc_obj(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 struct scsi_host_template jumpshot_host_template; + +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, + &jumpshot_host_template); + 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 = DRV_NAME, + .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_stor_driver(jumpshot_driver, jumpshot_host_template, DRV_NAME); diff --git a/drivers/usb/storage/karma.c b/drivers/usb/storage/karma.c new file mode 100644 index 000000000..5f199c21b --- /dev/null +++ b/drivers/usb/storage/karma.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Rio Karma + * + * (c) 2006 Bob Copeland + * (c) 2006 Keith Bennett + */ + +#include +#include + +#include +#include +#include + +#include "usb.h" +#include "transport.h" +#include "debug.h" +#include "scsiglue.h" + +#define DRV_NAME "ums-karma" + +MODULE_DESCRIPTION("Driver for Rio Karma"); +MODULE_AUTHOR("Bob Copeland , Keith Bennett "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USB_STORAGE"); + +#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 const 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 const 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; + 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, NULL); + if (result != USB_STOR_XFER_GOOD) + goto err; + + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + data->recv, RIO_RECV_LEN, NULL); + 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) +{ + struct karma_data *data = kzalloc_obj(struct karma_data, GFP_NOIO); + + if (!data) + return -ENOMEM; + + data->recv = kmalloc(RIO_RECV_LEN, GFP_NOIO); + if (!data->recv) { + kfree(data); + return -ENOMEM; + } + + us->extra = data; + us->extra_destructor = rio_karma_destructor; + if (rio_karma_send_command(RIO_ENTER_STORAGE, us)) + return -EIO; + + data->in_storage = 1; + + return 0; +} + +static struct scsi_host_template karma_host_template; + +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, + &karma_host_template); + 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 = DRV_NAME, + .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_stor_driver(karma_driver, karma_host_template, DRV_NAME); diff --git a/drivers/usb/storage/onetouch.c b/drivers/usb/storage/onetouch.c new file mode 100644 index 000000000..49d281f7b --- /dev/null +++ b/drivers/usb/storage/onetouch.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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) + * + */ +#include +#include +#include +#include +#include +#include "usb.h" +#include "debug.h" +#include "scsiglue.h" + +#define DRV_NAME "ums-onetouch" + +MODULE_DESCRIPTION("Maxtor USB OneTouch hard drive button driver"); +MODULE_AUTHOR("Nick Sillik "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USB_STORAGE"); + +#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 const 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 const 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); + maxp = min(maxp, ONETOUCH_PKT_LEN); + + onetouch = kzalloc_obj(struct usb_onetouch); + 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) + strscpy(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 struct scsi_host_template onetouch_host_template; + +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, + &onetouch_host_template); + if (result) + return result; + + /* Use default transport and protocol */ + + result = usb_stor_probe2(us); + return result; +} + +static struct usb_driver onetouch_driver = { + .name = DRV_NAME, + .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_stor_driver(onetouch_driver, onetouch_host_template, DRV_NAME); diff --git a/drivers/usb/storage/option_ms.c b/drivers/usb/storage/option_ms.c new file mode 100644 index 000000000..7c0b05a36 --- /dev/null +++ b/drivers/usb/storage/option_ms.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Option High Speed Mobile Devices. + * + * (c) 2008 Dan Williams + * + * Inspiration taken from sierra_ms.c by Kevin Lloyd + */ + +#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) +{ + static 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) +{ + static 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 000000000..643999218 --- /dev/null +++ b/drivers/usb/storage/option_ms.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#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 000000000..0cff54ad9 --- /dev/null +++ b/drivers/usb/storage/protocol.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#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 = 0; + struct scatterlist *sg = *sgptr; + struct sg_mapping_iter miter; + unsigned int nents = scsi_sg_count(srb); + + if (sg) + nents = sg_nents(sg); + else + sg = scsi_sglist(srb); + + sg_miter_start(&miter, sg, nents, dir == FROM_XFER_BUF ? + SG_MITER_FROM_SG: SG_MITER_TO_SG); + + if (!sg_miter_skip(&miter, *offset)) + return cnt; + + while (sg_miter_next(&miter) && cnt < buflen) { + unsigned int len = min(miter.length, buflen - cnt); + + if (dir == FROM_XFER_BUF) + memcpy(buffer + cnt, miter.addr, len); + else + memcpy(miter.addr, buffer + cnt, len); + + if (*offset + len < miter.piter.sg->length) { + *offset += len; + *sgptr = miter.piter.sg; + } else { + *offset = 0; + *sgptr = sg_next(miter.piter.sg); + } + cnt += len; + } + sg_miter_stop(&miter); + + 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 000000000..1d102463a --- /dev/null +++ b/drivers/usb/storage/protocol.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + */ + +#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 000000000..af038b897 --- /dev/null +++ b/drivers/usb/storage/realtek_cr.c @@ -0,0 +1,1071 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Realtek RTS51xx USB card reader + * + * Copyright(c) 2009 Realtek Semiconductor Corp. All rights reserved. + * + * 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" +#include "scsiglue.h" + +#define DRV_NAME "ums-realtek" + +MODULE_DESCRIPTION("Driver for Realtek USB Card Reader"); +MODULE_AUTHOR("wwang "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USB_STORAGE"); + +static int auto_delink_en = 1; +module_param(auto_delink_en, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(auto_delink_en, "auto delink mode (0=firmware, 1=software [default])"); + +#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 protocol chosen 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 const 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 : US_BULK_FLAG_OUT; + 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 = le32_to_cpu(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 > 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 : US_BULK_FLAG_OUT; + 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 -ENOMEM; + + 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; +} + +#ifdef CONFIG_PM +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"); + } + } +} +#endif + +#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 + secs_to_jiffies(ss_delay); + mod_timer(&chip->rts51x_suspend_timer, chip->timer_expires); +} + +static void rts51x_suspend_timer_fn(struct timer_list *t) +{ + struct rts51x_chip *chip = timer_container_of(chip, t, + rts51x_suspend_timer); + 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, power.usage:%d\n", + atomic_read(&us->pusb_intf->dev.power.usage_count)); + + if (atomic_read(&us->pusb_intf->dev.power.usage_count) > 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, power.usage:%d\n", + 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 const u8 media_not_present[] = { 0x70, 0, 0x02, 0, 0, 0, 0, + 10, 0, 0, 0, 0, 0x3A, 0, 0, 0, 0, 0 + }; + static const 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, power.usage:%d\n", + atomic_read(&us->pusb_intf->dev.power.usage_count)); + + if (atomic_read(&us->pusb_intf->dev.power.usage_count) <= 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; + timer_setup(&chip->rts51x_suspend_timer, rts51x_suspend_timer_fn, 0); + fw5895_init(us); + + /* enable autosuspend function 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) { + timer_delete(&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_obj(struct rts51x_chip); + 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_PID(chip, 0x0138) || CHECK_PID(chip, 0x0158) || + CHECK_PID(chip, 0x0159)) { + 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 struct scsi_host_template realtek_cr_host_template; + +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, + &realtek_cr_host_template); + if (result) + return result; + + result = usb_stor_probe2(us); + + return result; +} + +static struct usb_driver realtek_cr_driver = { + .name = DRV_NAME, + .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_stor_driver(realtek_cr_driver, realtek_cr_host_template, DRV_NAME); diff --git a/drivers/usb/storage/scsiglue.c b/drivers/usb/storage/scsiglue.c new file mode 100644 index 000000000..97de28e85 --- /dev/null +++ b/drivers/usb/storage/scsiglue.c @@ -0,0 +1,699 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#include +#include +#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 sdev_init (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; + + /* 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; + + /* + * Some USB storage devices reset if the IO advice hints grouping mode + * page is queried. Hence skip that mode page. + */ + sdev->sdev_bflags |= BLIST_SKIP_IO_HINTS; + + return 0; +} + +static int sdev_configure(struct scsi_device *sdev, struct queue_limits *lim) +{ + struct us_data *us = host_to_us(sdev->host); + struct device *dev = us->pusb_dev->bus->sysdev; + + /* + * 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_SIZE >> 9; + lim->max_hw_sectors = min(lim->max_hw_sectors, 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. + */ + lim->max_hw_sectors = 0x7FFFFF; + } else if (us->pusb_dev->speed >= USB_SPEED_SUPER) { + /* + * USB3 devices will be limited to 2048 sectors. This gives us + * better throughput on most devices. + */ + lim->max_hw_sectors = 2048; + } + + /* + * The max_hw_sectors should be up to maximum size of a mapping for + * the device. Otherwise, a DMA API might fail on swiotlb environment. + */ + lim->max_hw_sectors = min_t(size_t, + lim->max_hw_sectors, dma_max_mapping_size(dev) >> SECTOR_SHIFT); + + /* + * We can't put these settings in sdev_init() 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 report generic values until the media has been + * accessed. Force a READ(10) prior to querying device + * characteristics. + */ + sdev->read_before_ms = 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, so skip vpd + * pages if not forced by SCSI layer. + */ + sdev->skip_vpd_pages = !sdev->try_vpd_pages; + + /* 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 + * unless US_FL_BAD_SENSE quirk is specified. + */ + if (sdev->scsi_level > SCSI_SPC_2 && + !(us->fflags & US_FL_BAD_SENSE)) + 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 negligible 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; + + /* A few buggy USB-ATA bridges don't understand FUA */ + if (us->fflags & US_FL_BROKEN_FUA) + sdev->broken_fua = 1; + + /* Some even totally fail to indicate a cache */ + if (us->fflags & US_FL_ALWAYS_SYNC) { + /* don't read caching information */ + sdev->skip_ms_page_8 = 1; + sdev->skip_ms_page_3f = 1; + /* assume sync is needed */ + sdev->wce_default_on = 1; + } + } else { + + /* + * Non-disk-type devices don't need to ignore 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 enum scsi_qc_status queuecommand_lck(struct scsi_cmnd *srb) +{ + void (*done)(struct scsi_cmnd *) = scsi_done; + struct us_data *us = host_to_us(srb->device->host); + + /* check for state-transition errors */ + if (us->srb != NULL) { + dev_err(&us->pusb_intf->dev, + "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; + } + + if ((us->fflags & US_FL_NO_ATA_1X) && + (srb->cmnd[0] == ATA_12 || srb->cmnd[0] == ATA_16)) { + memcpy(srb->sense_buffer, usb_stor_sense_invalidCDB, + sizeof(usb_stor_sense_invalidCDB)); + srb->result = SAM_STAT_CHECK_CONDITION; + done(srb); + return 0; + } + + /* enqueue the command and wake up the control thread */ + 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_matching(struct us_data *us, struct scsi_cmnd *srb_match) +{ + /* + * us->srb together with the TIMED_OUT, RESETTING, and ABORTING + * bits are protected by the host lock. + */ + scsi_lock(us_to_host(us)); + + /* is there any active pending command to abort ? */ + if (!us->srb) { + scsi_unlock(us_to_host(us)); + usb_stor_dbg(us, "-- nothing to abort\n"); + return SUCCESS; + } + + /* Does the command match the passed srb if any ? */ + if (srb_match && us->srb != srb_match) { + scsi_unlock(us_to_host(us)); + usb_stor_dbg(us, "-- pending command mismatch\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; +} + +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__); + return command_abort_matching(us, srb); +} + +/* + * 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__); + + /* abort any pending command before reset */ + command_abort_matching(us, NULL); + + /* 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; +} + +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 */ + seq_printf(m, " 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"; + seq_printf(m, " 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"; + seq_printf(m, " Product: %s\n", string); + if (us->pusb_dev->serial) + string = us->pusb_dev->serial; + else + string = "None"; + seq_printf(m, "Serial Number: %s\n", string); + + /* show the protocol and transport */ + seq_printf(m, " Protocol: %s\n", us->protocol_name); + seq_printf(m, " Transport: %s\n", us->transport_name); + + /* show the device flags */ + seq_printf(m, " 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 max_sectors_show(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 max_sectors_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct scsi_device *sdev = to_scsi_device(dev); + struct queue_limits lim; + unsigned short ms; + int ret; + + if (sscanf(buf, "%hu", &ms) <= 0) + return -EINVAL; + + lim = queue_limits_start_update(sdev->request_queue); + lim.max_hw_sectors = ms; + ret = queue_limits_commit_update_frozen(sdev->request_queue, &lim); + if (ret) + return ret; + return count; +} +static DEVICE_ATTR_RW(max_sectors); + +static struct attribute *usb_sdev_attrs[] = { + &dev_attr_max_sectors.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(usb_sdev); + +/* + * this defines our host template, with which we'll allocate hosts + */ + +static const 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, + + /* unknown initiator id */ + .this_id = -1, + + .sdev_init = sdev_init, + .sdev_configure = sdev_configure, + .target_alloc = target_alloc, + + /* lots of sg segments can be handled */ + .sg_tablesize = SG_MAX_SEGMENTS, + + /* + * Some host controllers may have alignment requirements. + * We'll play it safe by requiring 512-byte alignment always. + */ + .dma_alignment = 511, + + /* + * Limit the total size of a transfer to 120 KB. + * + * Some devices are known to choke with anything larger. It seems like + * the problem stems from the fact that original IDE controllers had + * only an 8-bit register to hold the number of sectors in one transfer + * and even those couldn't handle a full 256 sectors. + * + * Because we want to make sure we interoperate with as many devices as + * possible, we will maintain a 240 sector transfer size limit for USB + * Mass Storage devices. + * + * Tests show that other operating have similar limits with Microsoft + * Windows 7 limiting transfers to 128 sectors for both USB2 and USB3 + * and Apple Mac OS X 10.11 limiting transfers to 256 sectors for USB2 + * and 2048 for USB3 devices. + */ + .max_sectors = 240, + + /* emulated HBA */ + .emulated = 1, + + /* we do our own delay after a device or bus reset */ + .skip_settle_delay = 1, + + /* sysfs device attributes */ + .sdev_groups = usb_sdev_groups, + + /* module management */ + .module = THIS_MODULE +}; + +void usb_stor_host_template_init(struct scsi_host_template *sht, + const char *name, struct module *owner) +{ + *sht = usb_stor_host_template; + sht->name = name; + sht->proc_name = name; + sht->module = owner; +} +EXPORT_SYMBOL_GPL(usb_stor_host_template_init); + +/* 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 000000000..2a79c3ed4 --- /dev/null +++ b/drivers/usb/storage/scsiglue.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + */ + +#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 void usb_stor_host_template_init(struct scsi_host_template *sht, + const char *name, struct module *owner); + +extern unsigned char usb_stor_sense_invalidCDB[18]; + +#endif diff --git a/drivers/usb/storage/sddr09.c b/drivers/usb/storage/sddr09.c new file mode 100644 index 000000000..3d45e1b54 --- /dev/null +++ b/drivers/usb/storage/sddr09.c @@ -0,0 +1,1790 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +/* + * 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" +#include "scsiglue.h" + +#define DRV_NAME "ums-sddr09" + +MODULE_DESCRIPTION("Driver for SanDisk SDDR-09 SmartMedia reader"); +MODULE_AUTHOR("Andries Brouwer , Robert Baruch "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USB_STORAGE"); + +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 const 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 const 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_t(unsigned int, sectors, info->blocksize) * info->pagesize; + buffer = kmalloc(len, GFP_NOIO); + if (!buffer) + 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; + + lbap = ((lba % 1000) << 1) | 0x1000; + if (parity[MSB_of(lbap) ^ LSB_of(lbap)]) + lbap ^= 1; + pba = info->lba_to_pba[lba]; + + 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; + } + + 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) + 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_t(unsigned int, sectors, info->blocksize) * info->pagesize; + buffer = kmalloc(len, GFP_NOIO); + if (!buffer) { + 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; + const char *wp_fmt; + + result = sddr09_read_status(us, &status); + if (result) { + usb_stor_dbg(us, "read_status fails\n"); + return result; + } + if ((status & 0x80) == 0) { + info->flags |= SDDR09_WP; /* write protected */ + wp_fmt = " WP"; + } else { + wp_fmt = ""; + } + usb_stor_dbg(us, "status 0x%02X%s%s%s%s\n", status, wp_fmt, + status & 0x40 ? " Ready" : "", + status & LUNBITS ? " Suspended" : "", + status & 0x01 ? " Error" : ""); + + 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 const struct nand_flash_dev * +sddr09_get_cardinfo(struct us_data *us, unsigned char flags) { + const 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 = %4ph", deviceID); + + /* 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) { + 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_objs(int, numblocks, GFP_NOIO); + info->pba_to_lba = kmalloc_objs(int, numblocks, 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_obj(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", (u8)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", (u8)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 const unsigned char inquiry_response[8] = { + 0x00, 0x80, 0x00, 0x02, 0x1F, 0x00, 0x00, 0x00 + }; + + /* note: no block descriptor support */ + static const 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) { + const 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 struct scsi_host_template sddr09_host_template; + +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, + &sddr09_host_template); + 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 = DRV_NAME, + .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_stor_driver(sddr09_driver, sddr09_host_template, DRV_NAME); diff --git a/drivers/usb/storage/sddr55.c b/drivers/usb/storage/sddr55.c new file mode 100644 index 000000000..e05e824cd --- /dev/null +++ b/drivers/usb/storage/sddr55.c @@ -0,0 +1,1020 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for SanDisk SDDR-55 SmartMedia reader + * + * SDDR55 driver v0.1: + * + * First release + * + * Current development and maintenance by: + * (c) 2002 Simon Munton + */ + +#include +#include +#include +#include + +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" +#include "scsiglue.h" + +#define DRV_NAME "ums-sddr55" + +MODULE_DESCRIPTION("Driver for SanDisk SDDR-55 SmartMedia reader"); +MODULE_AUTHOR("Simon Munton"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USB_STORAGE"); + +/* + * 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 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 const 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 int 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_t(unsigned int, sectors, 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_t(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 int 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_t(unsigned int, sectors, 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_t(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 if device-reported new_pba is out of range */ + if (new_pba >= (info->capacity >> (info->blockshift + info->pageshift))) { + result = USB_STOR_TRANSPORT_FAILED; + goto leave; + } + + /* 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 manufacturerID; + unsigned char 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; + fallthrough; + 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_array(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_objs(int, numblocks, GFP_NOIO); + info->pba_to_lba = kmalloc_objs(int, numblocks, 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 const unsigned char inquiry_response[8] = { + 0x00, 0x80, 0x00, 0x02, 0x1F, 0x00, 0x00, 0x00 + }; + // write-protected for now, no block descriptor support + static const 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_obj(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 struct scsi_host_template sddr55_host_template; + +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, + &sddr55_host_template); + 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 = DRV_NAME, + .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_stor_driver(sddr55_driver, sddr55_host_template, DRV_NAME); diff --git a/drivers/usb/storage/shuttle_usbat.c b/drivers/usb/storage/shuttle_usbat.c new file mode 100644 index 000000000..7e5424268 --- /dev/null +++ b/drivers/usb/storage/shuttle_usbat.c @@ -0,0 +1,1872 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" +#include "scsiglue.h" + +#define DRV_NAME "ums-usbat" + +MODULE_DESCRIPTION("Driver for SCM Microsystems (a.k.a. Shuttle) USB-ATAPI cable"); +MODULE_AUTHOR("Daniel Drake , Robert Baruch "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USB_STORAGE"); + +/* 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 const 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 const 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", + str_write_read(direction == DMA_TO_DEVICE)); + + } 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_obj(struct usbat_info, GFP_NOIO); + if (!us->extra) + return -ENOMEM; + + 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 -EIO; + + usb_stor_dbg(us, "INIT 1\n"); + + msleep(2000); + + rc = usbat_read_user_io(us, status); + if (rc != USB_STOR_TRANSPORT_GOOD) + return -EIO; + + usb_stor_dbg(us, "INIT 2\n"); + + rc = usbat_read_user_io(us, status); + if (rc != USB_STOR_XFER_GOOD) + return -EIO; + + rc = usbat_read_user_io(us, status); + if (rc != USB_STOR_XFER_GOOD) + return -EIO; + + usb_stor_dbg(us, "INIT 3\n"); + + rc = usbat_select_and_test_registers(us); + if (rc != USB_STOR_TRANSPORT_GOOD) + return -EIO; + + usb_stor_dbg(us, "INIT 4\n"); + + rc = usbat_read_user_io(us, status); + if (rc != USB_STOR_XFER_GOOD) + return -EIO; + + 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 -EIO; + + usb_stor_dbg(us, "INIT 6\n"); + + rc = usbat_read_user_io(us, status); + if (rc != USB_STOR_XFER_GOOD) + return -EIO; + + usb_stor_dbg(us, "INIT 7\n"); + + msleep(1400); + + rc = usbat_read_user_io(us, status); + if (rc != USB_STOR_XFER_GOOD) + return -EIO; + + usb_stor_dbg(us, "INIT 8\n"); + + rc = usbat_select_and_test_registers(us); + if (rc != USB_STOR_TRANSPORT_GOOD) + return -EIO; + + 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 -EIO; + + 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 -EIO; + + usb_stor_dbg(us, "INIT 11\n"); + + return 0; +} + +/* + * 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 const 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 struct scsi_host_template usbat_host_template; + +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, + &usbat_host_template); + 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 = DRV_NAME, + .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_stor_driver(usbat_driver, usbat_host_template, DRV_NAME); diff --git a/drivers/usb/storage/sierra_ms.c b/drivers/usb/storage/sierra_ms.c new file mode 100644 index 000000000..c5e3eeeb2 --- /dev/null +++ b/drivers/usb/storage/sierra_ms.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0 +#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 truinst_show(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 = sysfs_emit(buf, "Forced Mass Storage\n"); + } else { + swocInfo = kmalloc_obj(struct swoc_info); + if (!swocInfo) { + sysfs_emit(buf, "Error\n"); + return -ENOMEM; + } + result = sierra_get_swoc_info(udev, swocInfo); + if (result < 0) { + dev_dbg(dev, "SWIMS: failed SWoC query\n"); + kfree(swocInfo); + sysfs_emit(buf, "Error\n"); + return -EIO; + } + debug_swoc(dev, swocInfo); + result = sysfs_emit(buf, + "REV=%02d SKU=%04X VER=%04X\n", + swocInfo->rev, + swocInfo->LinuxSKU, + swocInfo->LinuxVer); + kfree(swocInfo); + } + return result; +} +static DEVICE_ATTR_RO(truinst); + +int sierra_ms_init(struct us_data *us) +{ + int result, retries; + struct swoc_info *swocInfo; + struct usb_device *udev; + + udev = us->pusb_dev; + + /* 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_obj(struct swoc_info); + 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: + return device_create_file(&us->pusb_intf->dev, &dev_attr_truinst); +} + diff --git a/drivers/usb/storage/sierra_ms.h b/drivers/usb/storage/sierra_ms.h new file mode 100644 index 000000000..3e9da537d --- /dev/null +++ b/drivers/usb/storage/sierra_ms.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#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 000000000..9a4bf86e7 --- /dev/null +++ b/drivers/usb/storage/transport.c @@ -0,0 +1,1462 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#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); + 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)) + goto 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); + goto 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); + +usb_stor_xfer_error: + if (act_len) + *act_len = 0; + return USB_STOR_XFER_ERROR; +} + +/* + * 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 const 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 = scsi_cmd_to_rq(srb)->q->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; + } + + /* Some devices (Kindle) require another command after SYNC CACHE */ + if ((us->fflags & US_FL_SENSE_AFTER_SYNC) && + srb->cmnd[0] == SYNCHRONIZE_CACHE) { + usb_stor_dbg(us, "-- sense after SYNC CACHE\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; + } + + /* + * ATA-passthru commands use sense data to report + * the command completion status, and often devices + * return Check Condition status when nothing is + * wrong. + */ + else if (srb->cmnd[0] == ATA_16 || + srb->cmnd[0] == ATA_12) { + /* leave the data alone */ + } + + /* + * 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 */ + /* + * Command is sometime (f.e. after scsi_eh_prep_cmnd) on the stack. + * Stack may be vmallocated. So no DMA for us. Make a copy. + */ + memcpy(us->iobuf, srb->cmnd, srb->cmd_len); + result = usb_stor_ctrl_transfer(us, us->send_ctrl_pipe, + US_CBI_ADSC, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0, + us->ifnum, us->iobuf, 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 valid. */ + if (result > 0) { + if (us->iobuf[0] <= US_BULK_MAX_LUN_LIMIT) { + return us->iobuf[0]; + } else { + dev_info(&us->pusb_intf->dev, + "Max LUN %d is not valid, using 0 instead", + 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 : US_BULK_FLAG_OUT; + 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)) + usleep_range(125, 150); + + 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 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)) { + unsigned char buf[US_BULK_CS_WRAP_LEN]; + + usb_stor_dbg(us, "Device skipped data phase\n"); + + /* + * Devices skipping data phase might leave CSW data in srb's + * transfer buffer. Zero it to prevent USB protocol leakage. + */ + sg = NULL; + offset = 0; + memset(buf, 0, sizeof(buf)); + if (usb_stor_access_xfer_buf(buf, + US_BULK_CS_WRAP_LEN, srb, &sg, + &offset, TO_XFER_BUF) != + US_BULK_CS_WRAP_LEN) + usb_stor_dbg(us, "Failed to clear CSW data\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), 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 000000000..74ffd0d7e --- /dev/null +++ b/drivers/usb/storage/transport.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + */ + +#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-detect.h b/drivers/usb/storage/uas-detect.h new file mode 100644 index 000000000..4d3b49e5b --- /dev/null +++ b/drivers/usb/storage/uas-detect.h @@ -0,0 +1,162 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include +#include +#include "usb.h" + +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 struct usb_host_interface *uas_find_uas_alt_setting( + struct usb_interface *intf) +{ + int i; + + for (i = 0; i < intf->num_altsetting; i++) { + struct usb_host_interface *alt = &intf->altsetting[i]; + + if (uas_is_interface(alt)) + return alt; + } + + return NULL; +} + +static int uas_find_endpoints(struct usb_host_interface *alt, + struct usb_host_endpoint *eps[]) +{ + struct usb_host_endpoint *endpoint = alt->endpoint; + unsigned i, n_endpoints = alt->desc.bNumEndpoints; + + for (i = 0; i < n_endpoints; i++) { + unsigned char *extra = endpoint[i].extra; + int len = endpoint[i].extralen; + while (len >= 3) { + 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]; + } + } + + if (!eps[0] || !eps[1] || !eps[2] || !eps[3]) + return -ENODEV; + + return 0; +} + +static int uas_use_uas_driver(struct usb_interface *intf, + const struct usb_device_id *id, + u64 *flags_ret) +{ + struct usb_host_endpoint *eps[4] = { }; + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + u64 flags = id->driver_info; + struct usb_host_interface *alt; + int r; + + alt = uas_find_uas_alt_setting(intf); + if (!alt) + return 0; + + r = uas_find_endpoints(alt, eps); + if (r < 0) + return 0; + + /* + * ASMedia has a number of usb3 to sata bridge chips, at the time of + * this writing the following versions exist: + * ASM1051 - no uas support version + * ASM1051 - with broken (*) uas support + * ASM1053 - with working uas support, but problems with large xfers + * ASM1153 - with working uas support + * + * Devices with these chips re-use a number of device-ids over the + * entire line, so the device-id is useless to determine if we're + * dealing with an ASM1051 (which we want to avoid). + * + * The ASM1153 can be identified by config.MaxPower == 0, + * where as the ASM105x models have config.MaxPower == 36. + * + * Differentiating between the ASM1053 and ASM1051 is trickier, when + * connected over USB-3 we can look at the number of streams supported, + * ASM1051 supports 32 streams, where as early ASM1053 versions support + * 16 streams, newer ASM1053-s also support 32 streams, but have a + * different prod-id. + * + * (*) ASM1051 chips do work with UAS with some disks (with the + * US_FL_NO_REPORT_OPCODES quirk), but are broken with other disks + */ + if (le16_to_cpu(udev->descriptor.idVendor) == 0x174c && + (le16_to_cpu(udev->descriptor.idProduct) == 0x5106 || + le16_to_cpu(udev->descriptor.idProduct) == 0x55aa)) { + if (udev->actconfig->desc.bMaxPower == 0) { + /* ASM1153, do nothing */ + } else if (udev->speed < USB_SPEED_SUPER) { + /* No streams info, assume ASM1051 */ + flags |= US_FL_IGNORE_UAS; + } else if (usb_ss_max_streams(&eps[1]->ss_ep_comp) == 32) { + /* Possibly an ASM1051, disable uas */ + flags |= US_FL_IGNORE_UAS; + } else { + /* ASM1053, these have issues with large transfers */ + flags |= US_FL_MAX_SECTORS_240; + } + } + + /* All Seagate disk enclosures have broken ATA pass-through support */ + if (le16_to_cpu(udev->descriptor.idVendor) == 0x0bc2) + flags |= US_FL_NO_ATA_1X; + + /* + * RTL9210-based enclosure from HIKSEMI, MD202 reportedly have issues + * with UAS. This isn't distinguishable with just idVendor and + * idProduct, use manufacturer and product too. + * + * Reported-by: Hongling Zeng + */ + if (le16_to_cpu(udev->descriptor.idVendor) == 0x0bda && + le16_to_cpu(udev->descriptor.idProduct) == 0x9210 && + (udev->manufacturer && !strcmp(udev->manufacturer, "HIKSEMI")) && + (udev->product && !strcmp(udev->product, "MD202"))) + flags |= US_FL_IGNORE_UAS; + + usb_stor_adjust_quirks(udev, &flags); + + if (flags & US_FL_IGNORE_UAS) { + dev_warn(&udev->dev, + "UAS is ignored for this device, using usb-storage instead\n"); + return 0; + } + + if (udev->bus->sg_tablesize == 0) { + 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 other USB controller if you wish to use UAS.\n"); + return 0; + } + + if (udev->speed >= USB_SPEED_SUPER && !hcd->can_do_streams) { + dev_warn(&udev->dev, + "USB controller %s does not support streams, which are required by the UAS driver.\n", + hcd_to_bus(hcd)->bus_name); + dev_warn(&udev->dev, + "Please try an other USB controller if you wish to use UAS.\n"); + return 0; + } + + if (flags_ret) + *flags_ret = flags; + + return 1; +} diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c new file mode 100644 index 000000000..265162981 --- /dev/null +++ b/drivers/usb/storage/uas.c @@ -0,0 +1,1304 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Attached SCSI + * Note that this is not the same as the USB Mass Storage driver + * + * Copyright Hans de Goede for Red Hat, Inc. 2013 - 2016 + * Copyright Matthew Wilcox for Intel Corp, 2010 + * Copyright Sarah Sharp for Intel Corp, 2010 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uas-detect.h" +#include "scsiglue.h" + +#define MAX_CMNDS 256 + +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; + u64 flags; + int qdepth, resetting; + unsigned cmd_pipe, status_pipe, data_in_pipe, data_out_pipe; + unsigned use_streams:1; + unsigned shutdown:1; + struct scsi_cmnd *cmnd[MAX_CMNDS]; + spinlock_t lock; + struct work_struct work; + struct work_struct scan_work; /* for async scanning */ +}; + +enum { + SUBMIT_STATUS_URB = BIT(1), + ALLOC_DATA_IN_URB = BIT(2), + SUBMIT_DATA_IN_URB = BIT(3), + ALLOC_DATA_OUT_URB = BIT(4), + SUBMIT_DATA_OUT_URB = BIT(5), + ALLOC_CMD_URB = BIT(6), + SUBMIT_CMD_URB = BIT(7), + COMMAND_INFLIGHT = BIT(8), + DATA_IN_URB_INFLIGHT = BIT(9), + DATA_OUT_URB_INFLIGHT = BIT(10), + COMMAND_ABORTED = BIT(11), + IS_IN_WORK_LIST = BIT(12), +}; + +/* Overrides scsi_pointer */ +struct uas_cmd_info { + unsigned int state; + unsigned int uas_tag; + struct urb *cmd_urb; + struct urb *data_in_urb; + struct urb *data_out_urb; +}; + +/* I hate forward declarations, but I actually have a loop */ +static int uas_submit_urbs(struct scsi_cmnd *cmnd, + struct uas_dev_info *devinfo); +static void uas_do_work(struct work_struct *work); +static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller); +static void uas_free_streams(struct uas_dev_info *devinfo); +static void uas_log_cmd_state(struct scsi_cmnd *cmnd, const char *prefix, + int status); + +/* + * This driver needs its own workqueue, as we need to control memory allocation. + * + * In the course of error handling and power management uas_wait_for_pending_cmnds() + * needs to flush pending work items. In these contexts we cannot allocate memory + * by doing block IO as we would deadlock. For the same reason we cannot wait + * for anything allocating memory not heeding these constraints. + * + * So we have to control all work items that can be on the workqueue we flush. + * Hence we cannot share a queue and need our own. + */ +static struct workqueue_struct *workqueue; + +static void uas_do_work(struct work_struct *work) +{ + struct uas_dev_info *devinfo = + container_of(work, struct uas_dev_info, work); + struct uas_cmd_info *cmdinfo; + struct scsi_cmnd *cmnd; + unsigned long flags; + int i, err; + + spin_lock_irqsave(&devinfo->lock, flags); + + if (devinfo->resetting) + goto out; + + for (i = 0; i < devinfo->qdepth; i++) { + if (!devinfo->cmnd[i]) + continue; + + cmnd = devinfo->cmnd[i]; + cmdinfo = scsi_cmd_priv(cmnd); + + if (!(cmdinfo->state & IS_IN_WORK_LIST)) + continue; + + err = uas_submit_urbs(cmnd, cmnd->device->hostdata); + if (!err) + cmdinfo->state &= ~IS_IN_WORK_LIST; + else + queue_work(workqueue, &devinfo->work); + } +out: + spin_unlock_irqrestore(&devinfo->lock, flags); +} + +static void uas_scan_work(struct work_struct *work) +{ + struct uas_dev_info *devinfo = + container_of(work, struct uas_dev_info, scan_work); + struct Scsi_Host *shost = usb_get_intfdata(devinfo->intf); + + dev_dbg(&devinfo->intf->dev, "starting scan\n"); + scsi_scan_host(shost); + dev_dbg(&devinfo->intf->dev, "scan complete\n"); +} + +static void uas_add_work(struct scsi_cmnd *cmnd) +{ + struct uas_cmd_info *cmdinfo = scsi_cmd_priv(cmnd); + struct uas_dev_info *devinfo = cmnd->device->hostdata; + + lockdep_assert_held(&devinfo->lock); + cmdinfo->state |= IS_IN_WORK_LIST; + queue_work(workqueue, &devinfo->work); +} + +static void uas_zap_pending(struct uas_dev_info *devinfo, int result) +{ + struct uas_cmd_info *cmdinfo; + struct scsi_cmnd *cmnd; + unsigned long flags; + int i, err; + + spin_lock_irqsave(&devinfo->lock, flags); + for (i = 0; i < devinfo->qdepth; i++) { + if (!devinfo->cmnd[i]) + continue; + + cmnd = devinfo->cmnd[i]; + cmdinfo = scsi_cmd_priv(cmnd); + uas_log_cmd_state(cmnd, __func__, 0); + /* Sense urbs were killed, clear COMMAND_INFLIGHT manually */ + cmdinfo->state &= ~COMMAND_INFLIGHT; + cmnd->result = result << 16; + err = uas_try_complete(cmnd, __func__); + WARN_ON(err != 0); + } + 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_log_cmd_state(struct scsi_cmnd *cmnd, const char *prefix, + int status) +{ + struct uas_cmd_info *ci = scsi_cmd_priv(cmnd); + + if (status == -ENODEV) /* too late */ + return; + + scmd_printk(KERN_INFO, cmnd, + "%s %d uas-tag %d inflight:%s%s%s%s%s%s%s%s%s%s%s%s ", + prefix, status, ci->uas_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_ABORTED) ? " abort" : "", + (ci->state & IS_IN_WORK_LIST) ? " work" : ""); + scsi_print_command(cmnd); +} + +static void uas_free_unsubmitted_urbs(struct scsi_cmnd *cmnd) +{ + struct uas_cmd_info *cmdinfo; + + if (!cmnd) + return; + + cmdinfo = scsi_cmd_priv(cmnd); + + if (cmdinfo->state & SUBMIT_CMD_URB) + usb_free_urb(cmdinfo->cmd_urb); + + /* data urbs may have never gotten their submit flag set */ + if (!(cmdinfo->state & DATA_IN_URB_INFLIGHT)) + usb_free_urb(cmdinfo->data_in_urb); + if (!(cmdinfo->state & DATA_OUT_URB_INFLIGHT)) + usb_free_urb(cmdinfo->data_out_urb); +} + +static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller) +{ + struct uas_cmd_info *cmdinfo = scsi_cmd_priv(cmnd); + struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; + + lockdep_assert_held(&devinfo->lock); + if (cmdinfo->state & (COMMAND_INFLIGHT | + DATA_IN_URB_INFLIGHT | + DATA_OUT_URB_INFLIGHT | + COMMAND_ABORTED)) + return -EBUSY; + devinfo->cmnd[cmdinfo->uas_tag - 1] = NULL; + uas_free_unsubmitted_urbs(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 = scsi_cmd_priv(cmnd); + int err; + + cmdinfo->state |= direction | SUBMIT_STATUS_URB; + err = uas_submit_urbs(cmnd, cmnd->device->hostdata); + if (err) { + uas_add_work(cmnd); + } +} + +static bool uas_evaluate_response_iu(struct response_iu *riu, struct scsi_cmnd *cmnd) +{ + u8 response_code = riu->response_code; + + switch (response_code) { + case RC_INCORRECT_LUN: + set_host_byte(cmnd, DID_BAD_TARGET); + break; + case RC_TMF_SUCCEEDED: + set_host_byte(cmnd, DID_OK); + break; + case RC_TMF_NOT_SUPPORTED: + set_host_byte(cmnd, DID_BAD_TARGET); + break; + default: + uas_log_cmd_state(cmnd, "response iu", response_code); + set_host_byte(cmnd, DID_ERROR); + break; + } + + return response_code == RC_TMF_SUCCEEDED; +} + +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 = (struct uas_dev_info *)shost->hostdata; + struct urb *data_in_urb = NULL; + struct urb *data_out_urb = NULL; + struct scsi_cmnd *cmnd; + struct uas_cmd_info *cmdinfo; + unsigned long flags; + unsigned int idx; + int status = urb->status; + bool success; + + if (status) { + if (status != -ENOENT && status != -ECONNRESET && status != -ESHUTDOWN) + dev_err(&urb->dev->dev, "stat urb: status %d\n", status); + goto bail; + } + + idx = be16_to_cpup(&iu->tag) - 1; + + spin_lock_irqsave(&devinfo->lock, flags); + + if (devinfo->resetting) + goto out; + if (idx >= MAX_CMNDS || !devinfo->cmnd[idx]) { + dev_err(&urb->dev->dev, + "stat urb: no pending cmd for uas-tag %d\n", idx + 1); + goto out; + } + + cmnd = devinfo->cmnd[idx]; + cmdinfo = scsi_cmd_priv(cmnd); + + if (!(cmdinfo->state & COMMAND_INFLIGHT)) { + uas_log_cmd_state(cmnd, "unexpected status cmplt", 0); + goto out; + } + + switch (iu->iu_id) { + case IU_ID_STATUS: + uas_sense(urb, cmnd); + if (cmnd->result != 0) { + /* cancel data transfers on error */ + data_in_urb = usb_get_urb(cmdinfo->data_in_urb); + data_out_urb = usb_get_urb(cmdinfo->data_out_urb); + } + cmdinfo->state &= ~COMMAND_INFLIGHT; + uas_try_complete(cmnd, __func__); + break; + case IU_ID_READ_READY: + if (!cmdinfo->data_in_urb || + (cmdinfo->state & DATA_IN_URB_INFLIGHT)) { + uas_log_cmd_state(cmnd, "unexpected read rdy", 0); + break; + } + uas_xfer_data(urb, cmnd, SUBMIT_DATA_IN_URB); + break; + case IU_ID_WRITE_READY: + if (!cmdinfo->data_out_urb || + (cmdinfo->state & DATA_OUT_URB_INFLIGHT)) { + uas_log_cmd_state(cmnd, "unexpected write rdy", 0); + break; + } + uas_xfer_data(urb, cmnd, SUBMIT_DATA_OUT_URB); + break; + case IU_ID_RESPONSE: + cmdinfo->state &= ~COMMAND_INFLIGHT; + success = uas_evaluate_response_iu((struct response_iu *)iu, cmnd); + if (!success) { + /* Error, cancel data transfers */ + data_in_urb = usb_get_urb(cmdinfo->data_in_urb); + data_out_urb = usb_get_urb(cmdinfo->data_out_urb); + } + uas_try_complete(cmnd, __func__); + break; + default: + uas_log_cmd_state(cmnd, "bogus IU", iu->iu_id); + } + spin_unlock_irqrestore(&devinfo->lock, flags); + usb_free_urb(urb); + + /* Unlinking of data urbs must be done without holding the lock */ + if (data_in_urb) { + usb_unlink_urb(data_in_urb); + usb_put_urb(data_in_urb); + } + if (data_out_urb) { + usb_unlink_urb(data_out_urb); + usb_put_urb(data_out_urb); + } + return; + +out: + spin_unlock_irqrestore(&devinfo->lock, flags); +bail: + usb_free_urb(urb); +} + +static void uas_data_cmplt(struct urb *urb) +{ + struct scsi_cmnd *cmnd = urb->context; + struct uas_cmd_info *cmdinfo = scsi_cmd_priv(cmnd); + struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; + struct scsi_data_buffer *sdb = &cmnd->sdb; + unsigned long flags; + int status = urb->status; + + spin_lock_irqsave(&devinfo->lock, flags); + + if (cmdinfo->data_in_urb == urb) { + cmdinfo->state &= ~DATA_IN_URB_INFLIGHT; + cmdinfo->data_in_urb = NULL; + } else if (cmdinfo->data_out_urb == urb) { + cmdinfo->state &= ~DATA_OUT_URB_INFLIGHT; + cmdinfo->data_out_urb = NULL; + } + + if (devinfo->resetting) + goto out; + + /* Data urbs should not complete before the cmd urb is submitted */ + if (cmdinfo->state & SUBMIT_CMD_URB) { + uas_log_cmd_state(cmnd, "unexpected data cmplt", 0); + goto out; + } + + if (status) { + if (status != -ENOENT && status != -ECONNRESET && status != -ESHUTDOWN) + uas_log_cmd_state(cmnd, "data cmplt err", status); + /* error: no data transfered */ + scsi_set_resid(cmnd, sdb->length); + set_host_byte(cmnd, DID_ERROR); + } else { + scsi_set_resid(cmnd, sdb->length - urb->actual_length); + } + uas_try_complete(cmnd, __func__); +out: + spin_unlock_irqrestore(&devinfo->lock, flags); + usb_free_urb(urb); +} + +static void uas_cmd_cmplt(struct urb *urb) +{ + if (urb->status) + dev_err(&urb->dev->dev, "cmd cmplt err %d\n", urb->status); + + usb_free_urb(urb); +} + +static struct urb *uas_alloc_data_urb(struct uas_dev_info *devinfo, gfp_t gfp, + struct scsi_cmnd *cmnd, + enum dma_data_direction dir) +{ + struct usb_device *udev = devinfo->udev; + struct uas_cmd_info *cmdinfo = scsi_cmd_priv(cmnd); + struct urb *urb = usb_alloc_urb(0, gfp); + struct scsi_data_buffer *sdb = &cmnd->sdb; + unsigned int pipe = (dir == DMA_FROM_DEVICE) + ? devinfo->data_in_pipe : devinfo->data_out_pipe; + + 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 = cmdinfo->uas_tag; + 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_cmnd *cmnd) +{ + struct usb_device *udev = devinfo->udev; + struct uas_cmd_info *cmdinfo = scsi_cmd_priv(cmnd); + struct urb *urb = usb_alloc_urb(0, gfp); + struct sense_iu *iu; + + if (!urb) + goto out; + + iu = kzalloc_obj(*iu, gfp); + if (!iu) + goto free; + + usb_fill_bulk_urb(urb, udev, devinfo->status_pipe, iu, sizeof(*iu), + uas_stat_cmplt, cmnd->device->host); + if (devinfo->use_streams) + urb->stream_id = cmdinfo->uas_tag; + 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) +{ + struct usb_device *udev = devinfo->udev; + struct scsi_device *sdev = cmnd->device; + struct uas_cmd_info *cmdinfo = scsi_cmd_priv(cmnd); + 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; + iu->tag = cpu_to_be16(cmdinfo->uas_tag); + 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, + uas_cmd_cmplt, NULL); + urb->transfer_flags |= URB_FREE_BUFFER; + out: + return urb; + free: + usb_free_urb(urb); + return NULL; +} + +/* + * 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_cmnd *cmnd, gfp_t gfp) +{ + struct uas_dev_info *devinfo = cmnd->device->hostdata; + struct urb *urb; + int err; + + urb = uas_alloc_sense_urb(devinfo, gfp, cmnd); + if (!urb) + return -ENOMEM; + usb_anchor_urb(urb, &devinfo->sense_urbs); + err = usb_submit_urb(urb, gfp); + if (err) { + usb_unanchor_urb(urb); + uas_log_cmd_state(cmnd, "sense submit err", err); + usb_free_urb(urb); + } + return err; +} + +static int uas_submit_urbs(struct scsi_cmnd *cmnd, + struct uas_dev_info *devinfo) +{ + struct uas_cmd_info *cmdinfo = scsi_cmd_priv(cmnd); + int err; + + lockdep_assert_held(&devinfo->lock); + if (cmdinfo->state & SUBMIT_STATUS_URB) { + err = uas_submit_sense_urb(cmnd, GFP_ATOMIC); + 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_ATOMIC, + cmnd, DMA_FROM_DEVICE); + if (!cmdinfo->data_in_urb) + return -ENOMEM; + cmdinfo->state &= ~ALLOC_DATA_IN_URB; + } + + if (cmdinfo->state & SUBMIT_DATA_IN_URB) { + usb_anchor_urb(cmdinfo->data_in_urb, &devinfo->data_urbs); + err = usb_submit_urb(cmdinfo->data_in_urb, GFP_ATOMIC); + if (err) { + usb_unanchor_urb(cmdinfo->data_in_urb); + uas_log_cmd_state(cmnd, "data in submit err", err); + return err; + } + cmdinfo->state &= ~SUBMIT_DATA_IN_URB; + cmdinfo->state |= DATA_IN_URB_INFLIGHT; + } + + if (cmdinfo->state & ALLOC_DATA_OUT_URB) { + cmdinfo->data_out_urb = uas_alloc_data_urb(devinfo, GFP_ATOMIC, + cmnd, DMA_TO_DEVICE); + if (!cmdinfo->data_out_urb) + return -ENOMEM; + cmdinfo->state &= ~ALLOC_DATA_OUT_URB; + } + + if (cmdinfo->state & SUBMIT_DATA_OUT_URB) { + usb_anchor_urb(cmdinfo->data_out_urb, &devinfo->data_urbs); + err = usb_submit_urb(cmdinfo->data_out_urb, GFP_ATOMIC); + if (err) { + usb_unanchor_urb(cmdinfo->data_out_urb); + uas_log_cmd_state(cmnd, "data out submit err", err); + return err; + } + cmdinfo->state &= ~SUBMIT_DATA_OUT_URB; + cmdinfo->state |= DATA_OUT_URB_INFLIGHT; + } + + if (cmdinfo->state & ALLOC_CMD_URB) { + cmdinfo->cmd_urb = uas_alloc_cmd_urb(devinfo, GFP_ATOMIC, cmnd); + if (!cmdinfo->cmd_urb) + return -ENOMEM; + cmdinfo->state &= ~ALLOC_CMD_URB; + } + + if (cmdinfo->state & SUBMIT_CMD_URB) { + usb_anchor_urb(cmdinfo->cmd_urb, &devinfo->cmd_urbs); + err = usb_submit_urb(cmdinfo->cmd_urb, GFP_ATOMIC); + if (err) { + usb_unanchor_urb(cmdinfo->cmd_urb); + uas_log_cmd_state(cmnd, "cmd submit err", err); + return err; + } + cmdinfo->cmd_urb = NULL; + cmdinfo->state &= ~SUBMIT_CMD_URB; + cmdinfo->state |= COMMAND_INFLIGHT; + } + + return 0; +} + +static enum scsi_qc_status uas_queuecommand_lck(struct scsi_cmnd *cmnd) +{ + struct scsi_device *sdev = cmnd->device; + struct uas_dev_info *devinfo = sdev->hostdata; + struct uas_cmd_info *cmdinfo = scsi_cmd_priv(cmnd); + unsigned long flags; + int idx, err; + + /* Re-check scsi_block_requests now that we've the host-lock */ + if (cmnd->device->host->host_self_blocked) + return SCSI_MLQUEUE_DEVICE_BUSY; + + if ((devinfo->flags & US_FL_NO_ATA_1X) && + (cmnd->cmnd[0] == ATA_12 || cmnd->cmnd[0] == ATA_16)) { + memcpy(cmnd->sense_buffer, usb_stor_sense_invalidCDB, + sizeof(usb_stor_sense_invalidCDB)); + cmnd->result = SAM_STAT_CHECK_CONDITION; + scsi_done(cmnd); + return 0; + } + + spin_lock_irqsave(&devinfo->lock, flags); + + if (devinfo->resetting) { + set_host_byte(cmnd, DID_ERROR); + scsi_done(cmnd); + goto zombie; + } + + /* Find a free uas-tag */ + for (idx = 0; idx < devinfo->qdepth; idx++) { + if (!devinfo->cmnd[idx]) + break; + } + if (idx == devinfo->qdepth) { + spin_unlock_irqrestore(&devinfo->lock, flags); + return SCSI_MLQUEUE_DEVICE_BUSY; + } + + memset(cmdinfo, 0, sizeof(*cmdinfo)); + cmdinfo->uas_tag = idx + 1; /* uas-tag == usb-stream-id, so 1 based */ + 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; + fallthrough; + case DMA_TO_DEVICE: + cmdinfo->state |= ALLOC_DATA_OUT_URB | SUBMIT_DATA_OUT_URB; + break; + case DMA_NONE: + break; + } + + if (!devinfo->use_streams) + cmdinfo->state &= ~(SUBMIT_DATA_IN_URB | SUBMIT_DATA_OUT_URB); + + err = uas_submit_urbs(cmnd, devinfo); + /* + * in case of fatal errors the SCSI layer is peculiar + * a command that has finished is a success for the purpose + * of queueing, no matter how fatal the error + */ + if (err == -ENODEV) { + if (cmdinfo->state & (COMMAND_INFLIGHT | DATA_IN_URB_INFLIGHT | + DATA_OUT_URB_INFLIGHT)) + goto out; + + set_host_byte(cmnd, DID_NO_CONNECT); + scsi_done(cmnd); + goto zombie; + } + 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; + } + uas_add_work(cmnd); + } + +out: + devinfo->cmnd[idx] = cmnd; +zombie: + spin_unlock_irqrestore(&devinfo->lock, flags); + return 0; +} + +static DEF_SCSI_QCMD(uas_queuecommand) + +/* + * For now we do not support actually sending an abort to the device, so + * this eh always fails. Still we must define it to make sure that we've + * dropped all references to the cmnd in question once this function exits. + */ +static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) +{ + struct uas_cmd_info *cmdinfo = scsi_cmd_priv(cmnd); + struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; + struct urb *data_in_urb = NULL; + struct urb *data_out_urb = NULL; + unsigned long flags; + + spin_lock_irqsave(&devinfo->lock, flags); + + uas_log_cmd_state(cmnd, __func__, 0); + + /* Ensure that try_complete does not call scsi_done */ + cmdinfo->state |= COMMAND_ABORTED; + + /* Drop all refs to this cmnd, kill data urbs to break their ref */ + devinfo->cmnd[cmdinfo->uas_tag - 1] = NULL; + if (cmdinfo->state & DATA_IN_URB_INFLIGHT) + data_in_urb = usb_get_urb(cmdinfo->data_in_urb); + if (cmdinfo->state & DATA_OUT_URB_INFLIGHT) + data_out_urb = usb_get_urb(cmdinfo->data_out_urb); + + uas_free_unsubmitted_urbs(cmnd); + + spin_unlock_irqrestore(&devinfo->lock, flags); + + if (data_in_urb) { + usb_kill_urb(data_in_urb); + usb_put_urb(data_in_urb); + } + if (data_out_urb) { + usb_kill_urb(data_out_urb); + usb_put_urb(data_out_urb); + } + + return FAILED; +} + +static int uas_eh_host_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; + unsigned long flags; + int err; + + err = usb_lock_device_for_reset(udev, devinfo->intf); + if (err) { + shost_printk(KERN_ERR, sdev->host, + "%s FAILED to get lock err %d\n", __func__, err); + return FAILED; + } + + shost_printk(KERN_INFO, sdev->host, "%s start\n", __func__); + + spin_lock_irqsave(&devinfo->lock, flags); + devinfo->resetting = 1; + spin_unlock_irqrestore(&devinfo->lock, flags); + + usb_kill_anchored_urbs(&devinfo->cmd_urbs); + usb_kill_anchored_urbs(&devinfo->sense_urbs); + usb_kill_anchored_urbs(&devinfo->data_urbs); + uas_zap_pending(devinfo, DID_RESET); + + err = usb_reset_device(udev); + + spin_lock_irqsave(&devinfo->lock, flags); + devinfo->resetting = 0; + spin_unlock_irqrestore(&devinfo->lock, flags); + + usb_unlock_device(udev); + + if (err) { + shost_printk(KERN_INFO, sdev->host, "%s FAILED err %d\n", + __func__, err); + return FAILED; + } + + shost_printk(KERN_INFO, sdev->host, "%s success\n", __func__); + return SUCCESS; +} + +static int uas_target_alloc(struct scsi_target *starget) +{ + struct uas_dev_info *devinfo = (struct uas_dev_info *) + dev_to_shost(starget->dev.parent)->hostdata; + + if (devinfo->flags & US_FL_NO_REPORT_LUNS) + starget->no_report_luns = 1; + + return 0; +} + +static int uas_sdev_init(struct scsi_device *sdev) +{ + struct uas_dev_info *devinfo = + (struct uas_dev_info *)sdev->host->hostdata; + + /* + * Some USB storage devices reset if the IO advice hints grouping mode + * page is queried. Hence skip that mode page. + */ + sdev->sdev_bflags |= BLIST_SKIP_IO_HINTS; + + sdev->hostdata = devinfo; + return 0; +} + +static int uas_sdev_configure(struct scsi_device *sdev, + struct queue_limits *lim) +{ + struct uas_dev_info *devinfo = sdev->hostdata; + + if (devinfo->flags & US_FL_MAX_SECTORS_64) + lim->max_hw_sectors = 64; + else if (devinfo->flags & US_FL_MAX_SECTORS_240) + lim->max_hw_sectors = 240; + + if (devinfo->flags & US_FL_NO_REPORT_OPCODES) + sdev->no_report_opcodes = 1; + + /* A few buggy USB-ATA bridges don't understand FUA */ + if (devinfo->flags & US_FL_BROKEN_FUA) + sdev->broken_fua = 1; + + /* UAS also needs to support FL_ALWAYS_SYNC */ + if (devinfo->flags & US_FL_ALWAYS_SYNC) { + sdev->skip_ms_page_3f = 1; + sdev->skip_ms_page_8 = 1; + sdev->wce_default_on = 1; + } + + /* Some disks cannot handle READ_CAPACITY_16 */ + if (devinfo->flags & US_FL_NO_READ_CAPACITY_16) + sdev->no_read_capacity_16 = 1; + + /* Some disks cannot handle WRITE_SAME */ + if (devinfo->flags & US_FL_NO_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 (devinfo->flags & US_FL_FIX_CAPACITY) + sdev->fix_capacity = 1; + + /* + * in some cases we have to guess + */ + if (devinfo->flags & US_FL_CAPACITY_HEURISTICS) + sdev->guess_capacity = 1; + + /* + * Some devices report generic values until the media has been + * accessed. Force a READ(10) prior to querying device + * characteristics. + */ + sdev->read_before_ms = 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 (devinfo->flags & US_FL_NO_WP_DETECT) + sdev->skip_ms_page_3f = 1; + + scsi_change_queue_depth(sdev, devinfo->qdepth - 2); + return 0; +} + +static const struct scsi_host_template uas_host_template = { + .module = THIS_MODULE, + .name = "uas", + .queuecommand = uas_queuecommand, + .target_alloc = uas_target_alloc, + .sdev_init = uas_sdev_init, + .sdev_configure = uas_sdev_configure, + .eh_abort_handler = uas_eh_abort_handler, + .eh_host_reset_handler = uas_eh_host_reset_handler, + .this_id = -1, + .skip_settle_delay = 1, + /* + * The protocol has no requirements on alignment in the strict sense. + * Controllers may or may not have alignment restrictions. + * As this is not exported, we use an extremely conservative guess. + */ + .dma_alignment = 511, + .dma_boundary = PAGE_SIZE - 1, + .cmd_size = sizeof(struct uas_cmd_info), +}; + +#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 uas_usb_ids[] = { +# include "unusual_uas.h" + { 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) }, + { } +}; +MODULE_DEVICE_TABLE(usb, uas_usb_ids); + +#undef UNUSUAL_DEV + +static int uas_switch_interface(struct usb_device *udev, + struct usb_interface *intf) +{ + struct usb_host_interface *alt; + + alt = uas_find_uas_alt_setting(intf); + if (!alt) + return -ENODEV; + + return usb_set_interface(udev, alt->desc.bInterfaceNumber, + alt->desc.bAlternateSetting); +} + +static int uas_configure_endpoints(struct uas_dev_info *devinfo) +{ + struct usb_host_endpoint *eps[4] = { }; + struct usb_device *udev = devinfo->udev; + int r; + + r = uas_find_endpoints(devinfo->intf->cur_altsetting, eps); + if (r) + return r; + + devinfo->cmd_pipe = usb_sndbulkpipe(udev, + usb_endpoint_num(&eps[0]->desc)); + devinfo->status_pipe = usb_rcvbulkpipe(udev, + usb_endpoint_num(&eps[1]->desc)); + devinfo->data_in_pipe = usb_rcvbulkpipe(udev, + usb_endpoint_num(&eps[2]->desc)); + devinfo->data_out_pipe = usb_sndbulkpipe(udev, + usb_endpoint_num(&eps[3]->desc)); + + if (udev->speed < USB_SPEED_SUPER) { + devinfo->qdepth = 32; + devinfo->use_streams = 0; + } else { + devinfo->qdepth = usb_alloc_streams(devinfo->intf, eps + 1, + 3, MAX_CMNDS, GFP_NOIO); + if (devinfo->qdepth < 0) + return devinfo->qdepth; + devinfo->use_streams = 1; + } + + return 0; +} + +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_NOIO); +} + +static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + int result = -ENOMEM; + struct Scsi_Host *shost = NULL; + struct uas_dev_info *devinfo; + struct usb_device *udev = interface_to_usbdev(intf); + u64 dev_flags; + + if (!uas_use_uas_driver(intf, id, &dev_flags)) + return -ENODEV; + + if (uas_switch_interface(udev, intf)) + return -ENODEV; + + shost = scsi_host_alloc(&uas_host_template, + sizeof(struct uas_dev_info)); + if (!shost) + goto set_alt0; + + 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 = (struct uas_dev_info *)shost->hostdata; + devinfo->intf = intf; + devinfo->udev = udev; + devinfo->resetting = 0; + devinfo->shutdown = 0; + devinfo->flags = dev_flags; + init_usb_anchor(&devinfo->cmd_urbs); + init_usb_anchor(&devinfo->sense_urbs); + init_usb_anchor(&devinfo->data_urbs); + spin_lock_init(&devinfo->lock); + INIT_WORK(&devinfo->work, uas_do_work); + INIT_WORK(&devinfo->scan_work, uas_scan_work); + + result = uas_configure_endpoints(devinfo); + if (result) + goto set_alt0; + + /* + * 1 tag is reserved for untagged commands + + * 1 tag to avoid off by one errors in some bridge firmwares + */ + shost->can_queue = devinfo->qdepth - 2; + + usb_set_intfdata(intf, shost); + result = scsi_add_host(shost, &intf->dev); + if (result) + goto free_streams; + + /* Submit the delayed_work for SCSI-device scanning */ + schedule_work(&devinfo->scan_work); + + return result; + +free_streams: + uas_free_streams(devinfo); + usb_set_intfdata(intf, NULL); +set_alt0: + usb_set_interface(udev, intf->altsetting[0].desc.bInterfaceNumber, 0); + if (shost) + scsi_host_put(shost); + return result; +} + +static int uas_cmnd_list_empty(struct uas_dev_info *devinfo) +{ + unsigned long flags; + int i, r = 1; + + spin_lock_irqsave(&devinfo->lock, flags); + + for (i = 0; i < devinfo->qdepth; i++) { + if (devinfo->cmnd[i]) { + r = 0; /* Not empty */ + break; + } + } + + spin_unlock_irqrestore(&devinfo->lock, flags); + + return r; +} + +/* + * Wait for any pending cmnds to complete, on usb-2 sense_urbs may temporarily + * get empty while there still is more work to do due to sense-urbs completing + * with a READ/WRITE_READY iu code, so keep waiting until the list gets empty. + */ +static int uas_wait_for_pending_cmnds(struct uas_dev_info *devinfo) +{ + unsigned long start_time; + int r; + + start_time = jiffies; + do { + flush_work(&devinfo->work); + + r = usb_wait_anchor_empty_timeout(&devinfo->sense_urbs, 5000); + if (r == 0) + return -ETIME; + + r = usb_wait_anchor_empty_timeout(&devinfo->data_urbs, 500); + if (r == 0) + return -ETIME; + + if (time_after(jiffies, start_time + 5 * HZ)) + return -ETIME; + } while (!uas_cmnd_list_empty(devinfo)); + + return 0; +} + +static int uas_pre_reset(struct usb_interface *intf) +{ + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; + unsigned long flags; + + if (devinfo->shutdown) + return 0; + + /* Block new requests */ + spin_lock_irqsave(shost->host_lock, flags); + scsi_block_requests(shost); + spin_unlock_irqrestore(shost->host_lock, flags); + + if (uas_wait_for_pending_cmnds(devinfo) != 0) { + shost_printk(KERN_ERR, shost, "%s: timed out\n", __func__); + scsi_unblock_requests(shost); + return 1; + } + + uas_free_streams(devinfo); + + return 0; +} + +static int uas_post_reset(struct usb_interface *intf) +{ + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; + unsigned long flags; + int err; + + if (devinfo->shutdown) + return 0; + + err = uas_configure_endpoints(devinfo); + if (err && err != -ENODEV) + shost_printk(KERN_ERR, shost, + "%s: alloc streams error %d after reset", + __func__, err); + + /* we must unblock the host in every case lest we deadlock */ + spin_lock_irqsave(shost->host_lock, flags); + scsi_report_bus_reset(shost, 0); + spin_unlock_irqrestore(shost->host_lock, flags); + + scsi_unblock_requests(shost); + + return err ? 1 : 0; +} + +static int uas_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; + + if (uas_wait_for_pending_cmnds(devinfo) != 0) { + shost_printk(KERN_ERR, shost, "%s: timed out\n", __func__); + return -ETIME; + } + + return 0; +} + +static int uas_resume(struct usb_interface *intf) +{ + return 0; +} + +static int uas_reset_resume(struct usb_interface *intf) +{ + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; + unsigned long flags; + int err; + + err = uas_configure_endpoints(devinfo); + if (err) { + shost_printk(KERN_ERR, shost, + "%s: alloc streams error %d after reset", + __func__, err); + return -EIO; + } + + spin_lock_irqsave(shost->host_lock, flags); + scsi_report_bus_reset(shost, 0); + spin_unlock_irqrestore(shost->host_lock, flags); + + return 0; +} + +static void uas_disconnect(struct usb_interface *intf) +{ + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; + unsigned long flags; + + spin_lock_irqsave(&devinfo->lock, flags); + devinfo->resetting = 1; + spin_unlock_irqrestore(&devinfo->lock, flags); + + cancel_work_sync(&devinfo->work); + usb_kill_anchored_urbs(&devinfo->cmd_urbs); + usb_kill_anchored_urbs(&devinfo->sense_urbs); + usb_kill_anchored_urbs(&devinfo->data_urbs); + uas_zap_pending(devinfo, DID_NO_CONNECT); + + /* + * Prevent SCSI scanning (if it hasn't started yet) + * or wait for the SCSI-scanning routine to stop. + */ + cancel_work_sync(&devinfo->scan_work); + + scsi_remove_host(shost); + uas_free_streams(devinfo); + scsi_host_put(shost); +} + +/* + * Put the device back in usb-storage mode on shutdown, as some BIOS-es + * hang on reboot when the device is still in uas mode. Note the reset is + * necessary as some devices won't revert to usb-storage mode without it. + */ +static void uas_shutdown(struct usb_interface *intf) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct Scsi_Host *shost = usb_get_intfdata(intf); + struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; + + if (system_state != SYSTEM_RESTART) + return; + + devinfo->shutdown = 1; + uas_free_streams(devinfo); + usb_set_interface(udev, intf->altsetting[0].desc.bInterfaceNumber, 0); + usb_reset_device(udev); +} + +static struct usb_driver uas_driver = { + .name = "uas", + .probe = uas_probe, + .disconnect = uas_disconnect, + .pre_reset = uas_pre_reset, + .post_reset = uas_post_reset, + .suspend = uas_suspend, + .resume = uas_resume, + .reset_resume = uas_reset_resume, + .shutdown = uas_shutdown, + .id_table = uas_usb_ids, +}; + +static int __init uas_init(void) +{ + int rv; + + workqueue = alloc_workqueue("uas", WQ_MEM_RECLAIM | WQ_PERCPU, 0); + if (!workqueue) + return -ENOMEM; + + rv = usb_register(&uas_driver); + if (rv) { + destroy_workqueue(workqueue); + return -ENOMEM; + } + + return 0; +} + +static void __exit uas_exit(void) +{ + usb_deregister(&uas_driver); + destroy_workqueue(workqueue); +} + +module_init(uas_init); +module_exit(uas_exit); + +MODULE_DESCRIPTION("USB Attached SCSI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USB_STORAGE"); +MODULE_AUTHOR( + "Hans de Goede , 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 000000000..13f61ec88 --- /dev/null +++ b/drivers/usb/storage/unusual_alauda.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Unusual Devices File for the Alauda-based card readers + */ + +#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 000000000..5df40759d --- /dev/null +++ b/drivers/usb/storage/unusual_cypress.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Unusual Devices File for devices based on the Cypress USB/ATA bridge + * with support for ATACB + */ + +#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, 0x0150, 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 000000000..5335b5d2b --- /dev/null +++ b/drivers/usb/storage/unusual_datafab.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Unusual Devices File for the Datafab USB Compact Flash reader + */ + +#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 000000000..ac22fa318 --- /dev/null +++ b/drivers/usb/storage/unusual_devs.h @@ -0,0 +1,2521 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + */ + +/* + * 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 /sys/kernel/debug/usb/devices with your device plugged in + * running with this patch. + * Send your submission to the USB development 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 https://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 ), + +/* Added by Lubomir Rintel , a very fine chap */ +UNUSUAL_DEV( 0x0421, 0x06c2, 0x0000, 0x0406, + "Nokia", + "Nokia 208", + 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), + +/* Reported by Egbert Eich */ +UNUSUAL_DEV( 0x0480, 0xd010, 0x0100, 0x9999, + "Toshiba", + "External USB 3.0", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_ALWAYS_SYNC), + +/* 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 Ai Chao */ +UNUSUAL_DEV( 0x04b4, 0xb708, 0x0000, 0xffff, + "Longmai Technologies", + "USB Key", + USB_SC_SCSI, USB_PR_BULK, NULL, + US_FL_NO_ATA_1X), + +/* + * 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), + +/* + * Reported by James Buren + * Virtual ISOs cannot be remounted if ejected while the device is locked + * Disable locking to mimic Windows behavior that bypasses the issue + */ +UNUSUAL_DEV( 0x04c5, 0x2028, 0x0001, 0x0001, + "iODD", + "2531/2541", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, US_FL_NOT_LOCKABLE), + +/* + * 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 + * Firmwares older than 2.6c (the latest one and the only that claims Linux + * support) have also broken tag handling + */ +UNUSUAL_DEV( 0x04ce, 0x0002, 0x0000, 0x026b, + "ScanLogic", + "SL11R-IDE", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY | US_FL_BULK_IGNORE_TAG), +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, 0x000f, 0x0000, 0x9999, + "SCM Microsystems", + "eUSB SCSI Adapter (Bus Powered)", + 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 ), + +/* Reported by Christian Schaller */ +UNUSUAL_DEV( 0x059f, 0x0651, 0x0000, 0x0000, + "LaCie", + "External HDD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_WP_DETECT ), + +/* + * 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 ), + +/* Added by Maël GUERIN */ +UNUSUAL_DEV( 0x0603, 0x8611, 0x0000, 0xffff, + "Novatek", + "NTK96550-based camera", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BULK_IGNORE_TAG ), + +/* + * 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 ), + +UNUSUAL_DEV( 0x06ca, 0x2003, 0x0100, 0x0100, + "Newer Technology", + "uSCSI", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_euscsi_init, + US_FL_SCM_MULT_TARG ), + +/* 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), + +/* Supplied with some Castlewood ORB removable drives */ +UNUSUAL_DEV( 0x084b, 0xa001, 0x0000, 0x9999, + "Castlewood Systems", + "USB to SCSI cable", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_euscsi_init, + US_FL_SCM_MULT_TARG ), + +/* + * 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 Icenowy Zheng + * The SMI SM3350 USB-UFS bridge controller will enter a wrong state + * that do not process read/write command if a long sense is requested, + * so force to use 18-byte sense. + */ +UNUSUAL_DEV( 0x090c, 0x3350, 0x0000, 0xffff, + "SMI", + "SM3350 UFS-to-USB-Mass-Storage bridge", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BAD_SENSE ), + +/* + * 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 ), + +/* + * Patch by Tasos Sahanidis + * This flash drive always shows up with write protect enabled + * during the first mode sense. + */ +UNUSUAL_DEV(0x0951, 0x1697, 0x0100, 0x0100, + "Kingston", + "DT Ultimate G3", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_WP_DETECT), + +/* + * 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 ), + +/* Reported by Kris Lindgren */ +UNUSUAL_DEV( 0x0bc2, 0x3332, 0x0000, 0x9999, + "Seagate", + "External", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_WP_DETECT ), + +/* + * Reported by Zenm Chen + * Ignore driver CD mode, otherwise usb_modeswitch may fail to switch + * the device into Wi-Fi mode. + */ +UNUSUAL_DEV( 0x0bda, 0x1a2b, 0x0000, 0xffff, + "Realtek", + "DISK", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_DEVICE ), + +/* + * Reported by Zenm Chen + * Ignore driver CD mode, otherwise usb_modeswitch may fail to switch + * the device into Wi-Fi mode. + */ +UNUSUAL_DEV( 0x0bda, 0xa192, 0x0000, 0xffff, + "Realtek", + "DISK", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_DEVICE ), + +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 Tobias Jakobi + * The INIC-3619 bridge is used in the StarTech SLSODDU33B + * SATA-USB enclosure for slimline optical drives. + * + * The quirk enables MakeMKV to properly exchange keys with + * an installed BD drive. + */ +UNUSUAL_DEV( 0x13fd, 0x3609, 0x0209, 0x0209, + "Initio Corporation", + "INIC-3619", + 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 Michael Büsch */ +UNUSUAL_DEV( 0x152d, 0x0567, 0x0114, 0x0117, + "JMicron", + "USB to ATA/ATAPI Bridge", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BROKEN_FUA ), + +/* Reported by David Kozub */ +UNUSUAL_DEV(0x152d, 0x0578, 0x0000, 0x9999, + "JMicron", + "JMS567", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BROKEN_FUA), + +/* + * 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 ), + +/* Reported by Dmitry Nezhevenko */ +UNUSUAL_DEV( 0x152d, 0x2566, 0x0114, 0x0114, + "JMicron", + "USB to ATA/ATAPI Bridge", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BROKEN_FUA ), + +/* Reported by Teijo Kinnunen */ +UNUSUAL_DEV( 0x152d, 0x2567, 0x0117, 0x0117, + "JMicron", + "USB to ATA/ATAPI Bridge", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BROKEN_FUA ), + +/* Reported-by George Cherian */ +UNUSUAL_DEV(0x152d, 0x9561, 0x0000, 0x9999, + "JMicron", + "JMS56x", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_REPORT_OPCODES), + +/* + * 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 Matthias Schwarzott + * The Amazon Kindle treats SYNCHRONIZE CACHE as an indication that + * the host may be finished with it, and automatically ejects its + * emulated media unless it receives another command within one second. + */ +UNUSUAL_DEV( 0x1949, 0x0004, 0x0000, 0x9999, + "Amazon", + "Kindle", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SENSE_AFTER_SYNC ), + +/* + * Reported by Oliver Neukum + * This device morphes spontaneously into another device if the access + * pattern of Windows isn't followed. Thus writable media would be dirty + * if the initial instance is used. So the device is limited to its + * virtual CD. + * And yes, the concept that BCD goes up to 9 is not heeded + */ +UNUSUAL_DEV( 0x19d2, 0x1225, 0x0000, 0xffff, + "ZTE,Incorporated", + "ZTE WCDMA Technologies MSM", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_SINGLE_LUN ), + +/* + * 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 ), + +/* + * Reported by Hans de Goede + * These are mini projectors using USB for both power and video data transport + * The usb-storage interface is a virtual windows driver CD, which the gm12u320 + * driver automatically converts into framebuffer & kms dri device nodes. + */ +UNUSUAL_DEV( 0x1de1, 0xc102, 0x0000, 0xffff, + "Grain-media Technology Corp.", + "USB3.0 Device GM12U320", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_DEVICE ), + +/* + * 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 ), + +/* Reported by Witold Lipieta */ +UNUSUAL_DEV( 0x1fc9, 0x0117, 0x0100, 0x0100, + "NXP Semiconductors", + "PN7462AU", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + +/* Supplied with some Castlewood ORB removable drives */ +UNUSUAL_DEV( 0x2027, 0xa001, 0x0000, 0x9999, + "Double-H Technology", + "USB to SCSI Intelligent Cable", + USB_SC_DEVICE, USB_PR_DEVICE, usb_stor_euscsi_init, + US_FL_SCM_MULT_TARG ), + +/* + * Reported by DocMAX , + * Thomas Weißschuh + * and Daniel Brát + */ +UNUSUAL_DEV( 0x2109, 0x0715, 0x0000, 0x9999, + "VIA Labs, Inc.", + "VL817 SATA Bridge", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_UAS), + +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: Tim Anderson */ +UNUSUAL_DEV( 0x2ca3, 0x0031, 0x0000, 0x9999, + "DJI", + "CineSSD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_ATA_1X), + +/* + * 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 Cyril Roelandt */ +UNUSUAL_DEV( 0x357d, 0x7788, 0x0114, 0x0114, + "JMicron", + "USB to ATA/ATAPI Bridge", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BROKEN_FUA | US_FL_IGNORE_UAS ), + +/* 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 ), + +/* "G-DRIVE" external HDD hangs on write without these. + * Patch submitted by Alexander Kappner + */ +UNUSUAL_DEV(0x4971, 0x8024, 0x0000, 0x9999, + "SimpleTech", + "External HDD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_ALWAYS_SYNC), + +/* + * 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 Icenowy Zheng + * This is an interface for vendor-specific cryptic commands instead + * of real USB storage device. + */ +UNUSUAL_DEV( 0xe5b7, 0x0811, 0x0100, 0x0100, + "ZhuHai JieLi Technology", + "JieLi BR21", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_DEVICE), + +/* 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 ), + +/* Unusual uas devices */ +#if IS_ENABLED(CONFIG_USB_UAS) +#include "unusual_uas.h" +#endif + +/* 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 000000000..a3b32abc2 --- /dev/null +++ b/drivers/usb/storage/unusual_ene_ub6250.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#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 000000000..9ca686364 --- /dev/null +++ b/drivers/usb/storage/unusual_freecom.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Unusual Devices File for the Freecom USB/IDE adaptor + */ + +#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 000000000..f248190bd --- /dev/null +++ b/drivers/usb/storage/unusual_isd200.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Unusual Devices File for In-System Design, Inc. ISD200 ASIC + */ + +#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 000000000..44878f849 --- /dev/null +++ b/drivers/usb/storage/unusual_jumpshot.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Unusual Devices File for the Lexar "Jumpshot" Compact Flash reader + */ + +#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 000000000..9fbed4cbc --- /dev/null +++ b/drivers/usb/storage/unusual_karma.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Unusual Devices File for the Rio Karma + */ + +#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 000000000..cdfee8f6c --- /dev/null +++ b/drivers/usb/storage/unusual_onetouch.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Unusual Devices File for the Maxtor OneTouch USB hard drive's button + */ + +#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 000000000..945dcb19d --- /dev/null +++ b/drivers/usb/storage/unusual_realtek.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Driver for Realtek RTS51xx USB card reader + * + * Copyright(c) 2009 Realtek Semiconductor Corp. All rights reserved. + * + * 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, 0x0153, 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), + +UNUSUAL_DEV(0x0bda, 0x0177, 0x0000, 0x9999, + "Realtek", + "USB Card Reader", + USB_SC_DEVICE, USB_PR_DEVICE, init_realtek_cr, 0), + +UNUSUAL_DEV(0x0bda, 0x0184, 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 000000000..bfb650974 --- /dev/null +++ b/drivers/usb/storage/unusual_sddr09.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Unusual Devices File for SanDisk SDDR-09 SmartMedia reader + */ + +#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 000000000..6d6f76eb0 --- /dev/null +++ b/drivers/usb/storage/unusual_sddr55.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Unusual Devices File for SanDisk SDDR-55 SmartMedia reader + */ + +#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_uas.h b/drivers/usb/storage/unusual_uas.h new file mode 100644 index 000000000..d6f86d5db --- /dev/null +++ b/drivers/usb/storage/unusual_uas.h @@ -0,0 +1,208 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Driver for USB Attached SCSI devices - Unusual Devices File + * + * (c) 2013 Hans de Goede + * + * Based on the same file for the usb-storage driver, which is: + * (c) 2000-2002 Matthew Dharm (mdharm-usb@one-eyed-alien.net) + * (c) 2000 Adam J. Richter (adam@yggdrasil.com), Yggdrasil Computing, Inc. + */ + +/* + * IMPORTANT NOTE: This file must be included in another file which defines + * a UNUSUAL_DEV macro 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), + * - lsusb -v output for the device + * Send your submission to Hans de Goede + * and don't forget to CC: the USB development list + */ + +/* Reported-by: Till Dörges */ +UNUSUAL_DEV(0x054c, 0x087d, 0x0000, 0x9999, + "Sony", + "PSZ-HA*", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_REPORT_OPCODES), + +/* + * Initially Reported-by: Julian Groß + * Further reports David C. Partridge + */ +UNUSUAL_DEV(0x059f, 0x105f, 0x0000, 0x9999, + "LaCie", + "2Big Quadra USB3", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_REPORT_OPCODES | US_FL_NO_SAME), + +/* Reported-by: Julian Sikorski */ +UNUSUAL_DEV(0x059f, 0x1061, 0x0000, 0x9999, + "LaCie", + "Rugged USB3-FW", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_REPORT_OPCODES | US_FL_NO_SAME), + +/* Reported-by: Zhihong Zhou */ +UNUSUAL_DEV(0x0781, 0x55e8, 0x0000, 0x9999, + "SanDisk", + "", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_UAS), + +/* Reported-by: Hongling Zeng */ +UNUSUAL_DEV(0x090c, 0x2000, 0x0000, 0x9999, + "Hiksemi", + "External HDD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_UAS), + +/* + * Apricorn USB3 dongle sometimes returns "USBSUSBSUSBS" in response to SCSI + * commands in UAS mode. Observed with the 1.28 firmware; are there others? + */ +UNUSUAL_DEV(0x0984, 0x0301, 0x0128, 0x0128, + "Apricorn", + "", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_UAS), + +/* Reported-by: Tom Hu */ +UNUSUAL_DEV(0x0b05, 0x1932, 0x0000, 0x9999, + "ASUS", + "External HDD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_UAS), + +/* Reported-by: David Webb */ +UNUSUAL_DEV(0x0bc2, 0x331a, 0x0000, 0x9999, + "Seagate", + "Expansion Desk", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_REPORT_LUNS), + +/* Reported-by: Oliver Neukum */ +UNUSUAL_DEV(0x125f, 0xa94a, 0x0160, 0x0160, + "ADATA", + "Portable HDD CH94", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_ATA_1X), + +/* Reported-by: Benjamin Tissoires */ +UNUSUAL_DEV(0x13fd, 0x3940, 0x0000, 0x0309, + "Initio Corporation", + "INIC-3069", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_ATA_1X | US_FL_IGNORE_RESIDUE), + +/* Reported-by: Tom Arild Naess */ +UNUSUAL_DEV(0x152d, 0x0539, 0x0000, 0x9999, + "JMicron", + "JMS539", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_REPORT_OPCODES), + +/* Reported-by: Claudio Bizzarri */ +UNUSUAL_DEV(0x152d, 0x0567, 0x0000, 0x9999, + "JMicron", + "JMS567", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BROKEN_FUA | US_FL_NO_REPORT_OPCODES), + +/* Reported-by: David Kozub */ +UNUSUAL_DEV(0x152d, 0x0578, 0x0000, 0x9999, + "JMicron", + "JMS567", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BROKEN_FUA), + +/* Reported by: Yaroslav Furman */ +UNUSUAL_DEV(0x152d, 0x0583, 0x0000, 0x9999, + "JMicron", + "JMS583Gen 2", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_REPORT_OPCODES), + +/* Reported-by: Sam Burkels */ +UNUSUAL_DEV(0x154b, 0xf009, 0x0000, 0x9999, + "PNY", + "PNY ELITE PSSD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_ATA_1X | US_FL_NO_REPORT_OPCODES), + +/* Reported-by: Thinh Nguyen */ +UNUSUAL_DEV(0x154b, 0xf00b, 0x0000, 0x9999, + "PNY", + "Pro Elite SSD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_ATA_1X), + +/* Reported-by: Thinh Nguyen */ +UNUSUAL_DEV(0x154b, 0xf00d, 0x0000, 0x9999, + "PNY", + "Pro Elite SSD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_ATA_1X), + +/* Reported-by: Hongling Zeng */ +UNUSUAL_DEV(0x17ef, 0x3899, 0x0000, 0x9999, + "Thinkplus", + "External HDD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_UAS), + +/* Reported-by: Hans de Goede */ +UNUSUAL_DEV(0x2109, 0x0711, 0x0000, 0x9999, + "VIA", + "VL711", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_ATA_1X), + +/* Reported-by: Icenowy Zheng */ +UNUSUAL_DEV(0x2537, 0x1068, 0x0000, 0x9999, + "Norelsys", + "NS1068X", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_UAS), + +/* + * Initially Reported-by: Takeo Nakayama + * UAS Ignore Reported by Steven Ellis + */ +UNUSUAL_DEV(0x357d, 0x7788, 0x0000, 0x9999, + "JMicron", + "JMS566", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_REPORT_OPCODES | US_FL_IGNORE_UAS), + +/* Reported-by: Hans de Goede */ +UNUSUAL_DEV(0x4971, 0x1012, 0x0000, 0x9999, + "Hitachi", + "External HDD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_IGNORE_UAS), + +/* Reported-by: Richard Henderson */ +UNUSUAL_DEV(0x4971, 0x8017, 0x0000, 0x9999, + "SimpleTech", + "External HDD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_NO_REPORT_OPCODES), + +/* "G-DRIVE" external HDD hangs on write without these. + * Patch submitted by Alexander Kappner + */ +UNUSUAL_DEV(0x4971, 0x8024, 0x0000, 0x9999, + "SimpleTech", + "External HDD", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_ALWAYS_SYNC), diff --git a/drivers/usb/storage/unusual_usbat.h b/drivers/usb/storage/unusual_usbat.h new file mode 100644 index 000000000..f9d3e5efc --- /dev/null +++ b/drivers/usb/storage/unusual_usbat.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Unusual Devices File for SCM Microsystems (a.k.a. Shuttle) USB-ATAPI cable + */ + +#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 000000000..064c7fc8e --- /dev/null +++ b/drivers/usb/storage/usb.c @@ -0,0 +1,1258 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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. + */ + +#ifdef CONFIG_USB_STORAGE_DEBUG +#define DEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "usb.h" +#include +#include "scsiglue.h" +#include "transport.h" +#include "protocol.h" +#include "debug.h" +#include "initializers.h" + +#include "sierra_ms.h" +#include "option_ms.h" + +#if IS_ENABLED(CONFIG_USB_UAS) +#include "uas-detect.h" +#endif + +#define DRV_NAME "usb-storage" + +/* Some informational data */ +MODULE_AUTHOR("Matthew Dharm "); +MODULE_DESCRIPTION("USB Mass Storage driver for Linux"); +MODULE_LICENSE("GPL"); + +static unsigned int delay_use = 1 * MSEC_PER_SEC; + +/** + * parse_delay_str - parse an unsigned decimal integer delay + * @str: String to parse. + * @ndecimals: Number of decimal to scale up. + * @suffix: Suffix string to parse. + * @val: Where to store the parsed value. + * + * Parse an unsigned decimal value in @str, optionally end with @suffix. + * Stores the parsed value in @val just as it is if @str ends with @suffix. + * Otherwise store the value scale up by 10^(@ndecimal). + * + * Returns 0 on success, a negative error code otherwise. + */ +static int parse_delay_str(const char *str, int ndecimals, const char *suffix, + unsigned int *val) +{ + int n, n2, l; + char buf[16]; + + l = strlen(suffix); + n = strlen(str); + if (n > 0 && str[n - 1] == '\n') + --n; + if (n >= l && !strncmp(&str[n - l], suffix, l)) { + n -= l; + n2 = 0; + } else + n2 = ndecimals; + + if (n + n2 > sizeof(buf) - 1) + return -EINVAL; + + memcpy(buf, str, n); + while (n2-- > 0) + buf[n++] = '0'; + buf[n] = 0; + + return kstrtouint(buf, 10, val); +} + +/** + * format_delay_ms - format an integer value into a delay string + * @val: The integer value to format, scaled by 10^(@ndecimals). + * @ndecimals: Number of decimal to scale down. + * @suffix: Suffix string to format. + * @str: Where to store the formatted string. + * @size: The size of buffer for @str. + * + * Format an integer value in @val scale down by 10^(@ndecimals) without @suffix + * if @val is divisible by 10^(@ndecimals). + * Otherwise format a value in @val just as it is with @suffix + * + * Returns the number of characters written into @str. + */ +static int format_delay_ms(unsigned int val, int ndecimals, const char *suffix, + char *str, int size) +{ + u64 delay_ms = val; + unsigned int rem = do_div(delay_ms, int_pow(10, ndecimals)); + int ret; + + if (rem) + ret = scnprintf(str, size, "%u%s\n", val, suffix); + else + ret = scnprintf(str, size, "%u\n", (unsigned int)delay_ms); + return ret; +} + +static int delay_use_set(const char *s, const struct kernel_param *kp) +{ + unsigned int delay_ms; + int ret; + + ret = parse_delay_str(skip_spaces(s), 3, "ms", &delay_ms); + if (ret < 0) + return ret; + + *((unsigned int *)kp->arg) = delay_ms; + return 0; +} + +static int delay_use_get(char *s, const struct kernel_param *kp) +{ + unsigned int delay_ms = *((unsigned int *)kp->arg); + + return format_delay_ms(delay_ms, 3, "ms", s, PAGE_SIZE); +} + +static const struct kernel_param_ops delay_use_ops = { + .set = delay_use_set, + .get = delay_use_get, +}; +module_param_cb(delay_use, &delay_use_ops, &delay_use, 0644); +MODULE_PARM_DESC(delay_use, "time 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 through 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, \ +} + +static const struct us_unusual_dev us_unusual_dev_list[] = { +# include "unusual_devs.h" + { } /* Terminating entry */ +}; + +static const struct us_unusual_dev for_dynamic_ids = + USUAL_DEV(USB_SC_SCSI, USB_PR_BULK); + +#undef UNUSUAL_DEV +#undef COMPLIANT_DEV +#undef USUAL_DEV + +#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); + + /* + * If any of the subdrivers implemented a reinitialization scheme, + * this is where the callback would be invoked. + */ + 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); + + /* + * If any of the subdrivers implemented a reinitialization scheme, + * this is where the callback would be invoked. + */ + + 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); + struct scsi_cmnd *srb; + + 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 */ + srb = us->srb; + if (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)) { + srb->result = DID_ABORT << 16; + goto SkipForAbort; + } + + scsi_unlock(host); + + /* + * reject the command if the direction indicator + * is UNKNOWN + */ + if (srb->sc_data_direction == DMA_BIDIRECTIONAL) { + usb_stor_dbg(us, "UNKNOWN data direction\n"); + srb->result = DID_ERROR << 16; + } + + /* + * reject if target != 0 or if LUN is higher than + * the maximum known LUN + */ + else if (srb->device->id && + !(us->fflags & US_FL_SCM_MULT_TARG)) { + usb_stor_dbg(us, "Bad target number (%d:%llu)\n", + srb->device->id, + srb->device->lun); + srb->result = DID_BAD_TARGET << 16; + } + + else if (srb->device->lun > us->max_lun) { + usb_stor_dbg(us, "Bad LUN (%d:%llu)\n", + srb->device->id, + srb->device->lun); + srb->result = DID_BAD_TARGET << 16; + } + + /* + * Handle those devices which need us to fake + * their inquiry data + */ + else if ((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); + srb->result = SAM_STAT_GOOD; + } + + /* we've got a command, let's do it! */ + else { + US_DEBUG(usb_stor_show_command(us, srb)); + us->proto_handler(srb, us); + usb_mark_last_busy(us->pusb_dev); + } + + /* lock access to the state */ + scsi_lock(host); + + /* was the command aborted? */ + if (srb->result == DID_ABORT << 16) { +SkipForAbort: + usb_stor_dbg(us, "scsi command aborted\n"); + srb = NULL; /* Don't call scsi_done() */ + } + + /* + * 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); + + /* now that the locks are released, notify the SCSI core */ + if (srb) { + usb_stor_dbg(us, "scsi cmd done, result=0x%x\n", + srb->result); + scsi_done_direct(srb); + } + } /* 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_obj(*us->cr); + 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 */ +void usb_stor_adjust_quirks(struct usb_device *udev, u64 *fflags) +{ + char *p; + u16 vid = le16_to_cpu(udev->descriptor.idVendor); + u16 pid = le16_to_cpu(udev->descriptor.idProduct); + u64 f = 0; + u64 mask = (US_FL_SANE_SENSE | US_FL_BAD_SENSE | + US_FL_FIX_CAPACITY | US_FL_IGNORE_UAS | + 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 | + US_FL_NO_ATA_1X | US_FL_NO_REPORT_OPCODES | + US_FL_MAX_SECTORS_240 | US_FL_NO_REPORT_LUNS | + US_FL_ALWAYS_SYNC | US_FL_NO_SAME); + + 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 'f': + f |= US_FL_NO_REPORT_OPCODES; + break; + case 'g': + f |= US_FL_MAX_SECTORS_240; + break; + case 'h': + f |= US_FL_CAPACITY_HEURISTICS; + break; + case 'i': + f |= US_FL_IGNORE_DEVICE; + break; + case 'j': + f |= US_FL_NO_REPORT_LUNS; + break; + case 'k': + f |= US_FL_NO_SAME; + 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 't': + f |= US_FL_NO_ATA_1X; + break; + case 'u': + f |= US_FL_IGNORE_UAS; + break; + case 'w': + f |= US_FL_NO_WP_DETECT; + break; + case 'y': + f |= US_FL_ALWAYS_SYNC; + break; + /* Ignore unrecognized flag characters */ + } + } + *fflags = (*fflags & ~mask) | f; +} +EXPORT_SYMBOL_GPL(usb_stor_adjust_quirks); + +/* Get the unusual_devs entries and the string descriptors */ +static int get_device_info(struct us_data *us, const struct usb_device_id *id, + const 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; + usb_stor_adjust_quirks(us->pusb_dev, &us->fflags); + + 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: %llx\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 *alt = us->pusb_intf->cur_altsetting; + struct usb_endpoint_descriptor *ep_in; + struct usb_endpoint_descriptor *ep_out; + struct usb_endpoint_descriptor *ep_int; + int res; + + /* + * 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. + */ + res = usb_find_common_endpoints(alt, &ep_in, &ep_out, NULL, NULL); + if (res) { + usb_stor_dbg(us, "bulk endpoints not found\n"); + return res; + } + + res = usb_find_int_in_endpoint(alt, &ep_int); + if (res && us->protocol == USB_PR_CBI) { + usb_stor_dbg(us, "interrupt endpoint not found\n"); + return res; + } + + /* 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) + 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) && + !(us->fflags & US_FL_SCM_MULT_TARG)) { + mutex_lock(&us->dev_mutex); + us->max_lun = usb_stor_Bulk_max_lun(us); + /* + * Allow proper scanning of devices that present more than 8 LUNs + * While not affecting other devices that may need the previous + * behavior + */ + if (us->max_lun >= 8) + us_to_host(us)->max_lun = us->max_lun+1; + 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, + const struct us_unusual_dev *unusual_dev, + const struct scsi_host_template *sht) +{ + 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(sht, 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; + + /* + * Some USB host controllers can't do DMA: They have to use PIO, or they + * have to use a small dedicated local memory area, or they have other + * restrictions on addressable memory. + * + * We can't support these controllers on highmem systems as we don't + * kmap or bounce buffer. + */ + if (IS_ENABLED(CONFIG_HIGHMEM) && + (!hcd_uses_dma(bus_to_hcd(us->pusb_dev->bus)) || + bus_to_hcd(us->pusb_dev->bus)->localmem_pool)) { + dev_warn(&intf->dev, "USB Mass Storage not supported on this host controller\n"); + result = -EINVAL; + goto release; + } + + /* 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: + 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); + + if (us->fflags & US_FL_SCM_MULT_TARG) { + /* + * SCM eUSCSI bridge devices can have different numbers + * of LUNs on different targets; allow all to be probed. + */ + us->max_lun = 7; + /* The eUSCSI itself has ID 7, so avoid scanning that */ + us_to_host(us)->this_id = 7; + /* max_id is 8 initially, so no need to set it here */ + } else { + /* In the normal case there is only a single target */ + us_to_host(us)->max_id = 1; + /* + * Like Windows, we won't store the LUN bits in CDB[1] for + * SCSI-2 devices using the Bulk-Only transport (even though + * this violates the SCSI spec). + */ + if (us->transport == usb_stor_Bulk_transport) + us_to_host(us)->no_scsi2_lun_in_cdb = 1; + } + + /* fix for single-lun devices */ + if (us->fflags & US_FL_SINGLE_LUN) + us->max_lun = 0; + + /* 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; + usb_autopm_get_interface_no_resume(us->pusb_intf); + 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 HostAddErr; + } + + /* Submit the delayed_work for SCSI-device scanning */ + 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, + msecs_to_jiffies(delay_use)); + return 0; + + /* We come here if there are any problems */ +HostAddErr: + usb_autopm_put_interface_no_suspend(us->pusb_intf); +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); + +static struct scsi_host_template usb_stor_host_template; + +/* The main probe routine for standard devices */ +static int storage_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + const struct us_unusual_dev *unusual_dev; + struct us_data *us; + int result; + int size; + + /* If uas is enabled and this device can do uas then ignore it. */ +#if IS_ENABLED(CONFIG_USB_UAS) + if (uas_use_uas_driver(intf, id, NULL)) + return -ENXIO; +#endif + + /* + * 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, + &usb_stor_host_template); + 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 = DRV_NAME, + .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_stor_driver(usb_storage_driver, usb_stor_host_template, DRV_NAME); diff --git a/drivers/usb/storage/usb.h b/drivers/usb/storage/usb.h new file mode 100644 index 000000000..97c6196d6 --- /dev/null +++ b/drivers/usb/storage/usb.h @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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. + */ + +#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 */ + const struct us_unusual_dev *unusual_dev; + /* device-filter entry */ + u64 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, + const struct us_unusual_dev *unusual_dev, + const struct scsi_host_template *sht); +extern int usb_stor_probe2(struct us_data *us); +extern void usb_stor_disconnect(struct usb_interface *intf); + +extern void usb_stor_adjust_quirks(struct usb_device *dev, + u64 *fflags); + +#define module_usb_stor_driver(__driver, __sht, __name) \ +static int __init __driver##_init(void) \ +{ \ + usb_stor_host_template_init(&(__sht), __name, THIS_MODULE); \ + return usb_register(&(__driver)); \ +} \ +module_init(__driver##_init); \ +static void __exit __driver##_exit(void) \ +{ \ + usb_deregister(&(__driver)); \ +} \ +module_exit(__driver##_exit) + +#endif diff --git a/drivers/usb/storage/usual-tables.c b/drivers/usb/storage/usual-tables.c new file mode 100644 index 000000000..a26029e43 --- /dev/null +++ b/drivers/usb/storage/usual-tables.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for USB Mass Storage devices + * Usual Tables File for usb-storage and libusual + * + * Copyright (C) 2009 Alan Stern (stern@rowland.harvard.edu) + */ + +#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 = (kernel_ulong_t)(flags) } + +#define COMPLIANT_DEV UNUSUAL_DEV + +#define USUAL_DEV(useProto, useTrans) \ +{ USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, useProto, useTrans) } + +const 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 + +/* + * 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 const 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; + const 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/typec/Kconfig b/drivers/usb/typec/Kconfig new file mode 100644 index 000000000..2f80c2792 --- /dev/null +++ b/drivers/usb/typec/Kconfig @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: GPL-2.0 + +menuconfig TYPEC + tristate "USB Type-C Support" + help + USB Type-C Specification defines a cable and connector for USB where + only one type of plug is supported on both ends, i.e. there will not + be Type-A plug on one end of the cable and Type-B plug on the other. + Determination of the host-to-device relationship happens through a + specific Configuration Channel (CC) which goes through the USB Type-C + cable. The Configuration Channel may also be used to detect optional + Accessory Modes - Analog Audio and Debug - and if USB Power Delivery + is supported, the Alternate Modes, where the connector is used for + something else then USB communication. + + USB Power Delivery Specification defines a protocol that can be used + to negotiate the voltage and current levels with the connected + partners. USB Power Delivery allows higher voltages then the normal + 5V, up to 20V, and current up to 5A over the cable. The USB Power + Delivery protocol is also used to negotiate the optional Alternate + Modes when they are supported. USB Power Delivery does not depend on + USB Type-C connector, however it is mostly used together with USB + Type-C connectors. + + USB Type-C and USB Power Delivery Specifications define a set of state + machines that need to be implemented in either software or firmware. + Simple USB Type-C PHYs, for example USB Type-C Port Controller + Interface Specification compliant "Port Controllers" need the state + machines to be handled in the OS, but stand-alone USB Type-C and Power + Delivery controllers handle the state machines inside their firmware. + The USB Type-C and Power Delivery controllers usually function + autonomously, and do not necessarily require drivers. + + Enable this configurations option if you have USB Type-C connectors on + your system and 1) you know your USB Type-C hardware requires OS + control (a driver) to function, or 2) if you need to be able to read + the status of the USB Type-C ports in your system, or 3) if you need + to be able to swap the power role (decide are you supplying or + consuming power over the cable) or data role (host or device) when + both roles are supported. + + For more information, see the kernel documentation for USB Type-C + Connector Class API (Documentation/driver-api/usb/typec.rst) + + and ABI (Documentation/ABI/testing/sysfs-class-typec). + +if TYPEC + +source "drivers/usb/typec/tcpm/Kconfig" + +source "drivers/usb/typec/ucsi/Kconfig" + +source "drivers/usb/typec/tipd/Kconfig" + +config TYPEC_ANX7411 + tristate "Analogix ANX7411 Type-C DRP Port controller driver" + depends on I2C + depends on USB_ROLE_SWITCH + depends on POWER_SUPPLY + help + Say Y or M here if your system has Analogix ANX7411 Type-C DRP Port + controller driver. + + If you choose to build this driver as a dynamically linked module, the + module will be called anx7411.ko. + +config TYPEC_RT1719 + tristate "Richtek RT1719 Sink Only Type-C controller driver" + depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH + depends on I2C + depends on POWER_SUPPLY + select REGMAP_I2C + help + Say Y or M here if your system has Richtek RT1719 sink only + Type-C port controller driver. + + If you choose to build this driver as a dynamically linked module, the + module will be called rt1719.ko + +config TYPEC_HD3SS3220 + tristate "TI HD3SS3220 Type-C DRP Port controller driver" + depends on I2C + depends on USB_ROLE_SWITCH + help + Say Y or M here if your system has TI HD3SS3220 Type-C DRP Port + controller driver. + + If you choose to build this driver as a dynamically linked module, the + module will be called hd3ss3220.ko. + +config TYPEC_STUSB160X + tristate "STMicroelectronics STUSB160x Type-C controller driver" + depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH + depends on I2C + select REGMAP_I2C + help + Say Y or M here if your system has STMicroelectronics STUSB160x + Type-C port controller. + + If you choose to build this driver as a dynamically linked module, the + module will be called stusb160x.ko. + +config TYPEC_WUSB3801 + tristate "Willsemi WUSB3801 Type-C port controller driver" + depends on I2C + select REGMAP_I2C + help + Say Y or M here if your system has a WUSB3801 Type-C port controller. + + If you choose to build this driver as a dynamically linked module, the + module will be called wusb3801.ko. + +source "drivers/usb/typec/mux/Kconfig" + +source "drivers/usb/typec/altmodes/Kconfig" + +endif # TYPEC diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile new file mode 100644 index 000000000..8a6a1c663 --- /dev/null +++ b/drivers/usb/typec/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_TYPEC) += typec.o +typec-y := class.o mux.o bus.o pd.o retimer.o mode_selection.o +typec-$(CONFIG_ACPI) += port-mapper.o +obj-$(CONFIG_TYPEC) += altmodes/ +obj-$(CONFIG_TYPEC_TCPM) += tcpm/ +obj-$(CONFIG_TYPEC_UCSI) += ucsi/ +obj-$(CONFIG_TYPEC_TPS6598X) += tipd/ +obj-$(CONFIG_TYPEC_ANX7411) += anx7411.o +obj-$(CONFIG_TYPEC_HD3SS3220) += hd3ss3220.o +obj-$(CONFIG_TYPEC_STUSB160X) += stusb160x.o +obj-$(CONFIG_TYPEC_RT1719) += rt1719.o +obj-$(CONFIG_TYPEC_WUSB3801) += wusb3801.o +obj-$(CONFIG_TYPEC) += mux/ diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig new file mode 100644 index 000000000..b054c0b6a --- /dev/null +++ b/drivers/usb/typec/altmodes/Kconfig @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: GPL-2.0 + +menu "USB Type-C Alternate Mode drivers" + +config TYPEC_DP_ALTMODE + tristate "DisplayPort Alternate Mode driver" + depends on DRM + select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF + help + DisplayPort USB Type-C Alternate Mode allows DisplayPort + displays and adapters to be attached to the USB Type-C + connectors on the system. + + To compile this driver as a module, choose M here: the + module will be called typec_displayport. + +config TYPEC_NVIDIA_ALTMODE + tristate "NVIDIA Alternate Mode driver" + depends on TYPEC_DP_ALTMODE + help + Latest NVIDIA GPUs support VirtualLink devices. Select this + to enable support for VirtualLink devices with NVIDIA GPUs. + + To compile this driver as a module, choose M here: the + module will be called typec_nvidia. + +config TYPEC_TBT_ALTMODE + tristate "Thunderbolt3 Alternate Mode driver" + help + Select this option if you have Thunderbolt3 hardware on your + system. + + To compile this driver as a module, choose M here: the + module will be called typec_thunderbolt. + +endmenu diff --git a/drivers/usb/typec/altmodes/Makefile b/drivers/usb/typec/altmodes/Makefile new file mode 100644 index 000000000..508a68351 --- /dev/null +++ b/drivers/usb/typec/altmodes/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_TYPEC_DP_ALTMODE) += typec_displayport.o +typec_displayport-y := displayport.o +obj-$(CONFIG_TYPEC_NVIDIA_ALTMODE) += typec_nvidia.o +typec_nvidia-y := nvidia.o +obj-$(CONFIG_TYPEC_TBT_ALTMODE) += typec_thunderbolt.o +typec_thunderbolt-y := thunderbolt.o diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c new file mode 100644 index 000000000..51c92dd88 --- /dev/null +++ b/drivers/usb/typec/altmodes/displayport.c @@ -0,0 +1,874 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Typec-C DisplayPort Alternate Mode driver + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus + * + * DisplayPort is trademark of VESA (www.vesa.org) + */ + +#include +#include +#include +#include +#include +#include +#include +#include "displayport.h" + +#define DP_HEADER(_dp, ver, cmd) (VDO((_dp)->alt->svid, 1, ver, cmd) \ + | VDO_OPOS(USB_TYPEC_DP_MODE)) + +enum { + DP_CONF_USB, + DP_CONF_DFP_D, + DP_CONF_UFP_D, + DP_CONF_DUAL_D, +}; + +/* Pin assignments that use USB3.1 Gen2 signaling to carry DP protocol */ +#define DP_PIN_ASSIGN_GEN2_BR_MASK (BIT(DP_PIN_ASSIGN_A) | \ + BIT(DP_PIN_ASSIGN_B)) + +/* Pin assignments that use DP v1.3 signaling to carry DP protocol */ +#define DP_PIN_ASSIGN_DP_BR_MASK (BIT(DP_PIN_ASSIGN_C) | \ + BIT(DP_PIN_ASSIGN_D) | \ + BIT(DP_PIN_ASSIGN_E) | \ + BIT(DP_PIN_ASSIGN_F)) + +/* DP only pin assignments */ +#define DP_PIN_ASSIGN_DP_ONLY_MASK (BIT(DP_PIN_ASSIGN_A) | \ + BIT(DP_PIN_ASSIGN_C) | \ + BIT(DP_PIN_ASSIGN_E)) + +/* Pin assignments where one channel is for USB */ +#define DP_PIN_ASSIGN_MULTI_FUNC_MASK (BIT(DP_PIN_ASSIGN_B) | \ + BIT(DP_PIN_ASSIGN_D) | \ + BIT(DP_PIN_ASSIGN_F)) + +enum dp_state { + DP_STATE_IDLE, + DP_STATE_ENTER, + DP_STATE_ENTER_PRIME, + DP_STATE_UPDATE, + DP_STATE_CONFIGURE, + DP_STATE_CONFIGURE_PRIME, + DP_STATE_EXIT, + DP_STATE_EXIT_PRIME, +}; + +struct dp_altmode { + struct typec_displayport_data data; + struct typec_displayport_data data_prime; + + enum dp_state state; + bool hpd; + bool pending_hpd; + u32 irq_hpd_count; + /* + * hpd is mandatory for irq_hpd assertion, so irq_hpd also needs its own pending flag if + * both hpd and irq_hpd are asserted in the first Status Update before the pin assignment + * is configured. + */ + bool pending_irq_hpd; + + struct mutex lock; /* device lock */ + struct work_struct work; + struct typec_altmode *alt; + const struct typec_altmode *port; + struct fwnode_handle *connector_fwnode; + struct typec_altmode *plug_prime; +}; + +static int dp_altmode_notify(struct dp_altmode *dp) +{ + unsigned long conf; + u8 state; + + if (dp->data.conf) { + state = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf)); + conf = TYPEC_MODAL_STATE(state); + } else { + conf = TYPEC_STATE_USB; + } + + return typec_altmode_notify(dp->alt, conf, &dp->data); +} + +static int dp_altmode_configure(struct dp_altmode *dp, u8 con) +{ + u8 pin_assign = 0; + u32 conf; + u32 signal; + + /* DP Signalling */ + signal = DP_CAP_DP_SIGNALLING(dp->port->vdo) & DP_CAP_DP_SIGNALLING(dp->alt->vdo); + if (dp->plug_prime) + signal &= DP_CAP_DP_SIGNALLING(dp->plug_prime->vdo); + + conf = signal << DP_CONF_SIGNALLING_SHIFT; + + switch (con) { + case DP_STATUS_CON_DISABLED: + return 0; + case DP_STATUS_CON_DFP_D: + conf |= DP_CONF_UFP_U_AS_DFP_D; + pin_assign = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo) & + DP_CAP_DFP_D_PIN_ASSIGN(dp->port->vdo); + /* Account for active cable capabilities */ + if (dp->plug_prime) + pin_assign &= DP_CAP_DFP_D_PIN_ASSIGN(dp->plug_prime->vdo); + break; + case DP_STATUS_CON_UFP_D: + case DP_STATUS_CON_BOTH: /* NOTE: First acting as DP source */ + conf |= DP_CONF_UFP_U_AS_UFP_D; + pin_assign = DP_CAP_PIN_ASSIGN_UFP_D(dp->alt->vdo) & + DP_CAP_PIN_ASSIGN_DFP_D(dp->port->vdo); + /* Account for active cable capabilities */ + if (dp->plug_prime) + pin_assign &= DP_CAP_UFP_D_PIN_ASSIGN(dp->plug_prime->vdo); + break; + default: + break; + } + + /* Determining the initial pin assignment. */ + if (!DP_CONF_GET_PIN_ASSIGN(dp->data.conf)) { + /* Is USB together with DP preferred */ + if (dp->data.status & DP_STATUS_PREFER_MULTI_FUNC && + pin_assign & DP_PIN_ASSIGN_MULTI_FUNC_MASK) + pin_assign &= DP_PIN_ASSIGN_MULTI_FUNC_MASK; + else if (pin_assign & DP_PIN_ASSIGN_DP_ONLY_MASK) { + pin_assign &= DP_PIN_ASSIGN_DP_ONLY_MASK; + /* Default to pin assign C if available */ + if (pin_assign & BIT(DP_PIN_ASSIGN_C)) + pin_assign = BIT(DP_PIN_ASSIGN_C); + } + + if (!pin_assign) + return -EINVAL; + + conf |= DP_CONF_SET_PIN_ASSIGN(pin_assign); + } + + dp->data.conf = conf; + if (dp->plug_prime) + dp->data_prime.conf = conf; + + return 0; +} + +static int dp_altmode_status_update(struct dp_altmode *dp) +{ + bool configured = !!DP_CONF_GET_PIN_ASSIGN(dp->data.conf); + bool hpd = !!(dp->data.status & DP_STATUS_HPD_STATE); + bool irq_hpd = !!(dp->data.status & DP_STATUS_IRQ_HPD); + u8 con = DP_STATUS_CONNECTION(dp->data.status); + int ret = 0; + + if (configured && (dp->data.status & DP_STATUS_SWITCH_TO_USB)) { + dp->data.conf = 0; + dp->data_prime.conf = 0; + dp->state = dp->plug_prime ? DP_STATE_CONFIGURE_PRIME : + DP_STATE_CONFIGURE; + } else if (dp->data.status & DP_STATUS_EXIT_DP_MODE) { + dp->state = DP_STATE_EXIT; + } else if (!(con & DP_CONF_CURRENTLY(dp->data.conf))) { + ret = dp_altmode_configure(dp, con); + if (!ret) { + dp->state = dp->plug_prime ? DP_STATE_CONFIGURE_PRIME : + DP_STATE_CONFIGURE; + if (dp->hpd != hpd) { + dp->hpd = hpd; + dp->pending_hpd = true; + } + if (dp->hpd && dp->pending_hpd && irq_hpd) + dp->pending_irq_hpd = true; + } + } else { + drm_connector_oob_hotplug_event(dp->connector_fwnode, + hpd ? connector_status_connected : + connector_status_disconnected); + dp->hpd = hpd; + sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); + if (hpd && irq_hpd) { + dp->irq_hpd_count++; + sysfs_notify(&dp->alt->dev.kobj, "displayport", "irq_hpd"); + } + } + + return ret; +} + +static int dp_altmode_configured(struct dp_altmode *dp) +{ + sysfs_notify(&dp->alt->dev.kobj, "displayport", "configuration"); + sysfs_notify(&dp->alt->dev.kobj, "displayport", "pin_assignment"); + /* + * If the DFP_D/UFP_D sends a change in HPD when first notifying the + * DisplayPort driver that it is connected, then we wait until + * configuration is complete to signal HPD. + */ + if (dp->pending_hpd) { + drm_connector_oob_hotplug_event(dp->connector_fwnode, + connector_status_connected); + sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); + dp->pending_hpd = false; + if (dp->pending_irq_hpd) { + dp->irq_hpd_count++; + sysfs_notify(&dp->alt->dev.kobj, "displayport", "irq_hpd"); + dp->pending_irq_hpd = false; + } + } + + return dp_altmode_notify(dp); +} + +static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf) +{ + int svdm_version = typec_altmode_get_svdm_version(dp->alt); + u32 header; + int ret; + + if (svdm_version < 0) + return svdm_version; + + header = DP_HEADER(dp, svdm_version, DP_CMD_CONFIGURE); + ret = typec_altmode_notify(dp->alt, TYPEC_STATE_SAFE, &dp->data); + if (ret) { + dev_err(&dp->alt->dev, + "unable to put to connector to safe mode\n"); + return ret; + } + + ret = typec_altmode_vdm(dp->alt, header, &conf, 2); + if (ret) + dp_altmode_notify(dp); + + return ret; +} + +static int dp_altmode_configure_vdm_cable(struct dp_altmode *dp, u32 conf) +{ + int svdm_version = typec_altmode_get_cable_svdm_version(dp->plug_prime); + u32 header; + + if (svdm_version < 0) + return svdm_version; + + header = DP_HEADER(dp, svdm_version, DP_CMD_CONFIGURE); + + return typec_cable_altmode_vdm(dp->plug_prime, TYPEC_PLUG_SOP_P, header, &conf, 2); +} + +static void dp_altmode_work(struct work_struct *work) +{ + struct dp_altmode *dp = container_of(work, struct dp_altmode, work); + int svdm_version; + u32 header; + u32 vdo; + int ret; + + mutex_lock(&dp->lock); + + switch (dp->state) { + case DP_STATE_ENTER: + ret = typec_altmode_enter(dp->alt, NULL); + if (ret && ret != -EBUSY) + dev_err(&dp->alt->dev, "failed to enter mode: %d\n", ret); + break; + case DP_STATE_ENTER_PRIME: + ret = typec_cable_altmode_enter(dp->alt, TYPEC_PLUG_SOP_P, NULL); + /* + * If we fail to enter Alt Mode on SOP', then we should drop the + * plug from the driver and attempt to run the driver without + * it. + */ + if (ret && ret != -EBUSY) { + dev_err(&dp->alt->dev, "plug failed to enter mode\n"); + dp->state = DP_STATE_ENTER; + goto disable_prime; + } + break; + case DP_STATE_UPDATE: + svdm_version = typec_altmode_get_svdm_version(dp->alt); + if (svdm_version < 0) + break; + header = DP_HEADER(dp, svdm_version, DP_CMD_STATUS_UPDATE); + vdo = 1; + ret = typec_altmode_vdm(dp->alt, header, &vdo, 2); + if (ret) + dev_err(&dp->alt->dev, + "unable to send Status Update command (%d)\n", + ret); + break; + case DP_STATE_CONFIGURE: + ret = dp_altmode_configure_vdm(dp, dp->data.conf); + if (ret) + dev_err(&dp->alt->dev, + "unable to send Configure command (%d)\n", ret); + break; + case DP_STATE_CONFIGURE_PRIME: + ret = dp_altmode_configure_vdm_cable(dp, dp->data_prime.conf); + if (ret) { + dev_err(&dp->plug_prime->dev, + "unable to send Configure command (%d)\n", + ret); + dp->state = DP_STATE_CONFIGURE; + goto disable_prime; + } + break; + case DP_STATE_EXIT: + if (typec_altmode_exit(dp->alt)) + dev_err(&dp->alt->dev, "Exit Mode Failed!\n"); + break; + case DP_STATE_EXIT_PRIME: + if (typec_cable_altmode_exit(dp->plug_prime, TYPEC_PLUG_SOP_P)) + dev_err(&dp->plug_prime->dev, "Exit Mode Failed!\n"); + break; + default: + break; + } + + dp->state = DP_STATE_IDLE; + + mutex_unlock(&dp->lock); + return; + +disable_prime: + typec_altmode_put_plug(dp->plug_prime); + dp->plug_prime = NULL; + schedule_work(&dp->work); + mutex_unlock(&dp->lock); +} + +static void dp_altmode_attention(struct typec_altmode *alt, const u32 vdo) +{ + struct dp_altmode *dp = typec_altmode_get_drvdata(alt); + u8 old_state; + + mutex_lock(&dp->lock); + + old_state = dp->state; + dp->data.status = vdo; + + if (old_state != DP_STATE_IDLE) + dev_warn(&alt->dev, "ATTENTION while processing state %d\n", + old_state); + + if (dp_altmode_status_update(dp)) + dev_warn(&alt->dev, "%s: status update failed\n", __func__); + + if (dp_altmode_notify(dp)) + dev_err(&alt->dev, "%s: notification failed\n", __func__); + + if (old_state == DP_STATE_IDLE && dp->state != DP_STATE_IDLE) + schedule_work(&dp->work); + + mutex_unlock(&dp->lock); +} + +static int dp_altmode_vdm(struct typec_altmode *alt, + const u32 hdr, const u32 *vdo, int count) +{ + struct dp_altmode *dp = typec_altmode_get_drvdata(alt); + int cmd_type = PD_VDO_CMDT(hdr); + int cmd = PD_VDO_CMD(hdr); + int ret = 0; + + mutex_lock(&dp->lock); + + if (dp->state != DP_STATE_IDLE) { + ret = -EBUSY; + goto err_unlock; + } + + switch (cmd_type) { + case CMDT_RSP_ACK: + switch (cmd) { + case CMD_ENTER_MODE: + typec_altmode_update_active(alt, true); + dp->state = DP_STATE_UPDATE; + break; + case CMD_EXIT_MODE: + typec_altmode_update_active(alt, false); + dp->data.status = 0; + dp->data.conf = 0; + if (dp->hpd) { + drm_connector_oob_hotplug_event(dp->connector_fwnode, + connector_status_disconnected); + dp->hpd = false; + sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); + } + if (dp->plug_prime) + dp->state = DP_STATE_EXIT_PRIME; + break; + case DP_CMD_STATUS_UPDATE: + if (count < 2) + break; + dp->data.status = *vdo; + ret = dp_altmode_status_update(dp); + break; + case DP_CMD_CONFIGURE: + ret = dp_altmode_configured(dp); + break; + default: + break; + } + break; + case CMDT_RSP_NAK: + switch (cmd) { + case DP_CMD_STATUS_UPDATE: + dp->state = DP_STATE_EXIT; + break; + case DP_CMD_CONFIGURE: + dp->data.conf = 0; + ret = dp_altmode_configured(dp); + break; + default: + break; + } + break; + default: + break; + } + + if (dp->state != DP_STATE_IDLE) + schedule_work(&dp->work); + +err_unlock: + mutex_unlock(&dp->lock); + return ret; +} + +static int dp_cable_altmode_vdm(struct typec_altmode *alt, enum typec_plug_index sop, + const u32 hdr, const u32 *vdo, int count) +{ + struct dp_altmode *dp = typec_altmode_get_drvdata(alt); + int cmd_type = PD_VDO_CMDT(hdr); + int cmd = PD_VDO_CMD(hdr); + int ret = 0; + + mutex_lock(&dp->lock); + + if (dp->state != DP_STATE_IDLE) { + ret = -EBUSY; + goto err_unlock; + } + + switch (cmd_type) { + case CMDT_RSP_ACK: + switch (cmd) { + case CMD_ENTER_MODE: + typec_altmode_update_active(dp->plug_prime, true); + dp->state = DP_STATE_ENTER; + break; + case CMD_EXIT_MODE: + dp->data_prime.status = 0; + dp->data_prime.conf = 0; + typec_altmode_update_active(dp->plug_prime, false); + break; + case DP_CMD_CONFIGURE: + dp->state = DP_STATE_CONFIGURE; + break; + default: + break; + } + break; + case CMDT_RSP_NAK: + switch (cmd) { + case DP_CMD_CONFIGURE: + dp->data_prime.conf = 0; + /* Attempt to configure on SOP, drop plug */ + typec_altmode_put_plug(dp->plug_prime); + dp->plug_prime = NULL; + dp->state = DP_STATE_CONFIGURE; + break; + default: + break; + } + break; + default: + break; + } + + if (dp->state != DP_STATE_IDLE) + schedule_work(&dp->work); + +err_unlock: + mutex_unlock(&dp->lock); + return ret; +} + +static int dp_altmode_activate(struct typec_altmode *alt, int activate) +{ + struct dp_altmode *dp = typec_altmode_get_drvdata(alt); + int ret; + + if (activate) { + if (dp->plug_prime) { + ret = typec_cable_altmode_enter(alt, TYPEC_PLUG_SOP_P, NULL); + if (ret < 0) { + typec_altmode_put_plug(dp->plug_prime); + dp->plug_prime = NULL; + } else { + return ret; + } + } + return typec_altmode_enter(alt, NULL); + } else { + return typec_altmode_exit(alt); + } +} + +static const struct typec_altmode_ops dp_altmode_ops = { + .attention = dp_altmode_attention, + .vdm = dp_altmode_vdm, + .activate = dp_altmode_activate, +}; + +static const struct typec_cable_ops dp_cable_ops = { + .vdm = dp_cable_altmode_vdm, +}; + +static const char * const configurations[] = { + [DP_CONF_USB] = "USB", + [DP_CONF_DFP_D] = "source", + [DP_CONF_UFP_D] = "sink", +}; + +static ssize_t +configuration_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct dp_altmode *dp = dev_get_drvdata(dev); + u32 conf; + u32 cap; + int con; + int ret = 0; + + con = sysfs_match_string(configurations, buf); + if (con < 0) + return con; + + mutex_lock(&dp->lock); + + if (dp->state != DP_STATE_IDLE) { + ret = -EBUSY; + goto err_unlock; + } + + cap = DP_CAP_CAPABILITY(dp->alt->vdo); + + if ((con == DP_CONF_DFP_D && !(cap & DP_CAP_DFP_D)) || + (con == DP_CONF_UFP_D && !(cap & DP_CAP_UFP_D))) { + ret = -EINVAL; + goto err_unlock; + } + + conf = dp->data.conf & ~DP_CONF_DUAL_D; + conf |= con; + + if (dp->alt->active) { + ret = dp_altmode_configure_vdm(dp, conf); + if (ret) + goto err_unlock; + } + + dp->data.conf = conf; + +err_unlock: + mutex_unlock(&dp->lock); + + return ret ? ret : size; +} + +static ssize_t configuration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dp_altmode *dp = dev_get_drvdata(dev); + int len; + u8 cap; + u8 cur; + int i; + + mutex_lock(&dp->lock); + + cap = DP_CAP_CAPABILITY(dp->alt->vdo); + cur = DP_CONF_CURRENTLY(dp->data.conf); + + len = sprintf(buf, "%s ", cur ? "USB" : "[USB]"); + + for (i = 1; i < ARRAY_SIZE(configurations); i++) { + if (i == cur) + len += sprintf(buf + len, "[%s] ", configurations[i]); + else if ((i == DP_CONF_DFP_D && cap & DP_CAP_DFP_D) || + (i == DP_CONF_UFP_D && cap & DP_CAP_UFP_D)) + len += sprintf(buf + len, "%s ", configurations[i]); + } + + mutex_unlock(&dp->lock); + + buf[len - 1] = '\n'; + return len; +} +static DEVICE_ATTR_RW(configuration); + +static const char * const pin_assignments[] = { + [DP_PIN_ASSIGN_A] = "A", + [DP_PIN_ASSIGN_B] = "B", + [DP_PIN_ASSIGN_C] = "C", + [DP_PIN_ASSIGN_D] = "D", + [DP_PIN_ASSIGN_E] = "E", + [DP_PIN_ASSIGN_F] = "F", +}; + +/* + * Helper function to extract a peripheral's currently supported + * Pin Assignments from its DisplayPort alternate mode state. + */ +static u8 get_current_pin_assignments(struct dp_altmode *dp) +{ + if (DP_CONF_CURRENTLY(dp->data.conf) == DP_CONF_UFP_U_AS_DFP_D) + return DP_CAP_PIN_ASSIGN_DFP_D(dp->alt->vdo); + else + return DP_CAP_PIN_ASSIGN_UFP_D(dp->alt->vdo); +} + +static ssize_t +pin_assignment_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct dp_altmode *dp = dev_get_drvdata(dev); + u8 assignments; + u32 conf; + int ret; + + ret = sysfs_match_string(pin_assignments, buf); + if (ret < 0) + return ret; + + conf = DP_CONF_SET_PIN_ASSIGN(BIT(ret)); + ret = 0; + + mutex_lock(&dp->lock); + + if (conf & dp->data.conf) + goto out_unlock; + + if (dp->state != DP_STATE_IDLE) { + ret = -EBUSY; + goto out_unlock; + } + + assignments = get_current_pin_assignments(dp); + + if (!(DP_CONF_GET_PIN_ASSIGN(conf) & assignments)) { + ret = -EINVAL; + goto out_unlock; + } + + conf |= dp->data.conf & ~DP_CONF_PIN_ASSIGNEMENT_MASK; + + /* Only send Configure command if a configuration has been set */ + if (dp->alt->active && DP_CONF_CURRENTLY(dp->data.conf)) { + /* todo: send manual configure over SOP'*/ + ret = dp_altmode_configure_vdm(dp, conf); + if (ret) + goto out_unlock; + } + + dp->data.conf = conf; + +out_unlock: + mutex_unlock(&dp->lock); + + return ret ? ret : size; +} + +static ssize_t pin_assignment_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dp_altmode *dp = dev_get_drvdata(dev); + u8 assignments; + int len = 0; + u8 cur; + int i; + + mutex_lock(&dp->lock); + + cur = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf)); + + assignments = get_current_pin_assignments(dp); + + for (i = 0; assignments && i < DP_PIN_ASSIGN_MAX; assignments >>= 1, i++) { + if (assignments & 1) { + if (i == cur) + len += sprintf(buf + len, "[%s] ", + pin_assignments[i]); + else + len += sprintf(buf + len, "%s ", + pin_assignments[i]); + } + } + + mutex_unlock(&dp->lock); + + /* get_current_pin_assignments can return 0 when no matching pin assignments are found */ + if (len == 0) + len++; + + buf[len - 1] = '\n'; + return len; +} +static DEVICE_ATTR_RW(pin_assignment); + +static ssize_t hpd_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dp_altmode *dp = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", dp->hpd); +} +static DEVICE_ATTR_RO(hpd); + +static ssize_t irq_hpd_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dp_altmode *dp = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", dp->irq_hpd_count); +} +static DEVICE_ATTR_RO(irq_hpd); + +static struct attribute *displayport_attrs[] = { + &dev_attr_configuration.attr, + &dev_attr_pin_assignment.attr, + &dev_attr_hpd.attr, + &dev_attr_irq_hpd.attr, + NULL +}; + +static const struct attribute_group displayport_group = { + .name = "displayport", + .attrs = displayport_attrs, +}; + +static const struct attribute_group *displayport_groups[] = { + &displayport_group, + NULL, +}; + +int dp_altmode_probe(struct typec_altmode *alt) +{ + const struct typec_altmode *port = typec_altmode_get_partner(alt); + struct typec_altmode *plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); + struct fwnode_handle *fwnode; + struct dp_altmode *dp; + u32 cap = DP_CAP_CAPABILITY(alt->vdo); + + /* Port can only be DFP_U. */ + if (typec_altmode_get_data_role(alt) != TYPEC_HOST) + return -EPROTO; + + /* Make sure we have compatible pin configurations */ + if (!(DP_CAP_PIN_ASSIGN_DFP_D(port->vdo) & + DP_CAP_PIN_ASSIGN_UFP_D(alt->vdo)) && + !(DP_CAP_PIN_ASSIGN_UFP_D(port->vdo) & + DP_CAP_PIN_ASSIGN_DFP_D(alt->vdo))) { + typec_altmode_put_plug(plug); + return -ENODEV; + } + + /* + * Make sure the DisplayPort VDO is valid (VESA DPAM v2.1a, Section + * 5.4.1, Table 5-6, DP Capabilities VDO). A device exposing DP on a + * USB-C receptacle must advertise at least one pin assignment for the + * capability it claims, otherwise Alt Mode can never be configured. + */ + if ((cap == DP_CAP_DFP_D && !DP_CAP_PIN_ASSIGN_DFP_D(alt->vdo)) || + (cap == DP_CAP_UFP_D && !DP_CAP_PIN_ASSIGN_UFP_D(alt->vdo))) { + typec_altmode_put_plug(plug); + return -ENODEV; + } + + dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL); + if (!dp) { + typec_altmode_put_plug(plug); + return -ENOMEM; + } + + INIT_WORK(&dp->work, dp_altmode_work); + mutex_init(&dp->lock); + dp->port = port; + dp->alt = alt; + + alt->desc = "DisplayPort"; + + if (plug) { + plug->desc = "Displayport"; + plug->cable_ops = &dp_cable_ops; + } + + dp->plug_prime = plug; + + fwnode = dev_fwnode(alt->dev.parent->parent); /* typec_port fwnode */ + if (fwnode_property_present(fwnode, "displayport")) + dp->connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0); + else + dp->connector_fwnode = fwnode_handle_get(fwnode); /* embedded DP */ + if (IS_ERR(dp->connector_fwnode)) + dp->connector_fwnode = NULL; + + typec_altmode_set_drvdata(alt, dp); + if (plug) + typec_altmode_set_drvdata(plug, dp); + + if ((alt->vdo & DP_CAP_RECEPTACLE) && typec_cable_altmode_unsupported(alt)) + return 0; + + typec_altmode_set_ops(alt, &dp_altmode_ops); + if (!alt->mode_selection) { + dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER; + schedule_work(&dp->work); + } + + return 0; +} +EXPORT_SYMBOL_GPL(dp_altmode_probe); + +void dp_altmode_remove(struct typec_altmode *alt) +{ + struct dp_altmode *dp = typec_altmode_get_drvdata(alt); + + cancel_work_sync(&dp->work); + typec_altmode_put_plug(dp->plug_prime); + + if (dp->connector_fwnode) { + drm_connector_oob_hotplug_event(dp->connector_fwnode, + connector_status_disconnected); + + fwnode_handle_put(dp->connector_fwnode); + } +} +EXPORT_SYMBOL_GPL(dp_altmode_remove); + +static const struct typec_device_id dp_typec_id[] = { + { USB_TYPEC_DP_SID }, + { }, +}; +MODULE_DEVICE_TABLE(typec, dp_typec_id); + +static struct typec_altmode_driver dp_altmode_driver = { + .id_table = dp_typec_id, + .probe = dp_altmode_probe, + .remove = dp_altmode_remove, + .driver = { + .name = "typec_displayport", + .dev_groups = displayport_groups, + }, +}; +module_typec_altmode_driver(dp_altmode_driver); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DisplayPort Alternate Mode"); diff --git a/drivers/usb/typec/altmodes/displayport.h b/drivers/usb/typec/altmodes/displayport.h new file mode 100644 index 000000000..e120364da --- /dev/null +++ b/drivers/usb/typec/altmodes/displayport.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE) +int dp_altmode_probe(struct typec_altmode *alt); +void dp_altmode_remove(struct typec_altmode *alt); +#else +int dp_altmode_probe(struct typec_altmode *alt) { return -ENOTSUPP; } +void dp_altmode_remove(struct typec_altmode *alt) { } +#endif /* CONFIG_TYPEC_DP_ALTMODE */ diff --git a/drivers/usb/typec/altmodes/nvidia.c b/drivers/usb/typec/altmodes/nvidia.c new file mode 100644 index 000000000..2b77d931e --- /dev/null +++ b/drivers/usb/typec/altmodes/nvidia.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 NVIDIA Corporation. All rights reserved. + * + * NVIDIA USB Type-C Alt Mode Driver + */ +#include +#include +#include +#include "displayport.h" + +static int nvidia_altmode_probe(struct typec_altmode *alt) +{ + if (alt->svid == USB_TYPEC_NVIDIA_VLINK_SID) + return dp_altmode_probe(alt); + else + return -ENOTSUPP; +} + +static void nvidia_altmode_remove(struct typec_altmode *alt) +{ + if (alt->svid == USB_TYPEC_NVIDIA_VLINK_SID) + dp_altmode_remove(alt); +} + +static const struct typec_device_id nvidia_typec_id[] = { + { USB_TYPEC_NVIDIA_VLINK_SID }, + { }, +}; +MODULE_DEVICE_TABLE(typec, nvidia_typec_id); + +static struct typec_altmode_driver nvidia_altmode_driver = { + .id_table = nvidia_typec_id, + .probe = nvidia_altmode_probe, + .remove = nvidia_altmode_remove, + .driver = { + .name = "typec_nvidia", + }, +}; +module_typec_altmode_driver(nvidia_altmode_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("NVIDIA USB Type-C Alt Mode Driver"); diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c new file mode 100644 index 000000000..2e1b1a1da --- /dev/null +++ b/drivers/usb/typec/altmodes/thunderbolt.c @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Typec-C Thunderbolt3 Alternate Mode driver + * + * Copyright (C) 2019 Intel Corporation + * Author: Heikki Krogerus + */ + +#include +#include +#include +#include +#include +#include +#include + +enum tbt_state { + TBT_STATE_IDLE, + TBT_STATE_SOP_P_ENTER, + TBT_STATE_SOP_PP_ENTER, + TBT_STATE_ENTER, + TBT_STATE_EXIT, + TBT_STATE_SOP_PP_EXIT, + TBT_STATE_SOP_P_EXIT +}; + +struct tbt_altmode { + enum tbt_state state; + struct typec_cable *cable; + struct typec_altmode *alt; + struct typec_altmode *plug[2]; + u32 enter_vdo; + + struct work_struct work; + struct mutex lock; /* device lock */ +}; + +static bool tbt_ready(struct typec_altmode *alt); + +static int tbt_enter_mode(struct tbt_altmode *tbt) +{ + return typec_altmode_enter(tbt->alt, &tbt->enter_vdo); +} + +static void tbt_altmode_work(struct work_struct *work) +{ + struct tbt_altmode *tbt = container_of(work, struct tbt_altmode, work); + int ret; + + mutex_lock(&tbt->lock); + + switch (tbt->state) { + case TBT_STATE_SOP_P_ENTER: + ret = typec_cable_altmode_enter(tbt->alt, TYPEC_PLUG_SOP_P, NULL); + if (ret) { + dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_P]->dev, + "failed to enter mode (%d)\n", ret); + goto disable_plugs; + } + break; + case TBT_STATE_SOP_PP_ENTER: + ret = typec_cable_altmode_enter(tbt->alt, TYPEC_PLUG_SOP_PP, NULL); + if (ret) { + dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_PP]->dev, + "failed to enter mode (%d)\n", ret); + goto disable_plugs; + } + break; + case TBT_STATE_ENTER: + ret = tbt_enter_mode(tbt); + if (ret) + dev_dbg(&tbt->alt->dev, "failed to enter mode (%d)\n", + ret); + break; + case TBT_STATE_EXIT: + typec_altmode_exit(tbt->alt); + break; + case TBT_STATE_SOP_PP_EXIT: + typec_cable_altmode_exit(tbt->alt, TYPEC_PLUG_SOP_PP); + break; + case TBT_STATE_SOP_P_EXIT: + typec_cable_altmode_exit(tbt->alt, TYPEC_PLUG_SOP_P); + break; + default: + break; + } + + tbt->state = TBT_STATE_IDLE; + + mutex_unlock(&tbt->lock); + return; + +disable_plugs: + for (int i = TYPEC_PLUG_SOP_PP; i >= 0; --i) { + if (tbt->plug[i]) + typec_altmode_put_plug(tbt->plug[i]); + + tbt->plug[i] = NULL; + } + + tbt->state = TBT_STATE_ENTER; + schedule_work(&tbt->work); + mutex_unlock(&tbt->lock); +} + +/* + * If SOP' is available, enter that first (which will trigger a VDM response + * that will enter SOP" if available and then the port). If entering SOP' fails, + * stop attempting to enter either cable altmode (probably not supported) and + * directly enter the port altmode. + */ +static int tbt_enter_modes_ordered(struct typec_altmode *alt) +{ + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); + int ret = 0; + + lockdep_assert_held(&tbt->lock); + + if (!tbt_ready(tbt->alt)) + return -ENODEV; + + if (tbt->plug[TYPEC_PLUG_SOP_P]) { + ret = typec_cable_altmode_enter(alt, TYPEC_PLUG_SOP_P, NULL); + if (ret < 0) { + for (int i = TYPEC_PLUG_SOP_PP; i >= 0; --i) { + if (tbt->plug[i]) + typec_altmode_put_plug(tbt->plug[i]); + + tbt->plug[i] = NULL; + } + } else { + return ret; + } + } + + return tbt_enter_mode(tbt); +} + +static int tbt_cable_altmode_vdm(struct typec_altmode *alt, + enum typec_plug_index sop, const u32 hdr, + const u32 *vdo, int count) +{ + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); + int cmd_type = PD_VDO_CMDT(hdr); + int cmd = PD_VDO_CMD(hdr); + + mutex_lock(&tbt->lock); + + if (tbt->state != TBT_STATE_IDLE) { + mutex_unlock(&tbt->lock); + return -EBUSY; + } + + switch (cmd_type) { + case CMDT_RSP_ACK: + switch (cmd) { + case CMD_ENTER_MODE: + /* + * Following the order described in USB Type-C Spec + * R2.0 Section 6.7.3: SOP', SOP", then port. + */ + if (sop == TYPEC_PLUG_SOP_P) { + if (tbt->plug[TYPEC_PLUG_SOP_PP]) + tbt->state = TBT_STATE_SOP_PP_ENTER; + else + tbt->state = TBT_STATE_ENTER; + } else if (sop == TYPEC_PLUG_SOP_PP) + tbt->state = TBT_STATE_ENTER; + + break; + case CMD_EXIT_MODE: + /* Exit in opposite order: Port, SOP", then SOP'. */ + if (sop == TYPEC_PLUG_SOP_PP) + tbt->state = TBT_STATE_SOP_P_EXIT; + break; + } + break; + default: + break; + } + + if (tbt->state != TBT_STATE_IDLE) + schedule_work(&tbt->work); + + mutex_unlock(&tbt->lock); + return 0; +} + +static int tbt_altmode_vdm(struct typec_altmode *alt, + const u32 hdr, const u32 *vdo, int count) +{ + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); + struct typec_thunderbolt_data data; + int cmd_type = PD_VDO_CMDT(hdr); + int cmd = PD_VDO_CMD(hdr); + + mutex_lock(&tbt->lock); + + if (tbt->state != TBT_STATE_IDLE) { + mutex_unlock(&tbt->lock); + return -EBUSY; + } + + switch (cmd_type) { + case CMDT_RSP_ACK: + /* Port altmode is last to enter and first to exit. */ + switch (cmd) { + case CMD_ENTER_MODE: + memset(&data, 0, sizeof(data)); + + data.device_mode = tbt->alt->vdo; + data.enter_vdo = tbt->enter_vdo; + if (tbt->plug[TYPEC_PLUG_SOP_P]) + data.cable_mode = tbt->plug[TYPEC_PLUG_SOP_P]->vdo; + + typec_altmode_notify(alt, TYPEC_STATE_MODAL, &data); + break; + case CMD_EXIT_MODE: + if (tbt->plug[TYPEC_PLUG_SOP_PP]) + tbt->state = TBT_STATE_SOP_PP_EXIT; + else if (tbt->plug[TYPEC_PLUG_SOP_P]) + tbt->state = TBT_STATE_SOP_P_EXIT; + break; + } + break; + case CMDT_RSP_NAK: + switch (cmd) { + case CMD_ENTER_MODE: + dev_warn(&alt->dev, "Enter Mode refused\n"); + break; + default: + break; + } + break; + default: + break; + } + + if (tbt->state != TBT_STATE_IDLE) + schedule_work(&tbt->work); + + mutex_unlock(&tbt->lock); + + return 0; +} + +static int tbt_altmode_activate(struct typec_altmode *alt, int activate) +{ + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); + int ret; + + mutex_lock(&tbt->lock); + + if (activate) + ret = tbt_enter_modes_ordered(alt); + else + ret = typec_altmode_exit(alt); + + mutex_unlock(&tbt->lock); + + return ret; +} + +static const struct typec_altmode_ops tbt_altmode_ops = { + .vdm = tbt_altmode_vdm, + .activate = tbt_altmode_activate +}; + +static const struct typec_cable_ops tbt_cable_ops = { + .vdm = tbt_cable_altmode_vdm, +}; + +static int tbt_altmode_probe(struct typec_altmode *alt) +{ + struct tbt_altmode *tbt; + + tbt = devm_kzalloc(&alt->dev, sizeof(*tbt), GFP_KERNEL); + if (!tbt) + return -ENOMEM; + + INIT_WORK(&tbt->work, tbt_altmode_work); + mutex_init(&tbt->lock); + tbt->alt = alt; + + alt->desc = "Thunderbolt3"; + typec_altmode_set_drvdata(alt, tbt); + + if (typec_cable_altmode_unsupported(alt)) + return 0; + + typec_altmode_set_ops(alt, &tbt_altmode_ops); + + if (!alt->mode_selection && tbt_ready(alt)) { + if (tbt->plug[TYPEC_PLUG_SOP_P]) + tbt->state = TBT_STATE_SOP_P_ENTER; + else if (tbt->plug[TYPEC_PLUG_SOP_PP]) + tbt->state = TBT_STATE_SOP_PP_ENTER; + else + tbt->state = TBT_STATE_ENTER; + schedule_work(&tbt->work); + } + + return 0; +} + +static void tbt_altmode_remove(struct typec_altmode *alt) +{ + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); + + disable_work_sync(&tbt->work); + + for (int i = TYPEC_PLUG_SOP_PP; i >= 0; --i) { + if (tbt->plug[i]) + typec_altmode_put_plug(tbt->plug[i]); + } + + if (tbt->cable) + typec_cable_put(tbt->cable); +} + +static bool tbt_ready(struct typec_altmode *alt) +{ + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); + struct typec_altmode *plug; + u32 vdo; + + if (tbt->cable) + return true; + + /* Thunderbolt 3 requires a cable with eMarker */ + tbt->cable = typec_cable_get(typec_altmode2port(tbt->alt)); + if (!tbt->cable) + return false; + + /* We accept systems without SOP' or SOP''. This means the port altmode + * driver will be responsible for properly ordering entry/exit. + */ + for (int i = 0; i < TYPEC_PLUG_SOP_PP + 1; i++) { + plug = typec_altmode_get_plug(tbt->alt, i); + if (!plug) + continue; + + if (plug->svid != USB_TYPEC_TBT_SID) + break; + + plug->desc = "Thunderbolt3"; + plug->cable_ops = &tbt_cable_ops; + typec_altmode_set_drvdata(plug, tbt); + + tbt->plug[i] = plug; + } + + vdo = tbt->alt->vdo & (TBT_VENDOR_SPECIFIC_B0 | TBT_VENDOR_SPECIFIC_B1); + vdo |= tbt->alt->vdo & TBT_INTEL_SPECIFIC_B0; + vdo |= TBT_MODE; + plug = tbt->plug[TYPEC_PLUG_SOP_P]; + + if (plug) { + if (typec_cable_is_active(tbt->cable)) + vdo |= TBT_ENTER_MODE_ACTIVE_CABLE; + + vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_SPEED(plug->vdo)); + vdo |= plug->vdo & TBT_CABLE_ROUNDED; + vdo |= plug->vdo & TBT_CABLE_OPTICAL; + vdo |= plug->vdo & TBT_CABLE_RETIMER; + vdo |= plug->vdo & TBT_CABLE_LINK_TRAINING; + } else { + vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_USB3_PASSIVE); + } + + tbt->enter_vdo = vdo; + + return true; +} + +static const struct typec_device_id tbt_typec_id[] = { + { USB_TYPEC_TBT_SID }, + { } +}; +MODULE_DEVICE_TABLE(typec, tbt_typec_id); + +static struct typec_altmode_driver tbt_altmode_driver = { + .id_table = tbt_typec_id, + .probe = tbt_altmode_probe, + .remove = tbt_altmode_remove, + .driver = { + .name = "typec-thunderbolt", + } +}; +module_typec_altmode_driver(tbt_altmode_driver); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Thunderbolt3 USB Type-C Alternate Mode"); diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c new file mode 100644 index 000000000..41df11591 --- /dev/null +++ b/drivers/usb/typec/anx7411.c @@ -0,0 +1,1611 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Driver for Analogix ANX7411 USB Type-C and PD controller + * + * Copyright(c) 2022, Analogix Semiconductor. All rights reserved. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TCPC_ADDRESS1 0x58 +#define TCPC_ADDRESS2 0x56 +#define TCPC_ADDRESS3 0x54 +#define TCPC_ADDRESS4 0x52 +#define SPI_ADDRESS1 0x7e +#define SPI_ADDRESS2 0x6e +#define SPI_ADDRESS3 0x64 +#define SPI_ADDRESS4 0x62 + +struct anx7411_i2c_select { + u8 tcpc_address; + u8 spi_address; +}; + +#define VID_ANALOGIX 0x1F29 +#define PID_ANALOGIX 0x7411 + +/* TCPC register define */ + +#define ANALOG_CTRL_10 0xAA + +#define STATUS_LEN 2 +#define ALERT_0 0xCB +#define RECEIVED_MSG BIT(7) +#define SOFTWARE_INT BIT(6) +#define MSG_LEN 32 +#define HEADER_LEN 2 +#define MSG_HEADER 0x00 +#define MSG_TYPE 0x01 +#define MSG_RAWDATA 0x02 +#define MSG_LEN_MASK 0x1F + +#define ALERT_1 0xCC +#define INTP_POW_ON BIT(7) +#define INTP_POW_OFF BIT(6) + +#define VBUS_THRESHOLD_H 0xDD +#define VBUS_THRESHOLD_L 0xDE + +#define FW_CTRL_0 0xF0 +#define UNSTRUCT_VDM_EN BIT(0) +#define DELAY_200MS BIT(1) +#define VSAFE0 0 +#define VSAFE1 BIT(2) +#define VSAFE2 BIT(3) +#define VSAFE3 (BIT(2) | BIT(3)) +#define FRS_EN BIT(7) + +#define FW_PARAM 0xF1 +#define DONGLE_IOP BIT(0) + +#define FW_CTRL_2 0xF7 +#define SINK_CTRL_DIS_FLAG BIT(5) + +/* SPI register define */ +#define OCM_CTRL_0 0x6E +#define OCM_RESET BIT(6) + +#define MAX_VOLTAGE 0xAC +#define MAX_POWER 0xAD +#define MIN_POWER 0xAE + +#define REQUEST_VOLTAGE 0xAF +#define VOLTAGE_UNIT 100 /* mV per unit */ + +#define REQUEST_CURRENT 0xB1 +#define CURRENT_UNIT 50 /* mA per unit */ + +#define CMD_SEND_BUF 0xC0 +#define CMD_RECV_BUF 0xE0 + +#define REQ_VOL_20V_IN_100MV 0xC8 +#define REQ_CUR_2_25A_IN_50MA 0x2D +#define REQ_CUR_3_25A_IN_50MA 0x41 + +#define DEF_5V 5000 +#define DEF_1_5A 1500 + +#define LOBYTE(w) ((u8)((w) & 0xFF)) +#define HIBYTE(w) ((u8)(((u16)(w) >> 8) & 0xFF)) + +enum anx7411_typec_message_type { + TYPE_SRC_CAP = 0x00, + TYPE_SNK_CAP = 0x01, + TYPE_SNK_IDENTITY = 0x02, + TYPE_SVID = 0x03, + TYPE_SET_SNK_DP_CAP = 0x08, + TYPE_PSWAP_REQ = 0x10, + TYPE_DSWAP_REQ = 0x11, + TYPE_VDM = 0x14, + TYPE_OBJ_REQ = 0x16, + TYPE_DP_ALT_ENTER = 0x19, + TYPE_DP_DISCOVER_MODES_INFO = 0x27, + TYPE_GET_DP_CONFIG = 0x29, + TYPE_DP_CONFIGURE = 0x2A, + TYPE_GET_DP_DISCOVER_MODES_INFO = 0x2E, + TYPE_GET_DP_ALT_ENTER = 0x2F, +}; + +#define FW_CTRL_1 0xB2 +#define AUTO_PD_EN BIT(1) +#define TRYSRC_EN BIT(2) +#define TRYSNK_EN BIT(3) +#define FORCE_SEND_RDO BIT(6) + +#define FW_VER 0xB4 +#define FW_SUBVER 0xB5 + +#define INT_MASK 0xB6 +#define INT_STS 0xB7 +#define OCM_BOOT_UP BIT(0) +#define OC_OV_EVENT BIT(1) +#define VCONN_CHANGE BIT(2) +#define VBUS_CHANGE BIT(3) +#define CC_STATUS_CHANGE BIT(4) +#define DATA_ROLE_CHANGE BIT(5) +#define PR_CONSUMER_GOT_POWER BIT(6) +#define HPD_STATUS_CHANGE BIT(7) + +#define SYSTEM_STSTUS 0xB8 +/* 0: SINK off; 1: SINK on */ +#define SINK_STATUS BIT(1) +/* 0: VCONN off; 1: VCONN on*/ +#define VCONN_STATUS BIT(2) +/* 0: vbus off; 1: vbus on*/ +#define VBUS_STATUS BIT(3) +/* 1: host; 0:device*/ +#define DATA_ROLE BIT(5) +/* 0: Chunking; 1: Unchunked*/ +#define SUPPORT_UNCHUNKING BIT(6) +/* 0: HPD low; 1: HPD high*/ +#define HPD_STATUS BIT(7) + +#define DATA_DFP 1 +#define DATA_UFP 2 +#define POWER_SOURCE 1 +#define POWER_SINK 2 + +#define CC_STATUS 0xB9 +#define CC1_RD BIT(0) +#define CC2_RD BIT(4) +#define CC1_RA BIT(1) +#define CC2_RA BIT(5) +#define CC1_RD BIT(0) +#define CC1_RP(cc) (((cc) >> 2) & 0x03) +#define CC2_RP(cc) (((cc) >> 6) & 0x03) + +#define PD_REV_INIT 0xBA + +#define PD_EXT_MSG_CTRL 0xBB +#define SRC_CAP_EXT_REPLY BIT(0) +#define MANUFACTURER_INFO_REPLY BIT(1) +#define BATTERY_STS_REPLY BIT(2) +#define BATTERY_CAP_REPLY BIT(3) +#define ALERT_REPLY BIT(4) +#define STATUS_REPLY BIT(5) +#define PPS_STATUS_REPLY BIT(6) +#define SNK_CAP_EXT_REPLY BIT(7) + +#define NO_CONNECT 0x00 +#define USB3_1_CONNECTED 0x01 +#define DP_ALT_4LANES 0x02 +#define USB3_1_DP_2LANES 0x03 +#define CC1_CONNECTED 0x01 +#define CC2_CONNECTED 0x02 +#define SELECT_PIN_ASSIGMENT_C 0x04 +#define SELECT_PIN_ASSIGMENT_D 0x08 +#define SELECT_PIN_ASSIGMENT_E 0x10 +#define SELECT_PIN_ASSIGMENT_U 0x00 +#define REDRIVER_ADDRESS 0x20 +#define REDRIVER_OFFSET 0x00 + +#define DP_SVID 0xFF01 +#define VDM_ACK 0x40 +#define VDM_CMD_RES 0x00 +#define VDM_CMD_DIS_ID 0x01 +#define VDM_CMD_DIS_SVID 0x02 +#define VDM_CMD_DIS_MOD 0x03 +#define VDM_CMD_ENTER_MODE 0x04 +#define VDM_CMD_EXIT_MODE 0x05 +#define VDM_CMD_ATTENTION 0x06 +#define VDM_CMD_GET_STS 0x10 +#define VDM_CMD_AND_ACK_MASK 0x5F + +#define MAX_ALTMODE 2 + +#define HAS_SOURCE_CAP BIT(0) +#define HAS_SINK_CAP BIT(1) +#define HAS_SINK_WATT BIT(2) + +enum anx7411_psy_state { + /* copy from drivers/usb/typec/tcpm */ + ANX7411_PSY_OFFLINE = 0, + ANX7411_PSY_FIXED_ONLINE, + + /* private */ + /* PD keep in, but disconnct power to bq25700, + * this state can be active when higher capacity adapter plug in, + * and change to ONLINE state when higher capacity adapter plug out + */ + ANX7411_PSY_HANG = 0xff, +}; + +struct typec_params { + int request_current; /* ma */ + int request_voltage; /* mv */ + int cc_connect; + int cc_orientation_valid; + int cc_status; + int data_role; + int power_role; + int vconn_role; + int dp_altmode_enter; + int cust_altmode_enter; + struct usb_role_switch *role_sw; + struct typec_port *port; + struct typec_partner *partner; + struct typec_mux_dev *typec_mux; + struct typec_switch_dev *typec_switch; + struct typec_altmode *amode[MAX_ALTMODE]; + struct typec_altmode *port_amode[MAX_ALTMODE]; + struct typec_displayport_data data; + int pin_assignment; + struct typec_capability caps; + u32 src_pdo[PDO_MAX_OBJECTS]; + u32 sink_pdo[PDO_MAX_OBJECTS]; + u8 caps_flags; + u8 src_pdo_nr; + u8 sink_pdo_nr; + u8 sink_watt; + u8 sink_voltage; +}; + +#define MAX_BUF_LEN 30 +struct fw_msg { + u8 msg_len; + u8 msg_type; + u8 buf[MAX_BUF_LEN]; +} __packed; + +struct anx7411_data { + int fw_version; + int fw_subversion; + struct i2c_client *tcpc_client; + struct i2c_client *spi_client; + struct fw_msg send_msg; + struct fw_msg recv_msg; + struct gpio_desc *intp_gpiod; + struct fwnode_handle *connector_fwnode; + struct typec_params typec; + int intp_irq; + struct work_struct work; + struct workqueue_struct *workqueue; + /* Lock for interrupt work queue */ + struct mutex lock; + + enum anx7411_psy_state psy_online; + enum power_supply_usb_type usb_type; + struct power_supply *psy; + struct power_supply_desc psy_desc; + struct device *dev; + struct fwnode_handle *switch_node; + struct fwnode_handle *mux_node; +}; + +static u8 snk_identity[] = { + LOBYTE(VID_ANALOGIX), HIBYTE(VID_ANALOGIX), 0x00, 0x82, /* snk_id_hdr */ + 0x00, 0x00, 0x00, 0x00, /* snk_cert */ + 0x00, 0x00, LOBYTE(PID_ANALOGIX), HIBYTE(PID_ANALOGIX), /* 5snk_ama */ +}; + +static u8 dp_caps[4] = {0xC6, 0x00, 0x00, 0x00}; + +static int anx7411_reg_read(struct i2c_client *client, + u8 reg_addr) +{ + return i2c_smbus_read_byte_data(client, reg_addr); +} + +static int anx7411_reg_block_read(struct i2c_client *client, + u8 reg_addr, u8 len, u8 *buf) +{ + return i2c_smbus_read_i2c_block_data(client, reg_addr, len, buf); +} + +static int anx7411_reg_write(struct i2c_client *client, + u8 reg_addr, u8 reg_val) +{ + return i2c_smbus_write_byte_data(client, reg_addr, reg_val); +} + +static int anx7411_reg_block_write(struct i2c_client *client, + u8 reg_addr, u8 len, u8 *buf) +{ + return i2c_smbus_write_i2c_block_data(client, reg_addr, len, buf); +} + +static struct anx7411_i2c_select anx7411_i2c_addr[] = { + {TCPC_ADDRESS1, SPI_ADDRESS1}, + {TCPC_ADDRESS2, SPI_ADDRESS2}, + {TCPC_ADDRESS3, SPI_ADDRESS3}, + {TCPC_ADDRESS4, SPI_ADDRESS4}, +}; + +static int anx7411_detect_power_mode(struct anx7411_data *ctx) +{ + int ret; + int mode; + + ret = anx7411_reg_read(ctx->spi_client, REQUEST_CURRENT); + if (ret < 0) + return ret; + + ctx->typec.request_current = ret * CURRENT_UNIT; /* 50ma per unit */ + + ret = anx7411_reg_read(ctx->spi_client, REQUEST_VOLTAGE); + if (ret < 0) + return ret; + + ctx->typec.request_voltage = ret * VOLTAGE_UNIT; /* 100mv per unit */ + + if (ctx->psy_online == ANX7411_PSY_OFFLINE) { + ctx->psy_online = ANX7411_PSY_FIXED_ONLINE; + ctx->usb_type = POWER_SUPPLY_USB_TYPE_PD; + power_supply_changed(ctx->psy); + } + + if (!ctx->typec.cc_orientation_valid) + return 0; + + if (ctx->typec.cc_connect == CC1_CONNECTED) + mode = CC1_RP(ctx->typec.cc_status); + else + mode = CC2_RP(ctx->typec.cc_status); + if (mode) { + typec_set_pwr_opmode(ctx->typec.port, mode - 1); + return 0; + } + + typec_set_pwr_opmode(ctx->typec.port, TYPEC_PWR_MODE_PD); + + return 0; +} + +static int anx7411_register_partner(struct anx7411_data *ctx, + int pd, int accessory) +{ + struct typec_partner_desc desc; + struct typec_partner *partner; + + if (ctx->typec.partner) + return 0; + + desc.usb_pd = pd; + desc.accessory = accessory; + desc.identity = NULL; + partner = typec_register_partner(ctx->typec.port, &desc); + if (IS_ERR(partner)) + return PTR_ERR(partner); + + ctx->typec.partner = partner; + + return 0; +} + +static int anx7411_detect_cc_orientation(struct anx7411_data *ctx) +{ + struct device *dev = &ctx->spi_client->dev; + int ret; + int cc1_rd, cc2_rd; + int cc1_ra, cc2_ra; + int cc1_rp, cc2_rp; + + ret = anx7411_reg_read(ctx->spi_client, CC_STATUS); + if (ret < 0) + return ret; + + ctx->typec.cc_status = ret; + + cc1_rd = ret & CC1_RD ? 1 : 0; + cc2_rd = ret & CC2_RD ? 1 : 0; + cc1_ra = ret & CC1_RA ? 1 : 0; + cc2_ra = ret & CC2_RA ? 1 : 0; + cc1_rp = CC1_RP(ret); + cc2_rp = CC2_RP(ret); + + /* Debug cable, nothing to do */ + if (cc1_rd && cc2_rd) { + ctx->typec.cc_orientation_valid = 0; + return anx7411_register_partner(ctx, 0, TYPEC_ACCESSORY_DEBUG); + } + + if (cc1_ra && cc2_ra) { + ctx->typec.cc_orientation_valid = 0; + return anx7411_register_partner(ctx, 0, TYPEC_ACCESSORY_AUDIO); + } + + ctx->typec.cc_orientation_valid = 1; + + ret = anx7411_register_partner(ctx, 1, TYPEC_ACCESSORY_NONE); + if (ret) { + dev_err(dev, "register partner\n"); + return ret; + } + + if (cc1_rd || cc1_rp) { + typec_set_orientation(ctx->typec.port, TYPEC_ORIENTATION_NORMAL); + ctx->typec.cc_connect = CC1_CONNECTED; + } + + if (cc2_rd || cc2_rp) { + typec_set_orientation(ctx->typec.port, TYPEC_ORIENTATION_REVERSE); + ctx->typec.cc_connect = CC2_CONNECTED; + } + + return 0; +} + +static int anx7411_set_mux(struct anx7411_data *ctx, int pin_assignment) +{ + int mode = TYPEC_STATE_SAFE; + + switch (pin_assignment) { + case SELECT_PIN_ASSIGMENT_U: + /* default 4 line USB 3.1 */ + mode = TYPEC_STATE_MODAL; + break; + case SELECT_PIN_ASSIGMENT_C: + case SELECT_PIN_ASSIGMENT_E: + /* 4 line DP */ + mode = TYPEC_STATE_SAFE; + break; + case SELECT_PIN_ASSIGMENT_D: + /* 2 line DP, 2 line USB */ + mode = TYPEC_MODE_USB3; + break; + default: + mode = TYPEC_STATE_SAFE; + break; + } + + ctx->typec.pin_assignment = pin_assignment; + + return typec_set_mode(ctx->typec.port, mode); +} + +static int anx7411_set_usb_role(struct anx7411_data *ctx, enum usb_role role) +{ + if (!ctx->typec.role_sw) + return 0; + + return usb_role_switch_set_role(ctx->typec.role_sw, role); +} + +static int anx7411_data_role_detect(struct anx7411_data *ctx) +{ + int ret; + + ret = anx7411_reg_read(ctx->spi_client, SYSTEM_STSTUS); + if (ret < 0) + return ret; + + ctx->typec.data_role = (ret & DATA_ROLE) ? TYPEC_HOST : TYPEC_DEVICE; + ctx->typec.vconn_role = (ret & VCONN_STATUS) ? TYPEC_SOURCE : TYPEC_SINK; + + typec_set_data_role(ctx->typec.port, ctx->typec.data_role); + + typec_set_vconn_role(ctx->typec.port, ctx->typec.vconn_role); + + if (ctx->typec.data_role == TYPEC_HOST) + return anx7411_set_usb_role(ctx, USB_ROLE_HOST); + + return anx7411_set_usb_role(ctx, USB_ROLE_DEVICE); +} + +static int anx7411_power_role_detect(struct anx7411_data *ctx) +{ + int ret; + + ret = anx7411_reg_read(ctx->spi_client, SYSTEM_STSTUS); + if (ret < 0) + return ret; + + ctx->typec.power_role = (ret & SINK_STATUS) ? TYPEC_SINK : TYPEC_SOURCE; + + if (ctx->typec.power_role == TYPEC_SOURCE) { + ctx->typec.request_current = DEF_1_5A; + ctx->typec.request_voltage = DEF_5V; + } + + typec_set_pwr_role(ctx->typec.port, ctx->typec.power_role); + + return 0; +} + +static int anx7411_cc_status_detect(struct anx7411_data *ctx) +{ + anx7411_detect_cc_orientation(ctx); + anx7411_detect_power_mode(ctx); + + return 0; +} + +static void anx7411_partner_unregister_altmode(struct anx7411_data *ctx) +{ + int i; + + ctx->typec.dp_altmode_enter = 0; + ctx->typec.cust_altmode_enter = 0; + + for (i = 0; i < MAX_ALTMODE; i++) + if (ctx->typec.amode[i]) { + typec_unregister_altmode(ctx->typec.amode[i]); + ctx->typec.amode[i] = NULL; + } + + ctx->typec.pin_assignment = 0; +} + +static int anx7411_typec_register_altmode(struct anx7411_data *ctx, + int svid, int vdo) +{ + struct device *dev = &ctx->spi_client->dev; + struct typec_altmode_desc desc; + int err; + int i; + + desc.svid = svid; + desc.vdo = vdo; + + for (i = 0; i < MAX_ALTMODE; i++) + if (!ctx->typec.amode[i]) + break; + + desc.mode = i + 1; /* start with 1 */ + + if (i >= MAX_ALTMODE) { + dev_err(dev, "no altmode space for registering\n"); + return -ENOMEM; + } + + ctx->typec.amode[i] = typec_partner_register_altmode(ctx->typec.partner, + &desc); + if (IS_ERR(ctx->typec.amode[i])) { + dev_err(dev, "failed to register altmode\n"); + err = PTR_ERR(ctx->typec.amode[i]); + ctx->typec.amode[i] = NULL; + return err; + } + + return 0; +} + +static void anx7411_unregister_partner(struct anx7411_data *ctx) +{ + if (ctx->typec.partner) { + typec_unregister_partner(ctx->typec.partner); + ctx->typec.partner = NULL; + } +} + +static int anx7411_update_altmode(struct anx7411_data *ctx, int svid) +{ + int i; + + if (svid == DP_SVID) + ctx->typec.dp_altmode_enter = 1; + else + ctx->typec.cust_altmode_enter = 1; + + for (i = 0; i < MAX_ALTMODE; i++) { + if (!ctx->typec.amode[i]) + continue; + + if (ctx->typec.amode[i]->svid == svid) { + typec_altmode_update_active(ctx->typec.amode[i], true); + typec_altmode_notify(ctx->typec.amode[i], + ctx->typec.pin_assignment, + &ctx->typec.data); + break; + } + } + + return 0; +} + +static int anx7411_register_altmode(struct anx7411_data *ctx, + bool dp_altmode, u8 *buf) +{ + int ret; + int svid; + int mid; + + if (!ctx->typec.partner) + return 0; + + svid = DP_SVID; + if (dp_altmode) { + mid = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + + return anx7411_typec_register_altmode(ctx, svid, mid); + } + + svid = (buf[3] << 8) | buf[2]; + if ((buf[0] & VDM_CMD_AND_ACK_MASK) != (VDM_ACK | VDM_CMD_ENTER_MODE)) + return anx7411_update_altmode(ctx, svid); + + if ((buf[0] & VDM_CMD_AND_ACK_MASK) != (VDM_ACK | VDM_CMD_DIS_MOD)) + return 0; + + mid = buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24); + + ret = anx7411_typec_register_altmode(ctx, svid, mid); + if (ctx->typec.cust_altmode_enter) + ret |= anx7411_update_altmode(ctx, svid); + + return ret; +} + +static int anx7411_parse_cmd(struct anx7411_data *ctx, u8 type, u8 *buf, u8 len) +{ + struct device *dev = &ctx->spi_client->dev; + u8 cur_50ma, vol_100mv; + + switch (type) { + case TYPE_SRC_CAP: + cur_50ma = anx7411_reg_read(ctx->spi_client, REQUEST_CURRENT); + vol_100mv = anx7411_reg_read(ctx->spi_client, REQUEST_VOLTAGE); + + ctx->typec.request_voltage = vol_100mv * VOLTAGE_UNIT; + ctx->typec.request_current = cur_50ma * CURRENT_UNIT; + + ctx->psy_online = ANX7411_PSY_FIXED_ONLINE; + ctx->usb_type = POWER_SUPPLY_USB_TYPE_PD; + power_supply_changed(ctx->psy); + break; + case TYPE_SNK_CAP: + break; + case TYPE_SVID: + break; + case TYPE_SNK_IDENTITY: + break; + case TYPE_GET_DP_ALT_ENTER: + /* DP alt mode enter success */ + if (buf[0]) + anx7411_update_altmode(ctx, DP_SVID); + break; + case TYPE_DP_ALT_ENTER: + /* Update DP altmode */ + anx7411_update_altmode(ctx, DP_SVID); + break; + case TYPE_OBJ_REQ: + anx7411_detect_power_mode(ctx); + break; + case TYPE_DP_CONFIGURE: + anx7411_set_mux(ctx, buf[1]); + break; + case TYPE_DP_DISCOVER_MODES_INFO: + /* Make sure discover modes valid */ + if (buf[0] | buf[1]) + /* Register DP Altmode */ + anx7411_register_altmode(ctx, 1, buf); + break; + case TYPE_VDM: + /* Register other altmode */ + anx7411_register_altmode(ctx, 0, buf); + break; + default: + dev_err(dev, "ignore message(0x%.02x).\n", type); + break; + } + + return 0; +} + +static u8 checksum(struct device *dev, u8 *buf, u8 len) +{ + u8 ret = 0; + u8 i; + + for (i = 0; i < len; i++) + ret += buf[i]; + + return ret; +} + +static int anx7411_read_msg_ctrl_status(struct i2c_client *client) +{ + return anx7411_reg_read(client, CMD_SEND_BUF); +} + +static int anx7411_wait_msg_empty(struct i2c_client *client) +{ + int val; + + return readx_poll_timeout(anx7411_read_msg_ctrl_status, + client, val, (val < 0) || (val == 0), + 2000, 2000 * 150); +} + +static int anx7411_send_msg(struct anx7411_data *ctx, u8 type, u8 *buf, u8 size) +{ + struct device *dev = &ctx->spi_client->dev; + struct fw_msg *msg = &ctx->send_msg; + u8 crc; + int ret; + + size = min_t(u8, size, (u8)MAX_BUF_LEN); + memcpy(msg->buf, buf, size); + msg->msg_type = type; + /* msg len equals buffer length + msg_type */ + msg->msg_len = size + 1; + + /* Do CRC check for all buffer data and msg_len and msg_type */ + crc = checksum(dev, (u8 *)msg, size + HEADER_LEN); + msg->buf[size] = 0 - crc; + + ret = anx7411_wait_msg_empty(ctx->spi_client); + if (ret) + return ret; + + ret = anx7411_reg_block_write(ctx->spi_client, + CMD_SEND_BUF + 1, size + HEADER_LEN, + &msg->msg_type); + ret |= anx7411_reg_write(ctx->spi_client, CMD_SEND_BUF, + msg->msg_len); + return ret; +} + +static int anx7411_process_cmd(struct anx7411_data *ctx) +{ + struct device *dev = &ctx->spi_client->dev; + struct fw_msg *msg = &ctx->recv_msg; + u8 len; + u8 crc; + int ret; + + /* Read message from firmware */ + ret = anx7411_reg_block_read(ctx->spi_client, CMD_RECV_BUF, + MSG_LEN, (u8 *)msg); + if (ret < 0) + return 0; + + if (!msg->msg_len) + return 0; + + ret = anx7411_reg_write(ctx->spi_client, CMD_RECV_BUF, 0); + if (ret) + return ret; + + len = msg->msg_len & MSG_LEN_MASK; + crc = checksum(dev, (u8 *)msg, len + HEADER_LEN); + if (crc) { + dev_err(dev, "message error crc(0x%.02x)\n", crc); + return -ERANGE; + } + + return anx7411_parse_cmd(ctx, msg->msg_type, msg->buf, len - 1); +} + +static void anx7411_translate_payload(struct device *dev, __le32 *payload, + u32 *pdo, int nr, const char *type) +{ + int i; + + if (nr > PDO_MAX_OBJECTS) { + dev_err(dev, "nr(%d) exceed PDO_MAX_OBJECTS(%d)\n", + nr, PDO_MAX_OBJECTS); + + return; + } + + for (i = 0; i < nr; i++) + payload[i] = cpu_to_le32(pdo[i]); +} + +static int anx7411_config(struct anx7411_data *ctx) +{ + struct device *dev = &ctx->spi_client->dev; + struct typec_params *typecp = &ctx->typec; + __le32 payload[PDO_MAX_OBJECTS]; + int ret; + + /* Config PD FW work under PD 2.0 */ + ret = anx7411_reg_write(ctx->spi_client, PD_REV_INIT, PD_REV20); + ret |= anx7411_reg_write(ctx->tcpc_client, FW_CTRL_0, + UNSTRUCT_VDM_EN | DELAY_200MS | + VSAFE1 | FRS_EN); + ret |= anx7411_reg_write(ctx->spi_client, FW_CTRL_1, + AUTO_PD_EN | FORCE_SEND_RDO); + + /* Set VBUS current threshold */ + ret |= anx7411_reg_write(ctx->tcpc_client, VBUS_THRESHOLD_H, 0xff); + ret |= anx7411_reg_write(ctx->tcpc_client, VBUS_THRESHOLD_L, 0x03); + + /* Fix dongle compatible issue */ + ret |= anx7411_reg_write(ctx->tcpc_client, FW_PARAM, + anx7411_reg_read(ctx->tcpc_client, FW_PARAM) | + DONGLE_IOP); + ret |= anx7411_reg_write(ctx->spi_client, INT_MASK, 0); + + ret |= anx7411_reg_write(ctx->spi_client, PD_EXT_MSG_CTRL, 0xFF); + if (ret) + return ret; + + if (typecp->caps_flags & HAS_SOURCE_CAP) { + anx7411_translate_payload(dev, payload, typecp->src_pdo, + typecp->src_pdo_nr, "source"); + anx7411_send_msg(ctx, TYPE_SRC_CAP, (u8 *)&payload, + typecp->src_pdo_nr * 4); + anx7411_send_msg(ctx, TYPE_SNK_IDENTITY, snk_identity, + sizeof(snk_identity)); + anx7411_send_msg(ctx, TYPE_SET_SNK_DP_CAP, dp_caps, + sizeof(dp_caps)); + } + + if (typecp->caps_flags & HAS_SINK_CAP) { + anx7411_translate_payload(dev, payload, typecp->sink_pdo, + typecp->sink_pdo_nr, "sink"); + anx7411_send_msg(ctx, TYPE_SNK_CAP, (u8 *)&payload, + typecp->sink_pdo_nr * 4); + } + + if (typecp->caps_flags & HAS_SINK_WATT) { + if (typecp->sink_watt) { + ret |= anx7411_reg_write(ctx->spi_client, MAX_POWER, + typecp->sink_watt); + /* Set min power to 1W */ + ret |= anx7411_reg_write(ctx->spi_client, MIN_POWER, 2); + } + + if (typecp->sink_voltage) + ret |= anx7411_reg_write(ctx->spi_client, MAX_VOLTAGE, + typecp->sink_voltage); + if (ret) + return ret; + } + + if (!typecp->caps_flags) + usleep_range(5000, 6000); + + ctx->fw_version = anx7411_reg_read(ctx->spi_client, FW_VER); + ctx->fw_subversion = anx7411_reg_read(ctx->spi_client, FW_SUBVER); + + return 0; +} + +static void anx7411_chip_standby(struct anx7411_data *ctx) +{ + int ret; + u8 cc1, cc2; + struct device *dev = &ctx->spi_client->dev; + + ret = anx7411_reg_write(ctx->spi_client, OCM_CTRL_0, + anx7411_reg_read(ctx->spi_client, OCM_CTRL_0) | + OCM_RESET); + ret |= anx7411_reg_write(ctx->tcpc_client, ANALOG_CTRL_10, 0x80); + /* Set TCPC to RD and DRP enable */ + cc1 = FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RD); + cc2 = FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RD); + ret |= anx7411_reg_write(ctx->tcpc_client, TCPC_ROLE_CTRL, + TCPC_ROLE_CTRL_DRP | cc1 | cc2); + + /* Send DRP toggle command */ + ret |= anx7411_reg_write(ctx->tcpc_client, TCPC_COMMAND, + TCPC_CMD_LOOK4CONNECTION); + + /* Send TCPC enter standby command */ + ret |= anx7411_reg_write(ctx->tcpc_client, + TCPC_COMMAND, TCPC_CMD_I2C_IDLE); + if (ret) + dev_err(dev, "Chip standby failed\n"); +} + +static void anx7411_work_func(struct work_struct *work) +{ + int ret; + u8 buf[STATUS_LEN]; + u8 int_change; /* Interrupt change */ + u8 int_status; /* Firmware status update */ + u8 alert0, alert1; /* Interrupt alert source */ + struct anx7411_data *ctx = container_of(work, struct anx7411_data, work); + struct device *dev = &ctx->spi_client->dev; + + mutex_lock(&ctx->lock); + + /* Read interrupt change status */ + ret = anx7411_reg_block_read(ctx->spi_client, INT_STS, STATUS_LEN, buf); + if (ret < 0) { + /* Power standby mode, just return */ + goto unlock; + } + int_change = buf[0]; + int_status = buf[1]; + + /* Read alert register */ + ret = anx7411_reg_block_read(ctx->tcpc_client, ALERT_0, STATUS_LEN, buf); + if (ret < 0) + goto unlock; + + alert0 = buf[0]; + alert1 = buf[1]; + + /* Clear interrupt and alert status */ + ret = anx7411_reg_write(ctx->spi_client, INT_STS, 0); + ret |= anx7411_reg_write(ctx->tcpc_client, ALERT_0, alert0); + ret |= anx7411_reg_write(ctx->tcpc_client, ALERT_1, alert1); + if (ret) + goto unlock; + + if (alert1 & INTP_POW_OFF) { + anx7411_partner_unregister_altmode(ctx); + if (anx7411_set_usb_role(ctx, USB_ROLE_NONE)) + dev_err(dev, "Set usb role\n"); + anx7411_unregister_partner(ctx); + ctx->psy_online = ANX7411_PSY_OFFLINE; + ctx->usb_type = POWER_SUPPLY_USB_TYPE_C; + ctx->typec.request_voltage = 0; + ctx->typec.request_current = 0; + power_supply_changed(ctx->psy); + anx7411_chip_standby(ctx); + goto unlock; + } + + if ((alert0 & SOFTWARE_INT) && (int_change & OCM_BOOT_UP)) { + if (anx7411_config(ctx)) + dev_err(dev, "Config failed\n"); + if (anx7411_data_role_detect(ctx)) + dev_err(dev, "set PD data role\n"); + if (anx7411_power_role_detect(ctx)) + dev_err(dev, "set PD power role\n"); + anx7411_set_mux(ctx, SELECT_PIN_ASSIGMENT_C); + } + + if (alert0 & RECEIVED_MSG) + anx7411_process_cmd(ctx); + + ret = (int_status & DATA_ROLE) ? TYPEC_HOST : TYPEC_DEVICE; + if (ctx->typec.data_role != ret) + if (anx7411_data_role_detect(ctx)) + dev_err(dev, "set PD data role\n"); + + ret = (int_status & SINK_STATUS) ? TYPEC_SINK : TYPEC_SOURCE; + if (ctx->typec.power_role != ret) + if (anx7411_power_role_detect(ctx)) + dev_err(dev, "set PD power role\n"); + + if ((alert0 & SOFTWARE_INT) && (int_change & CC_STATUS_CHANGE)) + anx7411_cc_status_detect(ctx); + +unlock: + mutex_unlock(&ctx->lock); +} + +static irqreturn_t anx7411_intr_isr(int irq, void *data) +{ + struct anx7411_data *ctx = (struct anx7411_data *)data; + + queue_work(ctx->workqueue, &ctx->work); + + return IRQ_HANDLED; +} + +static int anx7411_register_i2c_dummy_clients(struct anx7411_data *ctx, + struct i2c_client *client) +{ + int i; + u8 spi_addr; + + for (i = 0; i < ARRAY_SIZE(anx7411_i2c_addr); i++) { + if (client->addr == (anx7411_i2c_addr[i].tcpc_address >> 1)) { + spi_addr = anx7411_i2c_addr[i].spi_address >> 1; + ctx->spi_client = i2c_new_dummy_device(client->adapter, + spi_addr); + if (!IS_ERR(ctx->spi_client)) + return 0; + } + } + + dev_err(&client->dev, "unable to get SPI slave\n"); + return -ENOMEM; +} + +static void anx7411_port_unregister_altmodes(struct typec_altmode **adev) +{ + int i; + + for (i = 0; i < MAX_ALTMODE; i++) + if (adev[i]) { + typec_unregister_altmode(adev[i]); + adev[i] = NULL; + } +} + +static void anx7411_port_unregister(struct typec_params *typecp) +{ + fwnode_handle_put(typecp->caps.fwnode); + anx7411_port_unregister_altmodes(typecp->port_amode); + if (typecp->port) + typec_unregister_port(typecp->port); + if (typecp->role_sw) + usb_role_switch_put(typecp->role_sw); +} + +static int anx7411_usb_mux_set(struct typec_mux_dev *mux, + struct typec_mux_state *state) +{ + struct anx7411_data *ctx = typec_mux_get_drvdata(mux); + struct device *dev = &ctx->spi_client->dev; + int has_dp; + + has_dp = (state->alt && state->alt->svid == USB_TYPEC_DP_SID && + state->alt->mode == USB_TYPEC_DP_MODE); + if (!has_dp) + dev_err(dev, "dp altmode not register\n"); + + return 0; +} + +static int anx7411_usb_set_orientation(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + /* No need set */ + + return 0; +} + +static int anx7411_register_switch(struct anx7411_data *ctx, + struct device *dev, + struct fwnode_handle *fwnode) +{ + struct typec_switch_desc sw_desc = { }; + + sw_desc.fwnode = fwnode; + sw_desc.drvdata = ctx; + sw_desc.name = fwnode_get_name(fwnode); + sw_desc.set = anx7411_usb_set_orientation; + + ctx->typec.typec_switch = typec_switch_register(dev, &sw_desc); + if (IS_ERR(ctx->typec.typec_switch)) { + dev_err(dev, "switch register failed\n"); + return PTR_ERR(ctx->typec.typec_switch); + } + + return 0; +} + +static int anx7411_register_mux(struct anx7411_data *ctx, + struct device *dev, + struct fwnode_handle *fwnode) +{ + struct typec_mux_desc mux_desc = { }; + + mux_desc.fwnode = fwnode; + mux_desc.drvdata = ctx; + mux_desc.name = fwnode_get_name(fwnode); + mux_desc.set = anx7411_usb_mux_set; + + ctx->typec.typec_mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(ctx->typec.typec_mux)) { + dev_err(dev, "mux register failed\n"); + return PTR_ERR(ctx->typec.typec_mux); + } + + return 0; +} + +static void anx7411_unregister_mux(struct anx7411_data *ctx) +{ + if (ctx->typec.typec_mux) { + typec_mux_unregister(ctx->typec.typec_mux); + ctx->typec.typec_mux = NULL; + fwnode_handle_put(ctx->mux_node); + } +} + +static void anx7411_unregister_switch(struct anx7411_data *ctx) +{ + if (ctx->typec.typec_switch) { + typec_switch_unregister(ctx->typec.typec_switch); + ctx->typec.typec_switch = NULL; + fwnode_handle_put(ctx->switch_node); + } +} + +static int anx7411_typec_switch_probe(struct anx7411_data *ctx, + struct device *dev) +{ + int ret; + + ctx->switch_node = device_get_named_child_node(dev, "orientation_switch"); + if (!ctx->switch_node) + return 0; + + ret = anx7411_register_switch(ctx, dev, ctx->switch_node); + if (ret) { + dev_err(dev, "failed register switch"); + fwnode_handle_put(ctx->switch_node); + return ret; + } + + ctx->mux_node = device_get_named_child_node(dev, "mode_switch"); + if (!ctx->mux_node) { + dev_err(dev, "no typec mux exist"); + ret = -ENODEV; + goto unregister_switch; + } + + ret = anx7411_register_mux(ctx, dev, ctx->mux_node); + if (ret) { + dev_err(dev, "failed register mode switch"); + fwnode_handle_put(ctx->mux_node); + ret = -ENODEV; + goto unregister_switch; + } + + return 0; + +unregister_switch: + anx7411_unregister_switch(ctx); + + return ret; +} + +static int anx7411_typec_port_probe(struct anx7411_data *ctx, + struct device *dev) +{ + struct typec_capability *cap = &ctx->typec.caps; + struct typec_params *typecp = &ctx->typec; + struct fwnode_handle *fwnode; + const char *buf; + int ret, i; + + fwnode = device_get_named_child_node(dev, "connector"); + if (!fwnode) + return -EINVAL; + + ret = fwnode_property_read_string(fwnode, "power-role", &buf); + if (ret) { + dev_err(dev, "power-role not found: %d\n", ret); + goto put_fwnode; + } + + ret = typec_find_port_power_role(buf); + if (ret < 0) + goto put_fwnode; + cap->type = ret; + + ret = fwnode_property_read_string(fwnode, "data-role", &buf); + if (ret) { + dev_err(dev, "data-role not found: %d\n", ret); + goto put_fwnode; + } + + ret = typec_find_port_data_role(buf); + if (ret < 0) + goto put_fwnode; + cap->data = ret; + + ret = fwnode_property_read_string(fwnode, "try-power-role", &buf); + if (ret) { + dev_err(dev, "try-power-role not found: %d\n", ret); + goto put_fwnode; + } + + ret = typec_find_power_role(buf); + if (ret < 0) + goto put_fwnode; + cap->prefer_role = ret; + + /* Get source pdos */ + ret = fwnode_property_count_u32(fwnode, "source-pdos"); + if (ret > 0) { + typecp->src_pdo_nr = min_t(u8, ret, PDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(fwnode, "source-pdos", + typecp->src_pdo, + typecp->src_pdo_nr); + if (ret < 0) { + dev_err(dev, "source cap validate failed: %d\n", ret); + goto put_fwnode; + } + + typecp->caps_flags |= HAS_SOURCE_CAP; + } + + ret = fwnode_property_count_u32(fwnode, "sink-pdos"); + if (ret > 0) { + typecp->sink_pdo_nr = min_t(u8, ret, PDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(fwnode, "sink-pdos", + typecp->sink_pdo, + typecp->sink_pdo_nr); + if (ret < 0) { + dev_err(dev, "sink cap validate failed: %d\n", ret); + goto put_fwnode; + } + + for (i = 0; i < typecp->sink_pdo_nr; i++) { + ret = 0; + switch (pdo_type(typecp->sink_pdo[i])) { + case PDO_TYPE_FIXED: + ret = pdo_fixed_voltage(typecp->sink_pdo[i]); + break; + case PDO_TYPE_BATT: + case PDO_TYPE_VAR: + ret = pdo_max_voltage(typecp->sink_pdo[i]); + break; + case PDO_TYPE_APDO: + default: + ret = 0; + break; + } + + /* 100mv per unit */ + typecp->sink_voltage = max(5000, ret) / 100; + } + + typecp->caps_flags |= HAS_SINK_CAP; + } + + if (!fwnode_property_read_u32(fwnode, "op-sink-microwatt", &ret)) { + typecp->sink_watt = ret / 500000; /* 500mw per unit */ + typecp->caps_flags |= HAS_SINK_WATT; + } + + cap->fwnode = fwnode; + + ctx->typec.role_sw = usb_role_switch_get(dev); + if (IS_ERR(ctx->typec.role_sw)) { + dev_err(dev, "USB role switch not found.\n"); + ctx->typec.role_sw = NULL; + } + + ctx->typec.port = typec_register_port(dev, cap); + if (IS_ERR(ctx->typec.port)) { + ret = PTR_ERR(ctx->typec.port); + ctx->typec.port = NULL; + dev_err(dev, "Failed to register type c port %d\n", ret); + goto put_usb_role_switch; + } + + typec_port_register_altmodes(ctx->typec.port, NULL, ctx, + ctx->typec.port_amode, + MAX_ALTMODE); + return 0; + +put_usb_role_switch: + if (ctx->typec.role_sw) + usb_role_switch_put(ctx->typec.role_sw); +put_fwnode: + fwnode_handle_put(fwnode); + + return ret; +} + +static int anx7411_typec_check_connection(struct anx7411_data *ctx) +{ + int ret; + + ret = anx7411_reg_read(ctx->spi_client, FW_VER); + if (ret < 0) + return 0; /* No device attached in typec port */ + + /* Clear interrupt and alert status */ + ret = anx7411_reg_write(ctx->spi_client, INT_STS, 0); + ret |= anx7411_reg_write(ctx->tcpc_client, ALERT_0, 0xFF); + ret |= anx7411_reg_write(ctx->tcpc_client, ALERT_1, 0xFF); + if (ret) + return ret; + + ret = anx7411_cc_status_detect(ctx); + ret |= anx7411_power_role_detect(ctx); + ret |= anx7411_data_role_detect(ctx); + ret |= anx7411_set_mux(ctx, SELECT_PIN_ASSIGMENT_C); + if (ret) + return ret; + + ret = anx7411_send_msg(ctx, TYPE_GET_DP_ALT_ENTER, NULL, 0); + ret |= anx7411_send_msg(ctx, TYPE_GET_DP_DISCOVER_MODES_INFO, NULL, 0); + + return ret; +} + +static int __maybe_unused anx7411_runtime_pm_suspend(struct device *dev) +{ + struct anx7411_data *ctx = dev_get_drvdata(dev); + + mutex_lock(&ctx->lock); + + anx7411_partner_unregister_altmode(ctx); + + if (ctx->typec.partner) + anx7411_unregister_partner(ctx); + + mutex_unlock(&ctx->lock); + + return 0; +} + +static int __maybe_unused anx7411_runtime_pm_resume(struct device *dev) +{ + struct anx7411_data *ctx = dev_get_drvdata(dev); + + mutex_lock(&ctx->lock); + /* Detect PD connection */ + if (anx7411_typec_check_connection(ctx)) + dev_err(dev, "check connection"); + + mutex_unlock(&ctx->lock); + + return 0; +} + +static const struct dev_pm_ops anx7411_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(anx7411_runtime_pm_suspend, + anx7411_runtime_pm_resume, NULL) +}; + +static void anx7411_get_gpio_irq(struct anx7411_data *ctx) +{ + struct device *dev = &ctx->tcpc_client->dev; + + ctx->intp_gpiod = devm_gpiod_get_optional(dev, "interrupt", GPIOD_IN); + if (IS_ERR_OR_NULL(ctx->intp_gpiod)) { + dev_err(dev, "no interrupt gpio property\n"); + return; + } + + ctx->intp_irq = gpiod_to_irq(ctx->intp_gpiod); + if (ctx->intp_irq < 0) + dev_err(dev, "failed to get GPIO IRQ\n"); +} + +static enum power_supply_property anx7411_psy_props[] = { + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static int anx7411_psy_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct anx7411_data *ctx = power_supply_get_drvdata(psy); + int ret = 0; + + if (psp == POWER_SUPPLY_PROP_ONLINE) + ctx->psy_online = val->intval; + else + ret = -EINVAL; + + power_supply_changed(ctx->psy); + return ret; +} + +static int anx7411_psy_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + return psp == POWER_SUPPLY_PROP_ONLINE; +} + +static int anx7411_psy_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct anx7411_data *ctx = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = ctx->usb_type; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ctx->psy_online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = (ctx->psy_online) ? + ctx->typec.request_voltage * 1000 : 0; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = (ctx->psy_online) ? + ctx->typec.request_current * 1000 : 0; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int anx7411_psy_register(struct anx7411_data *ctx) +{ + struct power_supply_desc *psy_desc = &ctx->psy_desc; + struct power_supply_config psy_cfg = {}; + char *psy_name; + + psy_name = devm_kasprintf(ctx->dev, GFP_KERNEL, "anx7411-source-psy-%s", + dev_name(ctx->dev)); + if (!psy_name) + return -ENOMEM; + + psy_desc->name = psy_name; + psy_desc->type = POWER_SUPPLY_TYPE_USB; + psy_desc->usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD) | + BIT(POWER_SUPPLY_USB_TYPE_PD_PPS); + psy_desc->properties = anx7411_psy_props; + psy_desc->num_properties = ARRAY_SIZE(anx7411_psy_props); + + psy_desc->get_property = anx7411_psy_get_prop; + psy_desc->set_property = anx7411_psy_set_prop; + psy_desc->property_is_writeable = anx7411_psy_prop_writeable; + + ctx->usb_type = POWER_SUPPLY_USB_TYPE_C; + ctx->psy = devm_power_supply_register(ctx->dev, psy_desc, &psy_cfg); + + if (IS_ERR(ctx->psy)) + dev_warn(ctx->dev, "unable to register psy\n"); + + return PTR_ERR_OR_ZERO(ctx->psy); +} + +static int anx7411_i2c_probe(struct i2c_client *client) +{ + struct anx7411_data *plat; + struct device *dev = &client->dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + return -ENODEV; + + plat = devm_kzalloc(dev, sizeof(*plat), GFP_KERNEL); + if (!plat) + return -ENOMEM; + + plat->tcpc_client = client; + i2c_set_clientdata(client, plat); + + mutex_init(&plat->lock); + + ret = anx7411_register_i2c_dummy_clients(plat, client); + if (ret) { + dev_err(dev, "fail to reserve I2C bus\n"); + return ret; + } + + ret = anx7411_typec_switch_probe(plat, dev); + if (ret) { + dev_err(dev, "fail to probe typec switch\n"); + goto free_i2c_dummy; + } + + ret = anx7411_typec_port_probe(plat, dev); + if (ret) { + dev_err(dev, "fail to probe typec property.\n"); + ret = -ENODEV; + goto free_typec_switch; + } + + plat->intp_irq = client->irq; + if (!client->irq) + anx7411_get_gpio_irq(plat); + + if (!plat->intp_irq) { + dev_err(dev, "fail to get interrupt IRQ\n"); + ret = -EINVAL; + goto free_typec_port; + } + + plat->dev = dev; + plat->psy_online = ANX7411_PSY_OFFLINE; + ret = anx7411_psy_register(plat); + if (ret) { + dev_err(dev, "register psy\n"); + goto free_typec_port; + } + + INIT_WORK(&plat->work, anx7411_work_func); + plat->workqueue = alloc_workqueue("anx7411_work", + WQ_FREEZABLE | WQ_MEM_RECLAIM | WQ_PERCPU, + 1); + if (!plat->workqueue) { + dev_err(dev, "fail to create work queue\n"); + ret = -ENOMEM; + goto free_typec_port; + } + + ret = devm_request_threaded_irq(dev, plat->intp_irq, + NULL, anx7411_intr_isr, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "anx7411-intp", plat); + if (ret) { + dev_err(dev, "fail to request irq\n"); + goto free_wq; + } + + if (anx7411_typec_check_connection(plat)) + dev_err(dev, "check status\n"); + + ret = devm_pm_runtime_enable(dev); + if (ret) + goto free_wq; + + return 0; + +free_wq: + destroy_workqueue(plat->workqueue); + +free_typec_port: + anx7411_port_unregister(&plat->typec); + +free_typec_switch: + anx7411_unregister_switch(plat); + anx7411_unregister_mux(plat); + +free_i2c_dummy: + i2c_unregister_device(plat->spi_client); + + return ret; +} + +static void anx7411_i2c_remove(struct i2c_client *client) +{ + struct anx7411_data *plat = i2c_get_clientdata(client); + + anx7411_partner_unregister_altmode(plat); + anx7411_unregister_partner(plat); + + if (plat->workqueue) + destroy_workqueue(plat->workqueue); + + i2c_unregister_device(plat->spi_client); + + anx7411_unregister_mux(plat); + + anx7411_unregister_switch(plat); + + anx7411_port_unregister(&plat->typec); +} + +static const struct i2c_device_id anx7411_id[] = { + { .name = "anx7411" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, anx7411_id); + +static const struct of_device_id anx_match_table[] = { + {.compatible = "analogix,anx7411",}, + {}, +}; +MODULE_DEVICE_TABLE(of, anx_match_table); + +static struct i2c_driver anx7411_driver = { + .driver = { + .name = "anx7411", + .of_match_table = anx_match_table, + .pm = &anx7411_pm_ops, + }, + .probe = anx7411_i2c_probe, + .remove = anx7411_i2c_remove, + + .id_table = anx7411_id, +}; + +module_i2c_driver(anx7411_driver); + +MODULE_DESCRIPTION("Anx7411 USB Type-C PD driver"); +MODULE_AUTHOR("Xin Ji "); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1.5"); diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c new file mode 100644 index 000000000..e84b134a3 --- /dev/null +++ b/drivers/usb/typec/bus.c @@ -0,0 +1,572 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Bus for USB Type-C Alternate Modes + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus + */ + +#include + +#include "bus.h" +#include "class.h" +#include "mux.h" +#include "retimer.h" + +static inline int +typec_altmode_set_retimer(struct altmode *alt, unsigned long conf, void *data) +{ + struct typec_retimer_state state; + + if (!alt->retimer) + return 0; + + state.alt = &alt->adev; + state.mode = conf; + state.data = data; + + return typec_retimer_set(alt->retimer, &state); +} + +static inline int +typec_altmode_set_mux(struct altmode *alt, unsigned long conf, void *data) +{ + struct typec_mux_state state; + + if (!alt->mux) + return 0; + + state.alt = &alt->adev; + state.mode = conf; + state.data = data; + + return typec_mux_set(alt->mux, &state); +} + +/* Wrapper to set various Type-C port switches together. */ +static inline int +typec_altmode_set_switches(struct altmode *alt, unsigned long conf, void *data) +{ + int ret; + + ret = typec_altmode_set_retimer(alt, conf, data); + if (ret) + return ret; + + return typec_altmode_set_mux(alt, conf, data); +} + +static int typec_altmode_set_state(struct typec_altmode *adev, + unsigned long conf, void *data) +{ + bool is_port = is_typec_port(adev->dev.parent); + struct altmode *port_altmode; + + port_altmode = is_port ? to_altmode(adev) : to_altmode(adev)->partner; + + return typec_altmode_set_switches(port_altmode, conf, data); +} + +/* -------------------------------------------------------------------------- */ +/* Common API */ + +/** + * typec_altmode_notify - Communication between the OS and alternate mode driver + * @adev: Handle to the alternate mode + * @conf: Alternate mode specific configuration value + * @data: Alternate mode specific data + * + * The primary purpose for this function is to allow the alternate mode drivers + * to tell which pin configuration has been negotiated with the partner. That + * information will then be used for example to configure the muxes. + * Communication to the other direction is also possible, and low level device + * drivers can also send notifications to the alternate mode drivers. The actual + * communication will be specific for every SVID. + */ +int typec_altmode_notify(struct typec_altmode *adev, + unsigned long conf, void *data) +{ + bool is_port; + struct altmode *altmode; + struct altmode *partner; + int ret; + + if (!adev) + return 0; + + altmode = to_altmode(adev); + + if (!altmode->partner) + return -ENODEV; + + is_port = is_typec_port(adev->dev.parent); + partner = altmode->partner; + + ret = typec_altmode_set_switches(is_port ? altmode : partner, conf, data); + if (ret) + return ret; + + if (partner->adev.ops && partner->adev.ops->notify) + return partner->adev.ops->notify(&partner->adev, conf, data); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_altmode_notify); + +/** + * typec_altmode_enter - Enter Mode + * @adev: The alternate mode + * @vdo: VDO for the Enter Mode command + * + * The alternate mode drivers use this function to enter mode. The port drivers + * use this to inform the alternate mode drivers that the partner has initiated + * Enter Mode command. If the alternate mode does not require VDO, @vdo must be + * NULL. + */ +int typec_altmode_enter(struct typec_altmode *adev, u32 *vdo) +{ + struct altmode *partner = to_altmode(adev)->partner; + struct typec_altmode *pdev = &partner->adev; + int ret; + + if (!adev || adev->active) + return 0; + + if (!pdev->ops || !pdev->ops->enter) + return -EOPNOTSUPP; + + if (is_typec_port(pdev->dev.parent) && !pdev->active) + return -EPERM; + + /* Moving to USB Safe State */ + ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE, NULL); + if (ret) + return ret; + + /* Enter Mode */ + return pdev->ops->enter(pdev, vdo); +} +EXPORT_SYMBOL_GPL(typec_altmode_enter); + +/** + * typec_altmode_exit - Exit Mode + * @adev: The alternate mode + * + * The partner of @adev has initiated Exit Mode command. + */ +int typec_altmode_exit(struct typec_altmode *adev) +{ + struct altmode *partner = to_altmode(adev)->partner; + struct typec_altmode *pdev = &partner->adev; + int ret; + + if (!adev || !adev->active) + return 0; + + if (!pdev->ops || !pdev->ops->exit) + return -EOPNOTSUPP; + + /* Moving to USB Safe State */ + ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE, NULL); + if (ret) + return ret; + + /* Exit Mode command */ + return pdev->ops->exit(pdev); +} +EXPORT_SYMBOL_GPL(typec_altmode_exit); + +/** + * typec_altmode_attention - Attention command + * @adev: The alternate mode + * @vdo: VDO for the Attention command + * + * Notifies the partner of @adev about Attention command. + */ +int typec_altmode_attention(struct typec_altmode *adev, u32 vdo) +{ + struct altmode *partner = to_altmode(adev)->partner; + struct typec_altmode *pdev; + + if (!partner) + return -ENODEV; + + pdev = &partner->adev; + + if (pdev->ops && pdev->ops->attention) + pdev->ops->attention(pdev, vdo); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_altmode_attention); + +/** + * typec_altmode_vdm - Send Vendor Defined Messages (VDM) to the partner + * @adev: Alternate mode handle + * @header: VDM Header + * @vdo: Array of Vendor Defined Data Objects + * @count: Number of Data Objects + * + * The alternate mode drivers use this function for SVID specific communication + * with the partner. The port drivers use it to deliver the Structured VDMs + * received from the partners to the alternate mode drivers. + */ +int typec_altmode_vdm(struct typec_altmode *adev, + const u32 header, const u32 *vdo, int count) +{ + struct typec_altmode *pdev; + struct altmode *altmode; + + if (!adev) + return 0; + + altmode = to_altmode(adev); + + if (!altmode->partner) + return -ENODEV; + + pdev = &altmode->partner->adev; + + if (!pdev->ops || !pdev->ops->vdm) + return -EOPNOTSUPP; + + return pdev->ops->vdm(pdev, header, vdo, count); +} +EXPORT_SYMBOL_GPL(typec_altmode_vdm); + +const struct typec_altmode * +typec_altmode_get_partner(struct typec_altmode *adev) +{ + if (!adev || !to_altmode(adev)->partner) + return NULL; + + return &to_altmode(adev)->partner->adev; +} +EXPORT_SYMBOL_GPL(typec_altmode_get_partner); + +/* -------------------------------------------------------------------------- */ +/* API for cable alternate modes */ + +/** + * typec_cable_altmode_enter - Enter Mode + * @adev: The alternate mode + * @sop: Cable plug target for Enter Mode command + * @vdo: VDO for the Enter Mode command + * + * Alternate mode drivers use this function to enter mode on the cable plug. + * If the alternate mode does not require VDO, @vdo must be NULL. + */ +int typec_cable_altmode_enter(struct typec_altmode *adev, enum typec_plug_index sop, u32 *vdo) +{ + struct altmode *partner = to_altmode(adev)->partner; + struct typec_altmode *pdev; + + if (!adev || adev->active) + return 0; + + if (!partner) + return -ENODEV; + + pdev = &partner->adev; + + if (!pdev->active) + return -EPERM; + + if (!pdev->cable_ops || !pdev->cable_ops->enter) + return -EOPNOTSUPP; + + return pdev->cable_ops->enter(pdev, sop, vdo); +} +EXPORT_SYMBOL_GPL(typec_cable_altmode_enter); + +/** + * typec_cable_altmode_exit - Exit Mode + * @adev: The alternate mode + * @sop: Cable plug target for Exit Mode command + * + * The alternate mode drivers use this function to exit mode on the cable plug. + */ +int typec_cable_altmode_exit(struct typec_altmode *adev, enum typec_plug_index sop) +{ + struct altmode *partner = to_altmode(adev)->partner; + struct typec_altmode *pdev; + + if (!adev || !adev->active) + return 0; + + if (!partner) + return -ENODEV; + + pdev = &partner->adev; + + if (!pdev->cable_ops || !pdev->cable_ops->exit) + return -EOPNOTSUPP; + + return pdev->cable_ops->exit(pdev, sop); +} +EXPORT_SYMBOL_GPL(typec_cable_altmode_exit); + +/** + * typec_cable_altmode_vdm - Send Vendor Defined Messages (VDM) between the cable plug and port. + * @adev: Alternate mode handle + * @sop: Cable plug target for VDM + * @header: VDM Header + * @vdo: Array of Vendor Defined Data Objects + * @count: Number of Data Objects + * + * The alternate mode drivers use this function for SVID specific communication + * with the cable plugs. The port drivers use it to deliver the Structured VDMs + * received from the cable plugs to the alternate mode drivers. + */ +int typec_cable_altmode_vdm(struct typec_altmode *adev, enum typec_plug_index sop, + const u32 header, const u32 *vdo, int count) +{ + struct altmode *altmode; + struct typec_altmode *pdev; + + if (!adev) + return 0; + + altmode = to_altmode(adev); + + if (is_typec_plug(adev->dev.parent)) { + if (!altmode->partner) + return -ENODEV; + pdev = &altmode->partner->adev; + } else { + if (!altmode->plug[sop]) + return -ENODEV; + pdev = &altmode->plug[sop]->adev; + } + + if (!pdev->cable_ops || !pdev->cable_ops->vdm) + return -EOPNOTSUPP; + + return pdev->cable_ops->vdm(pdev, sop, header, vdo, count); +} +EXPORT_SYMBOL_GPL(typec_cable_altmode_vdm); + +/* -------------------------------------------------------------------------- */ +/* API for the alternate mode drivers */ + +/** + * typec_altmode_get_plug - Find cable plug alternate mode + * @adev: Handle to partner alternate mode + * @index: Cable plug index + * + * Increment reference count for cable plug alternate mode device. Returns + * handle to the cable plug alternate mode, or NULL if none is found. + */ +struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *adev, + enum typec_plug_index index) +{ + struct altmode *port = to_altmode(adev)->partner; + + if (port->plug[index]) { + get_device(&port->plug[index]->adev.dev); + return &port->plug[index]->adev; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(typec_altmode_get_plug); + +/** + * typec_altmode_put_plug - Decrement cable plug alternate mode reference count + * @plug: Handle to the cable plug alternate mode + */ +void typec_altmode_put_plug(struct typec_altmode *plug) +{ + if (plug) + put_device(&plug->dev); +} +EXPORT_SYMBOL_GPL(typec_altmode_put_plug); + +int __typec_altmode_register_driver(struct typec_altmode_driver *drv, + struct module *module) +{ + if (!drv->probe) + return -EINVAL; + + drv->driver.owner = module; + drv->driver.bus = &typec_bus; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__typec_altmode_register_driver); + +void typec_altmode_unregister_driver(struct typec_altmode_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(typec_altmode_unregister_driver); + +/* -------------------------------------------------------------------------- */ +/* API for the port drivers */ + +/** + * typec_match_altmode - Match SVID and mode to an array of alternate modes + * @altmodes: Array of alternate modes + * @n: Number of elements in the array, or -1 for NULL terminated arrays + * @svid: Standard or Vendor ID to match with + * @mode: Mode to match with + * + * Return pointer to an alternate mode with SVID matching @svid, or NULL when no + * match is found. + */ +struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes, + size_t n, u16 svid, u8 mode) +{ + int i; + + for (i = 0; i < n; i++) { + if (!altmodes[i]) + break; + if (altmodes[i]->svid == svid && altmodes[i]->mode == mode) + return altmodes[i]; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(typec_match_altmode); + +/* -------------------------------------------------------------------------- */ + +static ssize_t +description_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct typec_altmode *alt = to_typec_altmode(dev); + + return sprintf(buf, "%s\n", alt->desc ? alt->desc : ""); +} +static DEVICE_ATTR_RO(description); + +static struct attribute *typec_attrs[] = { + &dev_attr_description.attr, + NULL +}; + +static umode_t typec_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + if (is_typec_partner_altmode(kobj_to_dev(kobj))) + return attr->mode; + return 0; +} + +static const struct attribute_group typec_group = { + .is_visible = typec_is_visible, + .attrs = typec_attrs, +}; + +static const struct attribute_group *typec_groups[] = { + &typec_group, + NULL +}; + +static int typec_match(struct device *dev, const struct device_driver *driver) +{ + const struct typec_altmode_driver *drv = to_altmode_driver(driver); + struct typec_altmode *altmode = to_typec_altmode(dev); + const struct typec_device_id *id; + + if (!is_typec_partner_altmode(dev)) + return 0; + + for (id = drv->id_table; id->svid; id++) + if (id->svid == altmode->svid) + return 1; + return 0; +} + +static int typec_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + const struct typec_altmode *altmode = to_typec_altmode(dev); + + if (!is_typec_partner_altmode(dev)) + return 0; + + if (add_uevent_var(env, "SVID=%04X", altmode->svid)) + return -ENOMEM; + + if (add_uevent_var(env, "MODE=%u", altmode->mode)) + return -ENOMEM; + + return add_uevent_var(env, "MODALIAS=typec:id%04X", altmode->svid); +} + +static int typec_altmode_create_links(struct altmode *alt) +{ + struct device *port_dev = &alt->partner->adev.dev; + struct device *dev = &alt->adev.dev; + int err; + + err = sysfs_create_link(&dev->kobj, &port_dev->kobj, "port"); + if (err) + return err; + + err = sysfs_create_link(&port_dev->kobj, &dev->kobj, "partner"); + if (err) + sysfs_remove_link(&dev->kobj, "port"); + + return err; +} + +static void typec_altmode_remove_links(struct altmode *alt) +{ + sysfs_remove_link(&alt->partner->adev.dev.kobj, "partner"); + sysfs_remove_link(&alt->adev.dev.kobj, "port"); +} + +static int typec_probe(struct device *dev) +{ + struct typec_altmode_driver *drv = to_altmode_driver(dev->driver); + struct typec_altmode *adev = to_typec_altmode(dev); + struct altmode *altmode = to_altmode(adev); + int ret; + + /* Fail if the port does not support the alternate mode */ + if (!altmode->partner) + return -ENODEV; + + ret = typec_altmode_create_links(altmode); + if (ret) { + dev_warn(dev, "failed to create symlinks\n"); + return ret; + } + + ret = drv->probe(adev); + if (ret) + typec_altmode_remove_links(altmode); + + return ret; +} + +static void typec_remove(struct device *dev) +{ + struct typec_altmode_driver *drv = to_altmode_driver(dev->driver); + struct typec_altmode *adev = to_typec_altmode(dev); + struct altmode *altmode = to_altmode(adev); + + typec_altmode_remove_links(altmode); + + if (drv->remove) + drv->remove(to_typec_altmode(dev)); + + if (adev->active) { + WARN_ON(typec_altmode_set_state(adev, TYPEC_STATE_SAFE, NULL)); + typec_altmode_update_active(adev, false); + } + + adev->desc = NULL; + adev->ops = NULL; +} + +const struct bus_type typec_bus = { + .name = "typec", + .dev_groups = typec_groups, + .match = typec_match, + .uevent = typec_uevent, + .probe = typec_probe, + .remove = typec_remove, +}; +EXPORT_SYMBOL_GPL(typec_bus); diff --git a/drivers/usb/typec/bus.h b/drivers/usb/typec/bus.h new file mode 100644 index 000000000..7df5deb1d --- /dev/null +++ b/drivers/usb/typec/bus.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __USB_TYPEC_ALTMODE_H__ +#define __USB_TYPEC_ALTMODE_H__ + +#include + +struct typec_mux; +struct typec_retimer; + +struct altmode { + unsigned int id; + struct typec_altmode adev; + struct typec_mux *mux; + struct typec_retimer *retimer; + + enum typec_port_data roles; + + struct attribute *attrs[5]; + char group_name[8]; + struct attribute_group group; + const struct attribute_group *groups[2]; + + struct altmode *partner; + struct altmode *plug[2]; +}; + +#define to_altmode(d) container_of(d, struct altmode, adev) + +#endif /* __USB_TYPEC_ALTMODE_H__ */ diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c new file mode 100644 index 000000000..54c7810b5 --- /dev/null +++ b/drivers/usb/typec/class.c @@ -0,0 +1,2965 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Type-C Connector Class + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bus.h" +#include "class.h" +#include "pd.h" + +static DEFINE_IDA(typec_index_ida); + +const struct class typec_class = { + .name = "typec", +}; + +/* ------------------------------------------------------------------------- */ +/* Common attributes */ + +static const char * const typec_accessory_modes[] = { + [TYPEC_ACCESSORY_NONE] = "none", + [TYPEC_ACCESSORY_AUDIO] = "analog_audio", + [TYPEC_ACCESSORY_DEBUG] = "debug", +}; + +/* Product types defined in USB PD Specification R3.0 V2.0 */ +static const char * const product_type_ufp[8] = { + [IDH_PTYPE_NOT_UFP] = "not_ufp", + [IDH_PTYPE_HUB] = "hub", + [IDH_PTYPE_PERIPH] = "peripheral", + [IDH_PTYPE_PSD] = "psd", + [IDH_PTYPE_AMA] = "ama", +}; + +static const char * const product_type_dfp[8] = { + [IDH_PTYPE_NOT_DFP] = "not_dfp", + [IDH_PTYPE_DFP_HUB] = "hub", + [IDH_PTYPE_DFP_HOST] = "host", + [IDH_PTYPE_DFP_PB] = "power_brick", +}; + +static const char * const product_type_cable[8] = { + [IDH_PTYPE_NOT_CABLE] = "not_cable", + [IDH_PTYPE_PCABLE] = "passive", + [IDH_PTYPE_ACABLE] = "active", + [IDH_PTYPE_VPD] = "vpd", +}; + +static struct usb_pd_identity *get_pd_identity(struct device *dev) +{ + if (is_typec_partner(dev)) { + struct typec_partner *partner = to_typec_partner(dev); + + return partner->identity; + } else if (is_typec_cable(dev)) { + struct typec_cable *cable = to_typec_cable(dev); + + return cable->identity; + } + return NULL; +} + +static const char *get_pd_product_type(struct device *dev) +{ + struct typec_port *port = to_typec_port(dev->parent); + struct usb_pd_identity *id = get_pd_identity(dev); + const char *ptype = NULL; + + if (is_typec_partner(dev)) { + if (!id) + return NULL; + + if (port->data_role == TYPEC_HOST) + ptype = product_type_ufp[PD_IDH_PTYPE(id->id_header)]; + else + ptype = product_type_dfp[PD_IDH_DFP_PTYPE(id->id_header)]; + } else if (is_typec_cable(dev)) { + if (id) + ptype = product_type_cable[PD_IDH_PTYPE(id->id_header)]; + else + ptype = to_typec_cable(dev)->active ? + product_type_cable[IDH_PTYPE_ACABLE] : + product_type_cable[IDH_PTYPE_PCABLE]; + } + + return ptype; +} + +static ssize_t id_header_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sprintf(buf, "0x%08x\n", id->id_header); +} +static DEVICE_ATTR_RO(id_header); + +static ssize_t cert_stat_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sprintf(buf, "0x%08x\n", id->cert_stat); +} +static DEVICE_ATTR_RO(cert_stat); + +static ssize_t product_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sprintf(buf, "0x%08x\n", id->product); +} +static DEVICE_ATTR_RO(product); + +static ssize_t product_type_vdo1_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sysfs_emit(buf, "0x%08x\n", id->vdo[0]); +} +static DEVICE_ATTR_RO(product_type_vdo1); + +static ssize_t product_type_vdo2_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sysfs_emit(buf, "0x%08x\n", id->vdo[1]); +} +static DEVICE_ATTR_RO(product_type_vdo2); + +static ssize_t product_type_vdo3_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sysfs_emit(buf, "0x%08x\n", id->vdo[2]); +} +static DEVICE_ATTR_RO(product_type_vdo3); + +static struct attribute *usb_pd_id_attrs[] = { + &dev_attr_id_header.attr, + &dev_attr_cert_stat.attr, + &dev_attr_product.attr, + &dev_attr_product_type_vdo1.attr, + &dev_attr_product_type_vdo2.attr, + &dev_attr_product_type_vdo3.attr, + NULL +}; + +static const struct attribute_group usb_pd_id_group = { + .name = "identity", + .attrs = usb_pd_id_attrs, +}; + +static const struct attribute_group *usb_pd_id_groups[] = { + &usb_pd_id_group, + NULL, +}; + +static void typec_product_type_notify(struct device *dev) +{ + char *envp[2] = { }; + const char *ptype; + + ptype = get_pd_product_type(dev); + if (!ptype) + return; + + sysfs_notify(&dev->kobj, NULL, "type"); + + envp[0] = kasprintf(GFP_KERNEL, "PRODUCT_TYPE=%s", ptype); + if (!envp[0]) + return; + + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); + kfree(envp[0]); +} + +static void typec_report_identity(struct device *dev) +{ + sysfs_notify(&dev->kobj, "identity", "id_header"); + sysfs_notify(&dev->kobj, "identity", "cert_stat"); + sysfs_notify(&dev->kobj, "identity", "product"); + sysfs_notify(&dev->kobj, "identity", "product_type_vdo1"); + sysfs_notify(&dev->kobj, "identity", "product_type_vdo2"); + sysfs_notify(&dev->kobj, "identity", "product_type_vdo3"); + typec_product_type_notify(dev); +} + +static ssize_t +type_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + const char *ptype; + + ptype = get_pd_product_type(dev); + if (!ptype) + return 0; + + return sysfs_emit(buf, "%s\n", ptype); +} +static DEVICE_ATTR_RO(type); + +static ssize_t usb_power_delivery_revision_show(struct device *dev, + struct device_attribute *attr, + char *buf); +static DEVICE_ATTR_RO(usb_power_delivery_revision); + +static const char * const usb_modes[] = { + [USB_MODE_NONE] = "none", + [USB_MODE_USB2] = "usb2", + [USB_MODE_USB3] = "usb3", + [USB_MODE_USB4] = "usb4" +}; + +/* ------------------------------------------------------------------------- */ +/* Alternate Modes */ + +static int altmode_match(struct device *dev, const void *data) +{ + struct typec_altmode *adev = to_typec_altmode(dev); + const struct typec_device_id *id = data; + + if (!is_typec_port_altmode(dev)) + return 0; + + return (adev->svid == id->svid); +} + +static void typec_altmode_set_partner(struct altmode *altmode) +{ + struct typec_altmode *adev = &altmode->adev; + struct typec_device_id id = { adev->svid }; + struct typec_port *port = typec_altmode2port(adev); + struct altmode *partner; + struct device *dev; + + dev = device_find_child(&port->dev, &id, altmode_match); + if (!dev) + return; + + /* Bind the port alt mode to the partner/plug alt mode. */ + partner = to_altmode(to_typec_altmode(dev)); + altmode->partner = partner; + + /* Bind the partner/plug alt mode to the port alt mode. */ + if (is_typec_plug(adev->dev.parent)) { + struct typec_plug *plug = to_typec_plug(adev->dev.parent); + + partner->plug[plug->index] = altmode; + } else { + partner->partner = altmode; + } +} + +static void typec_altmode_put_partner(struct altmode *altmode) +{ + struct altmode *partner = altmode->partner; + struct typec_altmode *adev; + struct typec_altmode *partner_adev; + + if (!partner) + return; + + adev = &altmode->adev; + partner_adev = &partner->adev; + + if (is_typec_plug(adev->dev.parent)) { + struct typec_plug *plug = to_typec_plug(adev->dev.parent); + + partner->plug[plug->index] = NULL; + } else { + partner->partner = NULL; + } + put_device(&partner_adev->dev); +} + +/** + * typec_altmode_update_active - Report Enter/Exit mode + * @adev: Handle to the alternate mode + * @active: True when the mode has been entered + * + * If a partner or cable plug executes Enter/Exit Mode command successfully, the + * drivers use this routine to report the updated state of the mode. + */ +void typec_altmode_update_active(struct typec_altmode *adev, bool active) +{ + char dir[6]; + + if (adev->active == active) + return; + + if (!is_typec_port(adev->dev.parent) && adev->dev.driver) { + if (!active) + module_put(adev->dev.driver->owner); + else + WARN_ON(!try_module_get(adev->dev.driver->owner)); + } + + adev->active = active; + snprintf(dir, sizeof(dir), "mode%d", adev->mode); + sysfs_notify(&adev->dev.kobj, dir, "active"); + sysfs_notify(&adev->dev.kobj, NULL, "active"); + kobject_uevent(&adev->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_altmode_update_active); + +/** + * typec_altmode2port - Alternate Mode to USB Type-C port + * @alt: The Alternate Mode + * + * Returns handle to the port that a cable plug or partner with @alt is + * connected to. + */ +struct typec_port *typec_altmode2port(struct typec_altmode *alt) +{ + if (is_typec_plug(alt->dev.parent)) + return to_typec_port(alt->dev.parent->parent->parent); + if (is_typec_partner(alt->dev.parent)) + return to_typec_port(alt->dev.parent->parent); + if (is_typec_port(alt->dev.parent)) + return to_typec_port(alt->dev.parent); + + return NULL; +} +EXPORT_SYMBOL_GPL(typec_altmode2port); + +static ssize_t +vdo_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct typec_altmode *alt = to_typec_altmode(dev); + + return sprintf(buf, "0x%08x\n", alt->vdo); +} +static DEVICE_ATTR_RO(vdo); + +static ssize_t +description_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct typec_altmode *alt = to_typec_altmode(dev); + + return sprintf(buf, "%s\n", alt->desc ? alt->desc : ""); +} +static DEVICE_ATTR_RO(description); + +static ssize_t +active_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct typec_altmode *alt = to_typec_altmode(dev); + + return sprintf(buf, "%s\n", str_yes_no(alt->active)); +} + +static ssize_t active_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_altmode *adev = to_typec_altmode(dev); + struct altmode *altmode = to_altmode(adev); + bool enter; + int ret; + + ret = kstrtobool(buf, &enter); + if (ret) + return ret; + + if (adev->active == enter) + return size; + + if (is_typec_port(adev->dev.parent)) { + typec_altmode_update_active(adev, enter); + + /* Make sure that the partner exits the mode before disabling */ + if (altmode->partner && !enter && altmode->partner->adev.active) + typec_altmode_exit(&altmode->partner->adev); + } else if (altmode->partner) { + if (enter && !altmode->partner->adev.active) { + dev_warn(dev, "port has the mode disabled\n"); + return -EPERM; + } + } + + /* Note: If there is no driver, the mode will not be entered */ + if (adev->ops && adev->ops->activate) { + ret = adev->ops->activate(adev, enter); + if (ret) + return ret; + } + + return size; +} +static DEVICE_ATTR_RW(active); + +static ssize_t +supported_roles_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct altmode *alt = to_altmode(to_typec_altmode(dev)); + ssize_t ret; + + switch (alt->roles) { + case TYPEC_PORT_SRC: + ret = sprintf(buf, "source\n"); + break; + case TYPEC_PORT_SNK: + ret = sprintf(buf, "sink\n"); + break; + case TYPEC_PORT_DRP: + default: + ret = sprintf(buf, "source sink\n"); + break; + } + return ret; +} +static DEVICE_ATTR_RO(supported_roles); + +static ssize_t +mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct typec_altmode *adev = to_typec_altmode(dev); + + return sprintf(buf, "%u\n", adev->mode); +} +static DEVICE_ATTR_RO(mode); + +static ssize_t +svid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct typec_altmode *adev = to_typec_altmode(dev); + + return sprintf(buf, "%04x\n", adev->svid); +} +static DEVICE_ATTR_RO(svid); + +static int increment_duplicated_priority(struct device *dev, void *data) +{ + if (is_typec_port_altmode(dev)) { + struct typec_altmode **alt_target = (struct typec_altmode **)data; + struct typec_altmode *alt = to_typec_altmode(dev); + + if (alt != *alt_target && alt->priority == (*alt_target)->priority) { + alt->priority++; + *alt_target = alt; + return 1; + } + } + return 0; +} + +static int find_duplicated_priority(struct device *dev, void *data) +{ + if (is_typec_port_altmode(dev)) { + struct typec_altmode **alt_target = (struct typec_altmode **)data; + struct typec_altmode *alt = to_typec_altmode(dev); + + if (alt != *alt_target && alt->priority == (*alt_target)->priority) + return 1; + } + return 0; +} + +static int typec_mode_set_priority(struct typec_altmode *alt, const u8 priority) +{ + struct typec_port *port = to_typec_port(alt->dev.parent); + const u8 old_priority = alt->priority; + int res = 1; + + alt->priority = priority; + while (res) { + res = device_for_each_child(&port->dev, &alt, find_duplicated_priority); + if (res) { + alt->priority++; + if (alt->priority == 0) { + alt->priority = old_priority; + return -EOVERFLOW; + } + } + } + + res = 1; + alt->priority = priority; + while (res) + res = device_for_each_child(&port->dev, &alt, + increment_duplicated_priority); + + return 0; +} + +static ssize_t priority_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + u8 val; + int err = kstrtou8(buf, 10, &val); + + if (!err) + err = typec_mode_set_priority(to_typec_altmode(dev), val); + + if (!err) + return size; + return err; +} + +static ssize_t priority_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", to_typec_altmode(dev)->priority); +} +static DEVICE_ATTR_RW(priority); + +static struct attribute *typec_altmode_attrs[] = { + &dev_attr_active.attr, + &dev_attr_mode.attr, + &dev_attr_svid.attr, + &dev_attr_vdo.attr, + &dev_attr_priority.attr, + NULL +}; + +static umode_t typec_altmode_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct typec_altmode *adev = to_typec_altmode(kobj_to_dev(kobj)); + struct typec_port *port = typec_altmode2port(adev); + + if (attr == &dev_attr_active.attr) { + if (!is_typec_port(adev->dev.parent)) { + if (!port->mode_control || !adev->ops || !adev->ops->activate) + return 0444; + } + } else if (attr == &dev_attr_priority.attr) { + if (!is_typec_port(adev->dev.parent) || !port->mode_control) + return 0; + } + + return attr->mode; +} + +static const struct attribute_group typec_altmode_group = { + .is_visible = typec_altmode_attr_is_visible, + .attrs = typec_altmode_attrs, +}; + +static const struct attribute_group *typec_altmode_groups[] = { + &typec_altmode_group, + NULL +}; + +/** + * typec_altmode_set_ops - Set ops for altmode + * @adev: Handle to the alternate mode + * @ops: Ops for the alternate mode + * + * After setting ops, attribute visiblity needs to be refreshed if the alternate + * mode can be activated. + */ +void typec_altmode_set_ops(struct typec_altmode *adev, + const struct typec_altmode_ops *ops) +{ + adev->ops = ops; + sysfs_update_group(&adev->dev.kobj, &typec_altmode_group); +} +EXPORT_SYMBOL_GPL(typec_altmode_set_ops); + +static int altmode_id_get(struct device *dev) +{ + struct ida *ids; + + if (is_typec_partner(dev)) + ids = &to_typec_partner(dev)->mode_ids; + else if (is_typec_plug(dev)) + ids = &to_typec_plug(dev)->mode_ids; + else + ids = &to_typec_port(dev)->mode_ids; + + return ida_alloc(ids, GFP_KERNEL); +} + +static void altmode_id_remove(struct device *dev, int id) +{ + struct ida *ids; + + if (is_typec_partner(dev)) + ids = &to_typec_partner(dev)->mode_ids; + else if (is_typec_plug(dev)) + ids = &to_typec_plug(dev)->mode_ids; + else + ids = &to_typec_port(dev)->mode_ids; + + ida_free(ids, id); +} + +static void typec_altmode_release(struct device *dev) +{ + struct altmode *alt = to_altmode(to_typec_altmode(dev)); + + if (!is_typec_port(dev->parent)) + typec_altmode_put_partner(alt); + + altmode_id_remove(alt->adev.dev.parent, alt->id); + put_device(alt->adev.dev.parent); + kfree(alt); +} + +const struct device_type typec_port_altmode_dev_type = { + .name = "typec_port_alternate_mode", + .groups = typec_altmode_groups, + .release = typec_altmode_release, +}; +EXPORT_SYMBOL_GPL(typec_port_altmode_dev_type); + +const struct device_type typec_plug_altmode_dev_type = { + .name = "typec_plug_alternate_mode", + .groups = typec_altmode_groups, + .release = typec_altmode_release, +}; +EXPORT_SYMBOL_GPL(typec_plug_altmode_dev_type); + +const struct device_type typec_partner_altmode_dev_type = { + .name = "typec_partner_alternate_mode", + .groups = typec_altmode_groups, + .release = typec_altmode_release, +}; +EXPORT_SYMBOL_GPL(typec_partner_altmode_dev_type); + +static struct typec_altmode * +typec_register_altmode(struct device *parent, + const struct typec_altmode_desc *desc, + const struct device_type *type) +{ + unsigned int id = altmode_id_get(parent); + bool is_port = is_typec_port(parent); + struct altmode *alt; + int ret; + + alt = kzalloc_obj(*alt); + if (!alt) { + altmode_id_remove(parent, id); + return ERR_PTR(-ENOMEM); + } + + alt->adev.svid = desc->svid; + alt->adev.mode = desc->mode; + alt->adev.vdo = desc->vdo; + alt->adev.mode_selection = desc->mode_selection; + alt->roles = desc->roles; + alt->id = id; + + alt->attrs[0] = &dev_attr_vdo.attr; + alt->attrs[1] = &dev_attr_description.attr; + alt->attrs[2] = &dev_attr_active.attr; + + if (is_port) { + alt->attrs[3] = &dev_attr_supported_roles.attr; + alt->adev.active = !desc->inactive; /* Enabled by default */ + } + + sprintf(alt->group_name, "mode%d", desc->mode); + alt->group.name = alt->group_name; + alt->group.attrs = alt->attrs; + alt->groups[0] = &alt->group; + + alt->adev.dev.parent = parent; + alt->adev.dev.groups = alt->groups; + alt->adev.dev.type = type; + dev_set_name(&alt->adev.dev, "%s.%u", dev_name(parent), id); + + get_device(alt->adev.dev.parent); + + /* Link partners and plugs with the ports */ + if (!is_port) + typec_altmode_set_partner(alt); + + alt->adev.dev.bus = &typec_bus; + + ret = device_register(&alt->adev.dev); + if (ret) { + dev_err(parent, "failed to register alternate mode (%d)\n", + ret); + put_device(&alt->adev.dev); + return ERR_PTR(ret); + } + + return &alt->adev; +} + +/** + * typec_unregister_altmode - Unregister Alternate Mode + * @adev: The alternate mode to be unregistered + * + * Unregister device created with typec_partner_register_altmode(), + * typec_plug_register_altmode() or typec_port_register_altmode(). + */ +void typec_unregister_altmode(struct typec_altmode *adev) +{ + if (IS_ERR_OR_NULL(adev)) + return; + typec_retimer_put(to_altmode(adev)->retimer); + typec_mux_put(to_altmode(adev)->mux); + device_unregister(&adev->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_altmode); + +/* ------------------------------------------------------------------------- */ +/* Type-C Partners */ + +/** + * typec_partner_set_usb_mode - Assign active USB Mode for the partner + * @partner: USB Type-C partner + * @mode: USB Mode (USB2, USB3 or USB4) + * + * The port drivers can use this function to assign the active USB Mode to + * @partner. The USB Mode can change for example due to Data Reset. + */ +void typec_partner_set_usb_mode(struct typec_partner *partner, enum usb_mode mode) +{ + if (!partner || partner->usb_mode == mode) + return; + + partner->usb_capability |= BIT(mode - 1); + partner->usb_mode = mode; + sysfs_notify(&partner->dev.kobj, NULL, "usb_mode"); +} +EXPORT_SYMBOL_GPL(typec_partner_set_usb_mode); + +static ssize_t +usb_mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct typec_partner *partner = to_typec_partner(dev); + int len = 0; + int i; + + for (i = USB_MODE_USB2; i < USB_MODE_USB4 + 1; i++) { + if (!(BIT(i - 1) & partner->usb_capability)) + continue; + + if (i == partner->usb_mode) + len += sysfs_emit_at(buf, len, "[%s] ", usb_modes[i]); + else + len += sysfs_emit_at(buf, len, "%s ", usb_modes[i]); + } + + sysfs_emit_at(buf, len - 1, "\n"); + + return len; +} + +static ssize_t usb_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_partner *partner = to_typec_partner(dev); + struct typec_port *port = to_typec_port(dev->parent); + int mode; + int ret; + + if (!port->ops || !port->ops->enter_usb_mode) + return -EOPNOTSUPP; + + mode = sysfs_match_string(usb_modes, buf); + if (mode < 0) + return mode; + + if (mode == partner->usb_mode) + return size; + + ret = port->ops->enter_usb_mode(port, mode); + if (ret) + return ret; + + typec_partner_set_usb_mode(partner, mode); + + return size; +} +static DEVICE_ATTR_RW(usb_mode); + +static ssize_t accessory_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_partner *p = to_typec_partner(dev); + + return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]); +} +static DEVICE_ATTR_RO(accessory_mode); + +static ssize_t supports_usb_power_delivery_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_partner *p = to_typec_partner(dev); + + return sprintf(buf, "%s\n", str_yes_no(p->usb_pd)); +} +static DEVICE_ATTR_RO(supports_usb_power_delivery); + +static ssize_t number_of_alternate_modes_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_partner *partner; + struct typec_plug *plug; + int num_altmodes; + + if (is_typec_partner(dev)) { + partner = to_typec_partner(dev); + num_altmodes = partner->num_altmodes; + } else if (is_typec_plug(dev)) { + plug = to_typec_plug(dev); + num_altmodes = plug->num_altmodes; + } else { + return 0; + } + + return sysfs_emit(buf, "%d\n", num_altmodes); +} +static DEVICE_ATTR_RO(number_of_alternate_modes); + +static struct attribute *typec_partner_attrs[] = { + &dev_attr_accessory_mode.attr, + &dev_attr_supports_usb_power_delivery.attr, + &dev_attr_number_of_alternate_modes.attr, + &dev_attr_type.attr, + &dev_attr_usb_mode.attr, + &dev_attr_usb_power_delivery_revision.attr, + NULL +}; + +static umode_t typec_partner_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct typec_partner *partner = to_typec_partner(kobj_to_dev(kobj)); + struct typec_port *port = to_typec_port(partner->dev.parent); + + if (attr == &dev_attr_usb_mode.attr) { + if (!partner->usb_capability) + return 0; + if (!port->ops || !port->ops->enter_usb_mode) + return 0444; + } + + if (attr == &dev_attr_number_of_alternate_modes.attr) { + if (partner->num_altmodes < 0) + return 0; + } + + if (attr == &dev_attr_type.attr) + if (!get_pd_product_type(kobj_to_dev(kobj))) + return 0; + + return attr->mode; +} + +static const struct attribute_group typec_partner_group = { + .is_visible = typec_partner_attr_is_visible, + .attrs = typec_partner_attrs +}; + +static const struct attribute_group *typec_partner_groups[] = { + &typec_partner_group, + NULL +}; + +static void typec_partner_release(struct device *dev) +{ + struct typec_partner *partner = to_typec_partner(dev); + + ida_destroy(&partner->mode_ids); + kfree(partner); +} + +const struct device_type typec_partner_dev_type = { + .name = "typec_partner", + .groups = typec_partner_groups, + .release = typec_partner_release, +}; + +static void typec_partner_link_device(struct typec_partner *partner, struct device *dev) +{ + int ret; + + ret = sysfs_create_link(&dev->kobj, &partner->dev.kobj, "typec"); + if (ret) + return; + + ret = sysfs_create_link(&partner->dev.kobj, &dev->kobj, dev_name(dev)); + if (ret) { + sysfs_remove_link(&dev->kobj, "typec"); + return; + } + + if (partner->attach) + partner->attach(partner, dev); +} + +static void typec_partner_unlink_device(struct typec_partner *partner, struct device *dev) +{ + sysfs_remove_link(&partner->dev.kobj, dev_name(dev)); + sysfs_remove_link(&dev->kobj, "typec"); + + if (partner->deattach) + partner->deattach(partner, dev); +} + +/** + * typec_partner_set_identity - Report result from Discover Identity command + * @partner: The partner updated identity values + * + * This routine is used to report that the result of Discover Identity USB power + * delivery command has become available. + */ +int typec_partner_set_identity(struct typec_partner *partner) +{ + u8 usb_capability = partner->usb_capability; + struct device *dev = &partner->dev; + struct usb_pd_identity *id; + + id = get_pd_identity(dev); + if (!id) + return -EINVAL; + + if (to_typec_port(dev->parent)->data_role == TYPEC_HOST) { + u32 devcap = PD_VDO_UFP_DEVCAP(id->vdo[0]); + + if (devcap & (DEV_USB2_CAPABLE | DEV_USB2_BILLBOARD)) + usb_capability |= USB_CAPABILITY_USB2; + if (devcap & DEV_USB3_CAPABLE) + usb_capability |= USB_CAPABILITY_USB3; + if (devcap & DEV_USB4_CAPABLE) + usb_capability |= USB_CAPABILITY_USB4; + } else { + usb_capability = PD_VDO_DFP_HOSTCAP(id->vdo[0]); + } + + if (partner->usb_capability != usb_capability) { + partner->usb_capability = usb_capability; + sysfs_notify(&dev->kobj, NULL, "usb_mode"); + } + + typec_report_identity(dev); + return 0; +} +EXPORT_SYMBOL_GPL(typec_partner_set_identity); + +/** + * typec_partner_set_pd_revision - Set the PD revision supported by the partner + * @partner: The partner to be updated. + * @pd_revision: USB Power Delivery Specification Revision supported by partner + * + * This routine is used to report that the PD revision of the port partner has + * become available. + */ +void typec_partner_set_pd_revision(struct typec_partner *partner, u16 pd_revision) +{ + if (partner->pd_revision == pd_revision) + return; + + partner->pd_revision = pd_revision; + sysfs_notify(&partner->dev.kobj, NULL, "usb_power_delivery_revision"); + if (pd_revision != 0 && !partner->usb_pd) { + partner->usb_pd = 1; + sysfs_notify(&partner->dev.kobj, NULL, + "supports_usb_power_delivery"); + } + kobject_uevent(&partner->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_partner_set_pd_revision); + +/** + * typec_partner_set_usb_power_delivery - Declare USB Power Delivery Contract. + * @partner: The partner device. + * @pd: The USB PD instance. + * + * This routine can be used to declare USB Power Delivery Contract with @partner + * by linking @partner to @pd which contains the objects that were used during the + * negotiation of the contract. + * + * If @pd is NULL, the link is removed and the contract with @partner has ended. + */ +int typec_partner_set_usb_power_delivery(struct typec_partner *partner, + struct usb_power_delivery *pd) +{ + int ret; + + if (IS_ERR_OR_NULL(partner) || partner->pd == pd) + return 0; + + if (pd) { + ret = usb_power_delivery_link_device(pd, &partner->dev); + if (ret) + return ret; + } else { + usb_power_delivery_unlink_device(partner->pd, &partner->dev); + } + + partner->pd = pd; + + return 0; +} +EXPORT_SYMBOL_GPL(typec_partner_set_usb_power_delivery); + +/** + * typec_partner_set_num_altmodes - Set the number of available partner altmodes + * @partner: The partner to be updated. + * @num_altmodes: The number of altmodes we want to specify as available. + * + * This routine is used to report the number of alternate modes supported by the + * partner. This value is *not* enforced in alternate mode registration routines. + * + * @partner.num_altmodes is set to -1 on partner registration, denoting that + * a valid value has not been set for it yet. + * + * Returns 0 on success or negative error number on failure. + */ +int typec_partner_set_num_altmodes(struct typec_partner *partner, int num_altmodes) +{ + int ret; + + if (num_altmodes < 0) + return -EINVAL; + + partner->num_altmodes = num_altmodes; + ret = sysfs_update_group(&partner->dev.kobj, &typec_partner_group); + if (ret < 0) + return ret; + + sysfs_notify(&partner->dev.kobj, NULL, "number_of_alternate_modes"); + kobject_uevent(&partner->dev.kobj, KOBJ_CHANGE); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_partner_set_num_altmodes); + +/** + * typec_partner_register_altmode - Register USB Type-C Partner Alternate Mode + * @partner: USB Type-C Partner that supports the alternate mode + * @desc: Description of the alternate mode + * + * This routine is used to register each alternate mode individually that + * @partner has listed in response to Discover SVIDs command. The modes for a + * SVID listed in response to Discover Modes command need to be listed in an + * array in @desc. + * + * Returns handle to the alternate mode on success or ERR_PTR on failure. + */ +struct typec_altmode * +typec_partner_register_altmode(struct typec_partner *partner, + const struct typec_altmode_desc *desc) +{ + return typec_register_altmode(&partner->dev, desc, &typec_partner_altmode_dev_type); +} +EXPORT_SYMBOL_GPL(typec_partner_register_altmode); + +/** + * typec_partner_set_svdm_version - Set negotiated Structured VDM (SVDM) Version + * @partner: USB Type-C Partner that supports SVDM + * @svdm_version: Negotiated SVDM Version + * + * This routine is used to save the negotiated SVDM Version. + */ +void typec_partner_set_svdm_version(struct typec_partner *partner, + enum usb_pd_svdm_ver svdm_version) +{ + partner->svdm_version = svdm_version; +} +EXPORT_SYMBOL_GPL(typec_partner_set_svdm_version); + +/** + * typec_partner_usb_power_delivery_register - Register Type-C partner USB Power Delivery Support + * @partner: Type-C partner device. + * @desc: Description of the USB PD contract. + * + * This routine is a wrapper around usb_power_delivery_register(). It registers + * USB Power Delivery Capabilities for a Type-C partner device. Specifically, + * it sets the Type-C partner device as a parent for the resulting USB Power Delivery object. + * + * Returns handle to struct usb_power_delivery or ERR_PTR. + */ +struct usb_power_delivery * +typec_partner_usb_power_delivery_register(struct typec_partner *partner, + struct usb_power_delivery_desc *desc) +{ + return usb_power_delivery_register(&partner->dev, desc); +} +EXPORT_SYMBOL_GPL(typec_partner_usb_power_delivery_register); + +/** + * typec_register_partner - Register a USB Type-C Partner + * @port: The USB Type-C Port the partner is connected to + * @desc: Description of the partner + * + * Registers a device for USB Type-C Partner described in @desc. + * + * Returns handle to the partner on success or ERR_PTR on failure. + */ +struct typec_partner *typec_register_partner(struct typec_port *port, + struct typec_partner_desc *desc) +{ + struct typec_partner *partner; + int ret; + + partner = kzalloc_obj(*partner); + if (!partner) + return ERR_PTR(-ENOMEM); + + ida_init(&partner->mode_ids); + partner->usb_pd = desc->usb_pd; + partner->accessory = desc->accessory; + partner->num_altmodes = -1; + partner->usb_capability = desc->usb_capability; + partner->pd_revision = desc->pd_revision; + partner->svdm_version = port->cap->svdm_version; + partner->attach = desc->attach; + partner->deattach = desc->deattach; + + if (desc->identity) { + /* + * Creating directory for the identity only if the driver is + * able to provide data to it. + */ + partner->dev.groups = usb_pd_id_groups; + partner->identity = desc->identity; + } + + partner->dev.class = &typec_class; + partner->dev.parent = &port->dev; + partner->dev.type = &typec_partner_dev_type; + dev_set_name(&partner->dev, "%s-partner", dev_name(&port->dev)); + + if (port->usb2_dev) { + partner->usb_capability |= USB_CAPABILITY_USB2; + partner->usb_mode = USB_MODE_USB2; + } + if (port->usb3_dev) { + partner->usb_capability |= USB_CAPABILITY_USB2 | USB_CAPABILITY_USB3; + partner->usb_mode = USB_MODE_USB3; + } + + mutex_lock(&port->partner_link_lock); + ret = device_register(&partner->dev); + if (ret) { + dev_err(&port->dev, "failed to register partner (%d)\n", ret); + mutex_unlock(&port->partner_link_lock); + put_device(&partner->dev); + return ERR_PTR(ret); + } + + if (port->usb2_dev) + typec_partner_link_device(partner, port->usb2_dev); + if (port->usb3_dev) + typec_partner_link_device(partner, port->usb3_dev); + mutex_unlock(&port->partner_link_lock); + + return partner; +} +EXPORT_SYMBOL_GPL(typec_register_partner); + +/** + * typec_unregister_partner - Unregister a USB Type-C Partner + * @partner: The partner to be unregistered + * + * Unregister device created with typec_register_partner(). + */ +void typec_unregister_partner(struct typec_partner *partner) +{ + struct typec_port *port; + + if (IS_ERR_OR_NULL(partner)) + return; + + port = to_typec_port(partner->dev.parent); + + mutex_lock(&port->partner_link_lock); + if (port->usb2_dev) { + typec_partner_unlink_device(partner, port->usb2_dev); + port->usb2_dev = NULL; + } + if (port->usb3_dev) { + typec_partner_unlink_device(partner, port->usb3_dev); + port->usb3_dev = NULL; + } + + device_unregister(&partner->dev); + mutex_unlock(&port->partner_link_lock); +} +EXPORT_SYMBOL_GPL(typec_unregister_partner); + +/* ------------------------------------------------------------------------- */ +/* Type-C Cable Plugs */ + +static void typec_plug_release(struct device *dev) +{ + struct typec_plug *plug = to_typec_plug(dev); + + ida_destroy(&plug->mode_ids); + kfree(plug); +} + +static struct attribute *typec_plug_attrs[] = { + &dev_attr_number_of_alternate_modes.attr, + NULL +}; + +static umode_t typec_plug_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct typec_plug *plug = to_typec_plug(kobj_to_dev(kobj)); + + if (attr == &dev_attr_number_of_alternate_modes.attr) { + if (plug->num_altmodes < 0) + return 0; + } + + return attr->mode; +} + +static const struct attribute_group typec_plug_group = { + .is_visible = typec_plug_attr_is_visible, + .attrs = typec_plug_attrs +}; + +static const struct attribute_group *typec_plug_groups[] = { + &typec_plug_group, + NULL +}; + +const struct device_type typec_plug_dev_type = { + .name = "typec_plug", + .groups = typec_plug_groups, + .release = typec_plug_release, +}; + +/** + * typec_plug_set_num_altmodes - Set the number of available plug altmodes + * @plug: The plug to be updated. + * @num_altmodes: The number of altmodes we want to specify as available. + * + * This routine is used to report the number of alternate modes supported by the + * plug. This value is *not* enforced in alternate mode registration routines. + * + * @plug.num_altmodes is set to -1 on plug registration, denoting that + * a valid value has not been set for it yet. + * + * Returns 0 on success or negative error number on failure. + */ +int typec_plug_set_num_altmodes(struct typec_plug *plug, int num_altmodes) +{ + int ret; + + if (num_altmodes < 0) + return -EINVAL; + + plug->num_altmodes = num_altmodes; + ret = sysfs_update_group(&plug->dev.kobj, &typec_plug_group); + if (ret < 0) + return ret; + + sysfs_notify(&plug->dev.kobj, NULL, "number_of_alternate_modes"); + kobject_uevent(&plug->dev.kobj, KOBJ_CHANGE); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_plug_set_num_altmodes); + +/** + * typec_plug_register_altmode - Register USB Type-C Cable Plug Alternate Mode + * @plug: USB Type-C Cable Plug that supports the alternate mode + * @desc: Description of the alternate mode + * + * This routine is used to register each alternate mode individually that @plug + * has listed in response to Discover SVIDs command. The modes for a SVID that + * the plug lists in response to Discover Modes command need to be listed in an + * array in @desc. + * + * Returns handle to the alternate mode on success or ERR_PTR on failure. + */ +struct typec_altmode * +typec_plug_register_altmode(struct typec_plug *plug, + const struct typec_altmode_desc *desc) +{ + return typec_register_altmode(&plug->dev, desc, &typec_plug_altmode_dev_type); +} +EXPORT_SYMBOL_GPL(typec_plug_register_altmode); + +/** + * typec_register_plug - Register a USB Type-C Cable Plug + * @cable: USB Type-C Cable with the plug + * @desc: Description of the cable plug + * + * Registers a device for USB Type-C Cable Plug described in @desc. A USB Type-C + * Cable Plug represents a plug with electronics in it that can response to USB + * Power Delivery SOP Prime or SOP Double Prime packages. + * + * Returns handle to the cable plug on success or ERR_PTR on failure. + */ +struct typec_plug *typec_register_plug(struct typec_cable *cable, + struct typec_plug_desc *desc) +{ + struct typec_plug *plug; + char name[8]; + int ret; + + plug = kzalloc_obj(*plug); + if (!plug) + return ERR_PTR(-ENOMEM); + + sprintf(name, "plug%d", desc->index); + + ida_init(&plug->mode_ids); + plug->num_altmodes = -1; + plug->index = desc->index; + plug->dev.class = &typec_class; + plug->dev.parent = &cable->dev; + plug->dev.type = &typec_plug_dev_type; + dev_set_name(&plug->dev, "%s-%s", dev_name(cable->dev.parent), name); + + ret = device_register(&plug->dev); + if (ret) { + dev_err(&cable->dev, "failed to register plug (%d)\n", ret); + put_device(&plug->dev); + return ERR_PTR(ret); + } + + return plug; +} +EXPORT_SYMBOL_GPL(typec_register_plug); + +/** + * typec_unregister_plug - Unregister a USB Type-C Cable Plug + * @plug: The cable plug to be unregistered + * + * Unregister device created with typec_register_plug(). + */ +void typec_unregister_plug(struct typec_plug *plug) +{ + if (!IS_ERR_OR_NULL(plug)) + device_unregister(&plug->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_plug); + +/* Type-C Cables */ + +static const char * const typec_plug_types[] = { + [USB_PLUG_NONE] = "unknown", + [USB_PLUG_TYPE_A] = "type-a", + [USB_PLUG_TYPE_B] = "type-b", + [USB_PLUG_TYPE_C] = "type-c", + [USB_PLUG_CAPTIVE] = "captive", +}; + +static ssize_t plug_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_cable *cable = to_typec_cable(dev); + + return sprintf(buf, "%s\n", typec_plug_types[cable->type]); +} +static DEVICE_ATTR_RO(plug_type); + +static struct attribute *typec_cable_attrs[] = { + &dev_attr_type.attr, + &dev_attr_plug_type.attr, + &dev_attr_usb_power_delivery_revision.attr, + NULL +}; +ATTRIBUTE_GROUPS(typec_cable); + +static void typec_cable_release(struct device *dev) +{ + struct typec_cable *cable = to_typec_cable(dev); + + kfree(cable); +} + +const struct device_type typec_cable_dev_type = { + .name = "typec_cable", + .groups = typec_cable_groups, + .release = typec_cable_release, +}; + +/** + * typec_cable_get - Get a reference to the USB Type-C cable + * @port: The USB Type-C Port the cable is connected to + * + * The caller must decrement the reference count with typec_cable_put() after + * use. + */ +struct typec_cable *typec_cable_get(struct typec_port *port) +{ + struct device *dev; + + dev = device_find_child(&port->dev, &typec_cable_dev_type, + device_match_type); + if (!dev) + return NULL; + + return to_typec_cable(dev); +} +EXPORT_SYMBOL_GPL(typec_cable_get); + +/** + * typec_cable_put - Decrement the reference count on USB Type-C cable + * @cable: The USB Type-C cable + */ +void typec_cable_put(struct typec_cable *cable) +{ + put_device(&cable->dev); +} +EXPORT_SYMBOL_GPL(typec_cable_put); + +/** + * typec_cable_is_active - Check is the USB Type-C cable active or passive + * @cable: The USB Type-C Cable + * + * Return 1 if the cable is active or 0 if it's passive. + */ +int typec_cable_is_active(struct typec_cable *cable) +{ + return cable->active; +} +EXPORT_SYMBOL_GPL(typec_cable_is_active); + +enum typec_cable_altmode_support { + CABLE_SUPPORT_UNKNOWN, + CABLE_SUPPORTED, + CABLE_NOT_SUPPORTED, +}; + +static enum typec_cable_altmode_support +typec_cable_check_altmode_support(struct typec_cable *cable, + struct typec_altmode *alt) +{ + struct typec_altmode *plug; + u32 speed; + + /* + * Check if the cable has an e-marker, supports modal operation, and the + * SOP' altmode nodes are created. + */ + plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); + if (plug) { + typec_altmode_put_plug(plug); + return CABLE_SUPPORTED; + } + + /* The identity is not specified */ + if (!cable->identity) + return CABLE_SUPPORT_UNKNOWN; + + /* Non-e-marked cable */ + if (!cable->identity->id_header) + return CABLE_NOT_SUPPORTED; + + switch (PD_IDH_PTYPE(cable->identity->id_header)) { + case IDH_PTYPE_PCABLE: + speed = VDO_TYPEC_CABLE_SPEED(cable->identity->vdo[0]); + if (speed == CABLE_USB2_ONLY) + return CABLE_NOT_SUPPORTED; + return CABLE_SUPPORTED; + case IDH_PTYPE_ACABLE: + /* + * Active cables must establish an SOP' communication + * node. Since that check failed at the beginning of + * this function, this active cable does not support + * this specific altmode. + */ + return CABLE_NOT_SUPPORTED; + } + + return CABLE_SUPPORT_UNKNOWN; +} + +/** + * typec_cable_altmode_unsupported - Check if a cable restricts altmode + * @alt: The Alternate Mode to evaluate + * + * Returns true if the connected cable is incapable of handling the altmode. + */ +bool typec_cable_altmode_unsupported(struct typec_altmode *alt) +{ + enum typec_cable_altmode_support support = CABLE_SUPPORT_UNKNOWN; + struct typec_cable *cable; + + cable = typec_cable_get(typec_altmode2port(alt)); + if (cable) { + support = typec_cable_check_altmode_support(cable, alt); + typec_cable_put(cable); + } + + return support == CABLE_NOT_SUPPORTED; +} +EXPORT_SYMBOL_GPL(typec_cable_altmode_unsupported); + +/** + * typec_cable_set_identity - Report result from Discover Identity command + * @cable: The cable updated identity values + * + * This routine is used to report that the result of Discover Identity USB power + * delivery command has become available. + */ +int typec_cable_set_identity(struct typec_cable *cable) +{ + if (!cable->identity) + return -EINVAL; + + typec_report_identity(&cable->dev); + return 0; +} +EXPORT_SYMBOL_GPL(typec_cable_set_identity); + +/** + * typec_register_cable - Register a USB Type-C Cable + * @port: The USB Type-C Port the cable is connected to + * @desc: Description of the cable + * + * Registers a device for USB Type-C Cable described in @desc. The cable will be + * parent for the optional cable plug devises. + * + * Returns handle to the cable on success or ERR_PTR on failure. + */ +struct typec_cable *typec_register_cable(struct typec_port *port, + struct typec_cable_desc *desc) +{ + struct typec_cable *cable; + int ret; + + cable = kzalloc_obj(*cable); + if (!cable) + return ERR_PTR(-ENOMEM); + + cable->type = desc->type; + cable->active = desc->active; + cable->pd_revision = desc->pd_revision; + + if (desc->identity) { + /* + * Creating directory for the identity only if the driver is + * able to provide data to it. + */ + cable->dev.groups = usb_pd_id_groups; + cable->identity = desc->identity; + } + + cable->dev.class = &typec_class; + cable->dev.parent = &port->dev; + cable->dev.type = &typec_cable_dev_type; + dev_set_name(&cable->dev, "%s-cable", dev_name(&port->dev)); + + ret = device_register(&cable->dev); + if (ret) { + dev_err(&port->dev, "failed to register cable (%d)\n", ret); + put_device(&cable->dev); + return ERR_PTR(ret); + } + + return cable; +} +EXPORT_SYMBOL_GPL(typec_register_cable); + +/** + * typec_unregister_cable - Unregister a USB Type-C Cable + * @cable: The cable to be unregistered + * + * Unregister device created with typec_register_cable(). + */ +void typec_unregister_cable(struct typec_cable *cable) +{ + if (!IS_ERR_OR_NULL(cable)) + device_unregister(&cable->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_cable); + +/* ------------------------------------------------------------------------- */ +/* USB Type-C ports */ + +/** + * typec_port_set_usb_mode - Set the operational USB mode for the port + * @port: USB Type-C port + * @mode: USB Mode (USB2, USB3 or USB4) + * + * @mode will be used with the next Enter_USB message. Existing connections are + * not affected. + */ +void typec_port_set_usb_mode(struct typec_port *port, enum usb_mode mode) +{ + port->usb_mode = mode; +} +EXPORT_SYMBOL_GPL(typec_port_set_usb_mode); + +static ssize_t +usb_capability_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + int len = 0; + int i; + + for (i = USB_MODE_USB2; i < USB_MODE_USB4 + 1; i++) { + if (!(BIT(i - 1) & port->cap->usb_capability)) + continue; + + if (i == port->usb_mode) + len += sysfs_emit_at(buf, len, "[%s] ", usb_modes[i]); + else + len += sysfs_emit_at(buf, len, "%s ", usb_modes[i]); + } + + sysfs_emit_at(buf, len - 1, "\n"); + + return len; +} + +static ssize_t +usb_capability_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int ret = 0; + int mode; + + if (!port->ops || !port->ops->default_usb_mode_set) + return -EOPNOTSUPP; + + mode = sysfs_match_string(usb_modes, buf); + if (mode < 0) + return mode; + + ret = port->ops->default_usb_mode_set(port, mode); + if (ret) + return ret; + + port->usb_mode = mode; + + return size; +} +static DEVICE_ATTR_RW(usb_capability); + +/** + * typec_port_set_usb_power_delivery - Assign USB PD for port. + * @port: USB Type-C port. + * @pd: USB PD instance. + * + * This routine can be used to set the USB Power Delivery Capabilities for @port + * that it will advertise to the partner. + * + * If @pd is NULL, the assignment is removed. + */ +int typec_port_set_usb_power_delivery(struct typec_port *port, struct usb_power_delivery *pd) +{ + int ret; + + if (IS_ERR_OR_NULL(port) || port->pd == pd) + return 0; + + if (pd) { + ret = usb_power_delivery_link_device(pd, &port->dev); + if (ret) + return ret; + } else { + usb_power_delivery_unlink_device(port->pd, &port->dev); + } + + port->pd = pd; + + return 0; +} +EXPORT_SYMBOL_GPL(typec_port_set_usb_power_delivery); + +static ssize_t select_usb_power_delivery_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + struct usb_power_delivery *pd; + int ret; + + if (!port->ops || !port->ops->pd_set) + return -EOPNOTSUPP; + + pd = usb_power_delivery_find(buf); + if (!pd) + return -EINVAL; + + ret = port->ops->pd_set(port, pd); + put_device(&pd->dev); + if (ret) + return ret; + + return size; +} + +static ssize_t select_usb_power_delivery_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + struct usb_power_delivery **pds; + int i, ret = 0; + + if (!port->ops || !port->ops->pd_get) + return -EOPNOTSUPP; + + pds = port->ops->pd_get(port); + if (!pds) + return 0; + + for (i = 0; pds[i]; i++) { + if (pds[i] == port->pd) + ret += sysfs_emit_at(buf, ret, "[%s] ", dev_name(&pds[i]->dev)); + else + ret += sysfs_emit_at(buf, ret, "%s ", dev_name(&pds[i]->dev)); + } + + buf[ret - 1] = '\n'; + + return ret; +} +static DEVICE_ATTR_RW(select_usb_power_delivery); + +static struct attribute *port_attrs[] = { + &dev_attr_select_usb_power_delivery.attr, + NULL +}; + +static umode_t port_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct typec_port *port = to_typec_port(kobj_to_dev(kobj)); + + if (!port->pd || !port->ops || !port->ops->pd_get) + return 0; + if (!port->ops->pd_set) + return 0444; + + return attr->mode; +} + +static const struct attribute_group pd_group = { + .is_visible = port_attr_is_visible, + .attrs = port_attrs, +}; + +static const char * const typec_orientations[] = { + [TYPEC_ORIENTATION_NONE] = "unknown", + [TYPEC_ORIENTATION_NORMAL] = "normal", + [TYPEC_ORIENTATION_REVERSE] = "reverse", +}; + +static const char * const typec_roles[] = { + [TYPEC_SINK] = "sink", + [TYPEC_SOURCE] = "source", +}; + +static const char * const typec_data_roles[] = { + [TYPEC_DEVICE] = "device", + [TYPEC_HOST] = "host", +}; + +static const char * const typec_port_power_roles[] = { + [TYPEC_PORT_SRC] = "source", + [TYPEC_PORT_SNK] = "sink", + [TYPEC_PORT_DRP] = "dual", +}; + +static const char * const typec_port_data_roles[] = { + [TYPEC_PORT_DFP] = "host", + [TYPEC_PORT_UFP] = "device", + [TYPEC_PORT_DRD] = "dual", +}; + +static const char * const typec_port_types_drp[] = { + [TYPEC_PORT_SRC] = "dual [source] sink", + [TYPEC_PORT_SNK] = "dual source [sink]", + [TYPEC_PORT_DRP] = "[dual] source sink", +}; + +static ssize_t +preferred_role_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int role; + int ret; + + if (port->cap->type != TYPEC_PORT_DRP) { + dev_dbg(dev, "Preferred role only supported with DRP ports\n"); + return -EOPNOTSUPP; + } + + if (!port->ops || !port->ops->try_role) { + dev_dbg(dev, "Setting preferred role not supported\n"); + return -EOPNOTSUPP; + } + + role = sysfs_match_string(typec_roles, buf); + if (role < 0) { + if (sysfs_streq(buf, "none")) + role = TYPEC_NO_PREFERRED_ROLE; + else + return -EINVAL; + } + + ret = port->ops->try_role(port, role); + if (ret) + return ret; + + port->prefer_role = role; + return size; +} + +static ssize_t +preferred_role_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type != TYPEC_PORT_DRP) + return 0; + + if (port->prefer_role < 0) + return 0; + + return sprintf(buf, "%s\n", typec_roles[port->prefer_role]); +} +static DEVICE_ATTR_RW(preferred_role); + +static ssize_t data_role_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int ret; + + if (!port->ops || !port->ops->dr_set) { + dev_dbg(dev, "data role swapping not supported\n"); + return -EOPNOTSUPP; + } + + ret = sysfs_match_string(typec_data_roles, buf); + if (ret < 0) + return ret; + + mutex_lock(&port->port_type_lock); + if (port->cap->data != TYPEC_PORT_DRD) { + ret = -EOPNOTSUPP; + goto unlock_and_ret; + } + + ret = port->ops->dr_set(port, ret); + if (ret) + goto unlock_and_ret; + + ret = size; +unlock_and_ret: + mutex_unlock(&port->port_type_lock); + return ret; +} + +static ssize_t data_role_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->data == TYPEC_PORT_DRD) + return sprintf(buf, "%s\n", port->data_role == TYPEC_HOST ? + "[host] device" : "host [device]"); + + return sprintf(buf, "[%s]\n", typec_data_roles[port->data_role]); +} +static DEVICE_ATTR_RW(data_role); + +static ssize_t power_role_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int ret; + + if (!port->ops || !port->ops->pr_set) { + dev_dbg(dev, "power role swapping not supported\n"); + return -EOPNOTSUPP; + } + + if (port->pwr_opmode != TYPEC_PWR_MODE_PD) { + dev_dbg(dev, "partner unable to swap power role\n"); + return -EIO; + } + + ret = sysfs_match_string(typec_roles, buf); + if (ret < 0) + return ret; + + mutex_lock(&port->port_type_lock); + if (port->port_type != TYPEC_PORT_DRP) { + dev_dbg(dev, "port type fixed at \"%s\"", + typec_port_power_roles[port->port_type]); + ret = -EOPNOTSUPP; + goto unlock_and_ret; + } + + ret = port->ops->pr_set(port, ret); + if (ret) + goto unlock_and_ret; + + ret = size; +unlock_and_ret: + mutex_unlock(&port->port_type_lock); + return ret; +} + +static ssize_t power_role_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type == TYPEC_PORT_DRP) + return sprintf(buf, "%s\n", port->pwr_role == TYPEC_SOURCE ? + "[source] sink" : "source [sink]"); + + return sprintf(buf, "[%s]\n", typec_roles[port->pwr_role]); +} +static DEVICE_ATTR_RW(power_role); + +static ssize_t +port_type_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int ret; + enum typec_port_type type; + + if (port->cap->type != TYPEC_PORT_DRP || + !port->ops || !port->ops->port_type_set) { + dev_dbg(dev, "changing port type not supported\n"); + return -EOPNOTSUPP; + } + + ret = sysfs_match_string(typec_port_power_roles, buf); + if (ret < 0) + return ret; + + type = ret; + mutex_lock(&port->port_type_lock); + + if (port->port_type == type) { + ret = size; + goto unlock_and_ret; + } + + ret = port->ops->port_type_set(port, type); + if (ret) + goto unlock_and_ret; + + port->port_type = type; + ret = size; + +unlock_and_ret: + mutex_unlock(&port->port_type_lock); + return ret; +} + +static ssize_t +port_type_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type == TYPEC_PORT_DRP) + return sprintf(buf, "%s\n", + typec_port_types_drp[port->port_type]); + + return sprintf(buf, "[%s]\n", typec_port_power_roles[port->cap->type]); +} +static DEVICE_ATTR_RW(port_type); + +static const char * const typec_pwr_opmodes[] = { + [TYPEC_PWR_MODE_USB] = "default", + [TYPEC_PWR_MODE_1_5A] = "1.5A", + [TYPEC_PWR_MODE_3_0A] = "3.0A", + [TYPEC_PWR_MODE_PD] = "usb_power_delivery", +}; + +static ssize_t power_operation_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + return sprintf(buf, "%s\n", typec_pwr_opmodes[port->pwr_opmode]); +} +static DEVICE_ATTR_RO(power_operation_mode); + +static ssize_t vconn_source_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + bool source; + int ret; + + if (!port->cap->pd_revision) { + dev_dbg(dev, "VCONN swap depends on USB Power Delivery\n"); + return -EOPNOTSUPP; + } + + if (!port->ops || !port->ops->vconn_set) { + dev_dbg(dev, "VCONN swapping not supported\n"); + return -EOPNOTSUPP; + } + + ret = kstrtobool(buf, &source); + if (ret) + return ret; + + ret = port->ops->vconn_set(port, (enum typec_role)source); + if (ret) + return ret; + + return size; +} + +static ssize_t vconn_source_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + return sprintf(buf, "%s\n", + str_yes_no(port->vconn_role == TYPEC_SOURCE)); +} +static DEVICE_ATTR_RW(vconn_source); + +static ssize_t supported_accessory_modes_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + ssize_t ret = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(port->cap->accessory); i++) { + if (port->cap->accessory[i]) + ret += sprintf(buf + ret, "%s ", + typec_accessory_modes[port->cap->accessory[i]]); + } + + if (!ret) + return sprintf(buf, "none\n"); + + buf[ret - 1] = '\n'; + + return ret; +} +static DEVICE_ATTR_RO(supported_accessory_modes); + +static ssize_t usb_typec_revision_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + u16 rev = port->cap->revision; + + return sprintf(buf, "%d.%d\n", (rev >> 8) & 0xff, (rev >> 4) & 0xf); +} +static DEVICE_ATTR_RO(usb_typec_revision); + +static ssize_t usb_power_delivery_revision_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u16 rev = 0; + + if (is_typec_partner(dev)) { + struct typec_partner *partner = to_typec_partner(dev); + + rev = partner->pd_revision; + } else if (is_typec_cable(dev)) { + struct typec_cable *cable = to_typec_cable(dev); + + rev = cable->pd_revision; + } else if (is_typec_port(dev)) { + struct typec_port *p = to_typec_port(dev); + + rev = p->cap->pd_revision; + } + return sysfs_emit(buf, "%d.%d\n", (rev >> 8) & 0xff, (rev >> 4) & 0xf); +} + +static ssize_t orientation_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + return sprintf(buf, "%s\n", typec_orientations[port->orientation]); +} +static DEVICE_ATTR_RO(orientation); + +static struct attribute *typec_attrs[] = { + &dev_attr_data_role.attr, + &dev_attr_power_operation_mode.attr, + &dev_attr_power_role.attr, + &dev_attr_preferred_role.attr, + &dev_attr_supported_accessory_modes.attr, + &dev_attr_usb_power_delivery_revision.attr, + &dev_attr_usb_typec_revision.attr, + &dev_attr_vconn_source.attr, + &dev_attr_port_type.attr, + &dev_attr_orientation.attr, + &dev_attr_usb_capability.attr, + NULL, +}; + +static umode_t typec_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct typec_port *port = to_typec_port(kobj_to_dev(kobj)); + + if (attr == &dev_attr_data_role.attr) { + if (port->cap->data != TYPEC_PORT_DRD || + !port->ops || !port->ops->dr_set) + return 0444; + } else if (attr == &dev_attr_power_role.attr) { + if (port->cap->type != TYPEC_PORT_DRP || + !port->ops || !port->ops->pr_set) + return 0444; + } else if (attr == &dev_attr_vconn_source.attr) { + if (!port->cap->pd_revision || + !port->ops || !port->ops->vconn_set) + return 0444; + } else if (attr == &dev_attr_preferred_role.attr) { + if (port->cap->type != TYPEC_PORT_DRP || + !port->ops || !port->ops->try_role) + return 0444; + } else if (attr == &dev_attr_port_type.attr) { + if (!port->ops || !port->ops->port_type_set) + return 0; + if (port->cap->type != TYPEC_PORT_DRP) + return 0444; + } else if (attr == &dev_attr_orientation.attr) { + if (port->cap->orientation_aware) + return 0444; + return 0; + } else if (attr == &dev_attr_usb_capability.attr) { + if (!port->cap->usb_capability) + return 0; + if (!port->ops || !port->ops->default_usb_mode_set) + return 0444; + } + + return attr->mode; +} + +static const struct attribute_group typec_group = { + .is_visible = typec_attr_is_visible, + .attrs = typec_attrs, +}; + +static const struct attribute_group *typec_groups[] = { + &typec_group, + &pd_group, + NULL +}; + +static int typec_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + int ret; + + ret = add_uevent_var(env, "TYPEC_PORT=%s", dev_name(dev)); + if (ret) + dev_err(dev, "failed to add uevent TYPEC_PORT\n"); + + return ret; +} + +static void typec_release(struct device *dev) +{ + struct typec_port *port = to_typec_port(dev); + + ida_free(&typec_index_ida, port->id); + ida_destroy(&port->mode_ids); + typec_switch_put(port->sw); + typec_mux_put(port->mux); + typec_retimer_put(port->retimer); + kfree(port->cap); + kfree(port); +} + +const struct device_type typec_port_dev_type = { + .name = "typec_port", + .groups = typec_groups, + .uevent = typec_uevent, + .release = typec_release, +}; + +/* --------------------------------------- */ +/* Driver callbacks to report role updates */ + +static struct typec_partner *typec_get_partner(struct typec_port *port) +{ + struct device *dev; + + dev = device_find_child(&port->dev, &typec_partner_dev_type, + device_match_type); + if (!dev) + return NULL; + + return to_typec_partner(dev); +} + +static void typec_partner_attach(struct typec_connector *con, struct device *dev) +{ + struct typec_port *port = container_of(con, struct typec_port, con); + struct typec_partner *partner; + struct usb_device *udev = to_usb_device(dev); + enum usb_mode usb_mode; + + mutex_lock(&port->partner_link_lock); + if (udev->speed < USB_SPEED_SUPER) { + usb_mode = USB_MODE_USB2; + port->usb2_dev = dev; + } else { + usb_mode = USB_MODE_USB3; + port->usb3_dev = dev; + } + + partner = typec_get_partner(port); + if (partner) { + typec_partner_set_usb_mode(partner, usb_mode); + typec_partner_link_device(partner, dev); + put_device(&partner->dev); + } + mutex_unlock(&port->partner_link_lock); +} + +static void typec_partner_deattach(struct typec_connector *con, struct device *dev) +{ + struct typec_port *port = container_of(con, struct typec_port, con); + struct typec_partner *partner; + + mutex_lock(&port->partner_link_lock); + partner = typec_get_partner(port); + if (partner) { + typec_partner_unlink_device(partner, dev); + put_device(&partner->dev); + } + + if (port->usb2_dev == dev) + port->usb2_dev = NULL; + else if (port->usb3_dev == dev) + port->usb3_dev = NULL; + mutex_unlock(&port->partner_link_lock); +} + +/** + * typec_set_data_role - Report data role change + * @port: The USB Type-C Port where the role was changed + * @role: The new data role + * + * This routine is used by the port drivers to report data role changes. + */ +void typec_set_data_role(struct typec_port *port, enum typec_data_role role) +{ + struct typec_partner *partner; + + if (port->data_role == role) + return; + + port->data_role = role; + sysfs_notify(&port->dev.kobj, NULL, "data_role"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); + + partner = typec_get_partner(port); + if (!partner) + return; + + if (partner->identity) + typec_product_type_notify(&partner->dev); + + put_device(&partner->dev); +} +EXPORT_SYMBOL_GPL(typec_set_data_role); + +/** + * typec_get_data_role - Get port data role + * @port: The USB Type-C Port to query + * + * This routine is used by the altmode drivers to determine if the port is the + * DFP before issuing Enter Mode + */ +enum typec_data_role typec_get_data_role(struct typec_port *port) +{ + return port->data_role; +} +EXPORT_SYMBOL_GPL(typec_get_data_role); + +/** + * typec_set_pwr_role - Report power role change + * @port: The USB Type-C Port where the role was changed + * @role: The new data role + * + * This routine is used by the port drivers to report power role changes. + */ +void typec_set_pwr_role(struct typec_port *port, enum typec_role role) +{ + if (port->pwr_role == role) + return; + + port->pwr_role = role; + sysfs_notify(&port->dev.kobj, NULL, "power_role"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_set_pwr_role); + +/** + * typec_set_vconn_role - Report VCONN source change + * @port: The USB Type-C Port which VCONN role changed + * @role: Source when @port is sourcing VCONN, or Sink when it's not + * + * This routine is used by the port drivers to report if the VCONN source is + * changes. + */ +void typec_set_vconn_role(struct typec_port *port, enum typec_role role) +{ + if (port->vconn_role == role) + return; + + port->vconn_role = role; + sysfs_notify(&port->dev.kobj, NULL, "vconn_source"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_set_vconn_role); + +/** + * typec_set_pwr_opmode - Report changed power operation mode + * @port: The USB Type-C Port where the mode was changed + * @opmode: New power operation mode + * + * This routine is used by the port drivers to report changed power operation + * mode in @port. The modes are USB (default), 1.5A, 3.0A as defined in USB + * Type-C specification, and "USB Power Delivery" when the power levels are + * negotiated with methods defined in USB Power Delivery specification. + */ +void typec_set_pwr_opmode(struct typec_port *port, + enum typec_pwr_opmode opmode) +{ + struct device *partner_dev; + + if (port->pwr_opmode == opmode) + return; + + port->pwr_opmode = opmode; + sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); + + partner_dev = device_find_child(&port->dev, + &typec_partner_dev_type, + device_match_type); + if (partner_dev) { + struct typec_partner *partner = to_typec_partner(partner_dev); + + if (opmode == TYPEC_PWR_MODE_PD && !partner->usb_pd) { + partner->usb_pd = 1; + sysfs_notify(&partner_dev->kobj, NULL, + "supports_usb_power_delivery"); + kobject_uevent(&partner_dev->kobj, KOBJ_CHANGE); + } + put_device(partner_dev); + } +} +EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); + +/** + * typec_find_pwr_opmode - Get the typec power operation mode capability + * @name: power operation mode string + * + * This routine is used to find the typec_pwr_opmode by its string @name. + * + * Returns typec_pwr_opmode if success, otherwise negative error code. + */ +int typec_find_pwr_opmode(const char *name) +{ + return match_string(typec_pwr_opmodes, + ARRAY_SIZE(typec_pwr_opmodes), name); +} +EXPORT_SYMBOL_GPL(typec_find_pwr_opmode); + +/** + * typec_find_orientation - Convert orientation string to enum typec_orientation + * @name: Orientation string + * + * This routine is used to find the typec_orientation by its string name @name. + * + * Returns the orientation value on success, otherwise negative error code. + */ +int typec_find_orientation(const char *name) +{ + return match_string(typec_orientations, ARRAY_SIZE(typec_orientations), + name); +} +EXPORT_SYMBOL_GPL(typec_find_orientation); + +/** + * typec_find_port_power_role - Get the typec port power capability + * @name: port power capability string + * + * This routine is used to find the typec_port_type by its string name. + * + * Returns typec_port_type if success, otherwise negative error code. + */ +int typec_find_port_power_role(const char *name) +{ + return match_string(typec_port_power_roles, + ARRAY_SIZE(typec_port_power_roles), name); +} +EXPORT_SYMBOL_GPL(typec_find_port_power_role); + +/** + * typec_find_power_role - Find the typec one specific power role + * @name: power role string + * + * This routine is used to find the typec_role by its string name. + * + * Returns typec_role if success, otherwise negative error code. + */ +int typec_find_power_role(const char *name) +{ + return match_string(typec_roles, ARRAY_SIZE(typec_roles), name); +} +EXPORT_SYMBOL_GPL(typec_find_power_role); + +/** + * typec_find_port_data_role - Get the typec port data capability + * @name: port data capability string + * + * This routine is used to find the typec_port_data by its string name. + * + * Returns typec_port_data if success, otherwise negative error code. + */ +int typec_find_port_data_role(const char *name) +{ + return match_string(typec_port_data_roles, + ARRAY_SIZE(typec_port_data_roles), name); +} +EXPORT_SYMBOL_GPL(typec_find_port_data_role); + +/* ------------------------------------------ */ +/* API for Multiplexer/DeMultiplexer Switches */ + +/** + * typec_set_orientation - Set USB Type-C cable plug orientation + * @port: USB Type-C Port + * @orientation: USB Type-C cable plug orientation + * + * Set cable plug orientation for @port. + */ +int typec_set_orientation(struct typec_port *port, + enum typec_orientation orientation) +{ + int ret; + + ret = typec_switch_set(port->sw, orientation); + if (ret) + return ret; + + port->orientation = orientation; + sysfs_notify(&port->dev.kobj, NULL, "orientation"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_set_orientation); + +/** + * typec_get_orientation - Get USB Type-C cable plug orientation + * @port: USB Type-C Port + * + * Get current cable plug orientation for @port. + */ +enum typec_orientation typec_get_orientation(struct typec_port *port) +{ + return port->orientation; +} +EXPORT_SYMBOL_GPL(typec_get_orientation); + +/** + * typec_set_mode - Set mode of operation for USB Type-C connector + * @port: USB Type-C connector + * @mode: Accessory Mode, USB Operation or Safe State + * + * Configure @port for Accessory Mode @mode. This function will configure the + * muxes needed for @mode. + */ +int typec_set_mode(struct typec_port *port, int mode) +{ + struct typec_mux_state state = { }; + + state.mode = mode; + + return typec_mux_set(port->mux, &state); +} +EXPORT_SYMBOL_GPL(typec_set_mode); + +/* --------------------------------------- */ + +/** + * typec_get_negotiated_svdm_version - Get negotiated SVDM Version + * @port: USB Type-C Port. + * + * Get the negotiated SVDM Version. The Version is set to the port default + * value stored in typec_capability on partner registration, and updated after + * a successful Discover Identity if the negotiated value is less than the + * default value. + * + * Returns usb_pd_svdm_ver if the partner has been registered otherwise -ENODEV. + */ +int typec_get_negotiated_svdm_version(struct typec_port *port) +{ + enum usb_pd_svdm_ver svdm_version; + struct device *partner_dev; + + partner_dev = device_find_child(&port->dev, + &typec_partner_dev_type, + device_match_type); + if (!partner_dev) + return -ENODEV; + + svdm_version = to_typec_partner(partner_dev)->svdm_version; + put_device(partner_dev); + + return svdm_version; +} +EXPORT_SYMBOL_GPL(typec_get_negotiated_svdm_version); + +/** + * typec_get_cable_svdm_version - Get cable negotiated SVDM Version + * @port: USB Type-C Port. + * + * Get the negotiated SVDM Version for the cable. The Version is set to the port + * default value based on the PD Revision during cable registration, and updated + * after a successful Discover Identity if the negotiated value is less than the + * default. + * + * Returns usb_pd_svdm_ver if the cable has been registered otherwise -ENODEV. + */ +int typec_get_cable_svdm_version(struct typec_port *port) +{ + enum usb_pd_svdm_ver svdm_version; + struct device *cable_dev; + + cable_dev = device_find_child(&port->dev, &typec_cable_dev_type, + device_match_type); + if (!cable_dev) + return -ENODEV; + + svdm_version = to_typec_cable(cable_dev)->svdm_version; + put_device(cable_dev); + + return svdm_version; +} +EXPORT_SYMBOL_GPL(typec_get_cable_svdm_version); + +/** + * typec_cable_set_svdm_version - Set negotiated Structured VDM (SVDM) Version + * @cable: USB Type-C Active Cable that supports SVDM + * @svdm_version: Negotiated SVDM Version + * + * This routine is used to save the negotiated SVDM Version. + */ +void typec_cable_set_svdm_version(struct typec_cable *cable, enum usb_pd_svdm_ver svdm_version) +{ + cable->svdm_version = svdm_version; +} +EXPORT_SYMBOL_GPL(typec_cable_set_svdm_version); + +/** + * typec_get_drvdata - Return private driver data pointer + * @port: USB Type-C port + */ +void *typec_get_drvdata(struct typec_port *port) +{ + return dev_get_drvdata(&port->dev); +} +EXPORT_SYMBOL_GPL(typec_get_drvdata); + +int typec_get_fw_cap(struct typec_capability *cap, + struct fwnode_handle *fwnode) +{ + const char *cap_str; + int ret; + + cap->fwnode = fwnode; + + ret = fwnode_property_read_string(fwnode, "power-role", &cap_str); + if (ret < 0) + return ret; + + ret = typec_find_port_power_role(cap_str); + if (ret < 0) + return ret; + cap->type = ret; + + /* USB data support is optional */ + ret = fwnode_property_read_string(fwnode, "data-role", &cap_str); + if (ret == 0) { + ret = typec_find_port_data_role(cap_str); + if (ret < 0) + return ret; + cap->data = ret; + } + + /* Get the preferred power role for a DRP */ + if (cap->type == TYPEC_PORT_DRP) { + cap->prefer_role = TYPEC_NO_PREFERRED_ROLE; + + ret = fwnode_property_read_string(fwnode, "try-power-role", &cap_str); + if (ret == 0) { + ret = typec_find_power_role(cap_str); + if (ret < 0) + return ret; + cap->prefer_role = ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(typec_get_fw_cap); + +/** + * typec_port_register_altmode - Register USB Type-C Port Alternate Mode + * @port: USB Type-C Port that supports the alternate mode + * @desc: Description of the alternate mode + * + * This routine is used to register an alternate mode that @port is capable of + * supporting. + * + * Returns handle to the alternate mode on success or ERR_PTR on failure. + */ +struct typec_altmode * +typec_port_register_altmode(struct typec_port *port, + const struct typec_altmode_desc *desc) +{ + struct typec_altmode *adev; + struct typec_mux *mux; + struct typec_retimer *retimer; + int ret; + + mux = typec_mux_get(&port->dev); + if (IS_ERR(mux)) + return ERR_CAST(mux); + + retimer = typec_retimer_get(&port->dev); + if (IS_ERR(retimer)) { + typec_mux_put(mux); + return ERR_CAST(retimer); + } + + adev = typec_register_altmode(&port->dev, desc, &typec_port_altmode_dev_type); + if (IS_ERR(adev)) { + typec_retimer_put(retimer); + typec_mux_put(mux); + } else { + to_altmode(adev)->mux = mux; + to_altmode(adev)->retimer = retimer; + + ret = typec_mode_set_priority(adev, 0); + if (ret) { + typec_unregister_altmode(adev); + return ERR_PTR(ret); + } + } + + return adev; +} +EXPORT_SYMBOL_GPL(typec_port_register_altmode); + +void typec_port_register_altmodes(struct typec_port *port, + const struct typec_altmode_ops *ops, void *drvdata, + struct typec_altmode **altmodes, size_t n) +{ + struct fwnode_handle *child; + struct typec_altmode_desc desc; + struct typec_altmode *alt; + size_t index = 0; + u16 svid; + u32 vdo; + int ret; + + struct fwnode_handle *altmodes_node __free(fwnode_handle) = + device_get_named_child_node(&port->dev, "altmodes"); + + if (!altmodes_node) + return; /* No altmodes specified */ + + fwnode_for_each_child_node(altmodes_node, child) { + ret = fwnode_property_read_u16(child, "svid", &svid); + if (ret) { + dev_err(&port->dev, "Error reading svid for altmode %s\n", + fwnode_get_name(child)); + continue; + } + + ret = fwnode_property_read_u32(child, "vdo", &vdo); + if (ret) { + dev_err(&port->dev, "Error reading vdo for altmode %s\n", + fwnode_get_name(child)); + continue; + } + + if (index >= n) { + dev_err(&port->dev, "Error not enough space for altmode %s\n", + fwnode_get_name(child)); + continue; + } + + desc.svid = svid; + desc.vdo = vdo; + desc.mode = index + 1; + alt = typec_port_register_altmode(port, &desc); + if (IS_ERR(alt)) { + dev_err(&port->dev, "Error registering altmode %s\n", + fwnode_get_name(child)); + continue; + } + + typec_altmode_set_ops(alt, ops); + typec_altmode_set_drvdata(alt, drvdata); + altmodes[index] = alt; + index++; + } +} +EXPORT_SYMBOL_GPL(typec_port_register_altmodes); + +/** + * typec_port_register_cable_ops - Register typec_cable_ops to port altmodes + * @altmodes: USB Type-C Port's altmode vector + * @max_altmodes: The maximum number of alt modes supported by the port + * @ops: Cable alternate mode vector + */ +void typec_port_register_cable_ops(struct typec_altmode **altmodes, int max_altmodes, + const struct typec_cable_ops *ops) +{ + int i; + + for (i = 0; i < max_altmodes; i++) { + if (!altmodes[i]) + return; + altmodes[i]->cable_ops = ops; + } +} +EXPORT_SYMBOL_GPL(typec_port_register_cable_ops); + +/** + * typec_register_port - Register a USB Type-C Port + * @parent: Parent device + * @cap: Description of the port + * + * Registers a device for USB Type-C Port described in @cap. + * + * Returns handle to the port on success or ERR_PTR on failure. + */ +struct typec_port *typec_register_port(struct device *parent, + const struct typec_capability *cap) +{ + struct typec_port *port; + int ret; + int id; + + port = kzalloc_obj(*port); + if (!port) + return ERR_PTR(-ENOMEM); + + id = ida_alloc(&typec_index_ida, GFP_KERNEL); + if (id < 0) { + kfree(port); + return ERR_PTR(id); + } + + switch (cap->type) { + case TYPEC_PORT_SRC: + port->pwr_role = TYPEC_SOURCE; + port->vconn_role = TYPEC_SOURCE; + break; + case TYPEC_PORT_SNK: + port->pwr_role = TYPEC_SINK; + port->vconn_role = TYPEC_SINK; + break; + case TYPEC_PORT_DRP: + if (cap->prefer_role != TYPEC_NO_PREFERRED_ROLE) + port->pwr_role = cap->prefer_role; + else + port->pwr_role = TYPEC_SINK; + break; + } + + switch (cap->data) { + case TYPEC_PORT_DFP: + port->data_role = TYPEC_HOST; + break; + case TYPEC_PORT_UFP: + port->data_role = TYPEC_DEVICE; + break; + case TYPEC_PORT_DRD: + if (cap->prefer_role == TYPEC_SOURCE) + port->data_role = TYPEC_HOST; + else + port->data_role = TYPEC_DEVICE; + break; + } + + ida_init(&port->mode_ids); + mutex_init(&port->port_type_lock); + mutex_init(&port->partner_link_lock); + + port->id = id; + port->ops = cap->ops; + port->port_type = cap->type; + port->prefer_role = cap->prefer_role; + port->con.attach = typec_partner_attach; + port->con.deattach = typec_partner_deattach; + + if (cap->usb_capability & USB_CAPABILITY_USB4) + port->usb_mode = USB_MODE_USB4; + else if (cap->usb_capability & USB_CAPABILITY_USB3) + port->usb_mode = USB_MODE_USB3; + else if (cap->usb_capability & USB_CAPABILITY_USB2) + port->usb_mode = USB_MODE_USB2; + + device_initialize(&port->dev); + port->dev.class = &typec_class; + port->dev.parent = parent; + port->dev.fwnode = cap->fwnode; + port->dev.type = &typec_port_dev_type; + dev_set_name(&port->dev, "port%d", id); + dev_set_drvdata(&port->dev, cap->driver_data); + + port->cap = kmemdup(cap, sizeof(*cap), GFP_KERNEL); + if (!port->cap) { + put_device(&port->dev); + return ERR_PTR(-ENOMEM); + } + + port->sw = typec_switch_get(&port->dev); + if (IS_ERR(port->sw)) { + ret = PTR_ERR(port->sw); + put_device(&port->dev); + return ERR_PTR(ret); + } + + port->mux = typec_mux_get(&port->dev); + if (IS_ERR(port->mux)) { + ret = PTR_ERR(port->mux); + put_device(&port->dev); + return ERR_PTR(ret); + } + + port->retimer = typec_retimer_get(&port->dev); + if (IS_ERR(port->retimer)) { + ret = PTR_ERR(port->retimer); + put_device(&port->dev); + return ERR_PTR(ret); + } + + port->pd = cap->pd; + port->mode_control = !cap->no_mode_control; + + ret = device_add(&port->dev); + if (ret) { + dev_err(parent, "failed to register port (%d)\n", ret); + put_device(&port->dev); + return ERR_PTR(ret); + } + + ret = usb_power_delivery_link_device(port->pd, &port->dev); + if (ret) { + dev_err(&port->dev, "failed to link pd\n"); + device_unregister(&port->dev); + return ERR_PTR(ret); + } + + ret = typec_link_ports(port); + if (ret) + dev_warn(&port->dev, "failed to create symlinks (%d)\n", ret); + + return port; +} +EXPORT_SYMBOL_GPL(typec_register_port); + +/** + * typec_unregister_port - Unregister a USB Type-C Port + * @port: The port to be unregistered + * + * Unregister device created with typec_register_port(). + */ +void typec_unregister_port(struct typec_port *port) +{ + if (!IS_ERR_OR_NULL(port)) { + typec_unlink_ports(port); + typec_port_set_usb_power_delivery(port, NULL); + device_unregister(&port->dev); + } +} +EXPORT_SYMBOL_GPL(typec_unregister_port); + +static int __init typec_init(void) +{ + int ret; + + ret = bus_register(&typec_bus); + if (ret) + return ret; + + ret = class_register(&typec_mux_class); + if (ret) + goto err_unregister_bus; + + ret = class_register(&retimer_class); + if (ret) + goto err_unregister_mux_class; + + ret = class_register(&typec_class); + if (ret) + goto err_unregister_retimer_class; + + ret = usb_power_delivery_init(); + if (ret) + goto err_unregister_class; + + return 0; + +err_unregister_class: + class_unregister(&typec_class); + +err_unregister_retimer_class: + class_unregister(&retimer_class); + +err_unregister_mux_class: + class_unregister(&typec_mux_class); + +err_unregister_bus: + bus_unregister(&typec_bus); + + return ret; +} +subsys_initcall(typec_init); + +static void __exit typec_exit(void) +{ + usb_power_delivery_exit(); + class_unregister(&typec_class); + ida_destroy(&typec_index_ida); + bus_unregister(&typec_bus); + class_unregister(&typec_mux_class); + class_unregister(&retimer_class); +} +module_exit(typec_exit); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB Type-C Connector Class"); diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h new file mode 100644 index 000000000..d3435936e --- /dev/null +++ b/drivers/usb/typec/class.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __USB_TYPEC_CLASS__ +#define __USB_TYPEC_CLASS__ + +#include +#include + +struct typec_mux; +struct typec_switch; +struct usb_device; +struct mode_selection; + +struct typec_plug { + struct device dev; + enum typec_plug_index index; + struct ida mode_ids; + int num_altmodes; +}; + +struct typec_cable { + struct device dev; + enum typec_plug_type type; + struct usb_pd_identity *identity; + unsigned int active:1; + u16 pd_revision; /* 0300H = "3.0" */ + enum usb_pd_svdm_ver svdm_version; +}; + +struct typec_partner { + struct device dev; + unsigned int usb_pd:1; + struct usb_pd_identity *identity; + enum typec_accessory accessory; + struct ida mode_ids; + int num_altmodes; + u16 pd_revision; /* 0300H = "3.0" */ + enum usb_pd_svdm_ver svdm_version; + enum usb_mode usb_mode; + u8 usb_capability; + + struct usb_power_delivery *pd; + struct mode_selection *sel; + + void (*attach)(struct typec_partner *partner, struct device *dev); + void (*deattach)(struct typec_partner *partner, struct device *dev); +}; + +struct typec_port { + unsigned int id; + struct device dev; + struct ida mode_ids; + + struct usb_power_delivery *pd; + + int prefer_role; + enum typec_data_role data_role; + enum typec_role pwr_role; + enum typec_role vconn_role; + enum typec_pwr_opmode pwr_opmode; + enum typec_port_type port_type; + enum usb_mode usb_mode; + struct mutex port_type_lock; + struct mutex partner_link_lock; + + enum typec_orientation orientation; + bool mode_control; + struct typec_switch *sw; + struct typec_mux *mux; + struct typec_retimer *retimer; + + const struct typec_capability *cap; + const struct typec_operations *ops; + + struct typec_connector con; + + /* + * REVISIT: Only USB devices for now. If there are others, these need to + * be converted into a list. + * + * NOTE: These may be registered first before the typec_partner, so they + * will always have to be kept here instead of struct typec_partner. + */ + struct device *usb2_dev; + struct device *usb3_dev; +}; + +#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev) +#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev) +#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev) +#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev) + +extern const struct device_type typec_partner_dev_type; +extern const struct device_type typec_cable_dev_type; +extern const struct device_type typec_plug_dev_type; +extern const struct device_type typec_port_dev_type; + +#define is_typec_partner(dev) ((dev)->type == &typec_partner_dev_type) +#define is_typec_cable(dev) ((dev)->type == &typec_cable_dev_type) +#define is_typec_plug(dev) ((dev)->type == &typec_plug_dev_type) +#define is_typec_port(dev) ((dev)->type == &typec_port_dev_type) + +extern const struct class typec_mux_class; +extern const struct class retimer_class; +extern const struct class typec_class; + +#if defined(CONFIG_ACPI) +int typec_link_ports(struct typec_port *connector); +void typec_unlink_ports(struct typec_port *connector); +#else +static inline int typec_link_ports(struct typec_port *connector) { return 0; } +static inline void typec_unlink_ports(struct typec_port *connector) { } +#endif + +#endif /* __USB_TYPEC_CLASS__ */ diff --git a/drivers/usb/typec/hd3ss3220.c b/drivers/usb/typec/hd3ss3220.c new file mode 100644 index 000000000..d0de5a248 --- /dev/null +++ b/drivers/usb/typec/hd3ss3220.c @@ -0,0 +1,547 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * TI HD3SS3220 Type-C DRP Port Controller Driver + * + * Copyright (C) 2019 Renesas Electronics Corp. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HD3SS3220_REG_CN_STAT 0x08 +#define HD3SS3220_REG_CN_STAT_CTRL 0x09 +#define HD3SS3220_REG_GEN_CTRL 0x0A +#define HD3SS3220_REG_DEV_REV 0xA0 + +/* Register HD3SS3220_REG_CN_STAT */ +#define HD3SS3220_REG_CN_STAT_CURRENT_MODE_MASK (BIT(7) | BIT(6)) +#define HD3SS3220_REG_CN_STAT_CURRENT_MODE_DEFAULT 0x00 +#define HD3SS3220_REG_CN_STAT_CURRENT_MODE_MID BIT(6) +#define HD3SS3220_REG_CN_STAT_CURRENT_MODE_HIGH BIT(7) + +/* Register HD3SS3220_REG_CN_STAT_CTRL*/ +#define HD3SS3220_REG_CN_STAT_CTRL_ATTACHED_STATE_MASK (BIT(7) | BIT(6)) +#define HD3SS3220_REG_CN_STAT_CTRL_AS_DFP BIT(6) +#define HD3SS3220_REG_CN_STAT_CTRL_AS_UFP BIT(7) +#define HD3SS3220_REG_CN_STAT_CTRL_TO_ACCESSORY (BIT(7) | BIT(6)) +#define HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS BIT(4) + +/* Register HD3SS3220_REG_GEN_CTRL*/ +#define HD3SS3220_REG_GEN_CTRL_DISABLE_TERM BIT(0) +#define HD3SS3220_REG_GEN_CTRL_SRC_PREF_MASK (BIT(2) | BIT(1)) +#define HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT 0x00 +#define HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SNK BIT(1) +#define HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SRC (BIT(2) | BIT(1)) +#define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_MASK (BIT(5) | BIT(4)) +#define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DEFAULT 0x00 +#define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DFP BIT(5) +#define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_UFP BIT(4) +#define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DRP (BIT(5) | BIT(4)) + +struct hd3ss3220 { + struct device *dev; + struct regmap *regmap; + struct usb_role_switch *role_sw; + struct typec_port *port; + struct delayed_work output_poll_work; + enum usb_role role_state; + bool poll; + + struct gpio_desc *id_gpiod; + int id_irq; + + struct regulator *vbus; +}; + +static int hd3ss3220_set_power_opmode(struct hd3ss3220 *hd3ss3220, int power_opmode) +{ + int current_mode; + + switch (power_opmode) { + case TYPEC_PWR_MODE_USB: + current_mode = HD3SS3220_REG_CN_STAT_CURRENT_MODE_DEFAULT; + break; + case TYPEC_PWR_MODE_1_5A: + current_mode = HD3SS3220_REG_CN_STAT_CURRENT_MODE_MID; + break; + case TYPEC_PWR_MODE_3_0A: + current_mode = HD3SS3220_REG_CN_STAT_CURRENT_MODE_HIGH; + break; + case TYPEC_PWR_MODE_PD: /* Power delivery not supported */ + default: + dev_err(hd3ss3220->dev, "bad power operation mode: %d\n", power_opmode); + return -EINVAL; + } + + return regmap_update_bits(hd3ss3220->regmap, HD3SS3220_REG_CN_STAT, + HD3SS3220_REG_CN_STAT_CURRENT_MODE_MASK, + current_mode); +} + +static int hd3ss3220_set_port_type(struct hd3ss3220 *hd3ss3220, int type) +{ + int mode_select, err; + + switch (type) { + case TYPEC_PORT_SRC: + mode_select = HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DFP; + break; + case TYPEC_PORT_SNK: + mode_select = HD3SS3220_REG_GEN_CTRL_MODE_SELECT_UFP; + break; + case TYPEC_PORT_DRP: + mode_select = HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DRP; + break; + default: + dev_err(hd3ss3220->dev, "bad port type: %d\n", type); + return -EINVAL; + } + + /* Disable termination before changing MODE_SELECT as required by datasheet */ + err = regmap_update_bits(hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, + HD3SS3220_REG_GEN_CTRL_DISABLE_TERM, + HD3SS3220_REG_GEN_CTRL_DISABLE_TERM); + if (err < 0) { + dev_err(hd3ss3220->dev, "Failed to disable port for mode change: %d\n", err); + return err; + } + + err = regmap_update_bits(hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, + HD3SS3220_REG_GEN_CTRL_MODE_SELECT_MASK, + mode_select); + if (err < 0) { + dev_err(hd3ss3220->dev, "Failed to change mode: %d\n", err); + regmap_update_bits(hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, + HD3SS3220_REG_GEN_CTRL_DISABLE_TERM, 0); + return err; + } + + err = regmap_update_bits(hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, + HD3SS3220_REG_GEN_CTRL_DISABLE_TERM, 0); + if (err < 0) + dev_err(hd3ss3220->dev, "Failed to re-enable port after mode change: %d\n", err); + + return err; +} + +static int hd3ss3220_set_source_pref(struct hd3ss3220 *hd3ss3220, int prefer_role) +{ + int src_pref; + + switch (prefer_role) { + case TYPEC_NO_PREFERRED_ROLE: + src_pref = HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT; + break; + case TYPEC_SINK: + src_pref = HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SNK; + break; + case TYPEC_SOURCE: + src_pref = HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SRC; + break; + default: + dev_err(hd3ss3220->dev, "bad role preference: %d\n", prefer_role); + return -EINVAL; + } + + return regmap_update_bits(hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, + HD3SS3220_REG_GEN_CTRL_SRC_PREF_MASK, + src_pref); +} + +static enum usb_role hd3ss3220_get_attached_state(struct hd3ss3220 *hd3ss3220) +{ + unsigned int reg_val; + enum usb_role attached_state; + int ret; + + ret = regmap_read(hd3ss3220->regmap, HD3SS3220_REG_CN_STAT_CTRL, + ®_val); + if (ret < 0) + return ret; + + switch (reg_val & HD3SS3220_REG_CN_STAT_CTRL_ATTACHED_STATE_MASK) { + case HD3SS3220_REG_CN_STAT_CTRL_AS_DFP: + attached_state = USB_ROLE_HOST; + break; + case HD3SS3220_REG_CN_STAT_CTRL_AS_UFP: + attached_state = USB_ROLE_DEVICE; + break; + default: + attached_state = USB_ROLE_NONE; + break; + } + + return attached_state; +} + +static int hd3ss3220_try_role(struct typec_port *port, int role) +{ + struct hd3ss3220 *hd3ss3220 = typec_get_drvdata(port); + + return hd3ss3220_set_source_pref(hd3ss3220, role); +} + +static int hd3ss3220_port_type_set(struct typec_port *port, enum typec_port_type type) +{ + struct hd3ss3220 *hd3ss3220 = typec_get_drvdata(port); + + return hd3ss3220_set_port_type(hd3ss3220, type); +} + +static const struct typec_operations hd3ss3220_ops = { + .try_role = hd3ss3220_try_role, + .port_type_set = hd3ss3220_port_type_set, +}; + +static void hd3ss3220_regulator_control(struct hd3ss3220 *hd3ss3220, bool on) +{ + int ret; + + if (regulator_is_enabled(hd3ss3220->vbus) == on) + return; + + if (on) + ret = regulator_enable(hd3ss3220->vbus); + else + ret = regulator_disable(hd3ss3220->vbus); + + if (ret) + dev_err(hd3ss3220->dev, + "vbus regulator %s failed: %d\n", on ? "enable" : "disable", ret); +} + +static void hd3ss3220_set_role(struct hd3ss3220 *hd3ss3220) +{ + enum usb_role role_state = hd3ss3220_get_attached_state(hd3ss3220); + + usb_role_switch_set_role(hd3ss3220->role_sw, role_state); + + switch (role_state) { + case USB_ROLE_HOST: + typec_set_data_role(hd3ss3220->port, TYPEC_HOST); + break; + case USB_ROLE_DEVICE: + typec_set_data_role(hd3ss3220->port, TYPEC_DEVICE); + break; + default: + break; + } + + if (hd3ss3220->vbus && !hd3ss3220->id_gpiod) + hd3ss3220_regulator_control(hd3ss3220, role_state == USB_ROLE_HOST); + + hd3ss3220->role_state = role_state; +} + +static void output_poll_execute(struct work_struct *work) +{ + struct delayed_work *delayed_work = to_delayed_work(work); + struct hd3ss3220 *hd3ss3220 = container_of(delayed_work, + struct hd3ss3220, + output_poll_work); + enum usb_role role_state = hd3ss3220_get_attached_state(hd3ss3220); + + if (hd3ss3220->role_state != role_state) + hd3ss3220_set_role(hd3ss3220); + + schedule_delayed_work(&hd3ss3220->output_poll_work, HZ); +} + +static irqreturn_t hd3ss3220_irq(struct hd3ss3220 *hd3ss3220) +{ + int err; + + hd3ss3220_set_role(hd3ss3220); + err = regmap_write_bits(hd3ss3220->regmap, HD3SS3220_REG_CN_STAT_CTRL, + HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS, + HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS); + if (err < 0) + return IRQ_NONE; + + return IRQ_HANDLED; +} + +static irqreturn_t hd3ss3220_irq_handler(int irq, void *data) +{ + struct i2c_client *client = to_i2c_client(data); + struct hd3ss3220 *hd3ss3220 = i2c_get_clientdata(client); + + return hd3ss3220_irq(hd3ss3220); +} + +static int hd3ss3220_configure_power_opmode(struct hd3ss3220 *hd3ss3220, + struct fwnode_handle *connector) +{ + /* + * Supported power operation mode can be configured through device tree + */ + const char *cap_str; + int ret, power_opmode; + + ret = fwnode_property_read_string(connector, "typec-power-opmode", &cap_str); + if (ret) + return 0; + + power_opmode = typec_find_pwr_opmode(cap_str); + return hd3ss3220_set_power_opmode(hd3ss3220, power_opmode); +} + +static int hd3ss3220_configure_port_type(struct hd3ss3220 *hd3ss3220, + struct fwnode_handle *connector, + struct typec_capability *cap) +{ + /* + * Port type can be configured through device tree + */ + const char *cap_str; + int ret; + + ret = fwnode_property_read_string(connector, "power-role", &cap_str); + if (ret) + return 0; + + ret = typec_find_port_power_role(cap_str); + if (ret < 0) + return ret; + + cap->type = ret; + return hd3ss3220_set_port_type(hd3ss3220, cap->type); +} + +static int hd3ss3220_configure_source_pref(struct hd3ss3220 *hd3ss3220, + struct fwnode_handle *connector, + struct typec_capability *cap) +{ + /* + * Preferred role can be configured through device tree + */ + const char *cap_str; + int ret; + + ret = fwnode_property_read_string(connector, "try-power-role", &cap_str); + if (ret) + return 0; + + ret = typec_find_power_role(cap_str); + if (ret < 0) + return ret; + + cap->prefer_role = ret; + return hd3ss3220_set_source_pref(hd3ss3220, cap->prefer_role); +} + +static const struct regmap_config config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x0A, +}; + +static irqreturn_t hd3ss3220_id_isr(int irq, void *dev_id) +{ + struct hd3ss3220 *hd3ss3220 = dev_id; + int id; + + id = gpiod_get_value_cansleep(hd3ss3220->id_gpiod); + hd3ss3220_regulator_control(hd3ss3220, !id); + + return IRQ_HANDLED; +} + +static int hd3ss3220_probe(struct i2c_client *client) +{ + struct typec_capability typec_cap = { }; + struct fwnode_handle *connector, *ep; + struct hd3ss3220 *hd3ss3220; + struct regulator *vbus; + unsigned int data; + int ret; + + hd3ss3220 = devm_kzalloc(&client->dev, sizeof(struct hd3ss3220), + GFP_KERNEL); + if (!hd3ss3220) + return -ENOMEM; + + i2c_set_clientdata(client, hd3ss3220); + + hd3ss3220->dev = &client->dev; + hd3ss3220->regmap = devm_regmap_init_i2c(client, &config); + if (IS_ERR(hd3ss3220->regmap)) + return PTR_ERR(hd3ss3220->regmap); + + /* For backward compatibility check the connector child node first */ + connector = device_get_named_child_node(hd3ss3220->dev, "connector"); + if (connector) { + hd3ss3220->role_sw = fwnode_usb_role_switch_get(connector); + } else { + ep = fwnode_graph_get_next_endpoint(dev_fwnode(hd3ss3220->dev), NULL); + if (!ep) + return -ENODEV; + connector = fwnode_graph_get_remote_port_parent(ep); + fwnode_handle_put(ep); + if (!connector) + return -ENODEV; + hd3ss3220->role_sw = usb_role_switch_get(hd3ss3220->dev); + } + + if (IS_ERR(hd3ss3220->role_sw)) { + ret = PTR_ERR(hd3ss3220->role_sw); + goto err_put_fwnode; + } + + vbus = devm_of_regulator_get_optional(hd3ss3220->dev, + to_of_node(connector), + "vbus"); + if (IS_ERR(vbus) && vbus != ERR_PTR(-ENODEV)) { + ret = PTR_ERR(vbus); + dev_err(hd3ss3220->dev, "failed to get vbus: %d", ret); + goto err_put_fwnode; + } + + hd3ss3220->vbus = (vbus == ERR_PTR(-ENODEV) ? NULL : vbus); + + if (hd3ss3220->vbus) { + hd3ss3220->id_gpiod = devm_gpiod_get_optional(hd3ss3220->dev, + "id", + GPIOD_IN); + if (IS_ERR(hd3ss3220->id_gpiod)) { + ret = PTR_ERR(hd3ss3220->id_gpiod); + goto err_put_fwnode; + } + } + + if (hd3ss3220->id_gpiod) { + hd3ss3220->id_irq = gpiod_to_irq(hd3ss3220->id_gpiod); + if (hd3ss3220->id_irq < 0) { + ret = hd3ss3220->id_irq; + dev_err(hd3ss3220->dev, + "failed to get ID gpio: %d\n", + hd3ss3220->id_irq); + goto err_put_fwnode; + } + + ret = devm_request_threaded_irq(hd3ss3220->dev, + hd3ss3220->id_irq, NULL, + hd3ss3220_id_isr, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(hd3ss3220->dev), hd3ss3220); + if (ret < 0) { + dev_err(hd3ss3220->dev, "failed to get ID irq: %d\n", ret); + goto err_put_fwnode; + } + } + + typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; + typec_cap.driver_data = hd3ss3220; + typec_cap.type = TYPEC_PORT_DRP; + typec_cap.data = TYPEC_PORT_DRD; + typec_cap.ops = &hd3ss3220_ops; + typec_cap.fwnode = connector; + + ret = hd3ss3220_configure_source_pref(hd3ss3220, connector, &typec_cap); + if (ret < 0) + goto err_put_role; + + ret = hd3ss3220_configure_port_type(hd3ss3220, connector, &typec_cap); + if (ret < 0) + goto err_put_role; + + hd3ss3220->port = typec_register_port(&client->dev, &typec_cap); + if (IS_ERR(hd3ss3220->port)) { + ret = PTR_ERR(hd3ss3220->port); + goto err_put_role; + } + + ret = hd3ss3220_configure_power_opmode(hd3ss3220, connector); + if (ret < 0) + goto err_unreg_port; + + hd3ss3220_set_role(hd3ss3220); + ret = regmap_read(hd3ss3220->regmap, HD3SS3220_REG_CN_STAT_CTRL, &data); + if (ret < 0) + goto err_unreg_port; + + if (data & HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS) { + ret = regmap_write(hd3ss3220->regmap, + HD3SS3220_REG_CN_STAT_CTRL, + data | HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS); + if (ret < 0) + goto err_unreg_port; + } + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + hd3ss3220_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "hd3ss3220", &client->dev); + if (ret) + goto err_unreg_port; + } else { + INIT_DELAYED_WORK(&hd3ss3220->output_poll_work, output_poll_execute); + hd3ss3220->poll = true; + } + + ret = i2c_smbus_read_byte_data(client, HD3SS3220_REG_DEV_REV); + if (ret < 0) + goto err_unreg_port; + + fwnode_handle_put(connector); + + if (hd3ss3220->poll) + schedule_delayed_work(&hd3ss3220->output_poll_work, HZ); + + dev_info(&client->dev, "probed revision=0x%x\n", ret); + + return 0; +err_unreg_port: + typec_unregister_port(hd3ss3220->port); +err_put_role: + usb_role_switch_put(hd3ss3220->role_sw); +err_put_fwnode: + fwnode_handle_put(connector); + + return ret; +} + +static void hd3ss3220_remove(struct i2c_client *client) +{ + struct hd3ss3220 *hd3ss3220 = i2c_get_clientdata(client); + + if (hd3ss3220->poll) + cancel_delayed_work_sync(&hd3ss3220->output_poll_work); + + typec_unregister_port(hd3ss3220->port); + usb_role_switch_put(hd3ss3220->role_sw); +} + +static const struct of_device_id dev_ids[] = { + { .compatible = "ti,hd3ss3220"}, + {} +}; +MODULE_DEVICE_TABLE(of, dev_ids); + +static struct i2c_driver hd3ss3220_driver = { + .driver = { + .name = "hd3ss3220", + .of_match_table = dev_ids, + }, + .probe = hd3ss3220_probe, + .remove = hd3ss3220_remove, +}; + +module_i2c_driver(hd3ss3220_driver); + +MODULE_AUTHOR("Biju Das "); +MODULE_DESCRIPTION("TI HD3SS3220 DRP Port Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/mode_selection.c b/drivers/usb/typec/mode_selection.c new file mode 100644 index 000000000..cd376f67e --- /dev/null +++ b/drivers/usb/typec/mode_selection.c @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2025 Google LLC. + */ + +#include +#include +#include +#include +#include +#include + +#include "class.h" + +/** + * struct mode_state - State tracking for a specific Type-C alternate mode + * @svid: Standard or Vendor ID of the Alternate Mode + * @priority: Mode priority + * @error: Outcome of the last attempt to enter the mode + * @list: List head to link this mode state into a prioritized list + */ +struct mode_state { + u16 svid; + u8 priority; + int error; + struct list_head list; +}; + +/** + * struct mode_selection - Manages the selection and state of Alternate Modes + * @mode_list: Prioritized list of available Alternate Modes + * @lock: Mutex to protect mode_list + * @work: Work structure + * @partner: Handle to the Type-C partner device + * @active_svid: svid of currently active mode + * @timeout: Timeout for a mode entry attempt, ms + * @delay: Delay between mode entry/exit attempts, ms + */ +struct mode_selection { + struct list_head mode_list; + /* Protects the mode_list*/ + struct mutex lock; + struct delayed_work work; + struct typec_partner *partner; + u16 active_svid; + unsigned int timeout; + unsigned int delay; +}; + +/** + * struct mode_order - Mode activation tracking + * @svid: Standard or Vendor ID of the Alternate Mode + * @enter: Flag indicating if the driver is currently attempting to enter or + * exit the mode + * @result: Outcome of the attempt to activate the mode + */ +struct mode_order { + u16 svid; + int enter; + int result; +}; + +static int activate_altmode(struct device *dev, void *data) +{ + if (is_typec_partner_altmode(dev)) { + struct typec_altmode *alt = to_typec_altmode(dev); + struct mode_order *order = (struct mode_order *)data; + + if (order->svid == alt->svid) { + if (alt->ops && alt->ops->activate) + order->result = alt->ops->activate(alt, order->enter); + else + order->result = -EOPNOTSUPP; + return 1; + } + } + return 0; +} + +static int mode_selection_activate(struct mode_selection *sel, + const u16 svid, const int enter) + + __must_hold(&sel->lock) +{ + struct mode_order order = {.svid = svid, .enter = enter, .result = -ENODEV}; + + /* + * The port driver may acquire its internal mutex during alternate mode + * activation. Since this is the same mutex that may be held during the + * execution of typec_altmode_state_update(), it is crucial to release + * sel->mutex before activation to avoid potential deadlock. + * Note that sel->mode_list must remain invariant throughout this unlocked + * interval. + */ + mutex_unlock(&sel->lock); + device_for_each_child(&sel->partner->dev, &order, activate_altmode); + mutex_lock(&sel->lock); + + return order.result; +} + +static void mode_list_clean(struct mode_selection *sel) +{ + struct mode_state *ms, *tmp; + + list_for_each_entry_safe(ms, tmp, &sel->mode_list, list) { + list_del(&ms->list); + kfree(ms); + } +} + +/** + * mode_selection_work_fn() - Alternate mode activation task + * @work: work structure + * + * - If the Alternate Mode currently prioritized at the top of the list is already + * active, the entire selection process is considered finished. + * - If a different Alternate Mode is currently active, the system must exit that + * active mode first before attempting any new entry. + * + * The function then checks the result of the attempt to entre the current mode, + * stored in the `ms->error` field: + * - if the attempt FAILED, the mode is deactivated and removed from the list. + * - `ms->error` value of 0 signifies that the mode has not yet been activated. + * + * Once successfully activated, the task is scheduled for subsequent entry after + * a timeout period. The alternate mode driver is expected to call back with the + * actual mode entry result via `typec_altmode_state_update()`. + */ +static void mode_selection_work_fn(struct work_struct *work) +{ + struct mode_selection *sel = container_of(work, + struct mode_selection, work.work); + struct mode_state *ms; + unsigned int delay = sel->delay; + int result; + + guard(mutex)(&sel->lock); + + ms = list_first_entry_or_null(&sel->mode_list, struct mode_state, list); + if (!ms) + return; + + if (sel->active_svid == ms->svid) { + dev_dbg(&sel->partner->dev, "%x altmode is active\n", ms->svid); + mode_list_clean(sel); + } else if (sel->active_svid != 0) { + result = mode_selection_activate(sel, sel->active_svid, 0); + if (result) + mode_list_clean(sel); + else + sel->active_svid = 0; + } else if (ms->error) { + dev_err(&sel->partner->dev, "%x: entry error %pe\n", + ms->svid, ERR_PTR(ms->error)); + mode_selection_activate(sel, ms->svid, 0); + list_del(&ms->list); + kfree(ms); + } else { + result = mode_selection_activate(sel, ms->svid, 1); + if (result) { + dev_err(&sel->partner->dev, "%x: activation error %pe\n", + ms->svid, ERR_PTR(result)); + list_del(&ms->list); + kfree(ms); + } else { + delay = sel->timeout; + ms->error = -ETIMEDOUT; + } + } + + if (!list_empty(&sel->mode_list)) + schedule_delayed_work(&sel->work, msecs_to_jiffies(delay)); +} + +void typec_altmode_state_update(struct typec_partner *partner, const u16 svid, + const int error) +{ + struct mode_selection *sel = partner->sel; + struct mode_state *ms; + + if (sel) { + mutex_lock(&sel->lock); + ms = list_first_entry_or_null(&sel->mode_list, struct mode_state, list); + if (ms && ms->svid == svid) { + ms->error = error; + if (cancel_delayed_work(&sel->work)) + schedule_delayed_work(&sel->work, 0); + } + if (!error) + sel->active_svid = svid; + else + sel->active_svid = 0; + mutex_unlock(&sel->lock); + } +} +EXPORT_SYMBOL_GPL(typec_altmode_state_update); + +static int compare_priorities(void *priv, + const struct list_head *a, const struct list_head *b) +{ + const struct mode_state *msa = container_of(a, struct mode_state, list); + const struct mode_state *msb = container_of(b, struct mode_state, list); + + if (msa->priority < msb->priority) + return -1; + return 1; +} + +static int altmode_add_to_list(struct device *dev, void *data) +{ + if (is_typec_partner_altmode(dev)) { + struct list_head *list = (struct list_head *)data; + struct typec_altmode *altmode = to_typec_altmode(dev); + const struct typec_altmode *pdev = typec_altmode_get_partner(altmode); + struct mode_state *ms; + + if (pdev && altmode->ops && altmode->ops->activate) { + ms = kzalloc_obj(*ms); + if (!ms) + return -ENOMEM; + ms->svid = pdev->svid; + ms->priority = pdev->priority; + INIT_LIST_HEAD(&ms->list); + list_add_tail(&ms->list, list); + } + } + return 0; +} + +int typec_mode_selection_start(struct typec_partner *partner, + const unsigned int delay, const unsigned int timeout) +{ + struct mode_selection *sel; + int ret; + + if (partner->usb_mode == USB_MODE_USB4) + return -EBUSY; + + if (partner->sel) + return -EALREADY; + + sel = kzalloc_obj(*sel); + if (!sel) + return -ENOMEM; + + INIT_LIST_HEAD(&sel->mode_list); + + ret = device_for_each_child(&partner->dev, &sel->mode_list, + altmode_add_to_list); + + if (ret || list_empty(&sel->mode_list)) { + mode_list_clean(sel); + kfree(sel); + return ret; + } + + list_sort(NULL, &sel->mode_list, compare_priorities); + sel->partner = partner; + sel->delay = delay; + sel->timeout = timeout; + mutex_init(&sel->lock); + INIT_DELAYED_WORK(&sel->work, mode_selection_work_fn); + schedule_delayed_work(&sel->work, msecs_to_jiffies(delay)); + partner->sel = sel; + + return 0; +} +EXPORT_SYMBOL_GPL(typec_mode_selection_start); + +void typec_mode_selection_delete(struct typec_partner *partner) +{ + struct mode_selection *sel = partner->sel; + + if (sel) { + partner->sel = NULL; + cancel_delayed_work_sync(&sel->work); + mode_list_clean(sel); + mutex_destroy(&sel->lock); + kfree(sel); + } +} +EXPORT_SYMBOL_GPL(typec_mode_selection_delete); diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c new file mode 100644 index 000000000..9b908c46b --- /dev/null +++ b/drivers/usb/typec/mux.c @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Type-C Multiplexer/DeMultiplexer Switch support + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus + * Hans de Goede + */ + +#include +#include +#include +#include +#include +#include + +#include "class.h" +#include "mux.h" + +#define TYPEC_MUX_MAX_DEVS 3 + +struct typec_switch { + struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS]; + unsigned int num_sw_devs; +}; + +static int switch_fwnode_match(struct device *dev, const void *fwnode) +{ + if (!is_typec_switch_dev(dev)) + return 0; + + return device_match_fwnode(dev, fwnode); +} + +static void *typec_switch_match(const struct fwnode_handle *fwnode, + const char *id, void *data) +{ + struct typec_switch_dev **sw_devs = data; + struct device *dev; + int i; + + /* + * Device graph (OF graph) does not give any means to identify the + * device type or the device class of the remote port parent that @fwnode + * represents, so in order to identify the type or the class of @fwnode + * an additional device property is needed. With typec switches the + * property is named "orientation-switch" (@id). The value of the device + * property is ignored. + */ + if (id && !fwnode_property_present(fwnode, id)) + return NULL; + + /* + * At this point we are sure that @fwnode is a typec switch in all + * cases. If the switch hasn't yet been registered for some reason, the + * function "defers probe" for now. + */ + dev = class_find_device(&typec_mux_class, NULL, fwnode, + switch_fwnode_match); + + /* Skip duplicates */ + for (i = 0; i < TYPEC_MUX_MAX_DEVS; i++) + if (to_typec_switch_dev(dev) == sw_devs[i]) { + put_device(dev); + return NULL; + } + + return dev ? to_typec_switch_dev(dev) : ERR_PTR(-EPROBE_DEFER); +} + +/** + * fwnode_typec_switch_get - Find USB Type-C orientation switch + * @fwnode: The caller device node + * + * Finds a switch linked with @dev. Returns a reference to the switch on + * success, NULL if no matching connection was found, or + * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch + * has not been enumerated yet. + */ +struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode) +{ + struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS]; + struct typec_switch *sw; + int count; + int err; + int i; + + sw = kzalloc_obj(*sw); + if (!sw) + return ERR_PTR(-ENOMEM); + + count = fwnode_connection_find_matches(fwnode, "orientation-switch", + (void **)sw_devs, + typec_switch_match, + (void **)sw_devs, + ARRAY_SIZE(sw_devs)); + if (count <= 0) { + kfree(sw); + return NULL; + } + + for (i = 0; i < count; i++) { + if (IS_ERR(sw_devs[i])) { + err = PTR_ERR(sw_devs[i]); + goto put_sw_devs; + } + } + + for (i = 0; i < count; i++) { + WARN_ON(!try_module_get(sw_devs[i]->dev.parent->driver->owner)); + sw->sw_devs[i] = sw_devs[i]; + } + + sw->num_sw_devs = count; + + return sw; + +put_sw_devs: + for (i = 0; i < count; i++) { + if (!IS_ERR(sw_devs[i])) + put_device(&sw_devs[i]->dev); + } + + kfree(sw); + + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(fwnode_typec_switch_get); + +/** + * typec_switch_put - Release USB Type-C orientation switch + * @sw: USB Type-C orientation switch + * + * Decrement reference count for @sw. + */ +void typec_switch_put(struct typec_switch *sw) +{ + struct typec_switch_dev *sw_dev; + unsigned int i; + + if (IS_ERR_OR_NULL(sw)) + return; + + for (i = 0; i < sw->num_sw_devs; i++) { + sw_dev = sw->sw_devs[i]; + + module_put(sw_dev->dev.parent->driver->owner); + put_device(&sw_dev->dev); + } + kfree(sw); +} +EXPORT_SYMBOL_GPL(typec_switch_put); + +static void typec_switch_release(struct device *dev) +{ + kfree(to_typec_switch_dev(dev)); +} + +const struct device_type typec_switch_dev_type = { + .name = "orientation_switch", + .release = typec_switch_release, +}; + +/** + * typec_switch_register - Register USB Type-C orientation switch + * @parent: Parent device + * @desc: Orientation switch description + * + * This function registers a switch that can be used for routing the correct + * data pairs depending on the cable plug orientation from the USB Type-C + * connector to the USB controllers. USB Type-C plugs can be inserted + * right-side-up or upside-down. + */ +struct typec_switch_dev * +typec_switch_register(struct device *parent, + const struct typec_switch_desc *desc) +{ + struct typec_switch_dev *sw_dev; + int ret; + + if (!desc || !desc->set) + return ERR_PTR(-EINVAL); + + sw_dev = kzalloc_obj(*sw_dev); + if (!sw_dev) + return ERR_PTR(-ENOMEM); + + sw_dev->set = desc->set; + + device_initialize(&sw_dev->dev); + sw_dev->dev.parent = parent; + sw_dev->dev.fwnode = desc->fwnode; + sw_dev->dev.class = &typec_mux_class; + sw_dev->dev.type = &typec_switch_dev_type; + sw_dev->dev.driver_data = desc->drvdata; + ret = dev_set_name(&sw_dev->dev, "%s-switch", desc->name ? desc->name : dev_name(parent)); + if (ret) { + put_device(&sw_dev->dev); + return ERR_PTR(ret); + } + + ret = device_add(&sw_dev->dev); + if (ret) { + dev_err(parent, "failed to register switch (%d)\n", ret); + put_device(&sw_dev->dev); + return ERR_PTR(ret); + } + + return sw_dev; +} +EXPORT_SYMBOL_GPL(typec_switch_register); + +int typec_switch_set(struct typec_switch *sw, + enum typec_orientation orientation) +{ + struct typec_switch_dev *sw_dev; + unsigned int i; + int ret; + + if (IS_ERR_OR_NULL(sw)) + return 0; + + for (i = 0; i < sw->num_sw_devs; i++) { + sw_dev = sw->sw_devs[i]; + + ret = sw_dev->set(sw_dev, orientation); + if (ret && ret != -EOPNOTSUPP) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(typec_switch_set); + +/** + * typec_switch_unregister - Unregister USB Type-C orientation switch + * @sw_dev: USB Type-C orientation switch + * + * Unregister switch that was registered with typec_switch_register(). + */ +void typec_switch_unregister(struct typec_switch_dev *sw_dev) +{ + if (!IS_ERR_OR_NULL(sw_dev)) + device_unregister(&sw_dev->dev); +} +EXPORT_SYMBOL_GPL(typec_switch_unregister); + +void typec_switch_set_drvdata(struct typec_switch_dev *sw_dev, void *data) +{ + dev_set_drvdata(&sw_dev->dev, data); +} +EXPORT_SYMBOL_GPL(typec_switch_set_drvdata); + +void *typec_switch_get_drvdata(struct typec_switch_dev *sw_dev) +{ + return dev_get_drvdata(&sw_dev->dev); +} +EXPORT_SYMBOL_GPL(typec_switch_get_drvdata); + +/* ------------------------------------------------------------------------- */ + +struct typec_mux { + struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS]; + unsigned int num_mux_devs; +}; + +static int mux_fwnode_match(struct device *dev, const void *fwnode) +{ + if (!is_typec_mux_dev(dev)) + return 0; + + return device_match_fwnode(dev, fwnode); +} + +static void *typec_mux_match(const struct fwnode_handle *fwnode, + const char *id, void *data) +{ + struct device *dev; + + /* + * Device graph (OF graph) does not give any means to identify the + * device type or the device class of the remote port parent that @fwnode + * represents, so in order to identify the type or the class of @fwnode + * an additional device property is needed. With typec muxes the + * property is named "mode-switch" (@id). The value of the device + * property is ignored. + */ + if (id && !fwnode_property_present(fwnode, id)) + return NULL; + + dev = class_find_device(&typec_mux_class, NULL, fwnode, + mux_fwnode_match); + + return dev ? to_typec_mux_dev(dev) : ERR_PTR(-EPROBE_DEFER); +} + +/** + * fwnode_typec_mux_get - Find USB Type-C Multiplexer + * @fwnode: The caller device node + * + * Finds a mux linked to the caller. This function is primarily meant for the + * Type-C drivers. Returns a reference to the mux on success, NULL if no + * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection + * was found but the mux has not been enumerated yet. + */ +struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode) +{ + struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS]; + struct typec_mux *mux; + int count; + int err; + int i; + + mux = kzalloc_obj(*mux); + if (!mux) + return ERR_PTR(-ENOMEM); + + count = fwnode_connection_find_matches(fwnode, "mode-switch", + NULL, typec_mux_match, + (void **)mux_devs, + ARRAY_SIZE(mux_devs)); + if (count <= 0) { + kfree(mux); + return NULL; + } + + for (i = 0; i < count; i++) { + if (IS_ERR(mux_devs[i])) { + err = PTR_ERR(mux_devs[i]); + goto put_mux_devs; + } + } + + for (i = 0; i < count; i++) { + WARN_ON(!try_module_get(mux_devs[i]->dev.parent->driver->owner)); + mux->mux_devs[i] = mux_devs[i]; + } + + mux->num_mux_devs = count; + + return mux; + +put_mux_devs: + for (i = 0; i < count; i++) { + if (!IS_ERR(mux_devs[i])) + put_device(&mux_devs[i]->dev); + } + + kfree(mux); + + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(fwnode_typec_mux_get); + +/** + * typec_mux_put - Release handle to a Multiplexer + * @mux: USB Type-C Connector Multiplexer/DeMultiplexer + * + * Decrements reference count for @mux. + */ +void typec_mux_put(struct typec_mux *mux) +{ + struct typec_mux_dev *mux_dev; + unsigned int i; + + if (IS_ERR_OR_NULL(mux)) + return; + + for (i = 0; i < mux->num_mux_devs; i++) { + mux_dev = mux->mux_devs[i]; + module_put(mux_dev->dev.parent->driver->owner); + put_device(&mux_dev->dev); + } + kfree(mux); +} +EXPORT_SYMBOL_GPL(typec_mux_put); + +int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state) +{ + struct typec_mux_dev *mux_dev; + unsigned int i; + int ret; + + if (IS_ERR_OR_NULL(mux)) + return 0; + + for (i = 0; i < mux->num_mux_devs; i++) { + mux_dev = mux->mux_devs[i]; + + ret = mux_dev->set(mux_dev, state); + if (ret && ret != -EOPNOTSUPP) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(typec_mux_set); + +static void typec_mux_release(struct device *dev) +{ + kfree(to_typec_mux_dev(dev)); +} + +const struct device_type typec_mux_dev_type = { + .name = "mode_switch", + .release = typec_mux_release, +}; + +/** + * typec_mux_register - Register Multiplexer routing USB Type-C pins + * @parent: Parent device + * @desc: Multiplexer description + * + * USB Type-C connectors can be used for alternate modes of operation besides + * USB when Accessory/Alternate Modes are supported. With some of those modes, + * the pins on the connector need to be reconfigured. This function registers + * multiplexer switches routing the pins on the connector. + */ +struct typec_mux_dev * +typec_mux_register(struct device *parent, const struct typec_mux_desc *desc) +{ + struct typec_mux_dev *mux_dev; + int ret; + + if (!desc || !desc->set) + return ERR_PTR(-EINVAL); + + mux_dev = kzalloc_obj(*mux_dev); + if (!mux_dev) + return ERR_PTR(-ENOMEM); + + mux_dev->set = desc->set; + + device_initialize(&mux_dev->dev); + mux_dev->dev.parent = parent; + mux_dev->dev.fwnode = desc->fwnode; + mux_dev->dev.class = &typec_mux_class; + mux_dev->dev.type = &typec_mux_dev_type; + mux_dev->dev.driver_data = desc->drvdata; + ret = dev_set_name(&mux_dev->dev, "%s-mux", desc->name ? desc->name : dev_name(parent)); + if (ret) { + put_device(&mux_dev->dev); + return ERR_PTR(ret); + } + + ret = device_add(&mux_dev->dev); + if (ret) { + dev_err(parent, "failed to register mux (%d)\n", ret); + put_device(&mux_dev->dev); + return ERR_PTR(ret); + } + + return mux_dev; +} +EXPORT_SYMBOL_GPL(typec_mux_register); + +/** + * typec_mux_unregister - Unregister Multiplexer Switch + * @mux_dev: USB Type-C Connector Multiplexer/DeMultiplexer + * + * Unregister mux that was registered with typec_mux_register(). + */ +void typec_mux_unregister(struct typec_mux_dev *mux_dev) +{ + if (!IS_ERR_OR_NULL(mux_dev)) + device_unregister(&mux_dev->dev); +} +EXPORT_SYMBOL_GPL(typec_mux_unregister); + +void typec_mux_set_drvdata(struct typec_mux_dev *mux_dev, void *data) +{ + dev_set_drvdata(&mux_dev->dev, data); +} +EXPORT_SYMBOL_GPL(typec_mux_set_drvdata); + +void *typec_mux_get_drvdata(struct typec_mux_dev *mux_dev) +{ + return dev_get_drvdata(&mux_dev->dev); +} +EXPORT_SYMBOL_GPL(typec_mux_get_drvdata); + +const struct class typec_mux_class = { + .name = "typec_mux", +}; diff --git a/drivers/usb/typec/mux.h b/drivers/usb/typec/mux.h new file mode 100644 index 000000000..58f0f28b6 --- /dev/null +++ b/drivers/usb/typec/mux.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __USB_TYPEC_MUX__ +#define __USB_TYPEC_MUX__ + +#include + +struct typec_switch_dev { + struct device dev; + typec_switch_set_fn_t set; +}; + +struct typec_mux_dev { + struct device dev; + typec_mux_set_fn_t set; +}; + +#define to_typec_switch_dev(_dev_) container_of(_dev_, struct typec_switch_dev, dev) +#define to_typec_mux_dev(_dev_) container_of(_dev_, struct typec_mux_dev, dev) + +extern const struct device_type typec_switch_dev_type; +extern const struct device_type typec_mux_dev_type; + +#define is_typec_switch_dev(dev) ((dev)->type == &typec_switch_dev_type) +#define is_typec_mux_dev(dev) ((dev)->type == &typec_mux_dev_type) + +#endif /* __USB_TYPEC_MUX__ */ diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig new file mode 100644 index 000000000..6dd8f961b --- /dev/null +++ b/drivers/usb/typec/mux/Kconfig @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: GPL-2.0 + +menu "USB Type-C Multiplexer/DeMultiplexer Switch support" + +config TYPEC_MUX_FSA4480 + tristate "ON Semi FSA4480 Analog Audio Switch driver" + depends on I2C + select REGMAP_I2C + help + Driver for the ON Semiconductor FSA4480 Analog Audio Switch, which + provides support for muxing analog audio and sideband signals on a + common USB Type-C connector. + If compiled as a module, the module will be named fsa4480. + +config TYPEC_MUX_GPIO_SBU + tristate "Generic GPIO based SBU mux for USB Type-C applications" + help + Say Y or M if your system uses a GPIO based mux for managing the + connected state and the swapping of the SBU lines in a Type-C port. + +config TYPEC_MUX_PI3USB30532 + tristate "Pericom PI3USB30532 Type-C cross switch driver" + depends on I2C + help + Say Y or M if your system has a Pericom PI3USB30532 Type-C cross + switch / mux chip found on some devices with a Type-C port. + +config TYPEC_MUX_INTEL_PMC + tristate "Intel PMC mux control" + depends on ACPI + depends on INTEL_SCU_IPC + select USB_COMMON + select USB_ROLE_SWITCH + help + Driver for USB muxes controlled by Intel PMC FW. Intel PMC FW can + control the USB role switch and also the multiplexer/demultiplexer + switches used with USB Type-C Alternate Modes. + +config TYPEC_MUX_IT5205 + tristate "ITE IT5205 Type-C USB Alt Mode Passive MUX driver" + depends on I2C + select REGMAP_I2C + help + Driver for the ITE IT5205 Type-C USB Alternate Mode Passive MUX + which provides support for muxing DisplayPort and sideband signals + on a common USB Type-C connector. + If compiled as a module, the module will be named it5205. + +config TYPEC_MUX_NB7VPQ904M + tristate "On Semiconductor NB7VPQ904M Type-C redriver driver" + depends on I2C + depends on DRM || DRM=n + select DRM_AUX_BRIDGE if DRM_BRIDGE && OF + select REGMAP_I2C + help + Say Y or M if your system has a On Semiconductor NB7VPQ904M Type-C + redriver chip found on some devices with a Type-C port. + +config TYPEC_MUX_PS883X + tristate "Parade PS883x Type-C retimer driver" + depends on I2C + depends on DRM || DRM=n + select DRM_AUX_BRIDGE if DRM_BRIDGE && OF + select REGMAP_I2C + help + Say Y or M if your system has a Parade PS883x Type-C retimer chip + found on some devices with a Type-C port. + +config TYPEC_MUX_PTN36502 + tristate "NXP PTN36502 Type-C redriver driver" + depends on I2C + depends on DRM || DRM=n + select DRM_AUX_BRIDGE if DRM_BRIDGE && OF + select REGMAP_I2C + help + Say Y or M if your system has a NXP PTN36502 Type-C redriver chip + found on some devices with a Type-C port. + +config TYPEC_MUX_TUSB1046 + tristate "TI TUSB1046 Type-C crosspoint switch driver" + depends on I2C + help + Driver for the Texas Instruments TUSB1046-DCI crosspoint switch. + Supports flipping USB-C SuperSpeed lanes to adapt to orientation + changes, as well as muxing DisplayPort and sideband signals to a + common Type-C connector. + +config TYPEC_MUX_WCD939X_USBSS + tristate "Qualcomm WCD939x USBSS Analog Audio Switch driver" + depends on I2C + select REGMAP_I2C + help + Driver for the Qualcomm WCD939x Audio Codec USBSS domain which + provides support for muxing analog audio and sideband signals on a + common USB Type-C connector. + If compiled as a module, the module will be named wcd939x-usbss. + +endmenu diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile new file mode 100644 index 000000000..b4f599eb5 --- /dev/null +++ b/drivers/usb/typec/mux/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_TYPEC_MUX_FSA4480) += fsa4480.o +obj-$(CONFIG_TYPEC_MUX_GPIO_SBU) += gpio-sbu-mux.o +obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o +obj-$(CONFIG_TYPEC_MUX_INTEL_PMC) += intel_pmc_mux.o +obj-$(CONFIG_TYPEC_MUX_IT5205) += it5205.o +obj-$(CONFIG_TYPEC_MUX_NB7VPQ904M) += nb7vpq904m.o +obj-$(CONFIG_TYPEC_MUX_PS883X) += ps883x.o +obj-$(CONFIG_TYPEC_MUX_PTN36502) += ptn36502.o +obj-$(CONFIG_TYPEC_MUX_TUSB1046) += tusb1046.o +obj-$(CONFIG_TYPEC_MUX_WCD939X_USBSS) += wcd939x-usbss.o diff --git a/drivers/usb/typec/mux/fsa4480.c b/drivers/usb/typec/mux/fsa4480.c new file mode 100644 index 000000000..bea0c1dee --- /dev/null +++ b/drivers/usb/typec/mux/fsa4480.c @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021-2022 Linaro Ltd. + * Copyright (C) 2018-2020 The Linux Foundation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FSA4480_DEVICE_ID 0x00 + #define FSA4480_DEVICE_ID_VENDOR_ID GENMASK(7, 6) + #define FSA4480_DEVICE_ID_VERSION_ID GENMASK(5, 3) + #define FSA4480_DEVICE_ID_REV_ID GENMASK(2, 0) +#define FSA4480_SWITCH_ENABLE 0x04 +#define FSA4480_SWITCH_SELECT 0x05 +#define FSA4480_SWITCH_STATUS1 0x07 +#define FSA4480_SLOW_L 0x08 +#define FSA4480_SLOW_R 0x09 +#define FSA4480_SLOW_MIC 0x0a +#define FSA4480_SLOW_SENSE 0x0b +#define FSA4480_SLOW_GND 0x0c +#define FSA4480_DELAY_L_R 0x0d +#define FSA4480_DELAY_L_MIC 0x0e +#define FSA4480_DELAY_L_SENSE 0x0f +#define FSA4480_DELAY_L_AGND 0x10 +#define FSA4480_FUNCTION_ENABLE 0x12 +#define FSA4480_RESET 0x1e +#define FSA4480_MAX_REGISTER 0x1f + +#define FSA4480_ENABLE_DEVICE BIT(7) +#define FSA4480_ENABLE_SBU GENMASK(6, 5) +#define FSA4480_ENABLE_USB GENMASK(4, 3) +#define FSA4480_ENABLE_SENSE BIT(2) +#define FSA4480_ENABLE_MIC BIT(1) +#define FSA4480_ENABLE_AGND BIT(0) + +#define FSA4480_SEL_SBU_REVERSE GENMASK(6, 5) +#define FSA4480_SEL_USB GENMASK(4, 3) +#define FSA4480_SEL_SENSE BIT(2) +#define FSA4480_SEL_MIC BIT(1) +#define FSA4480_SEL_AGND BIT(0) + +#define FSA4480_ENABLE_AUTO_JACK_DETECT BIT(0) + +struct fsa4480 { + struct i2c_client *client; + + /* used to serialize concurrent change requests */ + struct mutex lock; + + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; + + struct regmap *regmap; + + enum typec_orientation orientation; + unsigned long mode; + unsigned int svid; + + u8 cur_enable; + bool swap_sbu_lanes; +}; + +static const struct regmap_config fsa4480_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = FSA4480_MAX_REGISTER, + /* Accesses only done under fsa4480->lock */ + .disable_locking = true, +}; + +static int fsa4480_set(struct fsa4480 *fsa) +{ + bool reverse = (fsa->orientation == TYPEC_ORIENTATION_REVERSE); + u8 enable = FSA4480_ENABLE_DEVICE; + u8 sel = 0; + + if (fsa->swap_sbu_lanes) + reverse = !reverse; + + /* USB Mode */ + if (fsa->mode < TYPEC_STATE_MODAL || + (!fsa->svid && (fsa->mode == TYPEC_MODE_USB2 || + fsa->mode == TYPEC_MODE_USB3))) { + enable |= FSA4480_ENABLE_USB; + sel = FSA4480_SEL_USB; + } else if (fsa->svid) { + switch (fsa->mode) { + /* DP Only */ + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + enable |= FSA4480_ENABLE_SBU; + if (reverse) + sel = FSA4480_SEL_SBU_REVERSE; + break; + + /* DP + USB */ + case TYPEC_DP_STATE_D: + case TYPEC_DP_STATE_F: + enable |= FSA4480_ENABLE_USB | FSA4480_ENABLE_SBU; + sel = FSA4480_SEL_USB; + if (reverse) + sel |= FSA4480_SEL_SBU_REVERSE; + break; + + default: + return -EOPNOTSUPP; + } + } else if (fsa->mode == TYPEC_MODE_AUDIO) { + /* Audio Accessory Mode, setup to auto Jack Detection */ + enable |= FSA4480_ENABLE_USB | FSA4480_ENABLE_AGND; + } else + return -EOPNOTSUPP; + + if (fsa->cur_enable & FSA4480_ENABLE_SBU) { + /* Disable SBU output while re-configuring the switch */ + regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, + fsa->cur_enable & ~FSA4480_ENABLE_SBU); + + /* 35us to allow the SBU switch to turn off */ + usleep_range(35, 1000); + } + + regmap_write(fsa->regmap, FSA4480_SWITCH_SELECT, sel); + regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, enable); + + /* Start AUDIO JACK DETECTION to setup MIC, AGND & Sense muxes */ + if (enable & FSA4480_ENABLE_AGND) + regmap_write(fsa->regmap, FSA4480_FUNCTION_ENABLE, + FSA4480_ENABLE_AUTO_JACK_DETECT); + + if (enable & FSA4480_ENABLE_SBU) { + /* 15us to allow the SBU switch to turn on again */ + usleep_range(15, 1000); + } + + fsa->cur_enable = enable; + + return 0; +} + +static int fsa4480_switch_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct fsa4480 *fsa = typec_switch_get_drvdata(sw); + int ret = 0; + + mutex_lock(&fsa->lock); + + if (fsa->orientation != orientation) { + fsa->orientation = orientation; + + ret = fsa4480_set(fsa); + } + + mutex_unlock(&fsa->lock); + + return ret; +} + +static int fsa4480_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) +{ + struct fsa4480 *fsa = typec_mux_get_drvdata(mux); + int ret = 0; + + mutex_lock(&fsa->lock); + + if (fsa->mode != state->mode) { + fsa->mode = state->mode; + + if (state->alt) + fsa->svid = state->alt->svid; + else + fsa->svid = 0; // No SVID + + ret = fsa4480_set(fsa); + } + + mutex_unlock(&fsa->lock); + + return ret; +} + +enum { + NORMAL_LANE_MAPPING, + INVERT_LANE_MAPPING, +}; + +#define DATA_LANES_COUNT 2 + +static const int supported_data_lane_mapping[][DATA_LANES_COUNT] = { + [NORMAL_LANE_MAPPING] = { 0, 1 }, + [INVERT_LANE_MAPPING] = { 1, 0 }, +}; + +static int fsa4480_parse_data_lanes_mapping(struct fsa4480 *fsa) +{ + struct fwnode_handle *ep; + u32 data_lanes[DATA_LANES_COUNT]; + int ret, i, j; + + ep = fwnode_graph_get_next_endpoint(dev_fwnode(&fsa->client->dev), NULL); + if (!ep) + return 0; + + ret = fwnode_property_read_u32_array(ep, "data-lanes", data_lanes, DATA_LANES_COUNT); + if (ret == -EINVAL) + /* Property isn't here, consider default mapping */ + goto out_done; + if (ret) { + dev_err(&fsa->client->dev, "invalid data-lanes property: %d\n", ret); + goto out_error; + } + + for (i = 0; i < ARRAY_SIZE(supported_data_lane_mapping); i++) { + for (j = 0; j < DATA_LANES_COUNT; j++) { + if (data_lanes[j] != supported_data_lane_mapping[i][j]) + break; + } + + if (j == DATA_LANES_COUNT) + break; + } + + switch (i) { + case NORMAL_LANE_MAPPING: + break; + case INVERT_LANE_MAPPING: + fsa->swap_sbu_lanes = true; + break; + default: + dev_err(&fsa->client->dev, "invalid data-lanes mapping\n"); + ret = -EINVAL; + goto out_error; + } + +out_done: + ret = 0; + +out_error: + fwnode_handle_put(ep); + + return ret; +} + +static int fsa4480_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct typec_switch_desc sw_desc = { }; + struct typec_mux_desc mux_desc = { }; + struct fsa4480 *fsa; + int val = 0; + int ret; + + fsa = devm_kzalloc(dev, sizeof(*fsa), GFP_KERNEL); + if (!fsa) + return -ENOMEM; + + fsa->client = client; + mutex_init(&fsa->lock); + + ret = fsa4480_parse_data_lanes_mapping(fsa); + if (ret) + return ret; + + fsa->regmap = devm_regmap_init_i2c(client, &fsa4480_regmap_config); + if (IS_ERR(fsa->regmap)) + return dev_err_probe(dev, PTR_ERR(fsa->regmap), "failed to initialize regmap\n"); + + ret = devm_regulator_get_enable_optional(dev, "vcc"); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + + ret = regmap_read(fsa->regmap, FSA4480_DEVICE_ID, &val); + if (ret) + return dev_err_probe(dev, -ENODEV, "FSA4480 not found\n"); + + dev_dbg(dev, "Found FSA4480 v%lu.%lu (Vendor ID = %lu)\n", + FIELD_GET(FSA4480_DEVICE_ID_VERSION_ID, val), + FIELD_GET(FSA4480_DEVICE_ID_REV_ID, val), + FIELD_GET(FSA4480_DEVICE_ID_VENDOR_ID, val)); + + /* Safe mode */ + fsa->cur_enable = FSA4480_ENABLE_DEVICE | FSA4480_ENABLE_USB; + fsa->mode = TYPEC_STATE_SAFE; + fsa->orientation = TYPEC_ORIENTATION_NONE; + + /* set default settings */ + regmap_write(fsa->regmap, FSA4480_SLOW_L, 0x00); + regmap_write(fsa->regmap, FSA4480_SLOW_R, 0x00); + regmap_write(fsa->regmap, FSA4480_SLOW_MIC, 0x00); + regmap_write(fsa->regmap, FSA4480_SLOW_SENSE, 0x00); + regmap_write(fsa->regmap, FSA4480_SLOW_GND, 0x00); + regmap_write(fsa->regmap, FSA4480_DELAY_L_R, 0x00); + regmap_write(fsa->regmap, FSA4480_DELAY_L_MIC, 0x00); + regmap_write(fsa->regmap, FSA4480_DELAY_L_SENSE, 0x00); + regmap_write(fsa->regmap, FSA4480_DELAY_L_AGND, 0x09); + regmap_write(fsa->regmap, FSA4480_SWITCH_SELECT, FSA4480_SEL_USB); + regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, fsa->cur_enable); + + sw_desc.drvdata = fsa; + sw_desc.fwnode = dev_fwnode(dev); + sw_desc.set = fsa4480_switch_set; + + fsa->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(fsa->sw)) + return dev_err_probe(dev, PTR_ERR(fsa->sw), "failed to register typec switch\n"); + + mux_desc.drvdata = fsa; + mux_desc.fwnode = dev_fwnode(dev); + mux_desc.set = fsa4480_mux_set; + + fsa->mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(fsa->mux)) { + typec_switch_unregister(fsa->sw); + return dev_err_probe(dev, PTR_ERR(fsa->mux), "failed to register typec mux\n"); + } + + i2c_set_clientdata(client, fsa); + return 0; +} + +static void fsa4480_remove(struct i2c_client *client) +{ + struct fsa4480 *fsa = i2c_get_clientdata(client); + + typec_mux_unregister(fsa->mux); + typec_switch_unregister(fsa->sw); +} + +static const struct i2c_device_id fsa4480_table[] = { + { .name = "fsa4480" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, fsa4480_table); + +static const struct of_device_id fsa4480_of_table[] = { + { .compatible = "fcs,fsa4480" }, + { } +}; +MODULE_DEVICE_TABLE(of, fsa4480_of_table); + +static struct i2c_driver fsa4480_driver = { + .driver = { + .name = "fsa4480", + .of_match_table = fsa4480_of_table, + }, + .probe = fsa4480_probe, + .remove = fsa4480_remove, + .id_table = fsa4480_table, +}; +module_i2c_driver(fsa4480_driver); + +MODULE_DESCRIPTION("ON Semiconductor FSA4480 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/mux/gpio-sbu-mux.c b/drivers/usb/typec/mux/gpio-sbu-mux.c new file mode 100644 index 000000000..1834f1a2d --- /dev/null +++ b/drivers/usb/typec/mux/gpio-sbu-mux.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include + +struct gpio_sbu_mux { + struct gpio_desc *enable_gpio; + struct gpio_desc *select_gpio; + + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; + + struct mutex lock; /* protect enabled and swapped */ + bool enabled; + bool swapped; +}; + +static int gpio_sbu_switch_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct gpio_sbu_mux *sbu_mux = typec_switch_get_drvdata(sw); + bool enabled; + bool swapped; + + mutex_lock(&sbu_mux->lock); + + enabled = sbu_mux->enabled; + swapped = sbu_mux->swapped; + + switch (orientation) { + case TYPEC_ORIENTATION_NONE: + enabled = false; + break; + case TYPEC_ORIENTATION_NORMAL: + swapped = false; + break; + case TYPEC_ORIENTATION_REVERSE: + swapped = true; + break; + } + + if (enabled != sbu_mux->enabled) + gpiod_set_value_cansleep(sbu_mux->enable_gpio, enabled); + + if (swapped != sbu_mux->swapped) + gpiod_set_value_cansleep(sbu_mux->select_gpio, swapped); + + sbu_mux->enabled = enabled; + sbu_mux->swapped = swapped; + + mutex_unlock(&sbu_mux->lock); + + return 0; +} + +static int gpio_sbu_mux_set(struct typec_mux_dev *mux, + struct typec_mux_state *state) +{ + struct gpio_sbu_mux *sbu_mux = typec_mux_get_drvdata(mux); + + if (!sbu_mux->enable_gpio) + return -EOPNOTSUPP; + + mutex_lock(&sbu_mux->lock); + + switch (state->mode) { + case TYPEC_STATE_SAFE: + case TYPEC_STATE_USB: + sbu_mux->enabled = false; + break; + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_D: + case TYPEC_DP_STATE_E: + sbu_mux->enabled = true; + break; + default: + break; + } + + gpiod_set_value_cansleep(sbu_mux->enable_gpio, sbu_mux->enabled); + + mutex_unlock(&sbu_mux->lock); + + return 0; +} + +static int gpio_sbu_mux_probe(struct platform_device *pdev) +{ + struct typec_switch_desc sw_desc = { }; + struct typec_mux_desc mux_desc = { }; + struct device *dev = &pdev->dev; + struct gpio_sbu_mux *sbu_mux; + + sbu_mux = devm_kzalloc(dev, sizeof(*sbu_mux), GFP_KERNEL); + if (!sbu_mux) + return -ENOMEM; + + mutex_init(&sbu_mux->lock); + + sbu_mux->enable_gpio = devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(sbu_mux->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(sbu_mux->enable_gpio), + "unable to acquire enable gpio\n"); + + sbu_mux->select_gpio = devm_gpiod_get(dev, "select", GPIOD_OUT_LOW); + if (IS_ERR(sbu_mux->select_gpio)) + return dev_err_probe(dev, PTR_ERR(sbu_mux->select_gpio), + "unable to acquire select gpio\n"); + + sw_desc.drvdata = sbu_mux; + sw_desc.fwnode = dev_fwnode(dev); + sw_desc.set = gpio_sbu_switch_set; + + sbu_mux->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(sbu_mux->sw)) + return dev_err_probe(dev, PTR_ERR(sbu_mux->sw), + "failed to register typec switch\n"); + + mux_desc.drvdata = sbu_mux; + mux_desc.fwnode = dev_fwnode(dev); + mux_desc.set = gpio_sbu_mux_set; + + sbu_mux->mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(sbu_mux->mux)) { + typec_switch_unregister(sbu_mux->sw); + return dev_err_probe(dev, PTR_ERR(sbu_mux->mux), + "failed to register typec mux\n"); + } + + platform_set_drvdata(pdev, sbu_mux); + + return 0; +} + +static void gpio_sbu_mux_remove(struct platform_device *pdev) +{ + struct gpio_sbu_mux *sbu_mux = platform_get_drvdata(pdev); + + gpiod_set_value_cansleep(sbu_mux->enable_gpio, 0); + + typec_mux_unregister(sbu_mux->mux); + typec_switch_unregister(sbu_mux->sw); +} + +static const struct of_device_id gpio_sbu_mux_match[] = { + { .compatible = "gpio-sbu-mux", }, + {} +}; +MODULE_DEVICE_TABLE(of, gpio_sbu_mux_match); + +static struct platform_driver gpio_sbu_mux_driver = { + .probe = gpio_sbu_mux_probe, + .remove = gpio_sbu_mux_remove, + .driver = { + .name = "gpio_sbu_mux", + .of_match_table = gpio_sbu_mux_match, + }, +}; +module_platform_driver(gpio_sbu_mux_driver); + +MODULE_DESCRIPTION("GPIO based SBU mux driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c new file mode 100644 index 000000000..219a32da1 --- /dev/null +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -0,0 +1,850 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Intel PMC USB mux control + * + * Copyright (C) 2020 Intel Corporation + * Author: Heikki Krogerus + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PMC_USBC_CMD 0xa7 + +/* Response status bits */ +#define PMC_USB_RESP_STATUS_FAILURE BIT(0) +#define PMC_USB_RESP_STATUS_FATAL BIT(1) + +/* "Usage" OOB Message field values */ +enum { + PMC_USB_CONNECT, + PMC_USB_DISCONNECT, + PMC_USB_SAFE_MODE, + PMC_USB_ALT_MODE, + PMC_USB_DP_HPD, +}; + +#define PMC_USB_MSG_USB2_PORT_SHIFT 0 +#define PMC_USB_MSG_USB3_PORT_SHIFT 4 +#define PMC_USB_MSG_UFP_SHIFT 4 +#define PMC_USB_MSG_ORI_HSL_SHIFT 5 +#define PMC_USB_MSG_ORI_AUX_SHIFT 6 + +/* Alt Mode Request */ +struct altmode_req { + u8 usage; + u8 mode_type; + u8 mode_id; + u8 reserved; + u32 mode_data; +} __packed; + +#define PMC_USB_MODE_TYPE_SHIFT 4 + +enum { + PMC_USB_MODE_TYPE_USB, + PMC_USB_MODE_TYPE_DP, + PMC_USB_MODE_TYPE_TBT, +}; + +/* Common Mode Data bits */ +#define PMC_USB_ALTMODE_RETIMER_CABLE BIT(2) + +#define PMC_USB_ALTMODE_ORI_SHIFT 1 +#define PMC_USB_ALTMODE_UFP_SHIFT 3 + +/* DP specific Mode Data bits */ +#define PMC_USB_ALTMODE_DP_MODE_SHIFT 8 + +/* TBT specific Mode Data bits */ +#define PMC_USB_ALTMODE_TBT_TYPE BIT(17) +#define PMC_USB_ALTMODE_CABLE_TYPE BIT(18) +#define PMC_USB_ALTMODE_ACTIVE_LINK BIT(20) +#define PMC_USB_ALTMODE_ACTIVE_CABLE BIT(22) +#define PMC_USB_ALTMODE_FORCE_LSR BIT(23) +#define PMC_USB_ALTMODE_CABLE_SPD(_s_) (((_s_) & GENMASK(2, 0)) << 25) +#define PMC_USB_ALTMODE_CABLE_USB31 1 +#define PMC_USB_ALTMODE_CABLE_10GPS 2 +#define PMC_USB_ALTMODE_CABLE_20GPS 3 +#define PMC_USB_ALTMODE_TBT_GEN(_g_) (((_g_) & GENMASK(1, 0)) << 28) + +/* Display HPD Request bits */ +#define PMC_USB_DP_HPD_LVL BIT(4) +#define PMC_USB_DP_HPD_IRQ BIT(5) + +/* + * Input Output Manager (IOM) PORT STATUS + */ +#define IOM_PORT_STATUS_ACTIVITY_TYPE_MASK GENMASK(9, 6) +#define IOM_PORT_STATUS_ACTIVITY_TYPE_SHIFT 6 +#define IOM_PORT_STATUS_ACTIVITY_TYPE_USB 0x03 +/* activity type: Safe Mode */ +#define IOM_PORT_STATUS_ACTIVITY_TYPE_SAFE_MODE 0x04 +/* activity type: Display Port */ +#define IOM_PORT_STATUS_ACTIVITY_TYPE_DP 0x05 +/* activity type: Display Port Multi Function Device */ +#define IOM_PORT_STATUS_ACTIVITY_TYPE_DP_MFD 0x06 +/* activity type: Thunderbolt */ +#define IOM_PORT_STATUS_ACTIVITY_TYPE_TBT 0x07 +#define IOM_PORT_STATUS_ACTIVITY_TYPE_ALT_MODE_USB 0x0c +#define IOM_PORT_STATUS_ACTIVITY_TYPE_ALT_MODE_TBT_USB 0x0d +/* Upstream Facing Port Information */ +#define IOM_PORT_STATUS_UFP BIT(10) +/* Display Port Hot Plug Detect status */ +#define IOM_PORT_STATUS_DHPD_HPD_STATUS_MASK GENMASK(13, 12) +#define IOM_PORT_STATUS_DHPD_HPD_STATUS_SHIFT 12 +#define IOM_PORT_STATUS_DHPD_HPD_STATUS_ASSERT 0x01 +#define IOM_PORT_STATUS_DHPD_HPD_SOURCE_TBT BIT(14) +#define IOM_PORT_STATUS_CONNECTED BIT(31) + +#define IOM_PORT_ACTIVITY_IS(_status_, _type_) \ + ((((_status_) & IOM_PORT_STATUS_ACTIVITY_TYPE_MASK) >> \ + IOM_PORT_STATUS_ACTIVITY_TYPE_SHIFT) == \ + (IOM_PORT_STATUS_ACTIVITY_TYPE_##_type_)) + +#define IOM_PORT_HPD_ASSERTED(_status_) \ + ((((_status_) & IOM_PORT_STATUS_DHPD_HPD_STATUS_MASK) >> \ + IOM_PORT_STATUS_DHPD_HPD_STATUS_SHIFT) & \ + IOM_PORT_STATUS_DHPD_HPD_STATUS_ASSERT) + +/* IOM port status register */ +#define IOM_PORT_STATUS_REGS(_offset_, _size_) ((_offset_) | (_size_)) +#define IOM_PORT_STATUS_REGS_SZ_MASK BIT(0) +#define IOM_PORT_STATUS_REGS_SZ_4 0 +#define IOM_PORT_STATUS_REGS_SZ_8 1 +#define IOM_PORT_STATUS_REGS_OFFSET(_d_) \ + ((_d_) & ~IOM_PORT_STATUS_REGS_SZ_MASK) +#define IOM_PORT_STATUS_REGS_SIZE(_d_) \ + (4 << ((_d_) & IOM_PORT_STATUS_REGS_SZ_MASK)) + +struct pmc_usb; + +struct pmc_usb_port { + int num; + u32 iom_status; + struct pmc_usb *pmc; + struct typec_mux_dev *typec_mux; + struct typec_switch_dev *typec_sw; + struct usb_role_switch *usb_sw; + + enum typec_orientation orientation; + enum usb_role role; + + u8 usb2_port; + u8 usb3_port; + + enum typec_orientation sbu_orientation; + enum typec_orientation hsl_orientation; +}; + +struct pmc_usb { + u8 num_ports; + struct device *dev; + struct intel_scu_ipc_dev *ipc; + struct acpi_device *iom_adev; + void __iomem *iom_base; + u32 iom_port_status_offset; + u8 iom_port_status_size; + + struct dentry *dentry; + + struct pmc_usb_port port[] __counted_by(num_ports); +}; + +static struct dentry *pmc_mux_debugfs_root; + +static void update_port_status(struct pmc_usb_port *port) +{ + u8 port_num; + + /* SoC expects the USB Type-C port numbers to start with 0 */ + port_num = port->usb3_port - 1; + + port->iom_status = readl(port->pmc->iom_base + + port->pmc->iom_port_status_offset + + port_num * port->pmc->iom_port_status_size); +} + +static int sbu_orientation(struct pmc_usb_port *port) +{ + if (port->sbu_orientation) + return port->sbu_orientation - 1; + + return port->orientation - 1; +} + +static int hsl_orientation(struct pmc_usb_port *port) +{ + if (port->hsl_orientation) + return port->hsl_orientation - 1; + + return port->orientation - 1; +} + +static bool is_pmc_mux_tbt(struct acpi_device *adev) +{ + return acpi_dev_hid_uid_match(adev, "INTC1072", NULL) || + acpi_dev_hid_uid_match(adev, "INTC1079", NULL); +} + +static int pmc_usb_send_command(struct intel_scu_ipc_dev *ipc, u8 *msg, u32 len) +{ + u8 response[4]; + u8 status_res; + int ret; + + /* + * Error bit will always be 0 with the USBC command. + * Status can be checked from the response message if the + * function intel_scu_ipc_dev_command succeeds. + */ + ret = intel_scu_ipc_dev_command(ipc, PMC_USBC_CMD, 0, msg, + len, response, sizeof(response)); + + if (ret) + return ret; + + status_res = (msg[0] & 0xf) < PMC_USB_SAFE_MODE ? + response[2] : response[1]; + + if (status_res & PMC_USB_RESP_STATUS_FAILURE) { + if (status_res & PMC_USB_RESP_STATUS_FATAL) + return -EIO; + + return -EBUSY; + } + + return 0; +} + +static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len) +{ + int retry_count = 3; + int ret; + + /* + * If PMC is busy then retry the command once again + */ + while (retry_count--) { + ret = pmc_usb_send_command(port->pmc->ipc, msg, len); + if (ret != -EBUSY) + break; + } + + return ret; +} + +static int +pmc_usb_mux_dp_hpd(struct pmc_usb_port *port, struct typec_displayport_data *dp) +{ + u8 msg[2] = { }; + int ret; + + msg[0] = PMC_USB_DP_HPD; + msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; + + /* Configure HPD first if HPD,IRQ comes together */ + if (!IOM_PORT_HPD_ASSERTED(port->iom_status) && + dp->status & DP_STATUS_IRQ_HPD && + dp->status & DP_STATUS_HPD_STATE) { + msg[1] = PMC_USB_DP_HPD_LVL; + ret = pmc_usb_command(port, msg, sizeof(msg)); + if (ret) + return ret; + } + + if (dp->status & DP_STATUS_IRQ_HPD) + msg[1] = PMC_USB_DP_HPD_IRQ; + + if (dp->status & DP_STATUS_HPD_STATE) + msg[1] |= PMC_USB_DP_HPD_LVL; + + return pmc_usb_command(port, msg, sizeof(msg)); +} + +static int +pmc_usb_mux_dp(struct pmc_usb_port *port, struct typec_mux_state *state) +{ + struct typec_displayport_data *data = state->data; + struct altmode_req req = { }; + int ret; + + if (IOM_PORT_ACTIVITY_IS(port->iom_status, DP) || + IOM_PORT_ACTIVITY_IS(port->iom_status, DP_MFD)) { + if (IOM_PORT_HPD_ASSERTED(port->iom_status) && + (!(data->status & DP_STATUS_IRQ_HPD) && + data->status & DP_STATUS_HPD_STATE)) + return 0; + + return pmc_usb_mux_dp_hpd(port, state->data); + } + + req.usage = PMC_USB_ALT_MODE; + req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; + req.mode_type = PMC_USB_MODE_TYPE_DP << PMC_USB_MODE_TYPE_SHIFT; + + req.mode_data = (port->orientation - 1) << PMC_USB_ALTMODE_ORI_SHIFT; + req.mode_data |= (port->role - 1) << PMC_USB_ALTMODE_UFP_SHIFT; + + req.mode_data |= (state->mode - TYPEC_STATE_MODAL) << + PMC_USB_ALTMODE_DP_MODE_SHIFT; + + if (!is_pmc_mux_tbt(port->pmc->iom_adev)) { + u8 cable_speed = (data->conf & DP_CONF_SIGNALLING_MASK) >> + DP_CONF_SIGNALLING_SHIFT; + + u8 cable_type = (data->conf & DP_CONF_CABLE_TYPE_MASK) >> + DP_CONF_CABLE_TYPE_SHIFT; + + req.mode_data |= PMC_USB_ALTMODE_CABLE_SPD(cable_speed); + + if (cable_type == DP_CONF_CABLE_TYPE_OPTICAL) + req.mode_data |= PMC_USB_ALTMODE_CABLE_TYPE; + else if (cable_type == DP_CONF_CABLE_TYPE_RE_TIMER) + req.mode_data |= PMC_USB_ALTMODE_ACTIVE_CABLE | + PMC_USB_ALTMODE_RETIMER_CABLE; + else if (cable_type == DP_CONF_CABLE_TYPE_RE_DRIVER) + req.mode_data |= PMC_USB_ALTMODE_ACTIVE_CABLE; + } + + ret = pmc_usb_command(port, (void *)&req, sizeof(req)); + if (ret) + return ret; + + if (data->status & (DP_STATUS_IRQ_HPD | DP_STATUS_HPD_STATE)) + return pmc_usb_mux_dp_hpd(port, state->data); + + return 0; +} + +static int +pmc_usb_mux_tbt(struct pmc_usb_port *port, struct typec_mux_state *state) +{ + struct typec_thunderbolt_data *data = state->data; + u8 cable_rounded = TBT_CABLE_ROUNDED_SUPPORT(data->cable_mode); + u8 cable_speed = TBT_CABLE_SPEED(data->cable_mode); + struct altmode_req req = { }; + + if (IOM_PORT_ACTIVITY_IS(port->iom_status, TBT) || + IOM_PORT_ACTIVITY_IS(port->iom_status, ALT_MODE_TBT_USB)) + return 0; + + req.usage = PMC_USB_ALT_MODE; + req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; + req.mode_type = PMC_USB_MODE_TYPE_TBT << PMC_USB_MODE_TYPE_SHIFT; + + req.mode_data = (port->orientation - 1) << PMC_USB_ALTMODE_ORI_SHIFT; + req.mode_data |= (port->role - 1) << PMC_USB_ALTMODE_UFP_SHIFT; + + if (TBT_ADAPTER(data->device_mode) == TBT_ADAPTER_TBT3) + req.mode_data |= PMC_USB_ALTMODE_TBT_TYPE; + + if (data->cable_mode & TBT_CABLE_OPTICAL) + req.mode_data |= PMC_USB_ALTMODE_CABLE_TYPE; + + if (data->cable_mode & TBT_CABLE_LINK_TRAINING) + req.mode_data |= PMC_USB_ALTMODE_ACTIVE_LINK; + + if (acpi_dev_hid_uid_match(port->pmc->iom_adev, "INTC1072", NULL) || + acpi_dev_hid_uid_match(port->pmc->iom_adev, "INTC1079", NULL)) { + if ((data->enter_vdo & TBT_ENTER_MODE_ACTIVE_CABLE) || + (data->cable_mode & TBT_CABLE_RETIMER)) + req.mode_data |= PMC_USB_ALTMODE_RETIMER_CABLE; + } else { + if (data->enter_vdo & TBT_ENTER_MODE_ACTIVE_CABLE) + req.mode_data |= PMC_USB_ALTMODE_ACTIVE_CABLE; + + if (data->cable_mode & TBT_CABLE_RETIMER) + req.mode_data |= PMC_USB_ALTMODE_RETIMER_CABLE; + } + + req.mode_data |= PMC_USB_ALTMODE_CABLE_SPD(cable_speed); + + req.mode_data |= PMC_USB_ALTMODE_TBT_GEN(cable_rounded); + + return pmc_usb_command(port, (void *)&req, sizeof(req)); +} + +static int +pmc_usb_mux_usb4(struct pmc_usb_port *port, struct typec_mux_state *state) +{ + struct enter_usb_data *data = state->data; + struct altmode_req req = { }; + u8 cable_speed; + + if (IOM_PORT_ACTIVITY_IS(port->iom_status, TBT) || + IOM_PORT_ACTIVITY_IS(port->iom_status, ALT_MODE_TBT_USB)) + return 0; + + req.usage = PMC_USB_ALT_MODE; + req.usage |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; + req.mode_type = PMC_USB_MODE_TYPE_TBT << PMC_USB_MODE_TYPE_SHIFT; + + /* USB4 Mode */ + req.mode_data = PMC_USB_ALTMODE_FORCE_LSR; + + if (data->active_link_training) + req.mode_data |= PMC_USB_ALTMODE_ACTIVE_LINK; + + req.mode_data |= (port->orientation - 1) << PMC_USB_ALTMODE_ORI_SHIFT; + req.mode_data |= (port->role - 1) << PMC_USB_ALTMODE_UFP_SHIFT; + + switch ((data->eudo & EUDO_CABLE_TYPE_MASK) >> EUDO_CABLE_TYPE_SHIFT) { + case EUDO_CABLE_TYPE_PASSIVE: + break; + case EUDO_CABLE_TYPE_OPTICAL: + req.mode_data |= PMC_USB_ALTMODE_CABLE_TYPE; + fallthrough; + case EUDO_CABLE_TYPE_RE_TIMER: + if (!acpi_dev_hid_uid_match(port->pmc->iom_adev, "INTC1072", NULL) || + !acpi_dev_hid_uid_match(port->pmc->iom_adev, "INTC1079", NULL)) + req.mode_data |= PMC_USB_ALTMODE_RETIMER_CABLE; + fallthrough; + default: + if (acpi_dev_hid_uid_match(port->pmc->iom_adev, "INTC1072", NULL) || + acpi_dev_hid_uid_match(port->pmc->iom_adev, "INTC1079", NULL)) + req.mode_data |= PMC_USB_ALTMODE_RETIMER_CABLE; + else + req.mode_data |= PMC_USB_ALTMODE_ACTIVE_CABLE; + + /* Configure data rate to rounded in the case of Active TBT3 + * and USB4 cables. + */ + req.mode_data |= PMC_USB_ALTMODE_TBT_GEN(1); + break; + } + + cable_speed = (data->eudo & EUDO_CABLE_SPEED_MASK) >> EUDO_CABLE_SPEED_SHIFT; + req.mode_data |= PMC_USB_ALTMODE_CABLE_SPD(cable_speed); + + return pmc_usb_command(port, (void *)&req, sizeof(req)); +} + +static int pmc_usb_mux_safe_state(struct pmc_usb_port *port, + struct typec_mux_state *state) +{ + u8 msg; + + if (IOM_PORT_ACTIVITY_IS(port->iom_status, SAFE_MODE)) + return 0; + + if ((IOM_PORT_ACTIVITY_IS(port->iom_status, DP) || + IOM_PORT_ACTIVITY_IS(port->iom_status, DP_MFD)) && + state->alt && state->alt->svid == USB_TYPEC_DP_SID) + return 0; + + if ((IOM_PORT_ACTIVITY_IS(port->iom_status, TBT) || + IOM_PORT_ACTIVITY_IS(port->iom_status, ALT_MODE_TBT_USB)) && + state->alt && state->alt->svid == USB_TYPEC_TBT_SID) + return 0; + + msg = PMC_USB_SAFE_MODE; + msg |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; + + return pmc_usb_command(port, &msg, sizeof(msg)); +} + +static int pmc_usb_disconnect(struct pmc_usb_port *port) +{ + struct typec_displayport_data data = { }; + u8 msg[2]; + + if (!(port->iom_status & IOM_PORT_STATUS_CONNECTED)) + return 0; + + /* Clear DisplayPort HPD if it's still asserted. */ + if (IOM_PORT_HPD_ASSERTED(port->iom_status)) + pmc_usb_mux_dp_hpd(port, &data); + + msg[0] = PMC_USB_DISCONNECT; + msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; + + msg[1] = port->usb2_port << PMC_USB_MSG_USB2_PORT_SHIFT; + + return pmc_usb_command(port, msg, sizeof(msg)); +} + +static int pmc_usb_connect(struct pmc_usb_port *port, enum usb_role role) +{ + u8 ufp = role == USB_ROLE_DEVICE ? 1 : 0; + u8 msg[2]; + int ret; + + if (port->orientation == TYPEC_ORIENTATION_NONE) + return -EINVAL; + + if (port->iom_status & IOM_PORT_STATUS_CONNECTED) { + if (port->role == role || port->role == USB_ROLE_NONE) + return 0; + + /* Role swap */ + ret = pmc_usb_disconnect(port); + if (ret) + return ret; + } + + msg[0] = PMC_USB_CONNECT; + msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; + + msg[1] = port->usb2_port << PMC_USB_MSG_USB2_PORT_SHIFT; + msg[1] |= ufp << PMC_USB_MSG_UFP_SHIFT; + msg[1] |= hsl_orientation(port) << PMC_USB_MSG_ORI_HSL_SHIFT; + msg[1] |= sbu_orientation(port) << PMC_USB_MSG_ORI_AUX_SHIFT; + + return pmc_usb_command(port, msg, sizeof(msg)); +} + +static int +pmc_usb_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) +{ + struct pmc_usb_port *port = typec_mux_get_drvdata(mux); + + update_port_status(port); + + if (port->orientation == TYPEC_ORIENTATION_NONE || port->role == USB_ROLE_NONE) + return 0; + + if (state->mode == TYPEC_STATE_SAFE) + return pmc_usb_mux_safe_state(port, state); + if (state->mode == TYPEC_STATE_USB) + return pmc_usb_connect(port, port->role); + + if (state->alt) { + switch (state->alt->svid) { + case USB_TYPEC_TBT_SID: + return pmc_usb_mux_tbt(port, state); + case USB_TYPEC_DP_SID: + return pmc_usb_mux_dp(port, state); + } + } else { + switch (state->mode) { + case TYPEC_MODE_USB2: + /* REVISIT: Try with usb3_port set to 0? */ + break; + case TYPEC_MODE_USB3: + return pmc_usb_connect(port, port->role); + case TYPEC_MODE_USB4: + return pmc_usb_mux_usb4(port, state); + } + } + + return -EOPNOTSUPP; +} + +static int pmc_usb_set_orientation(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct pmc_usb_port *port = typec_switch_get_drvdata(sw); + + update_port_status(port); + + port->orientation = orientation; + + return 0; +} + +static int pmc_usb_set_role(struct usb_role_switch *sw, enum usb_role role) +{ + struct pmc_usb_port *port = usb_role_switch_get_drvdata(sw); + int ret; + + update_port_status(port); + + if (role == USB_ROLE_NONE) + ret = pmc_usb_disconnect(port); + else + ret = pmc_usb_connect(port, role); + + port->role = role; + + return ret; +} + +static int pmc_usb_register_port(struct pmc_usb *pmc, int index, + struct fwnode_handle *fwnode) +{ + struct pmc_usb_port *port = &pmc->port[index]; + struct usb_role_switch_desc desc = { }; + struct typec_switch_desc sw_desc = { }; + struct typec_mux_desc mux_desc = { }; + const char *str; + int ret; + + ret = fwnode_property_read_u8(fwnode, "usb2-port-number", &port->usb2_port); + if (ret) + return ret; + + ret = fwnode_property_read_u8(fwnode, "usb3-port-number", &port->usb3_port); + if (ret) + return ret; + + ret = fwnode_property_read_string(fwnode, "sbu-orientation", &str); + if (!ret) + port->sbu_orientation = typec_find_orientation(str); + + ret = fwnode_property_read_string(fwnode, "hsl-orientation", &str); + if (!ret) + port->hsl_orientation = typec_find_orientation(str); + + port->num = index; + port->pmc = pmc; + + sw_desc.fwnode = fwnode; + sw_desc.drvdata = port; + sw_desc.name = fwnode_get_name(fwnode); + sw_desc.set = pmc_usb_set_orientation; + + port->typec_sw = typec_switch_register(pmc->dev, &sw_desc); + if (IS_ERR(port->typec_sw)) + return PTR_ERR(port->typec_sw); + + mux_desc.fwnode = fwnode; + mux_desc.drvdata = port; + mux_desc.name = fwnode_get_name(fwnode); + mux_desc.set = pmc_usb_mux_set; + + port->typec_mux = typec_mux_register(pmc->dev, &mux_desc); + if (IS_ERR(port->typec_mux)) { + ret = PTR_ERR(port->typec_mux); + goto err_unregister_switch; + } + + desc.fwnode = fwnode; + desc.driver_data = port; + desc.name = fwnode_get_name(fwnode); + desc.set = pmc_usb_set_role; + desc.allow_userspace_control = true; + + port->usb_sw = usb_role_switch_register(pmc->dev, &desc); + if (IS_ERR(port->usb_sw)) { + ret = PTR_ERR(port->usb_sw); + goto err_unregister_mux; + } + + return 0; + +err_unregister_mux: + typec_mux_unregister(port->typec_mux); + +err_unregister_switch: + typec_switch_unregister(port->typec_sw); + + return ret; +} + +/* IOM ACPI IDs and IOM_PORT_STATUS_OFFSET */ +static const struct acpi_device_id iom_acpi_ids[] = { + /* TigerLake */ + { "INTC1072", IOM_PORT_STATUS_REGS(0x560, IOM_PORT_STATUS_REGS_SZ_4) }, + + /* AlderLake */ + { "INTC1079", IOM_PORT_STATUS_REGS(0x160, IOM_PORT_STATUS_REGS_SZ_4) }, + + /* Meteor Lake */ + { "INTC107A", IOM_PORT_STATUS_REGS(0x160, IOM_PORT_STATUS_REGS_SZ_4) }, + + /* Lunar Lake */ + { "INTC10EA", IOM_PORT_STATUS_REGS(0x150, IOM_PORT_STATUS_REGS_SZ_8) }, + {} +}; + +static int pmc_usb_probe_iom(struct pmc_usb *pmc) +{ + struct list_head resource_list; + struct resource_entry *rentry; + static const struct acpi_device_id *dev_id; + struct acpi_device *adev = NULL; + int ret; + + for (dev_id = &iom_acpi_ids[0]; dev_id->id[0]; dev_id++) { + adev = acpi_dev_get_first_match_dev(dev_id->id, NULL, -1); + if (adev) + break; + } + if (!adev) + return -ENODEV; + + pmc->iom_port_status_offset = IOM_PORT_STATUS_REGS_OFFSET(dev_id->driver_data); + pmc->iom_port_status_size = IOM_PORT_STATUS_REGS_SIZE(dev_id->driver_data); + + INIT_LIST_HEAD(&resource_list); + ret = acpi_dev_get_memory_resources(adev, &resource_list); + if (ret < 0) { + acpi_dev_put(adev); + return ret; + } + + rentry = list_first_entry_or_null(&resource_list, struct resource_entry, node); + if (rentry) + pmc->iom_base = devm_ioremap_resource(pmc->dev, rentry->res); + + acpi_dev_free_resource_list(&resource_list); + + if (!pmc->iom_base) { + acpi_dev_put(adev); + return -ENOMEM; + } + + if (IS_ERR(pmc->iom_base)) { + acpi_dev_put(adev); + return PTR_ERR(pmc->iom_base); + } + + pmc->iom_adev = adev; + + return 0; +} + +static int port_iom_status_show(struct seq_file *s, void *unused) +{ + struct pmc_usb_port *port = s->private; + + update_port_status(port); + seq_printf(s, "0x%08x\n", port->iom_status); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(port_iom_status); + +static void pmc_mux_port_debugfs_init(struct pmc_usb_port *port) +{ + struct dentry *debugfs_dir; + char name[8]; + + snprintf(name, sizeof(name), "port%d", port->usb3_port - 1); + + debugfs_dir = debugfs_create_dir(name, port->pmc->dentry); + debugfs_create_file("iom_status", 0400, debugfs_dir, port, + &port_iom_status_fops); +} + +static int pmc_usb_probe(struct platform_device *pdev) +{ + struct fwnode_handle *fwnode = NULL; + struct pmc_usb *pmc; + u8 num_ports = 0; + int i = 0; + int ret; + + device_for_each_child_node(&pdev->dev, fwnode) + num_ports++; + + /* The IOM microcontroller has a limitation of max 4 ports. */ + if (num_ports > 4) { + dev_err(&pdev->dev, "driver limited to 4 ports\n"); + return -ERANGE; + } + + pmc = devm_kzalloc(&pdev->dev, struct_size(pmc, port, num_ports), GFP_KERNEL); + if (!pmc) + return -ENOMEM; + + pmc->num_ports = num_ports; + + pmc->ipc = devm_intel_scu_ipc_dev_get(&pdev->dev); + if (!pmc->ipc) + return -EPROBE_DEFER; + + pmc->dev = &pdev->dev; + + ret = pmc_usb_probe_iom(pmc); + if (ret) + return ret; + + pmc->dentry = debugfs_create_dir(dev_name(pmc->dev), pmc_mux_debugfs_root); + + /* + * For every physical USB connector (USB2 and USB3 combo) there is a + * child ACPI device node under the PMC mux ACPI device object. + */ + for (i = 0; i < pmc->num_ports; i++) { + fwnode = device_get_next_child_node(pmc->dev, fwnode); + if (!fwnode) + break; + + ret = pmc_usb_register_port(pmc, i, fwnode); + if (ret) { + fwnode_handle_put(fwnode); + goto err_remove_ports; + } + + pmc_mux_port_debugfs_init(&pmc->port[i]); + } + + platform_set_drvdata(pdev, pmc); + + return 0; + +err_remove_ports: + for (i = 0; i < pmc->num_ports; i++) { + typec_switch_unregister(pmc->port[i].typec_sw); + typec_mux_unregister(pmc->port[i].typec_mux); + usb_role_switch_unregister(pmc->port[i].usb_sw); + } + + acpi_dev_put(pmc->iom_adev); + + debugfs_remove(pmc->dentry); + + return ret; +} + +static void pmc_usb_remove(struct platform_device *pdev) +{ + struct pmc_usb *pmc = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < pmc->num_ports; i++) { + typec_switch_unregister(pmc->port[i].typec_sw); + typec_mux_unregister(pmc->port[i].typec_mux); + usb_role_switch_unregister(pmc->port[i].usb_sw); + } + + acpi_dev_put(pmc->iom_adev); + + debugfs_remove(pmc->dentry); +} + +static const struct acpi_device_id pmc_usb_acpi_ids[] = { + { "INTC105C", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, pmc_usb_acpi_ids); + +static struct platform_driver pmc_usb_driver = { + .driver = { + .name = "intel_pmc_usb", + .acpi_match_table = ACPI_PTR(pmc_usb_acpi_ids), + }, + .probe = pmc_usb_probe, + .remove = pmc_usb_remove, +}; + +static int __init pmc_usb_init(void) +{ + pmc_mux_debugfs_root = debugfs_create_dir("intel_pmc_mux", usb_debug_root); + + return platform_driver_register(&pmc_usb_driver); +} +module_init(pmc_usb_init); + +static void __exit pmc_usb_exit(void) +{ + platform_driver_unregister(&pmc_usb_driver); + debugfs_remove(pmc_mux_debugfs_root); +} +module_exit(pmc_usb_exit); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel PMC USB mux control"); diff --git a/drivers/usb/typec/mux/it5205.c b/drivers/usb/typec/mux/it5205.c new file mode 100644 index 000000000..5e1a120b2 --- /dev/null +++ b/drivers/usb/typec/mux/it5205.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ITE IT5205 Type-C USB alternate mode passive mux + * + * Copyright (c) 2020 MediaTek Inc. + * Copyright (c) 2024 Collabora Ltd. + * AngeloGioacchino Del Regno + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IT5205_REG_CHIP_ID(x) (0x4 + (x)) +#define IT5205FN_CHIP_ID 0x35303235 /* "5025" -> "5205" */ + +/* MUX power down register */ +#define IT5205_REG_MUXPDR 0x10 +#define IT5205_MUX_POWER_DOWN BIT(0) + +/* MUX control register */ +#define IT5205_REG_MUXCR 0x11 +#define IT5205_POLARITY_INVERTED BIT(4) +#define IT5205_DP_USB_CTRL_MASK GENMASK(3, 0) +#define IT5205_DP 0x0f +#define IT5205_DP_USB 0x03 +#define IT5205_USB 0x07 + +/* Vref Select Register */ +#define IT5205_REG_VSR 0x10 +#define IT5205_VREF_SELECT_MASK GENMASK(5, 4) +#define IT5205_VREF_SELECT_3_3V 0x00 +#define IT5205_VREF_SELECT_OFF 0x20 + +/* CSBU Over Voltage Protection Register */ +#define IT5205_REG_CSBUOVPSR 0x1e +#define IT5205_OVP_SELECT_MASK GENMASK(5, 4) +#define IT5205_OVP_3_90V 0x00 +#define IT5205_OVP_3_68V 0x10 +#define IT5205_OVP_3_62V 0x20 +#define IT5205_OVP_3_57V 0x30 + +/* CSBU Switch Register */ +#define IT5205_REG_CSBUSR 0x22 +#define IT5205_CSBUSR_SWITCH BIT(0) + +/* Interrupt Switch Register */ +#define IT5205_REG_ISR 0x25 +#define IT5205_ISR_CSBU_MASK BIT(4) +#define IT5205_ISR_CSBU_OVP BIT(0) + +struct it5205 { + struct i2c_client *client; + struct regmap *regmap; + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; +}; + +static int it5205_switch_set(struct typec_switch_dev *sw, enum typec_orientation orientation) +{ + struct it5205 *it = typec_switch_get_drvdata(sw); + + switch (orientation) { + case TYPEC_ORIENTATION_NORMAL: + regmap_update_bits(it->regmap, IT5205_REG_MUXCR, + IT5205_POLARITY_INVERTED, 0); + break; + case TYPEC_ORIENTATION_REVERSE: + regmap_update_bits(it->regmap, IT5205_REG_MUXCR, + IT5205_POLARITY_INVERTED, IT5205_POLARITY_INVERTED); + break; + case TYPEC_ORIENTATION_NONE: + fallthrough; + default: + regmap_write(it->regmap, IT5205_REG_MUXCR, 0); + break; + } + + return 0; +} + +static int it5205_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) +{ + struct it5205 *it = typec_mux_get_drvdata(mux); + u8 val; + + if (state->mode >= TYPEC_STATE_MODAL && + state->alt->svid != USB_TYPEC_DP_SID) + return -EINVAL; + + switch (state->mode) { + case TYPEC_STATE_USB: + val = IT5205_USB; + break; + case TYPEC_DP_STATE_C: + fallthrough; + case TYPEC_DP_STATE_E: + val = IT5205_DP; + break; + case TYPEC_DP_STATE_D: + val = IT5205_DP_USB; + break; + case TYPEC_STATE_SAFE: + fallthrough; + default: + val = 0; + break; + } + + return regmap_update_bits(it->regmap, IT5205_REG_MUXCR, + IT5205_DP_USB_CTRL_MASK, val); +} + +static irqreturn_t it5205_irq_handler(int irq, void *data) +{ + struct it5205 *it = data; + int ret; + u32 val; + + ret = regmap_read(it->regmap, IT5205_REG_ISR, &val); + if (ret) + return IRQ_NONE; + + if (val & IT5205_ISR_CSBU_OVP) { + dev_warn(&it->client->dev, "Overvoltage detected!\n"); + + /* Reset CSBU */ + regmap_update_bits(it->regmap, IT5205_REG_CSBUSR, + IT5205_CSBUSR_SWITCH, 0); + regmap_update_bits(it->regmap, IT5205_REG_CSBUSR, + IT5205_CSBUSR_SWITCH, IT5205_CSBUSR_SWITCH); + } + + return IRQ_HANDLED; +} + +static void it5205_enable_ovp(struct it5205 *it) +{ + /* Select Vref 3.3v */ + regmap_update_bits(it->regmap, IT5205_REG_VSR, + IT5205_VREF_SELECT_MASK, IT5205_VREF_SELECT_3_3V); + + /* Trigger OVP at 3.68V */ + regmap_update_bits(it->regmap, IT5205_REG_CSBUOVPSR, + IT5205_OVP_SELECT_MASK, IT5205_OVP_3_68V); + + /* Unmask OVP interrupt */ + regmap_update_bits(it->regmap, IT5205_REG_ISR, + IT5205_ISR_CSBU_MASK, 0); + + /* Enable CSBU Interrupt */ + regmap_update_bits(it->regmap, IT5205_REG_CSBUSR, + IT5205_CSBUSR_SWITCH, IT5205_CSBUSR_SWITCH); +} + +static const struct regmap_config it5205_regmap = { + .max_register = 0x2f, + .reg_bits = 8, + .val_bits = 8, +}; + +static int it5205_probe(struct i2c_client *client) +{ + struct typec_switch_desc sw_desc = { }; + struct typec_mux_desc mux_desc = { }; + struct device *dev = &client->dev; + struct it5205 *it; + u32 val, chipid = 0; + int i, ret; + + it = devm_kzalloc(dev, sizeof(*it), GFP_KERNEL); + if (!it) + return -ENOMEM; + + ret = devm_regulator_get_enable(dev, "vcc"); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + + it->client = client; + + it->regmap = devm_regmap_init_i2c(client, &it5205_regmap); + if (IS_ERR(it->regmap)) + return dev_err_probe(dev, PTR_ERR(it->regmap), + "Failed to init regmap\n"); + + /* IT5205 needs a long time to power up after enabling regulator */ + msleep(50); + + /* Unset poweroff bit */ + ret = regmap_write(it->regmap, IT5205_REG_MUXPDR, 0); + if (ret) + return dev_err_probe(dev, ret, "Failed to set power on\n"); + + /* Read the 32 bits ChipID */ + for (i = 3; i >= 0; i--) { + ret = regmap_read(it->regmap, IT5205_REG_CHIP_ID(i), &val); + if (ret) + return ret; + + chipid |= val << (i * 8); + } + + if (chipid != IT5205FN_CHIP_ID) + return dev_err_probe(dev, -EINVAL, + "Unknown ChipID 0x%x\n", chipid); + + /* Initialize as USB mode with default (non-inverted) polarity */ + ret = regmap_write(it->regmap, IT5205_REG_MUXCR, IT5205_USB); + if (ret) + return dev_err_probe(dev, ret, "Cannot set mode to USB\n"); + + sw_desc.drvdata = it; + sw_desc.fwnode = dev_fwnode(dev); + sw_desc.set = it5205_switch_set; + + it->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(it->sw)) + return dev_err_probe(dev, PTR_ERR(it->sw), + "failed to register typec switch\n"); + + mux_desc.drvdata = it; + mux_desc.fwnode = dev_fwnode(dev); + mux_desc.set = it5205_mux_set; + + it->mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(it->mux)) { + typec_switch_unregister(it->sw); + return dev_err_probe(dev, PTR_ERR(it->mux), + "failed to register typec mux\n"); + } + + i2c_set_clientdata(client, it); + + if (of_property_read_bool(dev->of_node, "ite,ovp-enable") && client->irq) { + it5205_enable_ovp(it); + + ret = devm_request_threaded_irq(dev, client->irq, NULL, + it5205_irq_handler, + IRQF_ONESHOT, dev_name(dev), it); + if (ret) { + typec_mux_unregister(it->mux); + typec_switch_unregister(it->sw); + return dev_err_probe(dev, ret, "Failed to request irq\n"); + } + } + + return 0; +} + +static void it5205_remove(struct i2c_client *client) +{ + struct it5205 *it = i2c_get_clientdata(client); + + typec_mux_unregister(it->mux); + typec_switch_unregister(it->sw); +} + +static const struct i2c_device_id it5205_table[] = { + { .name = "it5205" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, it5205_table); + +static const struct of_device_id it5205_of_table[] = { + { .compatible = "ite,it5205" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, it5205_of_table); + +static struct i2c_driver it5205_driver = { + .driver = { + .name = "it5205", + .of_match_table = it5205_of_table, + }, + .probe = it5205_probe, + .remove = it5205_remove, + .id_table = it5205_table, +}; +module_i2c_driver(it5205_driver); + +MODULE_AUTHOR("Tianping Fang "); +MODULE_AUTHOR("AngeloGioacchino Del Regno "); +MODULE_DESCRIPTION("ITE IT5205 alternate mode passive MUX driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/mux/nb7vpq904m.c b/drivers/usb/typec/mux/nb7vpq904m.c new file mode 100644 index 000000000..d1fa26ff4 --- /dev/null +++ b/drivers/usb/typec/mux/nb7vpq904m.c @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * OnSemi NB7VPQ904M Type-C driver + * + * Copyright (C) 2023 Dmitry Baryshkov + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NB7_CHNA 0 +#define NB7_CHNB 1 +#define NB7_CHNC 2 +#define NB7_CHND 3 +#define NB7_IS_CHAN_AD(channel) (channel == NB7_CHNA || channel == NB7_CHND) + +#define GEN_DEV_SET_REG 0x00 + +#define GEN_DEV_SET_CHIP_EN BIT(0) +#define GEN_DEV_SET_CHNA_EN BIT(4) +#define GEN_DEV_SET_CHNB_EN BIT(5) +#define GEN_DEV_SET_CHNC_EN BIT(6) +#define GEN_DEV_SET_CHND_EN BIT(7) + +#define GEN_DEV_SET_OP_MODE_MASK GENMASK(3, 1) + +#define GEN_DEV_SET_OP_MODE_DP_CC2 0 +#define GEN_DEV_SET_OP_MODE_DP_CC1 1 +#define GEN_DEV_SET_OP_MODE_DP_4LANE 2 +#define GEN_DEV_SET_OP_MODE_USB 5 + +#define EQ_SETTING_REG_BASE 0x01 +#define EQ_SETTING_REG(n) (EQ_SETTING_REG_BASE + (n) * 2) +#define EQ_SETTING_MASK GENMASK(3, 1) + +#define OUTPUT_COMPRESSION_AND_POL_REG_BASE 0x02 +#define OUTPUT_COMPRESSION_AND_POL_REG(n) (OUTPUT_COMPRESSION_AND_POL_REG_BASE + (n) * 2) +#define OUTPUT_COMPRESSION_MASK GENMASK(2, 1) + +#define FLAT_GAIN_REG_BASE 0x18 +#define FLAT_GAIN_REG(n) (FLAT_GAIN_REG_BASE + (n) * 2) +#define FLAT_GAIN_MASK GENMASK(1, 0) + +#define LOSS_MATCH_REG_BASE 0x19 +#define LOSS_MATCH_REG(n) (LOSS_MATCH_REG_BASE + (n) * 2) +#define LOSS_MATCH_MASK GENMASK(1, 0) + +#define AUX_CC_REG 0x09 + +#define CHIP_VERSION_REG 0x17 + +struct nb7vpq904m { + struct i2c_client *client; + struct gpio_desc *enable_gpio; + struct regulator *vcc_supply; + struct regmap *regmap; + struct typec_switch_dev *sw; + struct typec_retimer *retimer; + + bool swap_data_lanes; + struct typec_switch *typec_switch; + struct typec_mux *typec_mux; + + struct mutex lock; /* protect non-concurrent retimer & switch */ + + enum typec_orientation orientation; + unsigned long mode; + unsigned int svid; +}; + +static void nb7vpq904m_set_channel(struct nb7vpq904m *nb7, unsigned int channel, bool dp) +{ + u8 eq, out_comp, flat_gain, loss_match; + + if (dp) { + eq = NB7_IS_CHAN_AD(channel) ? 0x6 : 0x4; + out_comp = 0x3; + flat_gain = NB7_IS_CHAN_AD(channel) ? 0x2 : 0x1; + loss_match = 0x3; + } else { + eq = 0x4; + out_comp = 0x3; + flat_gain = NB7_IS_CHAN_AD(channel) ? 0x3 : 0x1; + loss_match = NB7_IS_CHAN_AD(channel) ? 0x1 : 0x3; + } + + regmap_update_bits(nb7->regmap, EQ_SETTING_REG(channel), + EQ_SETTING_MASK, FIELD_PREP(EQ_SETTING_MASK, eq)); + regmap_update_bits(nb7->regmap, OUTPUT_COMPRESSION_AND_POL_REG(channel), + OUTPUT_COMPRESSION_MASK, FIELD_PREP(OUTPUT_COMPRESSION_MASK, out_comp)); + regmap_update_bits(nb7->regmap, FLAT_GAIN_REG(channel), + FLAT_GAIN_MASK, FIELD_PREP(FLAT_GAIN_MASK, flat_gain)); + regmap_update_bits(nb7->regmap, LOSS_MATCH_REG(channel), + LOSS_MATCH_MASK, FIELD_PREP(LOSS_MATCH_MASK, loss_match)); +} + +static int nb7vpq904m_set(struct nb7vpq904m *nb7) +{ + bool reverse = (nb7->orientation == TYPEC_ORIENTATION_REVERSE); + + switch (nb7->mode) { + case TYPEC_STATE_SAFE: + regmap_write(nb7->regmap, GEN_DEV_SET_REG, + GEN_DEV_SET_CHIP_EN | + GEN_DEV_SET_CHNA_EN | + GEN_DEV_SET_CHNB_EN | + GEN_DEV_SET_CHNC_EN | + GEN_DEV_SET_CHND_EN | + FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, + GEN_DEV_SET_OP_MODE_USB)); + nb7vpq904m_set_channel(nb7, NB7_CHNA, false); + nb7vpq904m_set_channel(nb7, NB7_CHNB, false); + nb7vpq904m_set_channel(nb7, NB7_CHNC, false); + nb7vpq904m_set_channel(nb7, NB7_CHND, false); + regmap_write(nb7->regmap, AUX_CC_REG, 0x2); + + return 0; + + case TYPEC_STATE_USB: + /* + * Normal Orientation (CC1) + * A -> USB RX + * B -> USB TX + * C -> X + * D -> X + * Flipped Orientation (CC2) + * A -> X + * B -> X + * C -> USB TX + * D -> USB RX + * + * Reversed if data lanes are swapped + */ + if (reverse ^ nb7->swap_data_lanes) { + regmap_write(nb7->regmap, GEN_DEV_SET_REG, + GEN_DEV_SET_CHIP_EN | + GEN_DEV_SET_CHNA_EN | + GEN_DEV_SET_CHNB_EN | + FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, + GEN_DEV_SET_OP_MODE_USB)); + nb7vpq904m_set_channel(nb7, NB7_CHNA, false); + nb7vpq904m_set_channel(nb7, NB7_CHNB, false); + } else { + regmap_write(nb7->regmap, GEN_DEV_SET_REG, + GEN_DEV_SET_CHIP_EN | + GEN_DEV_SET_CHNC_EN | + GEN_DEV_SET_CHND_EN | + FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, + GEN_DEV_SET_OP_MODE_USB)); + nb7vpq904m_set_channel(nb7, NB7_CHNC, false); + nb7vpq904m_set_channel(nb7, NB7_CHND, false); + } + regmap_write(nb7->regmap, AUX_CC_REG, 0x2); + + return 0; + + default: + if (nb7->svid != USB_TYPEC_DP_SID) + return -EINVAL; + + break; + } + + /* DP Altmode Setup */ + + regmap_write(nb7->regmap, AUX_CC_REG, reverse ? 0x1 : 0x0); + + switch (nb7->mode) { + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + /* + * Normal Orientation (CC1) + * A -> DP3 + * B -> DP2 + * C -> DP1 + * D -> DP0 + * Flipped Orientation (CC2) + * A -> DP0 + * B -> DP1 + * C -> DP2 + * D -> DP3 + */ + regmap_write(nb7->regmap, GEN_DEV_SET_REG, + GEN_DEV_SET_CHIP_EN | + GEN_DEV_SET_CHNA_EN | + GEN_DEV_SET_CHNB_EN | + GEN_DEV_SET_CHNC_EN | + GEN_DEV_SET_CHND_EN | + FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, + GEN_DEV_SET_OP_MODE_DP_4LANE)); + nb7vpq904m_set_channel(nb7, NB7_CHNA, true); + nb7vpq904m_set_channel(nb7, NB7_CHNB, true); + nb7vpq904m_set_channel(nb7, NB7_CHNC, true); + nb7vpq904m_set_channel(nb7, NB7_CHND, true); + break; + + case TYPEC_DP_STATE_D: + case TYPEC_DP_STATE_F: + regmap_write(nb7->regmap, GEN_DEV_SET_REG, + GEN_DEV_SET_CHIP_EN | + GEN_DEV_SET_CHNA_EN | + GEN_DEV_SET_CHNB_EN | + GEN_DEV_SET_CHNC_EN | + GEN_DEV_SET_CHND_EN | + FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK, + reverse ^ nb7->swap_data_lanes ? + GEN_DEV_SET_OP_MODE_DP_CC2 + : GEN_DEV_SET_OP_MODE_DP_CC1)); + + /* + * Normal Orientation (CC1) + * A -> USB RX + * B -> USB TX + * C -> DP1 + * D -> DP0 + * Flipped Orientation (CC2) + * A -> DP0 + * B -> DP1 + * C -> USB TX + * D -> USB RX + * + * Reversed if data lanes are swapped + */ + if (nb7->swap_data_lanes) { + nb7vpq904m_set_channel(nb7, NB7_CHNA, !reverse); + nb7vpq904m_set_channel(nb7, NB7_CHNB, !reverse); + nb7vpq904m_set_channel(nb7, NB7_CHNC, reverse); + nb7vpq904m_set_channel(nb7, NB7_CHND, reverse); + } else { + nb7vpq904m_set_channel(nb7, NB7_CHNA, reverse); + nb7vpq904m_set_channel(nb7, NB7_CHNB, reverse); + nb7vpq904m_set_channel(nb7, NB7_CHNC, !reverse); + nb7vpq904m_set_channel(nb7, NB7_CHND, !reverse); + } + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int nb7vpq904m_sw_set(struct typec_switch_dev *sw, enum typec_orientation orientation) +{ + struct nb7vpq904m *nb7 = typec_switch_get_drvdata(sw); + int ret; + + ret = typec_switch_set(nb7->typec_switch, orientation); + if (ret) + return ret; + + mutex_lock(&nb7->lock); + + if (nb7->orientation != orientation) { + nb7->orientation = orientation; + + ret = nb7vpq904m_set(nb7); + } + + mutex_unlock(&nb7->lock); + + return ret; +} + +static int nb7vpq904m_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) +{ + struct nb7vpq904m *nb7 = typec_retimer_get_drvdata(retimer); + struct typec_mux_state mux_state; + int ret = 0; + + mutex_lock(&nb7->lock); + + if (nb7->mode != state->mode) { + nb7->mode = state->mode; + + if (state->alt) + nb7->svid = state->alt->svid; + else + nb7->svid = 0; // No SVID + + ret = nb7vpq904m_set(nb7); + } + + mutex_unlock(&nb7->lock); + + if (ret) + return ret; + + mux_state.alt = state->alt; + mux_state.data = state->data; + mux_state.mode = state->mode; + + return typec_mux_set(nb7->typec_mux, &mux_state); +} + +static const struct regmap_config nb7_regmap = { + .max_register = 0x1f, + .reg_bits = 8, + .val_bits = 8, +}; + +enum { + NORMAL_LANE_MAPPING, + INVERT_LANE_MAPPING, +}; + +#define DATA_LANES_COUNT 4 + +static const int supported_data_lane_mapping[][DATA_LANES_COUNT] = { + [NORMAL_LANE_MAPPING] = { 0, 1, 2, 3 }, + [INVERT_LANE_MAPPING] = { 3, 2, 1, 0 }, +}; + +static int nb7vpq904m_parse_data_lanes_mapping(struct nb7vpq904m *nb7) +{ + struct device_node *ep; + u32 data_lanes[4]; + int ret, i, j; + + ep = of_graph_get_endpoint_by_regs(nb7->client->dev.of_node, 1, 0); + + if (!ep) + return 0; + + + ret = of_property_count_u32_elems(ep, "data-lanes"); + if (ret == -EINVAL) + /* Property isn't here, consider default mapping */ + goto out_done; + if (ret < 0) + goto out_error; + + if (ret != DATA_LANES_COUNT) { + dev_err(&nb7->client->dev, "expected 4 data lanes\n"); + ret = -EINVAL; + goto out_error; + } + + ret = of_property_read_u32_array(ep, "data-lanes", data_lanes, DATA_LANES_COUNT); + if (ret) + goto out_error; + + for (i = 0; i < ARRAY_SIZE(supported_data_lane_mapping); i++) { + for (j = 0; j < DATA_LANES_COUNT; j++) { + if (data_lanes[j] != supported_data_lane_mapping[i][j]) + break; + } + + if (j == DATA_LANES_COUNT) + break; + } + + switch (i) { + case NORMAL_LANE_MAPPING: + break; + case INVERT_LANE_MAPPING: + nb7->swap_data_lanes = true; + dev_info(&nb7->client->dev, "using inverted data lanes mapping\n"); + break; + default: + dev_err(&nb7->client->dev, "invalid data lanes mapping\n"); + ret = -EINVAL; + goto out_error; + } + +out_done: + ret = 0; + +out_error: + of_node_put(ep); + + return ret; +} + +static int nb7vpq904m_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct typec_switch_desc sw_desc = { }; + struct typec_retimer_desc retimer_desc = { }; + struct nb7vpq904m *nb7; + int ret; + + nb7 = devm_kzalloc(dev, sizeof(*nb7), GFP_KERNEL); + if (!nb7) + return -ENOMEM; + + nb7->client = client; + + nb7->regmap = devm_regmap_init_i2c(client, &nb7_regmap); + if (IS_ERR(nb7->regmap)) { + dev_err(&client->dev, "Failed to allocate register map\n"); + return PTR_ERR(nb7->regmap); + } + + nb7->mode = TYPEC_STATE_SAFE; + nb7->orientation = TYPEC_ORIENTATION_NONE; + + mutex_init(&nb7->lock); + + nb7->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(nb7->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(nb7->enable_gpio), + "unable to acquire enable gpio\n"); + + nb7->vcc_supply = devm_regulator_get_optional(dev, "vcc"); + if (IS_ERR(nb7->vcc_supply)) + return PTR_ERR(nb7->vcc_supply); + + nb7->typec_switch = fwnode_typec_switch_get(dev->fwnode); + if (IS_ERR(nb7->typec_switch)) + return dev_err_probe(dev, PTR_ERR(nb7->typec_switch), + "failed to acquire orientation-switch\n"); + + nb7->typec_mux = fwnode_typec_mux_get(dev->fwnode); + if (IS_ERR(nb7->typec_mux)) { + ret = dev_err_probe(dev, PTR_ERR(nb7->typec_mux), + "Failed to acquire mode-switch\n"); + goto err_switch_put; + } + + ret = nb7vpq904m_parse_data_lanes_mapping(nb7); + if (ret) + goto err_mux_put; + + ret = regulator_enable(nb7->vcc_supply); + if (ret) + dev_warn(dev, "Failed to enable vcc: %d\n", ret); + + gpiod_set_value(nb7->enable_gpio, 1); + + ret = drm_aux_bridge_register(dev); + if (ret) + goto err_disable_gpio; + + sw_desc.drvdata = nb7; + sw_desc.fwnode = dev->fwnode; + sw_desc.set = nb7vpq904m_sw_set; + + nb7->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(nb7->sw)) { + ret = dev_err_probe(dev, PTR_ERR(nb7->sw), + "Error registering typec switch\n"); + goto err_disable_gpio; + } + + retimer_desc.drvdata = nb7; + retimer_desc.fwnode = dev->fwnode; + retimer_desc.set = nb7vpq904m_retimer_set; + + nb7->retimer = typec_retimer_register(dev, &retimer_desc); + if (IS_ERR(nb7->retimer)) { + ret = dev_err_probe(dev, PTR_ERR(nb7->retimer), + "Error registering typec retimer\n"); + goto err_switch_unregister; + } + + return 0; + +err_switch_unregister: + typec_switch_unregister(nb7->sw); + +err_disable_gpio: + gpiod_set_value(nb7->enable_gpio, 0); + regulator_disable(nb7->vcc_supply); + +err_mux_put: + typec_mux_put(nb7->typec_mux); + +err_switch_put: + typec_switch_put(nb7->typec_switch); + + return ret; +} + +static void nb7vpq904m_remove(struct i2c_client *client) +{ + struct nb7vpq904m *nb7 = i2c_get_clientdata(client); + + typec_retimer_unregister(nb7->retimer); + typec_switch_unregister(nb7->sw); + + gpiod_set_value(nb7->enable_gpio, 0); + + regulator_disable(nb7->vcc_supply); + + typec_mux_put(nb7->typec_mux); + typec_switch_put(nb7->typec_switch); +} + +static const struct i2c_device_id nb7vpq904m_table[] = { + { .name = "nb7vpq904m" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nb7vpq904m_table); + +static const struct of_device_id nb7vpq904m_of_table[] = { + { .compatible = "onnn,nb7vpq904m" }, + { } +}; +MODULE_DEVICE_TABLE(of, nb7vpq904m_of_table); + +static struct i2c_driver nb7vpq904m_driver = { + .driver = { + .name = "nb7vpq904m", + .of_match_table = nb7vpq904m_of_table, + }, + .probe = nb7vpq904m_probe, + .remove = nb7vpq904m_remove, + .id_table = nb7vpq904m_table, +}; + +module_i2c_driver(nb7vpq904m_driver); + +MODULE_AUTHOR("Dmitry Baryshkov "); +MODULE_DESCRIPTION("OnSemi NB7VPQ904M Type-C driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/mux/pi3usb30532.c b/drivers/usb/typec/mux/pi3usb30532.c new file mode 100644 index 000000000..985683fe4 --- /dev/null +++ b/drivers/usb/typec/mux/pi3usb30532.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Pericom PI3USB30532 Type-C cross switch / mux driver + * + * Copyright (c) 2017-2018 Hans de Goede + */ + +#include +#include +#include +#include +#include +#include + +#define PI3USB30532_CONF 0x00 + +#define PI3USB30532_CONF_OPEN 0x00 +#define PI3USB30532_CONF_SWAP 0x01 +#define PI3USB30532_CONF_4LANE_DP 0x02 +#define PI3USB30532_CONF_USB3 0x04 +#define PI3USB30532_CONF_USB3_AND_2LANE_DP 0x06 + +struct pi3usb30532 { + struct i2c_client *client; + struct mutex lock; /* protects the cached conf register */ + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; + u8 conf; +}; + +static int pi3usb30532_set_conf(struct pi3usb30532 *pi, u8 new_conf) +{ + int ret = 0; + + if (pi->conf == new_conf) + return 0; + + ret = i2c_smbus_write_byte_data(pi->client, PI3USB30532_CONF, new_conf); + if (ret) { + dev_err(&pi->client->dev, "Error writing conf: %d\n", ret); + return ret; + } + + pi->conf = new_conf; + return 0; +} + +static int pi3usb30532_sw_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct pi3usb30532 *pi = typec_switch_get_drvdata(sw); + u8 new_conf; + int ret; + + mutex_lock(&pi->lock); + new_conf = pi->conf; + + switch (orientation) { + case TYPEC_ORIENTATION_NONE: + new_conf = PI3USB30532_CONF_OPEN; + break; + case TYPEC_ORIENTATION_NORMAL: + new_conf &= ~PI3USB30532_CONF_SWAP; + break; + case TYPEC_ORIENTATION_REVERSE: + new_conf |= PI3USB30532_CONF_SWAP; + break; + } + + ret = pi3usb30532_set_conf(pi, new_conf); + mutex_unlock(&pi->lock); + + return ret; +} + +static int +pi3usb30532_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) +{ + struct pi3usb30532 *pi = typec_mux_get_drvdata(mux); + u8 new_conf; + int ret; + + mutex_lock(&pi->lock); + new_conf = pi->conf; + + switch (state->mode) { + case TYPEC_STATE_SAFE: + new_conf = (new_conf & PI3USB30532_CONF_SWAP) | + PI3USB30532_CONF_OPEN; + break; + case TYPEC_STATE_USB: + new_conf = (new_conf & PI3USB30532_CONF_SWAP) | + PI3USB30532_CONF_USB3; + break; + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + new_conf = (new_conf & PI3USB30532_CONF_SWAP) | + PI3USB30532_CONF_4LANE_DP; + break; + case TYPEC_DP_STATE_D: + new_conf = (new_conf & PI3USB30532_CONF_SWAP) | + PI3USB30532_CONF_USB3_AND_2LANE_DP; + break; + default: + break; + } + + ret = pi3usb30532_set_conf(pi, new_conf); + mutex_unlock(&pi->lock); + + return ret; +} + +static int pi3usb30532_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct typec_switch_desc sw_desc = { }; + struct typec_mux_desc mux_desc = { }; + struct pi3usb30532 *pi; + int ret; + + pi = devm_kzalloc(dev, sizeof(*pi), GFP_KERNEL); + if (!pi) + return -ENOMEM; + + pi->client = client; + mutex_init(&pi->lock); + + ret = i2c_smbus_read_byte_data(client, PI3USB30532_CONF); + if (ret < 0) { + dev_err(dev, "Error reading config register %d\n", ret); + return ret; + } + pi->conf = ret; + + sw_desc.drvdata = pi; + sw_desc.fwnode = dev->fwnode; + sw_desc.set = pi3usb30532_sw_set; + + pi->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(pi->sw)) { + dev_err(dev, "Error registering typec switch: %ld\n", + PTR_ERR(pi->sw)); + return PTR_ERR(pi->sw); + } + + mux_desc.drvdata = pi; + mux_desc.fwnode = dev->fwnode; + mux_desc.set = pi3usb30532_mux_set; + + pi->mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(pi->mux)) { + typec_switch_unregister(pi->sw); + dev_err(dev, "Error registering typec mux: %ld\n", + PTR_ERR(pi->mux)); + return PTR_ERR(pi->mux); + } + + i2c_set_clientdata(client, pi); + return 0; +} + +static void pi3usb30532_remove(struct i2c_client *client) +{ + struct pi3usb30532 *pi = i2c_get_clientdata(client); + + typec_mux_unregister(pi->mux); + typec_switch_unregister(pi->sw); +} + +static const struct i2c_device_id pi3usb30532_table[] = { + { .name = "pi3usb30532" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pi3usb30532_table); + +static struct i2c_driver pi3usb30532_driver = { + .driver = { + .name = "pi3usb30532", + }, + .probe = pi3usb30532_probe, + .remove = pi3usb30532_remove, + .id_table = pi3usb30532_table, +}; + +module_i2c_driver(pi3usb30532_driver); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("Pericom PI3USB30532 Type-C mux driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/mux/ps883x.c b/drivers/usb/typec/mux/ps883x.c new file mode 100644 index 000000000..64e0a61b7 --- /dev/null +++ b/drivers/usb/typec/mux/ps883x.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Parade ps883x usb retimer driver + * + * Copyright (C) 2024 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REG_USB_PORT_CONN_STATUS_0 0x00 + +#define CONN_STATUS_0_CONNECTION_PRESENT BIT(0) +#define CONN_STATUS_0_ORIENTATION_REVERSED BIT(1) +#define CONN_STATUS_0_ACTIVE_CABLE BIT(2) +#define CONN_STATUS_0_USB_3_1_CONNECTED BIT(5) + +#define REG_USB_PORT_CONN_STATUS_1 0x01 + +#define CONN_STATUS_1_DP_CONNECTED BIT(0) +#define CONN_STATUS_1_DP_SINK_REQUESTED BIT(1) +#define CONN_STATUS_1_DP_PIN_ASSIGNMENT_C_D BIT(2) +#define CONN_STATUS_1_DP_HPD_LEVEL BIT(7) + +#define REG_USB_PORT_CONN_STATUS_2 0x02 + +#define CONN_STATUS_2_TBT_CONNECTED BIT(0) +#define CONN_STATUS_2_TBT_UNIDIR_LSRX_ACT_LT BIT(4) +#define CONN_STATUS_2_USB4_CONNECTED BIT(7) + +struct ps883x_retimer { + struct i2c_client *client; + struct gpio_desc *reset_gpio; + struct regmap *regmap; + struct typec_switch_dev *sw; + struct typec_retimer *retimer; + struct clk *xo_clk; + struct regulator *vdd_supply; + struct regulator *vdd33_supply; + struct regulator *vdd33_cap_supply; + struct regulator *vddat_supply; + struct regulator *vddar_supply; + struct regulator *vddio_supply; + + struct typec_switch *typec_switch; + struct typec_mux *typec_mux; + + struct mutex lock; /* protect non-concurrent retimer & switch */ + + enum typec_orientation orientation; + bool in_reset; +}; + +static int ps883x_enable_vregs(struct ps883x_retimer *retimer) +{ + struct device *dev = &retimer->client->dev; + int ret; + + ret = regulator_enable(retimer->vdd33_supply); + if (ret) { + dev_err(dev, "cannot enable VDD 3.3V regulator: %d\n", ret); + return ret; + } + + ret = regulator_enable(retimer->vdd33_cap_supply); + if (ret) { + dev_err(dev, "cannot enable VDD 3.3V CAP regulator: %d\n", ret); + goto err_vdd33_disable; + } + + usleep_range(4000, 10000); + + ret = regulator_enable(retimer->vdd_supply); + if (ret) { + dev_err(dev, "cannot enable VDD regulator: %d\n", ret); + goto err_vdd33_cap_disable; + } + + ret = regulator_enable(retimer->vddar_supply); + if (ret) { + dev_err(dev, "cannot enable VDD AR regulator: %d\n", ret); + goto err_vdd_disable; + } + + ret = regulator_enable(retimer->vddat_supply); + if (ret) { + dev_err(dev, "cannot enable VDD AT regulator: %d\n", ret); + goto err_vddar_disable; + } + + ret = regulator_enable(retimer->vddio_supply); + if (ret) { + dev_err(dev, "cannot enable VDD IO regulator: %d\n", ret); + goto err_vddat_disable; + } + + return 0; + +err_vddat_disable: + regulator_disable(retimer->vddat_supply); +err_vddar_disable: + regulator_disable(retimer->vddar_supply); +err_vdd_disable: + regulator_disable(retimer->vdd_supply); +err_vdd33_cap_disable: + regulator_disable(retimer->vdd33_cap_supply); +err_vdd33_disable: + regulator_disable(retimer->vdd33_supply); + + return ret; +} + +static void ps883x_disable_vregs(struct ps883x_retimer *retimer) +{ + regulator_disable(retimer->vddio_supply); + regulator_disable(retimer->vddat_supply); + regulator_disable(retimer->vddar_supply); + regulator_disable(retimer->vdd_supply); + regulator_disable(retimer->vdd33_cap_supply); + regulator_disable(retimer->vdd33_supply); +} + +static void ps883x_reset(struct ps883x_retimer *retimer) +{ + if (retimer->in_reset) + return; + + gpiod_set_value(retimer->reset_gpio, 1); + ps883x_disable_vregs(retimer); + retimer->in_reset = true; +} + +static int ps883x_configure(struct ps883x_retimer *retimer, int cfg0, + int cfg1, int cfg2, bool reset) +{ + struct device *dev = &retimer->client->dev; + int ret; + + if (reset) { + ps883x_reset(retimer); + + return 0; + } else if (retimer->in_reset) { + ret = ps883x_enable_vregs(retimer); + if (ret) + return ret; + + gpiod_set_value(retimer->reset_gpio, 0); + + /* firmware initialization delay */ + msleep(60); + + retimer->in_reset = false; + } + + ret = regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_0, cfg0); + if (ret) { + dev_err(dev, "failed to write conn_status_0: %d\n", ret); + return ret; + } + + ret = regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_1, cfg1); + if (ret) { + dev_err(dev, "failed to write conn_status_1: %d\n", ret); + return ret; + } + + ret = regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_2, cfg2); + if (ret) { + dev_err(dev, "failed to write conn_status_2: %d\n", ret); + return ret; + } + + return 0; +} + +static int ps883x_set(struct ps883x_retimer *retimer, struct typec_retimer_state *state) +{ + struct typec_thunderbolt_data *tb_data; + const struct enter_usb_data *eudo_data; + int cfg0 = CONN_STATUS_0_CONNECTION_PRESENT; + int cfg1 = 0x00; + int cfg2 = 0x00; + bool reset = false; + + if (retimer->orientation == TYPEC_ORIENTATION_REVERSE) + cfg0 |= CONN_STATUS_0_ORIENTATION_REVERSED; + + if (state->alt) { + switch (state->alt->svid) { + case USB_TYPEC_DP_SID: + cfg1 |= CONN_STATUS_1_DP_CONNECTED | + CONN_STATUS_1_DP_HPD_LEVEL; + + switch (state->mode) { + case TYPEC_DP_STATE_D: + cfg0 |= CONN_STATUS_0_USB_3_1_CONNECTED; + fallthrough; + case TYPEC_DP_STATE_C: + cfg1 |= CONN_STATUS_1_DP_SINK_REQUESTED | + CONN_STATUS_1_DP_PIN_ASSIGNMENT_C_D; + break; + default: /* MODE_E */ + break; + } + break; + case USB_TYPEC_TBT_SID: + tb_data = state->data; + + /* Unconditional */ + cfg2 |= CONN_STATUS_2_TBT_CONNECTED; + + if (tb_data->cable_mode & TBT_CABLE_ACTIVE_PASSIVE) + cfg0 |= CONN_STATUS_0_ACTIVE_CABLE; + + if (tb_data->enter_vdo & TBT_ENTER_MODE_UNI_DIR_LSRX) + cfg2 |= CONN_STATUS_2_TBT_UNIDIR_LSRX_ACT_LT; + break; + default: + dev_err(&retimer->client->dev, "Got unsupported SID: 0x%x\n", + state->alt->svid); + return -EOPNOTSUPP; + } + } else { + switch (state->mode) { + /* SAFE can be transient or point to an actual disconnect */ + case TYPEC_STATE_SAFE: + reset = retimer->orientation == TYPEC_ORIENTATION_NONE; + break; + /* USB2 pins don't even go through this chip */ + case TYPEC_MODE_USB2: + reset = true; + break; + case TYPEC_STATE_USB: + case TYPEC_MODE_USB3: + cfg0 |= CONN_STATUS_0_USB_3_1_CONNECTED; + break; + case TYPEC_MODE_USB4: + eudo_data = state->data; + + cfg2 |= CONN_STATUS_2_USB4_CONNECTED; + + if (FIELD_GET(EUDO_CABLE_TYPE_MASK, eudo_data->eudo) != EUDO_CABLE_TYPE_PASSIVE) + cfg0 |= CONN_STATUS_0_ACTIVE_CABLE; + break; + default: + dev_err(&retimer->client->dev, "Got unsupported mode: %lu\n", + state->mode); + return -EOPNOTSUPP; + } + } + + return ps883x_configure(retimer, cfg0, cfg1, cfg2, reset); +} + +static int ps883x_sw_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct ps883x_retimer *retimer = typec_switch_get_drvdata(sw); + int ret = 0; + + ret = typec_switch_set(retimer->typec_switch, orientation); + if (ret) + return ret; + + guard(mutex)(&retimer->lock); + + if (retimer->orientation != orientation) { + retimer->orientation = orientation; + + /* + * Orientation notifications usually come prior to mode switch + * events. If the retimer is already in reset, we still want to + * cache the new orientation value for the subsequent ps883x_set(). + */ + if (retimer->in_reset) + return 0; + + ret = regmap_assign_bits(retimer->regmap, REG_USB_PORT_CONN_STATUS_0, + CONN_STATUS_0_ORIENTATION_REVERSED, + orientation == TYPEC_ORIENTATION_REVERSE); + if (ret) + dev_err(&retimer->client->dev, "failed to set orientation: %d\n", ret); + } + + return ret; +} + +static int ps883x_retimer_set(struct typec_retimer *rtmr, + struct typec_retimer_state *state) +{ + struct ps883x_retimer *retimer = typec_retimer_get_drvdata(rtmr); + struct typec_mux_state mux_state; + int ret = 0; + + mutex_lock(&retimer->lock); + ret = ps883x_set(retimer, state); + mutex_unlock(&retimer->lock); + + if (ret) + return ret; + + mux_state.alt = state->alt; + mux_state.data = state->data; + mux_state.mode = state->mode; + + return typec_mux_set(retimer->typec_mux, &mux_state); +} + +static int ps883x_get_vregs(struct ps883x_retimer *retimer) +{ + struct device *dev = &retimer->client->dev; + + retimer->vdd_supply = devm_regulator_get(dev, "vdd"); + if (IS_ERR(retimer->vdd_supply)) + return dev_err_probe(dev, PTR_ERR(retimer->vdd_supply), + "failed to get VDD\n"); + + retimer->vdd33_supply = devm_regulator_get(dev, "vdd33"); + if (IS_ERR(retimer->vdd33_supply)) + return dev_err_probe(dev, PTR_ERR(retimer->vdd33_supply), + "failed to get VDD 3.3V\n"); + + retimer->vdd33_cap_supply = devm_regulator_get(dev, "vdd33-cap"); + if (IS_ERR(retimer->vdd33_cap_supply)) + return dev_err_probe(dev, PTR_ERR(retimer->vdd33_cap_supply), + "failed to get VDD CAP 3.3V\n"); + + retimer->vddat_supply = devm_regulator_get(dev, "vddat"); + if (IS_ERR(retimer->vddat_supply)) + return dev_err_probe(dev, PTR_ERR(retimer->vddat_supply), + "failed to get VDD AT\n"); + + retimer->vddar_supply = devm_regulator_get(dev, "vddar"); + if (IS_ERR(retimer->vddar_supply)) + return dev_err_probe(dev, PTR_ERR(retimer->vddar_supply), + "failed to get VDD AR\n"); + + retimer->vddio_supply = devm_regulator_get(dev, "vddio"); + if (IS_ERR(retimer->vddio_supply)) + return dev_err_probe(dev, PTR_ERR(retimer->vddio_supply), + "failed to get VDD IO\n"); + + return 0; +} + +static const struct regmap_config ps883x_retimer_regmap = { + .max_register = 0x1f, + .reg_bits = 8, + .val_bits = 8, +}; + +static int ps883x_retimer_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct typec_switch_desc sw_desc = { }; + struct typec_retimer_desc rtmr_desc = { }; + struct ps883x_retimer *retimer; + unsigned int val; + int ret; + + retimer = devm_kzalloc(dev, sizeof(*retimer), GFP_KERNEL); + if (!retimer) + return -ENOMEM; + + retimer->client = client; + + mutex_init(&retimer->lock); + + retimer->regmap = devm_regmap_init_i2c(client, &ps883x_retimer_regmap); + if (IS_ERR(retimer->regmap)) + return dev_err_probe(dev, PTR_ERR(retimer->regmap), + "failed to allocate register map\n"); + + ret = ps883x_get_vregs(retimer); + if (ret) + return ret; + + retimer->xo_clk = devm_clk_get(dev, NULL); + if (IS_ERR(retimer->xo_clk)) + return dev_err_probe(dev, PTR_ERR(retimer->xo_clk), + "failed to get xo clock\n"); + + retimer->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_ASIS); + if (IS_ERR(retimer->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(retimer->reset_gpio), + "failed to get reset gpio\n"); + + retimer->typec_switch = typec_switch_get(dev); + if (IS_ERR(retimer->typec_switch)) + return dev_err_probe(dev, PTR_ERR(retimer->typec_switch), + "failed to acquire orientation-switch\n"); + + retimer->typec_mux = typec_mux_get(dev); + if (IS_ERR(retimer->typec_mux)) { + ret = dev_err_probe(dev, PTR_ERR(retimer->typec_mux), + "failed to acquire mode-mux\n"); + goto err_switch_put; + } + + ret = drm_aux_bridge_register(dev); + if (ret) + goto err_mux_put; + + ret = ps883x_enable_vregs(retimer); + if (ret) + goto err_mux_put; + + ret = clk_prepare_enable(retimer->xo_clk); + if (ret) { + dev_err(dev, "failed to enable XO: %d\n", ret); + goto err_vregs_disable; + } + + /* skip resetting if already configured */ + if (regmap_test_bits(retimer->regmap, REG_USB_PORT_CONN_STATUS_0, + CONN_STATUS_0_CONNECTION_PRESENT) == 1) { + gpiod_direction_output(retimer->reset_gpio, 0); + } else { + gpiod_direction_output(retimer->reset_gpio, 1); + + /* VDD IO supply enable to reset release delay */ + usleep_range(4000, 14000); + + gpiod_set_value(retimer->reset_gpio, 0); + + /* firmware initialization delay */ + msleep(60); + + /* make sure device is accessible */ + ret = regmap_read(retimer->regmap, REG_USB_PORT_CONN_STATUS_0, + &val); + if (ret) { + dev_err(dev, "failed to read conn_status_0: %d\n", ret); + if (ret == -ENXIO) + ret = -EIO; + goto err_clk_disable; + } + } + + /* Keep the retimer in reset until a Type-C notification comes */ + ps883x_reset(retimer); + + sw_desc.drvdata = retimer; + sw_desc.fwnode = dev_fwnode(dev); + sw_desc.set = ps883x_sw_set; + + retimer->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(retimer->sw)) { + ret = PTR_ERR(retimer->sw); + dev_err(dev, "failed to register typec switch: %d\n", ret); + goto err_clk_disable; + } + + rtmr_desc.drvdata = retimer; + rtmr_desc.fwnode = dev_fwnode(dev); + rtmr_desc.set = ps883x_retimer_set; + + retimer->retimer = typec_retimer_register(dev, &rtmr_desc); + if (IS_ERR(retimer->retimer)) { + ret = PTR_ERR(retimer->retimer); + dev_err(dev, "failed to register typec retimer: %d\n", ret); + goto err_switch_unregister; + } + + i2c_set_clientdata(client, retimer); + return 0; + +err_switch_unregister: + typec_switch_unregister(retimer->sw); +err_clk_disable: + clk_disable_unprepare(retimer->xo_clk); +err_vregs_disable: + gpiod_set_value(retimer->reset_gpio, 1); + ps883x_disable_vregs(retimer); +err_mux_put: + typec_mux_put(retimer->typec_mux); +err_switch_put: + typec_switch_put(retimer->typec_switch); + + return ret; +} + +static void ps883x_retimer_remove(struct i2c_client *client) +{ + struct ps883x_retimer *retimer = i2c_get_clientdata(client); + + typec_retimer_unregister(retimer->retimer); + typec_switch_unregister(retimer->sw); + + gpiod_set_value(retimer->reset_gpio, 1); + + clk_disable_unprepare(retimer->xo_clk); + + ps883x_disable_vregs(retimer); + + typec_mux_put(retimer->typec_mux); + typec_switch_put(retimer->typec_switch); +} + +static const struct of_device_id ps883x_retimer_of_table[] = { + { .compatible = "parade,ps8830" }, + { } +}; +MODULE_DEVICE_TABLE(of, ps883x_retimer_of_table); + +static struct i2c_driver ps883x_retimer_driver = { + .driver = { + .name = "ps883x_retimer", + .of_match_table = ps883x_retimer_of_table, + }, + .probe = ps883x_retimer_probe, + .remove = ps883x_retimer_remove, +}; + +module_i2c_driver(ps883x_retimer_driver); + +MODULE_DESCRIPTION("Parade ps883x Type-C Retimer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/mux/ptn36502.c b/drivers/usb/typec/mux/ptn36502.c new file mode 100644 index 000000000..afd16775d --- /dev/null +++ b/drivers/usb/typec/mux/ptn36502.c @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * NXP PTN36502 Type-C driver + * + * Copyright (C) 2023 Luca Weiss + * + * Based on NB7VPQ904M driver: + * Copyright (C) 2023 Dmitry Baryshkov + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PTN36502_CHIP_ID_REG 0x00 +#define PTN36502_CHIP_ID 0x02 + +#define PTN36502_CHIP_REVISION_REG 0x01 +#define PTN36502_CHIP_REVISION_BASE_MASK GENMASK(7, 4) +#define PTN36502_CHIP_REVISION_METAL_MASK GENMASK(3, 0) + +#define PTN36502_DP_LINK_CTRL_REG 0x06 +#define PTN36502_DP_LINK_CTRL_LANES_MASK GENMASK(3, 2) +#define PTN36502_DP_LINK_CTRL_LANES_2 (2) +#define PTN36502_DP_LINK_CTRL_LANES_4 (3) +#define PTN36502_DP_LINK_CTRL_LINK_RATE_MASK GENMASK(1, 0) +#define PTN36502_DP_LINK_CTRL_LINK_RATE_5_4GBPS (2) + +/* Registers for lane 0 (0x07) to lane 3 (0x0a) have the same layout */ +#define PTN36502_DP_LANE_CTRL_REG(n) (0x07 + (n)) +#define PTN36502_DP_LANE_CTRL_RX_GAIN_MASK GENMASK(6, 4) +#define PTN36502_DP_LANE_CTRL_RX_GAIN_3DB (2) +#define PTN36502_DP_LANE_CTRL_TX_SWING_MASK GENMASK(3, 2) +#define PTN36502_DP_LANE_CTRL_TX_SWING_800MVPPD (2) +#define PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_MASK GENMASK(1, 0) +#define PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_3_5DB (1) + +#define PTN36502_MODE_CTRL1_REG 0x0b +#define PTN36502_MODE_CTRL1_PLUG_ORIENT_MASK GENMASK(5, 5) +#define PTN36502_MODE_CTRL1_PLUG_ORIENT_REVERSE (1) +#define PTN36502_MODE_CTRL1_AUX_CROSSBAR_MASK GENMASK(3, 3) +#define PTN36502_MODE_CTRL1_AUX_CROSSBAR_SW_ON (1) +#define PTN36502_MODE_CTRL1_MODE_MASK GENMASK(2, 0) +#define PTN36502_MODE_CTRL1_MODE_OFF (0) +#define PTN36502_MODE_CTRL1_MODE_USB_ONLY (1) +#define PTN36502_MODE_CTRL1_MODE_USB_DP (2) +#define PTN36502_MODE_CTRL1_MODE_DP (3) + +#define PTN36502_DEVICE_CTRL_REG 0x0d +#define PTN36502_DEVICE_CTRL_AUX_MONITORING_MASK GENMASK(7, 7) +#define PTN36502_DEVICE_CTRL_AUX_MONITORING_EN (1) + +struct ptn36502 { + struct i2c_client *client; + struct regulator *vdd18_supply; + struct regmap *regmap; + struct typec_switch_dev *sw; + struct typec_retimer *retimer; + + struct typec_switch *typec_switch; + struct typec_mux *typec_mux; + + struct mutex lock; /* protect non-concurrent retimer & switch */ + + enum typec_orientation orientation; + unsigned long mode; + unsigned int svid; +}; + +static int ptn36502_set(struct ptn36502 *ptn) +{ + bool reverse = (ptn->orientation == TYPEC_ORIENTATION_REVERSE); + unsigned int ctrl1_val = 0; + unsigned int lane_ctrl_val = 0; + unsigned int link_ctrl_val = 0; + + switch (ptn->mode) { + case TYPEC_STATE_SAFE: + /* Deep power saving state */ + regmap_write(ptn->regmap, PTN36502_MODE_CTRL1_REG, + FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK, + PTN36502_MODE_CTRL1_MODE_OFF)); + return 0; + + case TYPEC_STATE_USB: + /* + * Normal Orientation (CC1) + * A -> USB RX + * B -> USB TX + * C -> X + * D -> X + * Flipped Orientation (CC2) + * A -> X + * B -> X + * C -> USB TX + * D -> USB RX + */ + + /* USB 3.1 Gen 1 only */ + ctrl1_val = FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK, + PTN36502_MODE_CTRL1_MODE_USB_ONLY); + if (reverse) + ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_PLUG_ORIENT_MASK, + PTN36502_MODE_CTRL1_PLUG_ORIENT_REVERSE); + + regmap_write(ptn->regmap, PTN36502_MODE_CTRL1_REG, ctrl1_val); + return 0; + + default: + if (ptn->svid != USB_TYPEC_DP_SID) + return -EINVAL; + + break; + } + + /* DP Altmode Setup */ + + switch (ptn->mode) { + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + /* + * Normal Orientation (CC1) + * A -> DP3 + * B -> DP2 + * C -> DP1 + * D -> DP0 + * Flipped Orientation (CC2) + * A -> DP0 + * B -> DP1 + * C -> DP2 + * D -> DP3 + */ + + /* 4-lane DP */ + ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK, + PTN36502_MODE_CTRL1_MODE_DP); + link_ctrl_val |= FIELD_PREP(PTN36502_DP_LINK_CTRL_LANES_MASK, + PTN36502_DP_LINK_CTRL_LANES_4); + break; + + case TYPEC_DP_STATE_D: + case TYPEC_DP_STATE_F: /* State F is deprecated */ + /* + * Normal Orientation (CC1) + * A -> USB RX + * B -> USB TX + * C -> DP1 + * D -> DP0 + * Flipped Orientation (CC2) + * A -> DP0 + * B -> DP1 + * C -> USB TX + * D -> USB RX + */ + + /* USB 3.1 Gen 1 and 2-lane DP */ + ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK, + PTN36502_MODE_CTRL1_MODE_USB_DP); + link_ctrl_val |= FIELD_PREP(PTN36502_DP_LINK_CTRL_LANES_MASK, + PTN36502_DP_LINK_CTRL_LANES_2); + break; + + default: + return -EOPNOTSUPP; + } + + /* Enable AUX monitoring */ + regmap_write(ptn->regmap, PTN36502_DEVICE_CTRL_REG, + FIELD_PREP(PTN36502_DEVICE_CTRL_AUX_MONITORING_MASK, + PTN36502_DEVICE_CTRL_AUX_MONITORING_EN)); + + /* Enable AUX switch path */ + ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_AUX_CROSSBAR_MASK, + PTN36502_MODE_CTRL1_AUX_CROSSBAR_SW_ON); + if (reverse) + ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_PLUG_ORIENT_MASK, + PTN36502_MODE_CTRL1_PLUG_ORIENT_REVERSE); + regmap_write(ptn->regmap, PTN36502_MODE_CTRL1_REG, ctrl1_val); + + /* DP Link rate: 5.4 Gbps (HBR2) */ + link_ctrl_val |= FIELD_PREP(PTN36502_DP_LINK_CTRL_LINK_RATE_MASK, + PTN36502_DP_LINK_CTRL_LINK_RATE_5_4GBPS); + regmap_write(ptn->regmap, PTN36502_DP_LINK_CTRL_REG, link_ctrl_val); + + /* + * For all lanes: + * - Rx equivalization gain: 3 dB + * - TX output swing control: 800 mVppd + * - Pre-emphasis control: 3.5 dB + */ + lane_ctrl_val = FIELD_PREP(PTN36502_DP_LANE_CTRL_RX_GAIN_MASK, + PTN36502_DP_LANE_CTRL_RX_GAIN_3DB) | + FIELD_PREP(PTN36502_DP_LANE_CTRL_TX_SWING_MASK, + PTN36502_DP_LANE_CTRL_TX_SWING_800MVPPD) | + FIELD_PREP(PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_MASK, + PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_3_5DB); + regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(0), lane_ctrl_val); + regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(1), lane_ctrl_val); + regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(2), lane_ctrl_val); + regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(3), lane_ctrl_val); + + return 0; +} + +static int ptn36502_sw_set(struct typec_switch_dev *sw, enum typec_orientation orientation) +{ + struct ptn36502 *ptn = typec_switch_get_drvdata(sw); + int ret; + + ret = typec_switch_set(ptn->typec_switch, orientation); + if (ret) + return ret; + + mutex_lock(&ptn->lock); + + if (ptn->orientation != orientation) { + ptn->orientation = orientation; + + ret = ptn36502_set(ptn); + } + + mutex_unlock(&ptn->lock); + + return ret; +} + +static int ptn36502_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) +{ + struct ptn36502 *ptn = typec_retimer_get_drvdata(retimer); + struct typec_mux_state mux_state; + int ret = 0; + + mutex_lock(&ptn->lock); + + if (ptn->mode != state->mode) { + ptn->mode = state->mode; + + if (state->alt) + ptn->svid = state->alt->svid; + else + ptn->svid = 0; // No SVID + + ret = ptn36502_set(ptn); + } + + mutex_unlock(&ptn->lock); + + if (ret) + return ret; + + mux_state.alt = state->alt; + mux_state.data = state->data; + mux_state.mode = state->mode; + + return typec_mux_set(ptn->typec_mux, &mux_state); +} + +static int ptn36502_detect(struct ptn36502 *ptn) +{ + struct device *dev = &ptn->client->dev; + unsigned int reg_val; + int ret; + + ret = regmap_read(ptn->regmap, PTN36502_CHIP_ID_REG, + ®_val); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read chip ID\n"); + + if (reg_val != PTN36502_CHIP_ID) + return dev_err_probe(dev, -ENODEV, "Unexpected chip ID: %x\n", reg_val); + + ret = regmap_read(ptn->regmap, PTN36502_CHIP_REVISION_REG, + ®_val); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read chip revision\n"); + + dev_dbg(dev, "Chip revision: base layer version %lx, metal layer version %lx\n", + FIELD_GET(PTN36502_CHIP_REVISION_BASE_MASK, reg_val), + FIELD_GET(PTN36502_CHIP_REVISION_METAL_MASK, reg_val)); + + return 0; +} + +static const struct regmap_config ptn36502_regmap = { + .max_register = 0x0d, + .reg_bits = 8, + .val_bits = 8, +}; + +static int ptn36502_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct typec_switch_desc sw_desc = { }; + struct typec_retimer_desc retimer_desc = { }; + struct ptn36502 *ptn; + int ret; + + ptn = devm_kzalloc(dev, sizeof(*ptn), GFP_KERNEL); + if (!ptn) + return -ENOMEM; + + ptn->client = client; + + ptn->regmap = devm_regmap_init_i2c(client, &ptn36502_regmap); + if (IS_ERR(ptn->regmap)) { + dev_err(&client->dev, "Failed to allocate register map\n"); + return PTR_ERR(ptn->regmap); + } + + ptn->mode = TYPEC_STATE_SAFE; + ptn->orientation = TYPEC_ORIENTATION_NONE; + + mutex_init(&ptn->lock); + + ptn->vdd18_supply = devm_regulator_get_optional(dev, "vdd18"); + if (IS_ERR(ptn->vdd18_supply)) + return PTR_ERR(ptn->vdd18_supply); + + ptn->typec_switch = fwnode_typec_switch_get(dev->fwnode); + if (IS_ERR(ptn->typec_switch)) + return dev_err_probe(dev, PTR_ERR(ptn->typec_switch), + "Failed to acquire orientation-switch\n"); + + ptn->typec_mux = fwnode_typec_mux_get(dev->fwnode); + if (IS_ERR(ptn->typec_mux)) { + ret = dev_err_probe(dev, PTR_ERR(ptn->typec_mux), + "Failed to acquire mode-switch\n"); + goto err_switch_put; + } + + ret = regulator_enable(ptn->vdd18_supply); + if (ret) { + ret = dev_err_probe(dev, ret, "Failed to enable vdd18\n"); + goto err_mux_put; + } + + ret = ptn36502_detect(ptn); + if (ret) + goto err_disable_regulator; + + ret = drm_aux_bridge_register(dev); + if (ret) + goto err_disable_regulator; + + sw_desc.drvdata = ptn; + sw_desc.fwnode = dev->fwnode; + sw_desc.set = ptn36502_sw_set; + + ptn->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(ptn->sw)) { + ret = dev_err_probe(dev, PTR_ERR(ptn->sw), + "Failed to register typec switch\n"); + goto err_disable_regulator; + } + + retimer_desc.drvdata = ptn; + retimer_desc.fwnode = dev->fwnode; + retimer_desc.set = ptn36502_retimer_set; + + ptn->retimer = typec_retimer_register(dev, &retimer_desc); + if (IS_ERR(ptn->retimer)) { + ret = dev_err_probe(dev, PTR_ERR(ptn->retimer), + "Failed to register typec retimer\n"); + goto err_switch_unregister; + } + + return 0; + +err_switch_unregister: + typec_switch_unregister(ptn->sw); + +err_disable_regulator: + regulator_disable(ptn->vdd18_supply); + +err_mux_put: + typec_mux_put(ptn->typec_mux); + +err_switch_put: + typec_switch_put(ptn->typec_switch); + + return ret; +} + +static void ptn36502_remove(struct i2c_client *client) +{ + struct ptn36502 *ptn = i2c_get_clientdata(client); + + typec_retimer_unregister(ptn->retimer); + typec_switch_unregister(ptn->sw); + + regulator_disable(ptn->vdd18_supply); + + typec_mux_put(ptn->typec_mux); + typec_switch_put(ptn->typec_switch); +} + +static const struct i2c_device_id ptn36502_table[] = { + { .name = "ptn36502" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ptn36502_table); + +static const struct of_device_id ptn36502_of_table[] = { + { .compatible = "nxp,ptn36502" }, + { } +}; +MODULE_DEVICE_TABLE(of, ptn36502_of_table); + +static struct i2c_driver ptn36502_driver = { + .driver = { + .name = "ptn36502", + .of_match_table = ptn36502_of_table, + }, + .probe = ptn36502_probe, + .remove = ptn36502_remove, + .id_table = ptn36502_table, +}; +module_i2c_driver(ptn36502_driver); + +MODULE_AUTHOR("Luca Weiss "); +MODULE_DESCRIPTION("NXP PTN36502 Type-C driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/mux/tusb1046.c b/drivers/usb/typec/mux/tusb1046.c new file mode 100644 index 000000000..34ef81f75 --- /dev/null +++ b/drivers/usb/typec/mux/tusb1046.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the TUSB1046-DCI USB Type-C crosspoint switch + * + * Copyright (C) 2024 Bootlin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TUSB1046_REG_GENERAL 0xa + +/* General register bits */ +#define TUSB1046_GENERAL_FLIPSEL BIT(2) +#define TUSB1046_GENERAL_CTLSEL GENMASK(1, 0) + +/* Mux modes */ +#define TUSB1046_CTLSEL_DISABLED 0x0 +#define TUSB1046_CTLSEL_USB3 0x1 +#define TUSB1046_CTLSEL_4LANE_DP 0x2 +#define TUSB1046_CTLSEL_USB3_AND_2LANE_DP 0x3 + +struct tusb1046_priv { + struct i2c_client *client; + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; + + /* Lock General register during accesses */ + struct mutex general_reg_lock; +}; + +static int tusb1046_mux_set(struct typec_mux_dev *mux, + struct typec_mux_state *state) +{ + struct tusb1046_priv *priv = typec_mux_get_drvdata(mux); + struct i2c_client *client = priv->client; + struct device *dev = &client->dev; + int mode, val, ret = 0; + + if (state->mode >= TYPEC_STATE_MODAL && + state->alt->svid != USB_TYPEC_DP_SID) + return -EINVAL; + + dev_dbg(dev, "mux mode requested: %lu\n", state->mode); + + mutex_lock(&priv->general_reg_lock); + + val = i2c_smbus_read_byte_data(client, TUSB1046_REG_GENERAL); + if (val < 0) { + dev_err(dev, "failed to read ctlsel status, err %d\n", val); + ret = val; + goto out_unlock; + } + + switch (state->mode) { + case TYPEC_STATE_USB: + mode = TUSB1046_CTLSEL_USB3; + break; + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + mode = TUSB1046_CTLSEL_4LANE_DP; + break; + case TYPEC_DP_STATE_D: + mode = TUSB1046_CTLSEL_USB3_AND_2LANE_DP; + break; + case TYPEC_STATE_SAFE: + default: + mode = TUSB1046_CTLSEL_DISABLED; + break; + } + + val &= ~TUSB1046_GENERAL_CTLSEL; + val |= mode; + + ret = i2c_smbus_write_byte_data(client, TUSB1046_REG_GENERAL, val); + +out_unlock: + mutex_unlock(&priv->general_reg_lock); + return ret; +} + +static int tusb1046_switch_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct tusb1046_priv *priv = typec_switch_get_drvdata(sw); + struct i2c_client *client = priv->client; + struct device *dev = &client->dev; + int val, ret = 0; + + dev_dbg(dev, "setting USB3.0 lane flip for orientation %d\n", orientation); + + mutex_lock(&priv->general_reg_lock); + + val = i2c_smbus_read_byte_data(client, TUSB1046_REG_GENERAL); + if (val < 0) { + dev_err(dev, "failed to read flipsel status, err %d\n", val); + ret = val; + goto out_unlock; + } + + if (orientation == TYPEC_ORIENTATION_REVERSE) + val |= TUSB1046_GENERAL_FLIPSEL; + else + val &= ~TUSB1046_GENERAL_FLIPSEL; + + ret = i2c_smbus_write_byte_data(client, TUSB1046_REG_GENERAL, val); + +out_unlock: + mutex_unlock(&priv->general_reg_lock); + return ret; +} + +static int tusb1046_i2c_probe(struct i2c_client *client) +{ + struct typec_switch_desc sw_desc = { }; + struct typec_mux_desc mux_desc = { }; + struct device *dev = &client->dev; + struct tusb1046_priv *priv; + int ret = 0; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + + mutex_init(&priv->general_reg_lock); + + sw_desc.drvdata = priv; + sw_desc.fwnode = dev_fwnode(dev); + sw_desc.set = tusb1046_switch_set; + + priv->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(priv->sw)) { + ret = dev_err_probe(dev, PTR_ERR(priv->sw), "failed to register type-c switch\n"); + goto err_destroy_mutex; + } + + mux_desc.drvdata = priv; + mux_desc.fwnode = dev_fwnode(dev); + mux_desc.set = tusb1046_mux_set; + + priv->mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(priv->mux)) { + ret = dev_err_probe(dev, PTR_ERR(priv->mux), "failed to register type-c mux\n"); + goto err_unregister_switch; + } + + i2c_set_clientdata(client, priv); + + return 0; + +err_unregister_switch: + typec_switch_unregister(priv->sw); +err_destroy_mutex: + mutex_destroy(&priv->general_reg_lock); + return ret; +} + +static void tusb1046_i2c_remove(struct i2c_client *client) +{ + struct tusb1046_priv *priv = i2c_get_clientdata(client); + + typec_switch_unregister(priv->sw); + typec_mux_unregister(priv->mux); + mutex_destroy(&priv->general_reg_lock); +} + +static const struct of_device_id tusb1046_match_table[] = { + {.compatible = "ti,tusb1046"}, + {}, +}; +MODULE_DEVICE_TABLE(of, tusb1046_match_table); + +static struct i2c_driver tusb1046_driver = { + .driver = { + .name = "tusb1046", + .of_match_table = tusb1046_match_table, + }, + .probe = tusb1046_i2c_probe, + .remove = tusb1046_i2c_remove, +}; + +module_i2c_driver(tusb1046_driver); + +MODULE_DESCRIPTION("TUSB1046 USB Type-C switch driver"); +MODULE_AUTHOR("Romain Gantois "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/mux/wcd939x-usbss.c b/drivers/usb/typec/mux/wcd939x-usbss.c new file mode 100644 index 000000000..73db3aa3c --- /dev/null +++ b/drivers/usb/typec/mux/wcd939x-usbss.c @@ -0,0 +1,779 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (C) 2023 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WCD_USBSS_PMP_OUT1 0x2 + +#define WCD_USBSS_DP_DN_MISC1 0x20 + +#define WCD_USBSS_DP_DN_MISC1_DP_PCOMP_2X_DYN_BST_ON_EN BIT(3) +#define WCD_USBSS_DP_DN_MISC1_DN_PCOMP_2X_DYN_BST_ON_EN BIT(0) + +#define WCD_USBSS_MG1_EN 0x24 + +#define WCD_USBSS_MG1_EN_CT_SNS_EN BIT(1) + +#define WCD_USBSS_MG1_BIAS 0x25 + +#define WCD_USBSS_MG1_BIAS_PCOMP_DYN_BST_EN BIT(3) + +#define WCD_USBSS_MG1_MISC 0x27 + +#define WCD_USBSS_MG1_MISC_PCOMP_2X_DYN_BST_ON_EN BIT(5) + +#define WCD_USBSS_MG2_EN 0x28 + +#define WCD_USBSS_MG2_EN_CT_SNS_EN BIT(1) + +#define WCD_USBSS_MG2_BIAS 0x29 + +#define WCD_USBSS_MG2_BIAS_PCOMP_DYN_BST_EN BIT(3) + +#define WCD_USBSS_MG2_MISC 0x30 + +#define WCD_USBSS_MG2_MISC_PCOMP_2X_DYN_BST_ON_EN BIT(5) + +#define WCD_USBSS_DISP_AUXP_THRESH 0x80 + +#define WCD_USBSS_DISP_AUXP_THRESH_DISP_AUXP_OVPON_CM GENMASK(7, 5) + +#define WCD_USBSS_DISP_AUXP_CTL 0x81 + +#define WCD_USBSS_DISP_AUXP_CTL_LK_CANCEL_TRK_COEFF GENMASK(2, 0) + +#define WCD_USBSS_CPLDO_CTL2 0xa1 + +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE 0x403 + +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DEVICE_ENABLE BIT(7) +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXP_TO_MGX_SWITCHES BIT(6) +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXM_TO_MGX_SWITCHES BIT(5) +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DNL_SWITCHES BIT(4) +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DPR_SWITCHES BIT(3) +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_SENSE_SWITCHES BIT(2) +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_MIC_SWITCHES BIT(1) +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_AGND_SWITCHES BIT(0) + +#define WCD_USBSS_SWITCH_SELECT0 0x404 + +#define WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES BIT(7) /* 1-> MG2 */ +#define WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES BIT(6) /* 1-> MG2 */ +#define WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES GENMASK(5, 4) +#define WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES GENMASK(3, 2) +#define WCD_USBSS_SWITCH_SELECT0_SENSE_SWITCHES BIT(1) /* 1-> SBU2 */ +#define WCD_USBSS_SWITCH_SELECT0_MIC_SWITCHES BIT(0) /* 1-> MG2 */ + +#define WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_L 0 +#define WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_DN 1 +#define WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_DN2 2 + +#define WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_R 0 +#define WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_DP 1 +#define WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_DR2 2 + +#define WCD_USBSS_SWITCH_SELECT1 0x405 + +#define WCD_USBSS_SWITCH_SELECT1_AGND_SWITCHES BIT(0) /* 1-> MG2 */ + +#define WCD_USBSS_DELAY_R_SW 0x40d +#define WCD_USBSS_DELAY_MIC_SW 0x40e +#define WCD_USBSS_DELAY_SENSE_SW 0x40f +#define WCD_USBSS_DELAY_GND_SW 0x410 +#define WCD_USBSS_DELAY_L_SW 0x411 + +#define WCD_USBSS_FUNCTION_ENABLE 0x413 + +#define WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT GENMASK(1, 0) + +#define WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT_MANUAL 1 +#define WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT_AUDIO_FSM 2 + +#define WCD_USBSS_EQUALIZER1 0x415 + +#define WCD_USBSS_EQUALIZER1_EQ_EN BIT(7) +#define WCD_USBSS_EQUALIZER1_BW_SETTINGS GENMASK(6, 3) + +#define WCD_USBSS_USB_SS_CNTL 0x419 + +#define WCD_USBSS_USB_SS_CNTL_STANDBY_STATE BIT(4) +#define WCD_USBSS_USB_SS_CNTL_RCO_EN BIT(3) +#define WCD_USBSS_USB_SS_CNTL_USB_SS_MODE GENMASK(2, 0) + +#define WCD_USBSS_USB_SS_CNTL_USB_SS_MODE_AATC 2 +#define WCD_USBSS_USB_SS_CNTL_USB_SS_MODE_USB 5 + +#define WCD_USBSS_AUDIO_FSM_START 0x433 + +#define WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG BIT(0) + +#define WCD_USBSS_RATIO_SPKR_REXT_L_LSB 0x461 +#define WCD_USBSS_RATIO_SPKR_REXT_L_MSB 0x462 +#define WCD_USBSS_RATIO_SPKR_REXT_R_LSB 0x463 +#define WCD_USBSS_RATIO_SPKR_REXT_R_MSB 0x464 +#define WCD_USBSS_AUD_COEF_L_K0_0 0x475 +#define WCD_USBSS_AUD_COEF_L_K0_1 0x476 +#define WCD_USBSS_AUD_COEF_L_K0_2 0x477 +#define WCD_USBSS_AUD_COEF_L_K1_0 0x478 +#define WCD_USBSS_AUD_COEF_L_K1_1 0x479 +#define WCD_USBSS_AUD_COEF_L_K2_0 0x47a +#define WCD_USBSS_AUD_COEF_L_K2_1 0x47b +#define WCD_USBSS_AUD_COEF_L_K3_0 0x47c +#define WCD_USBSS_AUD_COEF_L_K3_1 0x47d +#define WCD_USBSS_AUD_COEF_L_K4_0 0x47e +#define WCD_USBSS_AUD_COEF_L_K4_1 0x47f +#define WCD_USBSS_AUD_COEF_L_K5_0 0x480 +#define WCD_USBSS_AUD_COEF_L_K5_1 0x481 +#define WCD_USBSS_AUD_COEF_R_K0_0 0x482 +#define WCD_USBSS_AUD_COEF_R_K0_1 0x483 +#define WCD_USBSS_AUD_COEF_R_K0_2 0x484 +#define WCD_USBSS_AUD_COEF_R_K1_0 0x485 +#define WCD_USBSS_AUD_COEF_R_K1_1 0x486 +#define WCD_USBSS_AUD_COEF_R_K2_0 0x487 +#define WCD_USBSS_AUD_COEF_R_K2_1 0x488 +#define WCD_USBSS_AUD_COEF_R_K3_0 0x489 +#define WCD_USBSS_AUD_COEF_R_K3_1 0x48a +#define WCD_USBSS_AUD_COEF_R_K4_0 0x48b +#define WCD_USBSS_AUD_COEF_R_K4_1 0x48c +#define WCD_USBSS_AUD_COEF_R_K5_0 0x48d +#define WCD_USBSS_AUD_COEF_R_K5_1 0x48e +#define WCD_USBSS_GND_COEF_L_K0_0 0x48f +#define WCD_USBSS_GND_COEF_L_K0_1 0x490 +#define WCD_USBSS_GND_COEF_L_K0_2 0x491 +#define WCD_USBSS_GND_COEF_L_K1_0 0x492 +#define WCD_USBSS_GND_COEF_L_K1_1 0x493 +#define WCD_USBSS_GND_COEF_L_K2_0 0x494 +#define WCD_USBSS_GND_COEF_L_K2_1 0x495 +#define WCD_USBSS_GND_COEF_L_K3_0 0x496 +#define WCD_USBSS_GND_COEF_L_K3_1 0x497 +#define WCD_USBSS_GND_COEF_L_K4_0 0x498 +#define WCD_USBSS_GND_COEF_L_K4_1 0x499 +#define WCD_USBSS_GND_COEF_L_K5_0 0x49a +#define WCD_USBSS_GND_COEF_L_K5_1 0x49b +#define WCD_USBSS_GND_COEF_R_K0_0 0x49c +#define WCD_USBSS_GND_COEF_R_K0_1 0x49d +#define WCD_USBSS_GND_COEF_R_K0_2 0x49e +#define WCD_USBSS_GND_COEF_R_K1_0 0x49f +#define WCD_USBSS_GND_COEF_R_K1_1 0x4a0 +#define WCD_USBSS_GND_COEF_R_K2_0 0x4a1 +#define WCD_USBSS_GND_COEF_R_K2_1 0x4a2 +#define WCD_USBSS_GND_COEF_R_K3_0 0x4a3 +#define WCD_USBSS_GND_COEF_R_K3_1 0x4a4 +#define WCD_USBSS_GND_COEF_R_K4_0 0x4a5 +#define WCD_USBSS_GND_COEF_R_K4_1 0x4a6 +#define WCD_USBSS_GND_COEF_R_K5_0 0x4a7 +#define WCD_USBSS_GND_COEF_R_K5_1 0x4a8 + +#define WCD_USBSS_MAX_REGISTER 0x4c1 + +struct wcd939x_usbss { + struct i2c_client *client; + struct gpio_desc *reset_gpio; + struct regulator *vdd_supply; + + /* used to serialize concurrent change requests */ + struct mutex lock; + + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; + + struct regmap *regmap; + + struct typec_mux *codec; + struct typec_switch *codec_switch; + + enum typec_orientation orientation; + unsigned long mode; + unsigned int svid; +}; + +static const struct regmap_range_cfg wcd939x_usbss_ranges[] = { + { + .range_min = 0, + .range_max = WCD_USBSS_MAX_REGISTER, + .selector_reg = 0x0, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 0x100, + }, +}; + +static const struct regmap_config wcd939x_usbss_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = WCD_USBSS_MAX_REGISTER, + .ranges = wcd939x_usbss_ranges, + .num_ranges = ARRAY_SIZE(wcd939x_usbss_ranges), +}; + +/* Linearlizer coefficients for 32ohm load */ +static const struct { + unsigned int offset; + unsigned int mask; + unsigned int value; +} wcd939x_usbss_coeff_init[] = { + { WCD_USBSS_AUD_COEF_L_K5_0, GENMASK(7, 0), 0x39 }, + { WCD_USBSS_AUD_COEF_R_K5_0, GENMASK(7, 0), 0x39 }, + { WCD_USBSS_GND_COEF_L_K2_0, GENMASK(7, 0), 0xe8 }, + { WCD_USBSS_GND_COEF_L_K4_0, GENMASK(7, 0), 0x73 }, + { WCD_USBSS_GND_COEF_R_K2_0, GENMASK(7, 0), 0xe8 }, + { WCD_USBSS_GND_COEF_R_K4_0, GENMASK(7, 0), 0x73 }, + { WCD_USBSS_RATIO_SPKR_REXT_L_LSB, GENMASK(7, 0), 0x00 }, + { WCD_USBSS_RATIO_SPKR_REXT_L_MSB, GENMASK(6, 0), 0x04 }, + { WCD_USBSS_RATIO_SPKR_REXT_R_LSB, GENMASK(7, 0), 0x00 }, + { WCD_USBSS_RATIO_SPKR_REXT_R_MSB, GENMASK(6, 0), 0x04 }, +}; + +static int wcd939x_usbss_set(struct wcd939x_usbss *usbss) +{ + bool reverse = (usbss->orientation == TYPEC_ORIENTATION_REVERSE); + bool enable_audio = false; + bool enable_usb = false; + bool enable_dp = false; + int ret; + + /* USB Mode */ + if (usbss->mode < TYPEC_STATE_MODAL || + (!usbss->svid && (usbss->mode == TYPEC_MODE_USB2 || + usbss->mode == TYPEC_MODE_USB3))) { + enable_usb = true; + } else if (usbss->svid) { + switch (usbss->mode) { + /* DP Only */ + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + enable_dp = true; + break; + + /* DP + USB */ + case TYPEC_DP_STATE_D: + case TYPEC_DP_STATE_F: + enable_usb = true; + enable_dp = true; + break; + + default: + return -EOPNOTSUPP; + } + } else if (usbss->mode == TYPEC_MODE_AUDIO) { + enable_audio = true; + } else { + return -EOPNOTSUPP; + } + + /* Disable all switches */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXP_TO_MGX_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXM_TO_MGX_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DPR_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DNL_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_SENSE_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_MIC_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_AGND_SWITCHES); + if (ret) + return ret; + + /* Clear switches */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_SENSE_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_MIC_SWITCHES); + if (ret) + return ret; + + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT1, + WCD_USBSS_SWITCH_SELECT1_AGND_SWITCHES); + if (ret) + return ret; + + /* Enable OVP_MG1_BIAS PCOMP_DYN_BST_EN */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_MG1_BIAS, + WCD_USBSS_MG1_BIAS_PCOMP_DYN_BST_EN); + if (ret) + return ret; + + /* Enable OVP_MG2_BIAS PCOMP_DYN_BST_EN */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_MG2_BIAS, + WCD_USBSS_MG2_BIAS_PCOMP_DYN_BST_EN); + if (ret) + return ret; + + /* Disable Equalizer in safe mode */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_EQUALIZER1, + WCD_USBSS_EQUALIZER1_EQ_EN); + if (ret) + return ret; + + /* Start FSM with all disabled, force write */ + ret = regmap_write_bits(usbss->regmap, WCD_USBSS_AUDIO_FSM_START, + WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG, + WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG); + + /* 35us to allow the SBU switch to turn off */ + usleep_range(35, 1000); + + /* Setup Audio Accessory mux/switch */ + if (enable_audio) { + int i; + + /* + * AATC switch configuration: + * "Normal": + * - R: DNR + * - L: DNL + * - Sense: GSBU2 + * - Mic: MG1 + * - AGND: MG2 + * "Swapped": + * - R: DNR + * - L: DNL + * - Sense: GSBU1 + * - Mic: MG2 + * - AGND: MG1 + * Swapped information is given by the codec MBHC logic + */ + + /* Set AATC mode */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL, + WCD_USBSS_USB_SS_CNTL_USB_SS_MODE, + FIELD_PREP(WCD_USBSS_USB_SS_CNTL_USB_SS_MODE, + WCD_USBSS_USB_SS_CNTL_USB_SS_MODE_AATC)); + if (ret) + return ret; + + /* Select L for DNL_SWITCHES and R for DPR_SWITCHES */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES, + FIELD_PREP(WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_L) | + FIELD_PREP(WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_R)); + if (ret) + return ret; + + if (reverse) + /* Select MG2 for MIC, SBU1 for Sense */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_SWITCH_SELECT0_MIC_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_MIC_SWITCHES); + else + /* Select MG1 for MIC, SBU2 for Sense */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_SWITCH_SELECT0_SENSE_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_SENSE_SWITCHES); + if (ret) + return ret; + + if (reverse) + /* Disable OVP_MG1_BIAS PCOMP_DYN_BST_EN */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_MG1_BIAS, + WCD_USBSS_MG1_BIAS_PCOMP_DYN_BST_EN); + else + /* Disable OVP_MG2_BIAS PCOMP_DYN_BST_EN */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_MG2_BIAS, + WCD_USBSS_MG2_BIAS_PCOMP_DYN_BST_EN); + if (ret) + return ret; + + /* Enable SENSE, MIC switches */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_SWITCH_SETTINGS_ENABLE_SENSE_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_MIC_SWITCHES); + if (ret) + return ret; + + if (reverse) + /* Select MG1 for AGND_SWITCHES */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT1, + WCD_USBSS_SWITCH_SELECT1_AGND_SWITCHES); + else + /* Select MG2 for AGND_SWITCHES */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT1, + WCD_USBSS_SWITCH_SELECT1_AGND_SWITCHES); + if (ret) + return ret; + + /* Enable AGND switches */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_SWITCH_SETTINGS_ENABLE_AGND_SWITCHES); + if (ret) + return ret; + + /* Enable DPR, DNL switches */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DNL_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DPR_SWITCHES); + if (ret) + return ret; + + /* Setup FSM delays */ + ret = regmap_write(usbss->regmap, WCD_USBSS_DELAY_L_SW, 0x02); + if (ret) + return ret; + + ret = regmap_write(usbss->regmap, WCD_USBSS_DELAY_R_SW, 0x02); + if (ret) + return ret; + + ret = regmap_write(usbss->regmap, WCD_USBSS_DELAY_MIC_SW, 0x01); + if (ret) + return ret; + + /* Start FSM, force write */ + ret = regmap_write_bits(usbss->regmap, WCD_USBSS_AUDIO_FSM_START, + WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG, + WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG); + if (ret) + return ret; + + /* Default Linearlizer coefficients */ + for (i = 0; i < ARRAY_SIZE(wcd939x_usbss_coeff_init); ++i) + regmap_update_bits(usbss->regmap, + wcd939x_usbss_coeff_init[i].offset, + wcd939x_usbss_coeff_init[i].mask, + wcd939x_usbss_coeff_init[i].value); + + return 0; + } + + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL, + WCD_USBSS_USB_SS_CNTL_USB_SS_MODE, + FIELD_PREP(WCD_USBSS_USB_SS_CNTL_USB_SS_MODE, + WCD_USBSS_USB_SS_CNTL_USB_SS_MODE_USB)); + if (ret) + return ret; + + /* Enable USB muxes */ + if (enable_usb) { + /* Do not enable Equalizer in safe mode */ + if (usbss->mode != TYPEC_STATE_SAFE) { + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_EQUALIZER1, + WCD_USBSS_EQUALIZER1_EQ_EN); + if (ret) + return ret; + } + + /* Select DN for DNL_SWITCHES and DP for DPR_SWITCHES */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES, + FIELD_PREP(WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_DN) | + FIELD_PREP(WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_DP)); + if (ret) + return ret; + + /* Enable DNL_SWITCHES and DPR_SWITCHES */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DPR_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DNL_SWITCHES); + if (ret) + return ret; + } + + /* Enable DP AUX muxes */ + if (enable_dp) { + /* Update Leakage Canceller Coefficient for AUXP pins */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_DISP_AUXP_CTL, + WCD_USBSS_DISP_AUXP_CTL_LK_CANCEL_TRK_COEFF, + FIELD_PREP(WCD_USBSS_DISP_AUXP_CTL_LK_CANCEL_TRK_COEFF, + 5)); + if (ret) + return ret; + + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_DISP_AUXP_THRESH, + WCD_USBSS_DISP_AUXP_THRESH_DISP_AUXP_OVPON_CM); + if (ret) + return ret; + + if (reverse) + /* Select MG2 for AUXP and MG1 for AUXM */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES); + else + /* Select MG1 for AUXP and MG2 for AUXM */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES); + if (ret) + return ret; + + /* Enable DP_AUXP_TO_MGX and DP_AUXM_TO_MGX switches */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXP_TO_MGX_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXM_TO_MGX_SWITCHES); + + /* 15us to allow the SBU switch to turn on again */ + usleep_range(15, 1000); + } + + return 0; +} + +static int wcd939x_usbss_switch_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct wcd939x_usbss *usbss = typec_switch_get_drvdata(sw); + int ret = 0; + + mutex_lock(&usbss->lock); + + if (usbss->orientation != orientation) { + usbss->orientation = orientation; + + ret = wcd939x_usbss_set(usbss); + } + + mutex_unlock(&usbss->lock); + + if (ret) + return ret; + + /* Report orientation to codec after switch has been done */ + return typec_switch_set(usbss->codec_switch, orientation); +} + +static int wcd939x_usbss_mux_set(struct typec_mux_dev *mux, + struct typec_mux_state *state) +{ + struct wcd939x_usbss *usbss = typec_mux_get_drvdata(mux); + int ret = 0; + + mutex_lock(&usbss->lock); + + if (usbss->mode != state->mode) { + usbss->mode = state->mode; + + if (state->alt) + usbss->svid = state->alt->svid; + else + usbss->svid = 0; // No SVID + + ret = wcd939x_usbss_set(usbss); + } + + mutex_unlock(&usbss->lock); + + if (ret) + return ret; + + /* Report event to codec after switch has been done */ + return typec_mux_set(usbss->codec, state); +} + +static int wcd939x_usbss_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct typec_switch_desc sw_desc = { }; + struct typec_mux_desc mux_desc = { }; + struct wcd939x_usbss *usbss; + int ret; + + usbss = devm_kzalloc(dev, sizeof(*usbss), GFP_KERNEL); + if (!usbss) + return -ENOMEM; + + usbss->client = client; + mutex_init(&usbss->lock); + + usbss->regmap = devm_regmap_init_i2c(client, &wcd939x_usbss_regmap_config); + if (IS_ERR(usbss->regmap)) + return dev_err_probe(dev, PTR_ERR(usbss->regmap), "failed to initialize regmap\n"); + + usbss->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(usbss->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(usbss->reset_gpio), + "unable to acquire reset gpio\n"); + + usbss->vdd_supply = devm_regulator_get_optional(dev, "vdd"); + if (IS_ERR(usbss->vdd_supply)) + return PTR_ERR(usbss->vdd_supply); + + /* Get Codec's MUX & Switch devices */ + usbss->codec = fwnode_typec_mux_get(dev->fwnode); + if (IS_ERR(usbss->codec)) + return dev_err_probe(dev, PTR_ERR(usbss->codec), + "failed to acquire codec mode-switch\n"); + + usbss->codec_switch = fwnode_typec_switch_get(dev->fwnode); + if (IS_ERR(usbss->codec_switch)) { + ret = dev_err_probe(dev, PTR_ERR(usbss->codec_switch), + "failed to acquire codec orientation-switch\n"); + goto err_mux_put; + } + + usbss->mode = TYPEC_STATE_SAFE; + usbss->orientation = TYPEC_ORIENTATION_NONE; + + gpiod_set_value(usbss->reset_gpio, 1); + + ret = regulator_enable(usbss->vdd_supply); + if (ret) { + dev_err(dev, "Failed to enable vdd: %d\n", ret); + goto err_mux_switch; + } + + msleep(20); + + gpiod_set_value(usbss->reset_gpio, 0); + + msleep(20); + + /* Disable standby */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL, + WCD_USBSS_USB_SS_CNTL_STANDBY_STATE); + if (ret) + goto err_regulator_disable; + + /* Set manual mode by default */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_FUNCTION_ENABLE, + WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT, + FIELD_PREP(WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT, + WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT_MANUAL)); + if (ret) + goto err_regulator_disable; + + /* Enable dynamic boosting for DP and DN */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_DP_DN_MISC1, + WCD_USBSS_DP_DN_MISC1_DP_PCOMP_2X_DYN_BST_ON_EN | + WCD_USBSS_DP_DN_MISC1_DN_PCOMP_2X_DYN_BST_ON_EN); + if (ret) + goto err_regulator_disable; + + /* Enable dynamic boosting for MG1 OVP */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_MG1_MISC, + WCD_USBSS_MG1_MISC_PCOMP_2X_DYN_BST_ON_EN); + if (ret) + goto err_regulator_disable; + + /* Enable dynamic boosting for MG2 OVP */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_MG2_MISC, + WCD_USBSS_MG2_MISC_PCOMP_2X_DYN_BST_ON_EN); + if (ret) + goto err_regulator_disable; + + /* Write 0xFF to WCD_USBSS_CPLDO_CTL2 */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_CPLDO_CTL2, 0xff); + if (ret) + goto err_regulator_disable; + + /* Set RCO_EN: WCD_USBSS_USB_SS_CNTL Bit<3> --> 0x0 --> 0x1 */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL, + WCD_USBSS_USB_SS_CNTL_RCO_EN); + if (ret) + goto err_regulator_disable; + + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL, + WCD_USBSS_USB_SS_CNTL_RCO_EN); + if (ret) + goto err_regulator_disable; + + /* Disable all switches but enable the mux */ + ret = regmap_write(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DEVICE_ENABLE); + if (ret) + goto err_regulator_disable; + + /* Setup in SAFE mode */ + ret = wcd939x_usbss_set(usbss); + if (ret) + goto err_regulator_disable; + + sw_desc.drvdata = usbss; + sw_desc.fwnode = dev_fwnode(dev); + sw_desc.set = wcd939x_usbss_switch_set; + + usbss->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(usbss->sw)) { + ret = dev_err_probe(dev, PTR_ERR(usbss->sw), "failed to register typec switch\n"); + goto err_regulator_disable; + } + + mux_desc.drvdata = usbss; + mux_desc.fwnode = dev_fwnode(dev); + mux_desc.set = wcd939x_usbss_mux_set; + + usbss->mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(usbss->mux)) { + ret = dev_err_probe(dev, PTR_ERR(usbss->mux), "failed to register typec mux\n"); + goto err_switch_unregister; + } + + i2c_set_clientdata(client, usbss); + + return 0; + +err_switch_unregister: + typec_switch_unregister(usbss->sw); + +err_regulator_disable: + regulator_disable(usbss->vdd_supply); + +err_mux_switch: + typec_switch_put(usbss->codec_switch); + +err_mux_put: + typec_mux_put(usbss->codec); + + return ret; +} + +static void wcd939x_usbss_remove(struct i2c_client *client) +{ + struct wcd939x_usbss *usbss = i2c_get_clientdata(client); + + typec_mux_unregister(usbss->mux); + typec_switch_unregister(usbss->sw); + + regulator_disable(usbss->vdd_supply); + + typec_switch_put(usbss->codec_switch); + typec_mux_put(usbss->codec); +} + +static const struct i2c_device_id wcd939x_usbss_table[] = { + { .name = "wcd9390-usbss" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wcd939x_usbss_table); + +static const struct of_device_id wcd939x_usbss_of_table[] = { + { .compatible = "qcom,wcd9390-usbss" }, + { } +}; +MODULE_DEVICE_TABLE(of, wcd939x_usbss_of_table); + +static struct i2c_driver wcd939x_usbss_driver = { + .driver = { + .name = "wcd939x-usbss", + .of_match_table = wcd939x_usbss_of_table, + }, + .probe = wcd939x_usbss_probe, + .remove = wcd939x_usbss_remove, + .id_table = wcd939x_usbss_table, +}; +module_i2c_driver(wcd939x_usbss_driver); + +MODULE_DESCRIPTION("Qualcomm WCD939x USBSS driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/pd.c b/drivers/usb/typec/pd.c new file mode 100644 index 000000000..766c76d63 --- /dev/null +++ b/drivers/usb/typec/pd.c @@ -0,0 +1,797 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Power Delivery sysfs entries + * + * Copyright (C) 2022, Intel Corporation + * Author: Heikki Krogerus + */ + +#include +#include + +#include "pd.h" + +static DEFINE_IDA(pd_ida); + +static struct class pd_class = { + .name = "usb_power_delivery", +}; + +#define to_pdo(o) container_of(o, struct pdo, dev) + +struct pdo { + struct device dev; + int object_position; + u32 pdo; +}; + +static void pdo_release(struct device *dev) +{ + kfree(to_pdo(dev)); +} + +/* -------------------------------------------------------------------------- */ +/* Fixed Supply */ + +static ssize_t +dual_role_power_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_DUAL_ROLE)); +} +static DEVICE_ATTR_RO(dual_role_power); + +static ssize_t +usb_suspend_supported_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_SUSPEND)); +} +static DEVICE_ATTR_RO(usb_suspend_supported); + +static ssize_t +higher_capability_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_HIGHER_CAP)); +} +static DEVICE_ATTR_RO(higher_capability); + +static ssize_t +unconstrained_power_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_EXTPOWER)); +} +static DEVICE_ATTR_RO(unconstrained_power); + +static ssize_t +usb_communication_capable_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_USB_COMM)); +} +static DEVICE_ATTR_RO(usb_communication_capable); + +static ssize_t +dual_role_data_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_DATA_SWAP)); +} +static DEVICE_ATTR_RO(dual_role_data); + +static ssize_t +unchunked_extended_messages_supported_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_UNCHUNK_EXT)); +} +static DEVICE_ATTR_RO(unchunked_extended_messages_supported); + +static ssize_t +peak_current_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", (to_pdo(dev)->pdo >> PDO_FIXED_PEAK_CURR_SHIFT) & 3); +} +static DEVICE_ATTR_RO(peak_current); + +static ssize_t +fast_role_swap_current_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", (to_pdo(dev)->pdo >> PDO_FIXED_FRS_CURR_SHIFT) & 3); +} +static DEVICE_ATTR_RO(fast_role_swap_current); + +static ssize_t voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_fixed_voltage(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(voltage); + +/* Shared with Variable supplies, both source and sink */ +static ssize_t current_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umA\n", pdo_max_current(to_pdo(dev)->pdo)); +} + +/* Shared with Variable type supplies */ +static struct device_attribute maximum_current_attr = { + .attr = { + .name = "maximum_current", + .mode = 0444, + }, + .show = current_show, +}; + +static struct device_attribute operational_current_attr = { + .attr = { + .name = "operational_current", + .mode = 0444, + }, + .show = current_show, +}; + +static struct attribute *source_fixed_supply_attrs[] = { + &dev_attr_dual_role_power.attr, + &dev_attr_usb_suspend_supported.attr, + &dev_attr_unconstrained_power.attr, + &dev_attr_usb_communication_capable.attr, + &dev_attr_dual_role_data.attr, + &dev_attr_unchunked_extended_messages_supported.attr, + &dev_attr_peak_current.attr, + &dev_attr_voltage.attr, + &maximum_current_attr.attr, + NULL +}; + +static umode_t fixed_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + if (to_pdo(kobj_to_dev(kobj))->object_position && + attr != &dev_attr_peak_current.attr && + attr != &dev_attr_voltage.attr && + attr != &maximum_current_attr.attr && + attr != &operational_current_attr.attr) + return 0; + + return attr->mode; +} + +static const struct attribute_group source_fixed_supply_group = { + .is_visible = fixed_attr_is_visible, + .attrs = source_fixed_supply_attrs, +}; +__ATTRIBUTE_GROUPS(source_fixed_supply); + +static const struct device_type source_fixed_supply_type = { + .name = "pdo", + .release = pdo_release, + .groups = source_fixed_supply_groups, +}; + +static struct attribute *sink_fixed_supply_attrs[] = { + &dev_attr_dual_role_power.attr, + &dev_attr_higher_capability.attr, + &dev_attr_unconstrained_power.attr, + &dev_attr_usb_communication_capable.attr, + &dev_attr_dual_role_data.attr, + &dev_attr_unchunked_extended_messages_supported.attr, + &dev_attr_fast_role_swap_current.attr, + &dev_attr_voltage.attr, + &operational_current_attr.attr, + NULL +}; + +static const struct attribute_group sink_fixed_supply_group = { + .is_visible = fixed_attr_is_visible, + .attrs = sink_fixed_supply_attrs, +}; +__ATTRIBUTE_GROUPS(sink_fixed_supply); + +static const struct device_type sink_fixed_supply_type = { + .name = "pdo", + .release = pdo_release, + .groups = sink_fixed_supply_groups, +}; + +/* -------------------------------------------------------------------------- */ +/* Variable Supply */ + +static ssize_t +maximum_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_max_voltage(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(maximum_voltage); + +static ssize_t +minimum_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_min_voltage(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(minimum_voltage); + +static struct attribute *source_variable_supply_attrs[] = { + &dev_attr_maximum_voltage.attr, + &dev_attr_minimum_voltage.attr, + &maximum_current_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(source_variable_supply); + +static const struct device_type source_variable_supply_type = { + .name = "pdo", + .release = pdo_release, + .groups = source_variable_supply_groups, +}; + +static struct attribute *sink_variable_supply_attrs[] = { + &dev_attr_maximum_voltage.attr, + &dev_attr_minimum_voltage.attr, + &operational_current_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(sink_variable_supply); + +static const struct device_type sink_variable_supply_type = { + .name = "pdo", + .release = pdo_release, + .groups = sink_variable_supply_groups, +}; + +/* -------------------------------------------------------------------------- */ +/* Battery */ + +static ssize_t +maximum_power_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umW\n", pdo_max_power(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(maximum_power); + +static ssize_t +operational_power_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umW\n", pdo_max_power(to_pdo(dev)->pdo)); +} +static DEVICE_ATTR_RO(operational_power); + +static struct attribute *source_battery_attrs[] = { + &dev_attr_maximum_voltage.attr, + &dev_attr_minimum_voltage.attr, + &dev_attr_maximum_power.attr, + NULL +}; +ATTRIBUTE_GROUPS(source_battery); + +static const struct device_type source_battery_type = { + .name = "pdo", + .release = pdo_release, + .groups = source_battery_groups, +}; + +static struct attribute *sink_battery_attrs[] = { + &dev_attr_maximum_voltage.attr, + &dev_attr_minimum_voltage.attr, + &dev_attr_operational_power.attr, + NULL +}; +ATTRIBUTE_GROUPS(sink_battery); + +static const struct device_type sink_battery_type = { + .name = "pdo", + .release = pdo_release, + .groups = sink_battery_groups, +}; + +/* -------------------------------------------------------------------------- */ +/* Standard Power Range (SPR) Programmable Power Supply (PPS) */ + +static ssize_t +pps_power_limited_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & BIT(27))); +} +static DEVICE_ATTR_RO(pps_power_limited); + +static ssize_t +pps_max_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_pps_apdo_max_voltage(to_pdo(dev)->pdo)); +} + +static ssize_t +pps_min_voltage_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umV\n", pdo_pps_apdo_min_voltage(to_pdo(dev)->pdo)); +} + +static ssize_t +pps_max_current_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umA\n", pdo_pps_apdo_max_current(to_pdo(dev)->pdo)); +} + +static struct device_attribute pps_max_voltage_attr = { + .attr = { + .name = "maximum_voltage", + .mode = 0444, + }, + .show = pps_max_voltage_show, +}; + +static struct device_attribute pps_min_voltage_attr = { + .attr = { + .name = "minimum_voltage", + .mode = 0444, + }, + .show = pps_min_voltage_show, +}; + +static struct device_attribute pps_max_current_attr = { + .attr = { + .name = "maximum_current", + .mode = 0444, + }, + .show = pps_max_current_show, +}; + +static struct attribute *source_pps_attrs[] = { + &dev_attr_pps_power_limited.attr, + &pps_max_voltage_attr.attr, + &pps_min_voltage_attr.attr, + &pps_max_current_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(source_pps); + +static const struct device_type source_pps_type = { + .name = "pdo", + .release = pdo_release, + .groups = source_pps_groups, +}; + +static struct attribute *sink_pps_attrs[] = { + &pps_max_voltage_attr.attr, + &pps_min_voltage_attr.attr, + &pps_max_current_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(sink_pps); + +static const struct device_type sink_pps_type = { + .name = "pdo", + .release = pdo_release, + .groups = sink_pps_groups, +}; + +/* -------------------------------------------------------------------------- */ +/* Standard Power Range (SPR) Adjustable Voltage Supply (AVS) */ + +static ssize_t +spr_avs_9v_to_15v_max_current_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umA\n", + pdo_spr_avs_apdo_9v_to_15v_max_current_ma(to_pdo(dev)->pdo)); +} + +static ssize_t +spr_avs_15v_to_20v_max_current_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%umA\n", + pdo_spr_avs_apdo_15v_to_20v_max_current_ma(to_pdo(dev)->pdo)); +} + +static ssize_t +spr_avs_src_peak_current_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", + pdo_spr_avs_apdo_src_peak_current(to_pdo(dev)->pdo)); +} + +static struct device_attribute spr_avs_9v_to_15v_max_current_attr = { + .attr = { + .name = "maximum_current_9V_to_15V", + .mode = 0444, + }, + .show = spr_avs_9v_to_15v_max_current_show, +}; + +static struct device_attribute spr_avs_15v_to_20v_max_current_attr = { + .attr = { + .name = "maximum_current_15V_to_20V", + .mode = 0444, + }, + .show = spr_avs_15v_to_20v_max_current_show, +}; + +static struct device_attribute spr_avs_src_peak_current_attr = { + .attr = { + .name = "peak_current", + .mode = 0444, + }, + .show = spr_avs_src_peak_current_show, +}; + +static struct attribute *source_spr_avs_attrs[] = { + &spr_avs_9v_to_15v_max_current_attr.attr, + &spr_avs_15v_to_20v_max_current_attr.attr, + &spr_avs_src_peak_current_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(source_spr_avs); + +static const struct device_type source_spr_avs_type = { + .name = "pdo", + .release = pdo_release, + .groups = source_spr_avs_groups, +}; + +static struct attribute *sink_spr_avs_attrs[] = { + &spr_avs_9v_to_15v_max_current_attr.attr, + &spr_avs_15v_to_20v_max_current_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(sink_spr_avs); + +static const struct device_type sink_spr_avs_type = { + .name = "pdo", + .release = pdo_release, + .groups = sink_spr_avs_groups, +}; + +/* -------------------------------------------------------------------------- */ + +static const char * const supply_name[] = { + [PDO_TYPE_FIXED] = "fixed_supply", + [PDO_TYPE_BATT] = "battery", + [PDO_TYPE_VAR] = "variable_supply", +}; + +static const char * const apdo_supply_name[] = { + [APDO_TYPE_PPS] = "programmable_supply", + [APDO_TYPE_SPR_AVS] = "spr_adjustable_voltage_supply", +}; + +static const struct device_type *source_type[] = { + [PDO_TYPE_FIXED] = &source_fixed_supply_type, + [PDO_TYPE_BATT] = &source_battery_type, + [PDO_TYPE_VAR] = &source_variable_supply_type, +}; + +static const struct device_type *source_apdo_type[] = { + [APDO_TYPE_PPS] = &source_pps_type, + [APDO_TYPE_SPR_AVS] = &source_spr_avs_type, +}; + +static const struct device_type *sink_type[] = { + [PDO_TYPE_FIXED] = &sink_fixed_supply_type, + [PDO_TYPE_BATT] = &sink_battery_type, + [PDO_TYPE_VAR] = &sink_variable_supply_type, +}; + +static const struct device_type *sink_apdo_type[] = { + [APDO_TYPE_PPS] = &sink_pps_type, + [APDO_TYPE_SPR_AVS] = &sink_spr_avs_type, +}; + +/* REVISIT: Export when EPR_*_Capabilities need to be supported. */ +static int add_pdo(struct usb_power_delivery_capabilities *cap, u32 pdo, int position) +{ + const struct device_type *type; + const char *name; + struct pdo *p; + int ret; + + p = kzalloc_obj(*p); + if (!p) + return -ENOMEM; + + p->pdo = pdo; + p->object_position = position; + + if (pdo_type(pdo) == PDO_TYPE_APDO) { + /* + * FIXME: Only PPS, SPR_AVS supported for now! + * Skipping others. + */ + if (pdo_apdo_type(pdo) != APDO_TYPE_PPS && + pdo_apdo_type(pdo) != APDO_TYPE_SPR_AVS) { + dev_warn(&cap->dev, "Unknown APDO type. PDO 0x%08x\n", pdo); + kfree(p); + return 0; + } + + if (is_source(cap->role)) + type = source_apdo_type[pdo_apdo_type(pdo)]; + else + type = sink_apdo_type[pdo_apdo_type(pdo)]; + + name = apdo_supply_name[pdo_apdo_type(pdo)]; + } else { + if (is_source(cap->role)) + type = source_type[pdo_type(pdo)]; + else + type = sink_type[pdo_type(pdo)]; + + name = supply_name[pdo_type(pdo)]; + } + + p->dev.parent = &cap->dev; + p->dev.type = type; + dev_set_name(&p->dev, "%u:%s", position + 1, name); + + ret = device_register(&p->dev); + if (ret) { + put_device(&p->dev); + return ret; + } + + return 0; +} + +static int remove_pdo(struct device *dev, void *data) +{ + device_unregister(dev); + return 0; +} + +/* -------------------------------------------------------------------------- */ + +static const char * const cap_name[] = { + [TYPEC_SINK] = "sink-capabilities", + [TYPEC_SOURCE] = "source-capabilities", +}; + +static void pd_capabilities_release(struct device *dev) +{ + kfree(to_usb_power_delivery_capabilities(dev)); +} + +static const struct device_type pd_capabilities_type = { + .name = "capabilities", + .release = pd_capabilities_release, +}; + +/** + * usb_power_delivery_register_capabilities - Register a set of capabilities. + * @pd: The USB PD instance that the capabilities belong to. + * @desc: Description of the Capabilities Message. + * + * This function registers a Capabilities Message described in @desc. The + * capabilities will have their own sub-directory under @pd in sysfs. + * + * The function returns pointer to struct usb_power_delivery_capabilities, or + * ERR_PRT(errno). + */ +struct usb_power_delivery_capabilities * +usb_power_delivery_register_capabilities(struct usb_power_delivery *pd, + struct usb_power_delivery_capabilities_desc *desc) +{ + struct usb_power_delivery_capabilities *cap; + int ret; + int i; + + cap = kzalloc_obj(*cap); + if (!cap) + return ERR_PTR(-ENOMEM); + + cap->pd = pd; + cap->role = desc->role; + + cap->dev.parent = &pd->dev; + cap->dev.type = &pd_capabilities_type; + dev_set_name(&cap->dev, "%s", cap_name[cap->role]); + + ret = device_register(&cap->dev); + if (ret) { + put_device(&cap->dev); + return ERR_PTR(ret); + } + + for (i = 0; i < PDO_MAX_OBJECTS && desc->pdo[i]; i++) { + ret = add_pdo(cap, desc->pdo[i], i); + if (ret) { + usb_power_delivery_unregister_capabilities(cap); + return ERR_PTR(ret); + } + } + + return cap; +} +EXPORT_SYMBOL_GPL(usb_power_delivery_register_capabilities); + +/** + * usb_power_delivery_unregister_capabilities - Unregister a set of capabilities + * @cap: The capabilities + */ +void usb_power_delivery_unregister_capabilities(struct usb_power_delivery_capabilities *cap) +{ + if (!cap) + return; + + device_for_each_child(&cap->dev, NULL, remove_pdo); + device_unregister(&cap->dev); +} +EXPORT_SYMBOL_GPL(usb_power_delivery_unregister_capabilities); + +/* -------------------------------------------------------------------------- */ + +static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_power_delivery *pd = to_usb_power_delivery(dev); + + return sysfs_emit(buf, "%u.%u\n", (pd->revision >> 8) & 0xff, (pd->revision >> 4) & 0xf); +} +static DEVICE_ATTR_RO(revision); + +static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_power_delivery *pd = to_usb_power_delivery(dev); + + return sysfs_emit(buf, "%u.%u\n", (pd->version >> 8) & 0xff, (pd->version >> 4) & 0xf); +} +static DEVICE_ATTR_RO(version); + +static struct attribute *pd_attrs[] = { + &dev_attr_revision.attr, + &dev_attr_version.attr, + NULL +}; + +static umode_t pd_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct usb_power_delivery *pd = to_usb_power_delivery(kobj_to_dev(kobj)); + + if (attr == &dev_attr_version.attr && !pd->version) + return 0; + + return attr->mode; +} + +static const struct attribute_group pd_group = { + .is_visible = pd_attr_is_visible, + .attrs = pd_attrs, +}; +__ATTRIBUTE_GROUPS(pd); + +static void pd_release(struct device *dev) +{ + struct usb_power_delivery *pd = to_usb_power_delivery(dev); + + ida_free(&pd_ida, pd->id); + kfree(pd); +} + +static const struct device_type pd_type = { + .name = "usb_power_delivery", + .release = pd_release, + .groups = pd_groups, +}; + +struct usb_power_delivery *usb_power_delivery_find(const char *name) +{ + struct device *dev; + + dev = class_find_device_by_name(&pd_class, name); + + return dev ? to_usb_power_delivery(dev) : NULL; +} + +/** + * usb_power_delivery_register - Register USB Power Delivery Support. + * @parent: Parent device. + * @desc: Description of the USB PD contract. + * + * This routine can be used to register USB Power Delivery capabilities that a + * device or devices can support. These capabilities represent all the + * capabilities that can be negotiated with a partner, so not only the Power + * Capabilities that are negotiated using the USB PD Capabilities Message. + * + * The USB Power Delivery Support object that this routine generates can be used + * as the parent object for all the actual USB Power Delivery Messages and + * objects that can be negotiated with the partner. + * + * Returns handle to struct usb_power_delivery or ERR_PTR. + */ +struct usb_power_delivery * +usb_power_delivery_register(struct device *parent, struct usb_power_delivery_desc *desc) +{ + struct usb_power_delivery *pd; + int ret; + + pd = kzalloc_obj(*pd); + if (!pd) + return ERR_PTR(-ENOMEM); + + ret = ida_alloc(&pd_ida, GFP_KERNEL); + if (ret < 0) { + kfree(pd); + return ERR_PTR(ret); + } + + pd->id = ret; + pd->revision = desc->revision; + pd->version = desc->version; + + pd->dev.parent = parent; + pd->dev.type = &pd_type; + pd->dev.class = &pd_class; + dev_set_name(&pd->dev, "pd%d", pd->id); + + ret = device_register(&pd->dev); + if (ret) { + put_device(&pd->dev); + return ERR_PTR(ret); + } + + return pd; +} +EXPORT_SYMBOL_GPL(usb_power_delivery_register); + +/** + * usb_power_delivery_unregister - Unregister USB Power Delivery Support. + * @pd: The USB PD contract. + */ +void usb_power_delivery_unregister(struct usb_power_delivery *pd) +{ + if (IS_ERR_OR_NULL(pd)) + return; + + device_unregister(&pd->dev); +} +EXPORT_SYMBOL_GPL(usb_power_delivery_unregister); + +/** + * usb_power_delivery_link_device - Link device to its USB PD object. + * @pd: The USB PD instance. + * @dev: The device. + * + * This function can be used to create a symlink named "usb_power_delivery" for + * @dev that points to @pd. + */ +int usb_power_delivery_link_device(struct usb_power_delivery *pd, struct device *dev) +{ + int ret; + + if (IS_ERR_OR_NULL(pd) || !dev) + return 0; + + ret = sysfs_create_link(&dev->kobj, &pd->dev.kobj, "usb_power_delivery"); + if (ret) + return ret; + + get_device(&pd->dev); + get_device(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(usb_power_delivery_link_device); + +/** + * usb_power_delivery_unlink_device - Unlink device from its USB PD object. + * @pd: The USB PD instance. + * @dev: The device. + * + * Remove the symlink that was previously created with pd_link_device(). + */ +void usb_power_delivery_unlink_device(struct usb_power_delivery *pd, struct device *dev) +{ + if (IS_ERR_OR_NULL(pd) || !dev) + return; + + sysfs_remove_link(&dev->kobj, "usb_power_delivery"); + put_device(&pd->dev); + put_device(dev); +} +EXPORT_SYMBOL_GPL(usb_power_delivery_unlink_device); + +/* -------------------------------------------------------------------------- */ + +int __init usb_power_delivery_init(void) +{ + return class_register(&pd_class); +} + +void __exit usb_power_delivery_exit(void) +{ + ida_destroy(&pd_ida); + class_unregister(&pd_class); +} diff --git a/drivers/usb/typec/pd.h b/drivers/usb/typec/pd.h new file mode 100644 index 000000000..049a1aad4 --- /dev/null +++ b/drivers/usb/typec/pd.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __USB_POWER_DELIVERY__ +#define __USB_POWER_DELIVERY__ + +#include +#include + +struct usb_power_delivery { + struct device dev; + int id; + u16 revision; + u16 version; +}; + +struct usb_power_delivery_capabilities { + struct device dev; + struct usb_power_delivery *pd; + enum typec_role role; +}; + +#define to_usb_power_delivery_capabilities(o) container_of(o, struct usb_power_delivery_capabilities, dev) +#define to_usb_power_delivery(o) container_of(o, struct usb_power_delivery, dev) + +struct usb_power_delivery *usb_power_delivery_find(const char *name); + +int usb_power_delivery_init(void); +void usb_power_delivery_exit(void); + +#endif /* __USB_POWER_DELIVERY__ */ diff --git a/drivers/usb/typec/port-mapper.c b/drivers/usb/typec/port-mapper.c new file mode 100644 index 000000000..cdbb7c11d --- /dev/null +++ b/drivers/usb/typec/port-mapper.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Type-C Connector Class Port Mapping Utility + * + * Copyright (C) 2021, Intel Corporation + * Author: Heikki Krogerus + */ + +#include +#include +#include +#include + +#include "class.h" + +static int typec_aggregate_bind(struct device *dev) +{ + struct typec_port *port = to_typec_port(dev); + + return component_bind_all(dev, &port->con); +} + +static void typec_aggregate_unbind(struct device *dev) +{ + struct typec_port *port = to_typec_port(dev); + + component_unbind_all(dev, &port->con); +} + +static const struct component_master_ops typec_aggregate_ops = { + .bind = typec_aggregate_bind, + .unbind = typec_aggregate_unbind, +}; + +struct each_port_arg { + struct typec_port *port; + struct component_match *match; +}; + +static int usb4_port_compare(struct device *dev, void *fwnode) +{ + return usb4_usb3_port_match(dev, fwnode); +} + +static int typec_port_compare(struct device *dev, void *fwnode) +{ + return device_match_fwnode(dev, fwnode); +} + +static int typec_port_match(struct device *dev, void *data) +{ + struct acpi_device *adev = to_acpi_device(dev); + struct each_port_arg *arg = data; + struct acpi_device *con_adev; + + con_adev = ACPI_COMPANION(&arg->port->dev); + if (con_adev == adev) + return 0; + + if (con_adev->pld_crc == adev->pld_crc) { + struct fwnode_handle *adev_fwnode = acpi_fwnode_handle(adev); + + component_match_add(&arg->port->dev, &arg->match, typec_port_compare, + adev_fwnode); + + /* + * If dev is USB 3.x port, it may have reference to the + * USB4 host interface in which case we can also link the + * Type-C port with the USB4 port. + */ + if (fwnode_property_present(adev_fwnode, "usb4-host-interface")) + component_match_add(&arg->port->dev, &arg->match, + usb4_port_compare, adev_fwnode); + } + + return 0; +} + +int typec_link_ports(struct typec_port *con) +{ + struct each_port_arg arg = { .port = con, .match = NULL }; + + if (!has_acpi_companion(&con->dev)) + return 0; + + acpi_bus_for_each_dev(typec_port_match, &arg); + if (!arg.match) + return 0; + + /* + * REVISIT: Now each connector can have only a single component master. + * So far only the USB ports connected to the USB Type-C connector share + * the _PLD with it, but if there one day is something else (like maybe + * the DisplayPort ACPI device object) that also shares the _PLD with + * the connector, every one of those needs to have its own component + * master, because each different type of component needs to be bind to + * the connector independently of the other components. That requires + * improvements to the component framework. Right now you can only have + * one master per device. + */ + return component_master_add_with_match(&con->dev, &typec_aggregate_ops, arg.match); +} + +void typec_unlink_ports(struct typec_port *con) +{ + if (has_acpi_companion(&con->dev)) + component_master_del(&con->dev, &typec_aggregate_ops); +} diff --git a/drivers/usb/typec/retimer.c b/drivers/usb/typec/retimer.c new file mode 100644 index 000000000..bfd80292b --- /dev/null +++ b/drivers/usb/typec/retimer.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2022 Google LLC + * + * USB Type-C Retimer support. + * Author: Prashant Malani + * + */ + +#include +#include +#include +#include +#include +#include + +#include "class.h" +#include "retimer.h" + +static int retimer_fwnode_match(struct device *dev, const void *fwnode) +{ + return is_typec_retimer(dev) && device_match_fwnode(dev, fwnode); +} + +static void *typec_retimer_match(const struct fwnode_handle *fwnode, const char *id, void *data) +{ + struct device *dev; + + if (id && !fwnode_property_present(fwnode, id)) + return NULL; + + dev = class_find_device(&retimer_class, NULL, fwnode, + retimer_fwnode_match); + + return dev ? to_typec_retimer(dev) : ERR_PTR(-EPROBE_DEFER); +} + +/** + * fwnode_typec_retimer_get - Find USB Type-C retimer. + * @fwnode: The caller device node. + * + * Finds a retimer linked to the caller. This function is primarily meant for the + * Type-C drivers. Returns a reference to the retimer on success, NULL if no + * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection + * was found but the retimer has not been enumerated yet. + */ +struct typec_retimer *fwnode_typec_retimer_get(struct fwnode_handle *fwnode) +{ + struct typec_retimer *retimer; + + retimer = fwnode_connection_find_match(fwnode, "retimer-switch", NULL, typec_retimer_match); + if (!IS_ERR_OR_NULL(retimer)) + WARN_ON(!try_module_get(retimer->dev.parent->driver->owner)); + + return retimer; +} +EXPORT_SYMBOL_GPL(fwnode_typec_retimer_get); + +/** + * typec_retimer_put - Release handle to a retimer. + * @retimer: USB Type-C Connector Retimer. + * + * Decrements reference count for @retimer. + */ +void typec_retimer_put(struct typec_retimer *retimer) +{ + if (!IS_ERR_OR_NULL(retimer)) { + module_put(retimer->dev.parent->driver->owner); + put_device(&retimer->dev); + } +} +EXPORT_SYMBOL_GPL(typec_retimer_put); + +int typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) +{ + if (IS_ERR_OR_NULL(retimer)) + return 0; + + return retimer->set(retimer, state); +} +EXPORT_SYMBOL_GPL(typec_retimer_set); + +static void typec_retimer_release(struct device *dev) +{ + kfree(to_typec_retimer(dev)); +} + +const struct device_type typec_retimer_dev_type = { + .name = "typec_retimer", + .release = typec_retimer_release, +}; + +/** + * typec_retimer_register - Register a retimer device. + * @parent: Parent device. + * @desc: Retimer description. + * + * Some USB Type-C connectors have their physical lines routed through retimers before they + * reach muxes or host controllers. In some cases (for example: using alternate modes) + * these retimers need to be reconfigured appropriately. This function registers retimer + * switches which route and potentially modify the signals on the Type C physical lines + * enroute to the host controllers. + */ +struct typec_retimer * +typec_retimer_register(struct device *parent, const struct typec_retimer_desc *desc) +{ + struct typec_retimer *retimer; + int ret; + + if (!desc || !desc->set) + return ERR_PTR(-EINVAL); + + retimer = kzalloc_obj(*retimer); + if (!retimer) + return ERR_PTR(-ENOMEM); + + retimer->set = desc->set; + + device_initialize(&retimer->dev); + retimer->dev.parent = parent; + retimer->dev.fwnode = desc->fwnode; + retimer->dev.class = &retimer_class; + retimer->dev.type = &typec_retimer_dev_type; + retimer->dev.driver_data = desc->drvdata; + dev_set_name(&retimer->dev, "%s-retimer", + desc->name ? desc->name : dev_name(parent)); + + ret = device_add(&retimer->dev); + if (ret) { + dev_err(parent, "failed to register retimer (%d)\n", ret); + put_device(&retimer->dev); + return ERR_PTR(ret); + } + + return retimer; +} +EXPORT_SYMBOL_GPL(typec_retimer_register); + +/** + * typec_retimer_unregister - Unregister retimer device. + * @retimer: USB Type-C Connector retimer. + * + * Unregister retimer that was registered with typec_retimer_register(). + */ +void typec_retimer_unregister(struct typec_retimer *retimer) +{ + if (!IS_ERR_OR_NULL(retimer)) + device_unregister(&retimer->dev); +} +EXPORT_SYMBOL_GPL(typec_retimer_unregister); + +void *typec_retimer_get_drvdata(struct typec_retimer *retimer) +{ + return dev_get_drvdata(&retimer->dev); +} +EXPORT_SYMBOL_GPL(typec_retimer_get_drvdata); + +const struct class retimer_class = { + .name = "retimer", +}; diff --git a/drivers/usb/typec/retimer.h b/drivers/usb/typec/retimer.h new file mode 100644 index 000000000..d6a5ef988 --- /dev/null +++ b/drivers/usb/typec/retimer.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __USB_TYPEC_RETIMER__ +#define __USB_TYPEC_RETIMER__ + +#include + +struct typec_retimer { + struct device dev; + typec_retimer_set_fn_t set; +}; + +#define to_typec_retimer(_dev_) container_of(_dev_, struct typec_retimer, dev) + +extern const struct device_type typec_retimer_dev_type; + +#define is_typec_retimer(dev) ((dev)->type == &typec_retimer_dev_type) + +#endif /* __USB_TYPEC_RETIMER__ */ diff --git a/drivers/usb/typec/rt1719.c b/drivers/usb/typec/rt1719.c new file mode 100644 index 000000000..0b0c23a0b --- /dev/null +++ b/drivers/usb/typec/rt1719.c @@ -0,0 +1,954 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RT1719_REG_TXCTRL1 0x03 +#define RT1719_REG_TXCTRL2 0x04 +#define RT1719_REG_POLICYINFO 0x0E +#define RT1719_REG_SRCPDO1 0x11 +#define RT1719_REG_MASKS 0x2D +#define RT1719_REG_EVENTS 0x33 +#define RT1719_REG_STATS 0x37 +#define RT1719_REG_PSELINFO 0x3C +#define RT1719_REG_USBSETINFO 0x3E +#define RT1719_REG_VENID 0x82 + +#define RT1719_UNIQUE_PID 0x1719 +#define RT1719_REQDRSWAP_MASK BIT(7) +#define RT1719_EVALMODE_MASK BIT(4) +#define RT1719_REQSRCPDO_MASK GENMASK(2, 0) +#define RT1719_TXSPDOREQ_MASK BIT(7) +#define RT1719_INT_DRSW_ACCEPT BIT(23) +#define RT1719_INT_RX_SRCCAP BIT(21) +#define RT1719_INT_VBUS_DCT BIT(6) +#define RT1719_INT_VBUS_PRESENT BIT(5) +#define RT1719_INT_PE_SNK_RDY BIT(2) +#define RT1719_CC1_STAT GENMASK(9, 8) +#define RT1719_CC2_STAT GENMASK(11, 10) +#define RT1719_POLARITY_MASK BIT(23) +#define RT1719_DATAROLE_MASK BIT(22) +#define RT1719_PDSPECREV_MASK GENMASK(21, 20) +#define RT1719_SPDOSEL_MASK GENMASK(18, 16) +#define RT1719_SPDONUM_MASK GENMASK(15, 13) +#define RT1719_ATTACH_VBUS BIT(12) +#define RT1719_ATTACH_DBG BIT(10) +#define RT1719_ATTACH_SNK BIT(9) +#define RT1719_ATTACHDEV_MASK (RT1719_ATTACH_VBUS | RT1719_ATTACH_DBG | \ + RT1719_ATTACH_SNK) +#define RT1719_PE_EXP_CONTRACT BIT(2) +#define RT1719_PSEL_SUPPORT BIT(15) +#define RT1719_TBLSEL_MASK BIT(6) +#define RT1719_LATPSEL_MASK GENMASK(5, 0) +#define RT1719_USBINFO_MASK GENMASK(1, 0) +#define RT1719_USB_DFPUFP 3 +#define RT1719_MAX_SRCPDO 7 + +enum { + SNK_PWR_OPEN = 0, + SNK_PWR_DEF, + SNK_PWR_1P5A, + SNK_PWR_3A +}; + +enum { + USBPD_SPECREV_1_0 = 0, + USBPD_SPECREV_2_0, + USBPD_SPECREV_3_0 +}; + +enum rt1719_snkcap { + RT1719_SNKCAP_5V = 0, + RT1719_SNKCAP_9V, + RT1719_SNKCAP_12V, + RT1719_SNKCAP_15V, + RT1719_SNKCAP_20V, + RT1719_MAX_SNKCAP +}; + +struct rt1719_psel_cap { + u8 lomask; + u8 himask; + u32 milliwatt; + u32 milliamp; +}; + +struct rt1719_data { + struct device *dev; + struct regmap *regmap; + struct typec_port *port; + struct usb_role_switch *role_sw; + struct power_supply *psy; + struct typec_partner *partner; + struct power_supply_desc psy_desc; + struct usb_pd_identity partner_ident; + struct typec_partner_desc partner_desc; + struct completion req_completion; + enum power_supply_usb_type usb_type; + bool attached; + bool pd_capable; + bool drswap_support; + u32 voltage; + u32 req_voltage; + u32 max_current; + u32 op_current; + u32 spdos[RT1719_MAX_SRCPDO]; + u16 snkcaps[RT1719_MAX_SNKCAP]; + int spdo_num; + int spdo_sel; + u32 conn_info; + u16 conn_stat; +}; + +static const enum power_supply_property rt1719_psy_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW +}; + +static int rt1719_read16(struct rt1719_data *data, unsigned int reg, u16 *val) +{ + __le16 regval; + int ret; + + ret = regmap_raw_read(data->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + *val = le16_to_cpu(regval); + return 0; +} + +static int rt1719_read32(struct rt1719_data *data, unsigned int reg, u32 *val) +{ + __le32 regval; + int ret; + + ret = regmap_raw_read(data->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + *val = le32_to_cpu(regval); + return 0; +} + +static int rt1719_write32(struct rt1719_data *data, unsigned int reg, u32 val) +{ + __le32 regval = cpu_to_le32(val); + + return regmap_raw_write(data->regmap, reg, ®val, sizeof(regval)); +} + +static enum typec_pwr_opmode rt1719_get_pwr_opmode(u32 conn, u16 stat) +{ + u16 cc1, cc2, cc_stat; + + cc1 = FIELD_GET(RT1719_CC1_STAT, stat); + cc2 = FIELD_GET(RT1719_CC2_STAT, stat); + + if (conn & RT1719_ATTACH_SNK) { + if (conn & RT1719_POLARITY_MASK) + cc_stat = cc2; + else + cc_stat = cc1; + + switch (cc_stat) { + case SNK_PWR_3A: + return TYPEC_PWR_MODE_3_0A; + case SNK_PWR_1P5A: + return TYPEC_PWR_MODE_1_5A; + } + } else if (conn & RT1719_ATTACH_DBG) { + if ((cc1 == SNK_PWR_1P5A && cc2 == SNK_PWR_DEF) || + (cc1 == SNK_PWR_DEF && cc2 == SNK_PWR_1P5A)) + return TYPEC_PWR_MODE_1_5A; + else if ((cc1 == SNK_PWR_3A && cc2 == SNK_PWR_DEF) || + (cc1 == SNK_PWR_DEF && cc2 == SNK_PWR_3A)) + return TYPEC_PWR_MODE_3_0A; + } + + return TYPEC_PWR_MODE_USB; +} + +static enum typec_data_role rt1719_get_data_role(u32 conn) +{ + if (conn & RT1719_DATAROLE_MASK) + return TYPEC_HOST; + return TYPEC_DEVICE; +} + +static void rt1719_set_data_role(struct rt1719_data *data, + enum typec_data_role data_role, + bool attached) +{ + enum usb_role usb_role = USB_ROLE_NONE; + + if (attached) { + if (data_role == TYPEC_HOST) + usb_role = USB_ROLE_HOST; + else + usb_role = USB_ROLE_DEVICE; + } + + usb_role_switch_set_role(data->role_sw, usb_role); + typec_set_data_role(data->port, data_role); +} + +static void rt1719_update_data_role(struct rt1719_data *data) +{ + if (!data->attached) + return; + + rt1719_set_data_role(data, rt1719_get_data_role(data->conn_info), true); +} + +static void rt1719_register_partner(struct rt1719_data *data) +{ + u16 spec_rev = 0; + + if (data->pd_capable) { + u32 rev; + + rev = FIELD_GET(RT1719_PDSPECREV_MASK, data->conn_info); + switch (rev) { + case USBPD_SPECREV_3_0: + spec_rev = 0x0300; + break; + case USBPD_SPECREV_2_0: + spec_rev = 0x0200; + break; + default: + spec_rev = 0x0100; + break; + } + } + + /* Just to prevent multiple times attach */ + if (data->partner) + typec_unregister_partner(data->partner); + + memset(&data->partner_ident, 0, sizeof(data->partner_ident)); + data->partner_desc.usb_pd = data->pd_capable; + data->partner_desc.pd_revision = spec_rev; + + if (data->conn_info & RT1719_ATTACH_DBG) + data->partner_desc.accessory = TYPEC_ACCESSORY_DEBUG; + else + data->partner_desc.accessory = TYPEC_ACCESSORY_NONE; + + data->partner = typec_register_partner(data->port, &data->partner_desc); +} + +static void rt1719_attach(struct rt1719_data *data) +{ + enum typec_pwr_opmode pwr_opmode; + enum typec_data_role data_role; + u32 volt = 5000, curr = 500; + + if (!(data->conn_info & RT1719_ATTACHDEV_MASK)) + return; + + pwr_opmode = rt1719_get_pwr_opmode(data->conn_info, data->conn_stat); + data_role = rt1719_get_data_role(data->conn_info); + + typec_set_pwr_opmode(data->port, pwr_opmode); + rt1719_set_data_role(data, data_role, true); + + if (data->conn_info & RT1719_ATTACH_SNK) + rt1719_register_partner(data); + + if (pwr_opmode == TYPEC_PWR_MODE_3_0A) + curr = 3000; + else if (pwr_opmode == TYPEC_PWR_MODE_1_5A) + curr = 1500; + + data->voltage = volt * 1000; + data->max_current = data->op_current = curr * 1000; + data->attached = true; + + power_supply_changed(data->psy); +} + +static void rt1719_detach(struct rt1719_data *data) +{ + if (!data->attached || (data->conn_info & RT1719_ATTACHDEV_MASK)) + return; + + typec_unregister_partner(data->partner); + data->partner = NULL; + + typec_set_pwr_opmode(data->port, TYPEC_PWR_MODE_USB); + rt1719_set_data_role(data, TYPEC_DEVICE, false); + + memset32(data->spdos, 0, RT1719_MAX_SRCPDO); + data->spdo_num = 0; + data->voltage = data->max_current = data->op_current = 0; + data->attached = data->pd_capable = false; + + data->usb_type = POWER_SUPPLY_USB_TYPE_C; + + power_supply_changed(data->psy); +} + +static void rt1719_update_operating_status(struct rt1719_data *data) +{ + enum power_supply_usb_type usb_type = POWER_SUPPLY_USB_TYPE_PD; + u32 voltage, max_current, op_current; + int i, snk_sel; + + for (i = 0; i < data->spdo_num; i++) { + u32 pdo = data->spdos[i]; + enum pd_pdo_type type = pdo_type(pdo); + + if (type == PDO_TYPE_APDO) { + usb_type = POWER_SUPPLY_USB_TYPE_PD_PPS; + break; + } + } + + data->spdo_sel = FIELD_GET(RT1719_SPDOSEL_MASK, data->conn_info); + if (data->spdo_sel <= 0) + return; + + data->usb_type = usb_type; + + voltage = pdo_fixed_voltage(data->spdos[data->spdo_sel - 1]); + max_current = pdo_max_current(data->spdos[data->spdo_sel - 1]); + + switch (voltage) { + case 5000: + snk_sel = RT1719_SNKCAP_5V; + break; + case 9000: + snk_sel = RT1719_SNKCAP_9V; + break; + case 12000: + snk_sel = RT1719_SNKCAP_12V; + break; + case 15000: + snk_sel = RT1719_SNKCAP_15V; + break; + case 20000: + snk_sel = RT1719_SNKCAP_20V; + break; + default: + return; + } + + op_current = min(max_current, pdo_max_current(data->snkcaps[snk_sel])); + + /* covert mV/mA to uV/uA */ + data->voltage = voltage * 1000; + data->max_current = max_current * 1000; + data->op_current = op_current * 1000; + + power_supply_changed(data->psy); +} + +static void rt1719_update_pwr_opmode(struct rt1719_data *data) +{ + if (!data->attached) + return; + + if (!data->pd_capable) { + data->pd_capable = true; + + typec_set_pwr_opmode(data->port, TYPEC_PWR_MODE_PD); + rt1719_register_partner(data); + } + + rt1719_update_operating_status(data); +} + +static void rt1719_update_source_pdos(struct rt1719_data *data) +{ + int spdo_num = FIELD_GET(RT1719_SPDONUM_MASK, data->conn_info); + __le32 src_pdos[RT1719_MAX_SRCPDO] = { }; + int i, ret; + + if (!data->attached) + return; + + ret = regmap_raw_read(data->regmap, RT1719_REG_SRCPDO1, src_pdos, + sizeof(__le32) * spdo_num); + if (ret) + return; + + data->spdo_num = spdo_num; + for (i = 0; i < spdo_num; i++) + data->spdos[i] = le32_to_cpu(src_pdos[i]); +} + +static int rt1719_dr_set(struct typec_port *port, enum typec_data_role role) +{ + struct rt1719_data *data = typec_get_drvdata(port); + enum typec_data_role cur_role; + int ret; + + if (!data->attached || !data->pd_capable || !data->drswap_support) + return -EOPNOTSUPP; + + if (data->spdo_num > 0 && !(data->spdos[0] & PDO_FIXED_DATA_SWAP)) + return -EINVAL; + + cur_role = rt1719_get_data_role(data->conn_info); + if (cur_role == role) + return 0; + + ret = regmap_update_bits(data->regmap, RT1719_REG_TXCTRL1, + RT1719_REQDRSWAP_MASK, RT1719_REQDRSWAP_MASK); + if (ret) + return ret; + + reinit_completion(&data->req_completion); + ret = wait_for_completion_timeout(&data->req_completion, + msecs_to_jiffies(400)); + if (ret == 0) + return -ETIMEDOUT; + + cur_role = rt1719_get_data_role(data->conn_info); + if (cur_role != role) + return -EAGAIN; + + rt1719_set_data_role(data, role, true); + return 0; +} + +static const struct typec_operations rt1719_port_ops = { + .dr_set = rt1719_dr_set, +}; + +static int rt1719_usbpd_request_voltage(struct rt1719_data *data) +{ + u32 src_voltage; + int snk_sel, src_sel = -1; + int i, ret; + + if (!data->attached || !data->pd_capable || data->spdo_sel <= 0) + return -EINVAL; + + src_voltage = pdo_fixed_voltage(data->spdos[data->spdo_sel - 1]); + if (src_voltage == data->req_voltage) + return 0; + + switch (data->req_voltage) { + case 5000: + snk_sel = RT1719_SNKCAP_5V; + break; + case 9000: + snk_sel = RT1719_SNKCAP_9V; + break; + case 12000: + snk_sel = RT1719_SNKCAP_12V; + break; + case 15000: + snk_sel = RT1719_SNKCAP_15V; + break; + case 20000: + snk_sel = RT1719_SNKCAP_20V; + break; + default: + return -EINVAL; + } + + if (!(data->snkcaps[snk_sel] & RT1719_PSEL_SUPPORT)) + return -EINVAL; + + for (i = 0; i < data->spdo_num; i++) { + enum pd_pdo_type type = pdo_type(data->spdos[i]); + + if (type != PDO_TYPE_FIXED) + continue; + + src_voltage = pdo_fixed_voltage(data->spdos[i]); + if (src_voltage == data->req_voltage) { + src_sel = i; + break; + } + } + + if (src_sel == -1) + return -EOPNOTSUPP; + + ret = regmap_update_bits(data->regmap, RT1719_REG_TXCTRL1, + RT1719_EVALMODE_MASK | RT1719_REQSRCPDO_MASK, + RT1719_EVALMODE_MASK | (src_sel + 1)); + ret |= regmap_update_bits(data->regmap, RT1719_REG_TXCTRL2, + RT1719_TXSPDOREQ_MASK, RT1719_TXSPDOREQ_MASK); + if (ret) + return ret; + + reinit_completion(&data->req_completion); + ret = wait_for_completion_timeout(&data->req_completion, + msecs_to_jiffies(400)); + if (!ret) + return -ETIMEDOUT; + + return 0; +} + +static int rt1719_psy_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct rt1719_data *data = power_supply_get_drvdata(psy); + + if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW) { + data->req_voltage = val->intval / 1000; + return rt1719_usbpd_request_voltage(data); + } + + return -EINVAL; +} + +static int rt1719_psy_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rt1719_data *data = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = data->attached ? 1 : 0; + break; + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = data->usb_type; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = data->voltage; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = data->max_current; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = data->op_current; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int rt1719_psy_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW) + return 1; + return 0; +} + +static int devm_rt1719_psy_register(struct rt1719_data *data) +{ + struct power_supply_config psy_cfg = { }; + char *psy_name; + + psy_cfg.fwnode = dev_fwnode(data->dev); + psy_cfg.drv_data = data; + + psy_name = devm_kasprintf(data->dev, GFP_KERNEL, "rt1719-source-psy-%s", + dev_name(data->dev)); + if (!psy_name) + return -ENOMEM; + + data->psy_desc.name = psy_name; + data->psy_desc.type = POWER_SUPPLY_TYPE_USB; + data->psy_desc.usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD) | + BIT(POWER_SUPPLY_USB_TYPE_PD_PPS); + data->psy_desc.properties = rt1719_psy_properties; + data->psy_desc.num_properties = ARRAY_SIZE(rt1719_psy_properties); + data->psy_desc.get_property = rt1719_psy_get_property; + data->psy_desc.set_property = rt1719_psy_set_property; + data->psy_desc.property_is_writeable = rt1719_psy_property_is_writeable; + + data->usb_type = POWER_SUPPLY_USB_TYPE_C; + + data->psy = devm_power_supply_register(data->dev, &data->psy_desc, + &psy_cfg); + + return PTR_ERR_OR_ZERO(data->psy); +} + +static irqreturn_t rt1719_irq_handler(int irq, void *priv) +{ + struct rt1719_data *data = priv; + u32 events, conn_info; + u16 conn_stat; + int ret; + + ret = rt1719_read32(data, RT1719_REG_EVENTS, &events); + ret |= rt1719_read32(data, RT1719_REG_POLICYINFO, &conn_info); + ret |= rt1719_read16(data, RT1719_REG_STATS, &conn_stat); + if (ret) + return IRQ_NONE; + + data->conn_info = conn_info; + data->conn_stat = conn_stat; + + events &= (RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP | + RT1719_INT_VBUS_PRESENT | RT1719_INT_VBUS_DCT | + RT1719_INT_PE_SNK_RDY); + + if (events & RT1719_INT_DRSW_ACCEPT) + rt1719_update_data_role(data); + + if (events & RT1719_INT_VBUS_PRESENT) + rt1719_attach(data); + + if (events & RT1719_INT_VBUS_DCT) + rt1719_detach(data); + + if (events & RT1719_INT_RX_SRCCAP) + rt1719_update_source_pdos(data); + + if (events & RT1719_INT_PE_SNK_RDY) { + complete(&data->req_completion); + rt1719_update_pwr_opmode(data); + } + + /* Write 1 to clear already handled events */ + rt1719_write32(data, RT1719_REG_EVENTS, events); + + return IRQ_HANDLED; +} + +static int rt1719_irq_init(struct rt1719_data *data) +{ + struct i2c_client *i2c = to_i2c_client(data->dev); + u32 irq_enable; + int ret; + + irq_enable = RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP | + RT1719_INT_VBUS_DCT | RT1719_INT_VBUS_PRESENT | + RT1719_INT_PE_SNK_RDY; + + ret = rt1719_write32(data, RT1719_REG_MASKS, irq_enable); + if (ret) { + dev_err(&i2c->dev, "Failed to config irq enable\n"); + return ret; + } + + return devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, + rt1719_irq_handler, IRQF_ONESHOT, + dev_name(&i2c->dev), data); +} + +static int rt1719_init_attach_state(struct rt1719_data *data) +{ + u32 conn_info, irq_clear; + u16 conn_stat; + int ret; + + irq_clear = RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP | + RT1719_INT_VBUS_DCT | RT1719_INT_VBUS_PRESENT | + RT1719_INT_PE_SNK_RDY; + + ret = rt1719_read32(data, RT1719_REG_POLICYINFO, &conn_info); + ret |= rt1719_read16(data, RT1719_REG_STATS, &conn_stat); + ret |= rt1719_write32(data, RT1719_REG_EVENTS, irq_clear); + if (ret) + return ret; + + data->conn_info = conn_info; + data->conn_stat = conn_stat; + + if (conn_info & RT1719_ATTACHDEV_MASK) + rt1719_attach(data); + + if (conn_info & RT1719_PE_EXP_CONTRACT) { + rt1719_update_source_pdos(data); + rt1719_update_pwr_opmode(data); + } + + return 0; +} + +#define RT1719_PSEL_CAPINFO(_lomask, _milliwatt, _himask, _milliamp) { \ + .lomask = _lomask, \ + .milliwatt = _milliwatt, \ + .himask = _himask, \ + .milliamp = _milliamp, \ +} + +static const struct rt1719_psel_cap rt1719_psel_caps[] = { + RT1719_PSEL_CAPINFO(0x18, 75000, 0x10, 5000), + RT1719_PSEL_CAPINFO(0x18, 60000, 0x10, 4500), + RT1719_PSEL_CAPINFO(0x18, 45000, 0x10, 4000), + RT1719_PSEL_CAPINFO(0x18, 30000, 0x10, 3500), + RT1719_PSEL_CAPINFO(0x18, 25000, 0x10, 3000), + RT1719_PSEL_CAPINFO(0x18, 20000, 0x10, 2500), + RT1719_PSEL_CAPINFO(0x18, 15000, 0x10, 2000), + RT1719_PSEL_CAPINFO(0x18, 10000, 0x10, 1000), + RT1719_PSEL_CAPINFO(0x1C, 60000, 0x1F, 5000), + RT1719_PSEL_CAPINFO(0x1C, 45000, 0x1F, 4500), + RT1719_PSEL_CAPINFO(0x1C, 30000, 0x1F, 4000), + RT1719_PSEL_CAPINFO(0x1C, 24000, 0x1F, 3500), + RT1719_PSEL_CAPINFO(0x1C, 15000, 0x1F, 3000), + RT1719_PSEL_CAPINFO(0x1C, 10000, 0x1F, 2500), + RT1719_PSEL_CAPINFO(0x0C, 60000, 0x1F, 2000), + RT1719_PSEL_CAPINFO(0x0C, 45000, 0x1F, 1000), + RT1719_PSEL_CAPINFO(0x0C, 36000, 0x08, 5000), + RT1719_PSEL_CAPINFO(0x0C, 30000, 0x08, 4500), + RT1719_PSEL_CAPINFO(0x0C, 24000, 0x08, 4000), + RT1719_PSEL_CAPINFO(0x0C, 15000, 0x08, 3500), + RT1719_PSEL_CAPINFO(0x0C, 10000, 0x08, 3000), + RT1719_PSEL_CAPINFO(0x1E, 45000, 0x08, 2500), + RT1719_PSEL_CAPINFO(0x1E, 36000, 0x08, 2000), + RT1719_PSEL_CAPINFO(0x1E, 27000, 0x08, 1500), + RT1719_PSEL_CAPINFO(0x1E, 20000, 0x08, 1000), + RT1719_PSEL_CAPINFO(0x1E, 15000, 0x0F, 5000), + RT1719_PSEL_CAPINFO(0x1E, 9000, 0x0F, 4500), + RT1719_PSEL_CAPINFO(0x0E, 45000, 0x0F, 4000), + RT1719_PSEL_CAPINFO(0x0E, 36000, 0x0F, 3500), + RT1719_PSEL_CAPINFO(0x0E, 27000, 0x0F, 3000), + RT1719_PSEL_CAPINFO(0x0E, 20000, 0x0F, 2500), + RT1719_PSEL_CAPINFO(0x0E, 15000, 0x0F, 2000), + RT1719_PSEL_CAPINFO(0x0E, 9000, 0x0F, 1500), + RT1719_PSEL_CAPINFO(0x06, 45000, 0x0F, 1000), + RT1719_PSEL_CAPINFO(0x06, 36000, 0x0F, 500), + RT1719_PSEL_CAPINFO(0x06, 27000, 0x04, 5000), + RT1719_PSEL_CAPINFO(0x06, 24000, 0x04, 4500), + RT1719_PSEL_CAPINFO(0x06, 18000, 0x04, 4000), + RT1719_PSEL_CAPINFO(0x06, 12000, 0x04, 3500), + RT1719_PSEL_CAPINFO(0x06, 9000, 0x04, 3000), + RT1719_PSEL_CAPINFO(0x1F, 25000, 0x04, 2500), + RT1719_PSEL_CAPINFO(0x1F, 20000, 0x04, 2000), + RT1719_PSEL_CAPINFO(0x1F, 15000, 0x04, 1500), + RT1719_PSEL_CAPINFO(0x1F, 10000, 0x04, 1000), + RT1719_PSEL_CAPINFO(0x1F, 7500, 0x07, 5000), + RT1719_PSEL_CAPINFO(0x0F, 25000, 0x07, 4500), + RT1719_PSEL_CAPINFO(0x0F, 20000, 0x07, 4000), + RT1719_PSEL_CAPINFO(0x0F, 15000, 0x07, 3500), + RT1719_PSEL_CAPINFO(0x0F, 10000, 0x07, 3000), + RT1719_PSEL_CAPINFO(0x0F, 7500, 0x07, 2500), + RT1719_PSEL_CAPINFO(0x07, 25000, 0x07, 2000), + RT1719_PSEL_CAPINFO(0x07, 20000, 0x07, 1500), + RT1719_PSEL_CAPINFO(0x07, 15000, 0x07, 1000), + RT1719_PSEL_CAPINFO(0x07, 10000, 0x07, 500), + RT1719_PSEL_CAPINFO(0x07, 7500, 0x03, 5000), + RT1719_PSEL_CAPINFO(0x03, 25000, 0x03, 4500), + RT1719_PSEL_CAPINFO(0x03, 20000, 0x03, 4000), + RT1719_PSEL_CAPINFO(0x03, 15000, 0x03, 3500), + RT1719_PSEL_CAPINFO(0x03, 10000, 0x03, 3000), + RT1719_PSEL_CAPINFO(0x03, 7500, 0x03, 2500), + RT1719_PSEL_CAPINFO(0x01, 15000, 0x03, 2000), + RT1719_PSEL_CAPINFO(0x01, 10000, 0x03, 1500), + RT1719_PSEL_CAPINFO(0x01, 7500, 0x03, 1000), + RT1719_PSEL_CAPINFO(0x01, 2500, 0x03, 500) +}; + +static u16 rt1719_gen_snkcap_by_current(const struct rt1719_psel_cap *psel_cap, + enum rt1719_snkcap capsel) +{ + u16 cap = RT1719_PSEL_SUPPORT; + + if (!(psel_cap->himask & BIT(capsel))) + return 0; + + cap |= psel_cap->milliamp / 10; + return cap; +} + +static u16 rt1719_gen_snkcap_by_watt(const struct rt1719_psel_cap *psel_cap, + enum rt1719_snkcap capsel) +{ + u32 volt_div[RT1719_MAX_SNKCAP] = { 5, 9, 12, 15, 20 }; + u16 cap = RT1719_PSEL_SUPPORT; + + if (!(psel_cap->lomask & BIT(capsel))) + return 0; + + cap |= min(psel_cap->milliwatt / volt_div[capsel], (u32)5000) / 10; + return cap; +} + +static u16 rt1719_gen_snkcap(unsigned int pselinfo, enum rt1719_snkcap capsel) +{ + int psel = FIELD_GET(RT1719_LATPSEL_MASK, pselinfo); + const struct rt1719_psel_cap *psel_cap; + bool by_current = false; + + if (pselinfo & RT1719_TBLSEL_MASK) + by_current = true; + + psel_cap = rt1719_psel_caps + psel; + if (by_current) + return rt1719_gen_snkcap_by_current(psel_cap, capsel); + + return rt1719_gen_snkcap_by_watt(psel_cap, capsel); +} + +static int rt1719_get_caps(struct rt1719_data *data) +{ + unsigned int pselinfo, usbinfo; + int i, ret; + + ret = regmap_read(data->regmap, RT1719_REG_PSELINFO, &pselinfo); + ret |= regmap_read(data->regmap, RT1719_REG_USBSETINFO, &usbinfo); + if (ret) + return ret; + + for (i = 0; i < RT1719_MAX_SNKCAP; i++) + data->snkcaps[i] = rt1719_gen_snkcap(pselinfo, i); + + usbinfo = FIELD_GET(RT1719_USBINFO_MASK, usbinfo); + if (usbinfo == RT1719_USB_DFPUFP) + data->drswap_support = true; + + return 0; +} + +static int rt1719_check_exist(struct rt1719_data *data) +{ + u16 pid; + int ret; + + ret = rt1719_read16(data, RT1719_REG_VENID, &pid); + if (ret) + return ret; + + if (pid != RT1719_UNIQUE_PID) { + dev_err(data->dev, "Incorrect PID 0x%04x\n", pid); + return -ENODEV; + } + + return 0; +} + +static const struct regmap_config rt1719_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, +}; + +static int rt1719_probe(struct i2c_client *i2c) +{ + struct rt1719_data *data; + struct fwnode_handle *fwnode; + struct typec_capability typec_cap = { }; + int ret; + + data = devm_kzalloc(&i2c->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = &i2c->dev; + init_completion(&data->req_completion); + + data->regmap = devm_regmap_init_i2c(i2c, &rt1719_regmap_config); + if (IS_ERR(data->regmap)) { + ret = PTR_ERR(data->regmap); + dev_err(&i2c->dev, "Failed to init regmap (%d)\n", ret); + return ret; + } + + ret = rt1719_check_exist(data); + if (ret) + return ret; + + ret = rt1719_get_caps(data); + if (ret) + return ret; + + fwnode = device_get_named_child_node(&i2c->dev, "connector"); + if (!fwnode) + return -ENODEV; + + data->role_sw = fwnode_usb_role_switch_get(fwnode); + if (IS_ERR(data->role_sw)) { + ret = PTR_ERR(data->role_sw); + dev_err(&i2c->dev, "Failed to get usb role switch (%d)\n", ret); + goto err_fwnode_put; + } + + ret = devm_rt1719_psy_register(data); + if (ret) { + dev_err(&i2c->dev, "Failed to register psy (%d)\n", ret); + goto err_role_put; + } + + typec_cap.revision = USB_TYPEC_REV_1_2; + typec_cap.pd_revision = 0x300; /* USB-PD spec release 3.0 */ + typec_cap.type = TYPEC_PORT_SNK; + typec_cap.data = TYPEC_PORT_DRD; + typec_cap.ops = &rt1719_port_ops; + typec_cap.fwnode = fwnode; + typec_cap.driver_data = data; + typec_cap.accessory[0] = TYPEC_ACCESSORY_DEBUG; + + data->partner_desc.identity = &data->partner_ident; + + data->port = typec_register_port(&i2c->dev, &typec_cap); + if (IS_ERR(data->port)) { + ret = PTR_ERR(data->port); + dev_err(&i2c->dev, "Failed to register typec port (%d)\n", ret); + goto err_role_put; + } + + ret = rt1719_init_attach_state(data); + if (ret) { + dev_err(&i2c->dev, "Failed to init attach state (%d)\n", ret); + goto err_role_put; + } + + ret = rt1719_irq_init(data); + if (ret) { + dev_err(&i2c->dev, "Failed to init irq\n"); + goto err_role_put; + } + + fwnode_handle_put(fwnode); + + i2c_set_clientdata(i2c, data); + + return 0; + +err_role_put: + usb_role_switch_put(data->role_sw); +err_fwnode_put: + fwnode_handle_put(fwnode); + + return ret; +} + +static void rt1719_remove(struct i2c_client *i2c) +{ + struct rt1719_data *data = i2c_get_clientdata(i2c); + + typec_unregister_port(data->port); + usb_role_switch_put(data->role_sw); +} + +static const struct of_device_id __maybe_unused rt1719_device_table[] = { + { .compatible = "richtek,rt1719", }, + { } +}; +MODULE_DEVICE_TABLE(of, rt1719_device_table); + +static struct i2c_driver rt1719_driver = { + .driver = { + .name = "rt1719", + .of_match_table = rt1719_device_table, + }, + .probe = rt1719_probe, + .remove = rt1719_remove, +}; +module_i2c_driver(rt1719_driver); + +MODULE_AUTHOR("ChiYuan Huang "); +MODULE_DESCRIPTION("Richtek RT1719 Sink Only USBPD Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/stusb160x.c b/drivers/usb/typec/stusb160x.c new file mode 100644 index 000000000..6d85b25b4 --- /dev/null +++ b/drivers/usb/typec/stusb160x.c @@ -0,0 +1,879 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * STMicroelectronics STUSB160x Type-C controller family driver + * + * Copyright (C) 2020, STMicroelectronics + * Author(s): Amelie Delaunay + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STUSB160X_ALERT_STATUS 0x0B /* RC */ +#define STUSB160X_ALERT_STATUS_MASK_CTRL 0x0C /* RW */ +#define STUSB160X_CC_CONNECTION_STATUS_TRANS 0x0D /* RC */ +#define STUSB160X_CC_CONNECTION_STATUS 0x0E /* RO */ +#define STUSB160X_MONITORING_STATUS_TRANS 0x0F /* RC */ +#define STUSB160X_MONITORING_STATUS 0x10 /* RO */ +#define STUSB160X_CC_OPERATION_STATUS 0x11 /* RO */ +#define STUSB160X_HW_FAULT_STATUS_TRANS 0x12 /* RC */ +#define STUSB160X_HW_FAULT_STATUS 0x13 /* RO */ +#define STUSB160X_CC_CAPABILITY_CTRL 0x18 /* RW */ +#define STUSB160X_CC_VCONN_SWITCH_CTRL 0x1E /* RW */ +#define STUSB160X_VCONN_MONITORING_CTRL 0x20 /* RW */ +#define STUSB160X_VBUS_MONITORING_RANGE_CTRL 0x22 /* RW */ +#define STUSB160X_RESET_CTRL 0x23 /* RW */ +#define STUSB160X_VBUS_DISCHARGE_TIME_CTRL 0x25 /* RW */ +#define STUSB160X_VBUS_DISCHARGE_STATUS 0x26 /* RO */ +#define STUSB160X_VBUS_ENABLE_STATUS 0x27 /* RO */ +#define STUSB160X_CC_POWER_MODE_CTRL 0x28 /* RW */ +#define STUSB160X_VBUS_MONITORING_CTRL 0x2E /* RW */ +#define STUSB1600_REG_MAX 0x2F /* RO - Reserved */ + +/* STUSB160X_ALERT_STATUS/STUSB160X_ALERT_STATUS_MASK_CTRL bitfields */ +#define STUSB160X_HW_FAULT BIT(4) +#define STUSB160X_MONITORING BIT(5) +#define STUSB160X_CC_CONNECTION BIT(6) +#define STUSB160X_ALL_ALERTS GENMASK(6, 4) + +/* STUSB160X_CC_CONNECTION_STATUS_TRANS bitfields */ +#define STUSB160X_CC_ATTACH_TRANS BIT(0) + +/* STUSB160X_CC_CONNECTION_STATUS bitfields */ +#define STUSB160X_CC_ATTACH BIT(0) +#define STUSB160X_CC_VCONN_SUPPLY BIT(1) +#define STUSB160X_CC_DATA_ROLE(s) (!!((s) & BIT(2))) +#define STUSB160X_CC_POWER_ROLE(s) (!!((s) & BIT(3))) +#define STUSB160X_CC_ATTACHED_MODE GENMASK(7, 5) + +/* STUSB160X_MONITORING_STATUS_TRANS bitfields */ +#define STUSB160X_VCONN_PRESENCE_TRANS BIT(0) +#define STUSB160X_VBUS_PRESENCE_TRANS BIT(1) +#define STUSB160X_VBUS_VSAFE0V_TRANS BIT(2) +#define STUSB160X_VBUS_VALID_TRANS BIT(3) + +/* STUSB160X_MONITORING_STATUS bitfields */ +#define STUSB160X_VCONN_PRESENCE BIT(0) +#define STUSB160X_VBUS_PRESENCE BIT(1) +#define STUSB160X_VBUS_VSAFE0V BIT(2) +#define STUSB160X_VBUS_VALID BIT(3) + +/* STUSB160X_CC_OPERATION_STATUS bitfields */ +#define STUSB160X_TYPEC_FSM_STATE GENMASK(4, 0) +#define STUSB160X_SINK_POWER_STATE GENMASK(6, 5) +#define STUSB160X_CC_ATTACHED BIT(7) + +/* STUSB160X_HW_FAULT_STATUS_TRANS bitfields */ +#define STUSB160X_VCONN_SW_OVP_FAULT_TRANS BIT(0) +#define STUSB160X_VCONN_SW_OCP_FAULT_TRANS BIT(1) +#define STUSB160X_VCONN_SW_RVP_FAULT_TRANS BIT(2) +#define STUSB160X_VPU_VALID_TRANS BIT(4) +#define STUSB160X_VPU_OVP_FAULT_TRANS BIT(5) +#define STUSB160X_THERMAL_FAULT BIT(7) + +/* STUSB160X_HW_FAULT_STATUS bitfields */ +#define STUSB160X_VCONN_SW_OVP_FAULT_CC2 BIT(0) +#define STUSB160X_VCONN_SW_OVP_FAULT_CC1 BIT(1) +#define STUSB160X_VCONN_SW_OCP_FAULT_CC2 BIT(2) +#define STUSB160X_VCONN_SW_OCP_FAULT_CC1 BIT(3) +#define STUSB160X_VCONN_SW_RVP_FAULT_CC2 BIT(4) +#define STUSB160X_VCONN_SW_RVP_FAULT_CC1 BIT(5) +#define STUSB160X_VPU_VALID BIT(6) +#define STUSB160X_VPU_OVP_FAULT BIT(7) + +/* STUSB160X_CC_CAPABILITY_CTRL bitfields */ +#define STUSB160X_CC_VCONN_SUPPLY_EN BIT(0) +#define STUSB160X_CC_VCONN_DISCHARGE_EN BIT(4) +#define STUSB160X_CC_CURRENT_ADVERTISED GENMASK(7, 6) + +/* STUSB160X_VCONN_SWITCH_CTRL bitfields */ +#define STUSB160X_CC_VCONN_SWITCH_ILIM GENMASK(3, 0) + +/* STUSB160X_VCONN_MONITORING_CTRL bitfields */ +#define STUSB160X_VCONN_UVLO_THRESHOLD BIT(6) +#define STUSB160X_VCONN_MONITORING_EN BIT(7) + +/* STUSB160X_VBUS_MONITORING_RANGE_CTRL bitfields */ +#define STUSB160X_SHIFT_LOW_VBUS_LIMIT GENMASK(3, 0) +#define STUSB160X_SHIFT_HIGH_VBUS_LIMIT GENMASK(7, 4) + +/* STUSB160X_RESET_CTRL bitfields */ +#define STUSB160X_SW_RESET_EN BIT(0) + +/* STUSB160X_VBUS_DISCHARGE_TIME_CTRL bitfields */ +#define STUSBXX02_VBUS_DISCHARGE_TIME_TO_PDO GENMASK(3, 0) +#define STUSB160X_VBUS_DISCHARGE_TIME_TO_0V GENMASK(7, 4) + +/* STUSB160X_VBUS_DISCHARGE_STATUS bitfields */ +#define STUSB160X_VBUS_DISCHARGE_EN BIT(7) + +/* STUSB160X_VBUS_ENABLE_STATUS bitfields */ +#define STUSB160X_VBUS_SOURCE_EN BIT(0) +#define STUSB160X_VBUS_SINK_EN BIT(1) + +/* STUSB160X_CC_POWER_MODE_CTRL bitfields */ +#define STUSB160X_CC_POWER_MODE GENMASK(2, 0) + +/* STUSB160X_VBUS_MONITORING_CTRL bitfields */ +#define STUSB160X_VDD_UVLO_DISABLE BIT(0) +#define STUSB160X_VBUS_VSAFE0V_THRESHOLD GENMASK(2, 1) +#define STUSB160X_VBUS_RANGE_DISABLE BIT(4) +#define STUSB160X_VDD_OVLO_DISABLE BIT(6) + +enum stusb160x_pwr_mode { + SOURCE_WITH_ACCESSORY, + SINK_WITH_ACCESSORY, + SINK_WITHOUT_ACCESSORY, + DUAL_WITH_ACCESSORY, + DUAL_WITH_ACCESSORY_AND_TRY_SRC, + DUAL_WITH_ACCESSORY_AND_TRY_SNK, +}; + +enum stusb160x_attached_mode { + NO_DEVICE_ATTACHED, + SINK_ATTACHED, + SOURCE_ATTACHED, + DEBUG_ACCESSORY_ATTACHED, + AUDIO_ACCESSORY_ATTACHED, +}; + +struct stusb160x { + struct device *dev; + struct regmap *regmap; + struct regulator *vdd_supply; + struct regulator *vsys_supply; + struct regulator *vconn_supply; + struct regulator *main_supply; + + struct typec_port *port; + struct typec_capability capability; + struct typec_partner *partner; + + enum typec_port_type port_type; + enum typec_pwr_opmode pwr_opmode; + bool vbus_on; + + struct usb_role_switch *role_sw; +}; + +static bool stusb160x_reg_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STUSB160X_ALERT_STATUS_MASK_CTRL: + case STUSB160X_CC_CAPABILITY_CTRL: + case STUSB160X_CC_VCONN_SWITCH_CTRL: + case STUSB160X_VCONN_MONITORING_CTRL: + case STUSB160X_VBUS_MONITORING_RANGE_CTRL: + case STUSB160X_RESET_CTRL: + case STUSB160X_VBUS_DISCHARGE_TIME_CTRL: + case STUSB160X_CC_POWER_MODE_CTRL: + case STUSB160X_VBUS_MONITORING_CTRL: + return true; + default: + return false; + } +} + +static bool stusb160x_reg_readable(struct device *dev, unsigned int reg) +{ + if (reg <= 0x0A || + (reg >= 0x14 && reg <= 0x17) || + (reg >= 0x19 && reg <= 0x1D) || + (reg >= 0x29 && reg <= 0x2D) || + (reg == 0x1F || reg == 0x21 || reg == 0x24 || reg == 0x2F)) + return false; + else + return true; +} + +static bool stusb160x_reg_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STUSB160X_ALERT_STATUS: + case STUSB160X_CC_CONNECTION_STATUS_TRANS: + case STUSB160X_CC_CONNECTION_STATUS: + case STUSB160X_MONITORING_STATUS_TRANS: + case STUSB160X_MONITORING_STATUS: + case STUSB160X_CC_OPERATION_STATUS: + case STUSB160X_HW_FAULT_STATUS_TRANS: + case STUSB160X_HW_FAULT_STATUS: + case STUSB160X_VBUS_DISCHARGE_STATUS: + case STUSB160X_VBUS_ENABLE_STATUS: + return true; + default: + return false; + } +} + +static bool stusb160x_reg_precious(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STUSB160X_ALERT_STATUS: + case STUSB160X_CC_CONNECTION_STATUS_TRANS: + case STUSB160X_MONITORING_STATUS_TRANS: + case STUSB160X_HW_FAULT_STATUS_TRANS: + return true; + default: + return false; + } +} + +static const struct regmap_config stusb1600_regmap_config = { + .reg_bits = 8, + .reg_stride = 1, + .val_bits = 8, + .max_register = STUSB1600_REG_MAX, + .writeable_reg = stusb160x_reg_writeable, + .readable_reg = stusb160x_reg_readable, + .volatile_reg = stusb160x_reg_volatile, + .precious_reg = stusb160x_reg_precious, + .cache_type = REGCACHE_MAPLE, +}; + +static bool stusb160x_get_vconn(struct stusb160x *chip) +{ + u32 val; + int ret; + + ret = regmap_read(chip->regmap, STUSB160X_CC_CAPABILITY_CTRL, &val); + if (ret) { + dev_err(chip->dev, "Unable to get Vconn status: %d\n", ret); + return false; + } + + return !!FIELD_GET(STUSB160X_CC_VCONN_SUPPLY_EN, val); +} + +static int stusb160x_set_vconn(struct stusb160x *chip, bool on) +{ + int ret; + + /* Manage VCONN input supply */ + if (chip->vconn_supply) { + if (on) { + ret = regulator_enable(chip->vconn_supply); + if (ret) { + dev_err(chip->dev, + "failed to enable vconn supply: %d\n", + ret); + return ret; + } + } else { + regulator_disable(chip->vconn_supply); + } + } + + /* Manage VCONN monitoring and power path */ + ret = regmap_update_bits(chip->regmap, STUSB160X_VCONN_MONITORING_CTRL, + STUSB160X_VCONN_MONITORING_EN, + on ? STUSB160X_VCONN_MONITORING_EN : 0); + if (ret) + goto vconn_reg_disable; + + return 0; + +vconn_reg_disable: + if (chip->vconn_supply && on) + regulator_disable(chip->vconn_supply); + + return ret; +} + +static enum typec_pwr_opmode stusb160x_get_pwr_opmode(struct stusb160x *chip) +{ + u32 val; + int ret; + + ret = regmap_read(chip->regmap, STUSB160X_CC_CAPABILITY_CTRL, &val); + if (ret) { + dev_err(chip->dev, "Unable to get pwr opmode: %d\n", ret); + return TYPEC_PWR_MODE_USB; + } + + return FIELD_GET(STUSB160X_CC_CURRENT_ADVERTISED, val); +} + +static enum typec_accessory stusb160x_get_accessory(u32 status) +{ + enum stusb160x_attached_mode mode; + + mode = FIELD_GET(STUSB160X_CC_ATTACHED_MODE, status); + + switch (mode) { + case DEBUG_ACCESSORY_ATTACHED: + return TYPEC_ACCESSORY_DEBUG; + case AUDIO_ACCESSORY_ATTACHED: + return TYPEC_ACCESSORY_AUDIO; + default: + return TYPEC_ACCESSORY_NONE; + } +} + +static enum typec_role stusb160x_get_vconn_role(u32 status) +{ + if (FIELD_GET(STUSB160X_CC_VCONN_SUPPLY, status)) + return TYPEC_SOURCE; + + return TYPEC_SINK; +} + +static void stusb160x_set_data_role(struct stusb160x *chip, + enum typec_data_role data_role, + bool attached) +{ + enum usb_role usb_role = USB_ROLE_NONE; + + if (attached) { + if (data_role == TYPEC_HOST) + usb_role = USB_ROLE_HOST; + else + usb_role = USB_ROLE_DEVICE; + } + + usb_role_switch_set_role(chip->role_sw, usb_role); + typec_set_data_role(chip->port, data_role); +} + +static int stusb160x_attach(struct stusb160x *chip, u32 status) +{ + struct typec_partner_desc desc; + int ret; + + if ((STUSB160X_CC_POWER_ROLE(status) == TYPEC_SOURCE) && + chip->vdd_supply) { + ret = regulator_enable(chip->vdd_supply); + if (ret) { + dev_err(chip->dev, + "Failed to enable Vbus supply: %d\n", ret); + return ret; + } + chip->vbus_on = true; + } + + desc.usb_pd = false; + desc.accessory = stusb160x_get_accessory(status); + desc.identity = NULL; + + chip->partner = typec_register_partner(chip->port, &desc); + if (IS_ERR(chip->partner)) { + ret = PTR_ERR(chip->partner); + goto vbus_disable; + } + + typec_set_pwr_role(chip->port, STUSB160X_CC_POWER_ROLE(status)); + typec_set_pwr_opmode(chip->port, stusb160x_get_pwr_opmode(chip)); + typec_set_vconn_role(chip->port, stusb160x_get_vconn_role(status)); + stusb160x_set_data_role(chip, STUSB160X_CC_DATA_ROLE(status), true); + + return 0; + +vbus_disable: + if (chip->vbus_on) { + regulator_disable(chip->vdd_supply); + chip->vbus_on = false; + } + + return ret; +} + +static void stusb160x_detach(struct stusb160x *chip, u32 status) +{ + typec_unregister_partner(chip->partner); + chip->partner = NULL; + + typec_set_pwr_role(chip->port, STUSB160X_CC_POWER_ROLE(status)); + typec_set_pwr_opmode(chip->port, TYPEC_PWR_MODE_USB); + typec_set_vconn_role(chip->port, stusb160x_get_vconn_role(status)); + stusb160x_set_data_role(chip, STUSB160X_CC_DATA_ROLE(status), false); + + if (chip->vbus_on) { + regulator_disable(chip->vdd_supply); + chip->vbus_on = false; + } +} + +static irqreturn_t stusb160x_irq_handler(int irq, void *data) +{ + struct stusb160x *chip = data; + u32 pending, trans, status; + int ret; + + ret = regmap_read(chip->regmap, STUSB160X_ALERT_STATUS, &pending); + if (ret) + goto err; + + if (pending & STUSB160X_CC_CONNECTION) { + ret = regmap_read(chip->regmap, + STUSB160X_CC_CONNECTION_STATUS_TRANS, &trans); + if (ret) + goto err; + ret = regmap_read(chip->regmap, + STUSB160X_CC_CONNECTION_STATUS, &status); + if (ret) + goto err; + + if (trans & STUSB160X_CC_ATTACH_TRANS) { + if (status & STUSB160X_CC_ATTACH) { + ret = stusb160x_attach(chip, status); + if (ret) + goto err; + } else { + stusb160x_detach(chip, status); + } + } + } +err: + return IRQ_HANDLED; +} + +static int stusb160x_irq_init(struct stusb160x *chip, int irq) +{ + u32 status; + int ret; + + ret = regmap_read(chip->regmap, + STUSB160X_CC_CONNECTION_STATUS, &status); + if (ret) + return ret; + + if (status & STUSB160X_CC_ATTACH) { + ret = stusb160x_attach(chip, status); + if (ret) + dev_err(chip->dev, "attach failed: %d\n", ret); + } + + ret = devm_request_threaded_irq(chip->dev, irq, NULL, + stusb160x_irq_handler, IRQF_ONESHOT, + dev_name(chip->dev), chip); + if (ret) + goto partner_unregister; + + /* Unmask CC_CONNECTION events */ + ret = regmap_write_bits(chip->regmap, STUSB160X_ALERT_STATUS_MASK_CTRL, + STUSB160X_CC_CONNECTION, 0); + if (ret) + goto partner_unregister; + + return 0; + +partner_unregister: + if (chip->partner) { + typec_unregister_partner(chip->partner); + chip->partner = NULL; + } + + return ret; +} + +static int stusb160x_chip_init(struct stusb160x *chip) +{ + u32 val; + int ret; + + /* Change the default Type-C power mode */ + if (chip->port_type == TYPEC_PORT_SRC) + ret = regmap_update_bits(chip->regmap, + STUSB160X_CC_POWER_MODE_CTRL, + STUSB160X_CC_POWER_MODE, + SOURCE_WITH_ACCESSORY); + else if (chip->port_type == TYPEC_PORT_SNK) + ret = regmap_update_bits(chip->regmap, + STUSB160X_CC_POWER_MODE_CTRL, + STUSB160X_CC_POWER_MODE, + SINK_WITH_ACCESSORY); + else /* (chip->port_type == TYPEC_PORT_DRP) */ + ret = regmap_update_bits(chip->regmap, + STUSB160X_CC_POWER_MODE_CTRL, + STUSB160X_CC_POWER_MODE, + DUAL_WITH_ACCESSORY); + if (ret) + return ret; + + if (chip->port_type == TYPEC_PORT_SNK) + goto skip_src; + + /* Change the default Type-C Source power operation mode capability */ + ret = regmap_update_bits(chip->regmap, STUSB160X_CC_CAPABILITY_CTRL, + STUSB160X_CC_CURRENT_ADVERTISED, + FIELD_PREP(STUSB160X_CC_CURRENT_ADVERTISED, + chip->pwr_opmode)); + if (ret) + return ret; + + /* Manage Type-C Source Vconn supply */ + if (stusb160x_get_vconn(chip)) { + ret = stusb160x_set_vconn(chip, true); + if (ret) + return ret; + } + +skip_src: + /* Mask all events interrupts - to be unmasked with interrupt support */ + ret = regmap_update_bits(chip->regmap, STUSB160X_ALERT_STATUS_MASK_CTRL, + STUSB160X_ALL_ALERTS, STUSB160X_ALL_ALERTS); + if (ret) + return ret; + + /* Read status at least once to clear any stale interrupts */ + regmap_read(chip->regmap, STUSB160X_ALERT_STATUS, &val); + regmap_read(chip->regmap, STUSB160X_CC_CONNECTION_STATUS_TRANS, &val); + regmap_read(chip->regmap, STUSB160X_MONITORING_STATUS_TRANS, &val); + regmap_read(chip->regmap, STUSB160X_HW_FAULT_STATUS_TRANS, &val); + + return 0; +} + +static int stusb160x_get_fw_caps(struct stusb160x *chip, + struct fwnode_handle *fwnode) +{ + const char *cap_str; + int ret; + + chip->capability.fwnode = fwnode; + + /* + * Supported port type can be configured through device tree + * else it is read from chip registers in stusb160x_get_caps. + */ + ret = fwnode_property_read_string(fwnode, "power-role", &cap_str); + if (!ret) { + ret = typec_find_port_power_role(cap_str); + if (ret < 0) + return ret; + chip->port_type = ret; + } + chip->capability.type = chip->port_type; + + /* Skip DRP/Source capabilities in case of Sink only */ + if (chip->port_type == TYPEC_PORT_SNK) + return 0; + + if (chip->port_type == TYPEC_PORT_DRP) + chip->capability.prefer_role = TYPEC_SINK; + + /* + * Supported power operation mode can be configured through device tree + * else it is read from chip registers in stusb160x_get_caps. + */ + ret = fwnode_property_read_string(fwnode, "typec-power-opmode", &cap_str); + if (!ret) { + ret = typec_find_pwr_opmode(cap_str); + /* Power delivery not yet supported */ + if (ret < 0 || ret == TYPEC_PWR_MODE_PD) { + dev_err(chip->dev, "bad power operation mode: %d\n", ret); + return -EINVAL; + } + chip->pwr_opmode = ret; + } + + return 0; +} + +static int stusb160x_get_caps(struct stusb160x *chip) +{ + enum typec_port_type *type = &chip->capability.type; + enum typec_port_data *data = &chip->capability.data; + enum typec_accessory *accessory = chip->capability.accessory; + u32 val; + int ret; + + chip->capability.revision = USB_TYPEC_REV_1_2; + + ret = regmap_read(chip->regmap, STUSB160X_CC_POWER_MODE_CTRL, &val); + if (ret) + return ret; + + switch (FIELD_GET(STUSB160X_CC_POWER_MODE, val)) { + case SOURCE_WITH_ACCESSORY: + *type = TYPEC_PORT_SRC; + *data = TYPEC_PORT_DFP; + *accessory++ = TYPEC_ACCESSORY_AUDIO; + *accessory++ = TYPEC_ACCESSORY_DEBUG; + break; + case SINK_WITH_ACCESSORY: + *type = TYPEC_PORT_SNK; + *data = TYPEC_PORT_UFP; + *accessory++ = TYPEC_ACCESSORY_AUDIO; + *accessory++ = TYPEC_ACCESSORY_DEBUG; + break; + case SINK_WITHOUT_ACCESSORY: + *type = TYPEC_PORT_SNK; + *data = TYPEC_PORT_UFP; + break; + case DUAL_WITH_ACCESSORY: + case DUAL_WITH_ACCESSORY_AND_TRY_SRC: + case DUAL_WITH_ACCESSORY_AND_TRY_SNK: + *type = TYPEC_PORT_DRP; + *data = TYPEC_PORT_DRD; + *accessory++ = TYPEC_ACCESSORY_AUDIO; + *accessory++ = TYPEC_ACCESSORY_DEBUG; + break; + default: + return -EINVAL; + } + + chip->port_type = *type; + chip->pwr_opmode = stusb160x_get_pwr_opmode(chip); + + return 0; +} + +static const struct of_device_id stusb160x_of_match[] = { + { .compatible = "st,stusb1600", .data = &stusb1600_regmap_config}, + {}, +}; +MODULE_DEVICE_TABLE(of, stusb160x_of_match); + +static int stusb160x_probe(struct i2c_client *client) +{ + const struct regmap_config *regmap_config; + struct stusb160x *chip; + struct fwnode_handle *fwnode; + int ret; + + chip = devm_kzalloc(&client->dev, sizeof(struct stusb160x), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + i2c_set_clientdata(client, chip); + + regmap_config = i2c_get_match_data(client); + + chip->regmap = devm_regmap_init_i2c(client, regmap_config); + if (IS_ERR(chip->regmap)) { + ret = PTR_ERR(chip->regmap); + dev_err(&client->dev, + "Failed to allocate register map:%d\n", ret); + return ret; + } + + chip->dev = &client->dev; + + chip->vsys_supply = devm_regulator_get_optional(chip->dev, "vsys"); + if (IS_ERR(chip->vsys_supply)) { + ret = PTR_ERR(chip->vsys_supply); + if (ret != -ENODEV) + return ret; + chip->vsys_supply = NULL; + } + + chip->vdd_supply = devm_regulator_get_optional(chip->dev, "vdd"); + if (IS_ERR(chip->vdd_supply)) { + ret = PTR_ERR(chip->vdd_supply); + if (ret != -ENODEV) + return ret; + chip->vdd_supply = NULL; + } + + chip->vconn_supply = devm_regulator_get_optional(chip->dev, "vconn"); + if (IS_ERR(chip->vconn_supply)) { + ret = PTR_ERR(chip->vconn_supply); + if (ret != -ENODEV) + return ret; + chip->vconn_supply = NULL; + } + + fwnode = device_get_named_child_node(chip->dev, "connector"); + if (!fwnode) + return -ENODEV; + + /* + * This fwnode has a "compatible" property, but is never populated as a + * struct device. Instead we simply parse it to read the properties. + * This it breaks fw_devlink=on. To maintain backward compatibility + * with existing DT files, we work around this by deleting any + * fwnode_links to/from this fwnode. + */ + fw_devlink_purge_absent_suppliers(fwnode); + + /* + * When both VDD and VSYS power supplies are present, the low power + * supply VSYS is selected when VSYS voltage is above 3.1 V. + * Otherwise VDD is selected. + */ + if (chip->vdd_supply && + (!chip->vsys_supply || + (regulator_get_voltage(chip->vsys_supply) <= 3100000))) + chip->main_supply = chip->vdd_supply; + else + chip->main_supply = chip->vsys_supply; + + if (chip->main_supply) { + ret = regulator_enable(chip->main_supply); + if (ret) { + dev_err(chip->dev, + "Failed to enable main supply: %d\n", ret); + goto fwnode_put; + } + } + + /* Get configuration from chip */ + ret = stusb160x_get_caps(chip); + if (ret) { + dev_err(chip->dev, "Failed to get port caps: %d\n", ret); + goto main_reg_disable; + } + + /* Get optional re-configuration from device tree */ + ret = stusb160x_get_fw_caps(chip, fwnode); + if (ret) { + dev_err(chip->dev, "Failed to get connector caps: %d\n", ret); + goto main_reg_disable; + } + + ret = stusb160x_chip_init(chip); + if (ret) { + dev_err(chip->dev, "Failed to init port: %d\n", ret); + goto main_reg_disable; + } + + chip->port = typec_register_port(chip->dev, &chip->capability); + if (IS_ERR(chip->port)) { + ret = PTR_ERR(chip->port); + goto all_reg_disable; + } + + /* + * Default power operation mode initialization: will be updated upon + * attach/detach interrupt + */ + typec_set_pwr_opmode(chip->port, chip->pwr_opmode); + + if (client->irq) { + chip->role_sw = fwnode_usb_role_switch_get(fwnode); + if (IS_ERR(chip->role_sw)) { + ret = dev_err_probe(chip->dev, PTR_ERR(chip->role_sw), + "Failed to get usb role switch\n"); + goto port_unregister; + } + + ret = stusb160x_irq_init(chip, client->irq); + if (ret) + goto role_sw_put; + } else { + /* + * If Source or Dual power role, need to enable VDD supply + * providing Vbus if present. In case of interrupt support, + * VDD supply will be dynamically managed upon attach/detach + * interrupt. + */ + if (chip->port_type != TYPEC_PORT_SNK && chip->vdd_supply) { + ret = regulator_enable(chip->vdd_supply); + if (ret) { + dev_err(chip->dev, + "Failed to enable VDD supply: %d\n", + ret); + goto port_unregister; + } + chip->vbus_on = true; + } + } + + fwnode_handle_put(fwnode); + + return 0; + +role_sw_put: + if (chip->role_sw) + usb_role_switch_put(chip->role_sw); +port_unregister: + typec_unregister_port(chip->port); +all_reg_disable: + if (stusb160x_get_vconn(chip)) + stusb160x_set_vconn(chip, false); +main_reg_disable: + if (chip->main_supply) + regulator_disable(chip->main_supply); +fwnode_put: + fwnode_handle_put(fwnode); + + return ret; +} + +static void stusb160x_remove(struct i2c_client *client) +{ + struct stusb160x *chip = i2c_get_clientdata(client); + + if (chip->partner) { + typec_unregister_partner(chip->partner); + chip->partner = NULL; + } + + if (chip->vbus_on) + regulator_disable(chip->vdd_supply); + + if (chip->role_sw) + usb_role_switch_put(chip->role_sw); + + typec_unregister_port(chip->port); + + if (stusb160x_get_vconn(chip)) + stusb160x_set_vconn(chip, false); + + if (chip->main_supply) + regulator_disable(chip->main_supply); +} + +static int __maybe_unused stusb160x_suspend(struct device *dev) +{ + struct stusb160x *chip = dev_get_drvdata(dev); + + /* Mask interrupts */ + return regmap_update_bits(chip->regmap, + STUSB160X_ALERT_STATUS_MASK_CTRL, + STUSB160X_ALL_ALERTS, STUSB160X_ALL_ALERTS); +} + +static int __maybe_unused stusb160x_resume(struct device *dev) +{ + struct stusb160x *chip = dev_get_drvdata(dev); + u32 status; + int ret; + + ret = regcache_sync(chip->regmap); + if (ret) + return ret; + + /* Check if attach/detach occurred during low power */ + ret = regmap_read(chip->regmap, + STUSB160X_CC_CONNECTION_STATUS, &status); + if (ret) + return ret; + + if (chip->partner && !(status & STUSB160X_CC_ATTACH)) + stusb160x_detach(chip, status); + + if (!chip->partner && (status & STUSB160X_CC_ATTACH)) { + ret = stusb160x_attach(chip, status); + if (ret) + dev_err(chip->dev, "attach failed: %d\n", ret); + } + + /* Unmask interrupts */ + return regmap_write_bits(chip->regmap, STUSB160X_ALERT_STATUS_MASK_CTRL, + STUSB160X_CC_CONNECTION, 0); +} + +static SIMPLE_DEV_PM_OPS(stusb160x_pm_ops, stusb160x_suspend, stusb160x_resume); + +static struct i2c_driver stusb160x_driver = { + .driver = { + .name = "stusb160x", + .pm = &stusb160x_pm_ops, + .of_match_table = stusb160x_of_match, + }, + .probe = stusb160x_probe, + .remove = stusb160x_remove, +}; +module_i2c_driver(stusb160x_driver); + +MODULE_AUTHOR("Amelie Delaunay "); +MODULE_DESCRIPTION("STMicroelectronics STUSB160x Type-C controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/tcpm/Kconfig b/drivers/usb/typec/tcpm/Kconfig new file mode 100644 index 000000000..00baa7503 --- /dev/null +++ b/drivers/usb/typec/tcpm/Kconfig @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: GPL-2.0 + +config TYPEC_TCPM + tristate "USB Type-C Port Controller Manager" + depends on USB + select USB_ROLE_SWITCH + select POWER_SUPPLY + help + The Type-C Port Controller Manager provides a USB PD and USB Type-C + state machine for use with Type-C Port Controllers. + +if TYPEC_TCPM + +config TYPEC_TCPCI + tristate "Type-C Port Controller Interface driver" + depends on I2C + select REGMAP_I2C + help + Type-C Port Controller driver for TCPCI-compliant controller. + +if TYPEC_TCPCI + +config TYPEC_RT1711H + tristate "Richtek RT1711H Type-C chip driver" + help + Richtek RT1711H Type-C chip driver that works with + Type-C Port Controller Manager to provide USB PD and USB + Type-C functionalities. + +config TYPEC_MT6360 + tristate "Mediatek MT6360 Type-C driver" + depends on MFD_MT6360 + help + Mediatek MT6360 is a multi-functional IC that includes + USB Type-C. It works with Type-C Port Controller Manager + to provide USB PD and USB Type-C functionalities. + +config TYPEC_TCPCI_MT6370 + tristate "MediaTek MT6370 Type-C driver" + depends on MFD_MT6370 + help + MediaTek MT6370 is a multi-functional IC that includes + USB Type-C. It works with Type-C Port Controller Manager + to provide USB PD and USB Type-C functionalities. + + This driver can also be built as a module. The module + will be called "tcpci_mt6370". + +config TYPEC_TCPCI_MAXIM + tristate "Maxim TCPCI based Type-C chip driver" + help + MAXIM TCPCI based Type-C/PD chip driver. Works with + with Type-C Port Controller Manager. + +endif # TYPEC_TCPCI + +config TYPEC_FUSB302 + tristate "Fairchild FUSB302 Type-C chip driver" + depends on I2C + depends on EXTCON || !EXTCON + depends on DRM || DRM=n + select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF + help + The Fairchild FUSB302 Type-C chip driver that works with + Type-C Port Controller Manager to provide USB PD and USB + Type-C functionalities. + +config TYPEC_WCOVE + tristate "Intel WhiskeyCove PMIC USB Type-C PHY driver" + depends on ACPI + depends on MFD_INTEL_PMC_BXT + depends on BXT_WC_PMIC_OPREGION + help + This driver adds support for USB Type-C on Intel Broxton platforms + that have Intel Whiskey Cove PMIC. The driver works with USB Type-C + Port Controller Manager to provide USB PD and Type-C functionalities. + + To compile this driver as module, choose M here: the module will be + called typec_wcove.ko + +config TYPEC_QCOM_PMIC + tristate "Qualcomm PMIC USB Type-C Port Controller Manager driver" + depends on ARCH_QCOM || COMPILE_TEST + depends on DRM || DRM=n + select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF + help + A Type-C port and Power Delivery driver which aggregates two + discrete pieces of silicon in the PM8150b PMIC block: the + Type-C port controller and the Power Delivery PHY. + + This driver enables Type-C role switching, orientation, Alternate + mode and Power Delivery support both for VBUS and VCONN. + +endif # TYPEC_TCPM diff --git a/drivers/usb/typec/tcpm/Makefile b/drivers/usb/typec/tcpm/Makefile new file mode 100644 index 000000000..7a8cad0c0 --- /dev/null +++ b/drivers/usb/typec/tcpm/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_TYPEC_TCPM) += tcpm.o +obj-$(CONFIG_TYPEC_FUSB302) += fusb302.o +obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o +typec_wcove-y := wcove.o +obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o +obj-$(CONFIG_TYPEC_RT1711H) += tcpci_rt1711h.o +obj-$(CONFIG_TYPEC_MT6360) += tcpci_mt6360.o +obj-$(CONFIG_TYPEC_TCPCI_MT6370) += tcpci_mt6370.o +obj-$(CONFIG_TYPEC_TCPCI_MAXIM) += tcpci_maxim.o +tcpci_maxim-y += tcpci_maxim_core.o maxim_contaminant.o +obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom/ diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c new file mode 100644 index 000000000..3319f6a2b --- /dev/null +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -0,0 +1,1868 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2016-2017 Google, Inc + * + * Fairchild FUSB302 Type-C Chip Driver + */ + +#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 "fusb302_reg.h" + +/* + * When the device is SNK, BC_LVL interrupt is used to monitor cc pins + * for the current capability offered by the SRC. As FUSB302 chip fires + * the BC_LVL interrupt on PD signalings, cc lvl should be handled after + * a delay to avoid measuring on PD activities. The delay is slightly + * longer than PD_T_PD_DEBPUNCE (10-20ms). + */ +#define T_BC_LVL_DEBOUNCE_DELAY_MS 30 + +enum toggling_mode { + TOGGLING_MODE_OFF, + TOGGLING_MODE_DRP, + TOGGLING_MODE_SNK, + TOGGLING_MODE_SRC, +}; + +enum src_current_status { + SRC_CURRENT_DEFAULT, + SRC_CURRENT_MEDIUM, + SRC_CURRENT_HIGH, +}; + +static const u8 ra_mda_value[] = { + [SRC_CURRENT_DEFAULT] = 4, /* 210mV */ + [SRC_CURRENT_MEDIUM] = 9, /* 420mV */ + [SRC_CURRENT_HIGH] = 18, /* 798mV */ +}; + +static const u8 rd_mda_value[] = { + [SRC_CURRENT_DEFAULT] = 38, /* 1638mV */ + [SRC_CURRENT_MEDIUM] = 38, /* 1638mV */ + [SRC_CURRENT_HIGH] = 61, /* 2604mV */ +}; + +#define LOG_BUFFER_ENTRIES 1024 +#define LOG_BUFFER_ENTRY_SIZE 128 + +struct fusb302_chip { + struct device *dev; + struct i2c_client *i2c_client; + struct tcpm_port *tcpm_port; + struct tcpc_dev tcpc_dev; + + struct regulator *vbus; + + spinlock_t irq_lock; + struct work_struct irq_work; + bool irq_suspended; + bool irq_while_suspended; + struct gpio_desc *gpio_int_n; + int gpio_int_n_irq; + struct extcon_dev *extcon; + + struct workqueue_struct *wq; + struct delayed_work bc_lvl_handler; + + /* lock for sharing chip states */ + struct mutex lock; + + /* chip status */ + enum toggling_mode toggling_mode; + enum src_current_status src_current_status; + bool intr_togdone; + bool intr_bc_lvl; + bool intr_comp_chng; + + /* port status */ + bool vconn_on; + bool vbus_on; + bool charge_on; + bool pd_rx_on; + bool vbus_present; + enum typec_cc_polarity cc_polarity; + enum typec_cc_status cc1; + enum typec_cc_status cc2; + u32 snk_pdo[PDO_MAX_OBJECTS]; + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; + /* lock for log buffer access */ + struct mutex logbuffer_lock; + int logbuffer_head; + int logbuffer_tail; + u8 *logbuffer[LOG_BUFFER_ENTRIES]; +#endif +}; + +/* + * Logging + */ + +#ifdef CONFIG_DEBUG_FS +static bool fusb302_log_full(struct fusb302_chip *chip) +{ + return chip->logbuffer_tail == + (chip->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; +} + +__printf(2, 0) +static void _fusb302_log(struct fusb302_chip *chip, const char *fmt, + va_list args) +{ + char tmpbuffer[LOG_BUFFER_ENTRY_SIZE]; + u64 ts_nsec = local_clock(); + unsigned long rem_nsec; + + if (!chip->logbuffer[chip->logbuffer_head]) { + chip->logbuffer[chip->logbuffer_head] = + kzalloc(LOG_BUFFER_ENTRY_SIZE, GFP_KERNEL); + if (!chip->logbuffer[chip->logbuffer_head]) + return; + } + + vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args); + + mutex_lock(&chip->logbuffer_lock); + + if (fusb302_log_full(chip)) { + chip->logbuffer_head = max(chip->logbuffer_head - 1, 0); + strscpy(tmpbuffer, "overflow", sizeof(tmpbuffer)); + } + + if (chip->logbuffer_head < 0 || + chip->logbuffer_head >= LOG_BUFFER_ENTRIES) { + dev_warn(chip->dev, + "Bad log buffer index %d\n", chip->logbuffer_head); + goto abort; + } + + if (!chip->logbuffer[chip->logbuffer_head]) { + dev_warn(chip->dev, + "Log buffer index %d is NULL\n", chip->logbuffer_head); + goto abort; + } + + rem_nsec = do_div(ts_nsec, 1000000000); + scnprintf(chip->logbuffer[chip->logbuffer_head], + LOG_BUFFER_ENTRY_SIZE, "[%5lu.%06lu] %s", + (unsigned long)ts_nsec, rem_nsec / 1000, + tmpbuffer); + chip->logbuffer_head = (chip->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; + +abort: + mutex_unlock(&chip->logbuffer_lock); +} + +__printf(2, 3) +static void fusb302_log(struct fusb302_chip *chip, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + _fusb302_log(chip, fmt, args); + va_end(args); +} + +static int fusb302_debug_show(struct seq_file *s, void *v) +{ + struct fusb302_chip *chip = s->private; + int tail; + + mutex_lock(&chip->logbuffer_lock); + tail = chip->logbuffer_tail; + while (tail != chip->logbuffer_head) { + seq_printf(s, "%s\n", chip->logbuffer[tail]); + tail = (tail + 1) % LOG_BUFFER_ENTRIES; + } + if (!seq_has_overflowed(s)) + chip->logbuffer_tail = tail; + mutex_unlock(&chip->logbuffer_lock); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(fusb302_debug); + +static void fusb302_debugfs_init(struct fusb302_chip *chip) +{ + char name[NAME_MAX]; + + mutex_init(&chip->logbuffer_lock); + snprintf(name, NAME_MAX, "fusb302-%s", dev_name(chip->dev)); + chip->dentry = debugfs_create_dir(name, usb_debug_root); + debugfs_create_file("log", S_IFREG | 0444, chip->dentry, chip, + &fusb302_debug_fops); +} + +static void fusb302_debugfs_exit(struct fusb302_chip *chip) +{ + debugfs_remove(chip->dentry); +} + +#else + +static void fusb302_log(const struct fusb302_chip *chip, + const char *fmt, ...) { } +static void fusb302_debugfs_init(const struct fusb302_chip *chip) { } +static void fusb302_debugfs_exit(const struct fusb302_chip *chip) { } + +#endif + +static int fusb302_i2c_write(struct fusb302_chip *chip, + u8 address, u8 data) +{ + int ret = 0; + + ret = i2c_smbus_write_byte_data(chip->i2c_client, address, data); + if (ret < 0) + fusb302_log(chip, "cannot write 0x%02x to 0x%02x, ret=%d", + data, address, ret); + + return ret; +} + +static int fusb302_i2c_block_write(struct fusb302_chip *chip, u8 address, + u8 length, const u8 *data) +{ + int ret = 0; + + if (length <= 0) + return ret; + + ret = i2c_smbus_write_i2c_block_data(chip->i2c_client, address, + length, data); + if (ret < 0) + fusb302_log(chip, "cannot block write 0x%02x, len=%d, ret=%d", + address, length, ret); + + return ret; +} + +static int fusb302_i2c_read(struct fusb302_chip *chip, + u8 address, u8 *data) +{ + int ret = 0; + + ret = i2c_smbus_read_byte_data(chip->i2c_client, address); + *data = (u8)ret; + if (ret < 0) + fusb302_log(chip, "cannot read %02x, ret=%d", address, ret); + + return ret; +} + +static int fusb302_i2c_block_read(struct fusb302_chip *chip, u8 address, + u8 length, u8 *data) +{ + int ret = 0; + + if (length <= 0) + return ret; + + ret = i2c_smbus_read_i2c_block_data(chip->i2c_client, address, + length, data); + if (ret < 0) { + fusb302_log(chip, "cannot block read 0x%02x, len=%d, ret=%d", + address, length, ret); + goto done; + } + if (ret != length) { + fusb302_log(chip, "only read %d/%d bytes from 0x%02x", + ret, length, address); + ret = -EIO; + } + +done: + return ret; +} + +static int fusb302_i2c_mask_write(struct fusb302_chip *chip, u8 address, + u8 mask, u8 value) +{ + int ret = 0; + u8 data; + + ret = fusb302_i2c_read(chip, address, &data); + if (ret < 0) + return ret; + data &= ~mask; + data |= value; + ret = fusb302_i2c_write(chip, address, data); + if (ret < 0) + return ret; + + return ret; +} + +static int fusb302_i2c_set_bits(struct fusb302_chip *chip, u8 address, + u8 set_bits) +{ + return fusb302_i2c_mask_write(chip, address, 0x00, set_bits); +} + +static int fusb302_i2c_clear_bits(struct fusb302_chip *chip, u8 address, + u8 clear_bits) +{ + return fusb302_i2c_mask_write(chip, address, clear_bits, 0x00); +} + +static int fusb302_sw_reset(struct fusb302_chip *chip) +{ + int ret = 0; + + ret = fusb302_i2c_write(chip, FUSB_REG_RESET, + FUSB_REG_RESET_SW_RESET); + if (ret < 0) + fusb302_log(chip, "cannot sw reset the chip, ret=%d", ret); + else + fusb302_log(chip, "sw reset"); + + return ret; +} + +static int fusb302_enable_tx_auto_retries(struct fusb302_chip *chip, u8 retry_count) +{ + int ret = 0; + + ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, retry_count | + FUSB_REG_CONTROL3_AUTO_RETRY); + + return ret; +} + +/* + * initialize interrupt on the chip + * - unmasked interrupt: VBUS_OK + */ +static int fusb302_init_interrupt(struct fusb302_chip *chip) +{ + int ret = 0; + + ret = fusb302_i2c_write(chip, FUSB_REG_MASK, + 0xFF & ~FUSB_REG_MASK_VBUSOK); + if (ret < 0) + return ret; + ret = fusb302_i2c_write(chip, FUSB_REG_MASKA, 0xFF); + if (ret < 0) + return ret; + ret = fusb302_i2c_write(chip, FUSB_REG_MASKB, 0xFF); + if (ret < 0) + return ret; + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_INT_MASK); + if (ret < 0) + return ret; + + return ret; +} + +static int fusb302_set_power_mode(struct fusb302_chip *chip, u8 power_mode) +{ + int ret = 0; + + ret = fusb302_i2c_write(chip, FUSB_REG_POWER, power_mode); + + return ret; +} + +static int tcpm_init(struct tcpc_dev *dev) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + u8 data; + + ret = fusb302_sw_reset(chip); + if (ret < 0) + return ret; + ret = fusb302_enable_tx_auto_retries(chip, FUSB_REG_CONTROL3_N_RETRIES_3); + if (ret < 0) + return ret; + ret = fusb302_init_interrupt(chip); + if (ret < 0) + return ret; + ret = fusb302_set_power_mode(chip, FUSB_REG_POWER_PWR_ALL); + if (ret < 0) + return ret; + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &data); + if (ret < 0) + return ret; + chip->vbus_present = !!(data & FUSB_REG_STATUS0_VBUSOK); + ret = fusb302_i2c_read(chip, FUSB_REG_DEVICE_ID, &data); + if (ret < 0) + return ret; + fusb302_log(chip, "fusb302 device ID: 0x%02x", data); + + return ret; +} + +static int tcpm_get_vbus(struct tcpc_dev *dev) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + mutex_lock(&chip->lock); + ret = chip->vbus_present ? 1 : 0; + mutex_unlock(&chip->lock); + + return ret; +} + +static int tcpm_get_current_limit(struct tcpc_dev *dev) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int current_limit = 0; + unsigned long timeout; + + if (!chip->extcon) + return 0; + + /* + * USB2 Charger detection may still be in progress when we get here, + * this can take upto 600ms, wait 800ms max. + */ + timeout = jiffies + msecs_to_jiffies(800); + do { + if (extcon_get_state(chip->extcon, EXTCON_CHG_USB_SDP) == 1) + current_limit = 500; + + if (extcon_get_state(chip->extcon, EXTCON_CHG_USB_CDP) == 1 || + extcon_get_state(chip->extcon, EXTCON_CHG_USB_ACA) == 1) + current_limit = 1500; + + if (extcon_get_state(chip->extcon, EXTCON_CHG_USB_DCP) == 1) + current_limit = 2000; + + msleep(50); + } while (current_limit == 0 && time_before(jiffies, timeout)); + + return current_limit; +} + +static int fusb302_set_src_current(struct fusb302_chip *chip, + enum src_current_status status) +{ + int ret = 0; + + chip->src_current_status = status; + switch (status) { + case SRC_CURRENT_DEFAULT: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_HOST_CUR_MASK, + FUSB_REG_CONTROL0_HOST_CUR_DEF); + break; + case SRC_CURRENT_MEDIUM: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_HOST_CUR_MASK, + FUSB_REG_CONTROL0_HOST_CUR_MED); + break; + case SRC_CURRENT_HIGH: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_HOST_CUR_MASK, + FUSB_REG_CONTROL0_HOST_CUR_HIGH); + break; + default: + break; + } + + return ret; +} + +static int fusb302_set_toggling(struct fusb302_chip *chip, + enum toggling_mode mode) +{ + int ret = 0; + + /* first disable toggling */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_TOGGLE); + if (ret < 0) + return ret; + /* mask interrupts for SRC or SNK */ + ret = fusb302_i2c_set_bits(chip, FUSB_REG_MASK, + FUSB_REG_MASK_BC_LVL | + FUSB_REG_MASK_COMP_CHNG); + if (ret < 0) + return ret; + chip->intr_bc_lvl = false; + chip->intr_comp_chng = false; + /* configure toggling mode: none/snk/src/drp */ + switch (mode) { + case TOGGLING_MODE_OFF: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_NONE); + if (ret < 0) + return ret; + break; + case TOGGLING_MODE_SNK: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_UFP); + if (ret < 0) + return ret; + break; + case TOGGLING_MODE_SRC: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_DFP); + if (ret < 0) + return ret; + break; + case TOGGLING_MODE_DRP: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_DRP); + if (ret < 0) + return ret; + break; + default: + break; + } + + if (mode == TOGGLING_MODE_OFF) { + /* mask TOGDONE interrupt */ + ret = fusb302_i2c_set_bits(chip, FUSB_REG_MASKA, + FUSB_REG_MASKA_TOGDONE); + if (ret < 0) + return ret; + chip->intr_togdone = false; + } else { + /* Datasheet says vconn MUST be off when toggling */ + WARN(chip->vconn_on, "Vconn is on during toggle start"); + /* unmask TOGDONE interrupt */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASKA, + FUSB_REG_MASKA_TOGDONE); + if (ret < 0) + return ret; + chip->intr_togdone = true; + /* start toggling */ + ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_TOGGLE); + if (ret < 0) + return ret; + /* during toggling, consider cc as Open */ + chip->cc1 = TYPEC_CC_OPEN; + chip->cc2 = TYPEC_CC_OPEN; + } + chip->toggling_mode = mode; + + return ret; +} + +static const char * const typec_cc_status_name[] = { + [TYPEC_CC_OPEN] = "Open", + [TYPEC_CC_RA] = "Ra", + [TYPEC_CC_RD] = "Rd", + [TYPEC_CC_RP_DEF] = "Rp-def", + [TYPEC_CC_RP_1_5] = "Rp-1.5", + [TYPEC_CC_RP_3_0] = "Rp-3.0", +}; + +static const enum src_current_status cc_src_current[] = { + [TYPEC_CC_OPEN] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RA] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RD] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RP_DEF] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RP_1_5] = SRC_CURRENT_MEDIUM, + [TYPEC_CC_RP_3_0] = SRC_CURRENT_HIGH, +}; + +static int tcpm_set_cc(struct tcpc_dev *dev, enum typec_cc_status cc) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + u8 switches0_mask = FUSB_REG_SWITCHES0_CC1_PU_EN | + FUSB_REG_SWITCHES0_CC2_PU_EN | + FUSB_REG_SWITCHES0_CC1_PD_EN | + FUSB_REG_SWITCHES0_CC2_PD_EN; + u8 rd_mda, switches0_data = 0x00; + int ret = 0; + + mutex_lock(&chip->lock); + switch (cc) { + case TYPEC_CC_OPEN: + break; + case TYPEC_CC_RD: + switches0_data |= FUSB_REG_SWITCHES0_CC1_PD_EN | + FUSB_REG_SWITCHES0_CC2_PD_EN; + break; + case TYPEC_CC_RP_DEF: + case TYPEC_CC_RP_1_5: + case TYPEC_CC_RP_3_0: + switches0_data |= (chip->cc_polarity == TYPEC_POLARITY_CC1) ? + FUSB_REG_SWITCHES0_CC1_PU_EN : + FUSB_REG_SWITCHES0_CC2_PU_EN; + break; + default: + fusb302_log(chip, "unsupported cc value %s", + typec_cc_status_name[cc]); + ret = -EINVAL; + goto done; + } + + fusb302_log(chip, "cc := %s", typec_cc_status_name[cc]); + + ret = fusb302_set_toggling(chip, TOGGLING_MODE_OFF); + if (ret < 0) { + fusb302_log(chip, "cannot set toggling mode, ret=%d", ret); + goto done; + } + + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, + switches0_mask, switches0_data); + if (ret < 0) { + fusb302_log(chip, "cannot set pull-up/-down, ret = %d", ret); + goto done; + } + /* reset the cc status */ + chip->cc1 = TYPEC_CC_OPEN; + chip->cc2 = TYPEC_CC_OPEN; + + /* adjust current for SRC */ + ret = fusb302_set_src_current(chip, cc_src_current[cc]); + if (ret < 0) { + fusb302_log(chip, "cannot set src current %s, ret=%d", + typec_cc_status_name[cc], ret); + goto done; + } + + /* enable/disable interrupts, BC_LVL for SNK and COMP_CHNG for SRC */ + switch (cc) { + case TYPEC_CC_RP_DEF: + case TYPEC_CC_RP_1_5: + case TYPEC_CC_RP_3_0: + rd_mda = rd_mda_value[cc_src_current[cc]]; + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); + if (ret < 0) { + fusb302_log(chip, + "cannot set SRC measure value, ret=%d", + ret); + goto done; + } + ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK, + FUSB_REG_MASK_BC_LVL | + FUSB_REG_MASK_COMP_CHNG, + FUSB_REG_MASK_BC_LVL); + if (ret < 0) { + fusb302_log(chip, "cannot set SRC interrupt, ret=%d", + ret); + goto done; + } + chip->intr_comp_chng = true; + chip->intr_bc_lvl = false; + break; + case TYPEC_CC_RD: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK, + FUSB_REG_MASK_BC_LVL | + FUSB_REG_MASK_COMP_CHNG, + FUSB_REG_MASK_COMP_CHNG); + if (ret < 0) { + fusb302_log(chip, "cannot set SRC interrupt, ret=%d", + ret); + goto done; + } + chip->intr_bc_lvl = true; + chip->intr_comp_chng = false; + break; + default: + break; + } +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tcpm_get_cc(struct tcpc_dev *dev, enum typec_cc_status *cc1, + enum typec_cc_status *cc2) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + + mutex_lock(&chip->lock); + *cc1 = chip->cc1; + *cc2 = chip->cc2; + fusb302_log(chip, "cc1=%s, cc2=%s", typec_cc_status_name[*cc1], + typec_cc_status_name[*cc2]); + mutex_unlock(&chip->lock); + + return 0; +} + +static int tcpm_set_polarity(struct tcpc_dev *dev, + enum typec_cc_polarity polarity) +{ + return 0; +} + +static int tcpm_set_vconn(struct tcpc_dev *dev, bool on) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + u8 switches0_data = 0x00; + u8 switches0_mask = FUSB_REG_SWITCHES0_VCONN_CC1 | + FUSB_REG_SWITCHES0_VCONN_CC2; + + mutex_lock(&chip->lock); + if (chip->vconn_on == on) { + fusb302_log(chip, "vconn is already %s", str_on_off(on)); + goto done; + } + if (on) { + switches0_data = (chip->cc_polarity == TYPEC_POLARITY_CC1) ? + FUSB_REG_SWITCHES0_VCONN_CC2 : + FUSB_REG_SWITCHES0_VCONN_CC1; + } + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, + switches0_mask, switches0_data); + if (ret < 0) + goto done; + chip->vconn_on = on; + fusb302_log(chip, "vconn := %s", str_on_off(on)); +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tcpm_set_vbus(struct tcpc_dev *dev, bool on, bool charge) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + mutex_lock(&chip->lock); + if (chip->vbus_on == on) { + fusb302_log(chip, "vbus is already %s", str_on_off(on)); + } else { + if (on) + ret = regulator_enable(chip->vbus); + else + ret = regulator_disable(chip->vbus); + if (ret < 0) { + fusb302_log(chip, "cannot %s vbus regulator, ret=%d", + str_enable_disable(on), ret); + goto done; + } + chip->vbus_on = on; + fusb302_log(chip, "vbus := %s", str_on_off(on)); + } + if (chip->charge_on == charge) + fusb302_log(chip, "charge is already %s", str_on_off(charge)); + else + chip->charge_on = charge; + +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int fusb302_pd_tx_flush(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_TX_FLUSH); +} + +static int fusb302_pd_rx_flush(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL1, + FUSB_REG_CONTROL1_RX_FLUSH); +} + +static int fusb302_pd_set_auto_goodcrc(struct fusb302_chip *chip, bool on) +{ + if (on) + return fusb302_i2c_set_bits(chip, FUSB_REG_SWITCHES1, + FUSB_REG_SWITCHES1_AUTO_GCRC); + return fusb302_i2c_clear_bits(chip, FUSB_REG_SWITCHES1, + FUSB_REG_SWITCHES1_AUTO_GCRC); +} + +static int fusb302_pd_set_interrupts(struct fusb302_chip *chip, bool on) +{ + int ret = 0; + u8 mask_interrupts = FUSB_REG_MASK_COLLISION; + u8 maska_interrupts = FUSB_REG_MASKA_RETRYFAIL | + FUSB_REG_MASKA_HARDSENT | + FUSB_REG_MASKA_TX_SUCCESS | + FUSB_REG_MASKA_HARDRESET; + u8 maskb_interrupts = FUSB_REG_MASKB_GCRCSENT; + + ret = on ? + fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, mask_interrupts) : + fusb302_i2c_set_bits(chip, FUSB_REG_MASK, mask_interrupts); + if (ret < 0) + return ret; + ret = on ? + fusb302_i2c_clear_bits(chip, FUSB_REG_MASKA, maska_interrupts) : + fusb302_i2c_set_bits(chip, FUSB_REG_MASKA, maska_interrupts); + if (ret < 0) + return ret; + ret = on ? + fusb302_i2c_clear_bits(chip, FUSB_REG_MASKB, maskb_interrupts) : + fusb302_i2c_set_bits(chip, FUSB_REG_MASKB, maskb_interrupts); + return ret; +} + +static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + mutex_lock(&chip->lock); + if (chip->pd_rx_on == on) { + fusb302_log(chip, "pd is already %s", str_on_off(on)); + goto done; + } + + ret = fusb302_pd_rx_flush(chip); + if (ret < 0) { + fusb302_log(chip, "cannot flush pd rx buffer, ret=%d", ret); + goto done; + } + ret = fusb302_pd_tx_flush(chip); + if (ret < 0) { + fusb302_log(chip, "cannot flush pd tx buffer, ret=%d", ret); + goto done; + } + ret = fusb302_pd_set_auto_goodcrc(chip, on); + if (ret < 0) { + fusb302_log(chip, "cannot turn %s auto GCRC, ret=%d", + str_on_off(on), ret); + goto done; + } + ret = fusb302_pd_set_interrupts(chip, on); + if (ret < 0) { + fusb302_log(chip, "cannot turn %s pd interrupts, ret=%d", + str_on_off(on), ret); + goto done; + } + + chip->pd_rx_on = on; + fusb302_log(chip, "pd := %s", str_on_off(on)); +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static const char * const typec_role_name[] = { + [TYPEC_SINK] = "Sink", + [TYPEC_SOURCE] = "Source", +}; + +static const char * const typec_data_role_name[] = { + [TYPEC_DEVICE] = "Device", + [TYPEC_HOST] = "Host", +}; + +static int tcpm_set_roles(struct tcpc_dev *dev, bool attached, + enum typec_role pwr, enum typec_data_role data) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + u8 switches1_mask = FUSB_REG_SWITCHES1_POWERROLE | + FUSB_REG_SWITCHES1_DATAROLE; + u8 switches1_data = 0x00; + + mutex_lock(&chip->lock); + if (pwr == TYPEC_SOURCE) + switches1_data |= FUSB_REG_SWITCHES1_POWERROLE; + if (data == TYPEC_HOST) + switches1_data |= FUSB_REG_SWITCHES1_DATAROLE; + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES1, + switches1_mask, switches1_data); + if (ret < 0) { + fusb302_log(chip, "unable to set pd header %s, %s, ret=%d", + typec_role_name[pwr], typec_data_role_name[data], + ret); + goto done; + } + fusb302_log(chip, "pd header := %s, %s", typec_role_name[pwr], + typec_data_role_name[data]); +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tcpm_start_toggling(struct tcpc_dev *dev, + enum typec_port_type port_type, + enum typec_cc_status cc) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + enum toggling_mode mode = TOGGLING_MODE_OFF; + int ret = 0; + + switch (port_type) { + case TYPEC_PORT_SRC: + mode = TOGGLING_MODE_SRC; + break; + case TYPEC_PORT_SNK: + mode = TOGGLING_MODE_SNK; + break; + case TYPEC_PORT_DRP: + mode = TOGGLING_MODE_DRP; + break; + } + + mutex_lock(&chip->lock); + ret = fusb302_set_src_current(chip, cc_src_current[cc]); + if (ret < 0) { + fusb302_log(chip, "unable to set src current %s, ret=%d", + typec_cc_status_name[cc], ret); + goto done; + } + ret = fusb302_set_toggling(chip, mode); + if (ret < 0) { + fusb302_log(chip, + "unable to start drp toggling, ret=%d", ret); + goto done; + } + fusb302_log(chip, "start drp toggling"); +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int fusb302_pd_send_message(struct fusb302_chip *chip, + const struct pd_message *msg) +{ + int ret = 0; + u8 buf[40]; + u8 pos = 0; + int len; + + /* SOP tokens */ + buf[pos++] = FUSB302_TKN_SYNC1; + buf[pos++] = FUSB302_TKN_SYNC1; + buf[pos++] = FUSB302_TKN_SYNC1; + buf[pos++] = FUSB302_TKN_SYNC2; + + len = pd_header_cnt_le(msg->header) * 4; + /* plug 2 for header */ + len += 2; + if (len > 0x1F) { + fusb302_log(chip, + "PD message too long %d (incl. header)", len); + return -EINVAL; + } + /* packsym tells the FUSB302 chip that the next X bytes are payload */ + buf[pos++] = FUSB302_TKN_PACKSYM | (len & 0x1F); + memcpy(&buf[pos], &msg->header, sizeof(msg->header)); + pos += sizeof(msg->header); + + len -= 2; + memcpy(&buf[pos], msg->payload, len); + pos += len; + + /* CRC */ + buf[pos++] = FUSB302_TKN_JAMCRC; + /* EOP */ + buf[pos++] = FUSB302_TKN_EOP; + /* turn tx off after sending message */ + buf[pos++] = FUSB302_TKN_TXOFF; + /* start transmission */ + buf[pos++] = FUSB302_TKN_TXON; + + ret = fusb302_i2c_block_write(chip, FUSB_REG_FIFOS, pos, buf); + if (ret < 0) + return ret; + fusb302_log(chip, "sending PD message header: %x", msg->header); + fusb302_log(chip, "sending PD message len: %d", len); + + return ret; +} + +static int fusb302_pd_send_hardreset(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, + FUSB_REG_CONTROL3_SEND_HARDRESET); +} + +static const char * const transmit_type_name[] = { + [TCPC_TX_SOP] = "SOP", + [TCPC_TX_SOP_PRIME] = "SOP'", + [TCPC_TX_SOP_PRIME_PRIME] = "SOP''", + [TCPC_TX_SOP_DEBUG_PRIME] = "DEBUG'", + [TCPC_TX_SOP_DEBUG_PRIME_PRIME] = "DEBUG''", + [TCPC_TX_HARD_RESET] = "HARD_RESET", + [TCPC_TX_CABLE_RESET] = "CABLE_RESET", + [TCPC_TX_BIST_MODE_2] = "BIST_MODE_2", +}; + +static int tcpm_pd_transmit(struct tcpc_dev *dev, enum tcpm_transmit_type type, + const struct pd_message *msg, unsigned int negotiated_rev) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + mutex_lock(&chip->lock); + switch (type) { + case TCPC_TX_SOP: + /* nRetryCount 3 in P2.0 spec, whereas 2 in PD3.0 spec */ + ret = fusb302_enable_tx_auto_retries(chip, negotiated_rev > PD_REV20 ? + FUSB_REG_CONTROL3_N_RETRIES_2 : + FUSB_REG_CONTROL3_N_RETRIES_3); + if (ret < 0) + fusb302_log(chip, "Cannot update retry count ret=%d", ret); + + ret = fusb302_pd_send_message(chip, msg); + if (ret < 0) + fusb302_log(chip, + "cannot send PD message, ret=%d", ret); + break; + case TCPC_TX_HARD_RESET: + ret = fusb302_pd_send_hardreset(chip); + if (ret < 0) + fusb302_log(chip, + "cannot send hardreset, ret=%d", ret); + break; + default: + fusb302_log(chip, "type %s not supported", + transmit_type_name[type]); + ret = -EINVAL; + } + mutex_unlock(&chip->lock); + + return ret; +} + +static enum typec_cc_status fusb302_bc_lvl_to_cc(u8 bc_lvl) +{ + if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_1230_MAX) + return TYPEC_CC_RP_3_0; + if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_600_1230) + return TYPEC_CC_RP_1_5; + if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_200_600) + return TYPEC_CC_RP_DEF; + return TYPEC_CC_OPEN; +} + +static void fusb302_bc_lvl_handler_work(struct work_struct *work) +{ + struct fusb302_chip *chip = container_of(work, struct fusb302_chip, + bc_lvl_handler.work); + int ret = 0; + u8 status0; + u8 bc_lvl; + enum typec_cc_status cc_status; + + mutex_lock(&chip->lock); + if (!chip->intr_bc_lvl) { + fusb302_log(chip, "BC_LVL interrupt is turned off, abort"); + goto done; + } + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + goto done; + fusb302_log(chip, "BC_LVL handler, status0=0x%02x", status0); + if (status0 & FUSB_REG_STATUS0_ACTIVITY) { + fusb302_log(chip, "CC activities detected, delay handling"); + mod_delayed_work(chip->wq, &chip->bc_lvl_handler, + msecs_to_jiffies(T_BC_LVL_DEBOUNCE_DELAY_MS)); + goto done; + } + bc_lvl = status0 & FUSB_REG_STATUS0_BC_LVL_MASK; + cc_status = fusb302_bc_lvl_to_cc(bc_lvl); + if (chip->cc_polarity == TYPEC_POLARITY_CC1) { + if (chip->cc1 != cc_status) { + fusb302_log(chip, "cc1: %s -> %s", + typec_cc_status_name[chip->cc1], + typec_cc_status_name[cc_status]); + chip->cc1 = cc_status; + tcpm_cc_change(chip->tcpm_port); + } + } else { + if (chip->cc2 != cc_status) { + fusb302_log(chip, "cc2: %s -> %s", + typec_cc_status_name[chip->cc2], + typec_cc_status_name[cc_status]); + chip->cc2 = cc_status; + tcpm_cc_change(chip->tcpm_port); + } + } + +done: + mutex_unlock(&chip->lock); +} + +static void init_tcpc_dev(struct tcpc_dev *fusb302_tcpc_dev) +{ + fusb302_tcpc_dev->init = tcpm_init; + fusb302_tcpc_dev->get_vbus = tcpm_get_vbus; + fusb302_tcpc_dev->get_current_limit = tcpm_get_current_limit; + fusb302_tcpc_dev->set_cc = tcpm_set_cc; + fusb302_tcpc_dev->get_cc = tcpm_get_cc; + fusb302_tcpc_dev->set_polarity = tcpm_set_polarity; + fusb302_tcpc_dev->set_vconn = tcpm_set_vconn; + fusb302_tcpc_dev->set_vbus = tcpm_set_vbus; + fusb302_tcpc_dev->set_pd_rx = tcpm_set_pd_rx; + fusb302_tcpc_dev->set_roles = tcpm_set_roles; + fusb302_tcpc_dev->start_toggling = tcpm_start_toggling; + fusb302_tcpc_dev->pd_transmit = tcpm_pd_transmit; +} + +static const char * const cc_polarity_name[] = { + [TYPEC_POLARITY_CC1] = "Polarity_CC1", + [TYPEC_POLARITY_CC2] = "Polarity_CC2", +}; + +static int fusb302_set_cc_polarity_and_pull(struct fusb302_chip *chip, + enum typec_cc_polarity cc_polarity, + bool pull_up, bool pull_down) +{ + int ret = 0; + u8 switches0_data = 0x00; + u8 switches1_mask = FUSB_REG_SWITCHES1_TXCC1_EN | + FUSB_REG_SWITCHES1_TXCC2_EN; + u8 switches1_data = 0x00; + + if (pull_down) + switches0_data |= FUSB_REG_SWITCHES0_CC1_PD_EN | + FUSB_REG_SWITCHES0_CC2_PD_EN; + + if (cc_polarity == TYPEC_POLARITY_CC1) { + switches0_data |= FUSB_REG_SWITCHES0_MEAS_CC1; + if (chip->vconn_on) + switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC2; + if (pull_up) + switches0_data |= FUSB_REG_SWITCHES0_CC1_PU_EN; + switches1_data = FUSB_REG_SWITCHES1_TXCC1_EN; + } else { + switches0_data |= FUSB_REG_SWITCHES0_MEAS_CC2; + if (chip->vconn_on) + switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC1; + if (pull_up) + switches0_data |= FUSB_REG_SWITCHES0_CC2_PU_EN; + switches1_data = FUSB_REG_SWITCHES1_TXCC2_EN; + } + ret = fusb302_i2c_write(chip, FUSB_REG_SWITCHES0, switches0_data); + if (ret < 0) + return ret; + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES1, + switches1_mask, switches1_data); + if (ret < 0) + return ret; + chip->cc_polarity = cc_polarity; + + return ret; +} + +static int fusb302_handle_togdone_snk(struct fusb302_chip *chip, + u8 togdone_result) +{ + int ret = 0; + u8 status0; + u8 bc_lvl; + enum typec_cc_polarity cc_polarity; + enum typec_cc_status cc_status_active, cc1, cc2; + + /* set polarity and pull_up, pull_down */ + cc_polarity = (togdone_result == FUSB_REG_STATUS1A_TOGSS_SNK1) ? + TYPEC_POLARITY_CC1 : TYPEC_POLARITY_CC2; + ret = fusb302_set_cc_polarity_and_pull(chip, cc_polarity, false, true); + if (ret < 0) { + fusb302_log(chip, "cannot set cc polarity %s, ret=%d", + cc_polarity_name[cc_polarity], ret); + return ret; + } + /* fusb302_set_cc_polarity() has set the correct measure block */ + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + return ret; + bc_lvl = status0 & FUSB_REG_STATUS0_BC_LVL_MASK; + cc_status_active = fusb302_bc_lvl_to_cc(bc_lvl); + /* restart toggling if the cc status on the active line is OPEN */ + if (cc_status_active == TYPEC_CC_OPEN) { + fusb302_log(chip, "restart toggling as CC_OPEN detected"); + ret = fusb302_set_toggling(chip, chip->toggling_mode); + return ret; + } + /* update tcpm with the new cc value */ + cc1 = (cc_polarity == TYPEC_POLARITY_CC1) ? + cc_status_active : TYPEC_CC_OPEN; + cc2 = (cc_polarity == TYPEC_POLARITY_CC2) ? + cc_status_active : TYPEC_CC_OPEN; + if ((chip->cc1 != cc1) || (chip->cc2 != cc2)) { + chip->cc1 = cc1; + chip->cc2 = cc2; + tcpm_cc_change(chip->tcpm_port); + } + /* turn off toggling */ + ret = fusb302_set_toggling(chip, TOGGLING_MODE_OFF); + if (ret < 0) { + fusb302_log(chip, + "cannot set toggling mode off, ret=%d", ret); + return ret; + } + /* unmask bc_lvl interrupt */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, FUSB_REG_MASK_BC_LVL); + if (ret < 0) { + fusb302_log(chip, + "cannot unmask bc_lcl interrupt, ret=%d", ret); + return ret; + } + chip->intr_bc_lvl = true; + fusb302_log(chip, "detected cc1=%s, cc2=%s", + typec_cc_status_name[cc1], + typec_cc_status_name[cc2]); + + return ret; +} + +/* On error returns < 0, otherwise a typec_cc_status value */ +static int fusb302_get_src_cc_status(struct fusb302_chip *chip, + enum typec_cc_polarity cc_polarity, + enum typec_cc_status *cc) +{ + u8 ra_mda = ra_mda_value[chip->src_current_status]; + u8 rd_mda = rd_mda_value[chip->src_current_status]; + u8 switches0_data, status0; + int ret; + + /* Step 1: Set switches so that we measure the right CC pin */ + switches0_data = (cc_polarity == TYPEC_POLARITY_CC1) ? + FUSB_REG_SWITCHES0_CC1_PU_EN | FUSB_REG_SWITCHES0_MEAS_CC1 : + FUSB_REG_SWITCHES0_CC2_PU_EN | FUSB_REG_SWITCHES0_MEAS_CC2; + ret = fusb302_i2c_write(chip, FUSB_REG_SWITCHES0, switches0_data); + if (ret < 0) + return ret; + + fusb302_i2c_read(chip, FUSB_REG_SWITCHES0, &status0); + fusb302_log(chip, "get_src_cc_status switches: 0x%0x", status0); + + /* Step 2: Set compararator volt to differentiate between Open and Rd */ + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); + if (ret < 0) + return ret; + + usleep_range(50, 100); + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + return ret; + + fusb302_log(chip, "get_src_cc_status rd_mda status0: 0x%0x", status0); + if (status0 & FUSB_REG_STATUS0_COMP) { + *cc = TYPEC_CC_OPEN; + return 0; + } + + /* Step 3: Set compararator input to differentiate between Rd and Ra. */ + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, ra_mda); + if (ret < 0) + return ret; + + usleep_range(50, 100); + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + return ret; + + fusb302_log(chip, "get_src_cc_status ra_mda status0: 0x%0x", status0); + if (status0 & FUSB_REG_STATUS0_COMP) + *cc = TYPEC_CC_RD; + else + *cc = TYPEC_CC_RA; + + return 0; +} + +static int fusb302_handle_togdone_src(struct fusb302_chip *chip, + u8 togdone_result) +{ + /* + * - set polarity (measure cc, vconn, tx) + * - set pull_up, pull_down + * - set cc1, cc2, and update to tcpm_port + * - set I_COMP interrupt on + */ + int ret = 0; + u8 rd_mda = rd_mda_value[chip->src_current_status]; + enum toggling_mode toggling_mode = chip->toggling_mode; + enum typec_cc_polarity cc_polarity; + enum typec_cc_status cc1, cc2; + + /* + * The toggle-engine will stop in a src state if it sees either Ra or + * Rd. Determine the status for both CC pins, starting with the one + * where toggling stopped, as that is where the switches point now. + */ + if (togdone_result == FUSB_REG_STATUS1A_TOGSS_SRC1) + ret = fusb302_get_src_cc_status(chip, TYPEC_POLARITY_CC1, &cc1); + else + ret = fusb302_get_src_cc_status(chip, TYPEC_POLARITY_CC2, &cc2); + if (ret < 0) + return ret; + /* we must turn off toggling before we can measure the other pin */ + ret = fusb302_set_toggling(chip, TOGGLING_MODE_OFF); + if (ret < 0) { + fusb302_log(chip, "cannot set toggling mode off, ret=%d", ret); + return ret; + } + /* get the status of the other pin */ + if (togdone_result == FUSB_REG_STATUS1A_TOGSS_SRC1) + ret = fusb302_get_src_cc_status(chip, TYPEC_POLARITY_CC2, &cc2); + else + ret = fusb302_get_src_cc_status(chip, TYPEC_POLARITY_CC1, &cc1); + if (ret < 0) + return ret; + + /* determine polarity based on the status of both pins */ + if (cc1 == TYPEC_CC_RD && + (cc2 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_RA)) { + cc_polarity = TYPEC_POLARITY_CC1; + } else if (cc2 == TYPEC_CC_RD && + (cc1 == TYPEC_CC_OPEN || cc1 == TYPEC_CC_RA)) { + cc_polarity = TYPEC_POLARITY_CC2; + } else { + fusb302_log(chip, "unexpected CC status cc1=%s, cc2=%s, restarting toggling", + typec_cc_status_name[cc1], + typec_cc_status_name[cc2]); + return fusb302_set_toggling(chip, toggling_mode); + } + /* set polarity and pull_up, pull_down */ + ret = fusb302_set_cc_polarity_and_pull(chip, cc_polarity, true, false); + if (ret < 0) { + fusb302_log(chip, "cannot set cc polarity %s, ret=%d", + cc_polarity_name[cc_polarity], ret); + return ret; + } + /* update tcpm with the new cc value */ + if ((chip->cc1 != cc1) || (chip->cc2 != cc2)) { + chip->cc1 = cc1; + chip->cc2 = cc2; + tcpm_cc_change(chip->tcpm_port); + } + /* set MDAC to Rd threshold, and unmask I_COMP for unplug detection */ + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); + if (ret < 0) + return ret; + /* unmask comp_chng interrupt */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, + FUSB_REG_MASK_COMP_CHNG); + if (ret < 0) { + fusb302_log(chip, + "cannot unmask comp_chng interrupt, ret=%d", ret); + return ret; + } + chip->intr_comp_chng = true; + fusb302_log(chip, "detected cc1=%s, cc2=%s", + typec_cc_status_name[cc1], + typec_cc_status_name[cc2]); + + return ret; +} + +static int fusb302_handle_togdone(struct fusb302_chip *chip) +{ + int ret = 0; + u8 status1a; + u8 togdone_result; + + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS1A, &status1a); + if (ret < 0) + return ret; + togdone_result = (status1a >> FUSB_REG_STATUS1A_TOGSS_POS) & + FUSB_REG_STATUS1A_TOGSS_MASK; + switch (togdone_result) { + case FUSB_REG_STATUS1A_TOGSS_SNK1: + case FUSB_REG_STATUS1A_TOGSS_SNK2: + return fusb302_handle_togdone_snk(chip, togdone_result); + case FUSB_REG_STATUS1A_TOGSS_SRC1: + case FUSB_REG_STATUS1A_TOGSS_SRC2: + return fusb302_handle_togdone_src(chip, togdone_result); + case FUSB_REG_STATUS1A_TOGSS_AA: + /* doesn't support */ + fusb302_log(chip, "AudioAccessory not supported"); + fusb302_set_toggling(chip, chip->toggling_mode); + break; + default: + fusb302_log(chip, "TOGDONE with an invalid state: %d", + togdone_result); + fusb302_set_toggling(chip, chip->toggling_mode); + break; + } + return ret; +} + +static int fusb302_pd_reset(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_RESET, + FUSB_REG_RESET_PD_RESET); +} + +static int fusb302_pd_read_message(struct fusb302_chip *chip, + struct pd_message *msg) +{ + int ret = 0; + u8 token; + u8 crc[4]; + int len; + + /* first SOP token */ + ret = fusb302_i2c_read(chip, FUSB_REG_FIFOS, &token); + if (ret < 0) + return ret; + ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, 2, + (u8 *)&msg->header); + if (ret < 0) + return ret; + len = pd_header_cnt_le(msg->header) * 4; + /* add 4 to length to include the CRC */ + if (len > PD_MAX_PAYLOAD * 4) { + fusb302_log(chip, "PD message too long %d", len); + return -EINVAL; + } + if (len > 0) { + ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, len, + (u8 *)msg->payload); + if (ret < 0) + return ret; + } + /* another 4 bytes to read CRC out */ + ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, 4, crc); + if (ret < 0) + return ret; + fusb302_log(chip, "PD message header: %x", msg->header); + fusb302_log(chip, "PD message len: %d", len); + + /* + * Check if we've read off a GoodCRC message. If so then indicate to + * TCPM that the previous transmission has completed. Otherwise we pass + * the received message over to TCPM for processing. + * + * We make this check here instead of basing the reporting decision on + * the IRQ event type, as it's possible for the chip to report the + * TX_SUCCESS and GCRCSENT events out of order on occasion, so we need + * to check the message type to ensure correct reporting to TCPM. + */ + if ((!len) && (pd_header_type_le(msg->header) == PD_CTRL_GOOD_CRC)) + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS); + else + tcpm_pd_receive(chip->tcpm_port, msg, TCPC_TX_SOP); + + return ret; +} + +static irqreturn_t fusb302_irq_intn(int irq, void *dev_id) +{ + struct fusb302_chip *chip = dev_id; + unsigned long flags; + + /* Disable our level triggered IRQ until our irq_work has cleared it */ + disable_irq_nosync(chip->gpio_int_n_irq); + + spin_lock_irqsave(&chip->irq_lock, flags); + if (chip->irq_suspended) + chip->irq_while_suspended = true; + else + schedule_work(&chip->irq_work); + spin_unlock_irqrestore(&chip->irq_lock, flags); + + return IRQ_HANDLED; +} + +static void fusb302_irq_work(struct work_struct *work) +{ + struct fusb302_chip *chip = container_of(work, struct fusb302_chip, + irq_work); + int ret = 0; + u8 interrupt; + u8 interrupta; + u8 interruptb; + u8 status0; + bool vbus_present; + bool comp_result; + bool intr_togdone; + bool intr_bc_lvl; + bool intr_comp_chng; + struct pd_message pd_msg; + + mutex_lock(&chip->lock); + /* grab a snapshot of intr flags */ + intr_togdone = chip->intr_togdone; + intr_bc_lvl = chip->intr_bc_lvl; + intr_comp_chng = chip->intr_comp_chng; + + ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPT, &interrupt); + if (ret < 0) + goto done; + ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPTA, &interrupta); + if (ret < 0) + goto done; + ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPTB, &interruptb); + if (ret < 0) + goto done; + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + goto done; + fusb302_log(chip, + "IRQ: 0x%02x, a: 0x%02x, b: 0x%02x, status0: 0x%02x", + interrupt, interrupta, interruptb, status0); + + if (interrupt & FUSB_REG_INTERRUPT_VBUSOK) { + vbus_present = !!(status0 & FUSB_REG_STATUS0_VBUSOK); + fusb302_log(chip, "IRQ: VBUS_OK, vbus=%s", + str_on_off(vbus_present)); + if (vbus_present != chip->vbus_present) { + chip->vbus_present = vbus_present; + tcpm_vbus_change(chip->tcpm_port); + } + } + + if ((interrupta & FUSB_REG_INTERRUPTA_TOGDONE) && intr_togdone) { + fusb302_log(chip, "IRQ: TOGDONE"); + ret = fusb302_handle_togdone(chip); + if (ret < 0) { + fusb302_log(chip, + "handle togdone error, ret=%d", ret); + goto done; + } + } + + if ((interrupt & FUSB_REG_INTERRUPT_BC_LVL) && intr_bc_lvl) { + fusb302_log(chip, "IRQ: BC_LVL, handler pending"); + /* + * as BC_LVL interrupt can be affected by PD activity, + * apply delay to for the handler to wait for the PD + * signaling to finish. + */ + mod_delayed_work(chip->wq, &chip->bc_lvl_handler, + msecs_to_jiffies(T_BC_LVL_DEBOUNCE_DELAY_MS)); + } + + if ((interrupt & FUSB_REG_INTERRUPT_COMP_CHNG) && intr_comp_chng) { + comp_result = !!(status0 & FUSB_REG_STATUS0_COMP); + fusb302_log(chip, "IRQ: COMP_CHNG, comp=%s", + str_true_false(comp_result)); + if (comp_result) { + /* cc level > Rd_threshold, detach */ + chip->cc1 = TYPEC_CC_OPEN; + chip->cc2 = TYPEC_CC_OPEN; + tcpm_cc_change(chip->tcpm_port); + } + } + + if (interrupt & FUSB_REG_INTERRUPT_COLLISION) { + fusb302_log(chip, "IRQ: PD collision"); + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_FAILED); + } + + if (interrupta & FUSB_REG_INTERRUPTA_RETRYFAIL) { + fusb302_log(chip, "IRQ: PD retry failed"); + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_FAILED); + } + + if (interrupta & FUSB_REG_INTERRUPTA_HARDSENT) { + fusb302_log(chip, "IRQ: PD hardreset sent"); + ret = fusb302_pd_reset(chip); + if (ret < 0) { + fusb302_log(chip, "cannot PD reset, ret=%d", ret); + goto done; + } + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS); + } + + if (interrupta & FUSB_REG_INTERRUPTA_TX_SUCCESS) { + fusb302_log(chip, "IRQ: PD tx success"); + ret = fusb302_pd_read_message(chip, &pd_msg); + if (ret < 0) { + fusb302_log(chip, + "cannot read in PD message, ret=%d", ret); + goto done; + } + } + + if (interrupta & FUSB_REG_INTERRUPTA_HARDRESET) { + fusb302_log(chip, "IRQ: PD received hardreset"); + ret = fusb302_pd_reset(chip); + if (ret < 0) { + fusb302_log(chip, "cannot PD reset, ret=%d", ret); + goto done; + } + tcpm_pd_hard_reset(chip->tcpm_port); + } + + if (interruptb & FUSB_REG_INTERRUPTB_GCRCSENT) { + fusb302_log(chip, "IRQ: PD sent good CRC"); + ret = fusb302_pd_read_message(chip, &pd_msg); + if (ret < 0) { + fusb302_log(chip, + "cannot read in PD message, ret=%d", ret); + goto done; + } + } +done: + mutex_unlock(&chip->lock); + enable_irq(chip->gpio_int_n_irq); +} + +static int init_gpio(struct fusb302_chip *chip) +{ + struct device *dev = chip->dev; + int ret = 0; + + chip->gpio_int_n = devm_gpiod_get(dev, "fcs,int_n", GPIOD_IN); + if (IS_ERR(chip->gpio_int_n)) { + dev_err(dev, "failed to request gpio_int_n\n"); + return PTR_ERR(chip->gpio_int_n); + } + ret = gpiod_to_irq(chip->gpio_int_n); + if (ret < 0) { + dev_err(dev, + "cannot request IRQ for GPIO Int_N, ret=%d", ret); + return ret; + } + chip->gpio_int_n_irq = ret; + return 0; +} + +#define PDO_FIXED_FLAGS \ + (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM) + +static const u32 src_pdo[] = { + PDO_FIXED(5000, 400, PDO_FIXED_FLAGS) +}; + +static const u32 snk_pdo[] = { + PDO_FIXED(5000, 400, PDO_FIXED_FLAGS) +}; + +static const struct property_entry port_props[] = { + PROPERTY_ENTRY_STRING("data-role", "dual"), + PROPERTY_ENTRY_STRING("power-role", "dual"), + PROPERTY_ENTRY_STRING("try-power-role", "sink"), + PROPERTY_ENTRY_U32_ARRAY("source-pdos", src_pdo), + PROPERTY_ENTRY_U32_ARRAY("sink-pdos", snk_pdo), + PROPERTY_ENTRY_U32("op-sink-microwatt", 2500000), + { } +}; + +static struct fwnode_handle *fusb302_fwnode_get(struct device *dev) +{ + struct fwnode_handle *fwnode; + + fwnode = device_get_named_child_node(dev, "connector"); + if (!fwnode) + fwnode = fwnode_create_software_node(port_props, NULL); + + return fwnode; +} + +static int fusb302_probe(struct i2c_client *client) +{ + struct fusb302_chip *chip; + struct i2c_adapter *adapter = client->adapter; + struct auxiliary_device *bridge_dev; + struct device *dev = &client->dev; + const char *name; + int ret = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) { + dev_err(&client->dev, + "I2C/SMBus block functionality not supported!\n"); + return -ENODEV; + } + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->i2c_client = client; + chip->dev = &client->dev; + mutex_init(&chip->lock); + + /* + * Devicetree platforms should get extcon via phandle (not yet + * supported). On ACPI platforms, we get the name from a device prop. + * This device prop is for kernel internal use only and is expected + * to be set by the platform code which also registers the i2c client + * for the fusb302. + */ + if (device_property_read_string(dev, "linux,extcon-name", &name) == 0) { + chip->extcon = extcon_get_extcon_dev(name); + if (IS_ERR(chip->extcon)) + return PTR_ERR(chip->extcon); + } + + chip->vbus = devm_regulator_get(chip->dev, "vbus"); + if (IS_ERR(chip->vbus)) + return PTR_ERR(chip->vbus); + + chip->wq = create_singlethread_workqueue(dev_name(chip->dev)); + if (!chip->wq) + return -ENOMEM; + + spin_lock_init(&chip->irq_lock); + INIT_WORK(&chip->irq_work, fusb302_irq_work); + INIT_DELAYED_WORK(&chip->bc_lvl_handler, fusb302_bc_lvl_handler_work); + init_tcpc_dev(&chip->tcpc_dev); + fusb302_debugfs_init(chip); + + if (client->irq) { + chip->gpio_int_n_irq = client->irq; + } else { + ret = init_gpio(chip); + if (ret < 0) + goto destroy_workqueue; + } + + chip->tcpc_dev.fwnode = fusb302_fwnode_get(dev); + if (IS_ERR(chip->tcpc_dev.fwnode)) { + ret = PTR_ERR(chip->tcpc_dev.fwnode); + goto destroy_workqueue; + } + + bridge_dev = devm_drm_dp_hpd_bridge_alloc(chip->dev, to_of_node(chip->tcpc_dev.fwnode)); + if (IS_ERR(bridge_dev)) { + ret = dev_err_probe(chip->dev, PTR_ERR(bridge_dev), + "failed to alloc bridge\n"); + goto fwnode_put; + } + + chip->tcpm_port = tcpm_register_port(&client->dev, &chip->tcpc_dev); + if (IS_ERR(chip->tcpm_port)) { + ret = dev_err_probe(dev, PTR_ERR(chip->tcpm_port), + "cannot register tcpm port\n"); + goto fwnode_put; + } + + ret = devm_drm_dp_hpd_bridge_add(chip->dev, bridge_dev); + if (ret) + goto tcpm_unregister_port; + + ret = request_threaded_irq(chip->gpio_int_n_irq, NULL, fusb302_irq_intn, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "fsc_interrupt_int_n", chip); + if (ret < 0) { + dev_err(dev, "cannot request IRQ for GPIO Int_N, ret=%d", ret); + goto tcpm_unregister_port; + } + enable_irq_wake(chip->gpio_int_n_irq); + i2c_set_clientdata(client, chip); + + return 0; + +tcpm_unregister_port: + tcpm_unregister_port(chip->tcpm_port); +fwnode_put: + fwnode_handle_put(chip->tcpc_dev.fwnode); +destroy_workqueue: + fusb302_debugfs_exit(chip); + destroy_workqueue(chip->wq); + + return ret; +} + +static void fusb302_remove(struct i2c_client *client) +{ + struct fusb302_chip *chip = i2c_get_clientdata(client); + + disable_irq_wake(chip->gpio_int_n_irq); + free_irq(chip->gpio_int_n_irq, chip); + cancel_work_sync(&chip->irq_work); + cancel_delayed_work_sync(&chip->bc_lvl_handler); + tcpm_unregister_port(chip->tcpm_port); + fwnode_handle_put(chip->tcpc_dev.fwnode); + destroy_workqueue(chip->wq); + fusb302_debugfs_exit(chip); +} + +static int fusb302_pm_suspend(struct device *dev) +{ + struct fusb302_chip *chip = dev->driver_data; + unsigned long flags; + + spin_lock_irqsave(&chip->irq_lock, flags); + chip->irq_suspended = true; + spin_unlock_irqrestore(&chip->irq_lock, flags); + + /* Make sure any pending irq_work is finished before the bus suspends */ + flush_work(&chip->irq_work); + return 0; +} + +static int fusb302_pm_resume(struct device *dev) +{ + struct fusb302_chip *chip = dev->driver_data; + unsigned long flags; + + spin_lock_irqsave(&chip->irq_lock, flags); + if (chip->irq_while_suspended) { + schedule_work(&chip->irq_work); + chip->irq_while_suspended = false; + } + chip->irq_suspended = false; + spin_unlock_irqrestore(&chip->irq_lock, flags); + + return 0; +} + +static const struct of_device_id fusb302_dt_match[] __maybe_unused = { + {.compatible = "fcs,fusb302"}, + {}, +}; +MODULE_DEVICE_TABLE(of, fusb302_dt_match); + +static const struct i2c_device_id fusb302_i2c_device_id[] = { + { .name = "typec_fusb302" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, fusb302_i2c_device_id); + +static const struct dev_pm_ops fusb302_pm_ops = { + .suspend = fusb302_pm_suspend, + .resume = fusb302_pm_resume, +}; + +static struct i2c_driver fusb302_driver = { + .driver = { + .name = "typec_fusb302", + .pm = &fusb302_pm_ops, + .of_match_table = of_match_ptr(fusb302_dt_match), + }, + .probe = fusb302_probe, + .remove = fusb302_remove, + .id_table = fusb302_i2c_device_id, +}; +module_i2c_driver(fusb302_driver); + +MODULE_AUTHOR("Yueyao Zhu "); +MODULE_DESCRIPTION("Fairchild FUSB302 Type-C Chip Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/fusb302_reg.h b/drivers/usb/typec/tcpm/fusb302_reg.h new file mode 100644 index 000000000..edc0e4b0f --- /dev/null +++ b/drivers/usb/typec/tcpm/fusb302_reg.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2016-2017 Google, Inc + * + * Fairchild FUSB302 Type-C Chip Driver + */ + +#ifndef FUSB302_REG_H +#define FUSB302_REG_H + +#define FUSB_REG_DEVICE_ID 0x01 +#define FUSB_REG_SWITCHES0 0x02 +#define FUSB_REG_SWITCHES0_CC2_PU_EN BIT(7) +#define FUSB_REG_SWITCHES0_CC1_PU_EN BIT(6) +#define FUSB_REG_SWITCHES0_VCONN_CC2 BIT(5) +#define FUSB_REG_SWITCHES0_VCONN_CC1 BIT(4) +#define FUSB_REG_SWITCHES0_MEAS_CC2 BIT(3) +#define FUSB_REG_SWITCHES0_MEAS_CC1 BIT(2) +#define FUSB_REG_SWITCHES0_CC2_PD_EN BIT(1) +#define FUSB_REG_SWITCHES0_CC1_PD_EN BIT(0) +#define FUSB_REG_SWITCHES1 0x03 +#define FUSB_REG_SWITCHES1_POWERROLE BIT(7) +#define FUSB_REG_SWITCHES1_SPECREV1 BIT(6) +#define FUSB_REG_SWITCHES1_SPECREV0 BIT(5) +#define FUSB_REG_SWITCHES1_DATAROLE BIT(4) +#define FUSB_REG_SWITCHES1_AUTO_GCRC BIT(2) +#define FUSB_REG_SWITCHES1_TXCC2_EN BIT(1) +#define FUSB_REG_SWITCHES1_TXCC1_EN BIT(0) +#define FUSB_REG_MEASURE 0x04 +#define FUSB_REG_MEASURE_MDAC5 BIT(7) +#define FUSB_REG_MEASURE_MDAC4 BIT(6) +#define FUSB_REG_MEASURE_MDAC3 BIT(5) +#define FUSB_REG_MEASURE_MDAC2 BIT(4) +#define FUSB_REG_MEASURE_MDAC1 BIT(3) +#define FUSB_REG_MEASURE_MDAC0 BIT(2) +#define FUSB_REG_MEASURE_VBUS BIT(1) +#define FUSB_REG_MEASURE_XXXX5 BIT(0) +#define FUSB_REG_CONTROL0 0x06 +#define FUSB_REG_CONTROL0_TX_FLUSH BIT(6) +#define FUSB_REG_CONTROL0_INT_MASK BIT(5) +#define FUSB_REG_CONTROL0_HOST_CUR_MASK (0xC) +#define FUSB_REG_CONTROL0_HOST_CUR_HIGH (0xC) +#define FUSB_REG_CONTROL0_HOST_CUR_MED (0x8) +#define FUSB_REG_CONTROL0_HOST_CUR_DEF (0x4) +#define FUSB_REG_CONTROL0_TX_START BIT(0) +#define FUSB_REG_CONTROL1 0x07 +#define FUSB_REG_CONTROL1_ENSOP2DB BIT(6) +#define FUSB_REG_CONTROL1_ENSOP1DB BIT(5) +#define FUSB_REG_CONTROL1_BIST_MODE2 BIT(4) +#define FUSB_REG_CONTROL1_RX_FLUSH BIT(2) +#define FUSB_REG_CONTROL1_ENSOP2 BIT(1) +#define FUSB_REG_CONTROL1_ENSOP1 BIT(0) +#define FUSB_REG_CONTROL2 0x08 +#define FUSB_REG_CONTROL2_MODE BIT(1) +#define FUSB_REG_CONTROL2_MODE_MASK (0x6) +#define FUSB_REG_CONTROL2_MODE_DFP (0x6) +#define FUSB_REG_CONTROL2_MODE_UFP (0x4) +#define FUSB_REG_CONTROL2_MODE_DRP (0x2) +#define FUSB_REG_CONTROL2_MODE_NONE (0x0) +#define FUSB_REG_CONTROL2_TOGGLE BIT(0) +#define FUSB_REG_CONTROL3 0x09 +#define FUSB_REG_CONTROL3_SEND_HARDRESET BIT(6) +#define FUSB_REG_CONTROL3_BIST_TMODE BIT(5) /* 302B Only */ +#define FUSB_REG_CONTROL3_AUTO_HARDRESET BIT(4) +#define FUSB_REG_CONTROL3_AUTO_SOFTRESET BIT(3) +#define FUSB_REG_CONTROL3_N_RETRIES BIT(1) +#define FUSB_REG_CONTROL3_N_RETRIES_MASK (0x6) +#define FUSB_REG_CONTROL3_N_RETRIES_3 (0x6) +#define FUSB_REG_CONTROL3_N_RETRIES_2 (0x4) +#define FUSB_REG_CONTROL3_N_RETRIES_1 (0x2) +#define FUSB_REG_CONTROL3_AUTO_RETRY BIT(0) +#define FUSB_REG_MASK 0x0A +#define FUSB_REG_MASK_VBUSOK BIT(7) +#define FUSB_REG_MASK_ACTIVITY BIT(6) +#define FUSB_REG_MASK_COMP_CHNG BIT(5) +#define FUSB_REG_MASK_CRC_CHK BIT(4) +#define FUSB_REG_MASK_ALERT BIT(3) +#define FUSB_REG_MASK_WAKE BIT(2) +#define FUSB_REG_MASK_COLLISION BIT(1) +#define FUSB_REG_MASK_BC_LVL BIT(0) +#define FUSB_REG_POWER 0x0B +#define FUSB_REG_POWER_PWR BIT(0) +#define FUSB_REG_POWER_PWR_LOW 0x1 +#define FUSB_REG_POWER_PWR_MEDIUM 0x3 +#define FUSB_REG_POWER_PWR_HIGH 0x7 +#define FUSB_REG_POWER_PWR_ALL 0xF +#define FUSB_REG_RESET 0x0C +#define FUSB_REG_RESET_PD_RESET BIT(1) +#define FUSB_REG_RESET_SW_RESET BIT(0) +#define FUSB_REG_MASKA 0x0E +#define FUSB_REG_MASKA_OCP_TEMP BIT(7) +#define FUSB_REG_MASKA_TOGDONE BIT(6) +#define FUSB_REG_MASKA_SOFTFAIL BIT(5) +#define FUSB_REG_MASKA_RETRYFAIL BIT(4) +#define FUSB_REG_MASKA_HARDSENT BIT(3) +#define FUSB_REG_MASKA_TX_SUCCESS BIT(2) +#define FUSB_REG_MASKA_SOFTRESET BIT(1) +#define FUSB_REG_MASKA_HARDRESET BIT(0) +#define FUSB_REG_MASKB 0x0F +#define FUSB_REG_MASKB_GCRCSENT BIT(0) +#define FUSB_REG_STATUS0A 0x3C +#define FUSB_REG_STATUS0A_SOFTFAIL BIT(5) +#define FUSB_REG_STATUS0A_RETRYFAIL BIT(4) +#define FUSB_REG_STATUS0A_POWER BIT(2) +#define FUSB_REG_STATUS0A_RX_SOFT_RESET BIT(1) +#define FUSB_REG_STATUS0A_RX_HARD_RESET BIT(0) +#define FUSB_REG_STATUS1A 0x3D +#define FUSB_REG_STATUS1A_TOGSS BIT(3) +#define FUSB_REG_STATUS1A_TOGSS_RUNNING 0x0 +#define FUSB_REG_STATUS1A_TOGSS_SRC1 0x1 +#define FUSB_REG_STATUS1A_TOGSS_SRC2 0x2 +#define FUSB_REG_STATUS1A_TOGSS_SNK1 0x5 +#define FUSB_REG_STATUS1A_TOGSS_SNK2 0x6 +#define FUSB_REG_STATUS1A_TOGSS_AA 0x7 +#define FUSB_REG_STATUS1A_TOGSS_POS (3) +#define FUSB_REG_STATUS1A_TOGSS_MASK (0x7) +#define FUSB_REG_STATUS1A_RXSOP2DB BIT(2) +#define FUSB_REG_STATUS1A_RXSOP1DB BIT(1) +#define FUSB_REG_STATUS1A_RXSOP BIT(0) +#define FUSB_REG_INTERRUPTA 0x3E +#define FUSB_REG_INTERRUPTA_OCP_TEMP BIT(7) +#define FUSB_REG_INTERRUPTA_TOGDONE BIT(6) +#define FUSB_REG_INTERRUPTA_SOFTFAIL BIT(5) +#define FUSB_REG_INTERRUPTA_RETRYFAIL BIT(4) +#define FUSB_REG_INTERRUPTA_HARDSENT BIT(3) +#define FUSB_REG_INTERRUPTA_TX_SUCCESS BIT(2) +#define FUSB_REG_INTERRUPTA_SOFTRESET BIT(1) +#define FUSB_REG_INTERRUPTA_HARDRESET BIT(0) +#define FUSB_REG_INTERRUPTB 0x3F +#define FUSB_REG_INTERRUPTB_GCRCSENT BIT(0) +#define FUSB_REG_STATUS0 0x40 +#define FUSB_REG_STATUS0_VBUSOK BIT(7) +#define FUSB_REG_STATUS0_ACTIVITY BIT(6) +#define FUSB_REG_STATUS0_COMP BIT(5) +#define FUSB_REG_STATUS0_CRC_CHK BIT(4) +#define FUSB_REG_STATUS0_ALERT BIT(3) +#define FUSB_REG_STATUS0_WAKE BIT(2) +#define FUSB_REG_STATUS0_BC_LVL_MASK 0x03 +#define FUSB_REG_STATUS0_BC_LVL_0_200 0x0 +#define FUSB_REG_STATUS0_BC_LVL_200_600 0x1 +#define FUSB_REG_STATUS0_BC_LVL_600_1230 0x2 +#define FUSB_REG_STATUS0_BC_LVL_1230_MAX 0x3 +#define FUSB_REG_STATUS0_BC_LVL1 BIT(1) +#define FUSB_REG_STATUS0_BC_LVL0 BIT(0) +#define FUSB_REG_STATUS1 0x41 +#define FUSB_REG_STATUS1_RXSOP2 BIT(7) +#define FUSB_REG_STATUS1_RXSOP1 BIT(6) +#define FUSB_REG_STATUS1_RX_EMPTY BIT(5) +#define FUSB_REG_STATUS1_RX_FULL BIT(4) +#define FUSB_REG_STATUS1_TX_EMPTY BIT(3) +#define FUSB_REG_STATUS1_TX_FULL BIT(2) +#define FUSB_REG_INTERRUPT 0x42 +#define FUSB_REG_INTERRUPT_VBUSOK BIT(7) +#define FUSB_REG_INTERRUPT_ACTIVITY BIT(6) +#define FUSB_REG_INTERRUPT_COMP_CHNG BIT(5) +#define FUSB_REG_INTERRUPT_CRC_CHK BIT(4) +#define FUSB_REG_INTERRUPT_ALERT BIT(3) +#define FUSB_REG_INTERRUPT_WAKE BIT(2) +#define FUSB_REG_INTERRUPT_COLLISION BIT(1) +#define FUSB_REG_INTERRUPT_BC_LVL BIT(0) +#define FUSB_REG_FIFOS 0x43 + +/* Tokens defined for the FUSB302 TX FIFO */ +enum fusb302_txfifo_tokens { + FUSB302_TKN_TXON = 0xA1, + FUSB302_TKN_SYNC1 = 0x12, + FUSB302_TKN_SYNC2 = 0x13, + FUSB302_TKN_SYNC3 = 0x1B, + FUSB302_TKN_RST1 = 0x15, + FUSB302_TKN_RST2 = 0x16, + FUSB302_TKN_PACKSYM = 0x80, + FUSB302_TKN_JAMCRC = 0xFF, + FUSB302_TKN_EOP = 0x14, + FUSB302_TKN_TXOFF = 0xFE, +}; + +#endif diff --git a/drivers/usb/typec/tcpm/maxim_contaminant.c b/drivers/usb/typec/tcpm/maxim_contaminant.c new file mode 100644 index 000000000..af8da6dc6 --- /dev/null +++ b/drivers/usb/typec/tcpm/maxim_contaminant.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 Google, Inc + * + * USB-C module to reduce wakeups due to contaminants. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tcpci_maxim.h" + +enum fladc_select { + CC1_SCALE1 = 1, + CC1_SCALE2, + CC2_SCALE1, + CC2_SCALE2, + SBU1, + SBU2, +}; + +#define FLADC_1uA_LSB_MV 25 +/* High range CC */ +#define FLADC_CC_HIGH_RANGE_LSB_MV 208 +/* Low range CC */ +#define FLADC_CC_LOW_RANGE_LSB_MV 126 + +/* 1uA current source */ +#define FLADC_CC_SCALE1 1 +/* 5 uA current source */ +#define FLADC_CC_SCALE2 5 + +#define FLADC_1uA_CC_OFFSET_MV 300 +#define FLADC_CC_HIGH_RANGE_OFFSET_MV 624 +#define FLADC_CC_LOW_RANGE_OFFSET_MV 378 + +#define CONTAMINANT_THRESHOLD_SBU_K 1000 +#define CONTAMINANT_THRESHOLD_CC_K 1000 + +#define READ1_SLEEP_MS 10 +#define READ2_SLEEP_MS 5 + +#define IS_CC_OPEN(cc_status) \ + (FIELD_GET(TCPC_CC_STATUS_CC1, cc_status) == TCPC_CC_STATE_SRC_OPEN \ + && FIELD_GET(TCPC_CC_STATUS_CC2, cc_status) == TCPC_CC_STATE_SRC_OPEN) + +static int max_contaminant_adc_to_mv(struct max_tcpci_chip *chip, enum fladc_select channel, + bool ua_src, u8 fladc) +{ + /* SBU channels only have 1 scale with 1uA. */ + if ((ua_src && (channel == CC1_SCALE2 || channel == CC2_SCALE2 || channel == SBU1 || + channel == SBU2))) + /* Mean of range */ + return FLADC_1uA_CC_OFFSET_MV + (fladc * FLADC_1uA_LSB_MV); + else if (!ua_src && (channel == CC1_SCALE1 || channel == CC2_SCALE1)) + return FLADC_CC_HIGH_RANGE_OFFSET_MV + (fladc * FLADC_CC_HIGH_RANGE_LSB_MV); + else if (!ua_src && (channel == CC1_SCALE2 || channel == CC2_SCALE2)) + return FLADC_CC_LOW_RANGE_OFFSET_MV + (fladc * FLADC_CC_LOW_RANGE_LSB_MV); + + dev_err_once(chip->dev, "ADC ERROR: SCALE UNKNOWN"); + + return -EINVAL; +} + +static int max_contaminant_read_adc_mv(struct max_tcpci_chip *chip, enum fladc_select channel, + int sleep_msec, bool raw, bool ua_src) +{ + struct regmap *regmap = chip->data.regmap; + u8 fladc; + int ret; + + /* Channel & scale select */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCINSEL, + FIELD_PREP(ADCINSEL, channel)); + if (ret < 0) + return ret; + + /* Enable ADC */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCEN, ADCEN); + if (ret < 0) + return ret; + + usleep_range(sleep_msec * 1000, (sleep_msec + 1) * 1000); + ret = max_tcpci_read8(chip, TCPC_VENDOR_FLADC_STATUS, &fladc); + if (ret < 0) + return ret; + + /* Disable ADC */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCEN, 0); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCINSEL, + FIELD_PREP(ADCINSEL, 0)); + if (ret < 0) + return ret; + + if (!raw) + return max_contaminant_adc_to_mv(chip, channel, ua_src, fladc); + else + return fladc; +} + +static int max_contaminant_read_resistance_kohm(struct max_tcpci_chip *chip, + enum fladc_select channel, int sleep_msec, bool raw) +{ + struct regmap *regmap = chip->data.regmap; + int mv; + int ret; + + if (channel == CC1_SCALE1 || channel == CC2_SCALE1 || channel == CC1_SCALE2 || + channel == CC2_SCALE2) { + /* Enable 1uA current source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCLPMODESEL, + FIELD_PREP(CCLPMODESEL, ULTRA_LOW_POWER_MODE)); + if (ret < 0) + return ret; + + /* Enable 1uA current source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL, + FIELD_PREP(CCRPCTRL, UA_1_SRC)); + if (ret < 0) + return ret; + + /* OVP disable */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCOVPDIS, CCOVPDIS); + if (ret < 0) + return ret; + + mv = max_contaminant_read_adc_mv(chip, channel, sleep_msec, raw, true); + if (mv < 0) + return mv; + + /* OVP enable */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCOVPDIS, 0); + if (ret < 0) + return ret; + /* returns KOhm as 1uA source is used. */ + return mv; + } + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBUOVPDIS, SBUOVPDIS); + if (ret < 0) + return ret; + + /* SBU switches auto configure when channel is selected. */ + /* Enable 1ua current source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBURPCTRL, SBURPCTRL); + if (ret < 0) + return ret; + + mv = max_contaminant_read_adc_mv(chip, channel, sleep_msec, raw, true); + if (mv < 0) + return mv; + /* Disable current source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBURPCTRL, 0); + if (ret < 0) + return ret; + + /* OVP disable */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBUOVPDIS, 0); + if (ret < 0) + return ret; + + return mv; +} + +static int max_contaminant_read_comparators(struct max_tcpci_chip *chip, u8 *vendor_cc_status2_cc1, + u8 *vendor_cc_status2_cc2) +{ + struct regmap *regmap = chip->data.regmap; + int ret; + + /* Enable 80uA source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL, + FIELD_PREP(CCRPCTRL, UA_80_SRC)); + if (ret < 0) + return ret; + + /* Enable comparators */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCOMPEN, CCCOMPEN); + if (ret < 0) + return ret; + + /* Disable low power mode */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCLPMODESEL, + FIELD_PREP(CCLPMODESEL, + LOW_POWER_MODE_DISABLE)); + + /* Sleep to allow comparators settle */ + usleep_range(5000, 6000); + ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_ORIENTATION, PLUG_ORNT_CC1); + if (ret < 0) + return ret; + + usleep_range(5000, 6000); + ret = max_tcpci_read8(chip, VENDOR_CC_STATUS2, vendor_cc_status2_cc1); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_ORIENTATION, PLUG_ORNT_CC2); + if (ret < 0) + return ret; + + usleep_range(5000, 6000); + ret = max_tcpci_read8(chip, VENDOR_CC_STATUS2, vendor_cc_status2_cc2); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCOMPEN, 0); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL, + FIELD_PREP(CCRPCTRL, 0)); + if (ret < 0) + return ret; + + return 0; +} + +static int max_contaminant_detect_contaminant(struct max_tcpci_chip *chip) +{ + int cc1_k, cc2_k, sbu1_k, sbu2_k, ret; + u8 vendor_cc_status2_cc1 = 0xff, vendor_cc_status2_cc2 = 0xff; + u8 role_ctrl = 0, role_ctrl_backup = 0; + int inferred_state = NOT_DETECTED; + + ret = max_tcpci_read8(chip, TCPC_ROLE_CTRL, &role_ctrl); + if (ret < 0) + return NOT_DETECTED; + + role_ctrl_backup = role_ctrl; + role_ctrl = 0x0F; + ret = max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl); + if (ret < 0) + return NOT_DETECTED; + + cc1_k = max_contaminant_read_resistance_kohm(chip, CC1_SCALE2, READ1_SLEEP_MS, false); + if (cc1_k < 0) + goto exit; + + cc2_k = max_contaminant_read_resistance_kohm(chip, CC2_SCALE2, READ2_SLEEP_MS, false); + if (cc2_k < 0) + goto exit; + + sbu1_k = max_contaminant_read_resistance_kohm(chip, SBU1, READ1_SLEEP_MS, false); + if (sbu1_k < 0) + goto exit; + + sbu2_k = max_contaminant_read_resistance_kohm(chip, SBU2, READ2_SLEEP_MS, false); + if (sbu2_k < 0) + goto exit; + + ret = max_contaminant_read_comparators(chip, &vendor_cc_status2_cc1, + &vendor_cc_status2_cc2); + + if (ret < 0) + goto exit; + + if ((!(CC1_VUFP_RD0P5 & vendor_cc_status2_cc1) || + !(CC2_VUFP_RD0P5 & vendor_cc_status2_cc2)) && + !(CC1_VUFP_RD0P5 & vendor_cc_status2_cc1 && CC2_VUFP_RD0P5 & vendor_cc_status2_cc2)) + inferred_state = SINK; + else if ((cc1_k < CONTAMINANT_THRESHOLD_CC_K || cc2_k < CONTAMINANT_THRESHOLD_CC_K) && + (sbu1_k < CONTAMINANT_THRESHOLD_SBU_K || sbu2_k < CONTAMINANT_THRESHOLD_SBU_K)) + inferred_state = DETECTED; + + if (inferred_state == NOT_DETECTED) + max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl_backup); + else + max_tcpci_write8(chip, TCPC_ROLE_CTRL, (TCPC_ROLE_CTRL_DRP | 0xA)); + + return inferred_state; +exit: + max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl_backup); + return NOT_DETECTED; +} + +static int max_contaminant_enable_dry_detection(struct max_tcpci_chip *chip) +{ + struct regmap *regmap = chip->data.regmap; + u8 temp; + int ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL3, + CCWTRDEB | CCWTRSEL | WTRCYCLE, + FIELD_PREP(CCWTRDEB, CCWTRDEB_1MS) + | FIELD_PREP(CCWTRSEL, CCWTRSEL_1V) + | FIELD_PREP(WTRCYCLE, WTRCYCLE_4_8_S)); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_ROLE_CTRL, TCPC_ROLE_CTRL_DRP, TCPC_ROLE_CTRL_DRP); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCONNDRY, CCCONNDRY); + if (ret < 0) + return ret; + ret = max_tcpci_read8(chip, TCPC_VENDOR_CC_CTRL1, &temp); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCLPMODESEL, + FIELD_PREP(CCLPMODESEL, + ULTRA_LOW_POWER_MODE)); + if (ret < 0) + return ret; + ret = max_tcpci_read8(chip, TCPC_VENDOR_CC_CTRL2, &temp); + if (ret < 0) + return ret; + + /* Enable Look4Connection before sending the command */ + ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_EN_LK4CONN_ALRT, + TCPC_TCPC_CTRL_EN_LK4CONN_ALRT); + if (ret < 0) + return ret; + + ret = max_tcpci_write8(chip, TCPC_COMMAND, TCPC_CMD_LOOK4CONNECTION); + if (ret < 0) + return ret; + return 0; +} + +static int max_contaminant_enable_toggling(struct max_tcpci_chip *chip) +{ + struct regmap *regmap = chip->data.regmap; + int ret; + + /* Disable dry detection if enabled. */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCLPMODESEL, + FIELD_PREP(CCLPMODESEL, + LOW_POWER_MODE_DISABLE)); + if (ret) + return ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCONNDRY, 0); + if (ret) + return ret; + + ret = max_tcpci_write8(chip, TCPC_ROLE_CTRL, TCPC_ROLE_CTRL_DRP | + FIELD_PREP(TCPC_ROLE_CTRL_CC1, + TCPC_ROLE_CTRL_CC_RD) | + FIELD_PREP(TCPC_ROLE_CTRL_CC2, + TCPC_ROLE_CTRL_CC_RD)); + if (ret) + return ret; + + ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, + TCPC_TCPC_CTRL_EN_LK4CONN_ALRT, + TCPC_TCPC_CTRL_EN_LK4CONN_ALRT); + if (ret) + return ret; + + return max_tcpci_write8(chip, TCPC_COMMAND, TCPC_CMD_LOOK4CONNECTION); +} + +bool max_contaminant_is_contaminant(struct max_tcpci_chip *chip, bool disconnect_while_debounce, + bool *cc_handled) +{ + u8 cc_status, pwr_cntl; + int ret; + + *cc_handled = true; + + ret = max_tcpci_read8(chip, TCPC_CC_STATUS, &cc_status); + if (ret < 0) + return false; + + ret = max_tcpci_read8(chip, TCPC_POWER_CTRL, &pwr_cntl); + if (ret < 0) + return false; + + if (cc_status & TCPC_CC_STATUS_TOGGLING) { + if (chip->contaminant_state == DETECTED) + return true; + return false; + } + + if (chip->contaminant_state == NOT_DETECTED || chip->contaminant_state == SINK) { + if (!disconnect_while_debounce) + msleep(100); + + ret = max_tcpci_read8(chip, TCPC_CC_STATUS, &cc_status); + if (ret < 0) + return false; + + if (IS_CC_OPEN(cc_status)) { + u8 role_ctrl, role_ctrl_backup; + + ret = max_tcpci_read8(chip, TCPC_ROLE_CTRL, &role_ctrl); + if (ret < 0) + return false; + + role_ctrl_backup = role_ctrl; + role_ctrl |= 0x0F; + role_ctrl &= ~(TCPC_ROLE_CTRL_DRP); + ret = max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl); + if (ret < 0) + return false; + + chip->contaminant_state = max_contaminant_detect_contaminant(chip); + + ret = max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl_backup); + if (ret < 0) + return false; + + if (chip->contaminant_state == DETECTED) { + max_contaminant_enable_dry_detection(chip); + return true; + } + + ret = max_contaminant_enable_toggling(chip); + if (ret) + dev_err(chip->dev, + "Failed to enable toggling, ret=%d", + ret); + } + } else if (chip->contaminant_state == DETECTED) { + if (!(cc_status & TCPC_CC_STATUS_TOGGLING)) { + chip->contaminant_state = max_contaminant_detect_contaminant(chip); + if (chip->contaminant_state == DETECTED) { + max_contaminant_enable_dry_detection(chip); + return true; + } else { + ret = max_contaminant_enable_toggling(chip); + if (ret) { + dev_err(chip->dev, + "Failed to enable toggling, ret=%d", + ret); + return true; + } + } + } + } + + *cc_handled = false; + return false; +} + +MODULE_DESCRIPTION("MAXIM TCPC CONTAMINANT Module"); +MODULE_AUTHOR("Badhri Jagan Sridharan "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/qcom/Makefile b/drivers/usb/typec/tcpm/qcom/Makefile new file mode 100644 index 000000000..cc23042b9 --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom_pmic_tcpm.o +qcom_pmic_tcpm-y += qcom_pmic_typec.o \ + qcom_pmic_typec_port.o \ + qcom_pmic_typec_pdphy.o \ + qcom_pmic_typec_pdphy_stub.o \ diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c new file mode 100644 index 000000000..d770e58bc --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023, Linaro Ltd. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "qcom_pmic_typec.h" +#include "qcom_pmic_typec_pdphy.h" +#include "qcom_pmic_typec_port.h" + +struct pmic_typec_resources { + const struct pmic_typec_pdphy_resources *pdphy_res; + const struct pmic_typec_port_resources *port_res; +}; + +static int qcom_pmic_typec_init(struct tcpc_dev *tcpc) +{ + return 0; +} + +static int qcom_pmic_typec_probe(struct platform_device *pdev) +{ + struct pmic_typec *tcpm; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + const struct pmic_typec_resources *res; + struct regmap *regmap; + struct auxiliary_device *bridge_dev; + u32 base; + int ret; + + res = of_device_get_match_data(dev); + if (!res) + return -ENODEV; + + tcpm = devm_kzalloc(dev, sizeof(*tcpm), GFP_KERNEL); + if (!tcpm) + return -ENOMEM; + + tcpm->dev = dev; + tcpm->tcpc.init = qcom_pmic_typec_init; + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) { + dev_err(dev, "Failed to get regmap\n"); + return -ENODEV; + } + + ret = of_property_read_u32_index(np, "reg", 0, &base); + if (ret) + return ret; + + ret = qcom_pmic_typec_port_probe(pdev, tcpm, + res->port_res, regmap, base); + if (ret) + return ret; + + if (res->pdphy_res) { + ret = of_property_read_u32_index(np, "reg", 1, &base); + if (ret) + return ret; + + ret = qcom_pmic_typec_pdphy_probe(pdev, tcpm, + res->pdphy_res, regmap, base); + if (ret) + return ret; + } else { + ret = qcom_pmic_typec_pdphy_stub_probe(pdev, tcpm); + if (ret) + return ret; + } + + platform_set_drvdata(pdev, tcpm); + + tcpm->tcpc.fwnode = device_get_named_child_node(tcpm->dev, "connector"); + if (!tcpm->tcpc.fwnode) + return -EINVAL; + + bridge_dev = devm_drm_dp_hpd_bridge_alloc(tcpm->dev, to_of_node(tcpm->tcpc.fwnode)); + if (IS_ERR(bridge_dev)) { + ret = PTR_ERR(bridge_dev); + goto fwnode_remove; + } + + tcpm->tcpm_port = tcpm_register_port(tcpm->dev, &tcpm->tcpc); + if (IS_ERR(tcpm->tcpm_port)) { + ret = PTR_ERR(tcpm->tcpm_port); + goto fwnode_remove; + } + + ret = tcpm->port_start(tcpm, tcpm->tcpm_port); + if (ret) + goto port_unregister; + + ret = tcpm->pdphy_start(tcpm, tcpm->tcpm_port); + if (ret) + goto port_stop; + + ret = devm_drm_dp_hpd_bridge_add(tcpm->dev, bridge_dev); + if (ret) + goto pdphy_stop; + + return 0; + +pdphy_stop: + tcpm->pdphy_stop(tcpm); +port_stop: + tcpm->port_stop(tcpm); +port_unregister: + tcpm_unregister_port(tcpm->tcpm_port); +fwnode_remove: + fwnode_handle_put(tcpm->tcpc.fwnode); + + return ret; +} + +static void qcom_pmic_typec_remove(struct platform_device *pdev) +{ + struct pmic_typec *tcpm = platform_get_drvdata(pdev); + + tcpm->pdphy_stop(tcpm); + tcpm->port_stop(tcpm); + tcpm_unregister_port(tcpm->tcpm_port); + fwnode_handle_put(tcpm->tcpc.fwnode); +} + +static const struct pmic_typec_resources pm8150b_typec_res = { + .pdphy_res = &pm8150b_pdphy_res, + .port_res = &pm8150b_port_res, +}; + +static const struct pmic_typec_resources pmi632_typec_res = { + /* PD PHY not present */ + .port_res = &pm8150b_port_res, +}; + +static const struct of_device_id qcom_pmic_typec_table[] = { + { .compatible = "qcom,pm8150b-typec", .data = &pm8150b_typec_res }, + { .compatible = "qcom,pmi632-typec", .data = &pmi632_typec_res }, + { } +}; +MODULE_DEVICE_TABLE(of, qcom_pmic_typec_table); + +static struct platform_driver qcom_pmic_typec_driver = { + .driver = { + .name = "qcom,pmic-typec", + .of_match_table = qcom_pmic_typec_table, + }, + .probe = qcom_pmic_typec_probe, + .remove = qcom_pmic_typec_remove, +}; + +module_platform_driver(qcom_pmic_typec_driver); + +MODULE_DESCRIPTION("QCOM PMIC USB Type-C Port Manager Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.h b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.h new file mode 100644 index 000000000..3c75820c9 --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023, Linaro Ltd. All rights reserved. + */ + +#ifndef __QCOM_PMIC_TYPEC_H__ +#define __QCOM_PMIC_TYPEC_H__ + +struct pmic_typec { + struct device *dev; + struct tcpm_port *tcpm_port; + struct tcpc_dev tcpc; + struct pmic_typec_pdphy *pmic_typec_pdphy; + struct pmic_typec_port *pmic_typec_port; + + int (*pdphy_start)(struct pmic_typec *tcpm, + struct tcpm_port *tcpm_port); + void (*pdphy_stop)(struct pmic_typec *tcpm); + + int (*port_start)(struct pmic_typec *tcpm, + struct tcpm_port *tcpm_port); + void (*port_stop)(struct pmic_typec *tcpm); +}; + +#define tcpc_to_tcpm(_tcpc_) container_of(_tcpc_, struct pmic_typec, tcpc) + +#endif diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c new file mode 100644 index 000000000..e6b28648f --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c @@ -0,0 +1,648 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023, Linaro Ltd. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qcom_pmic_typec.h" +#include "qcom_pmic_typec_pdphy.h" + +/* PD PHY register offsets and bit fields */ +#define USB_PDPHY_MSG_CONFIG_REG 0x40 +#define MSG_CONFIG_PORT_DATA_ROLE BIT(3) +#define MSG_CONFIG_PORT_POWER_ROLE BIT(2) +#define MSG_CONFIG_SPEC_REV_MASK (BIT(1) | BIT(0)) + +#define USB_PDPHY_EN_CONTROL_REG 0x46 +#define CONTROL_ENABLE BIT(0) + +#define USB_PDPHY_RX_STATUS_REG 0x4A +#define RX_FRAME_TYPE (BIT(0) | BIT(1) | BIT(2)) + +#define USB_PDPHY_FRAME_FILTER_REG 0x4C +#define FRAME_FILTER_EN_HARD_RESET BIT(5) +#define FRAME_FILTER_EN_SOP BIT(0) + +#define USB_PDPHY_TX_SIZE_REG 0x42 +#define TX_SIZE_MASK 0xF + +#define USB_PDPHY_TX_CONTROL_REG 0x44 +#define TX_CONTROL_RETRY_COUNT(n) (((n) & 0x3) << 5) +#define TX_CONTROL_FRAME_TYPE(n) (((n) & 0x7) << 2) +#define TX_CONTROL_FRAME_TYPE_CABLE_RESET (0x1 << 2) +#define TX_CONTROL_SEND_SIGNAL BIT(1) +#define TX_CONTROL_SEND_MSG BIT(0) + +#define USB_PDPHY_RX_SIZE_REG 0x48 + +#define USB_PDPHY_RX_ACKNOWLEDGE_REG 0x4B +#define RX_BUFFER_TOKEN BIT(0) + +#define USB_PDPHY_BIST_MODE_REG 0x4E +#define BIST_MODE_MASK 0xF +#define BIST_ENABLE BIT(7) +#define PD_MSG_BIST 0x3 +#define PD_BIST_TEST_DATA_MODE 0x8 + +#define USB_PDPHY_TX_BUFFER_HDR_REG 0x60 +#define USB_PDPHY_TX_BUFFER_DATA_REG 0x62 + +#define USB_PDPHY_RX_BUFFER_REG 0x80 + +/* VDD regulator */ +#define VDD_PDPHY_VOL_MIN 2800000 /* uV */ +#define VDD_PDPHY_VOL_MAX 3300000 /* uV */ +#define VDD_PDPHY_HPM_LOAD 3000 /* uA */ + +/* Message Spec Rev field */ +#define PD_MSG_HDR_REV(hdr) (((hdr) >> 6) & 3) + +/* timers */ +#define RECEIVER_RESPONSE_TIME 15 /* tReceiverResponse */ +#define HARD_RESET_COMPLETE_TIME 5 /* tHardResetComplete */ + +/* Interrupt numbers */ +#define PMIC_PDPHY_SIG_TX_IRQ 0x0 +#define PMIC_PDPHY_SIG_RX_IRQ 0x1 +#define PMIC_PDPHY_MSG_TX_IRQ 0x2 +#define PMIC_PDPHY_MSG_RX_IRQ 0x3 +#define PMIC_PDPHY_MSG_TX_FAIL_IRQ 0x4 +#define PMIC_PDPHY_MSG_TX_DISCARD_IRQ 0x5 +#define PMIC_PDPHY_MSG_RX_DISCARD_IRQ 0x6 +#define PMIC_PDPHY_FR_SWAP_IRQ 0x7 + + +struct pmic_typec_pdphy_irq_data { + int virq; + int irq; + struct pmic_typec_pdphy *pmic_typec_pdphy; +}; + +struct pmic_typec_pdphy { + struct device *dev; + struct tcpm_port *tcpm_port; + struct regmap *regmap; + u32 base; + + unsigned int nr_irqs; + struct pmic_typec_pdphy_irq_data *irq_data; + + struct work_struct reset_work; + struct work_struct receive_work; + struct regulator *vdd_pdphy; + spinlock_t lock; /* Register atomicity */ +}; + +static void qcom_pmic_typec_pdphy_reset_on(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + struct device *dev = pmic_typec_pdphy->dev; + int ret; + + /* Terminate TX */ + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_CONTROL_REG, 0); + if (ret) + goto err; + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_FRAME_FILTER_REG, 0); + if (ret) + goto err; + + return; +err: + dev_err(dev, "pd_reset_on error\n"); +} + +static void qcom_pmic_typec_pdphy_reset_off(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + struct device *dev = pmic_typec_pdphy->dev; + int ret; + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_FRAME_FILTER_REG, + FRAME_FILTER_EN_SOP | FRAME_FILTER_EN_HARD_RESET); + if (ret) + dev_err(dev, "pd_reset_off error\n"); +} + +static void qcom_pmic_typec_pdphy_sig_reset_work(struct work_struct *work) +{ + struct pmic_typec_pdphy *pmic_typec_pdphy = container_of(work, struct pmic_typec_pdphy, + reset_work); + unsigned long flags; + + spin_lock_irqsave(&pmic_typec_pdphy->lock, flags); + + qcom_pmic_typec_pdphy_reset_on(pmic_typec_pdphy); + qcom_pmic_typec_pdphy_reset_off(pmic_typec_pdphy); + + spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags); + + tcpm_pd_hard_reset(pmic_typec_pdphy->tcpm_port); +} + +static int +qcom_pmic_typec_pdphy_clear_tx_control_reg(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + struct device *dev = pmic_typec_pdphy->dev; + unsigned int val; + int ret; + + /* Clear TX control register */ + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_CONTROL_REG, 0); + if (ret) + goto done; + + /* Perform readback to ensure sufficient delay for command to latch */ + ret = regmap_read(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_CONTROL_REG, &val); + +done: + if (ret) + dev_err(dev, "pd_clear_tx_control_reg: clear tx flag\n"); + + return ret; +} + +static int +qcom_pmic_typec_pdphy_pd_transmit_signal(struct pmic_typec_pdphy *pmic_typec_pdphy, + enum tcpm_transmit_type type, + unsigned int negotiated_rev) +{ + struct device *dev = pmic_typec_pdphy->dev; + unsigned int val; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_pdphy->lock, flags); + + /* Clear TX control register */ + ret = qcom_pmic_typec_pdphy_clear_tx_control_reg(pmic_typec_pdphy); + if (ret) + goto done; + + val = TX_CONTROL_SEND_SIGNAL; + if (negotiated_rev == PD_REV30) + val |= TX_CONTROL_RETRY_COUNT(2); + else + val |= TX_CONTROL_RETRY_COUNT(3); + + if (type == TCPC_TX_CABLE_RESET || type == TCPC_TX_HARD_RESET) + val |= TX_CONTROL_FRAME_TYPE(1); + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_CONTROL_REG, val); + +done: + spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags); + + dev_vdbg(dev, "pd_transmit_signal: type %d negotiate_rev %d send %d\n", + type, negotiated_rev, ret); + + return ret; +} + +static int +qcom_pmic_typec_pdphy_pd_transmit_payload(struct pmic_typec_pdphy *pmic_typec_pdphy, + enum tcpm_transmit_type type, + const struct pd_message *msg, + unsigned int negotiated_rev) +{ + struct device *dev = pmic_typec_pdphy->dev; + unsigned int val, hdr_len, txbuf_len, txsize_len; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_pdphy->lock, flags); + + hdr_len = sizeof(msg->header); + txbuf_len = pd_header_cnt_le(msg->header) * 4; + txsize_len = hdr_len + txbuf_len - 1; + + ret = regmap_read(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_RX_ACKNOWLEDGE_REG, + &val); + if (ret) + goto done; + + if (val) { + dev_err(dev, "pd_transmit_payload: RX message pending\n"); + ret = -EBUSY; + goto done; + } + + /* Clear TX control register */ + ret = qcom_pmic_typec_pdphy_clear_tx_control_reg(pmic_typec_pdphy); + if (ret) + goto done; + + /* Write message header sizeof(u16) to USB_PDPHY_TX_BUFFER_HDR_REG */ + ret = regmap_bulk_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_BUFFER_HDR_REG, + &msg->header, hdr_len); + if (ret) + goto done; + + /* Write payload to USB_PDPHY_TX_BUFFER_DATA_REG for txbuf_len */ + if (txbuf_len) { + ret = regmap_bulk_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_BUFFER_DATA_REG, + &msg->payload, txbuf_len); + if (ret) + goto done; + } + + /* Write total length ((header + data) - 1) to USB_PDPHY_TX_SIZE_REG */ + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_SIZE_REG, + txsize_len); + if (ret) + goto done; + + /* Clear TX control register */ + ret = qcom_pmic_typec_pdphy_clear_tx_control_reg(pmic_typec_pdphy); + if (ret) + goto done; + + /* Initiate transmit with retry count as indicated by PD revision */ + val = TX_CONTROL_FRAME_TYPE(type) | TX_CONTROL_SEND_MSG; + if (pd_header_rev(msg->header) == PD_REV30) + val |= TX_CONTROL_RETRY_COUNT(2); + else + val |= TX_CONTROL_RETRY_COUNT(3); + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_TX_CONTROL_REG, val); + +done: + spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags); + + if (ret) { + dev_err(dev, "pd_transmit_payload: hdr %*ph data %*ph ret %d\n", + hdr_len, &msg->header, txbuf_len, &msg->payload, ret); + } + + return ret; +} + +static int qcom_pmic_typec_pdphy_pd_transmit(struct tcpc_dev *tcpc, + enum tcpm_transmit_type type, + const struct pd_message *msg, + unsigned int negotiated_rev) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; + struct device *dev = pmic_typec_pdphy->dev; + int ret; + + if (msg) { + ret = qcom_pmic_typec_pdphy_pd_transmit_payload(pmic_typec_pdphy, + type, msg, + negotiated_rev); + } else { + ret = qcom_pmic_typec_pdphy_pd_transmit_signal(pmic_typec_pdphy, + type, + negotiated_rev); + } + + if (ret) + dev_dbg(dev, "pd_transmit: type %x result %d\n", type, ret); + + return ret; +} + +static void qcom_pmic_typec_pdphy_pd_receive(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + struct device *dev = pmic_typec_pdphy->dev; + struct pd_message msg; + unsigned int size, rx_status; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_pdphy->lock, flags); + + ret = regmap_read(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_RX_SIZE_REG, &size); + if (ret) + goto done; + + /* Hardware requires +1 of the real read value to be passed */ + if (size < 1 || size > sizeof(msg.payload) + 1) { + dev_dbg(dev, "pd_receive: invalid size %d\n", size); + goto done; + } + + size += 1; + ret = regmap_read(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_RX_STATUS_REG, + &rx_status); + + if (ret) + goto done; + + ret = regmap_bulk_read(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_RX_BUFFER_REG, + (u8 *)&msg, size); + if (ret) + goto done; + + /* Return ownership of RX buffer to hardware */ + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_RX_ACKNOWLEDGE_REG, 0); + +done: + spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags); + + if (!ret) { + dev_vdbg(dev, "pd_receive: handing %d bytes to tcpm\n", size); + tcpm_pd_receive(pmic_typec_pdphy->tcpm_port, &msg, TCPC_TX_SOP); + } +} + +static irqreturn_t qcom_pmic_typec_pdphy_isr(int irq, void *dev_id) +{ + struct pmic_typec_pdphy_irq_data *irq_data = dev_id; + struct pmic_typec_pdphy *pmic_typec_pdphy = irq_data->pmic_typec_pdphy; + struct device *dev = pmic_typec_pdphy->dev; + + switch (irq_data->virq) { + case PMIC_PDPHY_SIG_TX_IRQ: + dev_err(dev, "isr: tx_sig\n"); + break; + case PMIC_PDPHY_SIG_RX_IRQ: + schedule_work(&pmic_typec_pdphy->reset_work); + break; + case PMIC_PDPHY_MSG_TX_IRQ: + tcpm_pd_transmit_complete(pmic_typec_pdphy->tcpm_port, + TCPC_TX_SUCCESS); + break; + case PMIC_PDPHY_MSG_RX_IRQ: + qcom_pmic_typec_pdphy_pd_receive(pmic_typec_pdphy); + break; + case PMIC_PDPHY_MSG_TX_FAIL_IRQ: + tcpm_pd_transmit_complete(pmic_typec_pdphy->tcpm_port, + TCPC_TX_FAILED); + break; + case PMIC_PDPHY_MSG_TX_DISCARD_IRQ: + tcpm_pd_transmit_complete(pmic_typec_pdphy->tcpm_port, + TCPC_TX_DISCARDED); + break; + } + + return IRQ_HANDLED; +} + +static int qcom_pmic_typec_pdphy_set_pd_rx(struct tcpc_dev *tcpc, bool on) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_pdphy->lock, flags); + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_RX_ACKNOWLEDGE_REG, !on); + + spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags); + + dev_dbg(pmic_typec_pdphy->dev, "set_pd_rx: %s\n", str_on_off(on)); + + return ret; +} + +static int qcom_pmic_typec_pdphy_set_roles(struct tcpc_dev *tcpc, bool attached, + enum typec_role power_role, + enum typec_data_role data_role) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; + struct device *dev = pmic_typec_pdphy->dev; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_pdphy->lock, flags); + + ret = regmap_update_bits(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_MSG_CONFIG_REG, + MSG_CONFIG_PORT_DATA_ROLE | + MSG_CONFIG_PORT_POWER_ROLE, + (data_role == TYPEC_HOST ? MSG_CONFIG_PORT_DATA_ROLE : 0) | + (power_role == TYPEC_SOURCE ? MSG_CONFIG_PORT_POWER_ROLE : 0)); + + spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags); + + dev_dbg(dev, "pdphy_set_roles: data_role_host=%d power_role_src=%d\n", + data_role, power_role); + + return ret; +} + +static int qcom_pmic_typec_pdphy_enable(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + struct device *dev = pmic_typec_pdphy->dev; + int ret; + + /* PD 2.0, DR=TYPEC_DEVICE, PR=TYPEC_SINK */ + ret = regmap_update_bits(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_MSG_CONFIG_REG, + MSG_CONFIG_SPEC_REV_MASK, PD_REV20); + if (ret) + goto done; + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_EN_CONTROL_REG, 0); + if (ret) + goto done; + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_EN_CONTROL_REG, + CONTROL_ENABLE); + if (ret) + goto done; + + qcom_pmic_typec_pdphy_reset_off(pmic_typec_pdphy); +done: + if (ret) + dev_err(dev, "pdphy_enable fail %d\n", ret); + + return ret; +} + +static int qcom_pmic_typec_pdphy_disable(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + int ret; + + qcom_pmic_typec_pdphy_reset_on(pmic_typec_pdphy); + + ret = regmap_write(pmic_typec_pdphy->regmap, + pmic_typec_pdphy->base + USB_PDPHY_EN_CONTROL_REG, 0); + + return ret; +} + +static int pmic_typec_pdphy_reset(struct pmic_typec_pdphy *pmic_typec_pdphy) +{ + int ret; + + ret = qcom_pmic_typec_pdphy_disable(pmic_typec_pdphy); + if (ret) + goto done; + + usleep_range(400, 500); + ret = qcom_pmic_typec_pdphy_enable(pmic_typec_pdphy); +done: + return ret; +} + +static int qcom_pmic_typec_pdphy_start(struct pmic_typec *tcpm, + struct tcpm_port *tcpm_port) +{ + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; + int i; + int ret; + + ret = regulator_enable(pmic_typec_pdphy->vdd_pdphy); + if (ret) + return ret; + + pmic_typec_pdphy->tcpm_port = tcpm_port; + + ret = pmic_typec_pdphy_reset(pmic_typec_pdphy); + if (ret) + goto err_disable_vdd_pdhy; + + for (i = 0; i < pmic_typec_pdphy->nr_irqs; i++) + enable_irq(pmic_typec_pdphy->irq_data[i].irq); + + return 0; + +err_disable_vdd_pdhy: + regulator_disable(pmic_typec_pdphy->vdd_pdphy); + + return ret; +} + +static void qcom_pmic_typec_pdphy_stop(struct pmic_typec *tcpm) +{ + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; + int i; + + for (i = 0; i < pmic_typec_pdphy->nr_irqs; i++) + disable_irq(pmic_typec_pdphy->irq_data[i].irq); + + qcom_pmic_typec_pdphy_reset_on(pmic_typec_pdphy); + + regulator_disable(pmic_typec_pdphy->vdd_pdphy); +} + +int qcom_pmic_typec_pdphy_probe(struct platform_device *pdev, + struct pmic_typec *tcpm, + const struct pmic_typec_pdphy_resources *res, + struct regmap *regmap, + u32 base) +{ + struct pmic_typec_pdphy *pmic_typec_pdphy; + struct device *dev = &pdev->dev; + struct pmic_typec_pdphy_irq_data *irq_data; + int i, ret, irq; + + pmic_typec_pdphy = devm_kzalloc(dev, sizeof(*pmic_typec_pdphy), GFP_KERNEL); + if (!pmic_typec_pdphy) + return -ENOMEM; + + if (!res->nr_irqs || res->nr_irqs > PMIC_PDPHY_MAX_IRQS) + return -EINVAL; + + irq_data = devm_kcalloc(dev, res->nr_irqs, sizeof(*irq_data), + GFP_KERNEL); + if (!irq_data) + return -ENOMEM; + + pmic_typec_pdphy->vdd_pdphy = devm_regulator_get(dev, "vdd-pdphy"); + if (IS_ERR(pmic_typec_pdphy->vdd_pdphy)) + return PTR_ERR(pmic_typec_pdphy->vdd_pdphy); + + pmic_typec_pdphy->dev = dev; + pmic_typec_pdphy->base = base; + pmic_typec_pdphy->regmap = regmap; + pmic_typec_pdphy->nr_irqs = res->nr_irqs; + pmic_typec_pdphy->irq_data = irq_data; + spin_lock_init(&pmic_typec_pdphy->lock); + INIT_WORK(&pmic_typec_pdphy->reset_work, qcom_pmic_typec_pdphy_sig_reset_work); + + for (i = 0; i < res->nr_irqs; i++, irq_data++) { + irq = platform_get_irq_byname(pdev, res->irq_params[i].irq_name); + if (irq < 0) + return irq; + + irq_data->pmic_typec_pdphy = pmic_typec_pdphy; + irq_data->irq = irq; + irq_data->virq = res->irq_params[i].virq; + + ret = devm_request_threaded_irq(dev, irq, NULL, + qcom_pmic_typec_pdphy_isr, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + res->irq_params[i].irq_name, + irq_data); + if (ret) + return ret; + } + + tcpm->pmic_typec_pdphy = pmic_typec_pdphy; + + tcpm->tcpc.set_pd_rx = qcom_pmic_typec_pdphy_set_pd_rx; + tcpm->tcpc.set_roles = qcom_pmic_typec_pdphy_set_roles; + tcpm->tcpc.pd_transmit = qcom_pmic_typec_pdphy_pd_transmit; + + tcpm->pdphy_start = qcom_pmic_typec_pdphy_start; + tcpm->pdphy_stop = qcom_pmic_typec_pdphy_stop; + + return 0; +} + +const struct pmic_typec_pdphy_resources pm8150b_pdphy_res = { + .irq_params = { + { + .virq = PMIC_PDPHY_SIG_TX_IRQ, + .irq_name = "sig-tx", + }, + { + .virq = PMIC_PDPHY_SIG_RX_IRQ, + .irq_name = "sig-rx", + }, + { + .virq = PMIC_PDPHY_MSG_TX_IRQ, + .irq_name = "msg-tx", + }, + { + .virq = PMIC_PDPHY_MSG_RX_IRQ, + .irq_name = "msg-rx", + }, + { + .virq = PMIC_PDPHY_MSG_TX_FAIL_IRQ, + .irq_name = "msg-tx-failed", + }, + { + .virq = PMIC_PDPHY_MSG_TX_DISCARD_IRQ, + .irq_name = "msg-tx-discarded", + }, + { + .virq = PMIC_PDPHY_MSG_RX_DISCARD_IRQ, + .irq_name = "msg-rx-discarded", + }, + }, + .nr_irqs = 7, +}; diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h new file mode 100644 index 000000000..04dee2029 --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. + * Copyright (c) 2023, Linaro Ltd. All rights reserved. + */ +#ifndef __QCOM_PMIC_PDPHY_H__ +#define __QCOM_PMIC_PDPHY_H__ + +#include +#include + +/* Resources */ +#define PMIC_PDPHY_MAX_IRQS 0x08 + +struct pmic_typec_pdphy_irq_params { + int virq; + char *irq_name; +}; + +struct pmic_typec_pdphy_resources { + unsigned int nr_irqs; + const struct pmic_typec_pdphy_irq_params irq_params[PMIC_PDPHY_MAX_IRQS]; +}; + +/* API */ +struct pmic_typec_pdphy; + +extern const struct pmic_typec_pdphy_resources pm8150b_pdphy_res; +int qcom_pmic_typec_pdphy_probe(struct platform_device *pdev, + struct pmic_typec *tcpm, + const struct pmic_typec_pdphy_resources *res, + struct regmap *regmap, + u32 base); +int qcom_pmic_typec_pdphy_stub_probe(struct platform_device *pdev, + struct pmic_typec *tcpm); + +#endif /* __QCOM_PMIC_TYPEC_PDPHY_H__ */ diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy_stub.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy_stub.c new file mode 100644 index 000000000..01b310549 --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy_stub.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2024, Linaro Ltd. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qcom_pmic_typec.h" +#include "qcom_pmic_typec_pdphy.h" + +static int qcom_pmic_typec_pdphy_stub_pd_transmit(struct tcpc_dev *tcpc, + enum tcpm_transmit_type type, + const struct pd_message *msg, + unsigned int negotiated_rev) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct device *dev = tcpm->dev; + + dev_dbg(dev, "pdphy_transmit: type=%d\n", type); + + tcpm_pd_transmit_complete(tcpm->tcpm_port, + TCPC_TX_SUCCESS); + + return 0; +} + +static int qcom_pmic_typec_pdphy_stub_set_pd_rx(struct tcpc_dev *tcpc, bool on) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct device *dev = tcpm->dev; + + dev_dbg(dev, "set_pd_rx: %s\n", str_on_off(on)); + + return 0; +} + +static int qcom_pmic_typec_pdphy_stub_set_roles(struct tcpc_dev *tcpc, bool attached, + enum typec_role power_role, + enum typec_data_role data_role) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct device *dev = tcpm->dev; + + dev_dbg(dev, "pdphy_set_roles: data_role_host=%d power_role_src=%d\n", + data_role, power_role); + + return 0; +} + +static int qcom_pmic_typec_pdphy_stub_start(struct pmic_typec *tcpm, + struct tcpm_port *tcpm_port) +{ + return 0; +} + +static void qcom_pmic_typec_pdphy_stub_stop(struct pmic_typec *tcpm) +{ +} + +int qcom_pmic_typec_pdphy_stub_probe(struct platform_device *pdev, + struct pmic_typec *tcpm) +{ + tcpm->tcpc.set_pd_rx = qcom_pmic_typec_pdphy_stub_set_pd_rx; + tcpm->tcpc.set_roles = qcom_pmic_typec_pdphy_stub_set_roles; + tcpm->tcpc.pd_transmit = qcom_pmic_typec_pdphy_stub_pd_transmit; + + tcpm->pdphy_start = qcom_pmic_typec_pdphy_stub_start; + tcpm->pdphy_stop = qcom_pmic_typec_pdphy_stub_stop; + + return 0; +} diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c new file mode 100644 index 000000000..bf985efe1 --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c @@ -0,0 +1,818 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023, Linaro Ltd. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qcom_pmic_typec.h" +#include "qcom_pmic_typec_port.h" + +#define TYPEC_SNK_STATUS_REG 0x06 +#define DETECTED_SNK_TYPE_MASK GENMASK(6, 0) +#define SNK_DAM_MASK GENMASK(6, 4) +#define SNK_DAM_500MA BIT(6) +#define SNK_DAM_1500MA BIT(5) +#define SNK_DAM_3000MA BIT(4) +#define SNK_RP_STD BIT(3) +#define SNK_RP_1P5 BIT(2) +#define SNK_RP_3P0 BIT(1) +#define SNK_RP_SHORT BIT(0) + +#define TYPEC_SRC_STATUS_REG 0x08 +#define DETECTED_SRC_TYPE_MASK GENMASK(4, 0) +#define SRC_HIGH_BATT BIT(5) +#define SRC_DEBUG_ACCESS BIT(4) +#define SRC_RD_OPEN BIT(3) +#define SRC_RD_RA_VCONN BIT(2) +#define SRC_RA_OPEN BIT(1) +#define AUDIO_ACCESS_RA_RA BIT(0) + +#define TYPEC_STATE_MACHINE_STATUS_REG 0x09 +#define TYPEC_ATTACH_DETACH_STATE BIT(5) + +#define TYPEC_SM_STATUS_REG 0x0A +#define TYPEC_SM_VBUS_VSAFE5V BIT(5) +#define TYPEC_SM_VBUS_VSAFE0V BIT(6) +#define TYPEC_SM_USBIN_LT_LV BIT(7) + +#define TYPEC_MISC_STATUS_REG 0x0B +#define TYPEC_WATER_DETECTION_STATUS BIT(7) +#define SNK_SRC_MODE BIT(6) +#define TYPEC_VBUS_DETECT BIT(5) +#define TYPEC_VBUS_ERROR_STATUS BIT(4) +#define TYPEC_DEBOUNCE_DONE BIT(3) +#define CC_ORIENTATION BIT(1) +#define CC_ATTACHED BIT(0) + +#define LEGACY_CABLE_STATUS_REG 0x0D +#define TYPEC_LEGACY_CABLE_STATUS BIT(1) +#define TYPEC_NONCOMP_LEGACY_CABLE_STATUS BIT(0) + +#define TYPEC_U_USB_STATUS_REG 0x0F +#define U_USB_GROUND_NOVBUS BIT(6) +#define U_USB_GROUND BIT(4) +#define U_USB_FMB1 BIT(3) +#define U_USB_FLOAT1 BIT(2) +#define U_USB_FMB2 BIT(1) +#define U_USB_FLOAT2 BIT(0) + +#define TYPEC_MODE_CFG_REG 0x44 +#define TYPEC_TRY_MODE_MASK GENMASK(4, 3) +#define EN_TRY_SNK BIT(4) +#define EN_TRY_SRC BIT(3) +#define TYPEC_POWER_ROLE_CMD_MASK GENMASK(2, 0) +#define EN_SRC_ONLY BIT(2) +#define EN_SNK_ONLY BIT(1) +#define TYPEC_DISABLE_CMD BIT(0) + +#define TYPEC_VCONN_CONTROL_REG 0x46 +#define VCONN_EN_ORIENTATION BIT(2) +#define VCONN_EN_VALUE BIT(1) +#define VCONN_EN_SRC BIT(0) + +#define TYPEC_CCOUT_CONTROL_REG 0x48 +#define TYPEC_CCOUT_BUFFER_EN BIT(2) +#define TYPEC_CCOUT_VALUE BIT(1) +#define TYPEC_CCOUT_SRC BIT(0) + +#define DEBUG_ACCESS_SRC_CFG_REG 0x4C +#define EN_UNORIENTED_DEBUG_ACCESS_SRC BIT(0) + +#define TYPE_C_CRUDE_SENSOR_CFG_REG 0x4e +#define EN_SRC_CRUDE_SENSOR BIT(1) +#define EN_SNK_CRUDE_SENSOR BIT(0) + +#define TYPEC_EXIT_STATE_CFG_REG 0x50 +#define BYPASS_VSAFE0V_DURING_ROLE_SWAP BIT(3) +#define SEL_SRC_UPPER_REF BIT(2) +#define USE_TPD_FOR_EXITING_ATTACHSRC BIT(1) +#define EXIT_SNK_BASED_ON_CC BIT(0) + +#define TYPEC_CURRSRC_CFG_REG 0x52 +#define TYPEC_SRC_RP_SEL_330UA BIT(1) +#define TYPEC_SRC_RP_SEL_180UA BIT(0) +#define TYPEC_SRC_RP_SEL_80UA 0 +#define TYPEC_SRC_RP_SEL_MASK GENMASK(1, 0) + +#define TYPEC_INTERRUPT_EN_CFG_1_REG 0x5E +#define TYPEC_LEGACY_CABLE_INT_EN BIT(7) +#define TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN BIT(6) +#define TYPEC_TRYSOURCE_DETECT_INT_EN BIT(5) +#define TYPEC_TRYSINK_DETECT_INT_EN BIT(4) +#define TYPEC_CCOUT_DETACH_INT_EN BIT(3) +#define TYPEC_CCOUT_ATTACH_INT_EN BIT(2) +#define TYPEC_VBUS_DEASSERT_INT_EN BIT(1) +#define TYPEC_VBUS_ASSERT_INT_EN BIT(0) + +#define TYPEC_INTERRUPT_EN_CFG_2_REG 0x60 +#define TYPEC_SRC_BATT_HPWR_INT_EN BIT(6) +#define MICRO_USB_STATE_CHANGE_INT_EN BIT(5) +#define TYPEC_STATE_MACHINE_CHANGE_INT_EN BIT(4) +#define TYPEC_DEBUG_ACCESS_DETECT_INT_EN BIT(3) +#define TYPEC_WATER_DETECTION_INT_EN BIT(2) +#define TYPEC_VBUS_ERROR_INT_EN BIT(1) +#define TYPEC_DEBOUNCE_DONE_INT_EN BIT(0) + +#define TYPEC_DEBOUNCE_OPTION_REG 0x62 +#define REDUCE_TCCDEBOUNCE_TO_2MS BIT(2) + +#define TYPE_C_SBU_CFG_REG 0x6A +#define SEL_SBU1_ISRC_VAL 0x04 +#define SEL_SBU2_ISRC_VAL 0x01 + +#define TYPEC_U_USB_CFG_REG 0x70 +#define EN_MICRO_USB_FACTORY_MODE BIT(1) +#define EN_MICRO_USB_MODE BIT(0) + +#define TYPEC_PMI632_U_USB_WATER_PROTECTION_CFG_REG 0x72 + +#define TYPEC_U_USB_WATER_PROTECTION_CFG_REG 0x73 +#define EN_MICRO_USB_WATER_PROTECTION BIT(4) +#define MICRO_USB_DETECTION_ON_TIME_CFG_MASK GENMASK(3, 2) +#define MICRO_USB_DETECTION_PERIOD_CFG_MASK GENMASK(1, 0) + +#define TYPEC_PMI632_MICRO_USB_MODE_REG 0x73 +#define MICRO_USB_MODE_ONLY BIT(0) + +/* Interrupt numbers */ +#define PMIC_TYPEC_OR_RID_IRQ 0x0 +#define PMIC_TYPEC_VPD_IRQ 0x1 +#define PMIC_TYPEC_CC_STATE_IRQ 0x2 +#define PMIC_TYPEC_VCONN_OC_IRQ 0x3 +#define PMIC_TYPEC_VBUS_IRQ 0x4 +#define PMIC_TYPEC_ATTACH_DETACH_IRQ 0x5 +#define PMIC_TYPEC_LEGACY_CABLE_IRQ 0x6 +#define PMIC_TYPEC_TRY_SNK_SRC_IRQ 0x7 + +struct pmic_typec_port_irq_data { + int virq; + int irq; + struct pmic_typec_port *pmic_typec_port; +}; + +struct pmic_typec_port { + struct device *dev; + struct tcpm_port *tcpm_port; + struct regmap *regmap; + u32 base; + unsigned int nr_irqs; + struct pmic_typec_port_irq_data *irq_data; + + struct regulator *vdd_vbus; + bool vbus_enabled; + struct mutex vbus_lock; /* VBUS state serialization */ + + int cc; + bool debouncing_cc; + struct delayed_work cc_debounce_dwork; + + spinlock_t lock; /* Register atomicity */ +}; + +static const char * const typec_cc_status_name[] = { + [TYPEC_CC_OPEN] = "Open", + [TYPEC_CC_RA] = "Ra", + [TYPEC_CC_RD] = "Rd", + [TYPEC_CC_RP_DEF] = "Rp-def", + [TYPEC_CC_RP_1_5] = "Rp-1.5", + [TYPEC_CC_RP_3_0] = "Rp-3.0", +}; + +static const char *rp_unknown = "unknown"; + +static const char *cc_to_name(enum typec_cc_status cc) +{ + if (cc > TYPEC_CC_RP_3_0) + return rp_unknown; + + return typec_cc_status_name[cc]; +} + +static const char * const rp_sel_name[] = { + [TYPEC_SRC_RP_SEL_80UA] = "Rp-def-80uA", + [TYPEC_SRC_RP_SEL_180UA] = "Rp-1.5-180uA", + [TYPEC_SRC_RP_SEL_330UA] = "Rp-3.0-330uA", +}; + +static const char *rp_sel_to_name(int rp_sel) +{ + if (rp_sel > TYPEC_SRC_RP_SEL_330UA) + return rp_unknown; + + return rp_sel_name[rp_sel]; +} + +#define misc_to_cc(msic) !!(misc & CC_ORIENTATION) ? "cc1" : "cc2" +#define misc_to_vconn(msic) !!(misc & CC_ORIENTATION) ? "cc2" : "cc1" + +static void qcom_pmic_typec_port_cc_debounce(struct work_struct *work) +{ + struct pmic_typec_port *pmic_typec_port = + container_of(work, struct pmic_typec_port, cc_debounce_dwork.work); + unsigned long flags; + + spin_lock_irqsave(&pmic_typec_port->lock, flags); + pmic_typec_port->debouncing_cc = false; + spin_unlock_irqrestore(&pmic_typec_port->lock, flags); + + dev_dbg(pmic_typec_port->dev, "Debounce cc complete\n"); +} + +static irqreturn_t pmic_typec_port_isr(int irq, void *dev_id) +{ + struct pmic_typec_port_irq_data *irq_data = dev_id; + struct pmic_typec_port *pmic_typec_port = irq_data->pmic_typec_port; + u32 misc_stat; + bool vbus_change = false; + bool cc_change = false; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_port->lock, flags); + + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MISC_STATUS_REG, + &misc_stat); + if (ret) + goto done; + + switch (irq_data->virq) { + case PMIC_TYPEC_VBUS_IRQ: + vbus_change = true; + break; + case PMIC_TYPEC_CC_STATE_IRQ: + case PMIC_TYPEC_ATTACH_DETACH_IRQ: + if (!pmic_typec_port->debouncing_cc) + cc_change = true; + break; + } + +done: + spin_unlock_irqrestore(&pmic_typec_port->lock, flags); + + if (vbus_change) + tcpm_vbus_change(pmic_typec_port->tcpm_port); + + if (cc_change) + tcpm_cc_change(pmic_typec_port->tcpm_port); + + return IRQ_HANDLED; +} + +static int qcom_pmic_typec_port_vbus_detect(struct pmic_typec_port *pmic_typec_port) +{ + struct device *dev = pmic_typec_port->dev; + unsigned int misc; + int ret; + + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MISC_STATUS_REG, + &misc); + if (ret) + misc = 0; + + dev_dbg(dev, "get_vbus: 0x%08x detect %d\n", misc, !!(misc & TYPEC_VBUS_DETECT)); + + return !!(misc & TYPEC_VBUS_DETECT); +} + +static int qcom_pmic_typec_port_vbus_toggle(struct pmic_typec_port *pmic_typec_port, bool on) +{ + u32 sm_stat; + u32 val; + int ret; + + if (on) { + ret = regulator_enable(pmic_typec_port->vdd_vbus); + if (ret) + return ret; + + val = TYPEC_SM_VBUS_VSAFE5V; + } else { + ret = regulator_disable(pmic_typec_port->vdd_vbus); + if (ret) + return ret; + + val = TYPEC_SM_VBUS_VSAFE0V; + } + + /* Poll waiting for transition to required vSafe5V or vSafe0V */ + ret = regmap_read_poll_timeout(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_SM_STATUS_REG, + sm_stat, sm_stat & val, + 100, 250000); + if (ret) + dev_warn(pmic_typec_port->dev, "vbus vsafe%dv fail\n", on ? 5 : 0); + + return 0; +} + +static int qcom_pmic_typec_port_get_vbus(struct tcpc_dev *tcpc) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; + int ret; + + mutex_lock(&pmic_typec_port->vbus_lock); + ret = pmic_typec_port->vbus_enabled || qcom_pmic_typec_port_vbus_detect(pmic_typec_port); + mutex_unlock(&pmic_typec_port->vbus_lock); + + return ret; +} + +static int qcom_pmic_typec_port_set_vbus(struct tcpc_dev *tcpc, bool on, bool sink) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; + int ret = 0; + + mutex_lock(&pmic_typec_port->vbus_lock); + if (pmic_typec_port->vbus_enabled == on) + goto done; + + ret = qcom_pmic_typec_port_vbus_toggle(pmic_typec_port, on); + if (ret) + goto done; + + pmic_typec_port->vbus_enabled = on; + tcpm_vbus_change(tcpm->tcpm_port); + +done: + dev_dbg(tcpm->dev, "set_vbus set: %d result %d\n", on, ret); + mutex_unlock(&pmic_typec_port->vbus_lock); + + return ret; +} + +static int qcom_pmic_typec_port_get_cc(struct tcpc_dev *tcpc, + enum typec_cc_status *cc1, + enum typec_cc_status *cc2) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; + struct device *dev = pmic_typec_port->dev; + unsigned int misc, val; + bool attached; + int ret = 0; + + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MISC_STATUS_REG, &misc); + if (ret) + goto done; + + attached = !!(misc & CC_ATTACHED); + + if (pmic_typec_port->debouncing_cc) { + ret = -EBUSY; + goto done; + } + + *cc1 = TYPEC_CC_OPEN; + *cc2 = TYPEC_CC_OPEN; + + if (!attached) + goto done; + + if (misc & SNK_SRC_MODE) { + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_SRC_STATUS_REG, + &val); + if (ret) + goto done; + switch (val & DETECTED_SRC_TYPE_MASK) { + case AUDIO_ACCESS_RA_RA: + val = TYPEC_CC_RA; + *cc1 = TYPEC_CC_RA; + *cc2 = TYPEC_CC_RA; + break; + case SRC_RD_OPEN: + val = TYPEC_CC_RD; + break; + case SRC_RD_RA_VCONN: + val = TYPEC_CC_RD; + *cc1 = TYPEC_CC_RA; + *cc2 = TYPEC_CC_RA; + break; + default: + dev_warn(dev, "unexpected src status %.2x\n", val); + val = TYPEC_CC_RD; + break; + } + } else { + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_SNK_STATUS_REG, + &val); + if (ret) + goto done; + switch (val & DETECTED_SNK_TYPE_MASK) { + case SNK_RP_STD: + val = TYPEC_CC_RP_DEF; + break; + case SNK_RP_1P5: + val = TYPEC_CC_RP_1_5; + break; + case SNK_RP_3P0: + val = TYPEC_CC_RP_3_0; + break; + default: + dev_warn(dev, "unexpected snk status %.2x\n", val); + val = TYPEC_CC_RP_DEF; + break; + } + } + + if (misc & CC_ORIENTATION) + *cc2 = val; + else + *cc1 = val; + +done: + dev_dbg(dev, "get_cc: misc 0x%08x cc1 0x%08x %s cc2 0x%08x %s attached %d cc=%s\n", + misc, *cc1, cc_to_name(*cc1), *cc2, cc_to_name(*cc2), attached, + misc_to_cc(misc)); + + return ret; +} + +static void qcom_pmic_set_cc_debounce(struct pmic_typec_port *pmic_typec_port) +{ + pmic_typec_port->debouncing_cc = true; + schedule_delayed_work(&pmic_typec_port->cc_debounce_dwork, + msecs_to_jiffies(2)); +} + +static int qcom_pmic_typec_port_set_cc(struct tcpc_dev *tcpc, + enum typec_cc_status cc) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; + struct device *dev = pmic_typec_port->dev; + unsigned int mode, currsrc; + unsigned int misc; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_port->lock, flags); + + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MISC_STATUS_REG, + &misc); + if (ret) + goto done; + + mode = EN_SRC_ONLY; + + switch (cc) { + case TYPEC_CC_OPEN: + currsrc = TYPEC_SRC_RP_SEL_80UA; + break; + case TYPEC_CC_RP_DEF: + currsrc = TYPEC_SRC_RP_SEL_80UA; + break; + case TYPEC_CC_RP_1_5: + currsrc = TYPEC_SRC_RP_SEL_180UA; + break; + case TYPEC_CC_RP_3_0: + currsrc = TYPEC_SRC_RP_SEL_330UA; + break; + case TYPEC_CC_RD: + currsrc = TYPEC_SRC_RP_SEL_80UA; + mode = EN_SNK_ONLY; + break; + default: + dev_warn(dev, "unexpected set_cc %d\n", cc); + ret = -EINVAL; + goto done; + } + + if (mode == EN_SRC_ONLY) { + ret = regmap_write(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_CURRSRC_CFG_REG, + currsrc); + if (ret) + goto done; + } + + pmic_typec_port->cc = cc; + qcom_pmic_set_cc_debounce(pmic_typec_port); + ret = 0; + +done: + spin_unlock_irqrestore(&pmic_typec_port->lock, flags); + + dev_dbg(dev, "set_cc: currsrc=%x %s mode %s debounce %d attached %d cc=%s\n", + currsrc, rp_sel_to_name(currsrc), + mode == EN_SRC_ONLY ? "EN_SRC_ONLY" : "EN_SNK_ONLY", + pmic_typec_port->debouncing_cc, !!(misc & CC_ATTACHED), + misc_to_cc(misc)); + + return ret; +} + +static int qcom_pmic_typec_port_set_polarity(struct tcpc_dev *tcpc, + enum typec_cc_polarity pol) +{ + /* Polarity is set separately by phy-qcom-qmp.c */ + return 0; +} + +static int qcom_pmic_typec_port_set_vconn(struct tcpc_dev *tcpc, bool on) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; + struct device *dev = pmic_typec_port->dev; + unsigned int orientation, misc, mask, value; + unsigned long flags; + int ret; + + spin_lock_irqsave(&pmic_typec_port->lock, flags); + + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MISC_STATUS_REG, &misc); + if (ret) + goto done; + + /* Set VCONN on the inversion of the active CC channel */ + orientation = (misc & CC_ORIENTATION) ? 0 : VCONN_EN_ORIENTATION; + if (on) { + mask = VCONN_EN_ORIENTATION | VCONN_EN_VALUE; + value = orientation | VCONN_EN_VALUE | VCONN_EN_SRC; + } else { + mask = VCONN_EN_VALUE; + value = 0; + } + + ret = regmap_update_bits(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_VCONN_CONTROL_REG, + mask, value); +done: + spin_unlock_irqrestore(&pmic_typec_port->lock, flags); + + dev_dbg(dev, "set_vconn: orientation %d control 0x%08x state %s cc %s vconn %s\n", + orientation, value, str_on_off(on), misc_to_vconn(misc), + misc_to_cc(misc)); + + return ret; +} + +static int qcom_pmic_typec_port_start_toggling(struct tcpc_dev *tcpc, + enum typec_port_type port_type, + enum typec_cc_status cc) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; + struct device *dev = pmic_typec_port->dev; + unsigned int misc; + u8 mode = 0; + unsigned long flags; + int ret; + + switch (port_type) { + case TYPEC_PORT_SRC: + mode = EN_SRC_ONLY; + break; + case TYPEC_PORT_SNK: + mode = EN_SNK_ONLY; + break; + case TYPEC_PORT_DRP: + mode = EN_TRY_SNK; + break; + } + + spin_lock_irqsave(&pmic_typec_port->lock, flags); + + ret = regmap_read(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MISC_STATUS_REG, &misc); + if (ret) + goto done; + + dev_dbg(dev, "start_toggling: misc 0x%08x attached %d port_type %d current cc %d new %d\n", + misc, !!(misc & CC_ATTACHED), port_type, pmic_typec_port->cc, cc); + + qcom_pmic_set_cc_debounce(pmic_typec_port); + + /* force it to toggle at least once */ + ret = regmap_write(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MODE_CFG_REG, + TYPEC_DISABLE_CMD); + if (ret) + goto done; + + ret = regmap_write(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MODE_CFG_REG, + mode); +done: + spin_unlock_irqrestore(&pmic_typec_port->lock, flags); + + return ret; +} + +#define TYPEC_INTR_EN_CFG_1_MASK \ + (TYPEC_LEGACY_CABLE_INT_EN | \ + TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN | \ + TYPEC_TRYSOURCE_DETECT_INT_EN | \ + TYPEC_TRYSINK_DETECT_INT_EN | \ + TYPEC_CCOUT_DETACH_INT_EN | \ + TYPEC_CCOUT_ATTACH_INT_EN | \ + TYPEC_VBUS_DEASSERT_INT_EN | \ + TYPEC_VBUS_ASSERT_INT_EN) + +#define TYPEC_INTR_EN_CFG_2_MASK \ + (TYPEC_STATE_MACHINE_CHANGE_INT_EN | TYPEC_VBUS_ERROR_INT_EN | \ + TYPEC_DEBOUNCE_DONE_INT_EN) + +static int qcom_pmic_typec_port_start(struct pmic_typec *tcpm, + struct tcpm_port *tcpm_port) +{ + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; + int i; + int mask; + int ret; + + /* Configure interrupt sources */ + ret = regmap_write(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_INTERRUPT_EN_CFG_1_REG, + TYPEC_INTR_EN_CFG_1_MASK); + if (ret) + goto done; + + ret = regmap_write(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_INTERRUPT_EN_CFG_2_REG, + TYPEC_INTR_EN_CFG_2_MASK); + if (ret) + goto done; + + /* start in TRY_SNK mode */ + ret = regmap_write(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_MODE_CFG_REG, EN_TRY_SNK); + if (ret) + goto done; + + /* Configure VCONN for software control */ + ret = regmap_update_bits(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_VCONN_CONTROL_REG, + VCONN_EN_SRC | VCONN_EN_VALUE, VCONN_EN_SRC); + if (ret) + goto done; + + /* Set CC threshold to 1.6 Volts | tPDdebounce = 10-20ms */ + mask = SEL_SRC_UPPER_REF | USE_TPD_FOR_EXITING_ATTACHSRC; + ret = regmap_update_bits(pmic_typec_port->regmap, + pmic_typec_port->base + TYPEC_EXIT_STATE_CFG_REG, + mask, mask); + if (ret) + goto done; + + pmic_typec_port->tcpm_port = tcpm_port; + + for (i = 0; i < pmic_typec_port->nr_irqs; i++) + enable_irq(pmic_typec_port->irq_data[i].irq); + +done: + return ret; +} + +static void qcom_pmic_typec_port_stop(struct pmic_typec *tcpm) +{ + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; + int i; + + for (i = 0; i < pmic_typec_port->nr_irqs; i++) + disable_irq(pmic_typec_port->irq_data[i].irq); +} + +int qcom_pmic_typec_port_probe(struct platform_device *pdev, + struct pmic_typec *tcpm, + const struct pmic_typec_port_resources *res, + struct regmap *regmap, + u32 base) +{ + struct device *dev = &pdev->dev; + struct pmic_typec_port_irq_data *irq_data; + struct pmic_typec_port *pmic_typec_port; + struct fwnode_handle *connector; + int i, ret, irq; + + pmic_typec_port = devm_kzalloc(dev, sizeof(*pmic_typec_port), GFP_KERNEL); + if (!pmic_typec_port) + return -ENOMEM; + + if (!res->nr_irqs || res->nr_irqs > PMIC_TYPEC_MAX_IRQS) + return -EINVAL; + + irq_data = devm_kcalloc(dev, res->nr_irqs, sizeof(*irq_data), + GFP_KERNEL); + if (!irq_data) + return -ENOMEM; + + mutex_init(&pmic_typec_port->vbus_lock); + + connector = device_get_named_child_node(dev, "connector"); + if (!connector) + return -EINVAL; + + pmic_typec_port->vdd_vbus = devm_of_regulator_get_optional(dev, + to_of_node(connector), + "vbus"); + if (pmic_typec_port->vdd_vbus == ERR_PTR(-ENODEV)) + pmic_typec_port->vdd_vbus = devm_regulator_get(dev, "vdd-vbus"); + if (IS_ERR(pmic_typec_port->vdd_vbus)) + return PTR_ERR(pmic_typec_port->vdd_vbus); + + pmic_typec_port->dev = dev; + pmic_typec_port->base = base; + pmic_typec_port->regmap = regmap; + pmic_typec_port->nr_irqs = res->nr_irqs; + pmic_typec_port->irq_data = irq_data; + spin_lock_init(&pmic_typec_port->lock); + INIT_DELAYED_WORK(&pmic_typec_port->cc_debounce_dwork, + qcom_pmic_typec_port_cc_debounce); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + for (i = 0; i < res->nr_irqs; i++, irq_data++) { + irq = platform_get_irq_byname(pdev, + res->irq_params[i].irq_name); + if (irq < 0) + return irq; + + irq_data->pmic_typec_port = pmic_typec_port; + irq_data->irq = irq; + irq_data->virq = res->irq_params[i].virq; + ret = devm_request_threaded_irq(dev, irq, NULL, pmic_typec_port_isr, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + res->irq_params[i].irq_name, + irq_data); + if (ret) + return ret; + } + + tcpm->pmic_typec_port = pmic_typec_port; + + tcpm->tcpc.get_vbus = qcom_pmic_typec_port_get_vbus; + tcpm->tcpc.set_vbus = qcom_pmic_typec_port_set_vbus; + tcpm->tcpc.set_cc = qcom_pmic_typec_port_set_cc; + tcpm->tcpc.get_cc = qcom_pmic_typec_port_get_cc; + tcpm->tcpc.set_polarity = qcom_pmic_typec_port_set_polarity; + tcpm->tcpc.set_vconn = qcom_pmic_typec_port_set_vconn; + tcpm->tcpc.start_toggling = qcom_pmic_typec_port_start_toggling; + + tcpm->port_start = qcom_pmic_typec_port_start; + tcpm->port_stop = qcom_pmic_typec_port_stop; + + return 0; +} + +const struct pmic_typec_port_resources pm8150b_port_res = { + .irq_params = { + { + .irq_name = "vpd-detect", + .virq = PMIC_TYPEC_VPD_IRQ, + }, + + { + .irq_name = "cc-state-change", + .virq = PMIC_TYPEC_CC_STATE_IRQ, + }, + { + .irq_name = "vconn-oc", + .virq = PMIC_TYPEC_VCONN_OC_IRQ, + }, + + { + .irq_name = "vbus-change", + .virq = PMIC_TYPEC_VBUS_IRQ, + }, + + { + .irq_name = "attach-detach", + .virq = PMIC_TYPEC_ATTACH_DETACH_IRQ, + }, + { + .irq_name = "legacy-cable-detect", + .virq = PMIC_TYPEC_LEGACY_CABLE_IRQ, + }, + + { + .irq_name = "try-snk-src-detect", + .virq = PMIC_TYPEC_TRY_SNK_SRC_IRQ, + }, + }, + .nr_irqs = 7, +}; diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h new file mode 100644 index 000000000..2ca83a46c --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. + * Copyright (c) 2023, Linaro Ltd. All rights reserved. + */ +#ifndef __QCOM_PMIC_TYPEC_PORT_H__ +#define __QCOM_PMIC_TYPEC_PORT_H__ + +#include +#include + +/* Resources */ +#define PMIC_TYPEC_MAX_IRQS 0x08 + +struct pmic_typec_port_irq_params { + int virq; + char *irq_name; +}; + +struct pmic_typec_port_resources { + unsigned int nr_irqs; + const struct pmic_typec_port_irq_params irq_params[PMIC_TYPEC_MAX_IRQS]; +}; + +/* API */ + +extern const struct pmic_typec_port_resources pm8150b_port_res; + +int qcom_pmic_typec_port_probe(struct platform_device *pdev, + struct pmic_typec *tcpm, + const struct pmic_typec_port_resources *res, + struct regmap *regmap, + u32 base); + +#endif /* __QCOM_PMIC_TYPE_C_PORT_H__ */ diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c new file mode 100644 index 000000000..6717ac914 --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -0,0 +1,1057 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2015-2017 Google, Inc + * + * USB Type-C Port Controller Interface. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PD_RETRY_COUNT_DEFAULT 3 +#define PD_RETRY_COUNT_3_0_OR_HIGHER 2 +#define AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV 3500 +#define VSINKPD_MIN_IR_DROP_MV 750 +#define VSRC_NEW_MIN_PERCENT 95 +#define VSRC_VALID_MIN_MV 500 +#define VPPS_NEW_MIN_PERCENT 95 +#define VPPS_VALID_MIN_MV 100 +#define VSINKDISCONNECT_PD_MIN_PERCENT 90 +#define VPPS_SHUTDOWN_MIN_PERCENT 85 + +struct tcpci { + struct device *dev; + + struct tcpm_port *port; + + struct regmap *regmap; + unsigned int alert_mask; + unsigned int rx_type_mask; + + bool controls_vbus; + + struct tcpc_dev tcpc; + struct tcpci_data *data; + struct gpio_desc *orientation_gpio; +}; + +struct tcpci_chip { + struct tcpci *tcpci; + struct tcpci_data data; +}; + +struct tcpm_port *tcpci_get_tcpm_port(struct tcpci *tcpci) +{ + return tcpci->port; +} +EXPORT_SYMBOL_GPL(tcpci_get_tcpm_port); + +static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc) +{ + return container_of(tcpc, struct tcpci, tcpc); +} + +static int tcpci_read16(struct tcpci *tcpci, unsigned int reg, u16 *val) +{ + return regmap_raw_read(tcpci->regmap, reg, val, sizeof(u16)); +} + +static int tcpci_write16(struct tcpci *tcpci, unsigned int reg, u16 val) +{ + return regmap_raw_write(tcpci->regmap, reg, &val, sizeof(u16)); +} + +static int tcpci_check_std_output_cap(struct regmap *regmap, u8 mask) +{ + unsigned int reg; + int ret; + + ret = regmap_read(regmap, TCPC_STD_OUTPUT_CAP, ®); + if (ret < 0) + return ret; + + return (reg & mask) == mask; +} + +static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + bool vconn_pres; + enum typec_cc_polarity polarity = TYPEC_POLARITY_CC1; + unsigned int reg; + int ret; + + ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, ®); + if (ret < 0) + return ret; + + vconn_pres = !!(reg & TCPC_POWER_STATUS_VCONN_PRES); + if (vconn_pres) { + ret = regmap_read(tcpci->regmap, TCPC_TCPC_CTRL, ®); + if (ret < 0) + return ret; + + if (reg & TCPC_TCPC_CTRL_ORIENTATION) + polarity = TYPEC_POLARITY_CC2; + } + + switch (cc) { + case TYPEC_CC_RA: + reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RA) + | FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RA)); + break; + case TYPEC_CC_RD: + reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RD) + | FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RD)); + break; + case TYPEC_CC_RP_DEF: + reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP) + | FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP) + | FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL, + TCPC_ROLE_CTRL_RP_VAL_DEF)); + break; + case TYPEC_CC_RP_1_5: + reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP) + | FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP) + | FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL, + TCPC_ROLE_CTRL_RP_VAL_1_5)); + break; + case TYPEC_CC_RP_3_0: + reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP) + | FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP) + | FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL, + TCPC_ROLE_CTRL_RP_VAL_3_0)); + break; + case TYPEC_CC_OPEN: + default: + reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_OPEN) + | FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_OPEN)); + break; + } + + if (vconn_pres) { + if (polarity == TYPEC_POLARITY_CC2) + FIELD_MODIFY(TCPC_ROLE_CTRL_CC1, ®, TCPC_ROLE_CTRL_CC_OPEN); + else + FIELD_MODIFY(TCPC_ROLE_CTRL_CC2, ®, TCPC_ROLE_CTRL_CC_OPEN); + } + + ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); + if (ret < 0) + return ret; + + return 0; +} + +static int tcpci_apply_rc(struct tcpc_dev *tcpc, enum typec_cc_status cc, + enum typec_cc_polarity polarity) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + + ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, ®); + if (ret < 0) + return ret; + + /* + * APPLY_RC state is when ROLE_CONTROL.CC1 != ROLE_CONTROL.CC2 and vbus autodischarge on + * disconnect is disabled. Bail out when ROLE_CONTROL.CC1 != ROLE_CONTROL.CC2. + */ + if (FIELD_GET(TCPC_ROLE_CTRL_CC2, reg) != FIELD_GET(TCPC_ROLE_CTRL_CC1, reg)) + return 0; + + return regmap_update_bits(tcpci->regmap, TCPC_ROLE_CTRL, polarity == TYPEC_POLARITY_CC1 ? + TCPC_ROLE_CTRL_CC2 : TCPC_ROLE_CTRL_CC1, + TCPC_ROLE_CTRL_CC_OPEN); +} + +static int tcpci_start_toggling(struct tcpc_dev *tcpc, + enum typec_port_type port_type, + enum typec_cc_status cc) +{ + int ret; + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg = TCPC_ROLE_CTRL_DRP; + + if (port_type != TYPEC_PORT_DRP) + return -EOPNOTSUPP; + + /* Handle vendor drp toggling */ + if (tcpci->data->start_drp_toggling) { + ret = tcpci->data->start_drp_toggling(tcpci, tcpci->data, cc); + if (ret < 0) + return ret; + } + + switch (cc) { + default: + case TYPEC_CC_RP_DEF: + reg |= FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL, + TCPC_ROLE_CTRL_RP_VAL_DEF); + break; + case TYPEC_CC_RP_1_5: + reg |= FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL, + TCPC_ROLE_CTRL_RP_VAL_1_5); + break; + case TYPEC_CC_RP_3_0: + reg |= FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL, + TCPC_ROLE_CTRL_RP_VAL_3_0); + break; + } + + if (cc == TYPEC_CC_RD) + reg |= (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RD) + | FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RD)); + else + reg |= (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP) + | FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP)); + ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); + if (ret < 0) + return ret; + return regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_LOOK4CONNECTION); +} + +static int tcpci_get_cc(struct tcpc_dev *tcpc, + enum typec_cc_status *cc1, enum typec_cc_status *cc2) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg, role_control; + int ret; + + ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, &role_control); + if (ret < 0) + return ret; + + ret = regmap_read(tcpci->regmap, TCPC_CC_STATUS, ®); + if (ret < 0) + return ret; + + *cc1 = tcpci_to_typec_cc(FIELD_GET(TCPC_CC_STATUS_CC1, reg), + reg & TCPC_CC_STATUS_TERM || + tcpc_presenting_rd(role_control, CC1)); + *cc2 = tcpci_to_typec_cc(FIELD_GET(TCPC_CC_STATUS_CC2, reg), + reg & TCPC_CC_STATUS_TERM || + tcpc_presenting_rd(role_control, CC2)); + + return 0; +} + +static int tcpci_set_polarity(struct tcpc_dev *tcpc, + enum typec_cc_polarity polarity) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + enum typec_cc_status cc1, cc2; + + /* Obtain Rp setting from role control */ + ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, ®); + if (ret < 0) + return ret; + + ret = tcpci_get_cc(tcpc, &cc1, &cc2); + if (ret < 0) + return ret; + + /* + * When port has drp toggling enabled, ROLE_CONTROL would only have the initial + * terminations for the toggling and does not indicate the final cc + * terminations when ConnectionResult is 0 i.e. drp toggling stops and + * the connection is resolved. Infer port role from TCPC_CC_STATUS based on the + * terminations seen. The port role is then used to set the cc terminations. + */ + if (reg & TCPC_ROLE_CTRL_DRP) { + /* Disable DRP for the OPEN setting to take effect */ + reg = reg & ~TCPC_ROLE_CTRL_DRP; + + if (polarity == TYPEC_POLARITY_CC2) { + reg &= ~TCPC_ROLE_CTRL_CC2; + /* Local port is source */ + if (cc2 == TYPEC_CC_RD) + /* Role control would have the Rp setting when DRP was enabled */ + reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP); + else if (cc2 >= TYPEC_CC_RP_DEF) + reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RD); + } else { + reg &= ~TCPC_ROLE_CTRL_CC1; + /* Local port is source */ + if (cc1 == TYPEC_CC_RD) + /* Role control would have the Rp setting when DRP was enabled */ + reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP); + else if (cc1 >= TYPEC_CC_RP_DEF) + reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RD); + } + } + + if (polarity == TYPEC_POLARITY_CC2) + reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_OPEN); + else + reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_OPEN); + ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); + if (ret < 0) + return ret; + + return regmap_write(tcpci->regmap, TCPC_TCPC_CTRL, + (polarity == TYPEC_POLARITY_CC2) ? + TCPC_TCPC_CTRL_ORIENTATION : 0); +} + +static int tcpci_set_orientation(struct tcpc_dev *tcpc, + enum typec_orientation orientation) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + + if (tcpci->orientation_gpio) + return gpiod_set_value_cansleep(tcpci->orientation_gpio, + orientation != TYPEC_ORIENTATION_NORMAL); + + switch (orientation) { + case TYPEC_ORIENTATION_NONE: + /* We can't put a single output into high impedance */ + fallthrough; + case TYPEC_ORIENTATION_NORMAL: + reg = TCPC_CONFIG_STD_OUTPUT_ORIENTATION_NORMAL; + break; + case TYPEC_ORIENTATION_REVERSE: + reg = TCPC_CONFIG_STD_OUTPUT_ORIENTATION_FLIPPED; + break; + } + + return regmap_update_bits(tcpci->regmap, TCPC_CONFIG_STD_OUTPUT, + TCPC_CONFIG_STD_OUTPUT_ORIENTATION_MASK, reg); +} + +static void tcpci_set_partner_usb_comm_capable(struct tcpc_dev *tcpc, bool capable) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + + if (tcpci->data->set_partner_usb_comm_capable) + tcpci->data->set_partner_usb_comm_capable(tcpci, tcpci->data, capable); +} + +static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + int ret; + + /* Handle vendor set vconn */ + if (tcpci->data->set_vconn) { + ret = tcpci->data->set_vconn(tcpci, tcpci->data, enable); + if (ret < 0) + return ret; + } + + return regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, + TCPC_POWER_CTRL_VCONN_ENABLE, + enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0); +} + +static int tcpci_enable_auto_vbus_discharge(struct tcpc_dev *dev, bool enable) +{ + struct tcpci *tcpci = tcpc_to_tcpci(dev); + int ret; + + ret = regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_POWER_CTRL_AUTO_DISCHARGE, + enable ? TCPC_POWER_CTRL_AUTO_DISCHARGE : 0); + return ret; +} + +static int tcpci_set_auto_vbus_discharge_threshold(struct tcpc_dev *dev, enum typec_pwr_opmode mode, + bool pps_active, u32 requested_vbus_voltage_mv, + u32 apdo_min_voltage_mv) +{ + struct tcpci *tcpci = tcpc_to_tcpci(dev); + unsigned int pwr_ctrl, threshold = 0; + int ret; + + /* + * Indicates that vbus is going to go away due PR_SWAP, hard reset etc. + * Do not discharge vbus here. + */ + if (requested_vbus_voltage_mv == 0) + goto write_thresh; + + ret = regmap_read(tcpci->regmap, TCPC_POWER_CTRL, &pwr_ctrl); + if (ret < 0) + return ret; + + if (pwr_ctrl & TCPC_FAST_ROLE_SWAP_EN) { + /* To prevent disconnect when the source is fast role swap is capable. */ + threshold = AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV; + } else if (mode == TYPEC_PWR_MODE_PD) { + if (pps_active) + /* + * To prevent disconnect when the source is in Current Limit Mode. + * Set the threshold to the lowest possible voltage vPpsShutdown (min) + */ + threshold = VPPS_SHUTDOWN_MIN_PERCENT * apdo_min_voltage_mv / 100 - + VSINKPD_MIN_IR_DROP_MV; + else + threshold = ((VSRC_NEW_MIN_PERCENT * requested_vbus_voltage_mv / 100) - + VSINKPD_MIN_IR_DROP_MV - VSRC_VALID_MIN_MV) * + VSINKDISCONNECT_PD_MIN_PERCENT / 100; + } else { + /* 3.5V for non-pd sink */ + threshold = AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV; + } + + threshold = threshold / TCPC_VBUS_SINK_DISCONNECT_THRESH_LSB_MV; + + if (threshold > TCPC_VBUS_SINK_DISCONNECT_THRESH_MAX) + return -EINVAL; + +write_thresh: + return tcpci_write16(tcpci, TCPC_VBUS_SINK_DISCONNECT_THRESH, threshold); +} + +static int tcpci_enable_frs(struct tcpc_dev *dev, bool enable) +{ + struct tcpci *tcpci = tcpc_to_tcpci(dev); + int ret; + + /* To prevent disconnect during FRS, set disconnect threshold to 3.5V */ + ret = tcpci_write16(tcpci, TCPC_VBUS_SINK_DISCONNECT_THRESH, enable ? 0 : 0x8c); + if (ret < 0) + return ret; + + ret = regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_FAST_ROLE_SWAP_EN, enable ? + TCPC_FAST_ROLE_SWAP_EN : 0); + + return ret; +} + +static void tcpci_frs_sourcing_vbus(struct tcpc_dev *dev) +{ + struct tcpci *tcpci = tcpc_to_tcpci(dev); + + if (tcpci->data->frs_sourcing_vbus) + tcpci->data->frs_sourcing_vbus(tcpci, tcpci->data); +} + +static void tcpci_check_contaminant(struct tcpc_dev *dev) +{ + struct tcpci *tcpci = tcpc_to_tcpci(dev); + + if (tcpci->data->check_contaminant) + tcpci->data->check_contaminant(tcpci, tcpci->data); +} + +static int tcpci_set_bist_data(struct tcpc_dev *tcpc, bool enable) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + + return regmap_update_bits(tcpci->regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_BIST_TM, + enable ? TCPC_TCPC_CTRL_BIST_TM : 0); +} + +static int tcpci_set_roles(struct tcpc_dev *tcpc, bool attached, + enum typec_role role, enum typec_data_role data) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + + reg = FIELD_PREP(TCPC_MSG_HDR_INFO_REV, PD_REV20); + if (role == TYPEC_SOURCE) + reg |= TCPC_MSG_HDR_INFO_PWR_ROLE; + if (data == TYPEC_HOST) + reg |= TCPC_MSG_HDR_INFO_DATA_ROLE; + ret = regmap_write(tcpci->regmap, TCPC_MSG_HDR_INFO, reg); + if (ret < 0) + return ret; + + return 0; +} + +static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg = 0; + int ret; + + if (enable) { + reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET; + if (tcpci->data->cable_comm_capable) + reg |= TCPC_RX_DETECT_SOP1; + } + + tcpci->rx_type_mask = reg; + ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg); + if (ret < 0) + return ret; + + return 0; +} + +static int tcpci_get_vbus(struct tcpc_dev *tcpc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + + ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, ®); + if (ret < 0) + return ret; + + return !!(reg & TCPC_POWER_STATUS_VBUS_PRES); +} + +static bool tcpci_is_vbus_vsafe0v(struct tcpc_dev *tcpc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + + ret = regmap_read(tcpci->regmap, TCPC_EXTENDED_STATUS, ®); + if (ret < 0) + return false; + + return !!(reg & TCPC_EXTENDED_STATUS_VSAFE0V); +} + +static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + int ret; + + if (tcpci->data->set_vbus) { + ret = tcpci->data->set_vbus(tcpci, tcpci->data, source, sink); + /* Bypass when ret > 0 */ + if (ret != 0) + return ret < 0 ? ret : 0; + } + + /* Disable both source and sink first before enabling anything */ + + if (!source) { + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_DISABLE_SRC_VBUS); + if (ret < 0) + return ret; + } + + if (!sink) { + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_DISABLE_SINK_VBUS); + if (ret < 0) + return ret; + } + + if (source) { + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_SRC_VBUS_DEFAULT); + if (ret < 0) + return ret; + } + + if (sink) { + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_SINK_VBUS); + if (ret < 0) + return ret; + } + + return 0; +} + +static int tcpci_pd_transmit(struct tcpc_dev *tcpc, enum tcpm_transmit_type type, + const struct pd_message *msg, unsigned int negotiated_rev) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + u16 header = msg ? le16_to_cpu(msg->header) : 0; + unsigned int reg, cnt; + int ret; + + cnt = msg ? pd_header_cnt(header) * 4 : 0; + /** + * TCPCI spec forbids direct access of TCPC_TX_DATA. + * But, since some of the chipsets offer this capability, + * it's fair to support both. + */ + if (tcpci->data->TX_BUF_BYTE_x_hidden) { + u8 buf[TCPC_TRANSMIT_BUFFER_MAX_LEN] = {0,}; + u8 pos = 0; + + /* Payload + header + TCPC_TX_BYTE_CNT */ + buf[pos++] = cnt + 2; + + if (msg) + memcpy(&buf[pos], &msg->header, sizeof(msg->header)); + + pos += sizeof(header); + + if (cnt > 0) + memcpy(&buf[pos], msg->payload, cnt); + + pos += cnt; + ret = regmap_raw_write(tcpci->regmap, TCPC_TX_BYTE_CNT, buf, pos); + if (ret < 0) + return ret; + } else { + ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt + 2); + if (ret < 0) + return ret; + + ret = tcpci_write16(tcpci, TCPC_TX_HDR, header); + if (ret < 0) + return ret; + + if (cnt > 0) { + ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA, &msg->payload, cnt); + if (ret < 0) + return ret; + } + } + + /* nRetryCount is 3 in PD2.0 spec where 2 in PD3.0 spec */ + reg = FIELD_PREP(TCPC_TRANSMIT_RETRY, + (negotiated_rev > PD_REV20 + ? PD_RETRY_COUNT_3_0_OR_HIGHER + : PD_RETRY_COUNT_DEFAULT)); + reg |= FIELD_PREP(TCPC_TRANSMIT_TYPE, type); + ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg); + if (ret < 0) + return ret; + + return 0; +} + +static bool tcpci_cable_comm_capable(struct tcpc_dev *tcpc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + + return tcpci->data->cable_comm_capable; +} + +static bool tcpci_attempt_vconn_swap_discovery(struct tcpc_dev *tcpc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + + if (tcpci->data->attempt_vconn_swap_discovery) + return tcpci->data->attempt_vconn_swap_discovery(tcpci, tcpci->data); + + return false; +} + +static int tcpci_init(struct tcpc_dev *tcpc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned long timeout = jiffies + msecs_to_jiffies(2000); /* XXX */ + unsigned int reg; + int ret; + + while (time_before_eq(jiffies, timeout)) { + ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, ®); + if (ret < 0) + return ret; + if (!(reg & TCPC_POWER_STATUS_UNINIT)) + break; + usleep_range(10000, 20000); + } + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + + ret = tcpci_write16(tcpci, TCPC_FAULT_STATUS, TCPC_FAULT_STATUS_ALL_REG_RST_TO_DEFAULT); + if (ret < 0) + return ret; + + /* Handle vendor init */ + if (tcpci->data->init) { + ret = tcpci->data->init(tcpci, tcpci->data); + if (ret < 0) + return ret; + } + + /* Clear all events */ + ret = tcpci_write16(tcpci, TCPC_ALERT, 0xffff); + if (ret < 0) + return ret; + + if (tcpci->controls_vbus) + reg = TCPC_POWER_STATUS_VBUS_PRES; + else + reg = 0; + ret = regmap_write(tcpci->regmap, TCPC_POWER_STATUS_MASK, reg); + if (ret < 0) + return ret; + + /* Enable Vbus detection */ + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_ENABLE_VBUS_DETECT); + if (ret < 0) + return ret; + + reg = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED | + TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS | + TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS; + if (tcpci->controls_vbus) + reg |= TCPC_ALERT_POWER_STATUS; + /* Enable VSAFE0V status interrupt when detecting VSAFE0V is supported */ + if (tcpci->data->vbus_vsafe0v) { + reg |= TCPC_ALERT_EXTENDED_STATUS; + ret = regmap_write(tcpci->regmap, TCPC_EXTENDED_STATUS_MASK, + TCPC_EXTENDED_STATUS_VSAFE0V); + if (ret < 0) + return ret; + } + + tcpci->alert_mask = reg; + + return 0; +} + +irqreturn_t tcpci_irq(struct tcpci *tcpci) +{ + u16 status; + int ret; + int irq_ret; + unsigned int raw; + + tcpci_read16(tcpci, TCPC_ALERT, &status); + irq_ret = status & tcpci->alert_mask; + +process_status: + /* + * Clear alert status for everything except RX_STATUS, which shouldn't + * be cleared until we have successfully retrieved message. + */ + if (status & ~TCPC_ALERT_RX_STATUS) + tcpci_write16(tcpci, TCPC_ALERT, + status & ~TCPC_ALERT_RX_STATUS); + + if (status & TCPC_ALERT_CC_STATUS) + tcpm_cc_change(tcpci->port); + + if (status & TCPC_ALERT_POWER_STATUS) { + regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, &raw); + /* + * If power status mask has been reset, then the TCPC + * has reset. + */ + if (raw == 0xff) + tcpm_tcpc_reset(tcpci->port); + else + tcpm_vbus_change(tcpci->port); + } + + if (status & TCPC_ALERT_RX_STATUS) { + struct pd_message msg; + unsigned int cnt, payload_cnt; + enum tcpm_transmit_type rx_type; + u16 header; + + regmap_read(tcpci->regmap, TCPC_RX_BYTE_CNT, &cnt); + /* + * 'cnt' corresponds to READABLE_BYTE_COUNT in section 4.4.14 + * of the TCPCI spec [Rev 2.0 Ver 1.0 October 2017] and is + * defined in table 4-36 as one greater than the number of + * bytes received. And that number includes the header. So: + */ + if (cnt > 3) + payload_cnt = cnt - (1 + sizeof(msg.header)); + else + payload_cnt = 0; + + tcpci_read16(tcpci, TCPC_RX_HDR, &header); + msg.header = cpu_to_le16(header); + + if (WARN_ON(payload_cnt > sizeof(msg.payload))) + payload_cnt = sizeof(msg.payload); + + if (payload_cnt > 0) + regmap_raw_read(tcpci->regmap, TCPC_RX_DATA, + &msg.payload, payload_cnt); + + ret = regmap_read(tcpci->regmap, TCPC_RX_BUF_FRAME_TYPE, &rx_type); + if (ret) + return ret; + + /* Read complete, clear RX status alert bit */ + tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS); + + rx_type &= TCPC_RX_BUF_FRAME_TYPE_MASK; + if (tcpci->rx_type_mask & BIT(rx_type)) + tcpm_pd_receive(tcpci->port, &msg, rx_type); + } + + if (tcpci->data->vbus_vsafe0v && (status & TCPC_ALERT_EXTENDED_STATUS)) { + ret = regmap_read(tcpci->regmap, TCPC_EXTENDED_STATUS, &raw); + if (!ret && (raw & TCPC_EXTENDED_STATUS_VSAFE0V)) + tcpm_vbus_change(tcpci->port); + } + + if (status & TCPC_ALERT_RX_HARD_RST) + tcpm_pd_hard_reset(tcpci->port); + + if (status & TCPC_ALERT_TX_SUCCESS) + tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_SUCCESS); + else if (status & TCPC_ALERT_TX_DISCARDED) + tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_DISCARDED); + else if (status & TCPC_ALERT_TX_FAILED) + tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_FAILED); + + tcpci_read16(tcpci, TCPC_ALERT, &status); + + if (status & tcpci->alert_mask) + goto process_status; + + return IRQ_RETVAL(irq_ret); +} +EXPORT_SYMBOL_GPL(tcpci_irq); + +static irqreturn_t _tcpci_irq(int irq, void *dev_id) +{ + struct tcpci_chip *chip = dev_id; + + return tcpci_irq(chip->tcpci); +} + +static const struct regmap_config tcpci_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0x7F, /* 0x80 .. 0xFF are vendor defined */ +}; + +static int tcpci_parse_config(struct tcpci *tcpci) +{ + tcpci->controls_vbus = true; /* XXX */ + + tcpci->tcpc.fwnode = device_get_named_child_node(tcpci->dev, + "connector"); + if (!tcpci->tcpc.fwnode) { + dev_err(tcpci->dev, "Can't find connector node.\n"); + return -EINVAL; + } + + return 0; +} + +struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) +{ + struct tcpci *tcpci; + int err; + + tcpci = devm_kzalloc(dev, sizeof(*tcpci), GFP_KERNEL); + if (!tcpci) + return ERR_PTR(-ENOMEM); + + tcpci->dev = dev; + tcpci->data = data; + tcpci->regmap = data->regmap; + + tcpci->tcpc.init = tcpci_init; + tcpci->tcpc.get_vbus = tcpci_get_vbus; + tcpci->tcpc.set_vbus = tcpci_set_vbus; + tcpci->tcpc.set_cc = tcpci_set_cc; + tcpci->tcpc.apply_rc = tcpci_apply_rc; + tcpci->tcpc.get_cc = tcpci_get_cc; + tcpci->tcpc.set_polarity = tcpci_set_polarity; + tcpci->tcpc.set_vconn = tcpci_set_vconn; + tcpci->tcpc.start_toggling = tcpci_start_toggling; + + tcpci->tcpc.set_pd_rx = tcpci_set_pd_rx; + tcpci->tcpc.set_roles = tcpci_set_roles; + tcpci->tcpc.pd_transmit = tcpci_pd_transmit; + tcpci->tcpc.set_bist_data = tcpci_set_bist_data; + tcpci->tcpc.enable_frs = tcpci_enable_frs; + tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus; + tcpci->tcpc.set_partner_usb_comm_capable = tcpci_set_partner_usb_comm_capable; + tcpci->tcpc.cable_comm_capable = tcpci_cable_comm_capable; + tcpci->tcpc.attempt_vconn_swap_discovery = tcpci_attempt_vconn_swap_discovery; + + if (tcpci->data->check_contaminant) + tcpci->tcpc.check_contaminant = tcpci_check_contaminant; + + if (tcpci->data->auto_discharge_disconnect) { + tcpci->tcpc.enable_auto_vbus_discharge = tcpci_enable_auto_vbus_discharge; + tcpci->tcpc.set_auto_vbus_discharge_threshold = + tcpci_set_auto_vbus_discharge_threshold; + regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_POWER_CTRL_BLEED_DISCHARGE, + TCPC_POWER_CTRL_BLEED_DISCHARGE); + } + + if (tcpci->data->vbus_vsafe0v) + tcpci->tcpc.is_vbus_vsafe0v = tcpci_is_vbus_vsafe0v; + + if (tcpci->data->set_orientation) + tcpci->tcpc.set_orientation = tcpci_set_orientation; + + err = tcpci_parse_config(tcpci); + if (err < 0) + return ERR_PTR(err); + + tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc); + if (IS_ERR(tcpci->port)) { + fwnode_handle_put(tcpci->tcpc.fwnode); + return ERR_CAST(tcpci->port); + } + + return tcpci; +} +EXPORT_SYMBOL_GPL(tcpci_register_port); + +void tcpci_unregister_port(struct tcpci *tcpci) +{ + tcpm_unregister_port(tcpci->port); + fwnode_handle_put(tcpci->tcpc.fwnode); +} +EXPORT_SYMBOL_GPL(tcpci_unregister_port); + +static int tcpci_probe(struct i2c_client *client) +{ + struct tcpci_chip *chip; + struct gpio_desc *orient_gpio = NULL; + int err; + u16 val = 0; + + err = devm_regulator_get_enable_optional(&client->dev, "vdd"); + if (err && err != -ENODEV) + return dev_err_probe(&client->dev, err, "Failed to get regulator\n"); + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->data.regmap = devm_regmap_init_i2c(client, &tcpci_regmap_config); + if (IS_ERR(chip->data.regmap)) + return PTR_ERR(chip->data.regmap); + + i2c_set_clientdata(client, chip); + + /* Disable chip interrupts before requesting irq */ + err = regmap_raw_write(chip->data.regmap, TCPC_ALERT_MASK, &val, + sizeof(u16)); + if (err < 0) + return err; + + err = tcpci_check_std_output_cap(chip->data.regmap, + TCPC_STD_OUTPUT_CAP_ORIENTATION); + if (err < 0) + return err; + + if (err == 0) { + orient_gpio = devm_gpiod_get_optional(&client->dev, "orientation", + GPIOD_OUT_LOW); + if (IS_ERR(orient_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(orient_gpio), + "unable to acquire orientation gpio\n"); + err = !!orient_gpio; + } + + chip->data.set_orientation = err; + + chip->tcpci = tcpci_register_port(&client->dev, &chip->data); + if (IS_ERR(chip->tcpci)) + return PTR_ERR(chip->tcpci); + + chip->tcpci->orientation_gpio = orient_gpio; + + err = devm_request_threaded_irq(&client->dev, client->irq, NULL, + _tcpci_irq, + IRQF_SHARED | IRQF_ONESHOT, + dev_name(&client->dev), chip); + if (err < 0) + goto unregister_port; + + /* Enable chip interrupts at last */ + err = tcpci_write16(chip->tcpci, TCPC_ALERT_MASK, chip->tcpci->alert_mask); + if (err < 0) + goto unregister_port; + + device_set_wakeup_capable(chip->tcpci->dev, true); + + return 0; + +unregister_port: + tcpci_unregister_port(chip->tcpci); + return err; +} + +static void tcpci_remove(struct i2c_client *client) +{ + struct tcpci_chip *chip = i2c_get_clientdata(client); + int err; + + /* Disable chip interrupts before unregistering port */ + err = tcpci_write16(chip->tcpci, TCPC_ALERT_MASK, 0); + if (err < 0) + dev_warn(&client->dev, "Failed to disable irqs (%pe)\n", ERR_PTR(err)); + + tcpci_unregister_port(chip->tcpci); +} + +static int tcpci_suspend(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct tcpci_chip *chip = i2c_get_clientdata(i2c); + int ret; + + if (device_may_wakeup(dev)) + ret = enable_irq_wake(i2c->irq); + else + ret = tcpci_write16(chip->tcpci, TCPC_ALERT_MASK, 0); + + return ret; +} + +static int tcpci_resume(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct tcpci_chip *chip = i2c_get_clientdata(i2c); + int ret; + + if (device_may_wakeup(dev)) + ret = disable_irq_wake(i2c->irq); + else + ret = tcpci_write16(chip->tcpci, TCPC_ALERT_MASK, chip->tcpci->alert_mask); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(tcpci_pm_ops, tcpci_suspend, tcpci_resume); + +static const struct i2c_device_id tcpci_id[] = { + { .name = "tcpci" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tcpci_id); + +#ifdef CONFIG_OF +static const struct of_device_id tcpci_of_match[] = { + { .compatible = "nxp,ptn5110", }, + { .compatible = "tcpci", }, + {}, +}; +MODULE_DEVICE_TABLE(of, tcpci_of_match); +#endif + +static struct i2c_driver tcpci_i2c_driver = { + .driver = { + .name = "tcpci", + .pm = pm_sleep_ptr(&tcpci_pm_ops), + .of_match_table = of_match_ptr(tcpci_of_match), + }, + .probe = tcpci_probe, + .remove = tcpci_remove, + .id_table = tcpci_id, +}; +module_i2c_driver(tcpci_i2c_driver); + +MODULE_DESCRIPTION("USB Type-C Port Controller Interface driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.h b/drivers/usb/typec/tcpm/tcpci_maxim.h new file mode 100644 index 000000000..b314606eb --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpci_maxim.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2022 Google, Inc + * + * MAXIM TCPC header file. + */ +#ifndef TCPCI_MAXIM_H_ +#define TCPCI_MAXIM_H_ + +#define VENDOR_CC_STATUS2 0x85 +#define CC1_VUFP_RD0P5 BIT(1) +#define CC2_VUFP_RD0P5 BIT(5) +#define TCPC_VENDOR_FLADC_STATUS 0x89 + +#define TCPC_VENDOR_CC_CTRL1 0x8c +#define CCCONNDRY BIT(7) +#define CCCOMPEN BIT(5) + +#define TCPC_VENDOR_CC_CTRL2 0x8d +#define SBUOVPDIS BIT(7) +#define CCOVPDIS BIT(6) +#define SBURPCTRL BIT(5) +#define CCLPMODESEL GENMASK(4, 3) +#define LOW_POWER_MODE_DISABLE 0 +#define ULTRA_LOW_POWER_MODE 1 +#define CCRPCTRL GENMASK(2, 0) +#define UA_1_SRC 1 +#define UA_80_SRC 3 + +#define TCPC_VENDOR_CC_CTRL3 0x8e +#define CCWTRDEB GENMASK(7, 6) +#define CCWTRDEB_1MS 1 +#define CCWTRSEL GENMASK(5, 3) +#define CCWTRSEL_1V 0x4 +#define CCLADDERDIS BIT(2) +#define WTRCYCLE GENMASK(0, 0) +#define WTRCYCLE_2_4_S 0 +#define WTRCYCLE_4_8_S 1 + +#define TCPC_VENDOR_ADC_CTRL1 0x91 +#define ADCINSEL GENMASK(7, 5) +#define ADCEN BIT(0) + +enum contamiant_state { + NOT_DETECTED, + DETECTED, + SINK, +}; + +/* + * @potential_contaminant: + * Last returned result to tcpm indicating whether the TCPM port + * has potential contaminant. + */ +struct max_tcpci_chip { + struct tcpci_data data; + struct tcpci *tcpci; + struct device *dev; + struct i2c_client *client; + struct tcpm_port *port; + enum contamiant_state contaminant_state; + bool veto_vconn_swap; + struct regulator *vbus_reg; +}; + +static inline int max_tcpci_read16(struct max_tcpci_chip *chip, unsigned int reg, u16 *val) +{ + return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u16)); +} + +static inline int max_tcpci_write16(struct max_tcpci_chip *chip, unsigned int reg, u16 val) +{ + return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u16)); +} + +static inline int max_tcpci_read8(struct max_tcpci_chip *chip, unsigned int reg, u8 *val) +{ + return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u8)); +} + +static inline int max_tcpci_write8(struct max_tcpci_chip *chip, unsigned int reg, u8 val) +{ + return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u8)); +} + +/** + * max_contaminant_is_contaminant - Test if CC was toggled due to contaminant + * + * @chip: Handle to a struct max_tcpci_chip + * @disconnect_while_debounce: Whether the disconnect was detected when CC + * pins were debouncing + * @cc_handled: Returns whether or not update to CC status was handled here + * + * Determine if a contaminant was detected. + * + * Returns: true if a contaminant was detected, false otherwise. cc_handled + * is updated to reflect whether or not further CC handling is required. + */ +bool max_contaminant_is_contaminant(struct max_tcpci_chip *chip, bool disconnect_while_debounce, + bool *cc_handled); + +#endif // TCPCI_MAXIM_H_ diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c new file mode 100644 index 000000000..998693a61 --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2020 - 2022, Google LLC + * + * MAXIM TCPCI based TCPC driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tcpci_maxim.h" + +#define PD_ACTIVITY_TIMEOUT_MS 10000 + +#define TCPC_VENDOR_ALERT 0x80 +#define TCPC_VENDOR_USBSW_CTRL 0x93 +#define TCPC_VENDOR_USBSW_CTRL_ENABLE_USB_DATA 0x9 +#define TCPC_VENDOR_USBSW_CTRL_DISABLE_USB_DATA 0 + +#define TCPC_RECEIVE_BUFFER_COUNT_OFFSET 0 +#define TCPC_RECEIVE_BUFFER_FRAME_TYPE_OFFSET 1 +#define TCPC_RECEIVE_BUFFER_RX_BYTE_BUF_OFFSET 2 + +/* + * LongMessage not supported, hence 32 bytes for buf to be read from RECEIVE_BUFFER. + * DEVICE_CAPABILITIES_2.LongMessage = 0, the value in READABLE_BYTE_COUNT reg shall be + * less than or equal to 31. Since, RECEIVE_BUFFER len = 31 + 1(READABLE_BYTE_COUNT). + */ +#define TCPC_RECEIVE_BUFFER_LEN 32 + +static const struct regmap_range max_tcpci_tcpci_range[] = { + regmap_reg_range(0x00, 0x95) +}; + +static const struct regmap_access_table max_tcpci_tcpci_write_table = { + .yes_ranges = max_tcpci_tcpci_range, + .n_yes_ranges = ARRAY_SIZE(max_tcpci_tcpci_range), +}; + +static const struct regmap_config max_tcpci_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x95, + .wr_table = &max_tcpci_tcpci_write_table, +}; + +static struct max_tcpci_chip *tdata_to_max_tcpci(struct tcpci_data *tdata) +{ + return container_of(tdata, struct max_tcpci_chip, data); +} + +static void max_tcpci_init_regs(struct max_tcpci_chip *chip) +{ + u16 alert_mask = 0; + int ret; + + ret = max_tcpci_write16(chip, TCPC_ALERT, 0xffff); + if (ret < 0) { + dev_err(chip->dev, "Error writing to TCPC_ALERT ret:%d\n", ret); + return; + } + + ret = max_tcpci_write16(chip, TCPC_VENDOR_ALERT, 0xffff); + if (ret < 0) { + dev_err(chip->dev, "Error writing to TCPC_VENDOR_ALERT ret:%d\n", ret); + return; + } + + ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED, 0xff); + if (ret < 0) { + dev_err(chip->dev, "Unable to clear TCPC_ALERT_EXTENDED ret:%d\n", ret); + return; + } + + /* Enable VSAFE0V detection */ + ret = max_tcpci_write8(chip, TCPC_EXTENDED_STATUS_MASK, TCPC_EXTENDED_STATUS_VSAFE0V); + if (ret < 0) { + dev_err(chip->dev, "Unable to unmask TCPC_EXTENDED_STATUS_VSAFE0V ret:%d\n", ret); + return; + } + + /* Vconn Over Current Protection */ + ret = max_tcpci_write8(chip, TCPC_FAULT_STATUS_MASK, TCPC_FAULT_STATUS_MASK_VCONN_OC); + if (ret < 0) + return; + + alert_mask = (TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_DISCARDED | + TCPC_ALERT_TX_FAILED | TCPC_ALERT_RX_HARD_RST | + TCPC_ALERT_RX_STATUS | TCPC_ALERT_POWER_STATUS | + TCPC_ALERT_CC_STATUS | + TCPC_ALERT_EXTND | TCPC_ALERT_EXTENDED_STATUS | + TCPC_ALERT_VBUS_DISCNCT | TCPC_ALERT_RX_BUF_OVF | + TCPC_ALERT_FAULT); + + ret = max_tcpci_write16(chip, TCPC_ALERT_MASK, alert_mask); + if (ret < 0) { + dev_err(chip->dev, + "Error enabling TCPC_ALERT: TCPC_ALERT_MASK write failed ret:%d\n", ret); + return; + } + + /* Enable vbus voltage monitoring and voltage alerts */ + ret = max_tcpci_write8(chip, TCPC_POWER_CTRL, 0); + if (ret < 0) { + dev_err(chip->dev, "Error writing to TCPC_POWER_CTRL ret:%d\n", ret); + return; + } + + ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED_MASK, TCPC_SINK_FAST_ROLE_SWAP); + if (ret < 0) + return; +} + +static void process_rx(struct max_tcpci_chip *chip, u16 status) +{ + struct pd_message msg; + u8 count, frame_type, rx_buf[TCPC_RECEIVE_BUFFER_LEN]; + int ret, payload_index; + u8 *rx_buf_ptr; + enum tcpm_transmit_type rx_type; + + /* + * READABLE_BYTE_COUNT: Indicates the number of bytes in the RX_BUF_BYTE_x registers + * plus one (for the RX_BUF_FRAME_TYPE) Table 4-36. + * Read the count and frame type. + */ + ret = regmap_raw_read(chip->data.regmap, TCPC_RX_BYTE_CNT, rx_buf, 2); + if (ret < 0) { + dev_err(chip->dev, "TCPC_RX_BYTE_CNT read failed ret:%d\n", ret); + return; + } + + count = rx_buf[TCPC_RECEIVE_BUFFER_COUNT_OFFSET]; + frame_type = rx_buf[TCPC_RECEIVE_BUFFER_FRAME_TYPE_OFFSET]; + + switch (frame_type) { + case TCPC_RX_BUF_FRAME_TYPE_SOP1: + rx_type = TCPC_TX_SOP_PRIME; + break; + case TCPC_RX_BUF_FRAME_TYPE_SOP: + rx_type = TCPC_TX_SOP; + break; + default: + rx_type = TCPC_TX_SOP; + break; + } + + if (count == 0 || (frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP && + frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP1)) { + max_tcpci_write16(chip, TCPC_ALERT, TCPC_ALERT_RX_STATUS); + dev_err(chip->dev, "%s\n", count == 0 ? "error: count is 0" : + "error frame_type is not SOP/SOP'"); + return; + } + + if (count > sizeof(struct pd_message) + 1 || + count + 1 > TCPC_RECEIVE_BUFFER_LEN) { + dev_err(chip->dev, "Invalid TCPC_RX_BYTE_CNT %d\n", count); + return; + } + + /* + * Read count + 1 as RX_BUF_BYTE_x is hidden and can only be read through + * TCPC_RX_BYTE_CNT + */ + count += 1; + ret = regmap_raw_read(chip->data.regmap, TCPC_RX_BYTE_CNT, rx_buf, count); + if (ret < 0) { + dev_err(chip->dev, "Error: TCPC_RX_BYTE_CNT read failed: %d\n", ret); + return; + } + + rx_buf_ptr = rx_buf + TCPC_RECEIVE_BUFFER_RX_BYTE_BUF_OFFSET; + msg.header = cpu_to_le16(*(u16 *)rx_buf_ptr); + rx_buf_ptr = rx_buf_ptr + sizeof(msg.header); + + if (count < TCPC_RECEIVE_BUFFER_RX_BYTE_BUF_OFFSET + sizeof(msg.header) + + pd_header_cnt_le(msg.header) * sizeof(msg.payload[0])) { + max_tcpci_write16(chip, TCPC_ALERT, TCPC_ALERT_RX_STATUS); + dev_err(chip->dev, "Invalid TCPC_RX_BYTE_CNT %d for header cnt %d\n", + count, pd_header_cnt_le(msg.header)); + return; + } + + for (payload_index = 0; payload_index < pd_header_cnt_le(msg.header); payload_index++, + rx_buf_ptr += sizeof(msg.payload[0])) + msg.payload[payload_index] = cpu_to_le32(*(u32 *)rx_buf_ptr); + + /* + * Read complete, clear RX status alert bit. + * Clear overflow as well if set. + */ + ret = max_tcpci_write16(chip, TCPC_ALERT, + TCPC_ALERT_RX_STATUS | (status & TCPC_ALERT_RX_BUF_OVF)); + if (ret < 0) + return; + + tcpm_pd_receive(chip->port, &msg, rx_type); +} + +static int get_vbus_regulator_handle(struct max_tcpci_chip *chip) +{ + if (IS_ERR_OR_NULL(chip->vbus_reg)) { + chip->vbus_reg = devm_regulator_get_exclusive(chip->dev, + "vbus"); + if (IS_ERR_OR_NULL(chip->vbus_reg)) { + dev_err(chip->dev, + "Failed to get vbus regulator handle\n"); + return -ENODEV; + } + } + + return 0; +} + +static int max_tcpci_set_vbus(struct tcpci *tcpci, struct tcpci_data *tdata, bool source, bool sink) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); + int ret; + + if (source && sink) { + dev_err(chip->dev, "Both source and sink set\n"); + return -EINVAL; + } + + ret = get_vbus_regulator_handle(chip); + if (ret) { + /* + * Regulator is not necessary for sink only applications. Return + * success in cases where sink mode is being modified. + */ + return source ? ret : 1; + } + + if (source) { + if (!regulator_is_enabled(chip->vbus_reg)) + ret = regulator_enable(chip->vbus_reg); + } else { + if (regulator_is_enabled(chip->vbus_reg)) + ret = regulator_disable(chip->vbus_reg); + } + + return ret < 0 ? ret : 1; +} + +static void process_power_status(struct max_tcpci_chip *chip) +{ + u8 pwr_status; + int ret; + + ret = max_tcpci_read8(chip, TCPC_POWER_STATUS, &pwr_status); + if (ret < 0) + return; + + if (pwr_status == 0xff) + max_tcpci_init_regs(chip); + else if (pwr_status & TCPC_POWER_STATUS_SOURCING_VBUS) + tcpm_sourcing_vbus(chip->port); + else + tcpm_vbus_change(chip->port); +} + +static void max_tcpci_frs_sourcing_vbus(struct tcpci *tcpci, struct tcpci_data *tdata) +{ + /* + * For Fast Role Swap case, Boost turns on autonomously without + * AP intervention, but, needs AP to enable source mode explicitly + * for AP to regain control. + */ + max_tcpci_set_vbus(tcpci, tdata, true, false); +} + +static void process_tx(struct max_tcpci_chip *chip, u16 status) +{ + if (status & TCPC_ALERT_TX_SUCCESS) + tcpm_pd_transmit_complete(chip->port, TCPC_TX_SUCCESS); + else if (status & TCPC_ALERT_TX_DISCARDED) + tcpm_pd_transmit_complete(chip->port, TCPC_TX_DISCARDED); + else if (status & TCPC_ALERT_TX_FAILED) + tcpm_pd_transmit_complete(chip->port, TCPC_TX_FAILED); + + /* Reinit regs as Hard reset sets them to default value */ + if ((status & TCPC_ALERT_TX_SUCCESS) && (status & TCPC_ALERT_TX_FAILED)) + max_tcpci_init_regs(chip); +} + +/* Enable USB switches when partner is USB communications capable */ +static void max_tcpci_set_partner_usb_comm_capable(struct tcpci *tcpci, struct tcpci_data *data, + bool capable) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(data); + int ret; + + ret = max_tcpci_write8(chip, TCPC_VENDOR_USBSW_CTRL, capable ? + TCPC_VENDOR_USBSW_CTRL_ENABLE_USB_DATA : + TCPC_VENDOR_USBSW_CTRL_DISABLE_USB_DATA); + + if (ret < 0) + dev_err(chip->dev, "Failed to enable USB switches"); +} + +static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status) +{ + u16 mask; + int ret; + u8 reg_status; + + /* + * Clear alert status for everything except RX_STATUS, which shouldn't + * be cleared until we have successfully retrieved message. + */ + if (status & ~TCPC_ALERT_RX_STATUS) { + mask = status & ~(TCPC_ALERT_RX_STATUS + | (status & TCPC_ALERT_RX_BUF_OVF)); + ret = max_tcpci_write16(chip, TCPC_ALERT, mask); + if (ret < 0) { + dev_err(chip->dev, "ALERT clear failed\n"); + return ret; + } + } + + if (status & TCPC_ALERT_RX_BUF_OVF && !(status & TCPC_ALERT_RX_STATUS)) { + ret = max_tcpci_write16(chip, TCPC_ALERT, (TCPC_ALERT_RX_STATUS | + TCPC_ALERT_RX_BUF_OVF)); + if (ret < 0) { + dev_err(chip->dev, "ALERT clear failed\n"); + return ret; + } + } + + if (status & TCPC_ALERT_FAULT) { + ret = max_tcpci_read8(chip, TCPC_FAULT_STATUS, ®_status); + if (ret < 0) + return ret; + + ret = max_tcpci_write8(chip, TCPC_FAULT_STATUS, reg_status); + if (ret < 0) + return ret; + + if (reg_status & TCPC_FAULT_STATUS_VCONN_OC) { + chip->veto_vconn_swap = true; + tcpm_port_error_recovery(chip->port); + } + } + + if (status & TCPC_ALERT_EXTND) { + ret = max_tcpci_read8(chip, TCPC_ALERT_EXTENDED, ®_status); + if (ret < 0) + return ret; + + ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED, reg_status); + if (ret < 0) + return ret; + + if (reg_status & TCPC_SINK_FAST_ROLE_SWAP) { + dev_info(chip->dev, "FRS Signal\n"); + tcpm_sink_frs(chip->port); + } + } + + if (status & TCPC_ALERT_EXTENDED_STATUS) { + ret = max_tcpci_read8(chip, TCPC_EXTENDED_STATUS, (u8 *)®_status); + if (ret >= 0 && (reg_status & TCPC_EXTENDED_STATUS_VSAFE0V)) + tcpm_vbus_change(chip->port); + } + + if (status & TCPC_ALERT_RX_STATUS) + process_rx(chip, status); + + if (status & TCPC_ALERT_VBUS_DISCNCT) + tcpm_vbus_change(chip->port); + + if (status & TCPC_ALERT_CC_STATUS) { + bool cc_handled = false; + + if (chip->contaminant_state == DETECTED || tcpm_port_is_toggling(chip->port)) { + if (!max_contaminant_is_contaminant(chip, false, &cc_handled)) + tcpm_port_clean(chip->port); + } + if (!cc_handled) + tcpm_cc_change(chip->port); + } + + if (status & TCPC_ALERT_POWER_STATUS) + process_power_status(chip); + + if (status & TCPC_ALERT_RX_HARD_RST) { + tcpm_pd_hard_reset(chip->port); + max_tcpci_init_regs(chip); + } + + if (status & TCPC_ALERT_TX_SUCCESS || status & TCPC_ALERT_TX_DISCARDED || status & + TCPC_ALERT_TX_FAILED) + process_tx(chip, status); + + return IRQ_HANDLED; +} + +static irqreturn_t max_tcpci_irq(int irq, void *dev_id) +{ + struct max_tcpci_chip *chip = dev_id; + u16 status; + irqreturn_t irq_return = IRQ_HANDLED; + int ret; + + if (!chip->port) + return IRQ_HANDLED; + + ret = max_tcpci_read16(chip, TCPC_ALERT, &status); + if (ret < 0) { + dev_err(chip->dev, "ALERT read failed\n"); + return ret; + } + while (status) { + irq_return = _max_tcpci_irq(chip, status); + /* Do not return if a (new) ALERT is set (again). */ + ret = max_tcpci_read16(chip, TCPC_ALERT, &status); + if (ret < 0) + break; + } + + return irq_return; +} + +static irqreturn_t max_tcpci_isr(int irq, void *dev_id) +{ + struct max_tcpci_chip *chip = dev_id; + + pm_wakeup_event(chip->dev, PD_ACTIVITY_TIMEOUT_MS); + + if (!chip->port) + return IRQ_HANDLED; + + return IRQ_WAKE_THREAD; +} + +static int max_tcpci_start_toggling(struct tcpci *tcpci, struct tcpci_data *tdata, + enum typec_cc_status cc) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); + + max_tcpci_init_regs(chip); + + return 0; +} + +static int tcpci_init(struct tcpci *tcpci, struct tcpci_data *data) +{ + /* + * Generic TCPCI overwrites the regs once this driver initializes + * them. Prevent this by returning -1. + */ + return -1; +} + +static void max_tcpci_check_contaminant(struct tcpci *tcpci, struct tcpci_data *tdata) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); + bool cc_handled; + + if (!max_contaminant_is_contaminant(chip, true, &cc_handled)) + tcpm_port_clean(chip->port); +} + +static bool max_tcpci_attempt_vconn_swap_discovery(struct tcpci *tcpci, struct tcpci_data *tdata) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); + + if (chip->veto_vconn_swap) { + chip->veto_vconn_swap = false; + return false; + } + + return true; +} + +static void max_tcpci_unregister_tcpci_port(void *tcpci) +{ + tcpci_unregister_port(tcpci); +} + +static int max_tcpci_probe(struct i2c_client *client) +{ + int ret; + struct max_tcpci_chip *chip; + u8 power_status; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->data.regmap = devm_regmap_init_i2c(client, &max_tcpci_regmap_config); + if (IS_ERR(chip->data.regmap)) + return dev_err_probe(&client->dev, PTR_ERR(chip->data.regmap), + "Regmap init failed\n"); + + chip->dev = &client->dev; + i2c_set_clientdata(client, chip); + + ret = max_tcpci_read8(chip, TCPC_POWER_STATUS, &power_status); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to read TCPC_POWER_STATUS\n"); + + /* Chip level tcpci callbacks */ + chip->data.set_vbus = max_tcpci_set_vbus; + chip->data.start_drp_toggling = max_tcpci_start_toggling; + chip->data.TX_BUF_BYTE_x_hidden = true; + chip->data.init = tcpci_init; + chip->data.frs_sourcing_vbus = max_tcpci_frs_sourcing_vbus; + chip->data.auto_discharge_disconnect = true; + chip->data.vbus_vsafe0v = true; + chip->data.set_partner_usb_comm_capable = max_tcpci_set_partner_usb_comm_capable; + chip->data.check_contaminant = max_tcpci_check_contaminant; + chip->data.cable_comm_capable = true; + chip->data.attempt_vconn_swap_discovery = max_tcpci_attempt_vconn_swap_discovery; + + max_tcpci_init_regs(chip); + chip->tcpci = tcpci_register_port(chip->dev, &chip->data); + if (IS_ERR(chip->tcpci)) + return dev_err_probe(&client->dev, PTR_ERR(chip->tcpci), + "TCPCI port registration failed\n"); + + ret = devm_add_action_or_reset(&client->dev, + max_tcpci_unregister_tcpci_port, + chip->tcpci); + if (ret) + return ret; + + chip->port = tcpci_get_tcpm_port(chip->tcpci); + + ret = devm_request_threaded_irq(&client->dev, client->irq, max_tcpci_isr, max_tcpci_irq, + (IRQF_TRIGGER_LOW | IRQF_ONESHOT), dev_name(chip->dev), + chip); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "IRQ initialization failed\n"); + + ret = devm_device_init_wakeup(chip->dev); + if (ret) + return dev_err_probe(chip->dev, ret, "Failed to init wakeup\n"); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max_tcpci_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret = 0; + + if (client->irq && device_may_wakeup(dev)) + ret = disable_irq_wake(client->irq); + + return ret; +} + +static int max_tcpci_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret = 0; + + if (client->irq && device_may_wakeup(dev)) + ret = enable_irq_wake(client->irq); + + return ret; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(max_tcpci_pm_ops, max_tcpci_suspend, max_tcpci_resume); + +static const struct i2c_device_id max_tcpci_id[] = { + { .name = "maxtcpc" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max_tcpci_id); + +static const struct of_device_id max_tcpci_of_match[] = { + { .compatible = "maxim,max33359", }, + {}, +}; +MODULE_DEVICE_TABLE(of, max_tcpci_of_match); + +static struct i2c_driver max_tcpci_i2c_driver = { + .driver = { + .name = "maxtcpc", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = max_tcpci_of_match, + .pm = &max_tcpci_pm_ops, + }, + .probe = max_tcpci_probe, + .id_table = max_tcpci_id, +}; +module_i2c_driver(max_tcpci_i2c_driver); + +MODULE_AUTHOR("Badhri Jagan Sridharan "); +MODULE_DESCRIPTION("Maxim TCPCI based USB Type-C Port Controller Interface Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/tcpm/tcpci_mt6360.c b/drivers/usb/typec/tcpm/tcpci_mt6360.c new file mode 100644 index 000000000..881ffacbf --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpci_mt6360.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 MediaTek Inc. + * + * Author: ChiYuan Huang + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MT6360_REG_PHYCTRL1 0x80 +#define MT6360_REG_PHYCTRL3 0x82 +#define MT6360_REG_PHYCTRL7 0x86 +#define MT6360_REG_VCONNCTRL1 0x8C +#define MT6360_REG_MODECTRL2 0x8F +#define MT6360_REG_SWRESET 0xA0 +#define MT6360_REG_DEBCTRL1 0xA1 +#define MT6360_REG_DRPCTRL1 0xA2 +#define MT6360_REG_DRPCTRL2 0xA3 +#define MT6360_REG_I2CTORST 0xBF +#define MT6360_REG_PHYCTRL11 0xCA +#define MT6360_REG_RXCTRL1 0xCE +#define MT6360_REG_RXCTRL2 0xCF +#define MT6360_REG_CTDCTRL2 0xEC + +/* MT6360_REG_VCONNCTRL1 */ +#define MT6360_VCONNCL_ENABLE BIT(0) +/* MT6360_REG_RXCTRL2 */ +#define MT6360_OPEN40M_ENABLE BIT(7) +/* MT6360_REG_CTDCTRL2 */ +#define MT6360_RPONESHOT_ENABLE BIT(6) + +struct mt6360_tcpc_info { + struct tcpci_data tdata; + struct tcpci *tcpci; + struct device *dev; + int irq; +}; + +static inline int mt6360_tcpc_write16(struct regmap *regmap, + unsigned int reg, u16 val) +{ + return regmap_raw_write(regmap, reg, &val, sizeof(u16)); +} + +static int mt6360_tcpc_init(struct tcpci *tcpci, struct tcpci_data *tdata) +{ + struct regmap *regmap = tdata->regmap; + int ret; + + ret = regmap_write(regmap, MT6360_REG_SWRESET, 0x01); + if (ret) + return ret; + + /* after reset command, wait 1~2ms to wait IC action */ + usleep_range(1000, 2000); + + /* write all alert to masked */ + ret = mt6360_tcpc_write16(regmap, TCPC_ALERT_MASK, 0); + if (ret) + return ret; + + /* config I2C timeout reset enable , and timeout to 200ms */ + ret = regmap_write(regmap, MT6360_REG_I2CTORST, 0x8F); + if (ret) + return ret; + + /* config CC Detect Debounce : 26.7*val us */ + ret = regmap_write(regmap, MT6360_REG_DEBCTRL1, 0x10); + if (ret) + return ret; + + /* DRP Toggle Cycle : 51.2 + 6.4*val ms */ + ret = regmap_write(regmap, MT6360_REG_DRPCTRL1, 4); + if (ret) + return ret; + + /* DRP Duyt Ctrl : dcSRC: /1024 */ + ret = mt6360_tcpc_write16(regmap, MT6360_REG_DRPCTRL2, 330); + if (ret) + return ret; + + /* Enable VCONN Current Limit function */ + ret = regmap_update_bits(regmap, MT6360_REG_VCONNCTRL1, MT6360_VCONNCL_ENABLE, + MT6360_VCONNCL_ENABLE); + if (ret) + return ret; + + /* Enable cc open 40ms when pmic send vsysuv signal */ + ret = regmap_update_bits(regmap, MT6360_REG_RXCTRL2, MT6360_OPEN40M_ENABLE, + MT6360_OPEN40M_ENABLE); + if (ret) + return ret; + + /* Enable Rpdet oneshot detection */ + ret = regmap_update_bits(regmap, MT6360_REG_CTDCTRL2, MT6360_RPONESHOT_ENABLE, + MT6360_RPONESHOT_ENABLE); + if (ret) + return ret; + + /* BMC PHY */ + ret = mt6360_tcpc_write16(regmap, MT6360_REG_PHYCTRL1, 0x3A70); + if (ret) + return ret; + + ret = regmap_write(regmap, MT6360_REG_PHYCTRL3, 0x82); + if (ret) + return ret; + + ret = regmap_write(regmap, MT6360_REG_PHYCTRL7, 0x36); + if (ret) + return ret; + + ret = mt6360_tcpc_write16(regmap, MT6360_REG_PHYCTRL11, 0x3C60); + if (ret) + return ret; + + ret = regmap_write(regmap, MT6360_REG_RXCTRL1, 0xE8); + if (ret) + return ret; + + /* Set shipping mode off, AUTOIDLE on */ + return regmap_write(regmap, MT6360_REG_MODECTRL2, 0x7A); +} + +static irqreturn_t mt6360_irq(int irq, void *dev_id) +{ + struct mt6360_tcpc_info *mti = dev_id; + + return tcpci_irq(mti->tcpci); +} + +static int mt6360_tcpc_probe(struct platform_device *pdev) +{ + struct mt6360_tcpc_info *mti; + int ret; + + mti = devm_kzalloc(&pdev->dev, sizeof(*mti), GFP_KERNEL); + if (!mti) + return -ENOMEM; + + mti->dev = &pdev->dev; + + mti->tdata.regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!mti->tdata.regmap) { + dev_err(&pdev->dev, "Failed to get parent regmap\n"); + return -ENODEV; + } + + mti->irq = platform_get_irq_byname(pdev, "PD_IRQB"); + if (mti->irq < 0) + return mti->irq; + + mti->tdata.init = mt6360_tcpc_init; + mti->tcpci = tcpci_register_port(&pdev->dev, &mti->tdata); + if (IS_ERR(mti->tcpci)) { + dev_err(&pdev->dev, "Failed to register tcpci port\n"); + return PTR_ERR(mti->tcpci); + } + + ret = devm_request_threaded_irq(mti->dev, mti->irq, NULL, mt6360_irq, IRQF_ONESHOT, + dev_name(&pdev->dev), mti); + if (ret) { + dev_err(mti->dev, "Failed to register irq\n"); + tcpci_unregister_port(mti->tcpci); + return ret; + } + + device_init_wakeup(&pdev->dev, true); + platform_set_drvdata(pdev, mti); + + return 0; +} + +static void mt6360_tcpc_remove(struct platform_device *pdev) +{ + struct mt6360_tcpc_info *mti = platform_get_drvdata(pdev); + + disable_irq(mti->irq); + tcpci_unregister_port(mti->tcpci); +} + +static int __maybe_unused mt6360_tcpc_suspend(struct device *dev) +{ + struct mt6360_tcpc_info *mti = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + enable_irq_wake(mti->irq); + + return 0; +} + +static int __maybe_unused mt6360_tcpc_resume(struct device *dev) +{ + struct mt6360_tcpc_info *mti = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(mti->irq); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(mt6360_tcpc_pm_ops, mt6360_tcpc_suspend, mt6360_tcpc_resume); + +static const struct of_device_id __maybe_unused mt6360_tcpc_of_id[] = { + { .compatible = "mediatek,mt6360-tcpc", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mt6360_tcpc_of_id); + +static struct platform_driver mt6360_tcpc_driver = { + .driver = { + .name = "mt6360-tcpc", + .pm = &mt6360_tcpc_pm_ops, + .of_match_table = mt6360_tcpc_of_id, + }, + .probe = mt6360_tcpc_probe, + .remove = mt6360_tcpc_remove, +}; +module_platform_driver(mt6360_tcpc_driver); + +MODULE_AUTHOR("ChiYuan Huang "); +MODULE_DESCRIPTION("MT6360 USB Type-C Port Controller Interface Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/tcpm/tcpci_mt6370.c b/drivers/usb/typec/tcpm/tcpci_mt6370.c new file mode 100644 index 000000000..7d6c75c70 --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpci_mt6370.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 Richtek Technology Corp. + * + * Author: ChiYuan Huang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MT6370_REG_SYSCTRL8 0x9B + +#define MT6370_AUTOIDLE_MASK BIT(3) + +#define MT6370_VENDOR_ID 0x29CF +#define MT6370_TCPC_DID_A 0x2170 + +struct mt6370_priv { + struct device *dev; + struct regulator *vbus; + struct tcpci *tcpci; + struct tcpci_data tcpci_data; +}; + +static const struct reg_sequence mt6370_reg_init[] = { + REG_SEQ(0xA0, 0x1, 1000), + REG_SEQ(0x81, 0x38, 0), + REG_SEQ(0x82, 0x82, 0), + REG_SEQ(0xBA, 0xFC, 0), + REG_SEQ(0xBB, 0x50, 0), + REG_SEQ(0x9E, 0x8F, 0), + REG_SEQ(0xA1, 0x5, 0), + REG_SEQ(0xA2, 0x4, 0), + REG_SEQ(0xA3, 0x4A, 0), + REG_SEQ(0xA4, 0x01, 0), + REG_SEQ(0x95, 0x01, 0), + REG_SEQ(0x80, 0x71, 0), + REG_SEQ(0x9B, 0x3A, 1000), +}; + +static int mt6370_tcpc_init(struct tcpci *tcpci, struct tcpci_data *data) +{ + u16 did; + int ret; + + ret = regmap_register_patch(data->regmap, mt6370_reg_init, + ARRAY_SIZE(mt6370_reg_init)); + if (ret) + return ret; + + ret = regmap_raw_read(data->regmap, TCPC_BCD_DEV, &did, sizeof(u16)); + if (ret) + return ret; + + if (did == MT6370_TCPC_DID_A) + return regmap_write(data->regmap, TCPC_FAULT_CTRL, 0x80); + + return 0; +} + +static int mt6370_tcpc_set_vconn(struct tcpci *tcpci, struct tcpci_data *data, + bool enable) +{ + return regmap_update_bits(data->regmap, MT6370_REG_SYSCTRL8, + MT6370_AUTOIDLE_MASK, + enable ? 0 : MT6370_AUTOIDLE_MASK); +} + +static int mt6370_tcpc_set_vbus(struct tcpci *tcpci, struct tcpci_data *data, + bool source, bool sink) +{ + struct mt6370_priv *priv = container_of(data, struct mt6370_priv, + tcpci_data); + int ret; + + ret = regulator_is_enabled(priv->vbus); + if (ret < 0) + return ret; + + if (ret && !source) + return regulator_disable(priv->vbus); + + if (!ret && source) + return regulator_enable(priv->vbus); + + return 0; +} + +static irqreturn_t mt6370_irq_handler(int irq, void *dev_id) +{ + struct mt6370_priv *priv = dev_id; + + return tcpci_irq(priv->tcpci); +} + +static int mt6370_check_vendor_info(struct mt6370_priv *priv) +{ + struct regmap *regmap = priv->tcpci_data.regmap; + u16 vid; + int ret; + + ret = regmap_raw_read(regmap, TCPC_VENDOR_ID, &vid, sizeof(u16)); + if (ret) + return ret; + + if (vid != MT6370_VENDOR_ID) + return dev_err_probe(priv->dev, -ENODEV, + "Vendor ID not correct 0x%02x\n", vid); + + return 0; +} + +static void mt6370_unregister_tcpci_port(void *tcpci) +{ + tcpci_unregister_port(tcpci); +} + +static int mt6370_tcpc_probe(struct platform_device *pdev) +{ + struct mt6370_priv *priv; + struct device *dev = &pdev->dev; + int irq, ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + + priv->tcpci_data.regmap = dev_get_regmap(dev->parent, NULL); + if (!priv->tcpci_data.regmap) + return dev_err_probe(dev, -ENODEV, "Failed to init regmap\n"); + + ret = mt6370_check_vendor_info(priv); + if (ret) + return ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + /* Assign TCPCI feature and ops */ + priv->tcpci_data.auto_discharge_disconnect = 1; + priv->tcpci_data.init = mt6370_tcpc_init; + priv->tcpci_data.set_vconn = mt6370_tcpc_set_vconn; + + priv->vbus = devm_regulator_get_optional(dev, "vbus"); + if (!IS_ERR(priv->vbus)) + priv->tcpci_data.set_vbus = mt6370_tcpc_set_vbus; + + priv->tcpci = tcpci_register_port(dev, &priv->tcpci_data); + if (IS_ERR(priv->tcpci)) + return dev_err_probe(dev, PTR_ERR(priv->tcpci), + "Failed to register tcpci port\n"); + + ret = devm_add_action_or_reset(dev, mt6370_unregister_tcpci_port, priv->tcpci); + if (ret) + return ret; + + ret = devm_request_threaded_irq(dev, irq, NULL, mt6370_irq_handler, + IRQF_ONESHOT, dev_name(dev), priv); + if (ret) + return dev_err_probe(dev, ret, "Failed to allocate irq\n"); + + device_init_wakeup(dev, true); + dev_pm_set_wake_irq(dev, irq); + + return 0; +} + +static void mt6370_tcpc_remove(struct platform_device *pdev) +{ + dev_pm_clear_wake_irq(&pdev->dev); + device_init_wakeup(&pdev->dev, false); +} + +static const struct of_device_id mt6370_tcpc_devid_table[] = { + { .compatible = "mediatek,mt6370-tcpc" }, + {} +}; +MODULE_DEVICE_TABLE(of, mt6370_tcpc_devid_table); + +static struct platform_driver mt6370_tcpc_driver = { + .driver = { + .name = "mt6370-tcpc", + .of_match_table = mt6370_tcpc_devid_table, + }, + .probe = mt6370_tcpc_probe, + .remove = mt6370_tcpc_remove, +}; +module_platform_driver(mt6370_tcpc_driver); + +MODULE_AUTHOR("ChiYuan Huang "); +MODULE_DESCRIPTION("MT6370 USB Type-C Port Controller Interface Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/tcpm/tcpci_rt1711h.c b/drivers/usb/typec/tcpm/tcpci_rt1711h.c new file mode 100644 index 000000000..af8356df6 --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpci_rt1711h.c @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018, Richtek Technology Corporation + * + * Richtek RT1711H Type-C Chip Driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RT1711H_PHYCTRL1 0x80 +#define RT1711H_PHYCTRL2 0x81 + +#define RT1711H_RTCTRL4 0x93 +/* rx threshold of rd/rp: 1b0 for level 0.4V/0.7V, 1b1 for 0.35V/0.75V */ +#define RT1711H_BMCIO_RXDZSEL BIT(0) + +#define RT1711H_RTCTRL8 0x9B +/* Autoidle timeout = (tout * 2 + 1) * 6.4ms */ +#define RT1711H_RTCTRL8_SET(ck300, ship_off, auto_idle, tout) \ + (((ck300) << 7) | ((ship_off) << 5) | \ + ((auto_idle) << 3) | ((tout) & 0x07)) +#define RT1711H_AUTOIDLEEN BIT(3) +#define RT1711H_ENEXTMSG BIT(4) + +#define RT1711H_RTCTRL11 0x9E + +/* I2C timeout = (tout + 1) * 12.5ms */ +#define RT1711H_RTCTRL11_SET(en, tout) \ + (((en) << 7) | ((tout) & 0x0F)) + +#define RT1711H_RTCTRL13 0xA0 +#define RT1711H_RTCTRL14 0xA1 +#define RT1711H_RTCTRL15 0xA2 +#define RT1711H_RTCTRL16 0xA3 + +#define RT1711H_RTCTRL18 0xAF +/* 1b0 as fixed rx threshold of rd/rp 0.55V, 1b1 depends on RTCRTL4[0] */ +#define BMCIO_RXDZEN BIT(0) + +struct rt1711h_chip_info { + u32 rxdz_sel; + bool enable_pd30_extended_message; +}; + +struct rt1711h_chip { + struct tcpci_data data; + struct tcpci *tcpci; + struct device *dev; + struct regulator *vbus; + const struct rt1711h_chip_info *info; + bool src_en; +}; + +static int rt1711h_read16(struct rt1711h_chip *chip, unsigned int reg, u16 *val) +{ + return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u16)); +} + +static int rt1711h_write16(struct rt1711h_chip *chip, unsigned int reg, u16 val) +{ + return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u16)); +} + +static int rt1711h_read8(struct rt1711h_chip *chip, unsigned int reg, u8 *val) +{ + return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u8)); +} + +static int rt1711h_write8(struct rt1711h_chip *chip, unsigned int reg, u8 val) +{ + return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u8)); +} + +static const struct regmap_config rt1711h_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xFF, /* 0x80 .. 0xFF are vendor defined */ +}; + +static struct rt1711h_chip *tdata_to_rt1711h(struct tcpci_data *tdata) +{ + return container_of(tdata, struct rt1711h_chip, data); +} + +static int rt1711h_init(struct tcpci *tcpci, struct tcpci_data *tdata) +{ + struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); + struct regmap *regmap = chip->data.regmap; + int ret; + + /* CK 300K from 320K, shipping off, auto_idle enable, tout = 32ms */ + ret = rt1711h_write8(chip, RT1711H_RTCTRL8, + RT1711H_RTCTRL8_SET(0, 1, 1, 2)); + if (ret < 0) + return ret; + + /* Enable PD30 extended message for RT1715 */ + if (chip->info->enable_pd30_extended_message) { + ret = regmap_update_bits(regmap, RT1711H_RTCTRL8, + RT1711H_ENEXTMSG, RT1711H_ENEXTMSG); + if (ret < 0) + return ret; + } + + /* I2C reset : (val + 1) * 12.5ms */ + ret = rt1711h_write8(chip, RT1711H_RTCTRL11, + RT1711H_RTCTRL11_SET(1, 0x0F)); + if (ret < 0) + return ret; + + /* tTCPCfilter : (26.7 * val) us */ + ret = rt1711h_write8(chip, RT1711H_RTCTRL14, 0x0F); + if (ret < 0) + return ret; + + /* tDRP : (51.2 + 6.4 * val) ms */ + ret = rt1711h_write8(chip, RT1711H_RTCTRL15, 0x04); + if (ret < 0) + return ret; + + /* dcSRC.DRP : 33% */ + ret = rt1711h_write16(chip, RT1711H_RTCTRL16, 330); + if (ret < 0) + return ret; + + /* Enable phy discard retry, retry count 7, rx filter deglitch 100 us */ + ret = rt1711h_write8(chip, RT1711H_PHYCTRL1, 0xF1); + if (ret < 0) + return ret; + + /* Decrease wait time of BMC-encoded 1 bit from 2.67us to 2.55us */ + /* wait time : (val * .4167) us */ + return rt1711h_write8(chip, RT1711H_PHYCTRL2, 62); +} + +static int rt1711h_set_vbus(struct tcpci *tcpci, struct tcpci_data *tdata, + bool src, bool snk) +{ + struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); + int ret; + + if (chip->src_en == src) + return 0; + + if (src) + ret = regulator_enable(chip->vbus); + else + ret = regulator_disable(chip->vbus); + + if (!ret) + chip->src_en = src; + return ret; +} + +static int rt1711h_set_vconn(struct tcpci *tcpci, struct tcpci_data *tdata, + bool enable) +{ + struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); + + return regmap_update_bits(chip->data.regmap, RT1711H_RTCTRL8, + RT1711H_AUTOIDLEEN, enable ? 0 : RT1711H_AUTOIDLEEN); +} + +/* + * Selects the CC PHY noise filter voltage level according to the remote current + * CC voltage level. + * + * @status: The port's current cc status read from IC + * Return 0 if writes succeed; failure code otherwise + */ +static inline int rt1711h_init_cc_params(struct rt1711h_chip *chip, u8 status) +{ + int ret, cc1, cc2; + u8 role = 0; + u32 rxdz_en, rxdz_sel; + + ret = rt1711h_read8(chip, TCPC_ROLE_CTRL, &role); + if (ret < 0) + return ret; + + cc1 = tcpci_to_typec_cc(FIELD_GET(TCPC_CC_STATUS_CC1, status), + status & TCPC_CC_STATUS_TERM || + tcpc_presenting_rd(role, CC1)); + cc2 = tcpci_to_typec_cc(FIELD_GET(TCPC_CC_STATUS_CC2, status), + status & TCPC_CC_STATUS_TERM || + tcpc_presenting_rd(role, CC2)); + + if ((cc1 >= TYPEC_CC_RP_1_5 && cc2 < TYPEC_CC_RP_DEF) || + (cc2 >= TYPEC_CC_RP_1_5 && cc1 < TYPEC_CC_RP_DEF)) { + rxdz_en = BMCIO_RXDZEN; + rxdz_sel = chip->info->rxdz_sel; + } else { + rxdz_en = 0; + rxdz_sel = RT1711H_BMCIO_RXDZSEL; + } + + ret = regmap_update_bits(chip->data.regmap, RT1711H_RTCTRL18, + BMCIO_RXDZEN, rxdz_en); + if (ret < 0) + return ret; + + return regmap_update_bits(chip->data.regmap, RT1711H_RTCTRL4, + RT1711H_BMCIO_RXDZSEL, rxdz_sel); +} + +static int rt1711h_start_drp_toggling(struct tcpci *tcpci, + struct tcpci_data *tdata, + enum typec_cc_status cc) +{ + struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); + int ret; + unsigned int reg = 0; + + switch (cc) { + default: + case TYPEC_CC_RP_DEF: + reg |= FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL, + TCPC_ROLE_CTRL_RP_VAL_DEF); + break; + case TYPEC_CC_RP_1_5: + reg |= FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL, + TCPC_ROLE_CTRL_RP_VAL_1_5); + break; + case TYPEC_CC_RP_3_0: + reg |= FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL, + TCPC_ROLE_CTRL_RP_VAL_3_0); + break; + } + + if (cc == TYPEC_CC_RD) + reg |= (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RD) + | FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RD)); + else + reg |= (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP) + | FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP)); + + ret = rt1711h_write8(chip, TCPC_ROLE_CTRL, reg); + if (ret < 0) + return ret; + usleep_range(500, 1000); + + return 0; +} + +static irqreturn_t rt1711h_irq(int irq, void *dev_id) +{ + int ret; + u16 alert; + u8 status; + struct rt1711h_chip *chip = dev_id; + + if (!chip->tcpci) + return IRQ_HANDLED; + + ret = rt1711h_read16(chip, TCPC_ALERT, &alert); + if (ret < 0) + goto out; + + if (alert & TCPC_ALERT_CC_STATUS) { + ret = rt1711h_read8(chip, TCPC_CC_STATUS, &status); + if (ret < 0) + goto out; + /* Clear cc change event triggered by starting toggling */ + if (status & TCPC_CC_STATUS_TOGGLING) + rt1711h_write8(chip, TCPC_ALERT, TCPC_ALERT_CC_STATUS); + else + rt1711h_init_cc_params(chip, status); + } + +out: + return tcpci_irq(chip->tcpci); +} + +static int rt1711h_sw_reset(struct rt1711h_chip *chip) +{ + int ret; + + ret = rt1711h_write8(chip, RT1711H_RTCTRL13, 0x01); + if (ret < 0) + return ret; + + usleep_range(1000, 2000); + return 0; +} + +static void rt1711h_unregister_tcpci_port(void *tcpci); + +static int rt1711h_probe(struct i2c_client *client) +{ + int ret; + struct rt1711h_chip *chip; + const u16 alert_mask = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_DISCARDED | + TCPC_ALERT_TX_FAILED | TCPC_ALERT_RX_HARD_RST | + TCPC_ALERT_RX_STATUS | TCPC_ALERT_POWER_STATUS | + TCPC_ALERT_CC_STATUS | TCPC_ALERT_RX_BUF_OVF | + TCPC_ALERT_FAULT; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->info = i2c_get_match_data(client); + + chip->data.regmap = devm_regmap_init_i2c(client, + &rt1711h_regmap_config); + if (IS_ERR(chip->data.regmap)) + return PTR_ERR(chip->data.regmap); + + chip->dev = &client->dev; + i2c_set_clientdata(client, chip); + + ret = rt1711h_sw_reset(chip); + if (ret < 0) + return ret; + + /* Disable chip interrupts before requesting irq */ + ret = rt1711h_write16(chip, TCPC_ALERT_MASK, 0); + if (ret < 0) + return ret; + + chip->vbus = devm_regulator_get(&client->dev, "vbus"); + if (IS_ERR(chip->vbus)) + return PTR_ERR(chip->vbus); + + chip->data.init = rt1711h_init; + chip->data.set_vbus = rt1711h_set_vbus; + chip->data.set_vconn = rt1711h_set_vconn; + chip->data.start_drp_toggling = rt1711h_start_drp_toggling; + chip->tcpci = tcpci_register_port(chip->dev, &chip->data); + if (IS_ERR_OR_NULL(chip->tcpci)) + return PTR_ERR(chip->tcpci); + + ret = devm_add_action_or_reset(chip->dev, rt1711h_unregister_tcpci_port, chip->tcpci); + if (ret) + return ret; + + ret = devm_request_threaded_irq(chip->dev, client->irq, NULL, + rt1711h_irq, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + dev_name(chip->dev), chip); + if (ret < 0) + return ret; + + /* Enable alert interrupts */ + ret = rt1711h_write16(chip, TCPC_ALERT_MASK, alert_mask); + if (ret < 0) + return ret; + + enable_irq_wake(client->irq); + + return 0; +} + +static void rt1711h_unregister_tcpci_port(void *tcpci) +{ + tcpci_unregister_port(tcpci); +} + +static const struct rt1711h_chip_info rt1711h = { +}; + +static const struct rt1711h_chip_info rt1715 = { + .rxdz_sel = RT1711H_BMCIO_RXDZSEL, + .enable_pd30_extended_message = true, +}; + +static const struct i2c_device_id rt1711h_id[] = { + { .name = "et7304", .driver_data = (kernel_ulong_t)&rt1715 }, + { .name = "rt1711h", .driver_data = (kernel_ulong_t)&rt1711h }, + { .name = "rt1715", .driver_data = (kernel_ulong_t)&rt1715 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt1711h_id); + +static const struct of_device_id rt1711h_of_match[] = { + { .compatible = "etekmicro,et7304", .data = &rt1715 }, + { .compatible = "richtek,rt1711h", .data = &rt1711h }, + { .compatible = "richtek,rt1715", .data = &rt1715 }, + {} +}; +MODULE_DEVICE_TABLE(of, rt1711h_of_match); + +static struct i2c_driver rt1711h_i2c_driver = { + .driver = { + .name = "rt1711h", + .of_match_table = rt1711h_of_match, + }, + .probe = rt1711h_probe, + .id_table = rt1711h_id, +}; +module_i2c_driver(rt1711h_i2c_driver); + +MODULE_AUTHOR("ShuFan Lee "); +MODULE_DESCRIPTION("RT1711H USB Type-C Port Controller Interface Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c new file mode 100644 index 000000000..a8cd1959c --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -0,0 +1,8985 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2015-2017 Google, Inc + * + * USB Power Delivery protocol stack. + */ + +#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 FOREACH_STATE(S) \ + S(INVALID_STATE), \ + S(TOGGLING), \ + S(CHECK_CONTAMINANT), \ + S(SRC_UNATTACHED), \ + S(SRC_ATTACH_WAIT), \ + S(SRC_ATTACHED), \ + S(SRC_STARTUP), \ + S(SRC_SEND_CAPABILITIES), \ + S(SRC_SEND_CAPABILITIES_TIMEOUT), \ + S(SRC_NEGOTIATE_CAPABILITIES), \ + S(SRC_TRANSITION_SUPPLY), \ + S(SRC_READY), \ + S(SRC_WAIT_NEW_CAPABILITIES), \ + \ + S(SNK_UNATTACHED), \ + S(SNK_ATTACH_WAIT), \ + S(SNK_DEBOUNCED), \ + S(SNK_ATTACHED), \ + S(SNK_STARTUP), \ + S(SNK_DISCOVERY), \ + S(SNK_DISCOVERY_DEBOUNCE), \ + S(SNK_DISCOVERY_DEBOUNCE_DONE), \ + S(SNK_WAIT_CAPABILITIES), \ + S(SNK_WAIT_CAPABILITIES_TIMEOUT), \ + S(SNK_NEGOTIATE_CAPABILITIES), \ + S(SNK_NEGOTIATE_PPS_CAPABILITIES), \ + S(SNK_NEGOTIATE_SPR_AVS_CAPABILITIES), \ + S(SNK_TRANSITION_SINK), \ + S(SNK_TRANSITION_SINK_VBUS), \ + S(SNK_READY), \ + \ + S(ACC_UNATTACHED), \ + S(DEBUG_ACC_ATTACHED), \ + S(DEBUG_ACC_DEBOUNCE), \ + S(AUDIO_ACC_ATTACHED), \ + S(AUDIO_ACC_DEBOUNCE), \ + \ + S(HARD_RESET_SEND), \ + S(HARD_RESET_START), \ + S(SRC_HARD_RESET_VBUS_OFF), \ + S(SRC_HARD_RESET_VBUS_ON), \ + S(SNK_HARD_RESET_SINK_OFF), \ + S(SNK_HARD_RESET_WAIT_VBUS), \ + S(SNK_HARD_RESET_SINK_ON), \ + \ + S(SOFT_RESET), \ + S(SRC_SOFT_RESET_WAIT_SNK_TX), \ + S(SNK_SOFT_RESET), \ + S(SOFT_RESET_SEND), \ + \ + S(DR_SWAP_ACCEPT), \ + S(DR_SWAP_SEND), \ + S(DR_SWAP_SEND_TIMEOUT), \ + S(DR_SWAP_CANCEL), \ + S(DR_SWAP_CHANGE_DR), \ + \ + S(PR_SWAP_ACCEPT), \ + S(PR_SWAP_SEND), \ + S(PR_SWAP_SEND_TIMEOUT), \ + S(PR_SWAP_CANCEL), \ + S(PR_SWAP_START), \ + S(PR_SWAP_SRC_SNK_TRANSITION_OFF), \ + S(PR_SWAP_SRC_SNK_SOURCE_OFF), \ + S(PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED), \ + S(PR_SWAP_SRC_SNK_SINK_ON), \ + S(PR_SWAP_SNK_SRC_SINK_OFF), \ + S(PR_SWAP_SNK_SRC_SOURCE_ON), \ + S(PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP), \ + \ + S(VCONN_SWAP_ACCEPT), \ + S(VCONN_SWAP_SEND), \ + S(VCONN_SWAP_SEND_TIMEOUT), \ + S(VCONN_SWAP_CANCEL), \ + S(VCONN_SWAP_START), \ + S(VCONN_SWAP_WAIT_FOR_VCONN), \ + S(VCONN_SWAP_TURN_ON_VCONN), \ + S(VCONN_SWAP_TURN_OFF_VCONN), \ + S(VCONN_SWAP_SEND_SOFT_RESET), \ + \ + S(FR_SWAP_SEND), \ + S(FR_SWAP_SEND_TIMEOUT), \ + S(FR_SWAP_SNK_SRC_TRANSITION_TO_OFF), \ + S(FR_SWAP_SNK_SRC_NEW_SINK_READY), \ + S(FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED), \ + S(FR_SWAP_CANCEL), \ + \ + S(SNK_TRY), \ + S(SNK_TRY_WAIT), \ + S(SNK_TRY_WAIT_DEBOUNCE), \ + S(SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS), \ + S(SRC_TRYWAIT), \ + S(SRC_TRYWAIT_DEBOUNCE), \ + S(SRC_TRYWAIT_UNATTACHED), \ + \ + S(SRC_TRY), \ + S(SRC_TRY_WAIT), \ + S(SRC_TRY_DEBOUNCE), \ + S(SNK_TRYWAIT), \ + S(SNK_TRYWAIT_DEBOUNCE), \ + S(SNK_TRYWAIT_VBUS), \ + S(BIST_RX), \ + \ + S(GET_STATUS_SEND), \ + S(GET_STATUS_SEND_TIMEOUT), \ + S(GET_PPS_STATUS_SEND), \ + S(GET_PPS_STATUS_SEND_TIMEOUT), \ + \ + S(GET_SINK_CAP), \ + S(GET_SINK_CAP_TIMEOUT), \ + \ + S(ERROR_RECOVERY), \ + S(PORT_RESET), \ + S(PORT_RESET_WAIT_OFF), \ + \ + S(AMS_START), \ + S(CHUNK_NOT_SUPP), \ + \ + S(SRC_VDM_IDENTITY_REQUEST) + +#define FOREACH_AMS(S) \ + S(NONE_AMS), \ + S(POWER_NEGOTIATION), \ + S(GOTOMIN), \ + S(SOFT_RESET_AMS), \ + S(HARD_RESET), \ + S(CABLE_RESET), \ + S(GET_SOURCE_CAPABILITIES), \ + S(GET_SINK_CAPABILITIES), \ + S(POWER_ROLE_SWAP), \ + S(FAST_ROLE_SWAP), \ + S(DATA_ROLE_SWAP), \ + S(VCONN_SWAP), \ + S(SOURCE_ALERT), \ + S(GETTING_SOURCE_EXTENDED_CAPABILITIES),\ + S(GETTING_SOURCE_SINK_STATUS), \ + S(GETTING_BATTERY_CAPABILITIES), \ + S(GETTING_BATTERY_STATUS), \ + S(GETTING_MANUFACTURER_INFORMATION), \ + S(SECURITY), \ + S(FIRMWARE_UPDATE), \ + S(DISCOVER_IDENTITY), \ + S(SOURCE_STARTUP_CABLE_PLUG_DISCOVER_IDENTITY), \ + S(DISCOVER_SVIDS), \ + S(DISCOVER_MODES), \ + S(DFP_TO_UFP_ENTER_MODE), \ + S(DFP_TO_UFP_EXIT_MODE), \ + S(DFP_TO_CABLE_PLUG_ENTER_MODE), \ + S(DFP_TO_CABLE_PLUG_EXIT_MODE), \ + S(ATTENTION), \ + S(BIST), \ + S(UNSTRUCTURED_VDMS), \ + S(STRUCTURED_VDMS), \ + S(COUNTRY_INFO), \ + S(COUNTRY_CODES), \ + S(REVISION_INFORMATION), \ + S(GETTING_SINK_EXTENDED_CAPABILITIES) + +#define GENERATE_ENUM(e) e +#define GENERATE_STRING(s) #s + +enum tcpm_state { + FOREACH_STATE(GENERATE_ENUM) +}; + +static const char * const tcpm_states[] = { + FOREACH_STATE(GENERATE_STRING) +}; + +enum tcpm_ams { + FOREACH_AMS(GENERATE_ENUM) +}; + +static const char * const tcpm_ams_str[] = { + FOREACH_AMS(GENERATE_STRING) +}; + +#define FOREACH_VDM_DISCOVERY(S) \ + S(VDM_DISCOVERY_UNKNOWN), \ + S(VDM_DISCOVERY_PARTNER_IDENT), \ + S(VDM_DISCOVERY_CABLE_IDENT), \ + S(VDM_DISCOVERY_PARTNER_SVIDS), \ + S(VDM_DISCOVERY_PARTNER_MODES), \ + S(VDM_DISCOVERY_CABLE_SVIDS), \ + S(VDM_DISCOVERY_CABLE_MODES), \ + S(VDM_DISCOVERY_COMPLETE) + +enum vdm_discovery_states { + FOREACH_VDM_DISCOVERY(GENERATE_ENUM) +}; + +static const char * const vdm_discovery_state_strings[] = { + FOREACH_VDM_DISCOVERY(GENERATE_STRING) +}; + +enum vdm_states { + VDM_STATE_ERR_BUSY = -3, + VDM_STATE_ERR_SEND = -2, + VDM_STATE_ERR_TMOUT = -1, + VDM_STATE_DONE = 0, + /* Anything >0 represents an active state */ + VDM_STATE_READY = 1, + VDM_STATE_BUSY = 2, + VDM_STATE_WAIT_RSP_BUSY = 3, + VDM_STATE_SEND_MESSAGE = 4, +}; + +enum pd_msg_request { + PD_MSG_NONE = 0, + PD_MSG_CTRL_REJECT, + PD_MSG_CTRL_WAIT, + PD_MSG_CTRL_NOT_SUPP, + PD_MSG_DATA_SINK_CAP, + PD_MSG_DATA_SOURCE_CAP, + PD_MSG_DATA_REV, + PD_MSG_EXT_SINK_CAP_EXT, + PD_MSG_DATA_BATT_STATUS, + PD_MSG_EXT_BATT_CAP, +}; + +enum adev_actions { + ADEV_NONE = 0, + ADEV_NOTIFY_USB_AND_QUEUE_VDM, + ADEV_QUEUE_VDM, + ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL, + ADEV_ATTENTION, +}; + +/* + * Initial current capability of the new source when vSafe5V is applied during PD3.0 Fast Role Swap. + * Based on "Table 6-14 Fixed Supply PDO - Sink" of "USB Power Delivery Specification Revision 3.0, + * Version 1.2" + */ +enum frs_typec_current { + FRS_NOT_SUPPORTED, + FRS_DEFAULT_POWER, + FRS_5V_1P5A, + FRS_5V_3A, +}; + +/* Events from low level driver */ + +#define TCPM_CC_EVENT BIT(0) +#define TCPM_VBUS_EVENT BIT(1) +#define TCPM_RESET_EVENT BIT(2) +#define TCPM_FRS_EVENT BIT(3) +#define TCPM_SOURCING_VBUS BIT(4) +#define TCPM_PORT_CLEAN BIT(5) +#define TCPM_PORT_ERROR BIT(6) + +#define LOG_BUFFER_ENTRIES 1024 +#define LOG_BUFFER_ENTRY_SIZE 128 + +/* Alternate mode support */ + +#define SVID_DISCOVERY_MAX 16 +#define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX) + +#define GET_SINK_CAP_RETRY_MS 100 +#define SEND_DISCOVERY_VDM_RETRY_MS 100 + +struct pd_mode_data { + int svid_index; /* current SVID index */ + int nsvids; + u16 svids[SVID_DISCOVERY_MAX]; + int altmodes; /* number of alternate modes */ + struct typec_altmode_desc altmode_desc[ALTMODE_DISCOVERY_MAX]; +}; + +/* + * @min_volt: Actual min voltage at the local port + * @req_min_volt: Requested min voltage to the port partner + * @max_volt: Actual max voltage at the local port + * @req_max_volt: Requested max voltage to the port partner + * @max_curr: Actual max current at the local port + * @req_max_curr: Requested max current of the port partner + * @req_out_volt: Requested output voltage to the port partner + * @req_op_curr: Requested operating current to the port partner + * @supported: Parter has at least one APDO hence supports PPS + * @active: PPS mode is active + */ +struct pd_pps_data { + u32 min_volt; + u32 req_min_volt; + u32 max_volt; + u32 req_max_volt; + u32 max_curr; + u32 req_max_curr; + u32 req_out_volt; + u32 req_op_curr; + bool supported; + bool active; +}; + +enum spr_avs_status { + SPR_AVS_UNKNOWN, + SPR_AVS_NOT_SUPPORTED, + SPR_AVS_SUPPORTED +}; + +static const char * const spr_avs_status_strings[] = { + [SPR_AVS_UNKNOWN] = "Unknown", + [SPR_AVS_SUPPORTED] = "Supported", + [SPR_AVS_NOT_SUPPORTED] = "Not Supported", +}; + +/* + * Standard Power Range Adjustable Voltage Supply (SPR - AVS) data + * @max_current_ma_9v_to_15v: Max current for 9V to 15V range derived from + * source cap & sink cap + * @max_current_ma_15v_to_20v: Max current for 15V to 20V range derived from + * source cap & sink cap + * @req_op_curr_ma: Requested operating current to the port partner acting as source + * @req_out_volt_mv: Requested output voltage to the port partner acting as source + * @max_out_volt_mv: Max SPR voltage supported by the port and the port partner + * @max_current_ma; MAX SPR current supported by the port and the port partner + * @port_partner_src_status: SPR AVS status of port partner acting as source + * @port_partner_src_pdo_index: PDO index of SPR AVS cap of the port partner + * acting as source. Valid only when + * port_partner_src_status is SPR_AVS_SUPPORTED. + * @port_snk_status: SPR AVS status of the local port acting as sink. + * @port_snk_pdo_index: PDO index of SPR AVS cap of local port acting as sink + * @active: True when the local port acting as the sink has negotiated SPR AVS + * with the partner acting as source. + */ +struct pd_spr_avs_data { + u32 max_current_ma_9v_to_15v; + u32 max_current_ma_15v_to_20v; + u32 req_op_curr_ma; + u32 req_out_volt_mv; + u32 max_out_volt_mv; + u32 max_current_ma; + enum spr_avs_status port_partner_src_status; + unsigned int port_partner_src_pdo_index; + enum spr_avs_status port_snk_status; + unsigned int port_snk_pdo_index; + bool active; +}; + +struct pd_data { + struct usb_power_delivery *pd; + struct usb_power_delivery_capabilities *source_cap; + struct usb_power_delivery_capabilities_desc source_desc; + struct usb_power_delivery_capabilities *sink_cap; + struct usb_power_delivery_capabilities_desc sink_desc; + unsigned int operating_snk_mw; +}; + +#define PD_CAP_REV10 0x1 +#define PD_CAP_REV20 0x2 +#define PD_CAP_REV30 0x3 + +struct pd_revision_info { + u8 rev_major; + u8 rev_minor; + u8 ver_major; + u8 ver_minor; +}; + +/* + * @sink_wait_cap_time: Deadline (in ms) for tTypeCSinkWaitCap timer + * @ps_src_wait_off_time: Deadline (in ms) for tPSSourceOff timer + * @cc_debounce_time: Deadline (in ms) for tCCDebounce timer + */ +struct pd_timings { + u32 sink_wait_cap_time; + u32 ps_src_off_time; + u32 cc_debounce_time; + u32 snk_bc12_cmpletion_time; +}; + +/* Convert microwatt to watt */ +#define UW_TO_W(pow) (div_u64((pow), 1000000)) + +/* + * As per USB PD Spec Rev 3.18 (Sec. 6.5.13.11), the number of fixed batteries + * that a port can be queried is restricted to 4. + */ +#define MAX_NUM_FIXED_BATT 4 + +#define BATTERY_PROPERTY_UNKNOWN 0xffff + +/* + * struct pd_identifier - Contains info about PD identifiers + * @vid: Vendor ID (assigned by USB-IF) + * @pid: Product ID (assigned by manufacturer) + * @xid: Value assigned by USB-IF for product + */ +struct pd_identifier { + u16 vid; + u16 pid; + u32 xid; +}; + +/* + * struct sink_caps_ext_data - Sink extended capability data + * @load_step: Indicates the load step slew rate. Value of 0 indicates 150mA/us + * & 1 indicates 500 mA/us + * @load_char: Snk overload characteristics + * @compliance: Types of sources the sink has been tested & certified on + * @modes: Charging caps & power sources supported + * @spr_min_pdp: Sink Minimum PDP for SPR mode (in Watts) + * @spr_op_pdp: Sink Operational PDP for SPR mode (in Watts) + * @spr_max_pdp: Sink Maximum PDP for SPR mode (in Watts) + */ +struct sink_caps_ext_data { + u8 load_step; + u16 load_char; + u8 compliance; + u8 modes; + u8 spr_min_pdp; + u8 spr_op_pdp; + u8 spr_max_pdp; +}; + +enum aug_req_type { + PD_PPS, + PD_SPR_AVS, +}; + +struct tcpm_port { + struct device *dev; + + struct mutex lock; /* tcpm state machine lock */ + struct kthread_worker *wq; + + struct typec_capability typec_caps; + struct typec_port *typec_port; + + struct tcpc_dev *tcpc; + struct usb_role_switch *role_sw; + + enum typec_role vconn_role; + enum typec_role pwr_role; + enum typec_data_role data_role; + enum typec_pwr_opmode pwr_opmode; + + struct usb_pd_identity partner_ident; + struct typec_partner_desc partner_desc; + struct typec_partner *partner; + + struct usb_pd_identity cable_ident; + struct typec_cable_desc cable_desc; + struct typec_cable *cable; + struct typec_plug_desc plug_prime_desc; + struct typec_plug *plug_prime; + + enum typec_cc_status cc_req; + enum typec_cc_status src_rp; /* work only if pd_supported == false */ + + enum typec_cc_status cc1; + enum typec_cc_status cc2; + enum typec_cc_polarity polarity; + + bool attached; + bool connected; + bool registered; + bool pd_supported; + enum typec_port_type port_type; + + /* + * Set to true when vbus is greater than VSAFE5V min. + * Set to false when vbus falls below vSinkDisconnect max threshold. + */ + bool vbus_present; + + /* + * Set to true when vbus is less than VSAFE0V max. + * Set to false when vbus is greater than VSAFE0V max. + */ + bool vbus_vsafe0v; + + bool vbus_never_low; + bool vbus_source; + bool vbus_charge; + + bool op_vsafe5v; + + int try_role; + int try_snk_count; + int try_src_count; + + enum pd_msg_request queued_message; + + enum tcpm_state enter_state; + enum tcpm_state prev_state; + enum tcpm_state state; + enum tcpm_state delayed_state; + ktime_t delayed_runtime; + unsigned long delay_ms; + + spinlock_t pd_event_lock; + u32 pd_events; + + struct kthread_work event_work; + struct hrtimer state_machine_timer; + struct kthread_work state_machine; + struct hrtimer vdm_state_machine_timer; + struct kthread_work vdm_state_machine; + struct hrtimer enable_frs_timer; + struct kthread_work enable_frs; + struct hrtimer vdm_discovery_timer; + struct kthread_work vdm_discovery_work; + bool state_machine_running; + /* Set to true when VDM State Machine has following actions. */ + bool vdm_sm_running; + + struct completion tx_complete; + enum tcpm_transmit_status tx_status; + + struct mutex swap_lock; /* swap command lock */ + bool swap_pending; + bool non_pd_role_swap; + struct completion swap_complete; + int swap_status; + + unsigned int negotiated_rev; + unsigned int message_id; + unsigned int caps_count; + unsigned int hard_reset_count; + bool pd_capable; + bool explicit_contract; + unsigned int rx_msgid; + + /* USB PD objects */ + struct usb_power_delivery **pds; + struct pd_data **pd_list; + struct usb_power_delivery_capabilities *port_source_caps; + struct usb_power_delivery_capabilities *port_sink_caps; + struct usb_power_delivery *partner_pd; + struct usb_power_delivery_capabilities *partner_source_caps; + struct usb_power_delivery_capabilities *partner_sink_caps; + struct usb_power_delivery *selected_pd; + + /* Partner capabilities/requests */ + u32 sink_request; + u32 source_caps[PDO_MAX_OBJECTS]; + unsigned int nr_source_caps; + u32 sink_caps[PDO_MAX_OBJECTS]; + unsigned int nr_sink_caps; + + /* Local capabilities */ + unsigned int pd_count; + u32 src_pdo[PDO_MAX_OBJECTS]; + unsigned int nr_src_pdo; + u32 snk_pdo[PDO_MAX_OBJECTS]; + unsigned int nr_snk_pdo; + u32 snk_vdo_v1[VDO_MAX_OBJECTS]; + unsigned int nr_snk_vdo_v1; + u32 snk_vdo[VDO_MAX_OBJECTS]; + unsigned int nr_snk_vdo; + + unsigned int operating_snk_mw; + bool update_sink_caps; + + /* Requested current / voltage to the port partner */ + u32 req_current_limit; + u32 req_supply_voltage; + /* Actual current / voltage limit of the local port */ + u32 current_limit; + u32 supply_voltage; + + /* Used to export TA voltage and current */ + struct power_supply *psy; + struct power_supply_desc psy_desc; + enum power_supply_usb_type usb_type; + + u32 bist_request; + + /* VDM Discovery State to determine message sent */ + enum vdm_discovery_states vdm_discovery_state; + + /* PD state for Vendor Defined Messages */ + enum vdm_states vdm_state; + u32 vdm_retries; + /* next Vendor Defined Message to send */ + u32 vdo_data[VDO_MAX_SIZE]; + u8 vdo_count; + /* VDO to retry if UFP responder replied busy */ + u32 vdo_retry; + + /* PPS */ + struct pd_pps_data pps_data; + + /* SPR AVS */ + struct pd_spr_avs_data spr_avs_data; + + /* Augmented supply request - PPS; SPR_AVS */ + struct completion aug_supply_req_complete; + bool aug_supply_req_pending; + int aug_supply_req_status; + + /* Alternate mode data */ + struct pd_mode_data mode_data; + struct pd_mode_data mode_data_prime; + struct typec_altmode *partner_altmode[ALTMODE_DISCOVERY_MAX]; + struct typec_altmode *plug_prime_altmode[ALTMODE_DISCOVERY_MAX]; + struct typec_altmode *port_altmode[ALTMODE_DISCOVERY_MAX]; + + /* Deadline in jiffies to exit src_try_wait state */ + unsigned long max_wait; + + /* port belongs to a self powered device */ + bool self_powered; + + /* Sink FRS */ + enum frs_typec_current new_source_frs_current; + + /* Sink caps have been queried */ + bool sink_cap_done; + + /* Collision Avoidance and Atomic Message Sequence */ + enum tcpm_state upcoming_state; + enum tcpm_ams ams; + enum tcpm_ams next_ams; + bool in_ams; + + /* Auto vbus discharge status */ + bool auto_vbus_discharge_enabled; + + /* + * When set, port requests PD_P_SNK_STDBY_MW upon entering SNK_DISCOVERY and + * the actual current limit after RX of PD_CTRL_PSRDY for PD link, + * SNK_READY for non-pd link. + */ + bool slow_charger_loop; + + /* + * When true indicates that the lower level drivers indicate potential presence + * of contaminant in the connector pins based on the tcpm state machine + * transitions. + */ + bool potential_contaminant; + + /* SOP* Related Fields */ + /* + * tx_sop_type determines which SOP* a message is being sent on. + * For messages that are queued and not sent immediately such as in + * tcpm_queue_message or messages that send after state changes, + * the tx_sop_type is set accordingly. + */ + enum tcpm_transmit_type tx_sop_type; + /* + * Prior to discovering the port partner's Specification Revision, the + * Vconn source and cable plug will use the lower of their two revisions. + * + * When the port partner's Specification Revision is discovered, the following + * rules are put in place. + * 1. If the cable revision (1) is lower than the revision negotiated + * between the port and partner (2), the port and partner will communicate + * on revision (2), but the port and cable will communicate on revision (1). + * 2. If the cable revision (1) is higher than the revision negotiated + * between the port and partner (2), the port and partner will communicate + * on revision (2), and the port and cable will communicate on revision (2) + * as well. + */ + unsigned int negotiated_rev_prime; + /* + * Each SOP* type must maintain their own tx and rx message IDs + */ + unsigned int message_id_prime; + unsigned int rx_msgid_prime; + + /* Timer deadline values configured at runtime */ + struct pd_timings timings; + + /* Indicates maximum (revision, version) supported */ + struct pd_revision_info pd_rev; + + struct pd_identifier pd_ident; + struct sink_caps_ext_data sink_caps_ext; + struct power_supply **fixed_batt; + u32 fixed_batt_cnt; + u32 batt_request_id; +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; + struct mutex logbuffer_lock; /* log buffer access lock */ + int logbuffer_head; + int logbuffer_tail; + u8 *logbuffer[LOG_BUFFER_ENTRIES]; +#endif +}; + +struct pd_rx_event { + struct kthread_work work; + struct tcpm_port *port; + struct pd_message msg; + enum tcpm_transmit_type rx_sop_type; +}; + +struct altmode_vdm_event { + struct kthread_work work; + struct tcpm_port *port; + u32 header; + int cnt; + enum tcpm_transmit_type tx_sop_type; + u32 data[] __counted_by(cnt); +}; + +static const char * const pd_rev[] = { + [PD_REV10] = "rev1", + [PD_REV20] = "rev2", + [PD_REV30] = "rev3", +}; + +#define tcpm_cc_is_sink(cc) \ + ((cc) == TYPEC_CC_RP_DEF || (cc) == TYPEC_CC_RP_1_5 || \ + (cc) == TYPEC_CC_RP_3_0) + +/* As long as cc is pulled up, we can consider it as sink. */ +#define tcpm_port_is_sink(port) \ + (tcpm_cc_is_sink((port)->cc1) || tcpm_cc_is_sink((port)->cc2)) + +#define tcpm_cc_is_source(cc) ((cc) == TYPEC_CC_RD) +#define tcpm_cc_is_audio(cc) ((cc) == TYPEC_CC_RA) +#define tcpm_cc_is_open(cc) ((cc) == TYPEC_CC_OPEN) + +#define tcpm_port_is_source(port) \ + ((tcpm_cc_is_source((port)->cc1) && \ + !tcpm_cc_is_source((port)->cc2)) || \ + (tcpm_cc_is_source((port)->cc2) && \ + !tcpm_cc_is_source((port)->cc1))) + +#define tcpm_port_is_debug_source(port) \ + (tcpm_cc_is_source((port)->cc1) && tcpm_cc_is_source((port)->cc2)) + +#define tcpm_port_is_debug_sink(port) \ + (tcpm_cc_is_sink((port)->cc1) && tcpm_cc_is_sink((port)->cc2)) + +#define tcpm_port_is_debug(port) \ + (tcpm_port_is_debug_source(port) || tcpm_port_is_debug_sink(port)) + +#define tcpm_port_is_audio(port) \ + (tcpm_cc_is_audio((port)->cc1) && tcpm_cc_is_audio((port)->cc2)) + +#define tcpm_port_is_audio_detached(port) \ + ((tcpm_cc_is_audio((port)->cc1) && tcpm_cc_is_open((port)->cc2)) || \ + (tcpm_cc_is_audio((port)->cc2) && tcpm_cc_is_open((port)->cc1))) + +#define tcpm_try_snk(port) \ + ((port)->try_snk_count == 0 && (port)->try_role == TYPEC_SINK && \ + (port)->port_type == TYPEC_PORT_DRP) + +#define tcpm_try_src(port) \ + ((port)->try_src_count == 0 && (port)->try_role == TYPEC_SOURCE && \ + (port)->port_type == TYPEC_PORT_DRP) + +#define tcpm_data_role_for_source(port) \ + ((port)->typec_caps.data == TYPEC_PORT_UFP ? \ + TYPEC_DEVICE : TYPEC_HOST) + +#define tcpm_data_role_for_sink(port) \ + ((port)->typec_caps.data == TYPEC_PORT_DFP ? \ + TYPEC_HOST : TYPEC_DEVICE) + +#define tcpm_sink_tx_ok(port) \ + (tcpm_port_is_sink(port) && \ + ((port)->cc1 == TYPEC_CC_RP_3_0 || (port)->cc2 == TYPEC_CC_RP_3_0)) + +#define tcpm_wait_for_discharge(port) \ + (((port)->auto_vbus_discharge_enabled && !(port)->vbus_vsafe0v) ? PD_T_SAFE_0V : 0) + +#define tcpm_can_send_vdm(state) \ + ((state == SRC_READY || state == SNK_READY || state == SRC_VDM_IDENTITY_REQUEST)) + +static enum tcpm_state tcpm_default_state(struct tcpm_port *port) +{ + if (port->port_type == TYPEC_PORT_DRP) { + if (port->try_role == TYPEC_SINK) + return SNK_UNATTACHED; + else if (port->try_role == TYPEC_SOURCE) + return SRC_UNATTACHED; + /* Fall through to return SRC_UNATTACHED */ + } else if (port->port_type == TYPEC_PORT_SNK) { + return SNK_UNATTACHED; + } + return SRC_UNATTACHED; +} + +static bool tcpm_port_is_disconnected(struct tcpm_port *port) +{ + return (!port->attached && port->cc1 == TYPEC_CC_OPEN && + port->cc2 == TYPEC_CC_OPEN) || + (port->attached && ((port->polarity == TYPEC_POLARITY_CC1 && + port->cc1 == TYPEC_CC_OPEN) || + (port->polarity == TYPEC_POLARITY_CC2 && + port->cc2 == TYPEC_CC_OPEN))); +} + +/* + * Logging + */ + +#ifdef CONFIG_DEBUG_FS + +static bool tcpm_log_full(struct tcpm_port *port) +{ + return port->logbuffer_tail == + (port->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; +} + +__printf(2, 0) +static void _tcpm_log(struct tcpm_port *port, const char *fmt, va_list args) +{ + char tmpbuffer[LOG_BUFFER_ENTRY_SIZE]; + u64 ts_nsec = local_clock(); + unsigned long rem_nsec; + + mutex_lock(&port->logbuffer_lock); + if (!port->logbuffer[port->logbuffer_head]) { + port->logbuffer[port->logbuffer_head] = + kzalloc(LOG_BUFFER_ENTRY_SIZE, GFP_KERNEL); + if (!port->logbuffer[port->logbuffer_head]) { + mutex_unlock(&port->logbuffer_lock); + return; + } + } + + vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args); + + if (tcpm_log_full(port)) { + port->logbuffer_head = max(port->logbuffer_head - 1, 0); + strscpy(tmpbuffer, "overflow"); + } + + if (port->logbuffer_head < 0 || + port->logbuffer_head >= LOG_BUFFER_ENTRIES) { + dev_warn(port->dev, + "Bad log buffer index %d\n", port->logbuffer_head); + goto abort; + } + + if (!port->logbuffer[port->logbuffer_head]) { + dev_warn(port->dev, + "Log buffer index %d is NULL\n", port->logbuffer_head); + goto abort; + } + + rem_nsec = do_div(ts_nsec, 1000000000); + scnprintf(port->logbuffer[port->logbuffer_head], + LOG_BUFFER_ENTRY_SIZE, "[%5lu.%06lu] %s", + (unsigned long)ts_nsec, rem_nsec / 1000, + tmpbuffer); + port->logbuffer_head = (port->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; + +abort: + mutex_unlock(&port->logbuffer_lock); +} + +__printf(2, 3) +static void tcpm_log(struct tcpm_port *port, const char *fmt, ...) +{ + va_list args; + + /* Do not log while disconnected and unattached */ + if (tcpm_port_is_disconnected(port) && + (port->state == SRC_UNATTACHED || port->state == SNK_UNATTACHED || + port->state == TOGGLING || port->state == CHECK_CONTAMINANT)) + return; + + va_start(args, fmt); + _tcpm_log(port, fmt, args); + va_end(args); +} + +__printf(2, 3) +static void tcpm_log_force(struct tcpm_port *port, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + _tcpm_log(port, fmt, args); + va_end(args); +} + +static void tcpm_log_source_caps(struct tcpm_port *port) +{ + int i; + + for (i = 0; i < port->nr_source_caps; i++) { + u32 pdo = port->source_caps[i]; + enum pd_pdo_type type = pdo_type(pdo); + char msg[64]; + + switch (type) { + case PDO_TYPE_FIXED: + scnprintf(msg, sizeof(msg), + "%u mV, %u mA [%s%s%s%s%s%s]", + pdo_fixed_voltage(pdo), + pdo_max_current(pdo), + (pdo & PDO_FIXED_DUAL_ROLE) ? + "R" : "", + (pdo & PDO_FIXED_SUSPEND) ? + "S" : "", + (pdo & PDO_FIXED_HIGHER_CAP) ? + "H" : "", + (pdo & PDO_FIXED_USB_COMM) ? + "U" : "", + (pdo & PDO_FIXED_DATA_SWAP) ? + "D" : "", + (pdo & PDO_FIXED_EXTPOWER) ? + "E" : ""); + break; + case PDO_TYPE_VAR: + scnprintf(msg, sizeof(msg), + "%u-%u mV, %u mA", + pdo_min_voltage(pdo), + pdo_max_voltage(pdo), + pdo_max_current(pdo)); + break; + case PDO_TYPE_BATT: + scnprintf(msg, sizeof(msg), + "%u-%u mV, %u mW", + pdo_min_voltage(pdo), + pdo_max_voltage(pdo), + pdo_max_power(pdo)); + break; + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) + scnprintf(msg, sizeof(msg), + "PPS %u-%u mV, %u mA", + pdo_pps_apdo_min_voltage(pdo), + pdo_pps_apdo_max_voltage(pdo), + pdo_pps_apdo_max_current(pdo)); + else if (pdo_apdo_type(pdo) == APDO_TYPE_EPR_AVS) + scnprintf(msg, sizeof(msg), + "EPR AVS %u-%u mV %u W peak_current: %u", + pdo_epr_avs_apdo_min_voltage_mv(pdo), + pdo_epr_avs_apdo_max_voltage_mv(pdo), + pdo_epr_avs_apdo_pdp_w(pdo), + pdo_epr_avs_apdo_src_peak_current(pdo)); + else if (pdo_apdo_type(pdo) == APDO_TYPE_SPR_AVS) + scnprintf(msg, sizeof(msg), + "SPR AVS 9-15 V: %u mA 15-20 V: %u mA peak_current: %u", + pdo_spr_avs_apdo_9v_to_15v_max_current_ma(pdo), + pdo_spr_avs_apdo_15v_to_20v_max_current_ma(pdo), + pdo_spr_avs_apdo_src_peak_current(pdo)); + else + strscpy(msg, "undefined APDO"); + break; + default: + strscpy(msg, "undefined"); + break; + } + tcpm_log(port, " PDO %d: type %d, %s", + i, type, msg); + } +} + +static int tcpm_debug_show(struct seq_file *s, void *v) +{ + struct tcpm_port *port = s->private; + int tail; + + mutex_lock(&port->logbuffer_lock); + tail = port->logbuffer_tail; + while (tail != port->logbuffer_head) { + seq_printf(s, "%s\n", port->logbuffer[tail]); + tail = (tail + 1) % LOG_BUFFER_ENTRIES; + } + if (!seq_has_overflowed(s)) + port->logbuffer_tail = tail; + mutex_unlock(&port->logbuffer_lock); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(tcpm_debug); + +static void tcpm_debugfs_init(struct tcpm_port *port) +{ + char name[NAME_MAX]; + + mutex_init(&port->logbuffer_lock); + snprintf(name, NAME_MAX, "tcpm-%s", dev_name(port->dev)); + port->dentry = debugfs_create_dir(name, usb_debug_root); + debugfs_create_file("log", S_IFREG | 0444, port->dentry, port, + &tcpm_debug_fops); +} + +static void tcpm_debugfs_exit(struct tcpm_port *port) +{ + int i; + + mutex_lock(&port->logbuffer_lock); + for (i = 0; i < LOG_BUFFER_ENTRIES; i++) { + kfree(port->logbuffer[i]); + port->logbuffer[i] = NULL; + } + mutex_unlock(&port->logbuffer_lock); + + debugfs_remove(port->dentry); +} + +#else + +__printf(2, 3) +static void tcpm_log(const struct tcpm_port *port, const char *fmt, ...) { } +__printf(2, 3) +static void tcpm_log_force(struct tcpm_port *port, const char *fmt, ...) { } +static void tcpm_log_source_caps(struct tcpm_port *port) { } +static void tcpm_debugfs_init(const struct tcpm_port *port) { } +static void tcpm_debugfs_exit(const struct tcpm_port *port) { } + +#endif + +static void tcpm_set_cc(struct tcpm_port *port, enum typec_cc_status cc) +{ + tcpm_log(port, "cc:=%d", cc); + port->cc_req = cc; + port->tcpc->set_cc(port->tcpc, cc); +} + +static int tcpm_enable_auto_vbus_discharge(struct tcpm_port *port, bool enable) +{ + int ret = 0; + + if (port->tcpc->enable_auto_vbus_discharge) { + ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, enable); + tcpm_log_force(port, "%s vbus discharge ret:%d", + str_enable_disable(enable), ret); + if (!ret) + port->auto_vbus_discharge_enabled = enable; + } + + return ret; +} + +static void tcpm_apply_rc(struct tcpm_port *port) +{ + /* + * TCPCI: Move to APPLY_RC state to prevent disconnect during PR_SWAP + * when Vbus auto discharge on disconnect is enabled. + */ + if (port->tcpc->enable_auto_vbus_discharge && port->tcpc->apply_rc) { + tcpm_log(port, "Apply_RC"); + port->tcpc->apply_rc(port->tcpc, port->cc_req, port->polarity); + tcpm_enable_auto_vbus_discharge(port, false); + } +} + +/* + * Determine RP value to set based on maximum current supported + * by a port if configured as source. + * Returns CC value to report to link partner. + */ +static enum typec_cc_status tcpm_rp_cc(struct tcpm_port *port) +{ + const u32 *src_pdo = port->src_pdo; + int nr_pdo = port->nr_src_pdo; + int i; + + if (!port->pd_supported) + return port->src_rp; + + /* + * Search for first entry with matching voltage. + * It should report the maximum supported current. + */ + for (i = 0; i < nr_pdo; i++) { + const u32 pdo = src_pdo[i]; + + if (pdo_type(pdo) == PDO_TYPE_FIXED && + pdo_fixed_voltage(pdo) == 5000) { + unsigned int curr = pdo_max_current(pdo); + + if (curr >= 3000) + return TYPEC_CC_RP_3_0; + else if (curr >= 1500) + return TYPEC_CC_RP_1_5; + return TYPEC_CC_RP_DEF; + } + } + + return TYPEC_CC_RP_DEF; +} + +static void tcpm_ams_finish(struct tcpm_port *port) +{ + tcpm_log(port, "AMS %s finished", tcpm_ams_str[port->ams]); + + if (port->pd_capable && port->pwr_role == TYPEC_SOURCE) { + if (port->negotiated_rev >= PD_REV30) + tcpm_set_cc(port, SINK_TX_OK); + else + tcpm_set_cc(port, SINK_TX_NG); + } else if (port->pwr_role == TYPEC_SOURCE) { + tcpm_set_cc(port, tcpm_rp_cc(port)); + } + + port->in_ams = false; + port->ams = NONE_AMS; +} + +static int tcpm_pd_transmit(struct tcpm_port *port, + enum tcpm_transmit_type tx_sop_type, + const struct pd_message *msg) +{ + unsigned long time_left; + int ret; + unsigned int negotiated_rev; + + switch (tx_sop_type) { + case TCPC_TX_SOP_PRIME: + negotiated_rev = port->negotiated_rev_prime; + break; + case TCPC_TX_SOP: + default: + negotiated_rev = port->negotiated_rev; + break; + } + + if (msg) + tcpm_log(port, "PD TX, header: %#x", le16_to_cpu(msg->header)); + else + tcpm_log(port, "PD TX, type: %#x", tx_sop_type); + + reinit_completion(&port->tx_complete); + ret = port->tcpc->pd_transmit(port->tcpc, tx_sop_type, msg, negotiated_rev); + if (ret < 0) + return ret; + + mutex_unlock(&port->lock); + time_left = wait_for_completion_timeout(&port->tx_complete, + msecs_to_jiffies(PD_T_TCPC_TX_TIMEOUT)); + mutex_lock(&port->lock); + if (!time_left) + return -ETIMEDOUT; + + switch (port->tx_status) { + case TCPC_TX_SUCCESS: + switch (tx_sop_type) { + case TCPC_TX_SOP_PRIME: + port->message_id_prime = (port->message_id_prime + 1) & + PD_HEADER_ID_MASK; + break; + case TCPC_TX_SOP: + default: + port->message_id = (port->message_id + 1) & + PD_HEADER_ID_MASK; + break; + } + /* + * USB PD rev 2.0, 8.3.2.2.1: + * USB PD rev 3.0, 8.3.2.1.3: + * "... Note that every AMS is Interruptible until the first + * Message in the sequence has been successfully sent (GoodCRC + * Message received)." + */ + if (port->ams != NONE_AMS) + port->in_ams = true; + break; + case TCPC_TX_DISCARDED: + ret = -EAGAIN; + break; + case TCPC_TX_FAILED: + default: + ret = -EIO; + break; + } + + /* Some AMS don't expect responses. Finish them here. */ + if (port->ams == ATTENTION || port->ams == SOURCE_ALERT) + tcpm_ams_finish(port); + + return ret; +} + +void tcpm_pd_transmit_complete(struct tcpm_port *port, + enum tcpm_transmit_status status) +{ + tcpm_log(port, "PD TX complete, status: %u", status); + port->tx_status = status; + complete(&port->tx_complete); +} +EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete); + +static int tcpm_mux_set(struct tcpm_port *port, int state, + enum usb_role usb_role, + enum typec_orientation orientation) +{ + int ret; + + tcpm_log(port, "Requesting mux state %d, usb-role %d, orientation %d", + state, usb_role, orientation); + + ret = typec_set_orientation(port->typec_port, orientation); + if (ret) + return ret; + + if (port->role_sw) { + ret = usb_role_switch_set_role(port->role_sw, usb_role); + if (ret) + return ret; + } + + return typec_set_mode(port->typec_port, state); +} + +static int tcpm_set_polarity(struct tcpm_port *port, + enum typec_cc_polarity polarity) +{ + int ret; + + tcpm_log(port, "polarity %d", polarity); + + ret = port->tcpc->set_polarity(port->tcpc, polarity); + if (ret < 0) + return ret; + + port->polarity = polarity; + + return 0; +} + +static int tcpm_set_vconn(struct tcpm_port *port, bool enable) +{ + int ret; + + tcpm_log(port, "vconn:=%d", enable); + + ret = port->tcpc->set_vconn(port->tcpc, enable); + if (!ret) { + port->vconn_role = enable ? TYPEC_SOURCE : TYPEC_SINK; + typec_set_vconn_role(port->typec_port, port->vconn_role); + } + + return ret; +} + +static u32 tcpm_get_current_limit(struct tcpm_port *port) +{ + enum typec_cc_status cc; + u32 limit; + + cc = port->polarity ? port->cc2 : port->cc1; + switch (cc) { + case TYPEC_CC_RP_1_5: + limit = 1500; + break; + case TYPEC_CC_RP_3_0: + limit = 3000; + break; + case TYPEC_CC_RP_DEF: + default: + if (port->tcpc->get_current_limit) + limit = port->tcpc->get_current_limit(port->tcpc); + else + limit = 0; + break; + } + + return limit; +} + +static int tcpm_set_current_limit(struct tcpm_port *port, u32 max_ma, u32 mv) +{ + int ret = -EOPNOTSUPP; + + tcpm_log(port, "Setting voltage/current limit %u mV %u mA", mv, max_ma); + + port->supply_voltage = mv; + port->current_limit = max_ma; + power_supply_changed(port->psy); + + if (port->tcpc->set_current_limit) + ret = port->tcpc->set_current_limit(port->tcpc, max_ma, mv); + + return ret; +} + +static int tcpm_set_attached_state(struct tcpm_port *port, bool attached) +{ + return port->tcpc->set_roles(port->tcpc, attached, port->pwr_role, + port->data_role); +} + +static int tcpm_set_roles(struct tcpm_port *port, bool attached, int state, + enum typec_role role, enum typec_data_role data) +{ + enum typec_orientation orientation; + enum usb_role usb_role; + int ret; + + if (port->polarity == TYPEC_POLARITY_CC1) + orientation = TYPEC_ORIENTATION_NORMAL; + else + orientation = TYPEC_ORIENTATION_REVERSE; + + if (port->typec_caps.data == TYPEC_PORT_DRD) { + if (data == TYPEC_HOST) + usb_role = USB_ROLE_HOST; + else + usb_role = USB_ROLE_DEVICE; + } else if (port->typec_caps.data == TYPEC_PORT_DFP) { + if (data == TYPEC_HOST) { + if (role == TYPEC_SOURCE) + usb_role = USB_ROLE_HOST; + else + usb_role = USB_ROLE_NONE; + } else { + return -ENOTSUPP; + } + } else { + if (data == TYPEC_DEVICE) { + if (role == TYPEC_SINK) + usb_role = USB_ROLE_DEVICE; + else + usb_role = USB_ROLE_NONE; + } else { + return -ENOTSUPP; + } + } + + ret = tcpm_mux_set(port, state, usb_role, orientation); + if (ret < 0) + return ret; + + ret = port->tcpc->set_roles(port->tcpc, attached, role, data); + if (ret < 0) + return ret; + + if (port->tcpc->set_orientation) { + ret = port->tcpc->set_orientation(port->tcpc, orientation); + if (ret < 0) + return ret; + } + + port->pwr_role = role; + port->data_role = data; + typec_set_data_role(port->typec_port, data); + typec_set_pwr_role(port->typec_port, role); + + return 0; +} + +static int tcpm_set_pwr_role(struct tcpm_port *port, enum typec_role role) +{ + int ret; + + ret = port->tcpc->set_roles(port->tcpc, true, role, + port->data_role); + if (ret < 0) + return ret; + + port->pwr_role = role; + typec_set_pwr_role(port->typec_port, role); + + return 0; +} + +/* + * Transform the PDO to be compliant to PD rev2.0. + * Return 0 if the PDO type is not defined in PD rev2.0. + * Otherwise, return the converted PDO. + */ +static u32 tcpm_forge_legacy_pdo(struct tcpm_port *port, u32 pdo, enum typec_role role) +{ + switch (pdo_type(pdo)) { + case PDO_TYPE_FIXED: + if (role == TYPEC_SINK) + return pdo & ~PDO_FIXED_FRS_CURR_MASK; + else + return pdo & ~PDO_FIXED_UNCHUNK_EXT; + case PDO_TYPE_VAR: + case PDO_TYPE_BATT: + return pdo; + case PDO_TYPE_APDO: + default: + return 0; + } +} + +static int tcpm_pd_send_revision(struct tcpm_port *port) +{ + struct pd_message msg; + u32 rmdo; + + memset(&msg, 0, sizeof(msg)); + rmdo = RMDO(port->pd_rev.rev_major, port->pd_rev.rev_minor, + port->pd_rev.ver_major, port->pd_rev.ver_minor); + msg.payload[0] = cpu_to_le32(rmdo); + msg.header = PD_HEADER_LE(PD_DATA_REVISION, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + 1); + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static int tcpm_pd_send_source_caps(struct tcpm_port *port) +{ + struct pd_message msg; + u32 pdo; + unsigned int i, nr_pdo = 0; + + memset(&msg, 0, sizeof(msg)); + + for (i = 0; i < port->nr_src_pdo; i++) { + if (port->negotiated_rev >= PD_REV30) { + msg.payload[nr_pdo++] = cpu_to_le32(port->src_pdo[i]); + } else { + pdo = tcpm_forge_legacy_pdo(port, port->src_pdo[i], TYPEC_SOURCE); + if (pdo) + msg.payload[nr_pdo++] = cpu_to_le32(pdo); + } + } + + if (!nr_pdo) { + /* No source capabilities defined, sink only */ + msg.header = PD_HEADER_LE(PD_CTRL_REJECT, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, 0); + } else { + msg.header = PD_HEADER_LE(PD_DATA_SOURCE_CAP, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + nr_pdo); + } + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static int tcpm_pd_send_sink_caps(struct tcpm_port *port) +{ + struct pd_message msg; + u32 pdo; + unsigned int i, nr_pdo = 0; + + memset(&msg, 0, sizeof(msg)); + + for (i = 0; i < port->nr_snk_pdo; i++) { + if (port->negotiated_rev >= PD_REV30) { + msg.payload[nr_pdo++] = cpu_to_le32(port->snk_pdo[i]); + } else { + pdo = tcpm_forge_legacy_pdo(port, port->snk_pdo[i], TYPEC_SINK); + if (pdo) + msg.payload[nr_pdo++] = cpu_to_le32(pdo); + } + } + + if (!nr_pdo) { + /* No sink capabilities defined, source only */ + msg.header = PD_HEADER_LE(PD_CTRL_REJECT, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, 0); + } else { + msg.header = PD_HEADER_LE(PD_DATA_SINK_CAP, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + nr_pdo); + } + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static void tcpm_get_fixed_batt(struct tcpm_port *port) +{ + int ret; + + if (!port->self_powered || port->fixed_batt_cnt > 0) + return; + + ret = power_supply_get_system_batteries(port->dev, &port->fixed_batt); + if (ret < 0) + tcpm_log(port, "Failed to get battery array, ret=%d", ret); + else + port->fixed_batt_cnt = ret; +} + +static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) +{ + u16 operating_snk_watt = port->operating_snk_mw / 1000; + struct sink_caps_ext_data *data = &port->sink_caps_ext; + struct pd_identifier *pd_ident = &port->pd_ident; + struct sink_caps_ext_msg skedb = {0}; + struct pd_message msg; + u8 data_obj_cnt; + + if (!port->self_powered) + data->spr_op_pdp = operating_snk_watt; + + tcpm_get_fixed_batt(port); + + /* + * SPR Sink Minimum PDP indicates the minimum power required to operate + * a sink device in its lowest level of functionality without requiring + * power from the battery. We can use the operating_snk_watt value to + * populate it, as operating_snk_watt indicates device's min operating + * power. + */ + data->spr_min_pdp = operating_snk_watt; + + if (data->spr_op_pdp < data->spr_min_pdp || + data->spr_max_pdp < data->spr_op_pdp) { + tcpm_log(port, + "Invalid PDP values, Min PDP:%u, Op PDP:%u, Max PDP:%u", + data->spr_min_pdp, data->spr_op_pdp, data->spr_max_pdp); + return -EOPNOTSUPP; + } + + memset(&msg, 0, sizeof(msg)); + skedb.vid = cpu_to_le16(pd_ident->vid); + skedb.pid = cpu_to_le16(pd_ident->pid); + skedb.xid = cpu_to_le32(pd_ident->xid); + skedb.skedb_ver = SKEDB_VER_1_0; + skedb.load_step = data->load_step; + skedb.load_char = cpu_to_le16(data->load_char); + skedb.compliance = data->compliance; + skedb.batt_info = min(port->fixed_batt_cnt, MAX_NUM_FIXED_BATT); + skedb.modes = data->modes; + skedb.spr_min_pdp = data->spr_min_pdp; + skedb.spr_op_pdp = data->spr_op_pdp; + skedb.spr_max_pdp = data->spr_max_pdp; + memcpy(msg.ext_msg.data, &skedb, sizeof(skedb)); + msg.ext_msg.header = PD_EXT_HDR_LE(sizeof(skedb), + 0, /* Denotes if request chunk */ + 0, /* Chunk Number */ + 1 /* Chunked */); + + data_obj_cnt = count_chunked_data_objs(sizeof(skedb)); + msg.header = cpu_to_le16(PD_HEADER(PD_EXT_SINK_CAP_EXT, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + data_obj_cnt, + 1 /* Denotes if ext header */)); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static u16 tcpm_charge_to_energy(int charge, int voltage) +{ + u64 energy = div_u64((u64)charge * voltage, 1000000); + + /* Battery telemetry is reported in increments of 0.1Wh */ + return (u16)UW_TO_W(energy * 10); +} + +static int tcpm_pd_send_batt_status(struct tcpm_port *port) +{ + u16 present_charge = BATTERY_PROPERTY_UNKNOWN; + bool batt_present = false, invalid_ref = true; + u32 batt_id = port->batt_request_id; + union power_supply_propval val; + struct power_supply *batt; + u8 charging_status = 0; + struct pd_message msg; + int ret, charge_now; + u32 bsdo; + + tcpm_get_fixed_batt(port); + memset(&msg, 0, sizeof(msg)); + + if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT) + goto send_status; + + invalid_ref = false; + batt = port->fixed_batt[batt_id]; + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_PRESENT, &val); + if (ret) + tcpm_log(port, + "Failed to fetch power_supply_prop_present ret %d", + ret); + else + batt_present = val.intval > 0; + + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_CHARGE_NOW, + &val); + if (!ret) { + charge_now = val.intval; + ret = power_supply_get_property(batt, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + &val); + if (!ret) + present_charge = tcpm_charge_to_energy(charge_now, + val.intval); + } + + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_STATUS, &val); + if (!ret) { + switch (val.intval) { + case POWER_SUPPLY_STATUS_CHARGING: + charging_status = BSDO_BATTERY_INFO_CHARGING; + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + charging_status = BSDO_BATTERY_INFO_DISCHARGING; + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: + case POWER_SUPPLY_STATUS_FULL: + charging_status = BSDO_BATTERY_INFO_IDLE; + break; + default: + charging_status = BSDO_BATTERY_INFO_RSVD; + break; + } + } + +send_status: + + bsdo = BSDO(present_charge, charging_status, batt_present, invalid_ref); + msg.payload[0] = cpu_to_le32(bsdo); + msg.header = PD_HEADER_LE(PD_DATA_BATT_STATUS, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + 1); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static int tcpm_pd_send_batt_cap(struct tcpm_port *port) +{ + u16 design_cap = BATTERY_PROPERTY_UNKNOWN; + u16 charge_cap = BATTERY_PROPERTY_UNKNOWN; + u32 batt_id = port->batt_request_id; + union power_supply_propval val; + struct batt_cap_ext_msg bcdb; + struct power_supply *batt; + bool invalid_ref = true; + struct pd_message msg; + u8 data_obj_cnt; + int ret, vol; + + tcpm_get_fixed_batt(port); + memset(&msg, 0, sizeof(msg)); + + if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT) + goto send_cap; + + invalid_ref = false; + batt = port->fixed_batt[batt_id]; + ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_VOLTAGE_AVG, + &val); + if (!ret) { + vol = val.intval; + ret = power_supply_get_property(batt, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + &val); + if (!ret) + design_cap = tcpm_charge_to_energy(val.intval, vol); + + ret = power_supply_get_property(batt, + POWER_SUPPLY_PROP_CHARGE_FULL, + &val); + if (!ret) + charge_cap = tcpm_charge_to_energy(val.intval, vol); + } + +send_cap: + + /* + * As per the USB PD Rev3.1 v1.8 spec, if a battery VID (assigned by the + * USB-IF) does not exist or an invalid battery reference is made by the + * requestor, then set the VID field to 0xffff. If the VID field is + * 0xffff, set the PID field to 0. + */ + bcdb.vid = BATTERY_PROPERTY_UNKNOWN; + bcdb.pid = 0; + bcdb.batt_design_cap = cpu_to_le16(design_cap); + bcdb.batt_last_chg_cap = cpu_to_le16(charge_cap); + bcdb.batt_type = invalid_ref ? BATT_CAP_BATT_TYPE_INVALID_REF : 0; + memcpy(msg.ext_msg.data, &bcdb, sizeof(bcdb)); + msg.ext_msg.header = PD_EXT_HDR_LE(sizeof(bcdb), + 0, /* Denotes if request chunk */ + 0, /* Chunk number */ + 1 /* Chunked */); + + data_obj_cnt = count_chunked_data_objs(sizeof(bcdb)); + msg.header = PD_HEADER_EXT_LE(PD_EXT_BATT_CAP, port->pwr_role, + port->data_role, port->negotiated_rev, + port->message_id, data_obj_cnt); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static void mod_tcpm_delayed_work(struct tcpm_port *port, unsigned int delay_ms) +{ + if (delay_ms) { + hrtimer_start(&port->state_machine_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); + } else { + hrtimer_cancel(&port->state_machine_timer); + kthread_queue_work(port->wq, &port->state_machine); + } +} + +static void mod_vdm_delayed_work(struct tcpm_port *port, unsigned int delay_ms) +{ + if (delay_ms) { + hrtimer_start(&port->vdm_state_machine_timer, ms_to_ktime(delay_ms), + HRTIMER_MODE_REL); + } else { + hrtimer_cancel(&port->vdm_state_machine_timer); + kthread_queue_work(port->wq, &port->vdm_state_machine); + } +} + +static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int delay_ms) +{ + if (delay_ms) { + hrtimer_start(&port->enable_frs_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); + } else { + hrtimer_cancel(&port->enable_frs_timer); + kthread_queue_work(port->wq, &port->enable_frs); + } +} + +static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port) +{ + hrtimer_cancel(&port->vdm_discovery_timer); + kthread_cancel_work_sync(&port->vdm_discovery_work); +} + +static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms) +{ + if (delay_ms) { + hrtimer_start(&port->vdm_discovery_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); + } else { + hrtimer_cancel(&port->vdm_discovery_timer); + kthread_queue_work(port->wq, &port->vdm_discovery_work); + } +} + +static void tcpm_set_state(struct tcpm_port *port, enum tcpm_state state, + unsigned int delay_ms) +{ + if (delay_ms) { + tcpm_log(port, "pending state change %s -> %s @ %u ms [%s %s]", + tcpm_states[port->state], tcpm_states[state], delay_ms, + pd_rev[port->negotiated_rev], tcpm_ams_str[port->ams]); + port->delayed_state = state; + mod_tcpm_delayed_work(port, delay_ms); + port->delayed_runtime = ktime_add(ktime_get(), ms_to_ktime(delay_ms)); + port->delay_ms = delay_ms; + } else { + tcpm_log(port, "state change %s -> %s [%s %s]", + tcpm_states[port->state], tcpm_states[state], + pd_rev[port->negotiated_rev], tcpm_ams_str[port->ams]); + port->delayed_state = INVALID_STATE; + port->prev_state = port->state; + port->state = state; + /* + * Don't re-queue the state machine work item if we're currently + * in the state machine and we're immediately changing states. + * tcpm_state_machine_work() will continue running the state + * machine. + */ + if (!port->state_machine_running) + mod_tcpm_delayed_work(port, 0); + } +} + +static void tcpm_set_state_cond(struct tcpm_port *port, enum tcpm_state state, + unsigned int delay_ms) +{ + if (port->enter_state == port->state) + tcpm_set_state(port, state, delay_ms); + else + tcpm_log(port, + "skipped %sstate change %s -> %s [%u ms], context state %s [%s %s]", + delay_ms ? "delayed " : "", + tcpm_states[port->state], tcpm_states[state], + delay_ms, tcpm_states[port->enter_state], + pd_rev[port->negotiated_rev], tcpm_ams_str[port->ams]); +} + +static void tcpm_queue_message(struct tcpm_port *port, + enum pd_msg_request message) +{ + port->queued_message = message; + mod_tcpm_delayed_work(port, 0); +} + +static bool tcpm_vdm_ams(struct tcpm_port *port) +{ + switch (port->ams) { + case DISCOVER_IDENTITY: + case SOURCE_STARTUP_CABLE_PLUG_DISCOVER_IDENTITY: + case DISCOVER_SVIDS: + case DISCOVER_MODES: + case DFP_TO_UFP_ENTER_MODE: + case DFP_TO_UFP_EXIT_MODE: + case DFP_TO_CABLE_PLUG_ENTER_MODE: + case DFP_TO_CABLE_PLUG_EXIT_MODE: + case ATTENTION: + case UNSTRUCTURED_VDMS: + case STRUCTURED_VDMS: + break; + default: + return false; + } + + return true; +} + +static bool tcpm_ams_interruptible(struct tcpm_port *port) +{ + switch (port->ams) { + /* Interruptible AMS */ + case NONE_AMS: + case SECURITY: + case FIRMWARE_UPDATE: + case DISCOVER_IDENTITY: + case SOURCE_STARTUP_CABLE_PLUG_DISCOVER_IDENTITY: + case DISCOVER_SVIDS: + case DISCOVER_MODES: + case DFP_TO_UFP_ENTER_MODE: + case DFP_TO_UFP_EXIT_MODE: + case DFP_TO_CABLE_PLUG_ENTER_MODE: + case DFP_TO_CABLE_PLUG_EXIT_MODE: + case UNSTRUCTURED_VDMS: + case STRUCTURED_VDMS: + case COUNTRY_INFO: + case COUNTRY_CODES: + break; + /* Non-Interruptible AMS */ + default: + if (port->in_ams) + return false; + break; + } + + return true; +} + +static int tcpm_ams_start(struct tcpm_port *port, enum tcpm_ams ams) +{ + int ret = 0; + + tcpm_log(port, "AMS %s start", tcpm_ams_str[ams]); + + if (!tcpm_ams_interruptible(port) && + !(ams == HARD_RESET || ams == SOFT_RESET_AMS)) { + port->upcoming_state = INVALID_STATE; + tcpm_log(port, "AMS %s not interruptible, aborting", + tcpm_ams_str[port->ams]); + return -EAGAIN; + } + + if (port->pwr_role == TYPEC_SOURCE) { + enum typec_cc_status cc_req = port->cc_req; + + port->ams = ams; + + if (ams == HARD_RESET) { + tcpm_set_cc(port, tcpm_rp_cc(port)); + tcpm_pd_transmit(port, TCPC_TX_HARD_RESET, NULL); + tcpm_set_state(port, HARD_RESET_START, 0); + return ret; + } else if (ams == SOFT_RESET_AMS) { + if (!port->explicit_contract) + tcpm_set_cc(port, tcpm_rp_cc(port)); + tcpm_set_state(port, SOFT_RESET_SEND, 0); + return ret; + } else if (tcpm_vdm_ams(port)) { + /* tSinkTx is enforced in vdm_run_state_machine */ + if (port->negotiated_rev >= PD_REV30) + tcpm_set_cc(port, SINK_TX_NG); + return ret; + } + + if (port->negotiated_rev >= PD_REV30) + tcpm_set_cc(port, SINK_TX_NG); + + switch (port->state) { + case SRC_READY: + case SRC_STARTUP: + case SRC_SOFT_RESET_WAIT_SNK_TX: + case SOFT_RESET: + case SOFT_RESET_SEND: + if (port->negotiated_rev >= PD_REV30) + tcpm_set_state(port, AMS_START, + cc_req == SINK_TX_OK ? + PD_T_SINK_TX : 0); + else + tcpm_set_state(port, AMS_START, 0); + break; + default: + if (port->negotiated_rev >= PD_REV30) + tcpm_set_state(port, SRC_READY, + cc_req == SINK_TX_OK ? + PD_T_SINK_TX : 0); + else + tcpm_set_state(port, SRC_READY, 0); + break; + } + } else { + if (port->negotiated_rev >= PD_REV30 && + !tcpm_sink_tx_ok(port) && + ams != SOFT_RESET_AMS && + ams != HARD_RESET) { + port->upcoming_state = INVALID_STATE; + tcpm_log(port, "Sink TX No Go"); + return -EAGAIN; + } + + port->ams = ams; + + if (ams == HARD_RESET) { + tcpm_pd_transmit(port, TCPC_TX_HARD_RESET, NULL); + tcpm_set_state(port, HARD_RESET_START, 0); + return ret; + } else if (tcpm_vdm_ams(port)) { + return ret; + } + + if (port->state == SNK_READY || + port->state == SNK_SOFT_RESET) + tcpm_set_state(port, AMS_START, 0); + else + tcpm_set_state(port, SNK_READY, 0); + } + + return ret; +} + +/* + * VDM/VDO handling functions + */ +static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, + const u32 *data, int cnt, enum tcpm_transmit_type tx_sop_type) +{ + u32 vdo_hdr = port->vdo_data[0]; + + WARN_ON(!mutex_is_locked(&port->lock)); + + /* If is sending discover_identity, handle received message first */ + if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) + mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS); + else + /* Make sure we are not still processing a previous VDM packet */ + WARN_ON(port->vdm_state > VDM_STATE_DONE); + + port->vdo_count = cnt + 1; + port->vdo_data[0] = header; + memcpy(&port->vdo_data[1], data, sizeof(u32) * cnt); + /* Set ready, vdm state machine will actually send */ + port->vdm_retries = 0; + port->vdm_state = VDM_STATE_READY; + port->vdm_sm_running = true; + + port->tx_sop_type = tx_sop_type; + + mod_vdm_delayed_work(port, 0); +} + +static void tcpm_queue_vdm_work(struct kthread_work *work) +{ + struct altmode_vdm_event *event = container_of(work, + struct altmode_vdm_event, + work); + struct tcpm_port *port = event->port; + + mutex_lock(&port->lock); + if (!tcpm_can_send_vdm(port->state)) { + tcpm_log_force(port, "dropping altmode_vdm_event"); + goto port_unlock; + } + + tcpm_queue_vdm(port, event->header, event->data, event->cnt, event->tx_sop_type); + +port_unlock: + kfree(event); + mutex_unlock(&port->lock); +} + +static int tcpm_queue_vdm_unlocked(struct tcpm_port *port, const u32 header, + const u32 *data, int cnt, enum tcpm_transmit_type tx_sop_type) +{ + struct altmode_vdm_event *event; + int ret = -ENOMEM; + + event = kzalloc_flex(*event, data, cnt); + if (!event) + goto err_event; + + kthread_init_work(&event->work, tcpm_queue_vdm_work); + event->cnt = cnt; + event->port = port; + event->header = header; + memcpy(event->data, data, sizeof(u32) * cnt); + event->tx_sop_type = tx_sop_type; + + ret = kthread_queue_work(port->wq, &event->work); + if (!ret) { + ret = -EBUSY; + goto err_data; + } + + return 0; + +err_data: + kfree(event); +err_event: + tcpm_log_force(port, "failed to queue altmode vdm, err:%d", ret); + return ret; +} + +static void svdm_consume_identity(struct tcpm_port *port, const u32 *p, int cnt) +{ + u32 vdo = p[VDO_INDEX_IDH]; + u32 product = p[VDO_INDEX_PRODUCT]; + + if (cnt <= VDO_INDEX_PRODUCT) + return; + + memset(&port->mode_data, 0, sizeof(port->mode_data)); + + port->partner_ident.id_header = vdo; + port->partner_ident.cert_stat = p[VDO_INDEX_CSTAT]; + port->partner_ident.product = product; + + if (port->partner) + typec_partner_set_identity(port->partner); + + tcpm_log(port, "Identity: %04x:%04x.%04x", + PD_IDH_VID(vdo), + PD_PRODUCT_PID(product), product & 0xffff); +} + +static void svdm_consume_identity_sop_prime(struct tcpm_port *port, const u32 *p, int cnt) +{ + u32 idh = p[VDO_INDEX_IDH]; + u32 product = p[VDO_INDEX_PRODUCT]; + int svdm_version; + + if (cnt <= VDO_INDEX_CABLE_1) + return; + + /* + * Attempt to consume identity only if cable currently is not set + */ + if (!IS_ERR_OR_NULL(port->cable)) + goto register_plug; + + /* Reset cable identity */ + memset(&port->cable_ident, 0, sizeof(port->cable_ident)); + + /* Fill out id header, cert, product, cable VDO 1 */ + port->cable_ident.id_header = idh; + port->cable_ident.cert_stat = p[VDO_INDEX_CSTAT]; + port->cable_ident.product = product; + port->cable_ident.vdo[0] = p[VDO_INDEX_CABLE_1]; + + /* Fill out cable desc, infer svdm_version from pd revision */ + port->cable_desc.type = (enum typec_plug_type) (VDO_TYPEC_CABLE_TYPE(p[VDO_INDEX_CABLE_1]) + + USB_PLUG_TYPE_A); + port->cable_desc.active = PD_IDH_PTYPE(idh) == IDH_PTYPE_ACABLE ? 1 : 0; + /* Log PD Revision and additional cable VDO from negotiated revision */ + switch (port->negotiated_rev_prime) { + case PD_REV30: + port->cable_desc.pd_revision = 0x0300; + if (port->cable_desc.active && cnt > VDO_INDEX_CABLE_2) + port->cable_ident.vdo[1] = p[VDO_INDEX_CABLE_2]; + break; + case PD_REV20: + port->cable_desc.pd_revision = 0x0200; + break; + default: + port->cable_desc.pd_revision = 0x0200; + break; + } + port->cable_desc.identity = &port->cable_ident; + /* Register Cable, set identity and svdm_version */ + port->cable = typec_register_cable(port->typec_port, &port->cable_desc); + if (IS_ERR_OR_NULL(port->cable)) + return; + typec_cable_set_identity(port->cable); + /* Get SVDM version */ + svdm_version = PD_VDO_SVDM_VER(p[VDO_INDEX_HDR]); + typec_cable_set_svdm_version(port->cable, svdm_version); + +register_plug: + if (IS_ERR_OR_NULL(port->plug_prime)) { + port->plug_prime_desc.index = TYPEC_PLUG_SOP_P; + port->plug_prime = typec_register_plug(port->cable, + &port->plug_prime_desc); + } +} + +static bool svdm_consume_svids(struct tcpm_port *port, const u32 *p, int cnt, + enum tcpm_transmit_type rx_sop_type) +{ + struct pd_mode_data *pmdata = rx_sop_type == TCPC_TX_SOP_PRIME ? + &port->mode_data_prime : &port->mode_data; + int i; + + for (i = 1; i < cnt; i++) { + u16 svid; + + svid = (p[i] >> 16) & 0xffff; + if (!svid) + return false; + + if (pmdata->nsvids >= SVID_DISCOVERY_MAX) + goto abort; + + pmdata->svids[pmdata->nsvids++] = svid; + tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid); + + svid = p[i] & 0xffff; + if (!svid) + return false; + + if (pmdata->nsvids >= SVID_DISCOVERY_MAX) + goto abort; + + pmdata->svids[pmdata->nsvids++] = svid; + tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid); + } + + /* + * PD3.0 Spec 6.4.4.3.2: The SVIDs are returned 2 per VDO (see Table + * 6-43), and can be returned maximum 6 VDOs per response (see Figure + * 6-19). If the Responder supports 12 or more SVID then the Discover + * SVIDs Command Shall be executed multiple times until a Discover + * SVIDs VDO is returned ending either with a SVID value of 0x0000 in + * the last part of the last VDO or with a VDO containing two SVIDs + * with values of 0x0000. + * + * However, some odd dockers support SVIDs less than 12 but without + * 0x0000 in the last VDO, so we need to break the Discover SVIDs + * request and return false here. + */ + return cnt == 7; +abort: + tcpm_log(port, "SVID_DISCOVERY_MAX(%d) too low!", SVID_DISCOVERY_MAX); + return false; +} + +static void svdm_consume_modes(struct tcpm_port *port, const u32 *p, int cnt, + enum tcpm_transmit_type rx_sop_type) +{ + struct pd_mode_data *pmdata = &port->mode_data; + struct typec_altmode_desc *paltmode; + int i; + + switch (rx_sop_type) { + case TCPC_TX_SOP_PRIME: + pmdata = &port->mode_data_prime; + break; + case TCPC_TX_SOP: + pmdata = &port->mode_data; + break; + default: + return; + } + + if (pmdata->svid_index < 0 || pmdata->svid_index >= pmdata->nsvids) { + tcpm_log(port, "Invalid SVID index %d", pmdata->svid_index); + return; + } + + for (i = 1; i < cnt; i++) { + if (pmdata->altmodes >= ALTMODE_DISCOVERY_MAX) { + /* Already logged in svdm_consume_svids() */ + return; + } + paltmode = &pmdata->altmode_desc[pmdata->altmodes]; + memset(paltmode, 0, sizeof(*paltmode)); + + paltmode->svid = pmdata->svids[pmdata->svid_index]; + paltmode->mode = i; + paltmode->vdo = p[i]; + + tcpm_log(port, " Alternate mode %d: SVID 0x%04x, VDO %d: 0x%08x", + pmdata->altmodes, paltmode->svid, + paltmode->mode, paltmode->vdo); + + pmdata->altmodes++; + } +} + +static void tcpm_register_partner_altmodes(struct tcpm_port *port) +{ + struct pd_mode_data *modep = &port->mode_data; + struct typec_altmode *altmode; + int i; + + if (!port->partner) + return; + + for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + altmode = typec_partner_register_altmode(port->partner, + &modep->altmode_desc[i]); + if (IS_ERR(altmode)) { + tcpm_log(port, "Failed to register partner SVID 0x%04x", + modep->altmode_desc[i].svid); + altmode = NULL; + } + port->partner_altmode[i] = altmode; + } +} + +static void tcpm_register_plug_altmodes(struct tcpm_port *port) +{ + struct pd_mode_data *modep = &port->mode_data_prime; + struct typec_altmode *altmode; + int i; + + typec_plug_set_num_altmodes(port->plug_prime, + min(modep->altmodes, ALTMODE_DISCOVERY_MAX)); + + for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + altmode = typec_plug_register_altmode(port->plug_prime, + &modep->altmode_desc[i]); + if (IS_ERR(altmode)) { + tcpm_log(port, "Failed to register plug SVID 0x%04x", + modep->altmode_desc[i].svid); + altmode = NULL; + } + port->plug_prime_altmode[i] = altmode; + } +} + +#define supports_modal(port) PD_IDH_MODAL_SUPP((port)->partner_ident.id_header) +#define supports_modal_cable(port) PD_IDH_MODAL_SUPP((port)->cable_ident.id_header) +#define supports_host(port) PD_IDH_HOST_SUPP((port->partner_ident.id_header)) + +/* + * Helper to determine whether the port is capable of SOP' communication at the + * current point in time. + */ +static bool tcpm_can_communicate_sop_prime(struct tcpm_port *port) +{ + /* Check to see if tcpc supports SOP' communication */ + if (!port->tcpc->cable_comm_capable || !port->tcpc->cable_comm_capable(port->tcpc)) + return false; + /* + * Power Delivery 2.0 Section 6.3.11 + * Before communicating with a Cable Plug a Port Should ensure that it + * is the Vconn Source and that the Cable Plugs are powered by + * performing a Vconn swap if necessary. Since it cannot be guaranteed + * that the present Vconn Source is supplying Vconn, the only means to + * ensure that the Cable Plugs are powered is for a Port wishing to + * communicate with a Cable Plug is to become the Vconn Source. + * + * Power Delivery 3.0 Section 6.3.11 + * Before communicating with a Cable Plug a Port Shall ensure that it + * is the Vconn source. + */ + if (port->vconn_role != TYPEC_SOURCE) + return false; + /* + * Power Delivery 2.0 Section 2.4.4 + * When no Contract or an Implicit Contract is in place the Source can + * communicate with a Cable Plug using SOP' packets in order to discover + * its characteristics. + * + * Power Delivery 3.0 Section 2.4.4 + * When no Contract or an Implicit Contract is in place only the Source + * port that is supplying Vconn is allowed to send packets to a Cable + * Plug and is allowed to respond to packets from the Cable Plug. + */ + if (!port->explicit_contract) + return port->pwr_role == TYPEC_SOURCE; + if (port->negotiated_rev == PD_REV30) + return true; + /* + * Power Delivery 2.0 Section 2.4.4 + * + * When an Explicit Contract is in place the DFP (either the Source or + * the Sink) can communicate with the Cable Plug(s) using SOP’/SOP” + * Packets (see Figure 2-3). + */ + if (port->negotiated_rev == PD_REV20) + return port->data_role == TYPEC_HOST; + return false; +} + +static bool tcpm_attempt_vconn_swap_discovery(struct tcpm_port *port) +{ + if (!port->tcpc->attempt_vconn_swap_discovery) + return false; + + /* Port is already source, no need to perform swap */ + if (port->vconn_role == TYPEC_SOURCE) + return false; + + /* + * Partner needs to support Alternate Modes with modal support. If + * partner is also capable of being a USB Host, it could be a device + * that supports Alternate Modes as the DFP. + */ + if (!supports_modal(port) || supports_host(port)) + return false; + + if ((port->negotiated_rev == PD_REV20 && port->data_role == TYPEC_HOST) || + port->negotiated_rev == PD_REV30) + return port->tcpc->attempt_vconn_swap_discovery(port->tcpc); + + return false; +} + + +static bool tcpm_cable_vdm_supported(struct tcpm_port *port) +{ + return !IS_ERR_OR_NULL(port->cable) && + typec_cable_is_active(port->cable) && + supports_modal_cable(port) && + tcpm_can_communicate_sop_prime(port); +} + +static void tcpm_update_vdm_discovery_state(struct tcpm_port *port, + enum vdm_discovery_states new_state) +{ + enum vdm_discovery_states old_state = port->vdm_discovery_state; + + if (old_state != new_state) + tcpm_log_force(port, "vdm discovery state changed: %s -> %s", + vdm_discovery_state_strings[old_state], + vdm_discovery_state_strings[new_state]); + + port->vdm_discovery_state = new_state; +} + +static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + enum tcpm_transmit_type rx_sop_type, + enum tcpm_transmit_type *response_tx_sop_type) +{ + struct typec_port *typec = port->typec_port; + struct pd_mode_data *modep; + + if (rx_sop_type == TCPC_TX_SOP) { + modep = &port->mode_data; + modep->svid_index++; + + if (modep->svid_index < modep->nsvids) { + u16 svid = modep->svids[modep->svid_index]; + *response_tx_sop_type = TCPC_TX_SOP; + response[0] = VDO(svid, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_MODES); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES); + return 1; + } + + if (tcpm_cable_vdm_supported(port)) { + *response_tx_sop_type = TCPC_TX_SOP_PRIME; + response[0] = VDO(USB_SID_PD, 1, + typec_get_cable_svdm_version(typec), + CMD_DISCOVER_SVID); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES); + return 1; + } + + tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + modep = &port->mode_data_prime; + modep->svid_index++; + + if (modep->svid_index < modep->nsvids) { + u16 svid = modep->svids[modep->svid_index]; + *response_tx_sop_type = TCPC_TX_SOP_PRIME; + response[0] = VDO(svid, 1, + typec_get_cable_svdm_version(typec), + CMD_DISCOVER_MODES); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_MODES); + return 1; + } + + tcpm_register_plug_altmodes(port); + tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + return 0; +} + +static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + const u32 *p, int cnt, u32 *response, + enum adev_actions *adev_action, + enum tcpm_transmit_type rx_sop_type, + enum tcpm_transmit_type *response_tx_sop_type) +{ + struct typec_port *typec = port->typec_port; + struct typec_altmode *pdev, *pdev_prime; + struct pd_mode_data *modep, *modep_prime; + int svdm_version; + int rlen = 0; + int cmd_type; + int cmd; + int i; + int ret; + + cmd_type = PD_VDO_CMDT(p[0]); + cmd = PD_VDO_CMD(p[0]); + + tcpm_log(port, "Rx VDM cmd 0x%x type %d cmd %d len %d", + p[0], cmd_type, cmd, cnt); + + switch (rx_sop_type) { + case TCPC_TX_SOP_PRIME: + modep_prime = &port->mode_data_prime; + pdev_prime = typec_match_altmode(port->plug_prime_altmode, + ALTMODE_DISCOVERY_MAX, + PD_VDO_VID(p[0]), + PD_VDO_OPOS(p[0])); + svdm_version = typec_get_cable_svdm_version(typec); + /* + * Update SVDM version if cable was discovered before port partner. + */ + if (!IS_ERR_OR_NULL(port->cable) && + PD_VDO_SVDM_VER(p[0]) < svdm_version) + typec_cable_set_svdm_version(port->cable, svdm_version); + break; + case TCPC_TX_SOP: + modep = &port->mode_data; + pdev = typec_match_altmode(port->partner_altmode, + ALTMODE_DISCOVERY_MAX, + PD_VDO_VID(p[0]), + PD_VDO_OPOS(p[0])); + svdm_version = typec_get_negotiated_svdm_version(typec); + if (svdm_version < 0) + return 0; + break; + default: + modep = &port->mode_data; + pdev = typec_match_altmode(port->partner_altmode, + ALTMODE_DISCOVERY_MAX, + PD_VDO_VID(p[0]), + PD_VDO_OPOS(p[0])); + svdm_version = typec_get_negotiated_svdm_version(typec); + if (svdm_version < 0) + return 0; + break; + } + + switch (cmd_type) { + case CMDT_INIT: + /* + * Only the port or port partner is allowed to initialize SVDM + * commands over SOP'. In case the port partner initializes a + * sequence when it is not allowed to send SOP' messages, drop + * the message should the TCPM port try to process it. + */ + if (rx_sop_type == TCPC_TX_SOP_PRIME) + return 0; + + switch (cmd) { + case CMD_DISCOVER_IDENT: + if (PD_VDO_VID(p[0]) != USB_SID_PD) + break; + + if (IS_ERR_OR_NULL(port->partner)) + break; + + if (PD_VDO_SVDM_VER(p[0]) < svdm_version) { + typec_partner_set_svdm_version(port->partner, + PD_VDO_SVDM_VER(p[0])); + svdm_version = PD_VDO_SVDM_VER(p[0]); + } + + port->ams = DISCOVER_IDENTITY; + /* + * PD2.0 Spec 6.10.3: respond with NAK as DFP (data host) + * PD3.1 Spec 6.4.4.2.5.1: respond with NAK if "invalid field" or + * "wrong configuation" or "Unrecognized" + */ + if ((port->data_role == TYPEC_DEVICE || svdm_version >= SVDM_VER_2_0) && + port->nr_snk_vdo) { + if (svdm_version < SVDM_VER_2_0) { + for (i = 0; i < port->nr_snk_vdo_v1; i++) + response[i + 1] = port->snk_vdo_v1[i]; + rlen = port->nr_snk_vdo_v1 + 1; + + } else { + for (i = 0; i < port->nr_snk_vdo; i++) + response[i + 1] = port->snk_vdo[i]; + rlen = port->nr_snk_vdo + 1; + } + } + break; + case CMD_DISCOVER_SVID: + port->ams = DISCOVER_SVIDS; + break; + case CMD_DISCOVER_MODES: + port->ams = DISCOVER_MODES; + break; + case CMD_ENTER_MODE: + port->ams = DFP_TO_UFP_ENTER_MODE; + break; + case CMD_EXIT_MODE: + port->ams = DFP_TO_UFP_EXIT_MODE; + break; + case CMD_ATTENTION: + /* Attention command does not have response */ + *adev_action = ADEV_ATTENTION; + return 0; + default: + break; + } + if (rlen >= 1) { + response[0] = p[0] | VDO_CMDT(CMDT_RSP_ACK); + } else if (rlen == 0) { + response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); + rlen = 1; + } else { + response[0] = p[0] | VDO_CMDT(CMDT_RSP_BUSY); + rlen = 1; + } + response[0] = (response[0] & ~VDO_SVDM_VERS_MASK) | + (VDO_SVDM_VERS(typec_get_negotiated_svdm_version(typec))); + break; + case CMDT_RSP_ACK: + /* + * Silently drop message if we are not connected, but can process + * if SOP' Discover Identity prior to explicit contract. + */ + if (IS_ERR_OR_NULL(port->partner) && + !(rx_sop_type == TCPC_TX_SOP_PRIME && cmd == CMD_DISCOVER_IDENT)) + break; + + tcpm_ams_finish(port); + + switch (cmd) { + /* + * SVDM Command Flow for SOP and SOP': + * SOP Discover Identity + * SOP' Discover Identity + * SOP Discover SVIDs + * Discover Modes + * (Active Cables) + * SOP' Discover SVIDs + * Discover Modes + * + * Perform Discover SOP' if the port can communicate with cable + * plug. + */ + case CMD_DISCOVER_IDENT: + switch (rx_sop_type) { + case TCPC_TX_SOP: + if (PD_VDO_SVDM_VER(p[0]) < svdm_version) { + typec_partner_set_svdm_version(port->partner, + PD_VDO_SVDM_VER(p[0])); + /* If cable is discovered before partner, downgrade svdm */ + if (!IS_ERR_OR_NULL(port->cable) && + (typec_get_cable_svdm_version(port->typec_port) > + svdm_version)) + typec_cable_set_svdm_version(port->cable, + svdm_version); + } + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_IDENT); + + /* 6.4.4.3.1 */ + svdm_consume_identity(port, p, cnt); + /* Attempt Vconn swap, delay SOP' discovery if necessary */ + if (tcpm_attempt_vconn_swap_discovery(port)) { + port->upcoming_state = VCONN_SWAP_SEND; + ret = tcpm_ams_start(port, VCONN_SWAP); + if (!ret) + return 0; + /* Cannot perform Vconn swap */ + port->upcoming_state = INVALID_STATE; + } + + /* + * Attempt Discover Identity on SOP' if the + * cable was not discovered previously, and use + * the SVDM version of the partner to probe. + */ + if (IS_ERR_OR_NULL(port->cable) && + tcpm_can_communicate_sop_prime(port)) { + *response_tx_sop_type = TCPC_TX_SOP_PRIME; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_IDENT); + rlen = 1; + } else { + *response_tx_sop_type = TCPC_TX_SOP; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_SVID); + rlen = 1; + } + break; + case TCPC_TX_SOP_PRIME: + /* + * svdm_consume_identity_sop_prime will determine + * the svdm_version for the cable moving forward. + */ + svdm_consume_identity_sop_prime(port, p, cnt); + + /* + * If received in SRC_VDM_IDENTITY_REQUEST, continue + * to SRC_SEND_CAPABILITIES + */ + if (port->state == SRC_VDM_IDENTITY_REQUEST) { + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + return 0; + } + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); + + *response_tx_sop_type = TCPC_TX_SOP; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_SVID); + rlen = 1; + break; + default: + return 0; + } + break; + case CMD_DISCOVER_SVID: + *response_tx_sop_type = rx_sop_type; + /* 6.4.4.3.2 */ + if (svdm_consume_svids(port, p, cnt, rx_sop_type)) { + response[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); + rlen = 1; + } else { + if (rx_sop_type == TCPC_TX_SOP) { + tcpm_update_vdm_discovery_state(port, + VDM_DISCOVERY_PARTNER_SVIDS); + if (modep->nsvids && supports_modal(port)) { + response[0] = VDO(modep->svids[0], 1, svdm_version, + CMD_DISCOVER_MODES); + rlen = 1; + } else { + tcpm_update_vdm_discovery_state(port, + VDM_DISCOVERY_COMPLETE); + } + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + tcpm_update_vdm_discovery_state(port, + VDM_DISCOVERY_CABLE_SVIDS); + if (modep_prime->nsvids) { + response[0] = VDO(modep_prime->svids[0], 1, + svdm_version, CMD_DISCOVER_MODES); + rlen = 1; + } else { + tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, + VDM_DISCOVERY_COMPLETE); + } + } + } + break; + case CMD_DISCOVER_MODES: + /* 6.4.4.3.3 */ + svdm_consume_modes(port, p, cnt, rx_sop_type); + rlen = tcpm_handle_discover_mode(port, response, + rx_sop_type, + response_tx_sop_type); + break; + case CMD_ENTER_MODE: + *response_tx_sop_type = rx_sop_type; + if (rx_sop_type == TCPC_TX_SOP) { + if (adev && pdev) { + typec_altmode_update_active(pdev, true); + *adev_action = ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL; + } + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + if (adev && pdev_prime) { + typec_altmode_update_active(pdev_prime, true); + *adev_action = ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL; + } + } + return 0; + case CMD_EXIT_MODE: + *response_tx_sop_type = rx_sop_type; + if (rx_sop_type == TCPC_TX_SOP) { + if (adev && pdev) { + typec_altmode_update_active(pdev, false); + /* Back to USB Operation */ + *adev_action = ADEV_NOTIFY_USB_AND_QUEUE_VDM; + return 0; + } + } + break; + case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15): + break; + default: + /* Unrecognized SVDM */ + response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); + rlen = 1; + response[0] = (response[0] & ~VDO_SVDM_VERS_MASK) | + (VDO_SVDM_VERS(svdm_version)); + break; + } + break; + case CMDT_RSP_NAK: + tcpm_ams_finish(port); + switch (cmd) { + /* + * The cable is not allowed to respond with NAK so this must've happened over SOP + */ + case CMD_DISCOVER_IDENT: + case CMD_DISCOVER_SVID: + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + break; + case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15): + break; + case CMD_DISCOVER_MODES: + tcpm_log(port, "Skip SVID 0x%04x (failed to discover mode)", + PD_VDO_SVID_SVID0(p[0])); + rlen = tcpm_handle_discover_mode(port, response, + rx_sop_type, + response_tx_sop_type); + break; + case CMD_ENTER_MODE: + /* Back to USB Operation */ + *adev_action = ADEV_NOTIFY_USB_AND_QUEUE_VDM; + return 0; + default: + /* Unrecognized SVDM */ + response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); + rlen = 1; + response[0] = (response[0] & ~VDO_SVDM_VERS_MASK) | + (VDO_SVDM_VERS(svdm_version)); + break; + } + break; + default: + response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); + rlen = 1; + response[0] = (response[0] & ~VDO_SVDM_VERS_MASK) | + (VDO_SVDM_VERS(svdm_version)); + break; + } + + /* Informing the alternate mode drivers about everything */ + *adev_action = ADEV_QUEUE_VDM; + return rlen; +} + +static void tcpm_pd_handle_msg(struct tcpm_port *port, + enum pd_msg_request message, + enum tcpm_ams ams); + +static void tcpm_handle_vdm_request(struct tcpm_port *port, + const __le32 *payload, int cnt, + enum tcpm_transmit_type rx_sop_type) +{ + enum adev_actions adev_action = ADEV_NONE; + struct typec_altmode *adev; + u32 p[PD_MAX_PAYLOAD]; + u32 response[8] = { }; + int i, rlen = 0; + enum tcpm_transmit_type response_tx_sop_type = TCPC_TX_SOP; + + for (i = 0; i < cnt; i++) + p[i] = le32_to_cpu(payload[i]); + + adev = typec_match_altmode(port->port_altmode, ALTMODE_DISCOVERY_MAX, + PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0])); + + if (port->vdm_state == VDM_STATE_BUSY) { + /* If UFP responded busy retry after timeout */ + if (PD_VDO_CMDT(p[0]) == CMDT_RSP_BUSY) { + port->vdm_state = VDM_STATE_WAIT_RSP_BUSY; + port->vdo_retry = (p[0] & ~VDO_CMDT_MASK) | + CMDT_INIT; + mod_vdm_delayed_work(port, PD_T_VDM_BUSY); + return; + } + port->vdm_state = VDM_STATE_DONE; + } + + if (PD_VDO_SVDM(p[0]) && (adev || tcpm_vdm_ams(port) || port->nr_snk_vdo)) { + /* + * Here a SVDM is received (INIT or RSP or unknown). Set the vdm_sm_running in + * advance because we are dropping the lock but may send VDMs soon. + * For the cases of INIT received: + * - If no response to send, it will be cleared later in this function. + * - If there are responses to send, it will be cleared in the state machine. + * For the cases of RSP received: + * - If no further INIT to send, it will be cleared later in this function. + * - Otherwise, it will be cleared in the state machine if timeout or it will go + * back here until no further INIT to send. + * For the cases of unknown type received: + * - We will send NAK and the flag will be cleared in the state machine. + */ + port->vdm_sm_running = true; + rlen = tcpm_pd_svdm(port, adev, p, cnt, response, &adev_action, + rx_sop_type, &response_tx_sop_type); + } else { + if (port->negotiated_rev >= PD_REV30) + tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); + } + + /* + * We are done with any state stored in the port struct now, except + * for any port struct changes done by the tcpm_queue_vdm() call + * below, which is a separate operation. + * + * So we can safely release the lock here; and we MUST release the + * lock here to avoid an AB BA lock inversion: + * + * If we keep the lock here then the lock ordering in this path is: + * 1. tcpm_pd_rx_handler take the tcpm port lock + * 2. One of the typec_altmode_* calls below takes the alt-mode's lock + * + * And we also have this ordering: + * 1. alt-mode driver takes the alt-mode's lock + * 2. alt-mode driver calls tcpm_altmode_enter which takes the + * tcpm port lock + * + * Dropping our lock here avoids this. + */ + mutex_unlock(&port->lock); + + if (adev) { + switch (adev_action) { + case ADEV_NONE: + break; + case ADEV_NOTIFY_USB_AND_QUEUE_VDM: + if (rx_sop_type == TCPC_TX_SOP_PRIME) { + typec_cable_altmode_vdm(adev, TYPEC_PLUG_SOP_P, p[0], &p[1], cnt); + } else { + WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB, NULL)); + typec_altmode_vdm(adev, p[0], &p[1], cnt); + } + break; + case ADEV_QUEUE_VDM: + if (rx_sop_type == TCPC_TX_SOP_PRIME) + typec_cable_altmode_vdm(adev, TYPEC_PLUG_SOP_P, p[0], &p[1], cnt); + else + typec_altmode_vdm(adev, p[0], &p[1], cnt); + break; + case ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL: + if (rx_sop_type == TCPC_TX_SOP_PRIME) { + if (typec_cable_altmode_vdm(adev, TYPEC_PLUG_SOP_P, + p[0], &p[1], cnt)) { + int svdm_version = typec_get_cable_svdm_version( + port->typec_port); + if (svdm_version < 0) + break; + + response[0] = VDO(adev->svid, 1, svdm_version, + CMD_EXIT_MODE); + response[0] |= VDO_OPOS(adev->mode); + rlen = 1; + } + } else { + if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) { + int svdm_version = typec_get_negotiated_svdm_version( + port->typec_port); + if (svdm_version < 0) + break; + + response[0] = VDO(adev->svid, 1, svdm_version, + CMD_EXIT_MODE); + response[0] |= VDO_OPOS(adev->mode); + rlen = 1; + } + } + break; + case ADEV_ATTENTION: + if (typec_altmode_attention(adev, p[1])) + tcpm_log(port, "typec_altmode_attention no port partner altmode"); + break; + } + } + + /* + * We must re-take the lock here to balance the unlock in + * tcpm_pd_rx_handler, note that no changes, other then the + * tcpm_queue_vdm call, are made while the lock is held again. + * All that is done after the call is unwinding the call stack until + * we return to tcpm_pd_rx_handler and do the unlock there. + */ + mutex_lock(&port->lock); + + if (rlen > 0) + tcpm_queue_vdm(port, response[0], &response[1], rlen - 1, response_tx_sop_type); + else + port->vdm_sm_running = false; +} + +static unsigned int vdm_ready_timeout(u32 vdm_hdr) +{ + unsigned int timeout; + int cmd = PD_VDO_CMD(vdm_hdr); + + /* its not a structured VDM command */ + if (!PD_VDO_SVDM(vdm_hdr)) + return PD_T_VDM_UNSTRUCTURED; + + switch (PD_VDO_CMDT(vdm_hdr)) { + case CMDT_INIT: + if (cmd == CMD_ENTER_MODE || cmd == CMD_EXIT_MODE) + timeout = PD_T_VDM_WAIT_MODE_E; + else + timeout = PD_T_VDM_SNDR_RSP; + break; + default: + if (cmd == CMD_ENTER_MODE || cmd == CMD_EXIT_MODE) + timeout = PD_T_VDM_E_MODE; + else + timeout = PD_T_VDM_RCVR_RSP; + break; + } + return timeout; +} + +static void vdm_run_state_machine(struct tcpm_port *port) +{ + struct pd_message msg; + int i, res = 0; + u32 vdo_hdr = port->vdo_data[0]; + u32 response[8] = { }; + + switch (port->vdm_state) { + case VDM_STATE_READY: + /* Only transmit VDM if attached */ + if (!port->attached) { + port->vdm_state = VDM_STATE_ERR_BUSY; + break; + } + + /* + * if there's traffic or we're not in PDO ready state don't send + * a VDM. + */ + if (!tcpm_can_send_vdm(port->state)) { + port->vdm_sm_running = false; + break; + } + + /* TODO: AMS operation for Unstructured VDM */ + if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMDT(vdo_hdr) == CMDT_INIT) { + switch (PD_VDO_CMD(vdo_hdr)) { + case CMD_DISCOVER_IDENT: + res = tcpm_ams_start(port, DISCOVER_IDENTITY); + if (res == -EAGAIN) { + port->vdo_data[0] = 0; + mod_vdm_discovery_delayed_work(port, + SEND_DISCOVERY_VDM_RETRY_MS); + } + break; + case CMD_DISCOVER_SVID: + res = tcpm_ams_start(port, DISCOVER_SVIDS); + break; + case CMD_DISCOVER_MODES: + res = tcpm_ams_start(port, DISCOVER_MODES); + break; + case CMD_ENTER_MODE: + res = tcpm_ams_start(port, DFP_TO_UFP_ENTER_MODE); + break; + case CMD_EXIT_MODE: + res = tcpm_ams_start(port, DFP_TO_UFP_EXIT_MODE); + break; + case CMD_ATTENTION: + res = tcpm_ams_start(port, ATTENTION); + break; + case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15): + res = tcpm_ams_start(port, STRUCTURED_VDMS); + break; + default: + res = -EOPNOTSUPP; + break; + } + + if (res < 0) { + port->vdm_state = VDM_STATE_ERR_BUSY; + return; + } + } + + port->vdm_state = VDM_STATE_SEND_MESSAGE; + mod_vdm_delayed_work(port, (port->negotiated_rev >= PD_REV30 && + port->pwr_role == TYPEC_SOURCE && + PD_VDO_SVDM(vdo_hdr) && + PD_VDO_CMDT(vdo_hdr) == CMDT_INIT) ? + PD_T_SINK_TX : 0); + break; + case VDM_STATE_WAIT_RSP_BUSY: + port->vdo_data[0] = port->vdo_retry; + port->vdo_count = 1; + port->vdm_state = VDM_STATE_READY; + tcpm_ams_finish(port); + break; + case VDM_STATE_BUSY: + port->vdm_state = VDM_STATE_ERR_TMOUT; + if (port->ams != NONE_AMS) + tcpm_ams_finish(port); + break; + case VDM_STATE_ERR_SEND: + /* + * When sending Discover Identity to SOP' before establishing an + * explicit contract, do not retry. Instead, weave sending + * Source_Capabilities over SOP and Discover Identity over SOP'. + */ + if (port->state == SRC_VDM_IDENTITY_REQUEST) { + tcpm_ams_finish(port); + port->vdo_data[0] = 0; + port->vdm_state = VDM_STATE_DONE; + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + /* + * A partner which does not support USB PD will not reply, + * so this is not a fatal error. At the same time, some + * devices may not return GoodCRC under some circumstances, + * so we need to retry. + */ + } else if (port->vdm_retries < 3) { + tcpm_log(port, "VDM Tx error, retry"); + port->vdm_retries++; + port->vdm_state = VDM_STATE_READY; + if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMDT(vdo_hdr) == CMDT_INIT) + tcpm_ams_finish(port); + } else { + tcpm_ams_finish(port); + port->vdo_data[0] = 0; + if (port->tx_sop_type == TCPC_TX_SOP) + break; + /* Handle SOP' Transmission Errors */ + switch (PD_VDO_CMD(vdo_hdr)) { + /* + * If Discover Identity fails on SOP', then resume + * discovery process on SOP only. + */ + case CMD_DISCOVER_IDENT: + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version( + port->typec_port), + CMD_DISCOVER_SVID); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); + tcpm_queue_vdm(port, response[0], &response[1], + 0, TCPC_TX_SOP); + break; + /* + * If Discover SVIDs or Discover Modes fail, then + * proceed with Alt Mode discovery process on SOP. + */ + case CMD_DISCOVER_SVID: + tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + break; + case CMD_DISCOVER_MODES: + tcpm_register_partner_altmodes(port); + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + break; + default: + break; + } + } + break; + case VDM_STATE_SEND_MESSAGE: + /* Prepare and send VDM */ + memset(&msg, 0, sizeof(msg)); + if (port->tx_sop_type == TCPC_TX_SOP_PRIME) { + msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, + 0, /* Cable Plug Indicator for DFP/UFP */ + 0, /* Reserved */ + port->negotiated_rev_prime, + port->message_id_prime, + port->vdo_count); + } else { + msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + port->vdo_count); + } + for (i = 0; i < port->vdo_count; i++) + msg.payload[i] = cpu_to_le32(port->vdo_data[i]); + res = tcpm_pd_transmit(port, port->tx_sop_type, &msg); + if (res < 0) { + port->vdm_state = VDM_STATE_ERR_SEND; + } else { + unsigned long timeout; + + port->vdm_retries = 0; + port->vdo_data[0] = 0; + port->vdm_state = VDM_STATE_BUSY; + timeout = vdm_ready_timeout(vdo_hdr); + mod_vdm_delayed_work(port, timeout); + } + break; + default: + break; + } +} + +static void vdm_state_machine_work(struct kthread_work *work) +{ + struct tcpm_port *port = container_of(work, struct tcpm_port, vdm_state_machine); + enum vdm_states prev_state; + + mutex_lock(&port->lock); + + /* + * Continue running as long as the port is not busy and there was + * a state change. + */ + do { + prev_state = port->vdm_state; + vdm_run_state_machine(port); + } while (port->vdm_state != prev_state && + port->vdm_state != VDM_STATE_BUSY && + port->vdm_state != VDM_STATE_SEND_MESSAGE); + + if (port->vdm_state < VDM_STATE_READY) + port->vdm_sm_running = false; + + mutex_unlock(&port->lock); +} + +enum pdo_err { + PDO_NO_ERR, + PDO_ERR_NO_VSAFE5V, + PDO_ERR_VSAFE5V_NOT_FIRST, + PDO_ERR_PDO_TYPE_NOT_IN_ORDER, + PDO_ERR_FIXED_NOT_SORTED, + PDO_ERR_VARIABLE_BATT_NOT_SORTED, + PDO_ERR_DUPE_PDO, + PDO_ERR_PPS_APDO_NOT_SORTED, + PDO_ERR_DUPE_PPS_APDO, +}; + +static const char * const pdo_err_msg[] = { + [PDO_ERR_NO_VSAFE5V] = + " err: source/sink caps should at least have vSafe5V", + [PDO_ERR_VSAFE5V_NOT_FIRST] = + " err: vSafe5V Fixed Supply Object Shall always be the first object", + [PDO_ERR_PDO_TYPE_NOT_IN_ORDER] = + " err: PDOs should be in the following order: Fixed; Battery; Variable", + [PDO_ERR_FIXED_NOT_SORTED] = + " err: Fixed supply pdos should be in increasing order of their fixed voltage", + [PDO_ERR_VARIABLE_BATT_NOT_SORTED] = + " err: Variable/Battery supply pdos should be in increasing order of their minimum voltage", + [PDO_ERR_DUPE_PDO] = + " err: Variable/Batt supply pdos cannot have same min/max voltage", + [PDO_ERR_PPS_APDO_NOT_SORTED] = + " err: Programmable power supply apdos should be in increasing order of their maximum voltage", + [PDO_ERR_DUPE_PPS_APDO] = + " err: Programmable power supply apdos cannot have same min/max voltage and max current", +}; + +static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo, + unsigned int nr_pdo) +{ + unsigned int i; + + /* Should at least contain vSafe5v */ + if (nr_pdo < 1) + return PDO_ERR_NO_VSAFE5V; + + /* The vSafe5V Fixed Supply Object Shall always be the first object */ + if (pdo_type(pdo[0]) != PDO_TYPE_FIXED || + pdo_fixed_voltage(pdo[0]) != VSAFE5V) + return PDO_ERR_VSAFE5V_NOT_FIRST; + + for (i = 1; i < nr_pdo; i++) { + if (pdo_type(pdo[i]) < pdo_type(pdo[i - 1])) { + return PDO_ERR_PDO_TYPE_NOT_IN_ORDER; + } else if (pdo_type(pdo[i]) == pdo_type(pdo[i - 1])) { + enum pd_pdo_type type = pdo_type(pdo[i]); + + switch (type) { + /* + * The remaining Fixed Supply Objects, if + * present, shall be sent in voltage order; + * lowest to highest. + */ + case PDO_TYPE_FIXED: + if (pdo_fixed_voltage(pdo[i]) <= + pdo_fixed_voltage(pdo[i - 1])) + return PDO_ERR_FIXED_NOT_SORTED; + break; + /* + * The Battery Supply Objects and Variable + * supply, if present shall be sent in Minimum + * Voltage order; lowest to highest. + */ + case PDO_TYPE_VAR: + case PDO_TYPE_BATT: + if (pdo_min_voltage(pdo[i]) < + pdo_min_voltage(pdo[i - 1])) + return PDO_ERR_VARIABLE_BATT_NOT_SORTED; + else if ((pdo_min_voltage(pdo[i]) == + pdo_min_voltage(pdo[i - 1])) && + (pdo_max_voltage(pdo[i]) == + pdo_max_voltage(pdo[i - 1]))) + return PDO_ERR_DUPE_PDO; + break; + /* + * The Programmable Power Supply APDOs, if present, + * shall be sent in Maximum Voltage order; + * lowest to highest. + */ + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo[i]) != APDO_TYPE_PPS) + break; + + if (pdo_pps_apdo_max_voltage(pdo[i]) < + pdo_pps_apdo_max_voltage(pdo[i - 1])) + return PDO_ERR_PPS_APDO_NOT_SORTED; + else if (pdo_pps_apdo_min_voltage(pdo[i]) == + pdo_pps_apdo_min_voltage(pdo[i - 1]) && + pdo_pps_apdo_max_voltage(pdo[i]) == + pdo_pps_apdo_max_voltage(pdo[i - 1]) && + pdo_pps_apdo_max_current(pdo[i]) == + pdo_pps_apdo_max_current(pdo[i - 1])) + return PDO_ERR_DUPE_PPS_APDO; + break; + default: + tcpm_log_force(port, " Unknown pdo type"); + } + } + } + + return PDO_NO_ERR; +} + +static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo, + unsigned int nr_pdo) +{ + enum pdo_err err_index = tcpm_caps_err(port, pdo, nr_pdo); + + if (err_index != PDO_NO_ERR) { + tcpm_log_force(port, " %s", pdo_err_msg[err_index]); + return -EINVAL; + } + + return 0; +} + +static int tcpm_altmode_enter(struct typec_altmode *altmode, u32 *vdo) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + int svdm_version; + u32 header; + + svdm_version = typec_get_negotiated_svdm_version(port->typec_port); + if (svdm_version < 0) + return svdm_version; + + header = VDO(altmode->svid, 1, svdm_version, CMD_ENTER_MODE); + header |= VDO_OPOS(altmode->mode); + + return tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0, TCPC_TX_SOP); +} + +static int tcpm_altmode_exit(struct typec_altmode *altmode) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + int svdm_version; + u32 header; + + svdm_version = typec_get_negotiated_svdm_version(port->typec_port); + if (svdm_version < 0) + return svdm_version; + + header = VDO(altmode->svid, 1, svdm_version, CMD_EXIT_MODE); + header |= VDO_OPOS(altmode->mode); + + return tcpm_queue_vdm_unlocked(port, header, NULL, 0, TCPC_TX_SOP); +} + +static int tcpm_altmode_vdm(struct typec_altmode *altmode, + u32 header, const u32 *data, int count) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + + return tcpm_queue_vdm_unlocked(port, header, data, count - 1, TCPC_TX_SOP); +} + +static const struct typec_altmode_ops tcpm_altmode_ops = { + .enter = tcpm_altmode_enter, + .exit = tcpm_altmode_exit, + .vdm = tcpm_altmode_vdm, +}; + + +static int tcpm_cable_altmode_enter(struct typec_altmode *altmode, enum typec_plug_index sop, + u32 *vdo) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + int svdm_version; + u32 header; + + svdm_version = typec_get_cable_svdm_version(port->typec_port); + if (svdm_version < 0) + return svdm_version; + + header = VDO(altmode->svid, 1, svdm_version, CMD_ENTER_MODE); + header |= VDO_OPOS(altmode->mode); + + return tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0, TCPC_TX_SOP_PRIME); +} + +static int tcpm_cable_altmode_exit(struct typec_altmode *altmode, enum typec_plug_index sop) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + int svdm_version; + u32 header; + + svdm_version = typec_get_cable_svdm_version(port->typec_port); + if (svdm_version < 0) + return svdm_version; + + header = VDO(altmode->svid, 1, svdm_version, CMD_EXIT_MODE); + header |= VDO_OPOS(altmode->mode); + + return tcpm_queue_vdm_unlocked(port, header, NULL, 0, TCPC_TX_SOP_PRIME); +} + +static int tcpm_cable_altmode_vdm(struct typec_altmode *altmode, enum typec_plug_index sop, + u32 header, const u32 *data, int count) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + + return tcpm_queue_vdm_unlocked(port, header, data, count - 1, TCPC_TX_SOP_PRIME); +} + +static const struct typec_cable_ops tcpm_cable_ops = { + .enter = tcpm_cable_altmode_enter, + .exit = tcpm_cable_altmode_exit, + .vdm = tcpm_cable_altmode_vdm, +}; + +/* + * PD (data, control) command handling functions + */ +static inline enum tcpm_state ready_state(struct tcpm_port *port) +{ + if (port->pwr_role == TYPEC_SOURCE) + return SRC_READY; + else + return SNK_READY; +} + +static int tcpm_pd_send_control(struct tcpm_port *port, + enum pd_ctrl_msg_type type, + enum tcpm_transmit_type tx_sop_type); + +static void tcpm_handle_alert(struct tcpm_port *port, const __le32 *payload, + int cnt) +{ + u32 p0 = le32_to_cpu(payload[0]); + unsigned int type = usb_pd_ado_type(p0); + + if (!type) { + tcpm_log(port, "Alert message received with no type"); + tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); + return; + } + + /* Just handling non-battery alerts for now */ + if (!(type & USB_PD_ADO_TYPE_BATT_STATUS_CHANGE)) { + if (port->pwr_role == TYPEC_SOURCE) { + port->upcoming_state = GET_STATUS_SEND; + tcpm_ams_start(port, GETTING_SOURCE_SINK_STATUS); + } else { + /* + * Do not check SinkTxOk here in case the Source doesn't set its Rp to + * SinkTxOk in time. + */ + port->ams = GETTING_SOURCE_SINK_STATUS; + tcpm_set_state(port, GET_STATUS_SEND, 0); + } + } else { + tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); + } +} + +static int tcpm_set_auto_vbus_discharge_threshold(struct tcpm_port *port, + enum typec_pwr_opmode mode, bool pps_active, + u32 requested_vbus_voltage) +{ + int ret; + + if (!port->tcpc->set_auto_vbus_discharge_threshold) + return 0; + + ret = port->tcpc->set_auto_vbus_discharge_threshold(port->tcpc, mode, pps_active, + requested_vbus_voltage, + port->pps_data.min_volt); + tcpm_log_force(port, + "set_auto_vbus_discharge_threshold mode:%d pps_active:%c vbus:%u pps_apdo_min_volt:%u ret:%d", + mode, pps_active ? 'y' : 'n', requested_vbus_voltage, + port->pps_data.min_volt, ret); + + return ret; +} + +static void tcpm_pd_handle_state(struct tcpm_port *port, + enum tcpm_state state, + enum tcpm_ams ams, + unsigned int delay_ms) +{ + switch (port->state) { + case SRC_READY: + case SNK_READY: + port->ams = ams; + tcpm_set_state(port, state, delay_ms); + break; + /* 8.3.3.4.1.1 and 6.8.1 power transitioning */ + case SNK_TRANSITION_SINK: + case SNK_TRANSITION_SINK_VBUS: + case SRC_TRANSITION_SUPPLY: + tcpm_set_state(port, HARD_RESET_SEND, 0); + break; + default: + if (!tcpm_ams_interruptible(port)) { + tcpm_set_state(port, port->pwr_role == TYPEC_SOURCE ? + SRC_SOFT_RESET_WAIT_SNK_TX : + SNK_SOFT_RESET, + 0); + } else { + /* process the Message 6.8.1 */ + port->upcoming_state = state; + port->next_ams = ams; + tcpm_set_state(port, ready_state(port), delay_ms); + } + break; + } +} + +static void tcpm_pd_handle_msg(struct tcpm_port *port, + enum pd_msg_request message, + enum tcpm_ams ams) +{ + switch (port->state) { + case SRC_READY: + case SNK_READY: + port->ams = ams; + tcpm_queue_message(port, message); + break; + /* PD 3.0 Spec 8.3.3.4.1.1 and 6.8.1 */ + case SNK_TRANSITION_SINK: + case SNK_TRANSITION_SINK_VBUS: + case SRC_TRANSITION_SUPPLY: + tcpm_set_state(port, HARD_RESET_SEND, 0); + break; + default: + if (!tcpm_ams_interruptible(port)) { + tcpm_set_state(port, port->pwr_role == TYPEC_SOURCE ? + SRC_SOFT_RESET_WAIT_SNK_TX : + SNK_SOFT_RESET, + 0); + } else { + port->next_ams = ams; + tcpm_set_state(port, ready_state(port), 0); + /* 6.8.1 process the Message */ + tcpm_queue_message(port, message); + } + break; + } +} + +static int tcpm_register_source_caps(struct tcpm_port *port) +{ + struct usb_power_delivery_desc desc = { port->negotiated_rev }; + struct usb_power_delivery_capabilities_desc caps = { }; + struct usb_power_delivery_capabilities *cap = port->partner_source_caps; + + if (!port->partner_pd) + port->partner_pd = usb_power_delivery_register(NULL, &desc); + if (IS_ERR(port->partner_pd)) + return PTR_ERR(port->partner_pd); + + memcpy(caps.pdo, port->source_caps, sizeof(u32) * port->nr_source_caps); + caps.role = TYPEC_SOURCE; + + if (cap) { + usb_power_delivery_unregister_capabilities(cap); + port->partner_source_caps = NULL; + } + + cap = usb_power_delivery_register_capabilities(port->partner_pd, &caps); + if (IS_ERR(cap)) + return PTR_ERR(cap); + + port->partner_source_caps = cap; + + return 0; +} + +static int tcpm_register_sink_caps(struct tcpm_port *port) +{ + struct usb_power_delivery_desc desc = { port->negotiated_rev }; + struct usb_power_delivery_capabilities_desc caps = { }; + struct usb_power_delivery_capabilities *cap; + + if (!port->partner_pd) + port->partner_pd = usb_power_delivery_register(NULL, &desc); + if (IS_ERR(port->partner_pd)) + return PTR_ERR(port->partner_pd); + + memcpy(caps.pdo, port->sink_caps, sizeof(u32) * port->nr_sink_caps); + caps.role = TYPEC_SINK; + + cap = usb_power_delivery_register_capabilities(port->partner_pd, &caps); + if (IS_ERR(cap)) + return PTR_ERR(cap); + + port->partner_sink_caps = cap; + + return 0; +} + +static void tcpm_pd_data_request(struct tcpm_port *port, + const struct pd_message *msg, + enum tcpm_transmit_type rx_sop_type) +{ + enum pd_data_msg_type type = pd_header_type_le(msg->header); + unsigned int cnt = pd_header_cnt_le(msg->header); + unsigned int rev = pd_header_rev_le(msg->header); + unsigned int i; + enum frs_typec_current partner_frs_current; + bool frs_enable; + int ret; + + if (tcpm_vdm_ams(port) && type != PD_DATA_VENDOR_DEF) { + port->vdm_state = VDM_STATE_ERR_BUSY; + tcpm_ams_finish(port); + mod_vdm_delayed_work(port, 0); + } + + switch (type) { + case PD_DATA_SOURCE_CAP: + port->spr_avs_data.port_partner_src_status = SPR_AVS_UNKNOWN; + for (i = 0; i < cnt; i++) + port->source_caps[i] = le32_to_cpu(msg->payload[i]); + + port->nr_source_caps = cnt; + + tcpm_log_source_caps(port); + + tcpm_validate_caps(port, port->source_caps, + port->nr_source_caps); + + tcpm_register_source_caps(port); + + /* + * Adjust revision in subsequent message headers, as required, + * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't + * support Rev 1.0 so just do nothing in that scenario. + */ + if (rev == PD_REV10) { + if (port->ams == GET_SOURCE_CAPABILITIES) + tcpm_ams_finish(port); + break; + } + + if (rev < PD_MAX_REV) { + port->negotiated_rev = rev; + if (port->negotiated_rev_prime > port->negotiated_rev) + port->negotiated_rev_prime = port->negotiated_rev; + } + + if (port->pwr_role == TYPEC_SOURCE) { + if (port->ams == GET_SOURCE_CAPABILITIES) + tcpm_pd_handle_state(port, SRC_READY, NONE_AMS, 0); + /* Unexpected Source Capabilities */ + else + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + } else if (port->state == SNK_WAIT_CAPABILITIES || + port->state == SNK_WAIT_CAPABILITIES_TIMEOUT) { + /* + * This message may be received even if VBUS is not + * present. This is quite unexpected; see USB PD + * specification, sections 8.3.3.6.3.1 and 8.3.3.6.3.2. + * However, at the same time, we must be ready to + * receive this message and respond to it 15ms after + * receiving PS_RDY during power swap operations, no matter + * if VBUS is available or not (USB PD specification, + * section 6.5.9.2). + * So we need to accept the message either way, + * but be prepared to keep waiting for VBUS after it was + * handled. + */ + port->ams = POWER_NEGOTIATION; + port->in_ams = true; + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); + } else { + if (port->ams == GET_SOURCE_CAPABILITIES) + tcpm_ams_finish(port); + tcpm_pd_handle_state(port, SNK_NEGOTIATE_CAPABILITIES, + POWER_NEGOTIATION, 0); + } + break; + case PD_DATA_REQUEST: + /* + * Adjust revision in subsequent message headers, as required, + * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't + * support Rev 1.0 so just reject in that scenario. + */ + if (rev == PD_REV10) { + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + break; + } + + if (rev < PD_MAX_REV) { + port->negotiated_rev = rev; + if (port->negotiated_rev_prime > port->negotiated_rev) + port->negotiated_rev_prime = port->negotiated_rev; + } + + if (port->pwr_role != TYPEC_SOURCE || cnt != 1) { + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + break; + } + + port->sink_request = le32_to_cpu(msg->payload[0]); + + if (port->vdm_sm_running && port->explicit_contract) { + tcpm_pd_handle_msg(port, PD_MSG_CTRL_WAIT, port->ams); + break; + } + + if (port->state == SRC_SEND_CAPABILITIES) + tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0); + else + tcpm_pd_handle_state(port, SRC_NEGOTIATE_CAPABILITIES, + POWER_NEGOTIATION, 0); + break; + case PD_DATA_SINK_CAP: + /* We don't do anything with this at the moment... */ + for (i = 0; i < cnt; i++) + port->sink_caps[i] = le32_to_cpu(msg->payload[i]); + + partner_frs_current = (port->sink_caps[0] & PDO_FIXED_FRS_CURR_MASK) >> + PDO_FIXED_FRS_CURR_SHIFT; + frs_enable = partner_frs_current && (partner_frs_current <= + port->new_source_frs_current); + tcpm_log(port, + "Port partner FRS capable partner_frs_current:%u port_frs_current:%u enable:%c", + partner_frs_current, port->new_source_frs_current, frs_enable ? 'y' : 'n'); + if (frs_enable) { + ret = port->tcpc->enable_frs(port->tcpc, true); + tcpm_log(port, "Enable FRS %s, ret:%d\n", ret ? "fail" : "success", ret); + } + + port->nr_sink_caps = cnt; + port->sink_cap_done = true; + tcpm_register_sink_caps(port); + + if (port->ams == GET_SINK_CAPABILITIES) + tcpm_set_state(port, ready_state(port), 0); + /* Unexpected Sink Capabilities */ + else + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + break; + case PD_DATA_VENDOR_DEF: + tcpm_handle_vdm_request(port, msg->payload, cnt, rx_sop_type); + break; + case PD_DATA_BIST: + port->bist_request = le32_to_cpu(msg->payload[0]); + tcpm_pd_handle_state(port, BIST_RX, BIST, 0); + break; + case PD_DATA_ALERT: + if (port->state != SRC_READY && port->state != SNK_READY) + tcpm_pd_handle_state(port, port->pwr_role == TYPEC_SOURCE ? + SRC_SOFT_RESET_WAIT_SNK_TX : SNK_SOFT_RESET, + NONE_AMS, 0); + else + tcpm_handle_alert(port, msg->payload, cnt); + break; + case PD_DATA_BATT_STATUS: + case PD_DATA_GET_COUNTRY_INFO: + /* Currently unsupported */ + tcpm_pd_handle_msg(port, port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + break; + default: + tcpm_pd_handle_msg(port, port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + tcpm_log(port, "Unrecognized data message type %#x", type); + break; + } +} + +static void tcpm_aug_supply_req_complete(struct tcpm_port *port, int result) +{ + if (port->aug_supply_req_pending) { + port->aug_supply_req_status = result; + port->aug_supply_req_pending = false; + complete(&port->aug_supply_req_complete); + } +} + +static void tcpm_pd_ctrl_request(struct tcpm_port *port, + const struct pd_message *msg, + enum tcpm_transmit_type rx_sop_type) +{ + enum pd_ctrl_msg_type type = pd_header_type_le(msg->header); + enum tcpm_state next_state; + unsigned int rev = pd_header_rev_le(msg->header); + + /* + * Stop VDM state machine if interrupted by other Messages while NOT_SUPP is allowed in + * VDM AMS if waiting for VDM responses and will be handled later. + */ + if (tcpm_vdm_ams(port) && type != PD_CTRL_NOT_SUPP && type != PD_CTRL_GOOD_CRC) { + port->vdm_state = VDM_STATE_ERR_BUSY; + tcpm_ams_finish(port); + mod_vdm_delayed_work(port, 0); + } + + switch (type) { + case PD_CTRL_GOOD_CRC: + case PD_CTRL_PING: + break; + case PD_CTRL_GET_SOURCE_CAP: + tcpm_pd_handle_msg(port, PD_MSG_DATA_SOURCE_CAP, GET_SOURCE_CAPABILITIES); + break; + case PD_CTRL_GET_SINK_CAP: + tcpm_pd_handle_msg(port, PD_MSG_DATA_SINK_CAP, GET_SINK_CAPABILITIES); + break; + case PD_CTRL_GOTO_MIN: + break; + case PD_CTRL_PS_RDY: + switch (port->state) { + case SNK_TRANSITION_SINK: + if (port->vbus_present) { + tcpm_set_current_limit(port, + port->req_current_limit, + port->req_supply_voltage); + port->explicit_contract = true; + tcpm_set_auto_vbus_discharge_threshold(port, + TYPEC_PWR_MODE_PD, + port->pps_data.active, + port->supply_voltage); + tcpm_set_state(port, SNK_READY, 0); + } else { + /* + * Seen after power swap. Keep waiting for VBUS + * in a transitional state. + */ + tcpm_set_state(port, + SNK_TRANSITION_SINK_VBUS, 0); + } + break; + case PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED: + tcpm_set_state(port, PR_SWAP_SRC_SNK_SINK_ON, 0); + break; + case PR_SWAP_SNK_SRC_SINK_OFF: + tcpm_set_state(port, PR_SWAP_SNK_SRC_SOURCE_ON, 0); + break; + case VCONN_SWAP_WAIT_FOR_VCONN: + tcpm_set_state(port, VCONN_SWAP_TURN_OFF_VCONN, 0); + break; + case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF: + tcpm_set_state(port, FR_SWAP_SNK_SRC_NEW_SINK_READY, 0); + break; + default: + tcpm_pd_handle_state(port, + port->pwr_role == TYPEC_SOURCE ? + SRC_SOFT_RESET_WAIT_SNK_TX : + SNK_SOFT_RESET, + NONE_AMS, 0); + break; + } + break; + case PD_CTRL_REJECT: + case PD_CTRL_WAIT: + case PD_CTRL_NOT_SUPP: + switch (port->state) { + case SNK_NEGOTIATE_CAPABILITIES: + /* USB PD specification, Figure 8-43 */ + if (port->explicit_contract) + next_state = SNK_READY; + else + next_state = SNK_WAIT_CAPABILITIES; + + /* Threshold was relaxed before sending Request. Restore it back. */ + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_PD, + port->pps_data.active, + port->supply_voltage); + tcpm_set_state(port, next_state, 0); + break; + case SNK_NEGOTIATE_PPS_CAPABILITIES: + /* Revert data back from any requested PPS updates */ + port->pps_data.req_out_volt = port->supply_voltage; + port->pps_data.req_op_curr = port->current_limit; + port->aug_supply_req_status = (type == PD_CTRL_WAIT ? + -EAGAIN : -EOPNOTSUPP); + + /* Threshold was relaxed before sending Request. Restore it back. */ + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_PD, + port->pps_data.active, + port->supply_voltage); + + tcpm_set_state(port, SNK_READY, 0); + break; + case SNK_NEGOTIATE_SPR_AVS_CAPABILITIES: + /* Revert data back from any requested SPR AVS updates */ + port->spr_avs_data.req_out_volt_mv = port->supply_voltage; + port->spr_avs_data.req_op_curr_ma = port->current_limit; + port->aug_supply_req_status = (type == PD_CTRL_WAIT ? + -EAGAIN : -EOPNOTSUPP); + + /* Threshold was relaxed before sending Request. Restore it back. */ + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_PD, + port->spr_avs_data.active, + port->supply_voltage); + + tcpm_set_state(port, SNK_READY, 0); + break; + case DR_SWAP_SEND: + port->swap_status = (type == PD_CTRL_WAIT ? + -EAGAIN : -EOPNOTSUPP); + tcpm_set_state(port, DR_SWAP_CANCEL, 0); + break; + case PR_SWAP_SEND: + port->swap_status = (type == PD_CTRL_WAIT ? + -EAGAIN : -EOPNOTSUPP); + tcpm_set_state(port, PR_SWAP_CANCEL, 0); + break; + case VCONN_SWAP_SEND: + port->swap_status = (type == PD_CTRL_WAIT ? + -EAGAIN : -EOPNOTSUPP); + tcpm_set_state(port, VCONN_SWAP_CANCEL, 0); + break; + case FR_SWAP_SEND: + tcpm_set_state(port, FR_SWAP_CANCEL, 0); + break; + case GET_SINK_CAP: + port->sink_cap_done = true; + tcpm_set_state(port, ready_state(port), 0); + break; + /* + * Some port partners do not support GET_STATUS, avoid soft reset the link to + * prevent redundant power re-negotiation + */ + case GET_STATUS_SEND: + tcpm_set_state(port, ready_state(port), 0); + break; + case SRC_READY: + case SNK_READY: + if (port->vdm_state > VDM_STATE_READY) { + port->vdm_state = VDM_STATE_DONE; + if (tcpm_vdm_ams(port)) + tcpm_ams_finish(port); + mod_vdm_delayed_work(port, 0); + break; + } + fallthrough; + default: + tcpm_pd_handle_state(port, + port->pwr_role == TYPEC_SOURCE ? + SRC_SOFT_RESET_WAIT_SNK_TX : + SNK_SOFT_RESET, + NONE_AMS, 0); + break; + } + break; + case PD_CTRL_ACCEPT: + switch (port->state) { + case SNK_NEGOTIATE_CAPABILITIES: + port->pps_data.active = false; + port->spr_avs_data.active = false; + tcpm_set_state(port, SNK_TRANSITION_SINK, 0); + break; + case SNK_NEGOTIATE_PPS_CAPABILITIES: + port->pps_data.active = true; + port->pps_data.min_volt = port->pps_data.req_min_volt; + port->pps_data.max_volt = port->pps_data.req_max_volt; + port->pps_data.max_curr = port->pps_data.req_max_curr; + port->req_supply_voltage = port->pps_data.req_out_volt; + port->req_current_limit = port->pps_data.req_op_curr; + power_supply_changed(port->psy); + tcpm_set_state(port, SNK_TRANSITION_SINK, 0); + break; + case SNK_NEGOTIATE_SPR_AVS_CAPABILITIES: + port->spr_avs_data.active = true; + port->req_supply_voltage = port->spr_avs_data.req_out_volt_mv; + port->req_current_limit = port->spr_avs_data.req_op_curr_ma; + power_supply_changed(port->psy); + tcpm_set_state(port, SNK_TRANSITION_SINK, 0); + break; + case SOFT_RESET_SEND: + if (port->ams == SOFT_RESET_AMS) + tcpm_ams_finish(port); + /* + * SOP' Soft Reset is done after Vconn Swap, + * which returns to ready state + */ + if (rx_sop_type == TCPC_TX_SOP_PRIME) { + if (rev < port->negotiated_rev_prime) + port->negotiated_rev_prime = rev; + tcpm_set_state(port, ready_state(port), 0); + break; + } + if (port->pwr_role == TYPEC_SOURCE) { + port->upcoming_state = SRC_SEND_CAPABILITIES; + tcpm_ams_start(port, POWER_NEGOTIATION); + } else { + tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); + } + break; + case DR_SWAP_SEND: + tcpm_set_state(port, DR_SWAP_CHANGE_DR, 0); + break; + case PR_SWAP_SEND: + tcpm_set_state(port, PR_SWAP_START, 0); + break; + case VCONN_SWAP_SEND: + tcpm_set_state(port, VCONN_SWAP_START, 0); + break; + case FR_SWAP_SEND: + tcpm_set_state(port, FR_SWAP_SNK_SRC_TRANSITION_TO_OFF, 0); + break; + default: + tcpm_pd_handle_state(port, + port->pwr_role == TYPEC_SOURCE ? + SRC_SOFT_RESET_WAIT_SNK_TX : + SNK_SOFT_RESET, + NONE_AMS, 0); + break; + } + break; + case PD_CTRL_SOFT_RESET: + port->ams = SOFT_RESET_AMS; + tcpm_set_state(port, SOFT_RESET, 0); + break; + case PD_CTRL_DR_SWAP: + /* + * XXX + * 6.3.9: If an alternate mode is active, a request to swap + * alternate modes shall trigger a port reset. + */ + if (port->typec_caps.data != TYPEC_PORT_DRD) { + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + } else { + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && + port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } + + tcpm_pd_handle_state(port, DR_SWAP_ACCEPT, DATA_ROLE_SWAP, 0); + } + break; + case PD_CTRL_PR_SWAP: + if (port->port_type != TYPEC_PORT_DRP) { + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + } else { + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && + port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } + + tcpm_pd_handle_state(port, PR_SWAP_ACCEPT, POWER_ROLE_SWAP, 0); + } + break; + case PD_CTRL_VCONN_SWAP: + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && + port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } + + tcpm_pd_handle_state(port, VCONN_SWAP_ACCEPT, VCONN_SWAP, 0); + break; + case PD_CTRL_GET_SOURCE_CAP_EXT: + case PD_CTRL_GET_STATUS: + case PD_CTRL_FR_SWAP: + case PD_CTRL_GET_PPS_STATUS: + case PD_CTRL_GET_COUNTRY_CODES: + /* Currently not supported */ + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + break; + case PD_CTRL_GET_REVISION: + if (port->negotiated_rev >= PD_REV30 && port->pd_rev.rev_major) + tcpm_pd_handle_msg(port, PD_MSG_DATA_REV, + REVISION_INFORMATION); + else + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + break; + case PD_CTRL_GET_SINK_CAP_EXT: + /* This is an unsupported message if port type is SRC */ + if (port->negotiated_rev >= PD_REV30 && + port->port_type != TYPEC_PORT_SRC) + tcpm_pd_handle_msg(port, PD_MSG_EXT_SINK_CAP_EXT, + GETTING_SINK_EXTENDED_CAPABILITIES); + else + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + break; + default: + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + tcpm_log(port, "Unrecognized ctrl message type %#x", type); + break; + } +} + +static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + const struct pd_message *msg) +{ + enum pd_ext_msg_type type = pd_header_type_le(msg->header); + unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header); + const struct pd_chunked_ext_message_data *ext_msg = &msg->ext_msg; + + /* stopping VDM state machine if interrupted by other Messages */ + if (tcpm_vdm_ams(port)) { + port->vdm_state = VDM_STATE_ERR_BUSY; + tcpm_ams_finish(port); + mod_vdm_delayed_work(port, 0); + } + + if (!(le16_to_cpu(ext_msg->header) & PD_EXT_HDR_CHUNKED)) { + tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); + tcpm_log(port, "Unchunked extended messages unsupported"); + return; + } + + if (data_size > PD_EXT_MAX_CHUNK_DATA) { + tcpm_pd_handle_state(port, CHUNK_NOT_SUPP, NONE_AMS, PD_T_CHUNK_NOT_SUPP); + tcpm_log(port, "Chunk handling not yet supported"); + return; + } + + switch (type) { + case PD_EXT_STATUS: + case PD_EXT_PPS_STATUS: + if (port->ams == GETTING_SOURCE_SINK_STATUS) { + tcpm_ams_finish(port); + tcpm_set_state(port, ready_state(port), 0); + } else { + /* unexpected Status or PPS_Status Message */ + tcpm_pd_handle_state(port, port->pwr_role == TYPEC_SOURCE ? + SRC_SOFT_RESET_WAIT_SNK_TX : SNK_SOFT_RESET, + NONE_AMS, 0); + } + break; + case PD_EXT_GET_BATT_STATUS: + if (data_size >= 1) { + port->batt_request_id = ext_msg->data[0]; + tcpm_pd_handle_msg(port, PD_MSG_DATA_BATT_STATUS, + GETTING_BATTERY_STATUS); + } else { + tcpm_set_state(port, SOFT_RESET_SEND, 0); + } + break; + case PD_EXT_GET_BATT_CAP: + if (data_size >= 1) { + port->batt_request_id = ext_msg->data[0]; + tcpm_pd_handle_msg(port, PD_MSG_EXT_BATT_CAP, + GETTING_BATTERY_CAPABILITIES); + } else { + tcpm_set_state(port, SOFT_RESET_SEND, 0); + } + break; + case PD_EXT_SOURCE_CAP_EXT: + case PD_EXT_BATT_CAP: + case PD_EXT_GET_MANUFACTURER_INFO: + case PD_EXT_MANUFACTURER_INFO: + case PD_EXT_SECURITY_REQUEST: + case PD_EXT_SECURITY_RESPONSE: + case PD_EXT_FW_UPDATE_REQUEST: + case PD_EXT_FW_UPDATE_RESPONSE: + case PD_EXT_COUNTRY_INFO: + case PD_EXT_COUNTRY_CODES: + tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); + break; + default: + tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); + tcpm_log(port, "Unrecognized extended message type %#x", type); + break; + } +} + +static void tcpm_pd_rx_handler(struct kthread_work *work) +{ + struct pd_rx_event *event = container_of(work, + struct pd_rx_event, work); + const struct pd_message *msg = &event->msg; + unsigned int cnt = pd_header_cnt_le(msg->header); + struct tcpm_port *port = event->port; + enum tcpm_transmit_type rx_sop_type = event->rx_sop_type; + + mutex_lock(&port->lock); + + tcpm_log(port, "PD RX, header: %#x [%d]", le16_to_cpu(msg->header), + port->attached); + + if (port->attached) { + enum pd_ctrl_msg_type type = pd_header_type_le(msg->header); + unsigned int msgid = pd_header_msgid_le(msg->header); + + /* + * Drop SOP' messages if cannot receive via + * tcpm_can_communicate_sop_prime + */ + if (rx_sop_type == TCPC_TX_SOP_PRIME && + !tcpm_can_communicate_sop_prime(port)) + goto done; + + /* + * USB PD standard, 6.6.1.2: + * "... if MessageID value in a received Message is the + * same as the stored value, the receiver shall return a + * GoodCRC Message with that MessageID value and drop + * the Message (this is a retry of an already received + * Message). Note: this shall not apply to the Soft_Reset + * Message which always has a MessageID value of zero." + */ + switch (rx_sop_type) { + case TCPC_TX_SOP_PRIME: + if (msgid == port->rx_msgid_prime) + goto done; + port->rx_msgid_prime = msgid; + break; + case TCPC_TX_SOP: + default: + if (msgid == port->rx_msgid && type != PD_CTRL_SOFT_RESET) + goto done; + port->rx_msgid = msgid; + break; + } + + /* + * If both ends believe to be DFP/host, we have a data role + * mismatch. + */ + if (!!(le16_to_cpu(msg->header) & PD_HEADER_DATA_ROLE) == + (port->data_role == TYPEC_HOST) && rx_sop_type == TCPC_TX_SOP) { + tcpm_log(port, + "Data role mismatch, initiating error recovery"); + tcpm_set_state(port, ERROR_RECOVERY, 0); + } else { + if (le16_to_cpu(msg->header) & PD_HEADER_EXT_HDR) + tcpm_pd_ext_msg_request(port, msg); + else if (cnt) + tcpm_pd_data_request(port, msg, rx_sop_type); + else + tcpm_pd_ctrl_request(port, msg, rx_sop_type); + } + } + +done: + mutex_unlock(&port->lock); + kfree(event); +} + +void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg, + enum tcpm_transmit_type rx_sop_type) +{ + struct pd_rx_event *event; + + event = kzalloc_obj(*event, GFP_ATOMIC); + if (!event) + return; + + kthread_init_work(&event->work, tcpm_pd_rx_handler); + event->port = port; + event->rx_sop_type = rx_sop_type; + memcpy(&event->msg, msg, sizeof(*msg)); + kthread_queue_work(port->wq, &event->work); +} +EXPORT_SYMBOL_GPL(tcpm_pd_receive); + +static int tcpm_pd_send_control(struct tcpm_port *port, + enum pd_ctrl_msg_type type, + enum tcpm_transmit_type tx_sop_type) +{ + struct pd_message msg; + + memset(&msg, 0, sizeof(msg)); + switch (tx_sop_type) { + case TCPC_TX_SOP_PRIME: + msg.header = PD_HEADER_LE(type, + 0, /* Cable Plug Indicator for DFP/UFP */ + 0, /* Reserved */ + port->negotiated_rev, + port->message_id_prime, + 0); + break; + case TCPC_TX_SOP: + msg.header = PD_HEADER_LE(type, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + 0); + break; + default: + msg.header = PD_HEADER_LE(type, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + 0); + break; + } + + return tcpm_pd_transmit(port, tx_sop_type, &msg); +} + +/* + * Send queued message without affecting state. + * Return true if state machine should go back to sleep, + * false otherwise. + */ +static bool tcpm_send_queued_message(struct tcpm_port *port) +{ + enum pd_msg_request queued_message; + int ret; + + do { + queued_message = port->queued_message; + port->queued_message = PD_MSG_NONE; + + switch (queued_message) { + case PD_MSG_CTRL_WAIT: + tcpm_pd_send_control(port, PD_CTRL_WAIT, TCPC_TX_SOP); + break; + case PD_MSG_CTRL_REJECT: + tcpm_pd_send_control(port, PD_CTRL_REJECT, TCPC_TX_SOP); + break; + case PD_MSG_CTRL_NOT_SUPP: + tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP, TCPC_TX_SOP); + break; + case PD_MSG_DATA_SINK_CAP: + ret = tcpm_pd_send_sink_caps(port); + if (ret < 0) { + tcpm_log(port, "Unable to send snk caps, ret=%d", ret); + tcpm_set_state(port, SNK_SOFT_RESET, 0); + } + tcpm_ams_finish(port); + break; + case PD_MSG_DATA_SOURCE_CAP: + ret = tcpm_pd_send_source_caps(port); + if (ret < 0) { + tcpm_log(port, + "Unable to send src caps, ret=%d", + ret); + tcpm_set_state(port, SOFT_RESET_SEND, 0); + } else if (port->pwr_role == TYPEC_SOURCE) { + tcpm_ams_finish(port); + tcpm_set_state(port, HARD_RESET_SEND, + PD_T_SENDER_RESPONSE); + } else { + tcpm_ams_finish(port); + } + break; + case PD_MSG_DATA_REV: + ret = tcpm_pd_send_revision(port); + if (ret) + tcpm_log(port, + "Unable to send revision msg, ret=%d", + ret); + tcpm_ams_finish(port); + break; + case PD_MSG_EXT_SINK_CAP_EXT: + ret = tcpm_pd_send_sink_cap_ext(port); + if (ret == -EOPNOTSUPP) + tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP, TCPC_TX_SOP); + else if (ret < 0) + tcpm_log(port, + "Unable to transmit sink cap extended, ret=%d", + ret); + tcpm_ams_finish(port); + break; + case PD_MSG_DATA_BATT_STATUS: + ret = tcpm_pd_send_batt_status(port); + if (ret) + tcpm_log(port, + "Failed to send battery status ret=%d", + ret); + tcpm_ams_finish(port); + break; + case PD_MSG_EXT_BATT_CAP: + ret = tcpm_pd_send_batt_cap(port); + if (ret) + tcpm_log(port, + "Failed to send battery cap ret=%d", + ret); + tcpm_ams_finish(port); + break; + default: + break; + } + } while (port->queued_message != PD_MSG_NONE); + + if (port->delayed_state != INVALID_STATE) { + if (ktime_after(port->delayed_runtime, ktime_get())) { + mod_tcpm_delayed_work(port, ktime_to_ms(ktime_sub(port->delayed_runtime, + ktime_get()))); + return true; + } + port->delayed_state = INVALID_STATE; + } + return false; +} + +static int tcpm_pd_check_request(struct tcpm_port *port) +{ + u32 pdo, rdo = port->sink_request; + unsigned int max, op, pdo_max, index; + enum pd_pdo_type type; + + index = rdo_index(rdo); + if (!index || index > port->nr_src_pdo) + return -EINVAL; + + pdo = port->src_pdo[index - 1]; + type = pdo_type(pdo); + switch (type) { + case PDO_TYPE_FIXED: + case PDO_TYPE_VAR: + max = rdo_max_current(rdo); + op = rdo_op_current(rdo); + pdo_max = pdo_max_current(pdo); + + if (op > pdo_max) + return -EINVAL; + if (max > pdo_max && !(rdo & RDO_CAP_MISMATCH)) + return -EINVAL; + + if (type == PDO_TYPE_FIXED) + tcpm_log(port, + "Requested %u mV, %u mA for %u / %u mA", + pdo_fixed_voltage(pdo), pdo_max, op, max); + else + tcpm_log(port, + "Requested %u -> %u mV, %u mA for %u / %u mA", + pdo_min_voltage(pdo), pdo_max_voltage(pdo), + pdo_max, op, max); + break; + case PDO_TYPE_BATT: + max = rdo_max_power(rdo); + op = rdo_op_power(rdo); + pdo_max = pdo_max_power(pdo); + + if (op > pdo_max) + return -EINVAL; + if (max > pdo_max && !(rdo & RDO_CAP_MISMATCH)) + return -EINVAL; + tcpm_log(port, + "Requested %u -> %u mV, %u mW for %u / %u mW", + pdo_min_voltage(pdo), pdo_max_voltage(pdo), + pdo_max, op, max); + break; + default: + return -EINVAL; + } + + port->op_vsafe5v = index == 1; + + return 0; +} + +#define min_power(x, y) min(pdo_max_power(x), pdo_max_power(y)) +#define min_current(x, y) min(pdo_max_current(x), pdo_max_current(y)) + +static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, + int *src_pdo) +{ + unsigned int i, j, max_src_mv = 0, min_src_mv = 0, max_mw = 0, + max_mv = 0, src_mw = 0, src_ma = 0, max_snk_mv = 0, + min_snk_mv = 0; + int ret = -EINVAL; + + port->pps_data.supported = false; + port->usb_type = POWER_SUPPLY_USB_TYPE_PD; + power_supply_changed(port->psy); + + /* + * Select the source PDO providing the most power which has a + * matchig sink cap. + */ + for (i = 0; i < port->nr_source_caps; i++) { + u32 pdo = port->source_caps[i]; + enum pd_pdo_type type = pdo_type(pdo); + + switch (type) { + case PDO_TYPE_FIXED: + max_src_mv = pdo_fixed_voltage(pdo); + min_src_mv = max_src_mv; + break; + case PDO_TYPE_BATT: + case PDO_TYPE_VAR: + max_src_mv = pdo_max_voltage(pdo); + min_src_mv = pdo_min_voltage(pdo); + break; + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) { + port->pps_data.supported = true; + } else if (pdo_apdo_type(pdo) == APDO_TYPE_SPR_AVS) { + port->spr_avs_data.port_partner_src_status = SPR_AVS_SUPPORTED; + port->spr_avs_data.port_partner_src_pdo_index = i; + } + continue; + default: + tcpm_log(port, "Invalid source PDO type, ignoring"); + continue; + } + + switch (type) { + case PDO_TYPE_FIXED: + case PDO_TYPE_VAR: + src_ma = pdo_max_current(pdo); + src_mw = src_ma * min_src_mv / 1000; + break; + case PDO_TYPE_BATT: + src_mw = pdo_max_power(pdo); + break; + case PDO_TYPE_APDO: + continue; + default: + tcpm_log(port, "Invalid source PDO type, ignoring"); + continue; + } + + for (j = 0; j < port->nr_snk_pdo; j++) { + pdo = port->snk_pdo[j]; + + switch (pdo_type(pdo)) { + case PDO_TYPE_FIXED: + max_snk_mv = pdo_fixed_voltage(pdo); + min_snk_mv = max_snk_mv; + break; + case PDO_TYPE_BATT: + case PDO_TYPE_VAR: + max_snk_mv = pdo_max_voltage(pdo); + min_snk_mv = pdo_min_voltage(pdo); + break; + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) == APDO_TYPE_SPR_AVS) { + port->spr_avs_data.port_snk_status = SPR_AVS_SUPPORTED; + port->spr_avs_data.port_snk_pdo_index = j; + } + continue; + default: + tcpm_log(port, "Invalid sink PDO type, ignoring"); + continue; + } + + if (max_src_mv <= max_snk_mv && + min_src_mv >= min_snk_mv) { + /* Prefer higher voltages if available */ + if ((src_mw == max_mw && min_src_mv > max_mv) || + src_mw > max_mw) { + *src_pdo = i; + *sink_pdo = j; + max_mw = src_mw; + max_mv = min_src_mv; + ret = 0; + } + } + } + } + + if (port->spr_avs_data.port_snk_status == SPR_AVS_UNKNOWN) + port->spr_avs_data.port_snk_status = SPR_AVS_NOT_SUPPORTED; + + if (port->spr_avs_data.port_partner_src_status == SPR_AVS_UNKNOWN) + port->spr_avs_data.port_partner_src_status = SPR_AVS_NOT_SUPPORTED; + + if (port->pps_data.supported && + port->spr_avs_data.port_partner_src_status == SPR_AVS_SUPPORTED) + port->usb_type = POWER_SUPPLY_USB_TYPE_PD_PPS_SPR_AVS; + else if (port->pps_data.supported) + port->usb_type = POWER_SUPPLY_USB_TYPE_PD_PPS; + else if (port->spr_avs_data.port_partner_src_status == SPR_AVS_SUPPORTED) + port->usb_type = POWER_SUPPLY_USB_TYPE_PD_SPR_AVS; + + if (port->usb_type != POWER_SUPPLY_USB_TYPE_PD) + power_supply_changed(port->psy); + + return ret; +} + +static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port) +{ + unsigned int i, src_ma, max_temp_mw = 0, max_op_ma, op_mw; + unsigned int src_pdo = 0; + u32 pdo, src; + + for (i = 1; i < port->nr_source_caps; ++i) { + pdo = port->source_caps[i]; + + switch (pdo_type(pdo)) { + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) { + tcpm_log(port, "Not PPS APDO (source), ignoring"); + continue; + } + + if (port->pps_data.req_out_volt > pdo_pps_apdo_max_voltage(pdo) || + port->pps_data.req_out_volt < pdo_pps_apdo_min_voltage(pdo)) + continue; + + src_ma = pdo_pps_apdo_max_current(pdo); + max_op_ma = min(src_ma, port->pps_data.req_op_curr); + op_mw = max_op_ma * port->pps_data.req_out_volt / 1000; + if (op_mw > max_temp_mw) { + src_pdo = i; + max_temp_mw = op_mw; + } + break; + default: + tcpm_log(port, "Not APDO type (source), ignoring"); + continue; + } + } + + if (src_pdo) { + src = port->source_caps[src_pdo]; + + port->pps_data.req_min_volt = pdo_pps_apdo_min_voltage(src); + port->pps_data.req_max_volt = pdo_pps_apdo_max_voltage(src); + port->pps_data.req_max_curr = pdo_pps_apdo_max_current(src); + port->pps_data.req_op_curr = min(port->pps_data.req_max_curr, + port->pps_data.req_op_curr); + } + + return src_pdo; +} + +static int tcpm_pd_select_spr_avs_apdo(struct tcpm_port *port) +{ + u32 req_out_volt_mv, req_op_curr_ma, src_max_curr_ma = 0, source_cap; + u32 snk_max_curr_ma = 0, src_pdo_index, snk_pdo_index, snk_pdo; + + if (port->spr_avs_data.port_snk_status != SPR_AVS_SUPPORTED || + port->spr_avs_data.port_partner_src_status != + SPR_AVS_SUPPORTED) { + tcpm_log(port, "SPR AVS not supported. port:%s partner:%s", + spr_avs_status_strings[port->spr_avs_data.port_snk_status], + spr_avs_status_strings[port->spr_avs_data.port_partner_src_status]); + return -EOPNOTSUPP; + } + + /* Round up to SPR_AVS_VOLT_MV_STEP */ + req_out_volt_mv = port->spr_avs_data.req_out_volt_mv; + if (req_out_volt_mv % SPR_AVS_VOLT_MV_STEP) { + req_out_volt_mv += SPR_AVS_VOLT_MV_STEP - + (req_out_volt_mv % SPR_AVS_VOLT_MV_STEP); + port->spr_avs_data.req_out_volt_mv = req_out_volt_mv; + } + + /* Round up to RDO_SPR_AVS_CURR_MA_STEP */ + req_op_curr_ma = port->spr_avs_data.req_op_curr_ma; + if (req_op_curr_ma % RDO_SPR_AVS_CURR_MA_STEP) { + req_op_curr_ma += RDO_SPR_AVS_CURR_MA_STEP - + (req_op_curr_ma % RDO_SPR_AVS_CURR_MA_STEP); + port->spr_avs_data.req_op_curr_ma = req_op_curr_ma; + } + + src_pdo_index = port->spr_avs_data.port_partner_src_pdo_index; + snk_pdo_index = port->spr_avs_data.port_snk_pdo_index; + source_cap = port->source_caps[src_pdo_index]; + snk_pdo = port->snk_pdo[snk_pdo_index]; + tcpm_log(port, + "SPR AVS src_pdo_index:%d snk_pdo_index:%d req_op_curr_ma roundup:%u req_out_volt_mv roundup:%u", + src_pdo_index, snk_pdo_index, req_op_curr_ma, req_out_volt_mv); + + if (req_out_volt_mv >= SPR_AVS_TIER1_MIN_VOLT_MV && + req_out_volt_mv <= SPR_AVS_TIER1_MAX_VOLT_MV) { + src_max_curr_ma = + pdo_spr_avs_apdo_9v_to_15v_max_current_ma(source_cap); + snk_max_curr_ma = + pdo_spr_avs_apdo_9v_to_15v_max_current_ma(snk_pdo); + } else if (req_out_volt_mv > SPR_AVS_TIER1_MAX_VOLT_MV && + req_out_volt_mv <= SPR_AVS_TIER2_MAX_VOLT_MV) { + src_max_curr_ma = + pdo_spr_avs_apdo_15v_to_20v_max_current_ma(source_cap); + snk_max_curr_ma = + pdo_spr_avs_apdo_15v_to_20v_max_current_ma(snk_pdo); + } else { + tcpm_log(port, "Invalid SPR AVS req_volt:%umV", req_out_volt_mv); + return -EINVAL; + } + + if (req_op_curr_ma > src_max_curr_ma || + req_op_curr_ma > snk_max_curr_ma) { + tcpm_log(port, + "Invalid SPR AVS request. req_volt:%umV req_curr:%umA src_max_cur:%umA snk_max_cur:%umA", + req_out_volt_mv, req_op_curr_ma, src_max_curr_ma, + snk_max_curr_ma); + return -EINVAL; + } + + /* Max SPR voltage based on both the port and the partner caps */ + if (pdo_spr_avs_apdo_15v_to_20v_max_current_ma(snk_pdo) && + pdo_spr_avs_apdo_15v_to_20v_max_current_ma(source_cap)) + port->spr_avs_data.max_out_volt_mv = SPR_AVS_TIER2_MAX_VOLT_MV; + else + port->spr_avs_data.max_out_volt_mv = SPR_AVS_TIER1_MAX_VOLT_MV; + + /* + * Max SPR AVS curr based on 9V to 15V. This should be higher than or + * equal to 15V to 20V range. + */ + port->spr_avs_data.max_current_ma = + min(pdo_spr_avs_apdo_9v_to_15v_max_current_ma(source_cap), + pdo_spr_avs_apdo_9v_to_15v_max_current_ma(snk_pdo)); + + return src_pdo_index; +} + +static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) +{ + unsigned int mv, ma, mw, flags; + unsigned int max_ma, max_mw; + enum pd_pdo_type type; + u32 pdo, matching_snk_pdo; + int src_pdo_index = 0; + int snk_pdo_index = 0; + int ret; + + ret = tcpm_pd_select_pdo(port, &snk_pdo_index, &src_pdo_index); + if (ret < 0) + return ret; + + pdo = port->source_caps[src_pdo_index]; + matching_snk_pdo = port->snk_pdo[snk_pdo_index]; + type = pdo_type(pdo); + + switch (type) { + case PDO_TYPE_FIXED: + mv = pdo_fixed_voltage(pdo); + break; + case PDO_TYPE_BATT: + case PDO_TYPE_VAR: + mv = pdo_min_voltage(pdo); + break; + default: + tcpm_log(port, "Invalid PDO selected!"); + return -EINVAL; + } + + /* Select maximum available current within the sink pdo's limit */ + if (type == PDO_TYPE_BATT) { + mw = min_power(pdo, matching_snk_pdo); + ma = 1000 * mw / mv; + } else { + ma = min_current(pdo, matching_snk_pdo); + mw = ma * mv / 1000; + } + + flags = RDO_USB_COMM | RDO_NO_SUSPEND; + + /* Set mismatch bit if offered power is less than operating power */ + max_ma = ma; + max_mw = mw; + if (mw < port->operating_snk_mw) { + flags |= RDO_CAP_MISMATCH; + if (type == PDO_TYPE_BATT && + (pdo_max_power(matching_snk_pdo) > pdo_max_power(pdo))) + max_mw = pdo_max_power(matching_snk_pdo); + else if (pdo_max_current(matching_snk_pdo) > + pdo_max_current(pdo)) + max_ma = pdo_max_current(matching_snk_pdo); + } + + tcpm_log(port, "cc=%d cc1=%d cc2=%d vbus=%d vconn=%s polarity=%d", + port->cc_req, port->cc1, port->cc2, port->vbus_source, + port->vconn_role == TYPEC_SOURCE ? "source" : "sink", + port->polarity); + + if (type == PDO_TYPE_BATT) { + *rdo = RDO_BATT(src_pdo_index + 1, mw, max_mw, flags); + + tcpm_log(port, "Requesting PDO %d: %u mV, %u mW%s", + src_pdo_index, mv, mw, + flags & RDO_CAP_MISMATCH ? " [mismatch]" : ""); + } else { + *rdo = RDO_FIXED(src_pdo_index + 1, ma, max_ma, flags); + + tcpm_log(port, "Requesting PDO %d: %u mV, %u mA%s", + src_pdo_index, mv, ma, + flags & RDO_CAP_MISMATCH ? " [mismatch]" : ""); + } + + port->req_current_limit = ma; + port->req_supply_voltage = mv; + + return 0; +} + +static int tcpm_pd_send_request(struct tcpm_port *port) +{ + struct pd_message msg; + int ret; + u32 rdo; + + ret = tcpm_pd_build_request(port, &rdo); + if (ret < 0) + return ret; + + /* + * Relax the threshold as voltage will be adjusted after Accept Message plus tSrcTransition. + * It is safer to modify the threshold here. + */ + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, 0); + + memset(&msg, 0, sizeof(msg)); + msg.header = PD_HEADER_LE(PD_DATA_REQUEST, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, 1); + msg.payload[0] = cpu_to_le32(rdo); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static int tcpm_pd_build_pps_request(struct tcpm_port *port, u32 *rdo) +{ + unsigned int out_mv, op_ma, op_mw, max_mv, max_ma, flags; + unsigned int src_pdo_index; + + src_pdo_index = tcpm_pd_select_pps_apdo(port); + if (!src_pdo_index) + return -EOPNOTSUPP; + + max_mv = port->pps_data.req_max_volt; + max_ma = port->pps_data.req_max_curr; + out_mv = port->pps_data.req_out_volt; + op_ma = port->pps_data.req_op_curr; + + flags = RDO_USB_COMM | RDO_NO_SUSPEND; + + op_mw = (op_ma * out_mv) / 1000; + if (op_mw < port->operating_snk_mw) { + /* + * Try raising current to meet power needs. If that's not enough + * then try upping the voltage. If that's still not enough + * then we've obviously chosen a PPS APDO which really isn't + * suitable so abandon ship. + */ + op_ma = (port->operating_snk_mw * 1000) / out_mv; + if ((port->operating_snk_mw * 1000) % out_mv) + ++op_ma; + op_ma += RDO_PROG_CURR_MA_STEP - (op_ma % RDO_PROG_CURR_MA_STEP); + + if (op_ma > max_ma) { + op_ma = max_ma; + out_mv = (port->operating_snk_mw * 1000) / op_ma; + if ((port->operating_snk_mw * 1000) % op_ma) + ++out_mv; + out_mv += RDO_PROG_VOLT_MV_STEP - + (out_mv % RDO_PROG_VOLT_MV_STEP); + + if (out_mv > max_mv) { + tcpm_log(port, "Invalid PPS APDO selected!"); + return -EINVAL; + } + } + } + + tcpm_log(port, "cc=%d cc1=%d cc2=%d vbus=%d vconn=%s polarity=%d", + port->cc_req, port->cc1, port->cc2, port->vbus_source, + port->vconn_role == TYPEC_SOURCE ? "source" : "sink", + port->polarity); + + *rdo = RDO_PROG(src_pdo_index + 1, out_mv, op_ma, flags); + + tcpm_log(port, "Requesting APDO %d: %u mV, %u mA", + src_pdo_index, out_mv, op_ma); + + port->pps_data.req_op_curr = op_ma; + port->pps_data.req_out_volt = out_mv; + + return 0; +} + +static int tcpm_pd_build_spr_avs_request(struct tcpm_port *port, u32 *rdo) +{ + u32 out_mv, op_ma, flags, snk_pdo_index, source_cap; + unsigned int src_power_mw, snk_power_mw; + int src_pdo_index; + u32 snk_pdo; + + src_pdo_index = tcpm_pd_select_spr_avs_apdo(port); + if (src_pdo_index < 0) + return src_pdo_index; + snk_pdo_index = port->spr_avs_data.port_snk_pdo_index; + source_cap = port->source_caps[src_pdo_index]; + snk_pdo = port->snk_pdo[snk_pdo_index]; + out_mv = port->spr_avs_data.req_out_volt_mv; + op_ma = port->spr_avs_data.req_op_curr_ma; + + flags = RDO_USB_COMM | RDO_NO_SUSPEND; + + /* + * Set capability mismatch when the maximum power needs in the current + * requested AVS voltage tier range is greater than + * port->operating_snk_mw, however, the maximum power offered by the + * source at the current requested AVS voltage tier is less than + * port->operating_sink_mw. + */ + if (out_mv > SPR_AVS_TIER1_MAX_VOLT_MV) { + src_power_mw = + pdo_spr_avs_apdo_15v_to_20v_max_current_ma(source_cap) * + SPR_AVS_TIER2_MAX_VOLT_MV / 1000; + snk_power_mw = + pdo_spr_avs_apdo_15v_to_20v_max_current_ma(snk_pdo) * + SPR_AVS_TIER2_MAX_VOLT_MV / 1000; + } else { + src_power_mw = + pdo_spr_avs_apdo_9v_to_15v_max_current_ma(source_cap) * + SPR_AVS_TIER1_MAX_VOLT_MV / 1000; + snk_power_mw = + pdo_spr_avs_apdo_9v_to_15v_max_current_ma(snk_pdo) * + SPR_AVS_TIER1_MAX_VOLT_MV / 1000; + } + + if (snk_power_mw >= port->operating_snk_mw && + src_power_mw < port->operating_snk_mw) + flags |= RDO_CAP_MISMATCH; + + *rdo = RDO_AVS(src_pdo_index + 1, out_mv, op_ma, flags); + + tcpm_log(port, "Requesting APDO SPR AVS %d: %u mV, %u mA", + src_pdo_index, out_mv, op_ma); + + return 0; +} + +static int tcpm_pd_send_aug_supply_request(struct tcpm_port *port, + enum aug_req_type type) +{ + struct pd_message msg; + int ret; + u32 rdo; + + if (type == PD_PPS) { + ret = tcpm_pd_build_pps_request(port, &rdo); + } else if (type == PD_SPR_AVS) { + ret = tcpm_pd_build_spr_avs_request(port, &rdo); + } else { + tcpm_log(port, "Invalid aug_req_type %d", type); + ret = -EOPNOTSUPP; + } + if (ret < 0) + return ret; + + /* Relax the threshold as voltage will be adjusted right after Accept Message. */ + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, 0); + + memset(&msg, 0, sizeof(msg)); + msg.header = PD_HEADER_LE(PD_DATA_REQUEST, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, 1); + msg.payload[0] = cpu_to_le32(rdo); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static int tcpm_set_vbus(struct tcpm_port *port, bool enable) +{ + int ret; + + if (enable && port->vbus_charge) + return -EINVAL; + + tcpm_log(port, "vbus:=%d charge=%d", enable, port->vbus_charge); + + ret = port->tcpc->set_vbus(port->tcpc, enable, port->vbus_charge); + if (ret < 0) + return ret; + + port->vbus_source = enable; + return 0; +} + +static int tcpm_set_charge(struct tcpm_port *port, bool charge) +{ + int ret; + + if (charge && port->vbus_source) + return -EINVAL; + + if (charge != port->vbus_charge) { + tcpm_log(port, "vbus=%d charge:=%d", port->vbus_source, charge); + ret = port->tcpc->set_vbus(port->tcpc, port->vbus_source, + charge); + if (ret < 0) + return ret; + } + port->vbus_charge = charge; + power_supply_changed(port->psy); + return 0; +} + +static bool tcpm_start_toggling(struct tcpm_port *port, enum typec_cc_status cc) +{ + int ret; + + if (!port->tcpc->start_toggling) + return false; + + tcpm_log_force(port, "Start toggling"); + ret = port->tcpc->start_toggling(port->tcpc, port->port_type, cc); + return ret == 0; +} + +static int tcpm_init_vbus(struct tcpm_port *port) +{ + int ret; + + ret = port->tcpc->set_vbus(port->tcpc, false, false); + port->vbus_source = false; + port->vbus_charge = false; + return ret; +} + +static int tcpm_init_vconn(struct tcpm_port *port) +{ + int ret; + + ret = port->tcpc->set_vconn(port->tcpc, false); + port->vconn_role = TYPEC_SINK; + return ret; +} + +static void tcpm_typec_connect(struct tcpm_port *port) +{ + struct typec_partner *partner; + + if (!port->connected) { + port->connected = true; + /* Make sure we don't report stale identity information */ + memset(&port->partner_ident, 0, sizeof(port->partner_ident)); + port->partner_desc.usb_pd = port->pd_capable; + if (tcpm_port_is_debug(port)) + port->partner_desc.accessory = TYPEC_ACCESSORY_DEBUG; + else if (tcpm_port_is_audio(port)) + port->partner_desc.accessory = TYPEC_ACCESSORY_AUDIO; + else + port->partner_desc.accessory = TYPEC_ACCESSORY_NONE; + partner = typec_register_partner(port->typec_port, &port->partner_desc); + if (IS_ERR(partner)) { + dev_err(port->dev, "Failed to register partner (%ld)\n", PTR_ERR(partner)); + return; + } + + port->partner = partner; + typec_partner_set_usb_power_delivery(port->partner, port->partner_pd); + } +} + +static int tcpm_src_attach(struct tcpm_port *port) +{ + enum typec_cc_polarity polarity = + port->cc2 == TYPEC_CC_RD ? TYPEC_POLARITY_CC2 + : TYPEC_POLARITY_CC1; + int ret; + + if (port->attached) + return 0; + + ret = tcpm_set_polarity(port, polarity); + if (ret < 0) + return ret; + + tcpm_enable_auto_vbus_discharge(port, true); + + /* + * USB Type-C specification, version 1.2, + * chapter 4.5.2.2.8.1 (Attached.SRC Requirements) + * Enable VCONN only if the non-RD port is set to RA. + */ + if ((polarity == TYPEC_POLARITY_CC1 && port->cc2 == TYPEC_CC_RA) || + (polarity == TYPEC_POLARITY_CC2 && port->cc1 == TYPEC_CC_RA)) { + ret = tcpm_set_vconn(port, true); + if (ret < 0) + return ret; + } + + ret = tcpm_set_vbus(port, true); + if (ret < 0) + goto out_disable_vconn; + + ret = tcpm_set_roles(port, true, TYPEC_STATE_USB, TYPEC_SOURCE, + tcpm_data_role_for_source(port)); + if (ret < 0) + goto out_disable_vbus; + + if (port->pd_supported) { + ret = port->tcpc->set_pd_rx(port->tcpc, true); + if (ret < 0) + goto out_disable_mux; + } + + port->pd_capable = false; + + port->partner = NULL; + + port->attached = true; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + + return 0; + +out_disable_mux: + tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE, + TYPEC_ORIENTATION_NONE); +out_disable_vbus: + tcpm_set_vbus(port, false); +out_disable_vconn: + tcpm_set_vconn(port, false); + + return ret; +} + +static void tcpm_typec_disconnect(struct tcpm_port *port) +{ + /* + * Unregister plug/cable outside of port->connected because cable can + * be discovered before SRC_READY/SNK_READY states where port->connected + * is set. + */ + typec_unregister_plug(port->plug_prime); + typec_unregister_cable(port->cable); + port->plug_prime = NULL; + port->cable = NULL; + if (port->connected) { + if (port->partner) { + typec_partner_set_usb_power_delivery(port->partner, NULL); + typec_unregister_partner(port->partner); + port->partner = NULL; + } + port->connected = false; + } +} + +static void tcpm_unregister_altmodes(struct tcpm_port *port) +{ + struct pd_mode_data *modep = &port->mode_data; + struct pd_mode_data *modep_prime = &port->mode_data_prime; + int i; + + for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + typec_unregister_altmode(port->partner_altmode[i]); + port->partner_altmode[i] = NULL; + } + for (i = 0; i < modep_prime->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + typec_unregister_altmode(port->plug_prime_altmode[i]); + port->plug_prime_altmode[i] = NULL; + } + + memset(modep, 0, sizeof(*modep)); + memset(modep_prime, 0, sizeof(*modep_prime)); +} + +static void tcpm_set_partner_usb_comm_capable(struct tcpm_port *port, bool capable) +{ + tcpm_log(port, "Setting usb_comm capable %s", str_true_false(capable)); + + if (port->tcpc->set_partner_usb_comm_capable) + port->tcpc->set_partner_usb_comm_capable(port->tcpc, capable); +} + +static void tcpm_partner_source_caps_reset(struct tcpm_port *port) +{ + usb_power_delivery_unregister_capabilities(port->partner_source_caps); + port->partner_source_caps = NULL; + port->spr_avs_data.port_partner_src_status = SPR_AVS_UNKNOWN; + port->spr_avs_data.active = false; +} + +static void tcpm_reset_port(struct tcpm_port *port) +{ + tcpm_enable_auto_vbus_discharge(port, false); + port->in_ams = false; + port->ams = NONE_AMS; + port->vdm_sm_running = false; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + mod_vdm_discovery_cancel_delayed_work(port); + tcpm_unregister_altmodes(port); + tcpm_typec_disconnect(port); + port->attached = false; + port->pd_capable = false; + port->pps_data.supported = false; + tcpm_set_partner_usb_comm_capable(port, false); + + /* + * First Rx ID should be 0; set this to a sentinel of -1 so that + * we can check tcpm_pd_rx_handler() if we had seen it before. + */ + port->rx_msgid = -1; + port->rx_msgid_prime = -1; + + port->tcpc->set_pd_rx(port->tcpc, false); + tcpm_init_vbus(port); /* also disables charging */ + tcpm_init_vconn(port); + tcpm_set_current_limit(port, 0, 0); + tcpm_set_polarity(port, TYPEC_POLARITY_CC1); + tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE, + TYPEC_ORIENTATION_NONE); + tcpm_set_attached_state(port, false); + port->try_src_count = 0; + port->try_snk_count = 0; + port->usb_type = POWER_SUPPLY_USB_TYPE_C; + power_supply_changed(port->psy); + port->nr_sink_caps = 0; + port->sink_cap_done = false; + if (port->tcpc->enable_frs) + port->tcpc->enable_frs(port->tcpc, false); + + usb_power_delivery_unregister_capabilities(port->partner_sink_caps); + port->partner_sink_caps = NULL; + tcpm_partner_source_caps_reset(port); + usb_power_delivery_unregister(port->partner_pd); + port->partner_pd = NULL; +} + +static void tcpm_detach(struct tcpm_port *port) +{ + if (tcpm_port_is_disconnected(port)) + port->hard_reset_count = 0; + + if (!port->attached) + return; + + if (port->tcpc->set_bist_data) { + tcpm_log(port, "disable BIST MODE TESTDATA"); + port->tcpc->set_bist_data(port->tcpc, false); + } + + tcpm_reset_port(port); +} + +static void tcpm_src_detach(struct tcpm_port *port) +{ + tcpm_detach(port); +} + +static int tcpm_snk_attach(struct tcpm_port *port) +{ + int ret; + + if (port->attached) + return 0; + + ret = tcpm_set_polarity(port, port->cc2 != TYPEC_CC_OPEN ? + TYPEC_POLARITY_CC2 : TYPEC_POLARITY_CC1); + if (ret < 0) + return ret; + + tcpm_enable_auto_vbus_discharge(port, true); + + ret = tcpm_set_roles(port, true, TYPEC_STATE_USB, + TYPEC_SINK, tcpm_data_role_for_sink(port)); + if (ret < 0) + return ret; + + port->pd_capable = false; + + port->partner = NULL; + + port->attached = true; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + + return 0; +} + +static void tcpm_snk_detach(struct tcpm_port *port) +{ + tcpm_detach(port); +} + +static int tcpm_acc_attach(struct tcpm_port *port) +{ + int ret; + enum typec_role role; + enum typec_data_role data; + int state = TYPEC_STATE_USB; + + if (port->attached) + return 0; + + role = tcpm_port_is_sink(port) ? TYPEC_SINK : TYPEC_SOURCE; + data = tcpm_port_is_sink(port) ? tcpm_data_role_for_sink(port) + : tcpm_data_role_for_source(port); + + if (tcpm_port_is_audio(port)) + state = TYPEC_MODE_AUDIO; + + if (tcpm_port_is_debug(port)) + state = TYPEC_MODE_DEBUG; + + ret = tcpm_set_roles(port, true, state, role, data); + if (ret < 0) + return ret; + + port->partner = NULL; + + tcpm_typec_connect(port); + + port->attached = true; + + return 0; +} + +static void tcpm_acc_detach(struct tcpm_port *port) +{ + tcpm_detach(port); +} + +static inline enum tcpm_state hard_reset_state(struct tcpm_port *port) +{ + if (port->hard_reset_count < PD_N_HARD_RESET_COUNT) + return HARD_RESET_SEND; + if (port->pd_capable) + return ERROR_RECOVERY; + if (port->pwr_role == TYPEC_SOURCE) + return SRC_UNATTACHED; + if (port->state == SNK_WAIT_CAPABILITIES || + port->state == SNK_WAIT_CAPABILITIES_TIMEOUT) + return SNK_READY; + return SNK_UNATTACHED; +} + +static inline enum tcpm_state unattached_state(struct tcpm_port *port) +{ + if (port->port_type == TYPEC_PORT_DRP) { + if (port->pwr_role == TYPEC_SOURCE) + return SRC_UNATTACHED; + else + return SNK_UNATTACHED; + } else if (port->port_type == TYPEC_PORT_SRC) { + return SRC_UNATTACHED; + } + + return SNK_UNATTACHED; +} + +static void tcpm_swap_complete(struct tcpm_port *port, int result) +{ + if (port->swap_pending) { + port->swap_status = result; + port->swap_pending = false; + port->non_pd_role_swap = false; + complete(&port->swap_complete); + } +} + +static enum typec_pwr_opmode tcpm_get_pwr_opmode(enum typec_cc_status cc) +{ + switch (cc) { + case TYPEC_CC_RP_1_5: + return TYPEC_PWR_MODE_1_5A; + case TYPEC_CC_RP_3_0: + return TYPEC_PWR_MODE_3_0A; + case TYPEC_CC_RP_DEF: + default: + return TYPEC_PWR_MODE_USB; + } +} + +static enum typec_cc_status tcpm_pwr_opmode_to_rp(enum typec_pwr_opmode opmode) +{ + switch (opmode) { + case TYPEC_PWR_MODE_USB: + return TYPEC_CC_RP_DEF; + case TYPEC_PWR_MODE_1_5A: + return TYPEC_CC_RP_1_5; + case TYPEC_PWR_MODE_3_0A: + case TYPEC_PWR_MODE_PD: + default: + return TYPEC_CC_RP_3_0; + } +} + +static void tcpm_set_initial_svdm_version(struct tcpm_port *port) +{ + if (!port->partner) + return; + + switch (port->negotiated_rev) { + case PD_REV30: + break; + /* + * 6.4.4.2.3 Structured VDM Version + * 2.0 states "At this time, there is only one version (1.0) defined. + * This field Shall be set to zero to indicate Version 1.0." + * 3.0 states "This field Shall be set to 01b to indicate Version 2.0." + * To ensure that we follow the Power Delivery revision we are currently + * operating on, downgrade the SVDM version to the highest one supported + * by the Power Delivery revision. + */ + case PD_REV20: + typec_partner_set_svdm_version(port->partner, SVDM_VER_1_0); + break; + default: + typec_partner_set_svdm_version(port->partner, SVDM_VER_1_0); + break; + } +} + +static void tcpm_set_initial_negotiated_rev(struct tcpm_port *port) +{ + switch (port->pd_rev.rev_major) { + case PD_CAP_REV10: + port->negotiated_rev = PD_REV10; + break; + case PD_CAP_REV20: + port->negotiated_rev = PD_REV20; + break; + case PD_CAP_REV30: + port->negotiated_rev = PD_REV30; + break; + default: + port->negotiated_rev = PD_MAX_REV; + break; + } + port->negotiated_rev_prime = port->negotiated_rev; +} + +static void run_state_machine(struct tcpm_port *port) +{ + int ret; + enum typec_pwr_opmode opmode; + unsigned int msecs; + enum tcpm_state upcoming_state; + + if (port->tcpc->check_contaminant && port->state != CHECK_CONTAMINANT) + port->potential_contaminant = ((port->enter_state == SRC_ATTACH_WAIT && + port->state == SRC_UNATTACHED) || + (port->enter_state == SNK_ATTACH_WAIT && + port->state == SNK_UNATTACHED) || + (port->enter_state == SNK_DEBOUNCED && + port->state == SNK_UNATTACHED)); + + port->enter_state = port->state; + switch (port->state) { + case TOGGLING: + break; + case CHECK_CONTAMINANT: + port->tcpc->check_contaminant(port->tcpc); + break; + /* SRC states */ + case SRC_UNATTACHED: + if (!port->non_pd_role_swap) + tcpm_swap_complete(port, -ENOTCONN); + tcpm_src_detach(port); + if (port->potential_contaminant) { + tcpm_set_state(port, CHECK_CONTAMINANT, 0); + break; + } + if (tcpm_start_toggling(port, tcpm_rp_cc(port))) { + tcpm_set_state(port, TOGGLING, 0); + break; + } + tcpm_set_cc(port, tcpm_rp_cc(port)); + if (port->port_type == TYPEC_PORT_DRP) + tcpm_set_state(port, SNK_UNATTACHED, PD_T_DRP_SNK); + break; + case SRC_ATTACH_WAIT: + if (tcpm_port_is_debug_source(port)) + tcpm_set_state(port, DEBUG_ACC_ATTACHED, + port->timings.cc_debounce_time); + else if (tcpm_port_is_audio(port)) + tcpm_set_state(port, AUDIO_ACC_ATTACHED, + port->timings.cc_debounce_time); + else if (tcpm_port_is_source(port) && port->vbus_vsafe0v) + tcpm_set_state(port, + tcpm_try_snk(port) ? SNK_TRY + : SRC_ATTACHED, + port->timings.cc_debounce_time); + break; + + case SNK_TRY: + port->try_snk_count++; + /* + * Requirements: + * - Do not drive vconn or vbus + * - Terminate CC pins (both) to Rd + * Action: + * - Wait for tDRPTry (PD_T_DRP_TRY). + * Until then, ignore any state changes. + */ + tcpm_set_cc(port, TYPEC_CC_RD); + tcpm_set_state(port, SNK_TRY_WAIT, PD_T_DRP_TRY); + break; + case SNK_TRY_WAIT: + if (tcpm_port_is_sink(port)) { + tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE, 0); + } else { + tcpm_set_state(port, SRC_TRYWAIT, 0); + port->max_wait = 0; + } + break; + case SNK_TRY_WAIT_DEBOUNCE: + tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS, + PD_T_TRY_CC_DEBOUNCE); + break; + case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS: + if (port->vbus_present && tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_ATTACHED, 0); + else + port->max_wait = 0; + break; + case SRC_TRYWAIT: + tcpm_set_cc(port, tcpm_rp_cc(port)); + if (port->max_wait == 0) { + port->max_wait = jiffies + + msecs_to_jiffies(PD_T_DRP_TRY); + tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED, + PD_T_DRP_TRY); + } else { + if (time_is_after_jiffies(port->max_wait)) + tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED, + jiffies_to_msecs(port->max_wait - + jiffies)); + else + tcpm_set_state(port, SNK_UNATTACHED, 0); + } + break; + case SRC_TRYWAIT_DEBOUNCE: + tcpm_set_state(port, SRC_ATTACHED, port->timings.cc_debounce_time); + break; + case SRC_TRYWAIT_UNATTACHED: + tcpm_set_state(port, SNK_UNATTACHED, 0); + break; + + case SRC_ATTACHED: + ret = tcpm_src_attach(port); + tcpm_set_state(port, SRC_UNATTACHED, + ret < 0 ? 0 : PD_T_PS_SOURCE_ON); + break; + case SRC_STARTUP: + opmode = tcpm_get_pwr_opmode(tcpm_rp_cc(port)); + typec_set_pwr_opmode(port->typec_port, opmode); + port->pwr_opmode = TYPEC_PWR_MODE_USB; + port->caps_count = 0; + tcpm_set_initial_negotiated_rev(port); + port->message_id = 0; + port->message_id_prime = 0; + port->rx_msgid = -1; + port->rx_msgid_prime = -1; + port->explicit_contract = false; + /* SNK -> SRC POWER/FAST_ROLE_SWAP finished */ + if (port->ams == POWER_ROLE_SWAP || + port->ams == FAST_ROLE_SWAP) + tcpm_ams_finish(port); + if (!port->pd_supported) { + tcpm_set_state(port, SRC_READY, 0); + break; + } + port->upcoming_state = SRC_SEND_CAPABILITIES; + tcpm_ams_start(port, POWER_NEGOTIATION); + break; + case SRC_SEND_CAPABILITIES: + port->caps_count++; + if (port->caps_count > PD_N_CAPS_COUNT) { + tcpm_set_state(port, SRC_READY, 0); + break; + } + ret = tcpm_pd_send_source_caps(port); + if (ret < 0) { + if (tcpm_can_communicate_sop_prime(port) && + IS_ERR_OR_NULL(port->cable)) + tcpm_set_state(port, SRC_VDM_IDENTITY_REQUEST, 0); + else + tcpm_set_state(port, SRC_SEND_CAPABILITIES, + PD_T_SEND_SOURCE_CAP); + } else { + /* + * Per standard, we should clear the reset counter here. + * However, that can result in state machine hang-ups. + * Reset it only in READY state to improve stability. + */ + /* port->hard_reset_count = 0; */ + port->caps_count = 0; + port->pd_capable = true; + tcpm_set_state_cond(port, SRC_SEND_CAPABILITIES_TIMEOUT, + PD_T_SENDER_RESPONSE); + } + break; + case SRC_SEND_CAPABILITIES_TIMEOUT: + /* + * Error recovery for a PD_DATA_SOURCE_CAP reply timeout. + * + * PD 2.0 sinks are supposed to accept src-capabilities with a + * 3.0 header and simply ignore any src PDOs which the sink does + * not understand such as PPS but some 2.0 sinks instead ignore + * the entire PD_DATA_SOURCE_CAP message, causing contract + * negotiation to fail. + * + * After PD_N_HARD_RESET_COUNT hard-reset attempts, we try + * sending src-capabilities with a lower PD revision to + * make these broken sinks work. + */ + if (port->hard_reset_count < PD_N_HARD_RESET_COUNT) { + tcpm_set_state(port, HARD_RESET_SEND, 0); + } else if (port->negotiated_rev > PD_REV20) { + port->negotiated_rev--; + port->hard_reset_count = 0; + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + } else { + tcpm_set_state(port, hard_reset_state(port), 0); + } + break; + case SRC_NEGOTIATE_CAPABILITIES: + ret = tcpm_pd_check_request(port); + if (ret < 0) { + tcpm_pd_send_control(port, PD_CTRL_REJECT, TCPC_TX_SOP); + if (!port->explicit_contract) { + tcpm_set_state(port, + SRC_WAIT_NEW_CAPABILITIES, 0); + } else { + tcpm_set_state(port, SRC_READY, 0); + } + } else { + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); + tcpm_set_partner_usb_comm_capable(port, + !!(port->sink_request & RDO_USB_COMM)); + tcpm_set_state(port, SRC_TRANSITION_SUPPLY, + PD_T_SRC_TRANSITION); + } + break; + case SRC_TRANSITION_SUPPLY: + /* XXX: regulator_set_voltage(vbus, ...) */ + tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP); + port->explicit_contract = true; + typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD); + port->pwr_opmode = TYPEC_PWR_MODE_PD; + tcpm_set_state_cond(port, SRC_READY, 0); + break; + case SRC_READY: +#if 1 + port->hard_reset_count = 0; +#endif + port->try_src_count = 0; + + tcpm_swap_complete(port, 0); + tcpm_typec_connect(port); + + if (port->ams != NONE_AMS) + tcpm_ams_finish(port); + if (port->next_ams != NONE_AMS) { + port->ams = port->next_ams; + port->next_ams = NONE_AMS; + } + + /* + * If previous AMS is interrupted, switch to the upcoming + * state. + */ + if (port->upcoming_state != INVALID_STATE) { + upcoming_state = port->upcoming_state; + port->upcoming_state = INVALID_STATE; + tcpm_set_state(port, upcoming_state, 0); + break; + } + + /* + * 6.4.4.3.1 Discover Identity + * "The Discover Identity Command Shall only be sent to SOP when there is an + * Explicit Contract." + * + * Discover Identity on SOP' should be discovered prior to the + * ready state, but if done after a Vconn Swap following Discover + * Identity on SOP then the discovery process can be run here + * as well. + */ + if (port->explicit_contract) { + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + tcpm_set_initial_svdm_version(port); + mod_vdm_discovery_delayed_work(port, 0); + } else { + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + /* + * 6.3.5 + * Sending ping messages is not necessary if + * - the source operates at vSafe5V + * or + * - The system is not operating in PD mode + * or + * - Both partners are connected using a Type-C connector + * + * There is no actual need to send PD messages since the local + * port type-c and the spec does not clearly say whether PD is + * possible when type-c is connected to Type-A/B + */ + break; + case SRC_WAIT_NEW_CAPABILITIES: + /* Nothing to do... */ + break; + + /* SNK states */ + case SNK_UNATTACHED: + if (!port->non_pd_role_swap) + tcpm_swap_complete(port, -ENOTCONN); + tcpm_aug_supply_req_complete(port, -ENOTCONN); + tcpm_snk_detach(port); + if (port->potential_contaminant) { + tcpm_set_state(port, CHECK_CONTAMINANT, 0); + break; + } + if (tcpm_start_toggling(port, TYPEC_CC_RD)) { + tcpm_set_state(port, TOGGLING, 0); + break; + } + tcpm_set_cc(port, TYPEC_CC_RD); + if (port->port_type == TYPEC_PORT_DRP) + tcpm_set_state(port, SRC_UNATTACHED, PD_T_DRP_SRC); + break; + case SNK_ATTACH_WAIT: + if (tcpm_port_is_debug_sink(port)) + tcpm_set_state(port, DEBUG_ACC_ATTACHED, + PD_T_CC_DEBOUNCE); + else if (tcpm_port_is_audio(port)) + tcpm_set_state(port, AUDIO_ACC_ATTACHED, + PD_T_CC_DEBOUNCE); + else if ((port->cc1 == TYPEC_CC_OPEN && + port->cc2 != TYPEC_CC_OPEN) || + (port->cc1 != TYPEC_CC_OPEN && + port->cc2 == TYPEC_CC_OPEN)) + tcpm_set_state(port, SNK_DEBOUNCED, + port->timings.cc_debounce_time); + else if (tcpm_port_is_disconnected(port)) + tcpm_set_state(port, SNK_UNATTACHED, + PD_T_PD_DEBOUNCE); + break; + case SNK_DEBOUNCED: + if (tcpm_port_is_disconnected(port)) + tcpm_set_state(port, SNK_UNATTACHED, + PD_T_PD_DEBOUNCE); + else if (tcpm_port_is_debug_sink(port)) + tcpm_set_state(port, DEBUG_ACC_ATTACHED, + PD_T_CC_DEBOUNCE); + else if (tcpm_port_is_audio(port)) + tcpm_set_state(port, AUDIO_ACC_ATTACHED, + PD_T_CC_DEBOUNCE); + else if (port->vbus_present) + tcpm_set_state(port, + tcpm_try_src(port) ? SRC_TRY + : SNK_ATTACHED, + 0); + break; + case SRC_TRY: + port->try_src_count++; + tcpm_set_cc(port, tcpm_rp_cc(port)); + port->max_wait = 0; + tcpm_set_state(port, SRC_TRY_WAIT, 0); + break; + case SRC_TRY_WAIT: + if (port->max_wait == 0) { + port->max_wait = jiffies + + msecs_to_jiffies(PD_T_DRP_TRY); + msecs = PD_T_DRP_TRY; + } else { + if (time_is_after_jiffies(port->max_wait)) + msecs = jiffies_to_msecs(port->max_wait - + jiffies); + else + msecs = 0; + } + tcpm_set_state(port, SNK_TRYWAIT, msecs); + break; + case SRC_TRY_DEBOUNCE: + tcpm_set_state(port, SRC_ATTACHED, PD_T_PD_DEBOUNCE); + break; + case SNK_TRYWAIT: + tcpm_set_cc(port, TYPEC_CC_RD); + tcpm_set_state(port, SNK_TRYWAIT_VBUS, port->timings.cc_debounce_time); + break; + case SNK_TRYWAIT_VBUS: + /* + * TCPM stays in this state indefinitely until VBUS + * is detected as long as Rp is not detected for + * more than a time period of tPDDebounce. + */ + if (port->vbus_present && tcpm_port_is_sink(port)) { + tcpm_set_state(port, SNK_ATTACHED, 0); + break; + } + if (!tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, 0); + break; + case SNK_TRYWAIT_DEBOUNCE: + tcpm_set_state(port, SNK_UNATTACHED, PD_T_PD_DEBOUNCE); + break; + case SNK_ATTACHED: + ret = tcpm_snk_attach(port); + if (ret < 0) + tcpm_set_state(port, SNK_UNATTACHED, 0); + else + /* + * For Type C port controllers that use Battery Charging + * Detection (based on BCv1.2 spec) to detect USB + * charger type, add a delay of "snk_bc12_cmpletion_time" + * before transitioning to SNK_STARTUP to allow BC1.2 + * detection to complete before PD is eventually enabled + * in later states. + */ + tcpm_set_state(port, SNK_STARTUP, + port->timings.snk_bc12_cmpletion_time); + break; + case SNK_STARTUP: + opmode = tcpm_get_pwr_opmode(port->polarity ? + port->cc2 : port->cc1); + typec_set_pwr_opmode(port->typec_port, opmode); + port->pwr_opmode = TYPEC_PWR_MODE_USB; + tcpm_set_initial_negotiated_rev(port); + port->message_id = 0; + port->message_id_prime = 0; + port->rx_msgid = -1; + port->rx_msgid_prime = -1; + port->explicit_contract = false; + + if (port->ams == POWER_ROLE_SWAP || + port->ams == FAST_ROLE_SWAP) + /* SRC -> SNK POWER/FAST_ROLE_SWAP finished */ + tcpm_ams_finish(port); + + tcpm_set_state(port, SNK_DISCOVERY, 0); + break; + case SNK_DISCOVERY: + if (port->vbus_present) { + u32 current_lim = tcpm_get_current_limit(port); + + if (port->slow_charger_loop && (current_lim > PD_P_SNK_STDBY_MW / 5)) + current_lim = PD_P_SNK_STDBY_MW / 5; + tcpm_set_current_limit(port, current_lim, 5000); + /* Not sink vbus if operational current is 0mA */ + tcpm_set_charge(port, !port->pd_supported || + pdo_max_current(port->snk_pdo[0])); + + if (!port->pd_supported) + tcpm_set_state(port, SNK_READY, 0); + else + tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); + break; + } + /* + * For DRP, timeouts differ. Also, handling is supposed to be + * different and much more complex (dead battery detection; + * see USB power delivery specification, section 8.3.3.6.1.5.1). + */ + tcpm_set_state(port, hard_reset_state(port), + port->port_type == TYPEC_PORT_DRP ? + PD_T_DB_DETECT : PD_T_NO_RESPONSE); + break; + case SNK_DISCOVERY_DEBOUNCE: + tcpm_set_state(port, SNK_DISCOVERY_DEBOUNCE_DONE, + port->timings.cc_debounce_time); + break; + case SNK_DISCOVERY_DEBOUNCE_DONE: + if (!tcpm_port_is_disconnected(port) && + tcpm_port_is_sink(port) && + ktime_after(port->delayed_runtime, ktime_get())) { + tcpm_set_state(port, SNK_DISCOVERY, + ktime_to_ms(ktime_sub(port->delayed_runtime, ktime_get()))); + break; + } + tcpm_set_state(port, unattached_state(port), 0); + break; + case SNK_WAIT_CAPABILITIES: + ret = port->tcpc->set_pd_rx(port->tcpc, true); + if (ret < 0) { + tcpm_set_state(port, SNK_READY, 0); + break; + } + /* + * If VBUS has never been low, and we time out waiting + * for source cap, try a soft reset first, in case we + * were already in a stable contract before this boot. + * Do this only once. + */ + if (port->vbus_never_low) { + port->vbus_never_low = false; + upcoming_state = SNK_SOFT_RESET; + } else { + if (!port->self_powered) + upcoming_state = SNK_WAIT_CAPABILITIES_TIMEOUT; + else + upcoming_state = hard_reset_state(port); + } + + tcpm_set_state(port, upcoming_state, + port->timings.sink_wait_cap_time); + break; + case SNK_WAIT_CAPABILITIES_TIMEOUT: + /* + * There are some USB PD sources in the field, which do not + * properly implement the specification and fail to start + * sending Source Capability messages after a soft reset. The + * specification suggests to do a hard reset when no Source + * capability message is received within PD_T_SINK_WAIT_CAP, + * but that might effectively kil the machine's power source. + * + * This slightly diverges from the specification and tries to + * recover from this by explicitly asking for the capabilities + * using the Get_Source_Cap control message before falling back + * to a hard reset. The control message should also be supported + * and handled by all USB PD source and dual role devices + * according to the specification. + */ + if (tcpm_pd_send_control(port, PD_CTRL_GET_SOURCE_CAP, TCPC_TX_SOP)) + tcpm_set_state_cond(port, hard_reset_state(port), 0); + else + tcpm_set_state(port, hard_reset_state(port), + port->timings.sink_wait_cap_time); + break; + case SNK_NEGOTIATE_CAPABILITIES: + port->pd_capable = true; + tcpm_set_partner_usb_comm_capable(port, + !!(port->source_caps[0] & PDO_FIXED_USB_COMM)); + port->hard_reset_count = 0; + ret = tcpm_pd_send_request(port); + if (ret < 0) { + /* Restore back to the original state */ + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_PD, + port->pps_data.active, + port->supply_voltage); + /* Let the Source send capabilities again. */ + tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); + } else { + tcpm_set_state_cond(port, hard_reset_state(port), + PD_T_SENDER_RESPONSE); + } + break; + case SNK_NEGOTIATE_PPS_CAPABILITIES: + case SNK_NEGOTIATE_SPR_AVS_CAPABILITIES: + ret = tcpm_pd_send_aug_supply_request(port, port->state == + SNK_NEGOTIATE_PPS_CAPABILITIES ? + PD_PPS : PD_SPR_AVS); + if (ret < 0) { + /* Restore back to the original state */ + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_PD, + port->pps_data.active, + port->supply_voltage); + port->aug_supply_req_status = ret; + /* + * If this was called due to updates to sink + * capabilities, and pps is no longer valid, we should + * safely fall back to a standard PDO. + */ + if (port->update_sink_caps) + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); + else + tcpm_set_state(port, SNK_READY, 0); + } else { + tcpm_set_state_cond(port, hard_reset_state(port), + PD_T_SENDER_RESPONSE); + } + break; + case SNK_TRANSITION_SINK: + if (port->spr_avs_data.active) { + if (abs(port->req_supply_voltage - port->supply_voltage) > + SPR_AVS_AVS_SMALL_STEP_V * 1000) { + /* + * The Sink Shall reduce its current draw to + * iSnkStdby within tSnkStdby. The reduction to + * iSnkStdby is not required if the voltage + * increase is less than or equal to + * vAvsSmallStep. + */ + tcpm_log(port, + "SPR AVS Setting iSnkstandby. Req vol: %u mV Curr vol: %u mV", + port->req_supply_voltage, + port->supply_voltage); + tcpm_set_current_limit(port, PD_I_SNK_STBY_MA, + port->supply_voltage); + } + /* + * Although tAvsSrcTransSmall is expected to be used + * for voltage transistions smaller than 1V, using + * tAvsSrcTransLarge to be resilient against chargers + * which strictly cannot honor tAvsSrcTransSmall to + * improve interoperability. + */ + tcpm_set_state(port, hard_reset_state(port), + PD_T_AVS_SRC_TRANS_LARGE); + /* + * From the USB PD spec: + * "The Sink Shall transition to Sink Standby before a + * positive ornegative voltage transition of VBUS. + * During Sink Standby the Sink Shall reduce its power + * draw to pSnkStdby." + * + * This is not applicable to PPS though as the port can + * continue to draw negotiated power without switching + * to standby. + */ + } else if (port->supply_voltage != port->req_supply_voltage && + !port->pps_data.active && + (port->current_limit * port->supply_voltage / 1000 > + PD_P_SNK_STDBY_MW)) { + u32 stdby_ma = PD_P_SNK_STDBY_MW * 1000 / + port->supply_voltage; + + tcpm_log(port, "Setting standby current %u mV @ %u mA", + port->supply_voltage, stdby_ma); + tcpm_set_current_limit(port, stdby_ma, + port->supply_voltage); + tcpm_set_state(port, hard_reset_state(port), + PD_T_PS_TRANSITION); + } + break; + case SNK_TRANSITION_SINK_VBUS: + tcpm_set_state(port, hard_reset_state(port), + PD_T_PS_TRANSITION); + break; + case SNK_READY: + port->try_snk_count = 0; + port->update_sink_caps = false; + if (port->explicit_contract) { + typec_set_pwr_opmode(port->typec_port, + TYPEC_PWR_MODE_PD); + port->pwr_opmode = TYPEC_PWR_MODE_PD; + } + + if (!port->pd_capable && port->slow_charger_loop) + tcpm_set_current_limit(port, tcpm_get_current_limit(port), 5000); + tcpm_swap_complete(port, 0); + tcpm_typec_connect(port); + if (port->pd_capable && port->source_caps[0] & PDO_FIXED_DUAL_ROLE) + mod_enable_frs_delayed_work(port, 0); + tcpm_aug_supply_req_complete(port, port->aug_supply_req_status); + + if (port->ams != NONE_AMS) + tcpm_ams_finish(port); + if (port->next_ams != NONE_AMS) { + port->ams = port->next_ams; + port->next_ams = NONE_AMS; + } + + /* + * If previous AMS is interrupted, switch to the upcoming + * state. + */ + if (port->upcoming_state != INVALID_STATE) { + upcoming_state = port->upcoming_state; + port->upcoming_state = INVALID_STATE; + tcpm_set_state(port, upcoming_state, 0); + break; + } + + /* + * 6.4.4.3.1 Discover Identity + * "The Discover Identity Command Shall only be sent to SOP when there is an + * Explicit Contract." + * + * Discover Identity on SOP' should be discovered prior to the + * ready state, but if done after a Vconn Swap following Discover + * Identity on SOP then the discovery process can be run here + * as well. + */ + if (port->explicit_contract) { + if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + tcpm_set_initial_svdm_version(port); + mod_vdm_discovery_delayed_work(port, 0); + } else { + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + power_supply_changed(port->psy); + break; + + /* Accessory states */ + case ACC_UNATTACHED: + tcpm_acc_detach(port); + if (port->port_type == TYPEC_PORT_SRC) + tcpm_set_state(port, SRC_UNATTACHED, 0); + else + tcpm_set_state(port, SNK_UNATTACHED, 0); + break; + case DEBUG_ACC_ATTACHED: + case AUDIO_ACC_ATTACHED: + ret = tcpm_acc_attach(port); + if (ret < 0) + tcpm_set_state(port, ACC_UNATTACHED, 0); + break; + case DEBUG_ACC_DEBOUNCE: + case AUDIO_ACC_DEBOUNCE: + tcpm_set_state(port, ACC_UNATTACHED, port->timings.cc_debounce_time); + break; + + /* Hard_Reset states */ + case HARD_RESET_SEND: + if (port->ams != NONE_AMS) + tcpm_ams_finish(port); + if (!port->self_powered && port->port_type == TYPEC_PORT_SNK) + dev_err(port->dev, "Initiating hard-reset, which might result in machine power-loss.\n"); + /* + * State machine will be directed to HARD_RESET_START, + * thus set upcoming_state to INVALID_STATE. + */ + port->upcoming_state = INVALID_STATE; + tcpm_ams_start(port, HARD_RESET); + break; + case HARD_RESET_START: + port->sink_cap_done = false; + if (port->tcpc->enable_frs) + port->tcpc->enable_frs(port->tcpc, false); + port->hard_reset_count++; + port->tcpc->set_pd_rx(port->tcpc, false); + tcpm_unregister_altmodes(port); + port->nr_sink_caps = 0; + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + mod_vdm_discovery_cancel_delayed_work(port); + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF, + PD_T_PS_HARD_RESET); + else + tcpm_set_state(port, SNK_HARD_RESET_SINK_OFF, 0); + break; + case SRC_HARD_RESET_VBUS_OFF: + /* + * 7.1.5 Response to Hard Resets + * Hard Reset Signaling indicates a communication failure has occurred and the + * Source Shall stop driving VCONN, Shall remove Rp from the VCONN pin and Shall + * drive VBUS to vSafe0V as shown in Figure 7-9. + */ + tcpm_set_vconn(port, false); + tcpm_set_vbus(port, false); + tcpm_set_roles(port, port->self_powered, TYPEC_STATE_USB, TYPEC_SOURCE, + tcpm_data_role_for_source(port)); + /* + * If tcpc fails to notify vbus off, TCPM will wait for PD_T_SAFE_0V + + * PD_T_SRC_RECOVER before turning vbus back on. + * From Table 7-12 Sequence Description for a Source Initiated Hard Reset: + * 4. Policy Engine waits tPSHardReset after sending Hard Reset Signaling and then + * tells the Device Policy Manager to instruct the power supply to perform a + * Hard Reset. The transition to vSafe0V Shall occur within tSafe0V (t2). + * 5. After tSrcRecover the Source applies power to VBUS in an attempt to + * re-establish communication with the Sink and resume USB Default Operation. + * The transition to vSafe5V Shall occur within tSrcTurnOn(t4). + */ + tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SAFE_0V + PD_T_SRC_RECOVER); + break; + case SRC_HARD_RESET_VBUS_ON: + tcpm_set_vconn(port, true); + tcpm_set_vbus(port, true); + if (port->ams == HARD_RESET) + tcpm_ams_finish(port); + if (port->pd_supported) + port->tcpc->set_pd_rx(port->tcpc, true); + tcpm_set_attached_state(port, true); + tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON); + break; + case SNK_HARD_RESET_SINK_OFF: + /* Do not discharge/disconnect during hard reset */ + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, 0); + memset(&port->pps_data, 0, sizeof(port->pps_data)); + tcpm_set_vconn(port, false); + if (port->pd_capable) + tcpm_set_charge(port, false); + tcpm_set_roles(port, port->self_powered, TYPEC_STATE_USB, TYPEC_SINK, + tcpm_data_role_for_sink(port)); + /* + * VBUS may or may not toggle, depending on the adapter. + * If it doesn't toggle, transition to SNK_HARD_RESET_SINK_ON + * directly after timeout. + */ + tcpm_set_state(port, SNK_HARD_RESET_SINK_ON, PD_T_SAFE_0V); + break; + case SNK_HARD_RESET_WAIT_VBUS: + if (port->ams == HARD_RESET) + tcpm_ams_finish(port); + /* Assume we're disconnected if VBUS doesn't come back. */ + tcpm_set_state(port, SNK_UNATTACHED, + PD_T_SRC_RECOVER_MAX + PD_T_SRC_TURN_ON); + break; + case SNK_HARD_RESET_SINK_ON: + /* Note: There is no guarantee that VBUS is on in this state */ + /* + * XXX: + * The specification suggests that dual mode ports in sink + * mode should transition to state PE_SRC_Transition_to_default. + * See USB power delivery specification chapter 8.3.3.6.1.3. + * This would mean to + * - turn off VCONN, reset power supply + * - request hardware reset + * - turn on VCONN + * - Transition to state PE_Src_Startup + * SNK only ports shall transition to state Snk_Startup + * (see chapter 8.3.3.3.8). + * Similar, dual-mode ports in source mode should transition + * to PE_SNK_Transition_to_default. + */ + if (port->pd_capable) { + tcpm_set_current_limit(port, + tcpm_get_current_limit(port), + 5000); + /* Not sink vbus if operational current is 0mA */ + tcpm_set_charge(port, !!pdo_max_current(port->snk_pdo[0])); + } + if (port->ams == HARD_RESET) + tcpm_ams_finish(port); + tcpm_set_attached_state(port, true); + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, VSAFE5V); + tcpm_set_state(port, SNK_STARTUP, 0); + break; + + /* Soft_Reset states */ + case SOFT_RESET: + port->message_id = 0; + port->rx_msgid = -1; + /* remove existing capabilities */ + tcpm_partner_source_caps_reset(port); + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); + port->vdm_sm_running = false; + port->explicit_contract = false; + tcpm_ams_finish(port); + if (port->pwr_role == TYPEC_SOURCE) { + port->upcoming_state = SRC_SEND_CAPABILITIES; + tcpm_ams_start(port, POWER_NEGOTIATION); + } else { + tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); + } + break; + case SRC_SOFT_RESET_WAIT_SNK_TX: + case SNK_SOFT_RESET: + if (port->ams != NONE_AMS) + tcpm_ams_finish(port); + port->upcoming_state = SOFT_RESET_SEND; + tcpm_ams_start(port, SOFT_RESET_AMS); + break; + case SOFT_RESET_SEND: + /* + * Power Delivery 3.0 Section 6.3.13 + * + * A Soft_Reset Message Shall be targeted at a specific entity + * depending on the type of SOP* packet used. + */ + if (port->tx_sop_type == TCPC_TX_SOP_PRIME) { + port->message_id_prime = 0; + port->rx_msgid_prime = -1; + tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET, TCPC_TX_SOP_PRIME); + tcpm_set_state_cond(port, ready_state(port), PD_T_SENDER_RESPONSE); + } else { + port->message_id = 0; + port->rx_msgid = -1; + /* remove existing capabilities */ + tcpm_partner_source_caps_reset(port); + if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET, TCPC_TX_SOP)) + tcpm_set_state_cond(port, hard_reset_state(port), 0); + else + tcpm_set_state_cond(port, hard_reset_state(port), + PD_T_SENDER_RESPONSE); + } + break; + + /* DR_Swap states */ + case DR_SWAP_SEND: + tcpm_pd_send_control(port, PD_CTRL_DR_SWAP, TCPC_TX_SOP); + tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case DR_SWAP_ACCEPT: + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); + tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0); + break; + case DR_SWAP_SEND_TIMEOUT: + tcpm_swap_complete(port, -ETIMEDOUT); + tcpm_ams_finish(port); + tcpm_set_state(port, ready_state(port), 0); + break; + case DR_SWAP_CHANGE_DR: + tcpm_unregister_altmodes(port); + if (port->data_role == TYPEC_HOST) + tcpm_set_roles(port, true, TYPEC_STATE_USB, port->pwr_role, + TYPEC_DEVICE); + else + tcpm_set_roles(port, true, TYPEC_STATE_USB, port->pwr_role, + TYPEC_HOST); + if (port->data_role == TYPEC_HOST || port->negotiated_rev > PD_REV20) + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + tcpm_ams_finish(port); + tcpm_set_state(port, ready_state(port), 0); + break; + + case FR_SWAP_SEND: + if (tcpm_pd_send_control(port, PD_CTRL_FR_SWAP, TCPC_TX_SOP)) { + tcpm_set_state(port, ERROR_RECOVERY, 0); + break; + } + tcpm_set_state_cond(port, FR_SWAP_SEND_TIMEOUT, PD_T_SENDER_RESPONSE); + break; + case FR_SWAP_SEND_TIMEOUT: + tcpm_set_state(port, ERROR_RECOVERY, 0); + break; + case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF: + tcpm_set_state(port, ERROR_RECOVERY, port->timings.ps_src_off_time); + break; + case FR_SWAP_SNK_SRC_NEW_SINK_READY: + if (port->vbus_source) + tcpm_set_state(port, FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED, 0); + else + tcpm_set_state(port, ERROR_RECOVERY, PD_T_RECEIVER_RESPONSE); + break; + case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED: + tcpm_set_pwr_role(port, TYPEC_SOURCE); + if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP)) { + tcpm_set_state(port, ERROR_RECOVERY, 0); + break; + } + tcpm_set_cc(port, tcpm_rp_cc(port)); + tcpm_set_state(port, SRC_STARTUP, PD_T_SWAP_SRC_START); + break; + + /* PR_Swap states */ + case PR_SWAP_ACCEPT: + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); + tcpm_set_state(port, PR_SWAP_START, 0); + break; + case PR_SWAP_SEND: + tcpm_pd_send_control(port, PD_CTRL_PR_SWAP, TCPC_TX_SOP); + tcpm_set_state_cond(port, PR_SWAP_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case PR_SWAP_SEND_TIMEOUT: + tcpm_swap_complete(port, -ETIMEDOUT); + tcpm_set_state(port, ready_state(port), 0); + break; + case PR_SWAP_START: + tcpm_apply_rc(port); + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, PR_SWAP_SRC_SNK_TRANSITION_OFF, + PD_T_SRC_TRANSITION); + else + tcpm_set_state(port, PR_SWAP_SNK_SRC_SINK_OFF, 0); + break; + case PR_SWAP_SRC_SNK_TRANSITION_OFF: + /* + * Prevent vbus discharge circuit from turning on during PR_SWAP + * as this is not a disconnect. + */ + tcpm_set_vbus(port, false); + port->explicit_contract = false; + /* allow time for Vbus discharge, must be < tSrcSwapStdby */ + tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF, + PD_T_SRCSWAPSTDBY); + break; + case PR_SWAP_SRC_SNK_SOURCE_OFF: + tcpm_set_cc(port, TYPEC_CC_RD); + /* allow CC debounce */ + tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED, + port->timings.cc_debounce_time); + break; + case PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED: + /* + * USB-PD standard, 6.2.1.4, Port Power Role: + * "During the Power Role Swap Sequence, for the initial Source + * Port, the Port Power Role field shall be set to Sink in the + * PS_RDY Message indicating that the initial Source’s power + * supply is turned off" + */ + tcpm_set_pwr_role(port, TYPEC_SINK); + if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP)) { + tcpm_set_state(port, ERROR_RECOVERY, 0); + break; + } + tcpm_set_state(port, ERROR_RECOVERY, PD_T_PS_SOURCE_ON_PRS); + break; + case PR_SWAP_SRC_SNK_SINK_ON: + tcpm_enable_auto_vbus_discharge(port, true); + /* Set the vbus disconnect threshold for implicit contract */ + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, VSAFE5V); + tcpm_set_state(port, SNK_STARTUP, 0); + break; + case PR_SWAP_SNK_SRC_SINK_OFF: + /* will be source, remove existing capabilities */ + tcpm_partner_source_caps_reset(port); + /* + * Prevent vbus discharge circuit from turning on during PR_SWAP + * as this is not a disconnect. + */ + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, + port->pps_data.active, 0); + tcpm_set_charge(port, false); + tcpm_set_state(port, ERROR_RECOVERY, port->timings.ps_src_off_time); + break; + case PR_SWAP_SNK_SRC_SOURCE_ON: + tcpm_enable_auto_vbus_discharge(port, true); + tcpm_set_cc(port, tcpm_rp_cc(port)); + tcpm_set_vbus(port, true); + /* + * allow time VBUS ramp-up, must be < tNewSrc + * Also, this window overlaps with CC debounce as well. + * So, Wait for the max of two which is PD_T_NEWSRC + */ + tcpm_set_state(port, PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP, + PD_T_NEWSRC); + break; + case PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP: + /* + * USB PD standard, 6.2.1.4: + * "Subsequent Messages initiated by the Policy Engine, + * such as the PS_RDY Message sent to indicate that Vbus + * is ready, will have the Port Power Role field set to + * Source." + */ + tcpm_set_pwr_role(port, TYPEC_SOURCE); + tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP); + tcpm_set_state(port, SRC_STARTUP, PD_T_SWAP_SRC_START); + break; + + case VCONN_SWAP_ACCEPT: + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); + tcpm_ams_finish(port); + tcpm_set_state(port, VCONN_SWAP_START, 0); + break; + case VCONN_SWAP_SEND: + tcpm_pd_send_control(port, PD_CTRL_VCONN_SWAP, TCPC_TX_SOP); + tcpm_set_state(port, VCONN_SWAP_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case VCONN_SWAP_SEND_TIMEOUT: + tcpm_swap_complete(port, -ETIMEDOUT); + tcpm_set_state(port, ready_state(port), 0); + break; + case VCONN_SWAP_START: + if (port->vconn_role == TYPEC_SOURCE) + tcpm_set_state(port, VCONN_SWAP_WAIT_FOR_VCONN, 0); + else + tcpm_set_state(port, VCONN_SWAP_TURN_ON_VCONN, 0); + break; + case VCONN_SWAP_WAIT_FOR_VCONN: + tcpm_set_state(port, hard_reset_state(port), + PD_T_VCONN_SOURCE_ON); + break; + case VCONN_SWAP_TURN_ON_VCONN: + ret = tcpm_set_vconn(port, true); + tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP); + /* + * USB PD 3.0 Section 6.4.4.3.1 + * + * Note that a Cable Plug or VPD will not be ready for PD + * Communication until tVCONNStable after VCONN has been applied + */ + if (!ret) + tcpm_set_state(port, VCONN_SWAP_SEND_SOFT_RESET, + PD_T_VCONN_STABLE); + else + tcpm_set_state(port, ready_state(port), 0); + break; + case VCONN_SWAP_TURN_OFF_VCONN: + tcpm_set_vconn(port, false); + tcpm_set_state(port, ready_state(port), 0); + break; + case VCONN_SWAP_SEND_SOFT_RESET: + tcpm_swap_complete(port, port->swap_status); + if (tcpm_can_communicate_sop_prime(port)) { + port->tx_sop_type = TCPC_TX_SOP_PRIME; + port->upcoming_state = SOFT_RESET_SEND; + tcpm_ams_start(port, SOFT_RESET_AMS); + } else { + tcpm_set_state(port, ready_state(port), 0); + } + break; + + case DR_SWAP_CANCEL: + case PR_SWAP_CANCEL: + case VCONN_SWAP_CANCEL: + tcpm_swap_complete(port, port->swap_status); + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, SRC_READY, 0); + else + tcpm_set_state(port, SNK_READY, 0); + break; + case FR_SWAP_CANCEL: + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, SRC_READY, 0); + else + tcpm_set_state(port, SNK_READY, 0); + break; + + case BIST_RX: + switch (BDO_MODE_MASK(port->bist_request)) { + case BDO_MODE_CARRIER2: + tcpm_pd_transmit(port, TCPC_TX_BIST_MODE_2, NULL); + tcpm_set_state(port, unattached_state(port), + PD_T_BIST_CONT_MODE); + break; + case BDO_MODE_TESTDATA: + if (port->tcpc->set_bist_data) { + tcpm_log(port, "Enable BIST MODE TESTDATA"); + port->tcpc->set_bist_data(port->tcpc, true); + } + break; + default: + break; + } + break; + case GET_STATUS_SEND: + tcpm_pd_send_control(port, PD_CTRL_GET_STATUS, TCPC_TX_SOP); + tcpm_set_state(port, GET_STATUS_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case GET_STATUS_SEND_TIMEOUT: + tcpm_set_state(port, ready_state(port), 0); + break; + case GET_PPS_STATUS_SEND: + tcpm_pd_send_control(port, PD_CTRL_GET_PPS_STATUS, TCPC_TX_SOP); + tcpm_set_state(port, GET_PPS_STATUS_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case GET_PPS_STATUS_SEND_TIMEOUT: + tcpm_set_state(port, ready_state(port), 0); + break; + case GET_SINK_CAP: + tcpm_pd_send_control(port, PD_CTRL_GET_SINK_CAP, TCPC_TX_SOP); + tcpm_set_state(port, GET_SINK_CAP_TIMEOUT, PD_T_SENDER_RESPONSE); + break; + case GET_SINK_CAP_TIMEOUT: + port->sink_cap_done = true; + tcpm_set_state(port, ready_state(port), 0); + break; + case ERROR_RECOVERY: + tcpm_swap_complete(port, -EPROTO); + tcpm_aug_supply_req_complete(port, -EPROTO); + tcpm_set_state(port, PORT_RESET, 0); + break; + case PORT_RESET: + tcpm_reset_port(port); + if (port->self_powered) + tcpm_set_cc(port, TYPEC_CC_OPEN); + else + tcpm_set_cc(port, tcpm_default_state(port) == SNK_UNATTACHED ? + TYPEC_CC_RD : tcpm_rp_cc(port)); + tcpm_set_state(port, PORT_RESET_WAIT_OFF, + PD_T_ERROR_RECOVERY); + break; + case PORT_RESET_WAIT_OFF: + tcpm_set_state(port, + tcpm_default_state(port), + port->vbus_present ? port->timings.ps_src_off_time : 0); + break; + + /* AMS intermediate state */ + case AMS_START: + if (port->upcoming_state == INVALID_STATE) { + tcpm_set_state(port, port->pwr_role == TYPEC_SOURCE ? + SRC_READY : SNK_READY, 0); + break; + } + + upcoming_state = port->upcoming_state; + port->upcoming_state = INVALID_STATE; + tcpm_set_state(port, upcoming_state, 0); + break; + + /* Chunk state */ + case CHUNK_NOT_SUPP: + tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP, TCPC_TX_SOP); + tcpm_set_state(port, port->pwr_role == TYPEC_SOURCE ? SRC_READY : SNK_READY, 0); + break; + + /* Cable states */ + case SRC_VDM_IDENTITY_REQUEST: + mod_vdm_discovery_delayed_work(port, 0); + port->upcoming_state = SRC_SEND_CAPABILITIES; + break; + + default: + WARN(1, "Unexpected port state %d\n", port->state); + break; + } +} + +static void tcpm_state_machine_work(struct kthread_work *work) +{ + struct tcpm_port *port = container_of(work, struct tcpm_port, state_machine); + enum tcpm_state prev_state; + + mutex_lock(&port->lock); + port->state_machine_running = true; + + if (port->queued_message && tcpm_send_queued_message(port)) + goto done; + + /* If we were queued due to a delayed state change, update it now */ + if (port->delayed_state) { + tcpm_log(port, "state change %s -> %s [delayed %ld ms]", + tcpm_states[port->state], + tcpm_states[port->delayed_state], port->delay_ms); + port->prev_state = port->state; + port->state = port->delayed_state; + port->delayed_state = INVALID_STATE; + } + + /* + * Continue running as long as we have (non-delayed) state changes + * to make. + */ + do { + prev_state = port->state; + run_state_machine(port); + if (port->queued_message) + tcpm_send_queued_message(port); + } while (port->state != prev_state && !port->delayed_state); + +done: + port->state_machine_running = false; + mutex_unlock(&port->lock); +} + +static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, + enum typec_cc_status cc2) +{ + enum typec_cc_status old_cc1, old_cc2; + enum tcpm_state new_state; + + old_cc1 = port->cc1; + old_cc2 = port->cc2; + port->cc1 = cc1; + port->cc2 = cc2; + + tcpm_log_force(port, + "CC1: %u -> %u, CC2: %u -> %u [state %s, polarity %d, %s]", + old_cc1, cc1, old_cc2, cc2, tcpm_states[port->state], + port->polarity, + tcpm_port_is_disconnected(port) ? "disconnected" + : "connected"); + + switch (port->state) { + case TOGGLING: + if (tcpm_port_is_debug_source(port) || tcpm_port_is_audio(port) || + tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_ATTACH_WAIT, 0); + else if (tcpm_port_is_debug_sink(port) || tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_ATTACH_WAIT, 0); + break; + case CHECK_CONTAMINANT: + /* Wait for Toggling to be resumed */ + break; + case SRC_UNATTACHED: + case ACC_UNATTACHED: + if (tcpm_port_is_debug_source(port) || tcpm_port_is_audio(port) || + tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_ATTACH_WAIT, 0); + else if (tcpm_port_is_debug_sink(port)) + tcpm_set_state(port, SNK_ATTACH_WAIT, 0); + break; + case SRC_ATTACH_WAIT: + if (tcpm_port_is_disconnected(port) || + tcpm_port_is_audio_detached(port)) + tcpm_set_state(port, SRC_UNATTACHED, 0); + else if (cc1 != old_cc1 || cc2 != old_cc2) + tcpm_set_state(port, SRC_ATTACH_WAIT, 0); + break; + case SRC_ATTACHED: + case SRC_STARTUP: + case SRC_SEND_CAPABILITIES: + case SRC_READY: + if (tcpm_port_is_disconnected(port) || + !tcpm_port_is_source(port)) { + if (port->port_type == TYPEC_PORT_SRC) + tcpm_set_state(port, SRC_UNATTACHED, tcpm_wait_for_discharge(port)); + else + tcpm_set_state(port, SNK_UNATTACHED, tcpm_wait_for_discharge(port)); + } + break; + case SNK_UNATTACHED: + if (tcpm_port_is_debug_sink(port) || tcpm_port_is_audio(port) || + tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_ATTACH_WAIT, 0); + break; + case SNK_ATTACH_WAIT: + if ((port->cc1 == TYPEC_CC_OPEN && + port->cc2 != TYPEC_CC_OPEN) || + (port->cc1 != TYPEC_CC_OPEN && + port->cc2 == TYPEC_CC_OPEN)) + new_state = SNK_DEBOUNCED; + else if (tcpm_port_is_disconnected(port)) + new_state = SNK_UNATTACHED; + else + break; + if (new_state != port->delayed_state) + tcpm_set_state(port, SNK_ATTACH_WAIT, 0); + break; + case SNK_DEBOUNCED: + if (tcpm_port_is_disconnected(port)) + new_state = SNK_UNATTACHED; + else if (port->vbus_present) + new_state = tcpm_try_src(port) ? SRC_TRY : SNK_ATTACHED; + else + new_state = SNK_UNATTACHED; + if (new_state != port->delayed_state) + tcpm_set_state(port, SNK_DEBOUNCED, 0); + break; + case SNK_READY: + /* + * EXIT condition is based primarily on vbus disconnect and CC is secondary. + * "A port that has entered into USB PD communications with the Source and + * has seen the CC voltage exceed vRd-USB may monitor the CC pin to detect + * cable disconnect in addition to monitoring VBUS. + * + * A port that is monitoring the CC voltage for disconnect (but is not in + * the process of a USB PD PR_Swap or USB PD FR_Swap) shall transition to + * Unattached.SNK within tSinkDisconnect after the CC voltage remains below + * vRd-USB for tPDDebounce." + * + * When set_auto_vbus_discharge_threshold is enabled, CC pins go + * away before vbus decays to disconnect threshold. Allow + * disconnect to be driven by vbus disconnect when auto vbus + * discharge is enabled. + */ + if (!port->auto_vbus_discharge_enabled && tcpm_port_is_disconnected(port)) + tcpm_set_state(port, unattached_state(port), 0); + else if (!port->pd_capable && + (cc1 != old_cc1 || cc2 != old_cc2)) + tcpm_set_current_limit(port, + tcpm_get_current_limit(port), + 5000); + break; + + case AUDIO_ACC_ATTACHED: + if (cc1 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_OPEN) + tcpm_set_state(port, AUDIO_ACC_DEBOUNCE, 0); + break; + case AUDIO_ACC_DEBOUNCE: + if (tcpm_port_is_audio(port)) + tcpm_set_state(port, AUDIO_ACC_ATTACHED, 0); + break; + + case DEBUG_ACC_ATTACHED: + if (cc1 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_OPEN) + tcpm_set_state(port, DEBUG_ACC_DEBOUNCE, 0); + break; + + case DEBUG_ACC_DEBOUNCE: + if (tcpm_port_is_debug(port)) + tcpm_set_state(port, DEBUG_ACC_ATTACHED, 0); + break; + + case SNK_TRY: + /* Do nothing, waiting for timeout */ + break; + + case SNK_DISCOVERY: + /* CC line is unstable, wait for debounce */ + if (tcpm_port_is_disconnected(port)) + tcpm_set_state(port, SNK_DISCOVERY_DEBOUNCE, 0); + break; + case SNK_DISCOVERY_DEBOUNCE: + break; + + case SRC_TRYWAIT: + /* Hand over to state machine if needed */ + if (!port->vbus_present && tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_TRYWAIT_DEBOUNCE, 0); + break; + case SRC_TRYWAIT_DEBOUNCE: + if (port->vbus_present || !tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_TRYWAIT, 0); + break; + case SNK_TRY_WAIT_DEBOUNCE: + if (!tcpm_port_is_sink(port)) { + port->max_wait = 0; + tcpm_set_state(port, SRC_TRYWAIT, PD_T_PD_DEBOUNCE); + } + break; + case SRC_TRY_WAIT: + if (tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_TRY_DEBOUNCE, 0); + break; + case SRC_TRY_DEBOUNCE: + tcpm_set_state(port, SRC_TRY_WAIT, 0); + break; + case SNK_TRYWAIT_DEBOUNCE: + if (tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_TRYWAIT_VBUS, 0); + break; + case SNK_TRYWAIT_VBUS: + if (!tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, 0); + break; + case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS: + if (!tcpm_port_is_sink(port)) + tcpm_set_state(port, SRC_TRYWAIT, PD_T_TRY_CC_DEBOUNCE); + else + tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS, 0); + break; + case SNK_TRYWAIT: + /* Do nothing, waiting for tCCDebounce */ + break; + case PR_SWAP_SNK_SRC_SINK_OFF: + case PR_SWAP_SRC_SNK_TRANSITION_OFF: + case PR_SWAP_SRC_SNK_SOURCE_OFF: + case PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED: + case PR_SWAP_SNK_SRC_SOURCE_ON: + /* + * CC state change is expected in PR_SWAP + * Ignore it. + */ + break; + case FR_SWAP_SEND: + case FR_SWAP_SEND_TIMEOUT: + case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF: + case FR_SWAP_SNK_SRC_NEW_SINK_READY: + case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED: + /* Do nothing, CC change expected */ + break; + + case PORT_RESET: + case PORT_RESET_WAIT_OFF: + /* + * State set back to default mode once the timer completes. + * Ignore CC changes here. + */ + break; + default: + /* + * While acting as sink and auto vbus discharge is enabled, Allow disconnect + * to be driven by vbus disconnect. + */ + if (tcpm_port_is_disconnected(port) && !(port->pwr_role == TYPEC_SINK && + port->auto_vbus_discharge_enabled)) + tcpm_set_state(port, unattached_state(port), 0); + break; + } +} + +static void _tcpm_pd_vbus_on(struct tcpm_port *port) +{ + tcpm_log_force(port, "VBUS on"); + port->vbus_present = true; + /* + * When vbus_present is true i.e. Voltage at VBUS is greater than VSAFE5V implicitly + * states that vbus is not at VSAFE0V, hence clear the vbus_vsafe0v flag here. + */ + port->vbus_vsafe0v = false; + + switch (port->state) { + case SNK_TRANSITION_SINK_VBUS: + port->explicit_contract = true; + tcpm_set_state(port, SNK_READY, 0); + break; + case SNK_DISCOVERY: + tcpm_set_state(port, SNK_DISCOVERY, 0); + break; + + case SNK_DEBOUNCED: + tcpm_set_state(port, tcpm_try_src(port) ? SRC_TRY + : SNK_ATTACHED, + 0); + break; + case SNK_HARD_RESET_WAIT_VBUS: + tcpm_set_state(port, SNK_HARD_RESET_SINK_ON, 0); + break; + case SRC_ATTACHED: + tcpm_set_state(port, SRC_STARTUP, 0); + break; + case SRC_HARD_RESET_VBUS_ON: + tcpm_set_state(port, SRC_STARTUP, 0); + break; + + case SNK_TRY: + /* Do nothing, waiting for timeout */ + break; + case SRC_TRYWAIT: + /* Do nothing, Waiting for Rd to be detected */ + break; + case SRC_TRYWAIT_DEBOUNCE: + tcpm_set_state(port, SRC_TRYWAIT, 0); + break; + case SNK_TRY_WAIT_DEBOUNCE: + /* Do nothing, waiting for PD_DEBOUNCE to do be done */ + break; + case SNK_TRYWAIT: + /* Do nothing, waiting for tCCDebounce */ + break; + case SNK_TRYWAIT_VBUS: + if (tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_ATTACHED, 0); + break; + case SNK_TRYWAIT_DEBOUNCE: + /* Do nothing, waiting for Rp */ + break; + case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS: + if (port->vbus_present && tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_ATTACHED, 0); + break; + case SRC_TRY_WAIT: + case SRC_TRY_DEBOUNCE: + /* Do nothing, waiting for sink detection */ + break; + case FR_SWAP_SEND: + case FR_SWAP_SEND_TIMEOUT: + case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF: + case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED: + if (port->tcpc->frs_sourcing_vbus) + port->tcpc->frs_sourcing_vbus(port->tcpc); + break; + case FR_SWAP_SNK_SRC_NEW_SINK_READY: + if (port->tcpc->frs_sourcing_vbus) + port->tcpc->frs_sourcing_vbus(port->tcpc); + tcpm_set_state(port, FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED, 0); + break; + + case PORT_RESET: + case PORT_RESET_WAIT_OFF: + /* + * State set back to default mode once the timer completes. + * Ignore vbus changes here. + */ + break; + + default: + break; + } +} + +static void _tcpm_pd_vbus_off(struct tcpm_port *port) +{ + tcpm_log_force(port, "VBUS off"); + port->vbus_present = false; + port->vbus_never_low = false; + switch (port->state) { + case SNK_HARD_RESET_SINK_OFF: + tcpm_set_state(port, SNK_HARD_RESET_WAIT_VBUS, 0); + break; + case HARD_RESET_SEND: + break; + case SNK_TRY: + /* Do nothing, waiting for timeout */ + break; + case SRC_TRYWAIT: + /* Hand over to state machine if needed */ + if (tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_TRYWAIT_DEBOUNCE, 0); + break; + case SNK_TRY_WAIT_DEBOUNCE: + /* Do nothing, waiting for PD_DEBOUNCE to do be done */ + break; + case SNK_TRYWAIT: + case SNK_TRYWAIT_VBUS: + case SNK_TRYWAIT_DEBOUNCE: + break; + case SNK_ATTACH_WAIT: + case SNK_DEBOUNCED: + /* Do nothing, as TCPM is still waiting for vbus to reach VSAFE5V to connect */ + break; + + case SNK_NEGOTIATE_CAPABILITIES: + break; + + case PR_SWAP_SRC_SNK_TRANSITION_OFF: + tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF, 0); + break; + + case PR_SWAP_SNK_SRC_SINK_OFF: + /* Do nothing, expected */ + break; + + case PR_SWAP_SNK_SRC_SOURCE_ON: + /* + * Do nothing when vbus off notification is received. + * TCPM can wait for PD_T_NEWSRC in PR_SWAP_SNK_SRC_SOURCE_ON + * for the vbus source to ramp up. + */ + break; + + case PORT_RESET_WAIT_OFF: + tcpm_set_state(port, tcpm_default_state(port), 0); + break; + + case SRC_TRY_WAIT: + case SRC_TRY_DEBOUNCE: + /* Do nothing, waiting for sink detection */ + break; + + case SRC_STARTUP: + case SRC_SEND_CAPABILITIES: + case SRC_SEND_CAPABILITIES_TIMEOUT: + case SRC_NEGOTIATE_CAPABILITIES: + case SRC_TRANSITION_SUPPLY: + case SRC_READY: + case SRC_WAIT_NEW_CAPABILITIES: + /* + * Force to unattached state to re-initiate connection. + * DRP port should move to Unattached.SNK instead of Unattached.SRC if + * sink removed. Although sink removal here is due to source's vbus collapse, + * treat it the same way for consistency. + */ + if (port->port_type == TYPEC_PORT_SRC) + tcpm_set_state(port, SRC_UNATTACHED, tcpm_wait_for_discharge(port)); + else + tcpm_set_state(port, SNK_UNATTACHED, tcpm_wait_for_discharge(port)); + break; + + case PORT_RESET: + /* + * State set back to default mode once the timer completes. + * Ignore vbus changes here. + */ + break; + + case FR_SWAP_SEND: + case FR_SWAP_SEND_TIMEOUT: + case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF: + case FR_SWAP_SNK_SRC_NEW_SINK_READY: + case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED: + /* Do nothing, vbus drop expected */ + break; + + case SNK_HARD_RESET_WAIT_VBUS: + /* Do nothing, its OK to receive vbus off events */ + break; + + default: + if (port->pwr_role == TYPEC_SINK && port->attached) + tcpm_set_state(port, SNK_UNATTACHED, tcpm_wait_for_discharge(port)); + break; + } +} + +static void _tcpm_pd_vbus_vsafe0v(struct tcpm_port *port) +{ + tcpm_log_force(port, "VBUS VSAFE0V"); + port->vbus_vsafe0v = true; + switch (port->state) { + case SRC_HARD_RESET_VBUS_OFF: + /* + * After establishing the vSafe0V voltage condition on VBUS, the Source Shall wait + * tSrcRecover before re-applying VCONN and restoring VBUS to vSafe5V. + */ + tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER); + break; + case SRC_ATTACH_WAIT: + if (tcpm_port_is_source(port)) + tcpm_set_state(port, tcpm_try_snk(port) ? SNK_TRY : SRC_ATTACHED, + port->timings.cc_debounce_time); + break; + case SRC_STARTUP: + case SRC_SEND_CAPABILITIES: + case SRC_SEND_CAPABILITIES_TIMEOUT: + case SRC_NEGOTIATE_CAPABILITIES: + case SRC_TRANSITION_SUPPLY: + case SRC_READY: + case SRC_WAIT_NEW_CAPABILITIES: + if (port->auto_vbus_discharge_enabled) { + if (port->port_type == TYPEC_PORT_SRC) + tcpm_set_state(port, SRC_UNATTACHED, 0); + else + tcpm_set_state(port, SNK_UNATTACHED, 0); + } + break; + case PR_SWAP_SNK_SRC_SINK_OFF: + case PR_SWAP_SNK_SRC_SOURCE_ON: + /* Do nothing, vsafe0v is expected during transition */ + break; + case SNK_ATTACH_WAIT: + case SNK_DEBOUNCED: + /*Do nothing, still waiting for VSAFE5V for connect */ + break; + case SNK_HARD_RESET_WAIT_VBUS: + /* Do nothing, its OK to receive vbus off events */ + break; + default: + if (port->pwr_role == TYPEC_SINK && port->auto_vbus_discharge_enabled) + tcpm_set_state(port, SNK_UNATTACHED, 0); + break; + } +} + +static void _tcpm_pd_hard_reset(struct tcpm_port *port) +{ + tcpm_log_force(port, "Received hard reset"); + if (port->bist_request == BDO_MODE_TESTDATA && port->tcpc->set_bist_data) + port->tcpc->set_bist_data(port->tcpc, false); + + switch (port->state) { + case TOGGLING: + case ERROR_RECOVERY: + case PORT_RESET: + case PORT_RESET_WAIT_OFF: + return; + default: + break; + } + + if (port->ams != NONE_AMS) + port->ams = NONE_AMS; + if (port->hard_reset_count < PD_N_HARD_RESET_COUNT) + port->ams = HARD_RESET; + /* + * If we keep receiving hard reset requests, executing the hard reset + * must have failed. Revert to error recovery if that happens. + */ + tcpm_set_state(port, + port->hard_reset_count < PD_N_HARD_RESET_COUNT ? + HARD_RESET_START : ERROR_RECOVERY, + 0); +} + +static void tcpm_pd_event_handler(struct kthread_work *work) +{ + struct tcpm_port *port = container_of(work, struct tcpm_port, + event_work); + u32 events; + + mutex_lock(&port->lock); + + spin_lock(&port->pd_event_lock); + while (port->pd_events) { + events = port->pd_events; + port->pd_events = 0; + spin_unlock(&port->pd_event_lock); + if (events & TCPM_RESET_EVENT) + _tcpm_pd_hard_reset(port); + if (events & TCPM_VBUS_EVENT) { + bool vbus; + + vbus = port->tcpc->get_vbus(port->tcpc); + if (vbus) { + _tcpm_pd_vbus_on(port); + } else { + _tcpm_pd_vbus_off(port); + /* + * When TCPC does not support detecting vsafe0v voltage level, + * treat vbus absent as vsafe0v. Else invoke is_vbus_vsafe0v + * to see if vbus has discharge to VSAFE0V. + */ + if (!port->tcpc->is_vbus_vsafe0v || + port->tcpc->is_vbus_vsafe0v(port->tcpc)) + _tcpm_pd_vbus_vsafe0v(port); + } + } + if (events & TCPM_CC_EVENT) { + enum typec_cc_status cc1, cc2; + + if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0) + _tcpm_cc_change(port, cc1, cc2); + } + if (events & TCPM_FRS_EVENT) { + if (port->state == SNK_READY) { + int ret; + + port->upcoming_state = FR_SWAP_SEND; + ret = tcpm_ams_start(port, FAST_ROLE_SWAP); + if (ret == -EAGAIN) + port->upcoming_state = INVALID_STATE; + } else { + tcpm_log(port, "Discarding FRS_SIGNAL! Not in sink ready"); + } + } + if (events & TCPM_SOURCING_VBUS) { + tcpm_log(port, "sourcing vbus"); + /* + * In fast role swap case TCPC autonomously sources vbus. Set vbus_source + * true as TCPM wouldn't have called tcpm_set_vbus. + * + * When vbus is sourced on the command on TCPM i.e. TCPM called + * tcpm_set_vbus to source vbus, vbus_source would already be true. + */ + port->vbus_source = true; + _tcpm_pd_vbus_on(port); + } + if (events & TCPM_PORT_CLEAN) { + tcpm_log(port, "port clean"); + if (port->state == CHECK_CONTAMINANT) { + if (tcpm_start_toggling(port, tcpm_rp_cc(port))) + tcpm_set_state(port, TOGGLING, 0); + else + tcpm_set_state(port, tcpm_default_state(port), 0); + } + } + if (events & TCPM_PORT_ERROR) { + tcpm_log(port, "port triggering error recovery"); + tcpm_set_state(port, ERROR_RECOVERY, 0); + } + + spin_lock(&port->pd_event_lock); + } + spin_unlock(&port->pd_event_lock); + mutex_unlock(&port->lock); +} + +void tcpm_cc_change(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events |= TCPM_CC_EVENT; + spin_unlock(&port->pd_event_lock); + kthread_queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_cc_change); + +void tcpm_vbus_change(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events |= TCPM_VBUS_EVENT; + spin_unlock(&port->pd_event_lock); + kthread_queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_vbus_change); + +void tcpm_pd_hard_reset(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events = TCPM_RESET_EVENT; + spin_unlock(&port->pd_event_lock); + kthread_queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_pd_hard_reset); + +void tcpm_sink_frs(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events |= TCPM_FRS_EVENT; + spin_unlock(&port->pd_event_lock); + kthread_queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_sink_frs); + +void tcpm_sourcing_vbus(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events |= TCPM_SOURCING_VBUS; + spin_unlock(&port->pd_event_lock); + kthread_queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_sourcing_vbus); + +void tcpm_port_clean(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events |= TCPM_PORT_CLEAN; + spin_unlock(&port->pd_event_lock); + kthread_queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_port_clean); + +bool tcpm_port_is_toggling(struct tcpm_port *port) +{ + return port->port_type == TYPEC_PORT_DRP && port->state == TOGGLING; +} +EXPORT_SYMBOL_GPL(tcpm_port_is_toggling); + +void tcpm_port_error_recovery(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events |= TCPM_PORT_ERROR; + spin_unlock(&port->pd_event_lock); + kthread_queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_port_error_recovery); + +static void tcpm_enable_frs_work(struct kthread_work *work) +{ + struct tcpm_port *port = container_of(work, struct tcpm_port, enable_frs); + int ret; + + mutex_lock(&port->lock); + /* Not FRS capable */ + if (!port->connected || port->port_type != TYPEC_PORT_DRP || + port->pwr_opmode != TYPEC_PWR_MODE_PD || + !port->tcpc->enable_frs || + /* Sink caps queried */ + port->sink_cap_done || port->negotiated_rev < PD_REV30) + goto unlock; + + /* Send when the state machine is idle */ + if (port->state != SNK_READY || port->vdm_sm_running || + port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + goto resched; + + port->upcoming_state = GET_SINK_CAP; + ret = tcpm_ams_start(port, GET_SINK_CAPABILITIES); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + } else { + port->sink_cap_done = true; + goto unlock; + } +resched: + mod_enable_frs_delayed_work(port, GET_SINK_CAP_RETRY_MS); +unlock: + mutex_unlock(&port->lock); +} + +static void tcpm_vdm_discovery_work(struct kthread_work *work) +{ + struct tcpm_port *port = container_of(work, struct tcpm_port, vdm_discovery_work); + enum tcpm_transmit_type tx_sop_type = TCPC_TX_SOP; + struct typec_port *typec = port->typec_port; + struct pd_mode_data *modep, *modep_prime; + u32 msg[2] = { }; + int svdm_version; + + mutex_lock(&port->lock); + + tcpm_log_force(port, "%s state [%s]", __func__, + vdm_discovery_state_strings[port->vdm_discovery_state]); + + /* No need to perform work if Discovery process is complete */ + if (port->vdm_discovery_state == VDM_DISCOVERY_COMPLETE) + goto unlock; + + /* Retry if the port is not idle */ + if (!tcpm_can_send_vdm(port->state) || port->vdm_sm_running) { + mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS); + goto unlock; + } + + modep = &port->mode_data; + modep_prime = &port->mode_data_prime; + + svdm_version = typec_get_negotiated_svdm_version(typec); + + switch (port->vdm_discovery_state) { + /* + * The port has not received a Discover Identity response from the port partner. + * + * 1. The port will send Discover Identity to the partner over SOP in the SRC_READY and + * SNK_READY states if there is an explicit contract + * 2. The port will send Discover Identity to the cable over SOP' in the + * SRC_VDM_IDENTITY_REQUEST state if capable of doing so. + */ + case VDM_DISCOVERY_UNKNOWN: + /* Can't send Discover Identity, VDM discovery is complete */ + if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + goto unlock; + } + + if (port->state == SRC_VDM_IDENTITY_REQUEST) { + tx_sop_type = TCPC_TX_SOP_PRIME; + svdm_version = SVDM_VER_MAX; + } + + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT); + break; + /* + * The port has received a Discover Identity ACK from the port partner. + * + * 1. The port will send Discover Identity to the cable over SOP' in the SRC_READY and + * SNK_READY states if it did not previously discover the cable but is capable of doing + * so. + * 2. The port will send Discover SVIDs to the partner over SOP in the SRC_READY and + * SNK_READY states otherwise. + */ + case VDM_DISCOVERY_PARTNER_IDENT: + if (tcpm_can_communicate_sop_prime(port) && !port->cable) { + tx_sop_type = TCPC_TX_SOP_PRIME; + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT); + } else { + if (tcpm_can_communicate_sop_prime(port)) + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); + } + break; + /* + * The port has received a Discover Identity ACK from the cable. + * + * 1. The port will send Discover SVIDs to the partner over SOP. + */ + case VDM_DISCOVERY_CABLE_IDENT: + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); + break; + /* + * The port has received a Discover SVIDs ACK from the partner or the last SVIDs supported + * by the partner. + * + * 1. The port will send Discover Modes for the first SVID over SOP if the partner supports + * modal operation and valid SVIDs were registered. + * 2. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise. + */ + case VDM_DISCOVERY_PARTNER_SVIDS: + if (modep->nsvids && supports_modal(port)) { + msg[0] = VDO(modep->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); + } else { + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + goto unlock; + } + break; + /* + * The port has received a Discover Modes ACK from the partner for any mode. + * + * 1. The port will send Discover Modes for the next SVID that has not been discovered to + * the port partner over SOP. + * 2. The port will send Discover SVIDs over SOP' if the port can communicate over SOP' + * and the cable supports VDMs. + * 3. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise. + */ + case VDM_DISCOVERY_PARTNER_MODES: + /* Not all modes have been discovered yet */ + if (modep->svid_index < modep->nsvids) { + msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version, + CMD_DISCOVER_MODES); + } else if (tcpm_can_communicate_sop_prime(port) && tcpm_cable_vdm_supported(port)) { + tx_sop_type = TCPC_TX_SOP_PRIME; + svdm_version = typec_get_cable_svdm_version(typec); + msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); + } else { + tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + goto unlock; + } + break; + /* + * The port has received a Discover SVIDs ACK from the cable over SOP'. + * + * 1. The port will send Discover Modes for the first SVID over SOP'. + */ + case VDM_DISCOVERY_CABLE_SVIDS: + if (modep_prime->nsvids) { + tx_sop_type = TCPC_TX_SOP_PRIME; + svdm_version = typec_get_cable_svdm_version(typec); + msg[0] = VDO(modep_prime->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); + } else { + goto unlock; + } + break; + /* + * The port has received a Discover Modes ACK from the cable for any mode. + * + * 1. The port will send Discover Modes for the next SVID that has not been discovered to + * the cable over SOP'. + */ + case VDM_DISCOVERY_CABLE_MODES: + if (modep_prime->svid_index < modep_prime->nsvids) { + tx_sop_type = TCPC_TX_SOP_PRIME; + svdm_version = typec_get_cable_svdm_version(typec); + msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version, + CMD_DISCOVER_MODES); + } else { + goto unlock; + } + break; + default: + goto unlock; + } + + if (svdm_version >= 0) + tcpm_queue_vdm(port, msg[0], &msg[1], 0, tx_sop_type); + +unlock: + mutex_unlock(&port->lock); +} + +static int tcpm_dr_set(struct typec_port *p, enum typec_data_role data) +{ + struct tcpm_port *port = typec_get_drvdata(p); + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (port->typec_caps.data != TYPEC_PORT_DRD) { + ret = -EINVAL; + goto port_unlock; + } + if (port->state != SRC_READY && port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (port->data_role == data) { + ret = 0; + goto port_unlock; + } + + /* + * XXX + * 6.3.9: If an alternate mode is active, a request to swap + * alternate modes shall trigger a port reset. + * Reject data role swap request in this case. + */ + + if (!port->pd_capable) { + /* + * If the partner is not PD capable, reset the port to + * trigger a role change. This can only work if a preferred + * role is configured, and if it matches the requested role. + */ + if (port->try_role == TYPEC_NO_PREFERRED_ROLE || + port->try_role == port->pwr_role) { + ret = -EINVAL; + goto port_unlock; + } + port->non_pd_role_swap = true; + tcpm_set_state(port, PORT_RESET, 0); + } else { + port->upcoming_state = DR_SWAP_SEND; + ret = tcpm_ams_start(port, DATA_ROLE_SWAP); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto port_unlock; + } + } + + port->swap_status = 0; + port->swap_pending = true; + reinit_completion(&port->swap_complete); + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->swap_complete, + msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->swap_status; + + port->non_pd_role_swap = false; + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + return ret; +} + +static int tcpm_pr_set(struct typec_port *p, enum typec_role role) +{ + struct tcpm_port *port = typec_get_drvdata(p); + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (port->port_type != TYPEC_PORT_DRP) { + ret = -EINVAL; + goto port_unlock; + } + if (port->state != SRC_READY && port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (role == port->pwr_role) { + ret = 0; + goto port_unlock; + } + + port->upcoming_state = PR_SWAP_SEND; + ret = tcpm_ams_start(port, POWER_ROLE_SWAP); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto port_unlock; + } + + port->swap_status = 0; + port->swap_pending = true; + reinit_completion(&port->swap_complete); + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->swap_complete, + msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->swap_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + return ret; +} + +static int tcpm_vconn_set(struct typec_port *p, enum typec_role role) +{ + struct tcpm_port *port = typec_get_drvdata(p); + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (port->state != SRC_READY && port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (role == port->vconn_role) { + ret = 0; + goto port_unlock; + } + + port->upcoming_state = VCONN_SWAP_SEND; + ret = tcpm_ams_start(port, VCONN_SWAP); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto port_unlock; + } + + port->swap_status = 0; + port->swap_pending = true; + reinit_completion(&port->swap_complete); + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->swap_complete, + msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->swap_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + return ret; +} + +static int tcpm_try_role(struct typec_port *p, int role) +{ + struct tcpm_port *port = typec_get_drvdata(p); + struct tcpc_dev *tcpc = port->tcpc; + int ret = 0; + + mutex_lock(&port->lock); + if (tcpc->try_role) + ret = tcpc->try_role(tcpc, role); + if (!ret) + port->try_role = role; + port->try_src_count = 0; + port->try_snk_count = 0; + mutex_unlock(&port->lock); + + return ret; +} + +static int tcpm_aug_set_op_curr(struct tcpm_port *port, u16 req_op_curr_ma) +{ + unsigned int target_mw; + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (port->pps_data.active) { + req_op_curr_ma = req_op_curr_ma - + (req_op_curr_ma % RDO_PROG_CURR_MA_STEP); + if (req_op_curr_ma > port->pps_data.max_curr) { + ret = -EINVAL; + goto port_unlock; + } + target_mw = (req_op_curr_ma * port->supply_voltage) / 1000; + if (target_mw < port->operating_snk_mw) { + ret = -EINVAL; + goto port_unlock; + } + } else if (!port->spr_avs_data.active) { + ret = -EOPNOTSUPP; + goto port_unlock; + } + + if (port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (port->pps_data.active) + port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES; + else + port->upcoming_state = SNK_NEGOTIATE_SPR_AVS_CAPABILITIES; + + ret = tcpm_ams_start(port, POWER_NEGOTIATION); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto port_unlock; + } + + reinit_completion(&port->aug_supply_req_complete); + if (port->pps_data.active) + port->pps_data.req_op_curr = req_op_curr_ma; + else + port->spr_avs_data.req_op_curr_ma = req_op_curr_ma; + port->aug_supply_req_status = 0; + port->aug_supply_req_pending = true; + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->aug_supply_req_complete, + msecs_to_jiffies(PD_AUG_PSY_CTRL_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->aug_supply_req_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + + return ret; +} + +static int tcpm_aug_set_out_volt(struct tcpm_port *port, u16 req_out_volt_mv) +{ + unsigned int target_mw; + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (port->pps_data.active) { + req_out_volt_mv = req_out_volt_mv - (req_out_volt_mv % + RDO_PROG_VOLT_MV_STEP); + /* Round down output voltage to align with PPS valid steps */ + target_mw = (port->current_limit * req_out_volt_mv) / 1000; + if (target_mw < port->operating_snk_mw) { + ret = -EINVAL; + goto port_unlock; + } + } else if (!port->spr_avs_data.active) { + ret = -EOPNOTSUPP; + goto port_unlock; + } + + if (port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (port->pps_data.active) + port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES; + else + port->upcoming_state = SNK_NEGOTIATE_SPR_AVS_CAPABILITIES; + + ret = tcpm_ams_start(port, POWER_NEGOTIATION); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto port_unlock; + } + + reinit_completion(&port->aug_supply_req_complete); + if (port->pps_data.active) + port->pps_data.req_out_volt = req_out_volt_mv; + else + port->spr_avs_data.req_out_volt_mv = req_out_volt_mv; + port->aug_supply_req_status = 0; + port->aug_supply_req_pending = true; + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->aug_supply_req_complete, + msecs_to_jiffies(PD_AUG_PSY_CTRL_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->aug_supply_req_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + + return ret; +} + +static int tcpm_pps_activate(struct tcpm_port *port, bool activate) +{ + int ret = 0; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (!port->pps_data.supported) { + ret = -EOPNOTSUPP; + goto port_unlock; + } + + /* Trying to deactivate PPS when already deactivated so just bail */ + if (!port->pps_data.active && !activate) + goto port_unlock; + + if (port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (activate) + port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES; + else + port->upcoming_state = SNK_NEGOTIATE_CAPABILITIES; + ret = tcpm_ams_start(port, POWER_NEGOTIATION); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto port_unlock; + } + + reinit_completion(&port->aug_supply_req_complete); + port->aug_supply_req_status = 0; + port->aug_supply_req_pending = true; + + /* Trigger PPS request or move back to standard PDO contract */ + if (activate) { + port->pps_data.req_out_volt = port->supply_voltage; + port->pps_data.req_op_curr = port->current_limit; + } + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->aug_supply_req_complete, + msecs_to_jiffies(PD_AUG_PSY_CTRL_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->aug_supply_req_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + + return ret; +} + +static int tcpm_spr_avs_activate(struct tcpm_port *port, bool activate) +{ + int ret = 0; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (port->spr_avs_data.port_snk_status == SPR_AVS_NOT_SUPPORTED || + port->spr_avs_data.port_partner_src_status == SPR_AVS_NOT_SUPPORTED) { + tcpm_log(port, "SPR_AVS not supported"); + ret = -EOPNOTSUPP; + goto port_unlock; + } + + /* Trying to deactivate SPR AVS when already deactivated so just bail */ + if (!port->spr_avs_data.active && !activate) + goto port_unlock; + + if (port->state != SNK_READY) { + tcpm_log(port, + "SPR_AVS cannot be activated. Port not in SNK_READY"); + ret = -EAGAIN; + goto port_unlock; + } + + if (activate) + port->upcoming_state = SNK_NEGOTIATE_SPR_AVS_CAPABILITIES; + else + port->upcoming_state = SNK_NEGOTIATE_CAPABILITIES; + ret = tcpm_ams_start(port, POWER_NEGOTIATION); + if (ret == -EAGAIN) { + tcpm_log(port, "SPR_AVS cannot be %s. AMS start failed", + activate ? "activated" : "deactivated"); + port->upcoming_state = INVALID_STATE; + goto port_unlock; + } + + reinit_completion(&port->aug_supply_req_complete); + port->aug_supply_req_status = 0; + port->aug_supply_req_pending = true; + + /* Trigger AVS request or move back to standard PDO contract */ + if (activate) { + port->spr_avs_data.req_out_volt_mv = port->supply_voltage; + port->spr_avs_data.req_op_curr_ma = port->current_limit; + } + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->aug_supply_req_complete, + msecs_to_jiffies(PD_AUG_PSY_CTRL_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->aug_supply_req_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + + return ret; +} + +static void tcpm_init(struct tcpm_port *port) +{ + enum typec_cc_status cc1, cc2; + + port->tcpc->init(port->tcpc); + + tcpm_reset_port(port); + + /* + * XXX + * Should possibly wait for VBUS to settle if it was enabled locally + * since tcpm_reset_port() will disable VBUS. + */ + port->vbus_present = port->tcpc->get_vbus(port->tcpc); + if (port->vbus_present) + port->vbus_never_low = true; + + /* + * 1. When vbus_present is true, voltage on VBUS is already at VSAFE5V. + * So implicitly vbus_vsafe0v = false. + * + * 2. When vbus_present is false and TCPC does NOT support querying + * vsafe0v status, then, it's best to assume vbus is at VSAFE0V i.e. + * vbus_vsafe0v is true. + * + * 3. When vbus_present is false and TCPC does support querying vsafe0v, + * then, query tcpc for vsafe0v status. + */ + if (port->vbus_present) + port->vbus_vsafe0v = false; + else if (!port->tcpc->is_vbus_vsafe0v) + port->vbus_vsafe0v = true; + else + port->vbus_vsafe0v = port->tcpc->is_vbus_vsafe0v(port->tcpc); + + tcpm_set_state(port, tcpm_default_state(port), 0); + + if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0) + _tcpm_cc_change(port, cc1, cc2); + + /* + * Some adapters need a clean slate at startup, and won't recover + * otherwise. So do not try to be fancy and force a clean disconnect. + */ + tcpm_set_state(port, PORT_RESET, 0); +} + +static int tcpm_port_type_set(struct typec_port *p, enum typec_port_type type) +{ + struct tcpm_port *port = typec_get_drvdata(p); + + mutex_lock(&port->lock); + if (type == port->port_type) + goto port_unlock; + + port->port_type = type; + + if (!port->connected) { + tcpm_set_state(port, PORT_RESET, 0); + } else if (type == TYPEC_PORT_SNK) { + if (!(port->pwr_role == TYPEC_SINK && + port->data_role == TYPEC_DEVICE)) + tcpm_set_state(port, PORT_RESET, 0); + } else if (type == TYPEC_PORT_SRC) { + if (!(port->pwr_role == TYPEC_SOURCE && + port->data_role == TYPEC_HOST)) + tcpm_set_state(port, PORT_RESET, 0); + } + +port_unlock: + mutex_unlock(&port->lock); + return 0; +} + +static struct pd_data *tcpm_find_pd_data(struct tcpm_port *port, struct usb_power_delivery *pd) +{ + int i; + + for (i = 0; port->pd_list[i]; i++) { + if (port->pd_list[i]->pd == pd) + return port->pd_list[i]; + } + + return ERR_PTR(-ENODATA); +} + +static struct usb_power_delivery **tcpm_pd_get(struct typec_port *p) +{ + struct tcpm_port *port = typec_get_drvdata(p); + + return port->pds; +} + +static int tcpm_pd_set(struct typec_port *p, struct usb_power_delivery *pd) +{ + struct tcpm_port *port = typec_get_drvdata(p); + struct pd_data *data; + int i, ret = 0; + + mutex_lock(&port->lock); + + if (port->selected_pd == pd) + goto unlock; + + data = tcpm_find_pd_data(port, pd); + if (IS_ERR(data)) { + ret = PTR_ERR(data); + goto unlock; + } + + if (data->sink_desc.pdo[0]) { + for (i = 0; i < PDO_MAX_OBJECTS && data->sink_desc.pdo[i]; i++) + port->snk_pdo[i] = data->sink_desc.pdo[i]; + port->nr_snk_pdo = i; + port->operating_snk_mw = data->operating_snk_mw; + } + + if (data->source_desc.pdo[0]) { + for (i = 0; i < PDO_MAX_OBJECTS && data->source_desc.pdo[i]; i++) + port->src_pdo[i] = data->source_desc.pdo[i]; + port->nr_src_pdo = i; + } + + switch (port->state) { + case SRC_UNATTACHED: + case SRC_ATTACH_WAIT: + case SRC_TRYWAIT: + tcpm_set_cc(port, tcpm_rp_cc(port)); + break; + case SRC_SEND_CAPABILITIES: + case SRC_SEND_CAPABILITIES_TIMEOUT: + case SRC_NEGOTIATE_CAPABILITIES: + case SRC_READY: + case SRC_WAIT_NEW_CAPABILITIES: + port->caps_count = 0; + port->upcoming_state = SRC_SEND_CAPABILITIES; + ret = tcpm_ams_start(port, POWER_NEGOTIATION); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto unlock; + } + break; + case SNK_NEGOTIATE_CAPABILITIES: + case SNK_NEGOTIATE_PPS_CAPABILITIES: + case SNK_NEGOTIATE_SPR_AVS_CAPABILITIES: + case SNK_READY: + case SNK_TRANSITION_SINK: + case SNK_TRANSITION_SINK_VBUS: + if (port->pps_data.active) { + port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES; + } else if (port->pd_capable) { + port->upcoming_state = SNK_NEGOTIATE_CAPABILITIES; + if (port->spr_avs_data.active) { + /* + * De-activate AVS and fallback to PD to + * re-evaluate whether AVS is supported in the + * current sink cap set. + */ + port->spr_avs_data.active = false; + port->spr_avs_data.port_snk_status = SPR_AVS_UNKNOWN; + } + } else { + break; + } + port->update_sink_caps = true; + + ret = tcpm_ams_start(port, POWER_NEGOTIATION); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto unlock; + } + break; + default: + break; + } + + port->port_source_caps = data->source_cap; + port->port_sink_caps = data->sink_cap; + typec_port_set_usb_power_delivery(p, NULL); + port->selected_pd = pd; + typec_port_set_usb_power_delivery(p, port->selected_pd); +unlock: + mutex_unlock(&port->lock); + return ret; +} + +static const struct typec_operations tcpm_ops = { + .try_role = tcpm_try_role, + .dr_set = tcpm_dr_set, + .pr_set = tcpm_pr_set, + .vconn_set = tcpm_vconn_set, + .port_type_set = tcpm_port_type_set, + .pd_get = tcpm_pd_get, + .pd_set = tcpm_pd_set +}; + +void tcpm_tcpc_reset(struct tcpm_port *port) +{ + mutex_lock(&port->lock); + /* XXX: Maintain PD connection if possible? */ + tcpm_init(port); + mutex_unlock(&port->lock); +} +EXPORT_SYMBOL_GPL(tcpm_tcpc_reset); + +static void tcpm_port_unregister_pd(struct tcpm_port *port) +{ + int i; + + port->port_sink_caps = NULL; + port->port_source_caps = NULL; + for (i = 0; i < port->pd_count; i++) { + usb_power_delivery_unregister_capabilities(port->pd_list[i]->sink_cap); + usb_power_delivery_unregister_capabilities(port->pd_list[i]->source_cap); + devm_kfree(port->dev, port->pd_list[i]); + port->pd_list[i] = NULL; + usb_power_delivery_unregister(port->pds[i]); + port->pds[i] = NULL; + } +} + +static int tcpm_port_register_pd(struct tcpm_port *port) +{ + u16 pd_revision = port->typec_caps.pd_revision; + u16 pd_version = port->pd_rev.ver_major << 8 | port->pd_rev.ver_minor; + struct usb_power_delivery_desc desc = { pd_revision, pd_version }; + struct usb_power_delivery_capabilities *cap; + int ret, i; + + if (!port->nr_src_pdo && !port->nr_snk_pdo) + return 0; + + for (i = 0; i < port->pd_count; i++) { + port->pds[i] = usb_power_delivery_register(port->dev, &desc); + if (IS_ERR(port->pds[i])) { + ret = PTR_ERR(port->pds[i]); + goto err_unregister; + } + port->pd_list[i]->pd = port->pds[i]; + + if (port->pd_list[i]->source_desc.pdo[0]) { + cap = usb_power_delivery_register_capabilities(port->pds[i], + &port->pd_list[i]->source_desc); + if (IS_ERR(cap)) { + ret = PTR_ERR(cap); + goto err_unregister; + } + port->pd_list[i]->source_cap = cap; + } + + if (port->pd_list[i]->sink_desc.pdo[0]) { + cap = usb_power_delivery_register_capabilities(port->pds[i], + &port->pd_list[i]->sink_desc); + if (IS_ERR(cap)) { + ret = PTR_ERR(cap); + goto err_unregister; + } + port->pd_list[i]->sink_cap = cap; + } + } + + port->port_source_caps = port->pd_list[0]->source_cap; + port->port_sink_caps = port->pd_list[0]->sink_cap; + port->selected_pd = port->pds[0]; + return 0; + +err_unregister: + tcpm_port_unregister_pd(port); + + return ret; +} + +static void tcpm_fw_get_timings(struct tcpm_port *port, struct fwnode_handle *fwnode) +{ + int ret; + u32 val; + + ret = fwnode_property_read_u32(fwnode, "sink-wait-cap-time-ms", &val); + if (!ret) + port->timings.sink_wait_cap_time = val; + else + port->timings.sink_wait_cap_time = PD_T_SINK_WAIT_CAP; + + ret = fwnode_property_read_u32(fwnode, "ps-source-off-time-ms", &val); + if (!ret) + port->timings.ps_src_off_time = val; + else + port->timings.ps_src_off_time = PD_T_PS_SOURCE_OFF; + + ret = fwnode_property_read_u32(fwnode, "cc-debounce-time-ms", &val); + if (!ret) + port->timings.cc_debounce_time = val; + else + port->timings.cc_debounce_time = PD_T_CC_DEBOUNCE; + + ret = fwnode_property_read_u32(fwnode, "sink-bc12-completion-time-ms", &val); + if (!ret) + port->timings.snk_bc12_cmpletion_time = val; +} + +static void tcpm_fw_get_pd_ident(struct tcpm_port *port) +{ + struct pd_identifier *pd_ident = &port->pd_ident; + u32 *vdo; + + /* First 3 vdo values contain info regarding USB PID, VID & XID */ + if (port->nr_snk_vdo >= 3) + vdo = port->snk_vdo; + else if (port->nr_snk_vdo_v1 >= 3) + vdo = port->snk_vdo_v1; + else + return; + + pd_ident->vid = PD_IDH_VID(vdo[0]); + pd_ident->pid = PD_PRODUCT_PID(vdo[2]); + pd_ident->xid = PD_CSTAT_XID(vdo[1]); + tcpm_log(port, "vid:%#x pid:%#x xid:%#x", + pd_ident->vid, pd_ident->pid, pd_ident->xid); +} + +static void tcpm_parse_snk_pdos(struct tcpm_port *port) +{ + struct sink_caps_ext_data *caps = &port->sink_caps_ext; + u32 max_mv, max_ma; + u8 avs_tier1_pdp, avs_tier2_pdp; + int i, pdo_itr; + u32 *snk_pdos; + + for (i = 0; i < port->pd_count; ++i) { + snk_pdos = port->pd_list[i]->sink_desc.pdo; + for (pdo_itr = 0; pdo_itr < PDO_MAX_OBJECTS && snk_pdos[pdo_itr]; + ++pdo_itr) { + u32 pdo = snk_pdos[pdo_itr]; + u8 curr_snk_pdp = 0; + + switch (pdo_type(pdo)) { + case PDO_TYPE_FIXED: + max_mv = pdo_fixed_voltage(pdo); + max_ma = pdo_fixed_current(pdo); + curr_snk_pdp = UW_TO_W(max_mv * max_ma); + break; + case PDO_TYPE_BATT: + curr_snk_pdp = UW_TO_W(pdo_max_power(pdo)); + break; + case PDO_TYPE_VAR: + max_mv = pdo_max_voltage(pdo); + max_ma = pdo_max_current(pdo); + curr_snk_pdp = UW_TO_W(max_mv * max_ma); + break; + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) { + max_mv = pdo_pps_apdo_max_voltage(pdo); + max_ma = pdo_pps_apdo_max_current(pdo); + curr_snk_pdp = UW_TO_W(max_mv * max_ma); + caps->modes |= SINK_MODE_PPS; + } else if (pdo_apdo_type(pdo) == + APDO_TYPE_SPR_AVS) { + avs_tier1_pdp = UW_TO_W(SPR_AVS_TIER1_MAX_VOLT_MV + * pdo_spr_avs_apdo_9v_to_15v_max_current_ma(pdo)); + avs_tier2_pdp = UW_TO_W(SPR_AVS_TIER2_MAX_VOLT_MV + * pdo_spr_avs_apdo_15v_to_20v_max_current_ma(pdo)); + curr_snk_pdp = max(avs_tier1_pdp, avs_tier2_pdp); + caps->modes |= SINK_MODE_AVS; + } + break; + default: + tcpm_log(port, "Invalid source PDO type, ignoring"); + continue; + } + + caps->spr_max_pdp = max(caps->spr_max_pdp, + curr_snk_pdp); + } + } +} + +static void tcpm_fw_get_sink_caps_ext(struct tcpm_port *port, + struct fwnode_handle *fwnode) +{ + struct sink_caps_ext_data *caps = &port->sink_caps_ext; + int ret; + u32 val; + + /* + * Load step represents the change in current per usec that a given + * source can tolerate while maintaining Vbus within the vSrcValid + * range. For a sink this represents the "preferred" load-step value. It + * can only have 2 values (150 mA/usec or 500 mA/usec) with 150 mA/usec + * being the default. + */ + ret = fwnode_property_read_u32(fwnode, "sink-load-step", &val); + if (!ret) + caps->load_step = val == 500 ? 1 : 0; + + fwnode_property_read_u16(fwnode, "sink-load-characteristics", + &caps->load_char); + fwnode_property_read_u8(fwnode, "sink-compliance", &caps->compliance); + caps->modes = SINK_MODE_VBUS; + + /* + * As per "6.5.13.14" SPR Sink Operational PDP definition, for battery + * powered devices, this value will correspond to the PDP of the + * charging adapter either shipped or recommended for use with it. For + * batteryless sink devices SPR Operational PDP indicates the power + * required to operate all the device's functional modes. Hence, this + * value may be considered equal to port's operating_snk_mw. As + * operating_sink_mw can change as per the pd set used thus, OP PDP + * is determined when populating Sink Caps Extended Data Block. + */ + if (port->self_powered) { + fwnode_property_read_u32(fwnode, "charging-adapter-pdp-milliwatt", + &val); + caps->spr_op_pdp = (u8)(val / 1000); + caps->modes |= SINK_MODE_BATT; + } + + tcpm_parse_snk_pdos(port); + tcpm_log(port, + "load-step:%#x load-char:%#x compl:%#x op-pdp:%#x max-pdp:%#x", + caps->load_step, caps->load_char, caps->compliance, + caps->spr_op_pdp, caps->spr_max_pdp); +} + +static int tcpm_fw_get_caps(struct tcpm_port *port, struct fwnode_handle *fwnode) +{ + struct fwnode_handle *capabilities, *caps = NULL; + unsigned int nr_src_pdo, nr_snk_pdo; + const char *opmode_str; + u32 *src_pdo, *snk_pdo; + u32 uw, frs_current; + int ret = 0, i; + int mode; + + if (!fwnode) + return -EINVAL; + + /* + * This fwnode has a "compatible" property, but is never populated as a + * struct device. Instead we simply parse it to read the properties. + * This it breaks fw_devlink=on. To maintain backward compatibility + * with existing DT files, we work around this by deleting any + * fwnode_links to/from this fwnode. + */ + fw_devlink_purge_absent_suppliers(fwnode); + + ret = typec_get_fw_cap(&port->typec_caps, fwnode); + if (ret < 0) + return ret; + + mode = 0; + + if (fwnode_property_read_bool(fwnode, "accessory-mode-audio")) + port->typec_caps.accessory[mode++] = TYPEC_ACCESSORY_AUDIO; + + if (fwnode_property_read_bool(fwnode, "accessory-mode-debug")) + port->typec_caps.accessory[mode++] = TYPEC_ACCESSORY_DEBUG; + + port->port_type = port->typec_caps.type; + port->pd_supported = !fwnode_property_read_bool(fwnode, "pd-disable"); + port->slow_charger_loop = fwnode_property_read_bool(fwnode, "slow-charger-loop"); + port->self_powered = fwnode_property_read_bool(fwnode, "self-powered"); + + if (!port->pd_supported) { + ret = fwnode_property_read_string(fwnode, "typec-power-opmode", &opmode_str); + if (ret) + return ret; + ret = typec_find_pwr_opmode(opmode_str); + if (ret < 0) + return ret; + port->src_rp = tcpm_pwr_opmode_to_rp(ret); + return 0; + } + + /* The following code are applicable to pd-capable ports, i.e. pd_supported is true. */ + + /* FRS can only be supported by DRP ports */ + if (port->port_type == TYPEC_PORT_DRP) { + ret = fwnode_property_read_u32(fwnode, "new-source-frs-typec-current", + &frs_current); + if (!ret && frs_current <= FRS_5V_3A) + port->new_source_frs_current = frs_current; + + if (ret) + ret = 0; + } + + /* For the backward compatibility, "capabilities" node is optional. */ + capabilities = fwnode_get_named_child_node(fwnode, "capabilities"); + if (!capabilities) { + port->pd_count = 1; + } else { + port->pd_count = fwnode_get_child_node_count(capabilities); + if (!port->pd_count) { + ret = -ENODATA; + goto put_capabilities; + } + } + + port->pds = devm_kcalloc(port->dev, port->pd_count, sizeof(struct usb_power_delivery *), + GFP_KERNEL); + if (!port->pds) { + ret = -ENOMEM; + goto put_capabilities; + } + + port->pd_list = devm_kcalloc(port->dev, port->pd_count, sizeof(struct pd_data *), + GFP_KERNEL); + if (!port->pd_list) { + ret = -ENOMEM; + goto put_capabilities; + } + + for (i = 0; i < port->pd_count; i++) { + port->pd_list[i] = devm_kzalloc(port->dev, sizeof(struct pd_data), GFP_KERNEL); + if (!port->pd_list[i]) { + ret = -ENOMEM; + goto put_capabilities; + } + + src_pdo = port->pd_list[i]->source_desc.pdo; + port->pd_list[i]->source_desc.role = TYPEC_SOURCE; + snk_pdo = port->pd_list[i]->sink_desc.pdo; + port->pd_list[i]->sink_desc.role = TYPEC_SINK; + + /* If "capabilities" is NULL, fall back to single pd cap population. */ + if (!capabilities) + caps = fwnode; + else + caps = fwnode_get_next_child_node(capabilities, caps); + + if (port->port_type != TYPEC_PORT_SNK) { + ret = fwnode_property_count_u32(caps, "source-pdos"); + if (ret == 0) { + ret = -EINVAL; + goto put_caps; + } + if (ret < 0) + goto put_caps; + + nr_src_pdo = min(ret, PDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(caps, "source-pdos", src_pdo, + nr_src_pdo); + if (ret) + goto put_caps; + + ret = tcpm_validate_caps(port, src_pdo, nr_src_pdo); + if (ret) + goto put_caps; + + if (i == 0) { + port->nr_src_pdo = nr_src_pdo; + memcpy_and_pad(port->src_pdo, sizeof(u32) * PDO_MAX_OBJECTS, + port->pd_list[0]->source_desc.pdo, + sizeof(u32) * nr_src_pdo, + 0); + } + } + + if (port->port_type != TYPEC_PORT_SRC) { + ret = fwnode_property_count_u32(caps, "sink-pdos"); + if (ret == 0) { + ret = -EINVAL; + goto put_caps; + } + + if (ret < 0) + goto put_caps; + + nr_snk_pdo = min(ret, PDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(caps, "sink-pdos", snk_pdo, + nr_snk_pdo); + if (ret) + goto put_caps; + + ret = tcpm_validate_caps(port, snk_pdo, nr_snk_pdo); + if (ret) + goto put_caps; + + if (fwnode_property_read_u32(caps, "op-sink-microwatt", &uw) < 0) { + ret = -EINVAL; + goto put_caps; + } + + port->pd_list[i]->operating_snk_mw = uw / 1000; + + if (i == 0) { + port->nr_snk_pdo = nr_snk_pdo; + memcpy_and_pad(port->snk_pdo, sizeof(u32) * PDO_MAX_OBJECTS, + port->pd_list[0]->sink_desc.pdo, + sizeof(u32) * nr_snk_pdo, + 0); + port->operating_snk_mw = port->pd_list[0]->operating_snk_mw; + } + } + } + + if (port->port_type != TYPEC_PORT_SRC) + tcpm_fw_get_sink_caps_ext(port, fwnode); + +put_caps: + if (caps != fwnode) + fwnode_handle_put(caps); +put_capabilities: + fwnode_handle_put(capabilities); + return ret; +} + +static int tcpm_fw_get_snk_vdos(struct tcpm_port *port, struct fwnode_handle *fwnode) +{ + int ret; + + /* sink-vdos is optional */ + ret = fwnode_property_count_u32(fwnode, "sink-vdos"); + if (ret < 0) + return 0; + + port->nr_snk_vdo = min(ret, VDO_MAX_OBJECTS); + if (port->nr_snk_vdo) { + ret = fwnode_property_read_u32_array(fwnode, "sink-vdos", + port->snk_vdo, + port->nr_snk_vdo); + if (ret < 0) + return ret; + } + + /* If sink-vdos is found, sink-vdos-v1 is expected for backward compatibility. */ + if (port->nr_snk_vdo) { + ret = fwnode_property_count_u32(fwnode, "sink-vdos-v1"); + if (ret < 0) + return ret; + else if (ret == 0) + return -ENODATA; + + port->nr_snk_vdo_v1 = min(ret, VDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(fwnode, "sink-vdos-v1", + port->snk_vdo_v1, + port->nr_snk_vdo_v1); + if (ret < 0) + return ret; + } + + tcpm_fw_get_pd_ident(port); + + return 0; +} + +static void tcpm_fw_get_pd_revision(struct tcpm_port *port, struct fwnode_handle *fwnode) +{ + int ret; + u8 val[4]; + + ret = fwnode_property_count_u8(fwnode, "pd-revision"); + if (!ret || ret != 4) { + tcpm_log(port, "Unable to find pd-revision property or incorrect array size"); + return; + } + + ret = fwnode_property_read_u8_array(fwnode, "pd-revision", val, 4); + if (ret) { + tcpm_log(port, "Failed to parse pd-revision, ret:(%d)", ret); + return; + } + + port->pd_rev.rev_major = val[0]; + port->pd_rev.rev_minor = val[1]; + port->pd_rev.ver_major = val[2]; + port->pd_rev.ver_minor = val[3]; +} + +/* Power Supply access to expose source power information */ +enum tcpm_psy_online_states { + TCPM_PSY_OFFLINE = 0, + TCPM_PSY_FIXED_ONLINE, + TCPM_PSY_PPS_ONLINE, + TCPM_PSY_SPR_AVS_ONLINE, +}; + +static enum power_supply_property tcpm_psy_props[] = { + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static int tcpm_psy_get_online(struct tcpm_port *port, + union power_supply_propval *val) +{ + if (port->vbus_charge) { + if (port->pps_data.active) + val->intval = TCPM_PSY_PPS_ONLINE; + else if (port->spr_avs_data.active) + val->intval = TCPM_PSY_SPR_AVS_ONLINE; + else + val->intval = TCPM_PSY_FIXED_ONLINE; + } else { + val->intval = TCPM_PSY_OFFLINE; + } + + return 0; +} + +static int tcpm_psy_get_voltage_min(struct tcpm_port *port, + union power_supply_propval *val) +{ + if (port->pps_data.active) + val->intval = port->pps_data.min_volt * 1000; + else if (port->spr_avs_data.active) + val->intval = SPR_AVS_TIER1_MIN_VOLT_MV * 1000; + else + val->intval = port->supply_voltage * 1000; + + return 0; +} + +static int tcpm_psy_get_voltage_max(struct tcpm_port *port, + union power_supply_propval *val) +{ + if (port->pps_data.active) + val->intval = port->pps_data.max_volt * 1000; + else if (port->spr_avs_data.active) + val->intval = port->spr_avs_data.max_out_volt_mv * 1000; + else + val->intval = port->supply_voltage * 1000; + + return 0; +} + +static int tcpm_psy_get_voltage_now(struct tcpm_port *port, + union power_supply_propval *val) +{ + val->intval = port->supply_voltage * 1000; + + return 0; +} + +static int tcpm_psy_get_current_max(struct tcpm_port *port, + union power_supply_propval *val) +{ + if (port->pps_data.active) + val->intval = port->pps_data.max_curr * 1000; + else if (port->spr_avs_data.active) + val->intval = port->spr_avs_data.max_current_ma * 1000; + else + val->intval = port->current_limit * 1000; + + return 0; +} + +static int tcpm_psy_get_current_now(struct tcpm_port *port, + union power_supply_propval *val) +{ + val->intval = port->current_limit * 1000; + + return 0; +} + +static int tcpm_psy_get_input_power_limit(struct tcpm_port *port, + union power_supply_propval *val) +{ + unsigned int src_mv, src_ma, max_src_uw = 0; + unsigned int i, tmp; + + for (i = 0; i < port->nr_source_caps; i++) { + u32 pdo = port->source_caps[i]; + + if (pdo_type(pdo) == PDO_TYPE_FIXED) { + src_mv = pdo_fixed_voltage(pdo); + src_ma = pdo_max_current(pdo); + tmp = src_mv * src_ma; + max_src_uw = max(tmp, max_src_uw); + } + } + + val->intval = max_src_uw; + return 0; +} + +static int tcpm_psy_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct tcpm_port *port = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = port->usb_type; + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = tcpm_psy_get_online(port, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + ret = tcpm_psy_get_voltage_min(port, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + ret = tcpm_psy_get_voltage_max(port, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = tcpm_psy_get_voltage_now(port, val); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + ret = tcpm_psy_get_current_max(port, val); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = tcpm_psy_get_current_now(port, val); + break; + case POWER_SUPPLY_PROP_INPUT_POWER_LIMIT: + tcpm_psy_get_input_power_limit(port, val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int tcpm_disable_pps_avs(struct tcpm_port *port) +{ + int ret = 0; + + if (port->pps_data.active) + ret = tcpm_pps_activate(port, false); + else if (port->spr_avs_data.active) + ret = tcpm_spr_avs_activate(port, false); + + return ret; +} + +static int tcpm_psy_set_online(struct tcpm_port *port, + const union power_supply_propval *val) +{ + int ret = 0; + + switch (val->intval) { + case TCPM_PSY_FIXED_ONLINE: + ret = tcpm_disable_pps_avs(port); + break; + case TCPM_PSY_PPS_ONLINE: + if (port->spr_avs_data.active) + ret = tcpm_spr_avs_activate(port, false); + if (!ret) + ret = tcpm_pps_activate(port, true); + break; + case TCPM_PSY_SPR_AVS_ONLINE: + tcpm_log(port, "request to set AVS online"); + if (port->spr_avs_data.active) + return 0; + ret = tcpm_disable_pps_avs(port); + if (ret) + break; + ret = tcpm_spr_avs_activate(port, true); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int tcpm_psy_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct tcpm_port *port = power_supply_get_drvdata(psy); + int ret; + + /* + * All the properties below are related to USB PD. The check needs to be + * property specific when a non-pd related property is added. + */ + if (!port->pd_supported) + return -EOPNOTSUPP; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = tcpm_psy_set_online(port, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = tcpm_aug_set_out_volt(port, val->intval / 1000); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = tcpm_aug_set_op_curr(port, val->intval / 1000); + break; + default: + ret = -EINVAL; + break; + } + power_supply_changed(port->psy); + return ret; +} + +static int tcpm_psy_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_CURRENT_NOW: + return 1; + default: + return 0; + } +} + +static const char *tcpm_psy_name_prefix = "tcpm-source-psy-"; + +static int devm_tcpm_psy_register(struct tcpm_port *port) +{ + struct power_supply_config psy_cfg = {}; + const char *port_dev_name = dev_name(port->dev); + size_t psy_name_len = strlen(tcpm_psy_name_prefix) + + strlen(port_dev_name) + 1; + char *psy_name; + + psy_cfg.drv_data = port; + psy_cfg.fwnode = dev_fwnode(port->dev); + psy_name = devm_kzalloc(port->dev, psy_name_len, GFP_KERNEL); + if (!psy_name) + return -ENOMEM; + + snprintf(psy_name, psy_name_len, "%s%s", tcpm_psy_name_prefix, + port_dev_name); + port->psy_desc.name = psy_name; + port->psy_desc.type = POWER_SUPPLY_TYPE_USB; + port->psy_desc.usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD) | + BIT(POWER_SUPPLY_USB_TYPE_PD_PPS) | + BIT(POWER_SUPPLY_USB_TYPE_PD_PPS_SPR_AVS) | + BIT(POWER_SUPPLY_USB_TYPE_PD_SPR_AVS); + port->psy_desc.properties = tcpm_psy_props; + port->psy_desc.num_properties = ARRAY_SIZE(tcpm_psy_props); + port->psy_desc.get_property = tcpm_psy_get_prop; + port->psy_desc.set_property = tcpm_psy_set_prop; + port->psy_desc.property_is_writeable = tcpm_psy_prop_writeable; + + port->usb_type = POWER_SUPPLY_USB_TYPE_C; + + port->psy = devm_power_supply_register(port->dev, &port->psy_desc, + &psy_cfg); + + return PTR_ERR_OR_ZERO(port->psy); +} + +static enum hrtimer_restart state_machine_timer_handler(struct hrtimer *timer) +{ + struct tcpm_port *port = container_of(timer, struct tcpm_port, state_machine_timer); + + if (port->registered) + kthread_queue_work(port->wq, &port->state_machine); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart vdm_state_machine_timer_handler(struct hrtimer *timer) +{ + struct tcpm_port *port = container_of(timer, struct tcpm_port, vdm_state_machine_timer); + + if (port->registered) + kthread_queue_work(port->wq, &port->vdm_state_machine); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart enable_frs_timer_handler(struct hrtimer *timer) +{ + struct tcpm_port *port = container_of(timer, struct tcpm_port, enable_frs_timer); + + if (port->registered) + kthread_queue_work(port->wq, &port->enable_frs); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart vdm_discovery_timer_handler(struct hrtimer *timer) +{ + struct tcpm_port *port = container_of(timer, struct tcpm_port, vdm_discovery_timer); + + if (port->registered) + kthread_queue_work(port->wq, &port->vdm_discovery_work); + return HRTIMER_NORESTART; +} + +struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) +{ + struct tcpm_port *port; + int err; + + if (!dev || !tcpc || + !tcpc->get_vbus || !tcpc->set_cc || !tcpc->get_cc || + !tcpc->set_polarity || !tcpc->set_vconn || !tcpc->set_vbus || + !tcpc->set_pd_rx || !tcpc->set_roles || !tcpc->pd_transmit) + return ERR_PTR(-EINVAL); + + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) + return ERR_PTR(-ENOMEM); + + port->dev = dev; + port->tcpc = tcpc; + + mutex_init(&port->lock); + mutex_init(&port->swap_lock); + + port->wq = kthread_run_worker(0, dev_name(dev)); + if (IS_ERR(port->wq)) + return ERR_CAST(port->wq); + sched_set_fifo(port->wq->task); + + kthread_init_work(&port->state_machine, tcpm_state_machine_work); + kthread_init_work(&port->vdm_state_machine, vdm_state_machine_work); + kthread_init_work(&port->event_work, tcpm_pd_event_handler); + kthread_init_work(&port->enable_frs, tcpm_enable_frs_work); + kthread_init_work(&port->vdm_discovery_work, tcpm_vdm_discovery_work); + hrtimer_setup(&port->state_machine_timer, state_machine_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + hrtimer_setup(&port->vdm_state_machine_timer, vdm_state_machine_timer_handler, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hrtimer_setup(&port->enable_frs_timer, enable_frs_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + hrtimer_setup(&port->vdm_discovery_timer, vdm_discovery_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + + spin_lock_init(&port->pd_event_lock); + + init_completion(&port->tx_complete); + init_completion(&port->swap_complete); + init_completion(&port->aug_supply_req_complete); + tcpm_debugfs_init(port); + + err = tcpm_fw_get_caps(port, tcpc->fwnode); + if (err < 0) + goto out_destroy_wq; + err = tcpm_fw_get_snk_vdos(port, tcpc->fwnode); + if (err < 0) + goto out_destroy_wq; + + tcpm_fw_get_timings(port, tcpc->fwnode); + tcpm_fw_get_pd_revision(port, tcpc->fwnode); + + port->try_role = port->typec_caps.prefer_role; + + port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ + + if (port->pd_rev.rev_major) + port->typec_caps.pd_revision = port->pd_rev.rev_major << 8 | + port->pd_rev.rev_minor; + else + port->typec_caps.pd_revision = 0x0300; /* USB-PD spec release 3.0 */ + + port->typec_caps.svdm_version = SVDM_VER_2_0; + port->typec_caps.driver_data = port; + port->typec_caps.ops = &tcpm_ops; + port->typec_caps.orientation_aware = 1; + + port->partner_desc.identity = &port->partner_ident; + + port->role_sw = fwnode_usb_role_switch_get(tcpc->fwnode); + if (!port->role_sw) + port->role_sw = usb_role_switch_get(port->dev); + if (IS_ERR(port->role_sw)) { + err = PTR_ERR(port->role_sw); + goto out_destroy_wq; + } + + err = devm_tcpm_psy_register(port); + if (err) + goto out_role_sw_put; + power_supply_changed(port->psy); + + err = tcpm_port_register_pd(port); + if (err) + goto out_role_sw_put; + + if (port->pds) + port->typec_caps.pd = port->pds[0]; + + port->typec_port = typec_register_port(port->dev, &port->typec_caps); + if (IS_ERR(port->typec_port)) { + err = PTR_ERR(port->typec_port); + goto out_unregister_pd; + } + + typec_port_register_altmodes(port->typec_port, + &tcpm_altmode_ops, port, + port->port_altmode, ALTMODE_DISCOVERY_MAX); + typec_port_register_cable_ops(port->port_altmode, ARRAY_SIZE(port->port_altmode), + &tcpm_cable_ops); + port->registered = true; + + mutex_lock(&port->lock); + tcpm_init(port); + mutex_unlock(&port->lock); + + tcpm_log(port, "%s: registered", dev_name(dev)); + return port; + +out_unregister_pd: + tcpm_port_unregister_pd(port); +out_role_sw_put: + usb_role_switch_put(port->role_sw); +out_destroy_wq: + tcpm_debugfs_exit(port); + kthread_destroy_worker(port->wq); + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(tcpm_register_port); + +void tcpm_unregister_port(struct tcpm_port *port) +{ + int i; + + port->registered = false; + kthread_destroy_worker(port->wq); + + hrtimer_cancel(&port->vdm_discovery_timer); + hrtimer_cancel(&port->enable_frs_timer); + hrtimer_cancel(&port->vdm_state_machine_timer); + hrtimer_cancel(&port->state_machine_timer); + + power_supply_put_system_batteries(port->fixed_batt, port->fixed_batt_cnt); + tcpm_reset_port(port); + + tcpm_port_unregister_pd(port); + + for (i = 0; i < ARRAY_SIZE(port->port_altmode); i++) + typec_unregister_altmode(port->port_altmode[i]); + typec_unregister_port(port->typec_port); + usb_role_switch_put(port->role_sw); + tcpm_debugfs_exit(port); +} +EXPORT_SYMBOL_GPL(tcpm_unregister_port); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("USB Type-C Port Manager"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/wcove.c b/drivers/usb/typec/tcpm/wcove.c new file mode 100644 index 000000000..0e5a3e277 --- /dev/null +++ b/drivers/usb/typec/tcpm/wcove.c @@ -0,0 +1,701 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * typec_wcove.c - WhiskeyCove PMIC USB Type-C PHY driver + * + * Copyright (C) 2017 Intel Corporation + * Author: Heikki Krogerus + */ + +#include +#include +#include +#include +#include +#include + +/* Register offsets */ +#define WCOVE_CHGRIRQ0 0x4e09 + +#define USBC_CONTROL1 0x7001 +#define USBC_CONTROL2 0x7002 +#define USBC_CONTROL3 0x7003 +#define USBC_CC1_CTRL 0x7004 +#define USBC_CC2_CTRL 0x7005 +#define USBC_STATUS1 0x7007 +#define USBC_STATUS2 0x7008 +#define USBC_STATUS3 0x7009 +#define USBC_CC1 0x700a +#define USBC_CC2 0x700b +#define USBC_CC1_STATUS 0x700c +#define USBC_CC2_STATUS 0x700d +#define USBC_IRQ1 0x7015 +#define USBC_IRQ2 0x7016 +#define USBC_IRQMASK1 0x7017 +#define USBC_IRQMASK2 0x7018 +#define USBC_PDCFG2 0x701a +#define USBC_PDCFG3 0x701b +#define USBC_PDSTATUS 0x701c +#define USBC_RXSTATUS 0x701d +#define USBC_RXINFO 0x701e +#define USBC_TXCMD 0x701f +#define USBC_TXINFO 0x7020 +#define USBC_RX_DATA 0x7028 +#define USBC_TX_DATA 0x7047 + +/* Register bits */ + +#define USBC_CONTROL1_MODE_MASK 0x3 +#define USBC_CONTROL1_MODE_SNK 0 +#define USBC_CONTROL1_MODE_SNKACC 1 +#define USBC_CONTROL1_MODE_SRC 2 +#define USBC_CONTROL1_MODE_SRCACC 3 +#define USBC_CONTROL1_MODE_DRP 4 +#define USBC_CONTROL1_MODE_DRPACC 5 +#define USBC_CONTROL1_MODE_TEST 7 +#define USBC_CONTROL1_CURSRC_MASK 0xc +#define USBC_CONTROL1_CURSRC_UA_0 (0 << 3) +#define USBC_CONTROL1_CURSRC_UA_80 (1 << 3) +#define USBC_CONTROL1_CURSRC_UA_180 (2 << 3) +#define USBC_CONTROL1_CURSRC_UA_330 (3 << 3) +#define USBC_CONTROL1_DRPTOGGLE_RANDOM 0xe0 + +#define USBC_CONTROL2_UNATT_SNK BIT(0) +#define USBC_CONTROL2_UNATT_SRC BIT(1) +#define USBC_CONTROL2_DIS_ST BIT(2) + +#define USBC_CONTROL3_DET_DIS BIT(0) +#define USBC_CONTROL3_PD_DIS BIT(1) +#define USBC_CONTROL3_RESETPHY BIT(2) + +#define USBC_CC_CTRL_PU_EN BIT(0) +#define USBC_CC_CTRL_VCONN_EN BIT(1) +#define USBC_CC_CTRL_TX_EN BIT(2) +#define USBC_CC_CTRL_PD_EN BIT(3) +#define USBC_CC_CTRL_CDET_EN BIT(4) +#define USBC_CC_CTRL_RDET_EN BIT(5) +#define USBC_CC_CTRL_ADC_EN BIT(6) +#define USBC_CC_CTRL_VBUSOK BIT(7) + +#define USBC_STATUS1_DET_ONGOING BIT(6) +#define USBC_STATUS1_RSLT(r) ((r) & 0xf) +#define USBC_RSLT_NOTHING 0 +#define USBC_RSLT_SRC_DEFAULT 1 +#define USBC_RSLT_SRC_1_5A 2 +#define USBC_RSLT_SRC_3_0A 3 +#define USBC_RSLT_SNK 4 +#define USBC_RSLT_DEBUG_ACC 5 +#define USBC_RSLT_AUDIO_ACC 6 +#define USBC_RSLT_UNDEF 15 +#define USBC_STATUS1_ORIENT(r) (((r) >> 4) & 0x3) +#define USBC_ORIENT_NORMAL 1 +#define USBC_ORIENT_REVERSE 2 + +#define USBC_STATUS2_VBUS_REQ BIT(5) + +#define UCSC_CC_STATUS_SNK_RP BIT(0) +#define UCSC_CC_STATUS_PWRDEFSNK BIT(1) +#define UCSC_CC_STATUS_PWR_1P5A_SNK BIT(2) +#define UCSC_CC_STATUS_PWR_3A_SNK BIT(3) +#define UCSC_CC_STATUS_SRC_RP BIT(4) +#define UCSC_CC_STATUS_RX(r) (((r) >> 5) & 0x3) +#define USBC_CC_STATUS_RD 1 +#define USBC_CC_STATUS_RA 2 + +#define USBC_IRQ1_ADCDONE1 BIT(2) +#define USBC_IRQ1_OVERTEMP BIT(1) +#define USBC_IRQ1_SHORT BIT(0) + +#define USBC_IRQ2_CC_CHANGE BIT(7) +#define USBC_IRQ2_RX_PD BIT(6) +#define USBC_IRQ2_RX_HR BIT(5) +#define USBC_IRQ2_RX_CR BIT(4) +#define USBC_IRQ2_TX_SUCCESS BIT(3) +#define USBC_IRQ2_TX_FAIL BIT(2) + +#define USBC_IRQMASK1_ALL (USBC_IRQ1_ADCDONE1 | USBC_IRQ1_OVERTEMP | \ + USBC_IRQ1_SHORT) + +#define USBC_IRQMASK2_ALL (USBC_IRQ2_CC_CHANGE | USBC_IRQ2_RX_PD | \ + USBC_IRQ2_RX_HR | USBC_IRQ2_RX_CR | \ + USBC_IRQ2_TX_SUCCESS | USBC_IRQ2_TX_FAIL) + +#define USBC_PDCFG2_SOP BIT(0) +#define USBC_PDCFG2_SOP_P BIT(1) +#define USBC_PDCFG2_SOP_PP BIT(2) +#define USBC_PDCFG2_SOP_P_DEBUG BIT(3) +#define USBC_PDCFG2_SOP_PP_DEBUG BIT(4) + +#define USBC_PDCFG3_DATAROLE_SHIFT 1 +#define USBC_PDCFG3_SOP_SHIFT 2 + +#define USBC_RXSTATUS_RXCLEAR BIT(0) +#define USBC_RXSTATUS_RXDATA BIT(7) + +#define USBC_RXINFO_RXBYTES(i) (((i) >> 3) & 0x1f) + +#define USBC_TXCMD_BUF_RDY BIT(0) +#define USBC_TXCMD_START BIT(1) +#define USBC_TXCMD_NOP (0 << 5) +#define USBC_TXCMD_MSG (1 << 5) +#define USBC_TXCMD_CR (2 << 5) +#define USBC_TXCMD_HR (3 << 5) +#define USBC_TXCMD_BIST (4 << 5) + +#define USBC_TXINFO_RETRIES(d) (d << 3) + +struct wcove_typec { + struct mutex lock; /* device lock */ + struct device *dev; + struct regmap *regmap; + guid_t guid; + + bool vbus; + + struct tcpc_dev tcpc; + struct tcpm_port *tcpm; +}; + +#define tcpc_to_wcove(_tcpc_) container_of(_tcpc_, struct wcove_typec, tcpc) + +enum wcove_typec_func { + WCOVE_FUNC_DRIVE_VBUS = 1, + WCOVE_FUNC_ORIENTATION, + WCOVE_FUNC_ROLE, + WCOVE_FUNC_DRIVE_VCONN, +}; + +enum wcove_typec_orientation { + WCOVE_ORIENTATION_NORMAL, + WCOVE_ORIENTATION_REVERSE, +}; + +enum wcove_typec_role { + WCOVE_ROLE_HOST, + WCOVE_ROLE_DEVICE, +}; + +#define WCOVE_DSM_UUID "482383f0-2876-4e49-8685-db66211af037" + +static int wcove_typec_func(struct wcove_typec *wcove, + enum wcove_typec_func func, int param) +{ + union acpi_object *obj; + union acpi_object tmp; + union acpi_object argv4 = ACPI_INIT_DSM_ARGV4(1, &tmp); + + tmp.type = ACPI_TYPE_INTEGER; + tmp.integer.value = param; + + obj = acpi_evaluate_dsm(ACPI_HANDLE(wcove->dev), &wcove->guid, 1, func, + &argv4); + if (!obj) { + dev_err(wcove->dev, "%s: failed to evaluate _DSM\n", __func__); + return -EIO; + } + + ACPI_FREE(obj); + return 0; +} + +static int wcove_init(struct tcpc_dev *tcpc) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + int ret; + + ret = regmap_write(wcove->regmap, USBC_CONTROL1, 0); + if (ret) + return ret; + + /* Unmask everything */ + ret = regmap_write(wcove->regmap, USBC_IRQMASK1, 0); + if (ret) + return ret; + + return regmap_write(wcove->regmap, USBC_IRQMASK2, 0); +} + +static int wcove_get_vbus(struct tcpc_dev *tcpc) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + unsigned int cc1ctrl; + int ret; + + ret = regmap_read(wcove->regmap, USBC_CC1_CTRL, &cc1ctrl); + if (ret) + return ret; + + wcove->vbus = !!(cc1ctrl & USBC_CC_CTRL_VBUSOK); + + return wcove->vbus; +} + +static int wcove_set_vbus(struct tcpc_dev *tcpc, bool on, bool sink) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + + return wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VBUS, on); +} + +static int wcove_set_vconn(struct tcpc_dev *tcpc, bool on) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + + return wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, on); +} + +static enum typec_cc_status wcove_to_typec_cc(unsigned int cc) +{ + if (cc & UCSC_CC_STATUS_SNK_RP) { + if (cc & UCSC_CC_STATUS_PWRDEFSNK) + return TYPEC_CC_RP_DEF; + else if (cc & UCSC_CC_STATUS_PWR_1P5A_SNK) + return TYPEC_CC_RP_1_5; + else if (cc & UCSC_CC_STATUS_PWR_3A_SNK) + return TYPEC_CC_RP_3_0; + } else { + switch (UCSC_CC_STATUS_RX(cc)) { + case USBC_CC_STATUS_RD: + return TYPEC_CC_RD; + case USBC_CC_STATUS_RA: + return TYPEC_CC_RA; + default: + break; + } + } + return TYPEC_CC_OPEN; +} + +static int wcove_get_cc(struct tcpc_dev *tcpc, enum typec_cc_status *cc1, + enum typec_cc_status *cc2) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + unsigned int cc1_status; + unsigned int cc2_status; + int ret; + + ret = regmap_read(wcove->regmap, USBC_CC1_STATUS, &cc1_status); + if (ret) + return ret; + + ret = regmap_read(wcove->regmap, USBC_CC2_STATUS, &cc2_status); + if (ret) + return ret; + + *cc1 = wcove_to_typec_cc(cc1_status); + *cc2 = wcove_to_typec_cc(cc2_status); + + return 0; +} + +static int wcove_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + unsigned int ctrl; + + switch (cc) { + case TYPEC_CC_RD: + ctrl = USBC_CONTROL1_MODE_SNK; + break; + case TYPEC_CC_RP_DEF: + ctrl = USBC_CONTROL1_CURSRC_UA_80 | USBC_CONTROL1_MODE_SRC; + break; + case TYPEC_CC_RP_1_5: + ctrl = USBC_CONTROL1_CURSRC_UA_180 | USBC_CONTROL1_MODE_SRC; + break; + case TYPEC_CC_RP_3_0: + ctrl = USBC_CONTROL1_CURSRC_UA_330 | USBC_CONTROL1_MODE_SRC; + break; + case TYPEC_CC_OPEN: + ctrl = 0; + break; + default: + return -EINVAL; + } + + return regmap_write(wcove->regmap, USBC_CONTROL1, ctrl); +} + +static int wcove_set_polarity(struct tcpc_dev *tcpc, enum typec_cc_polarity pol) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + + return wcove_typec_func(wcove, WCOVE_FUNC_ORIENTATION, pol); +} + +static int wcove_set_current_limit(struct tcpc_dev *tcpc, u32 max_ma, u32 mv) +{ + return 0; +} + +static int wcove_set_roles(struct tcpc_dev *tcpc, bool attached, + enum typec_role role, enum typec_data_role data) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + unsigned int val; + int ret; + + ret = wcove_typec_func(wcove, WCOVE_FUNC_ROLE, data == TYPEC_HOST ? + WCOVE_ROLE_HOST : WCOVE_ROLE_DEVICE); + if (ret) + return ret; + + val = role; + val |= data << USBC_PDCFG3_DATAROLE_SHIFT; + val |= PD_REV20 << USBC_PDCFG3_SOP_SHIFT; + + return regmap_write(wcove->regmap, USBC_PDCFG3, val); +} + +static int wcove_set_pd_rx(struct tcpc_dev *tcpc, bool on) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + + return regmap_write(wcove->regmap, USBC_PDCFG2, + on ? USBC_PDCFG2_SOP : 0); +} + +static int wcove_pd_transmit(struct tcpc_dev *tcpc, + enum tcpm_transmit_type type, + const struct pd_message *msg, + unsigned int negotiated_rev) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + unsigned int info = 0; + unsigned int cmd; + int ret; + + ret = regmap_read(wcove->regmap, USBC_TXCMD, &cmd); + if (ret) + return ret; + + if (!(cmd & USBC_TXCMD_BUF_RDY)) { + dev_warn(wcove->dev, "%s: Last transmission still ongoing!", + __func__); + return -EBUSY; + } + + if (msg) { + const u8 *data = (void *)msg; + int i; + + for (i = 0; i < pd_header_cnt_le(msg->header) * 4 + 2; i++) { + ret = regmap_write(wcove->regmap, USBC_TX_DATA + i, + data[i]); + if (ret) + return ret; + } + } + + switch (type) { + case TCPC_TX_SOP: + case TCPC_TX_SOP_PRIME: + case TCPC_TX_SOP_PRIME_PRIME: + case TCPC_TX_SOP_DEBUG_PRIME: + case TCPC_TX_SOP_DEBUG_PRIME_PRIME: + info = type + 1; + cmd = USBC_TXCMD_MSG; + break; + case TCPC_TX_HARD_RESET: + cmd = USBC_TXCMD_HR; + break; + case TCPC_TX_CABLE_RESET: + cmd = USBC_TXCMD_CR; + break; + case TCPC_TX_BIST_MODE_2: + cmd = USBC_TXCMD_BIST; + break; + default: + return -EINVAL; + } + + /* NOTE Setting maximum number of retries (7) */ + ret = regmap_write(wcove->regmap, USBC_TXINFO, + info | USBC_TXINFO_RETRIES(7)); + if (ret) + return ret; + + return regmap_write(wcove->regmap, USBC_TXCMD, cmd | USBC_TXCMD_START); +} + +static int wcove_start_toggling(struct tcpc_dev *tcpc, + enum typec_port_type port_type, + enum typec_cc_status cc) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + unsigned int usbc_ctrl; + + if (port_type != TYPEC_PORT_DRP) + return -EOPNOTSUPP; + + usbc_ctrl = USBC_CONTROL1_MODE_DRP | USBC_CONTROL1_DRPTOGGLE_RANDOM; + + switch (cc) { + case TYPEC_CC_RP_1_5: + usbc_ctrl |= USBC_CONTROL1_CURSRC_UA_180; + break; + case TYPEC_CC_RP_3_0: + usbc_ctrl |= USBC_CONTROL1_CURSRC_UA_330; + break; + default: + usbc_ctrl |= USBC_CONTROL1_CURSRC_UA_80; + break; + } + + return regmap_write(wcove->regmap, USBC_CONTROL1, usbc_ctrl); +} + +static int wcove_read_rx_buffer(struct wcove_typec *wcove, + struct pd_message *msg) +{ + unsigned int info, val, len; + u8 *buf = (u8 *)msg; + int ret; + int i; + + ret = regmap_read(wcove->regmap, USBC_RXINFO, &info); + if (ret) + return ret; + + len = min(USBC_RXINFO_RXBYTES(info), sizeof(*msg)); + + for (i = 0; i < len; i++) { + ret = regmap_read(wcove->regmap, USBC_RX_DATA + i, &val); + if (ret) + return ret; + buf[i] = val; + } + + return regmap_write(wcove->regmap, USBC_RXSTATUS, + USBC_RXSTATUS_RXCLEAR); +} + +static irqreturn_t wcove_typec_irq(int irq, void *data) +{ + struct wcove_typec *wcove = data; + unsigned int usbc_irq1 = 0; + unsigned int usbc_irq2 = 0; + unsigned int cc1ctrl; + int ret; + + mutex_lock(&wcove->lock); + + /* Read.. */ + ret = regmap_read(wcove->regmap, USBC_IRQ1, &usbc_irq1); + if (ret) + goto err; + + ret = regmap_read(wcove->regmap, USBC_IRQ2, &usbc_irq2); + if (ret) + goto err; + + ret = regmap_read(wcove->regmap, USBC_CC1_CTRL, &cc1ctrl); + if (ret) + goto err; + + if (!wcove->tcpm) + goto err; + + /* ..check.. */ + if (usbc_irq1 & USBC_IRQ1_OVERTEMP) { + dev_err(wcove->dev, "VCONN Switch Over Temperature!\n"); + wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, false); + /* REVISIT: Report an error? */ + } + + if (usbc_irq1 & USBC_IRQ1_SHORT) { + dev_err(wcove->dev, "VCONN Switch Short Circuit!\n"); + wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, false); + /* REVISIT: Report an error? */ + } + + if (wcove->vbus != !!(cc1ctrl & USBC_CC_CTRL_VBUSOK)) + tcpm_vbus_change(wcove->tcpm); + + /* REVISIT: See if tcpm code can be made to consider Type-C HW FSMs */ + if (usbc_irq2 & USBC_IRQ2_CC_CHANGE) + tcpm_cc_change(wcove->tcpm); + + if (usbc_irq2 & USBC_IRQ2_RX_PD) { + unsigned int status; + + /* + * FIXME: Need to check if TX is ongoing and report + * TX_DIREGARDED if needed? + */ + + ret = regmap_read(wcove->regmap, USBC_RXSTATUS, &status); + if (ret) + goto err; + + /* Flush all buffers */ + while (status & USBC_RXSTATUS_RXDATA) { + struct pd_message msg; + + ret = wcove_read_rx_buffer(wcove, &msg); + if (ret) { + dev_err(wcove->dev, "%s: RX read failed\n", + __func__); + goto err; + } + + tcpm_pd_receive(wcove->tcpm, &msg, TCPC_TX_SOP); + + ret = regmap_read(wcove->regmap, USBC_RXSTATUS, + &status); + if (ret) + goto err; + } + } + + if (usbc_irq2 & USBC_IRQ2_RX_HR) + tcpm_pd_hard_reset(wcove->tcpm); + + /* REVISIT: if (usbc_irq2 & USBC_IRQ2_RX_CR) */ + + if (usbc_irq2 & USBC_IRQ2_TX_SUCCESS) + tcpm_pd_transmit_complete(wcove->tcpm, TCPC_TX_SUCCESS); + + if (usbc_irq2 & USBC_IRQ2_TX_FAIL) + tcpm_pd_transmit_complete(wcove->tcpm, TCPC_TX_FAILED); + +err: + /* ..and clear. */ + if (usbc_irq1) { + ret = regmap_write(wcove->regmap, USBC_IRQ1, usbc_irq1); + if (ret) + dev_WARN(wcove->dev, "%s failed to clear IRQ1\n", + __func__); + } + + if (usbc_irq2) { + ret = regmap_write(wcove->regmap, USBC_IRQ2, usbc_irq2); + if (ret) + dev_WARN(wcove->dev, "%s failed to clear IRQ2\n", + __func__); + } + + /* REVISIT: Clear WhiskeyCove CHGR Type-C interrupt */ + regmap_write(wcove->regmap, WCOVE_CHGRIRQ0, BIT(5)); + + mutex_unlock(&wcove->lock); + return IRQ_HANDLED; +} + +/* + * The following power levels should be safe to use with Joule board. + */ +static const u32 src_pdo[] = { + PDO_FIXED(5000, 1500, PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | + PDO_FIXED_USB_COMM), +}; + +static const u32 snk_pdo[] = { + PDO_FIXED(5000, 500, PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | + PDO_FIXED_USB_COMM), + PDO_VAR(5000, 12000, 3000), +}; + +static const struct property_entry wcove_props[] = { + PROPERTY_ENTRY_STRING("data-role", "dual"), + PROPERTY_ENTRY_STRING("power-role", "dual"), + PROPERTY_ENTRY_STRING("try-power-role", "sink"), + PROPERTY_ENTRY_U32_ARRAY("source-pdos", src_pdo), + PROPERTY_ENTRY_U32_ARRAY("sink-pdos", snk_pdo), + PROPERTY_ENTRY_U32("op-sink-microwatt", 15000000), + { } +}; + +static int wcove_typec_probe(struct platform_device *pdev) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); + struct wcove_typec *wcove; + int irq; + int ret; + + wcove = devm_kzalloc(&pdev->dev, sizeof(*wcove), GFP_KERNEL); + if (!wcove) + return -ENOMEM; + + mutex_init(&wcove->lock); + wcove->dev = &pdev->dev; + wcove->regmap = pmic->regmap; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = guid_parse(WCOVE_DSM_UUID, &wcove->guid); + if (ret) + return ret; + + if (!acpi_check_dsm(ACPI_HANDLE(&pdev->dev), &wcove->guid, 0, 0x1f)) { + dev_err(&pdev->dev, "Missing _DSM functions\n"); + return -ENODEV; + } + + wcove->tcpc.init = wcove_init; + wcove->tcpc.get_vbus = wcove_get_vbus; + wcove->tcpc.set_vbus = wcove_set_vbus; + wcove->tcpc.set_cc = wcove_set_cc; + wcove->tcpc.get_cc = wcove_get_cc; + wcove->tcpc.set_polarity = wcove_set_polarity; + wcove->tcpc.set_vconn = wcove_set_vconn; + wcove->tcpc.set_current_limit = wcove_set_current_limit; + wcove->tcpc.start_toggling = wcove_start_toggling; + + wcove->tcpc.set_pd_rx = wcove_set_pd_rx; + wcove->tcpc.set_roles = wcove_set_roles; + wcove->tcpc.pd_transmit = wcove_pd_transmit; + + wcove->tcpc.fwnode = fwnode_create_software_node(wcove_props, NULL); + if (IS_ERR(wcove->tcpc.fwnode)) + return PTR_ERR(wcove->tcpc.fwnode); + + wcove->tcpm = tcpm_register_port(wcove->dev, &wcove->tcpc); + if (IS_ERR(wcove->tcpm)) { + fwnode_remove_software_node(wcove->tcpc.fwnode); + return PTR_ERR(wcove->tcpm); + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + wcove_typec_irq, IRQF_ONESHOT, + "wcove_typec", wcove); + if (ret) { + tcpm_unregister_port(wcove->tcpm); + fwnode_remove_software_node(wcove->tcpc.fwnode); + return ret; + } + + platform_set_drvdata(pdev, wcove); + return 0; +} + +static void wcove_typec_remove(struct platform_device *pdev) +{ + struct wcove_typec *wcove = platform_get_drvdata(pdev); + unsigned int val; + + /* Mask everything */ + regmap_read(wcove->regmap, USBC_IRQMASK1, &val); + regmap_write(wcove->regmap, USBC_IRQMASK1, val | USBC_IRQMASK1_ALL); + regmap_read(wcove->regmap, USBC_IRQMASK2, &val); + regmap_write(wcove->regmap, USBC_IRQMASK2, val | USBC_IRQMASK2_ALL); + + tcpm_unregister_port(wcove->tcpm); + fwnode_remove_software_node(wcove->tcpc.fwnode); +} + +static struct platform_driver wcove_typec_driver = { + .driver = { + .name = "bxt_wcove_usbc", + }, + .probe = wcove_typec_probe, + .remove = wcove_typec_remove, +}; + +module_platform_driver(wcove_typec_driver); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("WhiskeyCove PMIC USB Type-C PHY driver"); +MODULE_ALIAS("platform:bxt_wcove_usbc"); diff --git a/drivers/usb/typec/tipd/Kconfig b/drivers/usb/typec/tipd/Kconfig new file mode 100644 index 000000000..b82715293 --- /dev/null +++ b/drivers/usb/typec/tipd/Kconfig @@ -0,0 +1,12 @@ +config TYPEC_TPS6598X + tristate "TI TPS6598x USB Power Delivery controller driver" + depends on I2C + select POWER_SUPPLY + select REGMAP_I2C + select USB_ROLE_SWITCH + help + Say Y or M here if your system has TI TPS65982 or TPS65983 USB Power + Delivery controller. + + If you choose to build this driver as a dynamically linked module, the + module will be called tps6598x.ko. diff --git a/drivers/usb/typec/tipd/Makefile b/drivers/usb/typec/tipd/Makefile new file mode 100644 index 000000000..aa439f80a --- /dev/null +++ b/drivers/usb/typec/tipd/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS_trace.o := -I$(src) + +obj-$(CONFIG_TYPEC_TPS6598X) += tps6598x.o +tps6598x-y := core.o +tps6598x-$(CONFIG_TRACING) += trace.o diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c new file mode 100644 index 000000000..522f56742 --- /dev/null +++ b/drivers/usb/typec/tipd/core.c @@ -0,0 +1,2117 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for TI TPS6598x USB Power Delivery controller family + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tps6598x.h" +#include "trace.h" + +/* Register offsets */ +#define TPS_REG_VID 0x00 +#define TPS_REG_MODE 0x03 +#define TPS_REG_CMD1 0x08 +#define TPS_REG_DATA1 0x09 +#define TPS_REG_VERSION 0x0F +#define TPS_REG_INT_EVENT1 0x14 +#define TPS_REG_INT_EVENT2 0x15 +#define TPS_REG_INT_MASK1 0x16 +#define TPS_REG_INT_MASK2 0x17 +#define TPS_REG_INT_CLEAR1 0x18 +#define TPS_REG_INT_CLEAR2 0x19 +#define TPS_REG_STATUS 0x1a +#define TPS_REG_SYSTEM_POWER_STATE 0x20 +#define TPS_REG_USB4_STATUS 0x24 +#define TPS_REG_SYSTEM_CONF 0x28 +#define TPS_REG_CTRL_CONF 0x29 +#define TPS_REG_BOOT_STATUS 0x2D +#define TPS_REG_POWER_STATUS 0x3f +#define TPS_REG_PD_STATUS 0x40 +#define TPS_REG_RX_IDENTITY_SOP 0x48 +#define TPS_REG_CF_VID_STATUS 0x5e +#define TPS_REG_DP_SID_STATUS 0x58 +#define TPS_REG_INTEL_VID_STATUS 0x59 +#define TPS_REG_DATA_STATUS 0x5f +#define TPS_REG_SLEEP_CONF 0x70 + +/* TPS_REG_SYSTEM_CONF bits */ +#define TPS_SYSCONF_PORTINFO(c) ((c) & 7) + +/* + * BPMs task timeout, recommended 5 seconds + * pg.48 TPS2575 Host Interface Technical Reference + * Manual (Rev. A) + * https://www.ti.com/lit/ug/slvuc05a/slvuc05a.pdf + */ +#define TPS_BUNDLE_TIMEOUT 0x32 + +/* BPMs return code */ +#define TPS_TASK_BPMS_INVALID_BUNDLE_SIZE 0x4 +#define TPS_TASK_BPMS_INVALID_SLAVE_ADDR 0x5 +#define TPS_TASK_BPMS_INVALID_TIMEOUT 0x6 + +/* PBMc data out */ +#define TPS_PBMC_RC 0 /* Return code */ +#define TPS_PBMC_DPCS 2 /* device patch complete status */ + +/* reset de-assertion to ready for operation */ +#define TPS_SETUP_MS 1000 + +enum { + TPS_PORTINFO_SINK, + TPS_PORTINFO_SINK_ACCESSORY, + TPS_PORTINFO_DRP_UFP, + TPS_PORTINFO_DRP_UFP_DRD, + TPS_PORTINFO_DRP_DFP, + TPS_PORTINFO_DRP_DFP_DRD, + TPS_PORTINFO_SOURCE, +}; + +/* TPS_REG_RX_IDENTITY_SOP */ +struct tps6598x_rx_identity_reg { + u8 status; + struct usb_pd_identity identity; +} __packed; + +/* TPS_REG_USB4_STATUS */ +struct tps6598x_usb4_status_reg { + u8 mode_status; + __le32 eudo; + __le32 unknown; +} __packed; + +/* TPS_REG_DP_SID_STATUS */ +struct tps6598x_dp_sid_status_reg { + u8 mode_status; + __le32 status_tx; + __le32 status_rx; + __le32 configure; + __le32 mode_data; +} __packed; + +/* TPS_REG_INTEL_VID_STATUS */ +struct tps6598x_intel_vid_status_reg { + u8 mode_status; + __le32 attention_vdo; + __le16 enter_vdo; + __le16 device_mode; + __le16 cable_mode; +} __packed; + +/* Standard Task return codes */ +#define TPS_TASK_TIMEOUT 1 +#define TPS_TASK_REJECTED 3 + +/* Debounce delay for mode changes, in milliseconds */ +#define CD321X_DEBOUNCE_DELAY_MS 500 + +enum { + TPS_MODE_APP, + TPS_MODE_BOOT, + TPS_MODE_BIST, + TPS_MODE_DISC, + TPS_MODE_PTCH, + TPS_MODE_APP1, +}; + +static const char *const modes[] = { + [TPS_MODE_APP] = "APP ", + [TPS_MODE_BOOT] = "BOOT", + [TPS_MODE_BIST] = "BIST", + [TPS_MODE_DISC] = "DISC", + [TPS_MODE_PTCH] = "PTCH", + [TPS_MODE_APP1] = "APP1", +}; + +/* Unrecognized commands will be replaced with "!CMD" */ +#define INVALID_CMD(_cmd_) (_cmd_ == 0x444d4321) + +struct tps6598x; + +struct tipd_data { + irq_handler_t irq_handler; + u64 irq_mask1; + size_t tps_struct_size; + void (*remove)(struct tps6598x *tps); + int (*register_port)(struct tps6598x *tps, struct fwnode_handle *node); + void (*unregister_port)(struct tps6598x *tps); + void (*trace_data_status)(u32 status); + void (*trace_power_status)(u16 status); + void (*trace_status)(u32 status); + int (*apply_patch)(struct tps6598x *tps); + int (*init)(struct tps6598x *tps); + int (*switch_power_state)(struct tps6598x *tps, u8 target_state); + bool (*read_data_status)(struct tps6598x *tps); + bool (*read_power_status)(struct tps6598x *tps); + int (*reset)(struct tps6598x *tps); + int (*connect)(struct tps6598x *tps, u32 status); +}; + +struct tps6598x { + struct device *dev; + struct regmap *regmap; + struct mutex lock; /* device lock */ + u8 i2c_protocol:1; + + struct gpio_desc *reset; + struct typec_port *port; + struct typec_partner *partner; + struct usb_pd_identity partner_identity; + struct usb_role_switch *role_sw; + struct typec_capability typec_cap; + + struct power_supply *psy; + struct power_supply_desc psy_desc; + enum power_supply_usb_type usb_type; + + int wakeup; + u32 status; /* status reg */ + u32 data_status; + u16 pwr_status; + struct delayed_work wq_poll; + + const struct tipd_data *data; +}; + +struct cd321x_status { + u32 status; + u32 pwr_status; + u32 data_status; + u32 status_changed; + struct usb_pd_identity partner_identity; + struct tps6598x_dp_sid_status_reg dp_sid_status; + struct tps6598x_intel_vid_status_reg intel_vid_status; + struct tps6598x_usb4_status_reg usb4_status; +}; + +struct cd321x { + struct tps6598x tps; + + struct tps6598x_dp_sid_status_reg dp_sid_status; + struct tps6598x_intel_vid_status_reg intel_vid_status; + struct tps6598x_usb4_status_reg usb4_status; + + struct typec_altmode *port_altmode_dp; + struct typec_altmode *port_altmode_tbt; + + struct typec_mux *mux; + struct typec_mux_state state; + + struct cd321x_status update_status; + struct delayed_work update_work; + struct usb_pd_identity cur_partner_identity; +}; + +static enum power_supply_property tps6598x_psy_props[] = { + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_ONLINE, +}; + +static const char *tps6598x_psy_name_prefix = "tps6598x-source-psy-"; + +/* + * Max data bytes for Data1, Data2, and other registers. See ch 1.3.2: + * https://www.ti.com/lit/ug/slvuan1a/slvuan1a.pdf + */ +#define TPS_MAX_LEN 64 + +static int +tps6598x_block_read(struct tps6598x *tps, u8 reg, void *val, size_t len) +{ + u8 data[TPS_MAX_LEN + 1]; + int ret; + + if (len + 1 > sizeof(data)) + return -EINVAL; + + if (!tps->i2c_protocol) + return regmap_raw_read(tps->regmap, reg, val, len); + + ret = regmap_raw_read(tps->regmap, reg, data, len + 1); + if (ret) + return ret; + + if (data[0] < len) + return -EIO; + + memcpy(val, &data[1], len); + return 0; +} + +static int tps6598x_block_write(struct tps6598x *tps, u8 reg, + const void *val, size_t len) +{ + u8 data[TPS_MAX_LEN + 1]; + + if (len + 1 > sizeof(data)) + return -EINVAL; + + if (!tps->i2c_protocol) + return regmap_raw_write(tps->regmap, reg, val, len); + + data[0] = len; + memcpy(&data[1], val, len); + + return regmap_raw_write(tps->regmap, reg, data, len + 1); +} + +static inline int tps6598x_read8(struct tps6598x *tps, u8 reg, u8 *val) +{ + return tps6598x_block_read(tps, reg, val, sizeof(u8)); +} + +static inline int tps6598x_read16(struct tps6598x *tps, u8 reg, u16 *val) +{ + return tps6598x_block_read(tps, reg, val, sizeof(u16)); +} + +static inline int tps6598x_read32(struct tps6598x *tps, u8 reg, u32 *val) +{ + return tps6598x_block_read(tps, reg, val, sizeof(u32)); +} + +static inline int tps6598x_read64(struct tps6598x *tps, u8 reg, u64 *val) +{ + return tps6598x_block_read(tps, reg, val, sizeof(u64)); +} + +static inline int tps6598x_write8(struct tps6598x *tps, u8 reg, u8 val) +{ + return tps6598x_block_write(tps, reg, &val, sizeof(u8)); +} + +static inline int tps6598x_write64(struct tps6598x *tps, u8 reg, u64 val) +{ + return tps6598x_block_write(tps, reg, &val, sizeof(u64)); +} + +static inline int +tps6598x_write_4cc(struct tps6598x *tps, u8 reg, const char *val) +{ + return tps6598x_block_write(tps, reg, val, 4); +} + +static int tps6598x_read_partner_identity(struct tps6598x *tps) +{ + struct tps6598x_rx_identity_reg id; + int ret; + + ret = tps6598x_block_read(tps, TPS_REG_RX_IDENTITY_SOP, + &id, sizeof(id)); + if (ret) + return ret; + + tps->partner_identity = id.identity; + + return 0; +} + +static void tps6598x_set_data_role(struct tps6598x *tps, + enum typec_data_role role, bool connected) +{ + enum usb_role role_val; + + if (role == TYPEC_HOST) + role_val = USB_ROLE_HOST; + else + role_val = USB_ROLE_DEVICE; + + if (!connected) + role_val = USB_ROLE_NONE; + + usb_role_switch_set_role(tps->role_sw, role_val); + typec_set_data_role(tps->port, role); +} + +static int tps6598x_connect(struct tps6598x *tps, u32 status) +{ + struct typec_partner_desc desc; + enum typec_pwr_opmode mode; + int ret; + + if (tps->partner) + return 0; + + mode = TPS_POWER_STATUS_PWROPMODE(tps->pwr_status); + + desc.usb_pd = mode == TYPEC_PWR_MODE_PD; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ + desc.identity = NULL; + + if (desc.usb_pd) { + ret = tps6598x_read_partner_identity(tps); + if (ret) + return ret; + desc.identity = &tps->partner_identity; + } + + typec_set_pwr_opmode(tps->port, mode); + typec_set_pwr_role(tps->port, TPS_STATUS_TO_TYPEC_PORTROLE(status)); + typec_set_vconn_role(tps->port, TPS_STATUS_TO_TYPEC_VCONN(status)); + if (TPS_STATUS_TO_UPSIDE_DOWN(status)) + typec_set_orientation(tps->port, TYPEC_ORIENTATION_REVERSE); + else + typec_set_orientation(tps->port, TYPEC_ORIENTATION_NORMAL); + typec_set_mode(tps->port, TYPEC_STATE_USB); + tps6598x_set_data_role(tps, TPS_STATUS_TO_TYPEC_DATAROLE(status), true); + + tps->partner = typec_register_partner(tps->port, &desc); + if (IS_ERR(tps->partner)) + return PTR_ERR(tps->partner); + + if (desc.identity) + typec_partner_set_identity(tps->partner); + + power_supply_changed(tps->psy); + + return 0; +} + +static void tps6598x_disconnect(struct tps6598x *tps, u32 status) +{ + if (!IS_ERR(tps->partner)) + typec_unregister_partner(tps->partner); + tps->partner = NULL; + typec_set_pwr_opmode(tps->port, TYPEC_PWR_MODE_USB); + typec_set_pwr_role(tps->port, TPS_STATUS_TO_TYPEC_PORTROLE(status)); + typec_set_vconn_role(tps->port, TPS_STATUS_TO_TYPEC_VCONN(status)); + typec_set_orientation(tps->port, TYPEC_ORIENTATION_NONE); + typec_set_mode(tps->port, TYPEC_STATE_SAFE); + tps6598x_set_data_role(tps, TPS_STATUS_TO_TYPEC_DATAROLE(status), false); + + power_supply_changed(tps->psy); +} + +static int tps6598x_exec_cmd_tmo(struct tps6598x *tps, const char *cmd, + size_t in_len, const u8 *in_data, + size_t out_len, u8 *out_data, + u32 cmd_timeout_ms, u32 res_delay_ms) +{ + unsigned long timeout; + u32 val; + int ret; + + ret = tps6598x_read32(tps, TPS_REG_CMD1, &val); + if (ret) + return ret; + if (val && !INVALID_CMD(val)) + return -EBUSY; + + if (in_len) { + ret = tps6598x_block_write(tps, TPS_REG_DATA1, + in_data, in_len); + if (ret) + return ret; + } + + ret = tps6598x_write_4cc(tps, TPS_REG_CMD1, cmd); + if (ret < 0) + return ret; + + timeout = jiffies + msecs_to_jiffies(cmd_timeout_ms); + + do { + ret = tps6598x_read32(tps, TPS_REG_CMD1, &val); + if (ret) + return ret; + if (INVALID_CMD(val)) + return -EINVAL; + + if (time_is_before_jiffies(timeout)) + return -ETIMEDOUT; + } while (val); + + /* some commands require delay for the result to be available */ + mdelay(res_delay_ms); + + if (out_len) { + ret = tps6598x_block_read(tps, TPS_REG_DATA1, + out_data, out_len); + if (ret) + return ret; + val = out_data[0]; + } else { + ret = tps6598x_block_read(tps, TPS_REG_DATA1, &val, sizeof(u8)); + if (ret) + return ret; + } + + switch (val) { + case TPS_TASK_TIMEOUT: + return -ETIMEDOUT; + case TPS_TASK_REJECTED: + return -EPERM; + default: + break; + } + + return 0; +} + +static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd, + size_t in_len, const u8 *in_data, + size_t out_len, u8 *out_data) +{ + return tps6598x_exec_cmd_tmo(tps, cmd, in_len, in_data, + out_len, out_data, 1000, 0); +} + +static int tps6598x_dr_set(struct typec_port *port, enum typec_data_role role) +{ + const char *cmd = (role == TYPEC_DEVICE) ? "SWUF" : "SWDF"; + struct tps6598x *tps = typec_get_drvdata(port); + u32 status; + int ret; + + mutex_lock(&tps->lock); + + ret = tps6598x_exec_cmd(tps, cmd, 0, NULL, 0, NULL); + if (ret) + goto out_unlock; + + ret = tps6598x_read32(tps, TPS_REG_STATUS, &status); + if (ret) + goto out_unlock; + + if (role != TPS_STATUS_TO_TYPEC_DATAROLE(status)) { + ret = -EPROTO; + goto out_unlock; + } + + tps6598x_set_data_role(tps, role, true); + +out_unlock: + mutex_unlock(&tps->lock); + + return ret; +} + +static int tps6598x_pr_set(struct typec_port *port, enum typec_role role) +{ + const char *cmd = (role == TYPEC_SINK) ? "SWSk" : "SWSr"; + struct tps6598x *tps = typec_get_drvdata(port); + u32 status; + int ret; + + mutex_lock(&tps->lock); + + ret = tps6598x_exec_cmd(tps, cmd, 0, NULL, 0, NULL); + if (ret) + goto out_unlock; + + ret = tps6598x_read32(tps, TPS_REG_STATUS, &status); + if (ret) + goto out_unlock; + + if (role != TPS_STATUS_TO_TYPEC_PORTROLE(status)) { + ret = -EPROTO; + goto out_unlock; + } + + typec_set_pwr_role(tps->port, role); + +out_unlock: + mutex_unlock(&tps->lock); + + return ret; +} + +static const struct typec_operations tps6598x_ops = { + .dr_set = tps6598x_dr_set, + .pr_set = tps6598x_pr_set, +}; + +static bool tps6598x_read_status(struct tps6598x *tps, u32 *status) +{ + int ret; + + ret = tps6598x_read32(tps, TPS_REG_STATUS, status); + if (ret) { + dev_err(tps->dev, "%s: failed to read status\n", __func__); + return false; + } + + if (tps->data->trace_status) + tps->data->trace_status(*status); + + return true; +} + +static bool tps6598x_read_data_status(struct tps6598x *tps) +{ + u32 data_status; + int ret; + + ret = tps6598x_read32(tps, TPS_REG_DATA_STATUS, &data_status); + if (ret < 0) { + dev_err(tps->dev, "failed to read data status: %d\n", ret); + return false; + } + tps->data_status = data_status; + + if (tps->data->trace_data_status) + tps->data->trace_data_status(data_status); + + return true; +} + +static bool cd321x_read_data_status(struct tps6598x *tps) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + int ret; + + ret = tps6598x_read_data_status(tps); + if (!ret) + return false; + + if (tps->data_status & TPS_DATA_STATUS_DP_CONNECTION) { + ret = tps6598x_block_read(tps, TPS_REG_DP_SID_STATUS, + &cd321x->dp_sid_status, sizeof(cd321x->dp_sid_status)); + if (ret) { + dev_err(tps->dev, "Failed to read DP SID Status: %d\n", + ret); + return false; + } + } + + if (tps->data_status & TPS_DATA_STATUS_TBT_CONNECTION) { + ret = tps6598x_block_read(tps, TPS_REG_INTEL_VID_STATUS, + &cd321x->intel_vid_status, sizeof(cd321x->intel_vid_status)); + if (ret) { + dev_err(tps->dev, "Failed to read Intel VID Status: %d\n", ret); + return false; + } + } + + if (tps->data_status & CD321X_DATA_STATUS_USB4_CONNECTION) { + ret = tps6598x_block_read(tps, TPS_REG_USB4_STATUS, + &cd321x->usb4_status, sizeof(cd321x->usb4_status)); + if (ret) { + dev_err(tps->dev, + "Failed to read USB4 Status: %d\n", ret); + return false; + } + } + + return true; +} + +static bool tps6598x_read_power_status(struct tps6598x *tps) +{ + u16 pwr_status; + int ret; + + ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status); + if (ret < 0) { + dev_err(tps->dev, "failed to read power status: %d\n", ret); + return false; + } + tps->pwr_status = pwr_status; + + if (tps->data->trace_power_status) + tps->data->trace_power_status(pwr_status); + + return true; +} + +/* + * TPS66993 deprecated Power_Status register (0x3F). BC1.2 is not supported + * and the remaining bits are redundant with STATUS register (0x1A). + * Synthesize pwr_status from the already-read STATUS register. + */ +static bool tps66993_read_power_status(struct tps6598x *tps) +{ + u16 pwr_status = 0; + + /* Same masks as TPS_POWER_STATUS_CONNECTION() / SOURCESINK() / PWROPMODE() in tps6598x.h */ + if (tps->status & TPS_STATUS_PLUG_PRESENT) + pwr_status |= FIELD_PREP(TPS_POWER_STATUS_CONNECTION_MASK, 1); + + /* SOURCESINK: 1=sink; STATUS.PortRole 1=source, opposite convention */ + if (!TPS_STATUS_TO_TYPEC_PORTROLE(tps->status)) + pwr_status |= FIELD_PREP(TPS_POWER_STATUS_SOURCESINK_MASK, 1); + + if (TPS_STATUS_VBUS_STATUS(tps->status) == TPS_STATUS_VBUS_STATUS_PD) + pwr_status |= FIELD_PREP(TPS_POWER_STATUS_TYPEC_CURRENT_MASK, + TPS_POWER_STATUS_TYPEC_CURRENT_PD); + + tps->pwr_status = pwr_status; + + tps->data->trace_power_status(pwr_status); + + return true; +} + +static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status) +{ + int ret; + + if (status & TPS_STATUS_PLUG_PRESENT) { + ret = tps6598x_connect(tps, status); + if (ret) + dev_err(tps->dev, "failed to register partner\n"); + } else { + tps6598x_disconnect(tps, status); + } +} + +static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status *st) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + + if (!(st->data_status & TPS_DATA_STATUS_DATA_CONNECTION)) { + if (cd321x->state.mode == TYPEC_STATE_SAFE) + return; + cd321x->state.alt = NULL; + cd321x->state.mode = TYPEC_STATE_SAFE; + cd321x->state.data = NULL; + typec_mux_set(cd321x->mux, &cd321x->state); + } else if (st->data_status & TPS_DATA_STATUS_DP_CONNECTION) { + struct typec_displayport_data dp_data; + unsigned long mode; + + switch (TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT(st->data_status)) { + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_A: + mode = TYPEC_DP_STATE_A; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_B: + mode = TYPEC_DP_STATE_B; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_C: + mode = TYPEC_DP_STATE_C; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_D: + mode = TYPEC_DP_STATE_D; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_E: + mode = TYPEC_DP_STATE_E; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_F: + mode = TYPEC_DP_STATE_F; + break; + default: + dev_err(tps->dev, "Invalid DP pin assignment\n"); + return; + } + + if (cd321x->state.alt == cd321x->port_altmode_dp && + cd321x->state.mode == mode) { + return; + } + + dp_data.status = le32_to_cpu(st->dp_sid_status.status_rx); + dp_data.conf = le32_to_cpu(st->dp_sid_status.configure); + cd321x->state.alt = cd321x->port_altmode_dp; + cd321x->state.data = &dp_data; + cd321x->state.mode = mode; + typec_mux_set(cd321x->mux, &cd321x->state); + } else if (st->data_status & TPS_DATA_STATUS_TBT_CONNECTION) { + struct typec_thunderbolt_data tbt_data; + + if (cd321x->state.alt == cd321x->port_altmode_tbt && + cd321x->state.mode == TYPEC_TBT_MODE) + return; + + tbt_data.cable_mode = le16_to_cpu(st->intel_vid_status.cable_mode); + tbt_data.device_mode = le16_to_cpu(st->intel_vid_status.device_mode); + tbt_data.enter_vdo = le16_to_cpu(st->intel_vid_status.enter_vdo); + cd321x->state.alt = cd321x->port_altmode_tbt; + cd321x->state.mode = TYPEC_TBT_MODE; + cd321x->state.data = &tbt_data; + typec_mux_set(cd321x->mux, &cd321x->state); + } else if (st->data_status & CD321X_DATA_STATUS_USB4_CONNECTION) { + struct enter_usb_data eusb_data; + + if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_MODE_USB4) + return; + + eusb_data.eudo = le32_to_cpu(st->usb4_status.eudo); + eusb_data.active_link_training = + !!(st->data_status & TPS_DATA_STATUS_ACTIVE_LINK_TRAIN); + + cd321x->state.alt = NULL; + cd321x->state.data = &eusb_data; + cd321x->state.mode = TYPEC_MODE_USB4; + typec_mux_set(cd321x->mux, &cd321x->state); + } else { + if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_STATE_USB) + return; + cd321x->state.alt = NULL; + cd321x->state.mode = TYPEC_STATE_USB; + cd321x->state.data = NULL; + typec_mux_set(cd321x->mux, &cd321x->state); + } + + /* Clear data since it's no longer used after typec_mux_set and points to the stack */ + cd321x->state.data = NULL; +} + +static void cd321x_update_work(struct work_struct *work) +{ + struct cd321x *cd321x = container_of(to_delayed_work(work), + struct cd321x, update_work); + struct tps6598x *tps = &cd321x->tps; + struct cd321x_status st; + + guard(mutex)(&tps->lock); + + st = cd321x->update_status; + cd321x->update_status.status_changed = 0; + + bool old_connected = !!tps->partner; + bool new_connected = st.status & TPS_STATUS_PLUG_PRESENT; + bool was_disconnected = st.status_changed & TPS_STATUS_PLUG_PRESENT; + + bool usb_connection = st.data_status & + (TPS_DATA_STATUS_USB2_CONNECTION | TPS_DATA_STATUS_USB3_CONNECTION); + + enum usb_role old_role = usb_role_switch_get_role(tps->role_sw); + enum usb_role new_role = USB_ROLE_NONE; + enum typec_pwr_opmode pwr_opmode = TYPEC_PWR_MODE_USB; + enum typec_orientation orientation = TYPEC_ORIENTATION_NONE; + + if (usb_connection) { + if (tps->data_status & TPS_DATA_STATUS_USB_DATA_ROLE) + new_role = USB_ROLE_DEVICE; + else + new_role = USB_ROLE_HOST; + } + + if (new_connected) { + pwr_opmode = TPS_POWER_STATUS_PWROPMODE(st.pwr_status); + orientation = TPS_STATUS_TO_UPSIDE_DOWN(st.status) ? + TYPEC_ORIENTATION_REVERSE : TYPEC_ORIENTATION_NORMAL; + } + + bool is_pd = pwr_opmode == TYPEC_PWR_MODE_PD; + bool partner_changed = old_connected && new_connected && + (was_disconnected || + (is_pd && memcmp(&st.partner_identity, + &cd321x->cur_partner_identity, sizeof(struct usb_pd_identity)))); + + /* If we are switching from an active role, transition to USB_ROLE_NONE first */ + if (old_role != USB_ROLE_NONE && (new_role != old_role || was_disconnected)) + usb_role_switch_set_role(tps->role_sw, USB_ROLE_NONE); + + /* Process partner disconnection or change */ + if (!new_connected || partner_changed) { + if (!IS_ERR(tps->partner)) + typec_unregister_partner(tps->partner); + tps->partner = NULL; + } + + /* If there was a disconnection, set PHY to off */ + if (!new_connected || was_disconnected) { + cd321x->state.alt = NULL; + cd321x->state.mode = TYPEC_STATE_SAFE; + cd321x->state.data = NULL; + typec_set_mode(tps->port, TYPEC_STATE_SAFE); + } + + /* Update Type-C properties */ + typec_set_pwr_opmode(tps->port, pwr_opmode); + typec_set_pwr_role(tps->port, TPS_STATUS_TO_TYPEC_PORTROLE(st.status)); + typec_set_vconn_role(tps->port, TPS_STATUS_TO_TYPEC_VCONN(st.status)); + typec_set_orientation(tps->port, orientation); + typec_set_data_role(tps->port, TPS_STATUS_TO_TYPEC_DATAROLE(st.status)); + power_supply_changed(tps->psy); + + /* If the plug is disconnected, we are done */ + if (!new_connected) + return; + + /* Set up partner if we were previously disconnected (or changed). */ + if (!tps->partner) { + struct typec_partner_desc desc; + + desc.usb_pd = is_pd; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ + desc.identity = NULL; + + if (desc.usb_pd) + desc.identity = &st.partner_identity; + + tps->partner = typec_register_partner(tps->port, &desc); + if (IS_ERR(tps->partner)) { + dev_warn(tps->dev, "%s: failed to register partner\n", __func__); + return; + } + + if (desc.identity) { + typec_partner_set_identity(tps->partner); + cd321x->cur_partner_identity = st.partner_identity; + } + } + + /* Update the TypeC MUX/PHY state */ + cd321x_typec_update_mode(tps, &st); + + /* Launch the USB role switch */ + usb_role_switch_set_role(tps->role_sw, new_role); + + power_supply_changed(tps->psy); +} + +static void cd321x_queue_status(struct cd321x *cd321x) +{ + cd321x->update_status.status_changed |= cd321x->update_status.status ^ cd321x->tps.status; + + cd321x->update_status.status = cd321x->tps.status; + cd321x->update_status.pwr_status = cd321x->tps.pwr_status; + cd321x->update_status.data_status = cd321x->tps.data_status; + + cd321x->update_status.partner_identity = cd321x->tps.partner_identity; + cd321x->update_status.dp_sid_status = cd321x->dp_sid_status; + cd321x->update_status.intel_vid_status = cd321x->intel_vid_status; + cd321x->update_status.usb4_status = cd321x->usb4_status; +} + +static int cd321x_connect(struct tps6598x *tps, u32 status) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + + tps->status = status; + cd321x_queue_status(cd321x); + + /* + * Cancel pending work if not already running, then requeue after CD321X_DEBOUNCE_DELAY_MS + * regardless since the work function will check for any plug or altmodes changes since + * its last run anyway. + */ + cancel_delayed_work(&cd321x->update_work); + schedule_delayed_work(&cd321x->update_work, msecs_to_jiffies(CD321X_DEBOUNCE_DELAY_MS)); + + return 0; +} + +static irqreturn_t cd321x_interrupt(int irq, void *data) +{ + struct tps6598x *tps = data; + u64 event = 0; + u32 status; + int ret; + + mutex_lock(&tps->lock); + + ret = tps6598x_read64(tps, TPS_REG_INT_EVENT1, &event); + if (ret) { + dev_err(tps->dev, "%s: failed to read events\n", __func__); + goto err_unlock; + } + trace_cd321x_irq(event); + + if (!event) + goto err_unlock; + + tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event); + + if (!tps6598x_read_status(tps, &status)) + goto err_unlock; + + if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) { + if (!tps->data->read_power_status(tps)) + goto err_unlock; + if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD) { + if (tps6598x_read_partner_identity(tps)) { + dev_err(tps->dev, "failed to read partner identity\n"); + tps->partner_identity = (struct usb_pd_identity) {0}; + } + } + } + + if (event & APPLE_CD_REG_INT_DATA_STATUS_UPDATE) + if (!tps->data->read_data_status(tps)) + goto err_unlock; + + /* Can be called uncondtionally since it will check for any changes itself */ + cd321x_connect(tps, status); + +err_unlock: + mutex_unlock(&tps->lock); + + if (event) + return IRQ_HANDLED; + return IRQ_NONE; +} + +static bool tps6598x_has_role_changed(struct tps6598x *tps, u32 status) +{ + status ^= tps->status; + + return status & (TPS_STATUS_PORTROLE | TPS_STATUS_DATAROLE); +} + +static irqreturn_t tps25750_interrupt(int irq, void *data) +{ + struct tps6598x *tps = data; + u64 event[2] = { }; + u32 status; + int ret; + + mutex_lock(&tps->lock); + + ret = tps6598x_block_read(tps, TPS_REG_INT_EVENT1, event, 11); + if (ret) { + dev_err(tps->dev, "%s: failed to read events\n", __func__); + goto err_unlock; + } + trace_tps25750_irq(event[0]); + + if (!(event[0] | event[1])) + goto err_unlock; + + if (!tps6598x_read_status(tps, &status)) + goto err_clear_ints; + + if (event[0] & TPS_REG_INT_POWER_STATUS_UPDATE) + if (!tps->data->read_power_status(tps)) + goto err_clear_ints; + + if (event[0] & TPS_REG_INT_DATA_STATUS_UPDATE) + if (!tps->data->read_data_status(tps)) + goto err_clear_ints; + + /* + * data/port roles could be updated independently after + * a plug event. Therefore, we need to check + * for pr/dr status change to set TypeC dr/pr accordingly. + */ + if (event[0] & TPS_REG_INT_PLUG_EVENT || + tps6598x_has_role_changed(tps, status)) + tps6598x_handle_plug_event(tps, status); + + tps->status = status; + +err_clear_ints: + tps6598x_block_write(tps, TPS_REG_INT_CLEAR1, event, 11); + +err_unlock: + mutex_unlock(&tps->lock); + + if (event[0] | event[1]) + return IRQ_HANDLED; + return IRQ_NONE; +} + +static irqreturn_t tps6598x_interrupt(int irq, void *data) +{ + int intev_len = TPS_65981_2_6_INTEVENT_LEN; + struct tps6598x *tps = data; + u64 event1[2] = { }; + u64 event2[2] = { }; + u32 version; + u32 status; + int ret; + + mutex_lock(&tps->lock); + + ret = tps6598x_read32(tps, TPS_REG_VERSION, &version); + if (ret) + dev_warn(tps->dev, "%s: failed to read version (%d)\n", + __func__, ret); + + if (TPS_VERSION_HW_VERSION(version) == TPS_VERSION_HW_65987_8_DH || + TPS_VERSION_HW_VERSION(version) == TPS_VERSION_HW_65987_8_DK) + intev_len = TPS_65987_8_INTEVENT_LEN; + + ret = tps6598x_block_read(tps, TPS_REG_INT_EVENT1, event1, intev_len); + + ret = tps6598x_block_read(tps, TPS_REG_INT_EVENT1, event1, intev_len); + if (ret) { + dev_err(tps->dev, "%s: failed to read event1\n", __func__); + goto err_unlock; + } + ret = tps6598x_block_read(tps, TPS_REG_INT_EVENT2, event2, intev_len); + if (ret) { + dev_err(tps->dev, "%s: failed to read event2\n", __func__); + goto err_unlock; + } + trace_tps6598x_irq(event1[0], event2[0]); + + if (!(event1[0] | event1[1] | event2[0] | event2[1])) + goto err_unlock; + + tps6598x_block_write(tps, TPS_REG_INT_CLEAR1, event1, intev_len); + tps6598x_block_write(tps, TPS_REG_INT_CLEAR2, event2, intev_len); + + if (!tps6598x_read_status(tps, &status)) + goto err_unlock; + + tps->status = status; + + if ((event1[0] | event2[0]) & TPS_REG_INT_POWER_STATUS_UPDATE) + if (!tps->data->read_power_status(tps)) + goto err_unlock; + + if ((event1[0] | event2[0]) & TPS_REG_INT_DATA_STATUS_UPDATE) + if (!tps->data->read_data_status(tps)) + goto err_unlock; + + /* + * Refresh power status before connect - needed for TPS66993 which + * synthesizes pwr_status from STATUS and never gets POWER_STATUS_UPDATE. + */ + if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) { + if (!tps->data->read_power_status(tps)) + goto err_unlock; + tps6598x_handle_plug_event(tps, status); + } + +err_unlock: + mutex_unlock(&tps->lock); + + if (event1[0] | event1[1] | event2[0] | event2[1]) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +/* Time interval for Polling */ +#define POLL_INTERVAL 500 /* msecs */ +static void tps6598x_poll_work(struct work_struct *work) +{ + struct tps6598x *tps = container_of(to_delayed_work(work), + struct tps6598x, wq_poll); + + tps->data->irq_handler(0, tps); + queue_delayed_work(system_power_efficient_wq, + &tps->wq_poll, msecs_to_jiffies(POLL_INTERVAL)); +} + +static int tps6598x_check_mode(struct tps6598x *tps) +{ + char mode[5] = { }; + int ret; + + ret = tps6598x_read32(tps, TPS_REG_MODE, (void *)mode); + if (ret) + return ret; + + ret = match_string(modes, ARRAY_SIZE(modes), mode); + + switch (ret) { + case TPS_MODE_APP: + case TPS_MODE_APP1: + case TPS_MODE_PTCH: + return ret; + case TPS_MODE_BOOT: + dev_warn(tps->dev, "dead-battery condition\n"); + return ret; + case TPS_MODE_BIST: + case TPS_MODE_DISC: + default: + dev_err(tps->dev, "controller in unsupported mode \"%s\"\n", + mode); + break; + } + + return -ENODEV; +} + +static const struct regmap_config tps6598x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x7F, +}; + +static int tps6598x_psy_get_online(struct tps6598x *tps, + union power_supply_propval *val) +{ + if (TPS_POWER_STATUS_CONNECTION(tps->pwr_status) && + TPS_POWER_STATUS_SOURCESINK(tps->pwr_status)) { + val->intval = 1; + } else { + val->intval = 0; + } + return 0; +} + +static int tps6598x_psy_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct tps6598x *tps = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_USB_TYPE: + if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD) + val->intval = POWER_SUPPLY_USB_TYPE_PD; + else + val->intval = POWER_SUPPLY_USB_TYPE_C; + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = tps6598x_psy_get_online(tps, val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int cd321x_switch_power_state(struct tps6598x *tps, u8 target_state) +{ + u8 state; + int ret; + + ret = tps6598x_read8(tps, TPS_REG_SYSTEM_POWER_STATE, &state); + if (ret) + return ret; + + if (state == target_state) + return 0; + + ret = tps6598x_exec_cmd(tps, "SSPS", sizeof(u8), &target_state, 0, NULL); + if (ret) + return ret; + + ret = tps6598x_read8(tps, TPS_REG_SYSTEM_POWER_STATE, &state); + if (ret) + return ret; + + if (state != target_state) + return -EINVAL; + + return 0; +} + +static int devm_tps6598_psy_register(struct tps6598x *tps) +{ + struct power_supply_config psy_cfg = {}; + const char *port_dev_name = dev_name(tps->dev); + char *psy_name; + + psy_cfg.drv_data = tps; + psy_cfg.fwnode = dev_fwnode(tps->dev); + + psy_name = devm_kasprintf(tps->dev, GFP_KERNEL, "%s%s", tps6598x_psy_name_prefix, + port_dev_name); + if (!psy_name) + return -ENOMEM; + + tps->psy_desc.name = psy_name; + tps->psy_desc.type = POWER_SUPPLY_TYPE_USB; + tps->psy_desc.usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD); + tps->psy_desc.properties = tps6598x_psy_props; + tps->psy_desc.num_properties = ARRAY_SIZE(tps6598x_psy_props); + tps->psy_desc.get_property = tps6598x_psy_get_prop; + + tps->usb_type = POWER_SUPPLY_USB_TYPE_C; + + tps->psy = devm_power_supply_register(tps->dev, &tps->psy_desc, + &psy_cfg); + return PTR_ERR_OR_ZERO(tps->psy); +} + +static int +tps6598x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) +{ + int ret; + u32 conf; + struct typec_capability typec_cap = { }; + + ret = tps6598x_read32(tps, TPS_REG_SYSTEM_CONF, &conf); + if (ret) + return ret; + + typec_cap.revision = USB_TYPEC_REV_1_2; + typec_cap.pd_revision = 0x200; + typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; + typec_cap.driver_data = tps; + typec_cap.ops = &tps6598x_ops; + typec_cap.fwnode = fwnode; + + switch (TPS_SYSCONF_PORTINFO(conf)) { + case TPS_PORTINFO_SINK_ACCESSORY: + case TPS_PORTINFO_SINK: + typec_cap.type = TYPEC_PORT_SNK; + typec_cap.data = TYPEC_PORT_UFP; + break; + case TPS_PORTINFO_DRP_UFP_DRD: + case TPS_PORTINFO_DRP_DFP_DRD: + typec_cap.type = TYPEC_PORT_DRP; + typec_cap.data = TYPEC_PORT_DRD; + break; + case TPS_PORTINFO_DRP_UFP: + typec_cap.type = TYPEC_PORT_DRP; + typec_cap.data = TYPEC_PORT_UFP; + break; + case TPS_PORTINFO_DRP_DFP: + typec_cap.type = TYPEC_PORT_DRP; + typec_cap.data = TYPEC_PORT_DFP; + break; + case TPS_PORTINFO_SOURCE: + typec_cap.type = TYPEC_PORT_SRC; + typec_cap.data = TYPEC_PORT_DFP; + break; + default: + return -ENODEV; + } + + tps->port = typec_register_port(tps->dev, &typec_cap); + if (IS_ERR(tps->port)) + return PTR_ERR(tps->port); + + return 0; +} + +static int cd321x_register_port_altmodes(struct cd321x *cd321x) +{ + struct typec_altmode_desc desc; + struct typec_altmode *amode; + + memset(&desc, 0, sizeof(desc)); + desc.svid = USB_TYPEC_DP_SID; + desc.mode = USB_TYPEC_DP_MODE; + desc.vdo = DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D)); + desc.vdo |= DP_CAP_DFP_D; + amode = typec_port_register_altmode(cd321x->tps.port, &desc); + if (IS_ERR(amode)) + return PTR_ERR(amode); + cd321x->port_altmode_dp = amode; + + memset(&desc, 0, sizeof(desc)); + desc.svid = USB_TYPEC_TBT_SID; + desc.mode = TYPEC_ANY_MODE; + amode = typec_port_register_altmode(cd321x->tps.port, &desc); + if (IS_ERR(amode)) { + typec_unregister_altmode(cd321x->port_altmode_dp); + cd321x->port_altmode_dp = NULL; + return PTR_ERR(amode); + } + cd321x->port_altmode_tbt = amode; + + return 0; +} + +static int +cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + int ret; + + INIT_DELAYED_WORK(&cd321x->update_work, cd321x_update_work); + + ret = tps6598x_register_port(tps, fwnode); + if (ret) + return ret; + + ret = cd321x_register_port_altmodes(cd321x); + if (ret) + goto err_unregister_port; + + cd321x->mux = fwnode_typec_mux_get(fwnode); + if (IS_ERR(cd321x->mux)) { + ret = PTR_ERR(cd321x->mux); + goto err_unregister_altmodes; + } + + cd321x->state.alt = NULL; + cd321x->state.mode = TYPEC_STATE_SAFE; + cd321x->state.data = NULL; + typec_set_mode(tps->port, TYPEC_STATE_SAFE); + + return 0; + +err_unregister_altmodes: + typec_unregister_altmode(cd321x->port_altmode_dp); + typec_unregister_altmode(cd321x->port_altmode_tbt); + cd321x->port_altmode_dp = NULL; + cd321x->port_altmode_tbt = NULL; +err_unregister_port: + typec_unregister_port(tps->port); + return ret; +} + +static void +tps6598x_unregister_port(struct tps6598x *tps) +{ + typec_unregister_port(tps->port); +} + +static void +cd321x_unregister_port(struct tps6598x *tps) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + + typec_mux_put(cd321x->mux); + cd321x->mux = NULL; + typec_unregister_altmode(cd321x->port_altmode_dp); + cd321x->port_altmode_dp = NULL; + typec_unregister_altmode(cd321x->port_altmode_tbt); + cd321x->port_altmode_tbt = NULL; + typec_unregister_port(tps->port); +} + +static int tps_request_firmware(struct tps6598x *tps, const struct firmware **fw, + const char **firmware_name) +{ + int ret; + + ret = device_property_read_string(tps->dev, "firmware-name", + firmware_name); + if (ret) + return ret; + + ret = request_firmware(fw, *firmware_name, tps->dev); + if (ret) { + dev_err(tps->dev, "failed to retrieve \"%s\"\n", *firmware_name); + return ret; + } + + if ((*fw)->size == 0) { + release_firmware(*fw); + ret = -EINVAL; + } + + return ret; +} + +static int +tps25750_write_firmware(struct tps6598x *tps, + u8 bpms_addr, const u8 *data, size_t len) +{ + struct i2c_client *client = to_i2c_client(tps->dev); + int ret; + u8 slave_addr; + int timeout; + + slave_addr = client->addr; + timeout = client->adapter->timeout; + + /* + * binary configuration size is around ~16Kbytes + * which might take some time to finish writing it + */ + client->adapter->timeout = msecs_to_jiffies(5000); + client->addr = bpms_addr; + + ret = regmap_raw_write(tps->regmap, data[0], &data[1], len - 1); + + client->addr = slave_addr; + client->adapter->timeout = timeout; + + return ret; +} + +static int +tps25750_exec_pbms(struct tps6598x *tps, u8 *in_data, size_t in_len) +{ + int ret; + u8 rc; + + ret = tps6598x_exec_cmd_tmo(tps, "PBMs", in_len, in_data, + sizeof(rc), &rc, 4000, 0); + if (ret) + return ret; + + switch (rc) { + case TPS_TASK_BPMS_INVALID_BUNDLE_SIZE: + dev_err(tps->dev, "%s: invalid fw size\n", __func__); + return -EINVAL; + case TPS_TASK_BPMS_INVALID_SLAVE_ADDR: + dev_err(tps->dev, "%s: invalid slave address\n", __func__); + return -EINVAL; + case TPS_TASK_BPMS_INVALID_TIMEOUT: + dev_err(tps->dev, "%s: timed out\n", __func__); + return -ETIMEDOUT; + default: + break; + } + + return 0; +} + +static int tps25750_abort_patch_process(struct tps6598x *tps) +{ + int ret; + + ret = tps6598x_exec_cmd(tps, "PBMe", 0, NULL, 0, NULL); + if (ret) + return ret; + + ret = tps6598x_check_mode(tps); + if (ret != TPS_MODE_PTCH) + dev_err(tps->dev, "failed to switch to \"PTCH\" mode\n"); + + return ret; +} + +static int tps25750_start_patch_burst_mode(struct tps6598x *tps) +{ + int ret; + const struct firmware *fw; + const char *firmware_name; + struct { + u32 fw_size; + u8 addr; + u8 timeout; + } __packed bpms_data; + u32 addr; + struct device_node *np = tps->dev->of_node; + + ret = tps_request_firmware(tps, &fw, &firmware_name); + if (ret) + return ret; + + ret = of_property_match_string(np, "reg-names", "patch-address"); + if (ret < 0) { + dev_err(tps->dev, "failed to get patch-address %d\n", ret); + goto release_fw; + } + + ret = of_property_read_u32_index(np, "reg", ret, &addr); + if (ret) + goto release_fw; + + if (addr == 0 || (addr >= 0x20 && addr <= 0x23)) { + dev_err(tps->dev, "wrong patch address %u\n", addr); + ret = -EINVAL; + goto release_fw; + } + + bpms_data.addr = (u8)addr; + bpms_data.fw_size = fw->size; + bpms_data.timeout = TPS_BUNDLE_TIMEOUT; + + ret = tps25750_exec_pbms(tps, (u8 *)&bpms_data, sizeof(bpms_data)); + if (ret) + goto release_fw; + + ret = tps25750_write_firmware(tps, bpms_data.addr, fw->data, fw->size); + if (ret) { + dev_err(tps->dev, "Failed to write patch %s of %zu bytes\n", + firmware_name, fw->size); + goto release_fw; + } + + /* + * A delay of 500us is required after the firmware is written + * based on pg.62 in tps6598x Host Interface Technical + * Reference Manual + * https://www.ti.com/lit/ug/slvuc05a/slvuc05a.pdf + */ + udelay(500); + +release_fw: + release_firmware(fw); + + return ret; +} + +static int tps25750_complete_patch_process(struct tps6598x *tps) +{ + int ret; + u8 out_data[40]; + u8 dummy[2] = { }; + + /* + * Without writing something to DATA_IN, this command would + * return an error + */ + ret = tps6598x_exec_cmd_tmo(tps, "PBMc", sizeof(dummy), dummy, + sizeof(out_data), out_data, 2000, 20); + if (ret) + return ret; + + if (out_data[TPS_PBMC_RC]) { + dev_err(tps->dev, + "%s: pbmc failed: %u\n", __func__, + out_data[TPS_PBMC_RC]); + return -EIO; + } + + if (out_data[TPS_PBMC_DPCS]) { + dev_err(tps->dev, + "%s: failed device patch complete status: %u\n", + __func__, out_data[TPS_PBMC_DPCS]); + return -EIO; + } + + return 0; +} + +static int tps25750_apply_patch(struct tps6598x *tps) +{ + int ret; + unsigned long timeout; + u64 status = 0; + + ret = tps6598x_block_read(tps, TPS_REG_BOOT_STATUS, &status, 5); + if (ret) + return ret; + /* + * Nothing to be done if the configuration + * is being loaded from EERPOM + */ + if (status & TPS_BOOT_STATUS_I2C_EEPROM_PRESENT) + goto wait_for_app; + + ret = tps25750_start_patch_burst_mode(tps); + if (ret) { + tps25750_abort_patch_process(tps); + return ret; + } + + ret = tps25750_complete_patch_process(tps); + if (ret) + return ret; + +wait_for_app: + timeout = jiffies + msecs_to_jiffies(1000); + + do { + ret = tps6598x_check_mode(tps); + if (ret < 0) + return ret; + + if (time_is_before_jiffies(timeout)) + return -ETIMEDOUT; + + } while (ret != TPS_MODE_APP); + + /* + * The dead battery flag may be triggered when the controller + * port is connected to a device that can source power and + * attempts to power up both the controller and the board it is on. + * To restore controller functionality, it is necessary to clear + * this flag + */ + if (status & TPS_BOOT_STATUS_DEAD_BATTERY_FLAG) { + ret = tps6598x_exec_cmd(tps, "DBfg", 0, NULL, 0, NULL); + if (ret) { + dev_err(tps->dev, "failed to clear dead battery %d\n", ret); + return ret; + } + } + + dev_info(tps->dev, "controller switched to \"APP\" mode\n"); + + return 0; +}; + +static int tps6598x_apply_patch(struct tps6598x *tps) +{ + u8 in = TPS_PTCS_CONTENT_DEV | TPS_PTCS_CONTENT_APP; + u8 out[TPS_MAX_LEN] = {0}; + size_t in_len = sizeof(in); + size_t copied_bytes = 0; + size_t bytes_left; + const struct firmware *fw; + const char *firmware_name; + int ret; + + ret = tps_request_firmware(tps, &fw, &firmware_name); + if (ret) + return ret; + + ret = tps6598x_exec_cmd(tps, "PTCs", in_len, &in, + TPS_PTCS_OUT_BYTES, out); + if (ret || out[TPS_PTCS_STATUS] == TPS_PTCS_STATUS_FAIL) { + if (!ret) + ret = -EBUSY; + dev_err(tps->dev, "Update start failed (%d)\n", ret); + goto release_fw; + } + + bytes_left = fw->size; + while (bytes_left) { + in_len = min(bytes_left, TPS_MAX_LEN); + ret = tps6598x_exec_cmd(tps, "PTCd", in_len, + fw->data + copied_bytes, + TPS_PTCD_OUT_BYTES, out); + if (ret || out[TPS_PTCD_TRANSFER_STATUS] || + out[TPS_PTCD_LOADING_STATE] == TPS_PTCD_LOAD_ERR) { + if (!ret) + ret = -EBUSY; + dev_err(tps->dev, "Patch download failed (%d)\n", ret); + goto release_fw; + } + copied_bytes += in_len; + bytes_left -= in_len; + } + + ret = tps6598x_exec_cmd(tps, "PTCc", 0, NULL, TPS_PTCC_OUT_BYTES, out); + if (ret || out[TPS_PTCC_DEV] || out[TPS_PTCC_APP]) { + if (!ret) + ret = -EBUSY; + dev_err(tps->dev, "Update completion failed (%d)\n", ret); + goto release_fw; + } + msleep(TPS_SETUP_MS); + dev_info(tps->dev, "Firmware update succeeded\n"); + +release_fw: + if (ret) { + dev_err(tps->dev, "Failed to write patch %s of %zu bytes\n", + firmware_name, fw->size); + } + release_firmware(fw); + + return ret; +} + +static int cd321x_init(struct tps6598x *tps) +{ + return 0; +} + +static int tps25750_init(struct tps6598x *tps) +{ + int ret; + + ret = tps->data->apply_patch(tps); + if (ret) + return ret; + + ret = tps6598x_write8(tps, TPS_REG_SLEEP_CONF, + TPS_SLEEP_CONF_SLEEP_MODE_ALLOWED); + if (ret) + dev_warn(tps->dev, + "%s: failed to enable sleep mode: %d\n", + __func__, ret); + + return 0; +} + +static int tps6598x_init(struct tps6598x *tps) +{ + return tps->data->apply_patch(tps); +} + +static int cd321x_reset(struct tps6598x *tps) +{ + return 0; +} + +static int tps25750_reset(struct tps6598x *tps) +{ + return tps6598x_exec_cmd_tmo(tps, "GAID", 0, NULL, 0, NULL, 2000, 0); +} + +static int tps6598x_reset(struct tps6598x *tps) +{ + return 0; +} + +static int +tps25750_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) +{ + struct typec_capability typec_cap = { }; + const char *data_role; + u8 pd_status; + int ret; + + ret = tps6598x_read8(tps, TPS_REG_PD_STATUS, &pd_status); + if (ret) + return ret; + + ret = fwnode_property_read_string(fwnode, "data-role", &data_role); + if (ret) { + dev_err(tps->dev, "data-role not found: %d\n", ret); + return ret; + } + + ret = typec_find_port_data_role(data_role); + if (ret < 0) { + dev_err(tps->dev, "unknown data-role: %s\n", data_role); + return ret; + } + + typec_cap.data = ret; + typec_cap.revision = USB_TYPEC_REV_1_3; + typec_cap.pd_revision = 0x300; + typec_cap.orientation_aware = true; + typec_cap.driver_data = tps; + typec_cap.ops = &tps6598x_ops; + typec_cap.fwnode = fwnode; + typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; + + switch (TPS_PD_STATUS_PORT_TYPE(pd_status)) { + case TPS_PD_STATUS_PORT_TYPE_SINK_SOURCE: + case TPS_PD_STATUS_PORT_TYPE_SOURCE_SINK: + typec_cap.type = TYPEC_PORT_DRP; + break; + case TPS_PD_STATUS_PORT_TYPE_SINK: + typec_cap.type = TYPEC_PORT_SNK; + break; + case TPS_PD_STATUS_PORT_TYPE_SOURCE: + typec_cap.type = TYPEC_PORT_SRC; + break; + default: + return -ENODEV; + } + + tps->port = typec_register_port(tps->dev, &typec_cap); + if (IS_ERR(tps->port)) + return PTR_ERR(tps->port); + + return 0; +} + +static void cd321x_remove(struct tps6598x *tps) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + + cancel_delayed_work_sync(&cd321x->update_work); +} + +static int tps6598x_probe(struct i2c_client *client) +{ + const struct tipd_data *data; + struct tps6598x *tps; + struct fwnode_handle *fwnode; + u32 status; + u32 vid = 0; + int ret; + + data = i2c_get_match_data(client); + if (!data) + return -EINVAL; + + tps = devm_kzalloc(&client->dev, data->tps_struct_size, GFP_KERNEL); + if (!tps) + return -ENOMEM; + + mutex_init(&tps->lock); + tps->dev = &client->dev; + tps->data = data; + + tps->reset = devm_gpiod_get_optional(tps->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(tps->reset)) + return dev_err_probe(tps->dev, PTR_ERR(tps->reset), + "failed to get reset GPIO\n"); + if (tps->reset) + msleep(TPS_SETUP_MS); + + tps->regmap = devm_regmap_init_i2c(client, &tps6598x_regmap_config); + if (IS_ERR(tps->regmap)) + return PTR_ERR(tps->regmap); + + if (!device_is_compatible(tps->dev, "ti,tps25750")) { + ret = tps6598x_read32(tps, TPS_REG_VID, &vid); + if (ret < 0 || !vid) { + dev_err(tps->dev, "failed to read vendor ID: %d, vid: %#x\n", + ret, vid); + return -ENODEV; + } + } + + /* + * Checking can the adapter handle SMBus protocol. If it can not, the + * driver needs to take care of block reads separately. + */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + tps->i2c_protocol = true; + + if (tps->data->switch_power_state) { + ret = tps->data->switch_power_state(tps, TPS_SYSTEM_POWER_STATE_S0); + if (ret) + return ret; + } + + /* Make sure the controller has application firmware running */ + ret = tps6598x_check_mode(tps); + if (ret < 0) + return ret; + + if (ret == TPS_MODE_PTCH) { + ret = tps->data->init(tps); + if (ret) + return ret; + } + + ret = tps6598x_write64(tps, TPS_REG_INT_MASK1, tps->data->irq_mask1); + if (ret) + goto err_reset_controller; + + if (!tps6598x_read_status(tps, &status)) { + ret = -ENODEV; + goto err_clear_mask; + } + + tps->status = status; + + /* + * This fwnode has a "compatible" property, but is never populated as a + * struct device. Instead we simply parse it to read the properties. + * This breaks fw_devlink=on. To maintain backward compatibility + * with existing DT files, we work around this by deleting any + * fwnode_links to/from this fwnode. + */ + fwnode = device_get_named_child_node(&client->dev, "connector"); + if (fwnode) + fw_devlink_purge_absent_suppliers(fwnode); + + tps->role_sw = fwnode_usb_role_switch_get(fwnode); + if (IS_ERR(tps->role_sw)) { + ret = PTR_ERR(tps->role_sw); + goto err_fwnode_put; + } + + ret = devm_tps6598_psy_register(tps); + if (ret) + goto err_role_put; + + ret = tps->data->register_port(tps, fwnode); + if (ret) + goto err_role_put; + + if (status & TPS_STATUS_PLUG_PRESENT) { + ret = -EINVAL; + if (!tps->data->read_power_status(tps)) + goto err_unregister_port; + if (!tps->data->read_data_status(tps)) + goto err_unregister_port; + ret = tps->data->connect(tps, status); + if (ret) + dev_err(&client->dev, "failed to register partner\n"); + } + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + tps->data->irq_handler, + IRQF_SHARED | IRQF_ONESHOT, + dev_name(&client->dev), tps); + } else { + dev_dbg(tps->dev, "no IRQ specified, using polling mode\n"); + INIT_DELAYED_WORK(&tps->wq_poll, tps6598x_poll_work); + queue_delayed_work(system_power_efficient_wq, &tps->wq_poll, + msecs_to_jiffies(POLL_INTERVAL)); + } + + if (ret) + goto err_disconnect; + + i2c_set_clientdata(client, tps); + fwnode_handle_put(fwnode); + + tps->wakeup = device_property_read_bool(tps->dev, "wakeup-source"); + if (tps->wakeup && client->irq) { + devm_device_init_wakeup(&client->dev); + enable_irq_wake(client->irq); + } + + return 0; + +err_disconnect: + tps6598x_disconnect(tps, 0); +err_unregister_port: + tps->data->unregister_port(tps); +err_role_put: + usb_role_switch_put(tps->role_sw); +err_fwnode_put: + fwnode_handle_put(fwnode); +err_clear_mask: + tps6598x_write64(tps, TPS_REG_INT_MASK1, 0); +err_reset_controller: + /* Reset PD controller to remove any applied patch */ + tps->data->reset(tps); + + return ret; +} + +static void tps6598x_remove(struct i2c_client *client) +{ + struct tps6598x *tps = i2c_get_clientdata(client); + + if (!client->irq) + cancel_delayed_work_sync(&tps->wq_poll); + else + devm_free_irq(tps->dev, client->irq, tps); + + if (tps->data->remove) + tps->data->remove(tps); + + tps6598x_disconnect(tps, 0); + tps->data->unregister_port(tps); + usb_role_switch_put(tps->role_sw); + + /* Reset PD controller to remove any applied patch */ + tps->data->reset(tps); + + if (tps->reset) + gpiod_set_value_cansleep(tps->reset, 1); +} + +static int __maybe_unused tps6598x_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tps6598x *tps = i2c_get_clientdata(client); + + if (tps->wakeup) { + disable_irq(client->irq); + enable_irq_wake(client->irq); + } else if (tps->reset) { + gpiod_set_value_cansleep(tps->reset, 1); + } + + if (!client->irq) + cancel_delayed_work_sync(&tps->wq_poll); + + return 0; +} + +static int __maybe_unused tps6598x_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tps6598x *tps = i2c_get_clientdata(client); + int ret; + + ret = tps6598x_check_mode(tps); + if (ret < 0) + return ret; + + if (ret == TPS_MODE_PTCH) { + ret = tps->data->init(tps); + if (ret) + return ret; + } + + if (tps->wakeup) { + disable_irq_wake(client->irq); + enable_irq(client->irq); + } else if (tps->reset) { + gpiod_set_value_cansleep(tps->reset, 0); + msleep(TPS_SETUP_MS); + } + + if (!client->irq) + queue_delayed_work(system_power_efficient_wq, &tps->wq_poll, + msecs_to_jiffies(POLL_INTERVAL)); + + return 0; +} + +static const struct dev_pm_ops tps6598x_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tps6598x_suspend, tps6598x_resume) +}; + +static const struct tipd_data cd321x_data = { + .irq_handler = cd321x_interrupt, + .irq_mask1 = APPLE_CD_REG_INT_POWER_STATUS_UPDATE | + APPLE_CD_REG_INT_DATA_STATUS_UPDATE | + APPLE_CD_REG_INT_PLUG_EVENT, + .tps_struct_size = sizeof(struct cd321x), + .remove = cd321x_remove, + .register_port = cd321x_register_port, + .unregister_port = cd321x_unregister_port, + .trace_data_status = trace_cd321x_data_status, + .trace_power_status = trace_tps6598x_power_status, + .trace_status = trace_tps6598x_status, + .init = cd321x_init, + .read_data_status = cd321x_read_data_status, + .read_power_status = tps6598x_read_power_status, + .reset = cd321x_reset, + .switch_power_state = cd321x_switch_power_state, + .connect = cd321x_connect, +}; + +static const struct tipd_data tps6598x_data = { + .irq_handler = tps6598x_interrupt, + .irq_mask1 = TPS_REG_INT_POWER_STATUS_UPDATE | + TPS_REG_INT_DATA_STATUS_UPDATE | + TPS_REG_INT_PLUG_EVENT, + .tps_struct_size = sizeof(struct tps6598x), + .register_port = tps6598x_register_port, + .unregister_port = tps6598x_unregister_port, + .trace_data_status = trace_tps6598x_data_status, + .trace_power_status = trace_tps6598x_power_status, + .trace_status = trace_tps6598x_status, + .apply_patch = tps6598x_apply_patch, + .init = tps6598x_init, + .read_data_status = tps6598x_read_data_status, + .read_power_status = tps6598x_read_power_status, + .reset = tps6598x_reset, + .connect = tps6598x_connect, +}; + +static const struct tipd_data tps66993_data = { + .irq_handler = tps6598x_interrupt, + .irq_mask1 = TPS_REG_INT_DATA_STATUS_UPDATE | + TPS_REG_INT_PLUG_EVENT, + .tps_struct_size = sizeof(struct tps6598x), + .register_port = tps6598x_register_port, + .unregister_port = tps6598x_unregister_port, + .trace_data_status = trace_tps6598x_data_status, + .trace_power_status = trace_tps6598x_power_status, + .trace_status = trace_tps6598x_status, + .apply_patch = tps6598x_apply_patch, + .init = tps6598x_init, + .read_data_status = tps6598x_read_data_status, + .read_power_status = tps66993_read_power_status, + .reset = tps6598x_reset, + .connect = tps6598x_connect, +}; + +static const struct tipd_data tps25750_data = { + .irq_handler = tps25750_interrupt, + .irq_mask1 = TPS_REG_INT_POWER_STATUS_UPDATE | + TPS_REG_INT_DATA_STATUS_UPDATE | + TPS_REG_INT_PLUG_EVENT, + .tps_struct_size = sizeof(struct tps6598x), + .register_port = tps25750_register_port, + .unregister_port = tps6598x_unregister_port, + .trace_data_status = trace_tps6598x_data_status, + .trace_power_status = trace_tps25750_power_status, + .trace_status = trace_tps25750_status, + .apply_patch = tps25750_apply_patch, + .init = tps25750_init, + .read_data_status = tps6598x_read_data_status, + .read_power_status = tps6598x_read_power_status, + .reset = tps25750_reset, + .connect = tps6598x_connect, +}; + +static const struct of_device_id tps6598x_of_match[] = { + { .compatible = "ti,tps6598x", &tps6598x_data}, + { .compatible = "ti,tps66993", &tps66993_data}, + { .compatible = "apple,cd321x", &cd321x_data}, + { .compatible = "ti,tps25750", &tps25750_data}, + {} +}; +MODULE_DEVICE_TABLE(of, tps6598x_of_match); + +static const struct i2c_device_id tps6598x_id[] = { + { .name = "tps6598x", .driver_data = (kernel_ulong_t)&tps6598x_data }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tps6598x_id); + +static struct i2c_driver tps6598x_i2c_driver = { + .driver = { + .name = "tps6598x", + .pm = &tps6598x_pm_ops, + .of_match_table = tps6598x_of_match, + }, + .probe = tps6598x_probe, + .remove = tps6598x_remove, + .id_table = tps6598x_id, +}; +module_i2c_driver(tps6598x_i2c_driver); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TI TPS6598x USB Power Delivery Controller Driver"); diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h new file mode 100644 index 000000000..d4140f4da --- /dev/null +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -0,0 +1,276 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Driver for TI TPS6598x USB Power Delivery controller family + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus + */ + +#include +#include + +#ifndef __TPS6598X_H__ +#define __TPS6598X_H__ + +#define TPS_FIELD_GET(_mask, _reg) ((typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask))) + +/* TPS_REG_STATUS bits */ +#define TPS_STATUS_PLUG_PRESENT BIT(0) +#define TPS_STATUS_PLUG_UPSIDE_DOWN BIT(4) +#define TPS_STATUS_TO_UPSIDE_DOWN(s) (!!((s) & TPS_STATUS_PLUG_UPSIDE_DOWN)) +#define TPS_STATUS_PORTROLE BIT(5) +#define TPS_STATUS_TO_TYPEC_PORTROLE(s) (!!((s) & TPS_STATUS_PORTROLE)) +#define TPS_STATUS_DATAROLE BIT(6) +#define TPS_STATUS_TO_TYPEC_DATAROLE(s) (!!((s) & TPS_STATUS_DATAROLE)) +#define TPS_STATUS_VCONN BIT(7) +#define TPS_STATUS_TO_TYPEC_VCONN(s) (!!((s) & TPS_STATUS_VCONN)) +#define TPS_STATUS_OVERCURRENT BIT(16) +#define TPS_STATUS_GOTO_MIN_ACTIVE BIT(26) +#define TPS_STATUS_BIST BIT(27) +#define TPS_STATUS_HIGH_VOLTAGE_WARNING BIT(28) +#define TPS_STATUS_HIGH_LOW_VOLTAGE_WARNING BIT(29) + +#define TPS_STATUS_CONN_STATE_MASK GENMASK(3, 1) +#define TPS_STATUS_CONN_STATE(x) TPS_FIELD_GET(TPS_STATUS_CONN_STATE_MASK, (x)) +#define TPS_STATUS_PP_5V0_SWITCH_MASK GENMASK(9, 8) +#define TPS_STATUS_PP_5V0_SWITCH(x) TPS_FIELD_GET(TPS_STATUS_PP_5V0_SWITCH_MASK, (x)) +#define TPS_STATUS_PP_HV_SWITCH_MASK GENMASK(11, 10) +#define TPS_STATUS_PP_HV_SWITCH(x) TPS_FIELD_GET(TPS_STATUS_PP_HV_SWITCH_MASK, (x)) +#define TPS_STATUS_PP_EXT_SWITCH_MASK GENMASK(13, 12) +#define TPS_STATUS_PP_EXT_SWITCH(x) TPS_FIELD_GET(TPS_STATUS_PP_EXT_SWITCH_MASK, (x)) +#define TPS_STATUS_PP_CABLE_SWITCH_MASK GENMASK(15, 14) +#define TPS_STATUS_PP_CABLE_SWITCH(x) TPS_FIELD_GET(TPS_STATUS_PP_CABLE_SWITCH_MASK, (x)) +#define TPS_STATUS_POWER_SOURCE_MASK GENMASK(19, 18) +#define TPS_STATUS_POWER_SOURCE(x) TPS_FIELD_GET(TPS_STATUS_POWER_SOURCE_MASK, (x)) +#define TPS_STATUS_VBUS_STATUS_MASK GENMASK(21, 20) +#define TPS_STATUS_VBUS_STATUS(x) TPS_FIELD_GET(TPS_STATUS_VBUS_STATUS_MASK, (x)) +#define TPS_STATUS_USB_HOST_PRESENT_MASK GENMASK(23, 22) +#define TPS_STATUS_USB_HOST_PRESENT(x) TPS_FIELD_GET(TPS_STATUS_USB_HOST_PRESENT_MASK, (x)) +#define TPS_STATUS_LEGACY_MASK GENMASK(25, 24) +#define TPS_STATUS_LEGACY(x) TPS_FIELD_GET(TPS_STATUS_LEGACY_MASK, (x)) + +#define TPS_STATUS_CONN_STATE_NO_CONN 0 +#define TPS_STATUS_CONN_STATE_DISABLED 1 +#define TPS_STATUS_CONN_STATE_AUDIO_CONN 2 +#define TPS_STATUS_CONN_STATE_DEBUG_CONN 3 +#define TPS_STATUS_CONN_STATE_NO_CONN_R_A 4 +#define TPS_STATUS_CONN_STATE_RESERVED 5 +#define TPS_STATUS_CONN_STATE_CONN_NO_R_A 6 +#define TPS_STATUS_CONN_STATE_CONN_WITH_R_A 7 + +#define TPS_STATUS_PP_SWITCH_STATE_DISABLED 0 +#define TPS_STATUS_PP_SWITCH_STATE_FAULT 1 +#define TPS_STATUS_PP_SWITCH_STATE_OUT 2 +#define TPS_STATUS_PP_SWITCH_STATE_IN 3 + +#define TPS_STATUS_POWER_SOURCE_UNKNOWN 0 +#define TPS_STATUS_POWER_SOURCE_VIN_3P3 1 +#define TPS_STATUS_POWER_SOURCE_DEAD_BAT 2 +#define TPS_STATUS_POWER_SOURCE_VBUS 3 + +#define TPS_STATUS_VBUS_STATUS_VSAFE0V 0 +#define TPS_STATUS_VBUS_STATUS_VSAFE5V 1 +#define TPS_STATUS_VBUS_STATUS_PD 2 +#define TPS_STATUS_VBUS_STATUS_FAULT 3 + +#define TPS_STATUS_USB_HOST_PRESENT_NO 0 +#define TPS_STATUS_USB_HOST_PRESENT_PD_NO_USB 1 +#define TPS_STATUS_USB_HOST_PRESENT_NO_PD 2 +#define TPS_STATUS_USB_HOST_PRESENT_PD_USB 3 + +#define TPS_STATUS_LEGACY_NO 0 +#define TPS_STATUS_LEGACY_SINK 1 +#define TPS_STATUS_LEGACY_SOURCE 2 + +/* TPS_REG_INT_* bits */ +#define TPS_REG_INT_USER_VID_ALT_MODE_OTHER_VDM BIT_ULL(27+32) +#define TPS_REG_INT_USER_VID_ALT_MODE_ATTN_VDM BIT_ULL(26+32) +#define TPS_REG_INT_USER_VID_ALT_MODE_EXIT BIT_ULL(25+32) +#define TPS_REG_INT_USER_VID_ALT_MODE_ENTERED BIT_ULL(24+32) +#define TPS_REG_INT_EXIT_MODES_COMPLETE BIT_ULL(20+32) +#define TPS_REG_INT_DISCOVER_MODES_COMPLETE BIT_ULL(19+32) +#define TPS_REG_INT_VDM_MSG_SENT BIT_ULL(18+32) +#define TPS_REG_INT_VDM_ENTERED_MODE BIT_ULL(17+32) +#define TPS_REG_INT_ERROR_UNABLE_TO_SOURCE BIT_ULL(14+32) +#define TPS_REG_INT_SRC_TRANSITION BIT_ULL(10+32) +#define TPS_REG_INT_ERROR_DISCHARGE_FAILED BIT_ULL(9+32) +#define TPS_REG_INT_ERROR_MESSAGE_DATA BIT_ULL(7+32) +#define TPS_REG_INT_ERROR_PROTOCOL_ERROR BIT_ULL(6+32) +#define TPS_REG_INT_ERROR_MISSING_GET_CAP_MESSAGE BIT_ULL(4+32) +#define TPS_REG_INT_ERROR_POWER_EVENT_OCCURRED BIT_ULL(3+32) +#define TPS_REG_INT_ERROR_CAN_PROVIDE_PWR_LATER BIT_ULL(2+32) +#define TPS_REG_INT_ERROR_CANNOT_PROVIDE_PWR BIT_ULL(1+32) +#define TPS_REG_INT_ERROR_DEVICE_INCOMPATIBLE BIT_ULL(0+32) +#define TPS_REG_INT_CMD2_COMPLETE BIT(31) +#define TPS_REG_INT_CMD1_COMPLETE BIT(30) +#define TPS_REG_INT_ADC_HIGH_THRESHOLD BIT(29) +#define TPS_REG_INT_ADC_LOW_THRESHOLD BIT(28) +#define TPS_REG_INT_PD_STATUS_UPDATE BIT(27) +#define TPS_REG_INT_STATUS_UPDATE BIT(26) +#define TPS_REG_INT_DATA_STATUS_UPDATE BIT(25) +#define TPS_REG_INT_POWER_STATUS_UPDATE BIT(24) +#define TPS_REG_INT_PP_SWITCH_CHANGED BIT(23) +#define TPS_REG_INT_HIGH_VOLTAGE_WARNING BIT(22) +#define TPS_REG_INT_USB_HOST_PRESENT_NO_LONGER BIT(21) +#define TPS_REG_INT_USB_HOST_PRESENT BIT(20) +#define TPS_REG_INT_GOTO_MIN_RECEIVED BIT(19) +#define TPS_REG_INT_PR_SWAP_REQUESTED BIT(17) +#define TPS_REG_INT_SINK_CAP_MESSAGE_READY BIT(15) +#define TPS_REG_INT_SOURCE_CAP_MESSAGE_READY BIT(14) +#define TPS_REG_INT_NEW_CONTRACT_AS_PROVIDER BIT(13) +#define TPS_REG_INT_NEW_CONTRACT_AS_CONSUMER BIT(12) +#define TPS_REG_INT_VDM_RECEIVED BIT(11) +#define TPS_REG_INT_ATTENTION_RECEIVED BIT(10) +#define TPS_REG_INT_OVERCURRENT BIT(9) +#define TPS_REG_INT_BIST BIT(8) +#define TPS_REG_INT_RDO_RECEIVED_FROM_SINK BIT(7) +#define TPS_REG_INT_DR_SWAP_COMPLETE BIT(5) +#define TPS_REG_INT_PR_SWAP_COMPLETE BIT(4) +#define TPS_REG_INT_PLUG_EVENT BIT(3) +#define TPS_REG_INT_HARD_RESET BIT(1) +#define TPS_REG_INT_PD_SOFT_RESET BIT(0) + +/* Apple-specific TPS_REG_INT_* bits */ +#define APPLE_CD_REG_INT_DATA_STATUS_UPDATE BIT(10) +#define APPLE_CD_REG_INT_POWER_STATUS_UPDATE BIT(9) +#define APPLE_CD_REG_INT_STATUS_UPDATE BIT(8) +#define APPLE_CD_REG_INT_PLUG_EVENT BIT(1) + +/* TPS_REG_SYSTEM_POWER_STATE states */ +#define TPS_SYSTEM_POWER_STATE_S0 0x00 +#define TPS_SYSTEM_POWER_STATE_S3 0x03 +#define TPS_SYSTEM_POWER_STATE_S4 0x04 +#define TPS_SYSTEM_POWER_STATE_S5 0x05 + +/* TPS_REG_POWER_STATUS bits (masks shared by TPS_FIELD_GET accessors and FIELD_PREP) */ +#define TPS_POWER_STATUS_CONNECTION_MASK BIT(0) +#define TPS_POWER_STATUS_SOURCESINK_MASK BIT(1) +#define TPS_POWER_STATUS_CONNECTION(x) \ + TPS_FIELD_GET(TPS_POWER_STATUS_CONNECTION_MASK, (x)) +#define TPS_POWER_STATUS_SOURCESINK(x) \ + TPS_FIELD_GET(TPS_POWER_STATUS_SOURCESINK_MASK, (x)) +#define TPS_POWER_STATUS_BC12_DET(x) TPS_FIELD_GET(BIT(2), (x)) + +#define TPS_POWER_STATUS_TYPEC_CURRENT_MASK GENMASK(3, 2) +#define TPS_POWER_STATUS_PWROPMODE(p) TPS_FIELD_GET(TPS_POWER_STATUS_TYPEC_CURRENT_MASK, (p)) +#define TPS_POWER_STATUS_BC12_STATUS_MASK GENMASK(6, 5) +#define TPS_POWER_STATUS_BC12_STATUS(p) TPS_FIELD_GET(TPS_POWER_STATUS_BC12_STATUS_MASK, (p)) + +#define TPS_POWER_STATUS_TYPEC_CURRENT_USB 0 +#define TPS_POWER_STATUS_TYPEC_CURRENT_1A5 1 +#define TPS_POWER_STATUS_TYPEC_CURRENT_3A0 2 +#define TPS_POWER_STATUS_TYPEC_CURRENT_PD 3 + +#define TPS_POWER_STATUS_BC12_STATUS_SDP 0 +#define TPS_POWER_STATUS_BC12_STATUS_CDP 2 +#define TPS_POWER_STATUS_BC12_STATUS_DCP 3 + +/* TPS25750_REG_POWER_STATUS bits */ +#define TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS_MASK GENMASK(7, 4) +#define TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS(p) \ + TPS_FIELD_GET(TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS_MASK, (p)) +#define TPS25750_POWER_STATUS_CHARGER_ADVERTISE_STATUS_MASK GENMASK(9, 8) +#define TPS25750_POWER_STATUS_CHARGER_ADVERTISE_STATUS(p) \ + TPS_FIELD_GET(TPS25750_POWER_STATUS_CHARGER_ADVERTISE_STATUS_MASK, (p)) + +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DISABLED 0 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_IN_PROGRESS 1 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_NONE 2 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_SPD 3 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_CPD 4 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_DPD 5 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_1_DCP 6 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_2_DCP 7 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_3_DCP 8 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_1_2V_DCP 9 + +/* TPS_REG_DATA_STATUS bits */ +#define TPS_DATA_STATUS_DATA_CONNECTION BIT(0) +#define TPS_DATA_STATUS_UPSIDE_DOWN BIT(1) +#define TPS_DATA_STATUS_ACTIVE_CABLE BIT(2) +#define TPS_DATA_STATUS_USB2_CONNECTION BIT(4) +#define TPS_DATA_STATUS_USB3_CONNECTION BIT(5) +#define TPS_DATA_STATUS_USB3_GEN2 BIT(6) +#define TPS_DATA_STATUS_USB_DATA_ROLE BIT(7) +#define TPS_DATA_STATUS_DP_CONNECTION BIT(8) +#define TPS_DATA_STATUS_DP_SINK BIT(9) +#define TPS_DATA_STATUS_TBT_CONNECTION BIT(16) +#define TPS_DATA_STATUS_TBT_TYPE BIT(17) +#define TPS_DATA_STATUS_OPTICAL_CABLE BIT(18) +#define TPS_DATA_STATUS_ACTIVE_LINK_TRAIN BIT(20) +#define TPS_DATA_STATUS_FORCE_LSX BIT(23) +#define TPS_DATA_STATUS_POWER_MISMATCH BIT(24) + +/* modified TPS_REG_DATA_STATUS bits for CD321x (and likely also TPS65987DDK) */ +#define CD321X_DATA_STATUS_HPD_IRQ BIT(14) +#define CD321X_DATA_STATUS_HPD_LEVEL BIT(15) +#define CD321X_DATA_STATUS_USB4_CONNECTION BIT(23) + +#define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK GENMASK(11, 10) +#define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT(x) \ + TPS_FIELD_GET(TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK, (x)) +#define TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK GENMASK(27, 25) +#define TPS_DATA_STATUS_TBT_CABLE_SPEED \ + TPS_FIELD_GET(TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK, (x)) +#define TPS_DATA_STATUS_TBT_CABLE_GEN_MASK GENMASK(29, 28) +#define TPS_DATA_STATUS_TBT_CABLE_GEN \ + TPS_FIELD_GET(TPS_DATA_STATUS_TBT_CABLE_GEN_MASK, (x)) + +/* Map data status to DP spec assignments */ +#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT(x) \ + ((TPS_DATA_STATUS_DP_PIN_ASSIGNMENT(x) << 1) | \ + TPS_FIELD_GET(TPS_DATA_STATUS_USB3_CONNECTION, (x))) +#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_E 0 +#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_F BIT(0) +#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_C BIT(1) +#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_D (BIT(1) | BIT(0)) +#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_A BIT(2) +#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_B (BIT(2) | BIT(1)) + +/* BOOT STATUS REG*/ +#define TPS_BOOT_STATUS_DEAD_BATTERY_FLAG BIT(2) +#define TPS_BOOT_STATUS_I2C_EEPROM_PRESENT BIT(3) + +/* PD STATUS REG */ +#define TPS_REG_PD_STATUS_PORT_TYPE_MASK GENMASK(5, 4) +#define TPS_PD_STATUS_PORT_TYPE(x) \ + TPS_FIELD_GET(TPS_REG_PD_STATUS_PORT_TYPE_MASK, x) + +#define TPS_PD_STATUS_PORT_TYPE_SINK_SOURCE 0 +#define TPS_PD_STATUS_PORT_TYPE_SINK 1 +#define TPS_PD_STATUS_PORT_TYPE_SOURCE 2 +#define TPS_PD_STATUS_PORT_TYPE_SOURCE_SINK 3 + +/* SLEEP CONF REG */ +#define TPS_SLEEP_CONF_SLEEP_MODE_ALLOWED BIT(0) + +/* Start Patch Download Sequence */ +#define TPS_PTCS_CONTENT_APP BIT(0) +#define TPS_PTCS_CONTENT_DEV BIT(1) +#define TPS_PTCS_OUT_BYTES 4 +#define TPS_PTCS_STATUS 1 + +#define TPS_PTCS_STATUS_FAIL 0x80 +/* Patch Download */ +#define TPS_PTCD_OUT_BYTES 10 +#define TPS_PTCD_TRANSFER_STATUS 1 +#define TPS_PTCD_LOADING_STATE 2 + +#define TPS_PTCD_LOAD_ERR 0x09 +/* Patch Download Complete */ +#define TPS_PTCC_OUT_BYTES 4 +#define TPS_PTCC_DEV 2 +#define TPS_PTCC_APP 3 + +/* Version Register */ +#define TPS_VERSION_HW_VERSION_MASK GENMASK(31, 24) +#define TPS_VERSION_HW_VERSION(x) TPS_FIELD_GET(TPS_VERSION_HW_VERSION_MASK, (x)) +#define TPS_VERSION_HW_65981_2_6 0x00 +#define TPS_VERSION_HW_65987_8_DH 0xF7 +#define TPS_VERSION_HW_65987_8_DK 0xF9 + +/* Int Event Register length */ +#define TPS_65981_2_6_INTEVENT_LEN 8 +#define TPS_65987_8_INTEVENT_LEN 11 + +#endif /* __TPS6598X_H__ */ diff --git a/drivers/usb/typec/tipd/trace.c b/drivers/usb/typec/tipd/trace.c new file mode 100644 index 000000000..016e68048 --- /dev/null +++ b/drivers/usb/typec/tipd/trace.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TI TPS6598x USB Power Delivery Controller Trace Support + * + * Copyright (C) 2021, Intel Corporation + * Author: Heikki Krogerus + */ +#define CREATE_TRACE_POINTS +#include "trace.h" diff --git a/drivers/usb/typec/tipd/trace.h b/drivers/usb/typec/tipd/trace.h new file mode 100644 index 000000000..e9e404251 --- /dev/null +++ b/drivers/usb/typec/tipd/trace.h @@ -0,0 +1,437 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Driver for TI TPS6598x USB Power Delivery controller family + * + * Copyright (C) 2020 Purism SPC + * Author: Guido Günther + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM tps6598x + +#if !defined(_TPS6598X_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _TPS6598X_TRACE_H_ + +#include "tps6598x.h" + +#include +#include +#include + +#define show_irq_flags(flags) \ + __print_flags_u64(flags, "|", \ + { TPS_REG_INT_PD_SOFT_RESET, "PD_SOFT_RESET" }, \ + { TPS_REG_INT_HARD_RESET, "HARD_RESET" }, \ + { TPS_REG_INT_PLUG_EVENT, "PLUG_EVENT" }, \ + { TPS_REG_INT_PR_SWAP_COMPLETE, "PR_SWAP_COMPLETE" }, \ + { TPS_REG_INT_DR_SWAP_COMPLETE, "DR_SWAP_COMPLETE" }, \ + { TPS_REG_INT_RDO_RECEIVED_FROM_SINK, "RDO_RECEIVED_FROM_SINK" }, \ + { TPS_REG_INT_BIST, "BIST" }, \ + { TPS_REG_INT_OVERCURRENT, "OVERCURRENT" }, \ + { TPS_REG_INT_ATTENTION_RECEIVED, "ATTENTION_RECEIVED" }, \ + { TPS_REG_INT_VDM_RECEIVED, "VDM_RECEIVED" }, \ + { TPS_REG_INT_NEW_CONTRACT_AS_CONSUMER, "NEW_CONTRACT_AS_CONSUMER" }, \ + { TPS_REG_INT_NEW_CONTRACT_AS_PROVIDER, "NEW_CONTRACT_AS_PROVIDER" }, \ + { TPS_REG_INT_SOURCE_CAP_MESSAGE_READY, "SOURCE_CAP_MESSAGE_READY" }, \ + { TPS_REG_INT_SINK_CAP_MESSAGE_READY, "SINK_CAP_MESSAGE_READY" }, \ + { TPS_REG_INT_PR_SWAP_REQUESTED, "PR_SWAP_REQUESTED" }, \ + { TPS_REG_INT_GOTO_MIN_RECEIVED, "GOTO_MIN_RECEIVED" }, \ + { TPS_REG_INT_USB_HOST_PRESENT, "USB_HOST_PRESENT" }, \ + { TPS_REG_INT_USB_HOST_PRESENT_NO_LONGER, "USB_HOST_PRESENT_NO_LONGER" }, \ + { TPS_REG_INT_HIGH_VOLTAGE_WARNING, "HIGH_VOLTAGE_WARNING" }, \ + { TPS_REG_INT_PP_SWITCH_CHANGED, "PP_SWITCH_CHANGED" }, \ + { TPS_REG_INT_POWER_STATUS_UPDATE, "POWER_STATUS_UPDATE" }, \ + { TPS_REG_INT_DATA_STATUS_UPDATE, "DATA_STATUS_UPDATE" }, \ + { TPS_REG_INT_STATUS_UPDATE, "STATUS_UPDATE" }, \ + { TPS_REG_INT_PD_STATUS_UPDATE, "PD_STATUS_UPDATE" }, \ + { TPS_REG_INT_ADC_LOW_THRESHOLD, "ADC_LOW_THRESHOLD" }, \ + { TPS_REG_INT_ADC_HIGH_THRESHOLD, "ADC_HIGH_THRESHOLD" }, \ + { TPS_REG_INT_CMD1_COMPLETE, "CMD1_COMPLETE" }, \ + { TPS_REG_INT_CMD2_COMPLETE, "CMD2_COMPLETE" }, \ + { TPS_REG_INT_ERROR_DEVICE_INCOMPATIBLE, "ERROR_DEVICE_INCOMPATIBLE" }, \ + { TPS_REG_INT_ERROR_CANNOT_PROVIDE_PWR, "ERROR_CANNOT_PROVIDE_PWR" }, \ + { TPS_REG_INT_ERROR_CAN_PROVIDE_PWR_LATER, "ERROR_CAN_PROVIDE_PWR_LATER" }, \ + { TPS_REG_INT_ERROR_POWER_EVENT_OCCURRED, "ERROR_POWER_EVENT_OCCURRED" }, \ + { TPS_REG_INT_ERROR_MISSING_GET_CAP_MESSAGE, "ERROR_MISSING_GET_CAP_MESSAGE" }, \ + { TPS_REG_INT_ERROR_PROTOCOL_ERROR, "ERROR_PROTOCOL_ERROR" }, \ + { TPS_REG_INT_ERROR_MESSAGE_DATA, "ERROR_MESSAGE_DATA" }, \ + { TPS_REG_INT_ERROR_DISCHARGE_FAILED, "ERROR_DISCHARGE_FAILED" }, \ + { TPS_REG_INT_SRC_TRANSITION, "SRC_TRANSITION" }, \ + { TPS_REG_INT_ERROR_UNABLE_TO_SOURCE, "ERROR_UNABLE_TO_SOURCE" }, \ + { TPS_REG_INT_VDM_ENTERED_MODE, "VDM_ENTERED_MODE" }, \ + { TPS_REG_INT_VDM_MSG_SENT, "VDM_MSG_SENT" }, \ + { TPS_REG_INT_DISCOVER_MODES_COMPLETE, "DISCOVER_MODES_COMPLETE" }, \ + { TPS_REG_INT_EXIT_MODES_COMPLETE, "EXIT_MODES_COMPLETE" }, \ + { TPS_REG_INT_USER_VID_ALT_MODE_ENTERED, "USER_VID_ALT_MODE_ENTERED" }, \ + { TPS_REG_INT_USER_VID_ALT_MODE_EXIT, "USER_VID_ALT_MODE_EXIT" }, \ + { TPS_REG_INT_USER_VID_ALT_MODE_ATTN_VDM, "USER_VID_ALT_MODE_ATTN_VDM" }, \ + { TPS_REG_INT_USER_VID_ALT_MODE_OTHER_VDM, "USER_VID_ALT_MODE_OTHER_VDM" }) + +#define show_cd321x_irq_flags(flags) \ + __print_flags_u64(flags, "|", \ + { APPLE_CD_REG_INT_PLUG_EVENT, "PLUG_EVENT" }, \ + { APPLE_CD_REG_INT_POWER_STATUS_UPDATE, "POWER_STATUS_UPDATE" }, \ + { APPLE_CD_REG_INT_DATA_STATUS_UPDATE, "DATA_STATUS_UPDATE" }, \ + { APPLE_CD_REG_INT_STATUS_UPDATE, "STATUS_UPDATE" }) + +#define show_tps25750_irq_flags(flags) \ + __print_flags_u64(flags, "|", \ + { TPS_REG_INT_PLUG_EVENT, "PLUG_EVENT" }, \ + { TPS_REG_INT_POWER_STATUS_UPDATE, "POWER_STATUS_UPDATE" }, \ + { TPS_REG_INT_STATUS_UPDATE, "STATUS_UPDATE" }, \ + { TPS_REG_INT_PD_STATUS_UPDATE, "PD_STATUS_UPDATE" }) + +#define TPS6598X_STATUS_FLAGS_MASK (GENMASK(31, 0) ^ (TPS_STATUS_CONN_STATE_MASK | \ + TPS_STATUS_PP_5V0_SWITCH_MASK | \ + TPS_STATUS_PP_HV_SWITCH_MASK | \ + TPS_STATUS_PP_EXT_SWITCH_MASK | \ + TPS_STATUS_PP_CABLE_SWITCH_MASK | \ + TPS_STATUS_POWER_SOURCE_MASK | \ + TPS_STATUS_VBUS_STATUS_MASK | \ + TPS_STATUS_USB_HOST_PRESENT_MASK | \ + TPS_STATUS_LEGACY_MASK)) + +#define TPS25750_STATUS_FLAGS_MASK (GENMASK(31, 0) ^ (TPS_STATUS_CONN_STATE_MASK | \ + GENMASK(19, 7) | \ + TPS_STATUS_VBUS_STATUS_MASK | \ + TPS_STATUS_USB_HOST_PRESENT_MASK | \ + TPS_STATUS_LEGACY_MASK | \ + BIT(26) | \ + GENMASK(31, 28))) + +#define show_status_conn_state(status) \ + __print_symbolic(TPS_STATUS_CONN_STATE((status)), \ + { TPS_STATUS_CONN_STATE_CONN_WITH_R_A, "conn-Ra" }, \ + { TPS_STATUS_CONN_STATE_CONN_NO_R_A, "conn-no-Ra" }, \ + { TPS_STATUS_CONN_STATE_NO_CONN_R_A, "no-conn-Ra" }, \ + { TPS_STATUS_CONN_STATE_DEBUG_CONN, "debug" }, \ + { TPS_STATUS_CONN_STATE_AUDIO_CONN, "audio" }, \ + { TPS_STATUS_CONN_STATE_DISABLED, "disabled" }, \ + { TPS_STATUS_CONN_STATE_NO_CONN, "no-conn" }) + +#define show_status_pp_switch_state(status) \ + __print_symbolic(status, \ + { TPS_STATUS_PP_SWITCH_STATE_IN, "in" }, \ + { TPS_STATUS_PP_SWITCH_STATE_OUT, "out" }, \ + { TPS_STATUS_PP_SWITCH_STATE_FAULT, "fault" }, \ + { TPS_STATUS_PP_SWITCH_STATE_DISABLED, "off" }) + +#define show_status_power_sources(status) \ + __print_symbolic(TPS_STATUS_POWER_SOURCE(status), \ + { TPS_STATUS_POWER_SOURCE_VBUS, "vbus" }, \ + { TPS_STATUS_POWER_SOURCE_VIN_3P3, "vin-3p3" }, \ + { TPS_STATUS_POWER_SOURCE_DEAD_BAT, "dead-battery" }, \ + { TPS_STATUS_POWER_SOURCE_UNKNOWN, "unknown" }) + +#define show_status_vbus_status(status) \ + __print_symbolic(TPS_STATUS_VBUS_STATUS(status), \ + { TPS_STATUS_VBUS_STATUS_VSAFE0V, "vSafe0V" }, \ + { TPS_STATUS_VBUS_STATUS_VSAFE5V, "vSafe5V" }, \ + { TPS_STATUS_VBUS_STATUS_PD, "pd" }, \ + { TPS_STATUS_VBUS_STATUS_FAULT, "fault" }) + +#define show_status_usb_host_present(status) \ + __print_symbolic(TPS_STATUS_USB_HOST_PRESENT(status), \ + { TPS_STATUS_USB_HOST_PRESENT_PD_USB, "pd-usb" }, \ + { TPS_STATUS_USB_HOST_PRESENT_NO_PD, "no-pd" }, \ + { TPS_STATUS_USB_HOST_PRESENT_PD_NO_USB, "pd-no-usb" }, \ + { TPS_STATUS_USB_HOST_PRESENT_NO, "no" }) + +#define show_status_legacy(status) \ + __print_symbolic(TPS_STATUS_LEGACY(status), \ + { TPS_STATUS_LEGACY_SOURCE, "source" }, \ + { TPS_STATUS_LEGACY_SINK, "sink" }, \ + { TPS_STATUS_LEGACY_NO, "no" }) + +#define show_status_flags(flags) \ + __print_flags((flags & TPS6598X_STATUS_FLAGS_MASK), "|", \ + { TPS_STATUS_PLUG_PRESENT, "PLUG_PRESENT" }, \ + { TPS_STATUS_PLUG_UPSIDE_DOWN, "UPSIDE_DOWN" }, \ + { TPS_STATUS_PORTROLE, "PORTROLE" }, \ + { TPS_STATUS_DATAROLE, "DATAROLE" }, \ + { TPS_STATUS_VCONN, "VCONN" }, \ + { TPS_STATUS_OVERCURRENT, "OVERCURRENT" }, \ + { TPS_STATUS_GOTO_MIN_ACTIVE, "GOTO_MIN_ACTIVE" }, \ + { TPS_STATUS_BIST, "BIST" }, \ + { TPS_STATUS_HIGH_VOLTAGE_WARNING, "HIGH_VOLTAGE_WARNING" }, \ + { TPS_STATUS_HIGH_LOW_VOLTAGE_WARNING, "HIGH_LOW_VOLTAGE_WARNING" }) + +#define show_tps25750_status_flags(flags) \ + __print_flags((flags & TPS25750_STATUS_FLAGS_MASK), "|", \ + { TPS_STATUS_PLUG_PRESENT, "PLUG_PRESENT" }, \ + { TPS_STATUS_PLUG_UPSIDE_DOWN, "UPSIDE_DOWN" }, \ + { TPS_STATUS_PORTROLE, "PORTROLE" }, \ + { TPS_STATUS_DATAROLE, "DATAROLE" }, \ + { TPS_STATUS_BIST, "BIST" }) + +#define show_power_status_source_sink(power_status) \ + __print_symbolic(TPS_POWER_STATUS_SOURCESINK(power_status), \ + { 1, "sink" }, \ + { 0, "source" }) + +#define show_power_status_typec_status(power_status) \ + __print_symbolic(TPS_POWER_STATUS_PWROPMODE(power_status), \ + { TPS_POWER_STATUS_TYPEC_CURRENT_PD, "pd" }, \ + { TPS_POWER_STATUS_TYPEC_CURRENT_3A0, "3.0A" }, \ + { TPS_POWER_STATUS_TYPEC_CURRENT_1A5, "1.5A" }, \ + { TPS_POWER_STATUS_TYPEC_CURRENT_USB, "usb" }) + +#define show_power_status_bc12_status(power_status) \ + __print_symbolic(TPS_POWER_STATUS_BC12_STATUS(power_status), \ + { TPS_POWER_STATUS_BC12_STATUS_DCP, "dcp" }, \ + { TPS_POWER_STATUS_BC12_STATUS_CDP, "cdp" }, \ + { TPS_POWER_STATUS_BC12_STATUS_SDP, "sdp" }) + +#define show_tps25750_power_status_charger_detect_status(power_status) \ + __print_symbolic(TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS(power_status), \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DISABLED, "disabled"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_IN_PROGRESS, "in progress"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_NONE, "none"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_SPD, "spd"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_CPD, "cpd"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_DPD, "dpd"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_1_DCP, "divider 1 dcp"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_2_DCP, "divider 2 dcp"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_3_DCP, "divider 3 dpc"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_1_2V_DCP, "1.2V dpc"}) + +#define TPS_DATA_STATUS_FLAGS_MASK (GENMASK(31, 0) ^ (TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK | \ + TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK | \ + TPS_DATA_STATUS_TBT_CABLE_GEN_MASK)) + +#define show_data_status_flags(data_status) \ + __print_flags(data_status & TPS_DATA_STATUS_FLAGS_MASK, "|", \ + { TPS_DATA_STATUS_DATA_CONNECTION, "DATA_CONNECTION" }, \ + { TPS_DATA_STATUS_UPSIDE_DOWN, "DATA_UPSIDE_DOWN" }, \ + { TPS_DATA_STATUS_ACTIVE_CABLE, "ACTIVE_CABLE" }, \ + { TPS_DATA_STATUS_USB2_CONNECTION, "USB2_CONNECTION" }, \ + { TPS_DATA_STATUS_USB3_CONNECTION, "USB3_CONNECTION" }, \ + { TPS_DATA_STATUS_USB3_GEN2, "USB3_GEN2" }, \ + { TPS_DATA_STATUS_USB_DATA_ROLE, "USB_DATA_ROLE" }, \ + { TPS_DATA_STATUS_DP_CONNECTION, "DP_CONNECTION" }, \ + { TPS_DATA_STATUS_DP_SINK, "DP_SINK" }, \ + { TPS_DATA_STATUS_TBT_CONNECTION, "TBT_CONNECTION" }, \ + { TPS_DATA_STATUS_TBT_TYPE, "TBT_TYPE" }, \ + { TPS_DATA_STATUS_OPTICAL_CABLE, "OPTICAL_CABLE" }, \ + { TPS_DATA_STATUS_ACTIVE_LINK_TRAIN, "ACTIVE_LINK_TRAIN" }, \ + { TPS_DATA_STATUS_FORCE_LSX, "FORCE_LSX" }, \ + { TPS_DATA_STATUS_POWER_MISMATCH, "POWER_MISMATCH" }) + +#define show_cd321x_data_status_flags(data_status) \ + __print_flags(data_status & TPS_DATA_STATUS_FLAGS_MASK, "|", \ + { TPS_DATA_STATUS_DATA_CONNECTION, "DATA_CONNECTION" }, \ + { TPS_DATA_STATUS_UPSIDE_DOWN, "DATA_UPSIDE_DOWN" }, \ + { TPS_DATA_STATUS_ACTIVE_CABLE, "ACTIVE_CABLE" }, \ + { TPS_DATA_STATUS_USB2_CONNECTION, "USB2_CONNECTION" }, \ + { TPS_DATA_STATUS_USB3_CONNECTION, "USB3_CONNECTION" }, \ + { TPS_DATA_STATUS_USB3_GEN2, "USB3_GEN2" }, \ + { TPS_DATA_STATUS_USB_DATA_ROLE, "USB_DATA_ROLE" }, \ + { TPS_DATA_STATUS_DP_CONNECTION, "DP_CONNECTION" }, \ + { TPS_DATA_STATUS_DP_SINK, "DP_SINK" }, \ + { CD321X_DATA_STATUS_HPD_IRQ, "HPD_IRQ" }, \ + { CD321X_DATA_STATUS_HPD_LEVEL, "HPD_LEVEL" }, \ + { TPS_DATA_STATUS_TBT_CONNECTION, "TBT_CONNECTION" }, \ + { TPS_DATA_STATUS_TBT_TYPE, "TBT_TYPE" }, \ + { TPS_DATA_STATUS_OPTICAL_CABLE, "OPTICAL_CABLE" }, \ + { TPS_DATA_STATUS_ACTIVE_LINK_TRAIN, "ACTIVE_LINK_TRAIN" }, \ + { CD321X_DATA_STATUS_USB4_CONNECTION, "USB4" }, \ + { TPS_DATA_STATUS_POWER_MISMATCH, "POWER_MISMATCH" }) + +#define show_data_status_dp_pin_assignment(data_status) \ + __print_symbolic(TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT(data_status), \ + { TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_E, "E" }, \ + { TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_F, "F" }, \ + { TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_C, "C" }, \ + { TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_D, "D" }, \ + { TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_A, "A" }, \ + { TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_B, "B" }) + +#define maybe_show_data_status_dp_pin_assignment(data_status) \ + (data_status & TPS_DATA_STATUS_DP_CONNECTION ? \ + show_data_status_dp_pin_assignment(data_status) : "") + +TRACE_EVENT(tps6598x_irq, + TP_PROTO(u64 event1, + u64 event2), + TP_ARGS(event1, event2), + + TP_STRUCT__entry( + __field(u64, event1) + __field(u64, event2) + ), + + TP_fast_assign( + __entry->event1 = event1; + __entry->event2 = event2; + ), + + TP_printk("event1=%s, event2=%s", + show_irq_flags(__entry->event1), + show_irq_flags(__entry->event2)) +); + +TRACE_EVENT(cd321x_irq, + TP_PROTO(u64 event), + TP_ARGS(event), + + TP_STRUCT__entry( + __field(u64, event) + ), + + TP_fast_assign( + __entry->event = event; + ), + + TP_printk("event=%s", + show_cd321x_irq_flags(__entry->event)) +); + +TRACE_EVENT(tps25750_irq, + TP_PROTO(u64 event), + TP_ARGS(event), + + TP_STRUCT__entry( + __field(u64, event) + ), + + TP_fast_assign( + __entry->event = event; + ), + + TP_printk("event=%s", show_tps25750_irq_flags(__entry->event)) +); + +TRACE_EVENT(tps6598x_status, + TP_PROTO(u32 status), + TP_ARGS(status), + + TP_STRUCT__entry( + __field(u32, status) + ), + + TP_fast_assign( + __entry->status = status; + ), + + TP_printk("conn: %s, pp_5v0: %s, pp_hv: %s, pp_ext: %s, pp_cable: %s, " + "pwr-src: %s, vbus: %s, usb-host: %s, legacy: %s, flags: %s", + show_status_conn_state(__entry->status), + show_status_pp_switch_state(TPS_STATUS_PP_5V0_SWITCH(__entry->status)), + show_status_pp_switch_state(TPS_STATUS_PP_HV_SWITCH(__entry->status)), + show_status_pp_switch_state(TPS_STATUS_PP_EXT_SWITCH(__entry->status)), + show_status_pp_switch_state(TPS_STATUS_PP_CABLE_SWITCH(__entry->status)), + show_status_power_sources(__entry->status), + show_status_vbus_status(__entry->status), + show_status_usb_host_present(__entry->status), + show_status_legacy(__entry->status), + show_status_flags(__entry->status) + ) +); + +TRACE_EVENT(tps25750_status, + TP_PROTO(u32 status), + TP_ARGS(status), + + TP_STRUCT__entry( + __field(u32, status) + ), + + TP_fast_assign( + __entry->status = status; + ), + + TP_printk("conn: %s, vbus: %s, usb-host: %s, legacy: %s, flags: %s", + show_status_conn_state(__entry->status), + show_status_vbus_status(__entry->status), + show_status_usb_host_present(__entry->status), + show_status_legacy(__entry->status), + show_tps25750_status_flags(__entry->status) + ) +); + +TRACE_EVENT(tps6598x_power_status, + TP_PROTO(u16 power_status), + TP_ARGS(power_status), + + TP_STRUCT__entry( + __field(u16, power_status) + ), + + TP_fast_assign( + __entry->power_status = power_status; + ), + + TP_printk("conn: %d, pwr-role: %s, typec: %s, bc: %s", + !!TPS_POWER_STATUS_CONNECTION(__entry->power_status), + show_power_status_source_sink(__entry->power_status), + show_power_status_typec_status(__entry->power_status), + show_power_status_bc12_status(__entry->power_status) + ) +); + +TRACE_EVENT(tps25750_power_status, + TP_PROTO(u16 power_status), + TP_ARGS(power_status), + + TP_STRUCT__entry( + __field(u16, power_status) + ), + + TP_fast_assign( + __entry->power_status = power_status; + ), + + TP_printk("conn: %d, pwr-role: %s, typec: %s, charger detect: %s", + !!TPS_POWER_STATUS_CONNECTION(__entry->power_status), + show_power_status_source_sink(__entry->power_status), + show_power_status_typec_status(__entry->power_status), + show_tps25750_power_status_charger_detect_status(__entry->power_status) + ) +); + +TRACE_EVENT(tps6598x_data_status, + TP_PROTO(u32 data_status), + TP_ARGS(data_status), + + TP_STRUCT__entry( + __field(u32, data_status) + ), + + TP_fast_assign( + __entry->data_status = data_status; + ), + + TP_printk("%s%s%s", + show_data_status_flags(__entry->data_status), + __entry->data_status & TPS_DATA_STATUS_DP_CONNECTION ? ", DP pinout " : "", + maybe_show_data_status_dp_pin_assignment(__entry->data_status) + ) +); + +TRACE_EVENT(cd321x_data_status, + TP_PROTO(u32 data_status), + TP_ARGS(data_status), + + TP_STRUCT__entry( + __field(u32, data_status) + ), + + TP_fast_assign( + __entry->data_status = data_status; + ), + + TP_printk("%s%s%s", + show_cd321x_data_status_flags(__entry->data_status), + __entry->data_status & TPS_DATA_STATUS_DP_CONNECTION ? ", DP pinout " : "", + maybe_show_data_status_dp_pin_assignment(__entry->data_status) + ) +); + +#endif /* _TPS6598X_TRACE_H_ */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#include diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig new file mode 100644 index 000000000..87dd992a4 --- /dev/null +++ b/drivers/usb/typec/ucsi/Kconfig @@ -0,0 +1,107 @@ +# SPDX-License-Identifier: GPL-2.0 + +config TYPEC_UCSI + tristate "USB Type-C Connector System Software Interface driver" + depends on !CPU_BIG_ENDIAN + depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH + select USB_COMMON if DEBUG_FS + help + USB Type-C Connector System Software Interface (UCSI) is a + specification for an interface that allows the operating system to + control the USB Type-C ports. On UCSI system the USB Type-C ports + function autonomously by default, but in order to get the status of + the ports and support basic operations like role swapping, the driver + is required. UCSI is available on most of the new Intel based systems + that are equipped with Embedded Controller and USB Type-C ports. + + UCSI specification does not define the interface method, so depending + on the platform, ACPI, PCI, I2C, etc. may be used. Therefore this + driver only provides the core part, and separate drivers are needed + for every supported interface method. + + The UCSI specification can be downloaded from: + https://www.intel.com/content/www/us/en/io/universal-serial-bus/usb-type-c-ucsi-spec.html + + To compile the driver as a module, choose M here: the module will be + called typec_ucsi. + +if TYPEC_UCSI + +config UCSI_CCG + tristate "UCSI Interface Driver for Cypress CCGx" + depends on I2C + help + This driver enables UCSI support on platforms that expose a + Cypress CCGx Type-C controller over I2C interface. + + To compile the driver as a module, choose M here: the module will be + called ucsi_ccg. + +config UCSI_ACPI + tristate "UCSI ACPI Interface Driver" + depends on ACPI + help + This driver enables UCSI support on platforms that expose UCSI + interface as ACPI device. On new Intel Atom based platforms starting + from Broxton SoCs and Core platforms stating from Skylake, UCSI is an + ACPI enumerated device. + + To compile the driver as a module, choose M here: the module will be + called ucsi_acpi + +config UCSI_STM32G0 + tristate "UCSI Interface Driver for STM32G0" + depends on I2C + help + This driver enables UCSI support on platforms that expose a STM32G0 + Type-C controller over I2C interface. + + To compile the driver as a module, choose M here: the module will be + called ucsi_stm32g0. + +config UCSI_PMIC_GLINK + tristate "UCSI Qualcomm PMIC GLINK Interface Driver" + depends on QCOM_PMIC_GLINK + help + This driver enables UCSI support on platforms that expose UCSI + interface as PMIC GLINK device. + + To compile the driver as a module, choose M here: the module will be + called ucsi_glink. + +config CROS_EC_UCSI + tristate "UCSI Driver for ChromeOS EC" + depends on MFD_CROS_EC_DEV + depends on CROS_USBPD_NOTIFY + default MFD_CROS_EC_DEV + help + This driver enables UCSI support for a ChromeOS EC. The EC is + expected to implement a PPM. + + To compile the driver as a module, choose M here: the module + will be called cros_ec_ucsi. + +config UCSI_LENOVO_YOGA_C630 + tristate "UCSI Interface Driver for Lenovo Yoga C630" + depends on EC_LENOVO_YOGA_C630 + depends on DRM || !DRM + select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF + help + This driver enables UCSI support on the Lenovo Yoga C630 laptop. + + To compile the driver as a module, choose M here: the module will be + called ucsi_yoga_c630. + +config UCSI_HUAWEI_GAOKUN + tristate "UCSI Interface Driver for Huawei Matebook E Go" + depends on EC_HUAWEI_GAOKUN + depends on DRM || !DRM + select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF + help + This driver enables UCSI support on the Huawei Matebook E Go tablet, + which is a sc8280xp-based 2-in-1 tablet. + + To compile the driver as a module, choose M here: the module will be + called ucsi_huawei_gaokun. + +endif diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile new file mode 100644 index 000000000..c7e38bf01 --- /dev/null +++ b/drivers/usb/typec/ucsi/Makefile @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS_trace.o := -I$(src) + +obj-$(CONFIG_TYPEC_UCSI) += typec_ucsi.o + +typec_ucsi-y := ucsi.o + +typec_ucsi-$(CONFIG_DEBUG_FS) += debugfs.o + +typec_ucsi-$(CONFIG_TRACING) += trace.o + +ifneq ($(CONFIG_POWER_SUPPLY),) + typec_ucsi-y += psy.o +endif + +ifneq ($(CONFIG_TYPEC_DP_ALTMODE),) + typec_ucsi-y += displayport.o +endif + +ifneq ($(CONFIG_TYPEC_TBT_ALTMODE),) + typec_ucsi-y += thunderbolt.o +endif + +obj-$(CONFIG_UCSI_ACPI) += ucsi_acpi.o +obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o +obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o +obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o +obj-$(CONFIG_CROS_EC_UCSI) += cros_ec_ucsi.o +obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o +obj-$(CONFIG_UCSI_HUAWEI_GAOKUN) += ucsi_huawei_gaokun.o diff --git a/drivers/usb/typec/ucsi/cros_ec_ucsi.c b/drivers/usb/typec/ucsi/cros_ec_ucsi.c new file mode 100644 index 000000000..c38eb678d --- /dev/null +++ b/drivers/usb/typec/ucsi/cros_ec_ucsi.c @@ -0,0 +1,387 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UCSI driver for ChromeOS EC + * + * Copyright 2024 Google LLC. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ucsi.h" + +/* + * Maximum size in bytes of a UCSI message between AP and EC + */ +#define MAX_EC_DATA_SIZE 256 + +/* + * Maximum time in milliseconds the cros_ec_ucsi driver + * will wait for a response to a command or and ack. + */ +#define WRITE_TMO_MS 5000 + +/* Number of times to attempt recovery from a write timeout before giving up. */ +#define WRITE_TMO_CTR_MAX 5 + +/* Delay between mode entry/exit attempts, ms */ +static const unsigned int mode_selection_delay = 1000; +/* Timeout for a mode entry attempt, ms */ +static const unsigned int mode_selection_timeout = 4000; + +struct cros_ucsi_data { + struct device *dev; + struct ucsi *ucsi; + + struct cros_ec_device *ec; + struct notifier_block nb; + struct work_struct work; + struct delayed_work write_tmo; + int tmo_counter; + + struct completion complete; + unsigned long flags; +}; + +static int cros_ucsi_read(struct ucsi *ucsi, unsigned int offset, void *val, + size_t val_len) +{ + struct cros_ucsi_data *udata = ucsi_get_drvdata(ucsi); + struct ec_params_ucsi_ppm_get req = { + .offset = offset, + .size = val_len, + }; + int ret; + + if (val_len > MAX_EC_DATA_SIZE) { + dev_err(udata->dev, "Can't read %zu bytes. Too big.\n", val_len); + return -EINVAL; + } + + ret = cros_ec_cmd(udata->ec, 0, EC_CMD_UCSI_PPM_GET, + &req, sizeof(req), val, val_len); + if (ret < 0) { + dev_warn(udata->dev, "Failed to send EC message UCSI_PPM_GET: error=%d\n", ret); + return ret; + } + return 0; +} + +static int cros_ucsi_read_version(struct ucsi *ucsi, u16 *version) +{ + return cros_ucsi_read(ucsi, UCSI_VERSION, version, sizeof(*version)); +} + +static int cros_ucsi_read_cci(struct ucsi *ucsi, u32 *cci) +{ + return cros_ucsi_read(ucsi, UCSI_CCI, cci, sizeof(*cci)); +} + +static int cros_ucsi_read_message_in(struct ucsi *ucsi, void *val, + size_t val_len) +{ + return cros_ucsi_read(ucsi, UCSI_MESSAGE_IN, val, val_len); +} + +static int cros_ucsi_async_control(struct ucsi *ucsi, u64 cmd) +{ + struct cros_ucsi_data *udata = ucsi_get_drvdata(ucsi); + u8 ec_buf[sizeof(struct ec_params_ucsi_ppm_set) + sizeof(cmd)]; + struct ec_params_ucsi_ppm_set *req = (struct ec_params_ucsi_ppm_set *) ec_buf; + int ret; + + req->offset = UCSI_CONTROL; + memcpy(req->data, &cmd, sizeof(cmd)); + ret = cros_ec_cmd(udata->ec, 0, EC_CMD_UCSI_PPM_SET, + req, sizeof(ec_buf), NULL, 0); + if (ret < 0) { + dev_warn(udata->dev, "Failed to send EC message UCSI_PPM_SET: error=%d\n", ret); + return ret; + } + return 0; +} + +static int cros_ucsi_sync_control(struct ucsi *ucsi, u64 cmd, u32 *cci, + void *data, size_t size, void *msg_out, + size_t msg_out_size) +{ + struct cros_ucsi_data *udata = ucsi_get_drvdata(ucsi); + int ret; + + ret = ucsi_sync_control_common(ucsi, cmd, cci, data, size, msg_out, + msg_out_size); + switch (ret) { + case -EBUSY: + /* EC may return -EBUSY if CCI.busy is set. + * Convert this to a timeout. + */ + case -ETIMEDOUT: + /* Schedule recovery attempt when we timeout + * or tried to send a command while still busy. + */ + cancel_delayed_work_sync(&udata->write_tmo); + schedule_delayed_work(&udata->write_tmo, + msecs_to_jiffies(WRITE_TMO_MS)); + break; + case 0: + /* Successful write. Cancel any pending recovery work. */ + cancel_delayed_work_sync(&udata->write_tmo); + break; + } + + return ret; +} + +static void cros_ucsi_add_partner_altmodes(struct ucsi_connector *con) +{ + if (!con->typec_cap.no_mode_control) + typec_mode_selection_start(con->partner, + mode_selection_delay, + mode_selection_timeout); +} + +static void cros_ucsi_remove_partner_altmodes(struct ucsi_connector *con) +{ + if (!con->typec_cap.no_mode_control) + typec_mode_selection_delete(con->partner); +} + +static const struct ucsi_operations cros_ucsi_ops = { + .read_version = cros_ucsi_read_version, + .read_cci = cros_ucsi_read_cci, + .poll_cci = cros_ucsi_read_cci, + .read_message_in = cros_ucsi_read_message_in, + .async_control = cros_ucsi_async_control, + .sync_control = cros_ucsi_sync_control, + .add_partner_altmodes = cros_ucsi_add_partner_altmodes, + .remove_partner_altmodes = cros_ucsi_remove_partner_altmodes, +}; + +static void cros_ucsi_work(struct work_struct *work) +{ + struct cros_ucsi_data *udata = container_of(work, struct cros_ucsi_data, work); + u32 cci; + + if (cros_ucsi_read_cci(udata->ucsi, &cci)) + return; + + ucsi_notify_common(udata->ucsi, cci); +} + +static void cros_ucsi_write_timeout(struct work_struct *work) +{ + struct cros_ucsi_data *udata = + container_of(work, struct cros_ucsi_data, write_tmo.work); + u32 cci; + u64 cmd; + + if (cros_ucsi_read(udata->ucsi, UCSI_CCI, &cci, sizeof(cci))) { + dev_err(udata->dev, + "Reading CCI failed; no write timeout recovery possible.\n"); + return; + } + + if (cci & UCSI_CCI_BUSY) { + udata->tmo_counter++; + + if (udata->tmo_counter <= WRITE_TMO_CTR_MAX) + schedule_delayed_work(&udata->write_tmo, + msecs_to_jiffies(WRITE_TMO_MS)); + else + dev_err(udata->dev, + "PPM unresponsive - too many write timeouts.\n"); + + return; + } + + /* No longer busy means we can reset our timeout counter. */ + udata->tmo_counter = 0; + + /* Need to ack previous command which may have timed out. */ + if (cci & UCSI_CCI_COMMAND_COMPLETE) { + cmd = UCSI_ACK_CC_CI | UCSI_ACK_COMMAND_COMPLETE; + cros_ucsi_async_control(udata->ucsi, cmd); + + /* Check again after a few seconds that the system has + * recovered to make sure our async write above was successful. + */ + schedule_delayed_work(&udata->write_tmo, + msecs_to_jiffies(WRITE_TMO_MS)); + return; + } + + /* We recovered from a previous timeout. Treat this as a recovery from + * suspend and call resume. + */ + ucsi_resume(udata->ucsi); +} + +static int cros_ucsi_event(struct notifier_block *nb, + unsigned long host_event, void *_notify) +{ + struct cros_ucsi_data *udata = container_of(nb, struct cros_ucsi_data, nb); + + if (host_event & PD_EVENT_INIT) { + /* Late init event received from ChromeOS EC. Treat this as a + * system resume to re-enable communication with the PPM. + */ + dev_dbg(udata->dev, "Late PD init received\n"); + ucsi_resume(udata->ucsi); + } + + if (host_event & PD_EVENT_PPM) { + dev_dbg(udata->dev, "UCSI notification received\n"); + flush_work(&udata->work); + schedule_work(&udata->work); + } + + return NOTIFY_OK; +} + +static void cros_ucsi_destroy(struct cros_ucsi_data *udata) +{ + cros_usbpd_unregister_notify(&udata->nb); + cancel_delayed_work_sync(&udata->write_tmo); + cancel_work_sync(&udata->work); + ucsi_destroy(udata->ucsi); +} + +static int cros_ucsi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ucsi_data *udata; + int ret; + + udata = devm_kzalloc(dev, sizeof(*udata), GFP_KERNEL); + if (!udata) + return -ENOMEM; + + /* ACPI and OF FW nodes for cros_ec_ucsi are children of the ChromeOS EC. If the + * cros_ec_ucsi device has an ACPI or OF FW node, its parent is the ChromeOS EC device. + * Platforms without a FW node for cros_ec_ucsi may add it as a subdevice of cros_ec_dev. + */ + udata->dev = dev; + if (is_acpi_device_node(dev->fwnode) || is_of_node(dev->fwnode)) + udata->ec = dev_get_drvdata(dev->parent); + else + udata->ec = ((struct cros_ec_dev *)dev_get_drvdata(dev->parent))->ec_dev; + + if (!udata->ec) + return dev_err_probe(dev, -ENODEV, "couldn't find parent EC device\n"); + + platform_set_drvdata(pdev, udata); + + INIT_WORK(&udata->work, cros_ucsi_work); + INIT_DELAYED_WORK(&udata->write_tmo, cros_ucsi_write_timeout); + init_completion(&udata->complete); + + udata->ucsi = ucsi_create(dev, &cros_ucsi_ops); + if (IS_ERR(udata->ucsi)) + return dev_err_probe(dev, PTR_ERR(udata->ucsi), "failed to allocate UCSI instance\n"); + + ucsi_set_drvdata(udata->ucsi, udata); + + udata->nb.notifier_call = cros_ucsi_event; + ret = cros_usbpd_register_notify(&udata->nb); + if (ret) { + dev_err_probe(dev, ret, "failed to register notifier\n"); + ucsi_destroy(udata->ucsi); + return ret; + } + + ret = ucsi_register(udata->ucsi); + if (ret) { + dev_err_probe(dev, ret, "failed to register UCSI\n"); + cros_ucsi_destroy(udata); + return ret; + } + + return 0; +} + +static void cros_ucsi_remove(struct platform_device *dev) +{ + struct cros_ucsi_data *udata = platform_get_drvdata(dev); + + ucsi_unregister(udata->ucsi); + cros_ucsi_destroy(udata); +} + +static int __maybe_unused cros_ucsi_suspend(struct device *dev) +{ + struct cros_ucsi_data *udata = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&udata->write_tmo); + cancel_work_sync(&udata->work); + + return 0; +} + +static void __maybe_unused cros_ucsi_complete(struct device *dev) +{ + struct cros_ucsi_data *udata = dev_get_drvdata(dev); + + ucsi_resume(udata->ucsi); +} + +/* + * UCSI protocol is also used on ChromeOS platforms which reply on + * cros_ec_lpc.c driver for communication with embedded controller (EC). + * On such platforms communication with the EC is not available until + * the .complete() callback of the cros_ec_lpc driver is executed. + * For this reason we delay ucsi_resume() until the .complete() stage + * otherwise UCSI SET_NOTIFICATION_ENABLE command will fail and we won't + * receive any UCSI notifications from the EC where PPM is implemented. + */ +static const struct dev_pm_ops cros_ucsi_pm_ops = { +#ifdef CONFIG_PM_SLEEP + .suspend = cros_ucsi_suspend, + .complete = cros_ucsi_complete, +#endif +}; + +static const struct platform_device_id cros_ucsi_id[] = { + { KBUILD_MODNAME, 0 }, + {} +}; +MODULE_DEVICE_TABLE(platform, cros_ucsi_id); + +static const struct acpi_device_id cros_ec_ucsi_acpi_device_ids[] = { + { "GOOG0021", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cros_ec_ucsi_acpi_device_ids); + +static const struct of_device_id cros_ucsi_of_match[] = { + { .compatible = "google,cros-ec-ucsi", }, + {} +}; +MODULE_DEVICE_TABLE(of, cros_ucsi_of_match); + +static struct platform_driver cros_ucsi_driver = { + .driver = { + .name = KBUILD_MODNAME, + .pm = &cros_ucsi_pm_ops, + .acpi_match_table = cros_ec_ucsi_acpi_device_ids, + .of_match_table = cros_ucsi_of_match, + }, + .id_table = cros_ucsi_id, + .probe = cros_ucsi_probe, + .remove = cros_ucsi_remove, +}; + +module_platform_driver(cros_ucsi_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("UCSI driver for ChromeOS EC"); diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c new file mode 100644 index 000000000..77a0dd75e --- /dev/null +++ b/drivers/usb/typec/ucsi/debugfs.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UCSI debugfs interface + * + * Copyright (C) 2023 Intel Corporation + * + * Authors: Rajaram Regupathy + * Gopal Saranya + */ +#include +#include +#include +#include +#include +#include + +#include + +#include "ucsi.h" + +static struct dentry *ucsi_debugfs_root; + +static int ucsi_cmd(void *data, u64 val) +{ + struct ucsi *ucsi = data; + int ret; + + memset(&ucsi->debugfs->response, 0, sizeof(ucsi->debugfs->response)); + ucsi->debugfs->status = 0; + + switch (UCSI_COMMAND(val)) { + case UCSI_SET_CCOM: + case UCSI_SET_UOR: + case UCSI_SET_PDR: + case UCSI_CONNECTOR_RESET: + case UCSI_SET_SINK_PATH: + case UCSI_SET_NEW_CAM: + case UCSI_SET_USB: + case UCSI_SET_POWER_LEVEL: + case UCSI_READ_POWER_LEVEL: + ret = ucsi_send_command(ucsi, val, NULL, 0); + break; + case UCSI_SET_PDOS: + ret = ucsi_write_message_out_command(ucsi, val, NULL, 0, + ucsi->debugfs->message_out, + UCSI_COMMAND_DATA_LEN(val)); + break; + case UCSI_GET_CAPABILITY: + case UCSI_GET_CONNECTOR_CAPABILITY: + case UCSI_GET_ALTERNATE_MODES: + case UCSI_GET_CAM_SUPPORTED: + case UCSI_GET_CURRENT_CAM: + case UCSI_GET_PDOS: + case UCSI_GET_CABLE_PROPERTY: + case UCSI_GET_CONNECTOR_STATUS: + case UCSI_GET_ERROR_STATUS: + case UCSI_GET_PD_MESSAGE: + case UCSI_GET_ATTENTION_VDO: + case UCSI_GET_CAM_CS: + case UCSI_GET_LPM_PPM_INFO: + ret = ucsi_send_command(ucsi, val, + &ucsi->debugfs->response, + sizeof(ucsi->debugfs->response)); + break; + default: + ret = -EOPNOTSUPP; + } + + if (ret < 0) { + ucsi->debugfs->status = ret; + return ret; + } + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ucsi_cmd_fops, NULL, ucsi_cmd, "0x%llx\n"); + +static int ucsi_resp_show(struct seq_file *s, void *not_used) +{ + struct ucsi *ucsi = s->private; + + if (ucsi->debugfs->status) + return ucsi->debugfs->status; + + seq_printf(s, "0x%016llx%016llx%016llx\n", ucsi->debugfs->response.ext, + ucsi->debugfs->response.high, ucsi->debugfs->response.low); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ucsi_resp); + +static int ucsi_peak_curr_show(struct seq_file *m, void *v) +{ + struct ucsi *ucsi = m->private; + + seq_printf(m, "%u mA\n", ucsi->connector->peak_current); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ucsi_peak_curr); + +static int ucsi_avg_curr_show(struct seq_file *m, void *v) +{ + struct ucsi *ucsi = m->private; + + seq_printf(m, "%u mA\n", ucsi->connector->avg_current); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ucsi_avg_curr); + +static int ucsi_vbus_volt_show(struct seq_file *m, void *v) +{ + struct ucsi *ucsi = m->private; + + seq_printf(m, "%u mV\n", ucsi->connector->vbus_voltage); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ucsi_vbus_volt); + +static ssize_t ucsi_message_out_write(struct file *file, + const char __user *data, size_t count, loff_t *ppos) +{ + struct ucsi *ucsi = file->private_data; + int ret; + + char *buf __free(kfree) = memdup_user_nul(data, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = hex2bin(ucsi->debugfs->message_out, buf, + min(count / 2, sizeof(ucsi->debugfs->message_out))); + if (ret) + return ret; + + return count; +} + +static const struct file_operations ucsi_message_out_fops = { + .open = simple_open, + .write = ucsi_message_out_write, + .llseek = generic_file_llseek, +}; + +void ucsi_debugfs_register(struct ucsi *ucsi) +{ + ucsi->debugfs = kzalloc_obj(*ucsi->debugfs); + if (!ucsi->debugfs) + return; + + ucsi->debugfs->dentry = debugfs_create_dir(dev_name(ucsi->dev), ucsi_debugfs_root); + debugfs_create_file("command", 0200, ucsi->debugfs->dentry, ucsi, &ucsi_cmd_fops); + debugfs_create_file("response", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_resp_fops); + debugfs_create_file("peak_current", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_peak_curr_fops); + debugfs_create_file("avg_current", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_avg_curr_fops); + debugfs_create_file("vbus_voltage", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_vbus_volt_fops); + debugfs_create_file("message_out", 0200, ucsi->debugfs->dentry, ucsi, + &ucsi_message_out_fops); +} + +void ucsi_debugfs_unregister(struct ucsi *ucsi) +{ + if (IS_ERR_OR_NULL(ucsi) || !ucsi->debugfs) + return; + + debugfs_remove_recursive(ucsi->debugfs->dentry); + kfree(ucsi->debugfs); + ucsi->debugfs = NULL; +} + +void ucsi_debugfs_init(void) +{ + ucsi_debugfs_root = debugfs_create_dir("ucsi", usb_debug_root); +} + +void ucsi_debugfs_exit(void) +{ + debugfs_remove(ucsi_debugfs_root); +} diff --git a/drivers/usb/typec/ucsi/displayport.c b/drivers/usb/typec/ucsi/displayport.c new file mode 100644 index 000000000..7067f2561 --- /dev/null +++ b/drivers/usb/typec/ucsi/displayport.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UCSI DisplayPort Alternate Mode Support + * + * Copyright (C) 2018, Intel Corporation + * Author: Heikki Krogerus + */ + +#include +#include + +#include "ucsi.h" + +#define UCSI_CMD_SET_NEW_CAM(_con_num_, _enter_, _cam_, _am_) \ + (UCSI_SET_NEW_CAM | ((_con_num_) << 16) | ((_enter_) << 23) | \ + ((_cam_) << 24) | ((u64)(_am_) << 32)) + +struct ucsi_dp { + struct typec_displayport_data data; + struct ucsi_connector *con; + struct typec_altmode *alt; + struct work_struct work; + int offset; + + bool override; + bool initialized; + + u32 header; + u32 *vdo_data; + u8 vdo_size; +}; + +/* + * Note. Alternate mode control is optional feature in UCSI. It means that even + * if the system supports alternate modes, the OS may not be aware of them. + * + * In most cases however, the OS will be able to see the supported alternate + * modes, but it may still not be able to configure them, not even enter or exit + * them. That is because UCSI defines alt mode details and alt mode "overriding" + * as separate options. + * + * In case alt mode details are supported, but overriding is not, the driver + * will still display the supported pin assignments and configuration, but any + * changes the user attempts to do will lead into failure with return value of + * -EOPNOTSUPP. + */ + +static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo) +{ + struct ucsi_dp *dp = typec_altmode_get_drvdata(alt); + struct ucsi *ucsi = dp->con->ucsi; + int svdm_version; + u64 command; + u8 cur = 0; + int ret; + + if (!ucsi_con_mutex_lock(dp->con)) + return -ENOTCONN; + + if (!dp->override && dp->initialized) { + const struct typec_altmode *p = typec_altmode_get_partner(alt); + + dev_warn(&p->dev, + "firmware doesn't support alternate mode overriding\n"); + ret = -EOPNOTSUPP; + goto err_unlock; + } + + command = UCSI_GET_CURRENT_CAM | UCSI_CONNECTOR_NUMBER(dp->con->num); + ret = ucsi_send_command(ucsi, command, &cur, sizeof(cur)); + if (ret < 0) { + if (ucsi->version > 0x0100) + goto err_unlock; + cur = 0xff; + } + + if (cur != 0xff) { + ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; + goto err_unlock; + } + + /* + * We can't send the New CAM command yet to the PPM as it needs the + * configuration value as well. Pretending that we have now entered the + * mode, and letting the alt mode driver continue. + */ + + svdm_version = typec_altmode_get_svdm_version(alt); + if (svdm_version < 0) { + ret = svdm_version; + goto err_unlock; + } + + dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, CMD_ENTER_MODE); + dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE); + dp->header |= VDO_CMDT(CMDT_RSP_ACK); + + dp->vdo_data = NULL; + dp->vdo_size = 1; + + schedule_work(&dp->work); + ret = 0; +err_unlock: + ucsi_con_mutex_unlock(dp->con); + + return ret; +} + +static int ucsi_displayport_exit(struct typec_altmode *alt) +{ + struct ucsi_dp *dp = typec_altmode_get_drvdata(alt); + int svdm_version; + u64 command; + int ret = 0; + + if (!ucsi_con_mutex_lock(dp->con)) + return -ENOTCONN; + + if (!dp->override) { + const struct typec_altmode *p = typec_altmode_get_partner(alt); + + dev_warn(&p->dev, + "firmware doesn't support alternate mode overriding\n"); + ret = -EOPNOTSUPP; + goto out_unlock; + } + + command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 0, dp->offset, 0); + ret = ucsi_send_command(dp->con->ucsi, command, NULL, 0); + if (ret < 0) + goto out_unlock; + + svdm_version = typec_altmode_get_svdm_version(alt); + if (svdm_version < 0) { + ret = svdm_version; + goto out_unlock; + } + + dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, CMD_EXIT_MODE); + dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE); + dp->header |= VDO_CMDT(CMDT_RSP_ACK); + + dp->vdo_data = NULL; + dp->vdo_size = 1; + + schedule_work(&dp->work); + +out_unlock: + ucsi_con_mutex_unlock(dp->con); + + return ret; +} + +/* + * We do not actually have access to the Status Update VDO, so we have to guess + * things. + */ +static int ucsi_displayport_status_update(struct ucsi_dp *dp) +{ + u32 cap = dp->alt->vdo; + + dp->data.status = DP_STATUS_ENABLED; + + /* + * If pin assignement D is supported, claiming always + * that Multi-function is preferred. + */ + if (DP_CAP_CAPABILITY(cap) & DP_CAP_UFP_D) { + dp->data.status |= DP_STATUS_CON_DFP_D; + + if (DP_CAP_UFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D)) + dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC; + } else { + dp->data.status |= DP_STATUS_CON_UFP_D; + + if (DP_CAP_DFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D)) + dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC; + } + + dp->vdo_data = &dp->data.status; + dp->vdo_size = 2; + + return 0; +} + +static int ucsi_displayport_configure(struct ucsi_dp *dp) +{ + u64 command; + + if (!dp->override) + return 0; + + command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 1, dp->offset, dp->data.conf); + + return ucsi_send_command(dp->con->ucsi, command, NULL, 0); +} + +static int ucsi_displayport_vdm(struct typec_altmode *alt, + u32 header, const u32 *data, int count) +{ + struct ucsi_dp *dp = typec_altmode_get_drvdata(alt); + int cmd_type = PD_VDO_CMDT(header); + int cmd = PD_VDO_CMD(header); + int svdm_version; + + if (!ucsi_con_mutex_lock(dp->con)) + return -ENOTCONN; + + if (!dp->override && dp->initialized) { + const struct typec_altmode *p = typec_altmode_get_partner(alt); + + dev_warn(&p->dev, + "firmware doesn't support alternate mode overriding\n"); + ucsi_con_mutex_unlock(dp->con); + return -EOPNOTSUPP; + } + + svdm_version = typec_altmode_get_svdm_version(alt); + if (svdm_version < 0) { + ucsi_con_mutex_unlock(dp->con); + return svdm_version; + } + + switch (cmd_type) { + case CMDT_INIT: + if (PD_VDO_SVDM_VER(header) < svdm_version) { + typec_partner_set_svdm_version(dp->con->partner, PD_VDO_SVDM_VER(header)); + svdm_version = PD_VDO_SVDM_VER(header); + } + + dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, cmd); + dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE); + + switch (cmd) { + case DP_CMD_STATUS_UPDATE: + if (ucsi_displayport_status_update(dp)) + dp->header |= VDO_CMDT(CMDT_RSP_NAK); + else + dp->header |= VDO_CMDT(CMDT_RSP_ACK); + break; + case DP_CMD_CONFIGURE: + if (count < 2) { + dp->header |= VDO_CMDT(CMDT_RSP_NAK); + break; + } + dp->data.conf = *data; + if (ucsi_displayport_configure(dp)) { + dp->header |= VDO_CMDT(CMDT_RSP_NAK); + } else { + dp->header |= VDO_CMDT(CMDT_RSP_ACK); + if (dp->initialized) + ucsi_altmode_update_active(dp->con); + else + dp->initialized = true; + } + break; + default: + dp->header |= VDO_CMDT(CMDT_RSP_ACK); + break; + } + + schedule_work(&dp->work); + break; + default: + break; + } + + ucsi_con_mutex_unlock(dp->con); + + return 0; +} + +static const struct typec_altmode_ops ucsi_displayport_ops = { + .enter = ucsi_displayport_enter, + .exit = ucsi_displayport_exit, + .vdm = ucsi_displayport_vdm, +}; + +static void ucsi_displayport_work(struct work_struct *work) +{ + struct ucsi_dp *dp = container_of(work, struct ucsi_dp, work); + int ret; + + ret = typec_altmode_vdm(dp->alt, dp->header, + dp->vdo_data, dp->vdo_size); + if (ret) + dev_err(&dp->alt->dev, "VDM 0x%x failed\n", dp->header); + + dp->vdo_data = NULL; + dp->vdo_size = 0; + dp->header = 0; +} + +void ucsi_displayport_remove_partner(struct typec_altmode *alt) +{ + struct ucsi_dp *dp; + + if (!alt) + return; + + dp = typec_altmode_get_drvdata(alt); + if (!dp) + return; + + cancel_work_sync(&dp->work); + + dp->data.conf = 0; + dp->data.status = 0; + dp->initialized = false; +} + +struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con, + bool override, int offset, + struct typec_altmode_desc *desc) +{ + u8 all_assignments = BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) | + BIT(DP_PIN_ASSIGN_E); + struct typec_altmode *alt; + struct ucsi_dp *dp; + + /* We can't rely on the firmware with the capabilities. */ + desc->vdo |= DP_CAP_DP_SIGNALLING(0) | DP_CAP_RECEPTACLE; + + /* Claiming that we support all pin assignments */ + desc->vdo |= all_assignments << 8; + desc->vdo |= all_assignments << 16; + + alt = typec_port_register_altmode(con->port, desc); + if (IS_ERR(alt)) + return alt; + + dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL); + if (!dp) { + typec_unregister_altmode(alt); + return ERR_PTR(-ENOMEM); + } + + INIT_WORK(&dp->work, ucsi_displayport_work); + dp->override = override; + dp->offset = offset; + dp->con = con; + dp->alt = alt; + + typec_altmode_set_ops(alt, &ucsi_displayport_ops); + typec_altmode_set_drvdata(alt, dp); + + return alt; +} diff --git a/drivers/usb/typec/ucsi/psy.c b/drivers/usb/typec/ucsi/psy.c new file mode 100644 index 000000000..c8ebab8ba --- /dev/null +++ b/drivers/usb/typec/ucsi/psy.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Power Supply for UCSI + * + * Copyright (C) 2020, Intel Corporation + * Author: K V, Abhilash + * Author: Heikki Krogerus + */ + +#include +#include + +#include "ucsi.h" + +/* Power Supply access to expose source power information */ +enum ucsi_psy_online_states { + UCSI_PSY_OFFLINE = 0, + UCSI_PSY_FIXED_ONLINE, + UCSI_PSY_PROG_ONLINE, +}; + +static enum power_supply_property ucsi_psy_props[] = { + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_STATUS, +}; + +static int ucsi_psy_get_scope(struct ucsi_connector *con, + union power_supply_propval *val) +{ + u8 scope = POWER_SUPPLY_SCOPE_UNKNOWN; + struct device *dev = con->ucsi->dev; + + device_property_read_u8(dev, "scope", &scope); + if (scope == POWER_SUPPLY_SCOPE_UNKNOWN) { + u32 mask = UCSI_CAP_ATTR_POWER_AC_SUPPLY | + UCSI_CAP_ATTR_BATTERY_CHARGING; + + if (con->ucsi->cap.attributes & mask) + scope = POWER_SUPPLY_SCOPE_SYSTEM; + else + scope = POWER_SUPPLY_SCOPE_DEVICE; + } + val->intval = scope; + return 0; +} + +static int ucsi_psy_get_status(struct ucsi_connector *con, + union power_supply_propval *val) +{ + bool is_sink = UCSI_CONSTAT(con, PWR_DIR) == TYPEC_SINK; + bool sink_path_enabled = true; + + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + + if (con->ucsi->version >= UCSI_VERSION_2_0) + sink_path_enabled = + UCSI_CONSTAT(con, SINK_PATH_STATUS_V2_0) == + UCSI_CONSTAT_SINK_PATH_ENABLED; + + if (UCSI_CONSTAT(con, CONNECTED)) { + if (is_sink && sink_path_enabled) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (!is_sink) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } + + return 0; +} + +static int ucsi_psy_get_online(struct ucsi_connector *con, + union power_supply_propval *val) +{ + val->intval = UCSI_PSY_OFFLINE; + if (UCSI_CONSTAT(con, CONNECTED) && + (UCSI_CONSTAT(con, PWR_DIR) == TYPEC_SINK)) + val->intval = UCSI_PSY_FIXED_ONLINE; + return 0; +} + +static int ucsi_psy_get_voltage_min(struct ucsi_connector *con, + union power_supply_propval *val) +{ + u32 pdo; + + switch (UCSI_CONSTAT(con, PWR_OPMODE)) { + case UCSI_CONSTAT_PWR_OPMODE_PD: + pdo = con->src_pdos[0]; + val->intval = pdo_fixed_voltage(pdo) * 1000; + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: + case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: + case UCSI_CONSTAT_PWR_OPMODE_BC: + case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: + val->intval = UCSI_TYPEC_VSAFE5V * 1000; + break; + default: + val->intval = 0; + break; + } + return 0; +} + +static int ucsi_psy_get_voltage_max(struct ucsi_connector *con, + union power_supply_propval *val) +{ + u32 pdo; + int max_voltage = 0; + + switch (UCSI_CONSTAT(con, PWR_OPMODE)) { + case UCSI_CONSTAT_PWR_OPMODE_PD: + for (int i = 0; i < con->num_pdos; i++) { + int pdo_voltage = 0; + + pdo = con->src_pdos[i]; + if (pdo_type(pdo) == PDO_TYPE_FIXED) + pdo_voltage = pdo_fixed_voltage(pdo) * 1000; + max_voltage = (pdo_voltage > max_voltage) ? pdo_voltage + : max_voltage; + } + val->intval = max_voltage; + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: + case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: + case UCSI_CONSTAT_PWR_OPMODE_BC: + case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: + val->intval = UCSI_TYPEC_VSAFE5V * 1000; + break; + default: + val->intval = 0; + break; + } + return 0; +} + +static int ucsi_psy_get_voltage_now(struct ucsi_connector *con, + union power_supply_propval *val) +{ + int index; + u32 pdo; + + switch (UCSI_CONSTAT(con, PWR_OPMODE)) { + case UCSI_CONSTAT_PWR_OPMODE_PD: + index = rdo_index(con->rdo); + if (index > 0) { + pdo = con->src_pdos[index - 1]; + val->intval = pdo_fixed_voltage(pdo) * 1000; + } else { + val->intval = 0; + } + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: + case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: + case UCSI_CONSTAT_PWR_OPMODE_BC: + case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: + val->intval = UCSI_TYPEC_VSAFE5V * 1000; + break; + default: + val->intval = 0; + break; + } + return 0; +} + +static int ucsi_psy_get_current_max(struct ucsi_connector *con, + union power_supply_propval *val) +{ + u32 pdo; + int max_current = 0; + + if (!UCSI_CONSTAT(con, CONNECTED)) { + val->intval = 0; + return 0; + } + + switch (UCSI_CONSTAT(con, PWR_OPMODE)) { + case UCSI_CONSTAT_PWR_OPMODE_PD: + for (int i = 0; i < con->num_pdos; i++) { + int pdo_current = 0; + + pdo = con->src_pdos[i]; + if (pdo_type(pdo) == PDO_TYPE_FIXED) + pdo_current = pdo_max_current(pdo) * 1000; + max_current = (pdo_current > max_current) ? pdo_current + : max_current; + } + val->intval = max_current; + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: + val->intval = UCSI_TYPEC_1_5_CURRENT * 1000; + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: + val->intval = UCSI_TYPEC_3_0_CURRENT * 1000; + break; + case UCSI_CONSTAT_PWR_OPMODE_BC: + case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: + /* UCSI can't tell b/w DCP/CDP or USB2/3x1/3x2 SDP chargers */ + default: + val->intval = UCSI_TYPEC_DEFAULT_CURRENT * 1000; + break; + } + return 0; +} + +static int ucsi_psy_get_current_now(struct ucsi_connector *con, + union power_supply_propval *val) +{ + if (!UCSI_CONSTAT(con, CONNECTED)) { + val->intval = 0; + return 0; + } + + switch (UCSI_CONSTAT(con, PWR_OPMODE)) { + case UCSI_CONSTAT_PWR_OPMODE_PD: + val->intval = rdo_op_current(con->rdo) * 1000; + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: + val->intval = UCSI_TYPEC_1_5_CURRENT * 1000; + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: + val->intval = UCSI_TYPEC_3_0_CURRENT * 1000; + break; + case UCSI_CONSTAT_PWR_OPMODE_BC: + case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: + /* UCSI can't tell b/w DCP/CDP or USB2/3x1/3x2 SDP chargers */ + default: + val->intval = UCSI_TYPEC_DEFAULT_CURRENT * 1000; + break; + } + return 0; +} + +static int ucsi_psy_get_usb_type(struct ucsi_connector *con, + union power_supply_propval *val) +{ + val->intval = POWER_SUPPLY_USB_TYPE_C; + if (UCSI_CONSTAT(con, CONNECTED) && + UCSI_CONSTAT(con, PWR_OPMODE) == UCSI_CONSTAT_PWR_OPMODE_PD) + val->intval = POWER_SUPPLY_USB_TYPE_PD; + + return 0; +} + +static int ucsi_psy_get_charge_type(struct ucsi_connector *con, union power_supply_propval *val) +{ + if (!(UCSI_CONSTAT(con, CONNECTED))) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + return 0; + } + + /* The Battery Charging Cabability Status field is only valid in sink role. */ + if (UCSI_CONSTAT(con, PWR_DIR) != TYPEC_SINK) { + val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + return 0; + } + + switch (UCSI_CONSTAT(con, BC_STATUS)) { + case UCSI_CONSTAT_BC_NOMINAL_CHARGING: + val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD; + break; + case UCSI_CONSTAT_BC_SLOW_CHARGING: + case UCSI_CONSTAT_BC_TRICKLE_CHARGING: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + default: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + } + + return 0; +} + +static int ucsi_psy_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ucsi_connector *con = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + return ucsi_psy_get_charge_type(con, val); + case POWER_SUPPLY_PROP_USB_TYPE: + return ucsi_psy_get_usb_type(con, val); + case POWER_SUPPLY_PROP_ONLINE: + return ucsi_psy_get_online(con, val); + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + return ucsi_psy_get_voltage_min(con, val); + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + return ucsi_psy_get_voltage_max(con, val); + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return ucsi_psy_get_voltage_now(con, val); + case POWER_SUPPLY_PROP_CURRENT_MAX: + return ucsi_psy_get_current_max(con, val); + case POWER_SUPPLY_PROP_CURRENT_NOW: + return ucsi_psy_get_current_now(con, val); + case POWER_SUPPLY_PROP_SCOPE: + return ucsi_psy_get_scope(con, val); + case POWER_SUPPLY_PROP_STATUS: + return ucsi_psy_get_status(con, val); + default: + return -EINVAL; + } +} + +int ucsi_register_port_psy(struct ucsi_connector *con) +{ + struct power_supply_config psy_cfg = {}; + struct device *dev = con->ucsi->dev; + char *psy_name; + + psy_cfg.drv_data = con; + psy_cfg.fwnode = dev_fwnode(dev); + + psy_name = devm_kasprintf(dev, GFP_KERNEL, "ucsi-source-psy-%s%d", + dev_name(dev), con->num); + if (!psy_name) + return -ENOMEM; + + con->psy_desc.name = psy_name; + con->psy_desc.type = POWER_SUPPLY_TYPE_USB; + con->psy_desc.usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD) | + BIT(POWER_SUPPLY_USB_TYPE_PD_PPS); + con->psy_desc.properties = ucsi_psy_props; + con->psy_desc.num_properties = ARRAY_SIZE(ucsi_psy_props); + con->psy_desc.get_property = ucsi_psy_get_prop; + + con->psy = power_supply_register(dev, &con->psy_desc, &psy_cfg); + + return PTR_ERR_OR_ZERO(con->psy); +} + +void ucsi_unregister_port_psy(struct ucsi_connector *con) +{ + if (IS_ERR_OR_NULL(con->psy)) + return; + + power_supply_unregister(con->psy); + con->psy = NULL; +} + +void ucsi_port_psy_changed(struct ucsi_connector *con) +{ + if (IS_ERR_OR_NULL(con->psy)) + return; + + power_supply_changed(con->psy); +} diff --git a/drivers/usb/typec/ucsi/thunderbolt.c b/drivers/usb/typec/ucsi/thunderbolt.c new file mode 100644 index 000000000..434d3d8ea --- /dev/null +++ b/drivers/usb/typec/ucsi/thunderbolt.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UCSI Thunderbolt Alternate Mode Support + * + * Copyright 2026 Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ucsi.h" + +/** + * struct ucsi_tbt - Thunderbolt Alternate Mode private data structure + * @con: Pointer to UCSI connector structure + * @alt: Pointer to typec altmode structure + * @work: Work structure + * @cam: An offset into the list of alternate modes supported by the PPM + * @header: VDO header + */ +struct ucsi_tbt { + struct ucsi_connector *con; + struct typec_altmode *alt; + struct work_struct work; + int cam; + u32 header; +}; + +static void ucsi_thunderbolt_work(struct work_struct *work) +{ + struct ucsi_tbt *tbt = container_of(work, struct ucsi_tbt, work); + + if (typec_altmode_vdm(tbt->alt, tbt->header, NULL, 0)) + dev_err(&tbt->alt->dev, "VDM 0x%x failed\n", tbt->header); + + tbt->header = 0; +} + +static int ucsi_thunderbolt_set_altmode(struct ucsi_tbt *tbt, + bool enter, u32 vdo) +{ + int svdm_version; + int cmd; + int ret; + u64 command = UCSI_SET_NEW_CAM | + UCSI_CONNECTOR_NUMBER(tbt->con->num) | + UCSI_SET_NEW_CAM_SET_AM(tbt->cam) | + ((u64)vdo << 32); + + if (enter) + command |= (1 << 23); + + ret = ucsi_send_command(tbt->con->ucsi, command, NULL, 0); + if (ret < 0) + return ret; + + svdm_version = typec_altmode_get_svdm_version(tbt->alt); + if (svdm_version < 0) + return svdm_version; + + if (enter) + cmd = CMD_ENTER_MODE; + else + cmd = CMD_EXIT_MODE; + tbt->header = VDO(USB_TYPEC_TBT_SID, 1, svdm_version, cmd); + tbt->header |= VDO_OPOS(TYPEC_TBT_MODE); + tbt->header |= VDO_CMDT(CMDT_RSP_ACK); + + schedule_work(&tbt->work); + + return 0; +} + +static int ucsi_thunderbolt_enter(struct typec_altmode *alt, u32 *vdo) +{ + struct ucsi_tbt *tbt = typec_altmode_get_drvdata(alt); + struct ucsi_connector *con = tbt->con; + u64 command; + u8 cur = 0; + int ret; + + if (!ucsi_con_mutex_lock(con)) + return -ENOTCONN; + + command = UCSI_GET_CURRENT_CAM | UCSI_CONNECTOR_NUMBER(con->num); + ret = ucsi_send_command(con->ucsi, command, &cur, sizeof(cur)); + if (ret < 0) { + if (con->ucsi->version > 0x0100) + goto err_unlock; + cur = 0xff; + } + + if (cur != 0xff) { + if (cur >= UCSI_MAX_ALTMODES || con->port_altmode[cur] != alt) + ret = -EBUSY; + else + ret = 0; + goto err_unlock; + } + + ret = ucsi_thunderbolt_set_altmode(tbt, true, *vdo); + ucsi_altmode_update_active(tbt->con); + +err_unlock: + ucsi_con_mutex_unlock(con); + + return ret; +} + +static int ucsi_thunderbolt_exit(struct typec_altmode *alt) +{ + struct ucsi_tbt *tbt = typec_altmode_get_drvdata(alt); + int ret; + + if (!ucsi_con_mutex_lock(tbt->con)) + return -ENOTCONN; + + ret = ucsi_thunderbolt_set_altmode(tbt, false, 0); + + ucsi_con_mutex_unlock(tbt->con); + + return ret; +} + +static int ucsi_thunderbolt_vdm(struct typec_altmode *alt, + u32 header, const u32 *data, int count) +{ + struct ucsi_tbt *tbt = typec_altmode_get_drvdata(alt); + int cmd_type = PD_VDO_CMDT(header); + int cmd = PD_VDO_CMD(header); + int svdm_version; + + if (!ucsi_con_mutex_lock(tbt->con)) + return -ENOTCONN; + + svdm_version = typec_altmode_get_svdm_version(alt); + if (svdm_version < 0) { + ucsi_con_mutex_unlock(tbt->con); + return svdm_version; + } + + switch (cmd_type) { + case CMDT_INIT: + if (PD_VDO_SVDM_VER(header) < svdm_version) { + svdm_version = PD_VDO_SVDM_VER(header); + typec_partner_set_svdm_version(tbt->con->partner, svdm_version); + } + tbt->header = VDO(USB_TYPEC_TBT_SID, 1, svdm_version, cmd); + tbt->header |= VDO_OPOS(TYPEC_TBT_MODE); + tbt->header |= VDO_CMDT(CMDT_RSP_ACK); + + schedule_work(&tbt->work); + break; + default: + break; + } + + ucsi_con_mutex_unlock(tbt->con); + + return 0; +} + +static const struct typec_altmode_ops ucsi_thunderbolt_ops = { + .enter = ucsi_thunderbolt_enter, + .exit = ucsi_thunderbolt_exit, + .vdm = ucsi_thunderbolt_vdm, +}; + +struct typec_altmode *ucsi_register_thunderbolt(struct ucsi_connector *con, + bool override, int offset, + struct typec_altmode_desc *desc) +{ + struct typec_altmode *alt; + struct ucsi_tbt *tbt; + + alt = typec_port_register_altmode(con->port, desc); + if (IS_ERR(alt) || !override) + return alt; + + tbt = devm_kzalloc(&alt->dev, sizeof(*tbt), GFP_KERNEL); + if (!tbt) { + typec_unregister_altmode(alt); + return ERR_PTR(-ENOMEM); + } + + tbt->cam = offset; + tbt->con = con; + tbt->alt = alt; + INIT_WORK(&tbt->work, ucsi_thunderbolt_work); + typec_altmode_set_drvdata(alt, tbt); + typec_altmode_set_ops(alt, &ucsi_thunderbolt_ops); + + return alt; +} + +void ucsi_thunderbolt_remove_partner(struct typec_altmode *alt) +{ + struct ucsi_tbt *tbt; + + if (alt) { + tbt = typec_altmode_get_drvdata(alt); + if (tbt) + cancel_work_sync(&tbt->work); + } +} diff --git a/drivers/usb/typec/ucsi/trace.c b/drivers/usb/typec/ucsi/trace.c new file mode 100644 index 000000000..13a384227 --- /dev/null +++ b/drivers/usb/typec/ucsi/trace.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 +#define CREATE_TRACE_POINTS +#include "ucsi.h" +#include "trace.h" + +static const char * const ucsi_cmd_strs[] = { + [0] = "Unknown command", + [UCSI_PPM_RESET] = "PPM_RESET", + [UCSI_CANCEL] = "CANCEL", + [UCSI_CONNECTOR_RESET] = "CONNECTOR_RESET", + [UCSI_ACK_CC_CI] = "ACK_CC_CI", + [UCSI_SET_NOTIFICATION_ENABLE] = "SET_NOTIFICATION_ENABLE", + [UCSI_GET_CAPABILITY] = "GET_CAPABILITY", + [UCSI_GET_CONNECTOR_CAPABILITY] = "GET_CONNECTOR_CAPABILITY", + [UCSI_SET_CCOM] = "SET_CCOM", + [UCSI_SET_UOR] = "SET_UOR", + [UCSI_SET_PDM] = "SET_PDM", + [UCSI_SET_PDR] = "SET_PDR", + [UCSI_GET_ALTERNATE_MODES] = "GET_ALTERNATE_MODES", + [UCSI_GET_CAM_SUPPORTED] = "GET_CAM_SUPPORTED", + [UCSI_GET_CURRENT_CAM] = "GET_CURRENT_CAM", + [UCSI_SET_NEW_CAM] = "SET_NEW_CAM", + [UCSI_GET_PDOS] = "GET_PDOS", + [UCSI_GET_CABLE_PROPERTY] = "GET_CABLE_PROPERTY", + [UCSI_GET_CONNECTOR_STATUS] = "GET_CONNECTOR_STATUS", + [UCSI_GET_ERROR_STATUS] = "GET_ERROR_STATUS", +}; + +const char *ucsi_cmd_str(u64 raw_cmd) +{ + u8 cmd = raw_cmd & GENMASK(7, 0); + + return ucsi_cmd_strs[(cmd >= ARRAY_SIZE(ucsi_cmd_strs)) ? 0 : cmd]; +} + +static const char * const ucsi_recipient_strs[] = { + [UCSI_RECIPIENT_CON] = "port", + [UCSI_RECIPIENT_SOP] = "partner", + [UCSI_RECIPIENT_SOP_P] = "plug (prime)", + [UCSI_RECIPIENT_SOP_PP] = "plug (double prime)", +}; + +const char *ucsi_recipient_str(u8 recipient) +{ + return ucsi_recipient_strs[recipient]; +} diff --git a/drivers/usb/typec/ucsi/trace.h b/drivers/usb/typec/ucsi/trace.h new file mode 100644 index 000000000..9554a0207 --- /dev/null +++ b/drivers/usb/typec/ucsi/trace.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM ucsi + +#if !defined(__UCSI_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __UCSI_TRACE_H + +#include +#include + +const char *ucsi_cmd_str(u64 raw_cmd); +const char *ucsi_recipient_str(u8 recipient); + +DECLARE_EVENT_CLASS(ucsi_log_command, + TP_PROTO(u64 command, int ret), + TP_ARGS(command, ret), + TP_STRUCT__entry( + __field(u64, ctrl) + __field(int, ret) + ), + TP_fast_assign( + __entry->ctrl = command; + __entry->ret = ret; + ), + TP_printk("%s -> %s (err=%d)", ucsi_cmd_str(__entry->ctrl), + __entry->ret < 0 ? "FAIL" : "OK", + __entry->ret < 0 ? __entry->ret : 0) +); + +DEFINE_EVENT(ucsi_log_command, ucsi_run_command, + TP_PROTO(u64 command, int ret), + TP_ARGS(command, ret) +); + +DEFINE_EVENT(ucsi_log_command, ucsi_reset_ppm, + TP_PROTO(u64 command, int ret), + TP_ARGS(command, ret) +); + +DECLARE_EVENT_CLASS(ucsi_log_connector_status, + TP_PROTO(int port, struct ucsi_connector *con), + TP_ARGS(port, con), + TP_STRUCT__entry( + __field(int, port) + __field(u16, change) + __field(u8, opmode) + __field(u8, connected) + __field(u8, pwr_dir) + __field(u8, partner_flags) + __field(u8, partner_type) + __field(u32, request_data_obj) + __field(u8, bc_status) + ), + TP_fast_assign( + __entry->port = port - 1; + __entry->change = UCSI_CONSTAT(con, CHANGE); + __entry->opmode = UCSI_CONSTAT(con, PWR_OPMODE); + __entry->connected = UCSI_CONSTAT(con, CONNECTED); + __entry->pwr_dir = UCSI_CONSTAT(con, PWR_DIR); + __entry->partner_flags = UCSI_CONSTAT(con, PARTNER_FLAGS); + __entry->partner_type = UCSI_CONSTAT(con, PARTNER_TYPE); + __entry->request_data_obj = UCSI_CONSTAT(con, RDO); + __entry->bc_status = UCSI_CONSTAT(con, BC_STATUS); + ), + TP_printk("port%d status: change=%04x, opmode=%x, connected=%d, " + "sourcing=%d, partner_flags=%x, partner_type=%x, " + "request_data_obj=%08x, BC status=%x", __entry->port, + __entry->change, __entry->opmode, __entry->connected, + __entry->pwr_dir, __entry->partner_flags, __entry->partner_type, + __entry->request_data_obj, __entry->bc_status) +); + +DEFINE_EVENT(ucsi_log_connector_status, ucsi_connector_change, + TP_PROTO(int port, struct ucsi_connector *con), + TP_ARGS(port, con) +); + +DEFINE_EVENT(ucsi_log_connector_status, ucsi_register_port, + TP_PROTO(int port, struct ucsi_connector *con), + TP_ARGS(port, con) +); + +DECLARE_EVENT_CLASS(ucsi_log_register_altmode, + TP_PROTO(u8 recipient, struct typec_altmode *alt), + TP_ARGS(recipient, alt), + TP_STRUCT__entry( + __field(u8, recipient) + __field(u16, svid) + __field(u8, mode) + __field(u32, vdo) + ), + TP_fast_assign( + __entry->recipient = recipient; + __entry->svid = alt->svid; + __entry->mode = alt->mode; + __entry->vdo = alt->vdo; + ), + TP_printk("%s alt mode: svid %04x, mode %d vdo %x", + ucsi_recipient_str(__entry->recipient), __entry->svid, + __entry->mode, __entry->vdo) +); + +DEFINE_EVENT(ucsi_log_register_altmode, ucsi_register_altmode, + TP_PROTO(u8 recipient, struct typec_altmode *alt), + TP_ARGS(recipient, alt) +); + +#endif /* __UCSI_TRACE_H */ + +/* This part must be outside protection */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#include diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c new file mode 100644 index 000000000..bef3f9b71 --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -0,0 +1,2403 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Type-C Connector System Software Interface driver + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ucsi.h" +#include "trace.h" + +/* + * UCSI_TIMEOUT_MS - PPM communication timeout + * + * Ideally we could use MIN_TIME_TO_RESPOND_WITH_BUSY (which is defined in UCSI + * specification) here as reference, but unfortunately we can't. It is very + * difficult to estimate the time it takes for the system to process the command + * before it is actually passed to the PPM. + */ +#define UCSI_TIMEOUT_MS 10000 + +/* + * UCSI_SWAP_TIMEOUT_MS - Timeout for role swap requests + * + * 5 seconds is close to the time it takes for CapsCounter to reach 0, so even + * if the PPM does not generate Connector Change events before that with + * partners that do not support USB Power Delivery, this should still work. + */ +#define UCSI_SWAP_TIMEOUT_MS 5000 + +void ucsi_notify_common(struct ucsi *ucsi, u32 cci) +{ + /* Ignore bogus data in CCI if busy indicator is set. */ + if (cci & UCSI_CCI_BUSY) + return; + + if (UCSI_CCI_CONNECTOR(cci)) { + if (!ucsi->cap.num_connectors || + UCSI_CCI_CONNECTOR(cci) <= ucsi->cap.num_connectors) + ucsi_connector_change(ucsi, UCSI_CCI_CONNECTOR(cci)); + else + dev_err(ucsi->dev, "bogus connector number in CCI: %lu\n", + UCSI_CCI_CONNECTOR(cci)); + } + + if (cci & UCSI_CCI_ACK_COMPLETE && + test_and_clear_bit(ACK_PENDING, &ucsi->flags)) + complete(&ucsi->complete); + + if (cci & UCSI_CCI_COMMAND_COMPLETE && + test_and_clear_bit(COMMAND_PENDING, &ucsi->flags)) + complete(&ucsi->complete); +} +EXPORT_SYMBOL_GPL(ucsi_notify_common); + +int ucsi_sync_control_common(struct ucsi *ucsi, u64 command, u32 *cci, + void *data, size_t size, void *msg_out, + size_t msg_out_size) +{ + bool ack = UCSI_COMMAND(command) == UCSI_ACK_CC_CI; + int ret; + + if (ack) + set_bit(ACK_PENDING, &ucsi->flags); + else + set_bit(COMMAND_PENDING, &ucsi->flags); + + reinit_completion(&ucsi->complete); + + if (msg_out && msg_out_size) { + if (!ucsi->ops->write_message_out) { + ret = -EOPNOTSUPP; + goto out_clear_bit; + } + + ret = ucsi->ops->write_message_out(ucsi, msg_out, msg_out_size); + if (ret) + goto out_clear_bit; + } + + ret = ucsi->ops->async_control(ucsi, command); + if (ret) + goto out_clear_bit; + + if (!wait_for_completion_timeout(&ucsi->complete, + msecs_to_jiffies(UCSI_TIMEOUT_MS))) + ret = -ETIMEDOUT; + +out_clear_bit: + if (ack) + clear_bit(ACK_PENDING, &ucsi->flags); + else + clear_bit(COMMAND_PENDING, &ucsi->flags); + + if (!ret && cci) + ret = ucsi->ops->read_cci(ucsi, cci); + + if (!ret && data && + (*cci & UCSI_CCI_COMMAND_COMPLETE)) + ret = ucsi->ops->read_message_in(ucsi, data, size); + + return ret; +} +EXPORT_SYMBOL_GPL(ucsi_sync_control_common); + +static int ucsi_acknowledge(struct ucsi *ucsi, bool conn_ack) +{ + u64 ctrl; + + ctrl = UCSI_ACK_CC_CI; + ctrl |= UCSI_ACK_COMMAND_COMPLETE; + if (conn_ack) { + clear_bit(EVENT_PENDING, &ucsi->flags); + ctrl |= UCSI_ACK_CONNECTOR_CHANGE; + } + + return ucsi->ops->sync_control(ucsi, ctrl, NULL, NULL, 0, NULL, 0); +} + +static int ucsi_run_command(struct ucsi *ucsi, u64 command, u32 *cci, + void *data, size_t size, void *msg_out, + size_t msg_out_size, bool conn_ack) +{ + int ret, err; + + *cci = 0; + + if (size > UCSI_MAX_DATA_LENGTH(ucsi)) + return -EINVAL; + + ret = ucsi->ops->sync_control(ucsi, command, cci, data, size, + msg_out, msg_out_size); + + if (*cci & UCSI_CCI_BUSY) + return ucsi_run_command(ucsi, UCSI_CANCEL, cci, + NULL, 0, NULL, 0, false) ?: -EBUSY; + if (ret) + return ret; + + if (!(*cci & UCSI_CCI_COMMAND_COMPLETE)) + return -EIO; + + if (*cci & UCSI_CCI_NOT_SUPPORTED) + err = -EOPNOTSUPP; + else if (*cci & UCSI_CCI_ERROR) + err = -EIO; + else + err = 0; + + /* + * Don't ACK connection change if there was an error. + */ + ret = ucsi_acknowledge(ucsi, err ? false : conn_ack); + if (ret) + return ret; + + return err ?: UCSI_CCI_LENGTH(*cci); +} + +static int ucsi_read_error(struct ucsi *ucsi, u8 connector_num) +{ + u64 command; + u16 error; + u32 cci; + int ret; + + command = UCSI_GET_ERROR_STATUS | UCSI_CONNECTOR_NUMBER(connector_num); + ret = ucsi_run_command(ucsi, command, &cci, &error, + sizeof(error), NULL, 0, false); + if (ret < 0) + return ret; + + switch (error) { + case UCSI_ERROR_INCOMPATIBLE_PARTNER: + return -EOPNOTSUPP; + case UCSI_ERROR_CC_COMMUNICATION_ERR: + return -ECOMM; + case UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL: + return -EPROTO; + case UCSI_ERROR_DEAD_BATTERY: + dev_warn(ucsi->dev, "Dead battery condition!\n"); + return -EPERM; + case UCSI_ERROR_INVALID_CON_NUM: + case UCSI_ERROR_UNREGONIZED_CMD: + case UCSI_ERROR_INVALID_CMD_ARGUMENT: + dev_err(ucsi->dev, "possible UCSI driver bug %u\n", error); + return -EINVAL; + case UCSI_ERROR_OVERCURRENT: + dev_warn(ucsi->dev, "Overcurrent condition\n"); + break; + case UCSI_ERROR_PARTNER_REJECTED_SWAP: + dev_warn(ucsi->dev, "Partner rejected swap\n"); + break; + case UCSI_ERROR_HARD_RESET: + dev_warn(ucsi->dev, "Hard reset occurred\n"); + break; + case UCSI_ERROR_PPM_POLICY_CONFLICT: + dev_warn(ucsi->dev, "PPM Policy conflict\n"); + break; + case UCSI_ERROR_SWAP_REJECTED: + dev_warn(ucsi->dev, "Swap rejected\n"); + break; + case UCSI_ERROR_REVERSE_CURRENT_PROTECTION: + dev_warn(ucsi->dev, "Reverse Current Protection detected\n"); + break; + case UCSI_ERROR_SET_SINK_PATH_REJECTED: + dev_warn(ucsi->dev, "Set Sink Path rejected\n"); + break; + case UCSI_ERROR_UNDEFINED: + default: + dev_err(ucsi->dev, "unknown error %u\n", error); + break; + } + + return -EIO; +} + +static int ucsi_send_command_common(struct ucsi *ucsi, u64 cmd, + void *data, size_t size, void *msg_out, + size_t msg_out_size, bool conn_ack) +{ + u8 connector_num; + u32 cci; + int ret; + + if (ucsi->version > UCSI_VERSION_1_2) { + switch (UCSI_COMMAND(cmd)) { + case UCSI_GET_ALTERNATE_MODES: + connector_num = UCSI_GET_ALTMODE_GET_CONNECTOR_NUMBER(cmd); + break; + case UCSI_PPM_RESET: + case UCSI_CANCEL: + case UCSI_ACK_CC_CI: + case UCSI_SET_NOTIFICATION_ENABLE: + case UCSI_GET_CAPABILITY: + connector_num = 0; + break; + default: + connector_num = UCSI_DEFAULT_GET_CONNECTOR_NUMBER(cmd); + break; + } + } else { + connector_num = 0; + } + + mutex_lock(&ucsi->ppm_lock); + + ret = ucsi_run_command(ucsi, cmd, &cci, data, size, + msg_out, msg_out_size, conn_ack); + + if (cci & UCSI_CCI_ERROR) + ret = ucsi_read_error(ucsi, connector_num); + + trace_ucsi_run_command(cmd, ret); + + mutex_unlock(&ucsi->ppm_lock); + return ret; +} + +int ucsi_send_command(struct ucsi *ucsi, u64 command, + void *data, size_t size) +{ + return ucsi_send_command_common(ucsi, command, data, + size, NULL, 0, false); +} +EXPORT_SYMBOL_GPL(ucsi_send_command); + +int ucsi_write_message_out_command(struct ucsi *ucsi, u64 command, + void *data, size_t size, void *msg_out, + size_t msg_out_size) +{ + if (msg_out_size > UCSI_MAX_MSG_OUT_DATA_LEN(ucsi)) + return -EINVAL; + + return ucsi_send_command_common(ucsi, command, data, + size, msg_out, msg_out_size, false); +} +EXPORT_SYMBOL_GPL(ucsi_write_message_out_command); + +/* -------------------------------------------------------------------------- */ + +struct ucsi_work { + struct delayed_work work; + struct list_head node; + unsigned long delay; + unsigned int count; + struct ucsi_connector *con; + int (*cb)(struct ucsi_connector *); +}; + +static void ucsi_poll_worker(struct work_struct *work) +{ + struct ucsi_work *uwork = container_of(work, struct ucsi_work, work.work); + struct ucsi_connector *con = uwork->con; + int ret; + + mutex_lock(&con->lock); + + if (!con->partner) { + list_del(&uwork->node); + mutex_unlock(&con->lock); + kfree(uwork); + return; + } + + ret = uwork->cb(con); + + if (uwork->count-- && (ret == -EBUSY || ret == -ETIMEDOUT)) { + queue_delayed_work(con->wq, &uwork->work, uwork->delay); + } else { + list_del(&uwork->node); + kfree(uwork); + } + + mutex_unlock(&con->lock); +} + +static int ucsi_partner_task(struct ucsi_connector *con, + int (*cb)(struct ucsi_connector *), + int retries, unsigned long delay) +{ + struct ucsi_work *uwork; + + if (!con->partner) + return 0; + + uwork = kzalloc_obj(*uwork); + if (!uwork) + return -ENOMEM; + + INIT_DELAYED_WORK(&uwork->work, ucsi_poll_worker); + uwork->count = retries; + uwork->delay = delay; + uwork->con = con; + uwork->cb = cb; + + list_add_tail(&uwork->node, &con->partner_tasks); + queue_delayed_work(con->wq, &uwork->work, delay); + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +void ucsi_altmode_update_active(struct ucsi_connector *con) +{ + const struct typec_altmode *altmode = NULL; + u64 command; + u16 svid = 0; + int ret; + u8 cur; + int i; + + command = UCSI_GET_CURRENT_CAM | UCSI_CONNECTOR_NUMBER(con->num); + ret = ucsi_send_command(con->ucsi, command, &cur, sizeof(cur)); + if (ret < 0) { + if (con->ucsi->version > 0x0100) { + dev_err(con->ucsi->dev, + "GET_CURRENT_CAM command failed\n"); + return; + } + cur = 0xff; + } + + if (cur < UCSI_MAX_ALTMODES) + altmode = typec_altmode_get_partner(con->port_altmode[cur]); + + for (i = 0; con->partner_altmode[i]; i++) + typec_altmode_update_active(con->partner_altmode[i], + con->partner_altmode[i] == altmode); + + if (altmode) + svid = altmode->svid; + typec_altmode_state_update(con->partner, svid, 0); +} + +static int ucsi_altmode_next_mode(struct typec_altmode **alt, u16 svid) +{ + u8 mode = 1; + int i; + + for (i = 0; alt[i]; i++) { + if (i > MODE_DISCOVERY_MAX) + return -ERANGE; + + if (alt[i]->svid == svid) + mode++; + } + + return mode; +} + +static int ucsi_next_altmode(struct typec_altmode **alt) +{ + int i = 0; + + for (i = 0; i < UCSI_MAX_ALTMODES; i++) + if (!alt[i]) + return i; + + return -ENOENT; +} + +static int ucsi_get_num_altmode(struct typec_altmode **alt) +{ + int i; + + for (i = 0; i < UCSI_MAX_ALTMODES; i++) + if (!alt[i]) + break; + + return i; +} + +static int ucsi_register_altmode(struct ucsi_connector *con, + struct typec_altmode_desc *desc, + u8 recipient) +{ + struct typec_altmode *alt; + bool override; + int ret; + int i; + + override = !!(con->ucsi->cap.features & UCSI_CAP_ALT_MODE_OVERRIDE); + + switch (recipient) { + case UCSI_RECIPIENT_CON: + i = ucsi_next_altmode(con->port_altmode); + if (i < 0) { + ret = i; + goto err; + } + + ret = ucsi_altmode_next_mode(con->port_altmode, desc->svid); + if (ret < 0) + return ret; + + desc->mode = ret; + + switch (desc->svid) { + case USB_TYPEC_DP_SID: + alt = ucsi_register_displayport(con, override, i, desc); + break; + case USB_TYPEC_NVIDIA_VLINK_SID: + if (desc->vdo == USB_TYPEC_NVIDIA_VLINK_DBG_VDO) + alt = typec_port_register_altmode(con->port, + desc); + else + alt = ucsi_register_displayport(con, override, + i, desc); + break; + case USB_TYPEC_TBT_SID: + alt = ucsi_register_thunderbolt(con, override, i, desc); + break; + default: + alt = typec_port_register_altmode(con->port, desc); + break; + } + + if (IS_ERR(alt)) { + ret = PTR_ERR(alt); + goto err; + } + + con->port_altmode[i] = alt; + break; + case UCSI_RECIPIENT_SOP: + i = ucsi_next_altmode(con->partner_altmode); + if (i < 0) { + ret = i; + goto err; + } + + ret = ucsi_altmode_next_mode(con->partner_altmode, desc->svid); + if (ret < 0) + return ret; + + desc->mode = ret; + + alt = typec_partner_register_altmode(con->partner, desc); + if (IS_ERR(alt)) { + ret = PTR_ERR(alt); + goto err; + } + + con->partner_altmode[i] = alt; + break; + case UCSI_RECIPIENT_SOP_P: + i = ucsi_next_altmode(con->plug_altmode); + if (i < 0) { + ret = i; + goto err; + } + + ret = ucsi_altmode_next_mode(con->plug_altmode, desc->svid); + if (ret < 0) + return ret; + + desc->mode = ret; + + alt = typec_plug_register_altmode(con->plug, desc); + if (IS_ERR(alt)) { + ret = PTR_ERR(alt); + goto err; + } + + con->plug_altmode[i] = alt; + break; + default: + return -EINVAL; + } + + trace_ucsi_register_altmode(recipient, alt); + + return 0; + +err: + dev_err(con->ucsi->dev, "failed to registers svid 0x%04x mode %d\n", + desc->svid, desc->mode); + + return ret; +} + +static void ucsi_dump_duplicate_altmode(struct ucsi_connector *con, + u8 recipient, u16 svid, + u32 existing_vdo, u32 new_vdo, + int offset) +{ + static const char * const recipient_names[] = { + [UCSI_RECIPIENT_CON] = "port", + [UCSI_RECIPIENT_SOP] = "partner", + [UCSI_RECIPIENT_SOP_P] = "plug", + [UCSI_RECIPIENT_SOP_PP] = "cable plug prime", + }; + + dev_warn(con->ucsi->dev, + "con%d: Firmware bug: duplicate %s altmode SVID 0x%04x at offset %d, ignoring but please contact the BIOS vendor to fix this issue.\n", + con->num, recipient_names[recipient], svid, offset); + + if (existing_vdo != new_vdo) + dev_warn(con->ucsi->dev, + "con%d: VDO mismatch: 0x%08x vs 0x%08x\n", + con->num, existing_vdo, new_vdo); +} + +/* Count altmodes in @altmodes that advertise @svid. */ +static int ucsi_altmode_count_svid(struct typec_altmode **altmodes, u16 svid) +{ + int count = 0; + int k; + + for (k = 0; k < UCSI_MAX_ALTMODES; k++) { + if (!altmodes[k]) + break; + if (altmodes[k]->svid == svid) + count++; + } + + return count; +} + +/* + * Check if an altmode is a duplicate. Some firmware implementations + * incorrectly return the same altmode multiple times, causing sysfs errors. + * Returns true if the altmode should be skipped. + * + * The matching rules differ by recipient: + * + * - UCSI_RECIPIENT_CON (port) and UCSI_RECIPIENT_SOP_P (plug): + * Two altmodes with identical SVID and VDO are byte-for-byte duplicates + * and the second has no observable function. Drop them. + * + * - UCSI_RECIPIENT_SOP (partner): + * The typec class binds each partner altmode to a port altmode of the + * same SVID via altmode_match()/device_find_child(), which returns the + * first port altmode with a matching SVID. If the partner advertises + * more altmodes for SVID X than the port advertises, the surplus + * partner altmode(s) collapse onto an already-paired port altmode and + * trigger a "duplicate filename .../partner" sysfs error during + * typec_altmode_create_links(). Use the port-side altmode count for + * SVID X as the authoritative cap and reject any partner altmode that + * would exceed it. This preserves legitimate multi-Mode partner + * altmodes (e.g. vendor SVIDs that the port really does advertise + * twice) while filtering the firmware-generated duplicates that have + * no port counterpart. + */ +static bool ucsi_altmode_is_duplicate(struct ucsi_connector *con, u8 recipient, + const struct ucsi_altmode *alt_batch, int batch_idx, + u16 svid, u32 vdo, int offset) +{ + struct typec_altmode **altmodes; + int port_count, partner_count; + int k; + + /* Check for duplicates within the current batch first */ + for (k = 0; k < batch_idx; k++) { + if (alt_batch[k].svid == svid && alt_batch[k].mid == vdo) { + ucsi_dump_duplicate_altmode(con, recipient, svid, + vdo, vdo, offset); + return true; + } + } + + switch (recipient) { + case UCSI_RECIPIENT_SOP: + /* + * Cap partner altmodes per SVID by the port-side count: + * any further partner altmode for that SVID would alias an + * already-paired port altmode and break typec sysfs. + */ + port_count = ucsi_altmode_count_svid(con->port_altmode, svid); + partner_count = ucsi_altmode_count_svid(con->partner_altmode, + svid); + if (port_count && partner_count >= port_count) { + ucsi_dump_duplicate_altmode(con, recipient, svid, + con->partner_altmode[partner_count - 1]->vdo, + vdo, offset); + return true; + } + return false; + case UCSI_RECIPIENT_CON: + altmodes = con->port_altmode; + break; + case UCSI_RECIPIENT_SOP_P: + altmodes = con->plug_altmode; + break; + default: + return false; + } + + /* CON and SOP_P: drop only exact SVID+VDO duplicates. */ + for (k = 0; k < UCSI_MAX_ALTMODES; k++) { + if (!altmodes[k]) + break; + + if (altmodes[k]->svid != svid || altmodes[k]->vdo != vdo) + continue; + + ucsi_dump_duplicate_altmode(con, recipient, svid, + altmodes[k]->vdo, vdo, offset); + return true; + } + + return false; +} + +static int +ucsi_register_altmodes_nvidia(struct ucsi_connector *con, u8 recipient) +{ + int max_altmodes = UCSI_MAX_ALTMODES; + struct typec_altmode_desc desc; + struct ucsi_altmode alt; + struct ucsi_altmode orig[UCSI_MAX_ALTMODES]; + struct ucsi_altmode updated[UCSI_MAX_ALTMODES]; + struct ucsi *ucsi = con->ucsi; + bool multi_dp = false; + u64 command; + int ret; + int len; + int i; + int k = 0; + + if (recipient == UCSI_RECIPIENT_CON) + max_altmodes = con->ucsi->cap.num_alt_modes; + + memset(orig, 0, sizeof(orig)); + memset(updated, 0, sizeof(updated)); + + /* First get all the alternate modes */ + for (i = 0; i < max_altmodes; i++) { + memset(&alt, 0, sizeof(alt)); + command = UCSI_GET_ALTERNATE_MODES; + command |= UCSI_GET_ALTMODE_RECIPIENT(recipient); + command |= UCSI_GET_ALTMODE_CONNECTOR_NUMBER(con->num); + command |= UCSI_GET_ALTMODE_OFFSET(i); + len = ucsi_send_command(con->ucsi, command, &alt, sizeof(alt)); + /* + * We are collecting all altmodes first and then registering. + * Some type-C device will return zero length data beyond last + * alternate modes. We should not return if length is zero. + */ + if (len < 0) + return len; + + /* We got all altmodes, now break out and register them */ + if (!len || !alt.svid) + break; + + orig[k].mid = alt.mid; + orig[k].svid = alt.svid; + k++; + } + /* + * Update the original altmode table as some ppms may report + * multiple DP altmodes. + */ + multi_dp = ucsi->ops->update_altmodes(ucsi, recipient, orig, updated); + + /* now register altmodes */ + for (i = 0; i < max_altmodes; i++) { + struct ucsi_altmode *altmode_array = multi_dp ? updated : orig; + + if (!altmode_array[i].svid) + return 0; + + /* + * Check for duplicates in current array and already + * registered altmodes. Skip if duplicate found. + */ + if (ucsi_altmode_is_duplicate(con, recipient, altmode_array, i, + altmode_array[i].svid, + altmode_array[i].mid, i)) + continue; + + memset(&desc, 0, sizeof(desc)); + desc.svid = altmode_array[i].svid; + desc.vdo = altmode_array[i].mid; + desc.roles = TYPEC_PORT_DRD; + + ret = ucsi_register_altmode(con, &desc, recipient); + if (ret) + return ret; + } + + return 0; +} + +static int ucsi_register_altmodes(struct ucsi_connector *con, u8 recipient) +{ + int max_altmodes = UCSI_MAX_ALTMODES; + struct typec_altmode_desc desc; + struct ucsi_altmode alt[2]; + u64 command; + int num; + int ret; + int len; + int j; + int i; + + if (!(con->ucsi->cap.features & UCSI_CAP_ALT_MODE_DETAILS)) + return 0; + + if (recipient == UCSI_RECIPIENT_SOP && con->partner_altmode[0]) + return 0; + + if (con->ucsi->ops->update_altmodes) + return ucsi_register_altmodes_nvidia(con, recipient); + + if (recipient == UCSI_RECIPIENT_CON) + max_altmodes = con->ucsi->cap.num_alt_modes; + + for (i = 0; i < max_altmodes;) { + memset(alt, 0, sizeof(alt)); + command = UCSI_GET_ALTERNATE_MODES; + command |= UCSI_GET_ALTMODE_RECIPIENT(recipient); + command |= UCSI_GET_ALTMODE_CONNECTOR_NUMBER(con->num); + command |= UCSI_GET_ALTMODE_OFFSET(i); + len = ucsi_send_command(con->ucsi, command, alt, sizeof(alt)); + if (len == -EBUSY) + continue; + if (len <= 0) + return len; + + /* + * This code is requesting one alt mode at a time, but some PPMs + * may still return two. If that happens both alt modes need be + * registered and the offset for the next alt mode has to be + * incremented. + */ + num = len / sizeof(alt[0]); + i += num; + + for (j = 0; j < num; j++) { + if (!alt[j].svid) + return 0; + + /* + * Check for duplicates in current batch and already + * registered altmodes. Skip if duplicate found. + */ + if (ucsi_altmode_is_duplicate(con, recipient, alt, j, + alt[j].svid, alt[j].mid, + i - num + j)) + continue; + + memset(&desc, 0, sizeof(desc)); + desc.vdo = alt[j].mid; + desc.svid = alt[j].svid; + desc.roles = TYPEC_PORT_DRD; + desc.mode_selection = con->ucsi->ops->add_partner_altmodes && + !con->typec_cap.no_mode_control; + + ret = ucsi_register_altmode(con, &desc, recipient); + if (ret) + return ret; + } + } + + return 0; +} + +static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient) +{ + const struct typec_altmode *pdev; + struct typec_altmode **adev; + int i = 0; + + switch (recipient) { + case UCSI_RECIPIENT_CON: + adev = con->port_altmode; + break; + case UCSI_RECIPIENT_SOP: + adev = con->partner_altmode; + break; + case UCSI_RECIPIENT_SOP_P: + adev = con->plug_altmode; + break; + default: + return; + } + + while (adev[i]) { + if (recipient == UCSI_RECIPIENT_SOP) { + pdev = typec_altmode_get_partner(adev[i]); + + if (adev[i]->svid == USB_TYPEC_DP_SID || + (adev[i]->svid == USB_TYPEC_NVIDIA_VLINK_SID && + adev[i]->vdo != USB_TYPEC_NVIDIA_VLINK_DBG_VDO)) + ucsi_displayport_remove_partner((void *)pdev); + else if (adev[i]->svid == USB_TYPEC_TBT_SID) + ucsi_thunderbolt_remove_partner((void *)pdev); + } + typec_unregister_altmode(adev[i]); + adev[i++] = NULL; + } +} + +static int ucsi_get_connector_status(struct ucsi_connector *con, bool conn_ack) +{ + u64 command = UCSI_GET_CONNECTOR_STATUS | UCSI_CONNECTOR_NUMBER(con->num); + size_t size = min(sizeof(con->status), + UCSI_MAX_DATA_LENGTH(con->ucsi)); + int ret; + + ret = ucsi_send_command_common(con->ucsi, command, &con->status, size, + NULL, 0, conn_ack); + + return ret < 0 ? ret : 0; +} + +static int ucsi_read_pdos(struct ucsi_connector *con, + enum typec_role role, int is_partner, + u32 *pdos, int offset, int num_pdos) +{ + struct ucsi *ucsi = con->ucsi; + u64 command; + int ret; + + if (is_partner && + ucsi->quirks & UCSI_NO_PARTNER_PDOS && + (UCSI_CONSTAT(con, PWR_DIR) || !is_source(role))) + return 0; + + command = UCSI_COMMAND(UCSI_GET_PDOS) | UCSI_CONNECTOR_NUMBER(con->num); + command |= UCSI_GET_PDOS_PARTNER_PDO(is_partner); + command |= UCSI_GET_PDOS_PDO_OFFSET(offset); + command |= UCSI_GET_PDOS_NUM_PDOS(num_pdos - 1); + command |= is_source(role) ? UCSI_GET_PDOS_SRC_PDOS : 0; + ret = ucsi_send_command(ucsi, command, pdos + offset, + num_pdos * sizeof(u32)); + if (ret < 0 && ret != -ETIMEDOUT) + dev_err(ucsi->dev, "UCSI_GET_PDOS failed (%d)\n", ret); + + return ret; +} + +static int ucsi_get_pdos(struct ucsi_connector *con, enum typec_role role, + int is_partner, u32 *pdos) +{ + struct ucsi *ucsi = con->ucsi; + u8 num_pdos; + int ret; + + if (!(ucsi->cap.features & UCSI_CAP_PDO_DETAILS)) + return 0; + + /* UCSI max payload means only getting at most 4 PDOs at a time */ + ret = ucsi_read_pdos(con, role, is_partner, pdos, 0, UCSI_MAX_PDOS); + if (ret < 0) + return ret; + + num_pdos = ret / sizeof(u32); /* number of bytes to 32-bit PDOs */ + if (num_pdos < UCSI_MAX_PDOS) + return num_pdos; + + /* get the remaining PDOs, if any */ + ret = ucsi_read_pdos(con, role, is_partner, pdos, UCSI_MAX_PDOS, + PDO_MAX_OBJECTS - UCSI_MAX_PDOS); + if (ret < 0) + return ret; + + return ret / sizeof(u32) + num_pdos; +} + +static int ucsi_get_src_pdos(struct ucsi_connector *con) +{ + int ret; + + ret = ucsi_get_pdos(con, TYPEC_SOURCE, 1, con->src_pdos); + if (ret < 0) + return ret; + + con->num_pdos = ret; + + ucsi_port_psy_changed(con); + + return ret; +} + +static struct usb_power_delivery_capabilities *ucsi_get_pd_caps(struct ucsi_connector *con, + enum typec_role role, + bool is_partner) +{ + struct usb_power_delivery_capabilities_desc pd_caps; + int ret; + + ret = ucsi_get_pdos(con, role, is_partner, pd_caps.pdo); + if (ret <= 0) + return ERR_PTR(ret); + + if (ret < PDO_MAX_OBJECTS) + pd_caps.pdo[ret] = 0; + + pd_caps.role = role; + + return usb_power_delivery_register_capabilities(is_partner ? con->partner_pd : con->pd, + &pd_caps); +} + +static int ucsi_get_pd_message(struct ucsi_connector *con, u8 recipient, + size_t bytes, void *data, u8 type) +{ + size_t len = min(bytes, UCSI_MAX_DATA_LENGTH(con->ucsi)); + u64 command; + u8 offset; + int ret; + + for (offset = 0; offset < bytes; offset += len) { + len = min(len, bytes - offset); + + command = UCSI_COMMAND(UCSI_GET_PD_MESSAGE) | UCSI_CONNECTOR_NUMBER(con->num); + command |= UCSI_GET_PD_MESSAGE_RECIPIENT(recipient); + command |= UCSI_GET_PD_MESSAGE_OFFSET(offset); + command |= UCSI_GET_PD_MESSAGE_BYTES(len); + command |= UCSI_GET_PD_MESSAGE_TYPE(type); + + ret = ucsi_send_command(con->ucsi, command, data + offset, len); + if (ret < 0) + return ret; + } + + return 0; +} + +static int ucsi_get_partner_identity(struct ucsi_connector *con) +{ + u32 vdo[7] = {}; + int ret; + + ret = ucsi_get_pd_message(con, UCSI_RECIPIENT_SOP, sizeof(vdo), vdo, + UCSI_GET_PD_MESSAGE_TYPE_IDENTITY); + if (ret < 0) + return ret; + + /* VDM Header is not part of struct usb_pd_identity, so dropping it. */ + con->partner_identity = *(struct usb_pd_identity *)&vdo[1]; + + ret = typec_partner_set_identity(con->partner); + if (ret < 0) + dev_err(con->ucsi->dev, "Failed to set partner identity (%d)\n", ret); + + return ret; +} + +static int ucsi_get_cable_identity(struct ucsi_connector *con) +{ + u32 vdo[7] = {}; + int ret; + + ret = ucsi_get_pd_message(con, UCSI_RECIPIENT_SOP_P, sizeof(vdo), vdo, + UCSI_GET_PD_MESSAGE_TYPE_IDENTITY); + if (ret < 0) + return ret; + + con->cable_identity = *(struct usb_pd_identity *)&vdo[1]; + + ret = typec_cable_set_identity(con->cable); + if (ret < 0) + dev_err(con->ucsi->dev, "Failed to set cable identity (%d)\n", ret); + + return ret; +} + +static int ucsi_check_altmodes(struct ucsi_connector *con) +{ + int ret, num_partner_am; + + ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP); + if (ret && ret != -ETIMEDOUT) + dev_err(con->ucsi->dev, + "con%d: failed to register partner alt modes (%d)\n", + con->num, ret); + + /* Ignoring the errors in this case. */ + if (con->partner_altmode[0]) { + num_partner_am = ucsi_get_num_altmode(con->partner_altmode); + typec_partner_set_num_altmodes(con->partner, num_partner_am); + if (con->ucsi->ops->add_partner_altmodes) + con->ucsi->ops->add_partner_altmodes(con); + ucsi_altmode_update_active(con); + return 0; + } else { + typec_partner_set_num_altmodes(con->partner, 0); + } + + return ret; +} + +static void ucsi_register_device_pdos(struct ucsi_connector *con) +{ + struct ucsi *ucsi = con->ucsi; + struct usb_power_delivery_desc desc = { ucsi->cap.pd_version }; + struct usb_power_delivery_capabilities *pd_cap; + + if (con->pd) + return; + + con->pd = usb_power_delivery_register(ucsi->dev, &desc); + + pd_cap = ucsi_get_pd_caps(con, TYPEC_SOURCE, false); + if (!IS_ERR(pd_cap)) + con->port_source_caps = pd_cap; + + pd_cap = ucsi_get_pd_caps(con, TYPEC_SINK, false); + if (!IS_ERR(pd_cap)) + con->port_sink_caps = pd_cap; + + typec_port_set_usb_power_delivery(con->port, con->pd); +} + +static int ucsi_register_partner_pdos(struct ucsi_connector *con) +{ + struct usb_power_delivery_desc desc = { con->ucsi->cap.pd_version }; + struct usb_power_delivery_capabilities *cap; + + if (con->partner_pd) + return 0; + + con->partner_pd = typec_partner_usb_power_delivery_register(con->partner, &desc); + if (IS_ERR(con->partner_pd)) + return PTR_ERR(con->partner_pd); + + cap = ucsi_get_pd_caps(con, TYPEC_SOURCE, true); + if (IS_ERR(cap)) + return PTR_ERR(cap); + + con->partner_source_caps = cap; + + cap = ucsi_get_pd_caps(con, TYPEC_SINK, true); + if (IS_ERR(cap)) + return PTR_ERR(cap); + + con->partner_sink_caps = cap; + + return typec_partner_set_usb_power_delivery(con->partner, con->partner_pd); +} + +static void ucsi_unregister_partner_pdos(struct ucsi_connector *con) +{ + usb_power_delivery_unregister_capabilities(con->partner_sink_caps); + con->partner_sink_caps = NULL; + usb_power_delivery_unregister_capabilities(con->partner_source_caps); + con->partner_source_caps = NULL; + usb_power_delivery_unregister(con->partner_pd); + con->partner_pd = NULL; +} + +static int ucsi_register_plug(struct ucsi_connector *con) +{ + struct typec_plug *plug; + struct typec_plug_desc desc = {.index = TYPEC_PLUG_SOP_P}; + + plug = typec_register_plug(con->cable, &desc); + if (IS_ERR(plug)) { + dev_err(con->ucsi->dev, + "con%d: failed to register plug (%pe)\n", con->num, + plug); + return PTR_ERR(plug); + } + + con->plug = plug; + return 0; +} + +static void ucsi_unregister_plug(struct ucsi_connector *con) +{ + if (!con->plug) + return; + + ucsi_unregister_altmodes(con, UCSI_RECIPIENT_SOP_P); + typec_unregister_plug(con->plug); + con->plug = NULL; +} + +static int ucsi_register_cable(struct ucsi_connector *con) +{ + struct ucsi_cable_property cable_prop; + struct typec_cable *cable; + struct typec_cable_desc desc = {}; + u64 command; + int ret; + + command = UCSI_GET_CABLE_PROPERTY | UCSI_CONNECTOR_NUMBER(con->num); + ret = ucsi_send_command(con->ucsi, command, &cable_prop, sizeof(cable_prop)); + if (ret < 0) { + dev_err(con->ucsi->dev, "GET_CABLE_PROPERTY failed (%d)\n", ret); + return ret; + } + + switch (UCSI_CABLE_PROP_FLAG_PLUG_TYPE(cable_prop.flags)) { + case UCSI_CABLE_PROPERTY_PLUG_TYPE_A: + desc.type = USB_PLUG_TYPE_A; + break; + case UCSI_CABLE_PROPERTY_PLUG_TYPE_B: + desc.type = USB_PLUG_TYPE_B; + break; + case UCSI_CABLE_PROPERTY_PLUG_TYPE_C: + desc.type = USB_PLUG_TYPE_C; + break; + default: + desc.type = USB_PLUG_NONE; + break; + } + + if (con->ucsi->cap.features & UCSI_CAP_GET_PD_MESSAGE) + desc.identity = &con->cable_identity; + desc.active = !!(UCSI_CABLE_PROP_FLAG_ACTIVE_CABLE & cable_prop.flags); + + if (con->ucsi->version >= UCSI_VERSION_2_1) + desc.pd_revision = UCSI_CABLE_PROP_FLAG_PD_MAJOR_REV_AS_BCD(cable_prop.flags); + + cable = typec_register_cable(con->port, &desc); + if (IS_ERR(cable)) { + dev_err(con->ucsi->dev, + "con%d: failed to register cable (%pe)\n", con->num, + cable); + return PTR_ERR(cable); + } + + con->cable = cable; + return 0; +} + +static void ucsi_unregister_cable(struct ucsi_connector *con) +{ + if (!con->cable) + return; + + ucsi_unregister_plug(con); + typec_unregister_cable(con->cable); + memset(&con->cable_identity, 0, sizeof(con->cable_identity)); + con->cable = NULL; +} + +static int ucsi_check_connector_capability(struct ucsi_connector *con) +{ + u64 pd_revision; + u64 command; + int ret; + + if (!con->partner || con->ucsi->version < UCSI_VERSION_2_1) + return 0; + + command = UCSI_GET_CONNECTOR_CAPABILITY | UCSI_CONNECTOR_NUMBER(con->num); + ret = ucsi_send_command(con->ucsi, command, &con->cap, sizeof(con->cap)); + if (ret < 0) { + dev_err(con->ucsi->dev, "GET_CONNECTOR_CAPABILITY failed (%d)\n", ret); + return ret; + } + + pd_revision = UCSI_CONCAP(con, PARTNER_PD_REVISION_V2_1); + typec_partner_set_pd_revision(con->partner, UCSI_SPEC_REVISION_TO_BCD(pd_revision)); + + return ret; +} + +static void ucsi_orientation(struct ucsi_connector *con) +{ + if (con->ucsi->version < UCSI_VERSION_2_0) + return; + + if (!UCSI_CONSTAT(con, CONNECTED)) { + typec_set_orientation(con->port, TYPEC_ORIENTATION_NONE); + return; + } + + switch (UCSI_CONSTAT(con, ORIENTATION)) { + case UCSI_CONSTAT_ORIENTATION_NORMAL: + typec_set_orientation(con->port, TYPEC_ORIENTATION_NORMAL); + break; + case UCSI_CONSTAT_ORIENTATION_REVERSE: + typec_set_orientation(con->port, TYPEC_ORIENTATION_REVERSE); + break; + default: + break; + } +} + +static void ucsi_pwr_opmode_change(struct ucsi_connector *con) +{ + switch (UCSI_CONSTAT(con, PWR_OPMODE)) { + case UCSI_CONSTAT_PWR_OPMODE_PD: + con->rdo = UCSI_CONSTAT(con, RDO); + typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_PD); + ucsi_partner_task(con, ucsi_get_src_pdos, 30, 0); + ucsi_partner_task(con, ucsi_check_altmodes, 30, HZ); + ucsi_partner_task(con, ucsi_register_partner_pdos, 1, HZ); + ucsi_partner_task(con, ucsi_check_connector_capability, 1, HZ); + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: + con->rdo = 0; + typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_1_5A); + ucsi_port_psy_changed(con); + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: + con->rdo = 0; + typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_3_0A); + ucsi_port_psy_changed(con); + break; + default: + con->rdo = 0; + typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_USB); + ucsi_port_psy_changed(con); + break; + } +} + +static int ucsi_register_partner(struct ucsi_connector *con) +{ + u8 pwr_opmode = UCSI_CONSTAT(con, PWR_OPMODE); + struct typec_partner_desc desc; + struct typec_partner *partner; + + if (con->partner) + return 0; + + memset(&desc, 0, sizeof(desc)); + + switch (UCSI_CONSTAT(con, PARTNER_TYPE)) { + case UCSI_CONSTAT_PARTNER_TYPE_DEBUG: + desc.accessory = TYPEC_ACCESSORY_DEBUG; + break; + case UCSI_CONSTAT_PARTNER_TYPE_AUDIO: + desc.accessory = TYPEC_ACCESSORY_AUDIO; + break; + default: + break; + } + + if (pwr_opmode == UCSI_CONSTAT_PWR_OPMODE_PD) + ucsi_register_device_pdos(con); + + if (con->ucsi->cap.features & UCSI_CAP_GET_PD_MESSAGE) + desc.identity = &con->partner_identity; + desc.usb_pd = pwr_opmode == UCSI_CONSTAT_PWR_OPMODE_PD; + + if (con->ucsi->version >= UCSI_VERSION_2_1) { + u64 pd_revision = UCSI_CONCAP(con, PARTNER_PD_REVISION_V2_1); + desc.pd_revision = UCSI_SPEC_REVISION_TO_BCD(pd_revision); + } + + partner = typec_register_partner(con->port, &desc); + if (IS_ERR(partner)) { + dev_err(con->ucsi->dev, + "con%d: failed to register partner (%pe)\n", con->num, + partner); + return PTR_ERR(partner); + } + + con->partner = partner; + + if (con->ucsi->version >= UCSI_VERSION_3_0 && + UCSI_CONSTAT(con, PARTNER_FLAG_USB4_GEN4)) + typec_partner_set_usb_mode(partner, USB_MODE_USB4); + else if (con->ucsi->version >= UCSI_VERSION_2_0 && + UCSI_CONSTAT(con, PARTNER_FLAG_USB4_GEN3)) + typec_partner_set_usb_mode(partner, USB_MODE_USB4); + + return 0; +} + +static void ucsi_unregister_partner(struct ucsi_connector *con) +{ + if (!con->partner) + return; + + typec_set_mode(con->port, TYPEC_STATE_SAFE); + if (con->ucsi->ops->remove_partner_altmodes) + con->ucsi->ops->remove_partner_altmodes(con); + + typec_partner_set_usb_power_delivery(con->partner, NULL); + ucsi_unregister_partner_pdos(con); + ucsi_unregister_altmodes(con, UCSI_RECIPIENT_SOP); + ucsi_unregister_cable(con); + typec_unregister_partner(con->partner); + memset(&con->partner_identity, 0, sizeof(con->partner_identity)); + con->partner = NULL; +} + +static void ucsi_partner_change(struct ucsi_connector *con) +{ + enum usb_role u_role = USB_ROLE_NONE; + int ret; + + switch (UCSI_CONSTAT(con, PARTNER_TYPE)) { + case UCSI_CONSTAT_PARTNER_TYPE_UFP: + case UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP: + u_role = USB_ROLE_HOST; + fallthrough; + case UCSI_CONSTAT_PARTNER_TYPE_CABLE: + typec_set_data_role(con->port, TYPEC_HOST); + break; + case UCSI_CONSTAT_PARTNER_TYPE_DFP: + u_role = USB_ROLE_DEVICE; + typec_set_data_role(con->port, TYPEC_DEVICE); + break; + default: + break; + } + + if (UCSI_CONSTAT(con, CONNECTED)) { + switch (UCSI_CONSTAT(con, PARTNER_TYPE)) { + case UCSI_CONSTAT_PARTNER_TYPE_DEBUG: + typec_set_mode(con->port, TYPEC_MODE_DEBUG); + break; + case UCSI_CONSTAT_PARTNER_TYPE_AUDIO: + typec_set_mode(con->port, TYPEC_MODE_AUDIO); + break; + default: + if (UCSI_CONSTAT(con, PARTNER_FLAG_USB)) + typec_set_mode(con->port, TYPEC_STATE_USB); + } + + if (((con->ucsi->version >= UCSI_VERSION_3_0 && + UCSI_CONSTAT(con, PARTNER_FLAG_USB4_GEN4)) || + (con->ucsi->version >= UCSI_VERSION_2_0 && + UCSI_CONSTAT(con, PARTNER_FLAG_USB4_GEN3))) && con->partner) + typec_partner_set_usb_mode(con->partner, USB_MODE_USB4); + } + + if ((!UCSI_CONSTAT(con, PARTNER_FLAG_USB)) && + ((con->ucsi->quirks & UCSI_USB4_IMPLIES_USB) && + (!(UCSI_CONSTAT(con, PARTNER_FLAG_USB4_GEN3) || + UCSI_CONSTAT(con, PARTNER_FLAG_USB4_GEN4))))) + u_role = USB_ROLE_NONE; + + ret = usb_role_switch_set_role(con->usb_role_sw, u_role); + if (ret) + dev_err(con->ucsi->dev, "con:%d: failed to set usb role:%d\n", + con->num, u_role); +} + +static int ucsi_check_connection(struct ucsi_connector *con) +{ + u8 prev_state = UCSI_CONSTAT(con, CONNECTED); + int ret; + + ret = ucsi_get_connector_status(con, false); + if (ret) { + dev_err(con->ucsi->dev, "GET_CONNECTOR_STATUS failed (%d)\n", ret); + return ret; + } + + if (UCSI_CONSTAT(con, CONNECTED)) { + if (prev_state) + return 0; + ucsi_register_partner(con); + ucsi_pwr_opmode_change(con); + ucsi_partner_change(con); + } else { + ucsi_partner_change(con); + ucsi_port_psy_changed(con); + ucsi_unregister_partner(con); + } + + return 0; +} + +static int ucsi_check_cable(struct ucsi_connector *con) +{ + int ret, num_plug_am; + + if (con->cable) + return 0; + + ret = ucsi_register_cable(con); + if (ret < 0) + return ret; + + if (con->ucsi->cap.features & UCSI_CAP_GET_PD_MESSAGE) { + ret = ucsi_get_cable_identity(con); + if (ret < 0) + return ret; + } + + if (con->ucsi->cap.features & UCSI_CAP_ALT_MODE_DETAILS) { + ret = ucsi_register_plug(con); + if (ret < 0) + return ret; + + ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP_P); + if (ret < 0) + return ret; + + if (con->plug_altmode[0]) { + num_plug_am = ucsi_get_num_altmode(con->plug_altmode); + typec_plug_set_num_altmodes(con->plug, num_plug_am); + } else { + typec_plug_set_num_altmodes(con->plug, 0); + } + } + + return 0; +} + +static void ucsi_handle_connector_change(struct work_struct *work) +{ + struct ucsi_connector *con = container_of(work, struct ucsi_connector, + work); + struct ucsi *ucsi = con->ucsi; + u8 curr_scale, volt_scale; + enum typec_role role, prev_role; + u16 change; + int ret; + u32 val; + + mutex_lock(&con->lock); + + if (!test_and_set_bit(EVENT_PENDING, &ucsi->flags)) + dev_err_once(ucsi->dev, "%s entered without EVENT_PENDING\n", + __func__); + + prev_role = UCSI_CONSTAT(con, PWR_DIR); + + ret = ucsi_get_connector_status(con, true); + if (ret) { + dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n", + __func__, ret); + clear_bit(EVENT_PENDING, &con->ucsi->flags); + goto out_unlock; + } + + trace_ucsi_connector_change(con->num, con); + + if (ucsi->ops->connector_status) + ucsi->ops->connector_status(con); + + change = UCSI_CONSTAT(con, CHANGE); + role = UCSI_CONSTAT(con, PWR_DIR); + + if ((change & UCSI_CONSTAT_POWER_DIR_CHANGE) && role != prev_role) { + typec_set_pwr_role(con->port, role); + + /* Some power_supply properties vary depending on the power direction when + * connected + */ + if (UCSI_CONSTAT(con, CONNECTED)) + ucsi_port_psy_changed(con); + + /* Complete pending power role swap */ + if (!completion_done(&con->complete)) + complete(&con->complete); + } + + if (change & UCSI_CONSTAT_CONNECT_CHANGE) { + typec_set_pwr_role(con->port, role); + ucsi_port_psy_changed(con); + ucsi_partner_change(con); + ucsi_orientation(con); + + if (UCSI_CONSTAT(con, CONNECTED)) { + ucsi_register_partner(con); + ucsi_partner_task(con, ucsi_check_connection, 1, HZ); + if (con->ucsi->cap.features & UCSI_CAP_GET_PD_MESSAGE) + ucsi_partner_task(con, ucsi_get_partner_identity, 1, HZ); + if (con->ucsi->cap.features & UCSI_CAP_CABLE_DETAILS) + ucsi_partner_task(con, ucsi_check_cable, 1, HZ); + + if (UCSI_CONSTAT(con, PWR_OPMODE) == UCSI_CONSTAT_PWR_OPMODE_PD) { + ucsi_partner_task(con, ucsi_register_partner_pdos, 1, HZ); + ucsi_partner_task(con, ucsi_check_connector_capability, 1, HZ); + } + } else { + ucsi_unregister_partner(con); + } + } + + if (change & (UCSI_CONSTAT_POWER_OPMODE_CHANGE | UCSI_CONSTAT_POWER_LEVEL_CHANGE)) + ucsi_pwr_opmode_change(con); + + if (con->partner && (change & UCSI_CONSTAT_PARTNER_CHANGE)) { + ucsi_partner_change(con); + ucsi_altmode_update_active(con); + + /* Complete pending data role swap */ + if (!completion_done(&con->complete)) + complete(&con->complete); + } + + if (change & UCSI_CONSTAT_CAM_CHANGE) + ucsi_partner_task(con, ucsi_check_altmodes, 1, HZ); + + if (change & (UCSI_CONSTAT_BC_CHANGE | UCSI_CONSTAT_SINK_PATH_CHANGE)) + ucsi_port_psy_changed(con); + + if (con->ucsi->version >= UCSI_VERSION_2_1 && + UCSI_CONSTAT(con, PWR_READING_READY_V2_1)) { + curr_scale = UCSI_CONSTAT(con, CURRENT_SCALE_V2_1); + volt_scale = UCSI_CONSTAT(con, VOLTAGE_SCALE_V2_1); + + val = UCSI_CONSTAT(con, PEAK_CURRENT_V2_1); + con->peak_current = UCSI_CONSTAT_CURR_SCALE_MULT * curr_scale * val; + + val = UCSI_CONSTAT(con, AVG_CURRENT_V2_1); + con->avg_current = UCSI_CONSTAT_CURR_SCALE_MULT * curr_scale * val; + + val = UCSI_CONSTAT(con, VBUS_VOLTAGE_V2_1); + con->vbus_voltage = UCSI_CONSTAT_VOLT_SCALE_MULT * volt_scale * val; + } + +out_unlock: + mutex_unlock(&con->lock); +} + +/** + * ucsi_connector_change - Process Connector Change Event + * @ucsi: UCSI Interface + * @num: Connector number + */ +void ucsi_connector_change(struct ucsi *ucsi, u8 num) +{ + struct ucsi_connector *con; + + if (!(ucsi->ntfy & UCSI_ENABLE_NTFY_CONNECTOR_CHANGE)) { + dev_dbg(ucsi->dev, "Early connector change event\n"); + return; + } + + if (!num || num > ucsi->cap.num_connectors) { + dev_warn_ratelimited(ucsi->dev, + "Bogus connector change on %u (max %u)\n", + num, ucsi->cap.num_connectors); + return; + } + + con = &ucsi->connector[num - 1]; + + if (!test_and_set_bit(EVENT_PENDING, &ucsi->flags)) + schedule_work(&con->work); +} +EXPORT_SYMBOL_GPL(ucsi_connector_change); + +/* -------------------------------------------------------------------------- */ + +/* + * Hard Reset bit field was defined with value 1 in UCSI spec version 1.0. + * Starting with spec version 1.1, Hard Reset bit field was removed from the + * CONNECTOR_RESET command, until spec 2.0 reintroduced it with value 0, so, in effect, + * the value to pass in to the command for a Hard Reset is different depending + * on the supported UCSI version by the LPM. + * + * For performing a Data Reset on LPMs supporting version 2.0 and greater, + * this function needs to be called with the second argument set to 0. + */ +static int ucsi_reset_connector(struct ucsi_connector *con, bool hard) +{ + u64 command; + + command = UCSI_CONNECTOR_RESET | UCSI_CONNECTOR_NUMBER(con->num); + + if (con->ucsi->version < UCSI_VERSION_1_1) + command |= hard ? UCSI_CONNECTOR_RESET_HARD_VER_1_0 : 0; + else if (con->ucsi->version >= UCSI_VERSION_2_0) + command |= hard ? 0 : UCSI_CONNECTOR_RESET_DATA_VER_2_0; + + return ucsi_send_command(con->ucsi, command, NULL, 0); +} + +static int ucsi_reset_ppm(struct ucsi *ucsi) +{ + u64 command; + unsigned long tmo; + u32 cci; + int ret; + + mutex_lock(&ucsi->ppm_lock); + + ret = ucsi->ops->poll_cci(ucsi, &cci); + if (ret < 0) + goto out; + + /* + * If UCSI_CCI_RESET_COMPLETE is already set we must clear + * the flag before we start another reset. Send a + * UCSI_SET_NOTIFICATION_ENABLE command to achieve this. + * Ignore a timeout and try the reset anyway if this fails. + */ + if (cci & UCSI_CCI_RESET_COMPLETE) { + command = UCSI_SET_NOTIFICATION_ENABLE; + ret = ucsi->ops->async_control(ucsi, command); + if (ret < 0) + goto out; + + tmo = jiffies + msecs_to_jiffies(UCSI_TIMEOUT_MS); + do { + ret = ucsi->ops->poll_cci(ucsi, &cci); + if (ret < 0) + goto out; + if (cci & UCSI_CCI_COMMAND_COMPLETE) + break; + if (time_is_before_jiffies(tmo)) + break; + msleep(20); + } while (1); + + WARN_ON(cci & UCSI_CCI_RESET_COMPLETE); + } + + command = UCSI_PPM_RESET; + ret = ucsi->ops->async_control(ucsi, command); + if (ret < 0) + goto out; + + tmo = jiffies + msecs_to_jiffies(UCSI_TIMEOUT_MS); + + do { + if (time_is_before_jiffies(tmo)) { + ret = -ETIMEDOUT; + goto out; + } + + /* Give the PPM time to process a reset before reading CCI */ + msleep(20); + + ret = ucsi->ops->poll_cci(ucsi, &cci); + if (ret) + goto out; + + /* If the PPM is still doing something else, reset it again. */ + if (cci & ~UCSI_CCI_RESET_COMPLETE) { + ret = ucsi->ops->async_control(ucsi, command); + if (ret < 0) + goto out; + } + + } while (!(cci & UCSI_CCI_RESET_COMPLETE)); + +out: + mutex_unlock(&ucsi->ppm_lock); + return ret; +} + +static int ucsi_role_cmd(struct ucsi_connector *con, u64 command) +{ + int ret; + + ret = ucsi_send_command(con->ucsi, command, NULL, 0); + if (ret == -ETIMEDOUT) { + u64 c; + + /* PPM most likely stopped responding. Resetting everything. */ + ucsi_reset_ppm(con->ucsi); + + c = UCSI_SET_NOTIFICATION_ENABLE | con->ucsi->ntfy; + ucsi_send_command(con->ucsi, c, NULL, 0); + + ucsi_reset_connector(con, true); + } + + return ret; +} + +static int ucsi_dr_swap(struct typec_port *port, enum typec_data_role role) +{ + struct ucsi_connector *con = typec_get_drvdata(port); + u8 partner_type; + u64 command; + int ret = 0; + + mutex_lock(&con->lock); + + if (!con->partner) { + ret = -ENOTCONN; + goto out_unlock; + } + + partner_type = UCSI_CONSTAT(con, PARTNER_TYPE); + if ((partner_type == UCSI_CONSTAT_PARTNER_TYPE_DFP && + role == TYPEC_DEVICE) || + (partner_type == UCSI_CONSTAT_PARTNER_TYPE_UFP && + role == TYPEC_HOST)) + goto out_unlock; + + reinit_completion(&con->complete); + + command = UCSI_SET_UOR | UCSI_CONNECTOR_NUMBER(con->num); + command |= UCSI_SET_UOR_ROLE(role); + command |= UCSI_SET_UOR_ACCEPT_ROLE_SWAPS; + ret = ucsi_role_cmd(con, command); + if (ret < 0) + goto out_unlock; + + mutex_unlock(&con->lock); + + if (!wait_for_completion_timeout(&con->complete, + msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS))) + return -ETIMEDOUT; + + return 0; + +out_unlock: + mutex_unlock(&con->lock); + + return ret; +} + +static int ucsi_pr_swap(struct typec_port *port, enum typec_role role) +{ + struct ucsi_connector *con = typec_get_drvdata(port); + enum typec_role cur_role; + u64 command; + int ret = 0; + + mutex_lock(&con->lock); + + if (!con->partner) { + ret = -ENOTCONN; + goto out_unlock; + } + + cur_role = UCSI_CONSTAT(con, PWR_DIR); + + if (cur_role == role) + goto out_unlock; + + reinit_completion(&con->complete); + + command = UCSI_SET_PDR | UCSI_CONNECTOR_NUMBER(con->num); + command |= UCSI_SET_PDR_ROLE(role); + command |= UCSI_SET_PDR_ACCEPT_ROLE_SWAPS; + ret = ucsi_role_cmd(con, command); + if (ret < 0) + goto out_unlock; + + mutex_unlock(&con->lock); + + if (!wait_for_completion_timeout(&con->complete, + msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS))) + return -ETIMEDOUT; + + mutex_lock(&con->lock); + + /* Something has gone wrong while swapping the role */ + if (UCSI_CONSTAT(con, PWR_OPMODE) != UCSI_CONSTAT_PWR_OPMODE_PD) { + ucsi_reset_connector(con, true); + ret = -EPROTO; + } + +out_unlock: + mutex_unlock(&con->lock); + + return ret; +} + +static const struct typec_operations ucsi_ops = { + .dr_set = ucsi_dr_swap, + .pr_set = ucsi_pr_swap +}; + +/* Caller must call fwnode_handle_put() after use */ +static struct fwnode_handle *ucsi_find_fwnode(struct ucsi_connector *con) +{ + struct fwnode_handle *fwnode; + int i = 1; + + device_for_each_child_node(con->ucsi->dev, fwnode) + if (i++ == con->num) + return fwnode; + return NULL; +} + +static int ucsi_register_port(struct ucsi *ucsi, struct ucsi_connector *con) +{ + struct typec_capability *cap = &con->typec_cap; + enum typec_accessory *accessory = cap->accessory; + enum usb_role u_role = USB_ROLE_NONE; + u64 command; + char *name; + int ret; + + name = kasprintf(GFP_KERNEL, "%s-con%d", dev_name(ucsi->dev), con->num); + if (!name) + return -ENOMEM; + + con->wq = create_singlethread_workqueue(name); + kfree(name); + if (!con->wq) + return -ENOMEM; + + INIT_WORK(&con->work, ucsi_handle_connector_change); + init_completion(&con->complete); + mutex_init(&con->lock); + lockdep_set_class(&con->lock, &con->lock_key); + INIT_LIST_HEAD(&con->partner_tasks); + con->ucsi = ucsi; + + cap->fwnode = ucsi_find_fwnode(con); + con->usb_role_sw = fwnode_usb_role_switch_get(cap->fwnode); + if (IS_ERR(con->usb_role_sw)) + return dev_err_probe(ucsi->dev, PTR_ERR(con->usb_role_sw), + "con%d: failed to get usb role switch\n", con->num); + + /* Delay other interactions with the con until registration is complete */ + mutex_lock(&con->lock); + + /* Get connector capability */ + command = UCSI_GET_CONNECTOR_CAPABILITY; + command |= UCSI_CONNECTOR_NUMBER(con->num); + ret = ucsi_send_command(ucsi, command, &con->cap, sizeof(con->cap)); + if (ret < 0) + goto out_unlock; + + if (UCSI_CONCAP(con, OPMODE_DRP)) + cap->data = TYPEC_PORT_DRD; + else if (UCSI_CONCAP(con, OPMODE_DFP)) + cap->data = TYPEC_PORT_DFP; + else if (UCSI_CONCAP(con, OPMODE_UFP)) + cap->data = TYPEC_PORT_UFP; + + if (UCSI_CONCAP(con, PROVIDER) && UCSI_CONCAP(con, CONSUMER)) + cap->type = TYPEC_PORT_DRP; + else if (UCSI_CONCAP(con, PROVIDER)) + cap->type = TYPEC_PORT_SRC; + else if (UCSI_CONCAP(con, CONSUMER)) + cap->type = TYPEC_PORT_SNK; + + cap->revision = ucsi->cap.typec_version; + cap->pd_revision = ucsi->cap.pd_version; + cap->svdm_version = SVDM_VER_2_0; + cap->prefer_role = TYPEC_NO_PREFERRED_ROLE; + + if (UCSI_CONCAP(con, OPMODE_AUDIO_ACCESSORY)) + *accessory++ = TYPEC_ACCESSORY_AUDIO; + if (UCSI_CONCAP(con, OPMODE_DEBUG_ACCESSORY)) + *accessory = TYPEC_ACCESSORY_DEBUG; + + if (UCSI_CONCAP_USB2_SUPPORT(con)) + cap->usb_capability |= USB_CAPABILITY_USB2; + if (UCSI_CONCAP_USB3_SUPPORT(con)) + cap->usb_capability |= USB_CAPABILITY_USB3; + if (UCSI_CONCAP_USB4_SUPPORT(con)) + cap->usb_capability |= USB_CAPABILITY_USB4; + + cap->driver_data = con; + cap->ops = &ucsi_ops; + cap->no_mode_control = !(con->ucsi->cap.features & UCSI_CAP_ALT_MODE_OVERRIDE); + + if (ucsi->version >= UCSI_VERSION_2_0) + con->typec_cap.orientation_aware = true; + + if (ucsi->ops->update_connector) + ucsi->ops->update_connector(con); + + ret = ucsi_register_port_psy(con); + if (ret) + goto out; + + /* Register the connector */ + con->port = typec_register_port(ucsi->dev, cap); + if (IS_ERR(con->port)) { + ret = PTR_ERR(con->port); + goto out; + } + + if (!(ucsi->quirks & UCSI_DELAY_DEVICE_PDOS)) + ucsi_register_device_pdos(con); + + /* Alternate modes */ + ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_CON); + if (ret) { + dev_err(ucsi->dev, "con%d: failed to register alt modes\n", + con->num); + goto out; + } + + /* Get the status */ + ret = ucsi_get_connector_status(con, false); + if (ret) { + dev_err(ucsi->dev, "con%d: failed to get status\n", con->num); + goto out; + } + + if (ucsi->ops->connector_status) + ucsi->ops->connector_status(con); + + switch (UCSI_CONSTAT(con, PARTNER_TYPE)) { + case UCSI_CONSTAT_PARTNER_TYPE_UFP: + case UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP: + u_role = USB_ROLE_HOST; + fallthrough; + case UCSI_CONSTAT_PARTNER_TYPE_CABLE: + typec_set_data_role(con->port, TYPEC_HOST); + break; + case UCSI_CONSTAT_PARTNER_TYPE_DFP: + u_role = USB_ROLE_DEVICE; + typec_set_data_role(con->port, TYPEC_DEVICE); + break; + default: + break; + } + + /* Check if there is already something connected */ + if (UCSI_CONSTAT(con, CONNECTED)) { + typec_set_pwr_role(con->port, UCSI_CONSTAT(con, PWR_DIR)); + ucsi_register_partner(con); + ucsi_pwr_opmode_change(con); + ucsi_orientation(con); + ucsi_port_psy_changed(con); + if (con->ucsi->cap.features & UCSI_CAP_GET_PD_MESSAGE) + ucsi_get_partner_identity(con); + if (con->ucsi->cap.features & UCSI_CAP_CABLE_DETAILS) + ucsi_check_cable(con); + } + + /* Only notify USB controller if partner supports USB data */ + if (!(UCSI_CONSTAT(con, PARTNER_FLAG_USB))) + u_role = USB_ROLE_NONE; + + ret = usb_role_switch_set_role(con->usb_role_sw, u_role); + if (ret) { + dev_err(ucsi->dev, "con:%d: failed to set usb role:%d\n", + con->num, u_role); + ret = 0; + } + + if (con->partner && UCSI_CONSTAT(con, PWR_OPMODE) == UCSI_CONSTAT_PWR_OPMODE_PD) { + ucsi_register_device_pdos(con); + ucsi_get_src_pdos(con); + ucsi_check_altmodes(con); + ucsi_check_connector_capability(con); + } + + trace_ucsi_register_port(con->num, con); + +out: + fwnode_handle_put(cap->fwnode); +out_unlock: + mutex_unlock(&con->lock); + + if (ret && con->wq) { + destroy_workqueue(con->wq); + con->wq = NULL; + } + + return ret; +} + +static void ucsi_unregister_port(struct ucsi_connector *con) +{ + struct ucsi_work *uwork; + + if (con->wq) { + mutex_lock(&con->lock); + ucsi_unregister_partner(con); + /* + * queue delayed items immediately so they can execute + * and free themselves before the wq is destroyed + */ + list_for_each_entry(uwork, &con->partner_tasks, node) { + if (cancel_delayed_work(&uwork->work)) + queue_delayed_work(con->wq, &uwork->work, 0); + } + mutex_unlock(&con->lock); + + destroy_workqueue(con->wq); + con->wq = NULL; + } else { + ucsi_unregister_partner(con); + } + + ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON); + ucsi_unregister_port_psy(con); + + usb_power_delivery_unregister_capabilities(con->port_sink_caps); + con->port_sink_caps = NULL; + usb_power_delivery_unregister_capabilities(con->port_source_caps); + con->port_source_caps = NULL; + usb_power_delivery_unregister(con->pd); + con->pd = NULL; + typec_unregister_port(con->port); + con->port = NULL; +} + +static u64 ucsi_get_supported_notifications(struct ucsi *ucsi) +{ + u16 features = ucsi->cap.features; + u64 ntfy = UCSI_ENABLE_NTFY_ALL; + + if (!(features & UCSI_CAP_ALT_MODE_DETAILS)) + ntfy &= ~UCSI_ENABLE_NTFY_CAM_CHANGE; + + if (!(features & UCSI_CAP_PDO_DETAILS)) + ntfy &= ~(UCSI_ENABLE_NTFY_PWR_LEVEL_CHANGE | + UCSI_ENABLE_NTFY_CAP_CHANGE); + + if (!(features & UCSI_CAP_EXT_SUPPLY_NOTIFICATIONS)) + ntfy &= ~UCSI_ENABLE_NTFY_EXT_PWR_SRC_CHANGE; + + if (!(features & UCSI_CAP_PD_RESET)) + ntfy &= ~UCSI_ENABLE_NTFY_PD_RESET_COMPLETE; + + if (ucsi->version <= UCSI_VERSION_1_2) + return ntfy; + + ntfy |= UCSI_ENABLE_NTFY_SINK_PATH_STS_CHANGE; + + if (features & UCSI_CAP_GET_ATTENTION_VDO) + ntfy |= UCSI_ENABLE_NTFY_ATTENTION; + + if (features & UCSI_CAP_FW_UPDATE_REQUEST) + ntfy |= UCSI_ENABLE_NTFY_LPM_FW_UPDATE_REQ; + + if (features & UCSI_CAP_SECURITY_REQUEST) + ntfy |= UCSI_ENABLE_NTFY_SECURITY_REQ_PARTNER; + + if (features & UCSI_CAP_SET_RETIMER_MODE) + ntfy |= UCSI_ENABLE_NTFY_SET_RETIMER_MODE; + + return ntfy; +} + +/** + * ucsi_init - Initialize UCSI interface + * @ucsi: UCSI to be initialized + * + * Registers all ports @ucsi has and enables all notification events. + */ +static int ucsi_init(struct ucsi *ucsi) +{ + struct ucsi_connector *con, *connector; + u64 command, ntfy; + u32 cci; + int ret; + int i; + + /* Reset the PPM */ + ret = ucsi_reset_ppm(ucsi); + if (ret) { + dev_err(ucsi->dev, "failed to reset PPM!\n"); + goto err; + } + + /* Enable basic notifications */ + ntfy = UCSI_ENABLE_NTFY_CMD_COMPLETE | UCSI_ENABLE_NTFY_ERROR; + command = UCSI_SET_NOTIFICATION_ENABLE | ntfy; + ret = ucsi_send_command(ucsi, command, NULL, 0); + if (ret < 0) + goto err_reset; + + /* Get PPM capabilities */ + command = UCSI_GET_CAPABILITY; + ret = ucsi_send_command(ucsi, command, &ucsi->cap, + BITS_TO_BYTES(UCSI_GET_CAPABILITY_SIZE)); + if (ret < 0) + goto err_reset; + + if (!ucsi->cap.num_connectors) { + ret = -ENODEV; + goto err_reset; + } + /* Check if reserved bit set. This is out of spec but happens in buggy FW */ + if (ucsi->cap.num_connectors & 0x80) { + dev_warn(ucsi->dev, "UCSI: Invalid num_connectors %d. Likely buggy FW\n", + ucsi->cap.num_connectors); + ucsi->cap.num_connectors &= 0x7f; // clear bit and carry on + } + + /* Allocate the connectors. Released in ucsi_unregister() */ + connector = kzalloc_objs(*connector, ucsi->cap.num_connectors + 1); + if (!connector) { + ret = -ENOMEM; + goto err_reset; + } + + for (i = 0; i < ucsi->cap.num_connectors; i++) + lockdep_register_key(&connector[i].lock_key); + + /* Register all connectors */ + for (i = 0; i < ucsi->cap.num_connectors; i++) { + connector[i].num = i + 1; + ret = ucsi_register_port(ucsi, &connector[i]); + if (ret) + goto err_unregister; + } + + /* Enable all supported notifications */ + ntfy = ucsi_get_supported_notifications(ucsi); + command = UCSI_SET_NOTIFICATION_ENABLE | ntfy; + ret = ucsi_send_command(ucsi, command, NULL, 0); + if (ret < 0) + goto err_unregister; + + ucsi->connector = connector; + ucsi->ntfy = ntfy; + + mutex_lock(&ucsi->ppm_lock); + ret = ucsi->ops->read_cci(ucsi, &cci); + mutex_unlock(&ucsi->ppm_lock); + if (ret) + return ret; + if (UCSI_CCI_CONNECTOR(cci)) + ucsi_connector_change(ucsi, UCSI_CCI_CONNECTOR(cci)); + + return 0; + +err_unregister: + for (con = connector; con->port; con++) + ucsi_unregister_port(con); + for (i = 0; i < ucsi->cap.num_connectors; i++) + lockdep_unregister_key(&connector[i].lock_key); + + kfree(connector); +err_reset: + memset(&ucsi->cap, 0, sizeof(ucsi->cap)); + ucsi_reset_ppm(ucsi); +err: + return ret; +} + +static void ucsi_resume_work(struct work_struct *work) +{ + struct ucsi *ucsi = container_of(work, struct ucsi, resume_work); + struct ucsi_connector *con; + u64 command; + int ret; + + /* Restore UCSI notification enable mask after system resume */ + command = UCSI_SET_NOTIFICATION_ENABLE | ucsi->ntfy; + ret = ucsi_send_command(ucsi, command, NULL, 0); + if (ret < 0) { + dev_err(ucsi->dev, "failed to re-enable notifications (%d)\n", ret); + return; + } + + for (con = ucsi->connector; con->port; con++) { + mutex_lock(&con->lock); + ucsi_partner_task(con, ucsi_check_connection, 1, 0); + mutex_unlock(&con->lock); + } +} + +int ucsi_suspend(struct ucsi *ucsi) +{ + int i; + + /* + * Cancel pending work so it cannot access the firmware after the ACPI + * EC is stopped for suspend; state is re-read on resume. + */ + cancel_delayed_work_sync(&ucsi->work); + + if (!ucsi->connector) + return 0; + + for (i = 0; i < ucsi->cap.num_connectors; i++) + cancel_work_sync(&ucsi->connector[i].work); + + return 0; +} +EXPORT_SYMBOL_GPL(ucsi_suspend); + +int ucsi_resume(struct ucsi *ucsi) +{ + if (ucsi->connector) + queue_work(system_long_wq, &ucsi->resume_work); + return 0; +} +EXPORT_SYMBOL_GPL(ucsi_resume); + +static void ucsi_init_work(struct work_struct *work) +{ + struct ucsi *ucsi = container_of(work, struct ucsi, work.work); + int ret; + + ret = ucsi_init(ucsi); + if (ret) + dev_err_probe(ucsi->dev, ret, "PPM init failed\n"); + + if (ret == -EPROBE_DEFER) { + if (ucsi->work_count++ > UCSI_ROLE_SWITCH_WAIT_COUNT) { + dev_err(ucsi->dev, "PPM init failed, stop trying\n"); + return; + } + + queue_delayed_work(system_dfl_long_wq, &ucsi->work, + UCSI_ROLE_SWITCH_INTERVAL); + } +} + +/** + * ucsi_get_drvdata - Return private driver data pointer + * @ucsi: UCSI interface + */ +void *ucsi_get_drvdata(struct ucsi *ucsi) +{ + return ucsi->driver_data; +} +EXPORT_SYMBOL_GPL(ucsi_get_drvdata); + +/** + * ucsi_set_drvdata - Assign private driver data pointer + * @ucsi: UCSI interface + * @data: Private data pointer + */ +void ucsi_set_drvdata(struct ucsi *ucsi, void *data) +{ + ucsi->driver_data = data; +} +EXPORT_SYMBOL_GPL(ucsi_set_drvdata); + +/** + * ucsi_con_mutex_lock - Acquire the connector mutex + * @con: The connector interface to lock + * + * Returns true on success, false if the connector is disconnected + */ +bool ucsi_con_mutex_lock(struct ucsi_connector *con) +{ + bool mutex_locked = false; + bool connected = true; + + while (connected && !mutex_locked) { + mutex_locked = mutex_trylock(&con->lock) != 0; + connected = UCSI_CONSTAT(con, CONNECTED); + if (connected && !mutex_locked) + msleep(20); + } + + connected = connected && con->partner; + if (!connected && mutex_locked) + mutex_unlock(&con->lock); + + return connected; +} + +/** + * ucsi_con_mutex_unlock - Release the connector mutex + * @con: The connector interface to unlock + */ +void ucsi_con_mutex_unlock(struct ucsi_connector *con) +{ + mutex_unlock(&con->lock); +} + +/** + * ucsi_create - Allocate UCSI instance + * @dev: Device interface to the PPM (Platform Policy Manager) + * @ops: I/O routines + */ +struct ucsi *ucsi_create(struct device *dev, const struct ucsi_operations *ops) +{ + struct ucsi *ucsi; + + if (!ops || + !ops->read_version || !ops->read_cci || !ops->poll_cci || + !ops->read_message_in || !ops->sync_control || !ops->async_control) + return ERR_PTR(-EINVAL); + + ucsi = kzalloc_obj(*ucsi); + if (!ucsi) + return ERR_PTR(-ENOMEM); + + INIT_WORK(&ucsi->resume_work, ucsi_resume_work); + INIT_DELAYED_WORK(&ucsi->work, ucsi_init_work); + mutex_init(&ucsi->ppm_lock); + init_completion(&ucsi->complete); + ucsi->dev = dev; + ucsi->ops = ops; + + return ucsi; +} +EXPORT_SYMBOL_GPL(ucsi_create); + +/** + * ucsi_destroy - Free UCSI instance + * @ucsi: UCSI instance to be freed + */ +void ucsi_destroy(struct ucsi *ucsi) +{ + ucsi_debugfs_unregister(ucsi); + kfree(ucsi); +} +EXPORT_SYMBOL_GPL(ucsi_destroy); + +/** + * ucsi_register - Register UCSI interface + * @ucsi: UCSI instance + */ +int ucsi_register(struct ucsi *ucsi) +{ + int ret; + + ret = ucsi->ops->read_version(ucsi, &ucsi->version); + if (ret) + return ret; + + if (!ucsi->version) + return -ENODEV; + + /* + * Version format is JJ.M.N (JJ = Major version, M = Minor version, + * N = sub-minor version). + */ + dev_dbg(ucsi->dev, "Registered UCSI interface with version %x.%x.%x", + UCSI_BCD_GET_MAJOR(ucsi->version), + UCSI_BCD_GET_MINOR(ucsi->version), + UCSI_BCD_GET_SUBMINOR(ucsi->version)); + + queue_delayed_work(system_dfl_long_wq, &ucsi->work, 0); + + ucsi_debugfs_register(ucsi); + return 0; +} +EXPORT_SYMBOL_GPL(ucsi_register); + +/** + * ucsi_unregister - Unregister UCSI interface + * @ucsi: UCSI interface to be unregistered + * + * Unregister UCSI interface that was created with ucsi_register(). + */ +void ucsi_unregister(struct ucsi *ucsi) +{ + u64 cmd = UCSI_SET_NOTIFICATION_ENABLE; + int i; + + /* Make sure that we are not in the middle of driver initialization */ + cancel_delayed_work_sync(&ucsi->work); + cancel_work_sync(&ucsi->resume_work); + + ucsi_debugfs_unregister(ucsi); + + /* Disable notifications */ + ucsi->ops->async_control(ucsi, cmd); + + if (!ucsi->connector) + return; + + for (i = 0; i < ucsi->cap.num_connectors; i++) { + cancel_work_sync(&ucsi->connector[i].work); + ucsi_unregister_port(&ucsi->connector[i]); + lockdep_unregister_key(&ucsi->connector[i].lock_key); + } + + kfree(ucsi->connector); +} +EXPORT_SYMBOL_GPL(ucsi_unregister); + +static int __init ucsi_module_init(void) +{ + ucsi_debugfs_init(); + return 0; +} +module_init(ucsi_module_init); + +static void __exit ucsi_module_exit(void) +{ + ucsi_debugfs_exit(); +} +module_exit(ucsi_module_exit); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB Type-C Connector System Software Interface driver"); diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h new file mode 100644 index 000000000..dc594388d --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -0,0 +1,664 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __DRIVER_USB_TYPEC_UCSI_H +#define __DRIVER_USB_TYPEC_UCSI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* -------------------------------------------------------------------------- */ + +struct ucsi; +struct ucsi_altmode; +struct ucsi_connector; +struct dentry; + +/* UCSI offsets (Bytes) */ +#define UCSI_VERSION 0 +#define UCSI_CCI 4 +#define UCSI_CONTROL 8 +#define UCSI_MESSAGE_IN 16 +#define UCSI_MESSAGE_OUT 32 +#define UCSIv2_MESSAGE_OUT 272 + +/* UCSI versions */ +#define UCSI_VERSION_1_0 0x0100 +#define UCSI_VERSION_1_1 0x0110 +#define UCSI_VERSION_1_2 0x0120 +#define UCSI_VERSION_2_0 0x0200 +#define UCSI_VERSION_2_1 0x0210 +#define UCSI_VERSION_3_0 0x0300 + +#define UCSI_BCD_GET_MAJOR(_v_) (((_v_) >> 8) & 0xFF) +#define UCSI_BCD_GET_MINOR(_v_) (((_v_) >> 4) & 0x0F) +#define UCSI_BCD_GET_SUBMINOR(_v_) ((_v_) & 0x0F) + +/* + * Per USB PD 3.2, Section 6.2.1.1.5, the spec revision is represented by 2 bits + * 0b00 = 1.0, 0b01 = 2.0, 0b10 = 3.0, 0b11 = Reserved, Shall NOT be used. + */ +#define UCSI_SPEC_REVISION_TO_BCD(_v_) (((_v_) + 1) << 8) + +/* Command Status and Connector Change Indication (CCI) bits */ +#define UCSI_CCI_CONNECTOR(_c_) (((_c_) & GENMASK(7, 1)) >> 1) +#define UCSI_CCI_LENGTH(_c_) (((_c_) & GENMASK(15, 8)) >> 8) +#define UCSI_SET_CCI_LENGTH(_c_) ((_c_) << 8) +#define UCSI_CCI_NOT_SUPPORTED BIT(25) +#define UCSI_CCI_CANCEL_COMPLETE BIT(26) +#define UCSI_CCI_RESET_COMPLETE BIT(27) +#define UCSI_CCI_BUSY BIT(28) +#define UCSI_CCI_ACK_COMPLETE BIT(29) +#define UCSI_CCI_ERROR BIT(30) +#define UCSI_CCI_COMMAND_COMPLETE BIT(31) + +/** + * struct ucsi_operations - UCSI I/O operations + * @read_version: Read implemented UCSI version + * @read_cci: Read CCI register + * @poll_cci: Read CCI register while polling with notifications disabled + * @read_message_in: Read message data from UCSI + * @write_message_out: Write message data to UCSI + * @sync_control: Blocking control operation + * @async_control: Non-blocking control operation + * @update_altmodes: Squashes duplicate DP altmodes + * @update_connector: Update connector capabilities before registering + * @connector_status: Updates connector status, called holding connector lock + * @add_partner_altmodes: Start mode selection + * @remove_partner_altmodes: Clean mode selection + * + * Read and write routines for UCSI interface. @sync_write must wait for the + * Command Completion Event from the PPM before returning, and @async_write must + * return immediately after sending the data to the PPM. + */ +struct ucsi_operations { + int (*read_version)(struct ucsi *ucsi, u16 *version); + int (*read_cci)(struct ucsi *ucsi, u32 *cci); + int (*poll_cci)(struct ucsi *ucsi, u32 *cci); + int (*read_message_in)(struct ucsi *ucsi, void *val, size_t val_len); + int (*write_message_out)(struct ucsi *ucsi, void *data, size_t data_len); + int (*sync_control)(struct ucsi *ucsi, u64 command, u32 *cci, + void *data, size_t size, void *msg_out, size_t msg_out_size); + int (*async_control)(struct ucsi *ucsi, u64 command); + bool (*update_altmodes)(struct ucsi *ucsi, u8 recipient, + struct ucsi_altmode *orig, + struct ucsi_altmode *updated); + void (*update_connector)(struct ucsi_connector *con); + void (*connector_status)(struct ucsi_connector *con); + void (*add_partner_altmodes)(struct ucsi_connector *con); + void (*remove_partner_altmodes)(struct ucsi_connector *con); +}; + +struct ucsi *ucsi_create(struct device *dev, const struct ucsi_operations *ops); +void ucsi_destroy(struct ucsi *ucsi); +int ucsi_register(struct ucsi *ucsi); +void ucsi_unregister(struct ucsi *ucsi); +void *ucsi_get_drvdata(struct ucsi *ucsi); +void ucsi_set_drvdata(struct ucsi *ucsi, void *data); +bool ucsi_con_mutex_lock(struct ucsi_connector *con); +void ucsi_con_mutex_unlock(struct ucsi_connector *con); + +void ucsi_connector_change(struct ucsi *ucsi, u8 num); + +/* -------------------------------------------------------------------------- */ + +/* Commands */ +#define UCSI_PPM_RESET 0x01 +#define UCSI_CANCEL 0x02 +#define UCSI_CONNECTOR_RESET 0x03 +#define UCSI_ACK_CC_CI 0x04 +#define UCSI_SET_NOTIFICATION_ENABLE 0x05 +#define UCSI_GET_CAPABILITY 0x06 +#define UCSI_GET_CAPABILITY_SIZE 128 +#define UCSI_GET_CONNECTOR_CAPABILITY 0x07 +#define UCSI_GET_CONNECTOR_CAPABILITY_SIZE 32 +#define UCSI_SET_CCOM 0x08 +#define UCSI_SET_UOR 0x09 +#define UCSI_SET_PDM 0x0a +#define UCSI_SET_PDR 0x0b +#define UCSI_GET_ALTERNATE_MODES 0x0c +#define UCSI_GET_CAM_SUPPORTED 0x0d +#define UCSI_GET_CURRENT_CAM 0x0e +#define UCSI_SET_NEW_CAM 0x0f +#define UCSI_GET_PDOS 0x10 +#define UCSI_GET_CABLE_PROPERTY 0x11 +#define UCSI_GET_CABLE_PROPERTY_SIZE 64 +#define UCSI_GET_CONNECTOR_STATUS 0x12 +#define UCSI_GET_CONNECTOR_STATUS_SIZE 152 +#define UCSI_GET_ERROR_STATUS 0x13 +#define UCSI_SET_POWER_LEVEL 0x14 +#define UCSI_GET_ATTENTION_VDO 0x16 +#define UCSI_GET_PD_MESSAGE 0x15 +#define UCSI_GET_CAM_CS 0x18 +#define UCSI_SET_SINK_PATH 0x1c +#define UCSI_SET_PDOS 0x1d +#define UCSI_READ_POWER_LEVEL 0x1e +#define UCSI_SET_USB 0x21 +#define UCSI_GET_LPM_PPM_INFO 0x22 + +#define UCSI_CONNECTOR_NUMBER(_num_) ((u64)(_num_) << 16) +#define UCSI_COMMAND(_cmd_) ((_cmd_) & 0xff) + +#define UCSI_GET_ALTMODE_GET_CONNECTOR_NUMBER(_cmd_) (((_cmd_) >> 24) & GENMASK(6, 0)) +#define UCSI_DEFAULT_GET_CONNECTOR_NUMBER(_cmd_) (((_cmd_) >> 16) & GENMASK(6, 0)) + +/* CONNECTOR_RESET command bits */ +#define UCSI_CONNECTOR_RESET_HARD_VER_1_0 BIT(23) /* Deprecated in v1.1 */ +#define UCSI_CONNECTOR_RESET_DATA_VER_2_0 BIT(23) /* Redefined in v2.0 */ + +/* ACK_CC_CI bits */ +#define UCSI_ACK_CONNECTOR_CHANGE BIT(16) +#define UCSI_ACK_COMMAND_COMPLETE BIT(17) + +/* SET_NOTIFICATION_ENABLE command bits */ +#define UCSI_ENABLE_NTFY_CMD_COMPLETE BIT_ULL(16) +#define UCSI_ENABLE_NTFY_EXT_PWR_SRC_CHANGE BIT_ULL(17) +#define UCSI_ENABLE_NTFY_PWR_OPMODE_CHANGE BIT_ULL(18) +#define UCSI_ENABLE_NTFY_ATTENTION BIT_ULL(19) +#define UCSI_ENABLE_NTFY_LPM_FW_UPDATE_REQ BIT_ULL(20) +#define UCSI_ENABLE_NTFY_CAP_CHANGE BIT_ULL(21) +#define UCSI_ENABLE_NTFY_PWR_LEVEL_CHANGE BIT_ULL(22) +#define UCSI_ENABLE_NTFY_PD_RESET_COMPLETE BIT_ULL(23) +#define UCSI_ENABLE_NTFY_CAM_CHANGE BIT_ULL(24) +#define UCSI_ENABLE_NTFY_BAT_STATUS_CHANGE BIT_ULL(25) +#define UCSI_ENABLE_NTFY_SECURITY_REQ_PARTNER BIT_ULL(26) +#define UCSI_ENABLE_NTFY_PARTNER_CHANGE BIT_ULL(27) +#define UCSI_ENABLE_NTFY_PWR_DIR_CHANGE BIT_ULL(28) +#define UCSI_ENABLE_NTFY_SET_RETIMER_MODE BIT_ULL(29) +#define UCSI_ENABLE_NTFY_CONNECTOR_CHANGE BIT_ULL(30) +#define UCSI_ENABLE_NTFY_ERROR BIT_ULL(31) +#define UCSI_ENABLE_NTFY_SINK_PATH_STS_CHANGE BIT_ULL(32) +#define UCSI_ENABLE_NTFY_ALL 0xdbe70000 + +/* SET_UOR command bits */ +#define UCSI_SET_UOR_ROLE(_r_) (((_r_) == TYPEC_HOST ? 1 : 2) << 23) +#define UCSI_SET_UOR_ACCEPT_ROLE_SWAPS BIT(25) + +/* SET_PDF command bits */ +#define UCSI_SET_PDR_ROLE(_r_) (((_r_) == TYPEC_SOURCE ? 1 : 2) << 23) +#define UCSI_SET_PDR_ACCEPT_ROLE_SWAPS BIT(25) + +/* GET_ALTERNATE_MODES command bits */ +#define UCSI_ALTMODE_RECIPIENT(_r_) (((_r_) >> 16) & 0x7) +#define UCSI_GET_ALTMODE_RECIPIENT(_r_) ((u64)(_r_) << 16) +#define UCSI_RECIPIENT_CON 0 +#define UCSI_RECIPIENT_SOP 1 +#define UCSI_RECIPIENT_SOP_P 2 +#define UCSI_RECIPIENT_SOP_PP 3 +#define UCSI_GET_ALTMODE_CONNECTOR_NUMBER(_r_) ((u64)(_r_) << 24) +#define UCSI_ALTMODE_OFFSET(_r_) (((_r_) >> 32) & 0xff) +#define UCSI_GET_ALTMODE_OFFSET(_r_) ((u64)(_r_) << 32) +#define UCSI_GET_ALTMODE_NUM_ALTMODES(_r_) ((u64)(_r_) << 40) + +/* GET_PDOS command bits */ +#define UCSI_GET_PDOS_PARTNER_PDO(_r_) ((u64)(_r_) << 23) +#define UCSI_GET_PDOS_PDO_OFFSET(_r_) ((u64)(_r_) << 24) +#define UCSI_GET_PDOS_NUM_PDOS(_r_) ((u64)(_r_) << 32) +#define UCSI_MAX_PDOS (4) +#define UCSI_GET_PDOS_SRC_PDOS ((u64)1 << 34) + +/* GET_PD_MESSAGE command bits */ +#define UCSI_GET_PD_MESSAGE_RECIPIENT(_r_) ((u64)(_r_) << 23) +#define UCSI_GET_PD_MESSAGE_OFFSET(_r_) ((u64)(_r_) << 26) +#define UCSI_GET_PD_MESSAGE_BYTES(_r_) ((u64)(_r_) << 34) +#define UCSI_GET_PD_MESSAGE_TYPE(_r_) ((u64)(_r_) << 42) +#define UCSI_GET_PD_MESSAGE_TYPE_SNK_CAP_EXT 0 +#define UCSI_GET_PD_MESSAGE_TYPE_SRC_CAP_EXT 1 +#define UCSI_GET_PD_MESSAGE_TYPE_BAT_CAP 2 +#define UCSI_GET_PD_MESSAGE_TYPE_BAT_STAT 3 +#define UCSI_GET_PD_MESSAGE_TYPE_IDENTITY 4 +#define UCSI_GET_PD_MESSAGE_TYPE_REVISION 5 + +/* Data length bits */ +#define UCSI_COMMAND_DATA_LEN(_cmd_) (((_cmd_) >> 8) & GENMASK(7, 0)) + +/* -------------------------------------------------------------------------- */ + +/* Error information returned by PPM in response to GET_ERROR_STATUS command. */ +#define UCSI_ERROR_UNREGONIZED_CMD BIT(0) +#define UCSI_ERROR_INVALID_CON_NUM BIT(1) +#define UCSI_ERROR_INVALID_CMD_ARGUMENT BIT(2) +#define UCSI_ERROR_INCOMPATIBLE_PARTNER BIT(3) +#define UCSI_ERROR_CC_COMMUNICATION_ERR BIT(4) +#define UCSI_ERROR_DEAD_BATTERY BIT(5) +#define UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL BIT(6) +#define UCSI_ERROR_OVERCURRENT BIT(7) +#define UCSI_ERROR_UNDEFINED BIT(8) +#define UCSI_ERROR_PARTNER_REJECTED_SWAP BIT(9) +#define UCSI_ERROR_HARD_RESET BIT(10) +#define UCSI_ERROR_PPM_POLICY_CONFLICT BIT(11) +#define UCSI_ERROR_SWAP_REJECTED BIT(12) +#define UCSI_ERROR_REVERSE_CURRENT_PROTECTION BIT(13) +#define UCSI_ERROR_SET_SINK_PATH_REJECTED BIT(14) + +#define UCSI_SET_NEW_CAM_ENTER(x) (((x) >> 23) & 0x1) +#define UCSI_SET_NEW_CAM_GET_AM(x) (((x) >> 24) & 0xff) +#define UCSI_SET_NEW_CAM_AM_MASK (0xff << 24) +#define UCSI_SET_NEW_CAM_SET_AM(x) (((x) & 0xff) << 24) +#define UCSI_CMD_CONNECTOR_MASK (0x7) + +/* Data structure filled by PPM in response to GET_CAPABILITY command. */ +struct ucsi_capability { + u32 attributes; +#define UCSI_CAP_ATTR_DISABLE_STATE BIT(0) +#define UCSI_CAP_ATTR_BATTERY_CHARGING BIT(1) +#define UCSI_CAP_ATTR_USB_PD BIT(2) +#define UCSI_CAP_ATTR_TYPEC_CURRENT BIT(6) +#define UCSI_CAP_ATTR_POWER_AC_SUPPLY BIT(8) +#define UCSI_CAP_ATTR_POWER_OTHER BIT(10) +#define UCSI_CAP_ATTR_POWER_VBUS BIT(14) + u8 num_connectors; + u16 features; +#define UCSI_CAP_SET_UOM BIT(0) +#define UCSI_CAP_SET_PDM BIT(1) +#define UCSI_CAP_ALT_MODE_DETAILS BIT(2) +#define UCSI_CAP_ALT_MODE_OVERRIDE BIT(3) +#define UCSI_CAP_PDO_DETAILS BIT(4) +#define UCSI_CAP_CABLE_DETAILS BIT(5) +#define UCSI_CAP_EXT_SUPPLY_NOTIFICATIONS BIT(6) +#define UCSI_CAP_PD_RESET BIT(7) +#define UCSI_CAP_GET_PD_MESSAGE BIT(8) +#define UCSI_CAP_GET_ATTENTION_VDO BIT(9) +#define UCSI_CAP_FW_UPDATE_REQUEST BIT(10) +#define UCSI_CAP_NEGOTIATED_PWR_LEVEL_CHANGE BIT(11) +#define UCSI_CAP_SECURITY_REQUEST BIT(12) +#define UCSI_CAP_SET_RETIMER_MODE BIT(13) +#define UCSI_CAP_CHUNKING_SUPPORT BIT(14) + u8 reserved_1; + u8 num_alt_modes; + u8 reserved_2; + u16 bc_version; + u16 pd_version; + u16 typec_version; +} __packed; + +struct ucsi_altmode { + u16 svid; + u32 mid; +} __packed; + +/* Data structure filled by PPM in response to GET_CABLE_PROPERTY command. */ +struct ucsi_cable_property { + u16 speed_supported; + u8 current_capability; + u8 flags; +#define UCSI_CABLE_PROP_FLAG_VBUS_IN_CABLE BIT(0) +#define UCSI_CABLE_PROP_FLAG_ACTIVE_CABLE BIT(1) +#define UCSI_CABLE_PROP_FLAG_DIRECTIONALITY BIT(2) +#define UCSI_CABLE_PROP_FLAG_PLUG_TYPE(_f_) (((_f_) & GENMASK(4, 3)) >> 3) +#define UCSI_CABLE_PROPERTY_PLUG_TYPE_A 0 +#define UCSI_CABLE_PROPERTY_PLUG_TYPE_B 1 +#define UCSI_CABLE_PROPERTY_PLUG_TYPE_C 2 +#define UCSI_CABLE_PROPERTY_PLUG_OTHER 3 +#define UCSI_CABLE_PROP_FLAG_MODE_SUPPORT BIT(5) +#define UCSI_CABLE_PROP_FLAG_PD_MAJOR_REV(_f_) (((_f_) & GENMASK(7, 6)) >> 6) +#define UCSI_CABLE_PROP_FLAG_PD_MAJOR_REV_AS_BCD(_f_) \ + UCSI_SPEC_REVISION_TO_BCD(UCSI_CABLE_PROP_FLAG_PD_MAJOR_REV(_f_)) + u8 latency; +} __packed; + +/* Get Connector Capability Fields. */ +#define UCSI_CONCAP_OPMODE UCSI_DECLARE_BITFIELD(0, 0, 8) +#define UCSI_CONCAP_OPMODE_DFP UCSI_DECLARE_BITFIELD(0, 0, 1) +#define UCSI_CONCAP_OPMODE_UFP UCSI_DECLARE_BITFIELD(0, 1, 1) +#define UCSI_CONCAP_OPMODE_DRP UCSI_DECLARE_BITFIELD(0, 2, 1) +#define UCSI_CONCAP_OPMODE_AUDIO_ACCESSORY UCSI_DECLARE_BITFIELD(0, 3, 1) +#define UCSI_CONCAP_OPMODE_DEBUG_ACCESSORY UCSI_DECLARE_BITFIELD(0, 4, 1) +#define UCSI_CONCAP_OPMODE_USB2 UCSI_DECLARE_BITFIELD(0, 5, 1) +#define UCSI_CONCAP_OPMODE_USB3 UCSI_DECLARE_BITFIELD(0, 6, 1) +#define UCSI_CONCAP_OPMODE_ALT_MODE UCSI_DECLARE_BITFIELD(0, 7, 1) +#define UCSI_CONCAP_PROVIDER UCSI_DECLARE_BITFIELD(0, 8, 1) +#define UCSI_CONCAP_CONSUMER UCSI_DECLARE_BITFIELD(0, 9, 1) +#define UCSI_CONCAP_SWAP_TO_DFP_V1_1 UCSI_DECLARE_BITFIELD_V1_1(10, 1) +#define UCSI_CONCAP_SWAP_TO_UFP_V1_1 UCSI_DECLARE_BITFIELD_V1_1(11, 1) +#define UCSI_CONCAP_SWAP_TO_SRC_V1_1 UCSI_DECLARE_BITFIELD_V1_1(12, 1) +#define UCSI_CONCAP_SWAP_TO_SNK_V1_1 UCSI_DECLARE_BITFIELD_V1_1(13, 1) +#define UCSI_CONCAP_EXT_OPMODE_V2_0 UCSI_DECLARE_BITFIELD_V2_0(14, 8) +#define UCSI_CONCAP_EXT_OPMODE_USB4_GEN2_V2_0 UCSI_DECLARE_BITFIELD_V2_0(14, 1) +#define UCSI_CONCAP_EXT_OPMODE_EPR_SRC_V2_0 UCSI_DECLARE_BITFIELD_V2_0(15, 1) +#define UCSI_CONCAP_EXT_OPMODE_EPR_SINK_V2_0 UCSI_DECLARE_BITFIELD_V2_0(16, 1) +#define UCSI_CONCAP_EXT_OPMODE_USB4_GEN3_V2_0 UCSI_DECLARE_BITFIELD_V2_0(17, 1) +#define UCSI_CONCAP_EXT_OPMODE_USB4_GEN4_V2_0 UCSI_DECLARE_BITFIELD_V2_0(18, 1) +#define UCSI_CONCAP_MISC_V2_0 UCSI_DECLARE_BITFIELD_V2_0(22, 4) +#define UCSI_CONCAP_MISC_FW_UPDATE_V2_0 UCSI_DECLARE_BITFIELD_V2_0(22, 1) +#define UCSI_CONCAP_MISC_SECURITY_V2_0 UCSI_DECLARE_BITFIELD_V2_0(23, 1) +#define UCSI_CONCAP_REV_CURR_PROT_SUPPORT_V2_0 UCSI_DECLARE_BITFIELD_V2_0(26, 1) +#define UCSI_CONCAP_PARTNER_PD_REVISION_V2_1 UCSI_DECLARE_BITFIELD_V2_1(27, 2) + +/* Helpers for USB capability checks. */ +#define UCSI_CONCAP_USB2_SUPPORT(_con_) UCSI_CONCAP((_con_), OPMODE_USB2) +#define UCSI_CONCAP_USB3_SUPPORT(_con_) UCSI_CONCAP((_con_), OPMODE_USB3) +#define UCSI_CONCAP_USB4_SUPPORT(_con_) \ + ((_con_)->ucsi->version >= UCSI_VERSION_2_0 && \ + (UCSI_CONCAP((_con_), EXT_OPMODE_USB4_GEN2_V2_0) | \ + UCSI_CONCAP((_con_), EXT_OPMODE_USB4_GEN3_V2_0) | \ + UCSI_CONCAP((_con_), EXT_OPMODE_USB4_GEN4_V2_0))) + +/* Get Connector Status Fields. */ +#define UCSI_CONSTAT_CHANGE UCSI_DECLARE_BITFIELD(0, 0, 16) +#define UCSI_CONSTAT_PWR_OPMODE UCSI_DECLARE_BITFIELD(0, 16, 3) +#define UCSI_CONSTAT_PWR_OPMODE_NONE 0 +#define UCSI_CONSTAT_PWR_OPMODE_DEFAULT 1 +#define UCSI_CONSTAT_PWR_OPMODE_BC 2 +#define UCSI_CONSTAT_PWR_OPMODE_PD 3 +#define UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5 4 +#define UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0 5 +#define UCSI_CONSTAT_CONNECTED UCSI_DECLARE_BITFIELD(0, 19, 1) +#define UCSI_CONSTAT_PWR_DIR UCSI_DECLARE_BITFIELD(0, 20, 1) +#define UCSI_CONSTAT_PARTNER_FLAGS UCSI_DECLARE_BITFIELD(0, 21, 8) +#define UCSI_CONSTAT_PARTNER_FLAG_USB UCSI_DECLARE_BITFIELD(0, 21, 1) +#define UCSI_CONSTAT_PARTNER_FLAG_ALT_MODE UCSI_DECLARE_BITFIELD(0, 22, 1) +#define UCSI_CONSTAT_PARTNER_FLAG_USB4_GEN3 UCSI_DECLARE_BITFIELD(0, 23, 1) +#define UCSI_CONSTAT_PARTNER_FLAG_USB4_GEN4 UCSI_DECLARE_BITFIELD(0, 24, 1) +#define UCSI_CONSTAT_PARTNER_TYPE UCSI_DECLARE_BITFIELD(0, 29, 3) +#define UCSI_CONSTAT_PARTNER_TYPE_DFP 1 +#define UCSI_CONSTAT_PARTNER_TYPE_UFP 2 +#define UCSI_CONSTAT_PARTNER_TYPE_CABLE 3 /* Powered Cable */ +#define UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP 4 /* Powered Cable */ +#define UCSI_CONSTAT_PARTNER_TYPE_DEBUG 5 +#define UCSI_CONSTAT_PARTNER_TYPE_AUDIO 6 +#define UCSI_CONSTAT_RDO UCSI_DECLARE_BITFIELD(0, 32, 32) +#define UCSI_CONSTAT_BC_STATUS UCSI_DECLARE_BITFIELD(0, 64, 2) +#define UCSI_CONSTAT_BC_NOT_CHARGING 0 +#define UCSI_CONSTAT_BC_NOMINAL_CHARGING 1 +#define UCSI_CONSTAT_BC_SLOW_CHARGING 2 +#define UCSI_CONSTAT_BC_TRICKLE_CHARGING 3 +#define UCSI_CONSTAT_PD_VERSION_V1_2 UCSI_DECLARE_BITFIELD_V1_2(70, 16) +#define UCSI_CONSTAT_ORIENTATION UCSI_DECLARE_BITFIELD_V2_0(86, 1) +#define UCSI_CONSTAT_ORIENTATION_NORMAL 0 +#define UCSI_CONSTAT_ORIENTATION_REVERSE 1 +#define UCSI_CONSTAT_SINK_PATH_STATUS_V2_0 UCSI_DECLARE_BITFIELD_V2_0(87, 1) +#define UCSI_CONSTAT_SINK_PATH_DISABLED 0 +#define UCSI_CONSTAT_SINK_PATH_ENABLED 1 +#define UCSI_CONSTAT_PWR_READING_READY_V2_1 UCSI_DECLARE_BITFIELD_V2_1(89, 1) +#define UCSI_CONSTAT_CURRENT_SCALE_V2_1 UCSI_DECLARE_BITFIELD_V2_1(90, 3) +#define UCSI_CONSTAT_PEAK_CURRENT_V2_1 UCSI_DECLARE_BITFIELD_V2_1(93, 16) +#define UCSI_CONSTAT_AVG_CURRENT_V2_1 UCSI_DECLARE_BITFIELD_V2_1(109, 16) +#define UCSI_CONSTAT_VOLTAGE_SCALE_V2_1 UCSI_DECLARE_BITFIELD_V2_1(125, 4) +#define UCSI_CONSTAT_VBUS_VOLTAGE_V2_1 UCSI_DECLARE_BITFIELD_V2_1(129, 16) +#define UCSI_CONSTAT_CURR_SCALE_MULT 5 +#define UCSI_CONSTAT_VOLT_SCALE_MULT 5 + +/* Connector Status Change Bits. */ +#define UCSI_CONSTAT_EXT_SUPPLY_CHANGE BIT(1) +#define UCSI_CONSTAT_POWER_OPMODE_CHANGE BIT(2) +#define UCSI_CONSTAT_PDOS_CHANGE BIT(5) +#define UCSI_CONSTAT_POWER_LEVEL_CHANGE BIT(6) +#define UCSI_CONSTAT_PD_RESET_COMPLETE BIT(7) +#define UCSI_CONSTAT_CAM_CHANGE BIT(8) +#define UCSI_CONSTAT_BC_CHANGE BIT(9) +#define UCSI_CONSTAT_PARTNER_CHANGE BIT(11) +#define UCSI_CONSTAT_POWER_DIR_CHANGE BIT(12) +#define UCSI_CONSTAT_SINK_PATH_CHANGE BIT(13) +#define UCSI_CONSTAT_CONNECT_CHANGE BIT(14) +#define UCSI_CONSTAT_ERROR BIT(15) + +#define UCSI_DECLARE_BITFIELD_V1_1(_offset_, _size_) \ + UCSI_DECLARE_BITFIELD(UCSI_VERSION_1_1, (_offset_), (_size_)) +#define UCSI_DECLARE_BITFIELD_V1_2(_offset_, _size_) \ + UCSI_DECLARE_BITFIELD(UCSI_VERSION_1_2, (_offset_), (_size_)) +#define UCSI_DECLARE_BITFIELD_V2_0(_offset_, _size_) \ + UCSI_DECLARE_BITFIELD(UCSI_VERSION_2_0, (_offset_), (_size_)) +#define UCSI_DECLARE_BITFIELD_V2_1(_offset_, _size_) \ + UCSI_DECLARE_BITFIELD(UCSI_VERSION_2_1, (_offset_), (_size_)) +#define UCSI_DECLARE_BITFIELD_V3_0(_offset_, _size_) \ + UCSI_DECLARE_BITFIELD(UCSI_VERSION_3_0, (_offset_), (_size_)) + +#define UCSI_DECLARE_BITFIELD(_ver_, _offset_, _size_) \ +(struct ucsi_bitfield) { \ + .version = _ver_, \ + .offset = _offset_, \ + .size = _size_, \ +} + +struct ucsi_bitfield { + const u16 version; + const u8 offset; + const u8 size; +}; + +/** + * ucsi_bitfield_read - Read a field from UCSI command response + * @_map_: UCSI command response + * @_field_: The field offset in the response data structure + * @_ver_: UCSI version where the field was introduced + * + * Reads the fields in the command responses by first checking that the field is + * valid with the UCSI interface version that is used in the system. + * @_ver_ is the minimum UCSI version for the @_field_. If the UCSI interface is + * older than @_ver_, a warning is generated. + * + * Caveats: + * - Removed fields are not checked - @_ver_ is just the minimum UCSI version. + * + * Returns the value of @_field_, or 0 when the UCSI interface is older than + * @_ver_. + */ +#define ucsi_bitfield_read(_map_, _field_, _ver_) \ +({ \ + struct ucsi_bitfield f = (_field_); \ + WARN((_ver_) < f.version, \ + "Access to unsupported field at offset 0x%x (need version %04x)", \ + f.offset, f.version) ? 0 : \ + bitmap_read((_map_), f.offset, f.size); \ +}) + +/* Helpers to access cached command responses. */ +#define UCSI_CONCAP(_con_, _field_) \ + ucsi_bitfield_read((_con_)->cap, UCSI_CONCAP_##_field_, (_con_)->ucsi->version) + +#define UCSI_CONSTAT(_con_, _field_) \ + ucsi_bitfield_read((_con_)->status, UCSI_CONSTAT_##_field_, (_con_)->ucsi->version) + +/* -------------------------------------------------------------------------- */ + +#define MESSAGE_OUT_MAX_LEN 256 + +struct ucsi_debugfs_entry { + u64 command; + struct ucsi_data { + u64 low; + u64 high; + u64 ext; + } response; + int status; + u8 message_out[MESSAGE_OUT_MAX_LEN]; + struct dentry *dentry; +}; + +struct ucsi { + u16 version; + struct device *dev; + void *driver_data; + + const struct ucsi_operations *ops; + + struct ucsi_capability cap; + struct ucsi_connector *connector; + struct ucsi_debugfs_entry *debugfs; + + struct work_struct resume_work; + struct delayed_work work; + int work_count; +#define UCSI_ROLE_SWITCH_RETRY_PER_HZ 10 +#define UCSI_ROLE_SWITCH_INTERVAL (HZ / UCSI_ROLE_SWITCH_RETRY_PER_HZ) +#define UCSI_ROLE_SWITCH_WAIT_COUNT (10 * UCSI_ROLE_SWITCH_RETRY_PER_HZ) + + /* PPM Communication lock */ + struct mutex ppm_lock; + + /* The latest "Notification Enable" bits (SET_NOTIFICATION_ENABLE) */ + u64 ntfy; + + /* PPM communication flags */ + unsigned long flags; +#define EVENT_PENDING 0 +#define COMMAND_PENDING 1 +#define ACK_PENDING 2 + struct completion complete; + + unsigned long quirks; +#define UCSI_NO_PARTNER_PDOS BIT(0) /* Don't read partner's PDOs */ +#define UCSI_DELAY_DEVICE_PDOS BIT(1) /* Reading PDOs fails until the parter is in PD mode */ + +/* USB4 connection can imply that USB communcation is supported */ +#define UCSI_USB4_IMPLIES_USB BIT(2) +}; + +#define UCSI_MAX_DATA_LENGTH(u) (((u)->version < UCSI_VERSION_2_0) ? 0x10 : 0xff) +#define UCSI_MAX_MSG_OUT_DATA_LEN(u) \ + (((u)->version >= UCSI_VERSION_3_0) ? 255 : \ + (((u)->version >= UCSI_VERSION_2_0) ? 256 : 16)) + +#define UCSI_MAX_SVID 5 +#define UCSI_MAX_ALTMODES (UCSI_MAX_SVID * 6) + +#define UCSI_TYPEC_VSAFE5V 5000 +#define UCSI_TYPEC_DEFAULT_CURRENT 100 +#define UCSI_TYPEC_1_5_CURRENT 1500 +#define UCSI_TYPEC_3_0_CURRENT 3000 + +struct ucsi_connector { + int num; + + struct ucsi *ucsi; + struct mutex lock; /* port lock */ + struct lock_class_key lock_key; + struct work_struct work; + struct completion complete; + struct workqueue_struct *wq; + struct list_head partner_tasks; + + struct typec_port *port; + struct typec_partner *partner; + struct typec_cable *cable; + struct typec_plug *plug; + + struct typec_altmode *port_altmode[UCSI_MAX_ALTMODES]; + struct typec_altmode *partner_altmode[UCSI_MAX_ALTMODES]; + struct typec_altmode *plug_altmode[UCSI_MAX_ALTMODES]; + + struct typec_capability typec_cap; + + /* Cached command responses. */ + DECLARE_BITMAP(cap, UCSI_GET_CONNECTOR_CAPABILITY_SIZE); + DECLARE_BITMAP(status, UCSI_GET_CONNECTOR_STATUS_SIZE); + + struct power_supply *psy; + struct power_supply_desc psy_desc; + u32 rdo; + u32 src_pdos[PDO_MAX_OBJECTS]; + int num_pdos; + + u32 peak_current; + u32 avg_current; + u32 vbus_voltage; + + /* USB PD objects */ + struct usb_power_delivery *pd; + struct usb_power_delivery_capabilities *port_source_caps; + struct usb_power_delivery_capabilities *port_sink_caps; + struct usb_power_delivery *partner_pd; + struct usb_power_delivery_capabilities *partner_source_caps; + struct usb_power_delivery_capabilities *partner_sink_caps; + + struct usb_role_switch *usb_role_sw; + + /* USB PD identity */ + struct usb_pd_identity partner_identity; + struct usb_pd_identity cable_identity; +}; + +int ucsi_send_command(struct ucsi *ucsi, u64 command, + void *retval, size_t size); +int ucsi_write_message_out_command(struct ucsi *ucsi, u64 command, + void *retval, size_t size, + void *msg_out, size_t msg_out_size); + +void ucsi_altmode_update_active(struct ucsi_connector *con); +int ucsi_suspend(struct ucsi *ucsi); +int ucsi_resume(struct ucsi *ucsi); + +void ucsi_notify_common(struct ucsi *ucsi, u32 cci); +int ucsi_sync_control_common(struct ucsi *ucsi, u64 command, u32 *cci, + void *data, size_t size, void *msg_out, + size_t msg_out_size); + +#if IS_ENABLED(CONFIG_POWER_SUPPLY) +int ucsi_register_port_psy(struct ucsi_connector *con); +void ucsi_unregister_port_psy(struct ucsi_connector *con); +void ucsi_port_psy_changed(struct ucsi_connector *con); +#else +static inline int ucsi_register_port_psy(struct ucsi_connector *con) { return 0; } +static inline void ucsi_unregister_port_psy(struct ucsi_connector *con) { } +static inline void ucsi_port_psy_changed(struct ucsi_connector *con) { } +#endif /* CONFIG_POWER_SUPPLY */ + +#if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE) +struct typec_altmode * +ucsi_register_displayport(struct ucsi_connector *con, + bool override, int offset, + struct typec_altmode_desc *desc); + +void ucsi_displayport_remove_partner(struct typec_altmode *adev); + +#else +static inline struct typec_altmode * +ucsi_register_displayport(struct ucsi_connector *con, + bool override, int offset, + struct typec_altmode_desc *desc) +{ + return typec_port_register_altmode(con->port, desc); +} + +static inline void +ucsi_displayport_remove_partner(struct typec_altmode *adev) { } +#endif /* CONFIG_TYPEC_DP_ALTMODE */ + +#if IS_ENABLED(CONFIG_TYPEC_TBT_ALTMODE) +struct typec_altmode * +ucsi_register_thunderbolt(struct ucsi_connector *con, + bool override, int offset, + struct typec_altmode_desc *desc); + +void ucsi_thunderbolt_remove_partner(struct typec_altmode *adev); +#else +static inline struct typec_altmode * +ucsi_register_thunderbolt(struct ucsi_connector *con, + bool override, int offset, + struct typec_altmode_desc *desc) +{ + return typec_port_register_altmode(con->port, desc); +} + +static inline void +ucsi_thunderbolt_remove_partner(struct typec_altmode *adev) { } +#endif /* CONFIG_TYPEC_TBT_ALTMODE */ + +#ifdef CONFIG_DEBUG_FS +void ucsi_debugfs_init(void); +void ucsi_debugfs_exit(void); +void ucsi_debugfs_register(struct ucsi *ucsi); +void ucsi_debugfs_unregister(struct ucsi *ucsi); +#else +static inline void ucsi_debugfs_init(void) { } +static inline void ucsi_debugfs_exit(void) { } +static inline void ucsi_debugfs_register(struct ucsi *ucsi) { } +static inline void ucsi_debugfs_unregister(struct ucsi *ucsi) { } +#endif /* CONFIG_DEBUG_FS */ + +/* + * NVIDIA VirtualLink (svid 0x955) has two altmode. VirtualLink + * DP mode with vdo=0x1 and NVIDIA test mode with vdo=0x3 + */ +#define USB_TYPEC_NVIDIA_VLINK_DP_VDO 0x1 +#define USB_TYPEC_NVIDIA_VLINK_DBG_VDO 0x3 + +#endif /* __DRIVER_USB_TYPEC_UCSI_H */ diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c new file mode 100644 index 000000000..18286d3e9 --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi_acpi.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UCSI ACPI driver + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus + */ + +#include +#include +#include +#include + +#include "ucsi.h" + +#define UCSI_DSM_UUID "6f8398c2-7ca4-11e4-ad36-631042b5008f" +#define UCSI_DSM_FUNC_WRITE 1 +#define UCSI_DSM_FUNC_READ 2 + +struct ucsi_acpi { + struct device *dev; + struct ucsi *ucsi; + void *base; + bool check_bogus_event; + guid_t guid; + u64 cmd; +}; + +static int ucsi_acpi_dsm(struct ucsi_acpi *ua, int func) +{ + union acpi_object *obj; + + obj = acpi_evaluate_dsm(ACPI_HANDLE(ua->dev), &ua->guid, 1, func, + NULL); + if (!obj) { + dev_err(ua->dev, "%s: failed to evaluate _DSM %d\n", + __func__, func); + return -EIO; + } + + ACPI_FREE(obj); + return 0; +} + +static int ucsi_acpi_read_version(struct ucsi *ucsi, u16 *version) +{ + struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); + int ret; + + ret = ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_READ); + if (ret) + return ret; + + memcpy(version, ua->base + UCSI_VERSION, sizeof(*version)); + + return 0; +} + +static int ucsi_acpi_read_cci(struct ucsi *ucsi, u32 *cci) +{ + struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); + + memcpy(cci, ua->base + UCSI_CCI, sizeof(*cci)); + + return 0; +} + +static int ucsi_acpi_poll_cci(struct ucsi *ucsi, u32 *cci) +{ + struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); + int ret; + + ret = ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_READ); + if (ret) + return ret; + + return ucsi_acpi_read_cci(ucsi, cci); +} + +static int ucsi_acpi_read_message_in(struct ucsi *ucsi, void *val, size_t val_len) +{ + struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); + + memcpy(val, ua->base + UCSI_MESSAGE_IN, val_len); + + return 0; +} + +static int ucsi_acpi_write_message_out(struct ucsi *ucsi, void *data, size_t data_len) +{ + struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); + + if (!data || !data_len) + return -EINVAL; + + if (ucsi->version <= UCSI_VERSION_1_2) + memcpy(ua->base + UCSI_MESSAGE_OUT, data, data_len); + else + memcpy(ua->base + UCSIv2_MESSAGE_OUT, data, data_len); + + return 0; +} + +static int ucsi_acpi_async_control(struct ucsi *ucsi, u64 command) +{ + struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); + + memcpy(ua->base + UCSI_CONTROL, &command, sizeof(command)); + ua->cmd = command; + + return ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_WRITE); +} + +static const struct ucsi_operations ucsi_acpi_ops = { + .read_version = ucsi_acpi_read_version, + .read_cci = ucsi_acpi_read_cci, + .poll_cci = ucsi_acpi_poll_cci, + .read_message_in = ucsi_acpi_read_message_in, + .write_message_out = ucsi_acpi_write_message_out, + .sync_control = ucsi_sync_control_common, + .async_control = ucsi_acpi_async_control +}; + +static int ucsi_gram_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, + void *val, size_t len, void *msg_out, + size_t msg_out_size) +{ + u16 bogus_change = UCSI_CONSTAT_POWER_LEVEL_CHANGE | + UCSI_CONSTAT_PDOS_CHANGE; + struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); + int ret; + + ret = ucsi_sync_control_common(ucsi, command, cci, val, len, + msg_out, msg_out_size); + if (ret < 0) + return ret; + + if (UCSI_COMMAND(ua->cmd) == UCSI_GET_PDOS && + ua->cmd & UCSI_GET_PDOS_PARTNER_PDO(1) && + ua->cmd & UCSI_GET_PDOS_SRC_PDOS) + ua->check_bogus_event = true; + + if (UCSI_COMMAND(ua->cmd) == UCSI_GET_CONNECTOR_STATUS && + ua->check_bogus_event) { + /* Clear the bogus change */ + if (*(u16 *)val == bogus_change) + *(u16 *)val = 0; + + ua->check_bogus_event = false; + } + + return ret; +} + +static const struct ucsi_operations ucsi_gram_ops = { + .read_version = ucsi_acpi_read_version, + .read_cci = ucsi_acpi_read_cci, + .poll_cci = ucsi_acpi_poll_cci, + .read_message_in = ucsi_acpi_read_message_in, + .sync_control = ucsi_gram_sync_control, + .async_control = ucsi_acpi_async_control +}; + +static const struct dmi_system_id ucsi_acpi_quirks[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LG Electronics"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "LG gram PC"), + DMI_MATCH(DMI_PRODUCT_NAME, "90Q"), + }, + .driver_data = (void *)&ucsi_gram_ops, + }, + { } +}; + +static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data) +{ + struct ucsi_acpi *ua = data; + u32 cci; + int ret; + + ret = ua->ucsi->ops->read_cci(ua->ucsi, &cci); + if (ret) + return; + + ucsi_notify_common(ua->ucsi, cci); +} + +static int ucsi_acpi_probe(struct platform_device *pdev) +{ + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + const struct ucsi_operations *ops = &ucsi_acpi_ops; + const struct dmi_system_id *id; + struct ucsi_acpi *ua; + struct resource *res; + acpi_status status; + int ret; + + if (adev->dep_unmet) + return -EPROBE_DEFER; + + ua = devm_kzalloc(&pdev->dev, sizeof(*ua), GFP_KERNEL); + if (!ua) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory resource\n"); + return -ENODEV; + } + + ua->base = devm_memremap(&pdev->dev, res->start, resource_size(res), MEMREMAP_WB); + if (IS_ERR(ua->base)) + return PTR_ERR(ua->base); + + ret = guid_parse(UCSI_DSM_UUID, &ua->guid); + if (ret) + return ret; + + ua->dev = &pdev->dev; + + id = dmi_first_match(ucsi_acpi_quirks); + if (id) + ops = id->driver_data; + + ua->ucsi = ucsi_create(&pdev->dev, ops); + if (IS_ERR(ua->ucsi)) + return PTR_ERR(ua->ucsi); + + ucsi_set_drvdata(ua->ucsi, ua); + + status = acpi_install_notify_handler(ACPI_HANDLE(&pdev->dev), + ACPI_DEVICE_NOTIFY, + ucsi_acpi_notify, ua); + if (ACPI_FAILURE(status)) { + dev_err(&pdev->dev, "failed to install notify handler\n"); + ucsi_destroy(ua->ucsi); + return -ENODEV; + } + + ret = ucsi_register(ua->ucsi); + if (ret) { + acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), + ACPI_DEVICE_NOTIFY, + ucsi_acpi_notify); + ucsi_destroy(ua->ucsi); + return ret; + } + + platform_set_drvdata(pdev, ua); + + return 0; +} + +static void ucsi_acpi_remove(struct platform_device *pdev) +{ + struct ucsi_acpi *ua = platform_get_drvdata(pdev); + + ucsi_unregister(ua->ucsi); + ucsi_destroy(ua->ucsi); + + acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), ACPI_DEVICE_NOTIFY, + ucsi_acpi_notify); +} + +static int ucsi_acpi_suspend(struct device *dev) +{ + struct ucsi_acpi *ua = dev_get_drvdata(dev); + + return ucsi_suspend(ua->ucsi); +} + +static int ucsi_acpi_resume(struct device *dev) +{ + struct ucsi_acpi *ua = dev_get_drvdata(dev); + + return ucsi_resume(ua->ucsi); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ucsi_acpi_pm_ops, ucsi_acpi_suspend, + ucsi_acpi_resume); + +static const struct acpi_device_id ucsi_acpi_match[] = { + { "PNP0CA0", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, ucsi_acpi_match); + +static struct platform_driver ucsi_acpi_platform_driver = { + .driver = { + .name = "ucsi_acpi", + .pm = pm_ptr(&ucsi_acpi_pm_ops), + .acpi_match_table = ACPI_PTR(ucsi_acpi_match), + }, + .probe = ucsi_acpi_probe, + .remove = ucsi_acpi_remove, +}; + +module_platform_driver(ucsi_acpi_platform_driver); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("UCSI ACPI driver"); diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c new file mode 100644 index 000000000..91c2958a7 --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -0,0 +1,1599 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UCSI driver for Cypress CCGx Type-C controller + * + * Copyright (C) 2017-2018 NVIDIA Corporation. All rights reserved. + * Author: Ajay Gupta + * + * Some code borrowed from drivers/usb/typec/ucsi/ucsi_acpi.c + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ucsi.h" + +enum enum_fw_mode { + BOOT, /* bootloader */ + FW1, /* FW partition-1 (contains secondary fw) */ + FW2, /* FW partition-2 (contains primary fw) */ + FW_INVALID, +}; + +#define CCGX_RAB_DEVICE_MODE 0x0000 +#define CCGX_RAB_INTR_REG 0x0006 +#define DEV_INT BIT(0) +#define PORT0_INT BIT(1) +#define PORT1_INT BIT(2) +#define UCSI_READ_INT BIT(7) +#define CCGX_RAB_JUMP_TO_BOOT 0x0007 +#define TO_BOOT 'J' +#define TO_ALT_FW 'A' +#define CCGX_RAB_RESET_REQ 0x0008 +#define RESET_SIG 'R' +#define CMD_RESET_I2C 0x0 +#define CMD_RESET_DEV 0x1 +#define CCGX_RAB_ENTER_FLASHING 0x000A +#define FLASH_ENTER_SIG 'P' +#define CCGX_RAB_VALIDATE_FW 0x000B +#define CCGX_RAB_FLASH_ROW_RW 0x000C +#define FLASH_SIG 'F' +#define FLASH_RD_CMD 0x0 +#define FLASH_WR_CMD 0x1 +#define FLASH_FWCT1_WR_CMD 0x2 +#define FLASH_FWCT2_WR_CMD 0x3 +#define FLASH_FWCT_SIG_WR_CMD 0x4 +#define CCGX_RAB_READ_ALL_VER 0x0010 +#define CCGX_RAB_READ_FW2_VER 0x0020 +#define CCGX_RAB_UCSI_CONTROL 0x0039 +#define CCGX_RAB_UCSI_CONTROL_START BIT(0) +#define CCGX_RAB_UCSI_CONTROL_STOP BIT(1) +#define CCGX_RAB_UCSI_DATA_BLOCK(offset) (0xf000 | ((offset) & 0xff)) +#define REG_FLASH_RW_MEM 0x0200 +#define DEV_REG_IDX CCGX_RAB_DEVICE_MODE +#define CCGX_RAB_PDPORT_ENABLE 0x002C +#define PDPORT_1 BIT(0) +#define PDPORT_2 BIT(1) +#define CCGX_RAB_RESPONSE 0x007E +#define ASYNC_EVENT BIT(7) + +/* CCGx events & async msg codes */ +#define RESET_COMPLETE 0x80 +#define EVENT_INDEX RESET_COMPLETE +#define PORT_CONNECT_DET 0x84 +#define PORT_DISCONNECT_DET 0x85 +#define ROLE_SWAP_COMPELETE 0x87 + +/* ccg firmware */ +#define CYACD_LINE_SIZE 527 +#define CCG4_ROW_SIZE 256 +#define FW1_METADATA_ROW 0x1FF +#define FW2_METADATA_ROW 0x1FE +#define FW_CFG_TABLE_SIG_SIZE 256 + +static int secondary_fw_min_ver = 41; + +enum enum_flash_mode { + SECONDARY_BL, /* update secondary using bootloader */ + PRIMARY, /* update primary using secondary */ + SECONDARY, /* update secondary using primary */ + FLASH_NOT_NEEDED, /* update not required */ + FLASH_INVALID, +}; + +static const char * const ccg_fw_names[] = { + "ccg_boot.cyacd", + "ccg_primary.cyacd", + "ccg_secondary.cyacd" +}; + +struct ccg_dev_info { +#define CCG_DEVINFO_FWMODE_SHIFT (0) +#define CCG_DEVINFO_FWMODE_MASK (0x3 << CCG_DEVINFO_FWMODE_SHIFT) +#define CCG_DEVINFO_PDPORTS_SHIFT (2) +#define CCG_DEVINFO_PDPORTS_MASK (0x3 << CCG_DEVINFO_PDPORTS_SHIFT) + u8 mode; + u8 bl_mode; + __le16 silicon_id; + __le16 bl_last_row; +} __packed; + +struct version_format { + __le16 build; + u8 patch; + u8 ver; +#define CCG_VERSION_PATCH(x) ((x) << 16) +#define CCG_VERSION(x) ((x) << 24) +#define CCG_VERSION_MIN_SHIFT (0) +#define CCG_VERSION_MIN_MASK (0xf << CCG_VERSION_MIN_SHIFT) +#define CCG_VERSION_MAJ_SHIFT (4) +#define CCG_VERSION_MAJ_MASK (0xf << CCG_VERSION_MAJ_SHIFT) +} __packed; + +/* + * Firmware version 3.1.10 or earlier, built for NVIDIA has known issue + * of missing interrupt when a device is connected for runtime resume + */ +#define CCG_FW_BUILD_NVIDIA (('n' << 8) | 'v') +#define CCG_OLD_FW_VERSION (CCG_VERSION(0x31) | CCG_VERSION_PATCH(10)) + +/* Firmware for Tegra doesn't support UCSI ALT command, built + * for NVIDIA has known issue of reporting wrong capability info + */ +#define CCG_FW_BUILD_NVIDIA_TEGRA (('g' << 8) | 'n') + +/* Altmode offset for NVIDIA Function Test Board (FTB) */ +#define NVIDIA_FTB_DP_OFFSET (2) +#define NVIDIA_FTB_DBG_OFFSET (3) + +struct version_info { + struct version_format base; + struct version_format app; +}; + +struct fw_config_table { + u32 identity; + u16 table_size; + u8 fwct_version; + u8 is_key_change; + u8 guid[16]; + struct version_format base; + struct version_format app; + u8 primary_fw_digest[32]; + u32 key_exp_length; + u8 key_modulus[256]; + u8 key_exp[4]; +}; + +/* CCGx response codes */ +enum ccg_resp_code { + CMD_NO_RESP = 0x00, + CMD_SUCCESS = 0x02, + FLASH_DATA_AVAILABLE = 0x03, + CMD_INVALID = 0x05, + FLASH_UPDATE_FAIL = 0x07, + INVALID_FW = 0x08, + INVALID_ARG = 0x09, + CMD_NOT_SUPPORT = 0x0A, + TRANSACTION_FAIL = 0x0C, + PD_CMD_FAIL = 0x0D, + UNDEF_ERROR = 0x0F, + INVALID_RESP = 0x10, +}; + +#define CCG_EVENT_MAX (EVENT_INDEX + 43) + +struct ccg_cmd { + u16 reg; + u32 data; + int len; + u32 delay; /* ms delay for cmd timeout */ +}; + +struct ccg_resp { + u8 code; + u8 length; +}; + +struct ucsi_ccg_altmode { + u16 svid; + u32 mid; + u8 linked_idx; + u8 active_idx; +#define UCSI_MULTI_DP_INDEX (0xff) + bool checked; +} __packed; + +#define CCGX_MESSAGE_IN_MAX 4 +struct op_region { + __le32 cci; + __le32 message_in[CCGX_MESSAGE_IN_MAX]; +}; + +struct ucsi_ccg { + struct device *dev; + struct ucsi *ucsi; + struct i2c_client *client; + + struct ccg_dev_info info; + /* version info for boot, primary and secondary */ + struct version_info version[FW2 + 1]; + u32 fw_version; + /* CCG HPI communication flags */ + unsigned long flags; +#define RESET_PENDING 0 +#define DEV_CMD_PENDING 1 + struct ccg_resp dev_resp; + u8 cmd_resp; + int port_num; + int irq; + struct work_struct work; + struct mutex lock; /* to sync between user and driver thread */ + + /* fw build with vendor information */ + u16 fw_build; + struct work_struct pm_work; + + bool has_multiple_dp; + struct ucsi_ccg_altmode orig[UCSI_MAX_ALTMODES]; + struct ucsi_ccg_altmode updated[UCSI_MAX_ALTMODES]; + + /* + * This spinlock protects op_data which includes CCI and MESSAGE_IN that + * will be updated in ISR + */ + spinlock_t op_lock; + struct op_region op_data; +}; + +static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len) +{ + struct i2c_client *client = uc->client; + const struct i2c_adapter_quirks *quirks = client->adapter->quirks; + unsigned char buf[2]; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0x0, + .len = sizeof(buf), + .buf = buf, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .buf = data, + }, + }; + u32 rlen, rem_len = len, max_read_len = len; + int status; + + /* check any max_read_len limitation on i2c adapter */ + if (quirks && quirks->max_read_len) + max_read_len = quirks->max_read_len; + + pm_runtime_get_sync(uc->dev); + while (rem_len > 0) { + msgs[1].buf = &data[len - rem_len]; + rlen = min_t(u16, rem_len, max_read_len); + msgs[1].len = rlen; + put_unaligned_le16(rab, buf); + status = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (status < 0) { + dev_err(uc->dev, "i2c_transfer failed %d\n", status); + pm_runtime_put_sync(uc->dev); + return status; + } + rab += rlen; + rem_len -= rlen; + } + + pm_runtime_put_sync(uc->dev); + return 0; +} + +static int ccg_write(struct ucsi_ccg *uc, u16 rab, const u8 *data, u32 len) +{ + struct i2c_client *client = uc->client; + unsigned char *buf; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0x0, + } + }; + int status; + + buf = kzalloc(len + sizeof(rab), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + put_unaligned_le16(rab, buf); + memcpy(buf + sizeof(rab), data, len); + + msgs[0].len = len + sizeof(rab); + msgs[0].buf = buf; + + pm_runtime_get_sync(uc->dev); + status = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (status < 0) { + dev_err(uc->dev, "i2c_transfer failed %d\n", status); + pm_runtime_put_sync(uc->dev); + kfree(buf); + return status; + } + + pm_runtime_put_sync(uc->dev); + kfree(buf); + return 0; +} + +static int ccg_op_region_update(struct ucsi_ccg *uc, u32 cci) +{ + u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_MESSAGE_IN); + struct op_region *data = &uc->op_data; + unsigned char *buf; + size_t size = sizeof(data->message_in); + + buf = kzalloc(size, GFP_ATOMIC); + if (!buf) + return -ENOMEM; + if (UCSI_CCI_LENGTH(cci)) { + int ret = ccg_read(uc, reg, (void *)buf, size); + + if (ret) { + kfree(buf); + return ret; + } + } + + spin_lock(&uc->op_lock); + data->cci = cpu_to_le32(cci); + if (UCSI_CCI_LENGTH(cci)) + memcpy(&data->message_in, buf, size); + spin_unlock(&uc->op_lock); + kfree(buf); + return 0; +} + +static int ucsi_ccg_init(struct ucsi_ccg *uc) +{ + unsigned int count = 10; + u8 data; + int status; + + spin_lock_init(&uc->op_lock); + + data = CCGX_RAB_UCSI_CONTROL_STOP; + status = ccg_write(uc, CCGX_RAB_UCSI_CONTROL, &data, sizeof(data)); + if (status < 0) + return status; + + data = CCGX_RAB_UCSI_CONTROL_START; + status = ccg_write(uc, CCGX_RAB_UCSI_CONTROL, &data, sizeof(data)); + if (status < 0) + return status; + + /* + * Flush CCGx RESPONSE queue by acking interrupts. Above ucsi control + * register write will push response which must be cleared. + */ + do { + status = ccg_read(uc, CCGX_RAB_INTR_REG, &data, sizeof(data)); + if (status < 0) + return status; + + if (!(data & DEV_INT)) + return 0; + + status = ccg_write(uc, CCGX_RAB_INTR_REG, &data, sizeof(data)); + if (status < 0) + return status; + + usleep_range(10000, 11000); + } while (--count); + + return -ETIMEDOUT; +} + +static void ucsi_ccg_update_get_current_cam_cmd(struct ucsi_ccg *uc, u8 *data) +{ + u8 cam, new_cam; + + cam = data[0]; + new_cam = uc->orig[cam].linked_idx; + uc->updated[new_cam].active_idx = cam; + data[0] = new_cam; +} + +static bool ucsi_ccg_update_altmodes(struct ucsi *ucsi, + u8 recipient, + struct ucsi_altmode *orig, + struct ucsi_altmode *updated) +{ + struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); + struct ucsi_ccg_altmode *alt, *new_alt; + int i, j, k = 0; + bool found = false; + + if (recipient != UCSI_RECIPIENT_CON) + return false; + + alt = uc->orig; + new_alt = uc->updated; + memset(uc->updated, 0, sizeof(uc->updated)); + + /* + * Copy original connector altmodes to new structure. + * We need this before second loop since second loop + * checks for duplicate altmodes. + */ + for (i = 0; i < UCSI_MAX_ALTMODES; i++) { + alt[i].svid = orig[i].svid; + alt[i].mid = orig[i].mid; + if (!alt[i].svid) + break; + } + + for (i = 0; i < UCSI_MAX_ALTMODES; i++) { + if (!alt[i].svid) + break; + + /* already checked and considered */ + if (alt[i].checked) + continue; + + if (!DP_CONF_GET_PIN_ASSIGN(alt[i].mid)) { + /* Found Non DP altmode */ + new_alt[k].svid = alt[i].svid; + new_alt[k].mid |= alt[i].mid; + new_alt[k].linked_idx = i; + alt[i].linked_idx = k; + updated[k].svid = new_alt[k].svid; + updated[k].mid = new_alt[k].mid; + k++; + continue; + } + + for (j = i + 1; j < UCSI_MAX_ALTMODES; j++) { + if (alt[i].svid != alt[j].svid || + !DP_CONF_GET_PIN_ASSIGN(alt[j].mid)) { + continue; + } else { + /* Found duplicate DP mode */ + new_alt[k].svid = alt[i].svid; + new_alt[k].mid |= alt[i].mid | alt[j].mid; + new_alt[k].linked_idx = UCSI_MULTI_DP_INDEX; + alt[i].linked_idx = k; + alt[j].linked_idx = k; + alt[j].checked = true; + found = true; + } + } + if (found) { + uc->has_multiple_dp = true; + } else { + /* Didn't find any duplicate DP altmode */ + new_alt[k].svid = alt[i].svid; + new_alt[k].mid |= alt[i].mid; + new_alt[k].linked_idx = i; + alt[i].linked_idx = k; + } + updated[k].svid = new_alt[k].svid; + updated[k].mid = new_alt[k].mid; + k++; + } + return found; +} + +static void ucsi_ccg_update_set_new_cam_cmd(struct ucsi_ccg *uc, + struct ucsi_connector *con, + u64 *cmd) +{ + struct ucsi_ccg_altmode *new_port, *port; + struct typec_altmode *alt = NULL; + u8 new_cam, cam, pin; + bool enter_new_mode; + int i, j, k = 0xff; + + port = uc->orig; + new_cam = UCSI_SET_NEW_CAM_GET_AM(*cmd); + if (new_cam >= ARRAY_SIZE(uc->updated)) + return; + new_port = &uc->updated[new_cam]; + cam = new_port->linked_idx; + enter_new_mode = UCSI_SET_NEW_CAM_ENTER(*cmd); + + /* + * If CAM is UCSI_MULTI_DP_INDEX then this is DP altmode + * with multiple DP mode. Find out CAM for best pin assignment + * among all DP mode. Priorite pin E->D->C after making sure + * the partner supports that pin. + */ + if (cam == UCSI_MULTI_DP_INDEX) { + if (enter_new_mode) { + for (i = 0; con->partner_altmode[i]; i++) { + alt = con->partner_altmode[i]; + if (alt->svid == new_port->svid) + break; + } + /* + * alt will always be non NULL since this is + * UCSI_SET_NEW_CAM command and so there will be + * at least one con->partner_altmode[i] with svid + * matching with new_port->svid. + */ + for (j = 0; port[j].svid; j++) { + pin = DP_CONF_GET_PIN_ASSIGN(port[j].mid); + if (alt && port[j].svid == alt->svid && + (pin & DP_CONF_GET_PIN_ASSIGN(alt->vdo))) { + /* prioritize pin E->D->C */ + if (k == 0xff || (k != 0xff && pin > + DP_CONF_GET_PIN_ASSIGN(port[k].mid)) + ) { + k = j; + } + } + } + cam = k; + new_port->active_idx = cam; + } else { + cam = new_port->active_idx; + } + } + *cmd &= ~UCSI_SET_NEW_CAM_AM_MASK; + *cmd |= UCSI_SET_NEW_CAM_SET_AM(cam); +} + +/* + * Change the order of vdo values of NVIDIA test device FTB + * (Function Test Board) which reports altmode list with vdo=0x3 + * first and then vdo=0x. Current logic to assign mode value is + * based on order in altmode list and it causes a mismatch of CON + * and SOP altmodes since NVIDIA GPU connector has order of vdo=0x1 + * first and then vdo=0x3 + */ +static void ucsi_ccg_nvidia_altmode(struct ucsi_ccg *uc, + struct ucsi_altmode *alt, + u64 command) +{ + switch (UCSI_ALTMODE_OFFSET(command)) { + case NVIDIA_FTB_DP_OFFSET: + if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DBG_VDO) + alt[0].mid = USB_TYPEC_NVIDIA_VLINK_DP_VDO | + DP_CAP_DP_SIGNALLING(0) | DP_CAP_USB | + DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_E)); + break; + case NVIDIA_FTB_DBG_OFFSET: + if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DP_VDO) + alt[0].mid = USB_TYPEC_NVIDIA_VLINK_DBG_VDO; + break; + default: + break; + } +} + +static int ucsi_ccg_read_version(struct ucsi *ucsi, u16 *version) +{ + struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); + u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_VERSION); + + return ccg_read(uc, reg, (u8 *)version, sizeof(*version)); +} + +static int ucsi_ccg_read_cci(struct ucsi *ucsi, u32 *cci) +{ + struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); + + spin_lock(&uc->op_lock); + *cci = uc->op_data.cci; + spin_unlock(&uc->op_lock); + + return 0; +} + +static int ucsi_ccg_read_message_in(struct ucsi *ucsi, void *val, size_t val_len) +{ + struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); + + spin_lock(&uc->op_lock); + memcpy(val, uc->op_data.message_in, val_len); + spin_unlock(&uc->op_lock); + + return 0; +} + +static int ucsi_ccg_async_control(struct ucsi *ucsi, u64 command) +{ + struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); + u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_CONTROL); + + /* + * UCSI may read CCI instantly after async_control, + * clear CCI to avoid caller getting wrong data before we get CCI from ISR + */ + spin_lock(&uc->op_lock); + uc->op_data.cci = 0; + spin_unlock(&uc->op_lock); + + return ccg_write(uc, reg, (u8 *)&command, sizeof(command)); +} + +static int ucsi_ccg_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, + void *data, size_t size, void *msg_out, + size_t msg_out_size) +{ + struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); + struct ucsi_connector *con; + int con_index; + int ret; + + mutex_lock(&uc->lock); + pm_runtime_get_sync(uc->dev); + + if (UCSI_COMMAND(command) == UCSI_SET_NEW_CAM && + uc->has_multiple_dp) { + con_index = (command >> 16) & + UCSI_CMD_CONNECTOR_MASK; + if (con_index == 0) { + ret = -EINVAL; + goto err_put; + } + con = &uc->ucsi->connector[con_index - 1]; + ucsi_ccg_update_set_new_cam_cmd(uc, con, &command); + } + + ret = ucsi_sync_control_common(ucsi, command, cci, data, size, + msg_out, msg_out_size); + + switch (UCSI_COMMAND(command)) { + case UCSI_GET_CURRENT_CAM: + if (uc->has_multiple_dp) + ucsi_ccg_update_get_current_cam_cmd(uc, (u8 *)data); + break; + case UCSI_GET_ALTERNATE_MODES: + if (UCSI_ALTMODE_RECIPIENT(command) == UCSI_RECIPIENT_SOP) { + struct ucsi_altmode *alt = data; + + if (alt[0].svid == USB_TYPEC_NVIDIA_VLINK_SID) + ucsi_ccg_nvidia_altmode(uc, alt, command); + } + break; + case UCSI_GET_CAPABILITY: + if (uc->fw_build == CCG_FW_BUILD_NVIDIA_TEGRA) { + struct ucsi_capability *cap = data; + + cap->features &= ~UCSI_CAP_ALT_MODE_DETAILS; + } + break; + default: + break; + } + +err_put: + pm_runtime_put_sync(uc->dev); + mutex_unlock(&uc->lock); + + return ret; +} + +static const struct ucsi_operations ucsi_ccg_ops = { + .read_version = ucsi_ccg_read_version, + .read_cci = ucsi_ccg_read_cci, + .poll_cci = ucsi_ccg_read_cci, + .read_message_in = ucsi_ccg_read_message_in, + .sync_control = ucsi_ccg_sync_control, + .async_control = ucsi_ccg_async_control, + .update_altmodes = ucsi_ccg_update_altmodes +}; + +static irqreturn_t ccg_irq_handler(int irq, void *data) +{ + u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_CCI); + struct ucsi_ccg *uc = data; + u8 intr_reg; + u32 cci = 0; + int ret = 0; + + ret = ccg_read(uc, CCGX_RAB_INTR_REG, &intr_reg, sizeof(intr_reg)); + if (ret) + return ret; + + if (!intr_reg) + return IRQ_HANDLED; + else if (!(intr_reg & UCSI_READ_INT)) + goto err_clear_irq; + + ret = ccg_read(uc, reg, (void *)&cci, sizeof(cci)); + if (ret) + goto err_clear_irq; + + /* + * As per CCGx UCSI interface guide, copy CCI and MESSAGE_IN + * to the OpRegion before clear the UCSI interrupt + */ + ret = ccg_op_region_update(uc, cci); + if (ret) + goto err_clear_irq; + +err_clear_irq: + ccg_write(uc, CCGX_RAB_INTR_REG, &intr_reg, sizeof(intr_reg)); + + if (!ret) + ucsi_notify_common(uc->ucsi, cci); + + return IRQ_HANDLED; +} + +static int ccg_request_irq(struct ucsi_ccg *uc) +{ + unsigned long flags = IRQF_ONESHOT; + + if (!dev_fwnode(uc->dev)) + flags |= IRQF_TRIGGER_HIGH; + + return request_threaded_irq(uc->irq, NULL, ccg_irq_handler, flags, dev_name(uc->dev), uc); +} + +static void ccg_pm_workaround_work(struct work_struct *pm_work) +{ + ccg_irq_handler(0, container_of(pm_work, struct ucsi_ccg, pm_work)); +} + +static int get_fw_info(struct ucsi_ccg *uc) +{ + int err; + + err = ccg_read(uc, CCGX_RAB_READ_ALL_VER, (u8 *)(&uc->version), + sizeof(uc->version)); + if (err < 0) + return err; + + uc->fw_version = CCG_VERSION(uc->version[FW2].app.ver) | + CCG_VERSION_PATCH(uc->version[FW2].app.patch); + + err = ccg_read(uc, CCGX_RAB_DEVICE_MODE, (u8 *)(&uc->info), + sizeof(uc->info)); + if (err < 0) + return err; + + return 0; +} + +static inline bool invalid_async_evt(int code) +{ + return (code >= CCG_EVENT_MAX) || (code < EVENT_INDEX); +} + +static void ccg_process_response(struct ucsi_ccg *uc) +{ + struct device *dev = uc->dev; + + if (uc->dev_resp.code & ASYNC_EVENT) { + if (uc->dev_resp.code == RESET_COMPLETE) { + if (test_bit(RESET_PENDING, &uc->flags)) + uc->cmd_resp = uc->dev_resp.code; + get_fw_info(uc); + } + if (invalid_async_evt(uc->dev_resp.code)) + dev_err(dev, "invalid async evt %d\n", + uc->dev_resp.code); + } else { + if (test_bit(DEV_CMD_PENDING, &uc->flags)) { + uc->cmd_resp = uc->dev_resp.code; + clear_bit(DEV_CMD_PENDING, &uc->flags); + } else { + dev_err(dev, "dev resp 0x%04x but no cmd pending\n", + uc->dev_resp.code); + } + } +} + +static int ccg_read_response(struct ucsi_ccg *uc) +{ + unsigned long target = jiffies + msecs_to_jiffies(1000); + struct device *dev = uc->dev; + u8 intval; + int status; + + /* wait for interrupt status to get updated */ + do { + status = ccg_read(uc, CCGX_RAB_INTR_REG, &intval, + sizeof(intval)); + if (status < 0) + return status; + + if (intval & DEV_INT) + break; + usleep_range(500, 600); + } while (time_is_after_jiffies(target)); + + if (time_is_before_jiffies(target)) { + dev_err(dev, "response timeout error\n"); + return -ETIME; + } + + status = ccg_read(uc, CCGX_RAB_RESPONSE, (u8 *)&uc->dev_resp, + sizeof(uc->dev_resp)); + if (status < 0) + return status; + + status = ccg_write(uc, CCGX_RAB_INTR_REG, &intval, sizeof(intval)); + if (status < 0) + return status; + + return 0; +} + +/* Caller must hold uc->lock */ +static int ccg_send_command(struct ucsi_ccg *uc, struct ccg_cmd *cmd) +{ + struct device *dev = uc->dev; + int ret; + + switch (cmd->reg & 0xF000) { + case DEV_REG_IDX: + set_bit(DEV_CMD_PENDING, &uc->flags); + break; + default: + dev_err(dev, "invalid cmd register\n"); + break; + } + + ret = ccg_write(uc, cmd->reg, (u8 *)&cmd->data, cmd->len); + if (ret < 0) + return ret; + + msleep(cmd->delay); + + ret = ccg_read_response(uc); + if (ret < 0) { + dev_err(dev, "response read error\n"); + switch (cmd->reg & 0xF000) { + case DEV_REG_IDX: + clear_bit(DEV_CMD_PENDING, &uc->flags); + break; + default: + dev_err(dev, "invalid cmd register\n"); + break; + } + return -EIO; + } + ccg_process_response(uc); + + return uc->cmd_resp; +} + +static int ccg_cmd_enter_flashing(struct ucsi_ccg *uc) +{ + struct ccg_cmd cmd; + int ret; + + cmd.reg = CCGX_RAB_ENTER_FLASHING; + cmd.data = FLASH_ENTER_SIG; + cmd.len = 1; + cmd.delay = 50; + + mutex_lock(&uc->lock); + + ret = ccg_send_command(uc, &cmd); + + mutex_unlock(&uc->lock); + + if (ret != CMD_SUCCESS) { + dev_err(uc->dev, "enter flashing failed ret=%d\n", ret); + return ret; + } + + return 0; +} + +static int ccg_cmd_reset(struct ucsi_ccg *uc) +{ + struct ccg_cmd cmd; + u8 *p; + int ret; + + p = (u8 *)&cmd.data; + cmd.reg = CCGX_RAB_RESET_REQ; + p[0] = RESET_SIG; + p[1] = CMD_RESET_DEV; + cmd.len = 2; + cmd.delay = 5000; + + mutex_lock(&uc->lock); + + set_bit(RESET_PENDING, &uc->flags); + + ret = ccg_send_command(uc, &cmd); + if (ret != RESET_COMPLETE) + goto err_clear_flag; + + ret = 0; + +err_clear_flag: + clear_bit(RESET_PENDING, &uc->flags); + + mutex_unlock(&uc->lock); + + return ret; +} + +static int ccg_cmd_port_control(struct ucsi_ccg *uc, bool enable) +{ + struct ccg_cmd cmd; + int ret; + + cmd.reg = CCGX_RAB_PDPORT_ENABLE; + if (enable) + cmd.data = (uc->port_num == 1) ? + PDPORT_1 : (PDPORT_1 | PDPORT_2); + else + cmd.data = 0x0; + cmd.len = 1; + cmd.delay = 10; + + mutex_lock(&uc->lock); + + ret = ccg_send_command(uc, &cmd); + + mutex_unlock(&uc->lock); + + if (ret != CMD_SUCCESS) { + dev_err(uc->dev, "port control failed ret=%d\n", ret); + return ret; + } + return 0; +} + +static int ccg_cmd_jump_boot_mode(struct ucsi_ccg *uc, int bl_mode) +{ + struct ccg_cmd cmd; + int ret; + + cmd.reg = CCGX_RAB_JUMP_TO_BOOT; + + if (bl_mode) + cmd.data = TO_BOOT; + else + cmd.data = TO_ALT_FW; + + cmd.len = 1; + cmd.delay = 100; + + mutex_lock(&uc->lock); + + set_bit(RESET_PENDING, &uc->flags); + + ret = ccg_send_command(uc, &cmd); + if (ret != RESET_COMPLETE) + goto err_clear_flag; + + ret = 0; + +err_clear_flag: + clear_bit(RESET_PENDING, &uc->flags); + + mutex_unlock(&uc->lock); + + return ret; +} + +static int +ccg_cmd_write_flash_row(struct ucsi_ccg *uc, u16 row, + const void *data, u8 fcmd) +{ + struct i2c_client *client = uc->client; + struct ccg_cmd cmd; + u8 buf[CCG4_ROW_SIZE + 2]; + u8 *p; + int ret; + + /* Copy the data into the flash read/write memory. */ + put_unaligned_le16(REG_FLASH_RW_MEM, buf); + + memcpy(buf + 2, data, CCG4_ROW_SIZE); + + mutex_lock(&uc->lock); + + ret = i2c_master_send(client, buf, CCG4_ROW_SIZE + 2); + if (ret != CCG4_ROW_SIZE + 2) { + dev_err(uc->dev, "REG_FLASH_RW_MEM write fail %d\n", ret); + mutex_unlock(&uc->lock); + return ret < 0 ? ret : -EIO; + } + + /* Use the FLASH_ROW_READ_WRITE register to trigger */ + /* writing of data to the desired flash row */ + p = (u8 *)&cmd.data; + cmd.reg = CCGX_RAB_FLASH_ROW_RW; + p[0] = FLASH_SIG; + p[1] = fcmd; + put_unaligned_le16(row, &p[2]); + cmd.len = 4; + cmd.delay = 50; + if (fcmd == FLASH_FWCT_SIG_WR_CMD) + cmd.delay += 400; + if (row == 510) + cmd.delay += 220; + ret = ccg_send_command(uc, &cmd); + + mutex_unlock(&uc->lock); + + if (ret != CMD_SUCCESS) { + dev_err(uc->dev, "write flash row failed ret=%d\n", ret); + return ret; + } + + return 0; +} + +static int ccg_cmd_validate_fw(struct ucsi_ccg *uc, unsigned int fwid) +{ + struct ccg_cmd cmd; + int ret; + + cmd.reg = CCGX_RAB_VALIDATE_FW; + cmd.data = fwid; + cmd.len = 1; + cmd.delay = 500; + + mutex_lock(&uc->lock); + + ret = ccg_send_command(uc, &cmd); + + mutex_unlock(&uc->lock); + + if (ret != CMD_SUCCESS) + return ret; + + return 0; +} + +static bool ccg_check_vendor_version(struct ucsi_ccg *uc, + struct version_format *app, + struct fw_config_table *fw_cfg) +{ + struct device *dev = uc->dev; + + /* Check if the fw build is for supported vendors */ + if (le16_to_cpu(app->build) != uc->fw_build) { + dev_info(dev, "current fw is not from supported vendor\n"); + return false; + } + + /* Check if the new fw build is for supported vendors */ + if (le16_to_cpu(fw_cfg->app.build) != uc->fw_build) { + dev_info(dev, "new fw is not from supported vendor\n"); + return false; + } + return true; +} + +static bool ccg_check_fw_version(struct ucsi_ccg *uc, const char *fw_name, + struct version_format *app) +{ + const struct firmware *fw = NULL; + struct device *dev = uc->dev; + struct fw_config_table fw_cfg; + u32 cur_version, new_version; + bool is_later = false; + + if (request_firmware(&fw, fw_name, dev) != 0) { + dev_err(dev, "error: Failed to open cyacd file %s\n", fw_name); + return false; + } + + /* + * check if signed fw + * last part of fw image is fw cfg table and signature + */ + if (fw->size < sizeof(fw_cfg) + FW_CFG_TABLE_SIG_SIZE) + goto out_release_firmware; + + memcpy((uint8_t *)&fw_cfg, fw->data + fw->size - + sizeof(fw_cfg) - FW_CFG_TABLE_SIG_SIZE, sizeof(fw_cfg)); + + if (fw_cfg.identity != ('F' | 'W' << 8 | 'C' << 16 | 'T' << 24)) { + dev_info(dev, "not a signed image\n"); + goto out_release_firmware; + } + + /* compare input version with FWCT version */ + cur_version = le16_to_cpu(app->build) | CCG_VERSION_PATCH(app->patch) | + CCG_VERSION(app->ver); + + new_version = le16_to_cpu(fw_cfg.app.build) | + CCG_VERSION_PATCH(fw_cfg.app.patch) | + CCG_VERSION(fw_cfg.app.ver); + + if (!ccg_check_vendor_version(uc, app, &fw_cfg)) + goto out_release_firmware; + + if (new_version > cur_version) + is_later = true; + +out_release_firmware: + release_firmware(fw); + return is_later; +} + +static int ccg_fw_update_needed(struct ucsi_ccg *uc, + enum enum_flash_mode *mode) +{ + struct device *dev = uc->dev; + int err; + struct version_info version[3]; + + err = ccg_read(uc, CCGX_RAB_DEVICE_MODE, (u8 *)(&uc->info), + sizeof(uc->info)); + if (err) { + dev_err(dev, "read device mode failed\n"); + return err; + } + + err = ccg_read(uc, CCGX_RAB_READ_ALL_VER, (u8 *)version, + sizeof(version)); + if (err) { + dev_err(dev, "read device mode failed\n"); + return err; + } + + if (memcmp(&version[FW1], "\0\0\0\0\0\0\0\0", + sizeof(struct version_info)) == 0) { + dev_info(dev, "secondary fw is not flashed\n"); + *mode = SECONDARY_BL; + } else if (le16_to_cpu(version[FW1].base.build) < + secondary_fw_min_ver) { + dev_info(dev, "secondary fw version is too low (< %d)\n", + secondary_fw_min_ver); + *mode = SECONDARY; + } else if (memcmp(&version[FW2], "\0\0\0\0\0\0\0\0", + sizeof(struct version_info)) == 0) { + dev_info(dev, "primary fw is not flashed\n"); + *mode = PRIMARY; + } else if (ccg_check_fw_version(uc, ccg_fw_names[PRIMARY], + &version[FW2].app)) { + dev_info(dev, "found primary fw with later version\n"); + *mode = PRIMARY; + } else { + dev_info(dev, "secondary and primary fw are the latest\n"); + *mode = FLASH_NOT_NEEDED; + } + return 0; +} + +static int do_flash(struct ucsi_ccg *uc, enum enum_flash_mode mode) +{ + struct device *dev = uc->dev; + const struct firmware *fw = NULL; + const char *p, *s; + const char *eof; + int err, row, len, line_sz, line_cnt = 0; + unsigned long start_time = jiffies; + struct fw_config_table fw_cfg; + u8 fw_cfg_sig[FW_CFG_TABLE_SIG_SIZE]; + u8 *wr_buf; + + err = request_firmware(&fw, ccg_fw_names[mode], dev); + if (err) { + dev_err(dev, "request %s failed err=%d\n", + ccg_fw_names[mode], err); + return err; + } + + if (((uc->info.mode & CCG_DEVINFO_FWMODE_MASK) >> + CCG_DEVINFO_FWMODE_SHIFT) == FW2) { + err = ccg_cmd_port_control(uc, false); + if (err < 0) + goto release_fw; + err = ccg_cmd_jump_boot_mode(uc, 0); + if (err < 0) + goto release_fw; + } + + eof = fw->data + fw->size; + + /* + * check if signed fw + * last part of fw image is fw cfg table and signature + */ + if (fw->size < sizeof(fw_cfg) + sizeof(fw_cfg_sig)) + goto not_signed_fw; + + memcpy((uint8_t *)&fw_cfg, fw->data + fw->size - + sizeof(fw_cfg) - sizeof(fw_cfg_sig), sizeof(fw_cfg)); + + if (fw_cfg.identity != ('F' | ('W' << 8) | ('C' << 16) | ('T' << 24))) { + dev_info(dev, "not a signed image\n"); + goto not_signed_fw; + } + eof = fw->data + fw->size - sizeof(fw_cfg) - sizeof(fw_cfg_sig); + + memcpy((uint8_t *)&fw_cfg_sig, + fw->data + fw->size - sizeof(fw_cfg_sig), sizeof(fw_cfg_sig)); + + /* flash fw config table and signature first */ + err = ccg_cmd_write_flash_row(uc, 0, (u8 *)&fw_cfg, + FLASH_FWCT1_WR_CMD); + if (err) + goto release_fw; + + err = ccg_cmd_write_flash_row(uc, 0, (u8 *)&fw_cfg + CCG4_ROW_SIZE, + FLASH_FWCT2_WR_CMD); + if (err) + goto release_fw; + + err = ccg_cmd_write_flash_row(uc, 0, &fw_cfg_sig, + FLASH_FWCT_SIG_WR_CMD); + if (err) + goto release_fw; + +not_signed_fw: + wr_buf = kzalloc(CCG4_ROW_SIZE + 4, GFP_KERNEL); + if (!wr_buf) { + err = -ENOMEM; + goto release_fw; + } + + err = ccg_cmd_enter_flashing(uc); + if (err) + goto release_mem; + + /***************************************************************** + * CCG firmware image (.cyacd) file line format + * + * :00rrrrllll[dd....]cc/r/n + * + * :00 header + * rrrr is row number to flash (4 char) + * llll is data len to flash (4 char) + * dd is a data field represents one byte of data (512 char) + * cc is checksum (2 char) + * \r\n newline + * + * Total length: 3 + 4 + 4 + 512 + 2 + 2 = 527 + * + *****************************************************************/ + + p = strnchr(fw->data, fw->size, ':'); + if (!p) { + dev_err(dev, "Bad FW format: no ':' record header found\n"); + err = -EINVAL; + goto release_mem; + } + while (p < eof) { + s = strnchr(p + 1, eof - p - 1, ':'); + + if (!s) + s = eof; + + line_sz = s - p; + + if (line_sz != CYACD_LINE_SIZE) { + dev_err(dev, "Bad FW format line_sz=%d\n", line_sz); + err = -EINVAL; + goto release_mem; + } + + if (hex2bin(wr_buf, p + 3, CCG4_ROW_SIZE + 4)) { + err = -EINVAL; + goto release_mem; + } + + row = get_unaligned_be16(wr_buf); + len = get_unaligned_be16(&wr_buf[2]); + + if (len != CCG4_ROW_SIZE) { + err = -EINVAL; + goto release_mem; + } + + err = ccg_cmd_write_flash_row(uc, row, wr_buf + 4, + FLASH_WR_CMD); + if (err) + goto release_mem; + + line_cnt++; + p = s; + } + + dev_info(dev, "total %d row flashed. time: %dms\n", + line_cnt, jiffies_to_msecs(jiffies - start_time)); + + err = ccg_cmd_validate_fw(uc, (mode == PRIMARY) ? FW2 : FW1); + if (err) + dev_err(dev, "%s validation failed err=%d\n", + (mode == PRIMARY) ? "FW2" : "FW1", err); + else + dev_info(dev, "%s validated\n", + (mode == PRIMARY) ? "FW2" : "FW1"); + + err = ccg_cmd_port_control(uc, false); + if (err < 0) + goto release_mem; + + err = ccg_cmd_reset(uc); + if (err < 0) + goto release_mem; + + err = ccg_cmd_port_control(uc, true); + if (err < 0) + goto release_mem; + +release_mem: + kfree(wr_buf); + +release_fw: + release_firmware(fw); + return err; +} + +/******************************************************************************* + * CCG4 has two copies of the firmware in addition to the bootloader. + * If the device is running FW1, FW2 can be updated with the new version. + * Dual firmware mode allows the CCG device to stay in a PD contract and support + * USB PD and Type-C functionality while a firmware update is in progress. + ******************************************************************************/ +static int ccg_fw_update(struct ucsi_ccg *uc, enum enum_flash_mode flash_mode) +{ + int err = 0; + + while (flash_mode != FLASH_NOT_NEEDED) { + err = do_flash(uc, flash_mode); + if (err < 0) + return err; + err = ccg_fw_update_needed(uc, &flash_mode); + if (err < 0) + return err; + } + dev_info(uc->dev, "CCG FW update successful\n"); + + return err; +} + +static int ccg_restart(struct ucsi_ccg *uc) +{ + struct device *dev = uc->dev; + int status; + + status = ucsi_ccg_init(uc); + if (status < 0) { + dev_err(dev, "ucsi_ccg_start fail, err=%d\n", status); + return status; + } + + status = ccg_request_irq(uc); + if (status < 0) { + dev_err(dev, "request_threaded_irq failed - %d\n", status); + return status; + } + + status = ucsi_register(uc->ucsi); + if (status) { + dev_err(uc->dev, "failed to register the interface\n"); + return status; + } + + pm_runtime_enable(uc->dev); + return 0; +} + +static void ccg_update_firmware(struct work_struct *work) +{ + struct ucsi_ccg *uc = container_of(work, struct ucsi_ccg, work); + enum enum_flash_mode flash_mode; + int status; + + status = ccg_fw_update_needed(uc, &flash_mode); + if (status < 0) + return; + + if (flash_mode != FLASH_NOT_NEEDED) { + ucsi_unregister(uc->ucsi); + pm_runtime_disable(uc->dev); + free_irq(uc->irq, uc); + + ccg_fw_update(uc, flash_mode); + ccg_restart(uc); + } +} + +static ssize_t do_flash_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t n) +{ + struct ucsi_ccg *uc = i2c_get_clientdata(to_i2c_client(dev)); + bool flash; + + if (kstrtobool(buf, &flash)) + return -EINVAL; + + if (!flash) + return n; + + schedule_work(&uc->work); + return n; +} + +static umode_t ucsi_ccg_attrs_is_visible(struct kobject *kobj, struct attribute *attr, int idx) +{ + struct device *dev = kobj_to_dev(kobj); + struct ucsi_ccg *uc = i2c_get_clientdata(to_i2c_client(dev)); + + if (!uc->fw_build) + return 0; + + return attr->mode; +} + +static DEVICE_ATTR_WO(do_flash); + +static struct attribute *ucsi_ccg_attrs[] = { + &dev_attr_do_flash.attr, + NULL, +}; +static struct attribute_group ucsi_ccg_attr_group = { + .attrs = ucsi_ccg_attrs, + .is_visible = ucsi_ccg_attrs_is_visible, +}; +static const struct attribute_group *ucsi_ccg_groups[] = { + &ucsi_ccg_attr_group, + NULL, +}; + +static int ucsi_ccg_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ucsi_ccg *uc; + const char *fw_name; + int status; + + uc = devm_kzalloc(dev, sizeof(*uc), GFP_KERNEL); + if (!uc) + return -ENOMEM; + + uc->dev = dev; + uc->client = client; + uc->irq = client->irq; + mutex_init(&uc->lock); + INIT_WORK(&uc->work, ccg_update_firmware); + INIT_WORK(&uc->pm_work, ccg_pm_workaround_work); + + /* Only fail FW flashing when FW build information is not provided */ + status = device_property_read_string(dev, "firmware-name", &fw_name); + if (!status) { + if (!strcmp(fw_name, "nvidia,jetson-agx-xavier")) + uc->fw_build = CCG_FW_BUILD_NVIDIA_TEGRA; + else if (!strcmp(fw_name, "nvidia,gpu")) + uc->fw_build = CCG_FW_BUILD_NVIDIA; + if (!uc->fw_build) + dev_err(uc->dev, "failed to get FW build information\n"); + } + + /* reset ccg device and initialize ucsi */ + status = ucsi_ccg_init(uc); + if (status < 0) { + dev_err(uc->dev, "ucsi_ccg_init failed - %d\n", status); + return status; + } + + status = get_fw_info(uc); + if (status < 0) { + dev_err(uc->dev, "get_fw_info failed - %d\n", status); + return status; + } + + uc->port_num = 1; + + if (uc->info.mode & CCG_DEVINFO_PDPORTS_MASK) + uc->port_num++; + + uc->ucsi = ucsi_create(dev, &ucsi_ccg_ops); + if (IS_ERR(uc->ucsi)) + return PTR_ERR(uc->ucsi); + + ucsi_set_drvdata(uc->ucsi, uc); + + status = ccg_request_irq(uc); + if (status < 0) { + dev_err(uc->dev, "request_threaded_irq failed - %d\n", status); + goto out_ucsi_destroy; + } + + status = ucsi_register(uc->ucsi); + if (status) + goto out_free_irq; + + i2c_set_clientdata(client, uc); + + device_disable_async_suspend(uc->dev); + + pm_runtime_set_active(uc->dev); + pm_runtime_enable(uc->dev); + pm_runtime_use_autosuspend(uc->dev); + pm_runtime_set_autosuspend_delay(uc->dev, 5000); + pm_runtime_idle(uc->dev); + + return 0; + +out_free_irq: + free_irq(uc->irq, uc); +out_ucsi_destroy: + ucsi_destroy(uc->ucsi); + + return status; +} + +static void ucsi_ccg_remove(struct i2c_client *client) +{ + struct ucsi_ccg *uc = i2c_get_clientdata(client); + + cancel_work_sync(&uc->pm_work); + cancel_work_sync(&uc->work); + pm_runtime_disable(uc->dev); + ucsi_unregister(uc->ucsi); + free_irq(uc->irq, uc); + ucsi_destroy(uc->ucsi); +} + +static const struct of_device_id ucsi_ccg_of_match_table[] = { + { .compatible = "cypress,cypd4226", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ucsi_ccg_of_match_table); + +static const struct i2c_device_id ucsi_ccg_device_id[] = { + { .name = "ccgx-ucsi" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ucsi_ccg_device_id); + +static const struct acpi_device_id amd_i2c_ucsi_match[] = { + {"AMDI0042"}, + {} +}; +MODULE_DEVICE_TABLE(acpi, amd_i2c_ucsi_match); + +static int ucsi_ccg_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ucsi_ccg *uc = i2c_get_clientdata(client); + + return ucsi_resume(uc->ucsi); +} + +static int ucsi_ccg_runtime_suspend(struct device *dev) +{ + return 0; +} + +static int ucsi_ccg_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ucsi_ccg *uc = i2c_get_clientdata(client); + + /* + * Firmware version 3.1.10 or earlier, built for NVIDIA has known issue + * of missing interrupt when a device is connected for runtime resume. + * Schedule a work to call ISR as a workaround. + */ + if (uc->fw_build == CCG_FW_BUILD_NVIDIA && + uc->fw_version <= CCG_OLD_FW_VERSION) + schedule_work(&uc->pm_work); + + return 0; +} + +static const struct dev_pm_ops ucsi_ccg_pm = { + .resume = ucsi_ccg_resume, + .runtime_suspend = ucsi_ccg_runtime_suspend, + .runtime_resume = ucsi_ccg_runtime_resume, +}; + +static struct i2c_driver ucsi_ccg_driver = { + .driver = { + .name = "ucsi_ccg", + .pm = &ucsi_ccg_pm, + .dev_groups = ucsi_ccg_groups, + .acpi_match_table = amd_i2c_ucsi_match, + .of_match_table = ucsi_ccg_of_match_table, + }, + .probe = ucsi_ccg_probe, + .remove = ucsi_ccg_remove, + .id_table = ucsi_ccg_device_id, +}; + +module_i2c_driver(ucsi_ccg_driver); + +MODULE_AUTHOR("Ajay Gupta "); +MODULE_DESCRIPTION("UCSI driver for Cypress CCGx Type-C controller"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c new file mode 100644 index 000000000..12e07b9fe --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi_glink.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2023, Linaro Ltd + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ucsi.h" + +#define PMIC_GLINK_MAX_PORTS 3 + +#define UCSI_BUF_V1_SIZE (UCSI_MESSAGE_OUT + (UCSI_MESSAGE_OUT - UCSI_MESSAGE_IN)) +#define UCSI_BUF_V2_SIZE (UCSIv2_MESSAGE_OUT + (UCSIv2_MESSAGE_OUT - UCSI_MESSAGE_IN)) + +#define MSG_TYPE_REQ_RESP 1 + +#define UC_NOTIFY_RECEIVER_UCSI 0x0 +#define UC_UCSI_READ_BUF_REQ 0x11 +#define UC_UCSI_WRITE_BUF_REQ 0x12 +#define UC_UCSI_USBC_NOTIFY_IND 0x13 + +struct ucsi_read_buf_req_msg { + struct pmic_glink_hdr hdr; +}; + +struct __packed ucsi_read_buf_resp_msg { + struct pmic_glink_hdr hdr; + union { + u8 v2_buf[UCSI_BUF_V2_SIZE]; + u8 v1_buf[UCSI_BUF_V1_SIZE]; + } buf; + u32 ret_code; +}; + +struct __packed ucsi_write_buf_req_msg { + struct pmic_glink_hdr hdr; + union { + u8 v2_buf[UCSI_BUF_V2_SIZE]; + u8 v1_buf[UCSI_BUF_V1_SIZE]; + } buf; + u32 reserved; +}; + +struct __packed ucsi_write_buf_resp_msg { + struct pmic_glink_hdr hdr; + u32 ret_code; +}; + +struct __packed ucsi_notify_ind_msg { + struct pmic_glink_hdr hdr; + u32 notification; + u32 receiver; + u32 reserved; +}; + +struct pmic_glink_ucsi { + struct device *dev; + + struct gpio_desc *port_orientation[PMIC_GLINK_MAX_PORTS]; + + struct pmic_glink_client *client; + + struct ucsi *ucsi; + struct completion read_ack; + struct completion write_ack; + struct mutex lock; /* protects concurrent access to PMIC Glink interface */ + + struct work_struct notify_work; + struct work_struct register_work; + spinlock_t state_lock; + bool ucsi_registered; + bool pd_running; + + u8 read_buf[UCSI_BUF_V2_SIZE]; +}; + +static int pmic_glink_ucsi_read(struct ucsi *__ucsi, unsigned int offset, + void *val, size_t val_len) +{ + struct pmic_glink_ucsi *ucsi = ucsi_get_drvdata(__ucsi); + struct ucsi_read_buf_req_msg req = {}; + unsigned long left; + int ret; + + req.hdr.owner = PMIC_GLINK_OWNER_USBC; + req.hdr.type = MSG_TYPE_REQ_RESP; + req.hdr.opcode = UC_UCSI_READ_BUF_REQ; + + mutex_lock(&ucsi->lock); + memset(ucsi->read_buf, 0, sizeof(ucsi->read_buf)); + reinit_completion(&ucsi->read_ack); + + ret = pmic_glink_send(ucsi->client, &req, sizeof(req)); + if (ret < 0) { + dev_err(ucsi->dev, "failed to send UCSI read request: %d\n", ret); + goto out_unlock; + } + + left = wait_for_completion_timeout(&ucsi->read_ack, 5 * HZ); + if (!left) { + dev_err(ucsi->dev, "timeout waiting for UCSI read response\n"); + ret = -ETIMEDOUT; + goto out_unlock; + } + + memcpy(val, &ucsi->read_buf[offset], val_len); + ret = 0; + +out_unlock: + mutex_unlock(&ucsi->lock); + + return ret; +} + +static int pmic_glink_ucsi_read_version(struct ucsi *ucsi, u16 *version) +{ + return pmic_glink_ucsi_read(ucsi, UCSI_VERSION, version, sizeof(*version)); +} + +static int pmic_glink_ucsi_read_cci(struct ucsi *ucsi, u32 *cci) +{ + return pmic_glink_ucsi_read(ucsi, UCSI_CCI, cci, sizeof(*cci)); +} + +static int pmic_glink_ucsi_read_message_in(struct ucsi *ucsi, void *val, size_t val_len) +{ + return pmic_glink_ucsi_read(ucsi, UCSI_MESSAGE_IN, val, val_len); +} + +static int pmic_glink_ucsi_locked_write(struct pmic_glink_ucsi *ucsi, unsigned int offset, + const void *val, size_t val_len) +{ + struct ucsi_write_buf_req_msg req = {}; + size_t req_len, buf_len; + unsigned long left; + int ret; + u8 *buf; + + req.hdr.owner = PMIC_GLINK_OWNER_USBC; + req.hdr.type = MSG_TYPE_REQ_RESP; + req.hdr.opcode = UC_UCSI_WRITE_BUF_REQ; + + if (ucsi->ucsi->version >= UCSI_VERSION_2_0) { + buf_len = UCSI_BUF_V2_SIZE; + buf = req.buf.v2_buf; + } else if (ucsi->ucsi->version) { + buf_len = UCSI_BUF_V1_SIZE; + buf = req.buf.v1_buf; + } else { + dev_err(ucsi->dev, "UCSI version unknown\n"); + return -EINVAL; + } + req_len = sizeof(struct pmic_glink_hdr) + buf_len + sizeof(u32); + + if (offset + val_len > buf_len) + return -EINVAL; + + memcpy(&buf[offset], val, val_len); + + reinit_completion(&ucsi->write_ack); + + ret = pmic_glink_send(ucsi->client, &req, req_len); + if (ret < 0) { + dev_err(ucsi->dev, "failed to send UCSI write request: %d\n", ret); + return ret; + } + + left = wait_for_completion_timeout(&ucsi->write_ack, 5 * HZ); + if (!left) { + dev_err(ucsi->dev, "timeout waiting for UCSI write response\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int pmic_glink_ucsi_async_control(struct ucsi *__ucsi, u64 command) +{ + struct pmic_glink_ucsi *ucsi = ucsi_get_drvdata(__ucsi); + int ret; + + mutex_lock(&ucsi->lock); + ret = pmic_glink_ucsi_locked_write(ucsi, UCSI_CONTROL, &command, sizeof(command)); + mutex_unlock(&ucsi->lock); + + return ret; +} + +static void pmic_glink_ucsi_update_connector(struct ucsi_connector *con) +{ + struct pmic_glink_ucsi *ucsi = ucsi_get_drvdata(con->ucsi); + + if (con->num > PMIC_GLINK_MAX_PORTS || + !ucsi->port_orientation[con->num - 1]) + return; + + con->typec_cap.orientation_aware = true; +} + +static void pmic_glink_ucsi_connector_status(struct ucsi_connector *con) +{ + struct pmic_glink_ucsi *ucsi = ucsi_get_drvdata(con->ucsi); + int orientation; + + if (!UCSI_CONSTAT(con, CONNECTED)) { + typec_set_orientation(con->port, TYPEC_ORIENTATION_NONE); + return; + } + + if (con->num > PMIC_GLINK_MAX_PORTS || + !ucsi->port_orientation[con->num - 1]) + return; + + orientation = gpiod_get_value(ucsi->port_orientation[con->num - 1]); + if (orientation >= 0) { + typec_set_orientation(con->port, + orientation ? + TYPEC_ORIENTATION_REVERSE : + TYPEC_ORIENTATION_NORMAL); + } +} + +static const struct ucsi_operations pmic_glink_ucsi_ops = { + .read_version = pmic_glink_ucsi_read_version, + .read_cci = pmic_glink_ucsi_read_cci, + .poll_cci = pmic_glink_ucsi_read_cci, + .read_message_in = pmic_glink_ucsi_read_message_in, + .sync_control = ucsi_sync_control_common, + .async_control = pmic_glink_ucsi_async_control, + .update_connector = pmic_glink_ucsi_update_connector, + .connector_status = pmic_glink_ucsi_connector_status, +}; + +static void pmic_glink_ucsi_read_ack(struct pmic_glink_ucsi *ucsi, const void *data, int len) +{ + u32 ret_code, resp_len, buf_len = 0; + u8 *buf; + + if (ucsi->ucsi->version) { + if (ucsi->ucsi->version >= UCSI_VERSION_2_0) { + buf = ((struct ucsi_read_buf_resp_msg *)data)->buf.v2_buf; + buf_len = UCSI_BUF_V2_SIZE; + } else { + buf = ((struct ucsi_read_buf_resp_msg *)data)->buf.v1_buf; + buf_len = UCSI_BUF_V1_SIZE; + } + } else if (!ucsi->ucsi_registered) { + /* + * If UCSI version is not known yet because device is not registered, choose buffer + * size which best fits incoming data + */ + if (len > sizeof(struct pmic_glink_hdr) + UCSI_BUF_V2_SIZE) { + buf = ((struct ucsi_read_buf_resp_msg *)data)->buf.v2_buf; + buf_len = UCSI_BUF_V2_SIZE; + } else { + buf = ((struct ucsi_read_buf_resp_msg *)data)->buf.v1_buf; + buf_len = UCSI_BUF_V1_SIZE; + } + } else { + dev_err(ucsi->dev, "Device has been registered but UCSI version is still unknown\n"); + return; + } + + resp_len = sizeof(struct pmic_glink_hdr) + buf_len + sizeof(u32); + + if (len > resp_len) + return; + + /* Ensure that buffer_len leaves space for ret_code to be read back from memory */ + if (buf_len > len - sizeof(struct pmic_glink_hdr) - sizeof(u32)) + buf_len = len - sizeof(struct pmic_glink_hdr) - sizeof(u32); + + memcpy(&ret_code, buf + buf_len, sizeof(u32)); + if (ret_code) + return; + + memcpy(ucsi->read_buf, buf, buf_len); + complete(&ucsi->read_ack); +} + +static void pmic_glink_ucsi_write_ack(struct pmic_glink_ucsi *ucsi, const void *data, int len) +{ + const struct ucsi_write_buf_resp_msg *resp = data; + + if (resp->ret_code) + return; + + complete(&ucsi->write_ack); +} + +static void pmic_glink_ucsi_notify(struct work_struct *work) +{ + struct pmic_glink_ucsi *ucsi = container_of(work, struct pmic_glink_ucsi, notify_work); + u32 cci; + int ret; + + ret = pmic_glink_ucsi_read(ucsi->ucsi, UCSI_CCI, &cci, sizeof(cci)); + if (ret) { + dev_err(ucsi->dev, "failed to read CCI on notification\n"); + return; + } + + ucsi_notify_common(ucsi->ucsi, cci); +} + +static void pmic_glink_ucsi_register(struct work_struct *work) +{ + struct pmic_glink_ucsi *ucsi = container_of(work, struct pmic_glink_ucsi, register_work); + unsigned long flags; + bool pd_running; + + spin_lock_irqsave(&ucsi->state_lock, flags); + pd_running = ucsi->pd_running; + spin_unlock_irqrestore(&ucsi->state_lock, flags); + + if (!ucsi->ucsi_registered && pd_running) { + ucsi_register(ucsi->ucsi); + ucsi->ucsi_registered = true; + } else if (ucsi->ucsi_registered && !pd_running) { + ucsi_unregister(ucsi->ucsi); + ucsi->ucsi_registered = false; + } +} + +static void pmic_glink_ucsi_callback(const void *data, size_t len, void *priv) +{ + struct pmic_glink_ucsi *ucsi = priv; + const struct pmic_glink_hdr *hdr = data; + + switch (le32_to_cpu(hdr->opcode)) { + case UC_UCSI_READ_BUF_REQ: + pmic_glink_ucsi_read_ack(ucsi, data, len); + break; + case UC_UCSI_WRITE_BUF_REQ: + pmic_glink_ucsi_write_ack(ucsi, data, len); + break; + case UC_UCSI_USBC_NOTIFY_IND: + schedule_work(&ucsi->notify_work); + break; + } +} + +static void pmic_glink_ucsi_pdr_notify(void *priv, int state) +{ + struct pmic_glink_ucsi *ucsi = priv; + unsigned long flags; + + spin_lock_irqsave(&ucsi->state_lock, flags); + ucsi->pd_running = (state == SERVREG_SERVICE_STATE_UP); + spin_unlock_irqrestore(&ucsi->state_lock, flags); + schedule_work(&ucsi->register_work); +} + +static void pmic_glink_ucsi_destroy(void *data) +{ + struct pmic_glink_ucsi *ucsi = data; + + /* Protect to make sure we're not in a middle of a transaction from a glink callback */ + mutex_lock(&ucsi->lock); + ucsi_destroy(ucsi->ucsi); + mutex_unlock(&ucsi->lock); +} + +static unsigned long quirk_sc8180x = UCSI_NO_PARTNER_PDOS; +static unsigned long quirk_sc8280xp = UCSI_NO_PARTNER_PDOS | UCSI_DELAY_DEVICE_PDOS; +static unsigned long quirk_sm8450 = UCSI_DELAY_DEVICE_PDOS; +static unsigned long quirk_x1e80100 = UCSI_DELAY_DEVICE_PDOS | UCSI_USB4_IMPLIES_USB; + +static const struct of_device_id pmic_glink_ucsi_of_quirks[] = { + { .compatible = "qcom,glymur-pmic-glink", .data = &quirk_sm8450, }, + { .compatible = "qcom,kaanapali-pmic-glink", .data = &quirk_sm8450, }, + { .compatible = "qcom,qcm6490-pmic-glink", .data = &quirk_sc8280xp, }, + { .compatible = "qcom,sc8180x-pmic-glink", .data = &quirk_sc8180x, }, + { .compatible = "qcom,sc8280xp-pmic-glink", .data = &quirk_sc8280xp, }, + { .compatible = "qcom,sm8350-pmic-glink", .data = &quirk_sc8180x, }, + { .compatible = "qcom,sm8450-pmic-glink", .data = &quirk_sm8450, }, + { .compatible = "qcom,sm8550-pmic-glink", .data = &quirk_sm8450, }, + { .compatible = "qcom,x1e80100-pmic-glink", .data = &quirk_x1e80100, }, + {} +}; + +static int pmic_glink_ucsi_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct pmic_glink_ucsi *ucsi; + struct device *dev = &adev->dev; + const struct of_device_id *match; + int ret; + + ucsi = devm_kzalloc(dev, sizeof(*ucsi), GFP_KERNEL); + if (!ucsi) + return -ENOMEM; + + ucsi->dev = dev; + dev_set_drvdata(dev, ucsi); + + INIT_WORK(&ucsi->notify_work, pmic_glink_ucsi_notify); + INIT_WORK(&ucsi->register_work, pmic_glink_ucsi_register); + init_completion(&ucsi->read_ack); + init_completion(&ucsi->write_ack); + spin_lock_init(&ucsi->state_lock); + mutex_init(&ucsi->lock); + + ucsi->ucsi = ucsi_create(dev, &pmic_glink_ucsi_ops); + if (IS_ERR(ucsi->ucsi)) + return PTR_ERR(ucsi->ucsi); + + /* Make sure we destroy *after* pmic_glink unregister */ + ret = devm_add_action_or_reset(dev, pmic_glink_ucsi_destroy, ucsi); + if (ret) + return ret; + + match = of_match_device(pmic_glink_ucsi_of_quirks, dev->parent); + if (match) + ucsi->ucsi->quirks = *(unsigned long *)match->data; + + ucsi_set_drvdata(ucsi->ucsi, ucsi); + + device_for_each_child_node_scoped(dev, fwnode) { + struct gpio_desc *desc; + u32 port; + + ret = fwnode_property_read_u32(fwnode, "reg", &port); + if (ret < 0) { + dev_err(dev, "missing reg property of %pOFn\n", fwnode); + return ret; + } + + if (port >= PMIC_GLINK_MAX_PORTS) { + dev_warn(dev, "invalid connector number, ignoring\n"); + continue; + } + + desc = devm_gpiod_get_index_optional(&adev->dev, "orientation", port, GPIOD_IN); + + /* If GPIO isn't found, continue */ + if (!desc) + continue; + + if (IS_ERR(desc)) + return dev_err_probe(dev, PTR_ERR(desc), + "unable to acquire orientation gpio\n"); + + ucsi->port_orientation[port] = desc; + } + + ucsi->client = devm_pmic_glink_client_alloc(dev, PMIC_GLINK_OWNER_USBC, + pmic_glink_ucsi_callback, + pmic_glink_ucsi_pdr_notify, + ucsi); + if (IS_ERR(ucsi->client)) + return PTR_ERR(ucsi->client); + + pmic_glink_client_register(ucsi->client); + + return 0; +} + +static void pmic_glink_ucsi_remove(struct auxiliary_device *adev) +{ + struct pmic_glink_ucsi *ucsi = dev_get_drvdata(&adev->dev); + + /* Unregister first to stop having read & writes */ + ucsi_unregister(ucsi->ucsi); +} + +static const struct auxiliary_device_id pmic_glink_ucsi_id_table[] = { + { .name = "pmic_glink.ucsi", }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, pmic_glink_ucsi_id_table); + +static struct auxiliary_driver pmic_glink_ucsi_driver = { + .name = "pmic_glink_ucsi", + .probe = pmic_glink_ucsi_probe, + .remove = pmic_glink_ucsi_remove, + .id_table = pmic_glink_ucsi_id_table, +}; + +module_auxiliary_driver(pmic_glink_ucsi_driver); + +MODULE_DESCRIPTION("Qualcomm PMIC GLINK UCSI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c new file mode 100644 index 000000000..57b37fca1 --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c @@ -0,0 +1,564 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ucsi-huawei-gaokun - A UCSI driver for HUAWEI Matebook E Go + * + * Copyright (C) 2024-2025 Pengyu Luo + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ucsi.h" + +#define EC_EVENT_UCSI 0x21 +#define EC_EVENT_USB 0x22 + +#define GAOKUN_CCX_MASK GENMASK(1, 0) +#define GAOKUN_MUX_MASK GENMASK(3, 2) + +#define GAOKUN_DPAM_MASK GENMASK(3, 0) +#define GAOKUN_HPD_STATE_MASK BIT(4) +#define GAOKUN_HPD_IRQ_MASK BIT(5) + +#define GET_IDX(updt) (ffs(updt) - 1) + +#define CCX_TO_ORI(ccx) (++(ccx) % 3) /* convert ccx to enum typec_orientation */ + +/* Configuration Channel Extension */ +enum gaokun_ucsi_ccx { + USBC_CCX_NORMAL, + USBC_CCX_REVERSE, + USBC_CCX_NONE, +}; + +enum gaokun_ucsi_mux { + USBC_MUX_NONE, + USBC_MUX_USB_2L, + USBC_MUX_DP_4L, + USBC_MUX_USB_DP, +}; + +/* based on pmic_glink_altmode_pin_assignment */ +enum gaokun_ucsi_dpam_pan { /* DP Alt Mode Pin Assignments */ + USBC_DPAM_PAN_NONE, + USBC_DPAM_PAN_A, /* Not supported after USB Type-C Standard v1.0b */ + USBC_DPAM_PAN_B, /* Not supported after USB Type-C Standard v1.0b */ + USBC_DPAM_PAN_C, /* USBC_DPAM_PAN_C_REVERSE - 6 */ + USBC_DPAM_PAN_D, + USBC_DPAM_PAN_E, + USBC_DPAM_PAN_F, /* Not supported after USB Type-C Standard v1.0b */ + USBC_DPAM_PAN_A_REVERSE,/* Not supported after USB Type-C Standard v1.0b */ + USBC_DPAM_PAN_B_REVERSE,/* Not supported after USB Type-C Standard v1.0b */ + USBC_DPAM_PAN_C_REVERSE, + USBC_DPAM_PAN_D_REVERSE, + USBC_DPAM_PAN_E_REVERSE, + USBC_DPAM_PAN_F_REVERSE,/* Not supported after USB Type-C Standard v1.0b */ +}; + +struct gaokun_ucsi_reg { + u8 num_ports; + u8 port_updt; + u8 port_data[4]; + u8 checksum; + u8 reserved; +} __packed; + +struct gaokun_ucsi_port { + struct completion usb_ack; + spinlock_t lock; /* serializing port resource access */ + + struct gaokun_ucsi *ucsi; + struct auxiliary_device *bridge; + + struct typec_mux *typec_mux; + struct typec_mux_state state; + struct typec_altmode dp_alt; + + int idx; + enum gaokun_ucsi_ccx ccx; + enum gaokun_ucsi_mux mux; + u8 mode; + u16 svid; + u8 hpd_state; + u8 hpd_irq; +}; + +struct gaokun_ucsi { + struct gaokun_ec *ec; + struct ucsi *ucsi; + struct gaokun_ucsi_port *ports; + struct device *dev; + struct delayed_work work; + struct notifier_block nb; + u16 version; + u8 num_ports; + bool registered; +}; + +/* -------------------------------------------------------------------------- */ +/* For UCSI */ + +static int gaokun_ucsi_read_version(struct ucsi *ucsi, u16 *version) +{ + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi); + + *version = uec->version; + + return 0; +} + +static int gaokun_ucsi_read_cci(struct ucsi *ucsi, u32 *cci) +{ + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi); + u8 buf[GAOKUN_UCSI_READ_SIZE]; + int ret; + + ret = gaokun_ec_ucsi_read(uec->ec, buf); + if (ret) + return ret; + + memcpy(cci, buf, sizeof(*cci)); + + return 0; +} + +static int gaokun_ucsi_read_message_in(struct ucsi *ucsi, + void *val, size_t val_len) +{ + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi); + u8 buf[GAOKUN_UCSI_READ_SIZE]; + int ret; + + ret = gaokun_ec_ucsi_read(uec->ec, buf); + if (ret) + return ret; + + memcpy(val, buf + GAOKUN_UCSI_CCI_SIZE, + min(val_len, GAOKUN_UCSI_MSGI_SIZE)); + + return 0; +} + +static int gaokun_ucsi_async_control(struct ucsi *ucsi, u64 command) +{ + struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi); + u8 buf[GAOKUN_UCSI_WRITE_SIZE] = {}; + + memcpy(buf, &command, sizeof(command)); + + return gaokun_ec_ucsi_write(uec->ec, buf); +} + +static void gaokun_ucsi_update_connector(struct ucsi_connector *con) +{ + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi); + + if (con->num > uec->num_ports) + return; + + con->typec_cap.orientation_aware = true; +} + +static void gaokun_set_orientation(struct ucsi_connector *con, + struct gaokun_ucsi_port *port) +{ + enum gaokun_ucsi_ccx ccx; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + ccx = port->ccx; + spin_unlock_irqrestore(&port->lock, flags); + + typec_set_orientation(con->port, CCX_TO_ORI(ccx)); +} + +static void gaokun_ucsi_connector_status(struct ucsi_connector *con) +{ + struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi); + int idx; + + idx = con->num - 1; + if (con->num > uec->num_ports) { + dev_warn(uec->dev, "set orientation out of range: con%d\n", idx); + return; + } + + gaokun_set_orientation(con, &uec->ports[idx]); +} + +static const struct ucsi_operations gaokun_ucsi_ops = { + .read_version = gaokun_ucsi_read_version, + .read_cci = gaokun_ucsi_read_cci, + .poll_cci = gaokun_ucsi_read_cci, + .read_message_in = gaokun_ucsi_read_message_in, + .sync_control = ucsi_sync_control_common, + .async_control = gaokun_ucsi_async_control, + .update_connector = gaokun_ucsi_update_connector, + .connector_status = gaokun_ucsi_connector_status, +}; + +/* -------------------------------------------------------------------------- */ +/* For Altmode */ + +static void gaokun_ucsi_port_update(struct gaokun_ucsi_port *port, + const u8 *port_data) +{ + struct gaokun_ucsi *uec = port->ucsi; + int offset = port->idx * 2; /* every port has 2 Bytes data */ + unsigned long flags; + u8 dcc, ddi; + + dcc = port_data[offset]; + ddi = port_data[offset + 1]; + + spin_lock_irqsave(&port->lock, flags); + + port->ccx = FIELD_GET(GAOKUN_CCX_MASK, dcc); + port->mux = FIELD_GET(GAOKUN_MUX_MASK, dcc); + port->mode = FIELD_GET(GAOKUN_DPAM_MASK, ddi); + port->hpd_state = FIELD_GET(GAOKUN_HPD_STATE_MASK, ddi); + port->hpd_irq = FIELD_GET(GAOKUN_HPD_IRQ_MASK, ddi); + + switch (port->mode) { + case USBC_DPAM_PAN_C: + case USBC_DPAM_PAN_C_REVERSE: + port->mode = TYPEC_DP_STATE_C; /* correct it for usb later */ + break; + case USBC_DPAM_PAN_D: + case USBC_DPAM_PAN_D_REVERSE: + port->mode = TYPEC_DP_STATE_D; + break; + case USBC_DPAM_PAN_E: + case USBC_DPAM_PAN_E_REVERSE: + port->mode = TYPEC_DP_STATE_E; + break; + case USBC_DPAM_PAN_NONE: + port->mode = TYPEC_STATE_SAFE; + break; + default: + dev_warn(uec->dev, "unknown mode %d\n", port->mode); + break; + } + + switch (port->mux) { + case USBC_MUX_NONE: + port->svid = 0; + break; + case USBC_MUX_USB_2L: + port->svid = USB_SID_PD; + port->mode = TYPEC_STATE_USB; /* same as PAN_C, correct it */ + break; + case USBC_MUX_DP_4L: + case USBC_MUX_USB_DP: + port->svid = USB_SID_DISPLAYPORT; + break; + default: + dev_warn(uec->dev, "unknown mux state %d\n", port->mux); + break; + } + + spin_unlock_irqrestore(&port->lock, flags); +} + +static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec) +{ + struct gaokun_ucsi_reg ureg; + int ret, idx; + + ret = gaokun_ec_ucsi_get_reg(uec->ec, &ureg); + if (ret) + return GAOKUN_UCSI_NO_PORT_UPDATE; + + uec->num_ports = ureg.num_ports; + idx = GET_IDX(ureg.port_updt); + + if (idx < 0 || idx >= ureg.num_ports) + return GAOKUN_UCSI_NO_PORT_UPDATE; + + gaokun_ucsi_port_update(&uec->ports[idx], ureg.port_data); + return idx; +} + +static void gaokun_ucsi_handle_usb_mode(struct gaokun_ucsi_port *port) +{ + struct gaokun_ucsi *uec = port->ucsi; + int idx = port->idx, ret; + + /* + * For every typec port on this platform, the only mode-switch is + * controlled by its qmp combo phy which consumes svid and mode only. + */ + port->dp_alt.svid = port->svid; + port->state.mode = port->mode; + port->state.alt = &port->dp_alt; + + if (idx >= uec->num_ports) { + dev_warn(uec->dev, "altmode port out of range: %d\n", idx); + return; + } + + ret = typec_mux_set(port->typec_mux, &port->state); + if (ret) + dev_err(uec->dev, "failed to set mux %d\n", ret); + + /* UCSI callback .connector_status() have set orientation */ + if (port->bridge && port->svid == USB_TYPEC_DP_SID) + drm_aux_hpd_bridge_notify(&port->bridge->dev, + port->hpd_state ? + connector_status_connected : + connector_status_disconnected); + + gaokun_ec_ucsi_pan_ack(uec->ec, port->idx); +} + +static void gaokun_ucsi_usb_notify_ind(struct gaokun_ucsi *uec) +{ + int idx; + + if (!uec->ucsi->connector) { /* slow to register */ + dev_err_ratelimited(uec->dev, "ucsi connector is not initialized yet\n"); + return; + } + + idx = gaokun_ucsi_refresh(uec); + if (idx == GAOKUN_UCSI_NO_PORT_UPDATE) + gaokun_ec_ucsi_pan_ack(uec->ec, idx); /* ack directly if no update */ + else + gaokun_ucsi_handle_usb_mode(&uec->ports[idx]); +} + +/* + * When inserting, 2 UCSI events(connector change) are followed by USB events. + * If we received one USB event, that means USB events are not blocked, so we + * can complelte for all ports, and we should signal all events. + */ +static void gaokun_ucsi_complete_usb_ack(struct gaokun_ucsi *uec) +{ + struct gaokun_ucsi_port *port; + int idx = 0; + + while (idx < uec->num_ports) { + port = &uec->ports[idx++]; + if (!completion_done(&port->usb_ack)) + complete_all(&port->usb_ack); + } +} + +/* + * USB event is necessary for enabling altmode, the event should follow + * UCSI event, if not after timeout(this notify may be disabled somehow), + * then force to enable altmode. + */ +static void gaokun_ucsi_handle_no_usb_event(struct gaokun_ucsi *uec, int idx) +{ + struct gaokun_ucsi_port *port; + + port = &uec->ports[idx]; + if (!wait_for_completion_timeout(&port->usb_ack, 2 * HZ)) { + dev_warn(uec->dev, "No USB EVENT, triggered by UCSI EVENT"); + gaokun_ucsi_usb_notify_ind(uec); + } +} + +static int gaokun_ucsi_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + u32 cci; + int ret; + struct gaokun_ucsi *uec = container_of(nb, struct gaokun_ucsi, nb); + + switch (action) { + case EC_EVENT_USB: + gaokun_ucsi_complete_usb_ack(uec); + gaokun_ucsi_usb_notify_ind(uec); + return NOTIFY_OK; + + case EC_EVENT_UCSI: + ret = gaokun_ucsi_read_cci(uec->ucsi, &cci); + if (ret) + return NOTIFY_DONE; + + ucsi_notify_common(uec->ucsi, cci); + if (UCSI_CCI_CONNECTOR(cci)) + gaokun_ucsi_handle_no_usb_event(uec, UCSI_CCI_CONNECTOR(cci) - 1); + + return NOTIFY_OK; + + default: + return NOTIFY_DONE; + } +} + +static int gaokun_ucsi_ports_init(struct gaokun_ucsi *uec) +{ + struct gaokun_ucsi_port *ucsi_port; + struct gaokun_ucsi_reg ureg = {}; + struct device *dev = uec->dev; + struct fwnode_handle *fwnode; + int i, ret, num_ports; + u32 port; + + gaokun_ec_ucsi_get_reg(uec->ec, &ureg); + num_ports = ureg.num_ports; + uec->ports = devm_kcalloc(dev, num_ports, sizeof(*uec->ports), + GFP_KERNEL); + if (!uec->ports) + return -ENOMEM; + + for (i = 0; i < num_ports; ++i) { + ucsi_port = &uec->ports[i]; + ucsi_port->ccx = USBC_CCX_NONE; + ucsi_port->idx = i; + ucsi_port->ucsi = uec; + init_completion(&ucsi_port->usb_ack); + spin_lock_init(&ucsi_port->lock); + } + + device_for_each_child_node(dev, fwnode) { + ret = fwnode_property_read_u32(fwnode, "reg", &port); + if (ret < 0) { + dev_err(dev, "missing reg property of %pOFn\n", fwnode); + fwnode_handle_put(fwnode); + return ret; + } + + if (port >= num_ports) { + dev_warn(dev, "invalid connector number %d, ignoring\n", port); + continue; + } + + ucsi_port = &uec->ports[port]; + ucsi_port->bridge = devm_drm_dp_hpd_bridge_alloc(dev, to_of_node(fwnode)); + if (IS_ERR(ucsi_port->bridge)) { + fwnode_handle_put(fwnode); + return PTR_ERR(ucsi_port->bridge); + } + + ucsi_port->typec_mux = fwnode_typec_mux_get(fwnode); + if (IS_ERR(ucsi_port->typec_mux)) { + fwnode_handle_put(fwnode); + return dev_err_probe(dev, PTR_ERR(ucsi_port->typec_mux), + "failed to acquire mode-switch for port: %d\n", + port); + } + } + for (i = 0; i < num_ports; i++) { + if (!uec->ports[i].bridge) + continue; + + ret = devm_drm_dp_hpd_bridge_add(dev, uec->ports[i].bridge); + if (ret) + return ret; + } + + return 0; +} + +static void gaokun_ucsi_register_worker(struct work_struct *work) +{ + struct gaokun_ucsi *uec; + struct ucsi *ucsi; + int ret; + + uec = container_of(work, struct gaokun_ucsi, work.work); + ucsi = uec->ucsi; + + ret = gaokun_ec_register_notify(uec->ec, &uec->nb); + if (ret) { + dev_err_probe(ucsi->dev, ret, "notifier register failed\n"); + return; + } + + ret = ucsi_register(ucsi); + if (ret) { + dev_err_probe(ucsi->dev, ret, "ucsi register failed\n"); + gaokun_ec_unregister_notify(uec->ec, &uec->nb); + return; + } + + uec->registered = true; +} + +static int gaokun_ucsi_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct gaokun_ec *ec = adev->dev.platform_data; + struct device *dev = &adev->dev; + struct gaokun_ucsi *uec; + int ret; + + uec = devm_kzalloc(dev, sizeof(*uec), GFP_KERNEL); + if (!uec) + return -ENOMEM; + + uec->ec = ec; + uec->dev = dev; + uec->version = UCSI_VERSION_1_0; + uec->nb.notifier_call = gaokun_ucsi_notify; + + INIT_DELAYED_WORK(&uec->work, gaokun_ucsi_register_worker); + + ret = gaokun_ucsi_ports_init(uec); + if (ret) + return ret; + + uec->ucsi = ucsi_create(dev, &gaokun_ucsi_ops); + if (IS_ERR(uec->ucsi)) + return PTR_ERR(uec->ucsi); + + ucsi_set_drvdata(uec->ucsi, uec); + auxiliary_set_drvdata(adev, uec); + + /* EC can't handle UCSI properly in the early stage */ + schedule_delayed_work(&uec->work, 3 * HZ); + + return 0; +} + +static void gaokun_ucsi_remove(struct auxiliary_device *adev) +{ + struct gaokun_ucsi *uec = auxiliary_get_drvdata(adev); + int i; + + disable_delayed_work_sync(&uec->work); + if (uec->registered) { + gaokun_ec_unregister_notify(uec->ec, &uec->nb); + ucsi_unregister(uec->ucsi); + } + + for (i = 0; i < uec->num_ports; ++i) + typec_mux_put(uec->ports[i].typec_mux); + + ucsi_destroy(uec->ucsi); +} + +static const struct auxiliary_device_id gaokun_ucsi_id_table[] = { + { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_UCSI, }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, gaokun_ucsi_id_table); + +static struct auxiliary_driver gaokun_ucsi_driver = { + .name = GAOKUN_DEV_UCSI, + .id_table = gaokun_ucsi_id_table, + .probe = gaokun_ucsi_probe, + .remove = gaokun_ucsi_remove, +}; + +module_auxiliary_driver(gaokun_ucsi_driver); + +MODULE_DESCRIPTION("HUAWEI Matebook E Go UCSI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/ucsi/ucsi_stm32g0.c b/drivers/usb/typec/ucsi/ucsi_stm32g0.c new file mode 100644 index 000000000..848ed459a --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi_stm32g0.c @@ -0,0 +1,760 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* + * UCSI driver for STMicroelectronics STM32G0 Type-C PD controller + * + * Copyright (C) 2022, STMicroelectronics - All Rights Reserved + * Author: Fabrice Gasnier . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ucsi.h" + +/* STM32G0 I2C bootloader addr: 0b1010001x (See AN2606) */ +#define STM32G0_I2C_BL_ADDR (0xa2 >> 1) + +/* STM32G0 I2C bootloader max data size */ +#define STM32G0_I2C_BL_SZ 256 + +/* STM32 I2C bootloader commands (See AN4221) */ +#define STM32_CMD_GVR 0x01 /* Gets the bootloader version */ +#define STM32_CMD_GVR_LEN 1 +#define STM32_CMD_RM 0x11 /* Reag memory */ +#define STM32_CMD_WM 0x31 /* Write memory */ +#define STM32_CMD_ADDR_LEN 5 /* Address len for go, mem write... */ +#define STM32_CMD_ERASE 0x44 /* Erase page, bank or all */ +#define STM32_CMD_ERASE_SPECIAL_LEN 3 +#define STM32_CMD_GLOBAL_MASS_ERASE 0xffff /* All-bank erase */ + +/* STM32 I2C bootloader answer status */ +#define STM32G0_I2C_BL_ACK 0x79 +#define STM32G0_I2C_BL_NACK 0x1f +#define STM32G0_I2C_BL_BUSY 0x76 + +/* STM32G0 flash definitions */ +#define STM32G0_USER_OPTION_BYTES 0x1fff7800 +#define STM32G0_USER_OB_NBOOT0 BIT(26) +#define STM32G0_USER_OB_NBOOT_SEL BIT(24) +#define STM32G0_USER_OB_BOOT_MAIN (STM32G0_USER_OB_NBOOT0 | STM32G0_USER_OB_NBOOT_SEL) +#define STM32G0_MAIN_MEM_ADDR 0x08000000 + +/* STM32 Firmware definitions: additional commands */ +#define STM32G0_FW_GETVER 0x00 /* Gets the firmware version */ +#define STM32G0_FW_GETVER_LEN 4 +#define STM32G0_FW_RSTGOBL 0x21 /* Reset and go to bootloader */ +#define STM32G0_FW_KEYWORD 0xa56959a6 + +/* ucsi_stm32g0_fw_info located at the end of the firmware */ +struct ucsi_stm32g0_fw_info { + u32 version; + u32 keyword; +}; + +struct ucsi_stm32g0 { + struct i2c_client *client; + struct i2c_client *i2c_bl; + bool in_bootloader; + u8 bl_version; + struct device *dev; + const char *fw_name; + struct ucsi *ucsi; + bool suspended; + bool wakeup_event; +}; + +/* + * Bootloader commands helpers: + * - send command (2 bytes) + * - check ack + * Then either: + * - receive data + * - receive data + check ack + * - send data + check ack + * These operations depends on the command and have various length. + */ +static int ucsi_stm32g0_bl_check_ack(struct ucsi *ucsi) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->i2c_bl; + unsigned char ack; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = 1, + .buf = &ack, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c bl ack (%02x), error: %d\n", client->addr, ret); + + return ret < 0 ? ret : -EIO; + } + + /* The 'ack' byte should contain bootloader answer: ack/nack/busy */ + switch (ack) { + case STM32G0_I2C_BL_ACK: + return 0; + case STM32G0_I2C_BL_NACK: + return -ENOENT; + case STM32G0_I2C_BL_BUSY: + return -EBUSY; + default: + dev_err(g0->dev, "i2c bl ack (%02x), invalid byte: %02x\n", + client->addr, ack); + return -EINVAL; + } +} + +static int ucsi_stm32g0_bl_cmd_check_ack(struct ucsi *ucsi, unsigned int cmd, bool check_ack) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->i2c_bl; + unsigned char buf[2]; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = sizeof(buf), + .buf = buf, + }, + }; + int ret; + + /* + * Send STM32 bootloader command format is two bytes: + * - command code + * - XOR'ed command code + */ + buf[0] = cmd; + buf[1] = cmd ^ 0xff; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_dbg(g0->dev, "i2c bl cmd %d (%02x), error: %d\n", cmd, client->addr, ret); + + return ret < 0 ? ret : -EIO; + } + + if (check_ack) + return ucsi_stm32g0_bl_check_ack(ucsi); + + return 0; +} + +static int ucsi_stm32g0_bl_cmd(struct ucsi *ucsi, unsigned int cmd) +{ + return ucsi_stm32g0_bl_cmd_check_ack(ucsi, cmd, true); +} + +static int ucsi_stm32g0_bl_rcv_check_ack(struct ucsi *ucsi, void *data, size_t len, bool check_ack) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->i2c_bl; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = data, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c bl rcv %02x, error: %d\n", client->addr, ret); + + return ret < 0 ? ret : -EIO; + } + + if (check_ack) + return ucsi_stm32g0_bl_check_ack(ucsi); + + return 0; +} + +static int ucsi_stm32g0_bl_rcv(struct ucsi *ucsi, void *data, size_t len) +{ + return ucsi_stm32g0_bl_rcv_check_ack(ucsi, data, len, true); +} + +static int ucsi_stm32g0_bl_rcv_woack(struct ucsi *ucsi, void *data, size_t len) +{ + return ucsi_stm32g0_bl_rcv_check_ack(ucsi, data, len, false); +} + +static int ucsi_stm32g0_bl_send(struct ucsi *ucsi, void *data, size_t len) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->i2c_bl; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = len, + .buf = data, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c bl send %02x, error: %d\n", client->addr, ret); + + return ret < 0 ? ret : -EIO; + } + + return ucsi_stm32g0_bl_check_ack(ucsi); +} + +/* Bootloader commands */ +static int ucsi_stm32g0_bl_get_version(struct ucsi *ucsi, u8 *bl_version) +{ + int ret; + + ret = ucsi_stm32g0_bl_cmd(ucsi, STM32_CMD_GVR); + if (ret) + return ret; + + return ucsi_stm32g0_bl_rcv(ucsi, bl_version, STM32_CMD_GVR_LEN); +} + +static int ucsi_stm32g0_bl_send_addr(struct ucsi *ucsi, u32 addr) +{ + u8 data8[STM32_CMD_ADDR_LEN]; + + /* Address format: 4 bytes addr (MSB first) + XOR'ed addr bytes */ + put_unaligned_be32(addr, data8); + data8[4] = data8[0] ^ data8[1] ^ data8[2] ^ data8[3]; + + return ucsi_stm32g0_bl_send(ucsi, data8, STM32_CMD_ADDR_LEN); +} + +static int ucsi_stm32g0_bl_global_mass_erase(struct ucsi *ucsi) +{ + u8 data8[4]; + u16 *data16 = (u16 *)&data8[0]; + int ret; + + data16[0] = STM32_CMD_GLOBAL_MASS_ERASE; + data8[2] = data8[0] ^ data8[1]; + + ret = ucsi_stm32g0_bl_cmd(ucsi, STM32_CMD_ERASE); + if (ret) + return ret; + + return ucsi_stm32g0_bl_send(ucsi, data8, STM32_CMD_ERASE_SPECIAL_LEN); +} + +static int ucsi_stm32g0_bl_write(struct ucsi *ucsi, u32 addr, const void *data, size_t len) +{ + u8 *data8; + int i, ret; + + if (!len || len > STM32G0_I2C_BL_SZ) + return -EINVAL; + + /* Write memory: len bytes -1, data up to 256 bytes + XOR'ed bytes */ + data8 = kmalloc(STM32G0_I2C_BL_SZ + 2, GFP_KERNEL); + if (!data8) + return -ENOMEM; + + ret = ucsi_stm32g0_bl_cmd(ucsi, STM32_CMD_WM); + if (ret) + goto free; + + ret = ucsi_stm32g0_bl_send_addr(ucsi, addr); + if (ret) + goto free; + + data8[0] = len - 1; + memcpy(data8 + 1, data, len); + data8[len + 1] = data8[0]; + for (i = 1; i <= len; i++) + data8[len + 1] ^= data8[i]; + + ret = ucsi_stm32g0_bl_send(ucsi, data8, len + 2); +free: + kfree(data8); + + return ret; +} + +static int ucsi_stm32g0_bl_read(struct ucsi *ucsi, u32 addr, void *data, size_t len) +{ + int ret; + + if (!len || len > STM32G0_I2C_BL_SZ) + return -EINVAL; + + ret = ucsi_stm32g0_bl_cmd(ucsi, STM32_CMD_RM); + if (ret) + return ret; + + ret = ucsi_stm32g0_bl_send_addr(ucsi, addr); + if (ret) + return ret; + + ret = ucsi_stm32g0_bl_cmd(ucsi, len - 1); + if (ret) + return ret; + + return ucsi_stm32g0_bl_rcv_woack(ucsi, data, len); +} + +/* Firmware commands (the same address as the bootloader) */ +static int ucsi_stm32g0_fw_cmd(struct ucsi *ucsi, unsigned int cmd) +{ + return ucsi_stm32g0_bl_cmd_check_ack(ucsi, cmd, false); +} + +static int ucsi_stm32g0_fw_rcv(struct ucsi *ucsi, void *data, size_t len) +{ + return ucsi_stm32g0_bl_rcv_woack(ucsi, data, len); +} + +/* UCSI ops */ +static int ucsi_stm32g0_read(struct ucsi *ucsi, unsigned int offset, void *val, size_t len) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->client; + u8 reg = offset; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = ®, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = val, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c read %02x, %02x error: %d\n", client->addr, reg, ret); + + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +static int ucsi_stm32g0_read_version(struct ucsi *ucsi, u16 *version) +{ + return ucsi_stm32g0_read(ucsi, UCSI_VERSION, version, sizeof(*version)); +} + +static int ucsi_stm32g0_read_cci(struct ucsi *ucsi, u32 *cci) +{ + return ucsi_stm32g0_read(ucsi, UCSI_CCI, cci, sizeof(*cci)); +} + +static int ucsi_stm32g0_read_message_in(struct ucsi *ucsi, void *val, size_t len) +{ + return ucsi_stm32g0_read(ucsi, UCSI_MESSAGE_IN, val, len); +} + +static int ucsi_stm32g0_async_control(struct ucsi *ucsi, u64 command) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->client; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + } + }; + unsigned char *buf; + int ret; + + buf = kmalloc(sizeof(command) + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = UCSI_CONTROL; + memcpy(&buf[1], &command, sizeof(command)); + msg[0].len = sizeof(command) + 1; + msg[0].buf = buf; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + kfree(buf); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c write %02x, %02x error: %d\n", client->addr, UCSI_CONTROL, ret); + + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +static irqreturn_t ucsi_stm32g0_irq_handler(int irq, void *data) +{ + struct ucsi_stm32g0 *g0 = data; + u32 cci; + int ret; + + if (g0->suspended) + g0->wakeup_event = true; + + ret = ucsi_stm32g0_read(g0->ucsi, UCSI_CCI, &cci, sizeof(cci)); + if (ret) + return IRQ_NONE; + + ucsi_notify_common(g0->ucsi, cci); + + return IRQ_HANDLED; +} + +static const struct ucsi_operations ucsi_stm32g0_ops = { + .read_version = ucsi_stm32g0_read_version, + .read_cci = ucsi_stm32g0_read_cci, + .poll_cci = ucsi_stm32g0_read_cci, + .read_message_in = ucsi_stm32g0_read_message_in, + .sync_control = ucsi_sync_control_common, + .async_control = ucsi_stm32g0_async_control, +}; + +static int ucsi_stm32g0_register(struct ucsi *ucsi) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->client; + int ret; + + /* Request alert interrupt */ + ret = request_threaded_irq(client->irq, NULL, ucsi_stm32g0_irq_handler, IRQF_ONESHOT, + dev_name(g0->dev), g0); + if (ret) { + dev_err(g0->dev, "request IRQ failed: %d\n", ret); + return ret; + } + + ret = ucsi_register(ucsi); + if (ret) { + dev_err_probe(g0->dev, ret, "ucsi_register failed\n"); + free_irq(client->irq, g0); + return ret; + } + + return 0; +} + +static void ucsi_stm32g0_unregister(struct ucsi *ucsi) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->client; + + ucsi_unregister(ucsi); + free_irq(client->irq, g0); +} + +static void ucsi_stm32g0_fw_cb(const struct firmware *fw, void *context) +{ + struct ucsi_stm32g0 *g0; + const u8 *data, *end; + const struct ucsi_stm32g0_fw_info *fw_info; + u32 addr = STM32G0_MAIN_MEM_ADDR, ob, fw_version; + int ret, size; + + if (!context) + return; + + g0 = ucsi_get_drvdata(context); + + if (!fw) + goto fw_release; + + fw_info = (struct ucsi_stm32g0_fw_info *)(fw->data + fw->size - sizeof(*fw_info)); + + if (!g0->in_bootloader) { + /* Read running firmware version */ + ret = ucsi_stm32g0_fw_cmd(g0->ucsi, STM32G0_FW_GETVER); + if (ret) { + dev_err(g0->dev, "Get version cmd failed %d\n", ret); + goto fw_release; + } + ret = ucsi_stm32g0_fw_rcv(g0->ucsi, &fw_version, + STM32G0_FW_GETVER_LEN); + if (ret) { + dev_err(g0->dev, "Get version failed %d\n", ret); + goto fw_release; + } + + /* Sanity check on keyword and firmware version */ + if (fw_info->keyword != STM32G0_FW_KEYWORD || fw_info->version == fw_version) + goto fw_release; + + dev_info(g0->dev, "Flashing FW: %08x (%08x cur)\n", fw_info->version, fw_version); + + /* Switch to bootloader mode */ + ucsi_stm32g0_unregister(g0->ucsi); + ret = ucsi_stm32g0_fw_cmd(g0->ucsi, STM32G0_FW_RSTGOBL); + if (ret) { + dev_err(g0->dev, "bootloader cmd failed %d\n", ret); + goto fw_release; + } + g0->in_bootloader = true; + + /* STM32G0 reboot delay */ + msleep(100); + } + + ret = ucsi_stm32g0_bl_global_mass_erase(g0->ucsi); + if (ret) { + dev_err(g0->dev, "Erase failed %d\n", ret); + goto fw_release; + } + + data = fw->data; + end = fw->data + fw->size; + while (data < end) { + size = min(end - data, STM32G0_I2C_BL_SZ); + ret = ucsi_stm32g0_bl_write(g0->ucsi, addr, data, size); + if (ret) { + dev_err(g0->dev, "Write failed %d\n", ret); + goto fw_release; + } + addr += size; + data += size; + } + + dev_dbg(g0->dev, "Configure to boot from main flash\n"); + + ret = ucsi_stm32g0_bl_read(g0->ucsi, STM32G0_USER_OPTION_BYTES, &ob, sizeof(ob)); + if (ret) { + dev_err(g0->dev, "read user option bytes failed %d\n", ret); + goto fw_release; + } + + dev_dbg(g0->dev, "STM32G0_USER_OPTION_BYTES 0x%08x\n", ob); + + /* Configure user option bytes to boot from main flash next time */ + ob |= STM32G0_USER_OB_BOOT_MAIN; + + /* Writing option bytes will also reset G0 for updates to be loaded */ + ret = ucsi_stm32g0_bl_write(g0->ucsi, STM32G0_USER_OPTION_BYTES, &ob, sizeof(ob)); + if (ret) { + dev_err(g0->dev, "write user option bytes failed %d\n", ret); + goto fw_release; + } + + dev_info(g0->dev, "Starting, option bytes:0x%08x\n", ob); + + /* STM32G0 FW boot delay */ + msleep(500); + + /* Register UCSI interface */ + if (!ucsi_stm32g0_register(g0->ucsi)) + g0->in_bootloader = false; + +fw_release: + release_firmware(fw); +} + +static int ucsi_stm32g0_probe_bootloader(struct ucsi *ucsi) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + int ret; + u16 ucsi_version; + + /* firmware-name is optional */ + if (device_property_present(g0->dev, "firmware-name")) { + ret = device_property_read_string(g0->dev, "firmware-name", &g0->fw_name); + if (ret < 0) + return dev_err_probe(g0->dev, ret, "Error reading firmware-name\n"); + } + + if (g0->fw_name) { + /* STM32G0 in bootloader mode communicates at reserved address 0x51 */ + g0->i2c_bl = i2c_new_dummy_device(g0->client->adapter, STM32G0_I2C_BL_ADDR); + if (IS_ERR(g0->i2c_bl)) { + ret = dev_err_probe(g0->dev, PTR_ERR(g0->i2c_bl), + "Failed to register bootloader I2C address\n"); + return ret; + } + } + + /* + * Try to guess if the STM32G0 is running a UCSI firmware. First probe the UCSI FW at its + * i2c address. Fallback to bootloader i2c address only if firmware-name is specified. + */ + ret = ucsi_stm32g0_read(ucsi, UCSI_VERSION, &ucsi_version, sizeof(ucsi_version)); + if (!ret || !g0->fw_name) + return ret; + + /* Speculatively read the bootloader version that has a known length. */ + ret = ucsi_stm32g0_bl_get_version(ucsi, &g0->bl_version); + if (ret < 0) { + i2c_unregister_device(g0->i2c_bl); + return ret; + } + + /* Device in bootloader mode */ + g0->in_bootloader = true; + dev_info(g0->dev, "Bootloader Version 0x%02x\n", g0->bl_version); + + return 0; +} + +static int ucsi_stm32g0_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ucsi_stm32g0 *g0; + int ret; + + g0 = devm_kzalloc(dev, sizeof(*g0), GFP_KERNEL); + if (!g0) + return -ENOMEM; + + g0->dev = dev; + g0->client = client; + i2c_set_clientdata(client, g0); + + g0->ucsi = ucsi_create(dev, &ucsi_stm32g0_ops); + if (IS_ERR(g0->ucsi)) + return PTR_ERR(g0->ucsi); + + ucsi_set_drvdata(g0->ucsi, g0); + + ret = ucsi_stm32g0_probe_bootloader(g0->ucsi); + if (ret < 0) + goto destroy; + + /* + * Don't register in bootloader mode: wait for the firmware to be loaded and started before + * registering UCSI device. + */ + if (!g0->in_bootloader) { + ret = ucsi_stm32g0_register(g0->ucsi); + if (ret < 0) + goto freei2c; + } + + if (g0->fw_name) { + /* + * Asynchronously flash (e.g. bootloader mode) or update the running firmware, + * not to hang the boot process + */ + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, g0->fw_name, g0->dev, + GFP_KERNEL, g0->ucsi, ucsi_stm32g0_fw_cb); + if (ret < 0) { + dev_err_probe(dev, ret, "firmware request failed\n"); + goto unregister; + } + } + + return 0; + +unregister: + if (!g0->in_bootloader) + ucsi_stm32g0_unregister(g0->ucsi); +freei2c: + if (g0->fw_name) + i2c_unregister_device(g0->i2c_bl); +destroy: + ucsi_destroy(g0->ucsi); + + return ret; +} + +static void ucsi_stm32g0_remove(struct i2c_client *client) +{ + struct ucsi_stm32g0 *g0 = i2c_get_clientdata(client); + + if (!g0->in_bootloader) + ucsi_stm32g0_unregister(g0->ucsi); + if (g0->fw_name) + i2c_unregister_device(g0->i2c_bl); + ucsi_destroy(g0->ucsi); +} + +static int ucsi_stm32g0_suspend(struct device *dev) +{ + struct ucsi_stm32g0 *g0 = dev_get_drvdata(dev); + struct i2c_client *client = g0->client; + + if (g0->in_bootloader) + return 0; + + /* Keep the interrupt disabled until the i2c bus has been resumed */ + disable_irq(client->irq); + + g0->suspended = true; + g0->wakeup_event = false; + + if (device_may_wakeup(dev) || device_wakeup_path(dev)) + enable_irq_wake(client->irq); + + return 0; +} + +static int ucsi_stm32g0_resume(struct device *dev) +{ + struct ucsi_stm32g0 *g0 = dev_get_drvdata(dev); + struct i2c_client *client = g0->client; + + if (g0->in_bootloader) + return 0; + + if (device_may_wakeup(dev) || device_wakeup_path(dev)) + disable_irq_wake(client->irq); + + enable_irq(client->irq); + + /* Enforce any pending handler gets called to signal a wakeup_event */ + synchronize_irq(client->irq); + + if (g0->wakeup_event) + pm_wakeup_event(g0->dev, 0); + + g0->suspended = false; + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ucsi_stm32g0_pm_ops, ucsi_stm32g0_suspend, ucsi_stm32g0_resume); + +static const struct of_device_id __maybe_unused ucsi_stm32g0_typec_of_match[] = { + { .compatible = "st,stm32g0-typec" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ucsi_stm32g0_typec_of_match); + +static const struct i2c_device_id ucsi_stm32g0_typec_i2c_devid[] = { + { .name = "stm32g0-typec" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ucsi_stm32g0_typec_i2c_devid); + +static struct i2c_driver ucsi_stm32g0_i2c_driver = { + .driver = { + .name = "ucsi-stm32g0-i2c", + .of_match_table = of_match_ptr(ucsi_stm32g0_typec_of_match), + .pm = pm_sleep_ptr(&ucsi_stm32g0_pm_ops), + }, + .probe = ucsi_stm32g0_probe, + .remove = ucsi_stm32g0_remove, + .id_table = ucsi_stm32g0_typec_i2c_devid +}; +module_i2c_driver(ucsi_stm32g0_i2c_driver); + +MODULE_AUTHOR("Fabrice Gasnier "); +MODULE_DESCRIPTION("STMicroelectronics STM32G0 Type-C controller"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:ucsi-stm32g0"); diff --git a/drivers/usb/typec/ucsi/ucsi_yoga_c630.c b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c new file mode 100644 index 000000000..bb7b09bc7 --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022-2024, Linaro Ltd + * Authors: + * Bjorn Andersson + * Dmitry Baryshkov + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ucsi.h" + +#define LENOVO_EC_USB_MUX 0x08 + +#define USB_MUX_MUXC GENMASK(1, 0) +#define USB_MUX_CCST GENMASK(3, 2) +#define USB_MUX_DPPN GENMASK(7, 4) +#define USB_MUX_HPDS BIT(8) +#define USB_MUX_HSFL GENMASK(11, 9) + +struct yoga_c630_ucsi { + struct yoga_c630_ec *ec; + struct ucsi *ucsi; + struct auxiliary_device *bridge; + struct notifier_block nb; + u16 version; +}; + +static int yoga_c630_ucsi_read_version(struct ucsi *ucsi, u16 *version) +{ + struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi); + + *version = uec->version; + + return 0; +} + +static int yoga_c630_ucsi_read_cci(struct ucsi *ucsi, u32 *cci) +{ + struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi); + u8 buf[YOGA_C630_UCSI_READ_SIZE]; + int ret; + + ret = yoga_c630_ec_ucsi_read(uec->ec, buf); + if (ret) + return ret; + + memcpy(cci, buf, sizeof(*cci)); + + return 0; +} + +static int yoga_c630_ucsi_read_message_in(struct ucsi *ucsi, + void *val, size_t val_len) +{ + struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi); + u8 buf[YOGA_C630_UCSI_READ_SIZE]; + int ret; + + ret = yoga_c630_ec_ucsi_read(uec->ec, buf); + if (ret) + return ret; + + memcpy(val, buf + YOGA_C630_UCSI_CCI_SIZE, + min(val_len, YOGA_C630_UCSI_DATA_SIZE)); + + return 0; +} + +static int yoga_c630_ucsi_async_control(struct ucsi *ucsi, u64 command) +{ + struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi); + + return yoga_c630_ec_ucsi_write(uec->ec, (u8*)&command); +} + +static int yoga_c630_ucsi_sync_control(struct ucsi *ucsi, + u64 command, + u32 *cci, + void *data, size_t size, + void *msg_out, size_t msg_out_size) +{ + int ret; + + /* + * EC doesn't return connector's DP mode even though it is supported. + * Fake it. + */ + if (UCSI_COMMAND(command) == UCSI_GET_ALTERNATE_MODES && + UCSI_GET_ALTMODE_GET_CONNECTOR_NUMBER(command) == 1 && + UCSI_ALTMODE_RECIPIENT(command) == UCSI_RECIPIENT_CON && + UCSI_ALTMODE_OFFSET(command) == 0) { + static const struct ucsi_altmode alt = { + .svid = USB_TYPEC_DP_SID, + .mid = USB_TYPEC_DP_MODE, + }; + + dev_dbg(ucsi->dev, "faking DP altmode for con1\n"); + memset(data, 0, size); + memcpy(data, &alt, min(sizeof(alt), size)); + *cci = UCSI_CCI_COMMAND_COMPLETE | UCSI_SET_CCI_LENGTH(sizeof(alt)); + return 0; + } + + /* + * EC can return AltModes present on CON1 (port0, right) for CON2 + * (port1, left) too. Ignore all requests going to CON2 (it doesn't + * support DP anyway). + */ + if (UCSI_COMMAND(command) == UCSI_GET_ALTERNATE_MODES && + UCSI_GET_ALTMODE_GET_CONNECTOR_NUMBER(command) == 2) { + dev_dbg(ucsi->dev, "ignoring altmodes for con2\n"); + memset(data, 0, size); + *cci = UCSI_CCI_COMMAND_COMPLETE; + return 0; + } + + ret = ucsi_sync_control_common(ucsi, command, cci, + data, size, msg_out, msg_out_size); + if (ret < 0) + return ret; + + /* UCSI_GET_CURRENT_CAM is off-by-one on all ports */ + if (UCSI_COMMAND(command) == UCSI_GET_CURRENT_CAM && data) + ((u8 *)data)[0]--; + + return ret; +} + +static void yoga_c630_ucsi_update_connector(struct ucsi_connector *con) +{ + if (con->num == 1) + con->typec_cap.orientation_aware = true; +} + +static const struct ucsi_operations yoga_c630_ucsi_ops = { + .read_version = yoga_c630_ucsi_read_version, + .read_cci = yoga_c630_ucsi_read_cci, + .poll_cci = yoga_c630_ucsi_read_cci, + .read_message_in = yoga_c630_ucsi_read_message_in, + .sync_control = yoga_c630_ucsi_sync_control, + .async_control = yoga_c630_ucsi_async_control, + .update_connector = yoga_c630_ucsi_update_connector, +}; + +static void yoga_c630_ucsi_read_port0_status(struct yoga_c630_ucsi *uec) +{ + int val; + unsigned int muxc, ccst, dppn, hpds, hsfl; + + val = yoga_c630_ec_read16(uec->ec, LENOVO_EC_USB_MUX); + + muxc = FIELD_GET(USB_MUX_MUXC, val); + ccst = FIELD_GET(USB_MUX_CCST, val); + dppn = FIELD_GET(USB_MUX_DPPN, val); + hpds = FIELD_GET(USB_MUX_HPDS, val); + hsfl = FIELD_GET(USB_MUX_HSFL, val); + + dev_dbg(uec->ucsi->dev, " mux %04x (muxc %d ccst %d dppn %d hpds %d hsfl %d)\n", + val, + muxc, ccst, dppn, hpds, hsfl); + + if (uec->ucsi->connector && uec->ucsi->connector[0].port) + typec_set_orientation(uec->ucsi->connector[0].port, + ccst == 1 ? + TYPEC_ORIENTATION_REVERSE : + TYPEC_ORIENTATION_NORMAL); + + if (uec->bridge) + drm_aux_hpd_bridge_notify(&uec->bridge->dev, + dppn != 0 ? + connector_status_connected : + connector_status_disconnected); + +} + +static int yoga_c630_ucsi_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct yoga_c630_ucsi *uec = container_of(nb, struct yoga_c630_ucsi, nb); + u32 cci; + int ret; + + switch (action) { + case LENOVO_EC_EVENT_USB: + case LENOVO_EC_EVENT_HPD: + yoga_c630_ucsi_read_port0_status(uec); + ucsi_connector_change(uec->ucsi, 1); + return NOTIFY_OK; + + case LENOVO_EC_EVENT_UCSI: + ret = uec->ucsi->ops->read_cci(uec->ucsi, &cci); + if (ret) + return NOTIFY_DONE; + + ucsi_notify_common(uec->ucsi, cci); + + return NOTIFY_OK; + + default: + return NOTIFY_DONE; + } +} + +static int yoga_c630_ucsi_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct yoga_c630_ec *ec = adev->dev.platform_data; + struct yoga_c630_ucsi *uec; + int ret; + + uec = devm_kzalloc(&adev->dev, sizeof(*uec), GFP_KERNEL); + if (!uec) + return -ENOMEM; + + uec->ec = ec; + uec->nb.notifier_call = yoga_c630_ucsi_notify; + + device_for_each_child_node_scoped(&adev->dev, fwnode) { + u32 port; + + ret = fwnode_property_read_u32(fwnode, "reg", &port); + if (ret < 0) { + dev_err(&adev->dev, "missing reg property of %pfwP\n", fwnode); + return ret; + } + + /* DP is only on port0 */ + if (port != 0) + continue; + + uec->bridge = devm_drm_dp_hpd_bridge_alloc(&adev->dev, to_of_node(fwnode)); + if (IS_ERR(uec->bridge)) + return PTR_ERR(uec->bridge); + } + + uec->ucsi = ucsi_create(&adev->dev, &yoga_c630_ucsi_ops); + if (IS_ERR(uec->ucsi)) + return PTR_ERR(uec->ucsi); + + ucsi_set_drvdata(uec->ucsi, uec); + + uec->version = yoga_c630_ec_ucsi_get_version(uec->ec); + + auxiliary_set_drvdata(adev, uec); + + ret = yoga_c630_ec_register_notify(ec, &uec->nb); + if (ret) + goto err_destroy; + + ret = ucsi_register(uec->ucsi); + if (ret) + goto err_unregister; + + if (uec->bridge) { + ret = devm_drm_dp_hpd_bridge_add(&adev->dev, uec->bridge); + if (ret) + goto err_ucsi_unregister; + } + + return 0; + +err_ucsi_unregister: + ucsi_unregister(uec->ucsi); + +err_unregister: + yoga_c630_ec_unregister_notify(uec->ec, &uec->nb); + +err_destroy: + ucsi_destroy(uec->ucsi); + + return ret; +} + +static void yoga_c630_ucsi_remove(struct auxiliary_device *adev) +{ + struct yoga_c630_ucsi *uec = auxiliary_get_drvdata(adev); + + ucsi_unregister(uec->ucsi); + yoga_c630_ec_unregister_notify(uec->ec, &uec->nb); + ucsi_destroy(uec->ucsi); +} + +static const struct auxiliary_device_id yoga_c630_ucsi_id_table[] = { + { .name = YOGA_C630_MOD_NAME "." YOGA_C630_DEV_UCSI, }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, yoga_c630_ucsi_id_table); + +static struct auxiliary_driver yoga_c630_ucsi_driver = { + .name = YOGA_C630_DEV_UCSI, + .id_table = yoga_c630_ucsi_id_table, + .probe = yoga_c630_ucsi_probe, + .remove = yoga_c630_ucsi_remove, +}; + +module_auxiliary_driver(yoga_c630_ucsi_driver); + +MODULE_DESCRIPTION("Lenovo Yoga C630 UCSI"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/wusb3801.c b/drivers/usb/typec/wusb3801.c new file mode 100644 index 000000000..6062875fb --- /dev/null +++ b/drivers/usb/typec/wusb3801.c @@ -0,0 +1,435 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Willsemi WUSB3801 Type-C port controller driver + * + * Copyright (C) 2022 Samuel Holland + */ + +#include +#include +#include +#include +#include + +#define WUSB3801_REG_DEVICE_ID 0x01 +#define WUSB3801_REG_CTRL0 0x02 +#define WUSB3801_REG_INT 0x03 +#define WUSB3801_REG_STAT 0x04 +#define WUSB3801_REG_CTRL1 0x05 +#define WUSB3801_REG_TEST00 0x06 +#define WUSB3801_REG_TEST01 0x07 +#define WUSB3801_REG_TEST02 0x08 +#define WUSB3801_REG_TEST03 0x09 +#define WUSB3801_REG_TEST04 0x0a +#define WUSB3801_REG_TEST05 0x0b +#define WUSB3801_REG_TEST06 0x0c +#define WUSB3801_REG_TEST07 0x0d +#define WUSB3801_REG_TEST08 0x0e +#define WUSB3801_REG_TEST09 0x0f +#define WUSB3801_REG_TEST0A 0x10 +#define WUSB3801_REG_TEST0B 0x11 +#define WUSB3801_REG_TEST0C 0x12 +#define WUSB3801_REG_TEST0D 0x13 +#define WUSB3801_REG_TEST0E 0x14 +#define WUSB3801_REG_TEST0F 0x15 +#define WUSB3801_REG_TEST10 0x16 +#define WUSB3801_REG_TEST11 0x17 +#define WUSB3801_REG_TEST12 0x18 + +#define WUSB3801_DEVICE_ID_VERSION_ID GENMASK(7, 3) +#define WUSB3801_DEVICE_ID_VENDOR_ID GENMASK(2, 0) + +#define WUSB3801_CTRL0_DIS_ACC_SUPPORT BIT(7) +#define WUSB3801_CTRL0_TRY GENMASK(6, 5) +#define WUSB3801_CTRL0_TRY_NONE (0x0 << 5) +#define WUSB3801_CTRL0_TRY_SNK (0x1 << 5) +#define WUSB3801_CTRL0_TRY_SRC (0x2 << 5) +#define WUSB3801_CTRL0_CURRENT GENMASK(4, 3) /* SRC */ +#define WUSB3801_CTRL0_CURRENT_DEFAULT (0x0 << 3) +#define WUSB3801_CTRL0_CURRENT_1_5A (0x1 << 3) +#define WUSB3801_CTRL0_CURRENT_3_0A (0x2 << 3) +#define WUSB3801_CTRL0_ROLE GENMASK(2, 1) +#define WUSB3801_CTRL0_ROLE_SNK (0x0 << 1) +#define WUSB3801_CTRL0_ROLE_SRC (0x1 << 1) +#define WUSB3801_CTRL0_ROLE_DRP (0x2 << 1) +#define WUSB3801_CTRL0_INT_MASK BIT(0) + +#define WUSB3801_INT_ATTACHED BIT(0) +#define WUSB3801_INT_DETACHED BIT(1) + +#define WUSB3801_STAT_VBUS_DETECTED BIT(7) +#define WUSB3801_STAT_CURRENT GENMASK(6, 5) /* SNK */ +#define WUSB3801_STAT_CURRENT_STANDBY (0x0 << 5) +#define WUSB3801_STAT_CURRENT_DEFAULT (0x1 << 5) +#define WUSB3801_STAT_CURRENT_1_5A (0x2 << 5) +#define WUSB3801_STAT_CURRENT_3_0A (0x3 << 5) +#define WUSB3801_STAT_PARTNER GENMASK(4, 2) +#define WUSB3801_STAT_PARTNER_STANDBY (0x0 << 2) +#define WUSB3801_STAT_PARTNER_SNK (0x1 << 2) +#define WUSB3801_STAT_PARTNER_SRC (0x2 << 2) +#define WUSB3801_STAT_PARTNER_AUDIO (0x3 << 2) +#define WUSB3801_STAT_PARTNER_DEBUG (0x4 << 2) +#define WUSB3801_STAT_ORIENTATION GENMASK(1, 0) +#define WUSB3801_STAT_ORIENTATION_NONE (0x0 << 0) +#define WUSB3801_STAT_ORIENTATION_CC1 (0x1 << 0) +#define WUSB3801_STAT_ORIENTATION_CC2 (0x2 << 0) +#define WUSB3801_STAT_ORIENTATION_BOTH (0x3 << 0) + +#define WUSB3801_CTRL1_SM_RESET BIT(0) + +#define WUSB3801_TEST01_VENDOR_SUB_ID (BIT(8) | BIT(6)) + +#define WUSB3801_TEST02_FORCE_ERR_RCY BIT(8) + +#define WUSB3801_TEST0A_WAIT_VBUS BIT(5) + +struct wusb3801 { + struct typec_capability cap; + struct device *dev; + struct typec_partner *partner; + struct typec_port *port; + struct regmap *regmap; + struct regulator *vbus_supply; + unsigned int partner_type; + enum typec_port_type port_type; + enum typec_pwr_opmode pwr_opmode; + bool vbus_on; +}; + +static enum typec_role wusb3801_get_default_role(struct wusb3801 *wusb3801) +{ + switch (wusb3801->port_type) { + case TYPEC_PORT_SRC: + return TYPEC_SOURCE; + case TYPEC_PORT_SNK: + return TYPEC_SINK; + case TYPEC_PORT_DRP: + default: + if (wusb3801->cap.prefer_role == TYPEC_SOURCE) + return TYPEC_SOURCE; + return TYPEC_SINK; + } +} + +static int wusb3801_map_port_type(enum typec_port_type type) +{ + switch (type) { + case TYPEC_PORT_SRC: + return WUSB3801_CTRL0_ROLE_SRC; + case TYPEC_PORT_SNK: + return WUSB3801_CTRL0_ROLE_SNK; + case TYPEC_PORT_DRP: + default: + return WUSB3801_CTRL0_ROLE_DRP; + } +} + +static int wusb3801_map_pwr_opmode(enum typec_pwr_opmode mode) +{ + switch (mode) { + case TYPEC_PWR_MODE_USB: + default: + return WUSB3801_CTRL0_CURRENT_DEFAULT; + case TYPEC_PWR_MODE_1_5A: + return WUSB3801_CTRL0_CURRENT_1_5A; + case TYPEC_PWR_MODE_3_0A: + return WUSB3801_CTRL0_CURRENT_3_0A; + } +} + +static unsigned int wusb3801_map_try_role(int role) +{ + switch (role) { + case TYPEC_NO_PREFERRED_ROLE: + default: + return WUSB3801_CTRL0_TRY_NONE; + case TYPEC_SINK: + return WUSB3801_CTRL0_TRY_SNK; + case TYPEC_SOURCE: + return WUSB3801_CTRL0_TRY_SRC; + } +} + +static enum typec_orientation wusb3801_unmap_orientation(unsigned int status) +{ + switch (status & WUSB3801_STAT_ORIENTATION) { + case WUSB3801_STAT_ORIENTATION_NONE: + case WUSB3801_STAT_ORIENTATION_BOTH: + default: + return TYPEC_ORIENTATION_NONE; + case WUSB3801_STAT_ORIENTATION_CC1: + return TYPEC_ORIENTATION_NORMAL; + case WUSB3801_STAT_ORIENTATION_CC2: + return TYPEC_ORIENTATION_REVERSE; + } +} + +static enum typec_pwr_opmode wusb3801_unmap_pwr_opmode(unsigned int status) +{ + switch (status & WUSB3801_STAT_CURRENT) { + case WUSB3801_STAT_CURRENT_STANDBY: + case WUSB3801_STAT_CURRENT_DEFAULT: + default: + return TYPEC_PWR_MODE_USB; + case WUSB3801_STAT_CURRENT_1_5A: + return TYPEC_PWR_MODE_1_5A; + case WUSB3801_STAT_CURRENT_3_0A: + return TYPEC_PWR_MODE_3_0A; + } +} + +static int wusb3801_try_role(struct typec_port *port, int role) +{ + struct wusb3801 *wusb3801 = typec_get_drvdata(port); + + return regmap_update_bits(wusb3801->regmap, WUSB3801_REG_CTRL0, + WUSB3801_CTRL0_TRY, + wusb3801_map_try_role(role)); +} + +static int wusb3801_port_type_set(struct typec_port *port, + enum typec_port_type type) +{ + struct wusb3801 *wusb3801 = typec_get_drvdata(port); + int ret; + + ret = regmap_update_bits(wusb3801->regmap, WUSB3801_REG_CTRL0, + WUSB3801_CTRL0_ROLE, + wusb3801_map_port_type(type)); + if (ret) + return ret; + + wusb3801->port_type = type; + + return 0; +} + +static const struct typec_operations wusb3801_typec_ops = { + .try_role = wusb3801_try_role, + .port_type_set = wusb3801_port_type_set, +}; + +static int wusb3801_hw_init(struct wusb3801 *wusb3801) +{ + return regmap_write(wusb3801->regmap, WUSB3801_REG_CTRL0, + wusb3801_map_try_role(wusb3801->cap.prefer_role) | + wusb3801_map_pwr_opmode(wusb3801->pwr_opmode) | + wusb3801_map_port_type(wusb3801->port_type)); +} + +static void wusb3801_hw_update(struct wusb3801 *wusb3801) +{ + struct typec_port *port = wusb3801->port; + struct device *dev = wusb3801->dev; + unsigned int partner_type, status; + int ret; + + ret = regmap_read(wusb3801->regmap, WUSB3801_REG_STAT, &status); + if (ret) { + dev_warn(dev, "Failed to read port status: %d\n", ret); + status = 0; + } + dev_dbg(dev, "status = 0x%02x\n", status); + + partner_type = status & WUSB3801_STAT_PARTNER; + + if (partner_type == WUSB3801_STAT_PARTNER_SNK) { + if (!wusb3801->vbus_on) { + ret = regulator_enable(wusb3801->vbus_supply); + if (ret) + dev_warn(dev, "Failed to enable VBUS: %d\n", ret); + wusb3801->vbus_on = true; + } + } else { + if (wusb3801->vbus_on) { + regulator_disable(wusb3801->vbus_supply); + wusb3801->vbus_on = false; + } + } + + if (partner_type != wusb3801->partner_type) { + struct typec_partner_desc desc = {}; + enum typec_data_role data_role; + enum typec_role pwr_role = wusb3801_get_default_role(wusb3801); + + switch (partner_type) { + case WUSB3801_STAT_PARTNER_STANDBY: + break; + case WUSB3801_STAT_PARTNER_SNK: + pwr_role = TYPEC_SOURCE; + break; + case WUSB3801_STAT_PARTNER_SRC: + pwr_role = TYPEC_SINK; + break; + case WUSB3801_STAT_PARTNER_AUDIO: + desc.accessory = TYPEC_ACCESSORY_AUDIO; + break; + case WUSB3801_STAT_PARTNER_DEBUG: + desc.accessory = TYPEC_ACCESSORY_DEBUG; + break; + } + + if (wusb3801->partner) { + typec_unregister_partner(wusb3801->partner); + wusb3801->partner = NULL; + } + + if (partner_type != WUSB3801_STAT_PARTNER_STANDBY) { + wusb3801->partner = typec_register_partner(port, &desc); + if (IS_ERR(wusb3801->partner)) + dev_err(dev, "Failed to register partner: %ld\n", + PTR_ERR(wusb3801->partner)); + } + + data_role = pwr_role == TYPEC_SOURCE ? TYPEC_HOST : TYPEC_DEVICE; + typec_set_data_role(port, data_role); + typec_set_pwr_role(port, pwr_role); + typec_set_vconn_role(port, pwr_role); + } + + typec_set_pwr_opmode(wusb3801->port, + partner_type == WUSB3801_STAT_PARTNER_SRC + ? wusb3801_unmap_pwr_opmode(status) + : wusb3801->pwr_opmode); + typec_set_orientation(wusb3801->port, + wusb3801_unmap_orientation(status)); + + wusb3801->partner_type = partner_type; +} + +static irqreturn_t wusb3801_irq(int irq, void *data) +{ + struct wusb3801 *wusb3801 = data; + unsigned int dummy; + + /* + * The interrupt register must be read in order to clear the IRQ, + * but all of the useful information is in the status register. + */ + regmap_read(wusb3801->regmap, WUSB3801_REG_INT, &dummy); + + wusb3801_hw_update(wusb3801); + + return IRQ_HANDLED; +} + +static const struct regmap_config config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = WUSB3801_REG_TEST12, +}; + +static int wusb3801_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct fwnode_handle *connector; + struct wusb3801 *wusb3801; + const char *cap_str; + int ret; + + wusb3801 = devm_kzalloc(dev, sizeof(*wusb3801), GFP_KERNEL); + if (!wusb3801) + return -ENOMEM; + + i2c_set_clientdata(client, wusb3801); + + wusb3801->dev = dev; + + wusb3801->regmap = devm_regmap_init_i2c(client, &config); + if (IS_ERR(wusb3801->regmap)) + return PTR_ERR(wusb3801->regmap); + + wusb3801->vbus_supply = devm_regulator_get(dev, "vbus"); + if (IS_ERR(wusb3801->vbus_supply)) + return PTR_ERR(wusb3801->vbus_supply); + + connector = device_get_named_child_node(dev, "connector"); + if (!connector) + return -ENODEV; + + ret = typec_get_fw_cap(&wusb3801->cap, connector); + if (ret) + goto err_put_connector; + wusb3801->port_type = wusb3801->cap.type; + + ret = fwnode_property_read_string(connector, "typec-power-opmode", &cap_str); + if (ret) + goto err_put_connector; + + ret = typec_find_pwr_opmode(cap_str); + if (ret < 0 || ret == TYPEC_PWR_MODE_PD) + goto err_put_connector; + wusb3801->pwr_opmode = ret; + + /* Initialize the hardware with the devicetree settings. */ + ret = wusb3801_hw_init(wusb3801); + if (ret) + goto err_put_connector; + + wusb3801->cap.revision = USB_TYPEC_REV_1_2; + wusb3801->cap.accessory[0] = TYPEC_ACCESSORY_AUDIO; + wusb3801->cap.accessory[1] = TYPEC_ACCESSORY_DEBUG; + wusb3801->cap.orientation_aware = true; + wusb3801->cap.driver_data = wusb3801; + wusb3801->cap.ops = &wusb3801_typec_ops; + + wusb3801->port = typec_register_port(dev, &wusb3801->cap); + if (IS_ERR(wusb3801->port)) { + ret = PTR_ERR(wusb3801->port); + goto err_put_connector; + } + + /* Initialize the port attributes from the hardware state. */ + wusb3801_hw_update(wusb3801); + + ret = request_threaded_irq(client->irq, NULL, wusb3801_irq, + IRQF_ONESHOT, dev_name(dev), wusb3801); + if (ret) + goto err_unregister_port; + + fwnode_handle_put(connector); + + return 0; + +err_unregister_port: + typec_unregister_port(wusb3801->port); +err_put_connector: + fwnode_handle_put(connector); + + return ret; +} + +static void wusb3801_remove(struct i2c_client *client) +{ + struct wusb3801 *wusb3801 = i2c_get_clientdata(client); + + free_irq(client->irq, wusb3801); + + if (wusb3801->partner) + typec_unregister_partner(wusb3801->partner); + typec_unregister_port(wusb3801->port); + + if (wusb3801->vbus_on) + regulator_disable(wusb3801->vbus_supply); +} + +static const struct of_device_id wusb3801_of_match[] = { + { .compatible = "willsemi,wusb3801" }, + {} +}; +MODULE_DEVICE_TABLE(of, wusb3801_of_match); + +static struct i2c_driver wusb3801_driver = { + .probe = wusb3801_probe, + .remove = wusb3801_remove, + .driver = { + .name = "wusb3801", + .of_match_table = wusb3801_of_match, + }, +}; + +module_i2c_driver(wusb3801_driver); + +MODULE_AUTHOR("Samuel Holland "); +MODULE_DESCRIPTION("Willsemi WUSB3801 Type-C port controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/usb-skeleton.c b/drivers/usb/usb-skeleton.c new file mode 100644 index 000000000..709a24092 --- /dev/null +++ b/drivers/usb/usb-skeleton.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Skeleton driver - 2.2 + * + * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) + * + * 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 + + +/* 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 */ + unsigned long disconnected:1; + 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_intf(dev->interface); + 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 */ + usb_autopm_put_interface(dev->interface); + + /* 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; + unsigned long flags; + + dev = urb->context; + + spin_lock_irqsave(&dev->err_lock, flags); + /* 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_irqrestore(&dev->err_lock, flags); + + 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 (!count) + return 0; + + /* no concurrent readers */ + rv = mutex_lock_interruptible(&dev->io_mutex); + if (rv < 0) + return rv; + + if (dev->disconnected) { /* 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 + goto retry; + } +exit: + mutex_unlock(&dev->io_mutex); + return rv; +} + +static void skel_write_bulk_callback(struct urb *urb) +{ + struct usb_skel *dev; + unsigned long flags; + + 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_irqsave(&dev->err_lock, flags); + dev->errors = urb->status; + spin_unlock_irqrestore(&dev->err_lock, flags); + } + + /* 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_t(size_t, count, 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->disconnected) { /* 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_endpoint_descriptor *bulk_in, *bulk_out; + int retval; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc_obj(*dev); + if (!dev) + return -ENOMEM; + + 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 = usb_get_intf(interface); + + /* set up the endpoint information */ + /* use only the first bulk-in and bulk-out endpoints */ + retval = usb_find_common_endpoints(interface->cur_altsetting, + &bulk_in, &bulk_out, NULL, NULL); + if (retval) { + dev_err(&interface->dev, + "Could not find both bulk-in and bulk-out endpoints\n"); + goto error; + } + + dev->bulk_in_size = usb_endpoint_maxp(bulk_in); + dev->bulk_in_endpointAddr = bulk_in->bEndpointAddress; + dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL); + if (!dev->bulk_in_buffer) { + retval = -ENOMEM; + goto error; + } + dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->bulk_in_urb) { + retval = -ENOMEM; + goto error; + } + + dev->bulk_out_endpointAddr = bulk_out->bEndpointAddress; + + /* 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: + /* 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); + + /* give back our minor */ + usb_deregister_dev(interface, &skel_class); + + /* prevent more I/O from starting */ + mutex_lock(&dev->io_mutex); + dev->disconnected = 1; + mutex_unlock(&dev->io_mutex); + + usb_kill_urb(dev->bulk_in_urb); + 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 v2"); diff --git a/drivers/usb/usbip/Kconfig b/drivers/usb/usbip/Kconfig new file mode 100644 index 000000000..50945b6fa --- /dev/null +++ b/drivers/usb/usbip/Kconfig @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: GPL-2.0 + +config USBIP_CORE + tristate "USB/IP support" + depends on NET + select USB_COMMON + select SGL_ALLOC + help + This enables pushing USB packets over IP to allow remote + machines direct access to USB devices. It provides the + USB/IP core that is required by both drivers. + + For more details, and to get the userspace utility + programs, please see . + + To compile this as a module, choose M here: the module will + be called usbip-core. + + If unsure, say N. + +config USBIP_VHCI_HCD + tristate "VHCI hcd" + depends on USBIP_CORE && USB + help + This enables the USB/IP virtual host controller driver, + which is run on the remote machine. + + To compile this driver as a module, choose M here: the + module will be called vhci-hcd. + +config USBIP_VHCI_HC_PORTS + int "Number of ports per USB/IP virtual host controller" + range 1 15 + default 8 + depends on USBIP_VHCI_HCD + help + To increase number of ports available for USB/IP virtual + host controller driver, this defines number of ports per + USB/IP virtual host controller. + +config USBIP_VHCI_NR_HCS + int "Number of USB/IP virtual host controllers" + range 1 32 + default 1 + depends on USBIP_VHCI_HCD + help + To increase number of ports available for USB/IP virtual + host controller driver, this defines number of USB/IP + virtual host controllers as if adding physical host + controllers. + +config USBIP_HOST + tristate "Host driver" + depends on USBIP_CORE && USB + help + This enables the USB/IP host driver, which is run on the + machine that is sharing the USB devices. + + To compile this driver as a module, choose M here: the + module will be called usbip-host. + +config USBIP_VUDC + tristate "VUDC driver" + depends on USBIP_CORE && USB_GADGET + help + This enables the USB/IP virtual USB device controller + driver, which is run on the host machine, allowing the + machine itself to act as a device. + + To compile this driver as a module, choose M here: the + module will be called usbip-vudc. + +config USBIP_DEBUG + bool "Debug messages for USB/IP" + depends on USBIP_CORE + help + This enables the debug messages from the USB/IP drivers. diff --git a/drivers/usb/usbip/Makefile b/drivers/usb/usbip/Makefile new file mode 100644 index 000000000..f4c8f3840 --- /dev/null +++ b/drivers/usb/usbip/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +ccflags-$(CONFIG_USBIP_DEBUG) := -DDEBUG + +obj-$(CONFIG_USBIP_CORE) += usbip-core.o +usbip-core-y := usbip_common.o usbip_event.o + +obj-$(CONFIG_USBIP_VHCI_HCD) += vhci-hcd.o +vhci-hcd-y := vhci_sysfs.o vhci_tx.o vhci_rx.o vhci_hcd.o + +obj-$(CONFIG_USBIP_HOST) += usbip-host.o +usbip-host-y := stub_dev.o stub_main.o stub_rx.o stub_tx.o + +obj-$(CONFIG_USBIP_VUDC) += usbip-vudc.o +usbip-vudc-y := vudc_dev.o vudc_sysfs.o vudc_tx.o vudc_rx.o vudc_transfer.o vudc_main.o diff --git a/drivers/usb/usbip/stub.h b/drivers/usb/usbip/stub.h new file mode 100644 index 000000000..d11270560 --- /dev/null +++ b/drivers/usb/usbip/stub.h @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + */ + +#ifndef __USBIP_STUB_H +#define __USBIP_STUB_H + +#include +#include +#include +#include +#include +#include + +#define STUB_BUSID_OTHER 0 +#define STUB_BUSID_REMOV 1 +#define STUB_BUSID_ADDED 2 +#define STUB_BUSID_ALLOC 3 + +struct stub_device { + struct usb_device *udev; + + struct usbip_device ud; + __u32 devid; + + /* + * stub_priv preserves private data of each urb. + * It is allocated as stub_priv_cache and assigned to urb->context. + * + * stub_priv is always linked to any one of 3 lists; + * priv_init: linked to this until the comletion of a urb. + * priv_tx : linked to this after the completion of a urb. + * priv_free: linked to this after the sending of the result. + * + * Any of these list operations should be locked by priv_lock. + */ + spinlock_t priv_lock; + struct list_head priv_init; + struct list_head priv_tx; + struct list_head priv_free; + + /* see comments for unlinking in stub_rx.c */ + struct list_head unlink_tx; + struct list_head unlink_free; + + wait_queue_head_t tx_waitq; +}; + +/* private data into urb->priv */ +struct stub_priv { + unsigned long seqnum; + struct list_head list; + struct stub_device *sdev; + struct urb **urbs; + struct scatterlist *sgl; + int num_urbs; + int completed_urbs; + int urb_status; + + int unlinking; +}; + +struct stub_unlink { + unsigned long seqnum; + struct list_head list; + __u32 status; +}; + +/* same as SYSFS_BUS_ID_SIZE */ +#define BUSID_SIZE 32 + +struct bus_id_priv { + char name[BUSID_SIZE]; + char status; + int interf_count; + struct stub_device *sdev; + struct usb_device *udev; + char shutdown_busid; + spinlock_t busid_lock; +}; + +/* stub_priv is allocated from stub_priv_cache */ +extern struct kmem_cache *stub_priv_cache; + +/* stub_dev.c */ +extern struct usb_device_driver stub_driver; + +/* stub_main.c */ +struct bus_id_priv *get_busid_priv(const char *busid); +void put_busid_priv(struct bus_id_priv *bid); +int del_match_busid(char *busid); +void stub_free_priv_and_urb(struct stub_priv *priv); +void stub_device_cleanup_urbs(struct stub_device *sdev); + +/* stub_rx.c */ +int stub_rx_loop(void *data); + +/* stub_tx.c */ +void stub_enqueue_ret_unlink(struct stub_device *sdev, __u32 seqnum, + __u32 status); +void stub_complete(struct urb *urb); +int stub_tx_loop(void *data); + +#endif /* __USBIP_STUB_H */ diff --git a/drivers/usb/usbip/stub_dev.c b/drivers/usb/usbip/stub_dev.c new file mode 100644 index 000000000..abfa11d6b --- /dev/null +++ b/drivers/usb/usbip/stub_dev.c @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + */ + +#include +#include +#include +#include + +#include "usbip_common.h" +#include "stub.h" + +/* + * usbip_status shows the status of usbip-host as long as this driver is bound + * to the target device. + */ +static ssize_t usbip_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct stub_device *sdev = dev_get_drvdata(dev); + int status; + + if (!sdev) { + dev_err(dev, "sdev is null\n"); + return -ENODEV; + } + + spin_lock_irq(&sdev->ud.lock); + status = sdev->ud.status; + spin_unlock_irq(&sdev->ud.lock); + + return sysfs_emit(buf, "%d\n", status); +} +static DEVICE_ATTR_RO(usbip_status); + +/* + * usbip_sockfd gets a socket descriptor of an established TCP connection that + * is used to transfer usbip requests by kernel threads. -1 is a magic number + * by which usbip connection is finished. + */ +static ssize_t usbip_sockfd_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct stub_device *sdev = dev_get_drvdata(dev); + int sockfd = 0; + struct socket *socket; + int rv; + struct task_struct *tcp_rx = NULL; + struct task_struct *tcp_tx = NULL; + + if (!sdev) { + dev_err(dev, "sdev is null\n"); + return -ENODEV; + } + + rv = sscanf(buf, "%d", &sockfd); + if (rv != 1) + return -EINVAL; + + if (sockfd != -1) { + int err; + + dev_info(dev, "stub up\n"); + + mutex_lock(&sdev->ud.sysfs_lock); + spin_lock_irq(&sdev->ud.lock); + + if (sdev->ud.status != SDEV_ST_AVAILABLE) { + dev_err(dev, "not ready\n"); + goto err; + } + + socket = sockfd_lookup(sockfd, &err); + if (!socket) { + dev_err(dev, "failed to lookup sock"); + goto err; + } + + if (socket->type != SOCK_STREAM) { + dev_err(dev, "Expecting SOCK_STREAM - found %d", + socket->type); + goto sock_err; + } + + /* unlock and create threads and get tasks */ + spin_unlock_irq(&sdev->ud.lock); + tcp_rx = kthread_create(stub_rx_loop, &sdev->ud, "stub_rx"); + if (IS_ERR(tcp_rx)) { + sockfd_put(socket); + goto unlock_mutex; + } + tcp_tx = kthread_create(stub_tx_loop, &sdev->ud, "stub_tx"); + if (IS_ERR(tcp_tx)) { + kthread_stop(tcp_rx); + sockfd_put(socket); + goto unlock_mutex; + } + + /* get task structs now */ + get_task_struct(tcp_rx); + get_task_struct(tcp_tx); + + /* lock and update sdev->ud state */ + spin_lock_irq(&sdev->ud.lock); + sdev->ud.tcp_socket = socket; + sdev->ud.sockfd = sockfd; + sdev->ud.tcp_rx = tcp_rx; + sdev->ud.tcp_tx = tcp_tx; + sdev->ud.status = SDEV_ST_USED; + spin_unlock_irq(&sdev->ud.lock); + + wake_up_process(sdev->ud.tcp_rx); + wake_up_process(sdev->ud.tcp_tx); + + mutex_unlock(&sdev->ud.sysfs_lock); + + } else { + dev_info(dev, "stub down\n"); + + mutex_lock(&sdev->ud.sysfs_lock); + + spin_lock_irq(&sdev->ud.lock); + if (sdev->ud.status != SDEV_ST_USED) + goto err; + + spin_unlock_irq(&sdev->ud.lock); + + usbip_event_add(&sdev->ud, SDEV_EVENT_DOWN); + mutex_unlock(&sdev->ud.sysfs_lock); + } + + return count; + +sock_err: + sockfd_put(socket); +err: + spin_unlock_irq(&sdev->ud.lock); +unlock_mutex: + mutex_unlock(&sdev->ud.sysfs_lock); + return -EINVAL; +} +static DEVICE_ATTR_WO(usbip_sockfd); + +static struct attribute *usbip_attrs[] = { + &dev_attr_usbip_status.attr, + &dev_attr_usbip_sockfd.attr, + &dev_attr_usbip_debug.attr, + NULL, +}; +ATTRIBUTE_GROUPS(usbip); + +static void stub_shutdown_connection(struct usbip_device *ud) +{ + struct stub_device *sdev = container_of(ud, struct stub_device, ud); + + /* + * When removing an exported device, kernel panic sometimes occurred + * and then EIP was sk_wait_data of stub_rx thread. Is this because + * sk_wait_data returned though stub_rx thread was already finished by + * step 1? + */ + if (ud->tcp_socket) { + dev_dbg(&sdev->udev->dev, "shutdown sockfd %d\n", ud->sockfd); + kernel_sock_shutdown(ud->tcp_socket, SHUT_RDWR); + } + + /* 1. stop threads */ + if (ud->tcp_rx) { + kthread_stop_put(ud->tcp_rx); + ud->tcp_rx = NULL; + } + if (ud->tcp_tx) { + kthread_stop_put(ud->tcp_tx); + ud->tcp_tx = NULL; + } + + /* + * 2. close the socket + * + * tcp_socket is freed after threads are killed so that usbip_xmit does + * not touch NULL socket. + */ + if (ud->tcp_socket) { + sockfd_put(ud->tcp_socket); + ud->tcp_socket = NULL; + ud->sockfd = -1; + } + + /* 3. free used data */ + stub_device_cleanup_urbs(sdev); + + /* 4. free stub_unlink */ + { + unsigned long flags; + struct stub_unlink *unlink, *tmp; + + spin_lock_irqsave(&sdev->priv_lock, flags); + list_for_each_entry_safe(unlink, tmp, &sdev->unlink_tx, list) { + list_del(&unlink->list); + kfree(unlink); + } + list_for_each_entry_safe(unlink, tmp, &sdev->unlink_free, + list) { + list_del(&unlink->list); + kfree(unlink); + } + spin_unlock_irqrestore(&sdev->priv_lock, flags); + } +} + +static void stub_device_reset(struct usbip_device *ud) +{ + struct stub_device *sdev = container_of(ud, struct stub_device, ud); + struct usb_device *udev = sdev->udev; + int ret; + + dev_dbg(&udev->dev, "device reset"); + + ret = usb_lock_device_for_reset(udev, NULL); + if (ret < 0) { + dev_err(&udev->dev, "lock for reset\n"); + spin_lock_irq(&ud->lock); + ud->status = SDEV_ST_ERROR; + spin_unlock_irq(&ud->lock); + return; + } + + /* try to reset the device */ + ret = usb_reset_device(udev); + usb_unlock_device(udev); + + spin_lock_irq(&ud->lock); + if (ret) { + dev_err(&udev->dev, "device reset\n"); + ud->status = SDEV_ST_ERROR; + } else { + dev_info(&udev->dev, "device reset\n"); + ud->status = SDEV_ST_AVAILABLE; + } + spin_unlock_irq(&ud->lock); +} + +static void stub_device_unusable(struct usbip_device *ud) +{ + spin_lock_irq(&ud->lock); + ud->status = SDEV_ST_ERROR; + spin_unlock_irq(&ud->lock); +} + +/** + * stub_device_alloc - allocate a new stub_device struct + * @udev: usb_device of a new device + * + * Allocates and initializes a new stub_device struct. + */ +static struct stub_device *stub_device_alloc(struct usb_device *udev) +{ + struct stub_device *sdev; + int busnum = udev->bus->busnum; + int devnum = udev->devnum; + + dev_dbg(&udev->dev, "allocating stub device"); + + /* yes, it's a new device */ + sdev = kzalloc_obj(struct stub_device); + if (!sdev) + return NULL; + + sdev->udev = udev; + + /* + * devid is defined with devnum when this driver is first allocated. + * devnum may change later if a device is reset. However, devid never + * changes during a usbip connection. + */ + sdev->devid = (busnum << 16) | devnum; + sdev->ud.side = USBIP_STUB; + sdev->ud.status = SDEV_ST_AVAILABLE; + spin_lock_init(&sdev->ud.lock); + mutex_init(&sdev->ud.sysfs_lock); + sdev->ud.tcp_socket = NULL; + sdev->ud.sockfd = -1; + + INIT_LIST_HEAD(&sdev->priv_init); + INIT_LIST_HEAD(&sdev->priv_tx); + INIT_LIST_HEAD(&sdev->priv_free); + INIT_LIST_HEAD(&sdev->unlink_free); + INIT_LIST_HEAD(&sdev->unlink_tx); + spin_lock_init(&sdev->priv_lock); + + init_waitqueue_head(&sdev->tx_waitq); + + sdev->ud.eh_ops.shutdown = stub_shutdown_connection; + sdev->ud.eh_ops.reset = stub_device_reset; + sdev->ud.eh_ops.unusable = stub_device_unusable; + + usbip_start_eh(&sdev->ud); + + dev_dbg(&udev->dev, "register new device\n"); + + return sdev; +} + +static void stub_device_free(struct stub_device *sdev) +{ + kfree(sdev); +} + +static int stub_probe(struct usb_device *udev) +{ + struct stub_device *sdev = NULL; + const char *udev_busid = dev_name(&udev->dev); + struct bus_id_priv *busid_priv; + int rc = 0; + char save_status; + + dev_dbg(&udev->dev, "Enter probe\n"); + + /* Not sure if this is our device. Allocate here to avoid + * calling alloc while holding busid_table lock. + */ + sdev = stub_device_alloc(udev); + if (!sdev) + return -ENOMEM; + + /* check we should claim or not by busid_table */ + busid_priv = get_busid_priv(udev_busid); + if (!busid_priv || (busid_priv->status == STUB_BUSID_REMOV) || + (busid_priv->status == STUB_BUSID_OTHER)) { + dev_info(&udev->dev, + "%s is not in match_busid table... skip!\n", + udev_busid); + + /* + * Return value should be ENODEV or ENOXIO to continue trying + * other matched drivers by the driver core. + * See driver_probe_device() in driver/base/dd.c + */ + rc = -ENODEV; + if (!busid_priv) + goto sdev_free; + + goto call_put_busid_priv; + } + + if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) { + dev_dbg(&udev->dev, "%s is a usb hub device... skip!\n", + udev_busid); + rc = -ENODEV; + goto call_put_busid_priv; + } + + if (!strcmp(udev->bus->bus_name, "vhci_hcd")) { + dev_dbg(&udev->dev, + "%s is attached on vhci_hcd... skip!\n", + udev_busid); + + rc = -ENODEV; + goto call_put_busid_priv; + } + + + dev_info(&udev->dev, + "usbip-host: register new device (bus %u dev %u)\n", + udev->bus->busnum, udev->devnum); + + busid_priv->shutdown_busid = 0; + + /* set private data to usb_device */ + dev_set_drvdata(&udev->dev, sdev); + + busid_priv->sdev = sdev; + busid_priv->udev = udev; + + save_status = busid_priv->status; + busid_priv->status = STUB_BUSID_ALLOC; + + /* release the busid_lock */ + put_busid_priv(busid_priv); + + /* + * Claim this hub port. + * It doesn't matter what value we pass as owner + * (struct dev_state) as long as it is unique. + */ + rc = usb_hub_claim_port(udev->parent, udev->portnum, + (struct usb_dev_state *) udev); + if (rc) { + dev_dbg(&udev->dev, "unable to claim port\n"); + goto err_port; + } + + return 0; + +err_port: + dev_set_drvdata(&udev->dev, NULL); + + /* we already have busid_priv, just lock busid_lock */ + spin_lock(&busid_priv->busid_lock); + busid_priv->sdev = NULL; + busid_priv->status = save_status; + spin_unlock(&busid_priv->busid_lock); + /* lock is released - go to free */ + goto sdev_free; + +call_put_busid_priv: + /* release the busid_lock */ + put_busid_priv(busid_priv); + +sdev_free: + stub_device_free(sdev); + + return rc; +} + +static void shutdown_busid(struct bus_id_priv *busid_priv) +{ + usbip_event_add(&busid_priv->sdev->ud, SDEV_EVENT_REMOVED); + + /* wait for the stop of the event handler */ + usbip_stop_eh(&busid_priv->sdev->ud); +} + +/* + * called in usb_disconnect() or usb_deregister() + * but only if actconfig(active configuration) exists + */ +static void stub_disconnect(struct usb_device *udev) +{ + struct stub_device *sdev; + const char *udev_busid = dev_name(&udev->dev); + struct bus_id_priv *busid_priv; + int rc; + + dev_dbg(&udev->dev, "Enter disconnect\n"); + + busid_priv = get_busid_priv(udev_busid); + if (!busid_priv) { + BUG(); + return; + } + + sdev = dev_get_drvdata(&udev->dev); + + /* get stub_device */ + if (!sdev) { + dev_err(&udev->dev, "could not get device"); + /* release busid_lock */ + put_busid_priv(busid_priv); + return; + } + + dev_set_drvdata(&udev->dev, NULL); + + /* release busid_lock before call to remove device files */ + put_busid_priv(busid_priv); + + /* + * NOTE: rx/tx threads are invoked for each usb_device. + */ + + /* release port */ + rc = usb_hub_release_port(udev->parent, udev->portnum, + (struct usb_dev_state *) udev); + /* + * NOTE: If a HUB disconnect triggered disconnect of the down stream + * device usb_hub_release_port will return -ENODEV so we can safely ignore + * that error here. + */ + if (rc && (rc != -ENODEV)) { + dev_dbg(&udev->dev, "unable to release port (%i)\n", rc); + return; + } + + /* If usb reset is called from event handler */ + if (usbip_in_eh(current)) + return; + + /* we already have busid_priv, just lock busid_lock */ + spin_lock(&busid_priv->busid_lock); + if (!busid_priv->shutdown_busid) + busid_priv->shutdown_busid = 1; + /* release busid_lock */ + spin_unlock(&busid_priv->busid_lock); + + /* shutdown the current connection */ + shutdown_busid(busid_priv); + + /* we already have busid_priv, just lock busid_lock */ + spin_lock(&busid_priv->busid_lock); + /* free sdev */ + busid_priv->sdev = NULL; + stub_device_free(sdev); + + if (busid_priv->status == STUB_BUSID_ALLOC) + busid_priv->status = STUB_BUSID_ADDED; + /* release busid_lock */ + spin_unlock(&busid_priv->busid_lock); + return; +} + +#ifdef CONFIG_PM + +/* These functions need usb_port_suspend and usb_port_resume, + * which reside in drivers/usb/core/usb.h. Skip for now. */ + +static int stub_suspend(struct usb_device *udev, pm_message_t message) +{ + dev_dbg(&udev->dev, "stub_suspend\n"); + + return 0; +} + +static int stub_resume(struct usb_device *udev, pm_message_t message) +{ + dev_dbg(&udev->dev, "stub_resume\n"); + + return 0; +} + +#endif /* CONFIG_PM */ + +struct usb_device_driver stub_driver = { + .name = "usbip-host", + .probe = stub_probe, + .disconnect = stub_disconnect, +#ifdef CONFIG_PM + .suspend = stub_suspend, + .resume = stub_resume, +#endif + .supports_autosuspend = 0, + .dev_groups = usbip_groups, +}; diff --git a/drivers/usb/usbip/stub_main.c b/drivers/usb/usbip/stub_main.c new file mode 100644 index 000000000..79110a69d --- /dev/null +++ b/drivers/usb/usbip/stub_main.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + */ + +#include +#include +#include +#include + +#include "usbip_common.h" +#include "stub.h" + +#define DRIVER_AUTHOR "Takahiro Hirofuchi" +#define DRIVER_DESC "USB/IP Host Driver" + +struct kmem_cache *stub_priv_cache; + +/* + * busid_tables defines matching busids that usbip can grab. A user can change + * dynamically what device is locally used and what device is exported to a + * remote host. + */ +#define MAX_BUSID 16 +static struct bus_id_priv busid_table[MAX_BUSID]; +static DEFINE_SPINLOCK(busid_table_lock); + +static void init_busid_table(void) +{ + int i; + + /* + * This also sets the bus_table[i].status to + * STUB_BUSID_OTHER, which is 0. + */ + memset(busid_table, 0, sizeof(busid_table)); + + for (i = 0; i < MAX_BUSID; i++) + spin_lock_init(&busid_table[i].busid_lock); +} + +/* + * Find the index of the busid by name. + * Must be called with busid_table_lock held. + */ +static int get_busid_idx(const char *busid) +{ + int i; + int idx = -1; + + for (i = 0; i < MAX_BUSID; i++) { + spin_lock(&busid_table[i].busid_lock); + if (busid_table[i].name[0]) + if (!strncmp(busid_table[i].name, busid, BUSID_SIZE)) { + idx = i; + spin_unlock(&busid_table[i].busid_lock); + break; + } + spin_unlock(&busid_table[i].busid_lock); + } + return idx; +} + +/* Returns holding busid_lock. Should call put_busid_priv() to unlock */ +struct bus_id_priv *get_busid_priv(const char *busid) +{ + int idx; + struct bus_id_priv *bid = NULL; + + spin_lock(&busid_table_lock); + idx = get_busid_idx(busid); + if (idx >= 0) { + bid = &(busid_table[idx]); + /* get busid_lock before returning */ + spin_lock(&bid->busid_lock); + } + spin_unlock(&busid_table_lock); + + return bid; +} + +void put_busid_priv(struct bus_id_priv *bid) +{ + if (bid) + spin_unlock(&bid->busid_lock); +} + +static int add_match_busid(char *busid) +{ + int i; + int ret = -1; + + spin_lock(&busid_table_lock); + /* already registered? */ + if (get_busid_idx(busid) >= 0) { + ret = 0; + goto out; + } + + for (i = 0; i < MAX_BUSID; i++) { + spin_lock(&busid_table[i].busid_lock); + if (!busid_table[i].name[0]) { + strscpy(busid_table[i].name, busid, BUSID_SIZE); + if ((busid_table[i].status != STUB_BUSID_ALLOC) && + (busid_table[i].status != STUB_BUSID_REMOV)) + busid_table[i].status = STUB_BUSID_ADDED; + ret = 0; + spin_unlock(&busid_table[i].busid_lock); + break; + } + spin_unlock(&busid_table[i].busid_lock); + } + +out: + spin_unlock(&busid_table_lock); + + return ret; +} + +int del_match_busid(char *busid) +{ + int idx; + int ret = -1; + + spin_lock(&busid_table_lock); + idx = get_busid_idx(busid); + if (idx < 0) + goto out; + + /* found */ + ret = 0; + + spin_lock(&busid_table[idx].busid_lock); + + if (busid_table[idx].status == STUB_BUSID_OTHER) + memset(busid_table[idx].name, 0, BUSID_SIZE); + + if ((busid_table[idx].status != STUB_BUSID_OTHER) && + (busid_table[idx].status != STUB_BUSID_ADDED)) + busid_table[idx].status = STUB_BUSID_REMOV; + + spin_unlock(&busid_table[idx].busid_lock); +out: + spin_unlock(&busid_table_lock); + + return ret; +} + +static ssize_t match_busid_show(struct device_driver *drv, char *buf) +{ + int i; + char *out = buf; + + spin_lock(&busid_table_lock); + for (i = 0; i < MAX_BUSID; i++) { + spin_lock(&busid_table[i].busid_lock); + if (busid_table[i].name[0]) + out += sprintf(out, "%s ", busid_table[i].name); + spin_unlock(&busid_table[i].busid_lock); + } + spin_unlock(&busid_table_lock); + out += sprintf(out, "\n"); + + return out - buf; +} + +static ssize_t match_busid_store(struct device_driver *dev, const char *buf, + size_t count) +{ + char busid[BUSID_SIZE]; + + if (count < 5) + return -EINVAL; + + /* busid needs to include \0 termination */ + if (strscpy(busid, buf + 4, BUSID_SIZE) < 0) + return -EINVAL; + + if (!strncmp(buf, "add ", 4)) { + if (add_match_busid(busid) < 0) + return -ENOMEM; + + pr_debug("add busid %s\n", busid); + return count; + } + + if (!strncmp(buf, "del ", 4)) { + if (del_match_busid(busid) < 0) + return -ENODEV; + + pr_debug("del busid %s\n", busid); + return count; + } + + return -EINVAL; +} +static DRIVER_ATTR_RW(match_busid); + +static int do_rebind(char *busid, struct bus_id_priv *busid_priv) +{ + int ret = 0; + + /* device_attach() callers should hold parent lock for USB */ + if (busid_priv->udev->dev.parent) + device_lock(busid_priv->udev->dev.parent); + ret = device_attach(&busid_priv->udev->dev); + if (busid_priv->udev->dev.parent) + device_unlock(busid_priv->udev->dev.parent); + if (ret < 0) + dev_err(&busid_priv->udev->dev, "rebind failed\n"); + return ret; +} + +static void stub_device_rebind(void) +{ +#if IS_MODULE(CONFIG_USBIP_HOST) + struct bus_id_priv *busid_priv; + int i; + + /* update status to STUB_BUSID_OTHER so probe ignores the device */ + spin_lock(&busid_table_lock); + for (i = 0; i < MAX_BUSID; i++) { + if (busid_table[i].name[0] && + busid_table[i].shutdown_busid) { + busid_priv = &(busid_table[i]); + busid_priv->status = STUB_BUSID_OTHER; + } + } + spin_unlock(&busid_table_lock); + + /* now run rebind - no need to hold locks. driver files are removed */ + for (i = 0; i < MAX_BUSID; i++) { + if (busid_table[i].name[0] && + busid_table[i].shutdown_busid) { + busid_priv = &(busid_table[i]); + do_rebind(busid_table[i].name, busid_priv); + } + } +#endif +} + +static ssize_t rebind_store(struct device_driver *dev, const char *buf, + size_t count) +{ + int ret; + int len; + struct bus_id_priv *bid; + + /* buf length should be less that BUSID_SIZE */ + len = strnlen(buf, BUSID_SIZE); + + if (!(len < BUSID_SIZE)) + return -EINVAL; + + bid = get_busid_priv(buf); + if (!bid) + return -ENODEV; + + /* mark the device for deletion so probe ignores it during rescan */ + bid->status = STUB_BUSID_OTHER; + /* release the busid lock */ + put_busid_priv(bid); + + ret = do_rebind((char *) buf, bid); + if (ret < 0) + return ret; + + /* delete device from busid_table */ + del_match_busid((char *) buf); + + return count; +} + +static DRIVER_ATTR_WO(rebind); + +static struct stub_priv *stub_priv_pop_from_listhead(struct list_head *listhead) +{ + struct stub_priv *priv, *tmp; + + list_for_each_entry_safe(priv, tmp, listhead, list) { + list_del_init(&priv->list); + return priv; + } + + return NULL; +} + +void stub_free_priv_and_urb(struct stub_priv *priv) +{ + struct urb *urb; + int i; + + for (i = 0; i < priv->num_urbs; i++) { + urb = priv->urbs[i]; + + if (!urb) + return; + + kfree(urb->setup_packet); + urb->setup_packet = NULL; + + + if (urb->transfer_buffer && !priv->sgl) { + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; + } + + if (urb->num_sgs) { + sgl_free(urb->sg); + urb->sg = NULL; + urb->num_sgs = 0; + } + + usb_free_urb(urb); + } + if (!list_empty(&priv->list)) + list_del(&priv->list); + if (priv->sgl) + sgl_free(priv->sgl); + kfree(priv->urbs); + kmem_cache_free(stub_priv_cache, priv); +} + +static struct stub_priv *stub_priv_pop(struct stub_device *sdev) +{ + unsigned long flags; + struct stub_priv *priv; + + spin_lock_irqsave(&sdev->priv_lock, flags); + + priv = stub_priv_pop_from_listhead(&sdev->priv_init); + if (priv) + goto done; + + priv = stub_priv_pop_from_listhead(&sdev->priv_tx); + if (priv) + goto done; + + priv = stub_priv_pop_from_listhead(&sdev->priv_free); + +done: + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + return priv; +} + +void stub_device_cleanup_urbs(struct stub_device *sdev) +{ + struct stub_priv *priv; + int i; + + dev_dbg(&sdev->udev->dev, "Stub device cleaning up urbs\n"); + + while ((priv = stub_priv_pop(sdev))) { + for (i = 0; i < priv->num_urbs; i++) + usb_kill_urb(priv->urbs[i]); + + stub_free_priv_and_urb(priv); + } +} + +static int __init usbip_host_init(void) +{ + int ret; + + init_busid_table(); + + stub_priv_cache = KMEM_CACHE(stub_priv, SLAB_HWCACHE_ALIGN); + if (!stub_priv_cache) { + pr_err("kmem_cache_create failed\n"); + return -ENOMEM; + } + + ret = usb_register_device_driver(&stub_driver, THIS_MODULE); + if (ret) { + pr_err("usb_register failed %d\n", ret); + goto err_usb_register; + } + + ret = driver_create_file(&stub_driver.driver, + &driver_attr_match_busid); + if (ret) { + pr_err("driver_create_file failed\n"); + goto err_create_file; + } + + ret = driver_create_file(&stub_driver.driver, + &driver_attr_rebind); + if (ret) { + pr_err("driver_create_file failed\n"); + goto err_create_file; + } + + return ret; + +err_create_file: + usb_deregister_device_driver(&stub_driver); +err_usb_register: + kmem_cache_destroy(stub_priv_cache); + return ret; +} + +static void __exit usbip_host_exit(void) +{ + driver_remove_file(&stub_driver.driver, + &driver_attr_match_busid); + + driver_remove_file(&stub_driver.driver, + &driver_attr_rebind); + + /* + * deregister() calls stub_disconnect() for all devices. Device + * specific data is cleared in stub_disconnect(). + */ + usb_deregister_device_driver(&stub_driver); + + /* initiate scan to attach devices */ + stub_device_rebind(); + + kmem_cache_destroy(stub_priv_cache); +} + +module_init(usbip_host_init); +module_exit(usbip_host_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/usbip/stub_rx.c b/drivers/usb/usbip/stub_rx.c new file mode 100644 index 000000000..1e9ae5788 --- /dev/null +++ b/drivers/usb/usbip/stub_rx.c @@ -0,0 +1,711 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + */ + +#include +#include +#include +#include +#include + +#include "usbip_common.h" +#include "stub.h" + +static int is_clear_halt_cmd(struct urb *urb) +{ + struct usb_ctrlrequest *req; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + + return (req->bRequest == USB_REQ_CLEAR_FEATURE) && + (req->bRequestType == USB_RECIP_ENDPOINT) && + (req->wValue == USB_ENDPOINT_HALT); +} + +static int is_set_interface_cmd(struct urb *urb) +{ + struct usb_ctrlrequest *req; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + + return (req->bRequest == USB_REQ_SET_INTERFACE) && + (req->bRequestType == USB_RECIP_INTERFACE); +} + +static int is_set_configuration_cmd(struct urb *urb) +{ + struct usb_ctrlrequest *req; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + + return (req->bRequest == USB_REQ_SET_CONFIGURATION) && + (req->bRequestType == USB_RECIP_DEVICE); +} + +static int is_reset_device_cmd(struct urb *urb) +{ + struct usb_ctrlrequest *req; + __u16 value; + __u16 index; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + value = le16_to_cpu(req->wValue); + index = le16_to_cpu(req->wIndex); + + if ((req->bRequest == USB_REQ_SET_FEATURE) && + (req->bRequestType == USB_RT_PORT) && + (value == USB_PORT_FEAT_RESET)) { + usbip_dbg_stub_rx("reset_device_cmd, port %u\n", index); + return 1; + } else + return 0; +} + +static int tweak_clear_halt_cmd(struct urb *urb) +{ + struct usb_ctrlrequest *req; + int target_endp; + int target_dir; + int target_pipe; + int ret; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + + /* + * The stalled endpoint is specified in the wIndex value. The endpoint + * of the urb is the target of this clear_halt request (i.e., control + * endpoint). + */ + target_endp = le16_to_cpu(req->wIndex) & 0x000f; + + /* the stalled endpoint direction is IN or OUT?. USB_DIR_IN is 0x80. */ + target_dir = le16_to_cpu(req->wIndex) & 0x0080; + + if (target_dir) + target_pipe = usb_rcvctrlpipe(urb->dev, target_endp); + else + target_pipe = usb_sndctrlpipe(urb->dev, target_endp); + + ret = usb_clear_halt(urb->dev, target_pipe); + if (ret < 0) + dev_err(&urb->dev->dev, + "usb_clear_halt error: devnum %d endp %d ret %d\n", + urb->dev->devnum, target_endp, ret); + else + dev_info(&urb->dev->dev, + "usb_clear_halt done: devnum %d endp %d\n", + urb->dev->devnum, target_endp); + + return ret; +} + +static int tweak_set_interface_cmd(struct urb *urb) +{ + struct usb_ctrlrequest *req; + __u16 alternate; + __u16 interface; + int ret; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + alternate = le16_to_cpu(req->wValue); + interface = le16_to_cpu(req->wIndex); + + usbip_dbg_stub_rx("set_interface: inf %u alt %u\n", + interface, alternate); + + ret = usb_set_interface(urb->dev, interface, alternate); + if (ret < 0) + dev_err(&urb->dev->dev, + "usb_set_interface error: inf %u alt %u ret %d\n", + interface, alternate, ret); + else + dev_info(&urb->dev->dev, + "usb_set_interface done: inf %u alt %u\n", + interface, alternate); + + return ret; +} + +static int tweak_set_configuration_cmd(struct urb *urb) +{ + struct stub_priv *priv = (struct stub_priv *) urb->context; + struct stub_device *sdev = priv->sdev; + struct usb_ctrlrequest *req; + __u16 config; + int err; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + config = le16_to_cpu(req->wValue); + + usb_lock_device(sdev->udev); + err = usb_set_configuration(sdev->udev, config); + usb_unlock_device(sdev->udev); + if (err && err != -ENODEV) + dev_err(&sdev->udev->dev, "can't set config #%d, error %d\n", + config, err); + return err; +} + +static int tweak_reset_device_cmd(struct urb *urb) +{ + struct stub_priv *priv = (struct stub_priv *) urb->context; + struct stub_device *sdev = priv->sdev; + int err; + + dev_info(&urb->dev->dev, "usb_queue_reset_device\n"); + + err = usb_lock_device_for_reset(sdev->udev, NULL); + if (err < 0) { + dev_err(&urb->dev->dev, "could not obtain lock to reset device\n"); + return err; + } + err = usb_reset_device(sdev->udev); + usb_unlock_device(sdev->udev); + + return err; +} + +/* + * clear_halt, set_interface, and set_configuration require special tricks. + * Returns 1 if request was tweaked, 0 otherwise. + */ +static int tweak_special_requests(struct urb *urb) +{ + int err; + + if (!urb || !urb->setup_packet) + return 0; + + if (usb_pipetype(urb->pipe) != PIPE_CONTROL) + return 0; + + if (is_clear_halt_cmd(urb)) + /* tweak clear_halt */ + err = tweak_clear_halt_cmd(urb); + + else if (is_set_interface_cmd(urb)) + /* tweak set_interface */ + err = tweak_set_interface_cmd(urb); + + else if (is_set_configuration_cmd(urb)) + /* tweak set_configuration */ + err = tweak_set_configuration_cmd(urb); + + else if (is_reset_device_cmd(urb)) + err = tweak_reset_device_cmd(urb); + else { + usbip_dbg_stub_rx("no need to tweak\n"); + return 0; + } + + return !err; +} + +/* + * stub_recv_unlink() unlinks the URB by a call to usb_unlink_urb(). + * By unlinking the urb asynchronously, stub_rx can continuously + * process coming urbs. Even if the urb is unlinked, its completion + * handler will be called and stub_tx will send a return pdu. + * + * See also comments about unlinking strategy in vhci_hcd.c. + */ +static int stub_recv_cmd_unlink(struct stub_device *sdev, + struct usbip_header *pdu) +{ + int ret, i; + unsigned long flags; + struct stub_priv *priv; + + spin_lock_irqsave(&sdev->priv_lock, flags); + + list_for_each_entry(priv, &sdev->priv_init, list) { + if (priv->seqnum != pdu->u.cmd_unlink.seqnum) + continue; + + /* + * This matched urb is not completed yet (i.e., be in + * flight in usb hcd hardware/driver). Now we are + * cancelling it. The unlinking flag means that we are + * now not going to return the normal result pdu of a + * submission request, but going to return a result pdu + * of the unlink request. + */ + priv->unlinking = 1; + + /* + * In the case that unlinking flag is on, prev->seqnum + * is changed from the seqnum of the cancelling urb to + * the seqnum of the unlink request. This will be used + * to make the result pdu of the unlink request. + */ + priv->seqnum = pdu->base.seqnum; + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + /* + * usb_unlink_urb() is now out of spinlocking to avoid + * spinlock recursion since stub_complete() is + * sometimes called in this context but not in the + * interrupt context. If stub_complete() is executed + * before we call usb_unlink_urb(), usb_unlink_urb() + * will return an error value. In this case, stub_tx + * will return the result pdu of this unlink request + * though submission is completed and actual unlinking + * is not executed. OK? + */ + /* In the above case, urb->status is not -ECONNRESET, + * so a driver in a client host will know the failure + * of the unlink request ? + */ + for (i = priv->completed_urbs; i < priv->num_urbs; i++) { + ret = usb_unlink_urb(priv->urbs[i]); + if (ret != -EINPROGRESS) + dev_err(&priv->urbs[i]->dev->dev, + "failed to unlink %d/%d urb of seqnum %lu, ret %d\n", + i + 1, priv->num_urbs, + priv->seqnum, ret); + } + return 0; + } + + usbip_dbg_stub_rx("seqnum %u is not pending\n", + pdu->u.cmd_unlink.seqnum); + + /* + * The urb of the unlink target is not found in priv_init queue. It was + * already completed and its results is/was going to be sent by a + * CMD_RET pdu. In this case, usb_unlink_urb() is not needed. We only + * return the completeness of this unlink request to vhci_hcd. + */ + stub_enqueue_ret_unlink(sdev, pdu->base.seqnum, 0); + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + return 0; +} + +static int valid_request(struct stub_device *sdev, struct usbip_header *pdu) +{ + struct usbip_device *ud = &sdev->ud; + int valid = 0; + + if (pdu->base.devid == sdev->devid) { + spin_lock_irq(&ud->lock); + if (ud->status == SDEV_ST_USED) { + /* A request is valid. */ + valid = 1; + } + spin_unlock_irq(&ud->lock); + } + + return valid; +} + +static struct stub_priv *stub_priv_alloc(struct stub_device *sdev, + struct usbip_header *pdu) +{ + struct stub_priv *priv; + struct usbip_device *ud = &sdev->ud; + unsigned long flags; + + spin_lock_irqsave(&sdev->priv_lock, flags); + + priv = kmem_cache_zalloc(stub_priv_cache, GFP_ATOMIC); + if (!priv) { + dev_err(&sdev->udev->dev, "alloc stub_priv\n"); + spin_unlock_irqrestore(&sdev->priv_lock, flags); + usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC); + return NULL; + } + + priv->seqnum = pdu->base.seqnum; + priv->sdev = sdev; + + /* + * After a stub_priv is linked to a list_head, + * our error handler can free allocated data. + */ + list_add_tail(&priv->list, &sdev->priv_init); + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + return priv; +} + +static int get_pipe(struct stub_device *sdev, struct usbip_header *pdu) +{ + struct usb_device *udev = sdev->udev; + struct usb_host_endpoint *ep; + struct usb_endpoint_descriptor *epd = NULL; + int epnum = pdu->base.ep; + int dir = pdu->base.direction; + + if (epnum < 0 || epnum > 15) + goto err_ret; + + if (dir == USBIP_DIR_IN) + ep = udev->ep_in[epnum & 0x7f]; + else + ep = udev->ep_out[epnum & 0x7f]; + if (!ep) + goto err_ret; + + epd = &ep->desc; + + if (usb_endpoint_xfer_control(epd)) { + if (dir == USBIP_DIR_OUT) + return usb_sndctrlpipe(udev, epnum); + else + return usb_rcvctrlpipe(udev, epnum); + } + + if (usb_endpoint_xfer_bulk(epd)) { + if (dir == USBIP_DIR_OUT) + return usb_sndbulkpipe(udev, epnum); + else + return usb_rcvbulkpipe(udev, epnum); + } + + if (usb_endpoint_xfer_int(epd)) { + if (dir == USBIP_DIR_OUT) + return usb_sndintpipe(udev, epnum); + else + return usb_rcvintpipe(udev, epnum); + } + + if (usb_endpoint_xfer_isoc(epd)) { + /* validate number of packets */ + if (pdu->u.cmd_submit.number_of_packets < 0 || + pdu->u.cmd_submit.number_of_packets > + USBIP_MAX_ISO_PACKETS) { + dev_err(&sdev->udev->dev, + "CMD_SUBMIT: isoc invalid num packets %d\n", + pdu->u.cmd_submit.number_of_packets); + return -1; + } + if (dir == USBIP_DIR_OUT) + return usb_sndisocpipe(udev, epnum); + else + return usb_rcvisocpipe(udev, epnum); + } + +err_ret: + /* NOT REACHED */ + dev_err(&sdev->udev->dev, "CMD_SUBMIT: invalid epnum %d\n", epnum); + return -1; +} + +static void masking_bogus_flags(struct urb *urb) +{ + int xfertype; + struct usb_device *dev; + struct usb_host_endpoint *ep; + int is_out; + unsigned int allowed; + + if (!urb || urb->hcpriv || !urb->complete) + return; + dev = urb->dev; + if ((!dev) || (dev->state < USB_STATE_UNAUTHENTICATED)) + return; + + ep = (usb_pipein(urb->pipe) ? dev->ep_in : dev->ep_out) + [usb_pipeendpoint(urb->pipe)]; + if (!ep) + return; + + 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; + is_out = !(setup->bRequestType & USB_DIR_IN) || + !setup->wLength; + } else { + is_out = usb_endpoint_dir_out(&ep->desc); + } + + /* enforce 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; + default: /* all non-iso endpoints */ + if (!is_out) + allowed |= URB_SHORT_NOT_OK; + break; + case USB_ENDPOINT_XFER_ISOC: + allowed |= URB_ISO_ASAP; + break; + } + urb->transfer_flags &= allowed; +} + +static int stub_recv_xbuff(struct usbip_device *ud, struct stub_priv *priv) +{ + int ret; + int i; + + for (i = 0; i < priv->num_urbs; i++) { + ret = usbip_recv_xbuff(ud, priv->urbs[i]); + if (ret < 0) + break; + } + + return ret; +} + +static void stub_recv_cmd_submit(struct stub_device *sdev, + struct usbip_header *pdu) +{ + struct stub_priv *priv; + struct usbip_device *ud = &sdev->ud; + struct usb_device *udev = sdev->udev; + struct scatterlist *sgl = NULL, *sg; + void *buffer = NULL; + unsigned long long buf_len; + int nents; + int num_urbs = 1; + int pipe = get_pipe(sdev, pdu); + int use_sg = pdu->u.cmd_submit.transfer_flags & USBIP_URB_DMA_MAP_SG; + int support_sg = 1; + int np = 0; + int ret, i; + int is_tweaked; + + if (pipe == -1) + return; + + /* + * Smatch reported the error case where use_sg is true and buf_len is 0. + * In this case, It adds SDEV_EVENT_ERROR_MALLOC and stub_priv will be + * released by stub event handler and connection will be shut down. + */ + priv = stub_priv_alloc(sdev, pdu); + if (!priv) + return; + + buf_len = (unsigned long long)pdu->u.cmd_submit.transfer_buffer_length; + + if (use_sg && !buf_len) { + dev_err(&udev->dev, "sg buffer with zero length\n"); + goto err_malloc; + } + + /* allocate urb transfer buffer, if needed */ + if (buf_len) { + if (use_sg) { + sgl = sgl_alloc(buf_len, GFP_KERNEL, &nents); + if (!sgl) + goto err_malloc; + + /* Check if the server's HCD supports SG */ + if (!udev->bus->sg_tablesize) { + /* + * If the server's HCD doesn't support SG, break + * a single SG request into several URBs and map + * each SG list entry to corresponding URB + * buffer. The previously allocated SG list is + * stored in priv->sgl (If the server's HCD + * support SG, SG list is stored only in + * urb->sg) and it is used as an indicator that + * the server split single SG request into + * several URBs. Later, priv->sgl is used by + * stub_complete() and stub_send_ret_submit() to + * reassemble the divied URBs. + */ + support_sg = 0; + num_urbs = nents; + priv->completed_urbs = 0; + pdu->u.cmd_submit.transfer_flags &= + ~USBIP_URB_DMA_MAP_SG; + } + } else { + buffer = kzalloc(buf_len, GFP_KERNEL); + if (!buffer) + goto err_malloc; + } + } + + /* allocate urb array */ + priv->num_urbs = num_urbs; + priv->urbs = kmalloc_objs(*priv->urbs, num_urbs); + if (!priv->urbs) + goto err_urbs; + + /* setup a urb */ + if (support_sg) { + if (usb_pipeisoc(pipe)) + np = pdu->u.cmd_submit.number_of_packets; + + priv->urbs[0] = usb_alloc_urb(np, GFP_KERNEL); + if (!priv->urbs[0]) + goto err_urb; + + if (buf_len) { + if (use_sg) { + priv->urbs[0]->sg = sgl; + priv->urbs[0]->num_sgs = nents; + priv->urbs[0]->transfer_buffer = NULL; + } else { + priv->urbs[0]->transfer_buffer = buffer; + } + } + + /* copy urb setup packet */ + priv->urbs[0]->setup_packet = kmemdup(&pdu->u.cmd_submit.setup, + 8, GFP_KERNEL); + if (!priv->urbs[0]->setup_packet) { + usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC); + return; + } + + usbip_pack_pdu(pdu, priv->urbs[0], USBIP_CMD_SUBMIT, 0); + } else { + for_each_sg(sgl, sg, nents, i) { + priv->urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + /* The URBs which is previously allocated will be freed + * in stub_device_cleanup_urbs() if error occurs. + */ + if (!priv->urbs[i]) + goto err_urb; + + usbip_pack_pdu(pdu, priv->urbs[i], USBIP_CMD_SUBMIT, 0); + priv->urbs[i]->transfer_buffer = sg_virt(sg); + priv->urbs[i]->transfer_buffer_length = sg->length; + } + priv->sgl = sgl; + } + + for (i = 0; i < num_urbs; i++) { + /* set other members from the base header of pdu */ + priv->urbs[i]->context = (void *) priv; + priv->urbs[i]->dev = udev; + priv->urbs[i]->pipe = pipe; + priv->urbs[i]->complete = stub_complete; + + /* + * all URBs belong to a single PDU, so a global is_tweaked flag is + * enough + */ + is_tweaked = tweak_special_requests(priv->urbs[i]); + + masking_bogus_flags(priv->urbs[i]); + } + + if (stub_recv_xbuff(ud, priv) < 0) + return; + + if (usbip_recv_iso(ud, priv->urbs[0]) < 0) + return; + + /* urb is now ready to submit */ + for (i = 0; i < priv->num_urbs; i++) { + if (!is_tweaked) { + ret = usb_submit_urb(priv->urbs[i], GFP_KERNEL); + + if (ret == 0) + usbip_dbg_stub_rx("submit urb ok, seqnum %u\n", + pdu->base.seqnum); + else { + dev_err(&udev->dev, "submit_urb error, %d\n", ret); + usbip_dump_header(pdu); + usbip_dump_urb(priv->urbs[i]); + + /* + * Pessimistic. + * This connection will be discarded. + */ + usbip_event_add(ud, SDEV_EVENT_ERROR_SUBMIT); + break; + } + } else { + /* + * An identical URB was already submitted in + * tweak_special_requests(). Skip submitting this URB to not + * duplicate the request. + */ + priv->urbs[i]->status = 0; + stub_complete(priv->urbs[i]); + } + } + + usbip_dbg_stub_rx("Leave\n"); + return; + +err_urb: + kfree(priv->urbs); +err_urbs: + kfree(buffer); + sgl_free(sgl); +err_malloc: + usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC); +} + +/* recv a pdu */ +static void stub_rx_pdu(struct usbip_device *ud) +{ + int ret; + struct usbip_header pdu; + struct stub_device *sdev = container_of(ud, struct stub_device, ud); + struct device *dev = &sdev->udev->dev; + + usbip_dbg_stub_rx("Enter\n"); + + memset(&pdu, 0, sizeof(pdu)); + + /* receive a pdu header */ + ret = usbip_recv(ud->tcp_socket, &pdu, sizeof(pdu)); + if (ret != sizeof(pdu)) { + dev_err(dev, "recv a header, %d\n", ret); + usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); + return; + } + + usbip_header_correct_endian(&pdu, 0); + + if (usbip_dbg_flag_stub_rx) + usbip_dump_header(&pdu); + + if (!valid_request(sdev, &pdu)) { + dev_err(dev, "recv invalid request\n"); + usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); + return; + } + + switch (pdu.base.command) { + case USBIP_CMD_UNLINK: + stub_recv_cmd_unlink(sdev, &pdu); + break; + + case USBIP_CMD_SUBMIT: + stub_recv_cmd_submit(sdev, &pdu); + break; + + default: + /* NOTREACHED */ + dev_err(dev, "unknown pdu\n"); + usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); + break; + } +} + +int stub_rx_loop(void *data) +{ + struct usbip_device *ud = data; + + while (!kthread_should_stop()) { + if (usbip_event_happened(ud)) + break; + + stub_rx_pdu(ud); + } + + return 0; +} diff --git a/drivers/usb/usbip/stub_tx.c b/drivers/usb/usbip/stub_tx.c new file mode 100644 index 000000000..4ffd34225 --- /dev/null +++ b/drivers/usb/usbip/stub_tx.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + */ + +#include +#include +#include +#include + +#include "usbip_common.h" +#include "stub.h" + +/* be in spin_lock_irqsave(&sdev->priv_lock, flags) */ +void stub_enqueue_ret_unlink(struct stub_device *sdev, __u32 seqnum, + __u32 status) +{ + struct stub_unlink *unlink; + + unlink = kzalloc_obj(struct stub_unlink, GFP_ATOMIC); + if (!unlink) { + usbip_event_add(&sdev->ud, VDEV_EVENT_ERROR_MALLOC); + return; + } + + unlink->seqnum = seqnum; + unlink->status = status; + + list_add_tail(&unlink->list, &sdev->unlink_tx); +} + +/** + * stub_complete - completion handler of a usbip urb + * @urb: pointer to the urb completed + * + * When a urb has completed, the USB core driver calls this function mostly in + * the interrupt context. To return the result of a urb, the completed urb is + * linked to the pending list of returning. + * + */ +void stub_complete(struct urb *urb) +{ + struct stub_priv *priv = (struct stub_priv *) urb->context; + struct stub_device *sdev = priv->sdev; + unsigned long flags; + + usbip_dbg_stub_tx("complete! status %d\n", urb->status); + + switch (urb->status) { + case 0: + /* OK */ + break; + case -ENOENT: + dev_info(&urb->dev->dev, + "stopped by a call to usb_kill_urb() because of cleaning up a virtual connection\n"); + return; + case -ECONNRESET: + dev_dbg(&urb->dev->dev, + "unlinked by a call to usb_unlink_urb()\n"); + break; + case -EPIPE: + dev_info(&urb->dev->dev, "endpoint %d is stalled\n", + usb_pipeendpoint(urb->pipe)); + break; + case -ESHUTDOWN: + dev_info(&urb->dev->dev, "device removed?\n"); + break; + default: + dev_info(&urb->dev->dev, + "urb completion with non-zero status %d\n", + urb->status); + break; + } + + /* + * If the server breaks single SG request into the several URBs, the + * URBs must be reassembled before sending completed URB to the vhci. + * Don't wake up the tx thread until all the URBs are completed. + */ + if (priv->sgl) { + priv->completed_urbs++; + + /* Only save the first error status */ + if (urb->status && !priv->urb_status) + priv->urb_status = urb->status; + + if (priv->completed_urbs < priv->num_urbs) + return; + } + + /* link a urb to the queue of tx. */ + spin_lock_irqsave(&sdev->priv_lock, flags); + if (sdev->ud.tcp_socket == NULL) { + usbip_dbg_stub_tx("ignore urb for closed connection\n"); + /* It will be freed in stub_device_cleanup_urbs(). */ + } else if (priv->unlinking) { + stub_enqueue_ret_unlink(sdev, priv->seqnum, urb->status); + stub_free_priv_and_urb(priv); + } else { + list_move_tail(&priv->list, &sdev->priv_tx); + } + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + /* wake up tx_thread */ + wake_up(&sdev->tx_waitq); +} + +static inline void setup_base_pdu(struct usbip_header_basic *base, + __u32 command, __u32 seqnum) +{ + base->command = command; + base->seqnum = seqnum; + base->devid = 0; + base->ep = 0; + base->direction = 0; +} + +static void setup_ret_submit_pdu(struct usbip_header *rpdu, struct urb *urb) +{ + struct stub_priv *priv = (struct stub_priv *) urb->context; + + setup_base_pdu(&rpdu->base, USBIP_RET_SUBMIT, priv->seqnum); + usbip_pack_pdu(rpdu, urb, USBIP_RET_SUBMIT, 1); +} + +static void setup_ret_unlink_pdu(struct usbip_header *rpdu, + struct stub_unlink *unlink) +{ + setup_base_pdu(&rpdu->base, USBIP_RET_UNLINK, unlink->seqnum); + rpdu->u.ret_unlink.status = unlink->status; +} + +static struct stub_priv *dequeue_from_priv_tx(struct stub_device *sdev) +{ + unsigned long flags; + struct stub_priv *priv, *tmp; + + spin_lock_irqsave(&sdev->priv_lock, flags); + + list_for_each_entry_safe(priv, tmp, &sdev->priv_tx, list) { + list_move_tail(&priv->list, &sdev->priv_free); + spin_unlock_irqrestore(&sdev->priv_lock, flags); + return priv; + } + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + return NULL; +} + +static int stub_send_ret_submit(struct stub_device *sdev) +{ + unsigned long flags; + struct stub_priv *priv, *tmp; + + struct msghdr msg; + size_t txsize; + + size_t total_size = 0; + + while ((priv = dequeue_from_priv_tx(sdev)) != NULL) { + struct urb *urb = priv->urbs[0]; + struct usbip_header pdu_header; + struct usbip_iso_packet_descriptor *iso_buffer = NULL; + struct kvec *iov = NULL; + struct scatterlist *sg; + u32 actual_length = 0; + int iovnum = 0; + int ret; + int i; + + txsize = 0; + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + + if (urb->actual_length > 0 && !urb->transfer_buffer && + !urb->num_sgs) { + dev_err(&sdev->udev->dev, + "urb: actual_length %d transfer_buffer null\n", + urb->actual_length); + return -1; + } + + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) + iovnum = 2 + urb->number_of_packets; + else if (usb_pipein(urb->pipe) && urb->actual_length > 0 && + urb->num_sgs) + iovnum = 1 + urb->num_sgs; + else if (usb_pipein(urb->pipe) && priv->sgl) + iovnum = 1 + priv->num_urbs; + else + iovnum = 2; + + iov = kzalloc_objs(struct kvec, iovnum); + + if (!iov) { + usbip_event_add(&sdev->ud, SDEV_EVENT_ERROR_MALLOC); + return -1; + } + + iovnum = 0; + + /* 1. setup usbip_header */ + setup_ret_submit_pdu(&pdu_header, urb); + usbip_dbg_stub_tx("setup txdata seqnum: %u\n", + pdu_header.base.seqnum); + + if (priv->sgl) { + for (i = 0; i < priv->num_urbs; i++) + actual_length += priv->urbs[i]->actual_length; + + pdu_header.u.ret_submit.status = priv->urb_status; + pdu_header.u.ret_submit.actual_length = actual_length; + } + + usbip_header_correct_endian(&pdu_header, 1); + + iov[iovnum].iov_base = &pdu_header; + iov[iovnum].iov_len = sizeof(pdu_header); + iovnum++; + txsize += sizeof(pdu_header); + + /* 2. setup transfer buffer */ + if (usb_pipein(urb->pipe) && priv->sgl) { + /* If the server split a single SG request into several + * URBs because the server's HCD doesn't support SG, + * reassemble the split URB buffers into a single + * return command. + */ + for (i = 0; i < priv->num_urbs; i++) { + iov[iovnum].iov_base = + priv->urbs[i]->transfer_buffer; + iov[iovnum].iov_len = + priv->urbs[i]->actual_length; + iovnum++; + } + txsize += actual_length; + } else if (usb_pipein(urb->pipe) && + usb_pipetype(urb->pipe) != PIPE_ISOCHRONOUS && + urb->actual_length > 0) { + if (urb->num_sgs) { + unsigned int copy = urb->actual_length; + unsigned int size; + + for_each_sg(urb->sg, sg, urb->num_sgs, i) { + if (copy == 0) + break; + + size = min(copy, sg->length); + iov[iovnum].iov_base = sg_virt(sg); + iov[iovnum].iov_len = size; + + iovnum++; + copy -= size; + } + } else { + iov[iovnum].iov_base = urb->transfer_buffer; + iov[iovnum].iov_len = urb->actual_length; + iovnum++; + } + txsize += urb->actual_length; + } else if (usb_pipein(urb->pipe) && + usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + /* + * For isochronous packets: actual length is the sum of + * the actual length of the individual, packets, but as + * the packet offsets are not changed there will be + * padding between the packets. To optimally use the + * bandwidth the padding is not transmitted. + */ + + int i; + + for (i = 0; i < urb->number_of_packets; i++) { + iov[iovnum].iov_base = urb->transfer_buffer + + urb->iso_frame_desc[i].offset; + iov[iovnum].iov_len = + urb->iso_frame_desc[i].actual_length; + iovnum++; + txsize += urb->iso_frame_desc[i].actual_length; + } + + if (txsize != sizeof(pdu_header) + urb->actual_length) { + dev_err(&sdev->udev->dev, + "actual length of urb %d does not match iso packet sizes %zu\n", + urb->actual_length, + txsize-sizeof(pdu_header)); + kfree(iov); + usbip_event_add(&sdev->ud, + SDEV_EVENT_ERROR_TCP); + return -1; + } + } + + /* 3. setup iso_packet_descriptor */ + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + ssize_t len = 0; + + iso_buffer = usbip_alloc_iso_desc_pdu(urb, &len); + if (!iso_buffer) { + usbip_event_add(&sdev->ud, + SDEV_EVENT_ERROR_MALLOC); + kfree(iov); + return -1; + } + + iov[iovnum].iov_base = iso_buffer; + iov[iovnum].iov_len = len; + txsize += len; + iovnum++; + } + + ret = kernel_sendmsg(sdev->ud.tcp_socket, &msg, + iov, iovnum, txsize); + if (ret != txsize) { + dev_err(&sdev->udev->dev, + "sendmsg failed!, retval %d for %zd\n", + ret, txsize); + kfree(iov); + kfree(iso_buffer); + usbip_event_add(&sdev->ud, SDEV_EVENT_ERROR_TCP); + return -1; + } + + kfree(iov); + kfree(iso_buffer); + + total_size += txsize; + } + + spin_lock_irqsave(&sdev->priv_lock, flags); + list_for_each_entry_safe(priv, tmp, &sdev->priv_free, list) { + stub_free_priv_and_urb(priv); + } + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + return total_size; +} + +static struct stub_unlink *dequeue_from_unlink_tx(struct stub_device *sdev) +{ + unsigned long flags; + struct stub_unlink *unlink, *tmp; + + spin_lock_irqsave(&sdev->priv_lock, flags); + + list_for_each_entry_safe(unlink, tmp, &sdev->unlink_tx, list) { + list_move_tail(&unlink->list, &sdev->unlink_free); + spin_unlock_irqrestore(&sdev->priv_lock, flags); + return unlink; + } + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + return NULL; +} + +static int stub_send_ret_unlink(struct stub_device *sdev) +{ + unsigned long flags; + struct stub_unlink *unlink, *tmp; + + struct msghdr msg; + struct kvec iov[1]; + size_t txsize; + + size_t total_size = 0; + + while ((unlink = dequeue_from_unlink_tx(sdev)) != NULL) { + int ret; + struct usbip_header pdu_header; + + txsize = 0; + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + memset(&iov, 0, sizeof(iov)); + + usbip_dbg_stub_tx("setup ret unlink %lu\n", unlink->seqnum); + + /* 1. setup usbip_header */ + setup_ret_unlink_pdu(&pdu_header, unlink); + usbip_header_correct_endian(&pdu_header, 1); + + iov[0].iov_base = &pdu_header; + iov[0].iov_len = sizeof(pdu_header); + txsize += sizeof(pdu_header); + + ret = kernel_sendmsg(sdev->ud.tcp_socket, &msg, iov, + 1, txsize); + if (ret != txsize) { + dev_err(&sdev->udev->dev, + "sendmsg failed!, retval %d for %zd\n", + ret, txsize); + usbip_event_add(&sdev->ud, SDEV_EVENT_ERROR_TCP); + return -1; + } + + usbip_dbg_stub_tx("send txdata\n"); + total_size += txsize; + } + + spin_lock_irqsave(&sdev->priv_lock, flags); + + list_for_each_entry_safe(unlink, tmp, &sdev->unlink_free, list) { + list_del(&unlink->list); + kfree(unlink); + } + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + return total_size; +} + +int stub_tx_loop(void *data) +{ + struct usbip_device *ud = data; + struct stub_device *sdev = container_of(ud, struct stub_device, ud); + + while (!kthread_should_stop()) { + if (usbip_event_happened(ud)) + break; + + /* + * send_ret_submit comes earlier than send_ret_unlink. stub_rx + * looks at only priv_init queue. If the completion of a URB is + * earlier than the receive of CMD_UNLINK, priv is moved to + * priv_tx queue and stub_rx does not find the target priv. In + * this case, vhci_rx receives the result of the submit request + * and then receives the result of the unlink request. The + * result of the submit is given back to the usbcore as the + * completion of the unlink request. The request of the + * unlink is ignored. This is ok because a driver who calls + * usb_unlink_urb() understands the unlink was too late by + * getting the status of the given-backed URB which has the + * status of usb_submit_urb(). + */ + if (stub_send_ret_submit(sdev) < 0) + break; + + if (stub_send_ret_unlink(sdev) < 0) + break; + + wait_event_interruptible(sdev->tx_waitq, + (!list_empty(&sdev->priv_tx) || + !list_empty(&sdev->unlink_tx) || + kthread_should_stop())); + } + + return 0; +} diff --git a/drivers/usb/usbip/usbip_common.c b/drivers/usb/usbip/usbip_common.c new file mode 100644 index 000000000..a5837c0fe --- /dev/null +++ b/drivers/usb/usbip/usbip_common.c @@ -0,0 +1,920 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015-2016 Samsung Electronics + * Krzysztof Opasiak + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbip_common.h" + +#define DRIVER_AUTHOR "Takahiro Hirofuchi " +#define DRIVER_DESC "USB/IP Core" + +#ifdef CONFIG_USBIP_DEBUG +unsigned long usbip_debug_flag = 0xffffffff; +#else +unsigned long usbip_debug_flag; +#endif +EXPORT_SYMBOL_GPL(usbip_debug_flag); +module_param(usbip_debug_flag, ulong, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(usbip_debug_flag, "debug flags (defined in usbip_common.h)"); + +/* FIXME */ +struct device_attribute dev_attr_usbip_debug; +EXPORT_SYMBOL_GPL(dev_attr_usbip_debug); + +static ssize_t usbip_debug_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%lx\n", usbip_debug_flag); +} + +static ssize_t usbip_debug_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + if (sscanf(buf, "%lx", &usbip_debug_flag) != 1) + return -EINVAL; + return count; +} +DEVICE_ATTR_RW(usbip_debug); + +static void usbip_dump_buffer(char *buff, int bufflen) +{ + print_hex_dump(KERN_DEBUG, "usbip-core", DUMP_PREFIX_OFFSET, 16, 4, + buff, bufflen, false); +} + +static void usbip_dump_pipe(unsigned int p) +{ + unsigned char type = usb_pipetype(p); + unsigned char ep = usb_pipeendpoint(p); + unsigned char dev = usb_pipedevice(p); + unsigned char dir = usb_pipein(p); + + pr_debug("dev(%d) ep(%d) [%s] ", dev, ep, dir ? "IN" : "OUT"); + + switch (type) { + case PIPE_ISOCHRONOUS: + pr_debug("ISO\n"); + break; + case PIPE_INTERRUPT: + pr_debug("INT\n"); + break; + case PIPE_CONTROL: + pr_debug("CTRL\n"); + break; + case PIPE_BULK: + pr_debug("BULK\n"); + break; + default: + pr_debug("ERR\n"); + break; + } +} + +static void usbip_dump_usb_device(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + int i; + + dev_dbg(dev, " devnum(%d) devpath(%s) usb speed(%s)", + udev->devnum, udev->devpath, usb_speed_string(udev->speed)); + + pr_debug("tt hub ttport %d\n", udev->ttport); + + dev_dbg(dev, " "); + for (i = 0; i < 16; i++) + pr_debug(" %2u", i); + pr_debug("\n"); + + dev_dbg(dev, " toggle0(IN) :"); + for (i = 0; i < 16; i++) + pr_debug(" %2u", (udev->toggle[0] & (1 << i)) ? 1 : 0); + pr_debug("\n"); + + dev_dbg(dev, " toggle1(OUT):"); + for (i = 0; i < 16; i++) + pr_debug(" %2u", (udev->toggle[1] & (1 << i)) ? 1 : 0); + pr_debug("\n"); + + dev_dbg(dev, " epmaxp_in :"); + for (i = 0; i < 16; i++) { + if (udev->ep_in[i]) + pr_debug(" %2u", + le16_to_cpu(udev->ep_in[i]->desc.wMaxPacketSize)); + } + pr_debug("\n"); + + dev_dbg(dev, " epmaxp_out :"); + for (i = 0; i < 16; i++) { + if (udev->ep_out[i]) + pr_debug(" %2u", + le16_to_cpu(udev->ep_out[i]->desc.wMaxPacketSize)); + } + pr_debug("\n"); + + dev_dbg(dev, "parent %s, bus %s\n", dev_name(&udev->parent->dev), + udev->bus->bus_name); + + dev_dbg(dev, "have_langid %d, string_langid %d\n", + udev->have_langid, udev->string_langid); + + dev_dbg(dev, "maxchild %d\n", udev->maxchild); +} + +static void usbip_dump_request_type(__u8 rt) +{ + switch (rt & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + pr_debug("DEVICE"); + break; + case USB_RECIP_INTERFACE: + pr_debug("INTERF"); + break; + case USB_RECIP_ENDPOINT: + pr_debug("ENDPOI"); + break; + case USB_RECIP_OTHER: + pr_debug("OTHER "); + break; + default: + pr_debug("------"); + break; + } +} + +static void usbip_dump_usb_ctrlrequest(struct usb_ctrlrequest *cmd) +{ + if (!cmd) { + pr_debug(" : null pointer\n"); + return; + } + + pr_debug(" "); + pr_debug("bRequestType(%02X) bRequest(%02X) wValue(%04X) wIndex(%04X) wLength(%04X) ", + cmd->bRequestType, cmd->bRequest, + cmd->wValue, cmd->wIndex, cmd->wLength); + pr_debug("\n "); + + if ((cmd->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + pr_debug("STANDARD "); + switch (cmd->bRequest) { + case USB_REQ_GET_STATUS: + pr_debug("GET_STATUS\n"); + break; + case USB_REQ_CLEAR_FEATURE: + pr_debug("CLEAR_FEAT\n"); + break; + case USB_REQ_SET_FEATURE: + pr_debug("SET_FEAT\n"); + break; + case USB_REQ_SET_ADDRESS: + pr_debug("SET_ADDRRS\n"); + break; + case USB_REQ_GET_DESCRIPTOR: + pr_debug("GET_DESCRI\n"); + break; + case USB_REQ_SET_DESCRIPTOR: + pr_debug("SET_DESCRI\n"); + break; + case USB_REQ_GET_CONFIGURATION: + pr_debug("GET_CONFIG\n"); + break; + case USB_REQ_SET_CONFIGURATION: + pr_debug("SET_CONFIG\n"); + break; + case USB_REQ_GET_INTERFACE: + pr_debug("GET_INTERF\n"); + break; + case USB_REQ_SET_INTERFACE: + pr_debug("SET_INTERF\n"); + break; + case USB_REQ_SYNCH_FRAME: + pr_debug("SYNC_FRAME\n"); + break; + default: + pr_debug("REQ(%02X)\n", cmd->bRequest); + break; + } + usbip_dump_request_type(cmd->bRequestType); + } else if ((cmd->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) { + pr_debug("CLASS\n"); + } else if ((cmd->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { + pr_debug("VENDOR\n"); + } else if ((cmd->bRequestType & USB_TYPE_MASK) == USB_TYPE_RESERVED) { + pr_debug("RESERVED\n"); + } +} + +void usbip_dump_urb(struct urb *urb) +{ + struct device *dev; + + if (!urb) { + pr_debug("urb: null pointer!!\n"); + return; + } + + if (!urb->dev) { + pr_debug("urb->dev: null pointer!!\n"); + return; + } + + dev = &urb->dev->dev; + + usbip_dump_usb_device(urb->dev); + + dev_dbg(dev, " pipe :%08x ", urb->pipe); + + usbip_dump_pipe(urb->pipe); + + dev_dbg(dev, " status :%d\n", urb->status); + dev_dbg(dev, " transfer_flags :%08X\n", urb->transfer_flags); + dev_dbg(dev, " transfer_buffer_length:%d\n", + urb->transfer_buffer_length); + dev_dbg(dev, " actual_length :%d\n", urb->actual_length); + + if (urb->setup_packet && usb_pipetype(urb->pipe) == PIPE_CONTROL) + usbip_dump_usb_ctrlrequest( + (struct usb_ctrlrequest *)urb->setup_packet); + + dev_dbg(dev, " start_frame :%d\n", urb->start_frame); + dev_dbg(dev, " number_of_packets :%d\n", urb->number_of_packets); + dev_dbg(dev, " interval :%d\n", urb->interval); + dev_dbg(dev, " error_count :%d\n", urb->error_count); +} +EXPORT_SYMBOL_GPL(usbip_dump_urb); + +void usbip_dump_header(struct usbip_header *pdu) +{ + pr_debug("BASE: cmd %u seq %u devid %u dir %u ep %u\n", + pdu->base.command, + pdu->base.seqnum, + pdu->base.devid, + pdu->base.direction, + pdu->base.ep); + + switch (pdu->base.command) { + case USBIP_CMD_SUBMIT: + pr_debug("USBIP_CMD_SUBMIT: x_flags %u x_len %u sf %u #p %d iv %d\n", + pdu->u.cmd_submit.transfer_flags, + pdu->u.cmd_submit.transfer_buffer_length, + pdu->u.cmd_submit.start_frame, + pdu->u.cmd_submit.number_of_packets, + pdu->u.cmd_submit.interval); + break; + case USBIP_CMD_UNLINK: + pr_debug("USBIP_CMD_UNLINK: seq %u\n", + pdu->u.cmd_unlink.seqnum); + break; + case USBIP_RET_SUBMIT: + pr_debug("USBIP_RET_SUBMIT: st %d al %u sf %d #p %d ec %d\n", + pdu->u.ret_submit.status, + pdu->u.ret_submit.actual_length, + pdu->u.ret_submit.start_frame, + pdu->u.ret_submit.number_of_packets, + pdu->u.ret_submit.error_count); + break; + case USBIP_RET_UNLINK: + pr_debug("USBIP_RET_UNLINK: status %d\n", + pdu->u.ret_unlink.status); + break; + default: + /* NOT REACHED */ + pr_err("unknown command\n"); + break; + } +} +EXPORT_SYMBOL_GPL(usbip_dump_header); + +/* Receive data over TCP/IP. */ +int usbip_recv(struct socket *sock, void *buf, int size) +{ + int result; + struct kvec iov = {.iov_base = buf, .iov_len = size}; + struct msghdr msg = {.msg_flags = MSG_NOSIGNAL}; + int total = 0; + + if (!sock || !buf || !size) + return -EINVAL; + + iov_iter_kvec(&msg.msg_iter, ITER_DEST, &iov, 1, size); + + usbip_dbg_xmit("enter\n"); + + do { + sock->sk->sk_allocation = GFP_NOIO; + sock->sk->sk_use_task_frag = false; + + result = sock_recvmsg(sock, &msg, MSG_WAITALL); + if (result <= 0) + goto err; + + total += result; + } while (msg_data_left(&msg)); + + if (usbip_dbg_flag_xmit) { + pr_debug("receiving....\n"); + usbip_dump_buffer(buf, size); + pr_debug("received, osize %d ret %d size %zd total %d\n", + size, result, msg_data_left(&msg), total); + } + + return total; + +err: + return result; +} +EXPORT_SYMBOL_GPL(usbip_recv); + +/* there may be more cases to tweak the flags. */ +static unsigned int tweak_transfer_flags(unsigned int flags) +{ + flags &= ~URB_NO_TRANSFER_DMA_MAP; + return flags; +} + +/* + * USBIP driver packs URB transfer flags in PDUs that are exchanged + * between Server (usbip_host) and Client (vhci_hcd). URB_* flags + * are internal to kernel and could change. Where as USBIP URB flags + * exchanged in PDUs are USBIP user API must not change. + * + * USBIP_URB* flags are exported as explicit API and client and server + * do mapping from kernel flags to USBIP_URB*. Details as follows: + * + * Client tx path (USBIP_CMD_SUBMIT): + * - Maps URB_* to USBIP_URB_* when it sends USBIP_CMD_SUBMIT packet. + * + * Server rx path (USBIP_CMD_SUBMIT): + * - Maps USBIP_URB_* to URB_* when it receives USBIP_CMD_SUBMIT packet. + * + * Flags aren't included in USBIP_CMD_UNLINK and USBIP_RET_SUBMIT packets + * and no special handling is needed for them in the following cases: + * - Server rx path (USBIP_CMD_UNLINK) + * - Client rx path & Server tx path (USBIP_RET_SUBMIT) + * + * Code paths: + * usbip_pack_pdu() is the common routine that handles packing pdu from + * urb and unpack pdu to an urb. + * + * usbip_pack_cmd_submit() and usbip_pack_ret_submit() handle + * USBIP_CMD_SUBMIT and USBIP_RET_SUBMIT respectively. + * + * usbip_map_urb_to_usbip() and usbip_map_usbip_to_urb() are used + * by usbip_pack_cmd_submit() and usbip_pack_ret_submit() to map + * flags. + */ + +struct urb_to_usbip_flags { + u32 urb_flag; + u32 usbip_flag; +}; + +#define NUM_USBIP_FLAGS 17 + +static const struct urb_to_usbip_flags flag_map[NUM_USBIP_FLAGS] = { + {URB_SHORT_NOT_OK, USBIP_URB_SHORT_NOT_OK}, + {URB_ISO_ASAP, USBIP_URB_ISO_ASAP}, + {URB_NO_TRANSFER_DMA_MAP, USBIP_URB_NO_TRANSFER_DMA_MAP}, + {URB_ZERO_PACKET, USBIP_URB_ZERO_PACKET}, + {URB_NO_INTERRUPT, USBIP_URB_NO_INTERRUPT}, + {URB_FREE_BUFFER, USBIP_URB_FREE_BUFFER}, + {URB_DIR_IN, USBIP_URB_DIR_IN}, + {URB_DIR_OUT, USBIP_URB_DIR_OUT}, + {URB_DIR_MASK, USBIP_URB_DIR_MASK}, + {URB_DMA_MAP_SINGLE, USBIP_URB_DMA_MAP_SINGLE}, + {URB_DMA_MAP_PAGE, USBIP_URB_DMA_MAP_PAGE}, + {URB_DMA_MAP_SG, USBIP_URB_DMA_MAP_SG}, + {URB_MAP_LOCAL, USBIP_URB_MAP_LOCAL}, + {URB_SETUP_MAP_SINGLE, USBIP_URB_SETUP_MAP_SINGLE}, + {URB_SETUP_MAP_LOCAL, USBIP_URB_SETUP_MAP_LOCAL}, + {URB_DMA_SG_COMBINED, USBIP_URB_DMA_SG_COMBINED}, + {URB_ALIGNED_TEMP_BUFFER, USBIP_URB_ALIGNED_TEMP_BUFFER}, +}; + +static unsigned int urb_to_usbip(unsigned int flags) +{ + unsigned int map_flags = 0; + int loop; + + for (loop = 0; loop < NUM_USBIP_FLAGS; loop++) { + if (flags & flag_map[loop].urb_flag) + map_flags |= flag_map[loop].usbip_flag; + } + + return map_flags; +} + +static unsigned int usbip_to_urb(unsigned int flags) +{ + unsigned int map_flags = 0; + int loop; + + for (loop = 0; loop < NUM_USBIP_FLAGS; loop++) { + if (flags & flag_map[loop].usbip_flag) + map_flags |= flag_map[loop].urb_flag; + } + + return map_flags; +} + +static void usbip_pack_cmd_submit(struct usbip_header *pdu, struct urb *urb, + int pack) +{ + struct usbip_header_cmd_submit *spdu = &pdu->u.cmd_submit; + + /* + * Some members are not still implemented in usbip. I hope this issue + * will be discussed when usbip is ported to other operating systems. + */ + if (pack) { + /* map after tweaking the urb flags */ + spdu->transfer_flags = urb_to_usbip(tweak_transfer_flags(urb->transfer_flags)); + spdu->transfer_buffer_length = urb->transfer_buffer_length; + spdu->start_frame = urb->start_frame; + spdu->number_of_packets = urb->number_of_packets; + spdu->interval = urb->interval; + } else { + urb->transfer_flags = usbip_to_urb(spdu->transfer_flags); + urb->transfer_buffer_length = spdu->transfer_buffer_length; + urb->start_frame = spdu->start_frame; + urb->number_of_packets = spdu->number_of_packets; + urb->interval = spdu->interval; + } +} + +static void usbip_pack_ret_submit(struct usbip_header *pdu, struct urb *urb, + int pack) +{ + struct usbip_header_ret_submit *rpdu = &pdu->u.ret_submit; + + if (pack) { + rpdu->status = urb->status; + rpdu->actual_length = urb->actual_length; + rpdu->start_frame = urb->start_frame; + rpdu->number_of_packets = urb->number_of_packets; + rpdu->error_count = urb->error_count; + } else { + urb->status = rpdu->status; + urb->actual_length = rpdu->actual_length; + urb->start_frame = rpdu->start_frame; + /* + * The number_of_packets field determines the length of + * iso_frame_desc[], which is a flexible array allocated + * at URB creation time. A response must never claim more + * packets than originally submitted; doing so would cause + * an out-of-bounds write in usbip_recv_iso() and + * usbip_pad_iso(). Clamp to zero on violation so both + * functions safely return early. + */ + if (rpdu->number_of_packets < 0 || + rpdu->number_of_packets > urb->number_of_packets) + rpdu->number_of_packets = 0; + urb->number_of_packets = rpdu->number_of_packets; + urb->error_count = rpdu->error_count; + } +} + +void usbip_pack_pdu(struct usbip_header *pdu, struct urb *urb, int cmd, + int pack) +{ + switch (cmd) { + case USBIP_CMD_SUBMIT: + usbip_pack_cmd_submit(pdu, urb, pack); + break; + case USBIP_RET_SUBMIT: + usbip_pack_ret_submit(pdu, urb, pack); + break; + default: + /* NOT REACHED */ + pr_err("unknown command\n"); + break; + } +} +EXPORT_SYMBOL_GPL(usbip_pack_pdu); + +static void correct_endian_basic(struct usbip_header_basic *base, int send) +{ + if (send) { + base->command = cpu_to_be32(base->command); + base->seqnum = cpu_to_be32(base->seqnum); + base->devid = cpu_to_be32(base->devid); + base->direction = cpu_to_be32(base->direction); + base->ep = cpu_to_be32(base->ep); + } else { + base->command = be32_to_cpu(base->command); + base->seqnum = be32_to_cpu(base->seqnum); + base->devid = be32_to_cpu(base->devid); + base->direction = be32_to_cpu(base->direction); + base->ep = be32_to_cpu(base->ep); + } +} + +static void correct_endian_cmd_submit(struct usbip_header_cmd_submit *pdu, + int send) +{ + if (send) { + pdu->transfer_flags = cpu_to_be32(pdu->transfer_flags); + + cpu_to_be32s(&pdu->transfer_buffer_length); + cpu_to_be32s(&pdu->start_frame); + cpu_to_be32s(&pdu->number_of_packets); + cpu_to_be32s(&pdu->interval); + } else { + pdu->transfer_flags = be32_to_cpu(pdu->transfer_flags); + + be32_to_cpus(&pdu->transfer_buffer_length); + be32_to_cpus(&pdu->start_frame); + be32_to_cpus(&pdu->number_of_packets); + be32_to_cpus(&pdu->interval); + } +} + +static void correct_endian_ret_submit(struct usbip_header_ret_submit *pdu, + int send) +{ + if (send) { + cpu_to_be32s(&pdu->status); + cpu_to_be32s(&pdu->actual_length); + cpu_to_be32s(&pdu->start_frame); + cpu_to_be32s(&pdu->number_of_packets); + cpu_to_be32s(&pdu->error_count); + } else { + be32_to_cpus(&pdu->status); + be32_to_cpus(&pdu->actual_length); + be32_to_cpus(&pdu->start_frame); + be32_to_cpus(&pdu->number_of_packets); + be32_to_cpus(&pdu->error_count); + } +} + +static void correct_endian_cmd_unlink(struct usbip_header_cmd_unlink *pdu, + int send) +{ + if (send) + pdu->seqnum = cpu_to_be32(pdu->seqnum); + else + pdu->seqnum = be32_to_cpu(pdu->seqnum); +} + +static void correct_endian_ret_unlink(struct usbip_header_ret_unlink *pdu, + int send) +{ + if (send) + cpu_to_be32s(&pdu->status); + else + be32_to_cpus(&pdu->status); +} + +void usbip_header_correct_endian(struct usbip_header *pdu, int send) +{ + __u32 cmd = 0; + + if (send) + cmd = pdu->base.command; + + correct_endian_basic(&pdu->base, send); + + if (!send) + cmd = pdu->base.command; + + switch (cmd) { + case USBIP_CMD_SUBMIT: + correct_endian_cmd_submit(&pdu->u.cmd_submit, send); + break; + case USBIP_RET_SUBMIT: + correct_endian_ret_submit(&pdu->u.ret_submit, send); + break; + case USBIP_CMD_UNLINK: + correct_endian_cmd_unlink(&pdu->u.cmd_unlink, send); + break; + case USBIP_RET_UNLINK: + correct_endian_ret_unlink(&pdu->u.ret_unlink, send); + break; + default: + /* NOT REACHED */ + pr_err("unknown command\n"); + break; + } +} +EXPORT_SYMBOL_GPL(usbip_header_correct_endian); + +static void usbip_iso_packet_correct_endian( + struct usbip_iso_packet_descriptor *iso, int send) +{ + /* does not need all members. but copy all simply. */ + if (send) { + iso->offset = cpu_to_be32(iso->offset); + iso->length = cpu_to_be32(iso->length); + iso->status = cpu_to_be32(iso->status); + iso->actual_length = cpu_to_be32(iso->actual_length); + } else { + iso->offset = be32_to_cpu(iso->offset); + iso->length = be32_to_cpu(iso->length); + iso->status = be32_to_cpu(iso->status); + iso->actual_length = be32_to_cpu(iso->actual_length); + } +} + +static void usbip_pack_iso(struct usbip_iso_packet_descriptor *iso, + struct usb_iso_packet_descriptor *uiso, int pack) +{ + if (pack) { + iso->offset = uiso->offset; + iso->length = uiso->length; + iso->status = uiso->status; + iso->actual_length = uiso->actual_length; + } else { + uiso->offset = iso->offset; + uiso->length = iso->length; + uiso->status = iso->status; + uiso->actual_length = iso->actual_length; + } +} + +/* must free buffer */ +struct usbip_iso_packet_descriptor* +usbip_alloc_iso_desc_pdu(struct urb *urb, ssize_t *bufflen) +{ + struct usbip_iso_packet_descriptor *iso; + int np = urb->number_of_packets; + ssize_t size = np * sizeof(*iso); + int i; + + iso = kzalloc(size, GFP_KERNEL); + if (!iso) + return NULL; + + for (i = 0; i < np; i++) { + usbip_pack_iso(&iso[i], &urb->iso_frame_desc[i], 1); + usbip_iso_packet_correct_endian(&iso[i], 1); + } + + *bufflen = size; + + return iso; +} +EXPORT_SYMBOL_GPL(usbip_alloc_iso_desc_pdu); + +/* some members of urb must be substituted before. */ +int usbip_recv_iso(struct usbip_device *ud, struct urb *urb) +{ + void *buff; + struct usbip_iso_packet_descriptor *iso; + int np = urb->number_of_packets; + int size; + int i; + int ret; + u32 total_length = 0; + + if (!usb_pipeisoc(urb->pipe)) + return 0; + + if (np <= 0 || np > USBIP_MAX_ISO_PACKETS) { + dev_err(&urb->dev->dev, + "recv iso: invalid number_of_packets %d\n", np); + /* + * usbip_pack_ret_submit() already set urb->number_of_packets + * from the wire. Zero it so processcompl() does not iterate + * OOB descriptors on the way out. + */ + urb->number_of_packets = 0; + return -EPROTO; + } + + size = np * sizeof(*iso); + + buff = kcalloc(np, sizeof(*iso), GFP_KERNEL); + if (!buff) + return -ENOMEM; + + ret = usbip_recv(ud->tcp_socket, buff, size); + if (ret != size) { + dev_err(&urb->dev->dev, "recv iso_frame_descriptor, %d\n", + ret); + kfree(buff); + + if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) + usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); + else + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + + return -EPIPE; + } + + iso = (struct usbip_iso_packet_descriptor *) buff; + for (i = 0; i < np; i++) { + usbip_iso_packet_correct_endian(&iso[i], 0); + usbip_pack_iso(&iso[i], &urb->iso_frame_desc[i], 0); + if (urb->iso_frame_desc[i].actual_length > + (unsigned int)urb->transfer_buffer_length) { + dev_err(&urb->dev->dev, + "recv iso: frame actual_length %u exceeds buffer %d\n", + urb->iso_frame_desc[i].actual_length, + urb->transfer_buffer_length); + kfree(buff); + return -EPROTO; + } + total_length += urb->iso_frame_desc[i].actual_length; + } + + kfree(buff); + + if (total_length != (u32)urb->actual_length) { + dev_err(&urb->dev->dev, + "total length of iso packets %u not equal to actual length of buffer %d\n", + total_length, urb->actual_length); + + if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) + usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); + else + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + + return -EPIPE; + } + + return ret; +} +EXPORT_SYMBOL_GPL(usbip_recv_iso); + +/* + * This functions restores the padding which was removed for optimizing + * the bandwidth during transfer over tcp/ip + * + * buffer and iso packets need to be stored and be in propeper endian in urb + * before calling this function + */ +void usbip_pad_iso(struct usbip_device *ud, struct urb *urb) +{ + int np = urb->number_of_packets; + int i; + int actualoffset = urb->actual_length; + + if (!usb_pipeisoc(urb->pipe)) + return; + + /* if no packets or length of data is 0, then nothing to unpack */ + if (np == 0 || urb->actual_length == 0) + return; + + /* + * if actual_length is transfer_buffer_length then no padding is + * present. + */ + if (urb->actual_length == urb->transfer_buffer_length) + return; + + /* + * loop over all packets from last to first (to prevent overwriting + * memory when padding) and move them into the proper place + */ + for (i = np-1; i > 0; i--) { + actualoffset -= urb->iso_frame_desc[i].actual_length; + + /* + * Validate source range: actualoffset can go negative + * via crafted actual_length values from the wire. + */ + if (actualoffset < 0 || + (unsigned int)actualoffset > + (unsigned int)urb->transfer_buffer_length || + urb->iso_frame_desc[i].actual_length > + (unsigned int)urb->transfer_buffer_length - + (unsigned int)actualoffset) { + dev_err(&urb->dev->dev, + "pad_iso: bad src off=%d len=%u bufsz=%d\n", + actualoffset, + urb->iso_frame_desc[i].actual_length, + urb->transfer_buffer_length); + return; + } + + /* + * Validate destination range: iso_frame_desc[i].offset + * is wire-supplied and must not exceed the buffer. + */ + if (urb->iso_frame_desc[i].offset > + (unsigned int)urb->transfer_buffer_length || + urb->iso_frame_desc[i].actual_length > + (unsigned int)urb->transfer_buffer_length - + urb->iso_frame_desc[i].offset) { + dev_err(&urb->dev->dev, + "pad_iso: bad dst off=%u len=%u bufsz=%d\n", + urb->iso_frame_desc[i].offset, + urb->iso_frame_desc[i].actual_length, + urb->transfer_buffer_length); + return; + } + + memmove(urb->transfer_buffer + urb->iso_frame_desc[i].offset, + urb->transfer_buffer + actualoffset, + urb->iso_frame_desc[i].actual_length); + } +} +EXPORT_SYMBOL_GPL(usbip_pad_iso); + +/* some members of urb must be substituted before. */ +int usbip_recv_xbuff(struct usbip_device *ud, struct urb *urb) +{ + struct scatterlist *sg; + int ret = 0; + int recv; + int size; + int copy; + int i; + + if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) { + /* the direction of urb must be OUT. */ + if (usb_pipein(urb->pipe)) + return 0; + + size = urb->transfer_buffer_length; + } else { + /* the direction of urb must be IN. */ + if (usb_pipeout(urb->pipe)) + return 0; + + size = urb->actual_length; + } + + /* no need to recv xbuff */ + if (!(size > 0)) + return 0; + + if (size > urb->transfer_buffer_length) + /* should not happen, probably malicious packet */ + goto error; + + if (urb->num_sgs) { + copy = size; + for_each_sg(urb->sg, sg, urb->num_sgs, i) { + int recv_size; + + if (copy < sg->length) + recv_size = copy; + else + recv_size = sg->length; + + recv = usbip_recv(ud->tcp_socket, sg_virt(sg), + recv_size); + + if (recv != recv_size) + goto error; + + copy -= recv; + ret += recv; + + if (!copy) + break; + } + + if (ret != size) + goto error; + } else { + ret = usbip_recv(ud->tcp_socket, urb->transfer_buffer, size); + if (ret != size) + goto error; + } + + return ret; + +error: + dev_err(&urb->dev->dev, "recv xbuf, %d\n", ret); + if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) + usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); + else + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + + return -EPIPE; +} +EXPORT_SYMBOL_GPL(usbip_recv_xbuff); + +static int __init usbip_core_init(void) +{ + return usbip_init_eh(); +} + +static void __exit usbip_core_exit(void) +{ + usbip_finish_eh(); + return; +} + +module_init(usbip_core_init); +module_exit(usbip_core_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/usbip/usbip_common.h b/drivers/usb/usbip/usbip_common.h new file mode 100644 index 000000000..be4c5e65a --- /dev/null +++ b/drivers/usb/usbip/usbip_common.h @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015-2016 Samsung Electronics + * Krzysztof Opasiak + */ + +#ifndef __USBIP_COMMON_H +#define __USBIP_COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef pr_fmt + +#ifdef DEBUG +#define pr_fmt(fmt) KBUILD_MODNAME ": %s:%d: " fmt, __func__, __LINE__ +#else +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#endif + +enum { + usbip_debug_xmit = (1 << 0), + usbip_debug_sysfs = (1 << 1), + usbip_debug_urb = (1 << 2), + usbip_debug_eh = (1 << 3), + + usbip_debug_stub_cmp = (1 << 8), + usbip_debug_stub_dev = (1 << 9), + usbip_debug_stub_rx = (1 << 10), + usbip_debug_stub_tx = (1 << 11), + + usbip_debug_vhci_rh = (1 << 8), + usbip_debug_vhci_hc = (1 << 9), + usbip_debug_vhci_rx = (1 << 10), + usbip_debug_vhci_tx = (1 << 11), + usbip_debug_vhci_sysfs = (1 << 12) +}; + +#define usbip_dbg_flag_xmit (usbip_debug_flag & usbip_debug_xmit) +#define usbip_dbg_flag_vhci_rh (usbip_debug_flag & usbip_debug_vhci_rh) +#define usbip_dbg_flag_vhci_hc (usbip_debug_flag & usbip_debug_vhci_hc) +#define usbip_dbg_flag_vhci_rx (usbip_debug_flag & usbip_debug_vhci_rx) +#define usbip_dbg_flag_vhci_tx (usbip_debug_flag & usbip_debug_vhci_tx) +#define usbip_dbg_flag_stub_rx (usbip_debug_flag & usbip_debug_stub_rx) +#define usbip_dbg_flag_stub_tx (usbip_debug_flag & usbip_debug_stub_tx) +#define usbip_dbg_flag_vhci_sysfs (usbip_debug_flag & usbip_debug_vhci_sysfs) + +extern unsigned long usbip_debug_flag; +extern struct device_attribute dev_attr_usbip_debug; + +#define usbip_dbg_with_flag(flag, fmt, args...) \ + do { \ + if (flag & usbip_debug_flag) \ + pr_debug(fmt, ##args); \ + } while (0) + +#define usbip_dbg_sysfs(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_sysfs, fmt , ##args) +#define usbip_dbg_xmit(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_xmit, fmt , ##args) +#define usbip_dbg_urb(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_urb, fmt , ##args) +#define usbip_dbg_eh(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_eh, fmt , ##args) + +#define usbip_dbg_vhci_rh(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_vhci_rh, fmt , ##args) +#define usbip_dbg_vhci_hc(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_vhci_hc, fmt , ##args) +#define usbip_dbg_vhci_rx(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_vhci_rx, fmt , ##args) +#define usbip_dbg_vhci_tx(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_vhci_tx, fmt , ##args) +#define usbip_dbg_vhci_sysfs(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_vhci_sysfs, fmt , ##args) + +#define usbip_dbg_stub_cmp(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_stub_cmp, fmt , ##args) +#define usbip_dbg_stub_rx(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_stub_rx, fmt , ##args) +#define usbip_dbg_stub_tx(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_stub_tx, fmt , ##args) + +/* + * USB/IP request headers + * + * Each request is transferred across the network to its counterpart, which + * facilitates the normal USB communication. The values contained in the headers + * are basically the same as in a URB. Currently, four request types are + * defined: + * + * - USBIP_CMD_SUBMIT: a USB request block, corresponds to usb_submit_urb() + * (client to server) + * + * - USBIP_RET_SUBMIT: the result of USBIP_CMD_SUBMIT + * (server to client) + * + * - USBIP_CMD_UNLINK: an unlink request of a pending USBIP_CMD_SUBMIT, + * corresponds to usb_unlink_urb() + * (client to server) + * + * - USBIP_RET_UNLINK: the result of USBIP_CMD_UNLINK + * (server to client) + * + */ +#define USBIP_CMD_SUBMIT 0x0001 +#define USBIP_CMD_UNLINK 0x0002 +#define USBIP_RET_SUBMIT 0x0003 +#define USBIP_RET_UNLINK 0x0004 + +#define USBIP_DIR_OUT 0x00 +#define USBIP_DIR_IN 0x01 + +/* + * Arbitrary limit for the maximum number of isochronous packets in an URB, + * compare for example the uhci_submit_isochronous function in + * drivers/usb/host/uhci-q.c + */ +#define USBIP_MAX_ISO_PACKETS 1024 + +/** + * struct usbip_header_basic - data pertinent to every request + * @command: the usbip request type + * @seqnum: sequential number that identifies requests; incremented per + * connection + * @devid: specifies a remote USB device uniquely instead of busnum and devnum; + * in the stub driver, this value is ((busnum << 16) | devnum) + * @direction: direction of the transfer + * @ep: endpoint number + */ +struct usbip_header_basic { + __u32 command; + __u32 seqnum; + __u32 devid; + __u32 direction; + __u32 ep; +} __packed; + +/** + * struct usbip_header_cmd_submit - USBIP_CMD_SUBMIT packet header + * @transfer_flags: URB flags + * @transfer_buffer_length: the data size for (in) or (out) transfer + * @start_frame: initial frame for isochronous or interrupt transfers + * @number_of_packets: number of isochronous packets + * @interval: maximum time for the request on the server-side host controller + * @setup: setup data for a control request + */ +struct usbip_header_cmd_submit { + __u32 transfer_flags; + __s32 transfer_buffer_length; + + /* it is difficult for usbip to sync frames (reserved only?) */ + __s32 start_frame; + __s32 number_of_packets; + __s32 interval; + + unsigned char setup[8]; +} __packed; + +/** + * struct usbip_header_ret_submit - USBIP_RET_SUBMIT packet header + * @status: return status of a non-iso request + * @actual_length: number of bytes transferred + * @start_frame: initial frame for isochronous or interrupt transfers + * @number_of_packets: number of isochronous packets + * @error_count: number of errors for isochronous transfers + */ +struct usbip_header_ret_submit { + __s32 status; + __s32 actual_length; + __s32 start_frame; + __s32 number_of_packets; + __s32 error_count; +} __packed; + +/** + * struct usbip_header_cmd_unlink - USBIP_CMD_UNLINK packet header + * @seqnum: the URB seqnum to unlink + */ +struct usbip_header_cmd_unlink { + __u32 seqnum; +} __packed; + +/** + * struct usbip_header_ret_unlink - USBIP_RET_UNLINK packet header + * @status: return status of the request + */ +struct usbip_header_ret_unlink { + __s32 status; +} __packed; + +/** + * struct usbip_header - common header for all usbip packets + * @base: the basic header + * @u: packet type dependent header + */ +struct usbip_header { + struct usbip_header_basic base; + + union { + struct usbip_header_cmd_submit cmd_submit; + struct usbip_header_ret_submit ret_submit; + struct usbip_header_cmd_unlink cmd_unlink; + struct usbip_header_ret_unlink ret_unlink; + } u; +} __packed; + +/* + * This is the same as usb_iso_packet_descriptor but packed for pdu. + */ +struct usbip_iso_packet_descriptor { + __u32 offset; + __u32 length; /* expected length */ + __u32 actual_length; + __u32 status; +} __packed; + +enum usbip_side { + USBIP_VHCI, + USBIP_STUB, + USBIP_VUDC, +}; + +/* event handler */ +#define USBIP_EH_SHUTDOWN (1 << 0) +#define USBIP_EH_BYE (1 << 1) +#define USBIP_EH_RESET (1 << 2) +#define USBIP_EH_UNUSABLE (1 << 3) + +#define SDEV_EVENT_REMOVED (USBIP_EH_SHUTDOWN | USBIP_EH_BYE) +#define SDEV_EVENT_DOWN (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +#define SDEV_EVENT_ERROR_TCP (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +#define SDEV_EVENT_ERROR_SUBMIT (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +#define SDEV_EVENT_ERROR_MALLOC (USBIP_EH_SHUTDOWN | USBIP_EH_UNUSABLE) + +#define VUDC_EVENT_REMOVED (USBIP_EH_SHUTDOWN | USBIP_EH_RESET | USBIP_EH_BYE) +#define VUDC_EVENT_DOWN (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +#define VUDC_EVENT_ERROR_TCP (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +/* catastrophic emulated usb error */ +#define VUDC_EVENT_ERROR_USB (USBIP_EH_SHUTDOWN | USBIP_EH_UNUSABLE) +#define VUDC_EVENT_ERROR_MALLOC (USBIP_EH_SHUTDOWN | USBIP_EH_UNUSABLE) + +#define VDEV_EVENT_REMOVED (USBIP_EH_SHUTDOWN | USBIP_EH_RESET | USBIP_EH_BYE) +#define VDEV_EVENT_DOWN (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +#define VDEV_EVENT_ERROR_TCP (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +#define VDEV_EVENT_ERROR_MALLOC (USBIP_EH_SHUTDOWN | USBIP_EH_UNUSABLE) + +/* a common structure for stub_device and vhci_device */ +struct usbip_device { + enum usbip_side side; + enum usbip_device_status status; + + /* lock for status */ + spinlock_t lock; + + /* mutex for synchronizing sysfs store paths */ + struct mutex sysfs_lock; + + int sockfd; + struct socket *tcp_socket; + + struct task_struct *tcp_rx; + struct task_struct *tcp_tx; + + unsigned long event; + wait_queue_head_t eh_waitq; + + struct eh_ops { + void (*shutdown)(struct usbip_device *); + void (*reset)(struct usbip_device *); + void (*unusable)(struct usbip_device *); + } eh_ops; + + struct kcov_common_handle_id kcov_handle; +}; + +#define kthread_get_run(threadfn, data, namefmt, ...) \ +({ \ + struct task_struct *__k \ + = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \ + if (!IS_ERR(__k)) { \ + get_task_struct(__k); \ + wake_up_process(__k); \ + } \ + __k; \ +}) + +/* usbip_common.c */ +void usbip_dump_urb(struct urb *purb); +void usbip_dump_header(struct usbip_header *pdu); + +int usbip_recv(struct socket *sock, void *buf, int size); + +void usbip_pack_pdu(struct usbip_header *pdu, struct urb *urb, int cmd, + int pack); +void usbip_header_correct_endian(struct usbip_header *pdu, int send); + +struct usbip_iso_packet_descriptor* +usbip_alloc_iso_desc_pdu(struct urb *urb, ssize_t *bufflen); + +/* some members of urb must be substituted before. */ +int usbip_recv_iso(struct usbip_device *ud, struct urb *urb); +void usbip_pad_iso(struct usbip_device *ud, struct urb *urb); +int usbip_recv_xbuff(struct usbip_device *ud, struct urb *urb); + +/* usbip_event.c */ +int usbip_init_eh(void); +void usbip_finish_eh(void); +int usbip_start_eh(struct usbip_device *ud); +void usbip_stop_eh(struct usbip_device *ud); +void usbip_event_add(struct usbip_device *ud, unsigned long event); +int usbip_event_happened(struct usbip_device *ud); +int usbip_in_eh(struct task_struct *task); + +static inline int interface_to_busnum(struct usb_interface *interface) +{ + struct usb_device *udev = interface_to_usbdev(interface); + + return udev->bus->busnum; +} + +static inline int interface_to_devnum(struct usb_interface *interface) +{ + struct usb_device *udev = interface_to_usbdev(interface); + + return udev->devnum; +} + +#endif /* __USBIP_COMMON_H */ diff --git a/drivers/usb/usbip/usbip_event.c b/drivers/usb/usbip/usbip_event.c new file mode 100644 index 000000000..0e00c2d00 --- /dev/null +++ b/drivers/usb/usbip/usbip_event.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015 Nobuo Iwata + */ + +#include +#include +#include +#include + +#include "usbip_common.h" + +struct usbip_event { + struct list_head node; + struct usbip_device *ud; +}; + +static DEFINE_SPINLOCK(event_lock); +static LIST_HEAD(event_list); + +static void set_event(struct usbip_device *ud, unsigned long event) +{ + unsigned long flags; + + spin_lock_irqsave(&ud->lock, flags); + ud->event |= event; + spin_unlock_irqrestore(&ud->lock, flags); +} + +static void unset_event(struct usbip_device *ud, unsigned long event) +{ + unsigned long flags; + + spin_lock_irqsave(&ud->lock, flags); + ud->event &= ~event; + spin_unlock_irqrestore(&ud->lock, flags); +} + +static struct usbip_device *get_event(void) +{ + struct usbip_event *ue = NULL; + struct usbip_device *ud = NULL; + unsigned long flags; + + spin_lock_irqsave(&event_lock, flags); + if (!list_empty(&event_list)) { + ue = list_first_entry(&event_list, struct usbip_event, node); + list_del(&ue->node); + } + spin_unlock_irqrestore(&event_lock, flags); + + if (ue) { + ud = ue->ud; + kfree(ue); + } + return ud; +} + +static struct task_struct *worker_context; + +static void event_handler(struct work_struct *work) +{ + struct usbip_device *ud; + + if (worker_context == NULL) { + worker_context = current; + } + + while ((ud = get_event()) != NULL) { + usbip_dbg_eh("pending event %lx\n", ud->event); + + mutex_lock(&ud->sysfs_lock); + /* + * NOTE: shutdown must come first. + * Shutdown the device. + */ + if (ud->event & USBIP_EH_SHUTDOWN) { + ud->eh_ops.shutdown(ud); + unset_event(ud, USBIP_EH_SHUTDOWN); + } + + /* Reset the device. */ + if (ud->event & USBIP_EH_RESET) { + ud->eh_ops.reset(ud); + unset_event(ud, USBIP_EH_RESET); + } + + /* Mark the device as unusable. */ + if (ud->event & USBIP_EH_UNUSABLE) { + ud->eh_ops.unusable(ud); + unset_event(ud, USBIP_EH_UNUSABLE); + } + mutex_unlock(&ud->sysfs_lock); + + wake_up(&ud->eh_waitq); + } +} + +int usbip_start_eh(struct usbip_device *ud) +{ + init_waitqueue_head(&ud->eh_waitq); + ud->event = 0; + return 0; +} +EXPORT_SYMBOL_GPL(usbip_start_eh); + +void usbip_stop_eh(struct usbip_device *ud) +{ + unsigned long pending = ud->event & ~USBIP_EH_BYE; + + if (!(ud->event & USBIP_EH_BYE)) + usbip_dbg_eh("usbip_eh stopping but not removed\n"); + + if (pending) + usbip_dbg_eh("usbip_eh waiting completion %lx\n", pending); + + wait_event_interruptible(ud->eh_waitq, !(ud->event & ~USBIP_EH_BYE)); + usbip_dbg_eh("usbip_eh has stopped\n"); +} +EXPORT_SYMBOL_GPL(usbip_stop_eh); + +#define WORK_QUEUE_NAME "usbip_event" + +static struct workqueue_struct *usbip_queue; +static DECLARE_WORK(usbip_work, event_handler); + +int usbip_init_eh(void) +{ + usbip_queue = create_singlethread_workqueue(WORK_QUEUE_NAME); + if (usbip_queue == NULL) { + pr_err("failed to create usbip_event\n"); + return -ENOMEM; + } + return 0; +} + +void usbip_finish_eh(void) +{ + destroy_workqueue(usbip_queue); + usbip_queue = NULL; +} + +void usbip_event_add(struct usbip_device *ud, unsigned long event) +{ + struct usbip_event *ue; + unsigned long flags; + + if (ud->event & USBIP_EH_BYE) + return; + + set_event(ud, event); + + spin_lock_irqsave(&event_lock, flags); + + list_for_each_entry_reverse(ue, &event_list, node) { + if (ue->ud == ud) + goto out; + } + + ue = kmalloc_obj(struct usbip_event, GFP_ATOMIC); + if (ue == NULL) + goto out; + + ue->ud = ud; + + list_add_tail(&ue->node, &event_list); + queue_work(usbip_queue, &usbip_work); + +out: + spin_unlock_irqrestore(&event_lock, flags); +} +EXPORT_SYMBOL_GPL(usbip_event_add); + +int usbip_event_happened(struct usbip_device *ud) +{ + int happened = 0; + unsigned long flags; + + spin_lock_irqsave(&ud->lock, flags); + if (ud->event != 0) + happened = 1; + spin_unlock_irqrestore(&ud->lock, flags); + + return happened; +} +EXPORT_SYMBOL_GPL(usbip_event_happened); + +int usbip_in_eh(struct task_struct *task) +{ + if (task == worker_context) + return 1; + + return 0; +} +EXPORT_SYMBOL_GPL(usbip_in_eh); diff --git a/drivers/usb/usbip/vhci.h b/drivers/usb/usbip/vhci.h new file mode 100644 index 000000000..5659dce15 --- /dev/null +++ b/drivers/usb/usbip/vhci.h @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015 Nobuo Iwata + */ + +#ifndef __USBIP_VHCI_H +#define __USBIP_VHCI_H + +#include +#include +#include +#include +#include +#include +#include +#include + +struct vhci_device { + struct usb_device *udev; + + /* + * devid specifies a remote usb device uniquely instead + * of combination of busnum and devnum. + */ + __u32 devid; + + /* speed of a remote device */ + enum usb_device_speed speed; + + /* vhci root-hub port to which this device is attached */ + __u32 rhport; + + struct usbip_device ud; + + /* lock for the below link lists */ + spinlock_t priv_lock; + + /* vhci_priv is linked to one of them. */ + struct list_head priv_tx; + struct list_head priv_rx; + + /* vhci_unlink is linked to one of them */ + struct list_head unlink_tx; + struct list_head unlink_rx; + + /* vhci_tx thread sleeps for this queue */ + wait_queue_head_t waitq_tx; +}; + +/* urb->hcpriv, use container_of() */ +struct vhci_priv { + unsigned long seqnum; + struct list_head list; + + struct vhci_device *vdev; + struct urb *urb; +}; + +struct vhci_unlink { + /* seqnum of this request */ + unsigned long seqnum; + + struct list_head list; + + /* seqnum of the unlink target */ + unsigned long unlink_seqnum; +}; + +enum hub_speed { + HUB_SPEED_HIGH = 0, + HUB_SPEED_SUPER, +}; + +/* Number of supported ports. Value has an upperbound of USB_MAXCHILDREN */ +#ifdef CONFIG_USBIP_VHCI_HC_PORTS +#define VHCI_HC_PORTS CONFIG_USBIP_VHCI_HC_PORTS +#else +#define VHCI_HC_PORTS 8 +#endif + +/* Each VHCI has 2 hubs (USB2 and USB3), each has VHCI_HC_PORTS ports */ +#define VHCI_PORTS (VHCI_HC_PORTS*2) + +#ifdef CONFIG_USBIP_VHCI_NR_HCS +#define VHCI_NR_HCS CONFIG_USBIP_VHCI_NR_HCS +#else +#define VHCI_NR_HCS 1 +#endif + +#define MAX_STATUS_NAME 16 + +struct vhci { + spinlock_t lock; + + struct platform_device *pdev; + + struct vhci_hcd *vhci_hcd_hs; + struct vhci_hcd *vhci_hcd_ss; +}; + +/* for usb_hcd.hcd_priv[0] */ +struct vhci_hcd { + struct vhci *vhci; + + u32 port_status[VHCI_HC_PORTS]; + + unsigned resuming:1; + unsigned long re_timeout; + + atomic_t seqnum; + + /* + * NOTE: + * wIndex shows the port number and begins from 1. + * But, the index of this array begins from 0. + */ + struct vhci_device vdev[VHCI_HC_PORTS]; +}; + +extern int vhci_num_controllers; +extern struct vhci *vhcis; +extern struct attribute_group vhci_attr_group; + +/* vhci_hcd.c */ +void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed); + +/* vhci_sysfs.c */ +int vhci_init_attr_group(void); +void vhci_finish_attr_group(void); + +/* vhci_rx.c */ +struct urb *pickup_urb_and_free_priv(struct vhci_device *vdev, __u32 seqnum); +int vhci_rx_loop(void *data); + +/* vhci_tx.c */ +int vhci_tx_loop(void *data); + +static inline __u32 port_to_rhport(__u32 port) +{ + return port % VHCI_HC_PORTS; +} + +static inline int port_to_pdev_nr(__u32 port) +{ + return port / VHCI_PORTS; +} + +static inline struct vhci_hcd *hcd_to_vhci_hcd(struct usb_hcd *hcd) +{ + return (struct vhci_hcd *) (hcd->hcd_priv); +} + +static inline struct device *hcd_dev(struct usb_hcd *hcd) +{ + return (hcd)->self.controller; +} + +static inline const char *hcd_name(struct usb_hcd *hcd) +{ + return (hcd)->self.bus_name; +} + +static inline struct usb_hcd *vhci_hcd_to_hcd(struct vhci_hcd *vhci_hcd) +{ + return container_of((void *) vhci_hcd, struct usb_hcd, hcd_priv); +} + +static inline struct vhci_hcd *vdev_to_vhci_hcd(struct vhci_device *vdev) +{ + return container_of((void *)(vdev - vdev->rhport), struct vhci_hcd, vdev); +} + +#endif /* __USBIP_VHCI_H */ diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c new file mode 100644 index 000000000..39e8faf4c --- /dev/null +++ b/drivers/usb/usbip/vhci_hcd.c @@ -0,0 +1,1587 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015-2016 Nobuo Iwata + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbip_common.h" +#include "vhci.h" + +#define DRIVER_AUTHOR "Takahiro Hirofuchi" +#define DRIVER_DESC "USB/IP 'Virtual' Host Controller (VHCI) Driver" + +/* + * TODO + * - update root hub emulation + * - move the emulation code to userland ? + * porting to other operating systems + * minimize kernel code + * - add suspend/resume code + * - clean up everything + */ + +/* See usb gadget dummy hcd */ + +static int vhci_hub_status(struct usb_hcd *hcd, char *buff); +static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buff, u16 wLength); +static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags); +static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status); +static int vhci_start(struct usb_hcd *vhci_hcd); +static void vhci_stop(struct usb_hcd *hcd); +static int vhci_get_frame_number(struct usb_hcd *hcd); + +static const char driver_name[] = "vhci_hcd"; +static const char driver_desc[] = "USB/IP Virtual Host Controller"; + +int vhci_num_controllers = VHCI_NR_HCS; +struct vhci *vhcis; + +static const char * const bit_desc[] = { + "CONNECTION", /*0*/ + "ENABLE", /*1*/ + "SUSPEND", /*2*/ + "OVER_CURRENT", /*3*/ + "RESET", /*4*/ + "L1", /*5*/ + "R6", /*6*/ + "R7", /*7*/ + "POWER", /*8*/ + "LOWSPEED", /*9*/ + "HIGHSPEED", /*10*/ + "PORT_TEST", /*11*/ + "INDICATOR", /*12*/ + "R13", /*13*/ + "R14", /*14*/ + "R15", /*15*/ + "C_CONNECTION", /*16*/ + "C_ENABLE", /*17*/ + "C_SUSPEND", /*18*/ + "C_OVER_CURRENT", /*19*/ + "C_RESET", /*20*/ + "C_L1", /*21*/ + "R22", /*22*/ + "R23", /*23*/ + "R24", /*24*/ + "R25", /*25*/ + "R26", /*26*/ + "R27", /*27*/ + "R28", /*28*/ + "R29", /*29*/ + "R30", /*30*/ + "R31", /*31*/ +}; + +static const char * const bit_desc_ss[] = { + "CONNECTION", /*0*/ + "ENABLE", /*1*/ + "SUSPEND", /*2*/ + "OVER_CURRENT", /*3*/ + "RESET", /*4*/ + "L1", /*5*/ + "R6", /*6*/ + "R7", /*7*/ + "R8", /*8*/ + "POWER", /*9*/ + "HIGHSPEED", /*10*/ + "PORT_TEST", /*11*/ + "INDICATOR", /*12*/ + "R13", /*13*/ + "R14", /*14*/ + "R15", /*15*/ + "C_CONNECTION", /*16*/ + "C_ENABLE", /*17*/ + "C_SUSPEND", /*18*/ + "C_OVER_CURRENT", /*19*/ + "C_RESET", /*20*/ + "C_BH_RESET", /*21*/ + "C_LINK_STATE", /*22*/ + "C_CONFIG_ERROR", /*23*/ + "R24", /*24*/ + "R25", /*25*/ + "R26", /*26*/ + "R27", /*27*/ + "R28", /*28*/ + "R29", /*29*/ + "R30", /*30*/ + "R31", /*31*/ +}; + +static void dump_port_status_diff(u32 prev_status, u32 new_status, bool usb3) +{ + int i = 0; + u32 bit = 1; + const char * const *desc = bit_desc; + + if (usb3) + desc = bit_desc_ss; + + pr_debug("status prev -> new: %08x -> %08x\n", prev_status, new_status); + while (bit) { + u32 prev = prev_status & bit; + u32 new = new_status & bit; + char change; + + if (!prev && new) + change = '+'; + else if (prev && !new) + change = '-'; + else + change = ' '; + + if (prev || new) { + pr_debug(" %c%s\n", change, desc[i]); + + if (bit == 1) /* USB_PORT_STAT_CONNECTION */ + pr_debug(" %c%s\n", change, "USB_PORT_STAT_SPEED_5GBPS"); + } + bit <<= 1; + i++; + } + pr_debug("\n"); +} + +void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed) +{ + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; + int rhport = vdev->rhport; + u32 status; + unsigned long flags; + + usbip_dbg_vhci_rh("rh_port_connect %d\n", rhport); + + spin_lock_irqsave(&vhci->lock, flags); + + status = vhci_hcd->port_status[rhport]; + + status |= USB_PORT_STAT_CONNECTION | (1 << USB_PORT_FEAT_C_CONNECTION); + + switch (speed) { + case USB_SPEED_HIGH: + status |= USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_LOW: + status |= USB_PORT_STAT_LOW_SPEED; + break; + default: + break; + } + + vhci_hcd->port_status[rhport] = status; + + spin_unlock_irqrestore(&vhci->lock, flags); + + usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci_hcd)); +} + +static void rh_port_disconnect(struct vhci_device *vdev) +{ + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; + int rhport = vdev->rhport; + u32 status; + unsigned long flags; + + usbip_dbg_vhci_rh("rh_port_disconnect %d\n", rhport); + + spin_lock_irqsave(&vhci->lock, flags); + + status = vhci_hcd->port_status[rhport]; + + status &= ~USB_PORT_STAT_CONNECTION; + status |= (1 << USB_PORT_FEAT_C_CONNECTION); + + vhci_hcd->port_status[rhport] = status; + + spin_unlock_irqrestore(&vhci->lock, flags); + usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci_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) + +/* + * 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. + * + * @buf: a bitmap to show which port status has been changed. + * bit 0: reserved + * bit 1: the status of port 0 has been changed. + * bit 2: the status of port 1 has been changed. + * ... + */ +static int vhci_hub_status(struct usb_hcd *hcd, char *buf) +{ + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + struct vhci *vhci = vhci_hcd->vhci; + int retval = DIV_ROUND_UP(VHCI_HC_PORTS + 1, 8); + int rhport; + int changed = 0; + unsigned long flags; + + memset(buf, 0, retval); + + spin_lock_irqsave(&vhci->lock, flags); + if (!HCD_HW_ACCESSIBLE(hcd)) { + usbip_dbg_vhci_rh("hw accessible flag not on?\n"); + goto done; + } + + /* check pseudo status register for each port */ + for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { + if ((vhci_hcd->port_status[rhport] & PORT_C_MASK)) { + /* The status of a port has been changed, */ + usbip_dbg_vhci_rh("port %d status changed\n", rhport); + + buf[(rhport + 1) / 8] |= 1 << (rhport + 1) % 8; + changed = 1; + } + } + + if ((hcd->state == HC_STATE_SUSPENDED) && (changed == 1)) + usb_hcd_resume_root_hub(hcd); + +done: + spin_unlock_irqrestore(&vhci->lock, flags); + return changed ? retval : 0; +} + +/* 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 = USB_DT_SS_HUB; + desc->bDescLength = 12; + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_COMMON_OCPM); + desc->bNbrPorts = VHCI_HC_PORTS; + 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) +{ + int width; + + memset(desc, 0, sizeof(*desc)); + desc->bDescriptorType = USB_DT_HUB; + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_COMMON_OCPM); + + desc->bNbrPorts = VHCI_HC_PORTS; + BUILD_BUG_ON(VHCI_HC_PORTS > USB_MAXCHILDREN); + width = desc->bNbrPorts / 8 + 1; + desc->bDescLength = USB_DT_HUB_NONVAR_SIZE + 2 * width; + memset(&desc->u.hs.DeviceRemovable[0], 0, width); + memset(&desc->u.hs.DeviceRemovable[width], 0xff, width); +} + +static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct vhci_hcd *vhci_hcd; + struct vhci *vhci; + int retval = 0; + int rhport = -1; + unsigned long flags; + bool invalid_rhport = false; + + u32 prev_port_status[VHCI_HC_PORTS]; + + if (!HCD_HW_ACCESSIBLE(hcd)) + return -ETIMEDOUT; + + /* + * NOTE: + * wIndex (bits 0-7) shows the port number and begins from 1? + */ + wIndex = ((__u8)(wIndex & 0x00ff)); + usbip_dbg_vhci_rh("typeReq %x wValue %x wIndex %x\n", typeReq, wValue, + wIndex); + + /* + * wIndex can be 0 for some request types (typeReq). rhport is + * in valid range when wIndex >= 1 and < VHCI_HC_PORTS. + * + * Reference port_status[] only with valid rhport when + * invalid_rhport is false. + */ + if (wIndex < 1 || wIndex > VHCI_HC_PORTS) { + invalid_rhport = true; + if (wIndex > VHCI_HC_PORTS) + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + } else { + rhport = wIndex - 1; + } + + vhci_hcd = hcd_to_vhci_hcd(hcd); + vhci = vhci_hcd->vhci; + + spin_lock_irqsave(&vhci->lock, flags); + + /* store old status and compare now and old later */ + if (usbip_dbg_flag_vhci_rh) { + if (!invalid_rhport) + memcpy(prev_port_status, vhci_hcd->port_status, + sizeof(prev_port_status)); + } + + switch (typeReq) { + case ClearHubFeature: + usbip_dbg_vhci_rh(" ClearHubFeature\n"); + break; + case ClearPortFeature: + if (invalid_rhport) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + goto error; + } + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + if (hcd->speed >= HCD_USB3) { + dev_err(hcd_dev(hcd), + "ClearPortFeature: USB_PORT_FEAT_SUSPEND req not supported for USB 3.0 roothub\n"); + goto error; + } + usbip_dbg_vhci_rh( + " ClearPortFeature: USB_PORT_FEAT_SUSPEND\n"); + if (vhci_hcd->port_status[rhport] & USB_PORT_STAT_SUSPEND) { + /* 20msec signaling */ + vhci_hcd->resuming = 1; + vhci_hcd->re_timeout = jiffies + msecs_to_jiffies(20); + } + break; + case USB_PORT_FEAT_POWER: + usbip_dbg_vhci_rh( + " ClearPortFeature: USB_PORT_FEAT_POWER\n"); + if (hcd->speed >= HCD_USB3) + vhci_hcd->port_status[rhport] &= ~USB_SS_PORT_STAT_POWER; + else + vhci_hcd->port_status[rhport] &= ~USB_PORT_STAT_POWER; + break; + default: + usbip_dbg_vhci_rh(" ClearPortFeature: default %x\n", + wValue); + if (wValue >= 32) + goto error; + vhci_hcd->port_status[rhport] &= ~(1 << wValue); + break; + } + break; + case GetHubDescriptor: + usbip_dbg_vhci_rh(" GetHubDescriptor\n"); + if (hcd->speed >= HCD_USB3 && + (wLength < USB_DT_SS_HUB_SIZE || + wValue != (USB_DT_SS_HUB << 8))) { + dev_err(hcd_dev(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: + usbip_dbg_vhci_rh(" GetHubStatus\n"); + *(__le32 *) buf = cpu_to_le32(0); + break; + case GetPortStatus: + usbip_dbg_vhci_rh(" GetPortStatus port %x\n", wIndex); + if (invalid_rhport) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + retval = -EPIPE; + goto error; + } + + /* we do not care about resume. */ + + /* whoever resets or resumes must GetPortStatus to + * complete it!! + */ + if (vhci_hcd->resuming && time_after(jiffies, vhci_hcd->re_timeout)) { + vhci_hcd->port_status[rhport] |= (1 << USB_PORT_FEAT_C_SUSPEND); + vhci_hcd->port_status[rhport] &= ~(1 << USB_PORT_FEAT_SUSPEND); + vhci_hcd->resuming = 0; + vhci_hcd->re_timeout = 0; + } + + if ((vhci_hcd->port_status[rhport] & (1 << USB_PORT_FEAT_RESET)) != + 0 && time_after(jiffies, vhci_hcd->re_timeout)) { + vhci_hcd->port_status[rhport] |= (1 << USB_PORT_FEAT_C_RESET); + vhci_hcd->port_status[rhport] &= ~(1 << USB_PORT_FEAT_RESET); + vhci_hcd->re_timeout = 0; + + /* + * A few drivers do usb reset during probe when + * the device could be in VDEV_ST_USED state + */ + if (vhci_hcd->vdev[rhport].ud.status == + VDEV_ST_NOTASSIGNED || + vhci_hcd->vdev[rhport].ud.status == + VDEV_ST_USED) { + usbip_dbg_vhci_rh( + " enable rhport %d (status %u)\n", + rhport, + vhci_hcd->vdev[rhport].ud.status); + vhci_hcd->port_status[rhport] |= + USB_PORT_STAT_ENABLE; + } + + if (hcd->speed < HCD_USB3) { + switch (vhci_hcd->vdev[rhport].speed) { + case USB_SPEED_HIGH: + vhci_hcd->port_status[rhport] |= + USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_LOW: + vhci_hcd->port_status[rhport] |= + USB_PORT_STAT_LOW_SPEED; + break; + default: + dev_err(hcd_dev(hcd), "vhci_device speed not set\n"); + break; + } + } + } + ((__le16 *) buf)[0] = cpu_to_le16(vhci_hcd->port_status[rhport]); + ((__le16 *) buf)[1] = + cpu_to_le16(vhci_hcd->port_status[rhport] >> 16); + + usbip_dbg_vhci_rh(" GetPortStatus bye %x %x\n", ((u16 *)buf)[0], + ((u16 *)buf)[1]); + break; + case SetHubFeature: + usbip_dbg_vhci_rh(" SetHubFeature\n"); + retval = -EPIPE; + break; + case SetPortFeature: + switch (wValue) { + case USB_PORT_FEAT_LINK_STATE: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_LINK_STATE\n"); + if (hcd->speed < HCD_USB3) { + dev_err(hcd_dev(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: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_U1_TIMEOUT\n"); + fallthrough; + case USB_PORT_FEAT_U2_TIMEOUT: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_U2_TIMEOUT\n"); + /* TODO: add suspend/resume support! */ + if (hcd->speed < HCD_USB3) { + dev_err(hcd_dev(hcd), + "USB_PORT_FEAT_U1/2_TIMEOUT req not supported for USB 2.0 roothub\n"); + goto error; + } + break; + case USB_PORT_FEAT_SUSPEND: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_SUSPEND\n"); + /* Applicable only for USB2.0 hub */ + if (hcd->speed >= HCD_USB3) { + dev_err(hcd_dev(hcd), + "USB_PORT_FEAT_SUSPEND req not supported for USB 3.0 roothub\n"); + goto error; + } + + if (invalid_rhport) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + goto error; + } + + vhci_hcd->port_status[rhport] |= USB_PORT_STAT_SUSPEND; + break; + case USB_PORT_FEAT_POWER: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_POWER\n"); + if (invalid_rhport) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + goto error; + } + if (hcd->speed >= HCD_USB3) + vhci_hcd->port_status[rhport] |= USB_SS_PORT_STAT_POWER; + else + vhci_hcd->port_status[rhport] |= USB_PORT_STAT_POWER; + break; + case USB_PORT_FEAT_BH_PORT_RESET: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_BH_PORT_RESET\n"); + if (invalid_rhport) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + goto error; + } + /* Applicable only for USB3.0 hub */ + if (hcd->speed < HCD_USB3) { + dev_err(hcd_dev(hcd), + "USB_PORT_FEAT_BH_PORT_RESET req not supported for USB 2.0 roothub\n"); + goto error; + } + fallthrough; + case USB_PORT_FEAT_RESET: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_RESET\n"); + if (invalid_rhport) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + goto error; + } + /* if it's already enabled, disable */ + if (hcd->speed >= HCD_USB3) { + vhci_hcd->port_status[rhport] = 0; + vhci_hcd->port_status[rhport] = + (USB_SS_PORT_STAT_POWER | + USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_RESET); + } else if (vhci_hcd->port_status[rhport] & USB_PORT_STAT_ENABLE) { + vhci_hcd->port_status[rhport] &= ~(USB_PORT_STAT_ENABLE + | USB_PORT_STAT_LOW_SPEED + | USB_PORT_STAT_HIGH_SPEED); + } + + /* 50msec reset signaling */ + vhci_hcd->re_timeout = jiffies + msecs_to_jiffies(50); + fallthrough; + default: + usbip_dbg_vhci_rh(" SetPortFeature: default %d\n", + wValue); + if (invalid_rhport) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + goto error; + } + if (wValue >= 32) + goto error; + if (hcd->speed >= HCD_USB3) { + if ((vhci_hcd->port_status[rhport] & + USB_SS_PORT_STAT_POWER) != 0) { + vhci_hcd->port_status[rhport] |= (1 << wValue); + } + } else + if ((vhci_hcd->port_status[rhport] & + USB_PORT_STAT_POWER) != 0) { + vhci_hcd->port_status[rhport] |= (1 << wValue); + } + } + break; + case GetPortErrorCount: + usbip_dbg_vhci_rh(" GetPortErrorCount\n"); + if (hcd->speed < HCD_USB3) { + dev_err(hcd_dev(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: + usbip_dbg_vhci_rh(" SetHubDepth\n"); + if (hcd->speed < HCD_USB3) { + dev_err(hcd_dev(hcd), + "SetHubDepth req not supported for USB 2.0 roothub\n"); + goto error; + } + break; + default: + dev_err(hcd_dev(hcd), + "default hub control req: %04x v%04x i%04x l%d\n", + typeReq, wValue, wIndex, wLength); +error: + /* "protocol stall" on error */ + retval = -EPIPE; + } + + if (usbip_dbg_flag_vhci_rh) { + dev_dbg(hcd_dev(hcd), "%s port %d\n", __func__, rhport); + /* Only dump valid port status */ + if (!invalid_rhport) { + dump_port_status_diff(prev_port_status[rhport], + vhci_hcd->port_status[rhport], + hcd->speed >= HCD_USB3); + } + } + usbip_dbg_vhci_rh(" bye\n"); + + spin_unlock_irqrestore(&vhci->lock, flags); + + if (!invalid_rhport && + (vhci_hcd->port_status[rhport] & PORT_C_MASK) != 0) { + usb_hcd_poll_rh_status(hcd); + } + + return retval; +} + +static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev) +{ + struct vhci_priv *priv; + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + unsigned long flags; + + priv = kzalloc_obj(struct vhci_priv, GFP_ATOMIC); + if (!priv) { + usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_MALLOC); + return; + } + + spin_lock_irqsave(&vdev->priv_lock, flags); + + priv->seqnum = (u32)atomic_inc_return(&vhci_hcd->seqnum); + if (priv->seqnum == 0xffff) + dev_info(&urb->dev->dev, "seqnum max\n"); + + priv->vdev = vdev; + priv->urb = urb; + + urb->hcpriv = (void *) priv; + + list_add_tail(&priv->list, &vdev->priv_tx); + + wake_up(&vdev->waitq_tx); + spin_unlock_irqrestore(&vdev->priv_lock, flags); +} + +static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) +{ + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + struct vhci *vhci = vhci_hcd->vhci; + struct device *dev = &urb->dev->dev; + u8 portnum = urb->dev->portnum; + int ret = 0; + struct vhci_device *vdev; + unsigned long flags; + + if (portnum > VHCI_HC_PORTS) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", portnum); + return -ENODEV; + } + vdev = &vhci_hcd->vdev[portnum-1]; + + if (!urb->transfer_buffer && !urb->num_sgs && + urb->transfer_buffer_length) { + dev_dbg(dev, "Null URB transfer buffer\n"); + return -EINVAL; + } + + spin_lock_irqsave(&vhci->lock, flags); + + if (urb->status != -EINPROGRESS) { + dev_err(dev, "URB already unlinked!, status %d\n", urb->status); + spin_unlock_irqrestore(&vhci->lock, flags); + return urb->status; + } + + /* refuse enqueue for dead connection */ + spin_lock(&vdev->ud.lock); + if (vdev->ud.status == VDEV_ST_NULL || + vdev->ud.status == VDEV_ST_ERROR) { + dev_err(dev, "enqueue for inactive port %d\n", vdev->rhport); + spin_unlock(&vdev->ud.lock); + spin_unlock_irqrestore(&vhci->lock, flags); + return -ENODEV; + } + spin_unlock(&vdev->ud.lock); + + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) + goto no_need_unlink; + + /* + * The enumeration process is as follows; + * + * 1. Get_Descriptor request to DevAddrs(0) EndPoint(0) + * to get max packet length of default pipe + * + * 2. Set_Address request to DevAddr(0) EndPoint(0) + * + */ + if (usb_pipedevice(urb->pipe) == 0) { + struct usb_device *old; + __u8 type = usb_pipetype(urb->pipe); + struct usb_ctrlrequest *ctrlreq = + (struct usb_ctrlrequest *) urb->setup_packet; + + if (type != PIPE_CONTROL || !ctrlreq) { + dev_err(dev, "invalid request to devnum 0\n"); + ret = -EINVAL; + goto no_need_xmit; + } + + old = vdev->udev; + switch (ctrlreq->bRequest) { + case USB_REQ_SET_ADDRESS: + /* set_address may come when a device is reset */ + dev_info(dev, "SetAddress Request (%d) to port %d\n", + ctrlreq->wValue, vdev->rhport); + + vdev->udev = usb_get_dev(urb->dev); + /* + * NOTE: A similar operation has been done via + * USB_REQ_GET_DESCRIPTOR handler below, which is + * supposed to always precede USB_REQ_SET_ADDRESS. + * + * It's not entirely clear if operating on a different + * usb_device instance here is a real possibility, + * otherwise this call and vdev->udev assignment above + * should be dropped. + */ + dev_pm_syscore_device(&vdev->udev->dev, true); + usb_put_dev(old); + + spin_lock(&vdev->ud.lock); + vdev->ud.status = VDEV_ST_USED; + spin_unlock(&vdev->ud.lock); + + if (urb->status == -EINPROGRESS) { + /* This request is successfully completed. */ + /* If not -EINPROGRESS, possibly unlinked. */ + urb->status = 0; + } + + goto no_need_xmit; + + case USB_REQ_GET_DESCRIPTOR: + if (ctrlreq->wValue == cpu_to_le16(USB_DT_DEVICE << 8)) + usbip_dbg_vhci_hc( + "Not yet?:Get_Descriptor to device 0 (get max pipe size)\n"); + + vdev->udev = usb_get_dev(urb->dev); + /* + * Set syscore PM flag for the virtually attached + * devices to ensure they will not enter suspend on + * the client side. + * + * Note this doesn't have any impact on the physical + * devices attached to the host system on the server + * side, hence there is no need to undo the operation + * on disconnect. + */ + dev_pm_syscore_device(&vdev->udev->dev, true); + usb_put_dev(old); + goto out; + + default: + /* NOT REACHED */ + dev_err(dev, + "invalid request to devnum 0 bRequest %u, wValue %u\n", + ctrlreq->bRequest, + ctrlreq->wValue); + ret = -EINVAL; + goto no_need_xmit; + } + + } + +out: + vhci_tx_urb(urb, vdev); + spin_unlock_irqrestore(&vhci->lock, flags); + + return 0; + +no_need_xmit: + usb_hcd_unlink_urb_from_ep(hcd, urb); +no_need_unlink: + if (!ret) { + /* usb_hcd_giveback_urb() should be called with + * irqs disabled + */ + spin_unlock(&vhci->lock); + usb_hcd_giveback_urb(hcd, urb, urb->status); + spin_lock(&vhci->lock); + } + spin_unlock_irqrestore(&vhci->lock, flags); + return ret; +} + +/* + * vhci_rx gives back the urb after receiving the reply of the urb. If an + * unlink pdu is sent or not, vhci_rx receives a normal return pdu and gives + * back its urb. For the driver unlinking the urb, the content of the urb is + * not important, but the calling to its completion handler is important; the + * completion of unlinking is notified by the completion handler. + * + * + * CLIENT SIDE + * + * - When vhci_hcd receives RET_SUBMIT, + * + * - case 1a). the urb of the pdu is not unlinking. + * - normal case + * => just give back the urb + * + * - case 1b). the urb of the pdu is unlinking. + * - usbip.ko will return a reply of the unlinking request. + * => give back the urb now and go to case 2b). + * + * - When vhci_hcd receives RET_UNLINK, + * + * - case 2a). a submit request is still pending in vhci_hcd. + * - urb was really pending in usbip.ko and urb_unlink_urb() was + * completed there. + * => free a pending submit request + * => notify unlink completeness by giving back the urb + * + * - case 2b). a submit request is *not* pending in vhci_hcd. + * - urb was already given back to the core driver. + * => do not give back the urb + * + * + * SERVER SIDE + * + * - When usbip receives CMD_UNLINK, + * + * - case 3a). the urb of the unlink request is now in submission. + * => do usb_unlink_urb(). + * => after the unlink is completed, send RET_UNLINK. + * + * - case 3b). the urb of the unlink request is not in submission. + * - may be already completed or never be received + * => send RET_UNLINK + * + */ +static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + struct vhci *vhci = vhci_hcd->vhci; + struct vhci_priv *priv; + struct vhci_device *vdev; + unsigned long flags; + + spin_lock_irqsave(&vhci->lock, flags); + + priv = urb->hcpriv; + if (!priv) { + /* URB was never linked! or will be soon given back by + * vhci_rx. */ + spin_unlock_irqrestore(&vhci->lock, flags); + return -EIDRM; + } + + { + int ret = 0; + + ret = usb_hcd_check_unlink_urb(hcd, urb, status); + if (ret) { + spin_unlock_irqrestore(&vhci->lock, flags); + return ret; + } + } + + /* send unlink request here? */ + vdev = priv->vdev; + + if (!vdev->ud.tcp_socket) { + /* tcp connection is closed */ + spin_lock(&vdev->priv_lock); + + list_del(&priv->list); + kfree(priv); + urb->hcpriv = NULL; + + spin_unlock(&vdev->priv_lock); + + /* + * If tcp connection is alive, we have sent CMD_UNLINK. + * vhci_rx will receive RET_UNLINK and give back the URB. + * Otherwise, we give back it here. + */ + usb_hcd_unlink_urb_from_ep(hcd, urb); + + spin_unlock_irqrestore(&vhci->lock, flags); + usb_hcd_giveback_urb(hcd, urb, urb->status); + spin_lock_irqsave(&vhci->lock, flags); + + } else { + /* tcp connection is alive */ + struct vhci_unlink *unlink; + + spin_lock(&vdev->priv_lock); + + /* setup CMD_UNLINK pdu */ + unlink = kzalloc_obj(struct vhci_unlink, GFP_ATOMIC); + if (!unlink) { + spin_unlock(&vdev->priv_lock); + spin_unlock_irqrestore(&vhci->lock, flags); + usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_MALLOC); + return -ENOMEM; + } + + unlink->seqnum = atomic_inc_return(&vhci_hcd->seqnum); + if (unlink->seqnum == 0xffff) + dev_info(hcd_dev(hcd), "seqnum max\n"); + + unlink->unlink_seqnum = priv->seqnum; + + /* send cmd_unlink and try to cancel the pending URB in the + * peer */ + list_add_tail(&unlink->list, &vdev->unlink_tx); + wake_up(&vdev->waitq_tx); + + spin_unlock(&vdev->priv_lock); + } + + spin_unlock_irqrestore(&vhci->lock, flags); + + usbip_dbg_vhci_hc("leave\n"); + return 0; +} + +static void vhci_cleanup_unlink_list(struct vhci_device *vdev, + struct list_head *unlink_list) +{ + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci_hcd); + struct vhci *vhci = vhci_hcd->vhci; + struct vhci_unlink *unlink, *tmp; + unsigned long flags; + + spin_lock_irqsave(&vhci->lock, flags); + spin_lock(&vdev->priv_lock); + + list_for_each_entry_safe(unlink, tmp, unlink_list, list) { + struct urb *urb; + + urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum); + if (!urb) { + list_del(&unlink->list); + kfree(unlink); + continue; + } + + urb->status = -ENODEV; + + usb_hcd_unlink_urb_from_ep(hcd, urb); + + list_del(&unlink->list); + + spin_unlock(&vdev->priv_lock); + spin_unlock_irqrestore(&vhci->lock, flags); + + usb_hcd_giveback_urb(hcd, urb, urb->status); + + spin_lock_irqsave(&vhci->lock, flags); + spin_lock(&vdev->priv_lock); + + kfree(unlink); + } + + spin_unlock(&vdev->priv_lock); + spin_unlock_irqrestore(&vhci->lock, flags); +} + +static void vhci_device_unlink_cleanup(struct vhci_device *vdev) +{ + /* give back URB of unsent unlink request */ + vhci_cleanup_unlink_list(vdev, &vdev->unlink_tx); + + /* give back URB of unanswered unlink request */ + vhci_cleanup_unlink_list(vdev, &vdev->unlink_rx); +} + +/* + * The important thing is that only one context begins cleanup. + * This is why error handling and cleanup become simple. + * We do not want to consider race condition as possible. + */ +static void vhci_shutdown_connection(struct usbip_device *ud) +{ + struct vhci_device *vdev = container_of(ud, struct vhci_device, ud); + struct usb_hcd *hcd = vhci_hcd_to_hcd(vdev_to_vhci_hcd(vdev)); + + /* need this? see stub_dev.c */ + if (ud->tcp_socket) { + dev_dbg(hcd_dev(hcd), "shutdown tcp_socket %d\n", ud->sockfd); + kernel_sock_shutdown(ud->tcp_socket, SHUT_RDWR); + } + + /* kill threads related to this sdev */ + if (vdev->ud.tcp_rx) { + kthread_stop_put(vdev->ud.tcp_rx); + vdev->ud.tcp_rx = NULL; + } + if (vdev->ud.tcp_tx) { + kthread_stop_put(vdev->ud.tcp_tx); + vdev->ud.tcp_tx = NULL; + } + dev_info(hcd_dev(hcd), "stop threads\n"); + + /* active connection is closed */ + if (vdev->ud.tcp_socket) { + sockfd_put(vdev->ud.tcp_socket); + vdev->ud.tcp_socket = NULL; + vdev->ud.sockfd = -1; + } + dev_info(hcd_dev(hcd), "release socket\n"); + + vhci_device_unlink_cleanup(vdev); + + /* + * rh_port_disconnect() is a trigger of ... + * usb_disable_device(): + * disable all the endpoints for a USB device. + * usb_disable_endpoint(): + * disable endpoints. pending urbs are unlinked(dequeued). + * + * NOTE: After calling rh_port_disconnect(), the USB device drivers of a + * detached device should release used urbs in a cleanup function (i.e. + * xxx_disconnect()). Therefore, vhci_hcd does not need to release + * pushed urbs and their private data in this function. + * + * NOTE: vhci_dequeue() must be considered carefully. When shutting down + * a connection, vhci_shutdown_connection() expects vhci_dequeue() + * gives back pushed urbs and frees their private data by request of + * the cleanup function of a USB driver. When unlinking a urb with an + * active connection, vhci_dequeue() does not give back the urb which + * is actually given back by vhci_rx after receiving its return pdu. + * + */ + rh_port_disconnect(vdev); + + dev_info(hcd_dev(hcd), "disconnect device\n"); +} + +static void vhci_device_reset(struct usbip_device *ud) +{ + struct vhci_device *vdev = container_of(ud, struct vhci_device, ud); + struct usb_device *old = vdev->udev; + unsigned long flags; + + spin_lock_irqsave(&ud->lock, flags); + + vdev->speed = 0; + vdev->devid = 0; + + vdev->udev = NULL; + usb_put_dev(old); + + if (ud->tcp_socket) { + sockfd_put(ud->tcp_socket); + ud->tcp_socket = NULL; + ud->sockfd = -1; + } + ud->status = VDEV_ST_NULL; + + spin_unlock_irqrestore(&ud->lock, flags); +} + +static void vhci_device_unusable(struct usbip_device *ud) +{ + unsigned long flags; + + spin_lock_irqsave(&ud->lock, flags); + ud->status = VDEV_ST_ERROR; + spin_unlock_irqrestore(&ud->lock, flags); +} + +static void vhci_device_init(struct vhci_device *vdev) +{ + memset(vdev, 0, sizeof(struct vhci_device)); + + vdev->ud.side = USBIP_VHCI; + vdev->ud.status = VDEV_ST_NULL; + spin_lock_init(&vdev->ud.lock); + mutex_init(&vdev->ud.sysfs_lock); + + INIT_LIST_HEAD(&vdev->priv_rx); + INIT_LIST_HEAD(&vdev->priv_tx); + INIT_LIST_HEAD(&vdev->unlink_tx); + INIT_LIST_HEAD(&vdev->unlink_rx); + spin_lock_init(&vdev->priv_lock); + + init_waitqueue_head(&vdev->waitq_tx); + + vdev->ud.eh_ops.shutdown = vhci_shutdown_connection; + vdev->ud.eh_ops.reset = vhci_device_reset; + vdev->ud.eh_ops.unusable = vhci_device_unusable; + + usbip_start_eh(&vdev->ud); +} + +static int hcd_name_to_id(const char *name) +{ + char *c; + long val; + int ret; + + c = strchr(name, '.'); + if (c == NULL) + return 0; + + ret = kstrtol(c+1, 10, &val); + if (ret < 0) + return ret; + + return val; +} + +static int vhci_setup(struct usb_hcd *hcd) +{ + struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller)); + + if (usb_hcd_is_primary_hcd(hcd)) { + vhci->vhci_hcd_hs = hcd_to_vhci_hcd(hcd); + vhci->vhci_hcd_hs->vhci = vhci; + /* + * Mark the first roothub as being USB 2.0. + * The USB 3.0 roothub will be registered later by + * vhci_hcd_probe() + */ + hcd->speed = HCD_USB2; + hcd->self.root_hub->speed = USB_SPEED_HIGH; + } else { + vhci->vhci_hcd_ss = hcd_to_vhci_hcd(hcd); + vhci->vhci_hcd_ss->vhci = vhci; + hcd->speed = HCD_USB31; + hcd->self.root_hub->speed = USB_SPEED_SUPER_PLUS; + } + + /* accept arbitrarily long scatter-gather lists */ + hcd->self.sg_tablesize = ~0; + hcd->self.no_sg_constraint = 1; + + return 0; +} + +static int vhci_start(struct usb_hcd *hcd) +{ + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + int id, rhport; + int err; + + usbip_dbg_vhci_hc("enter vhci_start\n"); + + if (usb_hcd_is_primary_hcd(hcd)) + spin_lock_init(&vhci_hcd->vhci->lock); + + /* initialize private data of usb_hcd */ + + for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { + struct vhci_device *vdev = &vhci_hcd->vdev[rhport]; + + vhci_device_init(vdev); + vdev->rhport = rhport; + } + + atomic_set(&vhci_hcd->seqnum, 0); + + hcd->power_budget = 0; /* no limit */ + hcd->uses_new_polling = 1; + +#ifdef CONFIG_USB_OTG + hcd->self.otg_port = 1; +#endif + + id = hcd_name_to_id(hcd_name(hcd)); + if (id < 0) { + dev_err(hcd_dev(hcd), "invalid vhci name %s\n", hcd_name(hcd)); + return -EINVAL; + } + + /* vhci_hcd is now ready to be controlled through sysfs */ + if (id == 0 && usb_hcd_is_primary_hcd(hcd)) { + err = vhci_init_attr_group(); + if (err) { + dev_err(hcd_dev(hcd), "init attr group failed, err = %d\n", err); + return err; + } + err = sysfs_create_group(&hcd_dev(hcd)->kobj, &vhci_attr_group); + if (err) { + dev_err(hcd_dev(hcd), "create sysfs files failed, err = %d\n", err); + vhci_finish_attr_group(); + return err; + } + dev_info(hcd_dev(hcd), "created sysfs %s\n", hcd_name(hcd)); + } + + return 0; +} + +static void vhci_stop(struct usb_hcd *hcd) +{ + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + int id, rhport; + + usbip_dbg_vhci_hc("stop VHCI controller\n"); + + /* 1. remove the userland interface of vhci_hcd */ + id = hcd_name_to_id(hcd_name(hcd)); + if (id == 0 && usb_hcd_is_primary_hcd(hcd)) { + sysfs_remove_group(&hcd_dev(hcd)->kobj, &vhci_attr_group); + vhci_finish_attr_group(); + } + + /* 2. shutdown all the ports of vhci_hcd */ + for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { + struct vhci_device *vdev = &vhci_hcd->vdev[rhport]; + + usbip_event_add(&vdev->ud, VDEV_EVENT_REMOVED); + usbip_stop_eh(&vdev->ud); + } +} + +static int vhci_get_frame_number(struct usb_hcd *hcd) +{ + dev_err_ratelimited(&hcd->self.root_hub->dev, "Not yet implemented\n"); + return 0; +} + +#ifdef CONFIG_PM + +/* FIXME: suspend/resume */ +static int vhci_bus_suspend(struct usb_hcd *hcd) +{ + struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller)); + unsigned long flags; + + dev_dbg(&hcd->self.root_hub->dev, "%s\n", __func__); + + spin_lock_irqsave(&vhci->lock, flags); + hcd->state = HC_STATE_SUSPENDED; + spin_unlock_irqrestore(&vhci->lock, flags); + + return 0; +} + +static int vhci_bus_resume(struct usb_hcd *hcd) +{ + struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller)); + int rc = 0; + unsigned long flags; + + dev_dbg(&hcd->self.root_hub->dev, "%s\n", __func__); + + spin_lock_irqsave(&vhci->lock, flags); + if (!HCD_HW_ACCESSIBLE(hcd)) + rc = -ESHUTDOWN; + else + hcd->state = HC_STATE_RUNNING; + spin_unlock_irqrestore(&vhci->lock, flags); + + return rc; +} + +#else + +#define vhci_bus_suspend NULL +#define vhci_bus_resume NULL +#endif + +/* Change a group of bulk endpoints to support multiple stream IDs */ +static int vhci_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) +{ + dev_dbg(&hcd->self.root_hub->dev, "vhci_alloc_streams not implemented\n"); + return 0; +} + +/* Reverts a group of bulk endpoints back to not using stream IDs. */ +static int vhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + gfp_t mem_flags) +{ + dev_dbg(&hcd->self.root_hub->dev, "vhci_free_streams not implemented\n"); + return 0; +} + +static const struct hc_driver vhci_hc_driver = { + .description = driver_name, + .product_desc = driver_desc, + .hcd_priv_size = sizeof(struct vhci_hcd), + + .flags = HCD_USB31 | HCD_SHARED, + + .reset = vhci_setup, + .start = vhci_start, + .stop = vhci_stop, + + .urb_enqueue = vhci_urb_enqueue, + .urb_dequeue = vhci_urb_dequeue, + + .get_frame_number = vhci_get_frame_number, + + .hub_status_data = vhci_hub_status, + .hub_control = vhci_hub_control, + .bus_suspend = vhci_bus_suspend, + .bus_resume = vhci_bus_resume, + + .alloc_streams = vhci_alloc_streams, + .free_streams = vhci_free_streams, +}; + +static int vhci_hcd_probe(struct platform_device *pdev) +{ + struct vhci *vhci = *((void **)dev_get_platdata(&pdev->dev)); + struct usb_hcd *hcd_hs; + struct usb_hcd *hcd_ss; + int ret; + + usbip_dbg_vhci_hc("name %s id %d\n", pdev->name, pdev->id); + + /* + * Allocate and initialize hcd. + * Our private data is also allocated automatically. + */ + hcd_hs = usb_create_hcd(&vhci_hc_driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd_hs) + return dev_err_probe(&pdev->dev, -ENOMEM, "create primary hcd failed\n"); + + hcd_hs->has_tt = 1; + + /* + * Finish generic HCD structure initialization and register. + * Call the driver's reset() and start() routines. + */ + ret = usb_add_hcd(hcd_hs, 0, 0); + if (ret) { + dev_err_probe(&pdev->dev, ret, "usb_add_hcd hs failed\n"); + goto put_usb2_hcd; + } + + hcd_ss = usb_create_shared_hcd(&vhci_hc_driver, &pdev->dev, + dev_name(&pdev->dev), hcd_hs); + if (!hcd_ss) { + ret = dev_err_probe(&pdev->dev, -ENOMEM, "create shared hcd failed\n"); + goto remove_usb2_hcd; + } + + ret = usb_add_hcd(hcd_ss, 0, 0); + if (ret) { + dev_err_probe(&pdev->dev, ret, "usb_add_hcd ss failed\n"); + goto put_usb3_hcd; + } + + usbip_dbg_vhci_hc("bye\n"); + return 0; + +put_usb3_hcd: + usb_put_hcd(hcd_ss); +remove_usb2_hcd: + usb_remove_hcd(hcd_hs); +put_usb2_hcd: + usb_put_hcd(hcd_hs); + vhci->vhci_hcd_hs = NULL; + vhci->vhci_hcd_ss = NULL; + return ret; +} + +static void vhci_hcd_remove(struct platform_device *pdev) +{ + struct vhci *vhci = *((void **)dev_get_platdata(&pdev->dev)); + + /* + * Disconnects the root hub, + * then reverses the effects of usb_add_hcd(), + * invoking the HCD's stop() methods. + */ + usb_remove_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_ss)); + usb_put_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_ss)); + + usb_remove_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_hs)); + usb_put_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_hs)); + + vhci->vhci_hcd_hs = NULL; + vhci->vhci_hcd_ss = NULL; +} + +#ifdef CONFIG_PM + +/* what should happen for USB/IP under suspend/resume? */ +static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct usb_hcd *hcd; + struct vhci *vhci; + int rhport; + int connected = 0; + int ret = 0; + unsigned long flags; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + hcd = platform_get_drvdata(pdev); + if (!hcd) + return 0; + + vhci = *((void **)dev_get_platdata(hcd->self.controller)); + + spin_lock_irqsave(&vhci->lock, flags); + + for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { + if (vhci->vhci_hcd_hs->port_status[rhport] & + USB_PORT_STAT_CONNECTION) + connected += 1; + + if (vhci->vhci_hcd_ss->port_status[rhport] & + USB_PORT_STAT_CONNECTION) + connected += 1; + } + + spin_unlock_irqrestore(&vhci->lock, flags); + + if (connected > 0) { + dev_info(&pdev->dev, + "We have %d active connection%s. Do not suspend.\n", + connected, str_plural(connected)); + ret = -EBUSY; + } else { + dev_info(&pdev->dev, "suspend vhci_hcd"); + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + } + + return ret; +} + +static int vhci_hcd_resume(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + hcd = platform_get_drvdata(pdev); + if (!hcd) + return 0; + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + usb_hcd_poll_rh_status(hcd); + + return 0; +} + +#else + +#define vhci_hcd_suspend NULL +#define vhci_hcd_resume NULL + +#endif + +static struct platform_driver vhci_driver = { + .probe = vhci_hcd_probe, + .remove = vhci_hcd_remove, + .suspend = vhci_hcd_suspend, + .resume = vhci_hcd_resume, + .driver = { + .name = driver_name, + }, +}; + +static void del_platform_devices(void) +{ + int i; + + for (i = 0; i < vhci_num_controllers; i++) { + platform_device_unregister(vhcis[i].pdev); + vhcis[i].pdev = NULL; + } + sysfs_remove_link(&platform_bus.kobj, driver_name); +} + +static int __init vhci_hcd_init(void) +{ + int i, ret; + + if (usb_disabled()) + return -ENODEV; + + if (vhci_num_controllers < 1) + vhci_num_controllers = 1; + + vhcis = kzalloc_objs(struct vhci, vhci_num_controllers); + if (vhcis == NULL) + return -ENOMEM; + + ret = platform_driver_register(&vhci_driver); + if (ret) + goto err_driver_register; + + for (i = 0; i < vhci_num_controllers; i++) { + void *vhci = &vhcis[i]; + struct platform_device_info pdevinfo = { + .name = driver_name, + .id = i, + .data = &vhci, + .size_data = sizeof(void *), + }; + + vhcis[i].pdev = platform_device_register_full(&pdevinfo); + ret = PTR_ERR_OR_ZERO(vhcis[i].pdev); + if (ret < 0) { + while (i--) + platform_device_unregister(vhcis[i].pdev); + goto err_add_hcd; + } + } + + return 0; + +err_add_hcd: + platform_driver_unregister(&vhci_driver); +err_driver_register: + kfree(vhcis); + return ret; +} + +static void __exit vhci_hcd_exit(void) +{ + del_platform_devices(); + platform_driver_unregister(&vhci_driver); + kfree(vhcis); +} + +module_init(vhci_hcd_init); +module_exit(vhci_hcd_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/usbip/vhci_rx.c b/drivers/usb/usbip/vhci_rx.c new file mode 100644 index 000000000..a678e7c89 --- /dev/null +++ b/drivers/usb/usbip/vhci_rx.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + */ + +#include +#include + +#include "usbip_common.h" +#include "vhci.h" + +/* get URB from transmitted urb queue. caller must hold vdev->priv_lock */ +struct urb *pickup_urb_and_free_priv(struct vhci_device *vdev, __u32 seqnum) +{ + struct vhci_priv *priv, *tmp; + struct urb *urb = NULL; + int status; + + list_for_each_entry_safe(priv, tmp, &vdev->priv_rx, list) { + if (priv->seqnum != seqnum) + continue; + + urb = priv->urb; + status = urb->status; + + usbip_dbg_vhci_rx("find urb seqnum %u\n", seqnum); + + switch (status) { + case -ENOENT: + fallthrough; + case -ECONNRESET: + dev_dbg(&urb->dev->dev, + "urb seq# %u was unlinked %ssynchronously\n", + seqnum, status == -ENOENT ? "" : "a"); + break; + case -EINPROGRESS: + /* no info output */ + break; + default: + dev_dbg(&urb->dev->dev, + "urb seq# %u may be in a error, status %d\n", + seqnum, status); + } + + list_del(&priv->list); + kfree(priv); + urb->hcpriv = NULL; + + break; + } + + return urb; +} + +static void vhci_recv_ret_submit(struct vhci_device *vdev, + struct usbip_header *pdu) +{ + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; + struct usbip_device *ud = &vdev->ud; + struct urb *urb; + unsigned long flags; + + spin_lock_irqsave(&vdev->priv_lock, flags); + urb = pickup_urb_and_free_priv(vdev, pdu->base.seqnum); + spin_unlock_irqrestore(&vdev->priv_lock, flags); + + if (!urb) { + pr_err("cannot find a urb of seqnum %u max seqnum %u\n", + pdu->base.seqnum, + atomic_read(&vhci_hcd->seqnum)); + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + return; + } + + /* unpack the pdu to a urb */ + usbip_pack_pdu(pdu, urb, USBIP_RET_SUBMIT, 0); + + /* recv transfer buffer */ + if (usbip_recv_xbuff(ud, urb) < 0) { + urb->status = -EPROTO; + goto error; + } + + /* recv iso_packet_descriptor */ + if (usbip_recv_iso(ud, urb) < 0) { + urb->status = -EPROTO; + goto error; + } + + /* restore the padding in iso packets */ + usbip_pad_iso(ud, urb); + +error: + if (usbip_dbg_flag_vhci_rx) + usbip_dump_urb(urb); + + if (urb->num_sgs) + urb->transfer_flags &= ~URB_DMA_MAP_SG; + + usbip_dbg_vhci_rx("now giveback urb %u\n", pdu->base.seqnum); + + spin_lock_irqsave(&vhci->lock, flags); + usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci_hcd), urb); + spin_unlock_irqrestore(&vhci->lock, flags); + + usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci_hcd), urb, urb->status); + + usbip_dbg_vhci_rx("Leave\n"); +} + +static struct vhci_unlink *dequeue_pending_unlink(struct vhci_device *vdev, + struct usbip_header *pdu) +{ + struct vhci_unlink *unlink, *tmp; + unsigned long flags; + + spin_lock_irqsave(&vdev->priv_lock, flags); + + list_for_each_entry_safe(unlink, tmp, &vdev->unlink_rx, list) { + pr_info("unlink->seqnum %lu\n", unlink->seqnum); + if (unlink->seqnum == pdu->base.seqnum) { + usbip_dbg_vhci_rx("found pending unlink, %lu\n", + unlink->seqnum); + list_del(&unlink->list); + + spin_unlock_irqrestore(&vdev->priv_lock, flags); + return unlink; + } + } + + spin_unlock_irqrestore(&vdev->priv_lock, flags); + + return NULL; +} + +static void vhci_recv_ret_unlink(struct vhci_device *vdev, + struct usbip_header *pdu) +{ + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; + struct vhci_unlink *unlink; + struct urb *urb; + unsigned long flags; + + usbip_dump_header(pdu); + + unlink = dequeue_pending_unlink(vdev, pdu); + if (!unlink) { + pr_info("cannot find the pending unlink %u\n", + pdu->base.seqnum); + return; + } + + spin_lock_irqsave(&vdev->priv_lock, flags); + urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum); + spin_unlock_irqrestore(&vdev->priv_lock, flags); + + if (!urb) { + /* + * I get the result of a unlink request. But, it seems that I + * already received the result of its submit result and gave + * back the URB. + */ + pr_info("the urb (seqnum %u) was already given back\n", + pdu->base.seqnum); + } else { + usbip_dbg_vhci_rx("now giveback urb %u\n", pdu->base.seqnum); + + /* If unlink is successful, status is -ECONNRESET */ + urb->status = pdu->u.ret_unlink.status; + pr_info("urb->status %d\n", urb->status); + + spin_lock_irqsave(&vhci->lock, flags); + usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci_hcd), urb); + spin_unlock_irqrestore(&vhci->lock, flags); + + usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci_hcd), urb, urb->status); + } + + kfree(unlink); +} + +static int vhci_priv_tx_empty(struct vhci_device *vdev) +{ + int empty = 0; + unsigned long flags; + + spin_lock_irqsave(&vdev->priv_lock, flags); + empty = list_empty(&vdev->priv_rx); + spin_unlock_irqrestore(&vdev->priv_lock, flags); + + return empty; +} + +/* recv a pdu */ +static void vhci_rx_pdu(struct usbip_device *ud) +{ + int ret; + struct usbip_header pdu; + struct vhci_device *vdev = container_of(ud, struct vhci_device, ud); + + usbip_dbg_vhci_rx("Enter\n"); + + memset(&pdu, 0, sizeof(pdu)); + + /* receive a pdu header */ + ret = usbip_recv(ud->tcp_socket, &pdu, sizeof(pdu)); + if (ret < 0) { + if (ret == -ECONNRESET) + pr_info("connection reset by peer\n"); + else if (ret == -EAGAIN) { + /* ignore if connection was idle */ + if (vhci_priv_tx_empty(vdev)) + return; + pr_info("connection timed out with pending urbs\n"); + } else if (ret != -ERESTARTSYS) + pr_info("xmit failed %d\n", ret); + + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + return; + } + if (ret == 0) { + pr_info("connection closed"); + usbip_event_add(ud, VDEV_EVENT_DOWN); + return; + } + if (ret != sizeof(pdu)) { + pr_err("received pdu size is %d, should be %d\n", ret, + (unsigned int)sizeof(pdu)); + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + return; + } + + usbip_header_correct_endian(&pdu, 0); + + if (usbip_dbg_flag_vhci_rx) + usbip_dump_header(&pdu); + + switch (pdu.base.command) { + case USBIP_RET_SUBMIT: + vhci_recv_ret_submit(vdev, &pdu); + break; + case USBIP_RET_UNLINK: + vhci_recv_ret_unlink(vdev, &pdu); + break; + default: + /* NOT REACHED */ + pr_err("unknown pdu %u\n", pdu.base.command); + usbip_dump_header(&pdu); + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + break; + } +} + +int vhci_rx_loop(void *data) +{ + struct usbip_device *ud = data; + + while (!kthread_should_stop()) { + if (usbip_event_happened(ud)) + break; + + kcov_remote_start_common(ud->kcov_handle); + vhci_rx_pdu(ud); + kcov_remote_stop(); + } + + return 0; +} diff --git a/drivers/usb/usbip/vhci_sysfs.c b/drivers/usb/usbip/vhci_sysfs.c new file mode 100644 index 000000000..a7ede6fb3 --- /dev/null +++ b/drivers/usb/usbip/vhci_sysfs.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015-2016 Nobuo Iwata + */ + +#include +#include +#include +#include +#include + +/* Hardening for Spectre-v1 */ +#include + +#include "usbip_common.h" +#include "vhci.h" + +/* TODO: refine locking ?*/ + +/* + * output example: + * hub port sta spd dev sockfd local_busid + * hs 0000 004 000 00000000 000003 1-2.3 + * ................................................ + * ss 0008 004 000 00000000 000004 2-3.4 + * ................................................ + * + * Output includes socket fd instead of socket pointer address to avoid + * leaking kernel memory address in: + * /sys/devices/platform/vhci_hcd.0/status and in debug output. + * The socket pointer address is not used at the moment and it was made + * visible as a convenient way to find IP address from socket pointer + * address by looking up /proc/net/{tcp,tcp6}. As this opens a security + * hole, the change is made to use sockfd instead. + * + */ +static void port_show_vhci(char **out, int hub, int port, struct vhci_device *vdev) +{ + if (hub == HUB_SPEED_HIGH) + *out += sprintf(*out, "hs %04u %03u ", + port, vdev->ud.status); + else /* hub == HUB_SPEED_SUPER */ + *out += sprintf(*out, "ss %04u %03u ", + port, vdev->ud.status); + + if (vdev->ud.status == VDEV_ST_USED) { + *out += sprintf(*out, "%03u %08x ", + vdev->speed, vdev->devid); + *out += sprintf(*out, "%06u %s", + vdev->ud.sockfd, + dev_name(&vdev->udev->dev)); + + } else { + *out += sprintf(*out, "000 00000000 "); + *out += sprintf(*out, "000000 0-0"); + } + + *out += sprintf(*out, "\n"); +} + +static ssize_t status_show_not_ready(int pdev_nr, char *out) +{ + char *s = out; + int i = 0; + + for (i = 0; i < VHCI_HC_PORTS; i++) { + out += sprintf(out, "hs %04u %03u ", + (pdev_nr * VHCI_PORTS) + i, + VDEV_ST_NOTASSIGNED); + out += sprintf(out, "000 00000000 0000000000000000 0-0"); + out += sprintf(out, "\n"); + } + + for (i = 0; i < VHCI_HC_PORTS; i++) { + out += sprintf(out, "ss %04u %03u ", + (pdev_nr * VHCI_PORTS) + VHCI_HC_PORTS + i, + VDEV_ST_NOTASSIGNED); + out += sprintf(out, "000 00000000 0000000000000000 0-0"); + out += sprintf(out, "\n"); + } + return out - s; +} + +/* Sysfs entry to show port status */ +static ssize_t status_show_vhci(int pdev_nr, char *out) +{ + struct platform_device *pdev = vhcis[pdev_nr].pdev; + struct vhci *vhci; + struct usb_hcd *hcd; + struct vhci_hcd *vhci_hcd; + char *s = out; + int i; + unsigned long flags; + + if (!pdev || !out) { + usbip_dbg_vhci_sysfs("show status error\n"); + return 0; + } + + hcd = platform_get_drvdata(pdev); + + if (!hcd) { + usbip_dbg_vhci_sysfs("show status error (hcd is NULL)\n"); + return status_show_not_ready(pdev_nr, out); + } + + vhci_hcd = hcd_to_vhci_hcd(hcd); + vhci = vhci_hcd->vhci; + + spin_lock_irqsave(&vhci->lock, flags); + + for (i = 0; i < VHCI_HC_PORTS; i++) { + struct vhci_device *vdev = &vhci->vhci_hcd_hs->vdev[i]; + + spin_lock(&vdev->ud.lock); + port_show_vhci(&out, HUB_SPEED_HIGH, + pdev_nr * VHCI_PORTS + i, vdev); + spin_unlock(&vdev->ud.lock); + } + + for (i = 0; i < VHCI_HC_PORTS; i++) { + struct vhci_device *vdev = &vhci->vhci_hcd_ss->vdev[i]; + + spin_lock(&vdev->ud.lock); + port_show_vhci(&out, HUB_SPEED_SUPER, + pdev_nr * VHCI_PORTS + VHCI_HC_PORTS + i, vdev); + spin_unlock(&vdev->ud.lock); + } + + spin_unlock_irqrestore(&vhci->lock, flags); + + return out - s; +} + +static int status_name_to_id(const char *name) +{ + char *c; + long val; + int ret; + + c = strchr(name, '.'); + if (c == NULL) + return 0; + + ret = kstrtol(c+1, 10, &val); + if (ret < 0) + return ret; + + return val; +} + +static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *out) +{ + char *s = out; + int pdev_nr; + + out += sprintf(out, + "hub port sta spd dev sockfd local_busid\n"); + + pdev_nr = status_name_to_id(attr->attr.name); + if (pdev_nr < 0) + out += status_show_not_ready(pdev_nr, out); + else + out += status_show_vhci(pdev_nr, out); + + return out - s; +} + +static ssize_t nports_show(struct device *dev, struct device_attribute *attr, + char *out) +{ + char *s = out; + + /* + * Half the ports are for SPEED_HIGH and half for SPEED_SUPER, + * thus the * 2. + */ + out += sprintf(out, "%d\n", VHCI_PORTS * vhci_num_controllers); + return out - s; +} +static DEVICE_ATTR_RO(nports); + +/* Sysfs entry to shutdown a virtual connection */ +static int vhci_port_disconnect(struct vhci_hcd *vhci_hcd, __u32 rhport) +{ + struct vhci_device *vdev = &vhci_hcd->vdev[rhport]; + struct vhci *vhci = vhci_hcd->vhci; + unsigned long flags; + + usbip_dbg_vhci_sysfs("enter\n"); + + mutex_lock(&vdev->ud.sysfs_lock); + + /* lock */ + spin_lock_irqsave(&vhci->lock, flags); + spin_lock(&vdev->ud.lock); + + if (vdev->ud.status == VDEV_ST_NULL) { + pr_err("not connected %d\n", vdev->ud.status); + + /* unlock */ + spin_unlock(&vdev->ud.lock); + spin_unlock_irqrestore(&vhci->lock, flags); + mutex_unlock(&vdev->ud.sysfs_lock); + + return -EINVAL; + } + + /* unlock */ + spin_unlock(&vdev->ud.lock); + spin_unlock_irqrestore(&vhci->lock, flags); + + usbip_event_add(&vdev->ud, VDEV_EVENT_DOWN); + + mutex_unlock(&vdev->ud.sysfs_lock); + + return 0; +} + +static int valid_port(__u32 *pdev_nr, __u32 *rhport) +{ + if (*pdev_nr >= vhci_num_controllers) { + pr_err("pdev %u\n", *pdev_nr); + return 0; + } + *pdev_nr = array_index_nospec(*pdev_nr, vhci_num_controllers); + + if (*rhport >= VHCI_HC_PORTS) { + pr_err("rhport %u\n", *rhport); + return 0; + } + *rhport = array_index_nospec(*rhport, VHCI_HC_PORTS); + + return 1; +} + +static ssize_t detach_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + __u32 port = 0, pdev_nr = 0, rhport = 0; + struct usb_hcd *hcd; + struct vhci_hcd *vhci_hcd; + int ret; + + if (kstrtoint(buf, 10, &port) < 0) + return -EINVAL; + + pdev_nr = port_to_pdev_nr(port); + rhport = port_to_rhport(port); + + if (!valid_port(&pdev_nr, &rhport)) + return -EINVAL; + + hcd = platform_get_drvdata(vhcis[pdev_nr].pdev); + if (hcd == NULL) { + dev_err(dev, "port is not ready %u\n", port); + return -EAGAIN; + } + + usbip_dbg_vhci_sysfs("rhport %d\n", rhport); + + if ((port / VHCI_HC_PORTS) % 2) + vhci_hcd = hcd_to_vhci_hcd(hcd)->vhci->vhci_hcd_ss; + else + vhci_hcd = hcd_to_vhci_hcd(hcd)->vhci->vhci_hcd_hs; + + ret = vhci_port_disconnect(vhci_hcd, rhport); + if (ret < 0) + return -EINVAL; + + usbip_dbg_vhci_sysfs("Leave\n"); + + return count; +} +static DEVICE_ATTR_WO(detach); + +static int valid_args(__u32 *pdev_nr, __u32 *rhport, + enum usb_device_speed speed) +{ + if (!valid_port(pdev_nr, rhport)) { + return 0; + } + + switch (speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + case USB_SPEED_WIRELESS: + case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + break; + default: + pr_err("Failed attach request for unsupported USB speed: %s\n", + usb_speed_string(speed)); + return 0; + } + + return 1; +} + +/* Sysfs entry to establish a virtual connection */ +/* + * To start a new USB/IP attachment, a userland program needs to setup a TCP + * connection and then write its socket descriptor with remote device + * information into this sysfs file. + * + * A remote device is virtually attached to the root-hub port of @rhport with + * @speed. @devid is embedded into a request to specify the remote device in a + * server host. + * + * write() returns 0 on success, else negative errno. + */ +static ssize_t attach_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct socket *socket; + int sockfd = 0; + __u32 port = 0, pdev_nr = 0, rhport = 0, devid = 0, speed = 0; + struct usb_hcd *hcd; + struct vhci_hcd *vhci_hcd; + struct vhci_device *vdev; + struct vhci *vhci; + int err; + unsigned long flags; + struct task_struct *tcp_rx = NULL; + struct task_struct *tcp_tx = NULL; + + /* + * @rhport: port number of vhci_hcd + * @sockfd: socket descriptor of an established TCP connection + * @devid: unique device identifier in a remote host + * @speed: usb device speed in a remote host + */ + if (sscanf(buf, "%u %u %u %u", &port, &sockfd, &devid, &speed) != 4) + return -EINVAL; + pdev_nr = port_to_pdev_nr(port); + rhport = port_to_rhport(port); + + usbip_dbg_vhci_sysfs("port(%u) pdev(%d) rhport(%u)\n", + port, pdev_nr, rhport); + usbip_dbg_vhci_sysfs("sockfd(%u) devid(%u) speed(%u)\n", + sockfd, devid, speed); + + /* check received parameters */ + if (!valid_args(&pdev_nr, &rhport, speed)) + return -EINVAL; + + hcd = platform_get_drvdata(vhcis[pdev_nr].pdev); + if (hcd == NULL) { + dev_err(dev, "port %d is not ready\n", port); + return -EAGAIN; + } + + vhci_hcd = hcd_to_vhci_hcd(hcd); + vhci = vhci_hcd->vhci; + + if (speed >= USB_SPEED_SUPER) + vdev = &vhci->vhci_hcd_ss->vdev[rhport]; + else + vdev = &vhci->vhci_hcd_hs->vdev[rhport]; + + mutex_lock(&vdev->ud.sysfs_lock); + + /* Extract socket from fd. */ + socket = sockfd_lookup(sockfd, &err); + if (!socket) { + dev_err(dev, "failed to lookup sock"); + err = -EINVAL; + goto unlock_mutex; + } + if (socket->type != SOCK_STREAM) { + dev_err(dev, "Expecting SOCK_STREAM - found %d", + socket->type); + sockfd_put(socket); + err = -EINVAL; + goto unlock_mutex; + } + + /* create threads before locking */ + tcp_rx = kthread_create(vhci_rx_loop, &vdev->ud, "vhci_rx"); + if (IS_ERR(tcp_rx)) { + sockfd_put(socket); + err = -EINVAL; + goto unlock_mutex; + } + tcp_tx = kthread_create(vhci_tx_loop, &vdev->ud, "vhci_tx"); + if (IS_ERR(tcp_tx)) { + kthread_stop(tcp_rx); + sockfd_put(socket); + err = -EINVAL; + goto unlock_mutex; + } + + /* get task structs now */ + get_task_struct(tcp_rx); + get_task_struct(tcp_tx); + + /* now begin lock until setting vdev status set */ + spin_lock_irqsave(&vhci->lock, flags); + spin_lock(&vdev->ud.lock); + + if (vdev->ud.status != VDEV_ST_NULL) { + /* end of the lock */ + spin_unlock(&vdev->ud.lock); + spin_unlock_irqrestore(&vhci->lock, flags); + + sockfd_put(socket); + kthread_stop_put(tcp_rx); + kthread_stop_put(tcp_tx); + + dev_err(dev, "port %d already used\n", rhport); + /* + * Will be retried from userspace + * if there's another free port. + */ + err = -EBUSY; + goto unlock_mutex; + } + + dev_info(dev, "pdev(%u) rhport(%u) sockfd(%d)\n", + pdev_nr, rhport, sockfd); + dev_info(dev, "devid(%u) speed(%u) speed_str(%s)\n", + devid, speed, usb_speed_string(speed)); + + vdev->devid = devid; + vdev->speed = speed; + vdev->ud.sockfd = sockfd; + vdev->ud.tcp_socket = socket; + vdev->ud.tcp_rx = tcp_rx; + vdev->ud.tcp_tx = tcp_tx; + vdev->ud.status = VDEV_ST_NOTASSIGNED; + vdev->ud.kcov_handle = kcov_common_handle(); + + spin_unlock(&vdev->ud.lock); + spin_unlock_irqrestore(&vhci->lock, flags); + /* end the lock */ + + wake_up_process(vdev->ud.tcp_rx); + wake_up_process(vdev->ud.tcp_tx); + + rh_port_connect(vdev, speed); + + dev_info(dev, "Device attached\n"); + + mutex_unlock(&vdev->ud.sysfs_lock); + + return count; + +unlock_mutex: + mutex_unlock(&vdev->ud.sysfs_lock); + return err; +} +static DEVICE_ATTR_WO(attach); + +#define MAX_STATUS_NAME 16 + +struct status_attr { + struct device_attribute attr; + char name[MAX_STATUS_NAME+1]; +}; + +static struct status_attr *status_attrs; + +static void set_status_attr(int id) +{ + struct status_attr *status; + + status = status_attrs + id; + if (id == 0) + strscpy(status->name, "status"); + else + snprintf(status->name, MAX_STATUS_NAME+1, "status.%d", id); + status->attr.attr.name = status->name; + status->attr.attr.mode = S_IRUGO; + status->attr.show = status_show; + sysfs_attr_init(&status->attr.attr); +} + +static int init_status_attrs(void) +{ + int id; + + status_attrs = kzalloc_objs(struct status_attr, vhci_num_controllers); + if (status_attrs == NULL) + return -ENOMEM; + + for (id = 0; id < vhci_num_controllers; id++) + set_status_attr(id); + + return 0; +} + +static void finish_status_attrs(void) +{ + kfree(status_attrs); +} + +struct attribute_group vhci_attr_group = { + .attrs = NULL, +}; + +int vhci_init_attr_group(void) +{ + struct attribute **attrs; + int ret, i; + + attrs = kzalloc_objs(struct attribute *, (vhci_num_controllers + 5)); + if (attrs == NULL) + return -ENOMEM; + + ret = init_status_attrs(); + if (ret) { + kfree(attrs); + return ret; + } + *attrs = &dev_attr_nports.attr; + *(attrs + 1) = &dev_attr_detach.attr; + *(attrs + 2) = &dev_attr_attach.attr; + *(attrs + 3) = &dev_attr_usbip_debug.attr; + for (i = 0; i < vhci_num_controllers; i++) + *(attrs + i + 4) = &((status_attrs + i)->attr.attr); + vhci_attr_group.attrs = attrs; + return 0; +} + +void vhci_finish_attr_group(void) +{ + finish_status_attrs(); + kfree(vhci_attr_group.attrs); +} diff --git a/drivers/usb/usbip/vhci_tx.c b/drivers/usb/usbip/vhci_tx.c new file mode 100644 index 000000000..32e6fabcc --- /dev/null +++ b/drivers/usb/usbip/vhci_tx.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + */ + +#include +#include +#include + +#include "usbip_common.h" +#include "vhci.h" + +static void setup_cmd_submit_pdu(struct usbip_header *pdup, struct urb *urb) +{ + struct vhci_priv *priv = ((struct vhci_priv *)urb->hcpriv); + struct vhci_device *vdev = priv->vdev; + + usbip_dbg_vhci_tx("URB, local devnum %u, remote devid %u\n", + usb_pipedevice(urb->pipe), vdev->devid); + + pdup->base.command = USBIP_CMD_SUBMIT; + pdup->base.seqnum = priv->seqnum; + pdup->base.devid = vdev->devid; + pdup->base.direction = usb_pipein(urb->pipe) ? + USBIP_DIR_IN : USBIP_DIR_OUT; + pdup->base.ep = usb_pipeendpoint(urb->pipe); + + usbip_pack_pdu(pdup, urb, USBIP_CMD_SUBMIT, 1); + + if (urb->setup_packet) + memcpy(pdup->u.cmd_submit.setup, urb->setup_packet, 8); +} + +static struct vhci_priv *dequeue_from_priv_tx(struct vhci_device *vdev) +{ + struct vhci_priv *priv, *tmp; + unsigned long flags; + + spin_lock_irqsave(&vdev->priv_lock, flags); + + list_for_each_entry_safe(priv, tmp, &vdev->priv_tx, list) { + list_move_tail(&priv->list, &vdev->priv_rx); + spin_unlock_irqrestore(&vdev->priv_lock, flags); + return priv; + } + + spin_unlock_irqrestore(&vdev->priv_lock, flags); + + return NULL; +} + +static int vhci_send_cmd_submit(struct vhci_device *vdev) +{ + struct usbip_iso_packet_descriptor *iso_buffer = NULL; + struct vhci_priv *priv = NULL; + struct scatterlist *sg; + + struct msghdr msg; + struct kvec *iov; + size_t txsize; + + size_t total_size = 0; + int iovnum; + int err = -ENOMEM; + int i; + + while ((priv = dequeue_from_priv_tx(vdev)) != NULL) { + int ret; + struct urb *urb = priv->urb; + struct usbip_header pdu_header; + + txsize = 0; + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + memset(&iov, 0, sizeof(iov)); + + usbip_dbg_vhci_tx("setup txdata urb seqnum %lu\n", + priv->seqnum); + + if (urb->num_sgs && usb_pipeout(urb->pipe)) + iovnum = 2 + urb->num_sgs; + else + iovnum = 3; + + iov = kzalloc_objs(*iov, iovnum); + if (!iov) { + usbip_event_add(&vdev->ud, SDEV_EVENT_ERROR_MALLOC); + return -ENOMEM; + } + + if (urb->num_sgs) + urb->transfer_flags |= URB_DMA_MAP_SG; + + /* 1. setup usbip_header */ + setup_cmd_submit_pdu(&pdu_header, urb); + usbip_header_correct_endian(&pdu_header, 1); + iovnum = 0; + + iov[iovnum].iov_base = &pdu_header; + iov[iovnum].iov_len = sizeof(pdu_header); + txsize += sizeof(pdu_header); + iovnum++; + + /* 2. setup transfer buffer */ + if (!usb_pipein(urb->pipe) && urb->transfer_buffer_length > 0) { + if (urb->num_sgs && + !usb_endpoint_xfer_isoc(&urb->ep->desc)) { + for_each_sg(urb->sg, sg, urb->num_sgs, i) { + iov[iovnum].iov_base = sg_virt(sg); + iov[iovnum].iov_len = sg->length; + iovnum++; + } + } else { + iov[iovnum].iov_base = urb->transfer_buffer; + iov[iovnum].iov_len = + urb->transfer_buffer_length; + iovnum++; + } + txsize += urb->transfer_buffer_length; + } + + /* 3. setup iso_packet_descriptor */ + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + ssize_t len = 0; + + iso_buffer = usbip_alloc_iso_desc_pdu(urb, &len); + if (!iso_buffer) { + usbip_event_add(&vdev->ud, + SDEV_EVENT_ERROR_MALLOC); + goto err_iso_buffer; + } + + iov[iovnum].iov_base = iso_buffer; + iov[iovnum].iov_len = len; + iovnum++; + txsize += len; + } + + ret = kernel_sendmsg(vdev->ud.tcp_socket, &msg, iov, iovnum, + txsize); + if (ret != txsize) { + pr_err("sendmsg failed!, ret=%d for %zd\n", ret, + txsize); + usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_TCP); + err = -EPIPE; + goto err_tx; + } + + kfree(iov); + /* This is only for isochronous case */ + kfree(iso_buffer); + iso_buffer = NULL; + + usbip_dbg_vhci_tx("send txdata\n"); + + total_size += txsize; + } + + return total_size; + +err_tx: + kfree(iso_buffer); +err_iso_buffer: + kfree(iov); + + return err; +} + +static struct vhci_unlink *dequeue_from_unlink_tx(struct vhci_device *vdev) +{ + struct vhci_unlink *unlink, *tmp; + unsigned long flags; + + spin_lock_irqsave(&vdev->priv_lock, flags); + + list_for_each_entry_safe(unlink, tmp, &vdev->unlink_tx, list) { + list_move_tail(&unlink->list, &vdev->unlink_rx); + spin_unlock_irqrestore(&vdev->priv_lock, flags); + return unlink; + } + + spin_unlock_irqrestore(&vdev->priv_lock, flags); + + return NULL; +} + +static int vhci_send_cmd_unlink(struct vhci_device *vdev) +{ + struct vhci_unlink *unlink = NULL; + + struct msghdr msg; + struct kvec iov; + size_t txsize; + size_t total_size = 0; + + while ((unlink = dequeue_from_unlink_tx(vdev)) != NULL) { + int ret; + struct usbip_header pdu_header; + + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + memset(&iov, 0, sizeof(iov)); + + usbip_dbg_vhci_tx("setup cmd unlink, %lu\n", unlink->seqnum); + + /* 1. setup usbip_header */ + pdu_header.base.command = USBIP_CMD_UNLINK; + pdu_header.base.seqnum = unlink->seqnum; + pdu_header.base.devid = vdev->devid; + pdu_header.base.ep = 0; + pdu_header.u.cmd_unlink.seqnum = unlink->unlink_seqnum; + + usbip_header_correct_endian(&pdu_header, 1); + + iov.iov_base = &pdu_header; + iov.iov_len = sizeof(pdu_header); + txsize = sizeof(pdu_header); + + ret = kernel_sendmsg(vdev->ud.tcp_socket, &msg, &iov, 1, txsize); + if (ret != txsize) { + pr_err("sendmsg failed!, ret=%d for %zd\n", ret, + txsize); + usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_TCP); + return -1; + } + + usbip_dbg_vhci_tx("send txdata\n"); + + total_size += txsize; + } + + return total_size; +} + +int vhci_tx_loop(void *data) +{ + struct usbip_device *ud = data; + struct vhci_device *vdev = container_of(ud, struct vhci_device, ud); + + while (!kthread_should_stop()) { + if (vhci_send_cmd_submit(vdev) < 0) + break; + + if (vhci_send_cmd_unlink(vdev) < 0) + break; + + wait_event_interruptible(vdev->waitq_tx, + (!list_empty(&vdev->priv_tx) || + !list_empty(&vdev->unlink_tx) || + kthread_should_stop())); + + usbip_dbg_vhci_tx("pending urbs ?, now wake up\n"); + } + + return 0; +} diff --git a/drivers/usb/usbip/vudc.h b/drivers/usb/usbip/vudc.h new file mode 100644 index 000000000..5ef0e7d9b --- /dev/null +++ b/drivers/usb/usbip/vudc.h @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Karol Kosik + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski + * Krzysztof Opasiak + */ + +#ifndef __USBIP_VUDC_H +#define __USBIP_VUDC_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbip_common.h" + +#define GADGET_NAME "usbip-vudc" + +struct vep { + struct usb_ep ep; + unsigned type:2; /* type, as USB_ENDPOINT_XFER_* */ + char name[8]; /* space for ep name */ + + const struct usb_endpoint_descriptor *desc; + struct usb_gadget *gadget; + struct list_head req_queue; /* Request queue */ + unsigned halted:1; + unsigned wedged:1; + unsigned already_seen:1; + unsigned setup_stage:1; +}; + +struct vrequest { + struct usb_request req; + struct list_head req_entry; /* Request queue */ +}; + +struct urbp { + struct urb *urb; + struct vep *ep; + struct list_head urb_entry; /* urb queue */ + unsigned long seqnum; + unsigned type:2; /* for tx, since ep type can change after */ + unsigned new:1; +}; + +struct v_unlink { + unsigned long seqnum; + __u32 status; +}; + +enum tx_type { + TX_UNLINK, + TX_SUBMIT, +}; + +struct tx_item { + struct list_head tx_entry; + enum tx_type type; + union { + struct urbp *s; + struct v_unlink *u; + }; +}; + +enum tr_state { + VUDC_TR_RUNNING, + VUDC_TR_IDLE, + VUDC_TR_STOPPED, +}; + +struct transfer_timer { + struct timer_list timer; + enum tr_state state; + unsigned long frame_start; + int frame_limit; +}; + +struct vudc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct platform_device *pdev; + + struct usb_device_descriptor dev_desc; + + struct usbip_device ud; + struct transfer_timer tr_timer; + struct timespec64 start_time; + + struct list_head urb_queue; + + spinlock_t lock_tx; + struct list_head tx_queue; + wait_queue_head_t tx_waitq; + + spinlock_t lock; + struct vep *ep; + int address; + u16 devstatus; + + unsigned pullup:1; + unsigned connected:1; + unsigned desc_cached:1; +}; + +struct vudc_device { + struct platform_device *pdev; + struct list_head dev_entry; +}; + +extern const struct attribute_group *vudc_groups[]; + +/* visible everywhere */ + +static inline struct vep *to_vep(struct usb_ep *_ep) +{ + return container_of(_ep, struct vep, ep); +} + +static inline struct vrequest *to_vrequest( + struct usb_request *_req) +{ + return container_of(_req, struct vrequest, req); +} + +static inline struct vudc *usb_gadget_to_vudc( + struct usb_gadget *_gadget) +{ + return container_of(_gadget, struct vudc, gadget); +} + +static inline struct vudc *ep_to_vudc(struct vep *ep) +{ + return container_of(ep->gadget, struct vudc, gadget); +} + +/* vudc_sysfs.c */ + +int get_gadget_descs(struct vudc *udc); + +/* vudc_tx.c */ + +int v_tx_loop(void *data); +void v_enqueue_ret_unlink(struct vudc *udc, __u32 seqnum, __u32 status); +void v_enqueue_ret_submit(struct vudc *udc, struct urbp *urb_p); + +/* vudc_rx.c */ + +int v_rx_loop(void *data); + +/* vudc_transfer.c */ + +void v_init_timer(struct vudc *udc); +void v_start_timer(struct vudc *udc); +void v_kick_timer(struct vudc *udc, unsigned long time); +void v_stop_timer(struct vudc *udc); + +/* vudc_dev.c */ + +struct urbp *alloc_urbp(void); +void free_urbp_and_urb(struct urbp *urb_p); + +struct vep *vudc_find_endpoint(struct vudc *udc, u8 address); + +struct vudc_device *alloc_vudc_device(int devid); +void put_vudc_device(struct vudc_device *udc_dev); + +int vudc_probe(struct platform_device *pdev); +void vudc_remove(struct platform_device *pdev); + +#endif /* __USBIP_VUDC_H */ diff --git a/drivers/usb/usbip/vudc_dev.c b/drivers/usb/usbip/vudc_dev.c new file mode 100644 index 000000000..5ef881179 --- /dev/null +++ b/drivers/usb/usbip/vudc_dev.c @@ -0,0 +1,637 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Karol Kosik + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski + * Krzysztof Opasiak + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbip_common.h" +#include "vudc.h" + +#define VIRTUAL_ENDPOINTS (1 /* ep0 */ + 15 /* in eps */ + 15 /* out eps */) + +/* urb-related structures alloc / free */ + + +static void free_urb(struct urb *urb) +{ + if (!urb) + return; + + kfree(urb->setup_packet); + urb->setup_packet = NULL; + + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; + + usb_free_urb(urb); +} + +struct urbp *alloc_urbp(void) +{ + struct urbp *urb_p; + + urb_p = kzalloc_obj(*urb_p); + if (!urb_p) + return urb_p; + + urb_p->urb = NULL; + urb_p->ep = NULL; + INIT_LIST_HEAD(&urb_p->urb_entry); + return urb_p; +} + +static void free_urbp(struct urbp *urb_p) +{ + kfree(urb_p); +} + +void free_urbp_and_urb(struct urbp *urb_p) +{ + if (!urb_p) + return; + free_urb(urb_p->urb); + free_urbp(urb_p); +} + + +/* utilities ; almost verbatim from dummy_hcd.c */ + +/* called with spinlock held */ +static void nuke(struct vudc *udc, struct vep *ep) +{ + struct vrequest *req; + + while (!list_empty(&ep->req_queue)) { + req = list_first_entry(&ep->req_queue, struct vrequest, + req_entry); + list_del_init(&req->req_entry); + req->req.status = -ESHUTDOWN; + + spin_unlock(&udc->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&udc->lock); + } +} + +/* caller must hold lock */ +static void stop_activity(struct vudc *udc) +{ + int i; + struct urbp *urb_p, *tmp; + + udc->address = 0; + + for (i = 0; i < VIRTUAL_ENDPOINTS; i++) + nuke(udc, &udc->ep[i]); + + list_for_each_entry_safe(urb_p, tmp, &udc->urb_queue, urb_entry) { + list_del(&urb_p->urb_entry); + free_urbp_and_urb(urb_p); + } +} + +struct vep *vudc_find_endpoint(struct vudc *udc, u8 address) +{ + int i; + + if ((address & ~USB_DIR_IN) == 0) + return &udc->ep[0]; + + for (i = 1; i < VIRTUAL_ENDPOINTS; i++) { + struct vep *ep = &udc->ep[i]; + + if (!ep->desc) + continue; + if (ep->desc->bEndpointAddress == address) + return ep; + } + return NULL; +} + +/* gadget ops */ + +static int vgadget_get_frame(struct usb_gadget *_gadget) +{ + struct timespec64 now; + struct vudc *udc = usb_gadget_to_vudc(_gadget); + + ktime_get_ts64(&now); + return ((now.tv_sec - udc->start_time.tv_sec) * 1000 + + (now.tv_nsec - udc->start_time.tv_nsec) / NSEC_PER_MSEC) + & 0x7FF; +} + +static int vgadget_set_selfpowered(struct usb_gadget *_gadget, int value) +{ + struct vudc *udc = usb_gadget_to_vudc(_gadget); + + if (value) + udc->devstatus |= (1 << USB_DEVICE_SELF_POWERED); + else + udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + return 0; +} + +static int vgadget_pullup(struct usb_gadget *_gadget, int value) +{ + struct vudc *udc = usb_gadget_to_vudc(_gadget); + unsigned long flags; + int ret; + + + spin_lock_irqsave(&udc->lock, flags); + value = !!value; + if (value == udc->pullup) + goto unlock; + + udc->pullup = value; + if (value) { + udc->gadget.speed = min_t(u8, USB_SPEED_HIGH, + udc->driver->max_speed); + udc->ep[0].ep.maxpacket = 64; + /* + * This is the first place where we can ask our + * gadget driver for descriptors. + */ + ret = get_gadget_descs(udc); + if (ret) { + dev_err(&udc->gadget.dev, "Unable go get desc: %d", ret); + goto unlock; + } + + spin_unlock_irqrestore(&udc->lock, flags); + usbip_start_eh(&udc->ud); + } else { + /* Invalidate descriptors */ + udc->desc_cached = 0; + + spin_unlock_irqrestore(&udc->lock, flags); + usbip_event_add(&udc->ud, VUDC_EVENT_REMOVED); + usbip_stop_eh(&udc->ud); /* Wait for eh completion */ + } + + return 0; + +unlock: + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int vgadget_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct vudc *udc = usb_gadget_to_vudc(g); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + udc->driver = driver; + udc->pullup = udc->connected = udc->desc_cached = 0; + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int vgadget_udc_stop(struct usb_gadget *g) +{ + struct vudc *udc = usb_gadget_to_vudc(g); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + udc->driver = NULL; + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static const struct usb_gadget_ops vgadget_ops = { + .get_frame = vgadget_get_frame, + .set_selfpowered = vgadget_set_selfpowered, + .pullup = vgadget_pullup, + .udc_start = vgadget_udc_start, + .udc_stop = vgadget_udc_stop, +}; + + +/* endpoint ops */ + +static int vep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct vep *ep; + struct vudc *udc; + unsigned int maxp; + unsigned long flags; + + ep = to_vep(_ep); + udc = ep_to_vudc(ep); + + if (!_ep || !desc || ep->desc || _ep->caps.type_control + || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + + if (!udc->driver) + return -ESHUTDOWN; + + spin_lock_irqsave(&udc->lock, flags); + + maxp = usb_endpoint_maxp(desc); + _ep->maxpacket = maxp; + ep->desc = desc; + ep->type = usb_endpoint_type(desc); + ep->halted = ep->wedged = 0; + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int vep_disable(struct usb_ep *_ep) +{ + struct vep *ep; + struct vudc *udc; + unsigned long flags; + + ep = to_vep(_ep); + udc = ep_to_vudc(ep); + if (!_ep || !ep->desc || _ep->caps.type_control) + return -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + ep->desc = NULL; + nuke(udc, ep); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static struct usb_request *vep_alloc_request(struct usb_ep *_ep, + gfp_t mem_flags) +{ + struct vrequest *req; + + if (!_ep) + return NULL; + + req = kzalloc_obj(*req, mem_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->req_entry); + + return &req->req; +} + +static void vep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct vrequest *req; + + /* ep is always valid here - see usb_ep_free_request() */ + if (!_req) + return; + + req = to_vrequest(_req); + kfree(req); +} + +static int vep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t mem_flags) +{ + struct vep *ep; + struct vrequest *req; + struct vudc *udc; + unsigned long flags; + + if (!_ep || !_req) + return -EINVAL; + + ep = to_vep(_ep); + req = to_vrequest(_req); + udc = ep_to_vudc(ep); + + spin_lock_irqsave(&udc->lock, flags); + _req->actual = 0; + _req->status = -EINPROGRESS; + + list_add_tail(&req->req_entry, &ep->req_queue); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int vep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct vep *ep; + struct vudc *udc; + struct vrequest *lst; + unsigned long flags; + int ret = -EINVAL; + + if (!_ep || !_req) + return ret; + + ep = to_vep(_ep); + udc = ep_to_vudc(ep); + + if (!udc->driver) + return -ESHUTDOWN; + + spin_lock_irqsave(&udc->lock, flags); + list_for_each_entry(lst, &ep->req_queue, req_entry) { + if (&lst->req == _req) { + list_del_init(&lst->req_entry); + _req->status = -ECONNRESET; + ret = 0; + break; + } + } + spin_unlock_irqrestore(&udc->lock, flags); + + if (ret == 0) + usb_gadget_giveback_request(_ep, _req); + + return ret; +} + +static int +vep_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedged) +{ + struct vep *ep; + struct vudc *udc; + unsigned long flags; + int ret = 0; + + ep = to_vep(_ep); + if (!_ep) + return -EINVAL; + + udc = ep_to_vudc(ep); + if (!udc->driver) + return -ESHUTDOWN; + + spin_lock_irqsave(&udc->lock, flags); + if (!value) + ep->halted = ep->wedged = 0; + else if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) && + !list_empty(&ep->req_queue)) + ret = -EAGAIN; + else { + ep->halted = 1; + if (wedged) + ep->wedged = 1; + } + + spin_unlock_irqrestore(&udc->lock, flags); + return ret; +} + +static int +vep_set_halt(struct usb_ep *_ep, int value) +{ + return vep_set_halt_and_wedge(_ep, value, 0); +} + +static int vep_set_wedge(struct usb_ep *_ep) +{ + return vep_set_halt_and_wedge(_ep, 1, 1); +} + +static const struct usb_ep_ops vep_ops = { + .enable = vep_enable, + .disable = vep_disable, + + .alloc_request = vep_alloc_request, + .free_request = vep_free_request, + + .queue = vep_queue, + .dequeue = vep_dequeue, + + .set_halt = vep_set_halt, + .set_wedge = vep_set_wedge, +}; + + +/* shutdown / reset / error handlers */ + +static void vudc_shutdown(struct usbip_device *ud) +{ + struct vudc *udc = container_of(ud, struct vudc, ud); + int call_disconnect = 0; + unsigned long flags; + + dev_dbg(&udc->pdev->dev, "device shutdown"); + if (ud->tcp_socket) + kernel_sock_shutdown(ud->tcp_socket, SHUT_RDWR); + + if (ud->tcp_rx) { + kthread_stop_put(ud->tcp_rx); + ud->tcp_rx = NULL; + } + if (ud->tcp_tx) { + kthread_stop_put(ud->tcp_tx); + ud->tcp_tx = NULL; + } + + if (ud->tcp_socket) { + sockfd_put(ud->tcp_socket); + ud->tcp_socket = NULL; + } + + spin_lock_irqsave(&udc->lock, flags); + stop_activity(udc); + if (udc->connected && udc->driver->disconnect) + call_disconnect = 1; + udc->connected = 0; + spin_unlock_irqrestore(&udc->lock, flags); + if (call_disconnect) + udc->driver->disconnect(&udc->gadget); +} + +static void vudc_device_reset(struct usbip_device *ud) +{ + struct vudc *udc = container_of(ud, struct vudc, ud); + unsigned long flags; + + dev_dbg(&udc->pdev->dev, "device reset"); + spin_lock_irqsave(&udc->lock, flags); + stop_activity(udc); + spin_unlock_irqrestore(&udc->lock, flags); + if (udc->driver) + usb_gadget_udc_reset(&udc->gadget, udc->driver); + spin_lock_irqsave(&ud->lock, flags); + ud->status = SDEV_ST_AVAILABLE; + spin_unlock_irqrestore(&ud->lock, flags); +} + +static void vudc_device_unusable(struct usbip_device *ud) +{ + unsigned long flags; + + spin_lock_irqsave(&ud->lock, flags); + ud->status = SDEV_ST_ERROR; + spin_unlock_irqrestore(&ud->lock, flags); +} + +/* device setup / cleanup */ + +struct vudc_device *alloc_vudc_device(int devid) +{ + struct vudc_device *udc_dev; + + udc_dev = kzalloc_obj(*udc_dev); + if (!udc_dev) + return NULL; + + INIT_LIST_HEAD(&udc_dev->dev_entry); + + udc_dev->pdev = platform_device_alloc(GADGET_NAME, devid); + if (!udc_dev->pdev) { + kfree(udc_dev); + udc_dev = NULL; + } + + return udc_dev; +} + +void put_vudc_device(struct vudc_device *udc_dev) +{ + platform_device_put(udc_dev->pdev); + kfree(udc_dev); +} + +static int init_vudc_hw(struct vudc *udc) +{ + int i; + struct usbip_device *ud = &udc->ud; + struct vep *ep; + + udc->ep = kzalloc_objs(*udc->ep, VIRTUAL_ENDPOINTS); + if (!udc->ep) + goto nomem_ep; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + /* create ep0 and 15 in, 15 out general purpose eps */ + for (i = 0; i < VIRTUAL_ENDPOINTS; ++i) { + int is_out = i % 2; + int num = (i + 1) / 2; + + ep = &udc->ep[i]; + + sprintf(ep->name, "ep%d%s", num, + i ? (is_out ? "out" : "in") : ""); + ep->ep.name = ep->name; + + ep->ep.ops = &vep_ops; + + usb_ep_set_maxpacket_limit(&ep->ep, ~0); + ep->ep.max_streams = 16; + ep->gadget = &udc->gadget; + INIT_LIST_HEAD(&ep->req_queue); + + if (i == 0) { + /* ep0 */ + ep->ep.caps.type_control = true; + ep->ep.caps.dir_out = true; + ep->ep.caps.dir_in = true; + + udc->gadget.ep0 = &ep->ep; + } else { + /* All other eps */ + ep->ep.caps.type_iso = true; + ep->ep.caps.type_int = true; + ep->ep.caps.type_bulk = true; + + if (is_out) + ep->ep.caps.dir_out = true; + else + ep->ep.caps.dir_in = true; + + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + } + } + + spin_lock_init(&udc->lock); + spin_lock_init(&udc->lock_tx); + INIT_LIST_HEAD(&udc->urb_queue); + INIT_LIST_HEAD(&udc->tx_queue); + init_waitqueue_head(&udc->tx_waitq); + + spin_lock_init(&ud->lock); + mutex_init(&ud->sysfs_lock); + ud->status = SDEV_ST_AVAILABLE; + ud->side = USBIP_VUDC; + + ud->eh_ops.shutdown = vudc_shutdown; + ud->eh_ops.reset = vudc_device_reset; + ud->eh_ops.unusable = vudc_device_unusable; + + v_init_timer(udc); + return 0; + +nomem_ep: + return -ENOMEM; +} + +static void cleanup_vudc_hw(struct vudc *udc) +{ + kfree(udc->ep); +} + +/* platform driver ops */ + +int vudc_probe(struct platform_device *pdev) +{ + struct vudc *udc; + int ret = -ENOMEM; + + udc = kzalloc_obj(*udc); + if (!udc) + goto out; + + udc->gadget.name = GADGET_NAME; + udc->gadget.ops = &vgadget_ops; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->gadget.dev.parent = &pdev->dev; + udc->pdev = pdev; + + ret = init_vudc_hw(udc); + if (ret) + goto err_init_vudc_hw; + + ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget); + if (ret < 0) + goto err_add_udc; + + platform_set_drvdata(pdev, udc); + + return ret; + +err_add_udc: + cleanup_vudc_hw(udc); +err_init_vudc_hw: + kfree(udc); +out: + return ret; +} + +void vudc_remove(struct platform_device *pdev) +{ + struct vudc *udc = platform_get_drvdata(pdev); + + v_stop_timer(udc); + usb_del_gadget_udc(&udc->gadget); + cleanup_vudc_hw(udc); + kfree(udc); +} diff --git a/drivers/usb/usbip/vudc_main.c b/drivers/usb/usbip/vudc_main.c new file mode 100644 index 000000000..993e721cb --- /dev/null +++ b/drivers/usb/usbip/vudc_main.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Karol Kosik + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski + * Krzysztof Opasiak + */ + +#include +#include +#include + +#include "vudc.h" + +static unsigned int vudc_number = 1; + +module_param_named(num, vudc_number, uint, S_IRUGO); +MODULE_PARM_DESC(num, "number of emulated controllers"); + +static struct platform_driver vudc_driver = { + .probe = vudc_probe, + .remove = vudc_remove, + .driver = { + .name = GADGET_NAME, + .dev_groups = vudc_groups, + }, +}; + +static LIST_HEAD(vudc_devices); + +static int __init vudc_init(void) +{ + int retval = -ENOMEM; + int i; + struct vudc_device *udc_dev = NULL, *udc_dev2 = NULL; + + if (usb_disabled()) + return -ENODEV; + + if (vudc_number < 1) { + pr_err("Number of emulated UDC must be no less than 1"); + return -EINVAL; + } + + retval = platform_driver_register(&vudc_driver); + if (retval < 0) + goto out; + + for (i = 0; i < vudc_number; i++) { + udc_dev = alloc_vudc_device(i); + if (!udc_dev) { + retval = -ENOMEM; + goto cleanup; + } + + retval = platform_device_add(udc_dev->pdev); + if (retval < 0) { + put_vudc_device(udc_dev); + goto cleanup; + } + + list_add_tail(&udc_dev->dev_entry, &vudc_devices); + if (!platform_get_drvdata(udc_dev->pdev)) { + /* + * The udc was added successfully but its probe + * function failed for some reason. + */ + retval = -EINVAL; + goto cleanup; + } + } + goto out; + +cleanup: + list_for_each_entry_safe(udc_dev, udc_dev2, &vudc_devices, dev_entry) { + list_del(&udc_dev->dev_entry); + /* + * Just do platform_device_del() here, put_vudc_device() + * calls the platform_device_put() + */ + platform_device_del(udc_dev->pdev); + put_vudc_device(udc_dev); + } + + platform_driver_unregister(&vudc_driver); +out: + return retval; +} +module_init(vudc_init); + +static void __exit vudc_cleanup(void) +{ + struct vudc_device *udc_dev = NULL, *udc_dev2 = NULL; + + list_for_each_entry_safe(udc_dev, udc_dev2, &vudc_devices, dev_entry) { + list_del(&udc_dev->dev_entry); + /* + * Just do platform_device_del() here, put_vudc_device() + * calls the platform_device_put() + */ + platform_device_del(udc_dev->pdev); + put_vudc_device(udc_dev); + } + platform_driver_unregister(&vudc_driver); +} +module_exit(vudc_cleanup); + +MODULE_DESCRIPTION("USB over IP Device Controller"); +MODULE_AUTHOR("Krzysztof Opasiak, Karol Kosik, Igor Kotrasinski"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/usbip/vudc_rx.c b/drivers/usb/usbip/vudc_rx.c new file mode 100644 index 000000000..51bb70837 --- /dev/null +++ b/drivers/usb/usbip/vudc_rx.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Karol Kosik + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski + */ + +#include +#include +#include + +#include "usbip_common.h" +#include "vudc.h" + +static int alloc_urb_from_cmd(struct urb **urbp, + struct usbip_header *pdu, u8 type) +{ + struct urb *urb; + + if (type == USB_ENDPOINT_XFER_ISOC) + urb = usb_alloc_urb(pdu->u.cmd_submit.number_of_packets, + GFP_KERNEL); + else + urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!urb) + goto err; + + usbip_pack_pdu(pdu, urb, USBIP_CMD_SUBMIT, 0); + + if (urb->transfer_buffer_length > 0) { + urb->transfer_buffer = kzalloc(urb->transfer_buffer_length, + GFP_KERNEL); + if (!urb->transfer_buffer) + goto free_urb; + } + + urb->setup_packet = kmemdup(&pdu->u.cmd_submit.setup, 8, + GFP_KERNEL); + if (!urb->setup_packet) + goto free_buffer; + + /* + * FIXME - we only setup pipe enough for usbip functions + * to behave nicely + */ + urb->pipe |= pdu->base.direction == USBIP_DIR_IN ? + USB_DIR_IN : USB_DIR_OUT; + + *urbp = urb; + return 0; + +free_buffer: + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; +free_urb: + usb_free_urb(urb); +err: + return -ENOMEM; +} + +static int v_recv_cmd_unlink(struct vudc *udc, + struct usbip_header *pdu) +{ + unsigned long flags; + struct urbp *urb_p; + + spin_lock_irqsave(&udc->lock, flags); + list_for_each_entry(urb_p, &udc->urb_queue, urb_entry) { + if (urb_p->seqnum != pdu->u.cmd_unlink.seqnum) + continue; + urb_p->urb->unlinked = -ECONNRESET; + urb_p->seqnum = pdu->base.seqnum; + v_kick_timer(udc, jiffies); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; + } + /* Not found, completed / not queued */ + spin_lock(&udc->lock_tx); + v_enqueue_ret_unlink(udc, pdu->base.seqnum, 0); + wake_up(&udc->tx_waitq); + spin_unlock(&udc->lock_tx); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int v_recv_cmd_submit(struct vudc *udc, + struct usbip_header *pdu) +{ + int ret = 0; + struct urbp *urb_p; + u8 address; + unsigned long flags; + + urb_p = alloc_urbp(); + if (!urb_p) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_MALLOC); + return -ENOMEM; + } + + /* base.ep is pipeendpoint(pipe) */ + address = pdu->base.ep; + if (pdu->base.direction == USBIP_DIR_IN) + address |= USB_DIR_IN; + + spin_lock_irqsave(&udc->lock, flags); + urb_p->ep = vudc_find_endpoint(udc, address); + if (!urb_p->ep) { + /* we don't know the type, there may be isoc data! */ + dev_err(&udc->pdev->dev, "request to nonexistent endpoint"); + spin_unlock_irqrestore(&udc->lock, flags); + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP); + ret = -EPIPE; + goto free_urbp; + } + urb_p->type = urb_p->ep->type; + spin_unlock_irqrestore(&udc->lock, flags); + + urb_p->new = 1; + urb_p->seqnum = pdu->base.seqnum; + + if (urb_p->ep->type == USB_ENDPOINT_XFER_ISOC) { + /* validate packet size and number of packets */ + unsigned int maxp, packets, bytes; + + maxp = usb_endpoint_maxp(urb_p->ep->desc); + maxp *= usb_endpoint_maxp_mult(urb_p->ep->desc); + bytes = pdu->u.cmd_submit.transfer_buffer_length; + packets = DIV_ROUND_UP(bytes, maxp); + + if (pdu->u.cmd_submit.number_of_packets < 0 || + pdu->u.cmd_submit.number_of_packets > packets) { + dev_err(&udc->gadget.dev, + "CMD_SUBMIT: isoc invalid num packets %d\n", + pdu->u.cmd_submit.number_of_packets); + ret = -EMSGSIZE; + goto free_urbp; + } + } + + ret = alloc_urb_from_cmd(&urb_p->urb, pdu, urb_p->ep->type); + if (ret) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_MALLOC); + ret = -ENOMEM; + goto free_urbp; + } + + urb_p->urb->status = -EINPROGRESS; + + /* FIXME: more pipe setup to please usbip_common */ + BUILD_BUG_ON_MSG(PIPE_BULK != 3, "PIPE_* doesn't range from 0 to 3"); + + urb_p->urb->pipe &= ~(PIPE_BULK << 30); + switch (urb_p->ep->type) { + case USB_ENDPOINT_XFER_BULK: + urb_p->urb->pipe |= (PIPE_BULK << 30); + break; + case USB_ENDPOINT_XFER_INT: + urb_p->urb->pipe |= (PIPE_INTERRUPT << 30); + break; + case USB_ENDPOINT_XFER_CONTROL: + urb_p->urb->pipe |= (PIPE_CONTROL << 30); + break; + case USB_ENDPOINT_XFER_ISOC: + urb_p->urb->pipe |= (PIPE_ISOCHRONOUS << 30); + break; + } + ret = usbip_recv_xbuff(&udc->ud, urb_p->urb); + if (ret < 0) + goto free_urbp; + + ret = usbip_recv_iso(&udc->ud, urb_p->urb); + if (ret < 0) + goto free_urbp; + + spin_lock_irqsave(&udc->lock, flags); + v_kick_timer(udc, jiffies); + list_add_tail(&urb_p->urb_entry, &udc->urb_queue); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; + +free_urbp: + free_urbp_and_urb(urb_p); + return ret; +} + +static int v_rx_pdu(struct usbip_device *ud) +{ + int ret; + struct usbip_header pdu; + struct vudc *udc = container_of(ud, struct vudc, ud); + + memset(&pdu, 0, sizeof(pdu)); + ret = usbip_recv(ud->tcp_socket, &pdu, sizeof(pdu)); + if (ret != sizeof(pdu)) { + usbip_event_add(ud, VUDC_EVENT_ERROR_TCP); + if (ret >= 0) + return -EPIPE; + return ret; + } + usbip_header_correct_endian(&pdu, 0); + + spin_lock_irq(&ud->lock); + ret = (ud->status == SDEV_ST_USED); + spin_unlock_irq(&ud->lock); + if (!ret) { + usbip_event_add(ud, VUDC_EVENT_ERROR_TCP); + return -EBUSY; + } + + switch (pdu.base.command) { + case USBIP_CMD_UNLINK: + ret = v_recv_cmd_unlink(udc, &pdu); + break; + case USBIP_CMD_SUBMIT: + ret = v_recv_cmd_submit(udc, &pdu); + break; + default: + ret = -EPIPE; + pr_err("rx: unknown command"); + break; + } + return ret; +} + +int v_rx_loop(void *data) +{ + struct usbip_device *ud = data; + int ret = 0; + + while (!kthread_should_stop()) { + if (usbip_event_happened(ud)) + break; + ret = v_rx_pdu(ud); + if (ret < 0) { + pr_warn("v_rx exit with error %d", ret); + break; + } + } + return ret; +} diff --git a/drivers/usb/usbip/vudc_sysfs.c b/drivers/usb/usbip/vudc_sysfs.c new file mode 100644 index 000000000..47f3a7d51 --- /dev/null +++ b/drivers/usb/usbip/vudc_sysfs.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Karol Kosik + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski + * Krzysztof Opasiak + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "usbip_common.h" +#include "vudc.h" + +#include + +/* called with udc->lock held */ +int get_gadget_descs(struct vudc *udc) +{ + struct vrequest *usb_req; + struct vep *ep0 = to_vep(udc->gadget.ep0); + struct usb_device_descriptor *ddesc = &udc->dev_desc; + struct usb_ctrlrequest req; + int ret; + + if (!udc->driver || !udc->pullup) + return -EINVAL; + + req.bRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE; + req.bRequest = USB_REQ_GET_DESCRIPTOR; + req.wValue = cpu_to_le16(USB_DT_DEVICE << 8); + req.wIndex = cpu_to_le16(0); + req.wLength = cpu_to_le16(sizeof(*ddesc)); + + spin_unlock(&udc->lock); + ret = udc->driver->setup(&(udc->gadget), &req); + spin_lock(&udc->lock); + if (ret < 0) + goto out; + + /* assuming request queue is empty; request is now on top */ + usb_req = list_last_entry(&ep0->req_queue, struct vrequest, req_entry); + list_del(&usb_req->req_entry); + + if (usb_req->req.length > sizeof(*ddesc)) { + ret = -EOVERFLOW; + goto giveback_req; + } + + memcpy(ddesc, usb_req->req.buf, sizeof(*ddesc)); + udc->desc_cached = 1; + ret = 0; +giveback_req: + usb_req->req.status = 0; + usb_req->req.actual = usb_req->req.length; + usb_gadget_giveback_request(&(ep0->ep), &(usb_req->req)); +out: + return ret; +} + +/* + * Exposes device descriptor from the gadget driver. + */ +static ssize_t dev_desc_read(struct file *file, struct kobject *kobj, + const struct bin_attribute *attr, char *out, + loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct vudc *udc = (struct vudc *)dev_get_drvdata(dev); + char *desc_ptr = (char *) &udc->dev_desc; + unsigned long flags; + int ret; + + spin_lock_irqsave(&udc->lock, flags); + if (!udc->desc_cached) { + ret = -ENODEV; + goto unlock; + } + + memcpy(out, desc_ptr + off, count); + ret = count; +unlock: + spin_unlock_irqrestore(&udc->lock, flags); + return ret; +} +static const BIN_ATTR_RO(dev_desc, sizeof(struct usb_device_descriptor)); + +static ssize_t usbip_sockfd_store(struct device *dev, + struct device_attribute *attr, + const char *in, size_t count) +{ + struct vudc *udc = (struct vudc *) dev_get_drvdata(dev); + int rv; + int sockfd = 0; + int err; + struct socket *socket; + unsigned long flags; + int ret; + struct task_struct *tcp_rx = NULL; + struct task_struct *tcp_tx = NULL; + + rv = kstrtoint(in, 0, &sockfd); + if (rv != 0) + return -EINVAL; + + if (!udc) { + dev_err(dev, "no device"); + return -ENODEV; + } + mutex_lock(&udc->ud.sysfs_lock); + spin_lock_irqsave(&udc->lock, flags); + /* Don't export what we don't have */ + if (!udc->driver || !udc->pullup) { + dev_err(dev, "gadget not bound"); + ret = -ENODEV; + goto unlock; + } + + if (sockfd != -1) { + if (udc->connected) { + dev_err(dev, "Device already connected"); + ret = -EBUSY; + goto unlock; + } + + spin_lock(&udc->ud.lock); + + if (udc->ud.status != SDEV_ST_AVAILABLE) { + ret = -EINVAL; + goto unlock_ud; + } + + socket = sockfd_lookup(sockfd, &err); + if (!socket) { + dev_err(dev, "failed to lookup sock"); + ret = -EINVAL; + goto unlock_ud; + } + + if (socket->type != SOCK_STREAM) { + dev_err(dev, "Expecting SOCK_STREAM - found %d", + socket->type); + ret = -EINVAL; + goto sock_err; + } + + /* unlock and create threads and get tasks */ + spin_unlock(&udc->ud.lock); + spin_unlock_irqrestore(&udc->lock, flags); + + tcp_rx = kthread_create(&v_rx_loop, &udc->ud, "vudc_rx"); + if (IS_ERR(tcp_rx)) { + sockfd_put(socket); + mutex_unlock(&udc->ud.sysfs_lock); + return -EINVAL; + } + tcp_tx = kthread_create(&v_tx_loop, &udc->ud, "vudc_tx"); + if (IS_ERR(tcp_tx)) { + kthread_stop(tcp_rx); + sockfd_put(socket); + mutex_unlock(&udc->ud.sysfs_lock); + return -EINVAL; + } + + /* get task structs now */ + get_task_struct(tcp_rx); + get_task_struct(tcp_tx); + + /* lock and update udc->ud state */ + spin_lock_irqsave(&udc->lock, flags); + spin_lock(&udc->ud.lock); + + udc->ud.tcp_socket = socket; + udc->ud.tcp_rx = tcp_rx; + udc->ud.tcp_tx = tcp_tx; + udc->ud.status = SDEV_ST_USED; + + spin_unlock(&udc->ud.lock); + + ktime_get_ts64(&udc->start_time); + v_start_timer(udc); + udc->connected = 1; + + spin_unlock_irqrestore(&udc->lock, flags); + + wake_up_process(udc->ud.tcp_rx); + wake_up_process(udc->ud.tcp_tx); + + mutex_unlock(&udc->ud.sysfs_lock); + return count; + + } else { + if (!udc->connected) { + dev_err(dev, "Device not connected"); + ret = -EINVAL; + goto unlock; + } + + spin_lock(&udc->ud.lock); + if (udc->ud.status != SDEV_ST_USED) { + ret = -EINVAL; + goto unlock_ud; + } + spin_unlock(&udc->ud.lock); + + usbip_event_add(&udc->ud, VUDC_EVENT_DOWN); + } + + spin_unlock_irqrestore(&udc->lock, flags); + mutex_unlock(&udc->ud.sysfs_lock); + + return count; + +sock_err: + sockfd_put(socket); +unlock_ud: + spin_unlock(&udc->ud.lock); +unlock: + spin_unlock_irqrestore(&udc->lock, flags); + mutex_unlock(&udc->ud.sysfs_lock); + + return ret; +} +static DEVICE_ATTR_WO(usbip_sockfd); + +static ssize_t usbip_status_show(struct device *dev, + struct device_attribute *attr, char *out) +{ + struct vudc *udc = (struct vudc *) dev_get_drvdata(dev); + int status; + + if (!udc) { + dev_err(dev, "no device"); + return -ENODEV; + } + spin_lock_irq(&udc->ud.lock); + status = udc->ud.status; + spin_unlock_irq(&udc->ud.lock); + + return sysfs_emit(out, "%d\n", status); +} +static DEVICE_ATTR_RO(usbip_status); + +static struct attribute *dev_attrs[] = { + &dev_attr_usbip_sockfd.attr, + &dev_attr_usbip_status.attr, + NULL, +}; + +static const struct bin_attribute *const dev_bin_attrs[] = { + &bin_attr_dev_desc, + NULL, +}; + +static const struct attribute_group vudc_attr_group = { + .attrs = dev_attrs, + .bin_attrs = dev_bin_attrs, +}; + +const struct attribute_group *vudc_groups[] = { + &vudc_attr_group, + NULL, +}; diff --git a/drivers/usb/usbip/vudc_transfer.c b/drivers/usb/usbip/vudc_transfer.c new file mode 100644 index 000000000..d4ce85c4c --- /dev/null +++ b/drivers/usb/usbip/vudc_transfer.c @@ -0,0 +1,497 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Karol Kosik + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski + * + * Based on dummy_hcd.c, which is: + * Copyright (C) 2003 David Brownell + * Copyright (C) 2003-2005 Alan Stern + */ + +#include +#include +#include + +#include "vudc.h" + +#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) + +static int get_frame_limit(enum usb_device_speed speed) +{ + switch (speed) { + case USB_SPEED_LOW: + return 8 /*bytes*/ * 12 /*packets*/; + case USB_SPEED_FULL: + return 64 /*bytes*/ * 19 /*packets*/; + case USB_SPEED_HIGH: + return 512 /*bytes*/ * 13 /*packets*/ * 8 /*uframes*/; + case USB_SPEED_SUPER: + /* Bus speed is 500000 bytes/ms, so use a little less */ + return 490000; + default: + /* error */ + return -1; + } + +} + +/* + * handle_control_request() - handles all control transfers + * @udc: pointer to vudc + * @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 + * + * Adapted from drivers/usb/gadget/udc/dummy_hcd.c + */ +static int handle_control_request(struct vudc *udc, struct urb *urb, + struct usb_ctrlrequest *setup, + int *status) +{ + struct vep *ep2; + int ret_val = 1; + unsigned int w_index; + unsigned int 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; + udc->address = w_value; + ret_val = 0; + *status = 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: + udc->gadget.b_hnp_enable = 1; + break; + case USB_DEVICE_A_HNP_SUPPORT: + udc->gadget.a_hnp_support = 1; + break; + case USB_DEVICE_A_ALT_HNP_SUPPORT: + udc->gadget.a_alt_hnp_support = 1; + break; + default: + ret_val = -EOPNOTSUPP; + } + if (ret_val == 0) { + udc->devstatus |= (1 << w_value); + *status = 0; + } + } else if (setup->bRequestType == EP_REQUEST) { + /* endpoint halt */ + ep2 = vudc_find_endpoint(udc, w_index); + if (!ep2 || ep2->ep.name == udc->ep[0].ep.name) { + 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: + case USB_DEVICE_U2_ENABLE: + case USB_DEVICE_LTM_ENABLE: + ret_val = -EOPNOTSUPP; + break; + default: + ret_val = -EOPNOTSUPP; + break; + } + if (ret_val == 0) { + udc->devstatus &= ~(1 << w_value); + *status = 0; + } + } else if (setup->bRequestType == EP_REQUEST) { + /* endpoint halt */ + ep2 = vudc_find_endpoint(udc, 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 = vudc_find_endpoint(udc, w_index); + if (!ep2) { + ret_val = -EOPNOTSUPP; + break; + } + buf[0] = ep2->halted; + } else if (setup->bRequestType == + DEV_INREQUEST) { + buf[0] = (u8)udc->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; +} + +/* Adapted from dummy_hcd.c ; caller must hold lock */ +static int transfer(struct vudc *udc, + struct urb *urb, struct vep *ep, int limit) +{ + struct vrequest *req; + int sent = 0; +top: + /* if there's no request queued, the device is NAKing; return */ + list_for_each_entry(req, &ep->req_queue, req_entry) { + unsigned int host_len, dev_len, len; + void *ubuf_pos, *rbuf_pos; + int is_short, to_host; + int rescan = 0; + + /* + * 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); + + to_host = usb_pipein(urb->pipe); + if (unlikely(len == 0)) + is_short = 1; + else { + /* send multiple of maxpacket first, then remainder */ + if (len >= ep->ep.maxpacket) { + is_short = 0; + if (len % ep->ep.maxpacket > 0) + rescan = 1; + len -= len % ep->ep.maxpacket; + } else { + is_short = 1; + } + + ubuf_pos = urb->transfer_buffer + urb->actual_length; + rbuf_pos = req->req.buf + req->req.actual; + + if (urb->pipe & USB_DIR_IN) + memcpy(ubuf_pos, rbuf_pos, len); + else + memcpy(rbuf_pos, ubuf_pos, len); + + urb->actual_length += len; + req->req.actual += len; + sent += 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; + urb->status = 0; + } else if (to_host) { + req->req.status = 0; + if (dev_len > host_len) + urb->status = -EOVERFLOW; + else + urb->status = 0; + } else { + urb->status = 0; + if (host_len > dev_len) + req->req.status = -EOVERFLOW; + else + req->req.status = 0; + } + + /* many requests terminate without a short packet */ + /* also check if we need to send zlp */ + } else { + if (req->req.length == req->req.actual) { + if (req->req.zero && to_host) + rescan = 1; + else + req->req.status = 0; + } + if (urb->transfer_buffer_length == urb->actual_length) { + if (urb->transfer_flags & URB_ZERO_PACKET && + !to_host) + rescan = 1; + else + urb->status = 0; + } + } + + /* device side completion --> continuable */ + if (req->req.status != -EINPROGRESS) { + + list_del_init(&req->req_entry); + spin_unlock(&udc->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&udc->lock); + + /* requests might have been unlinked... */ + rescan = 1; + } + + /* host side completion --> terminate */ + if (urb->status != -EINPROGRESS) + break; + + /* rescan to continue with any other queued i/o */ + if (rescan) + goto top; + } + return sent; +} + +static void v_timer(struct timer_list *t) +{ + struct vudc *udc = timer_container_of(udc, t, tr_timer.timer); + struct transfer_timer *timer = &udc->tr_timer; + struct urbp *urb_p, *tmp; + unsigned long flags; + struct usb_ep *_ep; + struct vep *ep; + int ret = 0; + int total, limit; + + spin_lock_irqsave(&udc->lock, flags); + + total = get_frame_limit(udc->gadget.speed); + if (total < 0) { /* unknown speed, or not set yet */ + timer->state = VUDC_TR_IDLE; + spin_unlock_irqrestore(&udc->lock, flags); + return; + } + /* is it next frame now? */ + if (time_after(jiffies, timer->frame_start + msecs_to_jiffies(1))) { + timer->frame_limit = total; + /* FIXME: how to make it accurate? */ + timer->frame_start = jiffies; + } else { + total = timer->frame_limit; + } + + /* We have to clear ep0 flags separately as it's not on the list */ + udc->ep[0].already_seen = 0; + list_for_each_entry(_ep, &udc->gadget.ep_list, ep_list) { + ep = to_vep(_ep); + ep->already_seen = 0; + } + +restart: + list_for_each_entry_safe(urb_p, tmp, &udc->urb_queue, urb_entry) { + struct urb *urb = urb_p->urb; + + ep = urb_p->ep; + if (urb->unlinked) + goto return_urb; + if (timer->state != VUDC_TR_RUNNING) + continue; + + if (!ep) { + urb->status = -EPROTO; + goto return_urb; + } + + /* Used up bandwidth? */ + if (total <= 0 && ep->type == USB_ENDPOINT_XFER_BULK) + continue; + + if (ep->already_seen) + continue; + ep->already_seen = 1; + if (ep == &udc->ep[0] && urb_p->new) { + ep->setup_stage = 1; + urb_p->new = 0; + } + if (ep->halted && !ep->setup_stage) { + urb->status = -EPIPE; + goto return_urb; + } + + if (ep == &udc->ep[0] && ep->setup_stage) { + /* TODO - flush any stale requests */ + ep->setup_stage = 0; + ep->halted = 0; + + ret = handle_control_request(udc, urb, + (struct usb_ctrlrequest *) urb->setup_packet, + (&urb->status)); + if (ret > 0) { + spin_unlock(&udc->lock); + ret = udc->driver->setup(&udc->gadget, + (struct usb_ctrlrequest *) + urb->setup_packet); + spin_lock(&udc->lock); + } + if (ret >= 0) { + /* no delays (max 64kb data stage) */ + limit = 64 * 1024; + goto treat_control_like_bulk; + } else { + urb->status = -EPIPE; + urb->actual_length = 0; + goto return_urb; + } + } + + limit = total; + switch (ep->type) { + case USB_ENDPOINT_XFER_ISOC: + /* TODO: support */ + urb->status = -EXDEV; + break; + + case USB_ENDPOINT_XFER_INT: + /* + * TODO: figure out bandwidth guarantees + * for now, give unlimited bandwidth + */ + limit += urb->transfer_buffer_length; + fallthrough; + default: +treat_control_like_bulk: + total -= transfer(udc, urb, ep, limit); + } + if (urb->status == -EINPROGRESS) + continue; + +return_urb: + if (ep) + ep->already_seen = ep->setup_stage = 0; + + spin_lock(&udc->lock_tx); + list_del(&urb_p->urb_entry); + if (!urb->unlinked) { + v_enqueue_ret_submit(udc, urb_p); + } else { + v_enqueue_ret_unlink(udc, urb_p->seqnum, + urb->unlinked); + free_urbp_and_urb(urb_p); + } + wake_up(&udc->tx_waitq); + spin_unlock(&udc->lock_tx); + + goto restart; + } + + /* TODO - also wait on empty usb_request queues? */ + if (list_empty(&udc->urb_queue)) + timer->state = VUDC_TR_IDLE; + else + mod_timer(&timer->timer, + timer->frame_start + msecs_to_jiffies(1)); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +/* All timer functions are run with udc->lock held */ + +void v_init_timer(struct vudc *udc) +{ + struct transfer_timer *t = &udc->tr_timer; + + timer_setup(&t->timer, v_timer, 0); + t->state = VUDC_TR_STOPPED; +} + +void v_start_timer(struct vudc *udc) +{ + struct transfer_timer *t = &udc->tr_timer; + + dev_dbg(&udc->pdev->dev, "timer start"); + switch (t->state) { + case VUDC_TR_RUNNING: + return; + case VUDC_TR_IDLE: + return v_kick_timer(udc, jiffies); + case VUDC_TR_STOPPED: + t->state = VUDC_TR_IDLE; + t->frame_start = jiffies; + t->frame_limit = get_frame_limit(udc->gadget.speed); + return v_kick_timer(udc, jiffies); + } +} + +void v_kick_timer(struct vudc *udc, unsigned long time) +{ + struct transfer_timer *t = &udc->tr_timer; + + dev_dbg(&udc->pdev->dev, "timer kick"); + switch (t->state) { + case VUDC_TR_RUNNING: + return; + case VUDC_TR_IDLE: + t->state = VUDC_TR_RUNNING; + fallthrough; + case VUDC_TR_STOPPED: + /* we may want to kick timer to unqueue urbs */ + mod_timer(&t->timer, time); + } +} + +void v_stop_timer(struct vudc *udc) +{ + struct transfer_timer *t = &udc->tr_timer; + + /* Delete the timer synchronously before teardown frees udc. */ + dev_dbg(&udc->pdev->dev, "timer stop"); + timer_delete_sync(&t->timer); + t->state = VUDC_TR_STOPPED; +} diff --git a/drivers/usb/usbip/vudc_tx.c b/drivers/usb/usbip/vudc_tx.c new file mode 100644 index 000000000..88edfbcc0 --- /dev/null +++ b/drivers/usb/usbip/vudc_tx.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Karol Kosik + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski + */ + +#include +#include +#include + +#include "usbip_common.h" +#include "vudc.h" + +static inline void setup_base_pdu(struct usbip_header_basic *base, + __u32 command, __u32 seqnum) +{ + base->command = command; + base->seqnum = seqnum; + base->devid = 0; + base->ep = 0; + base->direction = 0; +} + +static void setup_ret_submit_pdu(struct usbip_header *rpdu, struct urbp *urb_p) +{ + setup_base_pdu(&rpdu->base, USBIP_RET_SUBMIT, urb_p->seqnum); + usbip_pack_pdu(rpdu, urb_p->urb, USBIP_RET_SUBMIT, 1); +} + +static void setup_ret_unlink_pdu(struct usbip_header *rpdu, + struct v_unlink *unlink) +{ + setup_base_pdu(&rpdu->base, USBIP_RET_UNLINK, unlink->seqnum); + rpdu->u.ret_unlink.status = unlink->status; +} + +static int v_send_ret_unlink(struct vudc *udc, struct v_unlink *unlink) +{ + struct msghdr msg; + struct kvec iov[1]; + size_t txsize; + + int ret; + struct usbip_header pdu_header; + + txsize = 0; + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + memset(&iov, 0, sizeof(iov)); + + /* 1. setup usbip_header */ + setup_ret_unlink_pdu(&pdu_header, unlink); + usbip_header_correct_endian(&pdu_header, 1); + + iov[0].iov_base = &pdu_header; + iov[0].iov_len = sizeof(pdu_header); + txsize += sizeof(pdu_header); + + ret = kernel_sendmsg(udc->ud.tcp_socket, &msg, iov, + 1, txsize); + if (ret != txsize) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP); + if (ret >= 0) + return -EPIPE; + return ret; + } + kfree(unlink); + + return txsize; +} + +static int v_send_ret_submit(struct vudc *udc, struct urbp *urb_p) +{ + struct urb *urb = urb_p->urb; + struct usbip_header pdu_header; + struct usbip_iso_packet_descriptor *iso_buffer = NULL; + struct kvec *iov = NULL; + int iovnum = 0; + int ret = 0; + size_t txsize; + struct msghdr msg; + + txsize = 0; + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + + if (urb->actual_length > 0 && !urb->transfer_buffer) { + dev_err(&udc->gadget.dev, + "urb: actual_length %d transfer_buffer null\n", + urb->actual_length); + return -1; + } + + if (urb_p->type == USB_ENDPOINT_XFER_ISOC) + iovnum = 2 + urb->number_of_packets; + else + iovnum = 2; + + iov = kzalloc_objs(*iov, iovnum); + if (!iov) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_MALLOC); + ret = -ENOMEM; + goto out; + } + iovnum = 0; + + /* 1. setup usbip_header */ + setup_ret_submit_pdu(&pdu_header, urb_p); + usbip_dbg_stub_tx("setup txdata seqnum: %u\n", + pdu_header.base.seqnum); + usbip_header_correct_endian(&pdu_header, 1); + + iov[iovnum].iov_base = &pdu_header; + iov[iovnum].iov_len = sizeof(pdu_header); + iovnum++; + txsize += sizeof(pdu_header); + + /* 2. setup transfer buffer */ + if (urb_p->type != USB_ENDPOINT_XFER_ISOC && + usb_pipein(urb->pipe) && urb->actual_length > 0) { + iov[iovnum].iov_base = urb->transfer_buffer; + iov[iovnum].iov_len = urb->actual_length; + iovnum++; + txsize += urb->actual_length; + } else if (urb_p->type == USB_ENDPOINT_XFER_ISOC && + usb_pipein(urb->pipe)) { + /* FIXME - copypasted from stub_tx, refactor */ + int i; + + for (i = 0; i < urb->number_of_packets; i++) { + iov[iovnum].iov_base = urb->transfer_buffer + + urb->iso_frame_desc[i].offset; + iov[iovnum].iov_len = + urb->iso_frame_desc[i].actual_length; + iovnum++; + txsize += urb->iso_frame_desc[i].actual_length; + } + + if (txsize != sizeof(pdu_header) + urb->actual_length) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP); + ret = -EPIPE; + goto out; + } + } + /* else - no buffer to send */ + + /* 3. setup iso_packet_descriptor */ + if (urb_p->type == USB_ENDPOINT_XFER_ISOC) { + ssize_t len = 0; + + iso_buffer = usbip_alloc_iso_desc_pdu(urb, &len); + if (!iso_buffer) { + usbip_event_add(&udc->ud, + VUDC_EVENT_ERROR_MALLOC); + ret = -ENOMEM; + goto out; + } + + iov[iovnum].iov_base = iso_buffer; + iov[iovnum].iov_len = len; + txsize += len; + iovnum++; + } + + ret = kernel_sendmsg(udc->ud.tcp_socket, &msg, + iov, iovnum, txsize); + if (ret != txsize) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP); + if (ret >= 0) + ret = -EPIPE; + goto out; + } + +out: + kfree(iov); + kfree(iso_buffer); + free_urbp_and_urb(urb_p); + if (ret < 0) + return ret; + return txsize; +} + +static int v_send_ret(struct vudc *udc) +{ + unsigned long flags; + struct tx_item *txi; + size_t total_size = 0; + int ret = 0; + + spin_lock_irqsave(&udc->lock_tx, flags); + while (!list_empty(&udc->tx_queue)) { + txi = list_first_entry(&udc->tx_queue, struct tx_item, + tx_entry); + list_del(&txi->tx_entry); + spin_unlock_irqrestore(&udc->lock_tx, flags); + + switch (txi->type) { + case TX_SUBMIT: + ret = v_send_ret_submit(udc, txi->s); + break; + case TX_UNLINK: + ret = v_send_ret_unlink(udc, txi->u); + break; + } + kfree(txi); + + if (ret < 0) + return ret; + + total_size += ret; + + spin_lock_irqsave(&udc->lock_tx, flags); + } + + spin_unlock_irqrestore(&udc->lock_tx, flags); + return total_size; +} + + +int v_tx_loop(void *data) +{ + struct usbip_device *ud = (struct usbip_device *) data; + struct vudc *udc = container_of(ud, struct vudc, ud); + int ret; + + while (!kthread_should_stop()) { + if (usbip_event_happened(&udc->ud)) + break; + ret = v_send_ret(udc); + if (ret < 0) { + pr_warn("v_tx exit with error %d", ret); + break; + } + wait_event_interruptible(udc->tx_waitq, + (!list_empty(&udc->tx_queue) || + kthread_should_stop())); + } + + return 0; +} + +/* called with spinlocks held */ +void v_enqueue_ret_unlink(struct vudc *udc, __u32 seqnum, __u32 status) +{ + struct tx_item *txi; + struct v_unlink *unlink; + + txi = kzalloc_obj(*txi, GFP_ATOMIC); + if (!txi) { + usbip_event_add(&udc->ud, VDEV_EVENT_ERROR_MALLOC); + return; + } + unlink = kzalloc_obj(*unlink, GFP_ATOMIC); + if (!unlink) { + kfree(txi); + usbip_event_add(&udc->ud, VDEV_EVENT_ERROR_MALLOC); + return; + } + + unlink->seqnum = seqnum; + unlink->status = status; + txi->type = TX_UNLINK; + txi->u = unlink; + + list_add_tail(&txi->tx_entry, &udc->tx_queue); +} + +/* called with spinlocks held */ +void v_enqueue_ret_submit(struct vudc *udc, struct urbp *urb_p) +{ + struct tx_item *txi; + + txi = kzalloc_obj(*txi, GFP_ATOMIC); + if (!txi) { + usbip_event_add(&udc->ud, VDEV_EVENT_ERROR_MALLOC); + return; + } + + txi->type = TX_SUBMIT; + txi->s = urb_p; + + list_add_tail(&txi->tx_entry, &udc->tx_queue); +} -- cgit v1.3.1